diff options
Diffstat (limited to 'lib/bt/controller/esp32')
| -rw-r--r-- | lib/bt/controller/esp32/Kconfig.in | 446 | ||||
| -rw-r--r-- | lib/bt/controller/esp32/bt.c | 1851 | ||||
| -rw-r--r-- | lib/bt/controller/esp32/hli_api.c | 297 | ||||
| -rw-r--r-- | lib/bt/controller/esp32/hli_api.h | 167 | ||||
| -rw-r--r-- | lib/bt/controller/esp32/hli_vectors.S | 267 |
5 files changed, 3028 insertions, 0 deletions
diff --git a/lib/bt/controller/esp32/Kconfig.in b/lib/bt/controller/esp32/Kconfig.in new file mode 100644 index 00000000..2d7cbf85 --- /dev/null +++ b/lib/bt/controller/esp32/Kconfig.in @@ -0,0 +1,446 @@ +choice BTDM_CTRL_MODE + prompt "Bluetooth controller mode (BR/EDR/BLE/DUALMODE)" + help + Specify the bluetooth controller mode (BR/EDR, BLE or dual mode). + + config BTDM_CTRL_MODE_BLE_ONLY + bool "BLE Only" + + config BTDM_CTRL_MODE_BR_EDR_ONLY + bool "BR/EDR Only" + + config BTDM_CTRL_MODE_BTDM + bool "Bluetooth Dual Mode" + +endchoice + +config BTDM_CTRL_BLE_MAX_CONN + int "BLE Max Connections" + depends on BTDM_CTRL_MODE_BLE_ONLY || BTDM_CTRL_MODE_BTDM + default 3 + range 1 9 + help + BLE maximum connections of bluetooth controller. + Each connection uses 1KB static DRAM whenever the BT controller is enabled. + +config BTDM_CTRL_BR_EDR_MAX_ACL_CONN + int "BR/EDR ACL Max Connections" + depends on BTDM_CTRL_MODE_BR_EDR_ONLY || BTDM_CTRL_MODE_BTDM + default 2 + range 1 7 + help + BR/EDR ACL maximum connections of bluetooth controller. + Each connection uses 1.2 KB DRAM whenever the BT controller is enabled. + +config BTDM_CTRL_BR_EDR_MAX_SYNC_CONN + int "BR/EDR Sync(SCO/eSCO) Max Connections" + depends on BTDM_CTRL_MODE_BR_EDR_ONLY || BTDM_CTRL_MODE_BTDM + default 0 + range 0 3 + help + BR/EDR Synchronize maximum connections of bluetooth controller. + Each connection uses 2 KB DRAM whenever the BT controller is enabled. + + + +choice BTDM_CTRL_BR_EDR_SCO_DATA_PATH + prompt "BR/EDR Sync(SCO/eSCO) default data path" + depends on BTDM_CTRL_MODE_BR_EDR_ONLY || BTDM_CTRL_MODE_BTDM + default BTDM_CTRL_BR_EDR_SCO_DATA_PATH_PCM + help + SCO data path, i.e. HCI or PCM. + SCO data can be sent/received through HCI synchronous packets, or the data + can be routed to on-chip PCM module on ESP32. PCM input/output signals can + be "matrixed" to GPIOs. The default data path can also be set using API + "esp_bredr_sco_datapath_set" + + config BTDM_CTRL_BR_EDR_SCO_DATA_PATH_HCI + bool "HCI" + config BTDM_CTRL_BR_EDR_SCO_DATA_PATH_PCM + bool "PCM" +endchoice + +config BTDM_CTRL_BR_EDR_SCO_DATA_PATH_EFF + int + default 0 if BTDM_CTRL_BR_EDR_SCO_DATA_PATH_HCI + default 1 if BTDM_CTRL_BR_EDR_SCO_DATA_PATH_PCM + default 0 + +menuconfig BTDM_CTRL_PCM_ROLE_EDGE_CONFIG + bool "PCM Signal Config (Role and Polar)" + depends on BTDM_CTRL_BR_EDR_SCO_DATA_PATH_PCM + default y + +choice BTDM_CTRL_PCM_ROLE + prompt "PCM Role" + depends on BTDM_CTRL_PCM_ROLE_EDGE_CONFIG + help + PCM role can be configured as PCM master or PCM slave + + config BTDM_CTRL_PCM_ROLE_MASTER + bool "PCM Master" + config BTDM_CTRL_PCM_ROLE_SLAVE + bool "PCM Slave" +endchoice + +choice BTDM_CTRL_PCM_POLAR + prompt "PCM Polar" + depends on BTDM_CTRL_PCM_ROLE_EDGE_CONFIG + help + PCM polarity can be configured as Falling Edge or Rising Edge + + config BTDM_CTRL_PCM_POLAR_FALLING_EDGE + bool "Falling Edge" + config BTDM_CTRL_PCM_POLAR_RISING_EDGE + bool "Rising Edge" +endchoice + +config BTDM_CTRL_PCM_ROLE_EFF + int + default 0 if BTDM_CTRL_PCM_ROLE_MASTER + default 1 if BTDM_CTRL_PCM_ROLE_SLAVE + default 0 + +config BTDM_CTRL_PCM_POLAR_EFF + int + default 0 if BTDM_CTRL_PCM_POLAR_FALLING_EDGE + default 1 if BTDM_CTRL_PCM_POLAR_RISING_EDGE + default 0 + +config BTDM_CTRL_AUTO_LATENCY + bool "Auto latency" + depends on BTDM_CTRL_MODE_BTDM + default n + help + BLE auto latency, used to enhance classic BT performance + while classic BT and BLE are enabled at the same time. + +config BTDM_CTRL_AUTO_LATENCY_EFF + bool + default BTDM_CTRL_AUTO_LATENCY if BTDM_CTRL_MODE_BTDM + default n + +config BTDM_CTRL_LEGACY_AUTH_VENDOR_EVT + bool "Legacy Authentication Vendor Specific Event Enable" + depends on BTDM_CTRL_MODE_BR_EDR_ONLY || BTDM_CTRL_MODE_BTDM + default y + help + To protect from BIAS attack during Legacy authentication, + Legacy authentication Vendor specific event should be enabled + +config BTDM_CTRL_LEGACY_AUTH_VENDOR_EVT_EFF + bool + default BTDM_CTRL_LEGACY_AUTH_VENDOR_EVT if BTDM_CTRL_MODE_BR_EDR_ONLY || BTDM_CTRL_MODE_BTDM + default 0 + + +config BTDM_CTRL_BLE_MAX_CONN_EFF + int + default BTDM_CTRL_BLE_MAX_CONN if BTDM_CTRL_MODE_BLE_ONLY || BTDM_CTRL_MODE_BTDM + default 0 + +config BTDM_CTRL_BR_EDR_MAX_ACL_CONN_EFF + int + default BTDM_CTRL_BR_EDR_MAX_ACL_CONN if BTDM_CTRL_MODE_BR_EDR_ONLY || BTDM_CTRL_MODE_BTDM + default 0 + +config BTDM_CTRL_BR_EDR_MAX_SYNC_CONN_EFF + int + default BTDM_CTRL_BR_EDR_MAX_SYNC_CONN if BTDM_CTRL_MODE_BR_EDR_ONLY || BTDM_CTRL_MODE_BTDM + default 0 + +choice BTDM_CTRL_PINNED_TO_CORE_CHOICE + prompt "The cpu core which bluetooth controller run" + depends on !FREERTOS_UNICORE + help + Specify the cpu core to run bluetooth controller. + Can not specify no-affinity. + + config BTDM_CTRL_PINNED_TO_CORE_0 + bool "Core 0 (PRO CPU)" + config BTDM_CTRL_PINNED_TO_CORE_1 + bool "Core 1 (APP CPU)" + depends on !FREERTOS_UNICORE +endchoice + +config BTDM_CTRL_PINNED_TO_CORE + int + default 0 if BTDM_CTRL_PINNED_TO_CORE_0 + default 1 if BTDM_CTRL_PINNED_TO_CORE_1 + default 0 + +choice BTDM_CTRL_HCI_MODE_CHOICE + prompt "HCI mode" + help + Speicify HCI mode as VHCI or UART(H4) + + config BTDM_CTRL_HCI_MODE_VHCI + bool "VHCI" + help + Normal option. Mostly, choose this VHCI when bluetooth host run on ESP32, too. + + config BTDM_CTRL_HCI_MODE_UART_H4 + bool "UART(H4)" + help + If use external bluetooth host which run on other hardware and use UART as the HCI interface, + choose this option. +endchoice + +menu "HCI UART(H4) Options" + visible if BTDM_CTRL_HCI_MODE_UART_H4 + + config BTDM_CTRL_HCI_UART_NO + int "UART Number for HCI" + depends on BTDM_CTRL_HCI_MODE_UART_H4 + range 1 2 + default 1 + help + Uart number for HCI. The available uart is UART1 and UART2. + + config BTDM_CTRL_HCI_UART_BAUDRATE + int "UART Baudrate for HCI" + depends on BTDM_CTRL_HCI_MODE_UART_H4 + range 115200 921600 + default 921600 + help + UART Baudrate for HCI. Please use standard baudrate. + + config BTDM_CTRL_HCI_UART_FLOW_CTRL_EN + bool "Enable UART flow control" + depends on BTDM_CTRL_HCI_MODE_UART_H4 + default y + +endmenu + +menu "MODEM SLEEP Options" + config BTDM_CTRL_MODEM_SLEEP + bool "Bluetooth modem sleep" + default y + help + Enable/disable bluetooth controller low power mode. + + choice BTDM_CTRL_MODEM_SLEEP_MODE + prompt "Bluetooth Modem sleep mode" + depends on BTDM_CTRL_MODEM_SLEEP + help + To select which strategy to use for modem sleep + + config BTDM_CTRL_MODEM_SLEEP_MODE_ORIG + bool "ORIG Mode(sleep with low power clock)" + help + ORIG mode is a bluetooth sleep mode that can be used for dual mode controller. In this mode, + bluetooth controller sleeps between BR/EDR frames and BLE events. A low power clock is used to + maintain bluetooth reference clock. + + config BTDM_CTRL_MODEM_SLEEP_MODE_EVED + bool "EVED Mode(For internal test only)" + help + EVED mode is for BLE only and is only for internal test. Do not use it for production. this + mode is not compatible with DFS nor light sleep + endchoice + + choice BTDM_CTRL_LOW_POWER_CLOCK + prompt "Bluetooth low power clock" + depends on BTDM_CTRL_MODEM_SLEEP_MODE_ORIG + help + Select the low power clock source for bluetooth controller. Bluetooth low power clock is + the clock source to maintain time in sleep mode. + + - "Main crystal" option provides good accuracy and can support Dynamic Frequency Scaling + to be used with Bluetooth modem sleep. Light sleep is not supported. + - "External 32kHz crystal" option allows user to use a 32.768kHz crystal as Bluetooth low + power clock. This option is allowed as long as External 32kHz crystal is configured as + the system RTC clock source. This option provides good accuracy and supports Bluetooth + modem sleep to be used alongside Dynamic Frequency Scaling or light sleep. + + config BTDM_CTRL_LPCLK_SEL_MAIN_XTAL + bool "Main crystal" + help + Main crystal can be used as low power clock for bluetooth modem sleep. If this option is + selected, bluetooth modem sleep can work under Dynamic Frequency Scaling(DFS) enabled, but + cannot work when light sleep is enabled. Main crystal has a good performance in accuracy as + the bluetooth low power clock source. + + config BTDM_CTRL_LPCLK_SEL_EXT_32K_XTAL + bool "External 32kHz crystal" + depends on RTC_CLK_SRC_EXT_CRYS + help + External 32kHz crystal has a nominal frequency of 32.768kHz and provides good frequency + stability. If used as Bluetooth low power clock, External 32kHz can support Bluetooth + modem sleep to be used with both DFS and light sleep. + endchoice + +endmenu + +choice BTDM_BLE_SLEEP_CLOCK_ACCURACY + prompt "BLE Sleep Clock Accuracy" + depends on BTDM_CTRL_MODE_BLE_ONLY || BTDM_CTRL_MODE_BTDM + default BTDM_BLE_DEFAULT_SCA_250PPM + help + BLE Sleep Clock Accuracy(SCA) for the local device is used to estimate window widening in BLE + connection events. With a lower level of clock accuracy(e.g. 500ppm over 250ppm), the slave + needs a larger RX window to synchronize with master in each anchor point, thus resulting in an + increase of power consumption but a higher level of robustness in keeping connected. According + to the requirements of Bluetooth Core specification 4.2, the worst-case accuracy of Classic + Bluetooth low power oscialltor(LPO) is +/-250ppm in STANDBY and in low power modes such as + sniff. For BLE the worst-case SCA is +/-500ppm. + + - "151ppm to 250ppm" option is the default value for Bluetooth Dual mode + - "251ppm to 500ppm" option can be used in BLE only mode when using external 32kHz crystal as + low power clock. This option is provided in case that BLE sleep clock has a lower level of + accuracy, or other error sources contribute to the inaccurate timing during sleep. + + config BTDM_BLE_DEFAULT_SCA_500PPM + bool "251ppm to 500ppm" + depends on BTDM_CTRL_LPCLK_SEL_EXT_32K_XTAL && BTDM_CTRL_MODE_BLE_ONLY + config BTDM_BLE_DEFAULT_SCA_250PPM + bool "151ppm to 250ppm" +endchoice +config BTDM_BLE_SLEEP_CLOCK_ACCURACY_INDEX_EFF + int + default 0 if BTDM_BLE_DEFAULT_SCA_500PPM + default 1 if BTDM_BLE_DEFAULT_SCA_250PPM + default 1 + +config BTDM_BLE_SCAN_DUPL + bool "BLE Scan Duplicate Options" + depends on (BTDM_CTRL_MODE_BTDM || BTDM_CTRL_MODE_BLE_ONLY) + default y + help + This select enables parameters setting of BLE scan duplicate. + +choice BTDM_SCAN_DUPL_TYPE + prompt "Scan Duplicate Type" + default BTDM_SCAN_DUPL_TYPE_DEVICE + depends on BTDM_BLE_SCAN_DUPL + help + Scan duplicate have three ways. one is "Scan Duplicate By Device Address", This way is to use + advertiser address filtering. The adv packet of the same address is only allowed to be reported once. + Another way is "Scan Duplicate By Device Address And Advertising Data". This way is to use advertising + data and device address filtering. All different adv packets with the same address are allowed to be + reported. The last way is "Scan Duplicate By Advertising Data". This way is to use advertising data + filtering. All same advertising data only allow to be reported once even though they are from + different devices. + + config BTDM_SCAN_DUPL_TYPE_DEVICE + bool "Scan Duplicate By Device Address" + help + This way is to use advertiser address filtering. The adv packet of the same address is only + allowed to be reported once + + config BTDM_SCAN_DUPL_TYPE_DATA + bool "Scan Duplicate By Advertising Data" + help + This way is to use advertising data filtering. All same advertising data only allow to be reported + once even though they are from different devices. + + config BTDM_SCAN_DUPL_TYPE_DATA_DEVICE + bool "Scan Duplicate By Device Address And Advertising Data" + help + This way is to use advertising data and device address filtering. All different adv packets with + the same address are allowed to be reported. +endchoice + +config BTDM_SCAN_DUPL_TYPE + int + depends on BTDM_BLE_SCAN_DUPL + default 0 if BTDM_SCAN_DUPL_TYPE_DEVICE + default 1 if BTDM_SCAN_DUPL_TYPE_DATA + default 2 if BTDM_SCAN_DUPL_TYPE_DATA_DEVICE + default 0 + +config BTDM_SCAN_DUPL_CACHE_SIZE + int "Maximum number of devices in scan duplicate filter" + depends on BTDM_BLE_SCAN_DUPL + range 10 1000 + default 100 + help + Maximum number of devices which can be recorded in scan duplicate filter. + When the maximum amount of device in the filter is reached, the oldest device will be refreshed. + +config BTDM_SCAN_DUPL_CACHE_REFRESH_PERIOD + int "Duplicate scan list refresh period (seconds)" + depends on BTDM_BLE_SCAN_DUPL + range 0 1000 + default 0 + help + If the period value is non-zero, the controller will periodically clear the device information + stored in the scan duuplicate filter. If it is 0, the scan duuplicate filter will not be cleared + until the scanning is disabled. Duplicate advertisements for this period should not be sent to the + Host in advertising report events. + There are two scenarios where the ADV packet will be repeatedly reported: + 1. The duplicate scan cache is full, the controller will delete the oldest device information and + add new device information. + 2. When the refresh period is up, the controller will clear all device information and start filtering + again. + +config BTDM_BLE_MESH_SCAN_DUPL_EN + bool "Special duplicate scan mechanism for BLE Mesh scan" + depends on BTDM_BLE_SCAN_DUPL + default n + help + This enables the BLE scan duplicate for special BLE Mesh scan. + +config BTDM_MESH_DUPL_SCAN_CACHE_SIZE + int "Maximum number of Mesh adv packets in scan duplicate filter" + depends on BTDM_BLE_MESH_SCAN_DUPL_EN + range 10 1000 + default 100 + help + Maximum number of adv packets which can be recorded in duplicate scan cache for BLE Mesh. + When the maximum amount of device in the filter is reached, the cache will be refreshed. + +config BTDM_CTRL_FULL_SCAN_SUPPORTED + bool "BLE full scan feature supported" + depends on BTDM_CTRL_MODE_BLE_ONLY || BTDM_CTRL_MODE_BTDM + default y + help + The full scan function is mainly used to provide BLE scan performance. + This is required for scenes with high scan performance requirements, such as BLE Mesh scenes. + +config BTDM_BLE_ADV_REPORT_FLOW_CTRL_SUPP + bool "BLE adv report flow control supported" + depends on (BTDM_CTRL_MODE_BTDM || BTDM_CTRL_MODE_BLE_ONLY) + default y + help + The function is mainly used to enable flow control for advertising reports. When it is enabled, + advertising reports will be discarded by the controller if the number of unprocessed advertising + reports exceeds the size of BLE adv report flow control. + +config BTDM_BLE_ADV_REPORT_FLOW_CTRL_NUM + int "BLE adv report flow control number" + depends on BTDM_BLE_ADV_REPORT_FLOW_CTRL_SUPP + range 50 1000 + default 100 + help + The number of unprocessed advertising report that Bluedroid can save.If you set + `BTDM_BLE_ADV_REPORT_FLOW_CTRL_NUM` to a small value, this may cause adv packets lost. + If you set `BTDM_BLE_ADV_REPORT_FLOW_CTRL_NUM` to a large value, Bluedroid may cache a + lot of adv packets and this may cause system memory run out. For example, if you set + it to 50, the maximum memory consumed by host is 35 * 50 bytes. Please set + `BTDM_BLE_ADV_REPORT_FLOW_CTRL_NUM` according to your system free memory and handle adv + packets as fast as possible, otherwise it will cause adv packets lost. + +config BTDM_BLE_ADV_REPORT_DISCARD_THRSHOLD + int "BLE adv lost event threshold value" + depends on BTDM_BLE_ADV_REPORT_FLOW_CTRL_SUPP + range 1 1000 + default 20 + help + When adv report flow control is enabled, The ADV lost event will be generated when the number + of ADV packets lost in the controller reaches this threshold. It is better to set a larger value. + If you set `BTDM_BLE_ADV_REPORT_DISCARD_THRSHOLD` to a small value or printf every adv lost event, it + may cause adv packets lost more. + + +config BTDM_RESERVE_DRAM + hex + default 0xdb5c if BT_ENABLED + default 0 + +config BTDM_CTRL_HLI + bool "High level interrupt" + depends on BT_ENABLED + default y + help + Using Level 4 interrupt for Bluetooth. diff --git a/lib/bt/controller/esp32/bt.c b/lib/bt/controller/esp32/bt.c new file mode 100644 index 00000000..d8792e0f --- /dev/null +++ b/lib/bt/controller/esp32/bt.c @@ -0,0 +1,1851 @@ +/* + * SPDX-FileCopyrightText: 2015-2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include <stddef.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> + +#include "sdkconfig.h" +#include "esp_heap_caps.h" +#include "esp_heap_caps_init.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/queue.h" +#include "freertos/semphr.h" +#include "freertos/portmacro.h" +#include "xtensa_api.h" // Replace with interrupt allocator API (IDF-3891) +#include "xtensa/core-macros.h" +#include "esp_types.h" +#include "esp_mac.h" +#include "esp_random.h" +#include "esp_task.h" +#include "esp_intr_alloc.h" +#include "esp_attr.h" +#include "esp_phy_init.h" +#include "esp_bt.h" +#include "esp_err.h" +#include "esp_log.h" +#include "esp_pm.h" +#include "esp_private/esp_clk.h" +#include "esp_private/periph_ctrl.h" +#include "soc/rtc.h" +#include "soc/soc_memory_layout.h" +#include "soc/dport_reg.h" +#include "private/esp_coexist_internal.h" +#include "esp_timer.h" +#if !CONFIG_FREERTOS_UNICORE +#include "esp_ipc.h" +#endif + +#include "esp_rom_sys.h" +#include "hli_api.h" + +#if CONFIG_BT_ENABLED + +/* Macro definition + ************************************************************************ + */ + +#define UNUSED(x) (void)(x) +#define BTDM_LOG_TAG "BTDM_INIT" + +#define BTDM_INIT_PERIOD (5000) /* ms */ + +/* Bluetooth system and controller config */ +#define BTDM_CFG_BT_DATA_RELEASE (1<<0) +#define BTDM_CFG_HCI_UART (1<<1) +#define BTDM_CFG_CONTROLLER_RUN_APP_CPU (1<<2) +#define BTDM_CFG_SCAN_DUPLICATE_OPTIONS (1<<3) +#define BTDM_CFG_SEND_ADV_RESERVED_SIZE (1<<4) +#define BTDM_CFG_BLE_FULL_SCAN_SUPPORTED (1<<5) + +/* Sleep mode */ +#define BTDM_MODEM_SLEEP_MODE_NONE (0) +#define BTDM_MODEM_SLEEP_MODE_ORIG (1) +#define BTDM_MODEM_SLEEP_MODE_EVED (2) // sleep mode for BLE controller, used only for internal test. + +/* Low Power Clock Selection */ +#define BTDM_LPCLK_SEL_XTAL (0) +#define BTDM_LPCLK_SEL_XTAL32K (1) +#define BTDM_LPCLK_SEL_RTC_SLOW (2) +#define BTDM_LPCLK_SEL_8M (3) + +/* Sleep and wakeup interval control */ +#define BTDM_MIN_SLEEP_DURATION (12) // threshold of interval in slots to allow to fall into modem sleep +#define BTDM_MODEM_WAKE_UP_DELAY (4) // delay in slots of modem wake up procedure, including re-enable PHY/RF + +#define BT_DEBUG(...) +#define BT_API_CALL_CHECK(info, api_call, ret) \ +do{\ + esp_err_t __err = (api_call);\ + if ((ret) != __err) {\ + BT_DEBUG("%s %d %s ret=0x%X\n", __FUNCTION__, __LINE__, (info), __err);\ + return __err;\ + }\ +} while(0) + +#define OSI_FUNCS_TIME_BLOCKING 0xffffffff +#define OSI_VERSION 0x00010004 +#define OSI_MAGIC_VALUE 0xFADEBEAD + +/* Types definition + ************************************************************************ + */ + +/* VHCI function interface */ +typedef struct vhci_host_callback { + void (*notify_host_send_available)(void); /*!< callback used to notify that the host can send packet to controller */ + int (*notify_host_recv)(uint8_t *data, uint16_t len); /*!< callback used to notify that the controller has a packet to send to the host*/ +} vhci_host_callback_t; + +/* Dram region */ +typedef struct { + esp_bt_mode_t mode; + intptr_t start; + intptr_t end; +} btdm_dram_available_region_t; + +typedef struct { + void *handle; +} btdm_queue_item_t; + +/* OSI function */ +struct osi_funcs_t { + uint32_t _version; + xt_handler (*_set_isr)(int n, xt_handler f, void *arg); + void (*_ints_on)(unsigned int mask); + void (*_interrupt_disable)(void); + void (*_interrupt_restore)(void); + void (*_task_yield)(void); + void (*_task_yield_from_isr)(void); + void *(*_semphr_create)(uint32_t max, uint32_t init); + void (*_semphr_delete)(void *semphr); + int32_t (*_semphr_take_from_isr)(void *semphr, void *hptw); + int32_t (*_semphr_give_from_isr)(void *semphr, void *hptw); + int32_t (*_semphr_take)(void *semphr, uint32_t block_time_ms); + int32_t (*_semphr_give)(void *semphr); + void *(*_mutex_create)(void); + void (*_mutex_delete)(void *mutex); + int32_t (*_mutex_lock)(void *mutex); + int32_t (*_mutex_unlock)(void *mutex); + void *(* _queue_create)(uint32_t queue_len, uint32_t item_size); + void (* _queue_delete)(void *queue); + int32_t (* _queue_send)(void *queue, void *item, uint32_t block_time_ms); + int32_t (* _queue_send_from_isr)(void *queue, void *item, void *hptw); + int32_t (* _queue_recv)(void *queue, void *item, uint32_t block_time_ms); + int32_t (* _queue_recv_from_isr)(void *queue, void *item, void *hptw); + int32_t (* _task_create)(void *task_func, const char *name, uint32_t stack_depth, void *param, uint32_t prio, void *task_handle, uint32_t core_id); + void (* _task_delete)(void *task_handle); + bool (* _is_in_isr)(void); + int (* _cause_sw_intr_to_core)(int core_id, int intr_no); + void *(* _malloc)(size_t size); + void *(* _malloc_internal)(size_t size); + void (* _free)(void *p); + int32_t (* _read_efuse_mac)(uint8_t mac[6]); + void (* _srand)(unsigned int seed); + int (* _rand)(void); + uint32_t (* _btdm_lpcycles_2_us)(uint32_t cycles); + uint32_t (* _btdm_us_2_lpcycles)(uint32_t us); + bool (* _btdm_sleep_check_duration)(uint32_t *slot_cnt); + void (* _btdm_sleep_enter_phase1)(uint32_t lpcycles); /* called when interrupt is disabled */ + void (* _btdm_sleep_enter_phase2)(void); + void (* _btdm_sleep_exit_phase1)(void); /* called from ISR */ + void (* _btdm_sleep_exit_phase2)(void); /* called from ISR */ + void (* _btdm_sleep_exit_phase3)(void); /* called from task */ + bool (* _coex_bt_wakeup_request)(void); + void (* _coex_bt_wakeup_request_end)(void); + int (* _coex_bt_request)(uint32_t event, uint32_t latency, uint32_t duration); + int (* _coex_bt_release)(uint32_t event); + int (* _coex_register_bt_cb)(coex_func_cb_t cb); + uint32_t (* _coex_bb_reset_lock)(void); + void (* _coex_bb_reset_unlock)(uint32_t restore); + int (* _coex_schm_register_btdm_callback)(void *callback); + void (* _coex_schm_status_bit_clear)(uint32_t type, uint32_t status); + void (* _coex_schm_status_bit_set)(uint32_t type, uint32_t status); + uint32_t (* _coex_schm_interval_get)(void); + uint8_t (* _coex_schm_curr_period_get)(void); + void *(* _coex_schm_curr_phase_get)(void); + int (* _coex_wifi_channel_get)(uint8_t *primary, uint8_t *secondary); + int (* _coex_register_wifi_channel_change_callback)(void *cb); + xt_handler (*_set_isr_l3)(int n, xt_handler f, void *arg); + void (*_interrupt_l3_disable)(void); + void (*_interrupt_l3_restore)(void); + void *(* _customer_queue_create)(uint32_t queue_len, uint32_t item_size); + int (* _coex_version_get)(unsigned int *major, unsigned int *minor, unsigned int *patch); + uint32_t _magic; +}; + +typedef void (*workitem_handler_t)(void* arg); + +/* External functions or values + ************************************************************************ + */ + +/* not for user call, so don't put to include file */ +/* OSI */ +extern int btdm_osi_funcs_register(void *osi_funcs); +/* Initialise and De-initialise */ +extern int btdm_controller_init(uint32_t config_mask, esp_bt_controller_config_t *config_opts); +extern void btdm_controller_deinit(void); +extern int btdm_controller_enable(esp_bt_mode_t mode); +extern void btdm_controller_disable(void); +extern uint8_t btdm_controller_get_mode(void); +extern const char *btdm_controller_get_compile_version(void); +extern void btdm_rf_bb_init_phase2(void); // shall be called after PHY/RF is enabled +extern int btdm_dispatch_work_to_controller(workitem_handler_t callback, void *arg, bool blocking); +/* Sleep */ +extern void btdm_controller_enable_sleep(bool enable); +extern void btdm_controller_set_sleep_mode(uint8_t mode); +extern uint8_t btdm_controller_get_sleep_mode(void); +extern bool btdm_power_state_active(void); +extern void btdm_wakeup_request(void); +extern void btdm_in_wakeup_requesting_set(bool in_wakeup_requesting); +/* Low Power Clock */ +extern bool btdm_lpclk_select_src(uint32_t sel); +extern bool btdm_lpclk_set_div(uint32_t div); +/* VHCI */ +extern bool API_vhci_host_check_send_available(void); +extern void API_vhci_host_send_packet(uint8_t *data, uint16_t len); +extern int API_vhci_host_register_callback(const vhci_host_callback_t *callback); +/* TX power */ +extern int ble_txpwr_set(int power_type, int power_level); +extern int ble_txpwr_get(int power_type); +extern int bredr_txpwr_set(int min_power_level, int max_power_level); +extern int bredr_txpwr_get(int *min_power_level, int *max_power_level); +extern void bredr_sco_datapath_set(uint8_t data_path); +extern void btdm_controller_scan_duplicate_list_clear(void); +/* Shutdown */ +extern void esp_bt_controller_shutdown(void); +extern void sdk_config_set_bt_pll_track_enable(bool enable); +extern void sdk_config_set_uart_flow_ctrl_enable(bool enable); + +extern char _bss_start_btdm; +extern char _bss_end_btdm; +extern char _data_start_btdm; +extern char _data_end_btdm; +extern uint32_t _data_start_btdm_rom; +extern uint32_t _data_end_btdm_rom; + +extern uint32_t _bt_bss_start; +extern uint32_t _bt_bss_end; +extern uint32_t _nimble_bss_start; +extern uint32_t _nimble_bss_end; +extern uint32_t _btdm_bss_start; +extern uint32_t _btdm_bss_end; +extern uint32_t _bt_data_start; +extern uint32_t _bt_data_end; +extern uint32_t _nimble_data_start; +extern uint32_t _nimble_data_end; +extern uint32_t _btdm_data_start; +extern uint32_t _btdm_data_end; + +/* Local Function Declare + ********************************************************************* + */ +#if CONFIG_BTDM_CTRL_HLI +static xt_handler set_isr_hlevel_wrapper(int n, xt_handler f, void *arg); +static void interrupt_hlevel_disable(void); +static void interrupt_hlevel_restore(void); +#endif /* CONFIG_BTDM_CTRL_HLI */ +static void task_yield(void); +static void task_yield_from_isr(void); +static void *semphr_create_wrapper(uint32_t max, uint32_t init); +static void semphr_delete_wrapper(void *semphr); +static int32_t semphr_take_from_isr_wrapper(void *semphr, void *hptw); +static int32_t semphr_give_from_isr_wrapper(void *semphr, void *hptw); +static int32_t semphr_take_wrapper(void *semphr, uint32_t block_time_ms); +static int32_t semphr_give_wrapper(void *semphr); +static void *mutex_create_wrapper(void); +static void mutex_delete_wrapper(void *mutex); +static int32_t mutex_lock_wrapper(void *mutex); +static int32_t mutex_unlock_wrapper(void *mutex); +#if CONFIG_BTDM_CTRL_HLI +static void *queue_create_hlevel_wrapper(uint32_t queue_len, uint32_t item_size); +static void queue_delete_hlevel_wrapper(void *queue); +static int32_t queue_send_hlevel_wrapper(void *queue, void *item, uint32_t block_time_ms); +static int32_t queue_send_from_isr_hlevel_wrapper(void *queue, void *item, void *hptw); +static int32_t queue_recv_hlevel_wrapper(void *queue, void *item, uint32_t block_time_ms); +static int32_t queue_recv_from_isr_hlevel_wrapper(void *queue, void *item, void *hptw); +#else +static void *queue_create_wrapper(uint32_t queue_len, uint32_t item_size); +static void queue_delete_wrapper(void *queue); +static int32_t queue_send_wrapper(void *queue, void *item, uint32_t block_time_ms); +static int32_t queue_send_from_isr_wrapper(void *queue, void *item, void *hptw); +static int32_t queue_recv_wrapper(void *queue, void *item, uint32_t block_time_ms); +static int32_t queue_recv_from_isr_wrapper(void *queue, void *item, void *hptw); +#endif /* CONFIG_BTDM_CTRL_HLI */ +static int32_t task_create_wrapper(void *task_func, const char *name, uint32_t stack_depth, void *param, uint32_t prio, void *task_handle, uint32_t core_id); +static void task_delete_wrapper(void *task_handle); +static bool is_in_isr_wrapper(void); +static void cause_sw_intr(void *arg); +static int cause_sw_intr_to_core_wrapper(int core_id, int intr_no); +static void *malloc_internal_wrapper(size_t size); +static int32_t read_mac_wrapper(uint8_t mac[6]); +static void srand_wrapper(unsigned int seed); +static int rand_wrapper(void); +static uint32_t btdm_lpcycles_2_us(uint32_t cycles); +static uint32_t btdm_us_2_lpcycles(uint32_t us); +static bool btdm_sleep_check_duration(uint32_t *slot_cnt); +static void btdm_sleep_enter_phase1_wrapper(uint32_t lpcycles); +static void btdm_sleep_enter_phase2_wrapper(void); +static void btdm_sleep_exit_phase3_wrapper(void); +static bool coex_bt_wakeup_request(void); +static void coex_bt_wakeup_request_end(void); +static int coex_bt_request_wrapper(uint32_t event, uint32_t latency, uint32_t duration); +static int coex_bt_release_wrapper(uint32_t event); +static int coex_register_bt_cb_wrapper(coex_func_cb_t cb); +static uint32_t coex_bb_reset_lock_wrapper(void); +static void coex_bb_reset_unlock_wrapper(uint32_t restore); +static int coex_schm_register_btdm_callback_wrapper(void *callback); +static void coex_schm_status_bit_clear_wrapper(uint32_t type, uint32_t status); +static void coex_schm_status_bit_set_wrapper(uint32_t type, uint32_t status); +static uint32_t coex_schm_interval_get_wrapper(void); +static uint8_t coex_schm_curr_period_get_wrapper(void); +static void * coex_schm_curr_phase_get_wrapper(void); +static int coex_wifi_channel_get_wrapper(uint8_t *primary, uint8_t *secondary); +static int coex_register_wifi_channel_change_callback_wrapper(void *cb); +static int coex_version_get_wrapper(unsigned int *major, unsigned int *minor, unsigned int *patch); +#if CONFIG_BTDM_CTRL_HLI +static void *customer_queue_create_hlevel_wrapper(uint32_t queue_len, uint32_t item_size); +#endif /* CONFIG_BTDM_CTRL_HLI */ +static void interrupt_l3_disable(void); +static void interrupt_l3_restore(void); +static void bt_controller_deinit_internal(void); + +/* Local variable definition + *************************************************************************** + */ +/* OSI funcs */ +static const struct osi_funcs_t osi_funcs_ro = { + ._version = OSI_VERSION, +#if CONFIG_BTDM_CTRL_HLI + ._set_isr = set_isr_hlevel_wrapper, + ._ints_on = xt_ints_on, + ._interrupt_disable = interrupt_hlevel_disable, + ._interrupt_restore = interrupt_hlevel_restore, +#else + ._set_isr = xt_set_interrupt_handler, + ._ints_on = xt_ints_on, + ._interrupt_disable = interrupt_l3_disable, + ._interrupt_restore = interrupt_l3_restore, +#endif /* CONFIG_BTDM_CTRL_HLI */ + ._task_yield = task_yield, + ._task_yield_from_isr = task_yield_from_isr, + ._semphr_create = semphr_create_wrapper, + ._semphr_delete = semphr_delete_wrapper, + ._semphr_take_from_isr = semphr_take_from_isr_wrapper, + ._semphr_give_from_isr = semphr_give_from_isr_wrapper, + ._semphr_take = semphr_take_wrapper, + ._semphr_give = semphr_give_wrapper, + ._mutex_create = mutex_create_wrapper, + ._mutex_delete = mutex_delete_wrapper, + ._mutex_lock = mutex_lock_wrapper, + ._mutex_unlock = mutex_unlock_wrapper, +#if CONFIG_BTDM_CTRL_HLI + ._queue_create = queue_create_hlevel_wrapper, + ._queue_delete = queue_delete_hlevel_wrapper, + ._queue_send = queue_send_hlevel_wrapper, + ._queue_send_from_isr = queue_send_from_isr_hlevel_wrapper, + ._queue_recv = queue_recv_hlevel_wrapper, + ._queue_recv_from_isr = queue_recv_from_isr_hlevel_wrapper, +#else + ._queue_create = queue_create_wrapper, + ._queue_delete = queue_delete_wrapper, + ._queue_send = queue_send_wrapper, + ._queue_send_from_isr = queue_send_from_isr_wrapper, + ._queue_recv = queue_recv_wrapper, + ._queue_recv_from_isr = queue_recv_from_isr_wrapper, +#endif /* CONFIG_BTDM_CTRL_HLI */ + ._task_create = task_create_wrapper, + ._task_delete = task_delete_wrapper, + ._is_in_isr = is_in_isr_wrapper, + ._cause_sw_intr_to_core = cause_sw_intr_to_core_wrapper, + ._malloc = malloc, + ._malloc_internal = malloc_internal_wrapper, + ._free = free, + ._read_efuse_mac = read_mac_wrapper, + ._srand = srand_wrapper, + ._rand = rand_wrapper, + ._btdm_lpcycles_2_us = btdm_lpcycles_2_us, + ._btdm_us_2_lpcycles = btdm_us_2_lpcycles, + ._btdm_sleep_check_duration = btdm_sleep_check_duration, + ._btdm_sleep_enter_phase1 = btdm_sleep_enter_phase1_wrapper, + ._btdm_sleep_enter_phase2 = btdm_sleep_enter_phase2_wrapper, + ._btdm_sleep_exit_phase1 = NULL, + ._btdm_sleep_exit_phase2 = NULL, + ._btdm_sleep_exit_phase3 = btdm_sleep_exit_phase3_wrapper, + ._coex_bt_wakeup_request = coex_bt_wakeup_request, + ._coex_bt_wakeup_request_end = coex_bt_wakeup_request_end, + ._coex_bt_request = coex_bt_request_wrapper, + ._coex_bt_release = coex_bt_release_wrapper, + ._coex_register_bt_cb = coex_register_bt_cb_wrapper, + ._coex_bb_reset_lock = coex_bb_reset_lock_wrapper, + ._coex_bb_reset_unlock = coex_bb_reset_unlock_wrapper, + ._coex_schm_register_btdm_callback = coex_schm_register_btdm_callback_wrapper, + ._coex_schm_status_bit_clear = coex_schm_status_bit_clear_wrapper, + ._coex_schm_status_bit_set = coex_schm_status_bit_set_wrapper, + ._coex_schm_interval_get = coex_schm_interval_get_wrapper, + ._coex_schm_curr_period_get = coex_schm_curr_period_get_wrapper, + ._coex_schm_curr_phase_get = coex_schm_curr_phase_get_wrapper, + ._coex_wifi_channel_get = coex_wifi_channel_get_wrapper, + ._coex_register_wifi_channel_change_callback = coex_register_wifi_channel_change_callback_wrapper, + ._set_isr_l3 = xt_set_interrupt_handler, + ._interrupt_l3_disable = interrupt_l3_disable, + ._interrupt_l3_restore = interrupt_l3_restore, +#if CONFIG_BTDM_CTRL_HLI + ._customer_queue_create = customer_queue_create_hlevel_wrapper, +#else + ._customer_queue_create = NULL, +#endif /* CONFIG_BTDM_CTRL_HLI */ + ._coex_version_get = coex_version_get_wrapper, + ._magic = OSI_MAGIC_VALUE, +}; + +/* the mode column will be modified by release function to indicate the available region */ +static btdm_dram_available_region_t btdm_dram_available_region[] = { + //following is .data + {ESP_BT_MODE_BTDM, SOC_MEM_BT_DATA_START, SOC_MEM_BT_DATA_END }, + //following is memory which HW will use + {ESP_BT_MODE_BTDM, SOC_MEM_BT_EM_BTDM0_START, SOC_MEM_BT_EM_BTDM0_END }, + {ESP_BT_MODE_BLE, SOC_MEM_BT_EM_BLE_START, SOC_MEM_BT_EM_BLE_END }, + {ESP_BT_MODE_BTDM, SOC_MEM_BT_EM_BTDM1_START, SOC_MEM_BT_EM_BTDM1_END }, + {ESP_BT_MODE_CLASSIC_BT, SOC_MEM_BT_EM_BREDR_START, SOC_MEM_BT_EM_BREDR_REAL_END}, + //following is .bss + {ESP_BT_MODE_BTDM, SOC_MEM_BT_BSS_START, SOC_MEM_BT_BSS_END }, + {ESP_BT_MODE_BTDM, SOC_MEM_BT_MISC_START, SOC_MEM_BT_MISC_END }, +}; + +/* Reserve the full memory region used by Bluetooth Controller, + * some may be released later at runtime. */ +SOC_RESERVE_MEMORY_REGION(SOC_MEM_BT_EM_START, SOC_MEM_BT_EM_BREDR_REAL_END, rom_bt_em); +SOC_RESERVE_MEMORY_REGION(SOC_MEM_BT_BSS_START, SOC_MEM_BT_BSS_END, rom_bt_bss); +SOC_RESERVE_MEMORY_REGION(SOC_MEM_BT_MISC_START, SOC_MEM_BT_MISC_END, rom_bt_misc); +SOC_RESERVE_MEMORY_REGION(SOC_MEM_BT_DATA_START, SOC_MEM_BT_DATA_END, rom_bt_data); + +static DRAM_ATTR struct osi_funcs_t *osi_funcs_p; + +/* Static variable declare */ +// timestamp when PHY/RF was switched on +static DRAM_ATTR int64_t s_time_phy_rf_just_enabled = 0; +static DRAM_ATTR esp_bt_controller_status_t btdm_controller_status = ESP_BT_CONTROLLER_STATUS_IDLE; + +static DRAM_ATTR portMUX_TYPE global_int_mux = portMUX_INITIALIZER_UNLOCKED; + +// measured average low power clock period in micro seconds +static DRAM_ATTR uint32_t btdm_lpcycle_us = 0; +static DRAM_ATTR uint8_t btdm_lpcycle_us_frac = 0; // number of fractional bit for btdm_lpcycle_us + +#if CONFIG_BTDM_CTRL_MODEM_SLEEP_MODE_ORIG +// used low power clock +static DRAM_ATTR uint8_t btdm_lpclk_sel; +#endif /* #ifdef CONFIG_BTDM_CTRL_MODEM_SLEEP_MODE_ORIG */ + +static DRAM_ATTR QueueHandle_t s_wakeup_req_sem = NULL; +#ifdef CONFIG_PM_ENABLE +static DRAM_ATTR esp_timer_handle_t s_btdm_slp_tmr; +static DRAM_ATTR esp_pm_lock_handle_t s_pm_lock; +static bool s_pm_lock_acquired = true; +static DRAM_ATTR bool s_btdm_allow_light_sleep; +// pm_lock to prevent light sleep when using main crystal as Bluetooth low power clock +static DRAM_ATTR esp_pm_lock_handle_t s_light_sleep_pm_lock; +static void btdm_slp_tmr_callback(void *arg); +#endif /* #ifdef CONFIG_PM_ENABLE */ + + +static inline void esp_bt_power_domain_on(void) +{ + // Bluetooth module power up + esp_wifi_bt_power_domain_on(); +} + +static inline void esp_bt_power_domain_off(void) +{ + // Bluetooth module power down + esp_wifi_bt_power_domain_off(); +} + +static inline void btdm_check_and_init_bb(void) +{ + /* init BT-BB if PHY/RF has been switched off since last BT-BB init */ + int64_t latest_ts = esp_phy_rf_get_on_ts(); + if (latest_ts != s_time_phy_rf_just_enabled || + s_time_phy_rf_just_enabled == 0) { + btdm_rf_bb_init_phase2(); + s_time_phy_rf_just_enabled = latest_ts; + } +} + +#if CONFIG_BTDM_CTRL_HLI +struct interrupt_hlevel_cb{ + uint32_t status; + uint8_t nested; +}; + +static DRAM_ATTR struct interrupt_hlevel_cb hli_cb = { + .status = 0, + .nested = 0, +}; + +static xt_handler set_isr_hlevel_wrapper(int mask, xt_handler f, void *arg) +{ + esp_err_t err = hli_intr_register((intr_handler_t) f, arg, DPORT_PRO_INTR_STATUS_0_REG, mask); + if (err == ESP_OK) { + return f; + } else { + return 0; + } + } + +static void IRAM_ATTR interrupt_hlevel_disable(void) +{ + assert(xPortGetCoreID() == CONFIG_BTDM_CTRL_PINNED_TO_CORE); + assert(hli_cb.nested != UCHAR_MAX); + uint32_t status = hli_intr_disable(); + if (hli_cb.nested++ == 0) { + hli_cb.status = status; + } +} + +static void IRAM_ATTR interrupt_hlevel_restore(void) +{ + assert(xPortGetCoreID() == CONFIG_BTDM_CTRL_PINNED_TO_CORE); + assert(hli_cb.nested > 0); + if (--hli_cb.nested == 0) { + hli_intr_restore(hli_cb.status); + } +} +#endif /* CONFIG_BTDM_CTRL_HLI */ + +static void IRAM_ATTR interrupt_l3_disable(void) +{ + if (xPortInIsrContext()) { + portENTER_CRITICAL_ISR(&global_int_mux); + } else { + portENTER_CRITICAL(&global_int_mux); + } +} + +static void IRAM_ATTR interrupt_l3_restore(void) +{ + if (xPortInIsrContext()) { + portEXIT_CRITICAL_ISR(&global_int_mux); + } else { + portEXIT_CRITICAL(&global_int_mux); + } +} + +static void IRAM_ATTR task_yield(void) +{ + vPortYield(); +} + + +static void IRAM_ATTR task_yield_from_isr(void) +{ + portYIELD_FROM_ISR(); +} + +static void *semphr_create_wrapper(uint32_t max, uint32_t init) +{ + btdm_queue_item_t *semphr = heap_caps_calloc(1, sizeof(btdm_queue_item_t), MALLOC_CAP_8BIT|MALLOC_CAP_INTERNAL); + assert(semphr); + + void *handle = NULL; + + /* IDF FreeRTOS guarantees that all dynamic memory allocation goes to internal RAM. */ + handle = (void *)xSemaphoreCreateCounting(max, init); + assert(handle); + +#if CONFIG_BTDM_CTRL_HLI + SemaphoreHandle_t downstream_semaphore = handle; + assert(downstream_semaphore); + hli_queue_handle_t s_semaphore = hli_semaphore_create(max, downstream_semaphore); + assert(s_semaphore); + semphr->handle = (void *)s_semaphore; +#else + semphr->handle = handle; +#endif /* CONFIG_BTDM_CTRL_HLI */ + + return semphr; +} + +static void semphr_delete_wrapper(void *semphr) +{ + if (semphr == NULL) { + return; + } + + btdm_queue_item_t *semphr_item = (btdm_queue_item_t *)semphr; + void *handle = NULL; +#if CONFIG_BTDM_CTRL_HLI + if (semphr_item->handle) { + handle = ((hli_queue_handle_t)(semphr_item->handle))->downstream; + hli_queue_delete((hli_queue_handle_t)(semphr_item->handle)); + } +#else + handle = semphr_item->handle; +#endif /* CONFIG_BTDM_CTRL_HLI */ + + if (handle) { + vSemaphoreDelete(handle); + } + + free(semphr); +} + +static int32_t IRAM_ATTR semphr_take_from_isr_wrapper(void *semphr, void *hptw) +{ +#if CONFIG_BTDM_CTRL_HLI + // Not support it + assert(0); + return 0; +#else + void *handle = ((btdm_queue_item_t *)semphr)->handle; + return (int32_t)xSemaphoreTakeFromISR(handle, hptw); +#endif /* CONFIG_BTDM_CTRL_HLI */ +} + +static int32_t IRAM_ATTR semphr_give_from_isr_wrapper(void *semphr, void *hptw) +{ + void *handle = ((btdm_queue_item_t *)semphr)->handle; +#if CONFIG_BTDM_CTRL_HLI + UNUSED(hptw); + assert(xPortGetCoreID() == CONFIG_BTDM_CTRL_PINNED_TO_CORE); + return hli_semaphore_give(handle); +#else + return (int32_t)xSemaphoreGiveFromISR(handle, hptw); +#endif /* CONFIG_BTDM_CTRL_HLI */ +} + +static int32_t semphr_take_wrapper(void *semphr, uint32_t block_time_ms) +{ + bool ret; + void *handle = ((btdm_queue_item_t *)semphr)->handle; +#if CONFIG_BTDM_CTRL_HLI + if (block_time_ms == OSI_FUNCS_TIME_BLOCKING) { + ret = xSemaphoreTake(((hli_queue_handle_t)handle)->downstream, portMAX_DELAY); + } else { + ret = xSemaphoreTake(((hli_queue_handle_t)handle)->downstream, block_time_ms / portTICK_PERIOD_MS); + } +#else + if (block_time_ms == OSI_FUNCS_TIME_BLOCKING) { + ret = xSemaphoreTake(handle, portMAX_DELAY); + } else { + ret = xSemaphoreTake(handle, block_time_ms / portTICK_PERIOD_MS); + } +#endif /* CONFIG_BTDM_CTRL_HLI */ + return (int32_t)ret; +} + +static int32_t semphr_give_wrapper(void *semphr) +{ + void *handle = ((btdm_queue_item_t *)semphr)->handle; +#if CONFIG_BTDM_CTRL_HLI + return (int32_t)xSemaphoreGive(((hli_queue_handle_t)handle)->downstream); +#else + return (int32_t)xSemaphoreGive(handle); +#endif /* CONFIG_BTDM_CTRL_HLI */ +} + +static void *mutex_create_wrapper(void) +{ + return (void *)xSemaphoreCreateMutex(); +} + +static void mutex_delete_wrapper(void *mutex) +{ + vSemaphoreDelete(mutex); +} + +static int32_t mutex_lock_wrapper(void *mutex) +{ + return (int32_t)xSemaphoreTake(mutex, portMAX_DELAY); +} + +static int32_t mutex_unlock_wrapper(void *mutex) +{ + return (int32_t)xSemaphoreGive(mutex); +} + +static void *queue_create_wrapper(uint32_t queue_len, uint32_t item_size) +{ + btdm_queue_item_t *queue = NULL; + + queue = (btdm_queue_item_t*)heap_caps_malloc(sizeof(btdm_queue_item_t), MALLOC_CAP_INTERNAL|MALLOC_CAP_8BIT); + assert(queue); + + /* IDF FreeRTOS guarantees that all dynamic memory allocation goes to internal RAM. */ + queue->handle = xQueueCreate( queue_len, item_size); + assert(queue->handle); + + return queue; +} + +static void queue_delete_wrapper(void *queue) +{ + btdm_queue_item_t *queue_item = (btdm_queue_item_t *)queue; + if (queue_item) { + if(queue_item->handle){ + vQueueDelete(queue_item->handle); + } + free(queue_item); + } +} + +#if CONFIG_BTDM_CTRL_HLI +static void *queue_create_hlevel_wrapper(uint32_t queue_len, uint32_t item_size) +{ + btdm_queue_item_t *queue_item = queue_create_wrapper(queue_len, item_size); + assert(queue_item); + QueueHandle_t downstream_queue = queue_item->handle; + assert(queue_item->handle); + hli_queue_handle_t queue = hli_queue_create(queue_len, item_size, downstream_queue); + assert(queue); + queue_item->handle = queue; + return (void *)queue_item; +} + +static void *customer_queue_create_hlevel_wrapper(uint32_t queue_len, uint32_t item_size) +{ + btdm_queue_item_t *queue_item = queue_create_wrapper(queue_len, item_size); + assert(queue_item); + QueueHandle_t downstream_queue = queue_item->handle; + assert(queue_item->handle); + hli_queue_handle_t queue = hli_customer_queue_create(queue_len, item_size, downstream_queue); + assert(queue); + queue_item->handle = queue; + return (void *)queue_item; +} + +static void queue_delete_hlevel_wrapper(void *queue) +{ + if (queue == NULL) { + return; + } + + btdm_queue_item_t *queue_item = (btdm_queue_item_t *)queue; + + if (queue_item->handle) { + void *handle = ((hli_queue_handle_t)(queue_item->handle))->downstream; + hli_queue_delete(queue_item->handle); + queue_item->handle = handle; + queue_delete_wrapper(queue_item); + } +} + +static int32_t queue_send_hlevel_wrapper(void *queue, void *item, uint32_t block_time_ms) +{ + void *handle = ((btdm_queue_item_t *)queue)->handle; + if (block_time_ms == OSI_FUNCS_TIME_BLOCKING) { + return (int32_t)xQueueSend(((hli_queue_handle_t)handle)->downstream, item, portMAX_DELAY); + } else { + return (int32_t)xQueueSend(((hli_queue_handle_t)handle)->downstream, item, block_time_ms / portTICK_PERIOD_MS); + } +} + +/** + * Queue send from isr + * @param queue The queue which will send to + * @param item The message which will be send + * @param hptw need do task yield or not + * @return send success or not + * There is an issue here: When the queue is full, it may reture true but it send fail to the queue, sometimes. + * But in Bluetooth controller's isr, We don't care about the return value. + * It only required tp send success when the queue is empty all the time. + * So, this function meets the requirement. + */ +static int32_t IRAM_ATTR queue_send_from_isr_hlevel_wrapper(void *queue, void *item, void *hptw) +{ + UNUSED(hptw); + assert(xPortGetCoreID() == CONFIG_BTDM_CTRL_PINNED_TO_CORE); + void *handle = ((btdm_queue_item_t *)queue)->handle; + return hli_queue_put(handle, item); +} + +static int32_t queue_recv_hlevel_wrapper(void *queue, void *item, uint32_t block_time_ms) +{ + bool ret; + void *handle = ((btdm_queue_item_t *)queue)->handle; + if (block_time_ms == OSI_FUNCS_TIME_BLOCKING) { + ret = xQueueReceive(((hli_queue_handle_t)handle)->downstream, item, portMAX_DELAY); + } else { + ret = xQueueReceive(((hli_queue_handle_t)handle)->downstream, item, block_time_ms / portTICK_PERIOD_MS); + } + + return (int32_t)ret; +} + +static int32_t IRAM_ATTR queue_recv_from_isr_hlevel_wrapper(void *queue, void *item, void *hptw) +{ + // Not support it + assert(0); + return 0; +} + +#else + +static int32_t queue_send_wrapper(void *queue, void *item, uint32_t block_time_ms) +{ + if (block_time_ms == OSI_FUNCS_TIME_BLOCKING) { + return (int32_t)xQueueSend(((btdm_queue_item_t*)queue)->handle, item, portMAX_DELAY); + } else { + return (int32_t)xQueueSend(((btdm_queue_item_t*)queue)->handle, item, block_time_ms / portTICK_PERIOD_MS); + } +} + +static int32_t IRAM_ATTR queue_send_from_isr_wrapper(void *queue, void *item, void *hptw) +{ + return (int32_t)xQueueSendFromISR(((btdm_queue_item_t*)queue)->handle, item, hptw); +} + +static int32_t queue_recv_wrapper(void *queue, void *item, uint32_t block_time_ms) + { + bool ret; + if (block_time_ms == OSI_FUNCS_TIME_BLOCKING) { + ret = xQueueReceive(((btdm_queue_item_t*)queue)->handle, item, portMAX_DELAY); + } else { + ret = xQueueReceive(((btdm_queue_item_t*)queue)->handle, item, block_time_ms / portTICK_PERIOD_MS); + } + + return (int32_t)ret; + } + +static int32_t IRAM_ATTR queue_recv_from_isr_wrapper(void *queue, void *item, void *hptw) +{ + return (int32_t)xQueueReceiveFromISR(((btdm_queue_item_t*)queue)->handle, item, hptw); +} +#endif /* CONFIG_BTDM_CTRL_HLI */ + + +static int32_t task_create_wrapper(void *task_func, const char *name, uint32_t stack_depth, void *param, uint32_t prio, void *task_handle, uint32_t core_id) +{ + return (uint32_t)xTaskCreatePinnedToCore(task_func, name, stack_depth, param, prio, task_handle, (core_id < portNUM_PROCESSORS ? core_id : tskNO_AFFINITY)); +} + +static void task_delete_wrapper(void *task_handle) +{ + vTaskDelete(task_handle); +} + +static bool IRAM_ATTR is_in_isr_wrapper(void) +{ + return !xPortCanYield(); +} + +static void IRAM_ATTR cause_sw_intr(void *arg) +{ + /* just convert void * to int, because the width is the same */ + uint32_t intr_no = (uint32_t)arg; + XTHAL_SET_INTSET((1<<intr_no)); +} + +static int IRAM_ATTR cause_sw_intr_to_core_wrapper(int core_id, int intr_no) +{ + esp_err_t err = ESP_OK; + +#if CONFIG_FREERTOS_UNICORE + cause_sw_intr((void *)intr_no); +#else /* CONFIG_FREERTOS_UNICORE */ + if (xPortGetCoreID() == core_id) { + cause_sw_intr((void *)intr_no); + } else { + err = esp_ipc_call(core_id, cause_sw_intr, (void *)intr_no); + } +#endif /* !CONFIG_FREERTOS_UNICORE */ + return err; +} + +static void *malloc_internal_wrapper(size_t size) +{ + return heap_caps_malloc(size, MALLOC_CAP_8BIT|MALLOC_CAP_DMA|MALLOC_CAP_INTERNAL); +} + +static int32_t IRAM_ATTR read_mac_wrapper(uint8_t mac[6]) +{ + int ret = esp_read_mac(mac, ESP_MAC_BT); + ESP_LOGI(BTDM_LOG_TAG, "Bluetooth MAC: %02x:%02x:%02x:%02x:%02x:%02x", + mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); + + return ret; +} + +static void IRAM_ATTR srand_wrapper(unsigned int seed) +{ + /* empty function */ +} + +static int IRAM_ATTR rand_wrapper(void) +{ + return (int)esp_random(); +} + +static uint32_t IRAM_ATTR btdm_lpcycles_2_us(uint32_t cycles) +{ + // The number of lp cycles should not lead to overflow. Thrs: 100s + // clock measurement is conducted + uint64_t us = (uint64_t)btdm_lpcycle_us * cycles; + us = (us + (1 << (btdm_lpcycle_us_frac - 1))) >> btdm_lpcycle_us_frac; + return (uint32_t)us; +} + +/* + * @brief Converts a duration in slots into a number of low power clock cycles. + */ +static uint32_t IRAM_ATTR btdm_us_2_lpcycles(uint32_t us) +{ + // The number of sleep duration(us) should not lead to overflow. Thrs: 100s + // Compute the sleep duration in us to low power clock cycles, with calibration result applied + // clock measurement is conducted + uint64_t cycles = ((uint64_t)(us) << btdm_lpcycle_us_frac) / btdm_lpcycle_us; + + return (uint32_t)cycles; +} + +static bool IRAM_ATTR btdm_sleep_check_duration(uint32_t *slot_cnt) +{ + if (*slot_cnt < BTDM_MIN_SLEEP_DURATION) { + return false; + } + /* wake up in advance considering the delay in enabling PHY/RF */ + *slot_cnt -= BTDM_MODEM_WAKE_UP_DELAY; + return true; +} + +static void btdm_sleep_enter_phase1_wrapper(uint32_t lpcycles) +{ +#ifdef CONFIG_PM_ENABLE + // start a timer to wake up and acquire the pm_lock before modem_sleep awakes + uint32_t us_to_sleep = btdm_lpcycles_2_us(lpcycles); + +#define BTDM_MIN_TIMER_UNCERTAINTY_US (500) + assert(us_to_sleep > BTDM_MIN_TIMER_UNCERTAINTY_US); + // allow a maximum time uncertainty to be about 488ppm(1/2048) at least as clock drift + // and set the timer in advance + uint32_t uncertainty = (us_to_sleep >> 11); + if (uncertainty < BTDM_MIN_TIMER_UNCERTAINTY_US) { + uncertainty = BTDM_MIN_TIMER_UNCERTAINTY_US; + } + + if (esp_timer_start_once(s_btdm_slp_tmr, us_to_sleep - uncertainty) != ESP_OK) { + ESP_LOGW(BTDM_LOG_TAG, "timer start failed"); + } +#endif +} + +static void btdm_sleep_enter_phase2_wrapper(void) +{ + if (btdm_controller_get_sleep_mode() == BTDM_MODEM_SLEEP_MODE_ORIG) { + esp_phy_disable(PHY_MODEM_BT); +#ifdef CONFIG_PM_ENABLE + if (s_pm_lock_acquired) { + esp_pm_lock_release(s_pm_lock); + s_pm_lock_acquired = false; + } +#endif + } else if (btdm_controller_get_sleep_mode() == BTDM_MODEM_SLEEP_MODE_EVED) { + esp_phy_disable(PHY_MODEM_BT); + // pause bluetooth baseband + periph_module_disable(PERIPH_BT_BASEBAND_MODULE); + } +} + +static void btdm_sleep_exit_phase3_wrapper(void) +{ +#ifdef CONFIG_PM_ENABLE + if (!s_pm_lock_acquired) { + s_pm_lock_acquired = true; + esp_pm_lock_acquire(s_pm_lock); + } +#endif + + if (btdm_controller_get_sleep_mode() == BTDM_MODEM_SLEEP_MODE_ORIG) { + esp_phy_enable(PHY_MODEM_BT); + btdm_check_and_init_bb(); +#ifdef CONFIG_PM_ENABLE + esp_timer_stop(s_btdm_slp_tmr); +#endif + } else if (btdm_controller_get_sleep_mode() == BTDM_MODEM_SLEEP_MODE_EVED) { + // resume bluetooth baseband + periph_module_enable(PERIPH_BT_BASEBAND_MODULE); + esp_phy_enable(PHY_MODEM_BT); + } +} + +#ifdef CONFIG_PM_ENABLE +static void btdm_slp_tmr_customer_callback(void * arg) +{ + (void)(arg); + + if (!s_pm_lock_acquired) { + s_pm_lock_acquired = true; + esp_pm_lock_acquire(s_pm_lock); + } +} + +static void IRAM_ATTR btdm_slp_tmr_callback(void *arg) +{ + (void)(arg); + btdm_dispatch_work_to_controller(btdm_slp_tmr_customer_callback, NULL, true); +} +#endif + +#define BTDM_ASYNC_WAKEUP_REQ_HCI 0 +#define BTDM_ASYNC_WAKEUP_REQ_COEX 1 +#define BTDM_ASYNC_WAKEUP_REQ_CTRL_DISA 2 +#define BTDM_ASYNC_WAKEUP_REQMAX 3 + +static void btdm_wakeup_request_callback(void * arg) +{ + (void)(arg); + +#if CONFIG_PM_ENABLE + if (!s_pm_lock_acquired) { + s_pm_lock_acquired = true; + esp_pm_lock_acquire(s_pm_lock); + } + esp_timer_stop(s_btdm_slp_tmr); +#endif + btdm_wakeup_request(); + + semphr_give_wrapper(s_wakeup_req_sem); +} + +static bool async_wakeup_request(int event) +{ + bool do_wakeup_request = false; + + switch (event) { + case BTDM_ASYNC_WAKEUP_REQ_HCI: + btdm_in_wakeup_requesting_set(true); + // NO break + case BTDM_ASYNC_WAKEUP_REQ_CTRL_DISA: + if (!btdm_power_state_active()) { + do_wakeup_request = true; + + btdm_dispatch_work_to_controller(btdm_wakeup_request_callback, NULL, true); + semphr_take_wrapper(s_wakeup_req_sem, OSI_FUNCS_TIME_BLOCKING); + } + break; + case BTDM_ASYNC_WAKEUP_REQ_COEX: + if (!btdm_power_state_active()) { + do_wakeup_request = true; +#if CONFIG_PM_ENABLE + if (!s_pm_lock_acquired) { + s_pm_lock_acquired = true; + esp_pm_lock_acquire(s_pm_lock); + } + esp_timer_stop(s_btdm_slp_tmr); +#endif + btdm_wakeup_request(); + } + break; + default: + return false; + } + + return do_wakeup_request; +} + +static void async_wakeup_request_end(int event) +{ + bool request_lock = false; + switch (event) { + case BTDM_ASYNC_WAKEUP_REQ_HCI: + request_lock = true; + break; + case BTDM_ASYNC_WAKEUP_REQ_COEX: + case BTDM_ASYNC_WAKEUP_REQ_CTRL_DISA: + request_lock = false; + break; + default: + return; + } + + if (request_lock) { + btdm_in_wakeup_requesting_set(false); + } + + return; +} + +static bool coex_bt_wakeup_request(void) +{ + return async_wakeup_request(BTDM_ASYNC_WAKEUP_REQ_COEX); +} + +static void coex_bt_wakeup_request_end(void) +{ + async_wakeup_request_end(BTDM_ASYNC_WAKEUP_REQ_COEX); + return; +} + +static int IRAM_ATTR coex_bt_request_wrapper(uint32_t event, uint32_t latency, uint32_t duration) +{ +#if CONFIG_SW_COEXIST_ENABLE + return coex_bt_request(event, latency, duration); +#else + return 0; +#endif +} + +static int IRAM_ATTR coex_bt_release_wrapper(uint32_t event) +{ +#if CONFIG_SW_COEXIST_ENABLE + return coex_bt_release(event); +#else + return 0; +#endif +} + +static int coex_register_bt_cb_wrapper(coex_func_cb_t cb) +{ +#if CONFIG_SW_COEXIST_ENABLE + return coex_register_bt_cb(cb); +#else + return 0; +#endif +} + +static uint32_t IRAM_ATTR coex_bb_reset_lock_wrapper(void) +{ +#if CONFIG_SW_COEXIST_ENABLE + return coex_bb_reset_lock(); +#else + return 0; +#endif +} + +static void IRAM_ATTR coex_bb_reset_unlock_wrapper(uint32_t restore) +{ +#if CONFIG_SW_COEXIST_ENABLE + coex_bb_reset_unlock(restore); +#endif +} + +static int coex_schm_register_btdm_callback_wrapper(void *callback) +{ +#if CONFIG_SW_COEXIST_ENABLE + return coex_schm_register_callback(COEX_SCHM_CALLBACK_TYPE_BT, callback); +#else + return 0; +#endif +} + +static void coex_schm_status_bit_clear_wrapper(uint32_t type, uint32_t status) +{ +#if CONFIG_SW_COEXIST_ENABLE + coex_schm_status_bit_clear(type, status); +#endif +} + +static void coex_schm_status_bit_set_wrapper(uint32_t type, uint32_t status) +{ +#if CONFIG_SW_COEXIST_ENABLE + coex_schm_status_bit_set(type, status); +#endif +} + +static uint32_t coex_schm_interval_get_wrapper(void) +{ +#if CONFIG_SW_COEXIST_ENABLE + return coex_schm_interval_get(); +#else + return 0; +#endif +} + +static uint8_t coex_schm_curr_period_get_wrapper(void) +{ +#if CONFIG_SW_COEXIST_ENABLE + return coex_schm_curr_period_get(); +#else + return 1; +#endif +} + +static void * coex_schm_curr_phase_get_wrapper(void) +{ +#if CONFIG_SW_COEXIST_ENABLE + return coex_schm_curr_phase_get(); +#else + return NULL; +#endif +} + +static int coex_wifi_channel_get_wrapper(uint8_t *primary, uint8_t *secondary) +{ +#if CONFIG_SW_COEXIST_ENABLE + return coex_wifi_channel_get(primary, secondary); +#else + return -1; +#endif +} + +static int coex_register_wifi_channel_change_callback_wrapper(void *cb) +{ +#if CONFIG_SW_COEXIST_ENABLE + return coex_register_wifi_channel_change_callback(cb); +#else + return -1; +#endif +} + +static int coex_version_get_wrapper(unsigned int *major, unsigned int *minor, unsigned int *patch) +{ +#if CONFIG_SW_COEXIST_ENABLE + coex_version_t version; + ESP_ERROR_CHECK(coex_version_get_value(&version)); + *major = (unsigned int)version.major; + *minor = (unsigned int)version.minor; + *patch = (unsigned int)version.patch; + return 0; +#endif + return -1; +} + +bool esp_vhci_host_check_send_available(void) +{ + return API_vhci_host_check_send_available(); +} + +void esp_vhci_host_send_packet(uint8_t *data, uint16_t len) +{ + async_wakeup_request(BTDM_ASYNC_WAKEUP_REQ_HCI); + + API_vhci_host_send_packet(data, len); + + async_wakeup_request_end(BTDM_ASYNC_WAKEUP_REQ_HCI); +} + +esp_err_t esp_vhci_host_register_callback(const esp_vhci_host_callback_t *callback) +{ + return API_vhci_host_register_callback((const vhci_host_callback_t *)callback) == 0 ? ESP_OK : ESP_FAIL; +} + +static uint32_t btdm_config_mask_load(void) +{ + uint32_t mask = 0x0; + +#if CONFIG_BTDM_CTRL_HCI_MODE_UART_H4 + mask |= BTDM_CFG_HCI_UART; +#endif +#if CONFIG_BTDM_CTRL_PINNED_TO_CORE == 1 + mask |= BTDM_CFG_CONTROLLER_RUN_APP_CPU; +#endif +#if CONFIG_BTDM_CTRL_FULL_SCAN_SUPPORTED + mask |= BTDM_CFG_BLE_FULL_SCAN_SUPPORTED; +#endif /* CONFIG_BTDM_CTRL_FULL_SCAN_SUPPORTED */ + mask |= BTDM_CFG_SCAN_DUPLICATE_OPTIONS; + + mask |= BTDM_CFG_SEND_ADV_RESERVED_SIZE; + + return mask; +} + +static void btdm_controller_mem_init(void) +{ + /* initialise .data section */ + memcpy(&_data_start_btdm, (void *)_data_start_btdm_rom, &_data_end_btdm - &_data_start_btdm); + ESP_LOGD(BTDM_LOG_TAG, ".data initialise [0x%08x] <== [0x%08x]", (uint32_t)&_data_start_btdm, _data_start_btdm_rom); + + //initial em, .bss section + for (int i = 1; i < sizeof(btdm_dram_available_region)/sizeof(btdm_dram_available_region_t); i++) { + if (btdm_dram_available_region[i].mode != ESP_BT_MODE_IDLE) { + memset((void *)btdm_dram_available_region[i].start, 0x0, btdm_dram_available_region[i].end - btdm_dram_available_region[i].start); + ESP_LOGD(BTDM_LOG_TAG, ".bss initialise [0x%08x] - [0x%08x]", btdm_dram_available_region[i].start, btdm_dram_available_region[i].end); + } + } +} + +static esp_err_t try_heap_caps_add_region(intptr_t start, intptr_t end) +{ + int ret = heap_caps_add_region(start, end); + /* heap_caps_add_region() returns ESP_ERR_INVALID_SIZE if the memory region is + * is too small to fit a heap. This cannot be termed as a fatal error and hence + * we replace it by ESP_OK + */ + if (ret == ESP_ERR_INVALID_SIZE) { + return ESP_OK; + } + return ret; +} + +esp_err_t esp_bt_controller_mem_release(esp_bt_mode_t mode) +{ + bool update = true; + intptr_t mem_start=(intptr_t) NULL, mem_end=(intptr_t) NULL; + + if (btdm_controller_status != ESP_BT_CONTROLLER_STATUS_IDLE) { + return ESP_ERR_INVALID_STATE; + } + + //already released + if (!(mode & btdm_dram_available_region[0].mode)) { + return ESP_ERR_INVALID_STATE; + } + + for (int i = 0; i < sizeof(btdm_dram_available_region)/sizeof(btdm_dram_available_region_t); i++) { + //skip the share mode, idle mode and other mode + if (btdm_dram_available_region[i].mode == ESP_BT_MODE_IDLE + || (mode & btdm_dram_available_region[i].mode) != btdm_dram_available_region[i].mode) { + //clear the bit of the mode which will be released + btdm_dram_available_region[i].mode &= ~mode; + continue; + } else { + //clear the bit of the mode which will be released + btdm_dram_available_region[i].mode &= ~mode; + } + + if (update) { + mem_start = btdm_dram_available_region[i].start; + mem_end = btdm_dram_available_region[i].end; + update = false; + } + + if (i < sizeof(btdm_dram_available_region)/sizeof(btdm_dram_available_region_t) - 1) { + mem_end = btdm_dram_available_region[i].end; + if (btdm_dram_available_region[i+1].mode != ESP_BT_MODE_IDLE + && (mode & btdm_dram_available_region[i+1].mode) == btdm_dram_available_region[i+1].mode + && mem_end == btdm_dram_available_region[i+1].start) { + continue; + } else { + ESP_LOGD(BTDM_LOG_TAG, "Release DRAM [0x%08x] - [0x%08x]", mem_start, mem_end); + ESP_ERROR_CHECK(try_heap_caps_add_region(mem_start, mem_end)); + update = true; + } + } else { + mem_end = btdm_dram_available_region[i].end; + ESP_LOGD(BTDM_LOG_TAG, "Release DRAM [0x%08x] - [0x%08x]", mem_start, mem_end); + ESP_ERROR_CHECK(try_heap_caps_add_region(mem_start, mem_end)); + update = true; + } + } + + if (mode == ESP_BT_MODE_BTDM) { + mem_start = (intptr_t)&_btdm_bss_start; + mem_end = (intptr_t)&_btdm_bss_end; + if (mem_start != mem_end) { + ESP_LOGD(BTDM_LOG_TAG, "Release BTDM BSS [0x%08x] - [0x%08x]", mem_start, mem_end); + ESP_ERROR_CHECK(try_heap_caps_add_region(mem_start, mem_end)); + } + mem_start = (intptr_t)&_btdm_data_start; + mem_end = (intptr_t)&_btdm_data_end; + if (mem_start != mem_end) { + ESP_LOGD(BTDM_LOG_TAG, "Release BTDM Data [0x%08x] - [0x%08x]", mem_start, mem_end); + ESP_ERROR_CHECK(try_heap_caps_add_region(mem_start, mem_end)); + } + } + return ESP_OK; +} + +esp_err_t esp_bt_mem_release(esp_bt_mode_t mode) +{ + int ret; + intptr_t mem_start, mem_end; + + ret = esp_bt_controller_mem_release(mode); + if (ret != ESP_OK) { + return ret; + } + + if (mode == ESP_BT_MODE_BTDM) { + mem_start = (intptr_t)&_bt_bss_start; + mem_end = (intptr_t)&_bt_bss_end; + if (mem_start != mem_end) { + ESP_LOGD(BTDM_LOG_TAG, "Release BT BSS [0x%08x] - [0x%08x]", mem_start, mem_end); + ESP_ERROR_CHECK(try_heap_caps_add_region(mem_start, mem_end)); + } + mem_start = (intptr_t)&_bt_data_start; + mem_end = (intptr_t)&_bt_data_end; + if (mem_start != mem_end) { + ESP_LOGD(BTDM_LOG_TAG, "Release BT Data [0x%08x] - [0x%08x]", mem_start, mem_end); + ESP_ERROR_CHECK(try_heap_caps_add_region(mem_start, mem_end)); + } + + mem_start = (intptr_t)&_nimble_bss_start; + mem_end = (intptr_t)&_nimble_bss_end; + if (mem_start != mem_end) { + ESP_LOGD(BTDM_LOG_TAG, "Release NimBLE BSS [0x%08x] - [0x%08x]", mem_start, mem_end); + ESP_ERROR_CHECK(try_heap_caps_add_region(mem_start, mem_end)); + } + mem_start = (intptr_t)&_nimble_data_start; + mem_end = (intptr_t)&_nimble_data_end; + if (mem_start != mem_end) { + ESP_LOGD(BTDM_LOG_TAG, "Release NimBLE Data [0x%08x] - [0x%08x]", mem_start, mem_end); + ESP_ERROR_CHECK(try_heap_caps_add_region(mem_start, mem_end)); + } + } + return ESP_OK; +} + +#if CONFIG_BTDM_CTRL_HLI +static void hli_queue_setup_cb(void* arg) +{ + hli_queue_setup(); +} + +static void hli_queue_setup_pinned_to_core(int core_id) +{ +#if CONFIG_FREERTOS_UNICORE + hli_queue_setup_cb(NULL); +#else /* CONFIG_FREERTOS_UNICORE */ + if (xPortGetCoreID() == core_id) { + hli_queue_setup_cb(NULL); + } else { + esp_ipc_call(core_id, hli_queue_setup_cb, NULL); + } +#endif /* !CONFIG_FREERTOS_UNICORE */ +} +#endif /* CONFIG_BTDM_CTRL_HLI */ + +esp_err_t esp_bt_controller_init(esp_bt_controller_config_t *cfg) +{ + esp_err_t err; + uint32_t btdm_cfg_mask = 0; + +#if CONFIG_BTDM_CTRL_HLI + hli_queue_setup_pinned_to_core(CONFIG_BTDM_CTRL_PINNED_TO_CORE); +#endif /* CONFIG_BTDM_CTRL_HLI */ + + //if all the bt available memory was already released, cannot initialize bluetooth controller + if (btdm_dram_available_region[0].mode == ESP_BT_MODE_IDLE) { + return ESP_ERR_INVALID_STATE; + } + + osi_funcs_p = (struct osi_funcs_t *)malloc_internal_wrapper(sizeof(struct osi_funcs_t)); + if (osi_funcs_p == NULL) { + return ESP_ERR_NO_MEM; + } + + memcpy(osi_funcs_p, &osi_funcs_ro, sizeof(struct osi_funcs_t)); + if (btdm_osi_funcs_register(osi_funcs_p) != 0) { + return ESP_ERR_INVALID_ARG; + } + + if (btdm_controller_status != ESP_BT_CONTROLLER_STATUS_IDLE) { + return ESP_ERR_INVALID_STATE; + } + + if (cfg == NULL) { + return ESP_ERR_INVALID_ARG; + } + + if (cfg->controller_task_prio != ESP_TASK_BT_CONTROLLER_PRIO + || cfg->controller_task_stack_size < ESP_TASK_BT_CONTROLLER_STACK) { + return ESP_ERR_INVALID_ARG; + } + + //overwrite some parameters + cfg->bt_max_sync_conn = CONFIG_BTDM_CTRL_BR_EDR_MAX_SYNC_CONN_EFF; + cfg->magic = ESP_BT_CONTROLLER_CONFIG_MAGIC_VAL; + + if (((cfg->mode & ESP_BT_MODE_BLE) && (cfg->ble_max_conn <= 0 || cfg->ble_max_conn > BTDM_CONTROLLER_BLE_MAX_CONN_LIMIT)) + || ((cfg->mode & ESP_BT_MODE_CLASSIC_BT) && (cfg->bt_max_acl_conn <= 0 || cfg->bt_max_acl_conn > BTDM_CONTROLLER_BR_EDR_MAX_ACL_CONN_LIMIT)) + || ((cfg->mode & ESP_BT_MODE_CLASSIC_BT) && (cfg->bt_max_sync_conn > BTDM_CONTROLLER_BR_EDR_MAX_SYNC_CONN_LIMIT))) { + return ESP_ERR_INVALID_ARG; + } + + ESP_LOGI(BTDM_LOG_TAG, "BT controller compile version [%s]", btdm_controller_get_compile_version()); + + s_wakeup_req_sem = semphr_create_wrapper(1, 0); + if (s_wakeup_req_sem == NULL) { + err = ESP_ERR_NO_MEM; + goto error; + } + + esp_phy_modem_init(); + + esp_bt_power_domain_on(); + + btdm_controller_mem_init(); + + periph_module_enable(PERIPH_BT_MODULE); + +#ifdef CONFIG_PM_ENABLE + s_btdm_allow_light_sleep = false; +#endif + + // set default sleep clock cycle and its fractional bits + btdm_lpcycle_us_frac = RTC_CLK_CAL_FRACT; + btdm_lpcycle_us = 2 << (btdm_lpcycle_us_frac); + +#if CONFIG_BTDM_CTRL_MODEM_SLEEP_MODE_ORIG + + btdm_lpclk_sel = BTDM_LPCLK_SEL_XTAL; // set default value +#if CONFIG_BTDM_CTRL_LPCLK_SEL_EXT_32K_XTAL + // check whether or not EXT_CRYS is working + if (rtc_clk_slow_src_get() == SOC_RTC_SLOW_CLK_SRC_XTAL32K) { + btdm_lpclk_sel = BTDM_LPCLK_SEL_XTAL32K; // External 32kHz XTAL +#ifdef CONFIG_PM_ENABLE + s_btdm_allow_light_sleep = true; +#endif + } else { + ESP_LOGW(BTDM_LOG_TAG, "32.768kHz XTAL not detected, fall back to main XTAL as Bluetooth sleep clock\n" + "light sleep mode will not be able to apply when bluetooth is enabled"); + btdm_lpclk_sel = BTDM_LPCLK_SEL_XTAL; // set default value + } +#else + btdm_lpclk_sel = BTDM_LPCLK_SEL_XTAL; // set default value +#endif + + bool select_src_ret __attribute__((unused)); + bool set_div_ret __attribute__((unused)); + if (btdm_lpclk_sel == BTDM_LPCLK_SEL_XTAL) { + select_src_ret = btdm_lpclk_select_src(BTDM_LPCLK_SEL_XTAL); + set_div_ret = btdm_lpclk_set_div(esp_clk_xtal_freq() * 2 / MHZ - 1); + assert(select_src_ret && set_div_ret); + btdm_lpcycle_us_frac = RTC_CLK_CAL_FRACT; + btdm_lpcycle_us = 2 << (btdm_lpcycle_us_frac); + } else { // btdm_lpclk_sel == BTDM_LPCLK_SEL_XTAL32K + select_src_ret = btdm_lpclk_select_src(BTDM_LPCLK_SEL_XTAL32K); + set_div_ret = btdm_lpclk_set_div(0); + assert(select_src_ret && set_div_ret); + btdm_lpcycle_us_frac = RTC_CLK_CAL_FRACT; + btdm_lpcycle_us = (RTC_CLK_CAL_FRACT > 15) ? (1000000 << (RTC_CLK_CAL_FRACT - 15)) : + (1000000 >> (15 - RTC_CLK_CAL_FRACT)); + assert(btdm_lpcycle_us != 0); + } + btdm_controller_set_sleep_mode(BTDM_MODEM_SLEEP_MODE_ORIG); + +#elif CONFIG_BTDM_CTRL_MODEM_SLEEP_MODE_EVED + btdm_controller_set_sleep_mode(BTDM_MODEM_SLEEP_MODE_EVED); +#else + btdm_controller_set_sleep_mode(BTDM_MODEM_SLEEP_MODE_NONE); +#endif + +#if CONFIG_BTDM_CTRL_HCI_UART_FLOW_CTRL_EN + sdk_config_set_uart_flow_ctrl_enable(true); +#else + sdk_config_set_uart_flow_ctrl_enable(false); +#endif + +#ifdef CONFIG_PM_ENABLE + if (!s_btdm_allow_light_sleep) { + if ((err = esp_pm_lock_create(ESP_PM_NO_LIGHT_SLEEP, 0, "btLS", &s_light_sleep_pm_lock)) != ESP_OK) { + goto error; + } + } + if ((err = esp_pm_lock_create(ESP_PM_APB_FREQ_MAX, 0, "bt", &s_pm_lock)) != ESP_OK) { + goto error; + } + esp_timer_create_args_t create_args = { + .callback = btdm_slp_tmr_callback, + .arg = NULL, + .name = "btSlp" + }; + if ((err = esp_timer_create(&create_args, &s_btdm_slp_tmr)) != ESP_OK) { + goto error; + } + + s_pm_lock_acquired = true; +#endif + +#if CONFIG_SW_COEXIST_ENABLE + coex_init(); +#endif + + btdm_cfg_mask = btdm_config_mask_load(); + + if (btdm_controller_init(btdm_cfg_mask, cfg) != 0) { + err = ESP_ERR_NO_MEM; + goto error; + } + + btdm_controller_status = ESP_BT_CONTROLLER_STATUS_INITED; + + return ESP_OK; + +error: + + bt_controller_deinit_internal(); + + return err; +} + +esp_err_t esp_bt_controller_deinit(void) +{ + if (btdm_controller_status != ESP_BT_CONTROLLER_STATUS_INITED) { + return ESP_ERR_INVALID_STATE; + } + + btdm_controller_deinit(); + + bt_controller_deinit_internal(); + + return ESP_OK; +} + +static void bt_controller_deinit_internal(void) +{ + periph_module_disable(PERIPH_BT_MODULE); + +#ifdef CONFIG_PM_ENABLE + if (!s_btdm_allow_light_sleep) { + esp_pm_lock_delete(s_light_sleep_pm_lock); + s_light_sleep_pm_lock = NULL; + } + + if (s_pm_lock != NULL) { + esp_pm_lock_delete(s_pm_lock); + s_pm_lock = NULL; + } + + if (s_btdm_slp_tmr != NULL) { + esp_timer_stop(s_btdm_slp_tmr); + esp_timer_delete(s_btdm_slp_tmr); + s_btdm_slp_tmr = NULL; + } + + s_pm_lock_acquired = false; +#endif + + if (s_wakeup_req_sem) { + semphr_delete_wrapper(s_wakeup_req_sem); + s_wakeup_req_sem = NULL; + } + + if (osi_funcs_p) { + free(osi_funcs_p); + osi_funcs_p = NULL; + } + + btdm_controller_status = ESP_BT_CONTROLLER_STATUS_IDLE; + + btdm_lpcycle_us = 0; + btdm_controller_set_sleep_mode(BTDM_MODEM_SLEEP_MODE_NONE); + + esp_bt_power_domain_off(); + + esp_phy_modem_deinit(); +} + +static void bt_controller_shutdown(void* arg) +{ + esp_bt_controller_shutdown(); +} + +static void bt_shutdown(void) +{ + if (btdm_controller_status != ESP_BT_CONTROLLER_STATUS_ENABLED) { + return; + } +#if !CONFIG_FREERTOS_UNICORE + esp_ipc_call_blocking(CONFIG_BTDM_CTRL_PINNED_TO_CORE, bt_controller_shutdown, NULL); +#else + bt_controller_shutdown(NULL); +#endif + esp_phy_disable(PHY_MODEM_BT); + + return; +} + + +esp_err_t esp_bt_controller_enable(esp_bt_mode_t mode) +{ + int ret; + + if (btdm_controller_status != ESP_BT_CONTROLLER_STATUS_INITED) { + return ESP_ERR_INVALID_STATE; + } + + //As the history reason, mode should be equal to the mode which set in esp_bt_controller_init() + if (mode != btdm_controller_get_mode()) { + return ESP_ERR_INVALID_ARG; + } + +#ifdef CONFIG_PM_ENABLE + if (!s_btdm_allow_light_sleep) { + esp_pm_lock_acquire(s_light_sleep_pm_lock); + } + esp_pm_lock_acquire(s_pm_lock); +#endif + + esp_phy_enable(PHY_MODEM_BT); + +#if CONFIG_SW_COEXIST_ENABLE + coex_enable(); +#endif + + if (btdm_controller_get_sleep_mode() == BTDM_MODEM_SLEEP_MODE_ORIG) { + btdm_controller_enable_sleep(true); + } + + sdk_config_set_bt_pll_track_enable(true); + + // inititalize bluetooth baseband + btdm_check_and_init_bb(); + + ret = btdm_controller_enable(mode); + if (ret != 0) { +#if CONFIG_SW_COEXIST_ENABLE + coex_disable(); +#endif + esp_phy_disable(PHY_MODEM_BT); +#ifdef CONFIG_PM_ENABLE + if (!s_btdm_allow_light_sleep) { + esp_pm_lock_release(s_light_sleep_pm_lock); + } + esp_pm_lock_release(s_pm_lock); +#endif + return ESP_ERR_INVALID_STATE; + } + + btdm_controller_status = ESP_BT_CONTROLLER_STATUS_ENABLED; + ret = esp_register_shutdown_handler(bt_shutdown); + if (ret != ESP_OK) { + ESP_LOGW(BTDM_LOG_TAG, "Register shutdown handler failed, ret = 0x%x", ret); + } + + return ESP_OK; +} + +esp_err_t esp_bt_controller_disable(void) +{ + if (btdm_controller_status != ESP_BT_CONTROLLER_STATUS_ENABLED) { + return ESP_ERR_INVALID_STATE; + } + + // disable modem sleep and wake up from sleep mode + if (btdm_controller_get_sleep_mode() == BTDM_MODEM_SLEEP_MODE_ORIG) { + btdm_controller_enable_sleep(false); + async_wakeup_request(BTDM_ASYNC_WAKEUP_REQ_CTRL_DISA); + while (!btdm_power_state_active()) { + esp_rom_delay_us(1000); + } + } + + btdm_controller_disable(); + +#if CONFIG_SW_COEXIST_ENABLE + coex_disable(); +#endif + + esp_phy_disable(PHY_MODEM_BT); + btdm_controller_status = ESP_BT_CONTROLLER_STATUS_INITED; + esp_unregister_shutdown_handler(bt_shutdown); + +#ifdef CONFIG_PM_ENABLE + if (!s_btdm_allow_light_sleep) { + esp_pm_lock_release(s_light_sleep_pm_lock); + } + esp_pm_lock_release(s_pm_lock); +#endif + + return ESP_OK; +} + +esp_bt_controller_status_t esp_bt_controller_get_status(void) +{ + return btdm_controller_status; +} + +/* extra functions */ +esp_err_t esp_ble_tx_power_set(esp_ble_power_type_t power_type, esp_power_level_t power_level) +{ + if (ble_txpwr_set(power_type, power_level) != 0) { + return ESP_ERR_INVALID_ARG; + } + + return ESP_OK; +} + +esp_power_level_t esp_ble_tx_power_get(esp_ble_power_type_t power_type) +{ + return (esp_power_level_t)ble_txpwr_get(power_type); +} + +esp_err_t esp_bredr_tx_power_set(esp_power_level_t min_power_level, esp_power_level_t max_power_level) +{ + esp_err_t err; + int ret; + + ret = bredr_txpwr_set(min_power_level, max_power_level); + + if (ret == 0) { + err = ESP_OK; + } else if (ret == -1) { + err = ESP_ERR_INVALID_ARG; + } else { + err = ESP_ERR_INVALID_STATE; + } + + return err; +} + +esp_err_t esp_bredr_tx_power_get(esp_power_level_t *min_power_level, esp_power_level_t *max_power_level) +{ + if (bredr_txpwr_get((int *)min_power_level, (int *)max_power_level) != 0) { + return ESP_ERR_INVALID_ARG; + } + + return ESP_OK; +} + +esp_err_t esp_bt_sleep_enable (void) +{ + esp_err_t status; + if (btdm_controller_status != ESP_BT_CONTROLLER_STATUS_ENABLED) { + return ESP_ERR_INVALID_STATE; + } + if (btdm_controller_get_sleep_mode() == BTDM_MODEM_SLEEP_MODE_ORIG || + btdm_controller_get_sleep_mode() == BTDM_MODEM_SLEEP_MODE_EVED) { + btdm_controller_enable_sleep (true); + status = ESP_OK; + } else { + status = ESP_ERR_NOT_SUPPORTED; + } + + return status; +} + +esp_err_t esp_bt_sleep_disable (void) +{ + esp_err_t status; + if (btdm_controller_status != ESP_BT_CONTROLLER_STATUS_ENABLED) { + return ESP_ERR_INVALID_STATE; + } + if (btdm_controller_get_sleep_mode() == BTDM_MODEM_SLEEP_MODE_ORIG || + btdm_controller_get_sleep_mode() == BTDM_MODEM_SLEEP_MODE_EVED) { + btdm_controller_enable_sleep (false); + status = ESP_OK; + } else { + status = ESP_ERR_NOT_SUPPORTED; + } + + return status; +} + +esp_err_t esp_bredr_sco_datapath_set(esp_sco_data_path_t data_path) +{ + if (btdm_controller_status != ESP_BT_CONTROLLER_STATUS_ENABLED) { + return ESP_ERR_INVALID_STATE; + } + bredr_sco_datapath_set(data_path); + return ESP_OK; +} + +esp_err_t esp_ble_scan_dupilcate_list_flush(void) +{ + if (btdm_controller_status != ESP_BT_CONTROLLER_STATUS_ENABLED) { + return ESP_ERR_INVALID_STATE; + } + btdm_controller_scan_duplicate_list_clear(); + return ESP_OK; +} + +/** + * This function re-write controller's function, + * As coredump can not show paramerters in function which is in a .a file. + * + * After coredump fixing this issue, just delete this function. + */ +void IRAM_ATTR r_assert(const char *condition, int param0, int param1, const char *file, int line) +{ + __asm__ __volatile__("ill\n"); +} + +#endif /* CONFIG_BT_ENABLED */ diff --git a/lib/bt/controller/esp32/hli_api.c b/lib/bt/controller/esp32/hli_api.c new file mode 100644 index 00000000..30e16fa0 --- /dev/null +++ b/lib/bt/controller/esp32/hli_api.c @@ -0,0 +1,297 @@ +/* + * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include <string.h> +#include "esp_log.h" +#include "esp_heap_caps.h" +#include "xtensa/core-macros.h" +#include "soc/dport_reg.h" +#include "hli_api.h" +#include "freertos/FreeRTOS.h" +#include "freertos/queue.h" + +#if CONFIG_BTDM_CTRL_HLI +#define HLI_MAX_HANDLERS 4 + +typedef struct { + intr_handler_t handler; + void* arg; + uint32_t intr_reg; + uint32_t intr_mask; +} hli_handler_info_t; + +typedef struct { +#define CUSTOMER_TYPE_REQUEST (0) +#define CUSTOMER_TYPE_RELEASE (1) + struct { + uint32_t cb_type; + union { + int (* request)(uint32_t, uint32_t, uint32_t); + int (* release)(uint32_t); + } cb; + } customer_cb; + uint32_t arg0, arg1, arg2; +} customer_swisr_t; + +static void IRAM_ATTR customer_swisr_handle(customer_swisr_t *cus_swisr) +{ + if (cus_swisr->customer_cb.cb_type == CUSTOMER_TYPE_REQUEST) { + if (cus_swisr->customer_cb.cb.request != NULL) { + cus_swisr->customer_cb.cb.request(cus_swisr->arg0, cus_swisr->arg1, cus_swisr->arg2); + } + } else if(cus_swisr->customer_cb.cb_type == CUSTOMER_TYPE_RELEASE) { + if (cus_swisr->customer_cb.cb.release != NULL) { + cus_swisr->customer_cb.cb.release(cus_swisr->arg0); + } + } +} + +static DRAM_ATTR hli_handler_info_t s_hli_handlers[HLI_MAX_HANDLERS]; + +esp_err_t hli_intr_register(intr_handler_t handler, void* arg, uint32_t intr_reg, uint32_t intr_mask) +{ + for (hli_handler_info_t* hip = s_hli_handlers; + hip < s_hli_handlers + HLI_MAX_HANDLERS; + ++hip) { + if (hip->handler == NULL) { + hip->arg = arg; + hip->intr_reg = intr_reg; + hip->intr_mask = intr_mask; + hip->handler = handler; /* set last, indicates the entry as valid */ + return ESP_OK; + } + } + return ESP_ERR_NO_MEM; +} + +void IRAM_ATTR hli_c_handler(void) +{ + bool handled = false; + /* Iterate over registered interrupt handlers, + * and check if the expected mask is present in the interrupt status register. + */ + for (hli_handler_info_t* hip = s_hli_handlers; + hip < s_hli_handlers + HLI_MAX_HANDLERS; + ++hip) { + if (hip->handler == NULL) { + continue; + } + uint32_t reg = hip->intr_reg; + uint32_t val; + if (reg == 0) { /* special case for CPU internal interrupts */ + val = XTHAL_GET_INTERRUPT(); + } else { + /* "reg" might not be in DPORT, but this will work in any case */ + val = DPORT_REG_READ(reg); + } + if ((val & hip->intr_mask) != 0) { + handled = true; + (*hip->handler)(hip->arg); + } + } + if (!handled) { + /* no handler found, it is OK in this case. */ + } +} + +uint32_t IRAM_ATTR hli_intr_disable(void) +{ + /* disable level 4 and below */ + return XTOS_SET_INTLEVEL(XCHAL_DEBUGLEVEL - 2); +} + +void IRAM_ATTR hli_intr_restore(uint32_t state) +{ + XTOS_RESTORE_JUST_INTLEVEL(state); +} + +#define HLI_META_QUEUE_SIZE 16 +#define HLI_QUEUE_MAX_ELEM_SIZE 32 +#define HLI_QUEUE_SW_INT_NUM 29 + +#define HLI_QUEUE_FLAG_SEMAPHORE BIT(0) +#define HLI_QUEUE_FLAG_CUSTOMER BIT(1) + +static DRAM_ATTR struct hli_queue_t *s_meta_queue_ptr = NULL; +static intr_handle_t ret_handle; + +static inline char* IRAM_ATTR wrap_ptr(hli_queue_handle_t queue, char *ptr) +{ + return (ptr == queue->bufend) ? queue->buf : ptr; +} + +static inline bool IRAM_ATTR queue_empty(hli_queue_handle_t queue) +{ + return queue->begin == queue->end; +} + +static inline bool IRAM_ATTR queue_full(hli_queue_handle_t queue) +{ + return wrap_ptr(queue, queue->end + queue->elem_size) == queue->begin; +} + +static void IRAM_ATTR queue_isr_handler(void* arg) +{ + int do_yield = pdFALSE; + XTHAL_SET_INTCLEAR(BIT(HLI_QUEUE_SW_INT_NUM)); + hli_queue_handle_t queue; + + while (hli_queue_get(s_meta_queue_ptr, &queue)) { + static DRAM_ATTR char scratch[HLI_QUEUE_MAX_ELEM_SIZE]; + while (hli_queue_get(queue, scratch)) { + int res = pdPASS; + if ((queue->flags & HLI_QUEUE_FLAG_CUSTOMER) != 0) { + customer_swisr_handle((customer_swisr_t *)scratch); + } else if ((queue->flags & HLI_QUEUE_FLAG_SEMAPHORE) != 0) { + res = xSemaphoreGiveFromISR((SemaphoreHandle_t) queue->downstream, &do_yield); + } else { + res = xQueueSendFromISR(queue->downstream, scratch, &do_yield); + } + if (res == pdFAIL) { + /* Failed to send to downstream queue, it is OK in this case. */ + } + } + } + if (do_yield) { + portYIELD_FROM_ISR(); + } +} + +/* Notify the level 3 handler that an element is added to the given hli queue. + * Do this by placing the queue handle onto s_meta_queue, and raising a SW interrupt. + * + * This function must be called with HL interrupts disabled! + */ +static void IRAM_ATTR queue_signal(hli_queue_handle_t queue) +{ + /* See if the queue is already in s_meta_queue, before adding */ + bool found = false; + const hli_queue_handle_t *end = (hli_queue_handle_t*) s_meta_queue_ptr->end; + hli_queue_handle_t *item = (hli_queue_handle_t*) s_meta_queue_ptr->begin; + for (;item != end; item = (hli_queue_handle_t*) wrap_ptr(s_meta_queue_ptr, (char*) (item + 1))) { + if (*item == queue) { + found = true; + break; + } + } + if (!found) { + bool res = hli_queue_put(s_meta_queue_ptr, &queue); + if (!res) { + esp_rom_printf(DRAM_STR("Fatal error in queue_signal: s_meta_queue full\n")); + abort(); + } + XTHAL_SET_INTSET(BIT(HLI_QUEUE_SW_INT_NUM)); + } +} + +static void queue_init(hli_queue_handle_t queue, size_t buf_size, size_t elem_size, QueueHandle_t downstream) +{ + queue->elem_size = elem_size; + queue->begin = queue->buf; + queue->end = queue->buf; + queue->bufend = queue->buf + buf_size; + queue->downstream = downstream; + queue->flags = 0; +} + +void hli_queue_setup(void) +{ + if (s_meta_queue_ptr == NULL) { + s_meta_queue_ptr = hli_queue_create(HLI_META_QUEUE_SIZE, sizeof(void*), NULL); + ESP_ERROR_CHECK(esp_intr_alloc(ETS_INTERNAL_SW1_INTR_SOURCE, ESP_INTR_FLAG_IRAM, queue_isr_handler, NULL, &ret_handle)); + xt_ints_on(BIT(HLI_QUEUE_SW_INT_NUM)); + } +} + +void hli_queue_shutdown(void) +{ + if (s_meta_queue_ptr != NULL) { + hli_queue_delete(s_meta_queue_ptr); + s_meta_queue_ptr = NULL; + esp_intr_free(ret_handle); + xt_ints_off(BIT(HLI_QUEUE_SW_INT_NUM)); + } +} + +hli_queue_handle_t hli_queue_create(size_t nelem, size_t elem_size, QueueHandle_t downstream) +{ + const size_t buf_elem = nelem + 1; + if (elem_size > HLI_QUEUE_MAX_ELEM_SIZE) { + return NULL; + } + size_t buf_size = buf_elem * elem_size; + hli_queue_handle_t res = (hli_queue_handle_t) heap_caps_malloc(sizeof(struct hli_queue_t) + buf_size, + MALLOC_CAP_INTERNAL|MALLOC_CAP_8BIT); + if (res == NULL) { + return NULL; + } + queue_init(res, buf_size, elem_size, downstream); + return res; +} + +hli_queue_handle_t hli_customer_queue_create(size_t nelem, size_t elem_size, QueueHandle_t downstream) +{ + hli_queue_handle_t res = hli_queue_create(nelem, elem_size, (QueueHandle_t) downstream); + if (res == NULL) { + return NULL; + } + res->flags |= HLI_QUEUE_FLAG_CUSTOMER; + return res; +} + +hli_queue_handle_t hli_semaphore_create(size_t max_count, SemaphoreHandle_t downstream) +{ + const size_t elem_size = 1; + hli_queue_handle_t res = hli_queue_create(max_count, elem_size, (QueueHandle_t) downstream); + if (res == NULL) { + return NULL; + } + res->flags |= HLI_QUEUE_FLAG_SEMAPHORE; + return res; +} + +void hli_queue_delete(hli_queue_handle_t queue) +{ + free(queue); +} + +bool IRAM_ATTR hli_queue_get(hli_queue_handle_t queue, void* out) +{ + uint32_t int_state = hli_intr_disable(); + bool res = false; + if (!queue_empty(queue)) { + memcpy(out, queue->begin, queue->elem_size); + queue->begin = wrap_ptr(queue, queue->begin + queue->elem_size); + res = true; + } + hli_intr_restore(int_state); + return res; +} + +bool IRAM_ATTR hli_queue_put(hli_queue_handle_t queue, const void* data) +{ + uint32_t int_state = hli_intr_disable(); + bool res = false; + bool was_empty = queue_empty(queue); + if (!queue_full(queue)) { + memcpy(queue->end, data, queue->elem_size); + queue->end = wrap_ptr(queue, queue->end + queue->elem_size); + if (was_empty && queue != s_meta_queue_ptr) { + queue_signal(queue); + } + res = true; + } + hli_intr_restore(int_state); + return res; +} + +bool IRAM_ATTR hli_semaphore_give(hli_queue_handle_t queue) +{ + uint8_t data = 0; + return hli_queue_put(queue, &data); +} + +#endif /* CONFIG_BTDM_CTRL_HLI */ diff --git a/lib/bt/controller/esp32/hli_api.h b/lib/bt/controller/esp32/hli_api.h new file mode 100644 index 00000000..3b69156b --- /dev/null +++ b/lib/bt/controller/esp32/hli_api.h @@ -0,0 +1,167 @@ +/* + * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include <stdint.h> +#include "esp_err.h" +#include "esp_intr_alloc.h" +#include "freertos/FreeRTOS.h" +#include "freertos/queue.h" +#include "freertos/semphr.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#if CONFIG_BTDM_CTRL_HLI + +/*** Queues ***/ + +struct hli_queue_t +{ + size_t elem_size; + char* begin; + char* end; + const char* bufend; + QueueHandle_t downstream; + int flags; + char buf[0]; +}; + +/** + * @brief Register a high level interrupt function + * + * @param handler interrupt handler function + * @param arg argument to pass to the interrupt handler + * @param intr_reg address of the peripheral register containing the interrupt status, + * or value 0 to get the status from CPU INTERRUPT register + * @param intr_mask mask of the interrupt, in the interrupt status register + * @return + * - ESP_OK on success + * - ESP_ERR_NO_MEM if too many handlers are registered + */ +esp_err_t hli_intr_register(intr_handler_t handler, void* arg, uint32_t intr_reg, uint32_t intr_mask); + +/** + * @brief Mask all interrupts (including high level ones) on the current CPU + * + * @return uint32_t interrupt status, pass it to hli_intr_restore + */ +uint32_t hli_intr_disable(void); + +/** + * @brief Re-enable interrupts + * + * @param state value returned by hli_intr_disable + */ +void hli_intr_restore(uint32_t state); + +/** + * @brief Type of a hli queue + */ +typedef struct hli_queue_t* hli_queue_handle_t; + +/** + * @brief Initialize hli_queue module. Must be called once before using hli queue APIs. + */ +void hli_queue_setup(void); + +/** + * @brief Shutdown hli_queue module. + */ +void hli_queue_shutdown(void); + +/** + * @brief Create a hli queue, wrapping a FreeRTOS queue + * + * This queue can be used from high level interrupts, + * but **ONLY ON THE CPU WHERE hli_queue_setup WAS CALLED**. Values sent to this + * queue are automatically forwarded to "downstream" FreeRTOS queue using a level 3 + * software interrupt. + * + * @param nelem number of elements in the queue + * @param elem_size size of one element; must match element size of a downstream queue + * @param downstream FreeRTOS queue to send the values to + * @return hli_queue_handle_t handle of the created queue, or NULL on failure + */ +hli_queue_handle_t hli_queue_create(size_t nelem, size_t elem_size, QueueHandle_t downstream); + +/** + * @brief Create a customer hli queue, wrapping a FreeRTOS queue + * + * This queue can be used from high level interrupts, + * but **ONLY ON THE CPU WHERE hli_queue_setup WAS CALLED**. Values sent to this + * queue are automatically forwarded to "downstream" FreeRTOS queue using a level 3 + * software interrupt. + * + * @param nelem number of elements in the queue + * @param elem_size size of one element; must match element size of a downstream queue + * @param downstream FreeRTOS queue to send the values to + * @return hli_queue_handle_t handle of the created queue, or NULL on failure + */ +hli_queue_handle_t hli_customer_queue_create(size_t nelem, size_t elem_size, QueueHandle_t downstream); + +/** + * @brief Create a hli queue, wrapping a FreeRTOS semaphore + * + * See notes on hli_queue_create. + * + * @param max_count maximum semaphore count + * @param downstream FreeRTOS semaphore to forward the calls to + * @return hli_queue_handle_t handle of the created queue, or NULL on failure + */ +hli_queue_handle_t hli_semaphore_create(size_t max_count, SemaphoreHandle_t downstream); + +/** + * @brief Delete a hli queue + * + * Make sure noone is using the queue before deleting it. + * + * @param queue handle returned by hli_queue_create or hli_semaphore_create + */ +void hli_queue_delete(hli_queue_handle_t queue); + +/** + * @brief Get one element from a hli queue + * + * Usually not used, values get sent to a downstream FreeRTOS queue automatically. + * However if downstream queue is NULL, this API can be used to get values from a hli queue. + * + * @param queue handle of a queue + * @param out pointer where to store the element + * @return true if the element was successfully read from the queue + */ +bool hli_queue_get(hli_queue_handle_t queue, void* out); + +/** + * @brief Put one element into a hli queue + * + * This puts copies an element into the queue and raises a software interrupt (level 3). + * In the interrupt, the value is copied to a FreeRTOS "downstream" queue. + * + * Note that if the value does not fit into a downstream queue, no error is returned, + * and the value is lost. + * + * @param queue handle of a queue + * @param data pointer to the element to be sent + * @return true if data was placed into the hli queue successfully + */ +bool hli_queue_put(hli_queue_handle_t queue, const void* data); + +/** + * @brief "Give" a semaphore wrapped by a hli queue + * + * @param queue handle returned by hli_semaphore_create + * @return true if the event was sent to a hli queue successfully + */ +bool hli_semaphore_give(hli_queue_handle_t queue); + +#endif /* CONFIG_BTDM_CTRL_HLI */ + +#ifdef __cplusplus +} +#endif diff --git a/lib/bt/controller/esp32/hli_vectors.S b/lib/bt/controller/esp32/hli_vectors.S new file mode 100644 index 00000000..2af6ffae --- /dev/null +++ b/lib/bt/controller/esp32/hli_vectors.S @@ -0,0 +1,267 @@ +/* + * SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + + +#include <xtensa/coreasm.h> +#include <xtensa/corebits.h> +#include <xtensa/config/system.h> +#include "xtensa_context.h" +#include "sdkconfig.h" +#include "soc/soc.h" + +#if CONFIG_BTDM_CTRL_HLI + +/* Interrupt stack size, for C code. + * TODO: reduce and make configurable. + */ +#define L4_INTR_STACK_SIZE 4096 + +/* Save area for the CPU state: + * - 64 words for the general purpose registers + * - 7 words for some of the special registers: + * - WINDOWBASE, WINDOWSTART — only WINDOWSTART is truly needed + * - SAR, LBEG, LEND, LCOUNT — since the C code might use these + * - EPC1 — since the C code might cause window overflow exceptions + * This is not laid out as standard exception frame structure + * for simplicity of the save/restore code. + */ +#define REG_FILE_SIZE (64 * 4) +#define SPECREG_OFFSET REG_FILE_SIZE +#define SPECREG_SIZE (7 * 4) +#define REG_SAVE_AREA_SIZE (SPECREG_OFFSET + SPECREG_SIZE) + + .data +_l4_intr_stack: + .space L4_INTR_STACK_SIZE +_l4_save_ctx: + .space REG_SAVE_AREA_SIZE + + .section .iram1,"ax" + .global xt_highint4 + .type xt_highint4,@function + .align 4 + +xt_highint4: + +#if CONFIG_ESP32_ECO3_CACHE_LOCK_FIX + /* + Here, Timer2 is used to count a little time(50us). + The subsequent dram0 write operation is blocked due to live lock, which will + cause timer2 to timeout and trigger a level 5 interrupt. + */ + rsr.ccount a0 + addmi a0, a0, (CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ*50) + wsr a0, CCOMPARE2 + + /* Enable Timer 2 interrupt */ + rsr a0, INTENABLE + extui a0, a0, 16, 1 + bnez a0, 1f + movi a0, 0 + xsr a0, INTENABLE /* disable all interrupts */ + /* And a0 with (1 << 16) for Timer 2 interrupt mask */ + addmi a0, a0, (1<<14) + addmi a0, a0, (1<<14) + addmi a0, a0, (1<<14) + addmi a0, a0, (1<<14) + wsr a0, INTENABLE /* Enable Timer 2 */ +1: +#endif + + movi a0, _l4_save_ctx + /* save 4 lower registers */ + s32i a1, a0, 4 + s32i a2, a0, 8 + s32i a3, a0, 12 + rsr a2, EXCSAVE_4 /* holds the value of a0 */ + s32i a2, a0, 0 + + /* Save special registers */ + addi a0, a0, SPECREG_OFFSET + rsr a2, WINDOWBASE + s32i a2, a0, 0 + rsr a2, WINDOWSTART + s32i a2, a0, 4 + rsr a2, SAR + s32i a2, a0, 8 + rsr a2, LBEG + s32i a2, a0, 12 + rsr a2, LEND + s32i a2, a0, 16 + rsr a2, LCOUNT + s32i a2, a0, 20 + rsr a2, EPC1 + s32i a2, a0, 24 + +#if CONFIG_ESP32_ECO3_CACHE_LOCK_FIX + movi a0, 0 + xsr a0, INTENABLE /* disable all interrupts */ + movi a2, ~(1<<16) + and a0, a2, a0 + wsr a0, INTENABLE +#endif + + /* disable exception mode, window overflow */ + movi a0, PS_INTLEVEL(5) | PS_EXCM + wsr a0, PS + rsync + + /* Save the remaining physical registers. + * 4 registers are already saved, which leaves 60 registers to save. + * (FIXME: consider the case when the CPU is configured with physical 32 registers) + * These 60 registers are saved in 5 iterations, 12 registers at a time. + */ + movi a1, 5 + movi a3, _l4_save_ctx + 4 * 4 + + /* This is repeated 5 times, each time the window is shifted by 12 registers. + * We come here with a1 = downcounter, a3 = save pointer, a2 and a0 unused. + */ +1: + s32i a4, a3, 0 + s32i a5, a3, 4 + s32i a6, a3, 8 + s32i a7, a3, 12 + s32i a8, a3, 16 + s32i a9, a3, 20 + s32i a10, a3, 24 + s32i a11, a3, 28 + s32i a12, a3, 32 + s32i a13, a3, 36 + s32i a14, a3, 40 + s32i a15, a3, 44 + + /* We are about to rotate the window, so that a12-a15 will become the new a0-a3. + * Copy a0-a3 to a12-15 to still have access to these values. + * At the same time we can decrement the counter and adjust the save area pointer + */ + + /* a0 is constant (_l4_save_ctx), no need to copy */ + addi a13, a1, -1 /* copy and decrement the downcounter */ + /* a2 is scratch so no need to copy */ + addi a15, a3, 48 /* copy and adjust the save area pointer */ + beqz a13, 2f /* have saved all registers ? */ + rotw 3 /* rotate the window and go back */ + j 1b + + /* the loop is complete */ +2: + rotw 4 /* this brings us back to the original window */ + /* a0 still points to _l4_save_ctx */ + + /* Can clear WINDOWSTART now, all registers are saved */ + rsr a2, WINDOWBASE + /* WINDOWSTART = (1 << WINDOWBASE) */ + movi a3, 1 + ssl a2 + sll a3, a3 + wsr a3, WINDOWSTART + +_highint4_stack_switch: + movi a0, 0 + movi sp, _l4_intr_stack + L4_INTR_STACK_SIZE - 16 + s32e a0, sp, -12 /* For GDB: set null SP */ + s32e a0, sp, -16 /* For GDB: set null PC */ + movi a0, _highint4_stack_switch /* For GDB: cosmetics, for the frame where stack switch happened */ + + /* Set up PS for C, disable all interrupts except NMI and debug, and clear EXCM. */ + movi a6, PS_INTLEVEL(4) | PS_UM | PS_WOE + wsr a6, PS + rsync + + /* Call C handler */ + mov a6, sp + call4 hli_c_handler + + l32e sp, sp, -12 /* switch back to the original stack */ + + /* Done with C handler; re-enable exception mode, disabling window overflow */ + movi a2, PS_INTLEVEL(5) | PS_EXCM /* TOCHECK */ + wsr a2, PS + rsync + + /* Restore the special registers. + * WINDOWSTART will be restored near the end. + */ + movi a0, _l4_save_ctx + SPECREG_OFFSET + l32i a2, a0, 8 + wsr a2, SAR + l32i a2, a0, 12 + wsr a2, LBEG + l32i a2, a0, 16 + wsr a2, LEND + l32i a2, a0, 20 + wsr a2, LCOUNT + l32i a2, a0, 24 + wsr a2, EPC1 + + /* Restoring the physical registers. + * This is the reverse to the saving process above. + */ + + /* Rotate back to the final window, then start loading 12 registers at a time, + * in 5 iterations. + * Again, a1 is the downcounter and a3 is the save area pointer. + * After each rotation, a1 and a3 are copied from a13 and a15. + * To simplify the loop, we put the initial values into a13 and a15. + */ + rotw -4 + movi a15, _l4_save_ctx + 64 * 4 /* point to the end of the save area */ + movi a13, 5 + +1: + /* Copy a1 and a3 from their previous location, + * at the same time decrementing and adjusting the save area pointer. + */ + addi a1, a13, -1 + addi a3, a15, -48 + + /* Load 12 registers */ + l32i a4, a3, 0 + l32i a5, a3, 4 + l32i a6, a3, 8 + l32i a7, a3, 12 + l32i a8, a3, 16 + l32i a9, a3, 20 + l32i a10, a3, 24 + l32i a11, a3, 28 /* ensure PS and EPC written */ + l32i a12, a3, 32 + l32i a13, a3, 36 + l32i a14, a3, 40 + l32i a15, a3, 44 + + /* Done with the loop? */ + beqz a1, 2f + /* If no, rotate the window and repeat */ + rotw -3 + j 1b + +2: + /* Done with the loop. Only 4 registers (a0-a3 in the original window) remain + * to be restored. Also need to restore WINDOWSTART, since all the general + * registers are now in place. + */ + movi a0, _l4_save_ctx + + l32i a2, a0, SPECREG_OFFSET + 4 + wsr a2, WINDOWSTART + + l32i a1, a0, 4 + l32i a2, a0, 8 + l32i a3, a0, 12 + rsr a0, EXCSAVE_4 /* holds the value of a0 before the interrupt handler */ + + /* Return from the interrupt, restoring PS from EPS_4 */ + rfi 4 + +#endif /* CONFIG_BTDM_CTRL_HLI */ + +/* The linker has no reason to link in this file; all symbols it exports are already defined + (weakly!) in the default int handler. Define a symbol here so we can use it to have the + linker inspect this anyway. */ + + .global ld_include_hli_vectors_bt +ld_include_hli_vectors_bt: |
