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