Skip to content

util/smmstoretool: add debug tool for low-level interactions with SMM… #713

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jul 22, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions util/smmstore_debug/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
smmstore_debug
42 changes: 42 additions & 0 deletions util/smmstore_debug/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# SPDX-License-Identifier: GPL-2.0-or-later

PRG := smmstore_debug

CC ?= $(CROSS_COMPILE)gcc
HOSTCC ?= $(CC)
INSTALL ?= /usr/bin/env install
PREFIX ?= /usr/local

HOSTCFLAGS ?= $(CFLAGS)
HOSTCFLAGS += -Wall -Wextra -MMD -MP -O3

HOSTLDFLAGS ?= $(LDFLAGS)

# there files are in this directory
SRC := smmstore_debug.c

OBJ := $(SRC:.c=.o)
DEP := $(SRC:.c=.o.d)

.PHONY: all debug clean install

all: $(PRG)

debug: HOSTCFLAGS += -O0 -g
debug: HOSTLDFLAGS += -g
debug: all

install: $(PRG)
$(INSTALL) -d $(DESTDIR)$(PREFIX)/bin/
$(INSTALL) $^ $(DESTDIR)$(PREFIX)/bin/

clean:
-$(RM) $(PRG) $(OBJ) $(DEP)

$(PRG): $(OBJ)
$(HOSTCC) -o $@ $^ $(HOSTLDFLAGS)

%.o: %.c
$(HOSTCC) $(HOSTCFLAGS) -c -o $@ -MF [email protected] $<

-include $(DEP)
1 change: 1 addition & 0 deletions util/smmstore_debug/description.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Debug tool for low-level interactions with SMMSTORE `C`
189 changes: 189 additions & 0 deletions util/smmstore_debug/smmstore_debug.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */

#include <fcntl.h>
#include <getopt.h>
#include <limits.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/io.h>
#include <sys/mman.h>

#define STR(x) _STR(x)
#define _STR(x) #x

#define APM_CNT 0xb2
#define SMMSTORE_CMD 0xed
#define SMMSTORE_PARAMS_SIZE 0x1000
#define SMMSTORE_COMBUF_SIZE 0x10000

/* Size is doubled to account for page alignment required for mmap() */
static unsigned char tmpbuf[2 * SMMSTORE_PARAMS_SIZE];

static inline unsigned int call_smm (unsigned int cmd, unsigned int arg)
{
unsigned int res = 0;
asm volatile (
"outb %b0, $" STR(APM_CNT) "\n\t"
"nop; nop; nop\n\t"
: "=a" (res)
: "a" (cmd),
"b" (arg)
: "memory");
return res;
}

static int usage (char *name)
{
printf ("Usage:\n"
"\t%s --tmp_buf <addr> --com_buf <addr> --command <cmd>\n"
"\t%*s [--in_file <path>] [--out_file <path>] param1 ...\n"
"\t%s -t <addr> -b <addr> -c <cmd>\n"
"\t%*s [-i <path>] [-o <path>] param1 ...\n\n",
name, (int)strlen(name), "", name, (int)strlen(name), "");
printf ("--tmp_buf, -t - physical address of a buffer in low 4GB of RAM. Up to 8KB\n"
" after this address will be overwritten with params and then\n"
" restored. Point it to something that won't break because of\n"
" such temporary modification (e.g. part of CONSOLE in CBMEM,\n"
" **after** the header - so SMI handler can still print to\n"
" the CBMEM console).\n"
"--com_buf, -b - physical address of SMM COMBUFFER, can be read from\n"
" `cbmem -l`. It must be aligned to a page size.\n"
"--command, -c - command to execute, see `coreboot/src/include/smmstore.h`.\n"
" For SMMSTOREv2 this is 5 for read, 6 for write, 7 for\n"
" clear.\n"
"param1 ... - params passed to SMI handler, as set of uint32_t values.\n"
" Numbers are parsed as hexadecimal values, even without '0x'\n"
" prefix. Expected structure depends on the command, refer to\n"
" `smmstore_params_raw*` structure definitions in\n"
" `coreboot/src/include/smmstore.h` file. At least one\n"
" parameter must be specified.\n"
"--in_file, -i - optional, binary file which content is written to com_buf\n"
" before SMM call.\n"
"--out_file, -o - optional, path to binary file (it is created if doesn't exist)\n"
" to which the content of com_buf after SMM call is saved.\n\n"
"Some of the combinations don't make sense (e.g. writing to COMBUFFER before\n"
"read command), but the tool doesn't check this. It is supposed to help\n"
"with testing and debugging, hence it allows for sending garbage to SMMSTORE\n"
"handler, with the assumption that the user knows what he's doing.\n\n"
"The utility must be run with CAP_SYS_RAWIO privilege (e.g. as root)\n"
"and with no kernel_lockdown in place (i.e. without SecureBoot).\n");
return -1;
}

static struct option long_options[] = {
{"tmp_buf", required_argument, 0, 't'},
{"com_buf", required_argument, 0, 'b'},
{"command", required_argument, 0, 'c'},
{"out_file", required_argument, 0, 'o'},
{"in_file", required_argument, 0, 'i'},
{0, 0, 0, 0 }
};

int main (int argc, char *argv[])
{
int fd;
void *combuf;
unsigned long combuf_addr = 0;
unsigned char *params;
unsigned long params_phys = 0;
unsigned long params_phys_aligned;
unsigned int command = (unsigned int)-1;
char *in_file = NULL;
char *out_file = NULL;
char *end;

while (1) {
int idx = 0;
int opt;

opt = getopt_long (argc, argv, "t:b:c:o:i:", long_options, &idx);

if (opt == -1)
break;

switch (opt) {
case 't':
params_phys = strtoul (optarg, &end, 16);
if (params_phys == 0 || params_phys == ULONG_MAX) return usage(argv[0]);
break;
case 'b':
combuf_addr = strtoul (optarg, &end, 16);
if (combuf_addr == 0 || combuf_addr == ULONG_MAX) return usage(argv[0]);
break;
case 'c':
command = strtoul (optarg, &end, 16);
if (command == 0 || command == UINT_MAX) return usage(argv[0]);
break;
case 'o':
out_file = optarg;
break;
case 'i':
in_file = optarg;
break;
default:
return usage(argv[0]);
}
}

/* Check if all required options were given */
if (!combuf_addr || !params_phys || command == (unsigned int)-1 || optind == argc)
return usage(argv[0]);

params_phys_aligned = params_phys & ~((unsigned long)(getpagesize() - 1));

/* Map COMBUF and temporary buffer for params */
fd = open ("/dev/mem", O_SYNC | O_RDWR);
if (fd < 0) {
perror ("Can't access physical memory");
printf ("This tool must be run as root with SecureBoot disabled.\n");
return -1;
}
combuf = mmap (NULL, SMMSTORE_COMBUF_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_LOCKED | MAP_POPULATE, fd, combuf_addr);
params = mmap (NULL, 2 * SMMSTORE_PARAMS_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_LOCKED | MAP_POPULATE, fd, params_phys_aligned);
close (fd);

/* Fill COMBUF from in_file */
if (in_file) {
fd = open (in_file, O_RDONLY);
if (fd < 0) {
perror ("Error opening in_file");
return -1;
}
read (fd, combuf, SMMSTORE_COMBUF_SIZE);
close (fd);
}

/* Request IO access to APM_CNT port */
if (ioperm (APM_CNT, 1, 1) < 0) {
perror ("Can't access IO port");
printf ("This tool must be run as root with SecureBoot disabled.\n");
return -1;
}

/* Backup temporary buffer and fill params */
memcpy (tmpbuf, params, sizeof(tmpbuf));
for (int i = 0; i < (argc - optind) && i < (int)(SMMSTORE_PARAMS_SIZE / sizeof (unsigned int)); i++) {
unsigned int *param = (unsigned int *)(params + (params_phys - params_phys_aligned));
param[i] = strtoul (argv[i + optind], &end, 16);
}

printf ("SMI handler returned %#x\n", call_smm ((command << 8) | SMMSTORE_CMD, params_phys));

/* Restore original contents of memory used for params */
memcpy (params, tmpbuf, sizeof(tmpbuf));

/* Store COMBUF to out_file */
if (out_file) {
fd = creat (out_file, 0666);
if (fd < 0) {
perror ("Error opening out_file");
return -1;
}
write (fd, combuf, SMMSTORE_COMBUF_SIZE);
close (fd);
}

return 0;
}
19 changes: 17 additions & 2 deletions util/smmstoretool/data.c
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ void *make_data(const char source[], size_t *data_size, enum data_type type)
bool boolean;
uint64_t uint;
bool failed;
FILE *f;

case DATA_TYPE_BOOL:
if (str_eq(source, "true")) {
Expand Down Expand Up @@ -174,8 +175,22 @@ void *make_data(const char source[], size_t *data_size, enum data_type type)
case DATA_TYPE_UNICODE:
return to_uchars(source, data_size);
case DATA_TYPE_RAW:
fprintf(stderr, "Raw data type is output only\n");
return NULL;
f = fopen(source, "rb");
if (!f) {
perror(source);
return NULL;
}
fseek(f, 0, SEEK_END);
*data_size = ftell(f);
fseek(f, 0, SEEK_SET);
data = xmalloc(*data_size);
if (fread(data, 1, *data_size, f) != *data_size) {
fprintf(stderr, "Error reading data from '%s', aborting\n", source);
free(data);
data = NULL;
}
fclose(f);
return data;
}

return NULL;
Expand Down
2 changes: 1 addition & 1 deletion util/smmstoretool/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ static void print_types(FILE *f)
fprintf(f, " * uint64 (0..2^64-1)\n");
fprintf(f, " * ascii (NUL-terminated)\n");
fprintf(f, " * unicode (widened and NUL-terminated)\n");
fprintf(f, " * raw (output only; raw bytes on output)\n");
fprintf(f, " * raw (file name on input; raw bytes on output)\n");
}

static void help_set(FILE *f, const struct subcommand_t *info)
Expand Down