From 5e9a5e827b3265fca53529883abdcc87608abad5 Mon Sep 17 00:00:00 2001 From: graham sanderson Date: Wed, 29 Jun 2022 23:03:32 -0500 Subject: [PATCH] Add Pico W and lwIP support --- .gitmodules | 6 + README.md | 18 + docs/Doxyfile.in | 3 +- docs/index.h | 7 + lib/cyw43-driver | 1 + lib/lwip | 1 + src/boards/include/boards/pico_w.h | 102 ++++ src/boards/pico_w.cmake | 2 + src/common/pico_base/include/pico/error.h | 8 +- src/rp2_common/CMakeLists.txt | 4 + src/rp2_common/cyw43_driver/CMakeLists.txt | 81 +++ .../cyw43_driver/cyw43_bus_pio_spi.c | 545 ++++++++++++++++++ .../cyw43_driver/cyw43_bus_pio_spi.pio | 61 ++ src/rp2_common/hardware_dma/dma.c | 6 + .../hardware_dma/include/hardware/dma.h | 9 +- src/rp2_common/pico_cyw43_arch/CMakeLists.txt | 66 +++ src/rp2_common/pico_cyw43_arch/cyw43_arch.c | 145 +++++ .../pico_cyw43_arch/cyw43_arch_freertos.c | 251 ++++++++ .../pico_cyw43_arch/cyw43_arch_poll.c | 113 ++++ .../cyw43_arch_threadsafe_background.c | 319 ++++++++++ .../include/cyw43_configport.h | 73 +++ .../pico_cyw43_arch/include/pico/cyw43_arch.h | 328 +++++++++++ .../include/pico/cyw43_arch/arch_common.h | 74 +++ .../include/pico/cyw43_arch/arch_freertos.h | 61 ++ .../include/pico/cyw43_arch/arch_poll.h | 58 ++ .../cyw43_arch/arch_threadsafe_background.h | 67 +++ .../rp2040_usb_device_enumeration.c | 9 +- src/rp2_common/pico_lwip/CMakeLists.txt | 278 +++++++++ src/rp2_common/pico_lwip/doc.h | 44 ++ src/rp2_common/pico_lwip/include/arch/cc.h | 79 +++ src/rp2_common/pico_lwip/nosys.c | 26 + src/rp2_common/pico_lwip/random.c | 29 + src/rp2_common/tinyusb/CMakeLists.txt | 1 + test/kitchen_sink/CMakeLists.txt | 36 +- test/kitchen_sink/kitchen_sink.c | 3 + test/kitchen_sink/lwipopts.h | 14 + 36 files changed, 2915 insertions(+), 13 deletions(-) create mode 160000 lib/cyw43-driver create mode 160000 lib/lwip create mode 100644 src/boards/include/boards/pico_w.h create mode 100644 src/boards/pico_w.cmake create mode 100644 src/rp2_common/cyw43_driver/CMakeLists.txt create mode 100644 src/rp2_common/cyw43_driver/cyw43_bus_pio_spi.c create mode 100644 src/rp2_common/cyw43_driver/cyw43_bus_pio_spi.pio create mode 100644 src/rp2_common/pico_cyw43_arch/CMakeLists.txt create mode 100644 src/rp2_common/pico_cyw43_arch/cyw43_arch.c create mode 100644 src/rp2_common/pico_cyw43_arch/cyw43_arch_freertos.c create mode 100644 src/rp2_common/pico_cyw43_arch/cyw43_arch_poll.c create mode 100644 src/rp2_common/pico_cyw43_arch/cyw43_arch_threadsafe_background.c create mode 100644 src/rp2_common/pico_cyw43_arch/include/cyw43_configport.h create mode 100644 src/rp2_common/pico_cyw43_arch/include/pico/cyw43_arch.h create mode 100644 src/rp2_common/pico_cyw43_arch/include/pico/cyw43_arch/arch_common.h create mode 100644 src/rp2_common/pico_cyw43_arch/include/pico/cyw43_arch/arch_freertos.h create mode 100644 src/rp2_common/pico_cyw43_arch/include/pico/cyw43_arch/arch_poll.h create mode 100644 src/rp2_common/pico_cyw43_arch/include/pico/cyw43_arch/arch_threadsafe_background.h create mode 100644 src/rp2_common/pico_lwip/CMakeLists.txt create mode 100644 src/rp2_common/pico_lwip/doc.h create mode 100644 src/rp2_common/pico_lwip/include/arch/cc.h create mode 100644 src/rp2_common/pico_lwip/nosys.c create mode 100644 src/rp2_common/pico_lwip/random.c create mode 100644 test/kitchen_sink/lwipopts.h diff --git a/.gitmodules b/.gitmodules index 4846c9c..a37debe 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,9 @@ [submodule "tinyusb"] path = lib/tinyusb url = https://github.com/hathach/tinyusb.git +[submodule "lib/cyw43-driver"] + path = lib/cyw43-driver + url = https://github.com/georgerobotics/cyw43-driver.git +[submodule "lib/lwip"] + path = lib/lwip + url = https://github.com/lwip-tcpip/lwip.git diff --git a/README.md b/README.md index db6cef6..26084ca 100644 --- a/README.md +++ b/README.md @@ -110,6 +110,24 @@ instructions for other platforms, and just in general, we recommend you see [Ras ``` + * Or by cloning the SDK locally, but without copying `pico_sdk_import.cmake`: + 1. `git clone` this Raspberry Pi Pico SDK repository + 2. Setup a `CMakeLists.txt` like: + + ```cmake + cmake_minimum_required(VERSION 3.13) + + # initialize the SDK directly + include(/path/to/pico-sdk/pico_sdk_init.cmake) + + project(my_project) + + # initialize the Raspberry Pi Pico SDK + pico_sdk_init() + + # rest of your project + + ``` 1. Write your code (see [pico-examples](https://github.com/raspberrypi/pico-examples) or the [Raspberry Pi Pico C/C++ SDK](https://rptl.io/pico-c-sdk) documentation for more information) About the simplest you can do is a single source file (e.g. hello_world.c) diff --git a/docs/Doxyfile.in b/docs/Doxyfile.in index cc133f8..5dc4be7 100644 --- a/docs/Doxyfile.in +++ b/docs/Doxyfile.in @@ -60,4 +60,5 @@ PREDEFINED = __not_in_flash_func(x) \ __time_critical_func(x) \ __not_in_flash(x)= \ __no_inline_not_in_flash(x)= \ - __attribute__(x)= + __attribute__(x)= \ + DOXYGEN_GENERATION= diff --git a/docs/index.h b/docs/index.h index c7ef735..a334fc3 100644 --- a/docs/index.h +++ b/docs/index.h @@ -56,6 +56,13 @@ * \defgroup tinyusb_host tinyusb_host * @} * + * \defgroup networking Networking Libraries + * Functions for implementing networking + * @{ + * \defgroup pico_lwip pico_lwip + * \defgroup pico_cyw43_arch pico_cyw43_arch + * @} + * * \defgroup runtime Runtime Infrastructure * Libraries that are used to provide efficient implementation of certain * language level and C library functions, as well as CMake INTERFACE libraries diff --git a/lib/cyw43-driver b/lib/cyw43-driver new file mode 160000 index 0000000..195dfcc --- /dev/null +++ b/lib/cyw43-driver @@ -0,0 +1 @@ +Subproject commit 195dfcc10bb6f379e3dea45147590db2203d3c7b diff --git a/lib/lwip b/lib/lwip new file mode 160000 index 0000000..239918c --- /dev/null +++ b/lib/lwip @@ -0,0 +1 @@ +Subproject commit 239918ccc173cb2c2a62f41a40fd893f57faf1d6 diff --git a/src/boards/include/boards/pico_w.h b/src/boards/include/boards/pico_w.h new file mode 100644 index 0000000..b4a8bd7 --- /dev/null +++ b/src/boards/include/boards/pico_w.h @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2022 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +// ----------------------------------------------------- +// NOTE: THIS HEADER IS ALSO INCLUDED BY ASSEMBLER SO +// SHOULD ONLY CONSIST OF PREPROCESSOR DIRECTIVES +// ----------------------------------------------------- + +// This header may be included by other board headers as "boards/pico.h" + +#ifndef _BOARDS_PICO_W_H +#define _BOARDS_PICO_W_H + +// For board detection +#define RASPBERRYPI_PICO_W + +// --- UART --- +#ifndef PICO_DEFAULT_UART +#define PICO_DEFAULT_UART 0 +#endif +#ifndef PICO_DEFAULT_UART_TX_PIN +#define PICO_DEFAULT_UART_TX_PIN 0 +#endif +#ifndef PICO_DEFAULT_UART_RX_PIN +#define PICO_DEFAULT_UART_RX_PIN 1 +#endif + +// --- LED --- +// no PICO_DEFAULT_LED_PIN - LED is on Wireless chip +// no PICO_DEFAULT_WS2812_PIN + +// --- I2C --- +#ifndef PICO_DEFAULT_I2C +#define PICO_DEFAULT_I2C 0 +#endif +#ifndef PICO_DEFAULT_I2C_SDA_PIN +#define PICO_DEFAULT_I2C_SDA_PIN 4 +#endif +#ifndef PICO_DEFAULT_I2C_SCL_PIN +#define PICO_DEFAULT_I2C_SCL_PIN 5 +#endif + +// --- SPI --- +#ifndef PICO_DEFAULT_SPI +#define PICO_DEFAULT_SPI 0 +#endif +#ifndef PICO_DEFAULT_SPI_SCK_PIN +#define PICO_DEFAULT_SPI_SCK_PIN 18 +#endif +#ifndef PICO_DEFAULT_SPI_TX_PIN +#define PICO_DEFAULT_SPI_TX_PIN 19 +#endif +#ifndef PICO_DEFAULT_SPI_RX_PIN +#define PICO_DEFAULT_SPI_RX_PIN 16 +#endif +#ifndef PICO_DEFAULT_SPI_CSN_PIN +#define PICO_DEFAULT_SPI_CSN_PIN 17 +#endif + +// --- FLASH --- + +#define PICO_BOOT_STAGE2_CHOOSE_W25Q080 1 + +#ifndef PICO_FLASH_SPI_CLKDIV +#define PICO_FLASH_SPI_CLKDIV 2 +#endif + +#ifndef PICO_FLASH_SIZE_BYTES +#define PICO_FLASH_SIZE_BYTES (2 * 1024 * 1024) +#endif + +// note the SMSP mode pin is on WL_GPIO1 +// #define PICO_SMPS_MODE_PIN + +#ifndef PICO_RP2040_B0_SUPPORTED +#define PICO_RP2040_B0_SUPPORTED 0 +#endif + +#ifndef PICO_RP2040_B1_SUPPORTED +#define PICO_RP2040_B1_SUPPORTED 0 +#endif + +#ifndef CYW43_PIN_WL_HOST_WAKE +#define CYW43_PIN_WL_HOST_WAKE 24 +#endif + +#ifndef CYW43_PIN_WL_REG_ON +#define CYW43_PIN_WL_REG_ON 23 +#endif + +#ifndef CYW43_WL_GPIO_COUNT +#define CYW43_WL_GPIO_COUNT 3 +#endif + +#ifndef CYW43_WL_GPIO_LED_PIN +#define CYW43_WL_GPIO_LED_PIN 0 +#endif + +#endif diff --git a/src/boards/pico_w.cmake b/src/boards/pico_w.cmake new file mode 100644 index 0000000..15b901d --- /dev/null +++ b/src/boards/pico_w.cmake @@ -0,0 +1,2 @@ +set(PICO_CYW43_SUPPORTED "1" CACHE INTERNAL "Try to add support for PICO_CYW43") +include(${CMAKE_CURRENT_LIST_DIR}/generic_board.cmake) diff --git a/src/common/pico_base/include/pico/error.h b/src/common/pico_base/include/pico/error.h index fadb45e..a5cbc39 100644 --- a/src/common/pico_base/include/pico/error.h +++ b/src/common/pico_base/include/pico/error.h @@ -10,14 +10,18 @@ #ifndef __ASSEMBLER__ /*! - * Common return codes from pico_sdk methods that return a status + * \brief Common return codes from pico_sdk methods that return a status + * \ingroup pico_base */ -enum { +enum pico_error_codes { PICO_OK = 0, PICO_ERROR_NONE = 0, PICO_ERROR_TIMEOUT = -1, PICO_ERROR_GENERIC = -2, PICO_ERROR_NO_DATA = -3, + PICO_ERROR_NOT_PERMITTED = -4, + PICO_ERROR_INVALID_ARG = -5, + PICO_ERROR_IO = -6, }; #endif // !__ASSEMBLER__ diff --git a/src/rp2_common/CMakeLists.txt b/src/rp2_common/CMakeLists.txt index 4ca55be..0d0f9b9 100644 --- a/src/rp2_common/CMakeLists.txt +++ b/src/rp2_common/CMakeLists.txt @@ -58,6 +58,10 @@ if (NOT PICO_BARE_METAL) pico_add_subdirectory(tinyusb) pico_add_subdirectory(pico_stdio_usb) + pico_add_subdirectory(cyw43_driver) + pico_add_subdirectory(pico_lwip) + pico_add_subdirectory(pico_cyw43_arch) + pico_add_subdirectory(pico_stdlib) pico_add_subdirectory(pico_cxx_options) diff --git a/src/rp2_common/cyw43_driver/CMakeLists.txt b/src/rp2_common/cyw43_driver/CMakeLists.txt new file mode 100644 index 0000000..8951df4 --- /dev/null +++ b/src/rp2_common/cyw43_driver/CMakeLists.txt @@ -0,0 +1,81 @@ +if (DEFINED ENV{PICO_CYW43_DRIVER_PATH} AND (NOT PICO_CYW43_DRIVER_PATH)) + set(PICO_CYW43_DRIVER_PATH $ENV{PICO_CYW43_DRIVER_PATH}) + message("Using PICO_CYW43_DRIVER_PATH from environment ('${PICO_CYW43_DRIVER_PATH}')") +endif() + +set(CYW43_DRIVER_TEST_FILE "src/cyw43.h") + +if (NOT PICO_CYW43_DRIVER_PATH) + set(PICO_CYW43_DRIVER_PATH ${PICO_SDK_PATH}/lib/cyw43-driver) + if (PICO_CYW43_SUPPORTED AND NOT EXISTS ${PICO_CYW43_DRIVER_PATH}/${CYW43_DRIVER_TEST_FILE}) + message(WARNING "cyw43-driver submodule has not been initialized; Pico W wireless support will be unavailable +hint: try 'git submodule update --init' from your SDK directory (${PICO_SDK_PATH}).") + endif() +elseif (NOT EXISTS ${PICO_CYW43_DRIVER_PATH}/${CYW43_DRIVER_TEST_FILE}) + message(WARNING "PICO_CYW43_DRIVER_PATH specified but content not present.") +endif() + +if (EXISTS ${PICO_CYW43_DRIVER_PATH}/${CYW43_DRIVER_TEST_FILE}) + message("cyw43-driver available at ${PICO_CYW43_DRIVER_PATH}") + + pico_register_common_scope_var(PICO_CYW43_DRIVER_PATH) + + # base driver without our bus + add_library(cyw43_driver_base INTERFACE) + target_sources(cyw43_driver_base INTERFACE + ${PICO_CYW43_DRIVER_PATH}/src/cyw43_ll.c + ${PICO_CYW43_DRIVER_PATH}/src/cyw43_stats.c + ${PICO_CYW43_DRIVER_PATH}/src/cyw43_lwip.c + ${PICO_CYW43_DRIVER_PATH}/src/cyw43_ctrl.c + ) + target_include_directories(cyw43_driver_base INTERFACE + ${PICO_CYW43_DRIVER_PATH}/src + ${PICO_CYW43_DRIVER_PATH}/firmware + ) + + # Build the driver for cyw43 for pico w + + # Firmware stuff + set(CYW43_FIRMWARE_BIN 43439A0-7.95.49.00.combined) + string(REGEX REPLACE [\\\.\-] _ CYW43_FIRMWARE_BIN_ ${CYW43_FIRMWARE_BIN}) + string(REGEX MATCH [^_]+_?[^_]*_?[^_]*_?[^_]*_?[^_]* CYW43_FIRMWARE_PRETTY ${CYW43_FIRMWARE_BIN_}) + set(CYW43_FIRMWARE_PRETTY fw_${CYW43_FIRMWARE_PRETTY}) + set(RESOURCE_SECNAME .big_const) + set(RESOURCE_SECFLAGS contents,alloc,load,readonly,data) + set(CYW43_FIRMWARE_OBJ ${CMAKE_CURRENT_BINARY_DIR}/cyw43_resource.o) + + add_custom_target(cyw43_firmware_package DEPENDS ${CYW43_FIRMWARE_OBJ}) + + # cyw43_resource.o contains the WiFi and BT firmware as a binary blob + add_custom_command( + OUTPUT ${CYW43_FIRMWARE_OBJ} + DEPENDS ${PICO_CYW43_DRIVER_PATH}/firmware/${CYW43_FIRMWARE_BIN} + WORKING_DIRECTORY ${PICO_CYW43_DRIVER_PATH}/firmware + COMMAND ${CMAKE_OBJCOPY} -I binary -O elf32-littlearm -B arm + --readonly-text + --rename-section .data=${RESOURCE_SECNAME},${RESOURCE_SECFLAGS} + --redefine-sym _binary_${CYW43_FIRMWARE_BIN_}_start=${CYW43_FIRMWARE_PRETTY}_start + --redefine-sym _binary_${CYW43_FIRMWARE_BIN_}_end=${CYW43_FIRMWARE_PRETTY}_end + --redefine-sym _binary_${CYW43_FIRMWARE_BIN_}_size=${CYW43_FIRMWARE_PRETTY}_size + ${CYW43_FIRMWARE_BIN} ${CYW43_FIRMWARE_OBJ} + ) + + add_library(cyw43_driver_picow INTERFACE) + target_sources(cyw43_driver_picow INTERFACE + ${CMAKE_CURRENT_LIST_DIR}/cyw43_bus_pio_spi.c + ) + pico_generate_pio_header(cyw43_driver_picow ${CMAKE_CURRENT_LIST_DIR}/cyw43_bus_pio_spi.pio) + add_dependencies(cyw43_driver_picow INTERFACE cyw43_firmware_package) + target_link_libraries(cyw43_driver_picow INTERFACE + ${CYW43_FIRMWARE_OBJ} + ) + target_link_libraries(cyw43_driver_picow INTERFACE + cyw43_driver_base + pico_stdlib + hardware_pio + hardware_dma + hardware_exception + ) + + pico_promote_common_scope_vars() +endif() diff --git a/src/rp2_common/cyw43_driver/cyw43_bus_pio_spi.c b/src/rp2_common/cyw43_driver/cyw43_bus_pio_spi.c new file mode 100644 index 0000000..0fd17f4 --- /dev/null +++ b/src/rp2_common/cyw43_driver/cyw43_bus_pio_spi.c @@ -0,0 +1,545 @@ +/* + * Copyright (c) 2020 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include +#include +#include + +#include "pico/stdlib.h" +#include "hardware/gpio.h" +#include "hardware/pio.h" +#include "hardware/clocks.h" +#include "hardware/structs/iobank0.h" +#include "hardware/sync.h" +#include "hardware/dma.h" +#include "cyw43_bus_pio_spi.pio.h" +#include "cyw43.h" +#include "cyw43_internal.h" +#include "cyw43_spi.h" +#include "cyw43_debug_pins.h" + +#if CYW43_SPI_PIO +#define WL_REG_ON 23 +#define DATA_OUT_PIN 24u +#define DATA_IN_PIN 24u +#define IRQ_PIN 24u +// #define MONITOR_PIN 3u +#define CLOCK_PIN 29u +#define CS_PIN 25u +#define IRQ_SAMPLE_DELAY_NS 100 + +#define SPI_PROGRAM_NAME spi_gap01_sample0 +#define SPI_PROGRAM_FUNC __CONCAT(SPI_PROGRAM_NAME, _program) +#define SPI_PROGRAM_GET_DEFAULT_CONFIG_FUNC __CONCAT(SPI_PROGRAM_NAME, _program_get_default_config) +#define SPI_OFFSET_END __CONCAT(SPI_PROGRAM_NAME, _offset_end) +#define SPI_OFFSET_LP1_END __CONCAT(SPI_PROGRAM_NAME, _offset_lp1_end) + +#define CLOCK_DIV 2 +#define CLOCK_DIV_MINOR 0 +#define PADS_DRIVE_STRENGTH PADS_BANK0_GPIO0_DRIVE_VALUE_12MA + +#if !CYW43_USE_SPI +#error CYW43_USE_SPI should be true +#endif + +#ifndef NDEBUG +//#define ENABLE_SPI_DUMPING 1 +#endif + +// Set to 1 to enable +#if ENABLE_SPI_DUMPING //NDEBUG +#if 0 +#define DUMP_SPI_TRANSACTIONS(A) A +#else +static bool enable_spi_packet_dumping; // set to true to dump +#define DUMP_SPI_TRANSACTIONS(A) if (enable_spi_packet_dumping) {A} +#endif + +static uint32_t counter = 0; +#else +#define DUMP_SPI_TRANSACTIONS(A) +#endif + +//#define SWAP32(A) ((((A) & 0xff000000U) >> 8) | (((A) & 0xff0000U) << 8) | (((A) & 0xff00U) >> 8) | (((A) & 0xffU) << 8)) +__force_inline static uint32_t __swap16x2(uint32_t a) { + __asm ("rev16 %0, %0" : "+l" (a) : : ); + return a; +} +#define SWAP32(a) __swap16x2(a) + +#ifndef CYW43_SPI_PIO_PREFERRED_PIO +#define CYW43_SPI_PIO_PREFERRED_PIO 1 +#endif +static_assert(CYW43_SPI_PIO_PREFERRED_PIO >=0 && CYW43_SPI_PIO_PREFERRED_PIO < NUM_PIOS, ""); + +typedef struct { + pio_hw_t *pio; + uint8_t pio_func_sel; + int8_t pio_offset; + int8_t pio_sm; + int8_t dma_out; + int8_t dma_in; +} bus_data_t; + +static bus_data_t bus_data_instance; + +int cyw43_spi_init(cyw43_int_t *self) { + // Only does something if CYW43_LOGIC_DEBUG=1 + logic_debug_init(); + + static_assert(NUM_PIOS == 2, ""); + + pio_hw_t *pios[2] = {pio0, pio1}; + uint pio_index = CYW43_SPI_PIO_PREFERRED_PIO; + // Check we can add the program + if (!pio_can_add_program(pios[pio_index], &SPI_PROGRAM_FUNC)) { + pio_index ^= 1; + if (!pio_can_add_program(pios[pio_index], &SPI_PROGRAM_FUNC)) { + return CYW43_FAIL_FAST_CHECK(-CYW43_EIO); + } + } + assert(!self->bus_data); + self->bus_data = &bus_data_instance; + bus_data_t *bus_data = (bus_data_t *)self->bus_data; + bus_data->pio = pios[pio_index]; + bus_data->dma_in = -1; + bus_data->dma_out = -1; + + static_assert(GPIO_FUNC_PIO1 == GPIO_FUNC_PIO0 + 1, ""); + bus_data->pio_func_sel = GPIO_FUNC_PIO0 + pio_index; + bus_data->pio_sm = (int8_t)pio_claim_unused_sm(bus_data->pio, false); + if (bus_data->pio_sm < 0) { + cyw43_spi_deinit(self); + return CYW43_FAIL_FAST_CHECK(-CYW43_EIO); + } + + bus_data->pio_offset = pio_add_program(bus_data->pio, &SPI_PROGRAM_FUNC); + pio_sm_config config = SPI_PROGRAM_GET_DEFAULT_CONFIG_FUNC(bus_data->pio_offset); + + sm_config_set_clkdiv_int_frac(&config, CLOCK_DIV, CLOCK_DIV_MINOR); + hw_write_masked(&padsbank0_hw->io[CLOCK_PIN], + (uint)PADS_DRIVE_STRENGTH << PADS_BANK0_GPIO0_DRIVE_LSB, + PADS_BANK0_GPIO0_DRIVE_BITS + ); + hw_write_masked(&padsbank0_hw->io[CLOCK_PIN], + (uint)1 << PADS_BANK0_GPIO0_SLEWFAST_LSB, + PADS_BANK0_GPIO0_SLEWFAST_BITS + ); + + sm_config_set_out_pins(&config, DATA_OUT_PIN, 1); + sm_config_set_in_pins(&config, DATA_IN_PIN); + sm_config_set_set_pins(&config, DATA_OUT_PIN, 1); + sm_config_set_sideset(&config, 1, false, false); + sm_config_set_sideset_pins(&config, CLOCK_PIN); + sm_config_set_in_shift(&config, false, true, 32); + sm_config_set_out_shift(&config, false, true, 32); + hw_set_bits(&bus_data->pio->input_sync_bypass, 1u << DATA_IN_PIN); + pio_sm_set_config(bus_data->pio, bus_data->pio_sm, &config); + pio_sm_set_consecutive_pindirs(bus_data->pio, bus_data->pio_sm, CLOCK_PIN, 1, true); + gpio_set_function(DATA_OUT_PIN, bus_data->pio_func_sel); + gpio_set_function(CLOCK_PIN, bus_data->pio_func_sel); + + // Set data pin to pull down and schmitt + gpio_set_pulls(DATA_IN_PIN, false, true); + gpio_set_input_hysteresis_enabled(DATA_IN_PIN, true); + + pio_sm_exec(bus_data->pio, bus_data->pio_sm, pio_encode_set(pio_pins, 1)); + + bus_data->dma_out = (int8_t) dma_claim_unused_channel(false); + bus_data->dma_in = (int8_t) dma_claim_unused_channel(false); + if (bus_data->dma_out < 0 || bus_data->dma_in < 0) { + cyw43_spi_deinit(self); + return CYW43_FAIL_FAST_CHECK(-CYW43_EIO); + } + return 0; +} + +void cyw43_spi_deinit(cyw43_int_t *self) { + if (self->bus_data) { + bus_data_t *bus_data = (bus_data_t *)self->bus_data; + if (bus_data->pio_sm >= 0) { + if (bus_data->pio_offset != -1) + pio_remove_program(bus_data->pio, &SPI_PROGRAM_FUNC, bus_data->pio_offset); + pio_sm_unclaim(bus_data->pio, bus_data->pio_sm); + } + if (bus_data->dma_out >= 0) { + dma_channel_unclaim(bus_data->dma_out); + bus_data->dma_out = -1; + } + if (bus_data->dma_in >= 0) { + dma_channel_unclaim(bus_data->dma_in); + bus_data->dma_in = -1; + } + self->bus_data = NULL; + } +} + +static void cs_set(bool value) { + gpio_put(CS_PIN, value); +} + +static __noinline void ns_delay(uint32_t ns) { + // cycles = ns * clk_sys_hz / 1,000,000,000 + uint32_t cycles = ns * (clock_get_hz(clk_sys) >> 16u) / (1000000000u >> 16u); + busy_wait_at_least_cycles(cycles); +} + +static void start_spi_comms(cyw43_int_t *self) { + bus_data_t *bus_data = (bus_data_t *)self->bus_data; + // Pull CS low + cs_set(false); + gpio_set_function(DATA_OUT_PIN, bus_data->pio_func_sel); +} + +// we need to atomically de-assert CS and enable IRQ +static void stop_spi_comms(void) { + // from this point a positive edge will cause an IRQ to be pending + cs_set(true); + + // we need to wait a bit in case the irq line is incorrectly high + ns_delay(IRQ_SAMPLE_DELAY_NS); +} + +#if ENABLE_SPI_DUMPING +static void dump_bytes(const uint8_t *bptr, uint32_t len) { + unsigned int i = 0; + + for (i = 0; i < len;) { + if ((i & 0x0f) == 0) { + printf("\n"); + } else if ((i & 0x07) == 0) { + printf(" "); + } + printf("%02x ", bptr[i++]); + } + printf("\n"); +} +#endif + +int cyw43_spi_transfer(cyw43_int_t *self, const uint8_t *tx, size_t tx_length, uint8_t *rx, + size_t rx_length) { + + if ((tx == NULL) && (rx == NULL)) { + return CYW43_FAIL_FAST_CHECK(-CYW43_EINVAL); + } + + bus_data_t *bus_data = (bus_data_t *)self->bus_data; + start_spi_comms(self); + if (rx != NULL) { + if (tx == NULL) { + tx = rx; + assert(tx_length && tx_length < rx_length); + } + DUMP_SPI_TRANSACTIONS( + printf("[%lu] bus TX/RX %u bytes rx %u:", counter++, tx_length, rx_length); + dump_bytes(tx, tx_length); + ) + assert(!(tx_length & 3)); + assert(!(((uintptr_t)tx) & 3)); + assert(!(((uintptr_t)rx) & 3)); + assert(!(rx_length & 3)); + + pio_sm_set_enabled(bus_data->pio, bus_data->pio_sm, false); + pio_sm_set_wrap(bus_data->pio, bus_data->pio_sm, bus_data->pio_offset, bus_data->pio_offset + SPI_OFFSET_END - 1); + pio_sm_clear_fifos(bus_data->pio, bus_data->pio_sm); + pio_sm_set_pindirs_with_mask(bus_data->pio, bus_data->pio_sm, 1u << DATA_OUT_PIN, 1u << DATA_OUT_PIN); + pio_sm_restart(bus_data->pio, bus_data->pio_sm); + pio_sm_clkdiv_restart(bus_data->pio, bus_data->pio_sm); + pio_sm_put(bus_data->pio, bus_data->pio_sm, tx_length * 8 - 1); + pio_sm_exec(bus_data->pio, bus_data->pio_sm, pio_encode_out(pio_x, 32)); + pio_sm_put(bus_data->pio, bus_data->pio_sm, (rx_length - tx_length) * 8 - 1); + pio_sm_exec(bus_data->pio, bus_data->pio_sm, pio_encode_out(pio_y, 32)); + pio_sm_exec(bus_data->pio, bus_data->pio_sm, pio_encode_jmp(bus_data->pio_offset)); + dma_channel_abort(bus_data->dma_out); + dma_channel_abort(bus_data->dma_in); + + dma_channel_config out_config = dma_channel_get_default_config(bus_data->dma_out); + channel_config_set_bswap(&out_config, true); + channel_config_set_dreq(&out_config, pio_get_dreq(bus_data->pio, 0, true)); + + dma_channel_configure(bus_data->dma_out, &out_config, &bus_data->pio->txf[0], tx, tx_length / 4, true); + + dma_channel_config in_config = dma_channel_get_default_config(bus_data->dma_in); + channel_config_set_bswap(&in_config, true); + channel_config_set_dreq(&in_config, pio_get_dreq(bus_data->pio, 0, false)); + channel_config_set_write_increment(&in_config, true); + channel_config_set_read_increment(&in_config, false); + dma_channel_configure(bus_data->dma_in, &in_config, rx + tx_length, &bus_data->pio->rxf[0], rx_length / 4 - tx_length / 4, true); + + pio_sm_set_enabled(bus_data->pio, bus_data->pio_sm, true); + __compiler_memory_barrier(); + + dma_channel_wait_for_finish_blocking(bus_data->dma_out); + dma_channel_wait_for_finish_blocking(bus_data->dma_in); + + __compiler_memory_barrier(); + memset(rx, 0, tx_length); // make sure we don't have garbage in what would have been returned data if using real SPI + } else if (tx != NULL) { + DUMP_SPI_TRANSACTIONS( + printf("[%lu] bus TX only %u bytes:", counter++, tx_length); + dump_bytes(tx, tx_length); + ) + assert(!(((uintptr_t)tx) & 3)); + assert(!(tx_length & 3)); + pio_sm_set_enabled(bus_data->pio, bus_data->pio_sm, false); + pio_sm_set_wrap(bus_data->pio, bus_data->pio_sm, bus_data->pio_offset, bus_data->pio_offset + SPI_OFFSET_LP1_END - 1); + pio_sm_clear_fifos(bus_data->pio, bus_data->pio_sm); + pio_sm_set_pindirs_with_mask(bus_data->pio, bus_data->pio_sm, 1u << DATA_OUT_PIN, 1u << DATA_OUT_PIN); + pio_sm_restart(bus_data->pio, bus_data->pio_sm); + pio_sm_clkdiv_restart(bus_data->pio, bus_data->pio_sm); + pio_sm_put(bus_data->pio, bus_data->pio_sm, tx_length * 8 - 1); + pio_sm_exec(bus_data->pio, bus_data->pio_sm, pio_encode_out(pio_x, 32)); + pio_sm_put(bus_data->pio, bus_data->pio_sm, 0); + pio_sm_exec(bus_data->pio, bus_data->pio_sm, pio_encode_out(pio_y, 32)); + pio_sm_exec(bus_data->pio, bus_data->pio_sm, pio_encode_jmp(bus_data->pio_offset)); + dma_channel_abort(bus_data->dma_out); + + dma_channel_config out_config = dma_channel_get_default_config(bus_data->dma_out); + channel_config_set_bswap(&out_config, true); + channel_config_set_dreq(&out_config, pio_get_dreq(bus_data->pio, 0, true)); + + dma_channel_configure(bus_data->dma_out, &out_config, &bus_data->pio->txf[0], tx, tx_length / 4, true); + + bus_data->pio->fdebug = 1u << PIO_FDEBUG_TXSTALL_LSB; + pio_sm_set_enabled(bus_data->pio, 0, true); + while (!(bus_data->pio->fdebug & (1u << PIO_FDEBUG_TXSTALL_LSB))) { + tight_loop_contents(); // todo timeout + } + __compiler_memory_barrier(); + pio_sm_set_enabled(bus_data->pio, bus_data->pio_sm, false); + pio_sm_set_consecutive_pindirs(bus_data->pio, bus_data->pio_sm, DATA_IN_PIN, 1, false); + } else if (rx != NULL) { /* currently do one at a time */ + DUMP_SPI_TRANSACTIONS( + printf("[%lu] bus TX %u bytes:", counter++, rx_length); + dump_bytes(rx, rx_length); + ) + panic_unsupported(); + } + pio_sm_exec(bus_data->pio, bus_data->pio_sm, pio_encode_mov(pio_pins, pio_null)); // for next time we turn output on + + stop_spi_comms(); + DUMP_SPI_TRANSACTIONS( + printf("RXed:"); + dump_bytes(rx, rx_length); + printf("\n"); + ) + + return 0; +} + +// Initialise our gpios +void cyw43_spi_gpio_setup(void) { + // Setup WL_REG_ON (23) + gpio_init(WL_REG_ON); + gpio_set_dir(WL_REG_ON, GPIO_OUT); + gpio_pull_up(WL_REG_ON); + + // Setup DO, DI and IRQ (24) + gpio_init(DATA_OUT_PIN); + gpio_set_dir(DATA_OUT_PIN, GPIO_OUT); + gpio_put(DATA_OUT_PIN, false); + + // Setup CS (25) + gpio_init(CS_PIN); + gpio_set_dir(CS_PIN, GPIO_OUT); + gpio_put(CS_PIN, true); +} + +// Reset wifi chip +void cyw43_spi_reset(void) { + gpio_put(WL_REG_ON, false); // off + sleep_ms(20); + gpio_put(WL_REG_ON, true); // on + sleep_ms(250); + + // Setup IRQ (24) - also used for DO, DI + gpio_init(IRQ_PIN); + gpio_set_dir(IRQ_PIN, GPIO_IN); +} + +static inline uint32_t make_cmd(bool write, bool inc, uint32_t fn, uint32_t addr, uint32_t sz) { + return write << 31 | inc << 30 | fn << 28 | (addr & 0x1ffff) << 11 | sz; +} + +#if CYW43_VERBOSE_DEBUG +static const char *func_name(int fn) { + switch (fn) + { + case BUS_FUNCTION: + return "BUS_FUNCTION"; + case BACKPLANE_FUNCTION: + return "BACKPLANE_FUNCTION"; + case WLAN_FUNCTION: + return "WLAN_FUNCTION"; + default: + return "UNKNOWN"; + } +} +#endif + +uint32_t read_reg_u32_swap(cyw43_int_t *self, uint32_t fn, uint32_t reg) { + uint32_t buf[2] = {0}; + assert(fn != BACKPLANE_FUNCTION); + buf[0] = SWAP32(make_cmd(false, true, fn, reg, 4)); + int ret = cyw43_spi_transfer(self, NULL, 4, (uint8_t *)buf, 8); + if (ret != 0) { + return ret; + } + return SWAP32(buf[1]); +} + +static inline uint32_t _cyw43_read_reg(cyw43_int_t *self, uint32_t fn, uint32_t reg, uint size) { + // Padding plus max read size of 32 bits + another 4? + static_assert(WHD_BUS_SPI_BACKPLANE_READ_PADD_SIZE % 4 == 0, ""); + uint32_t buf32[WHD_BUS_SPI_BACKPLANE_READ_PADD_SIZE/4 + 1 + 1]; + uint8_t *buf = (uint8_t *)buf32; + const uint32_t padding = (fn == BACKPLANE_FUNCTION) ? WHD_BUS_SPI_BACKPLANE_READ_PADD_SIZE : 0; // Add response delay + buf32[0] = make_cmd(false, true, fn, reg, size + padding); + + if (fn == BACKPLANE_FUNCTION) { + logic_debug_set(pin_BACKPLANE_READ, 1); + } + int ret = cyw43_spi_transfer(self, NULL, 4, buf, 8 + padding); + if (fn == BACKPLANE_FUNCTION) { + logic_debug_set(pin_BACKPLANE_READ, 0); + } + + if (ret != 0) { + return ret; + } + uint32_t result = buf32[padding > 0 ? 2 : 1]; + CYW43_VDEBUG("cyw43_read_reg_u%d %s 0x%lx=0x%lx\n", size * 8, func_name(fn), reg, result); + return result; +} + +uint32_t cyw43_read_reg_u32(cyw43_int_t *self, uint32_t fn, uint32_t reg) { + return _cyw43_read_reg(self, fn, reg, 4); +} + +int cyw43_read_reg_u16(cyw43_int_t *self, uint32_t fn, uint32_t reg) { + return _cyw43_read_reg(self, fn, reg, 2); +} + +int cyw43_read_reg_u8(cyw43_int_t *self, uint32_t fn, uint32_t reg) { + return _cyw43_read_reg(self, fn, reg, 1); +} + +// This is only used to switch the word order on boot +int write_reg_u32_swap(cyw43_int_t *self, uint32_t fn, uint32_t reg, uint32_t val) { + uint32_t buf[2]; + // Boots up in little endian so command needs swapping too + buf[0] = SWAP32(make_cmd(true, true, fn, reg, 4)); + buf[1] = SWAP32(val); + int ret = cyw43_spi_transfer(self, (uint8_t *)buf, 8, NULL, 0); + CYW43_VDEBUG("write_reg_u32_swap %s 0x%lx=0x%lx\n", func_name(fn), reg, val); + return ret; +} + +static inline int _cyw43_write_reg(cyw43_int_t *self, uint32_t fn, uint32_t reg, uint32_t val, uint size) { + uint32_t buf[2]; + buf[0] = make_cmd(true, true, fn, reg, size); + buf[1] = val; + if (fn == BACKPLANE_FUNCTION) { + // In case of f1 overflow + self->last_size = 8; + self->last_header[0] = buf[0]; + self->last_header[1] = buf[1]; + self->last_backplane_window = self->cur_backplane_window; + } + + if (fn == BACKPLANE_FUNCTION) { + logic_debug_set(pin_BACKPLANE_WRITE, 1); + } + + int ret = cyw43_spi_transfer(self, (uint8_t *)buf, 8, NULL, 0); + + if (fn == BACKPLANE_FUNCTION) { + logic_debug_set(pin_BACKPLANE_WRITE, 0); + } + + CYW43_VDEBUG("cyw43_write_reg_u%d %s 0x%lx=0x%lx\n", size * 8, func_name(fn), reg, val); + return ret; +} + +int cyw43_write_reg_u32(cyw43_int_t *self, uint32_t fn, uint32_t reg, uint32_t val) { + return _cyw43_write_reg(self, fn, reg, val, 4); +} + +int cyw43_write_reg_u16(cyw43_int_t *self, uint32_t fn, uint32_t reg, uint16_t val) { + return _cyw43_write_reg(self, fn, reg, val, 2); +} + +int cyw43_write_reg_u8(cyw43_int_t *self, uint32_t fn, uint32_t reg, uint32_t val) { + return _cyw43_write_reg(self, fn, reg, val, 1); +} + +#if MAX_BLOCK_SIZE > 0x7f8 +#error Block size is wrong for SPI +#endif + +// Assumes we're reading into spid_buf +int cyw43_read_bytes(cyw43_int_t *self, uint32_t fn, uint32_t addr, size_t len, uint8_t *buf) { + assert(fn != BACKPLANE_FUNCTION || (len <= 64 && (addr + len) <= 0x8000)); + const uint32_t padding = (fn == BACKPLANE_FUNCTION) ? 4 : 0; // Add response delay + size_t aligned_len = (len + 3) & ~3; + assert(aligned_len > 0 && aligned_len <= 0x7f8); + self->spi_header[padding > 0 ? 0 : 1] = make_cmd(false, true, fn, addr, len + padding); + if (fn == WLAN_FUNCTION) { + logic_debug_set(pin_WIFI_RX, 1); + } + int ret = cyw43_spi_transfer(self, NULL, 4, (uint8_t *)&self->spi_header[padding > 0 ? 0 : 1], aligned_len + 4 + padding); + if (fn == WLAN_FUNCTION) { + logic_debug_set(pin_WIFI_RX, 0); + } + if (ret != 0) { + printf("cyw43_read_bytes error %d", ret); + return ret; + } + if (buf != self->spid_buf) { // avoid a copy in the usual case just to add the header + memcpy(buf, self->spid_buf, len); + } + return 0; +} + +// See whd_bus_spi_transfer_bytes +// Note, uses spid_buf if src isn't using it already +// Apart from firmware download this appears to only be used for wlan functions? +int cyw43_write_bytes(cyw43_int_t *self, uint32_t fn, uint32_t addr, size_t len, const uint8_t *src) { + assert(fn != BACKPLANE_FUNCTION || (len <= 64 && (addr + len) <= 0x8000)); + size_t aligned_len = (len + 3) & ~3u; + assert(aligned_len > 0 && aligned_len <= 0x7f8); + if (fn == WLAN_FUNCTION) { + // Wait for FIFO to be ready to accept data + int f2_ready_attempts = 1000; + while (f2_ready_attempts-- > 0) { + uint32_t bus_status = cyw43_read_reg_u32(self, BUS_FUNCTION, SPI_STATUS_REGISTER); + if (bus_status & STATUS_F2_RX_READY) { + logic_debug_set(pin_F2_RX_READY_WAIT, 0); + break; + } else { + logic_debug_set(pin_F2_RX_READY_WAIT, 1); + } + } + if (f2_ready_attempts <= 0) { + printf("F2 not ready\n"); + return CYW43_FAIL_FAST_CHECK(-CYW43_EIO); + } + } + if (src == self->spid_buf) { // avoid a copy in the usual case just to add the header + self->spi_header[1] = make_cmd(true, true, fn, addr, len); + logic_debug_set(pin_WIFI_TX, 1); + int res = cyw43_spi_transfer(self, (uint8_t *)&self->spi_header[1], aligned_len + 4, NULL, 0); + logic_debug_set(pin_WIFI_TX, 0); + return res; + } else { + // todo: would be nice to get rid of this. Only used for firmware download? + assert(src < self->spid_buf || src >= (self->spid_buf + sizeof(self->spid_buf))); + self->spi_header[1] = make_cmd(true, true, fn, addr, len); + memcpy(self->spid_buf, src, len); + return cyw43_spi_transfer(self, (uint8_t *)&self->spi_header[1], aligned_len + 4, NULL, 0); + } +} +#endif diff --git a/src/rp2_common/cyw43_driver/cyw43_bus_pio_spi.pio b/src/rp2_common/cyw43_driver/cyw43_bus_pio_spi.pio new file mode 100644 index 0000000..ea0d195 --- /dev/null +++ b/src/rp2_common/cyw43_driver/cyw43_bus_pio_spi.pio @@ -0,0 +1,61 @@ +; +; Copyright (c) 2020 Raspberry Pi (Trading) Ltd. +; +; SPDX-License-Identifier: BSD-3-Clause +; + +.program spi_gap0_sample1 +.side_set 1 + +; always transmit multiple of 32 bytes +lp: out pins, 1 side 0 + jmp x-- lp side 1 +public lp1_end: + set pindirs, 0 side 0 +lp2: + in pins, 1 side 1 + jmp y-- lp2 side 0 +public end: + +.program spi_gap01_sample0 +.side_set 1 + +; always transmit multiple of 32 bytes +lp: out pins, 1 side 0 + jmp x-- lp side 1 +public lp1_end: + set pindirs, 0 side 0 + nop side 1 +lp2: + in pins, 1 side 0 + jmp y-- lp2 side 1 +public end: + +.program spi_gap010_sample1 +.side_set 1 + +; always transmit multiple of 32 bytes +lp: out pins, 1 side 0 + jmp x-- lp side 1 +public lp1_end: + set pindirs, 0 side 0 + nop side 1 + nop side 0 +lp2: + in pins, 1 side 1 + jmp y-- lp2 side 0 +public end: + +.program spi_gap0_sample1_regular +.side_set 1 + +; always transmit multiple of 32 bytes +lp: out pins, 1 side 0 + jmp x-- lp side 1 +public lp1_end: + set pindirs, 0 side 0 +lp2: + in pins, 1 side 1 + jmp y-- lp2 side 0 +public end: + diff --git a/src/rp2_common/hardware_dma/dma.c b/src/rp2_common/hardware_dma/dma.c index 90fde06..f142b53 100644 --- a/src/rp2_common/hardware_dma/dma.c +++ b/src/rp2_common/hardware_dma/dma.c @@ -36,6 +36,12 @@ void dma_channel_unclaim(uint channel) { hw_claim_clear((uint8_t *) &_claimed, channel); } +void dma_unclaim_mask(uint32_t mask) { + for(uint i = 0; mask; i++, mask >>= 1u) { + if (mask & 1u) dma_channel_unclaim(i); + } +} + int dma_claim_unused_channel(bool required) { return hw_claim_unused_from_range((uint8_t*)&_claimed, required, 0, NUM_DMA_CHANNELS-1, "No DMA channels are available"); } diff --git a/src/rp2_common/hardware_dma/include/hardware/dma.h b/src/rp2_common/hardware_dma/include/hardware/dma.h index 8ebd2e5..ec73564 100644 --- a/src/rp2_common/hardware_dma/include/hardware/dma.h +++ b/src/rp2_common/hardware_dma/include/hardware/dma.h @@ -88,12 +88,17 @@ void dma_claim_mask(uint32_t channel_mask); /*! \brief Mark a dma channel as no longer used * \ingroup hardware_dma * - * Method for cooperative claiming of hardware. - * * \param channel the dma channel to release */ void dma_channel_unclaim(uint channel); +/*! \brief Mark multiple dma channels as no longer used + * \ingroup hardware_dma + * + * \param channel_mask Bitfield of all channels to unclaim (bit 0 == channel 0, bit 1 == channel 1 etc) + */ +void dma_unclaim_mask(uint32_t channel_mask); + /*! \brief Claim a free dma channel * \ingroup hardware_dma * diff --git a/src/rp2_common/pico_cyw43_arch/CMakeLists.txt b/src/rp2_common/pico_cyw43_arch/CMakeLists.txt new file mode 100644 index 0000000..ede03f1 --- /dev/null +++ b/src/rp2_common/pico_cyw43_arch/CMakeLists.txt @@ -0,0 +1,66 @@ +if (PICO_CYW43_SUPPORTED) # set by BOARD=pico-w + if (TARGET cyw43_driver_picow) + message("Enabling build support for Pico W wireless.") + + pico_add_impl_library(pico_cyw43_arch) + target_sources(pico_cyw43_arch INTERFACE + ${CMAKE_CURRENT_LIST_DIR}/cyw43_arch.c + ${CMAKE_CURRENT_LIST_DIR}/cyw43_arch_poll.c + ${CMAKE_CURRENT_LIST_DIR}/cyw43_arch_threadsafe_background.c + ${CMAKE_CURRENT_LIST_DIR}/cyw43_arch_freertos.c + ) + + target_include_directories(pico_cyw43_arch INTERFACE + ${CMAKE_CURRENT_LIST_DIR}/include) + + target_link_libraries(pico_cyw43_arch INTERFACE + pico_unique_id + cyw43_driver_picow) + + if (NOT TARGET pico_lwip) + message(WARNING "lwIP is not available; Full Pico W wireless support will be unavailable too") + else() + add_library(pico_cyw43_arch_lwip_poll INTERFACE) + target_link_libraries(pico_cyw43_arch_lwip_poll INTERFACE + pico_cyw43_arch + pico_lwip + pico_lwip_nosys) + target_compile_definitions(pico_cyw43_arch_lwip_poll INTERFACE + CYW43_LWIP=1 + PICO_CYW43_ARCH_POLL=1 + ) + + add_library(pico_cyw43_arch_lwip_threadsafe_background INTERFACE) + target_link_libraries(pico_cyw43_arch_lwip_threadsafe_background INTERFACE + pico_cyw43_arch + pico_lwip + pico_lwip_nosys) + target_compile_definitions(pico_cyw43_arch_lwip_threadsafe_background INTERFACE + CYW43_LWIP=1 + PICO_CYW43_ARCH_THREADSAFE_BACKGROUND=1 + ) + + add_library(pico_cyw43_arch_lwip_sys_freertos INTERFACE) + target_link_libraries(pico_cyw43_arch_lwip_sys_freertos INTERFACE + pico_cyw43_arch + pico_lwip + pico_lwip_contrib_freertos) + target_compile_definitions(pico_cyw43_arch_lwip_sys_freertos INTERFACE + CYW43_LWIP=1 + LWIP_PROVIDE_ERRNO=1 + PICO_CYW43_ARCH_FREERTOS=1 + ) + endif() + + add_library(pico_cyw43_arch_none INTERFACE) + target_link_libraries(pico_cyw43_arch_none INTERFACE pico_cyw43_arch) + target_compile_definitions(pico_cyw43_arch_none INTERFACE + CYW43_LWIP=0 + PICO_CYW43_ARCH_THREADSAFE_BACKGROUND=1 # none still uses threadsafe_background to make gpio use easy + ) + endif() +endif() + +if (PICO_CYW43_DRIVER_PATH AND EXISTS "${PICO_CYW43_DRIVER_PATH}") + pico_add_doxygen(${PICO_CYW43_DRIVER_PATH}/src) +endif() \ No newline at end of file diff --git a/src/rp2_common/pico_cyw43_arch/cyw43_arch.c b/src/rp2_common/pico_cyw43_arch/cyw43_arch.c new file mode 100644 index 0000000..c328d6e --- /dev/null +++ b/src/rp2_common/pico_cyw43_arch/cyw43_arch.c @@ -0,0 +1,145 @@ +/* + * Copyright (c) 2022 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include +#include +#include + +#include "pico/unique_id.h" +#include "cyw43.h" +#include "pico/cyw43_arch.h" +#include "cyw43_ll.h" +#include "cyw43_stats.h" + +#if CYW43_ARCH_DEBUG_ENABLED +#define CYW43_ARCH_DEBUG(...) printf(__VA_ARGS__) +#else +#define CYW43_ARCH_DEBUG(...) ((void)0) +#endif + +static uint32_t country_code = PICO_CYW43_ARCH_DEFAULT_COUNTRY_CODE; + +void cyw43_arch_enable_sta_mode() { + assert(cyw43_is_initialized(&cyw43_state)); + cyw43_wifi_set_up(&cyw43_state, CYW43_ITF_STA, true, cyw43_arch_get_country_code()); +} + +void cyw43_arch_enable_ap_mode(const char *ssid, const char *password, uint32_t auth) { + assert(cyw43_is_initialized(&cyw43_state)); + cyw43_wifi_ap_set_ssid(&cyw43_state, strlen(ssid), (const uint8_t *) ssid); + if (password) { + cyw43_wifi_ap_set_password(&cyw43_state, strlen(password), (const uint8_t *) password); + cyw43_wifi_ap_set_auth(&cyw43_state, auth); + } else { + cyw43_wifi_ap_set_auth(&cyw43_state, CYW43_AUTH_OPEN); + } + cyw43_wifi_set_up(&cyw43_state, CYW43_ITF_AP, true, cyw43_arch_get_country_code()); +} + +#if CYW43_ARCH_DEBUG_ENABLED +// Return a string for the wireless state +static const char* status_name(int status) +{ + switch (status) { + case CYW43_LINK_DOWN: + return "link down"; + case CYW43_LINK_JOIN: + return "joining"; + case CYW43_LINK_NOIP: + return "no ip"; + case CYW43_LINK_UP: + return "link up"; + case CYW43_LINK_FAIL: + return "link fail"; + case CYW43_LINK_NONET: + return "network fail"; + case CYW43_LINK_BADAUTH: + return "bad auth"; + } + return "unknown"; +} +#endif + +int cyw43_arch_wifi_connect_async(const char *ssid, const char *pw, uint32_t auth) { + if (!pw) auth = CYW43_AUTH_OPEN; + // Connect to wireless + return cyw43_wifi_join(&cyw43_state, strlen(ssid), (const uint8_t *)ssid, pw ? strlen(pw) : 0, (const uint8_t *)pw, auth, NULL, CYW43_ITF_STA); +} + +// Connect to wireless, return with success when an IP address has been assigned +int cyw43_arch_wifi_connect_until(const char *ssid, const char *pw, uint32_t auth, absolute_time_t until) { + int err = cyw43_arch_wifi_connect_async(ssid, pw, auth); + if (err) return err; + + int status = CYW43_LINK_UP + 1; + while(status >= 0 && status != CYW43_LINK_UP) { + int new_status = cyw43_tcpip_link_status(&cyw43_state, CYW43_ITF_STA); + if (new_status != status) { + status = new_status; + CYW43_ARCH_DEBUG("connect status: %s\n", status_name(status)); + } + // in case polling is required + cyw43_arch_poll(); + best_effort_wfe_or_timeout(until); + if (time_reached(until)) { + return PICO_ERROR_TIMEOUT; + } + } + return status == CYW43_LINK_UP ? 0 : status; +} + +int cyw43_arch_wifi_connect_blocking(const char *ssid, const char *pw, uint32_t auth) { + return cyw43_arch_wifi_connect_until(ssid, pw, auth, at_the_end_of_time); +} + +int cyw43_arch_wifi_connect_timeout_ms(const char *ssid, const char *pw, uint32_t auth, uint32_t timeout_ms) { + return cyw43_arch_wifi_connect_until(ssid, pw, auth, make_timeout_time_ms(timeout_ms)); +} + +// todo maybe add an #ifdef in cyw43_driver +uint32_t storage_read_blocks(__unused uint8_t *dest, __unused uint32_t block_num, __unused uint32_t num_blocks) { + // shouldn't be used + panic_unsupported(); +} + +// Generate a mac address if one is not set in otp +void cyw43_hal_generate_laa_mac(__unused int idx, uint8_t buf[6]) { + CYW43_DEBUG("Warning. No mac in otp. Generating mac from board id\n"); + pico_unique_board_id_t board_id; + pico_get_unique_board_id(&board_id); + memcpy(buf, &board_id.id[2], 6); + buf[0] &= (uint8_t)~0x1; // unicast + buf[0] |= 0x2; // locally administered +} + +// Return mac address +void cyw43_hal_get_mac(__unused int idx, uint8_t buf[6]) { + // The mac should come from cyw43 otp. + // This is loaded into the state after the driver is initialised + // cyw43_hal_generate_laa_mac is called by the driver to generate a mac if otp is not set + memcpy(buf, cyw43_state.mac, 6); +} + +uint32_t cyw43_arch_get_country_code(void) { + return country_code; +} + +int cyw43_arch_init_with_country(uint32_t country) { + country_code = country; + return cyw43_arch_init(); +} + +void cyw43_arch_gpio_put(uint wl_gpio, bool value) { + invalid_params_if(CYW43_ARCH, wl_gpio >= CYW43_WL_GPIO_COUNT); + cyw43_gpio_set(&cyw43_state, (int)wl_gpio, value); +} + +bool cyw43_arch_gpio_get(uint wl_gpio) { + invalid_params_if(CYW43_ARCH, wl_gpio >= CYW43_WL_GPIO_COUNT); + bool value = false; + cyw43_gpio_get(&cyw43_state, (int)wl_gpio, &value); + return value; +} diff --git a/src/rp2_common/pico_cyw43_arch/cyw43_arch_freertos.c b/src/rp2_common/pico_cyw43_arch/cyw43_arch_freertos.c new file mode 100644 index 0000000..88dde6e --- /dev/null +++ b/src/rp2_common/pico_cyw43_arch/cyw43_arch_freertos.c @@ -0,0 +1,251 @@ +/* + * Copyright (c) 2022 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include + +#include "pico/cyw43_arch.h" + +#include "hardware/gpio.h" +#include "hardware/irq.h" +#include "hardware/sync.h" + +#include "cyw43_stats.h" + +#if CYW43_LWIP +#include +#endif + +#if PICO_CYW43_ARCH_FREERTOS + +// FreeRTOS includes +#include "FreeRTOS.h" +#include "timers.h" +#include "semphr.h" + +#if NO_SYS +#error example_cyw43_arch_frertos_sys requires NO_SYS=0 +#endif + +#ifndef CYW43_TASK_PRIORITY +#define CYW43_TASK_PRIORITY ( tskIDLE_PRIORITY + 4) +#endif + +#ifndef CYW43_SLEEP_CHECK_MS +#define CYW43_SLEEP_CHECK_MS 50 // How often to run lwip callback +#endif + +#define CYW43_GPIO_IRQ_HANDLER_PRIORITY 0x40 + +static void signal_cyw43_task(void); + +#if !LWIP_TCPIP_CORE_LOCKING_INPUT +static SemaphoreHandle_t cyw43_mutex; +#endif +static TimerHandle_t timer_handle; +static TaskHandle_t cyw43_task_handle; +static volatile bool cyw43_task_should_exit; +static SemaphoreHandle_t cyw43_worker_ran_sem; +static uint8_t cyw43_core_num; + +// Called in low priority pendsv interrupt only to do lwip processing and check cyw43 sleep +static void periodic_worker(__unused TimerHandle_t handle) +{ +#if CYW43_USE_STATS + static uint32_t counter; + if (counter++ % (30000 / LWIP_SYS_CHECK_MS) == 0) { + cyw43_dump_stats(); + } +#endif + + CYW43_STAT_INC(LWIP_RUN_COUNT); + if (cyw43_poll) { + if (cyw43_sleep > 0) { + if (--cyw43_sleep == 0) { + signal_cyw43_task(); + } + } + } +} + +void cyw43_await_background_or_timeout_us(uint32_t timeout_us) { + // if we are called from within an IRQ, then don't wait (we are only ever called in a polling loop) + assert(!portCHECK_IF_IN_ISR()); + xSemaphoreTake(cyw43_worker_ran_sem, pdMS_TO_TICKS(timeout_us / 1000)); +} + +// GPIO interrupt handler to tell us there's cyw43 has work to do +static void gpio_irq_handler(void) +{ + uint32_t events = gpio_get_irq_event_mask(CYW43_PIN_WL_HOST_WAKE); + if (events & GPIO_IRQ_LEVEL_HIGH) { + // As we use a high level interrupt, it will go off forever until it's serviced + // So disable the interrupt until this is done. It's re-enabled again by CYW43_POST_POLL_HOOK + // which is called at the end of cyw43_poll_func + gpio_set_irq_enabled(CYW43_PIN_WL_HOST_WAKE, GPIO_IRQ_LEVEL_HIGH, false); + signal_cyw43_task(); + CYW43_STAT_INC(IRQ_COUNT); + } +} + +// Low priority interrupt handler to perform background processing +static void cyw43_task(__unused void *param) { + do { + ulTaskNotifyTake(pdFALSE, portMAX_DELAY); + if (cyw43_task_should_exit) break; + cyw43_thread_enter(); + if (cyw43_poll) cyw43_poll(); + cyw43_thread_exit(); + xSemaphoreGive(cyw43_worker_ran_sem); + __sev(); // it is possible regular code is waiting on a WFE on the other core + } while (true); +} + +static void tcpip_init_done(void *param) { + xSemaphoreGive((SemaphoreHandle_t)param); +} + +int cyw43_arch_init(void) { + cyw43_core_num = get_core_num(); +#if configUSE_CORE_AFFINITY && configNUM_CORES > 1 + TaskHandle_t task_handle = xTaskGetCurrentTaskHandle(); + UBaseType_t affinity = vTaskCoreAffinityGet(task_handle); + // we must bind the main task to one core during init + vTaskCoreAffinitySet(task_handle, 1 << portGET_CORE_ID()); +#endif +#if !LWIP_TCPIP_CORE_LOCKING_INPUT + cyw43_mutex = xSemaphoreCreateRecursiveMutex(); +#endif + cyw43_init(&cyw43_state); + cyw43_worker_ran_sem = xSemaphoreCreateBinary(); + +#if CYW43_LWIP + SemaphoreHandle_t init_sem = xSemaphoreCreateBinary(); + tcpip_init(tcpip_init_done, init_sem); + xSemaphoreTake(init_sem, portMAX_DELAY); +#endif + + timer_handle = xTimerCreate( "cyw43_sleep_timer", // Just a text name, not used by the kernel. + pdMS_TO_TICKS(CYW43_SLEEP_CHECK_MS), + pdTRUE, // The timers will auto-reload themselves when they expire. + NULL, + periodic_worker); + + if (!timer_handle) { + return PICO_ERROR_GENERIC; + } + + gpio_add_raw_irq_handler_with_order_priority(IO_IRQ_BANK0, gpio_irq_handler, CYW43_GPIO_IRQ_HANDLER_PRIORITY); + gpio_set_irq_enabled(CYW43_PIN_WL_HOST_WAKE, GPIO_IRQ_LEVEL_HIGH, true); + irq_set_enabled(IO_IRQ_BANK0, true); + + cyw43_task_should_exit = false; + xTaskCreate(cyw43_task, "CYW43 Worker", configMINIMAL_STACK_SIZE, NULL, CYW43_TASK_PRIORITY, &cyw43_task_handle); +#if configUSE_CORE_AFFINITY && configNUM_CORES > 1 + // the cyw43 task mus tbe on the same core so it can restore IRQs + vTaskCoreAffinitySet(cyw43_task_handle, 1 << portGET_CORE_ID()); +#endif + +#if configUSE_CORE_AFFINITY && configNUM_CORES > 1 + vTaskCoreAffinitySet(task_handle, affinity); +#endif + + return PICO_OK; +} + +void cyw43_arch_deinit(void) { + assert(cyw43_core_num == get_core_num()); + if (timer_handle) { + xTimerDelete(timer_handle, 0); + timer_handle = 0; + } + if (cyw43_task_handle) { + cyw43_task_should_exit = true; + signal_cyw43_task(); + } + gpio_set_irq_enabled(CYW43_PIN_WL_HOST_WAKE, GPIO_IRQ_LEVEL_HIGH, false); + gpio_remove_raw_irq_handler(IO_IRQ_BANK0, gpio_irq_handler); +} + +void cyw43_post_poll_hook(void) { + assert(cyw43_core_num == get_core_num()); + gpio_set_irq_enabled(CYW43_PIN_WL_HOST_WAKE, GPIO_IRQ_LEVEL_HIGH, true); +} + +// This is called in the gpio and low_prio_irq interrupts and on either core +static void signal_cyw43_task(void) { + if (cyw43_task_handle) { + if (portCHECK_IF_IN_ISR()) { + vTaskNotifyGiveFromISR(cyw43_task_handle, NULL); + } else { + xTaskNotifyGive(cyw43_task_handle); + } + } +} + +void cyw43_schedule_internal_poll_dispatch(void (*func)(void)) { + assert(func == cyw43_poll); + signal_cyw43_task(); +} + +static int nesting; +// Prevent background processing in pensv and access by the other core +// These methods are called in pensv context and on either core +// They can be called recursively +void cyw43_thread_enter(void) { + // Lock the other core and stop low_prio_irq running + assert(!portCHECK_IF_IN_ISR()); +#if LWIP_TCPIP_CORE_LOCKING_INPUT + // we must share their mutex otherwise we can get deadlocks with two different recursive mutexes + LOCK_TCPIP_CORE(); +#else + xSemaphoreTakeRecursive(cyw43_mutex, portMAX_DELAY); +#endif + nesting++; +} + +#ifndef NDEBUG +void cyw43_thread_lock_check(void) { + // Lock the other core and stop low_prio_irq running +#if LWIP_TCPIP_CORE_LOCKING_INPUT + assert(xSemaphoreGetMutexHolder(lock_tcpip_core.mut) == xTaskGetCurrentTaskHandle()); +#else + assert(xSemaphoreGetMutexHolder(cyw43_mutex) == xTaskGetCurrentTaskHandle()); +#endif +} +#endif + +// Re-enable background processing +void cyw43_thread_exit(void) { + // Run low_prio_irq if needed + --nesting; +#if LWIP_TCPIP_CORE_LOCKING_INPUT + // we must share their mutex otherwise we can get deadlocks with two different recursive mutexes + UNLOCK_TCPIP_CORE(); +#else + xSemaphoreGiveRecursive(cyw43_mutex); +#endif + + if (!nesting && cyw43_task_handle != xTaskGetCurrentTaskHandle()) + signal_cyw43_task(); +} + +void cyw43_delay_ms(uint32_t ms) { + assert(!portCHECK_IF_IN_ISR()); + vTaskDelay(pdMS_TO_TICKS(ms)); +} + +void cyw43_delay_us(uint32_t us) { + if (us >= 1000) { + cyw43_delay_ms(us / 1000); + } else { + vTaskDelay(1); + } +} + +void cyw43_arch_poll() { +} + +#endif \ No newline at end of file diff --git a/src/rp2_common/pico_cyw43_arch/cyw43_arch_poll.c b/src/rp2_common/pico_cyw43_arch/cyw43_arch_poll.c new file mode 100644 index 0000000..e71f86a --- /dev/null +++ b/src/rp2_common/pico_cyw43_arch/cyw43_arch_poll.c @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2022 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include +#include "hardware/gpio.h" +#include "hardware/irq.h" +#include "pico/sem.h" +#include "pico/cyw43_arch.h" +#include "cyw43_stats.h" + +#if PICO_CYW43_ARCH_POLL +#include +#include "lwip/timeouts.h" + +#if CYW43_LWIP && !NO_SYS +#error PICO_CYW43_ARCH_POLL requires lwIP NO_SYS=1 +#endif + +#define CYW43_GPIO_IRQ_HANDLER_PRIORITY 0x40 + +#ifndef NDEBUG +uint8_t cyw43_core_num; +#endif + +bool cyw43_poll_required; + +// GPIO interrupt handler to tell us there's cyw43 has work to do +static void gpio_irq_handler(void) +{ + uint32_t events = gpio_get_irq_event_mask(CYW43_PIN_WL_HOST_WAKE); + if (events & GPIO_IRQ_LEVEL_HIGH) { + // As we use a high level interrupt, it will go off forever until it's serviced + // So disable the interrupt until this is done. It's re-enabled again by CYW43_POST_POLL_HOOK + // which is called at the end of cyw43_poll_func + gpio_set_irq_enabled(CYW43_PIN_WL_HOST_WAKE, GPIO_IRQ_LEVEL_HIGH, false); + // also clear the force bit which we use to programmatically cause this handler to fire (on the right core) + io_irq_ctrl_hw_t *irq_ctrl_base = get_core_num() ? + &iobank0_hw->proc1_irq_ctrl : &iobank0_hw->proc0_irq_ctrl; + hw_clear_bits(&irq_ctrl_base->intf[CYW43_PIN_WL_HOST_WAKE/8], GPIO_IRQ_LEVEL_HIGH << (4 * (CYW43_PIN_WL_HOST_WAKE & 7))); + cyw43_schedule_internal_poll_dispatch(cyw43_poll); + CYW43_STAT_INC(IRQ_COUNT); + } +} + +void cyw43_post_poll_hook(void) { + gpio_set_irq_enabled(CYW43_PIN_WL_HOST_WAKE, GPIO_IRQ_LEVEL_HIGH, true); +} + +int cyw43_arch_init(void) { +#ifndef NDEBUG + cyw43_core_num = (uint8_t)get_core_num(); +#endif + cyw43_init(&cyw43_state); + static bool done_lwip_init; + if (!done_lwip_init) { + lwip_init(); + done_lwip_init = true; + } + gpio_add_raw_irq_handler_with_order_priority(IO_IRQ_BANK0, gpio_irq_handler, CYW43_GPIO_IRQ_HANDLER_PRIORITY); + gpio_set_irq_enabled(CYW43_PIN_WL_HOST_WAKE, GPIO_IRQ_LEVEL_HIGH, true); + irq_set_enabled(IO_IRQ_BANK0, true); + return 0; +} + +void cyw43_arch_deinit(void) { + gpio_set_irq_enabled(CYW43_PIN_WL_HOST_WAKE, GPIO_IRQ_LEVEL_HIGH, false); + gpio_remove_raw_irq_handler(IO_IRQ_BANK0, gpio_irq_handler); + cyw43_deinit(&cyw43_state); +} + + +void cyw43_schedule_internal_poll_dispatch(__unused void (*func)(void)) { + cyw43_poll_required = true; +} + +void cyw43_arch_poll(void) +{ + CYW43_STAT_INC(LWIP_RUN_COUNT); + sys_check_timeouts(); + if (cyw43_poll) { + if (cyw43_sleep > 0) { + // todo check this; but we don't want to advance too quickly + static absolute_time_t last_poll_time; + absolute_time_t current = get_absolute_time(); + if (absolute_time_diff_us(last_poll_time, current) > 1000) { + if (--cyw43_sleep == 0) { + cyw43_poll_required = 1; + } + last_poll_time = current; + } + } + // todo graham i removed this because otherwise polling can do nothing during connect. + // in the polling only case, the caller is responsible for throttling how often they call anyway. + // The alternative would be to have the call to this function from the init set the poll_required flag first +// if (cyw43_poll_required) { + cyw43_poll(); +// cyw43_poll_required = false; +// } + } +} + +#ifndef NDEBUG +void cyw43_thread_check() { + if (__get_current_exception() || get_core_num() != cyw43_core_num) { + panic("cyw43_thread_lock_check failed"); + } +} +#endif + +#endif \ No newline at end of file diff --git a/src/rp2_common/pico_cyw43_arch/cyw43_arch_threadsafe_background.c b/src/rp2_common/pico_cyw43_arch/cyw43_arch_threadsafe_background.c new file mode 100644 index 0000000..a3dde74 --- /dev/null +++ b/src/rp2_common/pico_cyw43_arch/cyw43_arch_threadsafe_background.c @@ -0,0 +1,319 @@ +/* + * Copyright (c) 2022 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include + +#include "pico/cyw43_arch.h" +#include "pico/mutex.h" +#include "pico/sem.h" + +#include "hardware/gpio.h" +#include "hardware/irq.h" + +#include "cyw43_stats.h" + +#if CYW43_LWIP +#include +#include "lwip/timeouts.h" +#endif + +// note same code +#if PICO_CYW43_ARCH_THREADSAFE_BACKGROUND + +#if PICO_CYW43_ARCH_THREADSAFE_BACKGROUND && CYW43_LWIP && !NO_SYS +#error PICO_CYW43_ARCH_THREADSAFE_BACKGROUND requires lwIP NO_SYS=1 +#endif +#if PICO_CYW43_ARCH_THREADSAFE_BACKGROUND && CYW43_LWIP && MEM_LIBC_MALLOC +#error MEM_LIBC_MALLOC is incompatible with PICO_CYW43_ARCH_THREADSAFE_BACKGROUND +#endif +// todo right now we are now always doing a cyw43_dispatch along with a lwip one when hopping cores in low_prio_irq_schedule_dispatch + +#ifndef CYW43_SLEEP_CHECK_MS +#define CYW43_SLEEP_CHECK_MS 50 // How often to run lwip callback +#endif +static alarm_id_t periodic_alarm = -1; + +static inline uint recursive_mutex_enter_count(recursive_mutex_t *mutex) { + return mutex->enter_count; +} + +static inline lock_owner_id_t recursive_mutex_owner(recursive_mutex_t *mutex) { + return mutex->owner; +} + +#define CYW43_GPIO_IRQ_HANDLER_PRIORITY 0x40 + +enum { + CYW43_DISPATCH_SLOT_CYW43 = 0, + CYW43_DISPATCH_SLOT_ADAPTER, + CYW43_DISPATCH_SLOT_ENUM_COUNT +}; +#ifndef CYW43_DISPATCH_SLOT_COUNT +#define CYW43_DISPATCH_SLOT_COUNT CYW43_DISPATCH_SLOT_ENUM_COUNT +#endif + +typedef void (*low_prio_irq_dispatch_t)(void); +static void low_prio_irq_schedule_dispatch(size_t slot, low_prio_irq_dispatch_t f); + +static uint8_t cyw43_core_num; +#ifndef NDEBUG +static bool in_low_priority_irq; +#endif +static uint8_t low_priority_irq_num; +static bool low_priority_irq_missed; +static low_prio_irq_dispatch_t low_priority_irq_dispatch_slots[CYW43_DISPATCH_SLOT_COUNT]; +static recursive_mutex_t cyw43_mutex; +semaphore_t cyw43_irq_sem; + +// Called in low priority pendsv interrupt only to do lwip processing and check cyw43 sleep +static void periodic_worker(void) +{ +#if CYW43_USE_STATS + static uint32_t counter; + if (counter++ % (30000 / LWIP_SYS_CHECK_MS) == 0) { + cyw43_dump_stats(); + } +#endif + + CYW43_STAT_INC(LWIP_RUN_COUNT); +#if CYW43_LWIP + sys_check_timeouts(); +#endif + if (cyw43_poll) { + if (cyw43_sleep > 0) { + if (--cyw43_sleep == 0) { + low_prio_irq_schedule_dispatch(CYW43_DISPATCH_SLOT_CYW43, cyw43_poll); + } + } + } +} + +// Regular callback to get lwip to check for timeouts +static int64_t periodic_alarm_handler(__unused alarm_id_t id, __unused void *user_data) +{ + // Do lwip processing in low priority pendsv interrupt + low_prio_irq_schedule_dispatch(CYW43_DISPATCH_SLOT_ADAPTER, periodic_worker); + return CYW43_SLEEP_CHECK_MS * 1000; +} + +void cyw43_await_background_or_timeout_us(uint32_t timeout_us) { + // if we are called from within an IRQ, then don't wait (we are only ever called in a polling loop) + if (!__get_current_exception()) { + sem_acquire_timeout_us(&cyw43_irq_sem, timeout_us); + } +} + +// GPIO interrupt handler to tell us there's cyw43 has work to do +static void gpio_irq_handler(void) +{ + uint32_t events = gpio_get_irq_event_mask(CYW43_PIN_WL_HOST_WAKE); + if (events & GPIO_IRQ_LEVEL_HIGH) { + // As we use a high level interrupt, it will go off forever until it's serviced + // So disable the interrupt until this is done. It's re-enabled again by CYW43_POST_POLL_HOOK + // which is called at the end of cyw43_poll_func + gpio_set_irq_enabled(CYW43_PIN_WL_HOST_WAKE, GPIO_IRQ_LEVEL_HIGH, false); + // also clear the force bit which we use to progratically cause this handler to fire (on the right core) + io_irq_ctrl_hw_t *irq_ctrl_base = get_core_num() ? + &iobank0_hw->proc1_irq_ctrl : &iobank0_hw->proc0_irq_ctrl; + hw_clear_bits(&irq_ctrl_base->intf[CYW43_PIN_WL_HOST_WAKE/8], GPIO_IRQ_LEVEL_HIGH << (4 * (CYW43_PIN_WL_HOST_WAKE & 7))); + low_prio_irq_schedule_dispatch(CYW43_DISPATCH_SLOT_CYW43, cyw43_poll); + CYW43_STAT_INC(IRQ_COUNT); + } +} + +// Low priority interrupt handler to perform background processing +static void low_priority_irq_handler(void) { + assert(cyw43_core_num == get_core_num()); + if (recursive_mutex_try_enter(&cyw43_mutex, NULL)) { + if (recursive_mutex_enter_count(&cyw43_mutex) != 1) { + low_priority_irq_missed = true; + CYW43_STAT_INC(PENDSV_DISABLED_COUNT); + } else { + CYW43_STAT_INC(PENDSV_RUN_COUNT); +#ifndef NDEBUG + in_low_priority_irq = true; +#endif + for (size_t i = 0; i < count_of(low_priority_irq_dispatch_slots); i++) { + if (low_priority_irq_dispatch_slots[i] != NULL) { + low_prio_irq_dispatch_t f = low_priority_irq_dispatch_slots[i]; + low_priority_irq_dispatch_slots[i] = NULL; + f(); + } + } +#ifndef NDEBUG + in_low_priority_irq = false; +#endif + } + recursive_mutex_exit(&cyw43_mutex); + } else { + CYW43_STAT_INC(PENDSV_DISABLED_COUNT); + low_priority_irq_missed = true; + } + sem_release(&cyw43_irq_sem); +} + +static bool low_prio_irq_init(uint8_t priority) { + assert(get_core_num() == cyw43_core_num); + int irq = user_irq_claim_unused(false); + if (irq < 0) return false; + low_priority_irq_num = (uint8_t) irq; + irq_set_exclusive_handler(low_priority_irq_num, low_priority_irq_handler); + irq_set_enabled(low_priority_irq_num, true); + irq_set_priority(low_priority_irq_num, priority); + return true; +} + +static void low_prio_irq_deinit(void) { + if (low_priority_irq_num > 0) { + irq_set_enabled(low_priority_irq_num, false); + irq_remove_handler(low_priority_irq_num, low_priority_irq_handler); + user_irq_unclaim(low_priority_irq_num); + low_priority_irq_num = 0; + } +} + +int cyw43_arch_init(void) { + cyw43_core_num = get_core_num(); + recursive_mutex_init(&cyw43_mutex); + cyw43_init(&cyw43_state); + sem_init(&cyw43_irq_sem, 0, 1); + + // Start regular lwip callback to handle timeouts + periodic_alarm = add_alarm_in_us(CYW43_SLEEP_CHECK_MS * 1000, periodic_alarm_handler, NULL, true); + if (periodic_alarm < 0) { + return PICO_ERROR_GENERIC; + } + + gpio_add_raw_irq_handler_with_order_priority(IO_IRQ_BANK0, gpio_irq_handler, CYW43_GPIO_IRQ_HANDLER_PRIORITY); + gpio_set_irq_enabled(CYW43_PIN_WL_HOST_WAKE, GPIO_IRQ_LEVEL_HIGH, true); + irq_set_enabled(IO_IRQ_BANK0, true); + +#if CYW43_LWIP + lwip_init(); +#endif + // start low priority handler (no background work is done before this) + bool ok = low_prio_irq_init(PICO_LOWEST_IRQ_PRIORITY); + if (!ok) { + cyw43_arch_deinit(); + return PICO_ERROR_GENERIC; + } + return PICO_OK; +} + +void cyw43_arch_deinit(void) { + if (periodic_alarm >= 0) { + cancel_alarm(periodic_alarm); + periodic_alarm = -1; + } + gpio_set_irq_enabled(CYW43_PIN_WL_HOST_WAKE, GPIO_IRQ_LEVEL_HIGH, false); + gpio_remove_raw_irq_handler(IO_IRQ_BANK0, gpio_irq_handler); + low_prio_irq_deinit(); +} + +void cyw43_post_poll_hook(void) { + gpio_set_irq_enabled(CYW43_PIN_WL_HOST_WAKE, GPIO_IRQ_LEVEL_HIGH, true); +} + +// This is called in the gpio and low_prio_irq interrupts and on either core +static void low_prio_irq_schedule_dispatch(size_t slot, low_prio_irq_dispatch_t f) { + assert(slot < count_of(low_priority_irq_dispatch_slots)); + low_priority_irq_dispatch_slots[slot] = f; + if (cyw43_core_num == get_core_num()) { + //on same core, can dispatch directly + irq_set_pending(low_priority_irq_num); + } else { + // on wrong core, so force via GPIO IRQ which itself calls this method for the CYW43 slot. + // since the CYW43 slot always uses the same function, this is fine with the addition of an + // extra (but harmless) CYW43 slot call when another SLOT is invoked. + // We could do better, but would have to track why the IRQ was called. + io_irq_ctrl_hw_t *irq_ctrl_base = cyw43_core_num ? + &iobank0_hw->proc1_irq_ctrl : &iobank0_hw->proc0_irq_ctrl; + hw_set_bits(&irq_ctrl_base->intf[CYW43_PIN_WL_HOST_WAKE/8], GPIO_IRQ_LEVEL_HIGH << (4 * (CYW43_PIN_WL_HOST_WAKE & 7))); + } +} + +void cyw43_schedule_internal_poll_dispatch(void (*func)(void)) { + low_prio_irq_schedule_dispatch(CYW43_DISPATCH_SLOT_CYW43, func); +} + +// Prevent background processing in pensv and access by the other core +// These methods are called in pensv context and on either core +// They can be called recursively +void cyw43_thread_enter(void) { + // Lock the other core and stop low_prio_irq running + recursive_mutex_enter_blocking(&cyw43_mutex); +} + +#ifndef NDEBUG +void cyw43_thread_lock_check(void) { + // Lock the other core and stop low_prio_irq running + if (recursive_mutex_enter_count(&cyw43_mutex) < 1 || recursive_mutex_owner(&cyw43_mutex) != lock_get_caller_owner_id()) { + panic("cyw43_thread_lock_check failed"); + } +} +#endif + +// Re-enable background processing +void cyw43_thread_exit(void) { + // Run low_prio_irq if needed + if (1 == recursive_mutex_enter_count(&cyw43_mutex)) { + // note the outer release of the mutex is not via cyw43_exit in the low_priority_irq case (it is a direct mutex exit) + assert(!in_low_priority_irq); +// if (low_priority_irq_missed) { +// low_priority_irq_missed = false; + if (low_priority_irq_dispatch_slots[CYW43_DISPATCH_SLOT_CYW43]) { + low_prio_irq_schedule_dispatch(CYW43_DISPATCH_SLOT_CYW43, cyw43_poll); + } +// } + } + recursive_mutex_exit(&cyw43_mutex); +} + + +static void cyw43_delay_until(absolute_time_t until) { + // sleep can be called in IRQs, so there's not much we can do there + if (__get_current_exception()) { + busy_wait_until(until); + } else { + sleep_until(until); + } +} + +void cyw43_delay_ms(uint32_t ms) { + cyw43_delay_until(make_timeout_time_ms(ms)); +} + +void cyw43_delay_us(uint32_t us) { + cyw43_delay_until(make_timeout_time_us(us)); +} + +void cyw43_arch_poll() { + // should not be necessary +// if (cyw43_poll) { +// low_prio_irq_schedule_dispatch(CYW43_DISPATCH_SLOT_CYW43, cyw43_poll); +// } +} + +#if !CYW43_LWIP +static void no_lwip_fail() { + panic("You cannot use IP with pico_cyw43_arch_none"); +} +void cyw43_cb_tcpip_init(cyw43_t *self, int itf) { +} +void cyw43_cb_tcpip_deinit(cyw43_t *self, int itf) { +} +void cyw43_cb_tcpip_set_link_up(cyw43_t *self, int itf) { + no_lwip_fail(); +} +void cyw43_cb_tcpip_set_link_down(cyw43_t *self, int itf) { + no_lwip_fail(); +} +void cyw43_cb_process_ethernet(void *cb_data, int itf, size_t len, const uint8_t *buf) { + no_lwip_fail(); +} +#endif + +#endif \ No newline at end of file diff --git a/src/rp2_common/pico_cyw43_arch/include/cyw43_configport.h b/src/rp2_common/pico_cyw43_arch/include/cyw43_configport.h new file mode 100644 index 0000000..7827c81 --- /dev/null +++ b/src/rp2_common/pico_cyw43_arch/include/cyw43_configport.h @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2022 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +// This header is included by cyw43_driver to setup its environment + +#ifndef _CYW43_CONFIGPORT_H +#define _CYW43_CONFIGPORT_H + +#include "pico.h" + +#ifdef PICO_CYW43_ARCH_HEADER +#include __XSTRING(PICO_CYW43_ARCH_HEADER) +#else +#if PICO_CYW43_ARCH_POLL +#include "pico/cyw43_arch/arch_poll.h" +#elif PICO_CYW43_ARCH_THREADSAFE_BACKGROUND +#include "pico/cyw43_arch/arch_threadsafe_background.h" +#elif PICO_CYW43_ARCH_FREERTOS +#include "pico/cyw43_arch/arch_freertos.h" +#else +#error must specify support pico_cyw43_arch architecture type or set PICO_CYW43_ARCH_HEADER +#endif +#endif + +#ifndef CYW43_HOST_NAME +#define CYW43_HOST_NAME "PicoW" +#endif + +#ifndef CYW43_GPIO +#define CYW43_GPIO 1 +#endif + +#ifndef CYW43_LOGIC_DEBUG +#define CYW43_LOGIC_DEBUG 0 +#endif + +#ifndef CYW43_USE_OTP_MAC +#define CYW43_USE_OTP_MAC 1 +#endif + +#ifndef CYW43_NO_NETUTILS +#define CYW43_NO_NETUTILS 1 +#endif + +#ifndef CYW43_IOCTL_TIMEOUT_US +#define CYW43_IOCTL_TIMEOUT_US 1000000 +#endif + +#ifndef CYW43_USE_STATS +#define CYW43_USE_STATS 0 +#endif + +// todo should this be user settable? +#ifndef CYW43_HAL_MAC_WLAN0 +#define CYW43_HAL_MAC_WLAN0 0 +#endif + +#ifndef STATIC +#define STATIC static +#endif + +#ifndef CYW43_USE_SPI +#define CYW43_USE_SPI 1 +#endif + +#ifndef CYW43_SPI_PIO +#define CYW43_SPI_PIO 1 +#endif + +#endif \ No newline at end of file diff --git a/src/rp2_common/pico_cyw43_arch/include/pico/cyw43_arch.h b/src/rp2_common/pico_cyw43_arch/include/pico/cyw43_arch.h new file mode 100644 index 0000000..8d6a628 --- /dev/null +++ b/src/rp2_common/pico_cyw43_arch/include/pico/cyw43_arch.h @@ -0,0 +1,328 @@ +/* + * Copyright (c) 2022 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef _PICO_CYW43_ARCH_H +#define _PICO_CYW43_ARCH_H + +#include "pico.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#include "cyw43.h" +#include "cyw43_country.h" + +/** + * \defgroup cyw43_driver cyw43_driver + * \ingroup pico_cyw43_arch + * \brief Driver used for Pico W wireless +*/ + +/** + * \defgroup cyw43_ll cyw43_ll + * \ingroup cyw43_driver + * \brief Low Level CYW43 driver interface +*/ + +/** \file pico/cyw43_arch.h + * \defgroup pico_cyw43_arch pico_cyw43_arch + * + * Architecture for integrating the CYW43 driver (for the wireless on Pico W) and lwIP (for TCP/IP stack) into the SDK. It is also necessary for accessing the on-board LED on Pico W + * + * Both the low level \c cyw43_driver and the lwIP stack require periodic servicing, and have limitations + * on whether they can be called from multiple cores/threads. + * + * \c pico_cyw43_arch attempts to abstract these complications into several behavioral groups: + * + * * \em 'poll' - This not multi-core/IRQ safe, and requires the user to call \ref cyw43_arch_poll periodically from their main loop + * * \em 'thread_safe_background' - This is multi-core/thread/task safe, and maintenance of the driver and TCP/IP stack is handled automatically in the background + * + * As of right now, lwIP is the only supported TCP/IP stack, however the use of \c pico_cyw43_arch is intended to be independent of + * the particular TCP/IP stack used (and possibly Bluetooth stack used) in the future. For this reason, the integration of lwIP + * is handled in the base (\c pico_cyw43_arch) library based on the #define \ref CYW43_LWIP used by the \c cyw43_driver. + * + * Whilst you can use the \c pico_cyw43_arch library directly and specify \ref CYW$#_LWIP (and other defines) yourself, several + * other libraries are made available to the build which aggregate the defines and other dependencies for you: + * + * * \b pico_cyw43_arch_lwip_poll - For using the RAW lwIP API (in `NO_SYS=1` mode) without any background processing or multi-core/thread safety. + * + * The user must call \ref pico_cyw43_poll periodically from their main loop. + * + * This wrapper library: + * - Sets \c CYW43_LWIP=1 to enable lwIP support in \c pico_cyw43_arch and \c cyw43_driver. + * - Sets \c PICO_CYW43_ARCH_POLL=1 to select the polling behavior. + * - Adds the \c pico_lwip as a dependency to pull in lwIP. + * + * * \b pico_cyw43_arch_lwip_threadsafe_background - For using the RAW lwIP API (in `NO_SYS=1` mode) with multi-core/thread safety, and automatic servicing of the \c cyw43_driver and + * lwIP in background. + * + * Calls into the \c cyw43_driver high level API (cyw43.h) may be made from either core or from lwIP callbacks, however calls into lwIP (which + * is not thread-safe) other than those made from lwIP callbacks, must be bracketed with \ref cyw43_arch_lwip_begin and \ref cyw43_arch_lwip_end. It is fine to bracket + * calls made from within lwIP callbacks too; you just don't have to. + * + * \note lwIP callbacks happen in a (low priority) IRQ context (similar to an alarm callback), so care should be taken when interacting + * with other code. + * + * This wrapper library: + * - Sets \c CYW43_LWIP=1 to enable lwIP support in \c pico_cyw43_arch and \c cyw43_driver + * - Sets \c PICO_CYW43_ARCH_THREADSAFE_BACKGROUND=1 to select the thread-safe/non-polling behavior. + * - Adds the pico_lwip as a dependency to pull in lwIP. + * + * + * This library \em can also be used under the RP2040 port of FreeRTOS with lwIP in `NO_SYS=1` mode (allowing you to call \c cyw43_driver APIs + * from any task, and to call lwIP from lwIP callbacks, or from any task if you bracket the calls with \ref cyw43_arch_lwip_begin and \ref cyw43_arch_lwip_end. Again, you should be + * careful about what you do in lwIP callbacks, as you cannot call most FreeRTOS APIs from within an IRQ context. Unless you have good reason, you should probably + * use the full FreeRTOS integration (with `NO_SYS=0`) provided by \c pico_cyw43_arch_lwip_sys_freertos. + * + * * \b pico_cyw43_arch_lwip_sys_freertos - For using the full lwIP API including blocking sockets in OS (`NO_SYS=0`) mode, along with with multi-core/task/thread safety, and automatic servicing of the \c cyw43_driver and + * the lwIP stack. + * + * This wrapper library: + * - Sets \c CYW43_LWIP=1 to enable lwIP support in \c pico_cyw43_arch and \c cyw43_driver. + * - Sets \c PICO_CYW43_ARCH_FREERTOS=1 to select the NO_SYS=0 lwip/FreeRTOS integration + * - Sets \c LWIP_PROVIDE_ERRNO=1 to provide error numbers needed for compilation without an OS + * - Adds the \c pico_lwip as a dependency to pull in lwIP. + * - Adds the lwIP/FreeRTOS code from lwip-contrib (in the contrib directory of lwIP) + * + * Calls into the \c cyw43_driver high level API (cyw43.h) may be made from any task or from lwIP callbacks, but not from IRQs. Calls into the lwIP RAW API (which is not thread safe) + * must be bracketed with \ref cyw43_arch_lwip_begin and \ref cyw43_arch_lwip_end. It is fine to bracket calls made from within lwIP callbacks too; you just don't have to. + * + * \note this wrapper library requires you to link FreeRTOS functionality with your application yourself. + * + * * \b pico_cyw43_arch_none - If you do not need the TCP/IP stack but wish to use the on-board LED. + * + * This wrapper library: + * - Sets \c CYW43_LWIP=0 to disable lwIP support in \c pico_cyw43_arch and \c cyw43_driver + */ + +// PICO_CONFIG: PARAM_ASSERTIONS_ENABLED_CYW43_ARCH, Enable/disable assertions in the pico_cyw43_arch module, type=bool, default=0, group=pico_cyw43_arch +#ifndef PARAM_ASSERTIONS_ENABLED_CYW43_ARCH +#define PARAM_ASSERTIONS_ENABLED_CYW43_ARCH 0 +#endif + +// PICO_CONFIG: CYW43_ARCH_DEBUG_ENABLED, Enable/disable some debugging output in the pico_cyw43_arch module, type=bool, default=1 in debug builds, group=pico_cyw43_arch +#ifndef CYW43_ARCH_DEBUG_ENABLED +#ifndef NDEBUG +#define CYW43_ARCH_DEBUG_ENABLED 1 +#else +#define CYW43_ARCH_DEBUG_ENABLED 0 +#endif +#endif + +// PICO_CONFIG: PICO_CYW43_ARCH_DEFAULT_COUNTRY_CODE, Default country code for the cyw43 wireless driver, default=CYW43_COUNTRY_WORLDWIDE, group=pico_cyw43_arch +#ifndef PICO_CYW43_ARCH_DEFAULT_COUNTRY_CODE +#define PICO_CYW43_ARCH_DEFAULT_COUNTRY_CODE CYW43_COUNTRY_WORLDWIDE +#endif + +/*! + * \brief Initialize the CYW43 architecture + * \ingroup pico_cyw43_arch + * + * This method initializes the `cyw43_driver` code and initializes the lwIP stack (if it + * was enabled at build time). This method must be called prior to using any other \c pico_cyw43_arch, + * \cyw43_driver or lwIP functions. + * + * \note this method initializes wireless with a country code of \c PICO_CYW43_ARCH_DEFAULT_COUNTRY_CODE + * which defaults to \c CYW43_COUNTRY_WORLDWIDE. Worldwide settings may not give the best performance; consider + * setting PICO_CYW43_ARCH_DEFAULT_COUNTRY_CODE to a different value or calling \ref cyw43_arch_init_with_country + * \return 0 if the initialization is successful, an error code otherwise \see pico_error_codes + */ +int cyw43_arch_init(void); + +/*! + * \brief Initialize the CYW43 architecture for use in a specific country + * \ingroup pico_cyw43_arch + * + * This method initializes the `cyw43_driver` code and initializes the lwIP stack (if it + * was enabled at build time). This method must be called prior to using any other \c pico_cyw43_arch, + * \cyw43_driver or lwIP functions. + * + * \param country the country code to use (see \ref CYW43_COUNTRY_) + * \return 0 if the initialization is successful, an error code otherwise \see pico_error_codes + */ +int cyw43_arch_init_with_country(uint32_t country); + +/*! + * \brief Enables Wi-Fi STA (Station) mode. + * \ingroup pico_cyw43_arch + * + * This enables the Wi-Fi in \emStation mode such that connections can be made to other Wi-Fi Access Points + */ +void cyw43_arch_enable_sta_mode(void); + +/*! + * \brief Enables Wi-Fi AP (Access point) mode. + * \ingroup pico_cyw43_arch + * + * This enables the Wi-Fi in \em Access \em Point mode such that connections can be made to the device by other Wi-Fi clients + * \param ssid the name for the access point + * \param password the password to use or NULL for no password. + * \param auth the authorization type to use when the password is enabled. Values are \ref CYW43_AUTH_WPA_TKIP_PSK, + * \ref CYW43_AUTH_WPA2_AES_PSK, or \ref CYW43_AUTH_WPA2_MIXED_PSK (see \ref CYW43_AUTH_) + */ +void cyw43_arch_enable_ap_mode(const char *ssid, const char *password, uint32_t auth); + +/*! + * \brief De-initialize the CYW43 architecture + * \ingroup pico_cyw43_arch + * + * This method de-initializes the `cyw43_driver` code and de-initializes the lwIP stack (if it + * was enabled at build time). Note this method should always be called from the same core (or RTOS + * task, depending on the environment) as \ref cyw43_arch_init. + */ +void cyw43_arch_deinit(void); + +/*! + * \brief Attempt to connect to a wireless access point, blocking until the network is joined or a failure is detected. + * \ingroup pico_cyw43_arch + * + * \param ssid the network name to connect to + * \param password the network password or NULL if there is no password required + * \param auth the authorization type to use when the password is enabled. Values are \ref CYW43_AUTH_WPA_TKIP_PSK, + * \ref CYW43_AUTH_WPA2_AES_PSK, or \ref CYW43_AUTH_WPA2_MIXED_PSK (see \ref CYW43_AUTH_) + * + * \return 0 if the initialization is successful, an error code otherwise \see pico_error_codes + */ +int cyw43_arch_wifi_connect_blocking(const char *ssid, const char *pw, uint32_t auth); + +/*! + * \brief Attempt to connect to a wireless access point, blocking until the network is joined, a failure is detected or a timeout occurs + * \ingroup pico_cyw43_arch + * + * \param ssid the network name to connect to + * \param password the network password or NULL if there is no password required + * \param auth the authorization type to use when the password is enabled. Values are \ref CYW43_AUTH_WPA_TKIP_PSK, + * \ref CYW43_AUTH_WPA2_AES_PSK, or \ref CYW43_AUTH_WPA2_MIXED_PSK (see \ref CYW43_AUTH_) + * + * \return 0 if the initialization is successful, an error code otherwise \see pico_error_codes + */ +int cyw43_arch_wifi_connect_timeout_ms(const char *ssid, const char *pw, uint32_t auth, uint32_t timeout); + +/*! + * \brief Start attempting to connect to a wireless access point + * \ingroup pico_cyw43_arch + * + * This method tells the CYW43 driver to start connecting to an access point. You should subsequently check the + * status by calling \ref cyw43_wifi_link_status. + * + * \param ssid the network name to connect to + * \param password the network password or NULL if there is no password required + * \param auth the authorization type to use when the password is enabled. Values are \ref CYW43_AUTH_WPA_TKIP_PSK, + * \ref CYW43_AUTH_WPA2_AES_PSK, or \ref CYW43_AUTH_WPA2_MIXED_PSK (see \ref CYW43_AUTH_) + * + * \return 0 if the scan was started successfully, an error code otherwise \see pico_error_codes + */ +int cyw43_arch_wifi_connect_async(const char *ssid, const char *pw, uint32_t auth); + +/*! + * \brief Return the country code used to initialize cyw43_arch + * \ingroup pico_cyw43_arch + * + * \return the country code (see \ref CYW43_COUNTRY_) + */ +uint32_t cyw43_arch_get_country_code(void); + +/*! + * \brief Set a GPIO pin on the wireless chip to a given value + * \ingroup pico_cyw43_arch + * \note this method does not check for errors setting the GPIO. You can use the lower level \ref cyw43_gpio_set instead if you wish + * to check for errors. + * + * \param wl_gpio the GPIO number on the wireless chip + * \param value true to set the GPIO, false to clear it. + */ +void cyw43_arch_gpio_put(uint wl_gpio, bool value); + +/*! + * \brief Read the value of a GPIO pin on the wireless chip + * \ingroup pico_cyw43_arch + * \note this method does not check for errors setting the GPIO. You can use the lower level \ref cyw43_gpio_get instead if you wish + * to check for errors. + * + * \param wl_gpio the GPIO number on the wireless chip + * \return true if the GPIO is high, false otherwise + */ +bool cyw43_arch_gpio_get(uint wl_gpio); + +/*! + * \brief Perform any processing required by the \c cyw43_driver or the TCP/IP stack + * \ingroup pico_cyw43_arch + * + * This method must be called periodically from the main loop when using a + * \em polling style \c pico_cyw43_arch (e.g. \c pico_cyw43_arch_lwip_poll ). It + * may be called in other styles, but it is unnecessary to do so. + */ +void cyw43_arch_poll(void); + +/*! + * \fn cyw43_arch_lwip_begin + * \brief Acquire any locks required to call into lwIP + * \ingroup pico_cyw43_arch + * + * The lwIP API is not thread safe. You should surround calls into the lwIP API + * with calls to this method and \ref cyw43_arch_lwip_end. Note these calls are not + * necessary (but harmless) when you are calling back into the lwIP API from an lwIP callback. + * If you are using single-core polling only (pico_cyw43_arch_poll) then these calls are no-ops + * anyway it is good practice to call them anyway where they are necessary. + * + * \sa cyw43_arch_lwip_end + * \sa cyw43_arch_lwip_protect + */ + +/*! + * \fn void cyw43_arch_lwip_end(void) + * \brief Release any locks required for calling into lwIP + * \ingroup pico_cyw43_arch + * + * The lwIP API is not thread safe. You should surround calls into the lwIP API + * with calls to \ref cyw43_arch_lwip_begin and this method. Note these calls are not + * necessary (but harmless) when you are calling back into the lwIP API from an lwIP callback. + * If you are using single-core polling only (pico_cyw43_arch_poll) then these calls are no-ops + * anyway it is good practice to call them anyway where they are necessary. + * + * \sa cyw43_arch_lwip_begin + * \sa cyw43_arch_lwip_protect + */ + +/*! + * \fn int cyw43_arch_lwip_protect(int (*func)(void *param), void *param) + * \brief sad Release any locks required for calling into lwIP + * \ingroup pico_cyw43_arch + * + * The lwIP API is not thread safe. You can use this method to wrap a function + * with any locking required to call into the lwIP API. If you are using + * single-core polling only (pico_cyw43_arch_poll) then there are no + * locks to required, but it is still good practice to use this function. + * + * \param func the function ta call with any required locks held + * \param param parameter to pass to \c func + * \return the return value from \c func + * \sa cyw43_arch_lwip_begin + * \sa cyw43_arch_lwip_end + */ + +/*! + * \fn void cyw43_arch_lwip_check(void) + * \brief Checks the caller has any locks required for calling into lwIP + * \ingroup pico_cyw43_arch + * + * The lwIP API is not thread safe. You should surround calls into the lwIP API + * with calls to \ref cyw43_arch_lwip_begin and this method. Note these calls are not + * necessary (but harmless) when you are calling back into the lwIP API from an lwIP callback. + * + * This method will assert in debug mode, if the above conditions are not met (i.e. it is not safe to + * call into the lwIP API) + * + * \sa cyw43_arch_lwip_begin + * \sa cyw43_arch_lwip_protect + */ + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/rp2_common/pico_cyw43_arch/include/pico/cyw43_arch/arch_common.h b/src/rp2_common/pico_cyw43_arch/include/pico/cyw43_arch/arch_common.h new file mode 100644 index 0000000..3cd5aa9 --- /dev/null +++ b/src/rp2_common/pico_cyw43_arch/include/pico/cyw43_arch/arch_common.h @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2022 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef _PICO_CYW43_ARCH_ARCH_COMMON_H +#define _PICO_CYW43_ARCH_ARCH_COMMON_H + +#include "pico.h" +#include "pico/time.h" +#include "hardware/gpio.h" +#include "pico/error.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// Note, these are negated, because cyw43_driver negates them before returning! +#define CYW43_EPERM (-PICO_ERROR_NOT_PERMITTED) // Operation not permitted +#define CYW43_EIO (-PICO_ERROR_IO) // I/O error +#define CYW43_EINVAL (-PICO_ERROR_INVALID_ARG) // Invalid argument +#define CYW43_ETIMEDOUT (-PICO_ERROR_TIMEOUT) // Connection timed out + +#define CYW43_NUM_GPIOS CYW43_WL_GPIO_COUNT + +#define cyw43_hal_pin_obj_t uint + +// get the number of elements in a fixed-size array +#define CYW43_ARRAY_SIZE(a) count_of(a) + +static inline uint32_t cyw43_hal_ticks_us(void) { + return time_us_32(); +} + +static inline uint32_t cyw43_hal_ticks_ms(void) { + return to_ms_since_boot(get_absolute_time()); +} + +static inline int cyw43_hal_pin_read(cyw43_hal_pin_obj_t pin) { + return gpio_get(pin); +} + +static inline void cyw43_hal_pin_low(cyw43_hal_pin_obj_t pin) { + gpio_clr_mask(1 << pin); +} + +static inline void cyw43_hal_pin_high(cyw43_hal_pin_obj_t pin) { + gpio_set_mask(1 << pin); +} + +#define CYW43_HAL_PIN_MODE_INPUT (GPIO_IN) +#define CYW43_HAL_PIN_MODE_OUTPUT (GPIO_OUT) + +#define CYW43_HAL_PIN_PULL_NONE (0) +#define CYW43_HAL_PIN_PULL_UP (1) +#define CYW43_HAL_PIN_PULL_DOWN (2) + +static inline void cyw43_hal_pin_config(cyw43_hal_pin_obj_t pin, uint32_t mode, uint32_t pull, __unused uint32_t alt) { + assert((mode == CYW43_HAL_PIN_MODE_INPUT || mode == CYW43_HAL_PIN_MODE_OUTPUT) && alt == 0); + gpio_set_dir(pin, mode); + gpio_set_pulls(pin, pull == CYW43_HAL_PIN_PULL_UP, pull == CYW43_HAL_PIN_PULL_DOWN); +} + +void cyw43_hal_get_mac(int idx, uint8_t buf[6]); + +void cyw43_hal_generate_laa_mac(int idx, uint8_t buf[6]); + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/src/rp2_common/pico_cyw43_arch/include/pico/cyw43_arch/arch_freertos.h b/src/rp2_common/pico_cyw43_arch/include/pico/cyw43_arch/arch_freertos.h new file mode 100644 index 0000000..342a85c --- /dev/null +++ b/src/rp2_common/pico_cyw43_arch/include/pico/cyw43_arch/arch_freertos.h @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2022 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef _EXAMPLE_CYW43_ARCH_ARCH_FREERTOS_SYS_H +#define _EXAMPLE_CYW43_ARCH_ARCH_FREERTOS_SYS_H + +#include "pico/cyw43_arch/arch_common.h" + +#ifdef __cplusplus +extern "C" { +#endif + +void cyw43_thread_enter(void); +void cyw43_thread_exit(void); + +#define CYW43_THREAD_ENTER cyw43_thread_enter(); +#define CYW43_THREAD_EXIT cyw43_thread_exit(); +#ifndef NDEBUG +void cyw43_thread_lock_check(void); +#define cyw43_arch_lwip_check() cyw43_thread_lock_check() +#define CYW43_THREAD_LOCK_CHECK cyw43_arch_lwip_check(); +#else +#define cyw43_arch_lwip_check() ((void)0) +#define CYW43_THREAD_LOCK_CHECK +#endif + +void cyw43_await_background_or_timeout_us(uint32_t timeout_us); +// todo not 100% sure about the timeouts here; MP uses __WFI which will always wakeup periodically +#define CYW43_SDPCM_SEND_COMMON_WAIT cyw43_await_background_or_timeout_us(1000); +#define CYW43_DO_IOCTL_WAIT cyw43_await_background_or_timeout_us(1000); + +void cyw43_delay_ms(uint32_t ms); +void cyw43_delay_us(uint32_t us); + +void cyw43_schedule_internal_poll_dispatch(void (*func)(void)); + +void cyw43_post_poll_hook(void); +#define CYW43_POST_POLL_HOOK cyw43_post_poll_hook(); + +static inline void cyw43_arch_lwip_begin(void) { + cyw43_thread_enter(); +} +static inline void cyw43_arch_lwip_end(void) { + cyw43_thread_exit(); +} + +static inline int cyw43_arch_lwip_protect(int (*func)(void *param), void *param) { + cyw43_arch_lwip_begin(); + int rc = func(param); + cyw43_arch_lwip_end(); + return rc; +} + +#ifdef __cplusplus +} +#endif + +#endif \ No newline at end of file diff --git a/src/rp2_common/pico_cyw43_arch/include/pico/cyw43_arch/arch_poll.h b/src/rp2_common/pico_cyw43_arch/include/pico/cyw43_arch/arch_poll.h new file mode 100644 index 0000000..bfaea7f --- /dev/null +++ b/src/rp2_common/pico_cyw43_arch/include/pico/cyw43_arch/arch_poll.h @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2022 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef _PICO_CYW43_ARCH_ARCH_POLL_H +#define _PICO_CYW43_ARCH_ARCH_POLL_H + +#include "pico/cyw43_arch/arch_common.h" + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define CYW43_THREAD_ENTER +#define CYW43_THREAD_EXIT +#ifndef NDEBUG + +void cyw43_thread_check(void); + +#define cyw43_arch_lwip_check() cyw43_thread_check() +#define CYW43_THREAD_LOCK_CHECK cyw43_arch_lwip_check(); +#else +#define cyw43_arch_lwip_check() ((void)0) +#define CYW43_THREAD_LOCK_CHECK +#endif + +#define CYW43_SDPCM_SEND_COMMON_WAIT cyw43_poll_required = true; +#define CYW43_DO_IOCTL_WAIT cyw43_poll_required = true; + +#define cyw43_delay_ms sleep_ms +#define cyw43_delay_us sleep_us + +void cyw43_schedule_internal_poll_dispatch(void (*func)(void)); + +void cyw43_post_poll_hook(void); + +extern bool cyw43_poll_required; + +#define CYW43_POST_POLL_HOOK cyw43_post_poll_hook(); +#endif + +#ifndef DOXYGEN_GENERATION // multiple definitions in separate headers seems to confused doxygen +#define cyw43_arch_lwip_begin() ((void)0) +#define cyw43_arch_lwip_end() ((void)0) + +static inline int cyw43_arch_lwip_protect(int (*func)(void *param), void *param) { + return func(param); +} + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/rp2_common/pico_cyw43_arch/include/pico/cyw43_arch/arch_threadsafe_background.h b/src/rp2_common/pico_cyw43_arch/include/pico/cyw43_arch/arch_threadsafe_background.h new file mode 100644 index 0000000..713fcc1 --- /dev/null +++ b/src/rp2_common/pico_cyw43_arch/include/pico/cyw43_arch/arch_threadsafe_background.h @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2022 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef _PICO_CYW43_ARCH_ARCH_THREADSAFE_BACKGROUND_H +#define _PICO_CYW43_ARCH_ARCH_THREADSAFE_BACKGROUND_H + +#include "pico/cyw43_arch/arch_common.h" + +#ifdef __cplusplus +extern "C" { +#endif + +void cyw43_thread_enter(void); + +void cyw43_thread_exit(void); + +#define CYW43_THREAD_ENTER cyw43_thread_enter(); +#define CYW43_THREAD_EXIT cyw43_thread_exit(); +#ifndef NDEBUG + +void cyw43_thread_lock_check(void); + +#define cyw43_arch_lwip_check() cyw43_thread_lock_check() +#define CYW43_THREAD_LOCK_CHECK cyw43_arch_lwip_check(); +#else +#define cyw43_arch_lwip_check() ((void)0) +#define CYW43_THREAD_LOCK_CHECK +#endif + +void cyw43_await_background_or_timeout_us(uint32_t timeout_us); +// todo not 100% sure about the timeouts here; MP uses __WFI which will always wakeup periodically +#define CYW43_SDPCM_SEND_COMMON_WAIT cyw43_await_background_or_timeout_us(1000); +#define CYW43_DO_IOCTL_WAIT cyw43_await_background_or_timeout_us(1000); + +void cyw43_delay_ms(uint32_t ms); + +void cyw43_delay_us(uint32_t us); + +void cyw43_schedule_internal_poll_dispatch(void (*func)(void)); + +void cyw43_post_poll_hook(void); + +#define CYW43_POST_POLL_HOOK cyw43_post_poll_hook(); + +static inline void cyw43_arch_lwip_begin(void) { + cyw43_thread_enter(); +} + +static inline void cyw43_arch_lwip_end(void) { + cyw43_thread_exit(); +} + +static inline int cyw43_arch_lwip_protect(int (*func)(void *param), void *param) { + cyw43_arch_lwip_begin(); + int rc = func(param); + cyw43_arch_lwip_end(); + return rc; +} + +#ifdef __cplusplus +} +#endif + +#endif \ No newline at end of file diff --git a/src/rp2_common/pico_fix/rp2040_usb_device_enumeration/rp2040_usb_device_enumeration.c b/src/rp2_common/pico_fix/rp2040_usb_device_enumeration/rp2040_usb_device_enumeration.c index ad9e822..503cd12 100644 --- a/src/rp2_common/pico_fix/rp2040_usb_device_enumeration/rp2040_usb_device_enumeration.c +++ b/src/rp2_common/pico_fix/rp2040_usb_device_enumeration/rp2040_usb_device_enumeration.c @@ -17,12 +17,12 @@ #define LS_K 0b10 #define LS_SE1 0b11 +#if PICO_RP2040_B0_SUPPORTED || PICO_RP2040_B1_SUPPORTED static void hw_enumeration_fix_wait_se0(void); static void hw_enumeration_fix_force_ls_j(void); static void hw_enumeration_fix_finish(void); void rp2040_usb_device_enumeration_fix(void) { -#if PICO_RP2040_B0_SUPPORTED || PICO_RP2040_B1_SUPPORTED // Actually check for B0/B1 h/w if (rp2040_chip_version() == 1) { // After coming out of reset, the hardware expects 800us of LS_J (linestate J) time @@ -36,7 +36,6 @@ void rp2040_usb_device_enumeration_fix(void) { // Wait SE0 phase will call force ls_j phase which will call finish phase hw_enumeration_fix_wait_se0(); } -#endif } static inline uint8_t hw_line_state(void) { @@ -146,3 +145,9 @@ static void hw_enumeration_fix_finish(void) { // Restore the pad ctrl value padsbank0_hw->io[dp] = pad_ctrl_prev; } + +#else +void rp2040_usb_device_enumeration_fix(void) { + // nothing to do +} +#endif \ No newline at end of file diff --git a/src/rp2_common/pico_lwip/CMakeLists.txt b/src/rp2_common/pico_lwip/CMakeLists.txt new file mode 100644 index 0000000..7b1a8a7 --- /dev/null +++ b/src/rp2_common/pico_lwip/CMakeLists.txt @@ -0,0 +1,278 @@ +if (DEFINED ENV{PICO_LWIP_PATH} AND (NOT PICO_LWIP_PATH)) + set(PICO_LWIP_PATH $ENV{PICO_LWIP_PATH}) + message("Using PICO_LWIP_PATH from environment ('${PICO_LWIP_PATH}')") +endif () + +set(LWIP_TEST_PATH "src/Filelists.cmake") +if (NOT PICO_LWIP_PATH) + set(PICO_LWIP_PATH ${PROJECT_SOURCE_DIR}/lib/lwip) +# if (NOT EXISTS ${PICO_LWIP_PATH}/${LWIP_TEST_PATH}) +# message(WARNING "LWIP submodule has not been initialized; Pico W wireless support will be unavailable +#hint: try 'git submodule update --init' from your SDK directory (${PICO_SDK_PATH}).") +# endif() +elseif (NOT EXISTS ${PICO_LWIP_PATH}/${LWIP_TEST_PATH}) + message(WARNING "PICO_LWIP_PATH specified but content not present.") +endif() + +if (EXISTS ${PICO_LWIP_PATH}/${LWIP_TEST_PATH}) + message("lwIP available at ${PICO_LWIP_PATH}") + + # argh... wanted to use this, but they dump stuff into the source tree, which breaks parallel builds + #set(LWIP_DIR ${PICO_LWIP_PATH}) + #include(${PICO_LWIP_PATH}/src/Filelists.cmake) + + pico_register_common_scope_var(PICO_LWIP_PATH) + + # The minimum set of files needed for lwIP. + add_library(pico_lwip_core INTERFACE) + target_sources(pico_lwip_core INTERFACE + ${PICO_LWIP_PATH}/src/core/init.c + ${PICO_LWIP_PATH}/src/core/def.c + ${PICO_LWIP_PATH}/src/core/dns.c + ${PICO_LWIP_PATH}/src/core/inet_chksum.c + ${PICO_LWIP_PATH}/src/core/ip.c + ${PICO_LWIP_PATH}/src/core/mem.c + ${PICO_LWIP_PATH}/src/core/memp.c + ${PICO_LWIP_PATH}/src/core/netif.c + ${PICO_LWIP_PATH}/src/core/pbuf.c + ${PICO_LWIP_PATH}/src/core/raw.c + ${PICO_LWIP_PATH}/src/core/stats.c + ${PICO_LWIP_PATH}/src/core/sys.c + ${PICO_LWIP_PATH}/src/core/altcp.c + ${PICO_LWIP_PATH}/src/core/altcp_alloc.c + ${PICO_LWIP_PATH}/src/core/altcp_tcp.c + ${PICO_LWIP_PATH}/src/core/tcp.c + ${PICO_LWIP_PATH}/src/core/tcp_in.c + ${PICO_LWIP_PATH}/src/core/tcp_out.c + ${PICO_LWIP_PATH}/src/core/timeouts.c + ${PICO_LWIP_PATH}/src/core/udp.c + ${CMAKE_CURRENT_LIST_DIR}/random.c + ) + target_include_directories(pico_lwip_core INTERFACE + ${PICO_LWIP_PATH}/src/include) + + add_library(pico_lwip_core4 INTERFACE) + target_sources(pico_lwip_core4 INTERFACE + ${PICO_LWIP_PATH}/src/core/ipv4/acd.c + ${PICO_LWIP_PATH}/src/core/ipv4/autoip.c + ${PICO_LWIP_PATH}/src/core/ipv4/dhcp.c + ${PICO_LWIP_PATH}/src/core/ipv4/etharp.c + ${PICO_LWIP_PATH}/src/core/ipv4/icmp.c + ${PICO_LWIP_PATH}/src/core/ipv4/igmp.c + ${PICO_LWIP_PATH}/src/core/ipv4/ip4_frag.c + ${PICO_LWIP_PATH}/src/core/ipv4/ip4.c + ${PICO_LWIP_PATH}/src/core/ipv4/ip4_addr.c + ) + + add_library(pico_lwip_core6 INTERFACE) + target_sources(pico_lwip_core6 INTERFACE + ${PICO_LWIP_PATH}/src/core/ipv6/dhcp6.c + ${PICO_LWIP_PATH}/src/core/ipv6/ethip6.c + ${PICO_LWIP_PATH}/src/core/ipv6/icmp6.c + ${PICO_LWIP_PATH}/src/core/ipv6/inet6.c + ${PICO_LWIP_PATH}/src/core/ipv6/ip6.c + ${PICO_LWIP_PATH}/src/core/ipv6/ip6_addr.c + ${PICO_LWIP_PATH}/src/core/ipv6/ip6_frag.c + ${PICO_LWIP_PATH}/src/core/ipv6/mld6.c + ${PICO_LWIP_PATH}/src/core/ipv6/nd6.c + ) + + # APIFILES: The files which implement the sequential and socket APIs. + add_library(pico_lwip_api INTERFACE) + target_sources(pico_lwip_api INTERFACE + ${PICO_LWIP_PATH}/src/api/api_lib.c + ${PICO_LWIP_PATH}/src/api/api_msg.c + ${PICO_LWIP_PATH}/src/api/err.c + ${PICO_LWIP_PATH}/src/api/if_api.c + ${PICO_LWIP_PATH}/src/api/netbuf.c + ${PICO_LWIP_PATH}/src/api/netdb.c + ${PICO_LWIP_PATH}/src/api/netifapi.c + ${PICO_LWIP_PATH}/src/api/sockets.c + ${PICO_LWIP_PATH}/src/api/tcpip.c + ) + + # Files implementing various generic network interface functions + add_library(pico_lwip_netif INTERFACE) + target_sources(pico_lwip_netif INTERFACE + ${PICO_LWIP_PATH}/src/netif/ethernet.c + ${PICO_LWIP_PATH}/src/netif/bridgeif.c + ${PICO_LWIP_PATH}/src/netif/bridgeif_fdb.c + ${PICO_LWIP_PATH}/src/netif/slipif.c + ) + + # 6LoWPAN + add_library(pico_lwip_sixlowpan INTERFACE) + target_sources(pico_lwip_sixlowpan INTERFACE + ${PICO_LWIP_PATH}/src/netif/lowpan6_common.c + ${PICO_LWIP_PATH}/src/netif/lowpan6.c + ${PICO_LWIP_PATH}/src/netif/lowpan6_ble.c + ${PICO_LWIP_PATH}/src/netif/zepif.c + ) + + # PPP + add_library(pico_lwip_ppp INTERFACE) + target_sources(pico_lwip_ppp INTERFACE + ${PICO_LWIP_PATH}/src/netif/ppp/auth.c + ${PICO_LWIP_PATH}/src/netif/ppp/ccp.c + ${PICO_LWIP_PATH}/src/netif/ppp/chap-md5.c + ${PICO_LWIP_PATH}/src/netif/ppp/chap_ms.c + ${PICO_LWIP_PATH}/src/netif/ppp/chap-new.c + ${PICO_LWIP_PATH}/src/netif/ppp/demand.c + ${PICO_LWIP_PATH}/src/netif/ppp/eap.c + ${PICO_LWIP_PATH}/src/netif/ppp/ecp.c + ${PICO_LWIP_PATH}/src/netif/ppp/eui64.c + ${PICO_LWIP_PATH}/src/netif/ppp/fsm.c + ${PICO_LWIP_PATH}/src/netif/ppp/ipcp.c + ${PICO_LWIP_PATH}/src/netif/ppp/ipv6cp.c + ${PICO_LWIP_PATH}/src/netif/ppp/lcp.c + ${PICO_LWIP_PATH}/src/netif/ppp/magic.c + ${PICO_LWIP_PATH}/src/netif/ppp/mppe.c + ${PICO_LWIP_PATH}/src/netif/ppp/multilink.c + ${PICO_LWIP_PATH}/src/netif/ppp/ppp.c + ${PICO_LWIP_PATH}/src/netif/ppp/pppapi.c + ${PICO_LWIP_PATH}/src/netif/ppp/pppcrypt.c + ${PICO_LWIP_PATH}/src/netif/ppp/pppoe.c + ${PICO_LWIP_PATH}/src/netif/ppp/pppol2tp.c + ${PICO_LWIP_PATH}/src/netif/ppp/pppos.c + ${PICO_LWIP_PATH}/src/netif/ppp/upap.c + ${PICO_LWIP_PATH}/src/netif/ppp/utils.c + ${PICO_LWIP_PATH}/src/netif/ppp/vj.c + ${PICO_LWIP_PATH}/src/netif/ppp/polarssl/arc4.c + ${PICO_LWIP_PATH}/src/netif/ppp/polarssl/des.c + ${PICO_LWIP_PATH}/src/netif/ppp/polarssl/md4.c + ${PICO_LWIP_PATH}/src/netif/ppp/polarssl/md5.c + ${PICO_LWIP_PATH}/src/netif/ppp/polarssl/sha1.c + ) + + # SNMPv3 agent + add_library(pico_lwip_snmp INTERFACE) + target_sources(pico_lwip_snmp INTERFACE + ${PICO_LWIP_PATH}/src/apps/snmp/snmp_asn1.c + ${PICO_LWIP_PATH}/src/apps/snmp/snmp_core.c + ${PICO_LWIP_PATH}/src/apps/snmp/snmp_mib2.c + ${PICO_LWIP_PATH}/src/apps/snmp/snmp_mib2_icmp.c + ${PICO_LWIP_PATH}/src/apps/snmp/snmp_mib2_interfaces.c + ${PICO_LWIP_PATH}/src/apps/snmp/snmp_mib2_ip.c + ${PICO_LWIP_PATH}/src/apps/snmp/snmp_mib2_snmp.c + ${PICO_LWIP_PATH}/src/apps/snmp/snmp_mib2_system.c + ${PICO_LWIP_PATH}/src/apps/snmp/snmp_mib2_tcp.c + ${PICO_LWIP_PATH}/src/apps/snmp/snmp_mib2_udp.c + ${PICO_LWIP_PATH}/src/apps/snmp/snmp_snmpv2_framework.c + ${PICO_LWIP_PATH}/src/apps/snmp/snmp_snmpv2_usm.c + ${PICO_LWIP_PATH}/src/apps/snmp/snmp_msg.c + ${PICO_LWIP_PATH}/src/apps/snmp/snmpv3.c + ${PICO_LWIP_PATH}/src/apps/snmp/snmp_netconn.c + ${PICO_LWIP_PATH}/src/apps/snmp/snmp_pbuf_stream.c + ${PICO_LWIP_PATH}/src/apps/snmp/snmp_raw.c + ${PICO_LWIP_PATH}/src/apps/snmp/snmp_scalar.c + ${PICO_LWIP_PATH}/src/apps/snmp/snmp_table.c + ${PICO_LWIP_PATH}/src/apps/snmp/snmp_threadsync.c + ${PICO_LWIP_PATH}/src/apps/snmp/snmp_traps.c + ) + + # HTTP server + client + add_library(pico_lwip_http INTERFACE) + target_sources(pico_lwip_http INTERFACE + ${PICO_LWIP_PATH}/src/apps/http/altcp_proxyconnect.c + ${PICO_LWIP_PATH}/src/apps/http/fs.c + ${PICO_LWIP_PATH}/src/apps/http/http_client.c + ${PICO_LWIP_PATH}/src/apps/http/httpd.c + ) + + # MAKEFSDATA HTTP server host utility + add_library(pico_lwip_makefsdata INTERFACE) + target_sources(pico_lwip_makefsdata INTERFACE + ${PICO_LWIP_PATH}/src/apps/http/makefsdata/makefsdata.c + ) + + # iperf + add_library(pico_lwip_iperf INTERFACE) + target_sources(pico_lwip_iperf INTERFACE + ${PICO_LWIP_PATH}/src/apps/lwiperf/lwiperf.c + ) + + # SMTP client + add_library(pico_lwip_smtp INTERFACE) + target_sources(pico_lwip_smtp INTERFACE + ${PICO_LWIP_PATH}/src/apps/smtp/smtp.c + ) + + # SNTP client + add_library(pico_lwip_sntp INTERFACE) + target_sources(pico_lwip_sntp INTERFACE + ${PICO_LWIP_PATH}/src/apps/sntp/sntp.c + ) + + # MDNS responder + add_library(pico_lwip_mdns INTERFACE) + target_sources(pico_lwip_mdns INTERFACE + ${PICO_LWIP_PATH}/src/apps/mdns/mdns.c + ${PICO_LWIP_PATH}/src/apps/mdns/mdns_out.c + ${PICO_LWIP_PATH}/src/apps/mdns/mdns_domain.c + ) + + # NetBIOS name server + add_library(pico_lwip_netbios INTERFACE) + target_sources(pico_lwip_netbios INTERFACE + ${PICO_LWIP_PATH}/src/apps/netbiosns/netbiosns.c + ) + + # TFTP server files + add_library(pico_lwip_tftp INTERFACE) + target_sources(pico_lwip_tftp INTERFACE + ${PICO_LWIP_PATH}/src/apps/tftp/tftp.c + ) + + # MQTT client files + add_library(pico_lwip_mbedtls INTERFACE) + target_sources(pico_lwip_mbedtls INTERFACE + ${PICO_LWIP_PATH}/src/apps/altcp_tls/altcp_tls_mbedtls.c + ${PICO_LWIP_PATH}/src/apps/altcp_tls/altcp_tls_mbedtls_mem.c + ${PICO_LWIP_PATH}/src/apps/snmp/snmpv3_mbedtls.c + ) + + + # All LWIP files without apps + add_library(pico_lwip INTERFACE) + target_link_libraries(pico_lwip INTERFACE + pico_lwip_core + pico_lwip_core4 + pico_lwip_core6 + pico_lwip_api + pico_lwip_netif + pico_lwip_sixlowpan + pico_lwip_ppp + ) + + # our arch/cc.h + add_library(pico_lwip_arch INTERFACE) + target_include_directories(pico_lwip_arch INTERFACE + ${CMAKE_CURRENT_LIST_DIR}/include) + + # our nosys impl + add_library(pico_lwip_nosys INTERFACE) + target_sources(pico_lwip_nosys INTERFACE + ${CMAKE_CURRENT_LIST_DIR}/nosys.c + ) + target_link_libraries(pico_lwip_nosys INTERFACE + pico_lwip_arch) + + + if (NOT PICO_LWIP_CONTRIB_PATH) + set(PICO_LWIP_CONTRIB_PATH ${PICO_LWIP_PATH}/contrib) + endif() + pico_register_common_scope_var(PICO_LWIP_CONTRIB_PATH) + + # Make lwip_contrib_freertos library, with the FreeRTOS/lwIP code from lwip-contrib + add_library(pico_lwip_contrib_freertos INTERFACE) + target_sources(pico_lwip_contrib_freertos INTERFACE + ${PICO_LWIP_CONTRIB_PATH}/ports/freertos/sys_arch.c + ) + target_include_directories(pico_lwip_contrib_freertos INTERFACE + ${PICO_LWIP_CONTRIB_PATH}/ports/freertos/include + ) + target_link_libraries(pico_lwip_contrib_freertos INTERFACE + pico_lwip_arch) + + pico_promote_common_scope_vars() +endif() diff --git a/src/rp2_common/pico_lwip/doc.h b/src/rp2_common/pico_lwip/doc.h new file mode 100644 index 0000000..ede60b6 --- /dev/null +++ b/src/rp2_common/pico_lwip/doc.h @@ -0,0 +1,44 @@ +/** + * \defgroup pico_lwip pico_lwip + * \brief Wrapper libraries for lwIP + * + * The following libraries are provided that contain the equivalent lwIP functionality groups: + * + * * \c \b pico_lwip_core - + * * \c \b pico_lwip_core4 - + * * \c \b pico_lwip_core6 - + * * \c \b pico_lwip_netif - + * * \c \b pico_lwip_sixlowpan - + * * \c \b pico_lwip_ppp - + * * \c \b pico_lwip_api - + * + * The following libraries are provided that contain the equivalent lwIP application support: + * + * * \c \b pico_lwip_snmp - + * * \c \b pico_lwip_http - + * * \c \b pico_lwip_makefsdata - + * * \c \b pico_lwip_iperf - + * * \c \b pico_lwip_smtp - + * * \c \b pico_lwip_sntp - + * * \c \b pico_lwip_mdns - + * * \c \b pico_lwip_netbios - + * * \c \b pico_lwip_tftp - + * * \c \b pico_lwip_mbedtls - + * + * The SDK Provides a common set of functionality in \c \p pico_lwip which aggregates: + * + * * \c \b pico_lwip_core - + * * \c \b pico_lwip_core4 - + * * \c \b pico_lwip_core6 - + * * \c \b pico_lwip_netif - + * * \c \b pico_lwip_sixlowpan - + * * \c \b pico_lwip_ppp - + * + * The following additional libraries are provided: + * + * * \c \b pico_lwip - Aggregates the lwIP RAW API: \c \b pico_lwip_core, \c \b pico_lwip_core4, \c \b pico_lwip_core6, \c \b pico_lwip_api, \c \b pico_lwip_netif, \c \b pico_lwip_sixlowpan and \c \b pico_lwip_ppp. It does + * not include \c \b pico_lwip_api, which requires NO_SYS=0. You should include the latter separately if you want it. + * + * * \c \b pico_lwip_arch - lwIP required compiler adapters. This is not included in \c \b pico_lwip in case you wish to replace them. + * * \c \b pico_lwip_nosys - basic stub functions for NO_SYS mode. + */ diff --git a/src/rp2_common/pico_lwip/include/arch/cc.h b/src/rp2_common/pico_lwip/include/arch/cc.h new file mode 100644 index 0000000..447e4d2 --- /dev/null +++ b/src/rp2_common/pico_lwip/include/arch/cc.h @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2001-2003 Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. 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. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. + * + * This file is part of the lwIP TCP/IP stack. + * + * Author: Adam Dunkels + * + */ +#ifndef __CC_H__ +#define __CC_H__ + +#if NO_SYS +// todo really we should just not allow SYS_LIGHTWEIGHT_PROT for nosys mode (it doesn't do anything anyway) +typedef int sys_prot_t; +#endif + +/* define compiler specific symbols */ +#if defined (__ICCARM__) + +#define PACK_STRUCT_BEGIN +#define PACK_STRUCT_STRUCT +#define PACK_STRUCT_END +#define PACK_STRUCT_FIELD(x) x +#define PACK_STRUCT_USE_INCLUDES + +#elif defined (__CC_ARM) + +#define PACK_STRUCT_BEGIN __packed +#define PACK_STRUCT_STRUCT +#define PACK_STRUCT_END +#define PACK_STRUCT_FIELD(x) x + +#elif defined (__GNUC__) + +#define PACK_STRUCT_BEGIN +#define PACK_STRUCT_STRUCT __attribute__ ((__packed__)) +#define PACK_STRUCT_END +#define PACK_STRUCT_FIELD(x) x + +#elif defined (__TASKING__) + +#define PACK_STRUCT_BEGIN +#define PACK_STRUCT_STRUCT +#define PACK_STRUCT_END +#define PACK_STRUCT_FIELD(x) x + +#endif + +#define LWIP_PLATFORM_ASSERT(x) do { if(!(x)) while(1); } while(0) + +unsigned int pico_lwip_rand(void); +#ifndef LWIP_RAND +// Use ROSC based random number generation, more for the fact that rand() may not be seeded, than anything else +#define LWIP_RAND pico_lwip_rand +#endif +#endif /* __CC_H__ */ diff --git a/src/rp2_common/pico_lwip/nosys.c b/src/rp2_common/pico_lwip/nosys.c new file mode 100644 index 0000000..4e7605d --- /dev/null +++ b/src/rp2_common/pico_lwip/nosys.c @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2022 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include "lwip/init.h" +#include "pico/time.h" + +#if NO_SYS +/* lwip has provision for using a mutex, when applicable */ +sys_prot_t sys_arch_protect(void) { + return 0; +} + +void sys_arch_unprotect(sys_prot_t pval) { + (void) pval; +} + +/* lwip needs a millisecond time source, and the TinyUSB board support code has one available */ +uint32_t sys_now(void) { + return to_ms_since_boot(get_absolute_time()); +} + +#endif + diff --git a/src/rp2_common/pico_lwip/random.c b/src/rp2_common/pico_lwip/random.c new file mode 100644 index 0000000..2c4cdf8 --- /dev/null +++ b/src/rp2_common/pico_lwip/random.c @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2020 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include "pico.h" +#include "hardware/structs/rosc.h" + +static uint8_t pico_lwip_random_byte(int cycles) { + static uint8_t byte; + assert(cycles >= 8); + assert(rosc_hw->status & ROSC_STATUS_ENABLED_BITS); + for(int i=0;irandombit) ^ (byte & 0x80u ? 0x35u : 0); + // delay a little because the random bit is a little slow + busy_wait_at_least_cycles(30); + } + return byte; +} + +unsigned int pico_lwip_rand(void) { + uint32_t value = 0; + for (int i = 0; i < 4; i++) { + value = (value << 8u) | pico_lwip_random_byte(32); + } + return value; +} \ No newline at end of file diff --git a/src/rp2_common/tinyusb/CMakeLists.txt b/src/rp2_common/tinyusb/CMakeLists.txt index ea919cd..8978788 100644 --- a/src/rp2_common/tinyusb/CMakeLists.txt +++ b/src/rp2_common/tinyusb/CMakeLists.txt @@ -53,5 +53,6 @@ if (EXISTS ${PICO_TINYUSB_PATH}/${TINYUSB_TEST_PATH}) PROPERTIES COMPILE_FLAGS "-Wno-stringop-overflow -Wno-array-bounds") endfunction() + pico_promote_common_scope_vars() endif() diff --git a/test/kitchen_sink/CMakeLists.txt b/test/kitchen_sink/CMakeLists.txt index 3487fbb..a201991 100644 --- a/test/kitchen_sink/CMakeLists.txt +++ b/test/kitchen_sink/CMakeLists.txt @@ -41,11 +41,6 @@ target_link_libraries(kitchen_sink_libs INTERFACE pico_unique_id pico_util ) -# todo this is full of warnings atm -#if (TARGET tinyusb_device) -# target_include_directories(kitchen_sink_libs INTERFACE ${CMAKE_CURRENT_LIST_DIR}) -# target_link_libraries(kitchen_sink_libs INTERFACE tinyusb_device) -#endif() add_library(kitchen_sink_options INTERFACE) @@ -63,7 +58,7 @@ target_compile_options(kitchen_sink_options INTERFACE -Wcast-qual -Wfloat-equal -Wmissing-format-attribute - -Wconversion + #-Wconversion -Wsign-compare $<$:-Wstrict-prototypes> @@ -100,6 +95,13 @@ if (COMMAND suppress_tinyusb_warnings) # TinyUSB itself, so we have to guard against TinyUSB not being present with the above if suppress_tinyusb_warnings() endif() +if (TARGET pico_lwip) + set_source_files_properties( + ${PICO_LWIP_PATH}/src/core/ipv4/ip4_frag.c + PROPERTIES + COMPILE_FLAGS "-Wno-null-dereference") + +endif() target_link_libraries(kitchen_sink_extra_stdio kitchen_sink_libs kitchen_sink_options) pico_add_extra_outputs(kitchen_sink_extra_stdio) pico_enable_stdio_usb(kitchen_sink_extra_stdio 1) @@ -119,3 +121,25 @@ add_executable(kitchen_sink_cpp ${CMAKE_CURRENT_LIST_DIR}/kitchen_sink_cpp.cpp) target_link_libraries(kitchen_sink_cpp kitchen_sink_libs kitchen_sink_options) pico_set_program_name(kitchen_sink_cpp "Wombat tentacles CPP") pico_add_extra_outputs(kitchen_sink_cpp) + +if (TARGET pico_cyw43_arch) + # for lwipopts.h + add_executable(kitchen_sink_lwip_poll ${CMAKE_CURRENT_LIST_DIR}/kitchen_sink.c) + target_link_libraries(kitchen_sink_lwip_poll kitchen_sink_libs kitchen_sink_options) + pico_add_extra_outputs(kitchen_sink_lwip_poll) + target_link_libraries(kitchen_sink_lwip_poll + pico_cyw43_arch_lwip_poll) + # for lwipopts.h + target_include_directories(kitchen_sink_lwip_poll PRIVATE + ${CMAKE_CURRENT_LIST_DIR}) + + add_executable(kitchen_sink_lwip_background ${CMAKE_CURRENT_LIST_DIR}/kitchen_sink.c) + target_link_libraries(kitchen_sink_lwip_background kitchen_sink_libs kitchen_sink_options) + pico_add_extra_outputs(kitchen_sink_lwip_background) + target_link_libraries(kitchen_sink_lwip_background + pico_cyw43_arch_lwip_threadsafe_background) + # for lwipopts.h + target_include_directories(kitchen_sink_lwip_background PRIVATE + ${CMAKE_CURRENT_LIST_DIR}) + +endif() diff --git a/test/kitchen_sink/kitchen_sink.c b/test/kitchen_sink/kitchen_sink.c index 62fe03a..a49654d 100644 --- a/test/kitchen_sink/kitchen_sink.c +++ b/test/kitchen_sink/kitchen_sink.c @@ -47,6 +47,9 @@ #include "pico/sync.h" #include "pico/time.h" #include "pico/unique_id.h" +#if LIB_PICO_CYW43_ARCH +#include "pico/cyw43_arch.h" +#endif #include "hardware/structs/adc.h" #include "hardware/structs/bus_ctrl.h" diff --git a/test/kitchen_sink/lwipopts.h b/test/kitchen_sink/lwipopts.h new file mode 100644 index 0000000..58b704a --- /dev/null +++ b/test/kitchen_sink/lwipopts.h @@ -0,0 +1,14 @@ +#ifndef _LWIPOPTS_H +#define _LWIPOPTS_H + +// dummy lwip opts to allow compilation + +#define NO_SYS 1 +#define LWIP_DHCP 1 +#define LWIP_RAW 1 +#define LWIP_NETIF_HOSTNAME 1 +#define LWIP_NETIF_STATUS_CALLBACK 1 +#define LWIP_DNS 1 +#define LWIP_SOCKET 0 +#define LWIP_NETCONN 0 +#endif \ No newline at end of file