lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [thread-next>] [day] [month] [year] [list]
Message-Id: <20241118100503.14228-1-jonmail@163.com>
Date: Mon, 18 Nov 2024 18:05:03 +0800
From: zhixin zhang <jonmail@....com>
To: hdegoede@...hat.com,
	ilpo.jarvinen@...ux.intel.com
Cc: platform-driver-x86@...r.kernel.org,
	linux-kernel@...r.kernel.org,
	zhixin zhang <zhangzx36@...ovo.com>
Subject: [PATCH] Lenovo Legion Go WMI Control

From: zhixin zhang <zhangzx36@...ovo.com>

This driver provides support for modifying the performance mode
function of Lenovo's Legion Go series.

Signed-off-by: zhixin zhang <zhangzx36@...ovo.com>
---
 drivers/platform/x86/Kconfig         |   9 +
 drivers/platform/x86/Makefile        |   1 +
 drivers/platform/x86/legion-go-wmi.c | 552 +++++++++++++++++++++++++++
 3 files changed, 562 insertions(+)
 create mode 100644 drivers/platform/x86/legion-go-wmi.c

diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
index 3875abba5a79..d04018f69dc6 100644
--- a/drivers/platform/x86/Kconfig
+++ b/drivers/platform/x86/Kconfig
@@ -483,6 +483,15 @@ config LENOVO_YMC
 	  This driver maps the Tablet Mode Control switch to SW_TABLET_MODE input
 	  events for Lenovo Yoga notebooks.
 
+config LEGION_GO_WMI
+	tristate "Lenovo Legion Go WMI Control"
+	depends on ACPI_WMI
+	depends on INPUT
+	help
+	  This driver provides support for modifying the performance mode
+	  function of Lenovo's Legion Go series, as well as the ability to
+	  set CPU power consumption in custom mode.
+
 config SENSORS_HDAPS
 	tristate "Thinkpad Hard Drive Active Protection System (hdaps)"
 	depends on INPUT
diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile
index e1b142947067..74b1f107084f 100644
--- a/drivers/platform/x86/Makefile
+++ b/drivers/platform/x86/Makefile
@@ -68,6 +68,7 @@ obj-$(CONFIG_THINKPAD_LMI)	+= think-lmi.o
 obj-$(CONFIG_YOGABOOK)		+= lenovo-yogabook.o
 obj-$(CONFIG_YT2_1380)		+= lenovo-yoga-tab2-pro-1380-fastcharger.o
 obj-$(CONFIG_LENOVO_WMI_CAMERA)	+= lenovo-wmi-camera.o
+obj-$(CONFIG_LEGION_GO_WMI)	+= legion-go-wmi.o
 
 # Intel
 obj-y				+= intel/
diff --git a/drivers/platform/x86/legion-go-wmi.c b/drivers/platform/x86/legion-go-wmi.c
new file mode 100644
index 000000000000..e319219c3ace
--- /dev/null
+++ b/drivers/platform/x86/legion-go-wmi.c
@@ -0,0 +1,552 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * legion-go-wmi.c - Lenovo Legion Go WMI Control
+ *
+ * Copyright © 2024 zhixin zhang <zhangzx36@...ovo.com>
+ */
+
+#include <linux/kernel.h>
+#include <linux/acpi.h>
+#include <linux/printk.h>
+#include <linux/module.h>
+#include <linux/wmi.h>
+#include <linux/errno.h>
+#include <linux/string.h>
+#include <linux/proc_fs.h>
+#include <linux/slab.h>
+#include <linux/uaccess.h>
+#include <linux/version.h>
+
+//extern struct proc_dir_entry *acpi_root_dir;
+struct proc_dir_entry *acpi_root_dir;
+
+#define BUFFER_SIZE 256
+
+#define LEGION_GO_WMI_GAMEZONE_GUID			"887B54E3-DDDC-4B2C-8B88-68A26A8835D0"
+#define LEGION_GO_WMI_OTHER_GUID			"dc2a8805-3a8c-41ba-a6f7-092e0089cd3b"
+
+//wmi_device_id context string
+#define LEGION_GO_WMI_GAMEZONE_CONTEXT	"GameZone"
+#define LEGION_GO_WMI_OTHER_CONTEXT		"Other"
+
+//funciton name
+#define CMD_SET_SPL				"SetSPL"
+#define CMD_GET_SPL				"GetSPL"
+#define CMD_SET_SPPT			"SetSPPT"
+#define CMD_GET_SPPT			"GetSPPT"
+#define CMD_SET_FPPT			"SetFPPT"
+#define CMD_GET_FPPT			"GetFPPT"
+#define CMD_SET_SMART_FAN_MODE	"SetSmartFanMode"
+#define CMD_GET_SMART_FAN_MODE	"GetSmartFanMode"
+
+//function arg for ids
+enum legion_go_wmi_ids{
+	ARG_SPL_CUSTOM_MODE = 0x0102FF00,
+	ARG_SPL_GET_VALUE = 0x0102FF00,
+
+	ARG_SPPT_CUSTOM_MODE = 0x0101FF00,
+	ARG_SPPT_GET_VALUE = 0x0101FF00,
+
+	ARG_FPPT_CUSTOM_MODE = 0x0103FF00,
+	ARG_FPPT_GET_VALUE = 0x0103FF00,
+
+	ARG_SMART_FAN_QUIENT_MODE = 0x1,
+	ARG_SMART_FAN_BALANCE_MODE = 0x2,
+	ARG_SMART_FAN_PERFORMANCE_MODE = 0x3,
+	ARG_SMART_FAN_CUSTOM_MODE = 0xFF,
+};
+
+static const struct wmi_device_id legion_go_wmi_id_table[] = {
+	{ LEGION_GO_WMI_GAMEZONE_GUID, LEGION_GO_WMI_GAMEZONE_CONTEXT },
+	{ LEGION_GO_WMI_OTHER_GUID, LEGION_GO_WMI_OTHER_CONTEXT },
+	{ }
+};
+
+
+enum legion_go_wmi_gamezone_method {
+	legion_go_wmi_gamezone_method	= 0xAA,	// WMAA, DSDT
+	LEGION_GO_WMI_OTHER_METHOD		= 0xAE,	// WMAA, DSDT
+};
+
+//wmi command
+enum legion_go_wmi_command {
+	// smart fan mode
+	LEGION_GO_WMI_GAMEZONE_SET_SMARTFANMODE	= 0x2C,
+	LEGION_GO_WMI_GAMEZONE_GET_SMARTFANMODE	= 0x2D,
+	// set bois feature
+	LEGION_GO_WMI_OTHER_SET_FEATURE_VALUE	= 0x12,
+	LEGION_GO_WMI_OTHER_GET_FEATURE_VALUE	= 0x11,
+};
+
+//wmi call function
+enum legion_go_call_function {
+	LEGION_GO_FUNC_NONE,
+	LEGION_GO_FUNC_SET_SPL,
+	LEGION_GO_FUNC_GET_SPL,
+	LEGION_GO_FUNC_SET_SPPT,
+	LEGION_GO_FUNC_GET_SPPT,
+	LEGION_GO_FUNC_SET_FPPT,
+	LEGION_GO_FUNC_GET_FPPT,
+	LEGION_GO_FUNC_SET_SMART_FAN_MODE,
+	LEGION_GO_FUNC_GET_SMART_FAN_MODE
+};
+
+struct legion_go_wmi_args_3i {
+	u32 arg1;
+	u32 arg2;
+	u32 arg3;
+};
+
+struct legion_go_wmi_args_2i {
+	u32 arg1;
+	u32 arg2;
+};
+
+struct legion_go_wmi_args_1i {
+	u32 arg1;
+};
+
+struct legion_go_global {
+	struct wmi_device *legion_device[2]; //0:"GameZone"  1:"Other"
+	enum legion_go_call_function last_call_function;
+	bool first_read;
+	struct proc_dir_entry *acpi_entry;
+	char result_buffer[BUFFER_SIZE];
+};
+
+static struct legion_go_global g_Legion_Go_Global = {
+	.legion_device = {NULL, NULL},
+	.last_call_function = LEGION_GO_FUNC_NONE,
+	.first_read = true,
+	.acpi_entry = NULL,
+};
+
+static acpi_status legion_go_wmi_perform_query(struct wmi_device *wdev,
+		enum legion_go_wmi_gamezone_method method_id,
+		const struct acpi_buffer *in,
+		struct acpi_buffer *out)
+{
+	acpi_status ret = wmidev_evaluate_method(wdev, 0x0, method_id, in, out);
+
+	if (ACPI_FAILURE(ret)) {
+		dev_warn(&wdev->dev, "LEGION GO WMI: WMI query failed with error: %d\n", ret);
+		return -EIO;
+	}
+
+	return 0;
+}
+
+static acpi_status legion_go_wmi_query_integer(struct wmi_device *wdev,
+		enum legion_go_wmi_gamezone_method method_id,
+		const struct acpi_buffer *in,
+		u32 *res)
+{
+	union acpi_object *obj;
+	struct acpi_buffer result = { ACPI_ALLOCATE_BUFFER, NULL };
+	acpi_status ret;
+
+	ret = legion_go_wmi_perform_query(wdev, method_id, in, &result);
+	if (ret) {
+		return ret;
+	}
+
+	obj = result.pointer;
+	if (obj && obj->type == ACPI_TYPE_INTEGER) {
+		*res = obj->integer.value;
+	}
+	else {
+		ret = -EIO;
+	}
+
+	kfree(result.pointer);
+	return ret;
+}
+
+
+/**
+ * procfs write callback. Called when writing into /proc/acpi/call.
+*/
+static ssize_t acpi_proc_write(struct file *filp,
+		const char __user *buff,
+		size_t len,
+		loff_t *data)
+{
+    char input[2 * BUFFER_SIZE] = { '\0' };
+    union acpi_object *args;
+    int nargs, i;
+    char *method;
+
+	u32 prod_id;
+	acpi_status ret;
+
+    if (len > sizeof(input) - 1) {
+        printk(KERN_ERR "LEGION GO WMI: Input too long! (%lu)\n", len);
+        return -ENOSPC;
+    }
+
+    if (copy_from_user( input, buff, len )) {
+        return -EFAULT;
+    }
+
+    input[len] = '\0';
+    if (input[len-1] == '\n')
+        input[len-1] = '\0';
+
+	printk("LEGION GO WMI: procfs write is %s\n", input);
+
+	char cmd[2 * BUFFER_SIZE] = { '\0' };
+	char arg1[2 * BUFFER_SIZE] = { '\0' };
+	int arg1Num = 0;
+	int retNum = 0;
+
+	int pos = -1;
+	for(int i=0;i<2 * BUFFER_SIZE;i++) {
+		if(input[i]== ',') {
+			memcpy(cmd,input,i*sizeof(char));
+			pos = i+1;
+		}
+		else if(input[i]=='\0' && pos != -1) {
+			memcpy(arg1,input+pos,(i-pos)*sizeof(char));
+			pos = i+1;
+			break;
+		}
+	}
+	if(pos == -1) {
+		memcpy(cmd,input,len*sizeof(char));
+	}
+	else {
+		printk(KERN_ERR "LEGION GO WMI: cmd = %s, arg1 : %s\n", cmd,arg1);
+		retNum = kstrtoint(arg1,10,&arg1Num);
+		if(retNum != 0)
+		{
+			printk(KERN_ERR "LEGION GO WMI: arg1 = %s param error!\n",arg1);
+			return -ENOSPC;
+		}
+	}
+
+	if(ret == 0) {
+		if(strcmp(cmd,CMD_SET_SPL)==0) {
+			struct legion_go_wmi_args_2i args = {
+				.arg1 = ARG_SPL_CUSTOM_MODE,
+				.arg2 = arg1Num,
+			};
+			const struct acpi_buffer in = {
+				.length = sizeof(args),
+				.pointer = &args,
+			};
+
+			g_Legion_Go_Global.last_call_function = LEGION_GO_FUNC_SET_SPL;
+
+			ret = legion_go_wmi_query_integer(g_Legion_Go_Global.legion_device[1], 
+					LEGION_GO_WMI_OTHER_SET_FEATURE_VALUE, &in, &prod_id);
+			if (ret == 0) {
+				dev_info(&g_Legion_Go_Global.legion_device[1]->dev, 
+						"LEGION GO WMI: SetSPL result is %d\n", prod_id);
+			}
+			else {
+				dev_warn(&g_Legion_Go_Global.legion_device[1]->dev,
+						"LEGION GO WMI: SetSPL query failed with err: %d\n", ret);
+			}
+		}
+		else if(strcmp(cmd,CMD_GET_SPL)==0) {
+			g_Legion_Go_Global.last_call_function = LEGION_GO_FUNC_GET_SPL;
+		}
+		else if(strcmp(cmd,CMD_SET_SPPT)==0) {
+			struct legion_go_wmi_args_2i args = {
+				.arg1 = ARG_SPPT_CUSTOM_MODE,
+				.arg2 = arg1Num,
+			};
+			const struct acpi_buffer in = {
+				.length = sizeof(args),
+				.pointer = &args,
+			};
+
+			g_Legion_Go_Global.last_call_function = LEGION_GO_FUNC_SET_SPPT;
+
+			ret = legion_go_wmi_query_integer(g_Legion_Go_Global.legion_device[1],
+					LEGION_GO_WMI_OTHER_SET_FEATURE_VALUE,
+					&in,
+					&prod_id);
+			if (ret == 0) {
+				dev_info(&g_Legion_Go_Global.legion_device[1]->dev,
+						"LEGION GO WMI: SetSPPT result is %d\n", prod_id);
+			}
+			else {
+				dev_warn(&g_Legion_Go_Global.legion_device[1]->dev,
+						"LEGION GO WMI: SetSPPT query failed with err: %d\n", ret);
+			}
+		}
+		else if(strcmp(cmd,CMD_GET_SPPT)==0) {
+			g_Legion_Go_Global.last_call_function = LEGION_GO_FUNC_GET_SPPT;
+		}
+		else if(strcmp(cmd,CMD_SET_FPPT)==0) {
+			struct legion_go_wmi_args_2i args = {
+				.arg1 = ARG_FPPT_CUSTOM_MODE,
+				.arg2 = arg1Num,
+			};
+			const struct acpi_buffer in = {
+				.length = sizeof(args),
+				.pointer = &args,
+			};
+
+			g_Legion_Go_Global.last_call_function = LEGION_GO_FUNC_SET_FPPT;
+
+			ret = legion_go_wmi_query_integer(g_Legion_Go_Global.legion_device[1],
+					LEGION_GO_WMI_OTHER_SET_FEATURE_VALUE,
+					&in,
+					&prod_id);
+			if (ret == 0) {
+				dev_info(&g_Legion_Go_Global.legion_device[1]->dev,
+						"LEGION GO WMI: SetFPPT result is %d\n", prod_id);
+			}
+			else {
+				dev_warn(&g_Legion_Go_Global.legion_device[1]->dev,
+						"LEGION GO WMI: SetFPPT query failed with err: %d\n", ret);
+			}
+		}
+		else if(strcmp(cmd,CMD_GET_FPPT)==0) {
+			g_Legion_Go_Global.last_call_function = LEGION_GO_FUNC_GET_FPPT;
+		}
+		else if(strcmp(cmd,CMD_SET_SMART_FAN_MODE)==0) {
+			if(arg1Num != 1 && arg1Num != 2 && arg1Num != 3 && arg1Num != 0xFF) {
+				printk(KERN_ERR "LEGION GO WMI: %s arg1 = %s param error!\n",
+						CMD_SET_SMART_FAN_MODE,arg1);
+				return -ENOSPC;
+			}
+
+			struct legion_go_wmi_args_1i args = {
+				.arg1 = arg1Num,
+			};
+			const struct acpi_buffer in = {
+				.length = sizeof(args),
+				.pointer = &args,
+			};
+			g_Legion_Go_Global.last_call_function = LEGION_GO_FUNC_SET_SMART_FAN_MODE;
+			ret = legion_go_wmi_query_integer(g_Legion_Go_Global.legion_device[0],
+					LEGION_GO_WMI_GAMEZONE_SET_SMARTFANMODE,
+					&in,
+					&prod_id);
+
+			if (ret == 0) {
+				dev_info(&g_Legion_Go_Global.legion_device[0]->dev,
+					"LEGION GO WMI: SetSmartFanMode query result is %d\n", prod_id);
+			} 
+			else {
+				dev_warn(&g_Legion_Go_Global.legion_device[0]->dev,
+				"LEGION GO WMI: SetSmartFanMode query failed with err: %d\n", ret);
+			}
+		}
+		else if(strcmp(cmd,CMD_GET_SMART_FAN_MODE)==0) {
+			g_Legion_Go_Global.last_call_function = LEGION_GO_FUNC_GET_SMART_FAN_MODE;
+		}
+	}
+
+    return len;
+}
+
+//read other mothod
+acpi_status acpi_proc_read_other(struct wmi_device *wdev,
+		enum legion_go_wmi_command cmd,
+		struct legion_go_wmi_args_1i* args,
+		char* funciton_name)
+{
+	u32 prod_id = 0;
+	const struct acpi_buffer in = {
+		.length = sizeof(*args),
+		.pointer = args,
+	};
+	acpi_status ret = legion_go_wmi_query_integer(wdev, cmd,  &in, &prod_id);
+	if (ret == 0) {
+		dev_info(&wdev->dev, "LEGION GO WMI: Integer query result is %d\n", prod_id);
+		snprintf(g_Legion_Go_Global.result_buffer,BUFFER_SIZE,"%s,%u",funciton_name,prod_id);
+	} 
+	else {
+		dev_warn(&wdev->dev, "LEGION GO WMI: Integer query failed with err: %d\n", ret);
+		snprintf(g_Legion_Go_Global.result_buffer,BUFFER_SIZE,"%s,error",funciton_name);
+	}
+	return ret;
+}
+
+static ssize_t acpi_proc_read(struct file *filp, char __user *buff, size_t count, loff_t *off)
+{
+	u32 prod_id;
+	acpi_status ret;
+	int len = strlen(g_Legion_Go_Global.result_buffer);
+
+	memset(g_Legion_Go_Global.result_buffer,'\0',len);
+
+	if(g_Legion_Go_Global.last_call_function == LEGION_GO_FUNC_NONE) {
+		ssize_t result = simple_read_from_buffer(buff,
+				count,
+				off,
+				g_Legion_Go_Global.result_buffer,
+				len + 1);
+		return result;
+		//return -EIO;
+	}
+
+
+	switch(g_Legion_Go_Global.last_call_function) {
+		case LEGION_GO_FUNC_SET_SPL:
+		case LEGION_GO_FUNC_GET_SPL:
+		{
+			struct legion_go_wmi_args_1i args = {
+				.arg1 = ARG_SPL_GET_VALUE,
+			};
+			ret = acpi_proc_read_other(g_Legion_Go_Global.legion_device[1],
+				LEGION_GO_WMI_OTHER_GET_FEATURE_VALUE,
+				&args,
+				CMD_GET_SPL);
+
+			break;
+		}
+		case LEGION_GO_FUNC_SET_SPPT:
+		case LEGION_GO_FUNC_GET_SPPT:
+		{
+			struct legion_go_wmi_args_1i args = {
+				.arg1 = ARG_SPPT_GET_VALUE,
+			};
+			ret = acpi_proc_read_other(g_Legion_Go_Global.legion_device[1],
+					LEGION_GO_WMI_OTHER_GET_FEATURE_VALUE,
+					&args,
+					CMD_GET_SPPT);
+
+			break;
+		}
+		case LEGION_GO_FUNC_SET_FPPT:
+		case LEGION_GO_FUNC_GET_FPPT:
+		{
+			struct legion_go_wmi_args_1i args = {
+				.arg1 = ARG_FPPT_GET_VALUE,
+			};
+			ret = acpi_proc_read_other(g_Legion_Go_Global.legion_device[1],
+					LEGION_GO_WMI_OTHER_GET_FEATURE_VALUE,
+					&args,
+					CMD_GET_FPPT);
+
+			break;
+		}
+		case LEGION_GO_FUNC_SET_SMART_FAN_MODE:
+		case LEGION_GO_FUNC_GET_SMART_FAN_MODE:
+		{
+			struct legion_go_wmi_args_1i args = {
+				.arg1 = 255,
+			};
+			const struct acpi_buffer in = {
+				.length = sizeof(args),
+				.pointer = &args,
+			};
+
+			ret = legion_go_wmi_query_integer(g_Legion_Go_Global.legion_device[0],
+					LEGION_GO_WMI_GAMEZONE_GET_SMARTFANMODE,
+					&in,
+					&prod_id);
+			if (ret == 0) {
+				dev_info(&g_Legion_Go_Global.legion_device[0]->dev,
+						"LEGION GO WMI: Integer query result is %d\n", prod_id);
+				snprintf(g_Legion_Go_Global.result_buffer,BUFFER_SIZE,"%s,%u",
+						CMD_GET_SMART_FAN_MODE,prod_id);
+			}
+			else {
+				dev_warn(&g_Legion_Go_Global.legion_device[0]->dev,
+						"LEGION GO WMI: Integer query failed with err: %d\n", ret);
+				snprintf(g_Legion_Go_Global.result_buffer,BUFFER_SIZE,"%s,error",
+						CMD_GET_SMART_FAN_MODE);
+			}
+			break;
+		}
+		default:
+		{
+			strcpy(g_Legion_Go_Global.result_buffer,"LEGION GO WMI: nothing to write");
+		}
+	}
+
+	if(g_Legion_Go_Global.first_read == true) {
+		char temp[BUFFER_SIZE] = {'\0'};
+		strcpy(temp, g_Legion_Go_Global.result_buffer);
+		strcpy(g_Legion_Go_Global.result_buffer+1, temp);
+		g_Legion_Go_Global.first_read = false;
+	}
+	// output the current result buffer
+	ssize_t result = simple_read_from_buffer(buff,
+			count,
+			off,
+			g_Legion_Go_Global.result_buffer,
+			len + 1);
+
+    return result;
+}
+
+static const struct proc_ops proc_acpi_operations = {
+        .proc_read     = acpi_proc_read,
+        .proc_write    = acpi_proc_write,
+};
+
+static int legion_go_wmi_probe(struct wmi_device *wdev, const void *context)
+{
+	dev_info(&wdev->dev, "LEGION GO WMI: Probe is starting.\n");
+
+	if (!wmi_has_guid(LEGION_GO_WMI_OTHER_GUID)) {
+		dev_warn(&wdev->dev, "LEGION GO WMI: No known OTHER WMI GUID found\n");
+		return -ENODEV;
+	}
+
+	if (!wmi_has_guid(LEGION_GO_WMI_GAMEZONE_GUID)) {
+		dev_warn(&wdev->dev, "LEGION GO WMI: No known GAMEZONE WMI GUID found\n");
+		return -ENODEV;
+	}
+
+	if (g_Legion_Go_Global.acpi_entry == NULL) {
+		g_Legion_Go_Global.acpi_entry = proc_create("legion_go_call", 
+				0660,
+				acpi_root_dir,
+				&proc_acpi_operations);
+	}
+
+    if (g_Legion_Go_Global.acpi_entry == NULL)
+	{
+      dev_warn(&wdev->dev, "LEGION GO WMI: Couldn't create procfs entry\n");
+      return -ENOMEM;
+    }
+
+    dev_info(&wdev->dev, "LEGION GO WMI: procfs entry at /proc/acpi/legion_go_call created.\n");
+
+	dev_info(&wdev->dev, "LEGION GO WMI: Probe is exiting.\n");
+
+	if(strcmp(context, LEGION_GO_WMI_GAMEZONE_CONTEXT)== 0) {
+		g_Legion_Go_Global.legion_device[0] = wdev;
+	}
+	else {
+		g_Legion_Go_Global.legion_device[1] = wdev;
+	}
+
+	return 0;
+}
+
+static void legion_go_wmi_remove(struct wmi_device *wdev)
+{
+	g_Legion_Go_Global.legion_device[0] = NULL;
+	g_Legion_Go_Global.legion_device[1] = NULL;
+
+    remove_proc_entry("legion_go_call", acpi_root_dir);
+
+    dev_info(&wdev->dev, "LEGION GO WMI: procfs entry removed\n");
+}
+
+static struct wmi_driver legion_go_wmi_driver = {
+	.driver = {
+		.name = "legion-go-wmi",
+	},
+	.id_table = legion_go_wmi_id_table,
+	.probe = legion_go_wmi_probe,
+	.remove = legion_go_wmi_remove
+};
+
+module_wmi_driver(legion_go_wmi_driver);
+
+MODULE_DEVICE_TABLE(wmi, legion_go_wmi_id_table);
+
+MODULE_DESCRIPTION("Lenovo Legion Go WMI Driver");
+MODULE_AUTHOR("zhixin zhang<zhangzx36@...ovo.com>");
+MODULE_LICENSE("GPL");
+MODULE_VERSION("1.0.0.0");
-- 
2.34.1


Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ