History

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

Andi Kleen committed 14 years ago

Commit 644e9cb1 parent d6e48686 files · +165 −1Browse files

Unified · Split

6 files
  1. arch/x86/include/asm/cpu_device_id.h +13 −0
  2. arch/x86/kernel/cpu/Makefile +1 −0
  3. arch/x86/kernel/cpu/match.c +48 −0
  4. arch/x86/kernel/cpuid.c +58 −1
  5. include/linux/mod_devicetable.h +21 −0
  6. scripts/mod/file2alias.c +24 −0
arch/x86/include/asm/cpu_device_id.h+13−0
@@ -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+
11+extern const struct x86_cpu_id *x86_match_cpu(const struct x86_cpu_id *match);
12+
13+#endif
arch/x86/kernel/cpu/Makefile+1−0
@@ -16,6 +16,7 @@ obj-y := intel_cacheinfo.o scattered.o topology.o · 15 unmodified lines
1616 obj-y += proc.o capflags.o powerflags.o common.o
1717 obj-y += vmware.o hypervisor.o sched.o mshyperv.o
1818 obj-y += rdrand.o
19+obj-y += match.o
1920
2021 obj-$(CONFIG_X86_32) += bugs.o
2122 obj-$(CONFIG_X86_64) += bugs_64.o
arch/x86/kernel/cpu/match.c+48−0
@@ -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+ */
30+const 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+}
48+EXPORT_SYMBOL(x86_match_cpu);
arch/x86/kernel/cpuid.c+58−1
@@ -40,6 +40,7 @@ · 39 unmodified lines
4040 #include <linux/notifier.h>
4141 #include <linux/uaccess.h>
4242 #include <linux/gfp.h>
43+#include <linux/slab.h>
4344
4445 #include <asm/processor.h>
4546 #include <asm/msr.h>
@@ -138,13 +139,57 @@ static const struct file_operations cpuid_fops = { · 92 unmodified lines
138139 .open = cpuid_open,
139140 };
140141
142+static 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+
174+static DEVICE_ATTR(modalias, 0444, print_cpu_modalias, NULL);
175+
141176 static __cpuinit int cpuid_device_create(int cpu)
142177 {
143178 struct device *dev;
179+ int err;
144180
145181 dev = device_create(cpuid_class, NULL, MKDEV(CPUID_MAJOR, cpu), NULL,
146182 "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;
148193 }
149194
150195 static void cpuid_device_destroy(int cpu)
@@ -182,6 +227,17 @@ static char *cpuid_devnode(struct device *dev, umode_t *mode) · 31 unmodified lines
182227 return kasprintf(GFP_KERNEL, "cpu/%u/cpuid", MINOR(dev->devt));
183228 }
184229
230+static 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+
185241 static int __init cpuid_init(void)
186242 {
187243 int i, err = 0;
@@ -200,6 +256,7 @@ static int __init cpuid_init(void) · 12 unmodified lines
200256 goto out_chrdev;
201257 }
202258 cpuid_class->devnode = cpuid_devnode;
259+ cpuid_class->dev_uevent = cpuid_dev_uevent;
203260 for_each_online_cpu(i) {
204261 err = cpuid_device_create(i);
205262 if (err != 0)
include/linux/mod_devicetable.h+21−0
@@ -571,4 +571,25 @@ struct amba_id { · 570 unmodified lines
571571 #endif
572572 };
573573
574+/*
575+ * Match x86 CPUs for CPU specific drivers.
576+ * See documentation of "x86_match_cpu" for details.
577+ */
578+
579+struct 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+
574595 #endif /* LINUX_MOD_DEVICETABLE_H */
scripts/mod/file2alias.c+24−0
@@ -1013,6 +1013,30 @@ static int do_amba_entry(const char *filename, · 1012 unmodified lines
10131013 }
10141014 ADD_TO_DEVTABLE("amba", struct amba_id, do_amba_entry);
10151015
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+
1022+static 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+}
1038+ADD_TO_DEVTABLE("x86cpu", struct x86_cpu_id, do_x86cpu_entry);
1039+
10161040 /* Does namelen bytes of name exactly match the symbol? */
10171041 static bool sym_is(const char *name, unsigned namelen, const char *symbol)
10181042 {