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

Commit 7668a372 authored by Rohit Gupta's avatar Rohit Gupta Committed by Kyle Yan
Browse files

PM / devfreq: memlat: Prevent deadlock with hotplug in start_hwmon



When start_hwmon() runs with another thread trying to hotplug a
CPU the two threads can enter a deadlock situation as follows:

Thread A (start_hwmon())		Thread B (CPU down)
get_online_cpus()
	|
atomic_inc(&cpu_hotplug.refcount)
					     CPU down
						|
				     mutex_lock(&cpu_add_remove_lock)
						|
				     cpu_hotplug_begin() waits on
				     cpu_hotplug.refcount to reset
register_cpu_notifier()
	|
mutex_lock(&cpu_add_remove_lock)

With this change the notifers are registered and unregistered per
device rather than having a common notifier block for all the memlat
devices and unregistration only happens on stop_hwmon. This makes it
possible to move the registration outside the hotplug lock without
any race between multiple memlat devices.

Change-Id: I6ad561fe4967042e45190aea2c9b7fcfe05bafdd
Signed-off-by: default avatarRohit Gupta <rohgup@codeaurora.org>
parent 8b2d1771
Loading
Loading
Loading
Loading
+7 −39
Original line number Diff line number Diff line
/*
 * Copyright (c) 2014-2015, The Linux Foundation. All rights reserved.
 * Copyright (c) 2014-2016, 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
@@ -57,14 +57,9 @@ static DEFINE_PER_CPU(struct memlat_hwmon_data, pm_data);
struct cpu_grp_info {
	cpumask_t cpus;
	struct memlat_hwmon hw;
	struct notifier_block arm_memlat_cpu_notif;
};

static DEFINE_MUTEX(notif_mutex);
static unsigned int notif_refcount;
static void notif_unregister_work_fn(struct work_struct *work);
static void hotplug_notif_unregister(void);
static DECLARE_WORK(notif_unregister_work, notif_unregister_work_fn);

static unsigned long compute_freq(struct memlat_hwmon_data *hw_data,
						unsigned long cyc_cnt)
{
@@ -155,7 +150,7 @@ static void stop_hwmon(struct memlat_hwmon *hw)
	for_each_cpu(cpu, &cpu_grp->cpus) {
		hw_data = &per_cpu(pm_data, cpu);
		if (hw_data->init_pending)
			hotplug_notif_unregister();
			hw_data->init_pending = false;
		else
			delete_events(hw_data);

@@ -167,6 +162,7 @@ static void stop_hwmon(struct memlat_hwmon *hw)
	}
	put_online_cpus();

	unregister_cpu_notifier(&cpu_grp->arm_memlat_cpu_notif);
}

static struct perf_event_attr *alloc_attr(void)
@@ -239,40 +235,10 @@ static int arm_memlat_cpu_callback(struct notifier_block *nb,
		pr_warn("Failed to create perf event for CPU%lu\n", cpu);

	hw_data->init_pending = false;
	hotplug_notif_unregister();

	return NOTIFY_OK;
}

static struct notifier_block arm_memlat_cpu_notifier = {
	.notifier_call = arm_memlat_cpu_callback,
};

static void notif_unregister_work_fn(struct work_struct *work)
{
	unregister_cpu_notifier(&arm_memlat_cpu_notifier);
}

static void hotplug_notif_register(void)
{
	mutex_lock(&notif_mutex);
	if (!notif_refcount) {
		flush_work(&notif_unregister_work);
		register_cpu_notifier(&arm_memlat_cpu_notifier);
	}
	notif_refcount++;
	mutex_unlock(&notif_mutex);
}

static void hotplug_notif_unregister(void)
{
	mutex_lock(&notif_mutex);
	notif_refcount--;
	if (!notif_refcount)
		schedule_work(&notif_unregister_work);
	mutex_unlock(&notif_mutex);
}

static int start_hwmon(struct memlat_hwmon *hw)
{
	int cpu, ret = 0;
@@ -280,13 +246,14 @@ static int start_hwmon(struct memlat_hwmon *hw)
	struct cpu_grp_info *cpu_grp = container_of(hw,
					struct cpu_grp_info, hw);

	register_cpu_notifier(&cpu_grp->arm_memlat_cpu_notif);

	get_online_cpus();
	for_each_cpu(cpu, &cpu_grp->cpus) {
		hw_data = &per_cpu(pm_data, cpu);
		ret = set_events(hw_data, cpu);
		if (ret) {
			if (!cpu_online(cpu)) {
				hotplug_notif_register();
				hw_data->init_pending = true;
				ret = 0;
			} else {
@@ -337,6 +304,7 @@ static int arm_memlat_mon_driver_probe(struct platform_device *pdev)
	cpu_grp = devm_kzalloc(dev, sizeof(*cpu_grp), GFP_KERNEL);
	if (!cpu_grp)
		return -ENOMEM;
	cpu_grp->arm_memlat_cpu_notif.notifier_call = arm_memlat_cpu_callback;
	hw = &cpu_grp->hw;

	hw->dev = dev;