diff options
Diffstat (limited to 'cmd/sbc_harness/hw/w5500.c')
-rw-r--r-- | cmd/sbc_harness/hw/w5500.c | 526 |
1 files changed, 0 insertions, 526 deletions
diff --git a/cmd/sbc_harness/hw/w5500.c b/cmd/sbc_harness/hw/w5500.c deleted file mode 100644 index 5e36bcf..0000000 --- a/cmd/sbc_harness/hw/w5500.c +++ /dev/null @@ -1,526 +0,0 @@ -/* hw/w5500.c - libmisc/net.h implementation for the WIZnet W5500 chip - * - * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com> - * 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/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 <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. - * - * SPDX-Licence-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-Licence-Identifier: MIT - */ - -#include <pico/time.h> /* for sleep_ms() */ -#include <hardware/gpio.h> /* pico-sdk:hardware_gpio */ - -#include <libcr/coroutine.h> /* for cr_yield() */ -#include <libmisc/vcall.h> /* for VCALL_SELF() */ - -#include "hw/w5500_ll.h" -#include "hw/w5500.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<<socknum))) - continue; - struct _w5500_tcp_listener *listener = &chip->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 ***********************************************************/ |