/* libhw/w5500.c - implementation for the WIZnet W5500 chip * * Copyright (C) 2024-2025 Luke T. Shumaker * 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 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 /* for PRIu{n} */ /* TODO: Write a to avoid w5500.c being * pico-sdk-specific. */ #include /* pico-sdk:hardware_gpio */ #include /* for cr_yield() */ #include /* for sleep_*() */ #define LOG_NAME W5500 #include /* for errorf(), debugf(), const_byte_str() */ #define IMPLEMENTATION_FOR_LIBHW_W5500_H YES #include #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_NUM #error config.h must define CONFIG_W5500_NUM #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(net_stream_listener, struct _w5500_socket, w5500_tcplist, static) LO_IMPLEMENTATION_C(net_stream_conn, struct _w5500_socket, w5500_tcp, 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 struct w5500 *w5500_chips[CONFIG_W5500_NUM] = {0}; static void w5500_intrhandler(uint gpio, uint32_t LM_UNUSED(event_mask)) { debugf("w5500_intrhandler(): interrupt on pin %u", gpio); for (size_t i = 0; i < LM_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, 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. */ }; } /* 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. */ bool saved = cr_save_and_disable_interrupts(); for (size_t i = 0; i < LM_ARRAY_LEN(w5500_chips); i++) { if (w5500_chips[i] == NULL) { w5500_chips[i] = chip; break; } } cr_restore_interrupts(saved); 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 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) { 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_write(struct _w5500_socket *socket, void *buf, size_t count) { debugf("tcp_conn.write(%zu)", count); ASSERT_SELF(stream_conn, TCP); assert(count == 0 || buf); 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_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); 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_read(struct _w5500_socket *socket, void *buf, size_t count) { debugf("tcp_conn.read()"); ASSERT_SELF(stream_conn, TCP); assert(count == 0 || buf); 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_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. */ LO_CALL(bootclock, del_trigger, &trigger); cr_mutex_unlock(&chip->mu); return avail; } static int w5500_tcp_close(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; } /* 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_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); 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_read_deadline(struct _w5500_socket *socket, uint64_t ns) { debugf("udp_conn.set_read_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_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]); debugf(" => received %"PRIu16" bytes%s", len, len < avail-8 ? " (plus more messages)" : ""); /* 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. */ 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; }