summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md2
-rwxr-xr-xbuild-aux/lint-src14
-rw-r--r--build-aux/measurestack/app_plugins.py5
-rw-r--r--cmd/sbc_harness/CMakeLists.txt15
-rw-r--r--cmd/sbc_harness/fs_harness_flash_bin.c282
-rw-r--r--cmd/sbc_harness/fs_harness_flash_bin.h17
-rw-r--r--cmd/sbc_harness/fs_harness_uptime_txt.c65
-rw-r--r--cmd/sbc_harness/ihex.c231
-rw-r--r--cmd/sbc_harness/ihex.h49
-rw-r--r--cmd/sbc_harness/static/Documentation/harness_flash_bin.txt24
-rw-r--r--cmd/sbc_harness/static/Documentation/harness_uptime_txt.txt22
-rw-r--r--cmd/sbc_harness/tests/test_ihex.c128
-rw-r--r--lib9p/srv.c284
-rw-r--r--lib9p/srv_include/lib9p/srv.h42
-rw-r--r--lib9p/tests/test_server/fs_flush.c34
-rw-r--r--lib9p/tests/test_server/fs_shutdown.c25
-rw-r--r--lib9p/tests/test_server/fs_whoami.c32
-rw-r--r--lib9p/tests/test_server/main.c3
-rw-r--r--lib9p_util/static.c80
-rw-r--r--libdhcp/tests/test_client.c9
-rw-r--r--libhw_cr/host_alarmclock.c18
-rw-r--r--libhw_cr/host_include/libhw/host_net.h9
-rw-r--r--libhw_cr/host_net.c36
-rw-r--r--libhw_cr/rp2040_hwspi.c6
-rw-r--r--libhw_cr/rp2040_hwtimer.c3
-rw-r--r--libhw_cr/rp2040_include/libhw/w5500.h13
-rw-r--r--libhw_cr/w5500.c36
-rw-r--r--libhw_generic/include/libhw/generic/io.h134
-rw-r--r--libmisc/error.c9
-rw-r--r--libmisc/fmt.c6
-rw-r--r--libmisc/include/libmisc/alloc.h7
-rw-r--r--libmisc/include/libmisc/error.h1
-rw-r--r--libmisc/include/libmisc/obj.h97
-rw-r--r--libmisc/log.c3
-rw-r--r--libmisc/tests/test_obj.c8
-rw-r--r--libmisc/tests/test_obj_autobox.c14
-rw-r--r--libmisc/tests/test_obj_nest.c14
37 files changed, 1227 insertions, 550 deletions
diff --git a/README.md b/README.md
index 4ad8158..10c0270 100644
--- a/README.md
+++ b/README.md
@@ -56,7 +56,7 @@ There are several ways of putting this firmware file onto the harness:
If OpenOCD is not already running:
```
- openocd -f interface/cmsis-dap.cfg -f target/rp2040.cfg -c "program $PWD/build/rp2040-Debug/cmd/sbc_harness/sbc_harness.elf reset exit"`
+ openocd -f interface/cmsis-dap.cfg -f target/rp2040.cfg -c 'adapter speed 5000' -c "program $PWD/build/rp2040-Debug/cmd/sbc_harness/sbc_harness.elf reset exit"
```
If OpenOCD is already running:
diff --git a/build-aux/lint-src b/build-aux/lint-src
index 033340d..69b594a 100755
--- a/build-aux/lint-src
+++ b/build-aux/lint-src
@@ -13,7 +13,7 @@ err() {
}
get-dscname() {
- if [[ $1 == */Documentation/* ]] && [[ "$(sed 1q -- "$1")" == 'NAME' ]]; then
+ if [[ $1 == */Documentation/* && "$(sed 1q -- "$1")" == 'NAME' ]]; then
sed -n '
2{
s,[/.],_,g;
@@ -90,7 +90,7 @@ get-dscname() {
# File body ############################################################
- if grep -n --color=auto "$(printf '\\S\t')" "$filename"; then
+ if grep -n --color=auto $'\\S\t' "$filename"; then
err "$filename" 'uses tabs for alignment'
fi
done
@@ -112,6 +112,16 @@ get-dscname() {
grep -Fxq "#endif /* ${guard} */" "$filename"; }; then
err "$filename" "does not have ${guard} guard"
fi
+ if [[ $filename != libmisc/include/libmisc/obj.h ]] &&
+ grep -Fn --color=auto -e LO_IMPLEMENTATION_C -e LO_IMPLEMENTATION_STATIC "$filename"; then
+ err "$filename" "contains LO_IMPLEMENTATION_C and/or LO_IMPLEMENTATION_STATIC"
+ fi
+ fi
+ if [[ $filename == *.c ]]; then
+ if [[ $filename != libmisc/tests/test_obj.c ]] &&
+ grep -Fn --color=auto L_IMPLEMENTATION_H "$filename"; then
+ err "$filename" "contains LO_IMPLEMENTATION_H"
+ fi
fi
done
;;
diff --git a/build-aux/measurestack/app_plugins.py b/build-aux/measurestack/app_plugins.py
index 6fc81ec..c5407be 100644
--- a/build-aux/measurestack/app_plugins.py
+++ b/build-aux/measurestack/app_plugins.py
@@ -60,7 +60,10 @@ class LibMiscPlugin:
re_lo_iface = re.compile(r"^\s*#\s*define\s+(?P<name>\S+)_LO_IFACE")
re_lo_func = re.compile(r"LO_FUNC *\([^,]*, *(?P<name>[^,) ]+) *[,)]")
re_lo_implementation = re.compile(
- r"^LO_IMPLEMENTATION_[HC]\s*\(\s*(?P<iface>[^, ]+)\s*,\s*(?P<impl_typ>[^,]+)\s*,\s*(?P<impl_name>[^, ]+)\s*[,)].*"
+ r"^LO_IMPLEMENTATION_(?P<vis>H|C|STATIC)\s*\("
+ r"\s*(?P<iface>[^, ]+)\s*,"
+ r"\s*(?P<impl_typ>[^,]+)\s*,"
+ r"\s*(?P<impl_name>[^, ]+)\s\)"
)
re_call_objcall = re.compile(r"LO_CALL\((?P<obj>[^,]+), (?P<meth>[^,)]+)[,)].*")
diff --git a/cmd/sbc_harness/CMakeLists.txt b/cmd/sbc_harness/CMakeLists.txt
index 0e904ab..7656ab6 100644
--- a/cmd/sbc_harness/CMakeLists.txt
+++ b/cmd/sbc_harness/CMakeLists.txt
@@ -14,6 +14,8 @@ add_library(sbc_harness_objs OBJECT
fs_harness_flash_bin.c
fs_harness_uptime_txt.c
+
+ ihex.c
)
target_include_directories(sbc_harness_objs PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/config)
target_include_directories(sbc_harness_objs PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
@@ -79,3 +81,16 @@ target_embed_sources(sbc_harness_objs sbc_harness static.h
)
endif()
+
+# Tests ########################################################################
+if ((PICO_PLATFORM STREQUAL "host") AND (ENABLE_TESTS))
+ add_executable(test_ihex "tests/test_ihex.c" "ihex.c")
+ target_include_directories(test_ihex PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/tests)
+ target_link_libraries(test_ihex
+ libhw_generic
+ )
+ add_test(
+ NAME "cmd/sbc_harness/test_ihex"
+ COMMAND "${CMAKE_SOURCE_DIR}/build-aux/valgrind" "./test_ihex"
+ )
+endif()
diff --git a/cmd/sbc_harness/fs_harness_flash_bin.c b/cmd/sbc_harness/fs_harness_flash_bin.c
index ea60447..510a247 100644
--- a/cmd/sbc_harness/fs_harness_flash_bin.c
+++ b/cmd/sbc_harness/fs_harness_flash_bin.c
@@ -17,10 +17,8 @@
#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);
+LO_IMPLEMENTATION_C(lib9p_srv_file, struct flash_file, flash_file);
+LO_IMPLEMENTATION_STATIC(lib9p_srv_fio, struct flash_file, flash_file);
#define DATA_START ((const char *)(XIP_NOALLOC_BASE))
#define DATA_SIZE PICO_FLASH_SIZE_BYTES
@@ -28,8 +26,8 @@ LO_IMPLEMENTATION_C(lib9p_srv_fio, struct flash_file, flash_file, static);
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. */
+/* There are some memcpy()s (and memcmp()s?) in here that maybe should
+ * be replaced with SSI DMA. */
/* ab_flash_* (mid-level utilities for our A/B write scheme) ******************/
@@ -62,50 +60,24 @@ static_assert(DATA_HSIZE % FLASH_SECTOR_SIZE == 0);
}
/**
- * Set the upper half of flash to all zero bytes.
+ * Set the upper half of flash to all "1" bits.
*
* @param buf : a scratch buffer that is at least FLASH_SECTOR_SIZE
*/
-static void ab_flash_initialize_zero(uint8_t *buf) {
+static void ab_flash_initialize(void *buf) {
assert(buf);
- memset(buf, 0, FLASH_SECTOR_SIZE);
+ memset(buf, 0xFF, FLASH_SECTOR_SIZE);
- log_infoln("zeroing upper flash...");
+ log_infoln("erasing 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);
- }
- log_debugln("... 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);
-
- log_infoln("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);
+ flash_range_erase(off, FLASH_SECTOR_SIZE);
cr_restore_interrupts(saved);
}
- log_debugln("... initialized");
+ log_debugln("... erased");
}
/**
@@ -113,7 +85,7 @@ static void ab_flash_initialize(uint8_t *buf) {
* 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 pos : start-position of the sector to write to
* @param dat : the FLASH_SECTOR_SIZE bytes to write
*/
static void ab_flash_write_sector(size_t pos, uint8_t *dat) {
@@ -123,7 +95,7 @@ static void ab_flash_write_sector(size_t pos, uint8_t *dat) {
pos += DATA_HSIZE;
- log_infoln("write flash sector @ %zu...", pos);
+ log_infoln("write flash sector @ ", (base16_u32_, 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);
@@ -133,28 +105,124 @@ static void ab_flash_write_sector(size_t pos, uint8_t *dat) {
log_debugln("... written");
}
+/* io_preader_to, io_pwriter, io_closer ***************************************/
+
+LO_IMPLEMENTATION_STATIC(io_preader_to, struct flashio, flashio);
+LO_IMPLEMENTATION_STATIC(io_pwriter, struct flashio, flashio);
+LO_IMPLEMENTATION_STATIC(io_closer, struct flashio, flashio);
+
+/** read from anywhere on the chip */
+static size_t_and_error flashio_pread_to(struct flashio *self, lo_interface io_writer dst, uoff_t _src_offset, size_t count) {
+ assert(self);
+
+ if (_src_offset > DATA_SIZE)
+ return ERROR_AND(size_t, 0, error_new(E_POSIX_EINVAL, "offset is past the chip size"));
+ size_t src_offset = (size_t) _src_offset;
+
+ if (src_offset == DATA_SIZE)
+ return ERROR_AND(size_t, 0, error_new(E_EOF));
+
+ /* Assume that somewhere down the line the pointer we pass to
+ * io_write() 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.
+ */
+ size_t sector_base = LM_ROUND_DOWN(src_offset, FLASH_SECTOR_SIZE);
+ if (src_offset + count > sector_base + FLASH_SECTOR_SIZE)
+ count = (sector_base + FLASH_SECTOR_SIZE) - src_offset;
+ assert(src_offset + 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);
+ }
+
+ return io_write(dst, &self->rbuf.dat[src_offset-sector_base], count);
+}
+
+/** takes offsets in the lower half, writes to the upper half */
+static size_t_and_error flashio_pwritev(struct flashio *self, const struct iovec *iov, int iovcnt, uoff_t _offset) {
+ assert(self);
+ assert(iov);
+ assert(iovcnt > 0);
+
+ size_t total_count = 0;
+ for (int i = 0; i < iovcnt; i++)
+ total_count += iov[i].iov_len;
+ assert(total_count > 0);
+
+ if (_offset >= DATA_HSIZE)
+ return ERROR_AND(size_t, 0, error_new(E_POSIX_ENOSPC, "cannot write past half the chip size"));
+ size_t offset = (size_t) _offset;
+
+ size_t total_done = 0;
+ for (int i = 0; i < iovcnt; i++) {
+ size_t iov_done = 0;
+ while (iov_done < iov[i].iov_len) {
+ if (offset >= DATA_HSIZE)
+ return ERROR_AND(size_t, total_done, error_new(E_POSIX_ENOSPC, "cannot write past half the chip size"));
+ size_t sector_base = LM_ROUND_DOWN(offset, FLASH_SECTOR_SIZE);
+ size_t len = iov[i].iov_len - iov_done;
+ if (offset + len > sector_base + FLASH_SECTOR_SIZE)
+ len = (sector_base + FLASH_SECTOR_SIZE) - offset;
+ assert(offset + len <= 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 (len != FLASH_SECTOR_SIZE)
+ /* Don't bother with a read if we're just going to overwrite it. */
+ memcpy(self->wbuf.dat, DATA_START+DATA_HSIZE+sector_base, FLASH_SECTOR_SIZE);
+ }
+ memcpy(&self->wbuf.dat[offset-sector_base], iov[i].iov_base+iov_done, len);
+ total_done += len;
+ iov_done += len;
+ offset += len;
+ }
+ }
+ return ERROR_AND(size_t, total_done, ERROR_NULL);
+}
+
+static error flashio_close(struct flashio *self) {
+ assert(self);
+
+ if (self->finalize) {
+ if (self->wbuf.ok)
+ ab_flash_write_sector(self->wbuf.pos, self->wbuf.dat);
+ ab_flash_finalize(self->wbuf.dat);
+ }
+
+ return ERROR_NULL;
+}
+
/* srv_file *******************************************************************/
-static void flash_file_free(struct flash_file *self) {
+void flash_file_free(struct flash_file *self) {
assert(self);
}
-static struct lib9p_qid flash_file_qid(struct flash_file *self) {
+struct lib9p_qid flash_file_qid(struct flash_file *self) {
assert(self);
return (struct lib9p_qid){
- .type = LIB9P_QT_FILE|LIB9P_QT_EXCL,
+ .type = LIB9P_QT_FILE|LIB9P_QT_EXCL|LIB9P_QT_APPEND,
.vers = 1,
.path = self->pathnum,
};
}
-static lib9p_srv_stat_or_error flash_file_stat(struct flash_file *self, struct lib9p_srv_ctx *ctx) {
+lib9p_srv_stat_or_error flash_file_stat(struct flash_file *self, struct lib9p_srv_ctx *ctx) {
assert(self);
assert(ctx);
return ERROR_NEW_VAL(lib9p_srv_stat, ((struct lib9p_srv_stat){
.qid = flash_file_qid(self),
- .mode = LIB9P_DM_EXCL|0666,
+ .mode = LIB9P_DM_EXCL|LIB9P_DM_APPEND|0666,
.atime_sec = UTIL9P_ATIME,
.mtime_sec = UTIL9P_MTIME,
.size = DATA_SIZE,
@@ -165,47 +233,47 @@ static lib9p_srv_stat_or_error flash_file_stat(struct flash_file *self, struct l
.extension = lib9p_str(NULL),
}));
}
-static error flash_file_wstat(struct flash_file *self, struct lib9p_srv_ctx *ctx,
- struct lib9p_srv_stat) {
+error flash_file_wstat(struct flash_file *self, struct lib9p_srv_ctx *ctx, struct lib9p_srv_stat) {
assert(self);
assert(ctx);
return error_new(E_POSIX_EROFS, "read-only part of filesystem");
}
-static error flash_file_remove(struct flash_file *self, struct lib9p_srv_ctx *ctx) {
+error flash_file_remove(struct flash_file *self, struct lib9p_srv_ctx *ctx) {
assert(self);
assert(ctx);
return error_new(E_POSIX_EROFS, "read-only part of filesystem");
}
-LIB9P_SRV_NOTDIR(struct flash_file, flash_file);
+LIB9P_SRV_NOTDIR(, struct flash_file, flash_file);
+
+static error flash_handle_ihex_data(void *, uint32_t off, uint8_t count, uint8_t *dat);
+static error flash_handle_ihex_eof(void *);
-static lib9p_srv_fio_or_error flash_file_fopen(struct flash_file *self, struct lib9p_srv_ctx *ctx,
- bool rd, bool wr, bool trunc) {
+lib9p_srv_fio_or_error flash_file_fopen(struct flash_file *self, struct lib9p_srv_ctx *ctx,
+ bool LM_UNUSED(rd), bool wr, bool LM_UNUSED(trunc)) {
assert(self);
assert(ctx);
- if (rd) {
- self->rbuf.ok = false;
- }
-
+ memset(&self->io, 0, sizeof(self->io));
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;
+ ab_flash_initialize(self->io.wbuf.dat);
}
+ memset(&self->ihex, 0, sizeof(self->ihex));
+ self->ihex.handle_arg = self;
+ self->ihex.handle_data = flash_handle_ihex_data;
+ self->ihex.handle_eof = flash_handle_ihex_eof;
+
return ERROR_NEW_VAL(lib9p_srv_fio, LO_BOX(lib9p_srv_fio, self));
}
/* srv_fio ********************************************************************/
+static struct lib9p_qid flash_file_ioqid(struct flash_file *self) {
+ return flash_file_qid(self);
+}
static uint32_t flash_file_iounit(struct flash_file *self) {
assert(self);
return FLASH_SECTOR_SIZE;
@@ -214,81 +282,49 @@ static uint32_t flash_file_iounit(struct flash_file *self) {
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);
+ error err = flashio_close(&self->io);
+ assert(ERROR_IS_NULL(err));
- if (self->written)
- ab_flash_finalize(self->wbuf.dat);
+ err = ihex_decoder_close(&self->ihex);
+ assert(ERROR_IS_NULL(err));
}
-static iovec_or_error flash_file_pread(struct flash_file *self, struct lib9p_srv_ctx *ctx,
- uint32_t byte_count, uint64_t byte_offset) {
+static error flash_file_pread(struct flash_file *self, struct lib9p_srv_ctx *ctx,
+ lo_interface io_writer dst, uint64_t byte_offset, uint32_t byte_count) {
assert(self);
assert(ctx);
- if (byte_offset > DATA_SIZE)
- return ERROR_NEW_ERR(iovec, error_new(E_POSIX_EINVAL, "offset is past the chip size"));
+ return flashio_pread_to(&self->io, dst, byte_offset, byte_count).err;
+}
- /* 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)
- return ERROR_NEW_VAL(iovec, ((struct iovec){
- .iov_len = 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_SIZE);
+static error flash_handle_ihex_data(void *_self, uint32_t off, uint8_t count, uint8_t *dat) {
+ struct flash_file *self = _self;
- 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);
- }
+ if (off < XIP_BASE || off >= XIP_BASE + DATA_HSIZE)
+ return error_new(E_POSIX_ENOSPC, "cannot write outside of the first half of the chip: ",
+ (base16_u32_, off), " is outside of [", (base16_u32_, XIP_BASE), ",", (base16_u32_, XIP_BASE + DATA_HSIZE), ")");
- return ERROR_NEW_VAL(iovec, ((struct iovec){
- .iov_base = &self->rbuf.dat[byte_offset-sector_base],
- .iov_len = byte_count,
- }));
+ return flashio_pwritev(&self->io,
+ &((struct iovec){.iov_base = dat, .iov_len = count}), 1,
+ off - XIP_BASE).err;
+}
+static error flash_handle_ihex_eof(void *_self) {
+ struct flash_file *self = _self;
+ self->io.finalize = true;
+ return ERROR_NULL;
}
-/* 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. */
+/* TODO: Also support uf2, not just ihex. */
static uint32_t_or_error flash_file_pwrite(struct flash_file *self, struct lib9p_srv_ctx *ctx,
void *buf,
uint32_t byte_count,
- uint64_t byte_offset) {
+ uint64_t LM_UNUSED(byte_offset)) {
assert(self);
assert(ctx);
- if (byte_offset > DATA_HSIZE)
- return ERROR_NEW_ERR(uint32_t, error_new(E_POSIX_EINVAL, "offset is past half the chip size"));
- if (byte_count == 0)
- return ERROR_NEW_VAL(uint32_t, 0);
- if (byte_offset == DATA_HSIZE)
- return ERROR_NEW_ERR(uint32_t, error_new(E_POSIX_EINVAL, "offset is at half the chip size"));
-
- 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 ERROR_NEW_VAL(uint32_t, byte_count);
+ size_t_and_error r = ihex_decoder_writev(&self->ihex,
+ &((struct iovec){.iov_base = buf, .iov_len = byte_count}), 1);
+ if (r.size_t == 0 && !ERROR_IS_NULL(r.err))
+ return ERROR_NEW_ERR(uint32_t, r.err);
+ return ERROR_NEW_VAL(uint32_t, (uint32_t) r.size_t);
}
diff --git a/cmd/sbc_harness/fs_harness_flash_bin.h b/cmd/sbc_harness/fs_harness_flash_bin.h
index 148a446..84cc494 100644
--- a/cmd/sbc_harness/fs_harness_flash_bin.h
+++ b/cmd/sbc_harness/fs_harness_flash_bin.h
@@ -11,17 +11,24 @@
#include <lib9p/srv.h>
-struct flash_file {
- char *name;
- uint64_t pathnum;
+#include "ihex.h"
- BEGIN_PRIVATE(FS_HARNESS_FLASH_BIN);
- bool written;
+struct flashio {
+ bool finalize;
struct {
bool ok;
size_t pos;
uint8_t dat[FLASH_SECTOR_SIZE];
} wbuf, rbuf;
+};
+
+struct flash_file {
+ char *name;
+ uint64_t pathnum;
+
+ BEGIN_PRIVATE(FS_HARNESS_FLASH_BIN);
+ struct flashio io;
+ struct ihex_decoder ihex;
END_PRIVATE(FS_HARNESS_FLASH_BIN);
};
LO_IMPLEMENTATION_H(lib9p_srv_file, struct flash_file, flash_file);
diff --git a/cmd/sbc_harness/fs_harness_uptime_txt.c b/cmd/sbc_harness/fs_harness_uptime_txt.c
index 021a8bd..9b03b46 100644
--- a/cmd/sbc_harness/fs_harness_uptime_txt.c
+++ b/cmd/sbc_harness/fs_harness_uptime_txt.c
@@ -11,23 +11,27 @@
#include "fs_harness_uptime_txt.h"
-LO_IMPLEMENTATION_C(lib9p_srv_file, struct uptime_file, uptime_file, static);
+LO_IMPLEMENTATION_C(lib9p_srv_file, struct uptime_file, uptime_file);
struct uptime_fio {
struct uptime_file *parent;
size_t buf_len;
- char buf[24]; /* len(str(UINT64_MAX)+"ns\n\0") */
+ /* The maximum length (UINT64_MAX) string is 52 bytes, not
+ * including a nul-terminator:
+ *
+ * "18446744073709551615ns\n" # 22+1
+ * "584y343d 23h34m33.709551615s\n" # 28+1
+ */
+ char buf[52];
};
-
-LO_IMPLEMENTATION_H(lib9p_srv_fio, struct uptime_fio, uptime_fio);
-LO_IMPLEMENTATION_C(lib9p_srv_fio, struct uptime_fio, uptime_fio, static);
+LO_IMPLEMENTATION_STATIC(lib9p_srv_fio, struct uptime_fio, uptime_fio);
/* srv_file *******************************************************************/
-static void uptime_file_free(struct uptime_file *self) {
+void uptime_file_free(struct uptime_file *self) {
assert(self);
}
-static struct lib9p_qid uptime_file_qid(struct uptime_file *self) {
+struct lib9p_qid uptime_file_qid(struct uptime_file *self) {
assert(self);
return (struct lib9p_qid){
@@ -37,7 +41,7 @@ static struct lib9p_qid uptime_file_qid(struct uptime_file *self) {
};
}
-static lib9p_srv_stat_or_error uptime_file_stat(struct uptime_file *self, struct lib9p_srv_ctx *ctx) {
+lib9p_srv_stat_or_error uptime_file_stat(struct uptime_file *self, struct lib9p_srv_ctx *ctx) {
assert(self);
assert(ctx);
@@ -64,24 +68,23 @@ static lib9p_srv_stat_or_error uptime_file_stat(struct uptime_file *self, struct
.extension = lib9p_str(NULL),
}));
}
-static error uptime_file_wstat(struct uptime_file *self, struct lib9p_srv_ctx *ctx,
- struct lib9p_srv_stat) {
+error uptime_file_wstat(struct uptime_file *self, struct lib9p_srv_ctx *ctx, struct lib9p_srv_stat) {
assert(self);
assert(ctx);
return error_new(E_POSIX_EROFS, "read-only part of filesystem");
}
-static error uptime_file_remove(struct uptime_file *self, struct lib9p_srv_ctx *ctx) {
+error uptime_file_remove(struct uptime_file *self, struct lib9p_srv_ctx *ctx) {
assert(self);
assert(ctx);
return error_new(E_POSIX_EROFS, "read-only part of filesystem");
}
-LIB9P_SRV_NOTDIR(struct uptime_file, uptime_file);
+LIB9P_SRV_NOTDIR(, struct uptime_file, uptime_file);
-static lib9p_srv_fio_or_error uptime_file_fopen(struct uptime_file *self, struct lib9p_srv_ctx *ctx,
- bool LM_UNUSED(rd), bool LM_UNUSED(wr), bool LM_UNUSED(trunc)) {
+lib9p_srv_fio_or_error 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);
@@ -104,33 +107,51 @@ static void uptime_fio_iofree(struct uptime_fio *self) {
free(self);
}
-static struct lib9p_qid uptime_fio_qid(struct uptime_fio *self) {
+static struct lib9p_qid uptime_fio_ioqid(struct uptime_fio *self) {
assert(self);
assert(self->parent);
return uptime_file_qid(self->parent);
}
-static iovec_or_error uptime_fio_pread(struct uptime_fio *self, struct lib9p_srv_ctx *ctx,
- uint32_t byte_count, uint64_t byte_offset) {
+#define NS_PER_M (NS_PER_S*60)
+#define NS_PER_H (NS_PER_S*60*60)
+#define NS_PER_D (NS_PER_S*60*60*24)
+#define NS_PER_Y (NS_PER_S*60*60*24*365)
+
+static error uptime_fio_pread(struct uptime_fio *self, struct lib9p_srv_ctx *ctx,
+ lo_interface io_writer dst, uint64_t byte_offset, uint32_t byte_count) {
assert(self);
assert(ctx);
if (byte_offset == 0 || self->buf_len == 0) {
uint64_t now = LO_CALL(bootclock, get_time_ns);
self->buf_len = fmt_snprint(self->buf, sizeof(self->buf), now, "ns\n");
+
+ uint64_t ns = now;
+ uint64_t y = ns/NS_PER_Y; ns -= y*NS_PER_Y;
+ uint64_t d = ns/NS_PER_D; ns -= d*NS_PER_D;
+ uint64_t h = ns/NS_PER_H; ns -= h*NS_PER_H;
+ uint64_t m = ns/NS_PER_M; ns -= m*NS_PER_M;
+ uint64_t s = ns/NS_PER_S; ns -= s*NS_PER_S;
+ if (y)
+ self->buf_len += fmt_snprint(&self->buf[self->buf_len], sizeof(self->buf)-self->buf_len, y, "y");
+ if (y || d)
+ self->buf_len += fmt_snprint(&self->buf[self->buf_len], sizeof(self->buf)-self->buf_len, d, "d ");
+ if (y || d || h)
+ self->buf_len += fmt_snprint(&self->buf[self->buf_len], sizeof(self->buf)-self->buf_len, h, "h");
+ if (y || d || h || m)
+ self->buf_len += fmt_snprint(&self->buf[self->buf_len], sizeof(self->buf)-self->buf_len, m, "m");
+ self->buf_len += fmt_snprint(&self->buf[self->buf_len], sizeof(self->buf)-self->buf_len, s, ".", (rjust, 9, '0', ns), "s\n");
}
if (byte_offset > (uint64_t)self->buf_len)
- return ERROR_NEW_ERR(iovec, error_new(E_POSIX_EINVAL, "offset is past end-of-file length"));
+ return error_new(E_POSIX_EINVAL, "offset is past end-of-file length");
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;
- return ERROR_NEW_VAL(iovec, ((struct iovec){
- .iov_base = &self->buf[beg_off],
- .iov_len = end_off-beg_off,
- }));
+ return io_write(dst, &self->buf[beg_off], end_off-beg_off).err;
}
static uint32_t_or_error uptime_fio_pwrite(struct uptime_fio *self, struct lib9p_srv_ctx *ctx,
diff --git a/cmd/sbc_harness/ihex.c b/cmd/sbc_harness/ihex.c
new file mode 100644
index 0000000..5a7f6d5
--- /dev/null
+++ b/cmd/sbc_harness/ihex.c
@@ -0,0 +1,231 @@
+/* sbc_harness/ihex.c - Intel Hex decoder
+ *
+ * Copyright (C) 2025 Luke T. Shumaker <lukeshu@lukeshu.com>
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+/* https://archive.org/details/IntelHEXStandard */
+
+#include <string.h> /* for memchr() */
+
+#include <libmisc/assert.h>
+#include <libmisc/endian.h>
+
+#define IMPLEMENTATION_FOR_IHEX_H YES
+#include "ihex.h"
+
+LO_IMPLEMENTATION_C(io_writer, struct ihex_decoder, ihex_decoder);
+LO_IMPLEMENTATION_C(io_closer, struct ihex_decoder, ihex_decoder);
+
+enum ihex_record_type {
+ /* [U]SBA: [Upper] Segment Base Address : SBA = USBA<< 4 */
+ /* [U]LBA: [Upper] Linear Base Address : LBA = ULBA<<16 */
+ /* _EXT records define where DATA records are written to */
+ /* _START records define where execution should start */
+ IHEX_REC_DATA = 0x00, /* .dat is .len bytes of data, which go at either (USBA<<4)+(.off%64KiB) or ((ULBA<<16)+.off)%4GiB */
+ IHEX_REC_EOF = 0x01, /* .len=0, .off=0 */
+ IHEX_REC_ADDR_SEG_EXT = 0x02, /* .len=2, .off=0, .dat is u16be USBA */
+ IHEX_REC_ADDR_SEG_START = 0x03, /* .len=4, .off=0, .dat is u16be CS register then u16be IP register */
+ IHEX_REC_ADDR_LIN_EXT = 0x04, /* .len=2, .off=0, .dat is u16be ULBA */
+ IHEX_REC_ADDR_LIN_START = 0x05, /* .len=4, .off=0, .dat is u32be EIP register */
+};
+
+struct ihex_record {
+ uint8_t len;
+ uint16_t off;
+ enum ihex_record_type typ;
+ uint8_t *dat;
+};
+
+static error ihex_handle_record(struct ihex_decoder *self, struct ihex_record *rec) {
+ switch (rec->typ) {
+ case IHEX_REC_ADDR_SEG_EXT:
+ self->addr_mode = _IHEX_MODE_SEG;
+ self->addr_base = ((uint32_t)uint16be_decode(rec->dat)) << 4;
+ return ERROR_NULL;
+ case IHEX_REC_ADDR_LIN_EXT:
+ self->addr_mode = _IHEX_MODE_LIN;
+ self->addr_base = ((uint32_t)uint16be_decode(rec->dat)) << 16;
+ return ERROR_NULL;;
+ case IHEX_REC_DATA:
+ switch (self->addr_mode) {
+ case _IHEX_MODE_NONE:
+ return error_new(E_POSIX_EINVAL, "ihex: data record before base-address record");
+ case _IHEX_MODE_SEG:
+ if (!self->handle_data)
+ return ERROR_NULL;
+ if (rec->len <= UINT16_MAX - rec->off) {
+ /* 1 write */
+ return self->handle_data(self->handle_arg, self->addr_base + rec->off, rec->len, rec->dat);
+ } else {
+ /* wraps around; split into 2 writes */
+ uint8_t first_len = (uint8_t) (UINT16_MAX - rec->off);
+ if (first_len) {
+ error err = self->handle_data(self->handle_arg, self->addr_base + rec->off, first_len, rec->dat);
+ if (!ERROR_IS_NULL(err))
+ return err;
+ }
+ return self->handle_data(self->handle_arg, self->addr_base, rec->len - first_len, &rec->dat[first_len]);
+ }
+ case _IHEX_MODE_LIN:
+ if (!self->handle_data)
+ return ERROR_NULL;
+ uint32_t off = self->addr_base + rec->off;
+ if (rec->len <= UINT32_MAX - off) {
+ /* 1 write */
+ return self->handle_data(self->handle_arg, off, rec->len, rec->dat);
+ } else {
+ /* wraps around; split into 2 writes */
+ uint8_t first_len = (uint8_t) (UINT32_MAX - off);
+ if (first_len) {
+ error err = self->handle_data(self->handle_arg, off, first_len, rec->dat);
+ if (!ERROR_IS_NULL(err))
+ return err;
+ }
+ return self->handle_data(self->handle_arg, 0, rec->len - first_len, &rec->dat[first_len]);
+ }
+ default:
+ assert_notreached("bad addr_mode");
+ }
+ case IHEX_REC_EOF:
+ self->seen_eof = true;
+ if (!self->handle_eof)
+ return ERROR_NULL;
+ return self->handle_eof(self->handle_arg);
+ case IHEX_REC_ADDR_SEG_START:
+ if (!self->handle_set_exec_start_seg)
+ return ERROR_NULL;
+ uint16_t cs = uint16be_decode(&rec->dat[0]);
+ uint16_t ip = uint16be_decode(&rec->dat[2]);
+ return self->handle_set_exec_start_seg(self->handle_arg, cs, ip);
+ case IHEX_REC_ADDR_LIN_START:
+ if (!self->handle_set_exec_start_lin)
+ return ERROR_NULL;
+ uint32_t eip = uint32be_decode(rec->dat);
+ return self->handle_set_exec_start_lin(self->handle_arg, eip);
+ default:
+ assert_notreached("bad record type");
+ }
+}
+
+/**
+ * Hex-decode the byte 0xAB, and push it onto self->buf. If this
+ * completes the record in self->buf, then handle that record.
+ *
+ * @return the number of ASCII bytes consumed (0, 1, or 2) before
+ * encountering an error.
+ */
+static size_t_and_error ihex_decode_byte(struct ihex_decoder *self, char a, char b) {
+ uint8_t byte;
+ if ('0' <= a && a <= '9')
+ byte = (a - '0') << 4;
+ else if ('A' <= a && a <= 'F')
+ byte = (a - 'A' + 10) << 4;
+ else
+ return ERROR_AND(size_t, 0, error_new(E_POSIX_EILSEQ, "ihex: invalid hexadecimal: ", (qbyte, a)));
+ if ('0' <= b && b <= '9')
+ byte |= b - '0';
+ else if ('A' <= b && b <= 'F')
+ byte |= b - 'A' + 10;
+ else
+ return ERROR_AND(size_t, 1, error_new(E_POSIX_EILSEQ, "ihex: invalid hexadecimal: ", (qbyte, b)));
+ self->buf[self->buf_len++] = byte;
+ if (self->buf_len == self->buf[0]+5) {
+ uint8_t sum = 0;
+ for (size_t i = 0; i < (size_t)self->buf[0]+5; i++)
+ sum += self->buf[i];
+ if (sum != 0) {
+ self->sticky_err = error_new(E_POSIX_EPROTO, "ihex: checksum mismatch");
+ return ERROR_AND(size_t, 2, error_dup(self->sticky_err));
+ }
+ struct ihex_record rec = {
+ .len = self->buf[0],
+ .off = uint16be_decode(&self->buf[1]),
+ .typ = self->buf[3],
+ .dat = &self->buf[4],
+ };
+ error err = ihex_handle_record(self, &rec);
+ if (!ERROR_IS_NULL(err)) {
+ self->sticky_err = err;
+ return ERROR_AND(size_t, 2, error_dup(err));
+ }
+ self->in_record = false;
+ self->buf_len = 0;
+ }
+ return ERROR_AND(size_t, 2, ERROR_NULL);
+}
+
+static size_t_and_error ihex_decoder_write(struct ihex_decoder *self, const char *dat, size_t len_in) {
+ assert(self);
+ if (!len_in)
+ return ERROR_AND(size_t, 0, ERROR_NULL);
+ assert(dat);
+
+ if (!ERROR_IS_NULL(self->sticky_err))
+ return ERROR_AND(size_t, 0, error_dup(self->sticky_err));
+
+ size_t len_consumed = 0;
+
+ if (self->buf_char) {
+ assert(self->in_record);
+ size_t_and_error r = ihex_decode_byte(self, self->buf_char, dat[0]);
+ if (r.size_t)
+ len_consumed += r.size_t - 1;
+ self->buf_char = 0;
+ if (!ERROR_IS_NULL(r.err))
+ return ERROR_AND(size_t, len_consumed, r.err);
+ }
+
+ while (len_consumed < len_in) {
+ if (!self->in_record) {
+ const char *marker = memchr(&dat[len_consumed], ':', len_in-len_consumed);
+ if (!marker) {
+ len_consumed = len_in;
+ continue;
+ }
+ len_consumed += marker - &dat[len_consumed];
+
+ assert(dat[len_consumed] == ':');
+ if (self->seen_eof)
+ return ERROR_AND(size_t, len_consumed, error_new(E_POSIX_EPROTO, "ihex: record after EOF record"));
+ len_consumed++;
+ self->in_record = true;
+ }
+ while (len_in - len_consumed >= 2 && self->in_record) {
+ size_t_and_error r = ihex_decode_byte(self, dat[len_consumed], dat[len_consumed+1]);
+ len_consumed += r.size_t;
+ if (!ERROR_IS_NULL(r.err))
+ return ERROR_AND(size_t, len_consumed, r.err);
+ }
+ if (len_in - len_consumed && self->in_record) {
+ assert(len_in - len_consumed == 1);
+ if (!(('0' <= dat[len_consumed] && dat[len_consumed] <= '9') ||
+ ('A' <= dat[len_consumed] && dat[len_consumed] <= 'F')))
+ return ERROR_AND(size_t, len_consumed, error_new(E_POSIX_EILSEQ, "ihex: invalid hexadecimal: ", (qbyte, dat[len_consumed])));
+ self->buf_char = dat[len_consumed++];
+ }
+ }
+
+ assert(len_consumed == len_in);
+ return ERROR_AND(size_t, len_in, ERROR_NULL);
+}
+
+size_t_and_error ihex_decoder_writev(struct ihex_decoder *self, const struct iovec *iov, int iovcnt) {
+ assert(self);
+ assert(iov);
+ assert(iovcnt);
+
+ size_t total = 0;
+ for (int i = 0; i < iovcnt; i++) {
+ size_t_and_error r = ihex_decoder_write(self, iov[i].iov_base, iov[i].iov_len);
+ total += r.size_t;
+ if (!ERROR_IS_NULL(r.err))
+ return ERROR_AND(size_t, total, r.err);
+ }
+ return ERROR_AND(size_t, total, ERROR_NULL);
+}
+
+error ihex_decoder_close(struct ihex_decoder *self) {
+ error_cleanup(&self->sticky_err);
+ return ERROR_NULL;
+}
diff --git a/cmd/sbc_harness/ihex.h b/cmd/sbc_harness/ihex.h
new file mode 100644
index 0000000..35a3cbe
--- /dev/null
+++ b/cmd/sbc_harness/ihex.h
@@ -0,0 +1,49 @@
+/* sbc_harness/ihex.h - Intel Hex decoder
+ *
+ * Copyright (C) 2025 Luke T. Shumaker <lukeshu@lukeshu.com>
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+/* https://archive.org/details/IntelHEXStandard */
+
+#ifndef _SBC_HARNESS_IHEX_H_
+#define _SBC_HARNESS_IHEX_H_
+
+#include <stdbool.h> /* for bool */
+#include <stddef.h> /* for size_t */
+#include <stdint.h> /* for uint{n}_t */
+
+#include <libhw/generic/io.h>
+#include <libmisc/private.h>
+
+struct ihex_decoder {
+ void *handle_arg;
+ error (*handle_data)(void *arg, uint32_t off, uint8_t count, uint8_t *dat);
+ error (*handle_set_exec_start_seg)(void *arg, uint16_t cs, uint16_t ip);
+ error (*handle_set_exec_start_lin)(void *arg, uint32_t eip);
+ error (*handle_eof)(void *arg);
+
+ BEGIN_PRIVATE(IHEX_H);
+
+ bool seen_eof;
+ error sticky_err;
+
+ /* ihex_decoder_write: deal with ASCII soup */
+ bool in_record;
+ char buf_char; /* the first nibble of a byte (still in ASCII) */
+
+ /* ihex_decode_byte: build records from decoded bytes */
+ /* The currently-being-decoded record, after hex decoding. */
+ uint16_t buf_len;
+ uint8_t buf[0xFF+5];
+
+ /* ihex_handle_record: handle record semantics */
+ enum { _IHEX_MODE_NONE, _IHEX_MODE_SEG, _IHEX_MODE_LIN } addr_mode;
+ uint32_t addr_base;
+
+ END_PRIVATE(IHEX_H);
+};
+LO_IMPLEMENTATION_H(io_writer, struct ihex_decoder, ihex_decoder);
+LO_IMPLEMENTATION_H(io_closer, struct ihex_decoder, ihex_decoder);
+
+#endif /* _SBC_HARNESS_IHEX_H_ */
diff --git a/cmd/sbc_harness/static/Documentation/harness_flash_bin.txt b/cmd/sbc_harness/static/Documentation/harness_flash_bin.txt
index 115f2ee..1b58d6d 100644
--- a/cmd/sbc_harness/static/Documentation/harness_flash_bin.txt
+++ b/cmd/sbc_harness/static/Documentation/harness_flash_bin.txt
@@ -2,14 +2,20 @@ NAME
/harness/flash.bin
DESCRIPTION
- Access to the flash storage chip (where the harness firmware
- is stored).
+ Access the flash storage chip (where the harness firmware is
+ stored).
- Any number of readers may read the flash contents.
+ Reading from the file reads the raw flash contents.
- Only one writer can have the file open at a time; once the
+ Writing to the file does not accept raw data; instead the data
+ must be encapsulated in the [Intel Hex] format, with the Hex
+ file writing to the region 0x1000_0000-0x1010_0000. While less
+ convenient than verbatim data, the Hex format provides in-band
+ checksums and EOF-markers that help prevent rendering the
+ harness unbootable with corrupted or truncated writes. Any
+ holes in the Intel Hex file are filled with "1" bits. Once a
+ complete Intel Hex file has been written without error and 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
@@ -21,13 +27,17 @@ BUGS
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
+ chip capacity is usable (the size of the region specified
+ above is half the chip size); 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.
+SEE ALSO:
+ [Intel Hex]: https://archive.org/details/IntelHEXStandard
+
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
index 1ab86f7..09e9243 100644
--- a/cmd/sbc_harness/static/Documentation/harness_uptime_txt.txt
+++ b/cmd/sbc_harness/static/Documentation/harness_uptime_txt.txt
@@ -3,14 +3,24 @@ NAME
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.
+
+ {ns}ns
+ [[[[{y}y]{d}d ]{h}h]{m}m]{s.09}s
+
+ That is: the first line is simply the harness's uptime in an
+ integer number of nanoseconds; and the second line is this
+ same number in a more human-readable form; divided into
+ seconds, minutes, hours, days, and years.
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.
+ - 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.
+
+ - In the human-readable form, the days are always exactly
+ 60*60*24 seconds (leap seconds are ignored), and the years
+ are always exactly 365 days (leap years are ignored).
AUTHOR
Copyright (C) 2025 Luke T. Shumaker <lukeshu@lukeshu.com>
diff --git a/cmd/sbc_harness/tests/test_ihex.c b/cmd/sbc_harness/tests/test_ihex.c
new file mode 100644
index 0000000..143e3b4
--- /dev/null
+++ b/cmd/sbc_harness/tests/test_ihex.c
@@ -0,0 +1,128 @@
+/* cmd/sbc_harness/tests/test_ihex.c - Tests for ihex.c
+ *
+ * Copyright (C) 2025 Luke T. Shumaker <lukeshu@lukeshu.com>
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+#include <stdio.h> /* for putchar() */
+#include <string.h>
+
+#include <libmisc/fmt.h>
+
+#include "ihex.h"
+
+struct stdout { size_t len; };
+LO_IMPLEMENTATION_STATIC(fmt_dest, struct stdout, stdout);
+static void stdout_putb(struct stdout *self, uint8_t b) {
+ putchar(b);
+ self->len++;
+}
+static size_t stdout_tell(struct stdout *self) {
+ return self->len;
+}
+
+static lo_interface fmt_dest fmt_stdout = lo_box_stdout_as_fmt_dest(&((struct stdout){}));
+
+#define test_assert(expr) do { \
+ if (!(expr)) \
+ fmt_print( \
+ fmt_stdout, \
+ "test failure: ", __FILE__, ":", __LINE__, ":", __func__, \
+ ": " #expr "\n"); \
+ } while (0)
+
+static char *input =
+ /* ,-byte count
+ * | ,-address
+ * | | ,-record type
+ * | | | ,- checksum
+ *[][--][][..............................][]\r\n */
+ ":020000041000EA\r\n" /* base_addr = linear(0x1000) = 0x1000<<16 */
+ ":1000000000B5324B212058609868022188439860DF\r\n" /* memcpy(chip[base_addr+0x0000], "\x00\xB5\x32\x4B\x21\x20\x58\x60\x98\x68\x02\x21\x88\x43\x98\x60", 16) */
+ ":10001000D860186158612E4B002199600221596106\r\n" /* memcpy(chip[base_addr+0x0010], "\xD8\x60\x18\x61\x58\x61\x2E\x4B\x00\x21\x99\x60\x02\x21\x59\x61", 16) */
+ ":10BE9C000000000000000000000000000000000096\r\n" /* memcpy(chip[base_addr+0xBE9C], "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", 16) */
+ ":04BEAC00CC4A00205C\r\n" /* memcpy(chip[base_addr+0xBEAC], "\x00\xCC\x4A\x00\x20", 5) */
+ ":04000005100001E9FD\r\n" /* start_exec_at = linear(0x100001E9) */
+ ":00000001FF\r\n"; /* EOF */
+
+static int cnt = 0;
+
+static error handle_data(void *, uint32_t off, uint8_t count, uint8_t *dat) {
+ switch (cnt) {
+ case 0:
+ test_assert(off == UINT32_C(0x10000000));
+ test_assert(count == 16);
+ test_assert(memcmp(dat, "\x00\xB5\x32\x4B\x21\x20\x58\x60\x98\x68\x02\x21\x88\x43\x98\x60", 16) == 0);
+ break;
+ case 1:
+ test_assert(off == UINT32_C(0x10000010));
+ test_assert(count == 16);
+ test_assert(memcmp(dat, "\xD8\x60\x18\x61\x58\x61\x2E\x4B\x00\x21\x99\x60\x02\x21\x59\x61", 16) == 0);
+ break;
+ case 2:
+ test_assert(off == UINT32_C(0x1000BE9C));
+ test_assert(count == 16);
+ test_assert(memcmp(dat, "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", 16) == 0);
+ break;
+ case 4:
+ test_assert(off == UINT32_C(0x1000BE9C));
+ test_assert(count == 5);
+ test_assert(memcmp(dat, "\x00\xCC\x4A\x00\x20", 5) == 0);
+ break;
+ default:
+ test_assert(false);
+ }
+ cnt++;
+ return ERROR_NULL;
+}
+static error handle_set_exec_start_seg(void *, uint16_t LM_UNUSED(cs), uint16_t LM_UNUSED(ip)) {
+ switch (cnt) {
+ default:
+ test_assert(false);
+ }
+ cnt++;
+ return ERROR_NULL;
+}
+static error handle_set_exec_start_lin(void *, uint32_t eip) {
+ switch (cnt) {
+ case 5:
+ test_assert(eip == UINT32_C(0x100001E9));
+ break;
+ default:
+ test_assert(false);
+ }
+ cnt++;
+ return ERROR_NULL;
+}
+static error handle_eof(void *) {
+ switch (cnt) {
+ case 6:
+ break;
+ default:
+ test_assert(false);
+ }
+ cnt++;
+ return ERROR_NULL;
+}
+
+int main() {
+ struct ihex_decoder dec = {
+ .handle_data = handle_data,
+ .handle_set_exec_start_seg = handle_set_exec_start_seg,
+ .handle_set_exec_start_lin = handle_set_exec_start_lin,
+ .handle_eof = handle_eof,
+ };
+
+ size_t_and_error ret = ihex_decoder_writev(&dec, &((struct iovec){
+ .iov_base = input,
+ .iov_len = strlen(input),
+ }), 1);
+ fmt_print(fmt_stdout,
+ "ret = (", ret.size_t, ", ", (error, ret.err), ")\n",
+ "cnt = ", cnt, "\n");
+ test_assert(ret.size_t == strlen(input));
+ test_assert(ERROR_IS_NULL(ret.err));
+ test_assert(cnt == 6);
+
+ return 0;
+}
diff --git a/lib9p/srv.c b/lib9p/srv.c
index d1f5814..295bc1d 100644
--- a/lib9p/srv.c
+++ b/lib9p/srv.c
@@ -711,11 +711,13 @@ static inline void _srv_respond(struct srv_req *ctx, enum lib9p_msg_type resp_ty
/* handle_T* ******************************************************************/
-#define srv_handler_common(ctx, typ, req) \
+#define srv_handler_common_no_err(ctx, typ, req) \
assert(ctx); \
assert(req); \
struct lib9p_msg_T##typ *_typecheck_req [[maybe_unused]] = req; \
- struct lib9p_msg_R##typ resp = { .tag = ctx->tag }; \
+ struct lib9p_msg_R##typ resp = { .tag = ctx->tag }
+#define srv_handler_common(ctx, typ, req) \
+ srv_handler_common_no_err(ctx, typ, req); \
error err = {}
static void handle_Tversion(struct srv_req *ctx,
@@ -1107,7 +1109,7 @@ static void handle_Topen(struct srv_req *ctx,
fidinfo->dir.io = dio_r.lib9p_srv_dio;
fidinfo->dir.idx = 0;
fidinfo->dir.off = 0;
- qid = LO_CALL(fidinfo->dir.io, qid);
+ qid = LO_CALL(fidinfo->dir.io, ioqid);
iounit = 0;
break;
case SRV_FILETYPE_FILE:
@@ -1120,7 +1122,7 @@ static void handle_Topen(struct srv_req *ctx,
goto topen_return;
}
fidinfo->file.io = fio_r.lib9p_srv_fio;
- qid = LO_CALL(fidinfo->file.io, qid);
+ qid = LO_CALL(fidinfo->file.io, ioqid);
iounit = LO_CALL(fidinfo->file.io, iounit);
break;
case SRV_FILETYPE_AUTH:
@@ -1175,25 +1177,33 @@ static inline struct lib9p_stat srv_stat_to_net_stat(struct lib9p_srv_stat in) {
};
}
+static void handle_read_file(struct srv_req *ctx, struct srv_fidinfo *fidinfo, uint64_t offset, uint32_t count);
+static void handle_read_dir(struct srv_req *ctx, struct srv_fidinfo *fidinfo, uint64_t offset, uint32_t count);
+
static void handle_Tread(struct srv_req *ctx,
struct lib9p_msg_Tread *req) {
- srv_handler_common(ctx, read, req);
- char *heap = NULL;
+ assert(ctx);
+ assert(req);
/* TODO: serialize simultaneous reads to the same FID */
+ /* req->count <= CONFIG_9P_SRV_MAX_MSG_SIZE <= CONFIG_9P_SRV_MAX_HOSTMSG_SIZE <= SIZE_MAX */
+ assert(req->count <= SIZE_MAX);
+ /* req->offset is u64, uoff is u64 */
+ static_assert(req->offset <= UOFF_MAX);
+
if (req->count > ctx->basectx.max_msg_size - lib9p_version_min_Rread_size(ctx->basectx.version))
req->count = ctx->basectx.max_msg_size - lib9p_version_min_Rread_size(ctx->basectx.version);
/* Check that the FID is valid for this. */
struct srv_fidinfo *fidinfo = map_load(&ctx->parent_sess->fids, req->fid);
if (!fidinfo) {
- err = error_new(E_POSIX_EBADF, "bad file number ", req->fid);
- goto tread_return;
+ srv_respond(ctx, read, NULL, error_new(E_POSIX_EBADF, "bad file number ", req->fid));
+ return;
}
if (!(fidinfo->flags & FIDFLAG_OPEN_R)) {
- err = error_new(E_POSIX_EINVAL, "FID not open for reading");
- goto tread_return;
+ srv_respond(ctx, read, NULL, error_new(E_POSIX_EINVAL, "FID not open for reading"));
+ return;
}
/* Do it. */
@@ -1201,119 +1211,174 @@ static void handle_Tread(struct srv_req *ctx,
switch (fidinfo->type) {
case SRV_FILETYPE_DIR:
#if _LIB9P_ENABLE_stat
- /* Seek. */
- if (req->offset == 0) {
- fidinfo->dir.idx = 0;
- fidinfo->dir.off = 0;
- fidinfo->dir.buffered_dirent = (struct lib9p_srv_dirent){};
- } else if (req->offset != fidinfo->dir.off) {
- err = error_new(E_POSIX_EINVAL, "invalid offset (must be 0 or ", fidinfo->dir.off, "): ", req->offset);
- goto tread_return;
- }
- /* Read. */
- resp.data = heap = malloc(req->count);
- resp.count = 0;
- struct srv_pathinfo *dir_pathinfo = NULL;
- for (;;) {
- lo_interface lib9p_srv_file member_file = {};
- struct lib9p_srv_dirent member_dirent;
- if (fidinfo->dir.buffered_dirent.name.len) {
- member_dirent = fidinfo->dir.buffered_dirent;
- } else {
- lib9p_srv_dirent_or_error member_dirent_r;
- member_dirent_r = LO_CALL(fidinfo->dir.io, dread, ctx, fidinfo->dir.idx);
- if (member_dirent_r.is_err) {
- if (!resp.count) {
- err = member_dirent_r.err;
- goto tread_return;
- }
- error_cleanup(&member_dirent_r.err);
+ handle_read_dir(ctx, fidinfo, req->offset, req->count);
+#else
+ assert_notreached("Tread for directory on protocol version without that");
+#endif
+ break;
+ case SRV_FILETYPE_FILE:
+ handle_read_file(ctx, fidinfo, req->offset, req->count);
+ break;
+ case SRV_FILETYPE_AUTH:
+ assert_notreached("TODO: auth not yet implemented");
+ break;
+ }
+ ctx->user = srv_userid_decref(ctx->user);
+}
+
+struct rread_writer {
+ struct srv_req *ctx;
+ size_t count;
+ bool written;
+
+};
+LO_IMPLEMENTATION_STATIC(io_writer, struct rread_writer, rread);
+
+static size_t_and_error rread_writev(struct rread_writer *self, const struct iovec *iov, int iovcnt) {
+ assert(self);
+ assert(!self->written);
+ assert(iovcnt == 1);
+ assert(iov);
+ assert(iov->iov_len <= self->count);
+
+ struct lib9p_msg_Rread resp = {
+ .tag = self->ctx->tag,
+ .count = iov->iov_len,
+ .data = iov->iov_base,
+ };
+
+ srv_respond(self->ctx, read, &resp, ERROR_NULL);
+
+ self->written = true;
+ return ERROR_AND(size_t, iov->iov_len, ERROR_NULL);
+}
+
+static void handle_read_file(struct srv_req *ctx, struct srv_fidinfo *fidinfo, uint64_t offset, uint32_t count) {
+ struct rread_writer _writer = {
+ .ctx = ctx,
+ .count = (size_t) count,
+ .written = false,
+ };
+ lo_interface io_writer writer = LO_BOX(io_writer, &_writer);
+ error err = LO_CALL(fidinfo->file.io, pread, ctx, writer, offset, count);
+ assert(ERROR_IS_NULL(err) == _writer.written);
+ if (!ERROR_IS_NULL(err))
+ srv_respond(ctx, read, NULL, err);
+}
+
+#if _LIB9P_ENABLE_stat
+static void handle_read_dir(struct srv_req *ctx, struct srv_fidinfo *fidinfo, uint64_t offset, uint32_t count) {
+ /* Seek. */
+ if (offset == 0) {
+ fidinfo->dir.idx = 0;
+ fidinfo->dir.off = 0;
+ fidinfo->dir.buffered_dirent = (struct lib9p_srv_dirent){};
+ } else if (offset != fidinfo->dir.off) {
+ srv_respond(ctx, read, NULL, error_new(E_POSIX_EINVAL, "invalid offset (must be 0 or ", fidinfo->dir.off, "): ", offset));
+ return;
+ }
+
+ /* Allocate. */
+ [[gnu::cleanup(heap_cleanup)]] void *heap = NULL;
+ struct lib9p_msg_Rread resp = {
+ .tag = ctx->tag,
+ .data = heap = malloc(count),
+ .count = 0,
+ };
+
+ /* Read. */
+ struct srv_pathinfo *dir_pathinfo = NULL;
+ for (;;) {
+ /* 1. Call ->dread() to get `member_dirent`. */
+ struct lib9p_srv_dirent member_dirent;
+ if (fidinfo->dir.buffered_dirent.name.len) {
+ member_dirent = fidinfo->dir.buffered_dirent;
+ } else {
+ lib9p_srv_dirent_or_error r = LO_CALL(fidinfo->dir.io, dread, ctx, fidinfo->dir.idx);
+ if (r.is_err) {
+ if (resp.count) {
+ /* Just do a short-read; discard the error. */
+ error_cleanup(&r.err);
break;
}
- member_dirent = member_dirent_r.lib9p_srv_dirent;
+ srv_respond(ctx, read, NULL, r.err);
+ return;
}
- if (!member_dirent.name.len)
- break;
- struct lib9p_srv_stat member_stat;
+ member_dirent = r.lib9p_srv_dirent;
+ }
+ if (!member_dirent.name.len)
+ /* end-of-directory */
+ break;
+
+ /* 2. Call ->dwalk() to get the `member_file` object to call ->stat() on. */
+ lo_interface lib9p_srv_file member_file;
+ bool free_member_file;
+ {
struct srv_pathinfo *member_pathinfo = map_load(&ctx->parent_sess->paths, member_dirent.qid.path);
if (member_pathinfo) {
- lib9p_srv_stat_or_error r = LO_CALL(member_pathinfo->file, stat, ctx);
- if (r.is_err) {
- err = r.err;
- goto member_err;
- }
- member_stat = r.lib9p_srv_stat;
+ member_file = member_pathinfo->file;
+ free_member_file = false;
} else {
if (!dir_pathinfo)
dir_pathinfo = map_load(&ctx->parent_sess->paths, fidinfo->path);
assert(dir_pathinfo);
- lib9p_srv_file_or_error file_r = LO_CALL(dir_pathinfo->file, dwalk, ctx, member_dirent.name);
- if (file_r.is_err) {
- err = file_r.err;
- goto member_err;
- }
- member_file = file_r.lib9p_srv_file;
- lib9p_srv_stat_or_error stat_r = LO_CALL(member_file, stat, ctx);
- if (stat_r.is_err) {
- err = stat_r.err;
- goto member_err;
+ lib9p_srv_file_or_error r = LO_CALL(dir_pathinfo->file, dwalk, ctx, member_dirent.name);
+ if (r.is_err) {
+ if (resp.count) {
+ /* Just do a short-read; discard the error. */
+ error_cleanup(&r.err);
+ break;
+ }
+ srv_respond(ctx, read, NULL, r.err);
+ return;
}
- member_stat = stat_r.lib9p_srv_stat;
- }
- if (false) {
- member_err:
- if (!LO_IS_NULL(member_file))
- LO_CALL(member_file, free);
- if (!resp.count)
- goto tread_return;
- error_cleanup(&err);
- break;
+ member_file = r.lib9p_srv_file;
+ free_member_file = true;
}
- lib9p_srv_stat_assert(member_stat);
- struct lib9p_stat member_netstat = srv_stat_to_net_stat(member_stat);
- uint32_t nbytes = lib9p_stat_marshal(&ctx->basectx, req->count-resp.count, &member_netstat,
- (uint8_t *)&resp.data[resp.count]);
- if (!LO_IS_NULL(member_file))
+ }
+
+ /* 3. Call ->stat() to get `member_stat``. */
+ struct lib9p_srv_stat member_stat;
+ {
+ lib9p_srv_stat_or_error r = LO_CALL(member_file, stat, ctx);
+ if (free_member_file)
LO_CALL(member_file, free);
- if (!nbytes) {
- if (!resp.count) {
- err = error_new(E_POSIX_ERANGE, "stat object does not fit into negotiated max message size");
- goto tread_return;
+ if (r.is_err) {
+ if (resp.count) {
+ /* Just do a short-read; discard the error. */
+ error_cleanup(&r.err);
+ break;
}
+ srv_respond(ctx, read, NULL, r.err);
+ return;
+ }
+ member_stat = r.lib9p_srv_stat;
+ lib9p_srv_stat_assert(member_stat);
+ }
+
+ /* 4. Encode `member_stat` into `resp.data` and increment `resp.count`. */
+ struct lib9p_stat member_netstat = srv_stat_to_net_stat(member_stat);
+ uint32_t nbytes = lib9p_stat_marshal(&ctx->basectx, count-resp.count, &member_netstat,
+ (uint8_t *)&resp.data[resp.count]);
+ if (!nbytes) {
+ if (resp.count) {
+ /* Just do a short-read; discard the error.
+ * But save the member_dirent for next time. */
fidinfo->dir.buffered_dirent = member_dirent;
break;
}
- resp.count += nbytes;
- fidinfo->dir.idx++;
- fidinfo->dir.off += nbytes;
- fidinfo->dir.buffered_dirent = (struct lib9p_srv_dirent){};
- }
-#else
- assert_notreached("Tread for directory on protocol version without that");
-#endif
- break;
- case SRV_FILETYPE_FILE:
- iovec_or_error iov = LO_CALL(fidinfo->file.io, pread, ctx, req->count, req->offset);
- if (iov.is_err) {
- err = iov.err;
- } else {
- resp.count = iov.iovec.iov_len;
- resp.data = iov.iovec.iov_base;
- if (resp.count > req->count)
- resp.count = req->count;
+ srv_respond(ctx, read, NULL,
+ error_new(E_POSIX_ERANGE, "stat object does not fit into negotiated max message size"));
+ return;
}
- break;
- case SRV_FILETYPE_AUTH:
- assert_notreached("TODO: auth not yet implemented");
- break;
+ resp.count += nbytes;
+ fidinfo->dir.idx++;
+ fidinfo->dir.off += nbytes;
+ fidinfo->dir.buffered_dirent = (struct lib9p_srv_dirent){};
}
- tread_return:
- if (ctx->user)
- ctx->user = srv_userid_decref(ctx->user);
- srv_respond(ctx, read, &resp, err);
- if (heap)
- free(heap);
+ srv_respond(ctx, read, &resp, ERROR_NULL);
}
+#endif
static void handle_Twrite(struct srv_req *ctx,
struct lib9p_msg_Twrite *req) {
@@ -1349,18 +1414,17 @@ static void handle_Twrite(struct srv_req *ctx,
static void handle_Tclunk(struct srv_req *ctx,
struct lib9p_msg_Tclunk *req) {
- srv_handler_common(ctx, clunk, req);
+ srv_handler_common_no_err(ctx, clunk, req);
struct srv_fidinfo *fidinfo = map_load(&ctx->parent_sess->fids, req->fid);
if (!fidinfo) {
- err = error_new(E_POSIX_EBADF, "bad file number ", req->fid);
- goto tclunk_return;
+ srv_respond(ctx, clunk, NULL, error_new(E_POSIX_EBADF, "bad file number ", req->fid));
+ return;
}
+ srv_respond(ctx, clunk, &resp, ERROR_NULL);
+ /* Yes, don't actually perform the clunk until *after* we send Rclunk. */
srv_fid_del(ctx, req->fid, fidinfo, false);
-
- tclunk_return:
- srv_respond(ctx, clunk, &resp, err);
}
static void handle_Tremove(struct srv_req *ctx,
diff --git a/lib9p/srv_include/lib9p/srv.h b/lib9p/srv_include/lib9p/srv.h
index ce82e59..f4e6733 100644
--- a/lib9p/srv_include/lib9p/srv.h
+++ b/lib9p/srv_include/lib9p/srv.h
@@ -99,9 +99,6 @@ void lib9p_srv_stat_assert(struct lib9p_srv_stat stat);
/* interface definitions ******************************************************/
-typedef struct iovec iovec;
-DECLARE_ERROR_OR(iovec);
-
struct lib9p_srv_dirent {
struct lib9p_qid qid;
struct lib9p_s name;
@@ -109,22 +106,29 @@ struct lib9p_srv_dirent {
typedef struct lib9p_srv_dirent lib9p_srv_dirent;
DECLARE_ERROR_OR(lib9p_srv_dirent);
-/* FIXME: I don't like that the pointer returned by pread() has to
- * remain live after it returns. Perhaps a `respond()`-callback? But
- * that just reads as gross in C.
- *
- * FIXME: It would be nice if pread() could return more than 1 iovec.
+/* FIXME: It would be nice if pread() could return more than 1 iovec. This
+ * API allows it, but for the "just-1-iovec" requirement inherited from
+ * io_preader_to. We enforce this requirement because otherwise we wouldn't
+ * know at compile-time how big the iovec array in lib9p_Rmsg_send_buf needs
+ * to be.
*/
#define lib9p_srv_fio_LO_IFACE /*<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<*/ \
- LO_FUNC(struct lib9p_qid , qid ) \
+ LO_FUNC(struct lib9p_qid , ioqid ) \
LO_FUNC(void , iofree ) \
LO_FUNC(uint32_t , iounit ) \
- LO_FUNC(iovec_or_error , pread , struct lib9p_srv_ctx *, \
- uint32_t byte_count, \
- uint64_t byte_offset) \
+ /** \
+ * This is similar to io_preader_to->pread_to, and must follow the \
+ * same requirements. \
+ */ \
+ LO_FUNC(error , pread , struct lib9p_srv_ctx *, \
+ lo_interface io_writer dst, \
+ uint64_t src_offset, \
+ uint32_t count) \
/** \
* If the file was append-only when fopen()ed, then byte_offset will \
* always be 0. \
+ * \
+ * This similar to io_pwrite, but a short-write is not an error. \
*/ \
LO_FUNC(uint32_t_or_error , pwrite , struct lib9p_srv_ctx *, \
void *buf, \
@@ -135,7 +139,7 @@ typedef lo_interface lib9p_srv_fio lib9p_srv_fio;
DECLARE_ERROR_OR(lib9p_srv_fio);
#define lib9p_srv_dio_LO_IFACE /*<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<*/ \
- LO_FUNC(struct lib9p_qid , qid ) \
+ LO_FUNC(struct lib9p_qid , ioqid ) \
LO_FUNC(void , iofree ) \
/** \
* Return the idx-th dirent. idx will always be either 0 or \
@@ -203,15 +207,15 @@ DECLARE_ERROR_OR(lib9p_srv_file);
LO_FUNC(lib9p_srv_dio_or_error , dopen , struct lib9p_srv_ctx *)
LO_INTERFACE(lib9p_srv_file); /*>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>*/
-#define LIB9P_SRV_NOTDIR(TYP, NAM) \
- static lib9p_srv_file_or_error NAM##_dwalk (TYP *, struct lib9p_srv_ctx *, struct lib9p_s) { assert_notreached("not a directory"); } \
- static lib9p_srv_file_or_error NAM##_dcreate(TYP *, struct lib9p_srv_ctx *, struct lib9p_s, \
+#define LIB9P_SRV_NOTDIR(QUALS, TYP, NAM) \
+ QUALS lib9p_srv_file_or_error NAM##_dwalk (TYP *, struct lib9p_srv_ctx *, struct lib9p_s) { assert_notreached("not a directory"); } \
+ QUALS lib9p_srv_file_or_error NAM##_dcreate(TYP *, struct lib9p_srv_ctx *, struct lib9p_s, \
struct lib9p_srv_userid *, struct lib9p_srv_userid *, lib9p_dm_t) { assert_notreached("not a directory"); } \
- static lib9p_srv_dio_or_error NAM##_dopen (TYP *, struct lib9p_srv_ctx *) { assert_notreached("not a directory"); } \
+ QUALS lib9p_srv_dio_or_error NAM##_dopen (TYP *, struct lib9p_srv_ctx *) { assert_notreached("not a directory"); } \
LM_FORCE_SEMICOLON
-#define LIB9P_SRV_NOTFILE(TYP, NAM) \
- static lib9p_srv_fio_or_error NAM##_fopen (TYP *, struct lib9p_srv_ctx *, bool, bool, bool) { assert_notreached("not a file"); } \
+#define LIB9P_SRV_NOTFILE(QUALS, TYP, NAM) \
+ QUALS lib9p_srv_fio_or_error NAM##_fopen (TYP *, struct lib9p_srv_ctx *, bool, bool, bool) { assert_notreached("not a file"); } \
LM_FORCE_SEMICOLON
/* main server entrypoints ****************************************************/
diff --git a/lib9p/tests/test_server/fs_flush.c b/lib9p/tests/test_server/fs_flush.c
index 63a52af..0ae905f 100644
--- a/lib9p/tests/test_server/fs_flush.c
+++ b/lib9p/tests/test_server/fs_flush.c
@@ -9,20 +9,19 @@
#define IMPLEMENTATION_FOR_LIB9P_SRV_H YES /* for ctx->flush_ch */
#include "fs_flush.h"
-LO_IMPLEMENTATION_C(lib9p_srv_file, struct flush_file, flush_file, static);
+LO_IMPLEMENTATION_C(lib9p_srv_file, struct flush_file, flush_file);
struct flush_fio {
struct flush_file *parent;
};
-LO_IMPLEMENTATION_H(lib9p_srv_fio, struct flush_fio, flush_fio);
-LO_IMPLEMENTATION_C(lib9p_srv_fio, struct flush_fio, flush_fio, static);
+LO_IMPLEMENTATION_STATIC(lib9p_srv_fio, struct flush_fio, flush_fio);
/* srv_file *******************************************************************/
-static void flush_file_free(struct flush_file *self) {
+void flush_file_free(struct flush_file *self) {
assert(self);
}
-static struct lib9p_qid flush_file_qid(struct flush_file *self) {
+struct lib9p_qid flush_file_qid(struct flush_file *self) {
assert(self);
return (struct lib9p_qid){
.type = LIB9P_QT_FILE,
@@ -31,7 +30,7 @@ static struct lib9p_qid flush_file_qid(struct flush_file *self) {
};
}
-static lib9p_srv_stat_or_error flush_file_stat(struct flush_file *self, struct lib9p_srv_ctx *ctx) {
+lib9p_srv_stat_or_error flush_file_stat(struct flush_file *self, struct lib9p_srv_ctx *ctx) {
assert(self);
assert(ctx);
return ERROR_NEW_VAL(lib9p_srv_stat, ((struct lib9p_srv_stat){
@@ -47,20 +46,20 @@ static lib9p_srv_stat_or_error flush_file_stat(struct flush_file *self, struct l
.extension = lib9p_str(NULL),
}));
}
-static error flush_file_wstat(struct flush_file *self, struct lib9p_srv_ctx *ctx, struct lib9p_srv_stat) {
+error flush_file_wstat(struct flush_file *self, struct lib9p_srv_ctx *ctx, struct lib9p_srv_stat) {
assert(self);
assert(ctx);
return error_new(E_POSIX_EROFS, "cannot wstat API file");
}
-static error flush_file_remove(struct flush_file *self, struct lib9p_srv_ctx *ctx) {
+error flush_file_remove(struct flush_file *self, struct lib9p_srv_ctx *ctx) {
assert(self);
assert(ctx);
return error_new(E_POSIX_EROFS, "cannot remove API file");
}
-LIB9P_SRV_NOTDIR(struct flush_file, flush_file);
+LIB9P_SRV_NOTDIR(, struct flush_file, flush_file);
-static lib9p_srv_fio_or_error flush_file_fopen(struct flush_file *self, struct lib9p_srv_ctx *ctx, bool, bool, bool) {
+lib9p_srv_fio_or_error flush_file_fopen(struct flush_file *self, struct lib9p_srv_ctx *ctx, bool, bool, bool) {
assert(self);
assert(ctx);
@@ -77,7 +76,7 @@ static void flush_fio_iofree(struct flush_fio *self) {
free(self);
}
-static struct lib9p_qid flush_fio_qid(struct flush_fio *self) {
+static struct lib9p_qid flush_fio_ioqid(struct flush_fio *self) {
assert(self);
return flush_file_qid(self->parent);
}
@@ -94,8 +93,8 @@ static uint32_t_or_error flush_fio_pwrite(struct flush_fio *LM_UNUSED(self),
assert_notreached("not writable");
}
-static iovec_or_error flush_fio_pread(struct flush_fio *self, struct lib9p_srv_ctx *ctx,
- uint32_t byte_count, uint64_t LM_UNUSED(byte_offset)) {
+static error flush_fio_pread(struct flush_fio *self, struct lib9p_srv_ctx *ctx,
+ lo_interface io_writer dst, uint64_t LM_UNUSED(src_offset), uint32_t byte_count) {
assert(self);
assert(ctx);
@@ -115,14 +114,11 @@ static iovec_or_error flush_fio_pread(struct flush_fio *self, struct lib9p_srv_c
/* Return */
switch (self->parent->flush_behavior) {
case FLUSH_READ:
- return ERROR_NEW_VAL(iovec, ((struct iovec){
- .iov_base = "Sloth\n",
- .iov_len = 6 < byte_count ? 6 : byte_count,
- }));
+ return io_write(dst, "Sloth\n", 6 < byte_count ? 6 : byte_count).err;
case FLUSH_ERROR:
- return ERROR_NEW_ERR(iovec, error_new(E_POSIX_EAGAIN, "request canceled by flush"));
+ return error_new(E_POSIX_EAGAIN, "request canceled by flush");
case FLUSH_SILENT:
- return ERROR_NEW_ERR(iovec, error_new(E_POSIX_ECANCELED, "request canceled by flush"));
+ return error_new(E_POSIX_ECANCELED, "request canceled by flush");
default:
assert_notreached("invalid flush_behavior");
}
diff --git a/lib9p/tests/test_server/fs_shutdown.c b/lib9p/tests/test_server/fs_shutdown.c
index 079442e..22aca9e 100644
--- a/lib9p/tests/test_server/fs_shutdown.c
+++ b/lib9p/tests/test_server/fs_shutdown.c
@@ -8,20 +8,19 @@
#include "fs_shutdown.h"
-LO_IMPLEMENTATION_C(lib9p_srv_file, struct shutdown_file, shutdown_file, static);
+LO_IMPLEMENTATION_C(lib9p_srv_file, struct shutdown_file, shutdown_file);
struct shutdown_fio {
struct shutdown_file *parent;
};
-LO_IMPLEMENTATION_H(lib9p_srv_fio, struct shutdown_fio, shutdown_fio);
-LO_IMPLEMENTATION_C(lib9p_srv_fio, struct shutdown_fio, shutdown_fio, static);
+LO_IMPLEMENTATION_STATIC(lib9p_srv_fio, struct shutdown_fio, shutdown_fio);
/* srv_file *******************************************************************/
-static void shutdown_file_free(struct shutdown_file *self) {
+void shutdown_file_free(struct shutdown_file *self) {
assert(self);
}
-static struct lib9p_qid shutdown_file_qid(struct shutdown_file *self) {
+struct lib9p_qid shutdown_file_qid(struct shutdown_file *self) {
assert(self);
return (struct lib9p_qid){
.type = LIB9P_QT_FILE | LIB9P_QT_APPEND,
@@ -30,7 +29,7 @@ static struct lib9p_qid shutdown_file_qid(struct shutdown_file *self) {
};
}
-static lib9p_srv_stat_or_error shutdown_file_stat(struct shutdown_file *self, struct lib9p_srv_ctx *ctx) {
+lib9p_srv_stat_or_error shutdown_file_stat(struct shutdown_file *self, struct lib9p_srv_ctx *ctx) {
assert(self);
assert(ctx);
return ERROR_NEW_VAL(lib9p_srv_stat, ((struct lib9p_srv_stat){
@@ -46,20 +45,20 @@ static lib9p_srv_stat_or_error shutdown_file_stat(struct shutdown_file *self, st
.extension = lib9p_str(NULL),
}));
}
-static error shutdown_file_wstat(struct shutdown_file *self, struct lib9p_srv_ctx *ctx, struct lib9p_srv_stat) {
+error shutdown_file_wstat(struct shutdown_file *self, struct lib9p_srv_ctx *ctx, struct lib9p_srv_stat) {
assert(self);
assert(ctx);
return error_new(E_POSIX_EROFS, "cannot wstat API file");
}
-static error shutdown_file_remove(struct shutdown_file *self, struct lib9p_srv_ctx *ctx) {
+error shutdown_file_remove(struct shutdown_file *self, struct lib9p_srv_ctx *ctx) {
assert(self);
assert(ctx);
return error_new(E_POSIX_EROFS, "cannot remove API file");
}
-LIB9P_SRV_NOTDIR(struct shutdown_file, shutdown_file);
+LIB9P_SRV_NOTDIR(, struct shutdown_file, shutdown_file);
-static lib9p_srv_fio_or_error shutdown_file_fopen(struct shutdown_file *self, struct lib9p_srv_ctx *ctx, bool, bool, bool) {
+lib9p_srv_fio_or_error shutdown_file_fopen(struct shutdown_file *self, struct lib9p_srv_ctx *ctx, bool, bool, bool) {
assert(self);
assert(ctx);
@@ -76,7 +75,7 @@ static void shutdown_fio_iofree(struct shutdown_fio *self) {
free(self);
}
-static struct lib9p_qid shutdown_fio_qid(struct shutdown_fio *self) {
+static struct lib9p_qid shutdown_fio_ioqid(struct shutdown_fio *self) {
assert(self);
return shutdown_file_qid(self->parent);
}
@@ -97,7 +96,7 @@ static uint32_t_or_error shutdown_fio_pwrite(struct shutdown_fio *self, struct l
LO_CALL(LO_BOX(net_stream_listener, &self->parent->listeners[i]), close);
return ERROR_NEW_VAL(uint32_t, byte_count);
}
-static iovec_or_error shutdown_fio_pread(struct shutdown_fio *LM_UNUSED(self), struct lib9p_srv_ctx *LM_UNUSED(ctx),
- uint32_t LM_UNUSED(byte_count), uint64_t LM_UNUSED(byte_offset)) {
+static error shutdown_fio_pread(struct shutdown_fio *LM_UNUSED(self), struct lib9p_srv_ctx *LM_UNUSED(ctx),
+ lo_interface io_writer LM_UNUSED(dst), uint64_t LM_UNUSED(src_offset), uint32_t LM_UNUSED(count)) {
assert_notreached("not readable");
}
diff --git a/lib9p/tests/test_server/fs_whoami.c b/lib9p/tests/test_server/fs_whoami.c
index 3cc0683..5a8382a 100644
--- a/lib9p/tests/test_server/fs_whoami.c
+++ b/lib9p/tests/test_server/fs_whoami.c
@@ -12,15 +12,14 @@
#include "fs_whoami.h"
-LO_IMPLEMENTATION_C(lib9p_srv_file, struct whoami_file, whoami_file, static);
+LO_IMPLEMENTATION_C(lib9p_srv_file, struct whoami_file, whoami_file);
struct whoami_fio {
struct whoami_file *parent;
size_t buf_len;
char *buf;
};
-LO_IMPLEMENTATION_H(lib9p_srv_fio, struct whoami_fio, whoami_fio);
-LO_IMPLEMENTATION_C(lib9p_srv_fio, struct whoami_fio, whoami_fio, static);
+LO_IMPLEMENTATION_STATIC(lib9p_srv_fio, struct whoami_fio, whoami_fio);
size_t whoami_len(struct lib9p_srv_ctx *ctx) {
assert(ctx);
@@ -41,10 +40,10 @@ size_t whoami_len(struct lib9p_srv_ctx *ctx) {
/* srv_file *******************************************************************/
-static void whoami_file_free(struct whoami_file *self) {
+void whoami_file_free(struct whoami_file *self) {
assert(self);
}
-static struct lib9p_qid whoami_file_qid(struct whoami_file *self) {
+struct lib9p_qid whoami_file_qid(struct whoami_file *self) {
assert(self);
return (struct lib9p_qid){
.type = LIB9P_QT_FILE,
@@ -53,7 +52,7 @@ static struct lib9p_qid whoami_file_qid(struct whoami_file *self) {
};
}
-static lib9p_srv_stat_or_error whoami_file_stat(struct whoami_file *self, struct lib9p_srv_ctx *ctx) {
+lib9p_srv_stat_or_error whoami_file_stat(struct whoami_file *self, struct lib9p_srv_ctx *ctx) {
assert(self);
assert(ctx);
@@ -70,20 +69,20 @@ static lib9p_srv_stat_or_error whoami_file_stat(struct whoami_file *self, struct
.extension = lib9p_str(NULL),
}));
}
-static error whoami_file_wstat(struct whoami_file *self, struct lib9p_srv_ctx *ctx, struct lib9p_srv_stat) {
+error whoami_file_wstat(struct whoami_file *self, struct lib9p_srv_ctx *ctx, struct lib9p_srv_stat) {
assert(self);
assert(ctx);
return error_new(E_POSIX_EROFS, "cannot wstat API file");
}
-static error whoami_file_remove(struct whoami_file *self, struct lib9p_srv_ctx *ctx) {
+error whoami_file_remove(struct whoami_file *self, struct lib9p_srv_ctx *ctx) {
assert(self);
assert(ctx);
return error_new(E_POSIX_EROFS, "cannot remove API file");
}
-LIB9P_SRV_NOTDIR(struct whoami_file, whoami_file);
+LIB9P_SRV_NOTDIR(, struct whoami_file, whoami_file);
-static lib9p_srv_fio_or_error whoami_file_fopen(struct whoami_file *self, struct lib9p_srv_ctx *ctx, bool, bool, bool) {
+lib9p_srv_fio_or_error whoami_file_fopen(struct whoami_file *self, struct lib9p_srv_ctx *ctx, bool, bool, bool) {
assert(self);
assert(ctx);
@@ -104,7 +103,7 @@ static void whoami_fio_iofree(struct whoami_fio *self) {
free(self);
}
-static struct lib9p_qid whoami_fio_qid(struct whoami_fio *self) {
+static struct lib9p_qid whoami_fio_ioqid(struct whoami_fio *self) {
assert(self);
assert(self->parent);
return whoami_file_qid(self->parent);
@@ -121,8 +120,8 @@ static uint32_t_or_error whoami_fio_pwrite(struct whoami_fio *LM_UNUSED(self),
uint64_t LM_UNUSED(offset)) {
assert_notreached("not writable");
}
-static iovec_or_error whoami_fio_pread(struct whoami_fio *self, struct lib9p_srv_ctx *ctx,
- uint32_t byte_count, uint64_t byte_offset) {
+static error whoami_fio_pread(struct whoami_fio *self, struct lib9p_srv_ctx *ctx,
+ lo_interface io_writer dst, uint64_t byte_offset, uint32_t byte_count) {
assert(self);
assert(ctx);
@@ -135,15 +134,12 @@ static iovec_or_error whoami_fio_pread(struct whoami_fio *self, struct lib9p_srv
ctx->user->num, ctx->user->name.len, ctx->user->name.utf8);
if (byte_offset > (uint64_t)data_size)
- return ERROR_NEW_ERR(iovec, error_new(E_POSIX_EINVAL, "offset is past end-of-file length"));
+ return error_new(E_POSIX_EINVAL, "offset is past end-of-file length");
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;
- return ERROR_NEW_VAL(iovec, ((struct iovec){
- .iov_base = &self->buf[beg_off],
- .iov_len = end_off-beg_off,
- }));
+ return io_write(dst, &self->buf[beg_off], end_off-beg_off).err;
}
diff --git a/lib9p/tests/test_server/main.c b/lib9p/tests/test_server/main.c
index e28d19e..2519372 100644
--- a/lib9p/tests/test_server/main.c
+++ b/lib9p/tests/test_server/main.c
@@ -132,8 +132,7 @@ static COROUTINE init_cr(void *) {
}
struct tstlog_stdout {};
-LO_IMPLEMENTATION_H(fmt_dest, struct tstlog_stdout, tstlog_stdout);
-LO_IMPLEMENTATION_C(fmt_dest, struct tstlog_stdout, tstlog_stdout, static);
+LO_IMPLEMENTATION_STATIC(fmt_dest, struct tstlog_stdout, tstlog_stdout);
static size_t tstlog_bytes = 0;
diff --git a/lib9p_util/static.c b/lib9p_util/static.c
index 6861869..6071e03 100644
--- a/lib9p_util/static.c
+++ b/lib9p_util/static.c
@@ -9,21 +9,18 @@
#include <util9p/static.h>
-LO_IMPLEMENTATION_C(lib9p_srv_file, struct util9p_static_dir, util9p_static_dir, static);
-LO_IMPLEMENTATION_C(lib9p_srv_file, struct util9p_static_file, util9p_static_file, static);
+LO_IMPLEMENTATION_C(lib9p_srv_file, struct util9p_static_dir, util9p_static_dir);
+LO_IMPLEMENTATION_C(lib9p_srv_file, struct util9p_static_file, util9p_static_file);
-LO_IMPLEMENTATION_H(lib9p_srv_dio, struct util9p_static_dir, util9p_static_dir);
-LO_IMPLEMENTATION_C(lib9p_srv_dio, struct util9p_static_dir, util9p_static_dir, static);
-
-LO_IMPLEMENTATION_H(lib9p_srv_fio, struct util9p_static_file, util9p_static_file);
-LO_IMPLEMENTATION_C(lib9p_srv_fio, struct util9p_static_file, util9p_static_file, static);
+LO_IMPLEMENTATION_STATIC(lib9p_srv_dio, struct util9p_static_dir, util9p_static_dio);
+LO_IMPLEMENTATION_STATIC(lib9p_srv_fio, struct util9p_static_file, util9p_static_fio);
/* dir ************************************************************************/
-static void util9p_static_dir_free(struct util9p_static_dir *self) {
+void util9p_static_dir_free(struct util9p_static_dir *self) {
assert(self);
}
-static struct lib9p_qid util9p_static_dir_qid(struct util9p_static_dir *self) {
+struct lib9p_qid util9p_static_dir_qid(struct util9p_static_dir *self) {
assert(self);
return (struct lib9p_qid){
@@ -33,7 +30,7 @@ static struct lib9p_qid util9p_static_dir_qid(struct util9p_static_dir *self) {
};
}
-static lib9p_srv_stat_or_error util9p_static_dir_stat(struct util9p_static_dir *self, struct lib9p_srv_ctx *ctx) {
+lib9p_srv_stat_or_error util9p_static_dir_stat(struct util9p_static_dir *self, struct lib9p_srv_ctx *ctx) {
assert(self);
assert(ctx);
@@ -50,22 +47,22 @@ static lib9p_srv_stat_or_error util9p_static_dir_stat(struct util9p_static_dir *
.extension = lib9p_str(NULL),
}));
}
-static error util9p_static_dir_wstat(struct util9p_static_dir *self, struct lib9p_srv_ctx *ctx,
+error util9p_static_dir_wstat(struct util9p_static_dir *self, struct lib9p_srv_ctx *ctx,
struct lib9p_srv_stat) {
assert(self);
assert(ctx);
return error_new(E_POSIX_EROFS, "read-only part of filesystem");
}
-static error util9p_static_dir_remove(struct util9p_static_dir *self, struct lib9p_srv_ctx *ctx) {
+error util9p_static_dir_remove(struct util9p_static_dir *self, struct lib9p_srv_ctx *ctx) {
assert(self);
assert(ctx);
return error_new(E_POSIX_EROFS, "read-only part of filesystem");
}
-static lib9p_srv_file_or_error util9p_static_dir_dwalk(struct util9p_static_dir *self, struct lib9p_srv_ctx *ctx,
- struct lib9p_s childname) {
+lib9p_srv_file_or_error util9p_static_dir_dwalk(struct util9p_static_dir *self, struct lib9p_srv_ctx *ctx,
+ struct lib9p_s childname) {
assert(self);
assert(ctx);
@@ -81,7 +78,7 @@ static lib9p_srv_file_or_error util9p_static_dir_dwalk(struct util9p_static_dir
return ERROR_NEW_ERR(lib9p_srv_file, error_new(E_POSIX_ENOENT, "no such file or directory"));
}
-static lib9p_srv_file_or_error util9p_static_dir_dcreate(struct util9p_static_dir *self, struct lib9p_srv_ctx *ctx,
+lib9p_srv_file_or_error util9p_static_dir_dcreate(struct util9p_static_dir *self, struct lib9p_srv_ctx *ctx,
struct lib9p_s LM_UNUSED(childname),
struct lib9p_srv_userid *LM_UNUSED(user),
struct lib9p_srv_userid *LM_UNUSED(group),
@@ -92,17 +89,21 @@ static lib9p_srv_file_or_error util9p_static_dir_dcreate(struct util9p_static_di
return ERROR_NEW_ERR(lib9p_srv_file, error_new(E_POSIX_EROFS, "read-only part of filesystem"));
}
-LIB9P_SRV_NOTFILE(struct util9p_static_dir, util9p_static_dir);
+LIB9P_SRV_NOTFILE(, struct util9p_static_dir, util9p_static_dir);
-static lib9p_srv_dio_or_error util9p_static_dir_dopen(struct util9p_static_dir *self, struct lib9p_srv_ctx *ctx) {
+lib9p_srv_dio_or_error util9p_static_dir_dopen(struct util9p_static_dir *self, struct lib9p_srv_ctx *ctx) {
assert(self);
assert(ctx);
return ERROR_NEW_VAL(lib9p_srv_dio, LO_BOX(lib9p_srv_dio, self));
}
-static void util9p_static_dir_iofree(struct util9p_static_dir *self) {
+
+static struct lib9p_qid util9p_static_dio_ioqid(struct util9p_static_dir *self) {
+ return util9p_static_dir_qid(self);
+}
+static void util9p_static_dio_iofree(struct util9p_static_dir *self) {
assert(self);
}
-static lib9p_srv_dirent_or_error util9p_static_dir_dread(struct util9p_static_dir *self, struct lib9p_srv_ctx *ctx, size_t idx) {
+static lib9p_srv_dirent_or_error util9p_static_dio_dread(struct util9p_static_dir *self, struct lib9p_srv_ctx *ctx, size_t idx) {
assert(self);
assert(ctx);
@@ -123,10 +124,10 @@ static lib9p_srv_dirent_or_error util9p_static_dir_dread(struct util9p_static_di
/* file ***********************************************************************/
-static void util9p_static_file_free(struct util9p_static_file *self) {
+void util9p_static_file_free(struct util9p_static_file *self) {
assert(self);
}
-static struct lib9p_qid util9p_static_file_qid(struct util9p_static_file *self) {
+struct lib9p_qid util9p_static_file_qid(struct util9p_static_file *self) {
assert(self);
return (struct lib9p_qid){
@@ -148,7 +149,7 @@ static inline size_t util9p_static_file_size(struct util9p_static_file *file) {
}
-static lib9p_srv_stat_or_error util9p_static_file_stat(struct util9p_static_file *self, struct lib9p_srv_ctx *ctx) {
+lib9p_srv_stat_or_error util9p_static_file_stat(struct util9p_static_file *self, struct lib9p_srv_ctx *ctx) {
assert(self);
assert(ctx);
@@ -165,23 +166,23 @@ static lib9p_srv_stat_or_error util9p_static_file_stat(struct util9p_static_file
.extension = lib9p_str(NULL),
}));
}
-static error util9p_static_file_wstat(struct util9p_static_file *self, struct lib9p_srv_ctx *ctx,
+error util9p_static_file_wstat(struct util9p_static_file *self, struct lib9p_srv_ctx *ctx,
struct lib9p_srv_stat) {
assert(self);
assert(ctx);
return error_new(E_POSIX_EROFS, "read-only part of filesystem");
}
-static error util9p_static_file_remove(struct util9p_static_file *self, struct lib9p_srv_ctx *ctx) {
+error util9p_static_file_remove(struct util9p_static_file *self, struct lib9p_srv_ctx *ctx) {
assert(self);
assert(ctx);
return error_new(E_POSIX_EROFS, "read-only part of filesystem");
}
-LIB9P_SRV_NOTDIR(struct util9p_static_file, util9p_static_file);
+LIB9P_SRV_NOTDIR(, struct util9p_static_file, util9p_static_file);
-static lib9p_srv_fio_or_error util9p_static_file_fopen(struct util9p_static_file *self, struct lib9p_srv_ctx *ctx,
+lib9p_srv_fio_or_error util9p_static_file_fopen(struct util9p_static_file *self, struct lib9p_srv_ctx *ctx,
bool rd, bool wr, bool trunc) {
assert(self);
assert(ctx);
@@ -190,36 +191,37 @@ static lib9p_srv_fio_or_error util9p_static_file_fopen(struct util9p_static_file
assert(!trunc);
return ERROR_NEW_VAL(lib9p_srv_fio, LO_BOX(lib9p_srv_fio, self));
}
-static void util9p_static_file_iofree(struct util9p_static_file *self) {
+
+static struct lib9p_qid util9p_static_fio_ioqid(struct util9p_static_file *self) {
+ return util9p_static_file_qid(self);
+}
+static void util9p_static_fio_iofree(struct util9p_static_file *self) {
assert(self);
}
-static uint32_t util9p_static_file_iounit(struct util9p_static_file *self) {
+static uint32_t util9p_static_fio_iounit(struct util9p_static_file *self) {
assert(self);
return 0;
}
-static iovec_or_error util9p_static_file_pread(struct util9p_static_file *self, struct lib9p_srv_ctx *ctx,
- uint32_t byte_count, uint64_t byte_offset) {
+static error util9p_static_fio_pread(struct util9p_static_file *self, struct lib9p_srv_ctx *ctx,
+ lo_interface io_writer dst, uint64_t byte_offset, uint32_t byte_count) {
assert(self);
assert(ctx);
size_t data_size = util9p_static_file_size(self);
if (byte_offset > (uint64_t)data_size)
- return ERROR_NEW_ERR(iovec, error_new(E_POSIX_EINVAL, "offset is past end-of-file length"));
+ return error_new(E_POSIX_EINVAL, "offset is past end-of-file length");
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;
- return ERROR_NEW_VAL(iovec, ((struct iovec){
- .iov_base = &self->data_start[beg_off],
- .iov_len = end_off-beg_off,
- }));
+ return io_write(dst, &self->data_start[beg_off], end_off-beg_off).err;
}
-static uint32_t_or_error util9p_static_file_pwrite(struct util9p_static_file *self, struct lib9p_srv_ctx *ctx,
- void *LM_UNUSED(buf),
- uint32_t LM_UNUSED(byte_count),
- uint64_t LM_UNUSED(byte_offset)) {
+static uint32_t_or_error util9p_static_fio_pwrite(struct util9p_static_file *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);
diff --git a/libdhcp/tests/test_client.c b/libdhcp/tests/test_client.c
index cf76653..6446cef 100644
--- a/libdhcp/tests/test_client.c
+++ b/libdhcp/tests/test_client.c
@@ -18,10 +18,8 @@
struct test_udp {
};
-LO_IMPLEMENTATION_H(io_closer, struct test_udp, test_udp);
-LO_IMPLEMENTATION_C(io_closer, struct test_udp, test_udp, static);
-LO_IMPLEMENTATION_H(net_packet_conn, struct test_udp, test_udp);
-LO_IMPLEMENTATION_C(net_packet_conn, struct test_udp, test_udp, static);
+LO_IMPLEMENTATION_STATIC(io_closer, struct test_udp, test_udp);
+LO_IMPLEMENTATION_STATIC(net_packet_conn, struct test_udp, test_udp);
static error test_udp_sendto(struct test_udp *LM_UNUSED(self), void *LM_UNUSED(buf), size_t LM_UNUSED(len), struct net_ip4_addr LM_UNUSED(node), uint16_t LM_UNUSED(port)) {
static unsigned cnt = 0;
@@ -66,8 +64,7 @@ struct test_iface {
struct test_udp conn;
};
-LO_IMPLEMENTATION_H(net_iface, struct test_iface, test);
-LO_IMPLEMENTATION_C(net_iface, struct test_iface, test, static);
+LO_IMPLEMENTATION_STATIC(net_iface, struct test_iface, test);
static struct net_eth_addr test_hwaddr(struct test_iface *LM_UNUSED(self)) {
struct net_eth_addr ret = {{1, 2, 3, 4, 5, 6}};
diff --git a/libhw_cr/host_alarmclock.c b/libhw_cr/host_alarmclock.c
index 325f7e0..c1c5449 100644
--- a/libhw_cr/host_alarmclock.c
+++ b/libhw_cr/host_alarmclock.c
@@ -22,9 +22,9 @@
#include "host_util.h" /* for host_sigrt_alloc(), ns_to_host_ns_time() */
-LO_IMPLEMENTATION_C(alarmclock, struct hostclock, hostclock, static);
+LO_IMPLEMENTATION_C(alarmclock, struct hostclock, hostclock);
-static uint64_t hostclock_get_time_ns(struct hostclock *alarmclock) {
+uint64_t hostclock_get_time_ns(struct hostclock *alarmclock) {
assert(alarmclock);
struct timespec ts;
@@ -59,11 +59,11 @@ static void hostclock_handle_sig_alarm(int LM_UNUSED(sig), siginfo_t *info, void
}
}
-static bool hostclock_add_trigger(struct hostclock *alarmclock,
- struct alarmclock_trigger *trigger,
- uint64_t fire_at_ns,
- void (*cb)(void *),
- void *cb_arg) {
+bool hostclock_add_trigger(struct hostclock *alarmclock,
+ struct alarmclock_trigger *trigger,
+ uint64_t fire_at_ns,
+ void (*cb)(void *),
+ void *cb_arg) {
assert(alarmclock);
assert(trigger);
assert(fire_at_ns);
@@ -114,8 +114,8 @@ static bool hostclock_add_trigger(struct hostclock *alarmclock,
return false;
}
-static void hostclock_del_trigger(struct hostclock *alarmclock,
- struct alarmclock_trigger *trigger) {
+ void hostclock_del_trigger(struct hostclock *alarmclock,
+ struct alarmclock_trigger *trigger) {
assert(alarmclock);
assert(trigger);
diff --git a/libhw_cr/host_include/libhw/host_net.h b/libhw_cr/host_include/libhw/host_net.h
index a16ed01..6ff2779 100644
--- a/libhw_cr/host_include/libhw/host_net.h
+++ b/libhw_cr/host_include/libhw/host_net.h
@@ -13,13 +13,16 @@
#include <libhw/generic/net.h>
+/* TCP connection *************************************************************/
+
struct _hostnet_tcp_conn {
BEGIN_PRIVATE(LIBHW_HOST_NET_H);
int fd;
uint64_t read_deadline_ns;
END_PRIVATE(LIBHW_HOST_NET_H);
};
-LO_IMPLEMENTATION_H(net_stream_conn, struct _hostnet_tcp_conn, hostnet_tcp);
+
+/* TCP listener ***************************************************************/
struct hostnet_tcp_listener {
BEGIN_PRIVATE(LIBHW_HOST_NET_H);
@@ -27,16 +30,20 @@ struct hostnet_tcp_listener {
struct _hostnet_tcp_conn active_conn;
END_PRIVATE(LIBHW_HOST_NET_H);
};
+LO_IMPLEMENTATION_H(io_closer, struct hostnet_tcp_listener, hostnet_tcplist);
LO_IMPLEMENTATION_H(net_stream_listener, struct hostnet_tcp_listener, hostnet_tcplist);
void hostnet_tcp_listener_init(struct hostnet_tcp_listener *self, uint16_t port);
+/* UDP connection *************************************************************/
+
struct hostnet_udp_conn {
BEGIN_PRIVATE(LIBHW_HOST_NET_H);
int fd;
uint64_t read_deadline_ns;
END_PRIVATE(LIBHW_HOST_NET_H);
};
+LO_IMPLEMENTATION_H(io_closer, struct hostnet_udp_conn, hostnet_udp);
LO_IMPLEMENTATION_H(net_packet_conn, struct hostnet_udp_conn, hostnet_udp);
void hostnet_udp_conn_init(struct hostnet_udp_conn *self, uint16_t port);
diff --git a/libhw_cr/host_net.c b/libhw_cr/host_net.c
index 39bfd46..fe420c4 100644
--- a/libhw_cr/host_net.c
+++ b/libhw_cr/host_net.c
@@ -33,18 +33,18 @@
#include "host_util.h" /* for host_sigrt_alloc(), ns_to_host_us_time() */
-LO_IMPLEMENTATION_C(io_closer, struct hostnet_tcp_listener, hostnet_tcplist, static);
-LO_IMPLEMENTATION_C(net_stream_listener, struct hostnet_tcp_listener, hostnet_tcplist, static);
+LO_IMPLEMENTATION_C(io_closer, struct hostnet_tcp_listener, hostnet_tcplist);
+LO_IMPLEMENTATION_C(net_stream_listener, struct hostnet_tcp_listener, hostnet_tcplist);
-LO_IMPLEMENTATION_C(io_reader, struct _hostnet_tcp_conn, hostnet_tcp, static);
-LO_IMPLEMENTATION_C(io_writer, struct _hostnet_tcp_conn, hostnet_tcp, static);
-LO_IMPLEMENTATION_C(io_readwriter, struct _hostnet_tcp_conn, hostnet_tcp, static);
-LO_IMPLEMENTATION_C(io_closer, struct _hostnet_tcp_conn, hostnet_tcp, static);
-LO_IMPLEMENTATION_C(io_bidi_closer, struct _hostnet_tcp_conn, hostnet_tcp, static);
-LO_IMPLEMENTATION_C(net_stream_conn, struct _hostnet_tcp_conn, hostnet_tcp, static);
+LO_IMPLEMENTATION_STATIC(io_reader, struct _hostnet_tcp_conn, hostnet_tcp);
+LO_IMPLEMENTATION_STATIC(io_writer, struct _hostnet_tcp_conn, hostnet_tcp);
+LO_IMPLEMENTATION_STATIC(io_readwriter, struct _hostnet_tcp_conn, hostnet_tcp);
+LO_IMPLEMENTATION_STATIC(io_closer, struct _hostnet_tcp_conn, hostnet_tcp);
+LO_IMPLEMENTATION_STATIC(io_bidi_closer, struct _hostnet_tcp_conn, hostnet_tcp);
+LO_IMPLEMENTATION_STATIC(net_stream_conn, struct _hostnet_tcp_conn, hostnet_tcp);
-LO_IMPLEMENTATION_C(io_closer, struct hostnet_udp_conn, hostnet_udp, static);
-LO_IMPLEMENTATION_C(net_packet_conn, struct hostnet_udp_conn, hostnet_udp, static);
+LO_IMPLEMENTATION_C(io_closer, struct hostnet_udp_conn, hostnet_udp);
+LO_IMPLEMENTATION_C(net_packet_conn, struct hostnet_udp_conn, hostnet_udp);
/* common *********************************************************************/
@@ -172,7 +172,7 @@ static void *hostnet_pthread_accept(void *_args) {
return NULL;
}
-static net_stream_conn_or_error hostnet_tcplist_accept(struct hostnet_tcp_listener *listener) {
+net_stream_conn_or_error hostnet_tcplist_accept(struct hostnet_tcp_listener *listener) {
assert(listener);
int ret_connfd;
@@ -197,7 +197,7 @@ static net_stream_conn_or_error hostnet_tcplist_accept(struct hostnet_tcp_listen
/* TCP listener close() *******************************************************/
-static error hostnet_tcplist_close(struct hostnet_tcp_listener *listener) {
+error hostnet_tcplist_close(struct hostnet_tcp_listener *listener) {
assert(listener);
if (shutdown(listener->fd, SHUT_RDWR))
@@ -460,7 +460,7 @@ static void *hostnet_pthread_sendto(void *_args) {
return NULL;
}
-static error hostnet_udp_sendto(struct hostnet_udp_conn *conn, void *buf, size_t count,
+error hostnet_udp_sendto(struct hostnet_udp_conn *conn, void *buf, size_t count,
struct net_ip4_addr node, uint16_t port) {
assert(conn);
@@ -489,8 +489,8 @@ static error hostnet_udp_sendto(struct hostnet_udp_conn *conn, void *buf, size_t
/* UDP recvfrom() *************************************************************/
-static void hostnet_udp_set_recv_deadline(struct hostnet_udp_conn *conn,
- uint64_t ts_ns) {
+void hostnet_udp_set_recv_deadline(struct hostnet_udp_conn *conn,
+ uint64_t ts_ns) {
assert(conn);
conn->read_deadline_ns = ts_ns;
@@ -555,8 +555,8 @@ static void *hostnet_pthread_recvfrom(void *_args) {
return NULL;
}
-static size_t_or_error hostnet_udp_recvfrom(struct hostnet_udp_conn *conn, void *buf, size_t count,
- struct net_ip4_addr *ret_node, uint16_t *ret_port) {
+size_t_or_error hostnet_udp_recvfrom(struct hostnet_udp_conn *conn, void *buf, size_t count,
+ struct net_ip4_addr *ret_node, uint16_t *ret_port) {
assert(conn);
size_t ret_size;
@@ -593,7 +593,7 @@ static size_t_or_error hostnet_udp_recvfrom(struct hostnet_udp_conn *conn, void
/* UDP close() ****************************************************************/
-static error hostnet_udp_close(struct hostnet_udp_conn *conn) {
+error hostnet_udp_close(struct hostnet_udp_conn *conn) {
assert(conn);
if (close(conn->fd))
diff --git a/libhw_cr/rp2040_hwspi.c b/libhw_cr/rp2040_hwspi.c
index d717a79..f4ad956 100644
--- a/libhw_cr/rp2040_hwspi.c
+++ b/libhw_cr/rp2040_hwspi.c
@@ -28,8 +28,8 @@
#error config.h must define CONFIG_RP2040_SPI_DEBUG (bool)
#endif
-LO_IMPLEMENTATION_C(io_duplex_readwriter, struct rp2040_hwspi, rp2040_hwspi, static);
-LO_IMPLEMENTATION_C(spi, struct rp2040_hwspi, rp2040_hwspi, static);
+LO_IMPLEMENTATION_C(io_duplex_readwriter, struct rp2040_hwspi, rp2040_hwspi);
+LO_IMPLEMENTATION_C(spi, struct rp2040_hwspi, rp2040_hwspi);
static void rp2040_hwspi_intrhandler(void *_self, enum dmairq LM_UNUSED(irq), uint LM_UNUSED(channel)) {
struct rp2040_hwspi *self = _self;
@@ -136,7 +136,7 @@ void _rp2040_hwspi_init(struct rp2040_hwspi *self,
dmairq_set_and_enable_exclusive_handler(DMAIRQ_0, self->dma_rx_data, rp2040_hwspi_intrhandler, self);
}
-static size_t_and_error rp2040_hwspi_readwritev(struct rp2040_hwspi *self, const struct duplex_iovec *iov, int iovcnt) {
+size_t_and_error rp2040_hwspi_readwritev(struct rp2040_hwspi *self, const struct duplex_iovec *iov, int iovcnt) {
assert(self);
assert(self->inst);
assert(iov);
diff --git a/libhw_cr/rp2040_hwtimer.c b/libhw_cr/rp2040_hwtimer.c
index d9f0a24..3454383 100644
--- a/libhw_cr/rp2040_hwtimer.c
+++ b/libhw_cr/rp2040_hwtimer.c
@@ -27,8 +27,7 @@ struct rp2040_hwtimer {
bool initialized;
struct alarmclock_trigger *queue;
};
-LO_IMPLEMENTATION_H(alarmclock, struct rp2040_hwtimer, rp2040_hwtimer);
-LO_IMPLEMENTATION_C(alarmclock, struct rp2040_hwtimer, rp2040_hwtimer, static);
+LO_IMPLEMENTATION_STATIC(alarmclock, struct rp2040_hwtimer, rp2040_hwtimer);
/* Globals ********************************************************************/
diff --git a/libhw_cr/rp2040_include/libhw/w5500.h b/libhw_cr/rp2040_include/libhw/w5500.h
index 8dda1a1..43c58a3 100644
--- a/libhw_cr/rp2040_include/libhw/w5500.h
+++ b/libhw_cr/rp2040_include/libhw/w5500.h
@@ -41,19 +41,6 @@ struct _w5500_socket {
END_PRIVATE(LIBHW_W5500_H);
};
-LO_IMPLEMENTATION_H(io_closer, struct _w5500_socket, w5500_tcplist);
-LO_IMPLEMENTATION_H(net_stream_listener, struct _w5500_socket, w5500_tcplist);
-
-LO_IMPLEMENTATION_H(io_reader, struct _w5500_socket, w5500_tcp);
-LO_IMPLEMENTATION_H(io_writer, struct _w5500_socket, w5500_tcp);
-LO_IMPLEMENTATION_H(io_readwriter, struct _w5500_socket, w5500_tcp);
-LO_IMPLEMENTATION_H(io_closer, struct _w5500_socket, w5500_tcp);
-LO_IMPLEMENTATION_H(io_bidi_closer, struct _w5500_socket, w5500_tcp);
-LO_IMPLEMENTATION_H(net_stream_conn, struct _w5500_socket, w5500_tcp);
-
-LO_IMPLEMENTATION_H(io_closer, struct _w5500_socket, w5500_udp);
-LO_IMPLEMENTATION_H(net_packet_conn, struct _w5500_socket, w5500_udp);
-
struct w5500 {
BEGIN_PRIVATE(LIBHW_W5500_H);
/* const-after-init */
diff --git a/libhw_cr/w5500.c b/libhw_cr/w5500.c
index 594b391..c04cb14 100644
--- a/libhw_cr/w5500.c
+++ b/libhw_cr/w5500.c
@@ -127,20 +127,20 @@ static const char *w5500_state_str(uint8_t state) {
/* libmisc/obj.h **************************************************************/
-LO_IMPLEMENTATION_C(io_closer, struct _w5500_socket, w5500_tcplist, static);
-LO_IMPLEMENTATION_C(net_stream_listener, struct _w5500_socket, w5500_tcplist, static);
+LO_IMPLEMENTATION_STATIC(io_closer, struct _w5500_socket, w5500_tcplist);
+LO_IMPLEMENTATION_STATIC(net_stream_listener, struct _w5500_socket, w5500_tcplist);
-LO_IMPLEMENTATION_C(io_reader, struct _w5500_socket, w5500_tcp, static);
-LO_IMPLEMENTATION_C(io_writer, struct _w5500_socket, w5500_tcp, static);
-LO_IMPLEMENTATION_C(io_readwriter, struct _w5500_socket, w5500_tcp, static);
-LO_IMPLEMENTATION_C(io_closer, struct _w5500_socket, w5500_tcp, static);
-LO_IMPLEMENTATION_C(io_bidi_closer, struct _w5500_socket, w5500_tcp, static);
-LO_IMPLEMENTATION_C(net_stream_conn, struct _w5500_socket, w5500_tcp, static);
+LO_IMPLEMENTATION_STATIC(io_reader, struct _w5500_socket, w5500_tcp);
+LO_IMPLEMENTATION_STATIC(io_writer, struct _w5500_socket, w5500_tcp);
+LO_IMPLEMENTATION_STATIC(io_readwriter, struct _w5500_socket, w5500_tcp);
+LO_IMPLEMENTATION_STATIC(io_closer, struct _w5500_socket, w5500_tcp);
+LO_IMPLEMENTATION_STATIC(io_bidi_closer, struct _w5500_socket, w5500_tcp);
+LO_IMPLEMENTATION_STATIC(net_stream_conn, struct _w5500_socket, w5500_tcp);
-LO_IMPLEMENTATION_C(io_closer, struct _w5500_socket, w5500_udp, static);
-LO_IMPLEMENTATION_C(net_packet_conn, struct _w5500_socket, w5500_udp, static);
+LO_IMPLEMENTATION_STATIC(io_closer, struct _w5500_socket, w5500_udp);
+LO_IMPLEMENTATION_STATIC(net_packet_conn, struct _w5500_socket, w5500_udp);
-LO_IMPLEMENTATION_C(net_iface, struct w5500, w5500_if, static);
+LO_IMPLEMENTATION_C(net_iface, struct w5500, w5500_if);
/* mid-level utilities ********************************************************/
@@ -409,7 +409,7 @@ void w5500_soft_reset(struct w5500 *chip) {
cr_mutex_unlock(&chip->mu);
}
-static struct net_eth_addr w5500_if_hwaddr(struct w5500 *chip) {
+struct net_eth_addr w5500_if_hwaddr(struct w5500 *chip) {
assert(chip);
return chip->hwaddr;
@@ -427,7 +427,7 @@ static void _w5500_if_up(struct w5500 *chip, struct net_iface_config cfg) {
cr_mutex_unlock(&chip->mu);
}
-static void w5500_if_ifup(struct w5500 *chip, struct net_iface_config cfg) {
+void w5500_if_ifup(struct w5500 *chip, struct net_iface_config cfg) {
log_debugln("if_up()");
log_debugln(":: addr = ", (net_ip4_addr, cfg.addr));
log_debugln(":: gateway_addr = ", (net_ip4_addr, cfg.gateway_addr));
@@ -435,12 +435,12 @@ static void w5500_if_ifup(struct w5500 *chip, struct net_iface_config cfg) {
_w5500_if_up(chip, cfg);
}
-static void w5500_if_ifdown(struct w5500 *chip) {
+void w5500_if_ifdown(struct w5500 *chip) {
log_debugln("if_down()");
_w5500_if_up(chip, (struct net_iface_config){});
}
-static lo_interface net_stream_listener w5500_if_tcp_listen(struct w5500 *chip, uint16_t local_port) {
+lo_interface net_stream_listener w5500_if_tcp_listen(struct w5500 *chip, uint16_t local_port) {
assert(chip);
struct _w5500_socket *sock = w5500_alloc_socket(chip);
@@ -462,7 +462,7 @@ static lo_interface net_stream_listener w5500_if_tcp_listen(struct w5500 *chip,
return LO_BOX(net_stream_listener, sock);
}
-static net_stream_conn_or_error w5500_if_tcp_dial(struct w5500 *chip,
+net_stream_conn_or_error w5500_if_tcp_dial(struct w5500 *chip,
struct net_ip4_addr node, uint16_t port) {
assert(chip);
assert(memcmp(node.octets, net_ip4_addr_zero.octets, 4));
@@ -516,7 +516,7 @@ static net_stream_conn_or_error w5500_if_tcp_dial(struct w5500 *chip,
}
}
-static lo_interface net_packet_conn w5500_if_udp_conn(struct w5500 *chip, uint16_t local_port) {
+lo_interface net_packet_conn w5500_if_udp_conn(struct w5500 *chip, uint16_t local_port) {
assert(chip);
struct _w5500_socket *socket = w5500_alloc_socket(chip);
@@ -548,7 +548,7 @@ static lo_interface net_packet_conn w5500_if_udp_conn(struct w5500 *chip, uint16
return LO_BOX(net_packet_conn, socket);
}
-static bool w5500_if_arp_ping(struct w5500 *chip, struct net_ip4_addr addr) {
+bool w5500_if_arp_ping(struct w5500 *chip, struct net_ip4_addr addr) {
/* FIXME: This arp_ping implementation is really bad (and
* assumes that a UDP socket is open, which is "safe" because
* I only use it from inside of a DHCP client). */
diff --git a/libhw_generic/include/libhw/generic/io.h b/libhw_generic/include/libhw/generic/io.h
index ebbd6cb..c615932 100644
--- a/libhw_generic/include/libhw/generic/io.h
+++ b/libhw_generic/include/libhw/generic/io.h
@@ -7,14 +7,23 @@
#ifndef _LIBHW_GENERIC_IO_H_
#define _LIBHW_GENERIC_IO_H_
-#include <stddef.h> /* for size_t */
-#include <stdint.h> /* for uintptr_t */
+#include <stddef.h> /* for size_t */
+#include <stdint.h> /* for uintptr_t, uint{n}_t, UINT{n}_MAX */
#include <libmisc/error.h>
#include <libmisc/obj.h>
/* structs ********************************************************************/
+/* uoff_t ==========================================================*/
+
+typedef uint64_t uoff_t;
+#define UOFF_MAX UINT64_MAX
+DECLARE_ERROR_OR(uoff_t);
+DECLARE_ERROR_AND(uoff_t);
+
+/* iovec ===========================================================*/
+
#if __unix__
#include <sys/uio.h>
#else
@@ -23,6 +32,11 @@ struct iovec {
size_t iov_len;
};
#endif
+typedef struct iovec iovec;
+DECLARE_ERROR_OR(iovec);
+DECLARE_ERROR_AND(iovec);
+
+/* duplex_iovec ====================================================*/
#define IOVEC_DISCARD ((void*)(~((uintptr_t)0)))
@@ -36,8 +50,11 @@ struct duplex_iovec {
void *iov_write_from;
size_t iov_len;
};
+typedef struct duplex_iovec duplex_iovec;
+DECLARE_ERROR_OR(duplex_iovec);
+DECLARE_ERROR_AND(duplex_iovec);
-/* utilities ******************************************************************/
+/* iovec utilities ************************************************************/
/* If byte_max_cnt == 0, then there is no maximum. */
@@ -57,9 +74,27 @@ void io_slice_wr_to_duplex(struct duplex_iovec *dst, const struct iovec *src, in
/* basic interfaces ***********************************************************/
+/*
+ * Conventions:
+ *
+ * - Naming:
+ * + The "p"[osition] prefix means an explicit `uoff_t offset`,
+ * instead of using an internal cursor.
+ * + The "v"[ec] suffix means a sequence of iovecs, instead of a
+ * simple buf+len.
+ *
+ * - Errors:
+ * + Short *reads* are *not* errors (and so return size_t *or*
+ * error).
+ * + Short *writes* *are* errors (and so return size_t *and*
+ * (possibly-null) error).
+ */
+
+/* read ============================================================*/
+
/**
* Return bytes-read on success. A short read is *not* an error
- * (unlike writev).
+ * (unlike `write` methods).
*
* It is invalid to call readv when the sum length of iovecs is 0.
*/
@@ -70,7 +105,22 @@ LO_INTERFACE(io_reader);
#define io_read(r, buf, count) LO_CALL(r, readv, &((struct iovec){.iov_base = buf, .iov_len = count}), 1)
/**
- * Return bytes-written. A short write *is* an error (unlike readv)
+ * Returns bytes-read on success. A short read is *not* an error
+ * (unlike `write` methods).
+ *
+ * It is invalid to call preadv when the sum length of iovecs is 0.
+ */
+#define io_preader_LO_IFACE \
+ LO_FUNC(size_t_or_error, preadv, const struct iovec *iov, int iovcnt, uoff_t offset)
+LO_INTERFACE(io_preader);
+#define io_preadv(r, iov, iovcnt, off) LO_CALL(r, preadv, iov, iovcnt, off)
+#define io_pread(r, buf, count, off) LO_CALL(r, preadv, &((struct iovec){.iov_base = buf, .iov_len = count}), 1, off)
+
+/* write ===========================================================*/
+
+/**
+ * Return bytes-written. A short write *is* an error (unlike `read`
+ * methods).
*
* Writes are *not* guaranteed to be atomic, so if you have concurrent
* writers then you should arrange for a mutex to protect the writer.
@@ -83,21 +133,26 @@ LO_INTERFACE(io_writer);
#define io_writev(w, iov, iovcnt) LO_CALL(w, writev, iov, iovcnt)
#define io_write(w, buf, count) LO_CALL(w, writev, &((struct iovec){.iov_base = buf, .iov_len = count}), 1)
-#define io_closer_LO_IFACE \
- LO_FUNC(error, close)
-LO_INTERFACE(io_closer);
-#define io_close(c) LO_CALL(c, close)
+/**
+ * Returns bytes-written. A short write *is* an error (unlike `read`
+ * methods).
+ *
+ * Writes are *not* guaranteed to be atomic, so if you have concurrent
+ * writers then you should arrange for a mutex to protect the writer.
+ *
+ * It is invalid to call writev when the sum length of iovecs is 0.
+ */
+#define io_pwriter_LO_IFACE \
+ LO_FUNC(size_t_and_error, pwritev, const struct iovec *iov, int iovcnt, uoff_t offset)
+LO_INTERFACE(io_pwriter);
+#define io_pwritev(r, iov, iovcnt, off) LO_CALL(r, pwritev, iov, iovcnt, off)
+#define io_pwrite(r, buf, count, off) LO_CALL(r, pwritev, &((struct iovec){.iov_base = buf, .iov_len = count}), 1, off)
-#define io_bidi_closer_LO_IFACE \
- LO_NEST(io_closer) \
- LO_FUNC(error, close_read) \
- LO_FUNC(error, close_write)
-LO_INTERFACE(io_bidi_closer);
-#define io_close_read(c) LO_CALL(c, close_read)
-#define io_close_write(c) LO_CALL(c, close_write)
+/* readwrite =======================================================*/
/**
- * A short read/write *is* an error (like writev and unlike readv).
+ * A short read/write *is* an error (like `write` methods and unlike
+ * `read` methods).
*
* Reads/writes are *not* guaranteed to be atomic, so if you have
* concurrent readers/writers then you should arrange for a mutex to
@@ -108,10 +163,53 @@ LO_INTERFACE(io_bidi_closer);
#define io_duplex_readwriter_LO_IFACE \
LO_FUNC(size_t_and_error, readwritev, const struct duplex_iovec *iov, int iovcnt)
LO_INTERFACE(io_duplex_readwriter);
-
#define io_readwritev(rw, iov, iovcnt) \
LO_CALL(rw, readwritev, iov, iovcnt)
+/* read then write =================================================*/
+
+/**
+ * LO_CALL(src, pread_to, dst, src_offset, count) is functionally:
+ *
+ * size_t_and_error copy(lo_interface io_writer dst, lo_interface io_preader src, uoff_t src_offset, size_t count) {
+ * buf = malloc(count);
+ * size_t_or_error read_r = io_pread(src, buf, count, src_offset);
+ * if (read_r.is_err)
+ * return ERROR_AND(size_t, 0, read_r.err);
+ * size_t_and_error write_r = io_write(dst, buf, read_r.size_t);
+ * free(buf);
+ * return write_r;
+ * }
+ *
+ * except that it does not need to allocate a buffer. That is: It
+ * allows the reader to dump its data directly to the writer. This
+ * allows us to save a copy when the reader already has an in-memory
+ * buffer containing the data.
+ *
+ * The above code defines when a short read/write is an error or not.
+ *
+ * It must call writev() exactly 0-or-1 times, and if it does call it,
+ * then it must be called with exactly 1 iovec.
+ */
+#define io_preader_to_LO_IFACE \
+ LO_FUNC(size_t_and_error, pread_to, lo_interface io_writer dst, uoff_t src_offset, size_t count)
+LO_INTERFACE(io_preader_to);
+
+/* close ===========================================================*/
+
+#define io_closer_LO_IFACE \
+ LO_FUNC(error, close)
+LO_INTERFACE(io_closer);
+#define io_close(c) LO_CALL(c, close)
+
+#define io_bidi_closer_LO_IFACE \
+ LO_NEST(io_closer) \
+ LO_FUNC(error, close_read) \
+ LO_FUNC(error, close_write)
+LO_INTERFACE(io_bidi_closer);
+#define io_close_read(c) LO_CALL(c, close_read)
+#define io_close_write(c) LO_CALL(c, close_write)
+
/* aggregate interfaces *******************************************************/
#define io_readwriter_LO_IFACE \
diff --git a/libmisc/error.c b/libmisc/error.c
index dfe4e80..345755c 100644
--- a/libmisc/error.c
+++ b/libmisc/error.c
@@ -4,6 +4,8 @@
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
+#include <string.h> /* for strdup() */
+
#include <libmisc/error.h>
const char *error_msg(error err) {
@@ -12,6 +14,13 @@ const char *error_msg(error err) {
: _errnum_str_msg(err.num);
}
+error error_dup(error err) {
+ return (error){
+ .num = err.num,
+ ._msg = err._msg ? strdup(err._msg) : NULL,
+ };
+}
+
void error_cleanup(error *errptr) {
if (!errptr)
return;
diff --git a/libmisc/fmt.c b/libmisc/fmt.c
index 7c18ef5..175ad60 100644
--- a/libmisc/fmt.c
+++ b/libmisc/fmt.c
@@ -253,14 +253,14 @@ declare(16, 64);
/* fmt_buf ********************************************************************/
-LO_IMPLEMENTATION_C(fmt_dest, struct fmt_buf, fmt_buf, static);
+LO_IMPLEMENTATION_C(fmt_dest, struct fmt_buf, fmt_buf);
-static void fmt_buf_putb(struct fmt_buf *buf, uint8_t b) {
+void fmt_buf_putb(struct fmt_buf *buf, uint8_t b) {
if (buf->len < buf->cap)
((uint8_t *)(buf->dat))[buf->len] = b;
buf->len++;
}
-static size_t fmt_buf_tell(struct fmt_buf *buf) {
+size_t fmt_buf_tell(struct fmt_buf *buf) {
return buf->len;
}
diff --git a/libmisc/include/libmisc/alloc.h b/libmisc/include/libmisc/alloc.h
index afddbce..34becdb 100644
--- a/libmisc/include/libmisc/alloc.h
+++ b/libmisc/include/libmisc/alloc.h
@@ -23,4 +23,11 @@
#define heap_alloc(N, TYP) ((TYP *)calloc(N, sizeof(TYP)))
+static inline void heap_cleanup(void **ptrptr) {
+ if (!ptrptr)
+ return;
+ free(*ptrptr);
+ *ptrptr = NULL;
+}
+
#endif /* _LIBMISC_ALLOC_H_ */
diff --git a/libmisc/include/libmisc/error.h b/libmisc/include/libmisc/error.h
index 4110626..c9b53dd 100644
--- a/libmisc/include/libmisc/error.h
+++ b/libmisc/include/libmisc/error.h
@@ -136,6 +136,7 @@ typedef struct {
#define ERROR_IS_NULL(err) ((err).num == 0 && (err)._msg == NULL)
const char *error_msg(error err);
+error error_dup(error err);
void error_cleanup(error *errptr);
void fmt_print_error(lo_interface fmt_dest w, error err);
diff --git a/libmisc/include/libmisc/obj.h b/libmisc/include/libmisc/obj.h
index 3467d5b..c00e512 100644
--- a/libmisc/include/libmisc/obj.h
+++ b/libmisc/include/libmisc/obj.h
@@ -119,57 +119,62 @@
(_ARG_obj).vtable->_ARG_meth((_ARG_obj).self __VA_OPT__(,) __VA_ARGS__)
/**
- * Use `LO_IMPLEMENTATION_H(iface_name, impl_type, impl_name)` in a .h
- * file to declare that `{impl_type}` implements the `{iface_name}`
- * interface with functions named `{impl_name}_{method_name}`.
+ * `LO_IMPLEMENTATION_{H,C,STATIC}` declare that `{impl_type}`
+ * implements the `{iface_name}` interface with functions named
+ * `{impl_name}_{method_name}`.
*
- * This will also define a `lo_box_{impl_name}_as_{iface_name}(obj)`
- * const-expr macro.
+ * Either use _H and _C in the .h file and .c file respectively, or
+ * use _STATIC in just a .c file.
*
- * You must also call the LO_IMPLEMENTATION_C in a single .c file.
+ * These define:
+ * - The vtable symbol
+ * - The prototypes for the `{impl_name}_{method_name}` method
+ * functions.
+ * - A `lo_box_{impl_name}_as_{iface_name}(obj)` const-expr macro.
*/
-#define LO_IMPLEMENTATION_H(_ARG_iface_name, _ARG_impl_type, _ARG_impl_name) \
- /* Vtable. */ \
- extern const struct _lo_##_ARG_iface_name##_vtable \
- _lo_##_ARG_impl_name##_##_ARG_iface_name##_vtable; \
- /* Boxing. */ \
- LM_DEFAPPEND(_LO_REGISTRY_##_ARG_iface_name, \
- (_ARG_impl_type *, _ARG_impl_name)); \
- LM_DEFAPPEND(lo_box_##_ARG_impl_name##_as_##_ARG_iface_name(_ARG_self), ( \
- (lo_interface _ARG_iface_name){ \
- .self = (_ARG_self), \
- .vtable = &_lo_##_ARG_impl_name##_##_ARG_iface_name##_vtable, \
- } \
- )); \
+#define LO_IMPLEMENTATION_H( iface_name, impl_type, impl_name) _LO_IMPL_H(extern, iface_name, impl_type, impl_name)
+#define LO_IMPLEMENTATION_C( iface_name, impl_type, impl_name) _LO_IMPL_C(extern, iface_name, impl_type, impl_name)
+#define LO_IMPLEMENTATION_STATIC(iface_name, impl_type, impl_name) _LO_IMPL_H(static, iface_name, impl_type, impl_name); \
+ _LO_IMPL_C(static, iface_name, impl_type, impl_name)
+
+#define _LO_IMPL_H(_ARG_visibility, _ARG_iface_name, _ARG_impl_type, _ARG_impl_name) \
+ /* Vtable. */ \
+ _LO_h_vis_vtable_##_ARG_visibility const struct _lo_##_ARG_iface_name##_vtable \
+ _lo_##_ARG_impl_name##_##_ARG_iface_name##_vtable; \
+ /* Method prototypes. */ \
+ LM_FOREACH_TUPLE(_ARG_iface_name##_LO_IFACE, \
+ _LO_IMPL_PROTO, _LO_h_vis_fn_##_ARG_visibility, _ARG_impl_type, _ARG_impl_name) \
+ /* Boxing. */ \
+ LM_DEFAPPEND(_LO_REGISTRY_##_ARG_iface_name, \
+ (_ARG_impl_type *, _ARG_impl_name)); \
+ LM_DEFAPPEND(lo_box_##_ARG_impl_name##_as_##_ARG_iface_name(_ARG_self), ( \
+ (lo_interface _ARG_iface_name){ \
+ .self = (_ARG_self), \
+ .vtable = &_lo_##_ARG_impl_name##_##_ARG_iface_name##_vtable, \
+ } \
+ )); \
LM_FORCE_SEMICOLON
+#define _LO_h_vis_vtable_extern extern
+#define _LO_h_vis_vtable_static static
+#define _LO_h_vis_fn_extern
+#define _LO_h_vis_fn_static static
+
+#define _LO_IMPL_PROTO( _ARG_quals, _ARG_impl_type, _ARG_impl_name, _tuple_typ, ...) _LO_IMPL_PROTO_##_tuple_typ(_ARG_quals, _ARG_impl_type, _ARG_impl_name, __VA_ARGS__)
+#define _LO_IMPL_PROTO_lo_nest(_ARG_quals, _ARG_impl_type, _ARG_impl_name, _ARG_child_iface_name) /* empty */
+#define _LO_IMPL_PROTO_lo_func(_ARG_quals, _ARG_impl_type, _ARG_impl_name, _ARG_ret_type, _ARG_func_name, ...) _ARG_quals _ARG_ret_type _ARG_impl_name##_##_ARG_func_name(_ARG_impl_type * __VA_OPT__(,) __VA_ARGS__);
+
+#define _LO_IMPL_C(_ARG_visibility, _ARG_iface_name, _ARG_impl_type, _ARG_impl_name) \
+ /* Vtable. */ \
+ _LO_c_vis_vtable_##_ARG_visibility const struct _lo_##_ARG_iface_name##_vtable \
+ _lo_##_ARG_impl_name##_##_ARG_iface_name##_vtable = { \
+ LM_FOREACH_TUPLE(_ARG_iface_name##_LO_IFACE, \
+ _LO_IMPL_VTABLE, _ARG_impl_name) \
+ }; \
+ LM_FORCE_SEMICOLON
+#define _LO_c_vis_vtable_extern
+#define _LO_c_vis_vtable_static [[maybe_unused]] static
-/**
- * Use `LO_IMPLEMENTATION_C(iface_name, impl_type, impl_name[, static])` in a .c
- * file to declare that `{impl_type}` implements the `{iface_name}` interface
- * with functions named `{impl_name}_{method_name}`.
- *
- * You must also call the LO_IMPLEMENTATION_H in the corresponding .h file.
- *
- * If `iface_name` contains a nested interface, then the
- * implementation of the nested interfaces must be declared with
- * `LO_IMPLEMENTATION_C` first.
- */
-#define LO_IMPLEMENTATION_C(_ARG_iface_name, _ARG_impl_type, _ARG_impl_name, ...) \
- /* Method prototypes. */ \
- LM_FOREACH_TUPLE(_ARG_iface_name##_LO_IFACE, \
- _LO_IMPL_PROTO, _ARG_impl_type, _ARG_impl_name, __VA_ARGS__) \
- /* Vtable. */ \
- const struct _lo_##_ARG_iface_name##_vtable \
- _lo_##_ARG_impl_name##_##_ARG_iface_name##_vtable = { \
- LM_FOREACH_TUPLE(_ARG_iface_name##_LO_IFACE, \
- _LO_IMPL_VTABLE, _ARG_impl_name) \
- }
-
-#define _LO_IMPL_PROTO( _ARG_impl_type, _ARG_impl_name, _ARG_quals, _tuple_typ, ...) _LO_IMPL_PROTO_##_tuple_typ(_ARG_impl_type, _ARG_impl_name, _ARG_quals, __VA_ARGS__)
-#define _LO_IMPL_PROTO_lo_nest(_ARG_impl_type, _ARG_impl_name, _ARG_quals, _ARG_child_iface_name) /* empty */
-#define _LO_IMPL_PROTO_lo_func(_ARG_impl_type, _ARG_impl_name, _ARG_quals, _ARG_ret_type, _ARG_func_name, ...) _ARG_quals _ARG_ret_type _ARG_impl_name##_##_ARG_func_name(_ARG_impl_type * __VA_OPT__(,) __VA_ARGS__);
-
-#define _LO_IMPL_VTABLE(_ARG_impl_name, _tuple_typ, ...) _LO_IMPL_VTABLE_##_tuple_typ(_ARG_impl_name, __VA_ARGS__)
+#define _LO_IMPL_VTABLE( _ARG_impl_name, _tuple_typ, ...) _LO_IMPL_VTABLE_##_tuple_typ(_ARG_impl_name, __VA_ARGS__)
#define _LO_IMPL_VTABLE_lo_nest(_ARG_impl_name, _ARG_child_iface_name) ._lo_##_ARG_child_iface_name##_vtable = &_lo_##_ARG_impl_name##_##_ARG_child_iface_name##_vtable, LM_FOREACH_TUPLE2(_ARG_child_iface_name##_LO_IFACE, _LO_IMPL_VTABLE2, _ARG_impl_name)
#define _LO_IMPL_VTABLE_lo_func(_ARG_impl_name, _ARG_ret_type, _ARG_func_name, ...) ._ARG_func_name = (void*)_ARG_impl_name##_##_ARG_func_name,
diff --git a/libmisc/log.c b/libmisc/log.c
index 7e917c6..96e9ca4 100644
--- a/libmisc/log.c
+++ b/libmisc/log.c
@@ -12,8 +12,7 @@
#include <libmisc/log.h>
struct log_stdout {};
-LO_IMPLEMENTATION_H(fmt_dest, struct log_stdout, log_stdout);
-LO_IMPLEMENTATION_C(fmt_dest, struct log_stdout, log_stdout, static);
+LO_IMPLEMENTATION_STATIC(fmt_dest, struct log_stdout, log_stdout);
static size_t log_bytes = 0;
diff --git a/libmisc/tests/test_obj.c b/libmisc/tests/test_obj.c
index 687ad4e..c3c6786 100644
--- a/libmisc/tests/test_obj.c
+++ b/libmisc/tests/test_obj.c
@@ -28,19 +28,19 @@ LO_IMPLEMENTATION_H(frobber, struct myclass, myclass);
/* `struct myclass` implementation ********************************************/
-LO_IMPLEMENTATION_C(frobber, struct myclass, myclass, static);
+LO_IMPLEMENTATION_C(frobber, struct myclass, myclass);
-static int myclass_frob(struct myclass *self) {
+int myclass_frob(struct myclass *self) {
test_assert(self);
return self->a;
}
-static int myclass_frob1(struct myclass *self, int arg) {
+int myclass_frob1(struct myclass *self, int arg) {
test_assert(self);
return arg;
}
-static void myclass_frob0(struct myclass *self) {
+void myclass_frob0(struct myclass *self) {
test_assert(self);
}
diff --git a/libmisc/tests/test_obj_autobox.c b/libmisc/tests/test_obj_autobox.c
index 1110639..394f716 100644
--- a/libmisc/tests/test_obj_autobox.c
+++ b/libmisc/tests/test_obj_autobox.c
@@ -26,21 +26,15 @@ LO_INTERFACE(writer);
LO_NEST(writer)
LO_INTERFACE(read_writer);
-/* implementation header ******************************************************/
+/* implementation *************************************************************/
struct myclass {
size_t len;
char buf[512];
};
-LO_IMPLEMENTATION_H(reader, struct myclass, myclass);
-LO_IMPLEMENTATION_H(writer, struct myclass, myclass);
-LO_IMPLEMENTATION_H(read_writer, struct myclass, myclass);
-
-/* implementation main ********************************************************/
-
-LO_IMPLEMENTATION_C(reader, struct myclass, myclass, static);
-LO_IMPLEMENTATION_C(writer, struct myclass, myclass, static);
-LO_IMPLEMENTATION_C(read_writer, struct myclass, myclass, static);
+LO_IMPLEMENTATION_STATIC(reader, struct myclass, myclass);
+LO_IMPLEMENTATION_STATIC(writer, struct myclass, myclass);
+LO_IMPLEMENTATION_STATIC(read_writer, struct myclass, myclass);
static size_t myclass_read(struct myclass *self, void *buf, size_t count) {
test_assert(self);
diff --git a/libmisc/tests/test_obj_nest.c b/libmisc/tests/test_obj_nest.c
index 20ffe4a..b52cd7b 100644
--- a/libmisc/tests/test_obj_nest.c
+++ b/libmisc/tests/test_obj_nest.c
@@ -25,21 +25,15 @@ LO_INTERFACE(writer);
LO_NEST(writer)
LO_INTERFACE(read_writer);
-/* implementation header ******************************************************/
+/* implementation *************************************************************/
struct myclass {
size_t len;
char buf[512];
};
-LO_IMPLEMENTATION_H(reader, struct myclass, myclass);
-LO_IMPLEMENTATION_H(writer, struct myclass, myclass);
-LO_IMPLEMENTATION_H(read_writer, struct myclass, myclass);
-
-/* implementation main ********************************************************/
-
-LO_IMPLEMENTATION_C(reader, struct myclass, myclass, static);
-LO_IMPLEMENTATION_C(writer, struct myclass, myclass, static);
-LO_IMPLEMENTATION_C(read_writer, struct myclass, myclass, static);
+LO_IMPLEMENTATION_STATIC(reader, struct myclass, myclass);
+LO_IMPLEMENTATION_STATIC(writer, struct myclass, myclass);
+LO_IMPLEMENTATION_STATIC(read_writer, struct myclass, myclass);
static size_t myclass_read(struct myclass *self, void *buf, size_t count) {
test_assert(self);