aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/firmware/dmi-id.c
diff options
context:
space:
mode:
authorLennart Poettering <mzxreary@0pointer.de>2007-05-08 16:07:02 -0400
committerGreg Kroah-Hartman <gregkh@suse.de>2007-07-11 19:09:00 -0400
commit4f5c791a850e5305a5b1b48d0e4b4de248dc96f9 (patch)
tree3059bba718b9f8d5c07081221b1ab23845638935 /drivers/firmware/dmi-id.c
parentcfc94cdf8e0f14e692a5a40ef3cc10f464b2511b (diff)
DMI-based module autoloading
The patch below adds DMI/SMBIOS based module autoloading to the Linux kernel. The idea is to load laptop drivers automatically (and other drivers which cannot be autoloaded otherwise), based on the DMI system identification information of the BIOS. Right now most distros manually try to load all available laptop drivers on bootup in the hope that at least one of them loads successfully. This patch does away with all that, and uses udev to automatically load matching drivers on the right machines. Basically the patch just exports the DMI information that has been parsed by the kernel anyway to userspace via a sysfs device /sys/class/dmi/id and makes sure that proper modalias attributes are available. Besides adding the "modalias" attribute it also adds attributes for a few other DMI fields which might be useful for writing udev rules. This patch is not an attempt to export the entire DMI/SMBIOS data to userspace. We already have "dmidecode" which parses the complete DMI info from userspace. The purpose of this patch is machine model identification and good udev integration. To take advantage of DMI based module autoloading, a driver should export one or more MODULE_ALIAS fields similar to these: MODULE_ALIAS("dmi:*:svnMICRO-STARINT'LCO.,LTD:pnMS-1013:pvr0131*:cvnMICRO-STARINT'LCO.,LTD:ct10:*"); MODULE_ALIAS("dmi:*:svnMicro-StarInternational:pnMS-1058:pvr0581:rvnMSI:rnMS-1058:*:ct10:*"); MODULE_ALIAS("dmi:*:svnMicro-StarInternational:pnMS-1412:*:rvnMSI:rnMS-1412:*:cvnMICRO-STARINT'LCO.,LTD:ct10:*"); MODULE_ALIAS("dmi:*:svnNOTEBOOK:pnSAM2000:pvr0131*:cvnMICRO-STARINT'LCO.,LTD:ct10:*"); These lines are specific to my msi-laptop.c driver. They are basically just a concatenation of a few carefully selected DMI fields with all potentially bad characters stripped. Besides laptop drivers, modules like "hdaps", the i2c modules and the hwmon modules are good candidates for "dmi:" MODULE_ALIAS lines. Besides merely exporting the DMI data via sysfs the patch adds support for a few more DMI fields. Especially the CHASSIS fields are very useful to identify different laptop modules. The patch also adds working MODULE_ALIAS lines to my msi-laptop.c driver. I'd like to thank Kay Sievers for helping me to clean up this patch for posting it on lkml. Patch is against Linus' current GIT HEAD. Should probably apply to older kernels as well without modification. Signed-off-by: Lennart Poettering <mzxreary@0pointer.de> Signed-off-by: Kay Sievers <kay.sievers@vrfy.org> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
Diffstat (limited to 'drivers/firmware/dmi-id.c')
-rw-r--r--drivers/firmware/dmi-id.c222
1 files changed, 222 insertions, 0 deletions
diff --git a/drivers/firmware/dmi-id.c b/drivers/firmware/dmi-id.c
new file mode 100644
index 000000000000..59c3b5aa89f4
--- /dev/null
+++ b/drivers/firmware/dmi-id.c
@@ -0,0 +1,222 @@
1/*
2 * Export SMBIOS/DMI info via sysfs to userspace
3 *
4 * Copyright 2007, Lennart Poettering
5 *
6 * Licensed under GPLv2
7 */
8
9#include <linux/module.h>
10#include <linux/kernel.h>
11#include <linux/init.h>
12#include <linux/dmi.h>
13#include <linux/device.h>
14#include <linux/autoconf.h>
15
16#define DEFINE_DMI_ATTR(_name, _mode, _show) \
17static struct device_attribute sys_dmi_##_name##_attr = \
18 __ATTR(_name, _mode, _show, NULL);
19
20#define DEFINE_DMI_ATTR_WITH_SHOW(_name, _mode, _field) \
21static ssize_t sys_dmi_##_name##_show(struct device *dev, \
22 struct device_attribute *attr, \
23 char *page) \
24{ \
25 ssize_t len; \
26 len = scnprintf(page, PAGE_SIZE, "%s\n", dmi_get_system_info(_field)); \
27 page[len-1] = '\n'; \
28 return len; \
29} \
30DEFINE_DMI_ATTR(_name, _mode, sys_dmi_##_name##_show);
31
32DEFINE_DMI_ATTR_WITH_SHOW(bios_vendor, 0444, DMI_BIOS_VENDOR);
33DEFINE_DMI_ATTR_WITH_SHOW(bios_version, 0444, DMI_BIOS_VERSION);
34DEFINE_DMI_ATTR_WITH_SHOW(bios_date, 0444, DMI_BIOS_DATE);
35DEFINE_DMI_ATTR_WITH_SHOW(sys_vendor, 0444, DMI_SYS_VENDOR);
36DEFINE_DMI_ATTR_WITH_SHOW(product_name, 0444, DMI_PRODUCT_NAME);
37DEFINE_DMI_ATTR_WITH_SHOW(product_version, 0444, DMI_PRODUCT_VERSION);
38DEFINE_DMI_ATTR_WITH_SHOW(product_serial, 0400, DMI_PRODUCT_SERIAL);
39DEFINE_DMI_ATTR_WITH_SHOW(product_uuid, 0400, DMI_PRODUCT_UUID);
40DEFINE_DMI_ATTR_WITH_SHOW(board_vendor, 0444, DMI_BOARD_VENDOR);
41DEFINE_DMI_ATTR_WITH_SHOW(board_name, 0444, DMI_BOARD_NAME);
42DEFINE_DMI_ATTR_WITH_SHOW(board_version, 0444, DMI_BOARD_VERSION);
43DEFINE_DMI_ATTR_WITH_SHOW(board_serial, 0400, DMI_BOARD_SERIAL);
44DEFINE_DMI_ATTR_WITH_SHOW(board_asset_tag, 0444, DMI_BOARD_ASSET_TAG);
45DEFINE_DMI_ATTR_WITH_SHOW(chassis_vendor, 0444, DMI_CHASSIS_VENDOR);
46DEFINE_DMI_ATTR_WITH_SHOW(chassis_type, 0444, DMI_CHASSIS_TYPE);
47DEFINE_DMI_ATTR_WITH_SHOW(chassis_version, 0444, DMI_CHASSIS_VERSION);
48DEFINE_DMI_ATTR_WITH_SHOW(chassis_serial, 0400, DMI_CHASSIS_SERIAL);
49DEFINE_DMI_ATTR_WITH_SHOW(chassis_asset_tag, 0444, DMI_CHASSIS_ASSET_TAG);
50
51static void ascii_filter(char *d, const char *s)
52{
53 /* Filter out characters we don't want to see in the modalias string */
54 for (; *s; s++)
55 if (*s > ' ' && *s < 127 && *s != ':')
56 *(d++) = *s;
57
58 *d = 0;
59}
60
61static ssize_t get_modalias(char *buffer, size_t buffer_size)
62{
63 static const struct mafield {
64 const char *prefix;
65 int field;
66 } fields[] = {
67 { "bvn", DMI_BIOS_VENDOR },
68 { "bvr", DMI_BIOS_VERSION },
69 { "bd", DMI_BIOS_DATE },
70 { "svn", DMI_SYS_VENDOR },
71 { "pn", DMI_PRODUCT_NAME },
72 { "pvr", DMI_PRODUCT_VERSION },
73 { "rvn", DMI_BOARD_VENDOR },
74 { "rn", DMI_BOARD_NAME },
75 { "rvr", DMI_BOARD_VERSION },
76 { "cvn", DMI_CHASSIS_VENDOR },
77 { "ct", DMI_CHASSIS_TYPE },
78 { "cvr", DMI_CHASSIS_VERSION },
79 { NULL, DMI_NONE }
80 };
81
82 ssize_t l, left;
83 char *p;
84 const struct mafield *f;
85
86 strcpy(buffer, "dmi");
87 p = buffer + 3; left = buffer_size - 4;
88
89 for (f = fields; f->prefix && left > 0; f++) {
90 const char *c;
91 char *t;
92
93 c = dmi_get_system_info(f->field);
94 if (!c)
95 continue;
96
97 t = kmalloc(strlen(c) + 1, GFP_KERNEL);
98 if (!t)
99 break;
100 ascii_filter(t, c);
101 l = scnprintf(p, left, ":%s%s", f->prefix, t);
102 kfree(t);
103
104 p += l;
105 left -= l;
106 }
107
108 p[0] = ':';
109 p[1] = 0;
110
111 return p - buffer + 1;
112}
113
114static ssize_t sys_dmi_modalias_show(struct device *dev,
115 struct device_attribute *attr, char *page)
116{
117 ssize_t r;
118 r = get_modalias(page, PAGE_SIZE-1);
119 page[r] = '\n';
120 page[r+1] = 0;
121 return r+1;
122}
123
124DEFINE_DMI_ATTR(modalias, 0444, sys_dmi_modalias_show);
125
126static struct attribute *sys_dmi_attributes[DMI_STRING_MAX+2];
127
128static struct attribute_group sys_dmi_attribute_group = {
129 .attrs = sys_dmi_attributes,
130};
131
132static struct attribute_group* sys_dmi_attribute_groups[] = {
133 &sys_dmi_attribute_group,
134 NULL
135};
136
137static int dmi_dev_uevent(struct device *dev, char **envp,
138 int num_envp, char *buffer, int buffer_size)
139{
140 strcpy(buffer, "MODALIAS=");
141 get_modalias(buffer+9, buffer_size-9);
142 envp[0] = buffer;
143 envp[1] = NULL;
144
145 return 0;
146}
147
148static struct class dmi_class = {
149 .name = "dmi",
150 .dev_release = (void(*)(struct device *)) kfree,
151 .dev_uevent = dmi_dev_uevent,
152};
153
154static struct device *dmi_dev;
155
156/* Initialization */
157
158#define ADD_DMI_ATTR(_name, _field) \
159 if (dmi_get_system_info(_field)) \
160 sys_dmi_attributes[i++] = & sys_dmi_##_name##_attr.attr;
161
162extern int dmi_available;
163
164static int __init dmi_id_init(void)
165{
166 int ret, i;
167
168 if (!dmi_available)
169 return -ENODEV;
170
171 /* Not necessarily all DMI fields are available on all
172 * systems, hence let's built an attribute table of just
173 * what's available */
174 i = 0;
175 ADD_DMI_ATTR(bios_vendor, DMI_BIOS_VENDOR);
176 ADD_DMI_ATTR(bios_version, DMI_BIOS_VERSION);
177 ADD_DMI_ATTR(bios_date, DMI_BIOS_DATE);
178 ADD_DMI_ATTR(sys_vendor, DMI_SYS_VENDOR);
179 ADD_DMI_ATTR(product_name, DMI_PRODUCT_NAME);
180 ADD_DMI_ATTR(product_version, DMI_PRODUCT_VERSION);
181 ADD_DMI_ATTR(product_serial, DMI_PRODUCT_SERIAL);
182 ADD_DMI_ATTR(product_uuid, DMI_PRODUCT_UUID);
183 ADD_DMI_ATTR(board_vendor, DMI_BOARD_VENDOR);
184 ADD_DMI_ATTR(board_name, DMI_BOARD_NAME);
185 ADD_DMI_ATTR(board_version, DMI_BOARD_VERSION);
186 ADD_DMI_ATTR(board_serial, DMI_BOARD_SERIAL);
187 ADD_DMI_ATTR(board_asset_tag, DMI_BOARD_ASSET_TAG);
188 ADD_DMI_ATTR(chassis_vendor, DMI_CHASSIS_VENDOR);
189 ADD_DMI_ATTR(chassis_type, DMI_CHASSIS_TYPE);
190 ADD_DMI_ATTR(chassis_version, DMI_CHASSIS_VERSION);
191 ADD_DMI_ATTR(chassis_serial, DMI_CHASSIS_SERIAL);
192 ADD_DMI_ATTR(chassis_asset_tag, DMI_CHASSIS_ASSET_TAG);
193 sys_dmi_attributes[i++] = &sys_dmi_modalias_attr.attr;
194
195 ret = class_register(&dmi_class);
196 if (ret)
197 return ret;
198
199 dmi_dev = kzalloc(sizeof(*dmi_dev), GFP_KERNEL);
200 if (!dmi_dev) {
201 ret = -ENOMEM;
202 goto fail_class_unregister;
203 }
204
205 dmi_dev->class = &dmi_class;
206 strcpy(dmi_dev->bus_id, "id");
207 dmi_dev->groups = sys_dmi_attribute_groups;
208
209 ret = device_register(dmi_dev);
210 if (ret)
211 goto fail_class_unregister;
212
213 return 0;
214
215fail_class_unregister:
216
217 class_unregister(&dmi_class);
218
219 return ret;
220}
221
222arch_initcall(dmi_id_init);