summaryrefslogtreecommitdiff
path: root/util
diff options
context:
space:
mode:
Diffstat (limited to 'util')
-rw-r--r--util/nvmutil/nvmutil.c309
-rw-r--r--util/spkmodem_decode/.gitignore2
-rw-r--r--util/spkmodem_decode/Makefile (renamed from util/spkmodem_recv/Makefile)6
-rw-r--r--util/spkmodem_decode/spkmodem-decode.c725
-rw-r--r--util/spkmodem_recv/.gitignore1
-rw-r--r--util/spkmodem_recv/spkmodem-recv.c313
6 files changed, 917 insertions, 439 deletions
diff --git a/util/nvmutil/nvmutil.c b/util/nvmutil/nvmutil.c
index 151b6f40..6b7be3df 100644
--- a/util/nvmutil/nvmutil.c
+++ b/util/nvmutil/nvmutil.c
@@ -47,9 +47,6 @@
* TODO: bound checks for files per-command, e.g. only
* first 6 bytes for CMD_SETMAC
*
- * TODO: clean up the do_rw function: make PSCHREIB and
- * so on clearer, probably just define them inline and
- * validate them inline (no define).
* TODO: in command sanitizer: verify that each given
* entry corresponds to the correct function, in the
* pointer (this check is currently missing)
@@ -85,10 +82,6 @@
* TODO: also document the layout of Intel GbE files, so
* that wily individuals can easily expand the
* featureset of nvmutil.
- * TODO: remove some clever code, e.g.:
- * rw_type == PLESEN << 2
- * make stuff like that clearer.
- * ditto the invert copy/swap trick
* TODO: write a manpage
* TODO: simplify the command sanitization, implement more
* of it as build time checks, e.g. static asserts.
@@ -128,6 +121,22 @@ OBJ = $(SRC:.c=.o)
^ explicitly declare the order in which to build
*/
+/*
+TODO:
+further note when fuzzing is implemented:
+use deterministic randomisation, with a
+guaranteed seed - so e.g. don't use /dev/urandom
+in test builds. e.g. just use normal rand()
+but with a static seed e.g. 1234
+*/
+/*
+TODO: stricter build flags, e.g.
+CFLAGS += -fstack-protector-strong
+CFLAGS += -fno-common
+CFLAGS += -D_FORTIFY_SOURCE=2
+CFLAGS += -fPIE
+*/
+
#ifndef _XOPEN_SOURCE
#define _XOPEN_SOURCE 500
#endif
@@ -168,6 +177,7 @@ typedef unsigned int uint32_t;
#include <time.h>
#include <unistd.h>
+/* type asserts */
typedef char static_assert_char_is_8_bits[(CHAR_BIT == 8) ? 1 : -1];
typedef char static_assert_uint8_is_1[(sizeof(uint8_t) == 1) ? 1 : -1];
typedef char static_assert_uint16_is_2[(sizeof(uint16_t) == 2) ? 1 : -1];
@@ -225,8 +235,6 @@ typedef char static_assert_off_t_is_32[(sizeof(off_t) >= 4) ? 1 : -1];
*/
static void sanitize_command_list(void);
static void sanitize_command_index(size_t c);
-static void check_enum_bin(size_t a, const char *a_name,
- size_t b, const char *b_name);
/*
* Argument handling (user input)
@@ -335,6 +343,8 @@ static ssize_t do_rw(int fd,
static ssize_t prw(int fd, void *mem, size_t nrw,
off_t off, int rw_type);
static off_t lseek_eintr(int fd, off_t off, int whence);
+static int io_args(int fd, size_t nrw,
+ off_t off, int rw_type);
/*
* Error handling and cleanup
@@ -372,6 +382,8 @@ static void usage(uint8_t usage_exit);
#define NVM_WORDS (NVM_SIZE >> 1)
#define NVM_CHECKSUM_WORD (NVM_WORDS - 1)
+#define NUM_RANDOM_BYTES 12
+
/*
* Portable macro based on BSD nitems.
* Used to count the number of commands (see below).
@@ -425,10 +437,10 @@ static const char *argv0;
#define ARGC_4 4
enum {
- LESEN,
- PLESEN,
- SCHREIB,
- PSCHREIB
+ IO_READ,
+ IO_WRITE,
+ IO_PREAD,
+ IO_PWRITE
};
/*
@@ -549,8 +561,40 @@ static const struct commands command[] = {
*/
static size_t cmd_index = CMD_NULL;
+/*
+ * asserts (variables/defines sanity check)
+ */
typedef char assert_argc3[(ARGC_3==3)?1:-1];
typedef char assert_argc4[(ARGC_4==4)?1:-1];
+typedef char assert_read[(IO_READ==0)?1:-1];
+typedef char assert_write[(IO_WRITE==1)?1:-1];
+typedef char assert_pread[(IO_PREAD==2)?1:-1];
+typedef char assert_pwrite[(IO_PWRITE==3)?1:-1];
+typedef char assert_rand_byte[(NUM_RANDOM_BYTES>0)?1:-1];
+typedef char assert_rand_len[(NUM_RANDOM_BYTES<NVM_SIZE)?1:-1];
+/* commands */
+typedef char assert_cmd_dump[(CMD_DUMP==0)?1:-1];
+typedef char assert_cmd_setmac[(CMD_SETMAC==1)?1:-1];
+typedef char assert_cmd_swap[(CMD_SWAP==2)?1:-1];
+typedef char assert_cmd_copy[(CMD_COPY==3)?1:-1];
+typedef char assert_cmd_cat[(CMD_CAT==4)?1:-1];
+typedef char assert_cmd_cat16[(CMD_CAT16==5)?1:-1];
+typedef char assert_cmd_cat128[(CMD_CAT128==6)?1:-1];
+/* mod_type */
+typedef char assert_mod_off[(SET_MOD_OFF==0)?1:-1];
+typedef char assert_mod_0[(SET_MOD_0==1)?1:-1];
+typedef char assert_mod_1[(SET_MOD_1==2)?1:-1];
+typedef char assert_mod_n[(SET_MOD_N==3)?1:-1];
+typedef char assert_mod_both[(SET_MOD_BOTH==4)?1:-1];
+/* bool */
+typedef char bool_arg_nopart[(ARG_NOPART==0)?1:-1];
+typedef char bool_arg_part[(ARG_PART==1)?1:-1];
+typedef char bool_skip_checksum_read[(SKIP_CHECKSUM_READ==0)?1:-1];
+typedef char bool_checksum_read[(CHECKSUM_READ==1)?1:-1];
+typedef char bool_skip_checksum_write[(SKIP_CHECKSUM_WRITE==0)?1:-1];
+typedef char bool_checksum_write[(CHECKSUM_WRITE==1)?1:-1];
+typedef char bool_no_invert[(NO_INVERT==0)?1:-1];
+typedef char bool_part_invert[(PART_INVERT==1)?1:-1];
static int use_prng = 0;
@@ -567,8 +611,8 @@ main(int argc, char *argv[])
#ifdef NVMUTIL_UNVEIL
if (pledge("stdio rpath wpath unveil", NULL) == -1)
err(errno, "pledge");
- if (unveil("/dev/null", "r") == -1)
- err(errno, "unveil '/dev/null'");
+ if (unveil("/dev/urandom", "r") == -1)
+ err(errno, "unveil /dev/urandom");
#else
if (pledge("stdio rpath wpath", NULL) == -1)
err(errno, "pledge");
@@ -644,6 +688,9 @@ sanitize_command_list(void)
sanitize_command_index(c);
}
+/*
+ * TODO: specific config checks per command
+ */
static void
sanitize_command_index(size_t c)
{
@@ -669,15 +716,6 @@ sanitize_command_index(size_t c)
(unsigned long)c, command[c].str);
}
- if (!((CMD_SETMAC > CMD_DUMP) && (CMD_SWAP > CMD_SETMAC) &&
- (CMD_COPY > CMD_SWAP) && (CMD_CAT > CMD_COPY) &&
- (CMD_CAT16 > CMD_CAT) && (CMD_CAT128 > CMD_CAT16)))
- err(EINVAL, "Some command integers are the same");
-
- if (!((SET_MOD_0 > SET_MOD_OFF) && (SET_MOD_1 > SET_MOD_0) &&
- (SET_MOD_N > SET_MOD_1) && (SET_MOD_BOTH > SET_MOD_N)))
- err(EINVAL, "Some modtype integers are the same");
-
mod_type = command[c].set_modified;
switch (mod_type) {
case SET_MOD_0:
@@ -695,13 +733,6 @@ sanitize_command_index(size_t c)
check_bin(command[c].chksum_read, "cmd.chksum_read");
check_bin(command[c].chksum_write, "cmd.chksum_write");
- check_enum_bin(ARG_NOPART, "ARG_NOPART", ARG_PART, "ARG_PART");
- check_enum_bin(SKIP_CHECKSUM_READ, "SKIP_CHECKSUM_READ",
- CHECKSUM_READ, "CHECKSUM_READ");
- check_enum_bin(SKIP_CHECKSUM_WRITE, "SKIP_CHECKSUM_WRITE",
- CHECKSUM_WRITE, "CHECKSUM_WRITE");
- check_enum_bin(NO_INVERT, "NO_INVERT", PART_INVERT, "PART_INVERT");
-
gbe_rw_size = command[c].rw_size;
switch (gbe_rw_size) {
@@ -720,20 +751,6 @@ sanitize_command_index(size_t c)
if (command[c].flags != O_RDONLY &&
command[c].flags != O_RDWR)
err(EINVAL, "invalid cmd.flags setting");
-
- if (!((!LESEN) && (PLESEN == 1) && (SCHREIB == 2) && (PSCHREIB == 3)))
- err(EINVAL, "rw type integers are the wrong values");
-}
-
-static void
-check_enum_bin(size_t a, const char *a_name,
- size_t b, const char *b_name)
-{
- if (a)
- err(EINVAL, "%s is non-zero", a_name);
-
- if (b != 1)
- err(EINVAL, "%s is a value other than 1", b_name);
}
static void
@@ -890,7 +907,7 @@ read_gbe_file(void)
for (p = 0; p < 2; p++) {
if (do_read[p])
- rw_gbe_file_part(p, PLESEN, "pread");
+ rw_gbe_file_part(p, IO_PREAD, "pread");
}
}
@@ -1115,22 +1132,14 @@ static uint16_t
rhex(void)
{
static size_t n = 0;
- static uint8_t rnum[12];
+ static uint8_t rnum[NUM_RANDOM_BYTES];
- if (use_prng) {
- /*
- * On very old Unix systems that
- * lack /dev/random and /dev/urandom
- */
+ if (use_prng)
return fallback_rand();
- }
-
- if (urandom_fd < 0)
- err(ECANCELED, "Your operating system has no /dev/[u]random");
if (!n) {
n = sizeof(rnum);
- if (rw_file_exact(urandom_fd, rnum, n, 0, LESEN) == -1)
+ if (rw_file_exact(urandom_fd, rnum, n, 0, IO_READ) == -1)
err(errno, "Randomisation failed");
}
@@ -1169,6 +1178,7 @@ entropy_jitter(void)
{
struct timeval a, b;
unsigned long mix = 0;
+ long mix_diff;
int i;
for (i = 0; i < 8; i++) {
@@ -1176,7 +1186,15 @@ entropy_jitter(void)
getpid();
gettimeofday(&b, NULL);
- mix ^= (unsigned long)(b.tv_usec - a.tv_usec);
+ /*
+ * prevent negative numbers to prevent overflow,
+ * which would bias rand to large numbers
+ */
+ mix_diff = (long)(b.tv_usec - a.tv_usec);
+ if (mix_diff < 0)
+ mix_diff = -mix_diff;
+
+ mix ^= (unsigned long)(mix_diff);
mix ^= (unsigned long)&mix;
}
@@ -1231,7 +1249,9 @@ print_mac_from_nvm(size_t partnum)
for (c = 0; c < 3; c++) {
val16 = nvm_word(c, partnum);
- printf("%02x:%02x", val16 & 0xff, val16 >> 8);
+ printf("%02x:%02x",
+ (unsigned int)(val16 & 0xff),
+ (unsigned int)(val16 >> 8));
if (c == 2)
printf("\n");
else
@@ -1252,7 +1272,9 @@ hexdump(size_t partnum)
val16 = nvm_word((row << 3) + c, partnum);
if (c == 4)
printf(" ");
- printf(" %02x %02x", val16 & 0xff, val16 >> 8);
+ printf(" %02x %02x",
+ (unsigned int)(val16 & 0xff),
+ (unsigned int)(val16 >> 8));
}
printf("\n");
}
@@ -1289,7 +1311,7 @@ gbe_cat_buf(uint8_t *b)
while (1) {
rval = rw_file_exact(STDOUT_FILENO, b,
- GBE_PART_SIZE, 0, SCHREIB);
+ GBE_PART_SIZE, 0, IO_WRITE);
if (rval >= 0) {
/*
@@ -1330,7 +1352,7 @@ write_gbe_file(void)
if (update_checksum)
set_checksum(partnum);
- rw_gbe_file_part(partnum, PSCHREIB, "pwrite");
+ rw_gbe_file_part(partnum, IO_PWRITE, "pwrite");
}
}
@@ -1454,7 +1476,11 @@ rw_gbe_file_part(size_t p, int rw_type,
uint8_t *mem_offset;
- if (rw_type == SCHREIB || rw_type == PSCHREIB)
+ if (rw_type < IO_PREAD || rw_type > IO_PWRITE)
+ err(errno, "%s: %s: part %lu: invalid rw_type, %d",
+ fname, rw_type_str, (unsigned long)p, rw_type);
+
+ if (rw_type == IO_PWRITE)
invert = 0;
/*
@@ -1524,7 +1550,7 @@ gbe_x_offset(size_t p, const char *f_op, const char *d_type,
/*
* Read or write the exact contents of a file,
* along with a buffer, (if applicable) offset,
- * and number of bytes to be read. It unified
+ * and number of bytes to be read. It unifies
* the functionality of read(), pread(), write()
* and pwrite(), with retry-on-EINTR and also
* prevents infinite loop on zero-reads.
@@ -1537,102 +1563,86 @@ gbe_x_offset(size_t p, const char *f_op, const char *d_type,
* be used on sockets or pipes, because 0-byte
* reads are treated like fatal errors. This
* means that EOF is also considered fatal.
- *
- * WARNING: Do not use O_APPEND on open() when
- * using this function. If you do, POSIX allows
- * write() to ignore the current file offset and
- * write at EOF, which means that our use of
- * lseek in prw() does not guarantee writing at
- * a specified offset. So if using PSCHREIB or
- * PLESEN, make sure not to pass a file descriptor
- * with the O_APPEND flag. Alternatively, modify
- * do_rw() to directly use pwrite() and pread()
- * instead of prw().
*/
static ssize_t
-rw_file_exact(int fd, uint8_t *mem, size_t len,
+rw_file_exact(int fd, uint8_t *mem, size_t nrw,
off_t off, int rw_type)
{
ssize_t rv;
size_t rc;
- if (fd < 0 || !len || len > (size_t)SSIZE_MAX) {
+ if (io_args(fd, nrw, off, rw_type) == -1) {
errno = EIO;
return -1;
}
- for (rc = 0, rv = 0; rc < len; rc += (size_t)rv) {
- if ((rv = rw_file_once(fd, mem, len, off, rw_type, rc)) == -1)
+ for (rc = 0, rv = 0; rc < nrw; ) {
+ if ((rv = rw_file_once(fd, mem, nrw, off, rw_type, rc)) <= 0)
return -1;
+
+ rc += (size_t)rv;
}
return rc;
}
+/*
+ * May not return all requested bytes (len).
+ * Use rw_file_exact for guaranteed length.
+ */
static ssize_t
-rw_file_once(int fd, uint8_t *mem, size_t len,
+rw_file_once(int fd, uint8_t *mem, size_t nrw,
off_t off, int rw_type, size_t rc)
{
ssize_t rv;
size_t retries_on_zero = 0;
size_t max_retries = 10;
+ if (io_args(fd, nrw, off, rw_type) == -1)
+ goto err_rw_file_once;
+
read_again:
- rv = do_rw(fd, mem + rc, len - rc, off + rc, rw_type);
+ rv = do_rw(fd, mem + rc, nrw - rc, off + rc, rw_type);
- if (rv < 0 && errno == EINTR) {
+ if (rv < 0 && errno == EINTR)
goto read_again;
- } else if (rv < 0) {
- errno = EIO;
- return -1;
- }
- /*
- * Theoretical bug: if a buggy libc returned
- * a size larger than SSIZE_MAX, the cast may
- * cause an overflow. Specifications guarantee
- * this won't happen, but spec != implementation
- */
- if ((size_t)rv > SSIZE_MAX) {
- errno = EIO;
+ if (rv < 0)
return -1;
- /* we will not tolerate your buggy libc */
- }
- if ((size_t)rv > (len - rc) /* Prevent overflow */
- || rv == 0) { /* Prevent infinite 0-byte loop */
- if (rv == 0) {
- /*
- * Fault tolerance against infinite
- * zero-byte loop: re-try a finite
- * number of times. This mitigates
- * otherwise OK but slow filesystems
- * e.g. NFS or slow media.
- */
- if (retries_on_zero++ < max_retries)
- goto read_again;
- }
- errno = EIO;
- return -1;
- }
+ if ((size_t)rv > SSIZE_MAX /* theoretical buggy libc */
+ || (size_t)rv > (nrw - rc))/* don't overflow */
+ goto err_rw_file_once;
- return rv;
+ if (rv != 0)
+ return rv;
+
+ if (retries_on_zero++ < max_retries)
+ goto read_again;
+
+err_rw_file_once:
+ errno = EIO;
+ return -1;
}
static ssize_t
do_rw(int fd, uint8_t *mem,
- size_t len, off_t off, int rw_type)
+ size_t nrw, off_t off, int rw_type)
{
- if (rw_type == LESEN || rw_type == PLESEN << 2)
- return read(fd, mem, len);
+ if (io_args(fd, nrw, off, rw_type) == -1)
+ goto err_do_rw;
- if (rw_type == SCHREIB || rw_type == PSCHREIB << 2)
- return write(fd, mem, len);
+ if (rw_type == IO_READ)
+ return read(fd, mem, nrw);
- if (rw_type == PLESEN || rw_type == PSCHREIB)
- return prw(fd, mem, len, off, rw_type);
+ if (rw_type == IO_WRITE)
+ return write(fd, mem, nrw);
- errno = EINVAL;
+ if (rw_type == IO_PREAD || rw_type == IO_PWRITE)
+ return prw(fd, mem, nrw, off, rw_type);
+
+err_do_rw:
+ errno = EIO;
return -1;
}
@@ -1652,25 +1662,80 @@ prw(int fd, void *mem, size_t nrw,
off_t off_orig;
ssize_t r;
int saved_errno;
+ int prw_type;
+ int flags;
+
+ if (io_args(fd, nrw, off, rw_type) == -1)
+ goto err_prw;
+
+ prw_type = rw_type ^ IO_PREAD;
+
+ if ((unsigned int)prw_type > IO_WRITE)
+ goto err_prw;
+
+ flags = fcntl(fd, F_GETFL);
+ if (flags == -1)
+ return -1;
+
+ /*
+ * O_APPEND must not be used, because this
+ * allows POSIX write() to ignore the
+ * current write offset and write at EOF,
+ * which would therefore break pread/pwrite
+ */
+ if (flags & O_APPEND)
+ goto err_prw;
if ((off_orig = lseek_eintr(fd, (off_t)0, SEEK_CUR)) == (off_t)-1)
return -1;
+
if (lseek_eintr(fd, off, SEEK_SET) == (off_t)-1)
return -1;
do {
- r = do_rw(fd, mem, nrw, off, rw_type << 2);
+ r = do_rw(fd, mem, nrw, off, prw_type);
} while (r < 0 && errno == EINTR);
saved_errno = errno;
if (lseek_eintr(fd, off_orig, SEEK_SET) == (off_t)-1) {
if (r < 0)
errno = saved_errno;
+
return -1;
}
errno = saved_errno;
return r;
+
+err_prw:
+ errno = EIO;
+ return -1;
+}
+
+static int
+io_args(int fd, size_t nrw,
+ off_t off, int rw_type)
+{
+ if (off != 0
+ && off != gbe_file_offset(1, "i/o check"))
+ goto err_io_args;
+
+ if (nrw != GBE_PART_SIZE &&
+ nrw != NVM_SIZE &&
+ nrw != NUM_RANDOM_BYTES)
+ goto err_io_args;
+
+ if (fd < 0
+ || !nrw /* prevent zero read request */
+ || nrw > (size_t)SSIZE_MAX /* prevent overflow */
+ || (unsigned int)rw_type > IO_PWRITE)
+ goto err_io_args;
+
+ return 0;
+
+err_io_args:
+ errno = EIO;
+ return -1;
}
static off_t
diff --git a/util/spkmodem_decode/.gitignore b/util/spkmodem_decode/.gitignore
new file mode 100644
index 00000000..42814fe4
--- /dev/null
+++ b/util/spkmodem_decode/.gitignore
@@ -0,0 +1,2 @@
+spkmodem-recv
+spkmodem-decode
diff --git a/util/spkmodem_recv/Makefile b/util/spkmodem_decode/Makefile
index 293d7475..b00c4f43 100644
--- a/util/spkmodem_recv/Makefile
+++ b/util/spkmodem_decode/Makefile
@@ -8,12 +8,12 @@ DESTDIR?=
PREFIX?=/usr/local
INSTALL?=install
-PROG=spkmodem-recv
+PROG=spkmodem-decode
all: $(PROG)
-$(PROG): spkmodem-recv.c
- $(CC) $(CFLAGS) spkmodem-recv.c -o $(PROG)
+$(PROG): spkmodem-decode.c
+ $(CC) $(CFLAGS) spkmodem-decode.c -o $(PROG)
install: $(PROG)
mkdir -p $(DESTDIR)$(PREFIX)/bin/
diff --git a/util/spkmodem_decode/spkmodem-decode.c b/util/spkmodem_decode/spkmodem-decode.c
new file mode 100644
index 00000000..3b3b33f8
--- /dev/null
+++ b/util/spkmodem_decode/spkmodem-decode.c
@@ -0,0 +1,725 @@
+/*
+ * 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
deleted file mode 100644
index 2f5c946c..00000000
--- a/util/spkmodem_recv/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-spkmodem-recv
diff --git a/util/spkmodem_recv/spkmodem-recv.c b/util/spkmodem_recv/spkmodem-recv.c
deleted file mode 100644
index 4ff1fb5f..00000000
--- a/util/spkmodem_recv/spkmodem-recv.c
+++ /dev/null
@@ -1,313 +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. This is a special type of interface provided
- * by coreboot and GRUB, for computers that lack serial ports.
- *
- * Usage example:
- * parec --channels=1 --rate=48000 --format=s16le | ./spkmodem-recv
- *
- * 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.
- *
- * This fork of spkmodem-recv is provided with Libreboot releases:
- * https://libreboot.org/
- */
-
-#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 <stdio.h>
-#include <stdarg.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 READ_BUF 4096
-
-struct decoder_state {
- signed short frame[MAX_SAMPLES];
- 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;
-};
-
-static const char *argv0;
-
-static void handle_audio(struct decoder_state *st);
-static int valid_signal(struct decoder_state *st);
-static void decode_pulse(struct decoder_state *st);
-static signed short read_sample(struct decoder_state *st);
-static int set_ascii_bit(struct decoder_state *st);
-static void print_char(struct decoder_state *st);
-static void print_stats(struct decoder_state *st);
-static void reset_char(struct decoder_state *st);
-
-static void err(int errval, const char *msg, ...);
-static void usage(void);
-static const char *progname(void);
-
-int getopt(int, char * const *, const char *);
-extern char *optarg;
-extern int optind;
-extern int opterr;
-extern int optopt;
-
-#ifndef errno
-extern int errno;
-#endif
-
-int
-main(int argc, char **argv)
-{
- struct decoder_state st;
- int c;
-
-#if defined (__OpenBSD__) && defined(OpenBSD)
-#if OpenBSD >= 509
- if (pledge("stdio", NULL) == -1)
- err(errno, "pledge");
-#endif
-#endif
-
- memset(&st, 0, sizeof(st));
- st.ascii_bit = 7;
-
- st.ringpos = 0;
- st.sep_pos = SAMPLES_PER_FRAME;
-
- argv0 = argv[0];
-
- while ((c = getopt(argc, argv, "d")) != -1) {
- if (c != 'd')
- usage();
- st.debug = 1;
- break;
- }
-
- setvbuf(stdout, NULL, _IONBF, 0);
-
- for (;;)
- handle_audio(&st);
-
- return EXIT_SUCCESS;
-}
-
-static void
-handle_audio(struct decoder_state *st)
-{
- int sample;
-
- if (st->sample_count > (3 * SAMPLES_PER_FRAME))
- reset_char(st);
- if (!valid_signal(st)) {
- decode_pulse(st);
- return;
- }
-
- if (set_ascii_bit(st) < 0)
- print_char(st);
-
- st->sample_count = 0;
- for (sample = 0; sample < SAMPLES_PER_FRAME; sample++)
- decode_pulse(st);
-}
-
-static int
-valid_signal(struct decoder_state *st)
-{
- return (st->freq_separator > FREQ_SEP_MIN &&
- st->freq_separator < FREQ_SEP_MAX &&
- st->freq_data > FREQ_DATA_MIN &&
- st->freq_data < FREQ_DATA_MAX);
-}
-
-static void
-decode_pulse(struct decoder_state *st)
-{
- unsigned char old_ring, old_sep;
- unsigned char new_pulse;
-
- old_ring = st->pulse[st->ringpos];
- old_sep = st->pulse[st->sep_pos];
-
- st->freq_data -= old_ring;
- st->freq_data += old_sep;
- st->freq_separator -= old_sep;
-
- st->frame[st->ringpos] = read_sample(st);
-
- if ((unsigned)(st->frame[st->ringpos] + THRESHOLD)
- > (unsigned)(2 * THRESHOLD))
- new_pulse = 1;
- else
- new_pulse = 0;
-
- st->pulse[st->ringpos] = new_pulse;
- st->freq_separator += new_pulse;
-
- st->ringpos++;
- if (st->ringpos >= MAX_SAMPLES)
- st->ringpos = 0;
-
- st->sep_pos++;
- if (st->sep_pos >= MAX_SAMPLES)
- st->sep_pos = 0;
-
- st->sample_count++;
-}
-
-static signed short
-read_sample(struct decoder_state *st)
-{
- size_t n;
-
- while (st->inpos >= st->inlen) {
-
- n = fread(st->inbuf, sizeof(st->inbuf[0]),
- READ_BUF, stdin);
-
- if (n == 0) {
- if (feof(stdin))
- exit(EXIT_SUCCESS);
- err(errno, "stdin read");
- }
-
- st->inpos = 0;
- st->inlen = n;
- }
-
- return st->inbuf[st->inpos++];
-}
-
-static int
-set_ascii_bit(struct decoder_state *st)
-{
- if (st->debug)
- print_stats(st);
- if (st->freq_data < FREQ_DATA_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
-print_stats(struct decoder_state *st)
-{
- long pos;
-
- if ((pos = ftell(stdin)) == -1) {
- printf("%d %d %d\n",
- st->freq_data,
- st->freq_separator,
- FREQ_DATA_THRESHOLD);
- return;
- }
-
- printf("%d %d %d @%ld\n",
- st->freq_data,
- st->freq_separator,
- FREQ_DATA_THRESHOLD,
- pos - sizeof(st->frame));
-}
-
-static void
-reset_char(struct decoder_state *st)
-{
- st->ascii = 0;
- st->ascii_bit = 7;
-}
-
-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;
-}