summaryrefslogtreecommitdiff
path: root/libhw_generic/include/libhw/generic/io.h
diff options
context:
space:
mode:
Diffstat (limited to 'libhw_generic/include/libhw/generic/io.h')
-rw-r--r--libhw_generic/include/libhw/generic/io.h225
1 files changed, 225 insertions, 0 deletions
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_ */