2 * WMI methods for use with dell-smbios
4 * Copyright (c) 2017 Dell Inc.
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License version 2 as
8 * published by the Free Software Foundation.
10 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
12 #include <linux/dmi.h>
13 #include <linux/list.h>
14 #include <linux/module.h>
15 #include <linux/mutex.h>
16 #include <linux/uaccess.h>
17 #include <linux/wmi.h>
18 #include "dell-smbios.h"
19 #include "dell-wmi-descriptor.h"
21 static DEFINE_MUTEX(call_mutex
);
22 static DEFINE_MUTEX(list_mutex
);
23 static int wmi_supported
;
25 struct misc_bios_flags_structure
{
26 struct dmi_header header
;
29 #define FLAG_HAS_ACPI_WMI 0x02
31 #define DELL_WMI_SMBIOS_GUID "A80593CE-A997-11DA-B012-B622A1EF5492"
33 struct wmi_smbios_priv
{
34 struct dell_wmi_smbios_buffer
*buf
;
35 struct list_head list
;
36 struct wmi_device
*wdev
;
40 static LIST_HEAD(wmi_list
);
42 static inline struct wmi_smbios_priv
*get_first_smbios_priv(void)
44 return list_first_entry_or_null(&wmi_list
,
45 struct wmi_smbios_priv
,
49 static int run_smbios_call(struct wmi_device
*wdev
)
51 struct acpi_buffer output
= {ACPI_ALLOCATE_BUFFER
, NULL
};
52 struct wmi_smbios_priv
*priv
;
53 struct acpi_buffer input
;
54 union acpi_object
*obj
;
57 priv
= dev_get_drvdata(&wdev
->dev
);
58 input
.length
= priv
->req_buf_size
- sizeof(u64
);
59 input
.pointer
= &priv
->buf
->std
;
61 dev_dbg(&wdev
->dev
, "evaluating: %u/%u [%x,%x,%x,%x]\n",
62 priv
->buf
->std
.cmd_class
, priv
->buf
->std
.cmd_select
,
63 priv
->buf
->std
.input
[0], priv
->buf
->std
.input
[1],
64 priv
->buf
->std
.input
[2], priv
->buf
->std
.input
[3]);
66 status
= wmidev_evaluate_method(wdev
, 0, 1, &input
, &output
);
67 if (ACPI_FAILURE(status
))
69 obj
= (union acpi_object
*)output
.pointer
;
70 if (obj
->type
!= ACPI_TYPE_BUFFER
) {
71 dev_dbg(&wdev
->dev
, "received type: %d\n", obj
->type
);
72 if (obj
->type
== ACPI_TYPE_INTEGER
)
73 dev_dbg(&wdev
->dev
, "SMBIOS call failed: %llu\n",
77 memcpy(&priv
->buf
->std
, obj
->buffer
.pointer
, obj
->buffer
.length
);
78 dev_dbg(&wdev
->dev
, "result: [%08x,%08x,%08x,%08x]\n",
79 priv
->buf
->std
.output
[0], priv
->buf
->std
.output
[1],
80 priv
->buf
->std
.output
[2], priv
->buf
->std
.output
[3]);
81 kfree(output
.pointer
);
86 static int dell_smbios_wmi_call(struct calling_interface_buffer
*buffer
)
88 struct wmi_smbios_priv
*priv
;
93 mutex_lock(&call_mutex
);
94 priv
= get_first_smbios_priv();
100 size
= sizeof(struct calling_interface_buffer
);
101 difference
= priv
->req_buf_size
- sizeof(u64
) - size
;
103 memset(&priv
->buf
->ext
, 0, difference
);
104 memcpy(&priv
->buf
->std
, buffer
, size
);
105 ret
= run_smbios_call(priv
->wdev
);
106 memcpy(buffer
, &priv
->buf
->std
, size
);
108 mutex_unlock(&call_mutex
);
113 static long dell_smbios_wmi_filter(struct wmi_device
*wdev
, unsigned int cmd
,
114 struct wmi_ioctl_buffer
*arg
)
116 struct wmi_smbios_priv
*priv
;
120 case DELL_WMI_SMBIOS_CMD
:
121 mutex_lock(&call_mutex
);
122 priv
= dev_get_drvdata(&wdev
->dev
);
125 goto fail_smbios_cmd
;
127 memcpy(priv
->buf
, arg
, priv
->req_buf_size
);
128 if (dell_smbios_call_filter(&wdev
->dev
, &priv
->buf
->std
)) {
129 dev_err(&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]);
134 goto fail_smbios_cmd
;
136 ret
= run_smbios_call(priv
->wdev
);
138 goto fail_smbios_cmd
;
139 memcpy(arg
, priv
->buf
, priv
->req_buf_size
);
141 mutex_unlock(&call_mutex
);
149 static int dell_smbios_wmi_probe(struct wmi_device
*wdev
)
151 struct wmi_driver
*wdriver
=
152 container_of(wdev
->dev
.driver
, struct wmi_driver
, driver
);
153 struct wmi_smbios_priv
*priv
;
158 ret
= dell_wmi_get_descriptor_valid();
162 priv
= devm_kzalloc(&wdev
->dev
, sizeof(struct wmi_smbios_priv
),
167 /* WMI buffer size will be either 4k or 32k depending on machine */
168 if (!dell_wmi_get_size(&priv
->req_buf_size
))
169 return -EPROBE_DEFER
;
171 /* some SMBIOS calls fail unless BIOS contains hotfix */
172 if (!dell_wmi_get_hotfix(&hotfix
))
173 return -EPROBE_DEFER
;
176 "WMI SMBIOS userspace interface not supported(%u), try upgrading to a newer BIOS\n",
178 wdriver
->filter_callback
= NULL
;
181 /* add in the length object we will use internally with ioctl */
182 priv
->req_buf_size
+= sizeof(u64
);
183 ret
= set_required_buffer_size(wdev
, priv
->req_buf_size
);
187 count
= get_order(priv
->req_buf_size
);
188 priv
->buf
= (void *)__get_free_pages(GFP_KERNEL
, count
);
192 /* ID is used by dell-smbios to set priority of drivers */
194 ret
= dell_smbios_register_device(&wdev
->dev
, &dell_smbios_wmi_call
);
199 dev_set_drvdata(&wdev
->dev
, priv
);
200 mutex_lock(&list_mutex
);
201 list_add_tail(&priv
->list
, &wmi_list
);
202 mutex_unlock(&list_mutex
);
207 free_pages((unsigned long)priv
->buf
, count
);
211 static int dell_smbios_wmi_remove(struct wmi_device
*wdev
)
213 struct wmi_smbios_priv
*priv
= dev_get_drvdata(&wdev
->dev
);
216 mutex_lock(&call_mutex
);
217 mutex_lock(&list_mutex
);
218 list_del(&priv
->list
);
219 mutex_unlock(&list_mutex
);
220 dell_smbios_unregister_device(&wdev
->dev
);
221 count
= get_order(priv
->req_buf_size
);
222 free_pages((unsigned long)priv
->buf
, count
);
223 mutex_unlock(&call_mutex
);
227 static const struct wmi_device_id dell_smbios_wmi_id_table
[] = {
228 { .guid_string
= DELL_WMI_SMBIOS_GUID
},
232 static void parse_b1_table(const struct dmi_header
*dm
)
234 struct misc_bios_flags_structure
*flags
=
235 container_of(dm
, struct misc_bios_flags_structure
, header
);
237 /* 4 bytes header, 8 bytes flags */
240 if (dm
->handle
!= 0xb100)
242 if ((flags
->flags0
& FLAG_HAS_ACPI_WMI
))
246 static void find_b1(const struct dmi_header
*dm
, void *dummy
)
249 case 0xb1: /* misc bios flags */
255 static struct wmi_driver dell_smbios_wmi_driver
= {
257 .name
= "dell-smbios",
259 .probe
= dell_smbios_wmi_probe
,
260 .remove
= dell_smbios_wmi_remove
,
261 .id_table
= dell_smbios_wmi_id_table
,
262 .filter_callback
= dell_smbios_wmi_filter
,
265 int init_dell_smbios_wmi(void)
267 dmi_walk(find_b1
, NULL
);
272 return wmi_driver_register(&dell_smbios_wmi_driver
);
275 void exit_dell_smbios_wmi(void)
277 wmi_driver_unregister(&dell_smbios_wmi_driver
);
280 MODULE_ALIAS("wmi:" DELL_WMI_SMBIOS_GUID
);