diff options
Diffstat (limited to 'libhw/w5500.c')
-rw-r--r-- | libhw/w5500.c | 962 |
1 files changed, 0 insertions, 962 deletions
diff --git a/libhw/w5500.c b/libhw/w5500.c deleted file mode 100644 index c4d36f3..0000000 --- a/libhw/w5500.c +++ /dev/null @@ -1,962 +0,0 @@ -/* libhw/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; -} |