Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-2.0-only
0002 /*
0003  *  Common functions for kernel modules using Dell SMBIOS
0004  *
0005  *  Copyright (c) Red Hat <mjg@redhat.com>
0006  *  Copyright (c) 2014 Gabriele Mazzotta <gabriele.mzt@gmail.com>
0007  *  Copyright (c) 2014 Pali Rohár <pali@kernel.org>
0008  *
0009  *  Based on documentation in the libsmbios package:
0010  *  Copyright (C) 2005-2014 Dell Inc.
0011  */
0012 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
0013 
0014 #include <linux/kernel.h>
0015 #include <linux/module.h>
0016 #include <linux/capability.h>
0017 #include <linux/dmi.h>
0018 #include <linux/err.h>
0019 #include <linux/mutex.h>
0020 #include <linux/platform_device.h>
0021 #include <linux/slab.h>
0022 #include "dell-smbios.h"
0023 
0024 static u32 da_supported_commands;
0025 static int da_num_tokens;
0026 static struct platform_device *platform_device;
0027 static struct calling_interface_token *da_tokens;
0028 static struct device_attribute *token_location_attrs;
0029 static struct device_attribute *token_value_attrs;
0030 static struct attribute **token_attrs;
0031 static DEFINE_MUTEX(smbios_mutex);
0032 
0033 struct smbios_device {
0034     struct list_head list;
0035     struct device *device;
0036     int (*call_fn)(struct calling_interface_buffer *arg);
0037 };
0038 
0039 struct smbios_call {
0040     u32 need_capability;
0041     int cmd_class;
0042     int cmd_select;
0043 };
0044 
0045 /* calls that are whitelisted for given capabilities */
0046 static struct smbios_call call_whitelist[] = {
0047     /* generally tokens are allowed, but may be further filtered or
0048      * restricted by token blacklist or whitelist
0049      */
0050     {CAP_SYS_ADMIN, CLASS_TOKEN_READ,   SELECT_TOKEN_STD},
0051     {CAP_SYS_ADMIN, CLASS_TOKEN_READ,   SELECT_TOKEN_AC},
0052     {CAP_SYS_ADMIN, CLASS_TOKEN_READ,   SELECT_TOKEN_BAT},
0053     {CAP_SYS_ADMIN, CLASS_TOKEN_WRITE,  SELECT_TOKEN_STD},
0054     {CAP_SYS_ADMIN, CLASS_TOKEN_WRITE,  SELECT_TOKEN_AC},
0055     {CAP_SYS_ADMIN, CLASS_TOKEN_WRITE,  SELECT_TOKEN_BAT},
0056     /* used by userspace: fwupdate */
0057     {CAP_SYS_ADMIN, CLASS_ADMIN_PROP,   SELECT_ADMIN_PROP},
0058     /* used by userspace: fwupd */
0059     {CAP_SYS_ADMIN, CLASS_INFO,     SELECT_DOCK},
0060     {CAP_SYS_ADMIN, CLASS_FLASH_INTERFACE,  SELECT_FLASH_INTERFACE},
0061 };
0062 
0063 /* calls that are explicitly blacklisted */
0064 static struct smbios_call call_blacklist[] = {
0065     {0x0000,  1,  7}, /* manufacturing use */
0066     {0x0000,  6,  5}, /* manufacturing use */
0067     {0x0000, 11,  3}, /* write once */
0068     {0x0000, 11,  7}, /* write once */
0069     {0x0000, 11, 11}, /* write once */
0070     {0x0000, 19, -1}, /* diagnostics */
0071     /* handled by kernel: dell-laptop */
0072     {0x0000, CLASS_INFO, SELECT_RFKILL},
0073     {0x0000, CLASS_KBD_BACKLIGHT, SELECT_KBD_BACKLIGHT},
0074 };
0075 
0076 struct token_range {
0077     u32 need_capability;
0078     u16 min;
0079     u16 max;
0080 };
0081 
0082 /* tokens that are whitelisted for given capabilities */
0083 static struct token_range token_whitelist[] = {
0084     /* used by userspace: fwupdate */
0085     {CAP_SYS_ADMIN, CAPSULE_EN_TOKEN,   CAPSULE_DIS_TOKEN},
0086     /* can indicate to userspace that WMI is needed */
0087     {0x0000,    WSMT_EN_TOKEN,      WSMT_DIS_TOKEN}
0088 };
0089 
0090 /* tokens that are explicitly blacklisted */
0091 static struct token_range token_blacklist[] = {
0092     {0x0000, 0x0058, 0x0059}, /* ME use */
0093     {0x0000, 0x00CD, 0x00D0}, /* raid shadow copy */
0094     {0x0000, 0x013A, 0x01FF}, /* sata shadow copy */
0095     {0x0000, 0x0175, 0x0176}, /* write once */
0096     {0x0000, 0x0195, 0x0197}, /* diagnostics */
0097     {0x0000, 0x01DC, 0x01DD}, /* manufacturing use */
0098     {0x0000, 0x027D, 0x0284}, /* diagnostics */
0099     {0x0000, 0x02E3, 0x02E3}, /* manufacturing use */
0100     {0x0000, 0x02FF, 0x02FF}, /* manufacturing use */
0101     {0x0000, 0x0300, 0x0302}, /* manufacturing use */
0102     {0x0000, 0x0325, 0x0326}, /* manufacturing use */
0103     {0x0000, 0x0332, 0x0335}, /* fan control */
0104     {0x0000, 0x0350, 0x0350}, /* manufacturing use */
0105     {0x0000, 0x0363, 0x0363}, /* manufacturing use */
0106     {0x0000, 0x0368, 0x0368}, /* manufacturing use */
0107     {0x0000, 0x03F6, 0x03F7}, /* manufacturing use */
0108     {0x0000, 0x049E, 0x049F}, /* manufacturing use */
0109     {0x0000, 0x04A0, 0x04A3}, /* disagnostics */
0110     {0x0000, 0x04E6, 0x04E7}, /* manufacturing use */
0111     {0x0000, 0x4000, 0x7FFF}, /* internal BIOS use */
0112     {0x0000, 0x9000, 0x9001}, /* internal BIOS use */
0113     {0x0000, 0xA000, 0xBFFF}, /* write only */
0114     {0x0000, 0xEFF0, 0xEFFF}, /* internal BIOS use */
0115     /* handled by kernel: dell-laptop */
0116     {0x0000, BRIGHTNESS_TOKEN,  BRIGHTNESS_TOKEN},
0117     {0x0000, KBD_LED_OFF_TOKEN, KBD_LED_AUTO_TOKEN},
0118     {0x0000, KBD_LED_AC_TOKEN,  KBD_LED_AC_TOKEN},
0119     {0x0000, KBD_LED_AUTO_25_TOKEN, KBD_LED_AUTO_75_TOKEN},
0120     {0x0000, KBD_LED_AUTO_100_TOKEN,    KBD_LED_AUTO_100_TOKEN},
0121     {0x0000, GLOBAL_MIC_MUTE_ENABLE,    GLOBAL_MIC_MUTE_DISABLE},
0122 };
0123 
0124 static LIST_HEAD(smbios_device_list);
0125 
0126 int dell_smbios_error(int value)
0127 {
0128     switch (value) {
0129     case 0: /* Completed successfully */
0130         return 0;
0131     case -1: /* Completed with error */
0132         return -EIO;
0133     case -2: /* Function not supported */
0134         return -ENXIO;
0135     default: /* Unknown error */
0136         return -EINVAL;
0137     }
0138 }
0139 EXPORT_SYMBOL_GPL(dell_smbios_error);
0140 
0141 int dell_smbios_register_device(struct device *d, void *call_fn)
0142 {
0143     struct smbios_device *priv;
0144 
0145     priv = devm_kzalloc(d, sizeof(struct smbios_device), GFP_KERNEL);
0146     if (!priv)
0147         return -ENOMEM;
0148     get_device(d);
0149     priv->device = d;
0150     priv->call_fn = call_fn;
0151     mutex_lock(&smbios_mutex);
0152     list_add_tail(&priv->list, &smbios_device_list);
0153     mutex_unlock(&smbios_mutex);
0154     dev_dbg(d, "Added device: %s\n", d->driver->name);
0155     return 0;
0156 }
0157 EXPORT_SYMBOL_GPL(dell_smbios_register_device);
0158 
0159 void dell_smbios_unregister_device(struct device *d)
0160 {
0161     struct smbios_device *priv;
0162 
0163     mutex_lock(&smbios_mutex);
0164     list_for_each_entry(priv, &smbios_device_list, list) {
0165         if (priv->device == d) {
0166             list_del(&priv->list);
0167             put_device(d);
0168             break;
0169         }
0170     }
0171     mutex_unlock(&smbios_mutex);
0172     dev_dbg(d, "Remove device: %s\n", d->driver->name);
0173 }
0174 EXPORT_SYMBOL_GPL(dell_smbios_unregister_device);
0175 
0176 int dell_smbios_call_filter(struct device *d,
0177                 struct calling_interface_buffer *buffer)
0178 {
0179     u16 t = 0;
0180     int i;
0181 
0182     /* can't make calls over 30 */
0183     if (buffer->cmd_class > 30) {
0184         dev_dbg(d, "class too big: %u\n", buffer->cmd_class);
0185         return -EINVAL;
0186     }
0187 
0188     /* supported calls on the particular system */
0189     if (!(da_supported_commands & (1 << buffer->cmd_class))) {
0190         dev_dbg(d, "invalid command, supported commands: 0x%8x\n",
0191             da_supported_commands);
0192         return -EINVAL;
0193     }
0194 
0195     /* match against call blacklist  */
0196     for (i = 0; i < ARRAY_SIZE(call_blacklist); i++) {
0197         if (buffer->cmd_class != call_blacklist[i].cmd_class)
0198             continue;
0199         if (buffer->cmd_select != call_blacklist[i].cmd_select &&
0200             call_blacklist[i].cmd_select != -1)
0201             continue;
0202         dev_dbg(d, "blacklisted command: %u/%u\n",
0203             buffer->cmd_class, buffer->cmd_select);
0204         return -EINVAL;
0205     }
0206 
0207     /* if a token call, find token ID */
0208 
0209     if ((buffer->cmd_class == CLASS_TOKEN_READ ||
0210          buffer->cmd_class == CLASS_TOKEN_WRITE) &&
0211          buffer->cmd_select < 3) {
0212         /* tokens enabled ? */
0213         if (!da_tokens) {
0214             dev_dbg(d, "no token support on this system\n");
0215             return -EINVAL;
0216         }
0217 
0218         /* find the matching token ID */
0219         for (i = 0; i < da_num_tokens; i++) {
0220             if (da_tokens[i].location != buffer->input[0])
0221                 continue;
0222             t = da_tokens[i].tokenID;
0223             break;
0224         }
0225 
0226         /* token call; but token didn't exist */
0227         if (!t) {
0228             dev_dbg(d, "token at location %04x doesn't exist\n",
0229                 buffer->input[0]);
0230             return -EINVAL;
0231         }
0232 
0233         /* match against token blacklist */
0234         for (i = 0; i < ARRAY_SIZE(token_blacklist); i++) {
0235             if (!token_blacklist[i].min || !token_blacklist[i].max)
0236                 continue;
0237             if (t >= token_blacklist[i].min &&
0238                 t <= token_blacklist[i].max)
0239                 return -EINVAL;
0240         }
0241 
0242         /* match against token whitelist */
0243         for (i = 0; i < ARRAY_SIZE(token_whitelist); i++) {
0244             if (!token_whitelist[i].min || !token_whitelist[i].max)
0245                 continue;
0246             if (t < token_whitelist[i].min ||
0247                 t > token_whitelist[i].max)
0248                 continue;
0249             if (!token_whitelist[i].need_capability ||
0250                 capable(token_whitelist[i].need_capability)) {
0251                 dev_dbg(d, "whitelisted token: %x\n", t);
0252                 return 0;
0253             }
0254 
0255         }
0256     }
0257     /* match against call whitelist */
0258     for (i = 0; i < ARRAY_SIZE(call_whitelist); i++) {
0259         if (buffer->cmd_class != call_whitelist[i].cmd_class)
0260             continue;
0261         if (buffer->cmd_select != call_whitelist[i].cmd_select)
0262             continue;
0263         if (!call_whitelist[i].need_capability ||
0264             capable(call_whitelist[i].need_capability)) {
0265             dev_dbg(d, "whitelisted capable command: %u/%u\n",
0266             buffer->cmd_class, buffer->cmd_select);
0267             return 0;
0268         }
0269         dev_dbg(d, "missing capability %d for %u/%u\n",
0270             call_whitelist[i].need_capability,
0271             buffer->cmd_class, buffer->cmd_select);
0272 
0273     }
0274 
0275     /* not in a whitelist, only allow processes with capabilities */
0276     if (capable(CAP_SYS_RAWIO)) {
0277         dev_dbg(d, "Allowing %u/%u due to CAP_SYS_RAWIO\n",
0278             buffer->cmd_class, buffer->cmd_select);
0279         return 0;
0280     }
0281 
0282     return -EACCES;
0283 }
0284 EXPORT_SYMBOL_GPL(dell_smbios_call_filter);
0285 
0286 int dell_smbios_call(struct calling_interface_buffer *buffer)
0287 {
0288     int (*call_fn)(struct calling_interface_buffer *) = NULL;
0289     struct device *selected_dev = NULL;
0290     struct smbios_device *priv;
0291     int ret;
0292 
0293     mutex_lock(&smbios_mutex);
0294     list_for_each_entry(priv, &smbios_device_list, list) {
0295         if (!selected_dev || priv->device->id >= selected_dev->id) {
0296             dev_dbg(priv->device, "Trying device ID: %d\n",
0297                 priv->device->id);
0298             call_fn = priv->call_fn;
0299             selected_dev = priv->device;
0300         }
0301     }
0302 
0303     if (!selected_dev) {
0304         ret = -ENODEV;
0305         pr_err("No dell-smbios drivers are loaded\n");
0306         goto out_smbios_call;
0307     }
0308 
0309     ret = call_fn(buffer);
0310 
0311 out_smbios_call:
0312     mutex_unlock(&smbios_mutex);
0313     return ret;
0314 }
0315 EXPORT_SYMBOL_GPL(dell_smbios_call);
0316 
0317 struct calling_interface_token *dell_smbios_find_token(int tokenid)
0318 {
0319     int i;
0320 
0321     if (!da_tokens)
0322         return NULL;
0323 
0324     for (i = 0; i < da_num_tokens; i++) {
0325         if (da_tokens[i].tokenID == tokenid)
0326             return &da_tokens[i];
0327     }
0328 
0329     return NULL;
0330 }
0331 EXPORT_SYMBOL_GPL(dell_smbios_find_token);
0332 
0333 static BLOCKING_NOTIFIER_HEAD(dell_laptop_chain_head);
0334 
0335 int dell_laptop_register_notifier(struct notifier_block *nb)
0336 {
0337     return blocking_notifier_chain_register(&dell_laptop_chain_head, nb);
0338 }
0339 EXPORT_SYMBOL_GPL(dell_laptop_register_notifier);
0340 
0341 int dell_laptop_unregister_notifier(struct notifier_block *nb)
0342 {
0343     return blocking_notifier_chain_unregister(&dell_laptop_chain_head, nb);
0344 }
0345 EXPORT_SYMBOL_GPL(dell_laptop_unregister_notifier);
0346 
0347 void dell_laptop_call_notifier(unsigned long action, void *data)
0348 {
0349     blocking_notifier_call_chain(&dell_laptop_chain_head, action, data);
0350 }
0351 EXPORT_SYMBOL_GPL(dell_laptop_call_notifier);
0352 
0353 static void __init parse_da_table(const struct dmi_header *dm)
0354 {
0355     /* Final token is a terminator, so we don't want to copy it */
0356     int tokens = (dm->length-11)/sizeof(struct calling_interface_token)-1;
0357     struct calling_interface_token *new_da_tokens;
0358     struct calling_interface_structure *table =
0359         container_of(dm, struct calling_interface_structure, header);
0360 
0361     /*
0362      * 4 bytes of table header, plus 7 bytes of Dell header
0363      * plus at least 6 bytes of entry
0364      */
0365 
0366     if (dm->length < 17)
0367         return;
0368 
0369     da_supported_commands = table->supportedCmds;
0370 
0371     new_da_tokens = krealloc(da_tokens, (da_num_tokens + tokens) *
0372                  sizeof(struct calling_interface_token),
0373                  GFP_KERNEL);
0374 
0375     if (!new_da_tokens)
0376         return;
0377     da_tokens = new_da_tokens;
0378 
0379     memcpy(da_tokens+da_num_tokens, table->tokens,
0380            sizeof(struct calling_interface_token) * tokens);
0381 
0382     da_num_tokens += tokens;
0383 }
0384 
0385 static void zero_duplicates(struct device *dev)
0386 {
0387     int i, j;
0388 
0389     for (i = 0; i < da_num_tokens; i++) {
0390         if (da_tokens[i].tokenID == 0)
0391             continue;
0392         for (j = i+1; j < da_num_tokens; j++) {
0393             if (da_tokens[j].tokenID == 0)
0394                 continue;
0395             if (da_tokens[i].tokenID == da_tokens[j].tokenID) {
0396                 dev_dbg(dev, "Zeroing dup token ID %x(%x/%x)\n",
0397                     da_tokens[j].tokenID,
0398                     da_tokens[j].location,
0399                     da_tokens[j].value);
0400                 da_tokens[j].tokenID = 0;
0401             }
0402         }
0403     }
0404 }
0405 
0406 static void __init find_tokens(const struct dmi_header *dm, void *dummy)
0407 {
0408     switch (dm->type) {
0409     case 0xd4: /* Indexed IO */
0410     case 0xd5: /* Protected Area Type 1 */
0411     case 0xd6: /* Protected Area Type 2 */
0412         break;
0413     case 0xda: /* Calling interface */
0414         parse_da_table(dm);
0415         break;
0416     }
0417 }
0418 
0419 static int match_attribute(struct device *dev,
0420                struct device_attribute *attr)
0421 {
0422     int i;
0423 
0424     for (i = 0; i < da_num_tokens * 2; i++) {
0425         if (!token_attrs[i])
0426             continue;
0427         if (strcmp(token_attrs[i]->name, attr->attr.name) == 0)
0428             return i/2;
0429     }
0430     dev_dbg(dev, "couldn't match: %s\n", attr->attr.name);
0431     return -EINVAL;
0432 }
0433 
0434 static ssize_t location_show(struct device *dev,
0435                  struct device_attribute *attr, char *buf)
0436 {
0437     int i;
0438 
0439     if (!capable(CAP_SYS_ADMIN))
0440         return -EPERM;
0441 
0442     i = match_attribute(dev, attr);
0443     if (i > 0)
0444         return scnprintf(buf, PAGE_SIZE, "%08x", da_tokens[i].location);
0445     return 0;
0446 }
0447 
0448 static ssize_t value_show(struct device *dev,
0449               struct device_attribute *attr, char *buf)
0450 {
0451     int i;
0452 
0453     if (!capable(CAP_SYS_ADMIN))
0454         return -EPERM;
0455 
0456     i = match_attribute(dev, attr);
0457     if (i > 0)
0458         return scnprintf(buf, PAGE_SIZE, "%08x", da_tokens[i].value);
0459     return 0;
0460 }
0461 
0462 static struct attribute_group smbios_attribute_group = {
0463     .name = "tokens"
0464 };
0465 
0466 static struct platform_driver platform_driver = {
0467     .driver = {
0468         .name = "dell-smbios",
0469     },
0470 };
0471 
0472 static int build_tokens_sysfs(struct platform_device *dev)
0473 {
0474     char *location_name;
0475     char *value_name;
0476     size_t size;
0477     int ret;
0478     int i, j;
0479 
0480     /* (number of tokens  + 1 for null terminated */
0481     size = sizeof(struct device_attribute) * (da_num_tokens + 1);
0482     token_location_attrs = kzalloc(size, GFP_KERNEL);
0483     if (!token_location_attrs)
0484         return -ENOMEM;
0485     token_value_attrs = kzalloc(size, GFP_KERNEL);
0486     if (!token_value_attrs)
0487         goto out_allocate_value;
0488 
0489     /* need to store both location and value + terminator*/
0490     size = sizeof(struct attribute *) * ((2 * da_num_tokens) + 1);
0491     token_attrs = kzalloc(size, GFP_KERNEL);
0492     if (!token_attrs)
0493         goto out_allocate_attrs;
0494 
0495     for (i = 0, j = 0; i < da_num_tokens; i++) {
0496         /* skip empty */
0497         if (da_tokens[i].tokenID == 0)
0498             continue;
0499         /* add location */
0500         location_name = kasprintf(GFP_KERNEL, "%04x_location",
0501                       da_tokens[i].tokenID);
0502         if (location_name == NULL)
0503             goto out_unwind_strings;
0504         sysfs_attr_init(&token_location_attrs[i].attr);
0505         token_location_attrs[i].attr.name = location_name;
0506         token_location_attrs[i].attr.mode = 0444;
0507         token_location_attrs[i].show = location_show;
0508         token_attrs[j++] = &token_location_attrs[i].attr;
0509 
0510         /* add value */
0511         value_name = kasprintf(GFP_KERNEL, "%04x_value",
0512                        da_tokens[i].tokenID);
0513         if (value_name == NULL)
0514             goto loop_fail_create_value;
0515         sysfs_attr_init(&token_value_attrs[i].attr);
0516         token_value_attrs[i].attr.name = value_name;
0517         token_value_attrs[i].attr.mode = 0444;
0518         token_value_attrs[i].show = value_show;
0519         token_attrs[j++] = &token_value_attrs[i].attr;
0520         continue;
0521 
0522 loop_fail_create_value:
0523         kfree(location_name);
0524         goto out_unwind_strings;
0525     }
0526     smbios_attribute_group.attrs = token_attrs;
0527 
0528     ret = sysfs_create_group(&dev->dev.kobj, &smbios_attribute_group);
0529     if (ret)
0530         goto out_unwind_strings;
0531     return 0;
0532 
0533 out_unwind_strings:
0534     while (i--) {
0535         kfree(token_location_attrs[i].attr.name);
0536         kfree(token_value_attrs[i].attr.name);
0537     }
0538     kfree(token_attrs);
0539 out_allocate_attrs:
0540     kfree(token_value_attrs);
0541 out_allocate_value:
0542     kfree(token_location_attrs);
0543 
0544     return -ENOMEM;
0545 }
0546 
0547 static void free_group(struct platform_device *pdev)
0548 {
0549     int i;
0550 
0551     sysfs_remove_group(&pdev->dev.kobj,
0552                 &smbios_attribute_group);
0553     for (i = 0; i < da_num_tokens; i++) {
0554         kfree(token_location_attrs[i].attr.name);
0555         kfree(token_value_attrs[i].attr.name);
0556     }
0557     kfree(token_attrs);
0558     kfree(token_value_attrs);
0559     kfree(token_location_attrs);
0560 }
0561 
0562 static int __init dell_smbios_init(void)
0563 {
0564     int ret, wmi, smm;
0565 
0566     if (!dmi_find_device(DMI_DEV_TYPE_OEM_STRING, "Dell System", NULL) &&
0567         !dmi_find_device(DMI_DEV_TYPE_OEM_STRING, "www.dell.com", NULL)) {
0568         pr_err("Unable to run on non-Dell system\n");
0569         return -ENODEV;
0570     }
0571 
0572     dmi_walk(find_tokens, NULL);
0573 
0574     ret = platform_driver_register(&platform_driver);
0575     if (ret)
0576         goto fail_platform_driver;
0577 
0578     platform_device = platform_device_alloc("dell-smbios", 0);
0579     if (!platform_device) {
0580         ret = -ENOMEM;
0581         goto fail_platform_device_alloc;
0582     }
0583     ret = platform_device_add(platform_device);
0584     if (ret)
0585         goto fail_platform_device_add;
0586 
0587     /* register backends */
0588     wmi = init_dell_smbios_wmi();
0589     if (wmi)
0590         pr_debug("Failed to initialize WMI backend: %d\n", wmi);
0591     smm = init_dell_smbios_smm();
0592     if (smm)
0593         pr_debug("Failed to initialize SMM backend: %d\n", smm);
0594     if (wmi && smm) {
0595         pr_err("No SMBIOS backends available (wmi: %d, smm: %d)\n",
0596             wmi, smm);
0597         ret = -ENODEV;
0598         goto fail_create_group;
0599     }
0600 
0601     if (da_tokens)  {
0602         /* duplicate tokens will cause problems building sysfs files */
0603         zero_duplicates(&platform_device->dev);
0604 
0605         ret = build_tokens_sysfs(platform_device);
0606         if (ret)
0607             goto fail_sysfs;
0608     }
0609 
0610     return 0;
0611 
0612 fail_sysfs:
0613     free_group(platform_device);
0614 
0615 fail_create_group:
0616     platform_device_del(platform_device);
0617 
0618 fail_platform_device_add:
0619     platform_device_put(platform_device);
0620 
0621 fail_platform_device_alloc:
0622     platform_driver_unregister(&platform_driver);
0623 
0624 fail_platform_driver:
0625     kfree(da_tokens);
0626     return ret;
0627 }
0628 
0629 static void __exit dell_smbios_exit(void)
0630 {
0631     exit_dell_smbios_wmi();
0632     exit_dell_smbios_smm();
0633     mutex_lock(&smbios_mutex);
0634     if (platform_device) {
0635         if (da_tokens)
0636             free_group(platform_device);
0637         platform_device_unregister(platform_device);
0638         platform_driver_unregister(&platform_driver);
0639     }
0640     kfree(da_tokens);
0641     mutex_unlock(&smbios_mutex);
0642 }
0643 
0644 module_init(dell_smbios_init);
0645 module_exit(dell_smbios_exit);
0646 
0647 MODULE_AUTHOR("Matthew Garrett <mjg@redhat.com>");
0648 MODULE_AUTHOR("Gabriele Mazzotta <gabriele.mzt@gmail.com>");
0649 MODULE_AUTHOR("Pali Rohár <pali@kernel.org>");
0650 MODULE_AUTHOR("Mario Limonciello <mario.limonciello@outlook.com>");
0651 MODULE_DESCRIPTION("Common functions for kernel modules using Dell SMBIOS");
0652 MODULE_LICENSE("GPL");