Compare commits

...
Author SHA1 Message Date
Ben Meadors a1acad6dc4 Add support for Wi-Fi HaLow on Seeed XIAO ESP32-S3 2026-05-16 09:48:17 -05:00
54 changed files with 10407 additions and 4 deletions
+7
View File
@@ -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
+15 -2
View File
@@ -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"])
+28
View File
@@ -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)
+77
View File
@@ -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)
/** @} */
+124
View File
@@ -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
+331
View File
@@ -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 <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <time.h>
#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
/** @} */
+145
View File
@@ -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 <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#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
/** @} */
+89
View File
@@ -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 <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#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
/** @} */
+640
View File
@@ -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 <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <time.h>
#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
/** @} */
+383
View File
@@ -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 <stdbool.h>
#include <stdint.h>
/** 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
/** @} */
+64
View File
@@ -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 <stddef.h>
#include <stdint.h>
/**
* 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
File diff suppressed because it is too large Load Diff
+515
View File
@@ -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 <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#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
/** @} */
+172
View File
@@ -0,0 +1,172 @@
/*
* Copyright 2022-2024 Morse Micro
*
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @ingroup MMPKT
*
* @{
*/
#pragma once
#include <stddef.h>
#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
/**
* @}
*/
File diff suppressed because it is too large Load Diff
+302
View File
@@ -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 &regulatory_db;
}
/** \} */
Binary file not shown.
+14
View File
@@ -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"
}
}
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+148
View File
@@ -0,0 +1,148 @@
/*
* Copyright 2022-2024 Morse Micro
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#pragma once
#include <stdint.h>
/*
* ----
* 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
+27
View File
@@ -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
Binary file not shown.
+216
View File
@@ -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 */
+457
View File
@@ -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 <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#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
/** @} */
+50
View File
@@ -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 */
+40
View File
@@ -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 <stddef.h>
#include <stdint.h>
#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
/** @} */
+100
View File
@@ -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 */
+72
View File
@@ -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 */
+726
View File
@@ -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
+218
View File
@@ -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
+29
View File
@@ -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
@@ -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 functions xTicksToWait parameter (which specifies the functions 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 */
+245
View File
@@ -0,0 +1,245 @@
#ifdef USE_MM_IOT_ESP32
/*
* Copyright 2024 Morse Micro
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdatomic.h>
#include <stdint.h>
#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
+12
View File
@@ -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()
+301
View File
@@ -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 <stdarg.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#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
/** @} */
+163
View File
@@ -0,0 +1,163 @@
#ifdef USE_MM_IOT_ESP32
/*
* Copyright 2024 Morse Micro
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#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 */
+267
View File
@@ -0,0 +1,267 @@
#ifdef USE_MM_IOT_ESP32
/*
* Copyright 2021-2023 Morse Micro
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#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 */
+9 -1
View File
@@ -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);
+20
View File
@@ -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<RadioInterface> 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<HaLowInterface>(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;
}
+18
View File
@@ -0,0 +1,18 @@
#pragma once
#include <stddef.h>
#include <stdint.h>
// 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");
+287
View File
@@ -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 <string.h>
#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<HaLowInterface *>(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<const PacketHeader *>(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
+56
View File
@@ -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
+53
View File
@@ -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
+47
View File
@@ -0,0 +1,47 @@
#pragma once
#ifdef USE_HALOW_WIFI
#include <IPAddress.h>
#include <WString.h>
#include <stdint.h>
// 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 <WiFi.h> 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
+1 -1
View File
@@ -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;
@@ -0,0 +1,20 @@
#ifndef Pins_Arduino_h
#define Pins_Arduino_h
#include <stdint.h>
#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 */
@@ -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} -<mesh/http>
@@ -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