diff options
-rw-r--r-- | drivers/thermal/x86_pkg_temp_thermal.c | 73 |
1 files changed, 52 insertions, 21 deletions
diff --git a/drivers/thermal/x86_pkg_temp_thermal.c b/drivers/thermal/x86_pkg_temp_thermal.c index 91f267a4b1b8..07db08cabc12 100644 --- a/drivers/thermal/x86_pkg_temp_thermal.c +++ b/drivers/thermal/x86_pkg_temp_thermal.c @@ -65,6 +65,7 @@ struct pkg_device { u32 tj_max; u32 msr_pkg_therm_low; u32 msr_pkg_therm_high; + struct delayed_work work; struct thermal_zone_device *tzone; struct cpumask cpumask; }; @@ -80,9 +81,6 @@ static DEFINE_SPINLOCK(pkg_temp_lock); /* Protects zone operation in the work function against hotplug removal */ static DEFINE_MUTEX(thermal_zone_mutex); -/* Interrupt to work function schedule queue */ -static DEFINE_PER_CPU(struct delayed_work, pkg_temp_thermal_threshold_work); - /* Debug counters to show using debugfs */ static struct dentry *debugfs; static unsigned int pkg_interrupt_cnt; @@ -326,6 +324,13 @@ static void pkg_temp_thermal_threshold_work_fn(struct work_struct *work) mutex_unlock(&thermal_zone_mutex); } +static void pkg_thermal_schedule_work(int cpu, struct delayed_work *work) +{ + unsigned long ms = msecs_to_jiffies(notify_delay_ms); + + schedule_delayed_work_on(cpu, work, ms); +} + static int pkg_thermal_notify(u64 msr_val) { int cpu = smp_processor_id(); @@ -341,9 +346,7 @@ static int pkg_thermal_notify(u64 msr_val) pkgdev = pkg_temp_thermal_get_dev(cpu); if (pkgdev && !pkgdev->work_scheduled) { pkgdev->work_scheduled = true; - schedule_delayed_work_on(cpu, - &per_cpu(pkg_temp_thermal_threshold_work, cpu), - msecs_to_jiffies(notify_delay_ms)); + pkg_thermal_schedule_work(pkgdev->cpu, &pkgdev->work); } spin_unlock_irqrestore(&pkg_temp_lock, flags); @@ -374,6 +377,7 @@ static int pkg_temp_thermal_device_add(unsigned int cpu) if (!pkgdev) return -ENOMEM; + INIT_DELAYED_WORK(&pkgdev->work, pkg_temp_thermal_threshold_work_fn); pkgdev->phys_proc_id = topology_physical_package_id(cpu); pkgdev->cpu = cpu; pkgdev->tj_max = tj_max; @@ -401,7 +405,7 @@ static int pkg_temp_thermal_device_add(unsigned int cpu) static void put_core_offline(unsigned int cpu) { struct pkg_device *pkgdev = pkg_temp_thermal_get_dev(cpu); - bool lastcpu; + bool lastcpu, was_target; int target; if (!pkgdev) @@ -430,13 +434,24 @@ static void put_core_offline(unsigned int cpu) thermal_zone_device_unregister(tzone); } + /* Protect against work and interrupts */ + spin_lock_irq(&pkg_temp_lock); + /* - * If this is the last CPU in the package, restore the interrupt - * MSR and remove the package reference from the array. + * Check whether this cpu was the current target and store the new + * one. When we drop the lock, then the interrupt notify function + * will see the new target. + */ + was_target = pkgdev->cpu == cpu; + pkgdev->cpu = target; + + /* + * If this is the last CPU in the package remove the package + * reference from the list and restore the interrupt MSR. When we + * drop the lock neither the interrupt notify function nor the + * worker will see the package anymore. */ if (lastcpu) { - /* Protect against work and interrupts */ - spin_lock_irq(&pkg_temp_lock); list_del(&pkgdev->list); /* * After this point nothing touches the MSR anymore. We @@ -447,17 +462,36 @@ static void put_core_offline(unsigned int cpu) wrmsr_on_cpu(cpu, MSR_IA32_PACKAGE_THERM_INTERRUPT, pkgdev->msr_pkg_therm_low, pkgdev->msr_pkg_therm_high); - kfree(pkgdev); + spin_lock_irq(&pkg_temp_lock); } /* - * Note, this is broken when work was really scheduled on the - * outgoing cpu because this will leave the work_scheduled flag set - * and the thermal interrupts disabled. Will be fixed in the next - * step as there is no way to fix it in a sane way with the per cpu - * work nonsense. + * Check whether there is work scheduled and whether the work is + * targeted at the outgoing CPU. */ - cancel_delayed_work_sync(&per_cpu(pkg_temp_thermal_threshold_work, cpu)); + if (pkgdev->work_scheduled && was_target) { + /* + * To cancel the work we need to drop the lock, otherwise + * we might deadlock if the work needs to be flushed. + */ + spin_unlock_irq(&pkg_temp_lock); + cancel_delayed_work_sync(&pkgdev->work); + spin_lock_irq(&pkg_temp_lock); + /* + * If this is not the last cpu in the package and the work + * did not run after we dropped the lock above, then we + * need to reschedule the work, otherwise the interrupt + * stays disabled forever. + */ + if (!lastcpu && pkgdev->work_scheduled) + pkg_thermal_schedule_work(target, &pkgdev->work); + } + + spin_unlock_irq(&pkg_temp_lock); + + /* Final cleanup if this is the last cpu */ + if (lastcpu) + kfree(pkgdev); } static int get_core_online(unsigned int cpu) @@ -469,9 +503,6 @@ static int get_core_online(unsigned int cpu) if (!cpu_has(c, X86_FEATURE_DTHERM) || !cpu_has(c, X86_FEATURE_PTS)) return -ENODEV; - INIT_DELAYED_WORK(&per_cpu(pkg_temp_thermal_threshold_work, cpu), - pkg_temp_thermal_threshold_work_fn); - /* If the package exists, nothing to do */ if (pkgdev) { cpumask_set_cpu(cpu, &pkgdev->cpumask); |