summaryrefslogtreecommitdiff
path: root/libhw
diff options
context:
space:
mode:
authorLuke T. Shumaker <lukeshu@lukeshu.com>2024-10-27 23:22:01 -0600
committerLuke T. Shumaker <lukeshu@lukeshu.com>2024-10-27 23:49:37 -0600
commit88adb90f5e805bea27e619fd5209ef58dbff6fd1 (patch)
treec3e24877b40ce183f1d72f6e064b0478ecf92207 /libhw
parent89761191a98f7dce4d1049b9a84c3d645378222a (diff)
Factor out a libhw
Diffstat (limited to 'libhw')
-rw-r--r--libhw/CMakeLists.txt30
-rw-r--r--libhw/common_include/libhw/generic/net.h99
-rw-r--r--libhw/common_include/libhw/generic/spi.h47
-rw-r--r--libhw/host_include/libhw/host_net.h37
-rw-r--r--libhw/host_net.c455
-rw-r--r--libhw/rp2040_hwspi.c123
-rw-r--r--libhw/rp2040_include/libhw/rp2040_hwspi.h63
-rw-r--r--libhw/rp2040_include/libhw/w5500.h102
-rw-r--r--libhw/w5500.c527
-rw-r--r--libhw/w5500_ll.h363
10 files changed, 1846 insertions, 0 deletions
diff --git a/libhw/CMakeLists.txt b/libhw/CMakeLists.txt
new file mode 100644
index 0000000..ca58a72
--- /dev/null
+++ b/libhw/CMakeLists.txt
@@ -0,0 +1,30 @@
+# libhw/CMakeLists.txt - TODO
+#
+# Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com>
+# SPDX-Licence-Identifier: AGPL-3.0-or-later
+
+add_library(libhw INTERFACE)
+target_include_directories(libhw SYSTEM INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/common_include)
+target_link_libraries(libhw INTERFACE
+ libmisc
+)
+
+if (PICO_PLATFORM STREQUAL "rp2040")
+ target_include_directories(libhw SYSTEM INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/rp2040_include)
+ target_sources(libhw INTERFACE
+ rp2040_hwspi.c
+ w5500.c
+ )
+ target_link_libraries(libhw INTERFACE
+ pico_time
+ hardware_gpio
+ hardware_spi
+ )
+endif()
+
+if (PICO_PLATFORM STREQUAL "host")
+ target_include_directories(libhw SYSTEM INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/host_include)
+ target_sources(libhw INTERFACE
+ host_net.c
+ )
+endif()
diff --git a/libhw/common_include/libhw/generic/net.h b/libhw/common_include/libhw/generic/net.h
new file mode 100644
index 0000000..40bcd3b
--- /dev/null
+++ b/libhw/common_include/libhw/generic/net.h
@@ -0,0 +1,99 @@
+/* libhw/generic/net.h - Device-independent network definitions
+ *
+ * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com>
+ * SPDX-Licence-Identifier: AGPL-3.0-or-later
+ */
+
+#ifndef _LIBHW_GENERIC_NET_H_
+#define _LIBHW_GENERIC_NET_H_
+
+#include <stdbool.h> /* for bool */
+#include <stddef.h> /* for size_t */
+#include <stdint.h> /* for uint{n}_t} */
+#include <sys/types.h> /* for ssize_t */
+
+/* Errnos *********************************************************************/
+
+#define NET_EOTHER 1
+#define NET_ETIMEDOUT 2
+#define NET_ETHREAD 3
+
+/* 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}};
+
+struct net_eth_addr {
+ unsigned char octets[6];
+};
+
+/* Streams (e.g. TCP) *********************************************************/
+
+struct net_stream_listener_vtable;
+struct net_stream_conn_btable;
+
+typedef struct {
+ struct net_stream_listener_vtable *vtable;
+} implements_net_stream_listener;
+
+typedef struct {
+ struct net_stream_conn_vtable *vtable;
+} implements_net_stream_conn;
+
+struct net_stream_listener_vtable {
+ /**
+ * It is invalid to accept() a new connection if an existing
+ * connection is still open.
+ */
+ implements_net_stream_conn *(*accept)(implements_net_stream_listener *self);
+};
+
+struct net_stream_conn_vtable {
+ /**
+ * Return bytes-read on success, 0 on EOF, -errno on error; a
+ * short read is *not* an error.
+ */
+ ssize_t (*read)(implements_net_stream_conn *self,
+ void *buf, size_t count);
+
+ /**
+ * Return `count` on success, -errno on error; a short write *is* an
+ * error.
+ *
+ * Writes are *not* guaranteed to be atomic (as this would be
+ * expensive to implement), so if you have concurrent writers then you
+ * should arrange for a mutex to protect the connection.
+ */
+ ssize_t (*write)(implements_net_stream_conn *self,
+ void *buf, size_t count);
+
+ /**
+ * Return 0 on success, -errno on error.
+ */
+ int (*close)(implements_net_stream_conn *self,
+ bool rd, bool wr);
+};
+
+/* Packets (e.g. UDP) *********************************************************/
+
+struct net_packet_conn_vtable;
+
+typedef struct {
+ struct net_packet_conn_vtable *vtable;
+} implements_net_packet_conn;
+
+struct net_packet_conn_vtable {
+ ssize_t (*sendto )(implements_net_packet_conn *self,
+ void *buf, size_t len,
+ struct net_ip4_addr node, uint16_t port);
+ ssize_t (*recvfrom)(implements_net_packet_conn *self,
+ void *buf, size_t len,
+ struct net_ip4_addr *ret_node, uint16_t *ret_port);
+ int (*close )(implements_net_packet_conn *self);
+};
+
+#endif /* _LIBHW_GENERIC_NET_H_ */
diff --git a/libhw/common_include/libhw/generic/spi.h b/libhw/common_include/libhw/generic/spi.h
new file mode 100644
index 0000000..fcba84e
--- /dev/null
+++ b/libhw/common_include/libhw/generic/spi.h
@@ -0,0 +1,47 @@
+/* libhw/generic/spi.h - Device-independent SPI definitions
+ *
+ * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com>
+ * SPDX-Licence-Identifier: AGPL-3.0-or-later
+ */
+
+#ifndef _LIBHW_GENERIC_SPI_H_
+#define _LIBHW_GENERIC_SPI_H_
+
+#include <stddef.h> /* for size_t */
+
+enum spi_mode {
+ SPI_MODE_0 = 0, /* clk_polarity=0 (idle low), clk_phase=0 (sample on rise) */
+ SPI_MODE_1 = 1, /* clk_polarity=0 (idle low), clk_phase=1 (sample on fall) */
+ SPI_MODE_2 = 2, /* clk_polarity=1 (idle high), clk_phase=0 (sample on rise) */
+ SPI_MODE_3 = 3, /* clk_polarity=1 (idle high), clk_phase=1 (sample on fall) */
+};
+
+struct bidi_iovec {
+ void *iov_read_dst;
+ void *iov_write_src;
+ size_t iov_len;
+};
+
+struct spi_vtable;
+
+typedef struct {
+ struct spi_vtable *vtable;
+} implements_spi;
+
+/* 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.
+ */
+struct spi_vtable {
+ void (*readwritev)(implements_spi *, const struct bidi_iovec *iov, int iovcnt);
+};
+
+#endif /* _LIBHW_GENERIC_SPI_H_ */
diff --git a/libhw/host_include/libhw/host_net.h b/libhw/host_include/libhw/host_net.h
new file mode 100644
index 0000000..c74f0bd
--- /dev/null
+++ b/libhw/host_include/libhw/host_net.h
@@ -0,0 +1,37 @@
+/* libhw/host_net.h - <libhw/generic/net.h> implementation for hosted glibc
+ *
+ * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com>
+ * SPDX-Licence-Identifier: AGPL-3.0-or-later
+ */
+
+#ifndef _LIBHW_HOST_NET_H_
+#define _LIBHW_HOST_NET_H_
+
+#include <stdint.h> /* for uint16_6 */
+
+#include <libhw/generic/net.h>
+
+struct _hostnet_tcp_conn {
+ implements_net_stream_conn;
+
+ int fd;
+};
+
+struct hostnet_tcp_listener {
+ implements_net_stream_listener;
+
+ int fd;
+ struct _hostnet_tcp_conn active_conn;
+};
+
+void hostnet_tcp_listener_init(struct hostnet_tcp_listener *self, uint16_t port);
+
+struct hostnet_udp_conn {
+ implements_net_packet_conn;
+
+ int fd;
+};
+
+void hostnet_udp_conn_init(struct hostnet_udp_conn *self, uint16_t port);
+
+#endif /* _LIBHW_HOST_NET_H_ */
diff --git a/libhw/host_net.c b/libhw/host_net.c
new file mode 100644
index 0000000..d71afee
--- /dev/null
+++ b/libhw/host_net.c
@@ -0,0 +1,455 @@
+/* libhw/host_net.c - <libhw/generic/net.h> implementation for hosted glibc
+ *
+ * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com>
+ * SPDX-Licence-Identifier: AGPL-3.0-or-later
+ */
+
+#define _GNU_SOURCE /* for pthread_sigqueue(3gnu) */
+/* misc */
+#include <assert.h> /* for assert() */
+#include <errno.h> /* for errno, EAGAIN, EINVAL */
+#include <error.h> /* for error(3gnu) */
+#include <stdlib.h> /* for abs(), shutdown(), SHUT_RD, SHUT_WR, SHUT_RDWR */
+#include <unistd.h> /* for read(), write() */
+/* net */
+#include <arpa/inet.h> /* for htons(3p) */
+#include <netinet/in.h> /* for struct sockaddr_in */
+#include <sys/socket.h> /* for struct sockaddr{,_storage}, SOCK_*, SOL_*, SO_*, socket(), setsockopt(), bind(), listen(), accept() */
+/* async */
+#include <pthread.h> /* for pthread_* */
+#include <signal.h> /* for siginfo_t, struct sigaction, enum sigval, sigaction(), SIGRTMIN, SIGRTMAX, SA_SIGINFO */
+
+#include <libcr/coroutine.h>
+#include <libmisc/vcall.h>
+
+#include <libhw/host_net.h>
+
+/* common *********************************************************************/
+
+#define UNUSED(name) /* name __attribute__ ((unused)) */
+
+static int hostnet_sig_io = 0;
+
+static void hostnet_handle_sig_io(int UNUSED(sig), siginfo_t *info, void *UNUSED(ucontext)) {
+ cr_unpause_from_intrhandler((cid_t)info->si_value.sival_int);
+}
+
+static void hostnet_init(void) {
+ struct sigaction action = {0};
+
+ if (hostnet_sig_io)
+ return;
+
+ hostnet_sig_io = SIGRTMIN;
+ if (hostnet_sig_io > SIGRTMAX)
+ error(1, 0, "SIGRTMAX exceeded");
+
+ action.sa_flags = SA_SIGINFO;
+ action.sa_sigaction = hostnet_handle_sig_io;
+ if (sigaction(hostnet_sig_io, &action, NULL) < 0)
+ error(1, errno, "sigaction");
+}
+
+#define WAKE_COROUTINE(args) do { \
+ int r; \
+ union sigval val = {0}; \
+ val.sival_int = (int)((args)->cr_coroutine); \
+ do { \
+ r = pthread_sigqueue((args)->cr_thread, \
+ hostnet_sig_io, \
+ val); \
+ assert(r == 0 || r == EAGAIN); \
+ } while (r == EAGAIN); \
+ } while (0)
+
+static inline bool RUN_PTHREAD(void *(*fn)(void *), void *args) {
+ pthread_t thread;
+ if (pthread_create(&thread, NULL, fn, args))
+ return true;
+ cr_pause_and_yield();
+ if (pthread_join(thread, NULL))
+ return true;
+ return false;
+}
+
+static inline ssize_t hostnet_map_errno(ssize_t v) {
+ if (v >= 0)
+ return v;
+ switch (v) {
+ case ETIMEDOUT:
+ return NET_ETIMEDOUT;
+ default:
+ return NET_EOTHER;
+ }
+}
+
+/* TCP init() ( AKA socket(3) + listen(3) )************************************/
+
+static implements_net_stream_conn *hostnet_tcp_accept(implements_net_stream_listener *_listener);
+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 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,
+};
+
+void hostnet_tcp_listener_init(struct hostnet_tcp_listener *self, uint16_t port) {
+ int listenerfd;
+ union {
+ struct sockaddr_in in;
+ struct sockaddr gen;
+ } addr = { 0 };
+
+ hostnet_init();
+
+ addr.in.sin_family = AF_INET;
+ addr.in.sin_port = htons(port);
+ listenerfd = socket(AF_INET, SOCK_STREAM, 0);
+ if (listenerfd < 0)
+ error(1, errno, "socket");
+ if (setsockopt(listenerfd, SOL_SOCKET, SO_REUSEADDR, &(int){1}, sizeof(int)) < 0)
+ error(1, errno, "setsockopt(fd=%d, SO_REUSEADDR=1)", listenerfd);
+ if (setsockopt(listenerfd, SOL_SOCKET, SO_REUSEPORT, &(int){1}, sizeof(int)) < 0)
+ error(1, errno, "setsockopt(fd=%d, SO_REUSEPORT=1)", listenerfd);
+ if (bind(listenerfd, &addr.gen, sizeof addr) < 0)
+ error(1, errno, "bind(fd=%d)", listenerfd);
+ if (listen(listenerfd, 0) < 0)
+ error(1, errno, "listen(fd=%d)", listenerfd);
+
+ self->vtable = &hostnet_tcp_listener_vtable;
+ self->fd = listenerfd;
+}
+
+/* TCP accept() ***************************************************************/
+
+struct hostnet_pthread_accept_args {
+ pthread_t cr_thread;
+ cid_t cr_coroutine;
+
+ int listenerfd;
+
+ int *ret_connfd;
+};
+
+static void *hostnet_pthread_accept(void *_args) {
+ struct hostnet_pthread_accept_args *args = _args;
+ *(args->ret_connfd) = accept(args->listenerfd, NULL, NULL);
+ if (*(args->ret_connfd) < 0)
+ *(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 =
+ VCALL_SELF(struct hostnet_tcp_listener, implements_net_stream_listener, _listener);
+ assert(listener);
+
+ int ret_connfd;
+ struct hostnet_pthread_accept_args args = {
+ .cr_thread = pthread_self(),
+ .cr_coroutine = cr_getcid(),
+ .listenerfd = listener->fd,
+ .ret_connfd = &ret_connfd,
+ };
+ if (RUN_PTHREAD(hostnet_pthread_accept, &args))
+ return NULL;
+
+ listener->active_conn.vtable = &hostnet_tcp_conn_vtable;
+ listener->active_conn.fd = ret_connfd;
+ return &listener->active_conn;
+}
+
+/* TCP read() *****************************************************************/
+
+struct hostnet_pthread_read_args {
+ pthread_t cr_thread;
+ cid_t cr_coroutine;
+
+ int connfd;
+ void *buf;
+ size_t count;
+
+ ssize_t *ret;
+};
+
+static void *hostnet_pthread_read(void *_args) {
+ struct hostnet_pthread_read_args *args = _args;
+ *(args->ret) = read(args->connfd, args->buf, args->count);
+ 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 =
+ VCALL_SELF(struct _hostnet_tcp_conn, implements_net_stream_conn, _conn);
+ assert(conn);
+
+ ssize_t ret;
+ struct hostnet_pthread_read_args args = {
+ .cr_thread = pthread_self(),
+ .cr_coroutine = cr_getcid(),
+
+ .connfd = conn->fd,
+ .buf = buf,
+ .count = count,
+
+ .ret = &ret,
+ };
+ if (RUN_PTHREAD(hostnet_pthread_read, &args))
+ return -NET_ETHREAD;
+ return ret;
+}
+
+/* TCP write() ****************************************************************/
+
+struct hostnet_pthread_write_args {
+ pthread_t cr_thread;
+ cid_t cr_coroutine;
+
+ int connfd;
+ void *buf;
+ size_t count;
+
+ ssize_t *ret;
+};
+
+static void *hostnet_pthread_write(void *_args) {
+ struct hostnet_pthread_read_args *args = _args;
+ size_t done = 0;
+ while (done < args->count) {
+ ssize_t r = write(args->connfd, args->buf, args->count);
+ if (r < 0) {
+ hostnet_map_errno(-errno);
+ break;
+ }
+ done += r;
+ }
+ if (done == args->count)
+ *(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 =
+ VCALL_SELF(struct _hostnet_tcp_conn, implements_net_stream_conn, _conn);
+ assert(conn);
+
+ ssize_t ret;
+ struct hostnet_pthread_write_args args = {
+ .cr_thread = pthread_self(),
+ .cr_coroutine = cr_getcid(),
+
+ .connfd = conn->fd,
+ .buf = buf,
+ .count = count,
+
+ .ret = &ret,
+ };
+ if (RUN_PTHREAD(hostnet_pthread_write, &args))
+ return -NET_ETHREAD;
+ return ret;
+}
+
+/* TCP close() ****************************************************************/
+
+static int hostnet_tcp_close(implements_net_stream_conn *_conn, bool rd, bool wr) {
+ struct _hostnet_tcp_conn *conn =
+ VCALL_SELF(struct _hostnet_tcp_conn, implements_net_stream_conn, _conn);
+ assert(conn);
+
+ int how;
+ if (rd && wr)
+ how = SHUT_RDWR;
+ else if (rd && !wr)
+ how = SHUT_RD;
+ else if (!rd && wr)
+ how = SHUT_WR;
+ else
+ assert(false);
+ return hostnet_map_errno(shutdown(conn->fd, how) ? -errno : 0);
+}
+
+/* UDP init() *****************************************************************/
+
+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,
+ struct net_ip4_addr *ret_addr, uint16_t *ret_port);
+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,
+};
+
+void hostnet_udp_conn_init(struct hostnet_udp_conn *self, uint16_t port) {
+ int fd;
+ union {
+ struct sockaddr_in in;
+ struct sockaddr gen;
+ struct sockaddr_storage stor;
+ } addr = { 0 };
+
+ hostnet_init();
+
+ addr.in.sin_family = AF_INET;
+ addr.in.sin_port = htons(port);
+ fd = socket(AF_INET, SOCK_DGRAM, 0);
+ if (fd < 0)
+ error(1, errno, "socket");
+ if (bind(fd, &addr.gen, sizeof addr) < 0)
+ error(1, errno, "bind");
+
+ self->vtable = &hostnet_udp_conn_vtable;
+ self->fd = fd;
+}
+
+/* UDP sendto() ***************************************************************/
+
+struct hostnet_pthread_sendto_args {
+ pthread_t cr_thread;
+ cid_t cr_coroutine;
+
+ int connfd;
+ void *buf;
+ size_t count;
+ struct net_ip4_addr node;
+ uint16_t port;
+
+ ssize_t *ret;
+};
+
+static void *hostnet_pthread_sendto(void *_args) {
+ struct hostnet_pthread_sendto_args *args = _args;
+ union {
+ struct sockaddr_in in;
+ struct sockaddr gen;
+ struct sockaddr_storage stor;
+ } addr = { 0 };
+
+ addr.in.sin_family = AF_INET;
+ addr.in.sin_addr.s_addr =
+ (((uint32_t)args->node.octets[0])<<24) |
+ (((uint32_t)args->node.octets[1])<<16) |
+ (((uint32_t)args->node.octets[2])<< 8) |
+ (((uint32_t)args->node.octets[3])<< 0) ;
+ addr.in.sin_port = htons(args->port);
+ *(args->ret) = sendto(args->connfd, args->buf, args->count, 0, &addr.gen, sizeof(addr));
+ if (*(args->ret) < 0)
+ *(args->ret) = hostnet_map_errno(-errno);
+ WAKE_COROUTINE(args);
+ return NULL;
+}
+
+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 hostnet_udp_conn *conn =
+ VCALL_SELF(struct hostnet_udp_conn, implements_net_packet_conn, _conn);
+ assert(conn);
+
+ ssize_t ret;
+ struct hostnet_pthread_sendto_args args = {
+ .cr_thread = pthread_self(),
+ .cr_coroutine = cr_getcid(),
+
+ .connfd = conn->fd,
+ .buf = buf,
+ .count = count,
+ .node = node,
+ .port = port,
+
+ .ret = &ret,
+ };
+ if (RUN_PTHREAD(hostnet_pthread_sendto, &args))
+ return -NET_ETHREAD;
+ return ret;
+}
+
+/* UDP recvfrom() *************************************************************/
+
+struct hostnet_pthread_recvfrom_args {
+ pthread_t cr_thread;
+ cid_t cr_coroutine;
+
+ int connfd;
+ void *buf;
+ size_t count;
+
+ ssize_t *ret_size;
+ struct net_ip4_addr *ret_node;
+ uint16_t *ret_port;
+};
+
+static void *hostnet_pthread_recvfrom(void *_args) {
+ struct hostnet_pthread_recvfrom_args *args = _args;
+
+ union {
+ struct sockaddr_in in;
+ struct sockaddr gen;
+ struct sockaddr_storage stor;
+ } addr = { 0 };
+ socklen_t addr_size;
+
+ *(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);
+ }
+ 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 hostnet_udp_conn *conn =
+ VCALL_SELF(struct hostnet_udp_conn, implements_net_packet_conn, _conn);
+ assert(conn);
+
+ ssize_t ret;
+ struct hostnet_pthread_recvfrom_args args = {
+ .cr_thread = pthread_self(),
+ .cr_coroutine = cr_getcid(),
+
+ .connfd = conn->fd,
+ .buf = buf,
+ .count = count,
+
+ .ret_size = &ret,
+ .ret_node = ret_node,
+ .ret_port = ret_port,
+ };
+ if (RUN_PTHREAD(hostnet_pthread_recvfrom, &args))
+ return -NET_ETHREAD;
+ return ret;
+}
+
+/* UDP close() ****************************************************************/
+
+static int hostnet_udp_close(implements_net_packet_conn *_conn) {
+ struct hostnet_udp_conn *conn =
+ VCALL_SELF(struct hostnet_udp_conn, implements_net_packet_conn, _conn);
+ assert(conn);
+
+ return hostnet_map_errno(close(conn->fd) ? -errno : 0);
+}
diff --git a/libhw/rp2040_hwspi.c b/libhw/rp2040_hwspi.c
new file mode 100644
index 0000000..b041bc9
--- /dev/null
+++ b/libhw/rp2040_hwspi.c
@@ -0,0 +1,123 @@
+/* libhw/rp2040_hwspi.c - `implements_spi` implementation for the RP2040's
+ * ARM Primecell SSP (PL022) (implementation file)
+ *
+ * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com>
+ * SPDX-Licence-Identifier: AGPL-3.0-or-later
+ */
+
+#include <assert.h>
+
+#include <hardware/spi.h> /* pico-sdk:hardware_spi */
+#include <hardware/gpio.h> /* pico-sdk:hardware_gpio */
+
+#include <libmisc/vcall.h>
+
+#include <libhw/rp2040_hwspi.h>
+
+static void rp2040_hwspi_readwritev(implements_spi *, const struct bidi_iovec *iov, int iovcnt);
+
+struct spi_vtable rp2040_hwspi_vtable = {
+ .readwritev = rp2040_hwspi_readwritev,
+};
+
+void _rp2040_hwspi_init(struct rp2040_hwspi *self,
+ enum rp2040_hwspi_instance inst_num,
+ enum spi_mode mode,
+ uint baudrate_hz,
+ uint pin_miso,
+ uint pin_mosi,
+ uint pin_clk,
+ uint pin_cs) {
+ /* Be not weary: This is but 12 lines of actual code; and many
+ * lines of comments and assert()s. */
+ spi_inst_t *inst;
+
+ assert(self);
+ assert(baudrate_hz);
+ assert(pin_miso != pin_mosi);
+ assert(pin_miso != pin_clk);
+ assert(pin_miso != pin_cs);
+ assert(pin_mosi != pin_clk);
+ assert(pin_mosi != pin_cs);
+ assert(pin_clk != pin_cs);
+
+ /* I know we called this "hwspi", but we're actually going to
+ * disconnect the CS pin from the PL022 SSP and manually drive
+ * it from software. This is because the PL022 has a maximum
+ * of 16-bit frames, while we need to be able to do *at least*
+ * 32-bit frames (and ideally, much larger). By managing it
+ * ourselves, we can just keep CS pulled low extra-long,
+ * making the frame extra-long. */
+
+ /* Regarding the constraints on pin assignments: see the
+ * RP2040 datasheet, table 2, in §1.4.3 "GPIO Functions". */
+ switch (inst_num) {
+ case RP2040_HWSPI_0:
+ inst = spi0;
+ assert(pin_miso == 0 || pin_miso == 4 || pin_miso == 16 || pin_miso == 20);
+ /*assert(pin_cs == 1 || pin_cs == 5 || pin_cs == 17 || pin_cs == 21);*/
+ assert(pin_clk == 2 || pin_clk == 6 || pin_clk == 18 || pin_clk == 22);
+ assert(pin_mosi == 3 || pin_mosi == 7 || pin_mosi == 19 || pin_mosi == 23);
+ break;
+ case RP2040_HWSPI_1:
+ inst = spi1;
+ assert(pin_miso == 8 || pin_miso == 12 || pin_miso == 24 || pin_miso == 28);
+ /*assert(pin_cs == 9 || pin_cs == 13 || pin_cs == 25 || pin_cs == 29);*/
+ assert(pin_clk == 10 || pin_clk == 14 || pin_clk == 26);
+ assert(pin_mosi == 11 || pin_mosi == 15 || pin_mosi == 27);
+ break;
+ default:
+ assert(false);
+ }
+
+ spi_init(inst, baudrate_hz);
+ spi_set_format(inst, 8,
+ (mode & 0b10) ? SPI_CPOL_1 : SPI_CPOL_0,
+ (mode & 0b01) ? SPI_CPHA_1 : SPI_CPHA_0,
+ SPI_MSB_FIRST);
+
+ /* Connect the pins to the PL022; set them each to "function
+ * 1" (again, see the RP2040 datasheet, table 2, in §1.4.3
+ * "GPIO Functions").
+ *
+ * ("GPIO_FUNC_SPI" is how the pico-sdk spells "function 1",
+ * since on the RP2040 all of the "function 1" functions are
+ * some part of SPI.) */
+ gpio_set_function(pin_clk, GPIO_FUNC_SPI);
+ gpio_set_function(pin_mosi, GPIO_FUNC_SPI);
+ gpio_set_function(pin_miso, GPIO_FUNC_SPI);
+
+ /* Initialize the CS pin for software control. */
+ gpio_init(pin_cs);
+ gpio_set_dir(pin_cs, GPIO_OUT);
+ gpio_put(pin_cs, 1);
+
+ /* Return. */
+ self->vtable = &rp2040_hwspi_vtable;
+ self->inst = inst;
+ self->pin_cs = pin_cs;
+}
+
+static void rp2040_hwspi_readwritev(implements_spi *_self, const struct bidi_iovec *iov, int iovcnt) {
+ struct rp2040_hwspi *self = VCALL_SELF(struct rp2040_hwspi, implements_spi, _self);
+ assert(self);
+ spi_inst_t *inst = self->inst;
+
+ assert(inst);
+ assert(iov);
+ assert(iovcnt);
+
+ gpio_put(self->pin_cs, 0);
+ /* TODO: Replace blocking reads+writes with DMA. */
+ for (int i = 0; i < iovcnt; i++) {
+ if (iov[i].iov_write_src && iov[i].iov_read_dst)
+ spi_write_read_blocking(inst, iov[i].iov_write_src, iov[i].iov_read_dst, iov[i].iov_len);
+ else if (iov[i].iov_write_src)
+ spi_write_blocking(inst, iov[i].iov_write_src, iov[i].iov_len);
+ else if (iov[i].iov_read_dst)
+ spi_read_blocking(inst, 0, iov[i].iov_read_dst, iov[i].iov_len);
+ else
+ assert(false);
+ }
+ gpio_put(self->pin_cs, 1);
+}
diff --git a/libhw/rp2040_include/libhw/rp2040_hwspi.h b/libhw/rp2040_include/libhw/rp2040_hwspi.h
new file mode 100644
index 0000000..8e44e50
--- /dev/null
+++ b/libhw/rp2040_include/libhw/rp2040_hwspi.h
@@ -0,0 +1,63 @@
+/* libhw/rp2040_hwspi.h - `implements_spi` implementation for the RP2040's
+ * ARM Primecell SSP (PL022) (header file)
+ *
+ * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com>
+ * SPDX-Licence-Identifier: AGPL-3.0-or-later
+ */
+
+#ifndef _LIBHW_RP2040_HWSPI_H_
+#define _LIBHW_RP2040_HWSPI_H_
+
+#include <pico/binary_info.h> /* for bi_* */
+
+#include <libhw/generic/spi.h>
+
+enum rp2040_hwspi_instance {
+ RP2040_HWSPI_0 = 0,
+ RP2040_HWSPI_1 = 1,
+};
+
+struct rp2040_hwspi {
+ implements_spi;
+
+ void /*spi_inst_t*/ *inst;
+ uint pin_cs;
+};
+
+/**
+ * Initialize an instance of `struct rp2040_hwspi`.
+ *
+ * @param self : struct rp2040_hwspi : the structure to initialize
+ * @param name : char * : a name for the SPI port; to include in the bininfo
+ * @param inst_num : enum rp2040_hwspi_instance : the PL220 instance number; RP2040_HWSPI_{0,1}
+ * @param mode : enum spi_mode : the SPI mode; SPI_MODE_{0..3}
+ * @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
+ * @param pin_cs : uint : pin number; any unused GPIO pin
+ *
+ * There is no bit-order argument; the RP2040's hardware SPI always
+ * uses MSB-first bit order.
+ */
+#define rp2040_hwspi_init(self, name, \
+ inst_num, mode, baudrate_hz, \
+ pin_miso, pin_mosi, pin_clk, pin_cs) \
+ do { \
+ bi_decl(bi_4pins_with_names(pin_miso, name" SPI MISO", \
+ pin_mosi, name" SPI MOSI", \
+ pin_mosi, name" SPI CLK", \
+ pin_mosi, name" SPI CS")); \
+ _rp2040_hwspi_init(self, \
+ inst_num, mode, baudrate_hz, \
+ pin_miso, pin_mosi, pin_clk, pin_cs); \
+ } while(0)
+void _rp2040_hwspi_init(struct rp2040_hwspi *self,
+ enum rp2040_hwspi_instance inst_num,
+ enum spi_mode mode,
+ uint baudrate_hz,
+ uint pin_miso,
+ uint pin_mosi,
+ uint pin_clk,
+ uint pin_cs);
+
+#endif /* _LIBHW_RP2040_HWSPI_H_ */
diff --git a/libhw/rp2040_include/libhw/w5500.h b/libhw/rp2040_include/libhw/w5500.h
new file mode 100644
index 0000000..6b5a690
--- /dev/null
+++ b/libhw/rp2040_include/libhw/w5500.h
@@ -0,0 +1,102 @@
+/* libhw/w5500.h - <libhw/generic/net.h> implementation for the WIZnet W5500 chip
+ *
+ * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com>
+ * SPDX-Licence-Identifier: AGPL-3.0-or-later
+ */
+
+#ifndef _LIBHW_W5500_H_
+#define _LIBHW_W5500_H_
+
+#include <libcr_ipc/sema.h>
+#include <libcr_ipc/mutex.h>
+
+#include <libhw/generic/net.h>
+#include <libhw/generic/spi.h>
+
+struct _w5500_tcp_conn {
+ /* const-after-init */
+ implements_net_stream_conn;
+ /* mutable */
+ bool read_open;
+ bool write_open;
+};
+
+struct _w5500_tcp_listener {
+ /* const-after-init */
+ implements_net_stream_listener;
+ uint8_t socknum;
+ struct _w5500_tcp_conn active_conn;
+
+ /* mutable */
+ uint16_t port;
+ cr_mutex_t cmd_mu;
+ cr_sema_t listen_sema, read_sema;
+};
+
+struct w5500 {
+ /* const-after-init */
+ implements_spi *spidev;
+ uint pin_intr;
+ uint pin_reset;
+ struct net_eth_addr hwaddr;
+
+ /* mutable */
+ uint16_t next_local_port;
+ struct _w5500_tcp_listener listeners[8];
+ cr_sema_t intr;
+};
+
+/**
+ * Initialize a WIZnet W5500 Ethernet-and-TCP/IP-offload chip.
+ *
+ * The W5500 has 3 lines of communication with the MCU:
+ *
+ * - An SPI-based RPC protocol:
+ * + mode: mode 0 or mode 3
+ * + bit-order: MSB-first
+ * + clock frequency: 33.3MHz - 80MHz
+ * - An interrupt pin that it pulls low when an event happens (to let
+ * the MCU know that it should do an SPI RPC "get" to see what
+ * happened.)
+ * - A reset pin that the MCU can pull low to reset the W5500.
+ */
+#define w5500_init(self, name, spi, pin_intr, pin_reset, eth_addr) do { \
+ bi_decl(bi_2pins_with_names(pin_intr, name" interrupt", \
+ pin_reset, name" reset")); \
+ _w5500_init(self, spi, pin_intr, pin_reset, eth_addr); \
+ } while (0)
+void _w5500_init(struct w5500 *self,
+ implements_spi *spi, uint pin_intr, uint pin_reset,
+ struct net_eth_addr addr);
+
+/**
+ * TODO.
+ */
+void w5500_hard_reset(struct w5500 *self);
+
+/**
+ * TODO.
+ */
+void w5500_soft_reset(struct w5500 *self);
+
+struct w5500_netcfg {
+ struct net_ip4_addr gateway_addr;
+ struct net_ip4_addr subnet_mask;
+ struct net_ip4_addr addr;
+};
+
+/**
+ * TODO.
+ */
+void w5500_netcfg(struct w5500 *self, struct w5500_netcfg cfg);
+
+implements_net_stream_listener *w5500_tcp_listen(struct w5500 *self, uint8_t socknum,
+ uint16_t port);
+
+/**
+ * TODO.
+ */
+implements_net_packet_conn *w5500_udp_conn(struct w5500 *self, uint8_t socknum,
+ uint16_t port);
+
+#endif /* _LIBHW_W5500_H_ */
diff --git a/libhw/w5500.c b/libhw/w5500.c
new file mode 100644
index 0000000..70881ee
--- /dev/null
+++ b/libhw/w5500.c
@@ -0,0 +1,527 @@
+/* libhw/w5500.c - <libhw/generic/net.h> implementation for the WIZnet W5500 chip
+ *
+ * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com>
+ * SPDX-Licence-Identifier: AGPL-3.0-or-later
+ *
+ * -----------------------------------------------------------------------------
+ * https://github.com/Wiznet/ioLibrary_Driver/blob/b981401e7f3d07015619adf44c13998e13e777f9/Ethernet/wizchip_conf.c
+ * https://github.com/Wiznet/ioLibrary_Driver/blob/b981401e7f3d07015619adf44c13998e13e777f9/Ethernet/W5500/w5500.h
+ * https://github.com/Wiznet/ioLibrary_Driver/blob/b981401e7f3d07015619adf44c13998e13e777f9/Ethernet/W5500/w5500.c
+ * https://github.com/Wiznet/ioLibrary_Driver/blob/b981401e7f3d07015619adf44c13998e13e777f9/Ethernet/socket.c
+ *
+ * Copyright (c) 2013, WIZnet Co., LTD.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of the <ORGANIZATION> nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * SPDX-Licence-Identifier: BSD-3-Clause
+ *
+ * -----------------------------------------------------------------------------
+ * https://github.com/Wiznet/ioLibrary_Driver/blob/b981401e7f3d07015619adf44c13998e13e777f9/license.txt
+ *
+ * Copyright (c) 2014 WIZnet Co.,Ltd.
+ * Copyright (c) WIZnet ioLibrary Project.
+ * All rights reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ * SPDX-Licence-Identifier: MIT
+ */
+
+#include <pico/time.h> /* for sleep_ms() */
+#include <hardware/gpio.h> /* pico-sdk:hardware_gpio */
+
+#include <libcr/coroutine.h> /* for cr_yield() */
+#include <libmisc/vcall.h> /* for VCALL_SELF() */
+
+#include <libhw/w5500.h>
+
+#include "w5500_ll.h"
+
+/* Config *********************************************************************/
+
+#include "config.h"
+
+/* These are the default values of the Linux kernel's
+ * net.ipv4.ip_local_port_range, so I figure they're probably good
+ * values to use. */
+#ifndef CONFIG_W5500_LOCAL_PORT_MIN
+ #define CONFIG_W5500_LOCAL_PORT_MIN 32768
+#endif
+
+#ifndef CONFIG_W5500_LOCAL_PORT_MAX
+ #define CONFIG_W5500_LOCAL_PORT_MAX 60999
+#endif
+
+#ifndef CONFIG_W5500_NUM
+ #error config.h must define CONFIG_W5500_NUM
+#endif
+
+/* C language *****************************************************************/
+
+#define UNUSED(name)
+#define ARRAY_LEN(ary) (sizeof(ary)/sizeof((ary)[0]))
+
+/* mid-level utilities ********************************************************/
+
+#if 0
+static uint16_t w5500_get_local_port(struct w5500 *self) {
+ uint16_t ret = self->next_local_port++;
+ if (self->next_local_port > CONFIG_W5500_LOCAL_PORT_MAX)
+ self->next_local_port = CONFIG_W5500_LOCAL_PORT_MIN;
+ return ret;
+}
+#endif
+
+static COROUTINE w5500_irq_cr(void *_chip) {
+ struct w5500 *chip = _chip;
+ cr_begin();
+
+ for (;;) {
+ cr_sema_wait(&chip->intr);
+ if (w5500ll_read_common_reg(chip->spidev, chip_interrupt))
+ w5500ll_write_common_reg(chip->spidev, chip_interrupt, 0xFF);
+
+ uint8_t sockmask = w5500ll_read_common_reg(chip->spidev, sock_interrupt);
+ for (uint8_t socknum = 0; socknum < 8; socknum++) {
+ if (!(sockmask & (1<<socknum)))
+ continue;
+ struct _w5500_tcp_listener *listener = &chip->listeners[socknum];
+
+ uint8_t sockintr = w5500ll_read_sock_reg(chip->spidev, socknum, interrupt);
+
+ /* SOCKINTR_SEND_OK is useless. */
+ uint8_t listen_bits = sockintr & (SOCKINTR_TIMEOUT|SOCKINTR_CONN),
+ read_bits = sockintr & (SOCKINTR_TIMEOUT|SOCKINTR_RECV|SOCKINTR_FIN);
+
+ if (listen_bits)
+ cr_sema_signal(&listener->listen_sema);
+ if (read_bits)
+ cr_sema_signal(&listener->read_sema);
+
+ w5500ll_write_sock_reg(chip->spidev, socknum, interrupt, sockintr);
+ }
+ }
+
+ cr_end();
+}
+
+/* init() *********************************************************************/
+
+static implements_net_stream_conn *w5500_tcp_accept(implements_net_stream_listener *listener);
+static ssize_t w5500_tcp_read(implements_net_stream_conn *conn, void *buf, size_t count);
+static ssize_t w5500_tcp_write(implements_net_stream_conn *conn, void *buf, size_t count);
+static int w5500_tcp_close(implements_net_stream_conn *conn, bool rd, bool wr);
+
+static struct net_stream_listener_vtable w5500_tcp_listener_vtable = {
+ .accept = w5500_tcp_accept,
+};
+
+static struct net_stream_conn_vtable w5500_tcp_conn_vtable = {
+ .read = w5500_tcp_read,
+ .write = w5500_tcp_write,
+ .close = w5500_tcp_close,
+};
+
+static struct w5500 *w5500_chips[CONFIG_W5500_NUM] = {0};
+
+static void w5500_intrhandler(uint gpio, uint32_t UNUSED(event_mask)) {
+ for (size_t i = 0; i < ARRAY_LEN(w5500_chips); i++)
+ if (w5500_chips[i] && w5500_chips[i]->pin_intr == gpio)
+ cr_sema_signal_from_intrhandler(&w5500_chips[i]->intr);
+}
+
+void _w5500_init(struct w5500 *chip,
+ implements_spi *spi, uint pin_intr, uint pin_reset,
+ struct net_eth_addr addr) {
+ assert(chip);
+ assert(spi);
+
+ /* Initialize the data structures. */
+ *chip = (struct w5500){
+ /* const-after-init */
+ .spidev = spi,
+ .pin_intr = pin_intr,
+ .pin_reset = pin_reset,
+ .hwaddr = addr,
+ /* mutable */
+ .next_local_port = CONFIG_W5500_LOCAL_PORT_MIN,
+ };
+ for (uint8_t i = 0; i < 8; i++) {
+ chip->listeners[i] = (struct _w5500_tcp_listener){
+ /* const-after-init */
+ .vtable = &w5500_tcp_listener_vtable,
+ .socknum = i,
+ .active_conn = {
+ /* const-after-init */
+ .vtable = &w5500_tcp_conn_vtable,
+ /* mutable */
+ .read_open = false,
+ .write_open = false,
+ },
+ /* mutable */
+ .port = 0,
+ };
+ }
+
+ /* Initialize the hardware. */
+ gpio_set_irq_enabled_with_callback(pin_intr, GPIO_IRQ_EDGE_FALL, true, w5500_intrhandler);
+ gpio_set_dir(chip->pin_reset, GPIO_OUT);
+ w5500_hard_reset(chip);
+
+ /* Finally, wire in the interrupt handler. */
+ cr_disable_interrupts();
+ for (size_t i = 0; i < ARRAY_LEN(w5500_chips); i++) {
+ if (w5500_chips[i] == NULL) {
+ w5500_chips[i] = chip;
+ break;
+ }
+ }
+ cr_enable_interrupts();
+ coroutine_add(w5500_irq_cr, chip);
+}
+
+/* chip methods ***************************************************************/
+
+static inline void w5500_post_reset(struct w5500 *chip) {
+ /* The W5500 does not have a built-in MAC address, we must
+ * provide one. */
+ w5500ll_write_common_reg(chip->spidev, eth_addr, chip->hwaddr);
+
+ /* The RP2040 needs a 1/sys_clk hysteresis between interrupts
+ * for us to notice them. At the maximum-rated clock-rate of
+ * 133MHz, that means 7.5ns (but the sbc-harness overclocks
+ * the RP2040, so we could get away with even shorter).
+ *
+ * If intlevel is non-zero, then the hysteresis is
+ * (intlevel+1)*4/(150MHz), or (intlevel+1)*26.7ns; so even
+ * the shortest-possible hysteresis much larger than necessary
+ * for us. */
+ w5500ll_write_common_reg(chip->spidev, intlevel, uint16be_marshal(1));
+
+ /* This implementation does not care about any of the
+ * chip-level interrupts. */
+ w5500ll_write_common_reg(chip->spidev, chip_interrupt_mask, 0);
+
+ /* This implementation cares about interrupts for each
+ * socket. */
+ w5500ll_write_common_reg(chip->spidev, sock_interrupt_mask, 0xFF);
+
+ /* Configure retry/timeout.
+ *
+ * timeout_arp = 0.1ms * retry_time * (retry_count+1)
+ *
+ * retry_count
+ * timeout_tcp = 0.1ms * retry_time * Σ 2^min(n, floor(1+log_2(65535/retry_time)))
+ * n=0
+ *
+ * For retry_time=2000, retry_count=3, this means
+ *
+ * timeout_arp = 0.8s
+ * timeout_tcp = 3.0s
+ */
+ w5500ll_write_common_reg(chip->spidev, retry_time, uint16be_marshal(2000));
+ w5500ll_write_common_reg(chip->spidev, retry_count, 3);
+}
+
+void w5500_hard_reset(struct w5500 *chip) {
+ /* TODO: Replace blocking sleep_ms() with something libcr-friendly. */
+ gpio_put(chip->pin_reset, 0);
+ sleep_ms(1); /* minimum of 500us */
+ gpio_put(chip->pin_reset, 1);
+ sleep_ms(2); /* minimum of 1ms */
+
+ w5500_post_reset(chip);
+}
+
+void w5500_soft_reset(struct w5500 *chip) {
+ w5500ll_write_common_reg(chip->spidev, mode, CHIPMODE_RST);
+ while (w5500ll_read_common_reg(chip->spidev, mode) & CHIPMODE_RST)
+ cr_yield();
+
+ w5500_post_reset(chip);
+}
+
+void w5500_netcfg(struct w5500 *chip, struct w5500_netcfg cfg) {
+ w5500ll_write_common_reg(chip->spidev, ip_gateway_addr, cfg.gateway_addr);
+ w5500ll_write_common_reg(chip->spidev, ip_subnet_mask, cfg.subnet_mask);
+ w5500ll_write_common_reg(chip->spidev, ip_addr, cfg.addr);
+}
+
+implements_net_stream_listener *w5500_tcp_listen(struct w5500 *chip, uint8_t socknum,
+ uint16_t port) {
+ assert(chip);
+ assert(socknum < 8);
+ assert(port);
+
+ assert(chip->listeners[socknum].port == 0);
+ chip->listeners[socknum].port = port;
+
+ return &chip->listeners[socknum];
+}
+
+/*
+implements_net_packet_conn *w5500_udp_conn(struct w5500 *chip, uint8_t socknum,
+ uint16_t port) {
+ assert(chip);
+ assert(socknum < 8);
+ assert(port);
+
+ assert(chip->listeners[socknum].port == 0);
+ chip->listeners[socknum].port = port;
+
+ return &chip->listeners[socknum];
+}
+*/
+
+/* tcp_listener methods *******************************************************/
+
+static struct w5500 *w5500_tcp_listener_chip(struct _w5500_tcp_listener *listener) {
+ assert(listener);
+ assert(listener->socknum < 8);
+
+ struct _w5500_tcp_listener *sock0 = &listener[-listener->socknum];
+ assert(sock0);
+ struct w5500 *chip =
+ ((void *)sock0) - offsetof(struct w5500, listeners);
+ assert(chip);
+ return chip;
+}
+
+static inline void w5500_tcp_listener_cmd(struct _w5500_tcp_listener *listener, uint8_t cmd) {
+ assert(listener);
+ struct w5500 *chip = w5500_tcp_listener_chip(listener);
+ uint8_t socknum = listener->socknum;
+
+ cr_mutex_lock(&listener->cmd_mu);
+ w5500ll_write_sock_reg(chip->spidev, socknum, command, cmd);
+ while (w5500ll_read_sock_reg(chip->spidev, socknum, command) != 0x00)
+ cr_yield();
+ cr_mutex_unlock(&listener->cmd_mu);
+}
+
+static inline void w5500_tcp_listener_cmd_close(struct _w5500_tcp_listener *listener) {
+ assert(listener);
+ struct w5500 *chip = w5500_tcp_listener_chip(listener);
+ uint8_t socknum = listener->socknum;
+
+ w5500_tcp_listener_cmd(listener, CMD_CLOSE);
+ w5500ll_write_sock_reg(chip->spidev, socknum, interrupt, 0xFF);
+ while (w5500ll_read_sock_reg(chip->spidev, socknum, state) != STATE_CLOSED)
+ cr_yield();
+
+ listener->active_conn.read_open = listener->active_conn.write_open = false;
+}
+
+#define ASSERT_LISTENER() \
+ struct _w5500_tcp_listener *self = \
+ VCALL_SELF(struct _w5500_tcp_listener, \
+ implements_net_stream_listener, _self); \
+ struct w5500 *chip = w5500_tcp_listener_chip(self); \
+ uint8_t socknum = self->socknum;
+
+static implements_net_stream_conn *w5500_tcp_accept(implements_net_stream_listener *_self) {
+ ASSERT_LISTENER();
+
+ restart:
+ /* Mimics socket.c:socket(). */
+ w5500_tcp_listener_cmd_close(self);
+ w5500ll_write_sock_reg(chip->spidev, socknum, mode, SOCKMODE_TCP);
+ w5500ll_write_sock_reg(chip->spidev, socknum, local_port, uint16be_marshal(self->port));
+ w5500_tcp_listener_cmd(self, CMD_OPEN);
+ while (w5500ll_read_sock_reg(chip->spidev, socknum, state) != STATE_TCP_INIT)
+ cr_yield();
+
+ /* Mimics socket.c:listen(). */
+ w5500_tcp_listener_cmd(self, CMD_LISTEN);
+ for (;;) {
+ uint8_t state = w5500ll_read_sock_reg(chip->spidev, socknum, state);
+ switch (state) {
+ case STATE_TCP_LISTEN:
+ case STATE_TCP_SYNRECV:
+ cr_sema_wait(&self->listen_sema);
+ break;
+ case STATE_TCP_ESTABLISHED:
+ self->active_conn.read_open = true;
+ /* fall-through */
+ case STATE_TCP_CLOSE_WAIT:
+ self->active_conn.write_open = true;
+ return &self->active_conn;
+ default:
+ goto restart;
+ }
+ }
+}
+
+/* tcp_conn methods ***********************************************************/
+
+static struct _w5500_tcp_listener *w5500_tcp_conn_listener(struct _w5500_tcp_conn *conn) {
+ assert(conn);
+
+ struct _w5500_tcp_listener *list =
+ ((void *)conn) - offsetof(struct _w5500_tcp_listener, active_conn);
+ return list;
+}
+
+#define ASSERT_CONN() \
+ struct _w5500_tcp_conn *self = \
+ VCALL_SELF(struct _w5500_tcp_conn, implements_net_stream_conn, _self); \
+ struct _w5500_tcp_listener *listener = w5500_tcp_conn_listener(self); \
+ struct w5500 *chip = w5500_tcp_listener_chip(listener); \
+ uint8_t socknum = listener->socknum;
+
+static ssize_t w5500_tcp_write(implements_net_stream_conn *_self, void *buf, size_t count) {
+ ASSERT_CONN();
+ assert(buf);
+ assert(count);
+
+ /* What we really want is to pause until we receive an ACK for
+ * some data we just queued, so that we can line up some new
+ * data to keep the buffer full. But that's not what
+ * SEND_FINIAIUI, the SEND_FINISHED interrupt doesn't fire
+ * until we receive the *last* ACK for the data, when the
+ * buffer is entirely empty.
+ *
+ * Which means we basically have to busy-poll for space in the
+ * buffer becoming available.
+ *
+ * We'll add more data to the buffer whenever there is
+ * `min_free_space` in the buffer (or the rest of the data
+ * fits in the buffer).
+ *
+ * This `min_free_space` can probably stand to be tuned; must
+ * be >0, <=bufsize. `1500-58` is the 100BaseT MTU minus the
+ * Ethernet+IP+TCP overhead. */
+ uint16_t bufsize = ((uint16_t)w5500ll_read_sock_reg(chip->spidev, socknum, tx_buf_size))*1024;
+ uint16_t min_free_space = MIN(1500-58, bufsize/4);
+
+ size_t done = 0;
+ while (done < count) {
+ if (!self->write_open)
+ return -1;
+ uint8_t state = w5500ll_read_sock_reg(chip->spidev, socknum, state);
+ if (state != STATE_TCP_ESTABLISHED && state != STATE_TCP_CLOSE_WAIT)
+ return -1;
+
+ uint16_t freesize = uint16be_unmarshal(w5500ll_read_sock_reg(chip->spidev, socknum, tx_free_size));
+ if (freesize < count-done && freesize < min_free_space) {
+ /* Wait for more buffer space. */
+ cr_yield();
+ continue;
+ }
+
+ /* Queue data to be sent. */
+ uint16_t ptr = uint16be_unmarshal(w5500ll_read_sock_reg(chip->spidev, socknum, tx_write_pointer));
+ w5500ll_write(chip->spidev, ptr, CTL_BLOCK_SOCK(socknum, TX), &((char *)buf)[done], freesize);
+ w5500ll_write_sock_reg(chip->spidev, socknum, tx_write_pointer, uint16be_marshal(ptr+freesize));
+
+ /* Submit the queue. */
+ w5500_tcp_listener_cmd(listener, CMD_SEND);
+ done += freesize;
+ }
+ return done;
+}
+
+static ssize_t w5500_tcp_read(implements_net_stream_conn *_self, void *buf, size_t count) {
+ ASSERT_CONN();
+ assert(buf);
+ assert(count);
+
+ size_t done = 0;
+ while (!done) {
+ if (!self->read_open)
+ return -1;
+ uint8_t state = w5500ll_read_sock_reg(chip->spidev, socknum, state);
+ switch (state) {
+ case STATE_TCP_CLOSE_WAIT:
+ return 0; /* EOF */
+ case STATE_TCP_ESTABLISHED: case STATE_TCP_FIN_WAIT:
+ break; /* OK */
+ default:
+ return -1;
+ }
+
+ uint16_t avail = uint16be_unmarshal(w5500ll_read_sock_reg(chip->spidev, socknum, rx_size));
+ if (!avail) {
+ cr_sema_wait(&listener->read_sema);
+ continue;
+ }
+ if ((size_t)avail > count)
+ avail = count;
+ uint16_t ptr = uint16be_unmarshal(w5500ll_read_sock_reg(chip->spidev, socknum, rx_read_pointer));
+ w5500ll_read(chip->spidev, ptr, CTL_BLOCK_SOCK(socknum, RX), &((char *)buf)[done], avail);
+ w5500ll_write_sock_reg(chip->spidev, socknum, rx_read_pointer, uint16be_marshal(ptr+avail));
+
+ w5500_tcp_listener_cmd(listener, CMD_RECV);
+ done += avail;
+ }
+ return done;
+}
+
+static int w5500_tcp_close(implements_net_stream_conn *_self, bool rd, bool wr) {
+ ASSERT_CONN();
+
+ if (rd)
+ self->read_open = false;
+
+ if (wr && self->write_open) {
+ w5500_tcp_listener_cmd(listener, CMD_DISCON);
+ while (self->write_open) {
+ uint8_t state = w5500ll_read_sock_reg(chip->spidev, socknum, state);
+ switch (state) {
+ case STATE_TCP_FIN_WAIT:
+ self->write_open = false;
+ /* Can still read */
+ if (!self->read_open)
+ w5500_tcp_listener_cmd_close(listener);
+ break;
+ case STATE_CLOSED:
+ self->write_open = false;
+ break;
+ }
+ }
+ }
+
+ return 0;
+}
+
+/* udp_conn methods ***********************************************************/
diff --git a/libhw/w5500_ll.h b/libhw/w5500_ll.h
new file mode 100644
index 0000000..57bfccd
--- /dev/null
+++ b/libhw/w5500_ll.h
@@ -0,0 +1,363 @@
+/* libhw/w5500_ll.h - Low-level header library for the WIZnet W5500 chip
+ *
+ * Based entirely on the W5500 datasheet, v1.1.0.
+ * https://docs.wiznet.io/img/products/w5500/W5500_ds_v110e.pdf
+ *
+ * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com>
+ * SPDX-Licence-Identifier: AGPL-3.0-or-later
+ */
+
+#ifndef _LIBHW_W5500_LL_H_
+#define _LIBHW_W5500_LL_H_
+
+#include <assert.h> /* for assert(), static_assert() */
+#include <stdint.h> /* for uint{n}_t */
+#include <string.h> /* for memcmp() */
+
+#include <libmisc/vcall.h> /* for VCALL() */
+#include <libmisc/endian.h> /* for uint16be_t */
+
+#include <libhw/generic/net.h> /* for struct net_eth_addr, struct net_ip4_addr */
+#include <libhw/generic/spi.h> /* for implements_spi */
+
+
+/* Low-level protocol built on SPI frames. ***********************************/
+
+/* A u8 control byte has 3 parts: block-ID, R/W, and operating-mode. */
+
+/* Part 1: Block ID. */
+#define CTL_MASK_BLOCK 0b11111000
+#define _CTL_BLOCK_RES 0b00000
+#define _CTL_BLOCK_REG 0b01000
+#define _CTL_BLOCK_TX 0b10000
+#define _CTL_BLOCK_RX 0b11000
+#define CTL_BLOCK_SOCK(n,part) (((n)<<5)|(_CTL_BLOCK_##part))
+#define CTL_BLOCK_COMMON_REG CTL_BLOCK_SOCK(0,RES)
+
+/* Part 2: R/W. */
+#define CTL_MASK_RW 0b100
+#define CTL_R 0b000
+#define CTL_W 0b100
+
+/* Part 3: Operating mode. */
+#define CTL_MASK_OM 0b11
+#define CTL_OM_VDM 0b00
+#define CTL_OM_FDM1 0b01
+#define CTL_OM_FDM2 0b10
+#define CTL_OM_FDM4 0b11
+
+/* Even though SPI is a full-duplex protocol, the W5500's spiframe on top of it is only half-duplex.
+ * Lame. */
+
+static inline void
+w5500ll_write(implements_spi *spidev, uint16_t addr, uint8_t block, void *data, size_t data_len) {
+ assert(spidev);
+ assert((block & ~CTL_MASK_BLOCK) == 0);
+ assert(data);
+ assert(data_len);
+
+ uint8_t header[3] = {
+ (uint8_t)((addr >> 8) & 0xFF),
+ (uint8_t)(addr & 0xFF),
+ (block & CTL_MASK_BLOCK) | CTL_W | CTL_OM_VDM,
+ };
+ struct bidi_iovec iov[] = {
+ {.iov_read_dst = NULL, .iov_write_src = header, .iov_len = sizeof(header)},
+ {.iov_read_dst = NULL, .iov_write_src = data, .iov_len = data_len},
+ };
+ VCALL(spidev, readwritev, iov, 2);
+}
+
+static inline void
+w5500ll_read(implements_spi *spidev, uint16_t addr, uint8_t block, void *data, size_t data_len) {
+ assert(spidev);
+ assert((block & ~CTL_MASK_BLOCK) == 0);
+ assert(data);
+ assert(data_len);
+
+ uint8_t header[3] = {
+ (uint8_t)((addr >> 8) & 0xFF),
+ (uint8_t)(addr & 0xFF),
+ (block & CTL_MASK_BLOCK) | CTL_R | CTL_OM_VDM,
+ };
+ struct bidi_iovec iov[] = {
+ {.iov_read_dst = NULL, .iov_write_src = header, .iov_len = sizeof(header)},
+ {.iov_read_dst = data, .iov_write_src = NULL, .iov_len = data_len},
+ };
+ VCALL(spidev, readwritev, iov, 2);
+}
+
+/* Common chip-wide registers. ***********************************************/
+
+struct w5500ll_block_common_reg {
+ uint8_t mode; /* MR; bitfield, see CHIPMODE_{x} below */
+ struct net_ip4_addr ip_gateway_addr; /* GAR0 ... GAR3 */
+ struct net_ip4_addr ip_subnet_mask; /* SUBR0 ... SUBR3 */
+ struct net_eth_addr eth_addr; /* SHAR0 ... SHAR5 */
+ struct net_ip4_addr ip_addr; /* SIPR0 ... SIPR3 */
+
+ uint16be_t intlevel; /* INTLEVEL0, INTLEVEL1; if non-zero,
+ * hysteresis between pin_intr being pulled
+ * low (hysteresis=(intlevel+1)*4/(150MHz)) */
+ uint8_t chip_interrupt; /* IR; bitfield, see CHIPINTR_{x} below */
+ uint8_t chip_interrupt_mask; /* IMR; bitfield, see CHIPINTR_{x} below, 0=disable, 1=enable */
+ uint8_t sock_interrupt; /* SIR; bitfield of which sockets have their .interrupt set */
+ uint8_t sock_interrupt_mask; /* SIMR; bitfield of sockets, 0=disable, 1=enable */
+ uint16be_t retry_time; /* RTR0, RTR0; configures re-transmission period, in units of 100µs */
+ uint8_t retry_count; /* RCR; configures max re-transmission count */
+
+ uint8_t ppp_lcp_request_timer; /* PTIMER */
+ uint8_t ppp_lcp_magic_bumber; /* PMAGIC */
+ struct net_eth_addr ppp_dst_eth_addr; /* PHAR0 ... PHAR5 */
+ uint16be_t ppp_sess_id; /* PSID0 ... PSID1 */
+ uint16be_t ppp_max_seg_size; /* PMRU0 ... PMRU1 */
+
+ struct net_ip4_addr unreachable_ip_addr; /* UIPR0 ... UIPR3 */
+ uint16be_t unreachable_port; /* UPORTR0, UPORTR1 */
+
+ uint8_t phy_cfg; /* PHYCFGR */
+
+ uint8_t _reserved[10];
+
+ uint8_t chip_version; /* VERSIONR */
+};
+static_assert(sizeof(struct w5500ll_block_common_reg) == 0x3A);
+
+/* bitfield */
+#define CHIPMODE_RST ((uint8_t)(1<<7)) /* software reset */
+#define _CHIPMODE_UNUSED6 ((uint8_t)(1<<6))
+#define CHIPMODE_WOL ((uint8_t)(1<<5)) /* wake-on-lan */
+#define CHIPMODE_BLOCK_PING ((uint8_t)(1<<4))
+#define CHIPMODE_PPP ((uint8_t)(1<<3))
+#define _CHIPMODE_UNUSED2 ((uint8_t)(1<<2))
+#define CHIPMODE_FORCE_ARP ((uint8_t)(1<<1))
+#define _CHIPMODE_UNUSED0 ((uint8_t)(1<<0))
+
+#define CHIPINTR_CONFLICT ((uint8_t)(1<<7)) /* ARP says remote IP is self */
+#define CHIPINTR_UNREACH ((uint8_t)(1<<6))
+#define CHIPINTR_PPP_CLOSE ((uint8_t)(1<<6))
+#define CHIPINTR_WOL ((uint8_t)(1<<4)) /* wake-on-LAN */
+#define _CHIPINTR_UNUSED3 ((uint8_t)(1<<3))
+#define _CHIPINTR_UNUSED2 ((uint8_t)(1<<2))
+#define _CHIPINTR_UNUSED1 ((uint8_t)(1<<1))
+#define _CHIPINTR_UNUSED0 ((uint8_t)(1<<0))
+
+#define w5500ll_write_common_reg(spidev, field, val) \
+ w5500ll_write_reg(spidev, \
+ CTL_BLOCK_COMMON_REG, \
+ struct w5500ll_block_common_reg, \
+ field, val)
+
+
+#define w5500ll_read_common_reg(spidev, field) \
+ w5500ll_read_reg(spidev, \
+ CTL_BLOCK_COMMON_REG, \
+ struct w5500ll_block_common_reg, \
+ field)
+
+/* Per-socket registers. *****************************************************/
+
+struct w5500ll_block_sock_reg {
+ uint8_t mode; /* Sn_MR; see SOCKMODE_{x} below */
+ uint8_t command; /* Sn_CR; see CMD_{x} below */
+ uint8_t interrupt; /* Sn_IR; bitfield, see SOCKINTR_{x} below */
+ uint8_t state; /* Sn_SR; see STATE_{x} below */
+ uint16be_t local_port; /* Sn_PORT0, Sn_PORT1 */
+ struct net_eth_addr remote_eth_addr; /* Sn_DHAR0 ... SnDHAR5 */
+ struct net_ip4_addr remote_ip_addr; /* Sn_DIPR0 ... Sn_DIP3 */
+ uint16be_t remote_port; /* Sn_DPORT0 ... Sn_DPORT1 */
+
+ uint16be_t max_seg_size; /* Sn_MSSR0, Sn_MSSR1 */
+ uint8_t _reserved0[1];
+ uint8_t ip_tos; /* Sn_TOS */
+ uint8_t ip_ttl; /* Sn_TTL */
+ uint8_t _reserved1[7];
+
+ uint8_t rx_buf_size; /* Sn_RXBUF_SIZE; in KiB, power of 2, <= 16 */
+ uint8_t tx_buf_size; /* Sn_TXBUF_SIZE; in KiB, power of 2, <= 16 */
+ uint16be_t tx_free_size; /* Sn_TX_FSR0, Sn_TX_FSR1 */
+ uint16be_t tx_read_pointer; /* Sn_TX_RD0, Sn_TX_RD1 */
+ uint16be_t tx_write_pointer; /* Sn_TX_WR0, Sn_TX_WR1 */
+ uint16be_t rx_size; /* Sn_RX_RSR0, Sn_RX_RSR1 */
+ uint16be_t rx_read_pointer; /* Sn_RX_RD0, Sn_RX_RD1 */
+ uint16be_t rx_write_pointer; /* Sn_RX_WR0, Sn_RX_WR1 */
+
+ uint8_t interrupt_mask; /* Sn_IMR */
+ uint16be_t fragment_offset; /* Sn_FRAG0, Sn_FRAG1 */
+ uint8_t keepalive_timer; /* Sn_KPALVTR */
+};
+static_assert(sizeof(struct w5500ll_block_sock_reg) == 0x30);
+
+/* low 4 bits are the main enum, high 4 bits are flags */
+#define SOCKMODE_CLOSED ((uint8_t)0b0000)
+#define SOCKMODE_TCP ((uint8_t)0b0001)
+#define SOCKMODE_UDP ((uint8_t)0b0010)
+#define SOCKMODE_MACRAW ((uint8_t)0b0100)
+
+#define SOCKMODE_FLAG_TCP_NODELAY_ACK ((uint8_t)(1<<5))
+
+#define SOCKMODE_FLAG_UDP_ENABLE_MULTICAST ((uint8_t)(1<<7))
+#define SOCKMODE_FLAG_UDP_BLOCK_BROADCAST ((uint8_t)(1<<6))
+#define SOCKMODE_FLAG_UDP_MULTICAST_DOWNGRADE ((uint8_t)(1<<5))
+#define SOCKMODE_FLAG_UDP_BLOCK_UNICAST ((uint8_t)(1<<4))
+
+#define SOCKMODE_FLAG_MACRAW_MAC_FILTERING ((uint8_t)(1<<7))
+#define SOCKMODE_FLAG_MACRAW_BLOCK_BROADCAST ((uint8_t)(1<<6))
+#define SOCKMODE_FLAG_MACRAW_BLOCK_MULTICAST ((uint8_t)(1<<5))
+#define SOCKMODE_FLAG_MACRAW_BLOCK_V6 ((uint8_t)(1<<4))
+
+#define CMD_OPEN ((uint8_t)0x01)
+#define CMD_LISTEN ((uint8_t)0x02) /* TCP-only */
+#define CMD_CONNECT ((uint8_t)0x04) /* TCP-only: dial */
+#define CMD_DISCON ((uint8_t)0x08) /* TCP-only: send FIN */
+#define CMD_CLOSE ((uint8_t)0x10)
+#define CMD_SEND ((uint8_t)0x20)
+#define CMD_SEND_MAC ((uint8_t)0x21) /* UDP-only: send to remote_eth_addr without doing ARP on remote_ip_addr */
+#define CMD_SEND_KEEP ((uint8_t)0x22) /* TCP-only: send a keepalive without any data */
+#define CMD_RECV ((uint8_t)0x40)
+
+#define _SOCKINTR_SEND_UNUSED7 ((uint8_t)1<<7)
+#define _SOCKINTR_SEND_UNUSED6 ((uint8_t)1<<6)
+#define _SOCKINTR_SEND_UNUSED5 ((uint8_t)1<<5)
+#define SOCKINTR_SEND_OK ((uint8_t)1<<4)
+#define SOCKINTR_TIMEOUT ((uint8_t)1<<3)
+#define SOCKINTR_RECV ((uint8_t)1<<2)
+#define SOCKINTR_FIN ((uint8_t)1<<1)
+#define SOCKINTR_CONN ((uint8_t)1<<1) /* first for SYN, then when SOCKMODE_ESTABLISHED */
+
+#define STATE_CLOSED ((uint8_t)0x00)
+
+/**
+ * The TCP state diagram is as follows.
+ * - Reading the W5500's "state" register does not distinguish between FIN_WAIT_1 and FIN_WAIT_2;
+ * it just has a single FIN_WAIT.
+ * - At any point the state can jump to "CLOSED" either by CMD_CLOSE or by a timeout.
+ * - Writing data is valid in ESTABLISHED and CLOSE_WAIT.
+ * - Reading data is valid in ESTABLISHED and FIN_WAIT.
+ *
+ * TCP state diagram, showing the flow of │ CLOSED │ ━━ role separator ┌───────┐
+ * SYN, FIN, and their assocaited ACKs. └────────┘ ══ state transition │ state │
+ * V ┈┈ packet flow └───────┘
+ * (CMD_OPEN) ║
+ * V (action/event)
+ * ┌────────┐
+ * ┏━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━│ INIT │━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━┓
+ * ┃ server ┃ └────────┘ ┃ client ┃
+ * ┣━━━━━━━━━━━━━━━━┛ V ┃┃ V ┗━━━━━━━━━━━━━━━━┫
+ * ┃ ╔═══════════╝ ┃┃ ╚═══════════╗ ┃
+ * ┃ (CMD_LISTEN) ┃┃ (CMD_CONNECT) ┃
+ * ┃ V ┌┃┃┈┈┈┈┈┈┈┈<(send SYN) ┃
+ * ┃ ┌────────┐ ┊┃┃ V ┃
+ * ┃ │ LISTEN │ ┊┃┃ ┌─────────┐ ┃
+ * ┃ └────────┘ ┊┃┃ │ SYNSENT │ ┃
+ * ┃ V ┊┃┃ └─────────┘ ┃
+ * ┃ (recv SYN)<┈┈┈┈┈┈┘┃┃ V ┃
+ * ┃ (send SYN+ACK)>┈┈┈┈┐┃┃ ║ ┃
+ * ┃ V └┃┃┈┈┈┈┈┈>(recv SYN+ACK) ┃
+ * ┃ ┌─────────┐ ┌┃┃┈┈┈┈┈┈┈┈<(send ack) ┃
+ * ┃ │ SYNRECV │ ┊┃┃ ║ ┃
+ * ┃ └─────────┘ ┊┃┃ ║ ┃
+ * ┃ V V ┊┃┃ ║ ┃
+ * ┃ ║ (recv ACK)<┈┈┈┘┃┃ ║ ┃
+ * ┃ ║ ╚═════════╗ ┃┃ ╔═══════════╝ ┃
+ * ┃ ╚═══╗ V ┃┃ V ┃
+ * ┃ ║ ┌─────────────┐ ┃
+ * ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━║━━━━━━━│ ESTABLISHED │━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
+ * ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━║━━━━━━━└─────────────┘━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
+ * ┃ ║ V ┃┃ V ┃
+ * ┃ ╠═══════════╝ ┃┃ ╚═══════════╗ ┃
+ * ┃ (CMD_DISCON) ┃┃ ║ ┃
+ * ┃ Both sides sent ┌┈┈┈<(send FIN)>┈┈┈┈┈┈┐┃┃ ║ ┃
+ * ┃ FIN at the "same" ┊ V └┃┃┈┈┈┈┈┈┈┈>(recv FIN) ┃
+ * ┃ time; both are ┊ ┌────────────┐ ┌┃┃┈┈┈┈┈┈┈┈<(send ACK) ┃
+ * ┃ active closers ┊ │ FIN_WAIT_1 │ ┊┃┃ V ┃
+ * ┃ / \ ┊ └────────────┘ ┊┃┃ ┌────────────┐ ┃
+ * ┃ ,------' '------, ┊ V V ┊┃┃ │ CLOSE_WAIT │ ┃
+ * ┃ ╔════════════════╝ ║ ┊┃┃ └────────────┘ ┃
+ * ┃ (recv FIN)<┈┈┈┈┤ ╔══╝ ┊┃┃ V ┃
+ * ┃ ┌┈┈<(send ACK)>┈┈┐ ┊ ║ ┊┃┃ ║ ┃
+ * ┃ ┊ ║ └┈┈┈┈┈>(recv ACK)<┈┈┈┈┈┈┘┃┃ ║ ┃
+ * ┃ ┊ V ┊ V ┃┃ ║ ┃
+ * ┃ ┊ ┌─────────┐ ┊ ┌────────────┐ ┃┃ ║ ┃
+ * ┃ ┊ │ CLOSING │ ┊ │ FIN_WAIT_2 │ ┃┃ ║ ┃
+ * ┃ ┊ └─────────┘ ┊ └────────────┘ ┃┃ (CMD_DISCON) ┃
+ * ┃ ┊ V ┊ V ┌┃┃┈┈┈┈┈┈┈┈<(send FIN) ┃
+ * ┃ ┊ ║ └┈┈┈>(recv FIN)<┈┈┈┈┈┈┘┃┃ ║ ┃
+ * ┃ ┊ ║ ┌┈┈┈┈┈<(send ACK)>┈┈┈┈┈┈┐┃┃ V ┃
+ * ┃ └┈┈>(recv ACK)<┈┈┘ ╚═╗ ┊┃┃ ┌──────────┐ ┃
+ * ┃ ╚════════════════╗ ║ ┊┃┃ │ LAST_ACK │ ┃
+ * ┃ V V ┊┃┃ └──────────┘ ┃
+ * ┃ ┌───────────┐ ┊┃┃ V ┃
+ * ┃ │ TIME_WAIT │ ┊┃┃ ║ ┃
+ * ┃ └───────────┘ └┃┃┈┈┈┈┈┈┈┈>(recv ACK) ┃
+ * ┃ V ┃┃ ║ ┃
+ * ┣━━━━━━━━━━━━━━━━┓ (2*MSL has elapsed) ┃┃ ║ ┏━━━━━━━━━━━━━━━━┫
+ * ┃ active closer ┃ ╚═══════════╗ ┃┃ ╔═══════════╝ ┃ passive closer ┃
+ * ┗━━━━━━━━━━━━━━━━┻━━━━━━━━━━━━━━━━━━━━━━━━━V━┛┗━V━━━━━━━━━━━━━━━━━━━━━━━━━━┻━━━━━━━━━━━━━━━━┛
+ * ┌────────┐
+ * │ CLOSED │
+ */
+#define STATE_TCP_INIT ((uint8_t)0x13)
+#define STATE_TCP_LISTEN ((uint8_t)0x14) /* server */
+#define STATE_TCP_SYNSENT ((uint8_t)0x15) /* client; during dial */
+#define STATE_TCP_SYNRECV ((uint8_t)0x16) /* server; during accept */
+#define STATE_TCP_ESTABLISHED ((uint8_t)0x17)
+#define STATE_TCP_FIN_WAIT ((uint8_t)0x18) /* during active close */
+#define STATE_TCP_CLOSING ((uint8_t)0x1a) /* during active close */
+#define STATE_TCP_TIME_WAIT ((uint8_t)0x1b) /* during active close */
+#define STATE_TCP_CLOSE_WAIT ((uint8_t)0x1c) /* during passive close */
+#define STATE_TCP_LAST_ACK ((uint8_t)0x1d) /* during passive close */
+
+#define STATE_UDP ((uint8_t)0x22)
+
+#define STATE_MACRAW ((uint8_t)0x42)
+
+#define w5500ll_write_sock_reg(spidev, socknum, field, val) \
+ w5500ll_write_reg(spidev, \
+ CTL_BLOCK_SOCK(socknum, REG), \
+ struct w5500ll_block_sock_reg, \
+ field, val)
+
+#define w5500ll_read_sock_reg(spidev, socknum, field) \
+ w5500ll_read_reg(spidev, \
+ CTL_BLOCK_SOCK(socknum, REG), \
+ struct w5500ll_block_sock_reg, \
+ field)
+
+/******************************************************************************/
+
+#define w5500ll_write_reg(spidev, blockid, blocktyp, field, val) do { \
+ typeof((blocktyp){}.field) lval = val; \
+ w5500ll_write(spidev, \
+ offsetof(blocktyp, field), \
+ blockid, \
+ &lval, \
+ sizeof(lval)); \
+ } while (0)
+
+/* The datasheet tells us that multi-byte reads are non-atomic and
+ * that "it is recommended that you read all 16-bits twice or more
+ * until getting the same value". */
+#define w5500ll_read_reg(spidev, blockid, blocktyp, field) ({ \
+ typeof((blocktyp){}.field) val; \
+ w5500ll_read(spidev, \
+ offsetof(blocktyp, field), \
+ blockid, \
+ &val, \
+ sizeof(val)); \
+ if (sizeof(val) > 1) \
+ for (;;) { \
+ typeof(val) val2; \
+ w5500ll_read(spidev, \
+ offsetof(blocktyp, field), \
+ blockid, \
+ &val2, \
+ sizeof(val)); \
+ if (memcmp(&val2, &val, sizeof(val)) == 0) \
+ break; \
+ val = val2; \
+ } \
+ val; \
+ })
+
+#endif /* _LIBHW_W5500_LL_H_ */