diff options
25 files changed, 1106 insertions, 964 deletions
diff --git a/config/coreboot/x2e_n150/config/fspgop b/config/coreboot/x2e_n150/config/fspgop index 2e041b74..bcab6bbc 100644 --- a/config/coreboot/x2e_n150/config/fspgop +++ b/config/coreboot/x2e_n150/config/fspgop @@ -260,7 +260,7 @@ CONFIG_SOC_INTEL_UART_DEV_MAX=7 CONFIG_SOC_INTEL_COMMON_LPSS_UART_CLK_M_VAL=0x25a CONFIG_SOC_INTEL_COMMON_LPSS_UART_CLK_N_VAL=0x7fff CONFIG_FSP_TYPE_IOT=y -CONFIG_FSP_HEADER_PATH="3rdparty/fsp/AlderLakeFspBinPkg/IoT/AlderLakeN/Include/" +CONFIG_FSP_HEADER_PATH="3rdparty/fspcc36ae2b5775fa7400cb3282680afc0f6cb37a3c/AlderLakeFspBinPkg/IoT/AlderLakeN/Include/" CONFIG_SOC_INTEL_COMMON_DEBUG_CONSENT=0 CONFIG_DATA_BUS_WIDTH=128 CONFIG_DIMMS_PER_CHANNEL=2 diff --git a/include/inject.sh b/include/inject.sh index b61ad9d5..783e06ed 100644 --- a/include/inject.sh +++ b/include/inject.sh @@ -6,7 +6,7 @@ cbcfgsdir="config/coreboot" tmpromdel="$XBMK_CACHE/DO_NOT_FLASH" -nvmutil="util/libreboot-utils/nvmutil" +nvmutil="util/nvmutil/nvmutil" ifdtool="elf/coreboot/default/ifdtool" checkvars="CONFIG_GBE_BIN_PATH" @@ -197,8 +197,8 @@ modify_mac() x_ cp "${CONFIG_GBE_BIN_PATH##*../}" "$xbtmp/gbe" if [ -n "$new_mac" ] && [ "$new_mac" != "restore" ]; then - x_ make -C util/libreboot-utils clean - x_ make -C util/libreboot-utils + x_ make -C util/nvmutil clean + x_ make -C util/nvmutil x_ "$nvmutil" "$xbtmp/gbe" setmac "$new_mac" fi diff --git a/util/dell-flash-unlock/Makefile b/util/dell-flash-unlock/Makefile index 8528c10e..7bb7e1b3 100644 --- a/util/dell-flash-unlock/Makefile +++ b/util/dell-flash-unlock/Makefile @@ -2,7 +2,7 @@ # SPDX-FileCopyrightText: 2023 Nicholas Chin CC=cc -CFLAGS=-Wall -Wextra -Werror -O2 -pedantic +CFLAGS=-Wall -Wextra -O2 SRCS=dell_flash_unlock.c accessors.c all: $(SRCS) accessors.h diff --git a/util/dell-flash-unlock/dell_flash_unlock.c b/util/dell-flash-unlock/dell_flash_unlock.c index d59f5d5b..64fc6daf 100644 --- a/util/dell-flash-unlock/dell_flash_unlock.c +++ b/util/dell-flash-unlock/dell_flash_unlock.c @@ -47,9 +47,9 @@ main(int argc, char *argv[]) (void)argv; if (sys_iopl(3) == -1) - err(errno, "Could not access IO ports"); + err(EXIT_FAILURE, "Could not access IO ports"); if ((devmemfd = open("/dev/mem", O_RDONLY)) == -1) - err(errno, "/dev/mem"); + err(EXIT_FAILURE, "/dev/mem"); /* Read RCBA and PMBASE from the LPC config registers */ long int rcba = pci_read_32(LPC_DEV, 0xf0) & 0xffffc000; @@ -59,11 +59,11 @@ main(int argc, char *argv[]) rcba_mmio = mmap(0, RCBA_MMIO_LEN, PROT_READ, MAP_SHARED, devmemfd, rcba); if (rcba_mmio == MAP_FAILED) - err(errno, "Could not map RCBA"); + err(EXIT_FAILURE, "Could not map RCBA"); if (get_fdo_status() == 1) { /* Descriptor not overridden */ if (check_lpc_decode() == -1) - err(errno = ECANCELED, "Can't forward I/O to LPC"); + err(EXIT_FAILURE, "Can't forward I/O to LPC"); printf("Sending FDO override command to EC:\n"); ec_set_fdo(); @@ -80,7 +80,8 @@ main(int argc, char *argv[]) "to enable SMIs.\n (shutdown is buggy when " "SMIs are disabled)\n"); } else { - err(errno = ECANCELED, "Could not disable SMIs!"); + errno = EIO; + err(EXIT_FAILURE, "Could not disable SMIs!"); } } else { /* SMI locks not in place or bypassed */ if (get_gbl_smi_en()) { @@ -97,7 +98,7 @@ main(int argc, char *argv[]) } } sys_iopl(0); - return errno; + return EXIT_SUCCESS; } int @@ -137,6 +138,7 @@ check_lpc_decode(void) pci_write_32(LPC_DEV, 0x84 + 4 * gen_dec_free, 0x911); return 0; } else { + errno = EIO; return -1; } } @@ -165,7 +167,7 @@ send_ec_cmd(uint8_t cmd) sys_outb(EC_INDEX, 0); sys_outb(EC_DATA, cmd); if (wait_ec() == -1) - err(errno = ECANCELED, "Timeout while waiting for EC!"); + err(EXIT_FAILURE, "Timeout while waiting for EC!"); } int @@ -179,6 +181,8 @@ wait_ec(void) timeout--; usleep(1000); } while (busy && timeout > 0); + if (timeout <= 0) + errno = EIO; return timeout > 0 ? 0 : -1; } diff --git a/util/libreboot-utils/Makefile b/util/libreboot-utils/Makefile index 92e8a3a6..f19612d3 100644 --- a/util/libreboot-utils/Makefile +++ b/util/libreboot-utils/Makefile @@ -2,165 +2,59 @@ # Copyright (c) 2022,2026 Leah Rowe <leah@libreboot.org> # Copyright (c) 2023 Riku Viitanen <riku.viitanen@protonmail.com> -# Makefile for nvmutil, which is an application -# that modifies Intel GbE NVM configurations. - CC = cc -HELLCC = clang - CFLAGS = -Os -Wall -Wextra -std=c99 -pedantic LDFLAGS = -DESTDIR = PREFIX = /usr/local +DESTDIR = INSTALL = install -# used for portability testing on linux: -# -PORT_OPENAT = -DUSE_OPENAT=1 -PORT_ARC4 = -DUSE_ARC4=1 -PORT_URANDOM = -DUSE_URANDOM=1 - -.SUFFIXES: .c .o - -LDIR = - -HELLFLAGS = $(STRICT) -Weverything - -PROG = nvmutil -PROGMKH = mkhtemp -PROGLOT = lottery - -OBJS_NVMUTIL = \ - obj/nvmutil.o \ - obj/lib/state.o \ - obj/lib/file.o \ - obj/lib/string.o \ - obj/lib/usage.o \ - obj/lib/command.o \ - obj/lib/num.o \ - obj/lib/io.o \ - obj/lib/checksum.o \ - obj/lib/word.o \ - obj/lib/mkhtemp.o \ - obj/lib/rand.o - -OBJS_MKHTEMP = \ - obj/mkhtemp.o \ - obj/lib/file.o \ - obj/lib/string.o \ - obj/lib/num.o \ - obj/lib/mkhtemp.o \ - obj/lib/rand.o - -OBJS_LOTTERY = \ - obj/lottery.o \ - obj/lib/file.o \ - obj/lib/string.o \ - obj/lib/num.o \ - obj/lib/mkhtemp.o \ - obj/lib/rand.o - -# default mode -CC_MODE = $(CC) - -all: $(PROG) $(PROGMKH) $(PROGLOT) - -$(PROG): $(OBJS_NVMUTIL) - $(CC_MODE) $(CFLAGS) $(OBJS_NVMUTIL) -o $(PROG) $(LDFLAGS) - -$(PROGMKH): $(OBJS_MKHTEMP) - $(CC_MODE) $(CFLAGS) $(OBJS_MKHTEMP) -o $(PROGMKH) $(LDFLAGS) - -$(PROGLOT): $(OBJS_LOTTERY) - $(CC_MODE) $(CFLAGS) $(OBJS_LOTTERY) -o $(PROGLOT) $(LDFLAGS) +PROGS = nvmutil mkhtemp lottery -# ensure obj directory exists -$(OBJS_NVMUTIL): obj -$(OBJS_MKHTEMP): obj -$(OBJS_LOTTERY): obj +LIB_OBJS = \ + lib/state.o \ + lib/file.o \ + lib/string.o \ + lib/usage.o \ + lib/command.o \ + lib/num.o \ + lib/io.o \ + lib/checksum.o \ + lib/word.o \ + lib/mkhtemp.o \ + lib/rand.o -obj: - mkdir obj || true - mkdir obj/lib || true +OBJS_NVMUTIL = nvmutil.o $(LIB_OBJS) +OBJS_MKHTEMP = mkhtemp.o lib/file.o lib/string.o lib/num.o lib/mkhtemp.o lib/rand.o +OBJS_LOTTERY = lottery.o lib/file.o lib/string.o lib/num.o lib/mkhtemp.o lib/rand.o -# main program object +all: $(PROGS) -obj/nvmutil.o: nvmutil.c - $(CC_MODE) $(CFLAGS) -c nvmutil.c -o obj/nvmutil.o +nvmutil: $(OBJS_NVMUTIL) + $(CC) $(CFLAGS) $(OBJS_NVMUTIL) -o $@ $(LDFLAGS) -obj/mkhtemp.o: mkhtemp.c - $(CC_MODE) $(CFLAGS) -c mkhtemp.c -o obj/mkhtemp.o +mkhtemp: $(OBJS_MKHTEMP) + $(CC) $(CFLAGS) $(OBJS_MKHTEMP) -o $@ $(LDFLAGS) -obj/lottery.o: lottery.c - $(CC_MODE) $(CFLAGS) -c lottery.c -o obj/lottery.o +lottery: $(OBJS_LOTTERY) + $(CC) $(CFLAGS) $(OBJS_LOTTERY) -o $@ $(LDFLAGS) -# library/helper objects +.c.o: + $(CC) $(CFLAGS) -c $< -o $@ -obj/lib/state.o: lib/state.c - $(CC_MODE) $(CFLAGS) -c lib/state.c -o obj/lib/state.o - -obj/lib/file.o: lib/file.c - $(CC_MODE) $(CFLAGS) -c lib/file.c -o obj/lib/file.o - -obj/lib/string.o: lib/string.c - $(CC_MODE) $(CFLAGS) -c lib/string.c -o obj/lib/string.o - -obj/lib/usage.o: lib/usage.c - $(CC_MODE) $(CFLAGS) -c lib/usage.c -o obj/lib/usage.o - -obj/lib/command.o: lib/command.c - $(CC_MODE) $(CFLAGS) -c lib/command.c -o obj/lib/command.o - -obj/lib/num.o: lib/num.c - $(CC_MODE) $(CFLAGS) -c lib/num.c -o obj/lib/num.o - -obj/lib/io.o: lib/io.c - $(CC_MODE) $(CFLAGS) -c lib/io.c -o obj/lib/io.o - -obj/lib/checksum.o: lib/checksum.c - $(CC_MODE) $(CFLAGS) -c lib/checksum.c -o obj/lib/checksum.o - -obj/lib/word.o: lib/word.c - $(CC_MODE) $(CFLAGS) -c lib/word.c -o obj/lib/word.o - -obj/lib/mkhtemp.o: lib/mkhtemp.c - $(CC_MODE) $(CFLAGS) -c lib/mkhtemp.c -o obj/lib/mkhtemp.o - -obj/lib/rand.o: lib/rand.c - $(CC_MODE) $(CFLAGS) -c lib/rand.c -o obj/lib/rand.o - -# install - -install: $(PROG) $(PROGMKH) $(PROGLOT) - $(INSTALL) -d $(DESTDIR)$(PREFIX)/bin - $(INSTALL) $(PROG) $(DESTDIR)$(PREFIX)/bin/$(PROG) - chmod 755 $(DESTDIR)$(PREFIX)/bin/$(PROG) - $(INSTALL) $(PROGMKH) $(DESTDIR)$(PREFIX)/bin/$(PROGMKH) - chmod 755 $(DESTDIR)$(PREFIX)/bin/$(PROGMKH) - $(INSTALL) $(PROGLOT) $(DESTDIR)$(PREFIX)/bin/$(PROGLOT) - chmod 755 $(DESTDIR)$(PREFIX)/bin/$(PROGLOT) +install: $(PROGS) + mkdir -p $(DESTDIR)$(PREFIX)/bin + for p in $(PROGS); do \ + $(INSTALL) $$p $(DESTDIR)$(PREFIX)/bin/$$p; \ + chmod 755 $(DESTDIR)$(PREFIX)/bin/$$p; \ + done uninstall: - rm -f $(DESTDIR)$(PREFIX)/bin/$(PROG) - rm -f $(DESTDIR)$(PREFIX)/bin/$(PROGMKH) - rm -f $(DESTDIR)$(PREFIX)/bin/$(PROGLOT) + for p in $(PROGS); do \ + rm -f $(DESTDIR)$(PREFIX)/bin/$$p; \ + done clean: - rm -f $(PROG) $(PROGMKH) $(OBJS_NVMUTIL) $(OBJS_MKHTEMP) \ - $(OBJS_LOTTERY) $(PROGLOT) + rm -f $(PROGS) *.o lib/*.o distclean: clean - -# mode targets (portable replacement for ifeq) - -strict: - $(MAKE) CFLAGS="$(CFLAGS) $(HELLFLAGS)" CC_MODE="$(HELLCC)" - -# BSD-like portability test (openat + arc4random) -portable-bsd: - $(MAKE) CFLAGS="$(CFLAGS) $(PORT_OPENAT) $(PORT_ARC4)" CC_MODE="$(CC)" - -# fallback portability test (openat + urandom -- old linux mostly) -portable-urandom: - $(MAKE) CFLAGS="$(CFLAGS) $(PORT_OPENAT) $(PORT_URANDOM)" \ - CC_MODE="$(CC)" diff --git a/util/libreboot-utils/include/common.h b/util/libreboot-utils/include/common.h index d08828df..940c4364 100644 --- a/util/libreboot-utils/include/common.h +++ b/util/libreboot-utils/include/common.h @@ -357,8 +357,6 @@ void write_mac_part(size_t partnum); size_t page_remain(const void *p); long pagesize(void); -int xunveilx(const char *path, const char *permissions); -int xpledgex(const char *promises, const char *execpromises); char *smalloc(char **buf, size_t size); void *vmalloc(void **buf, size_t size); size_t slen(const char *scmp, size_t maxlen, @@ -368,6 +366,8 @@ int scmp(const char *a, const char *b, size_t maxlen, int *rval); int ccmp(const char *a, const char *b, size_t i, int *rval); +int dup_pair(char **dir, const char *d, + char **base, const char *b); char *sdup(const char *s, size_t n, char **dest); char *scatn(ssize_t sc, const char **sv, diff --git a/util/libreboot-utils/lib/file.c b/util/libreboot-utils/lib/file.c index efc23ba9..0385ebbb 100644 --- a/util/libreboot-utils/lib/file.c +++ b/util/libreboot-utils/lib/file.c @@ -521,6 +521,8 @@ fs_dirname_basename(const char *path, char *buf = NULL; char *slash; size_t len; + const char *d = NULL; + const char *b = NULL; errno = 0; if (if_err(path == NULL || dir == NULL || base == NULL, EFAULT)) @@ -539,22 +541,27 @@ fs_dirname_basename(const char *path, if (slash) { *slash = '\0'; - *dir = buf; - *base = slash + 1; + d = buf; + b = slash + 1; - if (**dir == '\0') { - (*dir)[0] = '/'; - (*dir)[1] = '\0'; - } + if (*d == '\0') + d = "/"; } else if (allow_relative) { - sdup(".", PATH_MAX, dir); - *base = buf; + d = "."; + b = buf; } else { free_and_set_null(&buf); goto err; } + if (dup_pair(dir, d, base, b) < 0) { + free_and_set_null(&buf); + goto err; + } + + free_and_set_null(&buf); + reset_caller_errno(0); return 0; err: diff --git a/util/libreboot-utils/lib/mkhtemp.c b/util/libreboot-utils/lib/mkhtemp.c index d9411104..d394ae73 100644 --- a/util/libreboot-utils/lib/mkhtemp.c +++ b/util/libreboot-utils/lib/mkhtemp.c @@ -195,7 +195,11 @@ env_tmpdir(int bypass_all_sticky_checks, char **tmpdir, bypass_all_sticky_checks)) goto err; - rval = t; + rval = NULL; + if (t != NULL) { + if (sdup(t, PATH_MAX, &rval) == NULL) + goto err; + } goto out; } @@ -547,7 +551,7 @@ mkhtemp_try_create(int dirfd, /* try O_TMPFILE fast path */ if (mkhtemp_tmpfile_linux(dirfd, st_dir_first, fname_copy, - p, xc, fd, st) == 0) { + p, xc, fd, st) >= 0) { errno = saved_errno; rval = 1; diff --git a/util/libreboot-utils/lib/string.c b/util/libreboot-utils/lib/string.c index 5e0b4c33..7388cf35 100644 --- a/util/libreboot-utils/lib/string.c +++ b/util/libreboot-utils/lib/string.c @@ -270,6 +270,27 @@ out: return *rval; } +int +dup_pair(char **dir, const char *d, + char **base, const char *b) +{ + char *dtmp = NULL; + char *btmp = NULL; + + if (d && sdup(d, PATH_MAX, &dtmp) == NULL) + return -1; + + if (b && sdup(b, PATH_MAX, &btmp) == NULL) { + free(dtmp); + return -1; + } + + *dir = dtmp; + *base = btmp; + + return 0; +} + /* strict word-based strdup */ char * sdup(const char *s, @@ -620,32 +641,3 @@ lbsetprogname(char *argv0) return progname; } - -/* https://man.openbsd.org/pledge.2 - https://man.openbsd.org/unveil.2 */ -int -xpledgex(const char *promises, const char *execpromises) -{ - int saved_errno = errno; - (void) promises, (void) execpromises, (void) saved_errno; - errno = 0; -#ifdef __OpenBSD__ - if (pledge(promises, execpromises) == -1) - exitf("pledge"); -#endif - reset_caller_errno(0); - return 0; -} -int -xunveilx(const char *path, const char *permissions) -{ - int saved_errno = errno; - (void) path, (void) permissions, (void) saved_errno; - errno = 0; -#ifdef __OpenBSD__ - if (pledge(promises, execpromises) == -1) - exitf("pledge"); -#endif - reset_caller_errno(0); - return 0; -} diff --git a/util/libreboot-utils/lottery.c b/util/libreboot-utils/lottery.c index 1648cbc7..3ac4d135 100644 --- a/util/libreboot-utils/lottery.c +++ b/util/libreboot-utils/lottery.c @@ -16,6 +16,9 @@ exit_cleanup(void); int main(int argc, char **argv) { +#ifndef __linux__ +#error This code is currently buggy on BSD systems. Only use on Linux. +#endif int same = 0; char *buf; size_t size = BUFSIZ; @@ -24,8 +27,11 @@ main(int argc, char **argv) (void) errhook(exit_cleanup); (void) lbsetprogname(argv[0]); +#ifdef __OpenBSD__ /* https://man.openbsd.org/pledge.2 */ - xpledgex("stdio", NULL); + if (pledge("stdio", NULL) == -1) + exitf("pledge"); +#endif buf = rmalloc(size); if (!vcmp(buf, buf + (size >> 1), size >> 1)) diff --git a/util/libreboot-utils/mkhtemp.c b/util/libreboot-utils/mkhtemp.c index 86aab536..9ff70328 100644 --- a/util/libreboot-utils/mkhtemp.c +++ b/util/libreboot-utils/mkhtemp.c @@ -41,6 +41,9 @@ exit_cleanup(void); int main(int argc, char *argv[]) { +#ifndef __linux__ +#error This code is currently buggy on BSD systems. Only use on Linux. +#endif size_t len; size_t tlen; size_t xc = 0; @@ -59,8 +62,11 @@ main(int argc, char *argv[]) (void) errhook(exit_cleanup); (void) lbsetprogname(argv[0]); +#ifdef __OpenBSD__ /* https://man.openbsd.org/pledge.2 */ - xpledgex("stdio flock rpath wpath cpath", NULL); + if (pledge("stdio flock rpath wpath cpath fattr", NULL) == -1) + exitf("pledge"); +#endif while ((c = getopt(argc, argv, "qdp:")) != -1) { @@ -117,7 +123,10 @@ main(int argc, char *argv[]) tmpdir, template) < 0) exitf("%s", s); - xpledgex("stdio", NULL); +#ifdef __OpenBSD__ + if (pledge("stdio", NULL) == -1) + exitf("pledge"); +#endif if (s == NULL) exitf("bad string initialisation"); diff --git a/util/libreboot-utils/nvmutil.c b/util/libreboot-utils/nvmutil.c index 66e47ec8..67b01ae7 100644 --- a/util/libreboot-utils/nvmutil.c +++ b/util/libreboot-utils/nvmutil.c @@ -27,6 +27,9 @@ exit_cleanup(void); int main(int argc, char *argv[]) { +#ifndef __linux__ +#error This code is currently buggy on BSD systems. Only use on Linux. +#endif struct xstate *x; struct commands *cmd; struct xfile *f; @@ -38,10 +41,14 @@ main(int argc, char *argv[]) (void) errhook(exit_cleanup); +#ifdef __OpenBSD /* https://man.openbsd.org/pledge.2 */ /* https://man.openbsd.org/unveil.2 */ - xpledgex("stdio flock rpath wpath cpath unveil", NULL); - xunveilx("/dev/urandom", "r"); + if (pledge("stdio flock rpath wpath cpath unveil", NULL) == -1) + exitf("pledge"); + if (unveil("/dev/urandom", "r") == -1) + exitf("unveil"); +#endif #ifndef S_ISREG exitf( @@ -62,14 +69,22 @@ main(int argc, char *argv[]) cmd = &x->cmd[x->i]; f = &x->f; - if ((cmd->flags & O_ACCMODE) == O_RDONLY) - xunveilx(f->fname, "r"); - else - xunveilx(f->fname, "rwc"); +#ifdef __OpenBSD__ + if ((cmd->flags & O_ACCMODE) == O_RDONLY) { + if (unveil(f->fname, "r") == -1) + exitf("unveil"); + } else { + if (unveil(f->fname, "rwc") == -1) + exitf("unveil"); + } - xunveilx(f->tname, "rwc"); - xunveilx(NULL, NULL); - xpledgex("stdio flock rpath wpath cpath", NULL); + if (unveil(f->tname, "rwc") == -1) + exitf("unveil"); + if (unveil(NULL, NULL) == -1) + exitf("unveil"); + if (pledge("stdio flock rpath wpath cpath", NULL) == -1) + exitf("pledge"); +#endif if (cmd->run == NULL) exitf("Command not set"); diff --git a/util/nvmutil/.gitignore b/util/nvmutil/.gitignore new file mode 100644 index 00000000..fbfbd130 --- /dev/null +++ b/util/nvmutil/.gitignore @@ -0,0 +1,7 @@ +/nvm +/nvmutil +/mkhtemp +/lottery +*.bin +*.o +*.d diff --git a/util/nvmutil/AUTHORS b/util/nvmutil/AUTHORS new file mode 100644 index 00000000..f38ea210 --- /dev/null +++ b/util/nvmutil/AUTHORS @@ -0,0 +1,2 @@ +Leah Rowe +Riku Viitanen diff --git a/util/nvmutil/COPYING b/util/nvmutil/COPYING new file mode 100644 index 00000000..a6ecf266 --- /dev/null +++ b/util/nvmutil/COPYING @@ -0,0 +1,21 @@ +Copyright (C) 2022-2025 Leah Rowe <leah@libreboot.org> +Copyright (c) 2023 Riku Viitanen <riku.viitanen@protonmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/util/nvmutil/ChangeLog.md b/util/nvmutil/ChangeLog.md new file mode 100644 index 00000000..e1ed5754 --- /dev/null +++ b/util/nvmutil/ChangeLog.md @@ -0,0 +1,8 @@ +This change log has moved. Please refer here for historical pre-osboot-merge +changes: + +<https://libreboot.org/docs/install/nvmutilimport.html> + +Osboot merged with Libreboot on November 17th, 2022. For nvmutil changes after +this date, please check regular Libreboot release announcements which shall +now specify any such changes. diff --git a/util/nvmutil/Makefile b/util/nvmutil/Makefile new file mode 100644 index 00000000..e8350203 --- /dev/null +++ b/util/nvmutil/Makefile @@ -0,0 +1,24 @@ +# SPDX-License-Identifier: MIT +# SPDX-FileCopyrightText: 2022,2025 Leah Rowe <leah@libreboot.org> +# SPDX-FileCopyrightText: 2023 Riku Viitanen <riku.viitanen@protonmail.com> + +CC?=cc +CFLAGS?=-Os -Wall -Wextra +DESTDIR?= +PREFIX?=/usr/local +INSTALL?=install + +nvmutil: nvmutil.c + $(CC) $(CFLAGS) nvmutil.c -o nvmutil + +install: + $(INSTALL) nvmutil $(DESTDIR)$(PREFIX)/bin/nvmutil + +uninstall: + rm -f $(DESTDIR)$(PREFIX)/bin/nvmutil + +distclean: + rm -f nvmutil + +clean: + rm -f nvmutil diff --git a/util/nvmutil/README.md b/util/nvmutil/README.md new file mode 100644 index 00000000..03a25bc4 --- /dev/null +++ b/util/nvmutil/README.md @@ -0,0 +1,4 @@ + +This documentation has become part of lbwww. See: + +<https://libreboot.org/docs/install/nvmutil.html> diff --git a/util/nvmutil/nvmutil.c b/util/nvmutil/nvmutil.c new file mode 100644 index 00000000..8cab66ac --- /dev/null +++ b/util/nvmutil/nvmutil.c @@ -0,0 +1,764 @@ +/* SPDX-License-Identifier: MIT */ +/* Copyright (c) 2022-2026 Leah Rowe <leah@libreboot.org> */ +/* Copyright (c) 2023 Riku Viitanen <riku.viitanen@protonmail.com> */ + +#include <sys/types.h> +#include <sys/stat.h> + +#include <err.h> +#include <errno.h> +#include <fcntl.h> +#include <stdarg.h> +#include <stddef.h> +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <unistd.h> +#include <limits.h> +#include <stdint.h> + +void cmd_setchecksum(void), cmd_brick(void), swap(int partnum), writeGbe(void), + cmd_dump(void), cmd_setmac(void), readGbe(void), + macf(int partnum), hexdump(int partnum), openFiles(const char *path), + cmd_copy(void), parseMacString(const char *strMac, uint16_t *mac), + cmd_swap(void), xclose(int *fd); +int goodChecksum(int partnum), open_on_eintr(const char *pathname, int flags), + fs_retry(int saved_errno, int rval), + rw_retry(int saved_errno, ssize_t rval), if_err(int condition, int errval), + if_err_sys(int condition); +ssize_t rw_exact(int fd, unsigned char *mem, size_t nrw, + off_t off, int rw_type); +ssize_t rw(int fd, void *mem, size_t nrw, + off_t off, int rw_type); +int io_args(int fd, void *mem, size_t nrw, + off_t off, int rw_type); +int with_fallback_errno(int fallback); +ssize_t rw_over_nrw(ssize_t r, size_t nrw); +uint8_t hextonum(char chs), rhex(void); + +#define COMMAND argv[2] +#define MAC_ADDRESS argv[3] +#define PARTN argv[3] +#define NVM_CHECKSUM 0xBABA +#define NVM_CHECKSUM_WORD 0x3F +#define NVM_SIZE 128 + +#define SIZE_4KB 0x1000 +#define SIZE_8KB 0x2000 +#define SIZE_16KB 0x4000 +#define SIZE_128KB 0x20000 + +#define IO_READ 0 +#define IO_WRITE 1 +#define IO_PREAD 2 +#define IO_PWRITE 3 + +uint16_t mac[3] = {0, 0, 0}; +ssize_t nf; +size_t partsize; +uintptr_t gbe[2]; +uint8_t nvmPartChanged[2] = {0, 0}, do_read[2] = {1, 1}; +int flags, rfd, fd, part; + +const char *strMac = NULL, *strRMac = "xx:xx:xx:xx:xx:xx", *filename = NULL; + +typedef struct op { + char *str; + void (*cmd)(void); + int args; +} op_t; +op_t op[] = { +{ .str = "dump", .cmd = cmd_dump, .args = 3}, +{ .str = "setmac", .cmd = cmd_setmac, .args = 3}, +{ .str = "swap", .cmd = cmd_swap, .args = 3}, +{ .str = "copy", .cmd = cmd_copy, .args = 4}, +{ .str = "brick", .cmd = cmd_brick, .args = 4}, +{ .str = "setchecksum", .cmd = cmd_setchecksum, .args = 4}, +}; +void (*cmd)(void) = NULL; + +#define err_if(x) \ + do { \ + if (x) { \ + err(EXIT_FAILURE, "%s", filename); \ + } \ + } while(0) + +#define xopen(f,l,p) \ + do { \ + if ((f = open_on_eintr(l, p)) == -1) { \ + err(EXIT_FAILURE, "%s", l); \ + } \ + if (fstat(f, &st) == -1) { \ + err(EXIT_FAILURE, "%s", l); \ + } \ + } while(0) + +#define word(pos16, partnum) \ + (((uint16_t *) gbe[partnum])[pos16]) + +#define setWord(pos16, p, val16) \ + do { \ + if (word(pos16, p) != val16) { \ + nvmPartChanged[p] = 1 | (word(pos16, p) = val16); \ + } \ + } while(0) + +#define SUCCESS(x) \ + ((x) >= 0) + +#define reset_caller_errno(return_value) \ + do { \ + if (SUCCESS(return_value) && (!errno)) { \ + errno = saved_errno; \ + } \ + } while (0) + +int +main(int argc, char *argv[]) +{ +#ifdef __OpenBSD__ + err_if(pledge("stdio rpath wpath unveil", NULL) == -1); +#endif + + if (argc < 2) { +#ifdef __OpenBSD__ + err_if(pledge("stdio", NULL) == -1); +#endif + fprintf(stderr, "Modify Intel GbE NVM images e.g. set MAC\n"); + fprintf(stderr, "USAGE:\n"); + fprintf(stderr, " %s FILE dump\n", argv[0]); + fprintf(stderr, " %s FILE\n # same as setmac without arg\n", + argv[0]); + fprintf(stderr, " %s FILE setmac [MAC]\n", argv[0]); + fprintf(stderr, " %s FILE swap\n", argv[0]); + fprintf(stderr, " %s FILE copy 0|1\n", argv[0]); + fprintf(stderr, " %s FILE brick 0|1\n", argv[0]); + fprintf(stderr, " %s FILE setchecksum 0|1\n", argv[0]); + errno = EINVAL; + err(EXIT_FAILURE, "Too few arguments"); + } + + filename = argv[1]; + + flags = O_RDWR; + + if (argc > 2) { + if (strcmp(COMMAND, "dump") == 0) { + flags = O_RDONLY; +#ifdef __OpenBSD__ + err_if(pledge("stdio rpath unveil", NULL) == -1); +#endif + } + } + +#ifdef __OpenBSD__ + err_if(unveil("/dev/urandom", "r") == -1); + + if (flags == O_RDONLY) { + err_if(unveil(filename, "r") == -1); + err_if(unveil(NULL, NULL) == -1); + err_if(pledge("stdio rpath", NULL) == -1); + } else { + err_if(unveil(filename, "rw") == -1); + err_if(unveil(NULL, NULL) == -1); + err_if(pledge("stdio rpath wpath", NULL) == -1); + } +#endif + + openFiles(filename); +#ifdef __OpenBSD__ + err_if(pledge("stdio", NULL) == -1); +#endif + + if (argc > 2) { + for (int i = 0; (i < 6) && (cmd == NULL); i++) { + if (strcmp(COMMAND, op[i].str) != 0) + continue; + if (argc >= op[i].args) { + cmd = op[i].cmd; + break; + } + errno = EINVAL; + err(EXIT_FAILURE, "Too few args on command '%s'", + op[i].str); + } + } else { + cmd = cmd_setmac; + } + + if ((cmd == NULL) && (argc > 2)) { /* nvm gbe [MAC] */ + strMac = COMMAND; + cmd = cmd_setmac; + } else if (cmd == cmd_setmac) { /* nvm gbe setmac [MAC] */ + strMac = strRMac; /* random MAC */ + if (argc > 3) + strMac = MAC_ADDRESS; + } else if ((cmd != NULL) && (argc > 3)) { /* user-supplied partnum */ + err_if((errno = (!((part = PARTN[0] - '0') == 0 || part == 1)) + || PARTN[1] ? EINVAL : 0)); /* only allow '0' or '1' */ + } + if (cmd == NULL) { + errno = EINVAL; + err(EXIT_FAILURE, "Bad command"); + } + + readGbe(); + (*cmd)(); + writeGbe(); + + return EXIT_SUCCESS; +} + +void +openFiles(const char *path) +{ + struct stat st; + + xopen(rfd, "/dev/urandom", O_RDONLY); + xopen(fd, path, flags); + + switch(st.st_size) { + case SIZE_8KB: + case SIZE_16KB: + case SIZE_128KB: + partsize = st.st_size >> 1; + break; + default: + errno = ECANCELED; + err(EXIT_FAILURE, "Invalid file size (not 8/16/128KiB)"); + break; + } + + if (if_err(!S_ISREG(st.st_mode), EBADF)) + err(EXIT_FAILURE, "Not a GbE file"); +} + +void +readGbe(void) +{ + if ((cmd == cmd_swap) || (cmd == cmd_copy)) + nf = SIZE_4KB; + else + nf = NVM_SIZE; + + if ((cmd == cmd_copy) || (cmd == cmd_setchecksum) || (cmd == cmd_brick)) + do_read[part ^ 1] = 0; + + char *buf = malloc(nf << (do_read[0] & do_read[1])); + if (buf == NULL) + err(EXIT_FAILURE, "malloc"); + + gbe[0] = (uintptr_t) buf; + gbe[1] = gbe[0] + (nf * (do_read[0] & do_read[1])); + + ssize_t tnr = 0; + + for (int p = 0; p < 2; p++) { + if (!do_read[p]) + continue; + + ssize_t nr = rw_exact(fd, (uint8_t *) gbe[p], + nf, p * partsize, IO_PREAD); + err_if(nr == -1); + if (nr != nf) + err(EXIT_FAILURE, + "%ld bytes read from '%s', expected %ld bytes\n", + nr, filename, nf); + + tnr += nr; + swap(p); /* handle big-endian host CPU */ + } + + printf("%ld bytes read from file '%s'\n", tnr, filename); +} + +void +cmd_setmac(void) +{ + int mac_updated = 0; + parseMacString(strMac, mac); + + printf("MAC address to be written: %s\n", strMac); + + for (int partnum = 0; partnum < 2; partnum++) { + if (!goodChecksum(part = partnum)) + continue; + + for (int w = 0; w < 3; w++) + setWord(w, partnum, mac[w]); + + printf("Wrote MAC address to part %d: ", partnum); + macf(partnum); + + cmd_setchecksum(); + mac_updated = 1; + } + + if (mac_updated) + return; + + errno = ECANCELED; + err(EXIT_FAILURE, "Error updating MAC address"); +} + +void +parseMacString(const char *strMac, uint16_t *mac) +{ + uint64_t total = 0; + if (strnlen(strMac, 20) != 17) { + errno = EINVAL; + err(EXIT_FAILURE, "Invalid MAC address string length"); + } + + for (uint8_t h, i = 0; i < 16; i += 3) { + if (i != 15) + if (strMac[i + 2] != ':') { + errno = EINVAL; + err(EXIT_FAILURE, + "Invalid MAC address separator '%c'", + strMac[i + 2]); + } + + int byte = i / 3; + + for (int nib = 0; nib < 2; nib++, total += h) { + if ((h = hextonum(strMac[i + nib])) > 15) { + errno = EINVAL; + err(EXIT_FAILURE, "Invalid character '%c'", + strMac[i + nib]); + } + + /* If random, ensure that local/unicast bits are set */ + if ((byte == 0) && (nib == 1)) + if ((strMac[i + nib] == '?') || + (strMac[i + nib] == 'x') || + (strMac[i + nib] == 'X')) /* random */ + h = (h & 0xE) | 2; /* local, unicast */ + + mac[byte >> 1] |= ((uint16_t ) h) + << ((8 * (byte % 2)) + (4 * (nib ^ 1))); + } + } + + if (!((total == 0) || (mac[0] & 1))) + return; + + errno = EINVAL; + + if (total == 0) + err(EXIT_FAILURE, "Invalid MAC (all-zero MAC address)"); + if (mac[0] & 1) + err(EXIT_FAILURE, "Invalid MAC (multicast bit set)"); +} + +uint8_t +hextonum(char ch) +{ + if ((ch >= '0') && (ch <= '9')) + return ch - '0'; + else if ((ch >= 'A') && (ch <= 'F')) + return ch - 'A' + 10; + else if ((ch >= 'a') && (ch <= 'f')) + return ch - 'a' + 10; + else if ((ch == '?') || (ch == 'x') || (ch == 'X')) + return rhex(); /* random hex value */ + else + return 16; /* error: invalid character */ +} + +uint8_t +rhex(void) +{ + static uint8_t n = 0, rnum[16]; + if (!n) + err_if(rw_exact(rfd, (uint8_t *) &rnum, + (n = 15) + 1, 0, IO_READ) == -1); + return rnum[n--] & 0xf; +} + +void +cmd_dump(void) +{ + for (int partnum = 0, numInvalid = 0; partnum < 2; partnum++) { + if ((cmd != cmd_dump) && (flags != O_RDONLY) && + (!nvmPartChanged[partnum])) + continue; + + if (!goodChecksum(partnum)) + ++numInvalid; + + printf("MAC (part %d): ", partnum); + macf(partnum); + hexdump(partnum); + + if (numInvalid > 1) { + errno = EINVAL; + err(EXIT_FAILURE, "dump: No valid checksums"); + } + } +} + +void +macf(int partnum) +{ + for (int c = 0; c < 3; c++) { + uint16_t val16 = word(c, partnum); + printf("%02x:%02x", val16 & 0xff, val16 >> 8); + if (c == 2) + printf("\n"); + else + printf(":"); + } +} + +void +hexdump(int partnum) +{ + for (int row = 0; row < 8; row++) { + printf("%08x ", row << 4); + for (int c = 0; c < 8; c++) { + uint16_t val16 = word((row << 3) + c, partnum); + if (c == 4) + printf(" "); + printf(" %02x %02x", val16 & 0xff, val16 >> 8); + } + printf("\n"); + } +} + +/* WARNING: Cannot fail. Make sure the file is valid. */ +void +cmd_setchecksum(void) +{ + uint16_t val16 = 0; + for (int c = 0; c < NVM_CHECKSUM_WORD; c++) + val16 += word(c, part); + + setWord(NVM_CHECKSUM_WORD, part, NVM_CHECKSUM - val16); +} + +void +cmd_brick(void) +{ + if (goodChecksum(part)) { + setWord(NVM_CHECKSUM_WORD, part, + ((word(NVM_CHECKSUM_WORD, part)) ^ 0xFF)); + } else { + errno = ECANCELED; + err(EXIT_FAILURE, "brick: Bad checksum in part %d", part); + } +} + +void +cmd_copy(void) +{ + nvmPartChanged[part ^ 1] = goodChecksum(part); + if (!nvmPartChanged[part ^ 1]) { + errno = ECANCELED; + err(EXIT_FAILURE, "copy: Bad checksum in part %d", part); + } +} + +void +cmd_swap(void) { + if(!(goodChecksum(0) || goodChecksum(1))) { + errno = ECANCELED; + err(EXIT_FAILURE, "swap: Bad checksums"); + } + + gbe[0] ^= gbe[1]; + gbe[1] ^= gbe[0]; + gbe[0] ^= gbe[1]; + + nvmPartChanged[0] = nvmPartChanged[1] = 1; +} + +int +goodChecksum(int partnum) +{ + uint16_t total = 0; + for(int w = 0; w <= NVM_CHECKSUM_WORD; w++) + total += word(w, partnum); + + if (total == NVM_CHECKSUM) + return 1; + + fprintf(stderr, "WARNING: BAD checksum in part %d\n", partnum); + return 0; +} + +void +writeGbe(void) +{ + ssize_t tnw = 0; + + for (int p = 0; p < 2; p++) { + if ((!nvmPartChanged[p]) || (flags == O_RDONLY)) + continue; + + swap(p); /* swap bytes on big-endian host CPUs */ + ssize_t nw = rw_exact(fd, (uint8_t *) gbe[p], nf, + p * partsize, IO_PWRITE); + err_if(nw == -1); + if (nw != nf) { + errno = ECANCELED; + err(EXIT_FAILURE, + "%ld bytes written to '%s', expected %ld bytes\n", + nw, filename, nf); + } + + tnw += nf; + } + + if ((flags != O_RDONLY) && (cmd != cmd_dump)) { + if (nvmPartChanged[0] || nvmPartChanged[1]) + printf("The following nvm words were written:\n"); + cmd_dump(); + } + + if ((!tnw) && (flags != O_RDONLY)) + fprintf(stderr, "No changes needed on file '%s'\n", filename); + else if (tnw) + printf("%ld bytes written to file '%s'\n", tnw, filename); + + xclose(&fd); +} + +void +xclose(int *fd) +{ + int saved_errno = errno; + int rval = 0; + + if (fd == NULL) { + errno = EBADF; + err(EXIT_FAILURE, "xclose: null pointer"); + } + if (*fd < 0) + return; + + errno = 0; + if ((rval = close(*fd)) < 0) { + if (errno != EINTR) + err(EXIT_FAILURE, "xclose: could not close"); + + /* regard EINTR as a successful close */ + rval = 0; + } + + *fd = -1; + + reset_caller_errno(rval); +} + +void +swap(int partnum) +{ + size_t w, x; + uint8_t *n = (uint8_t *) gbe[partnum]; + int e = 1; + + for (w = NVM_SIZE * ((uint8_t *) &e)[0], x = 1; w < NVM_SIZE; + w += 2, x += 2) { + n[w] ^= n[x]; + n[x] ^= n[w]; + n[w] ^= n[x]; + } +} + +int +open_on_eintr(const char *pathname, + int flags) +{ + int saved_errno = errno; + int rval = 0; + errno = 0; + + while (fs_retry(saved_errno, + rval = open(pathname, flags))); + + reset_caller_errno(rval); + return rval; +} + + +ssize_t +rw_exact(int fd, unsigned char *mem, size_t nrw, + off_t off, int rw_type) +{ + int saved_errno = errno; + ssize_t rval = 0; + ssize_t rc = 0; + size_t nrw_cur; + off_t off_cur; + void *mem_cur; + errno = 0; + + if (io_args(fd, mem, nrw, off, rw_type) == -1) + goto err_rw_exact; + + while (1) { + + /* Prevent theoretical overflow */ + if (if_err(rval >= 0 && (size_t)rval > (nrw - (size_t)rc), + EOVERFLOW)) + goto err_rw_exact; + + rc += rval; + if ((size_t)rc >= nrw) + break; + + mem_cur = (void *)(mem + (size_t)rc); + nrw_cur = (size_t)(nrw - (size_t)rc); + + if (if_err(off < 0, EOVERFLOW)) + goto err_rw_exact; + + off_cur = off + (off_t)rc; + + if ((rval = rw(fd, mem_cur, nrw_cur, off_cur, rw_type)) <= 0) + goto err_rw_exact; + } + + if (if_err((size_t)rc != nrw, EIO) || + (rval = rw_over_nrw(rc, nrw)) < 0) + goto err_rw_exact; + + reset_caller_errno(rval); + return rval; + +err_rw_exact: + return with_fallback_errno(EIO); +} + +ssize_t +rw(int fd, void *mem, size_t nrw, + off_t off, int rw_type) +{ + ssize_t rval = 0; + ssize_t r = -1; + int saved_errno = errno; + errno = 0; + + if (io_args(fd, mem, nrw, off, rw_type) == -1 || + if_err(mem == NULL, EFAULT) || + if_err(fd < 0, EBADF) || + if_err(off < 0, EFAULT) || + if_err(nrw == 0, EINVAL)) + return with_fallback_errno(EIO); + + do { + switch (rw_type) { + case IO_READ: + r = read(fd, mem, nrw); + break; + case IO_WRITE: + r = write(fd, mem, nrw); + break; + case IO_PREAD: + r = pread(fd, mem, nrw, off); + break; + case IO_PWRITE: + r = pwrite(fd, mem, nrw, off); + break; + default: + errno = EINVAL; + break; + } + + } while (rw_retry(saved_errno, r)); + + if ((rval = rw_over_nrw(r, nrw)) < 0) + return with_fallback_errno(EIO); + + reset_caller_errno(rval); + return rval; +} + +int +io_args(int fd, void *mem, size_t nrw, + off_t off, int rw_type) +{ + int saved_errno = errno; + errno = 0; + + if (if_err(mem == NULL, EFAULT) || + if_err(fd < 0, EBADF) || + if_err(off < 0, ERANGE) || + if_err(!nrw, EPERM) || /* TODO: toggle zero-byte check */ + if_err(nrw > (size_t)SSIZE_MAX, ERANGE) || + if_err(((size_t)off + nrw) < (size_t)off, ERANGE) || + if_err(rw_type > IO_PWRITE, EINVAL)) + goto err_io_args; + + reset_caller_errno(0); + return 0; + +err_io_args: + return with_fallback_errno(EINVAL); +} + +ssize_t +rw_over_nrw(ssize_t r, size_t nrw) +{ + if (if_err(!nrw, EIO) || + (r == -1) || + if_err((size_t)r > SSIZE_MAX, ERANGE) || + if_err((size_t)r > nrw, ERANGE)) + return with_fallback_errno(EIO); + + return r; +} + +int +with_fallback_errno(int fallback) +{ + if (!errno) + errno = fallback; + return -1; +} + +/* two functions that reduce sloccount by + * two hundred lines */ +int +if_err(int condition, int errval) +{ + if (!condition) + return 0; + if (errval) + errno = errval; + return 1; +} +int +if_err_sys(int condition) +{ + if (!condition) + return 0; + return 1; +} + +#define fs_err_retry() \ + do { \ + if ((rval == -1) && \ + (errno == EINTR)) { \ + return 1; \ + } \ + if (rval >= 0 && !errno) { \ + errno = saved_errno; \ + } \ + } while(0) + +int +fs_retry(int saved_errno, int rval) +{ + fs_err_retry(); + return 0; +} + +int +rw_retry(int saved_errno, ssize_t rval) +{ + fs_err_retry(); + return 0; +} diff --git a/util/spkmodem_decode/.gitignore b/util/spkmodem_decode/.gitignore deleted file mode 100644 index 42814fe4..00000000 --- a/util/spkmodem_decode/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -spkmodem-recv -spkmodem-decode diff --git a/util/spkmodem_decode/Makefile b/util/spkmodem_decode/Makefile deleted file mode 100644 index b00c4f43..00000000 --- a/util/spkmodem_decode/Makefile +++ /dev/null @@ -1,30 +0,0 @@ -# SPDX-License-Identifier: MIT -# SPDX-FileCopyrightText: 2022,2026 Leah Rowe <leah@libreboot.org> -# SPDX-FileCopyrightText: 2023 Riku Viitanen <riku.viitanen@protonmail.com> - -CC?=cc -CFLAGS?=-Os -Wall -Wextra -Werror -pedantic -std=c90 -DESTDIR?= -PREFIX?=/usr/local -INSTALL?=install - -PROG=spkmodem-decode - -all: $(PROG) - -$(PROG): spkmodem-decode.c - $(CC) $(CFLAGS) spkmodem-decode.c -o $(PROG) - -install: $(PROG) - mkdir -p $(DESTDIR)$(PREFIX)/bin/ - install -c $(PROG) $(DESTDIR)$(PREFIX)/bin/ - -uninstall: - rm -f $(DESTDIR)$(PREFIX)/bin/$(PROG) - -clean: - rm -f $(PROG) - -distclean: clean - -.PHONY: all install uninstall clean distclean diff --git a/util/spkmodem_decode/spkmodem-decode.c b/util/spkmodem_decode/spkmodem-decode.c deleted file mode 100644 index 3b3b33f8..00000000 --- a/util/spkmodem_decode/spkmodem-decode.c +++ /dev/null @@ -1,725 +0,0 @@ -/* - * SPDX-License-Identifier: GPL-2.0-or-later - * Copyright (c) 2013 Free Software Foundation, Inc. - * Copyright (c) 2023, 2026 Leah Rowe <leah@libreboot.org> - * - * This program receives text encoded as pulses on the PC speaker, - * and decodes them via simple FSK (Frequency Shift Keying) - * demodulation and FIR (Finite Impulse Response) frequency - * discriminator. - * - * It waits for specific tones at specific intervals. - * It detects tones within the audio stream and reconstructs - * characters bit-by-bit as the encoded modem signal is received. - * This is performance-efficient on most CPUs, and has relatively - * high tolerance for noisy signals (similar to techniques used - * for data stored on audio cassette tapes). - * - * This is a special interface provided by coreboot and GNU GRUB, - * for computers that lack serial ports (useful for debugging). - * Note that GRUB and coreboot can both send these signals; this - * tool merely decodes them. This tool does not *encode*, only - * decode. - * - * Usage example (NOTE: little endian!): - * parec --channels=1 --rate=48000 --format=s16le | ./spkmodem-decode - * - * Originally provided by GNU GRUB, this version is a heavily - * modified fork that complies with the OpenBSD Kernel Source - * File Style Guide (KNF) instead of GNU coding standards; it - * emphasises strict error handling, portability and code - * quality, as characterised by OpenBSD projects. Several magic - * numbers have been tidied up, calculated (not hardcoded) and - * thoroughly explained, unlike in the original version. - * - * The original version was essentially a blob, masquerading as - * source code. This forked source code is therefore the result - * of extensive reverse engineering (of the GNU source code)! - * This cleaned up code and extensive commenting will thoroughly - * explain how the decoding works. This was done as an academic - * exercise in 2023, continuing in 2026. - * - * This fork of spkmodem-recv, called spkmodem-decode, is provided - * with Libreboot releases: - * https://libreboot.org/ - * - * The original GNU version is here, if you're morbidly curious: - * https://cgit.git.savannah.gnu.org/cgit/grub.git/plain/util/spkmodem-recv.c?id=3dce38eb196f47bdf86ab028de74be40e13f19fd - * - * Libreboot's version was renamed to spkmodem-decode on 12 March 2026, - * since Libreboot's version no longer closely resembles the GNU - * version at all; ergo, a full rename was in order. GNU's version - * was called spkmodem-recv. - */ - -#define _POSIX_SOURCE - -/* - * For OpenBSD define, to detect version - * for deciding whether to use pledge(2) - */ -#ifdef __OpenBSD__ -#include <sys/param.h> -#endif - -#include <errno.h> -#include <limits.h> -#include <stdio.h> -#include <stdarg.h> -#include <stdlib.h> -#include <string.h> -#include <unistd.h> - -/* - * spkmodem is essentially using FSK (Frequency Shift Keying) - * with two primary tones representing encoded bits, - * separated by a framing tone. - * Very cheap on CPU cycles and avoids needing something more - * expensive like FFT or Goertzel filters, and tolerates - * weak/noisy signals. - */ - -/* - * Frequency of audio in Hz - * WARNING: if changing, make sure to adjust - * SAMPLES_PER_FRAME accordingly (see maths below) - */ -#define SAMPLE_RATE 48000 - -/* - * One analysis frame spans 5 ms. - * - * frame_time = SAMPLES_PER_FRAME / SAMPLE_RATE - * - * With the default sample rate (48 kHz): - * - * frame_time = N / 48000 - * 0.005 s = N / 48000 - * N = 0.005 × 48000 = 240 samples - */ -#define SAMPLES_PER_FRAME 240 - -/* - * Number of analysis frames per second. - * - * Each increment in the frequency counters corresponds - * roughly to this many Hertz of tone frequency. - * - * With the default values: - * FRAME_RATE = 48000 / 240 = 200 Hz - */ -#define FRAME_RATE ((SAMPLE_RATE) / (SAMPLES_PER_FRAME)) - -/* - * Two FIR windows are maintained; one for data tone, - * and one for the separator tone. They are positioned - * one frame apart in the ring buffer. - */ -#define MAX_SAMPLES (2 * (SAMPLES_PER_FRAME)) - -/* - * Approx byte offset for ring buffer span, just for - * easier debug output correlating to the audio stream. - */ -#define SAMPLE_OFFSET ((MAX_SAMPLES) * (sizeof(short))) - -/* - * Expected tone ranges (approximate, derived from spkmodem). - * These values are intentionally wide because real-world setups - * often involve microphones, room acoustics, and cheap ADCs. - */ -#define SEP_TONE_MIN_HZ 1000 -#define SEP_TONE_MAX_HZ 3000 - -#define SEP_TOLERANCE_PULSES \ - (((SEP_TONE_MAX_HZ) - (SEP_TONE_MIN_HZ)) / (2 * (FRAME_RATE))) - -#define DATA_TONE_MIN_HZ 3000 -#define DATA_TONE_MAX_HZ 12000 - -/* Mid point used to distinguish the two data tones. */ -#define DATA_TONE_THRESHOLD_HZ 5000 - -/* - * Convert tone frequency ranges into pulse counts within the - * sliding analysis window. - * - * pulse_count = tone_frequency / FRAME_RATE - * where FRAME_RATE = SAMPLE_RATE / SAMPLES_PER_FRAME. - */ -#define FREQ_SEP_MIN ((SEP_TONE_MIN_HZ) / (FRAME_RATE)) -#define FREQ_SEP_MAX ((SEP_TONE_MAX_HZ) / (FRAME_RATE)) - -#define FREQ_DATA_MIN ((DATA_TONE_MIN_HZ) / (FRAME_RATE)) -#define FREQ_DATA_MAX ((DATA_TONE_MAX_HZ) / (FRAME_RATE)) - -#define FREQ_DATA_THRESHOLD ((DATA_TONE_THRESHOLD_HZ) / (FRAME_RATE)) - -/* - * These determine how long the program will wait during - * tone auto-detection, before shifting to defaults. - * It is done every LEARN_FRAMES number of frames. - */ -#define LEARN_SECONDS 1 -#define LEARN_FRAMES ((LEARN_SECONDS) * (FRAME_RATE)) - -/* - * Sample amplitude threshold used to convert the waveform - * into a pulse stream. Values near zero are regarded as noise. - */ -#define THRESHOLD 500 - -#define READ_BUF 4096 - -struct decoder_state { - unsigned char pulse[MAX_SAMPLES]; - - signed short inbuf[READ_BUF]; - size_t inpos; - size_t inlen; - - int ringpos; - int sep_pos; - - /* - * Sliding window pulse counters - * used to detect modem tones - */ - int freq_data; - int freq_separator; - int sample_count; - - int ascii_bit; - unsigned char ascii; - - int debug; - int swap_bytes; - - /* dynamic separator calibration */ - int sep_sum; - int sep_samples; - int sep_min; - int sep_max; - - /* for automatic tone detection */ - int freq_min; - int freq_max; - int freq_threshold; - int learn_frames; - - /* previous sample used for edge detection */ - signed short prev_sample; -}; - -static const char *argv0; - -/* - * 16-bit little endian words are read - * continuously. we will swap them, if - * the host cpu is big endian. - */ -static int host_is_big_endian(void); - -/* main loop */ -static void handle_audio(struct decoder_state *st); - -/* separate tone tolerances */ -static void select_separator_tone(struct decoder_state *st); -static int is_valid_signal(struct decoder_state *st); - -/* output to terminal */ -static int set_ascii_bit(struct decoder_state *st); -static void print_char(struct decoder_state *st); -static void reset_char(struct decoder_state *st); - -/* process samples/frames */ -static void decode_pulse(struct decoder_state *st); -static signed short read_sample(struct decoder_state *st); -static void read_words(struct decoder_state *st); - -/* continually adjust tone */ -static void detect_tone(struct decoder_state *st); -static int silent_signal(struct decoder_state *st); -static void select_low_tone(struct decoder_state *st); - -/* debug */ -static void print_stats(struct decoder_state *st); - -/* error handling / usage */ -static void err(int errval, const char *msg, ...); -static void usage(void); -static const char *progname(void); - -/* portability (old systems) */ -int getopt(int, char * const *, const char *); -extern char *optarg; -extern int optind; -extern int opterr; -extern int optopt; - -#ifndef CHAR_BIT -#define CHAR_BIT 8 -#endif - -typedef char static_assert_char_is_8_bits[(CHAR_BIT == 8) ? 1 : -1]; -typedef char static_assert_char_is_1[(sizeof(char) == 1) ? 1 : -1]; -typedef char static_assert_short[(sizeof(short) == 2) ? 1 : -1]; -typedef char static_assert_int_is_4[(sizeof(int) >= 4) ? 1 : -1]; -typedef char static_assert_twos_complement[ - ((-1 & 3) == 3) ? 1 : -1 -]; - -int -main(int argc, char **argv) -{ - struct decoder_state st; - int c; - - argv0 = argv[0]; - -#if defined (__OpenBSD__) && defined(OpenBSD) -#if OpenBSD >= 509 - if (pledge("stdio", NULL) == -1) - err(errno, "pledge"); -#endif -#endif - - memset(&st, 0, sizeof(st)); - - while ((c = getopt(argc, argv, "d")) != -1) { - if (c != 'd') - usage(); - st.debug = 1; - break; - } - - /* fallback in case tone detection fails */ - st.freq_min = 100000; - st.freq_max = 0; - st.freq_threshold = FREQ_DATA_THRESHOLD; - - /* - * Used for separator calibration - */ - st.sep_min = FREQ_SEP_MIN; - st.sep_max = FREQ_SEP_MAX; - - st.ascii_bit = 7; - - st.ringpos = 0; - st.sep_pos = SAMPLES_PER_FRAME; - - if (host_is_big_endian()) - st.swap_bytes = 1; - - setvbuf(stdout, NULL, _IONBF, 0); - - for (;;) - handle_audio(&st); - - return EXIT_SUCCESS; -} - -static int -host_is_big_endian(void) -{ - unsigned int x = 1; - return (*(unsigned char *)&x == 0); -} - -static void -handle_audio(struct decoder_state *st) -{ - int sample; - - /* - * If the modem signal disappears for several (read: 3) - * frames, discard the partially assembled character. - */ - if (st->sample_count >= (3 * SAMPLES_PER_FRAME) || - st->freq_separator <= 0) - reset_char(st); - - st->sample_count = 0; - - /* process exactly one frame */ - for (sample = 0; sample < SAMPLES_PER_FRAME; sample++) - decode_pulse(st); - - select_separator_tone(st); - - if (set_ascii_bit(st) < 0) - print_char(st); - - /* Detect tone per each frame */ - detect_tone(st); -} - -/* - * collect separator tone statistics - * (and auto-adjust tolerances) - */ -static void -select_separator_tone(struct decoder_state *st) -{ - int avg; - - if (!is_valid_signal(st)) - return; - - st->sep_sum += st->freq_separator; - st->sep_samples++; - - if (st->sep_samples != 50) - return; - - avg = st->sep_sum / st->sep_samples; - - st->sep_min = avg - SEP_TOLERANCE_PULSES; - st->sep_max = avg + SEP_TOLERANCE_PULSES; - - /* reset calibration accumulators */ - st->sep_sum = 0; - st->sep_samples = 0; - - if (st->debug) - printf("separator calibrated: %dHz\n", - avg * FRAME_RATE); -} - -/* - * Verify that the observed pulse densities fall within the - * expected ranges for spkmodem tones. This prevents random noise - * from being misinterpreted as data. - */ -static int -is_valid_signal(struct decoder_state *st) -{ - if (st->freq_data <= 0) - return 0; - - if (st->freq_separator < st->sep_min || - st->freq_separator > st->sep_max) - return 0; - - return 1; -} - -/* - * Each validated frame contributes one bit of modem data. - * Bits are accumulated MSB-first into the ASCII byte. - */ -static int -set_ascii_bit(struct decoder_state *st) -{ - if (st->debug) - print_stats(st); - - if (!is_valid_signal(st)) - return st->ascii_bit; - - if (st->freq_data < st->freq_threshold) - st->ascii |= (1 << st->ascii_bit); - - st->ascii_bit--; - - return st->ascii_bit; -} - -static void -print_char(struct decoder_state *st) -{ - if (st->debug) - printf("<%c,%x>", st->ascii, st->ascii); - else - putchar(st->ascii); - - reset_char(st); -} - -static void -reset_char(struct decoder_state *st) -{ - st->ascii = 0; - st->ascii_bit = 7; -} - -/* - * Main demodulation step (moving-sum FIR filter). - */ -static void -decode_pulse(struct decoder_state *st) -{ - unsigned char old_ring, old_sep; - unsigned char new_pulse; - signed short sample; - int ringpos; - int sep_pos; - int diff_edge; - int diff_amp; - - ringpos = st->ringpos; - sep_pos = st->sep_pos; - - /* - * Sliding rectangular FIR (Finite Impulse Response) filter. - * - * After thresholding, the signal becomes a stream of 0/1 pulses. - * The decoder measures pulse density over two windows: - * - * freq_data: pulses in the "data" window - * freq_separator: pulses in the "separator" window - * - * Instead of calculating each window every time (O(N) per frame), we - * update the window sums incrementally: - * - * sum_new = sum_old - pulse_leaving + pulse_entering - * - * This keeps the filter O(1) per sample instead of O(N). - * The technique is equivalent to a rectangular FIR filter - * implemented as a sliding moving sum. - * - * The two windows are exactly SAMPLES_PER_FRAME apart in the ring - * buffer, so the pulse leaving the data window is simultaneously - * entering the separator window. - */ - old_ring = st->pulse[ringpos]; - old_sep = st->pulse[sep_pos]; - st->freq_data -= old_ring; - st->freq_data += old_sep; - st->freq_separator -= old_sep; - - sample = read_sample(st); - - /* - * Avoid startup edge. Since - * it's zero at startup, this - * may wrongly produce a pulse - */ - if (st->sample_count == 0) - st->prev_sample = sample; - - /* - * Detect edges instead of amplitude. - * This is more tolerant of weak microphones - * and speaker distortion.. - * - * However, we check both slope edges and - * amplitude, to mitagate noise. - */ - diff_amp = sample; - diff_edge = sample - st->prev_sample; - if (diff_edge < 0) - diff_edge = -diff_edge; - if (diff_amp < 0) - diff_amp = -diff_amp; - if (diff_edge > THRESHOLD && - diff_amp > THRESHOLD) - new_pulse = 1; - else - new_pulse = 0; - st->prev_sample = sample; - - st->pulse[ringpos] = new_pulse; - st->freq_separator += new_pulse; - - /* - * Advance both FIR windows through the ring buffer. - * The separator window always stays one frame ahead - * of the data window. - */ - if (++ringpos >= MAX_SAMPLES) - ringpos = 0; - if (++sep_pos >= MAX_SAMPLES) - sep_pos = 0; - - st->ringpos = ringpos; - st->sep_pos = sep_pos; - - st->sample_count++; -} - -static signed short -read_sample(struct decoder_state *st) -{ - signed short sample; - unsigned short u; - - while (st->inpos >= st->inlen) - read_words(st); - - sample = st->inbuf[st->inpos++]; - - if (st->swap_bytes) { - u = (unsigned short)sample; - u = (u >> 8) | (u << 8); - - sample = (signed short)u; - } - - return sample; -} - -static void -read_words(struct decoder_state *st) -{ - size_t n; - - n = fread(st->inbuf, sizeof(st->inbuf[0]), - READ_BUF, stdin); - - if (n != 0) { - st->inpos = 0; - st->inlen = n; - - return; - } - - if (ferror(stdin)) - err(errno, "stdin read"); - if (feof(stdin)) - exit(EXIT_SUCCESS); -} - -/* - * Automatically detect spkmodem tone - */ -static void -detect_tone(struct decoder_state *st) -{ - if (st->learn_frames >= LEARN_FRAMES) - return; - - st->learn_frames++; - - if (silent_signal(st)) - return; - - select_low_tone(st); - - if (st->learn_frames != LEARN_FRAMES) - return; - - /* - * If the observed frequencies are too close, - * learning likely failed (only one tone seen). - * Keep the default threshold. - */ - if (st->freq_max - st->freq_min < 2) - return; - - st->freq_threshold = - (st->freq_min + st->freq_max) / 2; - - if (st->debug) - printf("auto threshold: %dHz\n", - st->freq_threshold * FRAME_RATE); -} - -/* - * Ignore silence / near silence. - * Both FIR windows will be near zero when no signal exists. - */ -static int -silent_signal(struct decoder_state *st) -{ - return (st->freq_data <= 2 && - st->freq_separator <= 2); -} - -/* - * Choose the lowest active tone. - * Separator frames carry tone in the separator window, - * data frames carry tone in the data window. - */ -static void -select_low_tone(struct decoder_state *st) -{ - int f; - - f = st->freq_data; - - if (f <= 0 || (st->freq_separator > 0 && - st->freq_separator < f)) - f = st->freq_separator; - - if (f <= 0) - return; - - if (f < st->freq_min) - st->freq_min = f; - - if (f > st->freq_max) - st->freq_max = f; -} - -static void -print_stats(struct decoder_state *st) -{ - long pos; - - int data_hz = st->freq_data * FRAME_RATE; - int sep_hz = st->freq_separator * FRAME_RATE; - int sep_hz_min = st->sep_min * FRAME_RATE; - int sep_hz_max = st->sep_max * FRAME_RATE; - - if ((pos = ftell(stdin)) == -1) { - printf("%d %d %d data=%dHz sep=%dHz(min %dHz %dHz)\n", - st->freq_data, - st->freq_separator, - st->freq_threshold, - data_hz, - sep_hz, - sep_hz_min, - sep_hz_max); - return; - } - - printf("%d %d %d @%ld data=%dHz sep=%dHz(min %dHz %dHz)\n", - st->freq_data, - st->freq_separator, - st->freq_threshold, - pos - SAMPLE_OFFSET, - data_hz, - sep_hz, - sep_hz_min, - sep_hz_max); -} - -static void -err(int errval, const char *msg, ...) -{ - va_list ap; - - fprintf(stderr, "%s: ", progname()); - - va_start(ap, msg); - vfprintf(stderr, msg, ap); - va_end(ap); - - fprintf(stderr, ": %s\n", strerror(errval)); - exit(EXIT_FAILURE); -} - -static void -usage(void) -{ - fprintf(stderr, "usage: %s [-d]\n", progname()); - exit(EXIT_FAILURE); -} - -static const char * -progname(void) -{ - const char *p; - - if (argv0 == NULL || *argv0 == '\0') - return ""; - - p = strrchr(argv0, '/'); - - if (p) - return p + 1; - else - return argv0; -} diff --git a/util/spkmodem_recv/.gitignore b/util/spkmodem_recv/.gitignore new file mode 100644 index 00000000..2f5c946c --- /dev/null +++ b/util/spkmodem_recv/.gitignore @@ -0,0 +1 @@ +spkmodem-recv diff --git a/util/spkmodem_recv/Makefile b/util/spkmodem_recv/Makefile new file mode 100644 index 00000000..0132baf8 --- /dev/null +++ b/util/spkmodem_recv/Makefile @@ -0,0 +1,14 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +CC?=cc +CFLAGS?=-Os -Wall -Wextra +DESTDIR?= +PREFIX?=/usr/local +INSTALL?=install + +spkmodem-recv: + $(CC) $(CFLAGS) -o $@ $@.c +install: spkmodem-recv + $(INSTALL) -d $(DESTDIR)$(PREFIX)/bin/ + $(INSTALL) $< -t $(DESTDIR)$(PREFIX)/bin/ +clean: + rm -f spkmodem-recv diff --git a/util/spkmodem_recv/spkmodem-recv.c b/util/spkmodem_recv/spkmodem-recv.c new file mode 100644 index 00000000..6fcd356f --- /dev/null +++ b/util/spkmodem_recv/spkmodem-recv.c @@ -0,0 +1,123 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* SPDX-FileCopyrightText: 2013 Free Software Foundation, Inc. */ +/* Usage: parec --channels=1 --rate=48000 --format=s16le | ./spkmodem-recv */ + +/* Forked from coreboot's version, at util/spkmodem_recv/ in coreboot.git, + * revision 5c2b5fcf2f9c9259938fd03cfa3ea06b36a007f0 as of 3 January 2022. + * This version is heavily modified, re-written based on OpenBSD Kernel Source + * File Style Guide (KNF); this change is Copyright 2023 Leah Rowe. */ + +#include <err.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#define SAMPLES_PER_FRAME 240 +#define MAX_SAMPLES (2 * SAMPLES_PER_FRAME) +#define FREQ_SEP_MIN 5 +#define FREQ_SEP_MAX 15 +#define FREQ_DATA_MIN 15 +#define FREQ_DATA_THRESHOLD 25 +#define FREQ_DATA_MAX 60 +#define THRESHOLD 500 + +#define reset_char() ascii = 0, ascii_bit = 7 + +signed short frame[MAX_SAMPLES], pulse[MAX_SAMPLES]; +int ringpos, debug, freq_data, freq_separator, sample_count, ascii_bit = 7; +char ascii = 0; + +void handle_audio(void); +void decode_pulse(void); +int set_ascii_bit(void); +void print_char(void); +void print_stats(void); + +int +main(int argc, char *argv[]) +{ + int c; +#ifdef __OpenBSD__ + if (pledge("stdio", NULL) == -1) + err(EXIT_FAILURE, "pledge"); +#endif + while ((c = getopt(argc, argv, "d")) != -1) + if (!(debug = (c == 'd'))) { + errno = EINVAL; + err(EXIT_FAILURE, "%c: Invalid option", c); + } + setvbuf(stdout, NULL, _IONBF, 0); + while (!feof(stdin)) + handle_audio(); + return EXIT_SUCCESS; +} + +void +handle_audio(void) +{ + if (sample_count > (3 * SAMPLES_PER_FRAME)) + sample_count = reset_char(); + if ((freq_separator <= FREQ_SEP_MIN) || (freq_separator >= FREQ_SEP_MAX) + || (freq_data <= FREQ_DATA_MIN) || (freq_data >= FREQ_DATA_MAX)) { + decode_pulse(); + return; + } + + if (set_ascii_bit() < 0) + print_char(); + sample_count = 0; + for (int sample = 0; sample < SAMPLES_PER_FRAME; sample++) + decode_pulse(); +} + +void +decode_pulse(void) +{ + int next_ringpos = (ringpos + SAMPLES_PER_FRAME) % MAX_SAMPLES; + freq_data -= pulse[ringpos]; + freq_data += pulse[next_ringpos]; + freq_separator -= pulse[next_ringpos]; + + fread(frame + ringpos, 1, sizeof(frame[0]), stdin); + if (ferror(stdin) != 0) + err(EXIT_FAILURE, "Could not read from frame."); + + if ((pulse[ringpos] = (abs(frame[ringpos]) > THRESHOLD) ? 1 : 0)) + ++freq_separator; + ++ringpos; + ringpos %= MAX_SAMPLES; + ++sample_count; +} + +int +set_ascii_bit(void) +{ + if (debug) + print_stats(); + if (freq_data < FREQ_DATA_THRESHOLD) + ascii |= (1 << ascii_bit); + --ascii_bit; + return ascii_bit; +} + +void +print_char(void) +{ + if (debug) + printf("<%c, %x>", ascii, ascii); + else + printf("%c", ascii); + reset_char(); +} + +void +print_stats(void) +{ + long stdin_pos; + if ((stdin_pos = ftell(stdin)) == -1) + err(EXIT_FAILURE, NULL); + printf ("%d %d %d @%ld\n", freq_data, freq_separator, + FREQ_DATA_THRESHOLD, stdin_pos - sizeof(frame)); +} |
