314 lines
9.2 KiB
Diff
314 lines
9.2 KiB
Diff
From: Andi Kleen <ak@linux.intel.com>
|
|
Date: Thu, 26 Jan 2012 00:09:05 +0100
|
|
Subject: Add driver auto probing for x86 features v4
|
|
|
|
commit 644e9cbbe3fc032cc92d0936057e166a994dc246 upstream.
|
|
|
|
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>
|
|
[bwh: Backported to 3.2:
|
|
- adjust context
|
|
- make do without ADD_TO_DEVTABLE in file2alias]
|
|
---
|
|
--- /dev/null
|
|
+++ b/arch/x86/include/asm/cpu_device_id.h
|
|
@@ -0,0 +1,13 @@
|
|
+#ifndef _CPU_DEVICE_ID
|
|
+#define _CPU_DEVICE_ID 1
|
|
+
|
|
+/*
|
|
+ * Declare drivers belonging to specific x86 CPUs
|
|
+ * Similar in spirit to pci_device_id and related PCI functions
|
|
+ */
|
|
+
|
|
+#include <linux/mod_devicetable.h>
|
|
+
|
|
+extern const struct x86_cpu_id *x86_match_cpu(const struct x86_cpu_id *match);
|
|
+
|
|
+#endif
|
|
--- a/arch/x86/kernel/cpu/Makefile
|
|
+++ b/arch/x86/kernel/cpu/Makefile
|
|
@@ -16,6 +16,7 @@
|
|
obj-y += proc.o capflags.o powerflags.o common.o
|
|
obj-y += vmware.o hypervisor.o sched.o mshyperv.o
|
|
obj-y += rdrand.o
|
|
+obj-y += match.o
|
|
|
|
obj-$(CONFIG_X86_32) += bugs.o
|
|
obj-$(CONFIG_X86_64) += bugs_64.o
|
|
--- /dev/null
|
|
+++ b/arch/x86/kernel/cpu/match.c
|
|
@@ -0,0 +1,48 @@
|
|
+#include <asm/cpu_device_id.h>
|
|
+#include <asm/processor.h>
|
|
+#include <linux/cpu.h>
|
|
+#include <linux/module.h>
|
|
+
|
|
+/**
|
|
+ * x86_match_cpu - match current CPU again an array of x86_cpu_ids
|
|
+ * @match: Pointer to array of x86_cpu_ids. Last entry terminated with
|
|
+ * {}.
|
|
+ *
|
|
+ * Return the entry if the current CPU matches the entries in the
|
|
+ * passed x86_cpu_id match table. Otherwise NULL. The match table
|
|
+ * contains vendor (X86_VENDOR_*), family, model and feature bits or
|
|
+ * respective wildcard entries.
|
|
+ *
|
|
+ * A typical table entry would be to match a specific CPU
|
|
+ * { X86_VENDOR_INTEL, 6, 0x12 }
|
|
+ * or to match a specific CPU feature
|
|
+ * { X86_FEATURE_MATCH(X86_FEATURE_FOOBAR) }
|
|
+ *
|
|
+ * Fields can be wildcarded with %X86_VENDOR_ANY, %X86_FAMILY_ANY,
|
|
+ * %X86_MODEL_ANY, %X86_FEATURE_ANY or 0 (except for vendor)
|
|
+ *
|
|
+ * Arrays used to match for this should also be declared using
|
|
+ * MODULE_DEVICE_TABLE(x86_cpu, ...)
|
|
+ *
|
|
+ * This always matches against the boot cpu, assuming models and features are
|
|
+ * consistent over all CPUs.
|
|
+ */
|
|
+const struct x86_cpu_id *x86_match_cpu(const struct x86_cpu_id *match)
|
|
+{
|
|
+ const struct x86_cpu_id *m;
|
|
+ struct cpuinfo_x86 *c = &boot_cpu_data;
|
|
+
|
|
+ for (m = match; m->vendor | m->family | m->model | m->feature; m++) {
|
|
+ if (m->vendor != X86_VENDOR_ANY && c->x86_vendor != m->vendor)
|
|
+ continue;
|
|
+ if (m->family != X86_FAMILY_ANY && c->x86 != m->family)
|
|
+ continue;
|
|
+ if (m->model != X86_MODEL_ANY && c->x86_model != m->model)
|
|
+ continue;
|
|
+ if (m->feature != X86_FEATURE_ANY && !cpu_has(c, m->feature))
|
|
+ continue;
|
|
+ return m;
|
|
+ }
|
|
+ return NULL;
|
|
+}
|
|
+EXPORT_SYMBOL(x86_match_cpu);
|
|
--- a/arch/x86/kernel/cpuid.c
|
|
+++ b/arch/x86/kernel/cpuid.c
|
|
@@ -40,6 +40,7 @@
|
|
#include <linux/notifier.h>
|
|
#include <linux/uaccess.h>
|
|
#include <linux/gfp.h>
|
|
+#include <linux/slab.h>
|
|
|
|
#include <asm/processor.h>
|
|
#include <asm/msr.h>
|
|
@@ -138,13 +139,57 @@
|
|
.open = cpuid_open,
|
|
};
|
|
|
|
+static ssize_t print_cpu_modalias(struct device *dev,
|
|
+ struct device_attribute *attr,
|
|
+ char *bufptr)
|
|
+{
|
|
+ int size = PAGE_SIZE;
|
|
+ int i, n;
|
|
+ char *buf = bufptr;
|
|
+
|
|
+ n = snprintf(buf, size, "x86cpu:vendor:%04X:family:"
|
|
+ "%04X:model:%04X:feature:",
|
|
+ boot_cpu_data.x86_vendor,
|
|
+ boot_cpu_data.x86,
|
|
+ boot_cpu_data.x86_model);
|
|
+ size -= n;
|
|
+ buf += n;
|
|
+ size -= 2;
|
|
+ for (i = 0; i < NCAPINTS*32; i++) {
|
|
+ if (boot_cpu_has(i)) {
|
|
+ n = snprintf(buf, size, ",%04X", i);
|
|
+ if (n < 0) {
|
|
+ WARN(1, "x86 features overflow page\n");
|
|
+ break;
|
|
+ }
|
|
+ size -= n;
|
|
+ buf += n;
|
|
+ }
|
|
+ }
|
|
+ *buf++ = ',';
|
|
+ *buf++ = '\n';
|
|
+ return buf - bufptr;
|
|
+}
|
|
+
|
|
+static DEVICE_ATTR(modalias, 0444, print_cpu_modalias, NULL);
|
|
+
|
|
static __cpuinit int cpuid_device_create(int cpu)
|
|
{
|
|
struct device *dev;
|
|
+ int err;
|
|
|
|
dev = device_create(cpuid_class, NULL, MKDEV(CPUID_MAJOR, cpu), NULL,
|
|
"cpu%d", cpu);
|
|
- return IS_ERR(dev) ? PTR_ERR(dev) : 0;
|
|
+ if (IS_ERR(dev))
|
|
+ return PTR_ERR(dev);
|
|
+
|
|
+ err = device_create_file(dev, &dev_attr_modalias);
|
|
+ if (err) {
|
|
+ /* keep device around on error. attribute is optional. */
|
|
+ err = 0;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
}
|
|
|
|
static void cpuid_device_destroy(int cpu)
|
|
@@ -182,6 +227,17 @@
|
|
return kasprintf(GFP_KERNEL, "cpu/%u/cpuid", MINOR(dev->devt));
|
|
}
|
|
|
|
+static int cpuid_dev_uevent(struct device *dev, struct kobj_uevent_env *env)
|
|
+{
|
|
+ char *buf = kzalloc(PAGE_SIZE, GFP_KERNEL);
|
|
+ if (buf) {
|
|
+ print_cpu_modalias(NULL, NULL, buf);
|
|
+ add_uevent_var(env, "MODALIAS=%s", buf);
|
|
+ kfree(buf);
|
|
+ }
|
|
+ return 0;
|
|
+}
|
|
+
|
|
static int __init cpuid_init(void)
|
|
{
|
|
int i, err = 0;
|
|
@@ -200,6 +256,7 @@
|
|
goto out_chrdev;
|
|
}
|
|
cpuid_class->devnode = cpuid_devnode;
|
|
+ cpuid_class->dev_uevent = cpuid_dev_uevent;
|
|
for_each_online_cpu(i) {
|
|
err = cpuid_device_create(i);
|
|
if (err != 0)
|
|
--- a/include/linux/mod_devicetable.h
|
|
+++ b/include/linux/mod_devicetable.h
|
|
@@ -542,4 +542,25 @@
|
|
kernel_ulong_t driver_data; /* data private to the driver */
|
|
};
|
|
|
|
+/*
|
|
+ * Match x86 CPUs for CPU specific drivers.
|
|
+ * See documentation of "x86_match_cpu" for details.
|
|
+ */
|
|
+
|
|
+struct x86_cpu_id {
|
|
+ __u16 vendor;
|
|
+ __u16 family;
|
|
+ __u16 model;
|
|
+ __u16 feature; /* bit index */
|
|
+ kernel_ulong_t driver_data;
|
|
+};
|
|
+
|
|
+#define X86_FEATURE_MATCH(x) \
|
|
+ { X86_VENDOR_ANY, X86_FAMILY_ANY, X86_MODEL_ANY, x }
|
|
+
|
|
+#define X86_VENDOR_ANY 0xffff
|
|
+#define X86_FAMILY_ANY 0
|
|
+#define X86_MODEL_ANY 0
|
|
+#define X86_FEATURE_ANY 0 /* Same as FPU, you can't test for that */
|
|
+
|
|
#endif /* LINUX_MOD_DEVICETABLE_H */
|
|
--- a/scripts/mod/file2alias.c
|
|
+++ b/scripts/mod/file2alias.c
|
|
@@ -880,6 +880,29 @@
|
|
return 1;
|
|
}
|
|
|
|
+/* LOOKS like x86cpu:vendor:VVVV:family:FFFF:model:MMMM:feature:*,FEAT,*
|
|
+ * All fields are numbers. It would be nicer to use strings for vendor
|
|
+ * and feature, but getting those out of the build system here is too
|
|
+ * complicated.
|
|
+ */
|
|
+
|
|
+static int do_x86cpu_entry(const char *filename, struct x86_cpu_id *id,
|
|
+ char *alias)
|
|
+{
|
|
+ id->feature = TO_NATIVE(id->feature);
|
|
+ id->family = TO_NATIVE(id->family);
|
|
+ id->model = TO_NATIVE(id->model);
|
|
+ id->vendor = TO_NATIVE(id->vendor);
|
|
+
|
|
+ strcpy(alias, "x86cpu:");
|
|
+ ADD(alias, "vendor:", id->vendor != X86_VENDOR_ANY, id->vendor);
|
|
+ ADD(alias, ":family:", id->family != X86_FAMILY_ANY, id->family);
|
|
+ ADD(alias, ":model:", id->model != X86_MODEL_ANY, id->model);
|
|
+ ADD(alias, ":feature:*,", id->feature != X86_FEATURE_ANY, id->feature);
|
|
+ strcat(alias, ",*");
|
|
+ return 1;
|
|
+}
|
|
+
|
|
/* Ignore any prefix, eg. some architectures prepend _ */
|
|
static inline int sym_is(const char *symbol, const char *name)
|
|
{
|
|
@@ -1051,6 +1074,10 @@
|
|
do_table(symval, sym->st_size,
|
|
sizeof(struct isapnp_device_id), "isa",
|
|
do_isapnp_entry, mod);
|
|
+ else if (sym_is(symname, "__mod_x86cpu_device_table"))
|
|
+ do_table(symval, sym->st_size,
|
|
+ sizeof(struct x86_cpu_id), "x86cpu",
|
|
+ do_x86cpu_entry, mod);
|
|
free(zeros);
|
|
}
|
|
|