Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 47f9b50d authored by Ram Chandrasekar's avatar Ram Chandrasekar
Browse files

drivers: thermal: limits-dcvs: Prevent race in freq limits polling



Thermal-core when setting a software cap, will write to the hardware by
making a scm call. The TZ will disable the frequency cap momentarily
before writing the new frequency cap and then re-enable it. During this
scm call transaction, if the polling thread in LMH DCVSh driver reads
the hardware limits, it will show that the CPUs are not throttling and
will enable the interrupt. But when the scm transaction is done, it will
disable the interrupt again.

There is a possibility software frequency cap first decrements the atomic
variable, indicating the interrupt is disabled, and gets context switched.
During this time frame if a hardware interrupt occurs, it can go in a
interrupt storm till the software frequency cap thread actually disables
the interrupt.

To avoid these race conditions protect the LMH DCVSh driver software
frequency write, polling thread and the interrupt handling.

Change-Id: I601bf2c99d6cc6525f3809043330b816bb3e8737
Signed-off-by: default avatarRam Chandrasekar <rkumbako@codeaurora.org>
parent c9738304
Loading
Loading
Loading
Loading
+21 −17
Original line number Diff line number Diff line
/* Copyright (c) 2016-2017, The Linux Foundation. All rights reserved.
/* Copyright (c) 2016-2018, The Linux Foundation. All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 and
@@ -98,13 +98,13 @@ struct limits_dcvs_hw {
	void *int_clr_reg;
	void *min_freq_reg;
	cpumask_t core_map;
	struct timer_list poll_timer;
	struct delayed_work freq_poll_work;
	unsigned long max_freq;
	unsigned long min_freq;
	unsigned long hw_freq_limit;
	struct device_attribute lmh_freq_attr;
	struct list_head list;
	atomic_t is_irq_enabled;
	bool is_irq_enabled;
	struct mutex access_lock;
	struct __limits_cdev_data *cdev_data;
	struct regulator *isens_reg;
@@ -184,33 +184,37 @@ static unsigned long limits_mitigation_notify(struct limits_dcvs_hw *hw)
	return max_limit;
}

static void limits_dcvs_poll(unsigned long data)
static void limits_dcvs_poll(struct work_struct *work)
{
	unsigned long max_limit = 0;
	struct limits_dcvs_hw *hw = (struct limits_dcvs_hw *)data;
	struct limits_dcvs_hw *hw = container_of(work,
					struct limits_dcvs_hw,
					freq_poll_work.work);

	mutex_lock(&hw->access_lock);
	if (hw->max_freq == UINT_MAX)
		limits_dcvs_get_freq_limits(cpumask_first(&hw->core_map),
			&hw->max_freq, &hw->min_freq);
	max_limit = limits_mitigation_notify(hw);
	if (max_limit >= hw->max_freq) {
		del_timer(&hw->poll_timer);
		writel_relaxed(0xFF, hw->int_clr_reg);
		atomic_set(&hw->is_irq_enabled, 1);
		hw->is_irq_enabled = true;
		enable_irq(hw->irq_num);
	} else {
		mod_timer(&hw->poll_timer, jiffies + msecs_to_jiffies(
			LIMITS_POLLING_DELAY_MS));
		mod_delayed_work(system_highpri_wq, &hw->freq_poll_work,
			 msecs_to_jiffies(LIMITS_POLLING_DELAY_MS));
	}
	mutex_unlock(&hw->access_lock);
}

static void lmh_dcvs_notify(struct limits_dcvs_hw *hw)
{
	if (atomic_dec_and_test(&hw->is_irq_enabled)) {
	if (hw->is_irq_enabled) {
		hw->is_irq_enabled = false;
		disable_irq_nosync(hw->irq_num);
		limits_mitigation_notify(hw);
		mod_timer(&hw->poll_timer, jiffies + msecs_to_jiffies(
			LIMITS_POLLING_DELAY_MS));
		mod_delayed_work(system_highpri_wq, &hw->freq_poll_work,
			 msecs_to_jiffies(LIMITS_POLLING_DELAY_MS));
	}
}

@@ -218,7 +222,9 @@ static irqreturn_t lmh_dcvs_handle_isr(int irq, void *data)
{
	struct limits_dcvs_hw *hw = data;

	mutex_lock(&hw->access_lock);
	lmh_dcvs_notify(hw);
	mutex_unlock(&hw->access_lock);

	return IRQ_HANDLED;
}
@@ -373,8 +379,8 @@ static int lmh_set_max_limit(int cpu, u32 freq)
	ret = limits_dcvs_write(hw->affinity, LIMITS_SUB_FN_THERMAL,
				  LIMITS_FREQ_CAP, max_freq,
				  (max_freq == U32_MAX) ? 0 : 1, 1);
	mutex_unlock(&hw->access_lock);
	lmh_dcvs_notify(hw);
	mutex_unlock(&hw->access_lock);

	return ret;
}
@@ -626,9 +632,7 @@ static int limits_dcvs_probe(struct platform_device *pdev)
	}

	mutex_init(&hw->access_lock);
	init_timer_deferrable(&hw->poll_timer);
	hw->poll_timer.data = (unsigned long)hw;
	hw->poll_timer.function = limits_dcvs_poll;
	INIT_DEFERRABLE_WORK(&hw->freq_poll_work, limits_dcvs_poll);
	hw->osm_hw_reg = devm_ioremap(&pdev->dev, request_reg, 0x4);
	if (!hw->osm_hw_reg) {
		pr_err("register remap failed\n");
@@ -645,7 +649,7 @@ static int limits_dcvs_probe(struct platform_device *pdev)
		pr_err("Error getting IRQ number. err:%d\n", hw->irq_num);
		goto probe_exit;
	}
	atomic_set(&hw->is_irq_enabled, 1);
	hw->is_irq_enabled = true;
	ret = devm_request_threaded_irq(&pdev->dev, hw->irq_num, NULL,
		lmh_dcvs_handle_isr, IRQF_TRIGGER_HIGH | IRQF_ONESHOT
		| IRQF_NO_SUSPEND, hw->sensor_name, hw);