summaryrefslogtreecommitdiff
path: root/libhw_cr/w5500.c
diff options
context:
space:
mode:
Diffstat (limited to 'libhw_cr/w5500.c')
-rw-r--r--libhw_cr/w5500.c962
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;
+}