diff --git a/extmod/modlwip.c b/extmod/modlwip.c index bd952111d656a..83104a487f6f1 100644 --- a/extmod/modlwip.c +++ b/extmod/modlwip.c @@ -447,9 +447,12 @@ STATIC mp_uint_t lwip_udp_send(lwip_socket_obj_t *socket, const byte *buf, mp_ui len = 0xffff; } + MICROPY_PY_LWIP_ENTER + // FIXME: maybe PBUF_ROM? struct pbuf *p = pbuf_alloc(PBUF_TRANSPORT, len, PBUF_RAM); if (p == NULL) { + MICROPY_PY_LWIP_EXIT *_errno = MP_ENOMEM; return -1; } @@ -467,6 +470,8 @@ STATIC mp_uint_t lwip_udp_send(lwip_socket_obj_t *socket, const byte *buf, mp_ui pbuf_free(p); + MICROPY_PY_LWIP_EXIT + // udp_sendto can return 1 on occasion for ESP8266 port. It's not known why // but it seems that the send actually goes through without error in this case. // So we treat such cases as a success until further investigation. @@ -505,10 +510,14 @@ STATIC mp_uint_t lwip_udp_receive(lwip_socket_obj_t *socket, byte *buf, mp_uint_ struct pbuf *p = socket->incoming.pbuf; + MICROPY_PY_LWIP_ENTER + u16_t result = pbuf_copy_partial(p, buf, ((p->tot_len > len) ? len : p->tot_len), 0); pbuf_free(p); socket->incoming.pbuf = NULL; + MICROPY_PY_LWIP_EXIT + return (mp_uint_t) result; } @@ -526,11 +535,14 @@ STATIC mp_uint_t lwip_tcp_send(lwip_socket_obj_t *socket, const byte *buf, mp_ui // Check for any pending errors STREAM_ERROR_CHECK(socket); + MICROPY_PY_LWIP_ENTER + u16_t available = tcp_sndbuf(socket->pcb.tcp); if (available == 0) { // Non-blocking socket if (socket->timeout == 0) { + MICROPY_PY_LWIP_EXIT *_errno = MP_EAGAIN; return MP_STREAM_ERROR; } @@ -543,11 +555,13 @@ STATIC mp_uint_t lwip_tcp_send(lwip_socket_obj_t *socket, const byte *buf, mp_ui // reset) by error callback. // Avoid sending too small packets, so wait until at least 16 bytes available while (socket->state >= STATE_CONNECTED && (available = tcp_sndbuf(socket->pcb.tcp)) < 16) { + MICROPY_PY_LWIP_EXIT if (socket->timeout != -1 && mp_hal_ticks_ms() - start > socket->timeout) { *_errno = MP_ETIMEDOUT; return MP_STREAM_ERROR; } poll_sockets(); + MICROPY_PY_LWIP_REENTER } // While we waited, something could happen @@ -563,6 +577,8 @@ STATIC mp_uint_t lwip_tcp_send(lwip_socket_obj_t *socket, const byte *buf, mp_ui err = tcp_output(socket->pcb.tcp); } + MICROPY_PY_LWIP_EXIT + if (err != ERR_OK) { *_errno = error_lookup_table[-err]; return MP_STREAM_ERROR; @@ -608,6 +624,8 @@ STATIC mp_uint_t lwip_tcp_receive(lwip_socket_obj_t *socket, byte *buf, mp_uint_ } } + MICROPY_PY_LWIP_ENTER + assert(socket->pcb.tcp != NULL); struct pbuf *p = socket->incoming.pbuf; @@ -633,6 +651,8 @@ STATIC mp_uint_t lwip_tcp_receive(lwip_socket_obj_t *socket, byte *buf, mp_uint_ } tcp_recved(socket->pcb.tcp, len); + MICROPY_PY_LWIP_EXIT + return len; } @@ -865,16 +885,21 @@ STATIC mp_obj_t lwip_socket_connect(mp_obj_t self_in, mp_obj_t addr_in) { mp_raise_OSError(MP_EALREADY); } } + // Register our receive callback. + MICROPY_PY_LWIP_ENTER tcp_recv(socket->pcb.tcp, _lwip_tcp_recv); socket->state = STATE_CONNECTING; err = tcp_connect(socket->pcb.tcp, &dest, port, _lwip_tcp_connected); if (err != ERR_OK) { + MICROPY_PY_LWIP_EXIT socket->state = STATE_NEW; mp_raise_OSError(error_lookup_table[-err]); } socket->peer_port = (mp_uint_t)port; memcpy(socket->peer, &dest, sizeof(socket->peer)); + MICROPY_PY_LWIP_EXIT + // And now we wait... if (socket->timeout != -1) { for (mp_uint_t retries = socket->timeout / 100; retries--;) { @@ -1209,6 +1234,8 @@ STATIC mp_uint_t lwip_socket_ioctl(mp_obj_t self_in, mp_uint_t request, uintptr_ lwip_socket_obj_t *socket = MP_OBJ_TO_PTR(self_in); mp_uint_t ret; + MICROPY_PY_LWIP_ENTER + if (request == MP_STREAM_POLL) { uintptr_t flags = arg; ret = 0; @@ -1259,6 +1286,7 @@ STATIC mp_uint_t lwip_socket_ioctl(mp_obj_t self_in, mp_uint_t request, uintptr_ bool socket_is_listener = false; if (socket->pcb.tcp == NULL) { + MICROPY_PY_LWIP_EXIT return 0; } @@ -1305,6 +1333,8 @@ STATIC mp_uint_t lwip_socket_ioctl(mp_obj_t self_in, mp_uint_t request, uintptr_ ret = MP_STREAM_ERROR; } + MICROPY_PY_LWIP_EXIT + return ret; } @@ -1452,7 +1482,10 @@ STATIC mp_obj_t lwip_getaddrinfo(size_t n_args, const mp_obj_t *args) { getaddrinfo_state_t state; state.status = 0; + MICROPY_PY_LWIP_ENTER err_t ret = dns_gethostbyname(host, (ip_addr_t*)&state.ipaddr, lwip_getaddrinfo_cb, &state); + MICROPY_PY_LWIP_EXIT + switch (ret) { case ERR_OK: // cached diff --git a/lib/netutils/netutils.h b/lib/netutils/netutils.h index 4befc90db0e91..9261337858963 100644 --- a/lib/netutils/netutils.h +++ b/lib/netutils/netutils.h @@ -29,6 +29,10 @@ #define NETUTILS_IPV4ADDR_BUFSIZE 4 +#define NETUTILS_TRACE_IS_TX (0x0001) +#define NETUTILS_TRACE_PAYLOAD (0x0002) +#define NETUTILS_TRACE_NEWLINE (0x0004) + typedef enum _netutils_endian_t { NETUTILS_LITTLE, NETUTILS_BIG, @@ -47,4 +51,6 @@ void netutils_parse_ipv4_addr(mp_obj_t addr_in, uint8_t *out_ip, netutils_endian // puts IP in out_ip (which must take at least IPADDR_BUF_SIZE bytes). mp_uint_t netutils_parse_inet_addr(mp_obj_t addr_in, uint8_t *out_ip, netutils_endian_t endian); +void netutils_ethernet_trace(const mp_print_t *print, size_t len, const uint8_t *buf, unsigned int flags); + #endif // MICROPY_INCLUDED_LIB_NETUTILS_NETUTILS_H diff --git a/lib/netutils/trace.c b/lib/netutils/trace.c new file mode 100644 index 0000000000000..7c79713b32532 --- /dev/null +++ b/lib/netutils/trace.c @@ -0,0 +1,150 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2019 Damien P. George + * + * 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 "py/mphal.h" +#include "lib/netutils/netutils.h" + +static uint32_t get_be16(const uint8_t *buf) { + return buf[0] << 8 | buf[1]; +} + +static uint32_t get_be32(const uint8_t *buf) { + return buf[0] << 24 | buf[1] << 16 | buf[2] << 8 | buf[3]; +} + +static void dump_hex_bytes(const mp_print_t *print, size_t len, const uint8_t *buf) { + for (size_t i = 0; i < len; ++i) { + mp_printf(print, " %02x", buf[i]); + } +} + +static const char *ethertype_str(uint16_t type) { + // A value between 0x0000 - 0x05dc (inclusive) indicates a length, not type + switch (type) { + case 0x0800: return "IPv4"; + case 0x0806: return "ARP"; + case 0x86dd: return "IPv6"; + default: return NULL; + } +} + +void netutils_ethernet_trace(const mp_print_t *print, size_t len, const uint8_t *buf, unsigned int flags) { + mp_printf(print, "[% 8d] ETH%cX len=%u", mp_hal_ticks_ms(), flags & NETUTILS_TRACE_IS_TX ? 'T' : 'R', len); + mp_printf(print, " dst=%02x:%02x:%02x:%02x:%02x:%02x", buf[0], buf[1], buf[2], buf[3], buf[4], buf[5]); + mp_printf(print, " src=%02x:%02x:%02x:%02x:%02x:%02x", buf[6], buf[7], buf[8], buf[9], buf[10], buf[11]); + + const char *ethertype = ethertype_str(buf[12] << 8 | buf[13]); + if (ethertype) { + mp_printf(print, " type=%s", ethertype); + } else { + mp_printf(print, " type=0x%04x", buf[12] << 8 | buf[13]); + } + if (len > 14) { + len -= 14; + buf += 14; + if (buf[-2] == 0x08 && buf[-1] == 0x00 && buf[0] == 0x45) { + // IPv4 packet + len = get_be16(buf + 2); + mp_printf(print, " srcip=%u.%u.%u.%u dstip=%u.%u.%u.%u", + buf[12], buf[13], buf[14], buf[15], + buf[16], buf[17], buf[18], buf[19]); + uint8_t prot = buf[9]; + buf += 20; + len -= 20; + if (prot == 6) { + // TCP packet + uint16_t srcport = get_be16(buf); + uint16_t dstport = get_be16(buf + 2); + uint32_t seqnum = get_be32(buf + 4); + uint32_t acknum = get_be32(buf + 8); + uint16_t dataoff_flags = get_be16(buf + 12); + uint16_t winsz = get_be16(buf + 14); + mp_printf(print, " TCP srcport=%u dstport=%u seqnum=%u acknum=%u dataoff=%u flags=%x winsz=%u", + srcport, dstport, (unsigned)seqnum, (unsigned)acknum, dataoff_flags >> 12, dataoff_flags & 0x1ff, winsz); + buf += 20; + len -= 20; + if (dataoff_flags >> 12 > 5) { + mp_printf(print, " opts="); + size_t opts_len = ((dataoff_flags >> 12) - 5) * 4; + dump_hex_bytes(print, opts_len, buf); + buf += opts_len; + len -= opts_len; + } + } else if (prot == 17) { + // UDP packet + uint16_t srcport = get_be16(buf); + uint16_t dstport = get_be16(buf + 2); + mp_printf(print, " UDP srcport=%u dstport=%u", srcport, dstport); + len = get_be16(buf + 4); + buf += 8; + if ((srcport == 67 && dstport == 68) || (srcport == 68 && dstport == 67)) { + // DHCP + if (srcport == 67) { + mp_printf(print, " DHCPS"); + } else { + mp_printf(print, " DHCPC"); + } + dump_hex_bytes(print, 12 + 16 + 16 + 64, buf); + size_t n = 12 + 16 + 16 + 64 + 128; + len -= n; + buf += n; + mp_printf(print, " opts:"); + switch (buf[6]) { + case 1: mp_printf(print, " DISCOVER"); break; + case 2: mp_printf(print, " OFFER"); break; + case 3: mp_printf(print, " REQUEST"); break; + case 4: mp_printf(print, " DECLINE"); break; + case 5: mp_printf(print, " ACK"); break; + case 6: mp_printf(print, " NACK"); break; + case 7: mp_printf(print, " RELEASE"); break; + case 8: mp_printf(print, " INFORM"); break; + } + } + } else { + // Non-UDP packet + mp_printf(print, " prot=%u", prot); + } + } else if (buf[-2] == 0x86 && buf[-1] == 0xdd && (buf[0] >> 4) == 6) { + // IPv6 packet + uint32_t h = get_be32(buf); + uint16_t l = get_be16(buf + 4); + mp_printf(print, " tclass=%u flow=%u len=%u nexthdr=%u hoplimit=%u", (unsigned)((h >> 20) & 0xff), (unsigned)(h & 0xfffff), l, buf[6], buf[7]); + mp_printf(print, " srcip="); + dump_hex_bytes(print, 16, buf + 8); + mp_printf(print, " dstip="); + dump_hex_bytes(print, 16, buf + 24); + buf += 40; + len -= 40; + } + if (flags & NETUTILS_TRACE_PAYLOAD) { + mp_printf(print, " data="); + dump_hex_bytes(print, len, buf); + } + } + if (flags & NETUTILS_TRACE_NEWLINE) { + mp_printf(print, "\n"); + } +} diff --git a/ports/stm32/Makefile b/ports/stm32/Makefile index 20a26f23443f3..1a75bf0504a1d 100644 --- a/ports/stm32/Makefile +++ b/ports/stm32/Makefile @@ -110,6 +110,7 @@ SRC_LIB = $(addprefix lib/,\ oofatfs/option/unicode.c \ mp-readline/readline.c \ netutils/netutils.c \ + netutils/trace.c \ timeutils/timeutils.c \ utils/pyexec.c \ utils/interrupt_char.c \ @@ -240,6 +241,7 @@ SRC_C = \ can.c \ usb.c \ wdt.c \ + eth.c \ gccollect.c \ help.c \ machine_i2c.c \ @@ -251,6 +253,7 @@ SRC_C = \ moduos.c \ modutime.c \ modusocket.c \ + network_lan.c \ modnetwork.c \ extint.c \ usrsw.c \ diff --git a/ports/stm32/boards/NUCLEO_F429ZI/mpconfigboard.h b/ports/stm32/boards/NUCLEO_F429ZI/mpconfigboard.h index 17883a19202d7..3d587435563a7 100644 --- a/ports/stm32/boards/NUCLEO_F429ZI/mpconfigboard.h +++ b/ports/stm32/boards/NUCLEO_F429ZI/mpconfigboard.h @@ -1,6 +1,8 @@ #define MICROPY_HW_BOARD_NAME "NUCLEO-F429ZI" #define MICROPY_HW_MCU_NAME "STM32F429" +#define MICROPY_PY_LWIP (1) + #define MICROPY_HW_HAS_SWITCH (1) #define MICROPY_HW_HAS_FLASH (1) #define MICROPY_HW_ENABLE_RNG (1) @@ -81,3 +83,14 @@ #define MICROPY_HW_USB_FS (1) #define MICROPY_HW_USB_VBUS_DETECT_PIN (pin_A9) #define MICROPY_HW_USB_OTG_ID_PIN (pin_A10) + +// Ethernet via RMII +#define MICROPY_HW_ETH_MDC (pin_C1) +#define MICROPY_HW_ETH_MDIO (pin_A2) +#define MICROPY_HW_ETH_RMII_REF_CLK (pin_A1) +#define MICROPY_HW_ETH_RMII_CRS_DV (pin_A7) +#define MICROPY_HW_ETH_RMII_RXD0 (pin_C4) +#define MICROPY_HW_ETH_RMII_RXD1 (pin_C5) +#define MICROPY_HW_ETH_RMII_TX_EN (pin_G11) +#define MICROPY_HW_ETH_RMII_TXD0 (pin_G13) +#define MICROPY_HW_ETH_RMII_TXD1 (pin_B13) diff --git a/ports/stm32/boards/NUCLEO_F429ZI/pins.csv b/ports/stm32/boards/NUCLEO_F429ZI/pins.csv index 8a892d3c2f298..c8fe3355d3f3c 100644 --- a/ports/stm32/boards/NUCLEO_F429ZI/pins.csv +++ b/ports/stm32/boards/NUCLEO_F429ZI/pins.csv @@ -115,3 +115,12 @@ PG2,PG2 SW,PA0 LED_GREEN,PG13 LED_RED,PG14 +ETH_MDC,PC1 +ETH_MDIO,PA2 +ETH_RMII_REF_CLK,PA1 +ETH_RMII_CRS_DV,PA7 +ETH_RMII_RXD0,PC4 +ETH_RMII_RXD1,PC5 +ETH_RMII_TX_EN,PG11 +ETH_RMII_TXD0,PG13 +ETH_RMII_TXD1,PB13 diff --git a/ports/stm32/boards/NUCLEO_F767ZI/mpconfigboard.h b/ports/stm32/boards/NUCLEO_F767ZI/mpconfigboard.h index 3f23d77d48163..e3f255de4d43a 100644 --- a/ports/stm32/boards/NUCLEO_F767ZI/mpconfigboard.h +++ b/ports/stm32/boards/NUCLEO_F767ZI/mpconfigboard.h @@ -6,6 +6,8 @@ #define MICROPY_HW_BOARD_NAME "NUCLEO-F767ZI" #define MICROPY_HW_MCU_NAME "STM32F767" +#define MICROPY_PY_LWIP (1) + #define MICROPY_HW_HAS_SWITCH (1) #define MICROPY_HW_HAS_FLASH (1) #define MICROPY_HW_ENABLE_RNG (1) @@ -16,20 +18,12 @@ #define MICROPY_BOARD_EARLY_INIT NUCLEO_F767ZI_board_early_init void NUCLEO_F767ZI_board_early_init(void); -// HSE is 25MHz -// VCOClock = HSE * PLLN / PLLM = 25 MHz * 432 / 25 = 432 MHz -// SYSCLK = VCOClock / PLLP = 432 MHz / 2 = 216 MHz -// USB/SDMMC/RNG Clock = VCOClock / PLLQ = 432 MHz / 9 = 48 MHz +// HSE is 8MHz #define MICROPY_HW_CLK_PLLM (4) #define MICROPY_HW_CLK_PLLN (216) #define MICROPY_HW_CLK_PLLP (RCC_PLLP_DIV2) #define MICROPY_HW_CLK_PLLQ (9) - -// From the reference manual, for 2.7V to 3.6V -// 151-180 MHz => 5 wait states -// 181-210 MHz => 6 wait states -// 211-216 MHz => 7 wait states -#define MICROPY_HW_FLASH_LATENCY FLASH_LATENCY_7 // 210-216 MHz needs 7 wait states +#define MICROPY_HW_FLASH_LATENCY (FLASH_LATENCY_7) // 210-216 MHz needs 7 wait states // UART config #define MICROPY_HW_UART2_TX (pin_D5) @@ -78,3 +72,14 @@ void NUCLEO_F767ZI_board_early_init(void); #define MICROPY_HW_USB_FS (1) #define MICROPY_HW_USB_VBUS_DETECT_PIN (pin_A9) #define MICROPY_HW_USB_OTG_ID_PIN (pin_A10) + +// Ethernet via RMII +#define MICROPY_HW_ETH_MDC (pin_C1) +#define MICROPY_HW_ETH_MDIO (pin_A2) +#define MICROPY_HW_ETH_RMII_REF_CLK (pin_A1) +#define MICROPY_HW_ETH_RMII_CRS_DV (pin_A7) +#define MICROPY_HW_ETH_RMII_RXD0 (pin_C4) +#define MICROPY_HW_ETH_RMII_RXD1 (pin_C5) +#define MICROPY_HW_ETH_RMII_TX_EN (pin_G11) +#define MICROPY_HW_ETH_RMII_TXD0 (pin_G13) +#define MICROPY_HW_ETH_RMII_TXD1 (pin_B13) diff --git a/ports/stm32/boards/NUCLEO_F767ZI/pins.csv b/ports/stm32/boards/NUCLEO_F767ZI/pins.csv index 3cae615dabfe9..d84f8e9d1957b 100644 --- a/ports/stm32/boards/NUCLEO_F767ZI/pins.csv +++ b/ports/stm32/boards/NUCLEO_F767ZI/pins.csv @@ -70,3 +70,12 @@ UART6_RX,PG9 SPI_B_NSS,PA4 SPI_B_SCK,PB3 SPI_B_MOSI,PB5 +ETH_MDC,PC1 +ETH_MDIO,PA2 +ETH_RMII_REF_CLK,PA1 +ETH_RMII_CRS_DV,PA7 +ETH_RMII_RXD0,PC4 +ETH_RMII_RXD1,PC5 +ETH_RMII_TX_EN,PG11 +ETH_RMII_TXD0,PG13 +ETH_RMII_TXD1,PB13 diff --git a/ports/stm32/boards/STM32F769DISC/mpconfigboard.h b/ports/stm32/boards/STM32F769DISC/mpconfigboard.h index 4f41a81f9e16c..fac7c98fd8dcf 100644 --- a/ports/stm32/boards/STM32F769DISC/mpconfigboard.h +++ b/ports/stm32/boards/STM32F769DISC/mpconfigboard.h @@ -5,6 +5,8 @@ #define MICROPY_HW_BOARD_NAME "F769DISC" #define MICROPY_HW_MCU_NAME "STM32F769" +#define MICROPY_PY_LWIP (1) + #define MICROPY_HW_HAS_SWITCH (1) #define MICROPY_HW_HAS_FLASH (1) #define MICROPY_HW_HAS_SDCARD (1) @@ -76,6 +78,17 @@ // USB config (CN15 - USB OTG HS with external PHY) #define MICROPY_HW_USB_HS (1) +// Ethernet via RMII +#define MICROPY_HW_ETH_MDC (pin_C1) +#define MICROPY_HW_ETH_MDIO (pin_A2) +#define MICROPY_HW_ETH_RMII_REF_CLK (pin_A1) +#define MICROPY_HW_ETH_RMII_CRS_DV (pin_A7) +#define MICROPY_HW_ETH_RMII_RXD0 (pin_C4) +#define MICROPY_HW_ETH_RMII_RXD1 (pin_C5) +#define MICROPY_HW_ETH_RMII_TX_EN (pin_G11) +#define MICROPY_HW_ETH_RMII_TXD0 (pin_G13) +#define MICROPY_HW_ETH_RMII_TXD1 (pin_G14) + #if 0 // Optional SDRAM configuration; requires SYSCLK <= 200MHz #define MICROPY_HW_SDRAM_SIZE (128 * 1024 * 1024 / 8) // 128 Mbit diff --git a/ports/stm32/boards/STM32F769DISC/pins.csv b/ports/stm32/boards/STM32F769DISC/pins.csv index f587d44c1451f..2dcc374419273 100644 --- a/ports/stm32/boards/STM32F769DISC/pins.csv +++ b/ports/stm32/boards/STM32F769DISC/pins.csv @@ -69,6 +69,15 @@ UART5_TX,PC12 UART5_RX,PD2 CAN2_TX,PB13 CAN2_RX,PB12 +ETH_REF_CLK,PA1 +ETH_MDIO,PA2 +ETH_CRS_DV,PA7 +ETH_MDC,PC1 +ETH_RXD0,PC4 +ETH_RXD1,PC5 +ETH_TX_EN,PG11 +ETH_TXD0,PG13 +ETH_TXD1,PG14 FMC_SDCKE0,PH2 FMC_SDNE0,PH3 FMC_SDCLK,PG8 diff --git a/ports/stm32/boards/STM32F7DISC/mpconfigboard.h b/ports/stm32/boards/STM32F7DISC/mpconfigboard.h index 792206c400735..ff135b8a75426 100644 --- a/ports/stm32/boards/STM32F7DISC/mpconfigboard.h +++ b/ports/stm32/boards/STM32F7DISC/mpconfigboard.h @@ -1,6 +1,8 @@ #define MICROPY_HW_BOARD_NAME "F7DISC" #define MICROPY_HW_MCU_NAME "STM32F746" +#define MICROPY_PY_LWIP (1) + #define MICROPY_HW_HAS_SWITCH (1) #define MICROPY_HW_HAS_FLASH (1) #define MICROPY_HW_HAS_SDCARD (1) @@ -81,6 +83,18 @@ void STM32F7DISC_board_early_init(void); /*#define MICROPY_HW_USB_VBUS_DETECT_PIN (pin_J12)*/ #define MICROPY_HW_USB_OTG_ID_PIN (pin_A10) +// Ethernet via RMII +#define MICROPY_HW_ETH_MDC (pin_C1) +#define MICROPY_HW_ETH_MDIO (pin_A2) +#define MICROPY_HW_ETH_RMII_REF_CLK (pin_A1) +#define MICROPY_HW_ETH_RMII_CRS_DV (pin_A7) +#define MICROPY_HW_ETH_RMII_RXD0 (pin_C4) +#define MICROPY_HW_ETH_RMII_RXD1 (pin_C5) +#define MICROPY_HW_ETH_RMII_RXER (pin_G2) +#define MICROPY_HW_ETH_RMII_TX_EN (pin_G11) +#define MICROPY_HW_ETH_RMII_TXD0 (pin_G13) +#define MICROPY_HW_ETH_RMII_TXD1 (pin_G14) + // SDRAM #define MICROPY_HW_SDRAM_SIZE (64 / 8 * 1024 * 1024) // 64 Mbit #define MICROPY_HW_SDRAM_STARTUP_TEST (1) diff --git a/ports/stm32/boards/STM32F7DISC/pins.csv b/ports/stm32/boards/STM32F7DISC/pins.csv index dfafe67f52b38..99fa969322200 100644 --- a/ports/stm32/boards/STM32F7DISC/pins.csv +++ b/ports/stm32/boards/STM32F7DISC/pins.csv @@ -53,6 +53,16 @@ VCP_TX,PA9 VCP_RX,PB7 CAN_TX,PB13 CAN_RX,PB12 +ETH_MDC,PC1 +ETH_MDIO,PA2 +ETH_RMII_REF_CLK,PA1 +ETH_RMII_CRS_DV,PA7 +ETH_RMII_RXD0,PC4 +ETH_RMII_RXD1,PC5 +ETH_RMII_RXER,PG2 +ETH_RMII_TX_EN,PG11 +ETH_RMII_TXD0,PG13 +ETH_RMII_TXD1,PG14 SDRAM_SDCKE0,PC3 SDRAM_SDNE0,PH3 SDRAM_SDCLK,PG8 diff --git a/ports/stm32/eth.c b/ports/stm32/eth.c new file mode 100644 index 0000000000000..c3a18bde77d06 --- /dev/null +++ b/ports/stm32/eth.c @@ -0,0 +1,613 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2019 Damien P. George + * + * 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/mphal.h" +#include "py/mperrno.h" +#include "lib/netutils/netutils.h" +#include "lwip/etharp.h" +#include "lwip/dns.h" +#include "lwip/dhcp.h" +#include "netif/ethernet.h" +#include "pin_static_af.h" +#include "modnetwork.h" +#include "eth.h" + +#if defined(MICROPY_HW_ETH_MDC) + +// ETH PHY register definitions (for LAN8742) + +#undef PHY_BCR +#define PHY_BCR (0x0000) +#define PHY_BCR_SOFT_RESET (0x8000) +#define PHY_BCR_AUTONEG_EN (0x1000) + +#undef PHY_BSR +#define PHY_BSR (0x0001) +#define PHY_BSR_LINK_STATUS (0x0004) +#define PHY_BSR_AUTONEG_DONE (0x0020) + +#define PHY_SCSR (0x001f) +#define PHY_SCSR_SPEED_Pos (2) +#define PHY_SCSR_SPEED_Msk (7 << PHY_SCSR_SPEED_Pos) +#define PHY_SCSR_SPEED_10HALF (1 << PHY_SCSR_SPEED_Pos) +#define PHY_SCSR_SPEED_10FULL (5 << PHY_SCSR_SPEED_Pos) +#define PHY_SCSR_SPEED_100HALF (2 << PHY_SCSR_SPEED_Pos) +#define PHY_SCSR_SPEED_100FULL (6 << PHY_SCSR_SPEED_Pos) + +// ETH DMA RX and TX descriptor definitions + +#define RX_DESCR_0_OWN_Pos (31) +#define RX_DESCR_0_FL_Pos (16) +#define RX_DESCR_0_FL_Msk (0x3fff << RX_DESCR_0_FL_Pos) +#define RX_DESCR_1_RER_Pos (15) +#define RX_DESCR_1_RCH_Pos (14) +#define RX_DESCR_1_RBS2_Pos (16) +#define RX_DESCR_1_RBS1_Pos (0) + +#define TX_DESCR_0_OWN_Pos (31) +#define TX_DESCR_0_LS_Pos (29) +#define TX_DESCR_0_FS_Pos (28) +#define TX_DESCR_0_DP_Pos (26) +#define TX_DESCR_0_CIC_Pos (22) +#define TX_DESCR_0_TER_Pos (21) +#define TX_DESCR_0_TCH_Pos (20) +#define TX_DESCR_1_TBS1_Pos (0) + +// Configuration values + +#define PHY_INIT_TIMEOUT_MS (10000) + +#define RX_BUF_SIZE (1524) // includes 4-byte CRC at end +#define TX_BUF_SIZE (1524) + +#define RX_BUF_NUM (5) +#define TX_BUF_NUM (5) + +typedef struct _eth_dma_rx_descr_t { + volatile uint32_t rdes0, rdes1, rdes2, rdes3; +} eth_dma_rx_descr_t; + +typedef struct _eth_dma_tx_descr_t { + volatile uint32_t tdes0, tdes1, tdes2, tdes3; +} eth_dma_tx_descr_t; + +typedef struct _eth_dma_t { + eth_dma_rx_descr_t rx_descr[RX_BUF_NUM]; + eth_dma_tx_descr_t tx_descr[TX_BUF_NUM]; + uint8_t rx_buf[RX_BUF_NUM * RX_BUF_SIZE] __attribute__((aligned(4))); + uint8_t tx_buf[TX_BUF_NUM * TX_BUF_SIZE] __attribute__((aligned(4))); + size_t rx_descr_idx; + size_t tx_descr_idx; + uint8_t padding[16384 - 15408]; +} eth_dma_t; + +typedef struct _eth_t { + mod_network_nic_type_t base; + uint32_t trace_flags; + struct netif netif; + struct dhcp dhcp_struct; +} eth_t; + +static eth_dma_t eth_dma __attribute__((aligned(16384))); + +eth_t eth_instance; + +STATIC void eth_mac_deinit(eth_t *self); +STATIC void eth_process_frame(eth_t *self, size_t len, const uint8_t *buf); + +STATIC void eth_phy_write(uint32_t reg, uint32_t val) { + while (ETH->MACMIIAR & ETH_MACMIIAR_MB) { + } + ETH->MACMIIDR = val; + uint32_t ar = ETH->MACMIIAR; + ar = reg << ETH_MACMIIAR_MR_Pos | (ar & ETH_MACMIIAR_CR_Msk) | ETH_MACMIIAR_MW | ETH_MACMIIAR_MB; + ETH->MACMIIAR = ar; + while (ETH->MACMIIAR & ETH_MACMIIAR_MB) { + } +} + +STATIC uint32_t eth_phy_read(uint32_t reg) { + while (ETH->MACMIIAR & ETH_MACMIIAR_MB) { + } + uint32_t ar = ETH->MACMIIAR; + ar = reg << ETH_MACMIIAR_MR_Pos | (ar & ETH_MACMIIAR_CR_Msk) | ETH_MACMIIAR_MB; + ETH->MACMIIAR = ar; + while (ETH->MACMIIAR & ETH_MACMIIAR_MB) { + } + return ETH->MACMIIDR; +} + +STATIC void mpu_config(uint32_t region, uint32_t base_addr, uint32_t size) { + __DMB(); + + // Disable MPU + SCB->SHCSR &= ~SCB_SHCSR_MEMFAULTENA_Msk; + MPU->CTRL = 0; + + // Config MPU region + MPU->RNR = region; + MPU->RBAR = base_addr; + MPU->RASR = + MPU_INSTRUCTION_ACCESS_DISABLE << MPU_RASR_XN_Pos + | MPU_REGION_FULL_ACCESS << MPU_RASR_AP_Pos + | MPU_TEX_LEVEL1 << MPU_RASR_TEX_Pos + | MPU_ACCESS_SHAREABLE << MPU_RASR_S_Pos + | MPU_ACCESS_NOT_CACHEABLE << MPU_RASR_C_Pos + | MPU_ACCESS_NOT_BUFFERABLE << MPU_RASR_B_Pos + | 0x00 << MPU_RASR_SRD_Pos + | size << MPU_RASR_SIZE_Pos + | MPU_REGION_ENABLE << MPU_RASR_ENABLE_Pos; + + // Enable MPU + MPU->CTRL = MPU_PRIVILEGED_DEFAULT | MPU_CTRL_ENABLE_Msk; + SCB->SHCSR |= SCB_SHCSR_MEMFAULTENA_Msk; + + __DSB(); + __ISB(); +} + +void eth_init(eth_t *self, int mac_idx) { + mp_hal_get_mac(mac_idx, &self->netif.hwaddr[0]); + self->netif.hwaddr_len = 6; +} + +void eth_set_trace(eth_t *self, uint32_t value) { + self->trace_flags = value; +} + +STATIC int eth_mac_init(eth_t *self) { + // Configure MPU + mpu_config(MPU_REGION_NUMBER0, (uint32_t)ð_dma, MPU_REGION_SIZE_16KB); + + // Configure GPIO + mp_hal_pin_config_alt_static(MICROPY_HW_ETH_MDC, MP_HAL_PIN_MODE_ALT, MP_HAL_PIN_PULL_NONE, STATIC_AF_ETH_MDC); + mp_hal_pin_config_alt_static(MICROPY_HW_ETH_MDIO, MP_HAL_PIN_MODE_ALT, MP_HAL_PIN_PULL_NONE, STATIC_AF_ETH_MDIO); + mp_hal_pin_config_alt_static(MICROPY_HW_ETH_RMII_REF_CLK, MP_HAL_PIN_MODE_ALT, MP_HAL_PIN_PULL_NONE, STATIC_AF_ETH_RMII_REF_CLK); + mp_hal_pin_config_alt_static(MICROPY_HW_ETH_RMII_CRS_DV, MP_HAL_PIN_MODE_ALT, MP_HAL_PIN_PULL_NONE, STATIC_AF_ETH_RMII_CRS_DV); + mp_hal_pin_config_alt_static(MICROPY_HW_ETH_RMII_RXD0, MP_HAL_PIN_MODE_ALT, MP_HAL_PIN_PULL_NONE, STATIC_AF_ETH_RMII_RXD0); + mp_hal_pin_config_alt_static(MICROPY_HW_ETH_RMII_RXD1, MP_HAL_PIN_MODE_ALT, MP_HAL_PIN_PULL_NONE, STATIC_AF_ETH_RMII_RXD1); + mp_hal_pin_config_alt_static(MICROPY_HW_ETH_RMII_TX_EN, MP_HAL_PIN_MODE_ALT, MP_HAL_PIN_PULL_NONE, STATIC_AF_ETH_RMII_TX_EN); + mp_hal_pin_config_alt_static(MICROPY_HW_ETH_RMII_TXD0, MP_HAL_PIN_MODE_ALT, MP_HAL_PIN_PULL_NONE, STATIC_AF_ETH_RMII_TXD0); + mp_hal_pin_config_alt_static(MICROPY_HW_ETH_RMII_TXD1, MP_HAL_PIN_MODE_ALT, MP_HAL_PIN_PULL_NONE, STATIC_AF_ETH_RMII_TXD1); + + __HAL_RCC_ETH_CLK_ENABLE(); + __HAL_RCC_ETHMAC_FORCE_RESET(); + + // Select RMII interface + __HAL_RCC_SYSCFG_CLK_ENABLE(); + SYSCFG->PMC |= SYSCFG_PMC_MII_RMII_SEL; + + __HAL_RCC_ETHMAC_RELEASE_RESET(); + + __HAL_RCC_ETHMAC_CLK_SLEEP_ENABLE(); + __HAL_RCC_ETHMACTX_CLK_SLEEP_ENABLE(); + __HAL_RCC_ETHMACRX_CLK_SLEEP_ENABLE(); + + // Do a soft reset of the MAC core + ETH->DMABMR = ETH_DMABMR_SR; + mp_hal_delay_ms(2); + + // Wait for soft reset to finish + uint32_t t0 = mp_hal_ticks_ms(); + while (ETH->DMABMR & ETH_DMABMR_SR) { + if (mp_hal_ticks_ms() - t0 > 1000) { + return -MP_ETIMEDOUT; + } + } + + // Set MII clock range + uint32_t hclk = HAL_RCC_GetHCLKFreq(); + uint32_t cr_div; + if (hclk < 35000000) { + cr_div = ETH_MACMIIAR_CR_Div16; + } else if (hclk < 60000000) { + cr_div = ETH_MACMIIAR_CR_Div26; + } else if (hclk < 100000000) { + cr_div = ETH_MACMIIAR_CR_Div42; + } else if (hclk < 150000000) { + cr_div = ETH_MACMIIAR_CR_Div62; + } else { + cr_div = ETH_MACMIIAR_CR_Div102; + } + ETH->MACMIIAR = cr_div; + + // Reset the PHY + eth_phy_write(PHY_BCR, PHY_BCR_SOFT_RESET); + mp_hal_delay_ms(50); + + // Wait for the PHY link to be established + int phy_state = 0; + t0 = mp_hal_ticks_ms(); + while (phy_state != 3) { + if (mp_hal_ticks_ms() - t0 > PHY_INIT_TIMEOUT_MS) { + eth_mac_deinit(self); + return -MP_ETIMEDOUT; + } + uint16_t bcr = eth_phy_read(0); + uint16_t bsr = eth_phy_read(1); + switch (phy_state) { + case 0: + if (!(bcr & PHY_BCR_SOFT_RESET)) { + phy_state = 1; + } + break; + case 1: + if (bsr & PHY_BSR_LINK_STATUS) { + eth_phy_write(PHY_BCR, PHY_BCR_AUTONEG_EN); + phy_state = 2; + } + break; + case 2: + if ((bsr & (PHY_BSR_AUTONEG_DONE | PHY_BSR_LINK_STATUS)) + == (PHY_BSR_AUTONEG_DONE | PHY_BSR_LINK_STATUS)) { + phy_state = 3; + } + break; + } + mp_hal_delay_ms(2); + } + + // Get register with link status + uint16_t phy_scsr = eth_phy_read(PHY_SCSR); + + // Burst mode configuration + ETH->DMABMR = 0; + mp_hal_delay_ms(2); + + // Select DMA interrupts + ETH->DMAIER = + ETH_DMAIER_NISE // enable normal interrupts + | ETH_DMAIER_RIE // enable RX interrupt + ; + + // Configure RX descriptor lists + for (size_t i = 0; i < RX_BUF_NUM; ++i) { + eth_dma.rx_descr[i].rdes0 = 1 << RX_DESCR_0_OWN_Pos; + eth_dma.rx_descr[i].rdes1 = + 1 << RX_DESCR_1_RCH_Pos // chained + | RX_BUF_SIZE << RX_DESCR_1_RBS1_Pos + ; + eth_dma.rx_descr[i].rdes2 = (uint32_t)ð_dma.rx_buf[i * RX_BUF_SIZE]; + eth_dma.rx_descr[i].rdes3 = (uint32_t)ð_dma.rx_descr[(i + 1) % RX_BUF_NUM]; + } + ETH->DMARDLAR = (uint32_t)ð_dma.rx_descr[0]; + eth_dma.rx_descr_idx = 0; + + // Configure TX descriptor lists + for (size_t i = 0; i < TX_BUF_NUM; ++i) { + eth_dma.tx_descr[i].tdes0 = 1 << TX_DESCR_0_TCH_Pos; + eth_dma.tx_descr[i].tdes1 = 0; + eth_dma.tx_descr[i].tdes2 = 0; + eth_dma.tx_descr[i].tdes3 = (uint32_t)ð_dma.tx_descr[(i + 1) % TX_BUF_NUM]; + } + ETH->DMATDLAR = (uint32_t)ð_dma.tx_descr[0]; + eth_dma.tx_descr_idx = 0; + + // Configure DMA + ETH->DMAOMR = + ETH_DMAOMR_RSF // read from RX FIFO after a full frame is written + | ETH_DMAOMR_TSF // transmit when a full frame is in TX FIFO (needed by errata) + ; + mp_hal_delay_ms(2); + + // Select MAC filtering options + ETH->MACFFR = + ETH_MACFFR_RA // pass all frames up + ; + mp_hal_delay_ms(2); + + // Set MAC address + u8_t *mac = &self->netif.hwaddr[0]; + ETH->MACA0HR = mac[5] << 8 | mac[4]; + mp_hal_delay_ms(2); + ETH->MACA0LR = mac[3] << 24 | mac[2] << 16 | mac[1] << 8 | mac[0]; + mp_hal_delay_ms(2); + + // Set main MAC control register + ETH->MACCR = + (phy_scsr & PHY_SCSR_SPEED_Msk) == PHY_SCSR_SPEED_10FULL ? ETH_MACCR_DM + : (phy_scsr & PHY_SCSR_SPEED_Msk) == PHY_SCSR_SPEED_100HALF ? ETH_MACCR_FES + : (phy_scsr & PHY_SCSR_SPEED_Msk) == PHY_SCSR_SPEED_100FULL ? (ETH_MACCR_FES | ETH_MACCR_DM) + : 0 + ; + mp_hal_delay_ms(2); + + // Start MAC layer + ETH->MACCR |= + ETH_MACCR_TE // enable TX + | ETH_MACCR_RE // enable RX + ; + mp_hal_delay_ms(2); + + // Start DMA layer + ETH->DMAOMR |= + ETH_DMAOMR_ST // start TX + | ETH_DMAOMR_SR // start RX + ; + mp_hal_delay_ms(2); + + // Enable interrupts + NVIC_SetPriority(ETH_IRQn, IRQ_PRI_PENDSV); + HAL_NVIC_EnableIRQ(ETH_IRQn); + + return 0; +} + +STATIC void eth_mac_deinit(eth_t *self) { + (void)self; + HAL_NVIC_DisableIRQ(ETH_IRQn); + __HAL_RCC_ETHMAC_FORCE_RESET(); + __HAL_RCC_ETHMAC_RELEASE_RESET(); + __HAL_RCC_ETH_CLK_DISABLE(); +} + +STATIC int eth_tx_buf_get(size_t len, uint8_t **buf) { + if (len > TX_BUF_SIZE) { + return -MP_EINVAL; + } + + // Wait for DMA to release the current TX descriptor (if it has it) + eth_dma_tx_descr_t *tx_descr = ð_dma.tx_descr[eth_dma.tx_descr_idx]; + uint32_t t0 = mp_hal_ticks_ms(); + for (;;) { + if (!(tx_descr->tdes0 & (1 << TX_DESCR_0_OWN_Pos))) { + break; + } + if (mp_hal_ticks_ms() - t0 > 1000) { + return -MP_ETIMEDOUT; + } + } + + // Update TX descriptor with length, buffer pointer and linked list pointer + *buf = ð_dma.tx_buf[eth_dma.tx_descr_idx * TX_BUF_SIZE]; + tx_descr->tdes1 = len << TX_DESCR_1_TBS1_Pos; + tx_descr->tdes2 = (uint32_t)*buf; + tx_descr->tdes3 = (uint32_t)ð_dma.tx_descr[(eth_dma.tx_descr_idx + 1) % TX_BUF_NUM]; + + return 0; +} + +STATIC int eth_tx_buf_send(void) { + // Get TX descriptor and move to next one + eth_dma_tx_descr_t *tx_descr = ð_dma.tx_descr[eth_dma.tx_descr_idx]; + eth_dma.tx_descr_idx = (eth_dma.tx_descr_idx + 1) % TX_BUF_NUM; + + // Schedule to send next outgoing frame + tx_descr->tdes0 = + 1 << TX_DESCR_0_OWN_Pos // owned by DMA + | 1 << TX_DESCR_0_LS_Pos // last segment + | 1 << TX_DESCR_0_FS_Pos // first segment + | 3 << TX_DESCR_0_CIC_Pos // enable all checksums inserted by hardware + | 1 << TX_DESCR_0_TCH_Pos // TX descriptor is chained + ; + + // Notify ETH DMA that there is a new TX descriptor for sending + __DMB(); + if (ETH->DMASR & ETH_DMASR_TBUS) { + ETH->DMASR = ETH_DMASR_TBUS; + ETH->DMATPDR = 0; + } + + return 0; +} + +STATIC void eth_dma_rx_free(void) { + // Get RX descriptor, RX buffer and move to next one + eth_dma_rx_descr_t *rx_descr = ð_dma.rx_descr[eth_dma.rx_descr_idx]; + uint8_t *buf = ð_dma.rx_buf[eth_dma.rx_descr_idx * RX_BUF_SIZE]; + eth_dma.rx_descr_idx = (eth_dma.rx_descr_idx + 1) % RX_BUF_NUM; + + // Schedule to get next incoming frame + rx_descr->rdes1 = + 1 << RX_DESCR_1_RCH_Pos // RX descriptor is chained + | RX_BUF_SIZE << RX_DESCR_1_RBS1_Pos // maximum buffer length + ; + rx_descr->rdes2 = (uint32_t)buf; + rx_descr->rdes3 = (uint32_t)ð_dma.rx_descr[eth_dma.rx_descr_idx]; + rx_descr->rdes0 = 1 << RX_DESCR_0_OWN_Pos; // owned by DMA + + // Notify ETH DMA that there is a new RX descriptor available + __DMB(); + ETH->DMARPDR = 0; +} + +void ETH_IRQHandler(void) { + uint32_t sr = ETH->DMASR; + ETH->DMASR = ETH_DMASR_NIS; + if (sr & ETH_DMASR_RS) { + ETH->DMASR = ETH_DMASR_RS; + for (;;) { + eth_dma_rx_descr_t *rx_descr = ð_dma.rx_descr[eth_dma.rx_descr_idx]; + if (rx_descr->rdes0 & (1 << RX_DESCR_0_OWN_Pos)) { + // No more RX descriptors ready to read + break; + } + + // Get RX buffer containing new frame + size_t len = (rx_descr->rdes0 & RX_DESCR_0_FL_Msk) >> RX_DESCR_0_FL_Pos; + len -= 4; // discard CRC at end + uint8_t *buf = (uint8_t*)rx_descr->rdes2; + + // Process frame + eth_process_frame(ð_instance, len, buf); + eth_dma_rx_free(); + } + } +} + +/*******************************************************************************/ +// ETH-LwIP bindings + +#define TRACE_ASYNC_EV (0x0001) +#define TRACE_ETH_TX (0x0002) +#define TRACE_ETH_RX (0x0004) +#define TRACE_ETH_FULL (0x0008) + +STATIC void eth_trace(eth_t *self, size_t len, const void *data, unsigned int flags) { + if (((flags & NETUTILS_TRACE_IS_TX) && (self->trace_flags & TRACE_ETH_TX)) + || (!(flags & NETUTILS_TRACE_IS_TX) && (self->trace_flags & TRACE_ETH_RX))) { + const uint8_t *buf; + if (len == (size_t)-1) { + // data is a pbuf + const struct pbuf *pbuf = data; + buf = pbuf->payload; + len = pbuf->len; // restricted to print only the first chunk of the pbuf + } else { + // data is actual data buffer + buf = data; + } + if (self->trace_flags & TRACE_ETH_FULL) { + flags |= NETUTILS_TRACE_PAYLOAD; + } + netutils_ethernet_trace(MP_PYTHON_PRINTER, len, buf, flags); + } +} + +STATIC err_t eth_netif_output(struct netif *netif, struct pbuf *p) { + // This function should always be called from a context where PendSV-level IRQs are disabled + + LINK_STATS_INC(link.xmit); + eth_trace(netif->state, (size_t)-1, p, NETUTILS_TRACE_IS_TX | NETUTILS_TRACE_NEWLINE); + + uint8_t *buf; + int ret = eth_tx_buf_get(p->tot_len, &buf); + if (ret == 0) { + pbuf_copy_partial(p, buf, p->tot_len, 0); + ret = eth_tx_buf_send(); + } + + return ret ? ERR_BUF : ERR_OK; +} + +STATIC err_t eth_netif_init(struct netif *netif) { + netif->linkoutput = eth_netif_output; + netif->output = etharp_output; + netif->mtu = 1500; + netif->flags = NETIF_FLAG_BROADCAST | NETIF_FLAG_ETHARP | NETIF_FLAG_ETHERNET | NETIF_FLAG_IGMP; + // Checksums only need to be checked on incoming frames, not computed on outgoing frames + NETIF_SET_CHECKSUM_CTRL(netif, + NETIF_CHECKSUM_CHECK_IP + | NETIF_CHECKSUM_CHECK_UDP + | NETIF_CHECKSUM_CHECK_TCP + | NETIF_CHECKSUM_CHECK_ICMP + | NETIF_CHECKSUM_CHECK_ICMP6); + return ERR_OK; +} + +STATIC void eth_lwip_init(eth_t *self) { + ip_addr_t ipconfig[4]; + IP4_ADDR(&ipconfig[0], 0, 0, 0, 0); + IP4_ADDR(&ipconfig[2], 192, 168, 0, 1); + IP4_ADDR(&ipconfig[1], 255, 255, 255, 0); + IP4_ADDR(&ipconfig[3], 8, 8, 8, 8); + + MICROPY_PY_LWIP_ENTER + + struct netif *n = &self->netif; + n->name[0] = 'e'; + n->name[1] = '0'; + netif_add(n, &ipconfig[0], &ipconfig[1], &ipconfig[2], self, eth_netif_init, ethernet_input); + netif_set_hostname(n, "MPY"); + netif_set_default(n); + netif_set_up(n); + + dns_setserver(0, &ipconfig[3]); + dhcp_set_struct(n, &self->dhcp_struct); + dhcp_start(n); + + netif_set_link_up(n); + + MICROPY_PY_LWIP_EXIT +} + +STATIC void eth_lwip_deinit(eth_t *self) { + MICROPY_PY_LWIP_ENTER + for (struct netif *netif = netif_list; netif != NULL; netif = netif->next) { + if (netif == &self->netif) { + netif_remove(netif); + netif->ip_addr.addr = 0; + netif->flags = 0; + } + } + MICROPY_PY_LWIP_EXIT +} + +STATIC void eth_process_frame(eth_t *self, size_t len, const uint8_t *buf) { + eth_trace(self, len, buf, NETUTILS_TRACE_NEWLINE); + + struct netif *netif = &self->netif; + if (netif->flags & NETIF_FLAG_LINK_UP) { + struct pbuf *p = pbuf_alloc(PBUF_RAW, len, PBUF_POOL); + if (p != NULL) { + pbuf_take(p, buf, len); + if (netif->input(p, netif) != ERR_OK) { + pbuf_free(p); + } + } + } +} + +struct netif *eth_netif(eth_t *self) { + return &self->netif; +} + +int eth_link_status(eth_t *self) { + struct netif *netif = &self->netif; + if ((netif->flags & (NETIF_FLAG_UP | NETIF_FLAG_LINK_UP)) + == (NETIF_FLAG_UP | NETIF_FLAG_LINK_UP)) { + if (netif->ip_addr.addr != 0) { + return 3; // link up + } else { + return 2; // link no-ip; + } + } else { + int s = eth_phy_read(0) | eth_phy_read(0x10) << 16; + if (s == 0) { + return 0; // link down + } else { + return 1; // link join + } + } +} + +int eth_start(eth_t *self) { + eth_lwip_deinit(self); + int ret = eth_mac_init(self); + if (ret < 0) { + return ret; + } + eth_lwip_init(self); + return 0; +} + +int eth_stop(eth_t *self) { + eth_lwip_deinit(self); + eth_mac_deinit(self); + return 0; +} + +#endif // defined(MICROPY_HW_ETH_MDC) diff --git a/ports/stm32/eth.h b/ports/stm32/eth.h new file mode 100644 index 0000000000000..fd46c7fa7c01b --- /dev/null +++ b/ports/stm32/eth.h @@ -0,0 +1,39 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2019 Damien P. George + * + * 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. + */ +#ifndef MICROPY_INCLUDED_STM32_ETH_H +#define MICROPY_INCLUDED_STM32_ETH_H + +typedef struct _eth_t eth_t; +extern eth_t eth_instance; + +void eth_init(eth_t *self, int mac_idx); +void eth_set_trace(eth_t *self, uint32_t value); +struct netif *eth_netif(eth_t *self); +int eth_link_status(eth_t *self); +int eth_start(eth_t *self); +int eth_stop(eth_t *self); + +#endif // MICROPY_INCLUDED_STM32_ETH_H diff --git a/ports/stm32/lwip_inc/lwipopts.h b/ports/stm32/lwip_inc/lwipopts.h index b8ab8a2ab095f..64ff104f7126b 100644 --- a/ports/stm32/lwip_inc/lwipopts.h +++ b/ports/stm32/lwip_inc/lwipopts.h @@ -8,6 +8,7 @@ #define MEM_ALIGNMENT 4 #define LWIP_CHKSUM_ALGORITHM 3 +#define LWIP_CHECKSUM_CTRL_PER_NETIF 1 #define LWIP_ARP 1 #define LWIP_ETHERNET 1 diff --git a/ports/stm32/modnetwork.c b/ports/stm32/modnetwork.c index 4fa3d8c05bf16..ea43f75573680 100644 --- a/ports/stm32/modnetwork.c +++ b/ports/stm32/modnetwork.c @@ -57,7 +57,9 @@ STATIC void pyb_lwip_poll(void) { for (struct netif *netif = netif_list; netif != NULL; netif = netif->next) { if (netif->flags & NETIF_FLAG_LINK_UP) { mod_network_nic_type_t *nic = netif->state; - nic->poll_callback(nic, netif); + if (nic->poll_callback) { + nic->poll_callback(nic, netif); + } } } // Run the lwIP internal updates @@ -114,6 +116,10 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_0(network_route_obj, network_route); STATIC const mp_rom_map_elem_t mp_module_network_globals_table[] = { { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_network) }, + #if defined(MICROPY_HW_ETH_MDC) + { MP_ROM_QSTR(MP_QSTR_LAN), MP_ROM_PTR(&network_lan_type) }, + #endif + #if MICROPY_PY_WIZNET5K { MP_ROM_QSTR(MP_QSTR_WIZNET5K), MP_ROM_PTR(&mod_network_nic_type_wiznet5k) }, #endif diff --git a/ports/stm32/modnetwork.h b/ports/stm32/modnetwork.h index dd8113ebf53a5..41a0017dab007 100644 --- a/ports/stm32/modnetwork.h +++ b/ports/stm32/modnetwork.h @@ -44,6 +44,7 @@ typedef struct _mod_network_nic_type_t { void (*poll_callback)(void *data, struct netif *netif); } mod_network_nic_type_t; +extern const mp_obj_type_t network_lan_type; extern const mp_obj_type_t mod_network_nic_type_wiznet5k; void mod_network_lwip_poll_wrapper(uint32_t ticks_ms); diff --git a/ports/stm32/mpconfigport.h b/ports/stm32/mpconfigport.h index 7ae2ac77b0468..712697c80e03f 100644 --- a/ports/stm32/mpconfigport.h +++ b/ports/stm32/mpconfigport.h @@ -357,6 +357,11 @@ static inline mp_uint_t disable_irq(void) { #define MICROPY_THREAD_YIELD() #endif +// The LwIP interface must run at a raised IRQ priority +#define MICROPY_PY_LWIP_ENTER uint32_t irq_state = raise_irq_pri(IRQ_PRI_PENDSV); +#define MICROPY_PY_LWIP_REENTER irq_state = raise_irq_pri(IRQ_PRI_PENDSV); +#define MICROPY_PY_LWIP_EXIT restore_irq_pri(irq_state); + // We need an implementation of the log2 function which is not a macro #define MP_NEED_LOG2 (1) diff --git a/ports/stm32/mphalport.c b/ports/stm32/mphalport.c index 206221721bb80..1a9392b5808af 100644 --- a/ports/stm32/mphalport.c +++ b/ports/stm32/mphalport.c @@ -162,3 +162,15 @@ void mp_hal_pin_config_speed(mp_hal_pin_obj_t pin_obj, uint32_t speed) { uint32_t pin = pin_obj->pin; gpio->OSPEEDR = (gpio->OSPEEDR & ~(3 << (2 * pin))) | (speed << (2 * pin)); } + +MP_WEAK void mp_hal_get_mac(int idx, uint8_t buf[6]) { + // Generate a random locally administered MAC address (LAA) + // The algorithm here to is based on the DFU USB serial number algorithm + uint8_t *id = (uint8_t *)MP_HAL_UNIQUE_ID_ADDRESS; + buf[0] = 0x02; // LAA range + buf[1] = id[10] + id[2]; + buf[2] = id[9]; + buf[3] = id[8] + id[0]; + buf[4] = id[6]; + buf[5] = (id[7] & 0xfc) | idx; +} diff --git a/ports/stm32/mphalport.h b/ports/stm32/mphalport.h index a60365087e7fd..d828bb9dd4983 100644 --- a/ports/stm32/mphalport.h +++ b/ports/stm32/mphalport.h @@ -79,3 +79,12 @@ void mp_hal_gpio_clock_enable(GPIO_TypeDef *gpio); void mp_hal_pin_config(mp_hal_pin_obj_t pin, uint32_t mode, uint32_t pull, uint32_t alt); bool mp_hal_pin_config_alt(mp_hal_pin_obj_t pin, uint32_t mode, uint32_t pull, uint8_t fn, uint8_t unit); void mp_hal_pin_config_speed(mp_hal_pin_obj_t pin_obj, uint32_t speed); + +enum { + MP_HAL_MAC_WLAN0 = 0, + MP_HAL_MAC_WLAN1, + MP_HAL_MAC_BDADDR, + MP_HAL_MAC_ETH0, +}; + +void mp_hal_get_mac(int idx, uint8_t buf[6]); diff --git a/ports/stm32/network_lan.c b/ports/stm32/network_lan.c new file mode 100644 index 0000000000000..8c210802ceb81 --- /dev/null +++ b/ports/stm32/network_lan.c @@ -0,0 +1,164 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2019 Damien P. George + * + * 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 "py/runtime.h" +#include "py/mphal.h" +#include "lwip/netif.h" +#include "modnetwork.h" +#include "eth.h" + +#if defined(MICROPY_HW_ETH_MDC) + +typedef struct _network_lan_obj_t { + mp_obj_base_t base; + eth_t *eth; +} network_lan_obj_t; + +STATIC const network_lan_obj_t network_lan_eth0 = { { &network_lan_type }, ð_instance }; + +STATIC void network_lan_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { + network_lan_obj_t *self = MP_OBJ_TO_PTR(self_in); + struct netif *netif = eth_netif(self->eth); + int status = eth_link_status(self->eth); + mp_printf(print, "", + status, + netif->ip_addr.addr & 0xff, + netif->ip_addr.addr >> 8 & 0xff, + netif->ip_addr.addr >> 16 & 0xff, + netif->ip_addr.addr >> 24 + ); +} + +STATIC mp_obj_t network_lan_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { + mp_arg_check_num(n_args, n_kw, 0, 0, false); + const network_lan_obj_t *self = &network_lan_eth0; + eth_init(self->eth, MP_HAL_MAC_ETH0); + return MP_OBJ_FROM_PTR(self); +} + +STATIC mp_obj_t network_lan_active(size_t n_args, const mp_obj_t *args) { + network_lan_obj_t *self = MP_OBJ_TO_PTR(args[0]); + if (n_args == 1) { + return mp_obj_new_bool(eth_link_status(self->eth)); + } else { + int ret; + if (mp_obj_is_true(args[1])) { + ret = eth_start(self->eth); + } else { + ret = eth_stop(self->eth); + } + if (ret < 0) { + mp_raise_OSError(-ret); + } + return mp_const_none; + } +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(network_lan_active_obj, 1, 2, network_lan_active); + +STATIC mp_obj_t network_lan_isconnected(mp_obj_t self_in) { + network_lan_obj_t *self = MP_OBJ_TO_PTR(self_in); + return mp_obj_new_bool(eth_link_status(self->eth) == 3); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(network_lan_isconnected_obj, network_lan_isconnected); + +STATIC mp_obj_t network_lan_ifconfig(size_t n_args, const mp_obj_t *args) { + network_lan_obj_t *self = MP_OBJ_TO_PTR(args[0]); + return mod_network_nic_ifconfig(eth_netif(self->eth), n_args - 1, args + 1); +} +MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(network_lan_ifconfig_obj, 1, 2, network_lan_ifconfig); + +STATIC mp_obj_t network_lan_status(size_t n_args, const mp_obj_t *args) { + network_lan_obj_t *self = MP_OBJ_TO_PTR(args[0]); + (void)self; + + if (n_args == 1) { + // No arguments: return link status + return MP_OBJ_NEW_SMALL_INT(eth_link_status(self->eth)); + } + + mp_raise_ValueError("unknown status param"); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(network_lan_status_obj, 1, 2, network_lan_status); + +STATIC mp_obj_t network_lan_config(size_t n_args, const mp_obj_t *args, mp_map_t *kwargs) { + network_lan_obj_t *self = MP_OBJ_TO_PTR(args[0]); + + if (kwargs->used == 0) { + // Get config value + if (n_args != 2) { + mp_raise_TypeError("must query one param"); + } + + switch (mp_obj_str_get_qstr(args[1])) { + case MP_QSTR_mac: { + return mp_obj_new_bytes(ð_netif(self->eth)->hwaddr[0], 6); + } + default: + mp_raise_ValueError("unknown config param"); + } + } else { + // Set config value(s) + if (n_args != 1) { + mp_raise_TypeError("can't specify pos and kw args"); + } + + for (size_t i = 0; i < kwargs->alloc; ++i) { + if (MP_MAP_SLOT_IS_FILLED(kwargs, i)) { + mp_map_elem_t *e = &kwargs->table[i]; + switch (mp_obj_str_get_qstr(e->key)) { + case MP_QSTR_trace: { + eth_set_trace(self->eth, mp_obj_get_int(e->value)); + break; + } + default: + mp_raise_ValueError("unknown config param"); + } + } + } + + return mp_const_none; + } +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(network_lan_config_obj, 1, network_lan_config); + +STATIC const mp_rom_map_elem_t network_lan_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_active), MP_ROM_PTR(&network_lan_active_obj) }, + { MP_ROM_QSTR(MP_QSTR_isconnected), MP_ROM_PTR(&network_lan_isconnected_obj) }, + { MP_ROM_QSTR(MP_QSTR_ifconfig), MP_ROM_PTR(&network_lan_ifconfig_obj) }, + { MP_ROM_QSTR(MP_QSTR_status), MP_ROM_PTR(&network_lan_status_obj) }, + { MP_ROM_QSTR(MP_QSTR_config), MP_ROM_PTR(&network_lan_config_obj) }, +}; +STATIC MP_DEFINE_CONST_DICT(network_lan_locals_dict, network_lan_locals_dict_table); + +const mp_obj_type_t network_lan_type = { + { &mp_type_type }, + .name = MP_QSTR_LAN, + .print = network_lan_print, + .make_new = network_lan_make_new, + .locals_dict = (mp_obj_dict_t*)&network_lan_locals_dict, +}; + +#endif // defined(MICROPY_HW_ETH_MDC)