xref: /openbmc/linux/drivers/platform/x86/dell/dell-smbios-wmi.c (revision 762f99f4f3cb41a775b5157dd761217beba65873)
1f1e1ea51SMario Limonciello // SPDX-License-Identifier: GPL-2.0-only
2f1e1ea51SMario Limonciello /*
3f1e1ea51SMario Limonciello  *  WMI methods for use with dell-smbios
4f1e1ea51SMario Limonciello  *
5f1e1ea51SMario Limonciello  *  Copyright (c) 2017 Dell Inc.
6f1e1ea51SMario Limonciello  */
7f1e1ea51SMario Limonciello #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
8f1e1ea51SMario Limonciello 
9f1e1ea51SMario Limonciello #include <linux/dmi.h>
10f1e1ea51SMario Limonciello #include <linux/list.h>
11f1e1ea51SMario Limonciello #include <linux/module.h>
12f1e1ea51SMario Limonciello #include <linux/mutex.h>
13f1e1ea51SMario Limonciello #include <linux/uaccess.h>
14f1e1ea51SMario Limonciello #include <linux/wmi.h>
15f1e1ea51SMario Limonciello #include "dell-smbios.h"
16f1e1ea51SMario Limonciello #include "dell-wmi-descriptor.h"
17f1e1ea51SMario Limonciello 
18f1e1ea51SMario Limonciello static DEFINE_MUTEX(call_mutex);
19f1e1ea51SMario Limonciello static DEFINE_MUTEX(list_mutex);
20f1e1ea51SMario Limonciello static int wmi_supported;
21f1e1ea51SMario Limonciello 
22f1e1ea51SMario Limonciello struct misc_bios_flags_structure {
23f1e1ea51SMario Limonciello 	struct dmi_header header;
24f1e1ea51SMario Limonciello 	u16 flags0;
25f1e1ea51SMario Limonciello } __packed;
26f1e1ea51SMario Limonciello #define FLAG_HAS_ACPI_WMI 0x02
27f1e1ea51SMario Limonciello 
28f1e1ea51SMario Limonciello #define DELL_WMI_SMBIOS_GUID "A80593CE-A997-11DA-B012-B622A1EF5492"
29f1e1ea51SMario Limonciello 
30f1e1ea51SMario Limonciello struct wmi_smbios_priv {
31f1e1ea51SMario Limonciello 	struct dell_wmi_smbios_buffer *buf;
32f1e1ea51SMario Limonciello 	struct list_head list;
33f1e1ea51SMario Limonciello 	struct wmi_device *wdev;
34f1e1ea51SMario Limonciello 	struct device *child;
35f1e1ea51SMario Limonciello 	u32 req_buf_size;
36f1e1ea51SMario Limonciello };
37f1e1ea51SMario Limonciello static LIST_HEAD(wmi_list);
38f1e1ea51SMario Limonciello 
get_first_smbios_priv(void)39f1e1ea51SMario Limonciello static inline struct wmi_smbios_priv *get_first_smbios_priv(void)
40f1e1ea51SMario Limonciello {
41f1e1ea51SMario Limonciello 	return list_first_entry_or_null(&wmi_list,
42f1e1ea51SMario Limonciello 					struct wmi_smbios_priv,
43f1e1ea51SMario Limonciello 					list);
44f1e1ea51SMario Limonciello }
45f1e1ea51SMario Limonciello 
run_smbios_call(struct wmi_device * wdev)46f1e1ea51SMario Limonciello static int run_smbios_call(struct wmi_device *wdev)
47f1e1ea51SMario Limonciello {
48f1e1ea51SMario Limonciello 	struct acpi_buffer output = {ACPI_ALLOCATE_BUFFER, NULL};
49f1e1ea51SMario Limonciello 	struct wmi_smbios_priv *priv;
50f1e1ea51SMario Limonciello 	struct acpi_buffer input;
51f1e1ea51SMario Limonciello 	union acpi_object *obj;
52f1e1ea51SMario Limonciello 	acpi_status status;
53f1e1ea51SMario Limonciello 
54f1e1ea51SMario Limonciello 	priv = dev_get_drvdata(&wdev->dev);
55f1e1ea51SMario Limonciello 	input.length = priv->req_buf_size - sizeof(u64);
56f1e1ea51SMario Limonciello 	input.pointer = &priv->buf->std;
57f1e1ea51SMario Limonciello 
58f1e1ea51SMario Limonciello 	dev_dbg(&wdev->dev, "evaluating: %u/%u [%x,%x,%x,%x]\n",
59f1e1ea51SMario Limonciello 		priv->buf->std.cmd_class, priv->buf->std.cmd_select,
60f1e1ea51SMario Limonciello 		priv->buf->std.input[0], priv->buf->std.input[1],
61f1e1ea51SMario Limonciello 		priv->buf->std.input[2], priv->buf->std.input[3]);
62f1e1ea51SMario Limonciello 
63f1e1ea51SMario Limonciello 	status = wmidev_evaluate_method(wdev, 0, 1, &input, &output);
64f1e1ea51SMario Limonciello 	if (ACPI_FAILURE(status))
65f1e1ea51SMario Limonciello 		return -EIO;
66f1e1ea51SMario Limonciello 	obj = (union acpi_object *)output.pointer;
67f1e1ea51SMario Limonciello 	if (obj->type != ACPI_TYPE_BUFFER) {
68f1e1ea51SMario Limonciello 		dev_dbg(&wdev->dev, "received type: %d\n", obj->type);
69f1e1ea51SMario Limonciello 		if (obj->type == ACPI_TYPE_INTEGER)
70f1e1ea51SMario Limonciello 			dev_dbg(&wdev->dev, "SMBIOS call failed: %llu\n",
71f1e1ea51SMario Limonciello 				obj->integer.value);
72*0487d4fcSHans de Goede 		kfree(output.pointer);
73f1e1ea51SMario Limonciello 		return -EIO;
74f1e1ea51SMario Limonciello 	}
75fb49d994SKees Cook 	memcpy(input.pointer, obj->buffer.pointer, obj->buffer.length);
76f1e1ea51SMario Limonciello 	dev_dbg(&wdev->dev, "result: [%08x,%08x,%08x,%08x]\n",
77f1e1ea51SMario Limonciello 		priv->buf->std.output[0], priv->buf->std.output[1],
78f1e1ea51SMario Limonciello 		priv->buf->std.output[2], priv->buf->std.output[3]);
79f1e1ea51SMario Limonciello 	kfree(output.pointer);
80f1e1ea51SMario Limonciello 
81f1e1ea51SMario Limonciello 	return 0;
82f1e1ea51SMario Limonciello }
83f1e1ea51SMario Limonciello 
dell_smbios_wmi_call(struct calling_interface_buffer * buffer)84f1e1ea51SMario Limonciello static int dell_smbios_wmi_call(struct calling_interface_buffer *buffer)
85f1e1ea51SMario Limonciello {
86f1e1ea51SMario Limonciello 	struct wmi_smbios_priv *priv;
87f1e1ea51SMario Limonciello 	size_t difference;
88f1e1ea51SMario Limonciello 	size_t size;
89f1e1ea51SMario Limonciello 	int ret;
90f1e1ea51SMario Limonciello 
91f1e1ea51SMario Limonciello 	mutex_lock(&call_mutex);
92f1e1ea51SMario Limonciello 	priv = get_first_smbios_priv();
93f1e1ea51SMario Limonciello 	if (!priv) {
94f1e1ea51SMario Limonciello 		ret = -ENODEV;
95f1e1ea51SMario Limonciello 		goto out_wmi_call;
96f1e1ea51SMario Limonciello 	}
97f1e1ea51SMario Limonciello 
98f1e1ea51SMario Limonciello 	size = sizeof(struct calling_interface_buffer);
99f1e1ea51SMario Limonciello 	difference = priv->req_buf_size - sizeof(u64) - size;
100f1e1ea51SMario Limonciello 
101f1e1ea51SMario Limonciello 	memset(&priv->buf->ext, 0, difference);
102f1e1ea51SMario Limonciello 	memcpy(&priv->buf->std, buffer, size);
103f1e1ea51SMario Limonciello 	ret = run_smbios_call(priv->wdev);
104f1e1ea51SMario Limonciello 	memcpy(buffer, &priv->buf->std, size);
105f1e1ea51SMario Limonciello out_wmi_call:
106f1e1ea51SMario Limonciello 	mutex_unlock(&call_mutex);
107f1e1ea51SMario Limonciello 
108f1e1ea51SMario Limonciello 	return ret;
109f1e1ea51SMario Limonciello }
110f1e1ea51SMario Limonciello 
dell_smbios_wmi_filter(struct wmi_device * wdev,unsigned int cmd,struct wmi_ioctl_buffer * arg)111f1e1ea51SMario Limonciello static long dell_smbios_wmi_filter(struct wmi_device *wdev, unsigned int cmd,
112f1e1ea51SMario Limonciello 				   struct wmi_ioctl_buffer *arg)
113f1e1ea51SMario Limonciello {
114f1e1ea51SMario Limonciello 	struct wmi_smbios_priv *priv;
115f1e1ea51SMario Limonciello 	int ret = 0;
116f1e1ea51SMario Limonciello 
117f1e1ea51SMario Limonciello 	switch (cmd) {
118f1e1ea51SMario Limonciello 	case DELL_WMI_SMBIOS_CMD:
119f1e1ea51SMario Limonciello 		mutex_lock(&call_mutex);
120f1e1ea51SMario Limonciello 		priv = dev_get_drvdata(&wdev->dev);
121f1e1ea51SMario Limonciello 		if (!priv) {
122f1e1ea51SMario Limonciello 			ret = -ENODEV;
123f1e1ea51SMario Limonciello 			goto fail_smbios_cmd;
124f1e1ea51SMario Limonciello 		}
125f1e1ea51SMario Limonciello 		memcpy(priv->buf, arg, priv->req_buf_size);
126f1e1ea51SMario Limonciello 		if (dell_smbios_call_filter(&wdev->dev, &priv->buf->std)) {
127f1e1ea51SMario Limonciello 			dev_err(&wdev->dev, "Invalid call %d/%d:%8x\n",
128f1e1ea51SMario Limonciello 				priv->buf->std.cmd_class,
129f1e1ea51SMario Limonciello 				priv->buf->std.cmd_select,
130f1e1ea51SMario Limonciello 				priv->buf->std.input[0]);
131f1e1ea51SMario Limonciello 			ret = -EFAULT;
132f1e1ea51SMario Limonciello 			goto fail_smbios_cmd;
133f1e1ea51SMario Limonciello 		}
134f1e1ea51SMario Limonciello 		ret = run_smbios_call(priv->wdev);
135f1e1ea51SMario Limonciello 		if (ret)
136f1e1ea51SMario Limonciello 			goto fail_smbios_cmd;
137f1e1ea51SMario Limonciello 		memcpy(arg, priv->buf, priv->req_buf_size);
138f1e1ea51SMario Limonciello fail_smbios_cmd:
139f1e1ea51SMario Limonciello 		mutex_unlock(&call_mutex);
140f1e1ea51SMario Limonciello 		break;
141f1e1ea51SMario Limonciello 	default:
142f1e1ea51SMario Limonciello 		ret = -ENOIOCTLCMD;
143f1e1ea51SMario Limonciello 	}
144f1e1ea51SMario Limonciello 	return ret;
145f1e1ea51SMario Limonciello }
146f1e1ea51SMario Limonciello 
dell_smbios_wmi_probe(struct wmi_device * wdev,const void * context)147f1e1ea51SMario Limonciello static int dell_smbios_wmi_probe(struct wmi_device *wdev, const void *context)
148f1e1ea51SMario Limonciello {
149f1e1ea51SMario Limonciello 	struct wmi_driver *wdriver =
150f1e1ea51SMario Limonciello 		container_of(wdev->dev.driver, struct wmi_driver, driver);
151f1e1ea51SMario Limonciello 	struct wmi_smbios_priv *priv;
152f1e1ea51SMario Limonciello 	u32 hotfix;
153f1e1ea51SMario Limonciello 	int count;
154f1e1ea51SMario Limonciello 	int ret;
155f1e1ea51SMario Limonciello 
156f1e1ea51SMario Limonciello 	ret = dell_wmi_get_descriptor_valid();
157f1e1ea51SMario Limonciello 	if (ret)
158f1e1ea51SMario Limonciello 		return ret;
159f1e1ea51SMario Limonciello 
160f1e1ea51SMario Limonciello 	priv = devm_kzalloc(&wdev->dev, sizeof(struct wmi_smbios_priv),
161f1e1ea51SMario Limonciello 			    GFP_KERNEL);
162f1e1ea51SMario Limonciello 	if (!priv)
163f1e1ea51SMario Limonciello 		return -ENOMEM;
164f1e1ea51SMario Limonciello 
165f1e1ea51SMario Limonciello 	/* WMI buffer size will be either 4k or 32k depending on machine */
166f1e1ea51SMario Limonciello 	if (!dell_wmi_get_size(&priv->req_buf_size))
167f1e1ea51SMario Limonciello 		return -EPROBE_DEFER;
168f1e1ea51SMario Limonciello 
169f1e1ea51SMario Limonciello 	/* some SMBIOS calls fail unless BIOS contains hotfix */
170f1e1ea51SMario Limonciello 	if (!dell_wmi_get_hotfix(&hotfix))
171f1e1ea51SMario Limonciello 		return -EPROBE_DEFER;
172f1e1ea51SMario Limonciello 	if (!hotfix) {
173f1e1ea51SMario Limonciello 		dev_warn(&wdev->dev,
174f1e1ea51SMario Limonciello 			"WMI SMBIOS userspace interface not supported(%u), try upgrading to a newer BIOS\n",
175f1e1ea51SMario Limonciello 			hotfix);
176f1e1ea51SMario Limonciello 		wdriver->filter_callback = NULL;
177f1e1ea51SMario Limonciello 	}
178f1e1ea51SMario Limonciello 
179f1e1ea51SMario Limonciello 	/* add in the length object we will use internally with ioctl */
180f1e1ea51SMario Limonciello 	priv->req_buf_size += sizeof(u64);
181f1e1ea51SMario Limonciello 	ret = set_required_buffer_size(wdev, priv->req_buf_size);
182f1e1ea51SMario Limonciello 	if (ret)
183f1e1ea51SMario Limonciello 		return ret;
184f1e1ea51SMario Limonciello 
185f1e1ea51SMario Limonciello 	count = get_order(priv->req_buf_size);
186f1e1ea51SMario Limonciello 	priv->buf = (void *)__get_free_pages(GFP_KERNEL, count);
187f1e1ea51SMario Limonciello 	if (!priv->buf)
188f1e1ea51SMario Limonciello 		return -ENOMEM;
189f1e1ea51SMario Limonciello 
190f1e1ea51SMario Limonciello 	/* ID is used by dell-smbios to set priority of drivers */
191f1e1ea51SMario Limonciello 	wdev->dev.id = 1;
192f1e1ea51SMario Limonciello 	ret = dell_smbios_register_device(&wdev->dev, &dell_smbios_wmi_call);
193f1e1ea51SMario Limonciello 	if (ret)
194f1e1ea51SMario Limonciello 		goto fail_register;
195f1e1ea51SMario Limonciello 
196f1e1ea51SMario Limonciello 	priv->wdev = wdev;
197f1e1ea51SMario Limonciello 	dev_set_drvdata(&wdev->dev, priv);
198f1e1ea51SMario Limonciello 	mutex_lock(&list_mutex);
199f1e1ea51SMario Limonciello 	list_add_tail(&priv->list, &wmi_list);
200f1e1ea51SMario Limonciello 	mutex_unlock(&list_mutex);
201f1e1ea51SMario Limonciello 
202f1e1ea51SMario Limonciello 	return 0;
203f1e1ea51SMario Limonciello 
204f1e1ea51SMario Limonciello fail_register:
205f1e1ea51SMario Limonciello 	free_pages((unsigned long)priv->buf, count);
206f1e1ea51SMario Limonciello 	return ret;
207f1e1ea51SMario Limonciello }
208f1e1ea51SMario Limonciello 
dell_smbios_wmi_remove(struct wmi_device * wdev)2092b329f56SUwe Kleine-König static void dell_smbios_wmi_remove(struct wmi_device *wdev)
210f1e1ea51SMario Limonciello {
211f1e1ea51SMario Limonciello 	struct wmi_smbios_priv *priv = dev_get_drvdata(&wdev->dev);
212f1e1ea51SMario Limonciello 	int count;
213f1e1ea51SMario Limonciello 
214f1e1ea51SMario Limonciello 	mutex_lock(&call_mutex);
215f1e1ea51SMario Limonciello 	mutex_lock(&list_mutex);
216f1e1ea51SMario Limonciello 	list_del(&priv->list);
217f1e1ea51SMario Limonciello 	mutex_unlock(&list_mutex);
218f1e1ea51SMario Limonciello 	dell_smbios_unregister_device(&wdev->dev);
219f1e1ea51SMario Limonciello 	count = get_order(priv->req_buf_size);
220f1e1ea51SMario Limonciello 	free_pages((unsigned long)priv->buf, count);
221f1e1ea51SMario Limonciello 	mutex_unlock(&call_mutex);
222f1e1ea51SMario Limonciello }
223f1e1ea51SMario Limonciello 
224f1e1ea51SMario Limonciello static const struct wmi_device_id dell_smbios_wmi_id_table[] = {
225f1e1ea51SMario Limonciello 	{ .guid_string = DELL_WMI_SMBIOS_GUID },
226f1e1ea51SMario Limonciello 	{ },
227f1e1ea51SMario Limonciello };
228f1e1ea51SMario Limonciello 
parse_b1_table(const struct dmi_header * dm)229f1e1ea51SMario Limonciello static void parse_b1_table(const struct dmi_header *dm)
230f1e1ea51SMario Limonciello {
231f1e1ea51SMario Limonciello 	struct misc_bios_flags_structure *flags =
232f1e1ea51SMario Limonciello 	container_of(dm, struct misc_bios_flags_structure, header);
233f1e1ea51SMario Limonciello 
234f1e1ea51SMario Limonciello 	/* 4 bytes header, 8 bytes flags */
235f1e1ea51SMario Limonciello 	if (dm->length < 12)
236f1e1ea51SMario Limonciello 		return;
237f1e1ea51SMario Limonciello 	if (dm->handle != 0xb100)
238f1e1ea51SMario Limonciello 		return;
239f1e1ea51SMario Limonciello 	if ((flags->flags0 & FLAG_HAS_ACPI_WMI))
240f1e1ea51SMario Limonciello 		wmi_supported = 1;
241f1e1ea51SMario Limonciello }
242f1e1ea51SMario Limonciello 
find_b1(const struct dmi_header * dm,void * dummy)243f1e1ea51SMario Limonciello static void find_b1(const struct dmi_header *dm, void *dummy)
244f1e1ea51SMario Limonciello {
245f1e1ea51SMario Limonciello 	switch (dm->type) {
246f1e1ea51SMario Limonciello 	case 0xb1: /* misc bios flags */
247f1e1ea51SMario Limonciello 		parse_b1_table(dm);
248f1e1ea51SMario Limonciello 		break;
249f1e1ea51SMario Limonciello 	}
250f1e1ea51SMario Limonciello }
251f1e1ea51SMario Limonciello 
252f1e1ea51SMario Limonciello static struct wmi_driver dell_smbios_wmi_driver = {
253f1e1ea51SMario Limonciello 	.driver = {
254f1e1ea51SMario Limonciello 		.name = "dell-smbios",
255f1e1ea51SMario Limonciello 	},
256f1e1ea51SMario Limonciello 	.probe = dell_smbios_wmi_probe,
257f1e1ea51SMario Limonciello 	.remove = dell_smbios_wmi_remove,
258f1e1ea51SMario Limonciello 	.id_table = dell_smbios_wmi_id_table,
259f1e1ea51SMario Limonciello 	.filter_callback = dell_smbios_wmi_filter,
260f1e1ea51SMario Limonciello };
261f1e1ea51SMario Limonciello 
init_dell_smbios_wmi(void)262f1e1ea51SMario Limonciello int init_dell_smbios_wmi(void)
263f1e1ea51SMario Limonciello {
264f1e1ea51SMario Limonciello 	dmi_walk(find_b1, NULL);
265f1e1ea51SMario Limonciello 
266f1e1ea51SMario Limonciello 	if (!wmi_supported)
267f1e1ea51SMario Limonciello 		return -ENODEV;
268f1e1ea51SMario Limonciello 
269f1e1ea51SMario Limonciello 	return wmi_driver_register(&dell_smbios_wmi_driver);
270f1e1ea51SMario Limonciello }
271f1e1ea51SMario Limonciello 
exit_dell_smbios_wmi(void)272f1e1ea51SMario Limonciello void exit_dell_smbios_wmi(void)
273f1e1ea51SMario Limonciello {
2743a535874SHans de Goede 	if (wmi_supported)
275f1e1ea51SMario Limonciello 		wmi_driver_unregister(&dell_smbios_wmi_driver);
276f1e1ea51SMario Limonciello }
277f1e1ea51SMario Limonciello 
278f1e1ea51SMario Limonciello MODULE_DEVICE_TABLE(wmi, dell_smbios_wmi_id_table);
279