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