tools/power/acpi: Add userspace AML interface support
authorLv Zheng <lv.zheng@intel.com>
Thu, 3 Dec 2015 02:43:07 +0000 (10:43 +0800)
committerRafael J. Wysocki <rafael.j.wysocki@intel.com>
Mon, 14 Dec 2015 23:17:44 +0000 (00:17 +0100)
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>
tools/power/acpi/Makefile
tools/power/acpi/tools/acpidbg/Makefile [new file with mode: 0644]
tools/power/acpi/tools/acpidbg/acpidbg.c [new file with mode: 0644]

index e882c83..a8bf908 100644 (file)
 
 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 (file)
index 0000000..352df4b
--- /dev/null
@@ -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 (file)
index 0000000..d070fcc
--- /dev/null
@@ -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;
+}