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