1 // SPDX-License-Identifier: GPL-2.0-only 2 /* 3 * Common functions for kernel modules using Dell SMBIOS 4 * 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> 8 * 9 * Based on documentation in the libsmbios package: 10 * Copyright (C) 2005-2014 Dell Inc. 11 */ 12 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 13 14 #include <linux/container_of.h> 15 #include <linux/kernel.h> 16 #include <linux/module.h> 17 #include <linux/capability.h> 18 #include <linux/dmi.h> 19 #include <linux/err.h> 20 #include <linux/mutex.h> 21 #include <linux/platform_device.h> 22 #include <linux/slab.h> 23 #include "dell-smbios.h" 24 25 static u32 da_supported_commands; 26 static int da_num_tokens; 27 static struct platform_device *platform_device; 28 static struct calling_interface_token *da_tokens; 29 static struct token_sysfs_data *token_entries; 30 static struct attribute **token_attrs; 31 static DEFINE_MUTEX(smbios_mutex); 32 33 struct token_sysfs_data { 34 struct device_attribute location_attr; 35 struct device_attribute value_attr; 36 struct calling_interface_token *token; 37 }; 38 39 struct smbios_device { 40 struct list_head list; 41 struct device *device; 42 int priority; 43 int (*call_fn)(struct calling_interface_buffer *arg); 44 }; 45 46 struct smbios_call { 47 u32 need_capability; 48 int cmd_class; 49 int cmd_select; 50 }; 51 52 /* calls that are whitelisted for given capabilities */ 53 static struct smbios_call call_whitelist[] = { 54 /* generally tokens are allowed, but may be further filtered or 55 * restricted by token blacklist or whitelist 56 */ 57 {CAP_SYS_ADMIN, CLASS_TOKEN_READ, SELECT_TOKEN_STD}, 58 {CAP_SYS_ADMIN, CLASS_TOKEN_READ, SELECT_TOKEN_AC}, 59 {CAP_SYS_ADMIN, CLASS_TOKEN_READ, SELECT_TOKEN_BAT}, 60 {CAP_SYS_ADMIN, CLASS_TOKEN_WRITE, SELECT_TOKEN_STD}, 61 {CAP_SYS_ADMIN, CLASS_TOKEN_WRITE, SELECT_TOKEN_AC}, 62 {CAP_SYS_ADMIN, CLASS_TOKEN_WRITE, SELECT_TOKEN_BAT}, 63 /* used by userspace: fwupdate */ 64 {CAP_SYS_ADMIN, CLASS_ADMIN_PROP, SELECT_ADMIN_PROP}, 65 /* used by userspace: fwupd */ 66 {CAP_SYS_ADMIN, CLASS_INFO, SELECT_DOCK}, 67 {CAP_SYS_ADMIN, CLASS_FLASH_INTERFACE, SELECT_FLASH_INTERFACE}, 68 }; 69 70 /* calls that are explicitly blacklisted */ 71 static struct smbios_call call_blacklist[] = { 72 {0x0000, 1, 7}, /* manufacturing use */ 73 {0x0000, 6, 5}, /* manufacturing use */ 74 {0x0000, 11, 3}, /* write once */ 75 {0x0000, 11, 7}, /* write once */ 76 {0x0000, 11, 11}, /* write once */ 77 {0x0000, 19, -1}, /* diagnostics */ 78 /* handled by kernel: dell-laptop */ 79 {0x0000, CLASS_INFO, SELECT_RFKILL}, 80 {0x0000, CLASS_KBD_BACKLIGHT, SELECT_KBD_BACKLIGHT}, 81 {0x0000, CLASS_INFO, SELECT_THERMAL_MANAGEMENT}, 82 }; 83 84 struct token_range { 85 u32 need_capability; 86 u16 min; 87 u16 max; 88 }; 89 90 /* tokens that are whitelisted for given capabilities */ 91 static struct token_range token_whitelist[] = { 92 /* used by userspace: fwupdate */ 93 {CAP_SYS_ADMIN, CAPSULE_EN_TOKEN, CAPSULE_DIS_TOKEN}, 94 /* can indicate to userspace that WMI is needed */ 95 {0x0000, WSMT_EN_TOKEN, WSMT_DIS_TOKEN} 96 }; 97 98 /* tokens that are explicitly blacklisted */ 99 static struct token_range token_blacklist[] = { 100 {0x0000, 0x0058, 0x0059}, /* ME use */ 101 {0x0000, 0x00CD, 0x00D0}, /* raid shadow copy */ 102 {0x0000, 0x013A, 0x01FF}, /* sata shadow copy */ 103 {0x0000, 0x0175, 0x0176}, /* write once */ 104 {0x0000, 0x0195, 0x0197}, /* diagnostics */ 105 {0x0000, 0x01DC, 0x01DD}, /* manufacturing use */ 106 {0x0000, 0x027D, 0x0284}, /* diagnostics */ 107 {0x0000, 0x02E3, 0x02E3}, /* manufacturing use */ 108 {0x0000, 0x02FF, 0x02FF}, /* manufacturing use */ 109 {0x0000, 0x0300, 0x0302}, /* manufacturing use */ 110 {0x0000, 0x0325, 0x0326}, /* manufacturing use */ 111 {0x0000, 0x0332, 0x0335}, /* fan control */ 112 {0x0000, 0x0350, 0x0350}, /* manufacturing use */ 113 {0x0000, 0x0363, 0x0363}, /* manufacturing use */ 114 {0x0000, 0x0368, 0x0368}, /* manufacturing use */ 115 {0x0000, 0x03F6, 0x03F7}, /* manufacturing use */ 116 {0x0000, 0x049E, 0x049F}, /* manufacturing use */ 117 {0x0000, 0x04A0, 0x04A3}, /* disagnostics */ 118 {0x0000, 0x04E6, 0x04E7}, /* manufacturing use */ 119 {0x0000, 0x4000, 0x7FFF}, /* internal BIOS use */ 120 {0x0000, 0x9000, 0x9001}, /* internal BIOS use */ 121 {0x0000, 0xA000, 0xBFFF}, /* write only */ 122 {0x0000, 0xEFF0, 0xEFFF}, /* internal BIOS use */ 123 /* handled by kernel: dell-laptop */ 124 {0x0000, BRIGHTNESS_TOKEN, BRIGHTNESS_TOKEN}, 125 {0x0000, KBD_LED_OFF_TOKEN, KBD_LED_AUTO_TOKEN}, 126 {0x0000, KBD_LED_AC_TOKEN, KBD_LED_AC_TOKEN}, 127 {0x0000, KBD_LED_AUTO_25_TOKEN, KBD_LED_AUTO_75_TOKEN}, 128 {0x0000, KBD_LED_AUTO_100_TOKEN, KBD_LED_AUTO_100_TOKEN}, 129 {0x0000, GLOBAL_MIC_MUTE_ENABLE, GLOBAL_MIC_MUTE_DISABLE}, 130 }; 131 132 static LIST_HEAD(smbios_device_list); 133 134 int dell_smbios_error(int value) 135 { 136 switch (value) { 137 case 0: /* Completed successfully */ 138 return 0; 139 case -1: /* Completed with error */ 140 return -EIO; 141 case -2: /* Function not supported */ 142 return -ENXIO; 143 default: /* Unknown error */ 144 return -EINVAL; 145 } 146 } 147 EXPORT_SYMBOL_GPL(dell_smbios_error); 148 149 int dell_smbios_register_device(struct device *d, int priority, void *call_fn) 150 { 151 struct smbios_device *priv; 152 153 priv = devm_kzalloc(d, sizeof(struct smbios_device), GFP_KERNEL); 154 if (!priv) 155 return -ENOMEM; 156 get_device(d); 157 priv->device = d; 158 priv->priority = priority; 159 priv->call_fn = call_fn; 160 mutex_lock(&smbios_mutex); 161 list_add_tail(&priv->list, &smbios_device_list); 162 mutex_unlock(&smbios_mutex); 163 dev_dbg(d, "Added device: %s\n", d->driver->name); 164 return 0; 165 } 166 EXPORT_SYMBOL_GPL(dell_smbios_register_device); 167 168 void dell_smbios_unregister_device(struct device *d) 169 { 170 struct smbios_device *priv; 171 172 mutex_lock(&smbios_mutex); 173 list_for_each_entry(priv, &smbios_device_list, list) { 174 if (priv->device == d) { 175 list_del(&priv->list); 176 put_device(d); 177 break; 178 } 179 } 180 mutex_unlock(&smbios_mutex); 181 dev_dbg(d, "Remove device: %s\n", d->driver->name); 182 } 183 EXPORT_SYMBOL_GPL(dell_smbios_unregister_device); 184 185 int dell_smbios_call_filter(struct device *d, 186 struct calling_interface_buffer *buffer) 187 { 188 u16 t = 0; 189 int i; 190 191 /* can't make calls over 30 */ 192 if (buffer->cmd_class > 30) { 193 dev_dbg(d, "class too big: %u\n", buffer->cmd_class); 194 return -EINVAL; 195 } 196 197 /* supported calls on the particular system */ 198 if (!(da_supported_commands & (1 << buffer->cmd_class))) { 199 dev_dbg(d, "invalid command, supported commands: 0x%8x\n", 200 da_supported_commands); 201 return -EINVAL; 202 } 203 204 /* match against call blacklist */ 205 for (i = 0; i < ARRAY_SIZE(call_blacklist); i++) { 206 if (buffer->cmd_class != call_blacklist[i].cmd_class) 207 continue; 208 if (buffer->cmd_select != call_blacklist[i].cmd_select && 209 call_blacklist[i].cmd_select != -1) 210 continue; 211 dev_dbg(d, "blacklisted command: %u/%u\n", 212 buffer->cmd_class, buffer->cmd_select); 213 return -EINVAL; 214 } 215 216 /* if a token call, find token ID */ 217 218 if ((buffer->cmd_class == CLASS_TOKEN_READ || 219 buffer->cmd_class == CLASS_TOKEN_WRITE) && 220 buffer->cmd_select < 3) { 221 /* tokens enabled ? */ 222 if (!da_tokens) { 223 dev_dbg(d, "no token support on this system\n"); 224 return -EINVAL; 225 } 226 227 /* find the matching token ID */ 228 for (i = 0; i < da_num_tokens; i++) { 229 if (da_tokens[i].location != buffer->input[0]) 230 continue; 231 t = da_tokens[i].tokenID; 232 break; 233 } 234 235 /* token call; but token didn't exist */ 236 if (!t) { 237 dev_dbg(d, "token at location %04x doesn't exist\n", 238 buffer->input[0]); 239 return -EINVAL; 240 } 241 242 /* match against token blacklist */ 243 for (i = 0; i < ARRAY_SIZE(token_blacklist); i++) { 244 if (!token_blacklist[i].min || !token_blacklist[i].max) 245 continue; 246 if (t >= token_blacklist[i].min && 247 t <= token_blacklist[i].max) 248 return -EINVAL; 249 } 250 251 /* match against token whitelist */ 252 for (i = 0; i < ARRAY_SIZE(token_whitelist); i++) { 253 if (!token_whitelist[i].min || !token_whitelist[i].max) 254 continue; 255 if (t < token_whitelist[i].min || 256 t > token_whitelist[i].max) 257 continue; 258 if (!token_whitelist[i].need_capability || 259 capable(token_whitelist[i].need_capability)) { 260 dev_dbg(d, "whitelisted token: %x\n", t); 261 return 0; 262 } 263 264 } 265 } 266 /* match against call whitelist */ 267 for (i = 0; i < ARRAY_SIZE(call_whitelist); i++) { 268 if (buffer->cmd_class != call_whitelist[i].cmd_class) 269 continue; 270 if (buffer->cmd_select != call_whitelist[i].cmd_select) 271 continue; 272 if (!call_whitelist[i].need_capability || 273 capable(call_whitelist[i].need_capability)) { 274 dev_dbg(d, "whitelisted capable command: %u/%u\n", 275 buffer->cmd_class, buffer->cmd_select); 276 return 0; 277 } 278 dev_dbg(d, "missing capability %d for %u/%u\n", 279 call_whitelist[i].need_capability, 280 buffer->cmd_class, buffer->cmd_select); 281 282 } 283 284 /* not in a whitelist, only allow processes with capabilities */ 285 if (capable(CAP_SYS_RAWIO)) { 286 dev_dbg(d, "Allowing %u/%u due to CAP_SYS_RAWIO\n", 287 buffer->cmd_class, buffer->cmd_select); 288 return 0; 289 } 290 291 return -EACCES; 292 } 293 EXPORT_SYMBOL_GPL(dell_smbios_call_filter); 294 295 int dell_smbios_call(struct calling_interface_buffer *buffer) 296 { 297 struct smbios_device *selected = NULL; 298 struct smbios_device *priv; 299 int ret; 300 301 mutex_lock(&smbios_mutex); 302 list_for_each_entry(priv, &smbios_device_list, list) { 303 if (!selected || priv->priority >= selected->priority) { 304 dev_dbg(priv->device, "Trying device ID: %d\n", priv->priority); 305 selected = priv; 306 } 307 } 308 309 if (!selected) { 310 ret = -ENODEV; 311 pr_err("No dell-smbios drivers are loaded\n"); 312 goto out_smbios_call; 313 } 314 315 ret = selected->call_fn(buffer); 316 317 out_smbios_call: 318 mutex_unlock(&smbios_mutex); 319 return ret; 320 } 321 EXPORT_SYMBOL_GPL(dell_smbios_call); 322 323 void dell_fill_request(struct calling_interface_buffer *buffer, 324 u32 arg0, u32 arg1, u32 arg2, u32 arg3) 325 { 326 memset(buffer, 0, sizeof(struct calling_interface_buffer)); 327 buffer->input[0] = arg0; 328 buffer->input[1] = arg1; 329 buffer->input[2] = arg2; 330 buffer->input[3] = arg3; 331 } 332 EXPORT_SYMBOL_GPL(dell_fill_request); 333 334 int dell_send_request(struct calling_interface_buffer *buffer, 335 u16 class, u16 select) 336 { 337 int ret; 338 339 buffer->cmd_class = class; 340 buffer->cmd_select = select; 341 ret = dell_smbios_call(buffer); 342 if (ret != 0) 343 return ret; 344 return dell_smbios_error(buffer->output[0]); 345 } 346 EXPORT_SYMBOL_GPL(dell_send_request); 347 348 struct calling_interface_token *dell_smbios_find_token(int tokenid) 349 { 350 int i; 351 352 if (!da_tokens) 353 return NULL; 354 355 for (i = 0; i < da_num_tokens; i++) { 356 if (da_tokens[i].tokenID == tokenid) 357 return &da_tokens[i]; 358 } 359 360 return NULL; 361 } 362 EXPORT_SYMBOL_GPL(dell_smbios_find_token); 363 364 static BLOCKING_NOTIFIER_HEAD(dell_laptop_chain_head); 365 366 int dell_laptop_register_notifier(struct notifier_block *nb) 367 { 368 return blocking_notifier_chain_register(&dell_laptop_chain_head, nb); 369 } 370 EXPORT_SYMBOL_GPL(dell_laptop_register_notifier); 371 372 int dell_laptop_unregister_notifier(struct notifier_block *nb) 373 { 374 return blocking_notifier_chain_unregister(&dell_laptop_chain_head, nb); 375 } 376 EXPORT_SYMBOL_GPL(dell_laptop_unregister_notifier); 377 378 void dell_laptop_call_notifier(unsigned long action, void *data) 379 { 380 blocking_notifier_call_chain(&dell_laptop_chain_head, action, data); 381 } 382 EXPORT_SYMBOL_GPL(dell_laptop_call_notifier); 383 384 bool dell_smbios_class_is_supported(u16 class) 385 { 386 /* Classes over 30 always unsupported */ 387 if (class > 30) 388 return false; 389 return da_supported_commands & (1 << class); 390 } 391 EXPORT_SYMBOL_GPL(dell_smbios_class_is_supported); 392 393 static void __init parse_da_table(const struct dmi_header *dm) 394 { 395 /* Final token is a terminator, so we don't want to copy it */ 396 int tokens = (dm->length-11)/sizeof(struct calling_interface_token)-1; 397 struct calling_interface_token *new_da_tokens; 398 struct calling_interface_structure *table = 399 container_of(dm, struct calling_interface_structure, header); 400 401 /* 402 * 4 bytes of table header, plus 7 bytes of Dell header 403 * plus at least 6 bytes of entry 404 */ 405 406 if (dm->length < 17) 407 return; 408 409 da_supported_commands = table->supportedCmds; 410 411 new_da_tokens = krealloc(da_tokens, (da_num_tokens + tokens) * 412 sizeof(struct calling_interface_token), 413 GFP_KERNEL); 414 415 if (!new_da_tokens) 416 return; 417 da_tokens = new_da_tokens; 418 419 memcpy(da_tokens+da_num_tokens, table->tokens, 420 sizeof(struct calling_interface_token) * tokens); 421 422 da_num_tokens += tokens; 423 } 424 425 static void zero_duplicates(struct device *dev) 426 { 427 int i, j; 428 429 for (i = 0; i < da_num_tokens; i++) { 430 if (da_tokens[i].tokenID == 0) 431 continue; 432 for (j = i+1; j < da_num_tokens; j++) { 433 if (da_tokens[j].tokenID == 0) 434 continue; 435 if (da_tokens[i].tokenID == da_tokens[j].tokenID) { 436 dev_dbg(dev, "Zeroing dup token ID %x(%x/%x)\n", 437 da_tokens[j].tokenID, 438 da_tokens[j].location, 439 da_tokens[j].value); 440 da_tokens[j].tokenID = 0; 441 } 442 } 443 } 444 } 445 446 static void __init find_tokens(const struct dmi_header *dm, void *dummy) 447 { 448 switch (dm->type) { 449 case 0xd4: /* Indexed IO */ 450 case 0xd5: /* Protected Area Type 1 */ 451 case 0xd6: /* Protected Area Type 2 */ 452 break; 453 case 0xda: /* Calling interface */ 454 parse_da_table(dm); 455 break; 456 } 457 } 458 459 static ssize_t location_show(struct device *dev, 460 struct device_attribute *attr, char *buf) 461 { 462 struct token_sysfs_data *data = container_of(attr, struct token_sysfs_data, location_attr); 463 464 if (!capable(CAP_SYS_ADMIN)) 465 return -EPERM; 466 467 return sysfs_emit(buf, "%08x", data->token->location); 468 } 469 470 static ssize_t value_show(struct device *dev, 471 struct device_attribute *attr, char *buf) 472 { 473 struct token_sysfs_data *data = container_of(attr, struct token_sysfs_data, value_attr); 474 475 if (!capable(CAP_SYS_ADMIN)) 476 return -EPERM; 477 478 return sysfs_emit(buf, "%08x", data->token->value); 479 } 480 481 static struct attribute_group smbios_attribute_group = { 482 .name = "tokens" 483 }; 484 485 static struct platform_driver platform_driver = { 486 .driver = { 487 .name = "dell-smbios", 488 }, 489 }; 490 491 static int build_tokens_sysfs(struct platform_device *dev) 492 { 493 char *location_name; 494 char *value_name; 495 int ret; 496 int i, j; 497 498 token_entries = kcalloc(da_num_tokens, sizeof(*token_entries), GFP_KERNEL); 499 if (!token_entries) 500 return -ENOMEM; 501 502 /* need to store both location and value + terminator*/ 503 token_attrs = kcalloc((2 * da_num_tokens) + 1, sizeof(*token_attrs), GFP_KERNEL); 504 if (!token_attrs) 505 goto out_allocate_attrs; 506 507 for (i = 0, j = 0; i < da_num_tokens; i++) { 508 /* skip empty */ 509 if (da_tokens[i].tokenID == 0) 510 continue; 511 512 token_entries[i].token = &da_tokens[i]; 513 514 /* add location */ 515 location_name = kasprintf(GFP_KERNEL, "%04x_location", 516 da_tokens[i].tokenID); 517 if (location_name == NULL) 518 goto out_unwind_strings; 519 520 sysfs_attr_init(&token_entries[i].location_attr.attr); 521 token_entries[i].location_attr.attr.name = location_name; 522 token_entries[i].location_attr.attr.mode = 0444; 523 token_entries[i].location_attr.show = location_show; 524 token_attrs[j++] = &token_entries[i].location_attr.attr; 525 526 /* add value */ 527 value_name = kasprintf(GFP_KERNEL, "%04x_value", 528 da_tokens[i].tokenID); 529 if (!value_name) { 530 kfree(location_name); 531 goto out_unwind_strings; 532 } 533 534 sysfs_attr_init(&token_entries[i].value_attr.attr); 535 token_entries[i].value_attr.attr.name = value_name; 536 token_entries[i].value_attr.attr.mode = 0444; 537 token_entries[i].value_attr.show = value_show; 538 token_attrs[j++] = &token_entries[i].value_attr.attr; 539 } 540 smbios_attribute_group.attrs = token_attrs; 541 542 ret = sysfs_create_group(&dev->dev.kobj, &smbios_attribute_group); 543 if (ret) 544 goto out_unwind_strings; 545 return 0; 546 547 out_unwind_strings: 548 while (i--) { 549 kfree(token_entries[i].location_attr.attr.name); 550 kfree(token_entries[i].value_attr.attr.name); 551 } 552 kfree(token_attrs); 553 out_allocate_attrs: 554 kfree(token_entries); 555 556 return -ENOMEM; 557 } 558 559 static void free_group(struct platform_device *pdev) 560 { 561 int i; 562 563 sysfs_remove_group(&pdev->dev.kobj, 564 &smbios_attribute_group); 565 for (i = 0; i < da_num_tokens; i++) { 566 kfree(token_entries[i].location_attr.attr.name); 567 kfree(token_entries[i].value_attr.attr.name); 568 } 569 kfree(token_attrs); 570 kfree(token_entries); 571 } 572 573 static int __init dell_smbios_init(void) 574 { 575 int ret, wmi, smm; 576 577 if (!dmi_find_device(DMI_DEV_TYPE_OEM_STRING, "Dell System", NULL) && 578 !dmi_find_device(DMI_DEV_TYPE_OEM_STRING, "Alienware", NULL) && 579 !dmi_find_device(DMI_DEV_TYPE_OEM_STRING, "www.dell.com", NULL)) { 580 pr_err("Unable to run on non-Dell system\n"); 581 return -ENODEV; 582 } 583 584 dmi_walk(find_tokens, NULL); 585 586 ret = platform_driver_register(&platform_driver); 587 if (ret) 588 goto fail_platform_driver; 589 590 platform_device = platform_device_alloc("dell-smbios", 0); 591 if (!platform_device) { 592 ret = -ENOMEM; 593 goto fail_platform_device_alloc; 594 } 595 ret = platform_device_add(platform_device); 596 if (ret) 597 goto fail_platform_device_add; 598 599 /* register backends */ 600 wmi = init_dell_smbios_wmi(); 601 if (wmi) 602 pr_debug("Failed to initialize WMI backend: %d\n", wmi); 603 smm = init_dell_smbios_smm(); 604 if (smm) 605 pr_debug("Failed to initialize SMM backend: %d\n", smm); 606 if (wmi && smm) { 607 pr_err("No SMBIOS backends available (wmi: %d, smm: %d)\n", 608 wmi, smm); 609 ret = -ENODEV; 610 goto fail_create_group; 611 } 612 613 if (da_tokens) { 614 /* duplicate tokens will cause problems building sysfs files */ 615 zero_duplicates(&platform_device->dev); 616 617 ret = build_tokens_sysfs(platform_device); 618 if (ret) 619 goto fail_sysfs; 620 } 621 622 return 0; 623 624 fail_sysfs: 625 if (!wmi) 626 exit_dell_smbios_wmi(); 627 if (!smm) 628 exit_dell_smbios_smm(); 629 630 fail_create_group: 631 platform_device_del(platform_device); 632 633 fail_platform_device_add: 634 platform_device_put(platform_device); 635 636 fail_platform_device_alloc: 637 platform_driver_unregister(&platform_driver); 638 639 fail_platform_driver: 640 kfree(da_tokens); 641 return ret; 642 } 643 644 static void __exit dell_smbios_exit(void) 645 { 646 exit_dell_smbios_wmi(); 647 exit_dell_smbios_smm(); 648 mutex_lock(&smbios_mutex); 649 if (platform_device) { 650 if (da_tokens) 651 free_group(platform_device); 652 platform_device_unregister(platform_device); 653 platform_driver_unregister(&platform_driver); 654 } 655 kfree(da_tokens); 656 mutex_unlock(&smbios_mutex); 657 } 658 659 module_init(dell_smbios_init); 660 module_exit(dell_smbios_exit); 661 662 MODULE_AUTHOR("Matthew Garrett <mjg@redhat.com>"); 663 MODULE_AUTHOR("Gabriele Mazzotta <gabriele.mzt@gmail.com>"); 664 MODULE_AUTHOR("Pali Rohár <pali@kernel.org>"); 665 MODULE_AUTHOR("Mario Limonciello <mario.limonciello@outlook.com>"); 666 MODULE_DESCRIPTION("Common functions for kernel modules using Dell SMBIOS"); 667 MODULE_LICENSE("GPL"); 668