diff options
Diffstat (limited to 'cmd/sbc_harness/hw/w5500_ll.h')
-rw-r--r-- | cmd/sbc_harness/hw/w5500_ll.h | 362 |
1 files changed, 0 insertions, 362 deletions
diff --git a/cmd/sbc_harness/hw/w5500_ll.h b/cmd/sbc_harness/hw/w5500_ll.h deleted file mode 100644 index 4cc3b11..0000000 --- a/cmd/sbc_harness/hw/w5500_ll.h +++ /dev/null @@ -1,362 +0,0 @@ -/* hw/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 _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 <libmisc/endian.h> /* for uint16be_t */ -#include "hw/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 /* _HW_W5500_LL_H_ */ |