From ace33cbd04de2e2f83c5a03d283fb1733b91656d Mon Sep 17 00:00:00 2001 From: Jared Hancock Date: Sat, 25 Jan 2025 10:27:53 -0600 Subject: [PATCH] wiznet: lwip: Implement zero-copy for socket buffers. This avoids an issue for WIZnet devices: 1. In the `wiznet5k_poll` method, if the chip indicates there is data available to receive and it is received but no buffer space is available in LwIP, then the packet will be fetched from the device and discarded. Instead, now it will remain on the device until sufficient memory is available in the MCU. Additionally, it optimizes and simplifies the usage of the WIZnet device based on the assumption that the MACRAW mode will be used on exactly one socket on the device. It also sends and receives socket buffers directly between the device and LwIP PBUFs without a copy. It frees 1514 bytes of a static Ethernet frame buffer over the baseline. Signed-off-by: Jared Hancock --- drivers/wiznet/macraw.c | 141 ++++++++++++++++++++++++++++++++++++++ drivers/wiznet/macraw.h | 40 +++++++++++ drivers/wiznet/w5100.h | 74 ++++++++++++++++++++ drivers/wiznet/w5500.h | 56 +++++++++++++++ extmod/network_wiznet5k.c | 75 ++++++++------------ ports/rp2/CMakeLists.txt | 2 + 6 files changed, 342 insertions(+), 46 deletions(-) create mode 100644 drivers/wiznet/macraw.c create mode 100644 drivers/wiznet/macraw.h create mode 100644 drivers/wiznet/w5100.h create mode 100644 drivers/wiznet/w5500.h diff --git a/drivers/wiznet/macraw.c b/drivers/wiznet/macraw.c new file mode 100644 index 0000000000000..24ef415a2c895 --- /dev/null +++ b/drivers/wiznet/macraw.c @@ -0,0 +1,141 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2025 Jared Hancock + * + * 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. + */ + +// A lightweight shim on the stock WIZnet IOLibrary driver which supports +// reading and writing directly into LwIP PBUFs. It's specifically optimized +// based on the assumed usage of for a single socket on the chip configured in +// MACRAW mode. + +#if MICROPY_PY_NETWORK_WIZNET5K && MICROPY_PY_LWIP + +#include "lib/wiznet5k/Ethernet/socket.h" +#include "lwip/init.h" +#include "netif/etharp.h" + +#include "drivers/wiznet/w5100.h" +#include "drivers/wiznet/w5500.h" + +#ifndef printf +#include "py/runtime.h" +#define printf(...) mp_printf(MP_PYTHON_PRINTER, __VA_ARGS__) +#endif + +static bool send_is_pending = false; + +void wiznet_macraw_reset(uint8_t sn) { + send_is_pending = false; + + uint8_t addr[4] = {1, 1, 1, 1}; + setSn_DIPR(sn, addr); + setSn_DPORT(sn, 1111); +} + +int32_t wiznet_macraw_send(struct pbuf *head, uint8_t sn) { + if ((getSn_MR(sn) & 0x0F) != Sn_MR_MACRAW) { + return SOCKERR_SOCKMODE; + } + if (getSn_SR(sn) != SOCK_MACRAW) { + return SOCKERR_SOCKSTATUS; + } + + while (getSn_TX_FSR(sn) < head->tot_len) { + WIZCHIP_YIELD(); + } + + // According to the datasheet, "HOST must execute next SEND Command after + // Sn_IR [SENDOK] is set to ‘1’". Await previous send to complete. Then + // clear the interrupt bit. There's a potential for a threading/multicore + // lockup here if this is interrupted and another packet is sent AND the + // flag is cleared BEFORE returning back to this context. Therefore, we use + // a variable local to the MCU to indicate we should expect the SENDOK flag + // to show up on the WIZnet in addition to waiting for the flag. + while (send_is_pending && (getSn_IR(sn) & Sn_IR_SENDOK) == 0); + send_is_pending = false; + setSn_IR(sn, Sn_IR_SENDOK); + + // Actually send the PBUF to the device. This varies slightly from device to + // device and so is implemented in a breakout. + int32_t sent = wiznet_macraw_send_pbuf(head, sn); + + // Commit and send written data + setSn_CR(sn, Sn_CR_SEND); + while(getSn_CR(sn)); + + // Wait for this send to complete before sending more data + send_is_pending = true; + return sent; +} + +int32_t wiznet_macraw_recv(struct pbuf **pbuf, uint8_t sn) { + // Ensure that for early exits, it's indicated that the pbuf is invalid + *pbuf = NULL; + + if ((getSn_MR(sn) & 0x0F) != Sn_MR_MACRAW) { + return SOCKERR_SOCKMODE; + } + + // Fetch the size of the next packet, but don't commit the read in case + // there isn't sufficient memory for a receive buffer. + uint8_t header[2]; + wiz_recv_data(sn, header, 2); + + uint16_t size = (header[0] << 8) + header[1] - 2; + + if (size > 1514) { + printf("!!! Invalid receive size of %d bytes\n", size); + WIZCHIP_EXPORT(close)(sn); + return SOCKFATAL_PACKLEN; + } + + // Before consuming the packet size field from the device attempt to + // allocate a pbuf + struct pbuf *head = pbuf_alloc(PBUF_RAW, size, PBUF_POOL); + if (head == NULL) { + return SOCKERR_BUFFER; + } + + // Proceed to fetch the data. Commit the read of the header. After this we + // must read `size` bytes or the state of the data in the chip will be + // wrong. + setSn_CR(sn, Sn_CR_RECV); + while (getSn_CR(sn)); + + struct pbuf *p = head; + uint16_t to_copy = size; + while (to_copy) { + wiz_recv_data(sn, p->payload, MIN(to_copy, p->len)); + setSn_CR(sn, Sn_CR_RECV); + while (getSn_CR(sn)); + + to_copy -= p->len; + p = p->next; + } + + *pbuf = head; + return (int32_t) size; +} + +#endif \ No newline at end of file diff --git a/drivers/wiznet/macraw.h b/drivers/wiznet/macraw.h new file mode 100644 index 0000000000000..1943e7ed76b35 --- /dev/null +++ b/drivers/wiznet/macraw.h @@ -0,0 +1,40 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2025 Jared Hancock + * + * 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. + */ + +// A lightweight shim on the stock WIZnet IOLibrary driver which supports +// reading and writing directly into LwIP PBUFs. It's specifically optimized +// based on the assumed usage of for a single socket on the chip configured in +// MACRAW mode. + +#if MICROPY_PY_NETWORK_WIZNET5K && MICROPY_PY_LWIP + +#include + +int32_t wiznet_macraw_send(struct pbuf *head, uint8_t sn); +int32_t wiznet_macraw_recv(struct pbuf **head, uint8_t sn); +void wiznet_macraw_reset(uint8_t sn); + +#endif \ No newline at end of file diff --git a/drivers/wiznet/w5100.h b/drivers/wiznet/w5100.h new file mode 100644 index 0000000000000..0c3d1cbcc72ae --- /dev/null +++ b/drivers/wiznet/w5100.h @@ -0,0 +1,74 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2025 Jared Hancock + * + * 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. + */ + +#include +#include "py/runtime.h" + +#if MICROPY_PY_NETWORK_WIZNET5K && MICROPY_PY_LWIP +#if _WIZCHIP_ == W5100 || _WIZCHIP_ == W5100S + +#include "lib/wiznet5k/Ethernet/socket.h" +#include "netif/etharp.h" + +static inline int32_t wiznet_macraw_send_pbuf(struct pbuf * head, const uint8_t sn) { + const uint16_t txbase = getSn_TxBASE(sn); + const uint16_t txmax = getSn_TxMAX(sn); + const uint16_t txmask = txmax - 1; + + uint16_t ptr; + uint16_t dst_mask; + uint16_t dst_ptr; + uint16_t offset; + + ptr = getSn_TX_WR(sn); + + size_t left_to_send = head->tot_len; + struct pbuf *p = head; + while (p != NULL && left_to_send > 0) { + dst_mask = ptr & txmask; + dst_ptr = txbase + dst_mask; + + if (dst_mask + p->len > txmax) { + // Packet will wrap over the end of the chip buffer. Send to the end + // of the chip buffer and manually wrap around. + offset = txmax - dst_mask; + WIZCHIP_WRITE_BUF(dst_ptr, p->payload, offset); + dst_ptr = txbase; + } else { + offset = 0; + } + WIZCHIP_WRITE_BUF(dst_ptr, p->payload + offset, p->len - offset); + ptr += p->len; + left_to_send -= p->len; + p = p->next; + } + + setSn_TX_WR(0, ptr); + return head->tot_len; +} + +#endif +#endif diff --git a/drivers/wiznet/w5500.h b/drivers/wiznet/w5500.h new file mode 100644 index 0000000000000..9a4404b52b8b0 --- /dev/null +++ b/drivers/wiznet/w5500.h @@ -0,0 +1,56 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2025 Jared Hancock + * + * 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. + */ + +#include +#include "py/runtime.h" + +#if MICROPY_PY_NETWORK_WIZNET5K && MICROPY_PY_LWIP +#if _WIZCHIP_ == W5500 + +#include "lib/wiznet5k/Ethernet/socket.h" +#include "netif/etharp.h" + +static inline int32_t wiznet_macraw_send_pbuf(struct pbuf * head, const uint8_t sn) { + const uint32_t txbuf_block = WIZCHIP_TXBUF_BLOCK(0) << 3; + uint16_t ptr = getSn_TX_WR(sn); + uint32_t addrsel = 0; + + size_t left_to_send = head->tot_len; + struct pbuf *p = head; + while (p != NULL && left_to_send > 0) { + addrsel = ((uint32_t)ptr << 8) + txbuf_block; + WIZCHIP_WRITE_BUF(addrsel, p->payload, p->len); + + ptr += p->len; + left_to_send -= p->len; + p = p->next; + } + + setSn_TX_WR(sn, ptr); +} + +#endif +#endif \ No newline at end of file diff --git a/extmod/network_wiznet5k.c b/extmod/network_wiznet5k.c index 533d39a18719f..d8ee2d95acab3 100644 --- a/extmod/network_wiznet5k.c +++ b/extmod/network_wiznet5k.c @@ -64,6 +64,7 @@ #include "lwip/dhcp.h" #include "lwip/ethip6.h" #include "netif/etharp.h" +#include "drivers/wiznet/macraw.h" #define TRACE_ETH_TX (0x0002) #define TRACE_ETH_RX (0x0004) @@ -104,7 +105,6 @@ typedef struct _wiznet5k_obj_t { #if WIZNET5K_WITH_LWIP_STACK mp_hal_pin_obj_t pin_intn; bool use_interrupt; - uint8_t eth_frame[1514]; uint32_t trace_flags; struct netif netif; struct dhcp dhcp_struct; @@ -250,46 +250,21 @@ static void wiznet5k_init(void) { mod_network_register_nic(&wiznet5k_obj); } -static void wiznet5k_send_ethernet(wiznet5k_obj_t *self, size_t len, const uint8_t *buf) { - uint8_t ip[4] = {1, 1, 1, 1}; // dummy - int ret = WIZCHIP_EXPORT(sendto)(0, (byte *)buf, len, ip, 11); // dummy port - if (ret != len) { - printf("wiznet5k_send_ethernet: fatal error %d\n", ret); - netif_set_link_down(&self->netif); - netif_set_down(&self->netif); - } -} - -// Stores the frame in self->eth_frame and returns number of bytes in the frame, 0 for no frame -static uint16_t wiznet5k_recv_ethernet(wiznet5k_obj_t *self) { - uint16_t len = getSn_RX_RSR(0); - if (len == 0) { - return 0; - } - - byte ip[4]; - uint16_t port; - int ret = WIZCHIP_EXPORT(recvfrom)(0, self->eth_frame, 1514, ip, &port); - if (ret <= 0) { - printf("wiznet5k_recv_ethernet: fatal error len=%u ret=%d\n", len, ret); - netif_set_link_down(&self->netif); - netif_set_down(&self->netif); - return 0; - } - - return ret; -} - /*******************************************************************************/ // Wiznet5k lwIP interface static err_t wiznet5k_netif_output(struct netif *netif, struct pbuf *p) { wiznet5k_obj_t *self = netif->state; - pbuf_copy_partial(p, self->eth_frame, p->tot_len, 0); if (self->trace_flags & TRACE_ETH_TX) { - netutils_ethernet_trace(MP_PYTHON_PRINTER, p->tot_len, self->eth_frame, NETUTILS_TRACE_IS_TX | NETUTILS_TRACE_NEWLINE); + netutils_ethernet_trace(MP_PYTHON_PRINTER, p->len, p->payload, NETUTILS_TRACE_IS_TX | NETUTILS_TRACE_NEWLINE); + } + int ret = wiznet_macraw_send(p, 0); + if (ret != p->tot_len) { + printf("wiznet5k_netif_output: fatal error %d\n", ret); + netif_set_link_down(netif); + netif_set_down(netif); + return ERR_IF; } - wiznet5k_send_ethernet(self, p->tot_len, self->eth_frame); return ERR_OK; } @@ -305,6 +280,7 @@ static err_t wiznet5k_netif_init(struct netif *netif) { printf("WIZNET fatal error in netif_init: %d\n", ret); return ERR_IF; } + wiznet_macraw_reset(0); // Enable MAC filtering so we only get frames destined for us, to reduce load on lwIP setSn_MR(0, getSn_MR(0) | Sn_MR_MFEN); @@ -349,17 +325,21 @@ static void wiznet5k_lwip_init(wiznet5k_obj_t *self) { void wiznet5k_poll(void) { wiznet5k_obj_t *self = &wiznet5k_obj; if ((self->netif.flags & (NETIF_FLAG_UP | NETIF_FLAG_LINK_UP)) == (NETIF_FLAG_UP | NETIF_FLAG_LINK_UP)) { - uint16_t len; - while ((len = wiznet5k_recv_ethernet(self)) > 0) { - if (self->trace_flags & TRACE_ETH_RX) { - netutils_ethernet_trace(MP_PYTHON_PRINTER, len, self->eth_frame, NETUTILS_TRACE_NEWLINE); - } - struct pbuf *p = pbuf_alloc(PBUF_RAW, len, PBUF_POOL); - if (p != NULL) { - pbuf_take(p, self->eth_frame, len); - if (self->netif.input(p, &self->netif) != ERR_OK) { - pbuf_free(p); + uint16_t ready; + while ((ready = getSn_RX_RSR(0)) > 0) { + struct pbuf *packet = NULL; + int32_t status = wiznet_macraw_recv(&packet, 0); + if (packet != NULL) { + if (self->trace_flags & TRACE_ETH_RX) { + netutils_ethernet_trace(MP_PYTHON_PRINTER, packet->len, packet->payload, NETUTILS_TRACE_NEWLINE); } + if (status < 0 || self->netif.input(packet, &self->netif) != ERR_OK) { + pbuf_free(packet); + } + } else { + // No pbuf memory available. Will need to process this later + // when there's available buffer space. + break; } } } @@ -949,10 +929,13 @@ static mp_obj_t network_wiznet5k_ipconfig(size_t n_args, const mp_obj_t *args, m static MP_DEFINE_CONST_FUN_OBJ_KW(wiznet5k_ipconfig_obj, 1, network_wiznet5k_ipconfig); static mp_obj_t send_ethernet_wrapper(mp_obj_t self_in, mp_obj_t buf_in) { - wiznet5k_obj_t *self = MP_OBJ_TO_PTR(self_in); + (void) self_in; mp_buffer_info_t buf; mp_get_buffer_raise(buf_in, &buf, MP_BUFFER_READ); - wiznet5k_send_ethernet(self, buf.len, buf.buf); + struct pbuf *pbuf = pbuf_alloc_reference(buf.buf, buf.len, PBUF_REF); + if (pbuf != NULL) { + wiznet_macraw_send(pbuf, 0); + } return mp_const_none; } static MP_DEFINE_CONST_FUN_OBJ_2(send_ethernet_obj, send_ethernet_wrapper); diff --git a/ports/rp2/CMakeLists.txt b/ports/rp2/CMakeLists.txt index 1a5029c150417..65b24dbb29798 100644 --- a/ports/rp2/CMakeLists.txt +++ b/ports/rp2/CMakeLists.txt @@ -456,6 +456,7 @@ if (MICROPY_PY_NETWORK_WIZNET5K) target_include_directories(${MICROPY_TARGET} PRIVATE ${MICROPY_DIR}/lib/wiznet5k/ ${MICROPY_DIR}/lib/wiznet5k/Ethernet/ + ${MICROPY_DIR}/drivers/wiznet ) list(APPEND MICROPY_SOURCE_LIB @@ -468,6 +469,7 @@ if (MICROPY_PY_NETWORK_WIZNET5K) ${MICROPY_DIR}/lib/wiznet5k/Ethernet/wizchip_conf.c ${MICROPY_DIR}/lib/wiznet5k/Internet/DNS/dns.c ${MICROPY_DIR}/lib/wiznet5k/Internet/DHCP/dhcp.c + ${MICROPY_DIR}/drivers/wiznet/macraw.c ) endif() pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy