diff mbox

[2/5] cpu: add generic support for CPU feature based module autoloading

Message ID 1391014246-9715-3-git-send-email-ard.biesheuvel@linaro.org (mailing list archive)
State New, archived
Headers show

Commit Message

Ard Biesheuvel Jan. 29, 2014, 4:50 p.m. UTC
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.

This feature can be enabled by setting CONFIG_GENERIC_CPU_AUTOPROBE for the
architecture, and supplying a file <asm/cpufeature.h> which defines how the
numbers used in the modalias string map to actual CPU features.

Signed-off-by: Ard Biesheuvel <ard.biesheuvel@linaro.org>
---
 drivers/base/Kconfig              |  8 ++++++
 drivers/base/cpu.c                | 39 +++++++++++++++++++++----
 include/linux/cpufeature.h        | 60 +++++++++++++++++++++++++++++++++++++++
 include/linux/mod_devicetable.h   |  9 ++++++
 scripts/mod/devicetable-offsets.c |  3 ++
 scripts/mod/file2alias.c          | 10 +++++++
 6 files changed, 123 insertions(+), 6 deletions(-)
 create mode 100644 include/linux/cpufeature.h

Comments

Ard Biesheuvel Feb. 3, 2014, 5:13 p.m. UTC | #1
Hello Greg,

Would you care to comment on this patch?

It adds some Kconfig clutter, but this is removed again in the next patch
(http://marc.info/?l=linux-arm-kernel&m=139101443708663&w=2),
which has already been acked by hpa here:
http://marc.info/?l=linux-arm-kernel&m=139101589509168&w=2

Example enabling it for arm64:
http://marc.info/?l=linux-arm-kernel&m=139101486308822&w=2

Example using it (optional AES instructions on arm64):
http://marc.info/?l=linux-arm-kernel&m=139101446008665&w=2

Best regards,
Ard.



On 29 January 2014 17:50, Ard Biesheuvel <ard.biesheuvel@linaro.org> wrote:
> 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.
>
> This feature can be enabled by setting CONFIG_GENERIC_CPU_AUTOPROBE for the
> architecture, and supplying a file <asm/cpufeature.h> which defines how the
> numbers used in the modalias string map to actual CPU features.
>
> Signed-off-by: Ard Biesheuvel <ard.biesheuvel@linaro.org>
> ---
>  drivers/base/Kconfig              |  8 ++++++
>  drivers/base/cpu.c                | 39 +++++++++++++++++++++----
>  include/linux/cpufeature.h        | 60 +++++++++++++++++++++++++++++++++++++++
>  include/linux/mod_devicetable.h   |  9 ++++++
>  scripts/mod/devicetable-offsets.c |  3 ++
>  scripts/mod/file2alias.c          | 10 +++++++
>  6 files changed, 123 insertions(+), 6 deletions(-)
>  create mode 100644 include/linux/cpufeature.h
>
> 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
>         bool
>         default n
>
> +config HAVE_CPU_AUTOPROBE
> +       def_bool ARCH_HAS_CPU_AUTOPROBE
> +
> +config GENERIC_CPU_AUTOPROBE
> +       bool
> +       depends on !ARCH_HAS_CPU_AUTOPROBE
> +       select HAVE_CPU_AUTOPROBE
> +
>  config SOC_BUS
>         bool
>
> diff --git a/drivers/base/cpu.c b/drivers/base/cpu.c
> index 270649012e64..8a38bf8c792f 100644
> --- a/drivers/base/cpu.c
> +++ b/drivers/base/cpu.c
> @@ -15,6 +15,7 @@
>  #include <linux/percpu.h>
>  #include <linux/acpi.h>
>  #include <linux/of.h>
> +#include <linux/cpufeature.h>
>
>  #include "base.h"
>
> @@ -286,12 +287,38 @@ static void cpu_device_release(struct device *dev)
>          */
>  }
>
> -#ifdef CONFIG_ARCH_HAS_CPU_AUTOPROBE
> +#ifdef CONFIG_HAVE_CPU_AUTOPROBE
> +#ifdef CONFIG_GENERIC_CPU_AUTOPROBE
> +static ssize_t print_cpu_modalias(struct device *dev,
> +                                 struct device_attribute *attr,
> +                                 char *buf)
> +{
> +       ssize_t n;
> +       u32 i;
> +
> +       n = sprintf(buf, "cpu:type:" CPU_FEATURE_TYPEFMT ":feature:",
> +                   CPU_FEATURE_TYPEVAL);
> +
> +       for (i = 0; i < MAX_CPU_FEATURES; i++)
> +               if (cpu_have_feature(i)) {
> +                       if (PAGE_SIZE < n + sizeof(",XXXX\n")) {
> +                               WARN(1, "CPU features overflow page\n");
> +                               break;
> +                       }
> +                       n += sprintf(&buf[n], ",%04X", i);
> +               }
> +       buf[n++] = '\n';
> +       return n;
> +}
> +#else
> +#define print_cpu_modalias     arch_print_cpu_modalias
> +#endif
> +
>  static int cpu_uevent(struct device *dev, struct kobj_uevent_env *env)
>  {
>         char *buf = kzalloc(PAGE_SIZE, GFP_KERNEL);
>         if (buf) {
> -               arch_print_cpu_modalias(NULL, NULL, buf);
> +               print_cpu_modalias(NULL, NULL, buf);
>                 add_uevent_var(env, "MODALIAS=%s", buf);
>                 kfree(buf);
>         }
> @@ -319,7 +346,7 @@ int register_cpu(struct cpu *cpu, int num)
>         cpu->dev.offline_disabled = !cpu->hotpluggable;
>         cpu->dev.offline = !cpu_online(num);
>         cpu->dev.of_node = of_get_cpu_node(num, NULL);
> -#ifdef CONFIG_ARCH_HAS_CPU_AUTOPROBE
> +#ifdef CONFIG_HAVE_CPU_AUTOPROBE
>         cpu->dev.bus->uevent = cpu_uevent;
>  #endif
>         cpu->dev.groups = common_cpu_attr_groups;
> @@ -343,8 +370,8 @@ struct device *get_cpu_device(unsigned cpu)
>  }
>  EXPORT_SYMBOL_GPL(get_cpu_device);
>
> -#ifdef CONFIG_ARCH_HAS_CPU_AUTOPROBE
> -static DEVICE_ATTR(modalias, 0444, arch_print_cpu_modalias, NULL);
> +#ifdef CONFIG_HAVE_CPU_AUTOPROBE
> +static DEVICE_ATTR(modalias, 0444, print_cpu_modalias, NULL);
>  #endif
>
>  static struct attribute *cpu_root_attrs[] = {
> @@ -357,7 +384,7 @@ static struct attribute *cpu_root_attrs[] = {
>         &cpu_attrs[2].attr.attr,
>         &dev_attr_kernel_max.attr,
>         &dev_attr_offline.attr,
> -#ifdef CONFIG_ARCH_HAS_CPU_AUTOPROBE
> +#ifdef CONFIG_HAVE_CPU_AUTOPROBE
>         &dev_attr_modalias.attr,
>  #endif
>         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 @@
> +/*
> + * Copyright (C) 2014 Linaro Ltd. <ard.biesheuvel@linaro.org>
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> + */
> +
> +#ifndef __LINUX_CPUFEATURE_H
> +#define __LINUX_CPUFEATURE_H
> +
> +#ifdef CONFIG_GENERIC_CPU_AUTOPROBE
> +
> +#include <linux/mod_devicetable.h>
> +#include <asm/cpufeature.h>
> +
> +/*
> + * Macros imported from <asm/cpufeature.h>:
> + * - cpu_feature(x)            ordinal value of feature called 'x'
> + * - cpu_have_feature(u32 n)   whether feature #n is available
> + * - MAX_CPU_FEATURES          upper bound for feature ordinal values
> + * Optional:
> + * - CPU_FEATURE_TYPEFMT       format string fragment for printing the cpu type
> + * - CPU_FEATURE_TYPEVAL       set of values matching the format string above
> + */
> +
> +#ifndef CPU_FEATURE_TYPEFMT
> +#define CPU_FEATURE_TYPEFMT    "%s"
> +#endif
> +
> +#ifndef CPU_FEATURE_TYPEVAL
> +#define CPU_FEATURE_TYPEVAL    ELF_PLATFORM
> +#endif
> +
> +/*
> + * Use module_cpu_feature_match(feature, module_init_function) to
> + * declare that
> + * a) the module shall be probed upon discovery of CPU feature 'feature'
> + *    (typically at boot time using udev)
> + * b) the module must not be loaded if CPU feature 'feature' is not present
> + *    (not even by manual insmod).
> + *
> + * For a list of legal values for 'feature', please consult the file
> + * 'asm/cpufeature.h' of your favorite architecture.
> + */
> +#define module_cpu_feature_match(x, __init)                    \
> +static struct cpu_feature const cpu_feature_match_ ## x[] =    \
> +       { { .feature = cpu_feature(x) }, { } };                 \
> +MODULE_DEVICE_TABLE(cpu, cpu_feature_match_ ## x);             \
> +                                                               \
> +static int cpu_feature_match_ ## x ## _init(void)              \
> +{                                                              \
> +       if (!cpu_have_feature(cpu_feature(x)))                  \
> +               return -ENODEV;                                 \
> +       return __init();                                        \
> +}                                                              \
> +module_init(cpu_feature_match_ ## x ## _init)
> +
> +#endif
> +#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 {
>  #define X86_MODEL_ANY  0
>  #define X86_FEATURE_ANY 0      /* Same as FPU, you can't test for that */
>
> +/*
> + * Generic table type for matching CPU features.
> + * @feature:   the bit number of the feature (0 - 65535)
> + */
> +
> +struct cpu_feature {
> +       __u16   feature;
> +};
> +
>  #define IPACK_ANY_FORMAT 0xff
>  #define IPACK_ANY_ID (~0)
>  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)
>         DEVID_FIELD(x86_cpu_id, model);
>         DEVID_FIELD(x86_cpu_id, vendor);
>
> +       DEVID(cpu_feature);
> +       DEVID_FIELD(cpu_feature, feature);
> +
>         DEVID(mei_cl_device_id);
>         DEVID_FIELD(mei_cl_device_id, name);
>
> diff --git a/scripts/mod/file2alias.c b/scripts/mod/file2alias.c
> index 23708636b05c..8a69005228d8 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,
>  }
>  ADD_TO_DEVTABLE("x86cpu", x86_cpu_id, do_x86cpu_entry);
>
> +/* LOOKS like cpu:type:*:feature:*FEAT* */
> +static int do_cpu_entry(const char *filename, void *symval, char *alias)
> +{
> +       DEF_FIELD(symval, cpu_feature, feature);
> +
> +       sprintf(alias, "cpu:type:*:feature:*%04X*", feature);
> +       return 1;
> +}
> +ADD_TO_DEVTABLE("cpu", cpu_feature, do_cpu_entry);
> +
>  /* Looks like: mei:S */
>  static int do_mei_entry(const char *filename, void *symval,
>                         char *alias)
> --
> 1.8.3.2
>
diff mbox

Patch

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
 	bool
 	default n
 
+config HAVE_CPU_AUTOPROBE
+	def_bool ARCH_HAS_CPU_AUTOPROBE
+
+config GENERIC_CPU_AUTOPROBE
+	bool
+	depends on !ARCH_HAS_CPU_AUTOPROBE
+	select HAVE_CPU_AUTOPROBE
+
 config SOC_BUS
 	bool
 
diff --git a/drivers/base/cpu.c b/drivers/base/cpu.c
index 270649012e64..8a38bf8c792f 100644
--- a/drivers/base/cpu.c
+++ b/drivers/base/cpu.c
@@ -15,6 +15,7 @@ 
 #include <linux/percpu.h>
 #include <linux/acpi.h>
 #include <linux/of.h>
+#include <linux/cpufeature.h>
 
 #include "base.h"
 
@@ -286,12 +287,38 @@  static void cpu_device_release(struct device *dev)
 	 */
 }
 
-#ifdef CONFIG_ARCH_HAS_CPU_AUTOPROBE
+#ifdef CONFIG_HAVE_CPU_AUTOPROBE
+#ifdef CONFIG_GENERIC_CPU_AUTOPROBE
+static ssize_t print_cpu_modalias(struct device *dev,
+				  struct device_attribute *attr,
+				  char *buf)
+{
+	ssize_t n;
+	u32 i;
+
+	n = sprintf(buf, "cpu:type:" CPU_FEATURE_TYPEFMT ":feature:",
+		    CPU_FEATURE_TYPEVAL);
+
+	for (i = 0; i < MAX_CPU_FEATURES; i++)
+		if (cpu_have_feature(i)) {
+			if (PAGE_SIZE < n + sizeof(",XXXX\n")) {
+				WARN(1, "CPU features overflow page\n");
+				break;
+			}
+			n += sprintf(&buf[n], ",%04X", i);
+		}
+	buf[n++] = '\n';
+	return n;
+}
+#else
+#define print_cpu_modalias	arch_print_cpu_modalias
+#endif
+
 static int cpu_uevent(struct device *dev, struct kobj_uevent_env *env)
 {
 	char *buf = kzalloc(PAGE_SIZE, GFP_KERNEL);
 	if (buf) {
-		arch_print_cpu_modalias(NULL, NULL, buf);
+		print_cpu_modalias(NULL, NULL, buf);
 		add_uevent_var(env, "MODALIAS=%s", buf);
 		kfree(buf);
 	}
@@ -319,7 +346,7 @@  int register_cpu(struct cpu *cpu, int num)
 	cpu->dev.offline_disabled = !cpu->hotpluggable;
 	cpu->dev.offline = !cpu_online(num);
 	cpu->dev.of_node = of_get_cpu_node(num, NULL);
-#ifdef CONFIG_ARCH_HAS_CPU_AUTOPROBE
+#ifdef CONFIG_HAVE_CPU_AUTOPROBE
 	cpu->dev.bus->uevent = cpu_uevent;
 #endif
 	cpu->dev.groups = common_cpu_attr_groups;
@@ -343,8 +370,8 @@  struct device *get_cpu_device(unsigned cpu)
 }
 EXPORT_SYMBOL_GPL(get_cpu_device);
 
-#ifdef CONFIG_ARCH_HAS_CPU_AUTOPROBE
-static DEVICE_ATTR(modalias, 0444, arch_print_cpu_modalias, NULL);
+#ifdef CONFIG_HAVE_CPU_AUTOPROBE
+static DEVICE_ATTR(modalias, 0444, print_cpu_modalias, NULL);
 #endif
 
 static struct attribute *cpu_root_attrs[] = {
@@ -357,7 +384,7 @@  static struct attribute *cpu_root_attrs[] = {
 	&cpu_attrs[2].attr.attr,
 	&dev_attr_kernel_max.attr,
 	&dev_attr_offline.attr,
-#ifdef CONFIG_ARCH_HAS_CPU_AUTOPROBE
+#ifdef CONFIG_HAVE_CPU_AUTOPROBE
 	&dev_attr_modalias.attr,
 #endif
 	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 @@ 
+/*
+ * Copyright (C) 2014 Linaro Ltd. <ard.biesheuvel@linaro.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef __LINUX_CPUFEATURE_H
+#define __LINUX_CPUFEATURE_H
+
+#ifdef CONFIG_GENERIC_CPU_AUTOPROBE
+
+#include <linux/mod_devicetable.h>
+#include <asm/cpufeature.h>
+
+/*
+ * Macros imported from <asm/cpufeature.h>:
+ * - cpu_feature(x)		ordinal value of feature called 'x'
+ * - cpu_have_feature(u32 n)	whether feature #n is available
+ * - MAX_CPU_FEATURES		upper bound for feature ordinal values
+ * Optional:
+ * - CPU_FEATURE_TYPEFMT	format string fragment for printing the cpu type
+ * - CPU_FEATURE_TYPEVAL	set of values matching the format string above
+ */
+
+#ifndef CPU_FEATURE_TYPEFMT
+#define CPU_FEATURE_TYPEFMT	"%s"
+#endif
+
+#ifndef CPU_FEATURE_TYPEVAL
+#define CPU_FEATURE_TYPEVAL	ELF_PLATFORM
+#endif
+
+/*
+ * Use module_cpu_feature_match(feature, module_init_function) to
+ * declare that
+ * a) the module shall be probed upon discovery of CPU feature 'feature'
+ *    (typically at boot time using udev)
+ * b) the module must not be loaded if CPU feature 'feature' is not present
+ *    (not even by manual insmod).
+ *
+ * For a list of legal values for 'feature', please consult the file
+ * 'asm/cpufeature.h' of your favorite architecture.
+ */
+#define module_cpu_feature_match(x, __init)			\
+static struct cpu_feature const cpu_feature_match_ ## x[] =	\
+	{ { .feature = cpu_feature(x) }, { } };			\
+MODULE_DEVICE_TABLE(cpu, cpu_feature_match_ ## x);		\
+								\
+static int cpu_feature_match_ ## x ## _init(void)		\
+{								\
+	if (!cpu_have_feature(cpu_feature(x)))			\
+		return -ENODEV;					\
+	return __init();					\
+}								\
+module_init(cpu_feature_match_ ## x ## _init)
+
+#endif
+#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 {
 #define X86_MODEL_ANY  0
 #define X86_FEATURE_ANY 0	/* Same as FPU, you can't test for that */
 
+/*
+ * Generic table type for matching CPU features.
+ * @feature:	the bit number of the feature (0 - 65535)
+ */
+
+struct cpu_feature {
+	__u16	feature;
+};
+
 #define IPACK_ANY_FORMAT 0xff
 #define IPACK_ANY_ID (~0)
 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)
 	DEVID_FIELD(x86_cpu_id, model);
 	DEVID_FIELD(x86_cpu_id, vendor);
 
+	DEVID(cpu_feature);
+	DEVID_FIELD(cpu_feature, feature);
+
 	DEVID(mei_cl_device_id);
 	DEVID_FIELD(mei_cl_device_id, name);
 
diff --git a/scripts/mod/file2alias.c b/scripts/mod/file2alias.c
index 23708636b05c..8a69005228d8 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,
 }
 ADD_TO_DEVTABLE("x86cpu", x86_cpu_id, do_x86cpu_entry);
 
+/* LOOKS like cpu:type:*:feature:*FEAT* */
+static int do_cpu_entry(const char *filename, void *symval, char *alias)
+{
+	DEF_FIELD(symval, cpu_feature, feature);
+
+	sprintf(alias, "cpu:type:*:feature:*%04X*", feature);
+	return 1;
+}
+ADD_TO_DEVTABLE("cpu", cpu_feature, do_cpu_entry);
+
 /* Looks like: mei:S */
 static int do_mei_entry(const char *filename, void *symval,
 			char *alias)