diff options
author | Luke T. Shumaker <lukeshu@lukeshu.com> | 2025-03-26 16:15:42 -0600 |
---|---|---|
committer | Luke T. Shumaker <lukeshu@lukeshu.com> | 2025-03-26 22:01:16 -0600 |
commit | 63fcccd84cb348b1b4194024a0dea4dd81941daf (patch) | |
tree | 59f0a19116791a5894892d6275330b482b92f222 | |
parent | 0378f059440d4702203f9bc005894f7b53cad889 (diff) |
libhw_generic: Re-think duplex_iovec
-rw-r--r-- | libhw_cr/rp2040_hwspi.c | 8 | ||||
-rw-r--r-- | libhw_cr/rp2040_include/libhw/rp2040_hwspi.h | 2 | ||||
-rw-r--r-- | libhw_cr/w5500_ll.h | 63 | ||||
-rw-r--r-- | libhw_generic/CMakeLists.txt | 3 | ||||
-rw-r--r-- | libhw_generic/include/libhw/generic/io.h | 28 | ||||
-rw-r--r-- | libhw_generic/io.c | 98 | ||||
l--------- | libhw_generic/tests/test.h | 1 | ||||
-rw-r--r-- | libhw_generic/tests/test_io.c | 57 |
8 files changed, 204 insertions, 56 deletions
diff --git a/libhw_cr/rp2040_hwspi.c b/libhw_cr/rp2040_hwspi.c index 7b57792..d4adb11 100644 --- a/libhw_cr/rp2040_hwspi.c +++ b/libhw_cr/rp2040_hwspi.c @@ -185,23 +185,23 @@ static void rp2040_hwspi_readwritev(struct rp2040_hwspi *self, const struct dupl if (!iov[i].iov_len) continue; tx_data_blocks[j] = (typeof(tx_data_blocks[0])){ - .read_addr = iov[i].iov_write_src ?: &self->bogus_data, + .read_addr = (iov[i].iov_write_from != IOVEC_DISCARD) ? iov[i].iov_write_from : &self->bogus_data, .write_addr = &spi_get_hw(self->inst)->dr, .trans_count = iov[i].iov_len, .ctrl = (DMA_CTRL_ENABLE | DMA_CTRL_DATA_SIZE(DMA_SIZE_8) - | (iov[i].iov_write_src ? DMA_CTRL_INCR_READ : 0) + | ((iov[i].iov_write_from != IOVEC_DISCARD) ? DMA_CTRL_INCR_READ : 0) | DMA_CTRL_CHAIN_TO(self->dma_tx_ctrl) | DMA_CTRL_TREQ_SEL(SPI_DREQ_NUM(self->inst, true)) | DMA_CTRL_IRQ_QUIET), }; rx_data_blocks[j] = (typeof(rx_data_blocks[0])){ .read_addr = &spi_get_hw(self->inst)->dr, - .write_addr = iov[i].iov_read_dst ?: &bogus_rx_dst, + .write_addr = (iov[i].iov_read_to != IOVEC_DISCARD) ? iov[i].iov_read_to : &bogus_rx_dst, .trans_count = iov[i].iov_len, .ctrl = (DMA_CTRL_ENABLE | DMA_CTRL_DATA_SIZE(DMA_SIZE_8) - | (iov[i].iov_read_dst ? DMA_CTRL_INCR_WRITE : 0) + | ((iov[i].iov_read_to != IOVEC_DISCARD) ? DMA_CTRL_INCR_WRITE : 0) | DMA_CTRL_CHAIN_TO(self->dma_rx_ctrl) | DMA_CTRL_TREQ_SEL(SPI_DREQ_NUM(self->inst, false)) | DMA_CTRL_IRQ_QUIET), diff --git a/libhw_cr/rp2040_include/libhw/rp2040_hwspi.h b/libhw_cr/rp2040_include/libhw/rp2040_hwspi.h index eb54cdc..9d99f7b 100644 --- a/libhw_cr/rp2040_include/libhw/rp2040_hwspi.h +++ b/libhw_cr/rp2040_include/libhw/rp2040_hwspi.h @@ -48,7 +48,7 @@ LO_IMPLEMENTATION_H(spi, struct rp2040_hwspi, rp2040_hwspi) * @param mode : enum spi_mode : the SPI mode; SPI_MODE_{0..3} * @param baudrate_hz : uint : baudrate in Hz * @param min_delay_ns: uint64_t : minimum time for pin_cs to be high between messages - * @param bogus_data : uint8_t : bogus data to write when .iov_write_src is NULL + * @param bogus_data : uint8_t : bogus data to write when .iov_write_from is IOVEC_DISCARD * @param pin_miso : uint : pin number; 0, 4, 16, or 20 for _HWSPI_0; 8, 12, 24, or 28 for _HWSPI_1 * @param pin_mosi : uint : pin number; 3, 7, 19, or 23 for _HWSPI_0; 11, 15, or 27 for _HWSPI_1 * @param pin_clk : uint : pin number; 2, 6, 18, or 22 for _HWSPI_0; 10, 14, or 26 for _HWSPI_1 diff --git a/libhw_cr/w5500_ll.h b/libhw_cr/w5500_ll.h index 8f5f9ec..2506cd2 100644 --- a/libhw_cr/w5500_ll.h +++ b/libhw_cr/w5500_ll.h @@ -93,39 +93,15 @@ w5500ll_writev( (uint8_t)(addr & 0xFF), (block & CTL_MASK_BLOCK) | CTL_W | CTL_OM_VDM, }; - struct duplex_iovec *inner = alloca(sizeof(struct duplex_iovec)*(iovcnt+1)); + int inner_cnt = 1+io_slice_cnt(iov, iovcnt, skip, max); + struct duplex_iovec *inner = alloca(sizeof(struct duplex_iovec)*inner_cnt); inner[0] = (struct duplex_iovec){ - .iov_read_dst = NULL, - .iov_write_src = header, - .iov_len = sizeof(header), + .iov_read_to = IOVEC_DISCARD, + .iov_write_from = header, + .iov_len = sizeof(header), }; - int j = 1; - size_t skipped = 0, done = 0; - for (int i = 0; i < iovcnt && (max == 0 || done < max); i++) { - if (skipped < skip) { - if (skip - skipped >= iov[i].iov_len) { - skipped += iov[i].iov_len; - continue; - } - inner[j] = (struct duplex_iovec){ - .iov_read_dst = NULL, - .iov_write_src = iov[i].iov_base+(skip-skipped), - .iov_len = iov[i].iov_len-(skip-skipped), - }; - skipped = skip; - } else { - inner[j] = (struct duplex_iovec){ - .iov_read_dst = NULL, - .iov_write_src = iov[i].iov_base, - .iov_len = iov[i].iov_len, - }; - } - done += inner[j].iov_len; - if (max > 0 && done > max) - inner[j].iov_len -= done - max; - j++; - }; - LO_CALL(spidev, readwritev, inner, j); + io_slice_wr_to_duplex(&inner[1], iov, iovcnt, skip, max); + LO_CALL(spidev, readwritev, inner, inner_cnt); } static inline void @@ -154,26 +130,15 @@ w5500ll_readv( (uint8_t)(addr & 0xFF), (block & CTL_MASK_BLOCK) | CTL_R | CTL_OM_VDM, }; - struct duplex_iovec *inner = alloca(sizeof(struct duplex_iovec)*(iovcnt+1)); + int inner_cnt = 1+io_slice_cnt(iov, iovcnt, 0, max); + struct duplex_iovec *inner = alloca(sizeof(struct duplex_iovec)*inner_cnt); inner[0] = (struct duplex_iovec){ - .iov_read_dst = NULL, - .iov_write_src = header, - .iov_len = sizeof(header), - }; - int j = 1; - size_t done = 0; - for (int i = 0; i < iovcnt && (max == 0 || done < max); i++) { - inner[j] = (struct duplex_iovec){ - .iov_read_dst = iov[i].iov_base, - .iov_write_src = NULL, - .iov_len = iov[i].iov_len, - }; - done += inner[j].iov_len; - if (max > 0 && done > max) - inner[j].iov_len -= done - max; - j++; + .iov_read_to = IOVEC_DISCARD, + .iov_write_from = header, + .iov_len = sizeof(header), }; - LO_CALL(spidev, readwritev, inner, j); + io_slice_rd_to_duplex(&inner[1], iov, iovcnt, 0, max); + LO_CALL(spidev, readwritev, inner, inner_cnt); } /* Common chip-wide registers. ***********************************************/ diff --git a/libhw_generic/CMakeLists.txt b/libhw_generic/CMakeLists.txt index b1632aa..5a6014b 100644 --- a/libhw_generic/CMakeLists.txt +++ b/libhw_generic/CMakeLists.txt @@ -12,5 +12,8 @@ target_link_libraries(libhw_generic INTERFACE target_sources(libhw_generic INTERFACE alarmclock.c + io.c net.c ) + +add_lib_test(libhw_generic test_io) diff --git a/libhw_generic/include/libhw/generic/io.h b/libhw_generic/include/libhw/generic/io.h index a7f7378..7825c9f 100644 --- a/libhw_generic/include/libhw/generic/io.h +++ b/libhw_generic/include/libhw/generic/io.h @@ -8,6 +8,7 @@ #define _LIBHW_GENERIC_IO_H_ #include <stddef.h> /* for size_t */ +#include <stdint.h> /* for uintptr_t */ #include <sys/types.h> /* for ssize_t */ #include <libobj/obj.h> @@ -23,12 +24,35 @@ struct iovec { }; #endif +#define IOVEC_DISCARD ((void*)(~((uintptr_t)0))) + struct duplex_iovec { - void *iov_read_dst; - void *iov_write_src; + /** + * 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; + void *iov_write_from; size_t iov_len; }; +/* utilities ******************************************************************/ + +/* slice iovec lists */ +int io_slice_cnt ( const struct iovec *src, int src_cnt, size_t byte_start, size_t byte_max_cnt); +void io_slice (struct iovec *dst, const struct 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 iovec *src, int iovcnt); +void io_wr_to_duplex(struct duplex_iovec *dst, const struct iovec *src, int iovcnt); + +/* slice and convert in one go */ +void io_slice_rd_to_duplex(struct duplex_iovec *dst, const struct 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 iovec *src, int src_cnt, size_t byte_start, size_t byte_max_cnt); + /* basic interfaces ***********************************************************/ /** diff --git a/libhw_generic/io.c b/libhw_generic/io.c new file mode 100644 index 0000000..4ebff10 --- /dev/null +++ b/libhw_generic/io.c @@ -0,0 +1,98 @@ +/* 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++; \ + } + +int io_slice_cnt(const struct 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; +} + +void io_slice(struct iovec *dst, const struct iovec *src, int src_cnt, size_t byte_beg, size_t byte_max_cnt) { + assert(src_cnt == 0 || dst); + IOV_ITER( + dst[j] = ((struct iovec){ + .iov_base = src[i].iov_base+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, + }); + ); +} + +void io_slice_rd_to_duplex(struct duplex_iovec *dst, const struct 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_base+off, + .iov_write_from = IOVEC_DISCARD, + .iov_len = len, + }); + ); +} + +void io_slice_wr_to_duplex(struct duplex_iovec *dst, const struct 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_base+off, + .iov_len = len, + }); + ); +} + +void io_rd_to_duplex(struct duplex_iovec *dst, const struct 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 iovec *src, int iovcnt) { + io_slice_wr_to_duplex(dst, src, iovcnt, 0, 0); +} 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..0d6df11 --- /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 iovec src[] = { + {.iov_base = &data[1], .iov_len=3}, /* "bcd" */ + {.iov_base = &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_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; +} |