From a3ff4beeb3577cf3cb9b6451c986be56002954bb Mon Sep 17 00:00:00 2001 From: Zhang Hai Peng Date: Fri, 21 Nov 2025 17:53:22 +0800 Subject: [PATCH 01/40] fix(ble/bluedroid): Fixed BLE incorrect device record count issue (cherry picked from commit 9bfd9c5340a14a264380935e8f2fb55d2feb7b8a) Co-authored-by: zhanghaipeng --- .../bt/host/bluedroid/btc/core/btc_ble_storage.c | 2 +- components/bt/host/bluedroid/btc/core/btc_storage.c | 10 +++++----- .../host/bluedroid/common/include/common/bt_target.h | 8 ++++---- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/components/bt/host/bluedroid/btc/core/btc_ble_storage.c b/components/bt/host/bluedroid/btc/core/btc_ble_storage.c index f20a7241e1..aef3863111 100644 --- a/components/bt/host/bluedroid/btc/core/btc_ble_storage.c +++ b/components/bt/host/bluedroid/btc/core/btc_ble_storage.c @@ -16,7 +16,7 @@ #if (SMP_INCLUDED == TRUE) //the maximum number of bonded devices -#define BONED_DEVICES_MAX_COUNT (BTM_SEC_MAX_DEVICE_RECORDS) +#define BONED_DEVICES_MAX_COUNT (BTM_SEC_MAX_BONDS) static void _btc_storage_save(void) { diff --git a/components/bt/host/bluedroid/btc/core/btc_storage.c b/components/bt/host/bluedroid/btc/core/btc_storage.c index 1923dea0da..bee44e18d3 100644 --- a/components/bt/host/bluedroid/btc/core/btc_storage.c +++ b/components/bt/host/bluedroid/btc/core/btc_storage.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2024 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -40,7 +40,7 @@ bt_status_t btc_storage_add_bonded_device(bt_bdaddr_t *remote_bd_addr, bdaddr_to_string(remote_bd_addr, bdstr, sizeof(bdstr)); /* device not in bond list and exceed the maximum number of bonded devices, delete the inactive bonded device */ - if (btc_storage_get_num_all_bond_devices() >= BTM_SEC_MAX_DEVICE_RECORDS && !btc_config_has_section(bdstr)) { + if (btc_storage_get_num_all_bond_devices() >= BTM_SEC_MAX_BONDS && !btc_config_has_section(bdstr)) { const btc_config_section_iter_t *iter = btc_config_section_begin(); const btc_config_section_iter_t *remove_iter = iter; /* find the first device(the last node) */ @@ -57,7 +57,7 @@ bt_status_t btc_storage_add_bonded_device(bt_bdaddr_t *remote_bd_addr, // delete config info if (btc_config_remove_section(remove_section)) { - BTC_TRACE_WARNING("exceeded the maximum nubmer of bonded devices, delete the first device info : %02x:%02x:%02x:%02x:%02x:%02x", + BTC_TRACE_WARNING("exceeded the maximum number of bonded devices, delete the first device info : %02x:%02x:%02x:%02x:%02x:%02x", bd_addr.address[0], bd_addr.address[1], bd_addr.address[2], bd_addr.address[3], bd_addr.address[4], bd_addr.address[5]); } } @@ -141,8 +141,8 @@ static bt_status_t btc_in_fetch_bonded_devices(int add) continue; } dev_cnt ++; - /* if the number of device stored in nvs not exceed to BTM_SEC_MAX_DEVICE_RECORDS, load it */ - if (dev_cnt <= BTM_SEC_MAX_DEVICE_RECORDS) { + /* if the number of device stored in nvs not exceed to BTM_SEC_MAX_BONDS, load it */ + if (dev_cnt <= BTM_SEC_MAX_BONDS) { if (btc_config_exist(name, BTC_STORAGE_LINK_KEY_TYPE_STR) && btc_config_exist(name, BTC_STORAGE_PIN_LENGTH_STR) && btc_config_exist(name, BTC_STORAGE_SC_SUPPORT) && btc_config_exist(name, BTC_STORAGE_LINK_KEY_STR)) { /* load bt device */ diff --git a/components/bt/host/bluedroid/common/include/common/bt_target.h b/components/bt/host/bluedroid/common/include/common/bt_target.h index 23fca3f3d0..6ec7ec52ba 100644 --- a/components/bt/host/bluedroid/common/include/common/bt_target.h +++ b/components/bt/host/bluedroid/common/include/common/bt_target.h @@ -1132,11 +1132,11 @@ /* The number of security records for peer devices. 15 AS Default*/ #ifndef BTM_SEC_MAX_DEVICE_RECORDS -#if (UC_BT_SMP_MAX_BONDS < UC_BT_ACL_CONNECTIONS) -#define BTM_SEC_MAX_DEVICE_RECORDS UC_BT_ACL_CONNECTIONS -#else -#define BTM_SEC_MAX_DEVICE_RECORDS UC_BT_SMP_MAX_BONDS +#define BTM_SEC_MAX_DEVICE_RECORDS (UC_BT_SMP_MAX_BONDS + UC_BT_ACL_CONNECTIONS) #endif + +#ifndef BTM_SEC_MAX_BONDS +#define BTM_SEC_MAX_BONDS UC_BT_SMP_MAX_BONDS #endif #if BTA_SDP_INCLUDED From 131322d502930fd1bf3c22634e44299c3037f45c Mon Sep 17 00:00:00 2001 From: Zhang Hai Peng Date: Fri, 21 Nov 2025 21:53:24 +0800 Subject: [PATCH 02/40] fix(ble/bluedroid): Fixed missing BLE connect and disconnect events (cherry picked from commit 2dcbd64616a27668e6f2f7083681452b36091b24) Co-authored-by: zhanghaipeng --- components/bt/host/bluedroid/stack/btm/btm_ble.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/components/bt/host/bluedroid/stack/btm/btm_ble.c b/components/bt/host/bluedroid/stack/btm/btm_ble.c index 8f676a655a..27ebe49dcf 100644 --- a/components/bt/host/bluedroid/stack/btm/btm_ble.c +++ b/components/bt/host/bluedroid/stack/btm/btm_ble.c @@ -1704,6 +1704,9 @@ tBTM_STATUS btm_ble_start_encrypt(BD_ADDR bda, BOOLEAN use_stk, BT_OCTET16 stk) #if (SMP_INCLUDED == TRUE) void btm_ble_link_encrypted(BD_ADDR bd_addr, UINT8 encr_enable) { +#if BLE_INCLUDED == TRUE + l2cble_notify_le_connection(bd_addr); +#endif // BLE_INCLUDED == TRUE tBTM_SEC_DEV_REC *p_dev_rec = btm_find_dev (bd_addr); BOOLEAN enc_cback; From 62edb3f79cf5074e40c437ce37ab3b117336a8e6 Mon Sep 17 00:00:00 2001 From: morris Date: Mon, 24 Nov 2025 16:03:46 +0800 Subject: [PATCH 03/40] chor(mcpwm): replace p4 psram test --- .../{sdkconfig.defaults.esp32p4 => sdkconfig.defaults.esp32c5} | 1 - 1 file changed, 1 deletion(-) rename components/esp_driver_mcpwm/test_apps/mcpwm/{sdkconfig.defaults.esp32p4 => sdkconfig.defaults.esp32c5} (68%) diff --git a/components/esp_driver_mcpwm/test_apps/mcpwm/sdkconfig.defaults.esp32p4 b/components/esp_driver_mcpwm/test_apps/mcpwm/sdkconfig.defaults.esp32c5 similarity index 68% rename from components/esp_driver_mcpwm/test_apps/mcpwm/sdkconfig.defaults.esp32p4 rename to components/esp_driver_mcpwm/test_apps/mcpwm/sdkconfig.defaults.esp32c5 index d2699b2221..db575808cf 100644 --- a/components/esp_driver_mcpwm/test_apps/mcpwm/sdkconfig.defaults.esp32p4 +++ b/components/esp_driver_mcpwm/test_apps/mcpwm/sdkconfig.defaults.esp32c5 @@ -1,3 +1,2 @@ CONFIG_SPIRAM=y -CONFIG_SPIRAM_MODE_HEX=y CONFIG_SPIRAM_MALLOC_ALWAYSINTERNAL=0 From e3f03bb165239e20a7ece7f4d4d9675bdcd310bf Mon Sep 17 00:00:00 2001 From: Zhang Hai Peng Date: Thu, 27 Nov 2025 20:10:50 +0800 Subject: [PATCH 04/40] fix(ble/bluedroid): Fix potential CVE-2020-0022 in reassemble_and_dispatch - Reference: https://nvd.nist.gov/vuln/detail/CVE-2020-0022 (cherry picked from commit 1f7fd91b5a3b6a753bc0d93df1f3f0cb9fb8a21a) Co-authored-by: zhanghaipeng --- .../bt/host/bluedroid/hci/packet_fragmenter.c | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/components/bt/host/bluedroid/hci/packet_fragmenter.c b/components/bt/host/bluedroid/hci/packet_fragmenter.c index d8f2ff918f..9b444ab69c 100644 --- a/components/bt/host/bluedroid/hci/packet_fragmenter.c +++ b/components/bt/host/bluedroid/hci/packet_fragmenter.c @@ -161,6 +161,13 @@ static void reassemble_and_dispatch(BT_HDR *packet) osi_free(partial_packet); } + /* Check for integer overflow in length calculation */ + if (l2cap_length > (UINT16_MAX - L2CAP_HEADER_SIZE - HCI_ACL_PREAMBLE_SIZE)) { + HCI_TRACE_ERROR("L2CAP length too large: %u", l2cap_length); + osi_free(packet); + return; + } + uint16_t full_length = l2cap_length + L2CAP_HEADER_SIZE + HCI_ACL_PREAMBLE_SIZE; if (full_length <= packet->len) { if (full_length < packet->len) { @@ -200,6 +207,20 @@ static void reassemble_and_dispatch(BT_HDR *packet) packet->offset += HCI_ACL_PREAMBLE_SIZE; // skip ACL preamble packet->len -= HCI_ACL_PREAMBLE_SIZE; + + // CVE-2020-0022 (BlueFrag) Fix: Prevent integer underflow + if (partial_packet->offset > partial_packet->len) { + HCI_TRACE_ERROR("%s offset exceeds expected length. Dropping packet.\n", __func__); + osi_free(packet); + return; + } + + if (packet->len > UINT16_MAX - partial_packet->offset) { + HCI_TRACE_ERROR("%s: packet->len too large, would overflow. Dropping packet.\n", __func__); + osi_free(packet); + return; + } + uint16_t projected_offset = partial_packet->offset + packet->len; if (projected_offset > partial_packet->len) { // len stores the expected length HCI_TRACE_ERROR("%s got packet which would exceed expected length of %d. Truncating.\n", __func__, partial_packet->len); From 1d22e44e51526651f7fb285e27f9e39ff72f9f44 Mon Sep 17 00:00:00 2001 From: Zhang Hai Peng Date: Thu, 27 Nov 2025 20:11:01 +0800 Subject: [PATCH 05/40] fix(ble/bluedroid): Fix potential CVE-2024-0039 out-of-bounds write in attp_build_value_cmd - Reference: https://source.android.com/docs/security/bulletin/2024-03-01?hl=zh-cn (cherry picked from commit e1d39f630f7a5a8a3390429c42cd53329c4d0bb3) Co-authored-by: zhanghaipeng --- .../host/bluedroid/stack/gatt/att_protocol.c | 112 +++++++++++------- 1 file changed, 72 insertions(+), 40 deletions(-) diff --git a/components/bt/host/bluedroid/stack/gatt/att_protocol.c b/components/bt/host/bluedroid/stack/gatt/att_protocol.c index 310a9756ef..b336aecc38 100644 --- a/components/bt/host/bluedroid/stack/gatt/att_protocol.c +++ b/components/bt/host/bluedroid/stack/gatt/att_protocol.c @@ -285,57 +285,89 @@ BT_HDR *attp_build_opcode_cmd(UINT8 op_code) ** Returns None. ** *******************************************************************************/ -BT_HDR *attp_build_value_cmd (UINT16 payload_size, UINT8 op_code, UINT16 handle, - UINT16 offset, UINT16 len, UINT8 *p_data) +BT_HDR *attp_build_value_cmd(UINT16 payload_size, UINT8 op_code, + UINT16 handle, UINT16 offset, + UINT16 len, UINT8 *p_data) { - BT_HDR *p_buf = NULL; - UINT8 *p, *pp, pair_len, *p_pair_len; + BT_HDR *p_buf = NULL; + UINT8 *p = NULL, *pp = NULL, *p_pair_len = NULL; + size_t pair_len = 0; + size_t size_now = 1; /* track current buffer size including op_code */ - if ((p_buf = (BT_HDR *)osi_malloc((UINT16)(sizeof(BT_HDR) + payload_size + L2CAP_MIN_OFFSET))) != NULL) { - p = pp = (UINT8 *)(p_buf + 1) + L2CAP_MIN_OFFSET; +#define CHECK_SIZE() \ + do { \ + if (size_now > payload_size) { \ + GATT_TRACE_ERROR("payload size too small"); \ + osi_free(p_buf); \ + return NULL; \ + } \ + } while (0) - UINT8_TO_STREAM (p, op_code); - p_buf->offset = L2CAP_MIN_OFFSET; - p_buf->len = 1; + /* allocate buffer with extra space for L2CAP offset */ + p_buf = (BT_HDR *)osi_malloc(sizeof(BT_HDR) + payload_size + L2CAP_MIN_OFFSET); + if (!p_buf) return NULL; - if (op_code == GATT_RSP_READ_BY_TYPE) { - p_pair_len = p; - pair_len = len + 2; - UINT8_TO_STREAM (p, pair_len); - p_buf->len += 1; - } - if (op_code != GATT_RSP_READ_BLOB && op_code != GATT_RSP_READ && op_code != GATT_HANDLE_MULTI_VALUE_NOTIF) { - UINT16_TO_STREAM (p, handle); - p_buf->len += 2; + p = pp = (UINT8 *)(p_buf + 1) + L2CAP_MIN_OFFSET; + + CHECK_SIZE(); + UINT8_TO_STREAM(p, op_code); + p_buf->offset = L2CAP_MIN_OFFSET; + + /* handle Read By Type response: reserve space for pair_len */ + if (op_code == GATT_RSP_READ_BY_TYPE) { + p_pair_len = p++; + pair_len = len + 2; /* handle(2 bytes) + value length */ + size_now += 1; + CHECK_SIZE(); + /* pair_len will be backfilled after value is written */ + } + + /* write handle if needed */ + if (op_code != GATT_RSP_READ_BLOB && op_code != GATT_RSP_READ && + op_code != GATT_HANDLE_MULTI_VALUE_NOTIF) { + size_now += 2; + CHECK_SIZE(); + UINT16_TO_STREAM(p, handle); + } + + /* write offset for prepare write requests */ + if (op_code == GATT_REQ_PREPARE_WRITE || op_code == GATT_RSP_PREPARE_WRITE) { + size_now += 2; + CHECK_SIZE(); + UINT16_TO_STREAM(p, offset); + } + + /* write value data, ensure it does not exceed payload_size */ + if (len > 0 && p_data != NULL) { + if (payload_size - size_now < len) { + len = payload_size - size_now; + if (op_code == GATT_RSP_READ_BY_TYPE) { + pair_len = len + 2; + } + GATT_TRACE_WARNING("attribute value too long, truncated to %d", len); } - if (op_code == GATT_REQ_PREPARE_WRITE || op_code == GATT_RSP_PREPARE_WRITE ) { - UINT16_TO_STREAM (p, offset); - p_buf->len += 2; - } + size_now += len; + CHECK_SIZE(); - if(payload_size < GATT_DEF_BLE_MTU_SIZE || payload_size > GATT_MAX_MTU_SIZE) { - GATT_TRACE_ERROR("invalid payload_size %d", payload_size); + ARRAY_TO_STREAM(p, p_data, len); + } + + /* backfill pair_len for Read By Type response */ + if (op_code == GATT_RSP_READ_BY_TYPE) { + if (pair_len > UINT8_MAX) { + GATT_TRACE_ERROR("pair_len > UINT8_MAX"); osi_free(p_buf); return NULL; } - - if (len > 0 && p_data != NULL) { - /* ensure data not exceed MTU size */ - if (payload_size - p_buf->len < len) { - len = payload_size - p_buf->len; - /* update handle value pair length */ - if (op_code == GATT_RSP_READ_BY_TYPE) { - *p_pair_len = (len + 2); - } - - GATT_TRACE_WARNING("attribute value too long, to be truncated to %d", len); - } - - ARRAY_TO_STREAM (p, p_data, len); - p_buf->len += len; - } + *p_pair_len = (UINT8)pair_len; } + + /* update buffer length */ + p_buf->len = (UINT16)size_now; + +#undef CHECK_SIZE + return p_buf; } From 316c5a589a0b92b7a56cb51c699e9ead1ab82d56 Mon Sep 17 00:00:00 2001 From: Zhang Hai Peng Date: Thu, 27 Nov 2025 20:11:02 +0800 Subject: [PATCH 06/40] fix(ble/bluedroid): Fix potential out-of-bounds issue - add length check in hci_hal_h4_hdl_rx_packet to prevent OOB - add adv data length check in btm_ble_cache_adv_data - add indicate data length check in BTA_GATTS_HandleValueIndication - add report length check in bta_hh_parse_keybd_rpt - add report length check in BTA_HdSendReport - add descriptor length check in BTA_HdRegisterApp - prevent buffer overflow in attribute processing (cherry picked from commit 71efec78c59ccc3894012797ff6354435e8ed7b9) Co-authored-by: zhanghaipeng --- .../host/bluedroid/bta/gatt/bta_gatts_api.c | 10 ++++++++- .../bt/host/bluedroid/bta/hd/bta_hd_api.c | 21 +++++++++++++++++++ .../bt/host/bluedroid/bta/hh/bta_hh_utils.c | 14 ++++++++++++- components/bt/host/bluedroid/hci/hci_hal_h4.c | 7 ++++--- .../bt/host/bluedroid/stack/btm/btm_ble_gap.c | 7 +++++++ 5 files changed, 54 insertions(+), 5 deletions(-) diff --git a/components/bt/host/bluedroid/bta/gatt/bta_gatts_api.c b/components/bt/host/bluedroid/bta/gatt/bta_gatts_api.c index 4e1f76f636..b712da9b79 100644 --- a/components/bt/host/bluedroid/bta/gatt/bta_gatts_api.c +++ b/components/bt/host/bluedroid/bta/gatt/bta_gatts_api.c @@ -241,7 +241,7 @@ void BTA_GATTS_AddCharacteristic (UINT16 service_id, const tBT_UUID * p_char_u p_buf->attr_val.attr_max_len = attr_val->attr_max_len; p_buf->attr_val.attr_val = (uint8_t *)osi_malloc(len); if(p_buf->attr_val.attr_val != NULL){ - memcpy(p_buf->attr_val.attr_val, attr_val->attr_val, attr_val->attr_len); + memcpy(p_buf->attr_val.attr_val, attr_val->attr_val, len); } } @@ -411,6 +411,14 @@ void BTA_GATTS_StopService(UINT16 service_id) void BTA_GATTS_HandleValueIndication (UINT16 conn_id, UINT16 attr_id, UINT16 data_len, UINT8 *p_data, BOOLEAN need_confirm) { + + /* Validate data length against buffer size */ + if (data_len > BTA_GATT_MAX_ATTR_LEN) { + APPL_TRACE_ERROR("GATT indication data too large: %u > %u", + data_len, BTA_GATT_MAX_ATTR_LEN); + return; + } + tBTA_GATTS_API_INDICATION *p_buf; UINT16 len = sizeof(tBTA_GATTS_API_INDICATION); diff --git a/components/bt/host/bluedroid/bta/hd/bta_hd_api.c b/components/bt/host/bluedroid/bta/hd/bta_hd_api.c index 33875b957a..36aa540d29 100644 --- a/components/bt/host/bluedroid/bta/hd/bta_hd_api.c +++ b/components/bt/host/bluedroid/bta/hd/bta_hd_api.c @@ -89,6 +89,20 @@ void BTA_HdDisable(void) ******************************************************************************/ extern void BTA_HdRegisterApp(tBTA_HD_APP_INFO *p_app_info, tBTA_HD_QOS_INFO *p_in_qos, tBTA_HD_QOS_INFO *p_out_qos) { + + /* Validate descriptor length before copying */ + if (p_app_info->descriptor.dl_len > BTA_HD_APP_DESCRIPTOR_LEN) { + APPL_TRACE_ERROR("HID descriptor too large: %u > %u", + p_app_info->descriptor.dl_len, BTA_HD_APP_DESCRIPTOR_LEN); + return; + } + + /* Validate descriptor data pointer */ + if (p_app_info->descriptor.dl_len > 0 && p_app_info->descriptor.dsc_list == NULL) { + APPL_TRACE_ERROR("HID descriptor data NULL but length > 0: %u", p_app_info->descriptor.dl_len); + return; + } + tBTA_HD_REGISTER_APP *p_buf; APPL_TRACE_API("%s", __func__); if ((p_buf = (tBTA_HD_REGISTER_APP *)osi_malloc(sizeof(tBTA_HD_REGISTER_APP))) != NULL) { @@ -158,6 +172,13 @@ extern void BTA_HdSendReport(tBTA_HD_REPORT *p_report) __func__, p_report->len, BTA_HD_REPORT_LEN); return; } + + /* Validate report data pointer */ + if (p_report->len > 0 && p_report->p_data == NULL) { + APPL_TRACE_ERROR("HID report data pointer NULL but length > 0: %d", p_report->len); + return; + } + if ((p_buf = (tBTA_HD_SEND_REPORT *)osi_malloc(sizeof(tBTA_HD_SEND_REPORT))) != NULL) { p_buf->hdr.event = BTA_HD_API_SEND_REPORT_EVT; p_buf->use_intr = p_report->use_intr; diff --git a/components/bt/host/bluedroid/bta/hh/bta_hh_utils.c b/components/bt/host/bluedroid/bta/hh/bta_hh_utils.c index 6dc3ec02dd..7c89ed004d 100644 --- a/components/bt/host/bluedroid/bta/hh/bta_hh_utils.c +++ b/components/bt/host/bluedroid/bta/hh/bta_hh_utils.c @@ -263,6 +263,18 @@ void bta_hh_parse_keybd_rpt(tBTA_HH_BOOT_RPT *p_kb_data, UINT8 *p_report, UINT16 xx, yy, key_idx = 0; UINT8 this_report[BTA_HH_MAX_RPT_CHARS]; + /* Validate report length before processing */ + if (report_len > BTA_HH_MAX_RPT_CHARS) { + APPL_TRACE_ERROR("HID report length exceeds maximum: %u > %u", + report_len, BTA_HH_MAX_RPT_CHARS); + return; + } + + if (report_len == 0 || p_report == NULL) { + APPL_TRACE_ERROR("Invalid HID report data"); + return; + } + #if BTA_HH_DEBUG APPL_TRACE_DEBUG("bta_hh_parse_keybd_rpt: (report=%p, report_len=%d) called", p_report, report_len); @@ -463,7 +475,7 @@ void bta_hh_cleanup_disable(tBTA_HH_STATUS status) if (bta_hh_cb.p_cback) { (*bta_hh_cb.p_cback)(BTA_HH_DISABLE_EVT, (tBTA_HH*)&status); - /* all connections are down, no waiting for diconnect */ + /* all connections are down, no waiting for disconnect */ memset(&bta_hh_cb, 0, sizeof(tBTA_HH_CB)); } } diff --git a/components/bt/host/bluedroid/hci/hci_hal_h4.c b/components/bt/host/bluedroid/hci/hci_hal_h4.c index 5baf97fc08..f91a2260d9 100644 --- a/components/bt/host/bluedroid/hci/hci_hal_h4.c +++ b/components/bt/host/bluedroid/hci/hci_hal_h4.c @@ -508,9 +508,10 @@ static void hci_hal_h4_hdl_rx_packet(BT_HDR *packet) STREAM_TO_UINT8(length, stream); } - if ((length + hdr_size) != packet->len) { - HCI_TRACE_ERROR("Wrong packet length type=%d hdr_len=%d pd_len=%d " - "pkt_len=%d", type, hdr_size, length, packet->len); + // Prevents integer wrap-around when calculating (length + hdr_size). + if (length != (packet->len - hdr_size)) { + HCI_TRACE_ERROR("%s: SECURITY: parameter length (%d) exceeds packet bounds (%d)", + __func__, length, packet->len - hdr_size); osi_free(packet); return; } diff --git a/components/bt/host/bluedroid/stack/btm/btm_ble_gap.c b/components/bt/host/bluedroid/stack/btm/btm_ble_gap.c index faf2e1848f..973f32754a 100644 --- a/components/bt/host/bluedroid/stack/btm/btm_ble_gap.c +++ b/components/bt/host/bluedroid/stack/btm/btm_ble_gap.c @@ -3112,6 +3112,13 @@ void btm_ble_cache_adv_data(BD_ADDR bda, tBTM_INQ_RESULTS *p_cur, UINT8 data_len p_cur->scan_rsp_len = 0; } + /* Additional validation to prevent potential integer overflow */ + if (data_len > BTM_BLE_CACHE_ADV_DATA_MAX) { + BTM_TRACE_ERROR("BLE advertising data length exceeds maximum: %u > %u", + data_len, BTM_BLE_CACHE_ADV_DATA_MAX); + return; + } + if (data_len > 0) { p_cache = &p_le_inq_cb->adv_data_cache[p_le_inq_cb->adv_len]; if((data_len + p_le_inq_cb->adv_len) <= BTM_BLE_CACHE_ADV_DATA_MAX) { From 71c8c3431869753ff3a89c3091fdac1912dce8a8 Mon Sep 17 00:00:00 2001 From: Rahul Tank Date: Tue, 2 Dec 2025 11:55:53 +0530 Subject: [PATCH 07/40] fix(nimble): Add change to cancel sync in case of reattempt --- components/bt/host/nimble/nimble | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/bt/host/nimble/nimble b/components/bt/host/nimble/nimble index 6f9ff29d26..9551ac31af 160000 --- a/components/bt/host/nimble/nimble +++ b/components/bt/host/nimble/nimble @@ -1 +1 @@ -Subproject commit 6f9ff29d261939945c4f2fa74c01763009cb2cd0 +Subproject commit 9551ac31af0348d7de6cbe7527de3e5ba205460d From 9112b87c1913bb3f23ce4d317671278b3e46c757 Mon Sep 17 00:00:00 2001 From: Zhang Hai Peng Date: Wed, 3 Dec 2025 11:54:15 +0800 Subject: [PATCH 08/40] fix(ble/bluedroid): Add bounds check in ble_ancs example (cherry picked from commit 7e876249dfbc2da5b652665637d5e2f8dcdff811) Co-authored-by: zhanghaipeng --- .../bluedroid/ble/ble_ancs/main/ble_ancs.c | 14 ++++++-- .../ble/ble_ancs/main/ble_ancs_demo.c | 34 ++++++++++++++++--- 2 files changed, 42 insertions(+), 6 deletions(-) diff --git a/examples/bluetooth/bluedroid/ble/ble_ancs/main/ble_ancs.c b/examples/bluetooth/bluedroid/ble/ble_ancs/main/ble_ancs.c index 379d1ceda5..813a677b51 100644 --- a/examples/bluetooth/bluedroid/ble/ble_ancs/main/ble_ancs.c +++ b/examples/bluetooth/bluedroid/ble/ble_ancs/main/ble_ancs.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2021-2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Unlicense OR CC0-1.0 */ @@ -126,14 +126,24 @@ void esp_receive_apple_data_source(uint8_t *message, uint16_t message_len) switch (Command_id) { case CommandIDGetNotificationAttributes: { + // Security fix: Check minimum message length before accessing message[1..4] + if (message_len < 5) { + ESP_LOGE(BLE_ANCS_TAG, "Message too short for NotificationAttributes"); + break; + } uint32_t NotificationUID = (message[1]) | (message[2]<< 8) | (message[3]<< 16) | (message[4] << 24); uint32_t remian_attr_len = message_len - 5; uint8_t *attrs = &message[5]; ESP_LOGI(BLE_ANCS_TAG, "recevice Notification Attributes response Command_id %d NotificationUID %" PRIu32, Command_id, NotificationUID); while(remian_attr_len > 0) { + // Security fix: Need at least 3 bytes for AttributeID(1) + len(2) + if (remian_attr_len < 3) { + ESP_LOGE(BLE_ANCS_TAG, "Incomplete attribute header"); + break; + } uint8_t AttributeID = attrs[0]; uint16_t len = attrs[1] | (attrs[2] << 8); - if(len > (remian_attr_len -3)) { + if(len > (remian_attr_len - 3)) { ESP_LOGE(BLE_ANCS_TAG, "data error"); break; } diff --git a/examples/bluetooth/bluedroid/ble/ble_ancs/main/ble_ancs_demo.c b/examples/bluetooth/bluedroid/ble/ble_ancs/main/ble_ancs_demo.c index 04994799e8..4dfb0bd4b1 100644 --- a/examples/bluetooth/bluedroid/ble/ble_ancs/main/ble_ancs_demo.c +++ b/examples/bluetooth/bluedroid/ble/ble_ancs/main/ble_ancs_demo.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2021-2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Unlicense OR CC0-1.0 */ @@ -31,6 +31,8 @@ #define ADV_CONFIG_FLAG (1 << 0) #define SCAN_RSP_CONFIG_FLAG (1 << 1) #define INVALID_HANDLE 0 +#define ANCS_CMD_BUFFER_MAX_SIZE 600 + static uint8_t adv_config_done = 0; static bool get_service = false; static esp_gattc_char_elem_t *char_elem_result = NULL; @@ -168,15 +170,26 @@ esp_noti_attr_list_t p_attr[8] = { void esp_get_notification_attributes(uint8_t *notificationUID, uint8_t num_attr, esp_noti_attr_list_t *p_attr) { - uint8_t cmd[600] = {0}; + uint8_t cmd[ANCS_CMD_BUFFER_MAX_SIZE] = {0}; uint32_t index = 0; + cmd[0] = CommandIDGetNotificationAttributes; index ++; memcpy(&cmd[index], notificationUID, ESP_NOTIFICATIONUID_LEN); index += ESP_NOTIFICATIONUID_LEN; while(num_attr > 0) { + // Security fix: Check buffer boundary before writing + if (index >= ANCS_CMD_BUFFER_MAX_SIZE) { + ESP_LOGE(BLE_ANCS_TAG, "Command buffer overflow in get_notification_attributes"); + return; + } cmd[index ++] = p_attr->noti_attribute_id; if (p_attr->attribute_len > 0) { + // Need 2 more bytes for attribute_len + if ((index + 2) > ANCS_CMD_BUFFER_MAX_SIZE) { + ESP_LOGE(BLE_ANCS_TAG, "Command buffer overflow in get_notification_attributes"); + return; + } cmd[index ++] = p_attr->attribute_len; cmd[index ++] = (p_attr->attribute_len << 8); } @@ -195,8 +208,15 @@ void esp_get_notification_attributes(uint8_t *notificationUID, uint8_t num_attr, void esp_get_app_attributes(uint8_t *appidentifier, uint16_t appidentifier_len, uint8_t num_attr, uint8_t *p_app_attrs) { - uint8_t buffer[600] = {0}; + uint8_t buffer[ANCS_CMD_BUFFER_MAX_SIZE] = {0}; uint32_t index = 0; + + // Security fix: Check buffer boundary before memcpy + if ((1 + appidentifier_len + num_attr) > ANCS_CMD_BUFFER_MAX_SIZE) { + ESP_LOGE(BLE_ANCS_TAG, "Buffer overflow in get_app_attributes"); + return; + } + buffer[0] = CommandIDGetAppAttributes; index ++; memcpy(&buffer[index], appidentifier, appidentifier_len); @@ -215,7 +235,7 @@ void esp_get_app_attributes(uint8_t *appidentifier, uint16_t appidentifier_len, void esp_perform_notification_action(uint8_t *notificationUID, uint8_t ActionID) { - uint8_t buffer[600] = {0}; + uint8_t buffer[ANCS_CMD_BUFFER_MAX_SIZE] = {0}; uint32_t index = 0; buffer[0] = CommandIDPerformNotificationAction; index ++; @@ -517,6 +537,12 @@ static void gattc_profile_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_ esp_get_notification_attributes(notificationUID, sizeof(p_attr)/sizeof(esp_noti_attr_list_t), p_attr); } } else if (param->notify.handle == gl_profile_tab[PROFILE_A_APP_ID].data_source_handle) { + if ((data_buffer.len + param->notify.value_len) > sizeof(data_buffer.buffer)) { + ESP_LOGE(BLE_ANCS_TAG, "Data source buffer overflow detected, discarding data"); + memset(data_buffer.buffer, 0, sizeof(data_buffer.buffer)); + data_buffer.len = 0; + break; + } memcpy(&data_buffer.buffer[data_buffer.len], param->notify.value, param->notify.value_len); data_buffer.len += param->notify.value_len; if (param->notify.value_len == (gl_profile_tab[PROFILE_A_APP_ID].MTU_size - 3)) { From 7ca7eb47c49c852cef7ee23bec31c6a9704ef969 Mon Sep 17 00:00:00 2001 From: Zhang Hai Peng Date: Wed, 3 Dec 2025 11:54:15 +0800 Subject: [PATCH 09/40] fix(ble/bluedroid): Use snprintf in ble_eddystone example (cherry picked from commit 24e023e307062c668dd85178b66947dddd534ea8) Co-authored-by: zhanghaipeng --- .../main/esp_eddystone_api.c | 30 ++++++++++++++----- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/examples/bluetooth/bluedroid/ble/ble_eddystone_receiver/main/esp_eddystone_api.c b/examples/bluetooth/bluedroid/ble/ble_eddystone_receiver/main/esp_eddystone_api.c index 2832116506..e3051316e9 100644 --- a/examples/bluetooth/bluedroid/ble/ble_eddystone_receiver/main/esp_eddystone_api.c +++ b/examples/bluetooth/bluedroid/ble/ble_eddystone_receiver/main/esp_eddystone_api.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2021-2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Unlicense OR CC0-1.0 */ @@ -21,6 +21,7 @@ #include "esp_eddystone_protocol.h" #include "esp_eddystone_api.h" +#define EDDYSTONE_URL_BUF_SIZE 100 /* Declare static functions */ static esp_err_t esp_eddystone_uid_received(const uint8_t* buf, uint8_t len, esp_eddystone_result_t* res); @@ -101,18 +102,33 @@ static esp_err_t esp_eddystone_uid_received(const uint8_t* buf, uint8_t len, esp static char* esp_eddystone_resolve_url_scheme(const uint8_t *url_start, const uint8_t *url_end) { int pos = 0; - static char url_buf[100] = {0}; + static char url_buf[EDDYSTONE_URL_BUF_SIZE] = {0}; const uint8_t *p = url_start; + int written; - pos += sprintf(&url_buf[pos], "%s", eddystone_url_prefix[*p++]); + // Security fix: Use snprintf instead of sprintf to prevent buffer overflow + written = snprintf(&url_buf[pos], EDDYSTONE_URL_BUF_SIZE - pos, "%s", eddystone_url_prefix[*p++]); + if (written < 0 || written >= (EDDYSTONE_URL_BUF_SIZE - pos)) { + url_buf[EDDYSTONE_URL_BUF_SIZE - 1] = '\0'; + return url_buf; + } + pos += written; for (; p <= url_end; p++) { - if (esp_eddystone_is_char_invalid((*p))) { - pos += sprintf(&url_buf[pos], "%s", eddystone_url_encoding[*p]); - } else { - pos += sprintf(&url_buf[pos], "%c", *p); + if (pos >= EDDYSTONE_URL_BUF_SIZE - 1) { + break; } + if (esp_eddystone_is_char_invalid((*p))) { + written = snprintf(&url_buf[pos], EDDYSTONE_URL_BUF_SIZE - pos, "%s", eddystone_url_encoding[*p]); + } else { + written = snprintf(&url_buf[pos], EDDYSTONE_URL_BUF_SIZE - pos, "%c", *p); + } + if (written < 0 || written >= (EDDYSTONE_URL_BUF_SIZE - pos)) { + break; + } + pos += written; } + url_buf[EDDYSTONE_URL_BUF_SIZE - 1] = '\0'; return url_buf; } From 45835b025fd770ced104c078f3903637afba8d75 Mon Sep 17 00:00:00 2001 From: Zhang Hai Peng Date: Wed, 3 Dec 2025 11:54:16 +0800 Subject: [PATCH 10/40] fix(ble/bluedroid): Use calloc in gatt_server example (cherry picked from commit e26b60090d0452523ca7fb730dd2cebb047c1997) Co-authored-by: zhanghaipeng --- examples/bluetooth/bluedroid/ble/gatt_server/main/gatts_demo.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/bluetooth/bluedroid/ble/gatt_server/main/gatts_demo.c b/examples/bluetooth/bluedroid/ble/gatt_server/main/gatts_demo.c index a6d8912fff..a8b8254bc4 100644 --- a/examples/bluetooth/bluedroid/ble/gatt_server/main/gatts_demo.c +++ b/examples/bluetooth/bluedroid/ble/gatt_server/main/gatts_demo.c @@ -282,7 +282,8 @@ void example_write_event_env(esp_gatt_if_t gatts_if, prepare_type_env_t *prepare } } - esp_gatt_rsp_t *gatt_rsp = (esp_gatt_rsp_t *)malloc(sizeof(esp_gatt_rsp_t)); + // Security fix: Use calloc to ensure memory is zero-initialized + esp_gatt_rsp_t *gatt_rsp = (esp_gatt_rsp_t *)calloc(1, sizeof(esp_gatt_rsp_t)); if (gatt_rsp) { gatt_rsp->attr_value.len = param->write.len; gatt_rsp->attr_value.handle = param->write.handle; From dedf21c0ae2666743276d9623c73483a23c46731 Mon Sep 17 00:00:00 2001 From: Zhang Hai Peng Date: Wed, 3 Dec 2025 11:54:16 +0800 Subject: [PATCH 11/40] fix(ble/bluedroid): Fix memory leak in ble_spp_server example (cherry picked from commit b6b3b81bf698dd7f85303de0cdcdb2d050e6efd2) Co-authored-by: zhanghaipeng --- .../ble_spp_server/main/ble_spp_server_demo.c | 25 +++++++++++-------- .../ble_spp_server/main/ble_spp_server_demo.h | 1 - 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/examples/bluetooth/bluedroid/ble/ble_spp_server/main/ble_spp_server_demo.c b/examples/bluetooth/bluedroid/ble/ble_spp_server/main/ble_spp_server_demo.c index d2796402cf..8e4351231e 100644 --- a/examples/bluetooth/bluedroid/ble/ble_spp_server/main/ble_spp_server_demo.c +++ b/examples/bluetooth/bluedroid/ble/ble_spp_server/main/ble_spp_server_demo.c @@ -277,20 +277,25 @@ static bool store_wr_buffer(esp_ble_gatts_cb_param_t *p_data) ESP_LOGI(GATTS_TABLE_TAG, "malloc error %s %d", __func__, __LINE__); return false; } + + temp_spp_recv_data_node_p1->len = p_data->write.len; + temp_spp_recv_data_node_p1->next_node = NULL; + temp_spp_recv_data_node_p1->node_buff = (uint8_t *)malloc(p_data->write.len); + if (temp_spp_recv_data_node_p1->node_buff == NULL) { + ESP_LOGI(GATTS_TABLE_TAG, "malloc error %s %d\n", __func__, __LINE__); + // Security fix: Free the node and return false to prevent memory leak + free(temp_spp_recv_data_node_p1); + temp_spp_recv_data_node_p1 = NULL; + return false; + } + memcpy(temp_spp_recv_data_node_p1->node_buff, p_data->write.value, p_data->write.len); + + // Security fix: Link to list only after successful allocation if(temp_spp_recv_data_node_p2 != NULL){ temp_spp_recv_data_node_p2->next_node = temp_spp_recv_data_node_p1; } - temp_spp_recv_data_node_p1->len = p_data->write.len; - SppRecvDataBuff.buff_size += p_data->write.len; - temp_spp_recv_data_node_p1->next_node = NULL; - temp_spp_recv_data_node_p1->node_buff = (uint8_t *)malloc(p_data->write.len); temp_spp_recv_data_node_p2 = temp_spp_recv_data_node_p1; - if (temp_spp_recv_data_node_p1->node_buff == NULL) { - ESP_LOGI(GATTS_TABLE_TAG, "malloc error %s %d\n", __func__, __LINE__); - temp_spp_recv_data_node_p1->len = 0; - } else { - memcpy(temp_spp_recv_data_node_p1->node_buff,p_data->write.value,p_data->write.len); - } + SppRecvDataBuff.buff_size += p_data->write.len; if(SppRecvDataBuff.node_num == 0){ SppRecvDataBuff.first_node = temp_spp_recv_data_node_p1; diff --git a/examples/bluetooth/bluedroid/ble/ble_spp_server/main/ble_spp_server_demo.h b/examples/bluetooth/bluedroid/ble/ble_spp_server/main/ble_spp_server_demo.h index 9ca8065889..91829ea5df 100644 --- a/examples/bluetooth/bluedroid/ble/ble_spp_server/main/ble_spp_server_demo.h +++ b/examples/bluetooth/bluedroid/ble/ble_spp_server/main/ble_spp_server_demo.h @@ -16,7 +16,6 @@ //#define SUPPORT_HEARTBEAT //#define SPP_DEBUG_MODE -#define spp_sprintf(s,...) sprintf((char*)(s), ##__VA_ARGS__) #define SPP_DATA_MAX_LEN (512) #define SPP_CMD_MAX_LEN (20) #define SPP_STATUS_MAX_LEN (20) From f67bb6c31c635cc87eeb2a55164881f0253facc3 Mon Sep 17 00:00:00 2001 From: Zhang Hai Peng Date: Wed, 3 Dec 2025 11:54:17 +0800 Subject: [PATCH 12/40] fix(ble/blufi): Add DH param length validation (cherry picked from commit 773814c087effb3d69c2eeed3ac2e8472861be7c) Co-authored-by: zhanghaipeng --- examples/bluetooth/blufi/main/blufi_security.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/examples/bluetooth/blufi/main/blufi_security.c b/examples/bluetooth/blufi/main/blufi_security.c index 8b62777bba..123f506336 100644 --- a/examples/bluetooth/blufi/main/blufi_security.c +++ b/examples/bluetooth/blufi/main/blufi_security.c @@ -41,6 +41,7 @@ struct blufi_security { #define DH_SELF_PUB_KEY_LEN 128 +#define DH_PARAM_LEN_MAX 1024 uint8_t self_public_key[DH_SELF_PUB_KEY_LEN]; #define SHARE_KEY_LEN 128 uint8_t share_key[SHARE_KEY_LEN]; @@ -83,6 +84,13 @@ void blufi_dh_negotiate_data_handler(uint8_t *data, int len, uint8_t **output_da switch (type) { case SEC_TYPE_DH_PARAM_LEN: blufi_sec->dh_param_len = ((data[1]<<8)|data[2]); + // Security fix: Limit DH param length to prevent DoS via large memory allocation + if (blufi_sec->dh_param_len == 0 || blufi_sec->dh_param_len > DH_PARAM_LEN_MAX) { + BLUFI_ERROR("%s, invalid dh param len %d\n", __func__, blufi_sec->dh_param_len); + blufi_sec->dh_param_len = 0; + btc_blufi_report_error(ESP_BLUFI_DH_PARAM_ERROR); + return; + } if (blufi_sec->dh_param) { free(blufi_sec->dh_param); blufi_sec->dh_param = NULL; From 775b64753e03cc85a8f812ae4fe1ad762e082e2d Mon Sep 17 00:00:00 2001 From: Zhang Hai Peng Date: Wed, 3 Dec 2025 11:54:17 +0800 Subject: [PATCH 13/40] fix(ble/bt): Add NULL check in esp_hid_host example (cherry picked from commit 8b5dd7536890d2eca026f88a410972e4f19e2d3b) Co-authored-by: zhanghaipeng --- examples/bluetooth/esp_hid_host/main/esp_hid_host_main.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/bluetooth/esp_hid_host/main/esp_hid_host_main.c b/examples/bluetooth/esp_hid_host/main/esp_hid_host_main.c index 03e255d3ab..8934b60c1a 100644 --- a/examples/bluetooth/esp_hid_host/main/esp_hid_host_main.c +++ b/examples/bluetooth/esp_hid_host/main/esp_hid_host_main.c @@ -164,7 +164,7 @@ void hid_demo_task(void *pvParameters) printf("] srv 0x%03x, ", r->bt.cod.service); print_uuid(&r->bt.uuid); printf(", "); - if (strncmp(r->name, remote_device_name, strlen(remote_device_name)) == 0) { + if (r->name && strncmp(r->name, remote_device_name, strlen(remote_device_name)) == 0) { break; } } @@ -175,7 +175,7 @@ void hid_demo_task(void *pvParameters) } #if CONFIG_BT_HID_HOST_ENABLED - if (cr && strncmp(cr->name, remote_device_name, strlen(remote_device_name)) == 0) { + if (cr && cr->name && strncmp(cr->name, remote_device_name, strlen(remote_device_name)) == 0) { esp_hidh_dev_open(cr->bda, cr->transport, cr->ble.addr_type); } #else From de2b4af5bddd3a37420c4964ec98c05678db53eb Mon Sep 17 00:00:00 2001 From: Zhang Hai Peng Date: Wed, 3 Dec 2025 11:54:18 +0800 Subject: [PATCH 14/40] fix(ble/bt): Add NULL check in esp_hid_device example (cherry picked from commit 8e0f74a2cc52cd1ec5e039db34d09d4e07097f7b) Co-authored-by: zhanghaipeng --- examples/bluetooth/esp_hid_device/main/esp_hid_gap.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/examples/bluetooth/esp_hid_device/main/esp_hid_gap.c b/examples/bluetooth/esp_hid_device/main/esp_hid_gap.c index 6cc854920b..9c87942f88 100644 --- a/examples/bluetooth/esp_hid_device/main/esp_hid_gap.c +++ b/examples/bluetooth/esp_hid_device/main/esp_hid_gap.c @@ -278,6 +278,9 @@ static void handle_bt_device_result(struct disc_res_param *disc_res) GAP_DBG_PRINTF(", %s: ", gap_bt_prop_type_names[prop->type]); } if (prop->type == ESP_BT_GAP_DEV_PROP_BDNAME) { + if (prop->val == NULL) { + continue; + } name = (uint8_t *)prop->val; name_len = strlen((const char *)name); GAP_DBG_PRINTF("%s", (const char *)name); From 8ef83c23675dcdb694140738c2ae506e453f59ed Mon Sep 17 00:00:00 2001 From: Zhang Hai Peng Date: Wed, 3 Dec 2025 11:54:19 +0800 Subject: [PATCH 15/40] fix(ble/bluedroid): Add NULL check in esp_ble_gap_add_device_to_resolving_list (cherry picked from commit 4b44d8e814927083be952f31d5e09ae3bbaa3dc6) Co-authored-by: zhanghaipeng --- components/bt/host/bluedroid/api/esp_gap_ble_api.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/bt/host/bluedroid/api/esp_gap_ble_api.c b/components/bt/host/bluedroid/api/esp_gap_ble_api.c index 3c3a48e5b9..c7c16c037f 100644 --- a/components/bt/host/bluedroid/api/esp_gap_ble_api.c +++ b/components/bt/host/bluedroid/api/esp_gap_ble_api.c @@ -239,7 +239,7 @@ esp_err_t esp_ble_gap_add_device_to_resolving_list(esp_bd_addr_t peer_addr, uint { ESP_BLUEDROID_STATUS_CHECK(ESP_BLUEDROID_STATUS_ENABLED); - if (addr_type > BLE_ADDR_TYPE_RANDOM ||!peer_addr || (addr_type && ((peer_addr[0] & 0xC0) != 0xC0))) { + if (addr_type > BLE_ADDR_TYPE_RANDOM || !peer_addr || !peer_irk || (addr_type && ((peer_addr[0] & 0xC0) != 0xC0))) { return ESP_ERR_INVALID_ARG; } From 8960e3ef50d3d1b24c33a46f12500dc2ea96d435 Mon Sep 17 00:00:00 2001 From: Zhang Hai Peng Date: Wed, 3 Dec 2025 11:54:19 +0800 Subject: [PATCH 16/40] fix(ble/bluedroid): Add NULL checks in esp_gap_ble_api (cherry picked from commit b71c51564a7c2bb2be1377d40ae022baf2cea3b2) Co-authored-by: zhanghaipeng --- .../bt/host/bluedroid/api/esp_gap_ble_api.c | 71 ++++++++++++++++--- 1 file changed, 63 insertions(+), 8 deletions(-) diff --git a/components/bt/host/bluedroid/api/esp_gap_ble_api.c b/components/bt/host/bluedroid/api/esp_gap_ble_api.c index c7c16c037f..d592b6b77f 100644 --- a/components/bt/host/bluedroid/api/esp_gap_ble_api.c +++ b/components/bt/host/bluedroid/api/esp_gap_ble_api.c @@ -111,6 +111,10 @@ esp_err_t esp_ble_gap_start_advertising(esp_ble_adv_params_t *adv_params) ESP_BLUEDROID_STATUS_CHECK(ESP_BLUEDROID_STATUS_ENABLED); + if (adv_params == NULL) { + return ESP_ERR_INVALID_ARG; + } + msg.sig = BTC_SIG_API_CALL; msg.pid = BTC_PID_GAP_BLE; msg.act = BTC_GAP_BLE_ACT_START_ADV; @@ -171,6 +175,10 @@ esp_err_t esp_ble_gap_set_pkt_data_len(esp_bd_addr_t remote_device, uint16_t tx_ ESP_BLUEDROID_STATUS_CHECK(ESP_BLUEDROID_STATUS_ENABLED); + if (remote_device == NULL) { + return ESP_ERR_INVALID_ARG; + } + msg.sig = BTC_SIG_API_CALL; msg.pid = BTC_PID_GAP_BLE; msg.act = BTC_GAP_BLE_ACT_SET_PKT_DATA_LEN; @@ -182,6 +190,9 @@ esp_err_t esp_ble_gap_set_pkt_data_len(esp_bd_addr_t remote_device, uint16_t tx_ esp_err_t esp_ble_gap_addr_create_static(esp_bd_addr_t rand_addr) { + if (rand_addr == NULL) { + return ESP_ERR_INVALID_ARG; + } // Static device address: First two bits are '11', rest is random rand_addr[0] = 0xC0 | (esp_random() & 0x3F); for (int i = 1; i < 6; i++) { @@ -192,6 +203,9 @@ esp_err_t esp_ble_gap_addr_create_static(esp_bd_addr_t rand_addr) esp_err_t esp_ble_gap_addr_create_nrpa(esp_bd_addr_t rand_addr) { + if (rand_addr == NULL) { + return ESP_ERR_INVALID_ARG; + } // Non-resolvable private address: First two bits are '00', rest is random rand_addr[0] = (esp_random() & 0x3F); for (int i = 1; i < 6; i++) { @@ -207,6 +221,10 @@ esp_err_t esp_ble_gap_set_rand_addr(esp_bd_addr_t rand_addr) ESP_BLUEDROID_STATUS_CHECK(ESP_BLUEDROID_STATUS_ENABLED); + if (rand_addr == NULL) { + return ESP_ERR_INVALID_ARG; + } + msg.sig = BTC_SIG_API_CALL; msg.pid = BTC_PID_GAP_BLE; msg.act = BTC_GAP_BLE_ACT_SET_RAND_ADDRESS; @@ -357,6 +375,10 @@ esp_err_t esp_ble_gap_set_prefer_conn_params(esp_bd_addr_t bd_addr, return ESP_ERR_INVALID_STATE; } + if (bd_addr == NULL) { + return ESP_ERR_INVALID_ARG; + } + if (ESP_BLE_IS_VALID_PARAM(min_conn_int, ESP_BLE_CONN_INT_MIN, ESP_BLE_CONN_INT_MAX) && ESP_BLE_IS_VALID_PARAM(max_conn_int, ESP_BLE_CONN_INT_MIN, ESP_BLE_CONN_INT_MAX) && ESP_BLE_IS_VALID_PARAM(supervision_tout, ESP_BLE_CONN_SUP_TOUT_MIN, ESP_BLE_CONN_SUP_TOUT_MAX) && @@ -424,6 +446,9 @@ esp_err_t esp_ble_gap_get_local_used_addr(esp_bd_addr_t local_used_addr, uint8_t LOG_ERROR("%s, bluedroid status error", __func__); return ESP_FAIL; } + if (local_used_addr == NULL || addr_type == NULL) { + return ESP_ERR_INVALID_ARG; + } if(!BTM_BleGetCurrentAddress(local_used_addr, addr_type)) { return ESP_FAIL; } @@ -436,13 +461,6 @@ uint8_t *esp_ble_resolve_adv_data_by_type( uint8_t *adv_data, uint16_t adv_data_ return NULL; } - if (((type < ESP_BLE_AD_TYPE_FLAG) || (type > ESP_BLE_AD_TYPE_128SERVICE_DATA)) && - (type != ESP_BLE_AD_MANUFACTURER_SPECIFIC_TYPE)) { - LOG_ERROR("The advertising data type is not defined, type = %x", type); - *length = 0; - return NULL; - } - if (adv_data_len == 0) { *length = 0; return NULL; @@ -495,6 +513,10 @@ esp_err_t esp_ble_gap_read_rssi(esp_bd_addr_t remote_addr) return ESP_ERR_INVALID_STATE; } + if (remote_addr == NULL) { + return ESP_ERR_INVALID_ARG; + } + msg.sig = BTC_SIG_API_CALL; msg.pid = BTC_PID_GAP_BLE; msg.act = BTC_GAP_BLE_ACT_READ_RSSI; @@ -645,6 +667,10 @@ esp_err_t esp_ble_set_encryption(esp_bd_addr_t bd_addr, esp_ble_sec_act_t sec_ac ESP_BLUEDROID_STATUS_CHECK(ESP_BLUEDROID_STATUS_ENABLED); + if (bd_addr == NULL) { + return ESP_ERR_INVALID_ARG; + } + msg.sig = BTC_SIG_API_CALL; msg.pid = BTC_PID_GAP_BLE; msg.act = BTC_GAP_BLE_SET_ENCRYPTION_EVT; @@ -662,6 +688,10 @@ esp_err_t esp_ble_gap_security_rsp(esp_bd_addr_t bd_addr, bool accept) ESP_BLUEDROID_STATUS_CHECK(ESP_BLUEDROID_STATUS_ENABLED); + if (bd_addr == NULL) { + return ESP_ERR_INVALID_ARG; + } + msg.sig = BTC_SIG_API_CALL; msg.pid = BTC_PID_GAP_BLE; msg.act = BTC_GAP_BLE_SECURITY_RSP_EVT; @@ -680,6 +710,10 @@ esp_err_t esp_ble_passkey_reply(esp_bd_addr_t bd_addr, bool accept, uint32_t pas ESP_BLUEDROID_STATUS_CHECK(ESP_BLUEDROID_STATUS_ENABLED); + if (bd_addr == NULL) { + return ESP_ERR_INVALID_ARG; + } + msg.sig = BTC_SIG_API_CALL; msg.pid = BTC_PID_GAP_BLE; msg.act = BTC_GAP_BLE_PASSKEY_REPLY_EVT; @@ -698,6 +732,10 @@ esp_err_t esp_ble_confirm_reply(esp_bd_addr_t bd_addr, bool accept) ESP_BLUEDROID_STATUS_CHECK(ESP_BLUEDROID_STATUS_ENABLED); + if (bd_addr == NULL) { + return ESP_ERR_INVALID_ARG; + } + msg.sig = BTC_SIG_API_CALL; msg.pid = BTC_PID_GAP_BLE; msg.act = BTC_GAP_BLE_CONFIRM_REPLY_EVT; @@ -712,6 +750,11 @@ esp_err_t esp_ble_remove_bond_device(esp_bd_addr_t bd_addr) { btc_msg_t msg = {0}; btc_ble_gap_args_t arg; + + if (bd_addr == NULL) { + return ESP_ERR_INVALID_ARG; + } + msg.sig = BTC_SIG_API_CALL; msg.pid = BTC_PID_GAP_BLE; msg.act = BTC_GAP_BLE_REMOVE_BOND_DEV_EVT; @@ -757,6 +800,10 @@ esp_err_t esp_ble_oob_req_reply(esp_bd_addr_t bd_addr, uint8_t *TK, uint8_t len) return ESP_ERR_INVALID_ARG; } + if (bd_addr == NULL || TK == NULL) { + return ESP_ERR_INVALID_ARG; + } + btc_msg_t msg = {0}; btc_ble_gap_args_t arg; @@ -775,7 +822,7 @@ esp_err_t esp_ble_oob_req_reply(esp_bd_addr_t bd_addr, uint8_t *TK, uint8_t len) esp_err_t esp_ble_sc_oob_req_reply(esp_bd_addr_t bd_addr, uint8_t p_c[16], uint8_t p_r[16]) { - if (!p_c || !p_r) { + if (!bd_addr || !p_c || !p_r) { return ESP_ERR_INVALID_ARG; } @@ -836,6 +883,10 @@ esp_err_t esp_ble_gap_disconnect(esp_bd_addr_t remote_device) ESP_BLUEDROID_STATUS_CHECK(ESP_BLUEDROID_STATUS_ENABLED); + if (remote_device == NULL) { + return ESP_ERR_INVALID_ARG; + } + msg.sig = BTC_SIG_API_CALL; msg.pid = BTC_PID_GAP_BLE; msg.act = BTC_GAP_BLE_DISCONNECT_EVT; @@ -864,6 +915,10 @@ esp_err_t esp_gap_ble_set_channels(esp_gap_ble_channels channels) return ESP_ERR_INVALID_STATE; } + if (channels == NULL) { + return ESP_ERR_INVALID_ARG; + } + msg.sig = BTC_SIG_API_CALL; msg.pid = BTC_PID_GAP_BLE; msg.act = BTC_GAP_BLE_SET_AFH_CHANNELS; From 1ab91f0b5fc77117872be11a18e67eb4c6efbfe0 Mon Sep 17 00:00:00 2001 From: Zhang Hai Peng Date: Wed, 3 Dec 2025 11:54:20 +0800 Subject: [PATCH 17/40] fix(ble/bluedroid): Add NULL checks in GATT APIs (cherry picked from commit a6730c7da697477ed696148cf737bfb66514a742) Co-authored-by: zhanghaipeng --- .../bt/host/bluedroid/api/esp_gattc_api.c | 36 +++++++++++++++++++ .../bt/host/bluedroid/api/esp_gatts_api.c | 26 +++++++++++++- 2 files changed, 61 insertions(+), 1 deletion(-) diff --git a/components/bt/host/bluedroid/api/esp_gattc_api.c b/components/bt/host/bluedroid/api/esp_gattc_api.c index 83481a85c6..00c6a96e0b 100644 --- a/components/bt/host/bluedroid/api/esp_gattc_api.c +++ b/components/bt/host/bluedroid/api/esp_gattc_api.c @@ -173,6 +173,10 @@ esp_err_t esp_ble_gattc_enh_open(esp_gatt_if_t gattc_if, esp_ble_gatt_creat_conn #if (BLE_42_FEATURE_SUPPORT == TRUE) esp_err_t esp_ble_gattc_open(esp_gatt_if_t gattc_if, esp_bd_addr_t remote_bda, esp_ble_addr_type_t remote_addr_type, bool is_direct) { + if (remote_bda == NULL) { + return ESP_ERR_INVALID_ARG; + } + esp_ble_gatt_creat_conn_params_t creat_conn_params = {0}; memcpy(creat_conn_params.remote_bda, remote_bda, ESP_BD_ADDR_LEN); creat_conn_params.remote_addr_type = remote_addr_type; @@ -187,6 +191,10 @@ esp_err_t esp_ble_gattc_open(esp_gatt_if_t gattc_if, esp_bd_addr_t remote_bda, e #if (BLE_50_FEATURE_SUPPORT == TRUE) esp_err_t esp_ble_gattc_aux_open(esp_gatt_if_t gattc_if, esp_bd_addr_t remote_bda, esp_ble_addr_type_t remote_addr_type, bool is_direct) { + if (remote_bda == NULL) { + return ESP_ERR_INVALID_ARG; + } + esp_ble_gatt_creat_conn_params_t creat_conn_params = {0}; memcpy(creat_conn_params.remote_bda, remote_bda, ESP_BD_ADDR_LEN); creat_conn_params.remote_addr_type = remote_addr_type; @@ -521,6 +529,10 @@ esp_err_t esp_ble_gattc_read_multiple(esp_gatt_if_t gattc_if, ESP_BLUEDROID_STATUS_CHECK(ESP_BLUEDROID_STATUS_ENABLED); + if (read_multi == NULL) { + return ESP_ERR_INVALID_ARG; + } + tGATT_TCB *p_tcb = gatt_get_tcb_by_idx(conn_id); if (!gatt_check_connection_state_by_tcb(p_tcb)) { LOG_WARN("%s, The connection not created.", __func__); @@ -557,6 +569,10 @@ esp_err_t esp_ble_gattc_read_multiple_variable(esp_gatt_if_t gattc_if, ESP_BLUEDROID_STATUS_CHECK(ESP_BLUEDROID_STATUS_ENABLED); + if (read_multi == NULL) { + return ESP_ERR_INVALID_ARG; + } + tGATT_TCB *p_tcb = gatt_get_tcb_by_idx(conn_id); if (!gatt_check_connection_state_by_tcb(p_tcb)) { LOG_WARN("%s, The connection not created.", __func__); @@ -810,6 +826,10 @@ esp_err_t esp_ble_gattc_register_for_notify (esp_gatt_if_t gattc_if, ESP_BLUEDROID_STATUS_CHECK(ESP_BLUEDROID_STATUS_ENABLED); + if (server_bda == NULL) { + return ESP_ERR_INVALID_ARG; + } + if (handle == 0) { return ESP_GATT_INVALID_HANDLE; } @@ -832,6 +852,10 @@ esp_err_t esp_ble_gattc_unregister_for_notify (esp_gatt_if_t gattc_if, ESP_BLUEDROID_STATUS_CHECK(ESP_BLUEDROID_STATUS_ENABLED); + if (server_bda == NULL) { + return ESP_ERR_INVALID_ARG; + } + if (handle == 0) { return ESP_GATT_INVALID_HANDLE; } @@ -852,6 +876,10 @@ esp_err_t esp_ble_gattc_cache_refresh(esp_bd_addr_t remote_bda) ESP_BLUEDROID_STATUS_CHECK(ESP_BLUEDROID_STATUS_ENABLED); + if (remote_bda == NULL) { + return ESP_ERR_INVALID_ARG; + } + msg.sig = BTC_SIG_API_CALL; msg.pid = BTC_PID_GATTC; msg.act = BTC_GATTC_ACT_CACHE_REFRESH; @@ -867,6 +895,10 @@ esp_err_t esp_ble_gattc_cache_clean(esp_bd_addr_t remote_bda) ESP_BLUEDROID_STATUS_CHECK(ESP_BLUEDROID_STATUS_ENABLED); + if (remote_bda == NULL) { + return ESP_ERR_INVALID_ARG; + } + msg.sig = BTC_SIG_API_CALL; msg.pid = BTC_PID_GATTC; msg.act = BTC_GATTC_ACT_CACHE_CLEAN; @@ -882,6 +914,10 @@ esp_err_t esp_ble_gattc_cache_assoc(esp_gatt_if_t gattc_if, esp_bd_addr_t src_ad ESP_BLUEDROID_STATUS_CHECK(ESP_BLUEDROID_STATUS_ENABLED); + if (src_addr == NULL || assoc_addr == NULL) { + return ESP_ERR_INVALID_ARG; + } + msg.sig = BTC_SIG_API_CALL; msg.pid = BTC_PID_GATTC; msg.act = BTC_GATTC_ACT_CACHE_ASSOC; diff --git a/components/bt/host/bluedroid/api/esp_gatts_api.c b/components/bt/host/bluedroid/api/esp_gatts_api.c index 03c8186099..1d7c696126 100644 --- a/components/bt/host/bluedroid/api/esp_gatts_api.c +++ b/components/bt/host/bluedroid/api/esp_gatts_api.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -78,6 +78,10 @@ esp_err_t esp_ble_gatts_create_service(esp_gatt_if_t gatts_if, ESP_BLUEDROID_STATUS_CHECK(ESP_BLUEDROID_STATUS_ENABLED); + if (service_id == NULL) { + return ESP_ERR_INVALID_ARG; + } + msg.sig = BTC_SIG_API_CALL; msg.pid = BTC_PID_GATTS; msg.act = BTC_GATTS_ACT_CREATE_SERVICE; @@ -98,6 +102,10 @@ esp_err_t esp_ble_gatts_create_attr_tab(const esp_gatts_attr_db_t *gatts_attr_db ESP_BLUEDROID_STATUS_CHECK(ESP_BLUEDROID_STATUS_ENABLED); + if (gatts_attr_db == NULL) { + return ESP_ERR_INVALID_ARG; + } + if (max_nb_attr > ESP_GATT_ATTR_HANDLE_MAX) { LOG_ERROR("The number of attribute should not be greater than CONFIG_BT_GATT_MAX_SR_ATTRIBUTES\n"); return ESP_ERR_INVALID_ARG; @@ -143,6 +151,10 @@ esp_err_t esp_ble_gatts_add_char(uint16_t service_handle, esp_bt_uuid_t *char_ ESP_BLUEDROID_STATUS_CHECK(ESP_BLUEDROID_STATUS_ENABLED); + if (char_uuid == NULL) { + return ESP_ERR_INVALID_ARG; + } + /* parameter validation check */ status = esp_ble_gatts_add_char_desc_param_check(char_val, control); if (status != ESP_OK){ @@ -183,6 +195,10 @@ esp_err_t esp_ble_gatts_add_char_descr (uint16_t service_handle, ESP_BLUEDROID_STATUS_CHECK(ESP_BLUEDROID_STATUS_ENABLED); + if (descr_uuid == NULL) { + return ESP_ERR_INVALID_ARG; + } + /* parameter validation check */ status = esp_ble_gatts_add_char_desc_param_check(char_descr_val, control); if (status != ESP_OK){ @@ -344,6 +360,10 @@ esp_gatt_status_t esp_ble_gatts_get_attr_value(uint16_t attr_handle, uint16_t *l { ESP_BLUEDROID_STATUS_CHECK(ESP_BLUEDROID_STATUS_ENABLED); + if (length == NULL || value == NULL) { + return ESP_GATT_INVALID_PDU; + } + if (attr_handle == ESP_GATT_ILLEGAL_HANDLE) { *length = 0; return ESP_GATT_INVALID_HANDLE; @@ -359,6 +379,10 @@ esp_err_t esp_ble_gatts_open(esp_gatt_if_t gatts_if, esp_bd_addr_t remote_bda, b ESP_BLUEDROID_STATUS_CHECK(ESP_BLUEDROID_STATUS_ENABLED); + if (remote_bda == NULL) { + return ESP_ERR_INVALID_ARG; + } + msg.sig = BTC_SIG_API_CALL; msg.pid = BTC_PID_GATTS; msg.act = BTC_GATTS_ACT_OPEN; From 8739a2d82bbc5a9c660add40c007496217c66813 Mon Sep 17 00:00:00 2001 From: Zhang Hai Peng Date: Wed, 3 Dec 2025 11:54:20 +0800 Subject: [PATCH 18/40] fix(ble/bluedroid): Fix VLA in a2dp_gatts_coex example (cherry picked from commit e741f09e0f4b959b76183d4a3492e16235ac9785) Co-authored-by: zhanghaipeng --- .../coex/a2dp_gatts_coex/main/main.c | 32 +++++++++++++------ 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/examples/bluetooth/bluedroid/coex/a2dp_gatts_coex/main/main.c b/examples/bluetooth/bluedroid/coex/a2dp_gatts_coex/main/main.c index e4456ea165..12e8bdd8b1 100644 --- a/examples/bluetooth/bluedroid/coex/a2dp_gatts_coex/main/main.c +++ b/examples/bluetooth/bluedroid/coex/a2dp_gatts_coex/main/main.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2021-2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Unlicense OR CC0-1.0 */ @@ -131,8 +131,24 @@ static struct gatts_profile_inst gl_profile_tab[PROFILE_NUM] = { }; static void ble_init_adv_data(const char *name) { - int len = strlen(name); - uint8_t raw_adv_data[len+5]; + if (name == NULL) { + ESP_LOGE(BT_BLE_COEX_TAG, "ble_init_adv_data: name is NULL"); + return; + } + + size_t len = strlen(name); + // ADV data max is 31 bytes; overhead is 5 bytes (Flags: 3, Name header: 2) + #define ADV_DATA_MAX_LEN 31 + #define ADV_DATA_OVERHEAD 5 + + if (len > (ADV_DATA_MAX_LEN - ADV_DATA_OVERHEAD)) { + ESP_LOGW(BT_BLE_COEX_TAG, "ADV name too long (%d), truncating to %d", (int)len, ADV_DATA_MAX_LEN - ADV_DATA_OVERHEAD); + len = ADV_DATA_MAX_LEN - ADV_DATA_OVERHEAD; + } + + uint8_t raw_adv_data[ADV_DATA_MAX_LEN]; + size_t adv_data_len = len + ADV_DATA_OVERHEAD; + //flag raw_adv_data[0] = 2; raw_adv_data[1] = ESP_BT_EIR_TYPE_FLAGS; @@ -140,16 +156,14 @@ static void ble_init_adv_data(const char *name) //adv name raw_adv_data[3] = len + 1; raw_adv_data[4] = ESP_BLE_AD_TYPE_NAME_CMPL; - for (int i = 0;i < len;i++) - { - raw_adv_data[i+5] = *(name++); - } + memcpy(&raw_adv_data[5], name, len); + //The length of adv data must be less than 31 bytes - esp_err_t raw_adv_ret = esp_ble_gap_config_adv_data_raw(raw_adv_data, sizeof(raw_adv_data)); + esp_err_t raw_adv_ret = esp_ble_gap_config_adv_data_raw(raw_adv_data, adv_data_len); if (raw_adv_ret){ ESP_LOGE(BT_BLE_COEX_TAG, "config raw adv data failed, error code = 0x%x ", raw_adv_ret); } - esp_err_t raw_scan_ret = esp_ble_gap_config_scan_rsp_data_raw(raw_adv_data, sizeof(raw_adv_data)); + esp_err_t raw_scan_ret = esp_ble_gap_config_scan_rsp_data_raw(raw_adv_data, adv_data_len); if (raw_scan_ret){ ESP_LOGE(BT_BLE_COEX_TAG, "config raw scan rsp data failed, error code = 0x%x", raw_scan_ret); } From 1936ba80d714f88d3a8f577f25dc88d501a863b1 Mon Sep 17 00:00:00 2001 From: Zhang Hai Peng Date: Wed, 3 Dec 2025 11:54:21 +0800 Subject: [PATCH 19/40] fix(ble/bluedroid): Add boundary check for adv_handle in btm_ble_adv_set_terminated_evt (cherry picked from commit d2baf3b0d4b8695abec90fa3fc1d46ce1bdab47b) Co-authored-by: zhanghaipeng --- components/bt/host/bluedroid/stack/btm/btm_ble_5_gap.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/components/bt/host/bluedroid/stack/btm/btm_ble_5_gap.c b/components/bt/host/bluedroid/stack/btm/btm_ble_5_gap.c index 76fe63c08a..e82d95f61e 100644 --- a/components/bt/host/bluedroid/stack/btm/btm_ble_5_gap.c +++ b/components/bt/host/bluedroid/stack/btm/btm_ble_5_gap.c @@ -1254,6 +1254,11 @@ void btm_ble_adv_set_terminated_evt(tBTM_BLE_ADV_TERMINAT *params) return; } + if (params->adv_handle >= MAX_BLE_ADV_INSTANCE) { + BTM_TRACE_ERROR("%s, Invalid adv_handle %d, max is %d.", __func__, params->adv_handle, MAX_BLE_ADV_INSTANCE); + return; + } + // adv terminated due to connection, save the adv handle and connection handle if(params->status == 0x00) { adv_record[params->adv_handle].ter_con_handle = params->conn_handle; From 569854b55af3d032dc1be1f0c7d0ce2f8fd2d09d Mon Sep 17 00:00:00 2001 From: Zhang Hai Peng Date: Wed, 3 Dec 2025 11:54:22 +0800 Subject: [PATCH 20/40] fix(ble/bluedroid): Add length check in prepare write response (cherry picked from commit b03ff3cf218c3974b798a700cf1ede95641fe7af) Co-authored-by: zhanghaipeng --- .../bt/host/bluedroid/stack/gatt/gatt_cl.c | 12 +++++----- .../bluedroid/stack/smp/include/smp_int.h | 5 ++++- .../bt/host/bluedroid/stack/smp/smp_l2c.c | 22 ++++++++++++++++--- .../bt/host/bluedroid/stack/smp/smp_utils.c | 2 +- 4 files changed, 30 insertions(+), 11 deletions(-) diff --git a/components/bt/host/bluedroid/stack/gatt/gatt_cl.c b/components/bt/host/bluedroid/stack/gatt/gatt_cl.c index 12168dc00d..ddd089f4e0 100644 --- a/components/bt/host/bluedroid/stack/gatt/gatt_cl.c +++ b/components/bt/host/bluedroid/stack/gatt/gatt_cl.c @@ -66,7 +66,7 @@ static const UINT16 disc_type_to_uuid[GATT_DISC_MAX] = { 0 /* no type filtering for DISC_CHAR_DSCPT */ }; -// Use for GATTC discover infomation print +// Use for GATTC discover information print #define GATT_DISC_INFO(fmt, args...) {if (gatt_cb.auto_disc == FALSE) BT_PRINT_I("BT_GATT", fmt, ## args);} /******************************************************************************* @@ -604,7 +604,7 @@ void gatt_process_prep_write_rsp (tGATT_TCB *p_tcb, tGATT_CLCB *p_clcb, UINT8 op GATT_TRACE_DEBUG("value resp op_code = %s len = %d", gatt_dbg_op_name(op_code), len); - if (len < GATT_PREP_WRITE_RSP_MIN_LEN) { + if ((len < GATT_PREP_WRITE_RSP_MIN_LEN) || ((len - 4) > GATT_MAX_ATTR_LEN)) { GATT_TRACE_ERROR("illegal prepare write response length, discard"); gatt_end_operation(p_clcb, GATT_INVALID_PDU, &value); return; @@ -701,7 +701,7 @@ void gatt_process_notification(tGATT_TCB *p_tcb, UINT8 op_code, } p_tcb->ind_count = 0; - /* should notify all registered client with the handle value notificaion/indication + /* should notify all registered client with the handle value notification/indication Note: need to do the indication count and start timer first then do callback */ for (i = 0, p_reg = gatt_cb.cl_rcb; i < GATT_MAX_APPS; i++, p_reg++) { @@ -800,7 +800,7 @@ void gatt_process_read_by_type_rsp (tGATT_TCB *p_tcb, tGATT_CLCB *p_clcb, UINT8 handle_len = 4; } - value_len -= handle_len; /* substract the handle pairs bytes */ + value_len -= handle_len; /* subtract the handle pairs bytes */ len -= 1; while (len >= (handle_len + value_len)) { @@ -887,7 +887,7 @@ void gatt_process_read_by_type_rsp (tGATT_TCB *p_tcb, tGATT_CLCB *p_clcb, UINT8 gatt_end_operation(p_clcb, GATT_SUCCESS, (void *)p); } return; - } else { /* discover characterisitic */ + } else { /* discover characteristic */ STREAM_TO_UINT8 (record_value.dclr_value.char_prop, p); STREAM_TO_UINT16(record_value.dclr_value.val_handle, p); if (!GATT_HANDLE_IS_VALID(record_value.dclr_value.val_handle)) { @@ -979,7 +979,7 @@ void gatt_process_read_rsp(tGATT_TCB *p_tcb, tGATT_CLCB *p_clcb, UINT8 op_code, if (len == (p_tcb->payload_size - 1) && /* full packet for read or read blob rsp */ len + offset < GATT_MAX_ATTR_LEN) { - GATT_TRACE_DEBUG("full pkt issue read blob for remianing bytes old offset=%d len=%d new offset=%d", + GATT_TRACE_DEBUG("full pkt issue read blob for remaining bytes old offset=%d len=%d new offset=%d", offset, len, p_clcb->counter); gatt_act_read(p_clcb, p_clcb->counter); } else { /* end of request, send callback */ diff --git a/components/bt/host/bluedroid/stack/smp/include/smp_int.h b/components/bt/host/bluedroid/stack/smp/include/smp_int.h index f3ab7ad445..ca2dbfaf07 100644 --- a/components/bt/host/bluedroid/stack/smp/include/smp_int.h +++ b/components/bt/host/bluedroid/stack/smp/include/smp_int.h @@ -197,7 +197,7 @@ enum { }; typedef UINT8 tSMP_BR_STATE; -/* random and encrption activity state */ +/* random and encryption activity state */ enum { SMP_GEN_COMPARE = 1, SMP_GEN_CONFIRM, @@ -363,6 +363,9 @@ extern tSMP_CB *smp_cb_ptr; /* Functions provided by att_main.c */ extern void smp_init (void); +/* SMP command sizes per spec - defined in smp_utils.c */ +extern const UINT8 smp_cmd_size_per_spec[]; + /* smp main */ extern void smp_sm_event(tSMP_CB *p_cb, tSMP_EVENT event, void *p_data); diff --git a/components/bt/host/bluedroid/stack/smp/smp_l2c.c b/components/bt/host/bluedroid/stack/smp/smp_l2c.c index a6f0670598..1f3db502e2 100644 --- a/components/bt/host/bluedroid/stack/smp/smp_l2c.c +++ b/components/bt/host/bluedroid/stack/smp/smp_l2c.c @@ -156,15 +156,31 @@ static void smp_connect_callback (UINT16 channel, BD_ADDR bd_addr, BOOLEAN conne static void smp_data_received(UINT16 channel, BD_ADDR bd_addr, BT_HDR *p_buf) { tSMP_CB *p_cb = &smp_cb; - UINT8 *p = (UINT8 *)(p_buf + 1) + p_buf->offset; - UINT8 cmd ; + UINT8 *p; + UINT8 cmd; SMP_TRACE_EVENT ("\nSMDBG l2c %s\n", __FUNCTION__); + /* Validate packet length before accessing data to prevent out-of-bounds read */ + if (p_buf->len < 1) { + SMP_TRACE_WARNING ("Ignore empty SMP packet (len=%d)\n", p_buf->len); + osi_free (p_buf); + return; + } + + p = (UINT8 *)(p_buf + 1) + p_buf->offset; STREAM_TO_UINT8(cmd, p); /* sanity check */ if ((SMP_OPCODE_MAX < cmd) || (SMP_OPCODE_MIN > cmd)) { - SMP_TRACE_WARNING( "Ignore received command with RESERVED code 0x%02x\n", cmd); + SMP_TRACE_WARNING ("Ignore received command with RESERVED code 0x%02x\n", cmd); + osi_free (p_buf); + return; + } + + /* Validate command length to prevent out-of-bounds read in handler functions */ + if (p_buf->len != smp_cmd_size_per_spec[cmd]) { + SMP_TRACE_WARNING ("Ignore SMP cmd 0x%02x with invalid length %d (expected %d)\n", + cmd, p_buf->len, smp_cmd_size_per_spec[cmd]); osi_free (p_buf); return; } diff --git a/components/bt/host/bluedroid/stack/smp/smp_utils.c b/components/bt/host/bluedroid/stack/smp/smp_utils.c index 48ba6c0ae3..8ec4d548ac 100644 --- a/components/bt/host/bluedroid/stack/smp/smp_utils.c +++ b/components/bt/host/bluedroid/stack/smp/smp_utils.c @@ -55,7 +55,7 @@ #define SMP_PAIR_KEYPR_NOTIF_SIZE (1 /* opcode */ + 1 /*Notif Type*/) /* SMP command sizes per spec */ -static const UINT8 smp_cmd_size_per_spec[] = { +const UINT8 smp_cmd_size_per_spec[] = { 0, SMP_PAIRING_REQ_SIZE, /* 0x01: pairing request */ SMP_PAIRING_REQ_SIZE, /* 0x02: pairing response */ From 7c7f30aa09af698517da4683119259fd70f9d39a Mon Sep 17 00:00:00 2001 From: Zhang Hai Peng Date: Wed, 3 Dec 2025 11:54:22 +0800 Subject: [PATCH 21/40] fix(ble/bluedroid): Fix security issues in GAP module (cherry picked from commit 1ed5a4465dbb0f6a36e0514ad1b2162616d356ca) Co-authored-by: zhanghaipeng --- .../bt/host/bluedroid/stack/gap/gap_ble.c | 21 ++++++++++++++---- .../bt/host/bluedroid/stack/gap/gap_conn.c | 22 ++++++++++++++----- 2 files changed, 33 insertions(+), 10 deletions(-) diff --git a/components/bt/host/bluedroid/stack/gap/gap_ble.c b/components/bt/host/bluedroid/stack/gap/gap_ble.c index e32151298e..bf3d97d666 100644 --- a/components/bt/host/bluedroid/stack/gap/gap_ble.c +++ b/components/bt/host/bluedroid/stack/gap/gap_ble.c @@ -104,7 +104,7 @@ tGAP_CLCB *gap_ble_find_clcb_by_conn_id(UINT16 conn_id) } } - return p_clcb; + return NULL; } /******************************************************************************* @@ -126,10 +126,10 @@ tGAP_CLCB *gap_clcb_alloc (BD_ADDR bda) memset(p_clcb, 0, sizeof(tGAP_CLCB)); p_clcb->in_use = TRUE; memcpy (p_clcb->bda, bda, BD_ADDR_LEN); - break; + return p_clcb; } } - return p_clcb; + return NULL; } /******************************************************************************* @@ -665,6 +665,12 @@ static void gap_ble_c_cmpl_cback (UINT16 conn_id, tGATTC_OPTYPE op, tGATT_STATUS switch (op_type) { case GATT_UUID_GAP_PREF_CONN_PARAM: GAP_TRACE_EVENT ("GATT_UUID_GAP_PREF_CONN_PARAM"); + /* Verify sufficient data length before reading connection parameters */ + if (p_data->att_value.len < 8) { + GAP_TRACE_ERROR ("GATT_UUID_GAP_PREF_CONN_PARAM: insufficient data length %d", p_data->att_value.len); + gap_ble_cl_op_cmpl(p_clcb, FALSE, 0, NULL); + break; + } /* Extract the peripheral preferred connection parameters and save them */ STREAM_TO_UINT16 (min, pp); @@ -679,7 +685,8 @@ static void gap_ble_c_cmpl_cback (UINT16 conn_id, tGATTC_OPTYPE op, tGATT_STATUS case GATT_UUID_GAP_DEVICE_NAME: GAP_TRACE_EVENT ("GATT_UUID_GAP_DEVICE_NAME\n"); - len = (UINT16)strlen((char *)pp); + /* Use att_value.len instead of strlen to avoid reading beyond buffer */ + len = p_data->att_value.len; if (len > GAP_CHAR_DEV_NAME_SIZE) { len = GAP_CHAR_DEV_NAME_SIZE; } @@ -687,6 +694,12 @@ static void gap_ble_c_cmpl_cback (UINT16 conn_id, tGATTC_OPTYPE op, tGATT_STATUS break; case GATT_UUID_GAP_CENTRAL_ADDR_RESOL: + /* Verify sufficient data length */ + if (p_data->att_value.len < 1) { + GAP_TRACE_ERROR ("GATT_UUID_GAP_CENTRAL_ADDR_RESOL: insufficient data length"); + gap_ble_cl_op_cmpl(p_clcb, FALSE, 0, NULL); + break; + } gap_ble_cl_op_cmpl(p_clcb, TRUE, 1, pp); break; } diff --git a/components/bt/host/bluedroid/stack/gap/gap_conn.c b/components/bt/host/bluedroid/stack/gap/gap_conn.c index db9065de81..cdd6a2d3a9 100644 --- a/components/bt/host/bluedroid/stack/gap/gap_conn.c +++ b/components/bt/host/bluedroid/stack/gap/gap_conn.c @@ -148,7 +148,7 @@ UINT16 GAP_ConnOpen (const char *p_serv_name, UINT8 service_id, BOOLEAN is_serve memcpy (&p_ccb->rem_dev_address[0], p_rem_bda, BD_ADDR_LEN); } else if (!is_server) { - /* remore addr is not specified and is not a server -> bad */ + /* remote addr is not specified and is not a server -> bad */ return (GAP_INVALID_HANDLE); } @@ -775,7 +775,9 @@ static void gap_checks_con_flags (tGAP_CCB *p_ccb) if ((p_ccb->con_flags & GAP_CCB_FLAGS_CONN_DONE) == GAP_CCB_FLAGS_CONN_DONE) { p_ccb->con_state = GAP_CCB_STATE_CONNECTED; - p_ccb->p_callback (p_ccb->gap_handle, GAP_EVT_CONN_OPENED); + if (p_ccb->p_callback) { + p_ccb->p_callback (p_ccb->gap_handle, GAP_EVT_CONN_OPENED); + } } } @@ -933,7 +935,9 @@ static void gap_config_cfm (UINT16 l2cap_cid, tL2CAP_CFG_INFO *p_cfg) gap_checks_con_flags (p_ccb); } else { - p_ccb->p_callback (p_ccb->gap_handle, GAP_EVT_CONN_CLOSED); + if (p_ccb->p_callback) { + p_ccb->p_callback (p_ccb->gap_handle, GAP_EVT_CONN_CLOSED); + } gap_release_ccb (p_ccb); } } @@ -964,7 +968,9 @@ static void gap_disconnect_ind (UINT16 l2cap_cid, BOOLEAN ack_needed) L2CA_DISCONNECT_RSP (l2cap_cid); } - p_ccb->p_callback (p_ccb->gap_handle, GAP_EVT_CONN_CLOSED); + if (p_ccb->p_callback) { + p_ccb->p_callback (p_ccb->gap_handle, GAP_EVT_CONN_CLOSED); + } gap_release_ccb (p_ccb); } @@ -997,7 +1003,9 @@ static void gap_data_ind (UINT16 l2cap_cid, BT_HDR *p_msg) p_ccb->rx_queue_size, p_msg->len); */ - p_ccb->p_callback (p_ccb->gap_handle, GAP_EVT_CONN_DATA_AVAIL); + if (p_ccb->p_callback) { + p_ccb->p_callback (p_ccb->gap_handle, GAP_EVT_CONN_DATA_AVAIL); + } } else { osi_free (p_msg); } @@ -1030,7 +1038,9 @@ static void gap_congestion_ind (UINT16 lcid, BOOLEAN is_congested) p_ccb->is_congested = is_congested; event = (is_congested) ? GAP_EVT_CONN_CONGESTED : GAP_EVT_CONN_UNCONGESTED; - p_ccb->p_callback (p_ccb->gap_handle, event); + if (p_ccb->p_callback) { + p_ccb->p_callback (p_ccb->gap_handle, event); + } if (!is_congested) { while ((p_buf = (BT_HDR *)fixed_queue_dequeue(p_ccb->tx_queue, 0)) != NULL) { From ef96110b4d85792983dd98c204d0bba06b9b20bf Mon Sep 17 00:00:00 2001 From: Zhang Hai Peng Date: Wed, 3 Dec 2025 11:54:23 +0800 Subject: [PATCH 22/40] fix(ble/bluedroid): Fix security issues in HCI module (cherry picked from commit b163685c060047783b0ad3a4b83f2b27302f397c) Co-authored-by: zhanghaipeng --- components/bt/host/bluedroid/hci/hci_layer.c | 12 ++++++++---- components/bt/host/bluedroid/hci/hci_packet_parser.c | 2 +- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/components/bt/host/bluedroid/hci/hci_layer.c b/components/bt/host/bluedroid/hci/hci_layer.c index e5cd263fbc..34bdb57093 100644 --- a/components/bt/host/bluedroid/hci/hci_layer.c +++ b/components/bt/host/bluedroid/hci/hci_layer.c @@ -452,10 +452,12 @@ static bool filter_incoming_event(BT_HDR *packet) STREAM_TO_UINT8(hci_host_env.command_credits, stream); STREAM_TO_UINT16(opcode, stream); wait_entry = get_waiting_command(opcode); - metadata = (hci_cmd_metadata_t *)(wait_entry->data); if (!wait_entry) { HCI_TRACE_WARNING("%s command complete event with no matching command. opcode: 0x%x.", __func__, opcode); - } else if (metadata->command_complete_cb) { + goto intercepted; + } + metadata = (hci_cmd_metadata_t *)(wait_entry->data); + if (metadata->command_complete_cb) { metadata->command_complete_cb(packet, metadata->context); #if (BLE_50_FEATURE_SUPPORT == TRUE) BlE_SYNC *sync_info = btsnd_hcic_ble_get_sync_info(); @@ -482,10 +484,12 @@ static bool filter_incoming_event(BT_HDR *packet) // If a command generates a command status event, it won't be getting a command complete event wait_entry = get_waiting_command(opcode); - metadata = (hci_cmd_metadata_t *)(wait_entry->data); if (!wait_entry) { HCI_TRACE_WARNING("%s command status event with no matching command. opcode: 0x%x", __func__, opcode); - } else if (metadata->command_status_cb) { + goto intercepted; + } + metadata = (hci_cmd_metadata_t *)(wait_entry->data); + if (metadata->command_status_cb) { metadata->command_status_cb(status, &metadata->command, metadata->context); } diff --git a/components/bt/host/bluedroid/hci/hci_packet_parser.c b/components/bt/host/bluedroid/hci/hci_packet_parser.c index b254c9be2a..560ee7b4a1 100644 --- a/components/bt/host/bluedroid/hci/hci_packet_parser.c +++ b/components/bt/host/bluedroid/hci/hci_packet_parser.c @@ -164,7 +164,7 @@ static void parse_ble_read_buffer_size_response_v2 ( uint8_t *iso_pkt_num_ptr) { - uint8_t *stream = read_command_complete_header(response, HCI_BLE_READ_BUFFER_SZIE_V2, 3 /* bytes after */); + uint8_t *stream = read_command_complete_header(response, HCI_BLE_READ_BUFFER_SZIE_V2, 6 /* bytes after: 2+1+2+1 */); assert(stream != NULL); STREAM_TO_UINT16(*data_size_ptr, stream); STREAM_TO_UINT8(*acl_buffer_count_ptr, stream); From d28b874e35b194549d977a6e599cac77e6330a8e Mon Sep 17 00:00:00 2001 From: Zhang Hai Peng Date: Wed, 3 Dec 2025 11:54:24 +0800 Subject: [PATCH 23/40] fix(ble/bluedroid): Fix security issues in GATT module (cherry picked from commit f502b2aab1cfd0002f66e5978771ecbd9c1113ed) Co-authored-by: zhanghaipeng --- .../bt/host/bluedroid/stack/gatt/gatt_db.c | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/components/bt/host/bluedroid/stack/gatt/gatt_db.c b/components/bt/host/bluedroid/stack/gatt/gatt_db.c index 8844ef5dc8..6c578e678f 100644 --- a/components/bt/host/bluedroid/stack/gatt/gatt_db.c +++ b/components/bt/host/bluedroid/stack/gatt/gatt_db.c @@ -1039,20 +1039,23 @@ tGATT_STATUS gatts_write_attr_value_by_handle(tGATT_SVC_DB *p_db, return GATT_APP_RSP; } - if ((p_attr->p_value != NULL) && - (p_attr->p_value->attr_val.attr_max_len >= offset + len) && - p_attr->p_value->attr_val.attr_val != NULL) { - memcpy(p_attr->p_value->attr_val.attr_val + offset, p_value, len); - p_attr->p_value->attr_val.attr_len = len + offset; - return GATT_SUCCESS; - } else if (p_attr->p_value && p_attr->p_value->attr_val.attr_max_len < offset + len){ - GATT_TRACE_DEBUG("Remote device try to write with a length larger then attribute's max length\n"); - return GATT_INVALID_ATTR_LEN; - } else if ((p_attr->p_value == NULL) || (p_attr->p_value->attr_val.attr_val == NULL)){ + if (p_attr->p_value == NULL || p_attr->p_value->attr_val.attr_val == NULL) { GATT_TRACE_ERROR("Error in %s, line=%d, %s should not be NULL here\n", __func__, __LINE__, \ (p_attr->p_value == NULL) ? "p_value" : "attr_val.attr_val"); return GATT_UNKNOWN_ERROR; } + + /* Check for integer overflow: offset + len must not overflow UINT16 + * and must not exceed attr_max_len */ + if (offset > p_attr->p_value->attr_val.attr_max_len || + len > p_attr->p_value->attr_val.attr_max_len - offset) { + GATT_TRACE_DEBUG("Remote device try to write with a length larger than attribute's max length\n"); + return GATT_INVALID_ATTR_LEN; + } + + memcpy(p_attr->p_value->attr_val.attr_val + offset, p_value, len); + p_attr->p_value->attr_val.attr_len = offset + len; + return GATT_SUCCESS; } p_attr = (tGATT_ATTR16 *)p_attr->p_next; From 0f07ad18b614787b960ad5de93882842355ac438 Mon Sep 17 00:00:00 2001 From: sibeibei Date: Thu, 4 Dec 2025 20:10:11 +0800 Subject: [PATCH 24/40] fix: add mutex protection for software trigger RegDMA start to avoid data races --- .../include/esp_private/sleep_retention.h | 9 +++++++++ components/esp_hw_support/sleep_modem.c | 6 ++---- components/esp_hw_support/sleep_retention.c | 20 +++++++++++++++++++ .../esp32c5/include/soc/Kconfig.soc_caps.in | 4 ++++ components/soc/esp32c5/include/soc/soc_caps.h | 1 + 5 files changed, 36 insertions(+), 4 deletions(-) diff --git a/components/esp_hw_support/include/esp_private/sleep_retention.h b/components/esp_hw_support/include/esp_private/sleep_retention.h index 6603c5f0d7..0b211ff885 100644 --- a/components/esp_hw_support/include/esp_private/sleep_retention.h +++ b/components/esp_hw_support/include/esp_private/sleep_retention.h @@ -276,6 +276,15 @@ void sleep_retention_do_extra_retention(bool backup_or_restore); void sleep_retention_do_system_retention(bool backup_or_restore); #endif +#if SOC_PM_SUPPORT_PMU_MODEM_STATE +/** + * @brief Software trigger REGDMA to do phy linked list retention + * + * @param backup_or_restore true for backup register context to memory + * or false for restore to register from memory + */ +void sleep_retention_do_phy_retention(bool backup_or_restore); +#endif /*SOC_PM_SUPPORT_PMU_MODEM_STATE */ #endif // SOC_PAU_SUPPORTED #ifdef __cplusplus diff --git a/components/esp_hw_support/sleep_modem.c b/components/esp_hw_support/sleep_modem.c index 12269b777c..58e7abfbb7 100644 --- a/components/esp_hw_support/sleep_modem.c +++ b/components/esp_hw_support/sleep_modem.c @@ -173,10 +173,8 @@ __attribute__((unused)) void sleep_modem_wifi_modem_state_deinit(void) void IRAM_ATTR sleep_modem_wifi_do_phy_retention(bool restore) { - if (restore) { - pau_regdma_trigger_modem_link_restore(); - } else { - pau_regdma_trigger_modem_link_backup(); + sleep_retention_do_phy_retention(!restore); + if (!restore) { s_sleep_modem.wifi.modem_state_phy_done = 1; } } diff --git a/components/esp_hw_support/sleep_retention.c b/components/esp_hw_support/sleep_retention.c index 8afa63d6a8..003d1f1f1d 100644 --- a/components/esp_hw_support/sleep_retention.c +++ b/components/esp_hw_support/sleep_retention.c @@ -1005,3 +1005,23 @@ void IRAM_ATTR sleep_retention_do_system_retention(bool backup_or_restore) } } #endif + +#if SOC_PM_SUPPORT_PMU_MODEM_STATE +void IRAM_ATTR sleep_retention_do_phy_retention(bool backup_or_restore) +{ +/* since the PHY link and other module links are within the sleep-retention entry (4) context, +* add mutex protection to avoid data race. +*/ +#if SOC_PM_PAU_REGDMA_COMMON_PHY_LINK_ENTRY + _lock_acquire_recursive(&s_retention.lock); +#endif + if (backup_or_restore) { + pau_regdma_trigger_modem_link_backup(); + } else { + pau_regdma_trigger_modem_link_restore(); + } +#if SOC_PM_PAU_REGDMA_COMMON_PHY_LINK_ENTRY + _lock_release_recursive(&s_retention.lock); +#endif +} +#endif /*SOC_PM_SUPPORT_PMU_MODEM_STATE */ diff --git a/components/soc/esp32c5/include/soc/Kconfig.soc_caps.in b/components/soc/esp32c5/include/soc/Kconfig.soc_caps.in index 3c63b50a5c..58e00b8918 100644 --- a/components/soc/esp32c5/include/soc/Kconfig.soc_caps.in +++ b/components/soc/esp32c5/include/soc/Kconfig.soc_caps.in @@ -1739,6 +1739,10 @@ config SOC_PM_PAU_REGDMA_LINK_IDX_WIFIMAC int default 4 +config SOC_PM_PAU_REGDMA_COMMON_PHY_LINK_ENTRY + bool + default y + config SOC_PM_PMU_MIN_SLP_SLOW_CLK_CYCLE_FIXED bool default y diff --git a/components/soc/esp32c5/include/soc/soc_caps.h b/components/soc/esp32c5/include/soc/soc_caps.h index ad186c54e0..f34f326435 100644 --- a/components/soc/esp32c5/include/soc/soc_caps.h +++ b/components/soc/esp32c5/include/soc/soc_caps.h @@ -668,6 +668,7 @@ #define SOC_PM_PAU_LINK_NUM (5) #define SOC_PM_PAU_REGDMA_LINK_CONFIGURABLE (1) #define SOC_PM_PAU_REGDMA_LINK_IDX_WIFIMAC (4) // The range of values for the link index is [0, SOC_PM_PAU_LINK_NUM) +#define SOC_PM_PAU_REGDMA_COMMON_PHY_LINK_ENTRY (1) #define SOC_PM_PMU_MIN_SLP_SLOW_CLK_CYCLE_FIXED (1) From 0e541d83484f273c254a298834593c3bbb20fa7b Mon Sep 17 00:00:00 2001 From: Chen Jichang Date: Fri, 21 Nov 2025 11:46:04 +0800 Subject: [PATCH 25/40] feat(parlio): support parlio gdma eof on p4 rev3.0 --- components/hal/esp32p4/include/hal/parlio_ll.h | 1 + docs/en/api-reference/peripherals/parlio/parlio_tx.rst | 2 +- docs/zh_CN/api-reference/peripherals/parlio/parlio_tx.rst | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/components/hal/esp32p4/include/hal/parlio_ll.h b/components/hal/esp32p4/include/hal/parlio_ll.h index 8166a8bd92..a9bce617b4 100644 --- a/components/hal/esp32p4/include/hal/parlio_ll.h +++ b/components/hal/esp32p4/include/hal/parlio_ll.h @@ -43,6 +43,7 @@ #if HAL_CONFIG(CHIP_SUPPORT_MIN_REV) >= 300 #define PARLIO_LL_TX_VALID_MAX_DELAY 32767 +#define PARLIO_LL_SUPPORT_TX_EOF_FROM_DMA 1 // Support to treat DMA EOF as TX unit EOF #endif #ifdef __cplusplus diff --git a/docs/en/api-reference/peripherals/parlio/parlio_tx.rst b/docs/en/api-reference/peripherals/parlio/parlio_tx.rst index 716dad3b4b..3567f67d8c 100644 --- a/docs/en/api-reference/peripherals/parlio/parlio_tx.rst +++ b/docs/en/api-reference/peripherals/parlio/parlio_tx.rst @@ -325,7 +325,7 @@ The waveform of the external clock input is shown below: .. note:: - Due to hardware limitations, the bitstream generated by the BitScrambler cannot change the length compared to the original bitstream, otherwise transmission blocking or data loss may occur. + Due to hardware limitations, on chip versions prior to rev3.0, the bitstream generated by the bitscrambler must have the same length as the original bitstream, otherwise transmission blocking or data loss may occur. :cpp:func:`parlio_tx_unit_decorate_bitscrambler` and :cpp:func:`parlio_tx_unit_undecorate_bitscrambler` need to be used in pairs. When deleting the TX unit, you need to call :cpp:func:`parlio_tx_unit_undecorate_bitscrambler` first to remove the BitScrambler. diff --git a/docs/zh_CN/api-reference/peripherals/parlio/parlio_tx.rst b/docs/zh_CN/api-reference/peripherals/parlio/parlio_tx.rst index f7bae42759..b3c7a76510 100644 --- a/docs/zh_CN/api-reference/peripherals/parlio/parlio_tx.rst +++ b/docs/zh_CN/api-reference/peripherals/parlio/parlio_tx.rst @@ -325,7 +325,7 @@ TX 单元可以选择各种不同的时钟源,其中外部时钟源较为特 .. note:: - 由于硬件限制,使用比特调节器生成的比特流与原本比特流相比,长度不能发生变化,否则可能会发生传输阻塞或数据丢失。 + 由于硬件限制,在 rev3.0 之前的芯片版本上,使用比特调节器生成的比特流与原本比特流相比,长度不能发生变化,否则可能会发生传输阻塞或数据丢失。 :cpp:func:`parlio_tx_unit_decorate_bitscrambler` 和 :cpp:func:`parlio_tx_unit_undecorate_bitscrambler` 需要成对使用。在删除 TX 单元时,需要先调用 :cpp:func:`parlio_tx_unit_undecorate_bitscrambler` 移除比特调节器。 From 5452adbcb9c2e38cf97bb9ec7fd25282db25b828 Mon Sep 17 00:00:00 2001 From: Zhi Wei Jian Date: Mon, 15 Dec 2025 21:23:25 +0800 Subject: [PATCH 26/40] fix(ble/bluedroid): Fixed reconnection failed with extend adv (cherry picked from commit ec4052c1c7125ab1bc4223bc507e6e778d37728c) Co-authored-by: zhiweijian --- .../bt/host/bluedroid/stack/l2cap/l2c_link.c | 79 +++++++++++-------- 1 file changed, 45 insertions(+), 34 deletions(-) diff --git a/components/bt/host/bluedroid/stack/l2cap/l2c_link.c b/components/bt/host/bluedroid/stack/l2cap/l2c_link.c index fbad1cde15..9cb412e748 100644 --- a/components/bt/host/bluedroid/stack/l2cap/l2c_link.c +++ b/components/bt/host/bluedroid/stack/l2cap/l2c_link.c @@ -377,6 +377,9 @@ BOOLEAN l2c_link_hci_disc_comp (UINT16 handle, UINT8 reason) #endif ///BLE_INCLUDED == TRUE status = FALSE; } else { +#if (BLE_INCLUDED == TRUE) + tL2C_LINK_STATE link_state_temp = p_lcb->link_state; +#endif // (BLE_INCLUDED == TRUE) /* There can be a case when we rejected PIN code authentication */ /* otherwise save a new reason */ if (btm_cb.acl_disc_reason != HCI_ERR_HOST_REJECT_SECURITY) { @@ -471,46 +474,54 @@ BOOLEAN l2c_link_hci_disc_comp (UINT16 handle, UINT8 reason) p_lcb->p_pending_ccb = NULL; #if (BLE_INCLUDED == TRUE) - if(reason == HCI_ERR_CONN_FAILED_ESTABLISHMENT && p_lcb->transport == BT_TRANSPORT_LE) { - #if (GATTC_CONNECT_RETRY_EN == TRUE) - if(p_lcb->link_role == HCI_ROLE_MASTER && p_lcb->retry_create_con < GATTC_CONNECT_RETRY_COUNT) { - L2CAP_TRACE_DEBUG("master retry connect, retry count %d reason 0x%x\n", p_lcb->retry_create_con, reason); - p_lcb->retry_create_con ++; - // create connection retry - if (l2cu_create_conn(p_lcb, BT_TRANSPORT_LE)) { - btm_acl_removed (p_lcb->remote_bd_addr, BT_TRANSPORT_LE); - lcb_is_free = FALSE; /* still using this lcb */ - } else { - L2CAP_TRACE_ERROR("master retry connect failed"); - } - } - #endif // (GATTC_CONNECT_RETRY_EN == TRUE) + if(p_lcb->transport == BT_TRANSPORT_LE) { + // for legacy adv, adv restart in gatt_le_connect_cback->gatt_cleanup_upon_disc->BTM_Recovery_Pre_State + if (reason == HCI_ERR_CONN_FAILED_ESTABLISHMENT) { - #if (BLE_50_FEATURE_SUPPORT == TRUE) - #if (BLE_50_EXTEND_ADV_EN == TRUE) - if(btm_ble_inter_get() && p_lcb->link_role == HCI_ROLE_SLAVE) { - p_lcb->retry_create_con ++; - L2CAP_TRACE_DEBUG("slave restart extend adv, retry count %d reason 0x%x\n", p_lcb->retry_create_con, reason); - tBTM_STATUS start_adv_status = BTM_BleStartExtAdvRestart(handle); - if (start_adv_status != BTM_SUCCESS) { - L2CAP_TRACE_ERROR("slave restart extend adv failed (err 0x%x)", start_adv_status); + #if (BLE_42_FEATURE_SUPPORT == TRUE) + #if (BLE_42_ADV_EN == TRUE) + if(!btm_ble_inter_get() && p_lcb->link_role == HCI_ROLE_SLAVE) { + L2CAP_TRACE_DEBUG("slave resatrt adv, retry count %d reason 0x%x\n", p_lcb->retry_create_con, reason); + tBTM_STATUS start_adv_status = btm_ble_start_adv(); + if (start_adv_status != BTM_SUCCESS) { + L2CAP_TRACE_ERROR("slave resatrt adv failed (err 0x%x)", start_adv_status); + } } + #endif // #if (BLE_42_ADV_EN == TRUE) + #endif // #if (BLE_42_FEATURE_SUPPORT == TRUE) } - #endif // #if (BLE_50_EXTEND_ADV_EN == TRUE) - #endif // #if (BLE_50_FEATURE_SUPPORT == TRUE) + // try to reconnect for master + if ((reason == HCI_ERR_CONN_FAILED_ESTABLISHMENT) || (link_state_temp == LST_CONNECTING)) { - #if (BLE_42_FEATURE_SUPPORT == TRUE) - #if (BLE_42_ADV_EN == TRUE) - if(!btm_ble_inter_get() && p_lcb->link_role == HCI_ROLE_SLAVE) { - p_lcb->retry_create_con ++; - L2CAP_TRACE_DEBUG("slave resatrt adv, retry count %d reason 0x%x\n", p_lcb->retry_create_con, reason); - tBTM_STATUS start_adv_status = btm_ble_start_adv(); - if (start_adv_status != BTM_SUCCESS) { - L2CAP_TRACE_ERROR("slave resatrt adv failed (err 0x%x)", start_adv_status); + #if (GATTC_CONNECT_RETRY_EN == TRUE) + if(p_lcb->link_role == HCI_ROLE_MASTER && p_lcb->retry_create_con < GATTC_CONNECT_RETRY_COUNT) { + L2CAP_TRACE_DEBUG("master retry connect, retry count %d reason 0x%x\n", p_lcb->retry_create_con, reason); + p_lcb->retry_create_con ++; + // create connection retry + if (l2cu_create_conn(p_lcb, BT_TRANSPORT_LE)) { + btm_acl_removed (p_lcb->remote_bd_addr, BT_TRANSPORT_LE); + lcb_is_free = FALSE; /* still using this lcb */ + } else { + L2CAP_TRACE_ERROR("master retry connect failed"); + } } + #endif // (GATTC_CONNECT_RETRY_EN == TRUE) } - #endif // #if (BLE_42_ADV_EN == TRUE) - #endif // #if (BLE_42_FEATURE_SUPPORT == TRUE) + // try to restart extended adv for slave + if ((reason == HCI_ERR_CONN_FAILED_ESTABLISHMENT) || (link_state_temp == LST_DISCONNECTED)) { + #if (BLE_50_FEATURE_SUPPORT == TRUE) + #if (BLE_50_EXTEND_ADV_EN == TRUE) + if(btm_ble_inter_get() && p_lcb->link_role == HCI_ROLE_SLAVE) { + L2CAP_TRACE_DEBUG("slave restart extend adv, retry count %d reason 0x%x\n", p_lcb->retry_create_con, reason); + tBTM_STATUS start_adv_status = BTM_BleStartExtAdvRestart(handle); + if (start_adv_status != BTM_SUCCESS) { + L2CAP_TRACE_ERROR("slave restart extend adv failed (err 0x%x)", start_adv_status); + } + } + #endif // #if (BLE_50_EXTEND_ADV_EN == TRUE) + #endif // #if (BLE_50_FEATURE_SUPPORT == TRUE) + } + } From 3aa928d503b3b2157358f7642239063e7ea0fae6 Mon Sep 17 00:00:00 2001 From: liuning Date: Tue, 16 Dec 2025 12:04:28 +0800 Subject: [PATCH 27/40] fix(coex): fix esp32 coex crash issue (7260f71) --- components/esp_coex/lib | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/esp_coex/lib b/components/esp_coex/lib index adb3badffe..63e292b57b 160000 --- a/components/esp_coex/lib +++ b/components/esp_coex/lib @@ -1 +1 @@ -Subproject commit adb3badffe1201033d23c0a3ab596faadd673539 +Subproject commit 63e292b57b2cda9f9496a71a04bec43e1f0caeba From 9ab736cbd1b757a84df8eda59a3d6990e1f3e8b1 Mon Sep 17 00:00:00 2001 From: Mahavir Jain Date: Mon, 3 Nov 2025 09:56:58 +0530 Subject: [PATCH 28/40] fix(spi_flash): limit CPU clock to 160MHz for encrypted flash writes --- components/spi_flash/esp_flash_api.c | 53 +++++++++++++++++++++++++++- 1 file changed, 52 insertions(+), 1 deletion(-) diff --git a/components/spi_flash/esp_flash_api.c b/components/spi_flash/esp_flash_api.c index 63c1dba50f..b856154143 100644 --- a/components/spi_flash/esp_flash_api.c +++ b/components/spi_flash/esp_flash_api.c @@ -1248,9 +1248,50 @@ esp_err_t esp_flash_set_io_mode(esp_flash_t* chip, bool qe) } #endif //CONFIG_SPI_FLASH_ROM_IMPL +#if CONFIG_IDF_TARGET_ESP32C5 +// Hardware workaround: ESP32-C5 encrypted flash writes require CPU freq ≤ 160 MHz +#include "soc/rtc.h" + +static int s_esp32c5_saved_cpu_freq_mhz; + +static IRAM_ATTR void esp32c5_freq_limit_acquire(void) +{ + rtc_cpu_freq_config_t old_config, new_config; + rtc_clk_cpu_freq_get_config(&old_config); + + if (old_config.freq_mhz <= 160) { + s_esp32c5_saved_cpu_freq_mhz = 0; // No change needed + return; + } + s_esp32c5_saved_cpu_freq_mhz = old_config.freq_mhz; + + if (rtc_clk_cpu_freq_mhz_to_config(160, &new_config)) { + rtc_clk_cpu_freq_set_config_fast(&new_config); + } +} + +static IRAM_ATTR void esp32c5_freq_limit_release(void) +{ + if (s_esp32c5_saved_cpu_freq_mhz == 0) { + return; // No change was made + } + + rtc_cpu_freq_config_t new_config; + + if (rtc_clk_cpu_freq_mhz_to_config(s_esp32c5_saved_cpu_freq_mhz, &new_config)) { + rtc_clk_cpu_freq_set_config_fast(&new_config); + } + + s_esp32c5_saved_cpu_freq_mhz = 0; +} +#endif // CONFIG_IDF_TARGET_ESP32C5 + #if !(CONFIG_SPI_FLASH_ROM_IMPL && !ESP_ROM_HAS_ENCRYPTED_WRITES_USING_LEGACY_DRV) // use `esp_flash_write_encrypted` ROM version on chips later than C3 and S3 FORCE_INLINE_ATTR esp_err_t s_encryption_write_lock(esp_flash_t *chip) { +#if CONFIG_IDF_TARGET_ESP32C5 + esp32c5_freq_limit_acquire(); +#endif #if CONFIG_IDF_TARGET_ESP32S2 esp_crypto_dma_lock_acquire(); #endif //CONFIG_IDF_TARGET_ESP32S2 @@ -1262,6 +1303,9 @@ FORCE_INLINE_ATTR esp_err_t s_encryption_write_unlock(esp_flash_t *chip) { #if CONFIG_IDF_TARGET_ESP32S2 esp_crypto_dma_lock_release(); #endif //CONFIG_IDF_TARGET_ESP32S2 +#if CONFIG_IDF_TARGET_ESP32C5 + esp32c5_freq_limit_release(); +#endif return err; } @@ -1504,7 +1548,14 @@ esp_err_t IRAM_ATTR esp_flash_write_encrypted(esp_flash_t *chip, uint32_t addres if (length > chip->size - address) { return ESP_ERR_INVALID_ARG; } - return rom_esp_flash_write_encrypted(chip, address, buffer, length); +#if CONFIG_IDF_TARGET_ESP32C5 + esp32c5_freq_limit_acquire(); +#endif + err = rom_esp_flash_write_encrypted(chip, address, buffer, length); +#if CONFIG_IDF_TARGET_ESP32C5 + esp32c5_freq_limit_release(); +#endif + return err; } #endif // !(CONFIG_SPI_FLASH_ROM_IMPL && !ESP_ROM_HAS_ENCRYPTED_WRITES_USING_LEGACY_DRV) From faf6cc4f84eed799a4e72a3b4564d9b4d0374d80 Mon Sep 17 00:00:00 2001 From: Xiao Xufeng Date: Tue, 25 Nov 2025 15:03:25 +0000 Subject: [PATCH 29/40] feat(spi_flash): implement dynamic CPU frequency switching workaround for encrypted writes This commit implements a workaround that allows ESP32-C5 to run at 240MHz CPU frequency normally, while automatically reducing CPU frequency during encrypted flash writes to ensure correct operation. The frequency limit is chip revision dependent: - v1.2 and above: limited to 160MHz during encrypted writes - v1.0 and below: limited to 80MHz during encrypted writes Key implementation details: - Frequency limiting is triggered automatically when esp_flash_write_encrypted() is called - Uses start() flags (ESP_FLASH_START_FLAG_LIMIT_CPU_FREQ) to integrate with OS layer - Works with both PM enabled and disabled configurations - Frequency is automatically restored after encrypted write completes - For ESP32-C5 with 120MHz flash, Flash clock and timing registers are adjusted when CPU frequency is reduced to 80MHz - SPI1 timing registers are configured during frequency switching since encrypted writes use SPI1 and must work correctly at reduced CPU frequencies Code improvements: - Use SOC_MSPI_FREQ_AXI_CONSTRAINED capability macro instead of hardcoded chip checks - Control workaround via Kconfig (CONFIG_PM_WORKAROUND_FREQ_LIMIT_ENABLED) instead of hardcoded macros - Add comprehensive test cases covering various PM configurations and edge cases This workaround enables ESP32-C5 applications to benefit from 240MHz CPU performance while maintaining reliable encrypted flash write functionality. --- components/esp_hw_support/clk_utils.c | 41 +- .../include/esp_private/mspi_timing_tuning.h | 7 +- .../mspi_timing_tuning/mspi_timing_tuning.c | 27 +- components/esp_pm/CMakeLists.txt | 15 +- components/esp_pm/Kconfig | 8 + .../esp_pm/include/esp_private/pm_impl.h | 23 +- .../include/esp_private/pm_impl_freq_limit.h | 59 + components/esp_pm/pm_c5_flash_freq_limit.c | 168 +++ components/esp_pm/pm_impl.c | 98 +- .../esp_system/port/soc/esp32c5/Kconfig.cpu | 7 + components/esp_system/startup_funcs.c | 5 +- .../esp32c5/include/soc/Kconfig.soc_caps.in | 4 + components/soc/esp32c5/include/soc/soc_caps.h | 1 + .../esp32c61/include/soc/Kconfig.soc_caps.in | 4 + .../soc/esp32c61/include/soc/soc_caps.h | 1 + .../esp32h4/include/soc/Kconfig.soc_caps.in | 4 + components/soc/esp32h4/include/soc/soc_caps.h | 1 + .../esp32p4/include/soc/Kconfig.soc_caps.in | 4 + components/soc/esp32p4/include/soc/soc_caps.h | 1 + components/spi_flash/Kconfig | 7 + components/spi_flash/esp_flash_api.c | 151 +- components/spi_flash/esp_flash_spi_init.c | 26 +- components/spi_flash/include/esp_flash.h | 8 +- .../spi_flash/include/esp_flash_internal.h | 10 + .../esp_private/spi_flash_freq_limit_cbs.h | 49 + .../spi_flash/include/memspi_host_driver.h | 13 +- components/spi_flash/linker.lf | 7 +- components/spi_flash/memspi_host_driver.c | 57 +- components/spi_flash/spi_flash_os_func_app.c | 37 +- components/spi_flash/spi_flash_os_func_noos.c | 2 +- .../spi_flash/test_apps/.build-test-rules.yml | 9 + .../esp_flash_freq_limit/CMakeLists.txt | 17 + .../test_apps/esp_flash_freq_limit/README.md | 2 + .../esp_flash_freq_limit/main/CMakeLists.txt | 9 + .../esp_flash_freq_limit/main/test_app_main.c | 25 + .../main/test_esp_flash_freq_limit.c | 1250 +++++++++++++++++ .../esp_flash_freq_limit/partitions.csv | 5 + .../pytest_esp_flash_freq_limit.py | 23 + .../sdkconfig.ci.pm_disabled_160mhz | 2 + .../sdkconfig.ci.pm_disabled_240mhz | 2 + .../sdkconfig.ci.pm_disabled_240mhz_xip_psram | 8 + .../sdkconfig.ci.pm_enabled_160mhz | 6 + .../sdkconfig.ci.pm_enabled_240mhz | 6 + .../sdkconfig.ci.pm_enabled_240mhz_xip_psram | 12 + ...ig.ci.pm_enabled_240mhz_xip_psram_rom_impl | 13 + .../esp_flash_freq_limit/sdkconfig.defaults | 2 + .../release-5.x/5.5/peripherals.rst | 5 + .../release-5.x/5.5/peripherals.rst | 5 + 48 files changed, 2117 insertions(+), 129 deletions(-) create mode 100644 components/esp_pm/include/esp_private/pm_impl_freq_limit.h create mode 100644 components/esp_pm/pm_c5_flash_freq_limit.c create mode 100644 components/spi_flash/include/esp_private/spi_flash_freq_limit_cbs.h create mode 100644 components/spi_flash/test_apps/esp_flash_freq_limit/CMakeLists.txt create mode 100644 components/spi_flash/test_apps/esp_flash_freq_limit/README.md create mode 100644 components/spi_flash/test_apps/esp_flash_freq_limit/main/CMakeLists.txt create mode 100644 components/spi_flash/test_apps/esp_flash_freq_limit/main/test_app_main.c create mode 100644 components/spi_flash/test_apps/esp_flash_freq_limit/main/test_esp_flash_freq_limit.c create mode 100644 components/spi_flash/test_apps/esp_flash_freq_limit/partitions.csv create mode 100644 components/spi_flash/test_apps/esp_flash_freq_limit/pytest_esp_flash_freq_limit.py create mode 100644 components/spi_flash/test_apps/esp_flash_freq_limit/sdkconfig.ci.pm_disabled_160mhz create mode 100644 components/spi_flash/test_apps/esp_flash_freq_limit/sdkconfig.ci.pm_disabled_240mhz create mode 100644 components/spi_flash/test_apps/esp_flash_freq_limit/sdkconfig.ci.pm_disabled_240mhz_xip_psram create mode 100644 components/spi_flash/test_apps/esp_flash_freq_limit/sdkconfig.ci.pm_enabled_160mhz create mode 100644 components/spi_flash/test_apps/esp_flash_freq_limit/sdkconfig.ci.pm_enabled_240mhz create mode 100644 components/spi_flash/test_apps/esp_flash_freq_limit/sdkconfig.ci.pm_enabled_240mhz_xip_psram create mode 100644 components/spi_flash/test_apps/esp_flash_freq_limit/sdkconfig.ci.pm_enabled_240mhz_xip_psram_rom_impl create mode 100644 components/spi_flash/test_apps/esp_flash_freq_limit/sdkconfig.defaults diff --git a/components/esp_hw_support/clk_utils.c b/components/esp_hw_support/clk_utils.c index 4415d41754..99eabf816e 100644 --- a/components/esp_hw_support/clk_utils.c +++ b/components/esp_hw_support/clk_utils.c @@ -7,6 +7,7 @@ #include #include #include +#include #include "sdkconfig.h" #include "esp_check.h" #include "esp_log.h" @@ -17,25 +18,31 @@ #include "esp_private/mspi_timing_tuning.h" #include "esp_private/esp_clk_utils.h" +// Not directly divide to avoid truncation issue +// DIG-498 +#if CONFIG_IDF_TARGET_ESP32P4 +#define BELOW_FREQ_THRESHOLD(freq) ((freq) < CONFIG_SPIRAM_SPEED) +#elif CONFIG_IDF_TARGET_ESP32C5 || CONFIG_IDF_TARGET_ESP32C61 || CONFIG_IDF_TARGET_ESP32H4 +#define BELOW_FREQ_THRESHOLD(freq) ((freq) * 8 < CONFIG_SPIRAM_SPEED) +#endif + #if !CONFIG_APP_BUILD_TYPE_PURE_RAM_APP void esp_clk_utils_mspi_speed_mode_sync_before_cpu_freq_switching(uint32_t target_cpu_src_freq, uint32_t target_cpu_freq) { #if MSPI_TIMING_LL_FLASH_CPU_CLK_SRC_BINDED (void) target_cpu_freq; + /* For ESP32S3, the clock source of MSPI is same as the CPU. When CPU use XTAL as clock source, we need to sync the + * MSPI speed mode. */ if (target_cpu_src_freq <= clk_ll_xtal_load_freq_mhz()) { mspi_timing_change_speed_mode_cache_safe(true); } -#elif CONFIG_IDF_TARGET_ESP32P4 - (void) target_cpu_src_freq; - /** - * Workaround for ESP32P4, - * f_cpu >= f_mspi +#elif SOC_SPI_MEM_PSRAM_FREQ_AXI_CONSTRAINED && CONFIG_SPIRAM + /* On chips with AXI bus, currently there is a restriction that AXI frequency (usually equals to a portion of CPU + * frequency) needs to be greater than or equal to MSPI PSRAM frequency to avoid writing MSPI FIFO overflow. */ - if (((target_cpu_freq) < CONFIG_ESPTOOLPY_FLASHFREQ_VAL) -#if CONFIG_SPIRAM - || ((target_cpu_freq) < CONFIG_SPIRAM_SPEED) -#endif - ) { + if (BELOW_FREQ_THRESHOLD(target_cpu_freq)) { + // Before switching to low speed mode, verify CPU frequency meets the constraint + assert(target_cpu_freq >= mspi_timing_get_psram_low_speed_freq_mhz()); mspi_timing_change_speed_mode_cache_safe(true); } #else @@ -51,17 +58,11 @@ void esp_clk_utils_mspi_speed_mode_sync_after_cpu_freq_switching(uint32_t target if (target_cpu_src_freq > clk_ll_xtal_load_freq_mhz()) { mspi_timing_change_speed_mode_cache_safe(false); } -#elif CONFIG_IDF_TARGET_ESP32P4 - (void) target_cpu_src_freq; - /** - * Workaround for ESP32P4, - * f_cpu >= f_mspi +#elif SOC_SPI_MEM_PSRAM_FREQ_AXI_CONSTRAINED && CONFIG_SPIRAM + /* On chips with AXI bus, currently there is a restriction that AXI frequency (usually equals to a portion of CPU + * frequency) needs to be greater than or equal to MSPI PSRAM frequency to avoid writing MSPI FIFO overflow. */ - if (((target_cpu_freq) >= CONFIG_ESPTOOLPY_FLASHFREQ_VAL) -#if CONFIG_SPIRAM - && ((target_cpu_freq) >= CONFIG_SPIRAM_SPEED) -#endif - ) { + if (!BELOW_FREQ_THRESHOLD(target_cpu_freq)) { mspi_timing_change_speed_mode_cache_safe(false); } #else diff --git a/components/esp_hw_support/mspi_timing_tuning/include/esp_private/mspi_timing_tuning.h b/components/esp_hw_support/mspi_timing_tuning/include/esp_private/mspi_timing_tuning.h index c1e999cc18..be59ef7dfb 100644 --- a/components/esp_hw_support/mspi_timing_tuning/include/esp_private/mspi_timing_tuning.h +++ b/components/esp_hw_support/mspi_timing_tuning/include/esp_private/mspi_timing_tuning.h @@ -24,6 +24,12 @@ extern "C" { */ void mspi_timing_enter_low_speed_mode(bool control_spi1); +/** + * @brief Get PSRAM frequency in low speed mode (MHz) + * @return PSRAM frequency in MHz when in low speed mode + */ +uint32_t mspi_timing_get_psram_low_speed_freq_mhz(void); + /** * @brief Make MSPI work under the frequency as users set, may add certain delays to MSPI RX direction to meet timing requirements. * @param control_spi1 Select whether to control SPI1. For tuning, we need to use SPI1. After tuning (during startup stage), let the flash driver to control SPI1 @@ -54,7 +60,6 @@ void mspi_timing_psram_tuning(void); */ void mspi_timing_set_pin_drive_strength(void); - #ifdef __cplusplus } #endif diff --git a/components/esp_hw_support/mspi_timing_tuning/mspi_timing_tuning.c b/components/esp_hw_support/mspi_timing_tuning/mspi_timing_tuning.c index 97517c565e..e5451d5216 100644 --- a/components/esp_hw_support/mspi_timing_tuning/mspi_timing_tuning.c +++ b/components/esp_hw_support/mspi_timing_tuning/mspi_timing_tuning.c @@ -481,6 +481,11 @@ void mspi_timing_psram_tuning(void) /*------------------------------------------------------------------------------ * APIs to make SPI0 (and SPI1) FLASH work for high/low freq *----------------------------------------------------------------------------*/ +uint32_t mspi_timing_get_psram_low_speed_freq_mhz(void) +{ + return 20; +} + void mspi_timing_enter_low_speed_mode(bool control_spi1) { #if MSPI_TIMING_LL_FLASH_CLK_SRC_CHANGEABLE @@ -500,14 +505,14 @@ void mspi_timing_enter_low_speed_mode(bool control_spi1) * Should be extended to other no-timing-tuning chips if needed. e.g.: * we still need to turn down Flash / PSRAM clock speed at a certain period of time */ - mspi_timing_config_set_flash_clock(20, MSPI_TIMING_SPEED_MODE_LOW_PERF, control_spi1); - mspi_timing_config_set_psram_clock(20, MSPI_TIMING_SPEED_MODE_LOW_PERF, control_spi1); -#endif //#if SOC_SPI_MEM_SUPPORT_TIMING_TUNING - + uint32_t low_speed_freq_mhz = mspi_timing_get_psram_low_speed_freq_mhz(); + mspi_timing_config_set_flash_clock(low_speed_freq_mhz, MSPI_TIMING_SPEED_MODE_LOW_PERF, control_spi1); + mspi_timing_config_set_psram_clock(low_speed_freq_mhz, MSPI_TIMING_SPEED_MODE_LOW_PERF, control_spi1); #if MSPI_TIMING_FLASH_NEEDS_TUNING || MSPI_TIMING_PSRAM_NEEDS_TUNING mspi_timing_flash_config_clear_tuning_regs(control_spi1); mspi_timing_psram_config_clear_tuning_regs(control_spi1); #endif //#if MSPI_TIMING_FLASH_NEEDS_TUNING || MSPI_TIMING_PSRAM_NEEDS_TUNING +#endif //#if SOC_SPI_MEM_SUPPORT_TIMING_TUNING } /** @@ -568,10 +573,24 @@ void mspi_timing_change_speed_mode_cache_safe(bool switch_down) if (switch_down) { //enter MSPI low speed mode, extra delays should be removed +#if CONFIG_IDF_TARGET_ESP32C5 + // ESP32-C5 needs to perform encrypted flash writes even when CPU frequency is reduced. + // Since encrypted writes use SPI1, we need to configure SPI1 timing registers as well + // during runtime frequency switching to ensure proper operation. + mspi_timing_enter_low_speed_mode(true); +#else mspi_timing_enter_low_speed_mode(false); +#endif } else { //enter MSPI high speed mode, extra delays should be considered +#if CONFIG_IDF_TARGET_ESP32C5 + // ESP32-C5 needs to perform encrypted flash writes even when CPU frequency is reduced. + // Since encrypted writes use SPI1, we need to configure SPI1 timing registers as well + // during runtime frequency switching to ensure proper operation. + mspi_timing_enter_high_speed_mode(true); +#else mspi_timing_enter_high_speed_mode(false); +#endif } #if SOC_CACHE_FREEZE_SUPPORTED diff --git a/components/esp_pm/CMakeLists.txt b/components/esp_pm/CMakeLists.txt index 27ce9ada3c..e540b64b32 100644 --- a/components/esp_pm/CMakeLists.txt +++ b/components/esp_pm/CMakeLists.txt @@ -1,10 +1,21 @@ idf_build_get_property(target IDF_TARGET) +set(priv_requires esp_system esp_driver_gpio esp_timer) if(${target} STREQUAL "linux") return() # This component is not supported by the POSIX/Linux simulator endif() -idf_component_register(SRCS "pm_locks.c" "pm_trace.c" "pm_impl.c" +set(srcs "pm_locks.c" "pm_trace.c" "pm_impl.c") +if(CONFIG_PM_WORKAROUND_FREQ_LIMIT_ENABLED) + list(APPEND srcs "pm_c5_flash_freq_limit.c") +endif() + +if(${target} STREQUAL "esp32c5") + # pm_c5_flash_freq_limit.c needs spi_flash header when CONFIG_PM_WORKAROUND_FREQ_LIMIT_ENABLED is enabled + list(APPEND priv_requires spi_flash) +endif() + +idf_component_register(SRCS ${srcs} INCLUDE_DIRS include - PRIV_REQUIRES esp_system esp_driver_gpio esp_timer + PRIV_REQUIRES "${priv_requires}" LDFRAGMENTS linker.lf) diff --git a/components/esp_pm/Kconfig b/components/esp_pm/Kconfig index ee4ec6f560..ca843fff48 100644 --- a/components/esp_pm/Kconfig +++ b/components/esp_pm/Kconfig @@ -210,4 +210,12 @@ menu "Power Management" NOTE: Enabling these callbacks may change sleep duration calculations based on time spent in callback and hence it is highly recommended to keep them as short as possible + config PM_WORKAROUND_FREQ_LIMIT_ENABLED + bool + default y if SPI_FLASH_FREQ_LIMIT_C5_240MHZ + help + Workaround for frequency limit during encrypted flash writes. + Only enabled when SPI_FLASH_FREQ_LIMIT_C5_240MHZ is enabled. + This is an internal configuration, automatically set based on SPI Flash configuration. + endmenu # "Power Management" diff --git a/components/esp_pm/include/esp_private/pm_impl.h b/components/esp_pm/include/esp_private/pm_impl.h index 02364050c7..56fab2fec9 100644 --- a/components/esp_pm/include/esp_private/pm_impl.h +++ b/components/esp_pm/include/esp_private/pm_impl.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2016-2023 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2016-2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -15,7 +15,6 @@ #include "soc/rtc.h" #include "esp_pm.h" -#include "esp_timer.h" #include "sdkconfig.h" #ifdef __cplusplus @@ -26,11 +25,11 @@ extern "C" { * This is an enum of possible power modes supported by the implementation */ typedef enum { - PM_MODE_LIGHT_SLEEP,//!< Light sleep - PM_MODE_APB_MIN, //!< Idle (no CPU frequency or APB frequency locks) - PM_MODE_APB_MAX, //!< Maximum APB frequency mode - PM_MODE_CPU_MAX, //!< Maximum CPU frequency mode - PM_MODE_COUNT //!< Number of items + PM_MODE_LIGHT_SLEEP, //!< Light sleep + PM_MODE_APB_MIN, //!< Idle (no CPU frequency or APB frequency locks) + PM_MODE_APB_MAX, //!< Maximum APB frequency mode + PM_MODE_CPU_MAX, //!< Maximum CPU frequency mode + PM_MODE_COUNT //!< Number of items } pm_mode_t; /** @@ -141,8 +140,18 @@ esp_err_t esp_pm_register_skip_light_sleep_callback(skip_light_sleep_cb_t cb); */ esp_err_t esp_pm_unregister_skip_light_sleep_callback(skip_light_sleep_cb_t cb); + +/** + * @brief Initialize flash frequency limit + * + * This function initializes the flash frequency limit. + * @note This function is only available when CONFIG_PM_WORKAROUND_FREQ_LIMIT_ENABLED is enabled. + */ +void esp_pm_flash_freq_limit_init(void); + #ifdef CONFIG_PM_PROFILING #define WITH_PROFILING +#include "esp_timer.h" #endif #ifdef WITH_PROFILING diff --git a/components/esp_pm/include/esp_private/pm_impl_freq_limit.h b/components/esp_pm/include/esp_private/pm_impl_freq_limit.h new file mode 100644 index 0000000000..252ca780d7 --- /dev/null +++ b/components/esp_pm/include/esp_private/pm_impl_freq_limit.h @@ -0,0 +1,59 @@ +/* + * SPDX-FileCopyrightText: 2016-2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include "sdkconfig.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#if CONFIG_PM_WORKAROUND_FREQ_LIMIT_ENABLED + +/** + * @brief Initialize and pre-calculate forced CPU_MAX frequency configuration (private function for spi_flash) + * + * This function pre-calculates and stores the forced CPU_MAX frequency configuration + * based on the given frequency limit. The configuration is computed once during + * initialization and reused during runtime for better performance. + * + * @param limit_freq_mhz Frequency limit in MHz + * @note This is a private function, only for use by spi_flash component. + * @note Must be called during initialization before esp_pm_impl_cpu_max_freq_force(). + * @note This function is only available when CONFIG_PM_WORKAROUND_FREQ_LIMIT_ENABLED is enabled and CONFIG_PM_ENABLE is enabled. + */ +void esp_pm_impl_cpu_max_freq_force_init(uint32_t limit_freq_mhz); + +/** + * @brief Force CPU_MAX frequency to pre-configured limit (private function for spi_flash) + * + * This function activates the pre-configured forced CPU_MAX frequency limit. + * When forced, all reads of CPU_MAX frequency will use the pre-configured value + * instead of the configured value. + * + * @note This is a private function, only for use by spi_flash component. + * @note The forced frequency configuration must be pre-calculated during initialization. + * @note This function is only available when CONFIG_PM_WORKAROUND_FREQ_LIMIT_ENABLED is enabled and CONFIG_PM_ENABLE is enabled. + */ +void esp_pm_impl_cpu_max_freq_force(void); + +/** + * @brief Unforce CPU_MAX frequency (private function for spi_flash) + * + * This function removes the forced CPU_MAX frequency, allowing the configured + * value to be used again. + * @note This is a private function, only for use by spi_flash component. + * @note This function is only available when CONFIG_PM_WORKAROUND_FREQ_LIMIT_ENABLED is enabled and CONFIG_PM_ENABLE is enabled. + */ +void esp_pm_impl_cpu_max_freq_unforce(void); + +#endif // CONFIG_PM_WORKAROUND_FREQ_LIMIT_ENABLED + +#ifdef __cplusplus +} +#endif diff --git a/components/esp_pm/pm_c5_flash_freq_limit.c b/components/esp_pm/pm_c5_flash_freq_limit.c new file mode 100644 index 0000000000..f077015650 --- /dev/null +++ b/components/esp_pm/pm_c5_flash_freq_limit.c @@ -0,0 +1,168 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + + //This file implements esp_flash_freq_limit_cb and esp_flash_freq_unlimit_cb callbacks to limit and unlimit CPU + //frequency when encrypted flash writes are performed. + +#include "sdkconfig.h" + +#include "esp_log.h" +#include "esp_attr.h" +#include "esp_rom_sys.h" +#include "soc/chip_revision.h" +#include "soc/rtc.h" +#include "hal/efuse_hal.h" + +#include "esp_private/pm_impl_freq_limit.h" +#include "esp_private/spi_flash_freq_limit_cbs.h" +#include "esp_private/esp_clk_utils.h" + +/** + * @brief Get the frequency limit for flash encryption lock based on chip revision + * + * @return uint32_t Frequency limit (MHz) + * + * Logic: + * - v1.2+: limit to 160MHz + * - v1.0: limit to 80MHz + */ +static uint32_t IRAM_ATTR get_encrypt_lock_freq_limit(void) +{ + unsigned int chip_revision = efuse_hal_chip_revision(); + if (ESP_CHIP_REV_ABOVE(chip_revision, 102)) { + return 160; + } else { + return 80; + } +} + +void esp_pm_flash_freq_limit_init(void) +{ + uint32_t limit_freq_mhz = get_encrypt_lock_freq_limit(); + ESP_EARLY_LOGW("spi_flash", "CPU frequency is set to 240MHz. esp_flash_write_encrypted() will automatically limit CPU frequency to %dMHz during execution.", limit_freq_mhz); +#ifdef CONFIG_PM_ENABLE + /* Pre-calculate and store forced frequency configuration during initialization. + * This is done here to avoid runtime calculation overhead in lock/unlock functions. + */ + esp_pm_impl_cpu_max_freq_force_init(limit_freq_mhz); +#endif +} + +#if !CONFIG_PM_ENABLE +/* Saved original frequency for !PM_ENABLE case (0 means no change was made) */ +static uint32_t s_saved_freq_mhz = 0; + +// Switch CPU frequency with all necessary synchronization (for !PM_ENABLE case) +// Similar to do_switch() but without PM-specific state management +static void IRAM_ATTR esp_pm_impl_switch_cpu_freq(const rtc_cpu_freq_config_t *old_config, const rtc_cpu_freq_config_t *new_config) +{ + if (new_config->freq_mhz != old_config->freq_mhz) { + //No need to run on_freq_update for ccount, since that's not supported on C5. +#if !CONFIG_APP_BUILD_TYPE_PURE_RAM_APP + /* Synchronize MSPI speed mode before CPU frequency switching */ + esp_clk_utils_mspi_speed_mode_sync_before_cpu_freq_switching(new_config->source_freq_mhz, new_config->freq_mhz); +#endif + + //Not taking s_time_update_lock since C5 have fixed divider for systimer + rtc_clk_cpu_freq_set_config_fast(new_config); + +#if !CONFIG_APP_BUILD_TYPE_PURE_RAM_APP + /* Synchronize MSPI speed mode after CPU frequency switching */ + esp_clk_utils_mspi_speed_mode_sync_after_cpu_freq_switching(new_config->source_freq_mhz, new_config->freq_mhz); +#endif + } +} + +/** + * @brief Limit CPU frequency to target frequency (for !PM_ENABLE case only) + * + * This function automatically reads the current CPU frequency, saves it, + * and switches to the target frequency if current frequency is higher. + * The original frequency can be restored by calling unlimit_cpu_freq(). + * + * @param target_freq_mhz Target frequency limit in MHz + * @note limit->unlimit cannot be nested, and this function must not be called concurrently + * @note This is a private function, only for use by spi_flash component. + * @note This function is only available when CONFIG_PM_WORKAROUND_FREQ_LIMIT_ENABLED is enabled and CONFIG_PM_ENABLE is not set. + */ +static void IRAM_ATTR limit_cpu_freq(uint32_t target_freq_mhz) +{ + /* PM not enabled, directly switch frequency and save original frequency */ + rtc_cpu_freq_config_t old_config, new_config; + rtc_clk_cpu_freq_get_config(&old_config); + + if (old_config.freq_mhz > target_freq_mhz) { + s_saved_freq_mhz = old_config.freq_mhz; + if (rtc_clk_cpu_freq_mhz_to_config(target_freq_mhz, &new_config)) { + /* Use PM implementation function to switch frequency with all necessary synchronization */ + esp_pm_impl_switch_cpu_freq(&old_config, &new_config); + const unsigned wait_clock_stable_us = 10; + esp_rom_delay_us(wait_clock_stable_us); + } + } else { + s_saved_freq_mhz = 0; /* No change needed */ + } +} + +/** + * @brief Unlimit CPU frequency, restoring to original frequency (for !PM_ENABLE case only) + * + * This function restores the CPU frequency to the value saved by + * limit_cpu_freq(). If no frequency change was made, this function + * does nothing. + * + * @note limit->unlimit cannot be nested, and this function must not be called concurrently + * @note This is a private function, only for use by spi_flash component. + * @note This function is only available when CONFIG_PM_WORKAROUND_FREQ_LIMIT_ENABLED is enabled and CONFIG_PM_ENABLE is not set. + */ +static void IRAM_ATTR unlimit_cpu_freq(void) +{ + /* PM not enabled, restore to original frequency */ + if (s_saved_freq_mhz > 0) { + rtc_cpu_freq_config_t old_config, new_config; + rtc_clk_cpu_freq_get_config(&old_config); + if (rtc_clk_cpu_freq_mhz_to_config(s_saved_freq_mhz, &new_config)) { + /* Use PM implementation function to switch frequency with all necessary synchronization */ + esp_pm_impl_switch_cpu_freq(&old_config, &new_config); + const unsigned wait_clock_stable_us = 10; + esp_rom_delay_us(wait_clock_stable_us); + } + } + /* If s_saved_freq_mhz == 0, no change was made, so no need to restore */ + s_saved_freq_mhz = 0; +} +#endif // !CONFIG_PM_ENABLE + +void IRAM_ATTR esp_flash_freq_limit_cb(void) +{ + /* Limit the frequency */ +#ifdef CONFIG_PM_ENABLE + /* PM enabled, use force mechanism */ + esp_pm_impl_cpu_max_freq_force(); +#else + /* PM not enabled, use limit mechanism */ + uint32_t limit_freq_mhz = get_encrypt_lock_freq_limit(); + /* Call PM implementation function to limit CPU frequency. + * This function automatically reads current frequency, saves it, and switches to target frequency. + */ + limit_cpu_freq(limit_freq_mhz); +#endif +} + +void IRAM_ATTR esp_flash_freq_unlimit_cb(void) +{ + /* Restore the frequency */ +#ifdef CONFIG_PM_ENABLE + /* PM enabled, use unforce mechanism */ + esp_pm_impl_cpu_max_freq_unforce(); +#else + /* PM not enabled, use unlimit mechanism */ + /* Call PM implementation function to unlimit CPU frequency. + * This function restores the original frequency saved by limit_cpu_freq(). + */ + unlimit_cpu_freq(); +#endif +} diff --git a/components/esp_pm/pm_impl.c b/components/esp_pm/pm_impl.c index 3e33182d91..9d48750c73 100644 --- a/components/esp_pm/pm_impl.c +++ b/components/esp_pm/pm_impl.c @@ -10,6 +10,7 @@ #include #include #include +#include #include "sdkconfig.h" #include "esp_attr.h" @@ -34,6 +35,7 @@ #include "freertos/FreeRTOS.h" #include "freertos/task.h" +#include "freertos/portmacro.h" #if CONFIG_FREERTOS_SYSTICK_USES_CCOUNT #include "xtensa_timer.h" #include "xtensa/core-macros.h" @@ -42,6 +44,7 @@ #include "esp_private/pm_impl.h" #include "esp_private/pm_trace.h" #include "esp_private/esp_timer_private.h" +#include "esp_timer.h" #include "esp_private/esp_clk.h" #include "esp_private/esp_clk_tree_common.h" #include "esp_private/sleep_cpu.h" @@ -51,7 +54,7 @@ #include "esp_private/esp_clk_utils.h" #include "esp_sleep.h" #include "esp_memory_utils.h" - +#include "esp_rom_sys.h" #if SOC_PERIPH_CLK_CTRL_SHARED #define HP_UART_SRC_CLK_ATOMIC() PERIPH_RCC_ATOMIC() @@ -168,6 +171,16 @@ static esp_pm_lock_handle_t s_rtos_lock_handle[CONFIG_FREERTOS_NUMBER_OF_CORES]; */ static rtc_cpu_freq_config_t s_cpu_freq_by_mode[PM_MODE_COUNT]; +#if CONFIG_PM_WORKAROUND_FREQ_LIMIT_ENABLED +/* Forced CPU_MAX frequency configuration. + * s_cpu_max_freq_forced indicates whether CPU_MAX frequency is forced. + * s_cpu_max_freq_force_config stores the forced frequency configuration. + * Protected by s_switch_lock, same as s_cpu_freq_by_mode. + */ +static bool s_cpu_max_freq_forced = false; +static rtc_cpu_freq_config_t s_cpu_max_freq_force_config; +#endif + /* Whether automatic light sleep is enabled */ static bool s_light_sleep_en = false; @@ -393,6 +406,32 @@ static esp_err_t esp_pm_sleep_configure(const esp_pm_config_t *config) return err; } +/** + * @brief Get frequency configuration for a given mode, considering forced frequency + * + * This function returns a pointer to the frequency configuration for the given mode. + * For PM_MODE_CPU_MAX, if s_cpu_max_freq_forced is true, it returns + * a pointer to s_cpu_max_freq_force_config. Otherwise, it returns a pointer to + * s_cpu_freq_by_mode[mode]. + * + * @param mode Power mode to get configuration for + * @note Must be called with s_switch_lock held + * @note Must be in IRAM when called from ISR context (e.g., do_switch) + * @return rtc_cpu_freq_config_t* Pointer to frequency configuration for the given mode + */ +static inline IRAM_ATTR rtc_cpu_freq_config_t *get_cpu_freq_config_by_mode(pm_mode_t mode) +{ +#if CONFIG_PM_WORKAROUND_FREQ_LIMIT_ENABLED + if (mode == PM_MODE_CPU_MAX && s_cpu_max_freq_forced) { + return &s_cpu_max_freq_force_config; + } else { + return &s_cpu_freq_by_mode[mode]; + } +#else + return &s_cpu_freq_by_mode[mode]; +#endif +} + esp_err_t esp_pm_configure(const void* vconfig) { #ifndef CONFIG_PM_ENABLE @@ -642,18 +681,19 @@ static void IRAM_ATTR do_switch(pm_mode_t new_mode) s_is_switching = true; bool config_changed = s_config_changed; s_config_changed = false; - portENTER_CRITICAL_ISR(&s_cpu_freq_switch_lock[core_id]); - portEXIT_CRITICAL_ISR(&s_switch_lock); - rtc_cpu_freq_config_t new_config = s_cpu_freq_by_mode[new_mode]; + rtc_cpu_freq_config_t new_config = *get_cpu_freq_config_by_mode(new_mode); rtc_cpu_freq_config_t old_config; if (!config_changed) { - old_config = s_cpu_freq_by_mode[s_mode]; + old_config = *get_cpu_freq_config_by_mode(s_mode); } else { rtc_clk_cpu_freq_get_config(&old_config); } + portENTER_CRITICAL_ISR(&s_cpu_freq_switch_lock[core_id]); + portEXIT_CRITICAL_ISR(&s_switch_lock); + if (new_config.freq_mhz != old_config.freq_mhz) { uint32_t old_ticks_per_us = old_config.freq_mhz; uint32_t new_ticks_per_us = new_config.freq_mhz; @@ -866,6 +906,7 @@ void vApplicationSleep( TickType_t xExpectedIdleTime ) void esp_pm_impl_dump_stats(FILE* out) { pm_time_t time_in_mode[PM_MODE_COUNT]; + uint32_t freq_mhz_by_mode[PM_MODE_COUNT]; portENTER_CRITICAL_ISR(&s_switch_lock); memcpy(time_in_mode, s_time_in_mode, sizeof(time_in_mode)); @@ -875,6 +916,10 @@ void esp_pm_impl_dump_stats(FILE* out) bool light_sleep_en = s_light_sleep_en; uint32_t light_sleep_counts = s_light_sleep_counts; uint32_t light_sleep_reject_counts = s_light_sleep_reject_counts; + // Read all frequency configs while holding s_switch_lock + for (int i = 0; i < PM_MODE_COUNT; ++i) { + freq_mhz_by_mode[i] = get_cpu_freq_config_by_mode(i)->freq_mhz; + } portEXIT_CRITICAL_ISR(&s_switch_lock); time_in_mode[cur_mode] += now - last_mode_change_time; @@ -888,7 +933,7 @@ void esp_pm_impl_dump_stats(FILE* out) } fprintf(out, "%-8s %-3"PRIu32"M%-7s %-10lld %-2d%%\n", s_mode_names[i], - s_cpu_freq_by_mode[i].freq_mhz, + freq_mhz_by_mode[i], "", //Empty space to align columns time_in_mode[i], (int) (time_in_mode[i] * 100 / now)); @@ -905,7 +950,7 @@ int esp_pm_impl_get_cpu_freq(pm_mode_t mode) int freq_mhz; if (mode >= PM_MODE_LIGHT_SLEEP && mode < PM_MODE_COUNT) { portENTER_CRITICAL(&s_switch_lock); - freq_mhz = s_cpu_freq_by_mode[mode].freq_mhz; + freq_mhz = get_cpu_freq_config_by_mode(mode)->freq_mhz; portEXIT_CRITICAL(&s_switch_lock); } else { abort(); @@ -1051,3 +1096,42 @@ void esp_pm_impl_waiti(void) esp_cpu_wait_for_intr(); #endif // CONFIG_FREERTOS_USE_TICKLESS_IDLE } + +#if CONFIG_PM_WORKAROUND_FREQ_LIMIT_ENABLED && CONFIG_PM_ENABLE +void esp_pm_impl_cpu_max_freq_force_init(uint32_t limit_freq_mhz) +{ + // Pre-calculate frequency config (done outside critical section) + rtc_cpu_freq_config_t force_config; + bool res = rtc_clk_cpu_freq_mhz_to_config(limit_freq_mhz, &force_config); + assert(res && "Failed to convert forced CPU_MAX frequency to config"); + + // Store the pre-calculated config in critical section + portENTER_CRITICAL(&s_switch_lock); + s_cpu_max_freq_force_config = force_config; + portEXIT_CRITICAL(&s_switch_lock); +} + +void IRAM_ATTR esp_pm_impl_cpu_max_freq_force(void) +{ + portENTER_CRITICAL_SAFE(&s_switch_lock); + // Activate pre-configured forced frequency (no calculation needed at runtime) + s_cpu_max_freq_forced = true; + s_config_changed = true; + portEXIT_CRITICAL_SAFE(&s_switch_lock); + do_switch(PM_MODE_CPU_MAX); + const unsigned wait_clock_stable_us = 10; + esp_rom_delay_us(wait_clock_stable_us); +} + +void IRAM_ATTR esp_pm_impl_cpu_max_freq_unforce(void) +{ + portENTER_CRITICAL_SAFE(&s_switch_lock); + s_cpu_max_freq_forced = false; + s_config_changed = true; + portEXIT_CRITICAL_SAFE(&s_switch_lock); + do_switch(PM_MODE_CPU_MAX); + const unsigned wait_clock_stable_us = 10; + esp_rom_delay_us(wait_clock_stable_us); +} + +#endif // CONFIG_PM_WORKAROUND_FREQ_LIMIT_ENABLED && CONFIG_PM_ENABLE diff --git a/components/esp_system/port/soc/esp32c5/Kconfig.cpu b/components/esp_system/port/soc/esp32c5/Kconfig.cpu index cf42222229..95c3b92cbc 100644 --- a/components/esp_system/port/soc/esp32c5/Kconfig.cpu +++ b/components/esp_system/port/soc/esp32c5/Kconfig.cpu @@ -20,6 +20,13 @@ choice ESP_DEFAULT_CPU_FREQ_MHZ # Please see SoC Errata document for details. depends on !SECURE_FLASH_ENC_ENABLED bool "240 MHz" + help + When 240MHz is selected, esp_flash_write_encrypted() will automatically limit CPU frequency during + execution: + - v1.2 and above chips: limited to 160MHz + - v1.0 and below chips: limited to 80MHz + When 160MHz or lower is selected, no frequency limiting occurs during encrypted writes. Please refer to + the ESP32-C5 SoC Errata document for more details. endchoice config ESP_DEFAULT_CPU_FREQ_MHZ diff --git a/components/esp_system/startup_funcs.c b/components/esp_system/startup_funcs.c index 204108f128..cbfbc2838b 100644 --- a/components/esp_system/startup_funcs.c +++ b/components/esp_system/startup_funcs.c @@ -33,7 +33,7 @@ #include "private/esp_coexist_internal.h" #endif -#if CONFIG_PM_ENABLE +#if CONFIG_PM_ENABLE || CONFIG_PM_WORKAROUND_FREQ_LIMIT_ENABLED #include "esp_pm.h" #include "esp_private/pm_impl.h" #endif @@ -122,6 +122,9 @@ ESP_SYSTEM_INIT_FN(init_flash, CORE, BIT(0), 130) #endif // CONFIG_SPI_FLASH_BROWNOUT_RESET // The log library will call the registered callback function to check if the cache is disabled. esp_log_util_set_cache_enabled_cb(spi_flash_cache_enabled); +#if CONFIG_PM_WORKAROUND_FREQ_LIMIT_ENABLED + esp_pm_flash_freq_limit_init(); +#endif // CONFIG_PM_WORKAROUND_FREQ_LIMIT_ENABLED return ESP_OK; } #endif // !CONFIG_APP_BUILD_TYPE_PURE_RAM_APP diff --git a/components/soc/esp32c5/include/soc/Kconfig.soc_caps.in b/components/soc/esp32c5/include/soc/Kconfig.soc_caps.in index 58e00b8918..a04e289fbf 100644 --- a/components/soc/esp32c5/include/soc/Kconfig.soc_caps.in +++ b/components/soc/esp32c5/include/soc/Kconfig.soc_caps.in @@ -1259,6 +1259,10 @@ config SOC_SPI_MEM_SUPPORT_TSUS_TRES_SEPERATE_CTR bool default y +config SOC_SPI_MEM_PSRAM_FREQ_AXI_CONSTRAINED + bool + default y + config SOC_MEMSPI_TIMING_TUNING_BY_MSPI_DELAY bool default y diff --git a/components/soc/esp32c5/include/soc/soc_caps.h b/components/soc/esp32c5/include/soc/soc_caps.h index f34f326435..33ab9edf30 100644 --- a/components/soc/esp32c5/include/soc/soc_caps.h +++ b/components/soc/esp32c5/include/soc/soc_caps.h @@ -484,6 +484,7 @@ #define SOC_SPI_MEM_SUPPORT_CACHE_32BIT_ADDR_MAP (1) #define SOC_SPI_MEM_SUPPORT_TIMING_TUNING (1) #define SOC_SPI_MEM_SUPPORT_TSUS_TRES_SEPERATE_CTR (1) +#define SOC_SPI_MEM_PSRAM_FREQ_AXI_CONSTRAINED (1) #define SOC_MEMSPI_TIMING_TUNING_BY_MSPI_DELAY (1) #define SOC_MEMSPI_SRC_FREQ_120M_SUPPORTED 1 diff --git a/components/soc/esp32c61/include/soc/Kconfig.soc_caps.in b/components/soc/esp32c61/include/soc/Kconfig.soc_caps.in index 7469604368..11bdae6678 100644 --- a/components/soc/esp32c61/include/soc/Kconfig.soc_caps.in +++ b/components/soc/esp32c61/include/soc/Kconfig.soc_caps.in @@ -859,6 +859,10 @@ config SOC_SPI_MEM_SUPPORT_TSUS_TRES_SEPERATE_CTR bool default y +config SOC_SPI_MEM_PSRAM_FREQ_AXI_CONSTRAINED + bool + default y + config SOC_MEMSPI_TIMING_TUNING_BY_MSPI_DELAY bool default y diff --git a/components/soc/esp32c61/include/soc/soc_caps.h b/components/soc/esp32c61/include/soc/soc_caps.h index 6ca25d6feb..24c7fba935 100644 --- a/components/soc/esp32c61/include/soc/soc_caps.h +++ b/components/soc/esp32c61/include/soc/soc_caps.h @@ -348,6 +348,7 @@ #define SOC_SPI_MEM_SUPPORT_WRAP (1) #define SOC_SPI_MEM_SUPPORT_TIMING_TUNING (1) #define SOC_SPI_MEM_SUPPORT_TSUS_TRES_SEPERATE_CTR (1) +#define SOC_SPI_MEM_PSRAM_FREQ_AXI_CONSTRAINED (1) #define SOC_MEMSPI_TIMING_TUNING_BY_MSPI_DELAY (1) #define SOC_MEMSPI_SRC_FREQ_80M_SUPPORTED 1 diff --git a/components/soc/esp32h4/include/soc/Kconfig.soc_caps.in b/components/soc/esp32h4/include/soc/Kconfig.soc_caps.in index 4a736c4ad4..8e15f46312 100644 --- a/components/soc/esp32h4/include/soc/Kconfig.soc_caps.in +++ b/components/soc/esp32h4/include/soc/Kconfig.soc_caps.in @@ -267,6 +267,10 @@ config SOC_SPI_MEM_SUPPORT_WRAP bool default y +config SOC_SPI_MEM_PSRAM_FREQ_AXI_CONSTRAINED + bool + default y + config SOC_MEMSPI_SRC_FREQ_64M_SUPPORTED bool default y diff --git a/components/soc/esp32h4/include/soc/soc_caps.h b/components/soc/esp32h4/include/soc/soc_caps.h index 9166123814..868180cf23 100644 --- a/components/soc/esp32h4/include/soc/soc_caps.h +++ b/components/soc/esp32h4/include/soc/soc_caps.h @@ -414,6 +414,7 @@ #define SOC_SPI_MEM_SUPPORT_SW_SUSPEND (1) #define SOC_SPI_MEM_SUPPORT_CHECK_SUS (1) #define SOC_SPI_MEM_SUPPORT_WRAP (1) +#define SOC_SPI_MEM_PSRAM_FREQ_AXI_CONSTRAINED (1) #define SOC_MEMSPI_SRC_FREQ_64M_SUPPORTED 1 #define SOC_MEMSPI_SRC_FREQ_32M_SUPPORTED 1 diff --git a/components/soc/esp32p4/include/soc/Kconfig.soc_caps.in b/components/soc/esp32p4/include/soc/Kconfig.soc_caps.in index 2abc997658..c096e00cb6 100644 --- a/components/soc/esp32p4/include/soc/Kconfig.soc_caps.in +++ b/components/soc/esp32p4/include/soc/Kconfig.soc_caps.in @@ -1659,6 +1659,10 @@ config SOC_SPI_MEM_SUPPORT_CACHE_32BIT_ADDR_MAP bool default y +config SOC_SPI_MEM_PSRAM_FREQ_AXI_CONSTRAINED + bool + default y + config SOC_SPI_MEM_SUPPORT_TSUS_TRES_SEPERATE_CTR bool default y diff --git a/components/soc/esp32p4/include/soc/soc_caps.h b/components/soc/esp32p4/include/soc/soc_caps.h index af7dfdaabe..43311df230 100644 --- a/components/soc/esp32p4/include/soc/soc_caps.h +++ b/components/soc/esp32p4/include/soc/soc_caps.h @@ -609,6 +609,7 @@ #define SOC_MEMSPI_TIMING_TUNING_BY_DQS (1) #define SOC_MEMSPI_TIMING_TUNING_BY_FLASH_DELAY (1) #define SOC_SPI_MEM_SUPPORT_CACHE_32BIT_ADDR_MAP (1) +#define SOC_SPI_MEM_PSRAM_FREQ_AXI_CONSTRAINED (1) #define SOC_SPI_MEM_SUPPORT_TSUS_TRES_SEPERATE_CTR (1) #define SOC_SPI_PERIPH_SUPPORT_CONTROL_DUMMY_OUT (1) diff --git a/components/spi_flash/Kconfig b/components/spi_flash/Kconfig index aaab20eaa4..79dc5c50d7 100644 --- a/components/spi_flash/Kconfig +++ b/components/spi_flash/Kconfig @@ -465,4 +465,11 @@ menu "SPI Flash driver" application is not using flash encryption feature and is in need of some additional memory from IRAM region (~1KB) then this config can be disabled. + config SPI_FLASH_FREQ_LIMIT_C5_240MHZ + bool + default y if IDF_TARGET_ESP32C5 && ESP_DEFAULT_CPU_FREQ_MHZ_240 + help + Enable frequency limit workaround for encrypted flash writes on ESP32-C5 at 240MHz default CPU frequency. + This is an internal configuration, automatically set based on target chip and CPU frequency. + endmenu diff --git a/components/spi_flash/esp_flash_api.c b/components/spi_flash/esp_flash_api.c index b856154143..4473d24a3e 100644 --- a/components/spi_flash/esp_flash_api.c +++ b/components/spi_flash/esp_flash_api.c @@ -148,15 +148,6 @@ static const char io_mode_str[][IO_STR_LEN] = { _Static_assert(sizeof(io_mode_str)/IO_STR_LEN == SPI_FLASH_READ_MODE_MAX, "the io_mode_str should be consistent with the esp_flash_io_mode_t defined in spi_flash_types.h"); -esp_err_t esp_flash_read_chip_id(esp_flash_t* chip, uint32_t* flash_id); - -#if !CONFIG_SPI_FLASH_ROM_IMPL || ESP_ROM_HAS_ENCRYPTED_WRITES_USING_LEGACY_DRV -static esp_err_t spiflash_start_default(esp_flash_t *chip); -static esp_err_t spiflash_end_default(esp_flash_t *chip, esp_err_t err); -static esp_err_t check_chip_pointer_default(esp_flash_t **inout_chip); -static esp_err_t flash_end_flush_cache(esp_flash_t* chip, esp_err_t err, bool bus_acquired, uint32_t address, uint32_t length); -#endif // !CONFIG_SPI_FLASH_ROM_IMPL || ESP_ROM_HAS_ENCRYPTED_WRITES_USING_LEGACY_DRV - typedef struct { esp_err_t (*start)(esp_flash_t *chip); esp_err_t (*end)(esp_flash_t *chip, esp_err_t err); @@ -164,19 +155,70 @@ typedef struct { esp_err_t (*flash_end_flush_cache)(esp_flash_t* chip, esp_err_t err, bool bus_acquired, uint32_t address, uint32_t length); } rom_spiflash_api_func_t; + +esp_err_t esp_flash_read_chip_id(esp_flash_t* chip, uint32_t* flash_id); + +#if CONFIG_SPI_FLASH_ROM_IMPL +extern rom_spiflash_api_func_t *esp_flash_api_funcs; +#define rom_spiflash_api_funcs esp_flash_api_funcs +#else +#define rom_spiflash_api_funcs esp_flash_api_funcs_patched_ptr +#endif + #if !CONFIG_SPI_FLASH_ROM_IMPL || ESP_ROM_HAS_ENCRYPTED_WRITES_USING_LEGACY_DRV +// API funcs case 1 & 2 +static esp_err_t spiflash_start_default(esp_flash_t *chip); +static esp_err_t spiflash_end_default(esp_flash_t *chip, esp_err_t err); +static esp_err_t check_chip_pointer_default(esp_flash_t **inout_chip); +static esp_err_t flash_end_flush_cache(esp_flash_t* chip, esp_err_t err, bool bus_acquired, uint32_t address, uint32_t length); + // These functions can be placed in the ROM. For now we use the code in IDF. -DRAM_ATTR static rom_spiflash_api_func_t default_spiflash_rom_api = { +DRAM_ATTR static rom_spiflash_api_func_t esp_flash_api_funcs_patched = { .start = spiflash_start_default, .end = spiflash_end_default, .chip_check = check_chip_pointer_default, .flash_end_flush_cache = flash_end_flush_cache, }; -DRAM_ATTR rom_spiflash_api_func_t *rom_spiflash_api_funcs = &default_spiflash_rom_api; -#else -extern rom_spiflash_api_func_t *esp_flash_api_funcs; -#define rom_spiflash_api_funcs esp_flash_api_funcs +# if !CONFIG_SPI_FLASH_ROM_IMPL +// API funcs case 1: Not using ROM - define our own pointer and all functions +DRAM_ATTR static rom_spiflash_api_func_t *esp_flash_api_funcs_patched_ptr = &esp_flash_api_funcs_patched; + +# else // CONFIG_SPI_FLASH_ROM_IMPL +// API funcs case 2: Using ROM APIs but patch all api_funcs by updating esp_flash_api_funcs from ROM +void esp_flash_rom_api_funcs_init(void) +{ + // Point esp_flash_api_funcs to our default structure + esp_flash_api_funcs = &esp_flash_api_funcs_patched; +} + +# endif // CONFIG_SPI_FLASH_ROM_IMPL + +#else // CONFIG_SPI_FLASH_ROM_IMPL && !ESP_ROM_HAS_ENCRYPTED_WRITES_USING_LEGACY_DRV +// Using ROM implementation + +# if CONFIG_SPI_FLASH_FREQ_LIMIT_C5_240MHZ +// API funcs case 3: Using ROM APIs but patch start function to support flags parameter +static esp_err_t spiflash_start_default(esp_flash_t *chip); +DRAM_ATTR static rom_spiflash_api_func_t esp_flash_api_funcs_patched; + +// Copy ROM structure to RAM and patch start function to support flags +void esp_flash_rom_api_funcs_init(void) +{ + rom_spiflash_api_func_t *rom_ptr = esp_flash_api_funcs; + memcpy(&esp_flash_api_funcs_patched, rom_ptr, sizeof(rom_spiflash_api_func_t)); + esp_flash_api_funcs_patched.start = spiflash_start_default; + esp_flash_api_funcs = &esp_flash_api_funcs_patched; +} + +# else +// API funcs case 4: Using All ROM APIs directly +void esp_flash_rom_api_funcs_init(void) +{ + // Do nothing +} + +# endif // CONFIG_SPI_FLASH_FREQ_LIMIT_C5_240MHZ #endif // !CONFIG_SPI_FLASH_ROM_IMPL || ESP_ROM_HAS_ENCRYPTED_WRITES_USING_LEGACY_DRV /* Static function to notify OS of a new SPI flash operation. @@ -184,11 +226,13 @@ extern rom_spiflash_api_func_t *esp_flash_api_funcs; If returns an error result, caller must abort. If returns ESP_OK, caller must call rom_spiflash_api_funcs->end() before returning. */ -#if !CONFIG_SPI_FLASH_ROM_IMPL || ESP_ROM_HAS_ENCRYPTED_WRITES_USING_LEGACY_DRV -static esp_err_t spiflash_start_default(esp_flash_t *chip) +#if !CONFIG_SPI_FLASH_ROM_IMPL || ESP_ROM_HAS_ENCRYPTED_WRITES_USING_LEGACY_DRV || CONFIG_SPI_FLASH_FREQ_LIMIT_C5_240MHZ +//Avoid constprop issue that place this function into flash. +__attribute__((optimize("O0"))) //IDF-14941 +static esp_err_t spiflash_start_core(esp_flash_t *chip, uint32_t flags) { if (chip->os_func != NULL && chip->os_func->start != NULL) { - esp_err_t err = chip->os_func->start(chip->os_func_data); + esp_err_t err = chip->os_func->start(chip->os_func_data, flags); if (err != ESP_OK) { return err; } @@ -197,6 +241,13 @@ static esp_err_t spiflash_start_default(esp_flash_t *chip) return ESP_OK; } +static esp_err_t spiflash_start_default(esp_flash_t *chip) +{ + return spiflash_start_core(chip, 0); +} +#endif //!CONFIG_SPI_FLASH_ROM_IMPL || ESP_ROM_HAS_ENCRYPTED_WRITES_USING_LEGACY_DRV || CONFIG_SPI_FLASH_FREQ_LIMIT_C5_240MHZ + +#if !CONFIG_SPI_FLASH_ROM_IMPL || ESP_ROM_HAS_ENCRYPTED_WRITES_USING_LEGACY_DRV /* Static function to notify OS that SPI flash operation is complete. */ static esp_err_t spiflash_end_default(esp_flash_t *chip, esp_err_t err) @@ -1248,54 +1299,20 @@ esp_err_t esp_flash_set_io_mode(esp_flash_t* chip, bool qe) } #endif //CONFIG_SPI_FLASH_ROM_IMPL -#if CONFIG_IDF_TARGET_ESP32C5 -// Hardware workaround: ESP32-C5 encrypted flash writes require CPU freq ≤ 160 MHz -#include "soc/rtc.h" - -static int s_esp32c5_saved_cpu_freq_mhz; - -static IRAM_ATTR void esp32c5_freq_limit_acquire(void) +#if !(CONFIG_SPI_FLASH_ROM_IMPL && !ESP_ROM_HAS_ENCRYPTED_WRITES_USING_LEGACY_DRV) || CONFIG_SPI_FLASH_FREQ_LIMIT_C5_240MHZ +// use `esp_flash_write_encrypted` ROM version on chips later than C3, S3 +// For ESP32-C5, use IDF implementation when CPU frequency is 240MHz (calling start() with arg is required) +FORCE_INLINE_ATTR esp_err_t s_encryption_write_lock(esp_flash_t *chip) { - rtc_cpu_freq_config_t old_config, new_config; - rtc_clk_cpu_freq_get_config(&old_config); - - if (old_config.freq_mhz <= 160) { - s_esp32c5_saved_cpu_freq_mhz = 0; // No change needed - return; - } - s_esp32c5_saved_cpu_freq_mhz = old_config.freq_mhz; - - if (rtc_clk_cpu_freq_mhz_to_config(160, &new_config)) { - rtc_clk_cpu_freq_set_config_fast(&new_config); - } -} - -static IRAM_ATTR void esp32c5_freq_limit_release(void) -{ - if (s_esp32c5_saved_cpu_freq_mhz == 0) { - return; // No change was made - } - - rtc_cpu_freq_config_t new_config; - - if (rtc_clk_cpu_freq_mhz_to_config(s_esp32c5_saved_cpu_freq_mhz, &new_config)) { - rtc_clk_cpu_freq_set_config_fast(&new_config); - } - - s_esp32c5_saved_cpu_freq_mhz = 0; -} -#endif // CONFIG_IDF_TARGET_ESP32C5 - -#if !(CONFIG_SPI_FLASH_ROM_IMPL && !ESP_ROM_HAS_ENCRYPTED_WRITES_USING_LEGACY_DRV) -// use `esp_flash_write_encrypted` ROM version on chips later than C3 and S3 -FORCE_INLINE_ATTR esp_err_t s_encryption_write_lock(esp_flash_t *chip) { -#if CONFIG_IDF_TARGET_ESP32C5 - esp32c5_freq_limit_acquire(); -#endif #if CONFIG_IDF_TARGET_ESP32S2 esp_crypto_dma_lock_acquire(); #endif //CONFIG_IDF_TARGET_ESP32S2 +#if CONFIG_SPI_FLASH_FREQ_LIMIT_C5_240MHZ + // Use start_core with LIMIT_CPU_FREQ flag to trigger freq_limit_lock in OS layer + return spiflash_start_core(chip, ESP_FLASH_START_FLAG_LIMIT_CPU_FREQ); +#else return rom_spiflash_api_funcs->start(chip); +#endif } FORCE_INLINE_ATTR esp_err_t s_encryption_write_unlock(esp_flash_t *chip) { @@ -1303,9 +1320,6 @@ FORCE_INLINE_ATTR esp_err_t s_encryption_write_unlock(esp_flash_t *chip) { #if CONFIG_IDF_TARGET_ESP32S2 esp_crypto_dma_lock_release(); #endif //CONFIG_IDF_TARGET_ESP32S2 -#if CONFIG_IDF_TARGET_ESP32C5 - esp32c5_freq_limit_release(); -#endif return err; } @@ -1548,16 +1562,9 @@ esp_err_t IRAM_ATTR esp_flash_write_encrypted(esp_flash_t *chip, uint32_t addres if (length > chip->size - address) { return ESP_ERR_INVALID_ARG; } -#if CONFIG_IDF_TARGET_ESP32C5 - esp32c5_freq_limit_acquire(); -#endif - err = rom_esp_flash_write_encrypted(chip, address, buffer, length); -#if CONFIG_IDF_TARGET_ESP32C5 - esp32c5_freq_limit_release(); -#endif - return err; + return rom_esp_flash_write_encrypted(chip, address, buffer, length); } -#endif // !(CONFIG_SPI_FLASH_ROM_IMPL && !ESP_ROM_HAS_ENCRYPTED_WRITES_USING_LEGACY_DRV) +#endif // !(CONFIG_SPI_FLASH_ROM_IMPL && !ESP_ROM_HAS_ENCRYPTED_WRITES_USING_LEGACY_DRV) || CONFIG_SPI_FLASH_FREQ_LIMIT_C5_240MHZ //init suspend mode cmd, uses internal. esp_err_t esp_flash_suspend_cmd_init(esp_flash_t* chip) diff --git a/components/spi_flash/esp_flash_spi_init.c b/components/spi_flash/esp_flash_spi_init.c index cd86296ace..0e46bcde47 100644 --- a/components/spi_flash/esp_flash_spi_init.c +++ b/components/spi_flash/esp_flash_spi_init.c @@ -142,6 +142,19 @@ esp_flash_t *esp_flash_default_chip = NULL; #endif //!CONFIG_SPI_FLASH_AUTO_SUSPEND #endif // Other target +// Dynamic flash configuration is only needed when: +// 1. Frequency limit workaround is enabled (CONFIG_SPI_FLASH_FREQ_LIMIT_C5_240MHZ) +// 2. Flash frequency requires timing tuning (80MHz or 120MHz, i.e., > 40MHz) +// 3. CPU frequency reduction will trigger MSPI timing tuning to enter low speed mode +// This happens when: SOC_SPI_MEM_PSRAM_FREQ_AXI_CONSTRAINED && CONFIG_SPIRAM && +// (target_cpu_freq < CONFIG_SPIRAM_SPEED) +// Note: The runtime check for CPU freq < PSRAM speed is done in clk_utils.c, +// which calls mspi_timing_change_speed_mode_cache_safe(true) to enter low speed mode. +// For ESP32-C5, if PSRAM is enabled and CPU freq < PSRAM speed, timing tuning will be disabled. +#define C5_NEEDS_DYNAMIC_CONFIG (CONFIG_SPI_FLASH_FREQ_LIMIT_C5_240MHZ && CONFIG_SPIRAM && \ + (CONFIG_ESPTOOLPY_FLASHFREQ_80M || CONFIG_ESPTOOLPY_FLASHFREQ_120M)) + + static IRAM_ATTR NOINLINE_ATTR void cs_initialize(esp_flash_t *chip, const esp_flash_spi_device_config_t *config, bool cs_use_iomux, int cs_id) { //Not using spicommon_cs_initialize since we don't want to put the whole @@ -157,7 +170,7 @@ static IRAM_ATTR NOINLINE_ATTR void cs_initialize(esp_flash_t *chip, const esp_f //To avoid the panic caused by flash data line conflicts during cs line //initialization, disable the cache temporarily - chip->os_func->start(chip->os_func_data); + chip->os_func->start(chip->os_func_data, 0); gpio_hal_input_enable(&gpio_hal, cs_io_num); if (cs_use_iomux) { gpio_hal_func_sel(&gpio_hal, cs_io_num, spics_func); @@ -547,6 +560,16 @@ esp_err_t esp_flash_init_default_chip(void) if (err != ESP_OK) { return err; } +#if C5_NEEDS_DYNAMIC_CONFIG + err = memspi_host_init_c5_dynamic_config(&esp_flash_default_host); + if (err != ESP_OK) { + return err; + } +#endif + +#if CONFIG_SPI_FLASH_ROM_IMPL + esp_flash_rom_api_funcs_init(); +#endif // CONFIG_SPI_FLASH_ROM_IMPL // ROM TODO: account for non-standard default pins in efuse // ROM TODO: to account for chips which are slow to power on, maybe keep probing in a loop here @@ -608,6 +631,7 @@ esp_err_t esp_flash_app_init(void) spi_flash_init_lock(); spi_flash_guard_set(&g_flash_guard_default_ops); + #if CONFIG_SPI_FLASH_ENABLE_COUNTERS esp_flash_reset_counters(); #endif diff --git a/components/spi_flash/include/esp_flash.h b/components/spi_flash/include/esp_flash.h index 31c68611d4..d502052e13 100644 --- a/components/spi_flash/include/esp_flash.h +++ b/components/spi_flash/include/esp_flash.h @@ -37,11 +37,17 @@ typedef struct { * risk. */ typedef struct { + /** + * Flags for start function + */ + /** Limit CPU frequency during flash operations (ESP32-C5 only, 240MHz). + */ + #define ESP_FLASH_START_FLAG_LIMIT_CPU_FREQ BIT(0) /** * Called before commencing any flash operation. Does not need to be * recursive (ie is called at most once for each call to 'end'). */ - esp_err_t (*start)(void *arg); + esp_err_t (*start)(void *arg, uint32_t flags); /** Called after completing any flash operation. */ esp_err_t (*end)(void *arg); diff --git a/components/spi_flash/include/esp_flash_internal.h b/components/spi_flash/include/esp_flash_internal.h index d07b3e2290..b3a87206bf 100644 --- a/components/spi_flash/include/esp_flash_internal.h +++ b/components/spi_flash/include/esp_flash_internal.h @@ -117,6 +117,16 @@ esp_err_t esp_flash_app_disable_os_functions(esp_flash_t* chip); */ esp_err_t esp_flash_set_dangerous_write_protection(esp_flash_t *chip, const bool protect); +#if CONFIG_SPI_FLASH_ROM_IMPL +/** + * @brief Initialize ROM API functions structure + * + * This function initializes the ROM API functions structure, either by pointing + * to a custom structure or by patching the ROM structure in RAM. + */ +void esp_flash_rom_api_funcs_init(void); +#endif // CONFIG_SPI_FLASH_ROM_IMPL + #ifdef __cplusplus } #endif diff --git a/components/spi_flash/include/esp_private/spi_flash_freq_limit_cbs.h b/components/spi_flash/include/esp_private/spi_flash_freq_limit_cbs.h new file mode 100644 index 0000000000..3e95f36965 --- /dev/null +++ b/components/spi_flash/include/esp_private/spi_flash_freq_limit_cbs.h @@ -0,0 +1,49 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include "sdkconfig.h" +#include "esp_flash.h" +#include "esp_attr.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#if CONFIG_SPI_FLASH_FREQ_LIMIT_C5_240MHZ +/** + * @brief Callback to limit CPU frequency for flash encryption writes (ESP32-C5 only, 240MHz) + * + * This function limits CPU frequency to <= 160MHz during encrypted flash writes. + * It should be called before starting an encrypted flash write operation. + * + * @note This is an internal function, not exposed to users. + * @note This function is placed in IRAM (implementation uses IRAM_ATTR). + * @note This function is called from spi_flash_os_func_app.c::spi1_start() after cache + * and scheduler locks are acquired. As a result, there is no concurrency concern + * and no need for internal locking or reference counting. + */ +void esp_flash_freq_limit_cb(void); + +/** + * @brief Callback to unlimit CPU frequency after flash encryption writes (ESP32-C5 only, 240MHz) + * + * This function restores the CPU frequency after an encrypted flash write operation. + * It should be called after completing an encrypted flash write operation. + * + * @note This is an internal function, not exposed to users. + * @note This function is placed in IRAM (implementation uses IRAM_ATTR). + * @note This function is called from spi_flash_os_func_app.c::spi1_end() before cache + * and scheduler locks are released. As a result, there is no concurrency concern + * and no need for internal locking or reference counting. + */ +void esp_flash_freq_unlimit_cb(void); +#endif // CONFIG_SPI_FLASH_FREQ_LIMIT_C5_240MHZ + +#ifdef __cplusplus +} +#endif diff --git a/components/spi_flash/include/memspi_host_driver.h b/components/spi_flash/include/memspi_host_driver.h index 29379e21a2..269ea554c3 100644 --- a/components/spi_flash/include/memspi_host_driver.h +++ b/components/spi_flash/include/memspi_host_driver.h @@ -48,6 +48,17 @@ typedef spi_flash_hal_context_t memspi_host_inst_t; */ esp_err_t memspi_host_init_pointers(memspi_host_inst_t *host, const memspi_host_config_t *cfg); +/** + * Initialize the memory SPI host for ESP32-C5 dynamic configuration. + * + * @param host Pointer to the host structure. + * + * @note This function is available only when CONFIG_SPI_FLASH_FREQ_LIMIT_C5_240MHZ is defined. + * @note This function can only be called once at startup, after memspi_host_init_pointers() is called. + * @return always return ESP_OK + */ +esp_err_t memspi_host_init_c5_dynamic_config(memspi_host_inst_t *host); + /******************************************************************************* * NOTICE * Rest part of this file are part of the HAL layer @@ -161,7 +172,7 @@ int memspi_host_read_data_slicer(spi_flash_host_inst_t *host, uint32_t address, /** * @brief Slicer for write data used in non-encrypted regions. This slicer limit the length to the - * maximum size the host supports, and truncate if the write data lie accross the page boundary + * maximum size the host supports, and truncate if the write data lie across the page boundary * (256 bytes) * * @param address Flash address to write diff --git a/components/spi_flash/linker.lf b/components/spi_flash/linker.lf index 95d2aaa45c..e034bad491 100644 --- a/components/spi_flash/linker.lf +++ b/components/spi_flash/linker.lf @@ -45,12 +45,15 @@ entries: if SPI_FLASH_VERIFY_WRITE = y: esp_flash_api: s_verify_write (noflash) - if SPI_FLASH_ROM_IMPL = n || ESP_ROM_HAS_ENCRYPTED_WRITES_USING_LEGACY_DRV = y: + if SPI_FLASH_ROM_IMPL = n || ESP_ROM_HAS_ENCRYPTED_WRITES_USING_LEGACY_DRV = y || SPI_FLASH_FREQ_LIMIT_C5_240MHZ = y: esp_flash_api: spiflash_start_default (noflash) + esp_flash_api: spiflash_start_core (noflash) + esp_flash_api: esp_flash_write_encrypted (noflash) + + if SPI_FLASH_ROM_IMPL = n || ESP_ROM_HAS_ENCRYPTED_WRITES_USING_LEGACY_DRV = y: esp_flash_api: spiflash_end_default (noflash) esp_flash_api: check_chip_pointer_default (noflash) esp_flash_api: flash_end_flush_cache (noflash) - esp_flash_api: esp_flash_write_encrypted (noflash) if SPI_FLASH_ROM_IMPL = n: esp_flash_api: esp_flash_get_size (noflash) diff --git a/components/spi_flash/memspi_host_driver.c b/components/spi_flash/memspi_host_driver.c index d8210f81fc..8f690ad084 100644 --- a/components/spi_flash/memspi_host_driver.c +++ b/components/spi_flash/memspi_host_driver.c @@ -4,6 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ +#include "sdkconfig.h" #include "soc/soc_caps.h" #include "spi_flash_defs.h" #include "memspi_host_driver.h" @@ -12,12 +13,13 @@ #include "esp_private/cache_utils.h" #include "esp_flash_partitions.h" #include "esp_memory_utils.h" +#include "hal/mspi_ll.h" #define SPI_FLASH_HAL_MAX_WRITE_BYTES 64 #define SPI_FLASH_HAL_MAX_READ_BYTES 64 -DRAM_ATTR static const spi_flash_host_driver_t esp_flash_default_host = ESP_FLASH_DEFAULT_HOST_DRIVER(); +DRAM_ATTR static spi_flash_host_driver_t esp_flash_default_host = ESP_FLASH_DEFAULT_HOST_DRIVER(); #if SOC_MEMSPI_IS_INDEPENDENT extern void spi_flash_hal_gpspi_poll_cmd_done(spi_flash_host_inst_t *host); @@ -71,9 +73,9 @@ esp_err_t memspi_host_init_pointers(memspi_host_inst_t *host, const memspi_host_ } #if SOC_MEMSPI_IS_INDEPENDENT - if (cfg->host_id == SPI1_HOST) + if (cfg->host_id == SPI1_HOST) { host->inst.driver = &esp_flash_default_host; - else { + } else { host->inst.driver = &esp_flash_gpspi_host; } #else @@ -253,3 +255,52 @@ int memspi_host_read_data_slicer(spi_flash_host_inst_t *host, uint32_t address, } #endif // CONFIG_SPI_FLASH_ROM_IMPL + +#if CONFIG_SPI_FLASH_FREQ_LIMIT_C5_240MHZ +// Dynamic flash configuration based on timing tuning state: +// 1. The timing tuning system automatically sets core clock and timing parameters (including din_mode/num, etc.). +// It also generates configurations such as clock configuration and dummy configuration for driver use. +// Without calling this function, the driver gets these configurations during initialization and uses them forever. +// The timing tuning provides clock configuration (frequency division) and dummy count. +// 2. When dynamic configuration is needed, call this function. The function supports two states: +// - Timing tuned state (high speed): Restore the values generated by the timing tuning system to the driver, +// as if there was no frequency reduction. +// - Without timing tuning state (low speed): Set configuration to maximum frequency (40 MHz on C5), dummy=0. +static uint32_t s_high_speed_clock_reg; +static uint32_t s_high_speed_extra_dummy; + +ESP_STATIC_ASSERT(MSPI_TIMING_LL_CORE_CLOCK_MHZ_DEFAULT == 80); +#define LOW_SPEED_DIV (MSPI_TIMING_LL_CORE_CLOCK_MHZ_DEFAULT/40) +static esp_err_t spi_flash_hal_device_config_c5(spi_flash_host_inst_t *host) +{ + spi_flash_hal_context_t* ctx = (spi_flash_hal_context_t*)host; + + // Note: SPI0 and SPI1 have separate TIMING_CALI_REG registers, but SPI0's register reflects the timing tuning state + // since SPI0 and SPI1 share din_mode/din_num registers and timing tuning is configured on SPI0 + uint32_t timing_cali_reg = REG_READ(SPI_MEM_TIMING_CALI_REG(MSPI_TIMING_LL_MSPI_ID_0)); + bool is_low_speed = !(timing_cali_reg & SPI_MEM_TIMING_CALI_M); + + if (is_low_speed) { + // Low speed mode: Set to safe division, dummy=0 + uint32_t low_speed_clock_reg = mspi_timing_ll_calculate_clock_reg(LOW_SPEED_DIV); + ctx->clock_conf.spimem = low_speed_clock_reg; + ctx->extra_dummy = 0; + } else { + // High speed mode: Restore timing tuning values + ctx->clock_conf.spimem = s_high_speed_clock_reg; + ctx->extra_dummy = s_high_speed_extra_dummy; + } + return spi_flash_hal_device_config(host); +} + +esp_err_t memspi_host_init_c5_dynamic_config(memspi_host_inst_t *host) +{ + assert(s_high_speed_clock_reg == 0 && s_high_speed_extra_dummy == 0); + + spi_flash_hal_context_t* ctx = (spi_flash_hal_context_t*)host; + s_high_speed_clock_reg = ctx->clock_conf.spimem; + s_high_speed_extra_dummy = ctx->extra_dummy; + esp_flash_default_host.dev_config = spi_flash_hal_device_config_c5; + return ESP_OK; +} +#endif diff --git a/components/spi_flash/spi_flash_os_func_app.c b/components/spi_flash/spi_flash_os_func_app.c index 0b658ab811..45551914d9 100644 --- a/components/spi_flash/spi_flash_os_func_app.c +++ b/components/spi_flash/spi_flash_os_func_app.c @@ -20,9 +20,12 @@ #include "esp_rom_sys.h" #include "esp_private/spi_flash_os.h" #include "esp_private/cache_utils.h" - #include "esp_private/spi_share_hw_ctrl.h" +// For C5 encrypted write workaround +// Functions are only available when CONFIG_SPI_FLASH_FREQ_LIMIT_C5_240MHZ is true +#include "esp_private/spi_flash_freq_limit_cbs.h" + #define SPI_FLASH_CACHE_NO_DISABLE (CONFIG_SPI_FLASH_AUTO_SUSPEND || (CONFIG_SPIRAM_FETCH_INSTRUCTIONS && CONFIG_SPIRAM_RODATA) || CONFIG_APP_BUILD_TYPE_RAM) static const char TAG[] = "spi_flash"; @@ -53,6 +56,7 @@ typedef struct { bool no_protect; //to decide whether to check protected region (for the main chip) or not. uint32_t acquired_since_us; // Time since last explicit yield() uint32_t released_since_us; // Time since last end() (implicit yield) + uint32_t start_flags; // Flags passed to start() function, used to determine if freq_limit was called } app_func_arg_t; static inline void on_spi_released(app_func_arg_t* ctx); @@ -90,21 +94,27 @@ static IRAM_ATTR esp_err_t release_spi_bus_lock(void *arg) return spi_bus_lock_acquire_end(((app_func_arg_t *)arg)->dev_lock); } -static esp_err_t spi23_start(void *arg){ +static esp_err_t spi23_start(void *arg, uint32_t flags) +{ + (void)flags; esp_err_t ret = acquire_spi_bus_lock(arg); on_spi_acquired((app_func_arg_t*)arg); return ret; } -static esp_err_t spi23_end(void *arg){ +static esp_err_t spi23_end(void *arg) +{ esp_err_t ret = release_spi_bus_lock(arg); on_spi_released((app_func_arg_t*)arg); return ret; } -static IRAM_ATTR esp_err_t spi1_start(void *arg) +static IRAM_ATTR esp_err_t spi1_start(void *arg, uint32_t flags) { esp_err_t ret = ESP_OK; + app_func_arg_t* ctx = (app_func_arg_t*)arg; + ctx->start_flags = flags; + /** * There are three ways for ESP Flash API lock: * 1. spi bus lock, this is used when SPI1 is shared with GPSPI Master Driver @@ -136,13 +146,28 @@ static IRAM_ATTR esp_err_t spi1_start(void *arg) } #endif // CONFIG_SPI_FLASH_DISABLE_SCHEDULER_IN_SUSPEND - on_spi_acquired((app_func_arg_t*)arg); +#if CONFIG_SPI_FLASH_FREQ_LIMIT_C5_240MHZ + if (flags & ESP_FLASH_START_FLAG_LIMIT_CPU_FREQ) { + esp_flash_freq_limit_cb(); + } +#endif + + on_spi_acquired(ctx); return ret; } static IRAM_ATTR esp_err_t spi1_end(void *arg) { esp_err_t ret = ESP_OK; + app_func_arg_t* ctx = (app_func_arg_t*)arg; + + // Call freq_limit_unlock if needed, before releasing the lock +#if CONFIG_SPI_FLASH_FREQ_LIMIT_C5_240MHZ + uint32_t flags = ctx->start_flags; + if (flags & ESP_FLASH_START_FLAG_LIMIT_CPU_FREQ) { + esp_flash_freq_unlimit_cb(); + } +#endif /** * There are three ways for ESP Flash API lock, see `spi1_start` @@ -166,7 +191,7 @@ static IRAM_ATTR esp_err_t spi1_end(void *arg) } #endif // CONFIG_SPI_FLASH_DISABLE_SCHEDULER_IN_SUSPEND - on_spi_released((app_func_arg_t*)arg); + on_spi_released(ctx); return ret; } diff --git a/components/spi_flash/spi_flash_os_func_noos.c b/components/spi_flash/spi_flash_os_func_noos.c index e65efd6c1b..0ba7c006c5 100644 --- a/components/spi_flash/spi_flash_os_func_noos.c +++ b/components/spi_flash/spi_flash_os_func_noos.c @@ -15,7 +15,7 @@ #include "hal/cache_ll.h" #include "soc/soc_caps.h" -static IRAM_ATTR esp_err_t start(void *arg) +static IRAM_ATTR esp_err_t start(void *arg, uint32_t flags) { #if SOC_BRANCH_PREDICTOR_SUPPORTED //branch predictor will start cache request as well diff --git a/components/spi_flash/test_apps/.build-test-rules.yml b/components/spi_flash/test_apps/.build-test-rules.yml index 740a6b9f1d..1981f5fbb0 100644 --- a/components/spi_flash/test_apps/.build-test-rules.yml +++ b/components/spi_flash/test_apps/.build-test-rules.yml @@ -15,6 +15,15 @@ components/spi_flash/test_apps/esp_flash: - esp_driver_spi - esptool_py # Some flash related kconfigs are listed here. +components/spi_flash/test_apps/esp_flash_freq_limit: + enable: + - if: IDF_TARGET == "esp32c5" + depends_components: + - spi_flash + - esp_pm + - esp_driver_gptimer + - esp_hw_support + components/spi_flash/test_apps/esp_flash_stress: disable: - if: IDF_TARGET == "esp32h4" diff --git a/components/spi_flash/test_apps/esp_flash_freq_limit/CMakeLists.txt b/components/spi_flash/test_apps/esp_flash_freq_limit/CMakeLists.txt new file mode 100644 index 0000000000..71d8608b0d --- /dev/null +++ b/components/spi_flash/test_apps/esp_flash_freq_limit/CMakeLists.txt @@ -0,0 +1,17 @@ +# This is the project CMakeLists.txt file for the test subproject +cmake_minimum_required(VERSION 3.22) + +set(EXTRA_COMPONENT_DIRS "$ENV{IDF_PATH}/tools/unit-test-app/components") + +# "Trim" the build. Include the minimal set of components, main, and anything it depends on. We also depend on +# esptool_py as we set CONFIG_ESPTOOLPY_... options. +set(COMPONENTS main esptool_py) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) + +project(test_esp_flash_freq_limit) + +message(STATUS "Checking memspi registers are not read-write by half-word") +include($ENV{IDF_PATH}/tools/ci/check_register_rw_half_word.cmake) +check_register_rw_half_word(SOC_MODULES "spi_mem*" "spi1_mem*" + HAL_MODULES "spimem_flash") diff --git a/components/spi_flash/test_apps/esp_flash_freq_limit/README.md b/components/spi_flash/test_apps/esp_flash_freq_limit/README.md new file mode 100644 index 0000000000..1d5f1c0a57 --- /dev/null +++ b/components/spi_flash/test_apps/esp_flash_freq_limit/README.md @@ -0,0 +1,2 @@ +| Supported Targets | ESP32-C5 | +| ----------------- | -------- | diff --git a/components/spi_flash/test_apps/esp_flash_freq_limit/main/CMakeLists.txt b/components/spi_flash/test_apps/esp_flash_freq_limit/main/CMakeLists.txt new file mode 100644 index 0000000000..801934c79b --- /dev/null +++ b/components/spi_flash/test_apps/esp_flash_freq_limit/main/CMakeLists.txt @@ -0,0 +1,9 @@ +set(srcs "test_app_main.c" + "test_esp_flash_freq_limit.c") + +# In order for the cases defined by `TEST_CASE` to be linked into the final elf, +# the component can be registered as WHOLE_ARCHIVE +idf_component_register(SRCS ${srcs} + PRIV_REQUIRES unity test_utils spi_flash esp_pm bootloader_support freertos esp_timer + esp_driver_gptimer esp_hw_support esp_psram + WHOLE_ARCHIVE) diff --git a/components/spi_flash/test_apps/esp_flash_freq_limit/main/test_app_main.c b/components/spi_flash/test_apps/esp_flash_freq_limit/main/test_app_main.c new file mode 100644 index 0000000000..888d52aeab --- /dev/null +++ b/components/spi_flash/test_apps/esp_flash_freq_limit/main/test_app_main.c @@ -0,0 +1,25 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "unity.h" +#include "unity_test_utils.h" + +#define TEST_MEMORY_LEAK_THRESHOLD (700) + +void setUp(void) +{ + unity_utils_record_free_mem(); +} + +void tearDown(void) +{ + unity_utils_evaluate_leaks_direct(TEST_MEMORY_LEAK_THRESHOLD); +} + +void app_main(void) +{ + unity_run_menu(); +} diff --git a/components/spi_flash/test_apps/esp_flash_freq_limit/main/test_esp_flash_freq_limit.c b/components/spi_flash/test_apps/esp_flash_freq_limit/main/test_esp_flash_freq_limit.c new file mode 100644 index 0000000000..c76fa62508 --- /dev/null +++ b/components/spi_flash/test_apps/esp_flash_freq_limit/main/test_esp_flash_freq_limit.c @@ -0,0 +1,1250 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "sdkconfig.h" + +#if CONFIG_IDF_TARGET_ESP32C5 + +#include +#include +#include +#include +#include "unity.h" +#include "esp_flash.h" +#include "soc/rtc.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_pm.h" +#include "driver/gptimer.h" +#include "esp_private/esp_clk.h" +#include "esp_log.h" +#include "test_utils.h" +#include "esp_partition.h" +#include "spi_flash_mmap.h" // For SPI_FLASH_SEC_SIZE + +#define TAG "test_freq_limit" + +/** + * Note: All tests in this file do NOT enable flash encryption. + * Even without encryption enabled, esp_flash_write_encrypted() will still trigger + * frequency switching (which is what we're testing). + * This test does not verify the correctness of encrypted writes. + */ + +// Use test data partition for flash operations +#define TEST_DATA_SIZE 64 +#define FREQ_LIMIT_MHZ 160 +#define MHZ (1000000) + +static uint8_t test_write_buf[TEST_DATA_SIZE]; +static uint32_t s_test_flash_offset = 0; // Will be initialized from test partition +static uint32_t s_test_flash_offset_area_a = 0; // Area A: for normal write/read test +static uint32_t s_test_flash_offset_area_b = 0; // Area B: for encrypted write test (no verification) + +// Note: No erase needed for these tests. +// The tests focus on frequency behavior during esp_flash_write_encrypted, not data integrity or encryption functionality. +// esp_flash_write_encrypted overwrites existing data. +// Erase operations are blocking and may disable interrupts/task switching, potentially interfering with concurrency test scenarios. + +static uint32_t get_cpu_freq_mhz(void) +{ + rtc_cpu_freq_config_t config; + rtc_clk_cpu_freq_get_config(&config); + return config.freq_mhz; +} + +static void verify_freq_restored(uint32_t original_freq) +{ + uint32_t freq = get_cpu_freq_mhz(); + TEST_ASSERT_EQUAL_UINT32(original_freq, freq); +} + +static esp_flash_t* get_test_flash_chip(void) +{ + // Initialize test flash offset from test data partition if not already initialized + if (s_test_flash_offset == 0) { + const esp_partition_t *test_part = get_test_data_partition(); + TEST_ASSERT_NOT_NULL(test_part); + s_test_flash_offset = test_part->address; + // Area A: start from partition start + s_test_flash_offset_area_a = test_part->address; + // Area B: start from partition start + TEST_DATA_SIZE (aligned to sector) + uint32_t sector_aligned_size = ((TEST_DATA_SIZE + SPI_FLASH_SEC_SIZE - 1) / SPI_FLASH_SEC_SIZE) * SPI_FLASH_SEC_SIZE; + s_test_flash_offset_area_b = test_part->address + sector_aligned_size; + ESP_LOGI(TAG, "Using test partition at offset 0x%x, size 0x%x", + test_part->address, test_part->size); + ESP_LOGI(TAG, "Area A: 0x%x, Area B: 0x%x", s_test_flash_offset_area_a, s_test_flash_offset_area_b); + } + return esp_flash_default_chip; +} + +static void prepare_test_data(void) +{ + for (int i = 0; i < TEST_DATA_SIZE; i++) { + test_write_buf[i] = (uint8_t)(i & 0xFF); + } +} + +#if CONFIG_PM_ENABLE +// Test frequency configurations: dynamically built based on CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ +// Only include frequencies that are <= CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ +#if CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ >= 240 +// Support 240MHz: test default (0), 240MHz, 160MHz, 80MHz +static const uint32_t s_test_max_freqs[] = {0, 240, 160, 80}; +static const uint32_t s_test_min_freqs[] = {0, 80, 80, 80}; +#elif CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ >= 160 +// Support up to 160MHz: test default (0), 160MHz, 80MHz +static const uint32_t s_test_max_freqs[] = {0, 160, 80}; +static const uint32_t s_test_min_freqs[] = {0, 80, 80}; +#elif CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ >= 80 +// Support up to 80MHz: test default (0), 80MHz +static const uint32_t s_test_max_freqs[] = {0, 80}; +static const uint32_t s_test_min_freqs[] = {0, 80}; +#else +// Support lower frequencies: test default (0) only +static const uint32_t s_test_max_freqs[] = {0}; +static const uint32_t s_test_min_freqs[] = {0}; +#endif +#define NUM_TEST_CONFIGS (sizeof(s_test_max_freqs) / sizeof(s_test_max_freqs[0])) + +/** + * Setup PM configuration for testing + * @param max_freq_mhz Maximum CPU frequency in MHz. If 0, configure with CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ. + * @param min_freq_mhz Minimum CPU frequency in MHz. If max_freq_mhz is 0, use XTAL frequency as min. + * @return Expected frequency after setup (max_freq_mhz if configured, CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ if max_freq_mhz is 0) + */ +static uint32_t setup_pm_config(uint32_t max_freq_mhz, uint32_t min_freq_mhz) +{ + uint32_t actual_max_freq = max_freq_mhz; + uint32_t actual_min_freq = min_freq_mhz; + + if (max_freq_mhz == 0) { + // Configure with default values (same as CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ) + actual_max_freq = CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ; + if (min_freq_mhz == 0) { + // Use XTAL frequency as min when not specified + actual_min_freq = esp_clk_xtal_freq() / MHZ; + } + } + + // Configure PM with specific frequencies + esp_pm_config_t pm_config = { + .max_freq_mhz = actual_max_freq, + .min_freq_mhz = actual_min_freq, + .light_sleep_enable = false + }; + TEST_ESP_OK(esp_pm_configure(&pm_config)); + + // Wait a bit for PM to stabilize + vTaskDelay(pdMS_TO_TICKS(100)); + + uint32_t current_freq = get_cpu_freq_mhz(); + ESP_LOGI(TAG, "PM configured: max=%"PRIu32" MHz, min=%"PRIu32" MHz, current=%"PRIu32" MHz", + actual_max_freq, actual_min_freq, current_freq); + + // Verify current frequency matches max_freq_mhz (since no locks are held) + TEST_ASSERT_EQUAL_UINT32(actual_max_freq, current_freq); + return actual_max_freq; +} +#endif // CONFIG_PM_ENABLE + +// Note: We do NOT erase flash before write_encrypted operations in these tests. +// Reasons: +// 1. These tests focus on frequency behavior, not data integrity or encryption functionality. +// 2. We do not perform data verification (no read_encrypted or data comparison). +// 3. esp_flash_write_encrypted will overwrite existing data anyway. +// 4. Erase operations are blocking and may disable interrupts/task switching, which could +// interfere with concurrent testing scenarios (interrupts, task scheduling). +// 5. Skipping erase operations simplifies the tests and avoids potential timing issues. + +/** + * Helper function: Test frequency limit basic behavior with PM configuration + * @param max_freq_mhz Maximum CPU frequency in MHz. If 0, configure with CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ. + * @param min_freq_mhz Minimum CPU frequency in MHz. If max_freq_mhz is 0, use XTAL frequency as min. + */ +static void test_freq_limit_basic_behavior_common(uint32_t max_freq_mhz, uint32_t min_freq_mhz) +{ + esp_flash_t* chip = get_test_flash_chip(); + +#if CONFIG_PM_ENABLE + setup_pm_config(max_freq_mhz, min_freq_mhz); +#else + TEST_ASSERT_EQUAL_UINT32(CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ, get_cpu_freq_mhz()); +#endif + + uint32_t original_freq = get_cpu_freq_mhz(); + ESP_LOGI(TAG, "Original frequency: %"PRIu32" MHz", original_freq); + + prepare_test_data(); + + // Step 1: Erase and write known data to Area A, then verify + uint32_t erase_size = ((TEST_DATA_SIZE + SPI_FLASH_SEC_SIZE - 1) / SPI_FLASH_SEC_SIZE) * SPI_FLASH_SEC_SIZE; + ESP_LOGI(TAG, "Step 1: Erasing Area A at 0x%x, size %"PRIu32" bytes", s_test_flash_offset_area_a, erase_size); + esp_err_t ret = esp_flash_erase_region(chip, s_test_flash_offset_area_a, erase_size); + if (ret != ESP_OK) { + ESP_LOGW(TAG, "Flash erase failed (0x%x), continuing anyway.", ret); + } else { + ESP_LOGI(TAG, "Area A erased successfully"); + } + + ESP_LOGI(TAG, "Writing known data to Area A at 0x%x", s_test_flash_offset_area_a); + ret = esp_flash_write(chip, test_write_buf, s_test_flash_offset_area_a, TEST_DATA_SIZE); + TEST_ESP_OK(ret); + ESP_LOGI(TAG, "Data written to Area A successfully"); + + // Verify written data in Area A + uint8_t read_buf[TEST_DATA_SIZE]; + ESP_LOGI(TAG, "Reading back data from Area A at 0x%x", s_test_flash_offset_area_a); + ret = esp_flash_read(chip, read_buf, s_test_flash_offset_area_a, TEST_DATA_SIZE); + TEST_ESP_OK(ret); + ESP_LOGI(TAG, "Verifying data integrity in Area A"); + ESP_LOG_BUFFER_HEXDUMP(TAG, test_write_buf, TEST_DATA_SIZE, ESP_LOG_INFO); + ESP_LOG_BUFFER_HEXDUMP(TAG, read_buf, TEST_DATA_SIZE, ESP_LOG_INFO); + TEST_ASSERT_EQUAL_HEX8_ARRAY(test_write_buf, read_buf, TEST_DATA_SIZE); + ESP_LOGI(TAG, "Area A data verification passed"); + + // Step 2: Encrypted write to Area B (no verification) + uint32_t freq_before = get_cpu_freq_mhz(); + ESP_LOGI(TAG, "Step 2: Frequency before write_encrypted: %"PRIu32" MHz", freq_before); + ESP_LOGI(TAG, "About to call esp_flash_write_encrypted to Area B, addr=0x%x, len=%d", s_test_flash_offset_area_b, TEST_DATA_SIZE); + // Note: This test does not verify the correctness of encrypted writes + ret = esp_flash_write_encrypted(chip, s_test_flash_offset_area_b, test_write_buf, TEST_DATA_SIZE); + ESP_LOGI(TAG, "esp_flash_write_encrypted returned: %d", ret); + TEST_ESP_OK(ret); + + // Wait for flash operation to complete + vTaskDelay(pdMS_TO_TICKS(10)); + + // Frequency should be restored after the call completes (or unchanged if already <= 160MHz) + uint32_t freq_after = get_cpu_freq_mhz(); + ESP_LOGI(TAG, "Frequency after write_encrypted: %"PRIu32" MHz", freq_after); + + // Verify frequency behavior: + // - If original frequency > 160MHz: should be restored to original + // - If original frequency <= 160MHz: should remain unchanged + if (original_freq > FREQ_LIMIT_MHZ) { + verify_freq_restored(original_freq); + ESP_LOGI(TAG, "Frequency restored to original %"PRIu32" MHz (was > 160MHz)", original_freq); + } else { + TEST_ASSERT_EQUAL_UINT32(original_freq, freq_after); + ESP_LOGI(TAG, "Frequency unchanged at %"PRIu32" MHz (was <= 160MHz)", original_freq); + } + + // Step 3: Read and verify Area A again to confirm read API still works after encrypted write + ESP_LOGI(TAG, "Step 3: Reading Area A again at 0x%x to verify read API still works", s_test_flash_offset_area_a); + ret = esp_flash_read(chip, read_buf, s_test_flash_offset_area_a, TEST_DATA_SIZE); + TEST_ESP_OK(ret); + ESP_LOGI(TAG, "Verifying data integrity in Area A after encrypted write"); + ESP_LOG_BUFFER_HEXDUMP(TAG, test_write_buf, TEST_DATA_SIZE, ESP_LOG_INFO); + ESP_LOG_BUFFER_HEXDUMP(TAG, read_buf, TEST_DATA_SIZE, ESP_LOG_INFO); + TEST_ASSERT_EQUAL_HEX8_ARRAY(test_write_buf, read_buf, TEST_DATA_SIZE); + ESP_LOGI(TAG, "Area A data verification passed after encrypted write - read API is still functional"); +} + +/** + * Test: Frequency limit: basic behavior + * Target: Verify that esp_flash_write_encrypted properly limits CPU frequency to <= 160MHz during execution + * and restores the original frequency after completion, with different PM configurations. + * Expected: + * - If original frequency > 160MHz: frequency should be limited to 160MHz during write, restored to original after + * - If original frequency <= 160MHz: frequency should remain unchanged + * - Written data should be verified by reading it back + */ +TEST_CASE("Frequency limit: basic behavior", "[esp_flash_freq_limit]") +{ +#if CONFIG_PM_ENABLE + for (int i = 0; i < NUM_TEST_CONFIGS; i++) { + ESP_LOGI(TAG, "Testing with PM config: max=%"PRIu32" MHz, min=%"PRIu32" MHz", + s_test_max_freqs[i], s_test_min_freqs[i]); + test_freq_limit_basic_behavior_common(s_test_max_freqs[i], s_test_min_freqs[i]); + } +#else + test_freq_limit_basic_behavior_common(0, 0); +#endif +} + +/** + * Helper function: Test read Flash ID after write_encrypted with PM configuration + * @param max_freq_mhz Maximum CPU frequency in MHz. If 0, configure with CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ. + * @param min_freq_mhz Minimum CPU frequency in MHz. If max_freq_mhz is 0, use XTAL frequency as min. + */ +static void test_read_flash_id_after_write_common(uint32_t max_freq_mhz, uint32_t min_freq_mhz) +{ + esp_flash_t* chip = get_test_flash_chip(); + +#if CONFIG_PM_ENABLE + setup_pm_config(max_freq_mhz, min_freq_mhz); +#else + TEST_ASSERT_EQUAL_UINT32(CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ, get_cpu_freq_mhz()); +#endif + + uint32_t original_freq = get_cpu_freq_mhz(); + ESP_LOGI(TAG, "Original frequency: %"PRIu32" MHz", original_freq); + + // Read Flash ID before write_encrypted + uint32_t flash_id_before = 0; + ESP_LOGI(TAG, "Reading Flash ID before write_encrypted"); + esp_err_t ret = esp_flash_read_id(chip, &flash_id_before); + TEST_ESP_OK(ret); + ESP_LOGI(TAG, "Flash ID before write_encrypted: 0x%08"PRIx32, flash_id_before); + TEST_ASSERT_NOT_EQUAL(0, flash_id_before); + TEST_ASSERT_NOT_EQUAL(0xFFFFFFFF, flash_id_before); + + prepare_test_data(); + + // Erase the test region before writing + // esp_flash_erase_region requires size to be aligned to sector size (4KB) + // Calculate the number of sectors needed (round up) + uint32_t erase_size = ((TEST_DATA_SIZE + SPI_FLASH_SEC_SIZE - 1) / SPI_FLASH_SEC_SIZE) * SPI_FLASH_SEC_SIZE; + ESP_LOGI(TAG, "Erasing flash region at 0x%x, size %d (aligned to %"PRIu32" bytes)", + s_test_flash_offset, TEST_DATA_SIZE, erase_size); + ret = esp_flash_erase_region(chip, s_test_flash_offset, erase_size); + // If erase fails, it might be because the region is already erased or in use + // Try to continue anyway - write_encrypted will overwrite the data + if (ret != ESP_OK) { + ESP_LOGW(TAG, "Flash erase failed (0x%x), continuing anyway", ret); + } else { + ESP_LOGI(TAG, "Flash region erased successfully"); + } + + // Perform write_encrypted operation + uint32_t freq_before = get_cpu_freq_mhz(); + ESP_LOGI(TAG, "Frequency before write_encrypted: %"PRIu32" MHz", freq_before); + + ESP_LOGI(TAG, "Calling esp_flash_write_encrypted, addr=0x%x, len=%d", s_test_flash_offset, TEST_DATA_SIZE); + ret = esp_flash_write_encrypted(chip, s_test_flash_offset, test_write_buf, TEST_DATA_SIZE); + TEST_ESP_OK(ret); + + // Verify frequency is restored + uint32_t freq_after = get_cpu_freq_mhz(); + ESP_LOGI(TAG, "Frequency after write_encrypted: %"PRIu32" MHz", freq_after); + if (original_freq > FREQ_LIMIT_MHZ) { + verify_freq_restored(original_freq); + } else { + TEST_ASSERT_EQUAL_UINT32(original_freq, freq_after); + } + + // Read Flash ID after write_encrypted to verify driver is still functional + // This uses a real read operation that accesses the Flash hardware + uint32_t flash_id_after = 0; + ESP_LOGI(TAG, "Reading Flash ID after write_encrypted"); + ret = esp_flash_read_id(chip, &flash_id_after); + TEST_ESP_OK(ret); + ESP_LOGI(TAG, "Flash ID after write_encrypted: 0x%08"PRIx32, flash_id_after); + + // Verify Flash ID matches (driver should still be functional) + TEST_ASSERT_EQUAL_UINT32(flash_id_before, flash_id_after); + ESP_LOGI(TAG, "Flash ID verification passed - driver is still functional"); +} + +/** + * Helper function: Test read Flash ID only (no write operations) + * @param max_freq_mhz Maximum CPU frequency in MHz. If 0, configure with CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ. + * @param min_freq_mhz Minimum CPU frequency in MHz. If max_freq_mhz is 0, use XTAL frequency as min. + */ +#if CONFIG_PM_ENABLE +static void test_read_flash_id_only_common(uint32_t max_freq_mhz, uint32_t min_freq_mhz) +{ + esp_flash_t* chip = get_test_flash_chip(); + + setup_pm_config(max_freq_mhz, min_freq_mhz); + + uint32_t current_freq = get_cpu_freq_mhz(); + ESP_LOGI(TAG, "Current frequency: %"PRIu32" MHz", current_freq); + + // Read Flash ID multiple times to verify consistency + // Note: At high frequencies (240MHz, 160MHz), Flash timing may not be properly configured, + // causing incorrect Flash ID reads. We need to temporarily lower CPU frequency to 80MHz + // to read Flash ID correctly, then restore original frequency. + uint32_t flash_id = 0; + + // If CPU frequency is high (>= 160MHz), temporarily lower CPU frequency to 80MHz for reading Flash ID + uint32_t original_max_freq = 0; + uint32_t original_min_freq = 0; + bool freq_was_lowered = false; + if (current_freq >= 160) { + ESP_LOGI(TAG, "CPU frequency is %"PRIu32" MHz, temporarily lowering to 80MHz for Flash ID read", current_freq); + + // Save current PM config + esp_pm_config_t pm_config; + esp_pm_get_configuration(&pm_config); + original_max_freq = pm_config.max_freq_mhz; + original_min_freq = pm_config.min_freq_mhz; + + // Temporarily set max and min to 80MHz + esp_pm_config_t temp_pm_config = { + .max_freq_mhz = 80, + .min_freq_mhz = 80, + .light_sleep_enable = false + }; + TEST_ESP_OK(esp_pm_configure(&temp_pm_config)); + vTaskDelay(pdMS_TO_TICKS(100)); // Wait for frequency to stabilize + + uint32_t temp_freq = get_cpu_freq_mhz(); + ESP_LOGI(TAG, "Frequency lowered to %"PRIu32" MHz", temp_freq); + TEST_ASSERT_EQUAL_UINT32(80, temp_freq); + freq_was_lowered = true; + } + + extern esp_flash_t *esp_flash_default_chip; + uint32_t expect_chip_id = esp_flash_default_chip->chip_id; + for (int i = 0; i < 5; i++) { + ESP_LOGI(TAG, "Reading Flash ID (attempt %d)", i + 1); + esp_err_t ret = esp_flash_read_id(chip, &flash_id); + TEST_ESP_OK(ret); + uint32_t read_freq = get_cpu_freq_mhz(); + ESP_LOGI(TAG, "Flash ID at %"PRIu32" MHz: 0x%08"PRIx32, read_freq, flash_id); + TEST_ASSERT_EQUAL_UINT32(expect_chip_id, flash_id); + vTaskDelay(pdMS_TO_TICKS(10)); + } + + // Restore original frequency if we lowered it + if (freq_was_lowered) { + ESP_LOGI(TAG, "Restoring original frequency: max=%"PRIu32" MHz, min=%"PRIu32" MHz", + original_max_freq, original_min_freq); + esp_pm_config_t restore_pm_config = { + .max_freq_mhz = original_max_freq, + .min_freq_mhz = original_min_freq, + .light_sleep_enable = false + }; + TEST_ESP_OK(esp_pm_configure(&restore_pm_config)); + vTaskDelay(pdMS_TO_TICKS(100)); // Wait for frequency to stabilize + + uint32_t restored_freq = get_cpu_freq_mhz(); + ESP_LOGI(TAG, "Frequency restored to %"PRIu32" MHz", restored_freq); + TEST_ASSERT_EQUAL_UINT32(original_max_freq, restored_freq); + } + + ESP_LOGI(TAG, "Final Flash ID: 0x%08"PRIx32, flash_id); + TEST_ASSERT_EQUAL_HEX32(expect_chip_id, flash_id); +} + +/** + * Test: Frequency limit: read Flash ID only + * Target: Verify Flash ID reading at different frequencies + * Expected: + * - Flash ID should be readable at all frequencies + * - Flash ID should be same as the default chip id + */ +TEST_CASE("Frequency limit: read Flash ID only", "[esp_flash_freq_limit]") +{ + for (int i = 0; i < NUM_TEST_CONFIGS; i++) { + ESP_LOGI(TAG, "Testing read Flash ID only with PM config: max=%"PRIu32" MHz, min=%"PRIu32" MHz", + s_test_max_freqs[i], s_test_min_freqs[i]); + test_read_flash_id_only_common(s_test_max_freqs[i], s_test_min_freqs[i]); + } +} +#endif // CONFIG_PM_ENABLE + +/** + * Test: Frequency limit: read Flash ID after write_encrypted + * Target: Verify that the Flash driver remains functional after esp_flash_write_encrypted + * by reading the Flash ID using a real read operation. + * Expected: + * - Flash ID should be readable before and after write_encrypted + * - Flash ID should match before and after (driver state should be consistent) + * - This verifies that the driver is still in a normal state after write_encrypted + */ +TEST_CASE("Frequency limit: read Flash ID after write_encrypted", "[esp_flash_freq_limit]") +{ +#if CONFIG_PM_ENABLE + for (int i = 0; i < NUM_TEST_CONFIGS; i++) { + ESP_LOGI(TAG, "Testing read Flash ID with PM config: max=%"PRIu32" MHz, min=%"PRIu32" MHz", + s_test_max_freqs[i], s_test_min_freqs[i]); + test_read_flash_id_after_write_common(s_test_max_freqs[i], s_test_min_freqs[i]); + } +#else + test_read_flash_id_after_write_common(0, 0); +#endif +} + +/** + * Helper function: Test multiple write_encrypted calls with PM configuration + * @param max_freq_mhz Maximum CPU frequency in MHz. If 0, configure with CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ. + * @param min_freq_mhz Minimum CPU frequency in MHz. If max_freq_mhz is 0, use XTAL frequency as min. + */ +static void test_multiple_write_encrypted_calls_common(uint32_t max_freq_mhz, uint32_t min_freq_mhz) +{ + esp_flash_t* chip = get_test_flash_chip(); + +#if CONFIG_PM_ENABLE + setup_pm_config(max_freq_mhz, min_freq_mhz); +#else + TEST_ASSERT_EQUAL_UINT32(CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ, get_cpu_freq_mhz()); +#endif + + uint32_t original_freq = get_cpu_freq_mhz(); + ESP_LOGI(TAG, "Original frequency: %"PRIu32" MHz", original_freq); + + prepare_test_data(); + // Note: No erase needed - see comment above erase_test_region function + + for (int i = 0; i < 3; i++) { + uint32_t offset = s_test_flash_offset + (i * TEST_DATA_SIZE); + uint32_t freq_before = get_cpu_freq_mhz(); + + esp_err_t ret = esp_flash_write_encrypted(chip, offset, test_write_buf, TEST_DATA_SIZE); + TEST_ESP_OK(ret); + + uint32_t freq_after = get_cpu_freq_mhz(); + verify_freq_restored(original_freq); + + ESP_LOGI(TAG, "Iteration %d: freq_before=%"PRIu32" MHz, freq_after=%"PRIu32" MHz", i, freq_before, freq_after); + } +} + +/** + * Test: Frequency limit: multiple write_encrypted calls + * Target: Verify that frequency limiting works correctly across multiple consecutive write_encrypted calls. + * Expected: Frequency should be limited during each call and restored after each call completes. + */ +TEST_CASE("Frequency limit: multiple write_encrypted calls", "[esp_flash_freq_limit]") +{ +#if CONFIG_PM_ENABLE + for (int i = 0; i < NUM_TEST_CONFIGS; i++) { + ESP_LOGI(TAG, "Testing with PM config: max=%"PRIu32" MHz, min=%"PRIu32" MHz", + s_test_max_freqs[i], s_test_min_freqs[i]); + test_multiple_write_encrypted_calls_common(s_test_max_freqs[i], s_test_min_freqs[i]); + } +#else + test_multiple_write_encrypted_calls_common(0, 0); +#endif +} + +/** + * Test: Frequency limit: zero length write + * Target: Verify that zero-length write_encrypted calls do not trigger frequency limiting (early return). + * Expected: Frequency should remain unchanged when length is 0. + */ +TEST_CASE("Frequency limit: zero length write", "[esp_flash_freq_limit]") +{ + esp_flash_t* chip = get_test_flash_chip(); +#if CONFIG_PM_ENABLE + // Initialize PM config to ensure frequency is at max + setup_pm_config(0, 0); // Use default max frequency +#endif + uint32_t original_freq = get_cpu_freq_mhz(); + + // Verify initial frequency matches CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ + TEST_ASSERT_EQUAL_UINT32(CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ, original_freq); + + ESP_LOGI(TAG, "Original frequency: %"PRIu32" MHz", original_freq); + + esp_err_t ret = esp_flash_write_encrypted(chip, s_test_flash_offset, test_write_buf, 0); + TEST_ESP_OK(ret); + + uint32_t freq_after = get_cpu_freq_mhz(); + + TEST_ASSERT_EQUAL_UINT32(original_freq, freq_after); + ESP_LOGI(TAG, "Zero length write: freq unchanged (%"PRIu32" MHz)", freq_after); +} + +/** + * Test: Frequency limit: misaligned address + * Target: Verify that frequency limiting is not triggered when write fails due to misaligned address. + * Expected: Function should return ESP_ERR_INVALID_ARG and frequency should remain unchanged. + */ +TEST_CASE("Frequency limit: misaligned address", "[esp_flash_freq_limit]") +{ + esp_flash_t* chip = get_test_flash_chip(); +#if CONFIG_PM_ENABLE + // Initialize PM config to ensure frequency is at max + setup_pm_config(0, 0); // Use default max frequency +#endif + uint32_t original_freq = get_cpu_freq_mhz(); + + // Verify initial frequency matches CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ + TEST_ASSERT_EQUAL_UINT32(CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ, original_freq); + + ESP_LOGI(TAG, "Original frequency: %"PRIu32" MHz", original_freq); + + prepare_test_data(); + + esp_err_t ret = esp_flash_write_encrypted(chip, s_test_flash_offset + 1, test_write_buf, TEST_DATA_SIZE); + TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, ret); + + uint32_t freq_after = get_cpu_freq_mhz(); + + TEST_ASSERT_EQUAL_UINT32(original_freq, freq_after); + ESP_LOGI(TAG, "Misaligned address: freq unchanged (%"PRIu32" MHz)", freq_after); +} + +/** + * Test: Frequency limit: misaligned length + * Target: Verify that frequency limiting is not triggered when write fails due to misaligned length. + * Expected: Function should return ESP_ERR_INVALID_SIZE and frequency should remain unchanged. + */ +TEST_CASE("Frequency limit: misaligned length", "[esp_flash_freq_limit]") +{ + esp_flash_t* chip = get_test_flash_chip(); +#if CONFIG_PM_ENABLE + // Initialize PM config to ensure frequency is at max + setup_pm_config(0, 0); // Use default max frequency +#endif + uint32_t original_freq = get_cpu_freq_mhz(); + + // Verify initial frequency matches CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ + TEST_ASSERT_EQUAL_UINT32(CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ, original_freq); + + ESP_LOGI(TAG, "Original frequency: %"PRIu32" MHz", original_freq); + + prepare_test_data(); + + esp_err_t ret = esp_flash_write_encrypted(chip, s_test_flash_offset, test_write_buf, TEST_DATA_SIZE + 1); + TEST_ASSERT_EQUAL(ESP_ERR_INVALID_SIZE, ret); + + uint32_t freq_after = get_cpu_freq_mhz(); + + TEST_ASSERT_EQUAL_UINT32(original_freq, freq_after); + ESP_LOGI(TAG, "Misaligned length: freq unchanged (%"PRIu32" MHz)", freq_after); +} + +/** + * Test: Frequency limit: write failure recovery + * Target: Verify that frequency is properly restored even when write_encrypted fails with an error. + * Expected: Function should return error and frequency should be restored to original value. + */ +TEST_CASE("Frequency limit: write failure recovery", "[esp_flash_freq_limit]") +{ + esp_flash_t* chip = get_test_flash_chip(); +#if CONFIG_PM_ENABLE + // Initialize PM config to ensure frequency is at max + setup_pm_config(0, 0); // Use default max frequency +#endif + uint32_t original_freq = get_cpu_freq_mhz(); + + // Verify initial frequency matches CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ + TEST_ASSERT_EQUAL_UINT32(CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ, original_freq); + + ESP_LOGI(TAG, "Original frequency: %"PRIu32" MHz", original_freq); + + prepare_test_data(); + + esp_err_t ret = esp_flash_write_encrypted(chip, s_test_flash_offset + 1, test_write_buf, TEST_DATA_SIZE); + TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, ret); + + uint32_t freq_after = get_cpu_freq_mhz(); + + TEST_ASSERT_EQUAL_UINT32(original_freq, freq_after); + ESP_LOGI(TAG, "Write failure: freq restored to original (%"PRIu32" MHz)", freq_after); +} + +/** + * Helper function: Test multiple write_encrypted operations with PM configuration + * @param max_freq_mhz Maximum CPU frequency in MHz. If 0, configure with CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ. + * @param min_freq_mhz Minimum CPU frequency in MHz. If max_freq_mhz is 0, use XTAL frequency as min. + */ +static void test_multiple_write_encrypted_operations_common(uint32_t max_freq_mhz, uint32_t min_freq_mhz) +{ + esp_flash_t* chip = get_test_flash_chip(); + +#if CONFIG_PM_ENABLE + setup_pm_config(max_freq_mhz, min_freq_mhz); +#else + TEST_ASSERT_EQUAL_UINT32(CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ, get_cpu_freq_mhz()); +#endif + + uint32_t original_freq = get_cpu_freq_mhz(); + ESP_LOGI(TAG, "Original frequency: %"PRIu32" MHz", original_freq); + + prepare_test_data(); + // Note: No erase needed - see comment above erase_test_region function + + // First write - frequency should be limited during call and restored after + esp_err_t ret = esp_flash_write_encrypted(chip, s_test_flash_offset, test_write_buf, TEST_DATA_SIZE); + TEST_ESP_OK(ret); + verify_freq_restored(original_freq); + + // Second write - frequency should be limited during call and restored after + ret = esp_flash_write_encrypted(chip, s_test_flash_offset + TEST_DATA_SIZE, test_write_buf, TEST_DATA_SIZE); + TEST_ESP_OK(ret); + verify_freq_restored(original_freq); + + ESP_LOGI(TAG, "Multiple operations: freq properly restored after each (%"PRIu32" MHz)", original_freq); +} + +/** + * Test: Frequency limit: multiple write_encrypted operations + * Target: Verify that frequency limiting and restoration work correctly for multiple independent write operations. + * Expected: Frequency should be limited during each write and restored after each write completes. + */ +TEST_CASE("Frequency limit: multiple write_encrypted operations", "[esp_flash_freq_limit]") +{ +#if CONFIG_PM_ENABLE + for (int i = 0; i < NUM_TEST_CONFIGS; i++) { + ESP_LOGI(TAG, "Testing with PM config: max=%"PRIu32" MHz, min=%"PRIu32" MHz", + s_test_max_freqs[i], s_test_min_freqs[i]); + test_multiple_write_encrypted_operations_common(s_test_max_freqs[i], s_test_min_freqs[i]); + } +#else + test_multiple_write_encrypted_operations_common(0, 0); +#endif +} + +// ============================================================================ +// Interrupt concurrency test cases +// ============================================================================ + +#if CONFIG_PM_ENABLE + +/** + * Helper function: Test CPU lock held before encrypt with PM configuration + * @param max_freq_mhz Maximum CPU frequency in MHz. If 0, configure with CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ. + * @param min_freq_mhz Minimum CPU frequency in MHz. If max_freq_mhz is 0, use XTAL frequency as min. + */ +static void test_cpu_lock_held_before_encrypt_common(uint32_t max_freq_mhz, uint32_t min_freq_mhz) +{ + esp_flash_t* chip = get_test_flash_chip(); + +#if CONFIG_PM_ENABLE + uint32_t configured_max_freq = setup_pm_config(max_freq_mhz, min_freq_mhz); +#else + TEST_ASSERT_EQUAL_UINT32(CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ, get_cpu_freq_mhz()); + uint32_t configured_max_freq = CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ; +#endif + + uint32_t original_freq = get_cpu_freq_mhz(); + ESP_LOGI(TAG, "Original frequency: %"PRIu32" MHz", original_freq); + + prepare_test_data(); + // Note: No erase needed - see comment above erase_test_region function + + // Create and acquire CPU lock BEFORE encrypt + esp_pm_lock_handle_t cpu_lock = NULL; + TEST_ESP_OK(esp_pm_lock_create(ESP_PM_CPU_FREQ_MAX, 0, "test_lock", &cpu_lock)); + TEST_ASSERT_NOT_NULL(cpu_lock); + + TEST_ESP_OK(esp_pm_lock_acquire(cpu_lock)); + + // Verify frequency is at max + uint32_t freq_after_lock = get_cpu_freq_mhz(); + ESP_LOGI(TAG, "Frequency after acquiring CPU lock: %"PRIu32" MHz", freq_after_lock); + TEST_ASSERT_EQUAL_UINT32(configured_max_freq, freq_after_lock); + + // Perform encrypt operation - frequency should still be limited to 160MHz + // even though CPU lock is held (encrypt lock has higher priority) + esp_err_t ret = esp_flash_write_encrypted(chip, s_test_flash_offset, test_write_buf, TEST_DATA_SIZE); + TEST_ESP_OK(ret); + + // Verify frequency is restored to max (CPU lock still held) + uint32_t freq_after_encrypt = get_cpu_freq_mhz(); + ESP_LOGI(TAG, "Frequency after encrypt (lock still held): %"PRIu32" MHz", freq_after_encrypt); + TEST_ASSERT_EQUAL_UINT32(freq_after_lock, freq_after_encrypt); + + // Release CPU lock + esp_pm_lock_release(cpu_lock); + + // Verify frequency restored + verify_freq_restored(original_freq); + + // Clean up + esp_pm_lock_delete(cpu_lock); +} + +/** + * Test: Frequency limit: CPU lock held before encrypt + * Target: Verify that frequency limiting works correctly when a PM CPU lock is already held before encrypt execution. + * The encrypt frequency limit lock (PM_MODE_CPU_ENCRYPT_LOCK, limits to <= 160MHz) has higher priority than CPU lock (PM_MODE_CPU_MAX). + * Expected: Frequency should be limited to 160MHz during encrypt (even with CPU lock held), then restored to max + * after encrypt completes (CPU lock still held). + */ +TEST_CASE("Frequency limit: CPU lock held before encrypt", "[esp_flash_freq_limit]") +{ +#if CONFIG_PM_ENABLE + for (int i = 0; i < NUM_TEST_CONFIGS; i++) { + ESP_LOGI(TAG, "Testing with PM config: max=%"PRIu32" MHz, min=%"PRIu32" MHz", + s_test_max_freqs[i], s_test_min_freqs[i]); + test_cpu_lock_held_before_encrypt_common(s_test_max_freqs[i], s_test_min_freqs[i]); + } +#else + test_cpu_lock_held_before_encrypt_common(0, 0); +#endif +} + +#endif // CONFIG_PM_ENABLE + +// ============================================================================ +// Interrupt concurrency test cases +// ============================================================================ + +// Shared state for interrupt tests +static volatile bool s_interrupt_occurred = false; +static gptimer_handle_t s_test_timer = NULL; + +#if CONFIG_PM_ENABLE +// PM lock handles for ISR tests (must be set before interrupt) +static esp_pm_lock_handle_t s_isr_pm_lock_cpu = NULL; +static esp_pm_lock_handle_t s_isr_pm_lock_apb = NULL; +static volatile bool s_isr_lock_acquired = false; +static volatile bool s_isr_lock_released = false; +#endif // CONFIG_PM_ENABLE + +// ISR callback - must be IRAM-safe (declared before use) +static bool IRAM_ATTR timer_isr_callback(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *user_data) +{ + s_interrupt_occurred = true; + return false; +} + +#if CONFIG_PM_ENABLE + +// ISR callback for test 3: acquire and release lock in ISR +static bool IRAM_ATTR timer_isr_callback_acquire_release(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *user_data) +{ + s_interrupt_occurred = true; + + // Acquire lock in ISR + if (s_isr_pm_lock_cpu != NULL) { + esp_pm_lock_acquire(s_isr_pm_lock_cpu); + s_isr_lock_acquired = true; + // Release immediately + esp_pm_lock_release(s_isr_pm_lock_cpu); + s_isr_lock_released = true; + } + if (s_isr_pm_lock_apb != NULL) { + esp_pm_lock_acquire(s_isr_pm_lock_apb); + s_isr_lock_acquired = true; + // Release immediately + esp_pm_lock_release(s_isr_pm_lock_apb); + s_isr_lock_released = true; + } + + return false; +} + +// ISR callback for test 4: acquire lock in ISR, release after write completes +static bool IRAM_ATTR timer_isr_callback_acquire(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *user_data) +{ + s_interrupt_occurred = true; + + // Acquire lock in ISR (will be released after write completes in test code) + if (s_isr_pm_lock_cpu != NULL) { + esp_pm_lock_acquire(s_isr_pm_lock_cpu); + s_isr_lock_acquired = true; + } + if (s_isr_pm_lock_apb != NULL) { + esp_pm_lock_acquire(s_isr_pm_lock_apb); + s_isr_lock_acquired = true; + } + + return false; +} + +// ISR callback for test 5: release lock that was acquired before encrypt +static bool IRAM_ATTR timer_isr_callback_release(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *user_data) +{ + s_interrupt_occurred = true; + + // Release lock in ISR (was acquired before encrypt) + if (s_isr_pm_lock_cpu != NULL) { + esp_pm_lock_release(s_isr_pm_lock_cpu); + s_isr_lock_released = true; + } + if (s_isr_pm_lock_apb != NULL) { + esp_pm_lock_release(s_isr_pm_lock_apb); + s_isr_lock_released = true; + } + + return false; +} + +#endif // CONFIG_PM_ENABLE + +// Helper function to setup GPTimer for interrupt during encrypt +static void setup_interrupt_timer(uint32_t delay_us, gptimer_alarm_cb_t callback) +{ + // Clean up any existing timer first to avoid resource leaks + if (s_test_timer != NULL) { + gptimer_stop(s_test_timer); + gptimer_disable(s_test_timer); + gptimer_del_timer(s_test_timer); + s_test_timer = NULL; + } + + gptimer_config_t timer_config = { + .clk_src = GPTIMER_CLK_SRC_DEFAULT, + .direction = GPTIMER_COUNT_UP, + .resolution_hz = 1000000, // 1MHz, 1 tick = 1us + }; + TEST_ESP_OK(gptimer_new_timer(&timer_config, &s_test_timer)); + + gptimer_event_callbacks_t cbs = { + .on_alarm = callback, + }; + TEST_ESP_OK(gptimer_register_event_callbacks(s_test_timer, &cbs, NULL)); + TEST_ESP_OK(gptimer_enable(s_test_timer)); + + gptimer_alarm_config_t alarm_config = { + .reload_count = 0, + .alarm_count = delay_us, + .flags.auto_reload_on_alarm = false, + }; + TEST_ESP_OK(gptimer_set_alarm_action(s_test_timer, &alarm_config)); + + s_interrupt_occurred = false; +#if CONFIG_PM_ENABLE + s_isr_lock_acquired = false; + s_isr_lock_released = false; +#endif // CONFIG_PM_ENABLE + TEST_ESP_OK(gptimer_start(s_test_timer)); +} + +static void cleanup_interrupt_timer(void) +{ + if (s_test_timer != NULL) { + gptimer_stop(s_test_timer); + gptimer_disable(s_test_timer); + gptimer_del_timer(s_test_timer); + s_test_timer = NULL; + } +} + + +/** + * Helper function: Test interrupt during encrypt with PM configuration + * Common test logic for both PM enabled and disabled scenarios + * @param max_freq_mhz Maximum CPU frequency in MHz. If 0, configure with CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ. + * @param min_freq_mhz Minimum CPU frequency in MHz. If max_freq_mhz is 0, use XTAL frequency as min. + */ +static void test_interrupt_during_encrypt_common(uint32_t max_freq_mhz, uint32_t min_freq_mhz) +{ + esp_flash_t* chip = get_test_flash_chip(); + +#if CONFIG_PM_ENABLE + setup_pm_config(max_freq_mhz, min_freq_mhz); +#else + TEST_ASSERT_EQUAL_UINT32(CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ, get_cpu_freq_mhz()); +#endif + + uint32_t original_freq = get_cpu_freq_mhz(); + prepare_test_data(); + + // Setup interrupt to trigger during encrypt (ensure it happens before write completes) + setup_interrupt_timer(50, timer_isr_callback); // 50us delay - should trigger during write + + // Perform encrypt operation - interrupt should occur during this call + esp_err_t ret = esp_flash_write_encrypted(chip, s_test_flash_offset, test_write_buf, TEST_DATA_SIZE); + TEST_ESP_OK(ret); + + // Wait for interrupt to be processed + vTaskDelay(pdMS_TO_TICKS(10)); + + // Verify interrupt occurred + TEST_ASSERT_TRUE(s_interrupt_occurred); + + // Verify frequency was restored + verify_freq_restored(original_freq); + + cleanup_interrupt_timer(); +} + +/** + * Test: Frequency limit: interrupt during encrypt (PM disabled) + * Target: Verify that frequency limiting works correctly when an interrupt occurs during encrypt execution with PM disabled. + * Expected: Interrupt should occur during write, frequency should be limited during write, and restored after write completes. + */ +TEST_CASE("Frequency limit: interrupt during encrypt (PM disabled)", "[esp_flash_freq_limit]") +{ + test_interrupt_during_encrypt_common(0, 0); +} + +#if CONFIG_PM_ENABLE +/** + * Test: Frequency limit: interrupt during encrypt (PM enabled) + * Target: Verify that frequency limiting works correctly when an interrupt occurs during encrypt execution with PM enabled. + * Expected: Interrupt should occur during write, frequency should be limited during write, and restored after write completes. + */ +TEST_CASE("Frequency limit: interrupt during encrypt (PM enabled)", "[esp_flash_freq_limit]") +{ + for (int i = 0; i < NUM_TEST_CONFIGS; i++) { + ESP_LOGI(TAG, "Testing with PM config: max=%"PRIu32" MHz, min=%"PRIu32" MHz", + s_test_max_freqs[i], s_test_min_freqs[i]); + test_interrupt_during_encrypt_common(s_test_max_freqs[i], s_test_min_freqs[i]); + } +} + +/** + * Helper function: Test interrupt with PM lock acquire/release with PM configuration + * @param lock_type Lock type (CPU or APB) + * @param lock_name Lock name for logging + * @param max_freq_mhz Maximum CPU frequency in MHz. If 0, configure with CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ. + * @param min_freq_mhz Minimum CPU frequency in MHz. If max_freq_mhz is 0, use XTAL frequency as min. + */ +static void test_interrupt_with_pm_lock_acquire_release_common(esp_pm_lock_type_t lock_type, const char* lock_name, uint32_t max_freq_mhz, uint32_t min_freq_mhz) +{ + esp_flash_t* chip = get_test_flash_chip(); + +#if CONFIG_PM_ENABLE + setup_pm_config(max_freq_mhz, min_freq_mhz); +#else + TEST_ASSERT_EQUAL_UINT32(CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ, get_cpu_freq_mhz()); +#endif + + uint32_t original_freq = get_cpu_freq_mhz(); + prepare_test_data(); + + // Create PM lock + esp_pm_lock_handle_t pm_lock = NULL; + TEST_ESP_OK(esp_pm_lock_create(lock_type, 0, "isr_test", &pm_lock)); + TEST_ASSERT_NOT_NULL(pm_lock); + + // Set lock handle for ISR callback + if (lock_type == ESP_PM_CPU_FREQ_MAX) { + s_isr_pm_lock_cpu = pm_lock; + s_isr_pm_lock_apb = NULL; + } else { + s_isr_pm_lock_cpu = NULL; + s_isr_pm_lock_apb = pm_lock; + } + + // Setup interrupt to trigger during encrypt (50us delay) + // ISR will acquire and release lock + setup_interrupt_timer(50, timer_isr_callback_acquire_release); + + // Perform encrypt operation - interrupt will trigger PM lock acquire/release in ISR + esp_err_t ret = esp_flash_write_encrypted(chip, s_test_flash_offset, test_write_buf, TEST_DATA_SIZE); + TEST_ESP_OK(ret); + + // Wait for interrupt to be processed + vTaskDelay(pdMS_TO_TICKS(10)); + + // Verify interrupt occurred and lock operations happened + TEST_ASSERT_TRUE(s_interrupt_occurred); + TEST_ASSERT_TRUE(s_isr_lock_acquired); + TEST_ASSERT_TRUE(s_isr_lock_released); + + // Verify frequency was restored (pm_impl.c assertions will verify frequency during encrypt) + verify_freq_restored(original_freq); + + // Cleanup + s_isr_pm_lock_cpu = NULL; + s_isr_pm_lock_apb = NULL; + esp_pm_lock_delete(pm_lock); + cleanup_interrupt_timer(); +} + +/** + * Test: Frequency limit: interrupt with CPU lock acquire/release + * Target: Same as test_interrupt_with_pm_lock_acquire_release, but specifically for CPU lock (ESP_PM_CPU_FREQ_MAX). + */ +TEST_CASE("Frequency limit: interrupt with CPU lock acquire/release", "[esp_flash_freq_limit]") +{ + for (int i = 0; i < NUM_TEST_CONFIGS; i++) { + ESP_LOGI(TAG, "Testing CPU lock with PM config: max=%"PRIu32" MHz, min=%"PRIu32" MHz", + s_test_max_freqs[i], s_test_min_freqs[i]); + test_interrupt_with_pm_lock_acquire_release_common(ESP_PM_CPU_FREQ_MAX, "CPU", s_test_max_freqs[i], s_test_min_freqs[i]); + } +} + +/** + * Test: Frequency limit: interrupt with APB lock acquire/release + * Target: Same as test_interrupt_with_pm_lock_acquire_release, but specifically for APB lock (ESP_PM_APB_FREQ_MAX). + */ +TEST_CASE("Frequency limit: interrupt with APB lock acquire/release", "[esp_flash_freq_limit]") +{ + for (int i = 0; i < NUM_TEST_CONFIGS; i++) { + ESP_LOGI(TAG, "Testing APB lock with PM config: max=%"PRIu32" MHz, min=%"PRIu32" MHz", + s_test_max_freqs[i], s_test_min_freqs[i]); + test_interrupt_with_pm_lock_acquire_release_common(ESP_PM_APB_FREQ_MAX, "APB", s_test_max_freqs[i], s_test_min_freqs[i]); + } +} + +/** + * Helper function: Test interrupt with PM lock held after ISR with PM configuration + * @param lock_type Lock type (CPU or APB) + * @param lock_name Lock name for logging + * @param max_freq_mhz Maximum CPU frequency in MHz. If 0, configure with CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ. + * @param min_freq_mhz Minimum CPU frequency in MHz. If max_freq_mhz is 0, use XTAL frequency as min. + */ +static void test_interrupt_with_pm_lock_held_common(esp_pm_lock_type_t lock_type, const char* lock_name, uint32_t max_freq_mhz, uint32_t min_freq_mhz) +{ + esp_flash_t* chip = get_test_flash_chip(); + +#if CONFIG_PM_ENABLE + uint32_t configured_max_freq = setup_pm_config(max_freq_mhz, min_freq_mhz); +#else + TEST_ASSERT_EQUAL_UINT32(CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ, get_cpu_freq_mhz()); + uint32_t configured_max_freq = CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ; +#endif + + uint32_t original_freq = get_cpu_freq_mhz(); + prepare_test_data(); + + // Create PM lock + esp_pm_lock_handle_t pm_lock = NULL; + TEST_ESP_OK(esp_pm_lock_create(lock_type, 0, "hold_test", &pm_lock)); + TEST_ASSERT_NOT_NULL(pm_lock); + + // Set lock handle for ISR callback + if (lock_type == ESP_PM_CPU_FREQ_MAX) { + s_isr_pm_lock_cpu = pm_lock; + s_isr_pm_lock_apb = NULL; + } else { + s_isr_pm_lock_cpu = NULL; + s_isr_pm_lock_apb = pm_lock; + } + + // Setup interrupt to trigger during encrypt + // ISR will acquire lock, we'll release it after write completes + setup_interrupt_timer(50, timer_isr_callback_acquire); + + // Perform encrypt operation - interrupt will trigger PM lock acquire in ISR + esp_err_t ret = esp_flash_write_encrypted(chip, s_test_flash_offset, test_write_buf, TEST_DATA_SIZE); + TEST_ESP_OK(ret); + + // Wait for interrupt to be processed + vTaskDelay(pdMS_TO_TICKS(10)); + + // Verify interrupt occurred and lock was acquired + TEST_ASSERT_TRUE(s_interrupt_occurred); + TEST_ASSERT_TRUE(s_isr_lock_acquired); + + // Check frequency after write completes but before releasing lock + // Note: PM_MODE_CPU_ENCRYPT_LOCK lock (frequency limit) is released when write_encrypted returns, + // so frequency should be restored to the mode corresponding to the lock held in ISR + // If ISR acquired ESP_PM_CPU_FREQ_MAX lock, frequency should be configured max freq (PM_MODE_CPU_MAX) + // If ISR acquired ESP_PM_APB_FREQ_MAX lock, frequency depends on PM configuration + uint32_t freq_after_write = get_cpu_freq_mhz(); + uint32_t expected_freq; + if (lock_type == ESP_PM_CPU_FREQ_MAX) { + // CPU lock held -> PM_MODE_CPU_MAX -> configured max frequency + expected_freq = configured_max_freq; + } else { + // APB lock held -> depends on PM config, but typically >= 80MHz + // For simplicity, just verify frequency is restored (not limited to 160MHz) + expected_freq = configured_max_freq; // APB lock held, should be restored to configured max freq + } + ESP_LOGI(TAG, "Frequency after write (lock still held): %"PRIu32" MHz (expected: %"PRIu32" MHz)", + freq_after_write, expected_freq); + TEST_ASSERT_EQUAL_UINT32(expected_freq, freq_after_write); + + // Release lock after write completes (as per requirement) + esp_pm_lock_release(pm_lock); + + // Verify frequency was restored after lock release + verify_freq_restored(original_freq); + + // Cleanup + s_isr_pm_lock_cpu = NULL; + s_isr_pm_lock_apb = NULL; + esp_pm_lock_delete(pm_lock); + cleanup_interrupt_timer(); +} + +/** + * Test: Frequency limit: interrupt with CPU lock held after ISR + * Target: Same as test_interrupt_with_pm_lock_held, but specifically for CPU lock (ESP_PM_CPU_FREQ_MAX). + */ +TEST_CASE("Frequency limit: interrupt with CPU lock held after ISR", "[esp_flash_freq_limit]") +{ + for (int i = 0; i < NUM_TEST_CONFIGS; i++) { + ESP_LOGI(TAG, "Testing CPU lock with PM config: max=%"PRIu32" MHz, min=%"PRIu32" MHz", + s_test_max_freqs[i], s_test_min_freqs[i]); + test_interrupt_with_pm_lock_held_common(ESP_PM_CPU_FREQ_MAX, "CPU", s_test_max_freqs[i], s_test_min_freqs[i]); + } +} + +/** + * Test: Frequency limit: interrupt with APB lock held after ISR + * Target: Same as test_interrupt_with_pm_lock_held, but specifically for APB lock (ESP_PM_APB_FREQ_MAX). + */ +TEST_CASE("Frequency limit: interrupt with APB lock held after ISR", "[esp_flash_freq_limit]") +{ + for (int i = 0; i < NUM_TEST_CONFIGS; i++) { + ESP_LOGI(TAG, "Testing APB lock with PM config: max=%"PRIu32" MHz, min=%"PRIu32" MHz", + s_test_max_freqs[i], s_test_min_freqs[i]); + test_interrupt_with_pm_lock_held_common(ESP_PM_APB_FREQ_MAX, "APB", s_test_max_freqs[i], s_test_min_freqs[i]); + } +} + +/** + * Helper function: Test PM lock released in ISR with PM configuration + * @param lock_type Lock type (CPU or APB) + * @param lock_name Lock name for logging + * @param max_freq_mhz Maximum CPU frequency in MHz. If 0, configure with CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ. + * @param min_freq_mhz Minimum CPU frequency in MHz. If max_freq_mhz is 0, use XTAL frequency as min. + */ +static void test_lock_released_in_isr_common(esp_pm_lock_type_t lock_type, const char* lock_name, uint32_t max_freq_mhz, uint32_t min_freq_mhz) +{ + esp_flash_t* chip = get_test_flash_chip(); + +#if CONFIG_PM_ENABLE + setup_pm_config(max_freq_mhz, min_freq_mhz); +#else + TEST_ASSERT_EQUAL_UINT32(CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ, get_cpu_freq_mhz()); +#endif + + uint32_t original_freq = get_cpu_freq_mhz(); + prepare_test_data(); + + // Create and acquire PM lock BEFORE encrypt + esp_pm_lock_handle_t pm_lock = NULL; + TEST_ESP_OK(esp_pm_lock_create(lock_type, 0, "release_test", &pm_lock)); + TEST_ASSERT_NOT_NULL(pm_lock); + TEST_ESP_OK(esp_pm_lock_acquire(pm_lock)); + + // Set lock handle for ISR callback + if (lock_type == ESP_PM_CPU_FREQ_MAX) { + s_isr_pm_lock_cpu = pm_lock; + s_isr_pm_lock_apb = NULL; + } else { + s_isr_pm_lock_cpu = NULL; + s_isr_pm_lock_apb = pm_lock; + } + + // Setup interrupt to trigger during encrypt + // ISR will release the lock that was acquired before encrypt + setup_interrupt_timer(50, timer_isr_callback_release); + + // Perform encrypt operation - lock is already held, ISR will release it + esp_err_t ret = esp_flash_write_encrypted(chip, s_test_flash_offset, test_write_buf, TEST_DATA_SIZE); + TEST_ESP_OK(ret); + + // Wait for interrupt to be processed + vTaskDelay(pdMS_TO_TICKS(10)); + + // Verify interrupt occurred and lock was released + TEST_ASSERT_TRUE(s_interrupt_occurred); + TEST_ASSERT_TRUE(s_isr_lock_released); + + // Check frequency after write completes and after ISR released lock + // Frequency should be restored because lock was released in ISR + uint32_t freq_after_write = get_cpu_freq_mhz(); + ESP_LOGI(TAG, "Frequency after write (lock released in ISR): %"PRIu32" MHz (original: %"PRIu32" MHz)", + freq_after_write, original_freq); + TEST_ASSERT_EQUAL_UINT32(original_freq, freq_after_write); + + // Verify frequency was restored (pm_impl.c assertions will verify frequency during encrypt) + verify_freq_restored(original_freq); + + // Cleanup + s_isr_pm_lock_cpu = NULL; + s_isr_pm_lock_apb = NULL; + esp_pm_lock_delete(pm_lock); + cleanup_interrupt_timer(); +} + +TEST_CASE("Frequency limit: CPU lock released in ISR", "[esp_flash_freq_limit]") +{ + for (int i = 0; i < NUM_TEST_CONFIGS; i++) { + ESP_LOGI(TAG, "Testing CPU lock with PM config: max=%"PRIu32" MHz, min=%"PRIu32" MHz", + s_test_max_freqs[i], s_test_min_freqs[i]); + test_lock_released_in_isr_common(ESP_PM_CPU_FREQ_MAX, "CPU", s_test_max_freqs[i], s_test_min_freqs[i]); + } +} + +TEST_CASE("Frequency limit: APB lock released in ISR", "[esp_flash_freq_limit]") +{ + for (int i = 0; i < NUM_TEST_CONFIGS; i++) { + ESP_LOGI(TAG, "Testing APB lock with PM config: max=%"PRIu32" MHz, min=%"PRIu32" MHz", + s_test_max_freqs[i], s_test_min_freqs[i]); + test_lock_released_in_isr_common(ESP_PM_APB_FREQ_MAX, "APB", s_test_max_freqs[i], s_test_min_freqs[i]); + } +} +#endif // CONFIG_PM_ENABLE + + +#endif // CONFIG_IDF_TARGET_ESP32C5 diff --git a/components/spi_flash/test_apps/esp_flash_freq_limit/partitions.csv b/components/spi_flash/test_apps/esp_flash_freq_limit/partitions.csv new file mode 100644 index 0000000000..9903f621fb --- /dev/null +++ b/components/spi_flash/test_apps/esp_flash_freq_limit/partitions.csv @@ -0,0 +1,5 @@ +# Name, Type, SubType, Offset, Size, Flags +# Note: if you have increased the bootloader size, make sure to update the offsets to avoid overlap +nvs, data, nvs, 0x9000, 0x6000, +factory, 0, 0, 0x10000, 1M +flash_test, data, fat, , 700K diff --git a/components/spi_flash/test_apps/esp_flash_freq_limit/pytest_esp_flash_freq_limit.py b/components/spi_flash/test_apps/esp_flash_freq_limit/pytest_esp_flash_freq_limit.py new file mode 100644 index 0000000000..279ad61241 --- /dev/null +++ b/components/spi_flash/test_apps/esp_flash_freq_limit/pytest_esp_flash_freq_limit.py @@ -0,0 +1,23 @@ +# SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: Apache-2.0 +import pytest +from pytest_embedded import Dut +from pytest_embedded_idf.utils import idf_parametrize + + +@pytest.mark.generic +@pytest.mark.parametrize( + 'config', + [ + 'pm_disabled_240mhz', + 'pm_disabled_160mhz', + 'pm_enabled_240mhz', + 'pm_enabled_160mhz', + 'pm_disabled_240mhz_xip_psram', + 'pm_enabled_240mhz_xip_psram', + ], + indirect=True, +) +@idf_parametrize('target', ['esp32c5'], indirect=['target']) +def test_esp_flash_freq_limit(dut: Dut) -> None: + dut.run_all_single_board_cases(group='esp_flash_freq_limit', timeout=10) diff --git a/components/spi_flash/test_apps/esp_flash_freq_limit/sdkconfig.ci.pm_disabled_160mhz b/components/spi_flash/test_apps/esp_flash_freq_limit/sdkconfig.ci.pm_disabled_160mhz new file mode 100644 index 0000000000..0314f5699b --- /dev/null +++ b/components/spi_flash/test_apps/esp_flash_freq_limit/sdkconfig.ci.pm_disabled_160mhz @@ -0,0 +1,2 @@ +CONFIG_PM_ENABLE=n +CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_160=y diff --git a/components/spi_flash/test_apps/esp_flash_freq_limit/sdkconfig.ci.pm_disabled_240mhz b/components/spi_flash/test_apps/esp_flash_freq_limit/sdkconfig.ci.pm_disabled_240mhz new file mode 100644 index 0000000000..478b872a3a --- /dev/null +++ b/components/spi_flash/test_apps/esp_flash_freq_limit/sdkconfig.ci.pm_disabled_240mhz @@ -0,0 +1,2 @@ +CONFIG_PM_ENABLE=n +CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_240=y diff --git a/components/spi_flash/test_apps/esp_flash_freq_limit/sdkconfig.ci.pm_disabled_240mhz_xip_psram b/components/spi_flash/test_apps/esp_flash_freq_limit/sdkconfig.ci.pm_disabled_240mhz_xip_psram new file mode 100644 index 0000000000..f52050a58f --- /dev/null +++ b/components/spi_flash/test_apps/esp_flash_freq_limit/sdkconfig.ci.pm_disabled_240mhz_xip_psram @@ -0,0 +1,8 @@ +CONFIG_PM_ENABLE=n +CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_240=y +CONFIG_ESPTOOLPY_FLASHFREQ_120M=y +CONFIG_SPIRAM=y +CONFIG_SPIRAM_BOOT_HW_INIT=y +CONFIG_SPIRAM_BOOT_INIT=y +CONFIG_SPIRAM_XIP_FROM_PSRAM=y +CONFIG_SPIRAM_SPEED_120M=y diff --git a/components/spi_flash/test_apps/esp_flash_freq_limit/sdkconfig.ci.pm_enabled_160mhz b/components/spi_flash/test_apps/esp_flash_freq_limit/sdkconfig.ci.pm_enabled_160mhz new file mode 100644 index 0000000000..48f2036344 --- /dev/null +++ b/components/spi_flash/test_apps/esp_flash_freq_limit/sdkconfig.ci.pm_enabled_160mhz @@ -0,0 +1,6 @@ +CONFIG_FREERTOS_USE_TICKLESS_IDLE=y +CONFIG_COMPILER_OPTIMIZATION_SIZE=y +CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_SIZE=y +CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_SILENT=y +CONFIG_PM_ENABLE=y +CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_160=y diff --git a/components/spi_flash/test_apps/esp_flash_freq_limit/sdkconfig.ci.pm_enabled_240mhz b/components/spi_flash/test_apps/esp_flash_freq_limit/sdkconfig.ci.pm_enabled_240mhz new file mode 100644 index 0000000000..e1433523a3 --- /dev/null +++ b/components/spi_flash/test_apps/esp_flash_freq_limit/sdkconfig.ci.pm_enabled_240mhz @@ -0,0 +1,6 @@ +CONFIG_FREERTOS_USE_TICKLESS_IDLE=y +CONFIG_COMPILER_OPTIMIZATION_SIZE=y +CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_SIZE=y +CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_SILENT=y +CONFIG_PM_ENABLE=y +CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_240=y diff --git a/components/spi_flash/test_apps/esp_flash_freq_limit/sdkconfig.ci.pm_enabled_240mhz_xip_psram b/components/spi_flash/test_apps/esp_flash_freq_limit/sdkconfig.ci.pm_enabled_240mhz_xip_psram new file mode 100644 index 0000000000..5e105cba02 --- /dev/null +++ b/components/spi_flash/test_apps/esp_flash_freq_limit/sdkconfig.ci.pm_enabled_240mhz_xip_psram @@ -0,0 +1,12 @@ +CONFIG_FREERTOS_USE_TICKLESS_IDLE=y +CONFIG_COMPILER_OPTIMIZATION_SIZE=y +CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_SIZE=y +CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_SILENT=y +CONFIG_PM_ENABLE=y +CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_240=y +CONFIG_ESPTOOLPY_FLASHFREQ_120M=y +CONFIG_SPIRAM=y +CONFIG_SPIRAM_BOOT_HW_INIT=y +CONFIG_SPIRAM_BOOT_INIT=y +CONFIG_SPIRAM_XIP_FROM_PSRAM=y +CONFIG_SPIRAM_SPEED_120M=y diff --git a/components/spi_flash/test_apps/esp_flash_freq_limit/sdkconfig.ci.pm_enabled_240mhz_xip_psram_rom_impl b/components/spi_flash/test_apps/esp_flash_freq_limit/sdkconfig.ci.pm_enabled_240mhz_xip_psram_rom_impl new file mode 100644 index 0000000000..c376424651 --- /dev/null +++ b/components/spi_flash/test_apps/esp_flash_freq_limit/sdkconfig.ci.pm_enabled_240mhz_xip_psram_rom_impl @@ -0,0 +1,13 @@ +CONFIG_FREERTOS_USE_TICKLESS_IDLE=y +CONFIG_COMPILER_OPTIMIZATION_SIZE=y +CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_SIZE=y +CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_SILENT=y +CONFIG_PM_ENABLE=y +CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_240=y +CONFIG_ESPTOOLPY_FLASHFREQ_120M=y +CONFIG_SPIRAM=y +CONFIG_SPIRAM_BOOT_HW_INIT=y +CONFIG_SPIRAM_BOOT_INIT=y +CONFIG_SPIRAM_XIP_FROM_PSRAM=y +CONFIG_SPIRAM_SPEED_120M=y +CONFIG_SPI_FLASH_ROM_IMPL=y diff --git a/components/spi_flash/test_apps/esp_flash_freq_limit/sdkconfig.defaults b/components/spi_flash/test_apps/esp_flash_freq_limit/sdkconfig.defaults new file mode 100644 index 0000000000..91d7574d37 --- /dev/null +++ b/components/spi_flash/test_apps/esp_flash_freq_limit/sdkconfig.defaults @@ -0,0 +1,2 @@ +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv" diff --git a/docs/en/migration-guides/release-5.x/5.5/peripherals.rst b/docs/en/migration-guides/release-5.x/5.5/peripherals.rst index d7348c3970..531cb83139 100644 --- a/docs/en/migration-guides/release-5.x/5.5/peripherals.rst +++ b/docs/en/migration-guides/release-5.x/5.5/peripherals.rst @@ -59,3 +59,8 @@ Peripherals - The new and legacy drivers are not compatible and must not be used together. Mixing them will trigger warnings during startup, and may even cause crashes and system reboots. To suppress this compatibility check, you may enable the configuration option :ref:`CONFIG_TWAI_SKIP_LEGACY_CONFLICT_CHECK`. - The legacy driver will no longer receive new features, such as TWAI FD (Flexible Data-rate) support. + +SPI Flash Driver +---------------- + +- New argument ``flags`` is added to ``esp_flash_os_functions_t::start``. Caller and implementer should handle this argument properly. diff --git a/docs/zh_CN/migration-guides/release-5.x/5.5/peripherals.rst b/docs/zh_CN/migration-guides/release-5.x/5.5/peripherals.rst index bd457e8c4c..7ebb9ef63a 100644 --- a/docs/zh_CN/migration-guides/release-5.x/5.5/peripherals.rst +++ b/docs/zh_CN/migration-guides/release-5.x/5.5/peripherals.rst @@ -59,3 +59,8 @@ - 新旧驱动不互相兼容,不可混合使用,否则将在启动时打印警告,甚至崩溃并重启。可打开配置项 :ref:`CONFIG_TWAI_SKIP_LEGACY_CONFLICT_CHECK` 以屏蔽该检查。 - 旧版驱动将不再添加新特性(如 TWAIFD)的支持。 + +SPI flash 驱动 +-------------- + +- ``esp_flash_os_functions_t::start`` 新增了一个参数 ``flags``。调用者和实现者应正确处理此参数。 From 187f43a3bb6a49ba1e00c6f40934360cb8f53b26 Mon Sep 17 00:00:00 2001 From: Xiao Xufeng Date: Wed, 26 Nov 2025 02:37:14 +0800 Subject: [PATCH 30/40] Revert "fix(esp_system): limit CPU clock to 160MHz in ESP32-C5 for flash encryption" This reverts commit 3c5d2e6b5843da853fb81067eb2bd287f7fbd735. --- components/esp_security/src/init.c | 10 ---------- components/esp_system/port/soc/esp32c5/Kconfig.cpu | 8 +------- 2 files changed, 1 insertion(+), 17 deletions(-) diff --git a/components/esp_security/src/init.c b/components/esp_security/src/init.c index 86cc0a6fd2..16ef897bad 100644 --- a/components/esp_security/src/init.c +++ b/components/esp_security/src/init.c @@ -55,16 +55,6 @@ static void esp_key_mgr_init(void) ESP_SYSTEM_INIT_FN(esp_security_init, SECONDARY, BIT(0), 103) { -#if CONFIG_IDF_TARGET_ESP32C5 - // Check for unsupported configuration: flash encryption with CPU frequency > 160MHz - // Manual encrypted flash writes are not stable at higher CPU clock. - // Please refer to the ESP32-C5 SoC Errata document for more details. - if (efuse_hal_flash_encryption_enabled() && CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ > 160) { - ESP_EARLY_LOGE(TAG, "Flash encryption with CPU frequency > 160MHz is not supported. Please reconfigure the CPU frequency."); - return ESP_ERR_NOT_SUPPORTED; - } -#endif - esp_crypto_clk_init(); esp_key_mgr_init(); #if CONFIG_ESP_CRYPTO_DPA_PROTECTION_AT_STARTUP diff --git a/components/esp_system/port/soc/esp32c5/Kconfig.cpu b/components/esp_system/port/soc/esp32c5/Kconfig.cpu index 95c3b92cbc..a9fd58e28f 100644 --- a/components/esp_system/port/soc/esp32c5/Kconfig.cpu +++ b/components/esp_system/port/soc/esp32c5/Kconfig.cpu @@ -1,12 +1,9 @@ choice ESP_DEFAULT_CPU_FREQ_MHZ prompt "CPU frequency" default ESP_DEFAULT_CPU_FREQ_MHZ_40 if IDF_ENV_FPGA - default ESP_DEFAULT_CPU_FREQ_MHZ_160 if SECURE_FLASH_ENC_ENABLED default ESP_DEFAULT_CPU_FREQ_MHZ_240 help - CPU frequency to be set on application startup. For flash encryption enabled case, - the default CPU frequency is 160MHz as the encrypted flash writes are not stable at - higher CPU clock. Please see SoC Errata document for details. + CPU frequency to be set on application startup. config ESP_DEFAULT_CPU_FREQ_MHZ_40 bool "40 MHz" @@ -16,9 +13,6 @@ choice ESP_DEFAULT_CPU_FREQ_MHZ config ESP_DEFAULT_CPU_FREQ_MHZ_160 bool "160 MHz" config ESP_DEFAULT_CPU_FREQ_MHZ_240 - # Encrypted flash writes aren't supported at 240 MHz. - # Please see SoC Errata document for details. - depends on !SECURE_FLASH_ENC_ENABLED bool "240 MHz" help When 240MHz is selected, esp_flash_write_encrypted() will automatically limit CPU frequency during From 27f1484774b8a516d682b192393c3c186a97e53b Mon Sep 17 00:00:00 2001 From: Xiao Xufeng Date: Wed, 17 Dec 2025 12:16:55 +0800 Subject: [PATCH 31/40] test(esp_flash): fixed test config that c5 not support 120MHz --- .../esp_flash_freq_limit/pytest_esp_flash_freq_limit.py | 1 + .../sdkconfig.ci.pm_disabled_240mhz_xip_psram | 4 ++-- .../sdkconfig.ci.pm_enabled_240mhz_xip_psram | 4 ++-- .../sdkconfig.ci.pm_enabled_240mhz_xip_psram_rom_impl | 4 ++-- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/components/spi_flash/test_apps/esp_flash_freq_limit/pytest_esp_flash_freq_limit.py b/components/spi_flash/test_apps/esp_flash_freq_limit/pytest_esp_flash_freq_limit.py index 279ad61241..aa30ef27b8 100644 --- a/components/spi_flash/test_apps/esp_flash_freq_limit/pytest_esp_flash_freq_limit.py +++ b/components/spi_flash/test_apps/esp_flash_freq_limit/pytest_esp_flash_freq_limit.py @@ -15,6 +15,7 @@ from pytest_embedded_idf.utils import idf_parametrize 'pm_enabled_160mhz', 'pm_disabled_240mhz_xip_psram', 'pm_enabled_240mhz_xip_psram', + 'pm_enabled_240mhz_xip_psram_rom_impl', ], indirect=True, ) diff --git a/components/spi_flash/test_apps/esp_flash_freq_limit/sdkconfig.ci.pm_disabled_240mhz_xip_psram b/components/spi_flash/test_apps/esp_flash_freq_limit/sdkconfig.ci.pm_disabled_240mhz_xip_psram index f52050a58f..1173f62ef0 100644 --- a/components/spi_flash/test_apps/esp_flash_freq_limit/sdkconfig.ci.pm_disabled_240mhz_xip_psram +++ b/components/spi_flash/test_apps/esp_flash_freq_limit/sdkconfig.ci.pm_disabled_240mhz_xip_psram @@ -1,8 +1,8 @@ CONFIG_PM_ENABLE=n CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_240=y -CONFIG_ESPTOOLPY_FLASHFREQ_120M=y +CONFIG_ESPTOOLPY_FLASHFREQ_80M=y CONFIG_SPIRAM=y CONFIG_SPIRAM_BOOT_HW_INIT=y CONFIG_SPIRAM_BOOT_INIT=y CONFIG_SPIRAM_XIP_FROM_PSRAM=y -CONFIG_SPIRAM_SPEED_120M=y +CONFIG_SPIRAM_SPEED_80M=y diff --git a/components/spi_flash/test_apps/esp_flash_freq_limit/sdkconfig.ci.pm_enabled_240mhz_xip_psram b/components/spi_flash/test_apps/esp_flash_freq_limit/sdkconfig.ci.pm_enabled_240mhz_xip_psram index 5e105cba02..65c01ff2fa 100644 --- a/components/spi_flash/test_apps/esp_flash_freq_limit/sdkconfig.ci.pm_enabled_240mhz_xip_psram +++ b/components/spi_flash/test_apps/esp_flash_freq_limit/sdkconfig.ci.pm_enabled_240mhz_xip_psram @@ -4,9 +4,9 @@ CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_SIZE=y CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_SILENT=y CONFIG_PM_ENABLE=y CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_240=y -CONFIG_ESPTOOLPY_FLASHFREQ_120M=y +CONFIG_ESPTOOLPY_FLASHFREQ_80M=y CONFIG_SPIRAM=y CONFIG_SPIRAM_BOOT_HW_INIT=y CONFIG_SPIRAM_BOOT_INIT=y CONFIG_SPIRAM_XIP_FROM_PSRAM=y -CONFIG_SPIRAM_SPEED_120M=y +CONFIG_SPIRAM_SPEED_80M=y diff --git a/components/spi_flash/test_apps/esp_flash_freq_limit/sdkconfig.ci.pm_enabled_240mhz_xip_psram_rom_impl b/components/spi_flash/test_apps/esp_flash_freq_limit/sdkconfig.ci.pm_enabled_240mhz_xip_psram_rom_impl index c376424651..c70fcf4741 100644 --- a/components/spi_flash/test_apps/esp_flash_freq_limit/sdkconfig.ci.pm_enabled_240mhz_xip_psram_rom_impl +++ b/components/spi_flash/test_apps/esp_flash_freq_limit/sdkconfig.ci.pm_enabled_240mhz_xip_psram_rom_impl @@ -4,10 +4,10 @@ CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_SIZE=y CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_SILENT=y CONFIG_PM_ENABLE=y CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_240=y -CONFIG_ESPTOOLPY_FLASHFREQ_120M=y +CONFIG_ESPTOOLPY_FLASHFREQ_80M=y CONFIG_SPIRAM=y CONFIG_SPIRAM_BOOT_HW_INIT=y CONFIG_SPIRAM_BOOT_INIT=y CONFIG_SPIRAM_XIP_FROM_PSRAM=y -CONFIG_SPIRAM_SPEED_120M=y +CONFIG_SPIRAM_SPEED_80M=y CONFIG_SPI_FLASH_ROM_IMPL=y From ebd08a5aa68c8a7817ac03f970390f31653210d7 Mon Sep 17 00:00:00 2001 From: Chen Jian Hua Date: Wed, 17 Dec 2025 12:53:55 +0800 Subject: [PATCH 32/40] fix(bt): Update bt lib for ESP32-C3 and ESP32-S3(5106725) - Fixed BLE assert lld_con.c 1479 (cherry picked from commit 22641241faccb558353153eef06f7894c2599428) Co-authored-by: chenjianhua --- components/bt/controller/lib_esp32c3_family | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/bt/controller/lib_esp32c3_family b/components/bt/controller/lib_esp32c3_family index 099a7e1ab8..9b50531537 160000 --- a/components/bt/controller/lib_esp32c3_family +++ b/components/bt/controller/lib_esp32c3_family @@ -1 +1 @@ -Subproject commit 099a7e1ab87dd977754fc4ad35678ab7ebf2f2a2 +Subproject commit 9b50531537e755792ac827d00d233eab499a0b37 From c4cc87501b9b47b4bbfa0dfbee4a6edcdbea7229 Mon Sep 17 00:00:00 2001 From: zhanghaipeng Date: Wed, 17 Dec 2025 17:10:58 +0800 Subject: [PATCH 33/40] fix(ble/bledroid): fix codespell issues in bluedroid code --- components/bt/host/bluedroid/stack/gatt/att_protocol.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/bt/host/bluedroid/stack/gatt/att_protocol.c b/components/bt/host/bluedroid/stack/gatt/att_protocol.c index b336aecc38..9b3a2507a3 100644 --- a/components/bt/host/bluedroid/stack/gatt/att_protocol.c +++ b/components/bt/host/bluedroid/stack/gatt/att_protocol.c @@ -33,7 +33,7 @@ #define GATT_HDR_FIND_TYPE_VALUE_LEN 21 #define GATT_OP_CODE_SIZE 1 /********************************************************************** -** ATT protocl message building utility * +** ATT protocol message building utility * ***********************************************************************/ /******************************************************************************* ** @@ -494,7 +494,7 @@ BT_HDR *attp_build_sr_msg(tGATT_TCB *p_tcb, UINT8 op_code, tGATT_SR_MSG *p_msg) ** Description This function sends the server response or indication message ** to client. ** -** Parameter p_tcb: pointer to the connecton control block. +** Parameter p_tcb: pointer to the connection control block. ** p_msg: pointer to message parameters structure. ** ** Returns GATT_SUCCESS if successfully sent; otherwise error code. From c8ba72753651b5cbf424af27db1f05b30d334988 Mon Sep 17 00:00:00 2001 From: "wangtao@espressif.com" Date: Wed, 17 Dec 2025 20:36:28 +0800 Subject: [PATCH 34/40] fix(phy): fix esp32s2 phy lib issue --- components/esp_phy/lib | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/esp_phy/lib b/components/esp_phy/lib index cb9f62b420..3d57415af6 160000 --- a/components/esp_phy/lib +++ b/components/esp_phy/lib @@ -1 +1 @@ -Subproject commit cb9f62b42096ec1e20c05f2aa57fdd0d04df4d33 +Subproject commit 3d57415af6e4c92eff2c4c3463e20a51d7340aba From 3678a25e4cba3903b6e20aaa372793d51f7a5de6 Mon Sep 17 00:00:00 2001 From: Chen Jichang Date: Mon, 22 Dec 2025 20:16:00 +0800 Subject: [PATCH 35/40] fix(gdma): fix set dma burst size failure on p4 v3.0 --- components/soc/esp32p4/register/hw_ver3/soc/ahb_dma_struct.h | 1 + 1 file changed, 1 insertion(+) diff --git a/components/soc/esp32p4/register/hw_ver3/soc/ahb_dma_struct.h b/components/soc/esp32p4/register/hw_ver3/soc/ahb_dma_struct.h index a754086093..a650777a04 100644 --- a/components/soc/esp32p4/register/hw_ver3/soc/ahb_dma_struct.h +++ b/components/soc/esp32p4/register/hw_ver3/soc/ahb_dma_struct.h @@ -604,6 +604,7 @@ typedef union { * task. */ uint32_t out_etm_en_chn: 1; + uint32_t reserved_7: 1; /** out_data_burst_mode_sel_chn : R/W; bitpos: [9:8]; default: 1; * Configures max burst size for TX channeln. * 2'b00: single From 094518fa0d9efdd3f8ffc34ea4ad8e5c835f9901 Mon Sep 17 00:00:00 2001 From: morris Date: Tue, 23 Dec 2025 15:23:42 +0800 Subject: [PATCH 36/40] fix(check_placements): update method names to follow PEP 8 conventions --- .../ldgen_test/check_placements.py | 27 +++++++++++-------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/tools/test_apps/build_system/ldgen_test/check_placements.py b/tools/test_apps/build_system/ldgen_test/check_placements.py index c465f7bf0e..ed21368393 100644 --- a/tools/test_apps/build_system/ldgen_test/check_placements.py +++ b/tools/test_apps/build_system/ldgen_test/check_placements.py @@ -8,13 +8,13 @@ import argparse import subprocess -from pyparsing import alphanums -from pyparsing import hexnums from pyparsing import LineEnd from pyparsing import LineStart from pyparsing import Literal from pyparsing import Optional from pyparsing import Word +from pyparsing import alphanums +from pyparsing import hexnums argparser = argparse.ArgumentParser() @@ -29,21 +29,26 @@ contents = subprocess.check_output([args.objdump, '-t', args.elf]).decode() def check_location(symbol, expected): - pattern = (LineStart() + Word(hexnums).setResultsName('address') - + Optional(Word(alphanums, exact=1)) - + Optional(Word(alphanums,exact=1)) - + Word(alphanums + '._*').setResultsName('actual') - + Word(hexnums) - + Literal(symbol) - + LineEnd()) + pattern = ( + LineStart() + + Word(hexnums).set_results_name('address') + + Optional(Word(alphanums, exact=1)) + + Optional(Word(alphanums, exact=1)) + + Word(alphanums + '._*').set_results_name('actual') + + Word(hexnums) + + Literal(symbol) + + LineEnd() + ) try: - results = pattern.searchString(contents)[0] + results = pattern.search_string(contents)[0] except IndexError: raise Exception("check placement fail: '%s' was not found" % (symbol)) if results.actual != expected: - raise Exception("check placement fail: '%s' was placed in '%s', not in '%s'" % (symbol, results.actual, expected)) + raise Exception( + "check placement fail: '%s' was placed in '%s', not in '%s'" % (symbol, results.actual, expected) + ) print("check placement pass: '%s' was successfully placed in '%s'" % (symbol, results.actual)) return int(results.address, 16) From 5d5d15fbfcea5b2b032e075e3c966c2a7ac83520 Mon Sep 17 00:00:00 2001 From: wuzhenghui Date: Tue, 23 Dec 2025 17:08:43 +0800 Subject: [PATCH 37/40] fix(esp_hw_support): fix RNG to LP Peri domain dependency on C5 --- components/esp_hw_support/sleep_modes.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/components/esp_hw_support/sleep_modes.c b/components/esp_hw_support/sleep_modes.c index 391a7d4063..cdedc68a04 100644 --- a/components/esp_hw_support/sleep_modes.c +++ b/components/esp_hw_support/sleep_modes.c @@ -2754,6 +2754,9 @@ static SLEEP_FN_ATTR uint32_t get_power_down_flags(void) // TOP power domain depends on the RTC_PERIPH power domain on ESP32C6, RTC_PERIPH should only be disabled when the TOP domain is down. pd_flags &= ~RTC_SLEEP_PD_RTC_PERIPH; } +#elif CONFIG_IDF_TARGET_ESP32C5 + // TODO: [ESP32C5] PM-642 RNG module depends on LP PERIPH domain, force it on temporary. + pd_flags &= ~RTC_SLEEP_PD_RTC_PERIPH; #endif return pd_flags; } From 08f5f0d66be5ee7c72d32f682aa043bb7e261d97 Mon Sep 17 00:00:00 2001 From: Xiao Xufeng Date: Wed, 24 Dec 2025 02:21:34 +0800 Subject: [PATCH 38/40] fix(esp_system): limit CPU clock to 160MHz in ESP32-C5 for flash encryption This reverts commit 7145fc9558752a6532072bb272e94389807eda51. --- components/esp_security/src/init.c | 10 ++++++++++ components/esp_system/port/soc/esp32c5/Kconfig.cpu | 8 +++++++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/components/esp_security/src/init.c b/components/esp_security/src/init.c index 16ef897bad..86cc0a6fd2 100644 --- a/components/esp_security/src/init.c +++ b/components/esp_security/src/init.c @@ -55,6 +55,16 @@ static void esp_key_mgr_init(void) ESP_SYSTEM_INIT_FN(esp_security_init, SECONDARY, BIT(0), 103) { +#if CONFIG_IDF_TARGET_ESP32C5 + // Check for unsupported configuration: flash encryption with CPU frequency > 160MHz + // Manual encrypted flash writes are not stable at higher CPU clock. + // Please refer to the ESP32-C5 SoC Errata document for more details. + if (efuse_hal_flash_encryption_enabled() && CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ > 160) { + ESP_EARLY_LOGE(TAG, "Flash encryption with CPU frequency > 160MHz is not supported. Please reconfigure the CPU frequency."); + return ESP_ERR_NOT_SUPPORTED; + } +#endif + esp_crypto_clk_init(); esp_key_mgr_init(); #if CONFIG_ESP_CRYPTO_DPA_PROTECTION_AT_STARTUP diff --git a/components/esp_system/port/soc/esp32c5/Kconfig.cpu b/components/esp_system/port/soc/esp32c5/Kconfig.cpu index a9fd58e28f..95c3b92cbc 100644 --- a/components/esp_system/port/soc/esp32c5/Kconfig.cpu +++ b/components/esp_system/port/soc/esp32c5/Kconfig.cpu @@ -1,9 +1,12 @@ choice ESP_DEFAULT_CPU_FREQ_MHZ prompt "CPU frequency" default ESP_DEFAULT_CPU_FREQ_MHZ_40 if IDF_ENV_FPGA + default ESP_DEFAULT_CPU_FREQ_MHZ_160 if SECURE_FLASH_ENC_ENABLED default ESP_DEFAULT_CPU_FREQ_MHZ_240 help - CPU frequency to be set on application startup. + CPU frequency to be set on application startup. For flash encryption enabled case, + the default CPU frequency is 160MHz as the encrypted flash writes are not stable at + higher CPU clock. Please see SoC Errata document for details. config ESP_DEFAULT_CPU_FREQ_MHZ_40 bool "40 MHz" @@ -13,6 +16,9 @@ choice ESP_DEFAULT_CPU_FREQ_MHZ config ESP_DEFAULT_CPU_FREQ_MHZ_160 bool "160 MHz" config ESP_DEFAULT_CPU_FREQ_MHZ_240 + # Encrypted flash writes aren't supported at 240 MHz. + # Please see SoC Errata document for details. + depends on !SECURE_FLASH_ENC_ENABLED bool "240 MHz" help When 240MHz is selected, esp_flash_write_encrypted() will automatically limit CPU frequency during From 0f1ab3136ad524d1bd6ccbb60ef55855649127e1 Mon Sep 17 00:00:00 2001 From: zhangyanjiao Date: Wed, 24 Dec 2025 10:51:44 +0800 Subject: [PATCH 39/40] fix(wifi): fixed the crash issue when send espnow data in HE phymode --- components/esp_wifi/lib | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/esp_wifi/lib b/components/esp_wifi/lib index 1409b9d85f..01d52d9e69 160000 --- a/components/esp_wifi/lib +++ b/components/esp_wifi/lib @@ -1 +1 @@ -Subproject commit 1409b9d85f6a579f530d83322808b25209d4c7c5 +Subproject commit 01d52d9e69032c486015dc28b08c3bf6aaf348a9 From 30aaf64524299d3bde422ca9a2848090d1bc5d0f Mon Sep 17 00:00:00 2001 From: morris Date: Wed, 24 Dec 2025 22:34:47 +0800 Subject: [PATCH 40/40] change(version): Update version to 5.5.2 --- .gitlab/ci/common.yml | 2 +- components/esp_common/include/esp_idf_version.h | 2 +- tools/cmake/version.cmake | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.gitlab/ci/common.yml b/.gitlab/ci/common.yml index fdb2b9f091..b107e0d171 100644 --- a/.gitlab/ci/common.yml +++ b/.gitlab/ci/common.yml @@ -40,7 +40,7 @@ variables: GIT_FETCH_EXTRA_FLAGS: "--no-recurse-submodules --prune --prune-tags" # we're using .cache folder for caches GIT_CLEAN_FLAGS: -ffdx -e .cache/ - LATEST_GIT_TAG: v5.5.1 + LATEST_GIT_TAG: v5.5.2 SUBMODULE_FETCH_TOOL: "tools/ci/ci_fetch_submodule.py" # by default we will fetch all submodules diff --git a/components/esp_common/include/esp_idf_version.h b/components/esp_common/include/esp_idf_version.h index 30015cf1d7..966577bb6b 100644 --- a/components/esp_common/include/esp_idf_version.h +++ b/components/esp_common/include/esp_idf_version.h @@ -15,7 +15,7 @@ extern "C" { /** Minor version number (x.X.x) */ #define ESP_IDF_VERSION_MINOR 5 /** Patch version number (x.x.X) */ -#define ESP_IDF_VERSION_PATCH 1 +#define ESP_IDF_VERSION_PATCH 2 /** * Macro to convert IDF version number into an integer diff --git a/tools/cmake/version.cmake b/tools/cmake/version.cmake index cc62efdf3b..844f75fd2e 100644 --- a/tools/cmake/version.cmake +++ b/tools/cmake/version.cmake @@ -1,5 +1,5 @@ set(IDF_VERSION_MAJOR 5) set(IDF_VERSION_MINOR 5) -set(IDF_VERSION_PATCH 1) +set(IDF_VERSION_PATCH 2) set(ENV{IDF_VERSION} "${IDF_VERSION_MAJOR}.${IDF_VERSION_MINOR}.${IDF_VERSION_PATCH}")