/* libhw/w5500_ll.h - Low-level header library for the WIZnet W5500 chip * * Based entirely on the W5500 datasheet, v1.1.0. * https://docs.wiznet.io/img/products/w5500/W5500_ds_v110e.pdf * * Copyright (C) 2024 Luke T. Shumaker * SPDX-Licence-Identifier: AGPL-3.0-or-later */ #ifndef _LIBHW_W5500_LL_H_ #define _LIBHW_W5500_LL_H_ #include /* for assert(), static_assert() */ #include /* for uint{n}_t */ #include /* for memcmp() */ #include /* for VCALL() */ #include /* for uint16be_t */ #include /* for struct net_eth_addr, struct net_ip4_addr */ #include /* for implements_spi */ /* Low-level protocol built on SPI frames. ***********************************/ /* A u8 control byte has 3 parts: block-ID, R/W, and operating-mode. */ /* Part 1: Block ID. */ #define CTL_MASK_BLOCK 0b11111000 #define _CTL_BLOCK_RES 0b00000 #define _CTL_BLOCK_REG 0b01000 #define _CTL_BLOCK_TX 0b10000 #define _CTL_BLOCK_RX 0b11000 #define CTL_BLOCK_SOCK(n,part) (((n)<<5)|(_CTL_BLOCK_##part)) #define CTL_BLOCK_COMMON_REG CTL_BLOCK_SOCK(0,RES) /* Part 2: R/W. */ #define CTL_MASK_RW 0b100 #define CTL_R 0b000 #define CTL_W 0b100 /* Part 3: Operating mode. */ #define CTL_MASK_OM 0b11 #define CTL_OM_VDM 0b00 #define CTL_OM_FDM1 0b01 #define CTL_OM_FDM2 0b10 #define CTL_OM_FDM4 0b11 /* Even though SPI is a full-duplex protocol, the W5500's spiframe on top of it is only half-duplex. * Lame. */ static inline void w5500ll_write(implements_spi *spidev, uint16_t addr, uint8_t block, void *data, size_t data_len) { assert(spidev); assert((block & ~CTL_MASK_BLOCK) == 0); assert(data); assert(data_len); uint8_t header[3] = { (uint8_t)((addr >> 8) & 0xFF), (uint8_t)(addr & 0xFF), (block & CTL_MASK_BLOCK) | CTL_W | CTL_OM_VDM, }; struct bidi_iovec iov[] = { {.iov_read_dst = NULL, .iov_write_src = header, .iov_len = sizeof(header)}, {.iov_read_dst = NULL, .iov_write_src = data, .iov_len = data_len}, }; VCALL(spidev, readwritev, iov, 2); } static inline void w5500ll_read(implements_spi *spidev, uint16_t addr, uint8_t block, void *data, size_t data_len) { assert(spidev); assert((block & ~CTL_MASK_BLOCK) == 0); assert(data); assert(data_len); uint8_t header[3] = { (uint8_t)((addr >> 8) & 0xFF), (uint8_t)(addr & 0xFF), (block & CTL_MASK_BLOCK) | CTL_R | CTL_OM_VDM, }; struct bidi_iovec iov[] = { {.iov_read_dst = NULL, .iov_write_src = header, .iov_len = sizeof(header)}, {.iov_read_dst = data, .iov_write_src = NULL, .iov_len = data_len}, }; VCALL(spidev, readwritev, iov, 2); } /* Common chip-wide registers. ***********************************************/ struct w5500ll_block_common_reg { uint8_t mode; /* MR; bitfield, see CHIPMODE_{x} below */ struct net_ip4_addr ip_gateway_addr; /* GAR0 ... GAR3 */ struct net_ip4_addr ip_subnet_mask; /* SUBR0 ... SUBR3 */ struct net_eth_addr eth_addr; /* SHAR0 ... SHAR5 */ struct net_ip4_addr ip_addr; /* SIPR0 ... SIPR3 */ uint16be_t intlevel; /* INTLEVEL0, INTLEVEL1; if non-zero, * hysteresis between pin_intr being pulled * low (hysteresis=(intlevel+1)*4/(150MHz)) */ uint8_t chip_interrupt; /* IR; bitfield, see CHIPINTR_{x} below */ uint8_t chip_interrupt_mask; /* IMR; bitfield, see CHIPINTR_{x} below, 0=disable, 1=enable */ uint8_t sock_interrupt; /* SIR; bitfield of which sockets have their .interrupt set */ uint8_t sock_interrupt_mask; /* SIMR; bitfield of sockets, 0=disable, 1=enable */ uint16be_t retry_time; /* RTR0, RTR0; configures re-transmission period, in units of 100µs */ uint8_t retry_count; /* RCR; configures max re-transmission count */ uint8_t ppp_lcp_request_timer; /* PTIMER */ uint8_t ppp_lcp_magic_bumber; /* PMAGIC */ struct net_eth_addr ppp_dst_eth_addr; /* PHAR0 ... PHAR5 */ uint16be_t ppp_sess_id; /* PSID0 ... PSID1 */ uint16be_t ppp_max_seg_size; /* PMRU0 ... PMRU1 */ struct net_ip4_addr unreachable_ip_addr; /* UIPR0 ... UIPR3 */ uint16be_t unreachable_port; /* UPORTR0, UPORTR1 */ uint8_t phy_cfg; /* PHYCFGR */ uint8_t _reserved[10]; uint8_t chip_version; /* VERSIONR */ }; static_assert(sizeof(struct w5500ll_block_common_reg) == 0x3A); /* bitfield */ #define CHIPMODE_RST ((uint8_t)(1<<7)) /* software reset */ #define _CHIPMODE_UNUSED6 ((uint8_t)(1<<6)) #define CHIPMODE_WOL ((uint8_t)(1<<5)) /* wake-on-lan */ #define CHIPMODE_BLOCK_PING ((uint8_t)(1<<4)) #define CHIPMODE_PPP ((uint8_t)(1<<3)) #define _CHIPMODE_UNUSED2 ((uint8_t)(1<<2)) #define CHIPMODE_FORCE_ARP ((uint8_t)(1<<1)) #define _CHIPMODE_UNUSED0 ((uint8_t)(1<<0)) #define CHIPINTR_CONFLICT ((uint8_t)(1<<7)) /* ARP says remote IP is self */ #define CHIPINTR_UNREACH ((uint8_t)(1<<6)) #define CHIPINTR_PPP_CLOSE ((uint8_t)(1<<6)) #define CHIPINTR_WOL ((uint8_t)(1<<4)) /* wake-on-LAN */ #define _CHIPINTR_UNUSED3 ((uint8_t)(1<<3)) #define _CHIPINTR_UNUSED2 ((uint8_t)(1<<2)) #define _CHIPINTR_UNUSED1 ((uint8_t)(1<<1)) #define _CHIPINTR_UNUSED0 ((uint8_t)(1<<0)) #define w5500ll_write_common_reg(spidev, field, val) \ w5500ll_write_reg(spidev, \ CTL_BLOCK_COMMON_REG, \ struct w5500ll_block_common_reg, \ field, val) #define w5500ll_read_common_reg(spidev, field) \ w5500ll_read_reg(spidev, \ CTL_BLOCK_COMMON_REG, \ struct w5500ll_block_common_reg, \ field) /* Per-socket registers. *****************************************************/ struct w5500ll_block_sock_reg { uint8_t mode; /* Sn_MR; see SOCKMODE_{x} below */ uint8_t command; /* Sn_CR; see CMD_{x} below */ uint8_t interrupt; /* Sn_IR; bitfield, see SOCKINTR_{x} below */ uint8_t state; /* Sn_SR; see STATE_{x} below */ uint16be_t local_port; /* Sn_PORT0, Sn_PORT1 */ struct net_eth_addr remote_eth_addr; /* Sn_DHAR0 ... SnDHAR5 */ struct net_ip4_addr remote_ip_addr; /* Sn_DIPR0 ... Sn_DIP3 */ uint16be_t remote_port; /* Sn_DPORT0 ... Sn_DPORT1 */ uint16be_t max_seg_size; /* Sn_MSSR0, Sn_MSSR1 */ uint8_t _reserved0[1]; uint8_t ip_tos; /* Sn_TOS */ uint8_t ip_ttl; /* Sn_TTL */ uint8_t _reserved1[7]; uint8_t rx_buf_size; /* Sn_RXBUF_SIZE; in KiB, power of 2, <= 16 */ uint8_t tx_buf_size; /* Sn_TXBUF_SIZE; in KiB, power of 2, <= 16 */ uint16be_t tx_free_size; /* Sn_TX_FSR0, Sn_TX_FSR1 */ uint16be_t tx_read_pointer; /* Sn_TX_RD0, Sn_TX_RD1 */ uint16be_t tx_write_pointer; /* Sn_TX_WR0, Sn_TX_WR1 */ uint16be_t rx_size; /* Sn_RX_RSR0, Sn_RX_RSR1 */ uint16be_t rx_read_pointer; /* Sn_RX_RD0, Sn_RX_RD1 */ uint16be_t rx_write_pointer; /* Sn_RX_WR0, Sn_RX_WR1 */ uint8_t interrupt_mask; /* Sn_IMR */ uint16be_t fragment_offset; /* Sn_FRAG0, Sn_FRAG1 */ uint8_t keepalive_timer; /* Sn_KPALVTR */ }; static_assert(sizeof(struct w5500ll_block_sock_reg) == 0x30); /* low 4 bits are the main enum, high 4 bits are flags */ #define SOCKMODE_CLOSED ((uint8_t)0b0000) #define SOCKMODE_TCP ((uint8_t)0b0001) #define SOCKMODE_UDP ((uint8_t)0b0010) #define SOCKMODE_MACRAW ((uint8_t)0b0100) #define SOCKMODE_FLAG_TCP_NODELAY_ACK ((uint8_t)(1<<5)) #define SOCKMODE_FLAG_UDP_ENABLE_MULTICAST ((uint8_t)(1<<7)) #define SOCKMODE_FLAG_UDP_BLOCK_BROADCAST ((uint8_t)(1<<6)) #define SOCKMODE_FLAG_UDP_MULTICAST_DOWNGRADE ((uint8_t)(1<<5)) #define SOCKMODE_FLAG_UDP_BLOCK_UNICAST ((uint8_t)(1<<4)) #define SOCKMODE_FLAG_MACRAW_MAC_FILTERING ((uint8_t)(1<<7)) #define SOCKMODE_FLAG_MACRAW_BLOCK_BROADCAST ((uint8_t)(1<<6)) #define SOCKMODE_FLAG_MACRAW_BLOCK_MULTICAST ((uint8_t)(1<<5)) #define SOCKMODE_FLAG_MACRAW_BLOCK_V6 ((uint8_t)(1<<4)) #define CMD_OPEN ((uint8_t)0x01) #define CMD_LISTEN ((uint8_t)0x02) /* TCP-only */ #define CMD_CONNECT ((uint8_t)0x04) /* TCP-only: dial */ #define CMD_DISCON ((uint8_t)0x08) /* TCP-only: send FIN */ #define CMD_CLOSE ((uint8_t)0x10) #define CMD_SEND ((uint8_t)0x20) #define CMD_SEND_MAC ((uint8_t)0x21) /* UDP-only: send to remote_eth_addr without doing ARP on remote_ip_addr */ #define CMD_SEND_KEEP ((uint8_t)0x22) /* TCP-only: send a keepalive without any data */ #define CMD_RECV ((uint8_t)0x40) #define _SOCKINTR_SEND_UNUSED7 ((uint8_t)1<<7) #define _SOCKINTR_SEND_UNUSED6 ((uint8_t)1<<6) #define _SOCKINTR_SEND_UNUSED5 ((uint8_t)1<<5) #define SOCKINTR_SEND_OK ((uint8_t)1<<4) #define SOCKINTR_TIMEOUT ((uint8_t)1<<3) #define SOCKINTR_RECV ((uint8_t)1<<2) #define SOCKINTR_FIN ((uint8_t)1<<1) #define SOCKINTR_CONN ((uint8_t)1<<1) /* first for SYN, then when SOCKMODE_ESTABLISHED */ #define STATE_CLOSED ((uint8_t)0x00) /** * The TCP state diagram is as follows. * - Reading the W5500's "state" register does not distinguish between FIN_WAIT_1 and FIN_WAIT_2; * it just has a single FIN_WAIT. * - At any point the state can jump to "CLOSED" either by CMD_CLOSE or by a timeout. * - Writing data is valid in ESTABLISHED and CLOSE_WAIT. * - Reading data is valid in ESTABLISHED and FIN_WAIT. * * TCP state diagram, showing the flow of │ CLOSED │ ━━ role separator ┌───────┐ * SYN, FIN, and their assocaited ACKs. └────────┘ ══ state transition │ state │ * V ┈┈ packet flow └───────┘ * (CMD_OPEN) ║ * V (action/event) * ┌────────┐ * ┏━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━│ INIT │━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━┓ * ┃ server ┃ └────────┘ ┃ client ┃ * ┣━━━━━━━━━━━━━━━━┛ V ┃┃ V ┗━━━━━━━━━━━━━━━━┫ * ┃ ╔═══════════╝ ┃┃ ╚═══════════╗ ┃ * ┃ (CMD_LISTEN) ┃┃ (CMD_CONNECT) ┃ * ┃ V ┌┃┃┈┈┈┈┈┈┈┈<(send SYN) ┃ * ┃ ┌────────┐ ┊┃┃ V ┃ * ┃ │ LISTEN │ ┊┃┃ ┌─────────┐ ┃ * ┃ └────────┘ ┊┃┃ │ SYNSENT │ ┃ * ┃ V ┊┃┃ └─────────┘ ┃ * ┃ (recv SYN)<┈┈┈┈┈┈┘┃┃ V ┃ * ┃ (send SYN+ACK)>┈┈┈┈┐┃┃ ║ ┃ * ┃ V └┃┃┈┈┈┈┈┈>(recv SYN+ACK) ┃ * ┃ ┌─────────┐ ┌┃┃┈┈┈┈┈┈┈┈<(send ack) ┃ * ┃ │ SYNRECV │ ┊┃┃ ║ ┃ * ┃ └─────────┘ ┊┃┃ ║ ┃ * ┃ V V ┊┃┃ ║ ┃ * ┃ ║ (recv ACK)<┈┈┈┘┃┃ ║ ┃ * ┃ ║ ╚═════════╗ ┃┃ ╔═══════════╝ ┃ * ┃ ╚═══╗ V ┃┃ V ┃ * ┃ ║ ┌─────────────┐ ┃ * ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━║━━━━━━━│ ESTABLISHED │━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ * ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━║━━━━━━━└─────────────┘━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ * ┃ ║ V ┃┃ V ┃ * ┃ ╠═══════════╝ ┃┃ ╚═══════════╗ ┃ * ┃ (CMD_DISCON) ┃┃ ║ ┃ * ┃ Both sides sent ┌┈┈┈<(send FIN)>┈┈┈┈┈┈┐┃┃ ║ ┃ * ┃ FIN at the "same" ┊ V └┃┃┈┈┈┈┈┈┈┈>(recv FIN) ┃ * ┃ time; both are ┊ ┌────────────┐ ┌┃┃┈┈┈┈┈┈┈┈<(send ACK) ┃ * ┃ active closers ┊ │ FIN_WAIT_1 │ ┊┃┃ V ┃ * ┃ / \ ┊ └────────────┘ ┊┃┃ ┌────────────┐ ┃ * ┃ ,------' '------, ┊ V V ┊┃┃ │ CLOSE_WAIT │ ┃ * ┃ ╔════════════════╝ ║ ┊┃┃ └────────────┘ ┃ * ┃ (recv FIN)<┈┈┈┈┤ ╔══╝ ┊┃┃ V ┃ * ┃ ┌┈┈<(send ACK)>┈┈┐ ┊ ║ ┊┃┃ ║ ┃ * ┃ ┊ ║ └┈┈┈┈┈>(recv ACK)<┈┈┈┈┈┈┘┃┃ ║ ┃ * ┃ ┊ V ┊ V ┃┃ ║ ┃ * ┃ ┊ ┌─────────┐ ┊ ┌────────────┐ ┃┃ ║ ┃ * ┃ ┊ │ CLOSING │ ┊ │ FIN_WAIT_2 │ ┃┃ ║ ┃ * ┃ ┊ └─────────┘ ┊ └────────────┘ ┃┃ (CMD_DISCON) ┃ * ┃ ┊ V ┊ V ┌┃┃┈┈┈┈┈┈┈┈<(send FIN) ┃ * ┃ ┊ ║ └┈┈┈>(recv FIN)<┈┈┈┈┈┈┘┃┃ ║ ┃ * ┃ ┊ ║ ┌┈┈┈┈┈<(send ACK)>┈┈┈┈┈┈┐┃┃ V ┃ * ┃ └┈┈>(recv ACK)<┈┈┘ ╚═╗ ┊┃┃ ┌──────────┐ ┃ * ┃ ╚════════════════╗ ║ ┊┃┃ │ LAST_ACK │ ┃ * ┃ V V ┊┃┃ └──────────┘ ┃ * ┃ ┌───────────┐ ┊┃┃ V ┃ * ┃ │ TIME_WAIT │ ┊┃┃ ║ ┃ * ┃ └───────────┘ └┃┃┈┈┈┈┈┈┈┈>(recv ACK) ┃ * ┃ V ┃┃ ║ ┃ * ┣━━━━━━━━━━━━━━━━┓ (2*MSL has elapsed) ┃┃ ║ ┏━━━━━━━━━━━━━━━━┫ * ┃ active closer ┃ ╚═══════════╗ ┃┃ ╔═══════════╝ ┃ passive closer ┃ * ┗━━━━━━━━━━━━━━━━┻━━━━━━━━━━━━━━━━━━━━━━━━━V━┛┗━V━━━━━━━━━━━━━━━━━━━━━━━━━━┻━━━━━━━━━━━━━━━━┛ * ┌────────┐ * │ CLOSED │ */ #define STATE_TCP_INIT ((uint8_t)0x13) #define STATE_TCP_LISTEN ((uint8_t)0x14) /* server */ #define STATE_TCP_SYNSENT ((uint8_t)0x15) /* client; during dial */ #define STATE_TCP_SYNRECV ((uint8_t)0x16) /* server; during accept */ #define STATE_TCP_ESTABLISHED ((uint8_t)0x17) #define STATE_TCP_FIN_WAIT ((uint8_t)0x18) /* during active close */ #define STATE_TCP_CLOSING ((uint8_t)0x1a) /* during active close */ #define STATE_TCP_TIME_WAIT ((uint8_t)0x1b) /* during active close */ #define STATE_TCP_CLOSE_WAIT ((uint8_t)0x1c) /* during passive close */ #define STATE_TCP_LAST_ACK ((uint8_t)0x1d) /* during passive close */ #define STATE_UDP ((uint8_t)0x22) #define STATE_MACRAW ((uint8_t)0x42) #define w5500ll_write_sock_reg(spidev, socknum, field, val) \ w5500ll_write_reg(spidev, \ CTL_BLOCK_SOCK(socknum, REG), \ struct w5500ll_block_sock_reg, \ field, val) #define w5500ll_read_sock_reg(spidev, socknum, field) \ w5500ll_read_reg(spidev, \ CTL_BLOCK_SOCK(socknum, REG), \ struct w5500ll_block_sock_reg, \ field) /******************************************************************************/ #define w5500ll_write_reg(spidev, blockid, blocktyp, field, val) do { \ typeof((blocktyp){}.field) lval = val; \ w5500ll_write(spidev, \ offsetof(blocktyp, field), \ blockid, \ &lval, \ sizeof(lval)); \ } while (0) /* The datasheet tells us that multi-byte reads are non-atomic and * that "it is recommended that you read all 16-bits twice or more * until getting the same value". */ #define w5500ll_read_reg(spidev, blockid, blocktyp, field) ({ \ typeof((blocktyp){}.field) val; \ w5500ll_read(spidev, \ offsetof(blocktyp, field), \ blockid, \ &val, \ sizeof(val)); \ if (sizeof(val) > 1) \ for (;;) { \ typeof(val) val2; \ w5500ll_read(spidev, \ offsetof(blocktyp, field), \ blockid, \ &val2, \ sizeof(val)); \ if (memcmp(&val2, &val, sizeof(val)) == 0) \ break; \ val = val2; \ } \ val; \ }) #endif /* _LIBHW_W5500_LL_H_ */