root/tools/power/acpi/tools/acpidbg/acpidbg.c
// 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;
}