summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLuke T. Shumaker <lukeshu@lukeshu.com>2025-03-26 16:15:42 -0600
committerLuke T. Shumaker <lukeshu@lukeshu.com>2025-03-26 22:01:16 -0600
commit63fcccd84cb348b1b4194024a0dea4dd81941daf (patch)
tree59f0a19116791a5894892d6275330b482b92f222
parent0378f059440d4702203f9bc005894f7b53cad889 (diff)
libhw_generic: Re-think duplex_iovec
-rw-r--r--libhw_cr/rp2040_hwspi.c8
-rw-r--r--libhw_cr/rp2040_include/libhw/rp2040_hwspi.h2
-rw-r--r--libhw_cr/w5500_ll.h63
-rw-r--r--libhw_generic/CMakeLists.txt3
-rw-r--r--libhw_generic/include/libhw/generic/io.h28
-rw-r--r--libhw_generic/io.c98
l---------libhw_generic/tests/test.h1
-rw-r--r--libhw_generic/tests/test_io.c57
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;
+}