diff options
author | Lv Zheng <lv.zheng@intel.com> | 2015-12-03 10:43:07 +0800 |
---|---|---|
committer | Rafael J. Wysocki <rafael.j.wysocki@intel.com> | 2015-12-15 00:17:44 +0100 |
commit | 37645d6590a49d3009eecdf093599795da2b5b41 (patch) | |
tree | 44b1101bec5dd1ceb4ad2754591448e93a2af4c9 /tools/power | |
parent | 8cfb0cdf07e2c260c4d1a102bfec35183907834f (diff) | |
download | lwn-37645d6590a49d3009eecdf093599795da2b5b41.tar.gz lwn-37645d6590a49d3009eecdf093599795da2b5b41.zip |
tools/power/acpi: Add userspace AML interface support
This patch adds a userspace tool to access Linux kernel AML debugger
interface.
Tow modes are supported by this tool:
1. Interactive: Users are able to launch a debugging shell to talk with
in-kernel AML debugger.
Note that it's user duty to ensure kernel runtime integrity by using
this debugging tool:
A. Some control methods evaluated by the users may result in kernel
panics if those control methods shouldn't be evaluated by the OSPMs
according to the current BIOS/OS configurations.
B. Currently if a single stepping evaluation couldn't run to an end,
then the synchronization primitives acquired by the evaluation may
block normal OSPM control method evaluations.
2. Batch: Users are able to execute debugger commands in a script.
Note that in addition to the above duties, it's user duty to ensure
script runtime integrity by using this debugging tool in this mode:
C. Currently only those commands that are not used for single stepping
are suitable to be used in this mode.
D. If the execution of the command may cause a failure that could result
in an endless kernel execution, the execution of the script may also
get blocked.
To exit the utility, currently "exit/quit" commands are recommended, but
ctrl-C" can also be used.
Signed-off-by: Lv Zheng <lv.zheng@intel.com>
Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
Diffstat (limited to 'tools/power')
-rw-r--r-- | tools/power/acpi/Makefile | 16 | ||||
-rw-r--r-- | tools/power/acpi/tools/acpidbg/Makefile | 27 | ||||
-rw-r--r-- | tools/power/acpi/tools/acpidbg/acpidbg.c | 438 |
3 files changed, 473 insertions, 8 deletions
diff --git a/tools/power/acpi/Makefile b/tools/power/acpi/Makefile index e882c8320135..a8bf9081512b 100644 --- a/tools/power/acpi/Makefile +++ b/tools/power/acpi/Makefile @@ -10,18 +10,18 @@ include ../../scripts/Makefile.include -all: acpidump ec -clean: acpidump_clean ec_clean -install: acpidump_install ec_install -uninstall: acpidump_uninstall ec_uninstall +all: acpidbg acpidump ec +clean: acpidbg_clean acpidump_clean ec_clean +install: acpidbg_install acpidump_install ec_install +uninstall: acpidbg_uninstall acpidump_uninstall ec_uninstall -acpidump ec: FORCE +acpidbg acpidump ec: FORCE $(call descend,tools/$@,all) -acpidump_clean ec_clean: +acpidbg_clean acpidump_clean ec_clean: $(call descend,tools/$(@:_clean=),clean) -acpidump_install ec_install: +acpidbg_install acpidump_install ec_install: $(call descend,tools/$(@:_install=),install) -acpidump_uninstall ec_uninstall: +acpidbg_uninstall acpidump_uninstall ec_uninstall: $(call descend,tools/$(@:_uninstall=),uninstall) .PHONY: FORCE diff --git a/tools/power/acpi/tools/acpidbg/Makefile b/tools/power/acpi/tools/acpidbg/Makefile new file mode 100644 index 000000000000..352df4b41ae9 --- /dev/null +++ b/tools/power/acpi/tools/acpidbg/Makefile @@ -0,0 +1,27 @@ +# tools/power/acpi/tools/acpidbg/Makefile - ACPI tool Makefile +# +# Copyright (c) 2015, Intel Corporation +# Author: Lv Zheng <lv.zheng@intel.com> +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2 +# of the License. + +include ../../Makefile.config + +TOOL = acpidbg +vpath %.c \ + ../../../../../drivers/acpi/acpica\ + ../../common\ + ../../os_specific/service_layers\ + . +CFLAGS += -DACPI_APPLICATION -DACPI_SINGLE_THREAD -DACPI_DEBUGGER\ + -I.\ + -I../../../../../drivers/acpi/acpica\ + -I../../../../../include +LDFLAGS += -lpthread +TOOL_OBJS = \ + acpidbg.o + +include ../../Makefile.rules diff --git a/tools/power/acpi/tools/acpidbg/acpidbg.c b/tools/power/acpi/tools/acpidbg/acpidbg.c new file mode 100644 index 000000000000..d070fccdba6d --- /dev/null +++ b/tools/power/acpi/tools/acpidbg/acpidbg.c @@ -0,0 +1,438 @@ +/* + * ACPI AML interfacing userspace utility + * + * Copyright (C) 2015, Intel Corporation + * Authors: Lv Zheng <lv.zheng@intel.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <acpi/acpi.h> + +/* Headers not included by include/acpi/platform/aclinux.h */ +#include <stdbool.h> +#include <fcntl.h> +#include <assert.h> +#include <linux/circ_buf.h> + +#define ACPI_AML_FILE "/sys/kernel/debug/acpi/acpidbg" +#define ACPI_AML_SEC_TICK 1 +#define ACPI_AML_USEC_PEEK 200 +#define ACPI_AML_BUF_SIZE 4096 + +#define ACPI_AML_BATCH_WRITE_CMD 0x00 /* Write command to kernel */ +#define ACPI_AML_BATCH_READ_LOG 0x01 /* Read log from kernel */ +#define ACPI_AML_BATCH_WRITE_LOG 0x02 /* Write log to console */ + +#define ACPI_AML_LOG_START 0x00 +#define ACPI_AML_PROMPT_START 0x01 +#define ACPI_AML_PROMPT_STOP 0x02 +#define ACPI_AML_LOG_STOP 0x03 +#define ACPI_AML_PROMPT_ROLL 0x04 + +#define ACPI_AML_INTERACTIVE 0x00 +#define ACPI_AML_BATCH 0x01 + +#define circ_count(circ) \ + (CIRC_CNT((circ)->head, (circ)->tail, ACPI_AML_BUF_SIZE)) +#define circ_count_to_end(circ) \ + (CIRC_CNT_TO_END((circ)->head, (circ)->tail, ACPI_AML_BUF_SIZE)) +#define circ_space(circ) \ + (CIRC_SPACE((circ)->head, (circ)->tail, ACPI_AML_BUF_SIZE)) +#define circ_space_to_end(circ) \ + (CIRC_SPACE_TO_END((circ)->head, (circ)->tail, ACPI_AML_BUF_SIZE)) + +#define acpi_aml_cmd_count() circ_count(&acpi_aml_cmd_crc) +#define acpi_aml_log_count() circ_count(&acpi_aml_log_crc) +#define acpi_aml_cmd_space() circ_space(&acpi_aml_cmd_crc) +#define acpi_aml_log_space() circ_space(&acpi_aml_log_crc) + +#define ACPI_AML_DO(_fd, _op, _buf, _ret) \ + do { \ + _ret = acpi_aml_##_op(_fd, &acpi_aml_##_buf##_crc); \ + if (_ret == 0) { \ + fprintf(stderr, \ + "%s %s pipe closed.\n", #_buf, #_op); \ + return; \ + } \ + } while (0) +#define ACPI_AML_BATCH_DO(_fd, _op, _buf, _ret) \ + do { \ + _ret = acpi_aml_##_op##_batch_##_buf(_fd, \ + &acpi_aml_##_buf##_crc); \ + if (_ret == 0) \ + return; \ + } while (0) + + +static char acpi_aml_cmd_buf[ACPI_AML_BUF_SIZE]; +static char acpi_aml_log_buf[ACPI_AML_BUF_SIZE]; +static struct circ_buf acpi_aml_cmd_crc = { + .buf = acpi_aml_cmd_buf, + .head = 0, + .tail = 0, +}; +static struct circ_buf acpi_aml_log_crc = { + .buf = acpi_aml_log_buf, + .head = 0, + .tail = 0, +}; +static const char *acpi_aml_file_path = ACPI_AML_FILE; +static unsigned long acpi_aml_mode = ACPI_AML_INTERACTIVE; +static bool acpi_aml_exit; + +static bool acpi_aml_batch_drain; +static unsigned long acpi_aml_batch_state; +static char acpi_aml_batch_prompt; +static char acpi_aml_batch_roll; +static unsigned long acpi_aml_log_state; +static char *acpi_aml_batch_cmd = NULL; +static char *acpi_aml_batch_pos = NULL; + +static int acpi_aml_set_fl(int fd, int flags) +{ + int ret; + + ret = fcntl(fd, F_GETFL, 0); + if (ret < 0) { + perror("fcntl(F_GETFL)"); + return ret; + } + flags |= ret; + ret = fcntl(fd, F_SETFL, flags); + if (ret < 0) { + perror("fcntl(F_SETFL)"); + return ret; + } + return ret; +} + +static int acpi_aml_set_fd(int fd, int maxfd, fd_set *set) +{ + if (fd > maxfd) + maxfd = fd; + FD_SET(fd, set); + return maxfd; +} + +static int acpi_aml_read(int fd, struct circ_buf *crc) +{ + char *p; + int len; + + p = &crc->buf[crc->head]; + len = circ_space_to_end(crc); + len = read(fd, p, len); + if (len < 0) + perror("read"); + else if (len > 0) + crc->head = (crc->head + len) & (ACPI_AML_BUF_SIZE - 1); + return len; +} + +static int acpi_aml_read_batch_cmd(int unused, struct circ_buf *crc) +{ + char *p; + int len; + int remained = strlen(acpi_aml_batch_pos); + + p = &crc->buf[crc->head]; + len = circ_space_to_end(crc); + if (len > remained) { + memcpy(p, acpi_aml_batch_pos, remained); + acpi_aml_batch_pos += remained; + len = remained; + } else { + memcpy(p, acpi_aml_batch_pos, len); + acpi_aml_batch_pos += len; + } + if (len > 0) + crc->head = (crc->head + len) & (ACPI_AML_BUF_SIZE - 1); + return len; +} + +static int acpi_aml_read_batch_log(int fd, struct circ_buf *crc) +{ + char *p; + int len; + int ret = 0; + + p = &crc->buf[crc->head]; + len = circ_space_to_end(crc); + while (ret < len && acpi_aml_log_state != ACPI_AML_LOG_STOP) { + if (acpi_aml_log_state == ACPI_AML_PROMPT_ROLL) { + *p = acpi_aml_batch_roll; + len = 1; + crc->head = (crc->head + 1) & (ACPI_AML_BUF_SIZE - 1); + ret += 1; + acpi_aml_log_state = ACPI_AML_LOG_START; + } else { + len = read(fd, p, 1); + if (len <= 0) { + if (len < 0) + perror("read"); + ret = len; + break; + } + } + switch (acpi_aml_log_state) { + case ACPI_AML_LOG_START: + if (*p == '\n') + acpi_aml_log_state = ACPI_AML_PROMPT_START; + crc->head = (crc->head + 1) & (ACPI_AML_BUF_SIZE - 1); + ret += 1; + break; + case ACPI_AML_PROMPT_START: + if (*p == ACPI_DEBUGGER_COMMAND_PROMPT || + *p == ACPI_DEBUGGER_EXECUTE_PROMPT) { + acpi_aml_batch_prompt = *p; + acpi_aml_log_state = ACPI_AML_PROMPT_STOP; + } else { + if (*p != '\n') + acpi_aml_log_state = ACPI_AML_LOG_START; + crc->head = (crc->head + 1) & (ACPI_AML_BUF_SIZE - 1); + ret += 1; + } + break; + case ACPI_AML_PROMPT_STOP: + if (*p == ' ') { + acpi_aml_log_state = ACPI_AML_LOG_STOP; + acpi_aml_exit = true; + } else { + /* Roll back */ + acpi_aml_log_state = ACPI_AML_PROMPT_ROLL; + acpi_aml_batch_roll = *p; + *p = acpi_aml_batch_prompt; + crc->head = (crc->head + 1) & (ACPI_AML_BUF_SIZE - 1); + ret += 1; + } + break; + default: + assert(0); + break; + } + } + return ret; +} + +static int acpi_aml_write(int fd, struct circ_buf *crc) +{ + char *p; + int len; + + p = &crc->buf[crc->tail]; + len = circ_count_to_end(crc); + len = write(fd, p, len); + if (len < 0) + perror("write"); + else if (len > 0) + crc->tail = (crc->tail + len) & (ACPI_AML_BUF_SIZE - 1); + return len; +} + +static int acpi_aml_write_batch_log(int fd, struct circ_buf *crc) +{ + char *p; + int len; + + p = &crc->buf[crc->tail]; + len = circ_count_to_end(crc); + if (!acpi_aml_batch_drain) { + len = write(fd, p, len); + if (len < 0) + perror("write"); + } + if (len > 0) + crc->tail = (crc->tail + len) & (ACPI_AML_BUF_SIZE - 1); + return len; +} + +static int acpi_aml_write_batch_cmd(int fd, struct circ_buf *crc) +{ + int len; + + len = acpi_aml_write(fd, crc); + if (circ_count_to_end(crc) == 0) + acpi_aml_batch_state = ACPI_AML_BATCH_READ_LOG; + return len; +} + +static void acpi_aml_loop(int fd) +{ + fd_set rfds; + fd_set wfds; + struct timeval tv; + int ret; + int maxfd = 0; + + if (acpi_aml_mode == ACPI_AML_BATCH) { + acpi_aml_log_state = ACPI_AML_LOG_START; + acpi_aml_batch_pos = acpi_aml_batch_cmd; + if (acpi_aml_batch_drain) + acpi_aml_batch_state = ACPI_AML_BATCH_READ_LOG; + else + acpi_aml_batch_state = ACPI_AML_BATCH_WRITE_CMD; + } + acpi_aml_exit = false; + while (!acpi_aml_exit) { + tv.tv_sec = ACPI_AML_SEC_TICK; + tv.tv_usec = 0; + FD_ZERO(&rfds); + FD_ZERO(&wfds); + + if (acpi_aml_cmd_space()) { + if (acpi_aml_mode == ACPI_AML_INTERACTIVE) + maxfd = acpi_aml_set_fd(STDIN_FILENO, maxfd, &rfds); + else if (strlen(acpi_aml_batch_pos) && + acpi_aml_batch_state == ACPI_AML_BATCH_WRITE_CMD) + ACPI_AML_BATCH_DO(STDIN_FILENO, read, cmd, ret); + } + if (acpi_aml_cmd_count() && + (acpi_aml_mode == ACPI_AML_INTERACTIVE || + acpi_aml_batch_state == ACPI_AML_BATCH_WRITE_CMD)) + maxfd = acpi_aml_set_fd(fd, maxfd, &wfds); + if (acpi_aml_log_space() && + (acpi_aml_mode == ACPI_AML_INTERACTIVE || + acpi_aml_batch_state == ACPI_AML_BATCH_READ_LOG)) + maxfd = acpi_aml_set_fd(fd, maxfd, &rfds); + if (acpi_aml_log_count()) + maxfd = acpi_aml_set_fd(STDOUT_FILENO, maxfd, &wfds); + + ret = select(maxfd+1, &rfds, &wfds, NULL, &tv); + if (ret < 0) { + perror("select"); + break; + } + if (ret > 0) { + if (FD_ISSET(STDIN_FILENO, &rfds)) + ACPI_AML_DO(STDIN_FILENO, read, cmd, ret); + if (FD_ISSET(fd, &wfds)) { + if (acpi_aml_mode == ACPI_AML_BATCH) + ACPI_AML_BATCH_DO(fd, write, cmd, ret); + else + ACPI_AML_DO(fd, write, cmd, ret); + } + if (FD_ISSET(fd, &rfds)) { + if (acpi_aml_mode == ACPI_AML_BATCH) + ACPI_AML_BATCH_DO(fd, read, log, ret); + else + ACPI_AML_DO(fd, read, log, ret); + } + if (FD_ISSET(STDOUT_FILENO, &wfds)) { + if (acpi_aml_mode == ACPI_AML_BATCH) + ACPI_AML_BATCH_DO(STDOUT_FILENO, write, log, ret); + else + ACPI_AML_DO(STDOUT_FILENO, write, log, ret); + } + } + } +} + +static bool acpi_aml_readable(int fd) +{ + fd_set rfds; + struct timeval tv; + int ret; + int maxfd = 0; + + tv.tv_sec = 0; + tv.tv_usec = ACPI_AML_USEC_PEEK; + FD_ZERO(&rfds); + maxfd = acpi_aml_set_fd(fd, maxfd, &rfds); + ret = select(maxfd+1, &rfds, NULL, NULL, &tv); + if (ret < 0) + perror("select"); + if (ret > 0 && FD_ISSET(fd, &rfds)) + return true; + return false; +} + +/* + * This is a userspace IO flush implementation, replying on the prompt + * characters and can be turned into a flush() call after kernel implements + * .flush() filesystem operation. + */ +static void acpi_aml_flush(int fd) +{ + while (acpi_aml_readable(fd)) { + acpi_aml_batch_drain = true; + acpi_aml_loop(fd); + acpi_aml_batch_drain = false; + } +} + +void usage(FILE *file, char *progname) +{ + fprintf(file, "usage: %s [-b cmd] [-f file] [-h]\n", progname); + fprintf(file, "\nOptions:\n"); + fprintf(file, " -b Specify command to be executed in batch mode\n"); + fprintf(file, " -f Specify interface file other than"); + fprintf(file, " /sys/kernel/debug/acpi/acpidbg\n"); + fprintf(file, " -h Print this help message\n"); +} + +int main(int argc, char **argv) +{ + int fd = 0; + int ch; + int len; + int ret = EXIT_SUCCESS; + + while ((ch = getopt(argc, argv, "b:f:h")) != -1) { + switch (ch) { + case 'b': + if (acpi_aml_batch_cmd) { + fprintf(stderr, "Already specify %s\n", + acpi_aml_batch_cmd); + ret = EXIT_FAILURE; + goto exit; + } + len = strlen(optarg); + acpi_aml_batch_cmd = calloc(len + 2, 1); + if (!acpi_aml_batch_cmd) { + perror("calloc"); + ret = EXIT_FAILURE; + goto exit; + } + memcpy(acpi_aml_batch_cmd, optarg, len); + acpi_aml_batch_cmd[len] = '\n'; + acpi_aml_mode = ACPI_AML_BATCH; + break; + case 'f': + acpi_aml_file_path = optarg; + break; + case 'h': + usage(stdout, argv[0]); + goto exit; + break; + case '?': + default: + usage(stderr, argv[0]); + ret = EXIT_FAILURE; + goto exit; + break; + } + } + + fd = open(acpi_aml_file_path, O_RDWR | O_NONBLOCK); + if (fd < 0) { + perror("open"); + ret = EXIT_FAILURE; + goto exit; + } + acpi_aml_set_fl(STDIN_FILENO, O_NONBLOCK); + acpi_aml_set_fl(STDOUT_FILENO, O_NONBLOCK); + + if (acpi_aml_mode == ACPI_AML_BATCH) + acpi_aml_flush(fd); + acpi_aml_loop(fd); + +exit: + if (fd < 0) + close(fd); + if (acpi_aml_batch_cmd) + free(acpi_aml_batch_cmd); + return ret; +} |