1 // SPDX-License-Identifier: GPL-2.0-only
3 * Common functions for kernel modules using Dell SMBIOS
5 * Copyright (c) Red Hat <mjg@redhat.com>
6 * Copyright (c) 2014 Gabriele Mazzotta <gabriele.mzt@gmail.com>
7 * Copyright (c) 2014 Pali Rohár <pali@kernel.org>
9 * Based on documentation in the libsmbios package:
10 * Copyright (C) 2005-2014 Dell Inc.
12 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
14 #include <linux/kernel.h>
15 #include <linux/module.h>
16 #include <linux/capability.h>
17 #include <linux/dmi.h>
18 #include <linux/err.h>
19 #include <linux/mutex.h>
20 #include <linux/platform_device.h>
21 #include <linux/slab.h>
22 #include "dell-smbios.h"
24 static u32 da_supported_commands
;
25 static int da_num_tokens
;
26 static struct platform_device
*platform_device
;
27 static struct calling_interface_token
*da_tokens
;
28 static struct device_attribute
*token_location_attrs
;
29 static struct device_attribute
*token_value_attrs
;
30 static struct attribute
**token_attrs
;
31 static DEFINE_MUTEX(smbios_mutex
);
33 struct smbios_device
{
34 struct list_head list
;
35 struct device
*device
;
36 int (*call_fn
)(struct calling_interface_buffer
*arg
);
45 /* calls that are whitelisted for given capabilities */
46 static struct smbios_call call_whitelist
[] = {
47 /* generally tokens are allowed, but may be further filtered or
48 * restricted by token blacklist or whitelist
50 {CAP_SYS_ADMIN
, CLASS_TOKEN_READ
, SELECT_TOKEN_STD
},
51 {CAP_SYS_ADMIN
, CLASS_TOKEN_READ
, SELECT_TOKEN_AC
},
52 {CAP_SYS_ADMIN
, CLASS_TOKEN_READ
, SELECT_TOKEN_BAT
},
53 {CAP_SYS_ADMIN
, CLASS_TOKEN_WRITE
, SELECT_TOKEN_STD
},
54 {CAP_SYS_ADMIN
, CLASS_TOKEN_WRITE
, SELECT_TOKEN_AC
},
55 {CAP_SYS_ADMIN
, CLASS_TOKEN_WRITE
, SELECT_TOKEN_BAT
},
56 /* used by userspace: fwupdate */
57 {CAP_SYS_ADMIN
, CLASS_ADMIN_PROP
, SELECT_ADMIN_PROP
},
58 /* used by userspace: fwupd */
59 {CAP_SYS_ADMIN
, CLASS_INFO
, SELECT_DOCK
},
60 {CAP_SYS_ADMIN
, CLASS_FLASH_INTERFACE
, SELECT_FLASH_INTERFACE
},
63 /* calls that are explicitly blacklisted */
64 static struct smbios_call call_blacklist
[] = {
65 {0x0000, 1, 7}, /* manufacturing use */
66 {0x0000, 6, 5}, /* manufacturing use */
67 {0x0000, 11, 3}, /* write once */
68 {0x0000, 11, 7}, /* write once */
69 {0x0000, 11, 11}, /* write once */
70 {0x0000, 19, -1}, /* diagnostics */
71 /* handled by kernel: dell-laptop */
72 {0x0000, CLASS_INFO
, SELECT_RFKILL
},
73 {0x0000, CLASS_KBD_BACKLIGHT
, SELECT_KBD_BACKLIGHT
},
82 /* tokens that are whitelisted for given capabilities */
83 static struct token_range token_whitelist
[] = {
84 /* used by userspace: fwupdate */
85 {CAP_SYS_ADMIN
, CAPSULE_EN_TOKEN
, CAPSULE_DIS_TOKEN
},
86 /* can indicate to userspace that WMI is needed */
87 {0x0000, WSMT_EN_TOKEN
, WSMT_DIS_TOKEN
}
90 /* tokens that are explicitly blacklisted */
91 static struct token_range token_blacklist
[] = {
92 {0x0000, 0x0058, 0x0059}, /* ME use */
93 {0x0000, 0x00CD, 0x00D0}, /* raid shadow copy */
94 {0x0000, 0x013A, 0x01FF}, /* sata shadow copy */
95 {0x0000, 0x0175, 0x0176}, /* write once */
96 {0x0000, 0x0195, 0x0197}, /* diagnostics */
97 {0x0000, 0x01DC, 0x01DD}, /* manufacturing use */
98 {0x0000, 0x027D, 0x0284}, /* diagnostics */
99 {0x0000, 0x02E3, 0x02E3}, /* manufacturing use */
100 {0x0000, 0x02FF, 0x02FF}, /* manufacturing use */
101 {0x0000, 0x0300, 0x0302}, /* manufacturing use */
102 {0x0000, 0x0325, 0x0326}, /* manufacturing use */
103 {0x0000, 0x0332, 0x0335}, /* fan control */
104 {0x0000, 0x0350, 0x0350}, /* manufacturing use */
105 {0x0000, 0x0363, 0x0363}, /* manufacturing use */
106 {0x0000, 0x0368, 0x0368}, /* manufacturing use */
107 {0x0000, 0x03F6, 0x03F7}, /* manufacturing use */
108 {0x0000, 0x049E, 0x049F}, /* manufacturing use */
109 {0x0000, 0x04A0, 0x04A3}, /* disagnostics */
110 {0x0000, 0x04E6, 0x04E7}, /* manufacturing use */
111 {0x0000, 0x4000, 0x7FFF}, /* internal BIOS use */
112 {0x0000, 0x9000, 0x9001}, /* internal BIOS use */
113 {0x0000, 0xA000, 0xBFFF}, /* write only */
114 {0x0000, 0xEFF0, 0xEFFF}, /* internal BIOS use */
115 /* handled by kernel: dell-laptop */
116 {0x0000, BRIGHTNESS_TOKEN
, BRIGHTNESS_TOKEN
},
117 {0x0000, KBD_LED_OFF_TOKEN
, KBD_LED_AUTO_TOKEN
},
118 {0x0000, KBD_LED_AC_TOKEN
, KBD_LED_AC_TOKEN
},
119 {0x0000, KBD_LED_AUTO_25_TOKEN
, KBD_LED_AUTO_75_TOKEN
},
120 {0x0000, KBD_LED_AUTO_100_TOKEN
, KBD_LED_AUTO_100_TOKEN
},
121 {0x0000, GLOBAL_MIC_MUTE_ENABLE
, GLOBAL_MIC_MUTE_DISABLE
},
124 static LIST_HEAD(smbios_device_list
);
126 int dell_smbios_error(int value
)
129 case 0: /* Completed successfully */
131 case -1: /* Completed with error */
133 case -2: /* Function not supported */
135 default: /* Unknown error */
139 EXPORT_SYMBOL_GPL(dell_smbios_error
);
141 int dell_smbios_register_device(struct device
*d
, void *call_fn
)
143 struct smbios_device
*priv
;
145 priv
= devm_kzalloc(d
, sizeof(struct smbios_device
), GFP_KERNEL
);
150 priv
->call_fn
= call_fn
;
151 mutex_lock(&smbios_mutex
);
152 list_add_tail(&priv
->list
, &smbios_device_list
);
153 mutex_unlock(&smbios_mutex
);
154 dev_dbg(d
, "Added device: %s\n", d
->driver
->name
);
157 EXPORT_SYMBOL_GPL(dell_smbios_register_device
);
159 void dell_smbios_unregister_device(struct device
*d
)
161 struct smbios_device
*priv
;
163 mutex_lock(&smbios_mutex
);
164 list_for_each_entry(priv
, &smbios_device_list
, list
) {
165 if (priv
->device
== d
) {
166 list_del(&priv
->list
);
171 mutex_unlock(&smbios_mutex
);
172 dev_dbg(d
, "Remove device: %s\n", d
->driver
->name
);
174 EXPORT_SYMBOL_GPL(dell_smbios_unregister_device
);
176 int dell_smbios_call_filter(struct device
*d
,
177 struct calling_interface_buffer
*buffer
)
182 /* can't make calls over 30 */
183 if (buffer
->cmd_class
> 30) {
184 dev_dbg(d
, "class too big: %u\n", buffer
->cmd_class
);
188 /* supported calls on the particular system */
189 if (!(da_supported_commands
& (1 << buffer
->cmd_class
))) {
190 dev_dbg(d
, "invalid command, supported commands: 0x%8x\n",
191 da_supported_commands
);
195 /* match against call blacklist */
196 for (i
= 0; i
< ARRAY_SIZE(call_blacklist
); i
++) {
197 if (buffer
->cmd_class
!= call_blacklist
[i
].cmd_class
)
199 if (buffer
->cmd_select
!= call_blacklist
[i
].cmd_select
&&
200 call_blacklist
[i
].cmd_select
!= -1)
202 dev_dbg(d
, "blacklisted command: %u/%u\n",
203 buffer
->cmd_class
, buffer
->cmd_select
);
207 /* if a token call, find token ID */
209 if ((buffer
->cmd_class
== CLASS_TOKEN_READ
||
210 buffer
->cmd_class
== CLASS_TOKEN_WRITE
) &&
211 buffer
->cmd_select
< 3) {
212 /* tokens enabled ? */
214 dev_dbg(d
, "no token support on this system\n");
218 /* find the matching token ID */
219 for (i
= 0; i
< da_num_tokens
; i
++) {
220 if (da_tokens
[i
].location
!= buffer
->input
[0])
222 t
= da_tokens
[i
].tokenID
;
226 /* token call; but token didn't exist */
228 dev_dbg(d
, "token at location %04x doesn't exist\n",
233 /* match against token blacklist */
234 for (i
= 0; i
< ARRAY_SIZE(token_blacklist
); i
++) {
235 if (!token_blacklist
[i
].min
|| !token_blacklist
[i
].max
)
237 if (t
>= token_blacklist
[i
].min
&&
238 t
<= token_blacklist
[i
].max
)
242 /* match against token whitelist */
243 for (i
= 0; i
< ARRAY_SIZE(token_whitelist
); i
++) {
244 if (!token_whitelist
[i
].min
|| !token_whitelist
[i
].max
)
246 if (t
< token_whitelist
[i
].min
||
247 t
> token_whitelist
[i
].max
)
249 if (!token_whitelist
[i
].need_capability
||
250 capable(token_whitelist
[i
].need_capability
)) {
251 dev_dbg(d
, "whitelisted token: %x\n", t
);
257 /* match against call whitelist */
258 for (i
= 0; i
< ARRAY_SIZE(call_whitelist
); i
++) {
259 if (buffer
->cmd_class
!= call_whitelist
[i
].cmd_class
)
261 if (buffer
->cmd_select
!= call_whitelist
[i
].cmd_select
)
263 if (!call_whitelist
[i
].need_capability
||
264 capable(call_whitelist
[i
].need_capability
)) {
265 dev_dbg(d
, "whitelisted capable command: %u/%u\n",
266 buffer
->cmd_class
, buffer
->cmd_select
);
269 dev_dbg(d
, "missing capability %d for %u/%u\n",
270 call_whitelist
[i
].need_capability
,
271 buffer
->cmd_class
, buffer
->cmd_select
);
275 /* not in a whitelist, only allow processes with capabilities */
276 if (capable(CAP_SYS_RAWIO
)) {
277 dev_dbg(d
, "Allowing %u/%u due to CAP_SYS_RAWIO\n",
278 buffer
->cmd_class
, buffer
->cmd_select
);
284 EXPORT_SYMBOL_GPL(dell_smbios_call_filter
);
286 int dell_smbios_call(struct calling_interface_buffer
*buffer
)
288 int (*call_fn
)(struct calling_interface_buffer
*) = NULL
;
289 struct device
*selected_dev
= NULL
;
290 struct smbios_device
*priv
;
293 mutex_lock(&smbios_mutex
);
294 list_for_each_entry(priv
, &smbios_device_list
, list
) {
295 if (!selected_dev
|| priv
->device
->id
>= selected_dev
->id
) {
296 dev_dbg(priv
->device
, "Trying device ID: %d\n",
298 call_fn
= priv
->call_fn
;
299 selected_dev
= priv
->device
;
305 pr_err("No dell-smbios drivers are loaded\n");
306 goto out_smbios_call
;
309 ret
= call_fn(buffer
);
312 mutex_unlock(&smbios_mutex
);
315 EXPORT_SYMBOL_GPL(dell_smbios_call
);
317 struct calling_interface_token
*dell_smbios_find_token(int tokenid
)
324 for (i
= 0; i
< da_num_tokens
; i
++) {
325 if (da_tokens
[i
].tokenID
== tokenid
)
326 return &da_tokens
[i
];
331 EXPORT_SYMBOL_GPL(dell_smbios_find_token
);
333 static BLOCKING_NOTIFIER_HEAD(dell_laptop_chain_head
);
335 int dell_laptop_register_notifier(struct notifier_block
*nb
)
337 return blocking_notifier_chain_register(&dell_laptop_chain_head
, nb
);
339 EXPORT_SYMBOL_GPL(dell_laptop_register_notifier
);
341 int dell_laptop_unregister_notifier(struct notifier_block
*nb
)
343 return blocking_notifier_chain_unregister(&dell_laptop_chain_head
, nb
);
345 EXPORT_SYMBOL_GPL(dell_laptop_unregister_notifier
);
347 void dell_laptop_call_notifier(unsigned long action
, void *data
)
349 blocking_notifier_call_chain(&dell_laptop_chain_head
, action
, data
);
351 EXPORT_SYMBOL_GPL(dell_laptop_call_notifier
);
353 static void __init
parse_da_table(const struct dmi_header
*dm
)
355 /* Final token is a terminator, so we don't want to copy it */
356 int tokens
= (dm
->length
-11)/sizeof(struct calling_interface_token
)-1;
357 struct calling_interface_token
*new_da_tokens
;
358 struct calling_interface_structure
*table
=
359 container_of(dm
, struct calling_interface_structure
, header
);
362 * 4 bytes of table header, plus 7 bytes of Dell header
363 * plus at least 6 bytes of entry
369 da_supported_commands
= table
->supportedCmds
;
371 new_da_tokens
= krealloc(da_tokens
, (da_num_tokens
+ tokens
) *
372 sizeof(struct calling_interface_token
),
377 da_tokens
= new_da_tokens
;
379 memcpy(da_tokens
+da_num_tokens
, table
->tokens
,
380 sizeof(struct calling_interface_token
) * tokens
);
382 da_num_tokens
+= tokens
;
385 static void zero_duplicates(struct device
*dev
)
389 for (i
= 0; i
< da_num_tokens
; i
++) {
390 if (da_tokens
[i
].tokenID
== 0)
392 for (j
= i
+1; j
< da_num_tokens
; j
++) {
393 if (da_tokens
[j
].tokenID
== 0)
395 if (da_tokens
[i
].tokenID
== da_tokens
[j
].tokenID
) {
396 dev_dbg(dev
, "Zeroing dup token ID %x(%x/%x)\n",
397 da_tokens
[j
].tokenID
,
398 da_tokens
[j
].location
,
400 da_tokens
[j
].tokenID
= 0;
406 static void __init
find_tokens(const struct dmi_header
*dm
, void *dummy
)
409 case 0xd4: /* Indexed IO */
410 case 0xd5: /* Protected Area Type 1 */
411 case 0xd6: /* Protected Area Type 2 */
413 case 0xda: /* Calling interface */
419 static int match_attribute(struct device
*dev
,
420 struct device_attribute
*attr
)
424 for (i
= 0; i
< da_num_tokens
* 2; i
++) {
427 if (strcmp(token_attrs
[i
]->name
, attr
->attr
.name
) == 0)
430 dev_dbg(dev
, "couldn't match: %s\n", attr
->attr
.name
);
434 static ssize_t
location_show(struct device
*dev
,
435 struct device_attribute
*attr
, char *buf
)
439 if (!capable(CAP_SYS_ADMIN
))
442 i
= match_attribute(dev
, attr
);
444 return scnprintf(buf
, PAGE_SIZE
, "%08x", da_tokens
[i
].location
);
448 static ssize_t
value_show(struct device
*dev
,
449 struct device_attribute
*attr
, char *buf
)
453 if (!capable(CAP_SYS_ADMIN
))
456 i
= match_attribute(dev
, attr
);
458 return scnprintf(buf
, PAGE_SIZE
, "%08x", da_tokens
[i
].value
);
462 static struct attribute_group smbios_attribute_group
= {
466 static struct platform_driver platform_driver
= {
468 .name
= "dell-smbios",
472 static int build_tokens_sysfs(struct platform_device
*dev
)
480 /* (number of tokens + 1 for null terminated */
481 size
= sizeof(struct device_attribute
) * (da_num_tokens
+ 1);
482 token_location_attrs
= kzalloc(size
, GFP_KERNEL
);
483 if (!token_location_attrs
)
485 token_value_attrs
= kzalloc(size
, GFP_KERNEL
);
486 if (!token_value_attrs
)
487 goto out_allocate_value
;
489 /* need to store both location and value + terminator*/
490 size
= sizeof(struct attribute
*) * ((2 * da_num_tokens
) + 1);
491 token_attrs
= kzalloc(size
, GFP_KERNEL
);
493 goto out_allocate_attrs
;
495 for (i
= 0, j
= 0; i
< da_num_tokens
; i
++) {
497 if (da_tokens
[i
].tokenID
== 0)
500 location_name
= kasprintf(GFP_KERNEL
, "%04x_location",
501 da_tokens
[i
].tokenID
);
502 if (location_name
== NULL
)
503 goto out_unwind_strings
;
504 sysfs_attr_init(&token_location_attrs
[i
].attr
);
505 token_location_attrs
[i
].attr
.name
= location_name
;
506 token_location_attrs
[i
].attr
.mode
= 0444;
507 token_location_attrs
[i
].show
= location_show
;
508 token_attrs
[j
++] = &token_location_attrs
[i
].attr
;
511 value_name
= kasprintf(GFP_KERNEL
, "%04x_value",
512 da_tokens
[i
].tokenID
);
513 if (value_name
== NULL
)
514 goto loop_fail_create_value
;
515 sysfs_attr_init(&token_value_attrs
[i
].attr
);
516 token_value_attrs
[i
].attr
.name
= value_name
;
517 token_value_attrs
[i
].attr
.mode
= 0444;
518 token_value_attrs
[i
].show
= value_show
;
519 token_attrs
[j
++] = &token_value_attrs
[i
].attr
;
522 loop_fail_create_value
:
523 kfree(location_name
);
524 goto out_unwind_strings
;
526 smbios_attribute_group
.attrs
= token_attrs
;
528 ret
= sysfs_create_group(&dev
->dev
.kobj
, &smbios_attribute_group
);
530 goto out_unwind_strings
;
535 kfree(token_location_attrs
[i
].attr
.name
);
536 kfree(token_value_attrs
[i
].attr
.name
);
540 kfree(token_value_attrs
);
542 kfree(token_location_attrs
);
547 static void free_group(struct platform_device
*pdev
)
551 sysfs_remove_group(&pdev
->dev
.kobj
,
552 &smbios_attribute_group
);
553 for (i
= 0; i
< da_num_tokens
; i
++) {
554 kfree(token_location_attrs
[i
].attr
.name
);
555 kfree(token_value_attrs
[i
].attr
.name
);
558 kfree(token_value_attrs
);
559 kfree(token_location_attrs
);
562 static int __init
dell_smbios_init(void)
566 if (!dmi_find_device(DMI_DEV_TYPE_OEM_STRING
, "Dell System", NULL
) &&
567 !dmi_find_device(DMI_DEV_TYPE_OEM_STRING
, "www.dell.com", NULL
)) {
568 pr_err("Unable to run on non-Dell system\n");
572 dmi_walk(find_tokens
, NULL
);
574 ret
= platform_driver_register(&platform_driver
);
576 goto fail_platform_driver
;
578 platform_device
= platform_device_alloc("dell-smbios", 0);
579 if (!platform_device
) {
581 goto fail_platform_device_alloc
;
583 ret
= platform_device_add(platform_device
);
585 goto fail_platform_device_add
;
587 /* register backends */
588 wmi
= init_dell_smbios_wmi();
590 pr_debug("Failed to initialize WMI backend: %d\n", wmi
);
591 smm
= init_dell_smbios_smm();
593 pr_debug("Failed to initialize SMM backend: %d\n", smm
);
595 pr_err("No SMBIOS backends available (wmi: %d, smm: %d)\n",
598 goto fail_create_group
;
602 /* duplicate tokens will cause problems building sysfs files */
603 zero_duplicates(&platform_device
->dev
);
605 ret
= build_tokens_sysfs(platform_device
);
613 free_group(platform_device
);
616 platform_device_del(platform_device
);
618 fail_platform_device_add
:
619 platform_device_put(platform_device
);
621 fail_platform_device_alloc
:
622 platform_driver_unregister(&platform_driver
);
624 fail_platform_driver
:
629 static void __exit
dell_smbios_exit(void)
631 exit_dell_smbios_wmi();
632 exit_dell_smbios_smm();
633 mutex_lock(&smbios_mutex
);
634 if (platform_device
) {
636 free_group(platform_device
);
637 platform_device_unregister(platform_device
);
638 platform_driver_unregister(&platform_driver
);
641 mutex_unlock(&smbios_mutex
);
644 module_init(dell_smbios_init
);
645 module_exit(dell_smbios_exit
);
647 MODULE_AUTHOR("Matthew Garrett <mjg@redhat.com>");
648 MODULE_AUTHOR("Gabriele Mazzotta <gabriele.mzt@gmail.com>");
649 MODULE_AUTHOR("Pali Rohár <pali@kernel.org>");
650 MODULE_AUTHOR("Mario Limonciello <mario.limonciello@dell.com>");
651 MODULE_DESCRIPTION("Common functions for kernel modules using Dell SMBIOS");
652 MODULE_LICENSE("GPL");