diff options
author | Luke T. Shumaker <lukeshu@lukeshu.com> | 2024-10-19 17:36:28 -0600 |
---|---|---|
committer | Luke T. Shumaker <lukeshu@lukeshu.com> | 2024-10-19 17:36:28 -0600 |
commit | f4400fe41a5a80ea58f8881eaa263aae99636698 (patch) | |
tree | c0f0d7aa440d416241ae3595c1f8e76fa83be200 | |
parent | 221a0cb9c45c205cb07dfd6bc91d401363d3c8c1 (diff) |
finish w5500?
-rw-r--r-- | cmd/sbc_harness/config/config.h | 23 | ||||
-rw-r--r-- | cmd/sbc_harness/hw/w5500.c | 614 | ||||
-rw-r--r-- | cmd/sbc_harness/hw/w5500.h | 15 | ||||
-rw-r--r-- | cmd/sbc_harness/hw/w5500_ll.h | 374 |
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_ */ |