// SPDX-License-Identifier: GPL-2.0-only /* * ACPI AML interfacing userspace utility * * Copyright (C) 2015, Intel Corporation * Authors: Lv Zheng <lv.zheng@intel.com> */ #include <acpi/acpi.h> /* Headers not included by include/acpi/platform/aclinux.h */ #include <unistd.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <error.h> #include <stdbool.h> #include <fcntl.h> #include <assert.h> #include <sys/select.h> #include "../../../../../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 = -1; 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; }