alistair23-linux/drivers/cpufreq/cpufreq_userspace.c
Arjan van de Ven 153d7f3fca [PATCH] Reorganize the cpufreq cpu hotplug locking to not be totally bizare
The patch below moves the cpu hotplugging higher up in the cpufreq
layering; this is needed to avoid recursive taking of the cpu hotplug
lock and to otherwise detangle the mess.

The new rules are:
1. you must do lock_cpu_hotplug() around the following functions:
   __cpufreq_driver_target
   __cpufreq_governor (for CPUFREQ_GOV_LIMITS operation only)
   __cpufreq_set_policy
2. governer methods (.governer) must NOT take the lock_cpu_hotplug()
   lock in any way; they are called with the lock taken already
3. if your governer spawns a thread that does things, like calling
   __cpufreq_driver_target, your thread must honor rule #1.
4. the policy lock and other cpufreq internal locks nest within
   the lock_cpu_hotplug() lock.

I'm not entirely happy about how the __cpufreq_governor rule ended up
(conditional locking rule depending on the argument) but basically all
callers pass this as a constant so it's not too horrible.

The patch also removes the cpufreq_governor() function since during the
locking audit it turned out to be entirely unused (so no need to fix it)

The patch works on my testbox, but it could use more testing
(otoh... it can't be much worse than the current code)

Signed-off-by: Arjan van de Ven <arjan@linux.intel.com>
Signed-off-by: Linus Torvalds <torvalds@osdl.org>
2006-07-26 07:21:40 -07:00

214 lines
5.9 KiB
C

/*
* linux/drivers/cpufreq/cpufreq_userspace.c
*
* Copyright (C) 2001 Russell King
* (C) 2002 - 2004 Dominik Brodowski <linux@brodo.de>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/smp.h>
#include <linux/init.h>
#include <linux/spinlock.h>
#include <linux/interrupt.h>
#include <linux/cpufreq.h>
#include <linux/cpu.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/sysfs.h>
#include <linux/mutex.h>
#include <asm/uaccess.h>
/**
* A few values needed by the userspace governor
*/
static unsigned int cpu_max_freq[NR_CPUS];
static unsigned int cpu_min_freq[NR_CPUS];
static unsigned int cpu_cur_freq[NR_CPUS]; /* current CPU freq */
static unsigned int cpu_set_freq[NR_CPUS]; /* CPU freq desired by userspace */
static unsigned int cpu_is_managed[NR_CPUS];
static DEFINE_MUTEX (userspace_mutex);
#define dprintk(msg...) cpufreq_debug_printk(CPUFREQ_DEBUG_GOVERNOR, "userspace", msg)
/* keep track of frequency transitions */
static int
userspace_cpufreq_notifier(struct notifier_block *nb, unsigned long val,
void *data)
{
struct cpufreq_freqs *freq = data;
dprintk("saving cpu_cur_freq of cpu %u to be %u kHz\n", freq->cpu, freq->new);
cpu_cur_freq[freq->cpu] = freq->new;
return 0;
}
static struct notifier_block userspace_cpufreq_notifier_block = {
.notifier_call = userspace_cpufreq_notifier
};
/**
* cpufreq_set - set the CPU frequency
* @freq: target frequency in kHz
* @cpu: CPU for which the frequency is to be set
*
* Sets the CPU frequency to freq.
*/
static int cpufreq_set(unsigned int freq, struct cpufreq_policy *policy)
{
int ret = -EINVAL;
dprintk("cpufreq_set for cpu %u, freq %u kHz\n", policy->cpu, freq);
lock_cpu_hotplug();
mutex_lock(&userspace_mutex);
if (!cpu_is_managed[policy->cpu])
goto err;
cpu_set_freq[policy->cpu] = freq;
if (freq < cpu_min_freq[policy->cpu])
freq = cpu_min_freq[policy->cpu];
if (freq > cpu_max_freq[policy->cpu])
freq = cpu_max_freq[policy->cpu];
/*
* We're safe from concurrent calls to ->target() here
* as we hold the userspace_mutex lock. If we were calling
* cpufreq_driver_target, a deadlock situation might occur:
* A: cpufreq_set (lock userspace_mutex) -> cpufreq_driver_target(lock policy->lock)
* B: cpufreq_set_policy(lock policy->lock) -> __cpufreq_governor -> cpufreq_governor_userspace (lock userspace_mutex)
*/
ret = __cpufreq_driver_target(policy, freq, CPUFREQ_RELATION_L);
err:
mutex_unlock(&userspace_mutex);
unlock_cpu_hotplug();
return ret;
}
/************************** sysfs interface ************************/
static ssize_t show_speed (struct cpufreq_policy *policy, char *buf)
{
return sprintf (buf, "%u\n", cpu_cur_freq[policy->cpu]);
}
static ssize_t
store_speed (struct cpufreq_policy *policy, const char *buf, size_t count)
{
unsigned int freq = 0;
unsigned int ret;
ret = sscanf (buf, "%u", &freq);
if (ret != 1)
return -EINVAL;
cpufreq_set(freq, policy);
return count;
}
static struct freq_attr freq_attr_scaling_setspeed =
{
.attr = { .name = "scaling_setspeed", .mode = 0644, .owner = THIS_MODULE },
.show = show_speed,
.store = store_speed,
};
static int cpufreq_governor_userspace(struct cpufreq_policy *policy,
unsigned int event)
{
unsigned int cpu = policy->cpu;
switch (event) {
case CPUFREQ_GOV_START:
if (!cpu_online(cpu))
return -EINVAL;
BUG_ON(!policy->cur);
mutex_lock(&userspace_mutex);
cpu_is_managed[cpu] = 1;
cpu_min_freq[cpu] = policy->min;
cpu_max_freq[cpu] = policy->max;
cpu_cur_freq[cpu] = policy->cur;
cpu_set_freq[cpu] = policy->cur;
sysfs_create_file (&policy->kobj, &freq_attr_scaling_setspeed.attr);
dprintk("managing cpu %u started (%u - %u kHz, currently %u kHz)\n", cpu, cpu_min_freq[cpu], cpu_max_freq[cpu], cpu_cur_freq[cpu]);
mutex_unlock(&userspace_mutex);
break;
case CPUFREQ_GOV_STOP:
mutex_lock(&userspace_mutex);
cpu_is_managed[cpu] = 0;
cpu_min_freq[cpu] = 0;
cpu_max_freq[cpu] = 0;
cpu_set_freq[cpu] = 0;
sysfs_remove_file (&policy->kobj, &freq_attr_scaling_setspeed.attr);
dprintk("managing cpu %u stopped\n", cpu);
mutex_unlock(&userspace_mutex);
break;
case CPUFREQ_GOV_LIMITS:
mutex_lock(&userspace_mutex);
dprintk("limit event for cpu %u: %u - %u kHz,"
"currently %u kHz, last set to %u kHz\n",
cpu, policy->min, policy->max,
cpu_cur_freq[cpu], cpu_set_freq[cpu]);
if (policy->max < cpu_set_freq[cpu]) {
__cpufreq_driver_target(policy, policy->max,
CPUFREQ_RELATION_H);
}
else if (policy->min > cpu_set_freq[cpu]) {
__cpufreq_driver_target(policy, policy->min,
CPUFREQ_RELATION_L);
}
else {
__cpufreq_driver_target(policy, cpu_set_freq[cpu],
CPUFREQ_RELATION_L);
}
cpu_min_freq[cpu] = policy->min;
cpu_max_freq[cpu] = policy->max;
cpu_cur_freq[cpu] = policy->cur;
mutex_unlock(&userspace_mutex);
break;
}
return 0;
}
struct cpufreq_governor cpufreq_gov_userspace = {
.name = "userspace",
.governor = cpufreq_governor_userspace,
.owner = THIS_MODULE,
};
EXPORT_SYMBOL(cpufreq_gov_userspace);
static int __init cpufreq_gov_userspace_init(void)
{
cpufreq_register_notifier(&userspace_cpufreq_notifier_block, CPUFREQ_TRANSITION_NOTIFIER);
return cpufreq_register_governor(&cpufreq_gov_userspace);
}
static void __exit cpufreq_gov_userspace_exit(void)
{
cpufreq_unregister_governor(&cpufreq_gov_userspace);
cpufreq_unregister_notifier(&userspace_cpufreq_notifier_block, CPUFREQ_TRANSITION_NOTIFIER);
}
MODULE_AUTHOR ("Dominik Brodowski <linux@brodo.de>, Russell King <rmk@arm.linux.org.uk>");
MODULE_DESCRIPTION ("CPUfreq policy governor 'userspace'");
MODULE_LICENSE ("GPL");
fs_initcall(cpufreq_gov_userspace_init);
module_exit(cpufreq_gov_userspace_exit);