Re: [PATCH 5/5][RFC] selftests/pfru: add test for Platform Firmware Runtime Update and Telemetry

From: Mike Rapoport
Date: Wed Sep 08 2021 - 05:08:54 EST


On Tue, Sep 07, 2021 at 11:40:30PM +0800, Chen Yu wrote:
> Introduce a simple test for Platform Firmware Runtime Update and Telemetry
> drivers. It is based on ioctl to either update firmware driver or code injection,
> and read corresponding PFRU Telemetry log into user space.
>
> For example:
>
> ./pfru_test -h
> usage: pfru_test [OPTIONS]
> code injection:
> -l, --load
> -s, --stage
> -a, --activate
> -u, --update [stage and activate]
> -q, --query
> -d, --revid update
> telemetry:
> -G, --getloginfo
> -T, --type(0:execution, 1:history)
> -L, --level(0, 1, 2, 4)
> -R, --read
> -D, --revid log
>
> ./pfru_test -G
> log_level:4
> log_type:0
> log_revid:2
> max_data_size:65536
> chunk1_size:0
> chunk2_size:1401
> rollover_cnt:0
> reset_cnt:4
>
> ./pfru_test -q
> code injection image type:794bf8b2-6e7b-454e-885f-3fb9bb185402
> fw_version:0
> code_rt_version:1
> driver update image type:0e5f0b14-f849-7945-ad81-bc7b6d2bb245
> drv_rt_version:0
> drv_svn:0
> platform id:39214663-b1a8-4eaa-9024-f2bb53ea4723
> oem id:a36db54f-ea2a-e14e-b7c4-b5780e51ba3d
>
> Tested-by: Dou Shengnan <shengnanx.dou@xxxxxxxxx>
> Signed-off-by: Chen Yu <yu.c.chen@xxxxxxxxx>
> ---
> tools/testing/selftests/Makefile | 1 +
> tools/testing/selftests/pfru/Makefile | 7 +
> tools/testing/selftests/pfru/config | 2 +
> tools/testing/selftests/pfru/pfru.h | 152 +++++++++++
> tools/testing/selftests/pfru/pfru_test.c | 324 +++++++++++++++++++++++
> 5 files changed, 486 insertions(+)
> create mode 100644 tools/testing/selftests/pfru/Makefile
> create mode 100644 tools/testing/selftests/pfru/config
> create mode 100644 tools/testing/selftests/pfru/pfru.h
> create mode 100644 tools/testing/selftests/pfru/pfru_test.c
>
> diff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Makefile
> index fb010a35d61a..c8b53a2c4450 100644
> --- a/tools/testing/selftests/Makefile
> +++ b/tools/testing/selftests/Makefile
> @@ -56,6 +56,7 @@ TARGETS += seccomp
> TARGETS += sgx
> TARGETS += sigaltstack
> TARGETS += size
> +TARGETS += pfru
> TARGETS += sparc64
> TARGETS += splice
> TARGETS += static_keys
> diff --git a/tools/testing/selftests/pfru/Makefile b/tools/testing/selftests/pfru/Makefile
> new file mode 100644
> index 000000000000..c61916ccf637
> --- /dev/null
> +++ b/tools/testing/selftests/pfru/Makefile
> @@ -0,0 +1,7 @@
> +# SPDX-License-Identifier: GPL-2.0+
> +
> +CFLAGS += -Wall -O2
> +LDLIBS := -luuid
> +
> +TEST_GEN_PROGS := pfru_test
> +include ../lib.mk
> diff --git a/tools/testing/selftests/pfru/config b/tools/testing/selftests/pfru/config
> new file mode 100644
> index 000000000000..37f53609acbd
> --- /dev/null
> +++ b/tools/testing/selftests/pfru/config
> @@ -0,0 +1,2 @@
> +CONFIG_ACPI_PFRU=m
> +CONFIG_ACPI_PFRU_TELEMETRY=m
> diff --git a/tools/testing/selftests/pfru/pfru.h b/tools/testing/selftests/pfru/pfru.h
> new file mode 100644
> index 000000000000..8cd4ed80b161
> --- /dev/null
> +++ b/tools/testing/selftests/pfru/pfru.h
> @@ -0,0 +1,152 @@
> +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
> +/*
> + * Platform Firmware Runtime Update header
> + *
> + * Copyright(c) 2021 Intel Corporation. All rights reserved.
> + */
> +#ifndef __PFRU_H__
> +#define __PFRU_H__
> +
> +#include <linux/ioctl.h>
> +#include <uuid/uuid.h>
> +
> +#define PFRU_UUID "ECF9533B-4A3C-4E89-939E-C77112601C6D"
> +#define PFRU_CODE_INJ_UUID "B2F84B79-7B6E-4E45-885F-3FB9BB185402"
> +#define PFRU_DRV_UPDATE_UUID "4569DD8C-75F1-429A-A3D6-24DE8097A0DF"
> +
> +#define FUNC_STANDARD_QUERY 0
> +#define FUNC_QUERY_UPDATE_CAP 1
> +#define FUNC_QUERY_BUF 2
> +#define FUNC_START 3
> +
> +#define CODE_INJECT_TYPE 1
> +#define DRIVER_UPDATE_TYPE 2
> +
> +#define REVID_1 1
> +#define REVID_2 2
> +
> +#define PFRU_MAGIC 0xEE
> +
> +#define PFRU_IOC_SET_REV _IOW(PFRU_MAGIC, 0x01, unsigned int)
> +#define PFRU_IOC_STAGE _IOW(PFRU_MAGIC, 0x02, unsigned int)
> +#define PFRU_IOC_ACTIVATE _IOW(PFRU_MAGIC, 0x03, unsigned int)
> +#define PFRU_IOC_STAGE_ACTIVATE _IOW(PFRU_MAGIC, 0x04, unsigned int)
> +
> +static inline int valid_revid(int id)
> +{
> + return (id == REVID_1) || (id == REVID_2);
> +}
> +
> +/* Capsule file payload header */
> +struct payload_hdr {
> + __u32 sig;
> + __u32 hdr_version;
> + __u32 hdr_size;
> + __u32 hw_ver;
> + __u32 rt_ver;
> + uuid_t platform_id;
> +};
> +
> +enum start_action {
> + START_STAGE,
> + START_ACTIVATE,
> + START_STAGE_ACTIVATE,
> +};
> +
> +enum dsm_status {
> + DSM_SUCCEED,
> + DSM_FUNC_NOT_SUPPORT,
> + DSM_INVAL_INPUT,
> + DSM_HARDWARE_ERR,
> + DSM_RETRY_SUGGESTED,
> + DSM_UNKNOWN,
> + DSM_FUNC_SPEC_ERR,
> +};
> +
> +struct update_cap_info {
> + enum dsm_status status;
> + int update_cap;
> +
> + uuid_t code_type;
> + int fw_version;
> + int code_rt_version;
> +
> + uuid_t drv_type;
> + int drv_rt_version;
> + int drv_svn;
> +
> + uuid_t platform_id;
> + uuid_t oem_id;
> +
> + char oem_info[];
> +};
> +
> +struct com_buf_info {
> + enum dsm_status status;
> + enum dsm_status ext_status;
> + unsigned long addr_lo;
> + unsigned long addr_hi;
> + int buf_size;
> +};
> +
> +struct capsulate_buf_info {
> + unsigned long src;
> + int size;
> +};
> +
> +struct updated_result {
> + enum dsm_status status;
> + enum dsm_status ext_status;
> + unsigned long low_auth_time;
> + unsigned long high_auth_time;
> + unsigned long low_exec_time;
> + unsigned long high_exec_time;
> +};

Most of these types and constants seem to be a copy of uapu/linux/pfru.h.
Shouldn't the test get them from there?

> +
> +#define PFRU_TELEMETRY_UUID "75191659-8178-4D9D-B88F-AC5E5E93E8BF"
> +
> +/* Telemetry structures. */
> +struct telem_data_info {
> + enum dsm_status status;
> + enum dsm_status ext_status;
> + /* Maximum supported size of data of
> + * all Data Chunks combined.
> + */
> + unsigned long chunk1_addr_lo;
> + unsigned long chunk1_addr_hi;
> + unsigned long chunk2_addr_lo;
> + unsigned long chunk2_addr_hi;
> + int max_data_size;
> + int chunk1_size;
> + int chunk2_size;
> + int rollover_cnt;
> + int reset_cnt;
> +};
> +
> +struct telem_info {
> + int log_level;
> + int log_type;
> + int log_revid;
> +};
> +
> +/* Two logs: history and execution log */
> +#define LOG_EXEC_IDX 0
> +#define LOG_HISTORY_IDX 1
> +#define NR_LOG_TYPE 2
> +
> +#define LOG_ERR 0
> +#define LOG_WARN 1
> +#define LOG_INFO 2
> +#define LOG_VERB 4
> +
> +#define FUNC_SET_LEV 1
> +#define FUNC_GET_LEV 2
> +#define FUNC_GET_DATA 3
> +
> +#define LOG_NAME_SIZE 10
> +
> +#define PFRU_LOG_IOC_SET_INFO _IOW(PFRU_MAGIC, 0x05, struct telem_info)
> +#define PFRU_LOG_IOC_GET_INFO _IOR(PFRU_MAGIC, 0x06, struct telem_info)
> +#define PFRU_LOG_IOC_GET_DATA_INFO _IOR(PFRU_MAGIC, 0x07, struct telem_data_info)
> +
> +#endif /* __PFRU_H__ */
> diff --git a/tools/testing/selftests/pfru/pfru_test.c b/tools/testing/selftests/pfru/pfru_test.c
> new file mode 100644
> index 000000000000..d24d79d3836e
> --- /dev/null
> +++ b/tools/testing/selftests/pfru/pfru_test.c
> @@ -0,0 +1,324 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Tests Runtime Update/Telemetry (see Documentation/x86/pfru_update.rst)
> + */
> +#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 "pfru.h"
> +
> +#define MAX_LOG_SIZE 65536
> +
> +struct update_cap_info cap_info;
> +struct com_buf_info buf_info;
> +struct capsulate_buf_info image_info;
> +struct telem_data_info data_info;
> +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;
> +
> +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 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)
> +{
> + char *pathname;
> + int c;
> +
> + pathname = strdup(argv[0]);
> + progname = basename(pathname);
> +
> + while (1) {
> + int option_index = 0;
> +
> + c = getopt_long(argc, argv, option_string,
> + long_options, &option_index);
> + if (c == -1)
> + break;
> + switch (c) {
> + 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 = atoi(optarg);
> + set_log_type = 1;
> + break;
> + case 'L':
> + log_level = atoi(optarg);
> + 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();
> + break;
> + default:
> + break;
> + }
> + }
> +}
> +
> +void print_cap(struct update_cap_info *cap)
> +{
> + char *uuid = malloc(37);
> +
> + if (!uuid) {
> + perror("Can not allocate uuid buffer\n");
> + exit(1);
> + }
> + 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);
> +
> + free(uuid);
> +}
> +
> +int main(int argc, char *argv[])
> +{
> + int fd_update, fd_log, fd_capsule;
> + struct telem_data_info data_info;
> + struct telem_info info;
> + struct update_cap_info cap;
> + void *addr_map_capsule;
> + struct stat st;
> + char *log_buf;
> + int ret = 0;
> +
> + parse_options(argc, argv);
> +
> + fd_log = open("/dev/pfru/telemetry", O_RDWR);
> + if (fd_log < 0) {
> + perror("Cannot open telemetry device...");
> + return 1;
> + }
> + fd_update = open("/dev/pfru/update", O_RDWR);
> + if (fd_update < 0) {
> + perror("Cannot open code injection device...");
> + return 1;
> + }
> +
> + if (query_cap) {
> + ret = read(fd_update, &cap, sizeof(cap));
> + if (ret == -1) {
> + perror("Read error.");
> + return 1;
> + }
> + print_cap(&cap);
> + }
> +
> + if (log_getinfo) {
> + ret = ioctl(fd_log, PFRU_LOG_IOC_GET_DATA_INFO, &data_info);
> + if (ret) {
> + perror("Get log data info failed.");
> + return 1;
> + }
> + ret = ioctl(fd_log, PFRU_LOG_IOC_GET_INFO, &info);
> + if (ret) {
> + perror("Get log info failed.");
> + 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);
> +
> + 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_revid(log_revid)) {
> + printf("Invalid log revid %d\n",
> + log_revid);
> + } else {
> + info.log_revid = log_revid;
> + }
> + }
> +
> + ret = ioctl(fd_log, PFRU_LOG_IOC_SET_INFO, &info);
> + if (ret) {
> + perror("Log information set failed.(log_level, log_type, log_revid)");
> + return 1;
> + }
> +
> + if (set_revid) {
> + ret = ioctl(fd_update, PFRU_IOC_SET_REV, &revid);
> + if (ret) {
> + perror("mru update revid set failed");
> + return 1;
> + }
> + printf("mru 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...");
> + return 1;
> + }
> + if (fstat(fd_capsule, &st) < 0) {
> + perror("Can not fstat capsule file...");
> + 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.");
> + 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");
> + return 1;
> + }
> + munmap(addr_map_capsule, st.st_size);
> + 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
> + return 1;
> + printf("Update finished, return %d\n", ret);
> + }
> +
> + if (log_read) {
> + log_buf = malloc(MAX_LOG_SIZE + 1);
> + if (!log_buf) {
> + perror("log_buf allocate failed.");
> + return 1;
> + }
> + ret = read(fd_log, log_buf, MAX_LOG_SIZE);
> + if (ret == -1) {
> + perror("Read error.");
> + return 1;
> + }
> + log_buf[ret] = '\0';
> + printf("%s\n", log_buf);
> + free(log_buf);
> + }
> +
> + return 0;
> +}
> --
> 2.25.1
>

--
Sincerely yours,
Mike.