From 88adb90f5e805bea27e619fd5209ef58dbff6fd1 Mon Sep 17 00:00:00 2001 From: "Luke T. Shumaker" Date: Sun, 27 Oct 2024 23:22:01 -0600 Subject: Factor out a libhw --- CMakeLists.txt | 3 +- Makefile | 13 +- cmd/sbc_harness/CMakeLists.txt | 5 +- cmd/sbc_harness/hw/rp2040_hwspi.c | 123 ------- cmd/sbc_harness/hw/rp2040_hwspi.h | 63 ---- cmd/sbc_harness/hw/spi.h | 47 --- cmd/sbc_harness/hw/w5500.c | 526 ----------------------------- cmd/sbc_harness/hw/w5500.h | 102 ------ cmd/sbc_harness/hw/w5500_ll.h | 362 -------------------- cmd/sbc_harness/main.c | 15 +- cmd/sbc_harness/usb_keyboard.c | 2 +- cmd/srv9p/CMakeLists.txt | 1 - cmd/srv9p/gnet.c | 453 ------------------------- cmd/srv9p/gnet.h | 37 --- cmd/srv9p/main.c | 10 +- lib9p/CMakeLists.txt | 1 + lib9p/include/lib9p/srv.h | 2 +- libdhcp/include/libdhcp/dhcp.h | 2 +- libhw/CMakeLists.txt | 30 ++ libhw/common_include/libhw/generic/net.h | 99 ++++++ libhw/common_include/libhw/generic/spi.h | 47 +++ libhw/host_include/libhw/host_net.h | 37 +++ libhw/host_net.c | 455 ++++++++++++++++++++++++++ libhw/rp2040_hwspi.c | 123 +++++++ libhw/rp2040_include/libhw/rp2040_hwspi.h | 63 ++++ libhw/rp2040_include/libhw/w5500.h | 102 ++++++ libhw/w5500.c | 527 ++++++++++++++++++++++++++++++ libhw/w5500_ll.h | 363 ++++++++++++++++++++ libmisc/include/libmisc/net.h | 99 ------ 29 files changed, 1871 insertions(+), 1841 deletions(-) delete mode 100644 cmd/sbc_harness/hw/rp2040_hwspi.c delete mode 100644 cmd/sbc_harness/hw/rp2040_hwspi.h delete mode 100644 cmd/sbc_harness/hw/spi.h delete mode 100644 cmd/sbc_harness/hw/w5500.c delete mode 100644 cmd/sbc_harness/hw/w5500.h delete mode 100644 cmd/sbc_harness/hw/w5500_ll.h delete mode 100644 cmd/srv9p/gnet.c delete mode 100644 cmd/srv9p/gnet.h create mode 100644 libhw/CMakeLists.txt create mode 100644 libhw/common_include/libhw/generic/net.h create mode 100644 libhw/common_include/libhw/generic/spi.h create mode 100644 libhw/host_include/libhw/host_net.h create mode 100644 libhw/host_net.c create mode 100644 libhw/rp2040_hwspi.c create mode 100644 libhw/rp2040_include/libhw/rp2040_hwspi.h create mode 100644 libhw/rp2040_include/libhw/w5500.h create mode 100644 libhw/w5500.c create mode 100644 libhw/w5500_ll.h delete mode 100644 libmisc/include/libmisc/net.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 9b5c0e0..f0c12e8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,9 +20,10 @@ add_compile_options(-Wall -Wextra -Werror) add_subdirectory(libcr) add_subdirectory(libcr_ipc) +add_subdirectory(libmisc) +add_subdirectory(libhw) add_subdirectory(libdhcp) add_subdirectory(libusb) -add_subdirectory(libmisc) add_subdirectory(lib9p) add_subdirectory(cmd/sbc_harness) diff --git a/Makefile b/Makefile index bd4f9cc..0f57255 100644 --- a/Makefile +++ b/Makefile @@ -104,17 +104,14 @@ lint/all: lint/%: if ! grep -q 'Copyright (C) 2024 Luke T. Shumaker' $$filename; then \ echo "$$filename is missing a copyright statement"; r=1; \ fi; \ - dscname=$$($(get_dscname) $$filename); \ - filename_alt1=$$(echo "$$filename" | sed \ - -e 's,^cmd/,,' \ + dscname_act=$$($(get_dscname) $$filename); \ + dscname_exp=$$(echo "$$filename" | sed \ -e 's,.*/config/,,' \ - -e 's,.*/include/,,' \ + -e 's,.*include/,,' \ -e 's,^lib9p/idl/,,' \ -e 's/\.wip$$//'); \ - filename_alt2=$$(echo "$$filename_alt1" | sed \ - -e 's,^sbc_harness/hw/,hw/,'); \ - if ! { [ "$$dscname" == "$$filename" ] || [ "$$dscname" == "$$filename_alt1" ] || [ "$$dscname" == "$$filename_alt2" ]; }; then \ - echo "$$filename self-identifies as $$dscname"; r=1; \ + if [ "$$dscname_act" != "$$dscname_exp" ] && [ "cmd/$$dscname_act" != "$$dscname_exp" ]; then \ + echo "$$filename self-identifies as $$dscname_act (expected $$dscname_exp)"; r=1; \ fi; \ if grep -n --color=auto "$$(printf '\\S\t')" $$filename; then \ echo "$$filename uses tabs for alignment"; r=1; \ diff --git a/cmd/sbc_harness/CMakeLists.txt b/cmd/sbc_harness/CMakeLists.txt index d6261cb..57ebe99 100644 --- a/cmd/sbc_harness/CMakeLists.txt +++ b/cmd/sbc_harness/CMakeLists.txt @@ -8,20 +8,17 @@ if (PICO_PLATFORM STREQUAL "rp2040") add_executable(sbc_harness main.c usb_keyboard.c - hw/rp2040_hwspi.c - hw/w5500.c ) target_include_directories(sbc_harness PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/config) target_include_directories(sbc_harness PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) target_link_libraries(sbc_harness pico_stdlib hardware_flash - hardware_gpio - hardware_spi libmisc libusb #libdhcp + libhw ) pico_enable_stdio_usb(sbc_harness 0) diff --git a/cmd/sbc_harness/hw/rp2040_hwspi.c b/cmd/sbc_harness/hw/rp2040_hwspi.c deleted file mode 100644 index 6742c60..0000000 --- a/cmd/sbc_harness/hw/rp2040_hwspi.c +++ /dev/null @@ -1,123 +0,0 @@ -/* hw/rp2040_hwspi.c - `implements_spi` implementation for the RP2040's - * ARM Primecell SSP (PL022) (implementation file) - * - * Copyright (C) 2024 Luke T. Shumaker - * SPDX-Licence-Identifier: AGPL-3.0-or-later - */ - -#include - -#include /* pico-sdk:hardware_spi */ -#include /* pico-sdk:hardware_gpio5 */ - -#include - -#include "hw/rp2040_hwspi.h" - -static void rp2040_hwspi_readwritev(implements_spi *, const struct bidi_iovec *iov, int iovcnt); - -struct spi_vtable rp2040_hwspi_vtable = { - .readwritev = rp2040_hwspi_readwritev, -}; - -void _rp2040_hwspi_init(struct rp2040_hwspi *self, - enum rp2040_hwspi_instance inst_num, - enum spi_mode mode, - uint baudrate_hz, - uint pin_miso, - uint pin_mosi, - uint pin_clk, - uint pin_cs) { - /* Be not weary: This is but 12 lines of actual code; and many - * lines of comments and assert()s. */ - spi_inst_t *inst; - - assert(self); - assert(baudrate_hz); - assert(pin_miso != pin_mosi); - assert(pin_miso != pin_clk); - assert(pin_miso != pin_cs); - assert(pin_mosi != pin_clk); - assert(pin_mosi != pin_cs); - assert(pin_clk != pin_cs); - - /* I know we called this "hwspi", but we're actually going to - * disconnect the CS pin from the PL022 SSP and manually drive - * it from software. This is because the PL022 has a maximum - * of 16-bit frames, while we need to be able to do *at least* - * 32-bit frames (and ideally, much larger). By managing it - * ourselves, we can just keep CS pulled low extra-long, - * making the frame extra-long. */ - - /* Regarding the constraints on pin assignments: see the - * RP2040 datasheet, table 2, in §1.4.3 "GPIO Functions". */ - switch (inst_num) { - case RP2040_HWSPI_0: - inst = spi0; - assert(pin_miso == 0 || pin_miso == 4 || pin_miso == 16 || pin_miso == 20); - /*assert(pin_cs == 1 || pin_cs == 5 || pin_cs == 17 || pin_cs == 21);*/ - assert(pin_clk == 2 || pin_clk == 6 || pin_clk == 18 || pin_clk == 22); - assert(pin_mosi == 3 || pin_mosi == 7 || pin_mosi == 19 || pin_mosi == 23); - break; - case RP2040_HWSPI_1: - inst = spi1; - assert(pin_miso == 8 || pin_miso == 12 || pin_miso == 24 || pin_miso == 28); - /*assert(pin_cs == 9 || pin_cs == 13 || pin_cs == 25 || pin_cs == 29);*/ - assert(pin_clk == 10 || pin_clk == 14 || pin_clk == 26); - assert(pin_mosi == 11 || pin_mosi == 15 || pin_mosi == 27); - break; - default: - assert(false); - } - - spi_init(inst, baudrate_hz); - spi_set_format(inst, 8, - (mode & 0b10) ? SPI_CPOL_1 : SPI_CPOL_0, - (mode & 0b01) ? SPI_CPHA_1 : SPI_CPHA_0, - SPI_MSB_FIRST); - - /* Connect the pins to the PL022; set them each to "function - * 1" (again, see the RP2040 datasheet, table 2, in §1.4.3 - * "GPIO Functions"). - * - * ("GPIO_FUNC_SPI" is how the pico-sdk spells "function 1", - * since on the RP2040 all of the "function 1" functions are - * some part of SPI.) */ - gpio_set_function(pin_clk, GPIO_FUNC_SPI); - gpio_set_function(pin_mosi, GPIO_FUNC_SPI); - gpio_set_function(pin_miso, GPIO_FUNC_SPI); - - /* Initialize the CS pin for software control. */ - gpio_init(pin_cs); - gpio_set_dir(pin_cs, GPIO_OUT); - gpio_put(pin_cs, 1); - - /* Return. */ - self->vtable = &rp2040_hwspi_vtable; - self->inst = inst; - self->pin_cs = pin_cs; -} - -static void rp2040_hwspi_readwritev(implements_spi *_self, const struct bidi_iovec *iov, int iovcnt) { - struct rp2040_hwspi *self = VCALL_SELF(struct rp2040_hwspi, implements_spi, _self); - assert(self); - spi_inst_t *inst = self->inst; - - assert(inst); - assert(iov); - assert(iovcnt); - - gpio_put(self->pin_cs, 0); - /* TODO: Replace blocking reads+writes with DMA. */ - for (int i = 0; i < iovcnt; i++) { - if (iov[i].iov_write_src && iov[i].iov_read_dst) - spi_write_read_blocking(inst, iov[i].iov_write_src, iov[i].iov_read_dst, iov[i].iov_len); - else if (iov[i].iov_write_src) - spi_write_blocking(inst, iov[i].iov_write_src, iov[i].iov_len); - else if (iov[i].iov_read_dst) - spi_read_blocking(inst, 0, iov[i].iov_read_dst, iov[i].iov_len); - else - assert(false); - } - gpio_put(self->pin_cs, 1); -} diff --git a/cmd/sbc_harness/hw/rp2040_hwspi.h b/cmd/sbc_harness/hw/rp2040_hwspi.h deleted file mode 100644 index 393eae6..0000000 --- a/cmd/sbc_harness/hw/rp2040_hwspi.h +++ /dev/null @@ -1,63 +0,0 @@ -/* hw/rp2040_hwspi.h - `implements_spi` implementation for the RP2040's - * ARM Primecell SSP (PL022) (header file) - * - * Copyright (C) 2024 Luke T. Shumaker - * SPDX-Licence-Identifier: AGPL-3.0-or-later - */ - -#ifndef _HW_RP2040_HWSPI_H_ -#define _HW_RP2040_HWSPI_H_ - -#include /* for bi_* */ - -#include "hw/spi.h" - -enum rp2040_hwspi_instance { - RP2040_HWSPI_0 = 0, - RP2040_HWSPI_1 = 1, -}; - -struct rp2040_hwspi { - implements_spi; - - void /*spi_inst_t*/ *inst; - uint pin_cs; -}; - -/** - * Initialize an instance of `struct rp2040_hwspi`. - * - * @param self : struct rp2040_hwspi : the structure to initialize - * @param name : char * : a name for the SPI port; to include in the bininfo - * @param inst_num : enum rp2040_hwspi_instance : the PL220 instance number; RP2040_HWSPI_{0,1} - * @param mode : enum spi_mode : the SPI mode; SPI_MODE_{0..3} - * @param pin_miso : uint : pin number; 0, 4, 16, or 20 for _HWSPI_0; 8, 12, 24, or 28 for _HWSPI_1 - * @param pin_mosi : uint : pin number; 3, 7, 19, or 23 for _HWSPI_0; 11, 15, or 27 for _HWSPI_1 - * @param pin_clk : uint : pin number; 2, 6, 18, or 22 for _HWSPI_0; 10, 14, or 26 for _HWSPI_1 - * @param pin_cs : uint : pin number; any unused GPIO pin - * - * There is no bit-order argument; the RP2040's hardware SPI always - * uses MSB-first bit order. - */ -#define rp2040_hwspi_init(self, name, \ - inst_num, mode, baudrate_hz, \ - pin_miso, pin_mosi, pin_clk, pin_cs) \ - do { \ - bi_decl(bi_4pins_with_names(pin_miso, name" SPI MISO", \ - pin_mosi, name" SPI MOSI", \ - pin_mosi, name" SPI CLK", \ - pin_mosi, name" SPI CS")); \ - _rp2040_hwspi_init(self, \ - inst_num, mode, baudrate_hz, \ - pin_miso, pin_mosi, pin_clk, pin_cs); \ - } while(0) -void _rp2040_hwspi_init(struct rp2040_hwspi *self, - enum rp2040_hwspi_instance inst_num, - enum spi_mode mode, - uint baudrate_hz, - uint pin_miso, - uint pin_mosi, - uint pin_clk, - uint pin_cs); - -#endif /* _HW_RP2040_HWSPI_H_ */ diff --git a/cmd/sbc_harness/hw/spi.h b/cmd/sbc_harness/hw/spi.h deleted file mode 100644 index da588de..0000000 --- a/cmd/sbc_harness/hw/spi.h +++ /dev/null @@ -1,47 +0,0 @@ -/* hw/spi.h - Generic SPI definitions - * - * Copyright (C) 2024 Luke T. Shumaker - * SPDX-Licence-Identifier: AGPL-3.0-or-later - */ - -#ifndef _HW_SPI_H_ -#define _HW_SPI_H_ - -#include /* for size_t */ - -enum spi_mode { - SPI_MODE_0 = 0, /* clk_polarity=0 (idle low), clk_phase=0 (sample on rise) */ - SPI_MODE_1 = 1, /* clk_polarity=0 (idle low), clk_phase=1 (sample on fall) */ - SPI_MODE_2 = 2, /* clk_polarity=1 (idle high), clk_phase=0 (sample on rise) */ - SPI_MODE_3 = 3, /* clk_polarity=1 (idle high), clk_phase=1 (sample on fall) */ -}; - -struct bidi_iovec { - void *iov_read_dst; - void *iov_write_src; - size_t iov_len; -}; - -struct spi_vtable; - -typedef struct { - struct spi_vtable *vtable; -} implements_spi; - -/* This API assumes that an SPI frame is a multiple of 8-bits. - * - * It is my understanding that this is a common constraint of SPI - * hardware, and that the RP2040 is somewhat unusual in that it allows - * frames of any length 4-16 bits (we disconnect the CS pin from the - * PL022 SSP and manually GPIO it from the CPU in order to achieve - * longer frames). - * - * But, more relevantly: The W5500's protocol uses frames that are 4-N - * octets; so we have no need for an API that allows a - * non-multiple-of-8 number of bits. - */ -struct spi_vtable { - void (*readwritev)(implements_spi *, const struct bidi_iovec *iov, int iovcnt); -}; - -#endif /* _HW_SPI_H_ */ diff --git a/cmd/sbc_harness/hw/w5500.c b/cmd/sbc_harness/hw/w5500.c deleted file mode 100644 index 5e36bcf..0000000 --- a/cmd/sbc_harness/hw/w5500.c +++ /dev/null @@ -1,526 +0,0 @@ -/* hw/w5500.c - libmisc/net.h implementation for the WIZnet W5500 chip - * - * Copyright (C) 2024 Luke T. Shumaker - * SPDX-Licence-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-Licence-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-Licence-Identifier: MIT - */ - -#include /* for sleep_ms() */ -#include /* pico-sdk:hardware_gpio */ - -#include /* for cr_yield() */ -#include /* for VCALL_SELF() */ - -#include "hw/w5500_ll.h" -#include "hw/w5500.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])) - -/* mid-level utilities ********************************************************/ - -#if 0 -static uint16_t w5500_get_local_port(struct w5500 *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; -} -#endif - -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<listeners[socknum]; - - uint8_t sockintr = w5500ll_read_sock_reg(chip->spidev, socknum, interrupt); - - /* SOCKINTR_SEND_OK is useless. */ - uint8_t listen_bits = sockintr & (SOCKINTR_TIMEOUT|SOCKINTR_CONN), - read_bits = sockintr & (SOCKINTR_TIMEOUT|SOCKINTR_RECV|SOCKINTR_FIN); - - if (listen_bits) - cr_sema_signal(&listener->listen_sema); - if (read_bits) - cr_sema_signal(&listener->read_sema); - - w5500ll_write_sock_reg(chip->spidev, socknum, interrupt, sockintr); - } - } - - cr_end(); -} - -/* init() *********************************************************************/ - -static implements_net_stream_conn *w5500_tcp_accept(implements_net_stream_listener *listener); -static ssize_t w5500_tcp_read(implements_net_stream_conn *conn, void *buf, size_t count); -static ssize_t w5500_tcp_write(implements_net_stream_conn *conn, void *buf, size_t count); -static int w5500_tcp_close(implements_net_stream_conn *conn, bool rd, bool wr); - -static struct net_stream_listener_vtable w5500_tcp_listener_vtable = { - .accept = w5500_tcp_accept, -}; - -static struct net_stream_conn_vtable w5500_tcp_conn_vtable = { - .read = w5500_tcp_read, - .write = w5500_tcp_write, - .close = w5500_tcp_close, -}; - -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 */ - .spidev = spi, - .pin_intr = pin_intr, - .pin_reset = pin_reset, - .hwaddr = addr, - /* mutable */ - .next_local_port = CONFIG_W5500_LOCAL_PORT_MIN, - }; - for (uint8_t i = 0; i < 8; i++) { - chip->listeners[i] = (struct _w5500_tcp_listener){ - /* const-after-init */ - .vtable = &w5500_tcp_listener_vtable, - .socknum = i, - .active_conn = { - /* const-after-init */ - .vtable = &w5500_tcp_conn_vtable, - /* mutable */ - .read_open = false, - .write_open = false, - }, - /* mutable */ - .port = 0, - }; - } - - /* 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) { - /* TODO: Replace blocking sleep_ms() with something libcr-friendly. */ - gpio_put(chip->pin_reset, 0); - sleep_ms(1); /* minimum of 500us */ - gpio_put(chip->pin_reset, 1); - sleep_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); -} - -void w5500_netcfg(struct w5500 *chip, struct w5500_netcfg cfg) { - 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); -} - -implements_net_stream_listener *w5500_tcp_listen(struct w5500 *chip, uint8_t socknum, - uint16_t port) { - assert(chip); - assert(socknum < 8); - assert(port); - - assert(chip->listeners[socknum].port == 0); - chip->listeners[socknum].port = port; - - return &chip->listeners[socknum]; -} - -/* -implements_net_packet_conn *w5500_udp_conn(struct w5500 *chip, uint8_t socknum, - uint16_t port) { - assert(chip); - assert(socknum < 8); - assert(port); - - assert(chip->listeners[socknum].port == 0); - chip->listeners[socknum].port = port; - - return &chip->listeners[socknum]; -} -*/ - -/* tcp_listener methods *******************************************************/ - -static struct w5500 *w5500_tcp_listener_chip(struct _w5500_tcp_listener *listener) { - assert(listener); - assert(listener->socknum < 8); - - struct _w5500_tcp_listener *sock0 = &listener[-listener->socknum]; - assert(sock0); - struct w5500 *chip = - ((void *)sock0) - offsetof(struct w5500, listeners); - assert(chip); - return chip; -} - -static inline void w5500_tcp_listener_cmd(struct _w5500_tcp_listener *listener, uint8_t cmd) { - assert(listener); - struct w5500 *chip = w5500_tcp_listener_chip(listener); - uint8_t socknum = listener->socknum; - - cr_mutex_lock(&listener->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(&listener->cmd_mu); -} - -static inline void w5500_tcp_listener_cmd_close(struct _w5500_tcp_listener *listener) { - assert(listener); - struct w5500 *chip = w5500_tcp_listener_chip(listener); - uint8_t socknum = listener->socknum; - - w5500_tcp_listener_cmd(listener, CMD_CLOSE); - w5500ll_write_sock_reg(chip->spidev, socknum, interrupt, 0xFF); - while (w5500ll_read_sock_reg(chip->spidev, socknum, state) != STATE_CLOSED) - cr_yield(); - - listener->active_conn.read_open = listener->active_conn.write_open = false; -} - -#define ASSERT_LISTENER() \ - struct _w5500_tcp_listener *self = \ - VCALL_SELF(struct _w5500_tcp_listener, \ - implements_net_stream_listener, _self); \ - struct w5500 *chip = w5500_tcp_listener_chip(self); \ - uint8_t socknum = self->socknum; - -static implements_net_stream_conn *w5500_tcp_accept(implements_net_stream_listener *_self) { - ASSERT_LISTENER(); - - restart: - /* Mimics socket.c:socket(). */ - w5500_tcp_listener_cmd_close(self); - w5500ll_write_sock_reg(chip->spidev, socknum, mode, SOCKMODE_TCP); - w5500ll_write_sock_reg(chip->spidev, socknum, local_port, uint16be_marshal(self->port)); - w5500_tcp_listener_cmd(self, CMD_OPEN); - while (w5500ll_read_sock_reg(chip->spidev, socknum, state) != STATE_TCP_INIT) - cr_yield(); - - /* Mimics socket.c:listen(). */ - w5500_tcp_listener_cmd(self, 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(&self->listen_sema); - break; - case STATE_TCP_ESTABLISHED: - self->active_conn.read_open = true; - /* fall-through */ - case STATE_TCP_CLOSE_WAIT: - self->active_conn.write_open = true; - return &self->active_conn; - default: - goto restart; - } - } -} - -/* tcp_conn methods ***********************************************************/ - -static struct _w5500_tcp_listener *w5500_tcp_conn_listener(struct _w5500_tcp_conn *conn) { - assert(conn); - - struct _w5500_tcp_listener *list = - ((void *)conn) - offsetof(struct _w5500_tcp_listener, active_conn); - return list; -} - -#define ASSERT_CONN() \ - struct _w5500_tcp_conn *self = \ - VCALL_SELF(struct _w5500_tcp_conn, implements_net_stream_conn, _self); \ - struct _w5500_tcp_listener *listener = w5500_tcp_conn_listener(self); \ - struct w5500 *chip = w5500_tcp_listener_chip(listener); \ - uint8_t socknum = listener->socknum; - -static ssize_t w5500_tcp_write(implements_net_stream_conn *_self, void *buf, size_t count) { - ASSERT_CONN(); - 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_FINIAIUI, 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 (!self->write_open) - return -1; - uint8_t state = w5500ll_read_sock_reg(chip->spidev, socknum, state); - if (state != STATE_TCP_ESTABLISHED && state != STATE_TCP_CLOSE_WAIT) - return -1; - - 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. */ - 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_tcp_listener_cmd(listener, CMD_SEND); - done += freesize; - } - return done; -} - -static ssize_t w5500_tcp_read(implements_net_stream_conn *_self, void *buf, size_t count) { - ASSERT_CONN(); - assert(buf); - assert(count); - - size_t done = 0; - while (!done) { - if (!self->read_open) - return -1; - uint8_t state = w5500ll_read_sock_reg(chip->spidev, socknum, state); - switch (state) { - case STATE_TCP_CLOSE_WAIT: - return 0; /* EOF */ - case STATE_TCP_ESTABLISHED: case STATE_TCP_FIN_WAIT: - break; /* OK */ - default: - return -1; - } - - uint16_t avail = uint16be_unmarshal(w5500ll_read_sock_reg(chip->spidev, socknum, rx_size)); - if (!avail) { - cr_sema_wait(&listener->read_sema); - continue; - } - if ((size_t)avail > count) - avail = count; - uint16_t ptr = uint16be_unmarshal(w5500ll_read_sock_reg(chip->spidev, socknum, rx_read_pointer)); - w5500ll_read(chip->spidev, ptr, CTL_BLOCK_SOCK(socknum, RX), &((char *)buf)[done], avail); - w5500ll_write_sock_reg(chip->spidev, socknum, rx_read_pointer, uint16be_marshal(ptr+avail)); - - w5500_tcp_listener_cmd(listener, CMD_RECV); - done += avail; - } - return done; -} - -static int w5500_tcp_close(implements_net_stream_conn *_self, bool rd, bool wr) { - ASSERT_CONN(); - - if (rd) - self->read_open = false; - - if (wr && self->write_open) { - w5500_tcp_listener_cmd(listener, CMD_DISCON); - while (self->write_open) { - uint8_t state = w5500ll_read_sock_reg(chip->spidev, socknum, state); - switch (state) { - case STATE_TCP_FIN_WAIT: - self->write_open = false; - /* Can still read */ - if (!self->read_open) - w5500_tcp_listener_cmd_close(listener); - break; - case STATE_CLOSED: - self->write_open = false; - break; - } - } - } - - return 0; -} - -/* udp_conn methods ***********************************************************/ diff --git a/cmd/sbc_harness/hw/w5500.h b/cmd/sbc_harness/hw/w5500.h deleted file mode 100644 index 2ff04df..0000000 --- a/cmd/sbc_harness/hw/w5500.h +++ /dev/null @@ -1,102 +0,0 @@ -/* hw/w5500.h - libmisc/net.h implementation for the WIZnet W5500 chip - * - * Copyright (C) 2024 Luke T. Shumaker - * SPDX-Licence-Identifier: AGPL-3.0-or-later - */ - -#ifndef _HW_W5500_H_ -#define _HW_W5500_H_ - -#include -#include -#include - -#include "hw/spi.h" - -struct _w5500_tcp_conn { - /* const-after-init */ - implements_net_stream_conn; - /* mutable */ - bool read_open; - bool write_open; -}; - -struct _w5500_tcp_listener { - /* const-after-init */ - implements_net_stream_listener; - uint8_t socknum; - struct _w5500_tcp_conn active_conn; - - /* mutable */ - uint16_t port; - cr_mutex_t cmd_mu; - cr_sema_t listen_sema, read_sema; -}; - -struct w5500 { - /* const-after-init */ - implements_spi *spidev; - uint pin_intr; - uint pin_reset; - struct net_eth_addr hwaddr; - - /* mutable */ - uint16_t next_local_port; - struct _w5500_tcp_listener listeners[8]; - cr_sema_t intr; -}; - -/** - * Initialize a WIZnet W5500 Ethernet-and-TCP/IP-offload chip. - * - * The W5500 has 3 lines of communication with the MCU: - * - * - An SPI-based RPC protocol: - * + mode: mode 0 or mode 3 - * + bit-order: MSB-first - * + clock frequency: 33.3MHz - 80MHz - * - An interrupt pin that it pulls low when an event happens (to let - * the MCU know that it should do an SPI RPC "get" to see what - * happened.) - * - A reset pin that the MCU can pull low to reset the W5500. - */ -#define w5500_init(self, name, spi, pin_intr, pin_reset, eth_addr) do { \ - bi_decl(bi_2pins_with_names(pin_intr, name" interrupt", \ - pin_reset, name" reset")); \ - _w5500_init(self, spi, pin_intr, pin_reset, eth_addr); \ - } while (0) -void _w5500_init(struct w5500 *self, - implements_spi *spi, uint pin_intr, uint pin_reset, - struct net_eth_addr addr); - -/** - * TODO. - */ -void w5500_hard_reset(struct w5500 *self); - -/** - * TODO. - */ -void w5500_soft_reset(struct w5500 *self); - -struct w5500_netcfg { - struct net_ip4_addr gateway_addr; - struct net_ip4_addr subnet_mask; - struct net_ip4_addr addr; -}; - -/** - * TODO. - */ -void w5500_netcfg(struct w5500 *self, struct w5500_netcfg cfg); - -implements_net_stream_listener *w5500_tcp_listen(struct w5500 *self, uint8_t socknum, - uint16_t port); - -/** - * TODO. - */ -implements_net_packet_conn *w5500_udp_conn(struct w5500 *self, uint8_t socknum, - uint16_t port); - -#endif /* _HW_W5500_H_ */ diff --git a/cmd/sbc_harness/hw/w5500_ll.h b/cmd/sbc_harness/hw/w5500_ll.h deleted file mode 100644 index 4cc3b11..0000000 --- a/cmd/sbc_harness/hw/w5500_ll.h +++ /dev/null @@ -1,362 +0,0 @@ -/* hw/w5500_ll.h - Low-level header library for the WIZnet W5500 chip - * - * Based entirely on the W5500 datasheet, v1.1.0. - * https://docs.wiznet.io/img/products/w5500/W5500_ds_v110e.pdf - * - * Copyright (C) 2024 Luke T. Shumaker - * SPDX-Licence-Identifier: AGPL-3.0-or-later - */ - -#ifndef _HW_W5500_LL_H_ -#define _HW_W5500_LL_H_ - -#include /* for assert(), static_assert() */ -#include /* for uint{n}_t */ -#include /* for memcmp() */ - -#include /* for struct net_eth_addr, struct net_ip4_addr */ -#include /* for VCALL() */ -#include /* for uint16be_t */ -#include "hw/spi.h" /* for implements_spi */ - - -/* Low-level protocol built on SPI frames. ***********************************/ - -/* A u8 control byte has 3 parts: block-ID, R/W, and operating-mode. */ - -/* Part 1: Block ID. */ -#define CTL_MASK_BLOCK 0b11111000 -#define _CTL_BLOCK_RES 0b00000 -#define _CTL_BLOCK_REG 0b01000 -#define _CTL_BLOCK_TX 0b10000 -#define _CTL_BLOCK_RX 0b11000 -#define CTL_BLOCK_SOCK(n,part) (((n)<<5)|(_CTL_BLOCK_##part)) -#define CTL_BLOCK_COMMON_REG CTL_BLOCK_SOCK(0,RES) - -/* Part 2: R/W. */ -#define CTL_MASK_RW 0b100 -#define CTL_R 0b000 -#define CTL_W 0b100 - -/* Part 3: Operating mode. */ -#define CTL_MASK_OM 0b11 -#define CTL_OM_VDM 0b00 -#define CTL_OM_FDM1 0b01 -#define CTL_OM_FDM2 0b10 -#define CTL_OM_FDM4 0b11 - -/* Even though SPI is a full-duplex protocol, the W5500's spiframe on top of it is only half-duplex. - * Lame. */ - -static inline void -w5500ll_write(implements_spi *spidev, uint16_t addr, uint8_t block, void *data, size_t data_len) { - assert(spidev); - assert((block & ~CTL_MASK_BLOCK) == 0); - assert(data); - assert(data_len); - - uint8_t header[3] = { - (uint8_t)((addr >> 8) & 0xFF), - (uint8_t)(addr & 0xFF), - (block & CTL_MASK_BLOCK) | CTL_W | CTL_OM_VDM, - }; - struct bidi_iovec iov[] = { - {.iov_read_dst = NULL, .iov_write_src = header, .iov_len = sizeof(header)}, - {.iov_read_dst = NULL, .iov_write_src = data, .iov_len = data_len}, - }; - VCALL(spidev, readwritev, iov, 2); -} - -static inline void -w5500ll_read(implements_spi *spidev, uint16_t addr, uint8_t block, void *data, size_t data_len) { - assert(spidev); - assert((block & ~CTL_MASK_BLOCK) == 0); - assert(data); - assert(data_len); - - uint8_t header[3] = { - (uint8_t)((addr >> 8) & 0xFF), - (uint8_t)(addr & 0xFF), - (block & CTL_MASK_BLOCK) | CTL_R | CTL_OM_VDM, - }; - struct bidi_iovec iov[] = { - {.iov_read_dst = NULL, .iov_write_src = header, .iov_len = sizeof(header)}, - {.iov_read_dst = data, .iov_write_src = NULL, .iov_len = data_len}, - }; - VCALL(spidev, readwritev, iov, 2); -} - -/* Common chip-wide registers. ***********************************************/ - -struct w5500ll_block_common_reg { - uint8_t mode; /* MR; bitfield, see CHIPMODE_{x} below */ - struct net_ip4_addr ip_gateway_addr; /* GAR0 ... GAR3 */ - struct net_ip4_addr ip_subnet_mask; /* SUBR0 ... SUBR3 */ - struct net_eth_addr eth_addr; /* SHAR0 ... SHAR5 */ - struct net_ip4_addr ip_addr; /* SIPR0 ... SIPR3 */ - - uint16be_t intlevel; /* INTLEVEL0, INTLEVEL1; if non-zero, - * hysteresis between pin_intr being pulled - * low (hysteresis=(intlevel+1)*4/(150MHz)) */ - uint8_t chip_interrupt; /* IR; bitfield, see CHIPINTR_{x} below */ - uint8_t chip_interrupt_mask; /* IMR; bitfield, see CHIPINTR_{x} below, 0=disable, 1=enable */ - uint8_t sock_interrupt; /* SIR; bitfield of which sockets have their .interrupt set */ - uint8_t sock_interrupt_mask; /* SIMR; bitfield of sockets, 0=disable, 1=enable */ - uint16be_t retry_time; /* RTR0, RTR0; configures re-transmission period, in units of 100µs */ - uint8_t retry_count; /* RCR; configures max re-transmission count */ - - uint8_t ppp_lcp_request_timer; /* PTIMER */ - uint8_t ppp_lcp_magic_bumber; /* PMAGIC */ - struct net_eth_addr ppp_dst_eth_addr; /* PHAR0 ... PHAR5 */ - uint16be_t ppp_sess_id; /* PSID0 ... PSID1 */ - uint16be_t ppp_max_seg_size; /* PMRU0 ... PMRU1 */ - - struct net_ip4_addr unreachable_ip_addr; /* UIPR0 ... UIPR3 */ - uint16be_t unreachable_port; /* UPORTR0, UPORTR1 */ - - uint8_t phy_cfg; /* PHYCFGR */ - - uint8_t _reserved[10]; - - uint8_t chip_version; /* VERSIONR */ -}; -static_assert(sizeof(struct w5500ll_block_common_reg) == 0x3A); - -/* bitfield */ -#define CHIPMODE_RST ((uint8_t)(1<<7)) /* software reset */ -#define _CHIPMODE_UNUSED6 ((uint8_t)(1<<6)) -#define CHIPMODE_WOL ((uint8_t)(1<<5)) /* wake-on-lan */ -#define CHIPMODE_BLOCK_PING ((uint8_t)(1<<4)) -#define CHIPMODE_PPP ((uint8_t)(1<<3)) -#define _CHIPMODE_UNUSED2 ((uint8_t)(1<<2)) -#define CHIPMODE_FORCE_ARP ((uint8_t)(1<<1)) -#define _CHIPMODE_UNUSED0 ((uint8_t)(1<<0)) - -#define CHIPINTR_CONFLICT ((uint8_t)(1<<7)) /* ARP says remote IP is self */ -#define CHIPINTR_UNREACH ((uint8_t)(1<<6)) -#define CHIPINTR_PPP_CLOSE ((uint8_t)(1<<6)) -#define CHIPINTR_WOL ((uint8_t)(1<<4)) /* wake-on-LAN */ -#define _CHIPINTR_UNUSED3 ((uint8_t)(1<<3)) -#define _CHIPINTR_UNUSED2 ((uint8_t)(1<<2)) -#define _CHIPINTR_UNUSED1 ((uint8_t)(1<<1)) -#define _CHIPINTR_UNUSED0 ((uint8_t)(1<<0)) - -#define w5500ll_write_common_reg(spidev, field, val) \ - w5500ll_write_reg(spidev, \ - CTL_BLOCK_COMMON_REG, \ - struct w5500ll_block_common_reg, \ - field, val) - - -#define w5500ll_read_common_reg(spidev, field) \ - w5500ll_read_reg(spidev, \ - CTL_BLOCK_COMMON_REG, \ - struct w5500ll_block_common_reg, \ - field) - -/* Per-socket registers. *****************************************************/ - -struct w5500ll_block_sock_reg { - uint8_t mode; /* Sn_MR; see SOCKMODE_{x} below */ - uint8_t command; /* Sn_CR; see CMD_{x} below */ - uint8_t interrupt; /* Sn_IR; bitfield, see SOCKINTR_{x} below */ - uint8_t state; /* Sn_SR; see STATE_{x} below */ - uint16be_t local_port; /* Sn_PORT0, Sn_PORT1 */ - struct net_eth_addr remote_eth_addr; /* Sn_DHAR0 ... SnDHAR5 */ - struct net_ip4_addr remote_ip_addr; /* Sn_DIPR0 ... Sn_DIP3 */ - uint16be_t remote_port; /* Sn_DPORT0 ... Sn_DPORT1 */ - - uint16be_t max_seg_size; /* Sn_MSSR0, Sn_MSSR1 */ - uint8_t _reserved0[1]; - uint8_t ip_tos; /* Sn_TOS */ - uint8_t ip_ttl; /* Sn_TTL */ - uint8_t _reserved1[7]; - - uint8_t rx_buf_size; /* Sn_RXBUF_SIZE; in KiB, power of 2, <= 16 */ - uint8_t tx_buf_size; /* Sn_TXBUF_SIZE; in KiB, power of 2, <= 16 */ - uint16be_t tx_free_size; /* Sn_TX_FSR0, Sn_TX_FSR1 */ - uint16be_t tx_read_pointer; /* Sn_TX_RD0, Sn_TX_RD1 */ - uint16be_t tx_write_pointer; /* Sn_TX_WR0, Sn_TX_WR1 */ - uint16be_t rx_size; /* Sn_RX_RSR0, Sn_RX_RSR1 */ - uint16be_t rx_read_pointer; /* Sn_RX_RD0, Sn_RX_RD1 */ - uint16be_t rx_write_pointer; /* Sn_RX_WR0, Sn_RX_WR1 */ - - uint8_t interrupt_mask; /* Sn_IMR */ - uint16be_t fragment_offset; /* Sn_FRAG0, Sn_FRAG1 */ - uint8_t keepalive_timer; /* Sn_KPALVTR */ -}; -static_assert(sizeof(struct w5500ll_block_sock_reg) == 0x30); - -/* low 4 bits are the main enum, high 4 bits are flags */ -#define SOCKMODE_CLOSED ((uint8_t)0b0000) -#define SOCKMODE_TCP ((uint8_t)0b0001) -#define SOCKMODE_UDP ((uint8_t)0b0010) -#define SOCKMODE_MACRAW ((uint8_t)0b0100) - -#define SOCKMODE_FLAG_TCP_NODELAY_ACK ((uint8_t)(1<<5)) - -#define SOCKMODE_FLAG_UDP_ENABLE_MULTICAST ((uint8_t)(1<<7)) -#define SOCKMODE_FLAG_UDP_BLOCK_BROADCAST ((uint8_t)(1<<6)) -#define SOCKMODE_FLAG_UDP_MULTICAST_DOWNGRADE ((uint8_t)(1<<5)) -#define SOCKMODE_FLAG_UDP_BLOCK_UNICAST ((uint8_t)(1<<4)) - -#define SOCKMODE_FLAG_MACRAW_MAC_FILTERING ((uint8_t)(1<<7)) -#define SOCKMODE_FLAG_MACRAW_BLOCK_BROADCAST ((uint8_t)(1<<6)) -#define SOCKMODE_FLAG_MACRAW_BLOCK_MULTICAST ((uint8_t)(1<<5)) -#define SOCKMODE_FLAG_MACRAW_BLOCK_V6 ((uint8_t)(1<<4)) - -#define CMD_OPEN ((uint8_t)0x01) -#define CMD_LISTEN ((uint8_t)0x02) /* TCP-only */ -#define CMD_CONNECT ((uint8_t)0x04) /* TCP-only: dial */ -#define CMD_DISCON ((uint8_t)0x08) /* TCP-only: send FIN */ -#define CMD_CLOSE ((uint8_t)0x10) -#define CMD_SEND ((uint8_t)0x20) -#define CMD_SEND_MAC ((uint8_t)0x21) /* UDP-only: send to remote_eth_addr without doing ARP on remote_ip_addr */ -#define CMD_SEND_KEEP ((uint8_t)0x22) /* TCP-only: send a keepalive without any data */ -#define CMD_RECV ((uint8_t)0x40) - -#define _SOCKINTR_SEND_UNUSED7 ((uint8_t)1<<7) -#define _SOCKINTR_SEND_UNUSED6 ((uint8_t)1<<6) -#define _SOCKINTR_SEND_UNUSED5 ((uint8_t)1<<5) -#define SOCKINTR_SEND_OK ((uint8_t)1<<4) -#define SOCKINTR_TIMEOUT ((uint8_t)1<<3) -#define SOCKINTR_RECV ((uint8_t)1<<2) -#define SOCKINTR_FIN ((uint8_t)1<<1) -#define SOCKINTR_CONN ((uint8_t)1<<1) /* first for SYN, then when SOCKMODE_ESTABLISHED */ - -#define STATE_CLOSED ((uint8_t)0x00) - -/** - * The TCP state diagram is as follows. - * - Reading the W5500's "state" register does not distinguish between FIN_WAIT_1 and FIN_WAIT_2; - * it just has a single FIN_WAIT. - * - At any point the state can jump to "CLOSED" either by CMD_CLOSE or by a timeout. - * - Writing data is valid in ESTABLISHED and CLOSE_WAIT. - * - Reading data is valid in ESTABLISHED and FIN_WAIT. - * - * TCP state diagram, showing the flow of │ CLOSED │ ━━ role separator ┌───────┐ - * SYN, FIN, and their assocaited ACKs. └────────┘ ══ state transition │ state │ - * V ┈┈ packet flow └───────┘ - * (CMD_OPEN) ║ - * V (action/event) - * ┌────────┐ - * ┏━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━│ INIT │━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━┓ - * ┃ server ┃ └────────┘ ┃ client ┃ - * ┣━━━━━━━━━━━━━━━━┛ V ┃┃ V ┗━━━━━━━━━━━━━━━━┫ - * ┃ ╔═══════════╝ ┃┃ ╚═══════════╗ ┃ - * ┃ (CMD_LISTEN) ┃┃ (CMD_CONNECT) ┃ - * ┃ V ┌┃┃┈┈┈┈┈┈┈┈<(send SYN) ┃ - * ┃ ┌────────┐ ┊┃┃ V ┃ - * ┃ │ LISTEN │ ┊┃┃ ┌─────────┐ ┃ - * ┃ └────────┘ ┊┃┃ │ SYNSENT │ ┃ - * ┃ V ┊┃┃ └─────────┘ ┃ - * ┃ (recv SYN)<┈┈┈┈┈┈┘┃┃ V ┃ - * ┃ (send SYN+ACK)>┈┈┈┈┐┃┃ ║ ┃ - * ┃ V └┃┃┈┈┈┈┈┈>(recv SYN+ACK) ┃ - * ┃ ┌─────────┐ ┌┃┃┈┈┈┈┈┈┈┈<(send ack) ┃ - * ┃ │ SYNRECV │ ┊┃┃ ║ ┃ - * ┃ └─────────┘ ┊┃┃ ║ ┃ - * ┃ V V ┊┃┃ ║ ┃ - * ┃ ║ (recv ACK)<┈┈┈┘┃┃ ║ ┃ - * ┃ ║ ╚═════════╗ ┃┃ ╔═══════════╝ ┃ - * ┃ ╚═══╗ V ┃┃ V ┃ - * ┃ ║ ┌─────────────┐ ┃ - * ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━║━━━━━━━│ ESTABLISHED │━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ - * ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━║━━━━━━━└─────────────┘━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ - * ┃ ║ V ┃┃ V ┃ - * ┃ ╠═══════════╝ ┃┃ ╚═══════════╗ ┃ - * ┃ (CMD_DISCON) ┃┃ ║ ┃ - * ┃ Both sides sent ┌┈┈┈<(send FIN)>┈┈┈┈┈┈┐┃┃ ║ ┃ - * ┃ FIN at the "same" ┊ V └┃┃┈┈┈┈┈┈┈┈>(recv FIN) ┃ - * ┃ time; both are ┊ ┌────────────┐ ┌┃┃┈┈┈┈┈┈┈┈<(send ACK) ┃ - * ┃ active closers ┊ │ FIN_WAIT_1 │ ┊┃┃ V ┃ - * ┃ / \ ┊ └────────────┘ ┊┃┃ ┌────────────┐ ┃ - * ┃ ,------' '------, ┊ V V ┊┃┃ │ CLOSE_WAIT │ ┃ - * ┃ ╔════════════════╝ ║ ┊┃┃ └────────────┘ ┃ - * ┃ (recv FIN)<┈┈┈┈┤ ╔══╝ ┊┃┃ V ┃ - * ┃ ┌┈┈<(send ACK)>┈┈┐ ┊ ║ ┊┃┃ ║ ┃ - * ┃ ┊ ║ └┈┈┈┈┈>(recv ACK)<┈┈┈┈┈┈┘┃┃ ║ ┃ - * ┃ ┊ V ┊ V ┃┃ ║ ┃ - * ┃ ┊ ┌─────────┐ ┊ ┌────────────┐ ┃┃ ║ ┃ - * ┃ ┊ │ CLOSING │ ┊ │ FIN_WAIT_2 │ ┃┃ ║ ┃ - * ┃ ┊ └─────────┘ ┊ └────────────┘ ┃┃ (CMD_DISCON) ┃ - * ┃ ┊ V ┊ V ┌┃┃┈┈┈┈┈┈┈┈<(send FIN) ┃ - * ┃ ┊ ║ └┈┈┈>(recv FIN)<┈┈┈┈┈┈┘┃┃ ║ ┃ - * ┃ ┊ ║ ┌┈┈┈┈┈<(send ACK)>┈┈┈┈┈┈┐┃┃ V ┃ - * ┃ └┈┈>(recv ACK)<┈┈┘ ╚═╗ ┊┃┃ ┌──────────┐ ┃ - * ┃ ╚════════════════╗ ║ ┊┃┃ │ LAST_ACK │ ┃ - * ┃ V V ┊┃┃ └──────────┘ ┃ - * ┃ ┌───────────┐ ┊┃┃ V ┃ - * ┃ │ TIME_WAIT │ ┊┃┃ ║ ┃ - * ┃ └───────────┘ └┃┃┈┈┈┈┈┈┈┈>(recv ACK) ┃ - * ┃ V ┃┃ ║ ┃ - * ┣━━━━━━━━━━━━━━━━┓ (2*MSL has elapsed) ┃┃ ║ ┏━━━━━━━━━━━━━━━━┫ - * ┃ active closer ┃ ╚═══════════╗ ┃┃ ╔═══════════╝ ┃ passive closer ┃ - * ┗━━━━━━━━━━━━━━━━┻━━━━━━━━━━━━━━━━━━━━━━━━━V━┛┗━V━━━━━━━━━━━━━━━━━━━━━━━━━━┻━━━━━━━━━━━━━━━━┛ - * ┌────────┐ - * │ CLOSED │ - */ -#define STATE_TCP_INIT ((uint8_t)0x13) -#define STATE_TCP_LISTEN ((uint8_t)0x14) /* server */ -#define STATE_TCP_SYNSENT ((uint8_t)0x15) /* client; during dial */ -#define STATE_TCP_SYNRECV ((uint8_t)0x16) /* server; during accept */ -#define STATE_TCP_ESTABLISHED ((uint8_t)0x17) -#define STATE_TCP_FIN_WAIT ((uint8_t)0x18) /* during active close */ -#define STATE_TCP_CLOSING ((uint8_t)0x1a) /* during active close */ -#define STATE_TCP_TIME_WAIT ((uint8_t)0x1b) /* during active close */ -#define STATE_TCP_CLOSE_WAIT ((uint8_t)0x1c) /* during passive close */ -#define STATE_TCP_LAST_ACK ((uint8_t)0x1d) /* during passive close */ - -#define STATE_UDP ((uint8_t)0x22) - -#define STATE_MACRAW ((uint8_t)0x42) - -#define w5500ll_write_sock_reg(spidev, socknum, field, val) \ - w5500ll_write_reg(spidev, \ - CTL_BLOCK_SOCK(socknum, REG), \ - struct w5500ll_block_sock_reg, \ - field, val) - -#define w5500ll_read_sock_reg(spidev, socknum, field) \ - w5500ll_read_reg(spidev, \ - CTL_BLOCK_SOCK(socknum, REG), \ - struct w5500ll_block_sock_reg, \ - field) - -/******************************************************************************/ - -#define w5500ll_write_reg(spidev, blockid, blocktyp, field, val) do { \ - typeof((blocktyp){}.field) lval = val; \ - w5500ll_write(spidev, \ - offsetof(blocktyp, field), \ - blockid, \ - &lval, \ - sizeof(lval)); \ - } while (0) - -/* The datasheet tells us that multi-byte reads are non-atomic and - * that "it is recommended that you read all 16-bits twice or more - * until getting the same value". */ -#define w5500ll_read_reg(spidev, blockid, blocktyp, field) ({ \ - typeof((blocktyp){}.field) val; \ - w5500ll_read(spidev, \ - offsetof(blocktyp, field), \ - blockid, \ - &val, \ - sizeof(val)); \ - if (sizeof(val) > 1) \ - for (;;) { \ - typeof(val) val2; \ - w5500ll_read(spidev, \ - offsetof(blocktyp, field), \ - blockid, \ - &val2, \ - sizeof(val)); \ - if (memcmp(&val2, &val, sizeof(val)) == 0) \ - break; \ - val = val2; \ - } \ - val; \ - }) - -#endif /* _HW_W5500_LL_H_ */ diff --git a/cmd/sbc_harness/main.c b/cmd/sbc_harness/main.c index 8ecc56c..b0c9659 100644 --- a/cmd/sbc_harness/main.c +++ b/cmd/sbc_harness/main.c @@ -4,17 +4,18 @@ * SPDX-Licence-Identifier: AGPL-3.0-or-later */ -#include /* for strlen() */ -#include /* for printf() */ -#include "pico/stdlib.h" -#include "hardware/flash.h" +#include /* libc: for strlen() */ +#include /* libc: for printf() */ + +#include /* pico-sdk:pico_stdlib: for stdio_uart_init() */ +#include /* pico-sdk:hardware_flash: for flash_get_unique_id() */ #include -#include +#include +#include #include +#include -#include "hw/rp2040_hwspi.h" -#include "hw/w5500.h" #include "usb_keyboard.h" COROUTINE hello_world_cr(void *_chan) { diff --git a/cmd/sbc_harness/usb_keyboard.c b/cmd/sbc_harness/usb_keyboard.c index b98345c..fcd0fe1 100644 --- a/cmd/sbc_harness/usb_keyboard.c +++ b/cmd/sbc_harness/usb_keyboard.c @@ -4,7 +4,7 @@ * SPDX-Licence-Identifier: AGPL-3.0-or-later */ -#include "tusb.h" +#include #include /* for TUD_ENDPOINT_IN */ #include diff --git a/cmd/srv9p/CMakeLists.txt b/cmd/srv9p/CMakeLists.txt index ccad845..d24d3d9 100644 --- a/cmd/srv9p/CMakeLists.txt +++ b/cmd/srv9p/CMakeLists.txt @@ -13,7 +13,6 @@ set(static_srcs add_executable(srv9p main.c static9p.c - gnet.c ) target_include_directories(srv9p PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) target_include_directories(srv9p PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/config) diff --git a/cmd/srv9p/gnet.c b/cmd/srv9p/gnet.c deleted file mode 100644 index f1ea709..0000000 --- a/cmd/srv9p/gnet.c +++ /dev/null @@ -1,453 +0,0 @@ -/* srv9p/gnet.c - libmisc/net.h implementation for libcr + GNU libc - * - * Copyright (C) 2024 Luke T. Shumaker - * SPDX-Licence-Identifier: AGPL-3.0-or-later - */ - -#define _GNU_SOURCE /* for pthread_sigqueue(3gnu) */ -/* misc */ -#include /* for assert() */ -#include /* for errno, EAGAIN, EINVAL */ -#include /* for error(3gnu) */ -#include /* for abs(), shutdown(), SHUT_RD, SHUT_WR, SHUT_RDWR */ -#include /* for read(), write() */ -/* net */ -#include /* for htons(3p) */ -#include /* for struct sockaddr_in */ -#include /* for struct sockaddr{,_storage}, SOCK_*, SOL_*, SO_*, socket(), setsockopt(), bind(), listen(), accept() */ -/* async */ -#include /* for pthread_* */ -#include /* for siginfo_t, struct sigaction, enum sigval, sigaction(), SIGRTMIN, SIGRTMAX, SA_SIGINFO */ - -#include -#include - -#include "gnet.h" - -/* common *********************************************************************/ - -#define UNUSED(name) /* name __attribute__ ((unused)) */ - -static int gnet_sig_io = 0; - -static void gnet_handle_sig_io(int UNUSED(sig), siginfo_t *info, void *UNUSED(ucontext)) { - cr_unpause_from_intrhandler((cid_t)info->si_value.sival_int); -} - -static void gnet_init(void) { - struct sigaction action = {0}; - - if (gnet_sig_io) - return; - - gnet_sig_io = SIGRTMIN; - if (gnet_sig_io > SIGRTMAX) - error(1, 0, "SIGRTMAX exceeded"); - - action.sa_flags = SA_SIGINFO; - action.sa_sigaction = gnet_handle_sig_io; - if (sigaction(gnet_sig_io, &action, NULL) < 0) - error(1, errno, "sigaction"); -} - -#define WAKE_COROUTINE(args) do { \ - int r; \ - union sigval val = {0}; \ - val.sival_int = (int)((args)->cr_coroutine); \ - do { \ - r = pthread_sigqueue((args)->cr_thread, gnet_sig_io, val); \ - assert(r == 0 || r == EAGAIN); \ - } while (r == EAGAIN); \ - } while (0) - -static inline bool RUN_PTHREAD(void *(*fn)(void *), void *args) { - pthread_t thread; - if (pthread_create(&thread, NULL, fn, args)) - return true; - cr_pause_and_yield(); - if (pthread_join(thread, NULL)) - return true; - return false; -} - -static inline ssize_t gnet_map_errno(ssize_t v) { - if (v >= 0) - return v; - switch (v) { - case ETIMEDOUT: - return NET_ETIMEDOUT; - default: - return NET_EOTHER; - } -} - -/* TCP init() ( AKA socket(3) + listen(3) )************************************/ - -static implements_net_stream_conn *gnet_tcp_accept(implements_net_stream_listener *_listener); -static ssize_t gnet_tcp_read(implements_net_stream_conn *conn, void *buf, size_t count); -static ssize_t gnet_tcp_write(implements_net_stream_conn *conn, void *buf, size_t count); -static int gnet_tcp_close(implements_net_stream_conn *conn, bool rd, bool wr); - -static struct net_stream_listener_vtable gnet_tcp_listener_vtable = { - .accept = gnet_tcp_accept, -}; - -static struct net_stream_conn_vtable gnet_tcp_conn_vtable = { - .read = gnet_tcp_read, - .write = gnet_tcp_write, - .close = gnet_tcp_close, -}; - -void gnet_tcp_listener_init(struct gnet_tcp_listener *self, uint16_t port) { - int listenerfd; - union { - struct sockaddr_in in; - struct sockaddr gen; - } addr = { 0 }; - - gnet_init(); - - addr.in.sin_family = AF_INET; - addr.in.sin_port = htons(port); - listenerfd = socket(AF_INET, SOCK_STREAM, 0); - if (listenerfd < 0) - error(1, errno, "socket"); - if (setsockopt(listenerfd, SOL_SOCKET, SO_REUSEADDR, &(int){1}, sizeof(int)) < 0) - error(1, errno, "setsockopt(fd=%d, SO_REUSEADDR=1)", listenerfd); - if (setsockopt(listenerfd, SOL_SOCKET, SO_REUSEPORT, &(int){1}, sizeof(int)) < 0) - error(1, errno, "setsockopt(fd=%d, SO_REUSEPORT=1)", listenerfd); - if (bind(listenerfd, &addr.gen, sizeof addr) < 0) - error(1, errno, "bind(fd=%d)", listenerfd); - if (listen(listenerfd, 0) < 0) - error(1, errno, "listen(fd=%d)", listenerfd); - - self->vtable = &gnet_tcp_listener_vtable; - self->fd = listenerfd; -} - -/* TCP accept() ***************************************************************/ - -struct gnet_pthread_accept_args { - pthread_t cr_thread; - cid_t cr_coroutine; - - int listenerfd; - - int *ret_connfd; -}; - -static void *gnet_pthread_accept(void *_args) { - struct gnet_pthread_accept_args *args = _args; - *(args->ret_connfd) = accept(args->listenerfd, NULL, NULL); - if (*(args->ret_connfd) < 0) - *(args->ret_connfd) = -errno; - WAKE_COROUTINE(args); - return NULL; -}; - -static implements_net_stream_conn *gnet_tcp_accept(implements_net_stream_listener *_listener) { - struct gnet_tcp_listener *listener = - VCALL_SELF(struct gnet_tcp_listener, implements_net_stream_listener, _listener); - assert(listener); - - int ret_connfd; - struct gnet_pthread_accept_args args = { - .cr_thread = pthread_self(), - .cr_coroutine = cr_getcid(), - .listenerfd = listener->fd, - .ret_connfd = &ret_connfd, - }; - if (RUN_PTHREAD(gnet_pthread_accept, &args)) - return NULL; - - listener->active_conn.vtable = &gnet_tcp_conn_vtable; - listener->active_conn.fd = ret_connfd; - return &listener->active_conn; -} - -/* TCP read() *****************************************************************/ - -struct gnet_pthread_read_args { - pthread_t cr_thread; - cid_t cr_coroutine; - - int connfd; - void *buf; - size_t count; - - ssize_t *ret; -}; - -static void *gnet_pthread_read(void *_args) { - struct gnet_pthread_read_args *args = _args; - *(args->ret) = read(args->connfd, args->buf, args->count); - if (*(args->ret) < 0) - *(args->ret) = gnet_map_errno(-errno); - WAKE_COROUTINE(args); - return NULL; -}; - -static ssize_t gnet_tcp_read(implements_net_stream_conn *_conn, void *buf, size_t count) { - struct _gnet_tcp_conn *conn = - VCALL_SELF(struct _gnet_tcp_conn, implements_net_stream_conn, _conn); - assert(conn); - - ssize_t ret; - struct gnet_pthread_read_args args = { - .cr_thread = pthread_self(), - .cr_coroutine = cr_getcid(), - - .connfd = conn->fd, - .buf = buf, - .count = count, - - .ret = &ret, - }; - if (RUN_PTHREAD(gnet_pthread_read, &args)) - return -NET_ETHREAD; - return ret; -} - -/* TCP write() ****************************************************************/ - -struct gnet_pthread_write_args { - pthread_t cr_thread; - cid_t cr_coroutine; - - int connfd; - void *buf; - size_t count; - - ssize_t *ret; -}; - -static void *gnet_pthread_write(void *_args) { - struct gnet_pthread_read_args *args = _args; - size_t done = 0; - while (done < args->count) { - ssize_t r = write(args->connfd, args->buf, args->count); - if (r < 0) { - gnet_map_errno(-errno); - break; - } - done += r; - } - if (done == args->count) - *(args->ret) = done; - WAKE_COROUTINE(args); - return NULL; -}; - -static ssize_t gnet_tcp_write(implements_net_stream_conn *_conn, void *buf, size_t count) { - struct _gnet_tcp_conn *conn = - VCALL_SELF(struct _gnet_tcp_conn, implements_net_stream_conn, _conn); - assert(conn); - - ssize_t ret; - struct gnet_pthread_write_args args = { - .cr_thread = pthread_self(), - .cr_coroutine = cr_getcid(), - - .connfd = conn->fd, - .buf = buf, - .count = count, - - .ret = &ret, - }; - if (RUN_PTHREAD(gnet_pthread_write, &args)) - return -NET_ETHREAD; - return ret; -} - -/* TCP close() ****************************************************************/ - -static int gnet_tcp_close(implements_net_stream_conn *_conn, bool rd, bool wr) { - struct _gnet_tcp_conn *conn = - VCALL_SELF(struct _gnet_tcp_conn, implements_net_stream_conn, _conn); - assert(conn); - - int how; - if (rd && wr) - how = SHUT_RDWR; - else if (rd && !wr) - how = SHUT_RD; - else if (!rd && wr) - how = SHUT_WR; - else - assert(false); - return gnet_map_errno(shutdown(conn->fd, how) ? -errno : 0); -} - -/* UDP init() *****************************************************************/ - -static ssize_t gnet_udp_sendto(implements_net_packet_conn *self, void *buf, size_t len, - struct net_ip4_addr addr, uint16_t port); -static ssize_t gnet_udp_recvfrom(implements_net_packet_conn *self, void *buf, size_t len, - struct net_ip4_addr *ret_addr, uint16_t *ret_port); -static int gnet_udp_close(implements_net_packet_conn *self); - -static struct net_packet_conn_vtable gnet_udp_conn_vtable = { - .sendto = gnet_udp_sendto, - .recvfrom = gnet_udp_recvfrom, - .close = gnet_udp_close, -}; - -void gnet_udp_conn_init(struct gnet_udp_conn *self, uint16_t port) { - int fd; - union { - struct sockaddr_in in; - struct sockaddr gen; - struct sockaddr_storage stor; - } addr = { 0 }; - - gnet_init(); - - addr.in.sin_family = AF_INET; - addr.in.sin_port = htons(port); - fd = socket(AF_INET, SOCK_DGRAM, 0); - if (fd < 0) - error(1, errno, "socket"); - if (bind(fd, &addr.gen, sizeof addr) < 0) - error(1, errno, "bind"); - - self->vtable = &gnet_udp_conn_vtable; - self->fd = fd; -} - -/* UDP sendto() ***************************************************************/ - -struct gnet_pthread_sendto_args { - pthread_t cr_thread; - cid_t cr_coroutine; - - int connfd; - void *buf; - size_t count; - struct net_ip4_addr node; - uint16_t port; - - ssize_t *ret; -}; - -static void *gnet_pthread_sendto(void *_args) { - struct gnet_pthread_sendto_args *args = _args; - union { - struct sockaddr_in in; - struct sockaddr gen; - struct sockaddr_storage stor; - } addr = { 0 }; - - addr.in.sin_family = AF_INET; - addr.in.sin_addr.s_addr = - (((uint32_t)args->node.octets[0])<<24) | - (((uint32_t)args->node.octets[1])<<16) | - (((uint32_t)args->node.octets[2])<< 8) | - (((uint32_t)args->node.octets[3])<< 0) ; - addr.in.sin_port = htons(args->port); - *(args->ret) = sendto(args->connfd, args->buf, args->count, 0, &addr.gen, sizeof(addr)); - if (*(args->ret) < 0) - *(args->ret) = gnet_map_errno(-errno); - WAKE_COROUTINE(args); - return NULL; -} - -static ssize_t gnet_udp_sendto(implements_net_packet_conn *_conn, void *buf, size_t count, - struct net_ip4_addr node, uint16_t port) { - struct gnet_udp_conn *conn = - VCALL_SELF(struct gnet_udp_conn, implements_net_packet_conn, _conn); - assert(conn); - - ssize_t ret; - struct gnet_pthread_sendto_args args = { - .cr_thread = pthread_self(), - .cr_coroutine = cr_getcid(), - - .connfd = conn->fd, - .buf = buf, - .count = count, - .node = node, - .port = port, - - .ret = &ret, - }; - if (RUN_PTHREAD(gnet_pthread_sendto, &args)) - return -NET_ETHREAD; - return ret; -} - -/* UDP recvfrom() *************************************************************/ - -struct gnet_pthread_recvfrom_args { - pthread_t cr_thread; - cid_t cr_coroutine; - - int connfd; - void *buf; - size_t count; - - ssize_t *ret_size; - struct net_ip4_addr *ret_node; - uint16_t *ret_port; -}; - -static void *gnet_pthread_recvfrom(void *_args) { - struct gnet_pthread_recvfrom_args *args = _args; - - union { - struct sockaddr_in in; - struct sockaddr gen; - struct sockaddr_storage stor; - } addr = { 0 }; - socklen_t addr_size; - - *(args->ret_size) = recvfrom(args->connfd, args->buf, args->count, 0, &addr.gen, &addr_size); - if (*(args->ret_size) < 0) - *(args->ret_size) = gnet_map_errno(-errno); - else { - assert(addr.in.sin_family == AF_INET); - if (args->ret_node) { - args->ret_node->octets[0] = (addr.in.sin_addr.s_addr >> 24) & 0xFF; - args->ret_node->octets[1] = (addr.in.sin_addr.s_addr >> 16) & 0xFF; - args->ret_node->octets[2] = (addr.in.sin_addr.s_addr >> 8) & 0xFF; - args->ret_node->octets[3] = (addr.in.sin_addr.s_addr >> 0) & 0xFF; - } - if (args->ret_port) - (*args->ret_port) = ntohs(addr.in.sin_port); - } - WAKE_COROUTINE(args); - return NULL; -} - -static ssize_t gnet_udp_recvfrom(implements_net_packet_conn *_conn, void *buf, size_t count, - struct net_ip4_addr *ret_node, uint16_t *ret_port) { - struct gnet_udp_conn *conn = - VCALL_SELF(struct gnet_udp_conn, implements_net_packet_conn, _conn); - assert(conn); - - ssize_t ret; - struct gnet_pthread_recvfrom_args args = { - .cr_thread = pthread_self(), - .cr_coroutine = cr_getcid(), - - .connfd = conn->fd, - .buf = buf, - .count = count, - - .ret_size = &ret, - .ret_node = ret_node, - .ret_port = ret_port, - }; - if (RUN_PTHREAD(gnet_pthread_recvfrom, &args)) - return -NET_ETHREAD; - return ret; -} - -/* UDP close() ****************************************************************/ - -static int gnet_udp_close(implements_net_packet_conn *_conn) { - struct gnet_udp_conn *conn = - VCALL_SELF(struct gnet_udp_conn, implements_net_packet_conn, _conn); - assert(conn); - - return gnet_map_errno(close(conn->fd) ? -errno : 0); -} diff --git a/cmd/srv9p/gnet.h b/cmd/srv9p/gnet.h deleted file mode 100644 index 3e8a0f4..0000000 --- a/cmd/srv9p/gnet.h +++ /dev/null @@ -1,37 +0,0 @@ -/* srv9p/gnet.h - libmisc/net.h implementation for libcr + GNU libc - * - * Copyright (C) 2024 Luke T. Shumaker - * SPDX-Licence-Identifier: AGPL-3.0-or-later - */ - -#ifndef _SRV9P_GNET_H_ -#define _SRV9P_GNET_H_ - -#include /* for uint16_6 */ - -#include - -struct _gnet_tcp_conn { - implements_net_stream_conn; - - int fd; -}; - -struct gnet_tcp_listener { - implements_net_stream_listener; - - int fd; - struct _gnet_tcp_conn active_conn; -}; - -void gnet_tcp_listener_init(struct gnet_tcp_listener *self, uint16_t port); - -struct gnet_udp_conn { - implements_net_packet_conn; - - int fd; -}; - -void gnet_udp_conn_init(struct gnet_udp_conn *self, uint16_t port); - -#endif /* _SRV9P_GNET_H_ */ diff --git a/cmd/srv9p/main.c b/cmd/srv9p/main.c index a2b437e..6ef2cd3 100644 --- a/cmd/srv9p/main.c +++ b/cmd/srv9p/main.c @@ -7,13 +7,13 @@ #include #include -#include -#include #include +#include +#include +#include #include "static9p.h" #include "static.h" -#include "gnet.h" /* configuration **************************************************************/ @@ -91,8 +91,8 @@ static COROUTINE read_cr(void *_srv) { cr_begin(); - struct gnet_tcp_listener listener; - gnet_tcp_listener_init(&listener, 9000); + struct hostnet_tcp_listener listener; + hostnet_tcp_listener_init(&listener, 9000); lib9p_srv_read_cr(srv, &listener); diff --git a/lib9p/CMakeLists.txt b/lib9p/CMakeLists.txt index 829ac8e..2448724 100644 --- a/lib9p/CMakeLists.txt +++ b/lib9p/CMakeLists.txt @@ -13,4 +13,5 @@ target_sources(lib9p INTERFACE target_link_libraries(lib9p INTERFACE libcr_ipc libmisc + libhw ) diff --git a/lib9p/include/lib9p/srv.h b/lib9p/include/lib9p/srv.h index 072925f..3d667db 100644 --- a/lib9p/include/lib9p/srv.h +++ b/lib9p/include/lib9p/srv.h @@ -10,7 +10,7 @@ #include #include #include -#include +#include #include diff --git a/libdhcp/include/libdhcp/dhcp.h b/libdhcp/include/libdhcp/dhcp.h index 660e154..e259034 100644 --- a/libdhcp/include/libdhcp/dhcp.h +++ b/libdhcp/include/libdhcp/dhcp.h @@ -67,7 +67,7 @@ #ifndef _LIBDHCP_DHCP_H_ #define _LIBDHCP_DHCP_H_ -#include +#include #include "hw/w5500.h" enum dhcp_event { diff --git a/libhw/CMakeLists.txt b/libhw/CMakeLists.txt new file mode 100644 index 0000000..ca58a72 --- /dev/null +++ b/libhw/CMakeLists.txt @@ -0,0 +1,30 @@ +# libhw/CMakeLists.txt - TODO +# +# Copyright (C) 2024 Luke T. Shumaker +# SPDX-Licence-Identifier: AGPL-3.0-or-later + +add_library(libhw INTERFACE) +target_include_directories(libhw SYSTEM INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/common_include) +target_link_libraries(libhw INTERFACE + libmisc +) + +if (PICO_PLATFORM STREQUAL "rp2040") + target_include_directories(libhw SYSTEM INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/rp2040_include) + target_sources(libhw INTERFACE + rp2040_hwspi.c + w5500.c + ) + target_link_libraries(libhw INTERFACE + pico_time + hardware_gpio + hardware_spi + ) +endif() + +if (PICO_PLATFORM STREQUAL "host") + target_include_directories(libhw SYSTEM INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/host_include) + target_sources(libhw INTERFACE + host_net.c + ) +endif() diff --git a/libhw/common_include/libhw/generic/net.h b/libhw/common_include/libhw/generic/net.h new file mode 100644 index 0000000..40bcd3b --- /dev/null +++ b/libhw/common_include/libhw/generic/net.h @@ -0,0 +1,99 @@ +/* libhw/generic/net.h - Device-independent network definitions + * + * Copyright (C) 2024 Luke T. Shumaker + * SPDX-Licence-Identifier: AGPL-3.0-or-later + */ + +#ifndef _LIBHW_GENERIC_NET_H_ +#define _LIBHW_GENERIC_NET_H_ + +#include /* for bool */ +#include /* for size_t */ +#include /* for uint{n}_t} */ +#include /* for ssize_t */ + +/* Errnos *********************************************************************/ + +#define NET_EOTHER 1 +#define NET_ETIMEDOUT 2 +#define NET_ETHREAD 3 + +/* Address types **************************************************************/ + +struct net_ip4_addr { + unsigned char octets[4]; +}; + +static const struct net_ip4_addr net_ip4_addr_broadcast = {{255, 255, 255, 255}}; +static const struct net_ip4_addr net_ip4_addr_zero = {{0, 0, 0, 0}}; + +struct net_eth_addr { + unsigned char octets[6]; +}; + +/* Streams (e.g. TCP) *********************************************************/ + +struct net_stream_listener_vtable; +struct net_stream_conn_btable; + +typedef struct { + struct net_stream_listener_vtable *vtable; +} implements_net_stream_listener; + +typedef struct { + struct net_stream_conn_vtable *vtable; +} implements_net_stream_conn; + +struct net_stream_listener_vtable { + /** + * It is invalid to accept() a new connection if an existing + * connection is still open. + */ + implements_net_stream_conn *(*accept)(implements_net_stream_listener *self); +}; + +struct net_stream_conn_vtable { + /** + * Return bytes-read on success, 0 on EOF, -errno on error; a + * short read is *not* an error. + */ + ssize_t (*read)(implements_net_stream_conn *self, + void *buf, size_t count); + + /** + * Return `count` on success, -errno on error; a short write *is* an + * error. + * + * Writes are *not* guaranteed to be atomic (as this would be + * expensive to implement), so if you have concurrent writers then you + * should arrange for a mutex to protect the connection. + */ + ssize_t (*write)(implements_net_stream_conn *self, + void *buf, size_t count); + + /** + * Return 0 on success, -errno on error. + */ + int (*close)(implements_net_stream_conn *self, + bool rd, bool wr); +}; + +/* Packets (e.g. UDP) *********************************************************/ + +struct net_packet_conn_vtable; + +typedef struct { + struct net_packet_conn_vtable *vtable; +} implements_net_packet_conn; + +struct net_packet_conn_vtable { + ssize_t (*sendto )(implements_net_packet_conn *self, + void *buf, size_t len, + struct net_ip4_addr node, uint16_t port); + ssize_t (*recvfrom)(implements_net_packet_conn *self, + void *buf, size_t len, + struct net_ip4_addr *ret_node, uint16_t *ret_port); + int (*close )(implements_net_packet_conn *self); +}; + +#endif /* _LIBHW_GENERIC_NET_H_ */ diff --git a/libhw/common_include/libhw/generic/spi.h b/libhw/common_include/libhw/generic/spi.h new file mode 100644 index 0000000..fcba84e --- /dev/null +++ b/libhw/common_include/libhw/generic/spi.h @@ -0,0 +1,47 @@ +/* libhw/generic/spi.h - Device-independent SPI definitions + * + * Copyright (C) 2024 Luke T. Shumaker + * SPDX-Licence-Identifier: AGPL-3.0-or-later + */ + +#ifndef _LIBHW_GENERIC_SPI_H_ +#define _LIBHW_GENERIC_SPI_H_ + +#include /* for size_t */ + +enum spi_mode { + SPI_MODE_0 = 0, /* clk_polarity=0 (idle low), clk_phase=0 (sample on rise) */ + SPI_MODE_1 = 1, /* clk_polarity=0 (idle low), clk_phase=1 (sample on fall) */ + SPI_MODE_2 = 2, /* clk_polarity=1 (idle high), clk_phase=0 (sample on rise) */ + SPI_MODE_3 = 3, /* clk_polarity=1 (idle high), clk_phase=1 (sample on fall) */ +}; + +struct bidi_iovec { + void *iov_read_dst; + void *iov_write_src; + size_t iov_len; +}; + +struct spi_vtable; + +typedef struct { + struct spi_vtable *vtable; +} implements_spi; + +/* This API assumes that an SPI frame is a multiple of 8-bits. + * + * It is my understanding that this is a common constraint of SPI + * hardware, and that the RP2040 is somewhat unusual in that it allows + * frames of any length 4-16 bits (we disconnect the CS pin from the + * PL022 SSP and manually GPIO it from the CPU in order to achieve + * longer frames). + * + * But, more relevantly: The W5500's protocol uses frames that are 4-N + * octets; so we have no need for an API that allows a + * non-multiple-of-8 number of bits. + */ +struct spi_vtable { + void (*readwritev)(implements_spi *, const struct bidi_iovec *iov, int iovcnt); +}; + +#endif /* _LIBHW_GENERIC_SPI_H_ */ diff --git a/libhw/host_include/libhw/host_net.h b/libhw/host_include/libhw/host_net.h new file mode 100644 index 0000000..c74f0bd --- /dev/null +++ b/libhw/host_include/libhw/host_net.h @@ -0,0 +1,37 @@ +/* libhw/host_net.h - implementation for hosted glibc + * + * Copyright (C) 2024 Luke T. Shumaker + * SPDX-Licence-Identifier: AGPL-3.0-or-later + */ + +#ifndef _LIBHW_HOST_NET_H_ +#define _LIBHW_HOST_NET_H_ + +#include /* for uint16_6 */ + +#include + +struct _hostnet_tcp_conn { + implements_net_stream_conn; + + int fd; +}; + +struct hostnet_tcp_listener { + implements_net_stream_listener; + + int fd; + struct _hostnet_tcp_conn active_conn; +}; + +void hostnet_tcp_listener_init(struct hostnet_tcp_listener *self, uint16_t port); + +struct hostnet_udp_conn { + implements_net_packet_conn; + + int fd; +}; + +void hostnet_udp_conn_init(struct hostnet_udp_conn *self, uint16_t port); + +#endif /* _LIBHW_HOST_NET_H_ */ diff --git a/libhw/host_net.c b/libhw/host_net.c new file mode 100644 index 0000000..d71afee --- /dev/null +++ b/libhw/host_net.c @@ -0,0 +1,455 @@ +/* libhw/host_net.c - implementation for hosted glibc + * + * Copyright (C) 2024 Luke T. Shumaker + * SPDX-Licence-Identifier: AGPL-3.0-or-later + */ + +#define _GNU_SOURCE /* for pthread_sigqueue(3gnu) */ +/* misc */ +#include /* for assert() */ +#include /* for errno, EAGAIN, EINVAL */ +#include /* for error(3gnu) */ +#include /* for abs(), shutdown(), SHUT_RD, SHUT_WR, SHUT_RDWR */ +#include /* for read(), write() */ +/* net */ +#include /* for htons(3p) */ +#include /* for struct sockaddr_in */ +#include /* for struct sockaddr{,_storage}, SOCK_*, SOL_*, SO_*, socket(), setsockopt(), bind(), listen(), accept() */ +/* async */ +#include /* for pthread_* */ +#include /* for siginfo_t, struct sigaction, enum sigval, sigaction(), SIGRTMIN, SIGRTMAX, SA_SIGINFO */ + +#include +#include + +#include + +/* common *********************************************************************/ + +#define UNUSED(name) /* name __attribute__ ((unused)) */ + +static int hostnet_sig_io = 0; + +static void hostnet_handle_sig_io(int UNUSED(sig), siginfo_t *info, void *UNUSED(ucontext)) { + cr_unpause_from_intrhandler((cid_t)info->si_value.sival_int); +} + +static void hostnet_init(void) { + struct sigaction action = {0}; + + if (hostnet_sig_io) + return; + + hostnet_sig_io = SIGRTMIN; + if (hostnet_sig_io > SIGRTMAX) + error(1, 0, "SIGRTMAX exceeded"); + + action.sa_flags = SA_SIGINFO; + action.sa_sigaction = hostnet_handle_sig_io; + if (sigaction(hostnet_sig_io, &action, NULL) < 0) + error(1, errno, "sigaction"); +} + +#define WAKE_COROUTINE(args) do { \ + int r; \ + union sigval val = {0}; \ + val.sival_int = (int)((args)->cr_coroutine); \ + do { \ + r = pthread_sigqueue((args)->cr_thread, \ + hostnet_sig_io, \ + val); \ + assert(r == 0 || r == EAGAIN); \ + } while (r == EAGAIN); \ + } while (0) + +static inline bool RUN_PTHREAD(void *(*fn)(void *), void *args) { + pthread_t thread; + if (pthread_create(&thread, NULL, fn, args)) + return true; + cr_pause_and_yield(); + if (pthread_join(thread, NULL)) + return true; + return false; +} + +static inline ssize_t hostnet_map_errno(ssize_t v) { + if (v >= 0) + return v; + switch (v) { + case ETIMEDOUT: + return NET_ETIMEDOUT; + default: + return NET_EOTHER; + } +} + +/* TCP init() ( AKA socket(3) + listen(3) )************************************/ + +static implements_net_stream_conn *hostnet_tcp_accept(implements_net_stream_listener *_listener); +static ssize_t hostnet_tcp_read(implements_net_stream_conn *conn, void *buf, size_t count); +static ssize_t hostnet_tcp_write(implements_net_stream_conn *conn, void *buf, size_t count); +static int hostnet_tcp_close(implements_net_stream_conn *conn, bool rd, bool wr); + +static struct net_stream_listener_vtable hostnet_tcp_listener_vtable = { + .accept = hostnet_tcp_accept, +}; + +static struct net_stream_conn_vtable hostnet_tcp_conn_vtable = { + .read = hostnet_tcp_read, + .write = hostnet_tcp_write, + .close = hostnet_tcp_close, +}; + +void hostnet_tcp_listener_init(struct hostnet_tcp_listener *self, uint16_t port) { + int listenerfd; + union { + struct sockaddr_in in; + struct sockaddr gen; + } addr = { 0 }; + + hostnet_init(); + + addr.in.sin_family = AF_INET; + addr.in.sin_port = htons(port); + listenerfd = socket(AF_INET, SOCK_STREAM, 0); + if (listenerfd < 0) + error(1, errno, "socket"); + if (setsockopt(listenerfd, SOL_SOCKET, SO_REUSEADDR, &(int){1}, sizeof(int)) < 0) + error(1, errno, "setsockopt(fd=%d, SO_REUSEADDR=1)", listenerfd); + if (setsockopt(listenerfd, SOL_SOCKET, SO_REUSEPORT, &(int){1}, sizeof(int)) < 0) + error(1, errno, "setsockopt(fd=%d, SO_REUSEPORT=1)", listenerfd); + if (bind(listenerfd, &addr.gen, sizeof addr) < 0) + error(1, errno, "bind(fd=%d)", listenerfd); + if (listen(listenerfd, 0) < 0) + error(1, errno, "listen(fd=%d)", listenerfd); + + self->vtable = &hostnet_tcp_listener_vtable; + self->fd = listenerfd; +} + +/* TCP accept() ***************************************************************/ + +struct hostnet_pthread_accept_args { + pthread_t cr_thread; + cid_t cr_coroutine; + + int listenerfd; + + int *ret_connfd; +}; + +static void *hostnet_pthread_accept(void *_args) { + struct hostnet_pthread_accept_args *args = _args; + *(args->ret_connfd) = accept(args->listenerfd, NULL, NULL); + if (*(args->ret_connfd) < 0) + *(args->ret_connfd) = -errno; + WAKE_COROUTINE(args); + return NULL; +}; + +static implements_net_stream_conn *hostnet_tcp_accept(implements_net_stream_listener *_listener) { + struct hostnet_tcp_listener *listener = + VCALL_SELF(struct hostnet_tcp_listener, implements_net_stream_listener, _listener); + assert(listener); + + int ret_connfd; + struct hostnet_pthread_accept_args args = { + .cr_thread = pthread_self(), + .cr_coroutine = cr_getcid(), + .listenerfd = listener->fd, + .ret_connfd = &ret_connfd, + }; + if (RUN_PTHREAD(hostnet_pthread_accept, &args)) + return NULL; + + listener->active_conn.vtable = &hostnet_tcp_conn_vtable; + listener->active_conn.fd = ret_connfd; + return &listener->active_conn; +} + +/* TCP read() *****************************************************************/ + +struct hostnet_pthread_read_args { + pthread_t cr_thread; + cid_t cr_coroutine; + + int connfd; + void *buf; + size_t count; + + ssize_t *ret; +}; + +static void *hostnet_pthread_read(void *_args) { + struct hostnet_pthread_read_args *args = _args; + *(args->ret) = read(args->connfd, args->buf, args->count); + if (*(args->ret) < 0) + *(args->ret) = hostnet_map_errno(-errno); + WAKE_COROUTINE(args); + return NULL; +}; + +static ssize_t hostnet_tcp_read(implements_net_stream_conn *_conn, void *buf, size_t count) { + struct _hostnet_tcp_conn *conn = + VCALL_SELF(struct _hostnet_tcp_conn, implements_net_stream_conn, _conn); + assert(conn); + + ssize_t ret; + struct hostnet_pthread_read_args args = { + .cr_thread = pthread_self(), + .cr_coroutine = cr_getcid(), + + .connfd = conn->fd, + .buf = buf, + .count = count, + + .ret = &ret, + }; + if (RUN_PTHREAD(hostnet_pthread_read, &args)) + return -NET_ETHREAD; + return ret; +} + +/* TCP write() ****************************************************************/ + +struct hostnet_pthread_write_args { + pthread_t cr_thread; + cid_t cr_coroutine; + + int connfd; + void *buf; + size_t count; + + ssize_t *ret; +}; + +static void *hostnet_pthread_write(void *_args) { + struct hostnet_pthread_read_args *args = _args; + size_t done = 0; + while (done < args->count) { + ssize_t r = write(args->connfd, args->buf, args->count); + if (r < 0) { + hostnet_map_errno(-errno); + break; + } + done += r; + } + if (done == args->count) + *(args->ret) = done; + WAKE_COROUTINE(args); + return NULL; +}; + +static ssize_t hostnet_tcp_write(implements_net_stream_conn *_conn, void *buf, size_t count) { + struct _hostnet_tcp_conn *conn = + VCALL_SELF(struct _hostnet_tcp_conn, implements_net_stream_conn, _conn); + assert(conn); + + ssize_t ret; + struct hostnet_pthread_write_args args = { + .cr_thread = pthread_self(), + .cr_coroutine = cr_getcid(), + + .connfd = conn->fd, + .buf = buf, + .count = count, + + .ret = &ret, + }; + if (RUN_PTHREAD(hostnet_pthread_write, &args)) + return -NET_ETHREAD; + return ret; +} + +/* TCP close() ****************************************************************/ + +static int hostnet_tcp_close(implements_net_stream_conn *_conn, bool rd, bool wr) { + struct _hostnet_tcp_conn *conn = + VCALL_SELF(struct _hostnet_tcp_conn, implements_net_stream_conn, _conn); + assert(conn); + + int how; + if (rd && wr) + how = SHUT_RDWR; + else if (rd && !wr) + how = SHUT_RD; + else if (!rd && wr) + how = SHUT_WR; + else + assert(false); + return hostnet_map_errno(shutdown(conn->fd, how) ? -errno : 0); +} + +/* UDP init() *****************************************************************/ + +static ssize_t hostnet_udp_sendto(implements_net_packet_conn *self, void *buf, size_t len, + struct net_ip4_addr addr, uint16_t port); +static ssize_t hostnet_udp_recvfrom(implements_net_packet_conn *self, void *buf, size_t len, + struct net_ip4_addr *ret_addr, uint16_t *ret_port); +static int hostnet_udp_close(implements_net_packet_conn *self); + +static struct net_packet_conn_vtable hostnet_udp_conn_vtable = { + .sendto = hostnet_udp_sendto, + .recvfrom = hostnet_udp_recvfrom, + .close = hostnet_udp_close, +}; + +void hostnet_udp_conn_init(struct hostnet_udp_conn *self, uint16_t port) { + int fd; + union { + struct sockaddr_in in; + struct sockaddr gen; + struct sockaddr_storage stor; + } addr = { 0 }; + + hostnet_init(); + + addr.in.sin_family = AF_INET; + addr.in.sin_port = htons(port); + fd = socket(AF_INET, SOCK_DGRAM, 0); + if (fd < 0) + error(1, errno, "socket"); + if (bind(fd, &addr.gen, sizeof addr) < 0) + error(1, errno, "bind"); + + self->vtable = &hostnet_udp_conn_vtable; + self->fd = fd; +} + +/* UDP sendto() ***************************************************************/ + +struct hostnet_pthread_sendto_args { + pthread_t cr_thread; + cid_t cr_coroutine; + + int connfd; + void *buf; + size_t count; + struct net_ip4_addr node; + uint16_t port; + + ssize_t *ret; +}; + +static void *hostnet_pthread_sendto(void *_args) { + struct hostnet_pthread_sendto_args *args = _args; + union { + struct sockaddr_in in; + struct sockaddr gen; + struct sockaddr_storage stor; + } addr = { 0 }; + + addr.in.sin_family = AF_INET; + addr.in.sin_addr.s_addr = + (((uint32_t)args->node.octets[0])<<24) | + (((uint32_t)args->node.octets[1])<<16) | + (((uint32_t)args->node.octets[2])<< 8) | + (((uint32_t)args->node.octets[3])<< 0) ; + addr.in.sin_port = htons(args->port); + *(args->ret) = sendto(args->connfd, args->buf, args->count, 0, &addr.gen, sizeof(addr)); + if (*(args->ret) < 0) + *(args->ret) = hostnet_map_errno(-errno); + WAKE_COROUTINE(args); + return NULL; +} + +static ssize_t hostnet_udp_sendto(implements_net_packet_conn *_conn, void *buf, size_t count, + struct net_ip4_addr node, uint16_t port) { + struct hostnet_udp_conn *conn = + VCALL_SELF(struct hostnet_udp_conn, implements_net_packet_conn, _conn); + assert(conn); + + ssize_t ret; + struct hostnet_pthread_sendto_args args = { + .cr_thread = pthread_self(), + .cr_coroutine = cr_getcid(), + + .connfd = conn->fd, + .buf = buf, + .count = count, + .node = node, + .port = port, + + .ret = &ret, + }; + if (RUN_PTHREAD(hostnet_pthread_sendto, &args)) + return -NET_ETHREAD; + return ret; +} + +/* UDP recvfrom() *************************************************************/ + +struct hostnet_pthread_recvfrom_args { + pthread_t cr_thread; + cid_t cr_coroutine; + + int connfd; + void *buf; + size_t count; + + ssize_t *ret_size; + struct net_ip4_addr *ret_node; + uint16_t *ret_port; +}; + +static void *hostnet_pthread_recvfrom(void *_args) { + struct hostnet_pthread_recvfrom_args *args = _args; + + union { + struct sockaddr_in in; + struct sockaddr gen; + struct sockaddr_storage stor; + } addr = { 0 }; + socklen_t addr_size; + + *(args->ret_size) = recvfrom(args->connfd, args->buf, args->count, 0, &addr.gen, &addr_size); + if (*(args->ret_size) < 0) + *(args->ret_size) = hostnet_map_errno(-errno); + else { + assert(addr.in.sin_family == AF_INET); + if (args->ret_node) { + args->ret_node->octets[0] = (addr.in.sin_addr.s_addr >> 24) & 0xFF; + args->ret_node->octets[1] = (addr.in.sin_addr.s_addr >> 16) & 0xFF; + args->ret_node->octets[2] = (addr.in.sin_addr.s_addr >> 8) & 0xFF; + args->ret_node->octets[3] = (addr.in.sin_addr.s_addr >> 0) & 0xFF; + } + if (args->ret_port) + (*args->ret_port) = ntohs(addr.in.sin_port); + } + WAKE_COROUTINE(args); + return NULL; +} + +static ssize_t hostnet_udp_recvfrom(implements_net_packet_conn *_conn, void *buf, size_t count, + struct net_ip4_addr *ret_node, uint16_t *ret_port) { + struct hostnet_udp_conn *conn = + VCALL_SELF(struct hostnet_udp_conn, implements_net_packet_conn, _conn); + assert(conn); + + ssize_t ret; + struct hostnet_pthread_recvfrom_args args = { + .cr_thread = pthread_self(), + .cr_coroutine = cr_getcid(), + + .connfd = conn->fd, + .buf = buf, + .count = count, + + .ret_size = &ret, + .ret_node = ret_node, + .ret_port = ret_port, + }; + if (RUN_PTHREAD(hostnet_pthread_recvfrom, &args)) + return -NET_ETHREAD; + return ret; +} + +/* UDP close() ****************************************************************/ + +static int hostnet_udp_close(implements_net_packet_conn *_conn) { + struct hostnet_udp_conn *conn = + VCALL_SELF(struct hostnet_udp_conn, implements_net_packet_conn, _conn); + assert(conn); + + return hostnet_map_errno(close(conn->fd) ? -errno : 0); +} diff --git a/libhw/rp2040_hwspi.c b/libhw/rp2040_hwspi.c new file mode 100644 index 0000000..b041bc9 --- /dev/null +++ b/libhw/rp2040_hwspi.c @@ -0,0 +1,123 @@ +/* libhw/rp2040_hwspi.c - `implements_spi` implementation for the RP2040's + * ARM Primecell SSP (PL022) (implementation file) + * + * Copyright (C) 2024 Luke T. Shumaker + * SPDX-Licence-Identifier: AGPL-3.0-or-later + */ + +#include + +#include /* pico-sdk:hardware_spi */ +#include /* pico-sdk:hardware_gpio */ + +#include + +#include + +static void rp2040_hwspi_readwritev(implements_spi *, const struct bidi_iovec *iov, int iovcnt); + +struct spi_vtable rp2040_hwspi_vtable = { + .readwritev = rp2040_hwspi_readwritev, +}; + +void _rp2040_hwspi_init(struct rp2040_hwspi *self, + enum rp2040_hwspi_instance inst_num, + enum spi_mode mode, + uint baudrate_hz, + uint pin_miso, + uint pin_mosi, + uint pin_clk, + uint pin_cs) { + /* Be not weary: This is but 12 lines of actual code; and many + * lines of comments and assert()s. */ + spi_inst_t *inst; + + assert(self); + assert(baudrate_hz); + assert(pin_miso != pin_mosi); + assert(pin_miso != pin_clk); + assert(pin_miso != pin_cs); + assert(pin_mosi != pin_clk); + assert(pin_mosi != pin_cs); + assert(pin_clk != pin_cs); + + /* I know we called this "hwspi", but we're actually going to + * disconnect the CS pin from the PL022 SSP and manually drive + * it from software. This is because the PL022 has a maximum + * of 16-bit frames, while we need to be able to do *at least* + * 32-bit frames (and ideally, much larger). By managing it + * ourselves, we can just keep CS pulled low extra-long, + * making the frame extra-long. */ + + /* Regarding the constraints on pin assignments: see the + * RP2040 datasheet, table 2, in §1.4.3 "GPIO Functions". */ + switch (inst_num) { + case RP2040_HWSPI_0: + inst = spi0; + assert(pin_miso == 0 || pin_miso == 4 || pin_miso == 16 || pin_miso == 20); + /*assert(pin_cs == 1 || pin_cs == 5 || pin_cs == 17 || pin_cs == 21);*/ + assert(pin_clk == 2 || pin_clk == 6 || pin_clk == 18 || pin_clk == 22); + assert(pin_mosi == 3 || pin_mosi == 7 || pin_mosi == 19 || pin_mosi == 23); + break; + case RP2040_HWSPI_1: + inst = spi1; + assert(pin_miso == 8 || pin_miso == 12 || pin_miso == 24 || pin_miso == 28); + /*assert(pin_cs == 9 || pin_cs == 13 || pin_cs == 25 || pin_cs == 29);*/ + assert(pin_clk == 10 || pin_clk == 14 || pin_clk == 26); + assert(pin_mosi == 11 || pin_mosi == 15 || pin_mosi == 27); + break; + default: + assert(false); + } + + spi_init(inst, baudrate_hz); + spi_set_format(inst, 8, + (mode & 0b10) ? SPI_CPOL_1 : SPI_CPOL_0, + (mode & 0b01) ? SPI_CPHA_1 : SPI_CPHA_0, + SPI_MSB_FIRST); + + /* Connect the pins to the PL022; set them each to "function + * 1" (again, see the RP2040 datasheet, table 2, in §1.4.3 + * "GPIO Functions"). + * + * ("GPIO_FUNC_SPI" is how the pico-sdk spells "function 1", + * since on the RP2040 all of the "function 1" functions are + * some part of SPI.) */ + gpio_set_function(pin_clk, GPIO_FUNC_SPI); + gpio_set_function(pin_mosi, GPIO_FUNC_SPI); + gpio_set_function(pin_miso, GPIO_FUNC_SPI); + + /* Initialize the CS pin for software control. */ + gpio_init(pin_cs); + gpio_set_dir(pin_cs, GPIO_OUT); + gpio_put(pin_cs, 1); + + /* Return. */ + self->vtable = &rp2040_hwspi_vtable; + self->inst = inst; + self->pin_cs = pin_cs; +} + +static void rp2040_hwspi_readwritev(implements_spi *_self, const struct bidi_iovec *iov, int iovcnt) { + struct rp2040_hwspi *self = VCALL_SELF(struct rp2040_hwspi, implements_spi, _self); + assert(self); + spi_inst_t *inst = self->inst; + + assert(inst); + assert(iov); + assert(iovcnt); + + gpio_put(self->pin_cs, 0); + /* TODO: Replace blocking reads+writes with DMA. */ + for (int i = 0; i < iovcnt; i++) { + if (iov[i].iov_write_src && iov[i].iov_read_dst) + spi_write_read_blocking(inst, iov[i].iov_write_src, iov[i].iov_read_dst, iov[i].iov_len); + else if (iov[i].iov_write_src) + spi_write_blocking(inst, iov[i].iov_write_src, iov[i].iov_len); + else if (iov[i].iov_read_dst) + spi_read_blocking(inst, 0, iov[i].iov_read_dst, iov[i].iov_len); + else + assert(false); + } + gpio_put(self->pin_cs, 1); +} diff --git a/libhw/rp2040_include/libhw/rp2040_hwspi.h b/libhw/rp2040_include/libhw/rp2040_hwspi.h new file mode 100644 index 0000000..8e44e50 --- /dev/null +++ b/libhw/rp2040_include/libhw/rp2040_hwspi.h @@ -0,0 +1,63 @@ +/* libhw/rp2040_hwspi.h - `implements_spi` implementation for the RP2040's + * ARM Primecell SSP (PL022) (header file) + * + * Copyright (C) 2024 Luke T. Shumaker + * SPDX-Licence-Identifier: AGPL-3.0-or-later + */ + +#ifndef _LIBHW_RP2040_HWSPI_H_ +#define _LIBHW_RP2040_HWSPI_H_ + +#include /* for bi_* */ + +#include + +enum rp2040_hwspi_instance { + RP2040_HWSPI_0 = 0, + RP2040_HWSPI_1 = 1, +}; + +struct rp2040_hwspi { + implements_spi; + + void /*spi_inst_t*/ *inst; + uint pin_cs; +}; + +/** + * Initialize an instance of `struct rp2040_hwspi`. + * + * @param self : struct rp2040_hwspi : the structure to initialize + * @param name : char * : a name for the SPI port; to include in the bininfo + * @param inst_num : enum rp2040_hwspi_instance : the PL220 instance number; RP2040_HWSPI_{0,1} + * @param mode : enum spi_mode : the SPI mode; SPI_MODE_{0..3} + * @param pin_miso : uint : pin number; 0, 4, 16, or 20 for _HWSPI_0; 8, 12, 24, or 28 for _HWSPI_1 + * @param pin_mosi : uint : pin number; 3, 7, 19, or 23 for _HWSPI_0; 11, 15, or 27 for _HWSPI_1 + * @param pin_clk : uint : pin number; 2, 6, 18, or 22 for _HWSPI_0; 10, 14, or 26 for _HWSPI_1 + * @param pin_cs : uint : pin number; any unused GPIO pin + * + * There is no bit-order argument; the RP2040's hardware SPI always + * uses MSB-first bit order. + */ +#define rp2040_hwspi_init(self, name, \ + inst_num, mode, baudrate_hz, \ + pin_miso, pin_mosi, pin_clk, pin_cs) \ + do { \ + bi_decl(bi_4pins_with_names(pin_miso, name" SPI MISO", \ + pin_mosi, name" SPI MOSI", \ + pin_mosi, name" SPI CLK", \ + pin_mosi, name" SPI CS")); \ + _rp2040_hwspi_init(self, \ + inst_num, mode, baudrate_hz, \ + pin_miso, pin_mosi, pin_clk, pin_cs); \ + } while(0) +void _rp2040_hwspi_init(struct rp2040_hwspi *self, + enum rp2040_hwspi_instance inst_num, + enum spi_mode mode, + uint baudrate_hz, + uint pin_miso, + uint pin_mosi, + uint pin_clk, + uint pin_cs); + +#endif /* _LIBHW_RP2040_HWSPI_H_ */ diff --git a/libhw/rp2040_include/libhw/w5500.h b/libhw/rp2040_include/libhw/w5500.h new file mode 100644 index 0000000..6b5a690 --- /dev/null +++ b/libhw/rp2040_include/libhw/w5500.h @@ -0,0 +1,102 @@ +/* libhw/w5500.h - implementation for the WIZnet W5500 chip + * + * Copyright (C) 2024 Luke T. Shumaker + * SPDX-Licence-Identifier: AGPL-3.0-or-later + */ + +#ifndef _LIBHW_W5500_H_ +#define _LIBHW_W5500_H_ + +#include +#include + +#include +#include + +struct _w5500_tcp_conn { + /* const-after-init */ + implements_net_stream_conn; + /* mutable */ + bool read_open; + bool write_open; +}; + +struct _w5500_tcp_listener { + /* const-after-init */ + implements_net_stream_listener; + uint8_t socknum; + struct _w5500_tcp_conn active_conn; + + /* mutable */ + uint16_t port; + cr_mutex_t cmd_mu; + cr_sema_t listen_sema, read_sema; +}; + +struct w5500 { + /* const-after-init */ + implements_spi *spidev; + uint pin_intr; + uint pin_reset; + struct net_eth_addr hwaddr; + + /* mutable */ + uint16_t next_local_port; + struct _w5500_tcp_listener listeners[8]; + cr_sema_t intr; +}; + +/** + * Initialize a WIZnet W5500 Ethernet-and-TCP/IP-offload chip. + * + * The W5500 has 3 lines of communication with the MCU: + * + * - An SPI-based RPC protocol: + * + mode: mode 0 or mode 3 + * + bit-order: MSB-first + * + clock frequency: 33.3MHz - 80MHz + * - An interrupt pin that it pulls low when an event happens (to let + * the MCU know that it should do an SPI RPC "get" to see what + * happened.) + * - A reset pin that the MCU can pull low to reset the W5500. + */ +#define w5500_init(self, name, spi, pin_intr, pin_reset, eth_addr) do { \ + bi_decl(bi_2pins_with_names(pin_intr, name" interrupt", \ + pin_reset, name" reset")); \ + _w5500_init(self, spi, pin_intr, pin_reset, eth_addr); \ + } while (0) +void _w5500_init(struct w5500 *self, + implements_spi *spi, uint pin_intr, uint pin_reset, + struct net_eth_addr addr); + +/** + * TODO. + */ +void w5500_hard_reset(struct w5500 *self); + +/** + * TODO. + */ +void w5500_soft_reset(struct w5500 *self); + +struct w5500_netcfg { + struct net_ip4_addr gateway_addr; + struct net_ip4_addr subnet_mask; + struct net_ip4_addr addr; +}; + +/** + * TODO. + */ +void w5500_netcfg(struct w5500 *self, struct w5500_netcfg cfg); + +implements_net_stream_listener *w5500_tcp_listen(struct w5500 *self, uint8_t socknum, + uint16_t port); + +/** + * TODO. + */ +implements_net_packet_conn *w5500_udp_conn(struct w5500 *self, uint8_t socknum, + uint16_t port); + +#endif /* _LIBHW_W5500_H_ */ diff --git a/libhw/w5500.c b/libhw/w5500.c new file mode 100644 index 0000000..70881ee --- /dev/null +++ b/libhw/w5500.c @@ -0,0 +1,527 @@ +/* libhw/w5500.c - implementation for the WIZnet W5500 chip + * + * Copyright (C) 2024 Luke T. Shumaker + * SPDX-Licence-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-Licence-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-Licence-Identifier: MIT + */ + +#include /* for sleep_ms() */ +#include /* pico-sdk:hardware_gpio */ + +#include /* for cr_yield() */ +#include /* for VCALL_SELF() */ + +#include + +#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])) + +/* mid-level utilities ********************************************************/ + +#if 0 +static uint16_t w5500_get_local_port(struct w5500 *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; +} +#endif + +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<listeners[socknum]; + + uint8_t sockintr = w5500ll_read_sock_reg(chip->spidev, socknum, interrupt); + + /* SOCKINTR_SEND_OK is useless. */ + uint8_t listen_bits = sockintr & (SOCKINTR_TIMEOUT|SOCKINTR_CONN), + read_bits = sockintr & (SOCKINTR_TIMEOUT|SOCKINTR_RECV|SOCKINTR_FIN); + + if (listen_bits) + cr_sema_signal(&listener->listen_sema); + if (read_bits) + cr_sema_signal(&listener->read_sema); + + w5500ll_write_sock_reg(chip->spidev, socknum, interrupt, sockintr); + } + } + + cr_end(); +} + +/* init() *********************************************************************/ + +static implements_net_stream_conn *w5500_tcp_accept(implements_net_stream_listener *listener); +static ssize_t w5500_tcp_read(implements_net_stream_conn *conn, void *buf, size_t count); +static ssize_t w5500_tcp_write(implements_net_stream_conn *conn, void *buf, size_t count); +static int w5500_tcp_close(implements_net_stream_conn *conn, bool rd, bool wr); + +static struct net_stream_listener_vtable w5500_tcp_listener_vtable = { + .accept = w5500_tcp_accept, +}; + +static struct net_stream_conn_vtable w5500_tcp_conn_vtable = { + .read = w5500_tcp_read, + .write = w5500_tcp_write, + .close = w5500_tcp_close, +}; + +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 */ + .spidev = spi, + .pin_intr = pin_intr, + .pin_reset = pin_reset, + .hwaddr = addr, + /* mutable */ + .next_local_port = CONFIG_W5500_LOCAL_PORT_MIN, + }; + for (uint8_t i = 0; i < 8; i++) { + chip->listeners[i] = (struct _w5500_tcp_listener){ + /* const-after-init */ + .vtable = &w5500_tcp_listener_vtable, + .socknum = i, + .active_conn = { + /* const-after-init */ + .vtable = &w5500_tcp_conn_vtable, + /* mutable */ + .read_open = false, + .write_open = false, + }, + /* mutable */ + .port = 0, + }; + } + + /* 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) { + /* TODO: Replace blocking sleep_ms() with something libcr-friendly. */ + gpio_put(chip->pin_reset, 0); + sleep_ms(1); /* minimum of 500us */ + gpio_put(chip->pin_reset, 1); + sleep_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); +} + +void w5500_netcfg(struct w5500 *chip, struct w5500_netcfg cfg) { + 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); +} + +implements_net_stream_listener *w5500_tcp_listen(struct w5500 *chip, uint8_t socknum, + uint16_t port) { + assert(chip); + assert(socknum < 8); + assert(port); + + assert(chip->listeners[socknum].port == 0); + chip->listeners[socknum].port = port; + + return &chip->listeners[socknum]; +} + +/* +implements_net_packet_conn *w5500_udp_conn(struct w5500 *chip, uint8_t socknum, + uint16_t port) { + assert(chip); + assert(socknum < 8); + assert(port); + + assert(chip->listeners[socknum].port == 0); + chip->listeners[socknum].port = port; + + return &chip->listeners[socknum]; +} +*/ + +/* tcp_listener methods *******************************************************/ + +static struct w5500 *w5500_tcp_listener_chip(struct _w5500_tcp_listener *listener) { + assert(listener); + assert(listener->socknum < 8); + + struct _w5500_tcp_listener *sock0 = &listener[-listener->socknum]; + assert(sock0); + struct w5500 *chip = + ((void *)sock0) - offsetof(struct w5500, listeners); + assert(chip); + return chip; +} + +static inline void w5500_tcp_listener_cmd(struct _w5500_tcp_listener *listener, uint8_t cmd) { + assert(listener); + struct w5500 *chip = w5500_tcp_listener_chip(listener); + uint8_t socknum = listener->socknum; + + cr_mutex_lock(&listener->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(&listener->cmd_mu); +} + +static inline void w5500_tcp_listener_cmd_close(struct _w5500_tcp_listener *listener) { + assert(listener); + struct w5500 *chip = w5500_tcp_listener_chip(listener); + uint8_t socknum = listener->socknum; + + w5500_tcp_listener_cmd(listener, CMD_CLOSE); + w5500ll_write_sock_reg(chip->spidev, socknum, interrupt, 0xFF); + while (w5500ll_read_sock_reg(chip->spidev, socknum, state) != STATE_CLOSED) + cr_yield(); + + listener->active_conn.read_open = listener->active_conn.write_open = false; +} + +#define ASSERT_LISTENER() \ + struct _w5500_tcp_listener *self = \ + VCALL_SELF(struct _w5500_tcp_listener, \ + implements_net_stream_listener, _self); \ + struct w5500 *chip = w5500_tcp_listener_chip(self); \ + uint8_t socknum = self->socknum; + +static implements_net_stream_conn *w5500_tcp_accept(implements_net_stream_listener *_self) { + ASSERT_LISTENER(); + + restart: + /* Mimics socket.c:socket(). */ + w5500_tcp_listener_cmd_close(self); + w5500ll_write_sock_reg(chip->spidev, socknum, mode, SOCKMODE_TCP); + w5500ll_write_sock_reg(chip->spidev, socknum, local_port, uint16be_marshal(self->port)); + w5500_tcp_listener_cmd(self, CMD_OPEN); + while (w5500ll_read_sock_reg(chip->spidev, socknum, state) != STATE_TCP_INIT) + cr_yield(); + + /* Mimics socket.c:listen(). */ + w5500_tcp_listener_cmd(self, 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(&self->listen_sema); + break; + case STATE_TCP_ESTABLISHED: + self->active_conn.read_open = true; + /* fall-through */ + case STATE_TCP_CLOSE_WAIT: + self->active_conn.write_open = true; + return &self->active_conn; + default: + goto restart; + } + } +} + +/* tcp_conn methods ***********************************************************/ + +static struct _w5500_tcp_listener *w5500_tcp_conn_listener(struct _w5500_tcp_conn *conn) { + assert(conn); + + struct _w5500_tcp_listener *list = + ((void *)conn) - offsetof(struct _w5500_tcp_listener, active_conn); + return list; +} + +#define ASSERT_CONN() \ + struct _w5500_tcp_conn *self = \ + VCALL_SELF(struct _w5500_tcp_conn, implements_net_stream_conn, _self); \ + struct _w5500_tcp_listener *listener = w5500_tcp_conn_listener(self); \ + struct w5500 *chip = w5500_tcp_listener_chip(listener); \ + uint8_t socknum = listener->socknum; + +static ssize_t w5500_tcp_write(implements_net_stream_conn *_self, void *buf, size_t count) { + ASSERT_CONN(); + 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_FINIAIUI, 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 (!self->write_open) + return -1; + uint8_t state = w5500ll_read_sock_reg(chip->spidev, socknum, state); + if (state != STATE_TCP_ESTABLISHED && state != STATE_TCP_CLOSE_WAIT) + return -1; + + 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. */ + 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_tcp_listener_cmd(listener, CMD_SEND); + done += freesize; + } + return done; +} + +static ssize_t w5500_tcp_read(implements_net_stream_conn *_self, void *buf, size_t count) { + ASSERT_CONN(); + assert(buf); + assert(count); + + size_t done = 0; + while (!done) { + if (!self->read_open) + return -1; + uint8_t state = w5500ll_read_sock_reg(chip->spidev, socknum, state); + switch (state) { + case STATE_TCP_CLOSE_WAIT: + return 0; /* EOF */ + case STATE_TCP_ESTABLISHED: case STATE_TCP_FIN_WAIT: + break; /* OK */ + default: + return -1; + } + + uint16_t avail = uint16be_unmarshal(w5500ll_read_sock_reg(chip->spidev, socknum, rx_size)); + if (!avail) { + cr_sema_wait(&listener->read_sema); + continue; + } + if ((size_t)avail > count) + avail = count; + uint16_t ptr = uint16be_unmarshal(w5500ll_read_sock_reg(chip->spidev, socknum, rx_read_pointer)); + w5500ll_read(chip->spidev, ptr, CTL_BLOCK_SOCK(socknum, RX), &((char *)buf)[done], avail); + w5500ll_write_sock_reg(chip->spidev, socknum, rx_read_pointer, uint16be_marshal(ptr+avail)); + + w5500_tcp_listener_cmd(listener, CMD_RECV); + done += avail; + } + return done; +} + +static int w5500_tcp_close(implements_net_stream_conn *_self, bool rd, bool wr) { + ASSERT_CONN(); + + if (rd) + self->read_open = false; + + if (wr && self->write_open) { + w5500_tcp_listener_cmd(listener, CMD_DISCON); + while (self->write_open) { + uint8_t state = w5500ll_read_sock_reg(chip->spidev, socknum, state); + switch (state) { + case STATE_TCP_FIN_WAIT: + self->write_open = false; + /* Can still read */ + if (!self->read_open) + w5500_tcp_listener_cmd_close(listener); + break; + case STATE_CLOSED: + self->write_open = false; + break; + } + } + } + + return 0; +} + +/* udp_conn methods ***********************************************************/ diff --git a/libhw/w5500_ll.h b/libhw/w5500_ll.h new file mode 100644 index 0000000..57bfccd --- /dev/null +++ b/libhw/w5500_ll.h @@ -0,0 +1,363 @@ +/* libhw/w5500_ll.h - Low-level header library for the WIZnet W5500 chip + * + * Based entirely on the W5500 datasheet, v1.1.0. + * https://docs.wiznet.io/img/products/w5500/W5500_ds_v110e.pdf + * + * Copyright (C) 2024 Luke T. Shumaker + * SPDX-Licence-Identifier: AGPL-3.0-or-later + */ + +#ifndef _LIBHW_W5500_LL_H_ +#define _LIBHW_W5500_LL_H_ + +#include /* for assert(), static_assert() */ +#include /* for uint{n}_t */ +#include /* for memcmp() */ + +#include /* for VCALL() */ +#include /* for uint16be_t */ + +#include /* for struct net_eth_addr, struct net_ip4_addr */ +#include /* for implements_spi */ + + +/* Low-level protocol built on SPI frames. ***********************************/ + +/* A u8 control byte has 3 parts: block-ID, R/W, and operating-mode. */ + +/* Part 1: Block ID. */ +#define CTL_MASK_BLOCK 0b11111000 +#define _CTL_BLOCK_RES 0b00000 +#define _CTL_BLOCK_REG 0b01000 +#define _CTL_BLOCK_TX 0b10000 +#define _CTL_BLOCK_RX 0b11000 +#define CTL_BLOCK_SOCK(n,part) (((n)<<5)|(_CTL_BLOCK_##part)) +#define CTL_BLOCK_COMMON_REG CTL_BLOCK_SOCK(0,RES) + +/* Part 2: R/W. */ +#define CTL_MASK_RW 0b100 +#define CTL_R 0b000 +#define CTL_W 0b100 + +/* Part 3: Operating mode. */ +#define CTL_MASK_OM 0b11 +#define CTL_OM_VDM 0b00 +#define CTL_OM_FDM1 0b01 +#define CTL_OM_FDM2 0b10 +#define CTL_OM_FDM4 0b11 + +/* Even though SPI is a full-duplex protocol, the W5500's spiframe on top of it is only half-duplex. + * Lame. */ + +static inline void +w5500ll_write(implements_spi *spidev, uint16_t addr, uint8_t block, void *data, size_t data_len) { + assert(spidev); + assert((block & ~CTL_MASK_BLOCK) == 0); + assert(data); + assert(data_len); + + uint8_t header[3] = { + (uint8_t)((addr >> 8) & 0xFF), + (uint8_t)(addr & 0xFF), + (block & CTL_MASK_BLOCK) | CTL_W | CTL_OM_VDM, + }; + struct bidi_iovec iov[] = { + {.iov_read_dst = NULL, .iov_write_src = header, .iov_len = sizeof(header)}, + {.iov_read_dst = NULL, .iov_write_src = data, .iov_len = data_len}, + }; + VCALL(spidev, readwritev, iov, 2); +} + +static inline void +w5500ll_read(implements_spi *spidev, uint16_t addr, uint8_t block, void *data, size_t data_len) { + assert(spidev); + assert((block & ~CTL_MASK_BLOCK) == 0); + assert(data); + assert(data_len); + + uint8_t header[3] = { + (uint8_t)((addr >> 8) & 0xFF), + (uint8_t)(addr & 0xFF), + (block & CTL_MASK_BLOCK) | CTL_R | CTL_OM_VDM, + }; + struct bidi_iovec iov[] = { + {.iov_read_dst = NULL, .iov_write_src = header, .iov_len = sizeof(header)}, + {.iov_read_dst = data, .iov_write_src = NULL, .iov_len = data_len}, + }; + VCALL(spidev, readwritev, iov, 2); +} + +/* Common chip-wide registers. ***********************************************/ + +struct w5500ll_block_common_reg { + uint8_t mode; /* MR; bitfield, see CHIPMODE_{x} below */ + struct net_ip4_addr ip_gateway_addr; /* GAR0 ... GAR3 */ + struct net_ip4_addr ip_subnet_mask; /* SUBR0 ... SUBR3 */ + struct net_eth_addr eth_addr; /* SHAR0 ... SHAR5 */ + struct net_ip4_addr ip_addr; /* SIPR0 ... SIPR3 */ + + uint16be_t intlevel; /* INTLEVEL0, INTLEVEL1; if non-zero, + * hysteresis between pin_intr being pulled + * low (hysteresis=(intlevel+1)*4/(150MHz)) */ + uint8_t chip_interrupt; /* IR; bitfield, see CHIPINTR_{x} below */ + uint8_t chip_interrupt_mask; /* IMR; bitfield, see CHIPINTR_{x} below, 0=disable, 1=enable */ + uint8_t sock_interrupt; /* SIR; bitfield of which sockets have their .interrupt set */ + uint8_t sock_interrupt_mask; /* SIMR; bitfield of sockets, 0=disable, 1=enable */ + uint16be_t retry_time; /* RTR0, RTR0; configures re-transmission period, in units of 100µs */ + uint8_t retry_count; /* RCR; configures max re-transmission count */ + + uint8_t ppp_lcp_request_timer; /* PTIMER */ + uint8_t ppp_lcp_magic_bumber; /* PMAGIC */ + struct net_eth_addr ppp_dst_eth_addr; /* PHAR0 ... PHAR5 */ + uint16be_t ppp_sess_id; /* PSID0 ... PSID1 */ + uint16be_t ppp_max_seg_size; /* PMRU0 ... PMRU1 */ + + struct net_ip4_addr unreachable_ip_addr; /* UIPR0 ... UIPR3 */ + uint16be_t unreachable_port; /* UPORTR0, UPORTR1 */ + + uint8_t phy_cfg; /* PHYCFGR */ + + uint8_t _reserved[10]; + + uint8_t chip_version; /* VERSIONR */ +}; +static_assert(sizeof(struct w5500ll_block_common_reg) == 0x3A); + +/* bitfield */ +#define CHIPMODE_RST ((uint8_t)(1<<7)) /* software reset */ +#define _CHIPMODE_UNUSED6 ((uint8_t)(1<<6)) +#define CHIPMODE_WOL ((uint8_t)(1<<5)) /* wake-on-lan */ +#define CHIPMODE_BLOCK_PING ((uint8_t)(1<<4)) +#define CHIPMODE_PPP ((uint8_t)(1<<3)) +#define _CHIPMODE_UNUSED2 ((uint8_t)(1<<2)) +#define CHIPMODE_FORCE_ARP ((uint8_t)(1<<1)) +#define _CHIPMODE_UNUSED0 ((uint8_t)(1<<0)) + +#define CHIPINTR_CONFLICT ((uint8_t)(1<<7)) /* ARP says remote IP is self */ +#define CHIPINTR_UNREACH ((uint8_t)(1<<6)) +#define CHIPINTR_PPP_CLOSE ((uint8_t)(1<<6)) +#define CHIPINTR_WOL ((uint8_t)(1<<4)) /* wake-on-LAN */ +#define _CHIPINTR_UNUSED3 ((uint8_t)(1<<3)) +#define _CHIPINTR_UNUSED2 ((uint8_t)(1<<2)) +#define _CHIPINTR_UNUSED1 ((uint8_t)(1<<1)) +#define _CHIPINTR_UNUSED0 ((uint8_t)(1<<0)) + +#define w5500ll_write_common_reg(spidev, field, val) \ + w5500ll_write_reg(spidev, \ + CTL_BLOCK_COMMON_REG, \ + struct w5500ll_block_common_reg, \ + field, val) + + +#define w5500ll_read_common_reg(spidev, field) \ + w5500ll_read_reg(spidev, \ + CTL_BLOCK_COMMON_REG, \ + struct w5500ll_block_common_reg, \ + field) + +/* Per-socket registers. *****************************************************/ + +struct w5500ll_block_sock_reg { + uint8_t mode; /* Sn_MR; see SOCKMODE_{x} below */ + uint8_t command; /* Sn_CR; see CMD_{x} below */ + uint8_t interrupt; /* Sn_IR; bitfield, see SOCKINTR_{x} below */ + uint8_t state; /* Sn_SR; see STATE_{x} below */ + uint16be_t local_port; /* Sn_PORT0, Sn_PORT1 */ + struct net_eth_addr remote_eth_addr; /* Sn_DHAR0 ... SnDHAR5 */ + struct net_ip4_addr remote_ip_addr; /* Sn_DIPR0 ... Sn_DIP3 */ + uint16be_t remote_port; /* Sn_DPORT0 ... Sn_DPORT1 */ + + uint16be_t max_seg_size; /* Sn_MSSR0, Sn_MSSR1 */ + uint8_t _reserved0[1]; + uint8_t ip_tos; /* Sn_TOS */ + uint8_t ip_ttl; /* Sn_TTL */ + uint8_t _reserved1[7]; + + uint8_t rx_buf_size; /* Sn_RXBUF_SIZE; in KiB, power of 2, <= 16 */ + uint8_t tx_buf_size; /* Sn_TXBUF_SIZE; in KiB, power of 2, <= 16 */ + uint16be_t tx_free_size; /* Sn_TX_FSR0, Sn_TX_FSR1 */ + uint16be_t tx_read_pointer; /* Sn_TX_RD0, Sn_TX_RD1 */ + uint16be_t tx_write_pointer; /* Sn_TX_WR0, Sn_TX_WR1 */ + uint16be_t rx_size; /* Sn_RX_RSR0, Sn_RX_RSR1 */ + uint16be_t rx_read_pointer; /* Sn_RX_RD0, Sn_RX_RD1 */ + uint16be_t rx_write_pointer; /* Sn_RX_WR0, Sn_RX_WR1 */ + + uint8_t interrupt_mask; /* Sn_IMR */ + uint16be_t fragment_offset; /* Sn_FRAG0, Sn_FRAG1 */ + uint8_t keepalive_timer; /* Sn_KPALVTR */ +}; +static_assert(sizeof(struct w5500ll_block_sock_reg) == 0x30); + +/* low 4 bits are the main enum, high 4 bits are flags */ +#define SOCKMODE_CLOSED ((uint8_t)0b0000) +#define SOCKMODE_TCP ((uint8_t)0b0001) +#define SOCKMODE_UDP ((uint8_t)0b0010) +#define SOCKMODE_MACRAW ((uint8_t)0b0100) + +#define SOCKMODE_FLAG_TCP_NODELAY_ACK ((uint8_t)(1<<5)) + +#define SOCKMODE_FLAG_UDP_ENABLE_MULTICAST ((uint8_t)(1<<7)) +#define SOCKMODE_FLAG_UDP_BLOCK_BROADCAST ((uint8_t)(1<<6)) +#define SOCKMODE_FLAG_UDP_MULTICAST_DOWNGRADE ((uint8_t)(1<<5)) +#define SOCKMODE_FLAG_UDP_BLOCK_UNICAST ((uint8_t)(1<<4)) + +#define SOCKMODE_FLAG_MACRAW_MAC_FILTERING ((uint8_t)(1<<7)) +#define SOCKMODE_FLAG_MACRAW_BLOCK_BROADCAST ((uint8_t)(1<<6)) +#define SOCKMODE_FLAG_MACRAW_BLOCK_MULTICAST ((uint8_t)(1<<5)) +#define SOCKMODE_FLAG_MACRAW_BLOCK_V6 ((uint8_t)(1<<4)) + +#define CMD_OPEN ((uint8_t)0x01) +#define CMD_LISTEN ((uint8_t)0x02) /* TCP-only */ +#define CMD_CONNECT ((uint8_t)0x04) /* TCP-only: dial */ +#define CMD_DISCON ((uint8_t)0x08) /* TCP-only: send FIN */ +#define CMD_CLOSE ((uint8_t)0x10) +#define CMD_SEND ((uint8_t)0x20) +#define CMD_SEND_MAC ((uint8_t)0x21) /* UDP-only: send to remote_eth_addr without doing ARP on remote_ip_addr */ +#define CMD_SEND_KEEP ((uint8_t)0x22) /* TCP-only: send a keepalive without any data */ +#define CMD_RECV ((uint8_t)0x40) + +#define _SOCKINTR_SEND_UNUSED7 ((uint8_t)1<<7) +#define _SOCKINTR_SEND_UNUSED6 ((uint8_t)1<<6) +#define _SOCKINTR_SEND_UNUSED5 ((uint8_t)1<<5) +#define SOCKINTR_SEND_OK ((uint8_t)1<<4) +#define SOCKINTR_TIMEOUT ((uint8_t)1<<3) +#define SOCKINTR_RECV ((uint8_t)1<<2) +#define SOCKINTR_FIN ((uint8_t)1<<1) +#define SOCKINTR_CONN ((uint8_t)1<<1) /* first for SYN, then when SOCKMODE_ESTABLISHED */ + +#define STATE_CLOSED ((uint8_t)0x00) + +/** + * The TCP state diagram is as follows. + * - Reading the W5500's "state" register does not distinguish between FIN_WAIT_1 and FIN_WAIT_2; + * it just has a single FIN_WAIT. + * - At any point the state can jump to "CLOSED" either by CMD_CLOSE or by a timeout. + * - Writing data is valid in ESTABLISHED and CLOSE_WAIT. + * - Reading data is valid in ESTABLISHED and FIN_WAIT. + * + * TCP state diagram, showing the flow of │ CLOSED │ ━━ role separator ┌───────┐ + * SYN, FIN, and their assocaited ACKs. └────────┘ ══ state transition │ state │ + * V ┈┈ packet flow └───────┘ + * (CMD_OPEN) ║ + * V (action/event) + * ┌────────┐ + * ┏━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━│ INIT │━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━┓ + * ┃ server ┃ └────────┘ ┃ client ┃ + * ┣━━━━━━━━━━━━━━━━┛ V ┃┃ V ┗━━━━━━━━━━━━━━━━┫ + * ┃ ╔═══════════╝ ┃┃ ╚═══════════╗ ┃ + * ┃ (CMD_LISTEN) ┃┃ (CMD_CONNECT) ┃ + * ┃ V ┌┃┃┈┈┈┈┈┈┈┈<(send SYN) ┃ + * ┃ ┌────────┐ ┊┃┃ V ┃ + * ┃ │ LISTEN │ ┊┃┃ ┌─────────┐ ┃ + * ┃ └────────┘ ┊┃┃ │ SYNSENT │ ┃ + * ┃ V ┊┃┃ └─────────┘ ┃ + * ┃ (recv SYN)<┈┈┈┈┈┈┘┃┃ V ┃ + * ┃ (send SYN+ACK)>┈┈┈┈┐┃┃ ║ ┃ + * ┃ V └┃┃┈┈┈┈┈┈>(recv SYN+ACK) ┃ + * ┃ ┌─────────┐ ┌┃┃┈┈┈┈┈┈┈┈<(send ack) ┃ + * ┃ │ SYNRECV │ ┊┃┃ ║ ┃ + * ┃ └─────────┘ ┊┃┃ ║ ┃ + * ┃ V V ┊┃┃ ║ ┃ + * ┃ ║ (recv ACK)<┈┈┈┘┃┃ ║ ┃ + * ┃ ║ ╚═════════╗ ┃┃ ╔═══════════╝ ┃ + * ┃ ╚═══╗ V ┃┃ V ┃ + * ┃ ║ ┌─────────────┐ ┃ + * ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━║━━━━━━━│ ESTABLISHED │━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ + * ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━║━━━━━━━└─────────────┘━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ + * ┃ ║ V ┃┃ V ┃ + * ┃ ╠═══════════╝ ┃┃ ╚═══════════╗ ┃ + * ┃ (CMD_DISCON) ┃┃ ║ ┃ + * ┃ Both sides sent ┌┈┈┈<(send FIN)>┈┈┈┈┈┈┐┃┃ ║ ┃ + * ┃ FIN at the "same" ┊ V └┃┃┈┈┈┈┈┈┈┈>(recv FIN) ┃ + * ┃ time; both are ┊ ┌────────────┐ ┌┃┃┈┈┈┈┈┈┈┈<(send ACK) ┃ + * ┃ active closers ┊ │ FIN_WAIT_1 │ ┊┃┃ V ┃ + * ┃ / \ ┊ └────────────┘ ┊┃┃ ┌────────────┐ ┃ + * ┃ ,------' '------, ┊ V V ┊┃┃ │ CLOSE_WAIT │ ┃ + * ┃ ╔════════════════╝ ║ ┊┃┃ └────────────┘ ┃ + * ┃ (recv FIN)<┈┈┈┈┤ ╔══╝ ┊┃┃ V ┃ + * ┃ ┌┈┈<(send ACK)>┈┈┐ ┊ ║ ┊┃┃ ║ ┃ + * ┃ ┊ ║ └┈┈┈┈┈>(recv ACK)<┈┈┈┈┈┈┘┃┃ ║ ┃ + * ┃ ┊ V ┊ V ┃┃ ║ ┃ + * ┃ ┊ ┌─────────┐ ┊ ┌────────────┐ ┃┃ ║ ┃ + * ┃ ┊ │ CLOSING │ ┊ │ FIN_WAIT_2 │ ┃┃ ║ ┃ + * ┃ ┊ └─────────┘ ┊ └────────────┘ ┃┃ (CMD_DISCON) ┃ + * ┃ ┊ V ┊ V ┌┃┃┈┈┈┈┈┈┈┈<(send FIN) ┃ + * ┃ ┊ ║ └┈┈┈>(recv FIN)<┈┈┈┈┈┈┘┃┃ ║ ┃ + * ┃ ┊ ║ ┌┈┈┈┈┈<(send ACK)>┈┈┈┈┈┈┐┃┃ V ┃ + * ┃ └┈┈>(recv ACK)<┈┈┘ ╚═╗ ┊┃┃ ┌──────────┐ ┃ + * ┃ ╚════════════════╗ ║ ┊┃┃ │ LAST_ACK │ ┃ + * ┃ V V ┊┃┃ └──────────┘ ┃ + * ┃ ┌───────────┐ ┊┃┃ V ┃ + * ┃ │ TIME_WAIT │ ┊┃┃ ║ ┃ + * ┃ └───────────┘ └┃┃┈┈┈┈┈┈┈┈>(recv ACK) ┃ + * ┃ V ┃┃ ║ ┃ + * ┣━━━━━━━━━━━━━━━━┓ (2*MSL has elapsed) ┃┃ ║ ┏━━━━━━━━━━━━━━━━┫ + * ┃ active closer ┃ ╚═══════════╗ ┃┃ ╔═══════════╝ ┃ passive closer ┃ + * ┗━━━━━━━━━━━━━━━━┻━━━━━━━━━━━━━━━━━━━━━━━━━V━┛┗━V━━━━━━━━━━━━━━━━━━━━━━━━━━┻━━━━━━━━━━━━━━━━┛ + * ┌────────┐ + * │ CLOSED │ + */ +#define STATE_TCP_INIT ((uint8_t)0x13) +#define STATE_TCP_LISTEN ((uint8_t)0x14) /* server */ +#define STATE_TCP_SYNSENT ((uint8_t)0x15) /* client; during dial */ +#define STATE_TCP_SYNRECV ((uint8_t)0x16) /* server; during accept */ +#define STATE_TCP_ESTABLISHED ((uint8_t)0x17) +#define STATE_TCP_FIN_WAIT ((uint8_t)0x18) /* during active close */ +#define STATE_TCP_CLOSING ((uint8_t)0x1a) /* during active close */ +#define STATE_TCP_TIME_WAIT ((uint8_t)0x1b) /* during active close */ +#define STATE_TCP_CLOSE_WAIT ((uint8_t)0x1c) /* during passive close */ +#define STATE_TCP_LAST_ACK ((uint8_t)0x1d) /* during passive close */ + +#define STATE_UDP ((uint8_t)0x22) + +#define STATE_MACRAW ((uint8_t)0x42) + +#define w5500ll_write_sock_reg(spidev, socknum, field, val) \ + w5500ll_write_reg(spidev, \ + CTL_BLOCK_SOCK(socknum, REG), \ + struct w5500ll_block_sock_reg, \ + field, val) + +#define w5500ll_read_sock_reg(spidev, socknum, field) \ + w5500ll_read_reg(spidev, \ + CTL_BLOCK_SOCK(socknum, REG), \ + struct w5500ll_block_sock_reg, \ + field) + +/******************************************************************************/ + +#define w5500ll_write_reg(spidev, blockid, blocktyp, field, val) do { \ + typeof((blocktyp){}.field) lval = val; \ + w5500ll_write(spidev, \ + offsetof(blocktyp, field), \ + blockid, \ + &lval, \ + sizeof(lval)); \ + } while (0) + +/* The datasheet tells us that multi-byte reads are non-atomic and + * that "it is recommended that you read all 16-bits twice or more + * until getting the same value". */ +#define w5500ll_read_reg(spidev, blockid, blocktyp, field) ({ \ + typeof((blocktyp){}.field) val; \ + w5500ll_read(spidev, \ + offsetof(blocktyp, field), \ + blockid, \ + &val, \ + sizeof(val)); \ + if (sizeof(val) > 1) \ + for (;;) { \ + typeof(val) val2; \ + w5500ll_read(spidev, \ + offsetof(blocktyp, field), \ + blockid, \ + &val2, \ + sizeof(val)); \ + if (memcmp(&val2, &val, sizeof(val)) == 0) \ + break; \ + val = val2; \ + } \ + val; \ + }) + +#endif /* _LIBHW_W5500_LL_H_ */ diff --git a/libmisc/include/libmisc/net.h b/libmisc/include/libmisc/net.h deleted file mode 100644 index 057e875..0000000 --- a/libmisc/include/libmisc/net.h +++ /dev/null @@ -1,99 +0,0 @@ -/* libmisc/net.h - Base definitions for network interfaces - * - * Copyright (C) 2024 Luke T. Shumaker - * SPDX-Licence-Identifier: AGPL-3.0-or-later - */ - -#ifndef _LIBMISC_NET_H_ -#define _LIBMISC_NET_H_ - -#include /* for bool */ -#include /* for size_t */ -#include /* for uint{n}_t} */ -#include /* for ssize_t */ - -/* Errnos *********************************************************************/ - -#define NET_EOTHER 1 -#define NET_ETIMEDOUT 2 -#define NET_ETHREAD 3 - -/* Address types **************************************************************/ - -struct net_ip4_addr { - unsigned char octets[4]; -}; - -static const struct net_ip4_addr net_ip4_addr_broadcast = {{255, 255, 255, 255}}; -static const struct net_ip4_addr net_ip4_addr_zero = {{0, 0, 0, 0}}; - -struct net_eth_addr { - unsigned char octets[6]; -}; - -/* Streams (e.g. TCP) *********************************************************/ - -struct net_stream_listener_vtable; -struct net_stream_conn_btable; - -typedef struct { - struct net_stream_listener_vtable *vtable; -} implements_net_stream_listener; - -typedef struct { - struct net_stream_conn_vtable *vtable; -} implements_net_stream_conn; - -struct net_stream_listener_vtable { - /** - * It is invalid to accept() a new connection if an existing - * connection is still open. - */ - implements_net_stream_conn *(*accept)(implements_net_stream_listener *self); -}; - -struct net_stream_conn_vtable { - /** - * Return bytes-read on success, 0 on EOF, -errno on error; a - * short read is *not* an error. - */ - ssize_t (*read)(implements_net_stream_conn *self, - void *buf, size_t count); - - /** - * Return `count` on success, -errno on error; a short write *is* an - * error. - * - * Writes are *not* guaranteed to be atomic (as this would be - * expensive to implement), so if you have concurrent writers then you - * should arrange for a mutex to protect the connection. - */ - ssize_t (*write)(implements_net_stream_conn *self, - void *buf, size_t count); - - /** - * Return 0 on success, -errno on error. - */ - int (*close)(implements_net_stream_conn *self, - bool rd, bool wr); -}; - -/* Packets (e.g. UDP) *********************************************************/ - -struct net_packet_conn_vtable; - -typedef struct { - struct net_packet_conn_vtable *vtable; -} implements_net_packet_conn; - -struct net_packet_conn_vtable { - ssize_t (*sendto )(implements_net_packet_conn *self, - void *buf, size_t len, - struct net_ip4_addr node, uint16_t port); - ssize_t (*recvfrom)(implements_net_packet_conn *self, - void *buf, size_t len, - struct net_ip4_addr *ret_node, uint16_t *ret_port); - int (*close )(implements_net_packet_conn *self); -}; - -#endif /* _LIBMISC_NET_H_ */ -- cgit v1.2.3-2-g168b