linux/drivers/platform/x86/dell-smbios-base.c
<<
>>
Prefs
   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/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"
  23
  24static u32 da_supported_commands;
  25static int da_num_tokens;
  26static struct platform_device *platform_device;
  27static struct calling_interface_token *da_tokens;
  28static struct device_attribute *token_location_attrs;
  29static struct device_attribute *token_value_attrs;
  30static struct attribute **token_attrs;
  31static DEFINE_MUTEX(smbios_mutex);
  32
  33struct smbios_device {
  34        struct list_head list;
  35        struct device *device;
  36        int (*call_fn)(struct calling_interface_buffer *arg);
  37};
  38
  39struct smbios_call {
  40        u32 need_capability;
  41        int cmd_class;
  42        int cmd_select;
  43};
  44
  45/* calls that are whitelisted for given capabilities */
  46static struct smbios_call call_whitelist[] = {
  47        /* generally tokens are allowed, but may be further filtered or
  48         * restricted by token blacklist or whitelist
  49         */
  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},
  61};
  62
  63/* calls that are explicitly blacklisted */
  64static 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},
  74};
  75
  76struct token_range {
  77        u32 need_capability;
  78        u16 min;
  79        u16 max;
  80};
  81
  82/* tokens that are whitelisted for given capabilities */
  83static 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}
  88};
  89
  90/* tokens that are explicitly blacklisted */
  91static 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},
 122};
 123
 124static LIST_HEAD(smbios_device_list);
 125
 126int dell_smbios_error(int value)
 127{
 128        switch (value) {
 129        case 0: /* Completed successfully */
 130                return 0;
 131        case -1: /* Completed with error */
 132                return -EIO;
 133        case -2: /* Function not supported */
 134                return -ENXIO;
 135        default: /* Unknown error */
 136                return -EINVAL;
 137        }
 138}
 139EXPORT_SYMBOL_GPL(dell_smbios_error);
 140
 141int dell_smbios_register_device(struct device *d, void *call_fn)
 142{
 143        struct smbios_device *priv;
 144
 145        priv = devm_kzalloc(d, sizeof(struct smbios_device), GFP_KERNEL);
 146        if (!priv)
 147                return -ENOMEM;
 148        get_device(d);
 149        priv->device = d;
 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);
 155        return 0;
 156}
 157EXPORT_SYMBOL_GPL(dell_smbios_register_device);
 158
 159void dell_smbios_unregister_device(struct device *d)
 160{
 161        struct smbios_device *priv;
 162
 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);
 167                        put_device(d);
 168                        break;
 169                }
 170        }
 171        mutex_unlock(&smbios_mutex);
 172        dev_dbg(d, "Remove device: %s\n", d->driver->name);
 173}
 174EXPORT_SYMBOL_GPL(dell_smbios_unregister_device);
 175
 176int dell_smbios_call_filter(struct device *d,
 177                            struct calling_interface_buffer *buffer)
 178{
 179        u16 t = 0;
 180        int i;
 181
 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);
 185                return -EINVAL;
 186        }
 187
 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);
 192                return -EINVAL;
 193        }
 194
 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)
 198                        continue;
 199                if (buffer->cmd_select != call_blacklist[i].cmd_select &&
 200                    call_blacklist[i].cmd_select != -1)
 201                        continue;
 202                dev_dbg(d, "blacklisted command: %u/%u\n",
 203                        buffer->cmd_class, buffer->cmd_select);
 204                return -EINVAL;
 205        }
 206
 207        /* if a token call, find token ID */
 208
 209        if ((buffer->cmd_class == CLASS_TOKEN_READ ||
 210             buffer->cmd_class == CLASS_TOKEN_WRITE) &&
 211             buffer->cmd_select < 3) {
 212                /* tokens enabled ? */
 213                if (!da_tokens) {
 214                        dev_dbg(d, "no token support on this system\n");
 215                        return -EINVAL;
 216                }
 217
 218                /* find the matching token ID */
 219                for (i = 0; i < da_num_tokens; i++) {
 220                        if (da_tokens[i].location != buffer->input[0])
 221                                continue;
 222                        t = da_tokens[i].tokenID;
 223                        break;
 224                }
 225
 226                /* token call; but token didn't exist */
 227                if (!t) {
 228                        dev_dbg(d, "token at location %04x doesn't exist\n",
 229                                buffer->input[0]);
 230                        return -EINVAL;
 231                }
 232
 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)
 236                                continue;
 237                        if (t >= token_blacklist[i].min &&
 238                            t <= token_blacklist[i].max)
 239                                return -EINVAL;
 240                }
 241
 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)
 245                                continue;
 246                        if (t < token_whitelist[i].min ||
 247                            t > token_whitelist[i].max)
 248                                continue;
 249                        if (!token_whitelist[i].need_capability ||
 250                            capable(token_whitelist[i].need_capability)) {
 251                                dev_dbg(d, "whitelisted token: %x\n", t);
 252                                return 0;
 253                        }
 254
 255                }
 256        }
 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)
 260                        continue;
 261                if (buffer->cmd_select != call_whitelist[i].cmd_select)
 262                        continue;
 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);
 267                        return 0;
 268                }
 269                dev_dbg(d, "missing capability %d for %u/%u\n",
 270                        call_whitelist[i].need_capability,
 271                        buffer->cmd_class, buffer->cmd_select);
 272
 273        }
 274
 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);
 279                return 0;
 280        }
 281
 282        return -EACCES;
 283}
 284EXPORT_SYMBOL_GPL(dell_smbios_call_filter);
 285
 286int dell_smbios_call(struct calling_interface_buffer *buffer)
 287{
 288        int (*call_fn)(struct calling_interface_buffer *) = NULL;
 289        struct device *selected_dev = NULL;
 290        struct smbios_device *priv;
 291        int ret;
 292
 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",
 297                                priv->device->id);
 298                        call_fn = priv->call_fn;
 299                        selected_dev = priv->device;
 300                }
 301        }
 302
 303        if (!selected_dev) {
 304                ret = -ENODEV;
 305                pr_err("No dell-smbios drivers are loaded\n");
 306                goto out_smbios_call;
 307        }
 308
 309        ret = call_fn(buffer);
 310
 311out_smbios_call:
 312        mutex_unlock(&smbios_mutex);
 313        return ret;
 314}
 315EXPORT_SYMBOL_GPL(dell_smbios_call);
 316
 317struct calling_interface_token *dell_smbios_find_token(int tokenid)
 318{
 319        int i;
 320
 321        if (!da_tokens)
 322                return NULL;
 323
 324        for (i = 0; i < da_num_tokens; i++) {
 325                if (da_tokens[i].tokenID == tokenid)
 326                        return &da_tokens[i];
 327        }
 328
 329        return NULL;
 330}
 331EXPORT_SYMBOL_GPL(dell_smbios_find_token);
 332
 333static BLOCKING_NOTIFIER_HEAD(dell_laptop_chain_head);
 334
 335int dell_laptop_register_notifier(struct notifier_block *nb)
 336{
 337        return blocking_notifier_chain_register(&dell_laptop_chain_head, nb);
 338}
 339EXPORT_SYMBOL_GPL(dell_laptop_register_notifier);
 340
 341int dell_laptop_unregister_notifier(struct notifier_block *nb)
 342{
 343        return blocking_notifier_chain_unregister(&dell_laptop_chain_head, nb);
 344}
 345EXPORT_SYMBOL_GPL(dell_laptop_unregister_notifier);
 346
 347void dell_laptop_call_notifier(unsigned long action, void *data)
 348{
 349        blocking_notifier_call_chain(&dell_laptop_chain_head, action, data);
 350}
 351EXPORT_SYMBOL_GPL(dell_laptop_call_notifier);
 352
 353static void __init parse_da_table(const struct dmi_header *dm)
 354{
 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);
 360
 361        /*
 362         * 4 bytes of table header, plus 7 bytes of Dell header
 363         * plus at least 6 bytes of entry
 364         */
 365
 366        if (dm->length < 17)
 367                return;
 368
 369        da_supported_commands = table->supportedCmds;
 370
 371        new_da_tokens = krealloc(da_tokens, (da_num_tokens + tokens) *
 372                                 sizeof(struct calling_interface_token),
 373                                 GFP_KERNEL);
 374
 375        if (!new_da_tokens)
 376                return;
 377        da_tokens = new_da_tokens;
 378
 379        memcpy(da_tokens+da_num_tokens, table->tokens,
 380               sizeof(struct calling_interface_token) * tokens);
 381
 382        da_num_tokens += tokens;
 383}
 384
 385static void zero_duplicates(struct device *dev)
 386{
 387        int i, j;
 388
 389        for (i = 0; i < da_num_tokens; i++) {
 390                if (da_tokens[i].tokenID == 0)
 391                        continue;
 392                for (j = i+1; j < da_num_tokens; j++) {
 393                        if (da_tokens[j].tokenID == 0)
 394                                continue;
 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,
 399                                        da_tokens[j].value);
 400                                da_tokens[j].tokenID = 0;
 401                        }
 402                }
 403        }
 404}
 405
 406static void __init find_tokens(const struct dmi_header *dm, void *dummy)
 407{
 408        switch (dm->type) {
 409        case 0xd4: /* Indexed IO */
 410        case 0xd5: /* Protected Area Type 1 */
 411        case 0xd6: /* Protected Area Type 2 */
 412                break;
 413        case 0xda: /* Calling interface */
 414                parse_da_table(dm);
 415                break;
 416        }
 417}
 418
 419static int match_attribute(struct device *dev,
 420                           struct device_attribute *attr)
 421{
 422        int i;
 423
 424        for (i = 0; i < da_num_tokens * 2; i++) {
 425                if (!token_attrs[i])
 426                        continue;
 427                if (strcmp(token_attrs[i]->name, attr->attr.name) == 0)
 428                        return i/2;
 429        }
 430        dev_dbg(dev, "couldn't match: %s\n", attr->attr.name);
 431        return -EINVAL;
 432}
 433
 434static ssize_t location_show(struct device *dev,
 435                             struct device_attribute *attr, char *buf)
 436{
 437        int i;
 438
 439        if (!capable(CAP_SYS_ADMIN))
 440                return -EPERM;
 441
 442        i = match_attribute(dev, attr);
 443        if (i > 0)
 444                return scnprintf(buf, PAGE_SIZE, "%08x", da_tokens[i].location);
 445        return 0;
 446}
 447
 448static ssize_t value_show(struct device *dev,
 449                          struct device_attribute *attr, char *buf)
 450{
 451        int i;
 452
 453        if (!capable(CAP_SYS_ADMIN))
 454                return -EPERM;
 455
 456        i = match_attribute(dev, attr);
 457        if (i > 0)
 458                return scnprintf(buf, PAGE_SIZE, "%08x", da_tokens[i].value);
 459        return 0;
 460}
 461
 462static struct attribute_group smbios_attribute_group = {
 463        .name = "tokens"
 464};
 465
 466static struct platform_driver platform_driver = {
 467        .driver = {
 468                .name = "dell-smbios",
 469        },
 470};
 471
 472static int build_tokens_sysfs(struct platform_device *dev)
 473{
 474        char *location_name;
 475        char *value_name;
 476        size_t size;
 477        int ret;
 478        int i, j;
 479
 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)
 484                return -ENOMEM;
 485        token_value_attrs = kzalloc(size, GFP_KERNEL);
 486        if (!token_value_attrs)
 487                goto out_allocate_value;
 488
 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);
 492        if (!token_attrs)
 493                goto out_allocate_attrs;
 494
 495        for (i = 0, j = 0; i < da_num_tokens; i++) {
 496                /* skip empty */
 497                if (da_tokens[i].tokenID == 0)
 498                        continue;
 499                /* add location */
 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;
 509
 510                /* add value */
 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;
 520                continue;
 521
 522loop_fail_create_value:
 523                kfree(location_name);
 524                goto out_unwind_strings;
 525        }
 526        smbios_attribute_group.attrs = token_attrs;
 527
 528        ret = sysfs_create_group(&dev->dev.kobj, &smbios_attribute_group);
 529        if (ret)
 530                goto out_unwind_strings;
 531        return 0;
 532
 533out_unwind_strings:
 534        while (i--) {
 535                kfree(token_location_attrs[i].attr.name);
 536                kfree(token_value_attrs[i].attr.name);
 537        }
 538        kfree(token_attrs);
 539out_allocate_attrs:
 540        kfree(token_value_attrs);
 541out_allocate_value:
 542        kfree(token_location_attrs);
 543
 544        return -ENOMEM;
 545}
 546
 547static void free_group(struct platform_device *pdev)
 548{
 549        int i;
 550
 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);
 556        }
 557        kfree(token_attrs);
 558        kfree(token_value_attrs);
 559        kfree(token_location_attrs);
 560}
 561
 562static int __init dell_smbios_init(void)
 563{
 564        int ret, wmi, smm;
 565
 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");
 569                return -ENODEV;
 570        }
 571
 572        dmi_walk(find_tokens, NULL);
 573
 574        ret = platform_driver_register(&platform_driver);
 575        if (ret)
 576                goto fail_platform_driver;
 577
 578        platform_device = platform_device_alloc("dell-smbios", 0);
 579        if (!platform_device) {
 580                ret = -ENOMEM;
 581                goto fail_platform_device_alloc;
 582        }
 583        ret = platform_device_add(platform_device);
 584        if (ret)
 585                goto fail_platform_device_add;
 586
 587        /* register backends */
 588        wmi = init_dell_smbios_wmi();
 589        if (wmi)
 590                pr_debug("Failed to initialize WMI backend: %d\n", wmi);
 591        smm = init_dell_smbios_smm();
 592        if (smm)
 593                pr_debug("Failed to initialize SMM backend: %d\n", smm);
 594        if (wmi && smm) {
 595                pr_err("No SMBIOS backends available (wmi: %d, smm: %d)\n",
 596                        wmi, smm);
 597                goto fail_create_group;
 598        }
 599
 600        if (da_tokens)  {
 601                /* duplicate tokens will cause problems building sysfs files */
 602                zero_duplicates(&platform_device->dev);
 603
 604                ret = build_tokens_sysfs(platform_device);
 605                if (ret)
 606                        goto fail_sysfs;
 607        }
 608
 609        return 0;
 610
 611fail_sysfs:
 612        free_group(platform_device);
 613
 614fail_create_group:
 615        platform_device_del(platform_device);
 616
 617fail_platform_device_add:
 618        platform_device_put(platform_device);
 619
 620fail_platform_device_alloc:
 621        platform_driver_unregister(&platform_driver);
 622
 623fail_platform_driver:
 624        kfree(da_tokens);
 625        return ret;
 626}
 627
 628static void __exit dell_smbios_exit(void)
 629{
 630        exit_dell_smbios_wmi();
 631        exit_dell_smbios_smm();
 632        mutex_lock(&smbios_mutex);
 633        if (platform_device) {
 634                if (da_tokens)
 635                        free_group(platform_device);
 636                platform_device_unregister(platform_device);
 637                platform_driver_unregister(&platform_driver);
 638        }
 639        kfree(da_tokens);
 640        mutex_unlock(&smbios_mutex);
 641}
 642
 643module_init(dell_smbios_init);
 644module_exit(dell_smbios_exit);
 645
 646MODULE_AUTHOR("Matthew Garrett <mjg@redhat.com>");
 647MODULE_AUTHOR("Gabriele Mazzotta <gabriele.mzt@gmail.com>");
 648MODULE_AUTHOR("Pali Rohár <pali@kernel.org>");
 649MODULE_AUTHOR("Mario Limonciello <mario.limonciello@dell.com>");
 650MODULE_DESCRIPTION("Common functions for kernel modules using Dell SMBIOS");
 651MODULE_LICENSE("GPL");
 652