1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Export SMBIOS/DMI info via sysfs to userspace |
4 | * |
5 | * Copyright 2007, Lennart Poettering |
6 | */ |
7 | |
8 | #include <linux/module.h> |
9 | #include <linux/kernel.h> |
10 | #include <linux/init.h> |
11 | #include <linux/dmi.h> |
12 | #include <linux/device.h> |
13 | #include <linux/slab.h> |
14 | |
15 | struct dmi_device_attribute{ |
16 | struct device_attribute dev_attr; |
17 | int field; |
18 | }; |
19 | #define to_dmi_dev_attr(_dev_attr) \ |
20 | container_of(_dev_attr, struct dmi_device_attribute, dev_attr) |
21 | |
22 | static ssize_t sys_dmi_field_show(struct device *dev, |
23 | struct device_attribute *attr, |
24 | char *page) |
25 | { |
26 | int field = to_dmi_dev_attr(attr)->field; |
27 | ssize_t len; |
28 | len = scnprintf(buf: page, PAGE_SIZE, fmt: "%s\n" , dmi_get_system_info(field)); |
29 | page[len-1] = '\n'; |
30 | return len; |
31 | } |
32 | |
33 | #define DMI_ATTR(_name, _mode, _show, _field) \ |
34 | { .dev_attr = __ATTR(_name, _mode, _show, NULL), \ |
35 | .field = _field } |
36 | |
37 | #define DEFINE_DMI_ATTR_WITH_SHOW(_name, _mode, _field) \ |
38 | static struct dmi_device_attribute sys_dmi_##_name##_attr = \ |
39 | DMI_ATTR(_name, _mode, sys_dmi_field_show, _field); |
40 | |
41 | DEFINE_DMI_ATTR_WITH_SHOW(bios_vendor, 0444, DMI_BIOS_VENDOR); |
42 | DEFINE_DMI_ATTR_WITH_SHOW(bios_version, 0444, DMI_BIOS_VERSION); |
43 | DEFINE_DMI_ATTR_WITH_SHOW(bios_date, 0444, DMI_BIOS_DATE); |
44 | DEFINE_DMI_ATTR_WITH_SHOW(sys_vendor, 0444, DMI_SYS_VENDOR); |
45 | DEFINE_DMI_ATTR_WITH_SHOW(bios_release, 0444, DMI_BIOS_RELEASE); |
46 | DEFINE_DMI_ATTR_WITH_SHOW(ec_firmware_release, 0444, DMI_EC_FIRMWARE_RELEASE); |
47 | DEFINE_DMI_ATTR_WITH_SHOW(product_name, 0444, DMI_PRODUCT_NAME); |
48 | DEFINE_DMI_ATTR_WITH_SHOW(product_version, 0444, DMI_PRODUCT_VERSION); |
49 | DEFINE_DMI_ATTR_WITH_SHOW(product_serial, 0400, DMI_PRODUCT_SERIAL); |
50 | DEFINE_DMI_ATTR_WITH_SHOW(product_uuid, 0400, DMI_PRODUCT_UUID); |
51 | DEFINE_DMI_ATTR_WITH_SHOW(product_sku, 0444, DMI_PRODUCT_SKU); |
52 | DEFINE_DMI_ATTR_WITH_SHOW(product_family, 0444, DMI_PRODUCT_FAMILY); |
53 | DEFINE_DMI_ATTR_WITH_SHOW(board_vendor, 0444, DMI_BOARD_VENDOR); |
54 | DEFINE_DMI_ATTR_WITH_SHOW(board_name, 0444, DMI_BOARD_NAME); |
55 | DEFINE_DMI_ATTR_WITH_SHOW(board_version, 0444, DMI_BOARD_VERSION); |
56 | DEFINE_DMI_ATTR_WITH_SHOW(board_serial, 0400, DMI_BOARD_SERIAL); |
57 | DEFINE_DMI_ATTR_WITH_SHOW(board_asset_tag, 0444, DMI_BOARD_ASSET_TAG); |
58 | DEFINE_DMI_ATTR_WITH_SHOW(chassis_vendor, 0444, DMI_CHASSIS_VENDOR); |
59 | DEFINE_DMI_ATTR_WITH_SHOW(chassis_type, 0444, DMI_CHASSIS_TYPE); |
60 | DEFINE_DMI_ATTR_WITH_SHOW(chassis_version, 0444, DMI_CHASSIS_VERSION); |
61 | DEFINE_DMI_ATTR_WITH_SHOW(chassis_serial, 0400, DMI_CHASSIS_SERIAL); |
62 | DEFINE_DMI_ATTR_WITH_SHOW(chassis_asset_tag, 0444, DMI_CHASSIS_ASSET_TAG); |
63 | |
64 | static void ascii_filter(char *d, const char *s) |
65 | { |
66 | /* Filter out characters we don't want to see in the modalias string */ |
67 | for (; *s; s++) |
68 | if (*s > ' ' && *s < 127 && *s != ':') |
69 | *(d++) = *s; |
70 | |
71 | *d = 0; |
72 | } |
73 | |
74 | static ssize_t get_modalias(char *buffer, size_t buffer_size) |
75 | { |
76 | /* |
77 | * Note new fields need to be added at the end to keep compatibility |
78 | * with udev's hwdb which does matches on "`cat dmi/id/modalias`*". |
79 | */ |
80 | static const struct mafield { |
81 | const char *prefix; |
82 | int field; |
83 | } fields[] = { |
84 | { "bvn" , DMI_BIOS_VENDOR }, |
85 | { "bvr" , DMI_BIOS_VERSION }, |
86 | { "bd" , DMI_BIOS_DATE }, |
87 | { "br" , DMI_BIOS_RELEASE }, |
88 | { "efr" , DMI_EC_FIRMWARE_RELEASE }, |
89 | { "svn" , DMI_SYS_VENDOR }, |
90 | { "pn" , DMI_PRODUCT_NAME }, |
91 | { "pvr" , DMI_PRODUCT_VERSION }, |
92 | { "rvn" , DMI_BOARD_VENDOR }, |
93 | { "rn" , DMI_BOARD_NAME }, |
94 | { "rvr" , DMI_BOARD_VERSION }, |
95 | { "cvn" , DMI_CHASSIS_VENDOR }, |
96 | { "ct" , DMI_CHASSIS_TYPE }, |
97 | { "cvr" , DMI_CHASSIS_VERSION }, |
98 | { "sku" , DMI_PRODUCT_SKU }, |
99 | { NULL, DMI_NONE } |
100 | }; |
101 | |
102 | ssize_t l, left; |
103 | char *p; |
104 | const struct mafield *f; |
105 | |
106 | strcpy(p: buffer, q: "dmi" ); |
107 | p = buffer + 3; left = buffer_size - 4; |
108 | |
109 | for (f = fields; f->prefix && left > 0; f++) { |
110 | const char *c; |
111 | char *t; |
112 | |
113 | c = dmi_get_system_info(field: f->field); |
114 | if (!c) |
115 | continue; |
116 | |
117 | t = kmalloc(strlen(c) + 1, GFP_KERNEL); |
118 | if (!t) |
119 | break; |
120 | ascii_filter(d: t, s: c); |
121 | l = scnprintf(buf: p, size: left, fmt: ":%s%s" , f->prefix, t); |
122 | kfree(objp: t); |
123 | |
124 | p += l; |
125 | left -= l; |
126 | } |
127 | |
128 | p[0] = ':'; |
129 | p[1] = 0; |
130 | |
131 | return p - buffer + 1; |
132 | } |
133 | |
134 | static ssize_t sys_dmi_modalias_show(struct device *dev, |
135 | struct device_attribute *attr, char *page) |
136 | { |
137 | ssize_t r; |
138 | r = get_modalias(buffer: page, PAGE_SIZE-1); |
139 | page[r] = '\n'; |
140 | page[r+1] = 0; |
141 | return r+1; |
142 | } |
143 | |
144 | static struct device_attribute sys_dmi_modalias_attr = |
145 | __ATTR(modalias, 0444, sys_dmi_modalias_show, NULL); |
146 | |
147 | static struct attribute *sys_dmi_attributes[DMI_STRING_MAX+2]; |
148 | |
149 | static struct attribute_group sys_dmi_attribute_group = { |
150 | .attrs = sys_dmi_attributes, |
151 | }; |
152 | |
153 | static const struct attribute_group* sys_dmi_attribute_groups[] = { |
154 | &sys_dmi_attribute_group, |
155 | NULL |
156 | }; |
157 | |
158 | static int dmi_dev_uevent(const struct device *dev, struct kobj_uevent_env *env) |
159 | { |
160 | ssize_t len; |
161 | |
162 | if (add_uevent_var(env, format: "MODALIAS=" )) |
163 | return -ENOMEM; |
164 | len = get_modalias(buffer: &env->buf[env->buflen - 1], |
165 | buffer_size: sizeof(env->buf) - env->buflen); |
166 | if (len >= (sizeof(env->buf) - env->buflen)) |
167 | return -ENOMEM; |
168 | env->buflen += len; |
169 | return 0; |
170 | } |
171 | |
172 | static struct class dmi_class = { |
173 | .name = "dmi" , |
174 | .dev_release = (void(*)(struct device *)) kfree, |
175 | .dev_uevent = dmi_dev_uevent, |
176 | }; |
177 | |
178 | static struct device *dmi_dev; |
179 | |
180 | /* Initialization */ |
181 | |
182 | #define ADD_DMI_ATTR(_name, _field) \ |
183 | if (dmi_get_system_info(_field)) \ |
184 | sys_dmi_attributes[i++] = &sys_dmi_##_name##_attr.dev_attr.attr; |
185 | |
186 | /* In a separate function to keep gcc 3.2 happy - do NOT merge this in |
187 | dmi_id_init! */ |
188 | static void __init dmi_id_init_attr_table(void) |
189 | { |
190 | int i; |
191 | |
192 | /* Not necessarily all DMI fields are available on all |
193 | * systems, hence let's built an attribute table of just |
194 | * what's available */ |
195 | i = 0; |
196 | ADD_DMI_ATTR(bios_vendor, DMI_BIOS_VENDOR); |
197 | ADD_DMI_ATTR(bios_version, DMI_BIOS_VERSION); |
198 | ADD_DMI_ATTR(bios_date, DMI_BIOS_DATE); |
199 | ADD_DMI_ATTR(bios_release, DMI_BIOS_RELEASE); |
200 | ADD_DMI_ATTR(ec_firmware_release, DMI_EC_FIRMWARE_RELEASE); |
201 | ADD_DMI_ATTR(sys_vendor, DMI_SYS_VENDOR); |
202 | ADD_DMI_ATTR(product_name, DMI_PRODUCT_NAME); |
203 | ADD_DMI_ATTR(product_version, DMI_PRODUCT_VERSION); |
204 | ADD_DMI_ATTR(product_serial, DMI_PRODUCT_SERIAL); |
205 | ADD_DMI_ATTR(product_uuid, DMI_PRODUCT_UUID); |
206 | ADD_DMI_ATTR(product_family, DMI_PRODUCT_FAMILY); |
207 | ADD_DMI_ATTR(product_sku, DMI_PRODUCT_SKU); |
208 | ADD_DMI_ATTR(board_vendor, DMI_BOARD_VENDOR); |
209 | ADD_DMI_ATTR(board_name, DMI_BOARD_NAME); |
210 | ADD_DMI_ATTR(board_version, DMI_BOARD_VERSION); |
211 | ADD_DMI_ATTR(board_serial, DMI_BOARD_SERIAL); |
212 | ADD_DMI_ATTR(board_asset_tag, DMI_BOARD_ASSET_TAG); |
213 | ADD_DMI_ATTR(chassis_vendor, DMI_CHASSIS_VENDOR); |
214 | ADD_DMI_ATTR(chassis_type, DMI_CHASSIS_TYPE); |
215 | ADD_DMI_ATTR(chassis_version, DMI_CHASSIS_VERSION); |
216 | ADD_DMI_ATTR(chassis_serial, DMI_CHASSIS_SERIAL); |
217 | ADD_DMI_ATTR(chassis_asset_tag, DMI_CHASSIS_ASSET_TAG); |
218 | sys_dmi_attributes[i++] = &sys_dmi_modalias_attr.attr; |
219 | } |
220 | |
221 | static int __init dmi_id_init(void) |
222 | { |
223 | int ret; |
224 | |
225 | if (!dmi_available) |
226 | return -ENODEV; |
227 | |
228 | dmi_id_init_attr_table(); |
229 | |
230 | ret = class_register(class: &dmi_class); |
231 | if (ret) |
232 | return ret; |
233 | |
234 | dmi_dev = kzalloc(size: sizeof(*dmi_dev), GFP_KERNEL); |
235 | if (!dmi_dev) { |
236 | ret = -ENOMEM; |
237 | goto fail_class_unregister; |
238 | } |
239 | |
240 | dmi_dev->class = &dmi_class; |
241 | dev_set_name(dev: dmi_dev, name: "id" ); |
242 | dmi_dev->groups = sys_dmi_attribute_groups; |
243 | |
244 | ret = device_register(dev: dmi_dev); |
245 | if (ret) |
246 | goto fail_put_dmi_dev; |
247 | |
248 | return 0; |
249 | |
250 | fail_put_dmi_dev: |
251 | put_device(dev: dmi_dev); |
252 | |
253 | fail_class_unregister: |
254 | class_unregister(class: &dmi_class); |
255 | |
256 | return ret; |
257 | } |
258 | |
259 | arch_initcall(dmi_id_init); |
260 | |