aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAndi Kleen <ak@linux.intel.com>2012-01-25 18:09:05 -0500
committerGreg Kroah-Hartman <gregkh@suse.de>2012-01-26 19:44:41 -0500
commit644e9cbbe3fc032cc92d0936057e166a994dc246 (patch)
tree46f0c35d92419dcedf19067a060e7877dc3d6b28
parentd6e486868cde585842d55ba3b6ec57af090fc343 (diff)
Add driver auto probing for x86 features v4
There's a growing number of drivers that support a specific x86 feature or CPU. Currently loading these drivers currently on a generic distribution requires various driver specific hacks and it often doesn't work. This patch adds auto probing for drivers based on the x86 cpuid information, in particular based on vendor/family/model number and also based on CPUID feature bits. For example a common issue is not loading the SSE 4.2 accelerated CRC module: this can significantly lower the performance of BTRFS which relies on fast CRC. Another issue is loading the right CPUFREQ driver for the current CPU. Currently distributions often try all all possible driver until one sticks, which is not really a good way to do this. It works with existing udev without any changes. The code exports the x86 information as a generic string in sysfs that can be matched by udev's pattern matching. This scheme does not support numeric ranges, so if you want to handle e.g. ranges of model numbers they have to be encoded in ASCII or simply all models or families listed. Fixing that would require changing udev. Another issue is that udev will happily load all drivers that match, there is currently no nice way to stop a specific driver from being loaded if it's not needed (e.g. if you don't need fast CRC) But there are not that many cpu specific drivers around and they're all not that bloated, so this isn't a particularly serious issue. Originally this patch added the modalias to the normal cpu sysdevs. However sysdevs don't have all the infrastructure needed for udev, so it couldn't really autoload drivers. This patch instead adds the CPU modaliases to the cpuid devices, which are real devices with full support for udev. This implies that the cpuid driver has to be loaded to use this. This patch just adds infrastructure, some driver conversions in followups. Thanks to Kay for helping with some sysfs magic. v2: Constifcation, some updates v4: (trenn@suse.de): - Use kzalloc instead of kmalloc to terminate modalias buffer - Use uppercase hex values to match correctly against hex values containing letters Cc: Dave Jones <davej@redhat.com> Cc: Kay Sievers <kay.sievers@vrfy.org> Cc: Jen Axboe <axboe@kernel.dk> Cc: Herbert Xu <herbert@gondor.apana.org.au> Cc: Huang Ying <ying.huang@intel.com> Cc: Len Brown <lenb@kernel.org> Signed-off-by: Andi Kleen <ak@linux.intel.com> Signed-off-by: Thomas Renninger <trenn@suse.de> Acked-by: H. Peter Anvin <hpa@zytor.com> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
-rw-r--r--arch/x86/include/asm/cpu_device_id.h13
-rw-r--r--arch/x86/kernel/cpu/Makefile1
-rw-r--r--arch/x86/kernel/cpu/match.c48
-rw-r--r--arch/x86/kernel/cpuid.c59
-rw-r--r--include/linux/mod_devicetable.h21
-rw-r--r--scripts/mod/file2alias.c24
6 files changed, 165 insertions, 1 deletions
diff --git a/arch/x86/include/asm/cpu_device_id.h b/arch/x86/include/asm/cpu_device_id.h
new file mode 100644
index 000000000000..ff501e511d91
--- /dev/null
+++ b/arch/x86/include/asm/cpu_device_id.h
@@ -0,0 +1,13 @@
1#ifndef _CPU_DEVICE_ID
2#define _CPU_DEVICE_ID 1
3
4/*
5 * Declare drivers belonging to specific x86 CPUs
6 * Similar in spirit to pci_device_id and related PCI functions
7 */
8
9#include <linux/mod_devicetable.h>
10
11extern const struct x86_cpu_id *x86_match_cpu(const struct x86_cpu_id *match);
12
13#endif
diff --git a/arch/x86/kernel/cpu/Makefile b/arch/x86/kernel/cpu/Makefile
index 25f24dccdcfa..6ab6aa2fdfdd 100644
--- a/arch/x86/kernel/cpu/Makefile
+++ b/arch/x86/kernel/cpu/Makefile
@@ -16,6 +16,7 @@ obj-y := intel_cacheinfo.o scattered.o topology.o
16obj-y += proc.o capflags.o powerflags.o common.o 16obj-y += proc.o capflags.o powerflags.o common.o
17obj-y += vmware.o hypervisor.o sched.o mshyperv.o 17obj-y += vmware.o hypervisor.o sched.o mshyperv.o
18obj-y += rdrand.o 18obj-y += rdrand.o
19obj-y += match.o
19 20
20obj-$(CONFIG_X86_32) += bugs.o 21obj-$(CONFIG_X86_32) += bugs.o
21obj-$(CONFIG_X86_64) += bugs_64.o 22obj-$(CONFIG_X86_64) += bugs_64.o
diff --git a/arch/x86/kernel/cpu/match.c b/arch/x86/kernel/cpu/match.c
new file mode 100644
index 000000000000..7acc961422e7
--- /dev/null
+++ b/arch/x86/kernel/cpu/match.c
@@ -0,0 +1,48 @@
1#include <asm/cpu_device_id.h>
2#include <asm/processor.h>
3#include <linux/cpu.h>
4#include <linux/module.h>
5
6/**
7 * x86_match_cpu - match current CPU again an array of x86_cpu_ids
8 * @match: Pointer to array of x86_cpu_ids. Last entry terminated with
9 * {}.
10 *
11 * Return the entry if the current CPU matches the entries in the
12 * passed x86_cpu_id match table. Otherwise NULL. The match table
13 * contains vendor (X86_VENDOR_*), family, model and feature bits or
14 * respective wildcard entries.
15 *
16 * A typical table entry would be to match a specific CPU
17 * { X86_VENDOR_INTEL, 6, 0x12 }
18 * or to match a specific CPU feature
19 * { X86_FEATURE_MATCH(X86_FEATURE_FOOBAR) }
20 *
21 * Fields can be wildcarded with %X86_VENDOR_ANY, %X86_FAMILY_ANY,
22 * %X86_MODEL_ANY, %X86_FEATURE_ANY or 0 (except for vendor)
23 *
24 * Arrays used to match for this should also be declared using
25 * MODULE_DEVICE_TABLE(x86_cpu, ...)
26 *
27 * This always matches against the boot cpu, assuming models and features are
28 * consistent over all CPUs.
29 */
30const struct x86_cpu_id *x86_match_cpu(const struct x86_cpu_id *match)
31{
32 const struct x86_cpu_id *m;
33 struct cpuinfo_x86 *c = &boot_cpu_data;
34
35 for (m = match; m->vendor | m->family | m->model | m->feature; m++) {
36 if (m->vendor != X86_VENDOR_ANY && c->x86_vendor != m->vendor)
37 continue;
38 if (m->family != X86_FAMILY_ANY && c->x86 != m->family)
39 continue;
40 if (m->model != X86_MODEL_ANY && c->x86_model != m->model)
41 continue;
42 if (m->feature != X86_FEATURE_ANY && !cpu_has(c, m->feature))
43 continue;
44 return m;
45 }
46 return NULL;
47}
48EXPORT_SYMBOL(x86_match_cpu);
diff --git a/arch/x86/kernel/cpuid.c b/arch/x86/kernel/cpuid.c
index a524353d93f2..7c89880eefd0 100644
--- a/arch/x86/kernel/cpuid.c
+++ b/arch/x86/kernel/cpuid.c
@@ -40,6 +40,7 @@
40#include <linux/notifier.h> 40#include <linux/notifier.h>
41#include <linux/uaccess.h> 41#include <linux/uaccess.h>
42#include <linux/gfp.h> 42#include <linux/gfp.h>
43#include <linux/slab.h>
43 44
44#include <asm/processor.h> 45#include <asm/processor.h>
45#include <asm/msr.h> 46#include <asm/msr.h>
@@ -138,13 +139,57 @@ static const struct file_operations cpuid_fops = {
138 .open = cpuid_open, 139 .open = cpuid_open,
139}; 140};
140 141
142static ssize_t print_cpu_modalias(struct device *dev,
143 struct device_attribute *attr,
144 char *bufptr)
145{
146 int size = PAGE_SIZE;
147 int i, n;
148 char *buf = bufptr;
149
150 n = snprintf(buf, size, "x86cpu:vendor:%04X:family:"
151 "%04X:model:%04X:feature:",
152 boot_cpu_data.x86_vendor,
153 boot_cpu_data.x86,
154 boot_cpu_data.x86_model);
155 size -= n;
156 buf += n;
157 size -= 2;
158 for (i = 0; i < NCAPINTS*32; i++) {
159 if (boot_cpu_has(i)) {
160 n = snprintf(buf, size, ",%04X", i);
161 if (n < 0) {
162 WARN(1, "x86 features overflow page\n");
163 break;
164 }
165 size -= n;
166 buf += n;
167 }
168 }
169 *buf++ = ',';
170 *buf++ = '\n';
171 return buf - bufptr;
172}
173
174static DEVICE_ATTR(modalias, 0444, print_cpu_modalias, NULL);
175
141static __cpuinit int cpuid_device_create(int cpu) 176static __cpuinit int cpuid_device_create(int cpu)
142{ 177{
143 struct device *dev; 178 struct device *dev;
179 int err;
144 180
145 dev = device_create(cpuid_class, NULL, MKDEV(CPUID_MAJOR, cpu), NULL, 181 dev = device_create(cpuid_class, NULL, MKDEV(CPUID_MAJOR, cpu), NULL,
146 "cpu%d", cpu); 182 "cpu%d", cpu);
147 return IS_ERR(dev) ? PTR_ERR(dev) : 0; 183 if (IS_ERR(dev))
184 return PTR_ERR(dev);
185
186 err = device_create_file(dev, &dev_attr_modalias);
187 if (err) {
188 /* keep device around on error. attribute is optional. */
189 err = 0;
190 }
191
192 return 0;
148} 193}
149 194
150static void cpuid_device_destroy(int cpu) 195static void cpuid_device_destroy(int cpu)
@@ -182,6 +227,17 @@ static char *cpuid_devnode(struct device *dev, umode_t *mode)
182 return kasprintf(GFP_KERNEL, "cpu/%u/cpuid", MINOR(dev->devt)); 227 return kasprintf(GFP_KERNEL, "cpu/%u/cpuid", MINOR(dev->devt));
183} 228}
184 229
230static int cpuid_dev_uevent(struct device *dev, struct kobj_uevent_env *env)
231{
232 char *buf = kzalloc(PAGE_SIZE, GFP_KERNEL);
233 if (buf) {
234 print_cpu_modalias(NULL, NULL, buf);
235 add_uevent_var(env, "MODALIAS=%s", buf);
236 kfree(buf);
237 }
238 return 0;
239}
240
185static int __init cpuid_init(void) 241static int __init cpuid_init(void)
186{ 242{
187 int i, err = 0; 243 int i, err = 0;
@@ -200,6 +256,7 @@ static int __init cpuid_init(void)
200 goto out_chrdev; 256 goto out_chrdev;
201 } 257 }
202 cpuid_class->devnode = cpuid_devnode; 258 cpuid_class->devnode = cpuid_devnode;
259 cpuid_class->dev_uevent = cpuid_dev_uevent;
203 for_each_online_cpu(i) { 260 for_each_online_cpu(i) {
204 err = cpuid_device_create(i); 261 err = cpuid_device_create(i);
205 if (err != 0) 262 if (err != 0)
diff --git a/include/linux/mod_devicetable.h b/include/linux/mod_devicetable.h
index b29e7f6f8fa5..cff2cc08f45a 100644
--- a/include/linux/mod_devicetable.h
+++ b/include/linux/mod_devicetable.h
@@ -571,4 +571,25 @@ struct amba_id {
571#endif 571#endif
572}; 572};
573 573
574/*
575 * Match x86 CPUs for CPU specific drivers.
576 * See documentation of "x86_match_cpu" for details.
577 */
578
579struct x86_cpu_id {
580 __u16 vendor;
581 __u16 family;
582 __u16 model;
583 __u16 feature; /* bit index */
584 kernel_ulong_t driver_data;
585};
586
587#define X86_FEATURE_MATCH(x) \
588 { X86_VENDOR_ANY, X86_FAMILY_ANY, X86_MODEL_ANY, x }
589
590#define X86_VENDOR_ANY 0xffff
591#define X86_FAMILY_ANY 0
592#define X86_MODEL_ANY 0
593#define X86_FEATURE_ANY 0 /* Same as FPU, you can't test for that */
594
574#endif /* LINUX_MOD_DEVICETABLE_H */ 595#endif /* LINUX_MOD_DEVICETABLE_H */
diff --git a/scripts/mod/file2alias.c b/scripts/mod/file2alias.c
index c0e14b3f2306..026ba38759ca 100644
--- a/scripts/mod/file2alias.c
+++ b/scripts/mod/file2alias.c
@@ -1013,6 +1013,30 @@ static int do_amba_entry(const char *filename,
1013} 1013}
1014ADD_TO_DEVTABLE("amba", struct amba_id, do_amba_entry); 1014ADD_TO_DEVTABLE("amba", struct amba_id, do_amba_entry);
1015 1015
1016/* LOOKS like x86cpu:vendor:VVVV:family:FFFF:model:MMMM:feature:*,FEAT,*
1017 * All fields are numbers. It would be nicer to use strings for vendor
1018 * and feature, but getting those out of the build system here is too
1019 * complicated.
1020 */
1021
1022static int do_x86cpu_entry(const char *filename, struct x86_cpu_id *id,
1023 char *alias)
1024{
1025 id->feature = TO_NATIVE(id->feature);
1026 id->family = TO_NATIVE(id->family);
1027 id->model = TO_NATIVE(id->model);
1028 id->vendor = TO_NATIVE(id->vendor);
1029
1030 strcpy(alias, "x86cpu:");
1031 ADD(alias, "vendor:", id->vendor != X86_VENDOR_ANY, id->vendor);
1032 ADD(alias, ":family:", id->family != X86_FAMILY_ANY, id->family);
1033 ADD(alias, ":model:", id->model != X86_MODEL_ANY, id->model);
1034 ADD(alias, ":feature:*,", id->feature != X86_FEATURE_ANY, id->feature);
1035 strcat(alias, ",*");
1036 return 1;
1037}
1038ADD_TO_DEVTABLE("x86cpu", struct x86_cpu_id, do_x86cpu_entry);
1039
1016/* Does namelen bytes of name exactly match the symbol? */ 1040/* Does namelen bytes of name exactly match the symbol? */
1017static bool sym_is(const char *name, unsigned namelen, const char *symbol) 1041static bool sym_is(const char *name, unsigned namelen, const char *symbol)
1018{ 1042{