diff options
Diffstat (limited to 'libhw/w5500.c')
-rw-r--r-- | libhw/w5500.c | 836 |
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; -} |