/* * 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 * * 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 "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 /* 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 0b11111'000 #define _CTL_BLOCK_RES (0b00'000) #define _CTL_BLOCK_REG (0b01'000) #define _CTL_BLOCK_TX (0b10'000) #define _CTL_BLOCK_RX (0b11'000) #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 0b1'00 #define CTL_R 0b0'00 #define CTL_W 0b1'00 /* 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}, }; spidev->vtable->readwritev(spidev, 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}, }; spidev->vtable->readwritev(spidev, iov, 2); } /* Offsets and sizes for use with that protocol. *****************************/ struct w5500_block_common_reg { uint8_t mode; /* MR */ uint8_t ip_gateway_addr[4]; /* GAR0 ... GAR3 */ uint8_t ip_subnet_mask[4]; /* SUBR0 ... SUBR3 */ uint8_t eth_addr[6]; /* SHAR0 ... SHAR5 */ uint8_t ip_addr[4]; /* SIPR0 ... SIPR3 */ uint8_t intlevel_0; /* INTLEVEL0 */ uint8_t intlevel_1; /* INTLEVEL1 */ uint8_t interrupt; /* IR */ uint8_t interrupt_mask; /* IMR */ uint8_t sock_interrupt; /* SIR */ uint8_t sock_interrupt_mask; /* SIMR */ uint8_t retry_time_0; /* RTR0 */ uint8_t retry_time_1; /* RTR0 */ uint8_t retry_count; /* RCR */ uint8_t ppp_lcp_request_timer; /* PTIMER */ uint8_t ppp_lcp_magic_bumber; /* PMAGIC */ uint8_t ppp_dst_eth_addr[6]; /* PHAR0 ... PHAR5 */ uint8_t ppp_sess_id[2]; /* PSID0 ... PSID1 */ uint8_t ppp_max_seg_size[2]; /* PMRU0 ... PMRU1 */ uint8_t unreachable_ip_addr[4]; /* UIPR0 ... UIPR3 */ uint8_t unreachable_port[2]; /* 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); struct w5500_block_sock_reg { uint8_t mode; /* Sn_MR; see MODE_{x} below */ uint8_t command; /* Sn_CR; see CMD_{x} below */ uint8_t interrupt; /* Sn_IR */ uint8_t status; /* Sn_SR; see STATUS_{x} below */ uint8_t local_port[2]; /* Sn_PORT0, Sn_PORT1 */ uint8_t remote_eth_addr[6]; /* Sn_DHAR0 ... SnDHAR5 */ uint8_t remote_ip_addr[4]; /* Sn_DIPR0 ... Sn_DIP3 */ uint8_t remote_port[2]; /* Sn_DPORT0 ... Sn_DPORT1 */ uint8_t max_seg_size[2]; /* 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 */ uint8_t tx_free_size[2]; /* Sn_TX_FSR0, Sn_TX_FSR1 */ uint8_t tx_read_pointer[2]; /* Sn_TX_RD0, Sn_TX_RD1 */ uint8_t tx_write_pointer[2]; /* Sn_TX_WR0, Sn_TX_WR1 */ uint8_t rx_size[2]; /* Sn_RX_RSR0, Sn_RX_RSR1 */ uint8_t rx_read_pointer[2]; /* Sn_RX_RD0, Sn_RX_RD1 */ uint8_t rx_write_pointer[2]; /* Sn_RX_WR0, Sn_RX_WR1 */ uint8_t interrupt_mask; /* Sn_IMR */ uint8_t fragment_offset[2]; /* 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 MODE_CLOSED ((uint8_t)0b0000) #define MODE_TCP ((uint8_t)0b0001) #define MODE_UDP ((uint8_t)0b0010) #define MODE_MACRAW ((uint8_t)0b0100) #define MODE_FLAG_TCP_NODELAY_ACK ((uint8_t)(1<<5)) #define MODE_FLAG_UDP_ENABLE_MULTICAST ((uint8_t)(1<<7)) #define MODE_FLAG_UDP_BLOCK_BROADCAST ((uint8_t)(1<<6)) #define MODE_FLAG_UDP_MULTICAST_DOWNGRADE ((uint8_t)(1<<5)) #define MODE_FLAG_UDP_BLOCK_UNICAST ((uint8_t)(1<<4)) #define MODE_FLAG_MACRAW_MAC_FILTERING ((uint8_t)(1<<7)) #define MODE_FLAG_MACRAW_BLOCK_BROADCAST ((uint8_t)(1<<6)) #define MODE_FLAG_MACRAW_BLOCK_MULTICAST ((uint8_t)(1<<5)) #define MODE_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 STATUS_CLOSED ((uint8_t)0x00) /* The status modes starting with an underscore are expected to be * very short-lived. */ #define STATUS_TCP_INIT ((uint8_t)0x13) #define STATUS_TCP_LISTEN ((uint8_t)0x14) #define _STATUS_TCP_SYNSENT ((uint8_t)0x15) /* in the CMD_CONNECT INIT->SYNSENT->ESTABLISHED transition */ #define _STATUS_TCP_SYNRECV ((uint8_t)0x16) /* in the CMD_LISTEN LISTEN->SYNRECV->ESTABLISHED transition */ #define STATUS_TCP_ESTABLISHED ((uint8_t)0x17) /* The "graceful shutdown" state diagram (with the exception that * reading the W5500's "status" register does not distinguish between * FIN_WAIT_1 and FIN_WAIT_2; it just has a single FIN_WAIT): * * ║ * V * ┌─────────────┐ * "active close" │ ESTABLISHED │ "passive close" * (we send FIN first) └─────────────┘ (we send FIN second) * V V * ╔═══════════╝ ╚═════════╗ * (CMD_DISCON) ║ * ┌┈┈┈<(send FIN)>┈┈┈┈┈┈┈┈┐ ║ * ┊ ║ └┈┈┈┈┈┈┈>(recv FIN) * ┊ V ┌┈┈┈┈┈┈┈<(send ACK) * "double active" ┊ ┌────────────┐ ┊ ║ * (both sides think ┊ │ FIN_WAIT_1 │ ┊ V * they sent FIN first) ┊ └────────────┘ ┊ ┌────────────┐ * ┊ V V ┊ │ CLOSE_WAIT │ * ╔═════════════════╝ ╔══╝ ┊ └────────────┘ * (recv FIN)<┈┈┈┈┈┤ ║ ┊ V * ┌┈┈<(send ACK)>┈┈┈┐ ┊ ║ ┊ ║ * ┊ ║ └┈┈┈┈┈>(recv ACK)<┈┈┈┈┈┈┈┈┘ ║ * ┊ ║ ┊ ║ ║ * ┊ V ┊ V ║ * ┊ ┌─────────┐ ┊ ┌────────────┐ ║ * ┊ │ CLOSING │ ┊ │ FIN_WAIT_2 │ ║ * ┊ └─────────┘ ┊ └────────────┘ ║ * ┊ V ┊ V (CMD_DISCON) * ┊ ║ ┊ ║ ┌┈┈┈┈┈┈┈<(send FIN) * ┊ ║ └┈┈┈>(recv FIN)<┈┈┈┈┈┈┈┈┘ ║ * ┊ ║ ┌┈┈┈┈┈<(send ACK)>┈┈┈┈┈┈┈┈┐ V * └┈┈>(recv ACK)<┈┈┈┘ ║ ┊ ┌──────────┐ * ╚═════════════════╗ ╚═╗ ┊ │ LAST_ACK │ * V V ┊ └──────────┘ * ┌───────────┐ ┊ V * │ TIME_WAIT │ ┊ ║ * └───────────┘ └┈┈┈┈┈┈┈>(recv ACK) * V ║ * ║ ║ * (2*MSL has elapsed) ║ * ╚═══════════╗ ╔══════════╝ * V V * ┌────────┐ * │ CLOSED │ * └────────┘ * * One can still write in CLOSE_WAIT, and can still read in FIN_WAIT. */ #define _STATUS_TCP_FIN_WAIT ((uint8_t)0x18) /* during active close */ #define _STATUS_TCP_CLOSING ((uint8_t)0x1a) /* during active close */ #define _STATUS_TCP_TIME_WAIT ((uint8_t)0x1b) /* during active close */ #define STATUS_TCP_CLOSE_WAIT ((uint8_t)0x1c) /* during passive close */ #define _STATUS_TCP_LAST_ACK ((uint8_t)0x1d) /* during passive close */ #define STATUS_UDP ((uint8_t)0x22) #define STATUS_MACRAW ((uint8_t)0x42) #define REGWRITE_COMMON(spidev, field, val) do { \ assert(sizeof(val) == sizeof((struct w5500_block_common_reg).field)); \ w5500_spiframe_write(spidev, \ offsetof(struct w5500_block_common_reg, field), \ CTL_BLOCK_COMMON_REG, \ &(val), \ sizeof(struct w5500_block_common_reg, field)); \ } while (0) #define REGREAD_COMMON(spidev, field, typ) ({ \ typ val; \ assert(sizeof(val) == sizeof((struct w5500_block_sock_reg).field)); \ w5500_spiframe_read(spidev, \ offsetof(struct w5500_block_sock_reg, field), \ CTL_BLOCK_COMMON, \ &(val), \ sizeof(struct w5500_block_sock_reg, field)); \ val; \ }) #define REGWRITE_SOCK(spidev, socknum, field, val) do { \ assert(sizeof(val) == sizeof((struct w5500_block_sock_reg).field)); \ w5500_spiframe_write(spidev, \ offsetof(struct w5500_block_sock_reg, field), \ CTL_BLOCK_SOCK(socknum, REG), \ &(val), \ sizeof(struct w5500_block_sock_reg, field)); \ } while (0) #define REGREAD_SOCK(spidev, socknum, field, typ) ({ \ typ val; \ assert(sizeof(val) == sizeof((struct w5500_block_sock_reg).field)); \ w5500_spiframe_read(spidev, \ offsetof(struct w5500_block_sock_reg, field), \ CTL_BLOCK_SOCK(socknum, REG), \ &(val), \ sizeof(struct w5500_block_sock_reg, field)); \ val; \ }) /* init() *********************************************************************/ static struct libnet_conn *w5500_accept(struct libnet_listener *_listener); static ssize_t w5500_read(struct libnet_conn *conn, void *buf, size_t count); static ssize_t w5500_write(struct libnet_conn *conn, void *buf, size_t count); static int w5500_close(struct libnet_conn *conn, bool rd, bool wr); static struct libnet_listener_vtable w5500_listener_vtable = { .accept = w5500_accept, }; static struct libnet_conn_vtable w5500_conn_vtable = { .read = w5500_read, .write = w5500_write, .close = w5500_close, }; 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_init(struct w5500 *chip, struct spi* spi, uint pin_intr, uint pin_reset, struct eth_addr addr) { chip->spidev = spi; chip->pin_reset = pin_reset; for (uint8_t i = 0; i < 8; i++) { chip->_listeners[i].vtable = &w5500_listener_vtable; chip->_listeners[i].chip = chip; chip->_listeners[i].socknum = i; chip->_listeners[i].active_conn.vtable = &w5500_conn_vtable; chip->_listeners[i].active_conn.parent_listener = &chip->_listeners[i]; } chip->_next_local_port = CONFIG_W5500_LOCAL_PORT_MIN; gpio_set_irq_enabled_with_callback(pin_intr, GPIO_IRQ_EDGE_FALL, true, cbfn); gpio_set_dir(chip->pin_reset, GPIO_OUT); w5500_reset(chip); REGWRITE_COMMON(spi, eth_addr, addr); } void w5500_reset(struct w5500 *chip) { /* 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 */ } 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); } /* listen() *******************************************************************/ struct libnet_listener *w5500_listen(struct w5500 *chip, uint8_t socknum, uint16_t port) { assert(chip); assert(socknum < 8); assert(port); w5500_close(&chip->_listeners[socknum].active_socket, true, true); REGWRITE_SOCK(chip->spidev, socknum, mode, MODE_TCP); REGWRITE_SOCK(chip->spidev, socknum, local_port, (uint8_t[2]){port>>8, port}); REGWRITE_SOCK(chip->spidev, socknum, command, CMD_OPEN); while (REGREAD_SOCK(chip->spidev, socknum, command, uint8_t) != 0x00) cr_yield(); while (REGREAD_SOCK(spidev, socknum, status, uint8_t) != STATUS_TCP_INIT) cr_yield(); return (struct libnet_listener *)&chip->_listeners[socknum]; } /* accept() *******************************************************************/ #define ASSERT_LISTENER() \ struct w5500_listener *self = (struct w5500_listener *)_self; \ assert(self); \ \ struct w5500 *chip = self->chip; \ uint8_t socknum = self->socknum; \ assert(chip); \ assert(socknum < 8) \ static struct libnet_conn *w5500_accept(struct libnet_listener *_self) { ASSERT_LISTENER(); REGWRITE_SOCK(chip->spidev, socknum, command, CMD_LISTEN); while (REGREAD_SOCK(chip->spidev, socknum, command, uint8_t) != 0x00) cr_yield(); for (;;) { uint8_t mode = REGREAD_SOCK(spidev, socknum, status, uint8_t); switch (mode) { case STATUS_TCP_LISTEN: case _STATUS_TCP_SYNRECV: cr_yield(); break; default: return &self->active_conn; } } } /* write() ********************************************************************/ #define ASSERT_CONN() \ struct w5500_conn *self = (struct w5500_conn *)_self; \ assert(self); \ \ struct w5500 *chip = self->parent_listener->chip; \ uint8_t socknum = self->parent_listener->socknum; \ assert(chip); \ assert(socknum < 8) static ssize_t w5500_write(struct libnet_conn *_self, void *buf, size_t count) { ASSERT_CONN(); assert(buf); assert(count); } static int w5500_close(struct libnet_conn *_self, bool rd, bool wr) { ASSERT_CONN(); 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, status, uint8_t) != STATUS_CLOSED) cr_yield(); return 0; }