Message ID | 20241118100503.14228-1-jonmail@163.com (mailing list archive) |
---|---|
State | Changes Requested, archived |
Headers | show |
Series | Lenovo Legion Go WMI Control | expand |
On Mon, Nov 18, 2024 at 06:05:03PM +0800, zhixin zhang wrote: > From: zhixin zhang <zhangzx36@lenovo.com> > > This driver provides support for modifying the performance mode > function of Lenovo's Legion Go series. > > Signed-off-by: zhixin zhang <zhangzx36@lenovo.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 Hi! As a word of advice, you should analyze your patch with checkpatch.pl before submitting. It gives me the following output for your patch: ./scripts/checkpatch.pl [...] total: 104 errors, 51 warnings, 574 lines checked [...] Regards, Kurt
On Mon, 18 Nov 2024, zhixin zhang wrote: > From: zhixin zhang <zhangzx36@lenovo.com> > > This driver provides support for modifying the performance mode > function of Lenovo's Legion Go series. > > Signed-off-by: zhixin zhang <zhangzx36@lenovo.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@lenovo.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> Order alphabetically. > + > +//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 function All your comments seem to miss space. > +#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, Align values. Are these groups actually independent? If yes, they don't belong into same enum. > +}; > + > +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 }, > + { } > +}; > + > + Extra newline, but can you also place the array close to where it's used. > +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) Does this query "integer" or u32 as per the res parameter? If the latter, rename accordingly please. (And integer could be "int" too but I suspect you want _u32 in the name instead) > +{ > + union acpi_object *obj; > + struct acpi_buffer result = { ACPI_ALLOCATE_BUFFER, NULL }; > + acpi_status ret; Reverse xmas-tree order. > + > + ret = legion_go_wmi_perform_query(wdev, method_id, in, &result); > + if (ret) { > + return ret; > + } Unnecessary braces. > + > + obj = result.pointer; > + if (obj && obj->type == ACPI_TYPE_INTEGER) { > + *res = obj->integer.value; > + } > + else { } else { But then braces are unnecessary for one line blocks. > + ret = -EIO; > + } > + > + kfree(result.pointer); > + return ret; > +} > + > + > +/** > + * procfs write callback. Called when writing into /proc/acpi/call. Not formatted according to kerneldoc, either make it kerneldoc compliant or change /** -> /* > +*/ > +static ssize_t acpi_proc_write(struct file *filp, > + const char __user *buff, > + size_t len, > + loff_t *data) Misaligned. Try to use less lines. > +{ > + char input[2 * BUFFER_SIZE] = { '\0' }; Don't place large arrays into stack. = {}; is enough to initialize. > + union acpi_object *args; > + int nargs, i; > + char *method; Use tabs for indentation. > + > + u32 prod_id; > + acpi_status ret; > + > + if (len > sizeof(input) - 1) { > + printk(KERN_ERR "LEGION GO WMI: Input too long! (%lu)\n", len); Use dev_err() and don't provide prefix yourself. Although I'm not sure if this is useful on KERN_ERR level... > + return -ENOSPC; -EINVAL > + } > + > + if (copy_from_user( input, buff, len )) { No spaces next to function opening and closing parenthesis. > + 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' }; Not from stack. > + int arg1Num = 0; > + int retNum = 0; Wrong variable naming style. > + > + int pos = -1; > + for(int i=0;i<2 * BUFFER_SIZE;i++) { Wrong spacing. > + if(input[i]== ',') { Missing spaces. Is the input guaranteed to have only single comma? > + memcpy(cmd,input,i*sizeof(char)); Why you need to duplicate it again? > + pos = i+1; Missing spaces. > + } > + 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); Are these your debug prints or what? > + retNum = kstrtoint(arg1,10,&arg1Num); Parse in place, there's no need to do a memcpy because of parsing int. > + if(retNum != 0) > + { > + printk(KERN_ERR "LEGION GO WMI: arg1 = %s param error!\n",arg1); > + return -ENOSPC; -EINVAL? If given input is wrong/invalid, we return -EINVAL. > + } > + } > + > + if(ret == 0) { Not initialized!??! > + if(strcmp(cmd,CMD_SET_SPL)==0) { So you memcpy earlier to just run strcmp(), why memcpy? > + 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, Your variable name is perhaps a bit on the long side :-). Please try to make it a bit shorter. Also no CAPS in variable names. > + "LEGION GO WMI: SetSPL result is %d\n", prod_id); Don't print info level message on success. You might not need to print anything here as this kind of looks like something that was useful during development or so. > + } > + else { > + dev_warn(&g_Legion_Go_Global.legion_device[1]->dev, > + "LEGION GO WMI: SetSPL query failed with err: %d\n", ret); return error? > + } > + } > + 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 No new information given by this comment. If you have nothing else to stay, just comments like this as the function name tells the same information already. > +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) function > +{ > + 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) { acpi_status ret; ret = legion_go_wmi_query_integer(wdev, cmd, &in, &prod_id); if (...) { > + 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); Is the use of result_buffer race free? > + > + if(g_Legion_Go_Global.last_call_function == LEGION_GO_FUNC_NONE) { Why isn't this inside switch/case? > + ssize_t result = simple_read_from_buffer(buff, Define variable separately. > + count, > + off, > + g_Legion_Go_Global.result_buffer, > + len + 1); Badly misaligned. > + return result; > + //return -EIO; Remove all commented out code fragments. > + } > + > + > + 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; In general, you should produce the string only as the last step before return instead of all the copying you've going on. > +} > + > +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"); Don't print anything on success. > + 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"); You shouldn't need to add the prefix on every line. Use dev_fmt() instead. > + 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); > + } Perhaps procfs is not right place for what you're trying to do (I'm a bit loss what exactly because your code is quite hard to follow because of various style issues and nested layers that seem to serve no real purpose). > + 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@lenovo.com>"); > +MODULE_LICENSE("GPL"); > +MODULE_VERSION("1.0.0.0"); Drop MODULE_VERSION(). There were many repeated style issues in this submission. Please follow what is outlined in Documentation/process/coding-style.rst. As somebody already mentioned, checkpatch can help you find most of such issues but perhaps not all.
On 11/18/2024 04:05, zhixin zhang wrote: > From: zhixin zhang <zhangzx36@lenovo.com> > > This driver provides support for modifying the performance mode > function of Lenovo's Legion Go series. > > Signed-off-by: zhixin zhang <zhangzx36@lenovo.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@lenovo.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@lenovo.com>"); > +MODULE_LICENSE("GPL"); > +MODULE_VERSION("1.0.0.0"); Besides the comments from Ilpo and Kurt I notice that this driver is incredibly noisy. You've got a dev_info() or dev_warn() in nearly every function. While going through all the comments from checkpatch and Ilpo please also drop 99% of those.
Am 18.11.24 um 11:05 schrieb zhixin zhang: > From: zhixin zhang <zhangzx36@lenovo.com> > > This driver provides support for modifying the performance mode > function of Lenovo's Legion Go series. Thanks for submitting the driver. Apart from the styling issues (please use checkpatch for the next revision), i have a couple of issues with the general architecture of this driver: - it tries to handle two unrelated WMI devices at the same time. - it exposes a userspace interface for calling raw WMI methods, nothing else. - it uses procfs for this task. _For the LEGION_GO_WMI_GAMEZONE_GUID:_ It seems to me that this WMI device is also present on other machines manufactured by Lenovo, so please create a separate driver for it (you might call it "lenovo-gamezone-wmi"). Also please write a short documentation regarding the WMI interface used by this driver: [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"), Description("LENOVO_GAMEZONE_DATA class"), guid("{887B54E3-DDDC-4B2C-8B88-68A26A8835D0}")] class LENOVO_GAMEZONE_DATA { [key, read] string InstanceName; [read] boolean Active; [WmiMethodId(1), Implemented, Description("Get IR temp")] void GetIRTemp([out, Description("IR temperature")] uint32 Data); [WmiMethodId(2), Implemented, Description("Get ThermalTable ID")] void GetThermalTableID([out, Description("Get ThermalTable ID")] uint32 Data); [WmiMethodId(3), Implemented, Description("Set ThermalTable ID")] void SetThermalTableID([in, Description("Set ThermalTable ID")] uint32 Data); [WmiMethodId(4), Implemented, Description("Is SupportGpu OverClock")] void IsSupportGpuOC([out, Description("Is SupportGpu OverClock")] uint32 Data); [WmiMethodId(5), Implemented, Description("Get GpuGpsState")] void GetGpuGpsState([out, Description("Get GpuGpsState")] uint32 Data); [WmiMethodId(6), Implemented, Description("Set GpuGpsState")] void SetGpuGpsState([in, Description("Set GpuGpsState")] uint32 Data); [WmiMethodId(7), Implemented, Description("Get Fan Count")] void GetFanCount([out, Description("Fan Count")] uint32 Data); [WmiMethodId(8), Implemented, Description("Get Fan1 Speed")] void GetFan1Speed([out, Description("Fan1 Speed")] uint32 Data); [WmiMethodId(9), Implemented, Description("Get Fan2 Speed")] void GetFan2Speed([out, Description("Fan2 Speed")] uint32 Data); [WmiMethodId(10), Implemented, Description("Get Fan Max Speed")] void GetFanMaxSpeed([out, Description("Fan Max Speed")] uint32 Data); [WmiMethodId(11), Implemented, Description("Get AslCode Version")] void GetVersion([out, Description("AslCode version")] uint32 Data); [WmiMethodId(12), Implemented, Description("Fan cooling capabilty")] void IsSupportFanCooling([out, Description("Fan cooling capablity")] uint32 Data); [WmiMethodId(13), Implemented, Description("Set Fan cooling on/off")] void SetFanCooling([in, Description("Set Fan cooling on/off")] uint32 Data); [WmiMethodId(14), Implemented, Description("cpu oc capability")] void IsSupportCpuOC([out, Description("cpu oc capability")] uint32 Data); [WmiMethodId(15), Implemented, Description("bios has overclock capability")] void IsBIOSSupportOC([out, Description("bios has overclock capability")] uint32 Data); [WmiMethodId(16), Implemented, Description("enble or disable overclock in bios")] void SetBIOSOC([in, Description("enble or disable overclock in bios")] uint32 Data); [WmiMethodId(17), Implemented, Description("Get temperature change trigger temp value")] void GetTriggerTemperatureValue([out, Description("Get temperature change trigger temp value")] uint32 Data); [WmiMethodId(18), Implemented, Description("Get CPU temperature")] void GetCPUTemp([out, Description("Get CPU temperature")] uint32 Data); [WmiMethodId(19), Implemented, Description("Get GPU temperature")] void GetGPUTemp([out, Description("Get GPU temperature")] uint32 Data); [WmiMethodId(20), Implemented, Description("Get Fan cooling on/off status")] void GetFanCoolingStatus([out, Description("Get Fan cooling on/off status")] uint32 Data); [WmiMethodId(21), Implemented, Description("EC support disable windows key capability")] void IsSupportDisableWinKey([out, Description("EC support disable windows key capability")] uint32 Data); [WmiMethodId(22), Implemented, Description("Set windows key disable/enable")] void SetWinKeyStatus([in, Description("Set windows key disable/enable")] uint32 Data); [WmiMethodId(23), Implemented, Description("Get windows key disable/enable status")] void GetWinKeyStatus([out, Description("Get windows key disable/enable status")] uint32 Data); [WmiMethodId(24), Implemented, Description("EC support disable touchpad capability")] void IsSupportDisableTP([out, Description("EC support disable touchpad capability")] uint32 Data); [WmiMethodId(25), Implemented, Description("Set touchpad disable/enable")] void SetTPStatus([in, Description("Set touchpad disable/enable")] uint32 Data); [WmiMethodId(26), Implemented, Description("Get touchpad disable/enable status")] void GetTPStatus([out, Description("Get touchpad disable/enable status")] uint32 Data); [WmiMethodId(27), Implemented, Description("Get GPU normal mode max TDP(W)")] void GetGPUPow([out, Description("Get GPU normal mode max TDP(W)")] uint32 Data); [WmiMethodId(28), Implemented, Description("Get GPU OC mode max TDP(W)")] void GetGPUOCPow([out, Description("Get GPU OC mode max TDP(W)")] uint32 Data); [WmiMethodId(29), Implemented, Description("Get GPU OC type")] void GetGPUOCType([out, Description("Get GPU OC type")] uint32 Data); [WmiMethodId(30), Implemented, Description("Get Keyboard feature list")] void GetKeyboardfeaturelist([out, Description("Get Keyboard feature list")] uint32 Data); [WmiMethodId(31), Implemented, Description("Get Memory OC Information")] void GetMemoryOCInfo([out, Description("Get Memory OC Information")] uint32 Data); [WmiMethodId(32), Implemented, Description("Water Cooling feature capability")] void IsSupportWaterCooling([out, Description("Water Cooling feature capability")] uint32 Data); [WmiMethodId(33), Implemented, Description("Set Water Cooling status")] void SetWaterCoolingStatus([in, Description("Set Water Cooling status")] uint32 Data); [WmiMethodId(34), Implemented, Description("Get Water Cooling status")] void GetWaterCoolingStatus([out, Description("Get Water Cooling status")] uint32 Data); [WmiMethodId(35), Implemented, Description("Lighting feature capability")] void IsSupportLightingFeature([out, Description("Lighting feature capability")] uint32 Data); [WmiMethodId(36), Implemented, Description("Set keyboard light off or on to max")] void SetKeyboardLight([in, Description("keyboard light off or on switch")] uint32 Data); [WmiMethodId(37), Implemented, Description("Get keyboard light on/off status")] void GetKeyboardLight([out, Description("Get keyboard light on/off status")] uint32 Data); [WmiMethodId(38), Implemented, Description("Get Macrokey scan code")] void GetMacrokeyScancode([in, Description("Macrokey index")] uint32 idx, [out, Description("Scan code")] uint32 scancode); [WmiMethodId(39), Implemented, Description("Get Macrokey count")] void GetMacrokeyCount([out, Description("Macrokey count")] uint32 Data); [WmiMethodId(40), Implemented, Description("Support G-Sync feature")] void IsSupportGSync([out, Description("Support G-Sync feature")] uint32 Data); [WmiMethodId(41), Implemented, Description("Get G-Sync Statust")] void GetGSyncStatus([out, Description("Get G-Sync Statust")] uint32 Data); [WmiMethodId(42), Implemented, Description("Set G-Sync Statust")] void SetGSyncStatus([in, Description("Set G-Sync Statust")] uint32 Data); [WmiMethodId(43), Implemented, Description("Support Smart Fan feature")] void IsSupportSmartFan([out, Description("Support Smart Fan feature")] uint32 Data); [WmiMethodId(44), Implemented, Description("Set Smart Fan Mode")] void SetSmartFanMode([in, Description("Set Smart Fan Mode")] uint32 Data); [WmiMethodId(45), Implemented, Description("Get Smart Fan Mode")] void GetSmartFanMode([out, Description("Get Smart Fan Mode")] uint32 Data); [WmiMethodId(46), Implemented, Description("Get Smart Fan Setting Mode")] void GetSmartFanSetting([out, Description("Get Smart Setting Mode")] uint32 Data); [WmiMethodId(47), Implemented, Description("Get Power Charge Mode")] void GetPowerChargeMode([out, Description("Get Power Charge Mode")] uint32 Data); [WmiMethodId(48), Implemented, Description("Get Gaming Product Info")] void GetProductInfo([out, Description("Get Gaming Product Info")] uint32 Data); [WmiMethodId(49), Implemented, Description("Over Drive feature capability")] void IsSupportOD([out, Description("Over Drive feature capability")] uint32 Data); [WmiMethodId(50), Implemented, Description("Get Over Drive status")] void GetODStatus([out, Description("Get Over Drive status")] uint32 Data); [WmiMethodId(51), Implemented, Description("Set Over Drive status")] void SetODStatus([in, Description("Set Over Drive status")] uint32 Data); [WmiMethodId(52), Implemented, Description("Set Light Control Owner")] void SetLightControlOwner([in, Description("Set Light Control Owner")] uint32 Data); [WmiMethodId(53), Implemented, Description("Set DDS Control Owner")] void SetDDSControlOwner([in, Description("Set DDS Control Owner")] uint32 Data); [WmiMethodId(54), Implemented, Description("Get the flag of restore OC value")] void IsRestoreOCValue([in, Description("Clean this flag")] uint32 idx, [out, Description("Resotre oc value flag")] uint32 Data); [WmiMethodId(55), Implemented, Description("Get Real Thremal Mode")] void GetThermalMode([out, Description("Real Thremal Mode")] uint32 Data); [WmiMethodId(56), Implemented, Description("Get the OC switch status in BIOS")] void GetBIOSOCMode([out, Description("OC Mode")] uint32 Data); [WmiMethodId(57), Implemented, Description("Set the current mode in Intelligent Mode")] void SetIntelligentSubMode([in, Description("mode")] uint32 Data); [WmiMethodId(58), Implemented, Description("Get the current mode in Intelligent Mode")] void GetIntelligentSubMode([out, Description("mode")] uint32 Data); [WmiMethodId(59), Implemented, Description("Get hardware info support version")] void GetHardwareInfoSupportVersion([out, Description("version")] uint32 Data); [WmiMethodId(60), Implemented, Description("Get Cpu core 0 max frequency")] void GetCpuFrequency([out, Description("frequency")] uint32 Data); [WmiMethodId(61), Implemented, Description("Get Total count of Learning Profile")] void GetLearningProfileCount([out, Description("profile count")] uint32 Data); [WmiMethodId(62), Implemented, Description("Check the Adapter type fit for OC")] void IsACFitForOC([out, Description("AC check result")] uint32 Data); [WmiMethodId(63), Implemented, Description("Is support IGPU mode")] void IsSupportIGPUMode([out, Description("IGPU modes")] uint32 Data); [WmiMethodId(64), Implemented, Description("Get IGPU Mode Status")] void GetIGPUModeStatus([out, Description("IGPU Mode Status")] uint32 Data); [WmiMethodId(65), Implemented, Description("Set IGPU Mode")] void SetIGPUModeStatus([in, Description("IGPU Mode")] uint32 mode, [out, Description("return code")] uint32 Data); [WmiMethodId(66), Implemented, Description("Notify DGPU Status")] void NotifyDGPUStatus([in, Description("DGPU status")] uint32 status, [out, Description("return code")] uint32 Data); [WmiMethodId(67), Implemented, Description("Is changed Y log")] void IsChangedYLog([out, Description("Is changed Y Log")] uint32 Data); [WmiMethodId(68), Implemented, Description("Get DGPU Hardwawre ID")] void GetDGPUHWId([out, Description("Get DGPU Hardware ID")] string Data); }; Since the initial version of the lenovo-gamezone-wmi will not use all WMI methods provided by this WMI interface, you can restrict yourself to only documenting the WMI methods used by this driver. Also the WMI device provides a GetVersion() WMI method, likely (please correct me if i am wrong) for discovering which WMI methods are present. The driver should call this method during probing and return -ENODEV if the version number is to low for supporting the driver. For supporting the "Smart Fan Mode": Please use the IsSupportSmartFan() WMI method to check if this feature is available before using it. Also please use the platform-profile API to expose this setting to user space. _For the LEGION_GO_WMI_OTHER_GUID:_ Please also create a separate driver for this WMI device (you might call it "lenovo-tuning-wmi") together with some documentation. For exposing the the tunables you can use sysfs attributes. Thanks, Armin Wolf > > Signed-off-by: zhixin zhang <zhangzx36@lenovo.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@lenovo.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@lenovo.com>"); > +MODULE_LICENSE("GPL"); > +MODULE_VERSION("1.0.0.0");
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@lenovo.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@lenovo.com>"); +MODULE_LICENSE("GPL"); +MODULE_VERSION("1.0.0.0");