diff options
author | Ard Biesheuvel <ard.biesheuvel@linaro.org> | 2014-02-08 07:34:09 -0500 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2014-02-18 15:38:37 -0500 |
commit | 67bad2fdb754dbef14596c0b5d28b3a12c8dfe84 (patch) | |
tree | 05c3cd5f809618319346f9642549460f95149f02 | |
parent | 91219a3b20325689eb80f7598cce2dc745db171d (diff) |
cpu: add generic support for CPU feature based module autoloading
This patch adds support for advertising optional CPU features over udev
using the modalias, and for declaring compatibility with/dependency upon
such a feature in a module.
The mapping between feature numbers and actual features should be provided
by the architecture in a file called <asm/cpufeature.h> which exports the
following functions/macros:
- cpu_feature(FEAT), a preprocessor macro that maps token FEAT to a
numeric index;
- bool cpu_have_feature(n), returning whether this CPU has support for
feature #n;
- MAX_CPU_FEATURES, an upper bound for 'n' in the previous function.
The feature can then be enabled by setting CONFIG_GENERIC_CPU_AUTOPROBE
for the architecture.
For instance, a module that registers its module init function using
module_cpu_feature_match(FEAT_X, module_init_function)
will be probed automatically when the CPU's support for the 'FEAT_X'
feature is advertised over udev, and will only allow the module to be
loaded by hand if the 'FEAT_X' feature is supported.
Signed-off-by: Ard Biesheuvel <ard.biesheuvel@linaro.org>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
-rw-r--r-- | drivers/base/Kconfig | 8 | ||||
-rw-r--r-- | drivers/base/cpu.c | 50 | ||||
-rw-r--r-- | include/linux/cpufeature.h | 60 | ||||
-rw-r--r-- | include/linux/mod_devicetable.h | 9 | ||||
-rw-r--r-- | scripts/mod/devicetable-offsets.c | 3 | ||||
-rw-r--r-- | scripts/mod/file2alias.c | 10 |
6 files changed, 135 insertions, 5 deletions
diff --git a/drivers/base/Kconfig b/drivers/base/Kconfig index ec36e7772e57..3f0d3732df7f 100644 --- a/drivers/base/Kconfig +++ b/drivers/base/Kconfig | |||
@@ -185,6 +185,14 @@ config GENERIC_CPU_DEVICES | |||
185 | bool | 185 | bool |
186 | default n | 186 | default n |
187 | 187 | ||
188 | config HAVE_CPU_AUTOPROBE | ||
189 | def_bool ARCH_HAS_CPU_AUTOPROBE | ||
190 | |||
191 | config GENERIC_CPU_AUTOPROBE | ||
192 | bool | ||
193 | depends on !ARCH_HAS_CPU_AUTOPROBE | ||
194 | select HAVE_CPU_AUTOPROBE | ||
195 | |||
188 | config SOC_BUS | 196 | config SOC_BUS |
189 | bool | 197 | bool |
190 | 198 | ||
diff --git a/drivers/base/cpu.c b/drivers/base/cpu.c index f48370dfc908..8a38bf8c792f 100644 --- a/drivers/base/cpu.c +++ b/drivers/base/cpu.c | |||
@@ -15,6 +15,7 @@ | |||
15 | #include <linux/percpu.h> | 15 | #include <linux/percpu.h> |
16 | #include <linux/acpi.h> | 16 | #include <linux/acpi.h> |
17 | #include <linux/of.h> | 17 | #include <linux/of.h> |
18 | #include <linux/cpufeature.h> | ||
18 | 19 | ||
19 | #include "base.h" | 20 | #include "base.h" |
20 | 21 | ||
@@ -286,6 +287,45 @@ static void cpu_device_release(struct device *dev) | |||
286 | */ | 287 | */ |
287 | } | 288 | } |
288 | 289 | ||
290 | #ifdef CONFIG_HAVE_CPU_AUTOPROBE | ||
291 | #ifdef CONFIG_GENERIC_CPU_AUTOPROBE | ||
292 | static ssize_t print_cpu_modalias(struct device *dev, | ||
293 | struct device_attribute *attr, | ||
294 | char *buf) | ||
295 | { | ||
296 | ssize_t n; | ||
297 | u32 i; | ||
298 | |||
299 | n = sprintf(buf, "cpu:type:" CPU_FEATURE_TYPEFMT ":feature:", | ||
300 | CPU_FEATURE_TYPEVAL); | ||
301 | |||
302 | for (i = 0; i < MAX_CPU_FEATURES; i++) | ||
303 | if (cpu_have_feature(i)) { | ||
304 | if (PAGE_SIZE < n + sizeof(",XXXX\n")) { | ||
305 | WARN(1, "CPU features overflow page\n"); | ||
306 | break; | ||
307 | } | ||
308 | n += sprintf(&buf[n], ",%04X", i); | ||
309 | } | ||
310 | buf[n++] = '\n'; | ||
311 | return n; | ||
312 | } | ||
313 | #else | ||
314 | #define print_cpu_modalias arch_print_cpu_modalias | ||
315 | #endif | ||
316 | |||
317 | static int cpu_uevent(struct device *dev, struct kobj_uevent_env *env) | ||
318 | { | ||
319 | char *buf = kzalloc(PAGE_SIZE, GFP_KERNEL); | ||
320 | if (buf) { | ||
321 | print_cpu_modalias(NULL, NULL, buf); | ||
322 | add_uevent_var(env, "MODALIAS=%s", buf); | ||
323 | kfree(buf); | ||
324 | } | ||
325 | return 0; | ||
326 | } | ||
327 | #endif | ||
328 | |||
289 | /* | 329 | /* |
290 | * register_cpu - Setup a sysfs device for a CPU. | 330 | * register_cpu - Setup a sysfs device for a CPU. |
291 | * @cpu - cpu->hotpluggable field set to 1 will generate a control file in | 331 | * @cpu - cpu->hotpluggable field set to 1 will generate a control file in |
@@ -306,8 +346,8 @@ int register_cpu(struct cpu *cpu, int num) | |||
306 | cpu->dev.offline_disabled = !cpu->hotpluggable; | 346 | cpu->dev.offline_disabled = !cpu->hotpluggable; |
307 | cpu->dev.offline = !cpu_online(num); | 347 | cpu->dev.offline = !cpu_online(num); |
308 | cpu->dev.of_node = of_get_cpu_node(num, NULL); | 348 | cpu->dev.of_node = of_get_cpu_node(num, NULL); |
309 | #ifdef CONFIG_ARCH_HAS_CPU_AUTOPROBE | 349 | #ifdef CONFIG_HAVE_CPU_AUTOPROBE |
310 | cpu->dev.bus->uevent = arch_cpu_uevent; | 350 | cpu->dev.bus->uevent = cpu_uevent; |
311 | #endif | 351 | #endif |
312 | cpu->dev.groups = common_cpu_attr_groups; | 352 | cpu->dev.groups = common_cpu_attr_groups; |
313 | if (cpu->hotpluggable) | 353 | if (cpu->hotpluggable) |
@@ -330,8 +370,8 @@ struct device *get_cpu_device(unsigned cpu) | |||
330 | } | 370 | } |
331 | EXPORT_SYMBOL_GPL(get_cpu_device); | 371 | EXPORT_SYMBOL_GPL(get_cpu_device); |
332 | 372 | ||
333 | #ifdef CONFIG_ARCH_HAS_CPU_AUTOPROBE | 373 | #ifdef CONFIG_HAVE_CPU_AUTOPROBE |
334 | static DEVICE_ATTR(modalias, 0444, arch_print_cpu_modalias, NULL); | 374 | static DEVICE_ATTR(modalias, 0444, print_cpu_modalias, NULL); |
335 | #endif | 375 | #endif |
336 | 376 | ||
337 | static struct attribute *cpu_root_attrs[] = { | 377 | static struct attribute *cpu_root_attrs[] = { |
@@ -344,7 +384,7 @@ static struct attribute *cpu_root_attrs[] = { | |||
344 | &cpu_attrs[2].attr.attr, | 384 | &cpu_attrs[2].attr.attr, |
345 | &dev_attr_kernel_max.attr, | 385 | &dev_attr_kernel_max.attr, |
346 | &dev_attr_offline.attr, | 386 | &dev_attr_offline.attr, |
347 | #ifdef CONFIG_ARCH_HAS_CPU_AUTOPROBE | 387 | #ifdef CONFIG_HAVE_CPU_AUTOPROBE |
348 | &dev_attr_modalias.attr, | 388 | &dev_attr_modalias.attr, |
349 | #endif | 389 | #endif |
350 | NULL | 390 | NULL |
diff --git a/include/linux/cpufeature.h b/include/linux/cpufeature.h new file mode 100644 index 000000000000..c4d4eb8ac9fe --- /dev/null +++ b/include/linux/cpufeature.h | |||
@@ -0,0 +1,60 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2014 Linaro Ltd. <ard.biesheuvel@linaro.org> | ||
3 | * | ||
4 | * This program is free software; you can redistribute it and/or modify | ||
5 | * it under the terms of the GNU General Public License version 2 as | ||
6 | * published by the Free Software Foundation. | ||
7 | */ | ||
8 | |||
9 | #ifndef __LINUX_CPUFEATURE_H | ||
10 | #define __LINUX_CPUFEATURE_H | ||
11 | |||
12 | #ifdef CONFIG_GENERIC_CPU_AUTOPROBE | ||
13 | |||
14 | #include <linux/mod_devicetable.h> | ||
15 | #include <asm/cpufeature.h> | ||
16 | |||
17 | /* | ||
18 | * Macros imported from <asm/cpufeature.h>: | ||
19 | * - cpu_feature(x) ordinal value of feature called 'x' | ||
20 | * - cpu_have_feature(u32 n) whether feature #n is available | ||
21 | * - MAX_CPU_FEATURES upper bound for feature ordinal values | ||
22 | * Optional: | ||
23 | * - CPU_FEATURE_TYPEFMT format string fragment for printing the cpu type | ||
24 | * - CPU_FEATURE_TYPEVAL set of values matching the format string above | ||
25 | */ | ||
26 | |||
27 | #ifndef CPU_FEATURE_TYPEFMT | ||
28 | #define CPU_FEATURE_TYPEFMT "%s" | ||
29 | #endif | ||
30 | |||
31 | #ifndef CPU_FEATURE_TYPEVAL | ||
32 | #define CPU_FEATURE_TYPEVAL ELF_PLATFORM | ||
33 | #endif | ||
34 | |||
35 | /* | ||
36 | * Use module_cpu_feature_match(feature, module_init_function) to | ||
37 | * declare that | ||
38 | * a) the module shall be probed upon discovery of CPU feature 'feature' | ||
39 | * (typically at boot time using udev) | ||
40 | * b) the module must not be loaded if CPU feature 'feature' is not present | ||
41 | * (not even by manual insmod). | ||
42 | * | ||
43 | * For a list of legal values for 'feature', please consult the file | ||
44 | * 'asm/cpufeature.h' of your favorite architecture. | ||
45 | */ | ||
46 | #define module_cpu_feature_match(x, __init) \ | ||
47 | static struct cpu_feature const cpu_feature_match_ ## x[] = \ | ||
48 | { { .feature = cpu_feature(x) }, { } }; \ | ||
49 | MODULE_DEVICE_TABLE(cpu, cpu_feature_match_ ## x); \ | ||
50 | \ | ||
51 | static int cpu_feature_match_ ## x ## _init(void) \ | ||
52 | { \ | ||
53 | if (!cpu_have_feature(cpu_feature(x))) \ | ||
54 | return -ENODEV; \ | ||
55 | return __init(); \ | ||
56 | } \ | ||
57 | module_init(cpu_feature_match_ ## x ## _init) | ||
58 | |||
59 | #endif | ||
60 | #endif | ||
diff --git a/include/linux/mod_devicetable.h b/include/linux/mod_devicetable.h index 45e921401b06..f2ac87c613a5 100644 --- a/include/linux/mod_devicetable.h +++ b/include/linux/mod_devicetable.h | |||
@@ -564,6 +564,15 @@ struct x86_cpu_id { | |||
564 | #define X86_MODEL_ANY 0 | 564 | #define X86_MODEL_ANY 0 |
565 | #define X86_FEATURE_ANY 0 /* Same as FPU, you can't test for that */ | 565 | #define X86_FEATURE_ANY 0 /* Same as FPU, you can't test for that */ |
566 | 566 | ||
567 | /* | ||
568 | * Generic table type for matching CPU features. | ||
569 | * @feature: the bit number of the feature (0 - 65535) | ||
570 | */ | ||
571 | |||
572 | struct cpu_feature { | ||
573 | __u16 feature; | ||
574 | }; | ||
575 | |||
567 | #define IPACK_ANY_FORMAT 0xff | 576 | #define IPACK_ANY_FORMAT 0xff |
568 | #define IPACK_ANY_ID (~0) | 577 | #define IPACK_ANY_ID (~0) |
569 | struct ipack_device_id { | 578 | struct ipack_device_id { |
diff --git a/scripts/mod/devicetable-offsets.c b/scripts/mod/devicetable-offsets.c index bb5d115ca671..f282516acc7b 100644 --- a/scripts/mod/devicetable-offsets.c +++ b/scripts/mod/devicetable-offsets.c | |||
@@ -174,6 +174,9 @@ int main(void) | |||
174 | DEVID_FIELD(x86_cpu_id, model); | 174 | DEVID_FIELD(x86_cpu_id, model); |
175 | DEVID_FIELD(x86_cpu_id, vendor); | 175 | DEVID_FIELD(x86_cpu_id, vendor); |
176 | 176 | ||
177 | DEVID(cpu_feature); | ||
178 | DEVID_FIELD(cpu_feature, feature); | ||
179 | |||
177 | DEVID(mei_cl_device_id); | 180 | DEVID(mei_cl_device_id); |
178 | DEVID_FIELD(mei_cl_device_id, name); | 181 | DEVID_FIELD(mei_cl_device_id, name); |
179 | 182 | ||
diff --git a/scripts/mod/file2alias.c b/scripts/mod/file2alias.c index 25e5cb0aaef6..506146e5f4a8 100644 --- a/scripts/mod/file2alias.c +++ b/scripts/mod/file2alias.c | |||
@@ -1135,6 +1135,16 @@ static int do_x86cpu_entry(const char *filename, void *symval, | |||
1135 | } | 1135 | } |
1136 | ADD_TO_DEVTABLE("x86cpu", x86_cpu_id, do_x86cpu_entry); | 1136 | ADD_TO_DEVTABLE("x86cpu", x86_cpu_id, do_x86cpu_entry); |
1137 | 1137 | ||
1138 | /* LOOKS like cpu:type:*:feature:*FEAT* */ | ||
1139 | static int do_cpu_entry(const char *filename, void *symval, char *alias) | ||
1140 | { | ||
1141 | DEF_FIELD(symval, cpu_feature, feature); | ||
1142 | |||
1143 | sprintf(alias, "cpu:type:*:feature:*%04X*", feature); | ||
1144 | return 1; | ||
1145 | } | ||
1146 | ADD_TO_DEVTABLE("cpu", cpu_feature, do_cpu_entry); | ||
1147 | |||
1138 | /* Looks like: mei:S */ | 1148 | /* Looks like: mei:S */ |
1139 | static int do_mei_entry(const char *filename, void *symval, | 1149 | static int do_mei_entry(const char *filename, void *symval, |
1140 | char *alias) | 1150 | char *alias) |