/* libhw/w5500.c - implementation for the WIZnet W5500 chip * * Copyright (C) 2024 Luke T. Shumaker * SPDX-License-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/Ethernet/W5500/w5500.c * https://github.com/Wiznet/ioLibrary_Driver/blob/b981401e7f3d07015619adf44c13998e13e777f9/Ethernet/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. * * SPDX-License-Identifier: BSD-3-Clause * * ----------------------------------------------------------------------------- * https://github.com/Wiznet/ioLibrary_Driver/blob/b981401e7f3d07015619adf44c13998e13e777f9/license.txt * * Copyright (c) 2014 WIZnet Co.,Ltd. * Copyright (c) WIZnet ioLibrary Project. * All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. * * SPDX-License-Identifier: MIT */ #include /* for sleep_ms() */ #include /* pico-sdk:hardware_gpio */ #include /* for cr_yield() */ #include /* for VCALL_SELF() */ #include #include "w5500_ll.h" /* Config *********************************************************************/ #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 #ifndef CONFIG_W5500_NUM #error config.h must define CONFIG_W5500_NUM #endif /* C language *****************************************************************/ #define UNUSED(name) #define ARRAY_LEN(ary) (sizeof(ary)/sizeof((ary)[0])) /* mid-level utilities ********************************************************/ #if 0 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; } #endif static COROUTINE w5500_irq_cr(void *_chip) { struct w5500 *chip = _chip; cr_begin(); for (;;) { cr_sema_wait(&chip->intr); if (w5500ll_read_common_reg(chip->spidev, chip_interrupt)) w5500ll_write_common_reg(chip->spidev, chip_interrupt, 0xFF); uint8_t sockmask = w5500ll_read_common_reg(chip->spidev, sock_interrupt); for (uint8_t socknum = 0; socknum < 8; socknum++) { if (!(sockmask & (1<listeners[socknum]; uint8_t sockintr = w5500ll_read_sock_reg(chip->spidev, socknum, interrupt); /* SOCKINTR_SEND_OK is useless. */ uint8_t listen_bits = sockintr & (SOCKINTR_TIMEOUT|SOCKINTR_CONN), read_bits = sockintr & (SOCKINTR_TIMEOUT|SOCKINTR_RECV|SOCKINTR_FIN); if (listen_bits) cr_sema_signal(&listener->listen_sema); if (read_bits) cr_sema_signal(&listener->read_sema); w5500ll_write_sock_reg(chip->spidev, socknum, interrupt, sockintr); } } cr_end(); } /* init() *********************************************************************/ static implements_net_stream_conn *w5500_tcp_accept(implements_net_stream_listener *listener); static ssize_t w5500_tcp_read(implements_net_stream_conn *conn, void *buf, size_t count); static ssize_t w5500_tcp_write(implements_net_stream_conn *conn, void *buf, size_t count); static int w5500_tcp_close(implements_net_stream_conn *conn, bool rd, bool wr); static struct net_stream_listener_vtable w5500_tcp_listener_vtable = { .accept = w5500_tcp_accept, }; static struct net_stream_conn_vtable w5500_tcp_conn_vtable = { .read = w5500_tcp_read, .write = w5500_tcp_write, .close = w5500_tcp_close, }; static struct w5500 *w5500_chips[CONFIG_W5500_NUM] = {0}; static void w5500_intrhandler(uint gpio, uint32_t UNUSED(event_mask)) { for (size_t i = 0; i < ARRAY_LEN(w5500_chips); i++) if (w5500_chips[i] && w5500_chips[i]->pin_intr == gpio) cr_sema_signal_from_intrhandler(&w5500_chips[i]->intr); } void _w5500_init(struct w5500 *chip, implements_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_intr = pin_intr, .pin_reset = pin_reset, .hwaddr = addr, /* mutable */ .next_local_port = CONFIG_W5500_LOCAL_PORT_MIN, }; for (uint8_t i = 0; i < 8; i++) { chip->listeners[i] = (struct _w5500_tcp_listener){ /* const-after-init */ .vtable = &w5500_tcp_listener_vtable, .socknum = i, .active_conn = { /* const-after-init */ .vtable = &w5500_tcp_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_hard_reset(chip); /* Finally, wire in the interrupt handler. */ cr_disable_interrupts(); for (size_t i = 0; i < ARRAY_LEN(w5500_chips); i++) { if (w5500_chips[i] == NULL) { w5500_chips[i] = chip; break; } } cr_enable_interrupts(); coroutine_add(w5500_irq_cr, chip); } /* chip methods ***************************************************************/ static inline void w5500_post_reset(struct w5500 *chip) { /* The W5500 does not have a built-in MAC address, we must * provide one. */ w5500ll_write_common_reg(chip->spidev, eth_addr, chip->hwaddr); /* The RP2040 needs a 1/sys_clk hysteresis between interrupts * for us to notice them. At the maximum-rated clock-rate of * 133MHz, that means 7.5ns (but the sbc-harness overclocks * the RP2040, so we could get away with even shorter). * * If intlevel is non-zero, then the hysteresis is * (intlevel+1)*4/(150MHz), or (intlevel+1)*26.7ns; so even * the shortest-possible hysteresis much larger than necessary * for us. */ w5500ll_write_common_reg(chip->spidev, intlevel, uint16be_marshal(1)); /* This implementation does not care about any of the * chip-level interrupts. */ w5500ll_write_common_reg(chip->spidev, chip_interrupt_mask, 0); /* This implementation cares about interrupts for each * socket. */ w5500ll_write_common_reg(chip->spidev, sock_interrupt_mask, 0xFF); /* Configure retry/timeout. * * timeout_arp = 0.1ms * retry_time * (retry_count+1) * * retry_count * timeout_tcp = 0.1ms * retry_time * Σ 2^min(n, floor(1+log_2(65535/retry_time))) * n=0 * * For retry_time=2000, retry_count=3, this means * * timeout_arp = 0.8s * timeout_tcp = 3.0s */ w5500ll_write_common_reg(chip->spidev, retry_time, uint16be_marshal(2000)); w5500ll_write_common_reg(chip->spidev, retry_count, 3); } void w5500_hard_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 */ w5500_post_reset(chip); } void w5500_soft_reset(struct w5500 *chip) { w5500ll_write_common_reg(chip->spidev, mode, CHIPMODE_RST); while (w5500ll_read_common_reg(chip->spidev, mode) & CHIPMODE_RST) cr_yield(); w5500_post_reset(chip); } void w5500_netcfg(struct w5500 *chip, struct w5500_netcfg cfg) { w5500ll_write_common_reg(chip->spidev, ip_gateway_addr, cfg.gateway_addr); w5500ll_write_common_reg(chip->spidev, ip_subnet_mask, cfg.subnet_mask); w5500ll_write_common_reg(chip->spidev, ip_addr, cfg.addr); } implements_net_stream_listener *w5500_tcp_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]; } /* implements_net_packet_conn *w5500_udp_conn(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]; } */ /* tcp_listener methods *******************************************************/ static struct w5500 *w5500_tcp_listener_chip(struct _w5500_tcp_listener *listener) { assert(listener); assert(listener->socknum < 8); struct _w5500_tcp_listener *sock0 = &listener[-listener->socknum]; assert(sock0); struct w5500 *chip = ((void *)sock0) - offsetof(struct w5500, listeners); assert(chip); return chip; } static inline void w5500_tcp_listener_cmd(struct _w5500_tcp_listener *listener, uint8_t cmd) { assert(listener); struct w5500 *chip = w5500_tcp_listener_chip(listener); uint8_t socknum = listener->socknum; cr_mutex_lock(&listener->cmd_mu); w5500ll_write_sock_reg(chip->spidev, socknum, command, cmd); while (w5500ll_read_sock_reg(chip->spidev, socknum, command) != 0x00) cr_yield(); cr_mutex_unlock(&listener->cmd_mu); } static inline void w5500_tcp_listener_cmd_close(struct _w5500_tcp_listener *listener) { assert(listener); struct w5500 *chip = w5500_tcp_listener_chip(listener); uint8_t socknum = listener->socknum; w5500_tcp_listener_cmd(listener, CMD_CLOSE); w5500ll_write_sock_reg(chip->spidev, socknum, interrupt, 0xFF); while (w5500ll_read_sock_reg(chip->spidev, socknum, state) != STATE_CLOSED) cr_yield(); listener->active_conn.read_open = listener->active_conn.write_open = false; } #define ASSERT_LISTENER() \ struct _w5500_tcp_listener *self = \ VCALL_SELF(struct _w5500_tcp_listener, \ implements_net_stream_listener, _self); \ struct w5500 *chip = w5500_tcp_listener_chip(self); \ uint8_t socknum = self->socknum; static implements_net_stream_conn *w5500_tcp_accept(implements_net_stream_listener *_self) { ASSERT_LISTENER(); restart: /* Mimics socket.c:socket(). */ w5500_tcp_listener_cmd_close(self); w5500ll_write_sock_reg(chip->spidev, socknum, mode, SOCKMODE_TCP); w5500ll_write_sock_reg(chip->spidev, socknum, local_port, uint16be_marshal(self->port)); w5500_tcp_listener_cmd(self, CMD_OPEN); while (w5500ll_read_sock_reg(chip->spidev, socknum, state) != STATE_TCP_INIT) cr_yield(); /* Mimics socket.c:listen(). */ w5500_tcp_listener_cmd(self, CMD_LISTEN); for (;;) { uint8_t state = w5500ll_read_sock_reg(chip->spidev, socknum, state); switch (state) { case STATE_TCP_LISTEN: case STATE_TCP_SYNRECV: cr_sema_wait(&self->listen_sema); break; case STATE_TCP_ESTABLISHED: self->active_conn.read_open = true; /* fall-through */ case STATE_TCP_CLOSE_WAIT: self->active_conn.write_open = true; return &self->active_conn; default: goto restart; } } } /* tcp_conn methods ***********************************************************/ static struct _w5500_tcp_listener *w5500_tcp_conn_listener(struct _w5500_tcp_conn *conn) { assert(conn); struct _w5500_tcp_listener *list = ((void *)conn) - offsetof(struct _w5500_tcp_listener, active_conn); return list; } #define ASSERT_CONN() \ struct _w5500_tcp_conn *self = \ VCALL_SELF(struct _w5500_tcp_conn, implements_net_stream_conn, _self); \ struct _w5500_tcp_listener *listener = w5500_tcp_conn_listener(self); \ struct w5500 *chip = w5500_tcp_listener_chip(listener); \ uint8_t socknum = listener->socknum; static ssize_t w5500_tcp_write(implements_net_stream_conn *_self, void *buf, size_t count) { ASSERT_CONN(); assert(buf); assert(count); /* What we really want is to pause until we receive an ACK for * some data we just queued, so that we can line up some new * data to keep the buffer full. But that's not what * SEND_FINIAIUI, the SEND_FINISHED interrupt doesn't fire * until we receive the *last* ACK for the data, when the * buffer is entirely empty. * * Which means we basically have to busy-poll for space in the * buffer becoming available. * * We'll add more data to the buffer whenever there is * `min_free_space` in the buffer (or the rest of the data * fits in the buffer). * * This `min_free_space` can probably stand to be tuned; must * be >0, <=bufsize. `1500-58` is the 100BaseT MTU minus the * Ethernet+IP+TCP overhead. */ uint16_t bufsize = ((uint16_t)w5500ll_read_sock_reg(chip->spidev, socknum, tx_buf_size))*1024; uint16_t min_free_space = MIN(1500-58, bufsize/4); size_t done = 0; while (done < count) { if (!self->write_open) return -1; uint8_t state = w5500ll_read_sock_reg(chip->spidev, socknum, state); if (state != STATE_TCP_ESTABLISHED && state != STATE_TCP_CLOSE_WAIT) return -1; uint16_t freesize = uint16be_unmarshal(w5500ll_read_sock_reg(chip->spidev, socknum, tx_free_size)); if (freesize < count-done && freesize < min_free_space) { /* Wait for more buffer space. */ cr_yield(); continue; } /* Queue data to be sent. */ uint16_t ptr = uint16be_unmarshal(w5500ll_read_sock_reg(chip->spidev, socknum, tx_write_pointer)); w5500ll_write(chip->spidev, ptr, CTL_BLOCK_SOCK(socknum, TX), &((char *)buf)[done], freesize); w5500ll_write_sock_reg(chip->spidev, socknum, tx_write_pointer, uint16be_marshal(ptr+freesize)); /* Submit the queue. */ w5500_tcp_listener_cmd(listener, CMD_SEND); done += freesize; } return done; } static ssize_t w5500_tcp_read(implements_net_stream_conn *_self, void *buf, size_t count) { ASSERT_CONN(); assert(buf); assert(count); size_t done = 0; while (!done) { if (!self->read_open) return -1; uint8_t state = w5500ll_read_sock_reg(chip->spidev, socknum, state); switch (state) { case STATE_TCP_CLOSE_WAIT: return 0; /* EOF */ case STATE_TCP_ESTABLISHED: case STATE_TCP_FIN_WAIT: break; /* OK */ default: return -1; } uint16_t avail = uint16be_unmarshal(w5500ll_read_sock_reg(chip->spidev, socknum, rx_size)); if (!avail) { cr_sema_wait(&listener->read_sema); continue; } if ((size_t)avail > count) avail = count; uint16_t ptr = uint16be_unmarshal(w5500ll_read_sock_reg(chip->spidev, socknum, rx_read_pointer)); w5500ll_read(chip->spidev, ptr, CTL_BLOCK_SOCK(socknum, RX), &((char *)buf)[done], avail); w5500ll_write_sock_reg(chip->spidev, socknum, rx_read_pointer, uint16be_marshal(ptr+avail)); w5500_tcp_listener_cmd(listener, CMD_RECV); done += avail; } return done; } static int w5500_tcp_close(implements_net_stream_conn *_self, bool rd, bool wr) { ASSERT_CONN(); if (rd) self->read_open = false; if (wr && self->write_open) { w5500_tcp_listener_cmd(listener, CMD_DISCON); while (self->write_open) { uint8_t state = w5500ll_read_sock_reg(chip->spidev, socknum, state); switch (state) { case STATE_TCP_FIN_WAIT: self->write_open = false; /* Can still read */ if (!self->read_open) w5500_tcp_listener_cmd_close(listener); break; case STATE_CLOSED: self->write_open = false; break; } } } return 0; } /* udp_conn methods ***********************************************************/