diff --git a/components/bt/host/nimble/nimble b/components/bt/host/nimble/nimble index 973e7d4279d..f361449c87c 160000 --- a/components/bt/host/nimble/nimble +++ b/components/bt/host/nimble/nimble @@ -1 +1 @@ -Subproject commit 973e7d4279dc153452c52dc4d1da957c1ac7898e +Subproject commit f361449c87c96fe3308fb86127217dd6dc8197de diff --git a/components/esp_hid/include/esp_hid_common.h b/components/esp_hid/include/esp_hid_common.h index 461f48f8673..6abfdb92a47 100644 --- a/components/esp_hid/include/esp_hid_common.h +++ b/components/esp_hid/include/esp_hid_common.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2017-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2017-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -273,9 +273,14 @@ void esp_hid_cod_minor_print(uint8_t cod_min, FILE *fp); /** * @brief Convert BLE disconnect reason to string - * @param reason : The value of the reason * - * @return: a pointer to the string or NULL + * Accepts Bluedroid `esp_gatt_conn_reason_t` values (raw HCI for most + * cases) and NimBLE `BLE_HS_HCI_ERR()` values (`0x200 + HCI status`). + * + * @param transport : HID transport (string map is BLE-only) + * @param reason : Disconnect reason from the host stack + * + * @return: a pointer to the string (never NULL) */ const char *esp_hid_disconnect_reason_str(esp_hid_transport_t transport, int reason); diff --git a/components/esp_hid/src/esp_hid_common.c b/components/esp_hid/src/esp_hid_common.c index 636c53785d0..6df57f7e090 100644 --- a/components/esp_hid/src/esp_hid_common.c +++ b/components/esp_hid/src/esp_hid_common.c @@ -14,6 +14,9 @@ #if (CONFIG_GATTS_ENABLE || CONFIG_GATTC_ENABLE) #include "esp_gatt_defs.h" #endif +#if CONFIG_BT_NIMBLE_ENABLED +#include "host/ble_hs.h" +#endif static const char *TAG = "hid_parser"; typedef struct { @@ -524,22 +527,57 @@ void esp_hid_cod_minor_print(uint8_t cod_min, FILE *fp) } } +/* Core HCI disconnect statuses (Vol 1 Part F). Shared by Bluedroid GATT + * conn reasons and NimBLE BLE_HS_HCI_ERR(hci_status). + */ +static const char *ble_hci_disconn_reason_str(int hci_reason) +{ + switch (hci_reason) { + case 0x05: return "AUTH_FAIL"; + case 0x06: return "PINKEY_MISSING"; + case 0x08: return "TIMEOUT"; + case 0x13: return "TERMINATE_PEER_USER"; + case 0x14: return "TERMINATE_PEER_RESOURCES"; + case 0x15: return "TERMINATE_PEER_POWER_OFF"; + case 0x16: return "TERMINATE_LOCAL_HOST"; + case 0x1f: return "UNSPECIFIED"; + case 0x22: return "LMP_TIMEOUT"; + case 0x28: return "INSTANT_PASSED"; + case 0x3b: return "UNACCEPTABLE_CONN_PARAMS"; + case 0x3d: return "MIC_FAILURE"; + case 0x3e: return "FAIL_ESTABLISH"; + default: return NULL; + } +} + const char *esp_hid_disconnect_reason_str(esp_hid_transport_t transport, int reason) { - if (transport == ESP_HID_TRANSPORT_BLE) { -#if (CONFIG_GATTS_ENABLE || CONFIG_GATTC_ENABLE) - switch ((esp_gatt_conn_reason_t)reason) { - case ESP_GATT_CONN_L2C_FAILURE: return "L2C_FAILURE"; - case ESP_GATT_CONN_TIMEOUT: return "TIMEOUT"; - case ESP_GATT_CONN_TERMINATE_PEER_USER: return "TERMINATE_PEER_USER"; - case ESP_GATT_CONN_TERMINATE_LOCAL_HOST: return "TERMINATE_LOCAL_HOST"; - case ESP_GATT_CONN_LMP_TIMEOUT: return "LMP_TIMEOUT"; - case ESP_GATT_CONN_FAIL_ESTABLISH: return "FAIL_ESTABLISH"; - case ESP_GATT_CONN_CONN_CANCEL: return "CONN_CANCEL"; - case ESP_GATT_CONN_NONE: return "NONE"; - default: break; - } -#endif /* CONFIG_GATTS_ENABLE || CONFIG_GATTC_ENABLE */ + const char *str; + + if (transport != ESP_HID_TRANSPORT_BLE) { + return s_unknown_str; } - return s_unknown_str; + +#if CONFIG_BT_NIMBLE_ENABLED + /* NimBLE GAP posts BLE_HS_HCI_ERR(hci) = 0x200 + hci. Do not fall through: + * HCI 0x01 would collide with Bluedroid ESP_GATT_CONN_L2C_FAILURE. + */ + if (reason > BLE_HS_ERR_HCI_BASE && reason < BLE_HS_ERR_L2C_BASE) { + str = ble_hci_disconn_reason_str(reason - BLE_HS_ERR_HCI_BASE); + return str ? str : s_unknown_str; + } +#endif + +#if (CONFIG_GATTS_ENABLE || CONFIG_GATTC_ENABLE) + switch ((esp_gatt_conn_reason_t)reason) { + case ESP_GATT_CONN_L2C_FAILURE: return "L2C_FAILURE"; + case ESP_GATT_CONN_CONN_CANCEL: return "CONN_CANCEL"; + case ESP_GATT_CONN_NONE: return "NONE"; + default: + break; + } +#endif /* CONFIG_GATTS_ENABLE || CONFIG_GATTC_ENABLE */ + + str = ble_hci_disconn_reason_str(reason); + return str ? str : s_unknown_str; } diff --git a/examples/bluetooth/blufi/main/blufi_example_main.c b/examples/bluetooth/blufi/main/blufi_example_main.c index 2867bbf79d5..260ac971076 100644 --- a/examples/bluetooth/blufi/main/blufi_example_main.c +++ b/examples/bluetooth/blufi/main/blufi_example_main.c @@ -520,6 +520,13 @@ void app_main(void) } ESP_ERROR_CHECK( ret ); +#if !SOC_MPI_SUPPORTED + /* Software 3072-bit modular exponentiation needs ~4.2 kB of contiguous + * internal heap. Start it before Wi-Fi and the BLE host claim their pools, + * otherwise the allocation fails with PSA_ERROR_INSUFFICIENT_MEMORY. */ + blufi_dh_pregen_start(); +#endif + initialise_wifi(); #if CONFIG_BT_CONTROLLER_ENABLED || !CONFIG_BT_NIMBLE_ENABLED @@ -537,7 +544,6 @@ void app_main(void) } #if !SOC_MPI_SUPPORTED - blufi_dh_pregen_start(); blufi_dh_pregen_wait(); esp_blufi_adv_start(); #endif diff --git a/examples/bluetooth/blufi/main/blufi_security.c b/examples/bluetooth/blufi/main/blufi_security.c index 7f2e04fb3f9..9b1d405623e 100644 --- a/examples/bluetooth/blufi/main/blufi_security.c +++ b/examples/bluetooth/blufi/main/blufi_security.c @@ -47,7 +47,7 @@ #define BLUFI_DEC_DOMAIN_STR "blufi_dec" #if !SOC_MPI_SUPPORTED -#define BLUFI_DH_PREGEN_STACK_SIZE 4096 +#define BLUFI_DH_PREGEN_STACK_SIZE 6144 #define BLUFI_DH_PREGEN_PRIO (tskIDLE_PRIORITY + 1) #define BLUFI_DH_PREGEN_WAIT_MS 30000 #define BLUFI_DH_HEAVY_CRYPTO_WDT_MS 30000 @@ -125,11 +125,15 @@ static void blufi_cleanup_negotiation(bool abort_enc, bool abort_dec) * with BLE advertising so only key agreement remains on the critical path * when the phone sends its DH parameters. */ +#define BLUFI_PREGEN_DONE_BIT BIT0 + static psa_key_id_t s_pregen_private_key; static uint8_t s_pregen_public_key[DH_SELF_PUB_KEY_LEN]; static size_t s_pregen_public_key_len; -static SemaphoreHandle_t s_pregen_done; static bool s_pregen_ok; +static bool s_pregen_busy; +static EventGroupHandle_t s_pregen_evt; +static SemaphoreHandle_t s_pregen_req; static TaskHandle_t s_pregen_task_hdl; static void (*s_pregen_complete_cb)(void); @@ -143,12 +147,8 @@ static void blufi_wdt_set_timeout(uint32_t timeout_ms) esp_task_wdt_reconfigure(&cfg); } -static void blufi_dh_pregen_task(void *arg) +static void blufi_dh_pregen_generate(void) { - (void)arg; - - blufi_wdt_set_timeout(BLUFI_DH_HEAVY_CRYPTO_WDT_MS); - psa_key_attributes_t attr = psa_key_attributes_init(); psa_set_key_type(&attr, PSA_KEY_TYPE_DH_KEY_PAIR(PSA_DH_FAMILY_RFC7919)); psa_set_key_bits(&attr, 3072); @@ -170,44 +170,88 @@ static void blufi_dh_pregen_task(void *arg) } BLUFI_INFO("DH keypair pre-generation %s", s_pregen_ok ? "done" : "FAILED"); - - blufi_wdt_set_timeout(CONFIG_ESP_TASK_WDT_TIMEOUT_S * 1000); - - xSemaphoreGive(s_pregen_done); - - if (s_pregen_complete_cb) { - void (*cb)(void) = s_pregen_complete_cb; - s_pregen_complete_cb = NULL; - cb(); - } - - vTaskSuspend(NULL); } -static void blufi_dh_pregen_start_impl(void (*done_cb)(void)) +/* + * The worker outlives every connection. Creating it per keypair would mean + * asking for BLUFI_DH_PREGEN_STACK_SIZE of contiguous internal heap at the + * worst possible moment - right after a disconnect, with Wi-Fi and the BLE host + * fully up - which is where xTaskCreate() fails. + */ +static void blufi_dh_pregen_task(void *arg) { - if (s_pregen_done != NULL) { - return; + (void)arg; + + while (true) { + xSemaphoreTake(s_pregen_req, portMAX_DELAY); + + blufi_wdt_set_timeout(BLUFI_DH_HEAVY_CRYPTO_WDT_MS); + blufi_dh_pregen_generate(); + blufi_wdt_set_timeout(CONFIG_ESP_TASK_WDT_TIMEOUT_S * 1000); + + s_pregen_busy = false; + xEventGroupSetBits(s_pregen_evt, BLUFI_PREGEN_DONE_BIT); + + if (s_pregen_complete_cb) { + void (*cb)(void) = s_pregen_complete_cb; + s_pregen_complete_cb = NULL; + cb(); + } + } +} + +static bool blufi_dh_pregen_worker_ensure(void) +{ + if (s_pregen_task_hdl != NULL) { + return true; } - s_pregen_private_key = 0; - s_pregen_public_key_len = 0; - s_pregen_ok = false; - s_pregen_task_hdl = NULL; - s_pregen_complete_cb = done_cb; - s_pregen_done = xSemaphoreCreateBinary(); - if (s_pregen_done == NULL) { - BLUFI_ERROR("Failed to create pre-gen semaphore"); - return; + if (s_pregen_evt == NULL) { + s_pregen_evt = xEventGroupCreate(); + } + if (s_pregen_req == NULL) { + s_pregen_req = xSemaphoreCreateBinary(); + } + if (s_pregen_evt == NULL || s_pregen_req == NULL) { + BLUFI_ERROR("Failed to create pre-gen sync objects"); + return false; } if (xTaskCreate(blufi_dh_pregen_task, "blufi_pregen", BLUFI_DH_PREGEN_STACK_SIZE, NULL, BLUFI_DH_PREGEN_PRIO, &s_pregen_task_hdl) != pdPASS) { BLUFI_ERROR("Failed to create pre-gen task"); - vSemaphoreDelete(s_pregen_done); - s_pregen_done = NULL; + s_pregen_task_hdl = NULL; + return false; } + + return true; +} + +static void blufi_dh_pregen_start_impl(void (*done_cb)(void)) +{ + if (!blufi_dh_pregen_worker_ensure()) { + /* Advertise anyway; negotiation falls back to inline key generation. */ + if (done_cb) { + done_cb(); + } + return; + } + + /* A keypair is already banked, or one is on its way. */ + if (s_pregen_busy || (s_pregen_ok && s_pregen_private_key != 0)) { + if (done_cb) { + done_cb(); + } + return; + } + + s_pregen_public_key_len = 0; + s_pregen_ok = false; + s_pregen_complete_cb = done_cb; + s_pregen_busy = true; + xEventGroupClearBits(s_pregen_evt, BLUFI_PREGEN_DONE_BIT); + xSemaphoreGive(s_pregen_req); } void blufi_dh_pregen_start(void) @@ -222,29 +266,30 @@ void blufi_dh_pregen_start_with_cb(void (*done_cb)(void)) void blufi_dh_pregen_wait(void) { - if (s_pregen_done == NULL) { + if (s_pregen_evt == NULL) { return; } - xSemaphoreTake(s_pregen_done, portMAX_DELAY); - xSemaphoreGive(s_pregen_done); + xEventGroupWaitBits(s_pregen_evt, BLUFI_PREGEN_DONE_BIT, + pdFALSE, pdTRUE, portMAX_DELAY); } -static void blufi_dh_pregen_cleanup(void) +/** + * @brief Hand the banked keypair to the caller, which takes ownership of it + * + * @return The private key id, or 0 when no usable keypair is banked + */ +static psa_key_id_t blufi_dh_pregen_take(void) { - if (s_pregen_done != NULL) { - xSemaphoreTake(s_pregen_done, portMAX_DELAY); - vSemaphoreDelete(s_pregen_done); - s_pregen_done = NULL; - } - if (s_pregen_task_hdl != NULL) { - vTaskDelete(s_pregen_task_hdl); - s_pregen_task_hdl = NULL; - } - if (s_pregen_private_key != 0) { - psa_destroy_key(s_pregen_private_key); - s_pregen_private_key = 0; + if (!s_pregen_ok || s_pregen_private_key == 0) { + return 0; } + + psa_key_id_t key = s_pregen_private_key; + s_pregen_private_key = 0; s_pregen_ok = false; + xEventGroupClearBits(s_pregen_evt, BLUFI_PREGEN_DONE_BIT); + + return key; } #endif /* !SOC_MPI_SUPPORTED */ @@ -384,19 +429,27 @@ void blufi_dh_negotiate_data_handler(uint8_t *data, int len, uint8_t **output_da * heap before key agreement, and widen the task WDT window. */ bool used_pregen = false; - if (s_pregen_done != NULL && - xSemaphoreTake(s_pregen_done, pdMS_TO_TICKS(BLUFI_DH_PREGEN_WAIT_MS)) == pdTRUE) { - xSemaphoreGive(s_pregen_done); - if (s_pregen_ok && s_pregen_private_key != 0) { - private_key = s_pregen_private_key; - s_pregen_private_key = 0; - memcpy(blufi_sec->self_public_key, s_pregen_public_key, s_pregen_public_key_len); - public_key_len = s_pregen_public_key_len; + if (s_pregen_evt != NULL && + (xEventGroupWaitBits(s_pregen_evt, BLUFI_PREGEN_DONE_BIT, pdFALSE, pdTRUE, + pdMS_TO_TICKS(BLUFI_DH_PREGEN_WAIT_MS)) & BLUFI_PREGEN_DONE_BIT)) { + size_t pregen_len = s_pregen_public_key_len; + private_key = blufi_dh_pregen_take(); + if (private_key != 0) { + memcpy(blufi_sec->self_public_key, s_pregen_public_key, pregen_len); + public_key_len = pregen_len; used_pregen = true; BLUFI_INFO("Using pre-generated DH keypair"); } } + /* Release the DH parameter buffer before any further modular + * exponentiation. Both the inline keypair generation and the key + * agreement below need a single ~4.2 kB contiguous internal + * allocation, so nothing reclaimable may be held across them. */ + uint8_t peer_pub[DH_SELF_PUB_KEY_LEN]; + memcpy(peer_pub, param, pub_len); + blufi_cleanup_dh_param(); + if (!used_pregen) { BLUFI_INFO("Pre-gen unavailable, generating DH keypair inline"); blufi_wdt_set_timeout(BLUFI_DH_HEAVY_CRYPTO_WDT_MS); @@ -412,7 +465,6 @@ void blufi_dh_negotiate_data_handler(uint8_t *data, int len, uint8_t **output_da BLUFI_ERROR("%s psa_generate_key failed %d\n", __func__, status); blufi_wdt_set_timeout(CONFIG_ESP_TASK_WDT_TIMEOUT_S * 1000); btc_blufi_report_error(ESP_BLUFI_DH_MALLOC_ERROR); - blufi_cleanup_dh_param(); return; } psa_reset_key_attributes(&keygen_attr); @@ -424,20 +476,10 @@ void blufi_dh_negotiate_data_handler(uint8_t *data, int len, uint8_t **output_da blufi_wdt_set_timeout(CONFIG_ESP_TASK_WDT_TIMEOUT_S * 1000); psa_destroy_key(private_key); btc_blufi_report_error(ESP_BLUFI_DH_MALLOC_ERROR); - blufi_cleanup_dh_param(); return; } } - uint8_t peer_pub[DH_SELF_PUB_KEY_LEN]; - memcpy(peer_pub, param, pub_len); - blufi_cleanup_dh_param(); - - if (s_pregen_task_hdl != NULL) { - vTaskDelete(s_pregen_task_hdl); - s_pregen_task_hdl = NULL; - } - blufi_wdt_set_timeout(BLUFI_DH_HEAVY_CRYPTO_WDT_MS); status = psa_raw_key_agreement(alg, private_key, peer_pub, pub_len, blufi_sec->share_key, SHARE_KEY_LEN, @@ -738,9 +780,9 @@ void blufi_security_deinit(void) return; } -#if !SOC_MPI_SUPPORTED - blufi_dh_pregen_cleanup(); -#endif + /* The pre-generation worker and any keypair it has banked deliberately + * survive the disconnect: the next connection reuses them, and its task + * stack could not be reallocated once Wi-Fi and the BLE host are up. */ /* Clean up all resources */ blufi_cleanup_negotiation(true, true); diff --git a/examples/bluetooth/blufi/sdkconfig.defaults.esp32c2 b/examples/bluetooth/blufi/sdkconfig.defaults.esp32c2 index 50c9e67317f..a5300e5fda7 100644 --- a/examples/bluetooth/blufi/sdkconfig.defaults.esp32c2 +++ b/examples/bluetooth/blufi/sdkconfig.defaults.esp32c2 @@ -18,3 +18,7 @@ CONFIG_BT_NIMBLE_ROLE_OBSERVER=n CONFIG_BT_NIMBLE_50_FEATURE_SUPPORT=n CONFIG_BT_NIMBLE_SECURITY_ENABLE=n CONFIG_BT_NIMBLE_CRYPTO_STACK_MBEDTLS=n +# ESP32-C2 has no hardware MPI, so the 3072-bit BLUFI Diffie-Hellman runs in +# software and needs ~4.2 kB of contiguous internal heap. BLUFI only exchanges +# small packets, so the 320-byte msys_2 pool is trimmed to leave that headroom. +CONFIG_BT_NIMBLE_MSYS_2_BLOCK_COUNT=8 diff --git a/examples/bluetooth/nimble/ble_cts/cts_cent/main/main.c b/examples/bluetooth/nimble/ble_cts/cts_cent/main/main.c index c10e660b092..5f9870ab41a 100644 --- a/examples/bluetooth/nimble/ble_cts/cts_cent/main/main.c +++ b/examples/bluetooth/nimble/ble_cts/cts_cent/main/main.c @@ -22,7 +22,9 @@ #endif static const char *tag = "NimBLE_CTS_CENT"; +#if CONFIG_EXAMPLE_CI_ID && CONFIG_EXAMPLE_CI_PIPELINE_ID static char remote_device_name[32]; +#endif static int ble_cts_cent_gap_event(struct ble_gap_event *event, void *arg); #if CONFIG_EXAMPLE_CI_ID && CONFIG_EXAMPLE_CI_PIPELINE_ID diff --git a/examples/bluetooth/nimble/ble_htp/htp_cent/main/main.c b/examples/bluetooth/nimble/ble_htp/htp_cent/main/main.c index 32701fb7bcb..912517c17c8 100644 --- a/examples/bluetooth/nimble/ble_htp/htp_cent/main/main.c +++ b/examples/bluetooth/nimble/ble_htp/htp_cent/main/main.c @@ -21,7 +21,9 @@ #endif static const char *tag = "NimBLE_HTP_CENT"; +#if CONFIG_EXAMPLE_CI_ID && CONFIG_EXAMPLE_CI_PIPELINE_ID static char remote_device_name[32]; +#endif static int ble_htp_cent_gap_event(struct ble_gap_event *event, void *arg); void ble_store_config_init(void);