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/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 | |
22 | static DEFINE_MUTEX(call_mutex); |
23 | static DEFINE_MUTEX(list_mutex); |
24 | static int wmi_supported; |
25 | |
26 | struct misc_bios_flags_structure { |
27 | struct dmi_header ; |
28 | u16 flags0; |
29 | } __packed; |
30 | #define FLAG_HAS_ACPI_WMI 0x02 |
31 | |
32 | #define DELL_WMI_SMBIOS_GUID "A80593CE-A997-11DA-B012-B622A1EF5492" |
33 | |
34 | struct 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 | }; |
42 | static LIST_HEAD(wmi_list); |
43 | |
44 | static 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 | |
51 | static 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(dev: &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, instance: 0, method_id: 1, in: &input, out: &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(objp: 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(objp: output.pointer); |
85 | |
86 | return 0; |
87 | } |
88 | |
89 | static 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(wdev: priv->wdev); |
109 | memcpy(buffer, &priv->buf->std, size); |
110 | out_wmi_call: |
111 | mutex_unlock(lock: &call_mutex); |
112 | |
113 | return ret; |
114 | } |
115 | |
116 | static 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 | |
126 | static 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(to: buffer, count: length, ppos: offset, from: &priv->req_buf_size, |
132 | available: sizeof(priv->req_buf_size)); |
133 | } |
134 | |
135 | static 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(to: priv->buf, from: arg, n: priv->req_buf_size)) |
152 | return -EFAULT; |
153 | |
154 | if (dell_smbios_call_filter(d: &priv->wdev->dev, buffer: &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(wdev: priv->wdev); |
164 | if (ret) |
165 | return ret; |
166 | |
167 | if (copy_to_user(to: arg, from: priv->buf, n: priv->req_buf_size)) |
168 | return -EFAULT; |
169 | |
170 | return 0; |
171 | } |
172 | |
173 | static 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, arg: input); |
184 | mutex_unlock(lock: &call_mutex); |
185 | |
186 | return ret; |
187 | } |
188 | |
189 | static 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 | |
197 | static void dell_smbios_wmi_unregister_chardev(void *data) |
198 | { |
199 | struct miscdevice *char_dev = data; |
200 | |
201 | misc_deregister(misc: char_dev); |
202 | } |
203 | |
204 | static 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(misc: &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 | |
221 | static 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(dev: &wdev->dev, size: sizeof(struct wmi_smbios_priv), |
233 | GFP_KERNEL); |
234 | if (!priv) |
235 | return -ENOMEM; |
236 | |
237 | priv->wdev = wdev; |
238 | dev_set_drvdata(dev: &wdev->dev, data: priv); |
239 | |
240 | /* WMI buffer size will be either 4k or 32k depending on machine */ |
241 | if (!dell_wmi_get_size(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: &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(size: priv->req_buf_size); |
259 | priv->buf = (void *)devm_get_free_pages(dev: &wdev->dev, GFP_KERNEL, order: 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(d: &wdev->dev, call_fn: &dell_smbios_wmi_call); |
270 | if (ret) |
271 | return ret; |
272 | |
273 | mutex_lock(&list_mutex); |
274 | list_add_tail(new: &priv->list, head: &wmi_list); |
275 | mutex_unlock(lock: &list_mutex); |
276 | |
277 | return 0; |
278 | } |
279 | |
280 | static void dell_smbios_wmi_remove(struct wmi_device *wdev) |
281 | { |
282 | struct wmi_smbios_priv *priv = dev_get_drvdata(dev: &wdev->dev); |
283 | |
284 | mutex_lock(&call_mutex); |
285 | mutex_lock(&list_mutex); |
286 | list_del(entry: &priv->list); |
287 | mutex_unlock(lock: &list_mutex); |
288 | dell_smbios_unregister_device(d: &wdev->dev); |
289 | mutex_unlock(lock: &call_mutex); |
290 | } |
291 | |
292 | static const struct wmi_device_id dell_smbios_wmi_id_table[] = { |
293 | { .guid_string = DELL_WMI_SMBIOS_GUID }, |
294 | { }, |
295 | }; |
296 | |
297 | static 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 | |
311 | static 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 | |
320 | static 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 | |
329 | int init_dell_smbios_wmi(void) |
330 | { |
331 | dmi_walk(decode: find_b1, NULL); |
332 | |
333 | if (!wmi_supported) |
334 | return -ENODEV; |
335 | |
336 | return wmi_driver_register(&dell_smbios_wmi_driver); |
337 | } |
338 | |
339 | void exit_dell_smbios_wmi(void) |
340 | { |
341 | if (wmi_supported) |
342 | wmi_driver_unregister(driver: &dell_smbios_wmi_driver); |
343 | } |
344 | |
345 | MODULE_DEVICE_TABLE(wmi, dell_smbios_wmi_id_table); |
346 | |