From 3f14226e2e90dba5d72c106da29e1876eb7b88ff Mon Sep 17 00:00:00 2001 From: Denis Date: Thu, 28 Sep 2023 03:40:53 +0200 Subject: [PATCH] add acpi_call --- drivers/platform/x86/Kconfig | 5 + drivers/platform/x86/Makefile | 4 + drivers/platform/x86/acpi_call.c | 449 +++++++++++++++++++++++++++++++ 3 files changed, 458 insertions(+) create mode 100644 drivers/platform/x86/acpi_call.c diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index 49c2c4cd8d00..fde791e51261 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -170,6 +170,11 @@ config ACER_WIRELESS If you choose to compile this driver as a module the module will be called acer-wireless. +config ACPI_CALL + tristate "acpi_call module" + help + This embeds acpi_call module into the kernel + config ACER_WMI tristate "Acer WMI Laptop Extras" depends on BACKLIGHT_CLASS_DEVICE diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile index 52dfdf574ac2..1e434fcb8273 100644 --- a/drivers/platform/x86/Makefile +++ b/drivers/platform/x86/Makefile @@ -4,10 +4,14 @@ # x86 Platform-Specific Drivers # +# ACPI calls + # Windows Management Interface obj-$(CONFIG_ACPI_WMI) += wmi.o obj-$(CONFIG_WMI_BMOF) += wmi-bmof.o +obj-$(CONFIG_ACPI_CALL) += acpi_call.o + # WMI drivers obj-$(CONFIG_HUAWEI_WMI) += huawei-wmi.o obj-$(CONFIG_MXM_WMI) += mxm-wmi.o diff --git a/drivers/platform/x86/acpi_call.c b/drivers/platform/x86/acpi_call.c new file mode 100644 index 000000000000..d7bc238e16da --- /dev/null +++ b/drivers/platform/x86/acpi_call.c @@ -0,0 +1,449 @@ +/* Copyright (c) 2010: Michal Kottman */ + +#define BUILDING_ACPICA + +#include +#include +#include +#include +#include +#include +#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 14, 0) +#include +#endif +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 17, 0) +#include +#else +#include +#endif + +MODULE_LICENSE("GPL"); + +/* Uncomment the following line to enable debug messages */ +/* +#define DEBUG +*/ + +#define BUFFER_SIZE 4096 +#define INPUT_BUFFER_SIZE (2 * BUFFER_SIZE) +#define MAX_ACPI_ARGS 16 + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 0) +#define HAVE_PROC_CREATE +#endif + +extern struct proc_dir_entry *acpi_root_dir; + +static char input_buffer[INPUT_BUFFER_SIZE]; +static char result_buffer[BUFFER_SIZE]; +static char not_called_message[11] = "not called"; + +static u8 temporary_buffer[BUFFER_SIZE]; + +static size_t get_avail_bytes(void) { + return BUFFER_SIZE - strlen(result_buffer); +} +static char *get_buffer_end(void) { + return result_buffer + strlen(result_buffer); +} + +/** Appends the contents of an acpi_object to the result buffer +@param result An acpi object holding result data +@returns 0 if the result could fully be saved, a higher value otherwise +*/ +static int acpi_result_to_string(union acpi_object *result) { + if (result->type == ACPI_TYPE_INTEGER) { + snprintf(get_buffer_end(), get_avail_bytes(), + "0x%x", (int)result->integer.value); + } else if (result->type == ACPI_TYPE_STRING) { + snprintf(get_buffer_end(), get_avail_bytes(), + "\"%*s\"", result->string.length, result->string.pointer); + } else if (result->type == ACPI_TYPE_BUFFER) { + int i; + // do not store more than data if it does not fit. The first element is + // just 4 chars, but there is also two bytes from the curly brackets + int show_values = min((size_t)result->buffer.length, get_avail_bytes() / 6); + + snprintf(get_buffer_end(), get_avail_bytes(), "{"); + for (i = 0; i < show_values; i++) + sprintf(get_buffer_end(), + i == 0 ? "0x%02x" : ", 0x%02x", result->buffer.pointer[i]); + + if (result->buffer.length > show_values) { + // if data was truncated, show a trailing comma if there is space + snprintf(get_buffer_end(), get_avail_bytes(), ","); + return 1; + } else { + // in case show_values == 0, but the buffer is too small to hold + // more values (i.e. the buffer cannot have anything more than "{") + snprintf(get_buffer_end(), get_avail_bytes(), "}"); + } + } else if (result->type == ACPI_TYPE_PACKAGE) { + int i; + snprintf(get_buffer_end(), get_avail_bytes(), "["); + for (i=0; ipackage.count; i++) { + if (i > 0) + snprintf(get_buffer_end(), get_avail_bytes(), ", "); + + // abort if there is no more space available + if (!get_avail_bytes() || acpi_result_to_string(&result->package.elements[i])) + return 1; + } + snprintf(get_buffer_end(), get_avail_bytes(), "]"); + } else { + snprintf(get_buffer_end(), get_avail_bytes(), + "Object type 0x%x\n", result->type); + } + + // return 0 if there are still bytes available, 1 otherwise + return !get_avail_bytes(); +} + +/** +@param method The full name of ACPI method to call +@param argc The number of parameters +@param argv A pre-allocated array of arguments of type acpi_object +*/ +static void do_acpi_call(const char * method, int argc, union acpi_object *argv) +{ + acpi_status status; + acpi_handle handle; + struct acpi_object_list arg; + struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; + +#ifdef DEBUG + printk(KERN_INFO "acpi_call: Calling %s\n", method); +#endif + + // get the handle of the method, must be a fully qualified path + status = acpi_get_handle(NULL, (acpi_string) method, &handle); + + if (ACPI_FAILURE(status)) + { + snprintf(result_buffer, BUFFER_SIZE, "Error: %s", acpi_format_exception(status)); + printk(KERN_ERR "acpi_call: Cannot get handle: %s\n", result_buffer); + return; + } + + // prepare parameters + arg.count = argc; + arg.pointer = argv; + + // call the method + status = acpi_evaluate_object(handle, NULL, &arg, &buffer); + if (ACPI_FAILURE(status)) + { + snprintf(result_buffer, BUFFER_SIZE, "Error: %s", acpi_format_exception(status)); + printk(KERN_ERR "acpi_call: Method call failed: %s\n", result_buffer); + return; + } + + // reset the result buffer + *result_buffer = '\0'; + acpi_result_to_string(buffer.pointer); + kfree(buffer.pointer); + +#ifdef DEBUG + printk(KERN_INFO "acpi_call: Call successful: %s\n", result_buffer); +#endif +} + +/** Decodes 2 hex characters to an u8 int +*/ +u8 decodeHex(char *hex) { + char buf[3] = { hex[0], hex[1], 0}; + return (u8) simple_strtoul(buf, NULL, 16); +} + +/** Parses method name and arguments +@param input Input string to be parsed. Modified in the process. +@param nargs Set to number of arguments parsed (output) +@param args +*/ +static char *parse_acpi_args(char *input, int *nargs, union acpi_object **args) +{ + char *s = input; + int i; + + *nargs = 0; + *args = NULL; + + // the method name is separated from the arguments by a space + while (*s && *s != ' ') + s++; + // if no space is found, return 0 arguments + if (*s == 0) + return input; + + *args = (union acpi_object *) kmalloc(MAX_ACPI_ARGS * sizeof(union acpi_object), GFP_KERNEL); + if (!*args) { + printk(KERN_ERR "acpi_call: unable to allocate buffer\n"); + return NULL; + } + + while (*s) { + if (*s == ' ') { + if (*nargs == 0) + *s = 0; // change first space to nul + ++ *nargs; + ++ s; + } else { + union acpi_object *arg = (*args) + (*nargs - 1); + if (*s == '"') { + // decode string + arg->type = ACPI_TYPE_STRING; + arg->string.pointer = ++s; + arg->string.length = 0; + while (*s && *s++ != '"') + arg->string.length ++; + // skip the last " + if (*s == '"') + ++s; + } else if (*s == 'b') { + // decode buffer - bXXXX + char *p = ++s; + int len = 0, i; + u8 *buf = NULL; + + while (*p && *p!=' ') + p++; + + len = p - s; + if (len % 2 == 1) { + printk(KERN_ERR "acpi_call: buffer arg%d is not multiple of 8 bits\n", *nargs); + --*nargs; + goto err; + } + len /= 2; + + buf = (u8*) kmalloc(len, GFP_KERNEL); + if (!buf) { + printk(KERN_ERR "acpi_call: unable to allocate buffer\n"); + --*nargs; + goto err; + } + for (i=0; itype = ACPI_TYPE_BUFFER; + arg->buffer.pointer = buf; + arg->buffer.length = len; + } else if (*s == '{') { + // decode buffer - { b1, b2 ...} + u8 *buf = temporary_buffer; + arg->type = ACPI_TYPE_BUFFER; + arg->buffer.pointer = buf; + arg->buffer.length = 0; + while (*s && *s++ != '}') { + if (buf >= temporary_buffer + sizeof(temporary_buffer)) { + printk(KERN_ERR "acpi_call: buffer arg%d is truncated because the buffer is full\n", *nargs); + // clear remaining arguments + while (*s && *s != '}') + ++s; + break; + } + else if (*s >= '0' && *s <= '9') { + // decode integer into buffer + arg->buffer.length ++; + if (s[0] == '0' && s[1] == 'x') + *buf++ = simple_strtol(s+2, 0, 16); + else + *buf++ = simple_strtol(s, 0, 10); + } + // skip until space or comma or '}' + while (*s && *s != ' ' && *s != ',' && *s != '}') + ++s; + } + // store the result in new allocated buffer + buf = (u8*) kmalloc(arg->buffer.length, GFP_KERNEL); + if (!buf) { + printk(KERN_ERR "acpi_call: unable to allocate buffer\n"); + --*nargs; + goto err; + } + memcpy(buf, temporary_buffer, arg->buffer.length); + arg->buffer.pointer = buf; + } else { + // decode integer, N or 0xN + arg->type = ACPI_TYPE_INTEGER; + if (s[0] == '0' && s[1] == 'x') { + arg->integer.value = simple_strtol(s+2, 0, 16); + } else { + arg->integer.value = simple_strtol(s, 0, 10); + } + while (*s && *s != ' ') { + ++s; + } + } + } + } + + return input; + +err: + for (i=0; i<*nargs; i++) + if ((*args)[i].type == ACPI_TYPE_BUFFER && (*args)[i].buffer.pointer) + kfree((*args)[i].buffer.pointer); + kfree(*args); + return NULL; +} + +/** procfs write callback. Called when writing into /proc/acpi/call. +*/ +#ifdef HAVE_PROC_CREATE +static ssize_t acpi_proc_write( struct file *filp, const char __user *buff, + size_t len, loff_t *data ) +#else +static int acpi_proc_write( struct file *filp, const char __user *buff, + unsigned long len, void *data ) +#endif +{ + union acpi_object *args; + int nargs, i; + char *method; + + memset(input_buffer, 0, INPUT_BUFFER_SIZE); + if (len > sizeof(input_buffer) - 1) { +#ifdef HAVE_PROC_CREATE + printk(KERN_ERR "acpi_call: Input too long! (%zu)\n", len); +#else + printk(KERN_ERR "acpi_call: Input too long! (%lu)\n", len); +#endif + return -ENOSPC; + } + + if (copy_from_user( input_buffer, buff, len )) { + return -EFAULT; + } + input_buffer[len] = '\0'; + if (input_buffer[len-1] == '\n') + input_buffer[len-1] = '\0'; + + method = parse_acpi_args(input_buffer, &nargs, &args); + if (method) { + do_acpi_call(method, nargs, args); + if (args) { + for (i=0; i count) { + // user buffer is too small + ret = 0; + } else if(*off == len + 1) { + // we're done + ret = 0; + result_buffer[0] = '\0'; + } else { + // output the current result buffer + ret = simple_read_from_buffer(buff, count, off, result_buffer, len + 1); + *off = ret; + } + + return ret; +} + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0) +static struct proc_ops proc_acpi_operations = { + .proc_read = acpi_proc_read, + .proc_write = acpi_proc_write, +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 13, 0) + .proc_lseek = default_llseek, +#endif +}; +#else +static struct file_operations proc_acpi_operations = { + .owner = THIS_MODULE, + .read = acpi_proc_read, + .write = acpi_proc_write, +}; +#endif + +#else +static int acpi_proc_read(char *page, char **start, off_t off, + int count, int *eof, void *data) +{ + int len = 0; + + if (off > 0) { + *eof = 1; + return 0; + } + + // output the current result buffer + len = strlen(result_buffer); + memcpy(page, result_buffer, len + 1); + + // initialize the result buffer for later + strcpy(result_buffer, "not called"); + + return len; +} +#endif + +/** module initialization function */ +static int __init init_acpi_call(void) +{ +#ifdef HAVE_PROC_CREATE + struct proc_dir_entry *acpi_entry = proc_create("call", + 0660, + acpi_root_dir, + &proc_acpi_operations); +#else + struct proc_dir_entry *acpi_entry = create_proc_entry("call", 0660, acpi_root_dir); +#endif + + strcpy(result_buffer, "not called"); + + if (acpi_entry == NULL) { + printk(KERN_ERR "acpi_call: Couldn't create proc entry\n"); + return -ENOMEM; + } + +#ifndef HAVE_PROC_CREATE + acpi_entry->write_proc = acpi_proc_write; + acpi_entry->read_proc = acpi_proc_read; +#endif + +#ifdef DEBUG + printk(KERN_INFO "acpi_call: Module loaded successfully\n"); +#endif + + return 0; +} + +static void __exit unload_acpi_call(void) +{ + remove_proc_entry("call", acpi_root_dir); + +#ifdef DEBUG + printk(KERN_INFO "acpi_call: Module unloaded successfully\n"); +#endif +} + +module_init(init_acpi_call); +module_exit(unload_acpi_call); \ No newline at end of file -- 2.42.0