diff options
Diffstat (limited to 'cmd/sbc_harness/hw/w5500.c')
-rw-r--r-- | cmd/sbc_harness/hw/w5500.c | 352 |
1 files changed, 336 insertions, 16 deletions
diff --git a/cmd/sbc_harness/hw/w5500.c b/cmd/sbc_harness/hw/w5500.c index 993c2b5..b4000f4 100644 --- a/cmd/sbc_harness/hw/w5500.c +++ b/cmd/sbc_harness/hw/w5500.c @@ -1,4 +1,53 @@ +/* + * 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 <ORGANIZATION> 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 <pico/time.h> /* 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. */ @@ -56,7 +105,7 @@ void w5500_spiframe_write(struct spi *spidev, uint16_t addr, uint8_t block, void spidev->vtable->readwritev(spidev, iov, 2); } -void w5500_spiframe_read(uint16_t addr, uint8_t ctl, void *data, size_t data_len) { +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); @@ -74,10 +123,7 @@ void w5500_spiframe_read(uint16_t addr, uint8_t ctl, void *data, size_t data_len spidev->vtable->readwritev(spidev, iov, 2); } -void _w5500_init(struct w5500 *self, struct spi* spi, uint pin_intr) { - self->spidev = spi; - gpio_set_irq_enabled_with_callback(pin_intr, GPIO_IRQ_EDGE_FALL, true, cbfn); -} +/* Offsets and sizes for use with that protocol. *****************************/ struct w5500_block_common_reg { uint8_t mode; /* MR */ @@ -103,7 +149,7 @@ struct w5500_block_common_reg { uint8_t ppp_max_seg_size[2]; /* PMRU0 ... PMRU1 */ uint8_t unreachable_ip_addr[4]; /* UIPR0 ... UIPR3 */ - uint8_t unreachable_port[2]; /* UPORTR0, UPROTR1 */ + uint8_t unreachable_port[2]; /* UPORTR0, UPORTR1 */ uint8_t phy_cfg; /* PHYCFGR */ @@ -114,14 +160,14 @@ struct w5500_block_common_reg { static_assert(sizeof(struct w5500_block_common_reg) == 0x3A); struct w5500_block_sock_reg { - uint8_t mode; /* Sn_MR */ - uint8_t command; /* Sn_CR */ + 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 */ - uint8_t src_port[2]; /* Sn_PORT0, Sn_PORT1 */ - uint8_t dst_eth_addr[6]; /* Sn_DHAR0 ... SnDHAR5 */ - uint8_t dst_ip_addr[4]; /* Sn_DIPR0 ... Sn_DIP3 */ - uint8_t dst_port[2]; /* Sn_DPORT0 ... Sn_DPORT1 */ + 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]; @@ -144,8 +190,282 @@ struct w5500_block_sock_reg { }; 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. */ -struct w5500_socket { - struct spi *spidev; - uint8_t socknum; /* 0-7 */ +#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; +} |