summaryrefslogtreecommitdiff
path: root/libhw/w5500.c
diff options
context:
space:
mode:
Diffstat (limited to 'libhw/w5500.c')
-rw-r--r--libhw/w5500.c836
1 files changed, 0 insertions, 836 deletions
diff --git a/libhw/w5500.c b/libhw/w5500.c
deleted file mode 100644
index ae48a61..0000000
--- a/libhw/w5500.c
+++ /dev/null
@@ -1,836 +0,0 @@
-/* libhw/w5500.c - <libhw/generic/net.h> implementation for the WIZnet W5500 chip
- *
- * Copyright (C) 2024 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
- */
-
-/* TODO: Write a <libhw/generic/gpio.h> to avoid w5500.c being
- * pico-sdk-specific. */
-#include <hardware/gpio.h> /* pico-sdk:hardware_gpio */
-
-#include <libcr/coroutine.h> /* for cr_yield() */
-#include <libmisc/vcall.h> /* for VCALL_SELF() */
-
-#include <libhw/generic/alarmclock.h> /* for sleep_*() */
-
-#define IMPLEMENTATION_FOR_LIBHW_W5500_H YES
-#include <libhw/w5500.h>
-
-#include "w5500_ll.h"
-
-/* Config *********************************************************************/
-
-#include "config.h"
-
-/* These are the default values of the Linux kernel's
- * net.ipv4.ip_local_port_range, so I figure they're probably good
- * values to use. */
-#ifndef CONFIG_W5500_LOCAL_PORT_MIN
- #define CONFIG_W5500_LOCAL_PORT_MIN 32768
-#endif
-
-#ifndef CONFIG_W5500_LOCAL_PORT_MAX
- #define CONFIG_W5500_LOCAL_PORT_MAX 60999
-#endif
-
-#ifndef CONFIG_W5500_NUM
- #error config.h must define CONFIG_W5500_NUM
-#endif
-
-/* C language *****************************************************************/
-
-#define UNUSED(name)
-#define ARRAY_LEN(ary) (sizeof(ary)/sizeof((ary)[0]))
-
-/* vtables ********************************************************************/
-
-/* iface */
-static struct net_eth_addr w5500_if_hwaddr (implements_net_iface *);
-static void w5500_if_up (implements_net_iface *, struct net_iface_config);
-static void w5500_if_down (implements_net_iface *);
-static implements_net_stream_listener *w5500_if_tcp_listen (implements_net_iface *, uint16_t local_port);
-static implements_net_stream_conn *w5500_if_tcp_dial (implements_net_iface *, struct net_ip4_addr, uint16_t remote_port);
-static implements_net_packet_conn *w5500_if_udp_conn (implements_net_iface *, uint16_t local_port);
-
-/* stream_listener */
-static implements_net_stream_conn *w5500_tcplist_accept(implements_net_stream_listener *);
-static int w5500_tcplist_close (implements_net_stream_listener *);
-
-/* stream_conn */
-static void w5500_tcp_set_read_deadline (implements_net_stream_conn *, uint64_t ns);
-static ssize_t w5500_tcp_read (implements_net_stream_conn *, void *, size_t);
-static ssize_t w5500_tcp_write (implements_net_stream_conn *, void *, size_t);
-static int w5500_tcp_close (implements_net_stream_conn *, bool rd, bool wr);
-
-/* packet_conn */
-static void w5500_udp_set_read_deadline (implements_net_packet_conn *, uint64_t ns);
-static ssize_t w5500_udp_recvfrom (implements_net_packet_conn *, void *, size_t, struct net_ip4_addr *, uint16_t *);
-static ssize_t w5500_udp_sendto (implements_net_packet_conn *, void *, size_t, struct net_ip4_addr, uint16_t);
-static int w5500_udp_close (implements_net_packet_conn *);
-
-/* tables */
-
-static struct net_iface_vtable w5500_iface_vtable = {
- .hwaddr = w5500_if_hwaddr,
- .ifup = w5500_if_up,
- .ifdown = w5500_if_down,
- .tcp_listen = w5500_if_tcp_listen,
- .tcp_dial = w5500_if_tcp_dial,
- .udp_conn = w5500_if_udp_conn,
-};
-
-static struct net_stream_listener_vtable w5500_tcp_listener_vtable = {
- .accept = w5500_tcplist_accept,
- .close = w5500_tcplist_close,
-};
-
-static struct net_stream_conn_vtable w5500_tcp_conn_vtable = {
- .set_read_deadline = w5500_tcp_set_read_deadline,
- .read = w5500_tcp_read,
- .write = w5500_tcp_write,
- .close = w5500_tcp_close,
-};
-
-static struct net_packet_conn_vtable w5500_udp_conn_vtable = {
- .set_read_deadline = w5500_udp_set_read_deadline,
- .recvfrom = w5500_udp_recvfrom,
- .sendto = w5500_udp_sendto,
- .close = w5500_udp_close,
-};
-
-/* 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_sema_wait(&chip->intr);
- if (w5500ll_read_common_reg(chip->spidev, chip_interrupt))
- w5500ll_write_common_reg(chip->spidev, chip_interrupt, 0xFF);
-
- uint8_t sockmask = w5500ll_read_common_reg(chip->spidev, sock_interrupt);
- for (uint8_t socknum = 0; socknum < 8; socknum++) {
- if (!(sockmask & (1<<socknum)))
- continue;
- struct _w5500_socket *socket = &chip->sockets[socknum];
-
- uint8_t sockintr = w5500ll_read_sock_reg(chip->spidev, socknum, interrupt);
-
- switch (socket->mode) {
- case W5500_MODE_NONE:
- break;
- case W5500_MODE_TCP:
- /* SOCKINTR_SEND_OK is useless; just count on the write methods to
- * poll instead of waiting on notification from here. */
- uint8_t listen_bits = sockintr & (SOCKINTR_TIMEOUT|SOCKINTR_CONN),
- read_bits = sockintr & (SOCKINTR_TIMEOUT|SOCKINTR_RECV|SOCKINTR_FIN);
-
- if (listen_bits)
- cr_sema_signal(&socket->listen_sema);
- /* fallthrough */
- case W5500_MODE_UDP:
- if (read_bits)
- cr_sema_signal(&socket->read_sema);
- }
-
- w5500ll_write_sock_reg(chip->spidev, socknum, interrupt, sockintr);
- }
- }
-
- 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;
-
- cr_mutex_lock(&socket->cmd_mu);
- w5500ll_write_sock_reg(chip->spidev, socknum, command, cmd);
- while (w5500ll_read_sock_reg(chip->spidev, socknum, command) != 0x00)
- cr_yield();
- cr_mutex_unlock(&socket->cmd_mu);
-}
-
-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();
-
- /* Update our MCU-side bookkeeping. */
- socket->mode = W5500_MODE_NONE;
- socket->port = 0;
- socket->read_deadline_ns = 0;
- socket->read_open = socket->write_open = false;
-}
-
-#define ASSERT_SELF(_iface, _mode) \
- struct _w5500_socket *socket = \
- VCALL_SELF(struct _w5500_socket, \
- implements_net_##_iface, _socket); \
- 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 struct w5500 *w5500_chips[CONFIG_W5500_NUM] = {0};
-
-static void w5500_intrhandler(uint gpio, uint32_t UNUSED(event_mask)) {
- for (size_t i = 0; i < ARRAY_LEN(w5500_chips); i++)
- if (w5500_chips[i] && w5500_chips[i]->pin_intr == gpio)
- cr_sema_signal_from_intrhandler(&w5500_chips[i]->intr);
-}
-
-void _w5500_init(struct w5500 *chip,
- implements_spi *spi, uint pin_intr, uint pin_reset,
- struct net_eth_addr addr) {
- assert(chip);
- assert(spi);
-
- /* Initialize the data structures. */
- *chip = (struct w5500){
- /* const-after-init */
- .implements_net_iface = { .vtable = &w5500_iface_vtable },
- .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 */
- .implements_net_stream_listener = { .vtable = &w5500_tcp_listener_vtable },
- .implements_net_stream_conn = { .vtable = &w5500_tcp_conn_vtable },
- .implements_net_packet_conn = { .vtable = &w5500_udp_conn_vtable },
- .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 */
- };
- }
-
- /* Initialize the hardware. */
- gpio_set_irq_enabled_with_callback(pin_intr, GPIO_IRQ_EDGE_FALL, true, w5500_intrhandler);
- gpio_set_dir(chip->pin_reset, GPIO_OUT);
- w5500_hard_reset(chip);
-
- /* Finally, wire in the interrupt handler. */
- cr_disable_interrupts();
- for (size_t i = 0; i < ARRAY_LEN(w5500_chips); i++) {
- if (w5500_chips[i] == NULL) {
- w5500_chips[i] = chip;
- break;
- }
- }
- cr_enable_interrupts();
- coroutine_add(w5500_irq_cr, chip);
-}
-
-/* chip methods ***************************************************************/
-
-static inline void w5500_post_reset(struct w5500 *chip) {
- /* The W5500 does not have a built-in MAC address, we must
- * provide one. */
- w5500ll_write_common_reg(chip->spidev, eth_addr, chip->hwaddr);
-
- /* The RP2040 needs a 1/sys_clk hysteresis between interrupts
- * for us to notice them. At the maximum-rated clock-rate of
- * 133MHz, that means 7.5ns (but the sbc-harness overclocks
- * the RP2040, so we could get away with even shorter).
- *
- * If intlevel is non-zero, then the hysteresis is
- * (intlevel+1)*4/(150MHz), or (intlevel+1)*26.7ns; so even
- * the shortest-possible hysteresis much larger than necessary
- * for us. */
- w5500ll_write_common_reg(chip->spidev, intlevel, uint16be_marshal(1));
-
- /* This implementation does not care about any of the
- * chip-level interrupts. */
- w5500ll_write_common_reg(chip->spidev, chip_interrupt_mask, 0);
-
- /* This implementation cares about interrupts for each
- * socket. */
- w5500ll_write_common_reg(chip->spidev, sock_interrupt_mask, 0xFF);
-
- /* Configure retry/timeout.
- *
- * timeout_arp = 0.1ms * retry_time * (retry_count+1)
- *
- * retry_count
- * timeout_tcp = 0.1ms * retry_time * Σ 2^min(n, floor(1+log_2(65535/retry_time)))
- * n=0
- *
- * For retry_time=2000, retry_count=3, this means
- *
- * timeout_arp = 0.8s
- * timeout_tcp = 3.0s
- */
- w5500ll_write_common_reg(chip->spidev, retry_time, uint16be_marshal(2000));
- w5500ll_write_common_reg(chip->spidev, retry_count, 3);
-}
-
-void w5500_hard_reset(struct w5500 *chip) {
- 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);
-}
-
-void w5500_soft_reset(struct w5500 *chip) {
- w5500ll_write_common_reg(chip->spidev, mode, CHIPMODE_RST);
- while (w5500ll_read_common_reg(chip->spidev, mode) & CHIPMODE_RST)
- cr_yield();
-
- w5500_post_reset(chip);
-}
-
-static struct net_eth_addr w5500_if_hwaddr(implements_net_iface *_chip) {
- struct w5500 *chip = VCALL_SELF(struct w5500, implements_net_iface, _chip);
- assert(chip);
-
- return chip->hwaddr;
-}
-
-static void w5500_if_up(implements_net_iface *_chip, struct net_iface_config cfg) {
- struct w5500 *chip = VCALL_SELF(struct w5500, implements_net_iface, _chip);
- assert(chip);
-
- 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);
-}
-
-static void w5500_if_down(implements_net_iface *_chip) {
- w5500_if_up(_chip, (struct net_iface_config){0});
-}
-
-static implements_net_stream_listener *w5500_if_tcp_listen(implements_net_iface *_chip, uint16_t local_port) {
- struct w5500 *chip = VCALL_SELF(struct w5500, implements_net_iface, _chip);
- assert(chip);
-
- struct _w5500_socket *sock = w5500_alloc_socket(chip);
- if (!sock)
- return NULL;
-
- 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 &sock->implements_net_stream_listener;
-}
-
-static implements_net_stream_conn *w5500_if_tcp_dial(implements_net_iface *_chip,
- struct net_ip4_addr node, uint16_t port) {
- struct w5500 *chip = VCALL_SELF(struct w5500, implements_net_iface, _chip);
- 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)
- return NULL;
- uint8_t socknum = socket->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:
- /* 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);
- for (;;) {
- uint8_t state = w5500ll_read_sock_reg(chip->spidev, socknum, state);
- switch (state) {
- case STATE_TCP_SYNSENT:
- cr_yield();
- break;
- case STATE_TCP_ESTABLISHED:
- return &socket->implements_net_stream_conn;
- default:
- goto restart;
- }
- }
-}
-
-implements_net_packet_conn *w5500_if_udp_conn(implements_net_iface *_chip, uint16_t local_port) {
- struct w5500 *chip = VCALL_SELF(struct w5500, implements_net_iface, _chip);
- assert(chip);
-
- struct _w5500_socket *socket = w5500_alloc_socket(chip);
- if (!socket)
- return NULL;
-
- 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;
-
- return &socket->implements_net_packet_conn;
-}
-
-/* tcp_listener methods *******************************************************/
-
-static implements_net_stream_conn *w5500_tcplist_accept(implements_net_stream_listener *_socket) {
- ASSERT_SELF(stream_listener, TCP);
-
- restart:
- if (!socket->list_open)
- return NULL;
-
- /* 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);
- for (;;) {
- uint8_t state = w5500ll_read_sock_reg(chip->spidev, socknum, state);
- switch (state) {
- case STATE_TCP_LISTEN:
- case STATE_TCP_SYNRECV:
- cr_sema_wait(&socket->listen_sema);
- break;
- case STATE_TCP_ESTABLISHED:
- socket->read_open = true;
- /* fall-through */
- case STATE_TCP_CLOSE_WAIT:
- socket->write_open = true;
- return &socket->implements_net_stream_conn;
- default:
- goto restart;
- }
- }
-}
-
-static int w5500_tcplist_close(implements_net_stream_listener *_socket) {
- 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_write(implements_net_stream_conn *_socket, void *buf, size_t count) {
- ASSERT_SELF(stream_conn, TCP);
- assert(buf);
- assert(count);
-
- /* What we really want is to pause until we receive an ACK for
- * some data we just queued, so that we can line up some new
- * data to keep the buffer full. But that's not what
- * SEND_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)
- return -NET_ECLOSED;
- uint8_t state = w5500ll_read_sock_reg(chip->spidev, socknum, state);
- if (state != STATE_TCP_ESTABLISHED && state != STATE_TCP_CLOSE_WAIT)
- 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_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_write(chip->spidev, ptr, CTL_BLOCK_SOCK(socknum, TX), &((char *)buf)[done], freesize);
- w5500ll_write_sock_reg(chip->spidev, socknum, tx_write_pointer, uint16be_marshal(ptr+freesize));
-
- /* Submit the queue. */
- w5500_socket_cmd(socket, CMD_SEND);
- done += freesize;
- }
- return done;
-}
-
-static void w5500_tcp_set_read_deadline(implements_net_stream_conn *_socket, uint64_t 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(&socket->read_sema);
-}
-
-static ssize_t w5500_tcp_read(implements_net_stream_conn *_socket, void *buf, size_t count) {
- ASSERT_SELF(stream_conn, TCP);
- assert(buf);
- assert(count);
-
- struct alarmclock_trigger trigger = {0};
- if (socket->read_deadline_ns)
- VCALL(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) {
- VCALL(bootclock, del_trigger, &trigger);
- return -NET_ECLOSED;
- }
- if (socket->read_deadline_ns && socket->read_deadline_ns >= VCALL(bootclock, get_time_ns)) {
- VCALL(bootclock, del_trigger, &trigger);
- return -NET_ETIMEDOUT;
- }
- 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:
- VCALL(bootclock, del_trigger, &trigger);
- 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) {
- VCALL(bootclock, del_trigger, &trigger);
- return 0; /* EOF */
- }
-
- cr_sema_wait(&socket->read_sema);
- }
- assert(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_read(chip->spidev, ptr, CTL_BLOCK_SOCK(socknum, RX), buf, 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. */
- VCALL(bootclock, del_trigger, &trigger);
- return avail;
-}
-
-static int w5500_tcp_close(implements_net_stream_conn *_socket, bool rd, bool wr) {
- ASSERT_SELF(stream_conn, TCP);
-
- if (rd)
- socket->read_open = false;
-
- if (wr && socket->write_open) {
- 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;
- }
- }
- }
-
- w5500_tcp_maybe_free(chip, socket);
- return 0;
-}
-
-/* udp_conn methods ***********************************************************/
-
-static ssize_t w5500_udp_sendto(implements_net_packet_conn *_socket, void *buf, size_t count,
- struct net_ip4_addr node, uint16_t port) {
- 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)
- return -NET_EMSGSIZE;
-
- for (;;) {
- uint8_t state = w5500ll_read_sock_reg(chip->spidev, socknum, state);
- if (state != STATE_UDP)
- 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_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_write(chip->spidev, ptr, CTL_BLOCK_SOCK(socknum, TX), buf, count);
- w5500ll_write_sock_reg(chip->spidev, socknum, tx_write_pointer, uint16be_marshal(ptr+count));
- /* Submit the queue. */
- w5500_socket_cmd(socket, CMD_SEND);
-
- return count;
-}
-
-static void w5500_udp_set_read_deadline(implements_net_packet_conn *_socket, uint64_t 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(&socket->read_sema);
-}
-
-static ssize_t w5500_udp_recvfrom(implements_net_packet_conn *_socket, void *buf, size_t count,
- struct net_ip4_addr *ret_node, uint16_t *ret_port) {
- ASSERT_SELF(packet_conn, UDP);
- assert(buf);
- assert(count);
-
- struct alarmclock_trigger trigger = {0};
- if (socket->read_deadline_ns)
- VCALL(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 >= VCALL(bootclock, get_time_ns)) {
- VCALL(bootclock, del_trigger, &trigger);
- return -NET_ETIMEDOUT;
- }
- uint8_t state = w5500ll_read_sock_reg(chip->spidev, socknum, state);
- if (state != STATE_UDP) {
- VCALL(bootclock, del_trigger, &trigger);
- return -NET_ECLOSED;
- }
-
- uint16_t avail = uint16be_unmarshal(w5500ll_read_sock_reg(chip->spidev, socknum, rx_size));
- if (avail)
- /* We have data to read. */
- break;
-
- 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_read(chip->spidev, ptr, CTL_BLOCK_SOCK(socknum, RX), hdr, sizeof(hdr));
- 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]);
- /* Now read the actual data. */
- if (count > len)
- count = len;
- w5500ll_read(chip->spidev, ptr+8, CTL_BLOCK_SOCK(socknum, RX), buf, len);
- /* 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. */
- VCALL(bootclock, del_trigger, &trigger);
- return len;
-}
-
-static int w5500_udp_close(implements_net_packet_conn *_socket) {
- ASSERT_SELF(packet_conn, UDP);
-
- w5500_socket_close(socket);
- w5500_free_socket(chip, socket);
- return 0;
-}