diff --git a/README.md b/README.md index 22829739f..eeb6aff66 100644 --- a/README.md +++ b/README.md @@ -1 +1 @@ -# ATTENTION! THIS IS THE QMK FORK OF PICO SDK WHICH REMOVES TINYUSB AS A SUBMODULE. ALL USB FUNCTIONALITY IS HANDLED BY THE CHIBIOS USB STACK! +# ATTENTION! THIS IS THE QMK FORK OF PICO SDK WHICH REMOVES TINYUSB, CYW43-DRIVER and LWIP SUBMODULES. ALL USB FUNCTIONALITY IS HANDLED BY THE CHIBIOS USB STACK! diff --git a/cmake/pico_pre_load_platform.cmake b/cmake/pico_pre_load_platform.cmake index 51ee39a00..479eedf3b 100644 --- a/cmake/pico_pre_load_platform.cmake +++ b/cmake/pico_pre_load_platform.cmake @@ -13,7 +13,7 @@ endif () set(PICO_PLATFORM ${PICO_PLATFORM} CACHE STRING "PICO Build platform (e.g. rp2040, host)") -# PICO_CMAKE_CONFIG: PICO_CMAKE_RELOAD_PLATFORM_FILE, custom CMake file to use to set up the platform environment, default=none, group=build +# PICO_CMAKE_CONFIG: PICO_CMAKE_PRELOAD_PLATFORM_FILE, custom CMake file to use to set up the platform environment, default=none, group=build set(PICO_CMAKE_PRELOAD_PLATFORM_FILE "" CACHE INTERNAL "") set(PICO_CMAKE_PRELOAD_PLATFORM_DIR "${CMAKE_CURRENT_LIST_DIR}/preload/platforms" CACHE INTERNAL "") diff --git a/docs/Doxyfile.in b/docs/Doxyfile.in index cc133f88f..5dc4be72f 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 c7ef735a5..a334fc32e 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/pico_sdk_version.cmake b/pico_sdk_version.cmake index a9154cc64..63b30025d 100644 --- a/pico_sdk_version.cmake +++ b/pico_sdk_version.cmake @@ -1,18 +1,18 @@ # PICO_BUILD_DEFINE: PICO_SDK_VERSION_MAJOR, SDK major version number, type=int, group=pico_base -# PICO_CONFIG: PICO_SDK_VERSION_MAJOR, SDK major version number, type=int, group=pico_base +# PICO_CMAKE_CONFIG: PICO_SDK_VERSION_MAJOR, SDK major version number, type=int, group=pico_base set(PICO_SDK_VERSION_MAJOR 1) # PICO_BUILD_DEFINE: PICO_SDK_VERSION_MINOR, SDK minor version number, type=int, group=pico_base -# PICO_CONFIG: PICO_SDK_VERSION_MINOR, SDK minor version number, type=int, group=pico_base -set(PICO_SDK_VERSION_MINOR 3) +# PICO_CMAKE_CONFIG: PICO_SDK_VERSION_MINOR, SDK minor version number, type=int, group=pico_base +set(PICO_SDK_VERSION_MINOR 4) # PICO_BUILD_DEFINE: PICO_SDK_VERSION_REVISION, SDK version revision, type=int, group=pico_base -# PICO_CONFIG: PICO_SDK_VERSION_REVISION, SDK version revision, type=int, group=pico_base +# PICO_CMAKE_CONFIG: PICO_SDK_VERSION_REVISION, SDK version revision, type=int, group=pico_base set(PICO_SDK_VERSION_REVISION 1) # PICO_BUILD_DEFINE: PICO_SDK_VERSION_PRE_RELEASE_ID, optional SDK pre-release version identifier, type=string, group=pico_base -# PICO_CONFIG: PICO_SDK_VERSION_PRE_RELEASE_ID, optional SDK pre-release version identifier, type=string, group=pico_base +# PICO_CMAKE_CONFIG: PICO_SDK_VERSION_PRE_RELEASE_ID, optional SDK pre-release version identifier, type=string, group=pico_base set(PICO_SDK_VERSION_PRE_RELEASE_ID develop) # PICO_BUILD_DEFINE: PICO_SDK_VERSION_STRING, SDK version, type=string, group=pico_base -# PICO_CONFIG: PICO_SDK_VERSION_STRING, SDK version, type=string, group=pico_base +# PICO_CMAKE_CONFIG: PICO_SDK_VERSION_STRING, SDK version, type=string, group=pico_base set(PICO_SDK_VERSION_STRING "${PICO_SDK_VERSION_MAJOR}.${PICO_SDK_VERSION_MINOR}.${PICO_SDK_VERSION_REVISION}") if (PICO_SDK_VERSION_PRE_RELEASE_ID) diff --git a/src/boards/include/boards/datanoisetv_rp2040_dsp.h b/src/boards/include/boards/datanoisetv_rp2040_dsp.h new file mode 100644 index 000000000..49a357bc6 --- /dev/null +++ b/src/boards/include/boards/datanoisetv_rp2040_dsp.h @@ -0,0 +1,60 @@ +/* + * 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 +// ----------------------------------------------------- +// +//------------------------------------------------------------------------------------------ +// Board definition for the DatanoiseTV RP2040 DSP Board +// +// This header may be included by other board headers as "boards/datanoisetv_rp2040_dsp.h" + +#ifndef _BOARDS_DATANOISETV_RP2040_DSP_H +#define _BOARDS_DATANOISETV_RP2040_DSP_H + +// For board detection +#define DATANOISETV_RP2040_DSP + +// --- I2C --- +#ifndef PICO_DEFAULT_I2C +#define PICO_DEFAULT_I2C 0 +#endif +#ifndef PICO_DEFAULT_I2C_SDA_PIN +#define PICO_DEFAULT_I2C_SDA_PIN 24 +#endif +#ifndef PICO_DEFAULT_I2C_SCL_PIN +#define PICO_DEFAULT_I2C_SCL_PIN 25 +#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 (16 * 1024 * 1024) +#endif + +#ifndef PICO_RP2040_B0_SUPPORTED +#define PICO_RP2040_B0_SUPPORTED 0 +#endif + + +// --- I2S --- +#ifndef PICO_AUDIO_I2S_DATA_PIN +#define PICO_AUDIO_I2S_DATA_PIN 16 +#endif +#ifndef PICO_AUDIO_I2S_CLOCK_PIN_BASE +#define PICO_AUDIO_I2S_CLOCK_PIN_BASE 17 +#endif + +#include "boards/pico.h" + +#endif diff --git a/src/boards/include/boards/pico_w.h b/src/boards/include/boards/pico_w.h new file mode 100644 index 000000000..b4a8bd7ae --- /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/include/boards/solderparty_rp2040_stamp_round_carrier.h b/src/boards/include/boards/solderparty_rp2040_stamp_round_carrier.h new file mode 100644 index 000000000..bb9d3351c --- /dev/null +++ b/src/boards/include/boards/solderparty_rp2040_stamp_round_carrier.h @@ -0,0 +1,79 @@ +/* + * 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 +// ----------------------------------------------------- +// +//------------------------------------------------------------------------------------------ +// Board definition for the Solder Party RP2040 Stamp Round Carrier +// +// This header may be included by other board headers as "boards/solderparty_rp2040_stamp_round_carrier.h" + +#ifndef _BOARDS_SOLDERPARTY_RP2040_STAMP_ROUND_CARRIER_H +#define _BOARDS_SOLDERPARTY_RP2040_STAMP_ROUND_CARRIER_H + +// For board detection +#define SOLDERPARTY_RP2040_STAMP_ROUND_CARRIER + +// --- User LED --- +#ifndef PICO_DEFAULT_LED_PIN +#define PICO_DEFAULT_LED_PIN 25 +#endif + +// --- UART --- +#ifndef PICO_DEFAULT_UART +#define PICO_DEFAULT_UART 0 +#endif +#ifndef PICO_DEFAULT_UART_TX_PIN +#define PICO_DEFAULT_UART_TX_PIN 16 +#endif +#ifndef PICO_DEFAULT_UART_RX_PIN +#define PICO_DEFAULT_UART_RX_PIN 17 +#endif + +// --- Neopixel Ring --- +#ifndef PICO_DEFAULT_WS2812_PIN +#define PICO_DEFAULT_WS2812_PIN 24 +#endif + +// --- On-Stamp Neopixel --- +#ifndef SOLDERPARTY_RP2040_STAMP_WS2812_PIN +#define SOLDERPARTY_RP2040_STAMP_WS2812_PIN 21 +#endif + +// --- I2C --- +#ifndef PICO_DEFAULT_I2C +#define PICO_DEFAULT_I2C 0 +#endif +#ifndef PICO_DEFAULT_I2C_SDA_PIN +#define PICO_DEFAULT_I2C_SDA_PIN 0 +#endif +#ifndef PICO_DEFAULT_I2C_SCL_PIN +#define PICO_DEFAULT_I2C_SCL_PIN 1 +#endif + +// --- SPI --- +#ifndef PICO_DEFAULT_SPI +#define PICO_DEFAULT_SPI 1 +#endif +#ifndef PICO_DEFAULT_SPI_SCK_PIN +#define PICO_DEFAULT_SPI_SCK_PIN 10 +#endif +#ifndef PICO_DEFAULT_SPI_TX_PIN +#define PICO_DEFAULT_SPI_TX_PIN 11 +#endif +#ifndef PICO_DEFAULT_SPI_RX_PIN +#define PICO_DEFAULT_SPI_RX_PIN 8 +#endif +#ifndef PICO_DEFAULT_SPI_CSN_PIN +#define PICO_DEFAULT_SPI_CSN_PIN 9 +#endif + +#include "solderparty_rp2040_stamp.h" + +#endif diff --git a/src/boards/pico_w.cmake b/src/boards/pico_w.cmake new file mode 100644 index 000000000..15b901dd2 --- /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/generate_config_header.cmake b/src/common/pico_base/generate_config_header.cmake index 333dfa750..98401057c 100644 --- a/src/common/pico_base/generate_config_header.cmake +++ b/src/common/pico_base/generate_config_header.cmake @@ -13,8 +13,8 @@ endmacro() # PICO_CMAKE_CONFIG: PICO_CONFIG_HEADER_FILES, List of extra header files to include from pico/config.h for all platforms, type=list, default="", group=pico_base add_header_content_from_var(PICO_CONFIG_HEADER_FILES) -# PICO_CMAKE_CONFIG: PICO_CONFIG_RP2040_HEADER_FILES, List of extra header files to include from pico/config.h for rp2040 platform, type=list, default="", group=pico_base -# PICO_CMAKE_CONFIG: PICO_CONFIG_HOST_HEADER_FILES, List of extra header files to include from pico/config.h for host platform, type=list, default="", group=pico_base +# PICO_CMAKE_CONFIG: PICO_RP2040_CONFIG_HEADER_FILES, List of extra header files to include from pico/config.h for rp2040 platform, type=list, default="", group=pico_base +# PICO_CMAKE_CONFIG: PICO_HOST_CONFIG_HEADER_FILES, List of extra header files to include from pico/config.h for host platform, type=list, default="", group=pico_base add_header_content_from_var(PICO_${PICO_PLATFORM_UPPER}_CONFIG_HEADER_FILES) file(GENERATE @@ -26,4 +26,4 @@ configure_file( ${CMAKE_CURRENT_LIST_DIR}/include/pico/version.h.in ${CMAKE_BINA foreach(DIR IN LISTS PICO_INCLUDE_DIRS) target_include_directories(pico_base_headers INTERFACE ${DIR}) -endforeach() \ No newline at end of file +endforeach() diff --git a/src/common/pico_base/include/pico.h b/src/common/pico_base/include/pico.h index 1b7365120..784566e03 100644 --- a/src/common/pico_base/include/pico.h +++ b/src/common/pico_base/include/pico.h @@ -16,9 +16,18 @@ * This header may be included by assembly code */ +#define __PICO_STRING(x) #x +#define __PICO_XSTRING(x) __PICO_STRING(x) + #include "pico/types.h" #include "pico/version.h" + +// PICO_CONFIG: PICO_CONFIG_HEADER, unquoted path to header include in place of the default pico/config.h which may be desirable for build systems which can't easily generate the config_autogen header, group=pico_base +#ifdef PICO_CONFIG_HEADER +#include __PICO_XSTRING(PICO_CONFIG_HEADER) +#else #include "pico/config.h" +#endif #include "pico/platform.h" #include "pico/error.h" diff --git a/src/common/pico_base/include/pico/config.h b/src/common/pico_base/include/pico/config.h index 8d6926941..10a9c6af3 100644 --- a/src/common/pico_base/include/pico/config.h +++ b/src/common/pico_base/include/pico/config.h @@ -18,4 +18,9 @@ #include "pico/config_autogen.h" -#endif \ No newline at end of file +// PICO_CONFIG: PICO_CONFIG_RTOS_ADAPTER_HEADER, unquoted path to header include in the default pico/config.h for RTOS integration defines that must be included in all sources, group=pico_base +#ifdef PICO_CONFIG_RTOS_ADAPTER_HEADER +#include __PICO_XSTRING(PICO_CONFIG_RTOS_ADAPTER_HEADER) +#endif + +#endif diff --git a/src/common/pico_base/include/pico/error.h b/src/common/pico_base/include/pico/error.h index fadb45ec0..a5cbc3915 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/common/pico_binary_info/include/pico/binary_info/code.h b/src/common/pico_binary_info/include/pico/binary_info/code.h index 0f1cd1c73..9478477c9 100644 --- a/src/common/pico_binary_info/include/pico/binary_info/code.h +++ b/src/common/pico_binary_info/include/pico/binary_info/code.h @@ -7,7 +7,12 @@ #ifndef _PICO_BINARY_INFO_CODE_H #define _PICO_BINARY_INFO_CODE_H +// pico.h is not available when PICO_NO_BINARY_INFO=1 is used for builds outside of the SDK (e.g. picotool) +// and only needed anyway (because of macro definitions) in PICO_NO_BINARY_INFO=0 builds +#if !PICO_NO_BINARY_INFO #include "pico.h" +#endif + #include "pico/binary_info/structure.h" #if !PICO_NO_BINARY_INFO diff --git a/src/common/pico_sync/critical_section.c b/src/common/pico_sync/critical_section.c index f28732b7f..7cbb6227d 100644 --- a/src/common/pico_sync/critical_section.c +++ b/src/common/pico_sync/critical_section.c @@ -21,7 +21,5 @@ void critical_section_init_with_lock_num(critical_section_t *crit_sec, uint lock void critical_section_deinit(critical_section_t *crit_sec) { spin_lock_unclaim(spin_lock_get_num(crit_sec->spin_lock)); -#ifndef NDEBUG - crit_sec->spin_lock = (spin_lock_t *)-1; -#endif + crit_sec->spin_lock = NULL; } \ No newline at end of file diff --git a/src/common/pico_sync/include/pico/critical_section.h b/src/common/pico_sync/include/pico/critical_section.h index 2f9449475..9d61150a8 100644 --- a/src/common/pico_sync/include/pico/critical_section.h +++ b/src/common/pico_sync/include/pico/critical_section.h @@ -82,6 +82,16 @@ static inline void critical_section_exit(critical_section_t *crit_sec) { */ void critical_section_deinit(critical_section_t *crit_sec); +/*! \brief Test whether a critical_section has been initialized + * \ingroup mutex + * + * \param crit_sec Pointer to critical_section structure + * \return true if the critical section is initialized, false otherwise + */ +static inline bool critical_section_is_initialized(critical_section_t *crit_sec) { + return crit_sec->spin_lock != 0; +} + #ifdef __cplusplus } #endif diff --git a/src/common/pico_sync/include/pico/mutex.h b/src/common/pico_sync/include/pico/mutex.h index e834dc513..ca9376020 100644 --- a/src/common/pico_sync/include/pico/mutex.h +++ b/src/common/pico_sync/include/pico/mutex.h @@ -289,7 +289,7 @@ static inline bool recursive_mutex_is_initialized(recursive_mutex_t *mtx) { * * But the initialization of the mutex is performed automatically during runtime initialization */ -#define auto_init_recursive_mutex(name) static __attribute__((section(".mutex_array"))) recursive_mutex_t name = { .core.spin_lock = (spin_lock_t *)1 /* marker for runtime_init */ } +#define auto_init_recursive_mutex(name) static __attribute__((section(".mutex_array"))) recursive_mutex_t name = { .core = { .spin_lock = (spin_lock_t *)1 /* marker for runtime_init */ }, .owner = 0, .enter_count = 0 } #ifdef __cplusplus } diff --git a/src/common/pico_sync/include/pico/sem.h b/src/common/pico_sync/include/pico/sem.h index 6244e3246..f9f724b46 100644 --- a/src/common/pico_sync/include/pico/sem.h +++ b/src/common/pico_sync/include/pico/sem.h @@ -122,6 +122,17 @@ bool sem_acquire_timeout_us(semaphore_t *sem, uint32_t timeout_us); */ bool sem_acquire_block_until(semaphore_t *sem, absolute_time_t until); +/*! \brief Attempt to acquire a permit from a semaphore without blocking + * \ingroup sem + * + * This function will return false without blocking if no permits are + * available, otherwise it will acquire a permit and return true. + * + * \param sem Pointer to semaphore structure + * \return true if permit was acquired. + */ +bool sem_try_acquire(semaphore_t *sem); + #ifdef __cplusplus } #endif diff --git a/src/common/pico_sync/sem.c b/src/common/pico_sync/sem.c index 06b4946ef..ec49fdba5 100644 --- a/src/common/pico_sync/sem.c +++ b/src/common/pico_sync/sem.c @@ -23,7 +23,7 @@ void __time_critical_func(sem_acquire_blocking)(semaphore_t *sem) { uint32_t save = spin_lock_blocking(sem->core.spin_lock); if (sem->permits > 0) { sem->permits--; - lock_internal_spin_unlock_with_notify(&sem->core, save); + spin_unlock(sem->core.spin_lock, save); break; } lock_internal_spin_unlock_with_wait(&sem->core, save); @@ -43,7 +43,7 @@ bool __time_critical_func(sem_acquire_block_until)(semaphore_t *sem, absolute_ti uint32_t save = spin_lock_blocking(sem->core.spin_lock); if (sem->permits > 0) { sem->permits--; - lock_internal_spin_unlock_with_notify(&sem->core, save); + spin_unlock(sem->core.spin_lock, save); return true; } if (lock_internal_spin_unlock_with_best_effort_wait_or_timeout(&sem->core, save, until)) { @@ -52,6 +52,17 @@ bool __time_critical_func(sem_acquire_block_until)(semaphore_t *sem, absolute_ti } while (true); } +bool __time_critical_func(sem_try_acquire)(semaphore_t *sem) { + uint32_t save = spin_lock_blocking(sem->core.spin_lock); + if (sem->permits > 0) { + sem->permits--; + spin_unlock(sem->core.spin_lock, save); + return true; + } + spin_unlock(sem->core.spin_lock, save); + return false; +} + // todo this should really have a blocking variant for when permits are maxed out bool __time_critical_func(sem_release)(semaphore_t *sem) { uint32_t save = spin_lock_blocking(sem->core.spin_lock); diff --git a/src/common/pico_time/include/pico/time.h b/src/common/pico_time/include/pico/time.h index 0fde00feb..206c24c53 100644 --- a/src/common/pico_time/include/pico/time.h +++ b/src/common/pico_time/include/pico/time.h @@ -95,8 +95,9 @@ static inline absolute_time_t delayed_by_us(const absolute_time_t t, uint64_t us absolute_time_t t2; uint64_t base = to_us_since_boot(t); uint64_t delayed = base + us; - if (delayed < base) { - delayed = (uint64_t)-1; + if ((int64_t)delayed < 0) { + // absolute_time_t (to allow for signed time deltas) is never greater than INT64_MAX which == at_the_end_of_time + delayed = INT64_MAX; } update_us_since_boot(&t2, delayed); return t2; @@ -113,8 +114,9 @@ static inline absolute_time_t delayed_by_ms(const absolute_time_t t, uint32_t ms absolute_time_t t2; uint64_t base = to_us_since_boot(t); uint64_t delayed = base + ms * 1000ull; - if (delayed < base) { - delayed = (uint64_t)-1; + if ((int64_t)delayed < 0) { + // absolute_time_t (to allow for signed time deltas) is never greater than INT64_MAX which == at_the_end_of_time + delayed = INT64_MAX; } update_us_since_boot(&t2, delayed); return t2; @@ -198,7 +200,7 @@ static inline bool is_nil_time(absolute_time_t t) { * \note These functions should not be called from an IRQ handler. * * \note Lower powered sleep requires use of the \link alarm_pool_get_default default alarm pool\endlink which may - * be disabled by the #PICO_TIME_DEFAULT_ALARM_POOL_DISABLED define or currently full in which case these functions + * be disabled by the PICO_TIME_DEFAULT_ALARM_POOL_DISABLED #define or currently full in which case these functions * become busy waits instead. * * \note Whilst \a sleep_ functions are preferable to \a busy_wait functions from a power perspective, the \a busy_wait equivalent function @@ -403,6 +405,14 @@ alarm_pool_t *alarm_pool_create(uint hardware_alarm_num, uint max_timers); */ uint alarm_pool_hardware_alarm_num(alarm_pool_t *pool); +/** + * \brief Return the core number the alarm pool was initialized on (and hence callbacks are called on) + * \ingroup alarm + * \param pool the pool + * \return the core used by the pool + */ +uint alarm_pool_core_num(alarm_pool_t *pool); + /** * \brief Destroy the alarm pool, cancelling all alarms and freeing up the underlying hardware alarm * \ingroup alarm diff --git a/src/common/pico_time/time.c b/src/common/pico_time/time.c index 3a585f652..5ce704584 100644 --- a/src/common/pico_time/time.c +++ b/src/common/pico_time/time.c @@ -31,6 +31,7 @@ typedef struct alarm_pool { uint8_t *entry_ids_high; alarm_id_t alarm_in_progress; // this is set during a callback from the IRQ handler... it can be cleared by alarm_cancel to prevent repeats uint8_t hardware_alarm_num; + uint8_t core_num; } alarm_pool_t; #if !PICO_TIME_DEFAULT_ALARM_POOL_DISABLED @@ -190,6 +191,7 @@ void alarm_pool_post_alloc_init(alarm_pool_t *pool, uint hardware_alarm_num) { hardware_alarm_set_callback(hardware_alarm_num, alarm_pool_alarm_callback); pool->lock = spin_lock_instance(next_striped_spin_lock_num()); pool->hardware_alarm_num = (uint8_t) hardware_alarm_num; + pool->core_num = (uint8_t) get_core_num(); pools[hardware_alarm_num] = pool; } @@ -286,6 +288,10 @@ uint alarm_pool_hardware_alarm_num(alarm_pool_t *pool) { return pool->hardware_alarm_num; } +uint alarm_pool_core_num(alarm_pool_t *pool) { + return pool->core_num; +} + static void alarm_pool_dump_key(pheap_node_id_t id, void *user_data) { alarm_pool_t *pool = (alarm_pool_t *)user_data; #if PICO_ON_DEVICE diff --git a/src/host/hardware_sync/include/hardware/sync.h b/src/host/hardware_sync/include/hardware/sync.h index a27ea0100..58426f5e2 100644 --- a/src/host/hardware_sync/include/hardware/sync.h +++ b/src/host/hardware_sync/include/hardware/sync.h @@ -86,8 +86,6 @@ bool is_spin_locked(const spin_lock_t *lock); void spin_unlock(spin_lock_t *lock, uint32_t saved_irq); -uint get_core_num(); - spin_lock_t *spin_lock_init(uint lock_num); void clear_spin_locks(void); diff --git a/src/host/pico_platform/include/pico/platform.h b/src/host/pico_platform/include/pico/platform.h index a4d3eaf67..b5e81370d 100644 --- a/src/host/pico_platform/include/pico/platform.h +++ b/src/host/pico_platform/include/pico/platform.h @@ -139,6 +139,9 @@ static inline int32_t __mul_instruction(int32_t a,int32_t b) static inline void __compiler_memory_barrier(void) { } + +uint get_core_num(); + #ifdef __cplusplus } #endif diff --git a/src/rp2040/hardware_regs/include/hardware/platform_defs.h b/src/rp2040/hardware_regs/include/hardware/platform_defs.h index 08c715944..21f77cacd 100644 --- a/src/rp2040/hardware_regs/include/hardware/platform_defs.h +++ b/src/rp2040/hardware_regs/include/hardware/platform_defs.h @@ -21,6 +21,7 @@ #define NUM_DMA_CHANNELS _u(12) #define NUM_DMA_TIMERS _u(4) #define NUM_IRQS _u(32) +#define NUM_USER_IRQS _u(6) #define NUM_PIOS _u(2) #define NUM_PIO_STATE_MACHINES _u(4) #define NUM_PWM_SLICES _u(8) diff --git a/src/rp2040/hardware_regs/include/hardware/regs/pll.h b/src/rp2040/hardware_regs/include/hardware/regs/pll.h index a0f5ad0ef..9dba689ab 100644 --- a/src/rp2040/hardware_regs/include/hardware/regs/pll.h +++ b/src/rp2040/hardware_regs/include/hardware/regs/pll.h @@ -1,5 +1,5 @@ /** - * Copyright (c) 2021 Raspberry Pi (Trading) Ltd. + * Copyright (c) 2022 Raspberry Pi (Trading) Ltd. * * SPDX-License-Identifier: BSD-3-Clause */ @@ -17,7 +17,7 @@ // GENERAL CONSTRAINTS: // Reference clock frequency min=5MHz, max=800MHz // Feedback divider min=16, max=320 -// VCO frequency min=400MHz, max=1600MHz +// VCO frequency min=750MHz, max=1600MHz #define PLL_CS_OFFSET _u(0x00000000) #define PLL_CS_BITS _u(0x8000013f) #define PLL_CS_RESET _u(0x00000001) diff --git a/src/rp2040/hardware_regs/rp2040.svd b/src/rp2040/hardware_regs/rp2040.svd index 95e199027..3849776d7 100644 --- a/src/rp2040/hardware_regs/rp2040.svd +++ b/src/rp2040/hardware_regs/rp2040.svd @@ -22367,7 +22367,7 @@ GENERAL CONSTRAINTS:\n Reference clock frequency min=5MHz, max=800MHz\n Feedback divider min=16, max=320\n - VCO frequency min=400MHz, max=1600MHz + VCO frequency min=750MHz, max=1600MHz read-only diff --git a/src/rp2_common.cmake b/src/rp2_common.cmake index c04e55144..bc82e7cb0 100644 --- a/src/rp2_common.cmake +++ b/src/rp2_common.cmake @@ -22,7 +22,7 @@ function(pico_add_extra_outputs TARGET) pico_add_bin_output(${TARGET}) pico_add_dis_output(${TARGET}) - # PICO_CMAKE_CONFIG: PICO_NO_TARGET_NAME, Don't defined PICO_TARGET_NAME, type=bool, default=0, group=build + # PICO_CMAKE_CONFIG: PICO_NO_TARGET_NAME, Don't define PICO_TARGET_NAME, type=bool, default=0, group=build # PICO_BUILD_DEFINE: PICO_TARGET_NAME, The name of the build target being compiled (unless PICO_NO_TARGET_NAME set in build), type=string, default=target name, group=build if (NOT PICO_NO_TARGET_NAME) target_compile_definitions(${TARGET} PRIVATE @@ -49,11 +49,11 @@ endfunction() add_subdirectory(common) add_subdirectory(rp2_common) -# PICO_CMAKE_CONFIG: PICO_NO_HARDWARE, OPTION: Whether the build is not targeting an RP2040 device, type=bool, default=1 for PICO_PLATFORM=host 0 otherwise, group=build -# PICO_BUILD_DEFINE: PICO_NO_HARDWARE, Whether the build is not targeting an RP2040 device, type=bool, default=1 for PICO_PLATFORM=host 0 otherwise, group=build +# PICO_CMAKE_CONFIG: PICO_NO_HARDWARE, OPTION: Whether the build is not targeting an RP2040 device, type=bool, default=1 when PICO_PLATFORM is host, 0 otherwise, group=build +# PICO_BUILD_DEFINE: PICO_NO_HARDWARE, Whether the build is not targeting an RP2040 device, type=bool, default=1 when PICO_PLATFORM is host, 0 otherwise, group=build set(PICO_NO_HARDWARE "0" CACHE INTERNAL "") -# PICO_CMAKE_CONFIG: PICO_ON_DEVICE, OPTION: Whether the build is targeting an RP2040 device, type=bool, default=0 for PICO_PLATFORM=host 1 otherwise, group=build -# PICO_BUILD_DEFIN: PICO_ON_DEVICE, Whether the build is targeting an RP2040 device, type=bool, default=0 for PICO_PLATFORM=host 1 otherwise, group=build +# PICO_CMAKE_CONFIG: PICO_ON_DEVICE, OPTION: Whether the build is targeting an RP2040 device, type=bool, default=0 when PICO_PLATFORM is host, 1 otherwise, group=build +# PICO_BUILD_DEFINE: PICO_ON_DEVICE, Whether the build is targeting an RP2040 device, type=bool, default=0 when PICO_PLATFORM is host, 1 otherwise, group=build set(PICO_ON_DEVICE "1" CACHE INTERNAL "") set(CMAKE_EXECUTABLE_SUFFIX .elf PARENT_SCOPE) diff --git a/src/rp2_common/CMakeLists.txt b/src/rp2_common/CMakeLists.txt index 4ca55becb..0d0f9b93d 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 000000000..8951df400 --- /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 000000000..12b633a7e --- /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, bus_data->pio_sm, 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, bus_data->pio_sm, 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, bus_data->pio_sm, 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, bus_data->pio_sm, 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 000000000..ea0d19549 --- /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_clocks/clocks.c b/src/rp2_common/hardware_clocks/clocks.c index f51331aac..9ba51f03d 100644 --- a/src/rp2_common/hardware_clocks/clocks.c +++ b/src/rp2_common/hardware_clocks/clocks.c @@ -148,13 +148,13 @@ void clocks_init(void) { /// \tag::pll_settings[] // Configure PLLs // REF FBDIV VCO POSTDIV - // PLL SYS: 12 / 1 = 12MHz * 125 = 1500MHZ / 6 / 2 = 125MHz - // PLL USB: 12 / 1 = 12MHz * 40 = 480 MHz / 5 / 2 = 48MHz + // PLL SYS: 12 / 1 = 12MHz * 125 = 1500MHz / 6 / 2 = 125MHz + // PLL USB: 12 / 1 = 12MHz * 100 = 1200MHz / 5 / 5 = 48MHz /// \end::pll_settings[] /// \tag::pll_init[] pll_init(pll_sys, 1, 1500 * MHZ, 6, 2); - pll_init(pll_usb, 1, 480 * MHZ, 5, 2); + pll_init(pll_usb, 1, 1200 * MHZ, 5, 5); /// \end::pll_init[] // Configure clocks diff --git a/src/rp2_common/hardware_clocks/scripts/vcocalc.py b/src/rp2_common/hardware_clocks/scripts/vcocalc.py index 4d901465d..5d143b384 100755 --- a/src/rp2_common/hardware_clocks/scripts/vcocalc.py +++ b/src/rp2_common/hardware_clocks/scripts/vcocalc.py @@ -5,7 +5,7 @@ parser = argparse.ArgumentParser(description="PLL parameter calculator") parser.add_argument("--input", "-i", default=12, help="Input (reference) frequency. Default 12 MHz", type=float) parser.add_argument("--vco-max", default=1600, help="Override maximum VCO frequency. Default 1600 MHz", type=float) -parser.add_argument("--vco-min", default=400, help="Override minimum VCO frequency. Default 400 MHz", type=float) +parser.add_argument("--vco-min", default=750, help="Override minimum VCO frequency. Default 750 MHz", type=float) parser.add_argument("--low-vco", "-l", action="store_true", help="Use a lower VCO frequency when possible. This reduces power consumption, at the cost of increased jitter") parser.add_argument("output", help="Output frequency in MHz.", type=float) args = parser.parse_args() diff --git a/src/rp2_common/hardware_dma/dma.c b/src/rp2_common/hardware_dma/dma.c index 90fde06b4..f142b5379 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 f823caaec..b1e956fc3 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 * @@ -118,7 +123,6 @@ bool dma_channel_is_claimed(uint channel); * * A DMA channel needs to be configured, these functions provide handy helpers to set up configuration * structures. See \ref dma_channel_config - * */ /*! \brief Enumeration of available DMA channel transfer sizes. @@ -136,10 +140,10 @@ typedef struct { uint32_t ctrl; } dma_channel_config; -/*! \brief Set DMA channel read increment +/*! \brief Set DMA channel read increment in a channel configuration object * \ingroup channel_config * - * \param c Pointer to channel configuration data + * \param c Pointer to channel configuration object * \param incr True to enable read address increments, if false, each read will be from the same address * Usually disabled for peripheral to memory transfers */ @@ -147,10 +151,10 @@ static inline void channel_config_set_read_increment(dma_channel_config *c, bool c->ctrl = incr ? (c->ctrl | DMA_CH0_CTRL_TRIG_INCR_READ_BITS) : (c->ctrl & ~DMA_CH0_CTRL_TRIG_INCR_READ_BITS); } -/*! \brief Set DMA channel write increment +/*! \brief Set DMA channel write increment in a channel configuration object * \ingroup channel_config * - * \param c Pointer to channel configuration data + * \param c Pointer to channel configuration object * \param incr True to enable write address increments, if false, each write will be to the same address * Usually disabled for memory to peripheral transfers * Usually disabled for memory to peripheral transfers @@ -159,7 +163,7 @@ static inline void channel_config_set_write_increment(dma_channel_config *c, boo c->ctrl = incr ? (c->ctrl | DMA_CH0_CTRL_TRIG_INCR_WRITE_BITS) : (c->ctrl & ~DMA_CH0_CTRL_TRIG_INCR_WRITE_BITS); } -/*! \brief Select a transfer request signal +/*! \brief Select a transfer request signal in a channel configuration object * \ingroup channel_config * * The channel uses the transfer request signal to pace its data transfer rate. @@ -179,13 +183,13 @@ static inline void channel_config_set_dreq(dma_channel_config *c, uint dreq) { c->ctrl = (c->ctrl & ~DMA_CH0_CTRL_TRIG_TREQ_SEL_BITS) | (dreq << DMA_CH0_CTRL_TRIG_TREQ_SEL_LSB); } -/*! \brief Set DMA channel completion channel +/*! \brief Set DMA channel chain_to channel in a channel configuration object * \ingroup channel_config * * When this channel completes, it will trigger the channel indicated by chain_to. Disable by * setting chain_to to itself (the same channel) * - * \param c Pointer to channel configuration data + * \param c Pointer to channel configuration object * \param chain_to Channel to trigger when this channel completes. */ static inline void channel_config_set_chain_to(dma_channel_config *c, uint chain_to) { @@ -193,13 +197,13 @@ static inline void channel_config_set_chain_to(dma_channel_config *c, uint chain c->ctrl = (c->ctrl & ~DMA_CH0_CTRL_TRIG_CHAIN_TO_BITS) | (chain_to << DMA_CH0_CTRL_TRIG_CHAIN_TO_LSB); } -/*! \brief Set the size of each DMA bus transfer +/*! \brief Set the size of each DMA bus transfer in a channel configuration object * \ingroup channel_config * * Set the size of each bus transfer (byte/halfword/word). The read and write addresses * advance by the specific amount (1/2/4 bytes) with each transfer. * - * \param c Pointer to channel configuration data + * \param c Pointer to channel configuration object * \param size See enum for possible values. */ static inline void channel_config_set_transfer_data_size(dma_channel_config *c, enum dma_channel_transfer_size size) { @@ -207,7 +211,7 @@ static inline void channel_config_set_transfer_data_size(dma_channel_config *c, c->ctrl = (c->ctrl & ~DMA_CH0_CTRL_TRIG_DATA_SIZE_BITS) | (((uint)size) << DMA_CH0_CTRL_TRIG_DATA_SIZE_LSB); } -/*! \brief Set address wrapping parameters +/*! \brief Set address wrapping parameters in a channel configuration object * \ingroup channel_config * * Size of address wrap region. If 0, don’t wrap. For values n > 0, only the lower n bits of the address @@ -217,7 +221,7 @@ static inline void channel_config_set_transfer_data_size(dma_channel_config *c, * * 0x0 -> No wrapping. * - * \param c Pointer to channel configuration data + * \param c Pointer to channel configuration object * \param write True to apply to write addresses, false to apply to read addresses * \param size_bits 0 to disable wrapping. Otherwise the size in bits of the changing part of the address. * Effectively wraps the address on a (1 << size_bits) byte boundary. @@ -229,27 +233,27 @@ static inline void channel_config_set_ring(dma_channel_config *c, bool write, ui (write ? DMA_CH0_CTRL_TRIG_RING_SEL_BITS : 0); } -/*! \brief Set DMA byte swapping +/*! \brief Set DMA byte swapping config in a channel configuration object * \ingroup channel_config * * No effect for byte data, for halfword data, the two bytes of each halfword are * swapped. For word data, the four bytes of each word are swapped to reverse their order. * - * \param c Pointer to channel configuration data + * \param c Pointer to channel configuration object * \param bswap True to enable byte swapping */ static inline void channel_config_set_bswap(dma_channel_config *c, bool bswap) { c->ctrl = bswap ? (c->ctrl | DMA_CH0_CTRL_TRIG_BSWAP_BITS) : (c->ctrl & ~DMA_CH0_CTRL_TRIG_BSWAP_BITS); } -/*! \brief Set IRQ quiet mode +/*! \brief Set IRQ quiet mode in a channel configuration object * \ingroup channel_config * * In QUIET mode, the channel does not generate IRQs at the end of every transfer block. Instead, * an IRQ is raised when NULL is written to a trigger register, indicating the end of a control * block chain. * - * \param c Pointer to channel configuration data + * \param c Pointer to channel configuration object * \param irq_quiet True to enable quiet mode, false to disable. */ static inline void channel_config_set_irq_quiet(dma_channel_config *c, bool irq_quiet) { @@ -257,13 +261,31 @@ static inline void channel_config_set_irq_quiet(dma_channel_config *c, bool irq_ } /*! - * \brief Enable/Disable the DMA channel + * \brief Set the channel priority in a channel configuration object + * \ingroup channel_config + * + * When true, gives a channel preferential treatment in issue scheduling: in each scheduling round, + * all high priority channels are considered first, and then only a single low + * priority channel, before returning to the high priority channels. + * + * This only affects the order in which the DMA schedules channels. The DMA's bus priority is not changed. + * If the DMA is not saturated then a low priority channel will see no loss of throughput. + * + * \param c Pointer to channel configuration object + * \param high_priority True to enable high priority + */ +static inline void channel_config_set_high_priority(dma_channel_config *c, bool high_priority) { + c->ctrl = high_priority ? (c->ctrl | DMA_CH0_CTRL_TRIG_HIGH_PRIORITY_BITS) : (c->ctrl & ~DMA_CH0_CTRL_TRIG_HIGH_PRIORITY_BITS); +} + +/*! + * \brief Enable/Disable the DMA channel in a channel configuration object * \ingroup channel_config * * When false, the channel will ignore triggers, stop issuing transfers, and pause the current transfer sequence (i.e. BUSY will * remain high if already high) * - * \param c Pointer to channel configuration data + * \param c Pointer to channel configuration object * \param enable True to enable the DMA channel. When enabled, the channel will respond to triggering events, and start transferring data. * */ @@ -271,12 +293,12 @@ static inline void channel_config_set_enable(dma_channel_config *c, bool enable) c->ctrl = enable ? (c->ctrl | DMA_CH0_CTRL_TRIG_EN_BITS) : (c->ctrl & ~DMA_CH0_CTRL_TRIG_EN_BITS); } -/*! \brief Enable access to channel by sniff hardware. +/*! \brief Enable access to channel by sniff hardware in a channel configuration object * \ingroup channel_config * * Sniff HW must be enabled and have this channel selected. * - * \param c Pointer to channel configuration data + * \param c Pointer to channel configuration object * \param sniff_enable True to enable the Sniff HW access to this DMA channel. */ static inline void channel_config_set_sniff_enable(dma_channel_config *c, bool sniff_enable) { @@ -297,6 +319,7 @@ static inline void channel_config_set_sniff_enable(dma_channel_config *c, bool s * Ring | write=false, size=0 (i.e. off) * Byte Swap | false * Quiet IRQs | false + * High Priority | false * Channel Enable | true * Sniff Enable | false * @@ -315,6 +338,7 @@ static inline dma_channel_config dma_channel_get_default_config(uint channel) { channel_config_set_irq_quiet(&c, false); channel_config_set_enable(&c, true); channel_config_set_sniff_enable(&c, false); + channel_config_set_high_priority( &c, false); return c; } @@ -641,7 +665,7 @@ static inline bool dma_irqn_get_channel_status(uint irq_index, uint channel) { */ static inline void dma_channel_acknowledge_irq0(uint channel) { check_dma_channel_param(channel); - hw_set_bits(&dma_hw->ints0, (1u << channel)); + dma_hw->ints0 = 1u << channel; } /*! \brief Acknowledge a channel IRQ, resetting it as the cause of DMA_IRQ_1 @@ -651,7 +675,7 @@ static inline void dma_channel_acknowledge_irq0(uint channel) { */ static inline void dma_channel_acknowledge_irq1(uint channel) { check_dma_channel_param(channel); - hw_set_bits(&dma_hw->ints1, (1u << channel)); + dma_hw->ints1 = 1u << channel; } /*! \brief Acknowledge a channel IRQ, resetting it as the cause of DMA_IRQ_N @@ -663,7 +687,10 @@ static inline void dma_channel_acknowledge_irq1(uint channel) { static inline void dma_irqn_acknowledge_channel(uint irq_index, uint channel) { invalid_params_if(DMA, irq_index > 1); check_dma_channel_param(channel); - hw_set_bits(irq_index ? &dma_hw->ints1 : &dma_hw->ints0, (1u << channel)); + if (irq_index) + dma_hw->ints1 = 1u << channel; + else + dma_hw->ints0 = 1u << channel; } /*! \brief Check if DMA channel is busy diff --git a/src/rp2_common/hardware_gpio/CMakeLists.txt b/src/rp2_common/hardware_gpio/CMakeLists.txt index 1bfb078f3..97a935575 100644 --- a/src/rp2_common/hardware_gpio/CMakeLists.txt +++ b/src/rp2_common/hardware_gpio/CMakeLists.txt @@ -1 +1,2 @@ -pico_simple_hardware_target(gpio) \ No newline at end of file +pico_simple_hardware_target(gpio) +target_link_libraries(hardware_gpio INTERFACE hardware_irq) \ No newline at end of file diff --git a/src/rp2_common/hardware_gpio/gpio.c b/src/rp2_common/hardware_gpio/gpio.c index 4197a4b92..6e4261fcb 100644 --- a/src/rp2_common/hardware_gpio/gpio.c +++ b/src/rp2_common/hardware_gpio/gpio.c @@ -14,11 +14,13 @@ #include "pico/binary_info.h" #endif -static gpio_irq_callback_t _callbacks[NUM_CORES]; +static gpio_irq_callback_t callbacks[NUM_CORES]; +// a 1 bit means the IRQ is handled by a raw IRQ handler +static uint32_t raw_irq_mask[NUM_CORES]; // Get the raw value from the pin, bypassing any muxing or overrides. int gpio_get_pad(uint gpio) { - invalid_params_if(GPIO, gpio >= NUM_BANK0_GPIOS); + check_gpio_param(gpio); hw_set_bits(&padsbank0_hw->io[gpio], PADS_BANK0_GPIO0_IE_BITS); return (iobank0_hw->io[gpio].status & IO_BANK0_GPIO0_STATUS_INFROMPAD_BITS) >> IO_BANK0_GPIO0_STATUS_INFROMPAD_LSB; @@ -28,7 +30,7 @@ int gpio_get_pad(uint gpio) { // Select function for this GPIO, and ensure input/output are enabled at the pad. // This also clears the input/output/irq override bits. void gpio_set_function(uint gpio, enum gpio_function fn) { - invalid_params_if(GPIO, gpio >= NUM_BANK0_GPIOS); + check_gpio_param(gpio); invalid_params_if(GPIO, ((uint32_t)fn << IO_BANK0_GPIO0_CTRL_FUNCSEL_LSB) & ~IO_BANK0_GPIO0_CTRL_FUNCSEL_BITS); // Set input enable on, output disable off hw_write_masked(&padsbank0_hw->io[gpio], @@ -42,14 +44,14 @@ void gpio_set_function(uint gpio, enum gpio_function fn) { /// \end::gpio_set_function[] enum gpio_function gpio_get_function(uint gpio) { - invalid_params_if(GPIO, gpio >= NUM_BANK0_GPIOS); + check_gpio_param(gpio); return (enum gpio_function) ((iobank0_hw->io[gpio].ctrl & IO_BANK0_GPIO0_CTRL_FUNCSEL_BITS) >> IO_BANK0_GPIO0_CTRL_FUNCSEL_LSB); } // Note that, on RP2040, setting both pulls enables a "bus keep" function, // i.e. weak pull to whatever is current high/low state of GPIO. void gpio_set_pulls(uint gpio, bool up, bool down) { - invalid_params_if(GPIO, gpio >= NUM_BANK0_GPIOS); + check_gpio_param(gpio); hw_write_masked( &padsbank0_hw->io[gpio], (bool_to_bit(up) << PADS_BANK0_GPIO0_PUE_LSB) | (bool_to_bit(down) << PADS_BANK0_GPIO0_PDE_LSB), @@ -59,7 +61,7 @@ void gpio_set_pulls(uint gpio, bool up, bool down) { // Direct override for per-GPIO IRQ signal void gpio_set_irqover(uint gpio, uint value) { - invalid_params_if(GPIO, gpio >= NUM_BANK0_GPIOS); + check_gpio_param(gpio); hw_write_masked(&iobank0_hw->io[gpio].ctrl, value << IO_BANK0_GPIO0_CTRL_IRQOVER_LSB, IO_BANK0_GPIO0_CTRL_IRQOVER_BITS @@ -68,7 +70,7 @@ void gpio_set_irqover(uint gpio, uint value) { // Direct overrides for pad controls void gpio_set_inover(uint gpio, uint value) { - invalid_params_if(GPIO, gpio >= NUM_BANK0_GPIOS); + check_gpio_param(gpio); hw_write_masked(&iobank0_hw->io[gpio].ctrl, value << IO_BANK0_GPIO0_CTRL_INOVER_LSB, IO_BANK0_GPIO0_CTRL_INOVER_BITS @@ -76,7 +78,7 @@ void gpio_set_inover(uint gpio, uint value) { } void gpio_set_outover(uint gpio, uint value) { - invalid_params_if(GPIO, gpio >= NUM_BANK0_GPIOS); + check_gpio_param(gpio); hw_write_masked(&iobank0_hw->io[gpio].ctrl, value << IO_BANK0_GPIO0_CTRL_OUTOVER_LSB, IO_BANK0_GPIO0_CTRL_OUTOVER_BITS @@ -84,7 +86,7 @@ void gpio_set_outover(uint gpio, uint value) { } void gpio_set_oeover(uint gpio, uint value) { - invalid_params_if(GPIO, gpio >= NUM_BANK0_GPIOS); + check_gpio_param(gpio); hw_write_masked(&iobank0_hw->io[gpio].ctrl, value << IO_BANK0_GPIO0_CTRL_OEOVER_LSB, IO_BANK0_GPIO0_CTRL_OEOVER_BITS @@ -92,7 +94,7 @@ void gpio_set_oeover(uint gpio, uint value) { } void gpio_set_input_hysteresis_enabled(uint gpio, bool enabled) { - invalid_params_if(GPIO, gpio >= NUM_BANK0_GPIOS); + check_gpio_param(gpio); if (enabled) hw_set_bits(&padsbank0_hw->io[gpio], PADS_BANK0_GPIO0_SCHMITT_BITS); else @@ -101,12 +103,12 @@ void gpio_set_input_hysteresis_enabled(uint gpio, bool enabled) { bool gpio_is_input_hysteresis_enabled(uint gpio) { - invalid_params_if(GPIO, gpio >= NUM_BANK0_GPIOS); + check_gpio_param(gpio); return (padsbank0_hw->io[gpio] & PADS_BANK0_GPIO0_SCHMITT_BITS) != 0; } void gpio_set_slew_rate(uint gpio, enum gpio_slew_rate slew) { - invalid_params_if(GPIO, gpio >= NUM_BANK0_GPIOS); + check_gpio_param(gpio); hw_write_masked(&padsbank0_hw->io[gpio], (uint)slew << PADS_BANK0_GPIO0_SLEWFAST_LSB, PADS_BANK0_GPIO0_SLEWFAST_BITS @@ -114,7 +116,7 @@ void gpio_set_slew_rate(uint gpio, enum gpio_slew_rate slew) { } enum gpio_slew_rate gpio_get_slew_rate(uint gpio) { - invalid_params_if(GPIO, gpio >= NUM_BANK0_GPIOS); + check_gpio_param(gpio); return (enum gpio_slew_rate)((padsbank0_hw->io[gpio] & PADS_BANK0_GPIO0_SLEWFAST_BITS) >> PADS_BANK0_GPIO0_SLEWFAST_LSB); @@ -124,7 +126,7 @@ enum gpio_slew_rate gpio_get_slew_rate(uint gpio) { // Enum encoding should match hardware encoding on RP2040 static_assert(PADS_BANK0_GPIO0_DRIVE_VALUE_8MA == GPIO_DRIVE_STRENGTH_8MA, ""); void gpio_set_drive_strength(uint gpio, enum gpio_drive_strength drive) { - invalid_params_if(GPIO, gpio >= NUM_BANK0_GPIOS); + check_gpio_param(gpio); hw_write_masked(&padsbank0_hw->io[gpio], (uint)drive << PADS_BANK0_GPIO0_DRIVE_LSB, PADS_BANK0_GPIO0_DRIVE_BITS @@ -132,25 +134,28 @@ void gpio_set_drive_strength(uint gpio, enum gpio_drive_strength drive) { } enum gpio_drive_strength gpio_get_drive_strength(uint gpio) { - invalid_params_if(GPIO, gpio >= NUM_BANK0_GPIOS); + check_gpio_param(gpio); return (enum gpio_drive_strength)((padsbank0_hw->io[gpio] & PADS_BANK0_GPIO0_DRIVE_BITS) >> PADS_BANK0_GPIO0_DRIVE_LSB); } -static void gpio_irq_handler(void) { - io_irq_ctrl_hw_t *irq_ctrl_base = get_core_num() ? - &iobank0_hw->proc1_irq_ctrl : &iobank0_hw->proc0_irq_ctrl; - for (uint gpio = 0; gpio < NUM_BANK0_GPIOS; gpio++) { - io_ro_32 *status_reg = &irq_ctrl_base->ints[gpio / 8]; - uint events = (*status_reg >> 4 * (gpio % 8)) & 0xf; - if (events) { - // TODO: If both cores care about this event then the second core won't get the irq? - gpio_acknowledge_irq(gpio, events); - gpio_irq_callback_t callback = _callbacks[get_core_num()]; - if (callback) { - callback(gpio, events); +static void gpio_default_irq_handler(void) { + uint core = get_core_num(); + gpio_irq_callback_t callback = callbacks[core]; + io_irq_ctrl_hw_t *irq_ctrl_base = core ? &iobank0_hw->proc1_irq_ctrl : &iobank0_hw->proc0_irq_ctrl; + for (uint gpio = 0; gpio < NUM_BANK0_GPIOS; gpio+=8) { + uint32_t events8 = irq_ctrl_base->ints[gpio >> 3u]; + // note we assume events8 is 0 for non-existent GPIO + for(uint i=gpio;events8 && i>= 4; } } } @@ -178,21 +183,48 @@ void gpio_set_irq_enabled(uint gpio, uint32_t events, bool enabled) { void gpio_set_irq_enabled_with_callback(uint gpio, uint32_t events, bool enabled, gpio_irq_callback_t callback) { gpio_set_irq_enabled(gpio, events, enabled); + gpio_set_irq_callback(callback); + if (enabled) irq_set_enabled(IO_IRQ_BANK0, true); +} + +void gpio_set_irq_callback(gpio_irq_callback_t callback) { + uint core = get_core_num(); + if (callbacks[core]) { + if (!callback) { + irq_remove_handler(IO_IRQ_BANK0, gpio_default_irq_handler); + } + callbacks[core] = callback; + } else if (callback) { + callbacks[core] = callback; + irq_add_shared_handler(IO_IRQ_BANK0, gpio_default_irq_handler, GPIO_IRQ_CALLBACK_ORDER_PRIORITY); + } +} + +void gpio_add_raw_irq_handler_with_order_priority_masked(uint gpio_mask, irq_handler_t handler, uint8_t order_priority) { + hard_assert(!(raw_irq_mask[get_core_num()] & gpio_mask)); // should not add multiple handlers for the same event + raw_irq_mask[get_core_num()] |= gpio_mask; + irq_add_shared_handler(IO_IRQ_BANK0, handler, order_priority); +} + +void gpio_add_raw_irq_handler_masked(uint gpio_mask, irq_handler_t handler) { + gpio_add_raw_irq_handler_with_order_priority_masked(gpio_mask, handler, GPIO_RAW_IRQ_HANDLER_DEFAULT_ORDER_PRIORITY); +} - // TODO: Do we want to support a callback per GPIO pin? - // Install IRQ handler - _callbacks[get_core_num()] = callback; - irq_set_exclusive_handler(IO_IRQ_BANK0, gpio_irq_handler); - irq_set_enabled(IO_IRQ_BANK0, true); +void gpio_remove_raw_irq_handler_masked(uint gpio_mask, irq_handler_t handler) { + assert(raw_irq_mask[get_core_num()] & gpio_mask); // should not remove handlers that are not added + irq_remove_handler(IO_IRQ_BANK0, handler); + raw_irq_mask[get_core_num()] &= ~gpio_mask; } void gpio_set_dormant_irq_enabled(uint gpio, uint32_t events, bool enabled) { + check_gpio_param(gpio); io_irq_ctrl_hw_t *irq_ctrl_base = &iobank0_hw->dormant_wake_irq_ctrl; _gpio_set_irq_enabled(gpio, events, enabled, irq_ctrl_base); } void gpio_acknowledge_irq(uint gpio, uint32_t events) { - iobank0_hw->intr[gpio / 8] = events << 4 * (gpio % 8); + check_gpio_param(gpio); + iobank0_hw->intr[gpio / 8] = events << (4 * (gpio % 8)); } #define DEBUG_PIN_MASK (((1u << PICO_DEBUG_PIN_COUNT)-1) << PICO_DEBUG_PIN_BASE) @@ -222,7 +254,7 @@ void gpio_deinit(uint gpio) { } void gpio_init_mask(uint gpio_mask) { - for(uint i=0;i<32;i++) { + for(uint i=0;i= NUM_BANK0_GPIOS); +} + // ---------------------------------------------------------------------------- // Pad Controls + IO Muxing // ---------------------------------------------------------------------------- @@ -347,63 +353,288 @@ void gpio_set_drive_strength(uint gpio, enum gpio_drive_strength drive); */ enum gpio_drive_strength gpio_get_drive_strength(uint gpio); -/*! \brief Enable or disable interrupts for specified GPIO +/*! \brief Enable or disable specific interrupt events for specified GPIO * \ingroup hardware_gpio * - * \note The IO IRQs are independent per-processor. This configures IRQs for + * This function sets which GPIO events cause a GPIO interrupt on the calling core. See + * \ref gpio_set_irq_callback, \ref gpio_set_irq_enabled_with_callback and + * \ref gpio_add_raw_irq_handler to set up a GPIO interrupt handler to handle the events. + * + * \note The IO IRQs are independent per-processor. This configures the interrupt events for * the processor that calls the function. * * \param gpio GPIO number - * \param events Which events will cause an interrupt + * \param event_mask Which events will cause an interrupt * \param enabled Enable or disable flag * - * Events is a bitmask of the following: + * Events is a bitmask of the following \ref gpio_irq_level values: * - * bit | interrupt - * ----|---------- - * 0 | Low level - * 1 | High level - * 2 | Edge low - * 3 | Edge high + * bit | constant | interrupt + * ----|---------------------------------------------------------- + * 0 | GPIO_IRQ_LEVEL_LOW | Continuously while level is low + * 1 | GPIO_IRQ_LEVEL_HIGH | Continuously while level is high + * 2 | GPIO_IRQ_EDGE_FALL | On each transition from high to low + * 3 | GPIO_IRQ_EDGE_RISE | On each transition from low to high + * + * which are specified in \ref gpio_irq_level */ -void gpio_set_irq_enabled(uint gpio, uint32_t events, bool enabled); +void gpio_set_irq_enabled(uint gpio, uint32_t event_mask, bool enabled); + +// PICO_CONFIG: GPIO_IRQ_CALLBACK_ORDER_PRIORITY, the irq priority order of the default IRQ callback, min=0, max=255, default=PICO_SHARED_IRQ_HANDLER_LOWEST_ORDER_PRIORITY, group=hardware_gpio +#ifndef GPIO_IRQ_CALLBACK_ORDER_PRIORITY +#define GPIO_IRQ_CALLBACK_ORDER_PRIORITY PICO_SHARED_IRQ_HANDLER_LOWEST_ORDER_PRIORITY +#endif -/*! \brief Enable interrupts for specified GPIO +// PICO_CONFIG: GPIO_RAW_IRQ_HANDLER_DEFAULT_ORDER_PRIORITY, the irq priority order of raw IRQ handlers if the priortiy is not specified, min=0, max=255, default=PICO_SHARED_IRQ_HANDLER_DEFAULT_ORDER_PRIORITY, group=hardware_gpio +#ifndef GPIO_RAW_IRQ_HANDLER_DEFAULT_ORDER_PRIORITY +#define GPIO_RAW_IRQ_HANDLER_DEFAULT_ORDER_PRIORITY PICO_SHARED_IRQ_HANDLER_DEFAULT_ORDER_PRIORITY +#endif + +/*! \brief Set the generic callback used for GPIO IRQ events for the current core * \ingroup hardware_gpio * - * \note The IO IRQs are independent per-processor. This configures IRQs for + * This function sets the callback used for all GPIO IRQs on the current core that are not explicitly + * hooked via \ref gpio_add_raw_irq_handler or other gpio_add_raw_irq_handler_ functions. + * + * This function is called with the GPIO number and event mask for each of the (not explicitly hooked) + * GPIOs that have events enabled and that are pending (see \ref gpio_get_irq_event_mask). + * + * \note The IO IRQs are independent per-processor. This function affects * the processor that calls the function. * - * \param gpio GPIO number - * \param events Which events will cause an interrupt. See \ref gpio_set_irq_enabled for details. - * \param enabled Enable or disable flag - * \param callback user function to call on GPIO irq. Note only one of these can be set per processor. + * \param callback default user function to call on GPIO irq. Note only one of these can be set per processor. + */ +void gpio_set_irq_callback(gpio_irq_callback_t callback); + +/*! \brief Convenience function which performs multiple GPIO IRQ related initializations + * \ingroup hardware_gpio + * + * This method is a slightly eclectic mix of initialization, that: + * + * \li Updates whether the specified events for the specified GPIO causes an interrupt on the calling core based + * on the enable flag. + * + * \li Sets the callback handler for the calling core to callback (or clears the handler if the callback is NULL). + * + * \li Enables GPIO IRQs on the current core if enabled is true. + * + * This method is commonly used to perform a one time setup, and following that any additional IRQs/events are enabled + * via \ref gpio_set_irq_enabled. All GPIOs/events added in this way on the same core share the same callback; for multiple + * independent handlers for different GPIOs you should use \ref gpio_add_raw_irq_handler and related functions. + * + * This method is equivalent to: * - * \note Currently the GPIO parameter is ignored, and this callback will be called for any enabled GPIO IRQ on any pin. + * \code{.c} + * gpio_set_irq_enabled(gpio, event_mask, enabled); + * gpio_set_irq_callback(callback); + * if (enabled) irq_set_enabled(IO_IRQ_BANK0, true); + * \endcode * + * \note The IO IRQs are independent per-processor. This method affects only the processor that calls the function. + * + * \param gpio GPIO number + * \param event_mask Which events will cause an interrupt. See \ref gpio_irq_level for details. + * \param enabled Enable or disable flag + * \param callback user function to call on GPIO irq. if NULL, the callback is removed */ -void gpio_set_irq_enabled_with_callback(uint gpio, uint32_t events, bool enabled, gpio_irq_callback_t callback); +void gpio_set_irq_enabled_with_callback(uint gpio, uint32_t event_mask, bool enabled, gpio_irq_callback_t callback); -/*! \brief Enable dormant wake up interrupt for specified GPIO +/*! \brief Enable dormant wake up interrupt for specified GPIO and events * \ingroup hardware_gpio * * This configures IRQs to restart the XOSC or ROSC when they are * disabled in dormant mode * * \param gpio GPIO number - * \param events Which events will cause an interrupt. See \ref gpio_set_irq_enabled for details. + * \param event_mask Which events will cause an interrupt. See \ref gpio_irq_level for details. * \param enabled Enable/disable flag */ -void gpio_set_dormant_irq_enabled(uint gpio, uint32_t events, bool enabled); +void gpio_set_dormant_irq_enabled(uint gpio, uint32_t event_mask, bool enabled); + +/*! \brief Return the current interrupt status (pending events) for the given GPIO + * \ingroup hardware_gpio + * + * \param gpio GPIO number + * \return Bitmask of events that are currently pending for the GPIO. See \ref gpio_irq_level for details. + * \sa gpio_acknowledge_irq + */ +static inline uint32_t gpio_get_irq_event_mask(uint gpio) { + check_gpio_param(gpio); + io_irq_ctrl_hw_t *irq_ctrl_base = get_core_num() ? + &iobank0_hw->proc1_irq_ctrl : &iobank0_hw->proc0_irq_ctrl; + io_ro_32 *status_reg = &irq_ctrl_base->ints[gpio >> 3u]; + return (*status_reg >> (4 * (gpio & 7u))) & 0xfu; +} -/*! \brief Acknowledge a GPIO interrupt +/*! \brief Acknowledge a GPIO interrupt for the specified events on the calling core * \ingroup hardware_gpio * + * \note This may be called with a mask of any of valid bits specified in \ref gpio_irq_level, however + * it has no effect on \a level sensitive interrupts which remain pending while the GPIO is at the specified + * level. When handling \a level sensitive interrupts, you should generally disable the interrupt (see + * \ref gpio_set_irq_enabled) and then set it up again later once the GPIO level has changed (or to catch + * the opposite level). + * * \param gpio GPIO number * \param events Bitmask of events to clear. See \ref gpio_set_irq_enabled for details. - * + * + * \note For callbacks set with \ref gpio_set_irq_enabled_with_callback, or \ref gpio_set_irq_callback,this function is called automatically. + * \param event_mask Bitmask of events to clear. See \ref gpio_irq_level for details. + */ +void gpio_acknowledge_irq(uint gpio, uint32_t event_mask); + +/*! \brief Adds a raw GPIO IRQ handler for the specified GPIOs on the current core + * \ingroup hardware_gpio + * + * In addition to the default mechanism of a single GPIO IRQ event callback per core (see \ref gpio_set_irq_callback), + * it is possible to add explicit GPIO IRQ handlers which are called independent of the default callback. The order + * relative to the default callback can be controlled via the order_priority parameter (the default callback has the priority + * \ref GPIO_IRQ_CALLBACK_ORDER_PRIORITY which defaults to the lowest priority with the intention of it running last). + * + * This method adds such an explicit GPIO IRQ handler, and disables the "default" callback for the specified GPIOs. + * + * \note Multiple raw handlers should not be added for the same GPIOs, and this method will assert if you attempt to. + * + * A raw handler should check for whichever GPIOs and events it handles, and acknowledge them itself; it might look something like: + * + * \code{.c} + * void my_irq_handler(void) { + * if (gpio_get_irq_event_mask(my_gpio_num) & my_gpio_event_mask) { + * gpio_acknowledge_irq(my_gpio_num, my_gpio_event_mask); + * // handle the IRQ + * } + * if (gpio_get_irq_event_mask(my_gpio_num2) & my_gpio_event_mask2) { + * gpio_acknowledge_irq(my_gpio_num2, my_gpio_event_mask2); + * // handle the IRQ + * } + * } + * \endcode + * + * @param gpio_mask a bit mask of the GPIO numbers that will no longer be passed to the default callback for this core + * @param handler the handler to add to the list of GPIO IRQ handlers for this core + * @param order_priority the priority order to determine the relative position of the handler in the list of GPIO IRQ handlers for this core. + */ +void gpio_add_raw_irq_handler_with_order_priority_masked(uint gpio_mask, irq_handler_t handler, uint8_t order_priority); + +/*! \brief Adds a raw GPIO IRQ handler for a specific GPIO on the current core + * \ingroup hardware_gpio + * + * In addition to the default mechanism of a single GPIO IRQ event callback per core (see \ref gpio_set_irq_callback), + * it is possible to add explicit GPIO IRQ handlers which are called independent of the default callback. The order + * relative to the default callback can be controlled via the order_priority parameter(the default callback has the priority + * \ref GPIO_IRQ_CALLBACK_ORDER_PRIORITY which defaults to the lowest priority with the intention of it running last). + * + * This method adds such a callback, and disables the "default" callback for the specified GPIO. + * + * \note Multiple raw handlers should not be added for the same GPIO, and this method will assert if you attempt to. + * + * A raw handler should check for whichever GPIOs and events it handles, and acknowledge them itself; it might look something like: + * + * \code{.c} + * void my_irq_handler(void) { + * if (gpio_get_irq_event_mask(my_gpio_num) & my_gpio_event_mask) { + * gpio_acknowledge_irq(my_gpio_num, my_gpio_event_mask); + * // handle the IRQ + * } + * } + * \endcode + * + * @param gpio the GPIO number that will no longer be passed to the default callback for this core + * @param handler the handler to add to the list of GPIO IRQ handlers for this core + * @param order_priority the priority order to determine the relative position of the handler in the list of GPIO IRQ handlers for this core. */ -void gpio_acknowledge_irq(uint gpio, uint32_t events); +static inline void gpio_add_raw_irq_handler_with_order_priority(uint gpio, irq_handler_t handler, uint8_t order_priority) { + check_gpio_param(gpio); + gpio_add_raw_irq_handler_with_order_priority_masked(1u << gpio, handler, order_priority); +} + +/*! \brief Adds a raw GPIO IRQ handler for the specified GPIOs on the current core + * \ingroup hardware_gpio + * + * In addition to the default mechanism of a single GPIO IRQ event callback per core (see \ref gpio_set_irq_callback), + * it is possible to add explicit GPIO IRQ handlers which are called independent of the default event callback. + * + * This method adds such a callback, and disables the "default" callback for the specified GPIOs. + * + * \note Multiple raw handlers should not be added for the same GPIOs, and this method will assert if you attempt to. + * + * A raw handler should check for whichever GPIOs and events it handles, and acknowledge them itself; it might look something like: + * + * \code{.c} + * void my_irq_handler(void) { + * if (gpio_get_irq_event_mask(my_gpio_num) & my_gpio_event_mask) { + * gpio_acknowledge_irq(my_gpio_num, my_gpio_event_mask); + * // handle the IRQ + * } + * if (gpio_get_irq_event_mask(my_gpio_num2) & my_gpio_event_mask2) { + * gpio_acknowledge_irq(my_gpio_num2, my_gpio_event_mask2); + * // handle the IRQ + * } + * } + * \endcode + * + * @param gpio_mask a bit mask of the GPIO numbers that will no longer be passed to the default callback for this core + * @param handler the handler to add to the list of GPIO IRQ handlers for this core + */ +void gpio_add_raw_irq_handler_masked(uint gpio_mask, irq_handler_t handler); + +/*! \brief Adds a raw GPIO IRQ handler for a specific GPIO on the current core + * \ingroup hardware_gpio + * + * In addition to the default mechanism of a single GPIO IRQ event callback per core (see \ref gpio_set_irq_callback), + * it is possible to add explicit GPIO IRQ handlers which are called independent of the default event callback. + * + * This method adds such a callback, and disables the "default" callback for the specified GPIO. + * + * \note Multiple raw handlers should not be added for the same GPIO, and this method will assert if you attempt to. + * + * A raw handler should check for whichever GPIOs and events it handles, and acknowledge them itself; it might look something like: + * + * \code{.c} + * void my_irq_handler(void) { + * if (gpio_get_irq_event_mask(my_gpio_num) & my_gpio_event_mask) { + * gpio_acknowledge_irq(my_gpio_num, my_gpio_event_mask); + * // handle the IRQ + * } + * } + * \endcode + * + * @param gpio the GPIO number that will no longer be passed to the default callback for this core + * @param handler the handler to add to the list of GPIO IRQ handlers for this core + */ +static inline void gpio_add_raw_irq_handler(uint gpio, irq_handler_t handler) { + check_gpio_param(gpio); + gpio_add_raw_irq_handler_masked(1u << gpio, handler); +} + +/*! \brief Removes a raw GPIO IRQ handler for the specified GPIOs on the current core + * \ingroup hardware_gpio + * + * In addition to the default mechanism of a single GPIO IRQ event callback per core (see \ref gpio_set_irq_callback), + * it is possible to add explicit GPIO IRQ handlers which are called independent of the default event callback. + * + * This method removes such a callback, and enables the "default" callback for the specified GPIOs. + * + * @param gpio_mask a bit mask of the GPIO numbers that will now be passed to the default callback for this core + * @param handler the handler to remove from the list of GPIO IRQ handlers for this core + */ +void gpio_remove_raw_irq_handler_masked(uint gpio_mask, irq_handler_t handler); + +/*! \brief Removes a raw GPIO IRQ handler for the specified GPIO on the current core + * \ingroup hardware_gpio + * + * In addition to the default mechanism of a single GPIO IRQ event callback per core (see \ref gpio_set_irq_callback), + * it is possible to add explicit GPIO IRQ handlers which are called independent of the default event callback. + * + * This method removes such a callback, and enables the "default" callback for the specified GPIO. + * + * @param gpio the GPIO number that will now be passed to the default callback for this core + * @param handler the handler to remove from the list of GPIO IRQ handlers for this core + */ +static inline void gpio_remove_raw_irq_handler(uint gpio, irq_handler_t handler) { + check_gpio_param(gpio); + gpio_remove_raw_irq_handler_masked(1u << gpio, handler); +} /*! \brief Initialise a GPIO for (enabled I/O and set func to GPIO_FUNC_SIO) * \ingroup hardware_gpio diff --git a/src/rp2_common/hardware_irq/include/hardware/irq.h b/src/rp2_common/hardware_irq/include/hardware/irq.h index f4451b835..66679feb0 100644 --- a/src/rp2_common/hardware_irq/include/hardware/irq.h +++ b/src/rp2_common/hardware_irq/include/hardware/irq.h @@ -252,6 +252,14 @@ void irq_add_shared_handler(uint num, irq_handler_t handler, uint8_t order_prior */ void irq_remove_handler(uint num, irq_handler_t handler); +/*! \brief Determine if the current handler for the given number is shared + * \ingroup hardware_irq + * + * \param num Interrupt number \ref interrupt_nums + * \param return true if the specified IRQ has a shared handler + */ +bool irq_has_shared_handler(uint num); + /*! \brief Get the current IRQ handler for the specified IRQ from the currently installed hardware vector table (VTOR) * of the execution core * \ingroup hardware_irq @@ -289,6 +297,69 @@ void irq_set_pending(uint num); * \note This is an internal method and user should generally not call it. */ void irq_init_priorities(void); + +/*! \brief Claim ownership of a user IRQ on the calling core + * \ingroup hardware_irq + * + * User IRQs are numbered 26-31 and are not connected to any hardware, but can be triggered by \ref irq_set_pending. + * + * \note User IRQs are a core local feature; they cannot be used to communicate between cores. Therfore all functions + * dealing with Uer IRQs affect only the calling core + * + * This method explicitly claims ownership of a user IRQ, so other code can know it is being used. + * + * \param irq_num the user IRQ to claim + */ +void user_irq_claim(uint irq_num); + +/*! \brief Mark a user IRQ as no longer used on the calling core + * \ingroup hardware_irq + * + * User IRQs are numbered 26-31 and are not connected to any hardware, but can be triggered by \ref irq_set_pending. + * + * \note User IRQs are a core local feature; they cannot be used to communicate between cores. Therfore all functions + * dealing with Uer IRQs affect only the calling core + * + * This method explicitly releases ownership of a user IRQ, so other code can know it is free to use. + * + * \note it is customary to have disabled the irq and removed the handler prior to calling this method. + * + * \param irq_num the irq irq_num to unclaim + */ +void user_irq_unclaim(uint irq_num); + +/*! \brief Claim ownership of a free user IRQ on the calling core + * \ingroup hardware_irq + * + * User IRQs are numbered 26-31 and are not connected to any hardware, but can be triggered by \ref irq_set_pending. + * + * \note User IRQs are a core local feature; they cannot be used to communicate between cores. Therfore all functions + * dealing with Uer IRQs affect only the calling core + * + * This method explicitly claims ownership of an unused user IRQ if there is one, so other code can know it is being used. + * + * \param required if true the function will panic if none are available + * \return the user IRQ number or -1 if required was false, and none were free + */ +int user_irq_claim_unused(bool required); + +/* +*! \brief Check if a user IRQ is in use on the calling core + * \ingroup hardware_irq + * + * User IRQs are numbered 26-31 and are not connected to any hardware, but can be triggered by \ref irq_set_pending. + * + * \note User IRQs are a core local feature; they cannot be used to communicate between cores. Therfore all functions + * dealing with Uer IRQs affect only the calling core + * + * \param irq_num the irq irq_num + * \return true if the irq_num is claimed, false otherwise + * \sa user_irq_claim + * \sa user_irq_unclaim + * \sa user_irq_claim_unused + */ +bool user_irq_is_claimed(uint irq_num); + #ifdef __cplusplus } #endif diff --git a/src/rp2_common/hardware_irq/irq.c b/src/rp2_common/hardware_irq/irq.c index def098ba2..40af5fea7 100644 --- a/src/rp2_common/hardware_irq/irq.c +++ b/src/rp2_common/hardware_irq/irq.c @@ -8,12 +8,15 @@ #include "hardware/regs/m0plus.h" #include "hardware/platform_defs.h" #include "hardware/structs/scb.h" +#include "hardware/claim.h" #include "pico/mutex.h" #include "pico/assert.h" extern void __unhandled_user_irq(void); +static uint8_t user_irq_claimed[NUM_CORES]; + static inline irq_handler_t *get_vtable(void) { return (irq_handler_t *) scb_hw->vtor; } @@ -92,10 +95,21 @@ static int8_t irq_hander_chain_free_slot_head; static inline bool is_shared_irq_raw_handler(irq_handler_t raw_handler) { return (uintptr_t)raw_handler - (uintptr_t)irq_handler_chain_slots < sizeof(irq_handler_chain_slots); } + +bool irq_has_shared_handler(uint irq_num) { + check_irq_param(irq_num); + irq_handler_t handler = irq_get_vtable_handler(irq_num); + return handler && is_shared_irq_raw_handler(handler); +} + #else #define is_shared_irq_raw_handler(h) false +bool irq_has_shared_handler(uint irq_num) { + return false; +} #endif + irq_handler_t irq_get_vtable_handler(uint num) { check_irq_param(num); return get_vtable()[16 + num]; @@ -199,7 +213,7 @@ void irq_add_shared_handler(uint num, irq_handler_t handler, uint8_t order_prior struct irq_handler_chain_slot slot_data = { .inst1 = 0xa100, // add r1, pc, #0 .inst2 = make_branch(&slot->inst2, irq_handler_chain_first_slot), // b irq_handler_chain_first_slot - .inst3 = 0xbd00, // pop {pc} + .inst3 = 0xbd01, // pop {r0, pc} .link = -1, .priority = order_priority, .handler = handler @@ -223,7 +237,7 @@ void irq_add_shared_handler(uint num, irq_handler_t handler, uint8_t order_prior .inst2 = 0x4780, // blx r0 .inst3 = prev_slot->link >= 0 ? make_branch(&slot->inst3, resolve_branch(&prev_slot->inst3)) : // b next_slot - 0xbd00, // pop {pc} + 0xbd01, // pop {r0, pc} .link = prev_slot->link, .priority = order_priority, .handler = handler @@ -305,7 +319,7 @@ void irq_remove_handler(uint num, irq_handler_t handler) { to_free_slot->link = next_slot->link; to_free_slot->inst3 = next_slot->link >= 0 ? make_branch(&to_free_slot->inst3, resolve_branch(&next_slot->inst3)) : // b mext_>slot->next_slot - 0xbd00; // pop {pc} + 0xbd01; // pop {r0, pc} // add old next slot back to free list next_slot->link = irq_hander_chain_free_slot_head; @@ -317,7 +331,7 @@ void irq_remove_handler(uint num, irq_handler_t handler) { if (prev_slot) { // chain is not empty prev_slot->link = -1; - prev_slot->inst3 = 0xbd00; // pop {pc} + prev_slot->inst3 = 0xbd01; // pop {r0, pc} } else { // chain is not empty vtable_handler = __unhandled_user_irq; @@ -386,7 +400,7 @@ void irq_add_tail_to_free_list(struct irq_handler_chain_slot *slot) { for(uint i=0;i= NUM_IRQS); + // we count backwards from the last, to match the existing hard coded uses of user IRQs in the SDK which were previously using 31 + static_assert(NUM_IRQS - FIRST_USER_IRQ <= 8, ""); // we only use a single byte's worth of claim bits today. + return NUM_IRQS - irq_num - 1u; +} + +void user_irq_claim(uint irq_num) { + hw_claim_or_assert(&user_irq_claimed[get_core_num()], get_user_irq_claim_index(irq_num), "User IRQ is already claimed"); +} + +void user_irq_unclaim(uint irq_num) { + hw_claim_clear(&user_irq_claimed[get_core_num()], get_user_irq_claim_index(irq_num)); +} + +int user_irq_claim_unused(bool required) { + int bit = hw_claim_unused_from_range(&user_irq_claimed[get_core_num()], required, 0, NUM_USER_IRQS - 1, "No user IRQs are available"); + if (bit >= 0) bit = (int)NUM_IRQS - bit - 1; + return bit; +} + +bool user_irq_is_claimed(uint irq_num) { + return hw_is_claimed(&user_irq_claimed[get_core_num()], get_user_irq_claim_index(irq_num)); +} + diff --git a/src/rp2_common/hardware_irq/irq_handler_chain.S b/src/rp2_common/hardware_irq/irq_handler_chain.S index ca1529287..bb65fb160 100644 --- a/src/rp2_common/hardware_irq/irq_handler_chain.S +++ b/src/rp2_common/hardware_irq/irq_handler_chain.S @@ -54,7 +54,9 @@ irq_handler_chain_slots: .endr irq_handler_chain_first_slot: - push {lr} // Save EXC_RETURN token, so `pop {pc}` will return from interrupt + push {r0, lr} // Save EXC_RETURN token, so `pop {r0, pc}` will return from interrupt + // Note that r0 does not NEED to be saved, however we must maintain + // an 8 byte stack alignment, and this is the cheapest way to do so ldr r0, [r1, #4] // Get `handler` field of irq_handler_chain_slot adds r1, #1 // r1 points to `inst3` field of slot struct. Set Thumb bit on r1, mov lr, r1 // and copy to lr, so `inst3` is executed on return from handler @@ -65,7 +67,7 @@ irq_handler_chain_remove_tail: subs r0, #9 // so lr points to offset +8. Note also lr has its Thumb bit set! ldr r1, =irq_add_tail_to_free_list blx r1 - pop {pc} // Top of stack is EXC_RETURN + pop {r0, pc} // Top of stack is EXC_RETURN #endif diff --git a/src/rp2_common/hardware_pio/include/hardware/pio.h b/src/rp2_common/hardware_pio/include/hardware/pio.h index ebe6afd8f..87f27790c 100644 --- a/src/rp2_common/hardware_pio/include/hardware/pio.h +++ b/src/rp2_common/hardware_pio/include/hardware/pio.h @@ -95,6 +95,9 @@ typedef pio_hw_t *PIO; /** \brief PIO Configuration structure * \ingroup sm_config + * + * This structure is an in-memory representation of the configuration that can be applied to a PIO + * state machine later using pio_sm_set_config() or pio_sm_init(). */ typedef struct { uint32_t clkdiv; @@ -815,7 +818,7 @@ static inline bool pio_interrupt_get(PIO pio, uint pio_interrupt_num) { static inline void pio_interrupt_clear(PIO pio, uint pio_interrupt_num) { check_pio_param(pio); invalid_params_if(PIO, pio_interrupt_num >= 8); - hw_set_bits(&pio->irq, (1u << pio_interrupt_num)); + pio->irq = (1u << pio_interrupt_num); } /*! \brief Return the current program counter for a state machine @@ -902,7 +905,7 @@ static inline void pio_sm_set_wrap(PIO pio, uint sm, uint wrap_target, uint wrap } /*! \brief Set the current 'out' pins for a state machine - * \ingroup sm_config + * \ingroup hardware_pio * * Can overlap with the 'in', 'set' and 'sideset' pins * @@ -923,7 +926,7 @@ static inline void pio_sm_set_out_pins(PIO pio, uint sm, uint out_base, uint out /*! \brief Set the current 'set' pins for a state machine - * \ingroup sm_config + * \ingroup hardware_pio * * Can overlap with the 'in', 'out' and 'sideset' pins * @@ -943,7 +946,7 @@ static inline void pio_sm_set_set_pins(PIO pio, uint sm, uint set_base, uint set } /*! \brief Set the current 'in' pins for a state machine - * \ingroup sm_config + * \ingroup hardware_pio * * Can overlap with the 'out', 'set' and 'sideset' pins * @@ -960,7 +963,7 @@ static inline void pio_sm_set_in_pins(PIO pio, uint sm, uint in_base) { } /*! \brief Set the current 'sideset' pins for a state machine - * \ingroup sm_config + * \ingroup hardware_pio * * Can overlap with the 'in', 'out' and 'set' pins * diff --git a/src/rp2_common/hardware_pll/include/hardware/pll.h b/src/rp2_common/hardware_pll/include/hardware/pll.h index ee0c3aef0..0466504d5 100644 --- a/src/rp2_common/hardware_pll/include/hardware/pll.h +++ b/src/rp2_common/hardware_pll/include/hardware/pll.h @@ -31,6 +31,14 @@ typedef pll_hw_t *PLL; #define pll_sys pll_sys_hw #define pll_usb pll_usb_hw +#ifndef PICO_PLL_VCO_MIN_FREQ_MHZ +#define PICO_PLL_VCO_MIN_FREQ_MHZ 750 +#endif + +#ifndef PICO_PLL_VCO_MAX_FREQ_MHZ +#define PICO_PLL_VCO_MAX_FREQ_MHZ 1600 +#endif + /*! \brief Initialise specified PLL. * \ingroup hardware_pll * \param pll pll_sys or pll_usb diff --git a/src/rp2_common/hardware_pll/pll.c b/src/rp2_common/hardware_pll/pll.c index f1a09d536..e152b844d 100644 --- a/src/rp2_common/hardware_pll/pll.c +++ b/src/rp2_common/hardware_pll/pll.c @@ -13,6 +13,9 @@ void pll_init(PLL pll, uint refdiv, uint vco_freq, uint post_div1, uint post_div2) { uint32_t ref_mhz = XOSC_MHZ / refdiv; + // Check vco freq is in an acceptable range + assert(vco_freq >= (PICO_PLL_VCO_MIN_FREQ_MHZ * MHZ) && vco_freq <= (PICO_PLL_VCO_MAX_FREQ_MHZ * MHZ)); + // What are we multiplying the reference clock by to get the vco freq // (The regs are called div, because you divide the vco output and compare it to the refclk) uint32_t fbdiv = vco_freq / (ref_mhz * MHZ); diff --git a/src/rp2_common/hardware_rtc/include/hardware/rtc.h b/src/rp2_common/hardware_rtc/include/hardware/rtc.h index 8757e36e0..2a72a8eb9 100644 --- a/src/rp2_common/hardware_rtc/include/hardware/rtc.h +++ b/src/rp2_common/hardware_rtc/include/hardware/rtc.h @@ -47,6 +47,10 @@ void rtc_init(void); /*! \brief Set the RTC to the specified time * \ingroup hardware_rtc * + * \note Note that after setting the RTC date and time, a subsequent read of the values (e.g. via rtc_get_datetime()) may not + * reflect the new setting until up to three cycles of the potentially-much-slower RTC clock domain have passed. This represents a period + * of 64 microseconds with the default RTC clock configuration. + * * \param t Pointer to a \ref datetime_t structure contains time to set * \return true if set, false if the passed in datetime was invalid. */ diff --git a/src/rp2_common/hardware_rtc/rtc.c b/src/rp2_common/hardware_rtc/rtc.c index 5429acd4b..be9250f6e 100644 --- a/src/rp2_common/hardware_rtc/rtc.c +++ b/src/rp2_common/hardware_rtc/rtc.c @@ -65,13 +65,13 @@ bool rtc_set_datetime(datetime_t *t) { } // Write to setup registers - rtc_hw->setup_0 = (((uint)t->year) << RTC_SETUP_0_YEAR_LSB ) | - (((uint)t->month) << RTC_SETUP_0_MONTH_LSB) | - (((uint)t->day) << RTC_SETUP_0_DAY_LSB); - rtc_hw->setup_1 = (((uint)t->dotw) << RTC_SETUP_1_DOTW_LSB) | - (((uint)t->hour) << RTC_SETUP_1_HOUR_LSB) | - (((uint)t->min) << RTC_SETUP_1_MIN_LSB) | - (((uint)t->sec) << RTC_SETUP_1_SEC_LSB); + rtc_hw->setup_0 = (((uint32_t)t->year) << RTC_SETUP_0_YEAR_LSB ) | + (((uint32_t)t->month) << RTC_SETUP_0_MONTH_LSB) | + (((uint32_t)t->day) << RTC_SETUP_0_DAY_LSB); + rtc_hw->setup_1 = (((uint32_t)t->dotw) << RTC_SETUP_1_DOTW_LSB) | + (((uint32_t)t->hour) << RTC_SETUP_1_HOUR_LSB) | + (((uint32_t)t->min) << RTC_SETUP_1_MIN_LSB) | + (((uint32_t)t->sec) << RTC_SETUP_1_SEC_LSB); // Load setup values into rtc clock domain rtc_hw->ctrl = RTC_CTRL_LOAD_BITS; @@ -95,13 +95,13 @@ bool rtc_get_datetime(datetime_t *t) { uint32_t rtc_0 = rtc_hw->rtc_0; uint32_t rtc_1 = rtc_hw->rtc_1; - t->dotw = (rtc_0 & RTC_RTC_0_DOTW_BITS ) >> RTC_RTC_0_DOTW_LSB; - t->hour = (rtc_0 & RTC_RTC_0_HOUR_BITS ) >> RTC_RTC_0_HOUR_LSB; - t->min = (rtc_0 & RTC_RTC_0_MIN_BITS ) >> RTC_RTC_0_MIN_LSB; - t->sec = (rtc_0 & RTC_RTC_0_SEC_BITS ) >> RTC_RTC_0_SEC_LSB; - t->year = (rtc_1 & RTC_RTC_1_YEAR_BITS ) >> RTC_RTC_1_YEAR_LSB; - t->month = (rtc_1 & RTC_RTC_1_MONTH_BITS) >> RTC_RTC_1_MONTH_LSB; - t->day = (rtc_1 & RTC_RTC_1_DAY_BITS ) >> RTC_RTC_1_DAY_LSB; + t->dotw = (int8_t) ((rtc_0 & RTC_RTC_0_DOTW_BITS ) >> RTC_RTC_0_DOTW_LSB); + t->hour = (int8_t) ((rtc_0 & RTC_RTC_0_HOUR_BITS ) >> RTC_RTC_0_HOUR_LSB); + t->min = (int8_t) ((rtc_0 & RTC_RTC_0_MIN_BITS ) >> RTC_RTC_0_MIN_LSB); + t->sec = (int8_t) ((rtc_0 & RTC_RTC_0_SEC_BITS ) >> RTC_RTC_0_SEC_LSB); + t->year = (int16_t) ((rtc_1 & RTC_RTC_1_YEAR_BITS ) >> RTC_RTC_1_YEAR_LSB); + t->month = (int8_t) ((rtc_1 & RTC_RTC_1_MONTH_BITS) >> RTC_RTC_1_MONTH_LSB); + t->day = (int8_t) ((rtc_1 & RTC_RTC_1_DAY_BITS ) >> RTC_RTC_1_DAY_LSB); return true; } @@ -148,13 +148,13 @@ void rtc_set_alarm(datetime_t *t, rtc_callback_t user_callback) { rtc_disable_alarm(); // Only add to setup if it isn't -1 - rtc_hw->irq_setup_0 = ((t->year < 0) ? 0 : (((uint)t->year) << RTC_IRQ_SETUP_0_YEAR_LSB )) | - ((t->month < 0) ? 0 : (((uint)t->month) << RTC_IRQ_SETUP_0_MONTH_LSB)) | - ((t->day < 0) ? 0 : (((uint)t->day) << RTC_IRQ_SETUP_0_DAY_LSB )); - rtc_hw->irq_setup_1 = ((t->dotw < 0) ? 0 : (((uint)t->dotw) << RTC_IRQ_SETUP_1_DOTW_LSB)) | - ((t->hour < 0) ? 0 : (((uint)t->hour) << RTC_IRQ_SETUP_1_HOUR_LSB)) | - ((t->min < 0) ? 0 : (((uint)t->min) << RTC_IRQ_SETUP_1_MIN_LSB )) | - ((t->sec < 0) ? 0 : (((uint)t->sec) << RTC_IRQ_SETUP_1_SEC_LSB )); + rtc_hw->irq_setup_0 = ((t->year < 0) ? 0 : (((uint32_t)t->year) << RTC_IRQ_SETUP_0_YEAR_LSB )) | + ((t->month < 0) ? 0 : (((uint32_t)t->month) << RTC_IRQ_SETUP_0_MONTH_LSB)) | + ((t->day < 0) ? 0 : (((uint32_t)t->day) << RTC_IRQ_SETUP_0_DAY_LSB )); + rtc_hw->irq_setup_1 = ((t->dotw < 0) ? 0 : (((uint32_t)t->dotw) << RTC_IRQ_SETUP_1_DOTW_LSB)) | + ((t->hour < 0) ? 0 : (((uint32_t)t->hour) << RTC_IRQ_SETUP_1_HOUR_LSB)) | + ((t->min < 0) ? 0 : (((uint32_t)t->min) << RTC_IRQ_SETUP_1_MIN_LSB )) | + ((t->sec < 0) ? 0 : (((uint32_t)t->sec) << RTC_IRQ_SETUP_1_SEC_LSB )); // Set the match enable bits for things we care about if (t->year >= 0) hw_set_bits(&rtc_hw->irq_setup_0, RTC_IRQ_SETUP_0_YEAR_ENA_BITS); diff --git a/src/rp2_common/hardware_sync/include/hardware/sync.h b/src/rp2_common/hardware_sync/include/hardware/sync.h index 8f91d5595..eee675e4b 100644 --- a/src/rp2_common/hardware_sync/include/hardware/sync.h +++ b/src/rp2_common/hardware_sync/include/hardware/sync.h @@ -304,15 +304,6 @@ __force_inline static void spin_unlock(spin_lock_t *lock, uint32_t saved_irq) { restore_interrupts(saved_irq); } -/*! \brief Get the current core number - * \ingroup hardware_sync - * - * \return The core number the call was made from - */ -__force_inline static uint get_core_num(void) { - return (*(uint32_t *) (SIO_BASE + SIO_CPUID_OFFSET)); -} - /*! \brief Initialise a spin lock * \ingroup hardware_sync * diff --git a/src/rp2_common/hardware_watchdog/include/hardware/watchdog.h b/src/rp2_common/hardware_watchdog/include/hardware/watchdog.h index 747838be8..7ce89ffc1 100644 --- a/src/rp2_common/hardware_watchdog/include/hardware/watchdog.h +++ b/src/rp2_common/hardware_watchdog/include/hardware/watchdog.h @@ -97,7 +97,7 @@ bool watchdog_caused_reboot(void); * This would not be present if a watchdog reset is initiated by \ref watchdog_reboot or by the RP2040 bootrom * (e.g. dragging a UF2 onto the RPI-RP2 drive). * - * @return true If the watchdog timer or a watchdog force caused (see \reg watchdog_caused_reboot) the last reboot + * @return true If the watchdog timer or a watchdog force caused (see \ref watchdog_caused_reboot) the last reboot * and the watchdog reboot happened after \ref watchdog_enable was called * @return false If there has been no watchdog reboot since the last power on reset, or the watchdog reboot was not caused * by a watchdog timeout after \ref watchdog_enable was called. diff --git a/src/rp2_common/pico_bootrom/bootrom.c b/src/rp2_common/pico_bootrom/bootrom.c index af7d09148..5def61a09 100644 --- a/src/rp2_common/pico_bootrom/bootrom.c +++ b/src/rp2_common/pico_bootrom/bootrom.c @@ -12,9 +12,6 @@ // Returns the 32 bit pointer into the ROM if found or NULL otherwise. typedef void *(*rom_table_lookup_fn)(uint16_t *table, uint32_t code); -// Convert a 16 bit pointer stored at the given rom address into a 32 bit pointer -#define rom_hword_as_ptr(rom_address) (void *)(uintptr_t)(*(uint16_t *)rom_address) - void *rom_func_lookup(uint32_t code) { return rom_func_lookup_inline(code); } diff --git a/src/rp2_common/pico_bootrom/include/pico/bootrom.h b/src/rp2_common/pico_bootrom/include/pico/bootrom.h index e55789315..448456860 100644 --- a/src/rp2_common/pico_bootrom/include/pico/bootrom.h +++ b/src/rp2_common/pico_bootrom/include/pico/bootrom.h @@ -116,8 +116,18 @@ bool rom_funcs_lookup(uint32_t *table, unsigned int count); // Returns the 32 bit pointer into the ROM if found or NULL otherwise. typedef void *(*rom_table_lookup_fn)(uint16_t *table, uint32_t code); +#if defined(__GNUC__) && (__GNUC__ >= 12) // Convert a 16 bit pointer stored at the given rom address into a 32 bit pointer -#define rom_hword_as_ptr(rom_address) (void *)(uintptr_t)(*(uint16_t *)rom_address) +static inline void *rom_hword_as_ptr(uint16_t rom_address) { +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Warray-bounds" + return (void *)(uintptr_t)*(uint16_t *)(uintptr_t)rom_address; +#pragma GCC diagnostic pop +} +#else +// Convert a 16 bit pointer stored at the given rom address into a 32 bit pointer +#define rom_hword_as_ptr(rom_address) (void *)(uintptr_t)(*(uint16_t *)(uintptr_t)(rom_address)) +#endif /*! * \brief Lookup a bootrom function by code. This method is forceably inlined into the caller for FLASH/RAM sensitive code usage 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 000000000..ede03f12d --- /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 000000000..c328d6e6a --- /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 000000000..01d9baa0e --- /dev/null +++ b/src/rp2_common/pico_cyw43_arch/cyw43_arch_freertos.c @@ -0,0 +1,253 @@ +/* + * 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); + vTaskDelete(NULL); +} + +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); + cyw43_deinit(&cyw43_state); +} + +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 000000000..e71f86ac5 --- /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 000000000..79ccfd701 --- /dev/null +++ b/src/rp2_common/pico_cyw43_arch/cyw43_arch_threadsafe_background.c @@ -0,0 +1,320 @@ +/* + * 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(); + cyw43_deinit(&cyw43_state); +} + +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 000000000..68cc4b8c5 --- /dev/null +++ b/src/rp2_common/pico_cyw43_arch/include/cyw43_configport.h @@ -0,0 +1,77 @@ +/* + * 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 + +#ifndef CYW43_WIFI_NVRAM_INCLUDE_FILE +#define CYW43_WIFI_NVRAM_INCLUDE_FILE "wifi_nvram_43439.h" +#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 000000000..8d6a628fc --- /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 000000000..3cd5aa975 --- /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 000000000..342a85caf --- /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 000000000..bfaea7f0e --- /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 000000000..713fcc1a8 --- /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 ad9e82298..503cd12c4 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 000000000..643e26258 --- /dev/null +++ b/src/rp2_common/pico_lwip/CMakeLists.txt @@ -0,0 +1,283 @@ +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 + ) + + # Mbed TLS 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 + ) + + # MQTT client files + add_library(pico_lwip_mqtt INTERFACE) + target_sources(pico_lwip_mqtt INTERFACE + ${PICO_LWIP_PATH}/src/apps/mqtt/mqtt.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 000000000..33b15c14c --- /dev/null +++ b/src/rp2_common/pico_lwip/doc.h @@ -0,0 +1,45 @@ +/** + * \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 - + * * \c \b pico_lwip_mqtt - + * + * 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 000000000..447e4d24e --- /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 000000000..4e7605db7 --- /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 000000000..2c4cdf878 --- /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/pico_malloc/CMakeLists.txt b/src/rp2_common/pico_malloc/CMakeLists.txt index deeb30f3f..57ac26784 100644 --- a/src/rp2_common/pico_malloc/CMakeLists.txt +++ b/src/rp2_common/pico_malloc/CMakeLists.txt @@ -10,6 +10,7 @@ if (NOT TARGET pico_malloc) pico_wrap_function(pico_malloc malloc) pico_wrap_function(pico_malloc calloc) + pico_wrap_function(pico_malloc realloc) pico_wrap_function(pico_malloc free) target_link_libraries(pico_malloc INTERFACE pico_sync) diff --git a/src/rp2_common/pico_malloc/pico_malloc.c b/src/rp2_common/pico_malloc/pico_malloc.c index 4928a8e9a..a05398663 100644 --- a/src/rp2_common/pico_malloc/pico_malloc.c +++ b/src/rp2_common/pico_malloc/pico_malloc.c @@ -16,6 +16,7 @@ auto_init_mutex(malloc_mutex); extern void *__real_malloc(size_t size); extern void *__real_calloc(size_t count, size_t size); +extern void *__real_realloc(void *mem, size_t size); extern void __real_free(void *mem); extern char __StackLimit; /* Set by linker. */ @@ -62,6 +63,23 @@ void *__wrap_calloc(size_t count, size_t size) { return rc; } +void *__wrap_realloc(void *mem, size_t size) { +#if PICO_USE_MALLOC_MUTEX + mutex_enter_blocking(&malloc_mutex); +#endif + void *rc = __real_realloc(mem, size); +#if PICO_USE_MALLOC_MUTEX + mutex_exit(&malloc_mutex); +#endif +#if PICO_DEBUG_MALLOC + if (!rc || ((uint8_t *)rc) + size > (uint8_t*)PICO_DEBUG_MALLOC_LOW_WATER) { + printf("realloc %p %d->%p\n", mem, (uint) size, rc); + } +#endif + check_alloc(rc, size); + return rc; +} + void __wrap_free(void *mem) { #if PICO_USE_MALLOC_MUTEX mutex_enter_blocking(&malloc_mutex); diff --git a/src/rp2_common/pico_platform/include/pico/platform.h b/src/rp2_common/pico_platform/include/pico/platform.h index de64e3003..1d9e1bc7d 100644 --- a/src/rp2_common/pico_platform/include/pico/platform.h +++ b/src/rp2_common/pico_platform/include/pico/platform.h @@ -17,6 +17,8 @@ */ #include "hardware/platform_defs.h" +#include "hardware/regs/addressmap.h" +#include "hardware/regs/sio.h" // Marker for builds targeting the RP2040 #define PICO_RP2040 1 @@ -334,7 +336,10 @@ uint8_t rp2040_chip_version(void); * @return the RP2040 rom version number (1 for RP2040-B0, 2 for RP2040-B1, 3 for RP2040-B2) */ static inline uint8_t rp2040_rom_version(void) { +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Warray-bounds" return *(uint8_t*)0x13; +#pragma GCC diagnostic pop } /*! \brief No-op function for the body of tight loops @@ -405,12 +410,12 @@ uint __get_current_exception(void); * * This method is useful for introducing very short delays. * - * This method busy-waits in a tight loop for the give number of system clock cycles. The total wait time is only accurate to within 2 cycles, + * This method busy-waits in a tight loop for the given number of system clock cycles. The total wait time is only accurate to within 2 cycles, * and this method uses a loop counter rather than a hardware timer, so the method will always take longer than expected if an - * interrupt is handled on the calling core when during the busy-wait; you can of course disable interrupts to prevent this. + * interrupt is handled on the calling core during the busy-wait; you can of course disable interrupts to prevent this. * - * You can use \ref clock_get_hz(clk_sys) to determine the number of clock cycles per second if you want to convert to cycles - * from an actual time duration. + * You can use \ref clock_get_hz(clk_sys) to determine the number of clock cycles per second if you want to convert an actual + * time duration to a number of cycles. * * \param minimum_cycles the minimum number of system clock cycles to delay for */ @@ -423,6 +428,15 @@ static inline void busy_wait_at_least_cycles(uint32_t minimum_cycles) { ); } +/*! \brief Get the current core number + * \ingroup pico_platform + * + * \return The core number the call was made from + */ +__force_inline static uint get_core_num(void) { + return (*(uint32_t *) (SIO_BASE + SIO_CPUID_OFFSET)); +} + #else // __ASSEMBLER__ #define WRAPPER_FUNC_NAME(x) __wrap_##x diff --git a/src/rp2_common/pico_standard_link/CMakeLists.txt b/src/rp2_common/pico_standard_link/CMakeLists.txt index a7305403c..2efc287c9 100644 --- a/src/rp2_common/pico_standard_link/CMakeLists.txt +++ b/src/rp2_common/pico_standard_link/CMakeLists.txt @@ -62,7 +62,7 @@ if (NOT TARGET pico_standard_link) # PICO_BUILD_DEFINE: PICO_NO_FLASH, whether this is a 'no_flash' build, type=bool, default=0, but dependent on CMake options, group=pico_standard_link target_compile_definitions(pico_standard_link INTERFACE PICO_NO_FLASH=$,no_flash>,1,$,$>>>) # PICO_USE_BLOCKED_RAM will be set based on PICO_TARGET_BUILD_TYPE target property being equal to use_blocked_ram if set, otherwise to the value of the PICO_USE_BLOCKED_RAM cmake variable unless PICO_TARGET_TYPE is set to something else - # PICO_BUILD_DEFINE: PICO_USE_BLOCKS_RAM, whether this is a 'blocked_ram' build, type=bool, default=0, but dependent on CMake options, group=pico_standard_link + # PICO_BUILD_DEFINE: PICO_USE_BLOCKED_RAM, whether this is a 'blocked_ram' build, type=bool, default=0, but dependent on CMake options, group=pico_standard_link target_compile_definitions(pico_standard_link INTERFACE PICO_USE_BLOCKED_RAM=$,use_blocked_ram>,1,$,$>>>) # PICO_COPY_TO_RAM will be set based on PICO_TARGET_BUILD_TYPE target property being equal to copy_to_ram if set, otherwise to the value of the PICO_COPY_TO_RAM cmake variable unless PICO_TARGET_TYPE is set to something else # PICO_BUILD_DEFINE: PICO_COPY_TO_RAM, whether this is a 'copy_to_ram' build, type=bool, default=0, but dependent on CMake options, group=pico_standard_link @@ -89,7 +89,7 @@ if (NOT TARGET pico_standard_link) # this line occasionally useful for debugging ... todo maybe make a PICO_ var # target_compile_options(pico_standard_link INTERFACE --save-temps) #debugging only - # PICO_CMAKE_CONFIG: PICO_NO_GC_SECTIONS, Disable -ffunction-sections -fdata-sections, and --gc-sections, type=bool, default=0, advanced=true, group=pico_standard_link + # PICO_CMAKE_CONFIG: PICO_NO_GC_SECTIONS, Disable -ffunction-sections -fdata-sections and --gc-sections, type=bool, default=0, advanced=true, group=pico_standard_link if (NOT PICO_NO_GC_SECTIONS) target_compile_options(pico_standard_link INTERFACE -ffunction-sections -fdata-sections) target_link_options(pico_standard_link INTERFACE "LINKER:--gc-sections") diff --git a/src/rp2_common/pico_standard_link/crt0.S b/src/rp2_common/pico_standard_link/crt0.S index f57ddfdef..b2992f65f 100644 --- a/src/rp2_common/pico_standard_link/crt0.S +++ b/src/rp2_common/pico_standard_link/crt0.S @@ -225,6 +225,9 @@ _reset_handler: cmp r0, #0 bne hold_non_core0_in_bootrom + // In a NO_FLASH binary, don't perform .data copy, since it's loaded + // in-place by the SRAM load. Still need to clear .bss +#if !PICO_NO_FLASH adr r4, data_cpy_table // assume there is at least one entry @@ -235,6 +238,7 @@ _reset_handler: bl data_cpy b 1b 2: +#endif // Zero out the BSS ldr r1, =__bss_start__ @@ -266,6 +270,7 @@ _exit: bkpt #0 b 1b +#if !PICO_NO_FLASH data_cpy_loop: ldm r1!, {r0} stm r2!, {r0} @@ -273,6 +278,10 @@ data_cpy: cmp r2, r3 blo data_cpy_loop bx lr +#endif + +// Note the data copy table is still included for NO_FLASH builds, even though +// we skip the copy, because it is listed in binary info .align 2 data_cpy_table: diff --git a/src/rp2_common/pico_stdio/include/pico/stdio.h b/src/rp2_common/pico_stdio/include/pico/stdio.h index e44c01d05..87c245afe 100644 --- a/src/rp2_common/pico_stdio/include/pico/stdio.h +++ b/src/rp2_common/pico_stdio/include/pico/stdio.h @@ -52,9 +52,10 @@ typedef struct stdio_driver stdio_driver_t; * When stdio_usb is configured, this method can be optionally made to block, waiting for a connection * via the variables specified in \ref stdio_usb_init (i.e. \ref PICO_STDIO_USB_CONNECT_WAIT_TIMEOUT_MS) * + * \return true if at least one output was successfully initialized, false otherwise. * \see stdio_uart, stdio_usb, stdio_semihosting */ -void stdio_init_all(void); +bool stdio_init_all(void); /*! \brief Initialize all of the present standard stdio types that are linked into the binary. * \ingroup pico_stdio diff --git a/src/rp2_common/pico_stdio/stdio.c b/src/rp2_common/pico_stdio/stdio.c index e2eb5ffb9..cce9de8db 100644 --- a/src/rp2_common/pico_stdio/stdio.c +++ b/src/rp2_common/pico_stdio/stdio.c @@ -9,13 +9,15 @@ #include #include "pico.h" -#include "pico/mutex.h" #if LIB_PICO_PRINTF_PICO #include "pico/printf.h" #endif #include "pico/stdio.h" #include "pico/stdio/driver.h" #include "pico/time.h" +#if PICO_STDOUT_MUTEX +#include "pico/mutex.h" +#endif #if LIB_PICO_STDIO_UART #include "pico/stdio_uart.h" @@ -29,6 +31,10 @@ #include "pico/stdio_semihosting.h" #endif +#define STDIO_HANDLE_STDIN 0 +#define STDIO_HANDLE_STDOUT 1 +#define STDIO_HANDLE_STDERR 2 + static stdio_driver_t *drivers; static stdio_driver_t *filter; @@ -131,11 +137,13 @@ static int stdio_get_until(char *buf, int len, absolute_time_t until) { } } } + if (time_reached(until)) { + return PICO_ERROR_TIMEOUT; + } // we sleep here in case the in_chars methods acquire mutexes or disable IRQs and // potentially starve out what they are waiting on (have seen this with USB) busy_wait_us(1); - } while (!time_reached(until)); - return PICO_ERROR_TIMEOUT; + } while (true); } int WRAPPER_FUNC(putchar)(int c) { @@ -165,14 +173,14 @@ int puts_raw(const char *s) { } int _read(int handle, char *buffer, int length) { - if (handle == 0) { + if (handle == STDIO_HANDLE_STDIN) { return stdio_get_until(buffer, length, at_the_end_of_time); } return -1; } int _write(int handle, char *buffer, int length) { - if (handle == 1) { + if (handle == STDIO_HANDLE_STDOUT || handle == STDIO_HANDLE_STDERR) { stdio_put_string(buffer, length, false, false); return length; } @@ -261,20 +269,25 @@ int __printflike(1, 0) WRAPPER_FUNC(printf)(const char* format, ...) return ret; } -void stdio_init_all(void) { +bool stdio_init_all(void) { // todo add explicit custom, or registered although you can call stdio_enable_driver explicitly anyway // These are well known ones + + bool rc = false; #if LIB_PICO_STDIO_UART stdio_uart_init(); + rc = true; #endif #if LIB_PICO_STDIO_SEMIHOSTING stdio_semihosting_init(); + rc = true; #endif #if LIB_PICO_STDIO_USB - stdio_usb_init(); + rc |= stdio_usb_init(); #endif + return rc; } int WRAPPER_FUNC(getchar)(void) { @@ -304,6 +317,10 @@ void stdio_set_translate_crlf(stdio_driver_t *driver, bool enabled) { } driver->crlf_enabled = enabled; #else + // Suppress -Wunused-parameter + (void)driver; + (void)enabled; + panic_unsupported(); #endif -} +} \ No newline at end of file diff --git a/src/rp2_common/pico_stdio_usb/include/pico/stdio_usb.h b/src/rp2_common/pico_stdio_usb/include/pico/stdio_usb.h index c1ace0350..565b6c328 100644 --- a/src/rp2_common/pico_stdio_usb/include/pico/stdio_usb.h +++ b/src/rp2_common/pico_stdio_usb/include/pico/stdio_usb.h @@ -39,9 +39,9 @@ #define PICO_STDIO_USB_TASK_INTERVAL_US 1000 #endif -// PICO_CONFIG: PICO_STDIO_USB_LOW_PRIORITY_IRQ, low priority (non hardware) IRQ number to claim for tud_task() background execution, default=31, advanced=true, group=pico_stdio_usb +// PICO_CONFIG: PICO_STDIO_USB_LOW_PRIORITY_IRQ, Explicit User IRQ number to claim for tud_task() background execution instead of letting the implementation pick a free one dynamically (deprecated), advanced=true, group=pico_stdio_usb #ifndef PICO_STDIO_USB_LOW_PRIORITY_IRQ -#define PICO_STDIO_USB_LOW_PRIORITY_IRQ 31 +// this variable is no longer set by default (one is claimed dynamically), but will be respected if specified #endif // PICO_CONFIG: PICO_STDIO_USB_ENABLE_RESET_VIA_BAUD_RATE, Enable/disable resetting into BOOTSEL mode if the host sets the baud rate to a magic value (PICO_STDIO_USB_RESET_MAGIC_BAUD_RATE), type=bool, default=1, group=pico_stdio_usb @@ -97,6 +97,11 @@ #define PICO_STDIO_USB_RESET_RESET_TO_FLASH_DELAY_MS 100 #endif +// PICO_CONFIG: PICO_STDIO_USB_CONNECTION_WITHOUT_DTR, Disable use of DTR for connection checking meaning connection is assumed to be valid, type=bool, default=0, group=pico_stdio_usb +#ifndef PICO_STDIO_USB_CONNECTION_WITHOUT_DTR +#define PICO_STDIO_USB_CONNECTION_WITHOUT_DTR 0 +#endif + #ifdef __cplusplus extern "C" { #endif diff --git a/src/rp2_common/pico_stdio_usb/include/tusb_config.h b/src/rp2_common/pico_stdio_usb/include/tusb_config.h index c0a28435c..c0d2ed3e3 100644 --- a/src/rp2_common/pico_stdio_usb/include/tusb_config.h +++ b/src/rp2_common/pico_stdio_usb/include/tusb_config.h @@ -29,6 +29,7 @@ #include "pico/stdio_usb.h" +#if !defined(LIB_TINYUSB_HOST) && !defined(LIB_TINYUSB_DEVICE) #define CFG_TUSB_RHPORT0_MODE (OPT_MODE_DEVICE) #define CFG_TUD_CDC (1) @@ -38,3 +39,5 @@ // We use a vendor specific interface but with our own driver #define CFG_TUD_VENDOR (0) #endif + +#endif diff --git a/src/rp2_common/pico_stdio_usb/reset_interface.c b/src/rp2_common/pico_stdio_usb/reset_interface.c index 06dce86a9..cc2517799 100644 --- a/src/rp2_common/pico_stdio_usb/reset_interface.c +++ b/src/rp2_common/pico_stdio_usb/reset_interface.c @@ -7,6 +7,8 @@ #include "pico/bootrom.h" +#if !defined(LIB_TINYUSB_HOST) && !defined(LIB_TINYUSB_DEVICE) + #if PICO_STDIO_USB_ENABLE_RESET_VIA_VENDOR_INTERFACE && !(PICO_STDIO_USB_RESET_INTERFACE_SUPPORT_RESET_TO_BOOTSEL || PICO_STDIO_USB_RESET_INTERFACE_SUPPORT_RESET_TO_FLASH_BOOT) #warning PICO_STDIO_USB_ENABLE_RESET_VIA_VENDOR_INTERFACE has been selected but neither PICO_STDIO_USB_RESET_INTERFACE_SUPPORT_RESET_TO_BOOTSEL nor PICO_STDIO_USB_RESET_INTERFACE_SUPPORT_RESET_TO_FLASH_BOOT have been selected. #endif @@ -110,3 +112,4 @@ void tud_cdc_line_coding_cb(__unused uint8_t itf, cdc_line_coding_t const* p_lin } #endif +#endif \ No newline at end of file diff --git a/src/rp2_common/pico_stdio_usb/stdio_usb.c b/src/rp2_common/pico_stdio_usb/stdio_usb.c index 913d606c4..d7a649c32 100644 --- a/src/rp2_common/pico_stdio_usb/stdio_usb.c +++ b/src/rp2_common/pico_stdio_usb/stdio_usb.c @@ -4,33 +4,87 @@ * SPDX-License-Identifier: BSD-3-Clause */ -#if !defined(LIB_TINYUSB_HOST) && !defined(LIB_TINYUSB_DEVICE) +#ifndef LIB_TINYUSB_HOST #include "tusb.h" +#include "pico/stdio_usb.h" + +// these may not be set if the user is providing tud support (i.e. LIB_TINYUSB_DEVICE is 1 because +// the user linked in tinyusb_device) but they haven't selected CDC +#if (CFG_TUD_ENABLED | TUSB_OPT_DEVICE_ENABLED) && CFG_TUD_CDC +#include "pico/binary_info.h" #include "pico/time.h" #include "pico/stdio/driver.h" -#include "pico/binary_info.h" #include "pico/mutex.h" #include "hardware/irq.h" -static_assert(PICO_STDIO_USB_LOW_PRIORITY_IRQ > RTC_IRQ, ""); // note RTC_IRQ is currently the last one static mutex_t stdio_usb_mutex; +#ifndef NDEBUG +static uint8_t stdio_usb_core_num; +#endif + +// when tinyusb_device is explicitly linked we do no background tud processing +#if !LIB_TINYUSB_DEVICE +// if this crit_sec is initialized, we are not in periodic timer mode, and must make sure +// we don't either create multiple one shot timers, or miss creating one. this crit_sec +// is used to protect the one_shot_timer_pending flag +static critical_section_t one_shot_timer_crit_sec; +static volatile bool one_shot_timer_pending; +#ifdef PICO_STDIO_USB_LOW_PRIORITY_IRQ +static_assert(PICO_STDIO_USB_LOW_PRIORITY_IRQ >= NUM_IRQS - NUM_USER_IRQS, ""); +#define low_priority_irq_num PICO_STDIO_USB_LOW_PRIORITY_IRQ +#else +static uint8_t low_priority_irq_num; +#endif + +static int64_t timer_task(__unused alarm_id_t id, __unused void *user_data) { + int64_t repeat_time; + if (critical_section_is_initialized(&one_shot_timer_crit_sec)) { + critical_section_enter_blocking(&one_shot_timer_crit_sec); + one_shot_timer_pending = false; + critical_section_exit(&one_shot_timer_crit_sec); + repeat_time = 0; // don't repeat + } else { + repeat_time = PICO_STDIO_USB_TASK_INTERVAL_US; + } + irq_set_pending(low_priority_irq_num); + return repeat_time; +} static void low_priority_worker_irq(void) { - // if the mutex is already owned, then we are in user code - // in this file which will do a tud_task itself, so we'll just do nothing - // until the next tick; we won't starve if (mutex_try_enter(&stdio_usb_mutex, NULL)) { tud_task(); mutex_exit(&stdio_usb_mutex); + } else { + // if the mutex is already owned, then we are in non IRQ code in this file. + // + // it would seem simplest to just let that code call tud_task() at the end, however this + // code might run during the call to tud_task() and we might miss a necessary tud_task() call + // + // if we are using a periodic timer (crit_sec is not initialized in this case), + // then we are happy just to wait until the next tick, however when we are not using a periodic timer, + // we must kick off a one-shot timer to make sure the tud_task() DOES run (this method + // will be called again as a result, and will try the mutex_try_enter again, and if that fails + // create another one shot timer again, and so on). + if (critical_section_is_initialized(&one_shot_timer_crit_sec)) { + bool need_timer; + critical_section_enter_blocking(&one_shot_timer_crit_sec); + need_timer = !one_shot_timer_pending; + one_shot_timer_pending = true; + critical_section_exit(&one_shot_timer_crit_sec); + if (need_timer) { + add_alarm_in_us(PICO_STDIO_USB_TASK_INTERVAL_US, timer_task, NULL, true); + } + } } } -static int64_t timer_task(__unused alarm_id_t id, __unused void *user_data) { - irq_set_pending(PICO_STDIO_USB_LOW_PRIORITY_IRQ); - return PICO_STDIO_USB_TASK_INTERVAL_US; +static void usb_irq(void) { + irq_set_pending(low_priority_irq_num); } +#endif + static void stdio_usb_out_chars(const char *buf, int length) { static uint64_t last_avail_time; uint32_t owner; @@ -38,7 +92,7 @@ static void stdio_usb_out_chars(const char *buf, int length) { if (owner == get_core_num()) return; // would deadlock otherwise mutex_enter_blocking(&stdio_usb_mutex); } - if (tud_cdc_connected()) { + if (stdio_usb_connected()) { for (int i = 0; i < length;) { int n = length - i; int avail = (int) tud_cdc_write_available(); @@ -52,7 +106,7 @@ static void stdio_usb_out_chars(const char *buf, int length) { } else { tud_task(); tud_cdc_write_flush(); - if (!tud_cdc_connected() || + if (!stdio_usb_connected() || (!tud_cdc_write_available() && time_us_64() > last_avail_time + PICO_STDIO_USB_STDOUT_TIMEOUT_US)) { break; } @@ -72,9 +126,12 @@ int stdio_usb_in_chars(char *buf, int length) { mutex_enter_blocking(&stdio_usb_mutex); } int rc = PICO_ERROR_NO_DATA; - if (tud_cdc_connected() && tud_cdc_available()) { + if (stdio_usb_connected() && tud_cdc_available()) { int count = (int) tud_cdc_read(buf, (uint32_t) length); rc = count ? count : PICO_ERROR_NO_DATA; + } else { + // because our mutex use may starve out the background task, run tud_task here (we own the mutex) + tud_task(); } mutex_exit(&stdio_usb_mutex); return rc; @@ -89,18 +146,47 @@ stdio_driver_t stdio_usb = { }; bool stdio_usb_init(void) { + if (get_core_num() != alarm_pool_core_num(alarm_pool_get_default())) { + // included an assertion here rather than just returning false, as this is likely + // a coding bug, rather than anything else. + assert(false); + return false; + } +#ifndef NDEBUG + stdio_usb_core_num = (uint8_t)get_core_num(); +#endif #if !PICO_NO_BI_STDIO_USB bi_decl_if_func_used(bi_program_feature("USB stdin / stdout")); #endif - // initialize TinyUSB +#if !defined(LIB_TINYUSB_DEVICE) + // initialize TinyUSB, as user hasn't explicitly linked it tusb_init(); - - irq_set_exclusive_handler(PICO_STDIO_USB_LOW_PRIORITY_IRQ, low_priority_worker_irq); - irq_set_enabled(PICO_STDIO_USB_LOW_PRIORITY_IRQ, true); +#else + assert(tud_inited()); // we expect the caller to have initialized if they are using TinyUSB +#endif mutex_init(&stdio_usb_mutex); - bool rc = add_alarm_in_us(PICO_STDIO_USB_TASK_INTERVAL_US, timer_task, NULL, true); + bool rc = true; +#if !LIB_TINYUSB_DEVICE +#ifdef PICO_STDIO_USB_LOW_PRIORITY_IRQ + user_irq_claim(PICO_STDIO_USB_LOW_PRIORITY_IRQ); +#else + low_priority_irq_num = (uint8_t) user_irq_claim_unused(true); +#endif + irq_set_exclusive_handler(low_priority_irq_num, low_priority_worker_irq); + irq_set_enabled(low_priority_irq_num, true); + + if (irq_has_shared_handler(USBCTRL_IRQ)) { + // we can use a shared handler to notice when there may be work to do + irq_add_shared_handler(USBCTRL_IRQ, usb_irq, PICO_SHARED_IRQ_HANDLER_LOWEST_ORDER_PRIORITY); + critical_section_init_with_lock_num(&one_shot_timer_crit_sec, next_striped_spin_lock_num()); + } else { + rc = add_alarm_in_us(PICO_STDIO_USB_TASK_INTERVAL_US, timer_task, NULL, true) >= 0; + // we use initialization state of the one_shot_timer_critsec as a flag + memset(&one_shot_timer_crit_sec, 0, sizeof(one_shot_timer_crit_sec)); + } +#endif if (rc) { stdio_set_driver_enabled(&stdio_usb, true); #if PICO_STDIO_USB_CONNECT_WAIT_TIMEOUT_MS @@ -124,12 +210,24 @@ bool stdio_usb_init(void) { } bool stdio_usb_connected(void) { +#if PICO_STDIO_USB_CONNECTION_WITHOUT_DTR + return tud_ready(); +#else + // this actually checks DTR return tud_cdc_connected(); +#endif } + #else -#include "pico/stdio_usb.h" -#warning stdio USB was configured, but is being disabled as TinyUSB is explicitly linked +#warning stdio USB was configured along with user use of TinyUSB device mode, but CDC is not enabled bool stdio_usb_init(void) { return false; } -#endif +#endif // CFG_TUD_ENABLED && CFG_TUD_CDC +#else +#warning stdio USB was configured, but is being disabled as TinyUSB host is explicitly linked +bool stdio_usb_init(void) { + return false; +} +#endif // !LIB_TINYUSB_HOST + diff --git a/src/rp2_common/pico_stdlib/CMakeLists.txt b/src/rp2_common/pico_stdlib/CMakeLists.txt index b54639b44..cc48743ae 100644 --- a/src/rp2_common/pico_stdlib/CMakeLists.txt +++ b/src/rp2_common/pico_stdlib/CMakeLists.txt @@ -2,8 +2,8 @@ option(PICO_STDIO_UART "Globablly enable stdio UART" 1) # PICO_CMAKE_CONFIG: PICO_STDIO_USB, OPTION: Globally enable stdio USB, default=0, group=pico_stdlib option(PICO_STDIO_USB "Globablly enable stdio USB" 0) -# PICO_CMAKE_CONFIG: PICO_STDIO_USB, OPTIONS: Globally enable stdio semihosting, default=0, group=pico_stdlib -option(PICO_STDIO_USB "Globablly enable stdio semihosting " 0) +# PICO_CMAKE_CONFIG: PICO_STDIO_SEMIHOSTING, OPTION: Globally enable stdio semihosting, default=0, group=pico_stdlib +option(PICO_STDIO_SEMIHOSTING "Globablly enable stdio semihosting" 0) if (NOT TARGET pico_stdlib) pico_add_impl_library(pico_stdlib) diff --git a/src/rp2_common/pico_stdlib/stdlib.c b/src/rp2_common/pico_stdlib/stdlib.c index 7c9854e7a..159095ce4 100644 --- a/src/rp2_common/pico_stdlib/stdlib.c +++ b/src/rp2_common/pico_stdlib/stdlib.c @@ -73,7 +73,7 @@ bool check_sys_clock_khz(uint32_t freq_khz, uint *vco_out, uint *postdiv1_out, u uint crystal_freq_khz = clock_get_hz(clk_ref) / 1000; for (uint fbdiv = 320; fbdiv >= 16; fbdiv--) { uint vco = fbdiv * crystal_freq_khz; - if (vco < 400000 || vco > 1600000) continue; + if (vco < PICO_PLL_VCO_MIN_FREQ_MHZ * 1000 || vco > PICO_PLL_VCO_MAX_FREQ_MHZ * 1000) continue; for (uint postdiv1 = 7; postdiv1 >= 1; postdiv1--) { for (uint postdiv2 = postdiv1; postdiv2 >= 1; postdiv2--) { uint out = vco / (postdiv1 * postdiv2); diff --git a/src/rp2_common/tinyusb/CMakeLists.txt b/src/rp2_common/tinyusb/CMakeLists.txt index ea919cd64..897878855 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/CMakeLists.txt b/test/CMakeLists.txt index 73966057c..a61388a58 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -9,4 +9,5 @@ if (PICO_ON_DEVICE) add_subdirectory(hardware_irq_test) add_subdirectory(hardware_pwm_test) add_subdirectory(cmsis_test) -endif() \ No newline at end of file + add_subdirectory(pico_sem_test) +endif() diff --git a/test/kitchen_sink/CMakeLists.txt b/test/kitchen_sink/CMakeLists.txt index 3487fbb31..a2019919c 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 f61ece363..a49654d1b 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" @@ -110,6 +113,9 @@ __force_inline int something_inlined(int x) { return x * 2; } +auto_init_mutex(mutex); +auto_init_recursive_mutex(recursive_mutex); + int main(void) { spiggle(); @@ -118,6 +124,12 @@ int main(void) { printf("HI %d\n", something_inlined((int)time_us_32())); puts("Hello Everything!"); puts("Hello Everything2!"); + + hard_assert(mutex_try_enter(&mutex, NULL)); + hard_assert(!mutex_try_enter(&mutex, NULL)); + hard_assert(recursive_mutex_try_enter(&recursive_mutex, NULL)); + hard_assert(recursive_mutex_try_enter(&recursive_mutex, NULL)); // this should compile as we are Cortex M0+ __asm volatile("SVC #3"); + } diff --git a/test/kitchen_sink/lwipopts.h b/test/kitchen_sink/lwipopts.h new file mode 100644 index 000000000..58b704a1c --- /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 diff --git a/test/pico_sem_test/CMakeLists.txt b/test/pico_sem_test/CMakeLists.txt new file mode 100644 index 000000000..786963c37 --- /dev/null +++ b/test/pico_sem_test/CMakeLists.txt @@ -0,0 +1,4 @@ +add_executable(pico_sem_test pico_sem_test.c) + +target_link_libraries(pico_sem_test PRIVATE pico_test pico_sync) +pico_add_extra_outputs(pico_sem_test) diff --git a/test/pico_sem_test/pico_sem_test.c b/test/pico_sem_test/pico_sem_test.c new file mode 100644 index 000000000..8647c195f --- /dev/null +++ b/test/pico_sem_test/pico_sem_test.c @@ -0,0 +1,29 @@ +/** + * Copyright (c) 2022 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include + +#include "pico/sem.h" +#include "pico/test.h" +#include "pico/stdio.h" + +PICOTEST_MODULE_NAME("SEM", "semaphore test"); + +int main() { + semaphore_t sem; + + stdio_init_all(); + sem_init(&sem, 1, 1); + + PICOTEST_START(); + + PICOTEST_START_SECTION("sem_try_acquire"); + PICOTEST_CHECK(sem_try_acquire(&sem), "available permit not acquired"); + PICOTEST_CHECK(!sem_try_acquire(&sem), "success with no permits"); + PICOTEST_END_SECTION(); + + PICOTEST_END_TEST(); +} diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt index 59bf09f9e..bc49c9852 100644 --- a/tools/CMakeLists.txt +++ b/tools/CMakeLists.txt @@ -4,7 +4,7 @@ function(_pico_init_pioasm) find_package(Pioasm REQUIRED) endfunction() -# PICO_DEFAULT_PIOASM_OUTPUT_FORMAT, default output format used by pioasm when using pico_generate_pio_header, default=c-sdk, group=build +# PICO_CMAKE_CONFIG: PICO_DEFAULT_PIOASM_OUTPUT_FORMAT, default output format used by pioasm when using pico_generate_pio_header, default=c-sdk, group=build function(pico_generate_pio_header TARGET PIO) _pico_init_pioasm() cmake_parse_arguments(pico_generate_pio_header "" "OUTPUT_FORMAT;OUTPUT_DIR" "" ${ARGN} ) diff --git a/tools/check_configs.sh b/tools/check_configs.sh new file mode 100755 index 000000000..95e0a4c1b --- /dev/null +++ b/tools/check_configs.sh @@ -0,0 +1,9 @@ +#!/bin/bash +# Little script to find possibly-misnamed CONFIG lines +for CONF in PICO_CONFIG PICO_CMAKE_CONFIG PICO_BUILD_DEFINE; do + for DEF in $(git grep "$CONF:" | sed -n "s/.*$CONF: \+\(\w\+\).*/\1/p"); do + if [ $(git grep "$DEF" | wc -l) -lt 2 ]; then + echo "$CONF: $DEF only appears once" + fi + done +done diff --git a/tools/extract_configs.py b/tools/extract_configs.py index e45ae7c28..0c6942aeb 100755 --- a/tools/extract_configs.py +++ b/tools/extract_configs.py @@ -38,7 +38,7 @@ -def ValidateAttrs(config_attrs): +def ValidateAttrs(config_attrs, file_path, linenum): _type = config_attrs.get('type', 'int') # Validate attrs @@ -192,26 +192,31 @@ def ValidateAttrs(config_attrs): if val in all_defines: resolved_defines[d] = all_defines[val] -for config_name in all_configs: +for config_name, config_obj in all_configs.items(): + file_path = os.path.join(scandir, config_obj['filename']) + linenum = config_obj['line_number'] - ValidateAttrs(all_configs[config_name]['attrs']) + ValidateAttrs(config_obj['attrs'], file_path, linenum) # Check that default values match up - if 'default' in all_configs[config_name]['attrs']: + if 'default' in config_obj['attrs']: + config_default = config_obj['attrs']['default'] if config_name in all_defines: - if all_configs[config_name]['attrs']['default'] not in all_defines[config_name] and (config_name not in resolved_defines or all_configs[config_name]['attrs']['default'] not in resolved_defines[config_name]): - if '/' in all_configs[config_name]['attrs']['default'] or ' ' in all_configs[config_name]['attrs']['default']: + defines_obj = all_defines[config_name] + if config_default not in defines_obj and (config_name not in resolved_defines or config_default not in resolved_defines[config_name]): + if '/' in config_default or ' ' in config_default: continue # There _may_ be multiple matching defines, but arbitrarily display just one in the error message - first_define_value = list(all_defines[config_name].keys())[0] - raise Exception('Found {} at {}:{} with a default of {}, but #define says {} (at {}:{})'.format(config_name, os.path.join(scandir, all_configs[config_name]['filename']), all_configs[config_name]['line_number'], all_configs[config_name]['attrs']['default'], first_define_value, all_defines[config_name][first_define_value][0], all_defines[config_name][first_define_value][1])) + first_define_value = list(defines_obj.keys())[0] + first_define_file_path, first_define_linenum = defines_obj[first_define_value] + raise Exception('Found {} at {}:{} with a default of {}, but #define says {} (at {}:{})'.format(config_name, file_path, linenum, config_default, first_define_value, first_define_file_path, first_define_linenum)) else: - raise Exception('Found {} at {}:{} with a default of {}, but no matching #define found'.format(config_name, os.path.join(scandir, all_configs[config_name]['filename']), all_configs[config_name]['line_number'], all_configs[config_name]['attrs']['default'])) + raise Exception('Found {} at {}:{} with a default of {}, but no matching #define found'.format(config_name, file_path, linenum, config_default)) with open(outfile, 'w', newline='') as csvfile: fieldnames = ('name', 'location', 'description', 'type') + tuple(sorted(all_attrs - set(['type']))) writer = csv.DictWriter(csvfile, fieldnames=fieldnames, extrasaction='ignore', dialect='excel-tab') writer.writeheader() - for config_name in sorted(all_configs): - writer.writerow({'name': config_name, 'location': '{}:{}'.format(all_configs[config_name]['filename'], all_configs[config_name]['line_number']), 'description': all_configs[config_name]['description'], **all_configs[config_name]['attrs']}) + for config_name, config_obj in sorted(all_configs.items()): + writer.writerow({'name': config_name, 'location': '{}:{}'.format(config_obj['filename'], config_obj['line_number']), 'description': config_obj['description'], **config_obj['attrs']})