diff options
Diffstat (limited to 'cmd/sbc_harness/hw/w5500.c')
-rw-r--r-- | cmd/sbc_harness/hw/w5500.c | 614 |
1 files changed, 224 insertions, 390 deletions
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; |