summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLuke T. Shumaker <lukeshu@lukeshu.com>2024-10-19 17:36:28 -0600
committerLuke T. Shumaker <lukeshu@lukeshu.com>2024-10-19 17:36:28 -0600
commitf4400fe41a5a80ea58f8881eaa263aae99636698 (patch)
treec0f0d7aa440d416241ae3595c1f8e76fa83be200
parent221a0cb9c45c205cb07dfd6bc91d401363d3c8c1 (diff)
finish w5500?
-rw-r--r--cmd/sbc_harness/config/config.h23
-rw-r--r--cmd/sbc_harness/hw/w5500.c614
-rw-r--r--cmd/sbc_harness/hw/w5500.h15
-rw-r--r--cmd/sbc_harness/hw/w5500_ll.h374
4 files changed, 629 insertions, 397 deletions
diff --git a/cmd/sbc_harness/config/config.h b/cmd/sbc_harness/config/config.h
index 4817bf8..c3e8bf3 100644
--- a/cmd/sbc_harness/config/config.h
+++ b/cmd/sbc_harness/config/config.h
@@ -7,7 +7,14 @@
#ifndef _CONFIG_H_
#define _CONFIG_H_
-#define CONFIG_NETIO_NUM_CONNS 8
+/* W5500 **********************************************************************/
+
+/**
+ * How many W5500 chips we have.
+ */
+#define CONFIG_W5500_NUM 1
+
+/* 9P *************************************************************************/
#define CONFIG_9P_PORT 564
/**
@@ -38,14 +45,20 @@
#define CONFIG_9P_MAX_REQS 2
#define CONFIG_9P_MAX_ERR_SIZE 128 /* 128 is what Plan 9 4e uses */
+/* COROUTINE ******************************************************************/
+
#define CONFIG_COROUTINE_DEFAULT_STACK_SIZE (32*1024)
#define CONFIG_COROUTINE_MEASURE_STACK 1 /* bool */
#define CONFIG_COROUTINE_PROTECT_STACK 1 /* bool */
#define CONFIG_COROUTINE_DEBUG 0 /* bool */
#define CONFIG_COROUTINE_VALGRIND 0 /* bool */
-#define CONFIG_COROUTINE_NUM (1 /* usb_common */ +\
- 1 /* usb_keyboard */ +\
- CONFIG_NETIO_NUM_CONNS /* accept+read */ +\
- (CONFIG_9P_MAX_REQS*CONFIG_NETIO_NUM_CONNS) /* work+write */ )
+
+#define _CONFIG_9P_NUM_SOCKS 7
+#define CONFIG_COROUTINE_NUM ( \
+ 1 /* usb_common */ + \
+ 1 /* usb_keyboard */ + \
+ CONFIG_W5500_NUM /* irq handler */ + \
+ _CONFIG_9P_NUM_SOCKS /* 9P accept()+read() */ + \
+ (CONFIG_9P_MAX_REQS*_CONFIG_9P_NUM_SOCKS) /* 9P work+write() */ )
#endif /* _CONFIG_H_ */
diff --git a/cmd/sbc_harness/hw/w5500.c b/cmd/sbc_harness/hw/w5500.c
index 5e418f1..7bd2fde 100644
--- a/cmd/sbc_harness/hw/w5500.c
+++ b/cmd/sbc_harness/hw/w5500.c
@@ -39,12 +39,16 @@
*/
#include <pico/time.h> /* for sleep_ms() */
-#include <hardware/gpio.h> /* pico-sdk:hardware_gpio5 */
+#include <hardware/gpio.h> /* pico-sdk:hardware_gpio */
#include <libcr/coroutine.h>
#include <libmisc/vcall.h>
+#include "hw/w5500_ll.h"
#include "hw/w5500.h"
+
+/* Config *********************************************************************/
+
#include "config.h"
/* These are the default values of the Linux kernel's
@@ -57,343 +61,94 @@
# define CONFIG_W5500_LOCAL_PORT_MAX 60999
#endif
-#define UNUSED(name)
-
-#define ASSERT_SAMETYPE(a, b) \
- static_assert(_Generic(a, typeof(b): 1, default: 0), \
- "typeof("#a") != typeof("#b")")
-
-
-/* Low-level protocol built on SPI frames. ***********************************/
-
-/* A u8 control byte has 3 parts: block-ID, R/W, and
- * operating-mode. */
+#ifndef CONFIG_W5500_NUM
+# error config.h must define CONFIG_W5500_NUM
+#endif
-/* 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)
+/* C language *****************************************************************/
-/* Part 2: R/W. */
-#define CTL_MASK_RW 0b100
-#define CTL_R 0b000
-#define CTL_W 0b100
+#define UNUSED(name)
+#define ARRAY_LEN(ary) (sizeof(ary)/sizeof((ary)[0]))
-/* 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
+/* mid-level utilities ********************************************************/
-/* The W5500 has 2 channels 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.)
- *
- * Even though SPI is a full-duplex protocol, the W5500's RPC protocol
- * on top of it is only half-duplex. Lame.
+/*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;
+ *}
*/
-void w5500_spiframe_write(struct 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);
+static struct w5500 *w5500_sock_chip(struct _w5500_listener *listener) {
+ assert(listener);
+ assert(listener->socknum < 8);
- 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);
+ struct _w5500_listener *sock0 = &listener[-listener->socknum];
+ assert(sock0);
+ struct w5500 *chip =
+ ((void *)sock0) - offsetof(struct w5500, listeners);
+ assert(chip);
+ return chip;
}
-void w5500_spiframe_read(struct 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);
+static inline void w5500_sock_cmd(struct _w5500_listener *listener, uint8_t cmd) {
+ assert(listener);
+ struct w5500 *chip = w5500_sock_chip(listener);
+ uint8_t socknum = listener->socknum;
- 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);
+ 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);
}
-/* Offsets and sizes for use with that protocol. *****************************/
+static void w5500_sock_cmd_close(struct _w5500_listener *listener) {
+ assert(listener);
+ struct w5500 *chip = w5500_sock_chip(listener);
+ uint8_t socknum = listener->socknum;
-struct uint16_be {
- uint8_t octets[2];
-};
+ w5500_sock_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();
-struct w5500_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 */
+ listener->active_conn.read_open = listener->active_conn.write_open = false;
+}
- struct uint16_be intlevel; /* INTLEVEL0, INTLEVEL1; minimum time before pin_intr can be pulled low again (time=(intlevel+1)*4/PLL) */
- 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 */
- struct uint16_be retry_time; /* RTR0, RTR0; configures re-transmission period, in units of 100µs */
- uint8_t retry_count; /* RCR; configures max re-transmission count */
+static COROUTINE w5500_irq_cr(void *_chip) {
+ struct w5500 *chip = _chip;
+ cr_begin();
- uint8_t ppp_lcp_request_timer; /* PTIMER */
- uint8_t ppp_lcp_magic_bumber; /* PMAGIC */
- struct net_eth_addr ppp_dst_eth_addr; /* PHAR0 ... PHAR5 */
- struct uint16_be ppp_sess_id; /* PSID0 ... PSID1 */
- struct uint16_be ppp_max_seg_size; /* PMRU0 ... PMRU1 */
+ for (;;) {
+ cr_sema_wait(&chip->intr);
+ if (w5500ll_read_common_reg(chip->spidev, chip_interrupt))
+ w5500ll_write_common_reg(chip->spidev, chip_interrupt, 0xFF);
- struct net_ip4_addr unreachable_ip_addr; /* UIPR0 ... UIPR3 */
- struct uint16_be unreachable_port; /* UPORTR0, UPORTR1 */
+ 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_listener *listener = &chip->listeners[socknum];
- uint8_t phy_cfg; /* PHYCFGR */
+ uint8_t sockintr = w5500ll_read_sock_reg(chip->spidev, socknum, interrupt);
- uint8_t _reserved[10];
+ /* SOCKINTR_SEND_OK is useless. */
+ uint8_t listen_bits = sockintr & (SOCKINTR_TIMEOUT|SOCKINTR_CONN),
+ read_bits = sockintr & (SOCKINTR_TIMEOUT|SOCKINTR_RECV|SOCKINTR_FIN);
- uint8_t chip_version; /* VERSIONR */
-};
-static_assert(sizeof(struct w5500_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));
-#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));
-
-struct w5500_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 */
- struct uint16_be 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 */
- struct uint16_be remote_port; /* Sn_DPORT0 ... Sn_DPORT1 */
-
- struct uint16_be 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 */
- uint8_t tx_buf_size; /* Sn_TXBUF_SIZE */
- struct uint16_be tx_free_size; /* Sn_TX_FSR0, Sn_TX_FSR1 */
- struct uint16_be tx_read_pointer; /* Sn_TX_RD0, Sn_TX_RD1 */
- struct uint16_be tx_write_pointer; /* Sn_TX_WR0, Sn_TX_WR1 */
- struct uint16_be rx_size; /* Sn_RX_RSR0, Sn_RX_RSR1 */
- struct uint16_be rx_read_pointer; /* Sn_RX_RD0, Sn_RX_RD1 */
- struct uint16_be rx_write_pointer; /* Sn_RX_WR0, Sn_RX_WR1 */
-
- uint8_t interrupt_mask; /* Sn_IMR */
- struct uint16_be fragment_offset; /* Sn_FRAG0, Sn_FRAG1 */
- uint8_t keepalive_timer; /* Sn_KPALVTR */
-};
-static_assert(sizeof(struct w5500_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/reply 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_FINISHED ((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_CON ((uint8_t)1<<1) /* once for SYN, then once 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 REGWRITE_COMMON(spidev, field, val) do { \
- ASSERT_SAMETYPE(val, (struct w5500_block_common_reg){}.field); \
- typeof(val) lval = val; \
- w5500_spiframe_write(spidev, \
- offsetof(struct w5500_block_common_reg, field), \
- CTL_BLOCK_COMMON_REG, \
- &lval, \
- sizeof(lval)); \
- } while (0)
-
-
-#define REGREAD_COMMON(spidev, field, typ) ({ \
- typ val; \
- ASSERT_SAMETYPE(val, (struct w5500_block_common_reg){}.field); \
- w5500_spiframe_read(spidev, \
- offsetof(struct w5500_block_sock_reg, field), \
- CTL_BLOCK_COMMON, \
- &(val), \
- sizeof(val)); \
- val; \
- })
-
-#define REGWRITE_SOCK(spidev, socknum, field, val) do { \
- ASSERT_SAMETYPE(val, (struct w5500_block_sock_reg){}.field); \
- typeof(val) lval = val; \
- w5500_spiframe_write(spidev, \
- offsetof(struct w5500_block_sock_reg, field), \
- CTL_BLOCK_SOCK(socknum, REG), \
- &lval, \
- sizeof(lval)); \
- } while (0)
-
-#define REGREAD_SOCK(spidev, socknum, field, typ) ({ \
- typ val; \
- ASSERT_SAMETYPE(val, (struct w5500_block_sock_reg){}.field); \
- w5500_spiframe_read(spidev, \
- offsetof(struct w5500_block_sock_reg, field), \
- CTL_BLOCK_SOCK(socknum, REG), \
- &(val), \
- sizeof(val)); \
- val; \
- })
+ 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() *********************************************************************/
@@ -412,17 +167,13 @@ static struct net_conn_vtable w5500_conn_vtable = {
.close = w5500_close,
};
-/*
- 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;
- }
-*/
-
-static void w5500_intrhandler(uint UNUSED(gpio), uint32_t UNUSED(event_mask)) {
- /* TODO */
+
+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,
@@ -435,6 +186,7 @@ void _w5500_init(struct w5500 *chip,
*chip = (struct w5500){
/* const-after-init */
.spidev = spi,
+ .pin_intr = pin_intr,
.pin_reset = pin_reset,
/* mutable */
.next_local_port = CONFIG_W5500_LOCAL_PORT_MIN,
@@ -459,25 +211,65 @@ void _w5500_init(struct w5500 *chip,
/* 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_reset(chip, addr);
+ w5500_hard_reset(chip, addr);
+
+ /* 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 ***************************************************************/
-void w5500_reset(struct w5500 *chip, struct net_eth_addr addr) {
+static inline void w5500_post_reset(struct w5500 *chip, struct net_eth_addr addr) {
+ w5500ll_write_common_reg(chip->spidev, eth_addr, addr);
+
+ /* 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).
+ *
+ * The hysteresis is (intlevel+1)*4/(150MHz) (if intlevel is
+ * non-zero), or (intlevel+1)*26.7ns; so even the shortest-possible
+ * hysteresis much larger than necessary for us. */
+ w5500ll_write_common_reg(chip->spidev, intlevel, encode_u16be(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);
+}
+
+void w5500_hard_reset(struct w5500 *chip, struct net_eth_addr addr) {
/* 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 */
- REGWRITE_COMMON(chip->spidev, eth_addr, addr);
+ w5500_post_reset(chip, addr);
+}
+
+void w5500_soft_reset(struct w5500 *chip, struct net_eth_addr addr) {
+ 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, addr);
}
void w5500_netcfg(struct w5500 *chip, struct w5500_netcfg cfg) {
- REGWRITE_COMMON(chip->spidev, ip_gateway_addr, cfg.gateway_addr);
- REGWRITE_COMMON(chip->spidev, ip_subnet_mask, cfg.subnet_mask);
- REGWRITE_COMMON(chip->spidev, ip_addr, cfg.addr);
+ 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_listener *w5500_listen(struct w5500 *chip, uint8_t socknum,
@@ -494,66 +286,42 @@ implements_net_listener *w5500_listen(struct w5500 *chip, uint8_t socknum,
/* listener methods ***********************************************************/
-static struct w5500 *w5500_listener_chip(struct _w5500_listener *listener) {
- assert(listener);
- assert(listener->socknum < 8);
-
- struct _w5500_listener *sock0 = &listener[-listener->socknum];
- assert(sock0);
- struct w5500 *chip =
- ((void *)sock0) - offsetof(struct w5500, listeners);
- assert(chip);
- return chip;
-}
-
-#define ASSERT_LISTENER() \
- struct _w5500_listener *self = \
- VCALL_SELF(struct _w5500_listener, \
+#define ASSERT_LISTENER() \
+ struct _w5500_listener *self = \
+ VCALL_SELF(struct _w5500_listener, \
implements_net_listener, _self); \
- struct w5500 *chip = w5500_listener_chip(self); \
+ struct w5500 *chip = w5500_sock_chip(self); \
uint8_t socknum = self->socknum;
-static void w5500_cmd_close(struct _w5500_listener *listener) {
- struct w5500 *chip = w5500_listener_chip(listener);
- uint8_t socknum = listener->socknum;
-
- REGWRITE_SOCK(chip->spidev, socknum, command, CMD_CLOSE);
- while (REGREAD_SOCK(chip->spidev, socknum, command, uint8_t) != 0x00)
- cr_yield();
- REGWRITE_SOCK(chip->spidev, socknum, interrupt, (uint8_t)0xff);
- while (REGREAD_SOCK(chip->spidev, socknum, state, uint8_t) != STATE_CLOSED)
- cr_yield();
-}
-
static implements_net_conn *w5500_accept(implements_net_listener *_self) {
ASSERT_LISTENER();
+ restart:
/* Mimics socket.c:socket(). */
- w5500_cmd_close(self);
- REGWRITE_SOCK(chip->spidev, socknum, mode, SOCKMODE_TCP);
- struct uint16_be port = {{self->port>>8, self->port}};
- REGWRITE_SOCK(chip->spidev, socknum, local_port, port);
- REGWRITE_SOCK(chip->spidev, socknum, command, CMD_OPEN);
- while (REGREAD_SOCK(chip->spidev, socknum, command, uint8_t) != 0x00)
- cr_yield();
- while (REGREAD_SOCK(chip->spidev, socknum, state, uint8_t) != STATE_TCP_INIT)
+ w5500_sock_cmd_close(self);
+ w5500ll_write_sock_reg(chip->spidev, socknum, mode, SOCKMODE_TCP);
+ w5500ll_write_sock_reg(chip->spidev, socknum, local_port, encode_u16be(self->port));
+ w5500_sock_cmd(self, CMD_OPEN);
+ while (w5500ll_read_sock_reg(chip->spidev, socknum, state) != STATE_TCP_INIT)
cr_yield();
/* Mimics socket.c:listen(). */
- REGWRITE_SOCK(chip->spidev, socknum, command, CMD_LISTEN);
- while (REGREAD_SOCK(chip->spidev, socknum, command, uint8_t) != 0x00)
- cr_yield();
+ w5500_sock_cmd(self, CMD_LISTEN);
for (;;) {
- uint8_t state = REGREAD_SOCK(chip->spidev, socknum, state, uint8_t);
+ uint8_t state = w5500ll_read_sock_reg(chip->spidev, socknum, state);
switch (state) {
case STATE_TCP_LISTEN:
case STATE_TCP_SYNRECV:
- cr_yield();
+ cr_sema_wait(&self->listen_sema);
break;
- default:
+ 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;
}
}
}
@@ -568,11 +336,11 @@ static struct _w5500_listener *w5500_conn_listener(struct _w5500_conn *conn) {
return list;
}
-#define ASSERT_CONN() \
- struct _w5500_conn *self = \
+#define ASSERT_CONN() \
+ struct _w5500_conn *self = \
VCALL_SELF(struct _w5500_conn, implements_net_conn, _self); \
- struct _w5500_listener *listener = w5500_conn_listener(self); \
- struct w5500 *chip = w5500_listener_chip(listener); \
+ struct _w5500_listener *listener = w5500_conn_listener(self); \
+ struct w5500 *chip = w5500_sock_chip(listener); \
uint8_t socknum = listener->socknum;
static ssize_t w5500_write(implements_net_conn *_self, void *buf, size_t count) {
@@ -580,10 +348,50 @@ static ssize_t w5500_write(implements_net_conn *_self, void *buf, size_t count)
assert(buf);
assert(count);
- uint8_t state = REGREAD_SOCK(chip->spidev, socknum, state, uint8_t);
- if (state != STATE_TCP_ESTABLISHED && state != STATE_TCP_CLOSE_WAIT)
- return -1;
+ /* 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 = decode_u16be(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 = decode_u16be(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, encode_u16be(ptr+freesize));
+ /* Submit the queue. */
+ w5500_sock_cmd(listener, CMD_SEND);
+ done += freesize;
+ }
+ return done;
}
static ssize_t w5500_read(implements_net_conn *_self, void *buf, size_t count) {
@@ -591,7 +399,35 @@ static ssize_t w5500_read(implements_net_conn *_self, void *buf, size_t count) {
assert(buf);
assert(count);
- // TODO
+ 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 = decode_u16be(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 = decode_u16be(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, encode_u16be(ptr+avail));
+
+ w5500_sock_cmd(listener, CMD_RECV);
+ done += avail;
+ }
+ return done;
}
static int w5500_close(implements_net_conn *_self, bool rd, bool wr) {
@@ -601,17 +437,15 @@ static int w5500_close(implements_net_conn *_self, bool rd, bool wr) {
self->read_open = false;
if (wr && self->write_open) {
- REGWRITE_SOCK(chip->spidev, socknum, command, CMD_DISCON);
- while (REGREAD_SOCK(chip->spidev, socknum, command, uint8_t) != 0x00)
- cr_yield();
+ w5500_sock_cmd(listener, CMD_DISCON);
while (self->write_open) {
- uint8_t state = REGREAD_SOCK(chip->spidev, socknum, state, uint8_t);
+ 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_cmd_close(listener);
+ w5500_sock_cmd_close(listener);
break;
case STATE_CLOSED:
self->write_open = false;
diff --git a/cmd/sbc_harness/hw/w5500.h b/cmd/sbc_harness/hw/w5500.h
index 4f02152..d0c8d48 100644
--- a/cmd/sbc_harness/hw/w5500.h
+++ b/cmd/sbc_harness/hw/w5500.h
@@ -7,6 +7,8 @@
#ifndef _HW_W5500_H_
#define _HW_W5500_H_
+#include <libcr_ipc/sema.h>
+#include <libcr_ipc/mutex.h>
#include <libmisc/net.h>
#include "hw/spi.h"
@@ -29,22 +31,26 @@ struct _w5500_listener {
/* mutable */
uint16_t port;
+ cr_mutex_t cmd_mu;
+ cr_sema_t listen_sema, read_sema;
};
struct w5500 {
/* const-after-init */
struct spi *spidev;
+ uint pin_intr;
uint pin_reset;
/* mutable */
uint16_t next_local_port;
struct _w5500_listener listeners[8];
+ cr_sema_t intr;
};
/**
* Initialize a WIZnet W5500 Ethernet-and-TCP/IP-offload chip.
*
- * The W5500 has 3 channels of communication with the MCU:
+ * The W5500 has 3 lines of communication with the MCU:
*
* - An SPI-based RPC protocol:
* + mode: mode 0 or mode 3
@@ -67,7 +73,12 @@ void _w5500_init(struct w5500 *self,
/**
* TODO.
*/
-void w5500_reset(struct w5500 *self, struct net_eth_addr addr);
+void w5500_hard_reset(struct w5500 *self, struct net_eth_addr addr);
+
+/**
+ * TODO.
+ */
+void w5500_soft_reset(struct w5500 *self, struct net_eth_addr addr);
struct w5500_netcfg {
struct net_ip4_addr gateway_addr;
diff --git a/cmd/sbc_harness/hw/w5500_ll.h b/cmd/sbc_harness/hw/w5500_ll.h
new file mode 100644
index 0000000..4a08a6e
--- /dev/null
+++ b/cmd/sbc_harness/hw/w5500_ll.h
@@ -0,0 +1,374 @@
+/* hw/w5500_ll.h - Low-level W5500 header-only library
+ *
+ * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com>
+ * SPDX-Licence-Identifier: AGPL-3.0-or-later
+ */
+
+#ifndef _HW_W5500_LL_H_
+#define _HW_W5500_LL_H_
+
+#include <assert.h> /* for assert(), static_assert() */
+#include <stdint.h> /* for uint{n}_t */
+#include <string.h> /* for memcmp() */
+
+#include <libmisc/net.h> /* for struct net_eth_addr, struct net_ip4_addr */
+#include <libmisc/vcall.h> /* for VCALL() */
+#include "hw/spi.h" /* for implements_spi */
+
+/* uint16 utilities. *********************************************************/
+
+struct uint16_be {
+ uint8_t octets[2];
+};
+
+static inline struct uint16_be encode_u16be(uint16_t x) {
+ struct uint16_be ret;
+ ret.octets[0] = (uint8_t)(x>>8);
+ ret.octets[1] = (uint8_t)x;
+ return ret;
+}
+
+static inline uint16_t decode_u16be(struct uint16_be x) {
+ return ((uint16_t)x.octets[0])<<8 | x.octets[1];
+}
+
+/* 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 */
+
+ struct uint16_be 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 */
+ struct uint16_be 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 */
+ struct uint16_be ppp_sess_id; /* PSID0 ... PSID1 */
+ struct uint16_be ppp_max_seg_size; /* PMRU0 ... PMRU1 */
+
+ struct net_ip4_addr unreachable_ip_addr; /* UIPR0 ... UIPR3 */
+ struct uint16_be 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 */
+ struct uint16_be 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 */
+ struct uint16_be remote_port; /* Sn_DPORT0 ... Sn_DPORT1 */
+
+ struct uint16_be 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 */
+ struct uint16_be tx_free_size; /* Sn_TX_FSR0, Sn_TX_FSR1 */
+ struct uint16_be tx_read_pointer; /* Sn_TX_RD0, Sn_TX_RD1 */
+ struct uint16_be tx_write_pointer; /* Sn_TX_WR0, Sn_TX_WR1 */
+ struct uint16_be rx_size; /* Sn_RX_RSR0, Sn_RX_RSR1 */
+ struct uint16_be rx_read_pointer; /* Sn_RX_RD0, Sn_RX_RD1 */
+ struct uint16_be rx_write_pointer; /* Sn_RX_WR0, Sn_RX_WR1 */
+
+ uint8_t interrupt_mask; /* Sn_IMR */
+ struct uint16_be 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 /* _HW_W5500_LL_H_ */