diff --git a/.gitignore b/.gitignore index eebd94ef9..00b18a2f8 100644 --- a/.gitignore +++ b/.gitignore @@ -62,3 +62,10 @@ userPrefs.jsonc.mcp-session-bak # compiled .proto outputs are ephemeral build artifacts. build/fixtures/ bin/_generated/ + +# Build artifacts: anything compiling .o/.a outside .pio/ is accidental. +# Explicit exceptions for vendored binaries (Morse Micro mm-iot-esp32 SDK). +*.o +*.a +!lib/MorseWlan/lib/**/*.a +!lib/MorseWlan/src/*.mbin.o diff --git a/extra_scripts/esp32_extra.py b/extra_scripts/esp32_extra.py index f7698561a..753c9afda 100755 --- a/extra_scripts/esp32_extra.py +++ b/extra_scripts/esp32_extra.py @@ -82,5 +82,18 @@ if esp32_kind == "esp32": ] ) else: - # For newer ESP32 targets, using newlib nano works better. - env.Append(LINKFLAGS=["--specs=nano.specs", "-u", "_printf_float"]) + # For newer ESP32 targets, using newlib nano works better. Skip on + # variants that explicitly opt out — the IDF 5.1 framework override the + # HaLow variant uses already includes nano.specs, so re-adding it triggers + # a duplicate spec definition error at link time. + cppdefines = env.get("CPPDEFINES", []) + if not any( + (isinstance(d, str) and d == "MESHTASTIC_SKIP_NANO_SPECS") + or ( + isinstance(d, (list, tuple)) + and len(d) > 0 + and d[0] == "MESHTASTIC_SKIP_NANO_SPECS" + ) + for d in cppdefines + ): + env.Append(LINKFLAGS=["--specs=nano.specs", "-u", "_printf_float"]) diff --git a/lib/MorseWlan/extra_script.py b/lib/MorseWlan/extra_script.py new file mode 100644 index 000000000..76343e1b3 --- /dev/null +++ b/lib/MorseWlan/extra_script.py @@ -0,0 +1,28 @@ +""" +PlatformIO doesn't natively link .o files vendored inside a library directory. +The Morse Micro SDK ships the chip firmware (mm6108.mbin.o) and per-region BCF +(bcf_mf08651_us.mbin.o) as pre-built object files containing data sections. +This script appends them to LINKFLAGS so they land in the final ELF. + +US region only for now — when we add EU/JP/KR variants, gate the BCF here on a +build flag and pick the matching .o file. +""" + +import os + +Import("env", "projenv") + +LIB_DIR = os.path.join(env.subst("$PROJECT_DIR"), "lib", "MorseWlan") + +mbin_objects = [ + os.path.join(LIB_DIR, "src", "mm6108.mbin.o"), + os.path.join(LIB_DIR, "src", "bcf_mf08651_us.mbin.o"), +] + +# Only add objects that actually exist; missing ones surface as link errors, +# not silent corruption. +for obj in mbin_objects: + if not os.path.isfile(obj): + print("warning: mm-iot-esp32 blob missing: %s" % obj) + +env.Append(LINKFLAGS=mbin_objects) diff --git a/lib/MorseWlan/include/mbin.h b/lib/MorseWlan/include/mbin.h new file mode 100644 index 000000000..cba689b92 --- /dev/null +++ b/lib/MorseWlan/include/mbin.h @@ -0,0 +1,77 @@ +/* + * Copyright 2023 Morse Micro + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @defgroup MBIN Morse BINary Loader API + * + * This file defines the structure of the @c MBIN file. + * + * @{ + */ + +#pragma once + +#ifndef PACKED +/** Macro for the compiler packed attribute */ +#define PACKED __attribute__((packed)) +#endif + +/** Enumeration of TLV field types */ +enum mbin_tlv_types { + FIELD_TYPE_FW_TLV_BCF_ADDR = 0x0001, + FIELD_TYPE_MAGIC = 0x8000, + FIELD_TYPE_FW_SEGMENT = 0x8001, + FIELD_TYPE_FW_SEGMENT_DEFLATED = 0x8002, + FIELD_TYPE_BCF_BOARD_CONFIG = 0x8100, + FIELD_TYPE_BCF_REGDOM = 0x8101, + FIELD_TYPE_BCF_BOARD_DESC = 0x8102, + FIELD_TYPE_BCF_BUILD_VER = 0x8103, + FIELD_TYPE_SW_SEGMENT = 0x8201, + FIELD_TYPE_SW_SEGMENT_DEFLATED = 0x8202, + FIELD_TYPE_EOF = 0x8f00, + FIELD_TYPE_EOF_WITH_SIGNATURE = 0x8f01, +}; + +/** TLV header data structure. */ +struct PACKED mbin_tlv_hdr { + /** Type (see mbin_tlv_types). */ + uint16_t type; + /** Length of payload (excludes header). */ + uint16_t len; +}; + +/** Data header in a FIELD_TYPE_XX_SEGMENT field. */ +struct PACKED mbin_segment_hdr { + /** Destination base address at which the data should be loaded. */ + uint32_t base_address; +}; + +/** Data header in a FIELD_TYPE_XX_SEGMENT_DEFLATED field. */ +struct PACKED mbin_deflated_segment_hdr { + /** Destination base address at which the data should be loaded. */ + uint32_t base_address; + /** Size of deflated data, infer size of compressed data from TLV length */ + uint16_t chunk_size; + /** ZLib header */ + uint8_t zlib_header[2]; +}; + +/** Data header in a @c FIELD_TYPE_BCF_REGDOM field. */ +struct PACKED mbin_regdom_hdr { + /** Country code that this @c regdom applies to. */ + uint8_t country_code[2]; + /** Reserved */ + uint16_t reserved; +}; + +/** Expected value of the magic field for a SW image @c MMSW. */ +#define MBIN_SW_MAGIC_NUMBER (0x57534d4d) +/** Expected value of the magic field for a firmware image @c MMFW. */ +#define MBIN_FW_MAGIC_NUMBER (0x57464d4d) +/** Expected value of the magic field for a BCF @c MMBC. */ +#define MBIN_BCF_MAGIC_NUMBER (0x43424d4d) + +/** @} */ diff --git a/lib/MorseWlan/include/mm_mbedtls_config.h b/lib/MorseWlan/include/mm_mbedtls_config.h new file mode 100644 index 000000000..2531180c2 --- /dev/null +++ b/lib/MorseWlan/include/mm_mbedtls_config.h @@ -0,0 +1,124 @@ +/* + * Copyright 2024 Morse Micro + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* + * This file should be included from the application mbedtls_config.h file to ensure that + * the mbedTLS features necessary for morselib functionality are enabled. + * + * It is recommended to include this file at the _end_ of the application mbedtls_config.h file + * to avoid redefinition of macros. + */ + +/* Cipher modes */ +#ifndef MBEDTLS_CIPHER_MODE_CBC +#define MBEDTLS_CIPHER_MODE_CBC +#endif + +#ifndef MBEDTLS_CIPHER_MODE_CTR +#define MBEDTLS_CIPHER_MODE_CTR +#endif + +/* EC curves */ +#ifndef MBEDTLS_ECP_DP_SECP256R1_ENABLED +#define MBEDTLS_ECP_DP_SECP256R1_ENABLED +#endif + +#ifndef MBEDTLS_ECP_DP_SECP384R1_ENABLED +#define MBEDTLS_ECP_DP_SECP384R1_ENABLED +#endif + +#ifndef MBEDTLS_ECP_DP_SECP521R1_ENABLED +#define MBEDTLS_ECP_DP_SECP521R1_ENABLED +#endif + +/* Features */ +#ifndef MBEDTLS_AES_C +#define MBEDTLS_AES_C +#endif + +#ifndef MBEDTLS_ASN1_PARSE_C +#define MBEDTLS_ASN1_PARSE_C +#endif + +#ifndef MBEDTLS_ASN1_WRITE_C +#define MBEDTLS_ASN1_WRITE_C +#endif + +#ifndef MBEDTLS_BIGNUM_C +#define MBEDTLS_BIGNUM_C +#endif + +#ifndef MBEDTLS_CIPHER_C +#define MBEDTLS_CIPHER_C +#endif + +#ifndef MBEDTLS_CMAC_C +#define MBEDTLS_CMAC_C +#endif + +#ifndef MBEDTLS_CTR_DRBG_C +#define MBEDTLS_CTR_DRBG_C +#endif + +#ifndef MBEDTLS_ECDH_C +#define MBEDTLS_ECDH_C +#endif + +#ifndef MBEDTLS_ECP_C +#define MBEDTLS_ECP_C +#endif + +#ifndef MBEDTLS_ENTROPY_C +#define MBEDTLS_ENTROPY_C +#endif + +#ifndef MBEDTLS_MD_C +#define MBEDTLS_MD_C +#endif + +#ifndef MBEDTLS_NIST_KW_C +#define MBEDTLS_NIST_KW_C +#endif + +#ifndef MBEDTLS_OID_C +#define MBEDTLS_OID_C +#endif + +#ifndef MBEDTLS_PK_C +#define MBEDTLS_PK_C +#endif + +#ifndef MBEDTLS_PK_PARSE_C +#define MBEDTLS_PK_PARSE_C +#endif + +#ifndef MBEDTLS_PK_WRITE_C +#define MBEDTLS_PK_WRITE_C +#endif + +#ifndef MBEDTLS_PKCS5_C +#define MBEDTLS_PKCS5_C +#endif + +#ifndef MBEDTLS_SHA1_C +#define MBEDTLS_SHA1_C +#endif + +#ifndef MBEDTLS_SHA224_C +#define MBEDTLS_SHA224_C +#endif + +#ifndef MBEDTLS_SHA256_C +#define MBEDTLS_SHA256_C +#endif + +#ifndef MBEDTLS_SHA384_C +#define MBEDTLS_SHA384_C +#endif + +#ifndef MBEDTLS_SHA512_C +#define MBEDTLS_SHA512_C +#endif diff --git a/lib/MorseWlan/include/mmhal.h b/lib/MorseWlan/include/mmhal.h new file mode 100644 index 000000000..a2e9a3c70 --- /dev/null +++ b/lib/MorseWlan/include/mmhal.h @@ -0,0 +1,331 @@ +/* + * Copyright 2021-2023 Morse Micro + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @defgroup MMHAL Morse Micro Hardware Abstraction Layer (mmhal) API + * + * This API provides abstraction from the underlying hardware/BSP. + * + * @{ + */ + +#pragma once + +#include "mmhal_flash.h" +#include "mmhal_wlan.h" +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** Initialization before RTOS scheduler starts. */ +void mmhal_early_init(void); + +/** Initialization after RTOS scheduler started. */ +void mmhal_init(void); + +/** Enumeration of ISR states (i.e., whether in ISR or not). */ +enum mmhal_isr_state { + MMHAL_NOT_IN_ISR, /**< The function was not executed from ISR context. */ + MMHAL_IN_ISR, /**< The function was executed from ISR context. */ + MMHAL_ISR_STATE_UNKNOWN, /**< The HAL does not support checking ISR state. */ +}; + +/** + * Enumeration for different LED's on the board. + * + * @note Some of these LED's may not be available on all boards and some of these values + * may refer to the same LED. + */ +enum mmhal_led_id { LED_RED, LED_GREEN, LED_BLUE, LED_WHITE }; + +/** Enumeration of MCU sleep state. */ +enum mmhal_sleep_state { + MMHAL_SLEEP_DISABLED, /**< Disable MCU sleep. */ + MMHAL_SLEEP_SHALLOW, /**< MCU to enter shallow sleep. */ + MMHAL_SLEEP_DEEP, /**< MCU can enter deep sleep. */ +}; + +/** A value of 0 turns OFF an LED */ +#define LED_OFF 0 + +/** + * A value of 255 turns an LED ON fully. + * + * Some boards support varying an LED's brightness. For these boards a value between 1 and 255 + * will result in proportionately varying levels of brightness. LED's that do not have a + * brightness control feature will just turn ON fully for any non zero value. + */ +#define LED_ON 255 + +/** + * Get the current ISR state (i.e., whether in ISR or not). + * + * @returns the current ISR state, or @c MMHAL_ISR_STATE_UNKNOWN if the HAL does not support + * checking ISR state. + */ +enum mmhal_isr_state mmhal_get_isr_state(void); + +/** + * Write to the debug log. + * + * It is assumed the caller will have mechanisms in place to prevent concurrent access. + * + * @param data Buffer containing data to write. + * @param len Length of data in buffer. + */ +void mmhal_log_write(const uint8_t *data, size_t len); + +/** + * Flush the debug log before returning. + * + * @warning Implementations of this function must support being invoked with interrupts disabled. + */ +void mmhal_log_flush(void); + +/** + * Generate a random 32 bit integer within the given range. + * + * @param min Minimum value (inclusive). + * @param max Maximum value (inclusive). + * + * @returns a randomly generated integer (min <= i <= max). + */ +uint32_t mmhal_random_u32(uint32_t min, uint32_t max); + +/** Reset the microcontroller. */ +void mmhal_reset(void); + +/** + * Set the specified LED to the requested level. Do nothing if the requested LED does not exist. + * + * @param led The LED to set, if the platform supports it. See @ref mmhal_led_id + * @param level The level to set it to. 0 means OFF and non-zero means ON. If the platform supports + * brightness levels then 255 is full. Defines @ref LED_ON and @ref LED_OFF are + * provided for ease of use. + */ +void mmhal_set_led(uint8_t led, uint8_t level); + +/** + * Set the error LED to the requested state. + * + * @note This function is called by the bootloader and so needs to do all the initialization + * required to set the LED's as the bootloader does not use the regular BSP initialization + * located in main.c for configuring @c GPIO's and setting clock gates as required. + * + * @param state Set to true if the LED needs to be turned on, + * or false if the led needs to be turned off. + */ +void mmhal_set_error_led(bool state); + +/** + * Enumeration for buttons on the board. + * + * @note The support for each button is platform dependent. + */ +enum mmhal_button_id { BUTTON_ID_USER0 }; + +/** + * Enumeration for button states + */ +enum mmhal_button_state { BUTTON_RELEASED, BUTTON_PRESSED }; + +/** Button state callback function prototype. */ +typedef void (*mmhal_button_state_cb_t)(enum mmhal_button_id button_id, enum mmhal_button_state button_state); + +/** + * Registers a callback handler for button state changes. + * + * @note The callback will be executed in an Interrupt Service Routine context + * + * @param button_id The button whose state should be reported to the callback + * @param button_state_cb The function to call on button state change, or NULL to disable. + * @returns True if the callback is registered successfully, False if not supported + */ +bool mmhal_set_button_callback(enum mmhal_button_id button_id, mmhal_button_state_cb_t button_state_cb); + +/** + * Returns the registered callback handler for button state changes. + * + * @param button_id The button whose callback should be returned + * @returns The registered callback or NULL if no callback registered + */ +mmhal_button_state_cb_t mmhal_get_button_callback(enum mmhal_button_id button_id); + +/** + * Reads the state of the specified button. + * + * @param button_id The button state to read + * @returns The current button state, or BUTTON_RELEASED if not supported + */ +enum mmhal_button_state mmhal_get_button(enum mmhal_button_id button_id); + +/** + * Reads information that can be used to identify the hardware platform, such as + * hardware ID and version number, in the form of a user readable string. + * + * This function attempts to detect the correct hardware and version. + * The actual means of detecting the correct hardware and version will vary from + * implementation to implementation and may use means such as identification + * information stored in EEPROM or devices detected on GPIO, SPI or I2C interfaces. + * Returns false if the hardware could not be identified correctly. + * + * @param version_buffer The pre-allocated buffer to return the hardware ID and version in. + * @param version_buffer_length The length of the pre-allocated buffer. + * @returns True if the hardware was correctly identified and returned. + */ +bool mmhal_get_hardware_version(char *version_buffer, size_t version_buffer_length); + +/** + * Macro to simplify debug pin mask/value definition. + * + * @param _pin_num The pin number to set in the mask. Must be 0-31 (inclusive). + * + * Example: + * + * mmhal_set_debug_pins(MMHAL_DEBUG_PIN(0), MMHAL_DEBUG_PIN(0)); + */ +#define MMHAL_DEBUG_PIN(_pin_num) (1ul << (_pin_num)) + +/** Bit mask with all debug pins selected. */ +#define MMHAL_ALL_DEBUG_PINS (UINT32_MAX) + +/** + * Set the value one or more debug pins. + * + * Each platform can define up to 32 GPIOs for application use. If a GPIO is not supported + * by a platform then attempts to set it will be silently ignored. These GPIOs are intended + * for debug/test purposes. + * + * @param mask Mask of GPIOs to modify. Each bit in this mask that is set will result in + * the corresponding GPIO being being set to the corresponding value given in + * @p values. + * @param values Bit field, where each bit corresponds to a GPIO, specifying the direction + * to drive each GPIO with 1 meaning drive high and 0 meaning drive low. + * Only GPIOs with the corresponding bit set in @p mask will be modified. + * + * @sa MM_DEBUG_PIN_MASK + */ +void mmhal_set_debug_pins(uint32_t mask, uint32_t values); + +/** + * Returns the time of day as set in the RTC. + * Time is in UTC. + * + * @return Epoch time (seconds since 1 Jan 1970) or 0 on failure. + */ +time_t mmhal_get_time(); + +/** + * Sets the RTC to the specified time in UTC. + * + * @note While Unix epoch time supports years from 1970, most Real Time Clock + * chips support years from 2000 only as they store the year as years + * from 2000. So do not attempt to set any years below 2000 as this could cause + * the year to wrap around to an unreasonably high value. Definitely do not do: + * @code + * mmhal_set_time(0); + * @endcode + * + * @param epoch Time in Unix epoch time (seconds since 1 Jan 1970). + */ +void mmhal_set_time(time_t epoch); + +/** + * Function to prepare MCU to enter sleep. + * This will halt the timer that generates the RTOS tick. + * + * @param expected_idle_time_ms Expected time to sleep in milliseconds. + * + * @returns the type of sleep permitted by the current system state. + */ +enum mmhal_sleep_state mmhal_sleep_prepare(uint32_t expected_idle_time_ms); + +/** + * Function to enter MCU sleep. + * + * @param sleep_state Sleep state to enter into. + * @param expected_idle_time_ms Expected time to sleep in milliseconds. + * + * @returns Elapsed sleep time in milliseconds. + */ +uint32_t mmhal_sleep(enum mmhal_sleep_state sleep_state, uint32_t expected_idle_time_ms); + +/** + * Function to abort the MCU sleep state. + * + * @note This must only be invoked after @ref mmhal_sleep_prepare() + * and before @ref mmhal_sleep(). + * + * @param sleep_state Sleep state to abort. + */ +void mmhal_sleep_abort(enum mmhal_sleep_state sleep_state); + +/** + * Function to cleanup on exit from the MCU sleep state. + */ +void mmhal_sleep_cleanup(void); + +/** Enumeration of veto_id ranges for use with @ref mmhal_set_deep_sleep_veto() and + * @ref mmhal_clear_deep_sleep_veto(). */ +enum mmhal_veto_id { + /** Start of deep sleep veto ID range that is available for application use. */ + MMHAL_VETO_ID_APP_MIN = 0, + /** End of deep sleep veto ID range that is available for application use. */ + MMHAL_VETO_ID_APP_MAX = 7, + /** Start of deep sleep veto ID range that is available for HAL use. */ + MMHAL_VETO_ID_HAL_MIN = 8, + /** End of deep sleep veto ID range that is available for HAL use. */ + MMHAL_VETO_ID_HAL_MAX = 15, + /** Start of deep sleep veto ID range that is allocated for morselib use. Note that this + * must not be changed as it is built into morselib. */ + MMHAL_VETO_ID_MORSELIB_MIN = 16, + /** End of deep sleep veto ID range that is allocated for morselib use. Note that this must not + * be changed as it is built into morselib. */ + MMHAL_VETO_ID_MORSELIB_MAX = 19, + /** Deep sleep veto ID for data-link subsystem. */ + MMHAL_VETO_ID_DATALINK = 20, + /** Deep sleep veto ID allocated to @ref MMCONFIG. */ + MMHAL_VETO_ID_MMCONFIG = 21, + /** Start of deep sleep veto ID range reserved for future use. */ + MMHAL_VETO_ID_RESERVED_MIN = 22, + /** End of deep sleep veto ID range reserved for future use. */ + MMHAL_VETO_ID_RESERVED_MAX = 31, +}; + +/** + * Sets a deep sleep veto that will prevent the device from entering deep sleep. The device + * will not enter deep sleep until there are no vetoes remaining. This veto can be cleared + * by a call to @ref mmhal_clear_deep_sleep_veto() with the same veto_id. + * + * Up to 32 vetoes are supported (ID numbers 0-31). Each veto should be used exclusively by + * a given aspect of the system (e.g., to prevent deep sleep when a DMA transfer is in progress, + * or to prevent deep sleep when there is log data buffered for transmit, etc.). + * + * @param veto_id The veto identifier. Valid values are 0-31, and these are split up into ranges + * for use by different subsystems -- see @ref mmhal_veto_id. + */ +void mmhal_set_deep_sleep_veto(uint8_t veto_id); + +/** + * Clears a deep sleep veto that was preventing the device from entering deep sleep (see + * @ref mmhal_set_deep_sleep_veto()). If the given veto was not already set then this has + * no effect. + * + * @param veto_id The veto identifier. Valid values are 0-31, and these are split up into ranges + * for use by different subsystems -- see @ref mmhal_veto_id. + */ +void mmhal_clear_deep_sleep_veto(uint8_t veto_id); + +#ifdef __cplusplus +} +#endif + +/** @} */ diff --git a/lib/MorseWlan/include/mmhal_flash.h b/lib/MorseWlan/include/mmhal_flash.h new file mode 100644 index 000000000..1b667b631 --- /dev/null +++ b/lib/MorseWlan/include/mmhal_flash.h @@ -0,0 +1,145 @@ +/* + * Copyright 2021-2023 Morse Micro + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @ingroup MMHAL Morse Micro Flash Hardware Abstraction Layer (mmhal_flash) API + * + * This API provides abstraction from the underlying flash hardware/. + * + * @{ + */ + +#pragma once + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * This is the value erased flash bytes are set to. This shall be @c 0xFF as this is the + * value that hardware flash erases to. + */ +#define MMHAL_FLASH_ERASE_VALUE 0xFF + +/** LittleFS configuration structure. Include @c lfs.h for definition. */ +struct lfs_config; + +/** + * Flash partition configuration structure + * + * This should be initialized using @c MMHAL_FLASH_PARTITION_CONFIG_DEFAULT. + * For example: + * + * @code{.c} + * struct mmhal_flash_partition_config partition = MMHAL_FLASH_PARTITION_CONFIG_DEFAULT; + * @endcode + */ +struct mmhal_flash_partition_config { + /** The start address of the partition, this may be a physical address + * or a relative address depending on implementation + */ + uint32_t partition_start; + + /** The size of the partition */ + uint32_t partition_size; + + /** + * If true, then the partition is not memory mapped and cannot be directly accessed + * at the physical @c partition_start address + */ + bool not_memory_mapped; +}; + +/** Initial values for @ref mmhal_flash_partition_config. */ +#define MMHAL_FLASH_PARTITION_CONFIG_DEFAULT \ + { \ + 0, 0, false \ + } + +/** + * Get MMCONFIG flash partition configuration. + * + * MMCONFIG initialization is done by @c mmconfig_init() in @c mmconfig.c. + * which in turn calls this function to fetch the partition configuration for config store + * from the HAL layer. If config store is not supported by the platform then we just + * return NULL. This function returns a static pointer to + * @c struct @c mmhal_flash_partition_config. + * + * @return A static pointer to the partition config for MMCONFIG, or NULL if not supported. + */ +const struct mmhal_flash_partition_config *mmhal_get_mmconfig_partition(void); + +/** + * Erases a block of Flash pointed to by the block_address. + * + * The block address may be anywhere within the block to erase. The entire block gets erased. + * Once erased all bytes in the block shall be @c MMHAL_FLASH_ERASE_VALUE (@c 0xFF). + * + * @param block_address The address of the block of Flash to erase. + * @return 0 on success, negative number on failure + */ +int mmhal_flash_erase(uint32_t block_address); + +/** + * Returns the size of the Flash block at the specified address. + * + * @param block_address The address of the Flash block. + * @return The size of the Flash block in bytes. + * Returns 0 if an invalid address is specified. + */ +uint32_t mmhal_flash_getblocksize(uint32_t block_address); + +/** + * Read a block of data from the specified Flash address into the buffer. + * + * @param read_address The address in Flash to read from. + * @param buf The buffer to read into. + * @param size The number of bytes to read. + * @return 0 on success, or a negative number on failure. + */ +int mmhal_flash_read(uint32_t read_address, uint8_t *buf, size_t size); + +/** + * Write a block of data to the specified Flash address. + * + * There is no alignment or minimum size requirement. This function will + * take care of aligning the data and merging with existing Flash contents. + * The Flash block is not erased, it is up to the application to determine + * if the block needs to be erased before programming. + * + * @param write_address The address in Flash to write to. + * @param data A pointer to the block of data to write. + * @param size The number of bytes to write. + * @return 0 on success, or a negative number on failure. + */ +int mmhal_flash_write(uint32_t write_address, const uint8_t *data, size_t size); + +/** + * Get LittleFS configuration. + * + * LittleFS initialization is done by @c littlefs_init() in @c mmosal_shim_fileio.c. + * which in turn calls this function to fetch the hardware configuration for LittleFS + * from the HAL layer. The LittleFS configuration will vary from platform to platform. + * If LittleFS is not supported by the platform then we just return NULL. This function + * returns a static pointer to @c struct @c lfs_config which is defined in @c lfs.h. + * + * See @c mmhal_littlefs.c for the full HAL layer implementation for your platform. + * See @c mmosal_shim_fileio.c for the @c libc shims for LittleFS. + * See @c README.md in the @c src/littlefs folder for detailed information on LittleFS. + * + * @return A static pointer to the LittleFS config structure, or NULL if not supported. + */ +const struct lfs_config *mmhal_get_littlefs_config(void); + +#ifdef __cplusplus +} +#endif + +/** @} */ diff --git a/lib/MorseWlan/include/mmhal_uart.h b/lib/MorseWlan/include/mmhal_uart.h new file mode 100644 index 000000000..fab7faf64 --- /dev/null +++ b/lib/MorseWlan/include/mmhal_uart.h @@ -0,0 +1,89 @@ +/* + * Copyright 2024 Morse Micro + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @ingroup MMHAL + * @defgroup MMHAL_UART Morse Micro Abstraction Layer API for UART + * + * This provides an abstraction layer for a UART. This is used by MM-IoT-SDK example + * applications. + * + * This is a very simple API and leaves UART configuration to the HAL. + * + * @{ + */ + +#pragma once + +#include "mmhal.h" +#include "mmosal.h" +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Function type for UART RX callback. + * + * @note The UART HAL must not invoke this function from interrupt context. However, the + * implementation should not block for long periods of time or received data may be lost. + * + * @param data The received data. + * @param length Length of the received data. + * @param arg Opaque argument (as passed in to @ref mmhal_uart_init()). + */ +typedef void (*mmhal_uart_rx_cb_t)(const uint8_t *data, size_t length, void *arg); + +/** + * Initialize the UART HAL and perform any setup necessary. + * + * @param rx_cb Optional callback to be invoked on receive (may be NULL). + * This callback may be invoked from interrupt context so should return + * quickly. + * @param rx_cb_arg Optional opaque argument to be passed to the RX callback. May be NULL. + */ +void mmhal_uart_init(mmhal_uart_rx_cb_t rx_cb, void *rx_cb_arg); + +/** + * Deinitialize the UART HAL, and disable the UART. + */ +void mmhal_uart_deinit(void); + +/** + * Transmit data on the UART. This will block until all data is buffered for transmit (but may + * return before transmission has completed). + * + * @param data Data to transmit. + * @param length Length of @p data. + */ +void mmhal_uart_tx(const uint8_t *data, size_t length); + +/** Enumeration of deep sleep modes for the UART HAL. */ +enum mmhal_uart_deep_sleep_mode { + /** Deep sleep mode is disabled. */ + MMHAL_UART_DEEP_SLEEP_DISABLED, + /** Enable deep sleep until activity occurs on data-link transport. */ + MMHAL_UART_DEEP_SLEEP_ONE_SHOT, +}; + +/** + * Set the deep sleep mode for the UART. See @ref mmhal_uart_deep_sleep_mode for possible deep + * sleep modes. Note that a given platform may not support all modes. + * + * @param mode The deep sleep mode to set. + * + * @returns true if the mode was set successfully; false on failure (e.g., unsupported mode). + */ +bool mmhal_uart_set_deep_sleep_mode(enum mmhal_uart_deep_sleep_mode mode); + +#ifdef __cplusplus +} +#endif + +/** @} */ diff --git a/lib/MorseWlan/include/mmhal_wlan.h b/lib/MorseWlan/include/mmhal_wlan.h new file mode 100644 index 000000000..3d72d7286 --- /dev/null +++ b/lib/MorseWlan/include/mmhal_wlan.h @@ -0,0 +1,640 @@ +/* + * Copyright 2023 Morse Micro + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @ingroup MMHAL + * @defgroup MMHAL_WLAN WLAN HAL + * + * API for communicating with the WLAN transceiver. + * + * There are different interfaces supported for communicating with the transceiver: + * + * * @ref MMHAL_WLAN_SDIO + * * @ref MMHAL_WLAN_SPI + * + * @warning These functions shall not be called directly by the end application they are for use + * by Morselib. + * + * @{ + */ + +#pragma once + +#include "mmpkt.h" +#include "mmwlan.h" +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Function prototype for interrupt handler callbacks. + */ +typedef void (*mmhal_irq_handler_t)(void); + +/** + * Initialize the WLAN HAL. + * + * Things to do here may include: + * * Enable SPI peripheral + * * Configure GPIOs + * * Enable power to the Morse Micro transceiver + * + * @note If enabling power for the Morse Micro transceiver in this function you may need to add a + * blocking delay to allow the power rail to stabilize. This is hardware specific so is not + * accounted for in the calling function. + */ +void mmhal_wlan_init(void); + +/** + * Deinitialize the WLAN HAL. + * + * Things to do here may include: + * * Disable SPI peripheral + * * Disable GPIOs + * * Disable power to the Morse Micro transceiver + */ +void mmhal_wlan_deinit(void); + +/** + * Get MAC address override. + * + * This function allows the HAL to override the MAC address to be used by the device. The + * MAC address override should be written to @p mac_addr. If no override is required then + * @p mac_addr should be left untouched. + * + * @param[out] mac_addr Location where the MAC address will be stored. This will be initialized + * to zero the first time this function is invoked, and to the previously + * configured MAC address on subsequent invocations. + */ +void mmhal_read_mac_addr(uint8_t *mac_addr); + +/** + * Assert the WLAN wake pin. + */ +void mmhal_wlan_wake_assert(void); + +/** + * Deassert the WLAN wake pin. + */ +void mmhal_wlan_wake_deassert(void); + +/** + * Tests whether the busy pin is currently asserted. + * + * @note This is whether it is logically asserted and does not necessarily + * represent the level of the GPIO pin. + * + * @returns @c true if asserted, else @c false. + */ +bool mmhal_wlan_busy_is_asserted(void); + +/** + * Register a handler for busy interrupts. + * + * @param handler The handler to register. + */ +void mmhal_wlan_register_busy_irq_handler(mmhal_irq_handler_t handler); + +/** + * Sets whether the busy interrupt is enabled. + * + * @warning The interrupt handler function must be configured using + * mmhal_wlan_register_busy_irq_handler() before enabling the interrupt. + * + * @param enabled @c true to enable or @c false to disable. + */ +void mmhal_wlan_set_busy_irq_enabled(bool enabled); + +/** + * Read-only buffer data structure. + * + * The design of this data structure allows the buffer to exist either in statically or + * dynamically allocated memory. + * + * For statically allocated memory, the field @c free_cb may be set to @c NULL and @c free_arg + * ignored. For example: + * + * @code{.c} + * const uint8_t some_data[] = { 0x00, 0x01, 0x02 }; + * + * void put_some_data_into_buf(struct mmhal_robuf *robuf) + * { + * robuf->buf = some_data; + * robuf->len = sizeof(some_data); + * robuf->free_cb = NULL; + * } + * @endcode + * + * For dynamically allocated memory, the field @c free_cb is set to the appropriate function to + * free the buffer and @c free_arg is an opaque argument to the free function. This approach might + * be used, for example, when reading into a temporary buffer from storage that is not memory + * mapped. For example: + * + * @code{.c} + * #define SOME_DATA_MAXLEN (64) + * + * void put_some_data_into_buf(struct mmhal_robuf *robuf) + * { + * uint8_t *buf = malloc(SOME_DATA_MAXLEN); + * robuf->buf = buf; + * if (robuf->buf == NULL) + * return; + * + * // HERE: copy data into buf and set robuf->len as appropriate + * + * robuf->free_cb = free; + * robuf->free_arg = buf; + * } + * @endcode + * + */ +struct mmhal_robuf { + /** Pointer to the start of the read-only buffer. May be NULL only if @c len is zero. */ + const uint8_t *buf; + /** Length of the buffer contents. */ + uint32_t len; + /** + * Optional callback to be invoked by the consumer to release the buffer when it is + * no longer required. If not required, set to @c NULL. + * + * @note The values of @c buf and @c len in this structure may be modified before + * @c free_cb() is invoked. However, the value of @c free_arg will be passed + * to @c free_cb(). + */ + void (*free_cb)(void *arg); + /** Optional argument to @c free_cb. Ignored if @c free_cb is @c NULL. */ + void *free_arg; +}; + +/** Minimum length of data to be returned by @ref mmhal_wlan_read_bcf_file() and + * @ref mmhal_wlan_read_fw_file(). */ +#define MMHAL_WLAN_FW_BCF_MIN_READ_LENGTH (4) + +/** + * Retrieves the content of the Morse Micro Board Configuration File and places it into the given + * buffer. + * + * @param offset Offset from which to start reading the bcf + * @param requested_len Length of data we would like to read. The length of the data returned + * by this function may be less than @p requested_len, but must be at + * least @ref MMHAL_WLAN_FW_BCF_MIN_READ_LENGTH. + * @param robuf Read-only buffer data structure to be filled out by this function. + * + * @note On error, this function should set @c robuf->buf to @c NULL. + * @note The caller must zero @p robuf before invoking the function. + * @note The BCF must be in mbin format. + * + * @warning The caller is responsible for checking @c robuf->free_cb and calling when the buffer is + * no longer required. Ignored if @c robuf->free_cb is @c NULL. + */ +void mmhal_wlan_read_bcf_file(uint32_t offset, uint32_t requested_len, struct mmhal_robuf *robuf); + +/** + * Retrieves the content of the Morse Micro Chip Firmware and places it into the given buffer. + * + * @param offset Offset from which to start reading the bcf + * @param requested_len Length of data we would like to read. The length of the data returned + * by this function may be less than @p requested_len, but must be at + * least @ref MMHAL_WLAN_FW_BCF_MIN_READ_LENGTH. + * @param robuf Read-only buffer data structure to be filled out by this function. + * + * @note On error, this function should set @c robuf->buf to @c NULL. + * @note The caller must zero @p robuf before invoking the function. + * @note The firmware must be in mbin format. + * + * @warning The caller is responsible for checking @c robuf->free_cb and calling when the buffer + * is no longer required. Ignored if @c robuf->free_cb is @c NULL. + */ +void mmhal_wlan_read_fw_file(uint32_t offset, uint32_t requested_len, struct mmhal_robuf *robuf); + +/** + * @defgroup MMHAL_WLAN_SPI WLAN HAL API for SPI interface + * + * API for communicating with the WLAN transceiver over an SPI interface. + * + * @note These functions should only be implemented if using a SPI interface. They are not + * required when using the @ref MMHAL_WLAN_SDIO. + * + * @warning These functions shall not be called directly by the end application they are for use + * by Morselib. + * + * @{ + */ + +/** + * Assert the WLAN SPI chip select pin. + */ +void mmhal_wlan_spi_cs_assert(void); + +/** + * Deassert the WLAN SPI chip select pin. + */ +void mmhal_wlan_spi_cs_deassert(void); + +/** + * Simultaneously read and write on the SPI bus. + * + * @param data Data to be written. + * + * @return the value that was read. + */ +uint8_t mmhal_wlan_spi_rw(uint8_t data); + +/** + * Receive multiple octets of data from SPI bus. + * + * @param buf The buffer to receive into. + * @param len The number of octets to receive. + */ +void mmhal_wlan_spi_read_buf(uint8_t *buf, unsigned len); + +/** + * Transmit multiple octets of data to SPI bus. + * + * @param buf The buffer to transmit from. + * @param len The number of octets to transmit. + * + * @note Blocks until transfer complete. + */ +void mmhal_wlan_spi_write_buf(const uint8_t *buf, unsigned len); + +/** + * Hard reset the chip by asserting and then releasing the reset pin. + * + * @warning This function must return with the chip in a fully booted state. i.e only return once + * the reset_n line has been high for at least the boot time specified in the data sheet. + * Failure to do so may lead to undefined behavior. + */ +void mmhal_wlan_hard_reset(void); + +/** + * Invoked by the driver to check whether the external crystal initialization sequence is required. + * + * Implementation of this function is optional if the external crystal initialization sequence + * is not required. If this function is not implemented then the external crystal initialization + * sequence will be disabled. Refer to the data sheet for your module to check if this initialization + * is required. + * + * @returns true if the external crystal initialization sequence is required else false. + */ +bool mmhal_wlan_ext_xtal_init_is_required(void); + +/** + * Issue the training sequence required to put the transceiver into SPI mode. + */ +void mmhal_wlan_send_training_seq(void); + +/** + * Register a handler for SPI interrupts. + * + * @param handler The handler to register. + */ +void mmhal_wlan_register_spi_irq_handler(mmhal_irq_handler_t handler); + +/** + * Sets whether the SPI interrupt is enabled. + * + * @warning The interrupt handler function must be configured using + * @ref mmhal_wlan_register_spi_irq_handler() before enabling the interrupt. + * + * @param enabled @c true to enable or @c false to disable. + */ +void mmhal_wlan_set_spi_irq_enabled(bool enabled); + +/** + * Tests whether the SPI interrupt pin is currently asserted. + * + * @note This is whether it is logically asserted and does not necessarily + * represent the level of the GPIO pin. + * + * @returns @c true if asserted, else @c false. + */ +bool mmhal_wlan_spi_irq_is_asserted(void); + +/** + * Clear the SPI IRQ. + * + * @deprecated Do not invoke this function because it is deprecated and will be removed from the + * mmhal API in a future release. This function need not be implemented as a weak + * stub is used in morselib. + */ +void mmhal_wlan_clear_spi_irq(void); + +/** @} */ + +/** + * @defgroup MMHAL_WLAN_PKT WLAN HAL API for packet memory allocation + * + * API for allocating and freeing packet memory. + * + * @warning These functions shall not be called directly by the end application they are for use + * by Morselib. + * + * @{ + */ + +/** + * Flow control callback that can be invoked by the transmit packet memory manager to pause + * and resume the data path in response to resource availability. + * + * @param state Current flow control state. + */ +typedef void (*mmhal_wlan_pktmem_tx_flow_control_cb_t)(enum mmwlan_tx_flow_control_state state); + +/** Initialization arguments passed to @ref mmhal_wlan_pktmem_init(). */ +struct mmhal_wlan_pktmem_init_args { + /** Flow control callback that can be used by the transmit packet memory manager. */ + mmhal_wlan_pktmem_tx_flow_control_cb_t tx_flow_control_cb; +}; + +/** + * Invoked by the driver to initialize the packet memory in the HAL. + * + * @param args Initialization arguments. + */ +void mmhal_wlan_pktmem_init(struct mmhal_wlan_pktmem_init_args *args); + +/** + * Invoked by the driver to deinitialize the packet memory in the HAL. + * + * This can free reserved memory and check for memory leaks. + */ +void mmhal_wlan_pktmem_deinit(void); + +/** + * Enumeration of packet classes used by @ref mmhal_wlan_alloc_mmpkt_for_tx(). + * These definitions must match the corresponding values in @c mmdrv_pkt_class. + */ +enum mmhal_wlan_pkt_class { + MMHAL_WLAN_PKT_DATA_TID0, /**< Data TID0 */ + MMHAL_WLAN_PKT_DATA_TID1, /**< Data TID1 */ + MMHAL_WLAN_PKT_DATA_TID2, /**< Data TID2 */ + MMHAL_WLAN_PKT_DATA_TID3, /**< Data TID3 */ + MMHAL_WLAN_PKT_DATA_TID4, /**< Data TID4 */ + MMHAL_WLAN_PKT_DATA_TID5, /**< Data TID5 */ + MMHAL_WLAN_PKT_DATA_TID6, /**< Data TID6 */ + MMHAL_WLAN_PKT_DATA_TID7, /**< Data TID7 */ + MMHAL_WLAN_PKT_MANAGEMENT, /**< 802.11 Management and other important frames */ + MMHAL_WLAN_PKT_COMMAND, /**< Commands from driver to chip */ +}; + +/** + * Allocates an mmpkt for transmission. + * + * When the pool of mmpkt buffers available for TX is exhausted, the HAL should pause the TX + * path using the flow control callback that was registered when @ref mmhal_wlan_pktmem_init() + * was invoked. Similarly, when the buffers become available again (and assuming the TX path is + * not otherwise blocked) the driver should unpause the TX path. + * + * @param pkt_class The class of packet (to allow for prioritization). + * @param space_at_start Amount of space to allocate at start of mmpkt (for prepend). + * @param space_at_end Amount of space to allocate at end of mmpkt (for append). + * @param metadata_length Amount of space to allocate for metadata (used internally by the + * Morse driver). + * + * @returns a pointer to the allocated packet on success or @c NULL on allocation failure. + */ +struct mmpkt *mmhal_wlan_alloc_mmpkt_for_tx(uint8_t pkt_class, uint32_t space_at_start, uint32_t space_at_end, + uint32_t metadata_length); + +/** + * Allocates an mmpkt for reception. + * + * @param capacity Amount of space to allocate for data. + * @param metadata_length Amount of space to allocate for metadata (used internally by the + * Morse driver). + * + * @returns a pointer to the allocated packet on success or @c NULL on allocation failure. + */ +struct mmpkt *mmhal_wlan_alloc_mmpkt_for_rx(uint32_t capacity, uint32_t metadata_length); + +/** @} */ + +/** + * @defgroup MMHAL_WLAN_SDIO WLAN HAL API for SDIO interface + * + * API for communicating with the WLAN transceiver over an SDIO interface + * + * @warning These functions shall not be called directly by the end application they are for use + * by Morselib. + * + * @{ + */ + +/** Enumeration of error codes that may be returned from @c mmhal_wlan_sdio_XXX() functions. */ +enum mmhal_sdio_error_codes { + /** Invalid argument given (e.g., incorrect buffer alignment). */ + MMHAL_SDIO_INVALID_ARGUMENT = -1, + /** Local hardware error (e.g., issue with SDIO controller). */ + MMHAL_SDIO_HW_ERROR = -2, + /** Timeout executing SDIO command. */ + MMHAL_SDIO_CMD_TIMEOUT = -3, + /** CRC error executing SDIO command. */ + MMHAL_SDIO_CMD_CRC_ERROR = -4, + /** Timeout transferring data. */ + MMHAL_SDIO_DATA_TIMEOUT = -5, + /** CRC error transferring data. */ + MMHAL_SDIO_DATA_CRC_ERROR = -6, + /** Underflow filling SDIO controller FIFO. */ + MMHAL_SDIO_DATA_UNDERFLOW = -7, + /** Overflow reading from SDIO controller FIFO. */ + MMHAL_SDIO_DATA_OVERRUN = -8, + /** Another error not covered by the above error codes. */ + MMHAL_SDIO_OTHER_ERROR = -9, +}; + +/** + * Perform transport specific startup. + * + * @returns 0 on success, an error code from @ref mmhal_sdio_error_codes on failure. + */ +int mmhal_wlan_sdio_startup(void); + +/** + * Execute an SDIO command without data. + * + * @param[in] cmd_idx The Command Index. + * @param[in] arg Command argument. This corresponds to the 32 bits of the command between + * the Command Index field and the CRC7 field. + * @param[out] rsp The contents of the command response between the Command Index field + * and the CRC7 field. May be @c NULL if the response is not required. + * The returned value is undefined if the return code is not zero. + * + * @returns 0 on success, an error code from @ref mmhal_sdio_error_codes on failure. + */ +int mmhal_wlan_sdio_cmd(uint8_t cmd_idx, uint32_t arg, uint32_t *rsp); + +/** + * Arguments structure for @ref mmhal_wlan_sdio_cmd53_write(). + */ +struct mmhal_wlan_sdio_cmd53_write_args { + /** The SDIO argument. This corresponds to the 32 bits of the command between + * the Command Index field and the CRC7 field. */ + uint32_t sdio_arg; + /** Pointer to the data buffer. 32 bit word aligned. */ + const uint8_t *data; + /** Transfer length measured in blocks if block_size is non-zero otherwise in bytes. + * If transfer_length is measured in bytes, it will be a multiple of 4. */ + uint16_t transfer_length; + /** + * If non-zero this indicates that the data should be transferred in block mode with + * the given block size. If zero then the data should be transferred in byte mode and + * @c transfer_length is guaranteed to not exceed the block size of the function. + */ + uint16_t block_size; +}; + +/** + * Execute an SDIO CMD53 write. + * + * @param args The write arguments. + * + * @returns 0 on success, an error code from @ref mmhal_sdio_error_codes on failure. + */ +int mmhal_wlan_sdio_cmd53_write(const struct mmhal_wlan_sdio_cmd53_write_args *args); + +/** + * Arguments structure for @ref mmhal_wlan_sdio_cmd53_read(). + */ +struct mmhal_wlan_sdio_cmd53_read_args { + /** The SDIO argument. This corresponds to the 32 bits of the command between + * the Command Index field and the CRC7 field. */ + uint32_t sdio_arg; + /** Pointer to the data buffer to receive the data. 32 bit word aligned. */ + uint8_t *data; + /** Transfer length measured in blocks if block_size is non-zero otherwise in bytes. + * If transfer_length is measured in bytes, it will be a multiple of 4. */ + uint16_t transfer_length; + /** + * If non-zero this indicates that the data should be transferred in block mode with + * the given block size. If zero then the data should be transferred in byte mode and + * @c transfer_length is guaranteed to not exceed the block size of the function. + */ + uint16_t block_size; +}; + +/** + * Execute an SDIO CMD53 read. + * + * @param args The read arguments. + * + * @returns 0 on success, an error code from @ref mmhal_sdio_error_codes on failure. + */ +int mmhal_wlan_sdio_cmd53_read(const struct mmhal_wlan_sdio_cmd53_read_args *args); + +/** + * @defgroup MMHAL_WLAN_SDIO_UTILS SDIO Utilities + * + * Useful macros and inline utilities function for use by SDIO and SPI HALs. + * + * @{ + */ + +/* + * SDIO argument definition, per SDIO Specification Version 4.10, Part E1, Section 5.3. + */ + +/** SDIO CMD52/CMD53 R/W flag. */ +enum mmhal_sdio_rw { + MMHAL_SDIO_READ = 0, /**< Read operation */ + MMHAL_SDIO_WRITE = (1ul << 31), /**< Write operation */ +}; + +/** SDIO CMD52/CMD53 function number. */ +enum mmhal_sdio_function { + MMHAL_SDIO_FUNCTION_0 = 0, /** Function 0 */ + MMHAL_SDIO_FUNCTION_1 = (1ul << 28), /** Function 1 */ + MMHAL_SDIO_FUNCTION_2 = (2ul << 28), /** Function 2 */ +}; + +/** SDIO CMD53 block mode*/ +enum mmhal_sdio_mode { + MMHAL_SDIO_MODE_BYTE = 0, /** Byte mode */ + MMHAL_SDIO_MODE_BLOCK = (1ul << 27), /** Block mode */ +}; + +/** SDIO CMD53 OP code */ +enum mmhal_sdio_opcode { + /** Operate on a single, fixed address. */ + MMHAL_SDIO_OPCODE_FIXED_ADDR = 0, + /** Increment address by 1 after each byte. */ + MMHAL_SDIO_OPCODE_INC_ADDR = (1ul << 26), +}; + +/** CMD52/53 Register Address (17 bit) offset. */ +#define MMHAL_SDIO_ADDRESS_OFFSET (9) +/** CMD52/53 Register Address maximum value. */ +#define MMHAL_SDIO_ADDRESS_MAX ((1ul << 18) - 1) + +/** CMD53 Byte/block count offset (9 bit). */ +#define MMHAL_SDIO_COUNT_OFFSET (0) +/**CMD53 Byte/block count maximum value. */ +#define MMHAL_SDIO_COUNT_MAX ((1ul << 10) - 1) + +/** CMD52 Data (8 bit) offset */ +#define MMHAL_SDIO_CMD52_DATA_OFFSET (0) + +/** + * Construct an SDIO CMD52 argument based on the given arguments. + * + * @param rw Flag indication direction (read or write). + * @param fn The applicable function. + * @param address The address to read/write. Must be <= @c MMHAL_SDIO_ADDRESS_MAX. + * @param write_data The data to write if this is a write operation. Should be set to zero + * for a read operation. + * + * @return the SDIO CMD52 argument generated based on the given arguments. + */ +static inline uint32_t mmhal_make_cmd52_arg(enum mmhal_sdio_rw rw, enum mmhal_sdio_function fn, uint32_t address, + uint8_t write_data) +{ + uint32_t arg; + + arg = rw | fn; + arg |= (address << MMHAL_SDIO_ADDRESS_OFFSET); + arg |= (write_data << MMHAL_SDIO_CMD52_DATA_OFFSET); + return arg; +} + +/** + * Construct an SDIO CMD53 argument based on the given arguments. + * + * @param rw Flag indication direction (read or write). + * @param fn The applicable function. + * @param mode Selects between byte and block mode. + * @param address The address to read/write. Must be <= @c MMHAL_SDIO_ADDRESS_MAX. + * @param count The count of bytes/blocks (depending on @p mode) to transfer. Must + * be <= @c MMHAL_SDIO_COUNT_MAX. + * + * @note OP Code 1 (incrementing address) is assumed. See also @ref MMHAL_SDIO_OPCODE_INC_ADDR. + * + * @return the SDIO CMD53 argument generated based on the given arguments. + */ +static inline uint32_t mmhal_make_cmd53_arg(enum mmhal_sdio_rw rw, enum mmhal_sdio_function fn, enum mmhal_sdio_mode mode, + uint32_t address, uint16_t count) +{ + uint32_t arg; + + arg = rw | fn | MMHAL_SDIO_OPCODE_INC_ADDR | mode; + arg |= (address << MMHAL_SDIO_ADDRESS_OFFSET); + arg |= (count << MMHAL_SDIO_COUNT_OFFSET); + return arg; +} + +/** @} */ + +/** @} */ + +#ifdef __cplusplus +} +#endif + +/** @} */ diff --git a/lib/MorseWlan/include/mmipal.h b/lib/MorseWlan/include/mmipal.h new file mode 100644 index 000000000..1db6baf7a --- /dev/null +++ b/lib/MorseWlan/include/mmipal.h @@ -0,0 +1,383 @@ +/* + * Copyright 2023 Morse Micro + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @defgroup MMIPAL Morse Micro IP Stack Abstraction Layer (MMIPAL) API + * + * This API provides a layer of abstraction from the underlying IP stack for common operations + * such as configuring the link and getting link status. + * + * @{ + */ + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +/** Maximum length of an IP address string, including null-terminator. */ +#ifndef MMIPAL_IPADDR_STR_MAXLEN +#define MMIPAL_IPADDR_STR_MAXLEN (48) +#endif + +/** Maximum number of IPv6 addresses supported. */ +#ifndef MMIPAL_MAX_IPV6_ADDRESSES +#define MMIPAL_MAX_IPV6_ADDRESSES (3) +#endif + +/** Enumeration of status codes returned by MMIPAL functions. */ +enum mmipal_status { + /** Completed successfully. */ + MMIPAL_SUCCESS, + /** One or more arguments were invalid. */ + MMIPAL_INVALID_ARGUMENT, + /** The operation could not complete because the link is not up. */ + MMIPAL_NO_LINK, + /** Failed due to memory allocation failure. */ + MMIPAL_NO_MEM, + /** This functionality is not supported (e.g., due to build configuration). */ + MMIPAL_NOT_SUPPORTED, +}; + +/** Enumeration of link states. */ +enum mmipal_link_state { + /** Link is down. */ + MMIPAL_LINK_DOWN, + /** Link is up. */ + MMIPAL_LINK_UP, +}; + +/** Enumeration of IP address allocation modes. */ +enum mmipal_addr_mode { + /** Disabled. */ + MMIPAL_DISABLED, + /** Static IP address. */ + MMIPAL_STATIC, + /** IP address allocated via DHCP. @c LWIP_DHCP must be set to 1 if using LWIP. */ + MMIPAL_DHCP, + /** IP address allocated via AutoIP. @c LWIP_DHCP must be set to 1 if using LWIP. */ + MMIPAL_AUTOIP, + /** DHCP offloaded to chip. */ + MMIPAL_DHCP_OFFLOAD, +}; + +/** IP address string type. */ +typedef char mmipal_ip_addr_t[MMIPAL_IPADDR_STR_MAXLEN]; + +/** + * IPv4 configuration structure. + * + * This should be initialized using @c MMIPAL_IP_CONFIG_DEFAULT. + * For example: + * + * @code{.c} + * struct mmipal_ip_config config = MMIPAL_IP_CONFIG_DEAFULT; + * @endcode + */ +struct mmipal_ip_config { + /** IP address allocation mode. */ + enum mmipal_addr_mode mode; + /** local IP address */ + mmipal_ip_addr_t ip_addr; + /** Netmask address */ + mmipal_ip_addr_t netmask; + /** Gateway address */ + mmipal_ip_addr_t gateway_addr; +}; + +/** Initializer for @ref mmipal_ip_config. */ +#define MMIPAL_IP_CONFIG_DEFAULT \ + { \ + MMIPAL_DHCP, "", "", "", \ + } + +/** Enumeration of IPv6 address allocation modes. */ +enum mmipal_ip6_addr_mode { + /** Disabled. */ + MMIPAL_IP6_DISABLED, + /** Static IPv6 addresses. */ + MMIPAL_IP6_STATIC, + /** IPv6 address allocated via autoconfiguration. + * @c LWIP_IPV6_AUTOCONFIG must be set to 1 if using LWIP. */ + MMIPAL_IP6_AUTOCONFIG, + /** IPv6 address allocated via stateless DHCPv6. + * @c LWIP_IPV6_DHCP6_STATELESS must be set to 1 if using LWIP. */ + MMIPAL_IP6_DHCP6_STATELESS, +}; + +/** + * IPv6 configuration structure. + * + * This should be initialized using @c MMIPAL_IP6_CONFIG_DEFAULT. + * For example: + * + * @code{.c} + * struct mmipal_ip6_config config = MMIPAL_IP6_CONFIG_DEFAULT; + * @endcode + */ +struct mmipal_ip6_config { + /** IPv6 addresses allocation mode. */ + enum mmipal_ip6_addr_mode ip6_mode; + /** Array of IPv6 addresses. */ + mmipal_ip_addr_t ip6_addr[MMIPAL_MAX_IPV6_ADDRESSES]; +}; + +/** Initializer for @ref mmipal_ip6_config. */ +#define MMIPAL_IP6_CONFIG_DEFAULT \ + { \ + MMIPAL_IP6_AUTOCONFIG \ + } + +/** + * Initialize arguments structure. + * + * This should be initialized using @c MMIPAL_INIT_ARGS_DEFAULT. + * For example: + * + * @code{.c} + * struct mmipal_init_args args = MMIPAL_INIT_ARGS_DEFAULT; + * @endcode + */ +struct mmipal_init_args { + /** IP address allocation mode to use. */ + enum mmipal_addr_mode mode; + /** IP address to use (if @c mode is @c MMIPAL_STATIC). */ + mmipal_ip_addr_t ip_addr; + /** Netmask to use (if @c mode is @c MMIPAL_STATIC). */ + mmipal_ip_addr_t netmask; + /** Gateway IP address to use (if @c mode is @c MMIPAL_STATIC). */ + mmipal_ip_addr_t gateway_addr; + + /** IPv6 address allocation mode to use. */ + enum mmipal_ip6_addr_mode ip6_mode; + /** IPv6 address to use (if @c ip6_mode is @c MMIPAL_IP6_STATIC). */ + mmipal_ip_addr_t ip6_addr; + /** Flag requesting ARP response offload feature */ + bool offload_arp_response; + /** ARP refresh offload interval in seconds */ + uint32_t offload_arp_refresh_s; +}; + +/** + * Default values for @ref mmipal_init_args. This should be used when initializing the + * @ref mmipal_init_args structure. + */ +#define MMIPAL_INIT_ARGS_DEFAULT \ + { \ + MMIPAL_DHCP, {0}, {0}, {0}, MMIPAL_IP6_DISABLED, {0}, false, 0 \ + } + +/** + * Initialize the IP stack and enable the MMWLAN interface. + * + * This will implicitly initialize and boot MMWLAN, and will block until this has completed. + * + * @note This function will boot the Morse Micro transceiver using @ref mmwlan_boot() in order + * to read the MAC address. It is the responsibility of the caller to shut down the + * transceiver using @ref mmwlan_shutdown() as required. + * + * @warning @ref mmwlan_init() must be called before invoking this function. + * + * @param args Initialization arguments. + * + * @return @c MMIPAL_SUCCESS on success. otherwise a vendor specific error code. + */ +enum mmipal_status mmipal_init(const struct mmipal_init_args *args); + +/** + * Structure representing the current status of the link. + */ +struct mmipal_link_status { + /** State of the link (up/down). */ + enum mmipal_link_state link_state; + /** Current IP address. */ + mmipal_ip_addr_t ip_addr; + /** Current netmask. */ + mmipal_ip_addr_t netmask; + /** Current gateway IP address. */ + mmipal_ip_addr_t gateway; +}; + +/** + * Prototype for callback function invoked on link status changes. + * + * @param link_status The current link status. + */ +typedef void (*mmipal_link_status_cb_fn_t)(const struct mmipal_link_status *link_status); + +/** + * Sets the callback function to be invoked on link status changes. + * + * This will be used when DHCP is enabled. + * + * @note This is for IPv4 only. To get IPv6 status use @c mmipal_get_ip6_config. + * @note If an opaque argument is required then use @ref mmipal_set_ext_link_status_callback() + * instead. + * + * @param fn Function pointer to the callback function. + */ +void mmipal_set_link_status_callback(mmipal_link_status_cb_fn_t fn); + +/** + * Prototype for callback function invoked on link status changes. + * + * This is similar to @ref mmipal_link_status_cb_fn_t but with the addition of the @p arg + * parameter. + * + * @param link_status The current link status. + * @param arg Opaque argument that was provided when the callback was registered. + */ +typedef void (*mmipal_ext_link_status_cb_fn_t)(const struct mmipal_link_status *link_status, void *arg); + +/** + * Sets the extended link status callback function to be invoked on link status changes. + * This is similar to @ref mmipal_set_link_status_callback() with the exception that + * an opaque argument may also be specified. + * + * This will be used when DHCP is enabled. + * + * @note This is for IPv4 only. To get IPv6 status use @c mmipal_get_ip6_config. + * + * @param fn Function pointer to the callback function. + * @param arg Opaque argument to be passed to the callback. + */ +void mmipal_set_ext_link_status_callback(mmipal_ext_link_status_cb_fn_t fn, void *arg); + +/** + * Get the total number of transmitted and received packets on the MMWLAN interface + * + * @note If using LWIP, this function requires LWIP_STATS to be defined in your application, + * otherwise packet counters will always return as 0. + * + * @param tx_packets Pointer to location to store total tx packets + * @param rx_packets Pointer to location to store total rx packets + */ +void mmipal_get_link_packet_counts(uint32_t *tx_packets, uint32_t *rx_packets); + +/** + * Set the QoS Traffic ID to use when transmitting. + * + * @param tid The QoS TID to use (0 - @ref MMWLAN_MAX_QOS_TID). + */ +void mmipal_set_tx_qos_tid(uint8_t tid); + +/** + * Gets the local address for the MMWLAN interface that is appropriate for a given + * destination address. + * + * The following table shows how the returned @c local_addr is selected: + * + * | @p dest_addr | @c local_addr returned | + * |--------------|---------------------------| + * | type is IPv4 | IPv4 address | + * | type is IPv6 | An IPv6 source address selected from interface's IPv6 addresses or ERR_CONN | + * + * (X = don't care) + * + * If the given parameters would result in a @p local_addr type of IPv4 and IPv4 is not enabled, + * or IPv6 and IPv6 is not enabled, then @c MMIPAL_INVALID_ARGUMENT will be returned. + * + * @param[out] local_addr Output local address for the MMWLAN interface, as noted above. + * @param[in] dest_addr Destination address. + * + * @return @c MMIPAL_SUCESS if @p local_addr successfully set. otherwise an + * appropriate error code. + */ +enum mmipal_status mmipal_get_local_addr(mmipal_ip_addr_t local_addr, const mmipal_ip_addr_t dest_addr); + +/** + * Get the IP configurations. + * + * This can be used to get the local IP configurations. + * + * @param config Pointer to the IP configurations. + * + * @returns @c MMIPAL_SUCCESS on success, @c MMIPAL_NOT_SUPPORTED if IPv4 is not supported. + */ +enum mmipal_status mmipal_get_ip_config(struct mmipal_ip_config *config); + +/** + * Set the IP configurations. + * + * This can be used to set the local IP configurations. + * + * @param config Pointer to the IP configurations. + * + * @returns @c MMIPAL_SUCCESS on success, @c MMIPAL_NOT_SUPPORTED if IPv4 is not supported. + */ +enum mmipal_status mmipal_set_ip_config(const struct mmipal_ip_config *config); + +/** + * Gets the current IPv4 broadcast address. + * + * @param[out] broadcast_addr Buffer to receive the broadcast address as a string. + * + * @returns @c MMIPAL_SUCCESS on success, @c MMIPAL_NOT_SUPPORTED if IPv4 is not supported. + */ +enum mmipal_status mmipal_get_ip_broadcast_addr(mmipal_ip_addr_t broadcast_addr); + +/** + * Get the IP configurations. + * + * This can be used to get the local IP configurations. + * + * @param config Pointer to the IP configurations. + * + * @returns @c MMIPAL_SUCCESS on success, @c MMIPAL_NOT_SUPPORTED if IPv6 is not supported.. + */ +enum mmipal_status mmipal_get_ip6_config(struct mmipal_ip6_config *config); + +/** + * Set the IPv6 configurations. + * + * This can be used to set the local IPv6 configurations. + * + * @param config Pointer to the IPv6 configurations. + * + * @returns @c MMIPAL_SUCCESS on success, @c MMIPAL_NOT_SUPPORTED if IPv6 is not supported. + */ +enum mmipal_status mmipal_set_ip6_config(const struct mmipal_ip6_config *config); + +/** + * Get current IPv4 link state. + * + * @returns the current IPv4 link state (up or down). + */ +enum mmipal_link_state mmipal_get_link_state(void); + +/** + * Set the DNS server at the given index. + * + * @warning Depending on IP stack implementation, this setting may be overridden by DHCP. + * + * @param[in] index Index of the DNS server to set. + * @param[out] addr Address of the DNS server to set. + * + * @returns @c MMIPAL_SUCCESS on success, @c MMIPAL_INVALID_ARGUMENT if an invalid index or IP + * address was given. + */ +enum mmipal_status mmipal_set_dns_server(uint8_t index, const mmipal_ip_addr_t addr); + +/** + * Get the DNS server at the given index. + * + * @param[in] index Index of the DNS server to set. + * @param[out] addr IP address buffer to receive the IP address of the DNS server at the given + * index. Will be set to empty string if no server at the given index. + * + * @returns @c MMIPAL_SUCCESS on success. + */ +enum mmipal_status mmipal_get_dns_server(uint8_t index, mmipal_ip_addr_t addr); + +#ifdef __cplusplus +} +#endif + +/** @} */ diff --git a/lib/MorseWlan/include/mmlog.h b/lib/MorseWlan/include/mmlog.h new file mode 100644 index 000000000..ff22e9b9b --- /dev/null +++ b/lib/MorseWlan/include/mmlog.h @@ -0,0 +1,64 @@ +/* + * Morse logging API + * + * Copyright 2023 Morse Micro + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +/** + * Macro for printing a @c uint64_t as two separate @c uint32_t values. This is to allow printing of + * these values even when the @c printf implementation doesn't support it. + */ +#define MM_X64_VAL(value) ((uint32_t)(value >> 32)), ((uint32_t)value) + +/** Macro for format specifier to print @ref MM_X64_VAL */ +#define MM_X64_FMT "%08lx%08lx" + +/** + * Macro for printing a MAC address. This saves writing it out by hand. + * + * Must be used in conjunction with @ref MM_MAC_ADDR_FMT. For example: + * + * @code + * uint8_t mac_addr[] = { 0, 1, 2, 3, 4, 5 }; + * printf("MAC address: " MM_MAC_ADDR_FMT "\n", MM_MAC_ADDR_VAL(mac_addr)); + * @endcode + */ +#define MM_MAC_ADDR_VAL(value) ((value)[0]), ((value)[1]), ((value)[2]), ((value)[3]), ((value)[4]), ((value)[5]) + +/** Macro for format specifier to print @ref MM_MAC_ADDR_VAL */ +#define MM_MAC_ADDR_FMT "%02x:%02x:%02x:%02x:%02x:%02x" + +/** + * Initialize Morse logging API. + * + * This should be invoked after OS initialization since it will create a mutex for + * logging. + */ +void mm_logging_init(void); + +/** + * Dumps a binary buffer in hex. + * + * @param level A single character indicating log level. + * @param function Name of function this was invoked from. + * @param line_number Line number this was invoked from. + * @param title Title of the buffer. + * @param buf The buffer to dump. + * @param len Length of the buffer. + */ +void mm_hexdump(char level, const char *function, unsigned line_number, const char *title, const uint8_t *buf, size_t len); + +#ifdef __cplusplus +} +#endif diff --git a/lib/MorseWlan/include/mmosal.h b/lib/MorseWlan/include/mmosal.h new file mode 100644 index 000000000..8663b57bf --- /dev/null +++ b/lib/MorseWlan/include/mmosal.h @@ -0,0 +1,1036 @@ +/* + * Copyright 2021-2023 Morse Micro + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @defgroup MMOSAL Morse Micro Operating System Abstraction Layer (mmosal) API + * + * This API provides a layer of abstraction from the underlying operation system. Functionality + * is provided for typical RTOS features. + * + * @{ + */ + +#pragma once + +#include +#include +#include +#include +#include + +#include "mmport.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @defgroup MMOSAL_MAIN Main loop + * + * Provides the "main" function that runs the operating system scheduler. + * + * @{ + */ + +/** Application initialization callback (see @ref mmosal_main for details). */ +typedef void (*mmosal_app_init_cb_t)(void); + +/** + * OS main function. + * + * This should be invoked after early initialization. If further initialization is required after + * the scheduler is started an optional @p app_init_cb argument can be provided and the given + * function with be executed in a thread that will subsequently be destroyed if it returns. + * + * @param app_init_cb Application initialization callback that will be invoked after the + * scheduler has been started. This optional (may be NULL). If specified, + * the function will run in its own thread and may or may not return. + * + * @returns an error code if something goes wrong, otherwise does not return. + */ +int mmosal_main(mmosal_app_init_cb_t app_init_cb); + +/** + * @} + */ + +/* + * --------------------------------------------------------------------------------------------- + */ + +/** + * @defgroup MMOSAL_MEMMGMT Memory management + * + * Provides memory management functionality (allocation and free). + * + * @{ + */ + +/** + * Allocate memory of the given size and return a pointer to it (malloc). + * + * No guarantees made about initialization. + * + * @warning This function should not be used directly. Instead use @ref mmosal_malloc(). + * + * @param size The amount of memory to allocate (in bytes). + * + * @returns pointer to the allocated memory on success, or NULL on failure. + */ +void *mmosal_malloc_(size_t size); + +/** + * Allocate memory of the given size and return a pointer to it (malloc) -- debug version. + * + * No guarantees made about initialization. + * + * @warning This function should not be used directly. Instead use @ref mmosal_malloc(). + * + * @param size The amount of memory to allocate (in bytes). + * @param name Name of the function that allocated the memory. + * @param line_number The line number at which the allocation happened. + * + * @returns pointer to the allocated memory on success, or NULL on failure. + */ +void *mmosal_malloc_dbg(size_t size, const char *name, unsigned line_number); + +/** + * Free the given memory allocation. + * + * @param p The memory to free. + */ +void mmosal_free(void *p); + +/** + * Equivalent of standard library realloc(). + * + * @param ptr Previously allocated memory block to resize. + * @param size The new size. + * + * @returns pointer to the resized block of memory, or @c NULL. + */ +void *mmosal_realloc(void *ptr, size_t size); + +/** + * Equivalent of standard library calloc(). + * + * @param nitems Number of blocks to allocate + * @param size Size of each block. + * + * @returns pointer to the allocated memory on success, or NULL on failure. + */ +void *mmosal_calloc(size_t nitems, size_t size); + +#ifndef MMOSAL_TRACK_ALLOCATIONS +/** + * Allocate memory of the given size and return a pointer to it (malloc). + * + * No guarantees made about initialization. + * + * @param size The amount of memory to allocate (in bytes). + * + * @returns pointer to the allocated memory on success, or NULL on failure. + */ +#define mmosal_malloc(size) mmosal_malloc_(size) +#else +/* Note: we use function name because it should be shorter than the full filename path. */ +/** + * Allocate memory of the given size and return a pointer to it (malloc). + * + * No guarantees made about initialization. + * + * @param size The amount of memory to allocate (in bytes). + * + * @returns pointer to the allocated memory on success, or NULL on failure. + */ +#define mmosal_malloc(size) mmosal_malloc_dbg(size, __FUNCTION__, __LINE__) +#endif + +/** + * @} + */ + +/* + * --------------------------------------------------------------------------------------------- + */ + +/** + * @defgroup MMOSAL_TASKS Task (thread) management + * + * Provides memory management functionality managing tasks (create, destroy, yield, etc.). + * + * @{ + */ + +/** + * Type definition for task main functions. + * + * @param arg Opaque argument passed in at task creation. + */ +typedef void (*mmosal_task_fn_t)(void *arg); + +/** Enumeration of task priorities (ordered lowest to highest). */ +enum mmosal_task_priority { + /** Idle task priority. */ + MMOSAL_TASK_PRI_IDLE, + /** Minimum priority. */ + MMOSAL_TASK_PRI_MIN, + /** Low priority. */ + MMOSAL_TASK_PRI_LOW, + /** Normal priority. */ + MMOSAL_TASK_PRI_NORM, + /** High priority. */ + MMOSAL_TASK_PRI_HIGH, +}; + +/** + * Create a new task. + * + * @param task_fn Task main function. + * @param argument Argument to pass to main function. + * @param priority Task priority. + * @param stack_size_u32 Size of stack to allocate for task (in units of 32 bit words). + * @param name Name to give the task. + * + * @returns an opaque task handle, or @c NULL on failure. + */ +struct mmosal_task *mmosal_task_create(mmosal_task_fn_t task_fn, void *argument, enum mmosal_task_priority priority, + unsigned stack_size_u32, const char *name); + +/** + * Delete the given task. + * + * @param task Handle of the task to delete (or NULL to delete the current task). + * + * @note With FreeRTOS deleted tasks will not be cleaned up until the idle task runs. + */ +void mmosal_task_delete(struct mmosal_task *task); + +/** + * Block until the given task has terminated. + * + * @param task Handle of the task to wait for. + * + * @note With FreeRTOS deleted tasks will not be cleaned up until the idle task runs. + * + * @deprecated Do not invoke this function because it is deprecated and will be removed from + * the mmosal API in a future release. + */ +void mmosal_task_join(struct mmosal_task *task); + +/** + * Get the handle of the active task. + * + * @returns the handle of the active task. + */ +struct mmosal_task *mmosal_task_get_active(void); + +/** + * Yield the active task. + */ +void mmosal_task_yield(void); + +/** + * Sleep for a period of time, yielding during that time. + * + * @param duration_ms Sleep duration, in milliseconds. + */ +void mmosal_task_sleep(uint32_t duration_ms); + +/** + * Enter critical section. + * + * This may result in interrupts being disabled so critical sections must be kept short. + * Each entry to a critical section must be matched to a corresponding exit + * (@ref MMOSAL_TASK_EXIT_CRITICAL()). + */ +#define MMOSAL_TASK_ENTER_CRITICAL() \ + do { \ + MMPORT_MEM_SYNC(); \ + mmosal_task_enter_critical(); \ + } while (0) + +/** + * Exit critical section. + * + * Used to exit a critical section previously entered with @ref MMOSAL_TASK_ENTER_CRITICAL()). + */ +#define MMOSAL_TASK_EXIT_CRITICAL() \ + do { \ + MMPORT_MEM_SYNC(); \ + mmosal_task_exit_critical(); \ + } while (0) + +/** + * Enter critical section. + * + * This may result in interrupts being disabled so critical sections must be kept short. + * Each entry to a critical section must be matched to a corresponding exit + * (@ref mmosal_task_exit_critical()). + * + * @note This function should not be invoked directly. Use @ref MMOSAL_TASK_ENTER_CRITICAL(). + */ +void mmosal_task_enter_critical(void); + +/** + * Exit critical section. + * + * Used to exit a critical section previously entered with @ref mmosal_task_enter_critical()). + * + * @note This function should not be invoked directly. Use @ref MMOSAL_TASK_EXIT_CRITICAL(). + */ +void mmosal_task_exit_critical(void); + +/** + * Disable interrupts. + * + * @warning This function should generally not be used. Use @ref mmosal_task_enter_critical() + * instead. + */ +void mmosal_disable_interrupts(void); + +/** + * Enable interrupts. + * + * @warning This function should generally not be used. Use @ref mmosal_task_exit_critical() + * instead. + */ +void mmosal_enable_interrupts(void); + +/** + * Get the name of the running task. + * + * @returns the name of the running task. + */ +const char *mmosal_task_name(void); + +/** + * Blocks the current task until a notification is received. + * + * Notifications can be sent using @ref mmosal_task_notify() or @ref mmosal_task_notify_from_isr(). + * + * @param timeout_ms The time to wait before giving up (or @c UINT32_MAX to wait forever). + * + * @returns @c true of a notification was received or @c false if it timed out. + * + * @see mmosal_task_notify() + * @see mmosal_task_notify_from_isr() + */ +bool mmosal_task_wait_for_notification(uint32_t timeout_ms); + +/** + * Notifies a waiting task (@ref mmosal_task_wait_for_notification()) that it can continue. + * + * @warning Must not be called from ISR context. + * + * @param task Handle of the task to notify. + * + * @see mmosal_task_wait_for_notification() + */ +void mmosal_task_notify(struct mmosal_task *task); + +/** + * Notifies a waiting task (@ref mmosal_task_wait_for_notification()) that it can continue. + * + * @warning Must only be called from ISR context. + * + * @param task Handle of the task to notify. + * + * @see mmosal_task_wait_for_notification() + */ +void mmosal_task_notify_from_isr(struct mmosal_task *task); + +/** + * @} + */ + +/* + * --------------------------------------------------------------------------------------------- + */ + +/** + * @defgroup MMOSAL_MUTEX Mutex + * + * Provides mutex support for mutual exclusion. + * + * @{ + */ + +/** + * Create a new mutex. + * + * @param name The name for the mutex. + * + * @returns an opaque handle to the mutex, or @c NULL on failure. + */ +struct mmosal_mutex *mmosal_mutex_create(const char *name); + +/** + * Delete a mutex. + * + * @param mutex Handle of mutex to delete + */ +void mmosal_mutex_delete(struct mmosal_mutex *mutex); + +/** + * Acquire a mutex. + * + * @param mutex Handle of mutex to acquire. + * @param timeout_ms Timeout after which to give up. To wait infinitely use @c UINT32_MAX. + * + * @returns @c true if the mutex was acquired successfully otherwise @c false. + */ +bool mmosal_mutex_get(struct mmosal_mutex *mutex, uint32_t timeout_ms); + +/** + * Release a mutex. + * + * @param mutex Handle of mutex to release. + * + * @returns @c true if the mutex was released successfully otherwise @c false. + */ +bool mmosal_mutex_release(struct mmosal_mutex *mutex); + +/** + * Attempt to get a mutex, waiting infinitely long for it. Panics on failure. + */ +#define MMOSAL_MUTEX_GET_INF(_mutex) \ + do { \ + bool ok__ = mmosal_mutex_get((_mutex), UINT32_MAX); \ + MMOSAL_ASSERT(ok__); \ + } while (0) + +/** + * Same as @ref mmosal_mutex_release() except it panics on failure. + */ +#define MMOSAL_MUTEX_RELEASE(_mutex) \ + do { \ + bool ok__ = mmosal_mutex_release(_mutex); \ + MMOSAL_ASSERT(ok__); \ + } while (0) + +/** + * Check whether the given mutex is held by the active thread. + * + * @param mutex Handle of the mutex to check. + * + * @returns @c true if the mutex is held by the active thread, else @c false. + */ +bool mmosal_mutex_is_held_by_active_task(struct mmosal_mutex *mutex); + +/** + * @} + */ + +/* + * --------------------------------------------------------------------------------------------- + */ + +/** + * @defgroup MMOSAL_SEM Counting semaphores + * + * Provides counting semaphore support for task synchronization. + * + * @{ + */ + +/** + * Create a new counting semaphore. + * + * @param max_count The maximum count (i.e., number of times the semaphore can be given). + * @param initial_count The initial count (i.e., number of times the semaphore is implicitly + * given when it is created). + * @param name The name of the semaphore. + * + * @returns an opaque handle to the semaphore, or @c NULL on failure. + */ +struct mmosal_sem *mmosal_sem_create(unsigned max_count, unsigned initial_count, const char *name); + +/** + * Delete the given counting semaphore. + * + * @param sem Semaphore to delete. + */ +void mmosal_sem_delete(struct mmosal_sem *sem); + +/** + * Give a counting semaphore. + * + * @warning May not be invoked from an ISR. + * + * @param sem The semaphore to give. + * + * @returns @c true on success, else @c false. + */ +bool mmosal_sem_give(struct mmosal_sem *sem); + +/** + * Give a counting semaphore (from ISR context). + * + * @warning May only be invoked from an ISR. + * + * @param sem The semaphore to give. + * + * @returns @c true on success, else @c false. + */ +bool mmosal_sem_give_from_isr(struct mmosal_sem *sem); + +/** + * Wait for a counting semaphore. + * + * @param sem The semaphore to wait for. + * @param timeout_ms Timeout after which to give up waiting (in milliseconds). + * + * @returns @c true if the the semaphore was taken successfully, else @c false. + */ +bool mmosal_sem_wait(struct mmosal_sem *sem, uint32_t timeout_ms); + +/** + * Returns the current count of the semaphore. + * + * @param sem The semaphore being queried. + * + * @returns The count of the semaphore being queried. + */ +uint32_t mmosal_sem_get_count(struct mmosal_sem *sem); + +/** + * @} + */ + +/* + * --------------------------------------------------------------------------------------------- + */ + +/** + * @defgroup MMOSAL_SEMB Binary semaphores + * + * Provides binary semaphore support for task synchronization. + * + * @{ + */ + +/** + * Create a new binary semaphore. + * + * @param name The name of the semaphore. + * + * @returns an opaque handle to the semaphore, or @c NULL on failure. + */ +struct mmosal_semb *mmosal_semb_create(const char *name); + +/** + * Delete the given binary semaphore. + * + * @param semb Semaphore to delete. + */ +void mmosal_semb_delete(struct mmosal_semb *semb); + +/** + * Give a binary semaphore. + * + * @warning May not be invoked from an ISR. + * + * @param semb The semaphore to give. + * + * @returns @c true on success, else @c false. + */ +bool mmosal_semb_give(struct mmosal_semb *semb); + +/** + * Give a binary semaphore (from ISR context). + * + * @warning May only be invoked from an ISR. + * + * @param semb The semaphore to give. + * + * @returns @c true on success, else @c false. + */ +bool mmosal_semb_give_from_isr(struct mmosal_semb *semb); + +/** + * Wait for a counting semaphore. + * + * @param semb The semaphore to wait for. + * @param timeout_ms Timeout after which to give up waiting (in milliseconds). + * + * @returns @c true if the the semaphore was taken successfully, else @c false. + */ +bool mmosal_semb_wait(struct mmosal_semb *semb, uint32_t timeout_ms); + +/** + * @} + */ + +/* + * --------------------------------------------------------------------------------------------- + */ + +/** + * @defgroup MMOSAL_QUEUE Queues (aka pipes) + * + * Provides queue support for inter-task communication. + * + * @{ + */ + +/** + * Create a new queue. + * + * @param num_items The maximum number of items that may be in the queue at a time. + * @param item_size The size of each item in the queue. + * @param name The name of the queue. + * + * @returns an opaque handle to the queue, or @c NULL on failure. + */ +struct mmosal_queue *mmosal_queue_create(size_t num_items, size_t item_size, const char *name); + +/** + * Delete a queue. + * + * @param queue handle of the queue to delete. + */ +void mmosal_queue_delete(struct mmosal_queue *queue); + +/** + * Pop an item from the queue. + * + * @warning May not be invoked from an ISR. + * + * @param queue The queue to pop from. + * @param item Pointer to memory to receive the popped item. The item will be copied into + * this memory. The memory size must match the @c item_size given at creation. + * @param timeout_ms Timeout after which to give up waiting for an item if the queue is empty + * (in milliseconds). + * + * @returns @c true if an item was successfully popped, else @c false. + */ +bool mmosal_queue_pop(struct mmosal_queue *queue, void *item, uint32_t timeout_ms); + +/** + * Push an item into the queue. + * + * @warning May not be invoked from an ISR. + * + * @param queue The queue to push to. + * @param item Pointer to the item to push. This item will be copied into the queue. + * The size of the item must match the @c item_size given at creation. + * @param timeout_ms Timeout after which to give up waiting for space if the queue is full + * (in milliseconds). + * + * @returns @c true if an item was successfully pushed, else @c false. + */ +bool mmosal_queue_push(struct mmosal_queue *queue, const void *item, uint32_t timeout_ms); + +/** + * Pop an item from the queue (from ISR context). + * + * @warning May only be invoked from an ISR. + * + * @param queue The queue to pop from. + * @param item Pointer to memory to receive the popped item. The item will be copied into + * this memory. The memory size must match the @c item_size given at creation. + * + * @returns @c true if an item was successfully popped, else @c false. + */ +bool mmosal_queue_pop_from_isr(struct mmosal_queue *queue, void *item); + +/** + * Push an item into the queue (from ISR context). + * + * @warning May only be invoked from an ISR. + * + * @param queue The queue to push to. + * @param item Pointer to the item to push. This item will be copied into the queue. + * The size of the item must match the @c item_size given at creation. + * + * @returns @c true if an item was successfully pushed, else @c false. + */ +bool mmosal_queue_push_from_isr(struct mmosal_queue *queue, const void *item); + +/** + * @} + */ + +/* + * --------------------------------------------------------------------------------------------- + */ + +/** + * @defgroup MMOSAL_TIME Time + * + * Provides support for reading the system time. + * + * @{ + */ + +/** + * Get the system time in milliseconds. + * + * @returns the system time in milliseconds. + */ +uint32_t mmosal_get_time_ms(void); + +/** + * Get the system time in ticks. + * + * @returns the system time in ticks. + */ +uint32_t mmosal_get_time_ticks(void); + +/** + * Get the number of ticks in a second. + * + * @returns the number of ticks in a second. + */ +uint32_t mmosal_ticks_per_second(void); + +/** + * Check if time a is less than time b, taking into account wrapping. + * + * | a | b | return | + * |---------------|---------------|--------| + * | 0 | 0 | false | + * | 0 | 1 | true | + * | 1 | 0 | false | + * | @c 0xffffffff | 0 | true | + * | 0 | @c 0xffffffff | false | + * + * + * @param a time a + * @param b time b + * + * @returns true if time a < time b. + */ +static inline bool mmosal_time_lt(uint32_t a, uint32_t b) +{ + return ((int32_t)(a - b)) < 0; +} + +/** + * Check if time a is less than or equal to time b, taking into account wrapping. + * + * | a | b | return | + * |---------------|---------------|--------| + * | 0 | 0 | true | + * | 0 | 1 | true | + * | 1 | 0 | false | + * | @c 0xffffffff | 0 | true | + * | 0 | @c 0xffffffff | false | + * + * + * @param a time a + * @param b time b + * + * @returns true if time a <= time b. + */ +static inline bool mmosal_time_le(uint32_t a, uint32_t b) +{ + return ((int32_t)(a - b)) <= 0; +} + +/** + * Given two times, return the one that is greatest (taking into account wrapping). + * + * | a | b | return | + * |---------------|---------------|--------| + * | 0 | 0 | 0 | + * | 0 | 1 | 1 | + * | 1 | 0 | 1 | + * | @c 0xffffffff | 0 | 0 | + * | 0 | @c 0xffffffff | 0 | + * + * @param a time a + * @param b time b + * + * @returns a if time a >= time b else b. + */ +static inline uint32_t mmosal_time_max(uint32_t a, uint32_t b) +{ + if (mmosal_time_lt(a, b)) { + return b; + } else { + return a; + } +} + +/** + * Check if the given time has already passed. i.e the system time is greater than the given time. + * + * @param t The time to check. + * + * @return @c true if the given time has passed, else @c false. + */ +static inline bool mmosal_time_has_passed(uint32_t t) +{ + return mmosal_time_le(t, mmosal_get_time_ms()); +} + +/** + * @} + */ + +/* + * --------------------------------------------------------------------------------------------- + */ + +/** + * @defgroup MMOSAL_TIMER Timers + * + * Provides support for RTOS timers. + * + * @note The timer service must have a priority higher than @ref MMOSAL_TASK_PRI_HIGH. This is + * because tasks may yield to allow the timer service to execute. In some RTOS implementations + * if there are no other tasks at a higher or equal priority to the task that calls @ref + * mmosal_task_yield() then the RTOS scheduler will simply select the task that called + * @ref mmosal_task_yield() to run again. + * + * @{ + */ + +/** Timer opaque handler data type. */ +struct mmosal_timer; + +/** + * Function type definition for timer callbacks. + * + * @param timer The timer that triggered the callback. + */ +typedef void (*timer_callback_t)(struct mmosal_timer *timer); + +/** + * Create a new timer. + * + * @param name The name of the timer. + * @param timer_period_ms The period of the timer in milliseconds. + * @param auto_reload If @c true then the timer will automatically be reloaded when + * it expires. + * @param arg Void pointer that can be used to store a value for the timer callback. + * Pass NULL if unused. + * @param callback Callback to be triggered when the timer expires. + * + * @returns an opaque handle to the timer, or @c NULL on failure. + * + * @warning Ensure that the timer callback function does not block or cause the calling task to + * be placed in a blocked state. + */ +struct mmosal_timer *mmosal_timer_create(const char *name, uint32_t timer_period_ms, bool auto_reload, void *arg, + timer_callback_t callback); + +/** + * Delete a timer. + * + * @param timer The timer to delete. + */ +void mmosal_timer_delete(struct mmosal_timer *timer); + +/** + * Start a timer. + * + * @param timer The timer to start. + * + * @returns @c true if the timer was started successfully, else @c false. + */ +bool mmosal_timer_start(struct mmosal_timer *timer); + +/** + * Stop a timer. + * + * @param timer The timer to stop. + * + * @returns @c true if the timer was stopped successfully, else @c false. + */ +bool mmosal_timer_stop(struct mmosal_timer *timer); + +/** + * Change timer period. + * + * @warning May not be invoked from an ISR. + * + * @param timer The timer to assign the new period. + * @param new_period Period to assign to the timer. + * + * @returns @c true if the timer period was stopped change, else @c false. + */ +bool mmosal_timer_change_period(struct mmosal_timer *timer, uint32_t new_period); + +/** + * Get the opaque argument associated with a given timer. + * + * @param timer The timer to retrieve the argument from. + * + * @returns void pointer to the argument. + */ +void *mmosal_timer_get_arg(struct mmosal_timer *timer); + +/** + * Queries the timer to determine if it running. + * + * @param timer The timer to retrieve the argument from. + * + * @returns @c true if the timer is running, else @c false. + */ +bool mmosal_is_timer_active(struct mmosal_timer *timer); + +/** + * @} + */ + +/* + * --------------------------------------------------------------------------------------------- + */ + +/** + * @defgroup MMOSAL_ASSERT Assertions and failure handling + * + * Provides support for assertions and logging failure data. + * + * @{ + */ + +#ifndef MMOSAL_FILEID +/** File identifier: if not provided by compiler then define to zero. */ +#define MMOSAL_FILEID 0 +#endif + +/** Data structure used to store information about a failure that can be preserved across reset. */ +struct mmosal_failure_info { + /** Content of the PC register at assertion. */ + uint32_t pc; + /** Content of the LR register at assertion. */ + uint32_t lr; + /** File identifier. */ + uint32_t fileid; + /** Source code line at which the assertion was triggered. */ + uint32_t line; + /** Arbitrary platform-specific failure info. + * Will be zeroes in the case of assertion failure. */ + uint32_t platform_info[4]; +}; + +/** + * Log failure information in a way that it is preserved across reboots so that it can be + * available for postmortem analysis. + * + * @param info Failure information to log. + */ +void mmosal_log_failure_info(const struct mmosal_failure_info *info); + +#ifdef MMOSAL_NO_DEBUGLOG +/** Empty logging handler. */ +#define MMOSAL_LOG_FAILURE_INFO(...) +#else +/** Initialize a mmosal_failure_info struct based on current state and invoke + * mmosal_log_failure_info(). */ +#define MMOSAL_LOG_FAILURE_INFO(...) \ + do { \ + void *pc; \ + struct mmosal_failure_info info = { \ + .lr = (uint32_t)MMPORT_GET_LR(), \ + .fileid = MMOSAL_FILEID, \ + .line = __LINE__, \ + .platform_info = {__VA_ARGS__}, \ + }; \ + MMPORT_GET_PC(pc); \ + info.pc = (uint32_t)pc; \ + mmosal_log_failure_info(&info); \ + } while (0) +#endif + +/** + * Assertion handler implementation. + * + * @note This function does not return. + */ +void mmosal_impl_assert(void); + +#ifndef MMOSAL_NOASSERT +/** + * Assert that the given expression evaluates to true and abort execution if not. + * + * @param expr Expression to evaluation. If it evaluates to @c false then the assertion handler + * will be triggered. + */ +#ifndef MMOSAL_ASSERT +#define MMOSAL_ASSERT(expr) \ + do { \ + if (!(expr)) { \ + MMOSAL_LOG_FAILURE_INFO(0); \ + mmosal_impl_assert(); \ + while (1) { \ + } \ + } \ + } while (0) +#endif + +/** + * Assert that the given expression evaluates to true and abort execution if not. + * + * @param expr Expression to evaluation. If it evaluates to @c false then the assertion handler + * will be triggered. + * @param ... Up to 4 32-bit unsigned integers to log. + */ +#ifndef MMOSAL_ASSERT_LOG_DATA +#define MMOSAL_ASSERT_LOG_DATA(expr, ...) \ + do { \ + if (!(expr)) { \ + MMOSAL_LOG_FAILURE_INFO(__VA_ARGS__); \ + mmosal_impl_assert(); \ + while (1) { \ + } \ + } \ + } while (0) +#endif +#else +/** Empty assertion handler. */ +#define MMOSAL_ASSERT(expr) (void)(expr) +/** Empty assertion handler. */ +#define MMOSAL_ASSERT_LOG_DATA(expre, ...) (void)(expr) +#endif + +/** @} */ + +/** + * @defgroup MMOSAL_MISC Miscellaneous functions + * + * @{ + */ + +/** + * A safer version of @c strncpy. + * + * Up to `size - 1` bytes will be copied from @p src to @p dst and the result will be + * null-terminated. + * + * @param dst The buffer to copy the string into. + * @param src The source string. This must be null-terminated. + * @param size The size of the @p dst buffer. + * + * @returns true if the string was truncated else false (note that a size of 0 will always result + * this function returning true). + */ +static inline bool mmosal_safer_strcpy(char *dst, const char *src, size_t size) +{ + bool ret; + + if (size == 0) { + return true; + } + + strncpy(dst, src, size); + + ret = dst[size - 1] != '\0'; + dst[size - 1] = '\0'; + return ret; +} + +/** @} */ + +#ifdef __cplusplus +} +#endif + +/** @} */ diff --git a/lib/MorseWlan/include/mmpkt.h b/lib/MorseWlan/include/mmpkt.h new file mode 100644 index 000000000..cf08a16aa --- /dev/null +++ b/lib/MorseWlan/include/mmpkt.h @@ -0,0 +1,515 @@ +/* + * Copyright 2022-2024 Morse Micro + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @defgroup MMPKT Morse Micro Packet Buffer (mmpkt) API + * + * This API provides support for buffers tailored towards packets. + * + * @{ + */ + +#pragma once + +#include +#include +#include + +#include "mmosal.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Round @p x up to the next multiple of @p m (where @p m is a power of 2). + * + * @warning @p m must be a power of 2. + */ +#ifndef MM_FAST_ROUND_UP +#define MM_FAST_ROUND_UP(x, m) ((((x)-1) | ((m)-1)) + 1) +#endif + +struct mmdrv_cmd_metadata; +struct mmdrv_tx_metadata; +struct mmdrv_rx_metadata; +struct mmpkt_ops; + +/** + * Union of pointer types for mmpkt metadata. + * + * The metadata is accessed through one of these pointers, depending on which context the packet + * is being used in. + */ +union mmpkt_metadata_ptr { + /** Opaque pointer for contexts which are unaware of the specific metadata structure. */ + void *opaque; + /** Metadata for a packet which is being transmitted. */ + struct mmdrv_tx_metadata *tx; + /** Metadata for a packet which is being received. */ + struct mmdrv_rx_metadata *rx; + /** Control block for a command response sent to the host. */ + struct mmdrv_cmd_metadata *cmd; +}; + +/** + * Core mmpkt data structure. + * + * @note The contents of this data structure should never need to be accessed directly. Rather + * the various functions provided as part of this API should be used. + * + * @code + * +----------------------------------------------------------+--------------+ + * | RESERVED | Data | RESERVED | METADATA | + * +----------------------------------------------------------+--------------+ + * ^ ^ ^ ^ + * | | | | + * | |<-----------data_len--------->| | + * | start_offset | + * | | + * |<-----------------------buf_len-------------------------->| + * buf + * @endcode + */ +struct mmpkt { + /** The buffer where data is stored. */ + uint8_t *buf; + /** Length of the buffer. */ + uint32_t buf_len; + /** Offset where actual data starts in the buffer. */ + uint32_t start_offset; + /** Length of actual data in the buffer. */ + uint32_t data_len; + /** Packet metadata used by driver (context dependent). */ + union mmpkt_metadata_ptr metadata; + /** Reference to operations data structure for this mmpkt. */ + const struct mmpkt_ops *ops; + /** Pointer that can be used to construct linked lists. */ + struct mmpkt *volatile next; +}; + +/** Operations data structure for mmpkt. */ +struct mmpkt_ops { + /** Free the given mmpkt. */ + void (*free_mmpkt)(void *mmpkt); +}; + +/** + * Opened view of an mmpkt. + * + * In this implementation, this structure does not actually exist. We only use it as a pointer type + * which is incompatible with @ref mmpkt, to distinguish between functions which operate on opened + * packet views versus functions which can operate on unopened packets. + * + * In other implementations of this API, packets must be "opened" (mapped into memory) before their + * contents can be accessed. Thus the distinction between opened and unopened packets is important + * for those implementations. + */ +struct mmpktview; + +/** + * Initialize an mmpkt header with the given values. + * + * @param mmpkt mmpkt to initialize. + * @param buf Pointer to buffer. + * @param buf_len Length of @p buf. + * @param data_start_offset Initial value for @c start_offset. + * @param ops Operations data structure. + */ +static inline void mmpkt_init(struct mmpkt *mmpkt, uint8_t *buf, uint32_t buf_len, uint32_t data_start_offset, + const struct mmpkt_ops *ops) +{ + memset(mmpkt, 0, sizeof(*mmpkt)); + mmpkt->buf = buf; + mmpkt->buf_len = buf_len; + mmpkt->start_offset = data_start_offset; + mmpkt->ops = ops; +} + +/** + * Initialize an mmpkt in a single buffer using the given values. + * + * @param buf Pointer to buffer. + * @param buf_len Length of @p buf. + * @param space_at_start Amount of space to reserve at start of buffer. + * @param space_at_end Amount of space to reserve at end of buffer. + * @param metadata_size Size of metadata (0 for no metadata). + * + * @param ops Operations data structure. + * + * @note @p buf_len must be large enough to contain the @c mmkpt header, the data buffer + * (rounded up to the nearest 4 bytes) and metadata (rounded up to the nearest 4 bytes). + * + * @returns a pointer to the initialized @c mmpkt (will be the same address as @p buf) or @c NULL + * on error (@p buf length too short). + */ +static inline struct mmpkt *mmpkt_init_buf(uint8_t *buf, uint32_t buf_len, uint32_t space_at_start, uint32_t space_at_end, + uint32_t metadata_size, const struct mmpkt_ops *ops) +{ + struct mmpkt *mmpkt = (struct mmpkt *)buf; + + uint8_t *data_start; + uint32_t header_size = MM_FAST_ROUND_UP(sizeof(*mmpkt), 4); + uint32_t data_len = MM_FAST_ROUND_UP(space_at_start + space_at_end, 4); + metadata_size = MM_FAST_ROUND_UP(metadata_size, 4); + + if (header_size + data_len + metadata_size > buf_len) { + return NULL; + } + + data_start = ((uint8_t *)mmpkt) + header_size; + + mmpkt_init(mmpkt, data_start, data_len, space_at_start, ops); + + if (metadata_size != 0) { + mmpkt->metadata.opaque = data_start + data_len; + memset(mmpkt->metadata.opaque, 0, metadata_size); + } + + return mmpkt; +} + +/** + * Allocate a new mmpkt on the heap (using @ref mmosal_malloc()). + * + * @param space_at_start Amount of space to reserve at start of buffer. + * @param space_at_end Amount of space to reserve at end of buffer. + * @param metadata_size Size of metadata (0 for no metadata). + * + * @note @c start_offset will be set to @p space_at_start, and @c buf_len will be the sum + * of @p space_at_start and @p space_at_end (rounded up to a multiple of 4). + * + * @returns newly allocated mmpkt on success or @c NULL on failure. + */ +struct mmpkt *mmpkt_alloc_on_heap(uint32_t space_at_start, uint32_t space_at_end, uint32_t metadata_size); + +/** + * Release a reference to the given mmpkt. If this was the last reference (@c addition_ref_cnt + * was 0) then the mmpkt will be freed using the appropriate op callback. + * + * @param mmpkt The mmpkt to release reference to. May be @c NULL. + */ +void mmpkt_release(struct mmpkt *mmpkt); + +/** + * Open a view of the given mmpkt. + * + * Packets must be opened before the contents of their buffer can be accessed. Most of the + * functions below expect to be passed an opened view of a packet to operate on. + * + * The view must be closed by calling @ref mmpkt_close() before the packet is released by + * @ref mmpkt_release(). + * + * @param mmpkt The mmpkt to be opened. + * + * @returns a pointer representing the opened view. + */ +static inline struct mmpktview *mmpkt_open(struct mmpkt *mmpkt) +{ + return (struct mmpktview *)mmpkt; +} + +/** + * Close the given view. + * + * @param[in,out] view Pointer to a variable holding the view to be closed. + * This will be modified to indicate it is no longer valid. + */ +static inline void mmpkt_close(struct mmpktview **view) +{ + (void)(view); +} + +/** + * Get the underlying mmpkt from an opened view. + * + * @param view View of an mmpkt. + * + * @returns the underlying mmpkt. + */ +static inline struct mmpkt *mmpkt_from_view(struct mmpktview *view) +{ + return (struct mmpkt *)view; +} + +/** + * Gets a pointer to the start of the data in the mmpkt. + * + * @param view The opened mmpkt to operate on. + * + * @returns a pointer to the start of the data in the mmpkt. + */ +static inline uint8_t *mmpkt_get_data_start(struct mmpktview *view) +{ + struct mmpkt *mmpkt = (struct mmpkt *)view; + return mmpkt->buf + mmpkt->start_offset; +} + +/** + * Gets a pointer to the end of the data in the mmpkt. + * + * @param view The opened mmpkt to operate on. + * + * @returns a pointer to the end of the data in the mmpkt. + */ +static inline uint8_t *mmpkt_get_data_end(struct mmpktview *view) +{ + struct mmpkt *mmpkt = (struct mmpkt *)view; + return mmpkt->buf + mmpkt->start_offset + mmpkt->data_len; +} + +/** + * Peek the length of the data currently from an unopened mmpkt. + * + * @param mmpkt The unopened mmpkt to operate on. + * + * @returns the length of the data currently in the mmpkt (note that this is different from the + * length of the available buffer space). + */ +static inline uint32_t mmpkt_peek_data_length(struct mmpkt *mmpkt) +{ + return mmpkt->data_len; +} + +/** + * Gets the length of the data currently in the mmpkt. + * + * @param view The opened mmpkt to operate on. + * + * @returns the length of the data currently in the mmpkt (note that this is different from the + * length of the available buffer space). + */ +static inline uint32_t mmpkt_get_data_length(struct mmpktview *view) +{ + struct mmpkt *mmpkt = (struct mmpkt *)view; + return mmpkt->data_len; +} + +/** + * Returns the amount of space available for prepending to the data in the buffer. + * + * @param view The opened mmpkt to operate on. + * + * @returns the available space in bytes. + */ +static inline uint32_t mmpkt_available_space_at_start(struct mmpktview *view) +{ + struct mmpkt *mmpkt = (struct mmpkt *)view; + return mmpkt->start_offset; +} + +/** + * Returns the amount of space available for appending to the data in the buffer. + * + * @param view The opened mmpkt to operate on. + * + * @returns the available space in bytes. + */ +static inline uint32_t mmpkt_available_space_at_end(struct mmpktview *view) +{ + struct mmpkt *mmpkt = (struct mmpkt *)view; + return mmpkt->buf_len - (mmpkt->start_offset + mmpkt->data_len); +} + +/** + * Reserves space immediately before the data currently in the given mmpkt and returns + * a pointer to this space. + * + * For a function that also copies data in, see @ref mmpkt_prepend_data(). + * + * @warning @p len must be less than or equal to @ref mmpkt_available_space_at_start(). + * + * @param view The opened mmpkt to operate on. + * @param len Length of data to be prepended. + * + * @returns a pointer to the place in the buffer where the data should be put. + */ +static inline uint8_t *mmpkt_prepend(struct mmpktview *view, uint32_t len) +{ + struct mmpkt *mmpkt = (struct mmpkt *)view; + MMOSAL_ASSERT(len <= mmpkt_available_space_at_start(view)); + mmpkt->start_offset -= len; + mmpkt->data_len += len; + return mmpkt->buf + mmpkt->start_offset; +} + +/** + * Prepends the given data to the data already in the mmpkt. + * + * @warning @p len must be less than or equal to @ref mmpkt_available_space_at_start(). + * + * @warning The memory area pointed to by data must not overlap with the mmpkt data. + * + * @param view The opened mmpkt to operate on. + * @param data The data to be prepended. + * @param len Length of data to be prepended. + */ +static inline void mmpkt_prepend_data(struct mmpktview *view, const uint8_t *data, uint32_t len) +{ + uint8_t *dest = mmpkt_prepend(view, len); + memcpy(dest, data, len); +} + +/** + * Reserves space immediately after the data currently in the given mmpkt and returns + * a pointer to this space. + * + * For a function that also copies data in, see @ref mmpkt_append_data(). + * + * @warning @p len must be less than or equal to @ref mmpkt_available_space_at_end(). + * + * @param view The opened mmpkt to operate on. + * @param len Length of data to be append. + * + * @returns a pointer to the place in the buffer where the data should be put. + */ +static inline uint8_t *mmpkt_append(struct mmpktview *view, uint32_t len) +{ + struct mmpkt *mmpkt = (struct mmpkt *)view; + uint8_t *ret = mmpkt_get_data_end(view); + MMOSAL_ASSERT(len <= mmpkt_available_space_at_end(view)); + mmpkt->data_len += len; + return ret; +} + +/** + * Appends the given data to the data already in the mmpkt. + * + * @warning @p len must be less than or equal to @ref mmpkt_available_space_at_start(). + * + * @param view The opened mmpkt to operate on. + * @param data The data to be prepended. + * @param len Length of data to be prepended. + */ +static inline void mmpkt_append_data(struct mmpktview *view, const uint8_t *data, uint32_t len) +{ + uint8_t *dest = mmpkt_append(view, len); + memcpy(dest, data, len); +} + +/** + * Retrieve a reference to the metadata associated with the given mmpkt. + * + * @param mmpkt The mmpkt to operate on. + * + * @returns a reference to the mmpkt metadata. + */ +static inline union mmpkt_metadata_ptr mmpkt_get_metadata(struct mmpkt *mmpkt) +{ + return mmpkt->metadata; +} + +/** + * Remove data from the start of the mmpkt. + * + * @param view The opened mmpkt to operate on. + * @param len Length of data to remove. + * + * @returns a pointer to the removed data or NULL if the mmpkt data length was less than @p len. + */ +static inline uint8_t *mmpkt_remove_from_start(struct mmpktview *view, uint32_t len) +{ + struct mmpkt *mmpkt = (struct mmpkt *)view; + uint8_t *ret; + + if (mmpkt_get_data_length(view) < len) { + return NULL; + } + + ret = mmpkt_get_data_start(view); + + mmpkt->start_offset += len; + mmpkt->data_len -= len; + + return ret; +} + +/** + * Remove data from the end of the mmpkt. + * + * @param view The opened mmpkt to operate on. + * @param len Length of data to remove. + * + * @returns a pointer to the removed data or NULL if the mmpkt data length was less than @p len. + */ +static inline uint8_t *mmpkt_remove_from_end(struct mmpktview *view, uint32_t len) +{ + struct mmpkt *mmpkt = (struct mmpkt *)view; + uint8_t *ret; + + if (mmpkt_get_data_length(view) < len) { + return NULL; + } + + ret = mmpkt_get_data_end(view) - len; + + mmpkt->data_len -= len; + + return ret; +} + +/** + * Truncate the mmpkt data to the given length. + * + * @param mmpkt mmpkt to operate on. + * @param len New data length. (Must be less than or equal to the data length + * of the mmpkt). + */ +static inline void mmpkt_truncate(struct mmpkt *mmpkt, uint32_t len) +{ + MMOSAL_ASSERT(len <= mmpkt->data_len); + mmpkt->data_len = len; +} + +/** + * Get the `next` pointer embedded in the mmpkt. + * + * Used by the mmpkt_list structure for making linked lists of mmpkts. + * + * @param mmpkt The mmpkt to operate on. + * + * @returns pointer to the next mmpkt in the chain (may be NULL). + */ +static inline struct mmpkt *mmpkt_get_next(struct mmpkt *mmpkt) +{ + return mmpkt->next; +} + +/** + * Set the `next` pointer embedded in the mmpkt. + * + * Used by the mmpkt_list structure for making linked lists of mmpkts. + * + * @param mmpkt The mmpkt to operate on. + * @param next The next mmpkt in the chain. + */ +static inline void mmpkt_set_next(struct mmpkt *mmpkt, struct mmpkt *next) +{ + mmpkt->next = next; +} + +/** + * Check whether the given pointer is pointing inside the mmpkt's buffer. + * + * @note This checks against the full buffer, which includes any unused regions at the beginning and + * end of the buffer which do not contain valid packet data. + * + * @param view The opened mmpkt to operate on. + * @param ptr The pointer to check. + * + * @returns true if the pointer points into the buffer. + */ +static inline bool mmpkt_contains_ptr(struct mmpktview *view, const void *ptr) +{ + struct mmpkt *mmpkt = (struct mmpkt *)view; + return ((const uint8_t *)ptr >= &mmpkt->buf[0] && (const uint8_t *)ptr < &mmpkt->buf[mmpkt->buf_len]); +} + +#ifdef __cplusplus +} +#endif + +/** @} */ diff --git a/lib/MorseWlan/include/mmpkt_list.h b/lib/MorseWlan/include/mmpkt_list.h new file mode 100644 index 000000000..312db3ffc --- /dev/null +++ b/lib/MorseWlan/include/mmpkt_list.h @@ -0,0 +1,172 @@ +/* + * Copyright 2022-2024 Morse Micro + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @ingroup MMPKT + * + * @{ + */ + +#pragma once + +#include + +#include "mmpkt.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** Structure that can be used as the head of a linked list of mmpkts that counts its length. */ +struct mmpkt_list { + /** First mmpkt in the list. */ + struct mmpkt *volatile head; + /** Last mmpkt in the list. */ + struct mmpkt *volatile tail; + /** Length of the list. */ + volatile uint32_t len; +}; + +/** Static initializer for @ref mmpkt_list. */ +#define MMPKT_LIST_INIT \ + { \ + NULL, NULL, 0 \ + } + +/** + * Initialization function for @ref mmpkt_list, for cases where @c MMPKT_LIST_INIT + * cannot be used. + * + * @param list The mmpkt_list to init. + */ +static inline void mmpkt_list_init(struct mmpkt_list *list) +{ + list->head = NULL; + list->tail = NULL; + list->len = 0; +} + +/** + * Add an mmpkt to the start of an mmpkt list. + * + * @param list The list to prepend to. + * @param mmpkt The mmpkt to prepend. + */ +void mmpkt_list_prepend(struct mmpkt_list *list, struct mmpkt *mmpkt); + +/** + * Add an mmpkt to the end of an mmpkt list. + * + * @param list The list to append to. + * @param mmpkt The mmpkt to append. + */ +void mmpkt_list_append(struct mmpkt_list *list, struct mmpkt *mmpkt); + +/** + * Remove an mmpkt from an mmpkt list. + * + * @param list The list to remove from. + * @param mmpkt The mmpkt to remove. + */ +void mmpkt_list_remove(struct mmpkt_list *list, struct mmpkt *mmpkt); + +/** + * Remove the mmpkt at the head of the list and return it. + * + * @param list The list to dequeue from. + * + * @returns the dequeued mmpkt, or @c NULL if the list is empty. + */ +struct mmpkt *mmpkt_list_dequeue(struct mmpkt_list *list); + +/** + * Remove the mmpkt at the tail of the list and return it. + * + * @param list The list to dequeue from. + * + * @returns the dequeued mmpkt, or @c NULL if the list is empty. + */ +struct mmpkt *mmpkt_list_dequeue_tail(struct mmpkt_list *list); + +/** + * Remove all mmpkts from the list and return as a linked list. + * + * @param list The list to dequeue from. + * + * @returns the dequeued mmpkts, or @c NULL if the list is empty. + */ +static inline struct mmpkt *mmpkt_list_dequeue_all(struct mmpkt_list *list) +{ + struct mmpkt *head = list->head; + list->head = NULL; + list->tail = NULL; + list->len = 0; + return head; +} + +/** + * Checks whether the given mmpkt list is empty. + * + * @param list The list to check. + * + * @returns @c true if the list is empty, else @c false. + */ +static inline bool mmpkt_list_is_empty(struct mmpkt_list *list) +{ + return (list->head == NULL); +} + +/** + * Returns the head of the mmpkt list. + * + * @param list The list to peek into. + * + * @returns the mmpkt at the head of the list. + */ +static inline struct mmpkt *mmpkt_list_peek(struct mmpkt_list *list) +{ + return list->head; +} + +/** + * Returns the tail of the mmpkt list. + * + * @param list The list to peek into. + * + * @returns the mmpkt at the tail of the list. + */ +static inline struct mmpkt *mmpkt_list_peek_tail(struct mmpkt_list *list) +{ + return list->tail; +} + +/** + * Free all the packets in the given list and reset the list to empty state. + * + * @param list The list to clear. + */ +void mmpkt_list_clear(struct mmpkt_list *list); + +/** + * Safely walk the mmpkt list. + * + * @warning This macro cannot be used following an if statement with no parentheses if there + * is an else clause. For example, do not do: + * `if (x) MMPKT_LIST_WALK(a,b,c) else foo();` -- instead: + * `if (x) { MMPKT_LIST_WALK(a,b,c) } else foo();` + */ +#define MMPKT_LIST_WALK(_lst, _wlk, _nxt) \ + if ((_lst)->head != NULL) /* NOLINT(readability/braces) */ \ + for (_wlk = (_lst)->head, _nxt = mmpkt_get_next(_wlk); _wlk != NULL; \ + _wlk = _nxt, _nxt = _wlk ? mmpkt_get_next(_wlk) : NULL) + +#ifdef __cplusplus +} +#endif + +/** + * @} + */ diff --git a/lib/MorseWlan/include/mmwlan.h b/lib/MorseWlan/include/mmwlan.h new file mode 100644 index 000000000..f6c69dccf --- /dev/null +++ b/lib/MorseWlan/include/mmwlan.h @@ -0,0 +1,2178 @@ +/* + * Copyright 2021-2024 Morse Micro + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @defgroup MMWLAN Morse Micro Wireless LAN (mmwlan) API + * + * Wireless LAN control and datapath. + * + * @warning Aside from specific exceptions, the functions in this API must not be called + * concurrently (e.g., from different thread contexts). The exception to this + * is the TX API (@ref mmwlan_tx() and @ref mmwlan_tx_tid()). + * + * @section MMWLAN_THREADS Thread priorities + * + * The following table documents the threads created by the Morse WLAN driver and Upper MAC + * included in morselib. + * + * | Thread | Thread Name | Priority | + * | ----------------- | ----------------------- | ------------------------- | + * | SPI driver | @c spi_irq | @ref MMOSAL_TASK_PRI_HIGH | + * | Morse driver | @c drv | @ref MMOSAL_TASK_PRI_HIGH | + * | WLAN event loop | @c evtloop | @ref MMOSAL_TASK_PRI_HIGH | + * | Morse health check| @c health | @ref MMOSAL_TASK_PRI_LOW | + * + * Note that to get the best performance, the WLAN driver/UMAC threads run at a high priority + * while it is expected that application threads run at a lower priority. + * + * @{ + */ + +#pragma once + +#include +#include +#include +#include +#include + +#include "mmpkt.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** Enumeration of status return codes. */ +enum mmwlan_status { + /** The operation was successful. */ + MMWLAN_SUCCESS, + /** The operation failed with an unspecified error. */ + MMWLAN_ERROR, + /** The operation failed due to an invalid argument. */ + MMWLAN_INVALID_ARGUMENT, + /** Functionality is temporarily unavailable. */ + MMWLAN_UNAVAILABLE, + /** Unable to proceed because channel list has not been set. + * @see mmwlan_set_channel_list(). */ + MMWLAN_CHANNEL_LIST_NOT_SET, + /** Failed due to memory allocation failure. */ + MMWLAN_NO_MEM, + /** Failed due to timeout */ + MMWLAN_TIMED_OUT, + /** Used to indicate that a call to @ref mmwlan_sta_disable() did not shutdown + * the transceiver. */ + MMWLAN_SHUTDOWN_BLOCKED, + /** Attempted to tune to a channel that was not in the regulatory database or not supported. */ + MMWLAN_CHANNEL_INVALID, + /** The request could not be completed because the given resource was not found. */ + MMWLAN_NOT_FOUND, + /** Indicates that the operation failed because the UMAC was not running (e.g., the + * device was not booted). */ + MMWLAN_NOT_RUNNING, +}; + +/** Maximum allowable length of an SSID. */ +#define MMWLAN_SSID_MAXLEN (32) + +/** Maximum allowable length of a passphrase when connecting to an AP. */ +#define MMWLAN_PASSPHRASE_MAXLEN (100) + +/** Maximum allowable Restricted Access Window (RAW) priority for STA. */ +#define MMWLAN_RAW_MAX_PRIORITY (7) + +/** Length of a WLAN MAC address. */ +#define MMWLAN_MAC_ADDR_LEN (6) + +/** Maximum allowable number of EC Groups. */ +#define MMWLAN_MAX_EC_GROUPS (4) + +/** Size of an 802.11 OUI element in octets. */ +#define MMWLAN_OUI_SIZE (3) + +/** Default Background scan short interval in seconds. + * Setting to 0 will disable background scanning. */ +#define DEFAULT_BGSCAN_SHORT_INTERVAL_S (0) + +/** Default Background scan signal threshold in dBm. */ +#define DEFAULT_BGSCAN_THRESHOLD_DBM (0) + +/** Default Background scan long interval in seconds. + * Setting to 0 will disable background scanning. */ +#define DEFAULT_BGSCAN_LONG_INTERVAL_S (0) + +/** Default Target Wake Time (TWT) interval in micro seconds. */ +#define DEFAULT_TWT_WAKE_INTERVAL_US (300000000) + +/** Default min Target Wake Time (TWT) duration in micro seconds. */ +#define DEFAULT_TWT_MIN_WAKE_DURATION_US (65280) + +/** + * The maximum length of a user-specified payload (bytes) for Standby status + * frames. + */ +#define MMWLAN_STANDBY_STATUS_FRAME_USER_PAYLOAD_MAXLEN (64) + +/** The maximum allowed length of a user filter to apply to wake frames */ +#define MMWLAN_STANDBY_WAKE_FRAME_USER_FILTER_MAXLEN (64) + +/** Enumeration of supported security types. */ +enum mmwlan_security_type { + /** Open (no security) */ + MMWLAN_OPEN, + /** Opportunistic Wireless Encryption (OWE) */ + MMWLAN_OWE, + /** Simultaneous Authentication of Equals (SAE) */ + MMWLAN_SAE, +}; + +/** Enumeration of supported 802.11 power save modes. */ +enum mmwlan_ps_mode { + /** Power save disabled */ + MMWLAN_PS_DISABLED, + /** Power save enabled */ + MMWLAN_PS_ENABLED +}; + +/** Enumeration of Protected Management Frame (PMF) modes (802.11w). */ +enum mmwlan_pmf_mode { + /** Protected management frames must be used */ + MMWLAN_PMF_REQUIRED, + /** No protected management frames */ + MMWLAN_PMF_DISABLED +}; + +/** Enumeration of Centralized Authentication Control (CAC) modes. */ +enum mmwlan_cac_mode { + /** CAC disabled */ + MMWLAN_CAC_DISABLED, + /** CAC enabled */ + MMWLAN_CAC_ENABLED +}; + +/** + * Enumeration of Target Wake Time (TWT) modes. + * + * @note TWT is only supported as a requester. + */ +enum mmwlan_twt_mode { + /** TWT disabled */ + MMWLAN_TWT_DISABLED, + /** TWT enabled as a requester */ + MMWLAN_TWT_REQUESTER, + /** TWT enabled as a responder */ + MMWLAN_TWT_RESPONDER +}; + +/** Enumeration of Target Wake Time (TWT) setup commands. */ +enum mmwlan_twt_setup_command { + /** TWT setup request command */ + MMWLAN_TWT_SETUP_REQUEST, + /** TWT setup suggest command */ + MMWLAN_TWT_SETUP_SUGGEST, + /** TWT setup demand command */ + MMWLAN_TWT_SETUP_DEMAND +}; + +/** + * @defgroup MMWLAN_REGDB WLAN Regulatory Database API + * + * @{ + */ + +/** + * If either the global or s1g operating class is set to this, the operating class will + * not be checked when associating to an AP. + */ +#define MMWLAN_SKIP_OP_CLASS_CHECK -1 + +/** Regulatory domain information about an S1G channel. */ +struct mmwlan_s1g_channel { + /** Center frequency of the channel (in Hz). */ + uint32_t centre_freq_hz; + /** STA Duty Cycle (in 100th of %). */ + uint16_t duty_cycle_sta; + /** Boolean indicating whether to omit control response frames from duty cycle. */ + bool duty_cycle_omit_ctrl_resp; + /** Global operating class. If @ref MMWLAN_SKIP_OP_CLASS_CHECK, check is skipped */ + int16_t global_operating_class; + /** S1G operating class. If @ref MMWLAN_SKIP_OP_CLASS_CHECK, check is skipped */ + int16_t s1g_operating_class; + /** S1G channel number. */ + uint8_t s1g_chan_num; + /** Channel operating bandwidth (in MHz). */ + uint8_t bw_mhz; + /** Maximum transmit power (EIRP in dBm). */ + int8_t max_tx_eirp_dbm; + /** The length of time to close the tx window between packets (in microseconds). */ + uint32_t pkt_spacing_us; + /** The minimum packet airtime duration to trigger spacing (in microseconds). */ + uint32_t airtime_min_us; + /** The maximum allowable packet airtime duration (in microseconds). */ + uint32_t airtime_max_us; +}; + +/** A list of S1G channels supported by a given regulatory domain. */ +struct mmwlan_s1g_channel_list { + /** Two character country code (null-terminated) used to identify the regulatory domain. */ + uint8_t country_code[3]; + /** The number of channels in the list. */ + unsigned num_channels; + /** The channel data. */ + const struct mmwlan_s1g_channel *channels; +}; + +/** + * Regulatory database data structure. This is a list of @c mmwlan_s1g_channel_list structs, where + * each channel list corresponds to a regulatory domain. + */ +struct mmwlan_regulatory_db { + /** Number of regulatory domains in the database. */ + unsigned num_domains; + + /** The regulatory domain data */ + const struct mmwlan_s1g_channel_list **domains; +}; + +/** + * Look up the given country code in the regulatory database and return the matching channel + * list if found. + * + * @param db The regulatory database. + * @param country_code Country code to look up. + * + * @returns the matching channel list if found, else NULL. + */ +static inline const struct mmwlan_s1g_channel_list *mmwlan_lookup_regulatory_domain(const struct mmwlan_regulatory_db *db, + const char *country_code) +{ + unsigned ii; + + if (db == NULL) { + return NULL; + } + + for (ii = 0; ii < db->num_domains; ii++) { + const struct mmwlan_s1g_channel_list *channel_list = db->domains[ii]; + if (channel_list->country_code[0] == country_code[0] && channel_list->country_code[1] == country_code[1]) { + return channel_list; + } + } + return NULL; +} + +/** + * Set the list of channels that are supported by the regulatory domain in which the device + * resides. + * + * @note Must be invoked after WLAN initialization (see @ref mmwlan_init()) but only when inactive + * (i.e., STA not enabled). + * + * @warning This function takes a reference to the given channel list. It expects the channel + * list to remain valid in memory as long as WLAN is in use, or until a new channel + * list is configured. + * + * @param channel_list The channel list to set. The list must remain valid in memory. + * + * @return @ref MMWLAN_SUCCESS if the channel list was valid and updated successfully, + * @ref MMWLAN_UNAVAILABLE if the WLAN subsystem was currently active. + */ +enum mmwlan_status mmwlan_set_channel_list(const struct mmwlan_s1g_channel_list *channel_list); + +/** @} */ + +/** + * @defgroup MMWLAN_CTRL WLAN Control API + * + * @{ + */ + +/** Maximum length of the Morselib version string. */ +#define MMWLAN_MORSELIB_VERSION_MAXLEN (32) +/** Maximum length of the firmware version string. */ +#define MMWLAN_FW_VERSION_MAXLEN (32) + +/** Structure for retrieving version information from the mmwlan subsystem. */ +struct mmwlan_version { + /** Morselib version string. Null terminated. */ + char morselib_version[MMWLAN_MORSELIB_VERSION_MAXLEN]; + /** Morse transceiver firmware version string. Null terminated. */ + char morse_fw_version[MMWLAN_FW_VERSION_MAXLEN]; + /** Morse transceiver chip ID. */ + uint32_t morse_chip_id; +}; + +/** + * Retrieve version information from morselib and the connected Morse transceiver. + * + * @param version The data structure to fill out with version information. + * + * @note If the Morse transceiver has not previously been powered on then the @c fw_version + * field of @p version will be set to a zero length string. + * + * @returns @c MMWLAN_SUCCESS on success else an error code. + */ +enum mmwlan_status mmwlan_get_version(struct mmwlan_version *version); + +/** Maximum length of a BCF board description string (excluding null terminator). */ +#define MMWLAN_BCF_BOARD_DESC_MAXLEN (31) +/** Maximum length of a BCF build version string (excluding null terminator). */ +#define MMWLAN_BCF_BUILD_VERSION_MAXLEN (31) + +/** Board configuration file (BCF) metadata. */ +struct mmwlan_bcf_metadata { + /** BCF semantic version. */ + struct { + /** Major version field. */ + uint16_t major; + /** Minor version field. */ + uint8_t minor; + /** Patch version field. */ + uint8_t patch; + } version; + + /** + * Board description string. This is a free form text field included in the BCF. + * + * This string will be null-terminated and if it exceeds @ref MMWLAN_BCF_BOARD_DESC_MAXLEN + * characters in length it will be truncated. + */ + char board_desc[MMWLAN_BCF_BOARD_DESC_MAXLEN + 1]; + + /** + * Build version string. + * + * This string will be null-terminated and if it exceeds @ref MMWLAN_BCF_BOARD_DESC_MAXLEN + * characters in length it will be truncated. + */ + char build_version[MMWLAN_BCF_BUILD_VERSION_MAXLEN + 1]; +}; + +/** + * Read the metadata from the board configuration file (BCF)r. + * + * @param metadata Pointer to a metadata data structure to be filled out on success. + * + * @returns @c MMWLAN_SUCCESS on success else an error code. + */ +enum mmwlan_status mmwlan_get_bcf_metadata(struct mmwlan_bcf_metadata *metadata); + +/** + * Override the maximum TX power. If no override is specified then the maximum TX power used + * will be the maximum TX power allowed for the channel in the current regulatory domain. + * + * @note Must be invoked after WLAN initialization (see @ref mmwlan_init()). Only takes + * effect when switching channel (e.g., during scan or AP connection procedure). + * + * @note This will not increase the TX power over the maximum allowed for the current channel + * in the configured regulatory domain. Therefore, this override in effect will only + * reduce the maximum TX power and cannot increase it. + * + * @param tx_power_dbm The maximum TX power override to set (in dBm). Set to zero to disable + * the override. + * + * @return @ref MMWLAN_SUCCESS if the country code was valid and updated successfully, + * @ref MMWLAN_UNAVAILABLE if the WLAN subsystem was currently active. + */ +enum mmwlan_status mmwlan_override_max_tx_power(uint16_t tx_power_dbm); + +/** + * Set the RTS threshold. + * + * When packets larger than the RTS threshold are transmitted they are protected by an RTS/CTS + * exchange. + * + * @param rts_threshold The RTS threshold (in octets) to set, or 0 to disable. + * + * @return @ref MMWLAN_SUCCESS on success, else an appropriate error code. + */ +enum mmwlan_status mmwlan_set_rts_threshold(unsigned rts_threshold); + +/** + * Sets whether or not Short Guard Interval (SGI) support is enabled. Defaults to enabled + * if not set otherwise. + * + * @note This will not force use of SGI, only enable support for it. The rate control algorithm + * will make the decision as to which guard interval to use unless explicitly overridden + * by @ref mmwlan_ate_override_rate_control(). + * + * @note This must only be invoked when MMWLAN is inactive (i.e., STA mode not enabled). + * + * @param sgi_enabled Boolean value indicating whether SGI support should be enabled. + * + * @return @ref MMWLAN_SUCCESS on success, else an appropriate error code. + */ +enum mmwlan_status mmwlan_set_sgi_enabled(bool sgi_enabled); + +/** + * Sets whether or not sub-band support is enabled for transmit. Defaults to enabled + * if not set otherwise. + * + * @note This will not force use of sub-bands, only enable support for it. The rate control + * algorithm will make the decision as to which bandwidth to use unless explicitly overridden + * by @ref mmwlan_ate_override_rate_control(). + * + * @note This must only be invoked when MMWLAN is inactive (i.e., STA mode not enabled). + * + * @param subbands_enabled Boolean value indicating whether sub-band support should be enabled. + * + * @return @ref MMWLAN_SUCCESS on success, else an appropriate error code. + */ +enum mmwlan_status mmwlan_set_subbands_enabled(bool subbands_enabled); + +/** + * Sets whether or not the 802.11 power save is enabled. Defaults to @ref MMWLAN_PS_ENABLED + * + * @param mode enum indicating which 802.11 power save mode to use. + * + * @return @ref MMWLAN_SUCCESS on success, else an appropriate error code. + */ +enum mmwlan_status mmwlan_set_power_save_mode(enum mmwlan_ps_mode mode); + +/** + * Sets whether or not Aggregated MAC Protocol Data Unit (A-MPDU) support is enabled. + * This defaults to enabled, if not set otherwise. + * + * @note This must only be invoked when MMWLAN is inactive (i.e., STA mode not enabled). + * + * @param ampdu_enabled Boolean value indicating whether AMPDU support should be enabled. + * + * @return @ref MMWLAN_SUCCESS on success, else an appropriate error code. + */ +enum mmwlan_status mmwlan_set_ampdu_enabled(bool ampdu_enabled); + +/** + * Minimum value of fragmentation threshold that can be set with + * @ref mmwlan_set_fragment_threshold(). + */ +#define MMWLAN_MINIMUM_FRAGMENT_THRESHOLD (256) + +/** + * Set the Fragmentation threshold. + * + * Maximum length of the frame, beyond which packets must be fragmented into two or more frames. + * + * @note Even if the fragmentation threshold is set to 0 (disabled), fragmentation may still occur + * if a given packet is too large to be transmitted at the selected rate. + * + * @warning Setting a fragmentation threshold may have unintended side effects due to restrictions + * at lower bandwidths and MCS rates. In normal operation the fragmentation threshold + * should be disabled, in which case packets will be fragmented automatically as necessary + * based on the selected rate. + * + * @param fragment_threshold The fragmentation threshold (in octets) to set, + * or zero to disable the fragmentation threshold. Minimum value + * (if not zero) is given by @ref MMWLAN_MINIMUM_FRAGMENT_THRESHOLD. + * + * @return @ref MMWLAN_SUCCESS on success, else an appropriate error code. + */ +enum mmwlan_status mmwlan_set_fragment_threshold(unsigned fragment_threshold); + +/** + * Scan configuration data structure. + * + * Use @ref MMWLAN_SCAN_CONFIG_INIT for initialization. For example: + * + * @code{.c} + * struct mmwlan_scan_config scan_config = MMWLAN_SCAN_CONFIG_INIT; + * @endcode + * + * @see mmwlan_set_scan_config() + */ +struct mmwlan_scan_config { + /** + * Set the per-channel dwell time to use for scans that are requested internally within the + * mmwlan driver (e.g., when connecting or background scanning). + * + * @note This does not affect scans requested with the @ref mmwlan_scan_request(). + */ + uint32_t dwell_time_ms; +}; + +/** Initializer for @ref mmwlan_scan_config. */ +#define MMWLAN_SCAN_CONFIG_INIT \ + { \ + MMWLAN_SCAN_DEFAULT_DWELL_TIME_MS \ + } + +/** + * Update the scan configuration with the given settings. + * + * @param config The new configuration to set. + * + * @return @ref MMWLAN_SUCCESS on success, else an appropriate error code. + */ +enum mmwlan_status mmwlan_set_scan_config(const struct mmwlan_scan_config *config); + +/** Structure for storing Target Wake Time (TWT) configuration arguments. */ +struct mmwlan_twt_config_args { + /** Target Wake Time (TWT) modes, @ref mmwlan_twt_mode. */ + enum mmwlan_twt_mode twt_mode; + /** + * TWT service period interval in micro seconds. + * This parameter will be ignored if @c twt_wake_interval_mantissa or + * @c twt_wake_interval_exponent is non-zero. + */ + uint64_t twt_wake_interval_us; + /** + * TWT Wake interval mantissa + * If non-zero, this parameter will be used to calculate @c twt_wake_interval_us. + */ + uint16_t twt_wake_interval_mantissa; + /** + * TWT Wake interval exponent + * If non-zero, this parameter will be used to calculate @c twt_wake_interval_us. + */ + uint8_t twt_wake_interval_exponent; + /** Minimum TWT wake duration in micro seconds. */ + uint32_t twt_min_wake_duration_us; + /** TWT setup command, @ref mmwlan_twt_setup_command. */ + enum mmwlan_twt_setup_command twt_setup_command; +}; + +/** + * Initializer for @ref mmwlan_twt_config_args. + * + * For example: + * + * @code{c} + * struct mmwlan_twt_config_args twt_config_args = MMWLAN_TWT_CONFIG_ARGS_INIT; + * @endcode + */ +#define MMWLAN_TWT_CONFIG_ARGS_INIT \ + { \ + MMWLAN_TWT_DISABLED, DEFAULT_TWT_WAKE_INTERVAL_US, 0, 0, DEFAULT_TWT_MIN_WAKE_DURATION_US, MMWLAN_TWT_SETUP_REQUEST \ + } + +/** + * Add configurations for Target Wake Time (TWT). + * + * @note This is used to add TWT configuration for a new TWT agreement. + * This function should be invoked before @ref mmwlan_sta_enable. + * + * @param twt_config_args TWT configuration arguments @ref mmwlan_twt_config_args. + * + * @return @ref MMWLAN_SUCCESS on success, else an appropriate error code. + */ +enum mmwlan_status mmwlan_twt_add_configuration(const struct mmwlan_twt_config_args *twt_config_args); + +/** + * Arguments data structure for @ref mmwlan_boot(). + * + * This structure should be initialized using @ref MMWLAN_BOOT_ARGS_INIT for sensible + * default values, particularly for forward compatibility with new releases that may add + * new fields to the struct. For example: + * + * @note This struct currently does not include any sensible arguments yet, and is provided + * for forwards compatibility with potential future API extensions. + * + * @code{.c} + * enum mmwlan_status status; + * struct mmwlan_boot_args boot_args = MMWLAN_BOOT_ARGS_INIT; + * // HERE: initialize arguments + * status = mmwlan_boot(&boot_args); + * @endcode + */ +struct mmwlan_boot_args { + /** Note this field should not be used and will be removed in future. */ + uint8_t reserved; +}; + +/** + * Initializer for @ref mmwlan_boot_args. + * + * @see mmwlan_boot_args + */ +#define MMWLAN_BOOT_ARGS_INIT \ + { \ + 0 \ + } + +/** + * Boot the Morse Micro transceiver and leave it in an idle state. + * + * @note In general, it is not necessary to use this function as @ref mmwlan_sta_enable() + * will automatically boot the chip if required. It may be used, for example, to power + * on the chip for production test, etc. + * + * @warning Channel list must be set before booting the transceiver. @ref mmwlan_set_channel_list(). + * + * @param args Boot arguments. May be @c NULL, in which case default values will be used. + * + * @return @ref MMWLAN_SUCCESS on success, else an appropriate error code. + */ +enum mmwlan_status mmwlan_boot(const struct mmwlan_boot_args *args); + +/** + * Perform a clean shutdown of the Morse Micro transceiver, including cleanly disconnecting + * from a connected AP, if necessary. + * + * Has no effect if the transceiver is already shutdown. + * + * @return @ref MMWLAN_SUCCESS on success, else an appropriate error code. + */ +enum mmwlan_status mmwlan_shutdown(void); + +/** + * Enumeration of states in STA mode. + */ +enum mmwlan_sta_state { + MMWLAN_STA_DISABLED, + MMWLAN_STA_CONNECTING, + MMWLAN_STA_CONNECTED, +}; + +/** + * Enumeration of S1G non-AP STA types. + */ +enum mmwlan_station_type { + MMWLAN_STA_TYPE_SENSOR = 0x01, + MMWLAN_STA_TYPE_NON_SENSOR = 0x02, +}; + +/** Result of the scan request. */ +struct mmwlan_scan_result { + /** RSSI of the received frame. */ + int16_t rssi; + /** Pointer to the BSSID field within the Probe Response frame. */ + const uint8_t *bssid; + /** Pointer to the SSID within the SSID IE of the Probe Response frame. */ + const uint8_t *ssid; + /** Pointer to the start of the Information Elements within the Probe Response frame. */ + const uint8_t *ies; + /** Value of the Beacon Interval field. */ + uint16_t beacon_interval; + /** Value of the Capability Information field. */ + uint16_t capability_info; + /** Length of the Information Elements (@c ies). */ + uint16_t ies_len; + /** Length of the SSID (@c ssid). */ + uint8_t ssid_len; + /** Center frequency in Hz of the channel where the frame was received. */ + uint32_t channel_freq_hz; + /** Bandwidth, in MHz, where the frame was received. */ + uint8_t bw_mhz; + /** Operating bandwidth, in MHz, of the access point. */ + uint8_t op_bw_mhz; + /** TSF timestamp in the Probe Response frame. */ + uint64_t tsf; +}; + +/** mmwlan scan rx callback function prototype. */ +typedef void (*mmwlan_scan_rx_cb_t)(const struct mmwlan_scan_result *result, void *arg); + +/** STA status callback function prototype. */ +typedef void (*mmwlan_sta_status_cb_t)(enum mmwlan_sta_state sta_state); + +/** + * Arguments data structure for @ref mmwlan_sta_enable(). + * + * This structure should be initialized using @ref MMWLAN_STA_ARGS_INIT for sensible + * default values, particularly for forward compatibility with new releases that may add + * new fields to the struct. For example: + * + * @code{.c} + * enum mmwlan_status status; + * struct mmwlan_sta_args sta_args = MMWLAN_STA_ARGS_INIT; + * // HERE: initialize arguments + * status = mmwlan_sta_enable(&sta_args); + * @endcode + */ +struct mmwlan_sta_args { + /** SSID of the AP to connect to. */ + uint8_t ssid[MMWLAN_SSID_MAXLEN]; + /** Length of the SSID. */ + uint16_t ssid_len; + /** + * BSSID of the AP to connect to. + * If non-zero, the STA will only connect to an AP that matches this value. + */ + uint8_t bssid[MMWLAN_MAC_ADDR_LEN]; + /** Type of security to use. If @c MMWLAN_SAE then a @c passphrase must be specified. */ + enum mmwlan_security_type security_type; + /** Passphrase (only used if @c security_type is @c MMWLAN_SAE, otherwise ignored. */ + char passphrase[MMWLAN_PASSPHRASE_MAXLEN + 1]; + /** Length of @c passphrase. May be zero if @c passphrase is null-terminated. */ + uint16_t passphrase_len; + /** Protected Management Frame mode to use (802.11w) */ + enum mmwlan_pmf_mode pmf_mode; + /** + * Priority used by the AP to assign a STA to a Restricted Access Window (RAW) group. + * Valid range is 0 - @ref MMWLAN_RAW_MAX_PRIORITY, or -1 to disable RAW. + */ + int16_t raw_sta_priority; + /** S1G non-AP STA type. For valid STA types, @ref mmwlan_station_type */ + enum mmwlan_station_type sta_type; + /** + * Preference list of enabled elliptic curve groups for SAE and OWE. + * By default (if this parameter is not set), the mandatory group 19 is preferred. + */ + int sae_owe_ec_groups[MMWLAN_MAX_EC_GROUPS]; + /** Whether Centralized Authentication Controlled is enabled on the STA. */ + enum mmwlan_cac_mode cac_mode; + /** + * Background scan short interval, measured in seconds. + * + * When the signal strength falls below @c bgscan_signal_threshold_dbm, this interval + * will be used between iterations of background scan. After several iterations, or + * if the signal threshold increases above @c bgscan_signal_threshold_dbm background + * scan will return to using the long interval. + * + * @note Setting this to zero will disable background scanning. + */ + uint16_t bgscan_short_interval_s; + /** + * Background scan signal strength threshold that switches between short and long intervals. + */ + int bgscan_signal_threshold_dbm; + /** + * Background scan long interval, measured in seconds. + * + * When the signal strength is above @c bgscan_signal_threshold_dbm, this interval + * will be used between iterations of background scan. If the signal threshold + * falls below @c bgscan_signal_threshold_dbm, background scan will use the short + * interval. + * + * @note Setting this to zero will disable background scanning. + */ + uint16_t bgscan_long_interval_s; + /** Optional callback for scan results which are received during the connection process. */ + mmwlan_scan_rx_cb_t scan_rx_cb; + /** Opaque argument to be passed to @ref scan_rx_cb. */ + void *scan_rx_cb_arg; +}; + +/** + * Initializer for @ref mmwlan_sta_args. + * + * @see mmwlan_sta_args + */ +#define MMWLAN_STA_ARGS_INIT \ + { \ + {0}, 0, {0}, MMWLAN_OPEN, {0}, 0, MMWLAN_PMF_REQUIRED, -1, MMWLAN_STA_TYPE_NON_SENSOR, {0}, MMWLAN_CAC_DISABLED, \ + DEFAULT_BGSCAN_SHORT_INTERVAL_S, DEFAULT_BGSCAN_THRESHOLD_DBM, DEFAULT_BGSCAN_LONG_INTERVAL_S, NULL, NULL \ + } + +/** + * Enable station mode. + * + * This will power on the transceiver then initiate connection to the given Access Point. If + * station mode is already enabled when this function is invoked then it will disconnect from + * (if already connected) and initiate connection to the given AP. + * + * @note The STA status callback (@p sta_status_cb) must not block and MMWLAN API functions + * may not be invoked from the callback. + * + * @warning Channel list must be set before enabling station mode. @ref mmwlan_set_channel_list(). + * + * @param args STA arguments (e.g., SSID, etc.). See @ref mmwlan_sta_args. + * @param sta_status_cb Optional callback to be invoked on STA state changes. May be @c NULL. + * + * @return @ref MMWLAN_SUCCESS on success, else an appropriate error code. + */ +enum mmwlan_status mmwlan_sta_enable(const struct mmwlan_sta_args *args, mmwlan_sta_status_cb_t sta_status_cb); + +/** + * Disable station mode. + * + * This will disconnect from the AP. It will also shut down the transceiver if nothing else + * is holding it open. Note that if the transceiver was booted by @c mmwlan_boot() then + * this function will shut down the transceiver. + * + * @return @ref MMWLAN_SUCCESS if successful and the transceiver was also shut down, + * @ref MMWLAN_SHUTDOWN_BLOCKED if successful and the transceiver was not shut down, + * else an appropriate error code. + */ +enum mmwlan_status mmwlan_sta_disable(void); + +/** + * Gets the current WLAN STA state. + * + * @return the current WLAN STA state. + */ +enum mmwlan_sta_state mmwlan_get_sta_state(void); + +/** Default value for @c mmwlan_scan_args.dwell_time_ms. Note that reducing the dwell time + * below this value may impact scan reliability. */ +#define MMWLAN_SCAN_DEFAULT_DWELL_TIME_MS (105) + +/** Minimum value for @c mmwlan_scan_args.dwell_time_ms. */ +#define MMWLAN_SCAN_MIN_DWELL_TIME_MS (15) + +/** + * Enumeration of states in Scan mode. + */ +enum mmwlan_scan_state { + /** Scan was successful and all channels were scanned. */ + MMWLAN_SCAN_SUCCESSFUL, + /** Scan was incomplete. One or more channels may have been scanned and therefore an + * incomplete set of scan results may still have been received. */ + MMWLAN_SCAN_TERMINATED, + /** Scanning in progress. */ + MMWLAN_SCAN_RUNNING, +}; + +/** mmwlan scan complete callback function prototype. */ +typedef void (*mmwlan_scan_complete_cb_t)(enum mmwlan_scan_state scan_state, void *arg); + +/** + * Structure to hold scan arguments. This structure should be initialized using + * @ref MMWLAN_SCAN_ARGS_INIT for forward compatibility. + */ +struct mmwlan_scan_args { + /** + * Time to dwell on a channel waiting for probe responses/beacons. + * + * @note This includes the time it takes to actually tune to the channel. + */ + uint32_t dwell_time_ms; + /** + * Extra Information Elements to include in Probe Request frames. + * May be @c NULL if @c extra_ies_len is zero. + */ + uint8_t *extra_ies; + /** Length of @c extra_ies. */ + size_t extra_ies_len; + /** + * SSID used for scan. + * May be @c NULL, with @c ssid_len set to zero for an undirected scan. + */ + uint8_t ssid[MMWLAN_SSID_MAXLEN]; + /** Length of the SSID. */ + uint16_t ssid_len; +}; + +/** + * Initializer for @ref mmwlan_scan_args. + * + * For example: + * + * @code{c} + * struct mmwlan_scan_args scan_args = MMWLAN_SCAN_ARGS_INIT; + * @endcode + */ +#define MMWLAN_SCAN_ARGS_INIT \ + { \ + MMWLAN_SCAN_DEFAULT_DWELL_TIME_MS, NULL, 0, {0}, 0 \ + } + +/** + * Structure to hold arguments specific to a given instance of a scan. + */ +struct mmwlan_scan_req { + /** Scan response receive callback. Must not be @c NULL. */ + mmwlan_scan_rx_cb_t scan_rx_cb; + /** Scan complete callback. Must not be @c NULL. */ + mmwlan_scan_complete_cb_t scan_complete_cb; + /** Opaque argument to be passed to the callbacks. */ + void *scan_cb_arg; + /** Scan arguments to be used @ref mmwlan_scan_args. */ + struct mmwlan_scan_args args; +}; + +/** + * Initializer for @ref mmwlan_scan_req. + * + * For example: + * + * @code{c} + * struct mmwlan_scan_req scan_req = MMWLAN_SCAN_REQ_INIT; + * @endcode + */ +#define MMWLAN_SCAN_REQ_INIT \ + { \ + NULL, NULL, NULL, MMWLAN_SCAN_ARGS_INIT \ + } + +/** + * Request a scan. + * + * If the transceiver is not already powered on, it will be powered on before the scan is + * initiated. The power on procedure will block this function. The transceiver will remain + * powered on after scan completion and must be shutdown by invoking @ref mmwlan_shutdown(). + * + * @note Just because a scan is requested does not mean it will happen immediately. + * It may take some time for the request to be serviced. + * + * @note The scan request may be rejected, in which case the complete callback will be + * invoked immediately with an error status. + * + * @param scan_req Scan request instance. See @ref mmwlan_scan_req. + * + * @return @ref MMWLAN_SUCCESS on success, else an appropriate error code. + */ +enum mmwlan_status mmwlan_scan_request(const struct mmwlan_scan_req *scan_req); + +/** + * Abort in progress or pending scans. + * + * The scan callback will be called back with result code @ref MMWLAN_SCAN_TERMINATED for + * all aborted scans. + * + * @return @ref MMWLAN_SUCCESS on success, else an appropriate error code. + */ +enum mmwlan_status mmwlan_scan_abort(void); + +/** + * Gets the MAC address of this device. + * + * The MAC address address comes from one of the following sources, in descending priority order: + * 1. @c mmhal_read_mac_addr() + * 2. Morse Micro transceiver + * 3. Randomly generated in the form `02:01:XX:XX:XX:XX` + * + * @note If a MAC address override is not provided (via @c mmhal_read_mac_addr()) then the + * transceiver must have been booted at least once before this function is invoked. + * + * @param mac_addr Buffer to receive the MAC address. Length must be @ref MMWLAN_MAC_ADDR_LEN. + * + * @return @ref MMWLAN_SUCCESS on success, @ref MMWLAN_UNAVAILABLE if the MAC address was not + * able to be read from the transceiver because it was not booted, else an appropriate + * error code. + */ +enum mmwlan_status mmwlan_get_mac_addr(uint8_t *mac_addr); + +/** + * Gets the station's AID + * + * @return AID of station, or 0 if not associated + */ +uint16_t mmwlan_get_aid(void); + +/** + * Gets the BSSID of the AP to which the STA is associated. + * + * @param bssid Buffer to receive the BSSID. Length must be @ref MMWLAN_MAC_ADDR_LEN. + * Will be set to 00:00:00:00:00:00 if the station is not currently associated. + * + * @return @ref MMWLAN_SUCCESS on success, else an appropriate error code. + */ +enum mmwlan_status mmwlan_get_bssid(uint8_t *bssid); + +/** + * Gets the RSSI measured from the AP. + * + * When power save is enabled, this will only be updated when traffic is received from the AP. + * + * @returns last known RSSI of the AP or INT32_MIN on error. + */ +int32_t mmwlan_get_rssi(void); + +/** @} */ + +/** + * @defgroup MMWLAN_OFFLOAD WLAN offload features + * + * @{ + * + * WLAN offload features enable offloading some high level networking features to the WLAN chip. + * Features like ARP response, ARP refresh and DHCP lease updates can be offloaded to the chip + * allowing the host processor to sleep for longer resulting in better power savings. + * + */ + +/** + * Enables ARP response offload. + * + * When enabled the chip will automatically respond to ARP requests with the specified + * IPv4 address. + * + * @note ARP offload is not supported for IPv6 addresses. + * + * @param arp_addr The IPv4 address to respond with for ARP requests for this interface. + * + * @returns @ref MMWLAN_SUCCESS on success or @ref MMWLAN_ERROR on failure. + */ +enum mmwlan_status mmwlan_enable_arp_response_offload(uint32_t arp_addr); + +/** + * Enables ARP refresh offload. + * + * When enabled the Morse chip will periodically send ARP requests to the AP to refresh + * its ARP table. This keeps this stations ARP entry from expiring. ARP response offload + * needs to be enabled first for this feature to work. + * + * @note ARP refresh offload is not supported for IPv6 addresses. + * + * @param interval_s The interval in seconds to refresh the ARP entries on the AP. + * @param dest_ip The IP to send the ARP packets to. + * @param send_as_garp If true, send as gratuitous ARP. + * + * @returns @ref MMWLAN_SUCCESS on success or @ref MMWLAN_ERROR on failure. + */ +enum mmwlan_status mmwlan_enable_arp_refresh_offload(uint32_t interval_s, uint32_t dest_ip, bool send_as_garp); + +/** + * DHCP lease info structure. + */ +struct mmwlan_dhcp_lease_info { + /** local IP address */ + uint32_t ip4_addr; + /** Netmask address */ + uint32_t mask4_addr; + /** Gateway address */ + uint32_t gw4_addr; + /** DNS address */ + uint32_t dns4_addr; +}; + +/** DHCP Lease update callback function prototype - this is called whenever a lease is updated. */ +typedef void (*mmwlan_dhcp_lease_update_cb_t)(const struct mmwlan_dhcp_lease_info *lease_info, void *arg); + +/** + * Enables DHCP offload. + * + * When enabled the Morse chip will handle DHCP discovery and lease updates automatically. + * + * @note DHCP offload is not supported for IPv6 addresses. + * + * @note The DHCP lease update callback (@p dhcp_lease_update_cb) must not block and MMWLAN API + * functions may not be invoked from the callback. + * + * @note Once enabled this feature can only be disabled by a complete system reset or by calling + * @c mmwlan_shutdown(). + * + * @param dhcp_lease_update_cb The callback to call whenever the DHCP lease is updated. + * @param arg An opaque argument to pass to @c dhcp_lease_update_cb. + * + * @returns @ref MMWLAN_SUCCESS on success or @ref MMWLAN_ERROR on failure. + */ +enum mmwlan_status mmwlan_enable_dhcp_offload(mmwlan_dhcp_lease_update_cb_t dhcp_lease_update_cb, void *arg); + +/** + * Keep-alive offload configuration options for @ref mmwlan_tcp_keepalive_offload_args.set_cfgs + * bitmap. + */ +enum mmwlan_tcp_keepalive_offload_cfg { + /** Bitmap for TCP keep alive period parameter */ + MMWLAN_TCP_KEEPALIVE_SET_CFG_PERIOD = (0x01), + /** Bitmap for TCP keep alive retry count parameter */ + MMWLAN_TCP_KEEPALIVE_SET_CFG_RETRY_COUNT = (0x02), + /** Bitmap for TCP keep alive retry interval parameter */ + MMWLAN_TCP_KEEPALIVE_SET_CFG_RETRY_INTERVAL = (0x04), + /** Bitmap for TCP keep alive source IP parameter */ + MMWLAN_TCP_KEEPALIVE_SET_CFG_SRC_IP_ADDR = (0x08), + /** Bitmap for TCP keep alive destination IP parameter */ + MMWLAN_TCP_KEEPALIVE_SET_CFG_DEST_IP_ADDR = (0x10), + /** Bitmap for TCP keep alive source port parameter */ + MMWLAN_TCP_KEEPALIVE_SET_CFG_SRC_PORT = (0x20), + /** Bitmap for TCP keep alive destination port parameter */ + MMWLAN_TCP_KEEPALIVE_SET_CFG_DEST_PORT = (0x40), + /** Bitmap for TCP keep alive timing parameters only */ + MMWLAN_TCP_KEEPALIVE_SET_CFG_TIMING_ONLY = (0x07), + /** Bitmap for all TCP keep alive parameters */ + MMWLAN_TCP_KEEPALIVE_SET_CFG_ALL = (0x7F), +}; + +/** + * Arguments data structure for TCP keep-alive arguments. + * + * This structure should be initialized using @ref MMWLAN_TCP_KEEPALIVE_OFFLOAD_ARGS_INIT + * for sensible default values, particularly for forward compatibility with new releases + * that may add new fields to the struct. For example: + * + * @code{.c} + * struct mmwlan_tcp_keepalive_offload_args args = MMWLAN_TCP_KEEPALIVE_OFFLOAD_ARGS_INIT; + * @endcode + */ +struct mmwlan_tcp_keepalive_offload_args { + /** + * A bitmap specifying which configs to set. + * See @ref mmwlan_tcp_keepalive_offload_cfg for values. + */ + uint8_t set_cfgs; + /** + * The interval in seconds to send the keep alive in. @c MMWLAN_TCP_KEEPALIVE_SET_CFG_PERIOD + * bit in @ref set_cfgs must be set if specifying this parameter + */ + uint16_t period_s; + /** Number of times to retry before giving up. @c MMWLAN_TCP_KEEPALIVE_SET_CFG_RETRY_COUNT + * bit in @ref set_cfgs must be set if specifying this parameter + */ + uint8_t retry_count; + /** The time to wait between retries in seconds. @c MMWLAN_TCP_KEEPALIVE_SET_CFG_RETRY_INTERVAL + * bit in @ref set_cfgs must be set if specifying this parameter + */ + uint8_t retry_interval_s; + /** The source IP for the keep alive packet. @c MMWLAN_TCP_KEEPALIVE_SET_CFG_SRC_IP_ADDR + * bit in @ref set_cfgs must be set if specifying this parameter + */ + uint32_t src_ip; + /** The source port for the keep alive packet. @c MMWLAN_TCP_KEEPALIVE_SET_CFG_SRC_PORT + * bit in @ref set_cfgs must be set if specifying this parameter + */ + uint16_t src_port; + /** The destination IP for the keep alive packet. @c MMWLAN_TCP_KEEPALIVE_SET_CFG_DEST_IP_ADDR + * bit in @ref set_cfgs must be set if specifying this parameter + */ + uint32_t dest_ip; + /** The destination port for the keep alive packet. @c MMWLAN_TCP_KEEPALIVE_SET_CFG_SRC_PORT + * bit in @ref set_cfgs must be set if specifying this parameter + */ + uint16_t dest_port; +}; + +/** + * Initializer for @ref mmwlan_tcp_keepalive_offload_args. + * + * @see mmwlan_tcp_keepalive_offload_args + */ +#define MMWLAN_TCP_KEEPALIVE_OFFLOAD_ARGS_INIT \ + { \ + 0 \ + } + +/** + * Enables TCP keep-alive offload. + * + * When enabled the Morse chip will periodically send TCP keep-alive packets to the destination + * even when the host processor is in standby mode. This function needs to be called before + * opening a TCP connection. + * + * @note TCP keep-alive offload will only be applied to the first TCP connection matching the + * given configuration that is opened after this function is called. + * + * @note TCP keep-alive offload is not supported for IPv6 addresses. + * + * @param args A pointer to arguments of @c struct @c mmwlan_tcp_keepalive_offload_args. + * + * @returns @ref MMWLAN_SUCCESS on success or @ref MMWLAN_ERROR on failure. + */ +enum mmwlan_status mmwlan_enable_tcp_keepalive_offload(const struct mmwlan_tcp_keepalive_offload_args *args); + +/** + * Disables the TCP keep-alive offload feature. + * + * Has no effect if TCP keep-alive offload is not enabled. + * + * @returns @ref MMWLAN_SUCCESS on success or @ref MMWLAN_ERROR on failure. + */ +enum mmwlan_status mmwlan_disable_tcp_keepalive_offload(void); + +/** + * If this bit is set in the flags parameter of @ref mmwlan_config_whitelist then any active + * whitelist filters are cleared, + */ +#define MMWLAN_WHITELIST_FLAGS_CLEAR 0x01 + +/** + * Whitelist filter configuration + */ +struct mmwlan_config_whitelist { + /** Flags - clear any active whitelist filters if bit 0 is set */ + uint8_t flags; + /** The IP protocol to match - 6 for TCP, 17 for UDP, 0 for any */ + uint8_t ip_protocol; + /** The @c LLC protocol to match - 0x0800 for IPv4 and 0x86DD for IPv6, 0 for any */ + uint16_t llc_protocol; + /** The IPv4 source address to match, 0.0.0.0 for any. */ + uint32_t src_ip; + /** The IPv4 destination address to match, 0.0.0.0 for any - this is usually our IP address */ + uint32_t dest_ip; + /** The netmask to apply to the source or destination IP, 0.0.0.0 for any */ + uint32_t netmask; + /** The source TCP or UDP port to match, 0 for any */ + uint16_t src_port; + /** The destination TCP or UDP port to match, 0 for any */ + uint16_t dest_port; +}; + +/** + * Sets and enables the IP whitelist filter. + * + * The IP whitelist filter specifies which incoming IP packets can wake the host from standby. + * The filter can be used to specify parameters such as source or destination IP addresses to + * match on, source or destination ports and even IP layer (Such as TCP, ICMP or UDP) or @c LLC + * layer (Such as IPv4 or IPv6) protocols. If any filter parameter is set to 0, then it is + * excluded from the filtering process. + * + * If the @c flags parameter is set to @c MMWLAN_WHITELIST_FLAGS_CLEAR, then all active whitelist + * filters are cleared. + * + * @note Address/port level filtering is not supported for IPv6 packets. + * + * @param whitelist The whitelist filter to set. + * + * @returns @ref MMWLAN_SUCCESS on success or @ref MMWLAN_ERROR on failure. + */ +enum mmwlan_status mmwlan_set_whitelist_filter(const struct mmwlan_config_whitelist *whitelist); + +/** @} */ + +/** + * @defgroup MMWLAN_STANDBY WLAN Standby features + * + * @ingroup MMWLAN_OFFLOAD + * + * @{ + * + * The standby mode allows the system to power off or put the host processor in a deep sleep mode + * while the Morse chip takes over certain functionality to keep the connection alive with the + * provision to wake up the host processor when certain conditions are met. + * + * When in Standby mode, the chip will: + * - Not try to communicate with the host in any way. + * - Use normal Power Save mechanisms to wake for DTIM beacons and to receive data frames when a + * DTIM beacon indicates pending data. + * - Directly respond to ARP requests if ARP offload is enabled, handle DHCP lease expiry/renewal + * if DHCP offload is enabled, and drop all other traffic other than a wake request packet from + * the access point. + * - Monitor access point reach-ability and provide status by periodically transmitting encrypted + * keep-alive frames to the access point. + * - If access point is no longer reachable, then the chip will enter a snooze mode where + * it stops listening for beacons, waking up only occasionally to see if the access point is + * reachable again. This allows the chip to conserve power when the access point is not reachable. + * + * The chip will wake the host only if one of the following occurs. + * - An encrypted Standby wake packet is received. + * - Association has been lost (indicated via a @c deauth response to a keep-alive frame from the + * access point). + * - The access point was not reachable and beacons are now detected indicating the access point + * is available again. + * - The input trigger GPIO pin on the chip was toggled indicating some on board hardware requires + * attention. + * - A TCP connection maintained by the TCP keep-alive offload feature was lost. + * + * The host will be powered on by toggling a GPIO pin, the BUSY pin is also toggled to interrupt + * the host. By offloading features like ARP response, ARP refresh, TCP keep-alive, and DHCP lease + * updates while in standby mode allows the host processor to sleep for longer resulting in better + * power savings. + */ + +/** + * Reasons we can exit standby mode. + */ +enum mmwlan_standby_exit_reason { + /** No specific reason for exiting standby mode */ + MMWLAN_STANDBY_EXIT_REASON_NONE, + /** The STA has received the wake-up frame */ + MMWLAN_STANDBY_EXIT_REASON_WAKEUP_FRAME, + /** The STA needs to (re)associate */ + MMWLAN_STANDBY_EXIT_REASON_ASSOCIATE, + /** The external input pin has fired */ + MMWLAN_STANDBY_EXIT_REASON_EXT_INPUT, + /** Whitelisted packet received */ + MMWLAN_STANDBY_EXIT_REASON_WHITELIST_PKT, + /** TCP connection lost */ + MMWLAN_STANDBY_EXIT_REASON_TCP_CONNECTION_LOST, +}; + +/** + * Standby mode callback whenever an event requiring an exit from standby mode occurs. + * + * @param reason The reason we exited standby mode. See enum @ref mmwlan_standby_exit_reason. + * @param arg An opaque pointer passed from @ref mmwlan_standby_enter() + */ +typedef void (*mmwlan_standby_exit_cb_t)(uint8_t reason, void *arg); + +/** Arguments for @ref mmwlan_standby_enter */ +struct mmwlan_standby_enter_args { + /** Callback function to call when we exit standby mode */ + mmwlan_standby_exit_cb_t standby_exit_cb; + /** Arguments to pass to callback function */ + void *standby_exit_arg; +}; + +/** + * This function puts the Morse chip into standby mode allowing the host processor to go to sleep. + * + * When in standby mode the Morse chip takes over certain functionality to keep the connection + * alive with the provision to wake up the host processor when certain conditions are met. + * Exit from standby mode can be triggered by the Morse chip under certain conditions or by the host by + * invoking @ref mmwlan_standby_exit(). Before invoking this function, standby mode parameters can be + * configured by calling @ref mmwlan_standby_set_config(), @ref mmwlan_standby_set_status_payload() and/or + * @ref mmwlan_standby_set_wake_filter() + * + * @param args A pointer to the arguments for this function. + * + * @returns @ref MMWLAN_SUCCESS on success or @ref MMWLAN_ERROR on failure. + */ +enum mmwlan_status mmwlan_standby_enter(const struct mmwlan_standby_enter_args *args); + +/** + * Forces the Morse chip to exit standby mode. + * + * here may be certain instances such as a timer expiry, which cause the host chip to wake up + * independent of the Morse chip. In such situations, the host calls this function to instruct + * the Morse chip to exit standby mode and return to normal operating mode. + * + * Triggers @ref mmwlan_standby_exit_cb_t with reason @c MMWLAN_STANDBY_EXIT_REASON_NONE. + * + * @returns @ref MMWLAN_SUCCESS on success or @ref MMWLAN_ERROR on failure. + */ +enum mmwlan_status mmwlan_standby_exit(void); + +/** Arguments for @ref mmwlan_standby_set_status_payload */ +struct mmwlan_standby_set_status_payload_args { + /** User data to append to the standby status packets */ + uint8_t payload[MMWLAN_STANDBY_STATUS_FRAME_USER_PAYLOAD_MAXLEN]; + /** + * The length of the payload in bytes. + * See @ref MMWLAN_STANDBY_STATUS_FRAME_USER_PAYLOAD_MAXLEN for maximum payload length. + */ + uint32_t payload_len; +}; + +/** + * Sets the user payload in the standby status packet. + * + * Once standby mode is enabled, the Morse chip will periodically emit a UDP packet of the + * following format regardless of whether it is in standby or not. The UDP packet + * will also be sent immediately upon entering or exiting Standby mode. + * + * @code + * +----------------------+----------------------+--------------------------------+---------+ + * | Morse OUI (0c:bf:74) | Type: Standby (0x01) | Awake (0x00) or Standby (0x01) | Payload | + * +----------------------+----------------------+--------------------------------+---------+ + * @endcode + * + * @note The payload is optional and is not present if this function is not called. + * + * @param args A pointer to the arguments for this function. + * + * @returns @ref MMWLAN_SUCCESS on success or @ref MMWLAN_ERROR on failure. + */ +enum mmwlan_status mmwlan_standby_set_status_payload(const struct mmwlan_standby_set_status_payload_args *args); + +/** Arguments for @ref mmwlan_standby_set_wake_filter */ +struct mmwlan_standby_set_wake_filter_args { + /** Data to match with for filtering */ + uint8_t filter[MMWLAN_STANDBY_WAKE_FRAME_USER_FILTER_MAXLEN]; + /** + * The length of the filter data in bytes. + * See @ref MMWLAN_STANDBY_WAKE_FRAME_USER_FILTER_MAXLEN for maximum filter length. + */ + uint32_t filter_len; + /** The offset within the packet to search for the filter match */ + uint32_t offset; +}; + +/** + * Configures the standby mode UDP wake packet filter. + * + * The system can be woken up from standby mode by sending it a UDP wake packet of the following + * format. If a wake filter is set using this function then the wake packet will only wake up + * the system if the specified filter pattern matches the payload at the specified offset within + * the payload. + * + * The wake packet has the following format: + * @code + * +----------------------+----------------------+----------------+--------------------+ + * | Morse OUI (0c:bf:74) | Type: Standby (0x01) | Wake up (0x02) | Payload (optional) | + * +----------------------+----------------------+----------------+--------------------+ + * @endcode + * + * @note If a wake filter is not configured then the system will wake on any wake packet and + * the payload (if any) is ignored. + * + * @param args A pointer to the arguments for this function. + * + * @returns @ref MMWLAN_SUCCESS on success or @ref MMWLAN_ERROR on failure. + */ +enum mmwlan_status mmwlan_standby_set_wake_filter(const struct mmwlan_standby_set_wake_filter_args *args); + +/** + * Standby mode configuration parameters. If the @ref mmwlan_standby_set_config() + * function is not called, then the defaults are as specified. + */ +struct mmwlan_standby_config { + /** Interval in seconds for transmitting Standby status packets. (Default 15s) */ + uint32_t notify_period_s; + /** Source IP address to use for the standby status packets. (Default 0.0.0.0) */ + uint32_t src_ip; + /** Destination IP address for the standby status packets. (Default 0.0.0.0) */ + uint32_t dst_ip; + /** + * Destination UDP Port for the standby status packets, also used the source port for + * outgoing UDP port for outgoing UDP packets. (Default 22000) + */ + uint16_t dst_port; + /** + * The interval in seconds to wait after beacon loss before entering snooze. In + * snooze mode the chip stops listening for beacons to save power. (Default 120s) + */ + uint32_t bss_inactivity_before_snooze_s; + /** + * The interval in seconds to wake periodically from snooze and check for beacons. + * If no beacons are found then the chip will re-enter snooze. If beacons are found + * then the chip will exit standby mode so the host can re-associate. (Default 60s) + */ + uint32_t snooze_period_s; + /** + * The amount in seconds to increase successive snooze intervals. This saves power + * by sleeping for longer before checking for beacons again if no beacons are found. + * (Default 0s) + */ + uint32_t snooze_increment_s; + /** The maximum time in seconds to snooze for after increments. (Default @c UINT32_MAX) */ + uint32_t snooze_max_s; +}; + +/** + * Sets the configuration for standby mode. + * + * @param config A pointer to the configuration structure. See @ref mmwlan_standby_config. + * + * @returns @ref MMWLAN_SUCCESS on success or @ref MMWLAN_ERROR on failure. + */ +enum mmwlan_status mmwlan_standby_set_config(const struct mmwlan_standby_config *config); + +/** @} */ + +/** + * @defgroup MMWLAN_WNM WNM Sleep management + * + * @{ + * + * @note It is highly recommended to refer to Morse Micro App Note 04 on WNM sleep for a more + * detailed explanation on how this feature works. + * + * WNM sleep is an extended power save mode in which the STA need not listen for every DTIM beacon + * and need not perform rekey, allowing the STA to remain associated and reduce power consumption + * when it has no traffic to send or receive from the AP. While a STA is in WNM sleep it will be + * unable to communicate with the AP, and thus any traffic for the STA may be buffered or dropped + * at the discretion of the AP while the STA is in WNM sleep. + * + * @note WNM sleep can only be entered after the STA is associated to the AP. The AP must also + * support WNM sleep. + * + * When entry to WNM sleep is enabled via this API, the STA sends a request to the AP and after + * successful negotiation, the chip is either put into a lower power mode or powered down + * completely, depending on arguments given when WNM sleep mode was enabled. The datapath is paused + * during this time. If the STA does not successfully negotiate with the AP to enter WNM Sleep, it + * will return an error code and the chip will not enter a low power mode. + * + * When WNM sleep is disabled, the chip returns to normal operation and sends a request to the AP + * to exit WNM sleep. Note that the chip will exit low power mode regardless of whether or not the + * exit request was successful. WNM sleep will be exited if @ref mmwlan_shutdown or + * @ref mmwlan_sta_disable is invoked. + * + * If the AP takes down the link, then the station will be unaware until it sends the WNM Sleep + * exit request. At that point the AP will send a de-authentication frame with reason code + * "non-associated station". After validating this is actually the AP the station was connected + * to, the station will bring down the connection and return @ref MMWLAN_ERROR. The station will + * attempt to re-associate but will not re-enter WNM sleep. The application needs to enable WNM + * sleep again via this API if required. + * + * A high level overview of enabling and disabling WNM sleep is shown below. + * + * @include{doc} wnm_sleep_overview.puml + */ + +/** Structure for storing WNM sleep extended arguments. */ +struct mmwlan_set_wnm_sleep_enabled_args { + /** Boolean indicating whether WNM sleep is enabled. */ + bool wnm_sleep_enabled; + /** Boolean indicating whether chip should be powered down during WNM sleep. */ + bool chip_powerdown_enabled; +}; + +/** + * Initializer for @ref mmwlan_set_wnm_sleep_enabled_args. + * + * For example: + * + * @code{c} + * struct mmwlan_set_wnm_sleep_enabled_args wnm_sleep_args = MMWLAN_SET_WNM_SLEEP_ENABLED_ARGS_INIT; + * @endcode + */ +#define MMWLAN_SET_WNM_SLEEP_ENABLED_ARGS_INIT \ + { \ + false, false \ + } + +/** + * Sets extended WNM sleep mode. + * + * Provides an extended interface for setting WNM sleep. See @ref mmwlan_set_wnm_sleep_enabled_args + * for parameter details. If WNM sleep mode is enabled then the transceiver will sleep across + * multiple DTIM periods until WNM sleep mode is disabled. This allows the transceiver to use less + * power with the caveat that it will not wake up for group- or individually-addressed traffic. If + * a group rekey occurs while the device is in WNM sleep it will be applied when the device exits + * WNM sleep. + * + * @note Data should not be queued for transmission (using, e.g., @ref mmwlan_tx()) during + * WNM sleep. + * @note 802.11 power save must be enabled for any benefit to be obtained + * (see @ref mmwlan_set_power_save_mode()). + * @note Negotiation with the AP is required to enter WNM sleep. As such the transceiver will only + * be placed into low power mode when a connection has been establish and the AP has accepted + * the request to enter WNM sleep. This will automatically be handled within mmwlan once WNM + * sleep mode is enabled. + * + * @param args WNM sleep arguments - see @ref mmwlan_set_wnm_sleep_enabled_args. + * + * @return @ref MMWLAN_SUCCESS on success, MMWLAN_UNAVAILABLE if already requested, + * else an appropriate error code. + * @ref MMWLAN_TIMED_OUT if the maximum retry request reached. For WNM sleep exit request, + * this means that the device exited WNM sleep but failed to inform the AP. + */ +enum mmwlan_status mmwlan_set_wnm_sleep_enabled_ext(const struct mmwlan_set_wnm_sleep_enabled_args *args); + +/** + * Sets whether WNM sleep mode is enabled. + * + * If WNM sleep mode is enabled then the transceiver will sleep across multiple DTIM periods + * until WNM sleep mode is disabled. This allows the transceiver to use less power with the + * caveat that it will not wake up for group- or individually-addressed traffic. If a group + * rekey occurs while the device is in WNM sleep it will be applied when the device exits + * WNM sleep. + * + * @note Data should not be queued for transmission (using, e.g., @ref mmwlan_tx()) during + * WNM sleep. + * @note 802.11 power save must be enabled for any benefit to be obtained + * (see @ref mmwlan_set_power_save_mode()). + * @note Negotiation with the AP is required to enter WNM sleep. As such the transceiver will only + * be placed into low power mode when a connection has been establish and the AP has accepted + * the request to enter WNM sleep. This will automatically be handled within mmwlan once WNM + * sleep mode is enabled. + * + * @param wnm_sleep_enabled Boolean indicating whether WNM sleep is enabled. + * + * @return @ref MMWLAN_SUCCESS on success, MMWLAN_UNAVAILABLE if already requested, + * else an appropriate error code. + * @ref MMWLAN_TIMED_OUT if the maximum retry request reached. For WNM sleep exit request, + * this means that the device exited WNM sleep but failed to inform the AP. + */ +static inline enum mmwlan_status mmwlan_set_wnm_sleep_enabled(bool wnm_sleep_enabled) +{ + struct mmwlan_set_wnm_sleep_enabled_args wnm_sleep_args = MMWLAN_SET_WNM_SLEEP_ENABLED_ARGS_INIT; + wnm_sleep_args.wnm_sleep_enabled = wnm_sleep_enabled; + return mmwlan_set_wnm_sleep_enabled_ext(&wnm_sleep_args); +} + +/** + * @} + */ + +/** + * @defgroup MMWLAN_BEACON_VENDOR_IE_FILTER_API Beacon Vendor Specific IE Filter API + * + * @{ + * + * This API enables access to Vendor Specific information elements (IEs) in beacons. The application + * can register a callback to be executed if Vendor Specific IEs containing one of the specified + * Organizational Unique Identifiers (OUIs). Up to @ref MMWLAN_BEACON_VENDOR_IE_MAX_OUI_FILTERS can + * be specified. + */ + +/** Max number of OUIs supported in vendor IE OUI filter, @ref mmwlan_beacon_vendor_ie_filter. */ +#define MMWLAN_BEACON_VENDOR_IE_MAX_OUI_FILTERS (5) + +/** + * Beacon vendor ie filter callback function prototype. + * + * @param ies Reference to the list of information elements (little endian order) present in + * the beacon that matched the filter. + * @param ies_len Length of the IE list in octets. + * @param arg Reference to the opaque argument provided with the filter. + */ +typedef void (*mmwlan_beacon_vendor_ie_filter_cb_t)(const uint8_t *ies, uint32_t ies_len, void *arg); + +/** 24-bit OUI type. */ +typedef uint8_t mmwlan_oui_t[MMWLAN_OUI_SIZE]; + +/** Structure for storing beacon vendor ie filter parameter. */ +struct mmwlan_beacon_vendor_ie_filter { + /** + * Callback that will be executed upon reception of a beacon containing a Vendor Specific + * information element that has an OUI that matches this filter. Must not be @c NULL. + * + * @note This is executed in the MMWLAN main processing loop. As such, processing within the + * callback itself should be kept as short as possible. If extensive processing is done within + * the callback this could have an impact on throughput and/or connection stability. + */ + mmwlan_beacon_vendor_ie_filter_cb_t cb; + /** Opaque argument to be passed to the callbacks. */ + void *cb_arg; + /** Number of OUIs contained within @ref ouis. This can be @ref + * MMWLAN_BEACON_VENDOR_IE_MAX_OUI_FILTERS max. */ + uint8_t n_ouis; + /** List of OUIs to filter on. */ + mmwlan_oui_t ouis[MMWLAN_BEACON_VENDOR_IE_MAX_OUI_FILTERS]; +}; + +/** + * Function to update the filter used to pass beacon back. + * + * @note This will block until the filter has been installed or some error has occurred. + * + * @param filter Reference to the filter to use. This must reside in memory and not be modified + * until after the filter is **successfully** cleared. To clear the filter pass @c + * NULL here. + * + * @return @ref MMWLAN_SUCCESS on success, else an appropriate error code. + */ +enum mmwlan_status mmwlan_update_beacon_vendor_ie_filter(const struct mmwlan_beacon_vendor_ie_filter *filter); + +/** @} */ + +/* + * --------------------------------------------------------------------------------------------- + */ + +/** + * @defgroup MMWLAN_INIT WLAN Initialization/Deinitialization API + * + * @note If using LWIP, this API is invoked by the LWIP @c netif for MMWLAN, and thus does not + * need to be used directly by the application. + * + * @{ + */ + +/** + * Initialize the MMWLAN subsystem. + * + * @warning @ref mmhal_init() must be called before this function is executed. + */ +void mmwlan_init(void); + +/** + * Deinitialize the MMWLAN subsystem, freeing any allocated memory. + * + * Must not be called while there is any activity in progress (e.g., while connected). + * + * @warning @ref mmwlan_shutdown must be called before executing this function. + */ +void mmwlan_deinit(void); + +/** @} */ + +/** + * @defgroup MMWLAN_HEALTH WLAN Health check API + * + * @{ + */ + +/** + * The default minimum interval to wait after the last health check before triggering another. + * If this is 0 then health checks will always happen at the + * @ref MMWLAN_DEFAULT_MAX_HEALTH_CHECK_INTERVAL_MS value. + * @ref MMWLAN_DEFAULT_MIN_HEALTH_CHECK_INTERVAL_MS must always be less than or equal to + * @ref MMWLAN_DEFAULT_MAX_HEALTH_CHECK_INTERVAL_MS. + */ +#define MMWLAN_DEFAULT_MIN_HEALTH_CHECK_INTERVAL_MS 60000 + +/** + * The default maximum interval to wait after the last health check before triggering another. + * If this parameter is 0 then periodic health checks will be disabled. + * @ref MMWLAN_DEFAULT_MIN_HEALTH_CHECK_INTERVAL_MS must always be less than or equal to + * @ref MMWLAN_DEFAULT_MAX_HEALTH_CHECK_INTERVAL_MS. Set this to @c UINT32_MAX to have the + * maximum unbounded. + */ +#define MMWLAN_DEFAULT_MAX_HEALTH_CHECK_INTERVAL_MS 120000 + +/** + * Specify the periodic health check intervals. + * + * The system will always schedule the periodic health check for the @c max_interval_ms + * interval. It will however monitor system activity and opportunistically perform health + * checks after @c min_interval_ms time has passed and some activity was detected. If no + * activity was detected then the health check will be performed after @c max_interval_ms + * as originally scheduled. Every time the health check is performed, the next one will be + * rescheduled to occur after @c max_interval_ms. If both @c min_interval_ms and + * @c max_interval_ms are set to 0, then periodic health checks will be disabled. + * + * @param min_interval_ms The minimum interval to wait after the last health check before + * triggering another. If this parameter is 0 then health checks will + * always happen at the @c max_interval_ms value. @c min_interval_ms + * must always be less than or equal to @c max_interval_ms. + * + * @param max_interval_ms The maximum interval to wait after the last health check before + * triggering another. If this parameter is 0 then periodic health checks + * will be disabled. @c min_interval_ms must always be less than or + * equal to @c max_interval_ms. Set this to @c UINT32_MAX to have the + * maximum unbounded. + * + * @return @ref MMWLAN_SUCCESS on success, else an appropriate error code. + */ +enum mmwlan_status mmwlan_set_health_check_interval(uint32_t min_interval_ms, uint32_t max_interval_ms); + +/** @} */ + +/* + * --------------------------------------------------------------------------------------------- + */ + +/** + * @defgroup MMWLAN_DATA WLAN Datapath API + * + * @{ + */ + +/** Enumeration of link states. */ +enum mmwlan_link_state { + MMWLAN_LINK_DOWN, /**< The link is down. */ + MMWLAN_LINK_UP, /**< The link is up. */ +}; + +/** + * Prototype for link state change callbacks. + * + * @param link_state The new link state. + * @param arg Opaque argument that was given when the callback was registered. + */ +typedef void (*mmwlan_link_state_cb_t)(enum mmwlan_link_state link_state, void *arg); + +/** + * Register a link status callback. + * + * @note Only one link status callback may be registered. Further registration will overwrite the + * previously registered callback. + * + * @note The link status callback must not block and MMWLAN API functions may not be invoked + * from the callback. + * + * @param callback The callback to register. + * @param arg Opaque argument to be passed to the callback. + * + * @return @ref MMWLAN_SUCCESS on success, else an appropriate error code. + */ +enum mmwlan_status mmwlan_register_link_state_cb(mmwlan_link_state_cb_t callback, void *arg); + +/** + * Receive data packet callback function. + * + * @param header Buffer containing the 802.3 header for this packet. + * @param header_len Length of the @p header. + * @param payload Packet payload (excluding header). + * @param payload_len Length of @p payload. + * @param arg Opaque argument that was given when the callback was registered. + */ +typedef void (*mmwlan_rx_cb_t)(uint8_t *header, unsigned header_len, uint8_t *payload, unsigned payload_len, void *arg); + +/** + * Register a receive callback. + * + * @note Only one receive callback may be registered. Further registration will overwrite the + * previously registered callback. + * + * @note Only a single receive callback may be registered at a time. + * + * @param callback The callback to register (@c NULL to unregister). + * @param arg Opaque argument to be passed to the callback. + * + * @return @ref MMWLAN_SUCCESS on success, else an appropriate error code. + */ +enum mmwlan_status mmwlan_register_rx_cb(mmwlan_rx_cb_t callback, void *arg); + +/** + * Receive data packet callback function, consuming an mmpkt. + * + * @param mmpkt The mmpkt containing the received packet, including an 802.3 header. + * Ownership of the mmpkt is passed to this callback. + * @param arg Opaque argument that was given when the callback was registered. + */ +typedef void (*mmwlan_rx_pkt_cb_t)(struct mmpkt *mmpkt, void *arg); + +/** + * Register a receive callback which consumes an mmpkt. + * + * @note Only one receive callback of any type may be registered. Further registration will + * overwrite the previously registered callback. + * + * @param callback The callback to register (@c NULL to unregister). + * @param arg Opaque argument to be passed to the callback. + * + * @return @ref MMWLAN_SUCCESS on success, else an appropriate error code. + */ +enum mmwlan_status mmwlan_register_rx_pkt_cb(mmwlan_rx_pkt_cb_t callback, void *arg); + +/** + * Blocks until the transmit path is ready for transmit. + * + * @param timeout_ms The maximum time to wait, in milliseconds. If zero then this function + * does not block. + * + * @return @ref MMWLAN_SUCCESS on success, @ref MMWLAN_TIMED_OUT if the transmit datapath + * was not ready within the given timeout, or another error code as appropriate. + */ +enum mmwlan_status mmwlan_tx_wait_until_ready(uint32_t timeout_ms); + +/** + * Allocate an @c mmpkt data structure for transmission with at least enough space for the given + * payload length. + * + * The return mmpkt can be passed to @ref mmwlan_tx_pkt(). + * + * @param payload_len Minimum space required for payload. + * @param tid The TID that this packet will be transmitted at. This may be used + * by the allocation function to, for example, prioritize allocation of + * certain classes of traffic. + * + * @returns the allocated mmpkt on success or @c NULL on failure. + */ +struct mmpkt *mmwlan_alloc_mmpkt_for_tx(uint32_t payload_len, uint8_t tid); + +/** Default transmit timeout. Used by @ref mmwlan_tx() and @ref mmwlan_tx_tid(). */ +#define MMWLAN_TX_DEFAULT_TIMEOUT_MS (1000) + +/** Default QoS Traffic ID (TID) to use for transmit (@c mmwlan_tx()). */ +#define MMWLAN_TX_DEFAULT_QOS_TID (0) + +/** Maximum Traffic ID (TID) supported for QoS traffic. */ +#define MMWLAN_MAX_QOS_TID (7) + +/** + * Metadata for TX packets. + * + * This structure should be initialized using @ref MMWLAN_TX_METADATA_INIT for sensible + * default values, particularly for forward compatibility with new releases that may add + * new fields to the struct. For example: + * + * @code{.c} + * enum mmwlan_status status; + * struct mmwlan_tx_metadata metadata = MMWLAN_TX_METADATA_INIT; + * // HERE: initialize metadata + * status = mmwlan_tx_pkt(pkt, &metadata); + * @endcode + */ +struct mmwlan_tx_metadata { + /** + * Traffic ID (TID) to use. Must be less than or equal to @ref MMWLAN_MAX_QOS_TID. + * @see MMWLAN_TX_DEFAULT_QOS_TID + */ + uint8_t tid; +}; + +/** + * Initializer for @ref mmwlan_tx_metadata. + */ +#define MMWLAN_TX_METADATA_INIT \ + { \ + MMWLAN_TX_DEFAULT_QOS_TID \ + } + +/** + * Transmit the given packet. The packet must start with an 802.3 header, which will be + * translated into an 802.11 header by this function. + * + * @code + * + * +----------+----------+-----------+------------------+ + * | DST ADDR | SRC ADDR | ETHERTYPE | Payload | + * +----------+----------+-----------+------------------+ + * ^ ^ ^ + * |--------802.3 MAC Header---------| | + * | | + * | | + * | | + * |<----------------------len------------------------->| + * data + * + * @endcode + * + * @note The TID should be set in the tx metadata of @p txbuf before invoking this function. + * + * @note This function is non-blocking. It will return immediately if the tx path is blocked. + * Use the tx flow control callback. + * + * @warning The given @p txbuf must be allocated by @ref mmwlan_alloc_mmpkt_for_tx(). + * + * @param pkt mmpkt containing the packet to transmit. This will be consumed by this + * function. + * @param metadata Extra information relating to the packet transmission. May be @c NULL, + * in which case default values will be used. + * + * @return @ref MMWLAN_SUCCESS on success, else an appropriate error code. + */ +enum mmwlan_status mmwlan_tx_pkt(struct mmpkt *pkt, const struct mmwlan_tx_metadata *metadata); + +/** + * Transmit the given packet using the given QoS Traffic ID (TID). The packet must start with + * an 802.3 header that will be translated into an 802.11 header. + * + * @code + * + * +----------+----------+-----------+------------------+ + * | DST ADDR | SRC ADDR | ETHERTYPE | Payload | + * +----------+----------+-----------+------------------+ + * ^ ^ ^ + * |--------802.3 MAC Header---------| | + * | | + * | | + * | | + * |<----------------------len------------------------->| + * data + * + * @endcode + * + * @param data Packet data. + * @param len Length of packet. + * @param tid TID to use (0 - @ref MMWLAN_MAX_QOS_TID). + * + * @return @ref MMWLAN_SUCCESS on success, else an appropriate error code. + */ +static inline enum mmwlan_status mmwlan_tx_tid(const uint8_t *data, unsigned len, uint8_t tid) +{ + enum mmwlan_status status; + struct mmpkt *pkt; + struct mmpktview *pktview; + struct mmwlan_tx_metadata metadata = MMWLAN_TX_METADATA_INIT; + + status = mmwlan_tx_wait_until_ready(MMWLAN_TX_DEFAULT_TIMEOUT_MS); + if (status != MMWLAN_SUCCESS) { + return status; + } + + pkt = mmwlan_alloc_mmpkt_for_tx(len, tid); + if (pkt == NULL) { + return MMWLAN_NO_MEM; + } + + pktview = mmpkt_open(pkt); + mmpkt_append_data(pktview, data, len); + mmpkt_close(&pktview); + + metadata.tid = tid; + return mmwlan_tx_pkt(pkt, &metadata); +} + +/** + * Transmit the given packet using @ref MMWLAN_TX_DEFAULT_QOS_TID. The packet must start with an + * 802.3 header that will be translated into an 802.11 header. + * + * @code + * + * +----------+----------+-----------+------------------+ + * | DST ADDR | SRC ADDR | ETHERTYPE | Payload | + * +----------+----------+-----------+------------------+ + * ^ ^ ^ + * |--------802.3 MAC Header---------| | + * | | + * | | + * | | + * |<----------------------len------------------------->| + * data + * + * @endcode + * + * @param data Packet data. + * @param len Length of packet. + * + * @return @ref MMWLAN_SUCCESS on success, else an appropriate error code. + */ +static inline enum mmwlan_status mmwlan_tx(const uint8_t *data, unsigned len) +{ + return mmwlan_tx_tid(data, len, MMWLAN_TX_DEFAULT_QOS_TID); +} + +/** + * Enumeration of states that can be returned by the transmit flow control callback (as + * registered by @ref mmwlan_register_tx_flow_control_cb(). + */ +enum mmwlan_tx_flow_control_state { + MMWLAN_TX_READY, /**< Transmit data path ready for packets (not paused). */ + MMWLAN_TX_PAUSED, /**< Transmit data path paused (blocked). */ +}; + +/** + * Transmit flow control callback function type. When registered, this callback will be + * invoked when the transmit data path is paused and when unpaused. + * + * @note This function will always be invoked from the Upper MAC thread context. Therefore its + * invocation may not be synchronous with changes in flow control state. + * + * @param state Current transmit flow control state. + * @param arg Opaque argument that was given when the function was registered. + * + * @return @ref MMWLAN_SUCCESS on success, else an appropriate error code. + */ +typedef void (*mmwlan_tx_flow_control_cb_t)(enum mmwlan_tx_flow_control_state state, void *arg); + +/** + * Register a transmit flow control callback. This callback will be invoked when the tx data path + * is paused and when unpaused. + * + * @note This function will always be invoked from the Upper MAC thread context. Therefore its + * invocation may not be synchronous with changes in flow control state. + * + * @param cb The callback to register. + * @param arg Opaque argument to pass to the callback. + * + * @return @ref MMWLAN_SUCCESS on success, else an appropriate error code. + */ +enum mmwlan_status mmwlan_register_tx_flow_control_cb(mmwlan_tx_flow_control_cb_t cb, void *arg); + +/** @} */ + +/* + * --------------------------------------------------------------------------------------------- + */ + +/** + * @defgroup MMWLAN_STATS Statistics API + * + * API for retrieving statistics information from the WLAN subsystem. + * + * @{ + */ + +/** Rate control statistics data structure. */ +struct mmwlan_rc_stats { + /** The number of rate table entries. */ + uint32_t n_entries; + /** Rate info for each rate table entry. */ + uint32_t *rate_info; + /** Total number of packets sent for each rate table entry. */ + uint32_t *total_sent; + /** Total successes for each rate table entry. */ + uint32_t *total_success; +}; + +/** + * Enumeration defined offsets into the bit field of rate information (@c rate_info in + * @ref mmwlan_rc_stats). + * + * ``` + * 31 9 8 4 0 + * +----------+-------+------+----+ + * | Reserved | Guard | Rate | BW | + * | 23 | 1 | 4 | 4 | + * +----------+-------+------+----+ + * ``` + * * BW: 0 = 1 MHz, 1 = 2 MHz, 2 = 4 MHz + * * Rate: MCS Rate + * * Guard: 0 = LGI, 1 = SGI + */ +enum mmwlan_rc_stats_rate_info_offsets { + MMWLAN_RC_STATS_RATE_INFO_BW_OFFSET = 0, + MMWLAN_RC_STATS_RATE_INFO_RATE_OFFSET = 4, + MMWLAN_RC_STATS_RATE_INFO_GUARD_OFFSET = 8, +}; + +/** + * Retrieves WLAN rate control statistics. + * + * @note The returned data structure must be freed using @ref mmwlan_free_rc_stats(). + * + * @returns the statistics data structure on success or @c NULL on failure. + */ +struct mmwlan_rc_stats *mmwlan_get_rc_stats(void); + +/** + * Free a mmwlan_rc_stats structure that was allocated with @ref mmwlan_get_rc_stats(). + * + * @param stats The structure to be freed (may be @c NULL). + */ +void mmwlan_free_rc_stats(struct mmwlan_rc_stats *stats); + +/** + * Data structure used to represent an opaque buffer containing Morse statistics. This is + * returned by @c mmwlan_get_morse_stats() and must be freed by @c mmwlan_free_morse_stats(). + */ +struct mmwlan_morse_stats { + /** Buffer containing the stats. */ + uint8_t *buf; + /** Length of stats in @c buf. */ + uint32_t len; +}; + +/** + * Retrieves statistics from the Morse transceiver. The stats are returned as a binary blob + * that can be parsed by host tools. + * + * @param core_num The core to retrieve stats for. + * @param reset Boolean indicating whether to reset the stats after retrieving. + * + * @note The returned @c mmwlan_morse_stats instance must be freed using + * @ref mmwlan_free_morse_stats. + * + * @returns a @c mmwlan_morse_stats instance on success or @c NULL on failure. + */ +struct mmwlan_morse_stats *mmwlan_get_morse_stats(uint32_t core_num, bool reset); + +/** + * Frees a @c mmwlan_morse_stats instance that was returned by @c mmwlan_get_morse_stats(). + * + * @param stats The instance to free. May be @ NULL. + */ +void mmwlan_free_morse_stats(struct mmwlan_morse_stats *stats); + +/** @} */ + +/* + * --------------------------------------------------------------------------------------------- + */ + +/** + * @defgroup MMWLAN_TEST Test (ATE) API + * + * Extended API particularly intended for test use cases. + * + * @{ + */ + +/** Enumeration of MCS rates. */ +enum mmwlan_mcs { + MMWLAN_MCS_NONE = -1, /**< Use-case specific special value */ + MMWLAN_MCS_0 = 0, /**< MCS0 */ + MMWLAN_MCS_1, /**< MCS1 */ + MMWLAN_MCS_2, /**< MCS2 */ + MMWLAN_MCS_3, /**< MCS3*/ + MMWLAN_MCS_4, /**< MCS4 */ + MMWLAN_MCS_5, /**< MCS5 */ + MMWLAN_MCS_6, /**< MCS6 */ + MMWLAN_MCS_7, /**< MCS7 */ + MMWLAN_MCS_MAX = MMWLAN_MCS_7 /**< Maximum supported MCS rate */ +}; + +/** Enumeration of bandwidths. */ +enum mmwlan_bw { + MMWLAN_BW_NONE = -1, /**< Use-case specific special value */ + MMWLAN_BW_1MHZ = 1, /**< 1 MHz bandwidth */ + MMWLAN_BW_2MHZ = 2, /**< 2 MHz bandwidth */ + MMWLAN_BW_4MHZ = 4, /**< 4 MHz bandwidth */ + MMWLAN_BW_8MHZ = 8, /**< 8 MHz bandwidth */ + MMWLAN_BW_MAX = MMWLAN_BW_8MHZ, /**< Maximum supported bandwidth */ +}; + +/** Enumeration of guard intervals. */ +enum mmwlan_gi { + MMWLAN_GI_NONE = -1, /**< Use-case specific special value */ + MMWLAN_GI_SHORT = 0, /**< Short guard interval */ + MMWLAN_GI_LONG, /**< Long guard interval */ + MMWLAN_GI_MAX = MMWLAN_GI_LONG, /**< Maximum valid value of this @c enum. */ +}; + +/** + * Enable/disable override of rate control parameters. + * + * @param tx_rate_override Overrides the transmit MCS rate. Set to @ref MMWLAN_MCS_NONE for no + * override. + * @param bandwidth_override Overrides the TX bandwidth. Set to @ref MMWLAN_BW_NONE for no + * override. + * @param gi_override Overrides the guard interval. Set to @ref MMWLAN_GI_NONE for no + * override. + * + * @return @ref MMWLAN_SUCCESS on success, else an appropriate error code. + */ +enum mmwlan_status mmwlan_ate_override_rate_control(enum mmwlan_mcs tx_rate_override, enum mmwlan_bw bandwidth_override, + enum mmwlan_gi gi_override); + +/** + * Execute a test/debug command. The format of command and response is opaque to this API. + * + * @param[in] command Buffer containing the command to be executed. Note that buffer + * contents may be modified by this function. + * @param[in] command_len Length of the command to be executed. + * @param[out] response Buffer to received the response to the command. + * May be NULL, in which case @p response_len should also be NULL. + * @param[in,out] response_len Pointer to a @c uint32_t that is initialized to the length of the + * response buffer. On success, the value will be updated to the + * length of data that was put into the response buffer. + * May be NULL, in which case @p response must also be NULL. + * + * @returns @c MMWLAN_SUCCESS on success, else an appropriate error code. + */ +enum mmwlan_status mmwlan_ate_execute_command(uint8_t *command, uint32_t command_len, uint8_t *response, uint32_t *response_len); + +/** @} */ + +#ifdef __cplusplus +} +#endif + +/** @} */ diff --git a/lib/MorseWlan/include/mmwlan_regdb.def b/lib/MorseWlan/include/mmwlan_regdb.def new file mode 100644 index 000000000..202b16172 --- /dev/null +++ b/lib/MorseWlan/include/mmwlan_regdb.def @@ -0,0 +1,302 @@ +/* + * + * Copyright 2022-2023 Morse Micro + */ +/** + * @ingroup MMWLAN_REGDB + * @defgroup MMWLAN_REGDB_TEMPLATE Template S1G regulatory database + * + * \{ + * + * @section MMWLAN_REGDB_TEMPLATE_DISCLAIMER Disclaimer + * + * While every effort has been made to maintain accuracy of this database, no guarantee is + * given as to the accuracy of the information contained herein. + * + * @section MMWLAN_REGDB_TEMPLATE_COUNTRIES Country code list + * + * | Country Code | Country | + * | ------------ | ------- | + * | AU | Australia | + * | EU | EU | + * | IN | India | + * | JP | Japan | + * | KR | South Korea | + * | NZ | New Zealand | + * | SG | Singapore | + * | US | USA | + */ + +#include "mmwlan.h" + +/** List of valid S1G channels for Australia. */ +static const struct mmwlan_s1g_channel s1g_channels_AU[] = { + /* Ctr Freq (Hz), Duty Cycle (%/100), Omit Control Response, Global Op Class, S1G Op Class, S1G Chan #, Op BW, Max Tx EIRP (dBm), Min Packet Spacing Window (microsec), airtime_min (microsec), airtime_max (microsec) */ + { 915500000, 10000, false, 68, 22, 27, 1, 30, 0, 0, 0 }, + { 916500000, 10000, false, 68, 22, 29, 1, 30, 0, 0, 0 }, + { 917500000, 10000, false, 68, 22, 31, 1, 30, 0, 0, 0 }, + { 918500000, 10000, false, 68, 22, 33, 1, 30, 0, 0, 0 }, + { 919500000, 10000, false, 68, 22, 35, 1, 30, 0, 0, 0 }, + { 920500000, 10000, false, 68, 22, 37, 1, 30, 0, 0, 0 }, + { 921500000, 10000, false, 68, 22, 39, 1, 30, 0, 0, 0 }, + { 922500000, 10000, false, 68, 22, 41, 1, 30, 0, 0, 0 }, + { 923500000, 10000, false, 68, 22, 43, 1, 30, 0, 0, 0 }, + { 924500000, 10000, false, 68, 22, 45, 1, 30, 0, 0, 0 }, + { 925500000, 10000, false, 68, 22, 47, 1, 30, 0, 0, 0 }, + { 926500000, 10000, false, 68, 22, 49, 1, 30, 0, 0, 0 }, + { 927500000, 10000, false, 68, 22, 51, 1, 30, 0, 0, 0 }, + { 917000000, 10000, false, 69, 23, 30, 2, 30, 0, 0, 0 }, + { 919000000, 10000, false, 69, 23, 34, 2, 30, 0, 0, 0 }, + { 921000000, 10000, false, 69, 23, 38, 2, 30, 0, 0, 0 }, + { 923000000, 10000, false, 69, 23, 42, 2, 30, 0, 0, 0 }, + { 925000000, 10000, false, 69, 23, 46, 2, 30, 0, 0, 0 }, + { 927000000, 10000, false, 69, 23, 50, 2, 30, 0, 0, 0 }, + { 918000000, 10000, false, 70, 24, 32, 4, 30, 0, 0, 0 }, + { 922000000, 10000, false, 70, 24, 40, 4, 30, 0, 0, 0 }, + { 926000000, 10000, false, 70, 24, 48, 4, 30, 0, 0, 0 }, + { 924000000, 10000, false, 71, 25, 44, 8, 30, 0, 0, 0 }, +}; + +/** Channel list structure for Australia. */ +static const struct mmwlan_s1g_channel_list s1g_channel_list_AU = { + .country_code = "AU", + .num_channels = (sizeof(s1g_channels_AU)/sizeof(s1g_channels_AU[0])), + .channels = s1g_channels_AU, +}; + +/** List of valid S1G channels for EU. */ +static const struct mmwlan_s1g_channel s1g_channels_EU[] = { + /* Ctr Freq (Hz), Duty Cycle (%/100), Omit Control Response, Global Op Class, S1G Op Class, S1G Chan #, Op BW, Max Tx EIRP (dBm), Min Packet Spacing Window (microsec), airtime_min (microsec), airtime_max (microsec) */ + { 863500000, 280, false, 66, 6, 1, 1, 16, 0, 0, 0 }, + { 864500000, 280, false, 66, 6, 3, 1, 16, 0, 0, 0 }, + { 865500000, 280, false, 66, 6, 5, 1, 16, 0, 0, 0 }, + { 866500000, 280, false, 66, 6, 7, 1, 16, 0, 0, 0 }, + { 867500000, 280, false, 66, 6, 9, 1, 16, 0, 0, 0 }, +}; + +/** Channel list structure for EU. */ +static const struct mmwlan_s1g_channel_list s1g_channel_list_EU = { + .country_code = "EU", + .num_channels = (sizeof(s1g_channels_EU)/sizeof(s1g_channels_EU[0])), + .channels = s1g_channels_EU, +}; + +/** List of valid S1G channels for India. */ +static const struct mmwlan_s1g_channel s1g_channels_IN[] = { + /* Ctr Freq (Hz), Duty Cycle (%/100), Omit Control Response, Global Op Class, S1G Op Class, S1G Chan #, Op BW, Max Tx EIRP (dBm), Min Packet Spacing Window (microsec), airtime_min (microsec), airtime_max (microsec) */ + { 865500000, 280, false, 66, 6, 5, 1, 16, 0, 0, 0 }, + { 866500000, 280, false, 66, 6, 7, 1, 16, 0, 0, 0 }, + { 867500000, 280, false, 66, 6, 9, 1, 16, 0, 0, 0 }, +}; + +/** Channel list structure for India. */ +static const struct mmwlan_s1g_channel_list s1g_channel_list_IN = { + .country_code = "IN", + .num_channels = (sizeof(s1g_channels_IN)/sizeof(s1g_channels_IN[0])), + .channels = s1g_channels_IN, +}; + +/** List of valid S1G channels for Japan. */ +static const struct mmwlan_s1g_channel s1g_channels_JP[] = { + /* Ctr Freq (Hz), Duty Cycle (%/100), Omit Control Response, Global Op Class, S1G Op Class, S1G Chan #, Op BW, Max Tx EIRP (dBm), Min Packet Spacing Window (microsec), airtime_min (microsec), airtime_max (microsec) */ + { 921000000, 1000, true, 73, 8, 9, 1, 16, 2000, 2000, 100000 }, + { 923000000, 1000, true, 73, 8, 13, 1, 16, 2000, 2000, 100000 }, + { 924000000, 1000, true, 73, 8, 15, 1, 16, 2000, 2000, 100000 }, + { 925000000, 1000, true, 73, 8, 17, 1, 16, 2000, 2000, 100000 }, + { 926000000, 1000, true, 73, 8, 19, 1, 16, 2000, 2000, 100000 }, + { 927000000, 1000, true, 73, 8, 21, 1, 16, 2000, 2000, 100000 }, + { 923500000, 1000, true, 64, 9, 2, 2, 16, 2000, 2000, 100000 }, + { 924500000, 1000, true, 64, 10, 4, 2, 16, 2000, 2000, 100000 }, + { 925500000, 1000, true, 64, 9, 6, 2, 16, 2000, 2000, 100000 }, + { 926500000, 1000, true, 64, 10, 8, 2, 16, 2000, 2000, 100000 }, + { 924500000, 1000, true, 65, 11, 36, 4, 16, 2000, 2000, 100000 }, + { 925500000, 1000, true, 65, 12, 38, 4, 16, 2000, 2000, 100000 }, +}; + +/** Channel list structure for Japan. */ +static const struct mmwlan_s1g_channel_list s1g_channel_list_JP = { + .country_code = "JP", + .num_channels = (sizeof(s1g_channels_JP)/sizeof(s1g_channels_JP[0])), + .channels = s1g_channels_JP, +}; + +/** List of valid S1G channels for South Korea. */ +static const struct mmwlan_s1g_channel s1g_channels_KR[] = { + /* Ctr Freq (Hz), Duty Cycle (%/100), Omit Control Response, Global Op Class, S1G Op Class, S1G Chan #, Op BW, Max Tx EIRP (dBm), Min Packet Spacing Window (microsec), airtime_min (microsec), airtime_max (microsec) */ + { 918000000, 10000, false, 74, 14, 1, 1, 4, 50000, 0, 4000000 }, + { 919000000, 10000, false, 74, 14, 3, 1, 4, 50000, 0, 4000000 }, + { 920000000, 10000, false, 74, 14, 5, 1, 4, 50000, 0, 4000000 }, + { 921000000, 10000, false, 74, 14, 7, 1, 4, 50000, 0, 4000000 }, + { 922000000, 10000, false, 74, 14, 9, 1, 10, 50000, 0, 4000000 }, + { 923000000, 10000, false, 74, 14, 11, 1, 10, 50000, 0, 4000000 }, + { 918500000, 10000, false, 75, 15, 2, 2, 4, 50000, 0, 4000000 }, + { 920500000, 10000, false, 75, 15, 6, 2, 4, 50000, 0, 4000000 }, + { 922500000, 10000, false, 75, 15, 10, 2, 10, 50000, 0, 4000000 }, + { 921500000, 10000, false, 76, 16, 8, 4, 4, 50000, 0, 4000000 }, + { 926500000, 10000, false, 74, 14, 18, 1, 17, 264, 0, 220000 }, /* Warning: regulatory requirements may not be met */ + { 927500000, 10000, false, 74, 14, 20, 1, 17, 264, 0, 220000 }, /* Warning: regulatory requirements may not be met */ + { 928500000, 10000, false, 74, 14, 22, 1, 17, 264, 0, 220000 }, /* Warning: regulatory requirements may not be met */ + { 929500000, 10000, false, 74, 14, 24, 1, 17, 264, 0, 220000 }, /* Warning: regulatory requirements may not be met */ + { 927000000, 10000, false, 75, 15, 19, 2, 20, 264, 0, 220000 }, /* Warning: regulatory requirements may not be met */ + { 929000000, 10000, false, 75, 15, 23, 2, 20, 264, 0, 220000 }, /* Warning: regulatory requirements may not be met */ +}; + +/** Channel list structure for South Korea. */ +static const struct mmwlan_s1g_channel_list s1g_channel_list_KR = { + .country_code = "KR", + .num_channels = (sizeof(s1g_channels_KR)/sizeof(s1g_channels_KR[0])), + .channels = s1g_channels_KR, +}; + +/** List of valid S1G channels for New Zealand. */ +static const struct mmwlan_s1g_channel s1g_channels_NZ[] = { + /* Ctr Freq (Hz), Duty Cycle (%/100), Omit Control Response, Global Op Class, S1G Op Class, S1G Chan #, Op BW, Max Tx EIRP (dBm), Min Packet Spacing Window (microsec), airtime_min (microsec), airtime_max (microsec) */ + { 915500000, 10000, false, 68, 26, 27, 1, 30, 0, 0, 0 }, + { 916500000, 10000, false, 68, 26, 29, 1, 30, 0, 0, 0 }, + { 917500000, 10000, false, 68, 26, 31, 1, 30, 0, 0, 0 }, + { 918500000, 10000, false, 68, 26, 33, 1, 30, 0, 0, 0 }, + { 919500000, 10000, false, 68, 26, 35, 1, 30, 0, 0, 0 }, + { 920500000, 10000, false, 68, 26, 37, 1, 36, 0, 0, 0 }, + { 921500000, 10000, false, 68, 26, 39, 1, 36, 0, 0, 0 }, + { 922500000, 10000, false, 68, 26, 41, 1, 36, 0, 0, 0 }, + { 923500000, 10000, false, 68, 26, 43, 1, 36, 0, 0, 0 }, + { 924500000, 10000, false, 68, 26, 45, 1, 36, 0, 0, 0 }, + { 925500000, 10000, false, 68, 26, 47, 1, 36, 0, 0, 0 }, + { 926500000, 10000, false, 68, 26, 49, 1, 36, 0, 0, 0 }, + { 927500000, 10000, false, 68, 26, 51, 1, 36, 0, 0, 0 }, + { 917000000, 10000, false, 69, 27, 30, 2, 30, 0, 0, 0 }, + { 919000000, 10000, false, 69, 27, 34, 2, 30, 0, 0, 0 }, + { 921000000, 10000, false, 69, 27, 38, 2, 36, 0, 0, 0 }, + { 923000000, 10000, false, 69, 27, 42, 2, 36, 0, 0, 0 }, + { 925000000, 10000, false, 69, 27, 46, 2, 36, 0, 0, 0 }, + { 927000000, 10000, false, 69, 27, 50, 2, 36, 0, 0, 0 }, + { 918000000, 10000, false, 70, 28, 32, 4, 30, 0, 0, 0 }, + { 922000000, 10000, false, 70, 28, 40, 4, 36, 0, 0, 0 }, + { 926000000, 10000, false, 70, 28, 48, 4, 36, 0, 0, 0 }, + { 924000000, 10000, false, 71, 29, 44, 8, 36, 0, 0, 0 }, +}; + +/** Channel list structure for New Zealand. */ +static const struct mmwlan_s1g_channel_list s1g_channel_list_NZ = { + .country_code = "NZ", + .num_channels = (sizeof(s1g_channels_NZ)/sizeof(s1g_channels_NZ[0])), + .channels = s1g_channels_NZ, +}; + +/** List of valid S1G channels for Singapore. */ +static const struct mmwlan_s1g_channel s1g_channels_SG[] = { + /* Ctr Freq (Hz), Duty Cycle (%/100), Omit Control Response, Global Op Class, S1G Op Class, S1G Chan #, Op BW, Max Tx EIRP (dBm), Min Packet Spacing Window (microsec), airtime_min (microsec), airtime_max (microsec) */ + { 866500000, 277, false, 66, 17, 7, 1, 29, 100000, 0, 1000000 }, + { 867500000, 277, false, 66, 17, 9, 1, 29, 100000, 0, 1000000 }, + { 868500000, 277, false, 66, 17, 11, 1, 29, 100000, 0, 1000000 }, + { 868000000, 277, false, 67, 19, 10, 2, 29, 100000, 0, 1000000 }, + { 920500000, 10000, false, 68, 18, 37, 1, 22, 0, 0, 0 }, + { 921500000, 10000, false, 68, 18, 39, 1, 22, 0, 0, 0 }, + { 922500000, 10000, false, 68, 18, 41, 1, 22, 0, 0, 0 }, + { 923500000, 10000, false, 68, 18, 43, 1, 22, 0, 0, 0 }, + { 924500000, 10000, false, 68, 18, 45, 1, 22, 0, 0, 0 }, + { 921000000, 10000, false, 69, 20, 38, 2, 22, 0, 0, 0 }, + { 923000000, 10000, false, 69, 20, 42, 2, 22, 0, 0, 0 }, + { 922000000, 10000, false, 70, 21, 40, 4, 22, 0, 0, 0 }, +}; + +/** Channel list structure for Singapore. */ +static const struct mmwlan_s1g_channel_list s1g_channel_list_SG = { + .country_code = "SG", + .num_channels = (sizeof(s1g_channels_SG)/sizeof(s1g_channels_SG[0])), + .channels = s1g_channels_SG, +}; + +/** List of valid S1G channels for USA. */ +static const struct mmwlan_s1g_channel s1g_channels_US[] = { + /* Ctr Freq (Hz), Duty Cycle (%/100), Omit Control Response, Global Op Class, S1G Op Class, S1G Chan #, Op BW, Max Tx EIRP (dBm), Min Packet Spacing Window (microsec), airtime_min (microsec), airtime_max (microsec) */ + { 902500000, 10000, false, 68, 1, 1, 1, 36, 0, 0, 0 }, /* Warning: regulatory requirements may not be met */ + { 903500000, 10000, false, 68, 1, 3, 1, 36, 0, 0, 0 }, + { 904500000, 10000, false, 68, 1, 5, 1, 36, 0, 0, 0 }, + { 905500000, 10000, false, 68, 1, 7, 1, 36, 0, 0, 0 }, + { 906500000, 10000, false, 68, 1, 9, 1, 36, 0, 0, 0 }, + { 907500000, 10000, false, 68, 1, 11, 1, 36, 0, 0, 0 }, + { 908500000, 10000, false, 68, 1, 13, 1, 36, 0, 0, 0 }, + { 909500000, 10000, false, 68, 1, 15, 1, 36, 0, 0, 0 }, + { 910500000, 10000, false, 68, 1, 17, 1, 36, 0, 0, 0 }, + { 911500000, 10000, false, 68, 1, 19, 1, 36, 0, 0, 0 }, + { 912500000, 10000, false, 68, 1, 21, 1, 36, 0, 0, 0 }, + { 913500000, 10000, false, 68, 1, 23, 1, 36, 0, 0, 0 }, + { 914500000, 10000, false, 68, 1, 25, 1, 36, 0, 0, 0 }, + { 915500000, 10000, false, 68, 1, 27, 1, 36, 0, 0, 0 }, + { 916500000, 10000, false, 68, 1, 29, 1, 36, 0, 0, 0 }, + { 917500000, 10000, false, 68, 1, 31, 1, 36, 0, 0, 0 }, + { 918500000, 10000, false, 68, 1, 33, 1, 36, 0, 0, 0 }, + { 919500000, 10000, false, 68, 1, 35, 1, 36, 0, 0, 0 }, + { 920500000, 10000, false, 68, 1, 37, 1, 36, 0, 0, 0 }, + { 921500000, 10000, false, 68, 1, 39, 1, 36, 0, 0, 0 }, + { 922500000, 10000, false, 68, 1, 41, 1, 36, 0, 0, 0 }, + { 923500000, 10000, false, 68, 1, 43, 1, 36, 0, 0, 0 }, + { 924500000, 10000, false, 68, 1, 45, 1, 36, 0, 0, 0 }, + { 925500000, 10000, false, 68, 1, 47, 1, 36, 0, 0, 0 }, + { 926500000, 10000, false, 68, 1, 49, 1, 36, 0, 0, 0 }, + { 927500000, 10000, false, 68, 1, 51, 1, 36, 0, 0, 0 }, + { 903000000, 10000, false, 69, 2, 2, 2, 36, 0, 0, 0 }, /* Warning: regulatory requirements may not be met */ + { 905000000, 10000, false, 69, 2, 6, 2, 36, 0, 0, 0 }, + { 907000000, 10000, false, 69, 2, 10, 2, 36, 0, 0, 0 }, + { 909000000, 10000, false, 69, 2, 14, 2, 36, 0, 0, 0 }, + { 911000000, 10000, false, 69, 2, 18, 2, 36, 0, 0, 0 }, + { 913000000, 10000, false, 69, 2, 22, 2, 36, 0, 0, 0 }, + { 915000000, 10000, false, 69, 2, 26, 2, 36, 0, 0, 0 }, + { 917000000, 10000, false, 69, 2, 30, 2, 36, 0, 0, 0 }, + { 919000000, 10000, false, 69, 2, 34, 2, 36, 0, 0, 0 }, + { 921000000, 10000, false, 69, 2, 38, 2, 36, 0, 0, 0 }, + { 923000000, 10000, false, 69, 2, 42, 2, 36, 0, 0, 0 }, + { 925000000, 10000, false, 69, 2, 46, 2, 36, 0, 0, 0 }, + { 927000000, 10000, false, 69, 2, 50, 2, 36, 0, 0, 0 }, + { 906000000, 10000, false, 70, 3, 8, 4, 36, 0, 0, 0 }, + { 910000000, 10000, false, 70, 3, 16, 4, 36, 0, 0, 0 }, + { 914000000, 10000, false, 70, 3, 24, 4, 36, 0, 0, 0 }, + { 918000000, 10000, false, 70, 3, 32, 4, 36, 0, 0, 0 }, + { 922000000, 10000, false, 70, 3, 40, 4, 36, 0, 0, 0 }, + { 926000000, 10000, false, 70, 3, 48, 4, 36, 0, 0, 0 }, + { 908000000, 10000, false, 71, 4, 12, 8, 36, 0, 0, 0 }, + { 916000000, 10000, false, 71, 4, 28, 8, 36, 0, 0, 0 }, + { 924000000, 10000, false, 71, 4, 44, 8, 36, 0, 0, 0 }, +}; + +/** Channel list structure for USA. */ +static const struct mmwlan_s1g_channel_list s1g_channel_list_US = { + .country_code = "US", + .num_channels = (sizeof(s1g_channels_US)/sizeof(s1g_channels_US[0])), + .channels = s1g_channels_US, +}; + +/** Array of all channel list structs used for the regulatory database. */ + +static const struct mmwlan_s1g_channel_list *regulatory_db_domains[] = { + &s1g_channel_list_AU, + &s1g_channel_list_EU, + &s1g_channel_list_IN, + &s1g_channel_list_JP, + &s1g_channel_list_KR, + &s1g_channel_list_NZ, + &s1g_channel_list_SG, + &s1g_channel_list_US, +}; + +/** Regulatory database. */ +static const struct mmwlan_regulatory_db regulatory_db = { + .num_domains = (sizeof(regulatory_db_domains)/sizeof(regulatory_db_domains[0])), + .domains = regulatory_db_domains, +}; + +/** + * Get a pointer to regulatory_db. This function isn't strictly necessary, since regulatory_db + * can be accessed directly, but will prevent the compiler from generated warnings about + * regulatory_db being unused. + * + * @return Reference to the regulatory database + */ +static inline const struct mmwlan_regulatory_db *get_regulatory_db(void) +{ + return ®ulatory_db; +} + + +/** \} */ diff --git a/lib/MorseWlan/lib/esp32-xtensa-lx7/libmorse.a b/lib/MorseWlan/lib/esp32-xtensa-lx7/libmorse.a new file mode 100644 index 000000000..144d196f7 Binary files /dev/null and b/lib/MorseWlan/lib/esp32-xtensa-lx7/libmorse.a differ diff --git a/lib/MorseWlan/library.json b/lib/MorseWlan/library.json new file mode 100644 index 000000000..3496e92b6 --- /dev/null +++ b/lib/MorseWlan/library.json @@ -0,0 +1,14 @@ +{ + "name": "MorseWlan", + "version": "0.1.0", + "description": "Morse Micro mm-iot-esp32 SDK vendored for Meshtastic HaLow transport. Static morselib (Apache-2.0) + open-source shims.", + "frameworks": ["arduino", "espidf"], + "platforms": ["espressif32"], + "build": { + "srcDir": "src", + "srcFilter": ["+<*.c>"], + "includeDir": "include", + "libArchive": false, + "extraScript": "extra_script.py" + } +} diff --git a/lib/MorseWlan/src/bcf_aw_hm593.mbin.o b/lib/MorseWlan/src/bcf_aw_hm593.mbin.o new file mode 100644 index 000000000..b27b7b463 Binary files /dev/null and b/lib/MorseWlan/src/bcf_aw_hm593.mbin.o differ diff --git a/lib/MorseWlan/src/bcf_aw_hm593_4v3.mbin.o b/lib/MorseWlan/src/bcf_aw_hm593_4v3.mbin.o new file mode 100644 index 000000000..9bae4ac29 Binary files /dev/null and b/lib/MorseWlan/src/bcf_aw_hm593_4v3.mbin.o differ diff --git a/lib/MorseWlan/src/bcf_mf03120.mbin.o b/lib/MorseWlan/src/bcf_mf03120.mbin.o new file mode 100644 index 000000000..cf2c0659e Binary files /dev/null and b/lib/MorseWlan/src/bcf_mf03120.mbin.o differ diff --git a/lib/MorseWlan/src/bcf_mf08251.mbin.o b/lib/MorseWlan/src/bcf_mf08251.mbin.o new file mode 100644 index 000000000..4e9f883e9 Binary files /dev/null and b/lib/MorseWlan/src/bcf_mf08251.mbin.o differ diff --git a/lib/MorseWlan/src/bcf_mf08551.mbin.o b/lib/MorseWlan/src/bcf_mf08551.mbin.o new file mode 100644 index 000000000..4ef7c9675 Binary files /dev/null and b/lib/MorseWlan/src/bcf_mf08551.mbin.o differ diff --git a/lib/MorseWlan/src/bcf_mf08651_us.mbin.o b/lib/MorseWlan/src/bcf_mf08651_us.mbin.o new file mode 100644 index 000000000..3d6d3f674 Binary files /dev/null and b/lib/MorseWlan/src/bcf_mf08651_us.mbin.o differ diff --git a/lib/MorseWlan/src/bcf_mf16858_us.mbin.o b/lib/MorseWlan/src/bcf_mf16858_us.mbin.o new file mode 100644 index 000000000..6229413dd Binary files /dev/null and b/lib/MorseWlan/src/bcf_mf16858_us.mbin.o differ diff --git a/lib/MorseWlan/src/hostap_morse_common.h b/lib/MorseWlan/src/hostap_morse_common.h new file mode 100644 index 000000000..32ae95e9f --- /dev/null +++ b/lib/MorseWlan/src/hostap_morse_common.h @@ -0,0 +1,148 @@ +/* + * Copyright 2022-2024 Morse Micro + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#pragma once + +#include + +/* + * ---- + * Endianness operations + * ---- + */ + +#ifdef __big_endian__ +#define __BYTE_ORDER __BIG_ENDIAN +#else +#define __BYTE_ORDER __LITTLE_ENDIAN +#endif + +#define bswap_16(x) __builtin_bswap16(x) +#define bswap_32(x) __builtin_bswap32(x) + +#define INET_ADDRSTRLEN 16 +#define INET6_ADDRSTRLEN 46 + +/* Protocol families. */ +#define PF_INET 2 /* IP protocol family. */ +#define PF_INET6 10 /* IP version 6. */ + +/* Address families. */ +#define AF_INET PF_INET +#define AF_INET6 PF_INET6 + +/* + * ---- + * Type definitions + * ---- + */ +typedef signed char __s8; +typedef unsigned char __u8; +typedef signed short __s16; +typedef unsigned short __u16; +typedef signed int __s32; +typedef unsigned int __u32; + +struct in_addr { + __u32 s_addr; +}; + +struct in6_addr { + union { + __u32 u32_addr[4]; + __u8 u8_addr[16]; + } un; +#define s6_addr un.u8_addr +}; + +/* Stub function declaration for inet_ntop which is called from write_ipv4_info + * function in robust_av.c. Since they are not used we will create a dummy + * function declarion here. + */ +const char *inet_ntop(int __af, const void *__cp, char *__buf, int __len); + +/* Rename crypto functions to match symbol name mangling in morselib for avoidance of namespace + * collisions. */ +#define aes_decrypt mmint_aes_decrypt +#define aes_decrypt_deinit mmint_aes_decrypt_deinit +#define aes_decrypt_init mmint_aes_decrypt_init +#define crypto_bignum_add mmint_crypto_bignum_add +#define crypto_bignum_addmod mmint_crypto_bignum_addmod +#define crypto_bignum_cmp mmint_crypto_bignum_cmp +#define crypto_bignum_deinit mmint_crypto_bignum_deinit +#define crypto_bignum_div mmint_crypto_bignum_div +#define crypto_bignum_exptmod mmint_crypto_bignum_exptmod +#define crypto_bignum_init mmint_crypto_bignum_init +#define crypto_bignum_init_set mmint_crypto_bignum_init_set +#define crypto_bignum_init_uint mmint_crypto_bignum_init_uint +#define crypto_bignum_inverse mmint_crypto_bignum_inverse +#define crypto_bignum_is_odd mmint_crypto_bignum_is_odd +#define crypto_bignum_is_one mmint_crypto_bignum_is_one +#define crypto_bignum_is_zero mmint_crypto_bignum_is_zero +#define crypto_bignum_legendre mmint_crypto_bignum_legendre +#define crypto_bignum_mod mmint_crypto_bignum_mod +#define crypto_bignum_mulmod mmint_crypto_bignum_mulmod +#define crypto_bignum_rand mmint_crypto_bignum_rand +#define crypto_bignum_rshift mmint_crypto_bignum_rshift +#define crypto_bignum_sqrmod mmint_crypto_bignum_sqrmod +#define crypto_bignum_sub mmint_crypto_bignum_sub +#define crypto_bignum_to_bin mmint_crypto_bignum_to_bin +#define crypto_ec_deinit mmint_crypto_ec_deinit +#define crypto_ec_get_a mmint_crypto_ec_get_a +#define crypto_ec_get_b mmint_crypto_ec_get_b +#define crypto_ec_get_generator mmint_crypto_ec_get_generator +#define crypto_ec_get_order mmint_crypto_ec_get_order +#define crypto_ec_get_prime mmint_crypto_ec_get_prime +#define crypto_ec_init mmint_crypto_ec_init +#define crypto_ec_order_len mmint_crypto_ec_order_len +#define crypto_ec_point_add mmint_crypto_ec_point_add +#define crypto_ec_point_cmp mmint_crypto_ec_point_cmp +#define crypto_ec_point_x mmint_crypto_ec_point_x +#define crypto_ec_point_compute_y_sqr mmint_crypto_ec_point_compute_y_sqr +#define crypto_ec_point_deinit mmint_crypto_ec_point_deinit +#define crypto_ec_point_from_bin mmint_crypto_ec_point_from_bin +#define crypto_ec_point_init mmint_crypto_ec_point_init +#define crypto_ec_point_invert mmint_crypto_ec_point_invert +#define crypto_ec_point_is_at_infinity mmint_crypto_ec_point_is_at_infinity +#define crypto_ec_point_is_on_curve mmint_crypto_ec_point_is_on_curve +#define crypto_ec_point_mul mmint_crypto_ec_point_mul +#define crypto_ec_point_to_bin mmint_crypto_ec_point_to_bin +#define crypto_ec_prime_len mmint_crypto_ec_prime_len +#define crypto_ec_prime_len_bits mmint_crypto_ec_prime_len_bits +#define crypto_ecdh_deinit mmint_crypto_ecdh_deinit +#define crypto_ecdh_get_pubkey mmint_crypto_ecdh_get_pubkey +#define crypto_ecdh_init mmint_crypto_ecdh_init +#define crypto_ecdh_init2 mmint_crypto_ecdh_init2 +#define crypto_ecdh_set_peerkey mmint_crypto_ecdh_set_peerkey +#define crypto_ecdh_prime_len mmint_crypto_ecdh_prime_len +#define crypto_get_random mmint_crypto_get_random +#define crypto_unload mmint_crypto_unload +#define hmac_md5 mmint_hmac_md5 +#define hmac_sha1 mmint_hmac_sha1 +#define hmac_sha1_vector mmint_hmac_sha1_vector +#define hmac_sha256 mmint_hmac_sha256 +#define hmac_sha256_vector mmint_hmac_sha256_vector +#define hmac_sha384 mmint_hmac_sha384 +#define hmac_sha384_vector mmint_hmac_sha384_vector +#define hmac_sha512 mmint_hmac_sha512 +#define hmac_sha512_vector mmint_hmac_sha512_vector +#define omac1_aes_vector mmint_omac1_aes_vector +#define omac1_aes_128 mmint_omac1_aes_128 +#define pbkdf2_sha1 mmint_pbkdf2_sha1 +#define sha1_prf mmint_sha1_prf +#define sha1_vector mmint_sha1_vector +#define sha256_prf mmint_sha256_prf +#define sha256_prf_bits mmint_sha256_prf_bits +#define sha256_vector mmint_sha256_vector +#define sha384_prf mmint_sha384_prf +#define sha384_vector mmint_sha384_vector +#define sha512_prf mmint_sha512_prf +#define sha512_vector mmint_sha512_vector + +#define wpabuf_alloc mmint_wpabuf_alloc +#define wpabuf_alloc_copy mmint_wpabuf_alloc_copy +#define wpabuf_clear_free mmint_wpabuf_clear_free +#define wpabuf_put mmint_wpabuf_put diff --git a/lib/MorseWlan/src/lwip_compat_shim.c b/lib/MorseWlan/src/lwip_compat_shim.c new file mode 100644 index 000000000..f598dc76d --- /dev/null +++ b/lib/MorseWlan/src/lwip_compat_shim.c @@ -0,0 +1,27 @@ +#ifdef USE_MM_IOT_ESP32 +/* + * Stubs for LWIP netif callback API that Arduino-ESP32's prebuilt LWIP omits + * (CONFIG_LWIP_NETIF_STATUS_CALLBACK / _LINK_CALLBACK are disabled in its + * sdkconfig, so the real functions are absent from the static library). + * + * mmipal calls these once during init to register a single callback for + * link-up / IP-configured events. With these stubs, the callback never fires — + * mmwlan_register_link_state_cb (driven by the radio firmware) remains the + * authoritative source of link state, so this only costs us LWIP-level + * notifications (e.g. "DHCP got an address"). HaLowInterface polls + * mmipal_get_ip_config when it needs to know. + */ +#include "lwip/netif.h" + +void __attribute__((weak)) netif_set_link_callback(struct netif *netif, netif_status_callback_fn link_callback) +{ + (void)netif; + (void)link_callback; +} + +void __attribute__((weak)) netif_set_status_callback(struct netif *netif, netif_status_callback_fn status_callback) +{ + (void)netif; + (void)status_callback; +} +#endif diff --git a/lib/MorseWlan/src/mm6108.mbin.o b/lib/MorseWlan/src/mm6108.mbin.o new file mode 100644 index 000000000..7d021ff5d Binary files /dev/null and b/lib/MorseWlan/src/mm6108.mbin.o differ diff --git a/lib/MorseWlan/src/mmbuf.c b/lib/MorseWlan/src/mmbuf.c new file mode 100644 index 000000000..99681010f --- /dev/null +++ b/lib/MorseWlan/src/mmbuf.c @@ -0,0 +1,216 @@ +#ifdef USE_MM_IOT_ESP32 +/* + * Copyright 2024 Morse Micro + * + * SPDX-License-Identifier: Apache-2.0 + * + */ + +#include "mmbuf.h" +#include "mmosal.h" +#include "mmutils.h" + +static const struct mmbuf_ops mmbuf_heap_ops = {.free_mmbuf = mmosal_free}; + +struct mmbuf *mmbuf_alloc_on_heap(uint32_t space_at_start, uint32_t space_at_end) +{ + struct mmbuf *mmbuf; + uint8_t *buf; + uint32_t alloc_len = MM_FAST_ROUND_UP(sizeof(*mmbuf), 4) + MM_FAST_ROUND_UP(space_at_start + space_at_end, 4); + mmbuf = (struct mmbuf *)mmosal_malloc(alloc_len); + if (mmbuf == NULL) { + return NULL; + } + + /* We zero the buffer as a defensive measure to reduce the likelihood of unintentionally + * leaking information. */ + memset((uint8_t *)mmbuf, 0, alloc_len); + + buf = ((uint8_t *)mmbuf) + MM_FAST_ROUND_UP(sizeof(*mmbuf), 4); + + mmbuf_init(mmbuf, buf, MM_FAST_ROUND_UP(space_at_start + space_at_end, 4), space_at_start, &mmbuf_heap_ops); + + return mmbuf; +} + +struct mmbuf *mmbuf_make_copy_on_heap(struct mmbuf *original) +{ + struct mmbuf *mmbuf; + uint8_t *buf; + uint32_t alloc_len = MM_FAST_ROUND_UP(sizeof(*original), 4) + original->buf_len; + mmbuf = (struct mmbuf *)mmosal_malloc(alloc_len); + if (mmbuf == NULL) { + return NULL; + } + + buf = ((uint8_t *)mmbuf) + MM_FAST_ROUND_UP(sizeof(*mmbuf), 4); + mmbuf_init(mmbuf, buf, original->buf_len, original->start_offset, &mmbuf_heap_ops); + mmbuf->data_len = original->data_len; + + if (original->data_len) { + memcpy(mmbuf_get_data_start(mmbuf), mmbuf_get_data_start(original), mmbuf_get_data_length(original)); + } + + return mmbuf; +} + +void mmbuf_release(struct mmbuf *mmbuf) +{ + if (mmbuf == NULL) { + return; + } + + MMOSAL_ASSERT(mmbuf->ops != NULL && mmbuf->ops->free_mmbuf != NULL); + mmbuf->ops->free_mmbuf(mmbuf); +} + +#ifdef MMBUF_SANITY +static void mmbuf_list_sanity_check(struct mmbuf_list *list) +{ + unsigned cnt = 0; + struct mmbuf *walk; + struct mmbuf *prev = NULL; + + for (walk = list->head; walk != NULL; walk = walk->next) { + cnt++; + prev = walk; + } + + MMOSAL_ASSERT(cnt == list->len); + MMOSAL_ASSERT(prev == list->tail); +} +#endif + +void mmbuf_list_prepend(struct mmbuf_list *list, struct mmbuf *mmbuf) +{ + mmbuf->next = list->head; + list->head = mmbuf; + list->len++; + + if (list->tail == NULL) { + list->tail = list->head; + } + +#ifdef MMBUF_SANITY + mmbuf_list_sanity_check(list); +#endif +} + +void mmbuf_list_append(struct mmbuf_list *list, struct mmbuf *mmbuf) +{ + mmbuf->next = NULL; + if (list->head == NULL) { + list->head = mmbuf; + list->tail = mmbuf; + } else { + list->tail->next = mmbuf; + list->tail = mmbuf; + } + list->len++; + +#ifdef MMBUF_SANITY + mmbuf_list_sanity_check(list); +#endif +} + +static struct mmbuf *mmbuf_find_prev(struct mmbuf_list *list, struct mmbuf *mmbuf) +{ + struct mmbuf *walk, *next; + for (walk = list->head, next = walk->next; next != NULL; walk = next, next = walk->next) { + if (next == mmbuf) { + return walk; + } + } + return NULL; +} + +bool mmbuf_list_remove(struct mmbuf_list *list, struct mmbuf *mmbuf) +{ + struct mmbuf *prev = NULL; + + if (list->head == NULL) { + return false; + } + + if (list->head == mmbuf) { + list->head = mmbuf->next; + } else { + prev = mmbuf_find_prev(list, mmbuf); + if (prev == NULL) { + return false; + } + prev->next = mmbuf->next; + } + + if (list->tail == mmbuf) { + list->tail = prev; + } + + list->len--; + mmbuf->next = NULL; + +#ifdef MMBUF_SANITY + mmbuf_list_sanity_check(list); +#endif + + return true; +} + +struct mmbuf *mmbuf_list_dequeue(struct mmbuf_list *list) +{ + if (list->head == NULL) { + return NULL; + } else { + struct mmbuf *mmbuf = list->head; + list->head = mmbuf->next; + list->len--; + + if (list->tail == mmbuf) { + list->tail = NULL; + } + +#ifdef MMBUF_SANITY + mmbuf_list_sanity_check(list); +#endif + if (mmbuf != NULL) { + mmbuf->next = NULL; + } + return mmbuf; + } +} + +struct mmbuf *mmbuf_list_dequeue_tail(struct mmbuf_list *list) +{ + if (list->tail == NULL) { + return NULL; + } + + struct mmbuf *mmbuf = list->tail; + mmbuf_list_remove(list, mmbuf); + + return mmbuf; +} + +void mmbuf_list_clear(struct mmbuf_list *list) +{ + struct mmbuf *walk; + struct mmbuf *next; + +#ifdef MMBUF_SANITY + mmbuf_list_sanity_check(list); +#endif + + if (list->head == NULL) { + return; + } + + for (walk = list->head, next = walk->next; walk != NULL; walk = next, next = walk ? walk->next : NULL) { + mmbuf_release(walk); + } + + list->len = 0; + list->head = NULL; + list->tail = NULL; +} + +#endif /* USE_MM_IOT_ESP32 */ diff --git a/lib/MorseWlan/src/mmbuf.h b/lib/MorseWlan/src/mmbuf.h new file mode 100644 index 000000000..f26e2574c --- /dev/null +++ b/lib/MorseWlan/src/mmbuf.h @@ -0,0 +1,457 @@ +/* + * Copyright 2024 Morse Micro + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @defgroup MMBUF Morse Micro Buffer (mmbuf) API + * + * This API provides support for buffers tailored towards packet-like data that has + * headers and trailers that are applied at subsequent layers. + * + * It is designed to support various backends for memory allocation. The default + * is allocation on the heap, but other methods could be used due to the flexible API. + * + * @{ + */ + +#pragma once + +#include +#include +#include + +#include "mmosal.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct mmbuf_ops; + +/** + * Core mmbuf data structure. + * + * @note The contents of this data structure should never need to be accessed directly. Rather + * the various functions provided as part of this API should be used. + * + * @code + * +----------------------------------------------------------+ + * | RESERVED | Data | RESERVED | + * +----------------------------------------------------------+ + * ^ ^ ^ ^ + * | | | | + * | |<-----------data_len--------->| | + * | start_offset | + * | | + * |<-----------------------buf_len-------------------------->| + * buf + * @endcode + */ +struct mmbuf { + /** The buffer where data is stored. */ + uint8_t *buf; + /** Length of the buffer. */ + uint32_t buf_len; + /** Offset where actual data starts in the buffer. */ + uint32_t start_offset; + /** Length of actual data in the buffer. */ + uint32_t data_len; + /** Reference to operations data structure for this mmbuf. */ + const struct mmbuf_ops *ops; + /** Pointer that can be used to construct linked lists. */ + struct mmbuf *volatile next; +}; + +/** Operations data structure for mmbuf. */ +struct mmbuf_ops { + /** Free the given mmbuf. */ + void (*free_mmbuf)(void *mmbuf); +}; + +/** + * Initialize an mmbuf header with the given values. + * + * @param mmbuf mmbuf to initialize. + * @param buf Pointer to buffer. + * @param buf_len Length of @p buf. + * @param data_start_offset Initial value for @c start_offset. + * @param ops Operations data structure. + */ +static inline void mmbuf_init(struct mmbuf *mmbuf, uint8_t *buf, uint32_t buf_len, uint32_t data_start_offset, + const struct mmbuf_ops *ops) +{ + memset(mmbuf, 0, sizeof(*mmbuf)); + mmbuf->buf = buf; + mmbuf->buf_len = buf_len; + mmbuf->start_offset = data_start_offset; + mmbuf->ops = ops; +} + +/** + * Allocate a new mmbuf on the heap (using @ref mmosal_malloc()). + * + * @param space_at_start Amount of space to reserve at start of buffer. + * @param space_at_end Amount of space to reserve at end of buffer. + * + * @note @c start_offset will be set to @p space_at_start, and @c buf_len will be the sum + * of @p space_at_start and @p space_at_end (rounded up to a multiple of 4). + * + * @returns newly allocated mmbuf on success or @c NULL on failure. + */ +struct mmbuf *mmbuf_alloc_on_heap(uint32_t space_at_start, uint32_t space_at_end); + +/** + * Make a copy of the given mmbuf. Note that regardless of the backend that allocated + * @p original, the newly allocated mmbuf will be allocated on the heap using + * @ref mmbuf_alloc_on_heap(). + * + * @param original mmbuf to copy. + * + * @returns newly allocated mmbuf on success or @c NULL on failure. + */ +struct mmbuf *mmbuf_make_copy_on_heap(struct mmbuf *original); + +/** + * Release a reference to the given mmbuf. If this was the last reference (@c addition_ref_cnt + * was 0) then the mmbuf will be freed using the appropriate op callback. + * + * @param mmbuf The mmbuf to release reference to. May be @c NULL. + */ +void mmbuf_release(struct mmbuf *mmbuf); + +/** + * Gets a pointer to the start of the data in the mmbuf. + * + * @param mmbuf The mmbuf to operate on. + * + * @returns a pointer to the start of the data in the mmbuf. + */ +static inline uint8_t *mmbuf_get_data_start(struct mmbuf *mmbuf) +{ + return mmbuf->buf + mmbuf->start_offset; +} + +/** + * Gets a pointer to the end of the data in the mmbuf. + * + * @param mmbuf The mmbuf to operate on. + * + * @returns a pointer to the end of the data in the mmbuf. + */ +static inline uint8_t *mmbuf_get_data_end(struct mmbuf *mmbuf) +{ + return mmbuf->buf + mmbuf->start_offset + mmbuf->data_len; +} + +/** + * Gets the length of the data currently in the mmbuf. + * + * @param mmbuf The mmbuf to operate on. + * + * @returns the length of the data currently in the mmbuf (note that this is different from the + * length of the available buffer space). + */ +static inline uint32_t mmbuf_get_data_length(struct mmbuf *mmbuf) +{ + return mmbuf->data_len; +} + +/** + * Returns the amount of space available for prepending to the data in the buffer. + * + * @param mmbuf The mmbuf to operate on. + * + * @returns the available space in bytes. + */ +static inline uint32_t mmbuf_available_space_at_start(struct mmbuf *mmbuf) +{ + return mmbuf->start_offset; +} + +/** + * Returns the amount of space available for appending to the data in the buffer. + * + * @param mmbuf The mmbuf to operate on. + * + * @returns the available space in bytes. + */ +static inline uint32_t mmbuf_available_space_at_end(struct mmbuf *mmbuf) +{ + return mmbuf->buf_len - (mmbuf->start_offset + mmbuf->data_len); +} + +/** + * Reserves space immediately before the data currently in the given mmbuf and returns + * a pointer to this space. + * + * For a function that also copies data in, see @ref mmbuf_prepend_data(). + * + * @warning @p len must be less than or equal to @ref mmbuf_available_space_at_start(). + * + * @param mmbuf The mmbuf to operate on. + * @param len Length of data to be prepended. + * + * @returns a pointer to the place in the buffer where the data should be put. + */ +static inline uint8_t *mmbuf_prepend(struct mmbuf *mmbuf, uint32_t len) +{ + MMOSAL_ASSERT(len <= mmbuf_available_space_at_start(mmbuf)); + mmbuf->start_offset -= len; + mmbuf->data_len += len; + return mmbuf->buf + mmbuf->start_offset; +} + +/** + * Prepends the given data to the data already in the mmbuf. + * + * @warning @p len must be less than or equal to @ref mmbuf_available_space_at_start(). + * + * @warning The memory area pointed to by data must not overlap with the mmbuf data. + * + * @param mmbuf The mmbuf to operate on. + * @param data The data to be prepended. + * @param len Length of data to be prepended. + */ +static inline void mmbuf_prepend_data(struct mmbuf *mmbuf, const uint8_t *data, uint32_t len) +{ + uint8_t *dest = mmbuf_prepend(mmbuf, len); + memcpy(dest, data, len); +} + +/** + * Reserves space immediately after the data currently in the given mmbuf and returns + * a pointer to this space. + * + * For a function that also copies data in, see @ref mmbuf_append_data(). + * + * @warning @p len must be less than or equal to @ref mmbuf_available_space_at_end(). + * + * @param mmbuf The mmbuf to operate on. + * @param len Length of data to be append. + * + * @returns a pointer to the place in the buffer where the data should be put. + */ +static inline uint8_t *mmbuf_append(struct mmbuf *mmbuf, uint32_t len) +{ + uint8_t *ret = mmbuf_get_data_end(mmbuf); + MMOSAL_ASSERT(len <= mmbuf_available_space_at_end(mmbuf)); + mmbuf->data_len += len; + return ret; +} + +/** + * Appends the given data to the data already in the mmbuf. + * + * @warning @p len must be less than or equal to @ref mmbuf_available_space_at_start(). + * + * @param mmbuf The mmbuf to operate on. + * @param data The data to be prepended. + * @param len Length of data to be prepended. + */ +static inline void mmbuf_append_data(struct mmbuf *mmbuf, const uint8_t *data, uint32_t len) +{ + uint8_t *dest = mmbuf_append(mmbuf, len); + memcpy(dest, data, len); +} + +/** + * Remove data from the start of the mmbuf. + * + * @param mmbuf mmbuf to operate on. + * @param len Length of data to remove. + * + * @returns a pointer to the removed data or NULL if the mmbuf data length was less than @p len. + */ +static inline uint8_t *mmbuf_remove_from_start(struct mmbuf *mmbuf, uint32_t len) +{ + uint8_t *ret; + + if (mmbuf_get_data_length(mmbuf) < len) { + return NULL; + } + + ret = mmbuf_get_data_start(mmbuf); + + mmbuf->start_offset += len; + mmbuf->data_len -= len; + + return ret; +} + +/** + * Remove data from the end of the mmbuf. + * + * @param mmbuf mmbuf to operate on. + * @param len Length of data to remove. + * + * @returns a pointer to the removed data or NULL if the mmbuf data length was less than @p len. + */ +static inline uint8_t *mmbuf_remove_from_end(struct mmbuf *mmbuf, uint32_t len) +{ + uint8_t *ret; + + if (mmbuf_get_data_length(mmbuf) < len) { + return NULL; + } + + ret = mmbuf_get_data_end(mmbuf) - len; + + mmbuf->data_len -= len; + + return ret; +} + +/** + * Truncate the mmbuf data to the given length. + * + * @param mmbuf mmbuf to operate on. + * @param len New data length. (Must be less than or equal to the data length + * of the mmbuf). + */ +static inline void mmbuf_truncate(struct mmbuf *mmbuf, uint32_t len) +{ + MMOSAL_ASSERT(len <= mmbuf->data_len); + mmbuf->data_len = len; +} + +/* --------------------------------------------------------------------------------------------- */ + +/** Structure that can be used as the head of a linked list of mmbufs that counts its length. */ +struct mmbuf_list { + /** First mmbuf in the list. */ + struct mmbuf *volatile head; + /** Last mmbuf in the list. */ + struct mmbuf *volatile tail; + /** Length of the list. */ + volatile uint32_t len; +}; + +/** Static initializer for @ref mmbuf_list. */ +#define MMBUF_LIST_INIT \ + { \ + NULL, NULL, 0 \ + } + +/** + * Initialization function for @ref mmbuf_list, for cases where @c MMBUF_LIST_INIT + * cannot be used. + * + * @param list The mmbuf_list to init. + */ +static inline void mmbuf_list_init(struct mmbuf_list *list) +{ + list->head = NULL; + list->tail = NULL; + list->len = 0; +} + +/** + * Add an mmbuf to the start of an mmbuf list. + * + * @param list The list to prepend to. + * @param mmbuf The mmbuf to prepend. + */ +void mmbuf_list_prepend(struct mmbuf_list *list, struct mmbuf *mmbuf); + +/** + * Add an mmbuf to the end of an mmbuf list. + * + * @param list The list to append to. + * @param mmbuf The mmbuf to append. + */ +void mmbuf_list_append(struct mmbuf_list *list, struct mmbuf *mmbuf); + +/** + * Remove an mmbuf from an mmbuf list. + * + * @param list The list to remove from. + * @param mmbuf The mmbuf to remove. + * + * @returns @c true if the given @c mmbuf was present in @c list, else @c fase. + */ +bool mmbuf_list_remove(struct mmbuf_list *list, struct mmbuf *mmbuf); + +/** + * Remove the mmbuf at the head of the list and return it. + * + * @param list The list to dequeue from. + * + * @returns the dequeued mmbuf, or @c NULL if the list is empty. + */ +struct mmbuf *mmbuf_list_dequeue(struct mmbuf_list *list); + +/** + * Remove the mmbuf at the tail of the list and return it. + * + * @param list The list to dequeue from. + * + * @returns the dequeued mmbuf, or @c NULL if the list is empty. + */ +struct mmbuf *mmbuf_list_dequeue_tail(struct mmbuf_list *list); + +/** + * Remove all mmbufs from the list and return as a linked list. + * + * @param list The list to dequeue from. + * + * @returns the dequeued mmbufs, or @c NULL if the list is empty. + */ +static inline struct mmbuf *mmbuf_list_dequeue_all(struct mmbuf_list *list) +{ + struct mmbuf *head = list->head; + list->head = NULL; + list->tail = NULL; + list->len = 0; + return head; +} + +/** + * Checks whether the given mmbuf list is empty. + * + * @param list The list to check. + * + * @returns @c true if the list is empty, else @c false. + */ +static inline bool mmbuf_list_is_empty(struct mmbuf_list *list) +{ + return (list->head == NULL); +} + +/** + * Returns the head of the mmbuf list. + * + * @param list The list to peek into. + * + * @returns the mmbuf at the head of the list. + */ +static inline struct mmbuf *mmbuf_list_peek(struct mmbuf_list *list) +{ + return list->head; +} + +/** + * Returns the tail of the mmbuf list. + * + * @param list The list to peek into. + * + * @returns the mmbuf at the tail of the list. + */ +static inline struct mmbuf *mmbuf_list_peek_tail(struct mmbuf_list *list) +{ + return list->tail; +} + +/** + * Free all the packets in the given list and reset the list to empty state. + * + * @param list The list to clear. + */ +void mmbuf_list_clear(struct mmbuf_list *list); + +#ifdef __cplusplus +} +#endif + +/** @} */ diff --git a/lib/MorseWlan/src/mmcrc.c b/lib/MorseWlan/src/mmcrc.c new file mode 100644 index 000000000..4efa97946 --- /dev/null +++ b/lib/MorseWlan/src/mmcrc.c @@ -0,0 +1,50 @@ +#ifdef USE_MM_IOT_ESP32 +/* + * Copyright 2024 Morse Micro + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "mmcrc.h" + +/** + * Static table used for the table_driven implementation. + */ +static const uint16_t crc16_xmodem_lookup_table[256] = { + 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7, 0x8108, 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, + 0xf1ef, 0x1231, 0x0210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6, 0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c, + 0xf3ff, 0xe3de, 0x2462, 0x3443, 0x0420, 0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485, 0xa56a, 0xb54b, 0x8528, 0x9509, 0xe5ee, + 0xf5cf, 0xc5ac, 0xd58d, 0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, 0x5695, 0x46b4, 0xb75b, 0xa77a, 0x9719, 0x8738, + 0xf7df, 0xe7fe, 0xd79d, 0xc7bc, 0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823, 0xc9cc, 0xd9ed, 0xe98e, + 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b, 0x5af5, 0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0x0a50, 0x3a33, 0x2a12, 0xdbfd, 0xcbdc, + 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a, 0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 0x0c60, 0x1c41, 0xedae, + 0xfd8f, 0xcdec, 0xddcd, 0xad2a, 0xbd0b, 0x8d68, 0x9d49, 0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70, + 0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, 0x9f59, 0x8f78, 0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, + 0xe16f, 0x1080, 0x00a1, 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067, 0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, + 0xe37f, 0xf35e, 0x02b1, 0x1290, 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256, 0xb5ea, 0xa5cb, 0x95a8, 0x8589, 0xf56e, + 0xe54f, 0xd52c, 0xc50d, 0x34e2, 0x24c3, 0x14a0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, 0xa7db, 0xb7fa, 0x8799, 0x97b8, + 0xe75f, 0xf77e, 0xc71d, 0xd73c, 0x26d3, 0x36f2, 0x0691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634, 0xd94c, 0xc96d, 0xf90e, + 0xe92f, 0x99c8, 0x89e9, 0xb98a, 0xa9ab, 0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x08e1, 0x3882, 0x28a3, 0xcb7d, 0xdb5c, + 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a, 0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92, 0xfd2e, + 0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9, 0x7c26, 0x6c07, 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1, + 0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8, 0x6e17, 0x7e36, 0x4e55, 0x5e74, 0x2e93, 0x3eb2, 0x0ed1, + 0x1ef0}; + +/** + * @note This implementation(with a few modifications) and corresponding table was generated using + * pycrc v0.9.2 (MIT) using the XMODEM model. https://pycrc.org/. The code generated by pycrc + * is not considered a substantial portion of the software, therefore the licence does not + * cover the generated code, and the author of pycrc will not claim any copyright on the + * generated code (https://pypi.org/project/pycrc/0.9.2/). + */ +uint16_t mmcrc_16_xmodem(uint16_t crc, const void *data, size_t data_len) +{ + const uint8_t *d = (const uint8_t *)data; + + while (data_len--) { + crc = (crc16_xmodem_lookup_table[((crc >> 8) ^ *d++)] ^ (crc << 8)); + } + return crc; +} + +#endif /* USE_MM_IOT_ESP32 */ diff --git a/lib/MorseWlan/src/mmcrc.h b/lib/MorseWlan/src/mmcrc.h new file mode 100644 index 000000000..7508ed4b4 --- /dev/null +++ b/lib/MorseWlan/src/mmcrc.h @@ -0,0 +1,40 @@ +/* + * Copyright 2024 Morse Micro + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @defgroup MMCRC Morse Micro Cyclic Redundancy Check (mmcrc) API + * + * This API provides support for CRC algorithms used by Morse Micro code. + * + * @{ + */ + +#pragma once + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Compute the CRC-16 for the data buffer using the XMODEM model. + * + * @param crc Seed for CRC calc, zero in most cases this is zero (0). If chaining calls + * then this is the output from the previous invocation. + * @param data Pointer to the start of the data to calculate the crc over. + * @param data_len Length of the data array in bytes. + * + * @return Returns the CRC value. + */ +uint16_t mmcrc_16_xmodem(uint16_t crc, const void *data, size_t data_len); + +#ifdef __cplusplus +} +#endif + +/** @} */ diff --git a/lib/MorseWlan/src/mmhal.c b/lib/MorseWlan/src/mmhal.c new file mode 100644 index 000000000..efcf7bcba --- /dev/null +++ b/lib/MorseWlan/src/mmhal.c @@ -0,0 +1,100 @@ +#ifdef USE_MM_IOT_ESP32 +/* + * Copyright 2021-2023 Morse Micro + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "mmhal.h" +#include "mmosal.h" +#include "mmutils.h" +#include "mmwlan.h" + +#include "driver/gpio.h" +#include "esp_random.h" +#include "esp_system.h" +#include "sdkconfig.h" + +void mmhal_init(void) +{ + /* We initialise the MM_RESET_N Pin here so that we can hold the MM6108 in reset regardless of + * whether the mmhal_wlan_init/deinit function have been called. This allows us to ensure the + * chip is in its lowest power state. You may want to revise this depending on your particular + * hardware configuration. */ + gpio_config_t io_conf = {}; + io_conf.intr_type = GPIO_INTR_DISABLE; + io_conf.mode = GPIO_MODE_OUTPUT; + io_conf.pin_bit_mask = (1 << CONFIG_MM_RESET_N); + io_conf.pull_down_en = 0; + io_conf.pull_up_en = 0; + gpio_config(&io_conf); + + gpio_set_level(CONFIG_MM_RESET_N, 0); + + /* Initialise the gpio ISR handler service. This allows per-pin GPIO interrupt handlers and is + * what is used to register all the wlan related interrupt. */ + gpio_install_isr_service(0); +} + +void mmhal_log_write(const uint8_t *data, size_t length) +{ + while (length--) { + putc(*data++, stdout); + } +} + +void mmhal_log_flush(void) {} + +void mmhal_read_mac_addr(uint8_t *mac_addr) +{ + /* We do not override the MAC address here. Therefore the driver will attempt to read it from + * the chip and failing that will assign a randomly generated address. */ + (void)(mac_addr); +} + +uint32_t mmhal_random_u32(uint32_t min, uint32_t max) +{ + /* Note: the below implementation does not guarantee a uniform distribution. */ + + uint32_t random_value = esp_random(); + + if (min == 0 && max == UINT32_MAX) { + return random_value; + } else { + /* Calculate the range and shift required to fit within [min, max] */ + return (random_value % (max - min + 1)) + min; + } +} + +void mmhal_reset(void) +{ + esp_restart(); + while (1) { + } +} + +void mmhal_set_deep_sleep_veto(uint8_t veto_id) +{ + MM_UNUSED(veto_id); +} + +void mmhal_clear_deep_sleep_veto(uint8_t veto_id) +{ + MM_UNUSED(veto_id); +} + +void mmhal_set_led(uint8_t led, uint8_t level) +{ + MM_UNUSED(led); + MM_UNUSED(level); +} + +bool mmhal_get_hardware_version(char *version_buffer, size_t version_buffer_length) +{ + /* Note: You need to identify the correct hardware and or version + * here using whatever means available (GPIO's, version number stored in EEPROM, etc) + * and return the correct string here. */ + return !mmosal_safer_strcpy(version_buffer, "MM-ESP32S3 V1.0", version_buffer_length); +} + +#endif /* USE_MM_IOT_ESP32 */ diff --git a/lib/MorseWlan/src/mmhal_wlan_binaries.c b/lib/MorseWlan/src/mmhal_wlan_binaries.c new file mode 100644 index 000000000..00a8528d2 --- /dev/null +++ b/lib/MorseWlan/src/mmhal_wlan_binaries.c @@ -0,0 +1,72 @@ +#ifdef USE_MM_IOT_ESP32 +/* + * Copyright 2021-2023 Morse Micro + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "mmhal_wlan.h" +#include "mmosal.h" + +/* + * --------------------------------------------------------------------------------------------- + * BCF Retrieval + * --------------------------------------------------------------------------------------------- + */ + +/* + * The following implementation reads the BCF File from the config store. + */ + +void mmhal_wlan_read_bcf_file(uint32_t offset, uint32_t requested_len, struct mmhal_robuf *robuf) +{ + /** Points to the start of the BCF binary image. Defined as part of the Makefile */ + extern uint8_t bcf_binary_start; + /** Points to the end of the BCF binary image. Defined as part of the Makefile */ + extern uint8_t bcf_binary_end; + + size_t bcf_len = &bcf_binary_end - &bcf_binary_start; + + /* Initialise robuf */ + robuf->buf = NULL; + robuf->len = 0; + robuf->free_arg = NULL; + robuf->free_cb = NULL; + + /* Sanity check */ + if (bcf_len < offset) { + printf("Detected an attempt to start reading off the end of the bcf file.\n"); + return; + } + + robuf->buf = (uint8_t *)&bcf_binary_start + offset; + robuf->len = bcf_len - offset; + robuf->len = (robuf->len < requested_len) ? robuf->len : requested_len; +} + +/* + * --------------------------------------------------------------------------------------------- + * Firmware Retrieval + * --------------------------------------------------------------------------------------------- + */ +/** Points to the start of the firmware binary image. Defined as part of the Makefile */ +extern uint8_t firmware_binary_start; +/** Points to the end of the firmware binary image. Defined as part of the Makefile */ +extern uint8_t firmware_binary_end; + +void mmhal_wlan_read_fw_file(uint32_t offset, uint32_t requested_len, struct mmhal_robuf *robuf) +{ + uint32_t firmware_len = &firmware_binary_end - &firmware_binary_start; + if (offset > firmware_len) { + printf("Detected an attempt to start read off the end of the firmware file.\n"); + robuf->buf = NULL; + return; + } + + robuf->buf = (&firmware_binary_start + offset); + firmware_len -= offset; + + robuf->len = (firmware_len < requested_len) ? firmware_len : requested_len; +} + +#endif /* USE_MM_IOT_ESP32 */ diff --git a/lib/MorseWlan/src/mmipal_lwip.c b/lib/MorseWlan/src/mmipal_lwip.c new file mode 100644 index 000000000..ac2dcf5f8 --- /dev/null +++ b/lib/MorseWlan/src/mmipal_lwip.c @@ -0,0 +1,726 @@ +#ifdef USE_MM_IOT_ESP32 +/* + * Copyright 2021-2023 Morse Micro + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "mmipal.h" +#include "mmnetif.h" +#include "mmosal.h" +#include "mmutils.h" +#include "mmwlan.h" + +#include "lwip/api.h" +#include "lwip/autoip.h" +#include "lwip/def.h" +#include "lwip/dhcp.h" +#include "lwip/dhcp6.h" +#include "lwip/dns.h" +#include "lwip/etharp.h" +#include "lwip/ethip6.h" +#include "lwip/igmp.h" +#include "lwip/inet.h" +#include "lwip/ip4_frag.h" +#include "lwip/ip6_frag.h" +#include "lwip/ip_addr.h" +#include "lwip/mem.h" +#include "lwip/sockets.h" +#include "lwip/stats.h" +#include "lwip/tcp.h" +#include "lwip/tcpip.h" +#include "lwip/udp.h" + +static struct mmipal_data { + struct netif lwip_mmnetif; + /** This stores the IPv4 link state for the IP stack. I.e., do we have an IP address or not. */ + enum mmipal_link_state ip_link_state; + /** Flag requesting ARP response offload feature */ + bool offload_arp_response; + /** ARP refresh offload interval in seconds */ + uint32_t offload_arp_refresh_s; + bool dhcp_offload_init_complete; + /** The link status callback function that has been registered. */ + mmipal_link_status_cb_fn_t link_status_callback; + /** The extended link status callback function that has been registered. */ + mmipal_ext_link_status_cb_fn_t ext_link_status_callback; + /** Argument for the extended link status callback function that has been registered. */ + void *ext_link_status_callback_arg; +#if LWIP_IPV4 + enum mmipal_addr_mode ip4_mode; +#endif +#if LWIP_IPV6 + enum mmipal_ip6_addr_mode ip6_mode; +#endif +} mmipal_data = {}; + +/** Getter function to retrieve the global mmipal data structure.*/ +static inline struct mmipal_data *mmipal_get_data(void) +{ + return &mmipal_data; +} + +static void netif_status_callback(struct netif *netif); + +#if LWIP_IPV4 + +/** + * DHCP Lease update callback, invoked when we get a new DHCP lease. + * + * @param lease_info The new DHCP lease. + */ +static void mmipal_dhcp_lease_updated(const struct mmwlan_dhcp_lease_info *lease_info, void *arg) +{ + struct mmipal_data *data = mmipal_get_data(); + + ip4_addr_t ip_addr, netmask, gateway; + ip_addr_t dns_addr = ip_addr_any; + + MM_UNUSED(arg); + + data->dhcp_offload_init_complete = true; + + ip4_addr_set_u32(&ip_addr, lease_info->ip4_addr); + ip4_addr_set_u32(&netmask, lease_info->mask4_addr); + ip4_addr_set_u32(&gateway, lease_info->gw4_addr); + ip4_addr_set_u32(ip_2_ip4(&dns_addr), lease_info->dns4_addr); + + LOCK_TCPIP_CORE(); + netif_set_addr(&data->lwip_mmnetif, &ip_addr, &netmask, &gateway); + dns_setserver(0, &dns_addr); + UNLOCK_TCPIP_CORE(); + + netif_status_callback(&data->lwip_mmnetif); +} + +enum mmipal_status mmipal_get_ip_config(struct mmipal_ip_config *config) +{ + struct mmipal_data *data = mmipal_get_data(); + char *result; + + config->mode = data->ip4_mode; + + result = ipaddr_ntoa_r(&data->lwip_mmnetif.ip_addr, config->ip_addr, sizeof(config->ip_addr)); + LWIP_ASSERT("IP buf too short", result != NULL); + + result = ipaddr_ntoa_r(&data->lwip_mmnetif.netmask, config->netmask, sizeof(config->netmask)); + LWIP_ASSERT("IP buf too short", result != NULL); + + result = ipaddr_ntoa_r(&data->lwip_mmnetif.gw, config->gateway_addr, sizeof(config->gateway_addr)); + LWIP_ASSERT("IP buf too short", result != NULL); + + return MMIPAL_SUCCESS; +} + +enum mmipal_status mmipal_set_ip_config(const struct mmipal_ip_config *config) +{ + struct mmipal_data *data = mmipal_get_data(); + int result; + ip_addr_t ip_addr = ip_addr_any; + ip_addr_t netmask = ip_addr_any; + ip_addr_t gateway = ip_addr_any; + struct netif *netif = &data->lwip_mmnetif; + + if (config->mode != MMIPAL_DHCP_OFFLOAD && data->ip4_mode == MMIPAL_DHCP_OFFLOAD) { + printf("Once enabled DHCP offload mode cannot be disabled\n"); + return MMIPAL_NOT_SUPPORTED; + } + + switch (config->mode) { + case MMIPAL_DISABLED: + printf("%s mode not supported\n", "DISABLED"); + return MMIPAL_INVALID_ARGUMENT; + + case MMIPAL_AUTOIP: + printf("%s mode not supported\n", "AutoIP"); + return MMIPAL_INVALID_ARGUMENT; + + case MMIPAL_DHCP_OFFLOAD: + /* Currently we only support enabling DHCP offload when initialising */ + printf("%s mode not supported\n", "DHCP_OFFLOAD"); + return MMIPAL_INVALID_ARGUMENT; + + case MMIPAL_STATIC: + result = ipaddr_aton(config->ip_addr, &ip_addr); + if (!result) { + return MMIPAL_INVALID_ARGUMENT; + } + result = ipaddr_aton(config->netmask, &netmask); + if (!result) { + return MMIPAL_INVALID_ARGUMENT; + } + result = ipaddr_aton(config->gateway_addr, &gateway); + if (!result) { + return MMIPAL_INVALID_ARGUMENT; + } + break; + + case MMIPAL_DHCP: + break; + } + + LOCK_TCPIP_CORE(); + + if (config->mode != MMIPAL_DHCP && data->ip4_mode == MMIPAL_DHCP) { + /* Stop DHCP if it was started earlier before setting static IP */ + dhcp_stop(netif); + } + + data->ip4_mode = config->mode; + + netif_set_addr(netif, ip_2_ip4(&ip_addr), ip_2_ip4(&netmask), ip_2_ip4(&gateway)); + if (data->ip4_mode == MMIPAL_DHCP) { + result = dhcp_start(netif); + LWIP_ASSERT("DHCP start error", result == ERR_OK); + } + + UNLOCK_TCPIP_CORE(); + return MMIPAL_SUCCESS; +} + +enum mmipal_status mmipal_get_ip_broadcast_addr(mmipal_ip_addr_t broadcast_addr) +{ + struct mmipal_data *data = mmipal_get_data(); + char *result; + + uint32_t ip_addr = ip_addr_get_ip4_u32(&data->lwip_mmnetif.ip_addr); + uint32_t netmask = ip_addr_get_ip4_u32(&data->lwip_mmnetif.netmask); + uint32_t broadcast_u32 = (ip_addr & netmask) | (0xffffffff & ~netmask); + ip_addr_t broadcast_ip_addr; + ip_addr_t *_broadcast_ip_addr = &broadcast_ip_addr; + ip_addr_set_ip4_u32(_broadcast_ip_addr, broadcast_u32); + + result = ipaddr_ntoa_r(&broadcast_ip_addr, broadcast_addr, MMIPAL_IPADDR_STR_MAXLEN); + LWIP_ASSERT("IP buf too short", result != NULL); + + return MMIPAL_SUCCESS; +} + +#else +enum mmipal_status mmipal_get_ip_config(struct mmipal_ip_config *config) +{ + MM_UNUSED(config); + LWIP_ASSERT("IPv4 not enabled", false); + return MMIPAL_NOT_SUPPORTED; +} + +enum mmipal_status mmipal_set_ip_config(const struct mmipal_ip_config *config) +{ + MM_UNUSED(config); + LWIP_ASSERT("IPv4 not enabled", false); + return MMIPAL_NOT_SUPPORTED; +} + +enum mmipal_status mmipal_get_ip_broadcast_addr(mmipal_ip_addr_t broadcast_addr) +{ + MM_UNUSED(broadcast_addr); + LWIP_ASSERT("IPv4 not enabled", false); + return MMIPAL_NOT_SUPPORTED; +} + +#endif + +#if LWIP_IPV6 + +enum mmipal_status mmipal_get_ip6_config(struct mmipal_ip6_config *config) +{ + struct mmipal_data *data = mmipal_get_data(); + unsigned ii; + struct netif *netif = &data->lwip_mmnetif; + + if (config == NULL) { + return MMIPAL_INVALID_ARGUMENT; + } + config->ip6_mode = data->ip6_mode; + for (ii = 0; ii < LWIP_IPV6_NUM_ADDRESSES; ii++) { + char *result; + const ip_addr_t *addr = &ip6_addr_any; + + if (ip6_addr_isvalid(netif_ip6_addr_state(netif, ii))) { + addr = &data->lwip_mmnetif.ip6_addr[ii]; + } + + result = ipaddr_ntoa_r(addr, config->ip6_addr[ii], sizeof(config->ip6_addr[ii])); + LWIP_ASSERT("IP buf too short", result != NULL); + } + return MMIPAL_SUCCESS; +} + +enum mmipal_status mmipal_set_ip6_config(const struct mmipal_ip6_config *config) +{ + struct mmipal_data *data = mmipal_get_data(); + struct netif *netif = &data->lwip_mmnetif; + err_t result; + unsigned ii; + ip_addr_t ip6_addr[LWIP_IPV6_NUM_ADDRESSES]; + + for (ii = 0; ii < LWIP_IPV6_NUM_ADDRESSES; ii++) { + int result = ipaddr_aton(config->ip6_addr[ii], &ip6_addr[ii]); + if (!result) { + return MMIPAL_INVALID_ARGUMENT; + } + } + + LOCK_TCPIP_CORE(); + + if (config->ip6_mode == MMIPAL_IP6_STATIC) { + if (data->ip6_mode != MMIPAL_IP6_STATIC) { +#if LWIP_IPV6_DHCP6 + dhcp6_disable(netif); +#endif + netif_set_ip6_autoconfig_enabled(netif, 0); + data->ip6_mode = MMIPAL_IP6_STATIC; + } + + if (!ip6_addr_islinklocal(ip_2_ip6(&(ip6_addr[0])))) { + printf("First address must be linklocal address (address start with fe80)\n"); + } + + for (ii = 0; ii < LWIP_IPV6_NUM_ADDRESSES; ii++) { + if (ip_addr_isany_val(ip6_addr[ii])) { + netif_ip6_addr_set(netif, ii, IP6_ADDR_ANY6); + netif_ip6_addr_set_state(netif, ii, IP6_ADDR_INVALID); + } else { + netif_ip6_addr_set(netif, ii, ip_2_ip6(&(ip6_addr[ii]))); + netif_ip6_addr_set_state(netif, ii, IP6_ADDR_TENTATIVE); + netif_ip6_addr_set_valid_life(netif, ii, IP6_ADDR_LIFE_STATIC); + } + } + } else { + if (data->ip6_mode == MMIPAL_IP6_STATIC) { + for (ii = 0; ii < LWIP_IPV6_NUM_ADDRESSES; ii++) { + netif_ip6_addr_set(netif, ii, IP6_ADDR_ANY6); + netif_ip6_addr_set_state(netif, ii, IP6_ADDR_INVALID); + } + } + netif_set_ip6_autoconfig_enabled(netif, 1); + netif_create_ip6_linklocal_address(netif, 1); + data->ip6_mode = MMIPAL_IP6_AUTOCONFIG; + } + + if (config->ip6_mode == MMIPAL_IP6_DHCP6_STATELESS) +#if LWIP_IPV6_DHCP6 + { + result = dhcp6_enable_stateless(netif); + LWIP_ASSERT("Stateless DHCP6 start error", result == ERR_OK); + data->ip6_mode = MMIPAL_IP6_DHCP6_STATELESS; + } else { + dhcp6_disable(netif); + } +#else + { + printf("LWIP_IPV6_DHCP6 is not enabled\n"); + } +#endif + + UNLOCK_TCPIP_CORE(); + return MMIPAL_SUCCESS; +} + +#else +enum mmipal_status mmipal_get_ip6_config(struct mmipal_ip6_config *config) +{ + MM_UNUSED(config); + LWIP_ASSERT("IPv6 not enabled", false); + return MMIPAL_NOT_SUPPORTED; +} + +enum mmipal_status mmipal_set_ip6_config(const struct mmipal_ip6_config *config) +{ + MM_UNUSED(config); + LWIP_ASSERT("IPv6 not enabled", false); + return MMIPAL_NOT_SUPPORTED; +} + +#endif + +static bool mmipal_link_status_check(struct netif *netif) +{ + bool ip4_addr_check = true; + +#if LWIP_IPV4 + ip4_addr_check = !ip_addr_isany(&(netif->ip_addr)); +#endif + + return ip4_addr_check && netif_is_link_up(netif); +} + +/** Handler for @c netif status callbacks from LWIP. */ +static void netif_status_callback(struct netif *netif) +{ + struct mmipal_data *data = mmipal_get_data(); + enum mmipal_link_state new_link_state = MMIPAL_LINK_DOWN; + +#if LWIP_IPV4 + if (data->ip4_mode == MMIPAL_DHCP_OFFLOAD) { + /* Initialize DHCP offload on link up */ + if (mmwlan_enable_dhcp_offload(mmipal_dhcp_lease_updated, NULL) != MMWLAN_SUCCESS) { + printf("Failed to enable DHCP offload!\n"); + } + + if (!data->dhcp_offload_init_complete) { + /* This just prevents a spurious 'Link Up' message on very first call */ + return; + } + } +#endif + + if (mmipal_link_status_check(netif)) { + new_link_state = MMIPAL_LINK_UP; + } + + if (data->ip_link_state != new_link_state) { + data->ip_link_state = new_link_state; + + if (data->link_status_callback || data->ext_link_status_callback) { + struct mmipal_link_status link_status; + memset(&link_status, 0, sizeof(link_status)); + link_status.link_state = data->ip_link_state; + +#if LWIP_IPV4 + char *result = ipaddr_ntoa_r(&netif->ip_addr, link_status.ip_addr, sizeof(link_status.ip_addr)); + LWIP_ASSERT("IP buf too short", result != NULL); + + result = ipaddr_ntoa_r(&netif->netmask, link_status.netmask, sizeof(link_status.netmask)); + LWIP_ASSERT("IP buf too short", result != NULL); + + result = ipaddr_ntoa_r(&netif->gw, link_status.gateway, sizeof(link_status.gateway)); + LWIP_ASSERT("IP buf too short", result != NULL); + + if (data->ip_link_state == MMIPAL_LINK_UP) { + /* Check if ARP response offload feature is enabled */ + if (data->offload_arp_response) { + mmwlan_enable_arp_response_offload(ip4_addr_get_u32(netif_ip4_addr(netif))); + } + + /* Check if ARP refresh offload feature is enabled */ + if (data->offload_arp_refresh_s > 0) { + mmwlan_enable_arp_refresh_offload(data->offload_arp_refresh_s, ip4_addr_get_u32(netif_ip4_gw(netif)), true); + } + } +#endif + if (data->link_status_callback) { + data->link_status_callback(&link_status); + } + if (data->ext_link_status_callback) { + data->ext_link_status_callback(&link_status, data->ext_link_status_callback_arg); + } + } + } +} + +void mmipal_set_link_status_callback(mmipal_link_status_cb_fn_t fn) +{ + struct mmipal_data *data = mmipal_get_data(); + data->link_status_callback = fn; +} + +void mmipal_set_ext_link_status_callback(mmipal_ext_link_status_cb_fn_t fn, void *arg) +{ + struct mmipal_data *data = mmipal_get_data(); + data->ext_link_status_callback = fn; + data->ext_link_status_callback_arg = arg; +} + +static volatile bool tcpip_init_done = false; + +struct lwip_init_args { + enum mmipal_addr_mode mode; + enum mmipal_ip6_addr_mode ip6_mode; + ip_addr_t ip_addr; + ip_addr_t netmask; + ip_addr_t gateway_addr; + ip_addr_t ip6_addr; +}; + +static void tcpip_init_done_handler(void *arg) +{ + struct mmipal_data *data = mmipal_get_data(); + struct netif *netif = &data->lwip_mmnetif; + struct lwip_init_args *args = (struct lwip_init_args *)arg; + + netif_add_noaddr(netif, NULL, mmnetif_init, tcpip_input); + netif_set_default(netif); + netif_set_up(netif); + +#if LWIP_IPV4 + err_t result; + data->ip4_mode = args->mode; + if (args->mode == MMIPAL_DHCP) { + result = dhcp_start(netif); + LWIP_ASSERT("DHCP start error", result == ERR_OK); + } else if (args->mode == MMIPAL_STATIC) { + netif_set_addr(netif, ip_2_ip4(&(args->ip_addr)), ip_2_ip4(&(args->netmask)), ip_2_ip4(&(args->gateway_addr))); + } +#endif + + netif_set_link_callback(netif, netif_status_callback); + netif_set_status_callback(netif, netif_status_callback); + +#if LWIP_IPV6 + err_t result6; + data->ip6_mode = args->ip6_mode; + if (args->ip6_mode == MMIPAL_IP6_STATIC) { + netif_ip6_addr_set(netif, 0, ip_2_ip6(&(args->ip6_addr))); + netif_ip6_addr_set_state(netif, 0, IP6_ADDR_TENTATIVE); + } else if (data->ip6_mode == MMIPAL_IP6_AUTOCONFIG) { + netif_set_ip6_autoconfig_enabled(netif, 1); + netif_create_ip6_linklocal_address(netif, 1); + } else if (data->ip6_mode == MMIPAL_IP6_DHCP6_STATELESS) +#if LWIP_IPV6_DHCP6 + { + result6 = dhcp6_enable_stateless(netif); + LWIP_ASSERT("Stateless DHCP6 start error", result6 == ERR_OK); + } +#else + { + printf("LWIP_IPV6_DHCP6 is not enabled\n"); + } +#endif +#endif + + mmosal_free(args); + + tcpip_init_done = true; +} + +enum mmipal_status mmipal_init(const struct mmipal_init_args *args) +{ + struct mmipal_data *data = mmipal_get_data(); + enum mmipal_status status = MMIPAL_INVALID_ARGUMENT; + int result; + + struct lwip_init_args *lwip_args = (struct lwip_init_args *)mmosal_malloc(sizeof(*lwip_args)); + if (lwip_args == NULL) { + printf("malloc failure\n"); + return MMIPAL_NO_MEM; + } + + memset(lwip_args, 0, sizeof(*lwip_args)); + lwip_args->mode = args->mode; + lwip_args->ip6_mode = args->ip6_mode; + + data->link_status_callback = NULL; + + data->offload_arp_response = args->offload_arp_response; + data->offload_arp_refresh_s = args->offload_arp_refresh_s; + + /* Validate arguments */ +#if LWIP_IPV4 + switch (args->mode) { + case MMIPAL_DISABLED: + printf("%s mode not supported\n", "DISABLED"); + goto exit; + + case MMIPAL_DHCP_OFFLOAD: + case MMIPAL_STATIC: + result = ipaddr_aton(args->ip_addr, &lwip_args->ip_addr); + if (!result) { + goto exit; + } + result = ipaddr_aton(args->netmask, &lwip_args->netmask); + if (!result) { + goto exit; + } + result = ipaddr_aton(args->gateway_addr, &lwip_args->gateway_addr); + if (!result) { + goto exit; + } + + if (ip_addr_isany_val(lwip_args->ip_addr)) { + printf("IP address not specified\n"); + goto exit; + } + break; + + case MMIPAL_DHCP: + if (LWIP_DHCP == 0) { + printf("DHCP not compiled in\n"); + goto exit; + } + break; + + case MMIPAL_AUTOIP: + printf("%s mode not supported\n", "AutoIP"); + break; + } +#endif + +#if LWIP_IPV6 + switch (args->ip6_mode) { + case MMIPAL_IP6_DISABLED: + break; + + case MMIPAL_IP6_STATIC: + result = ipaddr_aton(args->ip6_addr, &lwip_args->ip6_addr); + if (!result) { + goto exit; + } + + if (ip_addr_isany_val(lwip_args->ip6_addr)) { + printf("IP address not specified\n"); + goto exit; + } + break; + + case MMIPAL_IP6_AUTOCONFIG: + if (LWIP_IPV6_AUTOCONFIG == 0) { + printf("AUTOCONFIG not compiled in\n"); + goto exit; + } + break; + + case MMIPAL_IP6_DHCP6_STATELESS: + if (LWIP_IPV6_DHCP6_STATELESS == 0) { + printf("DHCP6_STATELESS not compiled in\n"); + goto exit; + } + break; + } +#endif + + tcpip_init(tcpip_init_done_handler, lwip_args); + + /* Block until initialisation is complete */ + while (!tcpip_init_done) { + mmosal_task_sleep(10); + } + + return MMIPAL_SUCCESS; + +exit: + mmosal_free(lwip_args); + return status; +} + +void mmipal_get_link_packet_counts(uint32_t *tx_packets, uint32_t *rx_packets) +{ +#if LWIP_STATS + *tx_packets = lwip_stats.link.xmit; + *rx_packets = lwip_stats.link.recv; +#else + *tx_packets = 0; + *rx_packets = 0; +#endif +} + +void mmipal_set_tx_qos_tid(uint8_t tid) +{ + struct mmipal_data *data = mmipal_get_data(); + bool ok = tcpip_init_done; + MMOSAL_ASSERT(ok); + mmnetif_set_tx_qos_tid(&data->lwip_mmnetif, tid); +} + +enum mmipal_link_state mmipal_get_link_state(void) +{ + struct mmipal_data *data = mmipal_get_data(); + return data->ip_link_state; +} + +static enum mmipal_status mmipal_get_local_addr_(ip_addr_t *local_addr, const ip_addr_t *dest_addr) +{ + struct mmipal_data *data = mmipal_get_data(); + struct netif *netif = &data->lwip_mmnetif; +#if LWIP_IPV6 + if (IP_IS_V6(dest_addr)) { + const ip_addr_t *src_addr = ip6_select_source_address(netif, ip_2_ip6(dest_addr)); + if (src_addr == NULL) { + return MMIPAL_NO_LINK; + } + ip_addr_copy(*local_addr, *src_addr); + return MMIPAL_SUCCESS; + } else { + MM_UNUSED(dest_addr); + } +#endif + +#if LWIP_IPV4 + if (IP_IS_V4(dest_addr)) { + ip_addr_copy(*local_addr, netif->ip_addr); + return MMIPAL_SUCCESS; + } else { + MM_UNUSED(dest_addr); + } +#endif + +#if !LWIP_IPV4 && !LWIP_IPV6 + MM_UNUSED(local_addr); + MM_UNUSED(dest_addr); +#endif + + return MMIPAL_INVALID_ARGUMENT; +} + +enum mmipal_status mmipal_get_local_addr(mmipal_ip_addr_t local_addr, const mmipal_ip_addr_t dest_addr) +{ + ip_addr_t lwip_dest_addr; + ip_addr_t lwip_local_addr; + int ok; + enum mmipal_status status; + + if (dest_addr == NULL) { + return MMIPAL_INVALID_ARGUMENT; + } + + ok = ipaddr_aton(dest_addr, &lwip_dest_addr); + if (!ok) { + return MMIPAL_INVALID_ARGUMENT; + } + + status = mmipal_get_local_addr_(&lwip_local_addr, &lwip_dest_addr); + if (status != 0) { + return status; + } + + if (ipaddr_ntoa_r(&lwip_local_addr, local_addr, MMIPAL_IPADDR_STR_MAXLEN) == NULL) { + return MMIPAL_NO_MEM; + } else { + return MMIPAL_SUCCESS; + } +} + +enum mmipal_status mmipal_set_dns_server(uint8_t index, const mmipal_ip_addr_t addr) +{ + ip_addr_t dns_addr; + int ok; + + if (index >= DNS_MAX_SERVERS) { + return MMIPAL_INVALID_ARGUMENT; + } + + ok = ipaddr_aton(addr, &dns_addr); + if (!ok) { + return MMIPAL_INVALID_ARGUMENT; + } + + dns_setserver(index, &dns_addr); + return MMIPAL_SUCCESS; +} + +enum mmipal_status mmipal_get_dns_server(uint8_t index, mmipal_ip_addr_t addr) +{ + const ip_addr_t *dns_addr; + + if (index >= DNS_MAX_SERVERS) { + return MMIPAL_INVALID_ARGUMENT; + } + + dns_addr = dns_getserver(index); + +#if LWIP_IPV4 + /* dns_getserver() returns ip_addr_any if no address configured. */ + if (!memcmp(dns_addr, &ip_addr_any, sizeof(*dns_addr))) { + addr[0] = '\0'; + return MMIPAL_SUCCESS; + } +#endif + + if (ipaddr_ntoa_r(dns_addr, addr, MMIPAL_IPADDR_STR_MAXLEN) == NULL) { + return MMIPAL_NO_MEM; + } else { + return MMIPAL_SUCCESS; + } +} + +#endif diff --git a/lib/MorseWlan/src/mmnetif.c b/lib/MorseWlan/src/mmnetif.c new file mode 100644 index 000000000..f8298fe0f --- /dev/null +++ b/lib/MorseWlan/src/mmnetif.c @@ -0,0 +1,218 @@ +#ifdef USE_MM_IOT_ESP32 +/* + * Copyright 2021-2023 Morse Micro + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "mmnetif.h" +#include "mmosal.h" +#include "mmwlan.h" + +#include "lwip/etharp.h" +#include "lwip/ethip6.h" +#include "lwip/tcpip.h" +#if LWIP_SNMP +#include "lwip/snmp.h" +#endif + +struct netif_state { + volatile uint8_t tx_qos_tid; +}; + +static struct netif_state *get_netif_state(struct netif *netif) +{ + MMOSAL_ASSERT(netif->state != NULL); + return (struct netif_state *)netif->state; +} + +/** pbuf wrapper around an mmpkt. */ +struct mmpkt_pbuf_wrapper { + struct pbuf_custom p; + struct mmpkt *pkt; + struct mmpktview *pktview; +}; + +LWIP_MEMPOOL_DECLARE(RX_POOL, MMPKTMEM_RX_POOL_N_BLOCKS, sizeof(struct mmpkt_pbuf_wrapper), "mmpkt_rx"); + +static void mmpkt_pbuf_wrapper_free(struct pbuf *p) +{ + struct mmpkt_pbuf_wrapper *pbuf = (struct mmpkt_pbuf_wrapper *)p; + if (p == NULL) { + return; + } + mmpkt_close(&pbuf->pktview); + mmpkt_release(pbuf->pkt); + LWIP_MEMPOOL_FREE(RX_POOL, pbuf); +} + +static void mmnetif_rx(struct mmpkt *rxpkt, void *arg) +{ + struct netif *netif = (struct netif *)arg; + LWIP_ASSERT("arg NULL", netif != NULL); + + LWIP_DEBUGF(NETIF_DEBUG, ("mmnetif: packet received\n")); + + struct mmpkt_pbuf_wrapper *pbuf = (struct mmpkt_pbuf_wrapper *)LWIP_MEMPOOL_ALLOC(RX_POOL); + if (pbuf != NULL) { + struct pbuf *p; + + pbuf->p.custom_free_function = mmpkt_pbuf_wrapper_free; + pbuf->pkt = rxpkt; + pbuf->pktview = mmpkt_open(pbuf->pkt); + + p = pbuf_alloced_custom(PBUF_RAW, mmpkt_get_data_length(pbuf->pktview), PBUF_REF, &pbuf->p, + mmpkt_get_data_start(pbuf->pktview), mmpkt_get_data_length(pbuf->pktview)); + int ret = tcpip_input(p, netif); + if (ret == ERR_OK) { + LINK_STATS_INC(link.recv); + } else { + LWIP_DEBUGF(NETIF_DEBUG, ("mmnetif: input error\n")); + pbuf_free(p); + LINK_STATS_INC(link.memerr); + LINK_STATS_INC(link.drop); + } + } else { + LWIP_DEBUGF(NETIF_DEBUG | LWIP_DBG_LEVEL_SERIOUS, ("mmnetif: alloc error\n")); + LINK_STATS_INC(link.memerr); + mmpkt_release(rxpkt); + } +} + +static void mmnetif_link_state(enum mmwlan_link_state link_state, void *arg) +{ + struct netif *netif = (struct netif *)arg; + LWIP_ASSERT("arg NULL", netif != NULL); + + LOCK_TCPIP_CORE(); + if (link_state == MMWLAN_LINK_DOWN) { + LWIP_DEBUGF(NETIF_DEBUG | LWIP_DBG_LEVEL_ALL, ("mmnetif: link down\n")); + /* Note: we cast netif_set_link_down to tcpip_callback_fn since the tcpip_callback_fn + * has a "void *" parameter and netif_set_link_down has "struct netif *". */ + err_t err = tcpip_callback_with_block((tcpip_callback_fn)netif_set_link_down, netif, 0); + LWIP_ASSERT("sched callback failed", err == ERR_OK); + } else { + LWIP_DEBUGF(NETIF_DEBUG | LWIP_DBG_LEVEL_ALL, ("mmnetif: link up\n")); + /* Note: we cast netif_set_link_down to tcpip_callback_fn since the tcpip_callback_fn + * has a "void *" parameter and netif_set_link_down has "struct netif *". */ + err_t err = tcpip_callback_with_block((tcpip_callback_fn)netif_set_link_up, netif, 0); + LWIP_ASSERT("sched callback failed", err == ERR_OK); + } + UNLOCK_TCPIP_CORE(); +} + +static err_t mmnetif_tx(struct netif *netif, struct pbuf *p) +{ + struct mmpkt *pkt; + struct mmpktview *pktview; + enum mmwlan_status status; + struct pbuf *walk; + struct mmwlan_tx_metadata metadata = { + .tid = get_netif_state(netif)->tx_qos_tid, + }; + + status = mmwlan_tx_wait_until_ready(1000); + if (status != MMWLAN_SUCCESS) { + LWIP_DEBUGF(NETIF_DEBUG | LWIP_DBG_LEVEL_SERIOUS, ("mmnetif: transmit blocked\n")); + LINK_STATS_INC(link.drop); + return ERR_BUF; + } + + pkt = mmwlan_alloc_mmpkt_for_tx(p->tot_len, metadata.tid); + if (pkt == NULL) { + LWIP_DEBUGF(NETIF_DEBUG | LWIP_DBG_LEVEL_SERIOUS, ("mmnetif: allocation failure\n")); + LINK_STATS_INC(link.memerr); + return ERR_MEM; + } + pktview = mmpkt_open(pkt); + for (walk = p; walk != NULL; walk = walk->next) { + mmpkt_append_data(pktview, (const uint8_t *)walk->payload, walk->len); + } + mmpkt_close(&pktview); + + status = mmwlan_tx_pkt(pkt, &metadata); + if (status != MMWLAN_SUCCESS) { + LWIP_DEBUGF(NETIF_DEBUG | LWIP_DBG_LEVEL_SERIOUS, ("mmnetif: error sending packet\n")); + LINK_STATS_INC(link.drop); + return ERR_BUF; + } + + LWIP_DEBUGF(NETIF_DEBUG | LWIP_DBG_LEVEL_ALL, ("mmnetif: packet sent\n")); + LINK_STATS_INC(link.xmit); + return ERR_OK; +} + +err_t mmnetif_init(struct netif *netif) +{ + static bool initialised = false; + if (initialised) { + return ERR_IF; + } + + LWIP_MEMPOOL_INIT(RX_POOL); + + LWIP_DEBUGF(NETIF_DEBUG | LWIP_DBG_LEVEL_ALL, ("mmnetif: initialising mmnetif\n")); + +#if LWIP_SNMP + NETIF_INIT_SNMP(netif, snmp_ifType_ethernet_csmacd, 1000000UL); +#endif + + enum mmwlan_status status; + + /* Boot the transceiver so that we can read the MAC address. */ + struct mmwlan_boot_args boot_args = MMWLAN_BOOT_ARGS_INIT; + status = mmwlan_boot(&boot_args); + if (status != MMWLAN_SUCCESS) { + LWIP_DEBUGF(NETIF_DEBUG | LWIP_DBG_LEVEL_SEVERE, ("mmwlan_boot failed with code %d\n", status)); + } + MMOSAL_ASSERT(status == MMWLAN_SUCCESS); + + /* Set MAC hardware address */ + netif->hwaddr_len = MMWLAN_MAC_ADDR_LEN; + status = mmwlan_get_mac_addr(netif->hwaddr); + MMOSAL_ASSERT(status == MMWLAN_SUCCESS); + + netif->mtu = 1500; +#if LWIP_IPV4 && !LWIP_IPV6 + netif->flags |= NETIF_FLAG_BROADCAST | NETIF_FLAG_ETHARP | NETIF_FLAG_IGMP; +#else + netif->flags |= NETIF_FLAG_BROADCAST | NETIF_FLAG_ETHARP | NETIF_FLAG_IGMP | NETIF_FLAG_MLD6; +#endif + + netif->state = NULL; + netif->name[0] = 'M'; + netif->name[1] = 'M'; + +#if LWIP_IPV4 + netif->output = etharp_output; +#endif +#if LWIP_IPV6 + netif->output_ip6 = ethip6_output; +#endif + netif->linkoutput = mmnetif_tx; + + struct netif_state *state = (struct netif_state *)mmosal_malloc(sizeof(*state)); + MMOSAL_ASSERT(state != NULL); + state->tx_qos_tid = MMWLAN_TX_DEFAULT_QOS_TID; + netif->state = state; + + status = mmwlan_register_rx_pkt_cb(mmnetif_rx, netif); + MMOSAL_ASSERT(status == MMWLAN_SUCCESS); + status = mmwlan_register_link_state_cb(mmnetif_link_state, netif); + MMOSAL_ASSERT(status == MMWLAN_SUCCESS); + + printf("Morse LwIP interface initialised. MAC address %02x:%02x:%02x:%02x:%02x:%02x\n", netif->hwaddr[0], netif->hwaddr[1], + netif->hwaddr[2], netif->hwaddr[3], netif->hwaddr[4], netif->hwaddr[5]); + + initialised = true; + + return ERR_OK; +} + +void mmnetif_set_tx_qos_tid(struct netif *netif, uint8_t tid) +{ + MMOSAL_ASSERT(tid <= MMWLAN_MAX_QOS_TID); + get_netif_state(netif)->tx_qos_tid = tid; +} + +#endif diff --git a/lib/MorseWlan/src/mmnetif.h b/lib/MorseWlan/src/mmnetif.h new file mode 100644 index 000000000..eccd2dd47 --- /dev/null +++ b/lib/MorseWlan/src/mmnetif.h @@ -0,0 +1,29 @@ +/* + * Copyright 2021-2023 Morse Micro + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include "lwip/err.h" +#include "lwip/netif.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** Initializer for the Morse Micro network interface */ +err_t mmnetif_init(struct netif *netif); + +/** + * Configure the QoS TID for the @c netif. QoS data will be sent using this TID. + * + * @param netif The @c netif to configure. + * @param tid The TID value to set. + */ +void mmnetif_set_tx_qos_tid(struct netif *netif, uint8_t tid); + +#ifdef __cplusplus +} +#endif diff --git a/lib/MorseWlan/src/mmosal_shim_freertos_esp32.c b/lib/MorseWlan/src/mmosal_shim_freertos_esp32.c new file mode 100644 index 000000000..a0ac717d7 --- /dev/null +++ b/lib/MorseWlan/src/mmosal_shim_freertos_esp32.c @@ -0,0 +1,563 @@ +#ifdef USE_MM_IOT_ESP32 +/* + * Copyright 2021-2023 Morse Micro + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "esp_debug_helpers.h" +#include "esp_private/startup_internal.h" +#include "freertos/FreeRTOS.h" +#include "freertos/queue.h" +#include "freertos/semphr.h" +#include "freertos/task.h" +#include "freertos/timers.h" +#include "rom/ets_sys.h" + +#include "mmhal.h" +#include "mmosal.h" + +/* --------------------------------------------------------------------------------------------- */ + +/** Maximum number of failure records to store (must be a power of 2). */ +#define MAX_FAILURE_RECORDS 4 + +/** Fast implementation of _x % _m where _m is a power of 2. */ +#define FAST_MOD(_x, _m) ((_x) & ((_m)-1)) + +/** Duration to delay before resetting the device on assert. */ +#define DELAY_BEFORE_RESET_MS 1000 + +/** Data structure for assertion information to be preserved. */ +struct mmosal_preserved_failure_info { + /** Magic number, to check if the info is valid. */ + uint32_t magic; + + /** Number of failures recorded. */ + uint32_t failure_count; + + /** Number of most recently displayed failure. */ + uint32_t displayed_failure_count; + + /** Preserved information from the most recent failure(s). */ + struct mmosal_failure_info info[MAX_FAILURE_RECORDS]; +}; + +/** Magic number to put in @c mmosal_assert_info.magic to indicate that the assertion info + * is valid. */ +#define ASSERT_INFO_MAGIC (0xabcd1234) + +/* Persistent assertion info. Linker script should put this into memory that is not + * zeroed on boot. Be careful to update linker script if renaming. */ +struct mmosal_preserved_failure_info preserved_failure_info __attribute__((section(".noinit"))); + +void mmosal_log_failure_info(const struct mmosal_failure_info *info) +{ + uint32_t record_num; + + if (preserved_failure_info.magic != ASSERT_INFO_MAGIC) { + preserved_failure_info.failure_count = 0; + preserved_failure_info.displayed_failure_count = 0; + } + + preserved_failure_info.magic = ASSERT_INFO_MAGIC; + record_num = FAST_MOD(preserved_failure_info.failure_count, MAX_FAILURE_RECORDS); + preserved_failure_info.failure_count++; + memcpy(&preserved_failure_info.info[record_num], info, sizeof(*info)); +} + +static void mmosal_dump_failure_info(void) +{ + unsigned first_failure_num = preserved_failure_info.displayed_failure_count; + unsigned new_failure_count = preserved_failure_info.failure_count - preserved_failure_info.displayed_failure_count; + unsigned failure_offset; + + if (new_failure_count >= MAX_FAILURE_RECORDS) { + first_failure_num = FAST_MOD(preserved_failure_info.failure_count, MAX_FAILURE_RECORDS); + new_failure_count = MAX_FAILURE_RECORDS; + } + + for (failure_offset = 0; failure_offset < new_failure_count; failure_offset++) { + unsigned ii; + unsigned idx = FAST_MOD(first_failure_num + failure_offset, MAX_FAILURE_RECORDS); + struct mmosal_failure_info *info = &preserved_failure_info.info[idx]; + + ets_printf("Failure %u logged at pc 0x%08lx, lr 0x%08lx, line %ld in %08lx\n", first_failure_num + failure_offset, + info->pc, info->lr, info->line, info->fileid); + + for (ii = 0; ii < sizeof(info->platform_info) / sizeof(info->platform_info[0]); ii++) { + ets_printf(" 0x%08lx\n", info->platform_info[ii]); + } + } + + preserved_failure_info.displayed_failure_count = preserved_failure_info.failure_count; +} + +void mmosal_impl_assert(void) +{ + ets_printf("MMOSAL Assert, CPU %d (current core) backtrace", xPortGetCoreID()); + (void)esp_backtrace_print(100); +#ifdef HALT_ON_ASSERT + if (preserved_failure_info.magic == ASSERT_INFO_MAGIC) { + mmosal_dump_failure_info(); + } + mmosal_disable_interrupts(); + mmhal_log_flush(); + MMPORT_BREAKPOINT(); +#else + mmosal_task_sleep(DELAY_BEFORE_RESET_MS); + mmhal_reset(); +#endif + while (1) { + } +} + +/* Function to be called as part of the secondary initialization. See [System + * Initialization](https://docs.espressif.com/projects/esp-idf/en/latest/esp32s3/api-guides/startup.html#system-initialization) + * for more information. */ +ESP_SYSTEM_INIT_FN(mmosal_dump_failure_info, BIT(0), 999) +{ + if (preserved_failure_info.magic == ASSERT_INFO_MAGIC) { + mmosal_dump_failure_info(); + } + return ESP_OK; +} + +/* --------------------------------------------------------------------------------------------- */ + +void *mmosal_malloc_(size_t size) +{ + return pvPortMalloc(size); +} + +#ifdef MMOSAL_TRACK_ALLOCATIONS +void *mmosal_malloc_dbg(size_t size, const char *name, unsigned line_number) +{ + return pvPortMalloc_dbg(size, name, line_number); +} +#else +void *mmosal_malloc_dbg(size_t size, const char *name, unsigned line_number) +{ + (void)name; + (void)line_number; + return pvPortMalloc(size); +} +#endif + +void mmosal_free(void *p) +{ + vPortFree(p); +} + +void *mmosal_realloc(void *ptr, size_t size) +{ + return realloc(ptr, size); +} + +void *mmosal_calloc(size_t nitems, size_t size) +{ + void *ptr = pvPortMalloc(nitems * size); + if (ptr == NULL) { + return NULL; + } + + memset(ptr, 0, nitems * size); + return ptr; +} + +/* --------------------------------------------------------------------------------------------- */ + +struct mmosal_task_arg { + mmosal_task_fn_t task_fn; + void *task_fn_arg; +}; + +void mmosal_task_main(void *arg) +{ + struct mmosal_task_arg task_arg = *(struct mmosal_task_arg *)arg; + mmosal_free(arg); + task_arg.task_fn(task_arg.task_fn_arg); + mmosal_task_delete(NULL); +} + +struct mmosal_task *mmosal_task_create(mmosal_task_fn_t task_fn, void *argument, enum mmosal_task_priority priority, + unsigned stack_size_u32, const char *name) +{ + TaskHandle_t handle; + UBaseType_t freertos_priority = tskIDLE_PRIORITY + priority; + + struct mmosal_task_arg *task_arg = (struct mmosal_task_arg *)mmosal_malloc(sizeof(*task_arg)); + if (task_arg == NULL) { + return NULL; + } + task_arg->task_fn = task_fn; + task_arg->task_fn_arg = argument; + + BaseType_t result = xTaskCreate(mmosal_task_main, name, stack_size_u32 * 4, task_arg, freertos_priority, &handle); + if (result == pdFAIL) { + mmosal_free(task_arg); + return NULL; + } + + return (struct mmosal_task *)handle; +} + +void mmosal_task_delete(struct mmosal_task *task) +{ + vTaskDelete((TaskHandle_t)task); +} + +/* + * Warning: this function should not be used since eTaskGetState() is not a reliable + * means of testing whether a task has completed. + * + * This function will be removed in future. + */ +void mmosal_task_join(struct mmosal_task *task) +{ + while (eTaskGetState((TaskHandle_t)task) != eDeleted) { + mmosal_task_sleep(10); + } +} + +struct mmosal_task *mmosal_task_get_active(void) +{ + return (struct mmosal_task *)xTaskGetCurrentTaskHandle(); +} + +void mmosal_task_yield(void) +{ + taskYIELD(); +} + +void mmosal_task_sleep(uint32_t duration_ms) +{ + vTaskDelay(duration_ms / portTICK_PERIOD_MS); +} + +static portMUX_TYPE task_spinlock = portMUX_INITIALIZER_UNLOCKED; + +void mmosal_task_enter_critical(void) +{ + taskENTER_CRITICAL(&task_spinlock); +} + +void mmosal_task_exit_critical(void) +{ + taskEXIT_CRITICAL(&task_spinlock); +} + +void mmosal_disable_interrupts(void) +{ + taskDISABLE_INTERRUPTS(); +} + +void mmosal_enable_interrupts(void) +{ + taskENABLE_INTERRUPTS(); +} + +const char *mmosal_task_name(void) +{ + TaskHandle_t t = xTaskGetCurrentTaskHandle(); + return pcTaskGetName(t); +} + +bool mmosal_task_wait_for_notification(uint32_t timeout_ms) +{ + TickType_t wait = portMAX_DELAY; + if (timeout_ms < UINT32_MAX) { + wait = pdMS_TO_TICKS(timeout_ms); + } + uint32_t ret = ulTaskNotifyTake(pdTRUE, /* Act as binary semaphore */ + wait); + return (ret != 0); +} + +void mmosal_task_notify(struct mmosal_task *task) +{ + xTaskNotifyGive((TaskHandle_t)task); +} + +void mmosal_task_notify_from_isr(struct mmosal_task *task) +{ + BaseType_t higher_priority_task_woken = pdFALSE; + vTaskNotifyGiveFromISR((TaskHandle_t)task, &higher_priority_task_woken); + portYIELD_FROM_ISR(higher_priority_task_woken); +} + +/* --------------------------------------------------------------------------------------------- */ + +struct mmosal_mutex *mmosal_mutex_create(const char *name) +{ + struct mmosal_mutex *mutex = (struct mmosal_mutex *)xSemaphoreCreateMutex(); +#if (configUSE_TRACE_FACILITY == 1) && defined(ENABLE_TRACEALYZER) && ENABLE_TRACEALYZER + if (name != NULL) { + vTraceSetMutexName(mutex, name); + } +#else + (void)name; +#endif + return mutex; +} + +void mmosal_mutex_delete(struct mmosal_mutex *mutex) +{ + if (mutex != NULL) { + vQueueDelete((SemaphoreHandle_t)mutex); + } +} + +bool mmosal_mutex_get(struct mmosal_mutex *mutex, uint32_t timeout_ms) +{ + uint32_t timeout_ticks = portMAX_DELAY; + if (timeout_ms != UINT32_MAX) { + timeout_ticks = timeout_ms / portTICK_PERIOD_MS; + } + return (xSemaphoreTake((SemaphoreHandle_t)mutex, timeout_ticks) == pdPASS); +} + +bool mmosal_mutex_release(struct mmosal_mutex *mutex) +{ + return (xSemaphoreGive((SemaphoreHandle_t)mutex) == pdPASS); +} + +bool mmosal_mutex_is_held_by_active_task(struct mmosal_mutex *mutex) +{ + return xSemaphoreGetMutexHolder((SemaphoreHandle_t)mutex) == xTaskGetCurrentTaskHandle(); +} + +/* --------------------------------------------------------------------------------------------- */ + +struct mmosal_sem *mmosal_sem_create(unsigned max_count, unsigned initial_count, const char *name) +{ + struct mmosal_sem *sem = (struct mmosal_sem *)xSemaphoreCreateCounting(max_count, initial_count); +#if (configUSE_TRACE_FACILITY == 1) && defined(ENABLE_TRACEALYZER) && ENABLE_TRACEALYZER + if (name != NULL) { + vTraceSetSemaphoreName(sem, name); + } +#else + (void)name; +#endif + return sem; +} + +void mmosal_sem_delete(struct mmosal_sem *sem) +{ + vQueueDelete((SemaphoreHandle_t)sem); +} + +bool mmosal_sem_give(struct mmosal_sem *sem) +{ + return xSemaphoreGive((SemaphoreHandle_t)sem); +} + +bool mmosal_sem_give_from_isr(struct mmosal_sem *sem) +{ + BaseType_t task_woken = false; + BaseType_t ret = xSemaphoreGiveFromISR((SemaphoreHandle_t)sem, &task_woken); + if (ret == pdPASS) { + portYIELD_FROM_ISR(task_woken); + return true; + } else { + return false; + } +} + +bool mmosal_sem_wait(struct mmosal_sem *sem, uint32_t timeout_ms) +{ + uint32_t timeout_ticks = portMAX_DELAY; + if (timeout_ms != UINT32_MAX) { + timeout_ticks = timeout_ms / portTICK_PERIOD_MS; + } + return (xSemaphoreTake((SemaphoreHandle_t)sem, timeout_ticks) == pdPASS); +} + +uint32_t mmosal_sem_get_count(struct mmosal_sem *sem) +{ + return uxSemaphoreGetCount((SemaphoreHandle_t)sem); +} + +/* --------------------------------------------------------------------------------------------- */ + +struct mmosal_semb *mmosal_semb_create(const char *name) +{ + struct mmosal_semb *semb = (struct mmosal_semb *)xSemaphoreCreateBinary(); +#if (configUSE_TRACE_FACILITY == 1) && defined(ENABLE_TRACEALYZER) && ENABLE_TRACEALYZER + if (name != NULL) { + vTraceSetSemaphoreName(semb, name); + } +#else + (void)name; +#endif + return semb; +} + +void mmosal_semb_delete(struct mmosal_semb *semb) +{ + vQueueDelete((SemaphoreHandle_t)semb); +} + +bool mmosal_semb_give(struct mmosal_semb *semb) +{ + return (xSemaphoreGive((SemaphoreHandle_t)semb) == pdPASS); +} + +bool mmosal_semb_give_from_isr(struct mmosal_semb *semb) +{ + BaseType_t task_woken = pdFALSE; + BaseType_t ret = xSemaphoreGiveFromISR((SemaphoreHandle_t)semb, &task_woken); + if (ret == pdPASS) { + portYIELD_FROM_ISR(task_woken); + return true; + } else { + return false; + } +} + +bool mmosal_semb_wait(struct mmosal_semb *semb, uint32_t timeout_ms) +{ + uint32_t timeout_ticks = portMAX_DELAY; + if (timeout_ms != UINT32_MAX) { + timeout_ticks = timeout_ms / portTICK_PERIOD_MS; + } + return (xSemaphoreTake((SemaphoreHandle_t)semb, timeout_ticks) == pdPASS); +} + +/* --------------------------------------------------------------------------------------------- */ + +struct mmosal_queue *mmosal_queue_create(size_t num_items, size_t item_size, const char *name) +{ + struct mmosal_queue *queue = (struct mmosal_queue *)xQueueCreate(num_items, item_size); +#if (configUSE_TRACE_FACILITY == 1) && defined(ENABLE_TRACEALYZER) && ENABLE_TRACEALYZER + if (name != NULL) { + vTraceSetQueueName(queue, name); + } +#else + (void)name; +#endif + return queue; +} + +void mmosal_queue_delete(struct mmosal_queue *queue) +{ + vQueueDelete((SemaphoreHandle_t)queue); +} + +bool mmosal_queue_pop(struct mmosal_queue *queue, void *item, uint32_t timeout_ms) +{ + uint32_t timeout_ticks = portMAX_DELAY; + if (timeout_ms != UINT32_MAX) { + timeout_ticks = timeout_ms / portTICK_PERIOD_MS; + } + return (xQueueReceive((SemaphoreHandle_t)queue, item, timeout_ticks) == pdPASS); +} + +bool mmosal_queue_push(struct mmosal_queue *queue, const void *item, uint32_t timeout_ms) +{ + uint32_t timeout_ticks = portMAX_DELAY; + if (timeout_ms != UINT32_MAX) { + timeout_ticks = timeout_ms / portTICK_PERIOD_MS; + } + return (xQueueSendToBack((SemaphoreHandle_t)queue, item, timeout_ticks) == pdPASS); +} + +bool mmosal_queue_pop_from_isr(struct mmosal_queue *queue, void *item) +{ + BaseType_t task_woken = pdFALSE; + if (xQueueReceiveFromISR((SemaphoreHandle_t)queue, item, &task_woken) == pdTRUE) { + portYIELD_FROM_ISR(task_woken); + return true; + } else { + return false; + } +} + +bool mmosal_queue_push_from_isr(struct mmosal_queue *queue, const void *item) +{ + BaseType_t task_woken = pdFALSE; + if (xQueueSendToBackFromISR((SemaphoreHandle_t)queue, item, &task_woken) == pdTRUE) { + portYIELD_FROM_ISR(task_woken); + return true; + } else { + return false; + } +} + +/* --------------------------------------------------------------------------------------------- */ + +uint32_t mmosal_get_time_ms(void) +{ + return xTaskGetTickCount() * portTICK_PERIOD_MS; +} + +uint32_t mmosal_get_time_ticks(void) +{ + return xTaskGetTickCount(); +} + +uint32_t mmosal_ticks_per_second(void) +{ + return portTICK_PERIOD_MS * 1000; +} + +/* --------------------------------------------------------------------------------------------- */ + +struct mmosal_timer *mmosal_timer_create(const char *name, uint32_t timer_period, bool auto_reload, void *arg, + timer_callback_t callback) +{ + /* + * The software timer callback functions execute in the context of a task that is + * created automatically when the FreeRTOS scheduler is started. Therefore, it is essential that + * software timer callback functions never call FreeRTOS API functions that will result in the + * calling task entering the Blocked state. It is ok to call functions such as xQueueReceive(), but + * only if the function’s xTicksToWait parameter (which specifies the function’s block time) is set + * to 0. It is not ok to call functions such as vTaskDelay(), as calling vTaskDelay() will always + * place the calling task into the Blocked state. + */ + return (struct mmosal_timer *)xTimerCreate(name, pdMS_TO_TICKS(timer_period), (UBaseType_t)auto_reload, arg, + (TimerCallbackFunction_t)callback); +} + +void mmosal_timer_delete(struct mmosal_timer *timer) +{ + if (timer != NULL) { + BaseType_t ret = xTimerDelete((TimerHandle_t)timer, 0); + configASSERT(ret == pdPASS); + } +} + +bool mmosal_timer_start(struct mmosal_timer *timer) +{ + BaseType_t ret = xTimerStart((TimerHandle_t)timer, 0); + + return (ret == pdPASS); +} + +bool mmosal_timer_stop(struct mmosal_timer *timer) +{ + BaseType_t ret = xTimerStop((TimerHandle_t)timer, 0); + + return (ret == pdPASS); +} + +bool mmosal_timer_change_period(struct mmosal_timer *timer, uint32_t new_period) +{ + BaseType_t ret = xTimerChangePeriod((TimerHandle_t)timer, pdMS_TO_TICKS(new_period), 0); + + return (ret == pdPASS); +} + +void *mmosal_timer_get_arg(struct mmosal_timer *timer) +{ + return pvTimerGetTimerID((TimerHandle_t)timer); +} + +bool mmosal_is_timer_active(struct mmosal_timer *timer) +{ + BaseType_t ret = xTimerIsTimerActive((TimerHandle_t)timer); + + return (ret != pdFALSE); +} + +#endif /* USE_MM_IOT_ESP32 */ diff --git a/lib/MorseWlan/src/mmpktmem_heap.c b/lib/MorseWlan/src/mmpktmem_heap.c new file mode 100644 index 000000000..69c8c05ab --- /dev/null +++ b/lib/MorseWlan/src/mmpktmem_heap.c @@ -0,0 +1,245 @@ +#ifdef USE_MM_IOT_ESP32 +/* + * Copyright 2024 Morse Micro + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +#include "mmhal.h" +#include "mmosal.h" +#include "mmpkt.h" +#include "mmpkt_list.h" +#include "mmutils.h" + +/* MMPKTMEM_TX_POOL_N_BLOCKS and MMPKTMEM_RX_POOL_N_BLOCKS provide an upper bound on the number + * of packets we will allocate in the transmit and receive directions respectively. */ + +#ifndef MMPKTMEM_TX_POOL_N_BLOCKS +#error MMPKTMEM_TX_POOL_N_BLOCKS not defined +#endif + +#ifndef MMPKTMEM_RX_POOL_N_BLOCKS +#error MMPKTMEM_RX_POOL_N_BLOCKS not defined +#endif + +/* Packet pool for data/management frames configuration. */ +#define TX_DATA_POOL_UNPAUSE_THRESHOLD (MMPKTMEM_TX_POOL_N_BLOCKS - 2) +#define TX_DATA_POOL_PAUSE_THRESHOLD (MMPKTMEM_TX_POOL_N_BLOCKS - 1) + +/* Packet pool for commands configuration. */ +#define TX_COMMAND_POOL_BLOCK_SIZE (256) +#define TX_COMMAND_POOL_N_BLOCKS (2) + +#ifndef MMPKT_LOG +#define MMPKT_LOG(...) printf(__VA_ARGS__) +#endif + +struct pktmem_data { + /** Count of allocated tx packets (excluding command pool -- see below). */ + volatile atomic_int_least32_t tx_data_pool_allocated; + /** Boolean tracking whether the data path is currently paused. */ + volatile atomic_uint_fast8_t tx_data_pool_tx_paused; + /** Count of allocated rx packets. */ + volatile atomic_int_least32_t rx_pool_allocated; + + /** Command pool free (unallocated) packet list. */ + struct mmpkt_list tx_command_pool_free_list; + /** Statically allocated memory for the command pool. */ + uint8_t tx_command_pool[TX_COMMAND_POOL_BLOCK_SIZE * TX_COMMAND_POOL_N_BLOCKS]; + + /** Flow control callback function pointer. */ + mmhal_wlan_pktmem_tx_flow_control_cb_t tx_flow_control_cb; +}; + +static struct pktmem_data pktmem; + +void mmhal_wlan_pktmem_init(struct mmhal_wlan_pktmem_init_args *args) +{ + unsigned ii; + + memset(&pktmem, 0, sizeof(pktmem)); + + pktmem.tx_flow_control_cb = args->tx_flow_control_cb; + + /* Initialize the free (unallocated) packet list of the transmit command pool. */ + for (ii = 0; ii < TX_COMMAND_POOL_N_BLOCKS; ii++) { + size_t offset = TX_COMMAND_POOL_BLOCK_SIZE * ii; + mmpkt_list_append(&pktmem.tx_command_pool_free_list, (struct mmpkt *)(pktmem.tx_command_pool + offset)); + } +} + +void mmhal_wlan_pktmem_deinit(void) +{ + size_t ii; + + /* If there is still memory allocated, allow some time for other threads to clean up. */ + for (ii = 0; ii < 100; ii++) { + if ((pktmem.tx_command_pool_free_list.len | pktmem.tx_data_pool_allocated | pktmem.tx_data_pool_allocated) == 0) { + break; + } + mmosal_task_sleep(10); + } + + /* Check for memory leaks. */ + if (pktmem.tx_data_pool_allocated != 0) { + MMPKT_LOG("Potential memory leak: %d %s pool allocations at deinit\n", (int)pktmem.tx_data_pool_allocated, "data"); + } + + if (pktmem.tx_command_pool_free_list.len != TX_COMMAND_POOL_N_BLOCKS) { + MMPKT_LOG("Potential memory leak: %d %s pool allocations at deinit\n", + TX_COMMAND_POOL_N_BLOCKS - (int)pktmem.tx_command_pool_free_list.len, "command"); + } +} + +/* + * -------------------------------------------------------------------------------------- + * Command pool + * -------------------------------------------------------------------------------------- + */ + +static void tx_command_reserved_free(void *mmpkt) +{ + struct mmpkt *pkt = (struct mmpkt *)mmpkt; + MMOSAL_TASK_ENTER_CRITICAL(); + mmpkt_list_append(&pktmem.tx_command_pool_free_list, pkt); + MMOSAL_TASK_EXIT_CRITICAL(); +} + +static const struct mmpkt_ops tx_command_pool_ops = { + .free_mmpkt = tx_command_reserved_free, +}; + +static struct mmpkt *alloc_pkt_from_list(struct mmpkt_list *list, uint32_t pktbufsize, uint32_t space_at_start, + uint32_t space_at_end, uint32_t metadata_length) +{ + struct mmpkt *mmpkt_buf; + struct mmpkt *mmpkt; + + MMOSAL_TASK_ENTER_CRITICAL(); + mmpkt_buf = mmpkt_list_dequeue(list); + MMOSAL_TASK_EXIT_CRITICAL(); + + if (mmpkt_buf == NULL) { + return NULL; + } + + mmpkt = mmpkt_init_buf((uint8_t *)mmpkt_buf, pktbufsize, space_at_start, space_at_end, metadata_length, &tx_command_pool_ops); + if (mmpkt == NULL) { + /* Command was too big for the reserved buffer. Return the reserved buffer. */ + tx_command_reserved_free(mmpkt_buf); + } + + return mmpkt; +} + +static struct mmpkt *command_pool_alloc(uint32_t space_at_start, uint32_t space_at_end, uint32_t metadata_length) +{ + return alloc_pkt_from_list(&pktmem.tx_command_pool_free_list, TX_COMMAND_POOL_BLOCK_SIZE, space_at_start, space_at_end, + metadata_length); +} + +/* + * -------------------------------------------------------------------------------------- + * Data pool + * -------------------------------------------------------------------------------------- + */ + +static void tx_data_pool_pkt_free(void *mmpkt) +{ + atomic_int_least32_t old_value = atomic_fetch_sub(&pktmem.tx_data_pool_allocated, 1); + MMOSAL_ASSERT(old_value > 0); + mmosal_free(mmpkt); + + if (pktmem.tx_data_pool_allocated < TX_DATA_POOL_UNPAUSE_THRESHOLD) { + atomic_uint_fast8_t old_tx_paused = atomic_exchange(&pktmem.tx_data_pool_tx_paused, 0); + if (old_tx_paused) { + pktmem.tx_flow_control_cb(MMWLAN_TX_READY); + } + } +} + +static const struct mmpkt_ops tx_data_pool_pkt_ops = { + .free_mmpkt = tx_data_pool_pkt_free, +}; + +struct mmpkt *mmhal_wlan_alloc_mmpkt_for_tx(uint8_t pkt_class, uint32_t space_at_start, uint32_t space_at_end, + uint32_t metadata_length) +{ + atomic_int_least32_t old_value; + struct mmpkt *mmpkt; + + /* For command packets, try allocating from the command pool first. If that fails then + * we proceed to allocate from the data pool. */ + if (pkt_class == MMHAL_WLAN_PKT_COMMAND) { + mmpkt = command_pool_alloc(space_at_start, space_at_end, metadata_length); + if (mmpkt != NULL) { + return mmpkt; + } + } + + old_value = atomic_fetch_add(&pktmem.tx_data_pool_allocated, 1); + + if (old_value >= MMPKTMEM_TX_POOL_N_BLOCKS) { + /* Maximum allocations reached. Do not attempt to increase further. */ + atomic_fetch_sub(&pktmem.tx_data_pool_allocated, 1); + return NULL; + } + + mmpkt = mmpkt_alloc_on_heap(space_at_start, space_at_end, metadata_length); + if (mmpkt == NULL) { + atomic_fetch_sub(&pktmem.tx_data_pool_allocated, 1); + return NULL; + } + + mmpkt->ops = &tx_data_pool_pkt_ops; + + if (pktmem.tx_data_pool_allocated > TX_DATA_POOL_PAUSE_THRESHOLD) { + atomic_uint_fast8_t old_tx_paused = atomic_exchange(&pktmem.tx_data_pool_tx_paused, 1); + if (!old_tx_paused) { + pktmem.tx_flow_control_cb(MMWLAN_TX_PAUSED); + } + } + + return mmpkt; +} + +static void rx_pkt_free(void *mmpkt) +{ + if (mmpkt != NULL) { + atomic_fetch_sub(&pktmem.rx_pool_allocated, 1); + mmosal_free(mmpkt); + } +} + +static const struct mmpkt_ops mmpkt_rx_ops = {.free_mmpkt = rx_pkt_free}; + +struct mmpkt *mmhal_wlan_alloc_mmpkt_for_rx(uint32_t capacity, uint32_t metadata_length) +{ + atomic_int_least32_t old_value; + struct mmpkt *mmpkt; + + old_value = atomic_fetch_add(&pktmem.rx_pool_allocated, 1); + + if (old_value >= MMPKTMEM_RX_POOL_N_BLOCKS) { + /* Maximum allocations reached. Do not attempt to increase further. */ + atomic_fetch_sub(&pktmem.rx_pool_allocated, 1); + return NULL; + } + + /* For now we do not put an explicit limit on the of packets buffers on the RX path. */ + mmpkt = mmpkt_alloc_on_heap(0, capacity, metadata_length); + if (mmpkt == NULL) { + atomic_fetch_sub(&pktmem.rx_pool_allocated, 1); + return NULL; + } + + /* Override packet ops to use a custom free function that also decrements the + * allocation count. */ + mmpkt->ops = &mmpkt_rx_ops; + return mmpkt; +} + +#endif diff --git a/lib/MorseWlan/src/mmport.h b/lib/MorseWlan/src/mmport.h new file mode 100644 index 000000000..36efe7581 --- /dev/null +++ b/lib/MorseWlan/src/mmport.h @@ -0,0 +1,12 @@ +/* + * Copyright 2023 Morse Micro + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#define MMPORT_BREAKPOINT() while (1) +#define MMPORT_GET_LR() (__builtin_return_address(0)) +#define MMPORT_GET_PC(_a) ((_a) = 0) // TODO +#define MMPORT_MEM_SYNC() __sync_synchronize() \ No newline at end of file diff --git a/lib/MorseWlan/src/mmutils.h b/lib/MorseWlan/src/mmutils.h new file mode 100644 index 000000000..739aa262d --- /dev/null +++ b/lib/MorseWlan/src/mmutils.h @@ -0,0 +1,301 @@ +/* + * Copyright 2024 Morse Micro + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @defgroup MMUTILS Morse Micro Utilities + * + * Utility macros and functions to improve quality of life. + * + * @{ + */ + +#pragma once + +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Get the minimum of 2 values. + * + * Note that this macro is not ideal and should be used with caution. Caveats include: + * * The two parameters may be evaluated more than once, so should be constant values that + * do not have side effects. For example, do NOT do `MM_MIN(a++, b)`. + * * There are no explicit constraints on types, so be careful of comparing different integer + * types, etc. + * + * @param _x The first value to compare. + * @param _y The second value to compare. + * + * @returns the minimum of @p _x and @p _y. + */ +#define MM_MIN(_x, _y) (((_x) < (_y)) ? (_x) : (_y)) + +/** + * Get the maximum of 2 values. + * + * Note that this macro is not ideal and should be used with caution. Caveats include: + * * The two parameters may be evaluated more than once, so should be constant values that + * do not have side effects. For example, do NOT do `MM_MAX(a++, b)`. + * * There are no explicit constraints on types, so be careful of comparing different integer + * types, etc. + * + * @param _x The first value to compare. + * @param _y The second value to compare. + * + * @returns the maximum of @p _x and @p _y. + */ +#define MM_MAX(_x, _y) (((_x) > (_y)) ? (_x) : (_y)) + +/** + * Round @p x up to the next multiple of @p m (where @p m is a power of 2). + * + * @warning @p m must be a power of 2. + */ +#ifndef MM_FAST_ROUND_UP +#define MM_FAST_ROUND_UP(x, m) ((((x)-1) | ((m)-1)) + 1) +#endif + +/** Casts the given expression to void to avoid "unused" warnings from the compiler. */ +#define MM_UNUSED(_x) (void)(_x) + +/** Tells the compiler to pack the structure. */ +#ifndef MM_PACKED +#define MM_PACKED __attribute__((packed)) +#endif + +/** Used to declare a weak symbol. */ +#ifndef MM_WEAK +#define MM_WEAK __attribute__((weak)) +#endif + +#ifndef MM_STATIC_ASSERT +/** + * Assertion check that is evaluated at compile time. + * + * The constant expression, @p _expression, is evaluted at compile time. If zero then + * it triggers a compilation error and @p _message is displayed. If non-zero, no code + * is emitted. + * + * @param _expression Constant expression to evaluate. If zero then a compilation error + * is triggered. + * @param _message Message to display on error. + */ +#define MM_STATIC_ASSERT(_expression, _message) _Static_assert((_expression), _message) +#endif + +/** + * Return the number of elements in the given array. + * + * @param _a The array to get the element count for. Note that this must be an _array_ + * and not a pointer. Beware that array-type function arguments are + * actually treated as pointers by the compiler. Must not be NULL. + * + * @returns the count of elements in the given array. + */ +#define MM_ARRAY_COUNT(_a) (sizeof(_a) / sizeof((_a)[0])) + +/** + * Convert the least significant 4 bits of the given argument to a character representing their + * hexadecimal value. + * + * For example, for input 0xde this will return 'E', for 0x01 it will return '1'. + * + * @param nibble The input nibble (upper 4 bits will be discarded). + * + * @return The character that represents the hexadecimal value of the lower 4 bits of @p nibble. + * Values greater than 0x09 will be represented with upper case characters. + */ +static inline char mm_nibble_to_hex_char(uint8_t nibble) +{ + nibble &= 0x0f; + if (nibble < 0x0a) { + return '0' + nibble; + } else { + return 'A' + nibble - 0x0a; + } +} + +/** + * @defgroup MMUTILS_WLAN WLAN Utilities + * + * Utility macros and functions relating to WLAN. + * + * @{ + */ + +/** Enumeration of Authentication Key Management (AKM) Suite OUIs as BE32 integers. */ +enum mm_akm_suite_oui { + /** Open (no security) */ + MM_AKM_SUITE_NONE = 0, + /** Pre-shared key (WFA OUI) */ + MM_AKM_SUITE_PSK = 0x506f9a02, + /** Simultaneous Authentication of Equals (SAE) */ + MM_AKM_SUITE_SAE = 0x000fac08, + /** OWE */ + MM_AKM_SUITE_OWE = 0x000fac12, + /** Another suite not in this enum */ + MM_AKM_SUITE_OTHER = 1, +}; + +/** Enumeration of Cipher Suite OUIs as BE32 integers. */ +enum mm_cipher_suite_oui { + /** Open (no security) */ + MM_CIPHER_SUITE_AES_CCM = 0x000fac04, + /** Another cipher suite not in this enum */ + MM_CIPHER_SUITE_OTHER = 1, +}; + +/** Maximum number of pairwise cipher suites our parser will process. */ +#ifndef MM_RSN_INFORMATION_MAX_PAIRWISE_CIPHER_SUITES +#define MM_RSN_INFORMATION_MAX_PAIRWISE_CIPHER_SUITES (2) +#endif + +/** Maximum number of AKM suites our parser will process. */ +#ifndef MM_RSN_INFORMATION_MAX_AKM_SUITES +#define MM_RSN_INFORMATION_MAX_AKM_SUITES (2) +#endif + +/** Tag number of the RSN information element, in which we can find security details of the AP. */ +#define MM_RSN_INFORMATION_IE_TYPE (48) +/** Tag number of the Vendor Specific information element. */ +#define MM_VENDOR_SPECIFIC_IE_TYPE (221) + +/** Explicitly defined errno values to obviate the need to include errno.h. MM prefix to + * avoid namespace collision in case errno.h gets included. */ +enum mm_errno { + MM_ENOMEM = 12, + MM_EFAULT = 14, + MM_ENODEV = 19, + MM_EINVAL = 22, + MM_ETIMEDOUT = 110, +}; + +/** + * Data structure to represent information extracted from an RSN information element. + * + * All integers in host order. + */ +struct mm_rsn_information { + /** The group cipher suite OUI. */ + uint32_t group_cipher_suite; + /** Pairwise cipher suite OUIs. Count given by @c num_pairwise_cipher_suites. */ + uint32_t pairwise_cipher_suites[MM_RSN_INFORMATION_MAX_PAIRWISE_CIPHER_SUITES]; + /** AKM suite OUIs. Count given by @c num_akm_suites. */ + uint32_t akm_suites[MM_RSN_INFORMATION_MAX_AKM_SUITES]; + /** Number of pairwise cipher suites in @c pairwise_cipher_suites. */ + uint16_t num_pairwise_cipher_suites; + /** Number of AKM suites in @c akm_suites. */ + uint16_t num_akm_suites; + /** Version number of the RSN IE. */ + uint16_t version; + /** RSN Capabilities field of the RSN IE (in host order). */ + uint16_t rsn_capabilities; +}; + +/** + * Get the name of the given AKM Suite as a string. + * + * @param akm_suite_oui The OUI of the AKM suite as a big endian integer. + * + * @returns the string representation. + */ +const char *mm_akm_suite_to_string(uint32_t akm_suite_oui); + +/** + * Search a list of Information Elements (IEs) from the given starting offset and find the first + * instance of matching the given type. + * + * @warning A @p search_offset that is not aligned to the start of an IE header will result in + * undefined behaviour. + * + * @param ies Buffer containing the information elements. + * @param ies_len Length of @p ies + * @param search_offset Offset to start searching from. This **must** point to a IE header. + * @param ie_type The type of the IE to look for. + * + * @return If the information element is found, the offset of the start of the IE within @p ies; if + * no match is found then -1; if the IE is found but is malformed then -2. + */ +int mm_find_ie_from_offset(const uint8_t *ies, uint32_t ies_len, uint32_t search_offset, uint8_t ie_type); + +/** + * Search a list of Information Elements (IEs) and find the first instance of matching the + * given type. + * + * @param ies Buffer containing the information elements. + * @param ies_len Length of @p ies + * @param ie_type The type of the IE to look for. + * + * @return If the information element is found, the offset of the start of the IE within @p ies; + * if no match is found then -1; if the IE is found but is malformed then -2. + */ +static inline int mm_find_ie(const uint8_t *ies, uint32_t ies_len, uint8_t ie_type) +{ + return mm_find_ie_from_offset(ies, ies_len, 0, ie_type); +} + +/** + * Search through the given list of Information Elements (IEs) from the given starting offset to + * find the first Vendor Specific IE that matches the given id. + * + * @warning A @p search_offset that is not aligned to the start of an IE header will result in + * undefined behaviour. + * + * @param[in] ies Buffer containing the information elements. + * @param[in] ies_len Length of @p ies + * @param[in] search_offset Offset to start searching from. This **must** point to a IE header. + * @param[in] id Buffer containing the IE ID, usually OUI+TYPE. + * @param[in] id_len Length of the ID. + * + * @return If the information element is found, the offset of the start of the IE within @p ies; if + * no match is found then -1; if the IE is found but is malformed then -2. + */ +int mm_find_vendor_specific_ie_from_offset(const uint8_t *ies, uint32_t ies_len, uint32_t search_offset, const uint8_t *id, + size_t id_len); + +/** + * Search through the given list of Information Elements (IEs) to find the first Vendor Specific IE + * that matches the given id. + * + * @param[in] ies Buffer containing the information elements. + * @param[in] ies_len Length of @p ies + * @param[in] id Buffer containing the IE ID, usually OUI+TYPE. + * @param[in] id_len Length of the ID. + * + * @return If the information element is found, the offset of the start of the IE within @p ies; if + * no match is found then -1; if the IE is found but is malformed then -2. + */ +static inline int mm_find_vendor_specific_ie(const uint8_t *ies, uint32_t ies_len, const uint8_t *id, size_t id_len) +{ + return mm_find_vendor_specific_ie_from_offset(ies, ies_len, 0, id, id_len); +} + +/** + * Search through the given list of information elements to find the RSN IE then parse it + * to extract relevant information into an instance of @ref mm_rsn_information. + * + * @param[in] ies Buffer containing the information elements. + * @param[in] ies_len Length of @p ies + * @param[out] output Pointer to an instance of @ref mm_rsn_information to receive output. + * + * @returns -2 on parse error, -1 if the RSN IE was not found, 0 if the RSN IE was found. + */ +int mm_parse_rsn_information(const uint8_t *ies, uint32_t ies_len, struct mm_rsn_information *output); + +/** @} */ + +#ifdef __cplusplus +} +#endif + +/** @} */ diff --git a/lib/MorseWlan/src/mmutils_wlan.c b/lib/MorseWlan/src/mmutils_wlan.c new file mode 100644 index 000000000..7ab7e4407 --- /dev/null +++ b/lib/MorseWlan/src/mmutils_wlan.c @@ -0,0 +1,163 @@ +#ifdef USE_MM_IOT_ESP32 +/* + * Copyright 2024 Morse Micro + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#include "mmutils.h" + +const char *mm_akm_suite_to_string(uint32_t akm_suite_oui) +{ + switch (akm_suite_oui) { + case MM_AKM_SUITE_NONE: + return "None"; + + case MM_AKM_SUITE_PSK: + return "PSK"; + + case MM_AKM_SUITE_SAE: + return "SAE"; + + case MM_AKM_SUITE_OWE: + return "OWE"; + + default: + return "Other"; + } +} + +int mm_find_ie_from_offset(const uint8_t *ies, uint32_t ies_len, uint32_t search_offset, uint8_t ie_type) +{ + while ((search_offset + 2) <= ies_len) { + uint8_t type = ies[search_offset]; + uint8_t length = ies[search_offset + 1]; + + if (type == ie_type) { + if ((search_offset + 2 + length) > ies_len) { + return -2; + } + return search_offset; + } + + search_offset += 2 + length; + } + + return -1; +} + +int mm_find_vendor_specific_ie_from_offset(const uint8_t *ies, uint32_t ies_len, uint32_t search_offset, const uint8_t *id, + size_t id_len) +{ + int offset = 0; + + while ((search_offset + id_len) <= ies_len) { + offset = mm_find_ie_from_offset(ies, ies_len, search_offset, MM_VENDOR_SPECIFIC_IE_TYPE); + if (offset < 0) { + return offset; + } + + uint8_t ie_type = ies[offset]; + uint8_t ie_length = ies[offset + 1]; + const uint8_t *ie_data = ies + (offset + 2); + + if (ie_type == MM_VENDOR_SPECIFIC_IE_TYPE && id_len <= ie_length && (memcmp(id, ie_data, id_len) == 0)) { + if (((uint32_t)offset + 2 + ie_length) > ies_len) { + return -2; + } + return offset; + } + + search_offset = 2 + ie_length + (uint32_t)offset; + } + + return -1; +} + +int mm_parse_rsn_information(const uint8_t *ies, uint32_t ies_len, struct mm_rsn_information *output) +{ + uint8_t length; + uint16_t num_pairwise_cipher_suites; + uint16_t num_akm_suites; + uint16_t ii; + + int offset = mm_find_ie(ies, ies_len, MM_RSN_INFORMATION_IE_TYPE); + + memset(output, 0, sizeof(*output)); + + if (offset < 0) { + return offset; + } + + /* Note that we rely on mm_find_ie() to validate that the IE does not extend past the end + * of the given buffer. */ + + length = ies[offset + 1]; + offset += 2; + + if (length < 8) { + printf("*WRN* RSN IE too short\n"); + return -2; + } + + /* Skip version field */ + output->version = ies[offset] | ies[offset + 1] << 8; + offset += 2; + length -= 2; + + output->group_cipher_suite = ies[offset] << 24 | ies[offset + 1] << 16 | ies[offset + 2] << 8 | ies[offset + 3]; + offset += 4; + length -= 4; + + num_pairwise_cipher_suites = ies[offset] | ies[offset + 1] << 8; + offset += 2; + length -= 2; + + output->num_pairwise_cipher_suites = num_pairwise_cipher_suites; + if (num_pairwise_cipher_suites > MM_RSN_INFORMATION_MAX_PAIRWISE_CIPHER_SUITES) { + output->num_pairwise_cipher_suites = MM_RSN_INFORMATION_MAX_PAIRWISE_CIPHER_SUITES; + } + + if (length < 4 * num_pairwise_cipher_suites + 2) { + printf("*WRN* RSN IE too short\n"); + return -2; + } + + for (ii = 0; ii < num_pairwise_cipher_suites; ii++) { + if (ii < output->num_pairwise_cipher_suites) { + output->pairwise_cipher_suites[ii] = + ies[offset] << 24 | ies[offset + 1] << 16 | ies[offset + 2] << 8 | ies[offset + 3]; + } + offset += 4; + length -= 4; + } + + num_akm_suites = ies[offset] | ies[offset + 1] << 8; + offset += 2; + length -= 2; + + output->num_akm_suites = num_akm_suites; + if (num_akm_suites > MM_RSN_INFORMATION_MAX_AKM_SUITES) { + output->num_akm_suites = MM_RSN_INFORMATION_MAX_AKM_SUITES; + } + + if (length < 4 * num_akm_suites + 2) { + printf("*WRN* RSN IE too short\n"); + return -2; + } + + for (ii = 0; ii < num_akm_suites; ii++) { + if (ii < output->num_akm_suites) { + output->akm_suites[ii] = ies[offset] << 24 | ies[offset + 1] << 16 | ies[offset + 2] << 8 | ies[offset + 3]; + } + offset += 4; + length -= 4; + } + + output->rsn_capabilities = ies[offset] | ies[offset + 1] << 8; + return 0; +} + +#endif /* USE_MM_IOT_ESP32 */ diff --git a/lib/MorseWlan/src/wlan_hal.c b/lib/MorseWlan/src/wlan_hal.c new file mode 100644 index 000000000..cae932468 --- /dev/null +++ b/lib/MorseWlan/src/wlan_hal.c @@ -0,0 +1,267 @@ +#ifdef USE_MM_IOT_ESP32 +/* + * Copyright 2021-2023 Morse Micro + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include +#include +#include + +#include "mmhal.h" +#include "mmosal.h" + +#include "driver/gpio.h" +#include "driver/spi_common.h" +#include "driver/spi_master.h" +#include "esp_random.h" +#include "esp_system.h" + +/** 10x8bit training seq */ +#define BYTE_TRAIN 16 + +/** SPI hw interrupt handler. Must be set before enabling irq */ +static mmhal_irq_handler_t spi_irq_handler = NULL; + +/** busy interrupt handler. Must be set before enabling irq */ +static mmhal_irq_handler_t busy_irq_handler = NULL; + +static spi_device_handle_t spi_handle; + +static void wlan_hal_gpio_init(void) +{ + gpio_config_t io_conf = {}; + io_conf.intr_type = GPIO_INTR_DISABLE; + io_conf.mode = GPIO_MODE_OUTPUT; + io_conf.pin_bit_mask = ((1ull << CONFIG_MM_WAKE) | (1ull << CONFIG_MM_SPI_CS)); + io_conf.pull_down_en = 0; + io_conf.pull_up_en = 0; + gpio_config(&io_conf); + + gpio_set_level(CONFIG_MM_WAKE, 0); + gpio_set_level(CONFIG_MM_SPI_CS, 0); + + io_conf.intr_type = GPIO_INTR_DISABLE; + io_conf.mode = GPIO_MODE_INPUT; + io_conf.pin_bit_mask = (1ull << CONFIG_MM_BUSY); + io_conf.pull_down_en = 1; + gpio_config(&io_conf); + + io_conf.intr_type = GPIO_INTR_DISABLE; + io_conf.mode = GPIO_MODE_INPUT; + io_conf.pin_bit_mask = (1ull << CONFIG_MM_SPI_IRQ); + io_conf.pull_down_en = 0; + gpio_config(&io_conf); +} + +static void wlan_hal_spi_init(void) +{ + esp_err_t ret; + + spi_bus_config_t buscfg = { + .miso_io_num = CONFIG_MM_SPI_MISO, + .mosi_io_num = CONFIG_MM_SPI_MOSI, + .sclk_io_num = CONFIG_MM_SPI_SCK, + .quadwp_io_num = -1, + .quadhd_io_num = -1, + /* max_transfer_sz defaults to 4092 if 0 when DMA enabled, or to SOC_SPI_MAXIMUM_BUFFER_SIZE + * if DMA is disabled. */ + .max_transfer_sz = 0, + .flags = SPICOMMON_BUSFLAG_MASTER, + }; + ret = spi_bus_initialize(SPI2_HOST, &buscfg, SPI_DMA_CH_AUTO); + if (ret != ESP_OK) { + printf("spi_bus_initialize failed\n"); + } + + /* Selected the highest available SPI clock speed that is still below the MM6108's maximum of + * 50MHz */ + spi_device_interface_config_t dev_cfg = { + .clock_speed_hz = SPI_MASTER_FREQ_40M, + .mode = 0, + .spics_io_num = -1, + .queue_size = 1, + }; + ret = spi_bus_add_device(SPI2_HOST, &dev_cfg, &spi_handle); + if (ret != ESP_OK) { + printf("spi_bus_add_device failed\n"); + } + + /* The actual clock frequency may not be the one that was set as it is re-calculated by the + * driver to the nearest hardware-compatible number. Importantly it is the "nearest", so it could be above + * the value set. */ + int actual_freq_khz = 0; + spi_device_get_actual_freq(spi_handle, &actual_freq_khz); + printf("Actual SPI CLK %dkHz\n", actual_freq_khz); +} + +static void wlan_hal_spi_deinit(void) +{ + esp_err_t ret = spi_bus_remove_device(spi_handle); + if (ret != ESP_OK) { + printf("spi_bus_remove_device failed\n"); + } + + ret = spi_bus_free(SPI2_HOST); + if (ret != ESP_OK) { + printf("spi_bus_initialize failed\n"); + } +} + +/** + * Minium transfer length in bytes before interrupt based transactions are used. This is because + * there is some setup time associated with using the interrupt based method when compared to the + * polling method. In the cases where the difference in setup time exceeds the transaction duration + * it is more efficient to uses the polling method instead of the interrupt based one. The below + * equation was used to calculate this. + * + * (DMA_TRANSACTION_DURATION - POLL_TRANSACTION_DURATION) / (8/SPI_FREQ) + * + * The typical duration for the ESP32 can be found in the [transaction + * duration](https://docs.espressif.com/projects/esp-idf/en/v5.1.1/esp32s3/api-reference/peripherals/spi_master.html#transaction-duration) + * section of the docs. + */ +#define INTERRUPT_TRANSFER_MIN_LENGTH 75 + +static void spi_master_rw(const uint8_t *w_data, uint8_t *r_data, size_t len) +{ + spi_transaction_t trans_desc = { + .rx_buffer = r_data, + .tx_buffer = w_data, + .length = (len * 8), + .flags = 0, + }; + + esp_err_t err; + if (len < INTERRUPT_TRANSFER_MIN_LENGTH) { + err = spi_device_polling_transmit(spi_handle, &trans_desc); + } else { + err = spi_device_transmit(spi_handle, &trans_desc); + } + + if (err != ESP_OK) { + printf("SPI rw error = %x\n", err); + } +} + +void mmhal_wlan_hard_reset(void) +{ + gpio_set_level(CONFIG_MM_RESET_N, 0); + mmosal_task_sleep(5); + gpio_set_level(CONFIG_MM_RESET_N, 1); + mmosal_task_sleep(20); +} + +void mmhal_wlan_spi_cs_assert(void) +{ + gpio_set_level(CONFIG_MM_SPI_CS, 0); +} + +void mmhal_wlan_spi_cs_deassert(void) +{ + gpio_set_level(CONFIG_MM_SPI_CS, 1); +} + +uint8_t mmhal_wlan_spi_rw(uint8_t data) +{ + uint8_t readval; + spi_master_rw(&data, &readval, 1); + return readval; +} + +void mmhal_wlan_spi_read_buf(uint8_t *buf, unsigned len) +{ + spi_master_rw(NULL, buf, len); +} + +void mmhal_wlan_spi_write_buf(const uint8_t *buf, unsigned len) +{ + spi_master_rw(buf, NULL, len); +} + +void mmhal_wlan_send_training_seq(void) +{ + mmhal_wlan_spi_cs_deassert(); + /* Send >74 clock pulses to card to stabilize CLK. + * This method of stacking up the TX data is described in RM0090 rev 19 Figure 253. + * It results is a reduction in the time between bytes of ~85% (316ns -> 48ns). + * Could not get this to work for the other transactions however. + */ + uint8_t buf[BYTE_TRAIN] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; + spi_master_rw(buf, NULL, BYTE_TRAIN); +} + +void mmhal_wlan_register_spi_irq_handler(mmhal_irq_handler_t handler) +{ + spi_irq_handler = handler; + gpio_isr_handler_add(CONFIG_MM_SPI_IRQ, (gpio_isr_t)spi_irq_handler, NULL); +} + +bool mmhal_wlan_spi_irq_is_asserted(void) +{ + return !gpio_get_level(CONFIG_MM_SPI_IRQ); +} + +void mmhal_wlan_set_spi_irq_enabled(bool enabled) +{ + if (enabled) { + gpio_set_intr_type(CONFIG_MM_SPI_IRQ, GPIO_INTR_LOW_LEVEL); + } else { + gpio_set_intr_type(CONFIG_MM_SPI_IRQ, GPIO_INTR_DISABLE); + } +} + +void mmhal_wlan_init(void) +{ + wlan_hal_gpio_init(); + wlan_hal_spi_init(); + /* Raise the RESET_N line to enable the WLAN transceiver. */ + gpio_set_level(CONFIG_MM_RESET_N, 1); +} + +void mmhal_wlan_deinit(void) +{ + /* Lower the RESET_N line to disable the WLAN transceiver. This will put the transceiver in its + * lowest power state. */ + gpio_set_level(CONFIG_MM_RESET_N, 0); + + wlan_hal_spi_deinit(); + + /* Clean up any ISR handlers that have been added. These will be added again if the WLAN + * interface is brought back up. */ + gpio_isr_handler_remove(CONFIG_MM_SPI_IRQ); + gpio_isr_handler_remove(CONFIG_MM_BUSY); +} + +void mmhal_wlan_wake_assert(void) +{ + gpio_set_level(CONFIG_MM_WAKE, 1); +} + +void mmhal_wlan_wake_deassert(void) +{ + gpio_set_level(CONFIG_MM_WAKE, 0); +} + +bool mmhal_wlan_busy_is_asserted(void) +{ + return gpio_get_level(CONFIG_MM_BUSY); +} + +void mmhal_wlan_register_busy_irq_handler(mmhal_irq_handler_t handler) +{ + busy_irq_handler = handler; + gpio_isr_handler_add(CONFIG_MM_BUSY, (gpio_isr_t)busy_irq_handler, NULL); +} + +void mmhal_wlan_set_busy_irq_enabled(bool enabled) +{ + if (enabled) { + gpio_set_intr_type(CONFIG_MM_BUSY, GPIO_INTR_POSEDGE); + } else { + gpio_set_intr_type(CONFIG_MM_BUSY, GPIO_INTR_DISABLE); + } +} + +#endif /* USE_MM_IOT_ESP32 */ diff --git a/src/main.cpp b/src/main.cpp index 28842734c..ad37f4333 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -801,7 +801,15 @@ void setup() #endif #else // ESP32 -#if defined(HW_SPI1_DEVICE) +#if defined(USE_HALOW_RADIO) && !defined(USE_SX1262) && !defined(USE_RF95) && !defined(USE_LR11X0) + // HaLow-only ESP32 variant: Morse's wlan_hal claims SPI2_HOST itself via + // ESP-IDF's spi_bus_initialize() (lib/MorseWlan/src/wlan_hal.c). Calling + // Arduino SPI.begin() on the default LORA_* pins both (a) collides on + // SPI2_HOST and (b) reconfigures GPIO 5 as SPI clock, clobbering the + // HaLow BUSY input. Skip Arduino's bus init — there's no LoRa, no + // display, no SD card on this board. + LOG_DEBUG("Skipping Arduino SPI.begin() — HaLow owns SPI2_HOST"); +#elif defined(HW_SPI1_DEVICE) SPI1.begin(LORA_SCK, LORA_MISO, LORA_MOSI, LORA_CS); LOG_DEBUG("SPI1.begin(SCK=%d, MISO=%d, MOSI=%d, NSS=%d)", LORA_SCK, LORA_MISO, LORA_MOSI, LORA_CS); SPI1.setFrequency(4000000); diff --git a/src/mesh/RadioInterface.cpp b/src/mesh/RadioInterface.cpp index f4f25f80c..0cd2d239c 100644 --- a/src/mesh/RadioInterface.cpp +++ b/src/mesh/RadioInterface.cpp @@ -34,6 +34,10 @@ #include "STM32WLE5JCInterface.h" #endif +#ifdef USE_HALOW_RADIO +#include "halow/HaLowInterface.h" +#endif + static const meshtastic_Config_LoRaConfig_ModemPreset PRESETS_STD[] = { meshtastic_Config_LoRaConfig_ModemPreset_LONG_FAST, meshtastic_Config_LoRaConfig_ModemPreset_LONG_SLOW, meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_SLOW, meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_FAST, @@ -520,6 +524,22 @@ std::unique_ptr initLoRa() rebootAtMsec = millis() + 5000; } } + +#ifdef USE_HALOW_RADIO + // HaLow is the only radio on the dedicated XIAO+HaLow variant, so we only + // try it when no LoRa chip claimed the slot. The interface itself stubs + // out cleanly when USE_MM_IOT_ESP32 is not yet wired in (Phase 0 of plan). + if (!rIf) { + auto halowIf = std::unique_ptr(new HaLowInterface()); + if (!halowIf->init()) { + LOG_WARN("HaLow init failed (stub or SDK not present)"); + } else { + LOG_INFO("HaLow init success"); + rIf = std::move(halowIf); + } + } +#endif + return rIf; } diff --git a/src/mesh/halow/HaLowFrame.h b/src/mesh/halow/HaLowFrame.h new file mode 100644 index 000000000..265f89b3d --- /dev/null +++ b/src/mesh/halow/HaLowFrame.h @@ -0,0 +1,18 @@ +#pragma once + +#include +#include + +// IEEE 802 Local Experimental EtherType 1, valid on private LANs. Carries a +// Meshtastic RadioBuffer (PacketHeader + payload, ≤255 bytes) end-to-end so the +// LoRa wire format is preserved on HaLow. +static constexpr uint16_t ETHERTYPE_MESHTASTIC_HALOW = 0x88B5; + +static constexpr uint8_t HALOW_BROADCAST_MAC[6] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; + +struct __attribute__((packed)) HaLowEthFrameHeader { + uint8_t dst[6]; + uint8_t src[6]; + uint16_t ethertype; // network byte order on the wire +}; +static_assert(sizeof(HaLowEthFrameHeader) == 14, "HaLow ethernet header must be 14 bytes"); diff --git a/src/mesh/halow/HaLowInterface.cpp b/src/mesh/halow/HaLowInterface.cpp new file mode 100644 index 000000000..3458fa81f --- /dev/null +++ b/src/mesh/halow/HaLowInterface.cpp @@ -0,0 +1,287 @@ +#include "configuration.h" +#ifdef USE_HALOW_RADIO + +#include "HaLowFrame.h" +#include "HaLowInterface.h" +#include "MeshTypes.h" +#include "RTC.h" // getValidTime / RTCQualityFromNet +#include +#if HAS_UDP_MULTICAST +#include "main.h" // for `udpHandler` +#include "mesh/generated/meshtastic/config.pb.h" +#endif + +#ifdef USE_MM_IOT_ESP32 +extern "C" { +#include "mmhal.h" +#include "mmipal.h" +#include "mmwlan.h" +#include "mmwlan_regdb.def" +} + +#ifndef HALOW_COUNTRY_CODE +#define HALOW_COUNTRY_CODE "US" +#endif +// SSID/PSK supplied at build time. Empty SSID means "don't auto-associate" — +// the chip boots and idles, useful for testing without an AP nearby. +#ifndef HALOW_SSID +#define HALOW_SSID "" +#endif +#ifndef HALOW_PASSPHRASE +#define HALOW_PASSPHRASE "" +#endif +#endif + +HaLowInterface::HaLowInterface() : concurrency::OSThread("HaLow") {} + +#ifdef USE_MM_IOT_ESP32 +// Static glue so the C callbacks can reach into the (non-static) instance. +// Only one HaLowInterface exists, owned by Router::iface or constructed at +// boot, so a single pointer is sufficient. +static HaLowInterface *s_instance = nullptr; + +static void halow_link_state_cb(enum mmwlan_link_state link_state, void *arg) +{ + (void)arg; + if (link_state == MMWLAN_LINK_UP) { + struct mmipal_ip_config ipcfg = {}; + if (mmipal_get_ip_config(&ipcfg) == MMIPAL_SUCCESS) { + LOG_INFO("HaLow: link UP, ip=%s netmask=%s gw=%s", ipcfg.ip_addr, ipcfg.netmask, ipcfg.gateway_addr); + } else { + LOG_INFO("HaLow: link UP (no IP yet)"); + } + int32_t rssi = mmwlan_get_rssi(); + if (rssi != INT32_MIN) { + LOG_INFO("HaLow: AP RSSI %ld dBm", (long)rssi); + } + } else { + LOG_INFO("HaLow: link DOWN"); + } +} + +// mmwlan delivers Ethernet-framed packets here: 14-byte 802.3 header + payload. +// The payload is whatever EtherType we used on the TX side — we filter for our +// own marker and decode the inner RadioBuffer. +void HaLowInterface::rxTrampoline(uint8_t *header, unsigned header_len, uint8_t *payload, unsigned payload_len, void *arg) +{ + HaLowInterface *self = static_cast(arg); + if (!self || header_len < sizeof(HaLowEthFrameHeader)) { + return; + } + // EtherType is big-endian in the header (bytes 12-13). + uint16_t et = ((uint16_t)header[12] << 8) | header[13]; + if (et != ETHERTYPE_MESHTASTIC_HALOW) { + return; + } + self->onFrameReceived(payload, payload_len, /*rssi*/ 0); +} +#endif + +HaLowInterface::~HaLowInterface() = default; + +bool HaLowInterface::init() +{ + RadioInterface::init(); + +#ifdef USE_MM_IOT_ESP32 + LOG_INFO("HaLow: mmhal_init()"); + mmhal_init(); + + LOG_INFO("HaLow: mmwlan_init()"); + mmwlan_init(); + + const struct mmwlan_s1g_channel_list *channel_list = mmwlan_lookup_regulatory_domain(get_regulatory_db(), HALOW_COUNTRY_CODE); + if (!channel_list) { + LOG_ERROR("HaLow: country %s not in regdb", HALOW_COUNTRY_CODE); + return false; + } + if (mmwlan_set_channel_list(channel_list) != MMWLAN_SUCCESS) { + LOG_ERROR("HaLow: set_channel_list failed"); + return false; + } + + struct mmwlan_boot_args boot_args = MMWLAN_BOOT_ARGS_INIT; + enum mmwlan_status st = mmwlan_boot(&boot_args); + if (st != MMWLAN_SUCCESS) { + LOG_ERROR("HaLow: mmwlan_boot failed (%d) — firmware load or SPI wiring", (int)st); + return false; + } + + struct mmwlan_version version; + if (mmwlan_get_version(&version) == MMWLAN_SUCCESS) { + LOG_INFO("HaLow: chip 0x%lx, fw %s, lib %s", (unsigned long)version.morse_chip_id, version.morse_fw_version, + version.morselib_version); + } + + // Bring up the LWIP netif (DHCP by default). mmipal plugs into the + // Arduino-ESP32 framework's LWIP — no separate stack. + struct mmipal_init_args ipal_args = MMIPAL_INIT_ARGS_DEFAULT; + if (mmipal_init(&ipal_args) != MMIPAL_SUCCESS) { + LOG_ERROR("HaLow: mmipal_init failed"); + return false; + } + + if (HALOW_SSID[0] == '\0') { + LOG_INFO("HaLow: no SSID configured, chip will idle (set -DHALOW_SSID=...)"); + return false; + } + + struct mmwlan_sta_args sta_args = MMWLAN_STA_ARGS_INIT; + sta_args.ssid_len = strnlen(HALOW_SSID, sizeof(sta_args.ssid)); + memcpy(sta_args.ssid, HALOW_SSID, sta_args.ssid_len); + sta_args.passphrase_len = strnlen(HALOW_PASSPHRASE, sizeof(sta_args.passphrase)); + memcpy(sta_args.passphrase, HALOW_PASSPHRASE, sta_args.passphrase_len); + sta_args.security_type = (sta_args.passphrase_len > 0) ? MMWLAN_SAE : MMWLAN_OPEN; + + s_instance = this; + mmwlan_register_link_state_cb(halow_link_state_cb, NULL); + if (mmwlan_register_rx_cb(rxTrampoline, this) != MMWLAN_SUCCESS) { + LOG_ERROR("HaLow: register_rx_cb failed"); + return false; + } + + LOG_INFO("HaLow: associating with SSID '%s'", HALOW_SSID); + if (mmwlan_sta_enable(&sta_args, NULL) != MMWLAN_SUCCESS) { + LOG_ERROR("HaLow: mmwlan_sta_enable failed"); + return false; + } + + // From here on out, HaLow is the radio. send() encodes packets as 802.3 + // frames addressed to broadcast MAC with EtherType 0x88B5; the AP relays + // them to all associated STAs (Phase 3 closes the AP-less gap). + return true; +#else + LOG_WARN("HaLow: built without USE_MM_IOT_ESP32, transport is a stub"); + return false; +#endif +} + +bool HaLowInterface::reconfigure() +{ + return true; +} + +bool HaLowInterface::sleep() +{ + return true; +} + +bool HaLowInterface::canSleep() +{ + return true; +} + +ErrorCode HaLowInterface::send(meshtastic_MeshPacket *p) +{ + if (!p) { + return ERRNO_UNKNOWN; + } + +#ifdef USE_MM_IOT_ESP32 + // beginSending() serializes the MeshPacket into radioBuffer (PacketHeader + // + payload). We then prepend a 14-byte 802.3 header so mmwlan can wrap + // it as an 802.11 data frame and ship it through the AP. + size_t encoded = beginSending(p); + if (encoded == 0) { + packetPool.release(p); + return ERRNO_UNKNOWN; + } + + uint8_t txbuf[sizeof(HaLowEthFrameHeader) + sizeof(RadioBuffer)]; + if (encoded > sizeof(RadioBuffer)) { + LOG_ERROR("HaLow: encoded %u > radioBuffer", (unsigned)encoded); + packetPool.release(p); + return ERRNO_UNKNOWN; + } + + // Ethernet header: DA(6) || SA(6) || EtherType(2, big-endian). + memcpy(txbuf, HALOW_BROADCAST_MAC, 6); + if (mmwlan_get_mac_addr(txbuf + 6) != MMWLAN_SUCCESS) { + memset(txbuf + 6, 0, 6); // fallback so the frame still goes out + } + txbuf[12] = (uint8_t)(ETHERTYPE_MESHTASTIC_HALOW >> 8); + txbuf[13] = (uint8_t)(ETHERTYPE_MESHTASTIC_HALOW & 0xFF); + memcpy(txbuf + sizeof(HaLowEthFrameHeader), &radioBuffer, encoded); + + enum mmwlan_status st = mmwlan_tx(txbuf, sizeof(HaLowEthFrameHeader) + encoded); + packetPool.release(p); + sendingPacket = NULL; + return (st == MMWLAN_SUCCESS) ? ERRNO_OK : ERRNO_UNKNOWN; +#else + packetPool.release(p); + return ERRNO_DISABLED; +#endif +} + +meshtastic_QueueStatus HaLowInterface::getQueueStatus() +{ + meshtastic_QueueStatus qs = meshtastic_QueueStatus_init_zero; + qs.free = 16; + qs.maxlen = 16; + return qs; +} + +uint32_t HaLowInterface::getPacketTime(uint32_t totalPacketLen, bool /*received*/) +{ + // bytes * 8 bits / (HALOW_NOMINAL_KBPS * 1000 bits/sec) * 1000 ms/sec. + // Floor to 1 ms so the slot-time math upstream never divides by zero. + uint32_t ms = (totalPacketLen * 8u + HALOW_NOMINAL_KBPS - 1u) / HALOW_NOMINAL_KBPS; + return ms ? ms : 1u; +} + +int32_t HaLowInterface::runOnce() +{ + return 1000; // nothing to do until the SDK is wired in +} + +void HaLowInterface::onFrameReceived(const uint8_t *payload, size_t payload_len, int8_t rssi) +{ + if (!payload || payload_len < sizeof(PacketHeader)) { + return; + } + // Cap at our RadioBuffer size — anything larger is malformed for our wire + // format and we drop it rather than corrupt memory. + if (payload_len > sizeof(RadioBuffer)) { + LOG_WARN("HaLow: rx %u > RadioBuffer, dropping", (unsigned)payload_len); + return; + } + + meshtastic_MeshPacket *p = packetPool.allocZeroed(); + if (!p) { + return; + } + + // Unpack the RadioBuffer into a MeshPacket (mirrors the LoRa RX decode). + const PacketHeader *h = reinterpret_cast(payload); + p->from = h->from; + p->to = h->to; + p->id = h->id; + p->channel = h->channel; + p->hop_limit = h->flags & PACKET_FLAGS_HOP_LIMIT_MASK; + p->want_ack = !!(h->flags & PACKET_FLAGS_WANT_ACK_MASK); + p->via_mqtt = !!(h->flags & PACKET_FLAGS_VIA_MQTT_MASK); + p->hop_start = (h->flags & PACKET_FLAGS_HOP_START_MASK) >> PACKET_FLAGS_HOP_START_SHIFT; + p->relay_node = h->relay_node; + p->next_hop = h->next_hop; + + size_t payload_only = payload_len - sizeof(PacketHeader); + if (payload_only > sizeof(p->encrypted.bytes)) { + packetPool.release(p); + return; + } + memcpy(p->encrypted.bytes, payload + sizeof(PacketHeader), payload_only); + p->encrypted.size = payload_only; + p->which_payload_variant = meshtastic_MeshPacket_encrypted_tag; + + // mmwlan's rx callback doesn't carry per-frame RSSI on this SDK version — + // fall back to the connection-level RSSI for visibility in the phone UI. + (void)rssi; + int32_t link_rssi = mmwlan_get_rssi(); + p->rx_rssi = (link_rssi == INT32_MIN) ? 0 : (int8_t)link_rssi; + p->rx_snr = 0; + p->rx_time = getValidTime(RTCQualityFromNet); + + deliverToReceiver(p); +} + +#endif // USE_HALOW_RADIO diff --git a/src/mesh/halow/HaLowInterface.h b/src/mesh/halow/HaLowInterface.h new file mode 100644 index 000000000..1cbed5553 --- /dev/null +++ b/src/mesh/halow/HaLowInterface.h @@ -0,0 +1,56 @@ +#pragma once +#ifdef USE_HALOW_RADIO + +#include "RadioInterface.h" +#include "concurrency/OSThread.h" + +/** + * HaLow (802.11ah) transport. Derives directly from RadioInterface because + * RadioLib has no MM6108 driver and the model doesn't fit — mmwlan is a + * frame-level API, not a register-level SPI interface. + * + * Frames go out as Ethernet payloads (broadcast MAC, EtherType 0x88B5) carrying + * the same RadioBuffer the LoRa path builds via beginSending(), so the + * Meshtastic wire format is unchanged. + * + * True peer-broadcast requires MAC-layer support that mm-iot-esp32 does not + * currently expose (no 802.11s / IBSS / monitor mode). Until that gap closes, + * the send path is a no-op and packets must travel over UDP multicast via the + * existing UdpMulticastHandler path (HaLow operating as a STA against an AP). + * See the plan for the SDK-side workstream. + */ +class HaLowInterface : public RadioInterface, private concurrency::OSThread +{ + public: + HaLowInterface(); + ~HaLowInterface() override; + + bool init() override; + bool reconfigure() override; + bool sleep() override; + bool canSleep() override; + + ErrorCode send(meshtastic_MeshPacket *p) override; + meshtastic_QueueStatus getQueueStatus() override; + uint32_t getPacketTime(uint32_t totalPacketLen, bool received = false) override; + + protected: + int32_t runOnce() override; + + private: + void onFrameReceived(const uint8_t *payload, size_t payload_len, int8_t rssi); + +#ifdef USE_MM_IOT_ESP32 + // Trampoline registered with mmwlan_register_rx_cb. The callback hands us + // the 802.3 header and payload separately. + static void rxTrampoline(uint8_t *header, unsigned header_len, uint8_t *payload, unsigned payload_len, void *arg); +#endif + + // Approximate bytes-per-millisecond at the configured channel width / MCS. + // HaLow is 150 kbps to 32.5 Mbps depending on configuration — picking a + // single value is fiction, but airtime accounting needs *something*, and + // duty cycle isn't the constraint on HaLow that it is on LoRa. + static constexpr uint32_t HALOW_NOMINAL_KBPS = 1000; // 1 Mbps, 2 MHz MCS3 ballpark +}; + +#endif // USE_HALOW_RADIO diff --git a/src/mesh/wifi/HaLowWiFi.cpp b/src/mesh/wifi/HaLowWiFi.cpp new file mode 100644 index 000000000..c0a35449e --- /dev/null +++ b/src/mesh/wifi/HaLowWiFi.cpp @@ -0,0 +1,53 @@ +#include "configuration.h" +#ifdef USE_HALOW_WIFI + +#include "HaLowWiFi.h" + +#ifdef USE_MM_IOT_ESP32 +extern "C" { +#include "mmwlan.h" +} +#endif + +namespace halow +{ + +HaLowWiFiClass HaLowWiFi; + +void HaLowWiFiClass::begin(const char *ssid, const char *passphrase) +{ + (void)ssid; + (void)passphrase; +#ifdef USE_MM_IOT_ESP32 + // Phase 1: mmwlan_sta_connect(ssid, passphrase, ...). +#endif +} + +uint8_t HaLowWiFiClass::status() +{ +#ifdef USE_MM_IOT_ESP32 + // Phase 1: translate mmwlan link state to the Arduino-WiFi-compat enum. + return HALOW_WL_DISCONNECTED; +#else + return HALOW_WL_DISCONNECTED; +#endif +} + +IPAddress HaLowWiFiClass::localIP() +{ + return IPAddress(0, 0, 0, 0); +} + +String HaLowWiFiClass::macAddress() +{ + return String("00:00:00:00:00:00"); +} + +bool HaLowWiFiClass::isConnected() +{ + return status() == HALOW_WL_CONNECTED; +} + +} // namespace halow + +#endif // USE_HALOW_WIFI diff --git a/src/mesh/wifi/HaLowWiFi.h b/src/mesh/wifi/HaLowWiFi.h new file mode 100644 index 000000000..e0b9463a6 --- /dev/null +++ b/src/mesh/wifi/HaLowWiFi.h @@ -0,0 +1,47 @@ +#pragma once +#ifdef USE_HALOW_WIFI + +#include +#include +#include + +// Subset of the Arduino WiFi API that WiFiAPClient.cpp and UdpMulticastHandler +// touch, backed by Morse Micro mmwlan STA-mode association. Lets the existing +// mesh-over-IP path ride HaLow with no changes to call sites — required while +// the peer-broadcast HaLowInterface remains gated on SDK work. + +namespace halow +{ + +enum WiFiStatusCompat : uint8_t { + HALOW_WL_IDLE_STATUS = 0, + HALOW_WL_NO_SSID_AVAIL = 1, + HALOW_WL_CONNECTED = 3, + HALOW_WL_DISCONNECTED = 6, +}; + +class HaLowWiFiClass +{ + public: + void begin(const char *ssid, const char *passphrase); + uint8_t status(); + IPAddress localIP(); + String macAddress(); + bool isConnected(); +}; + +extern HaLowWiFiClass HaLowWiFi; + +} // namespace halow + +// When the HaLow variant is being built, redirect the Arduino WiFi handle +// expected by call sites to the HaLow shim. Including this header in the +// preprocessor-conditional places that currently #include is enough +// to swap the implementation at build time. +#define WiFi ::halow::HaLowWiFi +#define WL_CONNECTED ::halow::HALOW_WL_CONNECTED +#define WL_NO_SSID_AVAIL ::halow::HALOW_WL_NO_SSID_AVAIL +#define WL_DISCONNECTED ::halow::HALOW_WL_DISCONNECTED +#define WL_IDLE_STATUS ::halow::HALOW_WL_IDLE_STATUS + +#endif // USE_HALOW_WIFI diff --git a/src/platform/esp32/main-esp32.cpp b/src/platform/esp32/main-esp32.cpp index dbc573c95..4570c8824 100644 --- a/src/platform/esp32/main-esp32.cpp +++ b/src/platform/esp32/main-esp32.cpp @@ -165,7 +165,7 @@ void esp32Setup() // #define APP_WATCHDOG_SECS 45 #define APP_WATCHDOG_SECS 90 -#ifdef CONFIG_IDF_TARGET_ESP32C6 +#if defined(CONFIG_IDF_TARGET_ESP32C6) || (defined(ESP_IDF_VERSION) && ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)) esp_task_wdt_config_t *wdt_config = (esp_task_wdt_config_t *)malloc(sizeof(esp_task_wdt_config_t)); wdt_config->timeout_ms = APP_WATCHDOG_SECS * 1000; wdt_config->trigger_panic = true; diff --git a/variants/esp32s3/seeed_xiao_s3_halow/pins_arduino.h b/variants/esp32s3/seeed_xiao_s3_halow/pins_arduino.h new file mode 100644 index 000000000..6f71b9f66 --- /dev/null +++ b/variants/esp32s3/seeed_xiao_s3_halow/pins_arduino.h @@ -0,0 +1,20 @@ +#ifndef Pins_Arduino_h +#define Pins_Arduino_h + +#include + +#define USB_VID 0x2886 +#define USB_PID 0x0059 + +// I2C is unused on this variant (HaLow BUSY claims GPIO 5), but the Arduino +// framework's default Wire still needs valid SDA/SCL values to compile. +static const uint8_t SDA = 47; +static const uint8_t SCL = 48; + +// Default SPI is shared with the HaLow module. SS = HaLow CS. +static const uint8_t MISO = 8; +static const uint8_t SCK = 7; +static const uint8_t MOSI = 9; +static const uint8_t SS = 4; + +#endif /* Pins_Arduino_h */ diff --git a/variants/esp32s3/seeed_xiao_s3_halow/platformio.ini b/variants/esp32s3/seeed_xiao_s3_halow/platformio.ini new file mode 100644 index 000000000..562a4dfd8 --- /dev/null +++ b/variants/esp32s3/seeed_xiao_s3_halow/platformio.ini @@ -0,0 +1,104 @@ +[env:seeed-xiao-s3-halow] +; HW_MODEL: using PRIVATE_HW (255) until a SEEED_XIAO_S3_HALOW slot is allocated upstream. +custom_meshtastic_hw_model = 255 +custom_meshtastic_hw_model_slug = SEEED_XIAO_S3_HALOW +custom_meshtastic_architecture = esp32-s3 +custom_meshtastic_actively_supported = false +custom_meshtastic_support_level = 1 +custom_meshtastic_display_name = Seeed Xiao ESP32-S3 + Wi-Fi HaLow +custom_meshtastic_tags = Seeed, HaLow, Experimental +custom_meshtastic_requires_dfu = true +custom_meshtastic_partition_scheme = 8MB + +extends = esp32s3_base +board = seeed-xiao-s3 + +; Morse Micro mm-iot-esp32 requires ESP-IDF 5.1.x; PlatformIO's default +; espressif32 platform ships IDF 4.4.7. Override with the pioarduino fork + +; vidplace7's v5.1 framework artifact (same combination the ESP32-C6 variant +; uses). This unlocks IDF 5.1 driver APIs (spi_master, gpio) at the cost of +; some Arduino-ESP32 2.x APIs that aren't in 3.x — expect to compile-fix +; downstream callers (BLE/NimBLE most likely; that's why we keep BLE on by +; default but accept it may need to be disabled). +platform = + https://github.com/Jason2866/platform-espressif32/archive/22faa566df8c789000f8136cd8d0aca49617af55.zip +platform_packages = + framework-arduinoespressif32 @ https://github.com/vidplace7/platform-espressif32/releases/download/meshtastic-esp32c6/framework-arduinoespressif32-all-release_v5.1-124d64e.zip + +board_level = pr +board_check = true +board_build.partitions = default_8MB.csv +upload_protocol = esptool +upload_speed = 921600 + +build_unflags = + ${esp32s3_base.build_unflags} + -DARDUINO_USB_MODE=1 + +build_flags = + ${esp32s3_base.build_flags} + -D SEEED_XIAO_S3_HALOW + -I variants/esp32s3/seeed_xiao_s3_halow + -DBOARD_HAS_PSRAM + -DARDUINO_USB_MODE=0 + ; Compile the HaLow transport into the firmware. The SDK glue inside the + ; transport is gated on USE_MM_IOT_ESP32, which is OFF here until the + ; mm-iot-esp32 ESP-IDF component is wired into the PlatformIO build (Phase 0 + ; step 1 of the plan). With it OFF the variant builds as a no-radio node. + ; NimBLE-Arduino 1.4.3 (pinned in esp32-common.ini) doesn't compile against + ; IDF 5.1, so disable Bluetooth on this variant for now. Phone API still + ; works over Wi-Fi/HaLow. Mirrors the workaround the ESP32-C6 variant uses. + -DHAS_BLUETOOTH=0 + -DMESHTASTIC_EXCLUDE_BLUETOOTH=1 + ; The v5.1 framework already adds --specs=nano.specs; tell esp32_extra.py + ; to skip its own addition or the linker fails on a duplicate spec. + -DMESHTASTIC_SKIP_NANO_SPECS=1 + -DMESHTASTIC_EXCLUDE_PAXCOUNTER=1 + -DMESHTASTIC_EXCLUDE_WEBSERVER=1 + ; No I2C peripherals on the HaLow stack (BUSY claims SDA pin). The cardKB + ; rescan was spamming "Unknown error at address 0x1f" etc on boot. Skipping + ; just the rescan path is enough — main.cpp's I2C scan is already gated on + ; HAS_WIRE which we set to 0. We avoid MESHTASTIC_EXCLUDE_I2C because + ; InputBroker.cpp unconditionally references cardKbI2cImpl. + -DI2C_NO_RESCAN=1 + -DUSE_HALOW_RADIO=1 + -DUSE_HALOW_WIFI=1 + -DUSE_MM_IOT_ESP32=1 + ; Pin map fed to the vendored mm_shims (replaces Kconfig defaults at + ; framework/mm_shims/Kconfig — same values, just delivered as -D flags + ; because PlatformIO/Arduino doesn't run idf.py menuconfig). + -DCONFIG_MM_RESET_N=1 + -DCONFIG_MM_WAKE=2 + -DCONFIG_MM_BUSY=5 + -DCONFIG_MM_SPI_SCK=7 + -DCONFIG_MM_SPI_MOSI=9 + -DCONFIG_MM_SPI_MISO=8 + -DCONFIG_MM_SPI_CS=4 + -DCONFIG_MM_SPI_IRQ=3 + -DCONFIG_MM_SPI_FREQ_HZ=20000000 + -DCONFIG_MM_SPI_HOST=2 + -DMMPKTMEM_TX_POOL_N_BLOCKS=20 + -DMMPKTMEM_RX_POOL_N_BLOCKS=23 + ; HaLow STA credentials. Empty SSID = chip boots and idles (default — useful + ; for first-flash hardware verification). To actually join an AP, override + ; in your own platformio_override.ini or pass at the pio command line: + ; -DHALOW_SSID=\\\"MyHaLowAP\\\" -DHALOW_PASSPHRASE=\\\"secret\\\" + ; The trailing escaped quotes survive shell+SCons quoting. + -DHALOW_SSID=\"\" + -DHALOW_PASSPHRASE=\"\" + ; HaLow is the only transport on this variant — UDP multicast mesh-over-IP + ; needs to be on by default or the firmware boots into a useless state. + -DUSERPREFS_NETWORK_ENABLED_PROTOCOLS=meshtastic_Config_NetworkConfig_ProtocolFlags_UDP_BROADCAST + ; libmorse.a is precompiled for esp32-xtensa-lx7. The .mbin.o firmware blobs + ; are picked up via library.json's srcFilter; libmorse needs an explicit + ; -L/-l on the link line. + -Llib/MorseWlan/lib/esp32-xtensa-lx7 + -lmorse + +lib_ignore = + ${esp32_common.lib_ignore} + NimBLE-Arduino + libpax + +build_src_filter = + ${esp32_common.build_src_filter} - diff --git a/variants/esp32s3/seeed_xiao_s3_halow/variant.h b/variants/esp32s3/seeed_xiao_s3_halow/variant.h new file mode 100644 index 000000000..3c8a090f9 --- /dev/null +++ b/variants/esp32s3/seeed_xiao_s3_halow/variant.h @@ -0,0 +1,38 @@ +/* + * Seeed Studio XIAO ESP32-S3 + Wi-Fi HaLow add-on (Quectel FGH100M-H / Morse Micro MM6108) + * + * Pin map fixed by the Seeed HaLow add-on hardware. SCK/MOSI/MISO are the + * same XIAO pins the LoRa hat uses (7/9/8), but CS/IRQ/RST/WAKE/BUSY collide + * with the L76K GPS standby (GPIO 1) and I2C SDA (GPIO 5) used by the LoRa + * variant — so this variant deliberately ships without LoRa, GPS, or the + * SSD1306 OLED. The HaLow module replaces all of them. + * + * Hardware: https://wiki.seeedstudio.com/getting_started_with_wifi_halow_module_for_xiao/ + */ + +#define LED_POWER 48 +#define LED_STATE_ON 1 + +#define BUTTON_PIN 21 +#define BUTTON_NEED_PULLUP + +// No battery monitoring on this variant — leaving BATTERY_PIN undefined skips +// the legacy adc1_* ADC code in Power.cpp that isn't compatible with IDF 5.1. + +// HaLow BUSY claims GPIO 5 (the I2C SDA used by the LoRa variant), and HaLow +// RST claims GPIO 1 (the GPS standby). Disable I2C, the OLED screen, and GPS +// so nothing else drives those lines. +#define HAS_WIRE 0 +#define HAS_SCREEN 0 +#define HAS_GPS 0 +#define NO_GPS 1 + +// HaLow module pin map (Seeed XIAO HaLow add-on, fixed by hardware) +#define HALOW_SPI_SCK 7 +#define HALOW_SPI_MISO 8 +#define HALOW_SPI_MOSI 9 +#define HALOW_CS 4 +#define HALOW_IRQ 3 +#define HALOW_RST 1 +#define HALOW_WAKE 2 +#define HALOW_BUSY 5