diff options
author | Jason A. Donenfeld <Jason@zx2c4.com> | 2020-05-03 23:10:29 -0600 |
---|---|---|
committer | Jason A. Donenfeld <Jason@zx2c4.com> | 2024-03-12 22:23:13 -0600 |
commit | 8f285b98e385d912f60cb4e7ab94e5492f37c963 (patch) | |
tree | d018017f71158ee899bb4da5f9dd458ae5c20f2d | |
parent | Linux 6.8 (diff) | |
download | laptop-kernel-8f285b98e385d912f60cb4e7ab94e5492f37c963.tar.xz laptop-kernel-8f285b98e385d912f60cb4e7ab94e5492f37c963.zip |
acpi: import acpi_call driver
This code, from <https://github.com/mkottman/acpi_call>, is garbage, but
it's still quite useful, so import it here in a somewhat sane way.
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
-rw-r--r-- | drivers/acpi/Kconfig | 5 | ||||
-rw-r--r-- | drivers/acpi/Makefile | 2 | ||||
-rw-r--r-- | drivers/acpi/acpi_call.c | 363 |
3 files changed, 370 insertions, 0 deletions
diff --git a/drivers/acpi/Kconfig b/drivers/acpi/Kconfig index 3c3f8037ebed..169b93d85f83 100644 --- a/drivers/acpi/Kconfig +++ b/drivers/acpi/Kconfig @@ -74,6 +74,11 @@ config ACPI_DEBUGGER This is still under development, currently enabling this only results in the compilation of the ACPICA debugger files. +config ACPI_CALL + tristate "Raw ACPI call interface" + help + Enable /proc/acpi/call interface for making raw ACPI calls. + if ACPI_DEBUGGER config ACPI_DEBUGGER_USER diff --git a/drivers/acpi/Makefile b/drivers/acpi/Makefile index 12ef8180d272..f86bd2c10d4a 100644 --- a/drivers/acpi/Makefile +++ b/drivers/acpi/Makefile @@ -32,6 +32,8 @@ acpi-$(CONFIG_ACPI_SYSTEM_POWER_STATES_SUPPORT) += sleep.o acpi-y += device_sysfs.o device_pm.o acpi-$(CONFIG_ACPI_SLEEP) += proc.o +obj-$(CONFIG_ACPI_CALL) += acpi_call.o + # # ACPI Bus and Device Drivers diff --git a/drivers/acpi/acpi_call.c b/drivers/acpi/acpi_call.c new file mode 100644 index 000000000000..def7b8c785d7 --- /dev/null +++ b/drivers/acpi/acpi_call.c @@ -0,0 +1,363 @@ +/* + * Copyright (c) 2010: Michal Kottman + * Copyright (C) 2020 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved. + */ + +#define BUILDING_ACPICA + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/version.h> +#include <linux/proc_fs.h> +#include <linux/slab.h> +#include <linux/uaccess.h> +#include <linux/acpi.h> + + +#define BUFFER_SIZE 256 +#define MAX_ACPI_ARGS 16 + +extern struct proc_dir_entry *acpi_root_dir; + +static char result_buffer[BUFFER_SIZE]; + +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; i<result->package.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 }; + + pr_debug("acpi_call: Calling %s\n", method); + + // 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); + + pr_debug("acpi_call: Call successful: %s\n", result_buffer); +} + +/** Decodes 2 hex characters to an u8 int +*/ +static 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; i<len; i++) { + buf[i] = decodeHex(s + i*2); + } + s = p; + + arg->type = 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. +*/ +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; + + if (len > sizeof(input) - 1) { + printk(KERN_ERR "acpi_call: 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'; + + method = parse_acpi_args(input, &nargs, &args); + if (method) { + do_acpi_call(method, nargs, args); + if (args) { + for (i=0; i<nargs; i++) + if (args[i].type == ACPI_TYPE_BUFFER) + kfree(args[i].buffer.pointer); + kfree(args); + } + } + + return len; +} + +/** procfs 'call' read callback. Called when reading the content of /proc/acpi/call. +Returns the last call status: +- "not called" when no call was previously issued +- "failed" if the call failed +- "ok" if the call succeeded +*/ +static ssize_t acpi_proc_read( struct file *filp, char __user *buff, + size_t count, loff_t *off ) +{ + ssize_t ret; + int len = strlen(result_buffer); + + // output the current result buffer + ret = simple_read_from_buffer(buff, count, off, result_buffer, len + 1); + + // initialize the result buffer for later + strcpy(result_buffer, "not called"); + + return ret; +} + +static struct proc_ops proc_acpi_operations = { + .proc_read = acpi_proc_read, + .proc_write = acpi_proc_write, +}; + +/** module initialization function */ +static int __init init_acpi_call(void) +{ + struct proc_dir_entry *acpi_entry = proc_create("call", + 0660, + acpi_root_dir, + &proc_acpi_operations); + + strcpy(result_buffer, "not called"); + + if (acpi_entry == NULL) { + printk(KERN_ERR "acpi_call: Couldn't create proc entry\n"); + return -ENOMEM; + } + + pr_debug("acpi_call: Module loaded successfully\n"); + + return 0; +} + +static void __exit unload_acpi_call(void) +{ + remove_proc_entry("call", acpi_root_dir); + + pr_debug("acpi_call: Module unloaded successfully\n"); +} + +module_init(init_acpi_call); +module_exit(unload_acpi_call); +MODULE_LICENSE("GPL"); |