diff options
Diffstat (limited to 'libhw_cr/w5500.c')
-rw-r--r-- | libhw_cr/w5500.c | 962 |
1 files changed, 962 insertions, 0 deletions
diff --git a/libhw_cr/w5500.c b/libhw_cr/w5500.c new file mode 100644 index 0000000..295add2 --- /dev/null +++ b/libhw_cr/w5500.c @@ -0,0 +1,962 @@ +/* libhw_cr/w5500.c - <libhw/generic/net.h> implementation for the WIZnet W5500 chip + * + * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> + * 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 <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-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 <inttypes.h> /* for PRIu{n} */ + +/* TODO: Write a <libhw/generic/gpio.h> to avoid w5500.c being + * pico-sdk-specific. */ +#include <hardware/gpio.h> /* pico-sdk:hardware_gpio */ +#include "rp2040_gpioirq.h" + +#include <libcr/coroutine.h> /* for cr_yield() */ + +#include <libhw/generic/alarmclock.h> /* for sleep_*() */ + +#define LOG_NAME W5500 +#include <libmisc/log.h> /* for errorf(), debugf(), const_byte_str() */ + +#define IMPLEMENTATION_FOR_LIBHW_W5500_H YES +#include <libhw/w5500.h> + +#include "w5500_ll.h" + +/* Config *********************************************************************/ + +#include "config.h" + +#ifndef CONFIG_W5500_LOCAL_PORT_MIN + #error config.h must define CONFIG_W5500_LOCAL_PORT_MIN +#endif +#ifndef CONFIG_W5500_LOCAL_PORT_MAX + #error config.h must define CONFIG_W5500_LOCAL_PORT_MAX +#endif +#ifndef CONFIG_W5500_VALIDATE_SPI + #error config.h must define CONFIG_W5500_VALIDATE_SPI +#endif +#ifndef CONFIG_W5500_DEBUG + #error config.h must define CONFIG_W5500_DEBUG +#endif + +/* C language *****************************************************************/ + +static const char *w5500_state_str(uint8_t state) { + switch (state) { + case STATE_CLOSED: return "STATE_CLOSED"; + case STATE_TCP_INIT: return "STATE_TCP_INIT"; + case STATE_TCP_LISTEN: return "STATE_TCP_LISTEN"; + case STATE_TCP_SYNSENT: return "STATE_TCP_SYNSENT"; + case STATE_TCP_SYNRECV: return "STATE_TCP_SYNRECV"; + case STATE_TCP_ESTABLISHED: return "STATE_TCP_ESTABLISHED"; + case STATE_TCP_FIN_WAIT: return "STATE_TCP_FIN_WAIT"; + case STATE_TCP_CLOSING: return "STATE_TCP_CLOSING"; + case STATE_TCP_TIME_WAIT: return "STATE_TCP_TIME_WAIT"; + case STATE_TCP_CLOSE_WAIT: return "STATE_TCP_CLOSE_WAIT"; + case STATE_TCP_LAST_ACK: return "STATE_TCP_LAST_ACK"; + case STATE_UDP: return "STATE_UDP"; + case STATE_MACRAW: return "STATE_MACRAW"; + default: return const_byte_str(state); + } +} + +/* libobj *********************************************************************/ + +LO_IMPLEMENTATION_C(io_closer, struct _w5500_socket, w5500_tcplist, static) +LO_IMPLEMENTATION_C(net_stream_listener, struct _w5500_socket, w5500_tcplist, static) + +LO_IMPLEMENTATION_C(io_reader, struct _w5500_socket, w5500_tcp, static) +LO_IMPLEMENTATION_C(io_writer, struct _w5500_socket, w5500_tcp, static) +LO_IMPLEMENTATION_C(io_readwriter, struct _w5500_socket, w5500_tcp, static) +LO_IMPLEMENTATION_C(io_closer, struct _w5500_socket, w5500_tcp, static) +LO_IMPLEMENTATION_C(io_bidi_closer, struct _w5500_socket, w5500_tcp, static) +LO_IMPLEMENTATION_C(net_stream_conn, struct _w5500_socket, w5500_tcp, static) + +LO_IMPLEMENTATION_C(io_closer, struct _w5500_socket, w5500_udp, static) +LO_IMPLEMENTATION_C(net_packet_conn, struct _w5500_socket, w5500_udp, static) + +LO_IMPLEMENTATION_C(net_iface, struct w5500, w5500_if, static) + +/* mid-level utilities ********************************************************/ + +static uint16_t w5500_alloc_local_port(struct w5500 *self) { + assert(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; +} + +static struct _w5500_socket *w5500_alloc_socket(struct w5500 *self) { + assert(self); + struct _w5500_socket *sock = self->free; + if (!sock) + return NULL; + self->free = sock->next_free; + sock->next_free = NULL; + assert(sock->mode == W5500_MODE_NONE); + return sock; +} + +static void w5500_free_socket(struct w5500 *self, struct _w5500_socket *sock) { + assert(self); + assert(sock); + sock->mode = W5500_MODE_NONE; + sock->next_free = self->free; + self->free = sock; +} + +static void w5500_tcp_maybe_free(struct w5500 *chip, struct _w5500_socket *sock) { + assert(chip); + assert(sock); + assert(sock->mode == W5500_MODE_TCP); + if (!sock->list_open && !sock->read_open && !sock->write_open) + w5500_free_socket(chip, sock); +} + +static COROUTINE w5500_irq_cr(void *_chip) { + struct w5500 *chip = _chip; + cr_begin(); + + for (;;) { + cr_mutex_lock(&chip->mu); + bool had_intr = false; + + uint8_t chipintr = w5500ll_read_common_reg(chip->spidev, chip_interrupt); + n_debugf(W5500_LL, "w5500_irq_cr(): chipintr=%"PRIu8, chipintr); + had_intr = had_intr || (chipintr != 0); + if (chipintr) + w5500ll_write_common_reg(chip->spidev, chip_interrupt, 0xFF); + + for (uint8_t socknum = 0; socknum < 8; socknum++) { + struct _w5500_socket *socket = &chip->sockets[socknum]; + + uint8_t sockintr = w5500ll_read_sock_reg(chip->spidev, socknum, interrupt); + n_debugf(W5500_LL, "w5500_irq_cr(): sockintr[%"PRIu8"]=%"PRIu8, socknum, sockintr); + had_intr = had_intr || (sockintr != 0); + + switch (socket->mode) { + case W5500_MODE_NONE: + break; + case W5500_MODE_TCP: case W5500_MODE_UDP: + uint8_t listen_bits = sockintr & SOCKINTR_CONN, + send_bits = sockintr & (SOCKINTR_SEND_OK|SOCKINTR_SEND_TIMEOUT), + recv_bits = sockintr & (SOCKINTR_RECV_DAT|SOCKINTR_RECV_FIN); + + if (listen_bits) { + debugf("w5500_irq_cr(): signal sock[%"PRIu8"]->listen_sema", socknum); + cr_sema_signal(&socket->listen_sema); + } + if (recv_bits) { + debugf("w5500_irq_cr(): signal sock[%"PRIu8"]->read_sema", socknum); + cr_sema_signal(&socket->read_sema); + } + if (send_bits) { + debugf("w5500_irq_cr(): signal sock[%"PRIu8"]->write_ch", socknum); + _w5500_sockintr_ch_send(&socket->write_ch, send_bits); + } + break; + } + + w5500ll_write_sock_reg(chip->spidev, socknum, interrupt, sockintr); + } + + cr_mutex_unlock(&chip->mu); + + if (!had_intr && gpio_get(chip->pin_intr)) { + debugf("w5500_irq_cr(): looks like all interrupts have been processed, sleeping..."); + cr_sema_wait(&chip->intr); + } else + cr_yield(); + } + + cr_end(); +} + +static struct w5500 *w5500_socket_chip(struct _w5500_socket *socket) { + assert(socket); + assert(socket->socknum < 8); + + struct _w5500_socket *sock0 = &socket[-(socket->socknum)]; + assert(sock0); + struct w5500 *chip = + ((void *)sock0) - offsetof(struct w5500, sockets); + assert(chip); + return chip; +} + +static inline void w5500_socket_cmd(struct _w5500_socket *socket, uint8_t cmd) { + assert(socket); + struct w5500 *chip = w5500_socket_chip(socket); + uint8_t socknum = socket->socknum; + + w5500ll_write_sock_reg(chip->spidev, socknum, command, cmd); + while (w5500ll_read_sock_reg(chip->spidev, socknum, command) != 0x00) + cr_yield(); +} + +static inline void w5500_socket_close(struct _w5500_socket *socket) { + assert(socket); + struct w5500 *chip = w5500_socket_chip(socket); + uint8_t socknum = socket->socknum; + + /* Send CMD_CLOSE. */ + w5500_socket_cmd(socket, CMD_CLOSE); + /* Wait for it to transition to STATE_CLOSED. */ + while (w5500ll_read_sock_reg(chip->spidev, socknum, state) != STATE_CLOSED) + cr_yield(); +} + +#define ASSERT_SELF(_iface, _mode) \ + assert(socket); \ + uint8_t socknum = socket->socknum; \ + assert(socknum < 8); \ + assert(socket->mode == W5500_MODE_##_mode); \ + struct w5500 *chip = w5500_socket_chip(socket); \ + assert(chip); + +/* init() *********************************************************************/ + +static void w5500_intrhandler(void *_chip, uint LM_UNUSED(gpio), enum gpio_irq_level LM_UNUSED(event)) { + struct w5500 *chip = _chip; + debugf("w5500_intrhandler()"); + cr_sema_signal_from_intrhandler(&chip->intr); +} + +void _w5500_init(struct w5500 *chip, + lo_interface spi spi, uint pin_intr, uint pin_reset, + struct net_eth_addr addr) { + assert(chip); + assert(!LO_IS_NULL(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, + }; + chip->free = &chip->sockets[0]; + for (uint8_t i = 0; i < 8; i++) { + chip->sockets[i] = (struct _w5500_socket){ + /* const-after-init */ + .socknum = i, + /* mutable */ + .next_free = (i + 1 < 8) ? &chip->sockets[i+1] : NULL, + /* The rest of the mutable members get + * initialized to the zero values. */ + }; + } + +#if CONFIG_W5500_VALIDATE_SPI + /* Validate that SPI works correctly. */ + bool spi_ok = true; + for (uint16_t a = 0; a < 0x100; a++) { + w5500ll_write_sock_reg(chip->spidev, 0, mode, a); + uint8_t b = w5500ll_read_sock_reg(chip->spidev, 0, mode); + if (b != a) { + errorf("SPI to W5500 does not appear to be functional: wrote:0x%02"PRIx16" != read:0x%02"PRIx8, a, b); + spi_ok = false; + } + } + if (!spi_ok) + __lm_abort(); + w5500ll_write_sock_reg(chip->spidev, 0, mode, 0); +#endif + + /* Initialize the hardware. */ + gpioirq_set_and_enable_exclusive_handler(pin_intr, GPIO_IRQ_EDGE_FALL, w5500_intrhandler, chip); + gpio_set_dir(chip->pin_reset, GPIO_OUT); + w5500_hard_reset(chip); + + /* Finally, wire in the interrupt handler. */ + coroutine_add("w5500_irq", w5500_irq_cr, chip); +} + +/* chip methods ***************************************************************/ + +static 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 default clock-rate of + * 125MHz, that means 8ns; and at the maximum-rated clock-rate + * of 200MHz, that means 5ns. + * + * 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) { + cr_mutex_lock(&chip->mu); + + gpio_put(chip->pin_reset, 0); + sleep_for_ms(1); /* minimum of 500us */ + gpio_put(chip->pin_reset, 1); + sleep_for_ms(2); /* minimum of 1ms */ + + w5500_post_reset(chip); + + cr_mutex_unlock(&chip->mu); +} + +void w5500_soft_reset(struct w5500 *chip) { + cr_mutex_lock(&chip->mu); + + 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); + + cr_mutex_unlock(&chip->mu); +} + +static struct net_eth_addr w5500_if_hwaddr(struct w5500 *chip) { + assert(chip); + + return chip->hwaddr; +} + +static void _w5500_if_up(struct w5500 *chip, struct net_iface_config cfg) { + assert(chip); + + cr_mutex_lock(&chip->mu); + + 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); + + cr_mutex_unlock(&chip->mu); +} + +static void w5500_if_ifup(struct w5500 *chip, struct net_iface_config cfg) { + debugf("if_up()"); + debugf(":: addr = "PRI_net_ip4_addr, ARG_net_ip4_addr(cfg.addr)); + debugf(":: gateway_addr = "PRI_net_ip4_addr, ARG_net_ip4_addr(cfg.gateway_addr)); + debugf(":: subnet_mask = "PRI_net_ip4_addr, ARG_net_ip4_addr(cfg.subnet_mask)); + _w5500_if_up(chip, cfg); +} + +static void w5500_if_ifdown(struct w5500 *chip) { + debugf("if_down()"); + _w5500_if_up(chip, (struct net_iface_config){0}); +} + +static lo_interface net_stream_listener w5500_if_tcp_listen(struct w5500 *chip, uint16_t local_port) { + assert(chip); + + struct _w5500_socket *sock = w5500_alloc_socket(chip); + if (!sock) { + debugf("tcp_listen() => no sock"); + return LO_NULL(net_stream_listener); + } + debugf("tcp_listen() => sock[%"PRIu8"]", sock->socknum); + + if (!local_port) + local_port = w5500_alloc_local_port(chip); + + assert(sock->mode == W5500_MODE_NONE); + sock->mode = W5500_MODE_TCP; + sock->port = local_port; + sock->read_deadline_ns = 0; + sock->list_open = true; + + return lo_box_w5500_tcplist_as_net_stream_listener(sock); +} + +static lo_interface net_stream_conn w5500_if_tcp_dial(struct w5500 *chip, + struct net_ip4_addr node, uint16_t port) { + assert(chip); + assert(memcmp(node.octets, net_ip4_addr_zero.octets, 4)); + assert(memcmp(node.octets, net_ip4_addr_broadcast.octets, 4)); + assert(port); + + struct _w5500_socket *socket = w5500_alloc_socket(chip); + if (!socket) { + debugf("tcp_dial() => no sock"); + return LO_NULL(net_stream_conn); + } + uint8_t socknum = socket->socknum; + debugf("tcp_dial() => sock[%"PRIu8"]", socknum); + + uint16_t local_port = w5500_alloc_local_port(chip); + + assert(socket->mode == W5500_MODE_NONE); + socket->mode = W5500_MODE_TCP; + socket->port = local_port; + socket->read_deadline_ns = 0; + socket->read_open = socket->write_open = true; + + restart: + cr_mutex_lock(&chip->mu); + + /* Mimics socket.c:socket(). */ + w5500_socket_close(socket); + w5500ll_write_sock_reg(chip->spidev, socknum, mode, SOCKMODE_TCP); + w5500ll_write_sock_reg(chip->spidev, socknum, local_port, uint16be_marshal(socket->port)); + w5500_socket_cmd(socket, CMD_OPEN); + while (w5500ll_read_sock_reg(chip->spidev, socknum, state) != STATE_TCP_INIT) + cr_yield(); + + /* Mimics socket.c:connect(). */ + w5500ll_write_sock_reg(chip->spidev, socknum, remote_ip_addr, node); + w5500ll_write_sock_reg(chip->spidev, socknum, remote_port, uint16be_marshal(port)); + w5500_socket_cmd(socket, CMD_CONNECT); + cr_mutex_unlock(&chip->mu); + for (;;) { + uint8_t state = w5500ll_read_sock_reg(chip->spidev, socknum, state); + debugf("tcp_dial(): state=%s", w5500_state_str(state)); + switch (state) { + case STATE_TCP_SYNSENT: + cr_yield(); + break; + case STATE_TCP_ESTABLISHED: + return lo_box_w5500_tcp_as_net_stream_conn(socket); + default: + goto restart; + } + } +} + +static lo_interface net_packet_conn w5500_if_udp_conn(struct w5500 *chip, uint16_t local_port) { + assert(chip); + + struct _w5500_socket *socket = w5500_alloc_socket(chip); + if (!socket) { + debugf("udp_conn() => no sock"); + return LO_NULL(net_packet_conn); + } + uint8_t socknum = socket->socknum; + debugf("udp_conn() => sock[%"PRIu8"]", socknum); + + if (!local_port) + local_port = w5500_alloc_local_port(chip); + + assert(socket->mode == W5500_MODE_NONE); + socket->mode = W5500_MODE_UDP; + socket->port = local_port; + socket->read_deadline_ns = 0; + + /* Mimics socket.c:socket(). */ + cr_mutex_lock(&chip->mu); + w5500_socket_close(socket); + w5500ll_write_sock_reg(chip->spidev, socknum, mode, SOCKMODE_UDP); + w5500ll_write_sock_reg(chip->spidev, socknum, local_port, uint16be_marshal(socket->port)); + w5500_socket_cmd(socket, CMD_OPEN); + while (w5500ll_read_sock_reg(chip->spidev, socknum, state) != STATE_UDP) + cr_yield(); + cr_mutex_unlock(&chip->mu); + + return lo_box_w5500_udp_as_net_packet_conn(socket); +} + +/* tcp_listener methods *******************************************************/ + +static lo_interface net_stream_conn w5500_tcplist_accept(struct _w5500_socket *socket) { + ASSERT_SELF(stream_listener, TCP); + + restart: + if (!socket->list_open) { + debugf("tcp_listener.accept() => already closed"); + return LO_NULL(net_stream_conn); + } + + cr_mutex_lock(&chip->mu); + + /* Mimics socket.c:socket(). */ + w5500_socket_close(socket); + w5500ll_write_sock_reg(chip->spidev, socknum, mode, SOCKMODE_TCP); + w5500ll_write_sock_reg(chip->spidev, socknum, local_port, uint16be_marshal(socket->port)); + w5500_socket_cmd(socket, CMD_OPEN); + while (w5500ll_read_sock_reg(chip->spidev, socknum, state) != STATE_TCP_INIT) + cr_yield(); + + /* Mimics socket.c:listen(). */ + w5500_socket_cmd(socket, CMD_LISTEN); + cr_mutex_unlock(&chip->mu); + for (;;) { + uint8_t state = w5500ll_read_sock_reg(chip->spidev, socknum, state); + debugf("tcp_listener.accept() => state=%s", w5500_state_str(state)); + switch (state) { + case STATE_TCP_LISTEN: + case STATE_TCP_SYNRECV: + cr_sema_wait(&socket->listen_sema); + break; + case STATE_TCP_ESTABLISHED: + socket->read_open = true; + /* fall-through */ + case STATE_TCP_CLOSE_WAIT: + socket->write_open = true; + return lo_box_w5500_tcp_as_net_stream_conn(socket); + default: + goto restart; + } + } +} + +static int w5500_tcplist_close(struct _w5500_socket *socket) { + debugf("tcp_listener.close()"); + ASSERT_SELF(stream_listener, TCP); + + socket->list_open = false; + w5500_tcp_maybe_free(chip, socket); + return 0; +} + +/* tcp_conn methods ***********************************************************/ + +static ssize_t w5500_tcp_writev(struct _w5500_socket *socket, const struct iovec *iov, int iovcnt) { + assert(iov); + assert(iovcnt > 0); + size_t count = 0; + for (int i = 0; i < iovcnt; i++) + count += iov[i].iov_len; + debugf("tcp_conn.write(%zu)", count); + ASSERT_SELF(stream_conn, TCP); + if (count == 0) + return 0; + + /* 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_FINISHED does AIUI, 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 (!socket->write_open) { + debugf(" => soft closed"); + return -NET_ECLOSED; + } + cr_mutex_lock(&chip->mu); + uint8_t state = w5500ll_read_sock_reg(chip->spidev, socknum, state); + if (state != STATE_TCP_ESTABLISHED && state != STATE_TCP_CLOSE_WAIT) { + cr_mutex_unlock(&chip->mu); + debugf(" => hard closed"); + return -NET_ECLOSED; + } + + 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_mutex_unlock(&chip->mu); + cr_yield(); + continue; + } + + /* Queue data to be sent. */ + if ((size_t)freesize > count-done) + freesize = count-done; + uint16_t ptr = uint16be_unmarshal(w5500ll_read_sock_reg(chip->spidev, socknum, tx_write_pointer)); + w5500ll_writev(chip->spidev, ptr, CTL_BLOCK_SOCK(socknum, TX), iov, iovcnt, done, freesize); + w5500ll_write_sock_reg(chip->spidev, socknum, tx_write_pointer, uint16be_marshal(ptr+freesize)); + + /* Submit the queue. */ + w5500_socket_cmd(socket, CMD_SEND); + + cr_mutex_unlock(&chip->mu); + switch (_w5500_sockintr_ch_recv(&socket->write_ch)) { + case SOCKINTR_SEND_OK: + debugf(" => sent %zu", freesize); + done += freesize; + break; + case SOCKINTR_SEND_TIMEOUT: + debugf(" => ACK timeout"); + return -NET_EACK_TIMEOUT; + case SOCKINTR_SEND_OK|SOCKINTR_SEND_TIMEOUT: + assert_notreached("send both OK and timed out?"); + default: + assert_notreached("invalid write_ch bits"); + } + } + debugf(" => send finished"); + return done; +} + +static void w5500_tcp_set_read_deadline(struct _w5500_socket *socket, uint64_t ns) { + debugf("tcp_conn.set_read_deadline(%"PRIu64")", ns); + ASSERT_SELF(stream_conn, TCP); + socket->read_deadline_ns = ns; +} + +static void w5500_tcp_alarm_handler(void *_arg) { + struct _w5500_socket *socket = _arg; + cr_sema_signal_from_intrhandler(&socket->read_sema); +} + +static ssize_t w5500_tcp_readv(struct _w5500_socket *socket, const struct iovec *iov, int iovcnt) { + assert(iov); + assert(iovcnt > 0); + size_t count = 0; + for (int i = 0; i < iovcnt; i++) + count += iov[i].iov_len; + debugf("tcp_conn.read(%zu)", count); + ASSERT_SELF(stream_conn, TCP); + if (count == 0) + return 0; + + struct alarmclock_trigger trigger = {0}; + if (socket->read_deadline_ns) + LO_CALL(bootclock, add_trigger, &trigger, + socket->read_deadline_ns, + w5500_tcp_alarm_handler, + socket); + + /* Wait until there is data to read. */ + uint16_t avail = 0; + for (;;) { + if (!socket->read_open) { + LO_CALL(bootclock, del_trigger, &trigger); + debugf(" => soft closed"); + return -NET_ECLOSED; + } + if (socket->read_deadline_ns && socket->read_deadline_ns <= LO_CALL(bootclock, get_time_ns)) { + LO_CALL(bootclock, del_trigger, &trigger); + debugf(" => recv timeout"); + return -NET_ERECV_TIMEOUT; + } + cr_mutex_lock(&chip->mu); + uint8_t state = w5500ll_read_sock_reg(chip->spidev, socknum, state); + switch (state) { + case STATE_TCP_CLOSE_WAIT: + case STATE_TCP_ESTABLISHED: + case STATE_TCP_FIN_WAIT: + break; /* OK */ + default: + LO_CALL(bootclock, del_trigger, &trigger); + cr_mutex_unlock(&chip->mu); + debugf(" => hard closed"); + return -NET_ECLOSED; + } + + avail = uint16be_unmarshal(w5500ll_read_sock_reg(chip->spidev, socknum, rx_size)); + if (avail) + /* We have data to read. */ + break; + if (state == STATE_TCP_CLOSE_WAIT) { + LO_CALL(bootclock, del_trigger, &trigger); + cr_mutex_unlock(&chip->mu); + debugf(" => EOF"); + return 0; + } + + cr_mutex_unlock(&chip->mu); + cr_sema_wait(&socket->read_sema); + } + assert(avail); + debugf(" => received %"PRIu16" bytes", avail); + uint16_t ptr = uint16be_unmarshal(w5500ll_read_sock_reg(chip->spidev, socknum, rx_read_pointer)); + /* Read the data. */ + if ((size_t)avail > count) + avail = count; + w5500ll_readv(chip->spidev, ptr, CTL_BLOCK_SOCK(socknum, RX), iov, iovcnt, avail); + /* Tell the chip that we read the data. */ + w5500ll_write_sock_reg(chip->spidev, socknum, rx_read_pointer, uint16be_marshal(ptr+avail)); + w5500_socket_cmd(socket, CMD_RECV); + /* Return. */ + LO_CALL(bootclock, del_trigger, &trigger); + cr_mutex_unlock(&chip->mu); + return avail; +} + +static int w5500_tcp_close_inner(struct _w5500_socket *socket, bool rd, bool wr) { + debugf("tcp_conn.close(rd=%s, wr=%s)", rd ? "true" : "false", wr ? "true" : "false"); + ASSERT_SELF(stream_conn, TCP); + + if (rd) + socket->read_open = false; + + if (wr && socket->write_open) { + cr_mutex_lock(&chip->mu); + w5500_socket_cmd(socket, CMD_DISCON); + while (socket->write_open) { + uint8_t state = w5500ll_read_sock_reg(chip->spidev, socknum, state); + switch (state) { + case STATE_TCP_FIN_WAIT: + socket->write_open = false; + /* Can still read */ + if (!socket->read_open) + w5500_socket_close(socket); + break; + case STATE_CLOSED: + socket->write_open = false; + break; + } + } + cr_mutex_unlock(&chip->mu); + } + + w5500_tcp_maybe_free(chip, socket); + return 0; +} + +static int w5500_tcp_close(struct _w5500_socket *socket) { return w5500_tcp_close_inner(socket, true, true); } +static int w5500_tcp_close_read(struct _w5500_socket *socket) { return w5500_tcp_close_inner(socket, true, false); } +static int w5500_tcp_close_write(struct _w5500_socket *socket) { return w5500_tcp_close_inner(socket, false, true); } + +/* udp_conn methods ***********************************************************/ + +static ssize_t w5500_udp_sendto(struct _w5500_socket *socket, void *buf, size_t count, + struct net_ip4_addr node, uint16_t port) { + debugf("udp_conn.sendto()"); + ASSERT_SELF(packet_conn, UDP); + assert(buf); + assert(count); + + uint16_t bufsize = ((uint16_t)w5500ll_read_sock_reg(chip->spidev, socknum, tx_buf_size))*1024; + if (count > bufsize) { + debugf(" => msg too large"); + return -NET_EMSGSIZE; + } + + for (;;) { + cr_mutex_lock(&chip->mu); + uint8_t state = w5500ll_read_sock_reg(chip->spidev, socknum, state); + if (state != STATE_UDP) { + cr_mutex_unlock(&chip->mu); + debugf(" => closed"); + return -NET_ECLOSED; + } + + uint16_t freesize = uint16be_unmarshal(w5500ll_read_sock_reg(chip->spidev, socknum, tx_free_size)); + if (freesize >= count) + /* We can send. */ + break; + + /* Wait for more buffer space. */ + cr_mutex_unlock(&chip->mu); + cr_yield(); + } + + /* Where we're sending it. */ + w5500ll_write_sock_reg(chip->spidev, socknum, remote_ip_addr, node); + w5500ll_write_sock_reg(chip->spidev, socknum, remote_port, uint16be_marshal(port)); + /* Queue data to be sent. */ + uint16_t ptr = uint16be_unmarshal(w5500ll_read_sock_reg(chip->spidev, socknum, tx_write_pointer)); + w5500ll_writev(chip->spidev, ptr, CTL_BLOCK_SOCK(socknum, TX), &((struct iovec){ + .iov_base = buf, + .iov_len = count, + }), 1, 0, 0); + w5500ll_write_sock_reg(chip->spidev, socknum, tx_write_pointer, uint16be_marshal(ptr+count)); + /* Submit the queue. */ + w5500_socket_cmd(socket, CMD_SEND); + + cr_mutex_unlock(&chip->mu); + switch (_w5500_sockintr_ch_recv(&socket->write_ch)) { + case SOCKINTR_SEND_OK: + debugf(" => sent"); + return count; + case SOCKINTR_SEND_TIMEOUT: + debugf(" => ARP timeout"); + return -NET_EARP_TIMEOUT; + case SOCKINTR_SEND_OK|SOCKINTR_SEND_TIMEOUT: + assert_notreached("send both OK and timed out?"); + default: + assert_notreached("invalid write_ch bits"); + } +} + +static void w5500_udp_set_recv_deadline(struct _w5500_socket *socket, uint64_t ns) { + debugf("udp_conn.set_recv_deadline(%"PRIu64")", ns); + ASSERT_SELF(packet_conn, UDP); + socket->read_deadline_ns = ns; +} + +static void w5500_udp_alarm_handler(void *_arg) { + struct _w5500_socket *socket = _arg; + cr_sema_signal_from_intrhandler(&socket->read_sema); +} + +static ssize_t w5500_udp_recvfrom(struct _w5500_socket *socket, void *buf, size_t count, + struct net_ip4_addr *ret_node, uint16_t *ret_port) { + debugf("udp_conn.recvfrom()"); + ASSERT_SELF(packet_conn, UDP); + assert(buf); + assert(count); + + struct alarmclock_trigger trigger = {0}; + if (socket->read_deadline_ns) + LO_CALL(bootclock, add_trigger, &trigger, + socket->read_deadline_ns, + w5500_udp_alarm_handler, + socket); + + /* Wait until there is data to read. */ + uint16_t avail = 0; + for (;;) { + if (socket->read_deadline_ns && socket->read_deadline_ns <= LO_CALL(bootclock, get_time_ns)) { + LO_CALL(bootclock, del_trigger, &trigger); + debugf(" => recv timeout"); + return -NET_ERECV_TIMEOUT; + } + cr_mutex_lock(&chip->mu); + uint8_t state = w5500ll_read_sock_reg(chip->spidev, socknum, state); + if (state != STATE_UDP) { + LO_CALL(bootclock, del_trigger, &trigger); + debugf(" => hard closed"); + return -NET_ECLOSED; + } + + avail = uint16be_unmarshal(w5500ll_read_sock_reg(chip->spidev, socknum, rx_size)); + if (avail) + /* We have data to read. */ + break; + + cr_mutex_unlock(&chip->mu); + cr_sema_wait(&socket->read_sema); + } + assert(avail >= 8); + uint16_t ptr = uint16be_unmarshal(w5500ll_read_sock_reg(chip->spidev, socknum, rx_read_pointer)); + /* Read a munged form of the UDP packet header. I + * can't find in the datasheet where it describes + * this; this is based off of socket.c:recvfrom(). */ + uint8_t hdr[8]; + w5500ll_readv(chip->spidev, ptr, CTL_BLOCK_SOCK(socknum, RX), &((struct iovec){ + .iov_base = hdr, + .iov_len = sizeof(hdr), + }), 1, 0); + if (ret_node) { + ret_node->octets[0] = hdr[0]; + ret_node->octets[1] = hdr[1]; + ret_node->octets[2] = hdr[2]; + ret_node->octets[3] = hdr[3]; + } + if (ret_port) + *ret_port = uint16be_decode(&hdr[4]); + uint16_t len = uint16be_decode(&hdr[6]); + debugf(" => received %"PRIu16" bytes%s", len, len < avail-8 ? " (plus more messages)" : ""); + /* Now read the actual data. */ + if (count > len) + count = len; + w5500ll_readv(chip->spidev, ptr+8, CTL_BLOCK_SOCK(socknum, RX), &((struct iovec){ + .iov_base = buf, + .iov_len = len, + }), 1, 0); + /* Tell the chip that we read the data. */ + w5500ll_write_sock_reg(chip->spidev, socknum, rx_read_pointer, uint16be_marshal(ptr+8+len)); + w5500_socket_cmd(socket, CMD_RECV); + /* Return. */ + LO_CALL(bootclock, del_trigger, &trigger); + cr_mutex_unlock(&chip->mu); + return len; +} + +static int w5500_udp_close(struct _w5500_socket *socket) { + debugf("udp_conn.close()"); + ASSERT_SELF(packet_conn, UDP); + + w5500_socket_close(socket); + w5500_free_socket(chip, socket); + return 0; +} |