brintos

brintos / linux-shallow public Read only

0
0
Text · 8.3 KiB · ae90125 Raw
346 lines · c
1// SPDX-License-Identifier: GPL-2.0-only2/*3 *  WMI methods for use with dell-smbios4 *5 *  Copyright (c) 2017 Dell Inc.6 */7#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt8 9#include <linux/device.h>10#include <linux/dmi.h>11#include <linux/fs.h>12#include <linux/list.h>13#include <linux/miscdevice.h>14#include <linux/module.h>15#include <linux/mutex.h>16#include <linux/uaccess.h>17#include <linux/wmi.h>18#include <uapi/linux/wmi.h>19#include "dell-smbios.h"20#include "dell-wmi-descriptor.h"21 22static DEFINE_MUTEX(call_mutex);23static DEFINE_MUTEX(list_mutex);24static int wmi_supported;25 26struct misc_bios_flags_structure {27	struct dmi_header header;28	u16 flags0;29} __packed;30#define FLAG_HAS_ACPI_WMI 0x0231 32#define DELL_WMI_SMBIOS_GUID "A80593CE-A997-11DA-B012-B622A1EF5492"33 34struct wmi_smbios_priv {35	struct dell_wmi_smbios_buffer *buf;36	struct list_head list;37	struct wmi_device *wdev;38	struct device *child;39	u64 req_buf_size;40	struct miscdevice char_dev;41};42static LIST_HEAD(wmi_list);43 44static inline struct wmi_smbios_priv *get_first_smbios_priv(void)45{46	return list_first_entry_or_null(&wmi_list,47					struct wmi_smbios_priv,48					list);49}50 51static int run_smbios_call(struct wmi_device *wdev)52{53	struct acpi_buffer output = {ACPI_ALLOCATE_BUFFER, NULL};54	struct wmi_smbios_priv *priv;55	struct acpi_buffer input;56	union acpi_object *obj;57	acpi_status status;58 59	priv = dev_get_drvdata(&wdev->dev);60	input.length = priv->req_buf_size - sizeof(u64);61	input.pointer = &priv->buf->std;62 63	dev_dbg(&wdev->dev, "evaluating: %u/%u [%x,%x,%x,%x]\n",64		priv->buf->std.cmd_class, priv->buf->std.cmd_select,65		priv->buf->std.input[0], priv->buf->std.input[1],66		priv->buf->std.input[2], priv->buf->std.input[3]);67 68	status = wmidev_evaluate_method(wdev, 0, 1, &input, &output);69	if (ACPI_FAILURE(status))70		return -EIO;71	obj = (union acpi_object *)output.pointer;72	if (obj->type != ACPI_TYPE_BUFFER) {73		dev_dbg(&wdev->dev, "received type: %d\n", obj->type);74		if (obj->type == ACPI_TYPE_INTEGER)75			dev_dbg(&wdev->dev, "SMBIOS call failed: %llu\n",76				obj->integer.value);77		kfree(output.pointer);78		return -EIO;79	}80	memcpy(input.pointer, obj->buffer.pointer, obj->buffer.length);81	dev_dbg(&wdev->dev, "result: [%08x,%08x,%08x,%08x]\n",82		priv->buf->std.output[0], priv->buf->std.output[1],83		priv->buf->std.output[2], priv->buf->std.output[3]);84	kfree(output.pointer);85 86	return 0;87}88 89static int dell_smbios_wmi_call(struct calling_interface_buffer *buffer)90{91	struct wmi_smbios_priv *priv;92	size_t difference;93	size_t size;94	int ret;95 96	mutex_lock(&call_mutex);97	priv = get_first_smbios_priv();98	if (!priv) {99		ret = -ENODEV;100		goto out_wmi_call;101	}102 103	size = sizeof(struct calling_interface_buffer);104	difference = priv->req_buf_size - sizeof(u64) - size;105 106	memset(&priv->buf->ext, 0, difference);107	memcpy(&priv->buf->std, buffer, size);108	ret = run_smbios_call(priv->wdev);109	memcpy(buffer, &priv->buf->std, size);110out_wmi_call:111	mutex_unlock(&call_mutex);112 113	return ret;114}115 116static int dell_smbios_wmi_open(struct inode *inode, struct file *filp)117{118	struct wmi_smbios_priv *priv;119 120	priv = container_of(filp->private_data, struct wmi_smbios_priv, char_dev);121	filp->private_data = priv;122 123	return nonseekable_open(inode, filp);124}125 126static ssize_t dell_smbios_wmi_read(struct file *filp, char __user *buffer, size_t length,127				    loff_t *offset)128{129	struct wmi_smbios_priv *priv = filp->private_data;130 131	return simple_read_from_buffer(buffer, length, offset, &priv->req_buf_size,132				       sizeof(priv->req_buf_size));133}134 135static long dell_smbios_wmi_do_ioctl(struct wmi_smbios_priv *priv,136				     struct dell_wmi_smbios_buffer __user *arg)137{138	long ret;139 140	if (get_user(priv->buf->length, &arg->length))141		return -EFAULT;142 143	if (priv->buf->length < priv->req_buf_size)144		return -EINVAL;145 146	/* if it's too big, warn, driver will only use what is needed */147	if (priv->buf->length > priv->req_buf_size)148		dev_err(&priv->wdev->dev, "Buffer %llu is bigger than required %llu\n",149			priv->buf->length, priv->req_buf_size);150 151	if (copy_from_user(priv->buf, arg, priv->req_buf_size))152		return -EFAULT;153 154	if (dell_smbios_call_filter(&priv->wdev->dev, &priv->buf->std)) {155		dev_err(&priv->wdev->dev, "Invalid call %d/%d:%8x\n",156			priv->buf->std.cmd_class,157			priv->buf->std.cmd_select,158			priv->buf->std.input[0]);159 160		return -EINVAL;161	}162 163	ret = run_smbios_call(priv->wdev);164	if (ret)165		return ret;166 167	if (copy_to_user(arg, priv->buf, priv->req_buf_size))168		return -EFAULT;169 170	return 0;171}172 173static long dell_smbios_wmi_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)174{175	struct dell_wmi_smbios_buffer __user *input = (struct dell_wmi_smbios_buffer __user *)arg;176	struct wmi_smbios_priv *priv = filp->private_data;177	long ret;178 179	if (cmd != DELL_WMI_SMBIOS_CMD)180		return -ENOIOCTLCMD;181 182	mutex_lock(&call_mutex);183	ret = dell_smbios_wmi_do_ioctl(priv, input);184	mutex_unlock(&call_mutex);185 186	return ret;187}188 189static const struct file_operations dell_smbios_wmi_fops = {190	.owner		= THIS_MODULE,191	.open		= dell_smbios_wmi_open,192	.read		= dell_smbios_wmi_read,193	.unlocked_ioctl	= dell_smbios_wmi_ioctl,194	.compat_ioctl	= compat_ptr_ioctl,195};196 197static void dell_smbios_wmi_unregister_chardev(void *data)198{199	struct miscdevice *char_dev = data;200 201	misc_deregister(char_dev);202}203 204static int dell_smbios_wmi_register_chardev(struct wmi_smbios_priv *priv)205{206	int ret;207 208	priv->char_dev.minor = MISC_DYNAMIC_MINOR;209	priv->char_dev.name = "wmi/dell-smbios";210	priv->char_dev.fops = &dell_smbios_wmi_fops;211	priv->char_dev.mode = 0444;212 213	ret = misc_register(&priv->char_dev);214	if (ret < 0)215		return ret;216 217	return devm_add_action_or_reset(&priv->wdev->dev, dell_smbios_wmi_unregister_chardev,218					&priv->char_dev);219}220 221static int dell_smbios_wmi_probe(struct wmi_device *wdev, const void *context)222{223	struct wmi_smbios_priv *priv;224	u32 buffer_size, hotfix;225	int count;226	int ret;227 228	ret = dell_wmi_get_descriptor_valid();229	if (ret)230		return ret;231 232	priv = devm_kzalloc(&wdev->dev, sizeof(struct wmi_smbios_priv),233			    GFP_KERNEL);234	if (!priv)235		return -ENOMEM;236 237	priv->wdev = wdev;238	dev_set_drvdata(&wdev->dev, priv);239 240	/* WMI buffer size will be either 4k or 32k depending on machine */241	if (!dell_wmi_get_size(&buffer_size))242		return -EPROBE_DEFER;243 244	priv->req_buf_size = buffer_size;245 246	/* some SMBIOS calls fail unless BIOS contains hotfix */247	if (!dell_wmi_get_hotfix(&hotfix))248		return -EPROBE_DEFER;249 250	if (!hotfix)251		dev_warn(&wdev->dev,252			"WMI SMBIOS userspace interface not supported(%u), try upgrading to a newer BIOS\n",253			hotfix);254 255	/* add in the length object we will use internally with ioctl */256	priv->req_buf_size += sizeof(u64);257 258	count = get_order(priv->req_buf_size);259	priv->buf = (void *)devm_get_free_pages(&wdev->dev, GFP_KERNEL, count);260	if (!priv->buf)261		return -ENOMEM;262 263	ret = dell_smbios_wmi_register_chardev(priv);264	if (ret)265		return ret;266 267	/* ID is used by dell-smbios to set priority of drivers */268	wdev->dev.id = 1;269	ret = dell_smbios_register_device(&wdev->dev, &dell_smbios_wmi_call);270	if (ret)271		return ret;272 273	mutex_lock(&list_mutex);274	list_add_tail(&priv->list, &wmi_list);275	mutex_unlock(&list_mutex);276 277	return 0;278}279 280static void dell_smbios_wmi_remove(struct wmi_device *wdev)281{282	struct wmi_smbios_priv *priv = dev_get_drvdata(&wdev->dev);283 284	mutex_lock(&call_mutex);285	mutex_lock(&list_mutex);286	list_del(&priv->list);287	mutex_unlock(&list_mutex);288	dell_smbios_unregister_device(&wdev->dev);289	mutex_unlock(&call_mutex);290}291 292static const struct wmi_device_id dell_smbios_wmi_id_table[] = {293	{ .guid_string = DELL_WMI_SMBIOS_GUID },294	{ },295};296 297static void parse_b1_table(const struct dmi_header *dm)298{299	struct misc_bios_flags_structure *flags =300	container_of(dm, struct misc_bios_flags_structure, header);301 302	/* 4 bytes header, 8 bytes flags */303	if (dm->length < 12)304		return;305	if (dm->handle != 0xb100)306		return;307	if ((flags->flags0 & FLAG_HAS_ACPI_WMI))308		wmi_supported = 1;309}310 311static void find_b1(const struct dmi_header *dm, void *dummy)312{313	switch (dm->type) {314	case 0xb1: /* misc bios flags */315		parse_b1_table(dm);316		break;317	}318}319 320static struct wmi_driver dell_smbios_wmi_driver = {321	.driver = {322		.name = "dell-smbios",323	},324	.probe = dell_smbios_wmi_probe,325	.remove = dell_smbios_wmi_remove,326	.id_table = dell_smbios_wmi_id_table,327};328 329int init_dell_smbios_wmi(void)330{331	dmi_walk(find_b1, NULL);332 333	if (!wmi_supported)334		return -ENODEV;335 336	return wmi_driver_register(&dell_smbios_wmi_driver);337}338 339void exit_dell_smbios_wmi(void)340{341	if (wmi_supported)342		wmi_driver_unregister(&dell_smbios_wmi_driver);343}344 345MODULE_DEVICE_TABLE(wmi, dell_smbios_wmi_id_table);346