/* * 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 /* pico-sdk:hardware_gpio5 */ #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 /* 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}, }; 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 state; /* Sn_SR; see STATE_{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 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(sizeof(val) == sizeof(((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(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(val)); \ val; \ }) #define REGWRITE_SOCK(spidev, socknum, field, val) do { \ assert(sizeof(val) == sizeof(((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(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(val)); \ 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, TODO_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((struct libnet_conn *)&chip->_listeners[socknum].active_conn, 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(chip->spidev, socknum, state, uint8_t) != STATE_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(chip->spidev, socknum, state, uint8_t); switch (mode) { case STATE_TCP_LISTEN: case STATE_TCP_SYNRECV: cr_yield(); break; default: return (struct libnet_conn *)&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); // TODO } 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, state, uint8_t) != STATE_CLOSED) cr_yield(); return 0; }