diff options
-rw-r--r-- | libhw/CMakeLists.txt | 3 | ||||
-rw-r--r-- | libhw/common_include/libhw/generic/bootclock.h | 22 | ||||
-rw-r--r-- | libhw/common_include/libhw/generic/net.h | 16 | ||||
-rw-r--r-- | libhw/host_bootclock.c | 20 | ||||
-rw-r--r-- | libhw/host_include/libhw/host_net.h | 4 | ||||
-rw-r--r-- | libhw/host_net.c | 118 | ||||
-rw-r--r-- | libhw/rp2040_bootclock.c | 13 | ||||
-rw-r--r-- | notes.md | 2 |
8 files changed, 173 insertions, 25 deletions
diff --git a/libhw/CMakeLists.txt b/libhw/CMakeLists.txt index 521e697..47ffbdd 100644 --- a/libhw/CMakeLists.txt +++ b/libhw/CMakeLists.txt @@ -12,11 +12,13 @@ target_link_libraries(libhw INTERFACE if (PICO_PLATFORM STREQUAL "rp2040") target_include_directories(libhw SYSTEM INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/rp2040_include) target_sources(libhw INTERFACE + rp2040_bootclock.c rp2040_hwspi.c w5500.c ) target_link_libraries(libhw INTERFACE pico_time + hardware_timer hardware_gpio hardware_spi ) @@ -25,6 +27,7 @@ endif() if (PICO_PLATFORM STREQUAL "host") target_include_directories(libhw SYSTEM INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/host_include) target_sources(libhw INTERFACE + host_bootclock.c host_net.c ) endif() diff --git a/libhw/common_include/libhw/generic/bootclock.h b/libhw/common_include/libhw/generic/bootclock.h new file mode 100644 index 0000000..bea5338 --- /dev/null +++ b/libhw/common_include/libhw/generic/bootclock.h @@ -0,0 +1,22 @@ +/* libhw/generic/bootclock.h - Device-independent clock definitions + * + * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com> + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +#ifndef _LIBHW_GENERIC_BOOTCLOCK_H_ +#define _LIBHW_GENERIC_BOOTCLOCK_H_ + +#include <stdint.h> /* for uint{n}_t and UINT{n}_C */ + +#define NS_PER_S UINT64_C(1000000000) /* needs at least log₂(10⁹) ≈ 29.9 bits */ +#define US_PER_S UINT64_C(1000000) /* needs at least log₂(10⁶) ≈ 19.9 bits */ +#define MS_PER_S UINT64_C(1000) /* needs at least log₂(10³) ≈ 9.9 bits */ + +/** + * (2⁶⁴-1 nanoseconds is more than 500 years; there is little + * risk of this overflowing) + */ +uint64_t bootclock_get_ns(void); + +#endif /* _LIBHW_GENERIC_BOOTCLOCK_H_ */ diff --git a/libhw/common_include/libhw/generic/net.h b/libhw/common_include/libhw/generic/net.h index 419a8f2..af8844f 100644 --- a/libhw/common_include/libhw/generic/net.h +++ b/libhw/common_include/libhw/generic/net.h @@ -61,6 +61,20 @@ struct net_stream_conn_vtable { 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. * @@ -93,6 +107,8 @@ struct net_packet_conn_vtable { 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); }; diff --git a/libhw/host_bootclock.c b/libhw/host_bootclock.c new file mode 100644 index 0000000..ca9565c --- /dev/null +++ b/libhw/host_bootclock.c @@ -0,0 +1,20 @@ +/* libhw/host_bootclock.c - <libhw/generic/bootclock.h> implementation for POSIX hosts + * + * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com> + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +#include <assert.h> +#include <time.h> /* for clock_gettime(), CLOCK_MONOTONIC, struct timespec */ + +#include <libhw/generic/bootclock.h> + +uint64_t bootclock_get_ns(void) { + struct timespec ts; + int r; + + r = clock_gettime(CLOCK_MONOTONIC, &ts); + assert(r == 0); + + return (((uint64_t)ts.tv_sec) * NS_PER_S) + (uint64_t)ts.tv_nsec; +} diff --git a/libhw/host_include/libhw/host_net.h b/libhw/host_include/libhw/host_net.h index 7a0dff4..1b1ba7a 100644 --- a/libhw/host_include/libhw/host_net.h +++ b/libhw/host_include/libhw/host_net.h @@ -15,13 +15,14 @@ struct _hostnet_tcp_conn { implements_net_stream_conn; int fd; + uint64_t read_deadline_ns; }; struct hostnet_tcp_listener { implements_net_stream_listener; int fd; - struct _hostnet_tcp_conn active_conn; + struct _hostnet_tcp_conn active_conn; }; void hostnet_tcp_listener_init(struct hostnet_tcp_listener *self, uint16_t port); @@ -30,6 +31,7 @@ struct hostnet_udp_conn { implements_net_packet_conn; int fd; + uint64_t read_deadline_ns; }; 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 fcdac57..1563ced 100644 --- a/libhw/host_net.c +++ b/libhw/host_net.c @@ -22,6 +22,7 @@ #include <libcr/coroutine.h> #include <libmisc/vcall.h> +#include <libhw/generic/bootclock.h> #include <libhw/host_net.h> /* common *********************************************************************/ @@ -86,18 +87,20 @@ static inline ssize_t hostnet_map_errno(ssize_t v) { /* TCP init() ( AKA socket(3) + listen(3) )************************************/ static implements_net_stream_conn *hostnet_tcp_accept(implements_net_stream_listener *_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 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_tcp_accept, }; static struct net_stream_conn_vtable hostnet_tcp_conn_vtable = { - .read = hostnet_tcp_read, - .write = hostnet_tcp_write, - .close = hostnet_tcp_close, + .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) { @@ -145,7 +148,7 @@ static void *hostnet_pthread_accept(void *_args) { *(args->ret_connfd) = -errno; WAKE_COROUTINE(args); return NULL; -}; +} static implements_net_stream_conn *hostnet_tcp_accept(implements_net_stream_listener *_listener) { struct hostnet_tcp_listener *listener = @@ -169,11 +172,20 @@ static implements_net_stream_conn *hostnet_tcp_accept(implements_net_stream_list /* 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); + assert(conn); + + conn->read_deadline_ns = ts_ns; +} + struct hostnet_pthread_read_args { pthread_t cr_thread; cid_t cr_coroutine; int connfd; + struct timeval timeout; void *buf; size_t count; @@ -182,12 +194,22 @@ struct hostnet_pthread_read_args { static void *hostnet_pthread_read(void *_args) { struct hostnet_pthread_read_args *args = _args; + + *(args->ret) = setsockopt(args->connfd, SOL_SOCKET, SO_RCVTIMEO, + &args->timeout, sizeof(args->timeout)); + if (*(args->ret) < 0) + goto end; + *(args->ret) = read(args->connfd, args->buf, args->count); if (*(args->ret) < 0) + goto end; + + end: + if (*(args->ret) < 0) *(args->ret) = hostnet_map_errno(-errno); WAKE_COROUTINE(args); return NULL; -}; +} static ssize_t hostnet_tcp_read(implements_net_stream_conn *_conn, void *buf, size_t count) { struct _hostnet_tcp_conn *conn = @@ -205,6 +227,20 @@ 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 = bootclock_get_ns(); + if (conn->read_deadline_ns < now_ns) + return -NET_ETIMEDOUT; + uint64_t timeout_ns = conn->read_deadline_ns-now_ns; + args.timeout.tv_sec = timeout_ns + /NS_PER_S; + args.timeout.tv_usec = (timeout_ns - ((uint64_t)args.timeout.tv_sec)*NS_PER_S) + /(NS_PER_S/US_PER_S); + } else { + args.timeout.tv_sec = 0; + args.timeout.tv_usec = 0; + } + if (RUN_PTHREAD(hostnet_pthread_read, &args)) return -NET_ETHREAD; return ret; @@ -238,7 +274,7 @@ static void *hostnet_pthread_write(void *_args) { *(args->ret) = done; WAKE_COROUTINE(args); return NULL; -}; +} static ssize_t hostnet_tcp_write(implements_net_stream_conn *_conn, void *buf, size_t count) { struct _hostnet_tcp_conn *conn = @@ -282,6 +318,8 @@ 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, @@ -289,9 +327,10 @@ static ssize_t hostnet_udp_recvfrom(implements_net_packet_conn *self, void *buf, static int hostnet_udp_close(implements_net_packet_conn *self); static struct net_packet_conn_vtable hostnet_udp_conn_vtable = { - .sendto = hostnet_udp_sendto, - .recvfrom = hostnet_udp_recvfrom, - .close = hostnet_udp_close, + .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) { @@ -354,7 +393,7 @@ static void *hostnet_pthread_sendto(void *_args) { } static ssize_t hostnet_udp_sendto(implements_net_packet_conn *_conn, void *buf, size_t count, - struct net_ip4_addr node, uint16_t port) { + 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); @@ -379,11 +418,21 @@ 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, + 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; +} + struct hostnet_pthread_recvfrom_args { pthread_t cr_thread; cid_t cr_coroutine; int connfd; + struct timeval timeout; void *buf; size_t count; @@ -402,26 +451,35 @@ static void *hostnet_pthread_recvfrom(void *_args) { } addr = { 0 }; socklen_t addr_size; + *(args->ret_size) = setsockopt(args->connfd, SOL_SOCKET, SO_RCVTIMEO, + &args->timeout, sizeof(args->timeout)); + if (*(args->ret_size) < 0) + goto end; + *(args->ret_size) = recvfrom(args->connfd, args->buf, args->count, 0, &addr.gen, &addr_size); if (*(args->ret_size) < 0) - *(args->ret_size) = hostnet_map_errno(-errno); - else { - assert(addr.in.sin_family == AF_INET); - if (args->ret_node) { - args->ret_node->octets[0] = (addr.in.sin_addr.s_addr >> 24) & 0xFF; - args->ret_node->octets[1] = (addr.in.sin_addr.s_addr >> 16) & 0xFF; - args->ret_node->octets[2] = (addr.in.sin_addr.s_addr >> 8) & 0xFF; - args->ret_node->octets[3] = (addr.in.sin_addr.s_addr >> 0) & 0xFF; - } - if (args->ret_port) - (*args->ret_port) = ntohs(addr.in.sin_port); + goto end; + + assert(addr.in.sin_family == AF_INET); + if (args->ret_node) { + args->ret_node->octets[0] = (addr.in.sin_addr.s_addr >> 24) & 0xFF; + args->ret_node->octets[1] = (addr.in.sin_addr.s_addr >> 16) & 0xFF; + args->ret_node->octets[2] = (addr.in.sin_addr.s_addr >> 8) & 0xFF; + args->ret_node->octets[3] = (addr.in.sin_addr.s_addr >> 0) & 0xFF; } + if (args->ret_port) { + (*args->ret_port) = ntohs(addr.in.sin_port); + } + + end: + if (*(args->ret_size) < 0) + *(args->ret_size) = hostnet_map_errno(-errno); WAKE_COROUTINE(args); return NULL; } static ssize_t hostnet_udp_recvfrom(implements_net_packet_conn *_conn, void *buf, size_t count, - struct net_ip4_addr *ret_node, uint16_t *ret_port) { + 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); @@ -439,6 +497,20 @@ static ssize_t hostnet_udp_recvfrom(implements_net_packet_conn *_conn, void *buf .ret_node = ret_node, .ret_port = ret_port, }; + if (conn->read_deadline_ns) { + uint64_t now_ns = bootclock_get_ns(); + if (conn->read_deadline_ns < now_ns) + return -NET_ETIMEDOUT; + uint64_t timeout_ns = conn->read_deadline_ns-now_ns; + args.timeout.tv_sec = timeout_ns + /NS_PER_S; + args.timeout.tv_usec = (timeout_ns - ((uint64_t)args.timeout.tv_sec)*NS_PER_S) + /(NS_PER_S/US_PER_S); + } else { + args.timeout.tv_sec = 0; + args.timeout.tv_usec = 0; + } + if (RUN_PTHREAD(hostnet_pthread_recvfrom, &args)) return -NET_ETHREAD; return ret; diff --git a/libhw/rp2040_bootclock.c b/libhw/rp2040_bootclock.c new file mode 100644 index 0000000..0484910 --- /dev/null +++ b/libhw/rp2040_bootclock.c @@ -0,0 +1,13 @@ +/* libhw/rp2040_bootclock.c - <libhw/generic/bootclock.h> implementation for pico-sdk + * + * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com> + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +#include <hardware/timer.h> /* pico-sdk:hardware_timer */ + +#include <libhw/generic/bootclock.h> + +uint64_t bootclock_get_ns(void) { + return time_us_64()*(NS_PER_S/US_PER_S); +} @@ -15,7 +15,7 @@ Which file to include: | `U{CHAR,SHRT,INT,LONG,LLONG}_MAX` | `<limits.h>` | | |------------------------------------------|-----------------------------------|--------------------------------| | C SIZED INTS | | | -| `(C u)int{n}_t` (and `_{MIN,MAX,C}`) | `<stdint.h>` | exact | +| `(u)int{n}_t` (and `_{MIN,MAX,C}`) | `<stdint.h>` | exact | | `(u)int_least{n}_t` (and `_{MIN,MAX,C}`) | `<stdint.h>` | type may be more than `n` bits | | `(u)int_fast{n}_t` (and `_{MIN,MAX,C}`) | `<stdint.h>` | type may be more than `n` bits | | `(u)intptr_t` (and `_{MIN,MAX,C}`) | `<stdint.h>` | | |