]> Git Repo - linux.git/blob - drivers/platform/x86/msi-wmi-platform.c
i2c: Fix conditional for substituting empty ACPI functions
[linux.git] / drivers / platform / x86 / msi-wmi-platform.c
1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3  * Linux driver for WMI platform features on MSI notebooks.
4  *
5  * Copyright (C) 2024 Armin Wolf <[email protected]>
6  */
7
8 #define pr_format(fmt) KBUILD_MODNAME ": " fmt
9
10 #include <linux/acpi.h>
11 #include <linux/bits.h>
12 #include <linux/bitfield.h>
13 #include <linux/debugfs.h>
14 #include <linux/device.h>
15 #include <linux/device/driver.h>
16 #include <linux/errno.h>
17 #include <linux/hwmon.h>
18 #include <linux/kernel.h>
19 #include <linux/module.h>
20 #include <linux/printk.h>
21 #include <linux/rwsem.h>
22 #include <linux/types.h>
23 #include <linux/wmi.h>
24
25 #include <asm/unaligned.h>
26
27 #define DRIVER_NAME     "msi-wmi-platform"
28
29 #define MSI_PLATFORM_GUID       "ABBC0F6E-8EA1-11d1-00A0-C90629100000"
30
31 #define MSI_WMI_PLATFORM_INTERFACE_VERSION      2
32
33 #define MSI_PLATFORM_WMI_MAJOR_OFFSET   1
34 #define MSI_PLATFORM_WMI_MINOR_OFFSET   2
35
36 #define MSI_PLATFORM_EC_FLAGS_OFFSET    1
37 #define MSI_PLATFORM_EC_MINOR_MASK      GENMASK(3, 0)
38 #define MSI_PLATFORM_EC_MAJOR_MASK      GENMASK(5, 4)
39 #define MSI_PLATFORM_EC_CHANGED_PAGE    BIT(6)
40 #define MSI_PLATFORM_EC_IS_TIGERLAKE    BIT(7)
41 #define MSI_PLATFORM_EC_VERSION_OFFSET  2
42
43 static bool force;
44 module_param_unsafe(force, bool, 0);
45 MODULE_PARM_DESC(force, "Force loading without checking for supported WMI interface versions");
46
47 enum msi_wmi_platform_method {
48         MSI_PLATFORM_GET_PACKAGE        = 0x01,
49         MSI_PLATFORM_SET_PACKAGE        = 0x02,
50         MSI_PLATFORM_GET_EC             = 0x03,
51         MSI_PLATFORM_SET_EC             = 0x04,
52         MSI_PLATFORM_GET_BIOS           = 0x05,
53         MSI_PLATFORM_SET_BIOS           = 0x06,
54         MSI_PLATFORM_GET_SMBUS          = 0x07,
55         MSI_PLATFORM_SET_SMBUS          = 0x08,
56         MSI_PLATFORM_GET_MASTER_BATTERY = 0x09,
57         MSI_PLATFORM_SET_MASTER_BATTERY = 0x0a,
58         MSI_PLATFORM_GET_SLAVE_BATTERY  = 0x0b,
59         MSI_PLATFORM_SET_SLAVE_BATTERY  = 0x0c,
60         MSI_PLATFORM_GET_TEMPERATURE    = 0x0d,
61         MSI_PLATFORM_SET_TEMPERATURE    = 0x0e,
62         MSI_PLATFORM_GET_THERMAL        = 0x0f,
63         MSI_PLATFORM_SET_THERMAL        = 0x10,
64         MSI_PLATFORM_GET_FAN            = 0x11,
65         MSI_PLATFORM_SET_FAN            = 0x12,
66         MSI_PLATFORM_GET_DEVICE         = 0x13,
67         MSI_PLATFORM_SET_DEVICE         = 0x14,
68         MSI_PLATFORM_GET_POWER          = 0x15,
69         MSI_PLATFORM_SET_POWER          = 0x16,
70         MSI_PLATFORM_GET_DEBUG          = 0x17,
71         MSI_PLATFORM_SET_DEBUG          = 0x18,
72         MSI_PLATFORM_GET_AP             = 0x19,
73         MSI_PLATFORM_SET_AP             = 0x1a,
74         MSI_PLATFORM_GET_DATA           = 0x1b,
75         MSI_PLATFORM_SET_DATA           = 0x1c,
76         MSI_PLATFORM_GET_WMI            = 0x1d,
77 };
78
79 struct msi_wmi_platform_debugfs_data {
80         struct wmi_device *wdev;
81         enum msi_wmi_platform_method method;
82         struct rw_semaphore buffer_lock;        /* Protects debugfs buffer */
83         size_t length;
84         u8 buffer[32];
85 };
86
87 static const char * const msi_wmi_platform_debugfs_names[] = {
88         "get_package",
89         "set_package",
90         "get_ec",
91         "set_ec",
92         "get_bios",
93         "set_bios",
94         "get_smbus",
95         "set_smbus",
96         "get_master_battery",
97         "set_master_battery",
98         "get_slave_battery",
99         "set_slave_battery",
100         "get_temperature",
101         "set_temperature",
102         "get_thermal",
103         "set_thermal",
104         "get_fan",
105         "set_fan",
106         "get_device",
107         "set_device",
108         "get_power",
109         "set_power",
110         "get_debug",
111         "set_debug",
112         "get_ap",
113         "set_ap",
114         "get_data",
115         "set_data",
116         "get_wmi"
117 };
118
119 static int msi_wmi_platform_parse_buffer(union acpi_object *obj, u8 *output, size_t length)
120 {
121         if (obj->type != ACPI_TYPE_BUFFER)
122                 return -ENOMSG;
123
124         if (obj->buffer.length != length)
125                 return -EPROTO;
126
127         if (!obj->buffer.pointer[0])
128                 return -EIO;
129
130         memcpy(output, obj->buffer.pointer, obj->buffer.length);
131
132         return 0;
133 }
134
135 static int msi_wmi_platform_query(struct wmi_device *wdev, enum msi_wmi_platform_method method,
136                                   u8 *input, size_t input_length, u8 *output, size_t output_length)
137 {
138         struct acpi_buffer out = { ACPI_ALLOCATE_BUFFER, NULL };
139         struct acpi_buffer in = {
140                 .length = input_length,
141                 .pointer = input
142         };
143         union acpi_object *obj;
144         acpi_status status;
145         int ret;
146
147         if (!input_length || !output_length)
148                 return -EINVAL;
149
150         status = wmidev_evaluate_method(wdev, 0x0, method, &in, &out);
151         if (ACPI_FAILURE(status))
152                 return -EIO;
153
154         obj = out.pointer;
155         if (!obj)
156                 return -ENODATA;
157
158         ret = msi_wmi_platform_parse_buffer(obj, output, output_length);
159         kfree(obj);
160
161         return ret;
162 }
163
164 static umode_t msi_wmi_platform_is_visible(const void *drvdata, enum hwmon_sensor_types type,
165                                            u32 attr, int channel)
166 {
167         return 0444;
168 }
169
170 static int msi_wmi_platform_read(struct device *dev, enum hwmon_sensor_types type, u32 attr,
171                                  int channel, long *val)
172 {
173         struct wmi_device *wdev = dev_get_drvdata(dev);
174         u8 input[32] = { 0 };
175         u8 output[32];
176         u16 data;
177         int ret;
178
179         ret = msi_wmi_platform_query(wdev, MSI_PLATFORM_GET_FAN, input, sizeof(input), output,
180                                      sizeof(output));
181         if (ret < 0)
182                 return ret;
183
184         data = get_unaligned_be16(&output[channel * 2 + 1]);
185         if (!data)
186                 *val = 0;
187         else
188                 *val = 480000 / data;
189
190         return 0;
191 }
192
193 static const struct hwmon_ops msi_wmi_platform_ops = {
194         .is_visible = msi_wmi_platform_is_visible,
195         .read = msi_wmi_platform_read,
196 };
197
198 static const struct hwmon_channel_info * const msi_wmi_platform_info[] = {
199         HWMON_CHANNEL_INFO(fan,
200                            HWMON_F_INPUT,
201                            HWMON_F_INPUT,
202                            HWMON_F_INPUT,
203                            HWMON_F_INPUT
204                            ),
205         NULL
206 };
207
208 static const struct hwmon_chip_info msi_wmi_platform_chip_info = {
209         .ops = &msi_wmi_platform_ops,
210         .info = msi_wmi_platform_info,
211 };
212
213 static ssize_t msi_wmi_platform_write(struct file *fp, const char __user *input, size_t length,
214                                       loff_t *offset)
215 {
216         struct seq_file *seq = fp->private_data;
217         struct msi_wmi_platform_debugfs_data *data = seq->private;
218         u8 payload[32] = { };
219         ssize_t ret;
220
221         /* Do not allow partial writes */
222         if (*offset != 0)
223                 return -EINVAL;
224
225         /* Do not allow incomplete command buffers */
226         if (length != data->length)
227                 return -EINVAL;
228
229         ret = simple_write_to_buffer(payload, sizeof(payload), offset, input, length);
230         if (ret < 0)
231                 return ret;
232
233         down_write(&data->buffer_lock);
234         ret = msi_wmi_platform_query(data->wdev, data->method, payload, data->length, data->buffer,
235                                      data->length);
236         up_write(&data->buffer_lock);
237
238         if (ret < 0)
239                 return ret;
240
241         return length;
242 }
243
244 static int msi_wmi_platform_show(struct seq_file *seq, void *p)
245 {
246         struct msi_wmi_platform_debugfs_data *data = seq->private;
247         int ret;
248
249         down_read(&data->buffer_lock);
250         ret = seq_write(seq, data->buffer, data->length);
251         up_read(&data->buffer_lock);
252
253         return ret;
254 }
255
256 static int msi_wmi_platform_open(struct inode *inode, struct file *fp)
257 {
258         struct msi_wmi_platform_debugfs_data *data = inode->i_private;
259
260         /* The seq_file uses the last byte of the buffer for detecting buffer overflows */
261         return single_open_size(fp, msi_wmi_platform_show, data, data->length + 1);
262 }
263
264 static const struct file_operations msi_wmi_platform_debugfs_fops = {
265         .owner = THIS_MODULE,
266         .open = msi_wmi_platform_open,
267         .read = seq_read,
268         .write = msi_wmi_platform_write,
269         .llseek = seq_lseek,
270         .release = single_release,
271 };
272
273 static void msi_wmi_platform_debugfs_remove(void *data)
274 {
275         struct dentry *dir = data;
276
277         debugfs_remove_recursive(dir);
278 }
279
280 static void msi_wmi_platform_debugfs_add(struct wmi_device *wdev, struct dentry *dir,
281                                          const char *name, enum msi_wmi_platform_method method)
282 {
283         struct msi_wmi_platform_debugfs_data *data;
284         struct dentry *entry;
285
286         data = devm_kzalloc(&wdev->dev, sizeof(*data), GFP_KERNEL);
287         if (!data)
288                 return;
289
290         data->wdev = wdev;
291         data->method = method;
292         init_rwsem(&data->buffer_lock);
293
294         /* The ACPI firmware for now always requires a 32 byte input buffer due to
295          * a peculiarity in how Windows handles the CreateByteField() ACPI operator.
296          */
297         data->length = 32;
298
299         entry = debugfs_create_file(name, 0600, dir, data, &msi_wmi_platform_debugfs_fops);
300         if (IS_ERR(entry))
301                 devm_kfree(&wdev->dev, data);
302 }
303
304 static void msi_wmi_platform_debugfs_init(struct wmi_device *wdev)
305 {
306         struct dentry *dir;
307         char dir_name[64];
308         int ret, method;
309
310         scnprintf(dir_name, ARRAY_SIZE(dir_name), "%s-%s", DRIVER_NAME, dev_name(&wdev->dev));
311
312         dir = debugfs_create_dir(dir_name, NULL);
313         if (IS_ERR(dir))
314                 return;
315
316         ret = devm_add_action_or_reset(&wdev->dev, msi_wmi_platform_debugfs_remove, dir);
317         if (ret < 0)
318                 return;
319
320         for (method = MSI_PLATFORM_GET_PACKAGE; method <= MSI_PLATFORM_GET_WMI; method++)
321                 msi_wmi_platform_debugfs_add(wdev, dir, msi_wmi_platform_debugfs_names[method - 1],
322                                              method);
323 }
324
325 static int msi_wmi_platform_hwmon_init(struct wmi_device *wdev)
326 {
327         struct device *hdev;
328
329         hdev = devm_hwmon_device_register_with_info(&wdev->dev, "msi_wmi_platform", wdev,
330                                                     &msi_wmi_platform_chip_info, NULL);
331
332         return PTR_ERR_OR_ZERO(hdev);
333 }
334
335 static int msi_wmi_platform_ec_init(struct wmi_device *wdev)
336 {
337         u8 input[32] = { 0 };
338         u8 output[32];
339         u8 flags;
340         int ret;
341
342         ret = msi_wmi_platform_query(wdev, MSI_PLATFORM_GET_EC, input, sizeof(input), output,
343                                      sizeof(output));
344         if (ret < 0)
345                 return ret;
346
347         flags = output[MSI_PLATFORM_EC_FLAGS_OFFSET];
348
349         dev_dbg(&wdev->dev, "EC RAM version %lu.%lu\n",
350                 FIELD_GET(MSI_PLATFORM_EC_MAJOR_MASK, flags),
351                 FIELD_GET(MSI_PLATFORM_EC_MINOR_MASK, flags));
352         dev_dbg(&wdev->dev, "EC firmware version %.28s\n",
353                 &output[MSI_PLATFORM_EC_VERSION_OFFSET]);
354
355         if (!(flags & MSI_PLATFORM_EC_IS_TIGERLAKE)) {
356                 if (!force)
357                         return -ENODEV;
358
359                 dev_warn(&wdev->dev, "Loading on a non-Tigerlake platform\n");
360         }
361
362         return 0;
363 }
364
365 static int msi_wmi_platform_init(struct wmi_device *wdev)
366 {
367         u8 input[32] = { 0 };
368         u8 output[32];
369         int ret;
370
371         ret = msi_wmi_platform_query(wdev, MSI_PLATFORM_GET_WMI, input, sizeof(input), output,
372                                      sizeof(output));
373         if (ret < 0)
374                 return ret;
375
376         dev_dbg(&wdev->dev, "WMI interface version %u.%u\n",
377                 output[MSI_PLATFORM_WMI_MAJOR_OFFSET],
378                 output[MSI_PLATFORM_WMI_MINOR_OFFSET]);
379
380         if (output[MSI_PLATFORM_WMI_MAJOR_OFFSET] != MSI_WMI_PLATFORM_INTERFACE_VERSION) {
381                 if (!force)
382                         return -ENODEV;
383
384                 dev_warn(&wdev->dev, "Loading despite unsupported WMI interface version (%u.%u)\n",
385                          output[MSI_PLATFORM_WMI_MAJOR_OFFSET],
386                          output[MSI_PLATFORM_WMI_MINOR_OFFSET]);
387         }
388
389         return 0;
390 }
391
392 static int msi_wmi_platform_probe(struct wmi_device *wdev, const void *context)
393 {
394         int ret;
395
396         ret = msi_wmi_platform_init(wdev);
397         if (ret < 0)
398                 return ret;
399
400         ret = msi_wmi_platform_ec_init(wdev);
401         if (ret < 0)
402                 return ret;
403
404         msi_wmi_platform_debugfs_init(wdev);
405
406         return msi_wmi_platform_hwmon_init(wdev);
407 }
408
409 static const struct wmi_device_id msi_wmi_platform_id_table[] = {
410         { MSI_PLATFORM_GUID, NULL },
411         { }
412 };
413 MODULE_DEVICE_TABLE(wmi, msi_wmi_platform_id_table);
414
415 static struct wmi_driver msi_wmi_platform_driver = {
416         .driver = {
417                 .name = DRIVER_NAME,
418                 .probe_type = PROBE_PREFER_ASYNCHRONOUS,
419         },
420         .id_table = msi_wmi_platform_id_table,
421         .probe = msi_wmi_platform_probe,
422         .no_singleton = true,
423 };
424 module_wmi_driver(msi_wmi_platform_driver);
425
426 MODULE_AUTHOR("Armin Wolf <[email protected]>");
427 MODULE_DESCRIPTION("MSI WMI platform features");
428 MODULE_LICENSE("GPL");
This page took 0.058022 seconds and 4 git commands to generate.