root/tools/power/acpi/tools/pfrut/pfrut.c
// SPDX-License-Identifier: GPL-2.0
/*
 * Platform Firmware Runtime Update tool to do Management
 * Mode code injection/driver update and telemetry retrieval.
 *
 * This tool uses the interfaces provided by pfr_update and
 * pfr_telemetry drivers. These interfaces are exposed via
 * /dev/pfr_update and /dev/pfr_telemetry. Write operation
 * on the /dev/pfr_update is to load the EFI capsule into
 * kernel space. Mmap/read operations on /dev/pfr_telemetry
 * could be used to read the telemetry data to user space.
 */
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <getopt.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <uuid/uuid.h>
#include PFRUT_HEADER

char *capsule_name;
int action, query_cap, log_type, log_level, log_read, log_getinfo,
        revid, log_revid;
int set_log_level, set_log_type,
        set_revid, set_log_revid;

char *progname;

#define LOG_ERR         0
#define LOG_WARN        1
#define LOG_INFO        2
#define LOG_VERB        4
#define LOG_EXEC_IDX    0
#define LOG_HISTORY_IDX 1
#define REVID_1         1
#define REVID_2         2

static int valid_log_level(int level)
{
        return level == LOG_ERR || level == LOG_WARN ||
               level == LOG_INFO || level == LOG_VERB;
}

static int valid_log_type(int type)
{
        return type == LOG_EXEC_IDX || type == LOG_HISTORY_IDX;
}

static inline int valid_log_revid(int id)
{
        return id == REVID_1 || id == REVID_2;
}

static void help(void)
{
        fprintf(stderr,
                "usage: %s [OPTIONS]\n"
                " code injection:\n"
                "  -l, --load\n"
                "  -s, --stage\n"
                "  -a, --activate\n"
                "  -u, --update [stage and activate]\n"
                "  -q, --query\n"
                "  -d, --revid update\n"
                " telemetry:\n"
                "  -G, --getloginfo\n"
                "  -T, --type(0:execution, 1:history)\n"
                "  -L, --level(0, 1, 2, 4)\n"
                "  -R, --read\n"
                "  -D, --revid log\n",
                progname);
}

char *option_string = "l:sauqd:GT:L:RD:h";
static struct option long_options[] = {
        {"load", required_argument, 0, 'l'},
        {"stage", no_argument, 0, 's'},
        {"activate", no_argument, 0, 'a'},
        {"update", no_argument, 0, 'u'},
        {"query", no_argument, 0, 'q'},
        {"getloginfo", no_argument, 0, 'G'},
        {"type", required_argument, 0, 'T'},
        {"level", required_argument, 0, 'L'},
        {"read", no_argument, 0, 'R'},
        {"setrev", required_argument, 0, 'd'},
        {"setrevlog", required_argument, 0, 'D'},
        {"help", no_argument, 0, 'h'},
        {}
};

static void parse_options(int argc, char **argv)
{
        int option_index = 0;
        char *pathname, *endptr;
        int opt;

        pathname = strdup(argv[0]);
        progname = basename(pathname);

        while ((opt = getopt_long_only(argc, argv, option_string,
                                       long_options, &option_index)) != -1) {
                switch (opt) {
                case 'l':
                        capsule_name = optarg;
                        break;
                case 's':
                        action = 1;
                        break;
                case 'a':
                        action = 2;
                        break;
                case 'u':
                        action = 3;
                        break;
                case 'q':
                        query_cap = 1;
                        break;
                case 'G':
                        log_getinfo = 1;
                        break;
                case 'T':
                        log_type = strtol(optarg, &endptr, 0);
                        if (*endptr || (log_type != 0 && log_type != 1)) {
                                printf("Number expected: type(0:execution, 1:history) - Quit.\n");
                                exit(1);
                        }

                        set_log_type = 1;
                        break;
                case 'L':
                        log_level = strtol(optarg, &endptr, 0);
                        if (*endptr ||
                            (log_level != 0 && log_level != 1 &&
                             log_level != 2 && log_level != 4)) {
                                printf("Number expected: level(0, 1, 2, 4) - Quit.\n");
                                exit(1);
                        }

                        set_log_level = 1;
                        break;
                case 'R':
                        log_read = 1;
                        break;
                case 'd':
                        revid = atoi(optarg);
                        set_revid = 1;
                        break;
                case 'D':
                        log_revid = atoi(optarg);
                        set_log_revid = 1;
                        break;
                case 'h':
                        help();
                        exit(0);
                default:
                        break;
                }
        }
}

void print_cap(struct pfru_update_cap_info *cap)
{
        char *uuid;

        uuid = malloc(37);
        if (!uuid) {
                perror("Can not allocate uuid buffer\n");
                exit(1);
        }

        printf("update capability:%d\n", cap->update_cap);

        uuid_unparse(cap->code_type, uuid);
        printf("code injection image type:%s\n", uuid);
        printf("fw_version:%d\n", cap->fw_version);
        printf("code_rt_version:%d\n", cap->code_rt_version);

        uuid_unparse(cap->drv_type, uuid);
        printf("driver update image type:%s\n", uuid);
        printf("drv_rt_version:%d\n", cap->drv_rt_version);
        printf("drv_svn:%d\n", cap->drv_svn);

        uuid_unparse(cap->platform_id, uuid);
        printf("platform id:%s\n", uuid);
        uuid_unparse(cap->oem_id, uuid);
        printf("oem id:%s\n", uuid);
        printf("oem information length:%d\n", cap->oem_info_len);

        free(uuid);
}

int main(int argc, char *argv[])
{
        int fd_update, fd_update_log, fd_capsule;
        struct pfrt_log_data_info data_info;
        struct pfrt_log_info info;
        struct pfru_update_cap_info cap;
        void *addr_map_capsule;
        struct stat st;
        char *log_buf;
        int ret;

        if (getuid() != 0) {
                printf("Please run the tool as root - Exiting.\n");
                return 1;
        }

        parse_options(argc, argv);

        fd_update = open("/dev/acpi_pfr_update0", O_RDWR);
        if (fd_update < 0) {
                printf("PFRU device not supported - Quit...\n");
                return 1;
        }

        fd_update_log = open("/dev/acpi_pfr_telemetry0", O_RDWR);
        if (fd_update_log < 0) {
                printf("PFRT device not supported - Quit...\n");
                close(fd_update);
                return 1;
        }

        if (query_cap) {
                ret = ioctl(fd_update, PFRU_IOC_QUERY_CAP, &cap);
                if (ret)
                        perror("Query Update Capability info failed.");
                else
                        print_cap(&cap);

                close(fd_update);
                close(fd_update_log);

                return ret;
        }

        if (log_getinfo) {
                ret = ioctl(fd_update_log, PFRT_LOG_IOC_GET_DATA_INFO, &data_info);
                if (ret) {
                        perror("Get telemetry data info failed.");
                        close(fd_update);
                        close(fd_update_log);

                        return 1;
                }

                ret = ioctl(fd_update_log, PFRT_LOG_IOC_GET_INFO, &info);
                if (ret) {
                        perror("Get telemetry info failed.");
                        close(fd_update);
                        close(fd_update_log);

                        return 1;
                }

                printf("log_level:%d\n", info.log_level);
                printf("log_type:%d\n", info.log_type);
                printf("log_revid:%d\n", info.log_revid);
                printf("max_data_size:%d\n", data_info.max_data_size);
                printf("chunk1_size:%d\n", data_info.chunk1_size);
                printf("chunk2_size:%d\n", data_info.chunk2_size);
                printf("rollover_cnt:%d\n", data_info.rollover_cnt);
                printf("reset_cnt:%d\n", data_info.reset_cnt);
                close(fd_update);
                close(fd_update_log);
                return 0;
        }

        info.log_level = -1;
        info.log_type = -1;
        info.log_revid = -1;

        if (set_log_level) {
                if (!valid_log_level(log_level)) {
                        printf("Invalid log level %d\n",
                               log_level);
                } else {
                        info.log_level = log_level;
                }
        }

        if (set_log_type) {
                if (!valid_log_type(log_type)) {
                        printf("Invalid log type %d\n",
                               log_type);
                } else {
                        info.log_type = log_type;
                }
        }

        if (set_log_revid) {
                if (!valid_log_revid(log_revid)) {
                        printf("Invalid log revid %d, unchanged.\n",
                               log_revid);
                } else {
                        info.log_revid = log_revid;
                }
        }

        ret = ioctl(fd_update_log, PFRT_LOG_IOC_SET_INFO, &info);
        if (ret) {
                perror("Log information set failed.(log_level, log_type, log_revid)");
                close(fd_update);
                close(fd_update_log);

                return 1;
        }

        if (set_revid) {
                ret = ioctl(fd_update, PFRU_IOC_SET_REV, &revid);
                if (ret) {
                        perror("pfru update revid set failed");
                        close(fd_update);
                        close(fd_update_log);

                        return 1;
                }

                printf("pfru update revid set to %d\n", revid);
        }

        if (capsule_name) {
                fd_capsule = open(capsule_name, O_RDONLY);
                if (fd_capsule < 0) {
                        perror("Can not open capsule file...");
                        close(fd_update);
                        close(fd_update_log);

                        return 1;
                }

                if (fstat(fd_capsule, &st) < 0) {
                        perror("Can not fstat capsule file...");
                        close(fd_capsule);
                        close(fd_update);
                        close(fd_update_log);

                        return 1;
                }

                addr_map_capsule = mmap(NULL, st.st_size, PROT_READ, MAP_SHARED,
                                        fd_capsule, 0);
                if (addr_map_capsule == MAP_FAILED) {
                        perror("Failed to mmap capsule file.");
                        close(fd_capsule);
                        close(fd_update);
                        close(fd_update_log);

                        return 1;
                }

                ret = write(fd_update, (char *)addr_map_capsule, st.st_size);
                printf("Load %d bytes of capsule file into the system\n",
                       ret);

                if (ret == -1) {
                        perror("Failed to load capsule file");
                        munmap(addr_map_capsule, st.st_size);
                        close(fd_capsule);
                        close(fd_update);
                        close(fd_update_log);

                        return 1;
                }

                munmap(addr_map_capsule, st.st_size);
                close(fd_capsule);
                printf("Load done.\n");
        }

        if (action) {
                if (action == 1) {
                        ret = ioctl(fd_update, PFRU_IOC_STAGE, NULL);
                } else if (action == 2) {
                        ret = ioctl(fd_update, PFRU_IOC_ACTIVATE, NULL);
                } else if (action == 3) {
                        ret = ioctl(fd_update, PFRU_IOC_STAGE_ACTIVATE, NULL);
                } else {
                        close(fd_update);
                        close(fd_update_log);

                        return 1;
                }
                printf("Update finished, return %d\n", ret);
        }

        close(fd_update);

        if (log_read) {
                void *p_mmap;
                int max_data_sz;

                ret = ioctl(fd_update_log, PFRT_LOG_IOC_GET_DATA_INFO, &data_info);
                if (ret) {
                        perror("Get telemetry data info failed.");
                        close(fd_update_log);

                        return 1;
                }

                max_data_sz = data_info.max_data_size;
                if (!max_data_sz) {
                        printf("No telemetry data available.\n");
                        close(fd_update_log);

                        return 1;
                }

                log_buf = malloc(max_data_sz + 1);
                if (!log_buf) {
                        perror("log_buf allocate failed.");
                        close(fd_update_log);

                        return 1;
                }

                p_mmap = mmap(NULL, max_data_sz, PROT_READ, MAP_SHARED, fd_update_log, 0);
                if (p_mmap == MAP_FAILED) {
                        perror("mmap error.");
                        close(fd_update_log);
                        free(log_buf);
                        return 1;
                }

                memcpy(log_buf, p_mmap, max_data_sz);
                log_buf[max_data_sz] = '\0';
                printf("%s\n", log_buf);
                free(log_buf);

                munmap(p_mmap, max_data_sz);
        }

        close(fd_update_log);

        return 0;
}