18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci *  Common functions for kernel modules using Dell SMBIOS
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci *  Copyright (c) Red Hat <mjg@redhat.com>
68c2ecf20Sopenharmony_ci *  Copyright (c) 2014 Gabriele Mazzotta <gabriele.mzt@gmail.com>
78c2ecf20Sopenharmony_ci *  Copyright (c) 2014 Pali Rohár <pali@kernel.org>
88c2ecf20Sopenharmony_ci *
98c2ecf20Sopenharmony_ci *  Based on documentation in the libsmbios package:
108c2ecf20Sopenharmony_ci *  Copyright (C) 2005-2014 Dell Inc.
118c2ecf20Sopenharmony_ci */
128c2ecf20Sopenharmony_ci#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
138c2ecf20Sopenharmony_ci
148c2ecf20Sopenharmony_ci#include <linux/kernel.h>
158c2ecf20Sopenharmony_ci#include <linux/module.h>
168c2ecf20Sopenharmony_ci#include <linux/capability.h>
178c2ecf20Sopenharmony_ci#include <linux/dmi.h>
188c2ecf20Sopenharmony_ci#include <linux/err.h>
198c2ecf20Sopenharmony_ci#include <linux/mutex.h>
208c2ecf20Sopenharmony_ci#include <linux/platform_device.h>
218c2ecf20Sopenharmony_ci#include <linux/slab.h>
228c2ecf20Sopenharmony_ci#include "dell-smbios.h"
238c2ecf20Sopenharmony_ci
248c2ecf20Sopenharmony_cistatic u32 da_supported_commands;
258c2ecf20Sopenharmony_cistatic int da_num_tokens;
268c2ecf20Sopenharmony_cistatic struct platform_device *platform_device;
278c2ecf20Sopenharmony_cistatic struct calling_interface_token *da_tokens;
288c2ecf20Sopenharmony_cistatic struct device_attribute *token_location_attrs;
298c2ecf20Sopenharmony_cistatic struct device_attribute *token_value_attrs;
308c2ecf20Sopenharmony_cistatic struct attribute **token_attrs;
318c2ecf20Sopenharmony_cistatic DEFINE_MUTEX(smbios_mutex);
328c2ecf20Sopenharmony_ci
338c2ecf20Sopenharmony_cistruct smbios_device {
348c2ecf20Sopenharmony_ci	struct list_head list;
358c2ecf20Sopenharmony_ci	struct device *device;
368c2ecf20Sopenharmony_ci	int (*call_fn)(struct calling_interface_buffer *arg);
378c2ecf20Sopenharmony_ci};
388c2ecf20Sopenharmony_ci
398c2ecf20Sopenharmony_cistruct smbios_call {
408c2ecf20Sopenharmony_ci	u32 need_capability;
418c2ecf20Sopenharmony_ci	int cmd_class;
428c2ecf20Sopenharmony_ci	int cmd_select;
438c2ecf20Sopenharmony_ci};
448c2ecf20Sopenharmony_ci
458c2ecf20Sopenharmony_ci/* calls that are whitelisted for given capabilities */
468c2ecf20Sopenharmony_cistatic struct smbios_call call_whitelist[] = {
478c2ecf20Sopenharmony_ci	/* generally tokens are allowed, but may be further filtered or
488c2ecf20Sopenharmony_ci	 * restricted by token blacklist or whitelist
498c2ecf20Sopenharmony_ci	 */
508c2ecf20Sopenharmony_ci	{CAP_SYS_ADMIN,	CLASS_TOKEN_READ,	SELECT_TOKEN_STD},
518c2ecf20Sopenharmony_ci	{CAP_SYS_ADMIN,	CLASS_TOKEN_READ,	SELECT_TOKEN_AC},
528c2ecf20Sopenharmony_ci	{CAP_SYS_ADMIN,	CLASS_TOKEN_READ,	SELECT_TOKEN_BAT},
538c2ecf20Sopenharmony_ci	{CAP_SYS_ADMIN,	CLASS_TOKEN_WRITE,	SELECT_TOKEN_STD},
548c2ecf20Sopenharmony_ci	{CAP_SYS_ADMIN,	CLASS_TOKEN_WRITE,	SELECT_TOKEN_AC},
558c2ecf20Sopenharmony_ci	{CAP_SYS_ADMIN,	CLASS_TOKEN_WRITE,	SELECT_TOKEN_BAT},
568c2ecf20Sopenharmony_ci	/* used by userspace: fwupdate */
578c2ecf20Sopenharmony_ci	{CAP_SYS_ADMIN, CLASS_ADMIN_PROP,	SELECT_ADMIN_PROP},
588c2ecf20Sopenharmony_ci	/* used by userspace: fwupd */
598c2ecf20Sopenharmony_ci	{CAP_SYS_ADMIN,	CLASS_INFO,		SELECT_DOCK},
608c2ecf20Sopenharmony_ci	{CAP_SYS_ADMIN,	CLASS_FLASH_INTERFACE,	SELECT_FLASH_INTERFACE},
618c2ecf20Sopenharmony_ci};
628c2ecf20Sopenharmony_ci
638c2ecf20Sopenharmony_ci/* calls that are explicitly blacklisted */
648c2ecf20Sopenharmony_cistatic struct smbios_call call_blacklist[] = {
658c2ecf20Sopenharmony_ci	{0x0000,  1,  7}, /* manufacturing use */
668c2ecf20Sopenharmony_ci	{0x0000,  6,  5}, /* manufacturing use */
678c2ecf20Sopenharmony_ci	{0x0000, 11,  3}, /* write once */
688c2ecf20Sopenharmony_ci	{0x0000, 11,  7}, /* write once */
698c2ecf20Sopenharmony_ci	{0x0000, 11, 11}, /* write once */
708c2ecf20Sopenharmony_ci	{0x0000, 19, -1}, /* diagnostics */
718c2ecf20Sopenharmony_ci	/* handled by kernel: dell-laptop */
728c2ecf20Sopenharmony_ci	{0x0000, CLASS_INFO, SELECT_RFKILL},
738c2ecf20Sopenharmony_ci	{0x0000, CLASS_KBD_BACKLIGHT, SELECT_KBD_BACKLIGHT},
748c2ecf20Sopenharmony_ci};
758c2ecf20Sopenharmony_ci
768c2ecf20Sopenharmony_cistruct token_range {
778c2ecf20Sopenharmony_ci	u32 need_capability;
788c2ecf20Sopenharmony_ci	u16 min;
798c2ecf20Sopenharmony_ci	u16 max;
808c2ecf20Sopenharmony_ci};
818c2ecf20Sopenharmony_ci
828c2ecf20Sopenharmony_ci/* tokens that are whitelisted for given capabilities */
838c2ecf20Sopenharmony_cistatic struct token_range token_whitelist[] = {
848c2ecf20Sopenharmony_ci	/* used by userspace: fwupdate */
858c2ecf20Sopenharmony_ci	{CAP_SYS_ADMIN,	CAPSULE_EN_TOKEN,	CAPSULE_DIS_TOKEN},
868c2ecf20Sopenharmony_ci	/* can indicate to userspace that WMI is needed */
878c2ecf20Sopenharmony_ci	{0x0000,	WSMT_EN_TOKEN,		WSMT_DIS_TOKEN}
888c2ecf20Sopenharmony_ci};
898c2ecf20Sopenharmony_ci
908c2ecf20Sopenharmony_ci/* tokens that are explicitly blacklisted */
918c2ecf20Sopenharmony_cistatic struct token_range token_blacklist[] = {
928c2ecf20Sopenharmony_ci	{0x0000, 0x0058, 0x0059}, /* ME use */
938c2ecf20Sopenharmony_ci	{0x0000, 0x00CD, 0x00D0}, /* raid shadow copy */
948c2ecf20Sopenharmony_ci	{0x0000, 0x013A, 0x01FF}, /* sata shadow copy */
958c2ecf20Sopenharmony_ci	{0x0000, 0x0175, 0x0176}, /* write once */
968c2ecf20Sopenharmony_ci	{0x0000, 0x0195, 0x0197}, /* diagnostics */
978c2ecf20Sopenharmony_ci	{0x0000, 0x01DC, 0x01DD}, /* manufacturing use */
988c2ecf20Sopenharmony_ci	{0x0000, 0x027D, 0x0284}, /* diagnostics */
998c2ecf20Sopenharmony_ci	{0x0000, 0x02E3, 0x02E3}, /* manufacturing use */
1008c2ecf20Sopenharmony_ci	{0x0000, 0x02FF, 0x02FF}, /* manufacturing use */
1018c2ecf20Sopenharmony_ci	{0x0000, 0x0300, 0x0302}, /* manufacturing use */
1028c2ecf20Sopenharmony_ci	{0x0000, 0x0325, 0x0326}, /* manufacturing use */
1038c2ecf20Sopenharmony_ci	{0x0000, 0x0332, 0x0335}, /* fan control */
1048c2ecf20Sopenharmony_ci	{0x0000, 0x0350, 0x0350}, /* manufacturing use */
1058c2ecf20Sopenharmony_ci	{0x0000, 0x0363, 0x0363}, /* manufacturing use */
1068c2ecf20Sopenharmony_ci	{0x0000, 0x0368, 0x0368}, /* manufacturing use */
1078c2ecf20Sopenharmony_ci	{0x0000, 0x03F6, 0x03F7}, /* manufacturing use */
1088c2ecf20Sopenharmony_ci	{0x0000, 0x049E, 0x049F}, /* manufacturing use */
1098c2ecf20Sopenharmony_ci	{0x0000, 0x04A0, 0x04A3}, /* disagnostics */
1108c2ecf20Sopenharmony_ci	{0x0000, 0x04E6, 0x04E7}, /* manufacturing use */
1118c2ecf20Sopenharmony_ci	{0x0000, 0x4000, 0x7FFF}, /* internal BIOS use */
1128c2ecf20Sopenharmony_ci	{0x0000, 0x9000, 0x9001}, /* internal BIOS use */
1138c2ecf20Sopenharmony_ci	{0x0000, 0xA000, 0xBFFF}, /* write only */
1148c2ecf20Sopenharmony_ci	{0x0000, 0xEFF0, 0xEFFF}, /* internal BIOS use */
1158c2ecf20Sopenharmony_ci	/* handled by kernel: dell-laptop */
1168c2ecf20Sopenharmony_ci	{0x0000, BRIGHTNESS_TOKEN,	BRIGHTNESS_TOKEN},
1178c2ecf20Sopenharmony_ci	{0x0000, KBD_LED_OFF_TOKEN,	KBD_LED_AUTO_TOKEN},
1188c2ecf20Sopenharmony_ci	{0x0000, KBD_LED_AC_TOKEN,	KBD_LED_AC_TOKEN},
1198c2ecf20Sopenharmony_ci	{0x0000, KBD_LED_AUTO_25_TOKEN,	KBD_LED_AUTO_75_TOKEN},
1208c2ecf20Sopenharmony_ci	{0x0000, KBD_LED_AUTO_100_TOKEN,	KBD_LED_AUTO_100_TOKEN},
1218c2ecf20Sopenharmony_ci	{0x0000, GLOBAL_MIC_MUTE_ENABLE,	GLOBAL_MIC_MUTE_DISABLE},
1228c2ecf20Sopenharmony_ci};
1238c2ecf20Sopenharmony_ci
1248c2ecf20Sopenharmony_cistatic LIST_HEAD(smbios_device_list);
1258c2ecf20Sopenharmony_ci
1268c2ecf20Sopenharmony_ciint dell_smbios_error(int value)
1278c2ecf20Sopenharmony_ci{
1288c2ecf20Sopenharmony_ci	switch (value) {
1298c2ecf20Sopenharmony_ci	case 0: /* Completed successfully */
1308c2ecf20Sopenharmony_ci		return 0;
1318c2ecf20Sopenharmony_ci	case -1: /* Completed with error */
1328c2ecf20Sopenharmony_ci		return -EIO;
1338c2ecf20Sopenharmony_ci	case -2: /* Function not supported */
1348c2ecf20Sopenharmony_ci		return -ENXIO;
1358c2ecf20Sopenharmony_ci	default: /* Unknown error */
1368c2ecf20Sopenharmony_ci		return -EINVAL;
1378c2ecf20Sopenharmony_ci	}
1388c2ecf20Sopenharmony_ci}
1398c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(dell_smbios_error);
1408c2ecf20Sopenharmony_ci
1418c2ecf20Sopenharmony_ciint dell_smbios_register_device(struct device *d, void *call_fn)
1428c2ecf20Sopenharmony_ci{
1438c2ecf20Sopenharmony_ci	struct smbios_device *priv;
1448c2ecf20Sopenharmony_ci
1458c2ecf20Sopenharmony_ci	priv = devm_kzalloc(d, sizeof(struct smbios_device), GFP_KERNEL);
1468c2ecf20Sopenharmony_ci	if (!priv)
1478c2ecf20Sopenharmony_ci		return -ENOMEM;
1488c2ecf20Sopenharmony_ci	get_device(d);
1498c2ecf20Sopenharmony_ci	priv->device = d;
1508c2ecf20Sopenharmony_ci	priv->call_fn = call_fn;
1518c2ecf20Sopenharmony_ci	mutex_lock(&smbios_mutex);
1528c2ecf20Sopenharmony_ci	list_add_tail(&priv->list, &smbios_device_list);
1538c2ecf20Sopenharmony_ci	mutex_unlock(&smbios_mutex);
1548c2ecf20Sopenharmony_ci	dev_dbg(d, "Added device: %s\n", d->driver->name);
1558c2ecf20Sopenharmony_ci	return 0;
1568c2ecf20Sopenharmony_ci}
1578c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(dell_smbios_register_device);
1588c2ecf20Sopenharmony_ci
1598c2ecf20Sopenharmony_civoid dell_smbios_unregister_device(struct device *d)
1608c2ecf20Sopenharmony_ci{
1618c2ecf20Sopenharmony_ci	struct smbios_device *priv;
1628c2ecf20Sopenharmony_ci
1638c2ecf20Sopenharmony_ci	mutex_lock(&smbios_mutex);
1648c2ecf20Sopenharmony_ci	list_for_each_entry(priv, &smbios_device_list, list) {
1658c2ecf20Sopenharmony_ci		if (priv->device == d) {
1668c2ecf20Sopenharmony_ci			list_del(&priv->list);
1678c2ecf20Sopenharmony_ci			put_device(d);
1688c2ecf20Sopenharmony_ci			break;
1698c2ecf20Sopenharmony_ci		}
1708c2ecf20Sopenharmony_ci	}
1718c2ecf20Sopenharmony_ci	mutex_unlock(&smbios_mutex);
1728c2ecf20Sopenharmony_ci	dev_dbg(d, "Remove device: %s\n", d->driver->name);
1738c2ecf20Sopenharmony_ci}
1748c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(dell_smbios_unregister_device);
1758c2ecf20Sopenharmony_ci
1768c2ecf20Sopenharmony_ciint dell_smbios_call_filter(struct device *d,
1778c2ecf20Sopenharmony_ci			    struct calling_interface_buffer *buffer)
1788c2ecf20Sopenharmony_ci{
1798c2ecf20Sopenharmony_ci	u16 t = 0;
1808c2ecf20Sopenharmony_ci	int i;
1818c2ecf20Sopenharmony_ci
1828c2ecf20Sopenharmony_ci	/* can't make calls over 30 */
1838c2ecf20Sopenharmony_ci	if (buffer->cmd_class > 30) {
1848c2ecf20Sopenharmony_ci		dev_dbg(d, "class too big: %u\n", buffer->cmd_class);
1858c2ecf20Sopenharmony_ci		return -EINVAL;
1868c2ecf20Sopenharmony_ci	}
1878c2ecf20Sopenharmony_ci
1888c2ecf20Sopenharmony_ci	/* supported calls on the particular system */
1898c2ecf20Sopenharmony_ci	if (!(da_supported_commands & (1 << buffer->cmd_class))) {
1908c2ecf20Sopenharmony_ci		dev_dbg(d, "invalid command, supported commands: 0x%8x\n",
1918c2ecf20Sopenharmony_ci			da_supported_commands);
1928c2ecf20Sopenharmony_ci		return -EINVAL;
1938c2ecf20Sopenharmony_ci	}
1948c2ecf20Sopenharmony_ci
1958c2ecf20Sopenharmony_ci	/* match against call blacklist  */
1968c2ecf20Sopenharmony_ci	for (i = 0; i < ARRAY_SIZE(call_blacklist); i++) {
1978c2ecf20Sopenharmony_ci		if (buffer->cmd_class != call_blacklist[i].cmd_class)
1988c2ecf20Sopenharmony_ci			continue;
1998c2ecf20Sopenharmony_ci		if (buffer->cmd_select != call_blacklist[i].cmd_select &&
2008c2ecf20Sopenharmony_ci		    call_blacklist[i].cmd_select != -1)
2018c2ecf20Sopenharmony_ci			continue;
2028c2ecf20Sopenharmony_ci		dev_dbg(d, "blacklisted command: %u/%u\n",
2038c2ecf20Sopenharmony_ci			buffer->cmd_class, buffer->cmd_select);
2048c2ecf20Sopenharmony_ci		return -EINVAL;
2058c2ecf20Sopenharmony_ci	}
2068c2ecf20Sopenharmony_ci
2078c2ecf20Sopenharmony_ci	/* if a token call, find token ID */
2088c2ecf20Sopenharmony_ci
2098c2ecf20Sopenharmony_ci	if ((buffer->cmd_class == CLASS_TOKEN_READ ||
2108c2ecf20Sopenharmony_ci	     buffer->cmd_class == CLASS_TOKEN_WRITE) &&
2118c2ecf20Sopenharmony_ci	     buffer->cmd_select < 3) {
2128c2ecf20Sopenharmony_ci		/* tokens enabled ? */
2138c2ecf20Sopenharmony_ci		if (!da_tokens) {
2148c2ecf20Sopenharmony_ci			dev_dbg(d, "no token support on this system\n");
2158c2ecf20Sopenharmony_ci			return -EINVAL;
2168c2ecf20Sopenharmony_ci		}
2178c2ecf20Sopenharmony_ci
2188c2ecf20Sopenharmony_ci		/* find the matching token ID */
2198c2ecf20Sopenharmony_ci		for (i = 0; i < da_num_tokens; i++) {
2208c2ecf20Sopenharmony_ci			if (da_tokens[i].location != buffer->input[0])
2218c2ecf20Sopenharmony_ci				continue;
2228c2ecf20Sopenharmony_ci			t = da_tokens[i].tokenID;
2238c2ecf20Sopenharmony_ci			break;
2248c2ecf20Sopenharmony_ci		}
2258c2ecf20Sopenharmony_ci
2268c2ecf20Sopenharmony_ci		/* token call; but token didn't exist */
2278c2ecf20Sopenharmony_ci		if (!t) {
2288c2ecf20Sopenharmony_ci			dev_dbg(d, "token at location %04x doesn't exist\n",
2298c2ecf20Sopenharmony_ci				buffer->input[0]);
2308c2ecf20Sopenharmony_ci			return -EINVAL;
2318c2ecf20Sopenharmony_ci		}
2328c2ecf20Sopenharmony_ci
2338c2ecf20Sopenharmony_ci		/* match against token blacklist */
2348c2ecf20Sopenharmony_ci		for (i = 0; i < ARRAY_SIZE(token_blacklist); i++) {
2358c2ecf20Sopenharmony_ci			if (!token_blacklist[i].min || !token_blacklist[i].max)
2368c2ecf20Sopenharmony_ci				continue;
2378c2ecf20Sopenharmony_ci			if (t >= token_blacklist[i].min &&
2388c2ecf20Sopenharmony_ci			    t <= token_blacklist[i].max)
2398c2ecf20Sopenharmony_ci				return -EINVAL;
2408c2ecf20Sopenharmony_ci		}
2418c2ecf20Sopenharmony_ci
2428c2ecf20Sopenharmony_ci		/* match against token whitelist */
2438c2ecf20Sopenharmony_ci		for (i = 0; i < ARRAY_SIZE(token_whitelist); i++) {
2448c2ecf20Sopenharmony_ci			if (!token_whitelist[i].min || !token_whitelist[i].max)
2458c2ecf20Sopenharmony_ci				continue;
2468c2ecf20Sopenharmony_ci			if (t < token_whitelist[i].min ||
2478c2ecf20Sopenharmony_ci			    t > token_whitelist[i].max)
2488c2ecf20Sopenharmony_ci				continue;
2498c2ecf20Sopenharmony_ci			if (!token_whitelist[i].need_capability ||
2508c2ecf20Sopenharmony_ci			    capable(token_whitelist[i].need_capability)) {
2518c2ecf20Sopenharmony_ci				dev_dbg(d, "whitelisted token: %x\n", t);
2528c2ecf20Sopenharmony_ci				return 0;
2538c2ecf20Sopenharmony_ci			}
2548c2ecf20Sopenharmony_ci
2558c2ecf20Sopenharmony_ci		}
2568c2ecf20Sopenharmony_ci	}
2578c2ecf20Sopenharmony_ci	/* match against call whitelist */
2588c2ecf20Sopenharmony_ci	for (i = 0; i < ARRAY_SIZE(call_whitelist); i++) {
2598c2ecf20Sopenharmony_ci		if (buffer->cmd_class != call_whitelist[i].cmd_class)
2608c2ecf20Sopenharmony_ci			continue;
2618c2ecf20Sopenharmony_ci		if (buffer->cmd_select != call_whitelist[i].cmd_select)
2628c2ecf20Sopenharmony_ci			continue;
2638c2ecf20Sopenharmony_ci		if (!call_whitelist[i].need_capability ||
2648c2ecf20Sopenharmony_ci		    capable(call_whitelist[i].need_capability)) {
2658c2ecf20Sopenharmony_ci			dev_dbg(d, "whitelisted capable command: %u/%u\n",
2668c2ecf20Sopenharmony_ci			buffer->cmd_class, buffer->cmd_select);
2678c2ecf20Sopenharmony_ci			return 0;
2688c2ecf20Sopenharmony_ci		}
2698c2ecf20Sopenharmony_ci		dev_dbg(d, "missing capability %d for %u/%u\n",
2708c2ecf20Sopenharmony_ci			call_whitelist[i].need_capability,
2718c2ecf20Sopenharmony_ci			buffer->cmd_class, buffer->cmd_select);
2728c2ecf20Sopenharmony_ci
2738c2ecf20Sopenharmony_ci	}
2748c2ecf20Sopenharmony_ci
2758c2ecf20Sopenharmony_ci	/* not in a whitelist, only allow processes with capabilities */
2768c2ecf20Sopenharmony_ci	if (capable(CAP_SYS_RAWIO)) {
2778c2ecf20Sopenharmony_ci		dev_dbg(d, "Allowing %u/%u due to CAP_SYS_RAWIO\n",
2788c2ecf20Sopenharmony_ci			buffer->cmd_class, buffer->cmd_select);
2798c2ecf20Sopenharmony_ci		return 0;
2808c2ecf20Sopenharmony_ci	}
2818c2ecf20Sopenharmony_ci
2828c2ecf20Sopenharmony_ci	return -EACCES;
2838c2ecf20Sopenharmony_ci}
2848c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(dell_smbios_call_filter);
2858c2ecf20Sopenharmony_ci
2868c2ecf20Sopenharmony_ciint dell_smbios_call(struct calling_interface_buffer *buffer)
2878c2ecf20Sopenharmony_ci{
2888c2ecf20Sopenharmony_ci	int (*call_fn)(struct calling_interface_buffer *) = NULL;
2898c2ecf20Sopenharmony_ci	struct device *selected_dev = NULL;
2908c2ecf20Sopenharmony_ci	struct smbios_device *priv;
2918c2ecf20Sopenharmony_ci	int ret;
2928c2ecf20Sopenharmony_ci
2938c2ecf20Sopenharmony_ci	mutex_lock(&smbios_mutex);
2948c2ecf20Sopenharmony_ci	list_for_each_entry(priv, &smbios_device_list, list) {
2958c2ecf20Sopenharmony_ci		if (!selected_dev || priv->device->id >= selected_dev->id) {
2968c2ecf20Sopenharmony_ci			dev_dbg(priv->device, "Trying device ID: %d\n",
2978c2ecf20Sopenharmony_ci				priv->device->id);
2988c2ecf20Sopenharmony_ci			call_fn = priv->call_fn;
2998c2ecf20Sopenharmony_ci			selected_dev = priv->device;
3008c2ecf20Sopenharmony_ci		}
3018c2ecf20Sopenharmony_ci	}
3028c2ecf20Sopenharmony_ci
3038c2ecf20Sopenharmony_ci	if (!selected_dev) {
3048c2ecf20Sopenharmony_ci		ret = -ENODEV;
3058c2ecf20Sopenharmony_ci		pr_err("No dell-smbios drivers are loaded\n");
3068c2ecf20Sopenharmony_ci		goto out_smbios_call;
3078c2ecf20Sopenharmony_ci	}
3088c2ecf20Sopenharmony_ci
3098c2ecf20Sopenharmony_ci	ret = call_fn(buffer);
3108c2ecf20Sopenharmony_ci
3118c2ecf20Sopenharmony_ciout_smbios_call:
3128c2ecf20Sopenharmony_ci	mutex_unlock(&smbios_mutex);
3138c2ecf20Sopenharmony_ci	return ret;
3148c2ecf20Sopenharmony_ci}
3158c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(dell_smbios_call);
3168c2ecf20Sopenharmony_ci
3178c2ecf20Sopenharmony_cistruct calling_interface_token *dell_smbios_find_token(int tokenid)
3188c2ecf20Sopenharmony_ci{
3198c2ecf20Sopenharmony_ci	int i;
3208c2ecf20Sopenharmony_ci
3218c2ecf20Sopenharmony_ci	if (!da_tokens)
3228c2ecf20Sopenharmony_ci		return NULL;
3238c2ecf20Sopenharmony_ci
3248c2ecf20Sopenharmony_ci	for (i = 0; i < da_num_tokens; i++) {
3258c2ecf20Sopenharmony_ci		if (da_tokens[i].tokenID == tokenid)
3268c2ecf20Sopenharmony_ci			return &da_tokens[i];
3278c2ecf20Sopenharmony_ci	}
3288c2ecf20Sopenharmony_ci
3298c2ecf20Sopenharmony_ci	return NULL;
3308c2ecf20Sopenharmony_ci}
3318c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(dell_smbios_find_token);
3328c2ecf20Sopenharmony_ci
3338c2ecf20Sopenharmony_cistatic BLOCKING_NOTIFIER_HEAD(dell_laptop_chain_head);
3348c2ecf20Sopenharmony_ci
3358c2ecf20Sopenharmony_ciint dell_laptop_register_notifier(struct notifier_block *nb)
3368c2ecf20Sopenharmony_ci{
3378c2ecf20Sopenharmony_ci	return blocking_notifier_chain_register(&dell_laptop_chain_head, nb);
3388c2ecf20Sopenharmony_ci}
3398c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(dell_laptop_register_notifier);
3408c2ecf20Sopenharmony_ci
3418c2ecf20Sopenharmony_ciint dell_laptop_unregister_notifier(struct notifier_block *nb)
3428c2ecf20Sopenharmony_ci{
3438c2ecf20Sopenharmony_ci	return blocking_notifier_chain_unregister(&dell_laptop_chain_head, nb);
3448c2ecf20Sopenharmony_ci}
3458c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(dell_laptop_unregister_notifier);
3468c2ecf20Sopenharmony_ci
3478c2ecf20Sopenharmony_civoid dell_laptop_call_notifier(unsigned long action, void *data)
3488c2ecf20Sopenharmony_ci{
3498c2ecf20Sopenharmony_ci	blocking_notifier_call_chain(&dell_laptop_chain_head, action, data);
3508c2ecf20Sopenharmony_ci}
3518c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(dell_laptop_call_notifier);
3528c2ecf20Sopenharmony_ci
3538c2ecf20Sopenharmony_cistatic void __init parse_da_table(const struct dmi_header *dm)
3548c2ecf20Sopenharmony_ci{
3558c2ecf20Sopenharmony_ci	/* Final token is a terminator, so we don't want to copy it */
3568c2ecf20Sopenharmony_ci	int tokens = (dm->length-11)/sizeof(struct calling_interface_token)-1;
3578c2ecf20Sopenharmony_ci	struct calling_interface_token *new_da_tokens;
3588c2ecf20Sopenharmony_ci	struct calling_interface_structure *table =
3598c2ecf20Sopenharmony_ci		container_of(dm, struct calling_interface_structure, header);
3608c2ecf20Sopenharmony_ci
3618c2ecf20Sopenharmony_ci	/*
3628c2ecf20Sopenharmony_ci	 * 4 bytes of table header, plus 7 bytes of Dell header
3638c2ecf20Sopenharmony_ci	 * plus at least 6 bytes of entry
3648c2ecf20Sopenharmony_ci	 */
3658c2ecf20Sopenharmony_ci
3668c2ecf20Sopenharmony_ci	if (dm->length < 17)
3678c2ecf20Sopenharmony_ci		return;
3688c2ecf20Sopenharmony_ci
3698c2ecf20Sopenharmony_ci	da_supported_commands = table->supportedCmds;
3708c2ecf20Sopenharmony_ci
3718c2ecf20Sopenharmony_ci	new_da_tokens = krealloc(da_tokens, (da_num_tokens + tokens) *
3728c2ecf20Sopenharmony_ci				 sizeof(struct calling_interface_token),
3738c2ecf20Sopenharmony_ci				 GFP_KERNEL);
3748c2ecf20Sopenharmony_ci
3758c2ecf20Sopenharmony_ci	if (!new_da_tokens)
3768c2ecf20Sopenharmony_ci		return;
3778c2ecf20Sopenharmony_ci	da_tokens = new_da_tokens;
3788c2ecf20Sopenharmony_ci
3798c2ecf20Sopenharmony_ci	memcpy(da_tokens+da_num_tokens, table->tokens,
3808c2ecf20Sopenharmony_ci	       sizeof(struct calling_interface_token) * tokens);
3818c2ecf20Sopenharmony_ci
3828c2ecf20Sopenharmony_ci	da_num_tokens += tokens;
3838c2ecf20Sopenharmony_ci}
3848c2ecf20Sopenharmony_ci
3858c2ecf20Sopenharmony_cistatic void zero_duplicates(struct device *dev)
3868c2ecf20Sopenharmony_ci{
3878c2ecf20Sopenharmony_ci	int i, j;
3888c2ecf20Sopenharmony_ci
3898c2ecf20Sopenharmony_ci	for (i = 0; i < da_num_tokens; i++) {
3908c2ecf20Sopenharmony_ci		if (da_tokens[i].tokenID == 0)
3918c2ecf20Sopenharmony_ci			continue;
3928c2ecf20Sopenharmony_ci		for (j = i+1; j < da_num_tokens; j++) {
3938c2ecf20Sopenharmony_ci			if (da_tokens[j].tokenID == 0)
3948c2ecf20Sopenharmony_ci				continue;
3958c2ecf20Sopenharmony_ci			if (da_tokens[i].tokenID == da_tokens[j].tokenID) {
3968c2ecf20Sopenharmony_ci				dev_dbg(dev, "Zeroing dup token ID %x(%x/%x)\n",
3978c2ecf20Sopenharmony_ci					da_tokens[j].tokenID,
3988c2ecf20Sopenharmony_ci					da_tokens[j].location,
3998c2ecf20Sopenharmony_ci					da_tokens[j].value);
4008c2ecf20Sopenharmony_ci				da_tokens[j].tokenID = 0;
4018c2ecf20Sopenharmony_ci			}
4028c2ecf20Sopenharmony_ci		}
4038c2ecf20Sopenharmony_ci	}
4048c2ecf20Sopenharmony_ci}
4058c2ecf20Sopenharmony_ci
4068c2ecf20Sopenharmony_cistatic void __init find_tokens(const struct dmi_header *dm, void *dummy)
4078c2ecf20Sopenharmony_ci{
4088c2ecf20Sopenharmony_ci	switch (dm->type) {
4098c2ecf20Sopenharmony_ci	case 0xd4: /* Indexed IO */
4108c2ecf20Sopenharmony_ci	case 0xd5: /* Protected Area Type 1 */
4118c2ecf20Sopenharmony_ci	case 0xd6: /* Protected Area Type 2 */
4128c2ecf20Sopenharmony_ci		break;
4138c2ecf20Sopenharmony_ci	case 0xda: /* Calling interface */
4148c2ecf20Sopenharmony_ci		parse_da_table(dm);
4158c2ecf20Sopenharmony_ci		break;
4168c2ecf20Sopenharmony_ci	}
4178c2ecf20Sopenharmony_ci}
4188c2ecf20Sopenharmony_ci
4198c2ecf20Sopenharmony_cistatic int match_attribute(struct device *dev,
4208c2ecf20Sopenharmony_ci			   struct device_attribute *attr)
4218c2ecf20Sopenharmony_ci{
4228c2ecf20Sopenharmony_ci	int i;
4238c2ecf20Sopenharmony_ci
4248c2ecf20Sopenharmony_ci	for (i = 0; i < da_num_tokens * 2; i++) {
4258c2ecf20Sopenharmony_ci		if (!token_attrs[i])
4268c2ecf20Sopenharmony_ci			continue;
4278c2ecf20Sopenharmony_ci		if (strcmp(token_attrs[i]->name, attr->attr.name) == 0)
4288c2ecf20Sopenharmony_ci			return i/2;
4298c2ecf20Sopenharmony_ci	}
4308c2ecf20Sopenharmony_ci	dev_dbg(dev, "couldn't match: %s\n", attr->attr.name);
4318c2ecf20Sopenharmony_ci	return -EINVAL;
4328c2ecf20Sopenharmony_ci}
4338c2ecf20Sopenharmony_ci
4348c2ecf20Sopenharmony_cistatic ssize_t location_show(struct device *dev,
4358c2ecf20Sopenharmony_ci			     struct device_attribute *attr, char *buf)
4368c2ecf20Sopenharmony_ci{
4378c2ecf20Sopenharmony_ci	int i;
4388c2ecf20Sopenharmony_ci
4398c2ecf20Sopenharmony_ci	if (!capable(CAP_SYS_ADMIN))
4408c2ecf20Sopenharmony_ci		return -EPERM;
4418c2ecf20Sopenharmony_ci
4428c2ecf20Sopenharmony_ci	i = match_attribute(dev, attr);
4438c2ecf20Sopenharmony_ci	if (i > 0)
4448c2ecf20Sopenharmony_ci		return scnprintf(buf, PAGE_SIZE, "%08x", da_tokens[i].location);
4458c2ecf20Sopenharmony_ci	return 0;
4468c2ecf20Sopenharmony_ci}
4478c2ecf20Sopenharmony_ci
4488c2ecf20Sopenharmony_cistatic ssize_t value_show(struct device *dev,
4498c2ecf20Sopenharmony_ci			  struct device_attribute *attr, char *buf)
4508c2ecf20Sopenharmony_ci{
4518c2ecf20Sopenharmony_ci	int i;
4528c2ecf20Sopenharmony_ci
4538c2ecf20Sopenharmony_ci	if (!capable(CAP_SYS_ADMIN))
4548c2ecf20Sopenharmony_ci		return -EPERM;
4558c2ecf20Sopenharmony_ci
4568c2ecf20Sopenharmony_ci	i = match_attribute(dev, attr);
4578c2ecf20Sopenharmony_ci	if (i > 0)
4588c2ecf20Sopenharmony_ci		return scnprintf(buf, PAGE_SIZE, "%08x", da_tokens[i].value);
4598c2ecf20Sopenharmony_ci	return 0;
4608c2ecf20Sopenharmony_ci}
4618c2ecf20Sopenharmony_ci
4628c2ecf20Sopenharmony_cistatic struct attribute_group smbios_attribute_group = {
4638c2ecf20Sopenharmony_ci	.name = "tokens"
4648c2ecf20Sopenharmony_ci};
4658c2ecf20Sopenharmony_ci
4668c2ecf20Sopenharmony_cistatic struct platform_driver platform_driver = {
4678c2ecf20Sopenharmony_ci	.driver = {
4688c2ecf20Sopenharmony_ci		.name = "dell-smbios",
4698c2ecf20Sopenharmony_ci	},
4708c2ecf20Sopenharmony_ci};
4718c2ecf20Sopenharmony_ci
4728c2ecf20Sopenharmony_cistatic int build_tokens_sysfs(struct platform_device *dev)
4738c2ecf20Sopenharmony_ci{
4748c2ecf20Sopenharmony_ci	char *location_name;
4758c2ecf20Sopenharmony_ci	char *value_name;
4768c2ecf20Sopenharmony_ci	size_t size;
4778c2ecf20Sopenharmony_ci	int ret;
4788c2ecf20Sopenharmony_ci	int i, j;
4798c2ecf20Sopenharmony_ci
4808c2ecf20Sopenharmony_ci	/* (number of tokens  + 1 for null terminated */
4818c2ecf20Sopenharmony_ci	size = sizeof(struct device_attribute) * (da_num_tokens + 1);
4828c2ecf20Sopenharmony_ci	token_location_attrs = kzalloc(size, GFP_KERNEL);
4838c2ecf20Sopenharmony_ci	if (!token_location_attrs)
4848c2ecf20Sopenharmony_ci		return -ENOMEM;
4858c2ecf20Sopenharmony_ci	token_value_attrs = kzalloc(size, GFP_KERNEL);
4868c2ecf20Sopenharmony_ci	if (!token_value_attrs)
4878c2ecf20Sopenharmony_ci		goto out_allocate_value;
4888c2ecf20Sopenharmony_ci
4898c2ecf20Sopenharmony_ci	/* need to store both location and value + terminator*/
4908c2ecf20Sopenharmony_ci	size = sizeof(struct attribute *) * ((2 * da_num_tokens) + 1);
4918c2ecf20Sopenharmony_ci	token_attrs = kzalloc(size, GFP_KERNEL);
4928c2ecf20Sopenharmony_ci	if (!token_attrs)
4938c2ecf20Sopenharmony_ci		goto out_allocate_attrs;
4948c2ecf20Sopenharmony_ci
4958c2ecf20Sopenharmony_ci	for (i = 0, j = 0; i < da_num_tokens; i++) {
4968c2ecf20Sopenharmony_ci		/* skip empty */
4978c2ecf20Sopenharmony_ci		if (da_tokens[i].tokenID == 0)
4988c2ecf20Sopenharmony_ci			continue;
4998c2ecf20Sopenharmony_ci		/* add location */
5008c2ecf20Sopenharmony_ci		location_name = kasprintf(GFP_KERNEL, "%04x_location",
5018c2ecf20Sopenharmony_ci					  da_tokens[i].tokenID);
5028c2ecf20Sopenharmony_ci		if (location_name == NULL)
5038c2ecf20Sopenharmony_ci			goto out_unwind_strings;
5048c2ecf20Sopenharmony_ci		sysfs_attr_init(&token_location_attrs[i].attr);
5058c2ecf20Sopenharmony_ci		token_location_attrs[i].attr.name = location_name;
5068c2ecf20Sopenharmony_ci		token_location_attrs[i].attr.mode = 0444;
5078c2ecf20Sopenharmony_ci		token_location_attrs[i].show = location_show;
5088c2ecf20Sopenharmony_ci		token_attrs[j++] = &token_location_attrs[i].attr;
5098c2ecf20Sopenharmony_ci
5108c2ecf20Sopenharmony_ci		/* add value */
5118c2ecf20Sopenharmony_ci		value_name = kasprintf(GFP_KERNEL, "%04x_value",
5128c2ecf20Sopenharmony_ci				       da_tokens[i].tokenID);
5138c2ecf20Sopenharmony_ci		if (value_name == NULL)
5148c2ecf20Sopenharmony_ci			goto loop_fail_create_value;
5158c2ecf20Sopenharmony_ci		sysfs_attr_init(&token_value_attrs[i].attr);
5168c2ecf20Sopenharmony_ci		token_value_attrs[i].attr.name = value_name;
5178c2ecf20Sopenharmony_ci		token_value_attrs[i].attr.mode = 0444;
5188c2ecf20Sopenharmony_ci		token_value_attrs[i].show = value_show;
5198c2ecf20Sopenharmony_ci		token_attrs[j++] = &token_value_attrs[i].attr;
5208c2ecf20Sopenharmony_ci		continue;
5218c2ecf20Sopenharmony_ci
5228c2ecf20Sopenharmony_ciloop_fail_create_value:
5238c2ecf20Sopenharmony_ci		kfree(location_name);
5248c2ecf20Sopenharmony_ci		goto out_unwind_strings;
5258c2ecf20Sopenharmony_ci	}
5268c2ecf20Sopenharmony_ci	smbios_attribute_group.attrs = token_attrs;
5278c2ecf20Sopenharmony_ci
5288c2ecf20Sopenharmony_ci	ret = sysfs_create_group(&dev->dev.kobj, &smbios_attribute_group);
5298c2ecf20Sopenharmony_ci	if (ret)
5308c2ecf20Sopenharmony_ci		goto out_unwind_strings;
5318c2ecf20Sopenharmony_ci	return 0;
5328c2ecf20Sopenharmony_ci
5338c2ecf20Sopenharmony_ciout_unwind_strings:
5348c2ecf20Sopenharmony_ci	while (i--) {
5358c2ecf20Sopenharmony_ci		kfree(token_location_attrs[i].attr.name);
5368c2ecf20Sopenharmony_ci		kfree(token_value_attrs[i].attr.name);
5378c2ecf20Sopenharmony_ci	}
5388c2ecf20Sopenharmony_ci	kfree(token_attrs);
5398c2ecf20Sopenharmony_ciout_allocate_attrs:
5408c2ecf20Sopenharmony_ci	kfree(token_value_attrs);
5418c2ecf20Sopenharmony_ciout_allocate_value:
5428c2ecf20Sopenharmony_ci	kfree(token_location_attrs);
5438c2ecf20Sopenharmony_ci
5448c2ecf20Sopenharmony_ci	return -ENOMEM;
5458c2ecf20Sopenharmony_ci}
5468c2ecf20Sopenharmony_ci
5478c2ecf20Sopenharmony_cistatic void free_group(struct platform_device *pdev)
5488c2ecf20Sopenharmony_ci{
5498c2ecf20Sopenharmony_ci	int i;
5508c2ecf20Sopenharmony_ci
5518c2ecf20Sopenharmony_ci	sysfs_remove_group(&pdev->dev.kobj,
5528c2ecf20Sopenharmony_ci				&smbios_attribute_group);
5538c2ecf20Sopenharmony_ci	for (i = 0; i < da_num_tokens; i++) {
5548c2ecf20Sopenharmony_ci		kfree(token_location_attrs[i].attr.name);
5558c2ecf20Sopenharmony_ci		kfree(token_value_attrs[i].attr.name);
5568c2ecf20Sopenharmony_ci	}
5578c2ecf20Sopenharmony_ci	kfree(token_attrs);
5588c2ecf20Sopenharmony_ci	kfree(token_value_attrs);
5598c2ecf20Sopenharmony_ci	kfree(token_location_attrs);
5608c2ecf20Sopenharmony_ci}
5618c2ecf20Sopenharmony_ci
5628c2ecf20Sopenharmony_cistatic int __init dell_smbios_init(void)
5638c2ecf20Sopenharmony_ci{
5648c2ecf20Sopenharmony_ci	int ret, wmi, smm;
5658c2ecf20Sopenharmony_ci
5668c2ecf20Sopenharmony_ci	if (!dmi_find_device(DMI_DEV_TYPE_OEM_STRING, "Dell System", NULL) &&
5678c2ecf20Sopenharmony_ci	    !dmi_find_device(DMI_DEV_TYPE_OEM_STRING, "www.dell.com", NULL)) {
5688c2ecf20Sopenharmony_ci		pr_err("Unable to run on non-Dell system\n");
5698c2ecf20Sopenharmony_ci		return -ENODEV;
5708c2ecf20Sopenharmony_ci	}
5718c2ecf20Sopenharmony_ci
5728c2ecf20Sopenharmony_ci	dmi_walk(find_tokens, NULL);
5738c2ecf20Sopenharmony_ci
5748c2ecf20Sopenharmony_ci	ret = platform_driver_register(&platform_driver);
5758c2ecf20Sopenharmony_ci	if (ret)
5768c2ecf20Sopenharmony_ci		goto fail_platform_driver;
5778c2ecf20Sopenharmony_ci
5788c2ecf20Sopenharmony_ci	platform_device = platform_device_alloc("dell-smbios", 0);
5798c2ecf20Sopenharmony_ci	if (!platform_device) {
5808c2ecf20Sopenharmony_ci		ret = -ENOMEM;
5818c2ecf20Sopenharmony_ci		goto fail_platform_device_alloc;
5828c2ecf20Sopenharmony_ci	}
5838c2ecf20Sopenharmony_ci	ret = platform_device_add(platform_device);
5848c2ecf20Sopenharmony_ci	if (ret)
5858c2ecf20Sopenharmony_ci		goto fail_platform_device_add;
5868c2ecf20Sopenharmony_ci
5878c2ecf20Sopenharmony_ci	/* register backends */
5888c2ecf20Sopenharmony_ci	wmi = init_dell_smbios_wmi();
5898c2ecf20Sopenharmony_ci	if (wmi)
5908c2ecf20Sopenharmony_ci		pr_debug("Failed to initialize WMI backend: %d\n", wmi);
5918c2ecf20Sopenharmony_ci	smm = init_dell_smbios_smm();
5928c2ecf20Sopenharmony_ci	if (smm)
5938c2ecf20Sopenharmony_ci		pr_debug("Failed to initialize SMM backend: %d\n", smm);
5948c2ecf20Sopenharmony_ci	if (wmi && smm) {
5958c2ecf20Sopenharmony_ci		pr_err("No SMBIOS backends available (wmi: %d, smm: %d)\n",
5968c2ecf20Sopenharmony_ci			wmi, smm);
5978c2ecf20Sopenharmony_ci		ret = -ENODEV;
5988c2ecf20Sopenharmony_ci		goto fail_create_group;
5998c2ecf20Sopenharmony_ci	}
6008c2ecf20Sopenharmony_ci
6018c2ecf20Sopenharmony_ci	if (da_tokens)  {
6028c2ecf20Sopenharmony_ci		/* duplicate tokens will cause problems building sysfs files */
6038c2ecf20Sopenharmony_ci		zero_duplicates(&platform_device->dev);
6048c2ecf20Sopenharmony_ci
6058c2ecf20Sopenharmony_ci		ret = build_tokens_sysfs(platform_device);
6068c2ecf20Sopenharmony_ci		if (ret)
6078c2ecf20Sopenharmony_ci			goto fail_sysfs;
6088c2ecf20Sopenharmony_ci	}
6098c2ecf20Sopenharmony_ci
6108c2ecf20Sopenharmony_ci	return 0;
6118c2ecf20Sopenharmony_ci
6128c2ecf20Sopenharmony_cifail_sysfs:
6138c2ecf20Sopenharmony_ci	free_group(platform_device);
6148c2ecf20Sopenharmony_ci
6158c2ecf20Sopenharmony_cifail_create_group:
6168c2ecf20Sopenharmony_ci	platform_device_del(platform_device);
6178c2ecf20Sopenharmony_ci
6188c2ecf20Sopenharmony_cifail_platform_device_add:
6198c2ecf20Sopenharmony_ci	platform_device_put(platform_device);
6208c2ecf20Sopenharmony_ci
6218c2ecf20Sopenharmony_cifail_platform_device_alloc:
6228c2ecf20Sopenharmony_ci	platform_driver_unregister(&platform_driver);
6238c2ecf20Sopenharmony_ci
6248c2ecf20Sopenharmony_cifail_platform_driver:
6258c2ecf20Sopenharmony_ci	kfree(da_tokens);
6268c2ecf20Sopenharmony_ci	return ret;
6278c2ecf20Sopenharmony_ci}
6288c2ecf20Sopenharmony_ci
6298c2ecf20Sopenharmony_cistatic void __exit dell_smbios_exit(void)
6308c2ecf20Sopenharmony_ci{
6318c2ecf20Sopenharmony_ci	exit_dell_smbios_wmi();
6328c2ecf20Sopenharmony_ci	exit_dell_smbios_smm();
6338c2ecf20Sopenharmony_ci	mutex_lock(&smbios_mutex);
6348c2ecf20Sopenharmony_ci	if (platform_device) {
6358c2ecf20Sopenharmony_ci		if (da_tokens)
6368c2ecf20Sopenharmony_ci			free_group(platform_device);
6378c2ecf20Sopenharmony_ci		platform_device_unregister(platform_device);
6388c2ecf20Sopenharmony_ci		platform_driver_unregister(&platform_driver);
6398c2ecf20Sopenharmony_ci	}
6408c2ecf20Sopenharmony_ci	kfree(da_tokens);
6418c2ecf20Sopenharmony_ci	mutex_unlock(&smbios_mutex);
6428c2ecf20Sopenharmony_ci}
6438c2ecf20Sopenharmony_ci
6448c2ecf20Sopenharmony_cimodule_init(dell_smbios_init);
6458c2ecf20Sopenharmony_cimodule_exit(dell_smbios_exit);
6468c2ecf20Sopenharmony_ci
6478c2ecf20Sopenharmony_ciMODULE_AUTHOR("Matthew Garrett <mjg@redhat.com>");
6488c2ecf20Sopenharmony_ciMODULE_AUTHOR("Gabriele Mazzotta <gabriele.mzt@gmail.com>");
6498c2ecf20Sopenharmony_ciMODULE_AUTHOR("Pali Rohár <pali@kernel.org>");
6508c2ecf20Sopenharmony_ciMODULE_AUTHOR("Mario Limonciello <mario.limonciello@dell.com>");
6518c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Common functions for kernel modules using Dell SMBIOS");
6528c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL");
653