root/tools/power/acpi/tools/ec/ec_access.c
// SPDX-License-Identifier: GPL-2.0-only
/*
 * ec_access.c
 *
 * Copyright (C) 2010 SUSE Linux Products GmbH
 * Author:
 *      Thomas Renninger <trenn@suse.de>
 */

#include <fcntl.h>
#include <err.h>
#include <stdio.h>
#include <stdlib.h>
#include <libgen.h>
#include <unistd.h>
#include <getopt.h>
#include <stdint.h>
#include <sys/types.h>
#include <sys/stat.h>


#define EC_SPACE_SIZE 256
#define SYSFS_PATH "/sys/kernel/debug/ec/ec0/io"

/* TBD/Enhancements:
   - Provide param for accessing different ECs (not supported by kernel yet)
*/

static int read_mode = -1;
static int sleep_time;
static int write_byte_offset = -1;
static int read_byte_offset = -1;
static uint8_t write_value = -1;

void usage(char progname[], int exit_status)
{
        printf("Usage:\n");
        printf("1) %s -r [-s sleep]\n", basename(progname));
        printf("2) %s -b byte_offset\n", basename(progname));
        printf("3) %s -w byte_offset -v value\n\n", basename(progname));

        puts("\t-r [-s sleep]      : Dump EC registers");
        puts("\t                     If sleep is given, sleep x seconds,");
        puts("\t                     re-read EC registers and show changes");
        puts("\t-b offset          : Read value at byte_offset (in hex)");
        puts("\t-w offset -v value : Write value at byte_offset");
        puts("\t-h                 : Print this help\n\n");
        puts("Offsets and values are in hexadecimal number system.");
        puts("The offset and value must be between 0 and 0xff.");
        exit(exit_status);
}

void parse_opts(int argc, char *argv[])
{
        int c;

        while ((c = getopt(argc, argv, "rs:b:w:v:h")) != -1) {

                switch (c) {
                case 'r':
                        if (read_mode != -1)
                                usage(argv[0], EXIT_FAILURE);
                        read_mode = 1;
                        break;
                case 's':
                        if (read_mode != -1 && read_mode != 1)
                                usage(argv[0], EXIT_FAILURE);

                        sleep_time = atoi(optarg);
                        if (sleep_time <= 0) {
                                sleep_time = 0;
                                usage(argv[0], EXIT_FAILURE);
                                printf("Bad sleep time: %s\n", optarg);
                        }
                        break;
                case 'b':
                        if (read_mode != -1)
                                usage(argv[0], EXIT_FAILURE);
                        read_mode = 1;
                        read_byte_offset = strtoul(optarg, NULL, 16);
                        break;
                case 'w':
                        if (read_mode != -1)
                                usage(argv[0], EXIT_FAILURE);
                        read_mode = 0;
                        write_byte_offset = strtoul(optarg, NULL, 16);
                        break;
                case 'v':
                        write_value = strtoul(optarg, NULL, 16);
                        break;
                case 'h':
                        usage(argv[0], EXIT_SUCCESS);
                default:
                        fprintf(stderr, "Unknown option!\n");
                        usage(argv[0], EXIT_FAILURE);
                }
        }
        if (read_mode == 0) {
                if (write_byte_offset < 0 ||
                    write_byte_offset >= EC_SPACE_SIZE) {
                        fprintf(stderr, "Wrong byte offset 0x%.2x, valid: "
                                "[0-0x%.2x]\n",
                                write_byte_offset, EC_SPACE_SIZE - 1);
                        usage(argv[0], EXIT_FAILURE);
                }
                if (write_value < 0 ||
                    write_value >= 255) {
                        fprintf(stderr, "Wrong byte offset 0x%.2x, valid:"
                                "[0-0xff]\n", write_byte_offset);
                        usage(argv[0], EXIT_FAILURE);
                }
        }
        if (read_mode == 1 && read_byte_offset != -1) {
                if (read_byte_offset < -1 ||
                    read_byte_offset >= EC_SPACE_SIZE) {
                        fprintf(stderr, "Wrong byte offset 0x%.2x, valid: "
                                "[0-0x%.2x]\n",
                                read_byte_offset, EC_SPACE_SIZE - 1);
                        usage(argv[0], EXIT_FAILURE);
                }
        }
        /* Add additional parameter checks here */
}

void dump_ec(int fd)
{
        char buf[EC_SPACE_SIZE];
        char buf2[EC_SPACE_SIZE];
        int byte_off, bytes_read;

        bytes_read = read(fd, buf, EC_SPACE_SIZE);

        if (bytes_read == -1)
                err(EXIT_FAILURE, "Could not read from %s\n", SYSFS_PATH);

        if (bytes_read != EC_SPACE_SIZE)
                fprintf(stderr, "Could only read %d bytes\n", bytes_read);

        printf("     00  01  02  03  04  05  06  07  08  09  0A  0B  0C  0D  0E  0F");
        for (byte_off = 0; byte_off < bytes_read; byte_off++) {
                if ((byte_off % 16) == 0)
                        printf("\n%.2X: ", byte_off);
                printf(" %.2x ", (uint8_t)buf[byte_off]);
        }
        printf("\n");

        if (!sleep_time)
                return;

        printf("\n");
        lseek(fd, 0, SEEK_SET);
        sleep(sleep_time);

        bytes_read = read(fd, buf2, EC_SPACE_SIZE);

        if (bytes_read == -1)
                err(EXIT_FAILURE, "Could not read from %s\n", SYSFS_PATH);

        if (bytes_read != EC_SPACE_SIZE)
                fprintf(stderr, "Could only read %d bytes\n", bytes_read);

        printf("     00  01  02  03  04  05  06  07  08  09  0A  0B  0C  0D  0E  0F");
        for (byte_off = 0; byte_off < bytes_read; byte_off++) {
                if ((byte_off % 16) == 0)
                        printf("\n%.2X: ", byte_off);

                if (buf[byte_off] == buf2[byte_off])
                        printf(" %.2x ", (uint8_t)buf2[byte_off]);
                else
                        printf("*%.2x ", (uint8_t)buf2[byte_off]);
        }
        printf("\n");
}

void read_ec_val(int fd, int byte_offset)
{
        uint8_t buf;
        int error;

        error = lseek(fd, byte_offset, SEEK_SET);
        if (error != byte_offset)
                err(EXIT_FAILURE, "Cannot set offset to 0x%.2x", byte_offset);

        error = read(fd, &buf, 1);
        if (error != 1)
                err(EXIT_FAILURE, "Could not read byte 0x%.2x from %s\n",
                    byte_offset, SYSFS_PATH);
        printf("0x%.2x\n", buf);
        return;
}

void write_ec_val(int fd, int byte_offset, uint8_t value)
{
        int error;

        error = lseek(fd, byte_offset, SEEK_SET);
        if (error != byte_offset)
                err(EXIT_FAILURE, "Cannot set offset to 0x%.2x", byte_offset);

        error = write(fd, &value, 1);
        if (error != 1)
                err(EXIT_FAILURE, "Cannot write value 0x%.2x to offset 0x%.2x",
                    value, byte_offset);
}

int main(int argc, char *argv[])
{
        int file_mode = O_RDONLY;
        int fd;

        parse_opts(argc, argv);

        if (read_mode == 0)
                file_mode = O_WRONLY;
        else if (read_mode == 1)
                file_mode = O_RDONLY;
        else
                usage(argv[0], EXIT_FAILURE);

        fd = open(SYSFS_PATH, file_mode);
        if (fd == -1)
                err(EXIT_FAILURE, "%s", SYSFS_PATH);

        if (read_mode)
                if (read_byte_offset == -1)
                        dump_ec(fd);
                else if (read_byte_offset < 0 ||
                         read_byte_offset >= EC_SPACE_SIZE)
                        usage(argv[0], EXIT_FAILURE);
                else
                        read_ec_val(fd, read_byte_offset);
        else
                write_ec_val(fd, write_byte_offset, write_value);
        close(fd);

        exit(EXIT_SUCCESS);
}