summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CMakeLists.txt1
-rwxr-xr-xbuild-aux/stack.c.gen18
-rw-r--r--cmd/sbc_harness/main.c28
-rw-r--r--lib9p/include/lib9p/srv.h97
-rw-r--r--lib9p/srv.c91
-rwxr-xr-xlib9p/tests/runtest2
-rw-r--r--lib9p/tests/test_server/main.c179
-rw-r--r--lib9p_util/include/util9p/static.h12
-rw-r--r--lib9p_util/static.c177
-rw-r--r--libdhcp/dhcp_client.c53
-rw-r--r--libdhcp/include/libdhcp/client.h4
-rw-r--r--libhw/host_alarmclock.c68
-rw-r--r--libhw/host_include/libhw/host_alarmclock.h27
-rw-r--r--libhw/host_include/libhw/host_net.h11
-rw-r--r--libhw/host_net.c93
-rw-r--r--libhw/rp2040_hwspi.c13
-rw-r--r--libhw/rp2040_hwtimer.c46
-rw-r--r--libhw/rp2040_include/libhw/rp2040_hwspi.h5
-rw-r--r--libhw/rp2040_include/libhw/rp2040_hwtimer.h7
-rw-r--r--libhw/rp2040_include/libhw/w5500.h18
-rw-r--r--libhw/w5500.c172
-rw-r--r--libhw/w5500_ll.h17
-rw-r--r--libhw_generic/CMakeLists.txt3
-rw-r--r--libhw_generic/alarmclock.c9
-rw-r--r--libhw_generic/include/libhw/generic/alarmclock.h70
-rw-r--r--libhw_generic/include/libhw/generic/net.h188
-rw-r--r--libhw_generic/include/libhw/generic/spi.h16
-rw-r--r--libmisc/CMakeLists.txt5
-rw-r--r--libmisc/include/libmisc/macro.h41
-rw-r--r--libmisc/include/libmisc/vcall.h28
-rw-r--r--libmisc/tests/test_vcall.c74
-rw-r--r--libobj/CMakeLists.txt14
-rw-r--r--libobj/include/libobj/obj.h154
l---------libobj/tests/test.h1
-rw-r--r--libobj/tests/test_nest.c73
-rw-r--r--libobj/tests/test_obj.c61
36 files changed, 964 insertions, 912 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index ce1a942..b90d000 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -134,6 +134,7 @@ function(apply_matrix _m_arg_action _m_arg_matrix)
endfunction()
add_subdirectory(libmisc)
+add_subdirectory(libobj)
add_subdirectory(libcr)
add_subdirectory(libcr_ipc)
add_subdirectory(libhw_generic)
diff --git a/build-aux/stack.c.gen b/build-aux/stack.c.gen
index 2a23565..e327298 100755
--- a/build-aux/stack.c.gen
+++ b/build-aux/stack.c.gen
@@ -353,7 +353,7 @@ def main(
# The sbc-harness codebase #######################################
- vcalls: dict[str, set[str]] = {}
+ objcalls: dict[str, set[str]] = {}
re_vtable_start = re.compile(r"_vtable\s*=\s*\{")
re_vtable_entry = re.compile(r"^\s+\.(?P<meth>\S+)\s*=\s*(?P<impl>\S+),.*")
for fname in c_fnames:
@@ -367,9 +367,9 @@ def main(
impl = m.group("impl")
if impl == "NULL":
continue
- if m.group("meth") not in vcalls:
- vcalls[meth] = set()
- vcalls[meth].add(impl)
+ if m.group("meth") not in objcalls:
+ objcalls[meth] = set()
+ objcalls[meth].add(impl)
if "}" in line:
in_vtable = False
elif re_vtable_start.search(line):
@@ -401,15 +401,15 @@ def main(
typ = m.group("typ")
lib9p_msgs.add(typ)
- re_call_vcall = re.compile(r"VCALL\((?P<obj>[^,]+), (?P<meth>[^,)]+)[,)].*")
+ re_call_objcall = re.compile(r"LO_CALL\((?P<obj>[^,]+), (?P<meth>[^,)]+)[,)].*")
def sbc_indirect_callees(loc: str, line: str) -> list[str] | None:
if "/3rd-party/" in loc:
return None
- if m := re_call_vcall.fullmatch(line):
- if m.group("meth") in vcalls:
- return sorted(vcalls[m.group("meth")])
- return [f"__indirect_call:{m.group('obj')}->vtable->{m.group('meth')}"]
+ if m := re_call_objcall.fullmatch(line):
+ if m.group("meth") in objcalls:
+ return sorted(objcalls[m.group("meth")])
+ return [f"__indirect_call:{m.group('obj')}.vtable->{m.group('meth')}"]
if "trigger->cb(trigger->cb_arg)" in line:
return [
"alarmclock_sleep_intrhandler",
diff --git a/cmd/sbc_harness/main.c b/cmd/sbc_harness/main.c
index b17725d..ce80711 100644
--- a/cmd/sbc_harness/main.c
+++ b/cmd/sbc_harness/main.c
@@ -10,10 +10,11 @@
#include <hardware/flash.h> /* pico-sdk:hardware_flash: for flash_get_unique_id() */
#include <libcr/coroutine.h>
+#include <libhw/generic/alarmclock.h> /* so we can set `bootclock` */
#include <libhw/rp2040_hwspi.h>
+#include <libhw/rp2040_hwtimer.h>
#include <libhw/w5500.h>
#include <libmisc/hash.h>
-#include <libmisc/vcall.h>
#include <libusb/usb_common.h>
#include <libdhcp/client.h>
#include <lib9p/srv.h>
@@ -41,15 +42,6 @@ static COROUTINE hello_world_cr(void *_chan) {
cr_end();
}
-static COROUTINE dhcp_cr(void *_chip) {
- struct w5500 *chip = _chip;
- cr_begin();
-
- dhcp_client_main(chip, "harness");
-
- cr_end();
-}
-
struct {
struct rp2040_hwspi dev_spi;
struct w5500 dev_w5500;
@@ -58,11 +50,19 @@ struct {
struct lib9p_srv srv;
} globals;
+static COROUTINE dhcp_cr(void *) {
+ cr_begin();
+
+ dhcp_client_main(lo_box_w5500_if_as_net_iface(&globals.dev_w5500), "harness");
+
+ cr_end();
+}
+
static COROUTINE read9p_cr(void *) {
cr_begin();
lib9p_srv_read_cr(&globals.srv,
- VCALL(&globals.dev_w5500, tcp_listen, CONFIG_9P_PORT));
+ LO_CALL(lo_box_w5500_if_as_net_iface(&globals.dev_w5500), tcp_listen, CONFIG_9P_PORT));
cr_end();
}
@@ -94,7 +94,8 @@ COROUTINE init_cr(void *) {
19, /* PIN_MOSI */
18, /* PIN_CLK */
17); /* PIN_CS */
- w5500_init(&globals.dev_w5500, "W5500", &globals.dev_spi,
+ w5500_init(&globals.dev_w5500, "W5500",
+ lo_box_rp2040_hwspi_as_spi(&globals.dev_spi),
21, /* PIN_INTR */
20, /* PIN_RESET */
((struct net_eth_addr){{
@@ -117,7 +118,7 @@ COROUTINE init_cr(void *) {
coroutine_add("usb_common", usb_common_cr, NULL);
coroutine_add("usb_keyboard", usb_keyboard_cr, &globals.keyboard_chan);
coroutine_add("hello_world", hello_world_cr, &globals.keyboard_chan);
- coroutine_add_with_stack_size(4*1024, "dhcp", dhcp_cr, &globals.dev_w5500);
+ coroutine_add_with_stack_size(4*1024, "dhcp", dhcp_cr, NULL);
for (int i = 0; i < _CONFIG_9P_NUM_SOCKS; i++) {
char name[] = {'r', 'e', 'a', 'd', '-', hexdig[i], '\0'};
coroutine_add(name, read9p_cr, NULL);
@@ -131,6 +132,7 @@ COROUTINE init_cr(void *) {
}
int main() {
+ bootclock = rp2040_hwtimer(0);
stdio_uart_init();
/* char *hdr = "=" * (80-strlen("info : MAIN: ")); */
infof("===================================================================");
diff --git a/lib9p/include/lib9p/srv.h b/lib9p/include/lib9p/srv.h
index e9d2d7b..83aabc0 100644
--- a/lib9p/include/lib9p/srv.h
+++ b/lib9p/include/lib9p/srv.h
@@ -11,7 +11,9 @@
#include <libcr_ipc/rpc.h>
#include <libcr_ipc/chan.h>
#include <libhw/generic/net.h>
+#include <libmisc/assert.h>
#include <libmisc/private.h>
+#include <libobj/obj.h>
#include <lib9p/9p.h>
@@ -35,48 +37,53 @@ int lib9p_srv_acknowledge_flush(struct lib9p_srv_ctx *ctx);
/* interface definitions ******************************************************/
-struct lib9p_srv_file_vtable;
-
-typedef struct {
- struct lib9p_srv_file_vtable *vtable;
-} implements_lib9p_srv_file;
-
-struct lib9p_srv_file_vtable {
- /* all - resource management */
- void (*free )(implements_lib9p_srv_file *); /* must not error */
- struct lib9p_qid (*qid )(implements_lib9p_srv_file *); /* must not error */
- uint32_t (*chio )(implements_lib9p_srv_file *, struct lib9p_srv_ctx *,
- bool rd, bool wr, bool trunc);
-
- /* all - syscalls */
- struct lib9p_stat (*stat )(implements_lib9p_srv_file *, struct lib9p_srv_ctx *);
- void (*wstat )(implements_lib9p_srv_file *, struct lib9p_srv_ctx *,
- struct lib9p_stat new);
- void (*remove )(implements_lib9p_srv_file *, struct lib9p_srv_ctx *);
-
- /* directories - base */
- implements_lib9p_srv_file *(*dopen )(implements_lib9p_srv_file *, struct lib9p_srv_ctx *,
- struct lib9p_s childname);
- implements_lib9p_srv_file *(*dcreate)(implements_lib9p_srv_file *, struct lib9p_srv_ctx *,
- struct lib9p_s childname,
- lib9p_dm_t perm, lib9p_o_t flags);
-
- /* directories - once opened */
- size_t /* <- obj cnt */ (*dread )(implements_lib9p_srv_file *, struct lib9p_srv_ctx *,
- uint8_t *buf,
- uint32_t byte_count, /* <- num bytes */
- size_t obj_offset); /* <- starting at this object */
-
- /* non-directories - once opened */
- uint32_t (*pread )(implements_lib9p_srv_file *, struct lib9p_srv_ctx *,
- void *buf,
- uint32_t byte_count,
- uint64_t byte_offset);
- uint32_t (*pwrite )(implements_lib9p_srv_file *, struct lib9p_srv_ctx *,
- void *buf,
- uint32_t byte_count,
- uint64_t byte_offset);
-};
+#define lib9p_srv_file_LO_IFACE \
+ /* all - resource management */ \
+ LO_FUNC(void , free ) /* must not error */ \
+ LO_FUNC(struct lib9p_qid , qid ) /* must not error */ \
+ LO_FUNC(uint32_t , chio , struct lib9p_srv_ctx *, \
+ bool rd, bool wr, \
+ bool trunc) \
+ \
+ /* all - syscalls */ \
+ LO_FUNC(struct lib9p_stat , stat , struct lib9p_srv_ctx *) \
+ LO_FUNC(void , wstat , struct lib9p_srv_ctx *, \
+ struct lib9p_stat new) \
+ LO_FUNC(void , remove , struct lib9p_srv_ctx *) \
+ \
+ /* directories - base */ \
+ LO_FUNC(lo_interface lib9p_srv_file, dopen , struct lib9p_srv_ctx *, \
+ struct lib9p_s childname) \
+ LO_FUNC(lo_interface lib9p_srv_file, dcreate, struct lib9p_srv_ctx *, \
+ struct lib9p_s childname, \
+ lib9p_dm_t perm, \
+ lib9p_o_t flags) \
+ \
+ /* directories - once opened */ \
+ LO_FUNC(size_t /* <- obj cnt */ , dread , struct lib9p_srv_ctx *, \
+ uint8_t *buf, \
+ /* num bytes -> */ uint32_t byte_count, \
+ /* starting at this object -> */ size_t obj_offset) \
+ \
+ /* non-directories - once opened */ \
+ LO_FUNC(uint32_t , pread , struct lib9p_srv_ctx *, \
+ void *buf, \
+ uint32_t byte_count, \
+ uint64_t byte_offset) \
+ LO_FUNC(uint32_t , pwrite , struct lib9p_srv_ctx *, \
+ void *buf, \
+ uint32_t byte_count, \
+ uint64_t byte_offset)
+LO_INTERFACE(lib9p_srv_file)
+
+#define LIB9P_SRV_NOTDIR(TYP, NAM) \
+ static lo_interface lib9p_srv_file NAM##_dopen (TYP *, struct lib9p_srv_ctx *, struct lib9p_s) { assert_notreached("not a directory"); } \
+ static lo_interface lib9p_srv_file NAM##_dcreate(TYP *, struct lib9p_srv_ctx *, struct lib9p_s, lib9p_dm_t, lib9p_o_t) { assert_notreached("not a directory"); } \
+ static size_t NAM##_dread (TYP *, struct lib9p_srv_ctx *, uint8_t *, uint32_t, size_t) { assert_notreached("not a directory"); }
+
+#define LIB9P_SRV_NOTFILE(TYP, NAM) \
+ static uint32_t NAM##_pread (TYP *, struct lib9p_srv_ctx *, void *, uint32_t, uint64_t) { assert_notreached("not a file"); } \
+ static uint32_t NAM##_pwrite(TYP *, struct lib9p_srv_ctx *, void *, uint32_t, uint64_t) { assert_notreached("not a file"); }
/* main server entrypoints ****************************************************/
@@ -84,8 +91,8 @@ CR_RPC_DECLARE(_lib9p_srv_reqch, struct _lib9p_srv_req *, bool)
struct lib9p_srv {
/* Things you provide */
- void /*TODO*/ (*auth )(struct lib9p_srv_ctx *, struct lib9p_s treename); /* optional */
- implements_lib9p_srv_file *(*rootdir)(struct lib9p_srv_ctx *, struct lib9p_s treename);
+ void /*TODO*/ (*auth )(struct lib9p_srv_ctx *, struct lib9p_s treename); /* optional */
+ lo_interface lib9p_srv_file (*rootdir)(struct lib9p_srv_ctx *, struct lib9p_s treename);
/* For internal use */
BEGIN_PRIVATE(LIB9P_SRV_H)
@@ -105,7 +112,7 @@ struct lib9p_srv {
* @errno LINUX_ERANGE R-message does not fit into max_msg_size
*/
-[[noreturn]] void lib9p_srv_read_cr(struct lib9p_srv *srv, implements_net_stream_listener *listener);
+[[noreturn]] void lib9p_srv_read_cr(struct lib9p_srv *srv, lo_interface net_stream_listener listener);
COROUTINE lib9p_srv_write_cr(void *_srv);
#endif /* _LIB9P_SRV_H_ */
diff --git a/lib9p/srv.c b/lib9p/srv.c
index 47dd78d..0e58068 100644
--- a/lib9p/srv.c
+++ b/lib9p/srv.c
@@ -12,7 +12,6 @@
#include <libcr_ipc/mutex.h>
#include <libcr_ipc/select.h>
#include <libmisc/assert.h>
-#include <libmisc/vcall.h>
#include <libhw/generic/net.h>
#define LOG_NAME 9P_SRV
@@ -54,7 +53,7 @@ int lib9p_srv_acknowledge_flush(struct lib9p_srv_ctx *ctx) {
typedef typeof( ((struct lib9p_qid){}).path ) srv_path_t;
struct srv_pathinfo {
- implements_lib9p_srv_file *file;
+ lo_interface lib9p_srv_file file;
srv_path_t parent_dir;
/* References from other srv_pathinfos (via .parent_dir) or
@@ -108,7 +107,7 @@ struct _srv_fidinfo {
struct _srv_conn {
/* immutable */
struct lib9p_srv *parent_srv;
- implements_net_stream_conn *fd;
+ lo_interface net_stream_conn fd;
cid_t reader; /* the lib9p_srv_read_cr() coroutine */
/* mutable */
cr_mutex_t writelock;
@@ -197,7 +196,7 @@ static void respond_error(struct _lib9p_srv_req *req) {
&host, req->net_bytes);
cr_mutex_lock(&sess->parent_conn->writelock);
- r = VCALL(sess->parent_conn->fd, write,
+ r = LO_CALL(sess->parent_conn->fd, write,
req->net_bytes, uint32le_decode(req->net_bytes));
cr_mutex_unlock(&sess->parent_conn->writelock);
if (r < 0)
@@ -206,12 +205,12 @@ static void respond_error(struct _lib9p_srv_req *req) {
/* read coroutine *************************************************************/
-static bool read_at_least(implements_net_stream_conn *fd, uint8_t *buf, size_t goal, size_t *done) {
+static bool read_at_least(lo_interface net_stream_conn fd, uint8_t *buf, size_t goal, size_t *done) {
assert(buf);
assert(goal);
assert(done);
while (*done < goal) {
- ssize_t r = VCALL(fd, read, &buf[*done], CONFIG_9P_MAX_MSG_SIZE - *done);
+ ssize_t r = LO_CALL(fd, read, &buf[*done], CONFIG_9P_MAX_MSG_SIZE - *done);
if (r < 0) {
nonrespond_errorf("read: %s", net_strerror(-r));
return true;
@@ -227,12 +226,12 @@ static bool read_at_least(implements_net_stream_conn *fd, uint8_t *buf, size_t g
static void handle_message(struct _lib9p_srv_req *ctx);
-[[noreturn]] void lib9p_srv_read_cr(struct lib9p_srv *srv, implements_net_stream_listener *listener) {
+[[noreturn]] void lib9p_srv_read_cr(struct lib9p_srv *srv, lo_interface net_stream_listener listener) {
uint8_t buf[CONFIG_9P_MAX_MSG_SIZE];
assert(srv);
assert(srv->rootdir);
- assert(listener);
+ assert(!LO_IS_NULL(listener));
srv->readers++;
@@ -241,10 +240,10 @@ static void handle_message(struct _lib9p_srv_req *ctx);
for (;;) {
struct _srv_conn conn = {
.parent_srv = srv,
- .fd = VCALL(listener, accept),
+ .fd = LO_CALL(listener, accept),
.reader = cr_getcid(),
};
- if (!conn.fd) {
+ if (LO_IS_NULL(conn.fd)) {
nonrespond_errorf("accept: error");
srv->readers--;
if (srv->readers == 0)
@@ -305,12 +304,12 @@ static void handle_message(struct _lib9p_srv_req *ctx);
_lib9p_srv_reqch_send_req(&srv->_reqch, &req);
}
close:
- VCALL(conn.fd, close, true, sess.reqs.len == 0);
+ LO_CALL(conn.fd, close, true, sess.reqs.len == 0);
if (sess.reqs.len) {
sess.closing = true;
cr_pause_and_yield();
assert(sess.reqs.len == 0);
- VCALL(conn.fd, close, true, true);
+ LO_CALL(conn.fd, close, true, true);
}
}
}
@@ -438,8 +437,8 @@ static void handle_message(struct _lib9p_srv_req *ctx) {
goto write;
cr_mutex_lock(&ctx->parent_sess->parent_conn->writelock);
- VCALL(ctx->parent_sess->parent_conn->fd, write,
- ctx->net_bytes, uint32le_decode(ctx->net_bytes));
+ LO_CALL(ctx->parent_sess->parent_conn->fd, write,
+ ctx->net_bytes, uint32le_decode(ctx->net_bytes));
cr_mutex_unlock(&ctx->parent_sess->parent_conn->writelock);
}
}
@@ -470,15 +469,15 @@ static inline bool srv_util_check_perm(struct _lib9p_srv_req *ctx, struct lib9p_
* Returns a pointer to the stored pathinfo.
*/
static inline struct srv_pathinfo *srv_util_pathsave(struct _lib9p_srv_req *ctx,
- implements_lib9p_srv_file *file,
+ lo_interface lib9p_srv_file file,
srv_path_t parent_path) {
assert(ctx);
- assert(file);
+ assert(!LO_IS_NULL(file));
- struct lib9p_qid qid = VCALL(file, qid);
+ struct lib9p_qid qid = LO_CALL(file, qid);
struct srv_pathinfo *pathinfo = pathmap_load(&ctx->parent_sess->paths, qid.path);
if (pathinfo)
- assert(pathinfo->file == file);
+ assert(LO_EQ(pathinfo->file, file));
else {
pathinfo = pathmap_store(&ctx->parent_sess->paths, qid.path,
(struct srv_pathinfo){
@@ -512,14 +511,14 @@ static inline void srv_util_pathfree(struct _lib9p_srv_req *ctx, srv_path_t path
if (pathinfo->gc_refcount == 0) {
if (pathinfo->parent_dir != path)
srv_util_pathfree(ctx, pathinfo->parent_dir);
- VCALL(pathinfo->file, free);
+ LO_CALL(pathinfo->file, free);
pathmap_del(&ctx->parent_sess->paths, path);
}
}
static inline bool srv_util_pathisdir(struct srv_pathinfo *pathinfo) {
assert(pathinfo);
- return VCALL(pathinfo->file, qid).type & LIB9P_QT_DIR;
+ return LO_CALL(pathinfo->file, qid).type & LIB9P_QT_DIR;
}
/**
@@ -532,7 +531,7 @@ static inline struct _srv_fidinfo *srv_util_fidsave(struct _lib9p_srv_req *ctx,
assert(fid != LIB9P_FID_NOFID);
assert(pathinfo);
- struct lib9p_qid qid = VCALL(pathinfo->file, qid);
+ struct lib9p_qid qid = LO_CALL(pathinfo->file, qid);
struct _srv_fidinfo *fidinfo = fidmap_load(&ctx->parent_sess->fids, fid);
if (fidinfo) {
@@ -703,12 +702,12 @@ static void handle_Tattach(struct _lib9p_srv_req *ctx,
}
/* 1. File object */
- implements_lib9p_srv_file *root_file = srv->rootdir(&ctx->ctx, req->aname);
- assert((root_file == NULL) == lib9p_ctx_has_error(&ctx->ctx.basectx));
+ lo_interface lib9p_srv_file root_file = srv->rootdir(&ctx->ctx, req->aname);
+ assert(LO_IS_NULL(root_file) == lib9p_ctx_has_error(&ctx->ctx.basectx));
if (lib9p_ctx_has_error(&ctx->ctx.basectx))
return;
- struct lib9p_qid root_qid = VCALL(root_file, qid);
+ struct lib9p_qid root_qid = LO_CALL(root_file, qid);
assert(root_qid.type & LIB9P_QT_DIR);
/* 2. pathinfo */
@@ -770,37 +769,37 @@ static void handle_Twalk(struct _lib9p_srv_req *ctx,
break;
}
- implements_lib9p_srv_file *member_file = VCALL(pathinfo->file, dopen, &ctx->ctx, req->wname[resp->nwqid]);
- assert((member_file == NULL) == lib9p_ctx_has_error(&ctx->ctx.basectx));
+ lo_interface lib9p_srv_file member_file = LO_CALL(pathinfo->file, dopen, &ctx->ctx, req->wname[resp->nwqid]);
+ assert(LO_IS_NULL(member_file) == lib9p_ctx_has_error(&ctx->ctx.basectx));
if (lib9p_ctx_has_error(&ctx->ctx.basectx))
break;
- new_pathinfo = srv_util_pathsave(ctx, member_file, VCALL(pathinfo->file, qid).path);
+ new_pathinfo = srv_util_pathsave(ctx, member_file, LO_CALL(pathinfo->file, qid).path);
}
if (srv_util_pathisdir(new_pathinfo)) {
- struct lib9p_stat stat = VCALL(new_pathinfo->file, stat, &ctx->ctx);
+ struct lib9p_stat stat = LO_CALL(new_pathinfo->file, stat, &ctx->ctx);
if (lib9p_ctx_has_error(&ctx->ctx.basectx))
break;
lib9p_stat_assert(stat);
if (!srv_util_check_perm(ctx, &stat, 0b001)) {
lib9p_error(&ctx->ctx.basectx,
LINUX_EACCES, "you do not have execute permission on that directory");
- srv_util_pathfree(ctx, VCALL(new_pathinfo->file, qid).path);
+ srv_util_pathfree(ctx, LO_CALL(new_pathinfo->file, qid).path);
break;
}
}
- resp->wqid[resp->nwqid] = VCALL(new_pathinfo->file, qid);
+ resp->wqid[resp->nwqid] = LO_CALL(new_pathinfo->file, qid);
- srv_util_pathfree(ctx, VCALL(pathinfo->file, qid).path);
+ srv_util_pathfree(ctx, LO_CALL(pathinfo->file, qid).path);
pathinfo = new_pathinfo;
}
if (resp->nwqid == req->nwname) {
if (!srv_util_fidsave(ctx, req->newfid, pathinfo, req->newfid == req->fid))
- srv_util_pathfree(ctx, VCALL(pathinfo->file, qid).path);
+ srv_util_pathfree(ctx, LO_CALL(pathinfo->file, qid).path);
} else {
assert(lib9p_ctx_has_error(&ctx->ctx.basectx));
- srv_util_pathfree(ctx, VCALL(pathinfo->file, qid).path);
+ srv_util_pathfree(ctx, LO_CALL(pathinfo->file, qid).path);
if (resp->nwqid > 0)
lib9p_ctx_clear_error(&ctx->ctx.basectx);
}
@@ -844,7 +843,7 @@ static void handle_Topen(struct _lib9p_srv_req *ctx,
if (reqmode & LIB9P_O_RCLOSE) {
struct srv_pathinfo *parent = pathmap_load(&ctx->parent_sess->paths, pathinfo->parent_dir);
assert(parent);
- struct lib9p_stat parent_stat = VCALL(parent->file, stat, &ctx->ctx);
+ struct lib9p_stat parent_stat = LO_CALL(parent->file, stat, &ctx->ctx);
if (lib9p_ctx_has_error(&ctx->ctx.basectx))
return;
lib9p_stat_assert(parent_stat);
@@ -855,7 +854,7 @@ static void handle_Topen(struct _lib9p_srv_req *ctx,
}
fidflags = fidflags | FIDFLAG_RCLOSE;
}
- struct lib9p_stat stat = VCALL(pathinfo->file, stat, &ctx->ctx);
+ struct lib9p_stat stat = LO_CALL(pathinfo->file, stat, &ctx->ctx);
if (lib9p_ctx_has_error(&ctx->ctx.basectx))
return;
lib9p_stat_assert(stat);
@@ -893,10 +892,10 @@ static void handle_Topen(struct _lib9p_srv_req *ctx,
/* Actually make the call. */
if ((reqmode & LIB9P_O_TRUNC) || (rd && !pathinfo->rd_refcount) || (wr && !pathinfo->wr_refcount) ) {
- iounit = VCALL(pathinfo->file, chio, &ctx->ctx,
- fidflags & FIDFLAG_OPEN_R,
- fidflags & FIDFLAG_OPEN_W,
- reqmode & LIB9P_O_TRUNC);
+ iounit = LO_CALL(pathinfo->file, chio, &ctx->ctx,
+ fidflags & FIDFLAG_OPEN_R,
+ fidflags & FIDFLAG_OPEN_W,
+ reqmode & LIB9P_O_TRUNC);
if (lib9p_ctx_has_error(&ctx->ctx.basectx))
return;
}
@@ -962,7 +961,7 @@ static void handle_Tread(struct _lib9p_srv_req *ctx,
return;
}
/* Do it. */
- size_t num = VCALL(pathinfo->file, dread, &ctx->ctx, (uint8_t *)resp->data, req->count, idx);
+ size_t num = LO_CALL(pathinfo->file, dread, &ctx->ctx, (uint8_t *)resp->data, req->count, idx);
/* Translate object-count back to byte-count. */
uint32_t len = 0;
for (size_t i = 0; i < num; i++) {
@@ -975,7 +974,7 @@ static void handle_Tread(struct _lib9p_srv_req *ctx,
fidinfo->dir_idx = idx+num;
fidinfo->dir_off = req->offset + len;
} else
- resp->count = VCALL(pathinfo->file, pread, &ctx->ctx, resp->data, req->count, req->offset);
+ resp->count = LO_CALL(pathinfo->file, pread, &ctx->ctx, resp->data, req->count, req->offset);
}
static void handle_Twrite(struct _lib9p_srv_req *ctx,
@@ -1001,7 +1000,7 @@ static void handle_Twrite(struct _lib9p_srv_req *ctx,
assert(pathinfo);
/* Do it. */
- resp->count = VCALL(pathinfo->file, pwrite, &ctx->ctx, req->data, req->count, req->offset);
+ resp->count = LO_CALL(pathinfo->file, pwrite, &ctx->ctx, req->data, req->count, req->offset);
}
static void clunkremove(struct _lib9p_srv_req *ctx, lib9p_fid_t fid, bool remove) {
@@ -1024,17 +1023,17 @@ static void clunkremove(struct _lib9p_srv_req *ctx, lib9p_fid_t fid, bool remove
}
struct srv_pathinfo *parent = pathmap_load(&ctx->parent_sess->paths, pathinfo->parent_dir);
assert(parent);
- struct lib9p_stat parent_stat = VCALL(parent->file, stat, &ctx->ctx);
+ struct lib9p_stat parent_stat = LO_CALL(parent->file, stat, &ctx->ctx);
if (!srv_util_check_perm(ctx, &parent_stat, 0b010)) {
lib9p_error(&ctx->ctx.basectx,
LINUX_EACCES, "you do not have write permission on the parent directory");
goto clunk;
}
- VCALL(pathinfo->file, remove, &ctx->ctx);
+ LO_CALL(pathinfo->file, remove, &ctx->ctx);
}
clunk:
- srv_util_pathfree(ctx, VCALL(pathinfo->file, qid).path);
+ srv_util_pathfree(ctx, LO_CALL(pathinfo->file, qid).path);
fidmap_del(&ctx->parent_sess->fids, fid);
}
@@ -1069,7 +1068,7 @@ static void handle_Tstat(struct _lib9p_srv_req *ctx,
struct srv_pathinfo *pathinfo = pathmap_load(&ctx->parent_sess->paths, fidinfo->path);
assert(pathinfo);
- resp->stat = VCALL(pathinfo->file, stat, &ctx->ctx);
+ resp->stat = LO_CALL(pathinfo->file, stat, &ctx->ctx);
if (!lib9p_ctx_has_error(&ctx->ctx.basectx))
lib9p_stat_assert(resp->stat);
}
diff --git a/lib9p/tests/runtest b/lib9p/tests/runtest
index bb83a83..0966000 100755
--- a/lib9p/tests/runtest
+++ b/lib9p/tests/runtest
@@ -43,7 +43,7 @@ expect_lines \
out=$("${client[@]}" stat 'Documentation/x')
expect_lines \
- "'x' 'root' 'root' 'root' q (0000000000000008 1 ) m 0444 at 1728337905 mt 1728337904 l 4 t 0 d 0"
+ "'x' 'root' 'root' 'root' q (0000000000000001 1 ) m 0444 at 1728337905 mt 1728337904 l 4 t 0 d 0"
out=$("${client[@]}" write 'shutdown' <<<1)
expect_lines ''
diff --git a/lib9p/tests/test_server/main.c b/lib9p/tests/test_server/main.c
index 07fc74b..10f67a3 100644
--- a/lib9p/tests/test_server/main.c
+++ b/lib9p/tests/test_server/main.c
@@ -10,6 +10,7 @@
#include <libcr/coroutine.h>
#include <libhw/generic/net.h>
#include <libhw/generic/alarmclock.h>
+#include <libhw/host_alarmclock.h>
#include <libhw/host_net.h>
#include <libmisc/macro.h>
#include <util9p/static.h>
@@ -26,7 +27,7 @@
/* globals ********************************************************************/
-static implements_lib9p_srv_file *get_root(struct lib9p_srv_ctx *, struct lib9p_s);
+static lo_interface lib9p_srv_file get_root(struct lib9p_srv_ctx *, struct lib9p_s);
const char *hexdig = "0123456789abcdef";
@@ -42,31 +43,31 @@ struct {
/* api ************************************************************************/
struct api_file {
- implements_lib9p_srv_file;
-
uint64_t pathnum;
};
+LO_IMPLEMENTATION_H(lib9p_srv_file, struct api_file, api)
+LO_IMPLEMENTATION_C(lib9p_srv_file, struct api_file, api, static)
-static void api_free(implements_lib9p_srv_file *) {}
-static uint32_t api_chio(implements_lib9p_srv_file *, struct lib9p_srv_ctx *, bool, bool, bool) { return 0; }
-
-static void api_wstat(implements_lib9p_srv_file *, struct lib9p_srv_ctx *ctx, struct lib9p_stat) { lib9p_error(&ctx->basectx, LINUX_EROFS, "cannot wstat API file"); }
-static void api_remove(implements_lib9p_srv_file *, struct lib9p_srv_ctx *ctx) { lib9p_error(&ctx->basectx, LINUX_EROFS, "cannot remove API file"); }
-
-static struct lib9p_qid api_qid(implements_lib9p_srv_file *_self) {
- struct api_file *self = VCALL_SELF(struct api_file, implements_lib9p_srv_file, _self);
+static void api_free(struct api_file *self) {
+ assert(self);
+}
+static struct lib9p_qid api_qid(struct api_file *self) {
assert(self);
-
return (struct lib9p_qid){
.type = LIB9P_QT_FILE,
.vers = 1,
.path = self->pathnum,
};
}
-static struct lib9p_stat api_stat(implements_lib9p_srv_file *_self, struct lib9p_srv_ctx *) {
- struct api_file *self = VCALL_SELF(struct api_file, implements_lib9p_srv_file, _self);
+static uint32_t api_chio(struct api_file *self, struct lib9p_srv_ctx *ctx, bool, bool, bool) {
assert(self);
+ assert(ctx);
+ return 0;
+}
+static struct lib9p_stat api_stat(struct api_file *self, struct lib9p_srv_ctx *ctx) {
+ assert(self);
+ assert(ctx);
return (struct lib9p_stat){
.kern_type = 0,
.kern_dev = 0,
@@ -85,85 +86,89 @@ static struct lib9p_stat api_stat(implements_lib9p_srv_file *_self, struct lib9p
.file_last_modified_n_uid = 0,
};
}
-static uint32_t api_pwrite(implements_lib9p_srv_file *, struct lib9p_srv_ctx *, void *, uint32_t byte_count, uint64_t) {
+static void api_wstat(struct api_file *self, struct lib9p_srv_ctx *ctx, struct lib9p_stat) {
+ assert(self);
+ assert(ctx);
+ lib9p_error(&ctx->basectx, LINUX_EROFS, "cannot wstat API file");
+}
+static void api_remove(struct api_file *self, struct lib9p_srv_ctx *ctx) {
+ assert(self);
+ assert(ctx);
+ lib9p_error(&ctx->basectx, LINUX_EROFS, "cannot remove API file");
+}
+
+LIB9P_SRV_NOTDIR(struct api_file, api)
+
+static uint32_t api_pwrite(struct api_file *self, struct lib9p_srv_ctx *ctx, void *buf, uint32_t byte_count, uint64_t LM_UNUSED(offset)) {
+ assert(self);
+ assert(ctx);
+ assert(buf);
if (byte_count == 0)
return 0;
for (int i = 0; i < CONFIG_SRV9P_NUM_CONNS; i++)
- VCALL(&globals.listeners[i], close);
+ LO_CALL(lo_box_hostnet_tcplist_as_net_stream_listener(&globals.listeners[i]), close);
return byte_count;
}
-
-static struct lib9p_srv_file_vtable api_file_vtable = {
- .free = api_free,
- .qid = api_qid,
- .chio = api_chio,
-
- .stat = api_stat,
- .wstat = api_wstat,
- .remove = api_remove,
-
- .pread = NULL,
- .pwrite = api_pwrite,
-};
+static uint32_t api_pread(struct api_file *, struct lib9p_srv_ctx *, void *, uint32_t, uint64_t) {
+ assert_notreached("not readable");
+}
/* file tree ******************************************************************/
-#define FILE_COMMON(NAME) { \
- .vtable = &util9p_static_file_vtable, \
- \
- .u_name = "root", .u_num = 0, /* owner user */ \
- .g_name = "root", .g_num = 0, /* owner group */ \
- .m_name = "root", .m_num = 0, /* last-modified-by user */ \
- \
- .pathnum = __COUNTER__, \
- .name = NAME, \
- .perm = 0444, \
- .atime = 1728337905, \
- .mtime = 1728337904, \
- }
-
-#define DIR_COMMON(NAME) { \
- .vtable = &util9p_static_dir_vtable, \
- \
- .u_name = "root", .u_num = 0, /* owner user */ \
- .g_name = "root", .g_num = 0, /* owner group */ \
- .m_name = "root", .m_num = 0, /* last-modified-by user */ \
- \
- .pathnum = __COUNTER__, \
- .name = NAME, \
- .perm = 0555, \
- .atime = 1728337905, \
- .mtime = 1728337904, \
- }
-
-#define STATIC_FILE(STRNAME, SYMNAME) \
- &((static struct util9p_static_file){ \
- ._util9p_static_common = FILE_COMMON(STRNAME), \
- .data_start = _binary_static_##SYMNAME##_start, \
- .data_end = _binary_static_##SYMNAME##_end, \
+#define _box(nam, obj) \
+ ((struct lib9p_srv_file){ \
+ .self = obj, \
+ .vtable = (void*)&_lo_##nam##_lib9p_srv_file_vtable, \
})
-
-static struct util9p_static_dir root = {
- ._util9p_static_common = DIR_COMMON(""),
- .members = {
- &((static struct util9p_static_dir){
- ._util9p_static_common = DIR_COMMON("Documentation"),
- .members = {
- STATIC_FILE("x", Documentation_x),
- NULL
- },
- }),
- STATIC_FILE("README.md", README_md),
- &((struct api_file){
- .vtable = &api_file_vtable,
- .pathnum = __COUNTER__,
- }),
- NULL,
- },
-};
-
-static implements_lib9p_srv_file *get_root(struct lib9p_srv_ctx *LM_UNUSED(ctx), struct lib9p_s LM_UNUSED(treename)) {
- return &root;
+#define lo_box_util9p_static_file_as_lib9p_srv_file(obj) _box(util9p_static_file, obj)
+#define lo_box_util9p_static_dir_as_lib9p_srv_file(obj) _box(util9p_static_dir, obj)
+#define lo_box_api_as_lib9p_srv_file(obj) _box(api, obj)
+
+enum { PATH_BASE = __COUNTER__ };
+#define PATH_COUNTER __COUNTER__ - PATH_BASE
+
+#define STATIC_FILE(STRNAME, SYMNAME) \
+ lo_box_util9p_static_file_as_lib9p_srv_file(&((struct util9p_static_file){ \
+ ._util9p_static_common = { \
+ .u_name = "root", .u_num = 0, /* owner user */ \
+ .g_name = "root", .g_num = 0, /* owner group */ \
+ .m_name = "root", .m_num = 0, /* last-modified-by user */ \
+ \
+ .pathnum = PATH_COUNTER, \
+ .name = STRNAME, \
+ .perm = 0444, \
+ .atime = 1728337905, \
+ .mtime = 1728337904, \
+ }, \
+ .data_start = _binary_static_##SYMNAME##_start, \
+ .data_end = _binary_static_##SYMNAME##_end, \
+ }))
+
+#define STATIC_DIR(STRNAME, ...) \
+ lo_box_util9p_static_dir_as_lib9p_srv_file(&((struct util9p_static_dir){ \
+ ._util9p_static_common = { \
+ .u_name = "root", .u_num = 0, /* owner user */ \
+ .g_name = "root", .g_num = 0, /* owner group */ \
+ .m_name = "root", .m_num = 0, /* last-modified-by user */ \
+ \
+ .pathnum = PATH_COUNTER, \
+ .name = STRNAME, \
+ .perm = 0555, \
+ .atime = 1728337905, \
+ .mtime = 1728337904, \
+ }, \
+ .members = { __VA_ARGS__ __VA_OPT__(,) LO_NULL(lib9p_srv_file) }, \
+ }))
+
+struct lib9p_srv_file root =
+ STATIC_DIR("",
+ STATIC_DIR("Documentation",
+ STATIC_FILE("x", Documentation_x)),
+ STATIC_FILE("README.md", README_md),
+ lo_box_api_as_lib9p_srv_file(&(struct api_file){.pathnum = PATH_COUNTER}));
+
+static lo_interface lib9p_srv_file get_root(struct lib9p_srv_ctx *LM_UNUSED(ctx), struct lib9p_s LM_UNUSED(treename)) {
+ return root;
}
/* main ***********************************************************************/
@@ -174,7 +179,7 @@ static COROUTINE read_cr(void *_i) {
hostnet_tcp_listener_init(&globals.listeners[i], 9000);
- lib9p_srv_read_cr(&globals.srv, &globals.listeners[i]);
+ lib9p_srv_read_cr(&globals.srv, lo_box_hostnet_tcplist_as_net_stream_listener(&globals.listeners[i]));
cr_end();
}
@@ -199,6 +204,10 @@ static COROUTINE init_cr(void *) {
}
int main() {
+ struct hostclock clock_monotonic = {
+ .clock_id = CLOCK_MONOTONIC,
+ };
+ bootclock = lo_box_hostclock_as_alarmclock(&clock_monotonic);
coroutine_add("init", init_cr, NULL);
coroutine_main();
return 0;
diff --git a/lib9p_util/include/util9p/static.h b/lib9p_util/include/util9p/static.h
index 9ec03ef..4afdb51 100644
--- a/lib9p_util/include/util9p/static.h
+++ b/lib9p_util/include/util9p/static.h
@@ -1,6 +1,6 @@
/* util9p/static.h - Serve static files over 9P
*
- * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com>
+ * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
@@ -10,8 +10,6 @@
#include <lib9p/srv.h>
typedef struct {
- implements_lib9p_srv_file;
-
char *u_name;
uint32_t u_num;
char *g_name;
@@ -29,9 +27,9 @@ struct util9p_static_dir {
_util9p_static_common;
/* NULL-terminated */
- implements_lib9p_srv_file *members[];
+ lo_interface lib9p_srv_file members[];
};
-
+LO_IMPLEMENTATION_H(lib9p_srv_file, struct util9p_static_dir, util9p_static_dir);
struct util9p_static_file {
_util9p_static_common;
@@ -40,8 +38,6 @@ struct util9p_static_file {
char *data_end; /* may be NULL, in which case data_size is used */
size_t data_size; /* only used if .data_end==NULL */
};
-
-extern struct lib9p_srv_file_vtable util9p_static_dir_vtable;
-extern struct lib9p_srv_file_vtable util9p_static_file_vtable;
+LO_IMPLEMENTATION_H(lib9p_srv_file, struct util9p_static_file, util9p_static_file);
#endif /* _UTIL9P_STATIC_H_ */
diff --git a/lib9p_util/static.c b/lib9p_util/static.c
index 0f0efad..a6ea8f6 100644
--- a/lib9p_util/static.c
+++ b/lib9p_util/static.c
@@ -6,49 +6,18 @@
#include <libmisc/assert.h>
#include <libmisc/macro.h>
-#include <libmisc/vcall.h>
#include <util9p/static.h>
-/* common *********************************************************************/
+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);
-static void util9p_static_common_free(implements_lib9p_srv_file *_self) {
- _util9p_static_common *self = VCALL_SELF(_util9p_static_common, implements_lib9p_srv_file, _self);
- assert(self);
-
- /* do nothing */
-}
-
-static uint32_t util9p_static_common_chio(implements_lib9p_srv_file *_self, struct lib9p_srv_ctx *ctx,
- bool LM_UNUSED(rd), bool LM_UNUSED(wr), bool LM_UNUSED(trunc)) {
- _util9p_static_common *self = VCALL_SELF(_util9p_static_common, implements_lib9p_srv_file, _self);
- assert(self);
- assert(ctx);
-
- return 0;
-}
+/* dir ************************************************************************/
-static void util9p_static_common_wstat(implements_lib9p_srv_file *_self, struct lib9p_srv_ctx *ctx,
- struct lib9p_stat LM_UNUSED(new)) {
- _util9p_static_common *self = VCALL_SELF(_util9p_static_common, implements_lib9p_srv_file, _self);
+static void util9p_static_dir_free(struct util9p_static_dir *self) {
assert(self);
- assert(ctx);
-
- lib9p_error(&ctx->basectx, LINUX_EROFS, "read-only part of filesystem");
}
-
-static void util9p_static_common_remove(implements_lib9p_srv_file *_self, struct lib9p_srv_ctx *ctx) {
- _util9p_static_common *self = VCALL_SELF(_util9p_static_common, implements_lib9p_srv_file, _self);
- assert(self);
- assert(ctx);
-
- lib9p_error(&ctx->basectx, LINUX_EROFS, "read-only part of filesystem");
-}
-
-/* dir ************************************************************************/
-
-static struct lib9p_qid util9p_static_dir_qid(implements_lib9p_srv_file *_self) {
- struct util9p_static_dir *self = VCALL_SELF(struct util9p_static_dir, implements_lib9p_srv_file, _self);
+static struct lib9p_qid util9p_static_dir_qid(struct util9p_static_dir *self) {
assert(self);
return (struct lib9p_qid){
@@ -57,9 +26,13 @@ static struct lib9p_qid util9p_static_dir_qid(implements_lib9p_srv_file *_self)
.path = self->pathnum,
};
}
+static uint32_t util9p_static_dir_chio(struct util9p_static_dir *self, struct lib9p_srv_ctx *ctx, bool, bool, bool) {
+ assert(self);
+ assert(ctx);
+ return 0;
+}
-static struct lib9p_stat util9p_static_dir_stat(implements_lib9p_srv_file *_self, struct lib9p_srv_ctx *ctx) {
- struct util9p_static_dir *self = VCALL_SELF(struct util9p_static_dir, implements_lib9p_srv_file, _self);
+static struct lib9p_stat util9p_static_dir_stat(struct util9p_static_dir *self, struct lib9p_srv_ctx *ctx) {
assert(self);
assert(ctx);
@@ -81,51 +54,61 @@ static struct lib9p_stat util9p_static_dir_stat(implements_lib9p_srv_file *_self
.file_last_modified_n_uid = self->m_num,
};
}
+static void util9p_static_dir_wstat(struct util9p_static_dir *self, struct lib9p_srv_ctx *ctx,
+ struct lib9p_stat) {
+ assert(self);
+ assert(ctx);
-static implements_lib9p_srv_file *util9p_static_dir_dopen(implements_lib9p_srv_file *_self, struct lib9p_srv_ctx *ctx,
- struct lib9p_s childname) {
- struct util9p_static_dir *self = VCALL_SELF(struct util9p_static_dir, implements_lib9p_srv_file, _self);
+ lib9p_error(&ctx->basectx, LINUX_EROFS, "read-only part of filesystem");
+}
+static void util9p_static_dir_remove(struct util9p_static_dir *self, struct lib9p_srv_ctx *ctx) {
assert(self);
assert(ctx);
- for (size_t i = 0; self->members[i]; i++) {
- implements_lib9p_srv_file *filep = self->members[i];
- struct lib9p_stat stat = VCALL(filep, stat, ctx);
+ lib9p_error(&ctx->basectx, LINUX_EROFS, "read-only part of filesystem");
+}
+
+static lo_interface lib9p_srv_file util9p_static_dir_dopen(struct util9p_static_dir *self, struct lib9p_srv_ctx *ctx,
+ struct lib9p_s childname) {
+ assert(self);
+ assert(ctx);
+
+ for (size_t i = 0; !LO_IS_NULL(self->members[i]); i++) {
+ lo_interface lib9p_srv_file file = self->members[i];
+ struct lib9p_stat stat = LO_CALL(file, stat, ctx);
if (lib9p_ctx_has_error(&ctx->basectx))
break;
lib9p_stat_assert(stat);
if (lib9p_str_eq(stat.file_name, childname))
- return filep;
+ return file;
}
lib9p_error(&ctx->basectx,
LINUX_ENOENT, "no such file or directory");
- return NULL;
+ return LO_NULL(lib9p_srv_file);
}
-static implements_lib9p_srv_file *util9p_static_dir_dcreate(implements_lib9p_srv_file *_self, struct lib9p_srv_ctx *ctx,
- struct lib9p_s LM_UNUSED(childname),
- lib9p_dm_t LM_UNUSED(perm), lib9p_o_t LM_UNUSED(flags)) {
- struct util9p_static_dir *self = VCALL_SELF(struct util9p_static_dir, implements_lib9p_srv_file, _self);
+static lo_interface lib9p_srv_file util9p_static_dir_dcreate(struct util9p_static_dir *self, struct lib9p_srv_ctx *ctx,
+ struct lib9p_s LM_UNUSED(childname),
+ lib9p_dm_t LM_UNUSED(perm), lib9p_o_t LM_UNUSED(flags)) {
assert(self);
assert(ctx);
lib9p_error(&ctx->basectx, LINUX_EROFS, "read-only part of filesystem");
- return NULL;
+ return LO_NULL(lib9p_srv_file);
}
-static size_t util9p_static_dir_dread(implements_lib9p_srv_file *_self, struct lib9p_srv_ctx *ctx,
+static size_t util9p_static_dir_dread(struct util9p_static_dir *self, struct lib9p_srv_ctx *ctx,
uint8_t *buf,
uint32_t byte_count,
size_t _obj_offset) {
- struct util9p_static_dir *self = VCALL_SELF(struct util9p_static_dir, implements_lib9p_srv_file, _self);
assert(self);
assert(ctx);
uint32_t byte_offset = 0;
size_t obj_offset = _obj_offset;
- while (self->members[obj_offset]) {
- implements_lib9p_srv_file *filep = self->members[obj_offset];
- struct lib9p_stat stat = VCALL(filep, stat, ctx);
+ while (!LO_IS_NULL(self->members[obj_offset])) {
+ lo_interface lib9p_srv_file file = self->members[obj_offset];
+ struct lib9p_stat stat = LO_CALL(file, stat, ctx);
if (lib9p_ctx_has_error(&ctx->basectx))
break;
lib9p_stat_assert(stat);
@@ -143,22 +126,27 @@ static size_t util9p_static_dir_dread(implements_lib9p_srv_file *_self, struct l
return obj_offset - _obj_offset;
}
-struct lib9p_srv_file_vtable util9p_static_dir_vtable = {
- .free = util9p_static_common_free,
- .qid = util9p_static_dir_qid,
- .chio = util9p_static_common_chio,
-
- .stat = util9p_static_dir_stat,
- .wstat = util9p_static_common_wstat,
- .remove = util9p_static_common_remove,
+LIB9P_SRV_NOTFILE(struct util9p_static_dir, util9p_static_dir)
- .dopen = util9p_static_dir_dopen,
- .dcreate = util9p_static_dir_dcreate,
+/* file ***********************************************************************/
- .dread = util9p_static_dir_dread,
-};
+static 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) {
+ assert(self);
-/* file ***********************************************************************/
+ return (struct lib9p_qid){
+ .type = LIB9P_QT_FILE,
+ .vers = 1,
+ .path = self->pathnum,
+ };
+}
+static uint32_t util9p_static_file_chio(struct util9p_static_file *self, struct lib9p_srv_ctx *ctx, bool, bool, bool) {
+ assert(self);
+ assert(ctx);
+ return 0;
+}
static inline size_t util9p_static_file_size(struct util9p_static_file *file) {
assert(file);
@@ -171,19 +159,8 @@ static inline size_t util9p_static_file_size(struct util9p_static_file *file) {
return (size_t)((uintptr_t)file->data_end - (uintptr_t)file->data_start);
}
-static struct lib9p_qid util9p_static_file_qid(implements_lib9p_srv_file *_self) {
- struct util9p_static_file *self = VCALL_SELF(struct util9p_static_file, implements_lib9p_srv_file, _self);
- assert(self);
-
- return (struct lib9p_qid){
- .type = LIB9P_QT_FILE,
- .vers = 1,
- .path = self->pathnum,
- };
-}
-static struct lib9p_stat util9p_static_file_stat(implements_lib9p_srv_file *_self, struct lib9p_srv_ctx *ctx) {
- struct util9p_static_file *self = VCALL_SELF(struct util9p_static_file, implements_lib9p_srv_file, _self);
+static struct lib9p_stat util9p_static_file_stat(struct util9p_static_file *self, struct lib9p_srv_ctx *ctx) {
assert(self);
assert(ctx);
@@ -205,12 +182,26 @@ static struct lib9p_stat util9p_static_file_stat(implements_lib9p_srv_file *_sel
.file_last_modified_n_uid = self->m_num,
};
}
+static void util9p_static_file_wstat(struct util9p_static_file *self, struct lib9p_srv_ctx *ctx,
+ struct lib9p_stat) {
+ assert(self);
+ assert(ctx);
+
+ lib9p_error(&ctx->basectx, LINUX_EROFS, "read-only part of filesystem");
+}
+static void util9p_static_file_remove(struct util9p_static_file *self, struct lib9p_srv_ctx *ctx) {
+ assert(self);
+ assert(ctx);
-static uint32_t util9p_static_file_pread(implements_lib9p_srv_file *_self, struct lib9p_srv_ctx *ctx,
+ lib9p_error(&ctx->basectx, LINUX_EROFS, "read-only part of filesystem");
+}
+
+LIB9P_SRV_NOTDIR(struct util9p_static_file, util9p_static_file)
+
+static uint32_t util9p_static_file_pread(struct util9p_static_file *self, struct lib9p_srv_ctx *ctx,
void *buf,
uint32_t byte_count,
uint64_t byte_offset) {
- struct util9p_static_file *self = VCALL_SELF(struct util9p_static_file, implements_lib9p_srv_file, _self);
assert(self);
assert(ctx);
@@ -230,15 +221,13 @@ static uint32_t util9p_static_file_pread(implements_lib9p_srv_file *_self, struc
return (uint32_t)(end_off-beg_off);
}
-struct lib9p_srv_file_vtable util9p_static_file_vtable = {
- .free = util9p_static_common_free,
- .qid = util9p_static_file_qid,
- .chio = util9p_static_common_chio,
-
- .stat = util9p_static_file_stat,
- .wstat = util9p_static_common_wstat,
- .remove = util9p_static_common_remove,
+static uint32_t 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)) {
+ assert(self);
+ assert(ctx);
- .pread = util9p_static_file_pread,
- .pwrite = NULL,
-};
+ lib9p_error(&ctx->basectx, LINUX_EROFS, "read-only part of filesystem");
+ return 0;
+}
diff --git a/libdhcp/dhcp_client.c b/libdhcp/dhcp_client.c
index ea6ed28..e50c5f3 100644
--- a/libdhcp/dhcp_client.c
+++ b/libdhcp/dhcp_client.c
@@ -1,6 +1,6 @@
/* libdhcp/dhcp_client.c - A DHCP client
*
- * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com>
+ * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com>
* SPDX-License-Identifier: AGPL-3.0-or-later
*
* -----------------------------------------------------------------------------
@@ -87,7 +87,6 @@
#include <string.h> /* for strlen(), memcpy(), memset() */
#include <libmisc/rand.h>
-#include <libmisc/vcall.h>
#include <libhw/generic/alarmclock.h>
#define LOG_NAME DHCP
@@ -125,8 +124,8 @@ enum requirement {
struct dhcp_client {
/* Static. */
- implements_net_iface *iface;
- implements_net_packet_conn *sock;
+ lo_interface net_iface iface;
+ lo_interface net_packet_conn sock;
struct net_eth_addr self_eth_addr;
char *self_hostname;
size_t self_id_len;
@@ -313,7 +312,7 @@ static bool dhcp_client_send(struct dhcp_client *client, uint8_t msgtyp, const c
switch (msgtyp) {
case DHCP_MSGTYP_DISCOVER: case DHCP_MSGTYP_INFORM:
case DHCP_MSGTYP_REQUEST:
- secs = (VCALL(bootclock, get_time_ns) - client->time_ns_init)/NS_PER_S;
+ secs = (LO_CALL(bootclock, get_time_ns) - client->time_ns_init)/NS_PER_S;
if (!secs)
/* systemd's sd-dhcp-client.c asserts that some
* servers are broken and require .secs to be
@@ -462,7 +461,7 @@ static bool dhcp_client_send(struct dhcp_client *client, uint8_t msgtyp, const c
* Send *
\**********************************************************************/
debugf("state %s: sending DHCP %s", state_strs[client->state], dhcp_msgtyp_str(msgtyp));
- ssize_t r = VCALL(client->sock, sendto, scratch_msg, DHCP_MSG_BASE_SIZE + optlen,
+ ssize_t r = LO_CALL(client->sock, sendto, scratch_msg, DHCP_MSG_BASE_SIZE + optlen,
client_broadcasts ? net_ip4_addr_broadcast : client->lease_server_id, DHCP_PORT_SERVER);
if (r < 0) {
debugf("error: sendto: %zd", r);
@@ -579,7 +578,7 @@ static ssize_t dhcp_client_recv(struct dhcp_client *client, struct dhcp_recv_msg
assert(client);
ignore:
- msg_len = VCALL(client->sock, recvfrom, &ret->raw, sizeof(ret->raw), &srv_addr, &srv_port);
+ msg_len = LO_CALL(client->sock, recvfrom, &ret->raw, sizeof(ret->raw), &srv_addr, &srv_port);
if (msg_len < 0)
/* msg_len is -errno */
return msg_len;
@@ -702,9 +701,9 @@ static ssize_t dhcp_client_recv(struct dhcp_client *client, struct dhcp_recv_msg
}
/** @return true if there's a conflict, false if the addr appears to be unused */
-static bool dhcp_check_conflict(implements_net_packet_conn *sock, struct net_ip4_addr addr) {
- assert(sock);
- ssize_t v = VCALL(sock, sendto, "CHECK_IP_CONFLICT", 17, addr, 5000);
+static bool dhcp_check_conflict(lo_interface net_packet_conn sock, struct net_ip4_addr addr) {
+ assert(!LO_IS_NULL(sock));
+ ssize_t v = LO_CALL(sock, sendto, "CHECK_IP_CONFLICT", 17, addr, 5000);
debugf("check_ip_conflict => %zd", v);
return v != -NET_EARP_TIMEOUT;
}
@@ -745,7 +744,7 @@ static void dhcp_client_take_lease(struct dhcp_client *client, struct dhcp_recv_
infof(":: addr = "PRI_net_ip4_addr, ARG_net_ip4_addr(client->lease_client_addr));
infof(":: gateway_addr = "PRI_net_ip4_addr, ARG_net_ip4_addr(client->lease_auxdata.gateway_addr));
infof(":: subnet_mask = "PRI_net_ip4_addr, ARG_net_ip4_addr(client->lease_auxdata.subnet_mask));
- VCALL(client->iface, ifup, (struct net_iface_config){
+ LO_CALL(client->iface, ifup, (struct net_iface_config){
.addr = client->lease_client_addr,
.gateway_addr = client->lease_auxdata.gateway_addr,
.subnet_mask = client->lease_auxdata.subnet_mask,
@@ -772,11 +771,11 @@ static void dhcp_client_setstate(struct dhcp_client *client,
switch (client->state) {
case STATE_INIT:
client->xid = rand_uint63n(UINT32_MAX);
- client->time_ns_init = VCALL(bootclock, get_time_ns);
+ client->time_ns_init = LO_CALL(bootclock, get_time_ns);
dhcp_client_setstate(client, STATE_SELECTING, DHCP_MSGTYP_DISCOVER, NULL, scratch_msg);
break;
case STATE_SELECTING:
- VCALL(client->sock, set_read_deadline, client->time_ns_init+CONFIG_DHCP_SELECTING_NS);
+ LO_CALL(client->sock, set_read_deadline, client->time_ns_init+CONFIG_DHCP_SELECTING_NS);
switch ((r = dhcp_client_recv(client, scratch_msg))) {
case 0:
switch (scratch_msg->option_dat[scratch_msg->options[DHCP_OPT_DHCP_MSG_TYPE].off]) {
@@ -798,7 +797,7 @@ static void dhcp_client_setstate(struct dhcp_client *client,
}
break;
case STATE_REQUESTING:
- VCALL(client->sock, set_read_deadline, 0);
+ LO_CALL(client->sock, set_read_deadline, 0);
switch ((r = dhcp_client_recv(client, scratch_msg))) {
case 0:
switch (scratch_msg->option_dat[scratch_msg->options[DHCP_OPT_DHCP_MSG_TYPE].off]) {
@@ -825,7 +824,7 @@ static void dhcp_client_setstate(struct dhcp_client *client,
}
break;
case STATE_BOUND:
- VCALL(client->sock, set_read_deadline, client->lease_time_ns_t1);
+ LO_CALL(client->sock, set_read_deadline, client->lease_time_ns_t1);
switch ((r = dhcp_client_recv(client, scratch_msg))) {
case 0:
/* discard */
@@ -840,14 +839,14 @@ static void dhcp_client_setstate(struct dhcp_client *client,
break;
case STATE_RENEWING:
client->xid = rand_uint63n(UINT32_MAX);
- client->time_ns_init = VCALL(bootclock, get_time_ns);
+ client->time_ns_init = LO_CALL(bootclock, get_time_ns);
- VCALL(client->sock, set_read_deadline, client->lease_time_ns_t2);
+ LO_CALL(client->sock, set_read_deadline, client->lease_time_ns_t2);
switch ((r = dhcp_client_recv(client, scratch_msg))) {
case 0:
switch (scratch_msg->option_dat[scratch_msg->options[DHCP_OPT_DHCP_MSG_TYPE].off]) {
case DHCP_MSGTYP_NAK:
- VCALL(client->iface, ifdown);
+ LO_CALL(client->iface, ifdown);
dhcp_client_setstate(client, STATE_INIT, 0, NULL, scratch_msg);
break;
case DHCP_MSGTYP_ACK:
@@ -868,12 +867,12 @@ static void dhcp_client_setstate(struct dhcp_client *client,
}
break;
case STATE_REBINDING:
- VCALL(client->sock, set_read_deadline, client->lease_time_ns_end);
+ LO_CALL(client->sock, set_read_deadline, client->lease_time_ns_end);
switch ((r = dhcp_client_recv(client, scratch_msg))) {
case 0:
switch (scratch_msg->option_dat[scratch_msg->options[DHCP_OPT_DHCP_MSG_TYPE].off]) {
case DHCP_MSGTYP_NAK:
- VCALL(client->iface, ifdown);
+ LO_CALL(client->iface, ifdown);
dhcp_client_setstate(client, STATE_BOUND, 0, NULL, scratch_msg);
break;
case DHCP_MSGTYP_ACK:
@@ -885,7 +884,7 @@ static void dhcp_client_setstate(struct dhcp_client *client,
}
break;
case -NET_ERECV_TIMEOUT:
- VCALL(client->iface, ifdown);
+ LO_CALL(client->iface, ifdown);
dhcp_client_setstate(client, STATE_BOUND, 0, NULL, scratch_msg);
break;
default:
@@ -902,9 +901,9 @@ static void dhcp_client_setstate(struct dhcp_client *client,
}
}
-[[noreturn]] void dhcp_client_main(implements_net_iface *iface,
+[[noreturn]] void dhcp_client_main(lo_interface net_iface iface,
char *self_hostname) {
- assert(iface);
+ assert(!LO_IS_NULL(iface));
/* Even though a client ID is optional and not meaningful for
* us (the best we can do is to duplicate .chaddr), systemd's
@@ -912,14 +911,14 @@ static void dhcp_client_setstate(struct dhcp_client *client,
* require it to be set. */
struct {uint8_t typ; struct net_eth_addr dat;} client_id = {
DHCP_HTYPE_ETHERNET,
- VCALL(iface, hwaddr),
+ LO_CALL(iface, hwaddr),
};
struct dhcp_client client = {
/* Static. */
.iface = iface,
- .sock = VCALL(iface, udp_conn, DHCP_PORT_CLIENT),
- .self_eth_addr = VCALL(iface, hwaddr),
+ .sock = LO_CALL(iface, udp_conn, DHCP_PORT_CLIENT),
+ .self_eth_addr = LO_CALL(iface, hwaddr),
.self_hostname = self_hostname,
.self_id_len = sizeof(client_id),
.self_id_dat = &client_id,
@@ -927,7 +926,7 @@ static void dhcp_client_setstate(struct dhcp_client *client,
/* Mutable. */
.state = STATE_INIT,
};
- assert(client.sock);
+ assert(!LO_IS_NULL(client.sock));
struct dhcp_recv_msg scratch;
diff --git a/libdhcp/include/libdhcp/client.h b/libdhcp/include/libdhcp/client.h
index c3cb76f..f81e9b1 100644
--- a/libdhcp/include/libdhcp/client.h
+++ b/libdhcp/include/libdhcp/client.h
@@ -1,6 +1,6 @@
/* libdhcp/client.h - A DHCP client
*
- * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com>
+ * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com>
* SPDX-License-Identifier: AGPL-3.0-or-later
*
* -----------------------------------------------------------------------------
@@ -69,7 +69,7 @@
#include <libhw/generic/net.h>
-[[noreturn]] void dhcp_client_main(implements_net_iface *iface,
+[[noreturn]] void dhcp_client_main(lo_interface net_iface iface,
char *self_hostname);
#endif /* _LIBDHCP_CLIENT_H_ */
diff --git a/libhw/host_alarmclock.c b/libhw/host_alarmclock.c
index 5f7e494..19ece7c 100644
--- a/libhw/host_alarmclock.c
+++ b/libhw/host_alarmclock.c
@@ -1,62 +1,27 @@
/* libhw/host_alarmclock.c - <libhw/generic/alarmclock.h> implementation for POSIX hosts
*
- * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com>
+ * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
#include <errno.h>
#include <error.h>
#include <signal.h>
-#include <time.h>
#include <libcr/coroutine.h>
#include <libmisc/assert.h>
-#include <libmisc/vcall.h>
#define IMPLEMENTATION_FOR_LIBHW_GENERIC_ALARMCLOCK_H YES
#include <libhw/generic/alarmclock.h>
+#define IMPLEMENTATION_FOR_LIBHW_HOST_ALARMCLOCK_H YES
+#include <libhw/host_alarmclock.h>
+
#include "host_util.h" /* for host_sigrt_alloc(), ns_to_host_ns_time() */
-/* Types **********************************************************************/
-
-struct hostclock {
- implements_alarmclock;
- bool initialized;
- clockid_t clock_id;
- timer_t timer_id;
- struct alarmclock_trigger *queue;
-};
-
-/* Globals ********************************************************************/
-
-static uint64_t hostclock_get_time_ns(implements_alarmclock *self);
-static bool hostclock_add_trigger(implements_alarmclock *self,
- struct alarmclock_trigger *trigger,
- uint64_t fire_at_ns,
- void (*cb)(void *),
- void *cb_arg);
-static void hostclock_del_trigger(implements_alarmclock *self,
- struct alarmclock_trigger *trigger);
-
-static struct alarmclock_vtable hostclock_vtable = {
- .get_time_ns = hostclock_get_time_ns,
- .add_trigger = hostclock_add_trigger,
- .del_trigger = hostclock_del_trigger,
-};
-
-static struct hostclock clock_monotonic = {
- .vtable = &hostclock_vtable,
- .clock_id = CLOCK_MONOTONIC,
-};
-
-implements_alarmclock *bootclock = &clock_monotonic;
-
-/* Main implementation ********************************************************/
-
-static uint64_t hostclock_get_time_ns(implements_alarmclock *_alarmclock) {
- struct hostclock *alarmclock =
- VCALL_SELF(struct hostclock, implements_alarmclock, _alarmclock);
+LO_IMPLEMENTATION_C(alarmclock, struct hostclock, hostclock, static)
+
+static uint64_t hostclock_get_time_ns(struct hostclock *alarmclock) {
assert(alarmclock);
struct timespec ts;
@@ -91,13 +56,11 @@ static void hostclock_handle_sig_alarm(int LM_UNUSED(sig), siginfo_t *info, void
}
}
-static bool hostclock_add_trigger(implements_alarmclock *_alarmclock,
- struct alarmclock_trigger *trigger,
- uint64_t fire_at_ns,
- void (*cb)(void *),
- void *cb_arg) {
- struct hostclock *alarmclock =
- VCALL_SELF(struct hostclock, implements_alarmclock, _alarmclock);
+static 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);
@@ -148,11 +111,8 @@ static bool hostclock_add_trigger(implements_alarmclock *_alarmclock,
return false;
}
-static void hostclock_del_trigger(implements_alarmclock *_alarmclock,
- struct alarmclock_trigger *trigger) {
- struct hostclock *alarmclock =
- VCALL_SELF(struct hostclock, implements_alarmclock, _alarmclock);
-
+static void hostclock_del_trigger(struct hostclock *alarmclock,
+ struct alarmclock_trigger *trigger) {
assert(alarmclock);
assert(trigger);
diff --git a/libhw/host_include/libhw/host_alarmclock.h b/libhw/host_include/libhw/host_alarmclock.h
new file mode 100644
index 0000000..89df68a
--- /dev/null
+++ b/libhw/host_include/libhw/host_alarmclock.h
@@ -0,0 +1,27 @@
+/* libhw/host_alarmclock.h - <libhw/generic/alarmclock.h> implementation for hosted glibc
+ *
+ * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com>
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+#ifndef _LIBHW_HOST_ALARMCLOCK_H_
+#define _LIBHW_HOST_ALARMCLOCK_H_
+
+#include <stdbool.h> /* for bool */
+#include <time.h> /* for clockid_t, timer_t */
+
+#include <libmisc/private.h>
+#include <libhw/generic/alarmclock.h>
+
+struct hostclock {
+ clockid_t clock_id;
+
+ BEGIN_PRIVATE(LIBHW_HOST_ALARMCLOCK_H)
+ bool initialized;
+ timer_t timer_id;
+ struct alarmclock_trigger *queue;
+ END_PRIVATE(LIBHW_HOST_ALARMCLOCK_H)
+};
+LO_IMPLEMENTATION_H(alarmclock, struct hostclock, hostclock)
+
+#endif /* _LIBHW_HOST_ALARMCLOCK_H_ */
diff --git a/libhw/host_include/libhw/host_net.h b/libhw/host_include/libhw/host_net.h
index bfef5c9..fced229 100644
--- a/libhw/host_include/libhw/host_net.h
+++ b/libhw/host_include/libhw/host_net.h
@@ -1,6 +1,6 @@
/* libhw/host_net.h - <libhw/generic/net.h> implementation for hosted glibc
*
- * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com>
+ * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
@@ -14,33 +14,30 @@
#include <libhw/generic/net.h>
struct _hostnet_tcp_conn {
- implements_net_stream_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)
struct hostnet_tcp_listener {
- implements_net_stream_listener;
-
BEGIN_PRIVATE(LIBHW_HOST_NET_H)
int fd;
struct _hostnet_tcp_conn active_conn;
END_PRIVATE(LIBHW_HOST_NET_H)
};
+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);
struct hostnet_udp_conn {
- implements_net_packet_conn;
-
BEGIN_PRIVATE(LIBHW_HOST_NET_H)
int fd;
uint64_t read_deadline_ns;
END_PRIVATE(LIBHW_HOST_NET_H)
};
+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/host_net.c b/libhw/host_net.c
index bf79480..505c7dc 100644
--- a/libhw/host_net.c
+++ b/libhw/host_net.c
@@ -21,7 +21,7 @@
#include <libcr/coroutine.h>
#include <libmisc/assert.h>
#include <libmisc/macro.h>
-#include <libmisc/vcall.h>
+#include <libobj/obj.h>
#include <libhw/generic/alarmclock.h>
@@ -30,6 +30,10 @@
#include "host_util.h" /* for host_sigrt_alloc(), ns_to_host_us_time() */
+LO_IMPLEMENTATION_C(net_stream_conn, struct _hostnet_tcp_conn, hostnet_tcp, static)
+LO_IMPLEMENTATION_C(net_stream_listener, struct hostnet_tcp_listener, hostnet_tcplist, static)
+LO_IMPLEMENTATION_C(net_packet_conn, struct hostnet_udp_conn, hostnet_udp, static)
+
/* common *********************************************************************/
static int hostnet_sig_io = 0;
@@ -109,25 +113,6 @@ static inline ssize_t hostnet_map_negerrno(ssize_t v, enum hostnet_timeout_op op
/* TCP init() ( AKA socket(3) + listen(3) )************************************/
-static implements_net_stream_conn *hostnet_tcplist_accept(implements_net_stream_listener *);
-static int hostnet_tcplist_close(implements_net_stream_listener *);
-static void hostnet_tcp_set_read_deadline(implements_net_stream_conn *conn, uint64_t ts_ns);
-static ssize_t hostnet_tcp_read(implements_net_stream_conn *conn, void *buf, size_t count);
-static ssize_t hostnet_tcp_write(implements_net_stream_conn *conn, void *buf, size_t count);
-static int hostnet_tcp_close(implements_net_stream_conn *conn, bool rd, bool wr);
-
-static struct net_stream_listener_vtable hostnet_tcp_listener_vtable = {
- .accept = hostnet_tcplist_accept,
- .close = hostnet_tcplist_close,
-};
-
-static struct net_stream_conn_vtable hostnet_tcp_conn_vtable = {
- .set_read_deadline = hostnet_tcp_set_read_deadline,
- .read = hostnet_tcp_read,
- .write = hostnet_tcp_write,
- .close = hostnet_tcp_close,
-};
-
void hostnet_tcp_listener_init(struct hostnet_tcp_listener *self, uint16_t port) {
int listenerfd;
union {
@@ -151,7 +136,6 @@ void hostnet_tcp_listener_init(struct hostnet_tcp_listener *self, uint16_t port)
if (listen(listenerfd, 0) < 0)
error(1, errno, "listen(fd=%d)", listenerfd);
- self->vtable = &hostnet_tcp_listener_vtable;
self->fd = listenerfd;
}
@@ -175,9 +159,7 @@ static void *hostnet_pthread_accept(void *_args) {
return NULL;
}
-static implements_net_stream_conn *hostnet_tcplist_accept(implements_net_stream_listener *_listener) {
- struct hostnet_tcp_listener *listener =
- VCALL_SELF(struct hostnet_tcp_listener, implements_net_stream_listener, _listener);
+static lo_interface net_stream_conn hostnet_tcplist_accept(struct hostnet_tcp_listener *listener) {
assert(listener);
int ret_connfd;
@@ -188,22 +170,19 @@ static implements_net_stream_conn *hostnet_tcplist_accept(implements_net_stream_
.ret_connfd = &ret_connfd,
};
if (RUN_PTHREAD(hostnet_pthread_accept, &args))
- return NULL;
+ return LO_NULL(net_stream_conn);
if (ret_connfd < 0)
- return NULL;
+ return LO_NULL(net_stream_conn);
- listener->active_conn.vtable = &hostnet_tcp_conn_vtable;
listener->active_conn.fd = ret_connfd;
listener->active_conn.read_deadline_ns = 0;
- return &listener->active_conn;
+ return lo_box_hostnet_tcp_as_net_stream_conn(&listener->active_conn);
}
/* TCP listener close() *******************************************************/
-static int hostnet_tcplist_close(implements_net_stream_listener *_listener) {
- struct hostnet_tcp_listener *listener =
- VCALL_SELF(struct hostnet_tcp_listener, implements_net_stream_listener, _listener);
+static int hostnet_tcplist_close(struct hostnet_tcp_listener *listener) {
assert(listener);
return hostnet_map_negerrno(shutdown(listener->fd, SHUT_RDWR) ? -errno : 0, OP_NONE);
@@ -211,9 +190,7 @@ static int hostnet_tcplist_close(implements_net_stream_listener *_listener) {
/* TCP read() *****************************************************************/
-static void hostnet_tcp_set_read_deadline(implements_net_stream_conn *_conn, uint64_t ts_ns) {
- struct _hostnet_tcp_conn *conn =
- VCALL_SELF(struct _hostnet_tcp_conn, implements_net_stream_conn, _conn);
+static void hostnet_tcp_set_read_deadline(struct _hostnet_tcp_conn *conn, uint64_t ts_ns) {
assert(conn);
conn->read_deadline_ns = ts_ns;
@@ -250,9 +227,7 @@ static void *hostnet_pthread_read(void *_args) {
return NULL;
}
-static ssize_t hostnet_tcp_read(implements_net_stream_conn *_conn, void *buf, size_t count) {
- struct _hostnet_tcp_conn *conn =
- VCALL_SELF(struct _hostnet_tcp_conn, implements_net_stream_conn, _conn);
+static ssize_t hostnet_tcp_read(struct _hostnet_tcp_conn *conn, void *buf, size_t count) {
assert(conn);
ssize_t ret;
@@ -267,7 +242,7 @@ static ssize_t hostnet_tcp_read(implements_net_stream_conn *_conn, void *buf, si
.ret = &ret,
};
if (conn->read_deadline_ns) {
- uint64_t now_ns = VCALL(bootclock, get_time_ns);
+ uint64_t now_ns = LO_CALL(bootclock, get_time_ns);
if (conn->read_deadline_ns < now_ns)
return -NET_ERECV_TIMEOUT;
args.timeout = ns_to_host_us_time(conn->read_deadline_ns-now_ns);
@@ -310,9 +285,7 @@ static void *hostnet_pthread_write(void *_args) {
return NULL;
}
-static ssize_t hostnet_tcp_write(implements_net_stream_conn *_conn, void *buf, size_t count) {
- struct _hostnet_tcp_conn *conn =
- VCALL_SELF(struct _hostnet_tcp_conn, implements_net_stream_conn, _conn);
+static ssize_t hostnet_tcp_write(struct _hostnet_tcp_conn *conn, void *buf, size_t count) {
assert(conn);
ssize_t ret;
@@ -333,9 +306,7 @@ static ssize_t hostnet_tcp_write(implements_net_stream_conn *_conn, void *buf, s
/* TCP close() ****************************************************************/
-static int hostnet_tcp_close(implements_net_stream_conn *_conn, bool rd, bool wr) {
- struct _hostnet_tcp_conn *conn =
- VCALL_SELF(struct _hostnet_tcp_conn, implements_net_stream_conn, _conn);
+static int hostnet_tcp_close(struct _hostnet_tcp_conn *conn, bool rd, bool wr) {
assert(conn);
int how;
@@ -352,21 +323,6 @@ static int hostnet_tcp_close(implements_net_stream_conn *_conn, bool rd, bool wr
/* UDP init() *****************************************************************/
-static void hostnet_udp_set_read_deadline(implements_net_packet_conn *self,
- uint64_t ts_ns);
-static ssize_t hostnet_udp_sendto(implements_net_packet_conn *self, void *buf, size_t len,
- struct net_ip4_addr addr, uint16_t port);
-static ssize_t hostnet_udp_recvfrom(implements_net_packet_conn *self, void *buf, size_t len,
- struct net_ip4_addr *ret_addr, uint16_t *ret_port);
-static int hostnet_udp_close(implements_net_packet_conn *self);
-
-static struct net_packet_conn_vtable hostnet_udp_conn_vtable = {
- .set_read_deadline = hostnet_udp_set_read_deadline,
- .sendto = hostnet_udp_sendto,
- .recvfrom = hostnet_udp_recvfrom,
- .close = hostnet_udp_close,
-};
-
void hostnet_udp_conn_init(struct hostnet_udp_conn *self, uint16_t port) {
int fd;
union {
@@ -385,7 +341,6 @@ void hostnet_udp_conn_init(struct hostnet_udp_conn *self, uint16_t port) {
if (bind(fd, &addr.gen, sizeof addr) < 0)
error(1, errno, "bind");
- self->vtable = &hostnet_udp_conn_vtable;
self->fd = fd;
self->read_deadline_ns = 0;
}
@@ -427,10 +382,8 @@ static void *hostnet_pthread_sendto(void *_args) {
return NULL;
}
-static ssize_t hostnet_udp_sendto(implements_net_packet_conn *_conn, void *buf, size_t count,
+static ssize_t hostnet_udp_sendto(struct hostnet_udp_conn *conn, void *buf, size_t count,
struct net_ip4_addr node, uint16_t port) {
- struct hostnet_udp_conn *conn =
- VCALL_SELF(struct hostnet_udp_conn, implements_net_packet_conn, _conn);
assert(conn);
ssize_t ret;
@@ -453,10 +406,8 @@ static ssize_t hostnet_udp_sendto(implements_net_packet_conn *_conn, void *buf,
/* UDP recvfrom() *************************************************************/
-static void hostnet_udp_set_read_deadline(implements_net_packet_conn *_conn,
+static void hostnet_udp_set_read_deadline(struct hostnet_udp_conn *conn,
uint64_t ts_ns) {
- struct hostnet_udp_conn *conn =
- VCALL_SELF(struct hostnet_udp_conn, implements_net_packet_conn, _conn);
assert(conn);
conn->read_deadline_ns = ts_ns;
@@ -514,10 +465,8 @@ static void *hostnet_pthread_recvfrom(void *_args) {
return NULL;
}
-static ssize_t hostnet_udp_recvfrom(implements_net_packet_conn *_conn, void *buf, size_t count,
+static ssize_t hostnet_udp_recvfrom(struct hostnet_udp_conn *conn, void *buf, size_t count,
struct net_ip4_addr *ret_node, uint16_t *ret_port) {
- struct hostnet_udp_conn *conn =
- VCALL_SELF(struct hostnet_udp_conn, implements_net_packet_conn, _conn);
assert(conn);
ssize_t ret;
@@ -534,7 +483,7 @@ static ssize_t hostnet_udp_recvfrom(implements_net_packet_conn *_conn, void *buf
.ret_port = ret_port,
};
if (conn->read_deadline_ns) {
- uint64_t now_ns = VCALL(bootclock, get_time_ns);
+ uint64_t now_ns = LO_CALL(bootclock, get_time_ns);
if (conn->read_deadline_ns < now_ns)
return -NET_ERECV_TIMEOUT;
args.timeout = ns_to_host_us_time(conn->read_deadline_ns-now_ns);
@@ -549,9 +498,7 @@ static ssize_t hostnet_udp_recvfrom(implements_net_packet_conn *_conn, void *buf
/* UDP close() ****************************************************************/
-static int hostnet_udp_close(implements_net_packet_conn *_conn) {
- struct hostnet_udp_conn *conn =
- VCALL_SELF(struct hostnet_udp_conn, implements_net_packet_conn, _conn);
+static int hostnet_udp_close(struct hostnet_udp_conn *conn) {
assert(conn);
return hostnet_map_negerrno(close(conn->fd) ? -errno : 0, OP_NONE);
diff --git a/libhw/rp2040_hwspi.c b/libhw/rp2040_hwspi.c
index 47dfc97..23f3e8c 100644
--- a/libhw/rp2040_hwspi.c
+++ b/libhw/rp2040_hwspi.c
@@ -1,6 +1,6 @@
/* libhw/rp2040_hwspi.c - <libhw/generic/spi.h> implementation for the RP2040's ARM Primecell SSP (PL022)
*
- * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com>
+ * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
@@ -8,16 +8,11 @@
#include <hardware/gpio.h> /* pico-sdk:hardware_gpio */
#include <libmisc/assert.h>
-#include <libmisc/vcall.h>
#define IMPLEMENTATION_FOR_LIBHW_RP2040_HWSPI_H YES
#include <libhw/rp2040_hwspi.h>
-static void rp2040_hwspi_readwritev(implements_spi *, const struct bidi_iovec *iov, int iovcnt);
-
-struct spi_vtable rp2040_hwspi_vtable = {
- .readwritev = rp2040_hwspi_readwritev,
-};
+LO_IMPLEMENTATION_C(spi, struct rp2040_hwspi, rp2040_hwspi, static)
void _rp2040_hwspi_init(struct rp2040_hwspi *self,
enum rp2040_hwspi_instance inst_num,
@@ -84,13 +79,11 @@ void _rp2040_hwspi_init(struct rp2040_hwspi *self,
gpio_put(pin_cs, 1);
/* Return. */
- self->vtable = &rp2040_hwspi_vtable;
self->inst = inst;
self->pin_cs = pin_cs;
}
-static void rp2040_hwspi_readwritev(implements_spi *_self, const struct bidi_iovec *iov, int iovcnt) {
- struct rp2040_hwspi *self = VCALL_SELF(struct rp2040_hwspi, implements_spi, _self);
+static void rp2040_hwspi_readwritev(struct rp2040_hwspi *self, const struct bidi_iovec *iov, int iovcnt) {
assert(self);
spi_inst_t *inst = self->inst;
diff --git a/libhw/rp2040_hwtimer.c b/libhw/rp2040_hwtimer.c
index c8c281e..ada6246 100644
--- a/libhw/rp2040_hwtimer.c
+++ b/libhw/rp2040_hwtimer.c
@@ -1,6 +1,6 @@
/* libhw/rp2040_hwtimer.c - <libhw/generic/alarmclock.h> implementation for the RP2040's hardware timer
*
- * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com>
+ * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
@@ -9,10 +9,10 @@
#include <libcr/coroutine.h>
#include <libmisc/assert.h>
-#include <libmisc/vcall.h>
#define IMPLEMENTATION_FOR_LIBHW_GENERIC_ALARMCLOCK_H YES
#include <libhw/generic/alarmclock.h>
+
#include <libhw/rp2040_hwtimer.h>
/******************************************************************************/
@@ -23,48 +23,32 @@ void add_alarm_at(void) {};
/* Types **********************************************************************/
struct rp2040_hwtimer {
- implements_alarmclock;
enum rp2040_hwalarm_instance alarm_num;
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);
/* Globals ********************************************************************/
-static uint64_t rp2040_hwtimer_get_time_ns(implements_alarmclock *self);
-static bool rp2040_hwtimer_add_trigger(implements_alarmclock *self,
- struct alarmclock_trigger *trigger,
- uint64_t fire_at_ns,
- void (*cb)(void *),
- void *cb_arg);
-static void rp2040_hwtimer_del_trigger(implements_alarmclock *self,
- struct alarmclock_trigger *trigger);
-
-static struct alarmclock_vtable rp2040_hwtimer_vtable = {
- .get_time_ns = rp2040_hwtimer_get_time_ns,
- .add_trigger = rp2040_hwtimer_add_trigger,
- .del_trigger = rp2040_hwtimer_del_trigger,
-};
-
static struct rp2040_hwtimer hwtimers[] = {
- { .vtable = &rp2040_hwtimer_vtable, .alarm_num = 0 },
- { .vtable = &rp2040_hwtimer_vtable, .alarm_num = 1 },
- { .vtable = &rp2040_hwtimer_vtable, .alarm_num = 2 },
- { .vtable = &rp2040_hwtimer_vtable, .alarm_num = 3 },
+ { .alarm_num = 0 },
+ { .alarm_num = 1 },
+ { .alarm_num = 2 },
+ { .alarm_num = 3 },
};
static_assert(sizeof(hwtimers)/sizeof(hwtimers[0]) == _RP2040_HWALARM_NUM);
-implements_alarmclock *bootclock = &hwtimers[0];
-
/* Main implementation ********************************************************/
-implements_alarmclock *rp2040_hwtimer(enum rp2040_hwalarm_instance alarm_num) {
+lo_interface alarmclock rp2040_hwtimer(enum rp2040_hwalarm_instance alarm_num) {
assert(alarm_num < _RP2040_HWALARM_NUM);
- return &hwtimers[alarm_num];
+ return lo_box_rp2040_hwtimer_as_alarmclock(&hwtimers[alarm_num]);
}
-static uint64_t rp2040_hwtimer_get_time_ns(implements_alarmclock *) {
+static uint64_t rp2040_hwtimer_get_time_ns(struct rp2040_hwtimer *) {
return timer_time_us_64(timer_hw) * (NS_PER_S/US_PER_S);
}
@@ -93,13 +77,11 @@ static void rp2040_hwtimer_intrhandler(void) {
timer_hw->alarm[alarm_num] = (uint32_t)NS_TO_US_ROUNDUP(alarmclock->queue->fire_at_ns);
}
-static bool rp2040_hwtimer_add_trigger(implements_alarmclock *_alarmclock,
+static bool rp2040_hwtimer_add_trigger(struct rp2040_hwtimer *alarmclock,
struct alarmclock_trigger *trigger,
uint64_t fire_at_ns,
void (*cb)(void *),
void *cb_arg) {
- struct rp2040_hwtimer *alarmclock =
- VCALL_SELF(struct rp2040_hwtimer, implements_alarmclock, _alarmclock);
assert(alarmclock);
assert(trigger);
assert(fire_at_ns);
@@ -149,10 +131,8 @@ static bool rp2040_hwtimer_add_trigger(implements_alarmclock *_alarmclock,
return false;
}
-static void rp2040_hwtimer_del_trigger(implements_alarmclock *_alarmclock,
+static void rp2040_hwtimer_del_trigger(struct rp2040_hwtimer *alarmclock,
struct alarmclock_trigger *trigger) {
- struct rp2040_hwtimer *alarmclock =
- VCALL_SELF(struct rp2040_hwtimer, implements_alarmclock, _alarmclock);
assert(alarmclock);
assert(trigger);
diff --git a/libhw/rp2040_include/libhw/rp2040_hwspi.h b/libhw/rp2040_include/libhw/rp2040_hwspi.h
index 7c4991b..b1abe0c 100644
--- a/libhw/rp2040_include/libhw/rp2040_hwspi.h
+++ b/libhw/rp2040_include/libhw/rp2040_hwspi.h
@@ -1,6 +1,6 @@
/* libhw/rp2040_hwspi.h - <libhw/generic/spi.h> implementation for the RP2040's ARM Primecell SSP (PL022)
*
- * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com>
+ * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
@@ -19,13 +19,12 @@ enum rp2040_hwspi_instance {
};
struct rp2040_hwspi {
- implements_spi;
-
BEGIN_PRIVATE(LIBHW_RP2040_HWSPI_H)
void /*spi_inst_t*/ *inst;
uint pin_cs;
END_PRIVATE(LIBHW_RP2040_HWSPI_H)
};
+LO_IMPLEMENTATION_H(spi, struct rp2040_hwspi, rp2040_hwspi)
/**
* Initialize an instance of `struct rp2040_hwspi`.
diff --git a/libhw/rp2040_include/libhw/rp2040_hwtimer.h b/libhw/rp2040_include/libhw/rp2040_hwtimer.h
index 6710ab1..40e4172 100644
--- a/libhw/rp2040_include/libhw/rp2040_hwtimer.h
+++ b/libhw/rp2040_include/libhw/rp2040_hwtimer.h
@@ -1,6 +1,6 @@
/* libhw/rp2040_hwtimer.h - <libhw/generic/alarmclock.h> implementation for the RP2040's hardware timer
*
- * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com>
+ * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
@@ -10,8 +10,7 @@
#include <libhw/generic/alarmclock.h>
/**
- * The RP2040 has one system "timer" (which we also use for
- * ./rp2040_bootclock.c) with 4 alarm interrupts.
+ * The RP2040 has one system "timer" with 4 alarm interrupts.
*/
enum rp2040_hwalarm_instance {
RP2040_HWALARM_0 = 0,
@@ -21,6 +20,6 @@ enum rp2040_hwalarm_instance {
_RP2040_HWALARM_NUM,
};
-implements_alarmclock *rp2040_hwtimer(enum rp2040_hwalarm_instance alarm_num);
+lo_interface alarmclock rp2040_hwtimer(enum rp2040_hwalarm_instance alarm_num);
#endif /* _LIBHW_RP2040_HWTIMER_H_ */
diff --git a/libhw/rp2040_include/libhw/w5500.h b/libhw/rp2040_include/libhw/w5500.h
index 3cae620..ab9f50e 100644
--- a/libhw/rp2040_include/libhw/w5500.h
+++ b/libhw/rp2040_include/libhw/w5500.h
@@ -1,6 +1,6 @@
/* libhw/w5500.h - <libhw/generic/net.h> implementation for the WIZnet W5500 chip
*
- * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com>
+ * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
@@ -20,11 +20,8 @@
CR_CHAN_DECLARE(_w5500_sockintr_ch, uint8_t)
struct _w5500_socket {
- /* const-after-init */
- implements_net_stream_listener implements_net_stream_listener;
- implements_net_stream_conn implements_net_stream_conn;
- implements_net_packet_conn implements_net_packet_conn;
BEGIN_PRIVATE(LIBHW_W5500_H)
+ /* const-after-init */
uint8_t socknum;
/* mutable */
@@ -43,12 +40,14 @@ struct _w5500_socket {
END_PRIVATE(LIBHW_W5500_H)
};
+LO_IMPLEMENTATION_H(net_stream_listener, struct _w5500_socket, w5500_tcplist)
+LO_IMPLEMENTATION_H(net_stream_conn, struct _w5500_socket, w5500_tcp)
+LO_IMPLEMENTATION_H(net_packet_conn, struct _w5500_socket, w5500_udp)
struct w5500 {
- /* const-after-init */
- implements_net_iface;
BEGIN_PRIVATE(LIBHW_W5500_H)
- implements_spi *spidev;
+ /* const-after-init */
+ lo_interface spi spidev;
uint pin_intr;
uint pin_reset;
struct net_eth_addr hwaddr;
@@ -61,6 +60,7 @@ struct w5500 {
cr_mutex_t mu;
END_PRIVATE(LIBHW_W5500_H)
};
+LO_IMPLEMENTATION_H(net_iface, struct w5500, w5500_if)
/**
* Initialize a WIZnet W5500 Ethernet-and-TCP/IP-offload chip.
@@ -82,7 +82,7 @@ struct w5500 {
_w5500_init(self, spi, pin_intr, pin_reset, eth_addr); \
} while (0)
void _w5500_init(struct w5500 *self,
- implements_spi *spi, uint pin_intr, uint pin_reset,
+ lo_interface spi spi, uint pin_intr, uint pin_reset,
struct net_eth_addr addr);
/**
diff --git a/libhw/w5500.c b/libhw/w5500.c
index cebcf8e..dfe169f 100644
--- a/libhw/w5500.c
+++ b/libhw/w5500.c
@@ -1,6 +1,6 @@
/* libhw/w5500.c - <libhw/generic/net.h> implementation for the WIZnet W5500 chip
*
- * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com>
+ * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com>
* SPDX-License-Identifier: AGPL-3.0-or-later
*
* -----------------------------------------------------------------------------
@@ -74,7 +74,6 @@
#include <hardware/gpio.h> /* pico-sdk:hardware_gpio */
#include <libcr/coroutine.h> /* for cr_yield() */
-#include <libmisc/vcall.h> /* for VCALL_SELF() */
#include <libhw/generic/alarmclock.h> /* for sleep_*() */
@@ -124,61 +123,12 @@ static const char *w5500_state_str(uint8_t state) {
}
}
-/* vtables ********************************************************************/
-
-/* iface */
-static struct net_eth_addr w5500_if_hwaddr (implements_net_iface *);
-static void w5500_if_up (implements_net_iface *, struct net_iface_config);
-static void w5500_if_down (implements_net_iface *);
-static implements_net_stream_listener *w5500_if_tcp_listen (implements_net_iface *, uint16_t local_port);
-static implements_net_stream_conn *w5500_if_tcp_dial (implements_net_iface *, struct net_ip4_addr, uint16_t remote_port);
-static implements_net_packet_conn *w5500_if_udp_conn (implements_net_iface *, uint16_t local_port);
-
-/* stream_listener */
-static implements_net_stream_conn *w5500_tcplist_accept(implements_net_stream_listener *);
-static int w5500_tcplist_close (implements_net_stream_listener *);
-
-/* stream_conn */
-static void w5500_tcp_set_read_deadline (implements_net_stream_conn *, uint64_t ns);
-static ssize_t w5500_tcp_read (implements_net_stream_conn *, void *, size_t);
-static ssize_t w5500_tcp_write (implements_net_stream_conn *, void *, size_t);
-static int w5500_tcp_close (implements_net_stream_conn *, bool rd, bool wr);
-
-/* packet_conn */
-static void w5500_udp_set_read_deadline (implements_net_packet_conn *, uint64_t ns);
-static ssize_t w5500_udp_recvfrom (implements_net_packet_conn *, void *, size_t, struct net_ip4_addr *, uint16_t *);
-static ssize_t w5500_udp_sendto (implements_net_packet_conn *, void *, size_t, struct net_ip4_addr, uint16_t);
-static int w5500_udp_close (implements_net_packet_conn *);
-
-/* tables */
-
-static struct net_iface_vtable w5500_iface_vtable = {
- .hwaddr = w5500_if_hwaddr,
- .ifup = w5500_if_up,
- .ifdown = w5500_if_down,
- .tcp_listen = w5500_if_tcp_listen,
- .tcp_dial = w5500_if_tcp_dial,
- .udp_conn = w5500_if_udp_conn,
-};
-
-static struct net_stream_listener_vtable w5500_tcp_listener_vtable = {
- .accept = w5500_tcplist_accept,
- .close = w5500_tcplist_close,
-};
-
-static struct net_stream_conn_vtable w5500_tcp_conn_vtable = {
- .set_read_deadline = w5500_tcp_set_read_deadline,
- .read = w5500_tcp_read,
- .write = w5500_tcp_write,
- .close = w5500_tcp_close,
-};
-
-static struct net_packet_conn_vtable w5500_udp_conn_vtable = {
- .set_read_deadline = w5500_udp_set_read_deadline,
- .recvfrom = w5500_udp_recvfrom,
- .sendto = w5500_udp_sendto,
- .close = w5500_udp_close,
-};
+/* libobj *********************************************************************/
+
+LO_IMPLEMENTATION_C(net_stream_listener, struct _w5500_socket, w5500_tcplist, static)
+LO_IMPLEMENTATION_C(net_stream_conn, struct _w5500_socket, w5500_tcp, static)
+LO_IMPLEMENTATION_C(net_packet_conn, struct _w5500_socket, w5500_udp, static)
+LO_IMPLEMENTATION_C(net_iface, struct w5500, w5500_if, static)
/* mid-level utilities ********************************************************/
@@ -311,9 +261,6 @@ static inline void w5500_socket_close(struct _w5500_socket *socket) {
}
#define ASSERT_SELF(_iface, _mode) \
- struct _w5500_socket *socket = \
- VCALL_SELF(struct _w5500_socket, \
- implements_net_##_iface, _socket); \
assert(socket); \
uint8_t socknum = socket->socknum; \
assert(socknum < 8); \
@@ -333,15 +280,14 @@ static void w5500_intrhandler(uint gpio, uint32_t LM_UNUSED(event_mask)) {
}
void _w5500_init(struct w5500 *chip,
- implements_spi *spi, uint pin_intr, uint pin_reset,
+ lo_interface spi spi, uint pin_intr, uint pin_reset,
struct net_eth_addr addr) {
assert(chip);
- assert(spi);
+ assert(!LO_IS_NULL(spi));
/* Initialize the data structures. */
*chip = (struct w5500){
/* const-after-init */
- .implements_net_iface = { .vtable = &w5500_iface_vtable },
.spidev = spi,
.pin_intr = pin_intr,
.pin_reset = pin_reset,
@@ -353,9 +299,6 @@ void _w5500_init(struct w5500 *chip,
for (uint8_t i = 0; i < 8; i++) {
chip->sockets[i] = (struct _w5500_socket){
/* const-after-init */
- .implements_net_stream_listener = { .vtable = &w5500_tcp_listener_vtable },
- .implements_net_stream_conn = { .vtable = &w5500_tcp_conn_vtable },
- .implements_net_packet_conn = { .vtable = &w5500_udp_conn_vtable },
.socknum = i,
/* mutable */
.next_free = (i + 1 < 8) ? &chip->sockets[i+1] : NULL,
@@ -449,15 +392,13 @@ void w5500_soft_reset(struct w5500 *chip) {
cr_mutex_unlock(&chip->mu);
}
-static struct net_eth_addr w5500_if_hwaddr(implements_net_iface *_chip) {
- struct w5500 *chip = VCALL_SELF(struct w5500, implements_net_iface, _chip);
+static struct net_eth_addr w5500_if_hwaddr(struct w5500 *chip) {
assert(chip);
return chip->hwaddr;
}
-static void _w5500_if_up(implements_net_iface *_chip, struct net_iface_config cfg) {
- struct w5500 *chip = VCALL_SELF(struct w5500, implements_net_iface, _chip);
+static void _w5500_if_up(struct w5500 *chip, struct net_iface_config cfg) {
assert(chip);
cr_mutex_lock(&chip->mu);
@@ -469,27 +410,26 @@ static void _w5500_if_up(implements_net_iface *_chip, struct net_iface_config cf
cr_mutex_unlock(&chip->mu);
}
-static void w5500_if_up(implements_net_iface *_chip, struct net_iface_config cfg) {
+static void w5500_if_ifup(struct w5500 *chip, struct net_iface_config cfg) {
debugf("if_up()");
debugf(":: addr = "PRI_net_ip4_addr, ARG_net_ip4_addr(cfg.addr));
debugf(":: gateway_addr = "PRI_net_ip4_addr, ARG_net_ip4_addr(cfg.gateway_addr));
debugf(":: subnet_mask = "PRI_net_ip4_addr, ARG_net_ip4_addr(cfg.subnet_mask));
- _w5500_if_up(_chip, cfg);
+ _w5500_if_up(chip, cfg);
}
-static void w5500_if_down(implements_net_iface *_chip) {
+static void w5500_if_ifdown(struct w5500 *chip) {
debugf("if_down()");
- _w5500_if_up(_chip, (struct net_iface_config){0});
+ _w5500_if_up(chip, (struct net_iface_config){0});
}
-static implements_net_stream_listener *w5500_if_tcp_listen(implements_net_iface *_chip, uint16_t local_port) {
- struct w5500 *chip = VCALL_SELF(struct w5500, implements_net_iface, _chip);
+static 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);
if (!sock) {
debugf("tcp_listen() => no sock");
- return NULL;
+ return LO_NULL(net_stream_listener);
}
debugf("tcp_listen() => sock[%"PRIu8"]", sock->socknum);
@@ -502,12 +442,11 @@ static implements_net_stream_listener *w5500_if_tcp_listen(implements_net_iface
sock->read_deadline_ns = 0;
sock->list_open = true;
- return &sock->implements_net_stream_listener;
+ return lo_box_w5500_tcplist_as_net_stream_listener(sock);
}
-static implements_net_stream_conn *w5500_if_tcp_dial(implements_net_iface *_chip,
- struct net_ip4_addr node, uint16_t port) {
- struct w5500 *chip = VCALL_SELF(struct w5500, implements_net_iface, _chip);
+static lo_interface net_stream_conn 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));
assert(memcmp(node.octets, net_ip4_addr_broadcast.octets, 4));
@@ -516,7 +455,7 @@ static implements_net_stream_conn *w5500_if_tcp_dial(implements_net_iface *_chip
struct _w5500_socket *socket = w5500_alloc_socket(chip);
if (!socket) {
debugf("tcp_dial() => no sock");
- return NULL;
+ return LO_NULL(net_stream_conn);
}
uint8_t socknum = socket->socknum;
debugf("tcp_dial() => sock[%"PRIu8"]", socknum);
@@ -553,21 +492,20 @@ static implements_net_stream_conn *w5500_if_tcp_dial(implements_net_iface *_chip
cr_yield();
break;
case STATE_TCP_ESTABLISHED:
- return &socket->implements_net_stream_conn;
+ return lo_box_w5500_tcp_as_net_stream_conn(socket);
default:
goto restart;
}
}
}
-static implements_net_packet_conn *w5500_if_udp_conn(implements_net_iface *_chip, uint16_t local_port) {
- struct w5500 *chip = VCALL_SELF(struct w5500, implements_net_iface, _chip);
+static 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);
if (!socket) {
debugf("udp_conn() => no sock");
- return NULL;
+ return LO_NULL(net_packet_conn);
}
uint8_t socknum = socket->socknum;
debugf("udp_conn() => sock[%"PRIu8"]", socknum);
@@ -590,18 +528,18 @@ static implements_net_packet_conn *w5500_if_udp_conn(implements_net_iface *_chip
cr_yield();
cr_mutex_unlock(&chip->mu);
- return &socket->implements_net_packet_conn;
+ return lo_box_w5500_udp_as_net_packet_conn(socket);
}
/* tcp_listener methods *******************************************************/
-static implements_net_stream_conn *w5500_tcplist_accept(implements_net_stream_listener *_socket) {
+static lo_interface net_stream_conn w5500_tcplist_accept(struct _w5500_socket *socket) {
ASSERT_SELF(stream_listener, TCP);
restart:
if (!socket->list_open) {
debugf("tcp_listener.accept() => already closed");
- return NULL;
+ return LO_NULL(net_stream_conn);
}
cr_mutex_lock(&chip->mu);
@@ -630,14 +568,14 @@ static implements_net_stream_conn *w5500_tcplist_accept(implements_net_stream_li
/* fall-through */
case STATE_TCP_CLOSE_WAIT:
socket->write_open = true;
- return &socket->implements_net_stream_conn;
+ return lo_box_w5500_tcp_as_net_stream_conn(socket);
default:
goto restart;
}
}
}
-static int w5500_tcplist_close(implements_net_stream_listener *_socket) {
+static int w5500_tcplist_close(struct _w5500_socket *socket) {
debugf("tcp_listener.close()");
ASSERT_SELF(stream_listener, TCP);
@@ -648,7 +586,7 @@ static int w5500_tcplist_close(implements_net_stream_listener *_socket) {
/* tcp_conn methods ***********************************************************/
-static ssize_t w5500_tcp_write(implements_net_stream_conn *_socket, void *buf, size_t count) {
+static ssize_t w5500_tcp_write(struct _w5500_socket *socket, void *buf, size_t count) {
debugf("tcp_conn.write(%zu)", count);
ASSERT_SELF(stream_conn, TCP);
assert(buf);
@@ -725,7 +663,7 @@ static ssize_t w5500_tcp_write(implements_net_stream_conn *_socket, void *buf, s
return done;
}
-static void w5500_tcp_set_read_deadline(implements_net_stream_conn *_socket, uint64_t ns) {
+static void w5500_tcp_set_read_deadline(struct _w5500_socket *socket, uint64_t ns) {
debugf("tcp_conn.set_read_deadline(%"PRIu64")", ns);
ASSERT_SELF(stream_conn, TCP);
socket->read_deadline_ns = ns;
@@ -736,7 +674,7 @@ static void w5500_tcp_alarm_handler(void *_arg) {
cr_sema_signal_from_intrhandler(&socket->read_sema);
}
-static ssize_t w5500_tcp_read(implements_net_stream_conn *_socket, void *buf, size_t count) {
+static ssize_t w5500_tcp_read(struct _w5500_socket *socket, void *buf, size_t count) {
debugf("tcp_conn.read()");
ASSERT_SELF(stream_conn, TCP);
assert(buf);
@@ -744,21 +682,21 @@ static ssize_t w5500_tcp_read(implements_net_stream_conn *_socket, void *buf, si
struct alarmclock_trigger trigger = {0};
if (socket->read_deadline_ns)
- VCALL(bootclock, add_trigger, &trigger,
- socket->read_deadline_ns,
- w5500_tcp_alarm_handler,
- socket);
+ LO_CALL(bootclock, add_trigger, &trigger,
+ socket->read_deadline_ns,
+ w5500_tcp_alarm_handler,
+ socket);
/* Wait until there is data to read. */
uint16_t avail = 0;
for (;;) {
if (!socket->read_open) {
- VCALL(bootclock, del_trigger, &trigger);
+ LO_CALL(bootclock, del_trigger, &trigger);
debugf(" => soft closed");
return -NET_ECLOSED;
}
- if (socket->read_deadline_ns && socket->read_deadline_ns <= VCALL(bootclock, get_time_ns)) {
- VCALL(bootclock, del_trigger, &trigger);
+ if (socket->read_deadline_ns && socket->read_deadline_ns <= LO_CALL(bootclock, get_time_ns)) {
+ LO_CALL(bootclock, del_trigger, &trigger);
debugf(" => recv timeout");
return -NET_ERECV_TIMEOUT;
}
@@ -770,7 +708,7 @@ static ssize_t w5500_tcp_read(implements_net_stream_conn *_socket, void *buf, si
case STATE_TCP_FIN_WAIT:
break; /* OK */
default:
- VCALL(bootclock, del_trigger, &trigger);
+ LO_CALL(bootclock, del_trigger, &trigger);
cr_mutex_unlock(&chip->mu);
debugf(" => hard closed");
return -NET_ECLOSED;
@@ -781,7 +719,7 @@ static ssize_t w5500_tcp_read(implements_net_stream_conn *_socket, void *buf, si
/* We have data to read. */
break;
if (state == STATE_TCP_CLOSE_WAIT) {
- VCALL(bootclock, del_trigger, &trigger);
+ LO_CALL(bootclock, del_trigger, &trigger);
cr_mutex_unlock(&chip->mu);
debugf(" => EOF");
return 0;
@@ -801,12 +739,12 @@ static ssize_t w5500_tcp_read(implements_net_stream_conn *_socket, void *buf, si
w5500ll_write_sock_reg(chip->spidev, socknum, rx_read_pointer, uint16be_marshal(ptr+avail));
w5500_socket_cmd(socket, CMD_RECV);
/* Return. */
- VCALL(bootclock, del_trigger, &trigger);
+ LO_CALL(bootclock, del_trigger, &trigger);
cr_mutex_unlock(&chip->mu);
return avail;
}
-static int w5500_tcp_close(implements_net_stream_conn *_socket, bool rd, bool wr) {
+static int w5500_tcp_close(struct _w5500_socket *socket, bool rd, bool wr) {
debugf("tcp_conn.close(rd=%s, wr=%s)", rd ? "true" : "false", wr ? "true" : "false");
ASSERT_SELF(stream_conn, TCP);
@@ -839,7 +777,7 @@ static int w5500_tcp_close(implements_net_stream_conn *_socket, bool rd, bool wr
/* udp_conn methods ***********************************************************/
-static ssize_t w5500_udp_sendto(implements_net_packet_conn *_socket, void *buf, size_t count,
+static ssize_t w5500_udp_sendto(struct _w5500_socket *socket, void *buf, size_t count,
struct net_ip4_addr node, uint16_t port) {
debugf("udp_conn.sendto()");
ASSERT_SELF(packet_conn, UDP);
@@ -896,7 +834,7 @@ static ssize_t w5500_udp_sendto(implements_net_packet_conn *_socket, void *buf,
}
}
-static void w5500_udp_set_read_deadline(implements_net_packet_conn *_socket, uint64_t ns) {
+static void w5500_udp_set_read_deadline(struct _w5500_socket *socket, uint64_t ns) {
debugf("udp_conn.set_read_deadline(%"PRIu64")", ns);
ASSERT_SELF(packet_conn, UDP);
socket->read_deadline_ns = ns;
@@ -907,7 +845,7 @@ static void w5500_udp_alarm_handler(void *_arg) {
cr_sema_signal_from_intrhandler(&socket->read_sema);
}
-static ssize_t w5500_udp_recvfrom(implements_net_packet_conn *_socket, void *buf, size_t count,
+static ssize_t w5500_udp_recvfrom(struct _w5500_socket *socket, void *buf, size_t count,
struct net_ip4_addr *ret_node, uint16_t *ret_port) {
debugf("udp_conn.recvfrom()");
ASSERT_SELF(packet_conn, UDP);
@@ -916,23 +854,23 @@ static ssize_t w5500_udp_recvfrom(implements_net_packet_conn *_socket, void *buf
struct alarmclock_trigger trigger = {0};
if (socket->read_deadline_ns)
- VCALL(bootclock, add_trigger, &trigger,
- socket->read_deadline_ns,
- w5500_udp_alarm_handler,
- socket);
+ LO_CALL(bootclock, add_trigger, &trigger,
+ socket->read_deadline_ns,
+ w5500_udp_alarm_handler,
+ socket);
/* Wait until there is data to read. */
uint16_t avail = 0;
for (;;) {
- if (socket->read_deadline_ns && socket->read_deadline_ns <= VCALL(bootclock, get_time_ns)) {
- VCALL(bootclock, del_trigger, &trigger);
+ if (socket->read_deadline_ns && socket->read_deadline_ns <= LO_CALL(bootclock, get_time_ns)) {
+ LO_CALL(bootclock, del_trigger, &trigger);
debugf(" => recv timeout");
return -NET_ERECV_TIMEOUT;
}
cr_mutex_lock(&chip->mu);
uint8_t state = w5500ll_read_sock_reg(chip->spidev, socknum, state);
if (state != STATE_UDP) {
- VCALL(bootclock, del_trigger, &trigger);
+ LO_CALL(bootclock, del_trigger, &trigger);
debugf(" => hard closed");
return -NET_ECLOSED;
}
@@ -970,12 +908,12 @@ static ssize_t w5500_udp_recvfrom(implements_net_packet_conn *_socket, void *buf
w5500ll_write_sock_reg(chip->spidev, socknum, rx_read_pointer, uint16be_marshal(ptr+8+len));
w5500_socket_cmd(socket, CMD_RECV);
/* Return. */
- VCALL(bootclock, del_trigger, &trigger);
+ LO_CALL(bootclock, del_trigger, &trigger);
cr_mutex_unlock(&chip->mu);
return len;
}
-static int w5500_udp_close(implements_net_packet_conn *_socket) {
+static int w5500_udp_close(struct _w5500_socket *socket) {
debugf("udp_conn.close()");
ASSERT_SELF(packet_conn, UDP);
diff --git a/libhw/w5500_ll.h b/libhw/w5500_ll.h
index 25aa6b5..92d9f14 100644
--- a/libhw/w5500_ll.h
+++ b/libhw/w5500_ll.h
@@ -3,7 +3,7 @@
* Based entirely on the W5500 datasheet, v1.1.0.
* https://docs.wiznet.io/img/products/w5500/W5500_ds_v110e.pdf
*
- * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com>
+ * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
@@ -15,10 +15,9 @@
#include <libmisc/assert.h> /* for assert(), static_assert() */
#include <libmisc/endian.h> /* for uint16be_t */
-#include <libmisc/vcall.h> /* for VCALL() */
#include <libhw/generic/net.h> /* for struct net_eth_addr, struct net_ip4_addr */
-#include <libhw/generic/spi.h> /* for implements_spi */
+#include <libhw/generic/spi.h> /* for lo_interface spi */
/* Config *********************************************************************/
@@ -74,8 +73,8 @@ _w5500ll_write(const char *func,
#else
w5500ll_write(
#endif
- implements_spi *spidev, uint16_t addr, uint8_t block, void *data, size_t data_len) {
- assert(spidev);
+ lo_interface spi spidev, uint16_t addr, uint8_t block, void *data, size_t data_len) {
+ assert(!LO_IS_NULL(spidev));
assert((block & ~CTL_MASK_BLOCK) == 0);
assert(data);
assert(data_len);
@@ -94,7 +93,7 @@ w5500ll_write(
{.iov_read_dst = NULL, .iov_write_src = header, .iov_len = sizeof(header)},
{.iov_read_dst = NULL, .iov_write_src = data, .iov_len = data_len},
};
- VCALL(spidev, readwritev, iov, 2);
+ LO_CALL(spidev, readwritev, iov, 2);
}
static inline void
@@ -104,8 +103,8 @@ _w5500ll_read(const char *func,
#else
w5500ll_read(
#endif
- implements_spi *spidev, uint16_t addr, uint8_t block, void *data, size_t data_len) {
- assert(spidev);
+ lo_interface spi spidev, uint16_t addr, uint8_t block, void *data, size_t data_len) {
+ assert(!LO_IS_NULL(spidev));
assert((block & ~CTL_MASK_BLOCK) == 0);
assert(data);
assert(data_len);
@@ -124,7 +123,7 @@ w5500ll_read(
{.iov_read_dst = NULL, .iov_write_src = header, .iov_len = sizeof(header)},
{.iov_read_dst = data, .iov_write_src = NULL, .iov_len = data_len},
};
- VCALL(spidev, readwritev, iov, 2);
+ LO_CALL(spidev, readwritev, iov, 2);
}
/* Common chip-wide registers. ***********************************************/
diff --git a/libhw_generic/CMakeLists.txt b/libhw_generic/CMakeLists.txt
index 0356770..e38fbe9 100644
--- a/libhw_generic/CMakeLists.txt
+++ b/libhw_generic/CMakeLists.txt
@@ -1,12 +1,13 @@
# libhw_generic/CMakeLists.txt - UAPI device interfaces
#
-# Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com>
+# Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com>
# SPDX-License-Identifier: AGPL-3.0-or-later
add_library(libhw_generic INTERFACE)
target_include_directories(libhw_generic SYSTEM INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/include)
target_link_libraries(libhw_generic INTERFACE
libmisc
+ libobj
libcr
)
diff --git a/libhw_generic/alarmclock.c b/libhw_generic/alarmclock.c
index a16f2f6..7fd049e 100644
--- a/libhw_generic/alarmclock.c
+++ b/libhw_generic/alarmclock.c
@@ -1,24 +1,25 @@
/* libhw_generic/alarmclock.c - Device-independent <libhw/generic/alarmclock.h> utilities
*
- * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com>
+ * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
#include <libcr/coroutine.h>
-#include <libmisc/vcall.h>
#include <libhw/generic/alarmclock.h>
+lo_interface alarmclock bootclock = {0};
+
static void alarmclock_sleep_intrhandler(void *_arg) {
cid_t cid = *(cid_t *)_arg;
cr_unpause_from_intrhandler(cid);
}
-void alarmclock_sleep_until_ns(implements_alarmclock *clock, uint64_t abstime_ns) {
+void alarmclock_sleep_until_ns(lo_interface alarmclock clock, uint64_t abstime_ns) {
bool saved = cr_save_and_disable_interrupts();
cid_t cid = cr_getcid();
struct alarmclock_trigger trigger;
- VCALL(clock, add_trigger, &trigger, abstime_ns, alarmclock_sleep_intrhandler, &cid);
+ LO_CALL(clock, add_trigger, &trigger, abstime_ns, alarmclock_sleep_intrhandler, &cid);
cr_pause_and_yield();
cr_restore_interrupts(saved);
}
diff --git a/libhw_generic/include/libhw/generic/alarmclock.h b/libhw_generic/include/libhw/generic/alarmclock.h
index a9d816b..3817b4b 100644
--- a/libhw_generic/include/libhw/generic/alarmclock.h
+++ b/libhw_generic/include/libhw/generic/alarmclock.h
@@ -1,6 +1,6 @@
/* libhw/generic/alarmclock.h - Device-independent alarmclock definitions
*
- * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com>
+ * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
@@ -11,7 +11,7 @@
#include <stdint.h> /* for uint{n}_t and UINT{n}_C */
#include <libmisc/private.h>
-#include <libmisc/vcall.h>
+#include <libobj/obj.h>
/* Useful constants ***********************************************************/
@@ -35,59 +35,53 @@ struct alarmclock_trigger {
/* Interface ******************************************************************/
-struct alarmclock_vtable;
-
-typedef struct {
- struct alarmclock_vtable *vtable;
-} implements_alarmclock;
-
-struct alarmclock_vtable {
- /**
- * (2⁶⁴-1 nanoseconds is more than 500 years; there is little
- * risk of this overflowing)
- */
- uint64_t (*get_time_ns)(implements_alarmclock *self);
-
- /**
- * Returns true on error.
- *
- * Implementations may return an error if fire_at_ns is more
- * than UINT32_MAX µs (72 minutes) in the future.
- *
- * If fire_at_ns is in the past, then it will fire
- * immediately.
- */
- bool (*add_trigger)(implements_alarmclock *self, struct alarmclock_trigger *trigger,
- uint64_t fire_at_ns,
- void (*cb)(void *),
- void *cb_arg);
-
- void (*del_trigger)(implements_alarmclock *self, struct alarmclock_trigger *trigger);
-};
+#define alarmclock_LO_IFACE \
+ /** \
+ * (2⁶⁴-1 nanoseconds is more than 500 years; there is little \
+ * risk of this overflowing) \
+ */ \
+ LO_FUNC(uint64_t, get_time_ns) \
+ \
+ /** \
+ * Returns true on error. \
+ * \
+ * Implementations may return an error if fire_at_ns is more \
+ * than UINT32_MAX µs (72 minutes) in the future. \
+ * \
+ * If fire_at_ns is in the past, then it will fire \
+ * immediately. \
+ */ \
+ LO_FUNC(bool, add_trigger, struct alarmclock_trigger *trigger, \
+ uint64_t fire_at_ns, \
+ void (*cb)(void *), \
+ void *cb_arg) \
+ \
+ LO_FUNC(void, del_trigger, struct alarmclock_trigger *trigger)
+LO_INTERFACE(alarmclock)
/* Utilities ******************************************************************/
-void alarmclock_sleep_until_ns(implements_alarmclock *clock, uint64_t abstime_ns);
+void alarmclock_sleep_until_ns(lo_interface alarmclock clock, uint64_t abstime_ns);
-static inline void alarmclock_sleep_for_ns(implements_alarmclock *clock, uint64_t delta_ns) {
- alarmclock_sleep_until_ns(clock, VCALL(clock, get_time_ns) + delta_ns);
+static inline void alarmclock_sleep_for_ns(lo_interface alarmclock clock, uint64_t delta_ns) {
+ alarmclock_sleep_until_ns(clock, LO_CALL(clock, get_time_ns) + delta_ns);
}
-static inline void alarmclock_sleep_for_us(implements_alarmclock *clock, uint64_t delta_us) {
+static inline void alarmclock_sleep_for_us(lo_interface alarmclock clock, uint64_t delta_us) {
alarmclock_sleep_for_ns(clock, delta_us * (NS_PER_S/US_PER_S));
}
-static inline void alarmclock_sleep_for_ms(implements_alarmclock *clock, uint64_t delta_ms) {
+static inline void alarmclock_sleep_for_ms(lo_interface alarmclock clock, uint64_t delta_ms) {
alarmclock_sleep_for_ns(clock, delta_ms * (NS_PER_S/MS_PER_S));
}
-static inline void alarmclock_sleep_for_s(implements_alarmclock *clock, uint64_t delta_s) {
+static inline void alarmclock_sleep_for_s(lo_interface alarmclock clock, uint64_t delta_s) {
alarmclock_sleep_for_ns(clock, delta_s * NS_PER_S);
}
/* Globals ********************************************************************/
-extern implements_alarmclock *bootclock;
+extern lo_interface alarmclock bootclock;
#define sleep_until_ns(t) alarmclock_sleep_until_ns(bootclock, t)
#define sleep_for_ns(t) alarmclock_sleep_for_ns(bootclock, t)
diff --git a/libhw_generic/include/libhw/generic/net.h b/libhw_generic/include/libhw/generic/net.h
index 0f9872e..c888735 100644
--- a/libhw_generic/include/libhw/generic/net.h
+++ b/libhw_generic/include/libhw/generic/net.h
@@ -1,6 +1,6 @@
/* libhw/generic/net.h - Device-independent network definitions
*
- * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com>
+ * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
@@ -13,6 +13,8 @@
#include <stdint.h> /* for uint{n}_t} */
#include <sys/types.h> /* for ssize_t */
+#include <libobj/obj.h>
+
/* Errnos *********************************************************************/
#define NET_EOTHER 1
@@ -54,97 +56,81 @@ struct net_eth_addr {
/* Streams (e.g. TCP) *********************************************************/
-struct net_stream_listener_vtable;
-struct net_stream_conn_vtable;
-
-typedef struct {
- struct net_stream_listener_vtable *vtable;
-} implements_net_stream_listener;
-
-typedef struct {
- struct net_stream_conn_vtable *vtable;
-} implements_net_stream_conn;
-
-struct net_stream_listener_vtable {
- /**
- * It is invalid to accept() a new connection if an existing
- * connection is still open.
- */
- implements_net_stream_conn *(*accept)(implements_net_stream_listener *self);
-
- /**
- * The net_stream_conn returned from accept() may still be
- * valid after the listener is closed.
- *
- * Return 0 on success, -errno on error.
- */
- int (*close)(implements_net_stream_listener *self);
-};
-
-struct net_stream_conn_vtable {
- /**
- * Return bytes-read on success, 0 on EOF, -errno on error; a
- * short read is *not* an error.
- */
- ssize_t (*read)(implements_net_stream_conn *self,
- void *buf, size_t count);
-
- /**
- * Set a timestamp after which calls to read() will return
- * NET_ETIMEDOUT. The timestamp is in nanoseconds on the
- * system monotonic clock, which is usually (on pico-sdk and
- * on the Linux kernel) nanoseconds-since-boot.
- *
- * A zero value disables the deadline.
- *
- * (2⁶⁴-1 nanoseconds is more than 500 years; there is little
- * risk of this overflowing)
- */
- void (*set_read_deadline)(implements_net_stream_conn *self,
- uint64_t ns_since_boot);
-
- /**
- * Return `count` on success, -errno on error; a short write *is* an
- * error.
- *
- * Writes are *not* guaranteed to be atomic (as this would be
- * expensive to implement), so if you have concurrent writers then you
- * should arrange for a mutex to protect the connection.
- */
- ssize_t (*write)(implements_net_stream_conn *self,
- void *buf, size_t count);
-
- /**
- * Return 0 on success, -errno on error.
- */
- int (*close)(implements_net_stream_conn *self,
- bool rd, bool wr);
-};
+lo_interface net_stream_conn;
+
+#define net_stream_listener_LO_IFACE \
+ /** \
+ * It is invalid to accept() a new connection if an existing \
+ * connection is still open. \
+ */ \
+ LO_FUNC(lo_interface net_stream_conn, accept) \
+ \
+ /** \
+ * The net_stream_conn returned from accept() may still be \
+ * valid after the listener is closed. \
+ * \
+ * Return 0 on success, -errno on error. \
+ */ \
+ LO_FUNC(int, close)
+LO_INTERFACE(net_stream_listener)
+
+#define net_stream_conn_LO_IFACE \
+ /** \
+ * Return bytes-read on success, 0 on EOF, -errno on error; a \
+ * short read is *not* an error. \
+ */ \
+ LO_FUNC(ssize_t, read, void *buf, size_t count) \
+ \
+ /** \
+ * Set a timestamp after which calls to read() will return \
+ * NET_ETIMEDOUT. The timestamp is in nanoseconds on the \
+ * system monotonic clock, which is usually (on pico-sdk and \
+ * on the Linux kernel) nanoseconds-since-boot. \
+ * \
+ * A zero value disables the deadline. \
+ * \
+ * (2⁶⁴-1 nanoseconds is more than 500 years; there is little \
+ * risk of this overflowing) \
+ */ \
+ LO_FUNC(void, set_read_deadline, uint64_t ns_since_boot) \
+ \
+ /** \
+ * Return `count` on success, -errno on error; a short write *is* an \
+ * error. \
+ * \
+ * Writes are *not* guaranteed to be atomic (as this would be \
+ * expensive to implement), so if you have concurrent writers then you \
+ * should arrange for a mutex to protect the connection. \
+ */ \
+ LO_FUNC(ssize_t, write, void *buf, size_t count) \
+ \
+ /** \
+ * Return 0 on success, -errno on error. \
+ */ \
+ LO_FUNC(int, close, bool rd, bool wr)
+LO_INTERFACE(net_stream_conn)
/* Packets (e.g. UDP) *********************************************************/
-struct net_packet_conn_vtable;
-
-typedef struct {
- struct net_packet_conn_vtable *vtable;
-} implements_net_packet_conn;
-
-struct net_packet_conn_vtable {
- ssize_t (*sendto )(implements_net_packet_conn *self,
- void *buf, size_t len,
- struct net_ip4_addr node, uint16_t port);
- /**
- * @return The full length of the message, which may be more
- * than the given `len` (as if the Linux MSG_TRUNC flag were
- * given).
- */
- ssize_t (*recvfrom)(implements_net_packet_conn *self,
- void *buf, size_t len,
- struct net_ip4_addr *ret_node, uint16_t *ret_port);
- void (*set_read_deadline)(implements_net_packet_conn *self,
- uint64_t ns_since_boot);
- int (*close )(implements_net_packet_conn *self);
-};
+#define net_packet_conn_LO_IFACE \
+ LO_FUNC(ssize_t, sendto, \
+ void *buf, size_t len, \
+ struct net_ip4_addr node, uint16_t port) \
+ \
+ /** \
+ * @return The full length of the message, which may be more \
+ * than the given `len` (as if the Linux MSG_TRUNC flag were \
+ * given). \
+ */ \
+ LO_FUNC(ssize_t, recvfrom, \
+ void *buf, size_t len, \
+ struct net_ip4_addr *ret_node, uint16_t *ret_port) \
+ \
+ LO_FUNC(void, set_read_deadline, \
+ uint64_t ns_since_boot) \
+ \
+ LO_FUNC(int, close)
+LO_INTERFACE(net_packet_conn)
/* Interfaces *****************************************************************/
@@ -154,20 +140,14 @@ struct net_iface_config {
struct net_ip4_addr subnet_mask;
};
-struct net_iface_vtable;
-
-typedef struct {
- struct net_iface_vtable *vtable;
-} implements_net_iface;
-
-struct net_iface_vtable {
- struct net_eth_addr (*hwaddr )(implements_net_iface *);
- void (*ifup )(implements_net_iface *, struct net_iface_config);
- void (*ifdown )(implements_net_iface *);
-
- implements_net_stream_listener *(*tcp_listen)(implements_net_iface *, uint16_t local_port);
- implements_net_stream_conn *(*tcp_dial )(implements_net_iface *, struct net_ip4_addr remote_node, uint16_t remote_port);
- implements_net_packet_conn *(*udp_conn )(implements_net_iface *, uint16_t local_port);
-};
+#define net_iface_LO_IFACE \
+ LO_FUNC(struct net_eth_addr , hwaddr ) \
+ LO_FUNC(void , ifup , struct net_iface_config) \
+ LO_FUNC(void , ifdown ) \
+ \
+ LO_FUNC(lo_interface net_stream_listener, tcp_listen, uint16_t local_port) \
+ LO_FUNC(lo_interface net_stream_conn , tcp_dial , struct net_ip4_addr remote_node, uint16_t remote_port) \
+ LO_FUNC(lo_interface net_packet_conn , udp_conn , uint16_t local_port)
+LO_INTERFACE(net_iface)
#endif /* _LIBHW_GENERIC_NET_H_ */
diff --git a/libhw_generic/include/libhw/generic/spi.h b/libhw_generic/include/libhw/generic/spi.h
index 2207a2c..aeeca37 100644
--- a/libhw_generic/include/libhw/generic/spi.h
+++ b/libhw_generic/include/libhw/generic/spi.h
@@ -1,6 +1,6 @@
/* libhw/generic/spi.h - Device-independent SPI definitions
*
- * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com>
+ * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
@@ -9,6 +9,8 @@
#include <stddef.h> /* for size_t */
+#include <libobj/obj.h>
+
enum spi_mode {
SPI_MODE_0 = 0, /* clk_polarity=0 (idle low), clk_phase=0 (sample on rise) */
SPI_MODE_1 = 1, /* clk_polarity=0 (idle low), clk_phase=1 (sample on fall) */
@@ -22,12 +24,6 @@ struct bidi_iovec {
size_t iov_len;
};
-struct spi_vtable;
-
-typedef struct {
- struct spi_vtable *vtable;
-} implements_spi;
-
/* This API assumes that an SPI frame is a multiple of 8-bits.
*
* It is my understanding that this is a common constraint of SPI
@@ -40,8 +36,8 @@ typedef struct {
* octets; so we have no need for an API that allows a
* non-multiple-of-8 number of bits.
*/
-struct spi_vtable {
- void (*readwritev)(implements_spi *, const struct bidi_iovec *iov, int iovcnt);
-};
+#define spi_LO_IFACE \
+ LO_FUNC(void, readwritev, const struct bidi_iovec *iov, int iovcnt)
+LO_INTERFACE(spi)
#endif /* _LIBHW_GENERIC_SPI_H_ */
diff --git a/libmisc/CMakeLists.txt b/libmisc/CMakeLists.txt
index 8d842c3..f8f15bc 100644
--- a/libmisc/CMakeLists.txt
+++ b/libmisc/CMakeLists.txt
@@ -1,6 +1,6 @@
-# libmisc/CMakeLists.txt - A simple Go-ish object system built on GCC -fplan9-extensions
+# libmisc/CMakeLists.txt - TODO
#
-# Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com>
+# Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com>
# SPDX-License-Identifier: AGPL-3.0-or-later
add_library(libmisc INTERFACE)
@@ -20,4 +20,3 @@ add_lib_test(libmisc test_log)
add_lib_test(libmisc test_macro)
add_lib_test(libmisc test_private)
add_lib_test(libmisc test_rand)
-add_lib_test(libmisc test_vcall)
diff --git a/libmisc/include/libmisc/macro.h b/libmisc/include/libmisc/macro.h
index 9bb068f..d11b99f 100644
--- a/libmisc/include/libmisc/macro.h
+++ b/libmisc/include/libmisc/macro.h
@@ -1,6 +1,6 @@
/* libmisc/macro.h - Useful C preprocessor macros
*
- * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com>
+ * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
@@ -36,6 +36,7 @@
/* macro arguments */
+#define LM_FIRST(a, ...) a
#define LM_SECOND(a, b, ...) b
#define LM_EAT(...)
#define LM_EXPAND(...) __VA_ARGS__
@@ -52,4 +53,42 @@
#define _LM_IF__xxTxx(...) __VA_ARGS__ LM_EAT
#define _LM_IF__xxFxx(...) LM_EXPAND
+/* tuples */
+
+#define LM_IS_TUPLE(x) LM_IS_SENTINEL(_LM_IS_TUPLE x)
+#define _LM_IS_TUPLE(...) LM_SENTINEL()
+
+/* `tuples` is a sequence of `(tuple1)(tuple2)(tuple3)` */
+#define _LM_TUPLES_COMMA(tuple...) (tuple),
+#define LM_TUPLES_NONEMPTY(tuples) LM_IS_TUPLE(_LM_TUPLES_COMMA tuples)
+#define LM_TUPLES_HEAD(tuples) LM_EXPAND(LM_FIRST LM_EAT() (_LM_TUPLES_COMMA tuples))
+#define LM_TUPLES_TAIL(tuples) LM_EAT tuples
+
+/* iteration */
+
+/* BUG: LM_FOREACH_TUPLE maxes out at 1024 tuples. */
+#define LM_FOREACH_TUPLE(tuples, func, ...) \
+ _LM_EVAL(_LM_FOREACH_TUPLE(tuples, func, __VA_ARGS__))
+#define _LM_FOREACH_TUPLE(tuples, func, ...) \
+ LM_IF(LM_TUPLES_NONEMPTY(tuples))( \
+ _LM_DEFER2(func)(__VA_ARGS__ __VA_OPT__(,) LM_EXPAND LM_TUPLES_HEAD(tuples)) \
+ _LM_DEFER2(_LM_FOREACH_TUPLE_indirect)()(LM_TUPLES_TAIL(tuples), func, __VA_ARGS__) \
+ )()
+#define _LM_FOREACH_TUPLE_indirect() _LM_FOREACH_TUPLE
+
+#define _LM_DEFER2(macro) macro LM_EAT LM_EAT()()
+
+#define _LM_EVAL(...) _LM_EVAL__1024(__VA_ARGS__) /* 1024 iterations aught to be enough for anybody */
+#define _LM_EVAL__1024(...) _LM_EVAL__512(_LM_EVAL__512(__VA_ARGS__))
+#define _LM_EVAL__512(...) _LM_EVAL__256(_LM_EVAL__256(__VA_ARGS__))
+#define _LM_EVAL__256(...) _LM_EVAL__128(_LM_EVAL__128(__VA_ARGS__))
+#define _LM_EVAL__128(...) _LM_EVAL__64(_LM_EVAL__64(__VA_ARGS__))
+#define _LM_EVAL__64(...) _LM_EVAL__32(_LM_EVAL__32(__VA_ARGS__))
+#define _LM_EVAL__32(...) _LM_EVAL__16(_LM_EVAL__16(__VA_ARGS__))
+#define _LM_EVAL__16(...) _LM_EVAL__8(_LM_EVAL__8(__VA_ARGS__))
+#define _LM_EVAL__8(...) _LM_EVAL__4(_LM_EVAL__4(__VA_ARGS__))
+#define _LM_EVAL__4(...) _LM_EVAL__2(_LM_EVAL__2(__VA_ARGS__))
+#define _LM_EVAL__2(...) _LM_EVAL__1(_LM_EVAL__1(__VA_ARGS__))
+#define _LM_EVAL__1(...) __VA_ARGS__
+
#endif /* _LIBMISC_MACRO_H_ */
diff --git a/libmisc/include/libmisc/vcall.h b/libmisc/include/libmisc/vcall.h
deleted file mode 100644
index 31a8c7e..0000000
--- a/libmisc/include/libmisc/vcall.h
+++ /dev/null
@@ -1,28 +0,0 @@
-/* libmisc/vcall.h - A simple Go-ish object system built on GCC -fplan9-extensions
- *
- * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com>
- * SPDX-License-Identifier: AGPL-3.0-or-later
- */
-
-#ifndef _LIBMISC_VCALL_H_
-#define _LIBMISC_VCALL_H_
-
-#include <stddef.h> /* for offsetof() */
-
-#include <libmisc/assert.h>
-
-#define VCALL(o, m, ...) \
- ({ \
- assert(o); \
- (o)->vtable->m(o __VA_OPT__(,) __VA_ARGS__); \
- })
-
-#define VCALL_SELF(obj_typ, iface_typ, iface_ptr) \
- ({ \
- static_assert(_Generic(iface_ptr, iface_typ *: 1, default: 0), \
- "typeof("#iface_ptr") != "#iface_typ" *"); \
- assert(iface_ptr); \
- ((obj_typ*)(((void*)iface_ptr)-offsetof(obj_typ,iface_typ))); \
- })
-
-#endif /* _LIBMISC_VCALL_H_ */
diff --git a/libmisc/tests/test_vcall.c b/libmisc/tests/test_vcall.c
deleted file mode 100644
index f36fc4b..0000000
--- a/libmisc/tests/test_vcall.c
+++ /dev/null
@@ -1,74 +0,0 @@
-/* libmisc/tests/test_vcall.c - Tests for <libmisc/vcall.h>
- *
- * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com>
- * SPDX-License-Identifier: AGPL-3.0-or-later
- */
-
-#include <libmisc/assert.h>
-#include <libmisc/vcall.h>
-
-#include "test.h"
-
-/******************************************************************************/
-
-struct frobber_vtable;
-
-typedef struct {
- struct frobber_vtable *vtable;
-} implements_frobber;
-
-struct frobber_vtable {
- int (*frob)(implements_frobber *);
- int (*frob1)(implements_frobber *, int);
- void (*frob0)(implements_frobber *);
-};
-
-/******************************************************************************/
-
-struct myclass {
- int a;
- implements_frobber;
-};
-static_assert(offsetof(struct myclass, implements_frobber) != 0);
-
-static int myclass_frob(implements_frobber *_self) {
- struct myclass *self = VCALL_SELF(struct myclass, implements_frobber, _self);
- test_assert(self);
- test_assert((void*)self != (void*)_self);
- return self->a;
-}
-
-static int myclass_frob1(implements_frobber *_self, int arg) {
- struct myclass *self = VCALL_SELF(struct myclass, implements_frobber, _self);
- test_assert(self);
- test_assert((void*)self != (void*)_self);
- return arg;
-}
-
-static void myclass_frob0(implements_frobber *_self) {
- struct myclass *self = VCALL_SELF(struct myclass, implements_frobber, _self);
- test_assert(self);
- test_assert((void*)self != (void*)_self);
-}
-
-struct frobber_vtable myclass_vtable = {
- .frob = myclass_frob,
- .frob1 = myclass_frob1,
- .frob0 = myclass_frob0,
-};
-
-/******************************************************************************/
-
-#define MAGIC1 909837
-#define MAGIC2 657441
-
-int main() {
- struct myclass obj = {
- .implements_frobber = { .vtable = &myclass_vtable },
- .a = MAGIC1,
- };
- test_assert(VCALL(&obj, frob) == MAGIC1);
- test_assert(VCALL(&obj, frob1, MAGIC2) == MAGIC2);
- VCALL(&obj, frob0);
- return 0;
-}
diff --git a/libobj/CMakeLists.txt b/libobj/CMakeLists.txt
new file mode 100644
index 0000000..1cc552c
--- /dev/null
+++ b/libobj/CMakeLists.txt
@@ -0,0 +1,14 @@
+# libobj/CMakeLists.txt - A simple Go-ish object system built on GCC -fplan9-extensions
+#
+# Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com>
+# SPDX-License-Identifier: AGPL-3.0-or-later
+
+add_library(libobj INTERFACE)
+target_include_directories(libobj SYSTEM INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/include)
+target_link_libraries(libobj INTERFACE
+ libmisc
+)
+target_compile_options(libobj INTERFACE "$<$<COMPILE_LANGUAGE:C>:-fplan9-extensions>")
+
+add_lib_test(libobj test_obj)
+add_lib_test(libobj test_nest)
diff --git a/libobj/include/libobj/obj.h b/libobj/include/libobj/obj.h
new file mode 100644
index 0000000..d8a528a
--- /dev/null
+++ b/libobj/include/libobj/obj.h
@@ -0,0 +1,154 @@
+/* libobj/obj.h - A simple Go-ish object system
+ *
+ * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com>
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+#ifndef _LIBOBJ_OBJ_H_
+#define _LIBOBJ_OBJ_H_
+
+#include <libmisc/macro.h>
+
+/**
+ * Use `lo_interface` similarly to how you would use
+ * `struct`/`enum`/`union` when writing the type of an interface
+ * value.
+ */
+#define lo_interface struct
+
+/**
+ * Use `LO_INTERFACE` in a .h file to define an interface.
+ *
+ * First define a macro named `{iface_name}_LO_IFACE` consisting of a
+ * series of calls to LO_NEST and/or LO_FUNC, then call
+ * `LO_INTERFACE({iface_name})`:
+ *
+ * #define myiface_LO_IFACE \
+ * LO_NEST(wrapped_iface_name) \
+ * LO_FUNC(ret_type, func_name, args...)
+ * LO_INTERFACE(myiface)
+ *
+ * Use `lo_interface {iface_name}` as the type of this interface; it
+ * should not be a pointer type.
+ *
+ * If there are any LO_NEST interfaces, this will define a
+ * `lo_box_{iface_name}_as_{wrapped_iface_name}(obj)` function for
+ * each.
+ */
+#define LO_NEST(_ARG_child_iface_name) \
+ (lo_nest, _ARG_child_iface_name)
+#define LO_FUNC(_ARG_ret_type, _ARG_func_name, ...) \
+ (lo_func, _ARG_ret_type, _ARG_func_name __VA_OPT__(,) __VA_ARGS__)
+#define LO_INTERFACE(_ARG_iface_name) \
+ typedef struct { \
+ LM_FOREACH_TUPLE(_ARG_iface_name##_LO_IFACE, \
+ _LO_IFACE_VTABLE) \
+ } _lo_##_ARG_iface_name##_vtable; \
+ struct _ARG_iface_name { \
+ void *self; \
+ const _lo_##_ARG_iface_name##_vtable *vtable; \
+ }; \
+ LM_FOREACH_TUPLE(_ARG_iface_name##_LO_IFACE, \
+ _LO_IFACE_PROTO, _ARG_iface_name)
+#define _LO_IFACE_VTABLE(_tuple_typ, ...) \
+ _LO_IFACE_VTABLE_##_tuple_typ(__VA_ARGS__)
+#define _LO_IFACE_VTABLE_lo_nest(_ARG_child_iface_name) \
+ _lo_##_ARG_child_iface_name##_vtable;
+#define _LO_IFACE_VTABLE_lo_func(_ARG_ret_type, _ARG_func_name, ...) \
+ _ARG_ret_type (*_ARG_func_name)(void * __VA_OPT__(,) __VA_ARGS__);
+#define _LO_IFACE_PROTO(_ARG_iface_name, _tuple_typ, ...) \
+ _LO_IFACE_PROTO_##_tuple_typ(_ARG_iface_name, __VA_ARGS__)
+#define _LO_IFACE_PROTO_lo_nest(_ARG_iface_name, _ARG_child_iface_name) \
+ LM_ALWAYS_INLINE static lo_interface _ARG_child_iface_name \
+ box_##_ARG_iface_name##_as_##_ARG_child_iface_name(lo_interface _ARG_iface_name obj) { \
+ return (lo_interface _ARG_child_iface_name){ \
+ .self = obj.self, \
+ .vtable = &obj.vtable->_lo_##_ARG_child_iface_name##_vtable, \
+ }; \
+ }
+#define _LO_IFACE_PROTO_lo_func(_ARG_iface_name, _ARG_ret_type, _ARG_func_name, ...) \
+ /* empty */
+
+/**
+ * `LO_NULL(iface_name)` is the null/nil/zero value for `lo_interface {iface_name}`.
+ */
+#define LO_NULL(_ARG_iface_name) ((lo_interface _ARG_iface_name){0})
+
+/**
+ * `LO_IS_NULL(iface_val)` returns whether `iface_val` is LO_NULL.
+ */
+#define LO_IS_NULL(_ARG_iface_val) ((_ARG_iface_val).vtable == NULL)
+
+/**
+ * `LO_IFACE_EQ(a, b)` returns whether the interface values `a` and
+ * `b` are the same object.
+ */
+#define LO_EQ(_ARG_iface_val_a, _ARG_iface_val_b) \
+ ((_ARG_iface_val_a).self == (_ARG_iface_val_b).self)
+
+/**
+ * Use LO_CALL(obj, method_name, args...) to call a method on an `lo_interface`.
+ */
+#define LO_CALL(_ARG_obj, _ARG_meth, ...) \
+ (_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}`.
+ *
+ * This will also define a `lo_box_{impl_name}_as_{iface_name}(obj)`
+ * function.
+ *
+ * You must also call the LO_IMPLEMENTATION_C in a single .c file.
+ */
+#define LO_IMPLEMENTATION_H(_ARG_iface_name, _ARG_impl_type, _ARG_impl_name) \
+ /* Vtable. */ \
+ extern const _lo_##_ARG_iface_name##_vtable \
+ _lo_##_ARG_impl_name##_##_ARG_iface_name##_vtable; \
+ /* Boxing. */ \
+ LM_ALWAYS_INLINE static lo_interface _ARG_iface_name \
+ lo_box_##_ARG_impl_name##_as_##_ARG_iface_name(_ARG_impl_type *self) { \
+ return (lo_interface _ARG_iface_name){ \
+ .self = self, \
+ .vtable = &_lo_##_ARG_impl_name##_##_ARG_iface_name##_vtable, \
+ }; \
+ }
+
+/**
+ * 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 _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_lo_nest(_ARG_impl_name, _ARG_child_iface_name) \
+ ._lo_##_ARG_child_iface_name##_vtable = _lo_##_ARG_impl_name##_##_ARG_child_iface_name##_vtable,
+#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,
+
+#endif /* _LIBOBJ_OBJ_H_ */
diff --git a/libobj/tests/test.h b/libobj/tests/test.h
new file mode 120000
index 0000000..2fb1bd5
--- /dev/null
+++ b/libobj/tests/test.h
@@ -0,0 +1 @@
+../../libmisc/tests/test.h \ No newline at end of file
diff --git a/libobj/tests/test_nest.c b/libobj/tests/test_nest.c
new file mode 100644
index 0000000..c9f9eba
--- /dev/null
+++ b/libobj/tests/test_nest.c
@@ -0,0 +1,73 @@
+/* libobj/tests/test_nest.c - Tests for <libobj/obj.h>
+ *
+ * Copyright (C) 2025 Luke T. Shumaker <lukeshu@lukeshu.com>
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+#include <string.h> /* for memcpy() */
+
+#include <libobj/obj.h>
+
+#include "test.h"
+
+/* interfaces *****************************************************************/
+
+#define reader_LO_IFACE \
+ LO_FUNC(ssize_t, read, void *, size_t)
+LO_INTERFACE(reader)
+
+#define writer_LO_IFACE \
+ LO_FUNC(ssize_t, write, void *, size_t)
+LO_INTERFACE(writer)
+
+#define read_writer_LO_IFACE \
+ LO_NEST(reader) \
+ LO_NEST(writer)
+LO_INTERFACE(read_writer)
+
+/* implementation header ******************************************************/
+
+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)
+
+static ssize_t myclass_read(struct myclass *self, void *buf, size_t count) {
+ test_assert(self);
+ if (count > self->len)
+ count = self->len;
+ memcpy(buf, self->buf, count);
+ return count;
+}
+
+static ssize_t myclass_write(struct myclass *self, void *buf, size_t count) {
+ test_assert(self);
+ if (self->len)
+ return -1;
+ if (count > sizeof(self->buf))
+ count = sizeof(self->buf);
+ memcpy(self->buf, buf, count);
+ self->len = count;
+ return count;
+}
+
+/* main test body *************************************************************/
+
+int main() {
+ struct myclass _obj = {0};
+ lo_interface read_writer obj = lo_box_myclass_as_read_writer(&_obj);
+ test_assert(LO_CALL(obj, write, "Hello", 6) == 6);
+ char buf[6] = {0};
+ test_assert(LO_CALL(obj, read, buf, 3) == 3);
+ test_assert(memcmp(buf, "Hel\0\0\0", 6) == 0);
+ return 0;
+}
diff --git a/libobj/tests/test_obj.c b/libobj/tests/test_obj.c
new file mode 100644
index 0000000..89fff68
--- /dev/null
+++ b/libobj/tests/test_obj.c
@@ -0,0 +1,61 @@
+/* libobj/tests/test_obj.c - Tests for <libobj/obj.h>
+ *
+ * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com>
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+#include <libobj/obj.h>
+
+#include "test.h"
+
+/* `lo_inteface frobber` header ***********************************************/
+
+#define frobber_LO_IFACE \
+ /** Basic function. */ \
+ LO_FUNC(int, frob) \
+ /** Function that takes 1 argument. */ \
+ LO_FUNC(int, frob1, int) \
+ /** Function that returns nothing. */ \
+ LO_FUNC(void, frob0)
+LO_INTERFACE(frobber)
+
+/* `struct myclass` header ****************************************************/
+
+struct myclass {
+ int a;
+};
+LO_IMPLEMENTATION_H(frobber, struct myclass, myclass)
+
+/* `struct myclass` implementation ********************************************/
+
+LO_IMPLEMENTATION_C(frobber, struct myclass, myclass, static)
+
+static int myclass_frob(struct myclass *self) {
+ test_assert(self);
+ return self->a;
+}
+
+static int myclass_frob1(struct myclass *self, int arg) {
+ test_assert(self);
+ return arg;
+}
+
+static void myclass_frob0(struct myclass *self) {
+ test_assert(self);
+}
+
+/* main test body *************************************************************/
+
+#define MAGIC1 909837
+#define MAGIC2 657441
+
+int main() {
+ struct myclass obj = {
+ .a = MAGIC1,
+ };
+ lo_interface frobber iface = lo_box_myclass_as_frobber(&obj);
+ test_assert(LO_CALL(iface, frob) == MAGIC1);
+ test_assert(LO_CALL(iface, frob1, MAGIC2) == MAGIC2);
+ LO_CALL(iface, frob0);
+ return 0;
+}