[PATCH][RFC] Re: cpufreq: allow user to specify voltage

Dominik Brodowski (linux@brodo.de)
Fri, 28 Feb 2003 22:16:38 +0100


Here's my suggestion, (partly) based on something Patrick Mochel suggested
for the passing of attribute files of cpufreq drivers to the cpufreq core:
a NULL-terminated list of device_attributes *attr is passed to the core. And
if attr itself is NULL, no attribute is passed, of course. Using this
approach, this patch against cpufreq-CVS-HEAD adds a file

/sys/devices/sys/cpu0/scaling_available_freqs (something Carl Thompson
asked for)

for the powernow-k7.c and the p4-clockmod.c drivers (other frequency table
based drivers can be added at will -- Carl, please don't realy _solely_ on
this file: some drivers don't use frequency tables but still might want to
use your great userspace scaling program!)

And for powernow-k7.c , the file

/sys/devices/sys/cpu0/scaling_setvoltage

should show the current voltage for the current speed (scaling_setspeed).
"echoing" a different value (must be lower than the current voltage) changes
the voltage for this frequency only. However, this override is "static" so
that if you switch to a different frequency in the meantime but get back to
the one you wanted to override the voltage setting for, the new
user-specified value is remembered.

This is untested (don't have a powernow-k7-capable notebook), so handle with
care.

Comments most welcome,

Dominik

diff -ruN linux-original/arch/i386/kernel/cpu/cpufreq/p4-clockmod.c linux/arch/i386/kernel/cpu/cpufreq/p4-clockmod.c
--- linux-original/arch/i386/kernel/cpu/cpufreq/p4-clockmod.c 2003-02-28 21:42:13.000000000 +0100
+++ linux/arch/i386/kernel/cpu/cpufreq/p4-clockmod.c 2003-02-28 21:45:00.000000000 +0100
@@ -214,6 +214,7 @@
else
p4clockmod_table[i].frequency = (stock_freq * i)/8;
}
+ cpufreq_frequency_table_get_attr(p4clockmod_table, policy->cpu);

/* cpuinfo and default policy values */
policy->policy = CPUFREQ_POLICY_PERFORMANCE;
@@ -226,6 +227,7 @@

static int cpufreq_p4_cpu_exit(struct cpufreq_policy *policy)
{
+ cpufreq_frequency_table_put_attr(policy->cpu);
return cpufreq_p4_setdc(policy->cpu, DC_DISABLE);
}

@@ -236,6 +238,8 @@
.init = cpufreq_p4_cpu_init,
.exit = cpufreq_p4_cpu_exit,
.name = "p4-clockmod",
+ .attr = {&dev_attr_scaling_available_freqs,
+ NULL},
};


diff -ruN linux-original/arch/i386/kernel/cpu/cpufreq/powernow-k7.c linux/arch/i386/kernel/cpu/cpufreq/powernow-k7.c
--- linux-original/arch/i386/kernel/cpu/cpufreq/powernow-k7.c 2003-02-28 21:42:13.000000000 +0100
+++ linux/arch/i386/kernel/cpu/cpufreq/powernow-k7.c 2003-02-28 22:03:04.000000000 +0100
@@ -326,6 +326,7 @@
return -ENODEV;
}

+static DECLARE_MUTEX(change_speed_lock);

static int powernow_target (struct cpufreq_policy *policy,
unsigned int target_freq,
@@ -336,7 +337,9 @@
if (cpufreq_frequency_table_target(policy, powernow_table, target_freq, relation, &newstate))
return -EINVAL;

+ down(&change_speed_lock);
change_speed(newstate);
+ up(&change_speed_lock);

return 0;
}
@@ -365,6 +368,7 @@
printk (KERN_INFO PFX "Minimum speed %d MHz. Maximum speed %d MHz.\n",
minimum_speed, maximum_speed);

+ cpufreq_frequency_table_get_attr(powernow_table, policy->cpu);
policy->policy = CPUFREQ_POLICY_PERFORMANCE;
policy->cpuinfo.transition_latency = latency;
policy->cur = maximum_speed;
@@ -372,11 +376,103 @@
return cpufreq_frequency_table_cpuinfo(policy, powernow_table);
}

+static int powernow_cpu_exit(struct cpufreq_policy *policy)
+{
+ cpufreq_frequency_table_put_attr(policy->cpu);
+ return 0;
+}
+
+static ssize_t show_voltage (struct device *dev, char *buf)
+{
+ union msr_fidvidstatus fidvidstatus;
+ unsigned int freq;
+ unsigned int i;
+ u8 vid;
+
+ rdmsrl (MSR_K7_FID_VID_STATUS, fidvidstatus.val);
+ freq = fsb * fid_codes[fidvidstatus.bits.CFID] * 100;
+
+ for (i=0; (powernow_table[i].frequency != CPUFREQ_TABLE_END); i++) {
+ if (powernow_table[i].frequency == CPUFREQ_ENTRY_INVALID)
+ continue;
+ if (powernow_table[i].frequency == freq)
+ goto found;
+ }
+ return -EINVAL;
+
+ found:
+ vid = (powernow_table[i].index & 0xFF00) >> 8;
+ return sprintf(buf, "%dmV\n", mobile_vid_table[vid]);
+}
+
+static ssize_t store_voltage (struct device *dev,
+ const char *buf, size_t count)
+{
+ union msr_fidvidstatus fidvidstatus;
+ unsigned int freq;
+ unsigned int i;
+ unsigned int tmp;
+ unsigned int new_voltage;
+ unsigned int ret;
+ u8 vid, new_vid;
+
+ ret = sscanf (buf, "%d", &new_voltage);
+ if (ret != 1 || !new_voltage)
+ return -EINVAL;
+
+ for (i=0; i<32; i++) {
+ if (mobile_vid_table[i] == new_voltage)
+ goto match;
+ }
+ return -EINVAL;
+
+ match:
+ new_vid = (u8) i;
+
+ rdmsrl (MSR_K7_FID_VID_STATUS, fidvidstatus.val);
+ freq = fsb * fid_codes[fidvidstatus.bits.CFID] * 100;
+
+ for (i=0; (powernow_table[i].frequency != CPUFREQ_TABLE_END); i++) {
+ if (powernow_table[i].frequency == CPUFREQ_ENTRY_INVALID)
+ continue;
+ if (powernow_table[i].frequency == freq)
+ goto found;
+ }
+ return -ENODEV;
+
+ found:
+ vid = (powernow_table[i].index & 0xFF00) >> 8;
+
+ /* safety check */
+ if (mobile_vid_table[new_vid] > mobile_vid_table[vid])
+ return -EINVAL;
+
+ printk("overwriting voltage for frequency %d MHz with %d mV (old was %d mV).\n",
+ (freq/1000), mobile_vid_table[new_vid], mobile_vid_table[vid]);
+
+ down(&change_speed_lock);
+ tmp = powernow_table[i].index;
+ tmp &= powernow_table[i].index & 0x00FF;
+ tmp |= (new_vid << 8); /* upper 8 bits */
+ powernow_table[i].index = tmp; /* actual over-writing */
+
+ /* we're not changing speed but voltage, but anyways... */
+ change_speed (i);
+ up(&change_speed_lock);
+
+ return count;
+}
+static DEVICE_ATTR(scaling_setvoltage, (S_IRUGO | S_IWUSR), show_voltage, store_voltage);
+
static struct cpufreq_driver powernow_driver = {
.verify = powernow_verify,
.target = powernow_target,
.init = powernow_cpu_init,
+ .exit = powernow_cpu_exit,
.name = "powernow-k7",
+ .attr = {&dev_attr_scaling_available_freqs,
+ &dev_attr_scaling_setvoltage,
+ NULL},
};

static int __init powernow_init (void)
diff -ruN linux-original/drivers/cpufreq/freq_table.c linux/drivers/cpufreq/freq_table.c
--- linux-original/drivers/cpufreq/freq_table.c 2003-02-26 09:44:57.000000000 +0100
+++ linux/drivers/cpufreq/freq_table.c 2003-02-28 21:17:07.000000000 +0100
@@ -198,6 +198,53 @@
EXPORT_SYMBOL_GPL(cpufreq_frequency_table_target);


+static struct cpufreq_frequency_table *show_table[NR_CPUS];
+/**
+ * show_scaling_governor - show the current policy for the specified CPU
+ */
+static ssize_t show_available_freqs (struct device *dev, char *buf)
+{
+ unsigned int i = 0;
+ unsigned int cpu = to_cpu_nr(dev);
+ ssize_t count = 0;
+ struct cpufreq_frequency_table *table;
+
+ if (!show_table[cpu])
+ return -ENODEV;
+
+ table = show_table[cpu];
+
+ for (i=0; (table[i].frequency != CPUFREQ_TABLE_END); i++) {
+ if (table[i].frequency == CPUFREQ_ENTRY_INVALID)
+ continue;
+ count += sprintf(&buf[count], "%d ", table[i].frequency);
+ }
+ count += sprintf(&buf[count], "\n");
+
+ return count;
+
+}
+DEVICE_ATTR(scaling_available_freqs, S_IRUGO, show_available_freqs, NULL);
+EXPORT_SYMBOL_GPL(dev_attr_scaling_available_freqs);
+
+/*
+ * if you use these, you must assure that the frequency table is valid
+ * all the time between get_attr and put_attr!
+ */
+void cpufreq_frequency_table_get_attr(struct cpufreq_frequency_table *table,
+ unsigned int cpu)
+{
+ show_table[cpu] = table;
+}
+EXPORT_SYMBOL_GPL(cpufreq_frequency_table_get_attr);
+
+void cpufreq_frequency_table_put_attr(unsigned int cpu)
+{
+ show_table[cpu] = 0;
+}
+EXPORT_SYMBOL_GPL(cpufreq_frequency_table_put_attr);
+
+
MODULE_AUTHOR ("Dominik Brodowski <linux@brodo.de>");
MODULE_DESCRIPTION ("CPUfreq frequency table helpers");
MODULE_LICENSE ("GPL");
diff -ruN linux-original/include/linux/cpufreq.h linux/include/linux/cpufreq.h
--- linux-original/include/linux/cpufreq.h 2003-02-28 21:42:13.000000000 +0100
+++ linux/include/linux/cpufreq.h 2003-02-28 20:56:28.000000000 +0100
@@ -166,8 +166,11 @@
/* optional, for the moment */
int (*init) (struct cpufreq_policy *policy);
int (*exit) (struct cpufreq_policy *policy);
+ struct device_attribute *attr[];
};

+inline int to_cpu_nr (struct device *dev);
+
int cpufreq_register_driver(struct cpufreq_driver *driver_data);
int cpufreq_unregister_driver(struct cpufreq_driver *driver_data);
/* deprecated */
@@ -298,6 +301,14 @@
unsigned int relation,
unsigned int *index);

+/* the following are really really optional */
+extern struct device_attribute dev_attr_scaling_available_freqs;
+
+void cpufreq_frequency_table_get_attr(struct cpufreq_frequency_table *table,
+ unsigned int cpu);
+
+void cpufreq_frequency_table_put_attr(unsigned int cpu);
+
#endif /* CONFIG_CPU_FREQ_TABLE */

#endif /* _LINUX_CPUFREQ_H */
diff -ruN linux-original/kernel/cpufreq.c linux/kernel/cpufreq.c
--- linux-original/kernel/cpufreq.c 2003-02-28 21:42:13.000000000 +0100
+++ linux/kernel/cpufreq.c 2003-02-28 21:47:46.000000000 +0100
@@ -102,7 +102,7 @@
.devnum = 0,
};

-static inline int to_cpu_nr (struct device *dev)
+inline int to_cpu_nr (struct device *dev)
{
struct sys_device * cpu_sys_dev = container_of(dev, struct sys_device, dev);
return (cpu_sys_dev->id);
@@ -312,6 +312,7 @@
unsigned int cpu = to_cpu_nr(dev);
int ret = 0;
struct cpufreq_policy policy;
+ struct device_attribute ** drv_attr;

down(&cpufreq_driver_sem);
if (!cpufreq_driver) {
@@ -369,6 +370,12 @@
device_create_file (dev, &dev_attr_scaling_driver);
device_create_file (dev, &dev_attr_available_scaling_governors);

+ drv_attr = cpufreq_driver->attr;
+ while ((drv_attr) && (*drv_attr)) {
+ device_create_file(dev, *drv_attr);
+ drv_attr++;
+ }
+
up(&cpufreq_driver_sem);
return ret;
}
@@ -384,6 +391,7 @@
{
struct device * dev = intf->dev;
unsigned int cpu = to_cpu_nr(dev);
+ struct device_attribute ** drv_attr;

if (cpufreq_driver->target)
cpufreq_governor(cpu, CPUFREQ_GOV_STOP);
@@ -399,6 +407,12 @@
device_remove_file (dev, &dev_attr_scaling_driver);
device_remove_file (dev, &dev_attr_available_scaling_governors);

+ drv_attr = cpufreq_driver->attr;
+ while ((drv_attr) && (*drv_attr)) {
+ device_remove_file(dev, *drv_attr);
+ drv_attr++;
+ }
+
return 0;
}

-
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/