brintos

brintos / linux-shallow public Read only

0
0
Text · 10.4 KiB · 9b5c7f8 Raw
429 lines · c
1// SPDX-License-Identifier: GPL-2.0-or-later2/*3 * Linux driver for WMI platform features on MSI notebooks.4 *5 * Copyright (C) 2024 Armin Wolf <W_Armin@gmx.de>6 */7 8#define pr_format(fmt) KBUILD_MODNAME ": " fmt9 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 <linux/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	232 33#define MSI_PLATFORM_WMI_MAJOR_OFFSET	134#define MSI_PLATFORM_WMI_MINOR_OFFSET	235 36#define MSI_PLATFORM_EC_FLAGS_OFFSET	137#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	242 43static bool force;44module_param_unsafe(force, bool, 0);45MODULE_PARM_DESC(force, "Force loading without checking for supported WMI interface versions");46 47enum 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 79struct 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 87static 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 119static 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 135static 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 = input142	};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 164static 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 170static 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	else188		*val = 480000 / data;189 190	return 0;191}192 193static const struct hwmon_ops msi_wmi_platform_ops = {194	.is_visible = msi_wmi_platform_is_visible,195	.read = msi_wmi_platform_read,196};197 198static 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_INPUT204			   ),205	NULL206};207 208static const struct hwmon_chip_info msi_wmi_platform_chip_info = {209	.ops = &msi_wmi_platform_ops,210	.info = msi_wmi_platform_info,211};212 213static 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 244static 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 256static 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 264static 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 273static void msi_wmi_platform_debugfs_remove(void *data)274{275	struct dentry *dir = data;276 277	debugfs_remove_recursive(dir);278}279 280static 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 to295	 * 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 304static 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 325static 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 335static 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 365static 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 392static 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 409static const struct wmi_device_id msi_wmi_platform_id_table[] = {410	{ MSI_PLATFORM_GUID, NULL },411	{ }412};413MODULE_DEVICE_TABLE(wmi, msi_wmi_platform_id_table);414 415static 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};424module_wmi_driver(msi_wmi_platform_driver);425 426MODULE_AUTHOR("Armin Wolf <W_Armin@gmx.de>");427MODULE_DESCRIPTION("MSI WMI platform features");428MODULE_LICENSE("GPL");429