cachepc-linux

Fork of AMDESE/linux with modifications for CachePC side-channel attack
git clone https://git.sinitax.com/sinitax/cachepc-linux
Log | Files | Refs | README | LICENSE | sfeed.txt

dell-smbios-base.c (17598B)


      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		ret = -ENODEV;
    598		goto fail_create_group;
    599	}
    600
    601	if (da_tokens)  {
    602		/* duplicate tokens will cause problems building sysfs files */
    603		zero_duplicates(&platform_device->dev);
    604
    605		ret = build_tokens_sysfs(platform_device);
    606		if (ret)
    607			goto fail_sysfs;
    608	}
    609
    610	return 0;
    611
    612fail_sysfs:
    613	free_group(platform_device);
    614
    615fail_create_group:
    616	platform_device_del(platform_device);
    617
    618fail_platform_device_add:
    619	platform_device_put(platform_device);
    620
    621fail_platform_device_alloc:
    622	platform_driver_unregister(&platform_driver);
    623
    624fail_platform_driver:
    625	kfree(da_tokens);
    626	return ret;
    627}
    628
    629static void __exit dell_smbios_exit(void)
    630{
    631	exit_dell_smbios_wmi();
    632	exit_dell_smbios_smm();
    633	mutex_lock(&smbios_mutex);
    634	if (platform_device) {
    635		if (da_tokens)
    636			free_group(platform_device);
    637		platform_device_unregister(platform_device);
    638		platform_driver_unregister(&platform_driver);
    639	}
    640	kfree(da_tokens);
    641	mutex_unlock(&smbios_mutex);
    642}
    643
    644module_init(dell_smbios_init);
    645module_exit(dell_smbios_exit);
    646
    647MODULE_AUTHOR("Matthew Garrett <mjg@redhat.com>");
    648MODULE_AUTHOR("Gabriele Mazzotta <gabriele.mzt@gmail.com>");
    649MODULE_AUTHOR("Pali Rohár <pali@kernel.org>");
    650MODULE_AUTHOR("Mario Limonciello <mario.limonciello@outlook.com>");
    651MODULE_DESCRIPTION("Common functions for kernel modules using Dell SMBIOS");
    652MODULE_LICENSE("GPL");