diff options
Diffstat (limited to 'util/nvmutil')
| -rw-r--r-- | util/nvmutil/AUTHORS | 1 | ||||
| -rw-r--r-- | util/nvmutil/COPYING | 3 | ||||
| -rw-r--r-- | util/nvmutil/Makefile | 20 | ||||
| -rw-r--r-- | util/nvmutil/nvmutil.c | 866 |
4 files changed, 705 insertions, 185 deletions
diff --git a/util/nvmutil/AUTHORS b/util/nvmutil/AUTHORS index f3c00385..f38ea210 100644 --- a/util/nvmutil/AUTHORS +++ b/util/nvmutil/AUTHORS @@ -1 +1,2 @@ Leah Rowe +Riku Viitanen diff --git a/util/nvmutil/COPYING b/util/nvmutil/COPYING index 784581dd..a6ecf266 100644 --- a/util/nvmutil/COPYING +++ b/util/nvmutil/COPYING @@ -1,4 +1,5 @@ -Copyright (C) 2022, 2023 Leah Rowe <leah@libreboot.org> +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 diff --git a/util/nvmutil/Makefile b/util/nvmutil/Makefile index f25f6dd5..b8ec2ad3 100644 --- a/util/nvmutil/Makefile +++ b/util/nvmutil/Makefile @@ -1,16 +1,24 @@ # SPDX-License-Identifier: MIT -# SPDX-FileCopyrightText: 2022 Leah Rowe <leah@libreboot.org> +# SPDX-FileCopyrightText: 2022,2025 Leah Rowe <leah@libreboot.org> # SPDX-FileCopyrightText: 2023 Riku Viitanen <riku.viitanen@protonmail.com> -CC=cc -CFLAGS=-Os -Wall -Wextra -Werror -pedantic -PREFIX?=/usr/bin +CC?=cc +CFLAGS?=-Os -Wall -Wextra -Werror -pedantic +DESTDIR?= +PREFIX?=/usr/local +INSTALL?=install nvm: nvmutil.c $(CC) $(CFLAGS) nvmutil.c -o nvm -install: nvm - install nvm $(PREFIX)/nvm +install: + $(INSTALL) nvm $(DESTDIR)$(PREFIX)/bin/nvm + +uninstall: + rm -f $(DESTDIR)$(PREFIX)/bin/nvm + +distclean: + rm -f nvm clean: rm -f nvm diff --git a/util/nvmutil/nvmutil.c b/util/nvmutil/nvmutil.c index 35abbfae..319fe552 100644 --- a/util/nvmutil/nvmutil.c +++ b/util/nvmutil/nvmutil.c @@ -1,174 +1,383 @@ /* SPDX-License-Identifier: MIT */ -/* SPDX-FileCopyrightText: 2022, 2023 Leah Rowe <leah@libreboot.org> */ -/* SPDX-FileCopyrightText: 2023 Riku Viitanen <riku.viitanen@protonmail.com> */ +/* Copyright (c) 2022-2026 Leah Rowe <leah@libreboot.org> */ +/* Copyright (c) 2023 Riku Viitanen <riku.viitanen@protonmail.com> */ #include <sys/stat.h> -#include <dirent.h> -#include <err.h> #include <errno.h> #include <fcntl.h> +#include <stdarg.h> #include <stdint.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> -void cmd_setchecksum(void), cmd_brick(void), cmd_copy(void), writeGbeFile(void), - cmd_dump(void), cmd_setmac(void), readGbeFile(void), showmac(int partnum), - hexdump(int partnum), handle_endianness(int partnum), openFiles(const char *path); -int macAddress(const char *strMac, uint16_t *mac), validChecksum(int partnum); -uint8_t hextonum(char chs), rhex(void); +static void reset_global_state(void); +static void set_cmd(int, char **); +static void check_cmd_args(int, char **); +static void set_io_flags(int, char **); +static void open_files(void); +static void xopen(int *, const char *, int, struct stat *); +static void read_gbe(void); +static void read_gbe_part(int, int); +static void cmd_setmac(void); +static void parse_mac_string(void); +static void set_mac_byte(int); +static void check_mac_separator(int); +static void set_mac_nib(int, int); +static uint8_t hextonum(char); +static uint8_t rhex(void); +static void read_file_PERFECTLY_or_die(int, void *, size_t, + off_t, const char *, const char *); +static int check_read_or_die(const char *, + ssize_t, size_t, int, const char *); +static int write_mac_part(int); +static void cmd_dump(void); +static void print_mac_address(int); +static void hexdump(int); +static void cmd_setchecksum(void); +static void cmd_brick(void); +static void cmd_copy(void); +static void cmd_swap(void); +static int good_checksum(int); +static uint16_t word(int, int); +static void set_word(int, int, uint16_t); +static void check_bound(int, int); +static void write_gbe(void); +static void write_gbe_part(int); +static void swap(int); +static void usage(void); +static void err(int, const char *, ...); +static const char *getnvmprogname(void); +static void set_err(int); + +#define NVM_CHECKSUM 0xBABA +#define NVM_CHECKSUM_WORD 0x3F +#define NVM_SIZE 128 -#define COMMAND argv[2] -#define MAC_ADDRESS argv[3] -#define PARTNUM argv[3] #define SIZE_4KB 0x1000 +#define SIZE_8KB 0x2000 +#define SIZE_16KB 0x4000 +#define SIZE_128KB 0x20000 + +#define MAX_RETRY_READ 30 + +#define items(x) (sizeof((x)) / sizeof((x)[0])) -uint16_t buf16[SIZE_4KB], mac[3] = {0, 0, 0}; -uint8_t *buf = (uint8_t *) &buf16; -size_t nf = 128, gbe[2]; -uint8_t nvmPartModified[2] = {0, 0}, skipread[2] = {0, 0}; -int e = 1, flags = O_RDWR, rfd, fd, part, gbeFileModified = 0; +static uint8_t buf[SIZE_8KB]; +static uint16_t macbuf[3]; +static off_t partsize; -const char *strMac = NULL, *strRMac = "??:??:??:??:??:??", *filename = NULL; +static int flags; +static int rfd = -1; +static int fd = -1; +static int part; +static int invert; +static int part_modified[2]; -typedef struct op { - char *str; +static const char *mac = NULL; +static const char *rmac = "xx:xx:xx:xx:xx:xx"; +static const char *fname = ""; +static const char *argv0; + +struct op { + const 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 = writeGbeFile, .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() errno = errno ? errno : ECANCELED -#define err_if(x) if (x) err(ERR(), "%s", filename) - -#define xopen(f,l,p) if (opendir(l) != NULL) err(errno = EISDIR, "%s", l); \ - if ((f = open(l, p)) == -1) err(ERR(), "%s", l); \ - if (fstat(f, &st) == -1) err(ERR(), "%s", l) +static const struct op ops[] = { + { "dump", cmd_dump, 3 }, + { "setmac", cmd_setmac, 3 }, + { "swap", cmd_swap, 3 }, + { "copy", cmd_copy, 4 }, + { "brick", cmd_brick, 4 }, + { "setchecksum", cmd_setchecksum, 4 }, +}; -#define word(pos16, partnum) buf16[pos16 + (partnum << 11)] -#define setWord(pos16, p, val16) if ((gbeFileModified = 1) && \ - word(pos16, p) != val16) nvmPartModified[p] = 1 | (word(pos16, p) = val16) +static void (*cmd)(void) = NULL; int main(int argc, char *argv[]) { - if (argc < 3) { - fprintf(stderr, "USAGE:\n"); - fprintf(stderr, " %s FILE dump\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]); - err(errno = ECANCELED, "Too few arguments"); - } - flags = (strcmp(COMMAND, "dump") == 0) ? O_RDONLY : flags; - filename = argv[1]; + argv0 = argv[0]; + if (argc < 2) + usage(); + + reset_global_state(); + fname = argv[1]; + #ifdef __OpenBSD__ - err_if(unveil("/dev/urandom", "r") == -1); - err_if(unveil(filename, flags == O_RDONLY ? "r" : "rw") == -1); - err_if(pledge(flags == O_RDONLY ? "stdio rpath" : "stdio rpath wpath", - NULL) == -1); + if (pledge("stdio rpath wpath unveil", NULL) == -1) + err(ECANCELED, "pledge"); + if (unveil("/dev/urandom", "r") == -1) + err(ECANCELED, "unveil '/dev/urandom'"); #endif - openFiles(filename); + + set_cmd(argc, argv); + check_cmd_args(argc, argv); + set_io_flags(argc, argv); + #ifdef __OpenBSD__ - err_if(pledge("stdio", NULL) == -1); + if (flags == O_RDONLY) { + if (unveil(fname, "r") == -1) + err(ECANCELED, "unveil ro '%s'", fname); + if (unveil(NULL, NULL) == -1) + err(ECANCELED, "unveil block (ro)"); + if (pledge("stdio rpath", NULL) == -1) + err(ECANCELED, "pledge ro (kill unveil)"); + } else { + if (unveil(fname, "rw") == -1) + err(ECANCELED, "unveil rw '%s'", fname); + if (unveil(NULL, NULL) == -1) + err(ECANCELED, "unveil block (rw)"); + if (pledge("stdio rpath wpath", NULL) == -1) + err(ECANCELED, "pledge rw (kill unveil)"); + } +#endif + + open_files(); + +#ifdef __OpenBSD__ + if (pledge("stdio", NULL) == -1) + err(ECANCELED, "pledge stdio (main)"); #endif - for (int i = 0; i < 6; i++) - if (strcmp(COMMAND, op[i].str) == 0) - if ((cmd = argc >= op[i].args ? op[i].cmd : NULL)) - break; - if (cmd == cmd_setmac) - strMac = (argc > 3) ? MAC_ADDRESS : strRMac; - else if ((cmd != NULL) && (argc > 3)) - err_if((errno = (!((part = PARTNUM[0] - '0') == 0 || part == 1)) - || PARTNUM[1] ? EINVAL : errno)); - err_if((errno = (cmd == NULL) ? EINVAL : errno)); - - readGbeFile(); + read_gbe(); (*cmd)(); + write_gbe(); + + if (close(fd) == -1) + err(ECANCELED, "close '%s'", fname); + if (close(rfd) == -1) + err(ECANCELED, "close '/dev/urandom'"); - if ((gbeFileModified) && (flags != O_RDONLY) && (cmd != writeGbeFile)) - writeGbeFile(); - err_if((errno != 0) && (cmd != cmd_dump)); - return errno; + if (cmd != cmd_dump) { + if (errno) + err(ECANCELED, "Unhandled error on exit"); + } + + if (errno) + return EXIT_FAILURE; + else + return EXIT_SUCCESS; } -void -openFiles(const char *path) +/* + * Currently redundant, because the program only runs + * once, but I plan to expand this tool so that it can + * work on multiple files, using getop switches as args. + */ +static void +reset_global_state(void) { - struct stat st; - xopen(fd, path, flags); - if ((st.st_size != (SIZE_4KB << 1))) - err(errno = ECANCELED, "File `%s` not 8KiB", path); - xopen(rfd, "/dev/urandom", O_RDONLY); - errno = errno != ENOTDIR ? errno : 0; -} - -void -readGbeFile(void) -{ - nf = ((cmd == writeGbeFile) || (cmd == cmd_copy)) ? SIZE_4KB : nf; - skipread[part ^ 1] = (cmd == cmd_copy) | (cmd == cmd_setchecksum) - | (cmd == cmd_brick); - gbe[1] = (gbe[0] = (size_t) buf) + SIZE_4KB; - for (int p = 0; p < 2; p++) { - if (skipread[p]) + errno = 0; + + mac = NULL; + invert = 0; + part_modified[0] = 0; + part_modified[1] = 0; + fname = ""; + cmd = NULL; + fd = -1; + rfd = -1; + part = 0; + + memset(macbuf, 0, sizeof(macbuf)); + memset(buf, 0, sizeof(buf)); +} + +static void +set_cmd(int argc, char *argv[]) +{ + size_t i; + + if (argc == 2) { + cmd = cmd_setmac; + return; + } + + for (i = 0; (i < items(ops)) && (cmd == NULL); i++) { + if (strcmp(argv[2], ops[i].str) != 0) continue; - err_if(pread(fd, (uint8_t *) gbe[p], nf, p << 12) == -1); - handle_endianness(p); + if (argc >= ops[i].args) { + cmd = ops[i].cmd; + break; + } + err(EINVAL, "Too few args: command '%s'", ops[i].str); } } -void -cmd_setmac(void) +static void +check_cmd_args(int argc, char *argv[]) { - if (macAddress(strMac, mac)) - err(errno = ECANCELED, "Bad MAC address"); - for (int partnum = 0; partnum < 2; partnum++) { - if (!validChecksum(part = partnum)) - continue; - for (int w = 0; w < 3; w++) - setWord(w, partnum, mac[w]); - cmd_setchecksum(); + if ((cmd == NULL) && (argc > 2)) { /* nvm gbe [MAC] */ + mac = argv[2]; + cmd = cmd_setmac; + } else if (cmd == cmd_setmac) { /* nvm gbe setmac [MAC] */ + mac = rmac; /* random MAC */ + if (argc > 3) + mac = argv[3]; + } else if ((cmd != NULL) && (argc > 3)) { /* user-supplied partnum */ + part = argv[3][0] - '0'; + if (!((part == 0 || part == 1) && argv[3][1] == '\0')) + err(EINVAL, "Bad partnum: %s", argv[3]); } + + if (cmd == NULL) + err(EINVAL, "Bad command"); } -int -macAddress(const char *strMac, uint16_t *mac) -{ - uint64_t total = 0; - if (strnlen(strMac, 20) == 17) { - for (uint8_t h, i = 0; i < 16; i += 3) { - if (i != 15) - if (strMac[i + 2] != ':') - return 1; - int byte = i / 3; - for (int nib = 0; nib < 2; nib++, total += h) { - if ((h = hextonum(strMac[i + nib])) > 15) - return 1; - if ((byte == 0) && (nib == 1)) - if (strMac[i + nib] == '?') - h = (h & 0xE) | 2; /* local, unicast */ - mac[byte >> 1] |= ((uint16_t ) h) - << ((8 * (byte % 2)) + (4 * (nib ^ 1))); - } - }} - return ((total == 0) | (mac[0] & 1)); /* multicast/all-zero banned */ +static void +set_io_flags(int argc, char *argv[]) +{ + flags = O_RDWR; + if (argc > 2) { + if (strcmp(argv[2], "dump") == 0) + flags = O_RDONLY; + } } -uint8_t +static void +open_files(void) +{ + struct stat st; + struct stat st_rfd; + + xopen(&rfd, "/dev/urandom", O_RDONLY, &st_rfd); + xopen(&fd, fname, flags, &st); + + switch(st.st_size) { + case SIZE_8KB: + case SIZE_16KB: + case SIZE_128KB: + partsize = st.st_size >> 1; + break; + default: + err(ECANCELED, "File size must be 8KB, 16KB or 128KB"); + break; + } +} + +static void +xopen(int *f, const char *l, int p, struct stat *st) +{ + if ((*f = open(l, p)) == -1) + err(ECANCELED, "%s", l); + if (fstat(*f, st) == -1) + err(ECANCELED, "%s", l); +} + +static void +read_gbe(void) +{ + int p; + int do_read[2] = {1, 1}; + + if ((cmd == cmd_copy) || (cmd == cmd_brick) || + (cmd == cmd_setchecksum)) + do_read[part ^ 1] = 0; + + /* + * speedhack: if copy/swap, flip where data gets written to memory, + * so that cmd_copy and cmd_swap don't have to work on every word + */ + if ((cmd == cmd_copy) || (cmd == cmd_swap)) + invert = 1; + + for (p = 0; p < 2; p++) { + if (do_read[p]) + read_gbe_part(p, invert); + } +} + +static void +read_gbe_part(int p, int invert) +{ + read_file_PERFECTLY_or_die(fd, buf + (SIZE_4KB * (p ^ invert)), + SIZE_4KB, ((off_t)p) * partsize, fname, "pread"); + + swap(p ^ invert); +} + +static void +cmd_setmac(void) +{ + int partnum; + int mac_updated = 0; + + parse_mac_string(); + printf("MAC address to be written: %s\n", mac); + + for (partnum = 0; partnum < 2; partnum++) + mac_updated |= write_mac_part(partnum); + if (mac_updated) + errno = 0; +} + +static void +parse_mac_string(void) +{ + int mac_pos; + + if (strnlen(mac, 20) != 17) + err(EINVAL, "MAC address is the wrong length"); + + for (mac_pos = 0; mac_pos < 16; mac_pos += 3) + set_mac_byte(mac_pos); + + if ((macbuf[0] | macbuf[1] | macbuf[2]) == 0) + err(EINVAL, "Must not specify all-zeroes MAC address"); + if (macbuf[0] & 1) + err(EINVAL, "Must not specify multicast MAC address"); +} + +static void +set_mac_byte(int mac_pos) +{ + int nib; + check_mac_separator(mac_pos); + + for (nib = 0; nib < 2; nib++) + set_mac_nib(mac_pos, nib); +} + +static void +check_mac_separator(int mac_pos) +{ + char separator; + + if (mac_pos == 15) + return; + if ((separator = mac[mac_pos + 2]) == ':') + return; + + err(EINVAL, "Invalid MAC address separator '%c'", separator); +} + +static void +set_mac_nib(int mac_pos, int nib) +{ + uint8_t h; + int byte = mac_pos / 3; + + if ((h = hextonum(mac[mac_pos + nib])) > 15) + err(EINVAL, "Invalid character '%c'", + mac[mac_pos + nib]); + + /* If random, ensure that local/unicast bits are set */ + if ((byte == 0) && (nib == 1)) { + if ((mac[mac_pos + nib] == '?') || + (mac[mac_pos + nib] == 'x') || + (mac[mac_pos + nib] == 'X')) /* random */ + h = (h & 0xE) | 2; /* local, unicast */ + } + + macbuf[byte >> 1] |= (uint16_t)h << ((8 * (byte % 2)) + + (4 * (nib ^ 1))); +} + +static uint8_t hextonum(char ch) { if ((ch >= '0') && (ch <= '9')) @@ -177,105 +386,406 @@ hextonum(char ch) return ch - 'A' + 10; else if ((ch >= 'a') && (ch <= 'f')) return ch - 'a' + 10; - return (ch == '?') ? rhex() : 16; + else if ((ch == '?') || (ch == 'x') || (ch == 'X')) + return rhex(); /* random hex value */ + else + return 16; /* error: invalid character */ } -uint8_t +static uint8_t rhex(void) { - static uint8_t n = 0, rnum[16]; - if (!n) - err_if(pread(rfd, (uint8_t *) &rnum, (n = 15) + 1, 0) == -1); + static uint8_t n = 0; + static uint8_t rnum[12]; + + if (!n) { + n = sizeof(rnum) - 1; + read_file_PERFECTLY_or_die(rfd, rnum, sizeof(rnum), + 0, "/dev/urandom", NULL); + } + return rnum[n--] & 0xf; } -void +static void +read_file_PERFECTLY_or_die(int fd, void *buf, size_t len, + off_t off, const char *path, const char *op) +{ + int retry; + ssize_t rval; + + for (retry = 0; retry < MAX_RETRY_READ; retry++) { + if (op == NULL) + rval = read(fd, buf, len); + else + rval = pread(fd, buf, len, off); + + if (check_read_or_die(path, rval, len, retry, + op ? op : "read")) + return; + } + + err(EINTR, "%s: max retries exceeded on file: %s", + op ? op : "read", path); +} + +static int +check_read_or_die(const char *rpath, ssize_t rval, size_t rsize, + int retry, const char *readtype) +{ + if (rval == (ssize_t)rsize) { + errno = 0; + return 1; /* Successful read */ + } + + if (rval != -1) + err(ECANCELED, "Short %s, %zd bytes, on file: %s", + readtype, rval, rpath); + if (errno != EINTR) + err(ECANCELED, "Could not %s file: '%s'", readtype, rpath); + if (retry == MAX_RETRY_READ - 1) + err(EINTR, "%s: max retries exceeded on file: %s", + readtype, rpath); + + /* + * Bad read, with errno EINTR (syscall interrupted). + */ + return 0; +} + +static int +write_mac_part(int partnum) +{ + int w; + + part = partnum; + if (!good_checksum(partnum)) + return 0; + + for (w = 0; w < 3; w++) + set_word(w, partnum, macbuf[w]); + + printf("Wrote MAC address to part %d: ", partnum); + print_mac_address(partnum); + + cmd_setchecksum(); + return 1; +} + +static void cmd_dump(void) { - for (int partnum = 0, numInvalid = 0; partnum < 2; partnum++) { - if (!validChecksum(partnum)) - ++numInvalid; + int partnum; + int num_invalid = 0; + + for (partnum = 0; partnum < 2; partnum++) { + if (!good_checksum(partnum)) + ++num_invalid; + printf("MAC (part %d): ", partnum); - showmac(partnum), hexdump(partnum); - errno = ((numInvalid < 2) && (partnum)) ? 0 : errno; + print_mac_address(partnum); + hexdump(partnum); } + + if ((num_invalid < 2)) + errno = 0; } -void -showmac(int partnum) +static void +print_mac_address(int partnum) { - for (int c = 0; c < 3; c++) { + int c; + for (c = 0; c < 3; c++) { uint16_t val16 = word(c, partnum); printf("%02x:%02x", val16 & 0xff, val16 >> 8); - printf(c == 2 ? "\n" : ":"); + if (c == 2) + printf("\n"); + else + printf(":"); } } -void +static void hexdump(int partnum) { - for (int row = 0; row < 8; row++) { - printf("%07x", row << 4); - for (int c = 0; c < 8; c++) { - uint16_t val16 = word((row << 3) + c, partnum); - printf(" %02x%02x", val16 >> 8, val16 & 0xff); - } printf("\n"); + int c; + int row; + uint16_t val16; + + for (row = 0; row < 8; row++) { + printf("%08x ", row << 4); + for (c = 0; c < 8; c++) { + val16 = word((row << 3) + c, partnum); + if (c == 4) + printf(" "); + printf(" %02x %02x", val16 & 0xff, val16 >> 8); + } + printf("\n"); } } -void +static void cmd_setchecksum(void) { + int c; uint16_t val16 = 0; - for (int c = 0; c < 0x3F; c++) + + for (c = 0; c < NVM_CHECKSUM_WORD; c++) val16 += word(c, part); - setWord(0x3F, part, 0xBABA - val16); + + set_word(NVM_CHECKSUM_WORD, part, NVM_CHECKSUM - val16); } -void +static void cmd_brick(void) { - if (validChecksum(part)) - setWord(0x3F, part, ((word(0x3F, part)) ^ 0xFF)); + uint16_t checksum_word; + + if (!good_checksum(part)) { + err(ECANCELED, + "Part %d checksum already invalid in file '%s'", + part, fname); + } + + /* + * We know checksum_word is valid, so we need only + * flip one bit to invalidate it. + */ + checksum_word = word(NVM_CHECKSUM_WORD, part); + set_word(NVM_CHECKSUM_WORD, part, checksum_word ^ 1); } -void +static void cmd_copy(void) { - if ((gbeFileModified = nvmPartModified[part ^ 1] = validChecksum(part))) - gbe[part ^ 1] = gbe[part]; /* speedhack: copy ptr, not words */ + if (!good_checksum(part ^ 1)) + err(ECANCELED, "copy p%d, file '%s'", part ^ 1, fname); + + /* + * SPEED HACK: + * + * read_gbe() already performed the copy, + * by virtue of inverted read. We need + * only set the other part as changed. + */ + part_modified[part ^ 1] = 1; } -int -validChecksum(int partnum) +static void +cmd_swap(void) +{ + if (!(good_checksum(0) || good_checksum(1))) + err(ECANCELED, "swap parts, file '%s'", fname); + + /* + * good_checksum() can set errno, if one + * of the parts is bad. We will reset it. + */ + errno = 0; + + /* + * SPEED HACK: + * + * read_gbe() already performed the swap, + * by virtue of inverted read. We need + * only set both parts as changed. + */ + part_modified[1] = part_modified[0] = 1; +} + +static int +good_checksum(int partnum) { + int w; uint16_t total = 0; - for(int w = 0; w <= 0x3F; w++) + for (w = 0; w <= NVM_CHECKSUM_WORD; w++) total += word(w, partnum); - if (total == 0xBABA) + + if (total == NVM_CHECKSUM) return 1; - fprintf(stderr, "WARNING: BAD checksum in part %d\n", partnum); - return (errno = ECANCELED) & 0; + + fprintf(stderr, "WARNING: BAD checksum in part %d\n", + partnum ^ invert); + + set_err(ECANCELED); + return 0; } -void -writeGbeFile(void) +/* + * NOTE: memcpy is a bit sticky with host endianness, + * but we currently use it only when swap has + * been handled. just be careful about when the + * swap() function is called. + */ + +static uint16_t +word(int pos16, int p) { - err_if((cmd == writeGbeFile) && !(validChecksum(0) || validChecksum(1))); - for (int p = 0, x = (cmd == writeGbeFile) ? 1 : 0; p < 2; p++) { - if ((!nvmPartModified[p]) && (cmd != writeGbeFile)) - continue; - handle_endianness(p^x); - err_if(pwrite(fd, (uint8_t *) gbe[p^x], nf, p << 12) == -1); + uint16_t rval = 0; + + check_bound(pos16, p); + memcpy(&rval, buf + (SIZE_4KB * p) + (pos16 << 1), sizeof(uint16_t)); + + return rval; +} + +static void +set_word(int pos16, int p, uint16_t val16) +{ + check_bound(pos16, p); + memcpy(buf + (SIZE_4KB * p) + (pos16 << 1), &val16, sizeof(uint16_t)); + + part_modified[p] = 1; +} + +static void +check_bound(int c, int p) +{ + /* + * NVM_SIZE assumed as the limit, because the + * current design assumes that we will only + * ever modified the NVM area. + * + * The only exception is copy/swap, but these + * do not use word/set_word and therefore do + * not cause check_bound() to be called. + * + * TODO: + * This should be adjusted in the future, if + * we ever wish to work on the extented area. + */ + + if ((p != 0) && (p != 1)) + err(EINVAL, "check_bound: invalid partnum %d", p); + if ((c < 0) || (c >= (NVM_SIZE >> 1))) + err(EINVAL, "check_bound: out of bounds %d", c); +} + +static void +write_gbe(void) +{ + int p; + + if (flags == O_RDONLY) + return; + + for (p = 0; p < 2; p++) { + if (part_modified[p]) + write_gbe_part(p); } - errno = 0; - err_if(close(fd) == -1); } -void -handle_endianness(int partnum) +static void +write_gbe_part(int p) +{ + swap(p); /* swap bytes on big-endian host CPUs */ + + if (pwrite(fd, buf + (SIZE_4KB * p), + SIZE_4KB, (off_t)p * partsize) != (ssize_t)SIZE_4KB) { + err(ECANCELED, + "Can't write %d b to '%s' p%d", SIZE_4KB, fname, p); + } +} + +/* + * GbE files store bytes in little-endian order. + * This function ensures big-endian host CPU support. + */ +static void +swap(int partnum) +{ + /* + * NVM_SIZE assumed as the limit; see notes in + * check_bound(). + * + * TODO: + * This should be adjusted in the future, if + * we ever wish to work on the extended area. + */ + + size_t w; + size_t x; + + uint8_t *n = buf + (SIZE_4KB * partnum); + + int e = 1; + if (*((uint8_t *)&e) == 1) + return; /* Little-endian host CPU; no swap needed. */ + + /* + * The host CPU stores bytes in big-endian order. + * We will therefore reverse the order in memory: + */ + for (w = 0, x = 1; w < NVM_SIZE; w += 2, x += 2) { + uint8_t chg = n[w]; + n[w] = n[x]; + n[x] = chg; + } +} + +static void +usage(void) +{ + const char *util = getnvmprogname(); + +#ifdef __OpenBSD__ + if (pledge("stdio", NULL) == -1) + err(ECANCELED, "pledge"); +#endif + fprintf(stderr, + "Modify Intel GbE NVM images e.g. set MAC\n" + "USAGE:\n" + "\t%s FILE dump\n" + "\t%s FILE # same as setmac without [MAC]\n" + "\t%s FILE setmac [MAC]\n" + "\t%s FILE swap\n" + "\t%s FILE copy 0|1\n" + "\t%s FILE brick 0|1\n" + "\t%s FILE setchecksum 0|1\n", + util, util, util, util, util, util, util); + + err(ECANCELED, "Too few arguments"); +} + +static void +err(int nvm_errval, const char *msg, ...) +{ + va_list args; + + fprintf(stderr, "%s: ", getnvmprogname()); + + va_start(args, msg); + vfprintf(stderr, msg, args); + va_end(args); + + set_err(nvm_errval); + fprintf(stderr, ": %s", strerror(errno)); + + fprintf(stderr, "\n"); + exit(EXIT_FAILURE); +} + +static const char * +getnvmprogname(void) +{ + const char *p; + + if ((argv0 == NULL) || (*argv0 == '\0')) + return ""; + + if ((p = strrchr(argv0, '/'))) + return p + 1; + else + return argv0; +} + +static void +set_err(int x) { - uint8_t *n = (uint8_t *) gbe[partnum]; - for (size_t w = nf * ((uint8_t *) &e)[0], x = 1; w < nf; w += 2, x += 2) - n[w] ^= n[x], n[x] ^= n[w], n[w] ^= n[x]; + if (errno) + return; + if (x) + errno = x; + else + errno = ECANCELED; } |
