[ACPI/CPUFREQ] Introduce bios_limit per cpu cpufreq sysfs interface
authorThomas Renninger <trenn@suse.de>
Thu, 19 Nov 2009 11:31:01 +0000 (12:31 +0100)
committerDave Jones <davej@redhat.com>
Tue, 24 Nov 2009 18:33:34 +0000 (13:33 -0500)
This interface is mainly intended (and implemented) for ACPI _PPC BIOS
frequency limitations, but other cpufreq drivers can also use it for
similar use-cases.

Why is this needed:

Currently it's not obvious why cpufreq got limited.
People see cpufreq/scaling_max_freq reduced, but this could have
happened by:
  - any userspace prog writing to scaling_max_freq
  - thermal limitations
  - hardware (_PPC in ACPI case) limitiations

Therefore export bios_limit (in kHz) to:
  - Point the user that it's the BIOS (broken or intended) which limits
    frequency
  - Export it as a sysfs interface for userspace progs.
    While this was a rarely used feature on laptops, there will appear
    more and more server implemenations providing "Green IT" features like
    allowing the service processor to limit the frequency. People want
    to know about HW/BIOS frequency limitations.

All ACPI P-state driven cpufreq drivers are covered with this patch:
  - powernow-k8
  - powernow-k7
  - acpi-cpufreq

Tested with a patched DSDT which limits the first two cores (_PPC returns 1)
via _PPC, exposed by bios_limit:
# echo 2200000 >cpu2/cpufreq/scaling_max_freq
# cat cpu*/cpufreq/scaling_max_freq
2600000
2600000
2200000
2200000
# #scaling_max_freq shows general user/thermal/BIOS limitations

# cat cpu*/cpufreq/bios_limit
2600000
2600000
2800000
2800000
# #bios_limit only shows the HW/BIOS limitation

CC: Pallipadi Venkatesh <venkatesh.pallipadi@intel.com>
CC: Len Brown <lenb@kernel.org>
CC: davej@codemonkey.org.uk
CC: linux@dominikbrodowski.net
Signed-off-by: Thomas Renninger <trenn@suse.de>
Signed-off-by: Dave Jones <davej@redhat.com>
Documentation/cpu-freq/user-guide.txt
arch/x86/kernel/cpu/cpufreq/acpi-cpufreq.c
arch/x86/kernel/cpu/cpufreq/powernow-k7.c
arch/x86/kernel/cpu/cpufreq/powernow-k8.c
drivers/acpi/processor_perflib.c
drivers/cpufreq/cpufreq.c
include/acpi/processor.h
include/linux/cpufreq.h

index 2a5b850847c024e676395320f534c1982c8ae33a..04f6b32993e69ca4185a81bbaadb677aac5056e9 100644 (file)
@@ -203,6 +203,17 @@ scaling_cur_freq :         Current frequency of the CPU as determined by
                                the frequency the kernel thinks the CPU runs
                                at.
 
+bios_limit :                   If the BIOS tells the OS to limit a CPU to
+                               lower frequencies, the user can read out the
+                               maximum available frequency from this file.
+                               This typically can happen through (often not
+                               intended) BIOS settings, restrictions
+                               triggered through a service processor or other
+                               BIOS/HW based implementations.
+                               This does not cover thermal ACPI limitations
+                               which can be detected through the generic
+                               thermal driver.
+
 If you have selected the "userspace" governor which allows you to
 set the CPU operating frequency to a specific value, you can read out
 the current frequency in
index 8b581d3905cb47214af854582e24a379fac444f3..d2e7c77c1ea4901a8e3e6b2a70c99b5c991132f3 100644 (file)
@@ -764,14 +764,15 @@ static struct freq_attr *acpi_cpufreq_attr[] = {
 };
 
 static struct cpufreq_driver acpi_cpufreq_driver = {
-       .verify = acpi_cpufreq_verify,
-       .target = acpi_cpufreq_target,
-       .init = acpi_cpufreq_cpu_init,
-       .exit = acpi_cpufreq_cpu_exit,
-       .resume = acpi_cpufreq_resume,
-       .name = "acpi-cpufreq",
-       .owner = THIS_MODULE,
-       .attr = acpi_cpufreq_attr,
+       .verify         = acpi_cpufreq_verify,
+       .target         = acpi_cpufreq_target,
+       .bios_limit     = acpi_processor_get_bios_limit,
+       .init           = acpi_cpufreq_cpu_init,
+       .exit           = acpi_cpufreq_cpu_exit,
+       .resume         = acpi_cpufreq_resume,
+       .name           = "acpi-cpufreq",
+       .owner          = THIS_MODULE,
+       .attr           = acpi_cpufreq_attr,
 };
 
 static int __init acpi_cpufreq_init(void)
index d47c775eb0abce217593a99cd06dba2f87125977..9a97116f89e5ebc4805db1c608c5e79e96d847f5 100644 (file)
@@ -714,14 +714,17 @@ static struct freq_attr *powernow_table_attr[] = {
 };
 
 static struct cpufreq_driver powernow_driver = {
-       .verify = powernow_verify,
-       .target = powernow_target,
-       .get    = powernow_get,
-       .init   = powernow_cpu_init,
-       .exit   = powernow_cpu_exit,
-       .name   = "powernow-k7",
-       .owner  = THIS_MODULE,
-       .attr   = powernow_table_attr,
+       .verify         = powernow_verify,
+       .target         = powernow_target,
+       .get            = powernow_get,
+#ifdef CONFIG_X86_POWERNOW_K7_ACPI
+       .bios_limit     = acpi_processor_get_bios_limit,
+#endif
+       .init           = powernow_cpu_init,
+       .exit           = powernow_cpu_exit,
+       .name           = "powernow-k7",
+       .owner          = THIS_MODULE,
+       .attr           = powernow_table_attr,
 };
 
 static int __init powernow_init(void)
index f30d25383940db77458fb41f047e43896eaa75a6..a9df9441a9a240cd138b6492896cc0788a04cf1b 100644 (file)
@@ -1398,14 +1398,15 @@ static struct freq_attr *powernow_k8_attr[] = {
 };
 
 static struct cpufreq_driver cpufreq_amd64_driver = {
-       .verify = powernowk8_verify,
-       .target = powernowk8_target,
-       .init = powernowk8_cpu_init,
-       .exit = __devexit_p(powernowk8_cpu_exit),
-       .get = powernowk8_get,
-       .name = "powernow-k8",
-       .owner = THIS_MODULE,
-       .attr = powernow_k8_attr,
+       .verify         = powernowk8_verify,
+       .target         = powernowk8_target,
+       .bios_limit     = acpi_processor_get_bios_limit,
+       .init           = powernowk8_cpu_init,
+       .exit           = __devexit_p(powernowk8_cpu_exit),
+       .get            = powernowk8_get,
+       .name           = "powernow-k8",
+       .owner          = THIS_MODULE,
+       .attr           = powernow_k8_attr,
 };
 
 /* driver entry point for init */
index 8ba0ed0b9ddbc912684d398e2eda14357920aed4..01e366d2b6fb14475e0b6074b886fd8b55877348 100644 (file)
@@ -167,6 +167,19 @@ int acpi_processor_ppc_has_changed(struct acpi_processor *pr)
                return cpufreq_update_policy(pr->id);
 }
 
+int acpi_processor_get_bios_limit(int cpu, unsigned int *limit)
+{
+       struct acpi_processor *pr;
+
+       pr = per_cpu(processors, cpu);
+       if (!pr || !pr->performance || !pr->performance->state_count)
+               return -ENODEV;
+       *limit = pr->performance->states[pr->performance_platform_limit].
+               core_frequency * 1000;
+       return 0;
+}
+EXPORT_SYMBOL(acpi_processor_get_bios_limit);
+
 void acpi_processor_ppc_init(void)
 {
        if (!cpufreq_register_notifier
index 5b9b1c8c495000f5c8c071aac1e4a76d83728952..f20668c09ce0611d3da1fa8582514ccfec949347 100644 (file)
@@ -647,6 +647,21 @@ static ssize_t show_scaling_setspeed(struct cpufreq_policy *policy, char *buf)
        return policy->governor->show_setspeed(policy, buf);
 }
 
+/**
+ * show_scaling_driver - show the current cpufreq HW/BIOS limitation
+ */
+static ssize_t show_bios_limit(struct cpufreq_policy *policy, char *buf)
+{
+       unsigned int limit;
+       int ret;
+       if (cpufreq_driver->bios_limit) {
+               ret = cpufreq_driver->bios_limit(policy->cpu, &limit);
+               if (!ret)
+                       return sprintf(buf, "%u\n", limit);
+       }
+       return sprintf(buf, "%u\n", policy->cpuinfo.max_freq);
+}
+
 #define define_one_ro(_name) \
 static struct freq_attr _name = \
 __ATTR(_name, 0444, show_##_name, NULL)
@@ -666,6 +681,7 @@ define_one_ro(cpuinfo_transition_latency);
 define_one_ro(scaling_available_governors);
 define_one_ro(scaling_driver);
 define_one_ro(scaling_cur_freq);
+define_one_ro(bios_limit);
 define_one_ro(related_cpus);
 define_one_ro(affected_cpus);
 define_one_rw(scaling_min_freq);
@@ -905,6 +921,11 @@ static int cpufreq_add_dev_interface(unsigned int cpu,
                if (ret)
                        goto err_out_kobj_put;
        }
+       if (cpufreq_driver->bios_limit) {
+               ret = sysfs_create_file(&policy->kobj, &bios_limit.attr);
+               if (ret)
+                       goto err_out_kobj_put;
+       }
 
        spin_lock_irqsave(&cpufreq_driver_lock, flags);
        for_each_cpu(j, policy->cpus) {
index 740ac3ad8fd06fc61b8e2dacc10054ec27469439..8b668ead6d6e84a55f7cd9c743524cbeace28e1a 100644 (file)
@@ -295,6 +295,7 @@ static inline void acpi_processor_ffh_cstate_enter(struct acpi_processor_cx
 void acpi_processor_ppc_init(void);
 void acpi_processor_ppc_exit(void);
 int acpi_processor_ppc_has_changed(struct acpi_processor *pr);
+extern int acpi_processor_get_bios_limit(int cpu, unsigned int *limit);
 #else
 static inline void acpi_processor_ppc_init(void)
 {
@@ -316,6 +317,11 @@ static inline int acpi_processor_ppc_has_changed(struct acpi_processor *pr)
        }
        return 0;
 }
+static inline int acpi_processor_get_bios_limit(int cpu, unsigned int *limit)
+{
+       return -ENODEV;
+}
+
 #endif                         /* CONFIG_CPU_FREQ */
 
 /* in processor_throttling.c */
index 79a2340d83cda45c88138841fe4b2a8a3892bcad..4de02b10007ff4258ce6be681687d263239e1c12 100644 (file)
@@ -232,6 +232,7 @@ struct cpufreq_driver {
        /* optional */
        unsigned int (*getavg)  (struct cpufreq_policy *policy,
                                 unsigned int cpu);
+       int     (*bios_limit)   (int cpu, unsigned int *limit);
 
        int     (*exit)         (struct cpufreq_policy *policy);
        int     (*suspend)      (struct cpufreq_policy *policy, pm_message_t pmsg);