diff options
Diffstat (limited to 'libhw_generic')
-rw-r--r-- | libhw_generic/CMakeLists.txt | 24 | ||||
-rw-r--r-- | libhw_generic/alarmclock.c | 25 | ||||
-rw-r--r-- | libhw_generic/include/libhw/generic/alarmclock.h | 77 | ||||
-rw-r--r-- | libhw_generic/include/libhw/generic/io.h | 225 | ||||
-rw-r--r-- | libhw_generic/include/libhw/generic/net.h | 122 | ||||
-rw-r--r-- | libhw_generic/include/libhw/generic/spi.h | 37 | ||||
-rw-r--r-- | libhw_generic/io.c | 103 | ||||
-rw-r--r-- | libhw_generic/net.c | 49 | ||||
l--------- | libhw_generic/tests/test.h | 1 | ||||
-rw-r--r-- | libhw_generic/tests/test_io.c | 57 |
10 files changed, 720 insertions, 0 deletions
diff --git a/libhw_generic/CMakeLists.txt b/libhw_generic/CMakeLists.txt new file mode 100644 index 0000000..6f5fa34 --- /dev/null +++ b/libhw_generic/CMakeLists.txt @@ -0,0 +1,24 @@ +# libhw_generic/CMakeLists.txt - UAPI device interfaces +# +# Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> +# SPDX-License-Identifier: AGPL-3.0-or-later + +add_library(libhw_generic_headers INTERFACE) +target_include_directories(libhw_generic_headers PUBLIC INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/include) +target_link_libraries(libhw_generic_headers INTERFACE + libmisc_headers +) + +add_library(libhw_generic INTERFACE) +target_link_libraries(libhw_generic INTERFACE libhw_generic_headers) +target_link_libraries(libhw_generic INTERFACE + libmisc +) + +target_sources(libhw_generic INTERFACE + alarmclock.c + io.c + net.c +) + +add_lib_test(libhw_generic test_io) diff --git a/libhw_generic/alarmclock.c b/libhw_generic/alarmclock.c new file mode 100644 index 0000000..3579829 --- /dev/null +++ b/libhw_generic/alarmclock.c @@ -0,0 +1,25 @@ +/* libhw_generic/alarmclock.c - Device-independent <libhw/generic/alarmclock.h> storage + * + * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +#include <libhw/generic/alarmclock.h> + +lo_interface alarmclock bootclock = {}; + +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); +} + +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)); +} + +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)); +} + +void alarmclock_sleep_for_s(lo_interface alarmclock clock, uint64_t delta_s) { + alarmclock_sleep_for_ns(clock, delta_s * NS_PER_S); +} diff --git a/libhw_generic/include/libhw/generic/alarmclock.h b/libhw_generic/include/libhw/generic/alarmclock.h new file mode 100644 index 0000000..e7e66a1 --- /dev/null +++ b/libhw_generic/include/libhw/generic/alarmclock.h @@ -0,0 +1,77 @@ +/* libhw/generic/alarmclock.h - Device-independent alarmclock definitions + * + * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +#ifndef _LIBHW_GENERIC_ALARMCLOCK_H_ +#define _LIBHW_GENERIC_ALARMCLOCK_H_ + +#include <stdint.h> /* for uint{n}_t and UINT{n}_C */ + +#include <libmisc/obj.h> +#include <libmisc/private.h> + +/* Useful constants ***********************************************************/ + +#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 */ + +/* Structs ********************************************************************/ + +struct alarmclock_trigger; +struct alarmclock_trigger { + BEGIN_PRIVATE(LIBHW_GENERIC_ALARMCLOCK_H); + void *alarmclock; + struct alarmclock_trigger *prev, *next; + + uint64_t fire_at_ns; + void (*cb)(void *); + void *cb_arg; + END_PRIVATE(LIBHW_GENERIC_ALARMCLOCK_H); +}; + +/* Interface ******************************************************************/ + +#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. \ + * \ + * If fire_at_ns is in the past, then it will fire \ + * immediately. \ + */ \ + LO_FUNC(void, 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(lo_interface alarmclock clock, uint64_t abstime_ns); + +void alarmclock_sleep_for_ns(lo_interface alarmclock clock, uint64_t delta_ns); +void alarmclock_sleep_for_us(lo_interface alarmclock clock, uint64_t delta_us); +void alarmclock_sleep_for_ms(lo_interface alarmclock clock, uint64_t delta_ms); +void alarmclock_sleep_for_s(lo_interface alarmclock clock, uint64_t delta_s); + +/* Globals ********************************************************************/ + +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) +#define sleep_for_us(t) alarmclock_sleep_for_us(bootclock, t) +#define sleep_for_ms(t) alarmclock_sleep_for_ms(bootclock, t) +#define sleep_for_s(t) alarmclock_sleep_for_s(bootclock, t) + +#endif /* _LIBHW_GENERIC_ALARMCLOCK_H_ */ diff --git a/libhw_generic/include/libhw/generic/io.h b/libhw_generic/include/libhw/generic/io.h new file mode 100644 index 0000000..edaf30c --- /dev/null +++ b/libhw_generic/include/libhw/generic/io.h @@ -0,0 +1,225 @@ +/* libhw/generic/io.h - Device-independent I/O definitions + * + * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +#ifndef _LIBHW_GENERIC_IO_H_ +#define _LIBHW_GENERIC_IO_H_ + +#include <stddef.h> /* for size_t */ +#include <stdint.h> /* for uintptr_t, uint{n}_t, UINT{n}_MAX */ + +#include <libmisc/error.h> +#include <libmisc/obj.h> + +/* structs ********************************************************************/ + +/* uoff_t ==========================================================*/ + +typedef uint64_t uoff_t; +#define UOFF_MAX UINT64_MAX +DECLARE_ERROR_OR(uoff_t); +DECLARE_ERROR_AND(uoff_t); + +/* rd_iovec ========================================================*/ + +struct rd_iovec { + void *iov_read_to; + size_t iov_len; +}; +DECLARE_ERROR_OR_(struct rd_iovec, rd_iovec); +DECLARE_ERROR_AND_(struct rd_iovec, rd_iovec); + +/* wr_iovec ========================================================*/ + +struct wr_iovec { + const void *iov_write_from; + size_t iov_len; +}; +DECLARE_ERROR_OR_(struct wr_iovec, wr_iovec); +DECLARE_ERROR_AND_(struct wr_iovec, wr_iovec); + +/* duplex_iovec ====================================================*/ + +#define IOVEC_DISCARD ((void*)(~((uintptr_t)0))) + +struct duplex_iovec { + /** + * NULL is a valid pointer value in iov_read_to and + * iov_write_from. To skip a read or write, use the special + * value IOVEC_DISCARD. + */ + void *iov_read_to; + const void *iov_write_from; + size_t iov_len; +}; +DECLARE_ERROR_OR_(struct duplex_iovec, duplex_iovec); +DECLARE_ERROR_AND_(struct duplex_iovec, duplex_iovec); + +/* iovec utilities ************************************************************/ + +/* If byte_max_cnt == 0, then there is no maximum. */ + +/* slice iovec lists */ +int io_rd_slice_cnt ( const struct rd_iovec *src, int src_cnt, size_t byte_start, size_t byte_max_cnt); +void io_rd_slice (struct rd_iovec *dst, const struct rd_iovec *src, int src_cnt, size_t byte_start, size_t byte_max_cnt); +int io_wr_slice_cnt ( const struct wr_iovec *src, int src_cnt, size_t byte_start, size_t byte_max_cnt); +void io_wr_slice (struct wr_iovec *dst, const struct wr_iovec *src, int src_cnt, size_t byte_start, size_t byte_max_cnt); +int io_duplex_slice_cnt( const struct duplex_iovec *src, int src_cnt, size_t byte_start, size_t byte_max_cnt); +void io_duplex_slice (struct duplex_iovec *dst, const struct duplex_iovec *src, int src_cnt, size_t byte_start, size_t byte_max_cnt); + +/* convert iovec lists */ +void io_rd_to_duplex(struct duplex_iovec *dst, const struct rd_iovec *src, int iovcnt); +void io_wr_to_duplex(struct duplex_iovec *dst, const struct wr_iovec *src, int iovcnt); + +/* slice and convert in one go */ +void io_slice_rd_to_duplex(struct duplex_iovec *dst, const struct rd_iovec *src, int src_cnt, size_t byte_start, size_t byte_max_cnt); +void io_slice_wr_to_duplex(struct duplex_iovec *dst, const struct wr_iovec *src, int src_cnt, size_t byte_start, size_t byte_max_cnt); + +/* basic interfaces ***********************************************************/ + +/* + * Conventions: + * + * - Naming: + * + The "p"[osition] prefix means an explicit `uoff_t offset`, + * instead of using an internal cursor. + * + The "v"[ec] suffix means a sequence of iovecs, instead of a + * simple buf+len. + * + * - Errors: + * + Short *reads* are *not* errors (and so return size_t *or* + * error). + * + Short *writes* *are* errors (and so return size_t *and* + * (possibly-null) error). + */ + +/* read ============================================================*/ + +/** + * Return bytes-read on success. A short read is *not* an error + * (unlike `write` methods). + * + * It is invalid to call readv when the sum length of iovecs is 0. + */ +#define io_reader_LO_IFACE \ + LO_FUNC(size_t_or_error, readv, const struct rd_iovec *iov, int iovcnt) +LO_INTERFACE(io_reader); +#define io_readv(r, iov, iovcnt) LO_CALL(r, readv, iov, iovcnt) +#define io_read(r, buf, count) LO_CALL(r, readv, &((struct rd_iovec){.iov_read_to = buf, .iov_len = count}), 1) + +/** + * Returns bytes-read on success. A short read is *not* an error + * (unlike `write` methods). + * + * It is invalid to call preadv when the sum length of iovecs is 0. + */ +#define io_preader_LO_IFACE \ + LO_FUNC(size_t_or_error, preadv, const struct rd_iovec *iov, int iovcnt, uoff_t offset) +LO_INTERFACE(io_preader); +#define io_preadv(r, iov, iovcnt, off) LO_CALL(r, preadv, iov, iovcnt, off) +#define io_pread(r, buf, count, off) LO_CALL(r, preadv, &((struct rd_iovec){.iov_read_to = buf, .iov_len = count}), 1, off) + +/* write ===========================================================*/ + +/** + * Return bytes-written. A short write *is* an error (unlike `read` + * methods). + * + * Writes are *not* guaranteed to be atomic, so if you have concurrent + * writers then you should arrange for a mutex to protect the writer. + * + * It is invalid to call writev when the sum length of iovecs is 0. + */ +#define io_writer_LO_IFACE \ + LO_FUNC(size_t_and_error, writev, const struct wr_iovec *iov, int iovcnt) +LO_INTERFACE(io_writer); +#define io_writev(w, iov, iovcnt) LO_CALL(w, writev, iov, iovcnt) +#define io_write(w, buf, count) LO_CALL(w, writev, &((struct wr_iovec){.iov_write_from = buf, .iov_len = count}), 1) + +/** + * Returns bytes-written. A short write *is* an error (unlike `read` + * methods). + * + * Writes are *not* guaranteed to be atomic, so if you have concurrent + * writers then you should arrange for a mutex to protect the writer. + * + * It is invalid to call writev when the sum length of iovecs is 0. + */ +#define io_pwriter_LO_IFACE \ + LO_FUNC(size_t_and_error, pwritev, const struct wr_iovec *iov, int iovcnt, uoff_t offset) +LO_INTERFACE(io_pwriter); +#define io_pwritev(r, iov, iovcnt, off) LO_CALL(r, pwritev, iov, iovcnt, off) +#define io_pwrite(r, buf, count, off) LO_CALL(r, pwritev, &((struct wr_iovec){.iov_write_from = buf, .iov_len = count}), 1, off) + +/* readwrite =======================================================*/ + +/** + * A short read/write *is* an error (like `write` methods and unlike + * `read` methods). + * + * Reads/writes are *not* guaranteed to be atomic, so if you have + * concurrent readers/writers then you should arrange for a mutex to + * protect the duplex_readwriter. + * + * It is invalid to call readwritev when the sum length of iovecs is 0. + */ +#define io_duplex_readwriter_LO_IFACE \ + LO_FUNC(size_t_and_error, readwritev, const struct duplex_iovec *iov, int iovcnt) +LO_INTERFACE(io_duplex_readwriter); +#define io_readwritev(rw, iov, iovcnt) \ + LO_CALL(rw, readwritev, iov, iovcnt) + +/* read then write =================================================*/ + +/** + * LO_CALL(src, pread_to, dst, src_offset, count) is functionally: + * + * size_t_and_error copy(lo_interface io_writer dst, lo_interface io_preader src, uoff_t src_offset, size_t count) { + * buf = malloc(count); + * size_t_or_error read_r = io_pread(src, buf, count, src_offset); + * if (read_r.is_err) + * return ERROR_AND(size_t, 0, read_r.err); + * size_t_and_error write_r = io_write(dst, buf, read_r.size_t); + * free(buf); + * return write_r; + * } + * + * except that it does not need to allocate a buffer. That is: It + * allows the reader to dump its data directly to the writer. This + * allows us to save a copy when the reader already has an in-memory + * buffer containing the data. + * + * The above code defines when a short read/write is an error or not. + * + * It must call writev() exactly 0-or-1 times, and if it does call it, + * then it must be called with exactly 1 iovec. + */ +#define io_preader_to_LO_IFACE \ + LO_FUNC(size_t_and_error, pread_to, lo_interface io_writer dst, uoff_t src_offset, size_t count) +LO_INTERFACE(io_preader_to); + +/* close ===========================================================*/ + +#define io_closer_LO_IFACE \ + LO_FUNC(error, close) +LO_INTERFACE(io_closer); +#define io_close(c) LO_CALL(c, close) + +#define io_bidi_closer_LO_IFACE \ + LO_NEST(io_closer) \ + LO_FUNC(error, close_read) \ + LO_FUNC(error, close_write) +LO_INTERFACE(io_bidi_closer); +#define io_close_read(c) LO_CALL(c, close_read) +#define io_close_write(c) LO_CALL(c, close_write) + +/* aggregate interfaces *******************************************************/ + +#define io_readwriter_LO_IFACE \ + LO_NEST(io_reader) \ + LO_NEST(io_writer) +LO_INTERFACE(io_readwriter); + +#endif /* _LIBHW_GENERIC_IO_H_ */ diff --git a/libhw_generic/include/libhw/generic/net.h b/libhw_generic/include/libhw/generic/net.h new file mode 100644 index 0000000..e2d626f --- /dev/null +++ b/libhw_generic/include/libhw/generic/net.h @@ -0,0 +1,122 @@ +/* libhw/generic/net.h - Device-independent network definitions + * + * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +#ifndef _LIBHW_GENERIC_NET_H_ +#define _LIBHW_GENERIC_NET_H_ + +#include <stddef.h> /* for size_t */ +#include <stdint.h> /* for uint{n}_t} */ + +#include <libhw/generic/io.h> +#include <libmisc/fmt.h> + +/* Address types **************************************************************/ + +struct net_ip4_addr { + unsigned char octets[4]; +}; + +static const struct net_ip4_addr net_ip4_addr_broadcast = {{255, 255, 255, 255}}; +static const struct net_ip4_addr net_ip4_addr_zero = {{0, 0, 0, 0}}; +void fmt_print_net_ip4_addr(lo_interface fmt_dest, struct net_ip4_addr); + +struct net_eth_addr { + unsigned char octets[6]; +}; + +void fmt_print_net_eth_addr(lo_interface fmt_dest, struct net_eth_addr); + +/* Streams (e.g. TCP) *********************************************************/ + +#define net_stream_conn_LO_IFACE \ + LO_NEST(io_readwriter) \ + LO_NEST(io_bidi_closer) \ + \ + /** \ + * 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) +LO_INTERFACE(net_stream_conn); + +DECLARE_ERROR_OR_(lo_interface net_stream_conn, 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(net_stream_conn_or_error, accept) \ + \ + /** \ + * The net_stream_conn returned from accept() may still be \ + * valid after the listener is closed. \ + */ \ + LO_NEST(io_closer) +LO_INTERFACE(net_stream_listener); + +/* Packets (e.g. UDP) *********************************************************/ + +#define net_packet_conn_LO_IFACE \ + LO_FUNC(error, 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(size_t_or_error, recvfrom, \ + void *buf, size_t len, \ + struct net_ip4_addr *ret_node, uint16_t *ret_port) \ + \ + /** \ + * Set a timestamp after which calls to recvfrom() 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_recv_deadline, \ + uint64_t ns_since_boot) \ + \ + LO_NEST(io_closer) +LO_INTERFACE(net_packet_conn); + +/* Interfaces *****************************************************************/ + +struct net_iface_config { + struct net_ip4_addr addr; + struct net_ip4_addr gateway_addr; + struct net_ip4_addr subnet_mask; +}; + +#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(net_stream_conn_or_error , 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) \ + \ + /** FIXME: arp_ping should probably have an explicit timeout or something. */ \ + LO_FUNC(bool , arp_ping , struct net_ip4_addr) +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 new file mode 100644 index 0000000..c0d9d91 --- /dev/null +++ b/libhw_generic/include/libhw/generic/spi.h @@ -0,0 +1,37 @@ +/* libhw/generic/spi.h - Device-independent SPI definitions + * + * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +#ifndef _LIBHW_GENERIC_SPI_H_ +#define _LIBHW_GENERIC_SPI_H_ + +#include <stddef.h> /* for size_t */ + +#include <libhw/generic/io.h> + +enum spi_mode { + SPI_MODE_0 = 0, /* clk_polarity=0 (idle low), clk_phase=0 (sample on transition-from-idle (rise)) ; shift on clock fall and CS fall */ + SPI_MODE_1 = 1, /* clk_polarity=0 (idle low), clk_phase=1 (sample on transition-to-idle (fall)) ; shift on clock rise */ + SPI_MODE_2 = 2, /* clk_polarity=1 (idle high), clk_phase=0 (sample on transition-from-idle (fall)) ; shift on clock rise and CS fall */ + SPI_MODE_3 = 3, /* clk_polarity=1 (idle high), clk_phase=1 (sample on transition-to-idle (rise)) ; shift on clock fall */ +}; + +/* 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 + * hardware, and that the RP2040 is somewhat unusual in that it allows + * frames of any length 4-16 bits (we disconnect the CS pin from the + * PL022 SSP and manually GPIO it from the CPU in order to achieve + * longer frames). + * + * But, more relevantly: The W5500's protocol uses frames that are 4-N + * octets; so we have no need for an API that allows a + * non-multiple-of-8 number of bits. + */ +#define spi_LO_IFACE \ + LO_NEST(io_duplex_readwriter) +LO_INTERFACE(spi); + +#endif /* _LIBHW_GENERIC_SPI_H_ */ diff --git a/libhw_generic/io.c b/libhw_generic/io.c new file mode 100644 index 0000000..a8c9da6 --- /dev/null +++ b/libhw_generic/io.c @@ -0,0 +1,103 @@ +/* libhw_generic/io.c - Utilities for device-independent I/O definitions + * + * Copyright (C) 2025 Luke T. Shumaker <lukeshu@lukeshu.com> + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +#include <libmisc/assert.h> + +#include <libhw/generic/io.h> + +#define IOV_ITER(ACTION) \ + assert(src_cnt >= 0); \ + assert(src_cnt == 0 || src); \ + \ + const size_t byte_end = byte_beg + byte_max_cnt; \ + int j = 0; \ + size_t byte_pos = 0; \ + for (int i = 0; \ + i < src_cnt && (byte_max_cnt == 0 || byte_pos < byte_end); \ + i++) { \ + size_t off = 0, len; \ + if (byte_pos < byte_beg) { \ + if (byte_beg - byte_pos >= src[i].iov_len) { \ + byte_pos += src[i].iov_len; \ + continue; \ + } \ + off = byte_beg-byte_pos; \ + len = src[i].iov_len-off; \ + byte_pos = byte_beg; \ + } else { \ + len = src[i].iov_len; \ + } \ + if (byte_max_cnt && byte_end - byte_pos < len) \ + len = byte_end - byte_pos; \ + do { ACTION } while (0); \ + byte_pos += len; \ + j++; \ + } + +/* count */ +int io_rd_slice_cnt (const struct rd_iovec *src, int src_cnt, size_t byte_beg, size_t byte_max_cnt) { IOV_ITER(); return j; } +int io_wr_slice_cnt (const struct wr_iovec *src, int src_cnt, size_t byte_beg, size_t byte_max_cnt) { IOV_ITER(); return j; } +int io_duplex_slice_cnt(const struct duplex_iovec *src, int src_cnt, size_t byte_beg, size_t byte_max_cnt) { IOV_ITER(); return j; } + +/* slice */ +void io_rd_slice(struct rd_iovec *dst, const struct rd_iovec *src, int src_cnt, size_t byte_beg, size_t byte_max_cnt) { + assert(src_cnt == 0 || dst); + IOV_ITER( + dst[j] = ((struct rd_iovec){ + .iov_read_to = src[i].iov_read_to+off, + .iov_len = len, + }); + ); +} +void io_wr_slice(struct wr_iovec *dst, const struct wr_iovec *src, int src_cnt, size_t byte_beg, size_t byte_max_cnt) { + assert(src_cnt == 0 || dst); + IOV_ITER( + dst[j] = ((struct wr_iovec){ + .iov_write_from = src[i].iov_write_from+off, + .iov_len = len, + }); + ); +} +void io_slice_duplex(struct duplex_iovec *dst, const struct duplex_iovec *src, int src_cnt, size_t byte_beg, size_t byte_max_cnt) { + assert(src_cnt == 0 || dst); + IOV_ITER( + dst[j] = ((struct duplex_iovec){ + .iov_read_to = src[i].iov_read_to+off, + .iov_write_from = src[i].iov_write_from+off, + .iov_len = len, + }); + ); +} + +/* slice+convert */ +void io_slice_rd_to_duplex(struct duplex_iovec *dst, const struct rd_iovec *src, int src_cnt, size_t byte_beg, size_t byte_max_cnt) { + assert(src_cnt == 0 || dst); + IOV_ITER( + dst[j] = ((struct duplex_iovec){ + .iov_read_to = src[i].iov_read_to+off, + .iov_write_from = IOVEC_DISCARD, + .iov_len = len, + }); + ); +} +void io_slice_wr_to_duplex(struct duplex_iovec *dst, const struct wr_iovec *src, int src_cnt, size_t byte_beg, size_t byte_max_cnt) { + assert(src_cnt == 0 || dst); + IOV_ITER( + dst[j] = ((struct duplex_iovec){ + .iov_read_to = IOVEC_DISCARD, + .iov_write_from = src[i].iov_write_from+off, + .iov_len = len, + }); + ); +} + +/* convert */ +void io_rd_to_duplex(struct duplex_iovec *dst, const struct rd_iovec *src, int iovcnt) { + io_slice_rd_to_duplex(dst, src, iovcnt, 0, 0); +} +void io_wr_to_duplex(struct duplex_iovec *dst, const struct wr_iovec *src, int iovcnt) { + io_slice_wr_to_duplex(dst, src, iovcnt, 0, 0); +} diff --git a/libhw_generic/net.c b/libhw_generic/net.c new file mode 100644 index 0000000..906e6e4 --- /dev/null +++ b/libhw_generic/net.c @@ -0,0 +1,49 @@ +/* libhw_generic/net.c - Device-independent <libhw/generic/net.h> utilities + * + * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +#include <libmisc/assert.h> + +#include <libhw/generic/net.h> + +#define NET_EOTHER 1 +#define NET_EARP_TIMEOUT 2 +#define NET_EACK_TIMEOUT 3 +#define NET_ERECV_TIMEOUT 4 +#define NET_ETHREAD 5 +#define NET_ECLOSED 6 +#define NET_EMSGSIZE 7 + +const char *net_strerror(int net_errno) { + switch (net_errno) { + case NET_EOTHER: return "unknown error"; + case NET_EARP_TIMEOUT: return "ARP timeout"; + case NET_EACK_TIMEOUT: return "TCP ACK timeout"; + case NET_ERECV_TIMEOUT: return "receive timeout"; + case NET_ETHREAD: return "thread error"; + case NET_ECLOSED: return "already closed"; + case NET_EMSGSIZE: return "message too long"; + default: + assert_notreached("invalid net_errno"); + } +} + +void fmt_print_net_ip4_addr(lo_interface fmt_dest w, struct net_ip4_addr addr) { + fmt_print(w, + addr.octets[0], ".", + addr.octets[1], ".", + addr.octets[2], ".", + addr.octets[3]); +} + +void fmt_print_net_eth_addr(lo_interface fmt_dest w, struct net_eth_addr addr) { + fmt_print(w, + (rjust, 2, '0', (base16, addr.octets[0])), ":", + (rjust, 2, '0', (base16, addr.octets[1])), ":", + (rjust, 2, '0', (base16, addr.octets[2])), ":", + (rjust, 2, '0', (base16, addr.octets[3])), ":", + (rjust, 2, '0', (base16, addr.octets[4])), ":", + (rjust, 2, '0', (base16, addr.octets[5]))); +} diff --git a/libhw_generic/tests/test.h b/libhw_generic/tests/test.h new file mode 120000 index 0000000..2fb1bd5 --- /dev/null +++ b/libhw_generic/tests/test.h @@ -0,0 +1 @@ +../../libmisc/tests/test.h
\ No newline at end of file diff --git a/libhw_generic/tests/test_io.c b/libhw_generic/tests/test_io.c new file mode 100644 index 0000000..c6eeba0 --- /dev/null +++ b/libhw_generic/tests/test_io.c @@ -0,0 +1,57 @@ +/* libhw_generic/tests/test_io.c - Tests for <libmisc/io.h> + * + * Copyright (C) 2025 Luke T. Shumaker <lukeshu@lukeshu.com> + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +#include <string.h> + +#include <libhw/generic/io.h> + +#include "test.h" + +int main() { + char data[] = "abcdefghijklmnopqrstuvwxyz"; + struct wr_iovec src[] = { + {.iov_write_from = &data[1], .iov_len=3}, /* "bcd" */ + {.iov_write_from = &data[20], .iov_len=4}, /* "uvwx" */ + }; + const int src_cnt = sizeof(src)/sizeof(src[0]); + + struct duplex_iovec dst[2]; + +#define TC(start, max, ...) do { \ + char *exp[] = {__VA_ARGS__}; \ + int exp_cnt = sizeof(exp)/sizeof(exp[0]); \ + int act_cnt = io_wr_slice_cnt(src, src_cnt, start, max); \ + test_assert(act_cnt == exp_cnt); \ + memset(dst, 0, sizeof(dst)); \ + io_slice_wr_to_duplex(dst, src, src_cnt, start, max); \ + for (int i = 0; i < act_cnt; i++) { \ + test_assert(dst[i].iov_read_to == IOVEC_DISCARD); \ + test_assert(dst[i].iov_write_from != IOVEC_DISCARD); \ + test_assert(dst[i].iov_len == strlen(exp[i])); \ + test_assert(memcmp(dst[i].iov_write_from, exp[i], dst[i].iov_len) == 0); \ + } \ + } while (0) + + TC(0, 0, /* => */ "bcd", "uvwx"); + TC(1, 0, /* => */ "cd", "uvwx"); + TC(2, 0, /* => */ "d", "uvwx"); + TC(3, 0, /* => */ "uvwx"); + TC(4, 0, /* => */ "vwx"); + TC(5, 0, /* => */ "wx"); + TC(6, 0, /* => */ "x"); + TC(7, 0, /* => */ ); + + TC(0, 2, /* => */ "bc"); + TC(1, 2, /* => */ "cd"); + TC(2, 2, /* => */ "d", "u"); + TC(3, 2, /* => */ "uv"); + TC(4, 2, /* => */ "vw"); + TC(5, 2, /* => */ "wx"); + TC(6, 2, /* => */ "x"); + TC(7, 2, /* => */ ); + + return 0; +} |