summaryrefslogtreecommitdiff
path: root/cmd
diff options
context:
space:
mode:
Diffstat (limited to 'cmd')
-rw-r--r--cmd/sbc_harness/CMakeLists.txt7
-rw-r--r--cmd/sbc_harness/config/config.h23
-rw-r--r--cmd/sbc_harness/fs_harness_flash_bin.c312
-rw-r--r--cmd/sbc_harness/fs_harness_flash_bin.h30
-rw-r--r--cmd/sbc_harness/fs_harness_uptime_txt.c156
-rw-r--r--cmd/sbc_harness/fs_harness_uptime_txt.h19
-rw-r--r--cmd/sbc_harness/main.c46
-rw-r--r--cmd/sbc_harness/static/Documentation/YOUR_RIGHTS_AND_OBLIGATIONS.md12
-rw-r--r--cmd/sbc_harness/static/Documentation/harness_flash_bin.txt13
-rw-r--r--cmd/sbc_harness/static/Documentation/harness_uptime_txt.txt17
-rw-r--r--cmd/sbc_harness/usb_keyboard.c6
-rw-r--r--cmd/sbc_harness/usb_keyboard.h4
12 files changed, 611 insertions, 34 deletions
diff --git a/cmd/sbc_harness/CMakeLists.txt b/cmd/sbc_harness/CMakeLists.txt
index 64bf356..6e722d7 100644
--- a/cmd/sbc_harness/CMakeLists.txt
+++ b/cmd/sbc_harness/CMakeLists.txt
@@ -11,6 +11,9 @@ add_library(sbc_harness_objs OBJECT
main.c
usb_keyboard.c
tusb_log.c
+
+ fs_harness_flash_bin.c
+ fs_harness_uptime_txt.c
)
target_include_directories(sbc_harness_objs PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/config)
target_include_directories(sbc_harness_objs PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
@@ -20,13 +23,14 @@ target_link_libraries(sbc_harness_objs
pico_stdio_uart
hardware_flash
+ hardware_watchdog
libmisc
libfmt
libusb
libdhcp
libhw_cr
- lib9p
+ lib9p_srv
lib9p_util
)
pico_minimize_runtime(sbc_harness_objs
@@ -74,6 +78,7 @@ target_embed_sources(sbc_harness_objs sbc_harness static.h
static/Documentation/YOUR_RIGHTS_AND_OBLIGATIONS/newlib.txt
static/Documentation/harness_rom_bin.txt
static/Documentation/harness_flash_bin.txt
+ static/Documentation/harness_uptime_txt.txt
)
endif()
diff --git a/cmd/sbc_harness/config/config.h b/cmd/sbc_harness/config/config.h
index da3edad..5367dbe 100644
--- a/cmd/sbc_harness/config/config.h
+++ b/cmd/sbc_harness/config/config.h
@@ -9,6 +9,11 @@
#include <stddef.h> /* for size_t */
+#define CONFIG_FLASH_DEBUG 1
+
+#define _CONFIG_9P_MAX_CONNS 3 /* FIXME: bump this back up to 8 */
+#define _CONFIG_9P_MAX_REQS (2*_CONFIG_9P_MAX_CONNS)
+
/* RP2040 *********************************************************************/
#define CONFIG_RP2040_SPI_DEBUG 1 /* bool */
@@ -58,9 +63,6 @@
* struct padding, (2) array pointers.
*/
#define CONFIG_9P_SRV_MAX_HOSTMSG_SIZE CONFIG_9P_SRV_MAX_MSG_SIZE+16
-#define CONFIG_9P_SRV_MAX_FIDS 16
-#define CONFIG_9P_SRV_MAX_REQS 2
-#define CONFIG_9P_SRV_MAX_DEPTH 3
#define CONFIG_9P_ENABLE_9P2000 1 /* bool */
#define CONFIG_9P_ENABLE_9P2000_u 1 /* bool */
@@ -90,8 +92,8 @@
extern const size_t CONFIG_COROUTINE_STACK_SIZE_dhcp_cr;
extern const size_t CONFIG_COROUTINE_STACK_SIZE_init_cr;
-extern const size_t CONFIG_COROUTINE_STACK_SIZE_lib9p_srv_write_cr;
extern const size_t CONFIG_COROUTINE_STACK_SIZE_read9p_cr;
+extern const size_t CONFIG_COROUTINE_STACK_SIZE_write9p_cr;
extern const size_t CONFIG_COROUTINE_STACK_SIZE_usb_common_cr;
extern const size_t CONFIG_COROUTINE_STACK_SIZE_usb_keyboard_cr;
extern const size_t CONFIG_COROUTINE_STACK_SIZE_w5500_irq_cr;
@@ -102,12 +104,11 @@ extern const size_t CONFIG_COROUTINE_STACK_SIZE_w5500_irq_cr;
#define CONFIG_COROUTINE_VALGRIND 0 /* bool */
#define CONFIG_COROUTINE_GDB 1 /* bool */
-#define _CONFIG_9P_NUM_SOCKS 3 /* FIXME: bump this back up to 8 */
-#define CONFIG_COROUTINE_NUM ( \
- 1 /* usb_common */ + \
- 1 /* usb_keyboard */ + \
- 1 /* W5500 irq handler */ + \
- _CONFIG_9P_NUM_SOCKS /* 9P accept()+read() */ + \
- (CONFIG_9P_SRV_MAX_REQS*_CONFIG_9P_NUM_SOCKS) /* 9P work+write() */ )
+#define CONFIG_COROUTINE_NUM ( \
+ 1 /* usb_common */ + \
+ 1 /* usb_keyboard */ + \
+ 1 /* W5500 irq handler */ + \
+ _CONFIG_9P_MAX_CONNS /* 9P accept()+read() */ + \
+ _CONFIG_9P_MAX_REQS /* 9P work+write() */ )
#endif /* _CONFIG_H_ */
diff --git a/cmd/sbc_harness/fs_harness_flash_bin.c b/cmd/sbc_harness/fs_harness_flash_bin.c
new file mode 100644
index 0000000..bc3d061
--- /dev/null
+++ b/cmd/sbc_harness/fs_harness_flash_bin.c
@@ -0,0 +1,312 @@
+/* sbc_harness/fs_harness_flash_bin.c - 9P access to flash storage
+ *
+ * Copyright (C) 2025 Luke T. Shumaker <lukeshu@lukeshu.com>
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+#include <string.h>
+
+#include <hardware/flash.h>
+#include <hardware/watchdog.h>
+
+#define LOG_NAME FLASH
+#include <libmisc/log.h>
+
+#include <util9p/static.h>
+
+#define IMPLEMENTATION_FOR_FS_HARNESS_FLASH_BIN YES
+#include "fs_harness_flash_bin.h"
+
+LO_IMPLEMENTATION_C(lib9p_srv_file, struct flash_file, flash_file, static);
+
+LO_IMPLEMENTATION_H(lib9p_srv_fio, struct flash_file, flash_file);
+LO_IMPLEMENTATION_C(lib9p_srv_fio, struct flash_file, flash_file, static);
+
+#define DATA_START ((const char *)(XIP_NOALLOC_BASE))
+#define DATA_SIZE PICO_FLASH_SIZE_BYTES
+#define DATA_HSIZE (DATA_SIZE/2)
+static_assert(DATA_SIZE % FLASH_SECTOR_SIZE == 0);
+static_assert(DATA_HSIZE % FLASH_SECTOR_SIZE == 0);
+
+/* There are some memcpy()s (and memcmp()s?) in here that can (and
+ * arguably should) be replaced with SSI DMA. */
+
+/* ab_flash_* (mid-level utilities for our A/B write scheme) ******************/
+
+/**
+ * Copy the upper half of flash to the lower half of flash, then reboot.
+ *
+ * @param buf : a scratch buffer that is at least FLASH_SECTOR_SIZE
+ */
+[[noreturn]] static void __no_inline_not_in_flash_func(ab_flash_finalize)(uint8_t *buf) {
+ assert(buf);
+
+ infof("copying upper flash to lower flash...");
+
+ cr_save_and_disable_interrupts();
+
+ for (size_t off = 0; off < DATA_HSIZE; off += FLASH_SECTOR_SIZE) {
+ memcpy(buf, DATA_START+DATA_HSIZE+off, FLASH_SECTOR_SIZE);
+ if (memcmp(DATA_START+off, buf, FLASH_SECTOR_SIZE) == 0)
+ continue;
+ flash_range_erase(off, FLASH_SECTOR_SIZE);
+ flash_range_program(off, buf, FLASH_SECTOR_SIZE);
+ }
+
+ infof("rebooting...");
+
+ watchdog_reboot(0, 0, 300);
+
+ for (;;)
+ asm volatile ("nop");
+}
+
+/**
+ * Set the upper half of flash to all zero bytes.
+ *
+ * @param buf : a scratch buffer that is at least FLASH_SECTOR_SIZE
+ */
+static void ab_flash_initialize_zero(uint8_t *buf) {
+ assert(buf);
+
+ memset(buf, 0, FLASH_SECTOR_SIZE);
+
+ infof("zeroing upper flash...");
+ for (size_t off = DATA_HSIZE; off < DATA_SIZE; off += FLASH_SECTOR_SIZE) {
+ if (memcmp(buf, DATA_START+off, FLASH_SECTOR_SIZE) == 0)
+ continue;
+ bool saved = cr_save_and_disable_interrupts();
+ /* No need to `flash_range_erase()`; the way the flash
+ * works is that _erase() sets all bits to 1, and
+ * _program() sets some bits to 0. If we don't need
+ * any bits to be 1, then we can skip the
+ * _erase(). */
+ flash_range_program(off, buf, FLASH_SECTOR_SIZE);
+ cr_restore_interrupts(saved);
+ }
+ debugf("... zeroed");
+}
+
+/**
+ * Copy the lower half of flash to the upper half of flash.
+ *
+ * @param buf : a scratch buffer that is at least FLASH_SECTOR_SIZE
+ */
+static void ab_flash_initialize(uint8_t *buf) {
+ assert(buf);
+
+ infof("initializing upper flash...");
+ for (size_t off = 0; off < DATA_HSIZE; off += FLASH_SECTOR_SIZE) {
+ memcpy(buf, DATA_START+off, FLASH_SECTOR_SIZE);
+ if (memcmp(buf, DATA_START+DATA_HSIZE+off, FLASH_SECTOR_SIZE) == 0)
+ continue;
+ bool saved = cr_save_and_disable_interrupts();
+ flash_range_erase(DATA_HSIZE+off, FLASH_SECTOR_SIZE);
+ flash_range_program(DATA_HSIZE+off, buf, FLASH_SECTOR_SIZE);
+ cr_restore_interrupts(saved);
+ }
+ debugf("... initialized");
+}
+
+/**
+ * Write `dat` to flash sector `pos`+(DATA_SIZE/2) (i.e. `pos` is a
+ * sector in the lower half, but this function writes to the upper
+ * half).
+ *
+ * @param pos : start-position of the sector to write to, must be in the upper half of the flash
+ * @param dat : the FLASH_SECTOR_SIZE bytes to write
+ */
+static void ab_flash_write_sector(size_t pos, uint8_t *dat) {
+ assert(pos < DATA_HSIZE);
+ assert(pos % FLASH_SECTOR_SIZE == 0);
+ assert(dat);
+
+ pos += DATA_HSIZE;
+
+ infof("write flash sector @ %zu...", pos);
+ if (memcmp(dat, DATA_START+pos, FLASH_SECTOR_SIZE) != 0) {
+ bool saved = cr_save_and_disable_interrupts();
+ flash_range_erase(pos, FLASH_SECTOR_SIZE);
+ flash_range_program(pos, dat, FLASH_SECTOR_SIZE);
+ cr_restore_interrupts(saved);
+ }
+ debugf("... written");
+}
+
+/* srv_file *******************************************************************/
+
+static void flash_file_free(struct flash_file *self) {
+ assert(self);
+}
+static struct lib9p_qid flash_file_qid(struct flash_file *self) {
+ assert(self);
+
+ return (struct lib9p_qid){
+ .type = LIB9P_QT_FILE|LIB9P_QT_EXCL,
+ .vers = 1,
+ .path = self->pathnum,
+ };
+}
+
+static struct lib9p_stat flash_file_stat(struct flash_file *self, struct lib9p_srv_ctx *ctx) {
+ assert(self);
+ assert(ctx);
+
+ return (struct lib9p_stat){
+ .kern_type = 0,
+ .kern_dev = 0,
+ .file_qid = flash_file_qid(self),
+ .file_mode = LIB9P_DM_EXCL|0666,
+ .file_atime = UTIL9P_ATIME,
+ .file_mtime = UTIL9P_MTIME,
+ .file_size = DATA_SIZE,
+ .file_name = lib9p_str(self->name),
+ .file_owner_uid = lib9p_str("root"),
+ .file_owner_gid = lib9p_str("root"),
+ .file_last_modified_uid = lib9p_str("root"),
+ .file_extension = lib9p_str(NULL),
+ .file_owner_n_uid = 0,
+ .file_owner_n_gid = 0,
+ .file_last_modified_n_uid = 0,
+ };
+}
+static void flash_file_wstat(struct flash_file *self, struct lib9p_srv_ctx *ctx,
+ struct lib9p_stat) {
+ assert(self);
+ assert(ctx);
+
+ lib9p_error(&ctx->basectx, LIB9P_ERRNO_L_EROFS, "read-only part of filesystem");
+}
+static void flash_file_remove(struct flash_file *self, struct lib9p_srv_ctx *ctx) {
+ assert(self);
+ assert(ctx);
+
+ lib9p_error(&ctx->basectx, LIB9P_ERRNO_L_EROFS, "read-only part of filesystem");
+}
+
+LIB9P_SRV_NOTDIR(struct flash_file, flash_file);
+
+static lo_interface lib9p_srv_fio flash_file_fopen(struct flash_file *self, struct lib9p_srv_ctx *ctx,
+ bool rd, bool wr, bool trunc) {
+ assert(self);
+ assert(ctx);
+
+ if (rd) {
+ self->rbuf.ok = false;
+ }
+
+ if (wr) {
+ if (trunc) {
+ ab_flash_initialize_zero(self->wbuf.dat);
+ self->written = true;
+ } else {
+ ab_flash_initialize(self->wbuf.dat);
+ self->written = false;
+ }
+ self->wbuf.ok = false;
+ }
+
+ return lo_box_flash_file_as_lib9p_srv_fio(self);
+}
+
+/* srv_fio ********************************************************************/
+
+static uint32_t flash_file_iounit(struct flash_file *self) {
+ assert(self);
+ return FLASH_SECTOR_SIZE;
+}
+
+static void flash_file_iofree(struct flash_file *self) {
+ assert(self);
+
+ if (self->wbuf.ok)
+ ab_flash_write_sector(self->wbuf.pos, self->wbuf.dat);
+
+ if (self->written)
+ ab_flash_finalize(self->wbuf.dat);
+}
+
+static void flash_file_pread(struct flash_file *self, struct lib9p_srv_ctx *ctx,
+ uint32_t byte_count, uint64_t byte_offset,
+ struct iovec *ret) {
+ assert(self);
+ assert(ctx);
+ assert(ret);
+
+ if (byte_offset > DATA_SIZE) {
+ lib9p_error(&ctx->basectx,
+ LIB9P_ERRNO_L_EINVAL, "offset is past the chip size");
+ return;
+ }
+
+ /* Assume that somewhere down the line the iovec we return
+ * will be passed to DMA. We don't want the DMA engine to hit
+ * (slow) XIP (for instance, this can cause reads/writes to
+ * the SSP to get out of sync with eachother), so copy the
+ * data to a buffer in (fast) RAM first. It's lame that the
+ * DMA engine can only have a DREQ on one side of the channel.
+ */
+ if (byte_offset == DATA_SIZE) {
+ *ret = (struct iovec){
+ .iov_len = 0,
+ };
+ return;
+ }
+ size_t sector_base = LM_ROUND_DOWN(byte_offset, FLASH_SECTOR_SIZE);
+ if (byte_offset + byte_count > sector_base + FLASH_SECTOR_SIZE)
+ byte_count = (sector_base + FLASH_SECTOR_SIZE) - byte_offset;
+ assert(byte_offset + byte_count <= DATA_SIZE);
+
+ if (!self->rbuf.ok || self->rbuf.pos != sector_base) {
+ self->rbuf.ok = true;
+ self->rbuf.pos = sector_base;
+ memcpy(self->rbuf.dat, DATA_START+sector_base, FLASH_SECTOR_SIZE);
+ }
+
+ *ret = (struct iovec){
+ .iov_base = &self->rbuf.dat[byte_offset-sector_base],
+ .iov_len = byte_count,
+ };
+}
+
+/* TODO: Short/corrupt writes are dangerous. This should either (1)
+ * check a checksum, (2) use uf2 instead of verbatim data, or (3) use
+ * ihex instead of verbatim data. */
+static uint32_t flash_file_pwrite(struct flash_file *self, struct lib9p_srv_ctx *ctx,
+ void *buf,
+ uint32_t byte_count,
+ uint64_t byte_offset) {
+ assert(self);
+ assert(ctx);
+
+ if (byte_offset > DATA_HSIZE) {
+ lib9p_error(&ctx->basectx,
+ LIB9P_ERRNO_L_EINVAL, "offset is past half the chip size");
+ return 0;
+ }
+ if (byte_count == 0)
+ return 0;
+ if (byte_offset == DATA_HSIZE) {
+ lib9p_error(&ctx->basectx,
+ LIB9P_ERRNO_L_EINVAL, "offset is at half the chip size");
+ return 0;
+ }
+
+ size_t sector_base = LM_ROUND_DOWN(byte_offset, FLASH_SECTOR_SIZE);
+ if (byte_offset + byte_count > sector_base + FLASH_SECTOR_SIZE)
+ byte_count = (sector_base + FLASH_SECTOR_SIZE) - byte_offset;
+ assert(byte_offset + byte_count < DATA_HSIZE);
+
+ if (self->wbuf.ok && self->wbuf.pos != sector_base)
+ ab_flash_write_sector(self->wbuf.pos, self->wbuf.dat);
+ if (!self->wbuf.ok || self->wbuf.pos != sector_base) {
+ self->wbuf.ok = true;
+ self->wbuf.pos = sector_base;
+ if (byte_count != FLASH_SECTOR_SIZE)
+ memcpy(self->wbuf.dat, DATA_START+DATA_HSIZE+sector_base, FLASH_SECTOR_SIZE);
+ }
+ memcpy(&self->wbuf.dat[byte_offset-sector_base], buf, byte_count);
+
+ self->written = true;
+ return byte_count;
+}
diff --git a/cmd/sbc_harness/fs_harness_flash_bin.h b/cmd/sbc_harness/fs_harness_flash_bin.h
new file mode 100644
index 0000000..36382be
--- /dev/null
+++ b/cmd/sbc_harness/fs_harness_flash_bin.h
@@ -0,0 +1,30 @@
+/* sbc_harness/fs_harness_flash_bin.h - 9P access to flash storage
+ *
+ * Copyright (C) 2025 Luke T. Shumaker <lukeshu@lukeshu.com>
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+#ifndef _SBC_HARNESS_FS_HARNESS_FLASH_BIN_H_
+#define _SBC_HARNESS_FS_HARNESS_FLASH_BIN_H_
+
+#include <hardware/flash.h> /* for FLASH_SECTOR_SIZE */
+
+#include <lib9p/srv.h>
+
+struct flash_file {
+ char *name;
+ uint64_t pathnum;
+
+ BEGIN_PRIVATE(FS_HARNESS_FLASH_BIN);
+ bool written;
+ struct {
+ bool ok;
+ size_t pos;
+ uint8_t dat[FLASH_SECTOR_SIZE];
+ } wbuf, rbuf;
+ END_PRIVATE(FS_HARNESS_FLASH_BIN);
+};
+LO_IMPLEMENTATION_H(lib9p_srv_file, struct flash_file, flash_file);
+#define lo_box_flash_file_as_lib9p_srv_file(obj) util9p_box(flash_file, obj)
+
+#endif /* _SBC_HARNESS_FS_HARNESS_FLASH_BIN_H_ */
diff --git a/cmd/sbc_harness/fs_harness_uptime_txt.c b/cmd/sbc_harness/fs_harness_uptime_txt.c
new file mode 100644
index 0000000..1425bf9
--- /dev/null
+++ b/cmd/sbc_harness/fs_harness_uptime_txt.c
@@ -0,0 +1,156 @@
+/* sbc_harness/fs_harness_uptime_txt.c - 9P access to harness uptime
+ *
+ * Copyright (C) 2025 Luke T. Shumaker <lukeshu@lukeshu.com>
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+#include <stdio.h> /* for snprintf() */
+#include <stdlib.h> /* for malloc(), free() */
+
+#include <libhw/generic/alarmclock.h>
+#include <util9p/static.h>
+
+#include "fs_harness_uptime_txt.h"
+
+LO_IMPLEMENTATION_C(lib9p_srv_file, struct uptime_file, uptime_file, static);
+
+struct uptime_fio {
+ struct uptime_file *parent;
+ size_t buf_len;
+ char buf[24]; /* len(str(UINT64_MAX)+"ns\n\0") */
+};
+
+LO_IMPLEMENTATION_H(lib9p_srv_fio, struct uptime_fio, uptime_fio);
+LO_IMPLEMENTATION_C(lib9p_srv_fio, struct uptime_fio, uptime_fio, static);
+
+/* srv_file *******************************************************************/
+
+static void uptime_file_free(struct uptime_file *self) {
+ assert(self);
+}
+static struct lib9p_qid uptime_file_qid(struct uptime_file *self) {
+ assert(self);
+
+ return (struct lib9p_qid){
+ .type = LIB9P_QT_FILE,
+ .vers = 1,
+ .path = self->pathnum,
+ };
+}
+
+static struct lib9p_stat uptime_file_stat(struct uptime_file *self, struct lib9p_srv_ctx *ctx) {
+ assert(self);
+ assert(ctx);
+
+ uint64_t now = LO_CALL(bootclock, get_time_ns);
+ uint64_t size = 0;
+ while (now) {
+ size++;
+ now /= 10;
+ }
+ if (!size)
+ size++;
+ size += 3;
+
+ return (struct lib9p_stat){
+ .kern_type = 0,
+ .kern_dev = 0,
+ .file_qid = uptime_file_qid(self),
+ .file_mode = 0444,
+ .file_atime = UTIL9P_ATIME,
+ .file_mtime = UTIL9P_MTIME,
+ .file_size = size,
+ .file_name = lib9p_str(self->name),
+ .file_owner_uid = lib9p_str("root"),
+ .file_owner_gid = lib9p_str("root"),
+ .file_last_modified_uid = lib9p_str("root"),
+ .file_extension = lib9p_str(NULL),
+ .file_owner_n_uid = 0,
+ .file_owner_n_gid = 0,
+ .file_last_modified_n_uid = 0,
+ };
+}
+static void uptime_file_wstat(struct uptime_file *self, struct lib9p_srv_ctx *ctx,
+ struct lib9p_stat) {
+ assert(self);
+ assert(ctx);
+
+ lib9p_error(&ctx->basectx, LIB9P_ERRNO_L_EROFS, "read-only part of filesystem");
+}
+static void uptime_file_remove(struct uptime_file *self, struct lib9p_srv_ctx *ctx) {
+ assert(self);
+ assert(ctx);
+
+ lib9p_error(&ctx->basectx, LIB9P_ERRNO_L_EROFS, "read-only part of filesystem");
+}
+
+LIB9P_SRV_NOTDIR(struct uptime_file, uptime_file);
+
+static lo_interface lib9p_srv_fio uptime_file_fopen(struct uptime_file *self, struct lib9p_srv_ctx *ctx,
+ bool LM_UNUSED(rd), bool LM_UNUSED(wr), bool LM_UNUSED(trunc)) {
+ assert(self);
+ assert(ctx);
+
+ struct uptime_fio *ret = malloc(sizeof(struct uptime_fio));
+ ret->parent = self;
+ ret->buf_len = 0;
+
+ return lo_box_uptime_fio_as_lib9p_srv_fio(ret);
+}
+
+/* srv_fio ********************************************************************/
+
+static uint32_t uptime_fio_iounit(struct uptime_fio *self) {
+ assert(self);
+ return sizeof(self->buf)-1;
+}
+
+static void uptime_fio_iofree(struct uptime_fio *self) {
+ assert(self);
+ free(self);
+}
+
+static struct lib9p_qid uptime_fio_qid(struct uptime_fio *self) {
+ assert(self);
+ assert(self->parent);
+ return uptime_file_qid(self->parent);
+}
+
+static void uptime_fio_pread(struct uptime_fio *self, struct lib9p_srv_ctx *ctx,
+ uint32_t byte_count, uint64_t byte_offset,
+ struct iovec *ret) {
+ assert(self);
+ assert(ctx);
+ assert(ret);
+
+ if (byte_offset == 0 || self->buf_len == 0) {
+ uint64_t now = LO_CALL(bootclock, get_time_ns);
+ self->buf_len = snprintf(self->buf, sizeof(self->buf), "%"PRIu64"ns\n", now);
+ }
+
+ if (byte_offset > (uint64_t)self->buf_len) {
+ lib9p_error(&ctx->basectx,
+ LIB9P_ERRNO_L_EINVAL, "offset is past end-of-file length");
+ return;
+ }
+
+ size_t beg_off = (size_t)byte_offset;
+ size_t end_off = beg_off + (size_t)byte_count;
+ if (end_off > self->buf_len)
+ end_off = self->buf_len;
+ *ret = (struct iovec){
+ .iov_base = &self->buf[beg_off],
+ .iov_len = end_off-beg_off,
+ };
+}
+
+static uint32_t uptime_fio_pwrite(struct uptime_fio *self, struct lib9p_srv_ctx *ctx,
+ void *LM_UNUSED(buf),
+ uint32_t LM_UNUSED(byte_count),
+ uint64_t LM_UNUSED(byte_offset)) {
+ assert(self);
+ assert(ctx);
+
+ lib9p_error(&ctx->basectx, LIB9P_ERRNO_L_EROFS, "read-only part of filesystem");
+ return 0;
+}
diff --git a/cmd/sbc_harness/fs_harness_uptime_txt.h b/cmd/sbc_harness/fs_harness_uptime_txt.h
new file mode 100644
index 0000000..7bf2945
--- /dev/null
+++ b/cmd/sbc_harness/fs_harness_uptime_txt.h
@@ -0,0 +1,19 @@
+/* sbc_harness/fs_harness_uptime_txt.h - 9P access to harness uptime
+ *
+ * Copyright (C) 2025 Luke T. Shumaker <lukeshu@lukeshu.com>
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+#ifndef _SBC_HARNESS_FS_HARNESS_UPTIME_TXT_H_
+#define _SBC_HARNESS_FS_HARNESS_UPTIME_TXT_H_
+
+#include <lib9p/srv.h>
+
+struct uptime_file {
+ char *name;
+ uint64_t pathnum;
+};
+LO_IMPLEMENTATION_H(lib9p_srv_file, struct uptime_file, uptime_file);
+#define lo_box_uptime_file_as_lib9p_srv_file(obj) util9p_box(uptime_file, obj)
+
+#endif /* _SBC_HARNESS_FS_HARNESS_UPTIME_TXT_H_ */
diff --git a/cmd/sbc_harness/main.c b/cmd/sbc_harness/main.c
index 2b2b3f8..5630e83 100644
--- a/cmd/sbc_harness/main.c
+++ b/cmd/sbc_harness/main.c
@@ -32,11 +32,20 @@
/* local headers */
#include "usb_keyboard.h"
#include "static.h"
+#include "fs_harness_flash_bin.h"
+#include "fs_harness_uptime_txt.h"
/* configuration **************************************************************/
#include "config.h"
+#ifndef _CONFIG_9P_MAX_CONNS
+ #error config.h must define _CONFIG_9P_MAX_CONNS
+#endif
+#ifndef _CONFIG_9P_MAX_REQS
+ #error config.h must define _CONFIG_9P_MAX_REQS
+#endif
+
/* file tree ******************************************************************/
enum { PATH_BASE = __COUNTER__ };
@@ -45,6 +54,13 @@ enum { PATH_BASE = __COUNTER__ };
#define STATIC_FILE(STRNAME, ...) UTIL9P_STATIC_FILE(PATH_COUNTER, STRNAME, __VA_ARGS__)
#define STATIC_DIR(STRNAME, ...) UTIL9P_STATIC_DIR(PATH_COUNTER, STRNAME, __VA_ARGS__)
+#define API_FILE(STRNAME, SYMNAME, ...) \
+ lo_box_##SYMNAME##_file_as_lib9p_srv_file(&((struct SYMNAME##_file){ \
+ .name = STRNAME, \
+ .pathnum = PATH_COUNTER \
+ __VA_OPT__(,) __VA_ARGS__ \
+ }))
+
struct lib9p_srv_file root =
STATIC_DIR("",
STATIC_DIR("Documentation",
@@ -74,15 +90,18 @@ struct lib9p_srv_file root =
STATIC_FILE("harness_flash_bin.txt",
.data_start = _binary_static_Documentation_harness_flash_bin_txt_start,
.data_end = _binary_static_Documentation_harness_flash_bin_txt_end),
+ STATIC_FILE("harness_uptime_txt.txt",
+ .data_start = _binary_static_Documentation_harness_uptime_txt_txt_start,
+ .data_end = _binary_static_Documentation_harness_uptime_txt_txt_end),
),
STATIC_DIR("harness",
STATIC_FILE("rom.bin",
.data_start = (void*)0x00000000,
.data_size = 16*1024),
- // TODO: Make flash.bin writable.
- STATIC_FILE("flash.bin",
- .data_start = (void*)0x10000000,
- .data_size = PICO_FLASH_SIZE_BYTES),
+ API_FILE("flash.bin",
+ flash),
+ API_FILE("uptime.txt",
+ uptime),
// TODO: system.log
// TODO: proc.txt
// TODO: cpuinfo.txt
@@ -141,13 +160,21 @@ static COROUTINE read9p_cr(void *) {
lo_interface net_iface iface = lo_box_w5500_if_as_net_iface(&globals.dev_w5500);
lo_interface net_stream_listener listener = LO_CALL(iface, tcp_listen, LIB9P_DEFAULT_PORT_9FS);
- lib9p_srv_read_cr(&globals.srv, listener);
+ lib9p_srv_accept_and_read_loop(&globals.srv, listener);
+
+ cr_end();
+}
+
+static COROUTINE write9p_cr(void *) {
+ cr_begin();
+
+ lib9p_srv_worker_loop(&globals.srv);
cr_end();
}
const char *const hexdig = "0123456789ABCDEF";
-static_assert(CONFIG_9P_SRV_MAX_REQS*_CONFIG_9P_NUM_SOCKS <= 16);
+static_assert(_CONFIG_9P_MAX_REQS <= 16);
COROUTINE init_cr(void *) {
cr_begin();
@@ -203,13 +230,13 @@ COROUTINE init_cr(void *) {
coroutine_add("usb_keyboard", usb_keyboard_cr, &globals.keyboard_chan);
//coroutine_add("hello_world", hello_world_cr, &globals.keyboard_chan);
coroutine_add("dhcp", dhcp_cr, NULL);
- for (int i = 0; i < _CONFIG_9P_NUM_SOCKS; i++) {
+ for (int i = 0; i < _CONFIG_9P_MAX_CONNS; i++) {
char name[] = {'r', 'e', 'a', 'd', '-', hexdig[i], '\0'};
coroutine_add(name, read9p_cr, NULL);
}
- for (int i = 0; i < CONFIG_9P_SRV_MAX_REQS*_CONFIG_9P_NUM_SOCKS; i++) {
+ for (int i = 0; i < _CONFIG_9P_MAX_REQS; i++) {
char name[] = {'w', 'r', 'i', 't', 'e', '-', hexdig[i], '\0'};
- coroutine_add(name, lib9p_srv_write_cr, &globals.srv);
+ coroutine_add(name, write9p_cr, NULL);
}
cr_exit();
@@ -222,4 +249,5 @@ int main() {
infof("===================================================================");
coroutine_add("init", init_cr, NULL);
coroutine_main();
+ assert_notreached("all coroutines exited");
}
diff --git a/cmd/sbc_harness/static/Documentation/YOUR_RIGHTS_AND_OBLIGATIONS.md b/cmd/sbc_harness/static/Documentation/YOUR_RIGHTS_AND_OBLIGATIONS.md
index 1e89d72..b3fc12e 100644
--- a/cmd/sbc_harness/static/Documentation/YOUR_RIGHTS_AND_OBLIGATIONS.md
+++ b/cmd/sbc_harness/static/Documentation/YOUR_RIGHTS_AND_OBLIGATIONS.md
@@ -8,22 +8,22 @@
The firmware running on the SBC-Harness is Free Software -- if you
have access to this file, then you have the freedom to use, study,
-share, and improve; as long as you follow a few conditions that
-basically amount to "paying it forward".
+share, and improve the firmware; as long as you follow a few
+conditions that basically amount to "paying it forward".
The precise terms of your rights and obligations are given in the
files in the `YOUR_RIGHTS_AND_OBLIGATIONS/` directory. You must obey
-each of the files in that directory when distributing copies or
-modified copies of the firmware:
+each of the files in that directory when distributing
+verbatim-or-modified copies of the firmware:
- `agpl-3.0.txt`: The firmware is as-a-whole licensed to you under
the terms of the GNU Affero General Public License (GNU AGPL) as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version. This is the main
- document that protects your rights and defines obligations.
+ document that protects your rights and defines your obligations.
- other files: The firmware makes use of other code that is under
various other licenses. When taken with the AGPL, they amount to
requiring that you pass along the copyright and license text in
- those files when you distribute copies or modified copies of the
+ those files when you distribute verbatim-or-modified copies of the
firmware.
diff --git a/cmd/sbc_harness/static/Documentation/harness_flash_bin.txt b/cmd/sbc_harness/static/Documentation/harness_flash_bin.txt
index 982d7e0..115f2ee 100644
--- a/cmd/sbc_harness/static/Documentation/harness_flash_bin.txt
+++ b/cmd/sbc_harness/static/Documentation/harness_flash_bin.txt
@@ -7,6 +7,10 @@ DESCRIPTION
Any number of readers may read the flash contents.
+ Only one writer can have the file open at a time; once the
+ file is closed, the harness reboots into the new firmware.
+ Writes to the top half of the chip will fail.
+
BUGS
- The size of the chip is configured at compile-time. If the
firmware is loaded onto hardware with a larger flash chip
@@ -16,8 +20,13 @@ BUGS
compiled for, then accessing the missing upper part of the
chip will crash.
- - This file is not writable; it aught to be possible to update
- the harness firmware by writing to this file.
+ - When writing to the flash using this file, only half of the
+ chip capacity is usable; the top half and bottom half are
+ mirrors of each-other. This is to avoid the firmware
+ crashing as its program text is overwritten; the firmware is
+ executing out of the bottom half, and writing to the top
+ half; once the file is closed, a minimal in-RAM function
+ copies the top half to the bottom half and reboots.
AUTHOR
Copyright (C) 2025 Luke T. Shumaker <lukeshu@lukeshu.com>
diff --git a/cmd/sbc_harness/static/Documentation/harness_uptime_txt.txt b/cmd/sbc_harness/static/Documentation/harness_uptime_txt.txt
new file mode 100644
index 0000000..1ab86f7
--- /dev/null
+++ b/cmd/sbc_harness/static/Documentation/harness_uptime_txt.txt
@@ -0,0 +1,17 @@
+NAME
+ /harness/uptime.txt
+
+DESCRIPTION
+ Reading this file gives a text string of the format
+ `sprintf("%uns\n", uptime_ns)` indicating the harness's uptime
+ in an integer number of nanoseconds.
+
+BUGS
+ Using nanoseconds gives the illusion of more precision than
+ there actually is; the harness' clock only has microsecond
+ resolution; the last 3 digits of the returned nanosecond count
+ will always be 0.
+
+AUTHOR
+ Copyright (C) 2025 Luke T. Shumaker <lukeshu@lukeshu.com>
+ SPDX-License-Identifier: AGPL-3.0-or-later
diff --git a/cmd/sbc_harness/usb_keyboard.c b/cmd/sbc_harness/usb_keyboard.c
index f3cb42d..7dd8a24 100644
--- a/cmd/sbc_harness/usb_keyboard.c
+++ b/cmd/sbc_harness/usb_keyboard.c
@@ -54,8 +54,8 @@ COROUTINE usb_keyboard_cr(void *_chan) {
while (!tud_hid_n_ready(kbd_ifc))
cr_yield();
- if (usb_keyboard_rpc_can_recv_req(chan)) {
- usb_keyboard_rpc_req_t req = usb_keyboard_rpc_recv_req(chan);
+ if (cr_rpc_can_recv_req(chan)) {
+ usb_keyboard_rpc_req_t req = cr_rpc_recv_req(chan);
uint32_t rune = req.req;
modifier = ascii2keycode[rune][0] ? KEYBOARD_MODIFIER_LEFTSHIFT : 0;
@@ -69,7 +69,7 @@ COROUTINE usb_keyboard_cr(void *_chan) {
keycodes[0] = 0;
tud_hid_n_keyboard_report(kbd_ifc, report_id, modifier, keycodes);
- usb_keyboard_rpc_send_resp(req, 1);
+ cr_rpc_send_resp(req, 1);
} else {
modifier = 0;
keycodes[0] = 0;
diff --git a/cmd/sbc_harness/usb_keyboard.h b/cmd/sbc_harness/usb_keyboard.h
index 210014d..cf8483b 100644
--- a/cmd/sbc_harness/usb_keyboard.h
+++ b/cmd/sbc_harness/usb_keyboard.h
@@ -1,6 +1,6 @@
/* sbc_harness/usb_keyboard.h - Implementation of a USB keyboard device
*
- * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com>
+ * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
@@ -12,7 +12,7 @@
#include <libcr/coroutine.h> /* for COROUTINE */
#include <libcr_ipc/rpc.h> /* for CR_RPC_DECLARE */
-CR_RPC_DECLARE(usb_keyboard_rpc, uint32_t, int)
+CR_RPC_DECLARE(usb_keyboard_rpc, uint32_t, int);
void usb_keyboard_init(void);
COROUTINE usb_keyboard_cr(void *arg);