/* hw/w5500.c - libmisc/net.h implementation for the WIZnet W5500 chip * * Copyright (C) 2024 Luke T. Shumaker * SPDX-Licence-Identifier: AGPL-3.0-or-later * * https://github.com/Wiznet/ioLibrary_Driver/blob/b981401e7f3d07015619adf44c13998e13e777f9/Ethernet/wizchip_conf.c * https://github.com/Wiznet/ioLibrary_Driver/blob/b981401e7f3d07015619adf44c13998e13e777f9/Ethernet/W5500/w5500.h * https://github.com/Wiznet/ioLibrary_Driver/blob/b981401e7f3d07015619adf44c13998e13e777f9/socket.c * * SPDX-Licence-Identifier: BSD-3-Clause * * Copyright (c) 2013, WIZnet Co., LTD. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * Neither the name of the nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF * THE POSSIBILITY OF SUCH DAMAGE. */ #include /* for sleep_ms() */ #include /* pico-sdk:hardware_gpio5 */ #include #include #include "hw/w5500.h" #include "config.h" /* These are the default values of the Linux kernel's * net.ipv4.ip_local_port_range, so I figure they're probably good * values to use. */ #ifndef CONFIG_W5500_LOCAL_PORT_MIN # define CONFIG_W5500_LOCAL_PORT_MIN 32768 #endif #ifndef CONFIG_W5500_LOCAL_PORT_MAX # 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. */ /* 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 /* 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. */ 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); 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); } 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); 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); } /* Offsets and sizes for use with that protocol. *****************************/ struct uint16_be { uint8_t octets[2]; }; 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 */ 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 */ 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 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; \ }) /* init() *********************************************************************/ static implements_net_conn *w5500_accept(implements_net_listener *_listener); static ssize_t w5500_read(implements_net_conn *conn, void *buf, size_t count); static ssize_t w5500_write(implements_net_conn *conn, void *buf, size_t count); static int w5500_close(implements_net_conn *conn, bool rd, bool wr); static struct net_listener_vtable w5500_listener_vtable = { .accept = w5500_accept, }; static struct net_conn_vtable w5500_conn_vtable = { .read = w5500_read, .write = w5500_write, .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 */ } void _w5500_init(struct w5500 *chip, struct spi* spi, uint pin_intr, uint pin_reset, struct net_eth_addr addr) { assert(chip); assert(spi); /* Initialize the data structures. */ *chip = (struct w5500){ /* const-after-init */ .spidev = spi, .pin_reset = pin_reset, /* mutable */ .next_local_port = CONFIG_W5500_LOCAL_PORT_MIN, }; for (uint8_t i = 0; i < 8; i++) { chip->listeners[i] = (struct _w5500_listener){ /* const-after-init */ .vtable = &w5500_listener_vtable, .socknum = i, .active_conn = { /* const-after-init */ .vtable = &w5500_conn_vtable, /* mutable */ .read_open = false, .write_open = false, }, /* mutable */ .port = 0, }; } /* 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); } /* chip methods ***************************************************************/ void w5500_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); } 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); } implements_net_listener *w5500_listen(struct w5500 *chip, uint8_t socknum, uint16_t port) { assert(chip); assert(socknum < 8); assert(port); assert(chip->listeners[socknum].port == 0); chip->listeners[socknum].port = port; return &chip->listeners[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, \ implements_net_listener, _self); \ struct w5500 *chip = w5500_listener_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(); /* 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) 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(); for (;;) { uint8_t state = REGREAD_SOCK(chip->spidev, socknum, state, uint8_t); switch (state) { case STATE_TCP_LISTEN: case STATE_TCP_SYNRECV: cr_yield(); break; default: self->active_conn.read_open = true; self->active_conn.write_open = true; return &self->active_conn; } } } /* conn methods ***************************************************************/ static struct _w5500_listener *w5500_conn_listener(struct _w5500_conn *conn) { assert(conn); struct _w5500_listener *list = ((void *)conn) - offsetof(struct _w5500_listener, active_conn); return list; } #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); \ uint8_t socknum = listener->socknum; static ssize_t w5500_write(implements_net_conn *_self, void *buf, size_t count) { ASSERT_CONN(); 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; } static ssize_t w5500_read(implements_net_conn *_self, void *buf, size_t count) { ASSERT_CONN(); assert(buf); assert(count); // TODO } static int w5500_close(implements_net_conn *_self, bool rd, bool wr) { ASSERT_CONN(); if (rd) 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(); while (self->write_open) { uint8_t state = REGREAD_SOCK(chip->spidev, socknum, state, uint8_t); switch (state) { case STATE_TCP_FIN_WAIT: self->write_open = false; /* Can still read */ if (!self->read_open) w5500_cmd_close(listener); break; case STATE_CLOSED: self->write_open = false; break; } } } return 0; }