diff options
Diffstat (limited to 'cmd')
26 files changed, 890 insertions, 576 deletions
diff --git a/cmd/sbc_harness/CMakeLists.txt b/cmd/sbc_harness/CMakeLists.txt index e30101d..6e722d7 100644 --- a/cmd/sbc_harness/CMakeLists.txt +++ b/cmd/sbc_harness/CMakeLists.txt @@ -1,6 +1,6 @@ # cmd/sbc_harness/CMakeLists.txt - Build script for main sbc_harness.uf2 firmware file # -# 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 if (PICO_PLATFORM STREQUAL "rp2040") @@ -11,21 +11,41 @@ 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}) +target_include_directories(sbc_harness_objs PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) target_link_libraries(sbc_harness_objs pico_runtime pico_stdio_uart hardware_flash + hardware_watchdog libmisc + libfmt libusb libdhcp - libhw + libhw_cr + lib9p_srv + lib9p_util +) +pico_minimize_runtime(sbc_harness_objs + INCLUDE PRINTF PRINTF_MINIMAL PRINTF_LONG_LONG PRINTF_PTRDIFF_T +) +target_compile_definitions(sbc_harness_objs PRIVATE + #PICO_USE_FASTEST_SUPPORTED_CLOCK=1 + + # Calculated by `./3rd-party/pico-sdk/src/rp2_common/hardware_clocks/scripts/vcocalc.py --cmake-only 170` + PLL_SYS_REFDIV=2 + PLL_SYS_VCO_FREQ_HZ=1530000000 + PLL_SYS_POSTDIV1=3 + PLL_SYS_POSTDIV2=3 + SYS_CLK_HZ=170000000 ) -pico_minimize_runtime(sbc_harness_objs) suppress_tinyusb_warnings() @@ -36,10 +56,6 @@ add_stack_analysis(sbc_harness_stack.c sbc_harness_objs) # Link ######################################################################### add_executable(sbc_harness) -set_source_files_properties("$<TARGET_OBJECTS:sbc_harness_objs>" PROPERTIES - EXTERNAL_OBJECT true - GENERATED true -) target_sources(sbc_harness PRIVATE sbc_harness_stack.c "$<TARGET_OBJECTS:sbc_harness_objs>" @@ -47,7 +63,22 @@ target_sources(sbc_harness PRIVATE target_link_libraries(sbc_harness pico_standard_link ) +target_link_options(sbc_harness PRIVATE "$<TARGET_PROPERTY:sbc_harness_objs,LINK_OPTIONS>") pico_add_extra_outputs(sbc_harness) # create .map/.bin/.hex/.uf2 files in addition to .elf pico_set_program_url(sbc_harness "https://git.lukeshu.com/sbc-harness") +# Embed ######################################################################## + +target_embed_sources(sbc_harness_objs sbc_harness static.h + static/Documentation/YOUR_RIGHTS_AND_OBLIGATIONS.md + static/Documentation/YOUR_RIGHTS_AND_OBLIGATIONS/agpl-3.0.txt + static/Documentation/YOUR_RIGHTS_AND_OBLIGATIONS/pico-sdk.bsd3.txt + static/Documentation/YOUR_RIGHTS_AND_OBLIGATIONS/printf.mit.txt + static/Documentation/YOUR_RIGHTS_AND_OBLIGATIONS/tinyusb.mit.txt + 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 2b6a6cf..ca23462 100644 --- a/cmd/sbc_harness/config/config.h +++ b/cmd/sbc_harness/config/config.h @@ -1,18 +1,24 @@ /* config.h - Compile-time configuration for sbc_harness * - * 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 */ #ifndef _CONFIG_H_ #define _CONFIG_H_ -/* W5500 **********************************************************************/ +#include <stddef.h> /* for size_t */ -/** - * How many W5500 chips we have. - */ -#define CONFIG_W5500_NUM 1 +#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 */ + +/* W5500 **********************************************************************/ /** * When allocating an arbitrary local port, what range should it be @@ -25,13 +31,26 @@ #define CONFIG_W5500_LOCAL_PORT_MIN 32768 #define CONFIG_W5500_LOCAL_PORT_MAX 60999 -#define CONFIG_W5500_DEBUG 1 /* bool */ - +#define CONFIG_W5500_VALIDATE_SPI 1 /* bool */ +#define CONFIG_W5500_DEBUG 0 /* bool */ #define CONFIG_W5500_LL_DEBUG 0 /* bool */ /* 9P *************************************************************************/ -#define CONFIG_9P_PORT 564 +#define CONFIG_9P_MAX_ERR_SIZE 128 /* 128 is what Plan 9 4e uses */ + +#define CONFIG_9P_ENABLE_9P2000 1 /* bool */ +#define CONFIG_9P_ENABLE_9P2000_u 1 /* bool */ +#define CONFIG_9P_ENABLE_9P2000_e 0 /* bool */ +#define CONFIG_9P_ENABLE_9P2000_L 0 /* bool */ +#define CONFIG_9P_ENABLE_9P2000_p9p 0 /* bool */ + +/* 9P_SRV *********************************************************************/ + +#define CONFIG_9P_SRV_DEBUG 1 /* bool */ + +#define CONFIG_9P_SRV_MAX_MSG_SIZE ((4*1024)+24) + /** * This max-msg-size is sized so that a Twrite message can return * 8KiB of data. @@ -49,19 +68,13 @@ * negotiated. In Plan 9 1e it was (8*1024)+128, and was bumped to * (8*1024)+160 in 2e and 3e. */ -#define CONFIG_9P_MAX_MSG_SIZE ((4*1024)+24) +#define CONFIG_9P_SRV_MAX_MSG_SIZE ((4*1024)+24) /** * Maximum host-data-structure size. A message may be larger in * unmarshaled-host-structures than marshaled-net-bytes due to (1) - * struct padding, (2) nul-terminator byes for strings. + * struct padding, (2) array pointers. */ -#define CONFIG_9P_MAX_HOSTMSG_SIZE CONFIG_9P_MAX_MSG_SIZE+16 -#define CONFIG_9P_MAX_FIDS 16 -#define CONFIG_9P_MAX_REQS 2 -#define CONFIG_9P_MAX_ERR_SIZE 128 /* 128 is what Plan 9 4e uses */ -#define CONFIG_9P_ENABLE_9P2000 1 /* bool */ -#define CONFIG_9P_ENABLE_9P2000_u 1 /* bool */ -#define CONFIG_9P_ENABLE_9P2000_e 0 /* bool */ +#define CONFIG_9P_SRV_MAX_HOSTMSG_SIZE CONFIG_9P_SRV_MAX_MSG_SIZE+16 /* DHCP ***********************************************************************/ @@ -83,7 +96,13 @@ /* COROUTINE ******************************************************************/ -#define CONFIG_COROUTINE_DEFAULT_STACK_SIZE (2*1024) +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_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; #define CONFIG_COROUTINE_NAME_LEN 16 #define CONFIG_COROUTINE_MEASURE_STACK 1 /* bool */ #define CONFIG_COROUTINE_PROTECT_STACK 1 /* bool */ @@ -91,12 +110,11 @@ #define CONFIG_COROUTINE_VALGRIND 0 /* bool */ #define CONFIG_COROUTINE_GDB 1 /* bool */ -#define _CONFIG_9P_NUM_SOCKS 7 -#define CONFIG_COROUTINE_NUM ( \ - 1 /* usb_common */ + \ - 1 /* usb_keyboard */ + \ - CONFIG_W5500_NUM /* irq handler */ + \ - _CONFIG_9P_NUM_SOCKS /* 9P accept()+read() */ + \ - (CONFIG_9P_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/config/tusb_config.h b/cmd/sbc_harness/config/tusb_config.h index 0a6d3e4..5240311 100644 --- a/cmd/sbc_harness/config/tusb_config.h +++ b/cmd/sbc_harness/config/tusb_config.h @@ -1,6 +1,6 @@ /* tusb_config.h - Compile-time configuration for the TinyUSB library * - * 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 * * SPDX-License-Identifier: MIT @@ -61,7 +61,7 @@ extern "C" { TU_BREAKPOINT(); \ return _ret; \ } \ - } while(0) + } while (0) void _libmisc_tu_mess_failed(const char *expr, const char *file, unsigned int line, const char *func); 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..5920b85 --- /dev/null +++ b/cmd/sbc_harness/fs_harness_flash_bin.c @@ -0,0 +1,307 @@ +/* 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_srv_stat flash_file_stat(struct flash_file *self, struct lib9p_srv_ctx *ctx) { + assert(self); + assert(ctx); + + return (struct lib9p_srv_stat){ + .qid = flash_file_qid(self), + .mode = LIB9P_DM_EXCL|0666, + .atime_sec = UTIL9P_ATIME, + .mtime_sec = UTIL9P_MTIME, + .size = DATA_SIZE, + .name = lib9p_str(self->name), + .owner_uid = { .name = lib9p_str("root"), .num = 0 }, + .owner_gid = { .name = lib9p_str("root"), .num = 0 }, + .last_modifier_uid = { .name = lib9p_str("root"), .num = 0 }, + .extension = lib9p_str(NULL), + }; +} +static void flash_file_wstat(struct flash_file *self, struct lib9p_srv_ctx *ctx, + struct lib9p_srv_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..f7b755f --- /dev/null +++ b/cmd/sbc_harness/fs_harness_uptime_txt.c @@ -0,0 +1,151 @@ +/* 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 <libhw/generic/alarmclock.h> +#include <util9p/static.h> +#include <libmisc/alloc.h> /* for heap_alloc(), free() */ + +#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_srv_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_srv_stat){ + .qid = uptime_file_qid(self), + .mode = 0444, + .atime_sec = UTIL9P_ATIME, + .mtime_sec = UTIL9P_MTIME, + .size = size, + .name = lib9p_str(self->name), + .owner_uid = { .name = lib9p_str("root"), .num = 0 }, + .owner_gid = { .name = lib9p_str("root"), .num = 0 }, + .last_modifier_uid = { .name = lib9p_str("root"), .num = 0 }, + .extension = lib9p_str(NULL), + }; +} +static void uptime_file_wstat(struct uptime_file *self, struct lib9p_srv_ctx *ctx, + struct lib9p_srv_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 = heap_alloc(1, 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 fb4b696..5630e83 100644 --- a/cmd/sbc_harness/main.c +++ b/cmd/sbc_harness/main.c @@ -1,29 +1,127 @@ /* sbc_harness/main.c - Main entry point and event loop for sbc-harness * - * 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 */ -#include <string.h> /* libc: for strlen() */ +/* libc */ +#include <string.h> /* libc: for strlen() */ +/* pico-sdk */ #include <pico/stdio_uart.h> /* pico-sdk:pico_stdio_uart: for stdio_uart_init() */ #include <hardware/flash.h> /* pico-sdk:hardware_flash: for flash_get_unique_id() */ +/* our OS */ #include <libcr/coroutine.h> +#include <libhw/generic/alarmclock.h> /* so we can set `bootclock` */ #include <libhw/rp2040_hwspi.h> +#include <libhw/rp2040_hwtimer.h> #include <libhw/w5500.h> -#include <libmisc/hash.h> -#include <libusb/usb_common.h> + +/* our application libraries */ #include <libdhcp/client.h> +#include <libusb/usb_common.h> +#include <lib9p/srv.h> +#include <util9p/static.h> +/* our utility libraries */ +#include <libmisc/hash.h> #define LOG_NAME MAIN #include <libmisc/log.h> +/* 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__ }; +#define PATH_COUNTER __COUNTER__ - PATH_BASE + +#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", + STATIC_FILE("YOUR_RIGHTS_AND_OBLIGATIONS.md", + .data_start = _binary_static_Documentation_YOUR_RIGHTS_AND_OBLIGATIONS_md_start, + .data_end = _binary_static_Documentation_YOUR_RIGHTS_AND_OBLIGATIONS_md_end), + STATIC_DIR("YOUR_RIGHTS_AND_OBLIGATIONS", + STATIC_FILE("agpl-3.0.txt", + .data_start = _binary_static_Documentation_YOUR_RIGHTS_AND_OBLIGATIONS_agpl_3_0_txt_start, + .data_end = _binary_static_Documentation_YOUR_RIGHTS_AND_OBLIGATIONS_agpl_3_0_txt_end), + STATIC_FILE("pico-sdk.bsd3.txt", + .data_start = _binary_static_Documentation_YOUR_RIGHTS_AND_OBLIGATIONS_pico_sdk_bsd3_txt_start, + .data_end = _binary_static_Documentation_YOUR_RIGHTS_AND_OBLIGATIONS_pico_sdk_bsd3_txt_end), + STATIC_FILE("printf.mit.txt", + .data_start = _binary_static_Documentation_YOUR_RIGHTS_AND_OBLIGATIONS_printf_mit_txt_start, + .data_end = _binary_static_Documentation_YOUR_RIGHTS_AND_OBLIGATIONS_printf_mit_txt_end), + STATIC_FILE("tinyusb.mit.txt", + .data_start = _binary_static_Documentation_YOUR_RIGHTS_AND_OBLIGATIONS_tinyusb_mit_txt_start, + .data_end = _binary_static_Documentation_YOUR_RIGHTS_AND_OBLIGATIONS_tinyusb_mit_txt_end), + STATIC_FILE("newlib.txt", + .data_start = _binary_static_Documentation_YOUR_RIGHTS_AND_OBLIGATIONS_newlib_txt_start, + .data_end = _binary_static_Documentation_YOUR_RIGHTS_AND_OBLIGATIONS_newlib_txt_end), + ), + STATIC_FILE("harness_rom_bin.txt", + .data_start = _binary_static_Documentation_harness_rom_bin_txt_start, + .data_end = _binary_static_Documentation_harness_rom_bin_txt_end), + 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), + API_FILE("flash.bin", + flash), + API_FILE("uptime.txt", + uptime), + // TODO: system.log + // TODO: proc.txt + // TODO: cpuinfo.txt + // TODO: ctl + ), + STATIC_DIR("dut", + // TODO: hdmi.nut + // TODO: uart.txt + // TODO: usb-keyboard.txt + ), + ); + +static lo_interface lib9p_srv_file get_root(struct lib9p_srv_ctx *LM_UNUSED(ctx), struct lib9p_s LM_UNUSED(treename)) { + return root; +} -#define ARRAY_LEN(arr) (sizeof(arr)/sizeof((arr)[0])) +/* Code ***********************************************************************/ -COROUTINE hello_world_cr(void *_chan) { +/* +static COROUTINE hello_world_cr(void *_chan) { const char *msg = "Hello world!\n"; usb_keyboard_rpc_t *chan = _chan; cr_begin(); @@ -38,24 +136,45 @@ COROUTINE hello_world_cr(void *_chan) { cr_end(); } - -COROUTINE dhcp_cr(void *_chip) { - struct w5500 *chip = _chip; - cr_begin(); - - dhcp_client_main(chip, "harness"); - - cr_end(); -} +*/ struct { struct rp2040_hwspi dev_spi; struct w5500 dev_w5500; usb_keyboard_rpc_t keyboard_chan; uint16_t usb_serial[sizeof(uint64_t)*2]; /* UTF-16 */ + struct lib9p_srv srv; } globals; -const char *hexdig = "0123456789ABCDEF"; +static COROUTINE dhcp_cr(void *) { + cr_begin(); + + dhcp_client_main(lo_box_w5500_if_as_net_iface(&globals.dev_w5500), "harness"); + + cr_end(); +} + +static COROUTINE read9p_cr(void *) { + cr_begin(); + + 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_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_MAX_REQS <= 16); COROUTINE init_cr(void *) { cr_begin(); @@ -76,12 +195,16 @@ COROUTINE init_cr(void *) { rp2040_hwspi_init(&globals.dev_spi, "W5500", RP2040_HWSPI_0, SPI_MODE_0, /* the W5500 supports mode 0 or mode 3 */ - 60*1000*1000, /* as close to the W5500's max rate of 80MHz as we can without hwspi borking */ - 16, /* PIN_MISO */ - 19, /* PIN_MOSI */ - 18, /* PIN_CLK */ - 17); /* PIN_CS */ - w5500_init(&globals.dev_w5500, "W5500", &globals.dev_spi, + 42500000, /* min(w5500, hwspi); w5500=80MHz; hwspi=42.5MHz, see rp2040_hwspi.h for a comment about why this is so low */ + 30, /* W5500 datasheet says min(T_CS = SCSn High Time) = 30ns */ + 0, /* bogus write write data when doing a read */ + 16, /* PIN_MISO */ + 19, /* PIN_MOSI */ + 18, /* PIN_CLK */ + 17, /* PIN_CS */ + 0, 1, 2, 3); /* DMA channels */ + w5500_init(&globals.dev_w5500, "W5500", + lo_box_rp2040_hwspi_as_spi(&globals.dev_spi), 21, /* PIN_INTR */ 20, /* PIN_RESET */ ((struct net_eth_addr){{ @@ -91,8 +214,8 @@ COROUTINE init_cr(void *) { flash_id24[0], flash_id24[1], flash_id24[2], }})); - static_assert(sizeof(flash_id64)*2 == ARRAY_LEN(globals.usb_serial)); - for (size_t i = 0; i < ARRAY_LEN(globals.usb_serial); i++) + static_assert(sizeof(flash_id64)*2 == LM_ARRAY_LEN(globals.usb_serial)); + for (size_t i = 0; i < LM_ARRAY_LEN(globals.usb_serial); i++) globals.usb_serial[i] = hexdig[(flash_id64 >> ((sizeof(flash_id64)*8)-((i+1)*4))) & 0xF]; usb_common_earlyinit(globals.usb_serial, sizeof(globals.usb_serial)); usb_keyboard_init(); @@ -100,19 +223,31 @@ COROUTINE init_cr(void *) { globals.keyboard_chan = (usb_keyboard_rpc_t){0}; + globals.srv.rootdir = get_root; + /* set up coroutines **************************************************/ coroutine_add("usb_common", usb_common_cr, NULL); coroutine_add("usb_keyboard", usb_keyboard_cr, &globals.keyboard_chan); - //coroutine_add("hello_world", hello_world_cr, &keyboard_chan); - coroutine_add_with_stack_size(4*1024, "dhcp", dhcp_cr, &globals.dev_w5500); + //coroutine_add("hello_world", hello_world_cr, &globals.keyboard_chan); + coroutine_add("dhcp", dhcp_cr, NULL); + 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_MAX_REQS; i++) { + char name[] = {'w', 'r', 'i', 't', 'e', '-', hexdig[i], '\0'}; + coroutine_add(name, write9p_cr, NULL); + } cr_exit(); } int main() { + bootclock = rp2040_hwtimer(0); stdio_uart_init(); /* char *hdr = "=" * (80-strlen("info : 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 new file mode 100644 index 0000000..b3fc12e --- /dev/null +++ b/cmd/sbc_harness/static/Documentation/YOUR_RIGHTS_AND_OBLIGATIONS.md @@ -0,0 +1,29 @@ +<!-- + Documentation/YOUR_RIGHTS_AND_OBLIGATIONS.md - Overview of your + rights and obligations with regard to the SBC-Harness firmware + + Copyright (C) 2025 Luke T. Shumaker <lukeshu@lukeshu.com> + SPDX-License-Identifier: AGPL-3.0-or-later +--> + +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 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 +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 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 verbatim-or-modified copies of the + firmware. diff --git a/cmd/sbc_harness/static/Documentation/YOUR_RIGHTS_AND_OBLIGATIONS/agpl-3.0.txt b/cmd/sbc_harness/static/Documentation/YOUR_RIGHTS_AND_OBLIGATIONS/agpl-3.0.txt new file mode 120000 index 0000000..8a9b6f3 --- /dev/null +++ b/cmd/sbc_harness/static/Documentation/YOUR_RIGHTS_AND_OBLIGATIONS/agpl-3.0.txt @@ -0,0 +1 @@ +../../../../../COPYING.txt
\ No newline at end of file diff --git a/cmd/sbc_harness/static/Documentation/YOUR_RIGHTS_AND_OBLIGATIONS/newlib.txt b/cmd/sbc_harness/static/Documentation/YOUR_RIGHTS_AND_OBLIGATIONS/newlib.txt new file mode 120000 index 0000000..5c5939c --- /dev/null +++ b/cmd/sbc_harness/static/Documentation/YOUR_RIGHTS_AND_OBLIGATIONS/newlib.txt @@ -0,0 +1 @@ +../../../../../3rd-party/COPYING.newlib.txt
\ No newline at end of file diff --git a/cmd/sbc_harness/static/Documentation/YOUR_RIGHTS_AND_OBLIGATIONS/pico-sdk.bsd3.txt b/cmd/sbc_harness/static/Documentation/YOUR_RIGHTS_AND_OBLIGATIONS/pico-sdk.bsd3.txt new file mode 120000 index 0000000..52c4374 --- /dev/null +++ b/cmd/sbc_harness/static/Documentation/YOUR_RIGHTS_AND_OBLIGATIONS/pico-sdk.bsd3.txt @@ -0,0 +1 @@ +../../../../../3rd-party/pico-sdk/LICENSE.TXT
\ No newline at end of file diff --git a/cmd/sbc_harness/static/Documentation/YOUR_RIGHTS_AND_OBLIGATIONS/printf.mit.txt b/cmd/sbc_harness/static/Documentation/YOUR_RIGHTS_AND_OBLIGATIONS/printf.mit.txt new file mode 120000 index 0000000..5a6e342 --- /dev/null +++ b/cmd/sbc_harness/static/Documentation/YOUR_RIGHTS_AND_OBLIGATIONS/printf.mit.txt @@ -0,0 +1 @@ +../../../../../3rd-party/pico-sdk/src/rp2_common/pico_printf/LICENSE
\ No newline at end of file diff --git a/cmd/sbc_harness/static/Documentation/YOUR_RIGHTS_AND_OBLIGATIONS/tinyusb.mit.txt b/cmd/sbc_harness/static/Documentation/YOUR_RIGHTS_AND_OBLIGATIONS/tinyusb.mit.txt new file mode 120000 index 0000000..22a67cf --- /dev/null +++ b/cmd/sbc_harness/static/Documentation/YOUR_RIGHTS_AND_OBLIGATIONS/tinyusb.mit.txt @@ -0,0 +1 @@ +../../../../../3rd-party/pico-sdk/lib/tinyusb/LICENSE
\ No newline at end of file diff --git a/cmd/sbc_harness/static/Documentation/harness_flash_bin.txt b/cmd/sbc_harness/static/Documentation/harness_flash_bin.txt new file mode 100644 index 0000000..115f2ee --- /dev/null +++ b/cmd/sbc_harness/static/Documentation/harness_flash_bin.txt @@ -0,0 +1,33 @@ +NAME + /harness/flash.bin + +DESCRIPTION + Access to the flash storage chip (where the harness firmware + is stored). + + 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 + than it was compiled for, then the upper part of the chip + will not be accessible with this file. If the firmware is + loaded onto hardware with a smaller flash chip than it was + compiled for, then accessing the missing upper part of the + chip will crash. + + - 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> + SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/cmd/sbc_harness/static/Documentation/harness_rom_bin.txt b/cmd/sbc_harness/static/Documentation/harness_rom_bin.txt new file mode 100644 index 0000000..63fd0a3 --- /dev/null +++ b/cmd/sbc_harness/static/Documentation/harness_rom_bin.txt @@ -0,0 +1,41 @@ +NAME + /harness/rom.bin + +DESCRIPTION + Read access to the RP2040 CPU's ROM. This contains code that + initializes the chip to load the main firmware from the + external flash chip, provides a failsafe USB-programmable + mode, and provides a few functions that the main firmware can + call to. + +BUGS + This ROM is programmed into the chip at the factory; revising + it means issuing a new revison of the RP2040 CPU. So while + the source code to the ROM is freely available to be used, + studied, and shared; one cannot install modified versions onto + the CPU. + +HISTORY + - RP2040 B0 : chips manufactured before September 2020 or so + - RP2040 B1 : chips manufactured after September 2020 or so + - Released to the public January 2021; chance whether you get + a B0 or a B1 chip. + - RP2040 B2 : released September 2021 + + Printed on the physical CPU is a label that indicates which + revision it is. For example: + + RP2-B2 21/24 + + indicates that it is the "B2" revision (and was manufactured + the 21st week (late May) of 2024). + +SEE ALSO + - /harness/cpuinfo.txt can report which CPU version you have. + + - The source code to each ROM revision is published at + https://github.com/raspberrypi/pico-bootrom-rp2040 + +AUTHOR + Copyright (C) 2025 Luke T. Shumaker <lukeshu@lukeshu.com> + SPDX-License-Identifier: AGPL-3.0-or-later 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 f4816c1..7dd8a24 100644 --- a/cmd/sbc_harness/usb_keyboard.c +++ b/cmd/sbc_harness/usb_keyboard.c @@ -1,6 +1,6 @@ /* sbc_harness/usb_keyboard.c - 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 */ @@ -8,11 +8,10 @@ #include <libusb/tusb_helpers.h> /* for TUD_ENDPOINT_IN */ #include <libusb/usb_common.h> +#include <libmisc/macro.h> #include "usb_keyboard.h" -#define UNUSED(name) - /** * A USB-HID "Report Descriptor" (see USB-HID 1.11 ยง6.2.2 "Report * Descriptor") describing a keyboard. @@ -55,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; @@ -70,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; @@ -96,7 +95,7 @@ uint8_t const *tud_hid_descriptor_report_cb(uint8_t index) { uint16_t tud_hid_get_report_cb(uint8_t instance, uint8_t report_id, hid_report_type_t report_type, uint8_t* buffer, uint16_t reqlen) { - // TODO not Implemented + // TODO not implemented (void) instance; (void) report_id; (void) report_type; @@ -108,7 +107,11 @@ uint16_t tud_hid_get_report_cb(uint8_t instance, uint8_t report_id, hid_report_t // Invoked when received SET_REPORT control request or // received data on OUT endpoint ( Report ID = 0, Type = 0 ) -void tud_hid_set_report_cb(uint8_t UNUSED(instance), uint8_t UNUSED(report_id), hid_report_type_t UNUSED(report_type), uint8_t const *UNUSED(buffer), uint16_t UNUSED(bufsize)) +void tud_hid_set_report_cb(uint8_t LM_UNUSED(instance), + uint8_t LM_UNUSED(report_id), + hid_report_type_t LM_UNUSED(report_type), + uint8_t const *LM_UNUSED(buffer), + uint16_t LM_UNUSED(bufsize)) { // TODO not implemented } 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); diff --git a/cmd/srv9p/CMakeLists.txt b/cmd/srv9p/CMakeLists.txt deleted file mode 100644 index b747882..0000000 --- a/cmd/srv9p/CMakeLists.txt +++ /dev/null @@ -1,25 +0,0 @@ -# cmd/srv9p/CMakeLists.txt - Build script for srv9p test/dev executable -# -# Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com> -# SPDX-License-Identifier: AGPL-3.0-or-later - -if (PICO_PLATFORM STREQUAL "host") - -add_executable(srv9p - main.c - static9p.c -) -target_embed_sources(srv9p static.h - static/README.md - static/Documentation/x -) -target_include_directories(srv9p PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) -target_include_directories(srv9p PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/config) -target_link_libraries(srv9p - libcr - libcr_ipc - libmisc - lib9p -) - -endif() diff --git a/cmd/srv9p/config/config.h b/cmd/srv9p/config/config.h deleted file mode 100644 index 4f8384e..0000000 --- a/cmd/srv9p/config/config.h +++ /dev/null @@ -1,60 +0,0 @@ -/* config.h - Compile-time configuration for srv9p - * - * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com> - * SPDX-License-Identifier: AGPL-3.0-or-later - */ - -#ifndef _CONFIG_H_ -#define _CONFIG_H_ - -#define CONFIG_SRV9P_NUM_CONNS 8 - -/* 9P *************************************************************************/ - -#define CONFIG_9P_PORT 564 -/** - * This max-msg-size is sized so that a Twrite message can return - * 8KiB of data. - * - * This is the same as the default in Plan 9 4e's lib9p; it has the - * comment that "24" is "ample room for Twrite/Rread header - * (iounit)". In fact, the Twrite header is only 23 bytes - * ("size[4] Twrite[1] tag[2] fid[4] offset[8] count[4]") and the - * Rread header is even shorter at 11 bytes ("size[4] Rread[1] - * tag[2] count[4]"), so "24" appears to be the size of the Twrite - * header rounded up to a nice round number. - * - * In older versions of 9P ("9P1"), the max message size was - * defined as part of the protocol specification rather than - * negotiated. In Plan 9 1e it was (8*1024)+128, and was bumped to - * (8*1024)+160 in 2e and 3e. - */ -#define CONFIG_9P_MAX_MSG_SIZE ((4*1024)+24) -/** - * Maximum host-data-structure size. A message may be larger in - * unmarshaled-host-structures than marshaled-net-bytes due to (1) - * struct padding, (2) nul-terminator byes for strings. - */ -#define CONFIG_9P_MAX_HOSTMSG_SIZE CONFIG_9P_MAX_MSG_SIZE+16 -#define CONFIG_9P_MAX_FIDS 16 -#define CONFIG_9P_MAX_REQS 2 -#define CONFIG_9P_MAX_ERR_SIZE 128 /* 128 is what Plan 9 4e uses */ -#define CONFIG_9P_ENABLE_9P2000 1 /* bool */ -#define CONFIG_9P_ENABLE_9P2000_u 1 /* bool */ -#define CONFIG_9P_ENABLE_9P2000_e 0 /* bool */ - -/* COROUTINE ******************************************************************/ - -#define CONFIG_COROUTINE_DEFAULT_STACK_SIZE (32*1024) -#define CONFIG_COROUTINE_NAME_LEN 16 -#define CONFIG_COROUTINE_MEASURE_STACK 1 /* bool */ -#define CONFIG_COROUTINE_PROTECT_STACK 1 /* bool */ -#define CONFIG_COROUTINE_DEBUG 0 /* bool */ -#define CONFIG_COROUTINE_VALGRIND 1 /* bool */ -#define CONFIG_COROUTINE_GDB 1 /* bool */ -#define CONFIG_COROUTINE_NUM (1 /* usb_common */ +\ - 1 /* usb_keyboard */ +\ - CONFIG_SRV9P_NUM_CONNS /* accept+read */ +\ - (CONFIG_9P_MAX_REQS*CONFIG_SRV9P_NUM_CONNS) /* work+write */ ) - -#endif /* _CONFIG_H_ */ diff --git a/cmd/srv9p/main.c b/cmd/srv9p/main.c deleted file mode 100644 index 05368e5..0000000 --- a/cmd/srv9p/main.c +++ /dev/null @@ -1,130 +0,0 @@ -/* srv9p/main.c - Main entry point for test 9P server - * - * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com> - * SPDX-License-Identifier: AGPL-3.0-or-later - */ - -#include <error.h> - -#include <lib9p/srv.h> -#include <libcr/coroutine.h> -#include <libhw/generic/net.h> -#include <libhw/generic/alarmclock.h> -#include <libhw/host_net.h> - -#include "static9p.h" -#include "static.h" - -/* configuration **************************************************************/ - -#include "config.h" - -#ifndef CONFIG_SRV9P_NUM_CONNS - #error config.h must define CONFIG_SRV9P_NUM_CONNS -#endif - -/* implementation *************************************************************/ - -#define UNUSED(name) - -/* file tree ******************************************************************/ - -#define FILE_COMMON(NAME) { \ - .vtable = &static_file_vtable, \ - \ - .u_name = "root", .u_num = 0, /* owner user */ \ - .g_name = "root", .g_num = 0, /* owner group */ \ - .m_name = "root", .m_num = 0, /* last-modified-by user */ \ - \ - .pathnum = __COUNTER__, \ - .name = NAME, \ - .perm = 0444, \ - .atime = 1728337905, \ - .mtime = 1728337904, \ - } - -#define DIR_COMMON(NAME) { \ - .vtable = &static_dir_vtable, \ - \ - .u_name = "root", .u_num = 0, /* owner user */ \ - .g_name = "root", .g_num = 0, /* owner group */ \ - .m_name = "root", .m_num = 0, /* last-modified-by user */ \ - \ - .pathnum = __COUNTER__, \ - .name = NAME, \ - .perm = 0555, \ - .atime = 1728337905, \ - .mtime = 1728337904, \ - } - -#define STATIC_FILE(STRNAME, SYMNAME) \ - &((static struct static_file){ \ - ._static_common = FILE_COMMON(STRNAME), \ - .data_start = _binary_static_##SYMNAME##_start, \ - .data_end = _binary_static_##SYMNAME##_end, \ - }) - -static struct static_dir root = { - ._static_common = DIR_COMMON(""), - .members = { - &((static struct static_dir){ - ._static_common = DIR_COMMON("Documentation"), - .members = { - STATIC_FILE("x", Documentation_x), - NULL - }, - }), - STATIC_FILE("README.md", README_md), - NULL, - }, -}; - -static implements_lib9p_srv_file *get_root(struct lib9p_srv_ctx *UNUSED(ctx), char *UNUSED(treename)) { - return &root; -} - -/* main ***********************************************************************/ - -static COROUTINE read_cr(void *_srv) { - struct lib9p_srv *srv = _srv; - assert(srv); - - cr_begin(); - - struct hostnet_tcp_listener listener; - hostnet_tcp_listener_init(&listener, 9000); - - lib9p_srv_read_cr(srv, &listener); - - cr_end(); -} - -const char *hexdig = "0123456789abcdef"; - -struct lib9p_srv srv = { - .rootdir = get_root, -}; - -static COROUTINE init_cr(void *) { - cr_begin(); - - for (int i = 0; i < CONFIG_SRV9P_NUM_CONNS; i++) { - char name[] = {'r', 'e', 'a', 'd', '-', hexdig[i], '\0'}; - if (!coroutine_add(name, read_cr, &srv)) - error(1, 0, "coroutine_add(read_cr, &srv)"); - sleep_for_s(1); - } - for (int i = 0; i < 2*CONFIG_SRV9P_NUM_CONNS; i++) { - char name[] = {'w', 'r', 'i', 't', 'e', '-', hexdig[i], '\0'}; - if (!coroutine_add(name, lib9p_srv_write_cr, &srv)) - error(1, 0, "coroutine_add(lib9p_srv_write_cr, &srv)"); - sleep_for_s(1); - } - - cr_exit(); -} - -int main() { - coroutine_add("init", init_cr, NULL); - coroutine_main(); -} diff --git a/cmd/srv9p/static/Documentation/x b/cmd/srv9p/static/Documentation/x deleted file mode 100644 index 257cc56..0000000 --- a/cmd/srv9p/static/Documentation/x +++ /dev/null @@ -1 +0,0 @@ -foo diff --git a/cmd/srv9p/static/README.md b/cmd/srv9p/static/README.md deleted file mode 100644 index af5626b..0000000 --- a/cmd/srv9p/static/README.md +++ /dev/null @@ -1 +0,0 @@ -Hello, world! diff --git a/cmd/srv9p/static9p.c b/cmd/srv9p/static9p.c deleted file mode 100644 index b5b18e0..0000000 --- a/cmd/srv9p/static9p.c +++ /dev/null @@ -1,241 +0,0 @@ -/* srv9p/static9p.c - Serve static files over 9P - * - * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com> - * SPDX-License-Identifier: AGPL-3.0-or-later - */ - -#include <libmisc/assert.h> -#include <libmisc/vcall.h> - -#include "static9p.h" - -#define UNUSED(name) -#define p9_str(cstr) ((struct lib9p_s){ .len = strlen(cstr), .utf8 = cstr }) -#define p9_nulstr ((struct lib9p_s){ .len = 0, .utf8 = NULL }) - -/* common *********************************************************************/ - -static implements_lib9p_srv_file *static_common_clone(implements_lib9p_srv_file *_self, struct lib9p_srv_ctx *ctx) { - _static_common *self = VCALL_SELF(_static_common, implements_lib9p_srv_file, _self); - assert(self); - assert(ctx); - - return self; -} - -static void static_common_free(implements_lib9p_srv_file *_self, struct lib9p_srv_ctx *ctx) { - _static_common *self = VCALL_SELF(_static_common, implements_lib9p_srv_file, _self); - assert(self); - assert(ctx); - - /* do nothing */ -} - -static uint32_t static_common_io(implements_lib9p_srv_file *_self, struct lib9p_srv_ctx *ctx, lib9p_o_t UNUSED(flags)) { - _static_common *self = VCALL_SELF(_static_common, implements_lib9p_srv_file, _self); - assert(self); - assert(ctx); - - return 0; -} - -static void static_common_wstat(implements_lib9p_srv_file *_self, struct lib9p_srv_ctx *ctx, - struct lib9p_stat UNUSED(new)) { - _static_common *self = VCALL_SELF(_static_common, implements_lib9p_srv_file, _self); - assert(self); - assert(ctx); - - lib9p_error(&ctx->basectx, LINUX_EROFS, "read-only part of filesystem"); -} - -static void static_common_remove(implements_lib9p_srv_file *_self, struct lib9p_srv_ctx *ctx) { - _static_common *self = VCALL_SELF(_static_common, implements_lib9p_srv_file, _self); - assert(self); - assert(ctx); - - lib9p_error(&ctx->basectx, LINUX_EROFS, "read-only part of filesystem"); -} - -/* dir ************************************************************************/ - -static struct lib9p_stat static_dir_stat(implements_lib9p_srv_file *_self, struct lib9p_srv_ctx *ctx) { - struct static_dir *self = VCALL_SELF(struct static_dir, implements_lib9p_srv_file, _self); - assert(self); - assert(ctx); - - return (struct lib9p_stat){ - .kern_type = 0, - .kern_dev = 0, - .file_qid = { - .type = LIB9P_QT_DIR, - .vers = 1, - .path = self->pathnum, - }, - .file_mode = LIB9P_DM_DIR | (self->perm & 0555), - .file_atime = self->atime, - .file_mtime = self->mtime, - .file_size = 0, - .file_name = p9_str(self->name), - .file_owner_uid = p9_str(self->u_name), - .file_owner_gid = p9_str(self->g_name), - .file_last_modified_uid = p9_str(self->m_name), - .file_extension = p9_nulstr, - .file_owner_n_uid = self->u_num, - .file_owner_n_gid = self->g_num, - .file_last_modified_n_uid = self->m_num, - }; -} - -static implements_lib9p_srv_file *static_dir_dopen(implements_lib9p_srv_file *_self, struct lib9p_srv_ctx *ctx, - char *childname) { - struct static_dir *self = VCALL_SELF(struct static_dir, implements_lib9p_srv_file, _self); - assert(self); - assert(ctx); - - for (size_t i = 0; self->members[i]; i++) { - implements_lib9p_srv_file *filep = self->members[i]; - struct lib9p_stat stat = VCALL(filep, stat, ctx); - if (lib9p_ctx_has_error(&ctx->basectx)) - break; - lib9p_assert_stat(stat); - if (strcmp(stat.file_name.utf8, childname) == 0) - return filep; - } - lib9p_error(&ctx->basectx, - LINUX_ENOENT, "no such file or directory"); - return NULL; -} - -static implements_lib9p_srv_file *static_dir_dcreate(implements_lib9p_srv_file *_self, struct lib9p_srv_ctx *ctx, - char *UNUSED(childname), - lib9p_dm_t UNUSED(perm), lib9p_o_t UNUSED(flags)) { - struct static_dir *self = VCALL_SELF(struct static_dir, implements_lib9p_srv_file, _self); - assert(self); - assert(ctx); - - lib9p_error(&ctx->basectx, LINUX_EROFS, "read-only part of filesystem"); - return NULL; -} - -static size_t static_dir_dread(implements_lib9p_srv_file *_self, struct lib9p_srv_ctx *ctx, - uint8_t *buf, - uint32_t byte_count, - size_t _obj_offset) { - struct static_dir *self = VCALL_SELF(struct static_dir, implements_lib9p_srv_file, _self); - assert(self); - assert(ctx); - - uint32_t byte_offset = 0; - size_t obj_offset = _obj_offset; - while (self->members[obj_offset]) { - implements_lib9p_srv_file *filep = self->members[obj_offset]; - struct lib9p_stat stat = VCALL(filep, stat, ctx); - if (lib9p_ctx_has_error(&ctx->basectx)) - break; - lib9p_assert_stat(stat); - uint32_t nbytes = lib9p_marshal_stat(&ctx->basectx, byte_count-byte_offset, &stat, - &buf[byte_offset]); - if (!nbytes) { - if (obj_offset == _obj_offset) - lib9p_error(&ctx->basectx, - LINUX_ERANGE, "stat object does not fit into negotiated max message size"); - break; - } - byte_offset += nbytes; - obj_offset++; - } - return obj_offset - _obj_offset; -} - -struct lib9p_srv_file_vtable static_dir_vtable = { - .clone = static_common_clone, - .free = static_common_free, - - .io = static_common_io, - .stat = static_dir_stat, - .wstat = static_common_wstat, - .remove = static_common_remove, - - .dopen = static_dir_dopen, - .dcreate = static_dir_dcreate, - - .dread = static_dir_dread, -}; - -/* file ***********************************************************************/ - -static inline size_t static_file_size(struct static_file *file) { - assert(file); - -#if __unix__ - assert(file->data_start); -#endif - if (!file->data_end) - return file->data_size; - return (size_t)((uintptr_t)file->data_end - (uintptr_t)file->data_start); -} - -static struct lib9p_stat static_file_stat(implements_lib9p_srv_file *_self, struct lib9p_srv_ctx *ctx) { - struct static_file *self = VCALL_SELF(struct static_file, implements_lib9p_srv_file, _self); - assert(self); - assert(ctx); - - return (struct lib9p_stat){ - .kern_type = 0, - .kern_dev = 0, - .file_qid = { - .type = LIB9P_QT_FILE, - .vers = 1, - .path = self->pathnum, - }, - .file_mode = self->perm & 0444, - .file_atime = self->atime, - .file_mtime = self->mtime, - .file_size = (uint64_t)static_file_size(self), - .file_name = p9_str(self->name), - .file_owner_uid = p9_str(self->u_name), - .file_owner_gid = p9_str(self->g_name), - .file_last_modified_uid = p9_str(self->m_name), - .file_extension = p9_nulstr, - .file_owner_n_uid = self->u_num, - .file_owner_n_gid = self->g_num, - .file_last_modified_n_uid = self->m_num, - }; -} - -static uint32_t static_file_pread(implements_lib9p_srv_file *_self, struct lib9p_srv_ctx *ctx, - void *buf, - uint32_t byte_count, - uint64_t byte_offset) { - struct static_file *self = VCALL_SELF(struct static_file, implements_lib9p_srv_file, _self); - assert(self); - assert(ctx); - - size_t data_size = static_file_size(self); - - if (byte_offset > (uint64_t)data_size) { - lib9p_error(&ctx->basectx, - LINUX_EINVAL, "offset is past end-of-file length"); - return 0; - } - - size_t beg_off = (size_t)byte_offset; - size_t end_off = beg_off + (size_t)byte_count; - if (end_off > data_size) - end_off = data_size; - memcpy(buf, &self->data_start[beg_off], end_off-beg_off); - return (uint32_t)(end_off-beg_off); -} - -struct lib9p_srv_file_vtable static_file_vtable = { - .clone = static_common_clone, - .free = static_common_free, - - .io = static_common_io, - .stat = static_file_stat, - .wstat = static_common_wstat, - .remove = static_common_remove, - - .pread = static_file_pread, - .pwrite = NULL, -}; diff --git a/cmd/srv9p/static9p.h b/cmd/srv9p/static9p.h deleted file mode 100644 index 7a3d476..0000000 --- a/cmd/srv9p/static9p.h +++ /dev/null @@ -1,47 +0,0 @@ -/* srv9p/static9p.h - Serve static files over 9P - * - * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com> - * SPDX-License-Identifier: AGPL-3.0-or-later - */ - -#ifndef _SRV9P_STATIC9P_H_ -#define _SRV9P_STATIC9P_H_ - -#include <lib9p/srv.h> - -typedef struct { - implements_lib9p_srv_file; - - char *u_name; - uint32_t u_num; - char *g_name; - uint32_t g_num; - char *m_name; - uint32_t m_num; - - uint64_t pathnum; - char *name; - lib9p_dm_t perm; - uint32_t atime, mtime; -} _static_common; - -struct static_dir { - _static_common; - - /* NULL-terminated */ - implements_lib9p_srv_file *members[]; -}; - - -struct static_file { - _static_common; - - char *data_start; /* must not be NULL */ - char *data_end; /* may be NULL, in which case data_size is used */ - size_t data_size; /* only used if .data_end==NULL */ -}; - -extern struct lib9p_srv_file_vtable static_dir_vtable; -extern struct lib9p_srv_file_vtable static_file_vtable; - -#endif /* _SRV9P_STATIC9P_H_ */ |