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