diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 3d44cac7ab4..e26acbc2f11 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -29,6 +29,5 @@ include: - ".gitlab/ci/pre_check.yml" - ".gitlab/ci/build.yml" - ".gitlab/ci/host-test.yml" - - ".gitlab/ci/deploy.yml" - ".gitlab/ci/post_deploy.yml" - ".gitlab/ci/test-win.yml" diff --git a/.gitlab/ci/common.yml b/.gitlab/ci/common.yml index 9f2890e0596..9b48d5004e1 100644 --- a/.gitlab/ci/common.yml +++ b/.gitlab/ci/common.yml @@ -19,6 +19,10 @@ variables: # Common parameters for the 'make' during CI tests MAKEFLAGS: "-j5 --no-keep-going" + # By default, CI build jobs request and limit 4 CPU cores and 4 GB of memory + # https://github.com/ninja-build/ninja/blob/def9560a0b6d755936e615ce443a0aec45c39bdb/src/ninja.cc#L262 + # cpu_cores + 2 + IDF_PY_BUILD_JOBS: "6" # GitLab-CI environment # Thanks to pack-objects cache, clone strategy should behave faster than fetch @@ -36,7 +40,7 @@ variables: # --prune --prune-tags: in case remote branch or tag is force pushed GIT_FETCH_EXTRA_FLAGS: "--no-recurse-submodules --prune --prune-tags" - LATEST_GIT_TAG: v6.0.1 + LATEST_GIT_TAG: v6.0.2 SUBMODULE_FETCH_TOOL: "tools/ci/ci_fetch_submodule.py" # by default we will fetch all submodules @@ -119,6 +123,11 @@ variables: # configure cmake related flags source tools/ci/configure_ci_environment.sh + if [[ "$CI_CCACHE_STATS" == 1 ]] && command -v ccache >/dev/null 2>&1 && [[ -n "$CCACHE_STATSLOG" ]]; then + mkdir -p "$(dirname "$CCACHE_STATSLOG")" + rm -f "$CCACHE_STATSLOG" + fi + # add extra python packages export PYTHONPATH="$IDF_PATH/tools:$IDF_PATH/tools/ci:$IDF_PATH/tools/esp_app_trace:$IDF_PATH/components/partition_table:$IDF_PATH/tools/ci/python_packages:$PYTHONPATH" @@ -169,6 +178,15 @@ variables: # Done after sourcing export.sh so that we could easily invoke the right pip section_start "upgrade_ci_dependencies" "Upgrading CI dependencies" pip install --upgrade --upgrade-strategy=eager -r $IDF_PATH/tools/requirements/requirements.ci.txt -c ~/.espressif/${CI_PYTHON_CONSTRAINT_FILE} + # we need the latest DEV release for esptool to be installed in every job without bumping the minimal requirement in + # the constraint file. CI_ESPTOOL_EXTRA_INDEX_URL (set in GitLab project variables) optionally points to an internal + # package registry so that internal dev builds are preferred when the variable is set; it is a no-op when unset. + # Use an explicit if/else: the `${VAR:+--flag "$VAR"}` form collapses to a single token under zsh (macOS runners). + if [[ -n "$CI_ESPTOOL_EXTRA_INDEX_URL" ]]; then + pip install --upgrade --pre --extra-index-url "$CI_ESPTOOL_EXTRA_INDEX_URL" esptool -c ~/.espressif/${CI_PYTHON_CONSTRAINT_FILE} + else + pip install --upgrade --pre esptool -c ~/.espressif/${CI_PYTHON_CONSTRAINT_FILE} + fi section_end "upgrade_ci_dependencies" REEXPORT_NEEDED=0 @@ -231,7 +249,17 @@ variables: .show_ccache_statistics: &show_ccache_statistics | # Show ccache statistics if enabled globally section_start "ccache_show_stats" "Show ccache statistics" - test "$CI_CCACHE_STATS" == 1 && test -n "$(which ccache)" && ccache --show-stats -vv || true + if [[ "$CI_CCACHE_STATS" == 1 ]] && command -v ccache >/dev/null 2>&1; then + if ccache --help 2>/dev/null | grep -q -- '--show-log-stats'; then + if [[ -n "$CCACHE_STATSLOG" && -f "$CCACHE_STATSLOG" ]]; then + ccache --show-log-stats -vv + else + echo "INFO: No per-job ccache statistics were recorded" + fi + else + ccache --show-stats -vv + fi + fi || true section_end "ccache_show_stats" .upload_failed_job_log_artifacts: &upload_failed_job_log_artifacts | @@ -246,6 +274,7 @@ variables: .after_script:build: after_script: - source tools/ci/utils.sh + - source tools/ci/configure_ci_environment.sh - *show_ccache_statistics - *upload_failed_job_log_artifacts diff --git a/.gitlab/ci/deploy.yml b/.gitlab/ci/deploy.yml deleted file mode 100644 index c25fa7a8692..00000000000 --- a/.gitlab/ci/deploy.yml +++ /dev/null @@ -1,21 +0,0 @@ -.deploy_job_template: - stage: deploy - image: $ESP_ENV_IMAGE - tags: [ deploy ] - -deploy_update_SHA_in_esp-dockerfiles: - extends: - - .deploy_job_template - - .before_script:minimal - - .rules:protected:deploy - dependencies: [] - variables: - GIT_DEPTH: 2 - tags: [build, shiny] - script: - - 'curl --header "PRIVATE-TOKEN: ${ESPCI_SCRIPTS_TOKEN}" -o create_MR_in_esp_dockerfile.sh $GITLAB_HTTP_SERVER/api/v4/projects/1260/repository/files/create_MR_in_esp_dockerfile%2Fcreate_MR_in_esp_dockerfile.sh/raw\?ref\=master' - - chmod +x create_MR_in_esp_dockerfile.sh - - ./create_MR_in_esp_dockerfile.sh - environment: - name: deploy_update_SHA_in_esp-dockerfiles_production - deployment_tier: production diff --git a/.gitlab/ci/host-test.yml b/.gitlab/ci/host-test.yml index 775b1d51b77..1c409799c0a 100644 --- a/.gitlab/ci/host-test.yml +++ b/.gitlab/ci/host-test.yml @@ -184,6 +184,7 @@ test_tools: - run_cmd pytest --noconftest test_idf_py.py --junitxml=${IDF_PATH}/XUNIT_IDF_PY.xml --ignore-result-files ${KNOWN_FAILURE_CASES_FILE_NAME} || stat=1 - run_cmd pytest --noconftest test_hints.py --junitxml=${IDF_PATH}/XUNIT_HINTS.xml --ignore-result-files ${KNOWN_FAILURE_CASES_FILE_NAME} || stat=1 - run_cmd pytest --noconftest test_idf_qemu.py --junitxml=${IDF_PATH}/XUNIT_IDF_PY_QEMU.xml --ignore-result-files ${KNOWN_FAILURE_CASES_FILE_NAME} || stat=1 + - run_cmd pytest --noconftest test_mcp_ext.py --junitxml=${IDF_PATH}/XUNIT_MCP_EXT.xml --ignore-result-files ${KNOWN_FAILURE_CASES_FILE_NAME} || stat=1 - cd ${IDF_PATH}/tools/test_bsasm - run_cmd pytest --noconftest test_bsasm.py --junitxml=${IDF_PATH}/XUNIT_BSASM.xml --ignore-result-files ${KNOWN_FAILURE_CASES_FILE_NAME} || stat=1 - cd ${IDF_PATH}/tools/test_mkdfu diff --git a/.gitlab/ci/rules.yml b/.gitlab/ci/rules.yml index ea1d3b9ce61..7cd939739f7 100644 --- a/.gitlab/ci/rules.yml +++ b/.gitlab/ci/rules.yml @@ -206,12 +206,6 @@ rules: - <<: *if-protected-check -.rules:protected:deploy: - rules: - - <<: *if-qa-test-tag - when: never - - <<: *if-protected-deploy - .rules:master:push: rules: - <<: *if-master-push diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 93367be27fc..f35979ed204 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -228,7 +228,7 @@ repos: name: Lint rST files in docs folder using Sphinx Lint files: ^(docs/en|docs/zh_CN)/.*\.(rst|inc)$ - repo: https://github.com/espressif/esp-idf-kconfig.git - rev: v3.2.0 + rev: v3.10.0 hooks: - id: check-kconfig-files - id: check-deprecated-kconfig-options diff --git a/components/bootloader/Kconfig.projbuild b/components/bootloader/Kconfig.projbuild index 1c828429edb..12decf2f2d8 100644 --- a/components/bootloader/Kconfig.projbuild +++ b/components/bootloader/Kconfig.projbuild @@ -478,6 +478,18 @@ menu "Security features" default y depends on SOC_SECURE_BOOT_V2_ECC + # ECDSA based Secure Boot V2 is not functional for certain input vectors on these + # SoCs. The scheme stays available but, for hardware Secure Boot, must be explicitly + # turned on via SECURE_BOOT_V2_FORCE_ENABLE_ECDSA under "Allow potentially insecure + # options" (CONFIG_SECURE_BOOT_INSECURE). + # + # TODO: IDF-15721 - drop a SoC from this list once a fixing hardware ECO revision + # ships, gating on the selected minimum chip revision, e.g.: + # default y if IDF_TARGET_ESP32C5 && ESP32C5_REV_MIN_FULL < + config SECURE_BOOT_V2_ECDSA_INSECURE + bool + default y if IDF_TARGET_ESP32C5 || IDF_TARGET_ESP32C61 || IDF_TARGET_ESP32H2 || IDF_TARGET_ESP32P4 + config SECURE_BOOT_V1_SUPPORTED bool default y @@ -549,6 +561,10 @@ menu "Security features" config SECURE_SIGNED_APPS_ECDSA_V2_SCHEME bool "ECDSA (V2)" depends on SECURE_BOOT_V2_ECC_SUPPORTED && (SECURE_SIGNED_APPS_NO_SECURE_BOOT || SECURE_BOOT_V2_ENABLED) + # On the affected SoCs (SECURE_BOOT_V2_ECDSA_INSECURE), hardware Secure Boot with ECDSA + # is offered only when SECURE_BOOT_V2_FORCE_ENABLE_ECDSA is explicitly set. App signing + # without hardware Secure Boot is not affected by this gate. + depends on !SECURE_BOOT_V2_ENABLED || (!SECURE_BOOT_V2_ECDSA_INSECURE || SECURE_BOOT_V2_FORCE_ENABLE_ECDSA) help For Secure boot V2 (e.g., ESP32-C2 SoC), appends ECDSA based signature block to the application. Refer to documentation before enabling. @@ -931,6 +947,19 @@ menu "Security features" # it's possible for the insecure menu to be disabled but the insecure option # to remain on which is very bad.) + config SECURE_BOOT_V2_FORCE_ENABLE_ECDSA + bool "Force enable ECDSA based Secure Boot V2" + depends on SECURE_BOOT_INSECURE && SECURE_BOOT_V2_ECDSA_INSECURE + default n + help + ECDSA based Secure Boot V2 is not functional for certain input vectors on this SoC + and is therefore not offered by default. Refer to the hardware errata document for + details. + + Setting this option re-enables the ECDSA based Secure Boot V2 signing scheme despite + the known vulnerability. Only set this option if you fully understand the risk. RSA + based Secure Boot V2 is the recommended scheme on SoCs that support it. + config SECURE_BOOT_ALLOW_ROM_BASIC bool "Leave ROM BASIC Interpreter available on reset" depends on (SECURE_BOOT_INSECURE || SECURE_FLASH_ENCRYPTION_MODE_DEVELOPMENT) && IDF_TARGET_ESP32 @@ -983,13 +1012,8 @@ menu "Security features" Revoking unused digest slots makes ensures that no trusted keys can be added later by an attacker. If set, it means that you have a plan to use unused digests slots later. - Note that if you plan to enable secure boot during the first boot up, the bootloader will intentionally - revoke the unused digest slots while enabling secure boot, even if the above config is enabled because - keeping the unused key slots un-revoked would a security hazard. - In case for any development workflow if you need to avoid this revocation, you should enable - secure boot externally (host based mechanism) rather than enabling it during the boot up, - so that the bootloader would not need to enable secure boot and thus you could avoid its revocation - strategy. + This config is honored both at runtime in the app and while enabling secure boot during the first + boot up in the bootloader. When set, the unused digest slots are left un-revoked in both cases. config SECURE_BOOT_SKIP_WRITE_PROTECTION_SCA bool "Skip write-protection of SECURE_FLASH_PSEUDO_ROUND_FUNC_STRENGTH" diff --git a/components/bootloader/subproject/CMakeLists.txt b/components/bootloader/subproject/CMakeLists.txt index 4fba9e1ce55..68200320c08 100644 --- a/components/bootloader/subproject/CMakeLists.txt +++ b/components/bootloader/subproject/CMakeLists.txt @@ -55,13 +55,25 @@ if(IGNORE_EXTRA_COMPONENT) OUTPUT_VARIABLE EXTRA_COMPONENT_EXCLUDE_DIRS) endif() -# Consider each directory in the project's bootloader_components as a component to be compiled -file(GLOB proj_components RELATIVE ${PROJECT_EXTRA_COMPONENTS} ${PROJECT_EXTRA_COMPONENTS}/*) -foreach(component ${proj_components}) - # Only directories are considered components - if(IS_DIRECTORY "${PROJECT_EXTRA_COMPONENTS}/${component}" AND NOT ${component} IN_LIST IGNORE_EXTRA_COMPONENT) - list(APPEND COMPONENTS ${component}) - endif() +foreach(extra_component_dir ${EXTRA_COMPONENT_DIRS}) + if(EXISTS "${extra_component_dir}/CMakeLists.txt") + # BOOTLOADER_EXTRA_COMPONENT_DIRS may point directly to a single component. + # Add the directory name so the bootloader COMPONENTS filter keeps it. + get_filename_component(component "${extra_component_dir}" NAME) + if(NOT ${component} IN_LIST IGNORE_EXTRA_COMPONENT) + list(APPEND COMPONENTS ${component}) + endif() + else() + # BOOTLOADER_EXTRA_COMPONENT_DIRS may also point to a directory containing + # multiple component directories. Add each child directory to the filter. + file(GLOB proj_components RELATIVE ${extra_component_dir} ${extra_component_dir}/*) + foreach(component ${proj_components}) + # Only directories are considered components. + if(IS_DIRECTORY "${extra_component_dir}/${component}" AND NOT ${component} IN_LIST IGNORE_EXTRA_COMPONENT) + list(APPEND COMPONENTS ${component}) + endif() + endforeach() + endif() endforeach() set(BOOTLOADER_BUILD 1) diff --git a/components/bootloader/subproject/main/ld/esp32p4/bootloader.memory.ld.in b/components/bootloader/subproject/main/ld/esp32p4/bootloader.memory.ld.in index e7b0611af49..e7870d8dcb1 100644 --- a/components/bootloader/subproject/main/ld/esp32p4/bootloader.memory.ld.in +++ b/components/bootloader/subproject/main/ld/esp32p4/bootloader.memory.ld.in @@ -31,7 +31,7 @@ #endif bootloader_stack_overhead = 0x2000; /* For safety margin between bootloader data section and startup stacks */ bootloader_dram_seg_len = 0x5000; -bootloader_iram_loader_seg_len = 0x7000; +bootloader_iram_loader_seg_len = 0x8000; bootloader_iram_seg_len = 0x2D00; /* Start of the lower region is determined by region size and the end of the higher region */ @@ -54,9 +54,9 @@ MEMORY * 3. Update SRAM_DRAM_END in components/esp_system/ld/esp32p4/memory.ld.in to the same value. */ #if !CONFIG_ESP32P4_SELECTS_REV_LESS_V3 -#define BOOTLOADER_IRAM_LOADER_SEG_START_EXPECTED 0x4FFAEFC0 +#define BOOTLOADER_IRAM_LOADER_SEG_START_EXPECTED 0x4FFADFC0 #else -#define BOOTLOADER_IRAM_LOADER_SEG_START_EXPECTED 0x4FF2CBD0 +#define BOOTLOADER_IRAM_LOADER_SEG_START_EXPECTED 0x4FF2BBD0 #endif ASSERT(bootloader_iram_loader_seg_start == BOOTLOADER_IRAM_LOADER_SEG_START_EXPECTED, "bootloader_iram_loader_seg_start inconsistent with SRAM_DRAM_END"); diff --git a/components/bootloader_support/src/secure_boot_v2/secure_boot.c b/components/bootloader_support/src/secure_boot_v2/secure_boot.c index 9bfe03fb8e0..89e2b662b95 100644 --- a/components/bootloader_support/src/secure_boot_v2/secure_boot.c +++ b/components/bootloader_support/src/secure_boot_v2/secure_boot.c @@ -347,8 +347,12 @@ static esp_err_t check_and_generate_secure_boot_keys(const esp_image_metadata_t if (boot_key_digests.num_digests < SECURE_BOOT_NUM_BLOCKS) { /* The revocation index can be 0, 1, 2. Bootloader count can be 1,2,3. */ for (unsigned i = boot_key_digests.num_digests; i < SECURE_BOOT_NUM_BLOCKS; i++) { +#ifndef CONFIG_SECURE_BOOT_ALLOW_UNUSED_DIGEST_SLOTS ESP_LOGI(TAG, "Revoking empty key digest slot (%d)...", i); esp_efuse_set_digest_revoke(i); +#else + ESP_LOGW(TAG, "Unused key digest slot (%d) left un-revoked due to the config SECURE_BOOT_ALLOW_UNUSED_DIGEST_SLOTS", i); +#endif } } #endif // SOC_EFUSE_REVOKE_BOOT_KEY_DIGESTS diff --git a/components/bootloader_support/src/secure_boot_v2/secure_boot_ecdsa_signature.c b/components/bootloader_support/src/secure_boot_v2/secure_boot_ecdsa_signature.c index 698786df704..41eef7459c5 100644 --- a/components/bootloader_support/src/secure_boot_v2/secure_boot_ecdsa_signature.c +++ b/components/bootloader_support/src/secure_boot_v2/secure_boot_ecdsa_signature.c @@ -58,7 +58,12 @@ esp_err_t verify_ecdsa_signature_block(const ets_secure_boot_signature_t *sig_bl return ESP_ERR_INVALID_ARG; } - psa_set_key_algorithm(&key_attributes, PSA_ALG_ECDSA(PSA_ALG_SHA_256)); +#if CONFIG_SECURE_BOOT_ECDSA_KEY_LEN_384_BITS + const psa_algorithm_t alg = PSA_ALG_ECDSA(PSA_ALG_SHA_384); +#else + const psa_algorithm_t alg = PSA_ALG_ECDSA(PSA_ALG_SHA_256); +#endif + psa_set_key_algorithm(&key_attributes, alg); psa_set_key_type(&key_attributes, PSA_KEY_TYPE_ECC_PUBLIC_KEY(curve_family)); /* Prepare the public key data from X and Y coordinates */ @@ -95,7 +100,7 @@ esp_err_t verify_ecdsa_signature_block(const ets_secure_boot_signature_t *sig_bl } /* Verify the signature */ - status = psa_verify_hash(key_handle, PSA_ALG_ECDSA(PSA_ALG_SHA_256), + status = psa_verify_hash(key_handle, alg, image_digest, ESP_SECURE_BOOT_DIGEST_LEN, signature, 2 * key_size); diff --git a/components/bt/CMakeLists.txt b/components/bt/CMakeLists.txt index 999a9f2c45a..f4a6f5ec2b4 100644 --- a/components/bt/CMakeLists.txt +++ b/components/bt/CMakeLists.txt @@ -5,973 +5,70 @@ if(${target} STREQUAL "linux") endif() if(CONFIG_IDF_TARGET_ESP32S3) - set(target_name "esp32c3") + set(TARGET_SRC_NAME "esp32c3") elseif(CONFIG_IDF_TARGET_ESP32C61) - set(target_name "esp32c6") + set(TARGET_SRC_NAME "esp32c6") +elseif(CONFIG_IDF_TARGET_ESP32H21) + set(TARGET_SRC_NAME "esp32h2") else() - set(target_name "${idf_target}") + set(TARGET_SRC_NAME "${idf_target}") endif() +set(srcs "") +set(include_dirs "") +set(priv_include_dirs "") +set(ldscripts "linker_common.lf") + # API headers that are used in the docs are also compiled # even if CONFIG_BT_ENABLED=n as long as CONFIG_IDF_DOC_BUILD=y +if(CONFIG_IDF_DOC_BUILD OR CONFIG_BT_ENABLED) + add_subdirectory(common) + list(APPEND srcs ${bt_common_srcs}) + list(APPEND include_dirs ${bt_common_include_dirs}) + list(APPEND priv_include_dirs ${bt_common_priv_include_dirs}) -if(CONFIG_SOC_BT_SUPPORTED) - set(target_specific_include_dirs include/${target_name}/include) -endif() + # Controller + add_subdirectory(controller) + list(APPEND srcs ${bt_ctrl_srcs}) + list(APPEND include_dirs ${bt_ctrl_include_dirs}) + set(ldscripts ${bt_ctrl_ldscripts}) -set(common_include_dirs - common/api/include/api - common/btc/profile/esp/blufi/include - common/btc/profile/esp/include - common/hci_log/include - common/ble_log/include - common/ble_log/deprecated/include -) - -set(ble_mesh_include_dirs - "esp_ble_mesh/common/include" - "esp_ble_mesh/core" - "esp_ble_mesh/core/include" - "esp_ble_mesh/core/storage" - "esp_ble_mesh/btc/include" - "esp_ble_mesh/models/common/include" - "esp_ble_mesh/models/client/include" - "esp_ble_mesh/models/server/include" - "esp_ble_mesh/api/core/include" - "esp_ble_mesh/api/models/include" - "esp_ble_mesh/api" -) - -set(ble_mesh_v11_include_dirs - "esp_ble_mesh/lib/include" - "esp_ble_mesh/v1.1/api/core/include" - "esp_ble_mesh/v1.1/api/models/include" - "esp_ble_mesh/v1.1/btc/include" - "esp_ble_mesh/v1.1/include" - "esp_ble_mesh/v1.1/dfu" - "esp_ble_mesh/v1.1/mbt" -) - -if(CONFIG_IDF_DOC_BUILD) - list(APPEND ble_mesh_include_dirs - ${ble_mesh_v11_include_dirs}) -endif() - -set(bluedroid_include_dirs host/bluedroid/api/include/api) - -if(CONFIG_BT_CONTROLLER_ENABLED OR CONFIG_IDF_DOC_BUILD) -set(nimble_hci_include_dirs host/nimble/esp-hci/include) -endif() - -if(CONFIG_IDF_DOC_BUILD) - list(APPEND include_dirs - ${target_specific_include_dirs} - ${common_include_dirs} - ${ble_mesh_include_dirs} - ${bluedroid_include_dirs} - ${nimble_hci_include_dirs}) -endif() - -if(CONFIG_BT_ENABLED) - - set(srcs "") - set(include_dirs "") - set(ldscripts "linker_common.lf") - if(CONFIG_BT_CONTROLLER_ENABLED) - list(APPEND srcs "controller/${target_name}/bt.c") - - if(CONFIG_IDF_TARGET_ESP32) - list(APPEND srcs "controller/esp32/hli_api.c" - "controller/esp32/hli_vectors.S") - list(APPEND ldscripts "linker_rw_bt_controller.lf") - elseif(CONFIG_IDF_TARGET_ESP32C3) - list(APPEND ldscripts "linker_rw_bt_controller.lf") - elseif(CONFIG_IDF_TARGET_ESP32S3) - list(APPEND ldscripts "linker_rw_bt_controller.lf") - elseif(CONFIG_IDF_TARGET_ESP32C2) - list(APPEND srcs "controller/${target_name}/ble.c") - list(APPEND srcs "controller/esp32c2/dummy.c") - set(ldscripts "linker_esp32c2.lf") + # Controller porting or porting_btdm + if(CONFIG_SOC_ESP_NIMBLE_CONTROLLER) + if(CONFIG_BT_DUAL_MODE_ARCH) + add_subdirectory(porting_btdm) else() - list(APPEND srcs "controller/${target_name}/ble.c") - list(APPEND ldscripts "linker_esp_ble_controller.lf") - endif() - - list(APPEND include_dirs ${target_specific_include_dirs}) - endif() - - # Common - list(APPEND include_dirs common/osi/include) - - list(APPEND priv_include_dirs - common/btc/include - common/include - porting/mem/ - porting/include - ) - list(APPEND include_dirs ${common_include_dirs}) - - list(APPEND srcs "common/btc/core/btc_alarm.c" - "common/api/esp_blufi_api.c" - "common/hci_log/bt_hci_log.c" - "common/btc/core/btc_manage.c" - "common/btc/core/btc_task.c" - "common/btc/profile/esp/blufi/blufi_prf.c" - "common/btc/profile/esp/blufi/blufi_protocol.c" - "common/osi/alarm.c" - "common/osi/allocator.c" - "common/osi/buffer.c" - "common/osi/config.c" - "common/osi/fixed_queue.c" - "common/osi/pkt_queue.c" - "common/osi/fixed_pkt_queue.c" - "common/osi/future.c" - "common/osi/hash_functions.c" - "common/osi/hash_map.c" - "common/osi/list.c" - "common/osi/mutex.c" - "common/osi/thread.c" - "common/osi/osi.c" - "common/osi/semaphore.c" - "porting/mem/bt_osi_mem.c" - "common/ble_log/deprecated/ble_log_spi_out.c" - ) - - # BLE Log Module - if(CONFIG_BLE_LOG_ENABLED) - # Core source files - list(APPEND srcs - common/ble_log/src/ble_log.c - common/ble_log/src/ble_log_lbm.c - common/ble_log/src/ble_log_rt.c - common/ble_log/src/ble_log_util.c - ) - - # Includes - list(APPEND include_dirs - common/ble_log/include - ) - - # Private includes - list(APPEND priv_include_dirs - common/ble_log/src/internal_include - common/ble_log/src/internal_include/prph - ) - - # Timestamp synchronization extension - if(CONFIG_BLE_LOG_TS_ENABLED) - list(APPEND srcs common/ble_log/src/ble_log_ts.c) - endif() - - # Peripheral interface implementation - if(CONFIG_BLE_LOG_PRPH_DUMMY) - list(APPEND srcs common/ble_log/src/prph/ble_log_prph_dummy.c) - elseif(CONFIG_BLE_LOG_PRPH_SPI_MASTER_DMA) - list(APPEND srcs common/ble_log/src/prph/ble_log_prph_spi_master_dma.c) - elseif(CONFIG_BLE_LOG_PRPH_UART_DMA) - list(APPEND srcs common/ble_log/src/prph/ble_log_prph_uart_dma.c) + add_subdirectory(porting) endif() + list(APPEND srcs ${porting_srcs}) + list(APPEND include_dirs ${porting_include_dirs}) + list(APPEND priv_include_dirs ${porting_priv_include_dirs}) endif() # Host Bluedroid - if(CONFIG_BT_BLUEDROID_ENABLED) + add_subdirectory(host/bluedroid) + list(APPEND srcs ${bluedroid_host_srcs}) + list(APPEND include_dirs ${bluedroid_host_include_dirs}) + list(APPEND priv_include_dirs ${bluedroid_host_priv_include_dirs}) - list(APPEND priv_include_dirs - host/bluedroid/bta/include - host/bluedroid/bta/ar/include - host/bluedroid/bta/av/include - host/bluedroid/bta/dm/include - host/bluedroid/bta/gatt/include - host/bluedroid/bta/hf_ag/include - host/bluedroid/bta/hf_client/include - host/bluedroid/bta/hd/include - host/bluedroid/bta/hh/include - host/bluedroid/bta/jv/include - host/bluedroid/bta/pba/include - host/bluedroid/bta/sdp/include - host/bluedroid/bta/sys/include - host/bluedroid/device/include - host/bluedroid/hci/include - host/bluedroid/btc/profile/esp/include - host/bluedroid/btc/profile/std/a2dp/include - host/bluedroid/btc/profile/std/include - host/bluedroid/btc/include - host/bluedroid/stack/btm/include - host/bluedroid/stack/gap/include - host/bluedroid/stack/gatt/include - host/bluedroid/stack/hid/include - host/bluedroid/stack/l2cap/include - host/bluedroid/stack/sdp/include - host/bluedroid/stack/smp/include - host/bluedroid/stack/avct/include - host/bluedroid/stack/avrc/include - host/bluedroid/stack/avdt/include - host/bluedroid/stack/a2dp/include - host/bluedroid/stack/rfcomm/include - host/bluedroid/stack/obex/include - host/bluedroid/stack/goep/include - host/bluedroid/stack/include - host/bluedroid/common/include - host/bluedroid/config/include) + # NimBLE Host + add_subdirectory(host/nimble) + list(APPEND srcs ${nimble_host_srcs}) + list(APPEND include_dirs ${nimble_host_include_dirs}) - list(APPEND include_dirs ${bluedroid_include_dirs}) - - list(APPEND srcs "host/bluedroid/api/esp_a2dp_api.c" - "host/bluedroid/api/esp_avrc_api.c" - "host/bluedroid/api/esp_bluedroid_hci.c" - "host/bluedroid/api/esp_bt_device.c" - "host/bluedroid/api/esp_bt_main.c" - "host/bluedroid/api/esp_gap_ble_api.c" - "host/bluedroid/api/esp_gap_bt_api.c" - "host/bluedroid/api/esp_gatt_common_api.c" - "host/bluedroid/api/esp_gattc_api.c" - "host/bluedroid/api/esp_gatts_api.c" - "host/bluedroid/api/esp_hidd_api.c" - "host/bluedroid/api/esp_hidh_api.c" - "host/bluedroid/api/esp_hf_ag_api.c" - "host/bluedroid/api/esp_hf_client_api.c" - "host/bluedroid/api/esp_spp_api.c" - "host/bluedroid/api/esp_sdp_api.c" - "host/bluedroid/api/esp_l2cap_bt_api.c" - "host/bluedroid/api/esp_pbac_api.c" - "host/bluedroid/bta/ar/bta_ar.c" - "host/bluedroid/bta/av/bta_av_aact.c" - "host/bluedroid/bta/av/bta_av_act.c" - "host/bluedroid/bta/av/bta_av_api.c" - "host/bluedroid/bta/av/bta_av_ca_act.c" - "host/bluedroid/bta/av/bta_av_ca_sm.c" - "host/bluedroid/bta/av/bta_av_cfg.c" - "host/bluedroid/bta/av/bta_av_ci.c" - "host/bluedroid/bta/av/bta_av_main.c" - "host/bluedroid/bta/av/bta_av_sbc.c" - "host/bluedroid/bta/av/bta_av_ssm.c" - "host/bluedroid/bta/dm/bta_dm_act.c" - "host/bluedroid/bta/dm/bta_dm_api.c" - "host/bluedroid/bta/dm/bta_dm_cfg.c" - "host/bluedroid/bta/dm/bta_dm_ci.c" - "host/bluedroid/bta/dm/bta_dm_co.c" - "host/bluedroid/bta/dm/bta_dm_main.c" - "host/bluedroid/bta/dm/bta_dm_pm.c" - "host/bluedroid/bta/dm/bta_dm_sco.c" - "host/bluedroid/bta/dm/bta_dm_qos.c" - "host/bluedroid/bta/gatt/bta_gatt_common.c" - "host/bluedroid/bta/gatt/bta_gattc_act.c" - "host/bluedroid/bta/gatt/bta_gattc_api.c" - "host/bluedroid/bta/gatt/bta_gattc_cache.c" - "host/bluedroid/bta/gatt/bta_gattc_ci.c" - "host/bluedroid/bta/gatt/bta_gattc_co.c" - "host/bluedroid/bta/gatt/bta_gattc_main.c" - "host/bluedroid/bta/gatt/bta_gattc_utils.c" - "host/bluedroid/bta/gatt/bta_gatts_act.c" - "host/bluedroid/bta/gatt/bta_gatts_api.c" - "host/bluedroid/bta/gatt/bta_gatts_co.c" - "host/bluedroid/bta/gatt/bta_gatts_main.c" - "host/bluedroid/bta/gatt/bta_gatts_utils.c" - "host/bluedroid/bta/hd/bta_hd_api.c" - "host/bluedroid/bta/hd/bta_hd_act.c" - "host/bluedroid/bta/hd/bta_hd_main.c" - "host/bluedroid/bta/hh/bta_hh_act.c" - "host/bluedroid/bta/hh/bta_hh_api.c" - "host/bluedroid/bta/hh/bta_hh_cfg.c" - "host/bluedroid/bta/hh/bta_hh_le.c" - "host/bluedroid/bta/hh/bta_hh_main.c" - "host/bluedroid/bta/hh/bta_hh_utils.c" - "host/bluedroid/bta/jv/bta_jv_act.c" - "host/bluedroid/bta/jv/bta_jv_api.c" - "host/bluedroid/bta/jv/bta_jv_cfg.c" - "host/bluedroid/bta/jv/bta_jv_main.c" - "host/bluedroid/bta/hf_ag/bta_ag_act.c" - "host/bluedroid/bta/hf_ag/bta_ag_api.c" - "host/bluedroid/bta/hf_ag/bta_ag_at.c" - "host/bluedroid/bta/hf_ag/bta_ag_cfg.c" - "host/bluedroid/bta/hf_ag/bta_ag_cmd.c" - "host/bluedroid/bta/hf_ag/bta_ag_main.c" - "host/bluedroid/bta/hf_ag/bta_ag_rfc.c" - "host/bluedroid/bta/hf_ag/bta_ag_sco.c" - "host/bluedroid/bta/hf_ag/bta_ag_sdp.c" - "host/bluedroid/bta/hf_client/bta_hf_client_act.c" - "host/bluedroid/bta/hf_client/bta_hf_client_api.c" - "host/bluedroid/bta/hf_client/bta_hf_client_at.c" - "host/bluedroid/bta/hf_client/bta_hf_client_cmd.c" - "host/bluedroid/bta/hf_client/bta_hf_client_main.c" - "host/bluedroid/bta/hf_client/bta_hf_client_rfc.c" - "host/bluedroid/bta/hf_client/bta_hf_client_sco.c" - "host/bluedroid/bta/hf_client/bta_hf_client_sdp.c" - "host/bluedroid/bta/pba/bta_pba_client_act.c" - "host/bluedroid/bta/pba/bta_pba_client_api.c" - "host/bluedroid/bta/pba/bta_pba_client_main.c" - "host/bluedroid/bta/pba/bta_pba_client_sdp.c" - "host/bluedroid/bta/sdp/bta_sdp.c" - "host/bluedroid/bta/sdp/bta_sdp_act.c" - "host/bluedroid/bta/sdp/bta_sdp_api.c" - "host/bluedroid/bta/sdp/bta_sdp_cfg.c" - "host/bluedroid/bta/sys/bta_sys_conn.c" - "host/bluedroid/bta/sys/bta_sys_main.c" - "host/bluedroid/bta/sys/utl.c" - "host/bluedroid/btc/core/btc_ble_storage.c" - "host/bluedroid/btc/core/btc_config.c" - "host/bluedroid/btc/core/btc_dev.c" - "host/bluedroid/btc/core/btc_dm.c" - "host/bluedroid/btc/core/btc_main.c" - "host/bluedroid/btc/core/btc_profile_queue.c" - "host/bluedroid/btc/core/btc_sec.c" - "host/bluedroid/btc/core/btc_sm.c" - "host/bluedroid/btc/core/btc_storage.c" - "host/bluedroid/btc/core/btc_util.c" - "host/bluedroid/btc/profile/std/a2dp/bta_av_co.c" - "host/bluedroid/btc/profile/std/a2dp/btc_a2dp.c" - "host/bluedroid/btc/profile/std/a2dp/btc_a2dp_control.c" - "host/bluedroid/btc/profile/std/a2dp/btc_a2dp_sink.c" - "host/bluedroid/btc/profile/std/a2dp/btc_a2dp_sink_ext_coedc.c" - "host/bluedroid/btc/profile/std/a2dp/btc_a2dp_source.c" - "host/bluedroid/btc/profile/std/a2dp/btc_a2dp_source_ext_codec.c" - "host/bluedroid/btc/profile/std/a2dp/btc_av.c" - "host/bluedroid/btc/profile/std/avrc/btc_avrc.c" - "host/bluedroid/btc/profile/std/avrc/bta_avrc_co.c" - "host/bluedroid/btc/profile/std/hf_ag/bta_ag_co.c" - "host/bluedroid/btc/profile/std/hf_ag/btc_hf_ag.c" - "host/bluedroid/btc/profile/std/hf_client/btc_hf_client.c" - "host/bluedroid/btc/profile/std/hf_client/bta_hf_client_co.c" - "host/bluedroid/btc/profile/std/hid/btc_hd.c" - "host/bluedroid/btc/profile/std/hid/btc_hh.c" - "host/bluedroid/btc/profile/std/hid/bta_hh_co.c" - "host/bluedroid/btc/profile/std/gap/btc_gap_ble.c" - "host/bluedroid/btc/profile/std/gap/btc_gap_bt.c" - "host/bluedroid/btc/profile/std/gap/bta_gap_bt_co.c" - "host/bluedroid/btc/profile/std/gatt/btc_gatt_common.c" - "host/bluedroid/btc/profile/std/gatt/btc_gatt_util.c" - "host/bluedroid/btc/profile/std/gatt/btc_gattc.c" - "host/bluedroid/btc/profile/std/gatt/btc_gatts.c" - "host/bluedroid/btc/profile/std/spp/btc_spp.c" - "host/bluedroid/btc/profile/std/sdp/btc_sdp.c" - "host/bluedroid/btc/profile/std/l2cap/btc_l2cap.c" - "host/bluedroid/btc/profile/std/pba/btc_pba_client.c" - "host/bluedroid/device/bdaddr.c" - "host/bluedroid/device/controller.c" - "host/bluedroid/device/interop.c" - "host/bluedroid/hci/hci_audio.c" - "host/bluedroid/hci/hci_hal_h4.c" - "host/bluedroid/hci/hci_layer.c" - "host/bluedroid/hci/hci_packet_factory.c" - "host/bluedroid/hci/hci_packet_parser.c" - "host/bluedroid/hci/packet_fragmenter.c" - "host/bluedroid/main/bte_init.c" - "host/bluedroid/main/bte_main.c" - "host/bluedroid/stack/a2dp/a2d_api.c" - "host/bluedroid/stack/a2dp/a2d_sbc.c" - "host/bluedroid/stack/avct/avct_api.c" - "host/bluedroid/stack/avct/avct_ccb.c" - "host/bluedroid/stack/avct/avct_l2c.c" - "host/bluedroid/stack/avct/avct_lcb.c" - "host/bluedroid/stack/avct/avct_lcb_act.c" - "host/bluedroid/stack/avdt/avdt_ad.c" - "host/bluedroid/stack/avdt/avdt_api.c" - "host/bluedroid/stack/avdt/avdt_ccb.c" - "host/bluedroid/stack/avdt/avdt_ccb_act.c" - "host/bluedroid/stack/avdt/avdt_l2c.c" - "host/bluedroid/stack/avdt/avdt_msg.c" - "host/bluedroid/stack/avdt/avdt_scb.c" - "host/bluedroid/stack/avdt/avdt_scb_act.c" - "host/bluedroid/stack/avrc/avrc_api.c" - "host/bluedroid/stack/avrc/avrc_bld_ct.c" - "host/bluedroid/stack/avrc/avrc_bld_tg.c" - "host/bluedroid/stack/avrc/avrc_opt.c" - "host/bluedroid/stack/avrc/avrc_pars_ct.c" - "host/bluedroid/stack/avrc/avrc_pars_tg.c" - "host/bluedroid/stack/avrc/avrc_sdp.c" - "host/bluedroid/stack/avrc/avrc_utils.c" - "host/bluedroid/stack/hid/hidd_api.c" - "host/bluedroid/stack/hid/hidd_conn.c" - "host/bluedroid/stack/hid/hidh_api.c" - "host/bluedroid/stack/hid/hidh_conn.c" - "host/bluedroid/stack/btm/btm_acl.c" - "host/bluedroid/stack/btm/btm_ble.c" - "host/bluedroid/stack/btm/btm_ble_addr.c" - "host/bluedroid/stack/btm/btm_ble_adv_filter.c" - "host/bluedroid/stack/btm/btm_ble_batchscan.c" - "host/bluedroid/stack/btm/btm_ble_bgconn.c" - "host/bluedroid/stack/btm/btm_ble_cont_energy.c" - "host/bluedroid/stack/btm/btm_ble_gap.c" - "host/bluedroid/stack/btm/btm_ble_5_gap.c" - "host/bluedroid/stack/btm/btm_ble_multi_adv.c" - "host/bluedroid/stack/btm/btm_ble_privacy.c" - "host/bluedroid/stack/btm/btm_dev.c" - "host/bluedroid/stack/btm/btm_devctl.c" - "host/bluedroid/stack/btm/btm_inq.c" - "host/bluedroid/stack/btm/btm_bredr_pwr_ctrl.c" - "host/bluedroid/stack/btm/btm_main.c" - "host/bluedroid/stack/btm/btm_pm.c" - "host/bluedroid/stack/btm/btm_sco.c" - "host/bluedroid/stack/btm/btm_sec.c" - "host/bluedroid/stack/btu/btu_hcif.c" - "host/bluedroid/stack/btu/btu_init.c" - "host/bluedroid/stack/btu/btu_task.c" - "host/bluedroid/stack/gap/gap_api.c" - "host/bluedroid/stack/gap/gap_ble.c" - "host/bluedroid/stack/gap/gap_conn.c" - "host/bluedroid/stack/gap/gap_utils.c" - "host/bluedroid/stack/gatt/att_protocol.c" - "host/bluedroid/stack/gatt/gatt_api.c" - "host/bluedroid/stack/gatt/gatt_attr.c" - "host/bluedroid/stack/gatt/gatt_auth.c" - "host/bluedroid/stack/gatt/gatt_cl.c" - "host/bluedroid/stack/gatt/gatt_db.c" - "host/bluedroid/stack/gatt/gatt_main.c" - "host/bluedroid/stack/gatt/gatt_sr.c" - "host/bluedroid/stack/gatt/gatt_sr_hash.c" - "host/bluedroid/stack/gatt/gatt_utils.c" - "host/bluedroid/stack/goep/goepc_api.c" - "host/bluedroid/stack/goep/goepc_main.c" - "host/bluedroid/stack/hcic/hciblecmds.c" - "host/bluedroid/stack/hcic/hcicmds.c" - "host/bluedroid/stack/l2cap/l2c_api.c" - "host/bluedroid/stack/l2cap/l2c_ble.c" - "host/bluedroid/stack/l2cap/l2c_csm.c" - "host/bluedroid/stack/l2cap/l2c_fcr.c" - "host/bluedroid/stack/l2cap/l2c_link.c" - "host/bluedroid/stack/l2cap/l2c_main.c" - "host/bluedroid/stack/l2cap/l2c_ucd.c" - "host/bluedroid/stack/l2cap/l2c_utils.c" - "host/bluedroid/stack/l2cap/l2cap_client.c" - "host/bluedroid/stack/obex/obex_api.c" - "host/bluedroid/stack/obex/obex_main.c" - "host/bluedroid/stack/obex/obex_tl_l2cap.c" - "host/bluedroid/stack/obex/obex_tl_rfcomm.c" - "host/bluedroid/stack/rfcomm/port_api.c" - "host/bluedroid/stack/rfcomm/port_rfc.c" - "host/bluedroid/stack/rfcomm/port_utils.c" - "host/bluedroid/stack/rfcomm/rfc_l2cap_if.c" - "host/bluedroid/stack/rfcomm/rfc_mx_fsm.c" - "host/bluedroid/stack/rfcomm/rfc_port_fsm.c" - "host/bluedroid/stack/rfcomm/rfc_port_if.c" - "host/bluedroid/stack/rfcomm/rfc_ts_frames.c" - "host/bluedroid/stack/rfcomm/rfc_utils.c" - "host/bluedroid/stack/sdp/sdp_api.c" - "host/bluedroid/stack/sdp/sdp_db.c" - "host/bluedroid/stack/sdp/sdp_discovery.c" - "host/bluedroid/stack/sdp/sdp_main.c" - "host/bluedroid/stack/sdp/sdp_server.c" - "host/bluedroid/stack/sdp/sdp_utils.c" - "host/bluedroid/stack/smp/aes.c" - "host/bluedroid/stack/smp/p_256_curvepara.c" - "host/bluedroid/stack/smp/p_256_ecc_pp.c" - "host/bluedroid/stack/smp/p_256_multprecision.c" - "host/bluedroid/stack/smp/smp_act.c" - "host/bluedroid/stack/smp/smp_api.c" - "host/bluedroid/stack/smp/smp_br_main.c" - "host/bluedroid/stack/smp/smp_cmac.c" - "host/bluedroid/stack/smp/smp_keys.c" - "host/bluedroid/stack/smp/smp_l2c.c" - "host/bluedroid/stack/smp/smp_main.c" - "host/bluedroid/stack/smp/smp_utils.c" - "host/bluedroid/config/stack_config.c") - - - list(APPEND srcs "common/btc/profile/esp/blufi/bluedroid_host/esp_blufi.c") - - if(CONFIG_BLE_MESH) - list(APPEND srcs "esp_ble_mesh/core/bluedroid_host/adapter.c") - endif() - - if(CONFIG_BT_BLE_FEAT_ISO_EN) - list(APPEND srcs "host/bluedroid/stack/btm/btm_ble_iso.c" - "host/bluedroid/btc/profile/std/iso/btc_iso_ble.c" - "host/bluedroid/api/esp_ble_iso_api.c" - "host/bluedroid/hci/ble_hci_iso.c") - endif() - - if(CONFIG_BT_BLE_FEAT_CTE_EN) - list(APPEND srcs "host/bluedroid/stack/btm/btm_ble_cte.c" - "host/bluedroid/btc/profile/std/cte/btc_ble_cte.c" - "host/bluedroid/api/esp_ble_cte_api.c") - endif() - - if((CONFIG_BT_A2DP_ENABLE AND NOT CONFIG_BT_A2DP_USE_EXTERNAL_CODEC) OR - (CONFIG_BT_HFP_ENABLE AND CONFIG_BT_HFP_AUDIO_DATA_PATH_HCI AND NOT CONFIG_BT_HFP_USE_EXTERNAL_CODEC)) - list(APPEND priv_include_dirs - host/bluedroid/external/sbc/decoder/include - host/bluedroid/external/sbc/encoder/include - host/bluedroid/external/sbc/plc/include) - - list(APPEND srcs "host/bluedroid/external/sbc/decoder/srce/alloc.c" - "host/bluedroid/external/sbc/decoder/srce/bitalloc-sbc.c" - "host/bluedroid/external/sbc/decoder/srce/bitalloc.c" - "host/bluedroid/external/sbc/decoder/srce/bitstream-decode.c" - "host/bluedroid/external/sbc/decoder/srce/decoder-oina.c" - "host/bluedroid/external/sbc/decoder/srce/decoder-private.c" - "host/bluedroid/external/sbc/decoder/srce/decoder-sbc.c" - "host/bluedroid/external/sbc/decoder/srce/dequant.c" - "host/bluedroid/external/sbc/decoder/srce/framing-sbc.c" - "host/bluedroid/external/sbc/decoder/srce/framing.c" - "host/bluedroid/external/sbc/decoder/srce/oi_codec_version.c" - "host/bluedroid/external/sbc/decoder/srce/synthesis-8-generated.c" - "host/bluedroid/external/sbc/decoder/srce/synthesis-dct8.c" - "host/bluedroid/external/sbc/decoder/srce/synthesis-sbc.c" - "host/bluedroid/external/sbc/encoder/srce/sbc_analysis.c" - "host/bluedroid/external/sbc/encoder/srce/sbc_dct.c" - "host/bluedroid/external/sbc/encoder/srce/sbc_dct_coeffs.c" - "host/bluedroid/external/sbc/encoder/srce/sbc_enc_bit_alloc_mono.c" - "host/bluedroid/external/sbc/encoder/srce/sbc_enc_bit_alloc_ste.c" - "host/bluedroid/external/sbc/encoder/srce/sbc_enc_coeffs.c" - "host/bluedroid/external/sbc/encoder/srce/sbc_encoder.c" - "host/bluedroid/external/sbc/encoder/srce/sbc_packing.c" - "host/bluedroid/external/sbc/plc/sbc_plc.c") - endif() - - endif() - - if(CONFIG_BLE_MESH) - - if(CONFIG_BLE_MESH_USE_UNIFIED_CRYPTO) - message(WARNING "This configuration path is deprecated and will be removed" - "in a future version. Please use the corresponding Kconfig" - "options under $IDF_PATH/components/bt/common/Kconfig.") - endif() - - list(APPEND include_dirs ${ble_mesh_include_dirs}) - - list(APPEND srcs "esp_ble_mesh/api/core/esp_ble_mesh_ble_api.c" - "esp_ble_mesh/api/core/esp_ble_mesh_common_api.c" - "esp_ble_mesh/api/core/esp_ble_mesh_local_data_operation_api.c" - "esp_ble_mesh/api/core/esp_ble_mesh_low_power_api.c" - "esp_ble_mesh/api/core/esp_ble_mesh_networking_api.c" - "esp_ble_mesh/api/core/esp_ble_mesh_provisioning_api.c" - "esp_ble_mesh/api/core/esp_ble_mesh_proxy_api.c" - "esp_ble_mesh/api/models/esp_ble_mesh_config_model_api.c" - "esp_ble_mesh/api/models/esp_ble_mesh_generic_model_api.c" - "esp_ble_mesh/api/models/esp_ble_mesh_health_model_api.c" - "esp_ble_mesh/api/models/esp_ble_mesh_lighting_model_api.c" - "esp_ble_mesh/api/models/esp_ble_mesh_sensor_model_api.c" - "esp_ble_mesh/api/models/esp_ble_mesh_time_scene_model_api.c" - "esp_ble_mesh/btc/btc_ble_mesh_ble.c" - "esp_ble_mesh/btc/btc_ble_mesh_config_model.c" - "esp_ble_mesh/btc/btc_ble_mesh_generic_model.c" - "esp_ble_mesh/btc/btc_ble_mesh_health_model.c" - "esp_ble_mesh/btc/btc_ble_mesh_lighting_model.c" - "esp_ble_mesh/btc/btc_ble_mesh_prov.c" - "esp_ble_mesh/btc/btc_ble_mesh_sensor_model.c" - "esp_ble_mesh/btc/btc_ble_mesh_time_scene_model.c" - "esp_ble_mesh/common/atomic.c" - "esp_ble_mesh/common/buf.c" - "esp_ble_mesh/common/common.c" - "esp_ble_mesh/common/kernel.c" - "esp_ble_mesh/common/mutex.c" - "esp_ble_mesh/common/queue.c" - "esp_ble_mesh/common/timer.c" - "esp_ble_mesh/common/utils.c") - - # Select crypto implementation based on config - if(CONFIG_BT_SMP_CRYPTO_STACK_TINYCRYPT) - list(APPEND srcs "esp_ble_mesh/common/crypto_tc.c") - elseif(CONFIG_BT_SMP_CRYPTO_STACK_MBEDTLS) - if(CONFIG_MBEDTLS_VER_4_X_SUPPORT) - list(APPEND srcs "esp_ble_mesh/common/crypto_psa.c") - else() - list(APPEND srcs "esp_ble_mesh/common/crypto_mbedtls.c") - endif() - endif() - - list(APPEND srcs "esp_ble_mesh/core/storage/settings_nvs.c" - "esp_ble_mesh/core/storage/settings_uid.c" - "esp_ble_mesh/core/storage/settings.c" - "esp_ble_mesh/core/access.c" - "esp_ble_mesh/core/adv_common.c" - "esp_ble_mesh/core/beacon.c" - "esp_ble_mesh/core/cfg_cli.c" - "esp_ble_mesh/core/cfg_srv.c" - "esp_ble_mesh/core/crypto.c" - "esp_ble_mesh/core/fast_prov.c" - "esp_ble_mesh/core/friend.c" - "esp_ble_mesh/core/health_cli.c" - "esp_ble_mesh/core/health_srv.c" - "esp_ble_mesh/core/heartbeat.c" - "esp_ble_mesh/core/local.c" - "esp_ble_mesh/core/lpn.c" - "esp_ble_mesh/core/main.c" - "esp_ble_mesh/core/net.c" - "esp_ble_mesh/core/prov_common.c" - "esp_ble_mesh/core/prov_node.c" - "esp_ble_mesh/core/prov_pvnr.c" - "esp_ble_mesh/core/proxy_client.c" - "esp_ble_mesh/core/proxy_server.c" - "esp_ble_mesh/core/pvnr_mgmt.c" - "esp_ble_mesh/core/rpl.c" - "esp_ble_mesh/core/scan.c" - "esp_ble_mesh/core/test.c" - "esp_ble_mesh/models/common/device_property.c" - "esp_ble_mesh/models/common/model_common.c" - "esp_ble_mesh/models/client/client_common.c" - "esp_ble_mesh/models/client/generic_client.c" - "esp_ble_mesh/models/client/lighting_client.c" - "esp_ble_mesh/models/client/sensor_client.c" - "esp_ble_mesh/models/client/time_scene_client.c" - "esp_ble_mesh/models/server/generic_server.c" - "esp_ble_mesh/models/server/lighting_server.c" - "esp_ble_mesh/models/server/sensor_server.c" - "esp_ble_mesh/models/server/server_common.c" - "esp_ble_mesh/models/server/state_binding.c" - "esp_ble_mesh/models/server/state_transition.c" - "esp_ble_mesh/models/server/time_scene_server.c") - - if(CONFIG_BLE_MESH_V11_SUPPORT) - list(APPEND include_dirs ${ble_mesh_v11_include_dirs}) - - list(APPEND srcs - "esp_ble_mesh/v1.1/api/core/esp_ble_mesh_agg_model_api.c" - "esp_ble_mesh/v1.1/api/core/esp_ble_mesh_brc_model_api.c" - "esp_ble_mesh/v1.1/api/core/esp_ble_mesh_cm_data_api.c" - "esp_ble_mesh/v1.1/api/core/esp_ble_mesh_df_model_api.c" - "esp_ble_mesh/v1.1/api/core/esp_ble_mesh_lcd_model_api.c" - "esp_ble_mesh/v1.1/api/core/esp_ble_mesh_odp_model_api.c" - "esp_ble_mesh/v1.1/api/core/esp_ble_mesh_prb_model_api.c" - "esp_ble_mesh/v1.1/api/core/esp_ble_mesh_rpr_model_api.c" - "esp_ble_mesh/v1.1/api/core/esp_ble_mesh_sar_model_api.c" - "esp_ble_mesh/v1.1/api/core/esp_ble_mesh_srpl_model_api.c" - "esp_ble_mesh/v1.1/api/models/esp_ble_mesh_mbt_model_api.c" - "esp_ble_mesh/v1.1/api/models/esp_ble_mesh_dfu_model_api.c" - "esp_ble_mesh/v1.1/api/models/esp_ble_mesh_dfu_slot_api.c" - "esp_ble_mesh/v1.1/btc/btc_ble_mesh_agg_model.c" - "esp_ble_mesh/v1.1/btc/btc_ble_mesh_brc_model.c" - "esp_ble_mesh/v1.1/btc/btc_ble_mesh_df_model.c" - "esp_ble_mesh/v1.1/btc/btc_ble_mesh_dfu_model.c" - "esp_ble_mesh/v1.1/btc/btc_ble_mesh_dfu_slot.c" - "esp_ble_mesh/v1.1/btc/btc_ble_mesh_lcd_model.c" - "esp_ble_mesh/v1.1/btc/btc_ble_mesh_mbt_model.c" - "esp_ble_mesh/v1.1/btc/btc_ble_mesh_odp_model.c" - "esp_ble_mesh/v1.1/btc/btc_ble_mesh_prb_model.c" - "esp_ble_mesh/v1.1/btc/btc_ble_mesh_rpr_model.c" - "esp_ble_mesh/v1.1/btc/btc_ble_mesh_sar_model.c" - "esp_ble_mesh/v1.1/btc/btc_ble_mesh_srpl_model.c" - "esp_ble_mesh/v1.1/mbt/blob_srv.c" - "esp_ble_mesh/v1.1/mbt/blob_cli.c" - "esp_ble_mesh/v1.1/dfu/dfu_cli.c" - "esp_ble_mesh/v1.1/dfu/dfu_srv.c" - "esp_ble_mesh/v1.1/dfu/dfu_slot.c" - "esp_ble_mesh/v1.1/dfu/dfu_metadata.c" - "esp_ble_mesh/v1.1/dfu/dfd_srv.c" - "esp_ble_mesh/v1.1/dfu/dfd_cli.c" - "esp_ble_mesh/lib/ext.c") - - if(CONFIG_BLE_MESH_SAR_ENHANCEMENT) - list(APPEND srcs "esp_ble_mesh/core/transport.enh.c") - else() - list(APPEND srcs "esp_ble_mesh/core/transport.c") - endif() - else() - list(APPEND srcs "esp_ble_mesh/core/transport.c") - endif() - - if(CONFIG_BLE_MESH_SUPPORT_MULTI_ADV) - list(APPEND srcs "esp_ble_mesh/core/ext_adv.c") - else() - list(APPEND srcs "esp_ble_mesh/core/adv.c") - endif() - - if(CONFIG_BLE_MESH_SUPPORT_BLE_ADV) - list(APPEND srcs "esp_ble_mesh/core/ble_adv.c") - endif() - endif() - - if(CONFIG_BT_LE_CONTROLLER_NPL_OS_PORTING_SUPPORT) - list(APPEND srcs - "porting/npl/freertos/src/npl_os_freertos.c" - "porting/mem/os_msys_init.c" - "porting/mem/os_mempool.c" - "porting/transport/src/hci_transport.c" - ) - - list(APPEND include_dirs - host/nimble/port/include - ) - - if(CONFIG_BT_CONTROLLER_DISABLED) - list(APPEND srcs - "host/nimble/nimble/porting/nimble/src/hal_uart.c" - ) - elseif(CONFIG_BT_LE_HCI_INTERFACE_USE_RAM) - if(CONFIG_BT_NIMBLE_ENABLED) - list(APPEND srcs - "porting/transport/driver/vhci/hci_driver_nimble.c" - "host/nimble/nimble/nimble/transport/esp_ipc/src/hci_esp_ipc.c" - ) - else() - list(APPEND srcs - "porting/transport/driver/vhci/hci_driver_standard.c" - ) - endif() - elseif(CONFIG_BT_LE_HCI_INTERFACE_USE_UART) - list(APPEND srcs - "porting/transport/driver/common/hci_driver_util.c" - "porting/transport/driver/common/hci_driver_h4.c" - "porting/transport/driver/common/hci_driver_mem.c" - "porting/transport/driver/uart/hci_driver_uart_config.c" - ) - if(CONFIG_BT_LE_UART_HCI_DMA_MODE) - list(APPEND srcs - "porting/transport/driver/uart/hci_driver_uart_dma.c" - ) - else() - list(APPEND srcs - "porting/transport/driver/uart/hci_driver_uart.c" - ) - endif() - endif() - - list(APPEND include_dirs - porting/include - porting/npl/freertos/include - porting/transport/include - ) - endif() - - # Compile TinyCrypt if: - # 1. Controller uses TinyCrypt (not mbedTLS), OR - # 2. NimBLE uses TinyCrypt (not mbedTLS), OR - # 3. Bluedroid Host SMP uses TinyCrypt - if(CONFIG_BT_SMP_CRYPTO_STACK_TINYCRYPT OR NOT CONFIG_BT_NIMBLE_CRYPTO_STACK_MBEDTLS) - list(APPEND include_dirs - common/tinycrypt/include - common/tinycrypt/port - ) - list(APPEND srcs "common/tinycrypt/src/utils.c" - "common/tinycrypt/src/sha256.c" - "common/tinycrypt/src/ecc.c" - "common/tinycrypt/src/ctr_prng.c" - "common/tinycrypt/src/ctr_mode.c" - "common/tinycrypt/src/aes_decrypt.c" - "common/tinycrypt/src/aes_encrypt.c" - "common/tinycrypt/src/ccm_mode.c" - "common/tinycrypt/src/ecc_dsa.c" - "common/tinycrypt/src/cmac_mode.c" - "common/tinycrypt/src/ecc_dh.c" - "common/tinycrypt/src/hmac_prng.c" - "common/tinycrypt/src/ecc_platform_specific.c" - "common/tinycrypt/src/hmac.c" - "common/tinycrypt/src/cbc_mode.c" - "common/tinycrypt/port/esp_tinycrypt_port.c") - endif() - - if(CONFIG_BT_NIMBLE_ENABLED) - - list(APPEND include_dirs - host/nimble/nimble/nimble/host/include - host/nimble/nimble/nimble/include - host/nimble/nimble/nimble/host/services/ans/include - host/nimble/nimble/nimble/host/services/bas/include - host/nimble/nimble/nimble/host/services/dis/include - host/nimble/nimble/nimble/host/services/gap/include - host/nimble/nimble/nimble/host/services/gatt/include - host/nimble/nimble/nimble/host/services/hr/include - host/nimble/nimble/nimble/host/services/htp/include - host/nimble/nimble/nimble/host/services/ias/include - host/nimble/nimble/nimble/host/services/ipss/include - host/nimble/nimble/nimble/host/services/lls/include - host/nimble/nimble/nimble/host/services/prox/include - host/nimble/nimble/nimble/host/services/cts/include - host/nimble/nimble/nimble/host/services/tps/include - host/nimble/nimble/nimble/host/services/hid/include - host/nimble/nimble/nimble/host/services/sps/include - host/nimble/nimble/nimble/host/services/cte/include - host/nimble/nimble/nimble/host/util/include - host/nimble/nimble/nimble/host/store/ram/include - host/nimble/nimble/nimble/host/store/config/include - host/nimble/nimble/nimble/host/services/ras/include - ) - - list(APPEND srcs "host/nimble/nimble/nimble/transport/src/transport.c" - "host/nimble/nimble/nimble/host/util/src/addr.c" - "host/nimble/nimble/nimble/host/services/gatt/src/ble_svc_gatt.c" - "host/nimble/nimble/nimble/host/services/tps/src/ble_svc_tps.c" - "host/nimble/nimble/nimble/host/services/ias/src/ble_svc_ias.c" - "host/nimble/nimble/nimble/host/services/ipss/src/ble_svc_ipss.c" - "host/nimble/nimble/nimble/host/services/ans/src/ble_svc_ans.c" - "host/nimble/nimble/nimble/host/services/hr/src/ble_svc_hr.c" - "host/nimble/nimble/nimble/host/services/htp/src/ble_svc_htp.c" - "host/nimble/nimble/nimble/host/services/gap/src/ble_svc_gap.c" - "host/nimble/nimble/nimble/host/services/bas/src/ble_svc_bas.c" - "host/nimble/nimble/nimble/host/services/dis/src/ble_svc_dis.c" - "host/nimble/nimble/nimble/host/services/lls/src/ble_svc_lls.c" - "host/nimble/nimble/nimble/host/services/prox/src/ble_svc_prox.c" - "host/nimble/nimble/nimble/host/services/cts/src/ble_svc_cts.c" - "host/nimble/nimble/nimble/host/services/hid/src/ble_svc_hid.c" - "host/nimble/nimble/nimble/host/services/sps/src/ble_svc_sps.c" - "host/nimble/nimble/nimble/host/services/cte/src/ble_svc_cte.c" - "host/nimble/nimble/nimble/host/services/ras/src/ble_svc_ras.c" - "host/nimble/nimble/nimble/host/src/ble_cs.c" - "host/nimble/nimble/nimble/host/src/ble_hs_conn.c" - "host/nimble/nimble/nimble/host/src/ble_store_util.c" - "host/nimble/nimble/nimble/host/src/ble_sm.c" - "host/nimble/nimble/nimble/host/src/ble_hs_shutdown.c" - "host/nimble/nimble/nimble/host/src/ble_l2cap_sig_cmd.c" - "host/nimble/nimble/nimble/host/src/ble_hs_hci_cmd.c" - "host/nimble/nimble/nimble/host/src/ble_hs_id.c" - "host/nimble/nimble/nimble/host/src/ble_att_svr.c" - "host/nimble/nimble/nimble/host/src/ble_gatts_lcl.c" - "host/nimble/nimble/nimble/host/src/ble_ibeacon.c" - "host/nimble/nimble/nimble/host/src/ble_hs_atomic.c" - "host/nimble/nimble/nimble/host/src/ble_sm_alg.c" - "host/nimble/nimble/nimble/host/src/ble_hs_stop.c" - "host/nimble/nimble/nimble/host/src/ble_hs.c" - "host/nimble/nimble/nimble/host/src/ble_hs_hci_evt.c" - "host/nimble/nimble/nimble/host/src/ble_hs_mqueue.c" - "host/nimble/nimble/nimble/host/src/ble_hs_periodic_sync.c" - "host/nimble/nimble/nimble/host/src/ble_att.c" - "host/nimble/nimble/nimble/host/src/ble_ead.c" - "host/nimble/nimble/nimble/host/src/ble_aes_ccm.c" - "host/nimble/nimble/nimble/host/src/ble_gattc.c" - "host/nimble/nimble/nimble/host/src/ble_store.c" - "host/nimble/nimble/nimble/host/src/ble_sm_lgcy.c" - "host/nimble/nimble/nimble/host/src/ble_hs_cfg.c" - "host/nimble/nimble/nimble/host/src/ble_att_clt.c" - "host/nimble/nimble/nimble/host/src/ble_l2cap_coc.c" - "host/nimble/nimble/nimble/host/src/ble_hs_mbuf.c" - "host/nimble/nimble/nimble/host/src/ble_att_cmd.c" - "host/nimble/nimble/nimble/host/src/ble_hs_log.c" - "host/nimble/nimble/nimble/host/src/ble_eddystone.c" - "host/nimble/nimble/nimble/host/src/ble_hs_startup.c" - "host/nimble/nimble/nimble/host/src/ble_l2cap_sig.c" - "host/nimble/nimble/nimble/host/src/ble_gap.c" - "host/nimble/nimble/nimble/host/src/ble_sm_cmd.c" - "host/nimble/nimble/nimble/host/src/ble_uuid.c" - "host/nimble/nimble/nimble/host/src/ble_hs_pvcy.c" - "host/nimble/nimble/nimble/host/src/ble_hs_flow.c" - "host/nimble/nimble/nimble/host/src/ble_l2cap.c" - "host/nimble/nimble/nimble/host/src/ble_sm_sc.c" - "host/nimble/nimble/nimble/host/src/ble_hs_misc.c" - "host/nimble/nimble/nimble/host/src/ble_gatts.c" - "host/nimble/nimble/nimble/host/src/ble_hs_adv.c" - "host/nimble/nimble/nimble/host/src/ble_hs_hci.c" - "host/nimble/nimble/nimble/host/src/ble_hs_hci_util.c" - "host/nimble/nimble/nimble/host/src/ble_hs_resolv.c" - "host/nimble/nimble/nimble/host/store/ram/src/ble_store_ram.c" - "host/nimble/nimble/nimble/host/store/config/src/ble_store_config.c" - "host/nimble/nimble/nimble/host/store/config/src/ble_store_nvs.c" - "host/nimble/nimble/nimble/host/src/ble_gattc_cache.c" - "host/nimble/nimble/nimble/host/src/ble_gattc_cache_conn.c" - "host/nimble/nimble/nimble/host/src/ble_eatt.c" - ) - - if(CONFIG_BT_NIMBLE_ISO) - list(APPEND srcs - "host/nimble/nimble/nimble/host/src/ble_hs_iso_hci.c" - "host/nimble/nimble/nimble/host/src/ble_hs_iso.c" - ) - endif() - - if(CONFIG_BT_CONTROLLER_DISABLED AND CONFIG_BT_NIMBLE_TRANSPORT_UART) - list(APPEND srcs - "host/nimble/nimble/nimble/transport/uart_ll/src/hci_uart.c" - "host/nimble/nimble/nimble/transport/common/hci_h4/src/hci_h4.c" - ) - endif() - - list(APPEND srcs - "host/nimble/nimble/porting/nimble/src/nimble_port.c" - "host/nimble/nimble/porting/npl/freertos/src/nimble_port_freertos.c" - "host/nimble/port/src/nvs_port.c" - "host/nimble/port/src/esp_nimble_mem.c" - ) - - list(APPEND include_dirs - host/nimble/nimble/porting/nimble/include - host/nimble/port/include - host/nimble/nimble/nimble/transport/include - host/nimble/nimble/nimble/include - ) - - if(CONFIG_BT_CONTROLLER_DISABLED) - list(APPEND include_dirs - host/nimble/nimble/nimble/transport/common/hci_h4/include - ) - endif() - - if(NOT CONFIG_BT_LE_CONTROLLER_NPL_OS_PORTING_SUPPORT) - list(APPEND srcs - "host/nimble/nimble/porting/nimble/src/endian.c" - "porting/mem/os_mempool.c" - "host/nimble/nimble/porting/nimble/src/mem.c" - "host/nimble/nimble/porting/nimble/src/os_mbuf.c" - "host/nimble/nimble/porting/nimble/src/os_msys_init.c" - "host/nimble/nimble/porting/npl/freertos/src/npl_os_freertos.c" - ) - - if(CONFIG_BT_CONTROLLER_DISABLED AND CONFIG_BT_NIMBLE_TRANSPORT_UART) - list(APPEND srcs - "host/nimble/nimble/porting/nimble/src/hal_uart.c" - ) - endif() - - list(APPEND include_dirs - porting/include - host/nimble/nimble/porting/npl/freertos/include - ) - endif() - - if(CONFIG_BT_NIMBLE_LEGACY_VHCI_ENABLE AND CONFIG_BT_CONTROLLER_ENABLED) - list(APPEND srcs - "host/nimble/esp-hci/src/esp_nimble_hci.c" - "host/nimble/nimble/nimble/transport/esp_ipc_legacy/src/hci_esp_ipc_legacy.c" - ) - list(APPEND include_dirs ${nimble_hci_include_dirs}) - endif() - - list(APPEND srcs - "common/btc/profile/esp/blufi/nimble_host/esp_blufi.c") - - if(CONFIG_BLE_MESH) - list(APPEND srcs "esp_ble_mesh/core/nimble_host/adapter.c") - endif() - - if(CONFIG_BT_NIMBLE_MESH) - - list(APPEND include_dirs - host/nimble/nimble/nimble/host/mesh/include - host/nimble/nimble/nimble/host/include/host) - - list(APPEND srcs "host/nimble/nimble/nimble/host/mesh/src/shell.c" - "host/nimble/nimble/nimble/host/mesh/src/friend.c" - "host/nimble/nimble/nimble/host/mesh/src/crypto.c" - "host/nimble/nimble/nimble/host/mesh/src/settings.c" - "host/nimble/nimble/nimble/host/mesh/src/adv.c" - "host/nimble/nimble/nimble/host/mesh/src/adv_ext.c" - "host/nimble/nimble/nimble/host/mesh/src/adv_legacy.c" - "host/nimble/nimble/nimble/host/mesh/src/model_srv.c" - "host/nimble/nimble/nimble/host/mesh/src/msg.c" - "host/nimble/nimble/nimble/host/mesh/src/beacon.c" - "host/nimble/nimble/nimble/host/mesh/src/glue.c" - "host/nimble/nimble/nimble/host/mesh/src/model_cli.c" - "host/nimble/nimble/nimble/host/mesh/src/transport.c" - "host/nimble/nimble/nimble/host/mesh/src/prov.c" - "host/nimble/nimble/nimble/host/mesh/src/mesh.c" - "host/nimble/nimble/nimble/host/mesh/src/access.c" - "host/nimble/nimble/nimble/host/mesh/src/cfg_srv.c" - "host/nimble/nimble/nimble/host/mesh/src/cfg_cli.c" - "host/nimble/nimble/nimble/host/mesh/src/light_model.c" - "host/nimble/nimble/nimble/host/mesh/src/health_cli.c" - "host/nimble/nimble/nimble/host/mesh/src/lpn.c" - "host/nimble/nimble/nimble/host/mesh/src/health_srv.c" - "host/nimble/nimble/nimble/host/mesh/src/testing.c" - "host/nimble/nimble/nimble/host/mesh/src/aes-ccm.c" - "host/nimble/nimble/nimble/host/mesh/src/app_keys.c" - "host/nimble/nimble/nimble/host/mesh/src/cdb.c" - "host/nimble/nimble/nimble/host/mesh/src/cfg.c" - "host/nimble/nimble/nimble/host/mesh/src/pb_adv.c" - "host/nimble/nimble/nimble/host/mesh/src/pb_gatt.c" - "host/nimble/nimble/nimble/host/mesh/src/pb_gatt_srv.c" - "host/nimble/nimble/nimble/host/mesh/src/prov_device.c" - "host/nimble/nimble/nimble/host/mesh/src/provisioner.c" - "host/nimble/nimble/nimble/host/mesh/src/heartbeat.c" - "host/nimble/nimble/nimble/host/mesh/src/rpl.c" - "host/nimble/nimble/nimble/host/mesh/src/subnet.c" - "host/nimble/nimble/nimble/host/mesh/src/proxy_msg.c" - "host/nimble/nimble/nimble/host/mesh/src/proxy_srv.c" - "host/nimble/nimble/nimble/host/mesh/src/net.c") - endif() - endif() + # BLE Mesh + add_subdirectory(esp_ble_mesh) + list(APPEND srcs ${ble_mesh_srcs}) + list(APPEND include_dirs ${ble_mesh_include_dirs}) + # When log compression is enabled, selected logs are replaced + # by auto-generated macros that emit pre-encoded data. + # This eliminates the original format strings, reducing firmware size and + # removing runtime formatting overhead, so logs are produced faster and + # with less system impact. + replace_log_compression_srcs() endif() - set(bt_priv_requires nvs_flash soc @@ -986,33 +83,6 @@ set(bt_priv_requires esp_security ) -if(CONFIG_BLE_COMPRESSED_LOG_ENABLE) - set(CODE_BASE_PATH "${CMAKE_CURRENT_SOURCE_DIR}") - # When log compression is enabled, selected logs are replaced - # by auto-generated macros that emit pre-encoded data. - # This eliminates the original format strings, reducing firmware size and - # removing runtime formatting overhead, so logs are produced faster and - # with less system impact. - add_subdirectory(common/ble_log/extension/log_compression) - if(LOG_COMPRESSION_TARGET) - set(srcs ${LOG_COMPRESS_SRCS}) - set(include_dirs ${LOG_COMPRESS_INCLUDE_DIRS}) - # LOG_COMPRESS_FILES_WITH_FLAGS format: "file_path|compile_flags" - if(LOG_COMPRESS_FILES_WITH_FLAGS) - foreach(file_entry ${LOG_COMPRESS_FILES_WITH_FLAGS}) - # Split by '|' to get file path and flags - string(REPLACE "|" ";" file_parts "${file_entry}") - list(GET file_parts 0 file_path) - list(GET file_parts 1 compile_flags) - set_source_files_properties(${file_path} - PROPERTIES COMPILE_FLAGS "${compile_flags}") - endforeach() - endif() - else() - list(APPEND include_dirs ${LOG_COMPRESS_INCLUDE_DIRS}) - endif() -endif() - idf_component_register(SRCS "${srcs}" INCLUDE_DIRS "${include_dirs}" PRIV_INCLUDE_DIRS "${priv_include_dirs}" @@ -1020,137 +90,22 @@ idf_component_register(SRCS "${srcs}" PRIV_REQUIRES "${bt_priv_requires}" LDFRAGMENTS "${ldscripts}") -# UART redir wrap flags — needed whenever BLE Log uses UART DMA on port 0, -# regardless of whether BLE controller is enabled. -if(DEFINED CONFIG_BLE_LOG_PRPH_UART_DMA_PORT) - if(CONFIG_BLE_LOG_PRPH_UART_DMA_PORT EQUAL 0) - target_link_libraries(${COMPONENT_LIB} INTERFACE "-Wl,--wrap=uart_tx_chars") - target_link_libraries(${COMPONENT_LIB} INTERFACE "-Wl,--wrap=uart_write_bytes") - target_link_libraries(${COMPONENT_LIB} INTERFACE "-Wl,--wrap=uart_write_bytes_with_break") - endif() -endif() - -if(CONFIG_BLE_COMPRESSED_LOG_ENABLE) - if(LOG_COMPRESSION_TARGET) - add_dependencies(${COMPONENT_LIB} ${LOG_COMPRESSION_TARGET}) - endif() +idf_component_get_property(bt_component_type bt COMPONENT_TYPE) +if(bt_component_type STREQUAL "LIBRARY") + idf_component_optional_requires(PRIVATE ble_insights) +else() + idf_component_optional_requires(INTERFACE ble_insights) endif() if(CONFIG_BT_ENABLED) target_compile_options(${COMPONENT_LIB} PRIVATE -Wno-implicit-fallthrough -Wno-unused-const-variable) - if(CONFIG_IDF_TARGET_ESP32) - add_prebuilt_library(bt_btdm_app "${CMAKE_CURRENT_LIST_DIR}/controller/lib_esp32/esp32/libbtdm_app.a") - target_link_libraries(${COMPONENT_LIB} PRIVATE bt_btdm_app) - target_link_options(${COMPONENT_LIB} INTERFACE "SHELL:-u ld_include_hli_vectors_bt") - elseif(CONFIG_IDF_TARGET_ESP32C3) - if(CONFIG_BT_CTRL_RUN_IN_FLASH_ONLY) - set(lib_name "btdm_app_flash") - else() - set(lib_name "btdm_app") - endif() - add_prebuilt_library(bt_btdm_app - "${CMAKE_CURRENT_LIST_DIR}/controller/lib_esp32c3_family/esp32c3/lib${lib_name}.a") - target_link_libraries(${COMPONENT_LIB} PRIVATE bt_btdm_app) - elseif(CONFIG_IDF_TARGET_ESP32S3) - if(CONFIG_BT_CTRL_RUN_IN_FLASH_ONLY) - set(lib_name "btdm_app_flash") - else() - set(lib_name "btdm_app") - endif() - add_prebuilt_library(bt_btdm_app - "${CMAKE_CURRENT_LIST_DIR}/controller/lib_esp32c3_family/esp32s3/lib${lib_name}.a") - target_link_libraries(${COMPONENT_LIB} PRIVATE bt_btdm_app) - elseif(CONFIG_BT_CONTROLLER_ENABLED) - if(CONFIG_BT_LE_CONTROLLER_LOG_WRAP_PANIC_HANDLER_ENABLE) - target_link_options(${COMPONENT_LIB} INTERFACE "-Wl,--wrap=esp_panic_handler") - endif() - if(CONFIG_IDF_TARGET_ESP32C6) - add_prebuilt_library(libble_app - "${CMAKE_CURRENT_LIST_DIR}/controller/lib_esp32c6/esp32c6-bt-lib/esp32c6/libble_app.a" - REQUIRES esp_phy) - elseif(CONFIG_IDF_TARGET_ESP32C61) - add_prebuilt_library(libble_app - "${CMAKE_CURRENT_LIST_DIR}/controller/lib_esp32c6/esp32c6-bt-lib/esp32c61/libble_app.a" - REQUIRES esp_phy) - else() - if(CONFIG_BT_CTRL_RUN_IN_FLASH_ONLY AND CONFIG_IDF_TARGET_ESP32C2) - add_prebuilt_library(libble_app - "controller/lib_${target_name}/${target_name}-bt-lib/libble_app_flash.a" - REQUIRES esp_phy) - else() - add_prebuilt_library(libble_app - "controller/lib_${target_name}/${target_name}-bt-lib/libble_app.a" - REQUIRES esp_phy) - endif() - endif() - target_link_libraries(${COMPONENT_LIB} PRIVATE libble_app) - endif() - - set_source_files_properties( - "host/bluedroid/bta/gatt/bta_gattc_act.c" - "host/bluedroid/bta/gatt/bta_gattc_cache.c" - "host/bluedroid/btc/profile/std/gatt/btc_gatt_util.c" - "host/bluedroid/btc/profile/std/gatt/btc_gatts.c" - PROPERTIES COMPILE_FLAGS -Wno-address-of-packed-member) - target_compile_options(${COMPONENT_LIB} PRIVATE "-Wno-format") -endif() - -if(CONFIG_BLE_MESH_V11_SUPPORT) - -set(BLE_MESH_LIB_NAME "libble_mesh.a") - - if(CONFIG_IDF_TARGET_ESP32) - add_prebuilt_library(ble_mesh "esp_ble_mesh/lib/lib/esp32/${BLE_MESH_LIB_NAME}") - target_link_libraries(${COMPONENT_LIB} PRIVATE ble_mesh) - elseif(CONFIG_IDF_TARGET_ESP32S3) - add_prebuilt_library(ble_mesh "esp_ble_mesh/lib/lib/esp32s3/${BLE_MESH_LIB_NAME}") - target_link_libraries(${COMPONENT_LIB} PRIVATE ble_mesh) - elseif(CONFIG_IDF_TARGET_ESP32C3) - add_prebuilt_library(ble_mesh "esp_ble_mesh/lib/lib/esp32c3/${BLE_MESH_LIB_NAME}") - target_link_libraries(${COMPONENT_LIB} PRIVATE ble_mesh) - elseif(CONFIG_IDF_TARGET_ESP32C6) - add_prebuilt_library(ble_mesh "esp_ble_mesh/lib/lib/esp32c6/${BLE_MESH_LIB_NAME}") - target_link_libraries(${COMPONENT_LIB} PRIVATE ble_mesh) - elseif(CONFIG_IDF_TARGET_ESP32C61) - add_prebuilt_library(ble_mesh "esp_ble_mesh/lib/lib/esp32c61/${BLE_MESH_LIB_NAME}") - target_link_libraries(${COMPONENT_LIB} PRIVATE ble_mesh) - elseif(CONFIG_IDF_TARGET_ESP32H2) - add_prebuilt_library(ble_mesh "esp_ble_mesh/lib/lib/esp32h2/${BLE_MESH_LIB_NAME}") - target_link_libraries(${COMPONENT_LIB} PRIVATE ble_mesh) - elseif(CONFIG_IDF_TARGET_ESP32C5) - add_prebuilt_library(ble_mesh "esp_ble_mesh/lib/lib/esp32c5/${BLE_MESH_LIB_NAME}") - target_link_libraries(${COMPONENT_LIB} PRIVATE ble_mesh) - endif() -endif() - -if(CONFIG_BT_NIMBLE_MESH) - set_source_files_properties("host/nimble/nimble/nimble/host/mesh/src/net.c" - PROPERTIES COMPILE_FLAGS -Wno-type-limits) -endif() - -if(CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_DISABLE AND CONFIG_BT_NIMBLE_ENABLED) - # some variables in NimBLE are only used by asserts - target_compile_options(${COMPONENT_LIB} PRIVATE -Wno-unused-but-set-variable -Wno-unused-variable) -endif() - -if(NOT CMAKE_BUILD_EARLY_EXPANSION) - set(jump_table_opts "-fjump-tables") - if(NOT (CMAKE_C_COMPILER_ID MATCHES "Clang") ) - set(jump_table_opts "${jump_table_opts} -ftree-switch-conversion") - endif() - set_source_files_properties("${CMAKE_CURRENT_LIST_DIR}/host/bluedroid/bta/hf_ag/bta_ag_cmd.c" - "${CMAKE_CURRENT_LIST_DIR}/host/bluedroid/btc/profile/std/gap/btc_gap_ble.c" - PROPERTIES COMPILE_FLAGS "${jump_table_opts}") -endif() - -if(CMAKE_C_COMPILER_ID MATCHES "GNU" AND CMAKE_C_COMPILER_VERSION VERSION_GREATER 15.0) - if(CONFIG_BT_BLUEDROID_ENABLED) - set_source_files_properties("host/bluedroid/device/controller.c" - PROPERTIES COMPILE_FLAGS "-Wno-unterminated-string-initialization") - endif() - if(CONFIG_BT_NIMBLE_ENABLED AND CONFIG_BT_NIMBLE_MESH) - set_source_files_properties("host/nimble/nimble/nimble/host/mesh/src/prov.c" - PROPERTIES COMPILE_FLAGS "-Wno-unterminated-string-initialization") - endif() + + register_bt_ctrl_libs() + set_bluedroid_host_compile_flags() + set_nimble_host_compile_flags() + register_ble_mesh_libs() + + register_log_compression_dependency() + wrap_uart_log_tx_interface() endif() diff --git a/components/bt/Kconfig b/components/bt/Kconfig index 05f57d79e31..caf7d5440c4 100644 --- a/components/bt/Kconfig +++ b/components/bt/Kconfig @@ -109,6 +109,12 @@ menu "Bluetooth" This option is to configure the buffer size of the hci adv report cache in hci debug mode. This is a ring buffer, the new data will overwrite the oldest data if the buffer is full. + config BT_HCI_LOG_INSIGHTS_ENABLE + depends on BT_HCI_LOG_DEBUG_EN + bool "Enable Insights for HCI LOGS BT Stack" + help + Enable this to allow the BT stack to send diagnostic events. + endmenu menuconfig BLE_MESH diff --git a/components/bt/common/CMakeLists.txt b/components/bt/common/CMakeLists.txt new file mode 100644 index 00000000000..062a4498da8 --- /dev/null +++ b/components/bt/common/CMakeLists.txt @@ -0,0 +1,204 @@ +function(replace_log_compression_srcs) + if(NOT CONFIG_BLE_COMPRESSED_LOG_ENABLE) + return() + endif() + + get_filename_component(CODE_BASE_PATH "${CMAKE_CURRENT_FUNCTION_LIST_DIR}/.." ABSOLUTE) + # When log compression is enabled, selected logs are replaced + # by auto-generated macros that emit pre-encoded data. + # This eliminates the original format strings, reducing firmware size and + # removing runtime formatting overhead, so logs are produced faster and + # with less system impact. + add_subdirectory(${CMAKE_CURRENT_FUNCTION_LIST_DIR}/ble_log/extension/log_compression) + set(LOG_COMPRESSION_TARGET "${LOG_COMPRESSION_TARGET}" PARENT_SCOPE) + + if(LOG_COMPRESSION_TARGET) + # Replace the caller's srcs/include_dirs with the compressed-log ones + set(srcs "${LOG_COMPRESS_SRCS}" PARENT_SCOPE) + set(include_dirs "${LOG_COMPRESS_INCLUDE_DIRS}" PARENT_SCOPE) + # LOG_COMPRESS_FILES_WITH_FLAGS format: "file_path|compile_flags" + if(LOG_COMPRESS_FILES_WITH_FLAGS) + foreach(file_entry ${LOG_COMPRESS_FILES_WITH_FLAGS}) + # Split by '|' to get file path and flags + string(REPLACE "|" ";" file_parts "${file_entry}") + list(GET file_parts 0 file_path) + list(GET file_parts 1 compile_flags) + set_source_files_properties(${file_path} + PROPERTIES COMPILE_FLAGS "${compile_flags}") + endforeach() + endif() + else() + # Only append extra include dirs, keep caller's srcs/include_dirs intact + set(_merged_include_dirs ${include_dirs} ${LOG_COMPRESS_INCLUDE_DIRS}) + set(include_dirs "${_merged_include_dirs}" PARENT_SCOPE) + endif() +endfunction() + +# Make the bt component library depend on the log-compression custom target +# so that the Python-driven code-generation step (log index headers and +# replacement sources referenced by LOG_COMPRESS_SRCS) finishes before the +# component is compiled. Does nothing if compressed log is disabled or no +# module actually participates in log compression. +function(register_log_compression_dependency) + if(NOT CONFIG_BLE_COMPRESSED_LOG_ENABLE) + return() + endif() + if(NOT LOG_COMPRESSION_TARGET) + return() + endif() + + add_dependencies(${COMPONENT_LIB} ${LOG_COMPRESSION_TARGET}) +endfunction() + +function(wrap_uart_log_tx_interface) + if(NOT CONFIG_BT_ENABLED) + return() + endif() + + # UART redir wrap flags — needed whenever BLE Log uses UART DMA on port 0, + # regardless of whether BLE controller is enabled. + if(DEFINED CONFIG_BLE_LOG_PRPH_UART_DMA_PORT) + if(CONFIG_BLE_LOG_PRPH_UART_DMA_PORT EQUAL 0) + target_link_libraries(${COMPONENT_LIB} INTERFACE "-Wl,--wrap=uart_tx_chars") + target_link_libraries(${COMPONENT_LIB} INTERFACE "-Wl,--wrap=uart_write_bytes") + target_link_libraries(${COMPONENT_LIB} INTERFACE "-Wl,--wrap=uart_write_bytes_with_break") + endif() + endif() +endfunction() + +set(bt_common_srcs "" PARENT_SCOPE) +set(bt_common_include_dirs "" PARENT_SCOPE) +set(bt_common_priv_include_dirs "" PARENT_SCOPE) + +# API headers that are used in the docs are also compiled +# even if CONFIG_BT_ENABLED=n as long as CONFIG_IDF_DOC_BUILD=y +if(CONFIG_IDF_DOC_BUILD) + set(bt_common_include_dirs + "${CMAKE_CURRENT_LIST_DIR}/api/include/api" + "${CMAKE_CURRENT_LIST_DIR}/btc/profile/esp/blufi/include" + "${CMAKE_CURRENT_LIST_DIR}/btc/profile/esp/include" + "${CMAKE_CURRENT_LIST_DIR}/hci_log/include" + "${CMAKE_CURRENT_LIST_DIR}/ble_log/include" + "${CMAKE_CURRENT_LIST_DIR}/ble_log/deprecated/include" + PARENT_SCOPE + ) + return() +endif() + +if(NOT CONFIG_BT_ENABLED) + return() +endif() + +list(APPEND bt_common_srcs + "${CMAKE_CURRENT_LIST_DIR}/btc/core/btc_alarm.c" + "${CMAKE_CURRENT_LIST_DIR}/api/esp_blufi_api.c" + "${CMAKE_CURRENT_LIST_DIR}/hci_log/bt_hci_log.c" + "${CMAKE_CURRENT_LIST_DIR}/btc/core/btc_manage.c" + "${CMAKE_CURRENT_LIST_DIR}/hci_log/bt_hci_log_insights.c" + "${CMAKE_CURRENT_LIST_DIR}/btc/core/btc_task.c" + "${CMAKE_CURRENT_LIST_DIR}/btc/profile/esp/blufi/blufi_prf.c" + "${CMAKE_CURRENT_LIST_DIR}/btc/profile/esp/blufi/blufi_protocol.c" + "${CMAKE_CURRENT_LIST_DIR}/osi/alarm.c" + "${CMAKE_CURRENT_LIST_DIR}/osi/allocator.c" + "${CMAKE_CURRENT_LIST_DIR}/osi/buffer.c" + "${CMAKE_CURRENT_LIST_DIR}/osi/config.c" + "${CMAKE_CURRENT_LIST_DIR}/osi/fixed_queue.c" + "${CMAKE_CURRENT_LIST_DIR}/osi/pkt_queue.c" + "${CMAKE_CURRENT_LIST_DIR}/osi/fixed_pkt_queue.c" + "${CMAKE_CURRENT_LIST_DIR}/osi/future.c" + "${CMAKE_CURRENT_LIST_DIR}/osi/hash_functions.c" + "${CMAKE_CURRENT_LIST_DIR}/osi/hash_map.c" + "${CMAKE_CURRENT_LIST_DIR}/osi/list.c" + "${CMAKE_CURRENT_LIST_DIR}/osi/mutex.c" + "${CMAKE_CURRENT_LIST_DIR}/osi/thread.c" + "${CMAKE_CURRENT_LIST_DIR}/osi/osi.c" + "${CMAKE_CURRENT_LIST_DIR}/osi/semaphore.c" + "${CMAKE_CURRENT_LIST_DIR}/ble_log/deprecated/ble_log_spi_out.c" +) + +list(APPEND bt_common_include_dirs + "${CMAKE_CURRENT_LIST_DIR}/osi/include" + "${CMAKE_CURRENT_LIST_DIR}/api/include/api" + "${CMAKE_CURRENT_LIST_DIR}/btc/profile/esp/blufi/include" + "${CMAKE_CURRENT_LIST_DIR}/btc/profile/esp/include" + "${CMAKE_CURRENT_LIST_DIR}/hci_log/include" + "${CMAKE_CURRENT_LIST_DIR}/ble_log/include" + "${CMAKE_CURRENT_LIST_DIR}/ble_log/deprecated/include" +) + +list(APPEND bt_common_priv_include_dirs + "${CMAKE_CURRENT_LIST_DIR}/btc/include" + "${CMAKE_CURRENT_LIST_DIR}/include" +) + +if(CONFIG_BLE_LOG_ENABLED) + list(APPEND bt_common_srcs + "${CMAKE_CURRENT_LIST_DIR}/ble_log/src/ble_log.c" + "${CMAKE_CURRENT_LIST_DIR}/ble_log/src/ble_log_lbm.c" + "${CMAKE_CURRENT_LIST_DIR}/ble_log/src/ble_log_rt.c" + "${CMAKE_CURRENT_LIST_DIR}/ble_log/src/ble_log_util.c" + ) + + list(APPEND bt_common_include_dirs + "${CMAKE_CURRENT_LIST_DIR}/ble_log/include" + ) + + list(APPEND bt_common_priv_include_dirs + "${CMAKE_CURRENT_LIST_DIR}/ble_log/src/internal_include" + "${CMAKE_CURRENT_LIST_DIR}/ble_log/src/internal_include/prph" + ) + + # Timestamp synchronization extension + if(CONFIG_BLE_LOG_TS_ENABLED) + list(APPEND bt_common_srcs "${CMAKE_CURRENT_LIST_DIR}/ble_log/src/ble_log_ts.c") + endif() + + # Peripheral interface implementation + if(CONFIG_BLE_LOG_PRPH_DUMMY) + list(APPEND bt_common_srcs + "${CMAKE_CURRENT_LIST_DIR}/ble_log/src/prph/ble_log_prph_dummy.c" + ) + elseif(CONFIG_BLE_LOG_PRPH_SPI_MASTER_DMA) + list(APPEND bt_common_srcs + "${CMAKE_CURRENT_LIST_DIR}/ble_log/src/prph/ble_log_prph_spi_master_dma.c" + ) + elseif(CONFIG_BLE_LOG_PRPH_UART_DMA) + list(APPEND bt_common_srcs + "${CMAKE_CURRENT_LIST_DIR}/ble_log/src/prph/ble_log_prph_uart_dma.c" + ) + endif() +endif() + +# Compile TinyCrypt if: +# 1. Controller uses TinyCrypt (not mbedTLS), OR +# 2. NimBLE uses TinyCrypt (not mbedTLS), OR +# 3. Bluedroid Host SMP uses TinyCrypt +if(CONFIG_BT_SMP_CRYPTO_STACK_TINYCRYPT OR NOT CONFIG_BT_NIMBLE_CRYPTO_STACK_MBEDTLS) + list(APPEND bt_common_include_dirs + "${CMAKE_CURRENT_LIST_DIR}/tinycrypt/include" + "${CMAKE_CURRENT_LIST_DIR}/tinycrypt/port" + ) + list(APPEND bt_common_srcs + "${CMAKE_CURRENT_LIST_DIR}/tinycrypt/src/utils.c" + "${CMAKE_CURRENT_LIST_DIR}/tinycrypt/src/sha256.c" + "${CMAKE_CURRENT_LIST_DIR}/tinycrypt/src/ecc.c" + "${CMAKE_CURRENT_LIST_DIR}/tinycrypt/src/ctr_prng.c" + "${CMAKE_CURRENT_LIST_DIR}/tinycrypt/src/ctr_mode.c" + "${CMAKE_CURRENT_LIST_DIR}/tinycrypt/src/aes_decrypt.c" + "${CMAKE_CURRENT_LIST_DIR}/tinycrypt/src/aes_encrypt.c" + "${CMAKE_CURRENT_LIST_DIR}/tinycrypt/src/ccm_mode.c" + "${CMAKE_CURRENT_LIST_DIR}/tinycrypt/src/ecc_dsa.c" + "${CMAKE_CURRENT_LIST_DIR}/tinycrypt/src/cmac_mode.c" + "${CMAKE_CURRENT_LIST_DIR}/tinycrypt/src/ecc_dh.c" + "${CMAKE_CURRENT_LIST_DIR}/tinycrypt/src/hmac_prng.c" + "${CMAKE_CURRENT_LIST_DIR}/tinycrypt/src/ecc_platform_specific.c" + "${CMAKE_CURRENT_LIST_DIR}/tinycrypt/src/hmac.c" + "${CMAKE_CURRENT_LIST_DIR}/tinycrypt/src/cbc_mode.c" + "${CMAKE_CURRENT_LIST_DIR}/tinycrypt/port/esp_tinycrypt_port.c" + ) +endif() + +# Export the variables to the parent scope +set(bt_common_srcs "${bt_common_srcs}" PARENT_SCOPE) +set(bt_common_include_dirs "${bt_common_include_dirs}" PARENT_SCOPE) +set(bt_common_priv_include_dirs "${bt_common_priv_include_dirs}" PARENT_SCOPE) diff --git a/components/bt/common/Kconfig.in b/components/bt/common/Kconfig.in index 8f50fd6f941..d4c16df32bc 100644 --- a/components/bt/common/Kconfig.in +++ b/components/bt/common/Kconfig.in @@ -6,6 +6,21 @@ config BT_ALARM_MAX_NUM This option decides the maximum number of alarms which could be used by Bluetooth host. +config BT_BLE_HOST_ALLOW_SUB_SPEC_MIN_CONN_INT + bool "Allow BLE connection interval below Bluetooth Core Spec minimum (disable host check)" + depends on BT_BLE_ENABLED || BT_NIMBLE_ENABLED + default n + help + When enabled, BLE host-side validation accepts connection interval + values below the Bluetooth Core Specification minimum of 0x0006 + (7.5 ms), down to non-zero values. The BLE controller still enforces + what is actually supported in hardware and firmware. + + End users should NOT set this option directly. In typical IDF builds it + follows the active Controller integration when that Controller supports + this mode; use the Controller's own configuration instead of toggling + this host symbol manually. + choice BT_SMP_CRYPTO_STACK prompt "SMP cryptographic stack" depends on (BT_BLE_SMP_ENABLE || BT_SMP_ENABLE || BT_NIMBLE_SECURITY_ENABLE || BT_LE_SECURITY_ENABLE) diff --git a/components/bt/common/ble_log/Kconfig.in b/components/bt/common/ble_log/Kconfig.in index d7447475059..d4ddc3c0dfc 100644 --- a/components/bt/common/ble_log/Kconfig.in +++ b/components/bt/common/ble_log/Kconfig.in @@ -7,6 +7,14 @@ config BLE_LOG_ENABLED Enable BT Log Async Output if BLE_LOG_ENABLED + config BLE_LOG_LBM_AUTO_FLUSH + bool "Enable automatic BLE Log LBM buffer flush" + default n + help + Periodically flush partially-filled BLE Log LBM transport buffers + that remain pending, reducing latency for low-volume or + intermittent logging. + config BLE_LOG_TASK_STACK_SIZE int "Stack size for BLE Log Task" default 1024 if IDF_TARGET_ARCH_RISCV @@ -17,8 +25,7 @@ if BLE_LOG_ENABLED config BLE_LOG_LBM_TRANS_BUF_SIZE int "Total buffer memory per common LBM (bytes)" - default 512 if BT_BLUEDROID_ENABLED - default 1024 if BT_NIMBLE_ENABLED + default 2048 help Total buffer memory allocated for each common pool log buffer manager (LBM). This memory is divided equally among internal diff --git a/components/bt/common/ble_log/README.md b/components/bt/common/ble_log/README.md index 8d7df6e98e9..c24f1b862f9 100644 --- a/components/bt/common/ble_log/README.md +++ b/components/bt/common/ble_log/README.md @@ -80,7 +80,7 @@ void app_main() { uint8_t data[] = {0x01, 0x02, 0x03, 0x04}; ble_log_write_hex(BLE_LOG_SRC_CUSTOM, data, sizeof(data)); - // Force flush buffers + // End session and flush buffers ble_log_flush(); // Cleanup resources @@ -178,9 +178,15 @@ Write hexadecimal log data. #### `void ble_log_flush(void)` -Force flush all buffers and send pending logs immediately. +End the current logging session and flush pending logs immediately. -**Note**: This operation is blocking and will pause module operation until all buffers are cleared. +This API temporarily suspends ordinary log writes, waits for in-progress +writers to exit, emits a final statistics internal frame, flushes pending +transport buffers, resets statistics, and then restores the enable state that +was in effect before the call. + +**Note**: This operation is blocking. If BLE Log was enabled before the call, +it remains enabled after the flush completes. #### `void ble_log_dump_to_console(void)` @@ -272,7 +278,7 @@ void example_basic_logging() { uint8_t host_data[] = {0x02, 0x00, 0x20, 0x0B, 0x00, 0x07, 0x00, 0x04, 0x00, 0x10, 0x01, 0x00, 0xFF, 0xFF, 0x00, 0x28}; ble_log_write_hex(BLE_LOG_SRC_HOST, host_data, sizeof(host_data)); - // Force send + // End session and flush buffers ble_log_flush(); // Cleanup @@ -334,6 +340,7 @@ void example_performance_test() { ble_log_write_hex(BLE_LOG_SRC_CUSTOM, test_data, sizeof(test_data)); } + // End session and flush buffers ble_log_flush(); uint32_t end_time = esp_timer_get_time(); diff --git a/components/bt/common/ble_log/extension/log_compression/CMakeLists.txt b/components/bt/common/ble_log/extension/log_compression/CMakeLists.txt index a0c001998d2..0c757ca5c43 100644 --- a/components/bt/common/ble_log/extension/log_compression/CMakeLists.txt +++ b/components/bt/common/ble_log/extension/log_compression/CMakeLists.txt @@ -132,6 +132,12 @@ if(LOG_COMPRESSED_MODULE) string(REPLACE ";" "|" MODULE_CODE_PATH "${LOG_COMPRESSED_MODULE_CODE_PATH}") set(MATCH_PATTERN "(${MODULE_CODE_PATH}).+\\.c") foreach(src ${srcs}) + # Normalize absolute paths to relative paths (relative to CODE_BASE_PATH) + # so that pattern matching works and .compressed_srcs/ has a clean layout + if(IS_ABSOLUTE "${src}") + get_filename_component(src "${src}" ABSOLUTE) + file(RELATIVE_PATH src "${CODE_BASE_PATH}" "${src}") + endif() if(src MATCHES ${MATCH_PATTERN}) set(dest "${LOG_COMPRESSED_SRCS_DIR}/${src}") file(WRITE "${dest}" "") diff --git a/components/bt/common/ble_log/extension/log_compression/scripts/ble_log_compress.py b/components/bt/common/ble_log/extension/log_compression/scripts/ble_log_compress.py index 7f046e04934..b4378dff57b 100644 --- a/components/bt/common/ble_log/extension/log_compression/scripts/ble_log_compress.py +++ b/components/bt/common/ble_log/extension/log_compression/scripts/ble_log_compress.py @@ -699,9 +699,15 @@ class LogCompressor: compressed_file_cnt = 0 total_cnt = 0 for src in srcs: - if pattern.match(src): - src_path = self.code_base_path / src - dest_path = self.bt_compressed_srcs_path / src + # Convert absolute paths to relative (to code_base_path) for pattern matching + src_for_match = src + try: + src_for_match = str(Path(src).relative_to(self.code_base_path)) + except ValueError: + pass # Not under code_base_path, keep as-is + if pattern.match(src_for_match): + src_path = self.code_base_path / src_for_match + dest_path = self.bt_compressed_srcs_path / src_for_match temp_path = f'{dest_path}.tmp' total_cnt += 1 # Skip if already processed diff --git a/components/bt/common/ble_log/src/ble_log.c b/components/bt/common/ble_log/src/ble_log.c index 4e6ce25838d..83ac2fcbd40 100644 --- a/components/bt/common/ble_log/src/ble_log.c +++ b/components/bt/common/ble_log/src/ble_log.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -13,12 +13,22 @@ #include "ble_log_lbm.h" #include "ble_log_prph.h" #include "ble_log_util.h" +#include "esp_log.h" +#include "esp_system.h" #if CONFIG_BLE_LOG_TS_ENABLED #include "ble_log_ts.h" #endif /* CONFIG_BLE_LOG_TS_ENABLED */ /* VARIABLE */ +#define TAG "ble_log" + BLE_LOG_STATIC bool ble_log_inited = false; +BLE_LOG_STATIC bool shutdown_handler_registered = false; + +BLE_LOG_STATIC void ble_log_shutdown_handler(void) +{ + ble_log_flush(); +} /* INTERFACE */ bool ble_log_init(void) @@ -53,6 +63,12 @@ bool ble_log_init(void) /* Initialization done */ ble_log_inited = true; ble_log_enable(true); + esp_err_t ret = esp_register_shutdown_handler(ble_log_shutdown_handler); + if (ret == ESP_OK) { + shutdown_handler_registered = true; + } else { + ESP_LOGW(TAG, "Register shutdown handler failed, ret = 0x%x", ret); + } /* Write initialization done log */ ble_log_info_t ble_log_info = { @@ -69,6 +85,14 @@ exit: void ble_log_deinit(void) { + if (shutdown_handler_registered) { + esp_err_t ret = esp_unregister_shutdown_handler(ble_log_shutdown_handler); + if (ret == ESP_OK) { + shutdown_handler_registered = false; + } else { + ESP_LOGW(TAG, "Unregister shutdown handler failed, ret = 0x%x", ret); + } + } ble_log_enable(false); ble_log_inited = false; diff --git a/components/bt/common/ble_log/src/ble_log_lbm.c b/components/bt/common/ble_log/src/ble_log_lbm.c index db673c80be8..932c20d134e 100644 --- a/components/bt/common/ble_log/src/ble_log_lbm.c +++ b/components/bt/common/ble_log/src/ble_log_lbm.c @@ -11,15 +11,20 @@ #include "ble_log.h" #include "ble_log_lbm.h" #include "ble_log_rt.h" +#include "esp_log.h" #if CONFIG_SOC_ESP_NIMBLE_CONTROLLER #include "os/os_mbuf.h" #endif /* CONFIG_SOC_ESP_NIMBLE_CONTROLLER */ /* VARIABLE */ +#define TAG "ble_log" +#define BLE_LOG_LBM_WAIT_TIMEOUT_MS (1000) + BLE_LOG_STATIC volatile uint32_t lbm_ref_count = 0; BLE_LOG_STATIC bool lbm_inited = false; BLE_LOG_STATIC bool lbm_enabled = false; +BLE_LOG_STATIC volatile bool flush_in_progress = false; BLE_LOG_STATIC ble_log_lbm_ctx_t *lbm_ctx = NULL; BLE_LOG_STATIC ble_log_stat_mgr_t *stat_mgr_ctx[BLE_LOG_SRC_MAX] = {0}; @@ -30,10 +35,14 @@ bool ble_log_lbm_acquire_trans(size_t log_len, ble_log_lbm_t **out_lbm, BLE_LOG_STATIC void ble_log_lbm_release(ble_log_lbm_t *lbm); BLE_LOG_STATIC ble_log_prph_trans_t **ble_log_lbm_get_trans(ble_log_lbm_t *lbm, size_t log_len); +BLE_LOG_STATIC bool ble_log_lbm_flush_all_trans(void); +BLE_LOG_STATIC void ble_log_lbm_reset_stats(void); BLE_LOG_STATIC void ble_log_lbm_write_trans(ble_log_prph_trans_t **trans, ble_log_src_t src_code, const uint8_t *addr, uint16_t len, const uint8_t *addr_append, uint16_t len_append, bool omdata); +BLE_LOG_STATIC +bool ble_log_write_hex_core(ble_log_src_t src_code, const uint8_t *addr, size_t len); #if BLE_LOG_UART_REDIR_ENABLED BLE_LOG_STATIC void ble_log_lbm_stream_seal(ble_log_prph_trans_t **trans, ble_log_src_t src_code); @@ -110,6 +119,66 @@ void ble_log_lbm_release(ble_log_lbm_t *lbm) } } +BLE_LOG_STATIC bool ble_log_lbm_flush_all_trans(void) +{ + ble_log_lbm_t *lbm; + ble_log_prph_trans_t **trans; + bool in_progress; + TickType_t start_tick = xTaskGetTickCount(); + + /* Queue transports with logs */ + for (int i = 0; i < BLE_LOG_LBM_CNT; i++) { + lbm = &(lbm_ctx->lbm_pool[i]); + int trans_idx = lbm->trans_idx; + for (int j = 0; j < BLE_LOG_TRANS_BUF_CNT; j++) { + trans = &(lbm->trans[trans_idx]); + if (!__atomic_load_n(&(*trans)->prph_owned, __ATOMIC_ACQUIRE) && + (*trans)->pos) { + ble_log_rt_queue_trans(trans); + } + trans_idx = (trans_idx + 1) & (BLE_LOG_TRANS_BUF_CNT - 1); + } + } + + /* Wait for transportation to finish */ + do { + in_progress = false; + for (int i = 0; i < BLE_LOG_LBM_CNT; i++) { + lbm = &(lbm_ctx->lbm_pool[i]); + for (int j = 0; j < BLE_LOG_TRANS_BUF_CNT; j++) { + trans = &(lbm->trans[j]); + in_progress |= __atomic_load_n(&(*trans)->prph_owned, __ATOMIC_ACQUIRE); + } + } + if (in_progress) { + if ((xTaskGetTickCount() - start_tick) >= + pdMS_TO_TICKS(BLE_LOG_LBM_WAIT_TIMEOUT_MS)) { + ESP_LOGE(TAG, "Timed out waiting for BLE Log transports"); + return false; + } + vTaskDelay(1); + } + } while (in_progress); + + return true; +} + +BLE_LOG_STATIC void ble_log_lbm_reset_stats(void) +{ + ble_log_lbm_t *lbm; + + for (int i = 0; i < BLE_LOG_SRC_MAX; i++) { + BLE_LOG_MEMSET(stat_mgr_ctx[i], 0, sizeof(ble_log_stat_mgr_t)); + } + + for (int i = 0; i < BLE_LOG_LBM_CNT; i++) { + lbm = &(lbm_ctx->lbm_pool[i]); + __atomic_store_n(&lbm->trans_inflight, 0, __ATOMIC_RELAXED); + __atomic_store_n(&lbm->trans_inflight_peak, 0, __ATOMIC_RELAXED); + } + ble_log_prph_reset_util_counters(); +} + BLE_LOG_IRAM_ATTR BLE_LOG_STATIC void ble_log_lbm_write_trans(ble_log_prph_trans_t **trans, ble_log_src_t src_code, const uint8_t *addr, uint16_t len, @@ -203,6 +272,35 @@ void ble_log_stat_mgr_update(ble_log_src_t src_code, uint32_t len, bool lost) } } +BLE_LOG_IRAM_ATTR BLE_LOG_STATIC +bool ble_log_write_hex_core(ble_log_src_t src_code, const uint8_t *addr, size_t len) +{ + /* Get transport from the best available pool */ + size_t payload_len = len + sizeof(uint32_t); + ble_log_lbm_t *lbm; + ble_log_prph_trans_t **trans; + if (!ble_log_lbm_acquire_trans(payload_len, &lbm, &trans)) { + goto failed; + } + + /* Write transport */ + uint32_t os_ts = pdTICKS_TO_MS(BLE_LOG_IN_ISR()? + xTaskGetTickCountFromISR(): + xTaskGetTickCount()); + ble_log_lbm_write_trans(trans, src_code, (const uint8_t *)&os_ts, + sizeof(uint32_t), addr, len, false); + + /* Release */ + ble_log_lbm_release(lbm); + return true; + +failed: + if (lbm_inited) { + ble_log_stat_mgr_update(src_code, payload_len, true); + } + return false; +} + /* -------------------------- */ /* INTERNAL INTERFACE */ /* -------------------------- */ @@ -289,10 +387,15 @@ void ble_log_lbm_deinit(void) lbm_enabled = false; /* Disable module and wait for all references to be released */ - uint32_t time_waited = 0; + TickType_t start_tick = xTaskGetTickCount(); while (__atomic_load_n(&lbm_ref_count, __ATOMIC_ACQUIRE) > 0) { - vTaskDelay(pdMS_TO_TICKS(1)); - BLE_LOG_ASSERT(time_waited++ < 1000); + if ((xTaskGetTickCount() - start_tick) >= + pdMS_TO_TICKS(BLE_LOG_LBM_WAIT_TIMEOUT_MS)) { + ESP_LOGE(TAG, "Timed out waiting for BLE Log references during deinit"); + } + BLE_LOG_ASSERT((xTaskGetTickCount() - start_tick) < + pdMS_TO_TICKS(BLE_LOG_LBM_WAIT_TIMEOUT_MS)); + vTaskDelay(1); } /* Release statistic manager context */ @@ -501,6 +604,33 @@ deref: BLE_LOG_REF_COUNT_RELEASE(&lbm_ref_count); } +void ble_log_write_final_stat(void) +{ + BLE_LOG_REF_COUNT_ACQUIRE(&lbm_ref_count); + if (!lbm_inited) { + goto deref; + } + + ble_log_final_stat_t final_stat; + final_stat.int_src_code = BLE_LOG_INT_SRC_FINAL_STAT; + final_stat.src_cnt = BLE_LOG_SRC_MAX; + + BLE_LOG_ENTER_CRITICAL(); + for (int i = 0; i < BLE_LOG_SRC_MAX; i++) { + ble_log_final_stat_entry_t *entry = &final_stat.entries[i]; + entry->src_code = i; + BLE_LOG_MEMCPY(&entry->written_frame_cnt, + &stat_mgr_ctx[i]->written_frame_cnt, + 4 * sizeof(uint32_t)); + } + BLE_LOG_EXIT_CRITICAL(); + + ble_log_write_internal((const uint8_t *)&final_stat, sizeof(final_stat)); + +deref: + BLE_LOG_REF_COUNT_RELEASE(&lbm_ref_count); +} + /* ------------------------ */ /* PUBLIC INTERFACE */ /* ------------------------ */ @@ -518,7 +648,6 @@ void ble_log_flush(void) /* Prevent concurrent flush — two concurrent callers would deadlock on * the ref_count spin-wait (both hold a ref, both wait for ref_count <= 1). * Second caller returns immediately instead of deadlocking. */ - static volatile bool flush_in_progress = false; if (__atomic_test_and_set(&flush_in_progress, __ATOMIC_ACQUIRE)) { return; } @@ -527,8 +656,6 @@ void ble_log_flush(void) if (!lbm_inited) { goto deref; } - - /* Write enhanced statistics before module disable */ ble_log_write_enh_stat(); ble_log_write_buf_util(); @@ -541,64 +668,32 @@ void ble_log_flush(void) /* Disable module and wait for all other references to release */ bool lbm_enabled_copy = lbm_enabled; + lbm_enabled = false; - uint32_t time_waited = 0; + TickType_t start_tick = xTaskGetTickCount(); while (__atomic_load_n(&lbm_ref_count, __ATOMIC_ACQUIRE) > 1) { - vTaskDelay(pdMS_TO_TICKS(1)); - BLE_LOG_ASSERT(time_waited++ < 1000); - } - - /* Queue transports with logs */ - ble_log_lbm_t *lbm; - ble_log_prph_trans_t **trans; - - /* Flush pools */ - for (int i = 0; i < BLE_LOG_LBM_CNT; i++) { - lbm = &(lbm_ctx->lbm_pool[i]); - int trans_idx = lbm->trans_idx; - for (int j = 0; j < BLE_LOG_TRANS_BUF_CNT; j++) { - trans = &(lbm->trans[trans_idx]); - if (!__atomic_load_n(&(*trans)->prph_owned, __ATOMIC_ACQUIRE) && - (*trans)->pos) { - ble_log_rt_queue_trans(trans); - } - trans_idx = (trans_idx + 1) & (BLE_LOG_TRANS_BUF_CNT - 1); + if ((xTaskGetTickCount() - start_tick) >= + pdMS_TO_TICKS(BLE_LOG_LBM_WAIT_TIMEOUT_MS)) { + ESP_LOGE(TAG, "Timed out waiting for BLE Log writers"); + goto fail; } + vTaskDelay(1); } - /* Wait for transportation to finish */ - time_waited = 0; - bool in_progress; - do { - in_progress = false; - for (int i = 0; i < BLE_LOG_LBM_CNT; i++) { - lbm = &(lbm_ctx->lbm_pool[i]); - for (int j = 0; j < BLE_LOG_TRANS_BUF_CNT; j++) { - trans = &(lbm->trans[j]); - in_progress |= __atomic_load_n(&(*trans)->prph_owned, __ATOMIC_ACQUIRE); - } - } - if (in_progress) { - vTaskDelay(pdMS_TO_TICKS(1)); - BLE_LOG_ASSERT(time_waited++ < 1000); - } - } while (in_progress); - - /* Reset statistics manager after all operations complete */ - for (int i = 0; i < BLE_LOG_SRC_MAX; i++) { - BLE_LOG_MEMSET(stat_mgr_ctx[i], 0, sizeof(ble_log_stat_mgr_t)); + if (!ble_log_lbm_flush_all_trans()) { + goto fail; } - for (int i = 0; i < BLE_LOG_LBM_CNT; i++) { - lbm = &(lbm_ctx->lbm_pool[i]); - __atomic_store_n(&lbm->trans_inflight, 0, __ATOMIC_RELAXED); - __atomic_store_n(&lbm->trans_inflight_peak, 0, __ATOMIC_RELAXED); - } - ble_log_prph_reset_util_counters(); + ble_log_write_final_stat(); - /* Resume enable status */ + if (!ble_log_lbm_flush_all_trans()) { + goto fail; + } + ble_log_lbm_reset_stats(); + +fail: + /* Resume enable status after a completed or failed flush. */ lbm_enabled = lbm_enabled_copy; - deref: BLE_LOG_REF_COUNT_RELEASE(&lbm_ref_count); __atomic_clear(&flush_in_progress, __ATOMIC_RELEASE); @@ -607,38 +702,33 @@ deref: BLE_LOG_IRAM_ATTR bool ble_log_write_hex(ble_log_src_t src_code, const uint8_t *addr, size_t len) { + bool ret = false; + BLE_LOG_REF_COUNT_ACQUIRE(&lbm_ref_count); if (!lbm_enabled) { goto exit; } - /* Get transport from the best available pool */ - size_t payload_len = len + sizeof(uint32_t); - ble_log_lbm_t *lbm; - ble_log_prph_trans_t **trans; - if (!ble_log_lbm_acquire_trans(payload_len, &lbm, &trans)) { - goto failed; - } - - /* Write transport */ - uint32_t os_ts = pdTICKS_TO_MS(BLE_LOG_IN_ISR()? - xTaskGetTickCountFromISR(): - xTaskGetTickCount()); - ble_log_lbm_write_trans(trans, src_code, (const uint8_t *)&os_ts, - sizeof(uint32_t), addr, len, false); - - /* Release */ - ble_log_lbm_release(lbm); - BLE_LOG_REF_COUNT_RELEASE(&lbm_ref_count); - return true; - -failed: - if (lbm_inited) { - ble_log_stat_mgr_update(src_code, payload_len, true); - } + ret = ble_log_write_hex_core(src_code, addr, len); exit: BLE_LOG_REF_COUNT_RELEASE(&lbm_ref_count); - return false; + return ret; +} + +BLE_LOG_IRAM_ATTR +bool ble_log_write_internal(const uint8_t *addr, size_t len) +{ + bool ret = false; + + BLE_LOG_REF_COUNT_ACQUIRE(&lbm_ref_count); + if (!lbm_inited) { + goto exit; + } + + ret = ble_log_write_hex_core(BLE_LOG_SRC_INTERNAL, addr, len); +exit: + BLE_LOG_REF_COUNT_RELEASE(&lbm_ref_count); + return ret; } #if CONFIG_BLE_LOG_LL_ENABLED diff --git a/components/bt/common/ble_log/src/internal_include/ble_log_lbm.h b/components/bt/common/ble_log/src/internal_include/ble_log_lbm.h index 1d1e7042705..e63e6ff3a6e 100644 --- a/components/bt/common/ble_log/src/internal_include/ble_log_lbm.h +++ b/components/bt/common/ble_log/src/internal_include/ble_log_lbm.h @@ -13,6 +13,7 @@ /* ---------------- */ /* Includes */ /* ---------------- */ +#include "ble_log.h" #include "ble_log_prph.h" #include "freertos/FreeRTOS.h" @@ -174,6 +175,25 @@ typedef struct { uint32_t lost_bytes_cnt; } __attribute__((packed)) ble_log_enh_stat_t; +/* -------------------------------- */ +/* Final Statistics Defines */ +/* -------------------------------- */ +typedef struct { + uint8_t src_code; + uint32_t written_frame_cnt; + uint32_t lost_frame_cnt; + uint32_t written_bytes_cnt; + uint32_t lost_bytes_cnt; +} __attribute__((packed)) ble_log_final_stat_entry_t; + +typedef struct { + uint8_t int_src_code; + uint8_t src_cnt; + ble_log_final_stat_entry_t entries[BLE_LOG_SRC_MAX]; +} __attribute__((packed)) ble_log_final_stat_t; + +#define BLE_LOG_FINAL_STAT_LEN sizeof(ble_log_final_stat_t) + /* -------------------------------------- */ /* Log Statistics Manager Context */ /* -------------------------------------- */ @@ -220,6 +240,8 @@ _Static_assert(CONFIG_BLE_LOG_LBM_LL_TRANS_BUF_SIZE % BLE_LOG_TRANS_BUF_CNT == 0 #endif _Static_assert(CONFIG_BLE_LOG_LBM_TRANS_BUF_SIZE / BLE_LOG_TRANS_BUF_CNT >= BLE_LOG_FRAME_OVERHEAD, "Common LBM per-buffer size too small for a single frame"); +_Static_assert(BLE_LOG_TRANS_SIZE >= BLE_LOG_FINAL_STAT_LEN + sizeof(uint32_t) + BLE_LOG_FRAME_OVERHEAD, + "Common LBM per-buffer size too small for final statistics frame"); _Static_assert((BLE_LOG_TRANS_BUF_CNT & (BLE_LOG_TRANS_BUF_CNT - 1)) == 0, "BLE_LOG_TRANS_BUF_CNT must be a power of 2"); _Static_assert(1 + BLE_LOG_LBM_ATOMIC_TASK_CNT <= 16, @@ -235,6 +257,8 @@ _Static_assert(BLE_LOG_TRANS_BUF_CNT <= 255, bool ble_log_lbm_init(void); void ble_log_lbm_deinit(void); void ble_log_lbm_enable(bool enable); +bool ble_log_write_internal(const uint8_t *addr, size_t len); +void ble_log_write_final_stat(void); void ble_log_write_enh_stat(void); void ble_log_write_buf_util(void); #if BLE_LOG_UART_REDIR_ENABLED diff --git a/components/bt/common/ble_log/src/internal_include/ble_log_util.h b/components/bt/common/ble_log/src/internal_include/ble_log_util.h index 5e332ec3c56..f6e2f5ac054 100644 --- a/components/bt/common/ble_log/src/internal_include/ble_log_util.h +++ b/components/bt/common/ble_log/src/internal_include/ble_log_util.h @@ -144,6 +144,7 @@ typedef enum { BLE_LOG_INT_SRC_INFO, BLE_LOG_INT_SRC_FLUSH, BLE_LOG_INT_SRC_BUF_UTIL, + BLE_LOG_INT_SRC_FINAL_STAT, BLE_LOG_INT_SRC_MAX, } ble_log_int_src_t; diff --git a/components/bt/common/ble_log/src/prph/ble_log_prph_spi_master_dma.c b/components/bt/common/ble_log/src/prph/ble_log_prph_spi_master_dma.c index cbadf799ca5..6c43044bfaa 100644 --- a/components/bt/common/ble_log/src/prph/ble_log_prph_spi_master_dma.c +++ b/components/bt/common/ble_log/src/prph/ble_log_prph_spi_master_dma.c @@ -20,6 +20,12 @@ #define BLE_LOG_SPI_DMA_ALIGN_BYTES (4U) #define BLE_LOG_SPI_ALIGN_LOG_PERIOD (256U) +#if CONFIG_SPI_MASTER_ISR_IN_IRAM +#define BLE_LOG_SPI_MASTER_DMA_CB_ATTR BLE_LOG_IRAM_ATTR +#else +#define BLE_LOG_SPI_MASTER_DMA_CB_ATTR +#endif + /* VARIABLE */ BLE_LOG_STATIC bool prph_inited = false; BLE_LOG_STATIC spi_device_handle_t dev_handle = NULL; @@ -30,7 +36,7 @@ BLE_LOG_STATIC void spi_master_dma_tx_done_cb(spi_transaction_t *spi_trans); BLE_LOG_STATIC void spi_master_dma_pre_tx_cb(spi_transaction_t *spi_trans); /* PRIVATE FUNCTION */ -BLE_LOG_IRAM_ATTR BLE_LOG_STATIC void spi_master_dma_tx_done_cb(spi_transaction_t *spi_trans) +BLE_LOG_SPI_MASTER_DMA_CB_ATTR BLE_LOG_STATIC void spi_master_dma_tx_done_cb(spi_transaction_t *spi_trans) { /* SPI slave performance issue workaround */ last_tx_done_ts = esp_timer_get_time(); @@ -43,7 +49,7 @@ BLE_LOG_IRAM_ATTR BLE_LOG_STATIC void spi_master_dma_tx_done_cb(spi_transaction_ __atomic_store_n(&trans->prph_owned, false, __ATOMIC_RELEASE); } -BLE_LOG_IRAM_ATTR BLE_LOG_STATIC void spi_master_dma_pre_tx_cb(spi_transaction_t *spi_trans) +BLE_LOG_SPI_MASTER_DMA_CB_ATTR BLE_LOG_STATIC void spi_master_dma_pre_tx_cb(spi_transaction_t *spi_trans) { /* SPI slave performance issue workaround */ while ((esp_timer_get_time() - last_tx_done_ts) < BLE_LOG_SPI_TRANS_ITVL_MIN_US) {} diff --git a/components/bt/common/btc/core/btc_task.c b/components/bt/common/btc/core/btc_task.c index 3da01b697bb..7781d4d5779 100644 --- a/components/bt/common/btc/core/btc_task.c +++ b/components/bt/common/btc/core/btc_task.c @@ -353,7 +353,10 @@ bt_status_t btc_transfer_context(btc_msg_t *msg, void *arg, int arg_len, btc_arg memcpy(lmsg, msg, sizeof(btc_msg_t)); if (arg) { - memset(lmsg->arg, 0x00, arg_len); //important, avoid arg which have no length + /* memcpy below covers exactly arg_len bytes, which is the full size of + * the destination buffer (it was sized as sizeof(btc_msg_t) + arg_len), + * so a prior memset would be redundant. Deep-copy callbacks must only + * read fields that were written by the caller-supplied arg. */ memcpy(lmsg->arg, arg, arg_len); if (copy_func) { copy_func(lmsg, lmsg->arg, arg); diff --git a/components/bt/common/hci_log/bt_hci_log.c b/components/bt/common/hci_log/bt_hci_log.c index 5840adab856..317d4b1718c 100644 --- a/components/bt/common/hci_log/bt_hci_log.c +++ b/components/bt/common/hci_log/bt_hci_log.c @@ -40,6 +40,16 @@ static const char s_hex_to_char_mapping[16] = { static bt_hci_log_t g_bt_hci_log_data_ctl = {0}; static bt_hci_log_t g_bt_hci_log_adv_ctl = {0}; +uint8_t bt_hci_log_h4_type_to_data_type(uint8_t h4_type) +{ + switch (h4_type) { + case 0x05: + return HCI_LOG_DATA_TYPE_ISO_DATA; + default: + return h4_type; + } +} + esp_err_t bt_hci_log_init(void) { uint8_t *g_bt_hci_log_data_buffer = NULL; diff --git a/components/bt/common/hci_log/bt_hci_log_insights.c b/components/bt/common/hci_log/bt_hci_log_insights.c new file mode 100644 index 00000000000..8ab832efa4f --- /dev/null +++ b/components/bt/common/hci_log/bt_hci_log_insights.c @@ -0,0 +1,90 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include + +#include "freertos/FreeRTOS.h" +#include "freertos/portmacro.h" + +#include "esp_timer.h" +#include "bt_ble_insights.h" +#include "bt_common.h" +#include "hci_log/bt_hci_log.h" + +#if (BT_HCI_LOG_INCLUDED == TRUE) && BT_HCI_INSIGHTS_INCLUDED +static uint8_t s_hci_log_seq_num = 0; +static portMUX_TYPE s_hci_log_mux = portMUX_INITIALIZER_UNLOCKED; + +#define HCI_LOG_INSIGHTS_LINE_SIZE 128U +#define HCI_LOG_INSIGHTS_TS_LEN 8U + +static const char *bt_hci_log_insights_label(uint8_t data_type) +{ + switch (data_type) { + case HCI_LOG_DATA_TYPE_COMMAND: + return "C"; + case HCI_LOG_DATA_TYPE_H2C_ACL: + return "H"; + case HCI_LOG_DATA_TYPE_SCO: + return "S"; + case HCI_LOG_DATA_TYPE_EVENT: + return "E"; + case HCI_LOG_DATA_TYPE_ADV: + return "ADV"; + case HCI_LOG_DATA_TYPE_C2H_ACL: + return "D"; + case HCI_LOG_DATA_TYPE_ISO_DATA: + return "I"; + default: + return NULL; + } +} + +void bt_hci_log_record_insights(uint8_t data_type, const uint8_t *data, uint16_t data_len) +{ + const char *label = bt_hci_log_insights_label(data_type); + char line[HCI_LOG_INSIGHTS_LINE_SIZE]; + uint8_t ts_bytes[HCI_LOG_INSIGHTS_TS_LEN]; + uint64_t timestamp; + uint8_t seq_num; + int offset; + + if (!BT_BLE_INSIGHTS_AVAILABLE || label == NULL || data == NULL || data_len == 0) { + return; + } + + portENTER_CRITICAL(&s_hci_log_mux); + seq_num = ++s_hci_log_seq_num; + portEXIT_CRITICAL(&s_hci_log_mux); + + timestamp = esp_timer_get_time(); + memcpy(ts_bytes, ×tamp, sizeof(ts_bytes)); + + offset = snprintf(line, sizeof(line), "%02x %s:", (unsigned int)seq_num, label); + if (offset < 0 || (size_t)offset >= sizeof(line) - 1) { + return; + } + + for (size_t i = 0; i < sizeof(ts_bytes) && offset <= (int)sizeof(line) - 3; i++) { + offset += snprintf(&line[offset], sizeof(line) - offset, "%02x", ts_bytes[i]); + } + + if (offset < 0 || (size_t)offset >= sizeof(line) - 1) { + return; + } + + line[offset++] = ' '; + line[offset] = '\0'; + + for (uint16_t i = 0; i < data_len && offset <= (int)sizeof(line) - 3; i++) { + offset += snprintf(&line[offset], sizeof(line) - offset, "%02X", data[i]); + } + + ble_insights_log(line); +} +#endif diff --git a/components/bt/common/hci_log/include/hci_log/bt_hci_log.h b/components/bt/common/hci_log/include/hci_log/bt_hci_log.h index 4646862194c..8174bc06c95 100644 --- a/components/bt/common/hci_log/include/hci_log/bt_hci_log.h +++ b/components/bt/common/hci_log/include/hci_log/bt_hci_log.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2024 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -102,6 +102,17 @@ esp_err_t bt_hci_log_record_hci_data(uint8_t data_type, uint8_t *data, uint16_t */ esp_err_t bt_hci_log_record_hci_adv(uint8_t data_type, uint8_t *data, uint8_t data_len); +/** + * + * @brief Convert HCI H4 packet type to HCI log data type. + * + * @param h4_type : HCI H4 packet type byte + * + * @return corresponding HCI log data type + * + */ +uint8_t bt_hci_log_h4_type_to_data_type(uint8_t h4_type); + #ifdef __cplusplus } #endif diff --git a/components/bt/common/include/bt_ble_insights.h b/components/bt/common/include/bt_ble_insights.h new file mode 100644 index 00000000000..c433eed982e --- /dev/null +++ b/components/bt/common/include/bt_ble_insights.h @@ -0,0 +1,21 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef __BT_BLE_INSIGHTS_H__ +#define __BT_BLE_INSIGHTS_H__ + +#if __has_include("ble_insights.h") +#include "ble_insights.h" +#define BT_BLE_INSIGHTS_AVAILABLE 1 +#else +#define BT_BLE_INSIGHTS_AVAILABLE 0 +static inline void ble_insights_log(const char *log) +{ + (void)log; +} +#endif + +#endif /* __BT_BLE_INSIGHTS_H__ */ diff --git a/components/bt/common/include/bt_common.h b/components/bt/common/include/bt_common.h index 372ea68e7e9..343600f36e6 100644 --- a/components/bt/common/include/bt_common.h +++ b/components/bt/common/include/bt_common.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -100,6 +100,13 @@ #define BT_HCI_LOG_INCLUDED FALSE #endif +// HCI INSIGHTS LOG +#if UC_BT_HCI_LOG_INSIGHTS_ENABLE +#define BT_HCI_INSIGHTS_INCLUDED UC_BT_HCI_LOG_INSIGHTS_ENABLE +#else +#define BT_HCI_INSIGHTS_INCLUDED FALSE +#endif + // HCI LOG TO SPI #if UC_BT_BLE_LOG_SPI_OUT_HCI_ENABLED #define BT_BLE_LOG_SPI_OUT_HCI_ENABLED UC_BT_BLE_LOG_SPI_OUT_HCI_ENABLED @@ -117,10 +124,10 @@ #if UC_BT_HCI_LOG_DATA_BUFFER_SIZE #define HCI_LOG_DATA_BUFFER_SIZE UC_BT_HCI_LOG_DATA_BUFFER_SIZE #else -#define HCI_BUFFER_SIZE (5) +#define HCI_LOG_DATA_BUFFER_SIZE (5) #endif -#if UC_BT_HCI_ADV_BUFFER_SIZE +#if UC_BT_HCI_LOG_ADV_BUFFER_SIZE #define HCI_LOG_ADV_BUFFER_SIZE UC_BT_HCI_LOG_ADV_BUFFER_SIZE #else #define HCI_LOG_ADV_BUFFER_SIZE (5) @@ -277,4 +284,8 @@ typedef struct { #define BD_ADDR_LEN 6 /* Device address length */ typedef UINT8 BD_ADDR[BD_ADDR_LEN]; /* Device address */ +#if (BT_HCI_LOG_INCLUDED == TRUE) && BT_HCI_INSIGHTS_INCLUDED +void bt_hci_log_record_insights(uint8_t data_type, const uint8_t *data, uint16_t data_len); +#endif + #endif /* _BT_COMMON_H_ */ diff --git a/components/bt/common/include/bt_user_config.h b/components/bt/common/include/bt_user_config.h index 3ddc188f13b..bc8a41c77ec 100644 --- a/components/bt/common/include/bt_user_config.h +++ b/components/bt/common/include/bt_user_config.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -176,4 +176,12 @@ #define UC_BT_HCI_LOG_ADV_BUFFER_SIZE (5) #endif + +// HCI LOG INSIGHTS +#ifdef CONFIG_BT_HCI_LOG_INSIGHTS_ENABLE +#define UC_BT_HCI_LOG_INSIGHTS_ENABLE TRUE +#else +#define UC_BT_HCI_LOG_INSIGHTS_ENABLE FALSE +#endif + #endif /* __BT_USER_CONFIG_H__ */ diff --git a/components/bt/controller/CMakeLists.txt b/components/bt/controller/CMakeLists.txt new file mode 100644 index 00000000000..6c1dfe5340f --- /dev/null +++ b/components/bt/controller/CMakeLists.txt @@ -0,0 +1,133 @@ +# Function to register the libraries for the BT controller +function(register_bt_ctrl_libs) + # TODO: The coex will use controller library even if CONFIG_BT_CONTROLLER_ENABLED=n which is not correct. + if(CONFIG_IDF_TARGET_ESP32) + add_prebuilt_library(bt_btdm_app "${CMAKE_CURRENT_FUNCTION_LIST_DIR}/lib_esp32/esp32/libbtdm_app.a") + target_link_libraries(${COMPONENT_LIB} PRIVATE bt_btdm_app) + target_link_options(${COMPONENT_LIB} INTERFACE "SHELL:-u ld_include_hli_vectors_bt") + endif() + + if(NOT CONFIG_BT_CONTROLLER_ENABLED) + return() + endif() + + if(CONFIG_IDF_TARGET_ESP32C3) + if(CONFIG_BT_CTRL_RUN_IN_FLASH_ONLY) + set(lib_name "btdm_app_flash") + else() + set(lib_name "btdm_app") + endif() + add_prebuilt_library(bt_btdm_app + "${CMAKE_CURRENT_FUNCTION_LIST_DIR}/lib_esp32c3_family/esp32c3/lib${lib_name}.a") + target_link_libraries(${COMPONENT_LIB} PRIVATE bt_btdm_app) + elseif(CONFIG_IDF_TARGET_ESP32S3) + if(CONFIG_BT_CTRL_RUN_IN_FLASH_ONLY) + set(lib_name "btdm_app_flash") + else() + set(lib_name "btdm_app") + endif() + add_prebuilt_library(bt_btdm_app + "${CMAKE_CURRENT_FUNCTION_LIST_DIR}/lib_esp32c3_family/esp32s3/lib${lib_name}.a") + target_link_libraries(${COMPONENT_LIB} PRIVATE bt_btdm_app) + elseif(NOT CONFIG_IDF_TARGET_ESP32) + set(lib_path "${CMAKE_CURRENT_FUNCTION_LIST_DIR}/lib_${TARGET_SRC_NAME}/${TARGET_SRC_NAME}-bt-lib") + # BLE controller library + if(NOT CONFIG_BT_DUAL_MODE_ARCH OR CONFIG_BT_CTRL_BLE_ENABLE) + if(EXISTS "${lib_path}/libble_app.a") + if(CONFIG_BT_CTRL_RUN_IN_FLASH_ONLY AND EXISTS "${lib_path}/libble_app_flash.a") + add_prebuilt_library(libble_app "${lib_path}/libble_app_flash.a" REQUIRES esp_phy bt) + else() + add_prebuilt_library(libble_app "${lib_path}/libble_app.a" REQUIRES esp_phy bt) + endif() + else() + if(CONFIG_BT_CTRL_RUN_IN_FLASH_ONLY AND EXISTS "${lib_path}/${idf_target}/libble_app_flash.a") + add_prebuilt_library(libble_app "${lib_path}/${idf_target}/libble_app_flash.a" REQUIRES esp_phy bt) + else() + add_prebuilt_library(libble_app "${lib_path}/${idf_target}/libble_app.a" REQUIRES esp_phy bt) + endif() + endif() + target_link_libraries(${COMPONENT_LIB} PRIVATE libble_app) + endif() + # BREDR controller library + if(CONFIG_BT_CTRL_BREDR_ENABLE) + if(EXISTS "${lib_path}/libbredr_app.a") + add_prebuilt_library(libbredr_app "${lib_path}/libbredr_app.a" REQUIRES esp_phy bt) + else() + add_prebuilt_library(libbredr_app "${lib_path}/${idf_target}/libbredr_app.a" REQUIRES esp_phy bt) + endif() + target_link_libraries(${COMPONENT_LIB} PRIVATE libbredr_app) + endif() + # BTDM common library + if(CONFIG_BT_DUAL_MODE_ARCH) + if(EXISTS "${lib_path}/libbtdm_common.a") + add_prebuilt_library(libbtdm_common "${lib_path}/libbtdm_common.a" esp_phy bt) + else() + add_prebuilt_library(libbtdm_common "${lib_path}/${idf_target}/libbtdm_common.a" esp_phy bt) + endif() + target_link_libraries(${COMPONENT_LIB} PRIVATE libbtdm_common) + endif() + endif() + + # Used to dump logs in the panic handler + if(CONFIG_BT_LE_CONTROLLER_LOG_WRAP_PANIC_HANDLER_ENABLE) + target_link_options(${COMPONENT_LIB} INTERFACE "-Wl,--wrap=esp_panic_handler") + endif() +endfunction() + +set(bt_ctrl_srcs "" PARENT_SCOPE) +set(bt_ctrl_include_dirs "" PARENT_SCOPE) +set(bt_ctrl_ldscripts "${ldscripts}") +set(bt_ctrl_ldscripts "${bt_ctrl_ldscripts}" PARENT_SCOPE) + +# API headers that are used in the docs are also compiled +# even if CONFIG_BT_ENABLED=n as long as CONFIG_IDF_DOC_BUILD=y +if(CONFIG_IDF_DOC_BUILD AND CONFIG_SOC_BT_SUPPORTED) + get_filename_component( + _bt_ctrl_absolute_include_dir + "${CMAKE_CURRENT_LIST_DIR}/../include/${TARGET_SRC_NAME}/include" + ABSOLUTE + ) + set(bt_ctrl_include_dirs "${_bt_ctrl_absolute_include_dir}" PARENT_SCOPE) + return() +endif() + +if(NOT CONFIG_BT_CONTROLLER_ENABLED) + return() +endif() + +# Get all the .c files in the TARGET_SRC_NAME directory +file(GLOB_RECURSE _CTRL_SRCS "${TARGET_SRC_NAME}/*.c") +list(APPEND bt_ctrl_srcs ${_CTRL_SRCS}) +list(APPEND bt_ctrl_include_dirs "${CMAKE_CURRENT_LIST_DIR}/../include/${TARGET_SRC_NAME}/include") + +# TODO: Move this to a separate file for the controller and the host +if(CONFIG_SOC_ESP_NIMBLE_CONTROLLER AND NOT CONFIG_BT_NIMBLE_ENABLED) + list(APPEND bt_ctrl_srcs + "${CMAKE_CURRENT_LIST_DIR}/../host/nimble/port/src/esp_nimble_mem.c" + ) + list(APPEND bt_ctrl_include_dirs + "${CMAKE_CURRENT_LIST_DIR}/../host/nimble/port/include" + ) +endif() + +# Add the linker scripts +if(CONFIG_IDF_TARGET_ESP32) + list(APPEND bt_ctrl_srcs "${CMAKE_CURRENT_LIST_DIR}/esp32/hli_vectors.S" + "${CMAKE_CURRENT_LIST_DIR}/esp32/hli_api.c") + list(APPEND bt_ctrl_ldscripts "${CMAKE_CURRENT_LIST_DIR}/linker_rw_bt_controller.lf") +elseif(CONFIG_IDF_TARGET_ESP32C3) + list(APPEND bt_ctrl_ldscripts "${CMAKE_CURRENT_LIST_DIR}/linker_rw_bt_controller.lf") +elseif(CONFIG_IDF_TARGET_ESP32S3) + list(APPEND bt_ctrl_ldscripts "${CMAKE_CURRENT_LIST_DIR}/linker_rw_bt_controller.lf") +elseif(CONFIG_IDF_TARGET_ESP32C2) + set(bt_ctrl_ldscripts "${CMAKE_CURRENT_LIST_DIR}/linker_esp32c2.lf") +elseif(CONFIG_BT_DUAL_MODE_ARCH) + list(APPEND bt_ctrl_ldscripts "${CMAKE_CURRENT_LIST_DIR}/linker_esp_btdm_controller.lf") +else() + list(APPEND bt_ctrl_ldscripts "${CMAKE_CURRENT_LIST_DIR}/linker_esp_ble_controller.lf") +endif() + +# Export the variables to the parent scope +set(bt_ctrl_srcs "${bt_ctrl_srcs}" PARENT_SCOPE) +set(bt_ctrl_include_dirs "${bt_ctrl_include_dirs}" PARENT_SCOPE) +set(bt_ctrl_ldscripts "${bt_ctrl_ldscripts}" PARENT_SCOPE) diff --git a/components/bt/controller/esp32/hli_vectors.S b/components/bt/controller/esp32/hli_vectors.S index 25a631d7ede..8339e3c2851 100644 --- a/components/bt/controller/esp32/hli_vectors.S +++ b/components/bt/controller/esp32/hli_vectors.S @@ -46,7 +46,7 @@ _l4_save_ctx: xt_highint4: -#if CONFIG_ESP32_ECO3_CACHE_LOCK_FIX +#if CONFIG_ESP_INT_WDT && CONFIG_ESP32_ECO3_CACHE_LOCK_FIX /* Here, Timer2 is used to count a little time(50us). The subsequent dram0 write operation is blocked due to live lock, which will @@ -96,7 +96,7 @@ xt_highint4: rsr a2, EPC1 s32i a2, a0, 24 -#if CONFIG_ESP32_ECO3_CACHE_LOCK_FIX +#if CONFIG_ESP_INT_WDT && CONFIG_ESP32_ECO3_CACHE_LOCK_FIX movi a0, 0 xsr a0, XT_REG_INTENABLE /* disable all interrupts */ movi a2, ~(1<<16) diff --git a/components/bt/controller/esp32c2/dummy.c b/components/bt/controller/esp32c2/dummy.c index 819a3ed1b2f..1f542caf397 100644 --- a/components/bt/controller/esp32c2/dummy.c +++ b/components/bt/controller/esp32c2/dummy.c @@ -456,6 +456,8 @@ void r_ble_lll_conn_event_delete_and_reschedule(void){} void r_ble_lll_conn_event_delete_and_reschedule_eco4(void){} void r_ble_ll_utils_verify_aa(void){} void r_ble_ll_utils_verify_aa_eco4(void){} +int r_ble_lll_conn_process_rx_data_after_halt(void){return 0;} +int r_ble_lll_conn_process_rx_data_after_halt_eco4(void){return 0;} #endif // !DEFAULT_BT_LE_ROLE_PERIPHERAL #if !DEFAULT_BT_LE_ROLE_CENTROL && !DEFAULT_BT_LE_ROLE_PERIPHERAL diff --git a/components/bt/controller/esp32c3/Kconfig.in b/components/bt/controller/esp32c3/Kconfig.in index d6acc873092..383ebb16b4e 100644 --- a/components/bt/controller/esp32c3/Kconfig.in +++ b/components/bt/controller/esp32c3/Kconfig.in @@ -2,6 +2,15 @@ config BT_CTRL_MODE_EFF int default 1 +config BT_CTRL_CHECK_CONFIG_EFF + int + default 1 + help + Marker that controller Kconfig is active (always set in sdkconfig). + Must not be unset in normal IDF builds. Controller-only integrations + that do not export this symbol rely on esp_bt.h to apply compile-time + defaults for missing BLE feature CONFIG_* names. + config BT_CTRL_BLE_MAX_ACT int "BLE Max Instances" default 6 @@ -566,7 +575,7 @@ config BT_CTRL_CHECK_CONNECT_IND_ACCESS_ADDRESS config BT_CTRL_BLE_MIN_CONN_INTERVAL_ENABLE bool "Allow BLE connection interval below the spec minimum" default y - select BT_BLE_HOST_ALLOW_SUB_SPEC_MIN_CONN_INT if BT_BLUEDROID_ENABLED + select BT_BLE_HOST_ALLOW_SUB_SPEC_MIN_CONN_INT if BT_BLUEDROID_ENABLED || BT_NIMBLE_ENABLED help Enabling this option allows the BLE controller to use a connection interval smaller than the Bluetooth Core specification minimum of 7.5 ms. On @@ -581,12 +590,9 @@ config BT_CTRL_BLE_MIN_CONN_INTERVAL_ENABLE This option is enabled by default. Disable it to stay compliant with the BLE specification (minimum connection interval 7.5 ms). - Host stack: When Bluedroid is the BLE host, enabling this option also selects - BT_BLE_HOST_ALLOW_SUB_SPEC_MIN_CONN_INT so the host accepts connection - intervals below the spec minimum. NimBLE host does not provide equivalent - support yet; it is planned for a future release. Until then, use Bluedroid - if you need coordinated host and controller behavior for sub-minimum - intervals. + Host stack: Enabling this option also selects + BT_BLE_HOST_ALLOW_SUB_SPEC_MIN_CONN_INT so the active BLE host accepts + connection intervals below the spec minimum. menu "Controller debug log Options (Experimental)" config BT_CTRL_LE_LOG_EN diff --git a/components/bt/controller/esp32c3/bt.c b/components/bt/controller/esp32c3/bt.c index 8e3a94b023e..72acdcfbcf5 100644 --- a/components/bt/controller/esp32c3/bt.c +++ b/components/bt/controller/esp32c3/bt.c @@ -1867,6 +1867,11 @@ esp_err_t esp_bt_controller_init(esp_bt_controller_config_t *cfg) ESP_LOGI(BT_LOG_TAG, "BT controller compile version [%s]", btdm_controller_get_compile_version()); +#ifndef CONFIG_BT_CTRL_CHECK_CONFIG_EFF + ESP_LOGW(BT_LOG_TAG, "CONFIG_BT_CTRL_CHECK_CONFIG_EFF is not defined; " + "using compile-time default BLE controller feature options"); +#endif + #if (CONFIG_BT_CTRL_RUN_IN_FLASH_ONLY) ESP_LOGI(BT_LOG_TAG,"Put all controller code in flash"); #endif diff --git a/components/bt/controller/lib_esp32 b/components/bt/controller/lib_esp32 index 10f4e9ad626..98c6de2d541 160000 --- a/components/bt/controller/lib_esp32 +++ b/components/bt/controller/lib_esp32 @@ -1 +1 @@ -Subproject commit 10f4e9ad6260788765863ed7f05e0e649b288e36 +Subproject commit 98c6de2d5411a7297dbba00387dfca2bcc44fd18 diff --git a/components/bt/controller/lib_esp32c2/esp32c2-bt-lib b/components/bt/controller/lib_esp32c2/esp32c2-bt-lib index a1e63f9079c..6a5c0b8bfca 160000 --- a/components/bt/controller/lib_esp32c2/esp32c2-bt-lib +++ b/components/bt/controller/lib_esp32c2/esp32c2-bt-lib @@ -1 +1 @@ -Subproject commit a1e63f9079c48dbb01033f7930ba09f9336ea6ca +Subproject commit 6a5c0b8bfca1a2e984e89431b8ebf9b521dad5ea diff --git a/components/bt/controller/lib_esp32c3_family b/components/bt/controller/lib_esp32c3_family index 58d499bba10..0a08c4b32f3 160000 --- a/components/bt/controller/lib_esp32c3_family +++ b/components/bt/controller/lib_esp32c3_family @@ -1 +1 @@ -Subproject commit 58d499bba1019a80a622df60aa38f59c1e4565ba +Subproject commit 0a08c4b32f3666003080b662a1a61794da24ff0f diff --git a/components/bt/controller/lib_esp32c5/esp32c5-bt-lib b/components/bt/controller/lib_esp32c5/esp32c5-bt-lib index a55a1f74e6d..c328228f53f 160000 --- a/components/bt/controller/lib_esp32c5/esp32c5-bt-lib +++ b/components/bt/controller/lib_esp32c5/esp32c5-bt-lib @@ -1 +1 @@ -Subproject commit a55a1f74e6db6b3bae84b5cc31e5462782d2871f +Subproject commit c328228f53fe167cc9fbd05b5bef37158f64956d diff --git a/components/bt/controller/lib_esp32c6/esp32c6-bt-lib b/components/bt/controller/lib_esp32c6/esp32c6-bt-lib index 0fa003f25c5..e7be018522c 160000 --- a/components/bt/controller/lib_esp32c6/esp32c6-bt-lib +++ b/components/bt/controller/lib_esp32c6/esp32c6-bt-lib @@ -1 +1 @@ -Subproject commit 0fa003f25c51367e81ffe314d1c0d807b81feef0 +Subproject commit e7be018522c85e67c298effc8ffa7f5b84887a7b diff --git a/components/bt/controller/lib_esp32h2/esp32h2-bt-lib b/components/bt/controller/lib_esp32h2/esp32h2-bt-lib index 6b063dcfab9..b2b8fd009c3 160000 --- a/components/bt/controller/lib_esp32h2/esp32h2-bt-lib +++ b/components/bt/controller/lib_esp32h2/esp32h2-bt-lib @@ -1 +1 @@ -Subproject commit 6b063dcfab91135253adbb46a782b447aae8d0de +Subproject commit b2b8fd009c31816497d55726af297761147beb07 diff --git a/components/bt/linker_esp32c2.lf b/components/bt/controller/linker_esp32c2.lf similarity index 100% rename from components/bt/linker_esp32c2.lf rename to components/bt/controller/linker_esp32c2.lf diff --git a/components/bt/linker_esp_ble_controller.lf b/components/bt/controller/linker_esp_ble_controller.lf similarity index 100% rename from components/bt/linker_esp_ble_controller.lf rename to components/bt/controller/linker_esp_ble_controller.lf diff --git a/components/bt/linker_rw_bt_controller.lf b/components/bt/controller/linker_rw_bt_controller.lf similarity index 100% rename from components/bt/linker_rw_bt_controller.lf rename to components/bt/controller/linker_rw_bt_controller.lf diff --git a/components/bt/esp_ble_mesh/CMakeLists.txt b/components/bt/esp_ble_mesh/CMakeLists.txt new file mode 100644 index 00000000000..648bd8e3451 --- /dev/null +++ b/components/bt/esp_ble_mesh/CMakeLists.txt @@ -0,0 +1,221 @@ +function(register_ble_mesh_libs) + if(NOT CONFIG_BLE_MESH) + return() + endif() + + if(CONFIG_BLE_MESH_V11_SUPPORT) + add_prebuilt_library(ble_mesh "esp_ble_mesh/lib/lib/${idf_target}/libble_mesh.a") + target_link_libraries(${COMPONENT_LIB} PRIVATE ble_mesh) + endif() +endfunction() + +set(ble_mesh_srcs "" PARENT_SCOPE) +set(ble_mesh_include_dirs "" PARENT_SCOPE) + +# API headers that are used in the docs are also compiled +# even if CONFIG_BT_ENABLED=n as long as CONFIG_IDF_DOC_BUILD=y +if(CONFIG_IDF_DOC_BUILD) + set(ble_mesh_include_dirs + "${CMAKE_CURRENT_LIST_DIR}/common/include" + "${CMAKE_CURRENT_LIST_DIR}/core" + "${CMAKE_CURRENT_LIST_DIR}/core/include" + "${CMAKE_CURRENT_LIST_DIR}/core/storage" + "${CMAKE_CURRENT_LIST_DIR}/btc/include" + "${CMAKE_CURRENT_LIST_DIR}/models/common/include" + "${CMAKE_CURRENT_LIST_DIR}/models/client/include" + "${CMAKE_CURRENT_LIST_DIR}/models/server/include" + "${CMAKE_CURRENT_LIST_DIR}/api/core/include" + "${CMAKE_CURRENT_LIST_DIR}/api/models/include" + "${CMAKE_CURRENT_LIST_DIR}/api" + # BLE Mesh v1.1 headers + "${CMAKE_CURRENT_LIST_DIR}/lib/include" + "${CMAKE_CURRENT_LIST_DIR}/v1.1/api/core/include" + "${CMAKE_CURRENT_LIST_DIR}/v1.1/api/models/include" + "${CMAKE_CURRENT_LIST_DIR}/v1.1/btc/include" + "${CMAKE_CURRENT_LIST_DIR}/v1.1/include" + "${CMAKE_CURRENT_LIST_DIR}/v1.1/dfu" + "${CMAKE_CURRENT_LIST_DIR}/v1.1/mbt" + PARENT_SCOPE + ) + return() +endif() + +if(NOT CONFIG_BLE_MESH) + return() +endif() + +if(CONFIG_BLE_MESH_USE_UNIFIED_CRYPTO) + message(WARNING "This configuration path is deprecated and will be removed" + "in a future version. Please use the corresponding Kconfig" + "options under $IDF_PATH/components/bt/common/Kconfig.") +endif() + +list(APPEND ble_mesh_srcs + "${CMAKE_CURRENT_LIST_DIR}/api/core/esp_ble_mesh_ble_api.c" + "${CMAKE_CURRENT_LIST_DIR}/api/core/esp_ble_mesh_common_api.c" + "${CMAKE_CURRENT_LIST_DIR}/api/core/esp_ble_mesh_local_data_operation_api.c" + "${CMAKE_CURRENT_LIST_DIR}/api/core/esp_ble_mesh_low_power_api.c" + "${CMAKE_CURRENT_LIST_DIR}/api/core/esp_ble_mesh_networking_api.c" + "${CMAKE_CURRENT_LIST_DIR}/api/core/esp_ble_mesh_provisioning_api.c" + "${CMAKE_CURRENT_LIST_DIR}/api/core/esp_ble_mesh_proxy_api.c" + "${CMAKE_CURRENT_LIST_DIR}/api/models/esp_ble_mesh_config_model_api.c" + "${CMAKE_CURRENT_LIST_DIR}/api/models/esp_ble_mesh_generic_model_api.c" + "${CMAKE_CURRENT_LIST_DIR}/api/models/esp_ble_mesh_health_model_api.c" + "${CMAKE_CURRENT_LIST_DIR}/api/models/esp_ble_mesh_lighting_model_api.c" + "${CMAKE_CURRENT_LIST_DIR}/api/models/esp_ble_mesh_sensor_model_api.c" + "${CMAKE_CURRENT_LIST_DIR}/api/models/esp_ble_mesh_time_scene_model_api.c" + "${CMAKE_CURRENT_LIST_DIR}/btc/btc_ble_mesh_ble.c" + "${CMAKE_CURRENT_LIST_DIR}/btc/btc_ble_mesh_config_model.c" + "${CMAKE_CURRENT_LIST_DIR}/btc/btc_ble_mesh_generic_model.c" + "${CMAKE_CURRENT_LIST_DIR}/btc/btc_ble_mesh_health_model.c" + "${CMAKE_CURRENT_LIST_DIR}/btc/btc_ble_mesh_lighting_model.c" + "${CMAKE_CURRENT_LIST_DIR}/btc/btc_ble_mesh_prov.c" + "${CMAKE_CURRENT_LIST_DIR}/btc/btc_ble_mesh_sensor_model.c" + "${CMAKE_CURRENT_LIST_DIR}/btc/btc_ble_mesh_time_scene_model.c" + "${CMAKE_CURRENT_LIST_DIR}/common/atomic.c" + "${CMAKE_CURRENT_LIST_DIR}/common/buf.c" + "${CMAKE_CURRENT_LIST_DIR}/common/common.c" + "${CMAKE_CURRENT_LIST_DIR}/common/kernel.c" + "${CMAKE_CURRENT_LIST_DIR}/common/mutex.c" + "${CMAKE_CURRENT_LIST_DIR}/common/queue.c" + "${CMAKE_CURRENT_LIST_DIR}/common/timer.c" + "${CMAKE_CURRENT_LIST_DIR}/common/utils.c" + + "${CMAKE_CURRENT_LIST_DIR}/core/storage/settings_nvs.c" + "${CMAKE_CURRENT_LIST_DIR}/core/storage/settings_uid.c" + "${CMAKE_CURRENT_LIST_DIR}/core/storage/settings.c" + "${CMAKE_CURRENT_LIST_DIR}/core/access.c" + "${CMAKE_CURRENT_LIST_DIR}/core/adv_common.c" + "${CMAKE_CURRENT_LIST_DIR}/core/beacon.c" + "${CMAKE_CURRENT_LIST_DIR}/core/cfg_cli.c" + "${CMAKE_CURRENT_LIST_DIR}/core/cfg_srv.c" + "${CMAKE_CURRENT_LIST_DIR}/core/crypto.c" + "${CMAKE_CURRENT_LIST_DIR}/core/fast_prov.c" + "${CMAKE_CURRENT_LIST_DIR}/core/friend.c" + "${CMAKE_CURRENT_LIST_DIR}/core/health_cli.c" + "${CMAKE_CURRENT_LIST_DIR}/core/health_srv.c" + "${CMAKE_CURRENT_LIST_DIR}/core/heartbeat.c" + "${CMAKE_CURRENT_LIST_DIR}/core/local.c" + "${CMAKE_CURRENT_LIST_DIR}/core/lpn.c" + "${CMAKE_CURRENT_LIST_DIR}/core/main.c" + "${CMAKE_CURRENT_LIST_DIR}/core/net.c" + "${CMAKE_CURRENT_LIST_DIR}/core/prov_common.c" + "${CMAKE_CURRENT_LIST_DIR}/core/prov_node.c" + "${CMAKE_CURRENT_LIST_DIR}/core/prov_pvnr.c" + "${CMAKE_CURRENT_LIST_DIR}/core/proxy_client.c" + "${CMAKE_CURRENT_LIST_DIR}/core/proxy_server.c" + "${CMAKE_CURRENT_LIST_DIR}/core/pvnr_mgmt.c" + "${CMAKE_CURRENT_LIST_DIR}/core/rpl.c" + "${CMAKE_CURRENT_LIST_DIR}/core/scan.c" + "${CMAKE_CURRENT_LIST_DIR}/core/test.c" + "${CMAKE_CURRENT_LIST_DIR}/models/common/device_property.c" + "${CMAKE_CURRENT_LIST_DIR}/models/common/model_common.c" + "${CMAKE_CURRENT_LIST_DIR}/models/client/client_common.c" + "${CMAKE_CURRENT_LIST_DIR}/models/client/generic_client.c" + "${CMAKE_CURRENT_LIST_DIR}/models/client/lighting_client.c" + "${CMAKE_CURRENT_LIST_DIR}/models/client/sensor_client.c" + "${CMAKE_CURRENT_LIST_DIR}/models/client/time_scene_client.c" + "${CMAKE_CURRENT_LIST_DIR}/models/server/generic_server.c" + "${CMAKE_CURRENT_LIST_DIR}/models/server/lighting_server.c" + "${CMAKE_CURRENT_LIST_DIR}/models/server/sensor_server.c" + "${CMAKE_CURRENT_LIST_DIR}/models/server/server_common.c" + "${CMAKE_CURRENT_LIST_DIR}/models/server/state_binding.c" + "${CMAKE_CURRENT_LIST_DIR}/models/server/state_transition.c" + "${CMAKE_CURRENT_LIST_DIR}/models/server/time_scene_server.c" +) + +if(CONFIG_BLE_MESH_SUPPORT_MULTI_ADV) + list(APPEND ble_mesh_srcs "${CMAKE_CURRENT_LIST_DIR}/core/ext_adv.c") +else() + list(APPEND ble_mesh_srcs "${CMAKE_CURRENT_LIST_DIR}/core/adv.c") +endif() + +if(CONFIG_BLE_MESH_SUPPORT_BLE_ADV) + list(APPEND ble_mesh_srcs "${CMAKE_CURRENT_LIST_DIR}/core/ble_adv.c") +endif() + +# Select crypto implementation based on config +if(CONFIG_BT_SMP_CRYPTO_STACK_TINYCRYPT) + list(APPEND ble_mesh_srcs "${CMAKE_CURRENT_LIST_DIR}/common/crypto_tc.c") +elseif(CONFIG_BT_SMP_CRYPTO_STACK_MBEDTLS) + if(CONFIG_MBEDTLS_VER_4_X_SUPPORT) + list(APPEND ble_mesh_srcs "${CMAKE_CURRENT_LIST_DIR}/common/crypto_psa.c") + else() + list(APPEND ble_mesh_srcs "${CMAKE_CURRENT_LIST_DIR}/common/crypto_mbedtls.c") + endif() +endif() + +list(APPEND ble_mesh_include_dirs + "${CMAKE_CURRENT_LIST_DIR}/common/include" + "${CMAKE_CURRENT_LIST_DIR}/core" + "${CMAKE_CURRENT_LIST_DIR}/core/include" + "${CMAKE_CURRENT_LIST_DIR}/core/storage" + "${CMAKE_CURRENT_LIST_DIR}/btc/include" + "${CMAKE_CURRENT_LIST_DIR}/models/common/include" + "${CMAKE_CURRENT_LIST_DIR}/models/client/include" + "${CMAKE_CURRENT_LIST_DIR}/models/server/include" + "${CMAKE_CURRENT_LIST_DIR}/api/core/include" + "${CMAKE_CURRENT_LIST_DIR}/api/models/include" + "${CMAKE_CURRENT_LIST_DIR}/api" +) + +if(CONFIG_BLE_MESH_V11_SUPPORT) + list(APPEND ble_mesh_include_dirs + "${CMAKE_CURRENT_LIST_DIR}/lib/include" + "${CMAKE_CURRENT_LIST_DIR}/v1.1/api/core/include" + "${CMAKE_CURRENT_LIST_DIR}/v1.1/api/models/include" + "${CMAKE_CURRENT_LIST_DIR}/v1.1/btc/include" + "${CMAKE_CURRENT_LIST_DIR}/v1.1/include" + "${CMAKE_CURRENT_LIST_DIR}/v1.1/dfu" + "${CMAKE_CURRENT_LIST_DIR}/v1.1/mbt" + ) + + list(APPEND ble_mesh_srcs + "${CMAKE_CURRENT_LIST_DIR}/v1.1/api/core/esp_ble_mesh_agg_model_api.c" + "${CMAKE_CURRENT_LIST_DIR}/v1.1/api/core/esp_ble_mesh_brc_model_api.c" + "${CMAKE_CURRENT_LIST_DIR}/v1.1/api/core/esp_ble_mesh_cm_data_api.c" + "${CMAKE_CURRENT_LIST_DIR}/v1.1/api/core/esp_ble_mesh_df_model_api.c" + "${CMAKE_CURRENT_LIST_DIR}/v1.1/api/core/esp_ble_mesh_lcd_model_api.c" + "${CMAKE_CURRENT_LIST_DIR}/v1.1/api/core/esp_ble_mesh_odp_model_api.c" + "${CMAKE_CURRENT_LIST_DIR}/v1.1/api/core/esp_ble_mesh_prb_model_api.c" + "${CMAKE_CURRENT_LIST_DIR}/v1.1/api/core/esp_ble_mesh_rpr_model_api.c" + "${CMAKE_CURRENT_LIST_DIR}/v1.1/api/core/esp_ble_mesh_sar_model_api.c" + "${CMAKE_CURRENT_LIST_DIR}/v1.1/api/core/esp_ble_mesh_srpl_model_api.c" + "${CMAKE_CURRENT_LIST_DIR}/v1.1/api/models/esp_ble_mesh_mbt_model_api.c" + "${CMAKE_CURRENT_LIST_DIR}/v1.1/api/models/esp_ble_mesh_dfu_model_api.c" + "${CMAKE_CURRENT_LIST_DIR}/v1.1/api/models/esp_ble_mesh_dfu_slot_api.c" + "${CMAKE_CURRENT_LIST_DIR}/v1.1/btc/btc_ble_mesh_agg_model.c" + "${CMAKE_CURRENT_LIST_DIR}/v1.1/btc/btc_ble_mesh_brc_model.c" + "${CMAKE_CURRENT_LIST_DIR}/v1.1/btc/btc_ble_mesh_df_model.c" + "${CMAKE_CURRENT_LIST_DIR}/v1.1/btc/btc_ble_mesh_dfu_model.c" + "${CMAKE_CURRENT_LIST_DIR}/v1.1/btc/btc_ble_mesh_dfu_slot.c" + "${CMAKE_CURRENT_LIST_DIR}/v1.1/btc/btc_ble_mesh_lcd_model.c" + "${CMAKE_CURRENT_LIST_DIR}/v1.1/btc/btc_ble_mesh_mbt_model.c" + "${CMAKE_CURRENT_LIST_DIR}/v1.1/btc/btc_ble_mesh_odp_model.c" + "${CMAKE_CURRENT_LIST_DIR}/v1.1/btc/btc_ble_mesh_prb_model.c" + "${CMAKE_CURRENT_LIST_DIR}/v1.1/btc/btc_ble_mesh_rpr_model.c" + "${CMAKE_CURRENT_LIST_DIR}/v1.1/btc/btc_ble_mesh_sar_model.c" + "${CMAKE_CURRENT_LIST_DIR}/v1.1/btc/btc_ble_mesh_srpl_model.c" + "${CMAKE_CURRENT_LIST_DIR}/v1.1/mbt/blob_srv.c" + "${CMAKE_CURRENT_LIST_DIR}/v1.1/mbt/blob_cli.c" + "${CMAKE_CURRENT_LIST_DIR}/v1.1/dfu/dfu_cli.c" + "${CMAKE_CURRENT_LIST_DIR}/v1.1/dfu/dfu_srv.c" + "${CMAKE_CURRENT_LIST_DIR}/v1.1/dfu/dfu_slot.c" + "${CMAKE_CURRENT_LIST_DIR}/v1.1/dfu/dfu_metadata.c" + "${CMAKE_CURRENT_LIST_DIR}/v1.1/dfu/dfd_srv.c" + "${CMAKE_CURRENT_LIST_DIR}/v1.1/dfu/dfd_cli.c" + "${CMAKE_CURRENT_LIST_DIR}/lib/ext.c" + ) + + if(CONFIG_BLE_MESH_SAR_ENHANCEMENT) + list(APPEND ble_mesh_srcs "${CMAKE_CURRENT_LIST_DIR}/core/transport.enh.c") + else() + list(APPEND ble_mesh_srcs "${CMAKE_CURRENT_LIST_DIR}/core/transport.c") + endif() +else() + list(APPEND ble_mesh_srcs "${CMAKE_CURRENT_LIST_DIR}/core/transport.c") +endif() + +# Export the variables to the parent scope +set(ble_mesh_srcs "${ble_mesh_srcs}" PARENT_SCOPE) +set(ble_mesh_include_dirs "${ble_mesh_include_dirs}" PARENT_SCOPE) diff --git a/components/bt/esp_ble_mesh/api/core/esp_ble_mesh_ble_api.c b/components/bt/esp_ble_mesh/api/core/esp_ble_mesh_ble_api.c index 935918e7b17..f783b23f04e 100644 --- a/components/bt/esp_ble_mesh/api/core/esp_ble_mesh_ble_api.c +++ b/components/bt/esp_ble_mesh/api/core/esp_ble_mesh_ble_api.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2017-2024 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2017-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -75,6 +75,13 @@ esp_err_t esp_ble_mesh_start_ble_scanning(esp_ble_mesh_ble_scan_param_t *param) btc_ble_mesh_ble_args_t arg = {0}; btc_msg_t msg = {0}; + /* Note: + * Currently the function is only used to enable reporting + * non-mesh advertising packets to the application layer, + * and the input parameter will not be used for now. + */ + ARG_UNUSED(param); + ESP_BLE_HOST_STATUS_CHECK(ESP_BLE_HOST_STATUS_ENABLED); msg.sig = BTC_SIG_API_CALL; diff --git a/components/bt/esp_ble_mesh/api/core/esp_ble_mesh_local_data_operation_api.c b/components/bt/esp_ble_mesh/api/core/esp_ble_mesh_local_data_operation_api.c index e4f2e67b678..ef9b079c898 100644 --- a/components/bt/esp_ble_mesh/api/core/esp_ble_mesh_local_data_operation_api.c +++ b/components/bt/esp_ble_mesh/api/core/esp_ble_mesh_local_data_operation_api.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2017-2021 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2017-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -131,8 +131,10 @@ esp_err_t esp_ble_mesh_model_unsubscribe_group_addr(uint16_t element_addr, uint1 esp_err_t esp_ble_mesh_enable_directed_forwarding(uint16_t net_idx, bool directed_forwarding, bool directed_forwarding_relay) { - return btc_ble_mesh_enable_directed_forwarding(net_idx, directed_forwarding, - directed_forwarding_relay); + ESP_BLE_HOST_STATUS_CHECK(ESP_BLE_HOST_STATUS_ENABLED); + + return (btc_ble_mesh_enable_directed_forwarding(net_idx, directed_forwarding, + directed_forwarding_relay) == 0 ? ESP_OK : ESP_FAIL); } #endif /* CONFIG_BLE_MESH_DF_SRV */ diff --git a/components/bt/esp_ble_mesh/api/core/esp_ble_mesh_networking_api.c b/components/bt/esp_ble_mesh/api/core/esp_ble_mesh_networking_api.c index ac8ecece65d..d1f9230b339 100644 --- a/components/bt/esp_ble_mesh/api/core/esp_ble_mesh_networking_api.c +++ b/components/bt/esp_ble_mesh/api/core/esp_ble_mesh_networking_api.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2017-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2017-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -30,6 +30,35 @@ static esp_err_t ble_mesh_model_send_msg(esp_ble_mesh_model_t *model, ESP_BLE_HOST_STATUS_CHECK(ESP_BLE_HOST_STATUS_ENABLED); + /* When data is NULL, it is mandatory to set length to 0 to prevent users from misinterpreting parameters. */ + if (data == NULL) { + length = 0; + } + + /* Compute op_len from opcode before length validation */ + if (opcode < 0x100) { + op_len = 1; + } else if (opcode < 0x10000) { + op_len = 2; + } else { + op_len = 3; + } + + if (act == BTC_BLE_MESH_ACT_MODEL_PUBLISH) { + /* When "send_rel" is true and "send_szmic" is 1, 8-octets TransMIC will + * be used, otherwise 4-octets TransMIC will be used. + */ + mic_len = (model->pub->send_rel && model->pub->send_szmic) ? + ESP_BLE_MESH_MIC_LONG : ESP_BLE_MESH_MIC_SHORT; + } else { + /* When the message is tagged with the send-segmented tag and "send_szmic" + * is 1, 8-octets TransMIC will be used, otherwise 4-octets TransMIC will + * be used. + */ + mic_len = ((ctx->send_tag & ESP_BLE_MESH_TAG_SEND_SEGMENTED) && ctx->send_szmic) ? + ESP_BLE_MESH_MIC_LONG : ESP_BLE_MESH_MIC_SHORT; + } + if (ctx) { if (ctx->addr == ESP_BLE_MESH_ADDR_UNASSIGNED) { BT_ERR("Invalid destination address 0x0000"); @@ -63,6 +92,7 @@ static esp_err_t ble_mesh_model_send_msg(esp_ble_mesh_model_t *model, ctx->enh.long_pkt_cfg != ESP_BLE_MESH_LONG_PACKET_PREFER)) { BT_ERR("Invalid long packet configuration %d (expected FORCE=1 or PREFER=2)", ctx->enh.long_pkt_cfg); + return ESP_ERR_INVALID_ARG; } if (ctx->enh.long_pkt_cfg_used && (op_len + length + mic_len > ESP_BLE_MESH_EXT_SDU_MAX_LEN)) { @@ -82,19 +112,6 @@ static esp_err_t ble_mesh_model_send_msg(esp_ble_mesh_model_t *model, return ESP_ERR_INVALID_ARG; } - /* When data is NULL, it is mandatory to set length to 0 to prevent users from misinterpreting parameters. */ - if (data == NULL) { - length = 0; - } - - if (opcode < 0x100) { - op_len = 1; - } else if (opcode < 0x10000) { - op_len = 2; - } else { - op_len = 3; - } - if (act == BTC_BLE_MESH_ACT_MODEL_PUBLISH) { if (op_len + length > model->pub->msg->size) { BT_ERR("Too small publication msg size %d", model->pub->msg->size); @@ -102,31 +119,20 @@ static esp_err_t ble_mesh_model_send_msg(esp_ble_mesh_model_t *model, } } - if (act == BTC_BLE_MESH_ACT_MODEL_PUBLISH) { - /* When "send_rel" is true and "send_szmic" is 1, 8-octets TransMIC will - * be used, otherwise 4-octets TransMIC will be used. - */ - mic_len = (model->pub->send_rel && model->pub->send_szmic) ? - ESP_BLE_MESH_MIC_LONG : ESP_BLE_MESH_MIC_SHORT; - } else { - /* When the message is tagged with the send-segmented tag and "send_szmic" - * is 1, 8-octets TransMIC will be used, otherwise 4-octets TransMIC will - * be used. - */ - mic_len = ((ctx->send_tag & ESP_BLE_MESH_TAG_SEND_SEGMENTED) && ctx->send_szmic) ? - ESP_BLE_MESH_MIC_LONG : ESP_BLE_MESH_MIC_SHORT; - } - if (act == BTC_BLE_MESH_ACT_MODEL_PUBLISH) { bt_mesh_model_msg_init(model->pub->msg, opcode); - net_buf_simple_add_mem(model->pub->msg, data, length); + if (length > 0) { + net_buf_simple_add_mem(model->pub->msg, data, length); + } } else { msg_data = (uint8_t *)bt_mesh_calloc(op_len + length); if (msg_data == NULL) { return ESP_ERR_NO_MEM; } esp_ble_mesh_model_msg_opcode_init(msg_data, opcode); - memcpy(msg_data + op_len, data, length); + if (length > 0) { + memcpy(msg_data + op_len, data, length); + } } msg.sig = BTC_SIG_API_CALL; @@ -693,6 +699,7 @@ esp_err_t esp_ble_mesh_provisioner_open_settings_with_uid(const char *uid) msg.pid = BTC_PID_PROV; msg.act = BTC_BLE_MESH_ACT_PROVISIONER_OPEN_SETTINGS_WITH_UID; + memset(arg.open_settings_with_uid.uid, 0, sizeof(arg.open_settings_with_uid.uid)); strncpy(arg.open_settings_with_uid.uid, uid, ESP_BLE_MESH_SETTINGS_UID_SIZE); return (btc_transfer_context(&msg, &arg, sizeof(btc_ble_mesh_prov_args_t), NULL, NULL) @@ -736,6 +743,7 @@ esp_err_t esp_ble_mesh_provisioner_close_settings_with_uid(const char *uid, bool msg.pid = BTC_PID_PROV; msg.act = BTC_BLE_MESH_ACT_PROVISIONER_CLOSE_SETTINGS_WITH_UID; + memset(arg.close_settings_with_uid.uid, 0, sizeof(arg.close_settings_with_uid.uid)); strncpy(arg.close_settings_with_uid.uid, uid, ESP_BLE_MESH_SETTINGS_UID_SIZE); arg.close_settings_with_uid.erase = erase; @@ -779,6 +787,7 @@ esp_err_t esp_ble_mesh_provisioner_delete_settings_with_uid(const char *uid) msg.pid = BTC_PID_PROV; msg.act = BTC_BLE_MESH_ACT_PROVISIONER_DELETE_SETTINGS_WITH_UID; + memset(arg.delete_settings_with_uid.uid, 0, sizeof(arg.delete_settings_with_uid.uid)); strncpy(arg.delete_settings_with_uid.uid, uid, ESP_BLE_MESH_SETTINGS_UID_SIZE); return (btc_transfer_context(&msg, &arg, sizeof(btc_ble_mesh_prov_args_t), NULL, NULL) diff --git a/components/bt/esp_ble_mesh/api/core/esp_ble_mesh_provisioning_api.c b/components/bt/esp_ble_mesh/api/core/esp_ble_mesh_provisioning_api.c index c89903a1d83..6ad928f5f7b 100644 --- a/components/bt/esp_ble_mesh/api/core/esp_ble_mesh_provisioning_api.c +++ b/components/bt/esp_ble_mesh/api/core/esp_ble_mesh_provisioning_api.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2017-2023 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2017-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -44,7 +44,7 @@ static bool prov_bearers_valid(esp_ble_mesh_prov_bearer_t bearers) esp_err_t esp_ble_mesh_node_prov_enable(esp_ble_mesh_prov_bearer_t bearers) { - btc_ble_mesh_prov_args_t arg = {0}; + btc_ble_mesh_prov_args_t arg; btc_msg_t msg = {0}; if (prov_bearers_valid(bearers) == false) { @@ -200,7 +200,7 @@ esp_err_t esp_ble_mesh_provisioner_input_string(const char *string, uint8_t link btc_ble_mesh_prov_args_t arg = {0}; btc_msg_t msg = {0}; - if (!string || strlen(string) > ESP_BLE_MESH_PROV_OUTPUT_OOB_MAX_LEN || + if (!string || strlen(string) > ESP_BLE_MESH_PROV_INPUT_OOB_MAX_LEN || link_idx >= MAX_PROV_LINK_IDX) { return ESP_ERR_INVALID_ARG; } @@ -482,7 +482,10 @@ esp_err_t esp_ble_mesh_set_fast_prov_info(esp_ble_mesh_fast_prov_info_t *fast_pr btc_msg_t msg = {0}; if (fast_prov_info == NULL || (fast_prov_info->offset + - fast_prov_info->match_len > ESP_BLE_MESH_OCTET16_LEN)) { + fast_prov_info->match_len > ESP_BLE_MESH_OCTET16_LEN) || + !ESP_BLE_MESH_ADDR_IS_UNICAST(fast_prov_info->unicast_min) || + !ESP_BLE_MESH_ADDR_IS_UNICAST(fast_prov_info->unicast_max) || + fast_prov_info->unicast_min > fast_prov_info->unicast_max) { return ESP_ERR_INVALID_ARG; } diff --git a/components/bt/esp_ble_mesh/api/core/esp_ble_mesh_proxy_api.c b/components/bt/esp_ble_mesh/api/core/esp_ble_mesh_proxy_api.c index 0365711d868..fe053930c3f 100644 --- a/components/bt/esp_ble_mesh/api/core/esp_ble_mesh_proxy_api.c +++ b/components/bt/esp_ble_mesh/api/core/esp_ble_mesh_proxy_api.c @@ -224,7 +224,7 @@ esp_err_t esp_ble_mesh_proxy_client_directed_proxy_set(uint8_t conn_handle, uint #endif /* CONFIG_BLE_MESH_DF_CLI */ #if CONFIG_BLE_MESH_PROXY_SOLIC_PDU_TX -esp_err_t esp_ble_mesh_proxy_client_send_solic_pdu(uint8_t net_idx, uint16_t ssrc, uint16_t dst) +esp_err_t esp_ble_mesh_proxy_client_send_solic_pdu(uint16_t net_idx, uint16_t ssrc, uint16_t dst) { btc_ble_mesh_prov_args_t arg = {0}; btc_msg_t msg = {0}; diff --git a/components/bt/esp_ble_mesh/api/core/include/esp_ble_mesh_proxy_api.h b/components/bt/esp_ble_mesh/api/core/include/esp_ble_mesh_proxy_api.h index 66b1e1d4be0..092e8c8d758 100644 --- a/components/bt/esp_ble_mesh/api/core/include/esp_ble_mesh_proxy_api.h +++ b/components/bt/esp_ble_mesh/api/core/include/esp_ble_mesh_proxy_api.h @@ -161,7 +161,7 @@ esp_err_t esp_ble_mesh_proxy_client_directed_proxy_set(uint8_t conn_handle, uint * @return ESP_OK on success or error code otherwise. * */ -esp_err_t esp_ble_mesh_proxy_client_send_solic_pdu(uint8_t net_idx, uint16_t ssrc, uint16_t dst); +esp_err_t esp_ble_mesh_proxy_client_send_solic_pdu(uint16_t net_idx, uint16_t ssrc, uint16_t dst); #ifdef __cplusplus } diff --git a/components/bt/esp_ble_mesh/api/models/esp_ble_mesh_config_model_api.c b/components/bt/esp_ble_mesh/api/models/esp_ble_mesh_config_model_api.c index 98155caf174..bbf3a27a35f 100644 --- a/components/bt/esp_ble_mesh/api/models/esp_ble_mesh_config_model_api.c +++ b/components/bt/esp_ble_mesh/api/models/esp_ble_mesh_config_model_api.c @@ -14,6 +14,10 @@ #if CONFIG_BLE_MESH_CFG_CLI esp_err_t esp_ble_mesh_register_config_client_callback(esp_ble_mesh_cfg_client_cb_t callback) { + if (callback == NULL) { + return ESP_ERR_INVALID_ARG; + } + ESP_BLE_HOST_STATUS_CHECK(ESP_BLE_HOST_STATUS_ENABLED); return (btc_profile_cb_set(BTC_PID_CONFIG_CLIENT, callback) == 0 ? ESP_OK : ESP_FAIL); diff --git a/components/bt/esp_ble_mesh/api/models/esp_ble_mesh_generic_model_api.c b/components/bt/esp_ble_mesh/api/models/esp_ble_mesh_generic_model_api.c index e404c6a5bda..928a9005eaf 100644 --- a/components/bt/esp_ble_mesh/api/models/esp_ble_mesh_generic_model_api.c +++ b/components/bt/esp_ble_mesh/api/models/esp_ble_mesh_generic_model_api.c @@ -14,6 +14,10 @@ #if CONFIG_BLE_MESH_GENERIC_CLIENT esp_err_t esp_ble_mesh_register_generic_client_callback(esp_ble_mesh_generic_client_cb_t callback) { + if (callback == NULL) { + return ESP_ERR_INVALID_ARG; + } + ESP_BLE_HOST_STATUS_CHECK(ESP_BLE_HOST_STATUS_ENABLED); return (btc_profile_cb_set(BTC_PID_GENERIC_CLIENT, callback) == 0 ? ESP_OK : ESP_FAIL); diff --git a/components/bt/esp_ble_mesh/api/models/esp_ble_mesh_health_model_api.c b/components/bt/esp_ble_mesh/api/models/esp_ble_mesh_health_model_api.c index 2bdb35a30ae..225537886c6 100644 --- a/components/bt/esp_ble_mesh/api/models/esp_ble_mesh_health_model_api.c +++ b/components/bt/esp_ble_mesh/api/models/esp_ble_mesh_health_model_api.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2017-2021 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2017-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -14,6 +14,10 @@ #if CONFIG_BLE_MESH_HEALTH_CLI esp_err_t esp_ble_mesh_register_health_client_callback(esp_ble_mesh_health_client_cb_t callback) { + if (callback == NULL) { + return ESP_ERR_INVALID_ARG; + } + ESP_BLE_HOST_STATUS_CHECK(ESP_BLE_HOST_STATUS_ENABLED); return (btc_profile_cb_set(BTC_PID_HEALTH_CLIENT, callback) == 0 ? ESP_OK : ESP_FAIL); @@ -29,7 +33,9 @@ esp_err_t esp_ble_mesh_health_client_get_state(esp_ble_mesh_client_common_param_ params->ctx.net_idx == ESP_BLE_MESH_KEY_UNUSED || params->ctx.app_idx == ESP_BLE_MESH_KEY_UNUSED || params->ctx.addr == ESP_BLE_MESH_ADDR_UNASSIGNED || - (params->opcode == ESP_BLE_MESH_MODEL_OP_HEALTH_FAULT_GET && get_state == NULL)) { + ((params->opcode == ESP_BLE_MESH_MODEL_OP_HEALTH_FAULT_GET || + params->opcode == ESP_BLE_MESH_MODEL_OP_ATTENTION_GET || + params->opcode == ESP_BLE_MESH_MODEL_OP_HEALTH_PERIOD_GET) && get_state == NULL)) { return ESP_ERR_INVALID_ARG; } diff --git a/components/bt/esp_ble_mesh/api/models/esp_ble_mesh_lighting_model_api.c b/components/bt/esp_ble_mesh/api/models/esp_ble_mesh_lighting_model_api.c index 80bc62f82d0..c5e148e825c 100644 --- a/components/bt/esp_ble_mesh/api/models/esp_ble_mesh_lighting_model_api.c +++ b/components/bt/esp_ble_mesh/api/models/esp_ble_mesh_lighting_model_api.c @@ -14,6 +14,10 @@ #if CONFIG_BLE_MESH_LIGHTING_CLIENT esp_err_t esp_ble_mesh_register_light_client_callback(esp_ble_mesh_light_client_cb_t callback) { + if (callback == NULL) { + return ESP_ERR_INVALID_ARG; + } + ESP_BLE_HOST_STATUS_CHECK(ESP_BLE_HOST_STATUS_ENABLED); return (btc_profile_cb_set(BTC_PID_LIGHTING_CLIENT, callback) == 0 ? ESP_OK : ESP_FAIL); diff --git a/components/bt/esp_ble_mesh/api/models/esp_ble_mesh_sensor_model_api.c b/components/bt/esp_ble_mesh/api/models/esp_ble_mesh_sensor_model_api.c index c3c047f12b6..8d49677ff6c 100644 --- a/components/bt/esp_ble_mesh/api/models/esp_ble_mesh_sensor_model_api.c +++ b/components/bt/esp_ble_mesh/api/models/esp_ble_mesh_sensor_model_api.c @@ -14,6 +14,10 @@ #if CONFIG_BLE_MESH_SENSOR_CLI esp_err_t esp_ble_mesh_register_sensor_client_callback(esp_ble_mesh_sensor_client_cb_t callback) { + if (callback == NULL) { + return ESP_ERR_INVALID_ARG; + } + ESP_BLE_HOST_STATUS_CHECK(ESP_BLE_HOST_STATUS_ENABLED); return (btc_profile_cb_set(BTC_PID_SENSOR_CLIENT, callback) == 0 ? ESP_OK : ESP_FAIL); diff --git a/components/bt/esp_ble_mesh/api/models/include/esp_ble_mesh_generic_model_api.h b/components/bt/esp_ble_mesh/api/models/include/esp_ble_mesh_generic_model_api.h index fc4808af411..111e326b441 100644 --- a/components/bt/esp_ble_mesh/api/models/include/esp_ble_mesh_generic_model_api.h +++ b/components/bt/esp_ble_mesh/api/models/include/esp_ble_mesh_generic_model_api.h @@ -495,7 +495,8 @@ esp_err_t esp_ble_mesh_register_generic_client_callback(esp_ble_mesh_generic_cli * * @param[in] params: Pointer to BLE Mesh common client parameters. * @param[in] get_state: Pointer to generic get message value. - * Shall not be set to NULL. + * Shall not be set to NULL when the opcode requires + * parameters (property-related GET operations). * * @return ESP_OK on success or error code otherwise. * diff --git a/components/bt/esp_ble_mesh/api/models/include/esp_ble_mesh_lighting_model_api.h b/components/bt/esp_ble_mesh/api/models/include/esp_ble_mesh_lighting_model_api.h index c610c77a96e..e4fa684b01a 100644 --- a/components/bt/esp_ble_mesh/api/models/include/esp_ble_mesh_lighting_model_api.h +++ b/components/bt/esp_ble_mesh/api/models/include/esp_ble_mesh_lighting_model_api.h @@ -551,7 +551,8 @@ esp_err_t esp_ble_mesh_register_light_client_callback(esp_ble_mesh_light_client_ * * @param[in] params: Pointer to BLE Mesh common client parameters. * @param[in] get_state: Pointer of light get message value. - * Shall not be set to NULL. + * Shall not be set to NULL when the opcode requires + * parameters (e.g., ESP_BLE_MESH_MODEL_OP_LIGHT_LC_PROPERTY_GET). * * @return ESP_OK on success or error code otherwise. * diff --git a/components/bt/esp_ble_mesh/btc/btc_ble_mesh_ble.c b/components/bt/esp_ble_mesh/btc/btc_ble_mesh_ble.c index ac397aeb18d..3471b8814f2 100644 --- a/components/bt/esp_ble_mesh/btc/btc_ble_mesh_ble.c +++ b/components/bt/esp_ble_mesh/btc/btc_ble_mesh_ble.c @@ -38,6 +38,7 @@ static void btc_ble_mesh_ble_copy_req_data(btc_msg_t *msg, void *p_dst, void *p_ p_src_data->scan_ble_adv_pkt.length); } else { BT_ERR("%s, Out of memory, act %d", __func__, msg->act); + p_dst_data->scan_ble_adv_pkt.length = 0; } } break; diff --git a/components/bt/esp_ble_mesh/btc/btc_ble_mesh_config_model.c b/components/bt/esp_ble_mesh/btc/btc_ble_mesh_config_model.c index 3d6f53f5565..c39cf566c2d 100644 --- a/components/bt/esp_ble_mesh/btc/btc_ble_mesh_config_model.c +++ b/components/bt/esp_ble_mesh/btc/btc_ble_mesh_config_model.c @@ -248,6 +248,7 @@ static void btc_ble_mesh_config_client_copy_req_data(btc_msg_t *msg, void *p_des break; } } + __attribute__((fallthrough)); case ESP_BLE_MESH_CFG_CLIENT_TIMEOUT_EVT: break; default: @@ -300,6 +301,7 @@ static void btc_ble_mesh_config_client_free_req_data(btc_msg_t *msg) break; } } + __attribute__((fallthrough)); case ESP_BLE_MESH_CFG_CLIENT_TIMEOUT_EVT: if (arg->params) { bt_mesh_free(arg->params); diff --git a/components/bt/esp_ble_mesh/btc/btc_ble_mesh_generic_model.c b/components/bt/esp_ble_mesh/btc/btc_ble_mesh_generic_model.c index 3826eba0122..008ada9020a 100644 --- a/components/bt/esp_ble_mesh/btc/btc_ble_mesh_generic_model.c +++ b/components/bt/esp_ble_mesh/btc/btc_ble_mesh_generic_model.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2017-2021 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2017-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -54,7 +54,7 @@ void btc_ble_mesh_generic_client_arg_deep_copy(btc_msg_t *msg, void *p_dest, voi dst->generic_client_get_state.get_state = (esp_ble_mesh_generic_client_get_state_t *)bt_mesh_calloc(sizeof(esp_ble_mesh_generic_client_get_state_t)); if (dst->generic_client_get_state.get_state) { memcpy(dst->generic_client_get_state.get_state, src->generic_client_get_state.get_state, - sizeof(esp_ble_mesh_generic_client_get_state_t)); + sizeof(esp_ble_mesh_generic_client_get_state_t)); } else { BT_ERR("%s, Out of memory, act %d", __func__, msg->act); /* Free the previously allocated resources */ @@ -146,6 +146,11 @@ void btc_ble_mesh_generic_client_arg_deep_free(btc_msg_t *msg) } arg = (btc_ble_mesh_generic_client_args_t *)(msg->arg); + /** + * msg->arg is guaranteed to be non-NULL by btc_transfer_context + */ + ESP_ASSUME_NONNULL(arg); + switch (msg->act) { case BTC_BLE_MESH_ACT_GENERIC_CLIENT_GET_STATE: @@ -334,6 +339,7 @@ static void btc_ble_mesh_generic_client_copy_req_data(btc_msg_t *msg, void *p_de break; } } + __attribute__((fallthrough)); case ESP_BLE_MESH_GENERIC_CLIENT_TIMEOUT_EVT: break; default: @@ -393,6 +399,7 @@ static void btc_ble_mesh_generic_client_free_req_data(btc_msg_t *msg) break; } } + __attribute__((fallthrough)); case ESP_BLE_MESH_GENERIC_CLIENT_TIMEOUT_EVT: if (arg->params) { bt_mesh_free(arg->params); diff --git a/components/bt/esp_ble_mesh/btc/btc_ble_mesh_health_model.c b/components/bt/esp_ble_mesh/btc/btc_ble_mesh_health_model.c index f23ac1e2808..1a63686787b 100644 --- a/components/bt/esp_ble_mesh/btc/btc_ble_mesh_health_model.c +++ b/components/bt/esp_ble_mesh/btc/btc_ble_mesh_health_model.c @@ -53,7 +53,7 @@ void btc_ble_mesh_health_client_arg_deep_copy(btc_msg_t *msg, void *p_dest, void dst->health_client_get_state.get_state = (esp_ble_mesh_health_client_get_state_t *)bt_mesh_calloc(sizeof(esp_ble_mesh_health_client_get_state_t)); if (dst->health_client_get_state.get_state) { memcpy(dst->health_client_get_state.get_state, src->health_client_get_state.get_state, - sizeof(esp_ble_mesh_health_client_get_state_t)); + sizeof(esp_ble_mesh_health_client_get_state_t)); } else { BT_ERR("%s, Out of memory, act %d", __func__, msg->act); /* Free the previously allocated resources */ @@ -192,6 +192,7 @@ static void btc_ble_mesh_health_client_copy_req_data(btc_msg_t *msg, void *p_des break; } } + __attribute__((fallthrough)); case ESP_BLE_MESH_HEALTH_CLIENT_TIMEOUT_EVT: break; default: @@ -229,6 +230,7 @@ static void btc_ble_mesh_health_client_free_req_data(btc_msg_t *msg) break; } } + __attribute__((fallthrough)); case ESP_BLE_MESH_HEALTH_CLIENT_TIMEOUT_EVT: if (arg->params) { bt_mesh_free(arg->params); diff --git a/components/bt/esp_ble_mesh/btc/btc_ble_mesh_lighting_model.c b/components/bt/esp_ble_mesh/btc/btc_ble_mesh_lighting_model.c index d82b5f12047..731161c5ada 100644 --- a/components/bt/esp_ble_mesh/btc/btc_ble_mesh_lighting_model.c +++ b/components/bt/esp_ble_mesh/btc/btc_ble_mesh_lighting_model.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2017-2021 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2017-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -176,6 +176,7 @@ static void btc_ble_mesh_lighting_client_copy_req_data(btc_msg_t *msg, void *p_d break; } } + __attribute__((fallthrough)); case ESP_BLE_MESH_LIGHT_CLIENT_TIMEOUT_EVT: break; default: @@ -209,6 +210,7 @@ static void btc_ble_mesh_lighting_client_free_req_data(btc_msg_t *msg) break; } } + __attribute__((fallthrough)); case ESP_BLE_MESH_LIGHT_CLIENT_TIMEOUT_EVT: if (arg->params) { bt_mesh_free(arg->params); @@ -458,13 +460,13 @@ static void btc_ble_mesh_lighting_server_free_req_data(btc_msg_t *msg) switch (msg->act) { case ESP_BLE_MESH_LIGHTING_SERVER_STATE_CHANGE_EVT: if (arg->ctx.recv_op == ESP_BLE_MESH_MODEL_OP_LIGHT_LC_PROPERTY_SET || - arg->ctx.recv_op == ESP_BLE_MESH_MODEL_OP_LIGHT_LC_PROPERTY_SET_UNACK) { + arg->ctx.recv_op == ESP_BLE_MESH_MODEL_OP_LIGHT_LC_PROPERTY_SET_UNACK) { bt_mesh_free_buf(arg->value.state_change.lc_property_set.property_value); } break; case ESP_BLE_MESH_LIGHTING_SERVER_RECV_SET_MSG_EVT: if (arg->ctx.recv_op == ESP_BLE_MESH_MODEL_OP_LIGHT_LC_PROPERTY_SET || - arg->ctx.recv_op == ESP_BLE_MESH_MODEL_OP_LIGHT_LC_PROPERTY_SET_UNACK) { + arg->ctx.recv_op == ESP_BLE_MESH_MODEL_OP_LIGHT_LC_PROPERTY_SET_UNACK) { bt_mesh_free_buf(arg->value.set.lc_property.property_value); } break; diff --git a/components/bt/esp_ble_mesh/btc/btc_ble_mesh_prov.c b/components/bt/esp_ble_mesh/btc/btc_ble_mesh_prov.c index 5e5bbe30c39..e82c61cd188 100644 --- a/components/bt/esp_ble_mesh/btc/btc_ble_mesh_prov.c +++ b/components/bt/esp_ble_mesh/btc/btc_ble_mesh_prov.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2017-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2017-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -208,7 +208,7 @@ static void btc_ble_mesh_prov_copy_req_data(btc_msg_t *msg, void *p_dest, void * #if CONFIG_BLE_MESH_CERT_BASED_PROV case ESP_BLE_MESH_PROVISIONER_RECV_PROV_RECORDS_LIST_EVT: if (p_src_data->recv_provisioner_records_list.msg && - p_src_data->recv_provisioner_records_list.len) { + p_src_data->recv_provisioner_records_list.len) { p_dest_data->recv_provisioner_records_list.msg = (uint8_t *)bt_mesh_calloc(p_src_data->recv_provisioner_records_list.len); if (!p_dest_data->recv_provisioner_records_list.msg) { BT_ERR("%s, Out of memory, act %d", __func__, msg->act); @@ -222,7 +222,7 @@ static void btc_ble_mesh_prov_copy_req_data(btc_msg_t *msg, void *p_dest, void * break; case ESP_BLE_MESH_PROVISIONER_PROV_RECORD_RECV_COMP_EVT: if (p_src_data->provisioner_prov_record_recv_comp.record && - p_src_data->provisioner_prov_record_recv_comp.total_len) { + p_src_data->provisioner_prov_record_recv_comp.total_len) { p_dest_data->provisioner_prov_record_recv_comp.record = bt_mesh_calloc(p_src_data->provisioner_prov_record_recv_comp.total_len); if (!p_dest_data->provisioner_prov_record_recv_comp.record) { BT_ERR("%s, Out of memory, act %d", __func__, msg->act); @@ -263,10 +263,11 @@ static void btc_ble_mesh_prov_free_req_data(btc_msg_t *msg) bt_mesh_free(arg->provisioner_prov_record_recv_comp.record); } break; -#else /* CONFIG_BLE_MESH_CERT_BASED_PROV */ - ARG_UNUSED(arg); #endif /* CONFIG_BLE_MESH_CERT_BASED_PROV */ default: +#if !CONFIG_BLE_MESH_CERT_BASED_PROV + ARG_UNUSED(arg); +#endif /* !CONFIG_BLE_MESH_CERT_BASED_PROV */ break; } } @@ -381,7 +382,7 @@ static void btc_ble_mesh_model_copy_req_data(btc_msg_t *msg, void *p_dest, void } if (p_src_data->model_operation.msg && - p_src_data->model_operation.length) { + p_src_data->model_operation.length) { p_dest_data->model_operation.msg = (uint8_t *)bt_mesh_calloc(p_src_data->model_operation.length); if (!p_dest_data->model_operation.msg) { BT_ERR("%s, Out of memory, act %d", __func__, msg->act); @@ -412,7 +413,7 @@ static void btc_ble_mesh_model_copy_req_data(btc_msg_t *msg, void *p_dest, void } if (p_src_data->client_recv_publish_msg.msg && - p_src_data->client_recv_publish_msg.length) { + p_src_data->client_recv_publish_msg.length) { p_dest_data->client_recv_publish_msg.msg = (uint8_t *)bt_mesh_calloc(p_src_data->client_recv_publish_msg.length); if (!p_dest_data->client_recv_publish_msg.msg) { BT_ERR("%s, Out of memory, act %d", __func__, msg->act); @@ -530,6 +531,14 @@ static void btc_ble_mesh_server_model_op_cb(struct bt_mesh_model *model, { esp_ble_mesh_model_cb_param_t mesh_param = {0}; + /** + * model,ctx and buf is guaranteed to be non-NULL + * by bt_mesh_model_recv in access.c + */ + ESP_ASSUME_NONNULL(model); + ESP_ASSUME_NONNULL(ctx); + ESP_ASSUME_NONNULL(buf); + mesh_param.model_operation.opcode = ctx->recv_op; mesh_param.model_operation.model = (esp_ble_mesh_model_t *)model; mesh_param.model_operation.ctx = (esp_ble_mesh_msg_ctx_t *)ctx; @@ -707,7 +716,7 @@ static int btc_ble_mesh_output_string_cb(const char *str) memset(mesh_param.node_prov_output_str.string, 0, sizeof(mesh_param.node_prov_output_str.string)); strncpy(mesh_param.node_prov_output_str.string, str, - MIN(strlen(str), sizeof(mesh_param.node_prov_output_str.string))); + MIN(strlen(str), sizeof(mesh_param.node_prov_output_str.string) - 1)); ret = btc_ble_mesh_prov_callback(&mesh_param, ESP_BLE_MESH_NODE_PROV_OUTPUT_STRING_EVT); return (ret == BT_STATUS_SUCCESS) ? 0 : -1; @@ -797,7 +806,7 @@ static void btc_ble_mesh_provisioner_recv_unprov_adv_pkt_cb(const uint8_t addr[6 esp_ble_mesh_prov_cb_param_t mesh_param = {0}; if (addr == NULL || dev_uuid == NULL || - (bearer != BLE_MESH_PROV_ADV && bearer != BLE_MESH_PROV_GATT)) { + (bearer != BLE_MESH_PROV_ADV && bearer != BLE_MESH_PROV_GATT)) { BT_ERR("%s, Invalid parameter", __func__); return; } @@ -850,6 +859,17 @@ static int btc_ble_mesh_provisioner_prov_output_cb(uint8_t method, bt_mesh_input mesh_param.provisioner_prov_output.size = size; mesh_param.provisioner_prov_output.link_idx = link_idx; if (act == BLE_MESH_ENTER_STRING) { + /** + * data is guaranteed to be non-NULL by `prov_auth` in `prov_pvnr.c` + */ + ESP_ASSUME_NONNULL(data); + /** + * The size of the string should be less than or equal to 8 bytes, + * which is defined in the Bluetooth Mesh Profile Specification 5.4.1.3. + * Moreover, the size used here has been verified by the protocol stack + * and is legitimate, so it will definitely not cause a string out-of-bounds + * issue. + */ strncpy(mesh_param.provisioner_prov_output.string, (char *)data, size); } else { mesh_param.provisioner_prov_output.number = sys_get_le32((uint8_t *)data); @@ -1160,7 +1180,10 @@ int btc_ble_mesh_client_model_init(esp_ble_mesh_model_t *model) return -EINVAL; } - __ASSERT(model && model->op, "Invalid parameter"); + if (!model || !model->op) { + BT_ERR("%s, Invalid parameter", __func__); + return -EINVAL; + } esp_ble_mesh_model_op_t *op = model->op; while (op && op->opcode != 0) { op->param_cb = (esp_ble_mesh_cb_t)btc_ble_mesh_client_model_op_cb; @@ -2351,7 +2374,7 @@ static void btc_ble_mesh_model_op_set(esp_ble_mesh_model_t *model) model->pub->update = (esp_ble_mesh_cb_t)btc_ble_mesh_model_publish_update; } break; -#endif /* CONFIG_BLE_MESH_DFD_SRV */ +#endif /* CONFIG_BLE_MESH_DFD_CLI */ default: goto set_vnd_op; } @@ -2390,6 +2413,7 @@ void btc_ble_mesh_prov_call_handler(btc_msg_t *msg) esp_ble_mesh_model_t *sig_model = &elem->sig_models[j]; if (sig_model->op && BLE_MESH_MODEL_OP_LEN(sig_model->op->opcode) == 3) { /* Opcode of SIG model must be 1 or 2 bytes. */ + xSemaphoreGive(arg->mesh_init.semaphore); btc_ble_mesh_prov_register_complete_cb(-EINVAL); return; } @@ -2400,6 +2424,7 @@ void btc_ble_mesh_prov_call_handler(btc_msg_t *msg) esp_ble_mesh_model_t *vnd_model = &elem->vnd_models[k]; if (vnd_model->op && BLE_MESH_MODEL_OP_LEN(vnd_model->op->opcode) < 3) { /* Opcode of vendor model must be 3 bytes. */ + xSemaphoreGive(arg->mesh_init.semaphore); btc_ble_mesh_prov_register_complete_cb(-EINVAL); return; } @@ -2588,9 +2613,9 @@ void btc_ble_mesh_prov_call_handler(btc_msg_t *msg) act = ESP_BLE_MESH_PROVISIONER_PROV_DEV_WITH_ADDR_COMP_EVT; param.provisioner_prov_dev_with_addr_comp.err_code = bt_mesh_provisioner_prov_device_with_addr(arg->provisioner_prov_dev_with_addr.uuid, - arg->provisioner_prov_dev_with_addr.addr, arg->provisioner_prov_dev_with_addr.addr_type, - arg->provisioner_prov_dev_with_addr.bearer, arg->provisioner_prov_dev_with_addr.oob_info, - arg->provisioner_prov_dev_with_addr.unicast_addr); + arg->provisioner_prov_dev_with_addr.addr, arg->provisioner_prov_dev_with_addr.addr_type, + arg->provisioner_prov_dev_with_addr.bearer, arg->provisioner_prov_dev_with_addr.oob_info, + arg->provisioner_prov_dev_with_addr.unicast_addr); break; case BTC_BLE_MESH_ACT_PROVISIONER_DEV_DEL: { struct bt_mesh_device_delete del_dev = {0}; @@ -2608,9 +2633,9 @@ void btc_ble_mesh_prov_call_handler(btc_msg_t *msg) act = ESP_BLE_MESH_PROVISIONER_SET_DEV_UUID_MATCH_COMP_EVT; param.provisioner_set_dev_uuid_match_comp.err_code = bt_mesh_provisioner_set_dev_uuid_match(arg->set_dev_uuid_match.offset, - arg->set_dev_uuid_match.match_len, - arg->set_dev_uuid_match.match_val, - arg->set_dev_uuid_match.prov_after_match); + arg->set_dev_uuid_match.match_len, + arg->set_dev_uuid_match.match_val, + arg->set_dev_uuid_match.prov_after_match); break; case BTC_BLE_MESH_ACT_PROVISIONER_SET_PROV_DATA_INFO: { struct bt_mesh_prov_data_info info = {0}; @@ -2653,7 +2678,7 @@ void btc_ble_mesh_prov_call_handler(btc_msg_t *msg) act = ESP_BLE_MESH_PROVISIONER_ADD_LOCAL_APP_KEY_COMP_EVT; param.provisioner_add_app_key_comp.err_code = bt_mesh_provisioner_local_app_key_add(app_key, arg->add_local_app_key.net_idx, - &arg->add_local_app_key.app_idx); + &arg->add_local_app_key.app_idx); param.provisioner_add_app_key_comp.net_idx = arg->add_local_app_key.net_idx; param.provisioner_add_app_key_comp.app_idx = arg->add_local_app_key.app_idx; break; @@ -2664,7 +2689,7 @@ void btc_ble_mesh_prov_call_handler(btc_msg_t *msg) param.provisioner_update_app_key_comp.app_idx = arg->update_local_app_key.app_idx; param.provisioner_update_app_key_comp.err_code = bt_mesh_provisioner_local_app_key_update(arg->update_local_app_key.app_key, - arg->update_local_app_key.net_idx, arg->update_local_app_key.app_idx); + arg->update_local_app_key.net_idx, arg->update_local_app_key.app_idx); break; case BTC_BLE_MESH_ACT_PROVISIONER_BIND_LOCAL_MOD_APP: act = ESP_BLE_MESH_PROVISIONER_BIND_APP_KEY_TO_MODEL_COMP_EVT; @@ -2674,9 +2699,9 @@ void btc_ble_mesh_prov_call_handler(btc_msg_t *msg) param.provisioner_bind_app_key_to_model_comp.model_id = arg->local_mod_app_bind.model_id; param.provisioner_bind_app_key_to_model_comp.err_code = bt_mesh_provisioner_bind_local_model_app_idx(arg->local_mod_app_bind.elem_addr, - arg->local_mod_app_bind.model_id, - arg->local_mod_app_bind.cid, - arg->local_mod_app_bind.app_idx); + arg->local_mod_app_bind.model_id, + arg->local_mod_app_bind.cid, + arg->local_mod_app_bind.app_idx); break; case BTC_BLE_MESH_ACT_PROVISIONER_ADD_LOCAL_NET_KEY: { const uint8_t *net_key = NULL; @@ -2695,14 +2720,14 @@ void btc_ble_mesh_prov_call_handler(btc_msg_t *msg) param.provisioner_update_net_key_comp.net_idx = arg->update_local_net_key.net_idx; param.provisioner_update_net_key_comp.err_code = bt_mesh_provisioner_local_net_key_update(arg->update_local_net_key.net_key, - arg->update_local_net_key.net_idx); + arg->update_local_net_key.net_idx); break; case BTC_BLE_MESH_ACT_PROVISIONER_STORE_NODE_COMP_DATA: act = ESP_BLE_MESH_PROVISIONER_STORE_NODE_COMP_DATA_COMP_EVT; param.provisioner_store_node_comp_data_comp.addr = arg->store_node_comp_data.unicast_addr; param.provisioner_store_node_comp_data_comp.err_code = bt_mesh_provisioner_store_node_comp_data(arg->store_node_comp_data.unicast_addr, - arg->store_node_comp_data.data, arg->store_node_comp_data.length); + arg->store_node_comp_data.data, arg->store_node_comp_data.length); break; case BTC_BLE_MESH_ACT_PROVISIONER_DELETE_NODE_WITH_UUID: act = ESP_BLE_MESH_PROVISIONER_DELETE_NODE_WITH_UUID_COMP_EVT; @@ -2794,11 +2819,11 @@ void btc_ble_mesh_prov_call_handler(btc_msg_t *msg) break; #endif /* CONFIG_BLE_MESH_USE_MULTIPLE_NAMESPACE */ #if CONFIG_BLE_MESH_CERT_BASED_PROV - extern int bt_mesh_provisioner_send_prov_records_get(uint16_t link_idx); - extern int bt_mesh_provisioner_send_prov_record_req(uint16_t link_idx, uint16_t record_id, - uint16_t frag_offset, uint16_t max_size); - extern int bt_mesh_provisioner_send_prov_invite(uint16_t link_idx); - extern int bt_mesh_provisioner_send_link_close(uint16_t link_idx); + extern int bt_mesh_provisioner_send_prov_records_get(uint16_t link_idx); + extern int bt_mesh_provisioner_send_prov_record_req(uint16_t link_idx, uint16_t record_id, + uint16_t frag_offset, uint16_t max_size); + extern int bt_mesh_provisioner_send_prov_invite(uint16_t link_idx); + extern int bt_mesh_provisioner_send_link_close(uint16_t link_idx); case BTC_BLE_MESH_ACT_PROVISIONER_SEND_PROV_RECORDS_GET: act = ESP_BLE_MESH_PROVISIONER_SEND_PROV_RECORDS_GET_EVT; @@ -2837,13 +2862,13 @@ void btc_ble_mesh_prov_call_handler(btc_msg_t *msg) act = ESP_BLE_MESH_SET_FAST_PROV_INFO_COMP_EVT; param.set_fast_prov_info_comp.status_unicast = bt_mesh_set_fast_prov_unicast_addr_range(arg->set_fast_prov_info.unicast_min, - arg->set_fast_prov_info.unicast_max); + arg->set_fast_prov_info.unicast_max); param.set_fast_prov_info_comp.status_net_idx = bt_mesh_set_fast_prov_net_idx(arg->set_fast_prov_info.net_idx); param.set_fast_prov_info_comp.status_match = bt_mesh_provisioner_set_dev_uuid_match(arg->set_fast_prov_info.offset, - arg->set_fast_prov_info.match_len, - arg->set_fast_prov_info.match_val, false); + arg->set_fast_prov_info.match_len, + arg->set_fast_prov_info.match_val, false); break; case BTC_BLE_MESH_ACT_SET_FAST_PROV_ACTION: act = ESP_BLE_MESH_SET_FAST_PROV_ACTION_COMP_EVT; @@ -3039,11 +3064,18 @@ void btc_ble_mesh_model_call_handler(btc_msg_t *msg) break; } case BTC_BLE_MESH_ACT_SERVER_MODEL_SEND: { - assert(arg->model_send.model); - assert(arg->model_send.ctx); - if (arg->model_send.length) { - assert(arg->model_send.data); - } + /** + * model,ctx and data is guaranteed to be non-NULL + * by esp_ble_mesh_server_model_send_msg in esp_ble_mesh_networking_api.c + */ + ESP_ASSUME_NONNULL(arg->model_send.model); + ESP_ASSUME_NONNULL(arg->model_send.ctx); + ESP_ASSUME_NONNULL(arg->model_send.data); + /** + * length is guaranteed to be greater than 0 (opcode length + payload length) + * by ble_mesh_model_send_msg in esp_ble_mesh_networking_api.c + */ + ESP_ASSUME_NONNULL(arg->model_send.length); /* arg->model_send.length contains opcode & payload, plus extra 4-bytes TransMIC */ struct net_buf_simple *buf = bt_mesh_alloc_buf(arg->model_send.length + BLE_MESH_MIC_SHORT); @@ -3109,7 +3141,7 @@ void btc_ble_mesh_model_call_handler(btc_msg_t *msg) (struct bt_mesh_model *)arg->model_update_state.model, arg->model_update_state.type, (bt_mesh_server_state_value_t *)arg->model_update_state.value); btc_ble_mesh_server_model_update_state_comp_cb(arg->model_update_state.model, - arg->model_update_state.type, err); + arg->model_update_state.type, err); break; #endif /* CONFIG_BLE_MESH_SERVER_MODEL */ default: diff --git a/components/bt/esp_ble_mesh/btc/btc_ble_mesh_sensor_model.c b/components/bt/esp_ble_mesh/btc/btc_ble_mesh_sensor_model.c index 015e108c38c..90d0ce0d744 100644 --- a/components/bt/esp_ble_mesh/btc/btc_ble_mesh_sensor_model.c +++ b/components/bt/esp_ble_mesh/btc/btc_ble_mesh_sensor_model.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2017-2021 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2017-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -86,7 +86,6 @@ void btc_ble_mesh_sensor_client_arg_deep_copy(btc_msg_t *msg, void *p_dest, void length = src->sensor_client_get_state.get_state->series_get.raw_value_x1->len; dst->sensor_client_get_state.get_state->series_get.raw_value_x1 = bt_mesh_alloc_buf(length); if (!dst->sensor_client_get_state.get_state->series_get.raw_value_x1) { - BT_ERR("%s, Out of memory, act %d", __func__, msg->act); BT_ERR("%s, Out of memory, act %d", __func__, msg->act); /* Free the previously allocated resources */ bt_mesh_free(dst->sensor_client_get_state.params); @@ -103,7 +102,6 @@ void btc_ble_mesh_sensor_client_arg_deep_copy(btc_msg_t *msg, void *p_dest, void length = src->sensor_client_get_state.get_state->series_get.raw_value_x2->len; dst->sensor_client_get_state.get_state->series_get.raw_value_x2 = bt_mesh_alloc_buf(length); if (!dst->sensor_client_get_state.get_state->series_get.raw_value_x2) { - BT_ERR("%s, Out of memory, act %d", __func__, msg->act); BT_ERR("%s, Out of memory, act %d", __func__, msg->act); /* Free the previously allocated resources */ if (dst->sensor_client_get_state.get_state->series_get.raw_value_x1) { @@ -499,6 +497,7 @@ static void btc_ble_mesh_sensor_client_copy_req_data(btc_msg_t *msg, void *p_des break; } } + __attribute__((fallthrough)); case ESP_BLE_MESH_SENSOR_CLIENT_TIMEOUT_EVT: break; default: @@ -557,6 +556,7 @@ static void btc_ble_mesh_sensor_client_free_req_data(btc_msg_t *msg) break; } } + __attribute__((fallthrough)); case ESP_BLE_MESH_SENSOR_CLIENT_TIMEOUT_EVT: if (arg->params) { bt_mesh_free(arg->params); @@ -759,6 +759,7 @@ static void btc_ble_mesh_sensor_server_copy_req_data(btc_msg_t *msg, void *p_des p_dest_data->value.state_change.sensor_cadence_set.trigger_delta_up = bt_mesh_alloc_buf(length); if (p_dest_data->value.state_change.sensor_cadence_set.trigger_delta_up == NULL) { BT_ERR("%s, Out of memory, act %d", __func__, msg->act); + /* The allocated memory will be released by btc_ble_mesh_sensor_server_free_req_data */ return; } net_buf_simple_add_mem(p_dest_data->value.state_change.sensor_cadence_set.trigger_delta_up, @@ -770,6 +771,7 @@ static void btc_ble_mesh_sensor_server_copy_req_data(btc_msg_t *msg, void *p_des p_dest_data->value.state_change.sensor_cadence_set.fast_cadence_low = bt_mesh_alloc_buf(length); if (p_dest_data->value.state_change.sensor_cadence_set.fast_cadence_low == NULL) { BT_ERR("%s, Out of memory, act %d", __func__, msg->act); + /* The allocated memory will be released by btc_ble_mesh_sensor_server_free_req_data */ return; } net_buf_simple_add_mem(p_dest_data->value.state_change.sensor_cadence_set.fast_cadence_low, @@ -781,6 +783,7 @@ static void btc_ble_mesh_sensor_server_copy_req_data(btc_msg_t *msg, void *p_des p_dest_data->value.state_change.sensor_cadence_set.fast_cadence_high = bt_mesh_alloc_buf(length); if (p_dest_data->value.state_change.sensor_cadence_set.fast_cadence_high == NULL) { BT_ERR("%s, Out of memory, act %d", __func__, msg->act); + /* The allocated memory will be released by btc_ble_mesh_sensor_server_free_req_data */ return; } net_buf_simple_add_mem(p_dest_data->value.state_change.sensor_cadence_set.fast_cadence_high, @@ -877,7 +880,7 @@ static void btc_ble_mesh_sensor_server_free_req_data(btc_msg_t *msg) switch (msg->act) { case ESP_BLE_MESH_SENSOR_SERVER_STATE_CHANGE_EVT: if (arg->ctx.recv_op == ESP_BLE_MESH_MODEL_OP_SENSOR_CADENCE_SET || - arg->ctx.recv_op == ESP_BLE_MESH_MODEL_OP_SENSOR_CADENCE_SET_UNACK) { + arg->ctx.recv_op == ESP_BLE_MESH_MODEL_OP_SENSOR_CADENCE_SET_UNACK) { bt_mesh_free_buf(arg->value.state_change.sensor_cadence_set.trigger_delta_down); bt_mesh_free_buf(arg->value.state_change.sensor_cadence_set.trigger_delta_up); bt_mesh_free_buf(arg->value.state_change.sensor_cadence_set.fast_cadence_low); @@ -896,7 +899,7 @@ static void btc_ble_mesh_sensor_server_free_req_data(btc_msg_t *msg) break; case ESP_BLE_MESH_SENSOR_SERVER_RECV_SET_MSG_EVT: if (arg->ctx.recv_op == ESP_BLE_MESH_MODEL_OP_SENSOR_CADENCE_SET || - arg->ctx.recv_op == ESP_BLE_MESH_MODEL_OP_SENSOR_CADENCE_SET_UNACK) { + arg->ctx.recv_op == ESP_BLE_MESH_MODEL_OP_SENSOR_CADENCE_SET_UNACK) { bt_mesh_free_buf(arg->value.set.sensor_cadence.cadence); } else if (arg->ctx.recv_op == ESP_BLE_MESH_MODEL_OP_SENSOR_SETTING_SET || arg->ctx.recv_op == ESP_BLE_MESH_MODEL_OP_SENSOR_SETTING_SET_UNACK) { diff --git a/components/bt/esp_ble_mesh/btc/btc_ble_mesh_time_scene_model.c b/components/bt/esp_ble_mesh/btc/btc_ble_mesh_time_scene_model.c index 234dd8edff7..2a71ac24d13 100644 --- a/components/bt/esp_ble_mesh/btc/btc_ble_mesh_time_scene_model.c +++ b/components/bt/esp_ble_mesh/btc/btc_ble_mesh_time_scene_model.c @@ -176,6 +176,7 @@ static void btc_ble_mesh_time_scene_client_copy_req_data(btc_msg_t *msg, void *p break; } } + __attribute__((fallthrough)); case ESP_BLE_MESH_TIME_SCENE_CLIENT_TIMEOUT_EVT: break; default: @@ -210,6 +211,7 @@ static void btc_ble_mesh_time_scene_client_free_req_data(btc_msg_t *msg) break; } } + __attribute__((fallthrough)); case ESP_BLE_MESH_TIME_SCENE_CLIENT_TIMEOUT_EVT: if (arg->params) { bt_mesh_free(arg->params); diff --git a/components/bt/esp_ble_mesh/common/atomic.c b/components/bt/esp_ble_mesh/common/atomic.c index 9c856cc3372..5f4f0fe4a98 100644 --- a/components/bt/esp_ble_mesh/common/atomic.c +++ b/components/bt/esp_ble_mesh/common/atomic.c @@ -13,7 +13,7 @@ /* * SPDX-FileCopyrightText: 2016 Intel Corporation * SPDX-FileCopyrightText: 2011-2014 Wind River Systems, Inc. - * SPDX-FileContributor: 2018-2024 Espressif Systems (Shanghai) CO LTD + * SPDX-FileContributor: 2018-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -24,20 +24,28 @@ #ifndef CONFIG_ATOMIC_OPERATIONS_BUILTIN /** -* -* @brief Atomic get primitive -* -* @param target memory location to read from -* -* This routine provides the atomic get primitive to atomically read -* a value from . It simply does an ordinary load. Note that -* is expected to be aligned to a 4-byte boundary. -* -* @return The value read from -*/ + * + * @brief Atomic get primitive + * + * @param target memory location to read from + * + * This routine provides the atomic get primitive to atomically read + * a value from . It simply does an ordinary load. Note that + * is expected to be aligned to a 4-byte boundary. + * + * @return The value read from + */ bt_mesh_atomic_val_t bt_mesh_atomic_get(const bt_mesh_atomic_t *target) { - return *target; + bt_mesh_atomic_val_t ret; + + bt_mesh_atomic_lock(); + + ret = *target; + + bt_mesh_atomic_unlock(); + + return ret; } /** diff --git a/components/bt/esp_ble_mesh/common/buf.c b/components/bt/esp_ble_mesh/common/buf.c index f988c4e8bdc..3b439436890 100644 --- a/components/bt/esp_ble_mesh/common/buf.c +++ b/components/bt/esp_ble_mesh/common/buf.c @@ -1,6 +1,6 @@ /* * SPDX-FileCopyrightText: 2015 Intel Corporation - * SPDX-FileContributor: 2018-2021 Espressif Systems (Shanghai) CO LTD + * SPDX-FileContributor: 2018-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -378,6 +378,8 @@ void net_buf_reset(struct net_buf *buf) void net_buf_simple_init_with_data(struct net_buf_simple *buf, void *data, size_t size) { + NET_BUF_ASSERT(size <= UINT16_MAX); + buf->__buf = data; buf->data = data; buf->size = size; @@ -388,6 +390,7 @@ void net_buf_simple_reserve(struct net_buf_simple *buf, size_t reserve) { NET_BUF_ASSERT(buf); NET_BUF_ASSERT(buf->len == 0U); + NET_BUF_ASSERT(reserve <= buf->size); NET_BUF_DBG("buf %p reserve %zu", buf, reserve); buf->data = buf->__buf + reserve; @@ -416,18 +419,16 @@ struct net_buf *net_buf_slist_get(sys_slist_t *list) NET_BUF_ASSERT(list); bt_mesh_list_lock(); - buf = (void *)sys_slist_get(list); - bt_mesh_list_unlock(); + buf = (void *)sys_slist_get(list); if (!buf) { + bt_mesh_list_unlock(); return NULL; } /* Get any fragments belonging to this buffer */ for (frag = buf; (frag->flags & NET_BUF_FRAGS); frag = frag->frags) { - bt_mesh_list_lock(); frag->frags = (void *)sys_slist_get(list); - bt_mesh_list_unlock(); NET_BUF_ASSERT(frag->frags); @@ -435,6 +436,8 @@ struct net_buf *net_buf_slist_get(sys_slist_t *list) frag->flags &= ~NET_BUF_FRAGS; } + bt_mesh_list_unlock(); + /* Mark the end of the fragment list */ frag->frags = NULL; @@ -447,7 +450,10 @@ struct net_buf *net_buf_ref(struct net_buf *buf) NET_BUF_DBG("buf %p (old) ref %u pool %p", buf, buf->ref, buf->pool); + bt_mesh_buf_lock(); buf->ref++; + bt_mesh_buf_unlock(); + return buf; } @@ -459,6 +465,8 @@ void net_buf_unref(struct net_buf *buf) { NET_BUF_ASSERT(buf); + bt_mesh_buf_lock(); + while (buf) { struct net_buf *frags = buf->frags; struct net_buf_pool *pool = NULL; @@ -467,6 +475,7 @@ void net_buf_unref(struct net_buf *buf) if (!buf->ref) { NET_BUF_ERR("%s():%d: buf %p double free", func, line, buf); + bt_mesh_buf_unlock(); return; } #endif @@ -475,6 +484,7 @@ void net_buf_unref(struct net_buf *buf) /* Changed by Espressif. Add !buf->ref to avoid minus 0 */ if (!buf->ref || --buf->ref > 0) { + bt_mesh_buf_unlock(); return; } @@ -496,6 +506,8 @@ void net_buf_unref(struct net_buf *buf) buf = frags; } + + bt_mesh_buf_unlock(); } static uint8_t *fixed_data_alloc(struct net_buf *buf, size_t *size, int32_t timeout) @@ -525,6 +537,10 @@ static uint8_t *data_alloc(struct net_buf *buf, size_t *size, int32_t timeout) return pool->alloc->cb->alloc(buf, size, timeout); } +/** + * When using this function, Must ensure that the lock for + * buf->pool has been acquired; otherwise, race conditions may occur. + */ #if CONFIG_BLE_MESH_NET_BUF_LOG struct net_buf *net_buf_alloc_len_debug(struct net_buf_pool *pool, size_t size, int32_t timeout, const char *func, int line) @@ -541,11 +557,6 @@ struct net_buf *net_buf_alloc_len(struct net_buf_pool *pool, size_t size, NET_BUF_DBG("Alloc, pool %p, uninit_count %d, buf_count %d", pool, pool->uninit_count, pool->buf_count); - /* We need to lock interrupts temporarily to prevent race conditions - * when accessing pool->uninit_count. - */ - bt_mesh_buf_lock(); - /* If there are uninitialized buffers we're guaranteed to succeed * with the allocation one way or another. */ @@ -554,14 +565,11 @@ struct net_buf *net_buf_alloc_len(struct net_buf_pool *pool, size_t size, for (i = pool->buf_count; i > 0; i--) { buf = pool_get_uninit(pool, i); if (!buf->ref) { - bt_mesh_buf_unlock(); goto success; } } } - bt_mesh_buf_unlock(); - NET_BUF_ERR("Out of free buffer, pool %p", pool); return NULL; @@ -600,15 +608,25 @@ struct net_buf *net_buf_alloc_fixed_debug(struct net_buf_pool *pool, int line) { const struct net_buf_pool_fixed *fixed = pool->alloc->alloc_data; + struct net_buf *buf = NULL; - return net_buf_alloc_len_debug(pool, fixed->data_size, timeout, func, line); + bt_mesh_buf_lock(); + buf = net_buf_alloc_len_debug(pool, fixed->data_size, timeout, func, line); + bt_mesh_buf_unlock(); + + return buf; } #else struct net_buf *net_buf_alloc_fixed(struct net_buf_pool *pool, int32_t timeout) { const struct net_buf_pool_fixed *fixed = pool->alloc->alloc_data; + struct net_buf *buf = NULL; - return net_buf_alloc_len(pool, fixed->data_size, timeout); + bt_mesh_buf_lock(); + buf = net_buf_alloc_len(pool, fixed->data_size, timeout); + bt_mesh_buf_unlock(); + + return buf; } #endif diff --git a/components/bt/esp_ble_mesh/common/crypto_mbedtls.c b/components/bt/esp_ble_mesh/common/crypto_mbedtls.c index 4491fe779a9..bfafe70d4a2 100644 --- a/components/bt/esp_ble_mesh/common/crypto_mbedtls.c +++ b/components/bt/esp_ble_mesh/common/crypto_mbedtls.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -112,10 +112,14 @@ int bt_mesh_ccm_encrypt_raw_key(const uint8_t key[16], uint8_t nonce[13], uint8_t *enc_data, size_t mic_size) { struct bt_mesh_key mesh_key; + int ret; memcpy(mesh_key.key, key, 16); - return bt_mesh_ccm_encrypt(&mesh_key, nonce, plaintext, len, aad, aad_len, - enc_data, mic_size); + ret = bt_mesh_ccm_encrypt(&mesh_key, nonce, plaintext, len, aad, aad_len, + enc_data, mic_size); + + mbedtls_platform_zeroize(&mesh_key, sizeof(mesh_key)); + return ret; } int bt_mesh_ccm_decrypt_raw_key(const uint8_t key[16], uint8_t nonce[13], @@ -124,10 +128,14 @@ int bt_mesh_ccm_decrypt_raw_key(const uint8_t key[16], uint8_t nonce[13], uint8_t *plaintext, size_t mic_size) { struct bt_mesh_key mesh_key; + int ret; memcpy(mesh_key.key, key, 16); - return bt_mesh_ccm_decrypt(&mesh_key, nonce, enc_data, len, aad, aad_len, - plaintext, mic_size); + ret = bt_mesh_ccm_decrypt(&mesh_key, nonce, enc_data, len, aad, aad_len, + plaintext, mic_size); + + mbedtls_platform_zeroize(&mesh_key, sizeof(mesh_key)); + return ret; } int bt_mesh_aes_cmac_mesh_key(const struct bt_mesh_key *key, @@ -230,6 +238,10 @@ int bt_mesh_pub_key_gen(void) dh_pair.is_ready = false; do { + /** + * For now, there is no need to consider the security + * of the private key generated by the random function. + */ err = bt_mesh_rand(dh_pair.private_key, sizeof(dh_pair.private_key)); if (err) { BT_ERR("Failed to generate random private key"); @@ -360,12 +372,12 @@ bool bt_mesh_check_public_key_raw(const uint8_t key[64]) goto cleanup; } - ret = mbedtls_mpi_read_binary(&Q.MBEDTLS_PRIVATE(X), dh_pair.public_key, 32); + ret = mbedtls_mpi_read_binary(&Q.MBEDTLS_PRIVATE(X), key, 32); if (ret != 0) { goto cleanup; } - ret = mbedtls_mpi_read_binary(&Q.MBEDTLS_PRIVATE(Y), dh_pair.public_key + 32, 32); + ret = mbedtls_mpi_read_binary(&Q.MBEDTLS_PRIVATE(Y), key + 32, 32); if (ret != 0) { goto cleanup; } @@ -409,12 +421,12 @@ int bt_mesh_dhkey_gen_raw(const uint8_t *pub_key, const uint8_t *priv_key, } /* Load public key point */ - ret = mbedtls_mpi_read_binary(&Q.MBEDTLS_PRIVATE(X), dh_pair.public_key, 32); + ret = mbedtls_mpi_read_binary(&Q.MBEDTLS_PRIVATE(X), pub_key, 32); if (ret != 0) { goto cleanup; } - ret = mbedtls_mpi_read_binary(&Q.MBEDTLS_PRIVATE(Y), dh_pair.public_key + 32, 32); + ret = mbedtls_mpi_read_binary(&Q.MBEDTLS_PRIVATE(Y), pub_key + 32, 32); if (ret != 0) { goto cleanup; } diff --git a/components/bt/esp_ble_mesh/common/crypto_psa.c b/components/bt/esp_ble_mesh/common/crypto_psa.c index 340c027c390..ebb090b567d 100644 --- a/components/bt/esp_ble_mesh/common/crypto_psa.c +++ b/components/bt/esp_ble_mesh/common/crypto_psa.c @@ -1,6 +1,6 @@ /* * SPDX-FileCopyrightText: 2023 Nordic Semiconductor ASA - * SPDX-FileContributor: 2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileContributor: 2025-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -292,6 +292,7 @@ int bt_mesh_pub_key_gen(void) uint8_t private_key[PRIV_KEY_SIZE]; size_t key_len; int err; + int ret = 0; /* Destroy any existing key */ if (dh_pair.priv_key_id != PSA_KEY_ID_NULL) { @@ -300,15 +301,26 @@ int bt_mesh_pub_key_gen(void) } dh_pair.is_ready = false; - /* Generate a random private key (in little-endian format for storage) */ + /* Generate a random private key and let PSA validate the range. + * For NIST P-256, the private key scalar d must satisfy: 1 <= d < n. + * PSA will return PSA_ERROR_INVALID_ARGUMENT if the key is out of range. + */ + #define MAX_KEY_GEN_RETRIES 10 + int retries = 0; do { err = bt_mesh_rand(private_key, sizeof(private_key)); if (err) { BT_ERR("Failed to generate random private key"); - return err; + ret = err; + goto cleanup; } - /* Ensure the private key is valid (non-zero first bytes in BE) */ - } while (private_key[0] == 0 && private_key[1] == 0); + if (++retries > MAX_KEY_GEN_RETRIES) { + BT_ERR("Exceeded maximum key generation retries"); + ret = -EIO; + goto cleanup; + } + /* Minimal check for obviously invalid keys (all zeros in MSB region) */ + } while (private_key[0] == 0 && private_key[1] == 0 && private_key[2] == 0 && private_key[3] == 0); /* Configure key attributes for ECDH with P-256 */ psa_set_key_usage_flags(&key_attributes, PSA_KEY_USAGE_DERIVE); @@ -323,7 +335,8 @@ int bt_mesh_pub_key_gen(void) if (status != PSA_SUCCESS) { BT_ERR("PSA import private key failed: %d", status); psa_reset_key_attributes(&key_attributes); - return -EIO; + ret = -EIO; + goto cleanup; } /* Export public key (PSA computes it from the private key) */ @@ -334,13 +347,20 @@ int bt_mesh_pub_key_gen(void) psa_destroy_key(dh_pair.priv_key_id); dh_pair.priv_key_id = PSA_KEY_ID_NULL; psa_reset_key_attributes(&key_attributes); - return -EIO; + ret = -EIO; + goto cleanup; } dh_pair.is_ready = true; psa_reset_key_attributes(&key_attributes); - return 0; +cleanup: + /* Securely clear private key from stack to prevent key leakage */ + memset(private_key, 0, sizeof(private_key)); + /* Memory barrier to prevent compiler from optimizing out the memset */ + __asm__ __volatile__("" : : "r"(private_key) : "memory"); + + return ret; } const uint8_t *bt_mesh_pub_key_get_raw(void) @@ -360,6 +380,8 @@ void bt_mesh_set_private_key_raw(const uint8_t pri_key[32]) psa_status_t status; size_t key_len; + BT_DBG("Privkey:%s", bt_hex(pri_key, PRIV_KEY_SIZE)); + /* Destroy any existing key */ if (dh_pair.priv_key_id != PSA_KEY_ID_NULL) { psa_destroy_key(dh_pair.priv_key_id); @@ -393,7 +415,6 @@ void bt_mesh_set_private_key_raw(const uint8_t pri_key[32]) } BT_DBG("Pubkey:%s", bt_hex(&dh_pair.public_key[1], PUB_KEY_SIZE)); - BT_DBG("Privkey:%s", bt_hex(pri_key, PRIV_KEY_SIZE)); dh_pair.is_ready = true; psa_reset_key_attributes(&attributes); } @@ -407,7 +428,7 @@ bool bt_mesh_check_public_key_raw(const uint8_t key[64]) /* PSA requires 0x04 prefix for uncompressed point */ pub_be[0] = 0x04; - /* Convert from little-endian to big-endian */ + /* Copy X and Y coordinates (already in big-endian format) */ memcpy(&pub_be[1], key, 32); memcpy(&pub_be[33], key + 32, 32); @@ -587,15 +608,16 @@ void bt_mesh_key_assign(struct bt_mesh_key *dst, const struct bt_mesh_key *src) int bt_mesh_key_destroy(const struct bt_mesh_key *key) { psa_status_t status; + psa_key_id_t key_id = key->key; - status = psa_destroy_key(key->key); + status = psa_destroy_key(key_id); if (status != PSA_SUCCESS) { BT_ERR("PSA destroy key failed: %d", status); return -EIO; } #if CONFIG_BT_SETTINGS - return keyid_free(key->key); + return keyid_free(key_id); #else return 0; #endif diff --git a/components/bt/esp_ble_mesh/common/crypto_tc.c b/components/bt/esp_ble_mesh/common/crypto_tc.c index 24c8752c2a6..11979484e43 100644 --- a/components/bt/esp_ble_mesh/common/crypto_tc.c +++ b/components/bt/esp_ble_mesh/common/crypto_tc.c @@ -72,8 +72,8 @@ int bt_mesh_ccm_encrypt(const struct bt_mesh_key *key, uint8_t nonce[13], return -EIO; } - if (tc_ccm_generation_encryption(enc_data, len + mic_size, aad, aad_len, - plaintext, len, &ccm) == TC_CRYPTO_FAIL) { + if (tc_ccm_generation_encryption(enc_data, (unsigned int)(len + mic_size), aad, (unsigned int)aad_len, + plaintext, (unsigned int)len, &ccm) == TC_CRYPTO_FAIL) { return -EIO; } diff --git a/components/bt/esp_ble_mesh/common/include/mesh/atomic.h b/components/bt/esp_ble_mesh/common/include/mesh/atomic.h index b284974363a..16975d4a4d5 100644 --- a/components/bt/esp_ble_mesh/common/include/mesh/atomic.h +++ b/components/bt/esp_ble_mesh/common/include/mesh/atomic.h @@ -168,7 +168,7 @@ extern bt_mesh_atomic_val_t bt_mesh_atomic_and(bt_mesh_atomic_t *target, bt_mesh #ifdef CONFIG_ATOMIC_OPERATIONS_BUILTIN static inline bool bt_mesh_atomic_cas(bt_mesh_atomic_t *target, bt_mesh_atomic_val_t excepted, bt_mesh_atomic_val_t new_val) { - return __atomic_compare_exchange_n(target, &excepted, &new_val, false, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST); + return __atomic_compare_exchange_n(target, &excepted, new_val, false, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST); } #else extern bool bt_mesh_atomic_cas(bt_mesh_atomic_t *target, bt_mesh_atomic_val_t excepted, bt_mesh_atomic_val_t new_val); diff --git a/components/bt/esp_ble_mesh/common/include/mesh/mutex.h b/components/bt/esp_ble_mesh/common/include/mesh/mutex.h index 0cc47eb01a0..b32f0dcd722 100644 --- a/components/bt/esp_ble_mesh/common/include/mesh/mutex.h +++ b/components/bt/esp_ble_mesh/common/include/mesh/mutex.h @@ -50,7 +50,9 @@ void bt_mesh_atomic_lock(void); void bt_mesh_atomic_unlock(void); void bt_mesh_mutex_init(void); +#if CONFIG_BLE_MESH_DEINIT void bt_mesh_mutex_deinit(void); +#endif /* CONFIG_BLE_MESH_DEINIT */ #ifdef __cplusplus } diff --git a/components/bt/esp_ble_mesh/common/include/mesh/utils.h b/components/bt/esp_ble_mesh/common/include/mesh/utils.h index 4ef522ee322..4bcbe8df2da 100644 --- a/components/bt/esp_ble_mesh/common/include/mesh/utils.h +++ b/components/bt/esp_ble_mesh/common/include/mesh/utils.h @@ -41,6 +41,13 @@ extern "C" { #define INT_TO_POINTER(x) ((void *) (x)) #endif +#ifndef ESP_ASSUME_NONNULL +/** + * @brief Assume pointer parameters are non-null unless explicitly marked otherwise. +*/ +#define ESP_ASSUME_NONNULL(ptr) +#endif + /* Evaluates to 0 if cond is true-ish; compile error otherwise */ #ifndef ZERO_OR_COMPILE_ERROR #define ZERO_OR_COMPILE_ERROR(cond) ((int) sizeof(char[1 - 2 * !(cond)]) - 1) diff --git a/components/bt/esp_ble_mesh/common/kernel.c b/components/bt/esp_ble_mesh/common/kernel.c index 3db4ed45e12..e555bd42eef 100644 --- a/components/bt/esp_ble_mesh/common/kernel.c +++ b/components/bt/esp_ble_mesh/common/kernel.c @@ -1,14 +1,18 @@ /* * SPDX-FileCopyrightText: 2016 Intel Corporation * SPDX-FileCopyrightText: 2016 Wind River Systems, Inc. - * SPDX-FileContributor: 2020-2021 Espressif Systems (Shanghai) CO LTD + * SPDX-FileContributor: 2020-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ +#include "mesh/timer.h" #include "mesh/kernel.h" void k_sleep(int32_t duration) { - vTaskDelay(duration / portTICK_PERIOD_MS); + if (duration < 0 && duration != K_FOREVER) { + duration = 0; + } + vTaskDelay((duration == K_FOREVER) ? portMAX_DELAY : (duration / portTICK_PERIOD_MS)); } diff --git a/components/bt/esp_ble_mesh/common/mutex.c b/components/bt/esp_ble_mesh/common/mutex.c index d3d280c87b5..2cbd849d1aa 100644 --- a/components/bt/esp_ble_mesh/common/mutex.c +++ b/components/bt/esp_ble_mesh/common/mutex.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2017-2024 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2017-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -64,6 +64,8 @@ void bt_mesh_mutex_lock(bt_mesh_mutex_t *mutex) if (mutex->mutex) { xSemaphoreTake(mutex->mutex, portMAX_DELAY); + } else { + BT_ERR("Lock, no mutex"); } } @@ -76,6 +78,8 @@ void bt_mesh_mutex_unlock(bt_mesh_mutex_t *mutex) if (mutex->mutex) { xSemaphoreGive(mutex->mutex); + } else { + BT_ERR("Unlock, no mutex"); } } @@ -120,6 +124,8 @@ void bt_mesh_r_mutex_lock(bt_mesh_mutex_t *mutex) if (mutex->mutex) { xSemaphoreTakeRecursive(mutex->mutex, portMAX_DELAY); + } else { + BT_ERR("Lock, no recursive mutex"); } } @@ -132,6 +138,8 @@ void bt_mesh_r_mutex_unlock(bt_mesh_mutex_t *mutex) if (mutex->mutex) { xSemaphoreGiveRecursive(mutex->mutex); + } else { + BT_ERR("Unlock, no recursive mutex"); } } @@ -152,6 +160,11 @@ void bt_mesh_c_semaphore_create(bt_mesh_mutex_t *mutex, int max, int init) return; } + if (max <= 0 || init < 0 || init > max) { + BT_ERR("Create, invalid semaphore parameters (max=%d, init=%d)", max, init); + return; + } + #if CONFIG_BLE_MESH_FREERTOS_STATIC_ALLOC #if CONFIG_BLE_MESH_FREERTOS_STATIC_ALLOC_EXTERNAL mutex->buffer = heap_caps_calloc_prefer(1, sizeof(StaticQueue_t), 2, MALLOC_CAP_SPIRAM|MALLOC_CAP_8BIT, MALLOC_CAP_INTERNAL|MALLOC_CAP_8BIT); @@ -175,7 +188,11 @@ void bt_mesh_c_semaphore_take(bt_mesh_mutex_t *mutex, uint32_t timeout) } if (mutex->mutex) { - xSemaphoreTake(mutex->mutex, timeout / portTICK_PERIOD_MS); + if (xSemaphoreTake(mutex->mutex, timeout / portTICK_PERIOD_MS) != pdTRUE) { + BT_ERR("Failed to take semaphore"); + } + } else { + BT_ERR("Lock, no semaphore"); } } @@ -188,63 +205,65 @@ void bt_mesh_c_semaphore_give(bt_mesh_mutex_t *mutex) if (mutex->mutex) { xSemaphoreGive(mutex->mutex); + } else { + BT_ERR("Unlock, no semaphore"); } } void bt_mesh_alarm_lock(void) { - bt_mesh_mutex_lock(&alarm_lock); + bt_mesh_r_mutex_lock(&alarm_lock); } void bt_mesh_alarm_unlock(void) { - bt_mesh_mutex_unlock(&alarm_lock); + bt_mesh_r_mutex_unlock(&alarm_lock); } void bt_mesh_list_lock(void) { - bt_mesh_mutex_lock(&list_lock); + bt_mesh_r_mutex_lock(&list_lock); } void bt_mesh_list_unlock(void) { - bt_mesh_mutex_unlock(&list_lock); + bt_mesh_r_mutex_unlock(&list_lock); } void bt_mesh_buf_lock(void) { - bt_mesh_mutex_lock(&buf_lock); + bt_mesh_r_mutex_lock(&buf_lock); } void bt_mesh_buf_unlock(void) { - bt_mesh_mutex_unlock(&buf_lock); + bt_mesh_r_mutex_unlock(&buf_lock); } void bt_mesh_atomic_lock(void) { - bt_mesh_mutex_lock(&atomic_lock); + bt_mesh_r_mutex_lock(&atomic_lock); } void bt_mesh_atomic_unlock(void) { - bt_mesh_mutex_unlock(&atomic_lock); + bt_mesh_r_mutex_unlock(&atomic_lock); } void bt_mesh_mutex_init(void) { - bt_mesh_mutex_create(&alarm_lock); - bt_mesh_mutex_create(&list_lock); - bt_mesh_mutex_create(&buf_lock); - bt_mesh_mutex_create(&atomic_lock); + bt_mesh_r_mutex_create(&alarm_lock); + bt_mesh_r_mutex_create(&list_lock); + bt_mesh_r_mutex_create(&buf_lock); + bt_mesh_r_mutex_create(&atomic_lock); } #if CONFIG_BLE_MESH_DEINIT void bt_mesh_mutex_deinit(void) { - bt_mesh_mutex_free(&alarm_lock); - bt_mesh_mutex_free(&list_lock); - bt_mesh_mutex_free(&buf_lock); - bt_mesh_mutex_free(&atomic_lock); + bt_mesh_r_mutex_free(&alarm_lock); + bt_mesh_r_mutex_free(&list_lock); + bt_mesh_r_mutex_free(&buf_lock); + bt_mesh_r_mutex_free(&atomic_lock); } #endif /* CONFIG_BLE_MESH_DEINIT */ diff --git a/components/bt/esp_ble_mesh/common/queue.c b/components/bt/esp_ble_mesh/common/queue.c index 1fc3d66a618..3e19dca4d89 100644 --- a/components/bt/esp_ble_mesh/common/queue.c +++ b/components/bt/esp_ble_mesh/common/queue.c @@ -35,7 +35,7 @@ int bt_mesh_queue_init(bt_mesh_queue_t *queue, uint16_t queue_size, uint8_t item int bt_mesh_queue_deinit(bt_mesh_queue_t *queue) { - __ASSERT(queue, "Invalid queue init parameters"); + __ASSERT(queue && queue->handle, "Invalid queue deinit parameters"); vQueueDelete(queue->handle); queue->handle = NULL; #if CONFIG_BLE_MESH_FREERTOS_STATIC_ALLOC diff --git a/components/bt/esp_ble_mesh/common/timer.c b/components/bt/esp_ble_mesh/common/timer.c index 7547eecdbb6..95b6b73911d 100644 --- a/components/bt/esp_ble_mesh/common/timer.c +++ b/components/bt/esp_ble_mesh/common/timer.c @@ -135,7 +135,7 @@ int k_delayed_work_submit(struct k_delayed_work *work, int32_t delay) } /* If delay is 0, call the corresponding timeout handler. */ - if (delay == 0) { + if (delay <= 0) { k_work_submit(&work->work); return 0; } diff --git a/components/bt/esp_ble_mesh/common/utils.c b/components/bt/esp_ble_mesh/common/utils.c index 799035e4b30..05ef7436948 100644 --- a/components/bt/esp_ble_mesh/common/utils.c +++ b/components/bt/esp_ble_mesh/common/utils.c @@ -16,7 +16,10 @@ const char *bt_hex(const void *buf, size_t len) { static const char hex[] = "0123456789abcdef"; - static char hexbufs[2][129]; + /* WARNING: Buffer count limits concurrent bt_hex() calls in a single + * expression or across threads. Increase if more simultaneous calls needed. + */ + static char hexbufs[4][129]; static uint8_t curbuf; const uint8_t *b = buf; char *str = NULL; @@ -25,6 +28,11 @@ const char *bt_hex(const void *buf, size_t len) str = hexbufs[curbuf++]; curbuf %= ARRAY_SIZE(hexbufs); + if (buf == NULL) { + str[0] = '\0'; + return str; + } + len = MIN(len, (sizeof(hexbufs[0]) - 1) / 2); for (i = 0; i < len; i++) { @@ -39,6 +47,10 @@ const char *bt_hex(const void *buf, size_t len) void mem_rcopy(uint8_t *dst, uint8_t const *src, uint16_t len) { + if (dst == NULL || src == NULL || len == 0) { + return; + } + src += len; while (len--) { *dst++ = *--src; diff --git a/components/bt/esp_ble_mesh/core/access.c b/components/bt/esp_ble_mesh/core/access.c index 2f70e64eeae..584765fb6f7 100644 --- a/components/bt/esp_ble_mesh/core/access.c +++ b/components/bt/esp_ble_mesh/core/access.c @@ -2,7 +2,7 @@ /* * SPDX-FileCopyrightText: 2017 Intel Corporation - * SPDX-FileContributor: 2018-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileContributor: 2018-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -141,6 +141,11 @@ static int32_t next_period(struct bt_mesh_model *mod) return 0; } + if (pub->period_start == 0) { + BT_ERR("PubFailed,TryPubInNxtPeriod"); + return period; + } + elapsed = k_uptime_get_32() - pub->period_start; BT_INFO("Elapsed %u Period %u", elapsed, period); @@ -244,6 +249,13 @@ static int publish_retransmit(struct bt_mesh_model *mod) /* Tag with send-segmented */ ctx.send_tag |= BLE_MESH_TAG_SEND_SEGMENTED; } +#if CONFIG_BLE_MESH_LONG_PACKET + if ((pub->msg->len <= MIN(BLE_MESH_EXT_TX_SDU_MAX, BLE_MESH_EXT_SDU_MAX_LEN) - BLE_MESH_MIC_SHORT) && + (pub->msg->len > MIN(BLE_MESH_TX_SDU_MAX, BLE_MESH_SDU_MAX_LEN) - BLE_MESH_MIC_SHORT)) { + ctx.enh.long_pkt_cfg_used = true; + ctx.enh.long_pkt_cfg = BLE_MESH_LONG_PACKET_PREFER; + } +#endif /* CONFIG_BLE_MESH_LONG_PACKET */ BT_DBG("NetIdx 0x%04x AppIdx 0x%04x Dst 0x%04x", ctx.net_idx, ctx.app_idx, ctx.addr); @@ -316,13 +328,16 @@ static void mod_publish(struct k_work *work) * In the event, users can update the context of the publish message * which will be published in the next period. */ - if (pub->update && pub->update(pub->mod)) { - /* Cancel this publish attempt. */ - BT_ERR("Update failed, skipping publish (err %d)", err); + if (pub->update) { + err = pub->update(pub->mod); + if (err) { + /* Cancel this publish attempt. */ + BT_ERR("Update failed, skipping publish (err %d)", err); - pub->period_start = k_uptime_get_32(); - publish_retransmit_end(err, pub); - return; + pub->period_start = k_uptime_get_32(); + publish_retransmit_end(err, pub); + return; + } } err = bt_mesh_model_publish(pub->mod); @@ -335,6 +350,16 @@ struct bt_mesh_elem *bt_mesh_model_elem(const struct bt_mesh_model *mod) { BT_DBG("ModelElem, ElemIdx %u", mod->elem_idx); + if (!comp_0) { + BT_ERR("comp_0 not initialized"); + return NULL; + } + + if (mod->elem_idx >= comp_0->elem_count) { + BT_ERR("Invalid element index %u", mod->elem_idx); + return NULL; + } + return &comp_0->elem[mod->elem_idx]; } @@ -428,7 +453,7 @@ int bt_mesh_comp_register(const struct bt_mesh_comp *comp) BT_DBG("CompRegister, ElemCount %u", comp->elem_count); /* There must be at least one element */ - if (!comp->elem_count) { + if (!comp->elem_count || comp->elem_count > BLE_MESH_MODEL_MAX_ELEM_COUNT) { return -EINVAL; } @@ -504,6 +529,20 @@ void bt_mesh_comp_provision(uint16_t addr) { int i; + if (!comp_0) { + BT_ERR("comp_0 not initialized"); + return; + } + + /* Validate unicast address range: addr must be valid (0x0001-0x7FFF) and + * addr + elem_count - 1 must not exceed 0x7FFF (unicast address upper bound). + */ + if (!BLE_MESH_ADDR_IS_UNICAST(addr) || + (uint32_t)addr + comp_0->elem_count - 1 > 0x7FFF) { + BT_ERR("Address range overflow: addr 0x%04x, elem_count %u", addr, comp_0->elem_count); + return; + } + dev_primary_addr = addr; BT_INFO("CompProvision, PrimaryAddr 0x%04x ElemCount %u", addr, comp_0->elem_count); @@ -585,6 +624,11 @@ struct bt_mesh_elem *bt_mesh_elem_find(uint16_t addr) BT_DBG("ElemFind, Addr 0x%04x", addr); + if (!comp_0) { + BT_ERR("comp_0 not initialized"); + return NULL; + } + if (BLE_MESH_ADDR_IS_UNICAST(addr)) { index = (addr - comp_0->elem[0].addr); if (index < comp_0->elem_count) { @@ -609,6 +653,11 @@ bool bt_mesh_has_addr(uint16_t addr) { uint16_t index; + if (!comp_0) { + BT_ERR("comp_0 not initialized"); + return false; + } + if (BLE_MESH_ADDR_IS_UNICAST(addr)) { return bt_mesh_elem_find(addr) != NULL; } @@ -626,6 +675,11 @@ bool bt_mesh_has_addr(uint16_t addr) uint8_t bt_mesh_elem_count(void) { + if (!comp_0) { + BT_ERR("comp_0 not initialized"); + return 0; + } + BT_DBG("ElemCount %u", comp_0->elem_count); return comp_0->elem_count; @@ -797,6 +851,11 @@ void bt_mesh_model_recv(struct bt_mesh_net_rx *rx, struct net_buf_simple *buf) rx->ctx.app_idx, rx->ctx.addr, rx->ctx.recv_dst); BT_INFO("Len %u: %s", buf->len, bt_hex(buf->data, buf->len)); + if (!comp_0) { + BT_ERR("comp_0 not initialized"); + return; + } + if (get_opcode(buf, &opcode, true) < 0) { BT_WARN("Unable to decode OpCode"); return; @@ -911,7 +970,7 @@ static bool ready_to_send(uint16_t dst) if (IS_ENABLED(CONFIG_BLE_MESH_PROVISIONER) && bt_mesh_is_provisioner_en()) { if (bt_mesh_provisioner_check_msg_dst(dst) == false && - bt_mesh_elem_find(dst) == false) { + bt_mesh_elem_find(dst) == NULL) { BT_ERR("Failed to find Dst 0x%04x", dst); return false; } @@ -1205,10 +1264,22 @@ int bt_mesh_model_publish(struct bt_mesh_model *model) return -EADDRNOTAVAIL; } +#if CONFIG_BLE_MESH_LONG_PACKET + if (pub->msg->len + BLE_MESH_MIC_SHORT > MIN(BLE_MESH_EXT_TX_SDU_MAX, BLE_MESH_EXT_SDU_MAX_LEN)) { + BT_ERR("Message does not fit extended maximum SDU size"); + return -EMSGSIZE; + } + if ((pub->msg->len <= MIN(BLE_MESH_EXT_TX_SDU_MAX, BLE_MESH_EXT_SDU_MAX_LEN) - BLE_MESH_MIC_SHORT) && + (pub->msg->len > MIN(BLE_MESH_TX_SDU_MAX, BLE_MESH_SDU_MAX_LEN) - BLE_MESH_MIC_SHORT)) { + tx.ctx->enh.long_pkt_cfg_used = true; + tx.ctx->enh.long_pkt_cfg = BLE_MESH_LONG_PACKET_PREFER; + } +#else if (pub->msg->len + BLE_MESH_MIC_SHORT > MIN(BLE_MESH_TX_SDU_MAX, BLE_MESH_SDU_MAX_LEN)) { BT_ERR("Message does not fit maximum SDU size"); return -EMSGSIZE; } +#endif if (pub->count) { BT_WARN("Clearing publish retransmit timer"); @@ -1245,6 +1316,7 @@ int bt_mesh_model_publish(struct bt_mesh_model *model) bt_mesh_model_pub_use_directed(&tx, pub->directed_pub_policy); #endif /* CONFIG_BLE_MESH_DF_SRV */ + pub->period_start = 0; pub->count = BLE_MESH_PUB_TRANSMIT_COUNT(pub->retransmit); BT_INFO("PubCount %u PubInterval %u", diff --git a/components/bt/esp_ble_mesh/core/access.h b/components/bt/esp_ble_mesh/core/access.h index 669cff788c2..f98a2187900 100644 --- a/components/bt/esp_ble_mesh/core/access.h +++ b/components/bt/esp_ble_mesh/core/access.h @@ -15,6 +15,8 @@ extern "C" { #endif +#define BLE_MESH_MODEL_MAX_ELEM_COUNT 255 + /* bt_mesh_model.flags */ enum { BLE_MESH_MOD_BIND_PENDING = BIT(0), diff --git a/components/bt/esp_ble_mesh/core/adv.c b/components/bt/esp_ble_mesh/core/adv.c index e35d34c993c..e6de7c9d1b7 100644 --- a/components/bt/esp_ble_mesh/core/adv.c +++ b/components/bt/esp_ble_mesh/core/adv.c @@ -144,7 +144,7 @@ static int adv_send(struct net_buf *buf) struct bt_mesh_ble_adv_data data = {0}; struct bt_mesh_ble_adv_tx *tx = cb_data; - if (tx == NULL) { + if (tx == NULL || tx->buf == NULL) { BT_ERR("Invalid adv user data"); net_buf_unref(buf); return -EINVAL; @@ -217,7 +217,7 @@ static QueueHandle_t relay_adv_handle_get(void) adv_type = bt_mesh_adv_types_mgmt_get(BLE_MESH_ADV_RELAY_DATA); - if (adv_type->adv_q == NULL) { + if (adv_type == NULL || adv_type->adv_q == NULL) { BT_DBG("HandleNotFound"); return NULL; } diff --git a/components/bt/esp_ble_mesh/core/adv_common.c b/components/bt/esp_ble_mesh/core/adv_common.c index bf8e33e506b..37790fe10c3 100644 --- a/components/bt/esp_ble_mesh/core/adv_common.c +++ b/components/bt/esp_ble_mesh/core/adv_common.c @@ -216,6 +216,7 @@ int bt_mesh_adv_inst_deinit(enum bt_mesh_adv_inst_type inst_type) static struct bt_mesh_adv *adv_alloc(int id, enum bt_mesh_adv_type type) { BT_DBG("AdvAlloc, ID %d", id); + assert(id >= 0 && id < CONFIG_BLE_MESH_ADV_BUF_COUNT); init_adv_with_defaults(&adv_pool[id], type); return &adv_pool[id]; } @@ -700,7 +701,7 @@ void bt_mesh_relay_adv_init(void) &relay_adv_buf_pool, &relay_adv_alloc); #if CONFIG_BLE_MESH_EXT_ADV bt_mesh_adv_type_init(BLE_MESH_ADV_EXT_RELAY_DATA, &relay_adv_queue, - &ext_adv_buf_pool, &ext_relay_adv_alloc); + &ext_relay_adv_buf_pool, &ext_relay_adv_alloc); #if CONFIG_BLE_MESH_LONG_PACKET && CONFIG_BLE_MESH_LONG_PACKET_RELAY_ADV_BUF_COUNT bt_mesh_adv_type_init(BLE_MESH_ADV_EXT_LONG_RELAY_DATA, &relay_adv_queue, &ext_long_relay_adv_buf_pool, ext_long_relay_adv_alloc); diff --git a/components/bt/esp_ble_mesh/core/beacon.c b/components/bt/esp_ble_mesh/core/beacon.c index 4f030ec99d6..39c0188cc89 100644 --- a/components/bt/esp_ble_mesh/core/beacon.c +++ b/components/bt/esp_ble_mesh/core/beacon.c @@ -2,7 +2,7 @@ /* * SPDX-FileCopyrightText: 2017 Intel Corporation - * SPDX-FileContributor: 2018-2024 Espressif Systems (Shanghai) CO LTD + * SPDX-FileContributor: 2018-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -225,13 +225,14 @@ static int secure_beacon_send(void) #if (CONFIG_BLE_MESH_NODE && CONFIG_BLE_MESH_PB_ADV) static int unprovisioned_beacon_send(void) { + const struct bt_mesh_prov *prov = bt_mesh_prov_get(); uint8_t uri_hash[16] = {0}; struct net_buf *buf = NULL; uint16_t oob_info = 0U; BT_DBG("UnprovisionedBeaconSend"); - if (bt_mesh_prov_get() == NULL) { + if (prov == NULL) { BT_ERR("No provisioning context provided"); return -EINVAL; } @@ -243,13 +244,13 @@ static int unprovisioned_beacon_send(void) } net_buf_add_u8(buf, BEACON_TYPE_UNPROVISIONED); - net_buf_add_mem(buf, bt_mesh_prov_get()->uuid, 16); + net_buf_add_mem(buf, prov->uuid, 16); - if (bt_mesh_prov_get()->uri && - bt_mesh_s1(bt_mesh_prov_get()->uri, uri_hash) == 0) { - oob_info = bt_mesh_prov_get()->oob_info | BLE_MESH_PROV_OOB_URI; + if (prov->uri && + bt_mesh_s1(prov->uri, uri_hash) == 0) { + oob_info = prov->oob_info | BLE_MESH_PROV_OOB_URI; } else { - oob_info = bt_mesh_prov_get()->oob_info; + oob_info = prov->oob_info; } net_buf_add_be16(buf, oob_info); @@ -258,7 +259,7 @@ static int unprovisioned_beacon_send(void) bt_mesh_adv_send(buf, UNPROV_XMIT, NULL, NULL); net_buf_unref(buf); - if (bt_mesh_prov_get()->uri) { + if (prov->uri) { size_t len = 0; buf = bt_mesh_adv_create(BLE_MESH_ADV_URI, K_NO_WAIT); @@ -267,14 +268,14 @@ static int unprovisioned_beacon_send(void) return -ENOBUFS; } - len = strlen(bt_mesh_prov_get()->uri); + len = strlen(prov->uri); - BT_DBG("URI %u: %s", len, bt_mesh_prov_get()->uri); + BT_DBG("URI %u: %s", len, prov->uri); if (net_buf_tailroom(buf) < len) { BT_WARN("Too long URI to fit advertising data"); } else { - net_buf_add_mem(buf, bt_mesh_prov_get()->uri, len); + net_buf_add_mem(buf, prov->uri, len); bt_mesh_adv_send(buf, UNPROV_XMIT, NULL, NULL); } @@ -561,6 +562,7 @@ void bt_mesh_beacon_init(void) /* private beacon init */ if (bt_mesh_private_beacon_timer_init()) { BT_ERR("Failed to create a mpb_timer"); + k_delayed_work_free(&snb_timer); return; } #endif /* CONFIG_BLE_MESH_PRB_SRV */ diff --git a/components/bt/esp_ble_mesh/core/bluedroid_host/adapter.c b/components/bt/esp_ble_mesh/core/bluedroid_host/adapter.c index a3bd5491c1e..f815bcd0209 100644 --- a/components/bt/esp_ble_mesh/core/bluedroid_host/adapter.c +++ b/components/bt/esp_ble_mesh/core/bluedroid_host/adapter.c @@ -455,7 +455,7 @@ void ble_mesh_5_gap_callback(tBTA_DM_BLE_5_GAP_EVENT event, break; case BTA_DM_BLE_5_GAP_EXT_SCAN_STOP_COMPLETE_EVT: if (params->scan_stop.status != BTM_SUCCESS) { - BT_ERR("BTM_BLE_5_GAP_EXT_SCAN_START_COMPLETE_EVT Failed"); + BT_ERR("BTA_DM_BLE_5_GAP_EXT_SCAN_STOP_COMPLETE_EVT Failed"); } break; default: @@ -571,7 +571,7 @@ static int start_le_scan(uint8_t scan_type, uint16_t interval, uint16_t window, if (interval == 0 || interval < window) { BT_ERR("invalid scan param itvl %d win %d", interval, window); - return EINVAL; + return -EINVAL; } ext_scan_params.own_addr_type = BLE_MESH_ADDR_PUBLIC; @@ -970,11 +970,19 @@ int bt_mesh_ble_ext_adv_start(const uint8_t inst_id, if (data && param->adv_type != BLE_MESH_ADV_DIRECT_IND && param->adv_type != BLE_MESH_ADV_DIRECT_IND_LOW_DUTY) { if (data->adv_data_len) { + if (data->adv_data_len > sizeof(set.data)) { + BT_ERR("adv_data_len %u exceeds buffer size %zu", data->adv_data_len, sizeof(set.data)); + return -EINVAL; + } set.len = data->adv_data_len; memcpy(set.data, data->adv_data, data->adv_data_len); BTA_DmBleGapConfigExtAdvDataRaw(false, inst_id, set.len, set.data); } if (data->scan_rsp_data_len && param->adv_type != BLE_MESH_ADV_NONCONN_IND) { + if (data->scan_rsp_data_len > sizeof(set.data)) { + BT_ERR("scan_rsp_data_len %u exceeds buffer size %zu", data->scan_rsp_data_len, sizeof(set.data)); + return -EINVAL; + } set.len = data->scan_rsp_data_len; memcpy(set.data, data->scan_rsp_data, data->scan_rsp_data_len); BTA_DmBleGapConfigExtAdvDataRaw(true, inst_id, set.len, set.data); @@ -1608,6 +1616,10 @@ int bt_mesh_gatts_service_register(struct bt_mesh_gatt_service *svc) break; } case BLE_MESH_UUID_GATT_CHRC_VAL: { + if (i + 1 >= svc->attr_count) { + BT_ERR("Characteristic declaration at index %d missing value attribute", i); + goto cleanup; + } gatts_future_mesh = future_new(); struct bt_mesh_gatt_char *gatts_chrc = (struct bt_mesh_gatt_char *)svc->attrs[i].user_data; bta_uuid_to_bt_mesh_uuid(&bta_uuid, gatts_chrc->uuid); @@ -2315,7 +2327,11 @@ static void bt_mesh_bta_gattc_cb(tBTA_GATTC_EVT event, tBTA_GATTC *p_data) } break; case BTA_GATTC_CLOSE_EVT: - bta_gattc_clcb_dealloc_by_conn_id(p_data->close.conn_id); + /* CLCB lifetime is owned by BTA: bta_gattc_close() deallocates the + * CLCB right after invoking this synchronous callback. Calling + * bta_gattc_clcb_dealloc_by_conn_id() here would be a redundant + * double-dealloc (currently a no-op only because of NULL checks in + * bta_gattc_clcb_dealloc()). Keep this branch as a pure notification. */ BT_DBG("BTA_GATTC_CLOSE_EVT"); break; case BTA_GATTC_CONNECT_EVT: { diff --git a/components/bt/esp_ble_mesh/core/cfg_cli.c b/components/bt/esp_ble_mesh/core/cfg_cli.c index 6daae86afed..8946e45a558 100644 --- a/components/bt/esp_ble_mesh/core/cfg_cli.c +++ b/components/bt/esp_ble_mesh/core/cfg_cli.c @@ -2,7 +2,7 @@ /* * SPDX-FileCopyrightText: 2017 Intel Corporation - * SPDX-FileContributor: 2018-2021 Espressif Systems (Shanghai) CO LTD + * SPDX-FileContributor: 2018-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -759,6 +759,12 @@ int bt_mesh_cfg_ttl_set(bt_mesh_client_common_param_t *param, uint8_t val) { BT_DBG("TTLSet, Val 0x%02x", val); + /* Per BLE Mesh spec, TTL 0x01 is prohibited and 0x80-0xFF are reserved */ + if (val == 0x01 || val > 0x7F) { + BT_ERR("Invalid TTL value 0x%02x", val); + return -EINVAL; + } + return send_msg_with_u8(param, OP_DEFAULT_TTL_SET, val); } diff --git a/components/bt/esp_ble_mesh/core/cfg_srv.c b/components/bt/esp_ble_mesh/core/cfg_srv.c index 6f7396bc6ba..a0155398468 100644 --- a/components/bt/esp_ble_mesh/core/cfg_srv.c +++ b/components/bt/esp_ble_mesh/core/cfg_srv.c @@ -2,7 +2,7 @@ /* * SPDX-FileCopyrightText: 2017 Intel Corporation - * SPDX-FileContributor: 2018-2024 Espressif Systems (Shanghai) CO LTD + * SPDX-FileContributor: 2018-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -911,6 +911,7 @@ static void default_ttl_set(struct bt_mesh_model *model, } } else { BT_WARN("Prohibited Default TTL value 0x%02x", buf->data[0]); + return; } bt_mesh_model_msg_init(&msg, OP_DEFAULT_TTL_STATUS); @@ -1348,7 +1349,7 @@ static struct label *va_find(const uint8_t *label_uuid, for (i = 0; i < ARRAY_SIZE(labels); i++) { if (labels[i].ref == 0) { - if (free_slot != NULL) { + if (free_slot != NULL && *free_slot == NULL) { *free_slot = &labels[i]; } continue; @@ -1370,6 +1371,9 @@ uint8_t va_add(uint8_t *label_uuid, uint16_t *addr) update = va_find(label_uuid, &free_slot); if (update) { + if (update->ref == UINT16_MAX) { + return STATUS_INSUFF_RESOURCES; + } update->ref++; va_store(update); @@ -2590,6 +2594,7 @@ static void net_key_update(struct bt_mesh_model *model, switch (sub->kr_phase) { case BLE_MESH_KR_NORMAL: if (!memcmp(buf->data, sub->keys[0].net, 16)) { + send_net_key_status(model, ctx, idx, STATUS_SUCCESS); return; } break; diff --git a/components/bt/esp_ble_mesh/core/crypto.c b/components/bt/esp_ble_mesh/core/crypto.c index 8e85c8f4361..6c83c281ab0 100644 --- a/components/bt/esp_ble_mesh/core/crypto.c +++ b/components/bt/esp_ble_mesh/core/crypto.c @@ -2,7 +2,7 @@ /* * SPDX-FileCopyrightText: 2017 Intel Corporation - * SPDX-FileContributor: 2018-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileContributor: 2018-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -28,14 +28,19 @@ int bt_mesh_k1(const uint8_t *ikm, size_t ikm_len, const uint8_t salt[16], const char *info, uint8_t okm[16]) { + uint8_t t[16] = {0}; int err = 0; - err = bt_mesh_aes_cmac_one(salt, ikm, ikm_len, okm); + err = bt_mesh_aes_cmac_one(salt, ikm, ikm_len, t); if (err < 0) { + memset(t, 0, sizeof(t)); return err; } - return bt_mesh_aes_cmac_one(okm, info, strlen(info), okm); + err = bt_mesh_aes_cmac_one(t, info, strlen(info), okm); + + memset(t, 0, sizeof(t)); + return err; } int bt_mesh_k2(const uint8_t n[16], const uint8_t *p, size_t p_len, diff --git a/components/bt/esp_ble_mesh/core/crypto.h b/components/bt/esp_ble_mesh/core/crypto.h index ac6924c4f17..14a97f5f0d3 100644 --- a/components/bt/esp_ble_mesh/core/crypto.h +++ b/components/bt/esp_ble_mesh/core/crypto.h @@ -24,7 +24,7 @@ extern "C" { /* bt_mesh_aes_cmac_one is defined as inline in mesh/crypto.h */ -static inline bool bt_mesh_s1(const char *m, uint8_t salt[16]) +static inline int bt_mesh_s1(const char *m, uint8_t salt[16]) { const uint8_t zero[16] = { 0 }; diff --git a/components/bt/esp_ble_mesh/core/ext_adv.c b/components/bt/esp_ble_mesh/core/ext_adv.c index a7279b6c2e4..2ba48d0ac20 100644 --- a/components/bt/esp_ble_mesh/core/ext_adv.c +++ b/components/bt/esp_ble_mesh/core/ext_adv.c @@ -2,7 +2,7 @@ /* * SPDX-FileCopyrightText: 2017 Intel Corporation - * SPDX-FileContributor: 2018-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileContributor: 2018-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -30,7 +30,7 @@ static struct bt_mesh_adv_inst *adv_insts; -static int adv_send(struct bt_mesh_adv_inst *inst, uint16_t *adv_duration) +static int adv_send(struct bt_mesh_adv_inst *inst, int32_t *adv_duration) { struct net_buf *buf = inst->sending_buf; const struct bt_mesh_send_cb *cb = BLE_MESH_ADV(buf)->cb; @@ -57,9 +57,10 @@ static int adv_send(struct bt_mesh_adv_inst *inst, uint16_t *adv_duration) case BLE_MESH_ADV_EXT_LONG_DATA: case BLE_MESH_ADV_EXT_LONG_RELAY_DATA: #endif /* CONFIG_BLE_MESH_LONG_PACKET */ - { - is_ext_adv = true; - } + { + is_ext_adv = true; + } + __attribute__((fallthrough)); #endif /* CONFIG_BLE_MESH_EXT_ADV */ case BLE_MESH_ADV_PROV: case BLE_MESH_ADV_DATA: @@ -195,7 +196,7 @@ static int adv_send(struct bt_mesh_adv_inst *inst, uint16_t *adv_duration) return err; } - *adv_duration = duration; + *adv_duration = (int32_t)duration; BT_DBG("Advertising started. %u ms", duration); return 0; @@ -264,12 +265,12 @@ static int find_valid_msg_from_queue(bt_mesh_queue_t *msg_queue, bt_mesh_msg_t * return 0; } -static int activate_idle_adv_instance(uint32_t *update_evts, uint16_t *min_duration) +static int activate_idle_adv_instance(uint32_t *update_evts, int32_t *min_duration) { - uint16_t cur_min_duration = K_FOREVER; + int32_t cur_min_duration = K_FOREVER; enum bt_mesh_adv_type adv_type = 0; bt_mesh_queue_t *msg_queue = NULL; - uint16_t duration = K_FOREVER; + int32_t duration = K_FOREVER; bt_mesh_msg_t msg = {0}; uint32_t spt_mask = 0; uint32_t evts = 0; @@ -389,13 +390,17 @@ static uint32_t received_adv_evts_handle(uint32_t recv_evts) } else #endif { - BLE_MESH_SEND_END_CB(0, BLE_MESH_ADV(adv_insts[i].sending_buf)->cb, - BLE_MESH_ADV(adv_insts[i].sending_buf)->cb_data); + if (adv_insts[i].sending_buf == NULL) { + BT_WARN("sending_buf is NULL for inst %d, skipping", i); + } else { + BLE_MESH_SEND_END_CB(0, BLE_MESH_ADV(adv_insts[i].sending_buf)->cb, + BLE_MESH_ADV(adv_insts[i].sending_buf)->cb_data); - bt_mesh_adv_buf_ref_debug(__func__, adv_insts[i].sending_buf, 4U, BLE_MESH_BUF_REF_SMALL); + bt_mesh_adv_buf_ref_debug(__func__, adv_insts[i].sending_buf, 4U, BLE_MESH_BUF_REF_SMALL); - net_buf_unref(adv_insts[i].sending_buf); - adv_insts[i].sending_buf = NULL; + net_buf_unref(adv_insts[i].sending_buf); + adv_insts[i].sending_buf = NULL; + } } adv_insts[i].busy = false; @@ -407,7 +412,7 @@ static uint32_t received_adv_evts_handle(uint32_t recv_evts) static void adv_thread(void *p) { - uint16_t adv_duration = K_FOREVER; + int32_t adv_duration = K_FOREVER; uint32_t recv_evts = 0; uint32_t wait_evts = 0; diff --git a/components/bt/esp_ble_mesh/core/fast_prov.c b/components/bt/esp_ble_mesh/core/fast_prov.c index 6a256fb3bb3..a7b059eaae1 100644 --- a/components/bt/esp_ble_mesh/core/fast_prov.c +++ b/components/bt/esp_ble_mesh/core/fast_prov.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2017-2021 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2017-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -62,7 +62,8 @@ struct bt_mesh_subnet *bt_mesh_fast_prov_subnet_get(uint16_t net_idx) for (i = 0; i < ARRAY_SIZE(bt_mesh.sub); i++) { sub = &bt_mesh.sub[i]; - if (sub->net_idx == net_idx) { + if (sub->net_idx != BLE_MESH_KEY_UNUSED && + sub->net_idx == net_idx) { BT_DBG("NodeSub"); return sub; } @@ -194,6 +195,12 @@ uint8_t bt_mesh_set_fast_prov_action(uint8_t action) } if (action == ACTION_ENTER) { + /* Perform validation before state mutation */ + if (bt_mesh_provisioner_set_primary_elem_addr(bt_mesh_primary_addr()) < 0) { + BT_ERR("SetPrimaryElemAddrFail"); + return 0x01; + } + if (bt_mesh_secure_beacon_get() == BLE_MESH_SECURE_BEACON_ENABLED) { bt_mesh_secure_beacon_disable(); } @@ -201,11 +208,6 @@ uint8_t bt_mesh_set_fast_prov_action(uint8_t action) if (IS_ENABLED(CONFIG_BLE_MESH_PB_GATT)) { bt_mesh_proxy_client_prov_enable(); } - - if (bt_mesh_provisioner_set_primary_elem_addr(bt_mesh_primary_addr()) < 0) { - BT_ERR("SetPrimaryElemAddrFail"); - return 0x01; - } bt_mesh_provisioner_set_prov_bearer(BLE_MESH_PROV_ADV, false); bt_mesh_provisioner_fast_prov_enable(true); bt_mesh_atomic_or(bt_mesh.flags, BIT(BLE_MESH_PROVISIONER) | BIT(BLE_MESH_VALID_PROV)); diff --git a/components/bt/esp_ble_mesh/core/friend.c b/components/bt/esp_ble_mesh/core/friend.c index ed02cd3129e..6800eb68f0f 100644 --- a/components/bt/esp_ble_mesh/core/friend.c +++ b/components/bt/esp_ble_mesh/core/friend.c @@ -994,7 +994,7 @@ int bt_mesh_friend_clear_cfm(struct bt_mesh_net_rx *rx, frnd = find_clear(rx->ctx.addr); if (!frnd) { - BT_WARN("No pending clear procedure for 0x%02x", rx->ctx.addr); + BT_WARN("No pending clear procedure for 0x%04x", rx->ctx.addr); return 0; } diff --git a/components/bt/esp_ble_mesh/core/health_cli.c b/components/bt/esp_ble_mesh/core/health_cli.c index c458710db12..b2883220216 100644 --- a/components/bt/esp_ble_mesh/core/health_cli.c +++ b/components/bt/esp_ble_mesh/core/health_cli.c @@ -270,7 +270,7 @@ int bt_mesh_health_fault_test(bt_mesh_client_common_param_t *param, { BLE_MESH_MODEL_BUF_DEFINE(msg, OP_HEALTH_FAULT_TEST, 3); - BT_DBG("HealthFaultTest, CID 0x%04x TestID 0x%04x NeedAck %u", cid, test_id, need_ack); + BT_DBG("HealthFaultTest, CID 0x%04x TestID 0x%02x NeedAck %u", cid, test_id, need_ack); bt_mesh_model_msg_init(&msg, need_ack ? OP_HEALTH_FAULT_TEST : OP_HEALTH_FAULT_TEST_UNREL); net_buf_simple_add_u8(&msg, test_id); diff --git a/components/bt/esp_ble_mesh/core/health_srv.c b/components/bt/esp_ble_mesh/core/health_srv.c index f7db34babb3..c5e849ce051 100644 --- a/components/bt/esp_ble_mesh/core/health_srv.c +++ b/components/bt/esp_ble_mesh/core/health_srv.c @@ -2,7 +2,7 @@ /* * SPDX-FileCopyrightText: 2017 Intel Corporation - * SPDX-FileContributor: 2018-2021 Espressif Systems (Shanghai) CO LTD + * SPDX-FileContributor: 2018-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -418,6 +418,7 @@ static int health_pub_update(struct bt_mesh_model *model) int bt_mesh_fault_update(struct bt_mesh_elem *elem) { struct bt_mesh_model *model = NULL; + int err = 0; BT_DBG("FaultUpdate"); @@ -439,7 +440,10 @@ int bt_mesh_fault_update(struct bt_mesh_elem *elem) return 0; } - health_pub_update(model); + err = health_pub_update(model); + if (err) { + return err; + } return bt_mesh_model_publish(model); } @@ -449,10 +453,6 @@ static void attention_off(struct k_work *work) struct bt_mesh_health_srv *srv = CONTAINER_OF(work, struct bt_mesh_health_srv, attn_timer.work); - if (!srv) { - BT_ERR("No Health Server context provided"); - return; - } BT_DBG("AttentionOff"); @@ -532,6 +532,7 @@ static int health_srv_deinit(struct bt_mesh_model *model) model->pub->update = NULL; k_delayed_work_free(&srv->attn_timer); + srv->attn_timer_start = false; if (bt_mesh_model_in_primary(model)) { health_srv = NULL; diff --git a/components/bt/esp_ble_mesh/core/heartbeat.c b/components/bt/esp_ble_mesh/core/heartbeat.c index 4324da32234..a2cb153c607 100644 --- a/components/bt/esp_ble_mesh/core/heartbeat.c +++ b/components/bt/esp_ble_mesh/core/heartbeat.c @@ -1,6 +1,6 @@ /* * SPDX-FileCopyrightText: 2017 Intel Corporation - * SPDX-FileContributor: 2018-2023 Espressif Systems (Shanghai) CO LTD + * SPDX-FileContributor: 2018-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -77,28 +77,50 @@ void bt_mesh_heartbeat_send(void) uint8_t init_ttl; uint16_t feat; } hb; - struct bt_mesh_msg_ctx ctx = { - .net_idx = cfg->hb_pub.net_idx, - .app_idx = BLE_MESH_KEY_UNUSED, - .addr = cfg->hb_pub.dst, - .send_ttl = cfg->hb_pub.ttl, - .send_cred = BLE_MESH_FLOODING_CRED, - }; - struct bt_mesh_net_tx tx = { - .sub = bt_mesh_subnet_get(cfg->hb_pub.net_idx), - .ctx = &ctx, - .src = bt_mesh_model_elem(cfg->model)->addr, - .xmit = bt_mesh_net_transmit_get(), - }; + struct bt_mesh_msg_ctx ctx = {0}; + struct bt_mesh_net_tx tx = {0}; + struct bt_mesh_elem *elem = NULL; uint16_t feat = 0U; - BT_DBG("HeartbeatSend, Dst 0x%04x", cfg->hb_pub.dst); + if (cfg == NULL) { + BT_WARN("No configuration server context available"); + return; + } + + if (cfg->model == NULL) { + BT_ERR("Configuration server model is NULL"); + return; + } /* Do nothing if heartbeat publication is not enabled */ if (cfg->hb_pub.dst == BLE_MESH_ADDR_UNASSIGNED) { return; } + elem = bt_mesh_model_elem(cfg->model); + if (elem == NULL) { + BT_ERR("Failed to get element for heartbeat"); + return; + } + + ctx.net_idx = cfg->hb_pub.net_idx; + ctx.app_idx = BLE_MESH_KEY_UNUSED; + ctx.addr = cfg->hb_pub.dst; + ctx.send_ttl = cfg->hb_pub.ttl; + ctx.send_cred = BLE_MESH_FLOODING_CRED; + + tx.sub = bt_mesh_subnet_get(cfg->hb_pub.net_idx); + if (tx.sub == NULL) { + BT_ERR("No subnet found for heartbeat publication (net_idx 0x%04x)", cfg->hb_pub.net_idx); + return; + } + + tx.ctx = &ctx; + tx.src = elem->addr; + tx.xmit = bt_mesh_net_transmit_get(); + + BT_DBG("HeartbeatSend, Dst 0x%04x", cfg->hb_pub.dst); + hb.init_ttl = cfg->hb_pub.ttl; if (bt_mesh_relay_get() == BLE_MESH_RELAY_ENABLED) { diff --git a/components/bt/esp_ble_mesh/core/local.h b/components/bt/esp_ble_mesh/core/local.h index 3172d58e170..67688e7a554 100644 --- a/components/bt/esp_ble_mesh/core/local.h +++ b/components/bt/esp_ble_mesh/core/local.h @@ -16,8 +16,8 @@ extern "C" { #endif -int bt_mesh_model_subscribe_group_addr(uint16_t elem_addr, uint16_t mod_id, - uint16_t cid, uint16_t group_addr); +int bt_mesh_model_subscribe_group_addr(uint16_t elem_addr, uint16_t cid, + uint16_t mod_id, uint16_t group_addr); int bt_mesh_model_unsubscribe_group_addr(uint16_t elem_addr, uint16_t cid, uint16_t mod_id, uint16_t group_addr); diff --git a/components/bt/esp_ble_mesh/core/lpn.c b/components/bt/esp_ble_mesh/core/lpn.c index 79d97887c74..fbb168c3709 100644 --- a/components/bt/esp_ble_mesh/core/lpn.c +++ b/components/bt/esp_ble_mesh/core/lpn.c @@ -283,7 +283,7 @@ static void clear_friendship(bool force, bool disable) */ lpn->groups_changed = 1U; - if (cfg->hb_pub.feat & BLE_MESH_FEAT_LOW_POWER) { + if (cfg && (cfg->hb_pub.feat & BLE_MESH_FEAT_LOW_POWER)) { bt_mesh_heartbeat_send(); } @@ -335,6 +335,11 @@ static const struct bt_mesh_send_cb friend_req_sent_cb = { static int send_friend_req(struct bt_mesh_lpn *lpn) { const struct bt_mesh_comp *comp = bt_mesh_comp_get(); + if (!comp) { + BT_ERR("Invalid composition data"); + return -EINVAL; + } + struct bt_mesh_msg_ctx ctx = { .net_idx = bt_mesh.sub[0].net_idx, .app_idx = BLE_MESH_KEY_UNUSED, @@ -710,6 +715,8 @@ static inline int group_popcount(bt_mesh_atomic_t *target) for (i = 0; i < ARRAY_SIZE(bt_mesh.lpn.added); i++) { count += popcount(bt_mesh_atomic_get(&target[i])); } + + return count; #else /* CONFIG_BLE_MESH_LPN_GROUPS > 32 */ return popcount(bt_mesh_atomic_get(target)); #endif /* CONFIG_BLE_MESH_LPN_GROUPS > 32 */ @@ -1086,7 +1093,7 @@ int bt_mesh_lpn_friend_update(struct bt_mesh_net_rx *rx, BT_INFO("Friendship established with 0x%04x", lpn->frnd); - if (cfg->hb_pub.feat & BLE_MESH_FEAT_LOW_POWER) { + if (cfg && (cfg->hb_pub.feat & BLE_MESH_FEAT_LOW_POWER)) { bt_mesh_heartbeat_send(); } @@ -1122,10 +1129,10 @@ int bt_mesh_lpn_friend_update(struct bt_mesh_net_rx *rx, bt_mesh_net_iv_update(iv_index, BLE_MESH_IV_UPDATE(msg->flags)); if (lpn->groups_changed) { - sub_update(TRANS_CTL_OP_FRIEND_SUB_ADD); - sub_update(TRANS_CTL_OP_FRIEND_SUB_REM); + bool sent = sub_update(TRANS_CTL_OP_FRIEND_SUB_ADD); + sent = (sent || sub_update(TRANS_CTL_OP_FRIEND_SUB_REM)); - if (!lpn->sent_req) { + if (!lpn->sent_req && !sent) { lpn->groups_changed = 0U; } } diff --git a/components/bt/esp_ble_mesh/core/net.c b/components/bt/esp_ble_mesh/core/net.c index b436ce5d6b4..919d248da9d 100644 --- a/components/bt/esp_ble_mesh/core/net.c +++ b/components/bt/esp_ble_mesh/core/net.c @@ -2,7 +2,7 @@ /* * SPDX-FileCopyrightText: 2017 Intel Corporation - * SPDX-FileContributor: 2018-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileContributor: 2018-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -641,13 +641,12 @@ bool bt_mesh_kr_update(struct bt_mesh_subnet *sub, uint8_t new_kr, bool new_key) /* Ignore */ break; } - /* Upon receiving a Secure Network beacon with the KR flag set - * to 0 using the new NetKey in Phase 1, the node shall - * immediately transition to Phase 3, which effectively skips - * Phase 2. - * - * Intentional fall-through. - */ + /* Upon receiving a Secure Network beacon with the KR flag set + * to 0 using the new NetKey in Phase 1, the node shall + * immediately transition to Phase 3, which effectively skips + * Phase 2. + */ + __attribute__((fallthrough)); case BLE_MESH_KR_PHASE_2: BT_INFO("KrPhase 0x%02x -> Normal", sub->kr_phase); diff --git a/components/bt/esp_ble_mesh/core/nimble_host/adapter.c b/components/bt/esp_ble_mesh/core/nimble_host/adapter.c index e2e22ad594d..9608762a417 100644 --- a/components/bt/esp_ble_mesh/core/nimble_host/adapter.c +++ b/components/bt/esp_ble_mesh/core/nimble_host/adapter.c @@ -1,7 +1,7 @@ /* * SPDX-FileCopyrightText: 2017 Nordic Semiconductor ASA * SPDX-FileCopyrightText: 2015-2016 Intel Corporation - * SPDX-FileContributor: 2018-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileContributor: 2018-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -88,7 +88,7 @@ static inline bool bt_mesh_is_ble_adv_running(); static bool g_host_init = false; -#if CONFIG_BLE_MESH_NODE && CONFIG_BLE_MESH_USE_BLE_50 +#if CONFIG_BLE_MESH_NODE static void bt_mesh_gatts_conn_init(void) { int i; @@ -118,7 +118,7 @@ static int bt_mesh_find_conn_idx(uint16_t conn_handle) } return -ENODEV; } -#endif /* CONFIG_BLE_MESH_NODE && CONFIG_BLE_MESH_USE_BLE_50 */ +#endif /* CONFIG_BLE_MESH_NODE */ int bt_mesh_host_init(void) { @@ -322,9 +322,10 @@ static int chr_disced(uint16_t conn_handle, const struct ble_gatt_error *error, uint16_t uuid16 = 0; int i = (int)arg; /* service index */ struct bt_mesh_conn *conn = &bt_mesh_gattc_info[i].conn; - const ble_uuid_any_t *uuid = &chr->uuid; + const ble_uuid_any_t *uuid = NULL; if (chr) { + uuid = &chr->uuid; uuid16 = (uint16_t) BLE_UUID16(uuid)->value; } @@ -698,10 +699,9 @@ report_to_user: bt_mesh_gattc_info[i].wr_desc_done = false; break; } - - if (i == ARRAY_SIZE(bt_mesh_gattc_info)) { - goto transfer_to_user; - } + } + if (i == ARRAY_SIZE(bt_mesh_gattc_info)) { + goto transfer_to_user; } } else { goto transfer_to_user; @@ -748,7 +748,10 @@ report_to_user: } conn = &bt_mesh_gattc_info[i].conn; - ble_gap_conn_find(event->notify_rx.conn_handle, &conn_desc); + if (ble_gap_conn_find(event->notify_rx.conn_handle, &conn_desc) != 0) { + BT_ERR("Failed to find connection for notify handler"); + return 0; + } if (bt_mesh_gattc_info[i].data_out_handle != event->notify_rx.attr_handle) { /* Data isn't populated yet */ @@ -829,6 +832,8 @@ void *bt_mesh_nimble_gap_cb_get(void) static int start_le_scan(uint8_t scan_type, uint16_t interval, uint16_t window, uint8_t filter_dup) { + int rc; + #if CONFIG_BLE_MESH_USE_BLE_50 uncoded_disc_params.itvl = (window ? interval : 0); uncoded_disc_params.window = window; @@ -839,9 +844,13 @@ static int start_le_scan(uint8_t scan_type, uint16_t interval, uint16_t window, coded_disc_params.passive = (scan_type == BLE_MESH_SCAN_PASSIVE); uncoded_disc_params.passive = (scan_type == BLE_MESH_SCAN_PASSIVE); - ble_gap_ext_disc(BLE_OWN_ADDR_PUBLIC, 0, 0, filter_dup, 0, 0, - uncoded_disc_params.itvl ? &uncoded_disc_params : NULL, - coded_disc_params.itvl ? &coded_disc_params : NULL, disc_cb, NULL); + rc = ble_gap_ext_disc(BLE_OWN_ADDR_PUBLIC, 0, 0, filter_dup, 0, 0, + uncoded_disc_params.itvl ? &uncoded_disc_params : NULL, + coded_disc_params.itvl ? &coded_disc_params : NULL, disc_cb, NULL); + if (rc != 0) { + BT_ERR("Failed to start extended discovery (err %d)", rc); + return rc; + } #else /* CONFIG_BLE_MESH_USE_BLE_50 */ scan_param.filter_duplicates = filter_dup; scan_param.itvl = interval; @@ -852,7 +861,11 @@ static int start_le_scan(uint8_t scan_type, uint16_t interval, uint16_t window, } else { scan_param.passive = 0; } - ble_gap_disc(BLE_OWN_ADDR_PUBLIC, BLE_HS_FOREVER, &scan_param, disc_cb, NULL); + rc = ble_gap_disc(BLE_OWN_ADDR_PUBLIC, BLE_HS_FOREVER, &scan_param, disc_cb, NULL); + if (rc != 0) { + BT_ERR("Failed to start discovery (err %d)", rc); + return rc; + } #endif /* CONFIG_BLE_MESH_USE_BLE_50 */ #if BLE_MESH_DEV @@ -880,10 +893,14 @@ static int gap_event_cb(struct ble_gap_event *event, void *arg) MODLOG_DFLT(INFO, "connection %s; status=%d ", event->connect.status == 0 ? "established" : "failed", event->connect.status); - if (event->connect.status == 0) { - rc = ble_gap_conn_find(event->connect.conn_handle, &desc); - assert(rc == 0); + + if (event->connect.status != 0) { + return 0; } + + rc = ble_gap_conn_find(event->connect.conn_handle, &desc); + assert(rc == 0); + MODLOG_DFLT(INFO, "\n"); #if BLE_MESH_DEV /* When connection is created, advertising will be stopped automatically. */ @@ -904,9 +921,7 @@ static int gap_event_cb(struct ble_gap_event *event, void *arg) #endif if (bt_mesh_gatts_conn_cb != NULL && bt_mesh_gatts_conn_cb->connected != NULL) { - int index = 0; -#if CONFIG_BLE_MESH_USE_BLE_50 - index = bt_mesh_find_free_conn_idx(); + int index = bt_mesh_find_free_conn_idx(); if (index != -ENOMEM) { bt_mesh_gatts_conn[index].handle = BLE_MESH_GATT_GET_CONN_ID(event->connect.conn_handle); (bt_mesh_gatts_conn_cb->connected)(&bt_mesh_gatts_conn[index], 0); @@ -914,13 +929,6 @@ static int gap_event_cb(struct ble_gap_event *event, void *arg) BT_ERR("No space for new connection"); ble_gap_terminate(event->connect.conn_handle, BLE_ERR_CONN_LIMIT); } -#else /* CONFIG_BLE_MESH_USE_BLE_50 */ - index = BLE_MESH_GATT_GET_CONN_ID(event->connect.conn_handle); - if (index < BLE_MESH_MAX_CONN) { - bt_mesh_gatts_conn[index].handle = BLE_MESH_GATT_GET_CONN_ID(event->connect.conn_handle); - (bt_mesh_gatts_conn_cb->connected)(&bt_mesh_gatts_conn[index], 0); - } -#endif /* CONFIG_BLE_MESH_USE_BLE_50 */ memcpy(bt_mesh_gatts_addr, desc.peer_id_addr.val, BLE_MESH_ADDR_LEN); /* This is for EspBleMesh Android app. When it tries to connect with the * device at the first time and it fails due to some reason. And after @@ -940,22 +948,13 @@ static int gap_event_cb(struct ble_gap_event *event, void *arg) bt_mesh_atomic_test_and_clear_bit(bt_mesh_dev.flags, BLE_MESH_DEV_ADVERTISING); #endif if (bt_mesh_gatts_conn_cb != NULL && bt_mesh_gatts_conn_cb->disconnected != NULL) { - int index = 0; -#if CONFIG_BLE_MESH_USE_BLE_50 - index = bt_mesh_find_conn_idx(BLE_MESH_GATT_GET_CONN_ID(event->disconnect.conn.conn_handle)); + int index = bt_mesh_find_conn_idx(BLE_MESH_GATT_GET_CONN_ID(event->disconnect.conn.conn_handle)); if (index != -ENODEV) { bt_mesh_gatts_conn[index].handle = BLE_MESH_GATT_GET_CONN_ID(event->disconnect.conn.conn_handle); (bt_mesh_gatts_conn_cb->disconnected)(&bt_mesh_gatts_conn[index], event->disconnect.reason); } else { BT_ERR("No device"); } -#else /* CONFIG_BLE_MESH_USE_BLE_50 */ - index = BLE_MESH_GATT_GET_CONN_ID(event->disconnect.conn.conn_handle); - if (index < BLE_MESH_MAX_CONN) { - bt_mesh_gatts_conn[index].handle = BLE_MESH_GATT_GET_CONN_ID(event->disconnect.conn.conn_handle); - (bt_mesh_gatts_conn_cb->disconnected)(&bt_mesh_gatts_conn[index], event->disconnect.reason); - } -#endif /* CONFIG_BLE_MESH_USE_BLE_50 */ bt_mesh_gatts_conn[index].handle = BT_MESH_GATTS_CONN_UNUSED; memset(bt_mesh_gatts_addr, 0x0, BLE_MESH_ADDR_LEN); } @@ -1035,19 +1034,11 @@ static int gap_event_cb(struct ble_gap_event *event, void *arg) uint16_t len = 0; uint16_t ccc_val = 0; -#if CONFIG_BLE_MESH_USE_BLE_50 index = bt_mesh_find_conn_idx(BLE_MESH_GATT_GET_CONN_ID(event->subscribe.conn_handle)); if (index == -ENODEV) { BT_ERR("Couldn't find conn %d", event->subscribe.conn_handle); return 0; } -#else /* CONFIG_BLE_MESH_USE_BLE_50 */ - index = BLE_MESH_GATT_GET_CONN_ID(event->subscribe.conn_handle); - if (index >= BLE_MESH_MAX_CONN) { - BT_ERR("InvConnIdx[%d]", index); - return 0; - } -#endif /* CONFIG_BLE_MESH_USE_BLE_50 */ if (event->subscribe.prev_notify != event->subscribe.cur_notify) { ccc_val = event->subscribe.cur_notify; @@ -1224,6 +1215,7 @@ int bt_le_ext_adv_start(const uint8_t inst_id, err = os_mbuf_append(data, buf, buf_len); if (err) { bt_mesh_free(buf); + os_mbuf_free_chain(data); BT_ERR("Append ad data to os buf failed %d", err); return -EINVAL; } @@ -1236,19 +1228,22 @@ int bt_le_ext_adv_start(const uint8_t inst_id, buf = bt_mesh_calloc(sd_len * BLE_HS_ADV_MAX_SZ); if (!buf) { BT_ERR("ad buffer alloc failed"); + os_mbuf_free_chain(data); return -ENOMEM; } err = set_ad(sd, sd_len, buf, &buf_len); if (err) { bt_mesh_free(buf); + os_mbuf_free_chain(data); BT_ERR("SetScanRspDataFail[%d]", err); return err; } scan_rsp = os_msys_get_pkthdr(buf_len, 0); - if (!data) { + if (!scan_rsp) { bt_mesh_free(buf); + os_mbuf_free_chain(data); BT_ERR("os buf get failed"); return -ENOBUFS; } @@ -1256,6 +1251,8 @@ int bt_le_ext_adv_start(const uint8_t inst_id, err = os_mbuf_append(scan_rsp, buf, buf_len); if (err) { bt_mesh_free(buf); + os_mbuf_free_chain(data); + os_mbuf_free_chain(scan_rsp); BT_ERR("Append ad data to os buf failed %d", err); return -EINVAL; } @@ -1315,6 +1312,10 @@ int bt_le_ext_adv_start(const uint8_t inst_id, err = ble_gap_ext_adv_remove(inst_id); if (err != 0 && err != BLE_HS_EALREADY) { BT_ERR("Advertising rm failed: err %d", err); + os_mbuf_free_chain(data); + if (scan_rsp) { + os_mbuf_free_chain(scan_rsp); + } return err; } } @@ -1322,6 +1323,10 @@ int bt_le_ext_adv_start(const uint8_t inst_id, err = ble_gap_ext_adv_configure(inst_id, &adv_params, NULL, gap_event_cb, NULL); if (err != 0) { BT_ERR("Advertising config failed: err %d", err); + os_mbuf_free_chain(data); + if (scan_rsp) { + os_mbuf_free_chain(scan_rsp); + } return err; } @@ -1331,6 +1336,9 @@ int bt_le_ext_adv_start(const uint8_t inst_id, err = ble_gap_ext_adv_set_data(inst_id, data); if (err != 0) { BT_ERR("Advertising set failed: err %d", err); + if (scan_rsp) { + os_mbuf_free_chain(scan_rsp); + } return err; } @@ -1604,6 +1612,7 @@ int bt_mesh_ble_ext_adv_start(const uint8_t inst_id, if (os_mbuf_append(data, adv_data->adv_data, adv_data->adv_data_len)) { BT_ERR("Append data failed"); + os_mbuf_free_chain(data); return -EINVAL; } @@ -1622,6 +1631,7 @@ int bt_mesh_ble_ext_adv_start(const uint8_t inst_id, if (os_mbuf_append(data, adv_data->scan_rsp_data, adv_data->scan_rsp_data_len)) { BT_ERR("Append data failed"); + os_mbuf_free_chain(data); return -EINVAL; } err = ble_gap_ext_adv_rsp_set_data(inst_id, data); @@ -2420,23 +2430,25 @@ static int proxy_char_access_cb(uint16_t conn_handle, uint16_t attr_handle, if (ctxt->op == BLE_GATT_ACCESS_OP_WRITE_CHR || ctxt->op == BLE_GATT_ACCESS_OP_WRITE_DSC) { struct bt_mesh_gatt_attr *attr = bt_mesh_gatts_find_attr_by_handle(attr_handle); int index = 0; - uint16_t len = 0; + ssize_t len = 0; -#if CONFIG_BLE_MESH_USE_BLE_50 - index = bt_mesh_find_conn_idx(BLE_MESH_GATT_GET_CONN_ID(conn_handle)); + index = bt_mesh_find_conn_idx(BLE_MESH_GATT_GET_CONN_ID(conn_handle)); if (index == -ENODEV) { BT_ERR("Unknown conn handle"); return 0; } -#else - index = BLE_MESH_GATT_GET_CONN_ID(conn_handle); -#endif BT_DBG("write, handle %d, len %d, data %s", attr_handle, ctxt->om->om_len, bt_hex(ctxt->om->om_data, ctxt->om->om_len)); if (attr != NULL && attr->write != NULL) { + if (OS_MBUF_IS_PKTHDR(ctxt->om) && OS_MBUF_PKTLEN(ctxt->om) > ctxt->om->om_len) { + /* Handle fragmented mbuf chain: either linearize or return error */ + BT_ERR("Fragmented mbuf not supported"); + return BLE_ATT_ERR_UNLIKELY; + } + if ((len = attr->write(&bt_mesh_gatts_conn[index], attr, ctxt->om->om_data, ctxt->om->om_len, @@ -2554,9 +2566,7 @@ void bt_mesh_gatt_init(void) ble_gatts_svc_set_visibility(prov_svc_start_handle, 1); ble_gatts_svc_set_visibility(proxy_svc_start_handle, 0); -#if CONFIG_BLE_MESH_USE_BLE_50 bt_mesh_gatts_conn_init(); -#endif /* CONFIG_BLE_MESH_USE_BLE_50 */ init = true; } #endif /* CONFIG_BLE_MESH_NODE */ @@ -2596,6 +2606,7 @@ void bt_mesh_gatt_init(void) ble_gatts_svc_set_visibility(prov_svc_start_handle, 1); ble_gatts_svc_set_visibility(proxy_svc_start_handle, 0); + bt_mesh_gatts_conn_init(); init = true; } #endif /* CONFIG_BLE_MESH_NODE */ diff --git a/components/bt/esp_ble_mesh/core/prov_common.c b/components/bt/esp_ble_mesh/core/prov_common.c index 8f14b67efa5..58bbf7bce5c 100644 --- a/components/bt/esp_ble_mesh/core/prov_common.c +++ b/components/bt/esp_ble_mesh/core/prov_common.c @@ -2,7 +2,7 @@ /* * SPDX-FileCopyrightText: 2017 Intel Corporation - * SPDX-FileContributor: 2018-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileContributor: 2018-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -103,6 +103,14 @@ static const struct { bool bt_mesh_prov_pdu_check(uint8_t type, uint16_t length, uint8_t *reason) { + if (type >= ARRAY_SIZE(prov_pdu)) { + BT_ERR("Invalid PDU type 0x%02x", type); + if (reason) { + *reason = PROV_ERR_NVAL_PDU; + } + return false; + } + if (prov_pdu[type].length != length) { #if CONFIG_BLE_MESH_CERT_BASED_PROV if ((type == PROV_REC_LIST || type == PROV_REC_RSP) && @@ -224,12 +232,21 @@ bool bt_mesh_gen_prov_start(struct bt_mesh_prov_link *link, return false; } - if (START_LAST_SEG(rx->gpc) > 0 && link->rx.buf->len <= 20) { - BT_ERR("Too small total length for multi-segment PDU"); - if (close) { - *close = true; + if (START_LAST_SEG(rx->gpc) > 0) { + /* For multi-segment PDUs, validate that total length is consistent + * with the claimed segment count to prevent underflow in + * bt_mesh_gen_prov_cont() when computing expect_len. + * Minimum length = first segment (20) + (last_seg - 1) * full continuation (23) + 1 + */ + uint16_t min_len = 20 + 23 * (START_LAST_SEG(rx->gpc) - 1) + 1; + if (link->rx.buf->len < min_len) { + BT_ERR("Total length %u too small for %u segments (min %u)", + link->rx.buf->len, START_LAST_SEG(rx->gpc) + 1, min_len); + if (close) { + *close = true; + } + return false; } - return false; } link->rx.seg = (1 << (START_LAST_SEG(rx->gpc) + 1)) - 1; @@ -375,7 +392,7 @@ static void free_segments(struct bt_mesh_prov_link *link) struct net_buf *buf = link->tx.buf[i]; if (!buf) { - break; + continue; } link->tx.buf[i] = NULL; @@ -536,6 +553,8 @@ static void send_reliable(struct bt_mesh_prov_link *link, uint8_t xmit) { link->tx.start = k_uptime_get(); + bt_mesh_mutex_lock(&link->buf_lock); + for (size_t i = 0; i < ARRAY_SIZE(link->tx.buf); i++) { struct net_buf *buf = link->tx.buf[i]; @@ -549,6 +568,8 @@ static void send_reliable(struct bt_mesh_prov_link *link, uint8_t xmit) bt_mesh_adv_send(buf, xmit, &buf_sent_cb, link); } } + + bt_mesh_mutex_unlock(&link->buf_lock); } int bt_mesh_prov_bearer_ctl_send(struct bt_mesh_prov_link *link, uint8_t op, @@ -737,6 +758,7 @@ int bt_mesh_prov_send(struct bt_mesh_prov_link *link, struct net_buf_simple *buf return bt_mesh_prov_send_adv(link, buf); #endif /* CONFIG_BLE_MESH_PB_ADV */ - /* Shall not reach here. */ - return 0; + /* Shall not reach here - no provisioning bearer is enabled */ + BT_ERR("No provisioning bearer available"); + return -ENOTSUP; } diff --git a/components/bt/esp_ble_mesh/core/prov_node.c b/components/bt/esp_ble_mesh/core/prov_node.c index 721f8feb729..67d0a2e8078 100644 --- a/components/bt/esp_ble_mesh/core/prov_node.c +++ b/components/bt/esp_ble_mesh/core/prov_node.c @@ -124,7 +124,7 @@ static void reset_adv_link(struct bt_mesh_prov_link *link, uint8_t reason) { ARG_UNUSED(link); - BT_INFO("ResetAdvLink:%08x", link->link_id); + BT_INFO("ResetAdvLink:%08x", prov_link.link_id); bt_mesh_prov_clear_tx(&prov_link, true); if (bt_mesh_prov_get()->link_close) { @@ -285,6 +285,11 @@ static int prov_auth(uint8_t method, uint8_t action, uint8_t size) return -EINVAL; } + if (bt_mesh_prov_get()->static_val == NULL) { + BT_ERR("Static OOB value not set"); + return -EINVAL; + } + if (bt_mesh_prov_get()->static_val_len > auth_size) { memcpy(prov_link.auth, bt_mesh_prov_get()->static_val, auth_size); } else { @@ -306,7 +311,7 @@ static int prov_auth(uint8_t method, uint8_t action, uint8_t size) return -EINVAL; } - if (size > bt_mesh_prov_get()->output_size) { + if (size == 0 || size > bt_mesh_prov_get()->output_size) { return -EINVAL; } @@ -599,7 +604,7 @@ int bt_mesh_input_string(const char *str) } (void)memset(prov_link.auth, 0, sizeof(prov_link.auth)); - (void)memcpy(prov_link.auth, str, bt_mesh_prov_get()->input_size); + (void)memcpy(prov_link.auth, str, MIN(strlen(str), bt_mesh_prov_get()->input_size)); send_input_complete(); @@ -631,11 +636,13 @@ static void send_pub_key(void) if (bt_mesh_dh_key_gen(buf.data, dhkey)) { BT_ERR("Unable to generate DHKey"); + (void)memset(dhkey, 0, sizeof(dhkey)); close_link(PROV_ERR_UNEXP_ERR); return; } memcpy(prov_link.dhkey, dhkey, 32); + (void)memset(dhkey, 0, sizeof(dhkey)); BT_DBG("DHkey: %s", bt_hex(prov_link.dhkey, 32)); @@ -643,6 +650,7 @@ static void send_pub_key(void) if (bt_mesh_pub_key_copy(pub_key)) { BT_ERR("No public key available"); + (void)memset(pub_key, 0, sizeof(pub_key)); close_link(PROV_ERR_UNEXP_ERR); return; } @@ -654,6 +662,7 @@ static void send_pub_key(void) /* Public key is already in big-endian format from bt_mesh_pub_key_copy() */ memcpy(net_buf_simple_add(&buf, 32), pub_key, 32); memcpy(net_buf_simple_add(&buf, 32), &pub_key[32], 32); + (void)memset(pub_key, 0, sizeof(pub_key)); memcpy(&prov_link.conf_inputs[81], &buf.data[1], 64); @@ -1187,7 +1196,7 @@ static void prov_msg_recv(void) uint8_t type = 0; if (bt_mesh_atomic_test_bit(prov_link.flags, LINK_INVALID)) { - BT_WARN("Unexpected msg 0x%02x on invalidated link", type); + BT_WARN("Unexpected msg on invalidated link"); close_link(PROV_ERR_UNEXP_PDU); return; } @@ -1196,7 +1205,7 @@ static void prov_msg_recv(void) * should be ignored. */ if (bt_mesh_atomic_test_bit(prov_link.flags, LINK_CLOSING)) { - BT_WARN("Link is closing, unexpected msg 0x%02x", type); + BT_WARN("Link is closing, unexpected msg received"); return; } diff --git a/components/bt/esp_ble_mesh/core/prov_pvnr.c b/components/bt/esp_ble_mesh/core/prov_pvnr.c index 88ce691464e..a1dde3b5002 100644 --- a/components/bt/esp_ble_mesh/core/prov_pvnr.c +++ b/components/bt/esp_ble_mesh/core/prov_pvnr.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2017-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2017-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -655,7 +655,7 @@ start: #endif /* CONFIG_BLE_MESH_PB_GATT */ /* Shall not reach here. */ - return 0; + return -EINVAL; } int bt_mesh_provisioner_prov_device_with_addr(const uint8_t uuid[16], const uint8_t addr[6], @@ -754,7 +754,7 @@ int bt_mesh_provisioner_prov_device_with_addr(const uint8_t uuid[16], const uint #endif /* CONFIG_BLE_MESH_PB_GATT */ /* Shall not reach here. */ - return 0; + return -EINVAL; } int bt_mesh_provisioner_delete_device(struct bt_mesh_device_delete *del_dev) @@ -2487,7 +2487,7 @@ static void prov_msg_recv(struct bt_mesh_prov_link *link) * should be ignored. */ if (bt_mesh_atomic_test_bit(link->flags, LINK_CLOSING)) { - BT_WARN("Link is closing, unexpected msg 0x%02x", type); + BT_WARN("Link is closing, ignoring received PDU"); return; } @@ -3165,7 +3165,7 @@ int bt_mesh_rpr_cli_pdu_recv(struct bt_mesh_prov_link *link, uint8_t type, return -EINVAL; } - if (type != link->expect) { + if (type != PROV_FAILED && type != link->expect) { BT_ERR("PB-Remote, unexpected msg 0x%02x != 0x%02x", type, link->expect); return -EINVAL; } @@ -3188,7 +3188,8 @@ int bt_mesh_rpr_cli_pdu_send(struct bt_mesh_prov_link *link, uint8_t type) send_confirm(link); break; default: - break; + BT_WARN("Unsupported RPR CLI PDU type 0x%02x", type); + return -EINVAL; } return 0; diff --git a/components/bt/esp_ble_mesh/core/proxy_client.c b/components/bt/esp_ble_mesh/core/proxy_client.c index 125a902ebe1..9d2400b890e 100644 --- a/components/bt/esp_ble_mesh/core/proxy_client.c +++ b/components/bt/esp_ble_mesh/core/proxy_client.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2017-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2017-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -80,7 +80,7 @@ static void proxy_sar_timeout(struct k_work *work) BT_WARN("ProxySARTimeout"); server = CONTAINER_OF(work, struct bt_mesh_proxy_server, sar_timer.work); - if (!server || !server->conn) { + if (!server->conn) { BT_ERR("InvalidProxyServerParam"); return; } @@ -429,6 +429,10 @@ int bt_mesh_proxy_client_segment_send(struct bt_mesh_conn *conn, uint8_t type, net_buf_simple_push_u8(msg, BLE_MESH_PROXY_PDU_HDR(BLE_MESH_PROXY_SAR_FIRST, type)); err = proxy_send(conn, msg->data, mtu); + /* Note: + * Even if proxy_send() failed, do not return early here in order to + * keep the msg in a consistent final state. + */ net_buf_simple_pull(msg, mtu); while (msg->len) { @@ -440,6 +444,10 @@ int bt_mesh_proxy_client_segment_send(struct bt_mesh_conn *conn, uint8_t type, net_buf_simple_push_u8(msg, BLE_MESH_PROXY_PDU_HDR(BLE_MESH_PROXY_SAR_CONT, type)); err = proxy_send(conn, msg->data, mtu); + /* Note: + * Even if proxy_send() failed, do not return early here in order to + * keep the msg in a consistent final state. + */ net_buf_simple_pull(msg, mtu); } @@ -449,10 +457,16 @@ int bt_mesh_proxy_client_segment_send(struct bt_mesh_conn *conn, uint8_t type, int bt_mesh_proxy_client_send(struct bt_mesh_conn *conn, uint8_t type, struct net_buf_simple *msg) { - struct bt_mesh_proxy_server *server = find_server(conn); + struct bt_mesh_proxy_server *server = NULL; + + if (conn == NULL) { + BT_ERR("%s, Invalid parameter", __func__); + return -EINVAL; + } BT_DBG("ProxyClientSend, ConnHandle 0x%04x Type %u", conn->handle, type); + server = find_server(conn); if (!server) { BT_ERR("No Proxy Server object found"); return -ENOTCONN; @@ -645,8 +659,8 @@ int bt_mesh_proxy_client_prov_disable(void) struct bt_mesh_proxy_server *server = &servers[i]; if (server->conn && server->conn_type == CLI_PROV) { - bt_mesh_gattc_disconnect(server->conn); server->conn_type = CLI_NONE; + bt_mesh_gattc_disconnect(server->conn); } } @@ -728,7 +742,7 @@ int bt_mesh_proxy_client_gatt_enable(void) BT_DBG("ProxyClientGattEnable"); for (i = 0; i < ARRAY_SIZE(servers); i++) { - if (servers[i].conn) { + if (servers[i].conn && servers[i].conn_type == CLI_NONE) { servers[i].conn_type = CLI_PROXY; } } @@ -758,8 +772,8 @@ int bt_mesh_proxy_client_gatt_disable(void) struct bt_mesh_proxy_server *server = &servers[i]; if (server->conn && server->conn_type == CLI_PROXY) { - bt_mesh_gattc_disconnect(server->conn); server->conn_type = CLI_NONE; + bt_mesh_gattc_disconnect(server->conn); } } @@ -1127,13 +1141,13 @@ static int send_proxy_cfg(struct bt_mesh_conn *conn, uint16_t net_idx, struct bt case BLE_MESH_PROXY_CFG_FILTER_ADD: for (uint16_t i = 0U; i < cfg->add.addr_num; i++) { - net_buf_simple_add_le16(buf, cfg->add.addr[i]); + net_buf_simple_add_be16(buf, cfg->add.addr[i]); } break; case BLE_MESH_PROXY_CFG_FILTER_REMOVE: for (uint16_t i = 0U; i < cfg->remove.addr_num; i++) { - net_buf_simple_add_le16(buf, cfg->remove.addr[i]); + net_buf_simple_add_be16(buf, cfg->remove.addr[i]); } break; diff --git a/components/bt/esp_ble_mesh/core/proxy_server.c b/components/bt/esp_ble_mesh/core/proxy_server.c index 2dcc54f58d8..33f2169a04a 100644 --- a/components/bt/esp_ble_mesh/core/proxy_server.c +++ b/components/bt/esp_ble_mesh/core/proxy_server.c @@ -2,7 +2,7 @@ /* * SPDX-FileCopyrightText: 2017 Intel Corporation - * SPDX-FileContributor: 2018-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileContributor: 2018-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -182,7 +182,7 @@ static void proxy_sar_timeout(struct k_work *work) BT_WARN("ProxySARTimeout"); client = CONTAINER_OF(work, struct bt_mesh_proxy_client, sar_timer.work); - if (!client || !client->conn) { + if (!client->conn) { BT_ERR("Invalid proxy client parameter"); return; } @@ -312,15 +312,12 @@ static void filter_add(struct bt_mesh_proxy_client *client, for (i = 0; i < ARRAY_SIZE(client->filter); i++) { if (client->filter[i].addr == addr) { + BT_INFO("client addr 0x%04x already added", addr); return; } } for (i = 0; i < ARRAY_SIZE(client->filter); i++) { - if (client->filter[i].addr == addr) { - BT_INFO("client addr 0x%04x already added", addr); - return; - } if (client->filter[i].addr == BLE_MESH_ADDR_UNASSIGNED) { BT_INFO("Add client or filter addr 0x%04x", addr); client->filter[i].addr = addr; @@ -514,7 +511,7 @@ static void proxy_send_beacons(struct k_work *work) { struct bt_mesh_proxy_client *client = CONTAINER_OF(work, struct bt_mesh_proxy_client, - send_beacons);; + send_beacons); int i; BT_DBG("ProxySendBeacons"); @@ -1484,10 +1481,10 @@ int bt_mesh_proxy_server_segment_send(struct bt_mesh_conn *conn, uint8_t type, net_buf_simple_push_u8(msg, BLE_MESH_PROXY_PDU_HDR(BLE_MESH_PROXY_SAR_FIRST, type)); err = proxy_send(conn, msg->data, mtu); - if (err) { - BT_ERR("ProxyServerSendFail %d", err); - return err; - } + /* Note: + * Even if proxy_send() failed, do not return early here in order to + * keep the msg in a consistent final state. + */ net_buf_simple_pull(msg, mtu); while (msg->len) { @@ -1498,14 +1495,14 @@ int bt_mesh_proxy_server_segment_send(struct bt_mesh_conn *conn, uint8_t type, net_buf_simple_push_u8(msg, BLE_MESH_PROXY_PDU_HDR(BLE_MESH_PROXY_SAR_CONT, type)); err = proxy_send(conn, msg->data, mtu); - if (err) { - BT_ERR("ProxyServerSendFail %d", err); - return err; - } + /* Note: + * Even if proxy_send() failed, do not return early here in order to + * keep the msg in a consistent final state. + */ net_buf_simple_pull(msg, mtu); } - return 0; + return err; } int bt_mesh_proxy_server_send(struct bt_mesh_conn *conn, uint8_t type, @@ -2241,6 +2238,9 @@ int bt_mesh_proxy_server_deinit(void) k_delayed_work_free(&client->sar_timer); memset(client, 0, sizeof(struct bt_mesh_proxy_client)); +#if CONFIG_BLE_MESH_PROXY_PRIVACY + client->proxy_privacy = BLE_MESH_PROXY_PRIVACY_DISABLED; +#endif /* CONFIG_BLE_MESH_PROXY_PRIVACY */ } #if CONFIG_BLE_MESH_GATT_PROXY_SERVER && CONFIG_BLE_MESH_PRB_SRV diff --git a/components/bt/esp_ble_mesh/core/pvnr_mgmt.c b/components/bt/esp_ble_mesh/core/pvnr_mgmt.c index 8fc5915d31e..1c2270f88d0 100644 --- a/components/bt/esp_ble_mesh/core/pvnr_mgmt.c +++ b/components/bt/esp_ble_mesh/core/pvnr_mgmt.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2017-2021 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2017-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -378,11 +378,7 @@ static int provisioner_remove_node(uint16_t index, bool erase) /* Reset corresponding transport info when removing the node */ for (i = 0; i < node->element_num; i++) { bt_mesh_rx_reset_single(node->unicast_addr + i); - } - for (i = 0; i < node->element_num; i++) { bt_mesh_tx_reset_single(node->unicast_addr + i); - } - for (i = 0; i < node->element_num; i++) { bt_mesh_rpl_reset_single(node->unicast_addr + i, erase); } @@ -544,6 +540,11 @@ int bt_mesh_provisioner_delete_node_with_dev_addr(const bt_mesh_addr_t *addr) { int i; + if (addr == NULL) { + BT_ERR("Invalid device address"); + return -EINVAL; + } + bt_mesh_provisioner_lock(); for (i = 0; i < ARRAY_SIZE(mesh_nodes); i++) { diff --git a/components/bt/esp_ble_mesh/core/rpl.c b/components/bt/esp_ble_mesh/core/rpl.c index 43f4a4afdaa..f2035b9cd1b 100644 --- a/components/bt/esp_ble_mesh/core/rpl.c +++ b/components/bt/esp_ble_mesh/core/rpl.c @@ -36,7 +36,7 @@ void bt_mesh_update_rpl(struct bt_mesh_rpl *rpl, struct bt_mesh_net_rx *rx) */ static bool rpl_check_and_store(struct bt_mesh_net_rx *rx, struct bt_mesh_rpl **match) { - BT_DBG("%s, Src 0x%04x Seq %lu OldIV %u", + BT_DBG("%s, Src 0x%04x Seq 0x%06x OldIV %u", match ? "RPLOnlyCheck" : "RPLCheckAndStore", rx->ctx.addr, rx->seq, rx->old_iv); diff --git a/components/bt/esp_ble_mesh/core/scan.c b/components/bt/esp_ble_mesh/core/scan.c index 96b77e8a7f2..739f94200df 100644 --- a/components/bt/esp_ble_mesh/core/scan.c +++ b/components/bt/esp_ble_mesh/core/scan.c @@ -137,8 +137,12 @@ int bt_mesh_unprov_dev_info_query(uint8_t uuid[16], uint8_t addr[6], return 0; } - memcpy(addr, unprov_dev_info_fifo.info[idx].addr, 6); - *adv_type = unprov_dev_info_fifo.info[idx].adv_type; + if (addr) { + memcpy(addr, unprov_dev_info_fifo.info[idx].addr, 6); + } + if (adv_type) { + *adv_type = unprov_dev_info_fifo.info[idx].adv_type; + } break; } } @@ -324,19 +328,19 @@ static void handle_adv_service_data(struct net_buf_simple *buf, #if CONFIG_BLE_MESH_RPR_SRV if (bt_mesh_is_provisioned()) { - const bt_mesh_addr_t *addr = NULL; + const bt_mesh_addr_t *unprov_addr = NULL; if (buf->len != PROV_SVC_DATA_LEN) { BT_WARN("Invalid Mesh Prov Service Data length %d", buf->len); return; } - addr = bt_mesh_get_unprov_dev_addr(); - assert(addr); + unprov_addr = bt_mesh_get_unprov_dev_addr(); + assert(unprov_addr); - bt_mesh_unprov_dev_fifo_enqueue(buf->data, addr->val, bt_mesh_get_adv_type()); + bt_mesh_unprov_dev_fifo_enqueue(buf->data, unprov_addr->val, bt_mesh_get_adv_type()); - bt_mesh_rpr_srv_unprov_beacon_recv(buf, bt_mesh_get_adv_type(), addr, rssi); + bt_mesh_rpr_srv_unprov_beacon_recv(buf, bt_mesh_get_adv_type(), unprov_addr, rssi); } #endif /* CONFIG_BLE_MESH_RPR_SRV */ @@ -391,6 +395,14 @@ static bool ble_scan_en; int bt_mesh_start_ble_scan(struct bt_mesh_ble_scan_param *param) { BT_DBG("StartBLEScan"); + ARG_UNUSED(param); + + /* Note: + * Currently the function is only used to enable reporting + * non-mesh advertising packets to the application layer, + * and the input parameter will not be used for now. + */ + ARG_UNUSED(param); if (ble_scan_en == true) { BT_WARN("%s, Already", __func__); @@ -423,7 +435,7 @@ bool bt_mesh_ble_scan_state_get(void) return ble_scan_en; } -static void inline callback_ble_adv_pkt(const bt_mesh_addr_t *addr, +static inline void callback_ble_adv_pkt(const bt_mesh_addr_t *addr, uint8_t adv_type, uint8_t data[], uint16_t length, int8_t rssi) { @@ -570,6 +582,7 @@ static void bt_mesh_scan_cb(struct bt_mesh_adv_report *adv_rpt) #endif )) { BT_DBG("IgnorePkt, Type 0x%02x AdvType 0x%02x", type, adv_rpt->adv_type); + net_buf_simple_restore(buf, &buf_state); return; } @@ -724,18 +737,18 @@ int bt_mesh_scan_param_update(struct bt_mesh_scan_param *param) BT_DBG("ScanParamUpdate, Type %u Interval %u Window %u", param->type, param->interval, param->window); + err = bt_le_scan_stop(); + if (err && err != -EALREADY) { + BT_ERR("StopScanFailed, Err %d", err); + return err; + } + scan_param.interval = param->interval; scan_param.window = param->window; - err = bt_le_scan_stop(); - if (err) { - if (err == -EALREADY) { - BT_INFO("New scan parameters will take effect after scan starts"); - return 0; - } - - BT_ERR("StopScanFailed, Err %d", err); - return err; + if (err == -EALREADY) { + BT_INFO("New scan parameters will take effect after scan starts"); + return 0; } /* Since the user only needs to set the scan interval and scan window, diff --git a/components/bt/esp_ble_mesh/core/storage/settings.c b/components/bt/esp_ble_mesh/core/storage/settings.c index 8d783893d61..e2a935ac4ee 100644 --- a/components/bt/esp_ble_mesh/core/storage/settings.c +++ b/components/bt/esp_ble_mesh/core/storage/settings.c @@ -1,6 +1,6 @@ /* * SPDX-FileCopyrightText: 2018 Intel Corporation - * SPDX-FileContributor: 2018-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileContributor: 2018-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -244,7 +244,7 @@ static int net_set(const char *name) BT_ERR("Failed to load node net info"); memset(bt_mesh.dev_key, 0, sizeof(bt_mesh.dev_key)); bt_mesh_comp_unprovision(); - return 0; + return err; } if (exist == false) { @@ -2353,6 +2353,7 @@ static struct key_update *key_update_find(bool app_key, uint16_t key_idx, if (update->key_idx == key_idx) { match = update; + break; } } @@ -2949,12 +2950,12 @@ int bt_mesh_model_data_store(const struct bt_mesh_model *mod, bool vnd, char path[30] = {'\0'}; uint16_t model_key = 0U; + int len = 0; model_key = BLE_MESH_GET_MODEL_KEY(mod->elem_idx, mod->model_idx); - sprintf(path, "mesh/%s/%04x/d", vnd ? "v" : "s", model_key); - if (name) { - strcat(path, "/"); - strncat(path, name, SETTINGS_MAX_DIR_DEPTH); + len = snprintf(path, sizeof(path), "mesh/%s/%04x/d", vnd ? "v" : "s", model_key); + if (name && len > 0 && len < sizeof(path)) { + snprintf(path + len, sizeof(path) - len, "/%.*s", SETTINGS_MAX_DIR_DEPTH, name); } if (data_len) { diff --git a/components/bt/esp_ble_mesh/core/storage/settings_nvs.c b/components/bt/esp_ble_mesh/core/storage/settings_nvs.c index 6ca00833379..df108d4e089 100644 --- a/components/bt/esp_ble_mesh/core/storage/settings_nvs.c +++ b/components/bt/esp_ble_mesh/core/storage/settings_nvs.c @@ -600,9 +600,9 @@ static int settings_remove_item(bt_mesh_nvs_handle_t handle, const char *key, co length = buf->len - sizeof(val); if (!length) { - settings_save(handle, key, NULL, 0); + err = settings_save(handle, key, NULL, 0); bt_mesh_free_buf(buf); - return 0; + return err; } store = bt_mesh_alloc_buf(length); diff --git a/components/bt/esp_ble_mesh/core/storage/settings_uid.c b/components/bt/esp_ble_mesh/core/storage/settings_uid.c index fae4b89f5f4..8a6ecfc66a9 100644 --- a/components/bt/esp_ble_mesh/core/storage/settings_uid.c +++ b/components/bt/esp_ble_mesh/core/storage/settings_uid.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2020-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2020-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -101,6 +101,11 @@ int settings_uid_load(void) for (i = 0; i < length / SETTINGS_ITEM_SIZE; i++) { uint16_t index = net_buf_simple_pull_le16(buf); + if (index >= ARRAY_SIZE(user_ids)) { + BT_WARN("Invalid index %u in NVS, skipping", index); + continue; + } + sprintf(name, "mesh/id/%04x", index); err = bt_mesh_load_uid_settings(name, (uint8_t *)user_ids[index].id, @@ -117,7 +122,13 @@ int settings_uid_load(void) } bt_mesh_free_buf(buf); - return err; + + /* Return 0 since partial loads are acceptable by design. + * Individual load failures are logged via BT_WARN/BT_ERR + * in bt_mesh_load_uid_settings() and do not prevent + * successful restoration of other settings. + */ + return 0; } #if CONFIG_BLE_MESH_DEINIT diff --git a/components/bt/esp_ble_mesh/core/test.h b/components/bt/esp_ble_mesh/core/test.h index 23ef6a84278..94ed36ea0cc 100644 --- a/components/bt/esp_ble_mesh/core/test.h +++ b/components/bt/esp_ble_mesh/core/test.h @@ -2,7 +2,7 @@ /* * SPDX-FileCopyrightText: 2017 Intel Corporation - * SPDX-FileContributor: 2018-2021 Espressif Systems (Shanghai) CO LTD + * SPDX-FileContributor: 2018-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -18,6 +18,9 @@ extern "C" { #endif +#if CONFIG_BLE_MESH_SELF_TEST + +#if CONFIG_BLE_MESH_NODE && CONFIG_BLE_MESH_TEST_AUTO_ENTER_NETWORK struct bt_mesh_device_network_info { uint8_t net_key[16]; uint16_t net_idx; @@ -31,7 +34,9 @@ struct bt_mesh_device_network_info { }; int bt_mesh_device_auto_enter_network(struct bt_mesh_device_network_info *info); +#endif /* CONFIG_BLE_MESH_NODE && CONFIG_BLE_MESH_TEST_AUTO_ENTER_NETWORK */ +#if CONFIG_BLE_MESH_TEST_USE_WHITE_LIST /* Before trying to update the white list, users need to make sure that * one of the following conditions is satisfied: * 1. BLE scanning is disabled; @@ -44,6 +49,7 @@ int bt_mesh_test_update_white_list(struct bt_mesh_white_list *wl); int bt_mesh_test_start_scanning(bool wl_en); int bt_mesh_test_stop_scanning(void); +#endif /* CONFIG_BLE_MESH_TEST_USE_WHITE_LIST */ typedef void (* bt_mesh_test_net_pdu_cb_t)(const uint8_t *data, uint16_t length); @@ -53,6 +59,8 @@ void bt_mesh_test_register_net_pdu_cb(bt_mesh_test_net_pdu_cb_t cb); void bt_mesh_test_set_seq(uint32_t seq); +#endif /* CONFIG_BLE_MESH_SELF_TEST */ + #ifdef __cplusplus } #endif diff --git a/components/bt/esp_ble_mesh/core/transport.c b/components/bt/esp_ble_mesh/core/transport.c index 0c33dce5ef0..eaf1cfd2317 100644 --- a/components/bt/esp_ble_mesh/core/transport.c +++ b/components/bt/esp_ble_mesh/core/transport.c @@ -2,7 +2,7 @@ /* * SPDX-FileCopyrightText: 2017 Intel Corporation - * SPDX-FileContributor: 2018-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileContributor: 2018-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -48,10 +48,10 @@ _Static_assert(CONFIG_BLE_MESH_ADV_BUF_COUNT >= (CONFIG_BLE_MESH_TX_SEG_MAX + 3) #define APP_MIC_LEN(aszmic) ((aszmic) ? BLE_MESH_MIC_LONG : BLE_MESH_MIC_SHORT) -#define UNSEG_HDR(akf, aid) ((akf << 6) | (aid & AID_MASK)) +#define UNSEG_HDR(akf, aid) (((akf) << 6) | ((aid) & AID_MASK)) #define SEG_HDR(akf, aid) (UNSEG_HDR(akf, aid) | 0x80) -#define BLOCK_COMPLETE(seg_n) (uint32_t)(((uint64_t)1 << (seg_n + 1)) - 1) +#define BLOCK_COMPLETE(seg_n) (uint32_t)(((uint64_t)1 << ((seg_n) + 1)) - 1) #define SEQ_AUTH(iv_index, seq) (((uint64_t)iv_index) << 24 | (uint64_t)seq) @@ -795,7 +795,7 @@ int bt_mesh_trans_send(struct bt_mesh_net_tx *tx, struct net_buf_simple *msg, uint8_t aid = 0U; int err = 0; - BT_DBG("transcend"); + BT_DBG("TransLegSend"); if (msg->len < 1) { BT_ERR("Zero-length SDU not allowed"); @@ -1499,7 +1499,7 @@ static void seg_ack(struct k_work *work) bt_mesh_seg_rx_unlock(); } -static inline uint16_t sdu_len_max(uint8_t seg_n,uint16_t seg_len) +static inline uint16_t sdu_len_max(uint8_t seg_n, uint16_t seg_len) { BT_DBG("IsSduLenOK,Len:%u,SegN:%u", seg_len, seg_n); @@ -1515,7 +1515,12 @@ static inline bool sdu_len_is_ok(bool ctl, uint8_t seg_n, uint16_t buf_len) BT_DBG("IsSduLenOK, CTL %u SegN %u", ctl, seg_n); #if CONFIG_BLE_MESH_LONG_PACKET - if ((sdu_len_max(seg_n, buf_len) > CONFIG_BLE_MESH_RX_SDU_MAX)) { + /* Use maximum possible segment length based on CTL flag, not actual buf_len, + * to correctly detect long packets. The last segment can be shorter than + * regular segments, so using buf_len could underestimate the SDU size. + */ + uint8_t max_seg_len = ctl ? BLE_MESH_EXT_CTL_SEG_SDU_MAX : BLE_MESH_EXT_APP_SEG_SDU_MAX; + if ((sdu_len_max(seg_n, max_seg_len) > BLE_MESH_EXT_RX_SDU_MAX)) { si.long_pkt = 1; return ((seg_n + 1) * seg_len(&si) <= BLE_MESH_EXT_RX_SDU_MAX); } diff --git a/components/bt/esp_ble_mesh/core/transport.enh.c b/components/bt/esp_ble_mesh/core/transport.enh.c index 4a962a6ba33..543e3fc9bb0 100644 --- a/components/bt/esp_ble_mesh/core/transport.enh.c +++ b/components/bt/esp_ble_mesh/core/transport.enh.c @@ -2,7 +2,7 @@ /* * SPDX-FileCopyrightText: 2017 Intel Corporation - * SPDX-FileContributor: 2023-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileContributor: 2023-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -45,12 +45,12 @@ _Static_assert(CONFIG_BLE_MESH_ADV_BUF_COUNT >= (CONFIG_BLE_MESH_TX_SEG_MAX + 3) #define APP_MIC_LEN(aszmic) ((aszmic) ? BLE_MESH_MIC_LONG : BLE_MESH_MIC_SHORT) -#define UNSEG_HDR(akf, aid) ((akf << 6) | (aid & AID_MASK)) -#define SEG_HDR(akf, aid) (UNSEG_HDR(akf, aid) | 0x80) +#define UNSEG_HDR(akf, aid) (((akf) << 6) | ((aid) & AID_MASK)) +#define SEG_HDR(akf, aid) (UNSEG_HDR((akf), (aid)) | 0x80) -#define BLOCK_COMPLETE(seg_n) (uint32_t)(((uint64_t)1 << (seg_n + 1)) - 1) +#define BLOCK_COMPLETE(seg_n) (uint32_t)(((uint64_t)1 << ((seg_n) + 1)) - 1) -#define SEQ_AUTH(iv_index, seq) (((uint64_t)iv_index) << 24 | (uint64_t)seq) +#define SEQ_AUTH(iv_index, seq) (((uint64_t)(iv_index)) << 24 | (uint64_t)(seq)) /* How long to wait for available buffers before giving up */ #define BUF_TIMEOUT K_NO_WAIT @@ -226,12 +226,13 @@ uint32_t bt_mesh_seg_rx_interval(void) uint32_t bt_mesh_seg_ack_timeout(uint8_t seg_n) { uint32_t timeout = 0U; - float min = 0.0; + uint32_t min_x2 = 0U; - min = MIN((float)seg_n + 0.5, (float)bt_mesh_get_sar_adi() + 1.5); - timeout = (uint32_t)(min * bt_mesh_seg_rx_interval()); + /* Use fixed-point arithmetic (x2 scale) to avoid float on FPU-less chips */ + min_x2 = MIN((uint32_t)seg_n * 2U + 1U, (uint32_t)bt_mesh_get_sar_adi() * 2U + 3U); + timeout = (min_x2 * bt_mesh_seg_rx_interval()) / 2U; - BT_DBG("SegAckTimeout %lu, Min %f", timeout, min); + BT_DBG("SegAckTimeout %lu, Min %lu", timeout, min_x2); return timeout; } @@ -1196,7 +1197,7 @@ int bt_mesh_trans_send(struct bt_mesh_net_tx *tx, struct net_buf_simple *msg, uint8_t aid = 0U; int err = 0; - BT_DBG("transcend"); + BT_DBG("TransEnhSend"); if (msg->len < 1) { BT_ERR("Zero-length SDU not allowed"); @@ -1679,6 +1680,12 @@ static int trans_heartbeat(struct bt_mesh_net_rx *rx, init_ttl = (net_buf_simple_pull_u8(buf) & 0x7f); feat = net_buf_simple_pull_be16(buf); + if (rx->ctx.recv_ttl > init_ttl) { + BT_WARN("Malformed heartbeat: recv_ttl (%u) > init_ttl (%u)", + rx->ctx.recv_ttl, init_ttl); + return -EINVAL; + } + hops = (init_ttl - rx->ctx.recv_ttl + 1); BT_INFO("Src 0x%04x TTL %u InitTTL %u Hops %u Feat 0x%04x", @@ -2064,7 +2071,7 @@ static void discard_msg(struct k_work *work) seg_rx_reset(rx, false); } -static inline uint16_t sdu_len_max(uint8_t seg_n,uint16_t seg_len) +static inline uint16_t sdu_len_max(uint8_t seg_n, uint16_t seg_len) { BT_DBG("IsSduLenOK,Len:%u,SegN:%u", seg_len, seg_n); @@ -2080,7 +2087,12 @@ static inline bool sdu_len_is_ok(bool ctl, uint8_t seg_n, uint16_t buf_len) BT_DBG("IsSduLenOK, CTL %u SegN %u", ctl, seg_n); #if CONFIG_BLE_MESH_LONG_PACKET - if ((sdu_len_max(seg_n, buf_len) > CONFIG_BLE_MESH_RX_SDU_MAX)) { + /* Use maximum possible segment length based on CTL flag, not actual buf_len, + * to correctly detect long packets. The last segment can be shorter than + * regular segments, so using buf_len could underestimate the SDU size. + */ + uint8_t max_seg_len = ctl ? BLE_MESH_EXT_CTL_SEG_SDU_MAX : BLE_MESH_EXT_APP_SEG_SDU_MAX; + if ((sdu_len_max(seg_n, max_seg_len) > BLE_MESH_EXT_RX_SDU_MAX)) { si.long_pkt = 1; return ((seg_n + 1) * seg_len(&si) <= BLE_MESH_EXT_RX_SDU_MAX); } diff --git a/components/bt/esp_ble_mesh/lib/ext.c b/components/bt/esp_ble_mesh/lib/ext.c index 9f7b61d548e..923d6f7db0b 100644 --- a/components/bt/esp_ble_mesh/lib/ext.c +++ b/components/bt/esp_ble_mesh/lib/ext.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2023-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2023-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -527,7 +527,7 @@ const char *bt_mesh_ext_hex(const void *buf, size_t len) } /* Crypto */ -bool bt_mesh_ext_s1(const char *m, uint8_t salt[16]) +int bt_mesh_ext_s1(const char *m, uint8_t salt[16]) { return bt_mesh_s1(m, salt); } @@ -866,18 +866,21 @@ void *bt_mesh_ext_model_get_pub(void *model) uint16_t bt_mesh_ext_model_get_pub_addr(void *model) { - return MODEL(model)->pub->addr; + struct bt_mesh_model_pub *pub = MODEL(model)->pub; + return pub ? pub->addr : BLE_MESH_ADDR_UNASSIGNED; } uint16_t bt_mesh_ext_model_get_pub_key(void *model) { - return MODEL(model)->pub->key; + struct bt_mesh_model_pub *pub = MODEL(model)->pub; + return pub ? pub->key : 0; } uint8_t bt_mesh_ext_model_get_pub_directed_pub_policy(void *model) { #if CONFIG_BLE_MESH_DF_SRV - return MODEL(model)->pub->directed_pub_policy; + struct bt_mesh_model_pub *pub = MODEL(model)->pub; + return pub ? pub->directed_pub_policy : 0; #else assert(0); return 0; @@ -887,7 +890,10 @@ uint8_t bt_mesh_ext_model_get_pub_directed_pub_policy(void *model) void bt_mesh_ext_model_set_pub_directed_pub_policy(void *model, uint8_t directed_pub_policy) { #if CONFIG_BLE_MESH_DF_SRV - MODEL(model)->pub->directed_pub_policy = directed_pub_policy; + struct bt_mesh_model_pub *pub = MODEL(model)->pub; + if (pub) { + pub->directed_pub_policy = directed_pub_policy; + } #else assert(0); #endif /* CONFIG_BLE_MESH_DF_SRV */ @@ -895,7 +901,8 @@ void bt_mesh_ext_model_set_pub_directed_pub_policy(void *model, uint8_t directed void *bt_mesh_ext_model_get_pub_msg(void *model) { - return MODEL(model)->pub->msg; + struct bt_mesh_model_pub *pub = MODEL(model)->pub; + return pub ? pub->msg : NULL; } uint8_t bt_mesh_ext_model_get_keys_count(void *model) @@ -1037,7 +1044,7 @@ bool bt_mesh_ext_model_is_opcode_belongs(void *models, uint8_t model_count, uint struct bt_mesh_model *model = NULL; for (size_t i = 0; i < model_count; i++) { - model = models + i; + model = &((struct bt_mesh_model *)models)[i]; for (op = model->op; op->func; op++) { if (op->opcode == opcode) { return true; @@ -1127,6 +1134,9 @@ int bt_mesh_ext_net_pdu_decrypt(void *sub, const uint8_t *enc, uint16_t bt_mesh_ext_net_get_sub_net_idx(uint8_t index) { + if (index >= ARRAY_SIZE(bt_mesh.sub)) { + return BLE_MESH_KEY_UNUSED; + } return bt_mesh.sub[index].net_idx; } @@ -1137,6 +1147,9 @@ uint8_t bt_mesh_ext_net_get_sub_count(void) void *bt_mesh_ext_net_get_sub(uint8_t index) { + if (index >= ARRAY_SIZE(bt_mesh.sub)) { + return NULL; + } return &bt_mesh.sub[index]; } @@ -1167,11 +1180,17 @@ uint16_t bt_mesh_ext_net_get_rpl_count(void) uint16_t bt_mesh_ext_net_get_rpl_src(uint16_t index) { + if (index >= ARRAY_SIZE(bt_mesh.rpl)) { + return BLE_MESH_ADDR_UNASSIGNED; + } return bt_mesh.rpl[index].src; } void bt_mesh_ext_net_reset_rpl(uint16_t index) { + if (index >= ARRAY_SIZE(bt_mesh.rpl)) { + return; + } memset(&bt_mesh.rpl[index], 0, sizeof(bt_mesh.rpl[index])); } @@ -1271,13 +1290,13 @@ uint8_t bt_mesh_ext_default_ttl_get(void) void bt_mesh_ext_key_idx_pack(struct net_buf_simple *buf, uint16_t idx1, uint16_t idx2) { - return key_idx_pack(buf, idx1, idx2); + key_idx_pack(buf, idx1, idx2); } void bt_mesh_ext_key_idx_unpack(struct net_buf_simple *buf, uint16_t *idx1, uint16_t *idx2) { - return key_idx_unpack(buf, idx1, idx2); + key_idx_unpack(buf, idx1, idx2); } /* Provisioning */ @@ -1729,6 +1748,9 @@ void bt_mesh_ext_prov_link_free_pb_remote_data(void *link) uint8_t *bt_mesh_ext_prov_link_get_record(void *link, uint16_t id) { #if (CONFIG_BLE_MESH_PROVISIONER && CONFIG_BLE_MESH_CERT_BASED_PROV) + if (id >= BLE_MESH_REC_MAX_ID) { + return NULL; + } return LINK(link)->records[id]; #else assert(0); @@ -1742,6 +1764,9 @@ uint8_t *bt_mesh_ext_prov_link_alloc_record(void *link, uint16_t id, uint16_t le if (id >= BLE_MESH_REC_MAX_ID) { return NULL; } + if (LINK(link)->records[id] != NULL) { + return NULL; /* Slot already allocated, caller should free first */ + } LINK(link)->records[id] = bt_mesh_calloc(len * sizeof(uint8_t)); return LINK(link)->records[id]; #else @@ -1930,6 +1955,9 @@ uint16_t bt_mesh_ext_proxy_server_get_filter_size(void *client) uint16_t bt_mesh_ext_proxy_server_get_filter_addr(void *client, uint8_t index) { #if CONFIG_BLE_MESH_GATT_PROXY_SERVER + if (index >= ARRAY_SIZE(PROXY_CLI(client)->filter)) { + return 0; + } return PROXY_CLI(client)->filter[index].addr; #else assert(0); @@ -1940,6 +1968,9 @@ uint16_t bt_mesh_ext_proxy_server_get_filter_addr(void *client, uint8_t index) bool bt_mesh_ext_proxy_server_filter_is_client(void *client, uint8_t index) { #if CONFIG_BLE_MESH_GATT_PROXY_SERVER + if (index >= ARRAY_SIZE(PROXY_CLI(client)->filter)) { + return false; + } return PROXY_CLI(client)->filter[index].proxy_client; #else assert(0); @@ -2117,7 +2148,7 @@ int bt_mesh_ext_rpr_srv_set_waiting_prov_link(void* link, bt_mesh_addr_t *addr) #else assert(0); return 0; -#endif /* CONFIG_BLE_MESH_PB_GATT && CONFIG_BLE_MESH_RPR_SRV) */ +#endif /* (CONFIG_BLE_MESH_PB_GATT && CONFIG_BLE_MESH_RPR_SRV) */ } /* Friend */ @@ -4192,7 +4223,7 @@ static const bt_mesh_ext_config_t bt_mesh_ext_cfg = { .config_ble_mesh_prb_cli = IS_ENABLED(CONFIG_BLE_MESH_PRB_CLI), .config_ble_mesh_prb_srv = IS_ENABLED(CONFIG_BLE_MESH_PRB_SRV), .config_ble_mesh_private_beacon = (IS_ENABLED(CONFIG_BLE_MESH_PRB_SRV) | \ - IS_ENABLED(CONFIG_BLE_MESH_PRB_SRV)), + IS_ENABLED(CONFIG_BLE_MESH_PRB_CLI)), .config_ble_mesh_rpr_cli = IS_ENABLED(CONFIG_BLE_MESH_RPR_CLI), .config_ble_mesh_rpr_srv = IS_ENABLED(CONFIG_BLE_MESH_RPR_SRV), .config_ble_mesh_rpr_srv_active_scan = IS_ENABLED(CONFIG_BLE_MESH_RPR_SRV_ACTIVE_SCAN), @@ -5060,7 +5091,7 @@ void ble_mesh_lib_compressed_buf_out(uint8_t log_level, uint32_t log_index, uint */ void bt_mesh_lib_ext_func_dummy_call(void) { - (void *)bt_hex(NULL, 0); + (void)bt_hex(NULL, 0); } int bt_mesh_v11_ext_init(void) diff --git a/components/bt/esp_ble_mesh/models/client/client_common.c b/components/bt/esp_ble_mesh/models/client/client_common.c index 1c177c3cf9d..2647a2c7dfd 100644 --- a/components/bt/esp_ble_mesh/models/client/client_common.c +++ b/components/bt/esp_ble_mesh/models/client/client_common.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2017-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2017-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -20,17 +20,14 @@ #define HCI_TIME_FOR_START_ADV K_MSEC(5) /* Three adv related hci commands may take 4 ~ 5ms */ -static bt_mesh_client_node_t *client_pick_node(sys_slist_t *list, uint16_t tx_dst) +static bt_mesh_client_node_t *client_pick_node_unsafe(sys_slist_t *list, uint16_t tx_dst) { bt_mesh_client_node_t *node = NULL; sys_snode_t *cur = NULL; BT_DBG("ClientPickNode, Dst 0x%04x", tx_dst); - bt_mesh_list_lock(); - if (sys_slist_is_empty(list)) { - bt_mesh_list_unlock(); BT_DBG("ListEmpty"); return NULL; } @@ -39,18 +36,26 @@ static bt_mesh_client_node_t *client_pick_node(sys_slist_t *list, uint16_t tx_ds cur != NULL; cur = sys_slist_peek_next(cur)) { node = (bt_mesh_client_node_t *)cur; if (node->ctx.addr == tx_dst) { - bt_mesh_list_unlock(); BT_DBG("ListNodeFound"); return node; } } - bt_mesh_list_unlock(); - BT_DBG("ListNodeNotFound"); return NULL; } +static bt_mesh_client_node_t *client_pick_node(sys_slist_t *list, uint16_t tx_dst) +{ + bt_mesh_client_node_t *node = NULL; + + bt_mesh_list_lock(); + node = client_pick_node_unsafe(list, tx_dst); + bt_mesh_list_unlock(); + + return node; +} + bt_mesh_client_node_t *bt_mesh_is_client_recv_publish_msg(struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, struct net_buf_simple *buf, @@ -99,7 +104,11 @@ bt_mesh_client_node_t *bt_mesh_is_client_recv_publish_msg(struct bt_mesh_model * return NULL; } - if ((node = client_pick_node(&data->queue, ctx->addr)) == NULL) { + bt_mesh_list_lock(); + + node = client_pick_node_unsafe(&data->queue, ctx->addr); + if (node == NULL) { + bt_mesh_list_unlock(); BT_DBG("MsgFromUnknownSrc"); if (cli->publish_status && need_pub) { cli->publish_status(ctx->recv_op, model, ctx, buf); @@ -108,6 +117,7 @@ bt_mesh_client_node_t *bt_mesh_is_client_recv_publish_msg(struct bt_mesh_model * } if (node->op_pending != ctx->recv_op) { + bt_mesh_list_unlock(); BT_DBG("MsgWithUnknownOp"); if (cli->publish_status && need_pub) { cli->publish_status(ctx->recv_op, model, ctx, buf); @@ -116,6 +126,7 @@ bt_mesh_client_node_t *bt_mesh_is_client_recv_publish_msg(struct bt_mesh_model * } if (k_delayed_work_remaining_get(&node->timer) == 0) { + bt_mesh_list_unlock(); BT_DBG("MsgWithTimerExpired"); if (cli->publish_status && need_pub) { cli->publish_status(ctx->recv_op, model, ctx, buf); @@ -123,6 +134,8 @@ bt_mesh_client_node_t *bt_mesh_is_client_recv_publish_msg(struct bt_mesh_model * return NULL; } + bt_mesh_list_unlock(); + return node; } @@ -152,7 +165,8 @@ static uint32_t client_get_status_op(const bt_mesh_client_op_pair_t *op_pair, static int32_t client_get_adv_duration(struct bt_mesh_msg_ctx *ctx) { - uint16_t duration = 0, adv_int = 0; + int32_t duration = 0; + uint16_t adv_int = 0; uint8_t xmit = 0; /* Initialize with network transmission */ @@ -172,9 +186,9 @@ static int32_t client_get_adv_duration(struct bt_mesh_msg_ctx *ctx) adv_int = BLE_MESH_TRANSMIT_INT(xmit); duration = (BLE_MESH_TRANSMIT_COUNT(xmit) + 1) * (adv_int + 10); - BT_DBG("Duration %ld", (int32_t)duration); + BT_DBG("Duration %ld", duration); - return (int32_t)duration; + return duration; } static int32_t client_calc_timeout(struct bt_mesh_msg_ctx *ctx, diff --git a/components/bt/esp_ble_mesh/v1.1/btc/btc_ble_mesh_agg_model.c b/components/bt/esp_ble_mesh/v1.1/btc/btc_ble_mesh_agg_model.c index ad087c33eb4..4b2a66ed955 100644 --- a/components/bt/esp_ble_mesh/v1.1/btc/btc_ble_mesh_agg_model.c +++ b/components/bt/esp_ble_mesh/v1.1/btc/btc_ble_mesh_agg_model.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2020-2023 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2020-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -76,8 +76,8 @@ void btc_ble_mesh_agg_client_arg_deep_copy(btc_msg_t *msg, void *p_dest, void *p } net_buf_simple_add_mem(dst->agg_send.msg->agg_sequence.items, - src->agg_send.msg->agg_sequence.items->data, - src->agg_send.msg->agg_sequence.items->len); + src->agg_send.msg->agg_sequence.items->data, + src->agg_send.msg->agg_sequence.items->len); } } break; @@ -165,6 +165,7 @@ static void btc_ble_mesh_agg_client_copy_req_data(btc_msg_t *msg, void *p_dest, break; } } + __attribute__((fallthrough)); case ESP_BLE_MESH_AGG_CLIENT_SEND_COMP_EVT: case ESP_BLE_MESH_AGG_CLIENT_SEND_TIMEOUT_EVT: break; @@ -197,6 +198,7 @@ static void btc_ble_mesh_agg_client_free_req_data(btc_msg_t *msg) break; } } + __attribute__((fallthrough)); case ESP_BLE_MESH_AGG_CLIENT_SEND_COMP_EVT: case ESP_BLE_MESH_AGG_CLIENT_SEND_TIMEOUT_EVT: if (arg->params) { @@ -286,8 +288,8 @@ void btc_ble_mesh_agg_client_recv_pub_cb(uint32_t opcode, } bt_mesh_agg_client_cb_evt_to_btc(opcode, - BTC_BLE_MESH_EVT_AGG_CLIENT_RECV_PUB, - model, ctx, buf->data, buf->len); + BTC_BLE_MESH_EVT_AGG_CLIENT_RECV_PUB, + model, ctx, buf->data, buf->len); } static int btc_ble_mesh_agg_client_send(esp_ble_mesh_client_common_param_t *params, @@ -329,7 +331,7 @@ void btc_ble_mesh_agg_client_call_handler(btc_msg_t *msg) cb.send.err_code = btc_ble_mesh_agg_client_send(arg->agg_send.params, arg->agg_send.msg); btc_ble_mesh_agg_client_cb(&cb, - ESP_BLE_MESH_AGG_CLIENT_SEND_COMP_EVT); + ESP_BLE_MESH_AGG_CLIENT_SEND_COMP_EVT); break; default: break; @@ -430,7 +432,7 @@ static void btc_ble_mesh_agg_server_free_req_data(btc_msg_t *msg) } static void btc_ble_mesh_agg_server_cb( - esp_ble_mesh_agg_server_cb_param_t *cb_params, uint8_t act) + esp_ble_mesh_agg_server_cb_param_t *cb_params, uint8_t act) { btc_msg_t msg = {0}; diff --git a/components/bt/esp_ble_mesh/v1.1/btc/btc_ble_mesh_brc_model.c b/components/bt/esp_ble_mesh/v1.1/btc/btc_ble_mesh_brc_model.c index 6b607eab185..156d53b9ec4 100644 --- a/components/bt/esp_ble_mesh/v1.1/btc/btc_ble_mesh_brc_model.c +++ b/components/bt/esp_ble_mesh/v1.1/btc/btc_ble_mesh_brc_model.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2020-2023 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2020-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -166,6 +166,7 @@ static void btc_ble_mesh_brc_client_copy_req_data(btc_msg_t *msg, void *p_dest, break; } } + __attribute__((fallthrough)); case ESP_BLE_MESH_BRC_CLIENT_SEND_COMP_EVT: case ESP_BLE_MESH_BRC_CLIENT_SEND_TIMEOUT_EVT: break; @@ -202,6 +203,7 @@ static void btc_ble_mesh_brc_client_free_req_data(btc_msg_t *msg) break; } } + __attribute__((fallthrough)); case ESP_BLE_MESH_BRC_CLIENT_SEND_COMP_EVT: case ESP_BLE_MESH_BRC_CLIENT_SEND_TIMEOUT_EVT: if (arg->params) { @@ -291,8 +293,8 @@ void btc_ble_mesh_brc_client_recv_pub_cb(uint32_t opcode, } bt_mesh_brc_client_cb_evt_to_btc(opcode, - ESP_BLE_MESH_BRC_CLIENT_RECV_PUB_EVT, - model, ctx, buf->data, buf->len); + ESP_BLE_MESH_BRC_CLIENT_RECV_PUB_EVT, + model, ctx, buf->data, buf->len); } static int btc_ble_mesh_brc_client_send(esp_ble_mesh_client_common_param_t *params, @@ -360,7 +362,7 @@ void btc_ble_mesh_brc_client_call_handler(btc_msg_t *msg) cb.send.err_code = btc_ble_mesh_brc_client_send(arg->brc_send.params, arg->brc_send.msg); btc_ble_mesh_brc_client_cb(&cb, - ESP_BLE_MESH_BRC_CLIENT_SEND_COMP_EVT); + ESP_BLE_MESH_BRC_CLIENT_SEND_COMP_EVT); break; default: break; diff --git a/components/bt/esp_ble_mesh/v1.1/btc/btc_ble_mesh_df_model.c b/components/bt/esp_ble_mesh/v1.1/btc/btc_ble_mesh_df_model.c index 92dc75e4908..0ee8484b3fc 100644 --- a/components/bt/esp_ble_mesh/v1.1/btc/btc_ble_mesh_df_model.c +++ b/components/bt/esp_ble_mesh/v1.1/btc/btc_ble_mesh_df_model.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2020-2023 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2020-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -125,7 +125,7 @@ void btc_ble_mesh_df_client_arg_deep_copy(btc_msg_t *msg, void *p_dest, void *p_ switch (src->df_set.params->opcode) { case ESP_BLE_MESH_MODEL_OP_FORWARDING_TABLE_DEPS_ADD: if (src->df_set.set->forwarding_table_deps_add.dep_origin_uar_list && - src->df_set.set->forwarding_table_deps_add.dep_origin_uar_list_size) { + src->df_set.set->forwarding_table_deps_add.dep_origin_uar_list_size) { length = src->df_set.set->forwarding_table_deps_add.dep_origin_uar_list_size * sizeof(esp_ble_mesh_uar_t); dst->df_set.set->forwarding_table_deps_add.dep_origin_uar_list = bt_mesh_calloc(length); if (!dst->df_set.set->forwarding_table_deps_add.dep_origin_uar_list) { @@ -143,7 +143,7 @@ void btc_ble_mesh_df_client_arg_deep_copy(btc_msg_t *msg, void *p_dest, void *p_ length); } if (src->df_set.set->forwarding_table_deps_add.dep_target_uar_list && - src->df_set.set->forwarding_table_deps_add.dep_target_uar_list_size) { + src->df_set.set->forwarding_table_deps_add.dep_target_uar_list_size) { length = src->df_set.set->forwarding_table_deps_add.dep_target_uar_list_size * sizeof(esp_ble_mesh_uar_t); dst->df_set.set->forwarding_table_deps_add.dep_target_uar_list = bt_mesh_calloc(length); if (!dst->df_set.set->forwarding_table_deps_add.dep_target_uar_list) { @@ -167,7 +167,7 @@ void btc_ble_mesh_df_client_arg_deep_copy(btc_msg_t *msg, void *p_dest, void *p_ break; case ESP_BLE_MESH_MODEL_OP_FORWARDING_TABLE_DEPS_DEL: if (src->df_set.set->forwarding_table_deps_del.dep_origin_list && - src->df_set.set->forwarding_table_deps_del.dep_origin_list_size) { + src->df_set.set->forwarding_table_deps_del.dep_origin_list_size) { length = src->df_set.set->forwarding_table_deps_del.dep_origin_list_size * 2; dst->df_set.set->forwarding_table_deps_del.dep_origin_list = bt_mesh_calloc(length); if (!dst->df_set.set->forwarding_table_deps_del.dep_origin_list) { @@ -185,7 +185,7 @@ void btc_ble_mesh_df_client_arg_deep_copy(btc_msg_t *msg, void *p_dest, void *p_ length); } if (src->df_set.set->forwarding_table_deps_del.dep_target_list && - src->df_set.set->forwarding_table_deps_del.dep_target_list_size) { + src->df_set.set->forwarding_table_deps_del.dep_target_list_size) { length = src->df_set.set->forwarding_table_deps_del.dep_target_list_size * 2; dst->df_set.set->forwarding_table_deps_del.dep_target_list = bt_mesh_calloc(length); if (!dst->df_set.set->forwarding_table_deps_del.dep_target_list) { @@ -351,6 +351,7 @@ static void btc_ble_mesh_df_client_copy_req_data(btc_msg_t *msg, void *p_dest, v break; } } + __attribute__((fallthrough)); case ESP_BLE_MESH_DF_CLIENT_SEND_COMP_EVT: case ESP_BLE_MESH_DF_CLIENT_SEND_TIMEOUT_EVT: break; @@ -389,6 +390,7 @@ static void btc_ble_mesh_df_client_free_req_data(btc_msg_t *msg) break; } } + __attribute__((fallthrough)); case ESP_BLE_MESH_DF_CLIENT_SEND_COMP_EVT: case ESP_BLE_MESH_DF_CLIENT_SEND_TIMEOUT_EVT: if (arg->params) { @@ -481,8 +483,8 @@ void btc_ble_mesh_df_client_recv_pub_cb(uint32_t opcode, } bt_mesh_df_client_cb_evt_to_btc(opcode, - BTC_BLE_MESH_EVT_DF_CLIENT_RECV_PUB, - model, ctx, buf->data, buf->len); + BTC_BLE_MESH_EVT_DF_CLIENT_RECV_PUB, + model, ctx, buf->data, buf->len); } static int btc_ble_mesh_df_client_get_state(esp_ble_mesh_client_common_param_t *params, @@ -633,14 +635,14 @@ void btc_ble_mesh_df_client_call_handler(btc_msg_t *msg) cb.send.err_code = btc_ble_mesh_df_client_get_state(arg->df_get.params, arg->df_get.get); btc_ble_mesh_df_client_cb(&cb, - ESP_BLE_MESH_DF_CLIENT_SEND_COMP_EVT); + ESP_BLE_MESH_DF_CLIENT_SEND_COMP_EVT); break; case BTC_BLE_MESH_ACT_DF_CLIENT_SET_STATE: cb.params = arg->df_set.params; cb.send.err_code = btc_ble_mesh_df_client_set_state(arg->df_set.params, arg->df_set.set); btc_ble_mesh_df_client_cb(&cb, - ESP_BLE_MESH_DF_CLIENT_SEND_COMP_EVT); + ESP_BLE_MESH_DF_CLIENT_SEND_COMP_EVT); break; default: break; @@ -685,7 +687,7 @@ static inline void btc_ble_mesh_df_server_cb_to_app(esp_ble_mesh_df_server_cb_ev } static void btc_ble_mesh_df_server_cb( - esp_ble_mesh_df_server_cb_param_t *cb_params, uint8_t act) + esp_ble_mesh_df_server_cb_param_t *cb_params, uint8_t act) { btc_msg_t msg = {0}; diff --git a/components/bt/esp_ble_mesh/v1.1/btc/btc_ble_mesh_dfu_model.c b/components/bt/esp_ble_mesh/v1.1/btc/btc_ble_mesh_dfu_model.c index f316486c0c8..53e036512a5 100644 --- a/components/bt/esp_ble_mesh/v1.1/btc/btc_ble_mesh_dfu_model.c +++ b/components/bt/esp_ble_mesh/v1.1/btc/btc_ble_mesh_dfu_model.c @@ -94,8 +94,8 @@ void btc_ble_mesh_dfu_client_arg_deep_copy(btc_msg_t *msg, void *p_dest, void *p } case BTC_BLE_MESH_ACT_DFU_CLIENT_IMG_SEND: /* That will be freed when dfu completed or failed not on btc deep free */ - dst->send_arg.inputs =(struct esp_ble_mesh_blob_cli_inputs *) - dfu_targets_alloc((struct bt_mesh_blob_cli_inputs *)src->send_arg.inputs); + dst->send_arg.inputs = (struct esp_ble_mesh_blob_cli_inputs *) + dfu_targets_alloc((struct bt_mesh_blob_cli_inputs *)src->send_arg.inputs); break; default: BT_DBG("%s, Unknown act %d", __func__, msg->act); @@ -119,13 +119,13 @@ void btc_ble_mesh_dfu_client_arg_deep_free(btc_msg_t *msg) if (arg->dfu_get.params) { if (arg->dfu_get.get) { switch (arg->dfu_get.params->opcode) { - case ESP_BLE_MESH_DFU_OP_UPDATE_METADATA_CHECK: - if (arg->dfu_get.get->dfu_metadata_check.metadata) { - bt_mesh_free_buf(arg->dfu_get.get->dfu_metadata_check.metadata); - } - break; - default: - break; + case ESP_BLE_MESH_DFU_OP_UPDATE_METADATA_CHECK: + if (arg->dfu_get.get->dfu_metadata_check.metadata) { + bt_mesh_free_buf(arg->dfu_get.get->dfu_metadata_check.metadata); + } + break; + default: + break; } bt_mesh_free(arg->dfu_get.get); } @@ -185,6 +185,7 @@ static void btc_ble_mesh_dfu_client_copy_req_data(btc_msg_t *msg, void *p_dest, break; } } + __attribute__((fallthrough)); case ESP_BLE_MESH_DFU_CLIENT_SEND_COMP_EVT: case ESP_BLE_MESH_DFU_CLIENT_TIMEOUT_EVT: case ESP_BLE_MESH_DFU_CLIENT_IMG_SEND_CMP_EVT: @@ -221,6 +222,7 @@ static void btc_ble_mesh_dfu_client_free_req_data(btc_msg_t *msg) break; } } + __attribute__((fallthrough)); case ESP_BLE_MESH_DFU_CLIENT_SEND_COMP_EVT: case ESP_BLE_MESH_DFU_CLIENT_TIMEOUT_EVT: case ESP_BLE_MESH_DFU_CLIENT_IMG_SEND_CMP_EVT: @@ -457,7 +459,7 @@ static inline void btc_ble_mesh_dfd_client_cb_to_app(btc_ble_mesh_dfd_client_cb_ esp_ble_mesh_dfd_client_cb_param_t *param) { esp_ble_mesh_dfd_client_cb_t btc_ble_mesh_cb = - (esp_ble_mesh_dfd_client_cb_t)btc_profile_cb_get(BTC_PID_DFD_CLIENT); + (esp_ble_mesh_dfd_client_cb_t)btc_profile_cb_get(BTC_PID_DFD_CLIENT); if (btc_ble_mesh_cb) { btc_ble_mesh_cb(event, param); } @@ -476,7 +478,7 @@ static inline bool dfd_client_param_need(uint32_t opcode) case ESP_BLE_MESH_DFD_OP_FW_DELETE: return true; default: - break; + break; } return false; } @@ -496,38 +498,38 @@ static int btc_ble_mesh_dfd_client_get(esp_ble_mesh_client_common_param_t *param return -EINVAL; } - switch(params->opcode) { - case ESP_BLE_MESH_DFD_OP_RECEIVERS_GET: - case ESP_BLE_MESH_DFD_OP_FW_GET: - case ESP_BLE_MESH_DFD_OP_FW_GET_BY_INDEX: - if (get == NULL) { - BT_ERR("%s:InvParam", __func__); - return -EINVAL; - } + switch (params->opcode) { + case ESP_BLE_MESH_DFD_OP_RECEIVERS_GET: + case ESP_BLE_MESH_DFD_OP_FW_GET: + case ESP_BLE_MESH_DFD_OP_FW_GET_BY_INDEX: + if (get == NULL) { + BT_ERR("%s:InvParam", __func__); + return -EINVAL; + } break; - default: + default: break; } btc_ble_mesh_set_client_common_param(params, ¶m, false); switch (params->opcode) { - case ESP_BLE_MESH_DFD_OP_RECEIVERS_GET: - return bt_mesh_dfd_cli_receivers_get(¶m, get->receivers_get.first_index, - get->receivers_get.entries_limit); - case ESP_BLE_MESH_DFD_OP_CAPABILITIES_GET: - return bt_mesh_dfd_cli_distribution_capabilities_get(¶m); - case ESP_BLE_MESH_DFD_OP_GET: - return bt_mesh_dfd_cli_distribution_get(¶m); - case ESP_BLE_MESH_DFD_OP_UPLOAD_GET: - return bt_mesh_dfd_cli_distribution_upload_get(¶m); - case ESP_BLE_MESH_DFD_OP_FW_GET: - return bt_mesh_dfd_cli_firmware_get(¶m, get->dist_fw_get.fwid); - case ESP_BLE_MESH_DFD_OP_FW_GET_BY_INDEX: - return bt_mesh_dfd_cli_firmware_get_by_index(¶m, get->dist_fw_get_by_idx.dist_fw_idx); - default: - BT_ERR("UknOpc:%04x", params->opcode); - return -EINVAL; + case ESP_BLE_MESH_DFD_OP_RECEIVERS_GET: + return bt_mesh_dfd_cli_receivers_get(¶m, get->receivers_get.first_index, + get->receivers_get.entries_limit); + case ESP_BLE_MESH_DFD_OP_CAPABILITIES_GET: + return bt_mesh_dfd_cli_distribution_capabilities_get(¶m); + case ESP_BLE_MESH_DFD_OP_GET: + return bt_mesh_dfd_cli_distribution_get(¶m); + case ESP_BLE_MESH_DFD_OP_UPLOAD_GET: + return bt_mesh_dfd_cli_distribution_upload_get(¶m); + case ESP_BLE_MESH_DFD_OP_FW_GET: + return bt_mesh_dfd_cli_firmware_get(¶m, get->dist_fw_get.fwid); + case ESP_BLE_MESH_DFD_OP_FW_GET_BY_INDEX: + return bt_mesh_dfd_cli_firmware_get_by_index(¶m, get->dist_fw_get_by_idx.dist_fw_idx); + default: + BT_ERR("UknOpc:%04x", params->opcode); + return -EINVAL; } } @@ -549,31 +551,31 @@ static int btc_ble_mesh_dfd_client_set(esp_ble_mesh_client_common_param_t *param btc_ble_mesh_set_client_common_param(params, ¶m, false); switch (params->opcode) { - case ESP_BLE_MESH_DFD_OP_RECEIVERS_ADD: - return bt_mesh_dfd_cli_receivers_add(¶m, (dfd_cli_receiver_entry_t *)set->receivers_add.receivers, set->receivers_add.receivers_cnt); - case ESP_BLE_MESH_DFD_OP_RECEIVERS_DELETE_ALL: - return bt_mesh_dfd_cli_receivers_delete_all(¶m); - case ESP_BLE_MESH_DFD_OP_START: - return bt_mesh_dfd_cli_distribution_start(¶m, (dfd_cli_dist_start_t *)&set->dist_start); - case ESP_BLE_MESH_DFD_OP_SUSPEND: - return bt_mesh_dfd_cli_distribution_suspend(¶m); - case ESP_BLE_MESH_DFD_OP_CANCEL: - return bt_mesh_dfd_cli_distribution_cancel(¶m); - case ESP_BLE_MESH_DFD_OP_APPLY: - return bt_mesh_dfd_cli_distribution_apply(¶m); - case ESP_BLE_MESH_DFD_OP_UPLOAD_START: - return bt_mesh_dfd_cli_distribution_upload_start(¶m, (dfd_cli_dist_upload_start_t *)&set->dist_upload_start); - case ESP_BLE_MESH_DFD_OP_UPLOAD_START_OOB: - return bt_mesh_dfd_cli_distribution_upload_oob_start(¶m, (dfd_cli_dist_upload_oob_start_t *)&set->dist_upload_oob_start); - case ESP_BLE_MESH_DFD_OP_UPLOAD_CANCEL: - return bt_mesh_dfd_cli_distribution_upload_oob_cancel(¶m); - case ESP_BLE_MESH_DFD_OP_FW_DELETE: - return bt_mesh_dfd_cli_firmware_get_delete(¶m, set->dist_fw_del.fwid); - case ESP_BLE_MESH_DFD_OP_FW_DELETE_ALL: - return bt_mesh_dfd_cli_firmware_delete_all(¶m); - default: - BT_ERR("UknOpc:%04x", params->opcode); - return -EINVAL; + case ESP_BLE_MESH_DFD_OP_RECEIVERS_ADD: + return bt_mesh_dfd_cli_receivers_add(¶m, (dfd_cli_receiver_entry_t *)set->receivers_add.receivers, set->receivers_add.receivers_cnt); + case ESP_BLE_MESH_DFD_OP_RECEIVERS_DELETE_ALL: + return bt_mesh_dfd_cli_receivers_delete_all(¶m); + case ESP_BLE_MESH_DFD_OP_START: + return bt_mesh_dfd_cli_distribution_start(¶m, (dfd_cli_dist_start_t *)&set->dist_start); + case ESP_BLE_MESH_DFD_OP_SUSPEND: + return bt_mesh_dfd_cli_distribution_suspend(¶m); + case ESP_BLE_MESH_DFD_OP_CANCEL: + return bt_mesh_dfd_cli_distribution_cancel(¶m); + case ESP_BLE_MESH_DFD_OP_APPLY: + return bt_mesh_dfd_cli_distribution_apply(¶m); + case ESP_BLE_MESH_DFD_OP_UPLOAD_START: + return bt_mesh_dfd_cli_distribution_upload_start(¶m, (dfd_cli_dist_upload_start_t *)&set->dist_upload_start); + case ESP_BLE_MESH_DFD_OP_UPLOAD_START_OOB: + return bt_mesh_dfd_cli_distribution_upload_oob_start(¶m, (dfd_cli_dist_upload_oob_start_t *)&set->dist_upload_oob_start); + case ESP_BLE_MESH_DFD_OP_UPLOAD_CANCEL: + return bt_mesh_dfd_cli_distribution_upload_oob_cancel(¶m); + case ESP_BLE_MESH_DFD_OP_FW_DELETE: + return bt_mesh_dfd_cli_firmware_get_delete(¶m, set->dist_fw_del.fwid); + case ESP_BLE_MESH_DFD_OP_FW_DELETE_ALL: + return bt_mesh_dfd_cli_firmware_delete_all(¶m); + default: + BT_ERR("UknOpc:%04x", params->opcode); + return -EINVAL; } } @@ -633,34 +635,34 @@ void btc_ble_mesh_dfd_client_arg_deep_copy(btc_msg_t *msg, void *p_dest, void *p } switch (dst->dfd_client_get.params->opcode) { - case ESP_BLE_MESH_DFD_OP_FW_GET: - if (src->dfd_client_get.get->dist_fw_get.fwid == NULL) { - BT_ERR("%s:%d,InvParam", __func__, __LINE__); - /* Free the previously allocated resources */ - bt_mesh_free(dst->dfd_client_get.params); - dst->dfd_client_get.params = NULL; - bt_mesh_free(dst->dfd_client_get.get); - dst->dfd_client_get.get = NULL; - break; - } + case ESP_BLE_MESH_DFD_OP_FW_GET: + if (src->dfd_client_get.get->dist_fw_get.fwid == NULL) { + BT_ERR("%s:%d,InvParam", __func__, __LINE__); + /* Free the previously allocated resources */ + bt_mesh_free(dst->dfd_client_get.params); + dst->dfd_client_get.params = NULL; + bt_mesh_free(dst->dfd_client_get.get); + dst->dfd_client_get.get = NULL; + break; + } - dst->dfd_client_get.get->dist_fw_get.fwid = - bt_mesh_alloc_buf(src->dfd_client_get.get->dist_fw_get.fwid->len); - if (dst->dfd_client_get.get->dist_fw_get.fwid == NULL) { - BT_ERR("%s:%d,OutMem", __func__, __LINE__); - /* Free the previously allocated resources */ - bt_mesh_free(dst->dfd_client_get.params); - dst->dfd_client_get.params = NULL; - bt_mesh_free(dst->dfd_client_get.get); - dst->dfd_client_get.get = NULL; - break; - } - net_buf_simple_add_mem(dst->dfd_client_get.get->dist_fw_get.fwid, - src->dfd_client_get.get->dist_fw_get.fwid->data, - src->dfd_client_get.get->dist_fw_get.fwid->len); - break; - default: + dst->dfd_client_get.get->dist_fw_get.fwid = + bt_mesh_alloc_buf(src->dfd_client_get.get->dist_fw_get.fwid->len); + if (dst->dfd_client_get.get->dist_fw_get.fwid == NULL) { + BT_ERR("%s:%d,OutMem", __func__, __LINE__); + /* Free the previously allocated resources */ + bt_mesh_free(dst->dfd_client_get.params); + dst->dfd_client_get.params = NULL; + bt_mesh_free(dst->dfd_client_get.get); + dst->dfd_client_get.get = NULL; break; + } + net_buf_simple_add_mem(dst->dfd_client_get.get->dist_fw_get.fwid, + src->dfd_client_get.get->dist_fw_get.fwid->data, + src->dfd_client_get.get->dist_fw_get.fwid->len); + break; + default: + break; } break; case BTC_BLE_MESH_ACT_DFD_CLIENT_SET: @@ -687,150 +689,150 @@ void btc_ble_mesh_dfd_client_arg_deep_copy(btc_msg_t *msg, void *p_dest, void *p memcpy(dst->dfd_client_set.set, src->dfd_client_set.set, sizeof(esp_ble_mesh_dfd_client_set_param_t)); } switch (dst->dfd_client_set.params->opcode) { - case ESP_BLE_MESH_DFD_OP_RECEIVERS_ADD: - if (src->dfd_client_set.set->receivers_add.receivers_cnt == 0) { - dst->dfd_client_set.set->receivers_add.receivers = NULL; - BT_ERR("%s:%d,InvParam", __func__, __LINE__); - /* Free the previously allocated resources */ - bt_mesh_free(dst->dfd_client_set.params); - dst->dfd_client_set.params = NULL; - bt_mesh_free(dst->dfd_client_set.set); - dst->dfd_client_set.set = NULL; - break; - } - dst->dfd_client_set.set->receivers_add.receivers = - (esp_ble_mesh_dfd_cli_receiver_entry_t *)bt_mesh_calloc(dst->dfd_client_set.set->receivers_add.receivers_cnt * - sizeof(esp_ble_mesh_dfd_cli_receiver_entry_t)); - if (dst->dfd_client_set.set->receivers_add.receivers == NULL) { + case ESP_BLE_MESH_DFD_OP_RECEIVERS_ADD: + if (src->dfd_client_set.set->receivers_add.receivers_cnt == 0) { + dst->dfd_client_set.set->receivers_add.receivers = NULL; + BT_ERR("%s:%d,InvParam", __func__, __LINE__); + /* Free the previously allocated resources */ + bt_mesh_free(dst->dfd_client_set.params); + dst->dfd_client_set.params = NULL; + bt_mesh_free(dst->dfd_client_set.set); + dst->dfd_client_set.set = NULL; + break; + } + dst->dfd_client_set.set->receivers_add.receivers = + (esp_ble_mesh_dfd_cli_receiver_entry_t *)bt_mesh_calloc(dst->dfd_client_set.set->receivers_add.receivers_cnt * + sizeof(esp_ble_mesh_dfd_cli_receiver_entry_t)); + if (dst->dfd_client_set.set->receivers_add.receivers == NULL) { + BT_ERR("%s:%d,OutMem", __func__, __LINE__); + /* Free the previously allocated resources */ + bt_mesh_free(dst->dfd_client_set.params); + dst->dfd_client_set.params = NULL; + bt_mesh_free(dst->dfd_client_set.set); + dst->dfd_client_set.set = NULL; + break; + } + memcpy(dst->dfd_client_set.set->receivers_add.receivers, src->dfd_client_set.set->receivers_add.receivers, + dst->dfd_client_set.set->receivers_add.receivers_cnt * sizeof(esp_ble_mesh_dfd_cli_receiver_entry_t)); + break; + case ESP_BLE_MESH_DFD_OP_UPLOAD_START: + if (src->dfd_client_set.set->dist_upload_start.fwid == NULL) { + dst->dfd_client_set.set->dist_upload_start.fwid = NULL; + BT_ERR("%s:%d,InvParam", __func__, __LINE__); + /* Free the previously allocated resources */ + bt_mesh_free(dst->dfd_client_set.params); + dst->dfd_client_set.params = NULL; + bt_mesh_free(dst->dfd_client_set.set); + dst->dfd_client_set.set = NULL; + break; + } + + dst->dfd_client_set.set->dist_upload_start.fwid = + bt_mesh_alloc_buf(src->dfd_client_set.set->dist_upload_start.fwid->len); + if (dst->dfd_client_set.set->dist_upload_start.fwid == NULL) { + BT_ERR("%s:%d,OutMem", __func__, __LINE__); + /* Free the previously allocated resources */ + bt_mesh_free(dst->dfd_client_set.params); + dst->dfd_client_set.params = NULL; + bt_mesh_free(dst->dfd_client_set.set); + dst->dfd_client_set.set = NULL; + break; + } + net_buf_simple_add_mem(dst->dfd_client_set.set->dist_upload_start.fwid, + src->dfd_client_set.set->dist_upload_start.fwid->data, + src->dfd_client_set.set->dist_upload_start.fwid->len); + + if (src->dfd_client_set.set->dist_upload_start.fw_metadata->len == 0) { + dst->dfd_client_set.set->dist_upload_start.fw_metadata = NULL; + break; + } else { + dst->dfd_client_set.set->dist_upload_start.fw_metadata = + bt_mesh_alloc_buf(src->dfd_client_set.set->dist_upload_start.fw_metadata->len); + if (dst->dfd_client_set.set->dist_upload_start.fw_metadata == NULL) { BT_ERR("%s:%d,OutMem", __func__, __LINE__); /* Free the previously allocated resources */ - bt_mesh_free(dst->dfd_client_set.params); - dst->dfd_client_set.params = NULL; - bt_mesh_free(dst->dfd_client_set.set); - dst->dfd_client_set.set = NULL; - break; - } - memcpy(dst->dfd_client_set.set->receivers_add.receivers, src->dfd_client_set.set->receivers_add.receivers, - dst->dfd_client_set.set->receivers_add.receivers_cnt * sizeof(esp_ble_mesh_dfd_cli_receiver_entry_t)); - break; - case ESP_BLE_MESH_DFD_OP_UPLOAD_START: - if (src->dfd_client_set.set->dist_upload_start.fwid == NULL) { + bt_mesh_free_buf(dst->dfd_client_set.set->dist_upload_start.fwid); dst->dfd_client_set.set->dist_upload_start.fwid = NULL; - BT_ERR("%s:%d,InvParam", __func__, __LINE__); - /* Free the previously allocated resources */ bt_mesh_free(dst->dfd_client_set.params); dst->dfd_client_set.params = NULL; bt_mesh_free(dst->dfd_client_set.set); dst->dfd_client_set.set = NULL; break; } - - dst->dfd_client_set.set->dist_upload_start.fwid = - bt_mesh_alloc_buf(src->dfd_client_set.set->dist_upload_start.fwid->len); - if (dst->dfd_client_set.set->dist_upload_start.fwid == NULL) { - BT_ERR("%s:%d,OutMem", __func__, __LINE__); - /* Free the previously allocated resources */ - bt_mesh_free(dst->dfd_client_set.params); - dst->dfd_client_set.params = NULL; - bt_mesh_free(dst->dfd_client_set.set); - dst->dfd_client_set.set = NULL; - break; - } - net_buf_simple_add_mem(dst->dfd_client_set.set->dist_upload_start.fwid, - src->dfd_client_set.set->dist_upload_start.fwid->data, - src->dfd_client_set.set->dist_upload_start.fwid->len); - - if (src->dfd_client_set.set->dist_upload_start.fw_metadata->len == 0) { - dst->dfd_client_set.set->dist_upload_start.fw_metadata = NULL; - break; - } else { - dst->dfd_client_set.set->dist_upload_start.fw_metadata = - bt_mesh_alloc_buf(src->dfd_client_set.set->dist_upload_start.fw_metadata->len); - if (dst->dfd_client_set.set->dist_upload_start.fw_metadata == NULL) { - BT_ERR("%s:%d,OutMem", __func__, __LINE__); - /* Free the previously allocated resources */ - bt_mesh_free_buf(dst->dfd_client_set.set->dist_upload_start.fwid); - dst->dfd_client_set.set->dist_upload_start.fwid = NULL; - bt_mesh_free(dst->dfd_client_set.params); - dst->dfd_client_set.params = NULL; - bt_mesh_free(dst->dfd_client_set.set); - dst->dfd_client_set.set = NULL; - break; - } - net_buf_simple_add_mem(dst->dfd_client_set.set->dist_upload_start.fw_metadata, - src->dfd_client_set.set->dist_upload_start.fw_metadata->data, - src->dfd_client_set.set->dist_upload_start.fw_metadata->len); - } - break; - case ESP_BLE_MESH_DFD_OP_UPLOAD_START_OOB: - if (src->dfd_client_set.set->dist_upload_oob_start.url == NULL || + net_buf_simple_add_mem(dst->dfd_client_set.set->dist_upload_start.fw_metadata, + src->dfd_client_set.set->dist_upload_start.fw_metadata->data, + src->dfd_client_set.set->dist_upload_start.fw_metadata->len); + } + break; + case ESP_BLE_MESH_DFD_OP_UPLOAD_START_OOB: + if (src->dfd_client_set.set->dist_upload_oob_start.url == NULL || src->dfd_client_set.set->dist_upload_oob_start.fwid == NULL) { - BT_ERR("%s:%d,InvParam", __func__, __LINE__); - /* Free the previously allocated resources */ - bt_mesh_free(dst->dfd_client_set.params); - dst->dfd_client_set.params = NULL; - bt_mesh_free(dst->dfd_client_set.set); - dst->dfd_client_set.set = NULL; - break; - } - dst->dfd_client_set.set->dist_upload_oob_start.url = - bt_mesh_alloc_buf(src->dfd_client_set.set->dist_upload_oob_start.url->len); - if (dst->dfd_client_set.set->dist_upload_oob_start.url == NULL) { - BT_ERR("%s:%d,OutMem", __func__, __LINE__); - /* Free the previously allocated resources */ - bt_mesh_free(dst->dfd_client_set.params); - dst->dfd_client_set.params = NULL; - bt_mesh_free(dst->dfd_client_set.set); - dst->dfd_client_set.set = NULL; - break; - } - dst->dfd_client_set.set->dist_upload_oob_start.fwid = - bt_mesh_alloc_buf(src->dfd_client_set.set->dist_upload_oob_start.fwid->len); - if (dst->dfd_client_set.set->dist_upload_oob_start.fwid == NULL) { - BT_ERR("%s:%d,OutMem", __func__, __LINE__); - /* Free the previously allocated resources */ - bt_mesh_free_buf(dst->dfd_client_set.set->dist_upload_oob_start.url); - dst->dfd_client_set.set->dist_upload_oob_start.url = NULL; - bt_mesh_free(dst->dfd_client_set.params); - dst->dfd_client_set.params = NULL; - bt_mesh_free(dst->dfd_client_set.set); - dst->dfd_client_set.set = NULL; - break; - } - net_buf_simple_add_mem(dst->dfd_client_set.set->dist_upload_oob_start.url, - src->dfd_client_set.set->dist_upload_oob_start.url->data, - src->dfd_client_set.set->dist_upload_oob_start.url->len); - net_buf_simple_add_mem(dst->dfd_client_set.set->dist_upload_oob_start.fwid, - src->dfd_client_set.set->dist_upload_oob_start.fwid->data, - src->dfd_client_set.set->dist_upload_oob_start.fwid->len); + BT_ERR("%s:%d,InvParam", __func__, __LINE__); + /* Free the previously allocated resources */ + bt_mesh_free(dst->dfd_client_set.params); + dst->dfd_client_set.params = NULL; + bt_mesh_free(dst->dfd_client_set.set); + dst->dfd_client_set.set = NULL; break; - case ESP_BLE_MESH_DFD_OP_FW_DELETE: - if (src->dfd_client_set.set->dist_fw_del.fwid == NULL) { - BT_ERR("%s:%d,InvParam", __func__, __LINE__); - /* Free the previously allocated resources */ - bt_mesh_free(dst->dfd_client_set.params); - dst->dfd_client_set.params = NULL; - bt_mesh_free(dst->dfd_client_set.set); - dst->dfd_client_set.set = NULL; - break; - } - dst->dfd_client_set.set->dist_fw_del.fwid = - bt_mesh_alloc_buf(src->dfd_client_set.set->dist_fw_del.fwid->len); - if (dst->dfd_client_set.set->dist_fw_del.fwid == NULL) { - BT_ERR("%s:%d,OutMem", __func__, __LINE__); - /* Free the previously allocated resources */ - bt_mesh_free(dst->dfd_client_set.params); - dst->dfd_client_set.params = NULL; - bt_mesh_free(dst->dfd_client_set.set); - dst->dfd_client_set.set = NULL; - break; - } - net_buf_simple_add_mem(dst->dfd_client_set.set->dist_fw_del.fwid, - src->dfd_client_set.set->dist_fw_del.fwid->data, - src->dfd_client_set.set->dist_fw_del.fwid->len); + } + dst->dfd_client_set.set->dist_upload_oob_start.url = + bt_mesh_alloc_buf(src->dfd_client_set.set->dist_upload_oob_start.url->len); + if (dst->dfd_client_set.set->dist_upload_oob_start.url == NULL) { + BT_ERR("%s:%d,OutMem", __func__, __LINE__); + /* Free the previously allocated resources */ + bt_mesh_free(dst->dfd_client_set.params); + dst->dfd_client_set.params = NULL; + bt_mesh_free(dst->dfd_client_set.set); + dst->dfd_client_set.set = NULL; break; - default: + } + dst->dfd_client_set.set->dist_upload_oob_start.fwid = + bt_mesh_alloc_buf(src->dfd_client_set.set->dist_upload_oob_start.fwid->len); + if (dst->dfd_client_set.set->dist_upload_oob_start.fwid == NULL) { + BT_ERR("%s:%d,OutMem", __func__, __LINE__); + /* Free the previously allocated resources */ + bt_mesh_free_buf(dst->dfd_client_set.set->dist_upload_oob_start.url); + dst->dfd_client_set.set->dist_upload_oob_start.url = NULL; + bt_mesh_free(dst->dfd_client_set.params); + dst->dfd_client_set.params = NULL; + bt_mesh_free(dst->dfd_client_set.set); + dst->dfd_client_set.set = NULL; break; + } + net_buf_simple_add_mem(dst->dfd_client_set.set->dist_upload_oob_start.url, + src->dfd_client_set.set->dist_upload_oob_start.url->data, + src->dfd_client_set.set->dist_upload_oob_start.url->len); + net_buf_simple_add_mem(dst->dfd_client_set.set->dist_upload_oob_start.fwid, + src->dfd_client_set.set->dist_upload_oob_start.fwid->data, + src->dfd_client_set.set->dist_upload_oob_start.fwid->len); + break; + case ESP_BLE_MESH_DFD_OP_FW_DELETE: + if (src->dfd_client_set.set->dist_fw_del.fwid == NULL) { + BT_ERR("%s:%d,InvParam", __func__, __LINE__); + /* Free the previously allocated resources */ + bt_mesh_free(dst->dfd_client_set.params); + dst->dfd_client_set.params = NULL; + bt_mesh_free(dst->dfd_client_set.set); + dst->dfd_client_set.set = NULL; + break; + } + dst->dfd_client_set.set->dist_fw_del.fwid = + bt_mesh_alloc_buf(src->dfd_client_set.set->dist_fw_del.fwid->len); + if (dst->dfd_client_set.set->dist_fw_del.fwid == NULL) { + BT_ERR("%s:%d,OutMem", __func__, __LINE__); + /* Free the previously allocated resources */ + bt_mesh_free(dst->dfd_client_set.params); + dst->dfd_client_set.params = NULL; + bt_mesh_free(dst->dfd_client_set.set); + dst->dfd_client_set.set = NULL; + break; + } + net_buf_simple_add_mem(dst->dfd_client_set.set->dist_fw_del.fwid, + src->dfd_client_set.set->dist_fw_del.fwid->data, + src->dfd_client_set.set->dist_fw_del.fwid->len); + break; + default: + break; } default: BT_DBG("%s, Unknown act %d", __func__, msg->act); @@ -861,66 +863,66 @@ void btc_ble_mesh_dfd_client_arg_deep_free(btc_msg_t *msg) break; } switch (arg->dfd_client_get.params->opcode) { - case ESP_BLE_MESH_DFD_OP_FW_GET: - if (arg->dfd_client_get.get->dist_fw_get.fwid == NULL) { - BT_ERR("%s:%d,InvParam", __func__, __LINE__); - break; - } - bt_mesh_free_buf(arg->dfd_client_get.get->dist_fw_get.fwid); + case ESP_BLE_MESH_DFD_OP_FW_GET: + if (arg->dfd_client_get.get->dist_fw_get.fwid == NULL) { + BT_ERR("%s:%d,InvParam", __func__, __LINE__); + break; + } + bt_mesh_free_buf(arg->dfd_client_get.get->dist_fw_get.fwid); break; - default: + default: break; } if (arg->dfd_client_get.get) { bt_mesh_free(arg->dfd_client_get.get); } bt_mesh_free(arg->dfd_client_get.params); - break; + break; case ESP_BLE_MESH_ACT_DFD_CLIENT_SET: if (arg->dfd_client_set.params == NULL) { BT_ERR("%s:%d,InvParam", __func__, __LINE__); break; } switch (arg->dfd_client_set.params->opcode) { - case ESP_BLE_MESH_DFD_OP_RECEIVERS_ADD: - if (arg->dfd_client_set.set->receivers_add.receivers == NULL) { - BT_ERR("%s:%d,InvParam", __func__, __LINE__); - break; - } - bt_mesh_free(arg->dfd_client_set.set->receivers_add.receivers); + case ESP_BLE_MESH_DFD_OP_RECEIVERS_ADD: + if (arg->dfd_client_set.set->receivers_add.receivers == NULL) { + BT_ERR("%s:%d,InvParam", __func__, __LINE__); + break; + } + bt_mesh_free(arg->dfd_client_set.set->receivers_add.receivers); break; - case ESP_BLE_MESH_DFD_OP_UPLOAD_START: - if (arg->dfd_client_set.set->dist_upload_start.fwid == NULL) { - BT_ERR("%s:%d,InvParam", __func__, __LINE__); - break; - } - bt_mesh_free_buf(arg->dfd_client_set.set->dist_upload_start.fwid); - if (arg->dfd_client_set.set->dist_upload_start.fw_metadata) { - bt_mesh_free_buf(arg->dfd_client_set.set->dist_upload_start.fw_metadata); - } + case ESP_BLE_MESH_DFD_OP_UPLOAD_START: + if (arg->dfd_client_set.set->dist_upload_start.fwid == NULL) { + BT_ERR("%s:%d,InvParam", __func__, __LINE__); + break; + } + bt_mesh_free_buf(arg->dfd_client_set.set->dist_upload_start.fwid); + if (arg->dfd_client_set.set->dist_upload_start.fw_metadata) { + bt_mesh_free_buf(arg->dfd_client_set.set->dist_upload_start.fw_metadata); + } break; - case ESP_BLE_MESH_DFD_OP_UPLOAD_START_OOB: - if (arg->dfd_client_set.set->dist_upload_oob_start.url == NULL || + case ESP_BLE_MESH_DFD_OP_UPLOAD_START_OOB: + if (arg->dfd_client_set.set->dist_upload_oob_start.url == NULL || arg->dfd_client_set.set->dist_upload_oob_start.fwid == NULL) { - BT_ERR("%s:%d,InvParam", __func__, __LINE__); - break; - } - bt_mesh_free_buf(arg->dfd_client_set.set->dist_upload_oob_start.url); - bt_mesh_free_buf(arg->dfd_client_set.set->dist_upload_oob_start.fwid); + BT_ERR("%s:%d,InvParam", __func__, __LINE__); + break; + } + bt_mesh_free_buf(arg->dfd_client_set.set->dist_upload_oob_start.url); + bt_mesh_free_buf(arg->dfd_client_set.set->dist_upload_oob_start.fwid); break; - case ESP_BLE_MESH_DFD_OP_FW_DELETE: - if (arg->dfd_client_set.set->dist_fw_del.fwid == NULL) { - BT_ERR("%s:%d,InvParam", __func__, __LINE__); - break; - } - bt_mesh_free_buf(arg->dfd_client_set.set->dist_fw_del.fwid); + case ESP_BLE_MESH_DFD_OP_FW_DELETE: + if (arg->dfd_client_set.set->dist_fw_del.fwid == NULL) { + BT_ERR("%s:%d,InvParam", __func__, __LINE__); + break; + } + bt_mesh_free_buf(arg->dfd_client_set.set->dist_fw_del.fwid); break; } if (arg->dfd_client_set.set) { bt_mesh_free(arg->dfd_client_set.set); } bt_mesh_free(arg->dfd_client_set.params); - break; + break; default: BT_WARN("Unprocessed event %d", msg->act); break; @@ -931,8 +933,8 @@ void btc_ble_mesh_dfd_client_arg_deep_free(btc_msg_t *msg) void btc_ble_mesh_dfd_client_rsp_deep_copy(btc_msg_t *msg, void *p_dest, void *p_src) { - esp_ble_mesh_dfd_client_cb_param_t *dst =(esp_ble_mesh_dfd_client_cb_param_t *) p_dest; - esp_ble_mesh_dfd_client_cb_param_t *src =(esp_ble_mesh_dfd_client_cb_param_t *) p_src; + esp_ble_mesh_dfd_client_cb_param_t *dst = (esp_ble_mesh_dfd_client_cb_param_t *) p_dest; + esp_ble_mesh_dfd_client_cb_param_t *src = (esp_ble_mesh_dfd_client_cb_param_t *) p_src; if (!msg || !dst || !src) { BT_ERR("%s, Invalid parameter", __func__); @@ -953,7 +955,7 @@ void btc_ble_mesh_dfd_client_rsp_deep_copy(btc_msg_t *msg, void *p_dest, void *p switch (msg->act) { case ESP_BLE_MESH_EVT_DFD_CLIENT_RECV_RSP: if (src->params) { - switch(src->params->opcode) { + switch (src->params->opcode) { case BLE_MESH_DFD_OP_RECEIVERS_LIST: dst->status_cb.receiver_list.first_index = src->status_cb.receiver_list.first_index; dst->status_cb.receiver_list.entries_cnt = src->status_cb.receiver_list.entries_cnt; @@ -981,7 +983,7 @@ void btc_ble_mesh_dfd_client_rsp_deep_copy(btc_msg_t *msg, void *p_dest, void *p dst->status_cb.dist_caps.oob_retrieval_supported = src->status_cb.dist_caps.oob_retrieval_supported; if (src->status_cb.dist_caps.supported_url_scheme_names) { dst->status_cb.dist_caps.supported_url_scheme_names = - bt_mesh_alloc_buf(src->status_cb.dist_caps.supported_url_scheme_names->len); + bt_mesh_alloc_buf(src->status_cb.dist_caps.supported_url_scheme_names->len); if (dst->status_cb.dist_caps.supported_url_scheme_names == NULL) { BT_ERR("%s:%d,OutOfMem", __func__, __LINE__); /* Free the previously allocated resources */ @@ -1024,7 +1026,7 @@ void btc_ble_mesh_dfd_client_rsp_deep_copy(btc_msg_t *msg, void *p_dest, void *p BT_ERR("%s:%d,InvParam", __func__, __LINE__); } } else { - if(src->status_cb.upload_status.oob_fwid) { + if (src->status_cb.upload_status.oob_fwid) { dst->status_cb.upload_status.oob_fwid = bt_mesh_alloc_buf(src->status_cb.upload_status.oob_fwid->len); if (dst->status_cb.upload_status.oob_fwid == NULL) { BT_ERR("%s:%d,OutOfMem", __func__, __LINE__); @@ -1104,45 +1106,45 @@ void btc_ble_mesh_dfd_client_rsp_deep_free(btc_msg_t *msg) arg = (esp_ble_mesh_dfd_client_cb_param_t *)(msg->arg); if (arg->params == NULL && - msg->act != ESP_BLE_MESH_ACT_DFD_CLIEND_SEND_COMP) { + msg->act != ESP_BLE_MESH_ACT_DFD_CLIEND_SEND_COMP) { BT_ERR("%s:%d,InvParam", __func__, __LINE__); return; } switch (msg->act) { - case ESP_BLE_MESH_EVT_DFD_CLIENT_RECV_RSP: - switch (arg->params->opcode) { - case BLE_MESH_DFD_OP_RECEIVERS_LIST: - if (arg->status_cb.receiver_list.entries) { - bt_mesh_free(arg->status_cb.receiver_list.entries); - arg->status_cb.receiver_list.entries = NULL; - } - break; - case BLE_MESH_DFD_OP_CAPABILITIES_STATUS: - if (arg->status_cb.dist_caps.supported_url_scheme_names) { - bt_mesh_free_buf(arg->status_cb.dist_caps.supported_url_scheme_names); - arg->status_cb.dist_caps.supported_url_scheme_names = NULL; - } - break; - case BLE_MESH_DFD_OP_UPLOAD_STATUS: - /** - * firmware_id and upload_oob_firmware_id are a union - * structure, so only one pointer needs to be released - */ - if (arg->status_cb.upload_status.fwid) { - bt_mesh_free_buf(arg->status_cb.upload_status.fwid); - arg->status_cb.upload_status.fwid = NULL; - } - break; - case BLE_MESH_DFD_OP_FW_STATUS: - if (arg->status_cb.firmware_status.fwid) { - bt_mesh_free_buf(arg->status_cb.firmware_status.fwid); - arg->status_cb.firmware_status.fwid = NULL; - } - break; + case ESP_BLE_MESH_EVT_DFD_CLIENT_RECV_RSP: + switch (arg->params->opcode) { + case BLE_MESH_DFD_OP_RECEIVERS_LIST: + if (arg->status_cb.receiver_list.entries) { + bt_mesh_free(arg->status_cb.receiver_list.entries); + arg->status_cb.receiver_list.entries = NULL; } + break; + case BLE_MESH_DFD_OP_CAPABILITIES_STATUS: + if (arg->status_cb.dist_caps.supported_url_scheme_names) { + bt_mesh_free_buf(arg->status_cb.dist_caps.supported_url_scheme_names); + arg->status_cb.dist_caps.supported_url_scheme_names = NULL; + } + break; + case BLE_MESH_DFD_OP_UPLOAD_STATUS: + /** + * firmware_id and upload_oob_firmware_id are a union + * structure, so only one pointer needs to be released + */ + if (arg->status_cb.upload_status.fwid) { + bt_mesh_free_buf(arg->status_cb.upload_status.fwid); + arg->status_cb.upload_status.fwid = NULL; + } + break; + case BLE_MESH_DFD_OP_FW_STATUS: + if (arg->status_cb.firmware_status.fwid) { + bt_mesh_free_buf(arg->status_cb.firmware_status.fwid); + arg->status_cb.firmware_status.fwid = NULL; + } + break; + } break; - default: + default: break; } @@ -1184,14 +1186,14 @@ void bt_mesh_dfd_client_cb_evt_to_btc(btc_ble_mesh_dfd_client_cb_evt_t event, } switch (event) { - case BTC_BLE_MESH_EVT_DFD_CLIENT_RECV_RSP: - act = ESP_BLE_MESH_EVT_DFD_CLIENT_RECV_RSP; + case BTC_BLE_MESH_EVT_DFD_CLIENT_RECV_RSP: + act = ESP_BLE_MESH_EVT_DFD_CLIENT_RECV_RSP; break; - case BTC_BLE_MESH_EVT_DFD_CLIENT_TIMEOUT: - act = ESP_BLE_MESH_EVT_DFD_CLIENT_TIMEOUT; + case BTC_BLE_MESH_EVT_DFD_CLIENT_TIMEOUT: + act = ESP_BLE_MESH_EVT_DFD_CLIENT_TIMEOUT; break; - default: - BT_ERR("Unknown event %d", event); + default: + BT_ERR("Unknown event %d", event); break; } @@ -1251,8 +1253,8 @@ int btc_ble_mesh_dfd_srv_oob_check_complete(struct esp_ble_mesh_dfd_srv *srv, return 0; } int btc_ble_mesh_dfd_srv_oob_store_complete(struct esp_ble_mesh_dfd_srv *srv, - const struct esp_ble_mesh_dfu_slot *slot, bool success, - size_t size, const uint8_t *metadata, size_t metadata_len) + const struct esp_ble_mesh_dfu_slot *slot, bool success, + size_t size, const uint8_t *metadata, size_t metadata_len) { bt_mesh_dfd_srv_oob_store_complete((struct bt_mesh_dfd_srv *)srv, (struct bt_mesh_dfu_slot *)slot, success, size, metadata, metadata_len); return 0; diff --git a/components/bt/esp_ble_mesh/v1.1/btc/btc_ble_mesh_lcd_model.c b/components/bt/esp_ble_mesh/v1.1/btc/btc_ble_mesh_lcd_model.c index 600ed023174..feae6f27594 100644 --- a/components/bt/esp_ble_mesh/v1.1/btc/btc_ble_mesh_lcd_model.c +++ b/components/bt/esp_ble_mesh/v1.1/btc/btc_ble_mesh_lcd_model.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2020-2023 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2020-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -157,6 +157,7 @@ static void btc_ble_mesh_lcd_client_copy_req_data(btc_msg_t *msg, void *p_dest, break; } } + __attribute__((fallthrough)); case ESP_BLE_MESH_LCD_CLIENT_SEND_COMP_EVT: case ESP_BLE_MESH_LCD_CLIENT_SEND_TIMEOUT_EVT: break; @@ -193,6 +194,7 @@ static void btc_ble_mesh_lcd_client_free_req_data(btc_msg_t *msg) break; } } + __attribute__((fallthrough)); case ESP_BLE_MESH_LCD_CLIENT_SEND_COMP_EVT: case ESP_BLE_MESH_LCD_CLIENT_SEND_TIMEOUT_EVT: if (arg->params) { @@ -282,8 +284,8 @@ void btc_ble_mesh_lcd_client_recv_pub_cb(uint32_t opcode, } bt_mesh_lcd_client_cb_evt_to_btc(opcode, - BTC_BLE_MESH_EVT_LCD_CLIENT_RECV_PUB, - model, ctx, buf->data, buf->len); + BTC_BLE_MESH_EVT_LCD_CLIENT_RECV_PUB, + model, ctx, buf->data, buf->len); } static int btc_ble_mesh_lcd_client_send(esp_ble_mesh_client_common_param_t *params, @@ -374,7 +376,7 @@ static inline void btc_ble_mesh_lcd_server_cb_to_app(esp_ble_mesh_lcd_server_cb_ } static void btc_ble_mesh_lcd_server_cb( - esp_ble_mesh_lcd_server_cb_param_t *cb_params, uint8_t act) + esp_ble_mesh_lcd_server_cb_param_t *cb_params, uint8_t act) { btc_msg_t msg = {0}; diff --git a/components/bt/esp_ble_mesh/v1.1/btc/btc_ble_mesh_mbt_model.c b/components/bt/esp_ble_mesh/v1.1/btc/btc_ble_mesh_mbt_model.c index 430bd31eeee..ed5ced368e3 100644 --- a/components/bt/esp_ble_mesh/v1.1/btc/btc_ble_mesh_mbt_model.c +++ b/components/bt/esp_ble_mesh/v1.1/btc/btc_ble_mesh_mbt_model.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2020-2023 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2020-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -146,7 +146,7 @@ static void btc_ble_mesh_blob_trans_client_copy_req_data(btc_msg_t *msg, void *p switch (msg->act) { case BTC_BLE_MESH_ACT_MBT_CLIENT_RETRIEVE_CAPABILITIES: if (src->value.retrieve_capabilities_status.input.unicast_addr_count && - src->value.retrieve_capabilities_status.input.unicast_addr) { + src->value.retrieve_capabilities_status.input.unicast_addr) { dst->value.retrieve_capabilities_status.input.unicast_addr = bt_mesh_calloc(src->value.retrieve_capabilities_status.input.unicast_addr_count * 2); if (dst->value.retrieve_capabilities_status.input.unicast_addr) { memcpy(dst->value.retrieve_capabilities_status.input.unicast_addr, src->value.retrieve_capabilities_status.input.unicast_addr, @@ -159,7 +159,7 @@ static void btc_ble_mesh_blob_trans_client_copy_req_data(btc_msg_t *msg, void *p break; case BTC_BLE_MESH_ACT_MBT_CLIENT_TRANSFER_BLOB: if (src->value.transfer_blob_status.input.unicast_addr_count && - src->value.transfer_blob_status.input.unicast_addr) { + src->value.transfer_blob_status.input.unicast_addr) { dst->value.transfer_blob_status.input.unicast_addr = bt_mesh_calloc(src->value.transfer_blob_status.input.unicast_addr_count * 2); if (dst->value.transfer_blob_status.input.unicast_addr) { memcpy(dst->value.transfer_blob_status.input.unicast_addr, src->value.transfer_blob_status.input.unicast_addr, @@ -172,7 +172,7 @@ static void btc_ble_mesh_blob_trans_client_copy_req_data(btc_msg_t *msg, void *p break; case BTC_BLE_MESH_ACT_MBT_CLIENT_DETERMINE_TRANSFER_STATUS: if (src->value.determine_transfer_status_status.input.unicast_addr_count && - src->value.determine_transfer_status_status.input.unicast_addr) { + src->value.determine_transfer_status_status.input.unicast_addr) { dst->value.determine_transfer_status_status.input.unicast_addr = bt_mesh_calloc(src->value.determine_transfer_status_status.input.unicast_addr_count * 2); if (dst->value.determine_transfer_status_status.input.unicast_addr) { memcpy(dst->value.determine_transfer_status_status.input.unicast_addr, src->value.determine_transfer_status_status.input.unicast_addr, @@ -185,7 +185,7 @@ static void btc_ble_mesh_blob_trans_client_copy_req_data(btc_msg_t *msg, void *p break; case BTC_BLE_MESH_ACT_MBT_CLIENT_CANCEL_TRANSFER: if (src->value.cancel_transfer_status.input.unicast_addr_count && - src->value.cancel_transfer_status.input.unicast_addr) { + src->value.cancel_transfer_status.input.unicast_addr) { dst->value.cancel_transfer_status.input.unicast_addr = bt_mesh_calloc(src->value.cancel_transfer_status.input.unicast_addr_count * 2); if (dst->value.cancel_transfer_status.input.unicast_addr) { memcpy(dst->value.cancel_transfer_status.input.unicast_addr, src->value.cancel_transfer_status.input.unicast_addr, @@ -482,7 +482,7 @@ void bt_mesh_mbt_server_cb_evt_to_btc(uint8_t event, uint8_t cb_event = 0; if (model == NULL || (ctx == NULL && - event != BTC_BLE_MESH_EVT_MBT_SERVER_BLOB_RECEIVE_TIMEOUT)) { + event != BTC_BLE_MESH_EVT_MBT_SERVER_BLOB_RECEIVE_TIMEOUT)) { BT_ERR("%s, Invalid parameter", __func__); return; } diff --git a/components/bt/esp_ble_mesh/v1.1/btc/btc_ble_mesh_odp_model.c b/components/bt/esp_ble_mesh/v1.1/btc/btc_ble_mesh_odp_model.c index 4f455926775..693b929227b 100644 --- a/components/bt/esp_ble_mesh/v1.1/btc/btc_ble_mesh_odp_model.c +++ b/components/bt/esp_ble_mesh/v1.1/btc/btc_ble_mesh_odp_model.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2020-2023 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2020-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -40,8 +40,8 @@ void btc_ble_mesh_odp_client_arg_deep_copy(btc_msg_t *msg, void *p_dest, void *p switch (msg->act) { case BTC_BLE_MESH_ACT_ODP_CLIENT_SEND: - dst->odp_send.params= NULL; - dst->odp_send.msg= NULL; + dst->odp_send.params = NULL; + dst->odp_send.msg = NULL; dst->odp_send.params = bt_mesh_calloc(sizeof(esp_ble_mesh_client_common_param_t)); if (!dst->odp_send.params) { @@ -217,8 +217,8 @@ void btc_ble_mesh_odp_client_recv_pub_cb(uint32_t opcode, } bt_mesh_odp_client_cb_evt_to_btc(opcode, - BTC_BLE_MESH_EVT_ODP_CLIENT_RECV_PUB, - model, ctx, buf->data, buf->len); + BTC_BLE_MESH_EVT_ODP_CLIENT_RECV_PUB, + model, ctx, buf->data, buf->len); } static int btc_ble_mesh_odp_client_send(esp_ble_mesh_client_common_param_t *params, @@ -266,7 +266,7 @@ void btc_ble_mesh_odp_client_call_handler(btc_msg_t *msg) cb.send.err_code = btc_ble_mesh_odp_client_send(arg->odp_send.params, arg->odp_send.msg); btc_ble_mesh_odp_client_cb(&cb, - ESP_BLE_MESH_ODP_CLIENT_SEND_COMP_EVT); + ESP_BLE_MESH_ODP_CLIENT_SEND_COMP_EVT); } btc_ble_mesh_odp_client_arg_deep_free(msg); diff --git a/components/bt/esp_ble_mesh/v1.1/btc/btc_ble_mesh_prb_model.c b/components/bt/esp_ble_mesh/v1.1/btc/btc_ble_mesh_prb_model.c index 48ce1cc606e..6598717bec9 100644 --- a/components/bt/esp_ble_mesh/v1.1/btc/btc_ble_mesh_prb_model.c +++ b/components/bt/esp_ble_mesh/v1.1/btc/btc_ble_mesh_prb_model.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2020-2023 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2020-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -222,8 +222,8 @@ void btc_ble_mesh_prb_client_recv_pub_cb(uint32_t opcode, } bt_mesh_prb_client_cb_evt_to_btc(opcode, - BTC_BLE_MESH_EVT_PRB_CLIENT_RECV_PUB, - model, ctx, buf->data, buf->len); + BTC_BLE_MESH_EVT_PRB_CLIENT_RECV_PUB, + model, ctx, buf->data, buf->len); } static int btc_ble_mesh_prb_client_send(esp_ble_mesh_client_common_param_t *params, @@ -264,7 +264,7 @@ static int btc_ble_mesh_prb_client_send(esp_ble_mesh_client_common_param_t *para case ESP_BLE_MESH_MODEL_OP_PRIV_NODE_IDENTITY_GET: return bt_mesh_private_node_identity_get(¶m, msg->priv_node_id_get.net_idx); case ESP_BLE_MESH_MODEL_OP_PRIV_NODE_IDENTITY_SET: - return bt_mesh_private_node_identity_set(¶m, msg->priv_node_id_set.net_idx , msg->priv_node_id_set.private_node_id); + return bt_mesh_private_node_identity_set(¶m, msg->priv_node_id_set.net_idx, msg->priv_node_id_set.private_node_id); default: BT_ERR("Invalid Private Beacon opcode 0x%04x", param.opcode); return -EINVAL; @@ -289,7 +289,7 @@ void btc_ble_mesh_prb_client_call_handler(btc_msg_t *msg) cb.send.err_code = btc_ble_mesh_prb_client_send(arg->prb_send.params, arg->prb_send.msg); btc_ble_mesh_prb_client_cb(&cb, - ESP_BLE_MESH_PRB_CLIENT_SEND_COMP_EVT); + ESP_BLE_MESH_PRB_CLIENT_SEND_COMP_EVT); break; default: break; @@ -333,7 +333,7 @@ static inline void btc_ble_mesh_prb_server_cb_to_app(esp_ble_mesh_prb_server_cb_ } static void btc_ble_mesh_prb_server_cb( - esp_ble_mesh_prb_server_cb_param_t *cb_params, uint8_t act) + esp_ble_mesh_prb_server_cb_param_t *cb_params, uint8_t act) { btc_msg_t msg = {0}; diff --git a/components/bt/esp_ble_mesh/v1.1/btc/btc_ble_mesh_rpr_model.c b/components/bt/esp_ble_mesh/v1.1/btc/btc_ble_mesh_rpr_model.c index 34e6929af74..7dea145b313 100644 --- a/components/bt/esp_ble_mesh/v1.1/btc/btc_ble_mesh_rpr_model.c +++ b/components/bt/esp_ble_mesh/v1.1/btc/btc_ble_mesh_rpr_model.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2020-2024 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2020-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -154,7 +154,7 @@ static void btc_ble_mesh_rpr_client_copy_req_data(btc_msg_t *msg, void *p_dest, * publish event. */ if (msg->act == ESP_BLE_MESH_RPR_CLIENT_RECV_PUB_EVT && - p_src_data->recv.params->opcode == ESP_BLE_MESH_MODEL_OP_RPR_EXT_SCAN_REPORT) { + p_src_data->recv.params->opcode == ESP_BLE_MESH_MODEL_OP_RPR_EXT_SCAN_REPORT) { if (p_src_data->recv.val.ext_scan_report.adv_structures) { length = p_src_data->recv.val.ext_scan_report.adv_structures->len; p_dest_data->recv.val.ext_scan_report.adv_structures = bt_mesh_alloc_buf(length); @@ -201,8 +201,8 @@ static void btc_ble_mesh_rpr_client_free_req_data(btc_msg_t *msg) case ESP_BLE_MESH_RPR_CLIENT_RECV_RSP_EVT: case ESP_BLE_MESH_RPR_CLIENT_RECV_PUB_EVT: if (arg->recv.params && - msg->act == ESP_BLE_MESH_RPR_CLIENT_RECV_PUB_EVT && - arg->recv.params->opcode == ESP_BLE_MESH_MODEL_OP_RPR_EXT_SCAN_REPORT) { + msg->act == ESP_BLE_MESH_RPR_CLIENT_RECV_PUB_EVT && + arg->recv.params->opcode == ESP_BLE_MESH_MODEL_OP_RPR_EXT_SCAN_REPORT) { bt_mesh_free_buf(arg->recv.val.ext_scan_report.adv_structures); } if (arg->recv.params) { @@ -242,9 +242,9 @@ void bt_mesh_rpr_client_cb_evt_to_btc(uint32_t opcode, uint8_t event, uint8_t act = 0; if (model == NULL || ctx == NULL || - ((event == BTC_BLE_MESH_EVT_RPR_CLIENT_RECV_RSP || - event == BTC_BLE_MESH_EVT_RPR_CLIENT_RECV_PUB) && - (len > sizeof(cb_params.recv.val)))) { + ((event == BTC_BLE_MESH_EVT_RPR_CLIENT_RECV_RSP || + event == BTC_BLE_MESH_EVT_RPR_CLIENT_RECV_PUB) && + (len > sizeof(cb_params.recv.val)))) { BT_ERR("%s, Invalid parameter", __func__); return; } @@ -467,7 +467,7 @@ void btc_ble_mesh_rpr_client_cb_handler(btc_msg_t *msg) #if CONFIG_BLE_MESH_RPR_SRV /* Remote Provisioning Server model related functions */ -extern int bt_mesh_rpr_srv_scan_set_dev_uuid_match(uint8_t offset, uint8_t length,const uint8_t *match); +extern int bt_mesh_rpr_srv_scan_set_dev_uuid_match(uint8_t offset, uint8_t length, const uint8_t *match); void btc_ble_mesh_rpr_server_arg_deep_copy(btc_msg_t *msg, void *p_dest, void *p_src) { @@ -479,7 +479,7 @@ void btc_ble_mesh_rpr_server_arg_deep_copy(btc_msg_t *msg, void *p_dest, void *p return; } - switch(msg->act) { + switch (msg->act) { case BTC_BLE_MESH_ACT_RPR_SRV_SET_UUID_MATCH: dst->set_uuid_match.match_val = bt_mesh_calloc(src->set_uuid_match.match_len); if (dst->set_uuid_match.match_val) { @@ -509,7 +509,7 @@ void btc_ble_mesh_rpr_server_arg_deep_free(btc_msg_t *msg) arg = (btc_ble_mesh_rpr_server_args_t *)msg->arg; - switch(msg->act) { + switch (msg->act) { case BTC_BLE_MESH_ACT_RPR_SRV_SET_UUID_MATCH: if (arg->set_uuid_match.match_val) { bt_mesh_free(arg->set_uuid_match.match_val); diff --git a/components/bt/esp_ble_mesh/v1.1/btc/btc_ble_mesh_sar_model.c b/components/bt/esp_ble_mesh/v1.1/btc/btc_ble_mesh_sar_model.c index 06cdbd311f5..065b34bc962 100644 --- a/components/bt/esp_ble_mesh/v1.1/btc/btc_ble_mesh_sar_model.c +++ b/components/bt/esp_ble_mesh/v1.1/btc/btc_ble_mesh_sar_model.c @@ -235,7 +235,7 @@ static int btc_ble_mesh_sar_client_send(esp_ble_mesh_client_common_param_t *para } if ((params->opcode == ESP_BLE_MESH_MODEL_OP_SAR_TRANSMITTER_SET || - params->opcode == ESP_BLE_MESH_MODEL_OP_SAR_RECEIVER_SET) && msg == NULL) { + params->opcode == ESP_BLE_MESH_MODEL_OP_SAR_RECEIVER_SET) && msg == NULL) { BT_ERR("Invalid SAR Config message, opcode 0x%04x", params->opcode); return -EINVAL; } diff --git a/components/bt/esp_ble_mesh/v1.1/btc/btc_ble_mesh_srpl_model.c b/components/bt/esp_ble_mesh/v1.1/btc/btc_ble_mesh_srpl_model.c index 11813110160..1ee349d6f1a 100644 --- a/components/bt/esp_ble_mesh/v1.1/btc/btc_ble_mesh_srpl_model.c +++ b/components/bt/esp_ble_mesh/v1.1/btc/btc_ble_mesh_srpl_model.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2020-2023 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2020-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -214,8 +214,8 @@ void btc_ble_mesh_srpl_client_recv_pub_cb(uint32_t opcode, } bt_mesh_srpl_client_cb_evt_to_btc(opcode, - BTC_BLE_MESH_EVT_SRPL_CLIENT_RECV_PUB, - model, ctx, buf->data, buf->len); + BTC_BLE_MESH_EVT_SRPL_CLIENT_RECV_PUB, + model, ctx, buf->data, buf->len); } static int btc_ble_mesh_srpl_client_send(esp_ble_mesh_client_common_param_t *params, @@ -258,7 +258,7 @@ void btc_ble_mesh_srpl_client_call_handler(btc_msg_t *msg) cb.send.err_code = btc_ble_mesh_srpl_client_send(arg->srpl_send.params, arg->srpl_send.msg); btc_ble_mesh_srpl_client_cb(&cb, - ESP_BLE_MESH_SRPL_CLIENT_SEND_COMP_EVT); + ESP_BLE_MESH_SRPL_CLIENT_SEND_COMP_EVT); break; default: break; diff --git a/components/bt/host/bluedroid/CMakeLists.txt b/components/bt/host/bluedroid/CMakeLists.txt new file mode 100644 index 00000000000..0148e315864 --- /dev/null +++ b/components/bt/host/bluedroid/CMakeLists.txt @@ -0,0 +1,409 @@ +# TODO: These warnings should be resolved in the Bluedroid code +function(set_bluedroid_host_compile_flags) + if(NOT CONFIG_BT_BLUEDROID_ENABLED) + return() + endif() + + set_source_files_properties( + "${CMAKE_CURRENT_FUNCTION_LIST_DIR}/bta/gatt/bta_gattc_act.c" + "${CMAKE_CURRENT_FUNCTION_LIST_DIR}/bta/gatt/bta_gattc_cache.c" + "${CMAKE_CURRENT_FUNCTION_LIST_DIR}/btc/profile/std/gatt/btc_gatt_util.c" + "${CMAKE_CURRENT_FUNCTION_LIST_DIR}/btc/profile/std/gatt/btc_gatts.c" + PROPERTIES COMPILE_FLAGS -Wno-address-of-packed-member + ) + + if(NOT CMAKE_BUILD_EARLY_EXPANSION) + set(jump_table_opts "-fjump-tables") + if(NOT (CMAKE_C_COMPILER_ID MATCHES "Clang") ) + set(jump_table_opts "${jump_table_opts} -ftree-switch-conversion") + endif() + set_source_files_properties("${CMAKE_CURRENT_FUNCTION_LIST_DIR}/bta/hf_ag/bta_ag_cmd.c" + "${CMAKE_CURRENT_FUNCTION_LIST_DIR}/btc/profile/std/gap/btc_gap_ble.c" + PROPERTIES COMPILE_FLAGS "${jump_table_opts}") + endif() + + if(CMAKE_C_COMPILER_ID MATCHES "GNU" AND CMAKE_C_COMPILER_VERSION VERSION_GREATER 15.0) + set_source_files_properties("${CMAKE_CURRENT_FUNCTION_LIST_DIR}/device/controller.c" + PROPERTIES COMPILE_FLAGS "-Wno-unterminated-string-initialization") + endif() +endfunction() + +set(bluedroid_host_srcs "" PARENT_SCOPE) +set(bluedroid_host_include_dirs "" PARENT_SCOPE) +set(bluedroid_host_priv_include_dirs "" PARENT_SCOPE) + +# API headers that are used in the docs are also compiled +# even if CONFIG_BT_ENABLED=n as long as CONFIG_IDF_DOC_BUILD=y +if(CONFIG_IDF_DOC_BUILD) + set(bluedroid_host_include_dirs + "${CMAKE_CURRENT_LIST_DIR}/api/include/api" + PARENT_SCOPE + ) + return() +endif() + +if(NOT CONFIG_BT_BLUEDROID_ENABLED) + return() +endif() + +list(APPEND bluedroid_host_srcs + "${CMAKE_CURRENT_LIST_DIR}/api/esp_a2dp_api.c" + "${CMAKE_CURRENT_LIST_DIR}/api/esp_avrc_api.c" + "${CMAKE_CURRENT_LIST_DIR}/api/esp_bluedroid_hci.c" + "${CMAKE_CURRENT_LIST_DIR}/api/esp_bt_device.c" + "${CMAKE_CURRENT_LIST_DIR}/api/esp_bt_main.c" + "${CMAKE_CURRENT_LIST_DIR}/api/esp_gap_ble_api.c" + "${CMAKE_CURRENT_LIST_DIR}/api/esp_gap_bt_api.c" + "${CMAKE_CURRENT_LIST_DIR}/api/esp_gatt_common_api.c" + "${CMAKE_CURRENT_LIST_DIR}/api/esp_gattc_api.c" + "${CMAKE_CURRENT_LIST_DIR}/api/esp_gatts_api.c" + "${CMAKE_CURRENT_LIST_DIR}/api/esp_hidd_api.c" + "${CMAKE_CURRENT_LIST_DIR}/api/esp_hidh_api.c" + "${CMAKE_CURRENT_LIST_DIR}/api/esp_hf_ag_api.c" + "${CMAKE_CURRENT_LIST_DIR}/api/esp_hf_client_api.c" + "${CMAKE_CURRENT_LIST_DIR}/api/esp_spp_api.c" + "${CMAKE_CURRENT_LIST_DIR}/api/esp_sdp_api.c" + "${CMAKE_CURRENT_LIST_DIR}/api/esp_l2cap_bt_api.c" + "${CMAKE_CURRENT_LIST_DIR}/api/esp_pbac_api.c" + "${CMAKE_CURRENT_LIST_DIR}/bta/ar/bta_ar.c" + "${CMAKE_CURRENT_LIST_DIR}/bta/av/bta_av_aact.c" + "${CMAKE_CURRENT_LIST_DIR}/bta/av/bta_av_act.c" + "${CMAKE_CURRENT_LIST_DIR}/bta/av/bta_av_api.c" + "${CMAKE_CURRENT_LIST_DIR}/bta/av/bta_av_ca_act.c" + "${CMAKE_CURRENT_LIST_DIR}/bta/av/bta_av_ca_sm.c" + "${CMAKE_CURRENT_LIST_DIR}/bta/av/bta_av_cfg.c" + "${CMAKE_CURRENT_LIST_DIR}/bta/av/bta_av_ci.c" + "${CMAKE_CURRENT_LIST_DIR}/bta/av/bta_av_main.c" + "${CMAKE_CURRENT_LIST_DIR}/bta/av/bta_av_sbc.c" + # "${CMAKE_CURRENT_LIST_DIR}/bta/av/bta_av_m24.c" + "${CMAKE_CURRENT_LIST_DIR}/bta/av/bta_av_ssm.c" + "${CMAKE_CURRENT_LIST_DIR}/bta/dm/bta_dm_act.c" + "${CMAKE_CURRENT_LIST_DIR}/bta/dm/bta_dm_api.c" + "${CMAKE_CURRENT_LIST_DIR}/bta/dm/bta_dm_cfg.c" + "${CMAKE_CURRENT_LIST_DIR}/bta/dm/bta_dm_ci.c" + "${CMAKE_CURRENT_LIST_DIR}/bta/dm/bta_dm_co.c" + "${CMAKE_CURRENT_LIST_DIR}/bta/dm/bta_dm_main.c" + "${CMAKE_CURRENT_LIST_DIR}/bta/dm/bta_dm_pm.c" + "${CMAKE_CURRENT_LIST_DIR}/bta/dm/bta_dm_sco.c" + "${CMAKE_CURRENT_LIST_DIR}/bta/dm/bta_dm_qos.c" + "${CMAKE_CURRENT_LIST_DIR}/bta/gatt/bta_gatt_common.c" + "${CMAKE_CURRENT_LIST_DIR}/bta/gatt/bta_gattc_act.c" + "${CMAKE_CURRENT_LIST_DIR}/bta/gatt/bta_gattc_api.c" + "${CMAKE_CURRENT_LIST_DIR}/bta/gatt/bta_gattc_cache.c" + "${CMAKE_CURRENT_LIST_DIR}/bta/gatt/bta_gattc_ci.c" + "${CMAKE_CURRENT_LIST_DIR}/bta/gatt/bta_gattc_co.c" + "${CMAKE_CURRENT_LIST_DIR}/bta/gatt/bta_gattc_main.c" + "${CMAKE_CURRENT_LIST_DIR}/bta/gatt/bta_gattc_utils.c" + "${CMAKE_CURRENT_LIST_DIR}/bta/gatt/bta_gatts_act.c" + "${CMAKE_CURRENT_LIST_DIR}/bta/gatt/bta_gatts_api.c" + "${CMAKE_CURRENT_LIST_DIR}/bta/gatt/bta_gatts_co.c" + "${CMAKE_CURRENT_LIST_DIR}/bta/gatt/bta_gatts_main.c" + "${CMAKE_CURRENT_LIST_DIR}/bta/gatt/bta_gatts_utils.c" + "${CMAKE_CURRENT_LIST_DIR}/bta/hd/bta_hd_api.c" + "${CMAKE_CURRENT_LIST_DIR}/bta/hd/bta_hd_act.c" + "${CMAKE_CURRENT_LIST_DIR}/bta/hd/bta_hd_main.c" + "${CMAKE_CURRENT_LIST_DIR}/bta/hh/bta_hh_act.c" + "${CMAKE_CURRENT_LIST_DIR}/bta/hh/bta_hh_api.c" + "${CMAKE_CURRENT_LIST_DIR}/bta/hh/bta_hh_cfg.c" + "${CMAKE_CURRENT_LIST_DIR}/bta/hh/bta_hh_le.c" + "${CMAKE_CURRENT_LIST_DIR}/bta/hh/bta_hh_main.c" + "${CMAKE_CURRENT_LIST_DIR}/bta/hh/bta_hh_utils.c" + "${CMAKE_CURRENT_LIST_DIR}/bta/jv/bta_jv_act.c" + "${CMAKE_CURRENT_LIST_DIR}/bta/jv/bta_jv_api.c" + "${CMAKE_CURRENT_LIST_DIR}/bta/jv/bta_jv_cfg.c" + "${CMAKE_CURRENT_LIST_DIR}/bta/jv/bta_jv_main.c" + "${CMAKE_CURRENT_LIST_DIR}/bta/hf_ag/bta_ag_act.c" + "${CMAKE_CURRENT_LIST_DIR}/bta/hf_ag/bta_ag_api.c" + "${CMAKE_CURRENT_LIST_DIR}/bta/hf_ag/bta_ag_at.c" + "${CMAKE_CURRENT_LIST_DIR}/bta/hf_ag/bta_ag_cfg.c" + "${CMAKE_CURRENT_LIST_DIR}/bta/hf_ag/bta_ag_cmd.c" + "${CMAKE_CURRENT_LIST_DIR}/bta/hf_ag/bta_ag_main.c" + "${CMAKE_CURRENT_LIST_DIR}/bta/hf_ag/bta_ag_rfc.c" + "${CMAKE_CURRENT_LIST_DIR}/bta/hf_ag/bta_ag_sco.c" + "${CMAKE_CURRENT_LIST_DIR}/bta/hf_ag/bta_ag_sdp.c" + "${CMAKE_CURRENT_LIST_DIR}/bta/hf_client/bta_hf_client_act.c" + "${CMAKE_CURRENT_LIST_DIR}/bta/hf_client/bta_hf_client_api.c" + "${CMAKE_CURRENT_LIST_DIR}/bta/hf_client/bta_hf_client_at.c" + "${CMAKE_CURRENT_LIST_DIR}/bta/hf_client/bta_hf_client_cmd.c" + "${CMAKE_CURRENT_LIST_DIR}/bta/hf_client/bta_hf_client_main.c" + "${CMAKE_CURRENT_LIST_DIR}/bta/hf_client/bta_hf_client_rfc.c" + "${CMAKE_CURRENT_LIST_DIR}/bta/hf_client/bta_hf_client_sco.c" + "${CMAKE_CURRENT_LIST_DIR}/bta/hf_client/bta_hf_client_sdp.c" + "${CMAKE_CURRENT_LIST_DIR}/bta/pba/bta_pba_client_act.c" + "${CMAKE_CURRENT_LIST_DIR}/bta/pba/bta_pba_client_api.c" + "${CMAKE_CURRENT_LIST_DIR}/bta/pba/bta_pba_client_main.c" + "${CMAKE_CURRENT_LIST_DIR}/bta/pba/bta_pba_client_sdp.c" + "${CMAKE_CURRENT_LIST_DIR}/bta/sdp/bta_sdp.c" + "${CMAKE_CURRENT_LIST_DIR}/bta/sdp/bta_sdp_act.c" + "${CMAKE_CURRENT_LIST_DIR}/bta/sdp/bta_sdp_api.c" + "${CMAKE_CURRENT_LIST_DIR}/bta/sdp/bta_sdp_cfg.c" + "${CMAKE_CURRENT_LIST_DIR}/bta/sys/bta_sys_conn.c" + "${CMAKE_CURRENT_LIST_DIR}/bta/sys/bta_sys_main.c" + "${CMAKE_CURRENT_LIST_DIR}/bta/sys/utl.c" + "${CMAKE_CURRENT_LIST_DIR}/btc/core/btc_ble_storage.c" + "${CMAKE_CURRENT_LIST_DIR}/btc/core/btc_config.c" + "${CMAKE_CURRENT_LIST_DIR}/btc/core/btc_dev.c" + "${CMAKE_CURRENT_LIST_DIR}/btc/core/btc_dm.c" + "${CMAKE_CURRENT_LIST_DIR}/btc/core/btc_main.c" + "${CMAKE_CURRENT_LIST_DIR}/btc/core/btc_profile_queue.c" + "${CMAKE_CURRENT_LIST_DIR}/btc/core/btc_sec.c" + "${CMAKE_CURRENT_LIST_DIR}/btc/core/btc_sm.c" + "${CMAKE_CURRENT_LIST_DIR}/btc/core/btc_storage.c" + "${CMAKE_CURRENT_LIST_DIR}/btc/core/btc_util.c" + "${CMAKE_CURRENT_LIST_DIR}/btc/profile/std/a2dp/bta_av_co.c" + "${CMAKE_CURRENT_LIST_DIR}/btc/profile/std/a2dp/btc_a2dp.c" + "${CMAKE_CURRENT_LIST_DIR}/btc/profile/std/a2dp/btc_a2dp_control.c" + "${CMAKE_CURRENT_LIST_DIR}/btc/profile/std/a2dp/btc_a2dp_sink.c" + "${CMAKE_CURRENT_LIST_DIR}/btc/profile/std/a2dp/btc_a2dp_sink_ext_coedc.c" + # "${CMAKE_CURRENT_LIST_DIR}/btc/profile/std/a2dp/btc_a2dp_latm_raw.c" + "${CMAKE_CURRENT_LIST_DIR}/btc/profile/std/a2dp/btc_a2dp_source.c" + "${CMAKE_CURRENT_LIST_DIR}/btc/profile/std/a2dp/btc_a2dp_source_ext_codec.c" + "${CMAKE_CURRENT_LIST_DIR}/btc/profile/std/a2dp/btc_av.c" + "${CMAKE_CURRENT_LIST_DIR}/btc/profile/std/avrc/btc_avrc.c" + "${CMAKE_CURRENT_LIST_DIR}/btc/profile/std/avrc/bta_avrc_co.c" + "${CMAKE_CURRENT_LIST_DIR}/btc/profile/std/hf_ag/bta_ag_co.c" + "${CMAKE_CURRENT_LIST_DIR}/btc/profile/std/hf_ag/btc_hf_ag.c" + "${CMAKE_CURRENT_LIST_DIR}/btc/profile/std/hf_client/btc_hf_client.c" + "${CMAKE_CURRENT_LIST_DIR}/btc/profile/std/hf_client/bta_hf_client_co.c" + "${CMAKE_CURRENT_LIST_DIR}/btc/profile/std/hid/btc_hd.c" + "${CMAKE_CURRENT_LIST_DIR}/btc/profile/std/hid/btc_hh.c" + "${CMAKE_CURRENT_LIST_DIR}/btc/profile/std/hid/bta_hh_co.c" + "${CMAKE_CURRENT_LIST_DIR}/btc/profile/std/gap/btc_gap_ble.c" + "${CMAKE_CURRENT_LIST_DIR}/btc/profile/std/gap/btc_gap_bt.c" + "${CMAKE_CURRENT_LIST_DIR}/btc/profile/std/gap/bta_gap_bt_co.c" + "${CMAKE_CURRENT_LIST_DIR}/btc/profile/std/gatt/btc_gatt_common.c" + "${CMAKE_CURRENT_LIST_DIR}/btc/profile/std/gatt/btc_gatt_util.c" + "${CMAKE_CURRENT_LIST_DIR}/btc/profile/std/gatt/btc_gattc.c" + "${CMAKE_CURRENT_LIST_DIR}/btc/profile/std/gatt/btc_gatts.c" + "${CMAKE_CURRENT_LIST_DIR}/btc/profile/std/spp/btc_spp.c" + "${CMAKE_CURRENT_LIST_DIR}/btc/profile/std/sdp/btc_sdp.c" + "${CMAKE_CURRENT_LIST_DIR}/btc/profile/std/l2cap/btc_l2cap.c" + "${CMAKE_CURRENT_LIST_DIR}/btc/profile/std/pba/btc_pba_client.c" + "${CMAKE_CURRENT_LIST_DIR}/device/bdaddr.c" + "${CMAKE_CURRENT_LIST_DIR}/device/controller.c" + "${CMAKE_CURRENT_LIST_DIR}/device/interop.c" + "${CMAKE_CURRENT_LIST_DIR}/hci/hci_audio.c" + "${CMAKE_CURRENT_LIST_DIR}/hci/hci_hal_h4.c" + "${CMAKE_CURRENT_LIST_DIR}/hci/hci_layer.c" + "${CMAKE_CURRENT_LIST_DIR}/hci/hci_packet_factory.c" + "${CMAKE_CURRENT_LIST_DIR}/hci/hci_packet_parser.c" + "${CMAKE_CURRENT_LIST_DIR}/hci/packet_fragmenter.c" + "${CMAKE_CURRENT_LIST_DIR}/main/bte_init.c" + "${CMAKE_CURRENT_LIST_DIR}/main/bte_main.c" + "${CMAKE_CURRENT_LIST_DIR}/stack/a2dp/a2d_api.c" + "${CMAKE_CURRENT_LIST_DIR}/stack/a2dp/a2d_sbc.c" + # "${CMAKE_CURRENT_LIST_DIR}/stack/a2dp/a2d_m24.c" + "${CMAKE_CURRENT_LIST_DIR}/stack/avct/avct_api.c" + "${CMAKE_CURRENT_LIST_DIR}/stack/avct/avct_ccb.c" + "${CMAKE_CURRENT_LIST_DIR}/stack/avct/avct_l2c.c" + "${CMAKE_CURRENT_LIST_DIR}/stack/avct/avct_lcb.c" + "${CMAKE_CURRENT_LIST_DIR}/stack/avct/avct_lcb_act.c" + "${CMAKE_CURRENT_LIST_DIR}/stack/avdt/avdt_ad.c" + "${CMAKE_CURRENT_LIST_DIR}/stack/avdt/avdt_api.c" + "${CMAKE_CURRENT_LIST_DIR}/stack/avdt/avdt_ccb.c" + "${CMAKE_CURRENT_LIST_DIR}/stack/avdt/avdt_ccb_act.c" + "${CMAKE_CURRENT_LIST_DIR}/stack/avdt/avdt_l2c.c" + "${CMAKE_CURRENT_LIST_DIR}/stack/avdt/avdt_msg.c" + "${CMAKE_CURRENT_LIST_DIR}/stack/avdt/avdt_scb.c" + "${CMAKE_CURRENT_LIST_DIR}/stack/avdt/avdt_scb_act.c" + "${CMAKE_CURRENT_LIST_DIR}/stack/avrc/avrc_api.c" + "${CMAKE_CURRENT_LIST_DIR}/stack/avrc/avrc_bld_ct.c" + "${CMAKE_CURRENT_LIST_DIR}/stack/avrc/avrc_bld_tg.c" + "${CMAKE_CURRENT_LIST_DIR}/stack/avrc/avrc_opt.c" + "${CMAKE_CURRENT_LIST_DIR}/stack/avrc/avrc_pars_ct.c" + "${CMAKE_CURRENT_LIST_DIR}/stack/avrc/avrc_pars_tg.c" + "${CMAKE_CURRENT_LIST_DIR}/stack/avrc/avrc_sdp.c" + "${CMAKE_CURRENT_LIST_DIR}/stack/avrc/avrc_utils.c" + "${CMAKE_CURRENT_LIST_DIR}/stack/hid/hidd_api.c" + "${CMAKE_CURRENT_LIST_DIR}/stack/hid/hidd_conn.c" + "${CMAKE_CURRENT_LIST_DIR}/stack/hid/hidh_api.c" + "${CMAKE_CURRENT_LIST_DIR}/stack/hid/hidh_conn.c" + "${CMAKE_CURRENT_LIST_DIR}/stack/btm/btm_acl.c" + "${CMAKE_CURRENT_LIST_DIR}/stack/btm/btm_ble.c" + "${CMAKE_CURRENT_LIST_DIR}/stack/btm/btm_ble_addr.c" + "${CMAKE_CURRENT_LIST_DIR}/stack/btm/btm_ble_adv_filter.c" + "${CMAKE_CURRENT_LIST_DIR}/stack/btm/btm_ble_batchscan.c" + "${CMAKE_CURRENT_LIST_DIR}/stack/btm/btm_ble_bgconn.c" + "${CMAKE_CURRENT_LIST_DIR}/stack/btm/btm_ble_cont_energy.c" + "${CMAKE_CURRENT_LIST_DIR}/stack/btm/btm_ble_gap.c" + "${CMAKE_CURRENT_LIST_DIR}/stack/btm/btm_ble_5_gap.c" + "${CMAKE_CURRENT_LIST_DIR}/stack/btm/btm_ble_multi_adv.c" + "${CMAKE_CURRENT_LIST_DIR}/stack/btm/btm_ble_privacy.c" + "${CMAKE_CURRENT_LIST_DIR}/stack/btm/btm_dev.c" + "${CMAKE_CURRENT_LIST_DIR}/stack/btm/btm_devctl.c" + "${CMAKE_CURRENT_LIST_DIR}/stack/btm/btm_inq.c" + "${CMAKE_CURRENT_LIST_DIR}/stack/btm/btm_bredr_pwr_ctrl.c" + "${CMAKE_CURRENT_LIST_DIR}/stack/btm/btm_main.c" + "${CMAKE_CURRENT_LIST_DIR}/stack/btm/btm_pm.c" + "${CMAKE_CURRENT_LIST_DIR}/stack/btm/btm_sco.c" + "${CMAKE_CURRENT_LIST_DIR}/stack/btm/btm_sec.c" + "${CMAKE_CURRENT_LIST_DIR}/stack/btu/btu_hcif.c" + "${CMAKE_CURRENT_LIST_DIR}/stack/btu/btu_init.c" + "${CMAKE_CURRENT_LIST_DIR}/stack/btu/btu_task.c" + "${CMAKE_CURRENT_LIST_DIR}/stack/gap/gap_api.c" + "${CMAKE_CURRENT_LIST_DIR}/stack/gap/gap_ble.c" + "${CMAKE_CURRENT_LIST_DIR}/stack/gap/gap_conn.c" + "${CMAKE_CURRENT_LIST_DIR}/stack/gap/gap_utils.c" + "${CMAKE_CURRENT_LIST_DIR}/stack/gatt/att_protocol.c" + "${CMAKE_CURRENT_LIST_DIR}/stack/gatt/gatt_api.c" + "${CMAKE_CURRENT_LIST_DIR}/stack/gatt/gatt_attr.c" + "${CMAKE_CURRENT_LIST_DIR}/stack/gatt/gatt_auth.c" + "${CMAKE_CURRENT_LIST_DIR}/stack/gatt/gatt_cl.c" + "${CMAKE_CURRENT_LIST_DIR}/stack/gatt/gatt_db.c" + "${CMAKE_CURRENT_LIST_DIR}/stack/gatt/gatt_main.c" + "${CMAKE_CURRENT_LIST_DIR}/stack/gatt/gatt_sr.c" + "${CMAKE_CURRENT_LIST_DIR}/stack/gatt/gatt_sr_hash.c" + "${CMAKE_CURRENT_LIST_DIR}/stack/gatt/gatt_utils.c" + "${CMAKE_CURRENT_LIST_DIR}/stack/goep/goepc_api.c" + "${CMAKE_CURRENT_LIST_DIR}/stack/goep/goepc_main.c" + "${CMAKE_CURRENT_LIST_DIR}/stack/hcic/hciblecmds.c" + "${CMAKE_CURRENT_LIST_DIR}/stack/hcic/hcicmds.c" + "${CMAKE_CURRENT_LIST_DIR}/stack/l2cap/l2c_api.c" + "${CMAKE_CURRENT_LIST_DIR}/stack/l2cap/l2c_ble.c" + "${CMAKE_CURRENT_LIST_DIR}/stack/l2cap/l2c_csm.c" + "${CMAKE_CURRENT_LIST_DIR}/stack/l2cap/l2c_fcr.c" + "${CMAKE_CURRENT_LIST_DIR}/stack/l2cap/l2c_link.c" + "${CMAKE_CURRENT_LIST_DIR}/stack/l2cap/l2c_main.c" + "${CMAKE_CURRENT_LIST_DIR}/stack/l2cap/l2c_ucd.c" + "${CMAKE_CURRENT_LIST_DIR}/stack/l2cap/l2c_utils.c" + "${CMAKE_CURRENT_LIST_DIR}/stack/l2cap/l2cap_client.c" + "${CMAKE_CURRENT_LIST_DIR}/stack/obex/obex_api.c" + "${CMAKE_CURRENT_LIST_DIR}/stack/obex/obex_main.c" + "${CMAKE_CURRENT_LIST_DIR}/stack/obex/obex_tl_l2cap.c" + "${CMAKE_CURRENT_LIST_DIR}/stack/obex/obex_tl_rfcomm.c" + "${CMAKE_CURRENT_LIST_DIR}/stack/rfcomm/port_api.c" + "${CMAKE_CURRENT_LIST_DIR}/stack/rfcomm/port_rfc.c" + "${CMAKE_CURRENT_LIST_DIR}/stack/rfcomm/port_utils.c" + "${CMAKE_CURRENT_LIST_DIR}/stack/rfcomm/rfc_l2cap_if.c" + "${CMAKE_CURRENT_LIST_DIR}/stack/rfcomm/rfc_mx_fsm.c" + "${CMAKE_CURRENT_LIST_DIR}/stack/rfcomm/rfc_port_fsm.c" + "${CMAKE_CURRENT_LIST_DIR}/stack/rfcomm/rfc_port_if.c" + "${CMAKE_CURRENT_LIST_DIR}/stack/rfcomm/rfc_ts_frames.c" + "${CMAKE_CURRENT_LIST_DIR}/stack/rfcomm/rfc_utils.c" + "${CMAKE_CURRENT_LIST_DIR}/stack/sdp/sdp_api.c" + "${CMAKE_CURRENT_LIST_DIR}/stack/sdp/sdp_db.c" + "${CMAKE_CURRENT_LIST_DIR}/stack/sdp/sdp_discovery.c" + "${CMAKE_CURRENT_LIST_DIR}/stack/sdp/sdp_main.c" + "${CMAKE_CURRENT_LIST_DIR}/stack/sdp/sdp_server.c" + "${CMAKE_CURRENT_LIST_DIR}/stack/sdp/sdp_utils.c" + "${CMAKE_CURRENT_LIST_DIR}/stack/smp/aes.c" + "${CMAKE_CURRENT_LIST_DIR}/stack/smp/p_256_curvepara.c" + "${CMAKE_CURRENT_LIST_DIR}/stack/smp/p_256_ecc_pp.c" + "${CMAKE_CURRENT_LIST_DIR}/stack/smp/p_256_multprecision.c" + "${CMAKE_CURRENT_LIST_DIR}/stack/smp/smp_act.c" + "${CMAKE_CURRENT_LIST_DIR}/stack/smp/smp_api.c" + "${CMAKE_CURRENT_LIST_DIR}/stack/smp/smp_br_main.c" + "${CMAKE_CURRENT_LIST_DIR}/stack/smp/smp_cmac.c" + "${CMAKE_CURRENT_LIST_DIR}/stack/smp/smp_keys.c" + "${CMAKE_CURRENT_LIST_DIR}/stack/smp/smp_l2c.c" + "${CMAKE_CURRENT_LIST_DIR}/stack/smp/smp_main.c" + "${CMAKE_CURRENT_LIST_DIR}/stack/smp/smp_utils.c" + "${CMAKE_CURRENT_LIST_DIR}/config/stack_config.c" + + # TODO: Add this file in the blufi cmake file + "${CMAKE_CURRENT_LIST_DIR}/../../common/btc/profile/esp/blufi/bluedroid_host/esp_blufi.c" +) + +if(CONFIG_BT_BLE_FEAT_ISO_EN) + list(APPEND bluedroid_host_srcs + "${CMAKE_CURRENT_LIST_DIR}/stack/btm/btm_ble_iso.c" + "${CMAKE_CURRENT_LIST_DIR}/btc/profile/std/iso/btc_iso_ble.c" + "${CMAKE_CURRENT_LIST_DIR}/api/esp_ble_iso_api.c" + "${CMAKE_CURRENT_LIST_DIR}/hci/ble_hci_iso.c" + ) +endif() + +if(CONFIG_BT_BLE_FEAT_CTE_EN) + list(APPEND bluedroid_host_srcs + "${CMAKE_CURRENT_LIST_DIR}/stack/btm/btm_ble_cte.c" + "${CMAKE_CURRENT_LIST_DIR}/btc/profile/std/cte/btc_ble_cte.c" + "${CMAKE_CURRENT_LIST_DIR}/api/esp_ble_cte_api.c" + ) +endif() + +# TODO: Added this file in the ble mesh cmake file +if(CONFIG_BLE_MESH) + list(APPEND bluedroid_host_srcs "${CMAKE_CURRENT_LIST_DIR}/../../esp_ble_mesh/core/bluedroid_host/adapter.c") +endif() + +list(APPEND bluedroid_host_include_dirs + ${CMAKE_CURRENT_LIST_DIR}/api/include/api +) + +list(APPEND bluedroid_host_priv_include_dirs + ${CMAKE_CURRENT_LIST_DIR}/bta/include + ${CMAKE_CURRENT_LIST_DIR}/bta/ar/include + ${CMAKE_CURRENT_LIST_DIR}/bta/av/include + ${CMAKE_CURRENT_LIST_DIR}/bta/dm/include + ${CMAKE_CURRENT_LIST_DIR}/bta/gatt/include + ${CMAKE_CURRENT_LIST_DIR}/bta/hf_ag/include + ${CMAKE_CURRENT_LIST_DIR}/bta/hf_client/include + ${CMAKE_CURRENT_LIST_DIR}/bta/hd/include + ${CMAKE_CURRENT_LIST_DIR}/bta/hh/include + ${CMAKE_CURRENT_LIST_DIR}/bta/jv/include + ${CMAKE_CURRENT_LIST_DIR}/bta/pba/include + ${CMAKE_CURRENT_LIST_DIR}/bta/sdp/include + ${CMAKE_CURRENT_LIST_DIR}/bta/sys/include + ${CMAKE_CURRENT_LIST_DIR}/device/include + ${CMAKE_CURRENT_LIST_DIR}/hci/include + ${CMAKE_CURRENT_LIST_DIR}/btc/profile/esp/include + ${CMAKE_CURRENT_LIST_DIR}/btc/profile/std/a2dp/include + ${CMAKE_CURRENT_LIST_DIR}/btc/profile/std/include + ${CMAKE_CURRENT_LIST_DIR}/btc/include + ${CMAKE_CURRENT_LIST_DIR}/stack/btm/include + ${CMAKE_CURRENT_LIST_DIR}/stack/gap/include + ${CMAKE_CURRENT_LIST_DIR}/stack/gatt/include + ${CMAKE_CURRENT_LIST_DIR}/stack/hid/include + ${CMAKE_CURRENT_LIST_DIR}/stack/l2cap/include + ${CMAKE_CURRENT_LIST_DIR}/stack/sdp/include + ${CMAKE_CURRENT_LIST_DIR}/stack/smp/include + ${CMAKE_CURRENT_LIST_DIR}/stack/avct/include + ${CMAKE_CURRENT_LIST_DIR}/stack/avrc/include + ${CMAKE_CURRENT_LIST_DIR}/stack/avdt/include + ${CMAKE_CURRENT_LIST_DIR}/stack/a2dp/include + ${CMAKE_CURRENT_LIST_DIR}/stack/rfcomm/include + ${CMAKE_CURRENT_LIST_DIR}/stack/obex/include + ${CMAKE_CURRENT_LIST_DIR}/stack/goep/include + ${CMAKE_CURRENT_LIST_DIR}/stack/include + ${CMAKE_CURRENT_LIST_DIR}/common/include + ${CMAKE_CURRENT_LIST_DIR}/config/include +) + +if((CONFIG_BT_A2DP_ENABLE AND NOT CONFIG_BT_A2DP_USE_EXTERNAL_CODEC) OR + (CONFIG_BT_HFP_ENABLE AND CONFIG_BT_HFP_AUDIO_DATA_PATH_HCI AND NOT CONFIG_BT_HFP_USE_EXTERNAL_CODEC)) + list(APPEND bluedroid_host_priv_include_dirs + ${CMAKE_CURRENT_LIST_DIR}/external/sbc/decoder/include + ${CMAKE_CURRENT_LIST_DIR}/external/sbc/encoder/include + ${CMAKE_CURRENT_LIST_DIR}/external/sbc/plc/include + ) + + list(APPEND bluedroid_host_srcs + "${CMAKE_CURRENT_LIST_DIR}/external/sbc/decoder/srce/alloc.c" + "${CMAKE_CURRENT_LIST_DIR}/external/sbc/decoder/srce/bitalloc-sbc.c" + "${CMAKE_CURRENT_LIST_DIR}/external/sbc/decoder/srce/bitalloc.c" + "${CMAKE_CURRENT_LIST_DIR}/external/sbc/decoder/srce/bitstream-decode.c" + "${CMAKE_CURRENT_LIST_DIR}/external/sbc/decoder/srce/decoder-oina.c" + "${CMAKE_CURRENT_LIST_DIR}/external/sbc/decoder/srce/decoder-private.c" + "${CMAKE_CURRENT_LIST_DIR}/external/sbc/decoder/srce/decoder-sbc.c" + "${CMAKE_CURRENT_LIST_DIR}/external/sbc/decoder/srce/dequant.c" + "${CMAKE_CURRENT_LIST_DIR}/external/sbc/decoder/srce/framing-sbc.c" + "${CMAKE_CURRENT_LIST_DIR}/external/sbc/decoder/srce/framing.c" + "${CMAKE_CURRENT_LIST_DIR}/external/sbc/decoder/srce/oi_codec_version.c" + "${CMAKE_CURRENT_LIST_DIR}/external/sbc/decoder/srce/synthesis-8-generated.c" + "${CMAKE_CURRENT_LIST_DIR}/external/sbc/decoder/srce/synthesis-dct8.c" + "${CMAKE_CURRENT_LIST_DIR}/external/sbc/decoder/srce/synthesis-sbc.c" + "${CMAKE_CURRENT_LIST_DIR}/external/sbc/encoder/srce/sbc_analysis.c" + "${CMAKE_CURRENT_LIST_DIR}/external/sbc/encoder/srce/sbc_dct.c" + "${CMAKE_CURRENT_LIST_DIR}/external/sbc/encoder/srce/sbc_dct_coeffs.c" + "${CMAKE_CURRENT_LIST_DIR}/external/sbc/encoder/srce/sbc_enc_bit_alloc_mono.c" + "${CMAKE_CURRENT_LIST_DIR}/external/sbc/encoder/srce/sbc_enc_bit_alloc_ste.c" + "${CMAKE_CURRENT_LIST_DIR}/external/sbc/encoder/srce/sbc_enc_coeffs.c" + "${CMAKE_CURRENT_LIST_DIR}/external/sbc/encoder/srce/sbc_encoder.c" + "${CMAKE_CURRENT_LIST_DIR}/external/sbc/encoder/srce/sbc_packing.c" + "${CMAKE_CURRENT_LIST_DIR}/external/sbc/plc/sbc_plc.c") +endif() + +# Export the variables to the parent scope +set(bluedroid_host_srcs "${bluedroid_host_srcs}" PARENT_SCOPE) +set(bluedroid_host_include_dirs "${bluedroid_host_include_dirs}" PARENT_SCOPE) +set(bluedroid_host_priv_include_dirs "${bluedroid_host_priv_include_dirs}" PARENT_SCOPE) diff --git a/components/bt/host/bluedroid/Kconfig.in b/components/bt/host/bluedroid/Kconfig.in index 285e04d1fd2..d838338394d 100644 --- a/components/bt/host/bluedroid/Kconfig.in +++ b/components/bt/host/bluedroid/Kconfig.in @@ -48,6 +48,14 @@ config BT_CLASSIC_ENABLED help For now this option needs "SMP_ENABLE" to be set to yes +config BT_CLASSIC_MAX_RECONNECT_ON_COLLISION + int "Maximum number of reconnection attempts in case of collision" + depends on BT_CLASSIC_ENABLED + default 5 + help + The maximum number of reconnection attempts when encountering rejection of connection + request with error code 0x0B(Connection Already Exists) from peer device + config BT_CLASSIC_ENABLE_POWER_CTRL_VSC bool "Enable Espressif Vendor-specific HCI commands for power control of Classic Bluetooth" depends on BT_CLASSIC_ENABLED @@ -1380,23 +1388,6 @@ config BT_BLE_RPA_TIMEOUT This set RPA timeout of Controller and Host. Default is 900 s (15 minutes). Range is 1 s to 1 hour (3600 s). -config BT_BLE_HOST_ALLOW_SUB_SPEC_MIN_CONN_INT - bool "Allow BLE connection interval below Bluetooth Core Spec minimum (disable host check)" - depends on BT_BLE_ENABLED - default n - help - When enabled, the Bluedroid host skips the minimum BLE connection - interval validation (Bluetooth Core Spec minimum is 0x0006 / 7.5 ms) - and accepts any non-zero interval value from the application. The - BLE controller then enforces what is actually allowed; how small the - connection interval may be depends on controller capability and its - own configuration, not on this host option text. - - End users should NOT set this option directly. In typical IDF builds it - follows the active Controller integration when that Controller supports - this mode; use the Controller's own configuration (menu entries and symbol - names differ by chip) instead of toggling this host symbol manually. - menuconfig BT_BLE_50_FEATURES_SUPPORTED bool "Enable BLE 5.0 and above features(please disable BLE 4.2 if enable BLE 5.0)" depends on (BT_BLE_ENABLED && ((BT_CONTROLLER_ENABLED && SOC_BLE_50_SUPPORTED) || BT_CONTROLLER_DISABLED)) @@ -1603,7 +1594,20 @@ config BT_BLE_FEAT_CHANNEL_SOUNDING depends on (BT_BLE_50_FEATURES_SUPPORTED && ((BT_CONTROLLER_ENABLED && SOC_BLE_CHANNEL_SOUNDING_SUPPORTED) || BT_CONTROLLER_DISABLED)) # NOERROR default n help - Enable BLE channel sounding + Enable BLE channel sounding. + CS Security Requirements (Core 6.3) is configured separately via + BT_BLE_FEAT_CS_SECURITY_REQUIREMENTS. + +config BT_BLE_FEAT_CS_SECURITY_REQUIREMENTS + bool "Enable BLE CS Security Requirements (Core 6.3)" + depends on BT_BLE_FEAT_CHANNEL_SOUNDING && ((BT_CONTROLLER_ENABLED && SOC_BLE_CS_SECURITY_REQUIREMENTS_SUPPORTED) || BT_CONTROLLER_DISABLED) # NOERROR + default n + help + Enable Channel Sounding security requirements HCI commands (Bluetooth Core 6.3). + Supports LE CS Set Security Requirements (0x00A7) and + LE CS Set Default Security Requirements (0x00A8). + Use esp_ble_cs_set_security_requirements() and + esp_ble_cs_set_default_security_requirements(). config BT_BLE_FEAT_ADV_MONITOR bool "Enable BLE Advertising Monitor (LE Monitor Advertisement)" @@ -1614,6 +1618,50 @@ config BT_BLE_FEAT_ADV_MONITOR Allows the host to add devices to a monitor list and receive reports when the controller detects advertising from those devices (e.g. RSSI threshold). +config BT_BLE_FEAT_DBAF + bool "Enable BLE Decision-Based Advertising Filtering (DBAF)" + depends on (BT_BLE_50_FEATURES_SUPPORTED && ((BT_CONTROLLER_ENABLED && SOC_BLE_DBAF_SUPPORTED) || BT_CONTROLLER_DISABLED)) # NOERROR + default n + help + Enable Decision-Based Advertising Filtering (Bluetooth Core 6.0). + Supports LE Set Decision Data and LE Set Decision Instructions HCI commands. + +config BT_BLE_FEAT_FRAME_SPACE_UPDATE + bool "Enable BLE Frame Space Update" + depends on (BT_BLE_50_FEATURES_SUPPORTED && ((BT_CONTROLLER_ENABLED && SOC_BLE_FRAME_SPACE_SUPPORTED) || BT_CONTROLLER_DISABLED)) # NOERROR + default n + help + Enable Frame Space Update feature (Bluetooth Core 6.0). + +config BT_BLE_FEAT_LL_EXT_FEAT + bool "Enable BLE LL Extended Feature Set" + depends on (BT_BLE_50_FEATURES_SUPPORTED && ((BT_CONTROLLER_ENABLED && SOC_BLE_LL_EXT_FEAT_SUPPORTED) || BT_CONTROLLER_DISABLED)) # NOERROR + default n + help + Enable LL Extended Feature Set (Bluetooth Core 6.0). + Supports LE Read All Local/Remote Supported Features HCI commands. + +config BT_BLE_FEAT_SHORTER_CONN_INTERVALS + bool "Enable BLE Shorter Connection Intervals (Core 6.2)" + depends on BT_BLE_FEAT_CONN_SUBRATING && (BT_BLE_50_FEATURES_SUPPORTED && ((BT_CONTROLLER_ENABLED && SOC_BLE_SHORTER_CONN_INTERVALS_SUPPORTED) || BT_CONTROLLER_DISABLED)) # NOERROR + default n + help + Enable Shorter Connection Intervals feature (Bluetooth Core 6.2). + Requires Connection Subrating (Core 5.3) per [Vol 6] Part B, Section 4.6.50. + Supports LE Connection Rate Request, LE Set Default Rate Parameters, + LE Read Minimum Supported Connection Interval commands and LE Connection Rate Change event. + Connection intervals use 125 us units (minimum 375 us). Use esp_ble_gap_connection_rate_request(), + esp_ble_gap_set_default_rate_parameters(), esp_ble_gap_read_min_supported_connection_interval() + and ESP_BLE_GAP_CONN_RATE_* conversion macros in esp_gap_ble_api.h. + +config BT_BLE_FEAT_LE_UTP + bool "Enable BLE Unified Test Protocol (UTP)" + depends on (BT_BLE_50_FEATURES_SUPPORTED && ((BT_CONTROLLER_ENABLED && SOC_BLE_LE_UTP_SUPPORTED) || BT_CONTROLLER_DISABLED)) # NOERROR + default n + help + Enable LE Unified Test Protocol (Bluetooth Core 6.2). + Supports LE Enable UTP OTA Mode, LE UTP Send commands and LE UTP Receive event. + menuconfig BT_BLE_42_FEATURES_SUPPORTED bool "Enable BLE 4.2 features(please disable BLE 5.0 if enable BLE 4.2)" depends on BT_BLE_ENABLED diff --git a/components/bt/host/bluedroid/api/esp_a2dp_api.c b/components/bt/host/bluedroid/api/esp_a2dp_api.c index c9a48715e1e..83c04ed731a 100644 --- a/components/bt/host/bluedroid/api/esp_a2dp_api.c +++ b/components/bt/host/bluedroid/api/esp_a2dp_api.c @@ -507,7 +507,11 @@ esp_err_t esp_a2d_source_register_data_callback(esp_a2d_source_data_cb_t callbac esp_err_t esp_a2d_source_audio_data_send(esp_a2d_conn_hdl_t conn_hdl, esp_a2d_audio_buff_t *audio_buf) { - if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED || !btc_av_is_started()) { + if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { + return ESP_ERR_INVALID_STATE; + } + + if (g_a2dp_on_deinit || g_a2dp_source_ongoing_deinit || !btc_av_is_started()) { return ESP_ERR_INVALID_STATE; } diff --git a/components/bt/host/bluedroid/api/esp_ble_cte_api.c b/components/bt/host/bluedroid/api/esp_ble_cte_api.c index faeb58e8705..27f489d19e2 100644 --- a/components/bt/host/bluedroid/api/esp_ble_cte_api.c +++ b/components/bt/host/bluedroid/api/esp_ble_cte_api.c @@ -34,7 +34,7 @@ esp_ble_cte_cb_t esp_ble_cte_get_callback(void) #if (BLE_FEAT_CTE_CONNECTIONLESS_EN == TRUE) esp_err_t esp_ble_cte_set_connectionless_trans_params(esp_ble_cte_connless_trans_params_t *cte_trans_params) { - btc_msg_t msg; + btc_msg_t msg = {0}; btc_ble_cte_args_t arg; memset(&arg, 0, sizeof(arg)); @@ -42,7 +42,15 @@ esp_err_t esp_ble_cte_set_connectionless_trans_params(esp_ble_cte_connless_trans return ESP_ERR_INVALID_STATE; } - if ((cte_trans_params == NULL) || (cte_trans_params->antenna_ids == NULL)) { + if (cte_trans_params == NULL) { + return ESP_ERR_INVALID_ARG; + } + /* + * Per Core Spec, switching_pattern_len and Antenna_IDs is ignored when no switching pattern is used + * For AoA CTE type, the transmitter does not + * switch antenna, so Antenna_IDs may be omitted as well. + */ + if ((cte_trans_params->cte_type != ESP_BLE_CTE_TYPE_AOA) && (cte_trans_params->antenna_ids == NULL)) { return ESP_ERR_INVALID_ARG; } // The controller has performed parameter checking, and the host will no longer verify the validity of these parameters repeatedly. @@ -72,15 +80,24 @@ esp_err_t esp_ble_cte_set_connectionless_trans_params(esp_ble_cte_connless_trans arg.cte_trans_params.cte_len = cte_trans_params->cte_len; arg.cte_trans_params.cte_type = cte_trans_params->cte_type; arg.cte_trans_params.cte_count = cte_trans_params->cte_count; - arg.cte_trans_params.switching_pattern_len = cte_trans_params->switching_pattern_len; - arg.cte_trans_params.antenna_ids = cte_trans_params->antenna_ids; + /* For AoA CTE type, the transmitter does not switch antenna; normalize + * switching_pattern_len and antenna_ids so the BTC layer does not perform + * an unnecessary deep copy of data that the controller will ignore. + */ + if (cte_trans_params->cte_type != ESP_BLE_CTE_TYPE_AOA) { + arg.cte_trans_params.switching_pattern_len = cte_trans_params->switching_pattern_len; + arg.cte_trans_params.antenna_ids = cte_trans_params->antenna_ids; + } else { + arg.cte_trans_params.switching_pattern_len = 0; + arg.cte_trans_params.antenna_ids = NULL; + } return (btc_transfer_context(&msg, &arg, sizeof(btc_ble_cte_args_t), btc_ble_cte_arg_deep_copy, btc_ble_cte_arg_deep_free) == BT_STATUS_SUCCESS ? ESP_OK : ESP_FAIL); } esp_err_t esp_ble_cte_set_connectionless_trans_enable(esp_ble_cte_trans_enable_params_t *cte_trans_enable) { - btc_msg_t msg; + btc_msg_t msg = {0}; btc_ble_cte_args_t arg; memset(&arg, 0, sizeof(arg)); @@ -108,7 +125,7 @@ esp_err_t esp_ble_cte_set_connectionless_trans_enable(esp_ble_cte_trans_enable_p esp_err_t esp_ble_cte_set_connectionless_iq_sampling_enable(esp_ble_cte_iq_sampling_params_t *iq_sampling_en) { - btc_msg_t msg; + btc_msg_t msg = {0}; btc_ble_cte_args_t arg; memset(&arg, 0, sizeof(arg)); @@ -169,7 +186,7 @@ esp_err_t esp_ble_cte_set_connectionless_iq_sampling_enable(esp_ble_cte_iq_sampl #if (BLE_FEAT_CTE_CONNECTION_EN == TRUE) esp_err_t esp_ble_cte_set_connection_receive_params(esp_ble_cte_recv_params_params_t *cte_recv_params) { - btc_msg_t msg; + btc_msg_t msg = {0}; btc_ble_cte_args_t arg; memset(&arg, 0, sizeof(arg)); @@ -221,7 +238,7 @@ esp_err_t esp_ble_cte_set_connection_receive_params(esp_ble_cte_recv_params_para esp_err_t esp_ble_cte_set_connection_transmit_params(esp_ble_cte_conn_trans_params_t *cte_conn_trans_params) { - btc_msg_t msg; + btc_msg_t msg = {0}; btc_ble_cte_args_t arg; memset(&arg, 0, sizeof(arg)); @@ -229,9 +246,19 @@ esp_err_t esp_ble_cte_set_connection_transmit_params(esp_ble_cte_conn_trans_para return ESP_ERR_INVALID_STATE; } - if ((cte_conn_trans_params == NULL) || (cte_conn_trans_params->antenna_ids == NULL)) { + if (cte_conn_trans_params == NULL) { return ESP_ERR_INVALID_ARG; } + /* + * Per Core Spec, switching_pattern_len and Antenna_IDs is ignored when no switching pattern is used + * For AoA CTE type, the transmitter does not + * switch antenna, so Antenna_IDs may be omitted as well. + */ + if ((cte_conn_trans_params->cte_types & (ESP_BLE_CTE_TYPES_AOD_RESPONSE_WITH_1US | ESP_BLE_CTE_TYPES_AOD_RESPONSE_WITH_2US)) && + (cte_conn_trans_params->antenna_ids == NULL)) { + return ESP_ERR_INVALID_ARG; + } + // The controller has performed parameter checking, and the host will no longer verify the validity of these parameters repeatedly. #if (0) if ((cte_conn_trans_params->switching_pattern_len < ESP_BLE_CTE_MIN_SWITCHING_PATTERN_LENGTH) || @@ -251,15 +278,25 @@ esp_err_t esp_ble_cte_set_connection_transmit_params(esp_ble_cte_conn_trans_para arg.cte_conn_trans_params.conn_handle = cte_conn_trans_params->conn_handle; arg.cte_conn_trans_params.cte_types = cte_conn_trans_params->cte_types; - arg.cte_conn_trans_params.switching_pattern_len = cte_conn_trans_params->switching_pattern_len; - arg.cte_conn_trans_params.antenna_ids = cte_conn_trans_params->antenna_ids; + /* Antenna switching is only required for AoD CTE responses; when only AoA + * is enabled, normalize switching_pattern_len and antenna_ids so the BTC + * layer does not perform an unnecessary deep copy of data that the + * controller will ignore. + */ + if (cte_conn_trans_params->cte_types & (ESP_BLE_CTE_TYPES_AOD_RESPONSE_WITH_1US | ESP_BLE_CTE_TYPES_AOD_RESPONSE_WITH_2US)) { + arg.cte_conn_trans_params.switching_pattern_len = cte_conn_trans_params->switching_pattern_len; + arg.cte_conn_trans_params.antenna_ids = cte_conn_trans_params->antenna_ids; + } else { + arg.cte_conn_trans_params.switching_pattern_len = 0; + arg.cte_conn_trans_params.antenna_ids = NULL; + } return (btc_transfer_context(&msg, &arg, sizeof(btc_ble_cte_args_t), btc_ble_cte_arg_deep_copy, btc_ble_cte_arg_deep_free) == BT_STATUS_SUCCESS ? ESP_OK : ESP_FAIL); } esp_err_t esp_ble_cte_connection_cte_request_enable(esp_ble_cte_req_en_params_t *cte_conn_req_en) { - btc_msg_t msg; + btc_msg_t msg = {0}; btc_ble_cte_args_t arg; memset(&arg, 0, sizeof(arg)); @@ -302,7 +339,7 @@ esp_err_t esp_ble_cte_connection_cte_request_enable(esp_ble_cte_req_en_params_t esp_err_t esp_ble_cte_connection_cte_response_enable(esp_ble_cte_rsp_en_params_t *cte_conn_rsp_en) { - btc_msg_t msg; + btc_msg_t msg = {0}; btc_ble_cte_args_t arg; memset(&arg, 0, sizeof(arg)); @@ -334,7 +371,7 @@ esp_err_t esp_ble_cte_connection_cte_response_enable(esp_ble_cte_rsp_en_params_t esp_err_t esp_ble_cte_read_antenna_information(void) { - btc_msg_t msg; + btc_msg_t msg = {0}; if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { return ESP_ERR_INVALID_STATE; diff --git a/components/bt/host/bluedroid/api/esp_ble_iso_api.c b/components/bt/host/bluedroid/api/esp_ble_iso_api.c index 132b8880add..486c8676535 100644 --- a/components/bt/host/bluedroid/api/esp_ble_iso_api.c +++ b/components/bt/host/bluedroid/api/esp_ble_iso_api.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -63,7 +63,8 @@ esp_err_t esp_ble_iso_create_big(esp_ble_iso_big_creat_params_t *big_creat_param if (big_creat_param->rtn > 0x1E) { return ESP_ERR_INVALID_ARG; } - if ((big_creat_param->phy != 0x01) && (big_creat_param->phy != 0x02) && (big_creat_param->phy != 0x04)) { + /* phy is a bit field: bit0=1M, bit1=2M, bit2=Coded (HCI_LE_Create_BIG, Core Spec): at least one bit. */ + if ((big_creat_param->phy == 0) || (big_creat_param->phy & ~0x07)) { return ESP_ERR_INVALID_ARG; } if (big_creat_param->packing > 0x01) { @@ -118,7 +119,9 @@ esp_err_t esp_ble_iso_create_big_test(esp_ble_iso_big_creat_test_params_t *big_c if (big_creat_test_param->max_pdu < 0x0001 || big_creat_test_param->max_pdu > 0x00FB) { return ESP_ERR_INVALID_ARG; } - if ((big_creat_test_param->phy != 0x01) && (big_creat_test_param->phy != 0x02) && (big_creat_test_param->phy != 0x04)) { + /* HCI_LE_Create_BIG_Test (Core Spec): host shall set exactly one of 1M / 2M / Coded PHY. */ + if ((big_creat_test_param->phy != 0x01) && (big_creat_test_param->phy != 0x02) && + (big_creat_test_param->phy != 0x04)) { return ESP_ERR_INVALID_ARG; } if (big_creat_test_param->framing > BLE_ISO_FRAMING_FRAMED_PDU_UNSEGMENTABLE_MODE) { diff --git a/components/bt/host/bluedroid/api/esp_bluedroid_hci.c b/components/bt/host/bluedroid/api/esp_bluedroid_hci.c index 5476f43409d..59c1ab268b1 100644 --- a/components/bt/host/bluedroid/api/esp_bluedroid_hci.c +++ b/components/bt/host/bluedroid/api/esp_bluedroid_hci.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -8,6 +8,7 @@ #include "esp_log.h" #include "esp_bluedroid_hci.h" #include "common/bt_target.h" +#include "bt_common.h" #include "hci/hci_trans_int.h" #if (BT_CONTROLLER_INCLUDED == TRUE) #include "esp_bt.h" @@ -69,7 +70,11 @@ void hci_host_send_packet(uint8_t *data, uint16_t len) { #if (BT_HCI_LOG_INCLUDED == TRUE) if (data != NULL && len > 1) { - bt_hci_log_record_hci_data(data[0], &data[1], (uint16_t)(len - 1)); + uint8_t data_type = bt_hci_log_h4_type_to_data_type(data[0]); + bt_hci_log_record_hci_data(data_type, &data[1], (uint16_t)(len - 1)); +#if BT_HCI_INSIGHTS_INCLUDED + bt_hci_log_record_insights(data_type, &data[1], (uint16_t)(len - 1)); +#endif } #endif #if CONFIG_BT_BLE_LOG_SPI_OUT_HCI_ENABLED diff --git a/components/bt/host/bluedroid/api/esp_bt_main.c b/components/bt/host/bluedroid/api/esp_bt_main.c index 4329e2a0d94..6db37fd2a1a 100644 --- a/components/bt/host/bluedroid/api/esp_bt_main.c +++ b/components/bt/host/bluedroid/api/esp_bt_main.c @@ -218,6 +218,7 @@ esp_err_t esp_bluedroid_init_with_cfg(esp_bluedroid_config_t *cfg) if (future_await(*future_p) == FUTURE_FAIL) { LOG_ERROR("Bluedroid Initialize Fail"); + btc_cleanup_partial_init(); btc_deinit(); bluedroid_config_deinit(); #if HEAP_MEMORY_STATS 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 311c5d281ca..81a8069848b 100644 --- a/components/bt/host/bluedroid/api/esp_gap_ble_api.c +++ b/components/bt/host/bluedroid/api/esp_gap_ble_api.c @@ -15,6 +15,9 @@ #include "btc/btc_ble_storage.h" #include "esp_random.h" +/* Hard upper bound to prevent excessive allocations in BTC/BTA layers. */ +#define ESP_GAP_BLE_EXT_ADV_DATA_MAX_LEN 1650U + esp_err_t esp_ble_gap_register_callback(esp_gap_ble_cb_t callback) { ESP_BLUEDROID_STATUS_CHECK(ESP_BLUEDROID_STATUS_ENABLED); @@ -151,14 +154,29 @@ esp_err_t esp_ble_gap_update_conn_params(esp_ble_conn_update_params_t *params) ESP_BLUEDROID_STATUS_CHECK(ESP_BLUEDROID_STATUS_ENABLED); if(!params) { LOG_ERROR("%s,params is NULL", __func__); - return ESP_FAIL; + return ESP_ERR_INVALID_ARG; } if (ESP_BLE_IS_VALID_PARAM(params->min_int, BLE_CONN_INT_MIN_HOST_CHECK, ESP_BLE_CONN_INT_MAX) && ESP_BLE_IS_VALID_PARAM(params->max_int, BLE_CONN_INT_MIN_HOST_CHECK, ESP_BLE_CONN_INT_MAX) && ESP_BLE_IS_VALID_PARAM(params->timeout, ESP_BLE_CONN_SUP_TOUT_MIN, ESP_BLE_CONN_SUP_TOUT_MAX) && (params->latency <= ESP_BLE_CONN_LATENCY_MAX) && - ((params->timeout * 10) >= ((1 + params->latency) * ((params->max_int * 5) >> 1))) && params->min_int <= params->max_int) { + /* + * Core Spec (Vol 6, Part B, Section 4.5.2): + * supervision_timeout shall be strictly greater than + * (1 + connSlaveLatency) * connIntervalMax * 2. + * + * Here: + * - timeout is in 10 ms units + * - max_int is in 1.25 ms units + * + * Convert both sides into 0.5 ms units to avoid truncation: + * (timeout * 10 ms) -> timeout * 20 (0.5 ms units) + * (max_int * 1.25 ms * 2) -> max_int * 5 (0.5 ms units) + */ + (((uint32_t)params->timeout * 20U) > + ((uint32_t)(1U + (uint32_t)params->latency) * (uint32_t)params->max_int * 5U)) && + (params->min_int <= params->max_int)) { msg.sig = BTC_SIG_API_CALL; msg.pid = BTC_PID_GAP_BLE; @@ -169,7 +187,7 @@ esp_err_t esp_ble_gap_update_conn_params(esp_ble_conn_update_params_t *params) } else { LOG_ERROR("%s,invalid connection params:min_int = %d, max_int = %d, latency = %d, timeout = %d",\ __func__, params->min_int, params->max_int, params->latency, params->timeout); - return ESP_FAIL; + return ESP_ERR_INVALID_ARG; } } @@ -339,7 +357,7 @@ esp_err_t esp_ble_gap_update_whitelist(bool add_remove, esp_bd_addr_t remote_bda return ESP_ERR_INVALID_STATE; } if (!remote_bda){ - return ESP_ERR_INVALID_SIZE; + return ESP_ERR_INVALID_ARG; } msg.sig = BTC_SIG_API_CALL; msg.pid = BTC_PID_GAP_BLE; @@ -369,7 +387,7 @@ esp_err_t esp_ble_gap_clear_whitelist(void) esp_err_t esp_ble_gap_get_whitelist_size(uint16_t *length) { if (length == NULL) { - return ESP_FAIL; + return ESP_ERR_INVALID_ARG; } ESP_BLUEDROID_STATUS_CHECK(ESP_BLUEDROID_STATUS_ENABLED); btc_get_whitelist_size(length); @@ -397,7 +415,9 @@ esp_err_t esp_ble_gap_set_prefer_conn_params(esp_bd_addr_t bd_addr, ESP_BLE_IS_VALID_PARAM(max_conn_int, BLE_CONN_INT_MIN_HOST_CHECK, ESP_BLE_CONN_INT_MAX) && ESP_BLE_IS_VALID_PARAM(supervision_tout, ESP_BLE_CONN_SUP_TOUT_MIN, ESP_BLE_CONN_SUP_TOUT_MAX) && (slave_latency <= ESP_BLE_CONN_LATENCY_MAX) && - ((supervision_tout * 10) >= ((1 + slave_latency) * ((max_conn_int * 5) >> 1))) && min_conn_int <= max_conn_int) { + (((uint32_t)supervision_tout * 20U) > + ((uint32_t)(1U + (uint32_t)slave_latency) * (uint32_t)max_conn_int * 5U)) && + (min_conn_int <= max_conn_int)) { msg.sig = BTC_SIG_API_CALL; msg.pid = BTC_PID_GAP_BLE; @@ -413,7 +433,7 @@ esp_err_t esp_ble_gap_set_prefer_conn_params(esp_bd_addr_t bd_addr, } else { LOG_ERROR("%s,invalid connection params:min_int = %d, max_int = %d, latency = %d, timeout = %d",\ __func__, min_conn_int, max_conn_int, slave_latency, supervision_tout); - return ESP_FAIL; + return ESP_ERR_INVALID_ARG; } } #endif // #if (BLE_42_FEATURE_SUPPORT == TRUE) @@ -678,7 +698,7 @@ esp_err_t esp_ble_gap_set_security_param(esp_ble_sm_param_t param_type, uint32_t passkey = 0; for(uint8_t i = 0; i < len; i++) { - passkey += (((uint8_t *)value)[i]<<(8*i)); + passkey += ((uint32_t)((const uint8_t *)value)[i] << (8U * (uint32_t)i)); } if(passkey > 999999) { return ESP_ERR_INVALID_ARG; @@ -846,6 +866,15 @@ esp_err_t esp_ble_get_bond_device_list(int *dev_num, esp_ble_bond_dev_t *dev_lis *dev_num = dev_num_total; } + /* + * The storage layer updates some fields using |= (e.g. key_mask). Ensure + * the caller-provided list is zero-initialized to avoid propagating + * uninitialized heap contents (including padding) back to the caller. + */ + if (*dev_num > 0) { + memset(dev_list, 0, sizeof(*dev_list) * (size_t)(*dev_num)); + } + ret = btc_storage_get_bonded_ble_devices_list(dev_list, *dev_num); return (ret == BT_STATUS_SUCCESS ? ESP_OK : ESP_FAIL); @@ -1293,6 +1322,10 @@ esp_err_t esp_ble_gap_config_ext_adv_data_raw(uint8_t instance, uint16_t length, return ESP_ERR_INVALID_ARG; } + if (length > ESP_GAP_BLE_EXT_ADV_DATA_MAX_LEN) { + return ESP_ERR_INVALID_ARG; + } + msg.sig = BTC_SIG_API_CALL; msg.pid = BTC_PID_GAP_BLE; @@ -1319,6 +1352,10 @@ esp_err_t esp_ble_gap_config_ext_scan_rsp_data_raw(uint8_t instance, uint16_t le return ESP_ERR_INVALID_ARG; } + if (length > ESP_GAP_BLE_EXT_ADV_DATA_MAX_LEN) { + return ESP_ERR_INVALID_ARG; + } + msg.sig = BTC_SIG_API_CALL; msg.pid = BTC_PID_GAP_BLE; msg.act = BTC_GAP_BLE_CFG_EXT_SCAN_RSP_DATA_RAW; @@ -1454,6 +1491,10 @@ esp_err_t esp_ble_gap_config_periodic_adv_data_raw(uint8_t instance, uint16_t le return ESP_ERR_INVALID_ARG; } + if (length > ESP_GAP_BLE_EXT_ADV_DATA_MAX_LEN) { + return ESP_ERR_INVALID_ARG; + } + msg.sig = BTC_SIG_API_CALL; msg.pid = BTC_PID_GAP_BLE; @@ -1751,7 +1792,8 @@ esp_err_t esp_ble_gap_prefer_ext_connect_params_set(esp_bd_addr_t addr, ESP_BLE_IS_VALID_PARAM(phy_1m_conn_params->interval_max, BLE_CONN_INT_MIN_HOST_CHECK, ESP_BLE_CONN_INT_MAX) && ESP_BLE_IS_VALID_PARAM(phy_1m_conn_params->supervision_timeout, ESP_BLE_CONN_SUP_TOUT_MIN, ESP_BLE_CONN_SUP_TOUT_MAX) && (phy_1m_conn_params->latency <= ESP_BLE_CONN_LATENCY_MAX) && - ((phy_1m_conn_params->supervision_timeout * 10) >= ((1 + phy_1m_conn_params->latency) * ((phy_1m_conn_params->interval_max * 5) >> 1))) && + (((uint32_t)phy_1m_conn_params->supervision_timeout * 20U) > + ((uint32_t)(1U + (uint32_t)phy_1m_conn_params->latency) * (uint32_t)phy_1m_conn_params->interval_max * 5U)) && (phy_1m_conn_params->interval_min <= phy_1m_conn_params->interval_max)) { memcpy(&arg.set_ext_conn_params.phy_1m_conn_params, phy_1m_conn_params, sizeof(esp_ble_gap_conn_params_t)); @@ -1775,7 +1817,8 @@ esp_err_t esp_ble_gap_prefer_ext_connect_params_set(esp_bd_addr_t addr, ESP_BLE_IS_VALID_PARAM(phy_2m_conn_params->interval_max, BLE_CONN_INT_MIN_HOST_CHECK, ESP_BLE_CONN_INT_MAX) && ESP_BLE_IS_VALID_PARAM(phy_2m_conn_params->supervision_timeout, ESP_BLE_CONN_SUP_TOUT_MIN, ESP_BLE_CONN_SUP_TOUT_MAX) && (phy_2m_conn_params->latency <= ESP_BLE_CONN_LATENCY_MAX) && - ((phy_2m_conn_params->supervision_timeout * 10) >= ((1 + phy_2m_conn_params->latency) * ((phy_2m_conn_params->interval_max * 5) >> 1))) && + (((uint32_t)phy_2m_conn_params->supervision_timeout * 20U) > + ((uint32_t)(1U + (uint32_t)phy_2m_conn_params->latency) * (uint32_t)phy_2m_conn_params->interval_max * 5U)) && (phy_2m_conn_params->interval_min <= phy_2m_conn_params->interval_max)) { memcpy(&arg.set_ext_conn_params.phy_2m_conn_params, phy_2m_conn_params, sizeof(esp_ble_gap_conn_params_t)); @@ -1799,7 +1842,8 @@ esp_err_t esp_ble_gap_prefer_ext_connect_params_set(esp_bd_addr_t addr, ESP_BLE_IS_VALID_PARAM(phy_coded_conn_params->interval_max, BLE_CONN_INT_MIN_HOST_CHECK, ESP_BLE_CONN_INT_MAX) && ESP_BLE_IS_VALID_PARAM(phy_coded_conn_params->supervision_timeout, ESP_BLE_CONN_SUP_TOUT_MIN, ESP_BLE_CONN_SUP_TOUT_MAX) && (phy_coded_conn_params->latency <= ESP_BLE_CONN_LATENCY_MAX) && - ((phy_coded_conn_params->supervision_timeout * 10) >= ((1 + phy_coded_conn_params->latency) * ((phy_coded_conn_params->interval_max * 5) >> 1))) && + (((uint32_t)phy_coded_conn_params->supervision_timeout * 20U) > + ((uint32_t)(1U + (uint32_t)phy_coded_conn_params->latency) * (uint32_t)phy_coded_conn_params->interval_max * 5U)) && (phy_coded_conn_params->interval_min <= phy_coded_conn_params->interval_max)) { memcpy(&arg.set_ext_conn_params.phy_coded_conn_params, phy_coded_conn_params, sizeof(esp_ble_gap_conn_params_t)); @@ -1913,6 +1957,343 @@ esp_err_t esp_ble_gap_enable_monitor_adv(bool enable) } #endif // #if (BLE_FEAT_ADV_MONITOR == TRUE) +#if (BLE_FEAT_DBAF == TRUE) +esp_err_t esp_ble_gap_set_decision_data(const esp_ble_gap_set_decision_data_params_t *params) +{ + btc_msg_t msg = {0}; + btc_ble_5_gap_args_t arg; + memset(&arg, 0, sizeof(arg)); + + ESP_BLUEDROID_STATUS_CHECK(ESP_BLUEDROID_STATUS_ENABLED); + if (params == NULL) { + return ESP_ERR_INVALID_ARG; + } + if (params->data_len > ESP_BLE_GAP_DECISION_DATA_MAX_LEN) { + return ESP_ERR_INVALID_ARG; + } + if (params->data_len > 0 && params->data == NULL) { + return ESP_ERR_INVALID_ARG; + } + + msg.sig = BTC_SIG_API_CALL; + msg.pid = BTC_PID_GAP_BLE; + msg.act = BTC_GAP_BLE_SET_DECISION_DATA; + arg.set_decision_data.adv_handle = params->adv_handle; + arg.set_decision_data.decision_type_flags = params->decision_type_flags; + arg.set_decision_data.data_len = params->data_len; + arg.set_decision_data.data = (uint8_t *)params->data; + + return (btc_transfer_context(&msg, &arg, sizeof(btc_ble_5_gap_args_t), + btc_gap_ble_arg_deep_copy, btc_gap_ble_arg_deep_free) + == BT_STATUS_SUCCESS ? ESP_OK : ESP_FAIL); +} + +esp_err_t esp_ble_gap_set_decision_instructions(const esp_ble_gap_set_decision_instructions_params_t *params) +{ + btc_msg_t msg = {0}; + btc_ble_5_gap_args_t arg; + memset(&arg, 0, sizeof(arg)); + + ESP_BLUEDROID_STATUS_CHECK(ESP_BLUEDROID_STATUS_ENABLED); + if (params == NULL) { + return ESP_ERR_INVALID_ARG; + } + if (params->num_tests == 0 || params->num_tests > ESP_BLE_GAP_DECISION_MAX_TESTS) { + return ESP_ERR_INVALID_ARG; + } + if (params->test_flags == NULL || params->test_fields == NULL || params->test_params == NULL) { + return ESP_ERR_INVALID_ARG; + } + + msg.sig = BTC_SIG_API_CALL; + msg.pid = BTC_PID_GAP_BLE; + msg.act = BTC_GAP_BLE_SET_DECISION_INSTRUCTIONS; + arg.set_decision_instructions.num_tests = params->num_tests; + arg.set_decision_instructions.test_flags = (uint8_t *)params->test_flags; + arg.set_decision_instructions.test_fields = (uint8_t *)params->test_fields; + arg.set_decision_instructions.test_params = (uint8_t *)params->test_params; + + return (btc_transfer_context(&msg, &arg, sizeof(btc_ble_5_gap_args_t), + btc_gap_ble_arg_deep_copy, btc_gap_ble_arg_deep_free) + == BT_STATUS_SUCCESS ? ESP_OK : ESP_FAIL); +} +#endif // #if (BLE_FEAT_DBAF == TRUE) + +#if (BLE_FEAT_FRAME_SPACE_UPDATE == TRUE) +esp_err_t esp_ble_gap_frame_space_update(const esp_ble_gap_frame_space_update_params_t *params) +{ + btc_msg_t msg = {0}; + btc_ble_5_gap_args_t arg; + memset(&arg, 0, sizeof(arg)); + + ESP_BLUEDROID_STATUS_CHECK(ESP_BLUEDROID_STATUS_ENABLED); + if (params == NULL) { + return ESP_ERR_INVALID_ARG; + } + if (params->frame_space_min > ESP_BLE_GAP_FRAME_SPACE_MAX_US || + params->frame_space_max > ESP_BLE_GAP_FRAME_SPACE_MAX_US || + params->frame_space_min > params->frame_space_max) { + return ESP_ERR_INVALID_ARG; + } + if ((params->phys & ~ESP_BLE_GAP_FRAME_SPACE_PHY_MASK) != 0 || params->phys == 0) { + return ESP_ERR_INVALID_ARG; + } + if ((params->spacing_types & ~ESP_BLE_GAP_FRAME_SPACE_SPACING_MASK) != 0 || + params->spacing_types == 0) { + return ESP_ERR_INVALID_ARG; + } + + msg.sig = BTC_SIG_API_CALL; + msg.pid = BTC_PID_GAP_BLE; + msg.act = BTC_GAP_BLE_FRAME_SPACE_UPDATE; + arg.frame_space_update.conn_handle = params->conn_handle; + arg.frame_space_update.frame_space_min = params->frame_space_min; + arg.frame_space_update.frame_space_max = params->frame_space_max; + arg.frame_space_update.phys = params->phys; + arg.frame_space_update.spacing_types = params->spacing_types; + + return (btc_transfer_context(&msg, &arg, sizeof(btc_ble_5_gap_args_t), NULL, NULL) + == BT_STATUS_SUCCESS ? ESP_OK : ESP_FAIL); +} +#endif // #if (BLE_FEAT_FRAME_SPACE_UPDATE == TRUE) + +#if (BLE_FEAT_LL_EXT_FEAT == TRUE) +esp_err_t esp_ble_gap_read_all_local_supp_features(void) +{ + btc_msg_t msg = {0}; + + ESP_BLUEDROID_STATUS_CHECK(ESP_BLUEDROID_STATUS_ENABLED); + + msg.sig = BTC_SIG_API_CALL; + msg.pid = BTC_PID_GAP_BLE; + msg.act = BTC_GAP_BLE_READ_ALL_LOCAL_SUPP_FEAT; + + return (btc_transfer_context(&msg, NULL, 0, NULL, NULL) + == BT_STATUS_SUCCESS ? ESP_OK : ESP_FAIL); +} + +esp_err_t esp_ble_gap_read_all_remote_features(const esp_ble_gap_read_all_remote_feat_params_t *params) +{ + btc_msg_t msg = {0}; + btc_ble_5_gap_args_t arg; + memset(&arg, 0, sizeof(arg)); + + ESP_BLUEDROID_STATUS_CHECK(ESP_BLUEDROID_STATUS_ENABLED); + if (params == NULL) { + return ESP_ERR_INVALID_ARG; + } + if (params->page_requested > ESP_BLE_GAP_LL_EXT_FEAT_MAX_PAGE) { + return ESP_ERR_INVALID_ARG; + } + + msg.sig = BTC_SIG_API_CALL; + msg.pid = BTC_PID_GAP_BLE; + msg.act = BTC_GAP_BLE_READ_ALL_REMOTE_FEAT; + arg.read_all_remote_feat.conn_handle = params->conn_handle; + arg.read_all_remote_feat.page_requested = params->page_requested; + + return (btc_transfer_context(&msg, &arg, sizeof(btc_ble_5_gap_args_t), NULL, NULL) + == BT_STATUS_SUCCESS ? ESP_OK : ESP_FAIL); +} +#endif // #if (BLE_FEAT_LL_EXT_FEAT == TRUE) + +#if (BLE_FEAT_SHORTER_CONN_INTERVALS == TRUE) +static bool esp_ble_gap_conn_rate_common_params_valid(uint16_t conn_interval_min, + uint16_t conn_interval_max, + uint16_t subrate_min, + uint16_t subrate_max, + uint16_t max_latency, + uint16_t continuation_number, + uint16_t supervision_timeout, + uint16_t min_ce_len, + uint16_t max_ce_len) +{ + if (conn_interval_min < ESP_BLE_GAP_CONN_RATE_INTERVAL_MIN || + conn_interval_max < ESP_BLE_GAP_CONN_RATE_INTERVAL_MIN || + conn_interval_min > conn_interval_max || + conn_interval_max > ESP_BLE_GAP_CONN_RATE_INTERVAL_MAX) { + return false; + } + if (subrate_min < ESP_BLE_GAP_CONN_RATE_SUBRATE_MIN || + subrate_max < ESP_BLE_GAP_CONN_RATE_SUBRATE_MIN || + subrate_min > subrate_max || + subrate_max > ESP_BLE_GAP_CONN_RATE_SUBRATE_MAX) { + return false; + } + if (continuation_number >= subrate_max || + continuation_number > ESP_BLE_GAP_CONN_RATE_CONTINUATION_NUMBER_MAX) { + return false; + } + if (max_latency > ESP_BLE_GAP_CONN_RATE_MAX_LATENCY_MAX) { + return false; + } + if ((uint32_t)subrate_max * (uint32_t)(max_latency + 1U) > + ESP_BLE_GAP_CONN_RATE_SUBRATE_LATENCY_PRODUCT_MAX) { + return false; + } + if (supervision_timeout < ESP_BLE_GAP_CONN_RATE_SUPERVISION_TIMEOUT_MIN || + supervision_timeout > ESP_BLE_GAP_CONN_RATE_SUPERVISION_TIMEOUT_MAX) { + return false; + } + if ((uint32_t)conn_interval_max * (uint32_t)subrate_max * (uint32_t)(max_latency + 1U) >= + (uint32_t)supervision_timeout * ESP_BLE_GAP_CONN_RATE_SUPERVISION_TIMEOUT_FACTOR) { + return false; + } + if (min_ce_len > max_ce_len) { + return false; + } + return true; +} + +static bool esp_ble_gap_connection_rate_params_valid(const esp_ble_gap_connection_rate_request_params_t *params) +{ + return esp_ble_gap_conn_rate_common_params_valid(params->conn_interval_min, + params->conn_interval_max, + params->subrate_min, + params->subrate_max, + params->max_latency, + params->continuation_number, + params->supervision_timeout, + params->min_ce_len, + params->max_ce_len); +} + +static bool esp_ble_gap_default_rate_params_valid(const esp_ble_gap_default_rate_param_t *params) +{ + return esp_ble_gap_conn_rate_common_params_valid(params->conn_interval_min, + params->conn_interval_max, + params->subrate_min, + params->subrate_max, + params->max_latency, + params->continuation_number, + params->supervision_timeout, + params->min_ce_len, + params->max_ce_len); +} + +esp_err_t esp_ble_gap_connection_rate_request(const esp_ble_gap_connection_rate_request_params_t *params) +{ + btc_msg_t msg = {0}; + btc_ble_5_gap_args_t arg; + memset(&arg, 0, sizeof(arg)); + + ESP_BLUEDROID_STATUS_CHECK(ESP_BLUEDROID_STATUS_ENABLED); + if (params == NULL) { + return ESP_ERR_INVALID_ARG; + } + if (!esp_ble_gap_connection_rate_params_valid(params)) { + return ESP_ERR_INVALID_ARG; + } + + msg.sig = BTC_SIG_API_CALL; + msg.pid = BTC_PID_GAP_BLE; + msg.act = BTC_GAP_BLE_CONNECTION_RATE_REQUEST; + arg.connection_rate_request.conn_handle = params->conn_handle; + arg.connection_rate_request.conn_interval_min = params->conn_interval_min; + arg.connection_rate_request.conn_interval_max = params->conn_interval_max; + arg.connection_rate_request.subrate_min = params->subrate_min; + arg.connection_rate_request.subrate_max = params->subrate_max; + arg.connection_rate_request.max_latency = params->max_latency; + arg.connection_rate_request.continuation_number = params->continuation_number; + arg.connection_rate_request.supervision_timeout = params->supervision_timeout; + arg.connection_rate_request.min_ce_len = params->min_ce_len; + arg.connection_rate_request.max_ce_len = params->max_ce_len; + + return (btc_transfer_context(&msg, &arg, sizeof(btc_ble_5_gap_args_t), NULL, NULL) + == BT_STATUS_SUCCESS ? ESP_OK : ESP_FAIL); +} + +esp_err_t esp_ble_gap_set_default_rate_parameters(const esp_ble_gap_default_rate_param_t *params) +{ + btc_msg_t msg = {0}; + btc_ble_5_gap_args_t arg; + memset(&arg, 0, sizeof(arg)); + + ESP_BLUEDROID_STATUS_CHECK(ESP_BLUEDROID_STATUS_ENABLED); + if (params == NULL) { + return ESP_ERR_INVALID_ARG; + } + if (!esp_ble_gap_default_rate_params_valid(params)) { + return ESP_ERR_INVALID_ARG; + } + + msg.sig = BTC_SIG_API_CALL; + msg.pid = BTC_PID_GAP_BLE; + msg.act = BTC_GAP_BLE_SET_DEFAULT_RATE_PARAMETERS; + arg.set_default_rate_parameters.conn_interval_min = params->conn_interval_min; + arg.set_default_rate_parameters.conn_interval_max = params->conn_interval_max; + arg.set_default_rate_parameters.subrate_min = params->subrate_min; + arg.set_default_rate_parameters.subrate_max = params->subrate_max; + arg.set_default_rate_parameters.max_latency = params->max_latency; + arg.set_default_rate_parameters.continuation_number = params->continuation_number; + arg.set_default_rate_parameters.supervision_timeout = params->supervision_timeout; + arg.set_default_rate_parameters.min_ce_len = params->min_ce_len; + arg.set_default_rate_parameters.max_ce_len = params->max_ce_len; + + return (btc_transfer_context(&msg, &arg, sizeof(btc_ble_5_gap_args_t), NULL, NULL) + == BT_STATUS_SUCCESS ? ESP_OK : ESP_FAIL); +} + +esp_err_t esp_ble_gap_read_min_supported_connection_interval(void) +{ + btc_msg_t msg = {0}; + + ESP_BLUEDROID_STATUS_CHECK(ESP_BLUEDROID_STATUS_ENABLED); + + msg.sig = BTC_SIG_API_CALL; + msg.pid = BTC_PID_GAP_BLE; + msg.act = BTC_GAP_BLE_READ_MIN_SUPP_CONN_INTERVAL; + + return (btc_transfer_context(&msg, NULL, 0, NULL, NULL) + == BT_STATUS_SUCCESS ? ESP_OK : ESP_FAIL); +} +#endif // #if (BLE_FEAT_SHORTER_CONN_INTERVALS == TRUE) + +#if (BLE_FEAT_LE_UTP == TRUE) +esp_err_t esp_ble_gap_enable_utp_ota_mode(const esp_ble_gap_enable_utp_ota_mode_params_t *params) +{ + btc_msg_t msg = {0}; + btc_ble_5_gap_args_t arg; + memset(&arg, 0, sizeof(arg)); + + ESP_BLUEDROID_STATUS_CHECK(ESP_BLUEDROID_STATUS_ENABLED); + if (params == NULL || params->enable > 1) { + return ESP_ERR_INVALID_ARG; + } + + msg.sig = BTC_SIG_API_CALL; + msg.pid = BTC_PID_GAP_BLE; + msg.act = BTC_GAP_BLE_ENABLE_UTP_OTA_MODE; + arg.enable_utp_ota_mode.enable = params->enable; + + return (btc_transfer_context(&msg, &arg, sizeof(btc_ble_5_gap_args_t), NULL, NULL) + == BT_STATUS_SUCCESS ? ESP_OK : ESP_FAIL); +} + +esp_err_t esp_ble_gap_utp_send(const esp_ble_gap_utp_send_params_t *params) +{ + btc_msg_t msg = {0}; + btc_ble_5_gap_args_t arg; + memset(&arg, 0, sizeof(arg)); + + ESP_BLUEDROID_STATUS_CHECK(ESP_BLUEDROID_STATUS_ENABLED); + if (params == NULL || params->data == NULL || + params->data_len == 0 || params->data_len > ESP_BLE_GAP_UTP_DATA_MAX_LEN) { + return ESP_ERR_INVALID_ARG; + } + + msg.sig = BTC_SIG_API_CALL; + msg.pid = BTC_PID_GAP_BLE; + msg.act = BTC_GAP_BLE_UTP_SEND; + arg.utp_send.data_len = params->data_len; + arg.utp_send.data = (uint8_t *)params->data; + + return (btc_transfer_context(&msg, &arg, sizeof(btc_ble_5_gap_args_t), + btc_gap_ble_arg_deep_copy, btc_gap_ble_arg_deep_free) + == BT_STATUS_SUCCESS ? ESP_OK : ESP_FAIL); +} +#endif // #if (BLE_FEAT_LE_UTP == TRUE) + #endif //#if (BLE_50_FEATURE_SUPPORT == TRUE) #if (BLE_FEAT_PERIODIC_ADV_SYNC_TRANSFER == TRUE) @@ -2564,6 +2945,59 @@ esp_err_t esp_ble_cs_security_enable(uint16_t conn_handle) == BT_STATUS_SUCCESS ? ESP_OK : ESP_FAIL); } +#if (BT_BLE_FEAT_CS_SECURITY_REQUIREMENTS == TRUE) +/* Host does not validate conn_handle range or CS_Security_Requirements reserved bits; Controller checks. */ +esp_err_t esp_ble_cs_set_security_requirements(esp_ble_cs_set_security_requirements_params *params) +{ + btc_msg_t msg = {0}; + btc_ble_5_gap_args_t arg; + memset(&arg, 0, sizeof(arg)); + + if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { + return ESP_ERR_INVALID_STATE; + } + + if (!params) { + return ESP_ERR_INVALID_ARG; + } + + msg.sig = BTC_SIG_API_CALL; + msg.pid = BTC_PID_GAP_BLE; + msg.act = BTC_GAP_BLE_CS_SET_SECURITY_REQUIREMENTS; + + arg.cs_set_security_requirements_params.conn_handle = params->conn_handle; + arg.cs_set_security_requirements_params.cs_security_requirements = params->cs_security_requirements; + + return (btc_transfer_context(&msg, &arg, sizeof(btc_ble_5_gap_args_t), NULL, NULL) + == BT_STATUS_SUCCESS ? ESP_OK : ESP_FAIL); +} + +/* Host does not validate CS_Security_Requirements reserved bits; Controller checks. */ +esp_err_t esp_ble_cs_set_default_security_requirements(esp_ble_cs_set_default_security_requirements_params *params) +{ + btc_msg_t msg = {0}; + btc_ble_5_gap_args_t arg; + memset(&arg, 0, sizeof(arg)); + + if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { + return ESP_ERR_INVALID_STATE; + } + + if (!params) { + return ESP_ERR_INVALID_ARG; + } + + msg.sig = BTC_SIG_API_CALL; + msg.pid = BTC_PID_GAP_BLE; + msg.act = BTC_GAP_BLE_CS_SET_DEFAULT_SECURITY_REQUIREMENTS; + + arg.cs_set_default_security_requirements_params.cs_security_requirements = params->cs_security_requirements; + + return (btc_transfer_context(&msg, &arg, sizeof(btc_ble_5_gap_args_t), NULL, NULL) + == BT_STATUS_SUCCESS ? ESP_OK : ESP_FAIL); +} +#endif // (BT_BLE_FEAT_CS_SECURITY_REQUIREMENTS == TRUE) + esp_err_t esp_ble_cs_set_default_settings(esp_ble_cs_set_default_settings_params *default_setting_params) { btc_msg_t msg = {0}; diff --git a/components/bt/host/bluedroid/api/esp_gatt_common_api.c b/components/bt/host/bluedroid/api/esp_gatt_common_api.c index 9254b8d87af..d3348d5a450 100644 --- a/components/bt/host/bluedroid/api/esp_gatt_common_api.c +++ b/components/bt/host/bluedroid/api/esp_gatt_common_api.c @@ -64,6 +64,9 @@ uint16_t esp_ble_get_sendable_packets_num (void) /** * @brief This function is used to query the number of available buffers for the current connection. * When you need to query the current available buffer number, it is recommended to use this API. + * + * @note This API can only be called when a direct connection exists. + * * @param[in] conn_id: current connection id. * * @return diff --git a/components/bt/host/bluedroid/api/esp_gattc_api.c b/components/bt/host/bluedroid/api/esp_gattc_api.c index 0e2befd18d7..ff4a0684e76 100644 --- a/components/bt/host/bluedroid/api/esp_gattc_api.c +++ b/components/bt/host/bluedroid/api/esp_gattc_api.c @@ -93,7 +93,7 @@ esp_err_t esp_ble_gattc_enh_open(esp_gatt_if_t gattc_if, esp_ble_gatt_creat_conn memcpy(arg.open.remote_bda, creat_conn_params->remote_bda, ESP_BD_ADDR_LEN); arg.open.remote_addr_type = creat_conn_params->remote_addr_type; arg.open.is_direct = creat_conn_params->is_direct; - arg.open.is_aux= creat_conn_params->is_aux; + arg.open.is_aux = creat_conn_params->is_aux; #if (BT_BLE_FEAT_PAWR_EN == TRUE) arg.open.is_pawr_synced = false; arg.open.adv_handle = 0xFF; @@ -397,7 +397,9 @@ esp_err_t esp_ble_gattc_search_service(esp_gatt_if_t gattc_if, uint16_t conn_id, esp_gatt_status_t esp_ble_gattc_get_service(esp_gatt_if_t gattc_if, uint16_t conn_id, esp_bt_uuid_t *svc_uuid, esp_gattc_service_elem_t *result, uint16_t *count, uint16_t offset) { - ESP_BLUEDROID_STATUS_CHECK(ESP_BLUEDROID_STATUS_ENABLED); + if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { + return ESP_GATT_WRONG_STATE; + } if (result == NULL || count == NULL || *count == 0) { return ESP_GATT_INVALID_PDU; @@ -415,14 +417,16 @@ esp_gatt_status_t esp_ble_gattc_get_all_char(esp_gatt_if_t gattc_if, esp_gattc_char_elem_t *result, uint16_t *count, uint16_t offset) { - ESP_BLUEDROID_STATUS_CHECK(ESP_BLUEDROID_STATUS_ENABLED); + if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { + return ESP_GATT_WRONG_STATE; + } if (result == NULL || count == NULL || *count == 0) { return ESP_GATT_INVALID_PDU; } - if ((start_handle == 0) && (end_handle == 0)) { + if ((start_handle == 0 && end_handle == 0) || start_handle > end_handle) { *count = 0; return ESP_GATT_INVALID_HANDLE; } @@ -437,7 +441,9 @@ esp_gatt_status_t esp_ble_gattc_get_all_descr(esp_gatt_if_t gattc_if, esp_gattc_descr_elem_t *result, uint16_t *count, uint16_t offset) { - ESP_BLUEDROID_STATUS_CHECK(ESP_BLUEDROID_STATUS_ENABLED); + if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { + return ESP_GATT_WRONG_STATE; + } if (char_handle == 0) { return ESP_GATT_INVALID_HANDLE; @@ -459,13 +465,15 @@ esp_gatt_status_t esp_ble_gattc_get_char_by_uuid(esp_gatt_if_t gattc_if, esp_gattc_char_elem_t *result, uint16_t *count) { - ESP_BLUEDROID_STATUS_CHECK(ESP_BLUEDROID_STATUS_ENABLED); + if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { + return ESP_GATT_WRONG_STATE; + } if (result == NULL || count == NULL || *count == 0) { return ESP_GATT_INVALID_PDU; } - if (start_handle == 0 && end_handle == 0) { + if ((start_handle == 0 && end_handle == 0) || start_handle > end_handle) { *count = 0; return ESP_GATT_INVALID_HANDLE; } @@ -484,12 +492,19 @@ esp_gatt_status_t esp_ble_gattc_get_descr_by_uuid(esp_gatt_if_t gattc_if, esp_gattc_descr_elem_t *result, uint16_t *count) { - ESP_BLUEDROID_STATUS_CHECK(ESP_BLUEDROID_STATUS_ENABLED); + if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { + return ESP_GATT_WRONG_STATE; + } if (result == NULL || count == NULL || *count == 0) { return ESP_GATT_INVALID_PDU; } + if ((start_handle == 0 && end_handle == 0) || start_handle > end_handle) { + *count = 0; + return ESP_GATT_INVALID_HANDLE; + } + uint16_t conn_hdl = BTC_GATT_CREATE_CONN_ID(gattc_if, conn_id); return btc_ble_gattc_get_descr_by_uuid(conn_hdl, start_handle, end_handle, char_uuid, descr_uuid, result, count); } @@ -501,7 +516,9 @@ esp_gatt_status_t esp_ble_gattc_get_descr_by_char_handle(esp_gatt_if_t gattc_if, esp_gattc_descr_elem_t *result, uint16_t *count) { - ESP_BLUEDROID_STATUS_CHECK(ESP_BLUEDROID_STATUS_ENABLED); + if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { + return ESP_GATT_WRONG_STATE; + } if (result == NULL || count == NULL || *count == 0) { return ESP_GATT_INVALID_PDU; @@ -524,13 +541,15 @@ esp_gatt_status_t esp_ble_gattc_get_include_service(esp_gatt_if_t gattc_if, esp_gattc_incl_svc_elem_t *result, uint16_t *count) { - ESP_BLUEDROID_STATUS_CHECK(ESP_BLUEDROID_STATUS_ENABLED); + if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { + return ESP_GATT_WRONG_STATE; + } if (result == NULL || count == NULL || *count == 0) { return ESP_GATT_INVALID_PDU; } - if (start_handle == 0 && end_handle == 0) { + if ((start_handle == 0 && end_handle == 0) || start_handle > end_handle) { *count = 0; return ESP_GATT_INVALID_HANDLE; } @@ -547,13 +566,17 @@ esp_gatt_status_t esp_ble_gattc_get_attr_count(esp_gatt_if_t gattc_if, uint16_t char_handle, uint16_t *count) { - ESP_BLUEDROID_STATUS_CHECK(ESP_BLUEDROID_STATUS_ENABLED); + if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { + return ESP_GATT_WRONG_STATE; + } if (count == NULL) { return ESP_GATT_INVALID_PDU; } - if ((start_handle == 0 && end_handle == 0) && (type != ESP_GATT_DB_DESCRIPTOR)) { + /* start_handle/end_handle are ignored for ESP_GATT_DB_DESCRIPTOR (see esp_gattc_api.h). */ + if (type != ESP_GATT_DB_DESCRIPTOR && + ((start_handle == 0 && end_handle == 0) || start_handle > end_handle)) { *count = 0; return ESP_GATT_INVALID_HANDLE; } @@ -565,14 +588,16 @@ esp_gatt_status_t esp_ble_gattc_get_attr_count(esp_gatt_if_t gattc_if, esp_gatt_status_t esp_ble_gattc_get_db(esp_gatt_if_t gattc_if, uint16_t conn_id, uint16_t start_handle, uint16_t end_handle, esp_gattc_db_elem_t *db, uint16_t *count) { - ESP_BLUEDROID_STATUS_CHECK(ESP_BLUEDROID_STATUS_ENABLED); + if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { + return ESP_GATT_WRONG_STATE; + } if (db == NULL || count == NULL || *count == 0) { return ESP_GATT_INVALID_PDU; } - if (start_handle == 0 && end_handle == 0) { + if ((start_handle == 0 && end_handle == 0) || start_handle > end_handle) { *count = 0; return ESP_GATT_INVALID_HANDLE; } @@ -644,10 +669,14 @@ esp_err_t esp_ble_gattc_read_by_type (esp_gatt_if_t gattc_if, return ESP_FAIL; } - if (start_handle == 0 || end_handle == 0) { + if ((start_handle == 0 && end_handle == 0) || start_handle > end_handle) { return ESP_GATT_INVALID_HANDLE; } + if (start_handle == 0) { + start_handle = 1; + } + msg.sig = BTC_SIG_API_CALL; msg.pid = BTC_PID_GATTC; msg.act = BTC_GATTC_ACT_READ_BY_TYPE; @@ -670,7 +699,12 @@ 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) || (read_multi->num_attr == 0) || (read_multi->num_attr > ESP_GATT_MAX_READ_MULTI_HANDLES)) { + if (read_multi == NULL) { + return ESP_ERR_INVALID_ARG; + } + uint8_t num_attr = read_multi->num_attr; + + if ((num_attr == 0) || (num_attr > ESP_GATT_MAX_READ_MULTI_HANDLES)) { return ESP_ERR_INVALID_ARG; } @@ -689,10 +723,10 @@ esp_err_t esp_ble_gattc_read_multiple(esp_gatt_if_t gattc_if, msg.pid = BTC_PID_GATTC; msg.act = BTC_GATTC_ACT_READ_MULTIPLE_CHAR; arg.read_multiple.conn_id = BTC_GATT_CREATE_CONN_ID(gattc_if, conn_id); - arg.read_multiple.num_attr = read_multi->num_attr; + arg.read_multiple.num_attr = num_attr; arg.read_multiple.auth_req = auth_req; - memcpy(arg.read_multiple.handles, read_multi->handles, sizeof(uint16_t)*read_multi->num_attr); + memcpy(arg.read_multiple.handles, read_multi->handles, sizeof(uint16_t) * num_attr); return (btc_transfer_context(&msg, &arg, sizeof(btc_ble_gattc_args_t), NULL, NULL) == BT_STATUS_SUCCESS ? ESP_OK : ESP_FAIL); } @@ -707,7 +741,12 @@ 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) || (read_multi->num_attr == 0) || (read_multi->num_attr > ESP_GATT_MAX_READ_MULTI_HANDLES)) { + if (read_multi == NULL) { + return ESP_ERR_INVALID_ARG; + } + uint8_t num_attr = read_multi->num_attr; + + if ((num_attr == 0) || (num_attr > ESP_GATT_MAX_READ_MULTI_HANDLES)) { return ESP_ERR_INVALID_ARG; } @@ -726,9 +765,9 @@ esp_err_t esp_ble_gattc_read_multiple_variable(esp_gatt_if_t gattc_if, msg.pid = BTC_PID_GATTC; msg.act = BTC_GATTC_ACT_READ_MULTIPLE_VARIABLE_CHAR; arg.read_multiple.conn_id = BTC_GATT_CREATE_CONN_ID(gattc_if, conn_id); - arg.read_multiple.num_attr = read_multi->num_attr; + arg.read_multiple.num_attr = num_attr; arg.read_multiple.auth_req = auth_req; - memcpy(arg.read_multiple.handles, read_multi->handles, sizeof(uint16_t)*read_multi->num_attr); + memcpy(arg.read_multiple.handles, read_multi->handles, sizeof(uint16_t) * num_attr); return (btc_transfer_context(&msg, &arg, sizeof(btc_ble_gattc_args_t), NULL, NULL) == BT_STATUS_SUCCESS ? ESP_OK : ESP_FAIL); } @@ -829,6 +868,10 @@ esp_err_t esp_ble_gattc_write_char_descr (esp_gatt_if_t gattc_if, ESP_BLUEDROID_STATUS_CHECK(ESP_BLUEDROID_STATUS_ENABLED); + if ((value_len > 0) && (value == 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__); @@ -873,6 +916,10 @@ esp_err_t esp_ble_gattc_prepare_write(esp_gatt_if_t gattc_if, ESP_BLUEDROID_STATUS_CHECK(ESP_BLUEDROID_STATUS_ENABLED); + if ((value_len > 0) && (value == 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__); @@ -915,6 +962,10 @@ esp_err_t esp_ble_gattc_prepare_write_char_descr(esp_gatt_if_t gattc_if, ESP_BLUEDROID_STATUS_CHECK(ESP_BLUEDROID_STATUS_ENABLED); + if ((value_len > 0) && (value == 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__); diff --git a/components/bt/host/bluedroid/api/esp_gatts_api.c b/components/bt/host/bluedroid/api/esp_gatts_api.c index ceaf047875c..c93b2bdc96b 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-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -384,7 +384,9 @@ esp_err_t esp_ble_gatts_set_attr_value(uint16_t attr_handle, uint16_t length, co esp_gatt_status_t esp_ble_gatts_get_attr_value(uint16_t attr_handle, uint16_t *length, const uint8_t **value) { - ESP_BLUEDROID_STATUS_CHECK(ESP_BLUEDROID_STATUS_ENABLED); + if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { + return ESP_GATT_WRONG_STATE; + } if (length == NULL || value == NULL) { return ESP_GATT_INVALID_PDU; @@ -463,20 +465,44 @@ esp_err_t esp_ble_gatts_send_service_change_indication(esp_gatt_if_t gatts_if, e static esp_err_t esp_ble_gatts_add_char_desc_param_check(esp_attr_value_t *char_val, esp_attr_control_t *control) { - if ((control != NULL) && ((control->auto_rsp != ESP_GATT_AUTO_RSP) && (control->auto_rsp != ESP_GATT_RSP_BY_APP))){ - LOG_ERROR("Error in %s, line=%d, control->auto_rsp should be set to ESP_GATT_AUTO_RSP or ESP_GATT_RSP_BY_APP\n",\ - __func__, __LINE__); - return ESP_ERR_INVALID_ARG; + if ((control != NULL) && + (control->auto_rsp != ESP_GATT_AUTO_RSP) && + (control->auto_rsp != ESP_GATT_RSP_BY_APP)) { + return ESP_ERR_INVALID_ARG; } - if ((control != NULL) && (control->auto_rsp == ESP_GATT_AUTO_RSP)){ - if (char_val == NULL){ - LOG_ERROR("Error in %s, line=%d, for stack respond attribute, char_val should not be NULL here\n",\ - __func__, __LINE__); + /* Validate attribute value regardless of auto_rsp to avoid deep-copy waste and leaks. */ + if (char_val != NULL) { + bool invalid = false; + + if (char_val->attr_max_len > ESP_GATT_MAX_ATTR_LEN) { + invalid = true; + } + if (char_val->attr_len > ESP_GATT_MAX_ATTR_LEN) { + invalid = true; + } + /* Always require attr_len <= attr_max_len to avoid leaks and wasteful allocations. */ + if (char_val->attr_len > char_val->attr_max_len) { + invalid = true; + } + + if (invalid) { + LOG_ERROR("%s bad attr len=%u/%u lim=%u", + __func__, + (unsigned)char_val->attr_len, + (unsigned)char_val->attr_max_len, + (unsigned)ESP_GATT_MAX_ATTR_LEN); return ESP_ERR_INVALID_ARG; - } else if (char_val->attr_max_len == 0){ - LOG_ERROR("Error in %s, line=%d, for stack respond attribute, attribute max length should not be 0\n",\ - __func__, __LINE__); + } + } + + if ((control != NULL) && (control->auto_rsp == ESP_GATT_AUTO_RSP)) { + if (char_val == NULL) { + return ESP_ERR_INVALID_ARG; + } + + /* For stack auto response, attr_max_len must be non-zero. */ + if (char_val->attr_max_len == 0) { return ESP_ERR_INVALID_ARG; } } diff --git a/components/bt/host/bluedroid/api/esp_hf_ag_api.c b/components/bt/host/bluedroid/api/esp_hf_ag_api.c index 3c89b1be76a..ff9b4f20581 100644 --- a/components/bt/host/bluedroid/api/esp_hf_ag_api.c +++ b/components/bt/host/bluedroid/api/esp_hf_ag_api.c @@ -651,7 +651,7 @@ esp_err_t esp_hf_ag_audio_data_send(esp_hf_sync_conn_hdl_t sync_conn_hdl, esp_hf return ESP_ERR_INVALID_ARG; } - if (btc_hf_ag_audio_data_send(sync_conn_hdl, (uint8_t *)audio_buf, audio_buf->data, audio_buf->data_len)) { + if (btc_hf_ag_audio_data_send(sync_conn_hdl, (uint8_t *)audio_buf, audio_buf->data, audio_buf->data_len) == BT_STATUS_SUCCESS) { return ESP_OK; } return ESP_FAIL; diff --git a/components/bt/host/bluedroid/api/esp_hf_client_api.c b/components/bt/host/bluedroid/api/esp_hf_client_api.c index 64ef18eb320..6c93f44ed1f 100644 --- a/components/bt/host/bluedroid/api/esp_hf_client_api.c +++ b/components/bt/host/bluedroid/api/esp_hf_client_api.c @@ -518,9 +518,6 @@ esp_err_t esp_hf_client_register_data_callback(esp_hf_client_incoming_data_cb_t if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { return ESP_ERR_INVALID_STATE; } - if (recv == NULL || send == NULL) { - return ESP_ERR_INVALID_ARG; - } btc_msg_t msg; msg.sig = BTC_SIG_API_CALL; diff --git a/components/bt/host/bluedroid/api/include/api/esp_ble_iso_api.h b/components/bt/host/bluedroid/api/include/api/esp_ble_iso_api.h index c2afa745c75..d8eeb5b82d6 100644 --- a/components/bt/host/bluedroid/api/include/api/esp_ble_iso_api.h +++ b/components/bt/host/bluedroid/api/include/api/esp_ble_iso_api.h @@ -69,7 +69,7 @@ typedef enum { #define BLE_ISO_WORST_CASE_SCA_LEVEL_20_PPM (0x07) #define BLE_ISO_PACKING_SEQUENTIAL (0x00) -#define BLE_ISO_PACKING_INTERLEAVED (0x00) +#define BLE_ISO_PACKING_INTERLEAVED (0x01) #define BLE_ISO_FRAMING_UNFRAMED_PDU (0x00) #define BLE_ISO_FRAMING_FRAMED_PDU_SEGMENTABLE_MODE (0x01) diff --git a/components/bt/host/bluedroid/api/include/api/esp_bt_defs.h b/components/bt/host/bluedroid/api/include/api/esp_bt_defs.h index bd4b2c7e763..b6573c4de65 100644 --- a/components/bt/host/bluedroid/api/include/api/esp_bt_defs.h +++ b/components/bt/host/bluedroid/api/include/api/esp_bt_defs.h @@ -210,7 +210,7 @@ typedef uint8_t esp_link_key[ESP_BT_OCTET16_LEN]; /* Link Key */ /// Default GATT interface id #define ESP_DEFAULT_GATT_IF 0xff -#if BLE_HIGH_DUTY_ADV_INTERVAL +#if CONFIG_BT_BLE_HIGH_DUTY_ADV_INTERVAL #define ESP_BLE_PRIM_ADV_INT_MIN 0x000008 /*!< Minimum advertising interval for undirected and low duty cycle directed advertising */ #else #define ESP_BLE_PRIM_ADV_INT_MIN 0x000020 /*!< Minimum advertising interval for undirected and low duty cycle directed advertising */ diff --git a/components/bt/host/bluedroid/api/include/api/esp_gap_ble_api.h b/components/bt/host/bluedroid/api/include/api/esp_gap_ble_api.h index 945528b233d..b1fb16b0824 100644 --- a/components/bt/host/bluedroid/api/include/api/esp_gap_ble_api.h +++ b/components/bt/host/bluedroid/api/include/api/esp_gap_ble_api.h @@ -273,6 +273,20 @@ typedef enum { ESP_GAP_BLE_CLEAR_MONITOR_ADV_COMPLETE_EVT, /*!< When clear monitor advertiser list complete, the event comes */ ESP_GAP_BLE_READ_MONITOR_ADV_LIST_SIZE_COMPLETE_EVT, /*!< When read monitor advertiser list size complete, the event comes */ ESP_GAP_BLE_ENABLE_MONITOR_ADV_COMPLETE_EVT, /*!< When enable/disable monitor advertising complete, the event comes */ + ESP_GAP_BLE_SET_DECISION_DATA_COMPLETE_EVT, /*!< When set decision data complete, the event comes */ + ESP_GAP_BLE_SET_DECISION_INSTRUCTIONS_COMPLETE_EVT, /*!< When set decision instructions complete, the event comes */ + ESP_GAP_BLE_FRAME_SPACE_UPDATE_COMPLETE_EVT, /*!< When frame space update complete, the event comes */ + ESP_GAP_BLE_READ_ALL_LOCAL_SUPP_FEAT_COMPLETE_EVT, /*!< When read all local supported LE features complete, the event comes */ + ESP_GAP_BLE_READ_ALL_REMOTE_FEAT_COMPLETE_EVT, /*!< When read all remote LE features complete, the event comes */ + ESP_GAP_BLE_CONNECTION_RATE_REQUEST_COMPLETE_EVT, /*!< When connection rate request command complete, the event comes */ + ESP_GAP_BLE_CONN_RATE_CHANGE_EVT, /*!< When connection rate change event is received, the event comes */ + ESP_GAP_BLE_SET_DEFAULT_RATE_PARAMETERS_COMPLETE_EVT, /*!< When set default rate parameters complete, the event comes */ + ESP_GAP_BLE_READ_MIN_SUPP_CONN_INTERVAL_COMPLETE_EVT, /*!< When read minimum supported connection interval complete, the event comes */ + ESP_GAP_BLE_ENABLE_UTP_OTA_MODE_COMPLETE_EVT, /*!< When enable UTP OTA mode complete, the event comes */ + ESP_GAP_BLE_UTP_SEND_COMPLETE_EVT, /*!< When UTP send complete, the event comes */ + ESP_GAP_BLE_UTP_RECEIVE_EVT, /*!< When UTP data is received, the event comes */ + ESP_GAP_BLE_CS_SET_SECURITY_REQUIREMENTS_CMPL_EVT, /*!< When CS set security requirements complete, the event comes */ + ESP_GAP_BLE_CS_SET_DEFAULT_SECURITY_REQUIREMENTS_CMPL_EVT, /*!< When CS set default security requirements complete, the event comes */ ESP_GAP_BLE_EVT_MAX, /*!< when maximum advertising event complete, the event comes */ } esp_gap_ble_cb_event_t; @@ -296,6 +310,7 @@ typedef uint8_t esp_gap_ble_channels[ESP_GAP_BLE_CHANNELS_LEN]; * * - Advertising interval: unit is 0.625ms (range: 20ms to 10240ms) * - Connection interval: unit is 1.25ms (range: 7.5ms to 4000ms) + * - Connection rate interval (Core 6.2 SCI): unit is 125us (range: 375us), see ESP_BLE_GAP_CONN_RATE_* * - Scan interval/window: unit is 0.625ms * - Periodic advertising interval: unit is 1.25ms * - Supervision timeout: unit is 10ms (range: 100ms to 32000ms) @@ -984,10 +999,25 @@ typedef struct { esp_ble_addr_type_t peer_addr_type; /*!< ext adv peer address type */ esp_bd_addr_t peer_addr; /*!< ext adv peer address */ esp_ble_adv_filter_t filter_policy; /*!< ext adv filter policy */ - int8_t tx_power; /*!< ext adv tx power */ + int8_t tx_power; /*!< ext adv tx power. + For this advertising set, priority is higher than + `esp_ble_tx_power_set()`, `esp_ble_tx_power_set_enhanced()`, + and menuconfig default TX power (`CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL`). + The actual applied TX power may be different from the requested value, + depending on the Controller TX power granularity/level mechanism. + (for example ESP32-C3/ESP32-S3 with 3 dBm step), the actual + applied TX power may be rounded down and be 0 to 2 dBm lower + than the requested value.) */ esp_ble_gap_pri_phy_t primary_phy; /*!< ext adv primary phy */ uint8_t max_skip; /*!< ext adv maximum skip */ - esp_ble_gap_phy_t secondary_phy; /*!< ext adv secondary phy */ + esp_ble_gap_phy_t secondary_phy; /*!< ext adv secondary phy. + Note: If the advertiser sends connectable advertising packets on the LE Coded + PHY, the peer may then establish the ACL connection on the LE Coded PHY, which + will significantly degrade Wi-Fi performance in Bluetooth/Wi-Fi coexistence + scenarios because Coded PHY (S=2/S=8) packets occupy the radio for much longer + than 1M/2M PHY packets. It is recommended to use the LE 2M PHY (or LE 1M PHY) + first, and only use the LE Coded PHY when the long-range capability is really + required. */ uint8_t sid; /*!< ext adv sid */ bool scan_req_notif; /*!< ext adv scan request event notify */ #if (CONFIG_BT_BLE_FEAT_ADV_CODING_SELECTION) @@ -1239,6 +1269,172 @@ typedef struct { } esp_ble_gap_remove_monitor_adv_params_t; #endif // (BLE_FEAT_ADV_MONITOR == TRUE) +#if (BLE_FEAT_DBAF == TRUE) +#define ESP_BLE_GAP_DECISION_DATA_MAX_LEN 248 +#define ESP_BLE_GAP_DECISION_MAX_TESTS 8 +#define ESP_BLE_GAP_DECISION_TEST_PARAM_LEN 16 +#define ESP_BLE_GAP_DECISION_TEST_PARAMS_MAX_LEN (ESP_BLE_GAP_DECISION_MAX_TESTS * ESP_BLE_GAP_DECISION_TEST_PARAM_LEN) +#define ESP_BLE_GAP_DECISION_TYPE_FLAG_RESOLVABLE_TAG (1 << 0) + +#define ESP_BLE_GAP_DECISION_SCAN_FILTER_NO_DECISIONS 0x00 +#define ESP_BLE_GAP_DECISION_SCAN_FILTER_ALL_PDUS 0x04 +#define ESP_BLE_GAP_DECISION_SCAN_FILTER_DECISIONS_ONLY 0x0C + +/** + * @brief Parameters for setting decision data for an advertising set (DBAF) + */ +typedef struct { + uint8_t adv_handle; /*!< Advertising set handle */ + uint8_t decision_type_flags; /*!< Decision type flags (e.g. ESP_BLE_GAP_DECISION_TYPE_FLAG_RESOLVABLE_TAG) */ + uint8_t data_len; /*!< Length of decision data, max: ESP_BLE_GAP_DECISION_DATA_MAX_LEN */ + const uint8_t *data; /*!< Pointer to decision data */ +} esp_ble_gap_set_decision_data_params_t; + +/** + * @brief Parameters for setting decision instructions for decision-based advertising filtering (DBAF) + */ +typedef struct { + uint8_t num_tests; /*!< Number of decision tests, max: ESP_BLE_GAP_DECISION_MAX_TESTS */ + const uint8_t *test_flags; /*!< Pointer to test flags array (num_tests octets) */ + const uint8_t *test_fields; /*!< Pointer to test fields array (num_tests octets) */ + const uint8_t *test_params; /*!< Pointer to test parameters (num_tests * ESP_BLE_GAP_DECISION_TEST_PARAM_LEN octets) */ +} esp_ble_gap_set_decision_instructions_params_t; +#endif // #if (BLE_FEAT_DBAF == TRUE) + +#if (BLE_FEAT_FRAME_SPACE_UPDATE == TRUE) +#define ESP_BLE_GAP_FRAME_SPACE_MAX_US 10000 +#define ESP_BLE_GAP_FRAME_SPACE_PHY_1M_MASK (1 << 0) +#define ESP_BLE_GAP_FRAME_SPACE_PHY_2M_MASK (1 << 1) +#define ESP_BLE_GAP_FRAME_SPACE_PHY_CODED_MASK (1 << 2) +#define ESP_BLE_GAP_FRAME_SPACE_SPACING_IFS_ACL_CP_MASK (1 << 0) +#define ESP_BLE_GAP_FRAME_SPACE_SPACING_IFS_ACL_PC_MASK (1 << 1) +#define ESP_BLE_GAP_FRAME_SPACE_SPACING_MCES_MASK (1 << 2) +#define ESP_BLE_GAP_FRAME_SPACE_SPACING_IFS_CIS_MASK (1 << 3) +#define ESP_BLE_GAP_FRAME_SPACE_SPACING_MSS_CIS_MASK (1 << 4) +#define ESP_BLE_GAP_FRAME_SPACE_SPACING_ACL_IFS_MASK \ + (ESP_BLE_GAP_FRAME_SPACE_SPACING_IFS_ACL_CP_MASK | ESP_BLE_GAP_FRAME_SPACE_SPACING_IFS_ACL_PC_MASK) +#define ESP_BLE_GAP_FRAME_SPACE_SPACING_ACL_MASK \ + (ESP_BLE_GAP_FRAME_SPACE_SPACING_ACL_IFS_MASK | ESP_BLE_GAP_FRAME_SPACE_SPACING_MCES_MASK) +#define ESP_BLE_GAP_FRAME_SPACE_SPACING_CIS_MASK \ + (ESP_BLE_GAP_FRAME_SPACE_SPACING_IFS_CIS_MASK | ESP_BLE_GAP_FRAME_SPACE_SPACING_MSS_CIS_MASK) +#define ESP_BLE_GAP_FRAME_SPACE_PHY_MASK \ + (ESP_BLE_GAP_FRAME_SPACE_PHY_1M_MASK | ESP_BLE_GAP_FRAME_SPACE_PHY_2M_MASK | \ + ESP_BLE_GAP_FRAME_SPACE_PHY_CODED_MASK) +#define ESP_BLE_GAP_FRAME_SPACE_SPACING_MASK \ + (ESP_BLE_GAP_FRAME_SPACE_SPACING_IFS_ACL_CP_MASK | ESP_BLE_GAP_FRAME_SPACE_SPACING_IFS_ACL_PC_MASK | \ + ESP_BLE_GAP_FRAME_SPACE_SPACING_MCES_MASK | ESP_BLE_GAP_FRAME_SPACE_SPACING_IFS_CIS_MASK | \ + ESP_BLE_GAP_FRAME_SPACE_SPACING_MSS_CIS_MASK) + +/** + * @brief Parameters for requesting a Frame Space Update on a connection + */ +typedef struct { + uint16_t conn_handle; /*!< Connection handle */ + uint16_t frame_space_min; /*!< Minimum frame space in microseconds, max: ESP_BLE_GAP_FRAME_SPACE_MAX_US */ + uint16_t frame_space_max; /*!< Maximum frame space in microseconds, max: ESP_BLE_GAP_FRAME_SPACE_MAX_US */ + uint8_t phys; /*!< PHY mask (ESP_BLE_GAP_FRAME_SPACE_PHY_*_MASK) */ + uint16_t spacing_types; /*!< Spacing types mask (ESP_BLE_GAP_FRAME_SPACE_SPACING_*_MASK) */ +} esp_ble_gap_frame_space_update_params_t; +#endif // #if (BLE_FEAT_FRAME_SPACE_UPDATE == TRUE) + +#if (BLE_FEAT_LL_EXT_FEAT == TRUE) +#define ESP_BLE_GAP_LL_EXT_FEAT_DATA_LEN 248 +#define ESP_BLE_GAP_LL_EXT_FEAT_MAX_PAGE 10 + +/** + * @brief Parameters for reading all remote LE features for a connection + */ +typedef struct { + uint16_t conn_handle; /*!< Connection handle */ + uint8_t page_requested; /*!< The number of the highest-numbered page of features that the Host requires and the Controller shall obtain, + range: 0 to ESP_BLE_GAP_LL_EXT_FEAT_MAX_PAGE */ +} esp_ble_gap_read_all_remote_feat_params_t; +#endif // #if (BLE_FEAT_LL_EXT_FEAT == TRUE) + +#if (BLE_FEAT_SHORTER_CONN_INTERVALS == TRUE) +#define ESP_BLE_GAP_CONN_RATE_INTERVAL_UNIT_US 125 +#define ESP_BLE_GAP_CONN_RATE_INTERVAL_MIN 3 +#define ESP_BLE_GAP_CONN_RATE_INTERVAL_MAX 0x7D00 +#define ESP_BLE_GAP_CONN_RATE_SUBRATE_MIN 0x0001 +#define ESP_BLE_GAP_CONN_RATE_SUBRATE_MAX 0x01F4 +#define ESP_BLE_GAP_CONN_RATE_MAX_LATENCY_MAX 0x01F3 +#define ESP_BLE_GAP_CONN_RATE_SUBRATE_LATENCY_PRODUCT_MAX 500 +#define ESP_BLE_GAP_CONN_RATE_CONTINUATION_NUMBER_MAX 0x01F3 +#define ESP_BLE_GAP_CONN_RATE_SUPERVISION_TIMEOUT_MIN 0x000A +#define ESP_BLE_GAP_CONN_RATE_SUPERVISION_TIMEOUT_MAX 0x0C80 +#define ESP_BLE_GAP_CONN_RATE_SUPERVISION_TIMEOUT_FACTOR 40 + +#define ESP_BLE_GAP_CONN_RATE_INTERVAL_US(units) \ + ((uint32_t)(units) * ESP_BLE_GAP_CONN_RATE_INTERVAL_UNIT_US) +#define ESP_BLE_GAP_CONN_RATE_INTERVAL_FROM_US(us) \ + ((uint16_t)(((us) + (ESP_BLE_GAP_CONN_RATE_INTERVAL_UNIT_US - 1U)) / ESP_BLE_GAP_CONN_RATE_INTERVAL_UNIT_US)) +#define ESP_BLE_GAP_CONN_RATE_INTERVAL_FROM_MS(ms) \ + ESP_BLE_GAP_CONN_RATE_INTERVAL_FROM_US((uint32_t)(ms) * 1000U) +#define ESP_BLE_GAP_CONN_RATE_EFF_INTERVAL_US(interval, subrate) \ + (ESP_BLE_GAP_CONN_RATE_INTERVAL_US(interval) * (uint32_t)(subrate)) + +/** + * @brief Parameters for requesting a connection rate update (Shorter Connection Intervals) + */ +typedef struct { + uint16_t conn_handle; /*!< Connection handle */ + uint16_t conn_interval_min; /*!< Minimum connection interval in 125 us units. Range: 0x0003 to 0x7D00 */ + uint16_t conn_interval_max; /*!< Maximum connection interval in 125 us units. Range: 0x0003 to 0x7D00 */ + uint16_t subrate_min; /*!< Minimum subrate factor. Range: 0x0001 to 0x01F4 */ + uint16_t subrate_max; /*!< Maximum subrate factor. Range: 0x0001 to 0x01F4 */ + uint16_t max_latency; /*!< Maximum Peripheral latency in subrated connection intervals. Range: 0x0000 to 0x01F3 */ + uint16_t continuation_number; /*!< Continuation number. Range: 0x0000 to 0x01F3 */ + uint16_t supervision_timeout; /*!< Supervision timeout in 10 ms units. Range: 0x000A to 0x0C80 */ + uint16_t min_ce_len; /*!< Minimum connection event length in 125 us units */ + uint16_t max_ce_len; /*!< Maximum connection event length in 125 us units */ +} esp_ble_gap_connection_rate_request_params_t; + +/** + * @brief Default connection rate parameters for future Central connections + */ +typedef struct { + uint16_t conn_interval_min; /*!< Minimum connection interval in 125 us units. Range: 0x0003 to 0x7D00 */ + uint16_t conn_interval_max; /*!< Maximum connection interval in 125 us units. Range: 0x0003 to 0x7D00 */ + uint16_t subrate_min; /*!< Minimum subrate factor. Range: 0x0001 to 0x01F4 */ + uint16_t subrate_max; /*!< Maximum subrate factor. Range: 0x0001 to 0x01F4 */ + uint16_t max_latency; /*!< Maximum Peripheral latency in subrated connection intervals. Range: 0x0000 to 0x01F3 */ + uint16_t continuation_number; /*!< Continuation number. Range: 0x0000 to 0x01F3 */ + uint16_t supervision_timeout; /*!< Supervision timeout in 10 ms units. Range: 0x000A to 0x0C80 */ + uint16_t min_ce_len; /*!< Minimum connection event length in 125 us units */ + uint16_t max_ce_len; /*!< Maximum connection event length in 125 us units */ +} esp_ble_gap_default_rate_param_t; + +#define ESP_BLE_GAP_CONN_RATE_MAX_INTERVAL_GROUPS 41 + +/** + * @brief Supported connection interval group returned by read minimum supported connection interval + */ +typedef struct { + uint16_t min_125us; /*!< Lower bound of supported interval group in 125 us units */ + uint16_t max_125us; /*!< Upper bound of supported interval group in 125 us units */ + uint16_t stride_125us; /*!< Stride between supported intervals in 125 us units */ +} esp_ble_gap_min_conn_interval_group_t; +#endif // #if (BLE_FEAT_SHORTER_CONN_INTERVALS == TRUE) + +#if (BLE_FEAT_LE_UTP == TRUE) +#define ESP_BLE_GAP_UTP_DATA_MAX_LEN 254 + +/** + * @brief Parameters for enabling or disabling LE Unified Test Protocol (UTP) OTA mode + */ +typedef struct { + uint8_t enable; /*!< 0x00: Disable UTP OTA mode, 0x01: Enable UTP OTA mode */ +} esp_ble_gap_enable_utp_ota_mode_params_t; + +/** + * @brief Parameters for sending LE Unified Test Protocol (UTP) data + */ +typedef struct { + uint8_t data_len; /*!< UTP data length, range: 0x01 to 0xFE */ + const uint8_t *data; /*!< Pointer to UTP data */ +} esp_ble_gap_utp_send_params_t; +#endif // #if (BLE_FEAT_LE_UTP == TRUE) + typedef enum { ESP_BLE_NETWORK_PRIVACY_MODE = 0X00, /*!< Network Privacy Mode for peer device (default) */ ESP_BLE_DEVICE_PRIVACY_MODE = 0X01, /*!< Device Privacy Mode for peer device */ @@ -1536,6 +1732,34 @@ typedef enum { /** Reflector role is enabled */ #define ESP_BLE_CS_REFLECTOR_ROLE_ENABLED (1 << 1) +#if (BT_BLE_FEAT_CS_SECURITY_REQUIREMENTS == TRUE) +/** CS tone security requirement (bit 0 of CS_Security_Requirements) */ +#define ESP_BLE_CS_SECURITY_REQUIREMENT_CS_TONE (1ULL << 0) +/** 150 ns RTT accuracy security requirement (bit 1) */ +#define ESP_BLE_CS_SECURITY_REQUIREMENT_RTT_150NS_ACCURACY (1ULL << 1) +/** 10 ns RTT accuracy security requirement (bit 2) */ +#define ESP_BLE_CS_SECURITY_REQUIREMENT_RTT_10NS_ACCURACY (1ULL << 2) +/** RTT sounding sequence or random sequence security requirement (bit 3) */ +#define ESP_BLE_CS_SECURITY_REQUIREMENT_RTT_SOUNDING_OR_RANDOM (1ULL << 3) +/** Normalized Attack Detector Metric security requirement (bit 4) */ +#define ESP_BLE_CS_SECURITY_REQUIREMENT_NADM (1ULL << 4) + +/** +* @brief CS set security requirements parameters +*/ +typedef struct { + uint16_t conn_handle; /*!< Connection_Handle. Host does not validate the handle range; the Controller checks it (0x0000 to 0x0EFF). */ + uint64_t cs_security_requirements; /*!< 8-octet CS security requirements bitmask (bits 0-4). Host does not validate reserved bits 5-63; the Controller checks they are zero. */ +} esp_ble_cs_set_security_requirements_params; + +/** +* @brief CS set default security requirements parameters +*/ +typedef struct { + uint64_t cs_security_requirements; /*!< 8-octet CS security requirements bitmask for future connections (bits 0-4). Host does not validate reserved bits 5-63; the Controller checks they are zero. */ +} esp_ble_cs_set_default_security_requirements_params; +#endif // (BT_BLE_FEAT_CS_SECURITY_REQUIREMENTS == TRUE) + /** * @brief CS set default settings parameters */ @@ -2192,6 +2416,130 @@ typedef union { esp_bt_status_t status; /*!< Indicate enable/disable monitor advertising operation success status */ } enable_monitor_adv; /*!< Event parameter of ESP_GAP_BLE_ENABLE_MONITOR_ADV_COMPLETE_EVT */ #endif +#if (BLE_FEAT_DBAF == TRUE) + /** + * @brief ESP_GAP_BLE_SET_DECISION_DATA_COMPLETE_EVT + */ + struct ble_set_decision_data_cmpl_param { + esp_bt_status_t status; /*!< Indicate set decision data operation success status */ + } set_decision_data; /*!< Event parameter of ESP_GAP_BLE_SET_DECISION_DATA_COMPLETE_EVT */ + /** + * @brief ESP_GAP_BLE_SET_DECISION_INSTRUCTIONS_COMPLETE_EVT + */ + struct ble_set_decision_instructions_cmpl_param { + esp_bt_status_t status; /*!< Indicate set decision instructions operation success status */ + } set_decision_instructions; /*!< Event parameter of ESP_GAP_BLE_SET_DECISION_INSTRUCTIONS_COMPLETE_EVT */ +#endif // #if (BLE_FEAT_DBAF == TRUE) +#if (BLE_FEAT_FRAME_SPACE_UPDATE == TRUE) + /** + * @brief ESP_GAP_BLE_FRAME_SPACE_UPDATE_COMPLETE_EVT + */ + struct ble_frame_space_update_cmpl_param { + esp_bt_status_t status; /*!< Indicate frame space update operation success status */ + uint16_t conn_handle; /*!< Connection handle */ + uint8_t initiator; /*!< 0x00: Local Host initiated, 0x01: Local Controller initiated, 0x02: Peer initiated */ + uint16_t frame_space; /*!< Updated frame space in microseconds */ + uint8_t phys; /*!< PHY mask updated */ + uint16_t spacing_types; /*!< Spacing types mask updated */ + } frame_space_update; /*!< Event parameter of ESP_GAP_BLE_FRAME_SPACE_UPDATE_COMPLETE_EVT */ +#endif // #if (BLE_FEAT_FRAME_SPACE_UPDATE == TRUE) +#if (BLE_FEAT_LL_EXT_FEAT == TRUE) + /** + * @brief ESP_GAP_BLE_READ_ALL_LOCAL_SUPP_FEAT_COMPLETE_EVT + */ + struct ble_read_all_local_supp_feat_cmpl_param { + esp_bt_status_t status; /*!< Indicate read all local supported LE features operation success status */ + uint8_t max_page; /*!< Maximum supported features page number */ + uint8_t le_features[ESP_BLE_GAP_LL_EXT_FEAT_DATA_LEN]; /*!< LE features data */ + } read_all_local_supp_feat; /*!< Event parameter of ESP_GAP_BLE_READ_ALL_LOCAL_SUPP_FEAT_COMPLETE_EVT */ + /** + * @brief ESP_GAP_BLE_READ_ALL_REMOTE_FEAT_COMPLETE_EVT + */ + struct ble_read_all_remote_feat_cmpl_param { + esp_bt_status_t status; /*!< Indicate read all remote LE features operation success status */ + uint16_t conn_handle; /*!< Connection handle */ + uint8_t max_remote_page; /*!< Maximum remote features page number supported by peer */ + uint8_t max_valid_page; /*!< Maximum valid features page number in le_features */ + uint8_t le_features[ESP_BLE_GAP_LL_EXT_FEAT_DATA_LEN]; /*!< Remote LE features data */ + } read_all_remote_feat; /*!< Event parameter of ESP_GAP_BLE_READ_ALL_REMOTE_FEAT_COMPLETE_EVT */ +#endif // #if (BLE_FEAT_LL_EXT_FEAT == TRUE) +#if (BLE_FEAT_SHORTER_CONN_INTERVALS == TRUE) + /** + * @brief ESP_GAP_BLE_CONNECTION_RATE_REQUEST_COMPLETE_EVT + * + * Reported when the HCI Connection Rate Request command is accepted or rejected by the controller, + * or when the host fails to send the command locally. + */ + struct ble_connection_rate_request_cmpl_param { + esp_bt_status_t status; /*!< Command success status, or host/controller error code */ + uint16_t conn_handle; /*!< Connection handle */ + } connection_rate_req_cmpl; /*!< Event parameter of ESP_GAP_BLE_CONNECTION_RATE_REQUEST_COMPLETE_EVT */ + /** + * @brief ESP_GAP_BLE_CONN_RATE_CHANGE_EVT + */ + struct ble_conn_rate_change_evt { + esp_bt_status_t status; /*!< Indicate connection rate change status */ + uint16_t conn_handle; /*!< Connection handle */ + uint16_t conn_interval; /*!< Underlying connection interval in 125 us units */ + uint16_t subrate_factor; /*!< Subrate factor applied to the connection interval */ + uint16_t peripheral_latency; /*!< Peripheral latency in subrated connection intervals */ + uint16_t continuation_number; /*!< Continuation number */ + uint16_t supervision_timeout; /*!< Supervision timeout in 10 ms units */ + } conn_rate_change_evt; /*!< Event parameter of ESP_GAP_BLE_CONN_RATE_CHANGE_EVT. Effective interval (us) = ESP_BLE_GAP_CONN_RATE_EFF_INTERVAL_US(conn_interval, subrate_factor) */ + /** + * @brief ESP_GAP_BLE_SET_DEFAULT_RATE_PARAMETERS_COMPLETE_EVT + */ + struct ble_set_default_rate_parameters_cmpl_param { + esp_bt_status_t status; /*!< Indicate set default rate parameters operation success status */ + } set_default_rate_parameters_cmpl; /*!< Event parameter of ESP_GAP_BLE_SET_DEFAULT_RATE_PARAMETERS_COMPLETE_EVT */ + /** + * @brief ESP_GAP_BLE_READ_MIN_SUPP_CONN_INTERVAL_COMPLETE_EVT + */ + struct ble_read_min_supp_conn_interval_cmpl_param { + esp_bt_status_t status; /*!< Indicate read minimum supported connection interval operation success status */ + uint8_t min_supported_conn_interval; /*!< Minimum supported connection interval in 125 us units */ + uint8_t num_groups; /*!< Number of supported interval groups; 0 if only RCV is supported */ + esp_ble_gap_min_conn_interval_group_t groups[ESP_BLE_GAP_CONN_RATE_MAX_INTERVAL_GROUPS]; /*!< Supported interval groups; valid when num_groups > 0 */ + } read_min_supp_conn_interval; /*!< Event parameter of ESP_GAP_BLE_READ_MIN_SUPP_CONN_INTERVAL_COMPLETE_EVT */ +#endif // #if (BLE_FEAT_SHORTER_CONN_INTERVALS == TRUE) +#if (BLE_FEAT_LE_UTP == TRUE) + /** + * @brief ESP_GAP_BLE_ENABLE_UTP_OTA_MODE_COMPLETE_EVT + */ + struct ble_enable_utp_ota_mode_cmpl_param { + esp_bt_status_t status; /*!< Indicate enable UTP OTA mode operation success status */ + } enable_utp_ota_mode_cmpl; /*!< Event parameter of ESP_GAP_BLE_ENABLE_UTP_OTA_MODE_COMPLETE_EVT */ + /** + * @brief ESP_GAP_BLE_UTP_SEND_COMPLETE_EVT + */ + struct ble_utp_send_cmpl_param { + esp_bt_status_t status; /*!< Indicate UTP send operation success status */ + } utp_send_cmpl; /*!< Event parameter of ESP_GAP_BLE_UTP_SEND_COMPLETE_EVT */ + /** + * @brief ESP_GAP_BLE_UTP_RECEIVE_EVT + */ + struct ble_utp_receive_evt { + uint8_t len; /*!< UTP data length */ + uint8_t data[ESP_BLE_GAP_UTP_DATA_MAX_LEN]; /*!< UTP data */ + } utp_receive; /*!< Event parameter of ESP_GAP_BLE_UTP_RECEIVE_EVT */ +#endif // #if (BLE_FEAT_LE_UTP == TRUE) +#if (BT_BLE_FEAT_CS_SECURITY_REQUIREMENTS == TRUE) + /** + * @brief ESP_GAP_BLE_CS_SET_SECURITY_REQUIREMENTS_CMPL_EVT + */ + struct ble_cs_set_security_requirements { + esp_bt_status_t status; /*!< 0x00: CS set security requirements command succeeded + other: CS set security requirements command failed */ + uint16_t conn_handle; /*!< Connection Handle */ + } cs_set_security_requirements; /*!< Event parameter of ESP_GAP_BLE_CS_SET_SECURITY_REQUIREMENTS_CMPL_EVT */ + /** + * @brief ESP_GAP_BLE_CS_SET_DEFAULT_SECURITY_REQUIREMENTS_CMPL_EVT + */ + struct ble_cs_set_default_security_requirements { + esp_bt_status_t status; /*!< 0x00: CS set default security requirements command succeeded + other: CS set default security requirements command failed */ + } cs_set_default_security_requirements; /*!< Event parameter of ESP_GAP_BLE_CS_SET_DEFAULT_SECURITY_REQUIREMENTS_CMPL_EVT */ +#endif // (BT_BLE_FEAT_CS_SECURITY_REQUIREMENTS == TRUE) /** * @brief ESP_GAP_BLE_CHANNEL_SELECT_ALGORITHM_EVT */ @@ -2517,7 +2865,6 @@ typedef union { */ struct ble_cs_read_local_supp_caps_evt { esp_bt_status_t status; /*!< Indicate channel sounding read local supported capabilities command successfully completed */ - uint16_t conn_handle; /*!< Connection Handle */ uint8_t num_config_supported; /*!< Number of CS configurations supported per connection */ uint16_t max_consecutive_proc_supported; /*!< 0x0000: Support for both a fixed number of consecutive CS procedures and for an indefinite number of CS procedures until termination 0x0001 to 0xFFFF: Maximum number of consecutive CS procedures supported */ @@ -2895,7 +3242,11 @@ typedef void (* esp_gap_ble_cb_t)(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_p * * @param[in] callback: callback function * - * @note Avoid performing time-consuming operations within the callback functions. + * @note Do NOT perform time-consuming operations in the callback. Time-consuming operations + * include: taking semaphores that may block for a long time (e.g. xSemaphoreTake with + * long timeout or portMAX_DELAY), blocking delays (e.g. vTaskDelay), and flash + * read/write/erase. Such operations may block the Bluetooth stack and lead to + * instability or deadlock. Defer heavy work to a separate task if needed. * * @return * - ESP_OK : success @@ -3094,6 +3445,11 @@ esp_err_t esp_ble_gap_add_device_to_resolving_list(esp_bd_addr_t peer_addr, uint /** * @brief This function clears the random address for the application * + * @note This function shall not be used when: + * - Advertising is enabled, + * - Scanning is enabled, or + * - any LE connection exists / a create connection command is pending. + * * @return * - ESP_OK : success * - other : failed @@ -3632,6 +3988,12 @@ esp_err_t esp_ble_gap_read_phy(esp_bd_addr_t bd_addr); * @param[in] tx_phy_mask : indicates the transmitter PHYs that the Host prefers the Controller to use * @param[in] rx_phy_mask : indicates the receiver PHYs that the Host prefers the Controller to use * +* @note Including the LE Coded PHY in tx_phy_mask / rx_phy_mask may cause subsequent ACL connections to run on +* the LE Coded PHY, which will significantly degrade Wi-Fi performance in Bluetooth/Wi-Fi coexistence +* scenarios because Coded PHY (S=2/S=8) packets occupy the radio for much longer than 1M/2M PHY packets. +* It is recommended to use the LE 2M PHY (or LE 1M PHY) first, and only include the LE Coded PHY when the +* long-range capability is really required. +* * @return - ESP_OK : success * - other : failed * @@ -3648,6 +4010,11 @@ esp_err_t esp_ble_gap_set_preferred_default_phy(esp_ble_gap_phy_mask_t tx_phy_ma * @param[in] rx_phy_mask : a bit field that indicates the receiver PHYs that the Host prefers the Controller to use * @param[in] phy_options : a bit field that allows the Host to specify options for PHYs * +* @note Switching an existing ACL connection to the LE Coded PHY via tx_phy_mask / rx_phy_mask will significantly +* degrade Wi-Fi performance in Bluetooth/Wi-Fi coexistence scenarios, because Coded PHY (S=2/S=8) packets +* occupy the radio for much longer than 1M/2M PHY packets. It is recommended to use the LE 2M PHY (or LE 1M +* PHY) first, and only switch to the LE Coded PHY when the long-range capability is really required. +* * @return - ESP_OK : success * - other : failed * @@ -3960,6 +4327,12 @@ esp_err_t esp_ble_gap_get_periodic_list_size(uint8_t *size); * @param[in] phy_2m_conn_params : Connection parameters for the LE 2M PHY are provided. * @param[in] phy_coded_conn_params : Scan connectable advertisements on the LE Coded PHY. Connection parameters for the LE Coded PHY are provided. * +* @note Using the LE Coded PHY for the ACL connection will significantly degrade Wi-Fi performance, because +* the on-air transmission time of a Coded PHY packet (S=2 or S=8) is much longer than that of a 1M/2M PHY +* packet, so the Bluetooth controller occupies the radio for a longer time and leaves less airtime for Wi-Fi. +* In Bluetooth/Wi-Fi coexistence scenarios, it is recommended to use the LE 2M PHY or LE 1M PHY first, and +* only fall back to the LE Coded PHY when the long-range capability is really required. +* * @return - ESP_OK : success * - other : failed * @@ -4028,6 +4401,145 @@ esp_err_t esp_ble_gap_read_monitor_adv_list_size(void); */ esp_err_t esp_ble_gap_enable_monitor_adv(bool enable); #endif // #if (BLE_FEAT_ADV_MONITOR == TRUE) + +#if (BLE_FEAT_DBAF == TRUE) +/** +* @brief Set decision data for an advertising set (DBAF). +* +* @param[in] params : Pointer to decision data parameters. +* +* @return - ESP_OK : success +* - other : failed +* +*/ +esp_err_t esp_ble_gap_set_decision_data(const esp_ble_gap_set_decision_data_params_t *params); + +/** +* @brief Set decision instructions for decision-based advertising filtering (DBAF). +* +* @param[in] params : Pointer to decision instructions parameters. +* +* @return - ESP_OK : success +* - other : failed +* +*/ +esp_err_t esp_ble_gap_set_decision_instructions(const esp_ble_gap_set_decision_instructions_params_t *params); +#endif // #if (BLE_FEAT_DBAF == TRUE) + +#if (BLE_FEAT_FRAME_SPACE_UPDATE == TRUE) +/** +* @brief Request a Frame Space Update on a connection. +* +* @param[in] params : Pointer to frame space update parameters. +* +* @return - ESP_OK : success +* - other : failed +* +*/ +esp_err_t esp_ble_gap_frame_space_update(const esp_ble_gap_frame_space_update_params_t *params); +#endif // #if (BLE_FEAT_FRAME_SPACE_UPDATE == TRUE) + +#if (BLE_FEAT_LL_EXT_FEAT == TRUE) +/** +* @brief Read all local supported LE features (LL Extended Feature Set). +* +* @return - ESP_OK : success (command sent) +* - other : failed +* +*/ +esp_err_t esp_ble_gap_read_all_local_supp_features(void); + +/** +* @brief Read all remote LE features for a connection (LL Extended Feature Set). +* +* @param[in] params : Pointer to read remote features parameters. +* +* @return - ESP_OK : success (command sent) +* - other : failed +* +*/ +esp_err_t esp_ble_gap_read_all_remote_features(const esp_ble_gap_read_all_remote_feat_params_t *params); +#endif // #if (BLE_FEAT_LL_EXT_FEAT == TRUE) + +#if (BLE_FEAT_SHORTER_CONN_INTERVALS == TRUE) +/** +* @brief Request a connection rate update (Shorter Connection Intervals, Core 6.2). +* +* This API may be used by a Central or a Peripheral to request a change to the +* connection interval, subrate factor, and/or other connection parameters. +* `conn_interval_min/max` use 125 us units (minimum 375 us = 3). +* Use ESP_BLE_GAP_CONN_RATE_INTERVAL_FROM_MS() or ESP_BLE_GAP_CONN_RATE_INTERVAL_FROM_US() +* to convert from common time units. +* +* When the controller accepts or rejects the HCI command, `ESP_GAP_BLE_CONNECTION_RATE_REQUEST_COMPLETE_EVT` +* is reported with `status` and `conn_handle`. On command acceptance, `ESP_GAP_BLE_CONN_RATE_CHANGE_EVT` +* subsequently reports the applied parameters. The effective connection interval in microseconds is: +* ESP_BLE_GAP_CONN_RATE_EFF_INTERVAL_US(conn_interval, subrate_factor). +* +* @param[in] params : Pointer to connection rate request parameters. +* +* @return - ESP_OK : success (command sent) +* - ESP_ERR_INVALID_ARG : invalid parameters +* - ESP_ERR_INVALID_STATE : Bluedroid is not enabled +* - ESP_FAIL : other failures +* +*/ +esp_err_t esp_ble_gap_connection_rate_request(const esp_ble_gap_connection_rate_request_params_t *params); + +/** +* @brief Set default connection rate parameters for future Central connections (Core 6.2). +* +* Preconfigures the default connection rate parameters that may be requested by a +* Peripheral on new ACL connections where the local device is Central. +* +* @param[in] params : Pointer to default rate parameters. +* +* @return - ESP_OK : success +* - ESP_ERR_INVALID_ARG : invalid parameters +* - ESP_ERR_INVALID_STATE : Bluedroid is not enabled +* - ESP_FAIL : other failures +* +*/ +esp_err_t esp_ble_gap_set_default_rate_parameters(const esp_ble_gap_default_rate_param_t *params); + +/** +* @brief Read the Controller minimum supported connection interval and interval groups. +* +* On success, `ESP_GAP_BLE_READ_MIN_SUPP_CONN_INTERVAL_COMPLETE_EVT` returns the +* minimum supported interval and optional interval groups. If `num_groups` is 0, +* the Controller only supports Rounded Connection Interval Values (RCV). +* +* @return - ESP_OK : success (command sent) +* - ESP_ERR_INVALID_STATE : Bluedroid is not enabled +* - ESP_FAIL : other failures +* +*/ +esp_err_t esp_ble_gap_read_min_supported_connection_interval(void); +#endif // #if (BLE_FEAT_SHORTER_CONN_INTERVALS == TRUE) + +#if (BLE_FEAT_LE_UTP == TRUE) +/** +* @brief Enable or disable LE Unified Test Protocol (UTP) OTA mode. +* +* @param[in] params : Pointer to enable UTP OTA mode parameters. +* +* @return - ESP_OK : success +* - other : failed +* +*/ +esp_err_t esp_ble_gap_enable_utp_ota_mode(const esp_ble_gap_enable_utp_ota_mode_params_t *params); + +/** +* @brief Send LE UTP data. +* +* @param[in] params : Pointer to UTP send parameters. +* +* @return - ESP_OK : success +* - other : failed +* +*/ +esp_err_t esp_ble_gap_utp_send(const esp_ble_gap_utp_send_params_t *params); +#endif // #if (BLE_FEAT_LE_UTP == TRUE) #endif //#if (BLE_50_FEATURE_SUPPORT == TRUE) #if (BLE_FEAT_PERIODIC_ADV_SYNC_TRANSFER == TRUE) @@ -4385,11 +4897,17 @@ esp_err_t esp_ble_gap_set_default_subrate(esp_ble_default_subrate_param_t *defau */ esp_err_t esp_ble_gap_subrate_request(esp_ble_subrate_req_param_t *subrate_req_params); +/** Host Feature Set bit position: Channel Sounding Host Support (Bluetooth Core, bit 47) */ +#define ESP_BLE_HOST_FEATURE_CS_HOST_SUPPORT 47 + /** * @brief This function is called to set host feature. * - * @param[in] bit_num: the bit position in the FeatureSet. - * @param[in] bit_val: the feature is enabled or disabled + * @param[in] bit_num: the bit position in the FeatureSet (e.g. ESP_BLE_HOST_FEATURE_CS_HOST_SUPPORT). + * @param[in] bit_val: 0x00 to disable, 0x01 to enable Host support for the feature + * + * @note Channel Sounding Host APIs require CS Host Support enabled first: + * esp_ble_gap_set_host_feature(ESP_BLE_HOST_FEATURE_CS_HOST_SUPPORT, 1). * * @return * - ESP_OK : success @@ -4493,6 +5011,50 @@ esp_err_t esp_ble_cs_write_cached_remote_supported_capabilities(esp_ble_cs_write */ esp_err_t esp_ble_cs_security_enable(uint16_t conn_handle); +#if (BT_BLE_FEAT_CS_SECURITY_REQUIREMENTS == TRUE) +/** + * @brief Set Channel Sounding security requirements for a connection (Core 6.3). + * + * Requires CONFIG_BT_BLE_FEAT_CS_SECURITY_REQUIREMENTS and CS Host Support enabled + * via esp_ble_gap_set_host_feature(ESP_BLE_HOST_FEATURE_CS_HOST_SUPPORT, 1). + * Must be issued before CS procedures are enabled on the connection. + * + * The Host does not validate Connection_Handle range or CS_Security_Requirements + * reserved bits (5-63); the Controller performs these checks and reports errors + * via ESP_GAP_BLE_CS_SET_SECURITY_REQUIREMENTS_CMPL_EVT. + * + * @param[in] params: CS set security requirements parameters + * + * @return + * - ESP_OK : success (command queued) + * - ESP_ERR_INVALID_ARG : params is NULL + * - ESP_ERR_INVALID_STATE : Bluedroid is not enabled + * - ESP_FAIL : other failures + */ +esp_err_t esp_ble_cs_set_security_requirements(esp_ble_cs_set_security_requirements_params *params); + +/** + * @brief Set initial Channel Sounding security requirements for future connections (Core 6.3). + * + * Requires CONFIG_BT_BLE_FEAT_CS_SECURITY_REQUIREMENTS and CS Host Support enabled + * via esp_ble_gap_set_host_feature(ESP_BLE_HOST_FEATURE_CS_HOST_SUPPORT, 1). + * Does not affect existing connections. + * + * The Host does not validate CS_Security_Requirements reserved bits (5-63); + * the Controller performs this check and reports errors via + * ESP_GAP_BLE_CS_SET_DEFAULT_SECURITY_REQUIREMENTS_CMPL_EVT. + * + * @param[in] params: CS set default security requirements parameters + * + * @return + * - ESP_OK : success (command queued) + * - ESP_ERR_INVALID_ARG : params is NULL + * - ESP_ERR_INVALID_STATE : Bluedroid is not enabled + * - ESP_FAIL : other failures + */ +esp_err_t esp_ble_cs_set_default_security_requirements(esp_ble_cs_set_default_security_requirements_params *params); +#endif // (BT_BLE_FEAT_CS_SECURITY_REQUIREMENTS == TRUE) + /** * @brief This function is used to set default CS settings in the local Controller * diff --git a/components/bt/host/bluedroid/api/include/api/esp_gatt_defs.h b/components/bt/host/bluedroid/api/include/api/esp_gatt_defs.h index dcbfe2f6ebc..03ba9a01624 100644 --- a/components/bt/host/bluedroid/api/include/api/esp_gatt_defs.h +++ b/components/bt/host/bluedroid/api/include/api/esp_gatt_defs.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -361,10 +361,15 @@ typedef struct { /** * @brief Defines the GATT authentication request types. * - * This enumeration lists the types of authentication requests that can be made. - * It corresponds to the `BTA_GATT_AUTH_REQ_xxx` values defined in `bta/bta_gatt_api.h`. - * The types include options for no authentication, unauthenticated encryption, authenticated encryption, - * and both signed versions with and without MITM (Man-In-The-Middle) protection. + * Used as the `auth_req` argument in GATT client read/write APIs. It specifies the + * link security level required before the ATT request is sent, and is independent + * of server-side attribute permission flags (`ESP_GATT_PERM_xxx`). + * + * @note If `auth_req` is not `ESP_GATT_AUTH_REQ_NONE`, the stack may start link + * encryption or SMP pairing before the GATT operation. Handle + * `ESP_GAP_BLE_PASSKEY_REQ_EVT` and call `esp_ble_passkey_reply()` if needed. + * + * Corresponds to the `BTA_GATT_AUTH_REQ_xxx` values defined in `bta/bta_gatt_api.h`. */ typedef enum { ESP_GATT_AUTH_REQ_NONE = 0, /*!< No authentication required. Corresponds to BTA_GATT_AUTH_REQ_NONE. */ @@ -687,12 +692,31 @@ typedef struct { esp_bd_addr_t remote_bda; /*!< The Bluetooth address of the remote device */ esp_ble_addr_type_t remote_addr_type; /*!< Address type of the remote device */ bool is_direct; /*!< Direct connection or background auto connection(by now, background auto connection is not supported */ - bool is_aux; /*!< Set to true for BLE 5.0 or higher to enable auxiliary connections; set to false for BLE 4.2 or lower. */ + bool is_aux; /*!< Determines whether to use BLE 5.0 or BLE 4.2 create connection interface. + - If set to true, the BLE 5.0 interface (extended connection) will be used. + - If set to false, the BLE 4.2 interface (legacy connection) will be used. + - Note: When connecting to a legacy advertising device using BLE 5.0 interface, is_aux should be set to true. + - Auto-setting (handled in L2CAP layer): The system will automatically set this parameter based on the enabled BLE features: + * If only BLE 4.2 feature is enabled, is_aux will be automatically set to false. + * If only BLE 5.0 feature is enabled, is_aux will be automatically set to true. + * If both BLE 4.2 and BLE 5.0 features are enabled (not recommended), the stack will automatically + infer whether to use BLE 5.0 or BLE 4.2 interface based on previously used APIs. + Otherwise, the user-specified value will be used. + - Note: It is strongly recommended NOT to enable both BLE 4.2 and BLE 5.0 features simultaneously. */ esp_ble_addr_type_t own_addr_type; /*!< Specifies the address type used in the connection request. Set to 0xFF if the address type is unknown. */ esp_ble_phy_mask_t phy_mask; /*!< Indicates which PHY connection parameters will be used. When is_aux is false, only the connection params for 1M PHY can be specified */ const esp_ble_conn_params_t *phy_1m_conn_params; /*!< Connection parameters for the LE 1M PHY */ const esp_ble_conn_params_t *phy_2m_conn_params; /*!< Connection parameters for the LE 2M PHY */ - const esp_ble_conn_params_t *phy_coded_conn_params; /*!< Connection parameters for the LE Coded PHY */ + const esp_ble_conn_params_t *phy_coded_conn_params; /*!< Connection parameters for the LE Coded PHY. + Note: Establishing an ACL connection over the LE Coded PHY + will significantly degrade Wi-Fi performance, because the + on-air transmission time of a Coded PHY packet (S=2 or S=8) + is much longer than that of a 1M/2M PHY packet, so the + Bluetooth controller occupies the radio for a longer time + and leaves less airtime for Wi-Fi. In Bluetooth/Wi-Fi + coexistence scenarios, it is recommended to use the LE 2M + PHY or LE 1M PHY first, and only fall back to the LE Coded + PHY when the long-range capability is really required. */ } esp_ble_gatt_creat_conn_params_t; /** @brief Represents a creat connection element. */ @@ -705,7 +729,16 @@ typedef struct { esp_ble_phy_mask_t phy_mask; /*!< Indicates which PHY connection parameters will be used. When is_aux is false, only the connection params for 1M PHY can be specified */ const esp_ble_conn_params_t *phy_1m_conn_params; /*!< Connection parameters for the LE 1M PHY */ const esp_ble_conn_params_t *phy_2m_conn_params; /*!< Connection parameters for the LE 2M PHY */ - const esp_ble_conn_params_t *phy_coded_conn_params; /*!< Connection parameters for the LE Coded PHY */ + const esp_ble_conn_params_t *phy_coded_conn_params; /*!< Connection parameters for the LE Coded PHY. + Note: Establishing an ACL connection over the LE Coded PHY + will significantly degrade Wi-Fi performance, because the + on-air transmission time of a Coded PHY packet (S=2 or S=8) + is much longer than that of a 1M/2M PHY packet, so the + Bluetooth controller occupies the radio for a longer time + and leaves less airtime for Wi-Fi. In Bluetooth/Wi-Fi + coexistence scenarios, it is recommended to use the LE 2M + PHY or LE 1M PHY first, and only fall back to the LE Coded + PHY when the long-range capability is really required. */ } esp_ble_gatt_pawr_conn_params_t; #ifdef __cplusplus diff --git a/components/bt/host/bluedroid/api/include/api/esp_gattc_api.h b/components/bt/host/bluedroid/api/include/api/esp_gattc_api.h index 3426e330000..800bcf5c768 100644 --- a/components/bt/host/bluedroid/api/include/api/esp_gattc_api.h +++ b/components/bt/host/bluedroid/api/include/api/esp_gattc_api.h @@ -280,7 +280,11 @@ typedef void (* esp_gattc_cb_t)(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_ * * @param[in] callback The pointer to the application callback function * - * @note Avoid performing time-consuming operations within the callback functions. + * @note Do NOT perform time-consuming operations in the callback. Time-consuming operations + * include: taking semaphores that may block for a long time (e.g. xSemaphoreTake with + * long timeout or portMAX_DELAY), blocking delays (e.g. vTaskDelay), and flash + * read/write/erase. Such operations may block the Bluetooth stack and lead to + * instability or deadlock. Defer heavy work to a separate task if needed. * * @return * - ESP_OK: Success @@ -330,10 +334,22 @@ esp_err_t esp_ble_gattc_app_unregister(esp_gatt_if_t gattc_if); * * @note * 1. Do not enable `BT_BLE_42_FEATURES_SUPPORTED` and `BT_BLE_50_FEATURES_SUPPORTED` in the menuconfig simultaneously. - * 1. The function always triggers `ESP_GATTC_CONNECT_EVT` and `ESP_GATTC_OPEN_EVT`. - * 2. When the device acts as GATT server, besides the above two events, this function triggers `ESP_GATTS_CONNECT_EVT` as well. - * 3. This function will establish an ACL connection as a Central and a virtual connection as a GATT Client. If the ACL connection already exists, it will create a virtual connection only. - + * 2. The function always triggers `ESP_GATTC_CONNECT_EVT` and `ESP_GATTC_OPEN_EVT`. + * 3. When the device acts as GATT server, besides the above two events, this function triggers `ESP_GATTS_CONNECT_EVT` as well. + * 4. This function will establish an ACL connection as a Central and a virtual connection as a GATT Client. If the ACL connection already exists, it will create a virtual connection only. + * 5. The `is_aux` parameter in `esp_gatt_creat_conn_params_t` determines which connection interface to use: + * - If `is_aux` is true, the BLE 5.0 extended connection interface will be used. + * - If `is_aux` is false, the BLE 4.2 interface (legacy connection) will be used. + * - When connecting to a legacy advertising device using BLE 5.0 interface, `is_aux` should be set to true. + * 6. Auto-setting of `is_aux` parameter (handled in L2CAP layer): + * - If only BLE 4.2 feature is enabled, `is_aux` will be automatically set to false. + * - If only BLE 5.0 feature is enabled, `is_aux` will be automatically set to true. + * - If both BLE 4.2 and BLE 5.0 features are enabled (not recommended): + * * The stack will automatically infer whether to use BLE 5.0 or BLE 4.2 interface + * based on previously used APIs. + * * Otherwise, the user-specified value will be used. + * - Note: It is strongly recommended NOT to enable both BLE 4.2 and BLE 5.0 features + * simultaneously in menuconfig. * * @param[in] gattc_if: GATT client access interface. * @param[in] esp_gatt_create_conn: Pointer to the structure containing connection parameters. @@ -503,8 +519,12 @@ esp_err_t esp_ble_gattc_search_service(esp_gatt_if_t gattc_if, uint16_t conn_id, * 2. `esp_ble_gattc_cache_refresh` can be used to discover services again. * * @return - * - ESP_OK: Success - * - ESP_FAIL: Failure + * - ESP_GATT_OK: Success + * - ESP_GATT_WRONG_STATE: Bluedroid stack is not enabled + * - ESP_GATT_INVALID_PDU: NULL pointer to `result` or NULL pointer to `count` or the count value is 0 + * - ESP_GATT_NOT_FOUND: No matching service was found in the local cache + * - ESP_GATT_INVALID_OFFSET: `offset` is out of range of the matched services + * - ESP_GATT_NO_RESOURCES: Failed to allocate memory for the UUID lookup */ esp_gatt_status_t esp_ble_gattc_get_service(esp_gatt_if_t gattc_if, uint16_t conn_id, esp_bt_uuid_t *svc_uuid, esp_gattc_service_elem_t *result, uint16_t *count, uint16_t offset); @@ -522,13 +542,17 @@ esp_gatt_status_t esp_ble_gattc_get_service(esp_gatt_if_t gattc_if, uint16_t con * * @note * 1. This API does not trigger any event. - * 2. `start_handle` must be greater than 0, and smaller than `end_handle`. + * 2. `start_handle` must not be greater than `end_handle`, and `start_handle` and + * `end_handle` must not both be 0. 0 is allowed for `start_handle` alone and matches + * cached attributes from the beginning of the handle range. * * @return - * - ESP_OK: Success + * - ESP_GATT_OK: Success + * - ESP_GATT_WRONG_STATE: Bluedroid stack is not enabled * - ESP_GATT_INVALID_HANDLE: Invalid GATT `start_handle` or `end_handle` * - ESP_GATT_INVALID_PDU: NULL pointer to `result` or NULL pointer to `count` or the count value is 0 - * - ESP_FAIL: Failure due to other reasons + * - ESP_GATT_NOT_FOUND: No characteristic found in the given handle range + * - ESP_GATT_INVALID_OFFSET: `offset` is out of range of the matched characteristics */ esp_gatt_status_t esp_ble_gattc_get_all_char(esp_gatt_if_t gattc_if, uint16_t conn_id, @@ -552,10 +576,12 @@ esp_gatt_status_t esp_ble_gattc_get_all_char(esp_gatt_if_t gattc_if, * 2. `char_handle` must be greater than 0. * * @return - * - ESP_OK: Success + * - ESP_GATT_OK: Success + * - ESP_GATT_WRONG_STATE: Bluedroid stack is not enabled * - ESP_GATT_INVALID_HANDLE: Invalid GATT `char_handle` * - ESP_GATT_INVALID_PDU: NULL pointer to `result` or NULL pointer to `count` or the count value is 0 - * - ESP_FAIL: Failure due to other reasons + * - ESP_GATT_NOT_FOUND: No descriptor found under the given characteristic + * - ESP_GATT_INVALID_OFFSET: `offset` is out of range of the matched descriptors */ esp_gatt_status_t esp_ble_gattc_get_all_descr(esp_gatt_if_t gattc_if, uint16_t conn_id, @@ -576,13 +602,16 @@ esp_gatt_status_t esp_ble_gattc_get_all_descr(esp_gatt_if_t gattc_if, * * @note * 1. This API does not trigger any event. - * 2. `start_handle` must be greater than 0, and smaller than `end_handle`. + * 2. `start_handle` must not be greater than `end_handle`, and `start_handle` and + * `end_handle` must not both be 0. 0 is allowed for `start_handle` alone and matches + * cached attributes from the beginning of the handle range. * * @return - * - ESP_OK: Success + * - ESP_GATT_OK: Success + * - ESP_GATT_WRONG_STATE: Bluedroid stack is not enabled * - ESP_GATT_INVALID_HANDLE: Invalid GATT `start_handle` or `end_handle` * - ESP_GATT_INVALID_PDU: NULL pointer to `result` or NULL pointer to `count` or the count value is 0 - * - ESP_FAIL: Failure due to other reasons + * - ESP_GATT_NOT_FOUND: No characteristic matching `char_uuid` found in the handle range */ esp_gatt_status_t esp_ble_gattc_get_char_by_uuid(esp_gatt_if_t gattc_if, uint16_t conn_id, @@ -606,12 +635,16 @@ esp_gatt_status_t esp_ble_gattc_get_char_by_uuid(esp_gatt_if_t gattc_if, * * @note * 1. This API does not trigger any event. - * 2. `start_handle` must be greater than 0, and smaller than `end_handle`. + * 2. `start_handle` must not be greater than `end_handle`, and `start_handle` and + * `end_handle` must not both be 0. 0 is allowed for `start_handle` alone and matches + * cached attributes from the beginning of the handle range. * * @return - * - ESP_OK: Success + * - ESP_GATT_OK: Success + * - ESP_GATT_WRONG_STATE: Bluedroid stack is not enabled + * - ESP_GATT_INVALID_HANDLE: Invalid GATT `start_handle` or `end_handle` * - ESP_GATT_INVALID_PDU: NULL pointer to `result` or NULL pointer to `count` or the count value is 0 - * - ESP_FAIL: Failure due to other reasons + * - ESP_GATT_NOT_FOUND: No descriptor matching the given UUIDs found in the handle range */ esp_gatt_status_t esp_ble_gattc_get_descr_by_uuid(esp_gatt_if_t gattc_if, uint16_t conn_id, @@ -637,10 +670,11 @@ esp_gatt_status_t esp_ble_gattc_get_descr_by_uuid(esp_gatt_if_t gattc_if, * 2. `char_handle` must be greater than 0. * * @return - * - ESP_OK: Success + * - ESP_GATT_OK: Success + * - ESP_GATT_WRONG_STATE: Bluedroid stack is not enabled * - ESP_GATT_INVALID_HANDLE: Invalid GATT `char_handle` * - ESP_GATT_INVALID_PDU: NULL pointer to `result` or NULL pointer to `count` or the count value is 0 - * - ESP_FAIL: Failure due to other reasons + * - ESP_GATT_NOT_FOUND: No descriptor matching `descr_uuid` found under `char_handle` */ esp_gatt_status_t esp_ble_gattc_get_descr_by_char_handle(esp_gatt_if_t gattc_if, uint16_t conn_id, @@ -662,12 +696,16 @@ esp_gatt_status_t esp_ble_gattc_get_descr_by_char_handle(esp_gatt_if_t gattc_if, * * @note * 1. This API does not trigger any event. - * 2. `start_handle` must be greater than 0, and smaller than `end_handle`. + * 2. `start_handle` must not be greater than `end_handle`, and `start_handle` and + * `end_handle` must not both be 0. 0 is allowed for `start_handle` alone and matches + * cached attributes from the beginning of the handle range. * * @return - * - ESP_OK: Success + * - ESP_GATT_OK: Success + * - ESP_GATT_WRONG_STATE: Bluedroid stack is not enabled + * - ESP_GATT_INVALID_HANDLE: Invalid GATT `start_handle` or `end_handle` * - ESP_GATT_INVALID_PDU: NULL pointer to `result` or NULL pointer to `count` or the count value is 0 - * - ESP_FAIL: Failure due to other reasons + * - ESP_GATT_NOT_FOUND: No included service matching `incl_uuid` found in the handle range */ esp_gatt_status_t esp_ble_gattc_get_include_service(esp_gatt_if_t gattc_if, uint16_t conn_id, @@ -691,13 +729,15 @@ esp_gatt_status_t esp_ble_gattc_get_include_service(esp_gatt_if_t gattc_if, * * @note * 1. This API does not trigger any event. - * 2. `start_handle` must be greater than 0, and smaller than `end_handle` if the `type` is not `ESP_GATT_DB_DESCRIPTOR`. + * 2. If the `type` is not `ESP_GATT_DB_DESCRIPTOR`, `start_handle` must not be greater than + * `end_handle`, and `start_handle` and `end_handle` must not both be 0. 0 is allowed for + * `start_handle` alone and matches cached attributes from the beginning of the handle range. * * @return - * - ESP_OK: Success + * - ESP_GATT_OK: Success + * - ESP_GATT_WRONG_STATE: Bluedroid stack is not enabled * - ESP_GATT_INVALID_HANDLE: Invalid GATT `start_handle`, `end_handle` * - ESP_GATT_INVALID_PDU: NULL pointer to `count` - * - ESP_FAIL: Failure due to other reasons */ esp_gatt_status_t esp_ble_gattc_get_attr_count(esp_gatt_if_t gattc_if, uint16_t conn_id, @@ -719,13 +759,16 @@ esp_gatt_status_t esp_ble_gattc_get_attr_count(esp_gatt_if_t gattc_if, * * @note * 1. This API does not trigger any event. - * 2. `start_handle` must be greater than 0, and smaller than `end_handle`. + * 2. `start_handle` must not be greater than `end_handle`, and `start_handle` and + * `end_handle` must not both be 0. 0 is allowed for `start_handle` alone and matches + * cached attributes from the beginning of the handle range. * * @return - * - ESP_OK: Success + * - ESP_GATT_OK: Success + * - ESP_GATT_WRONG_STATE: Bluedroid stack is not enabled * - ESP_GATT_INVALID_HANDLE: Invalid GATT `start_handle`, `end_handle` * - ESP_GATT_INVALID_PDU: NULL pointer to `db` or NULL pointer to `count` or the count value is 0 - * - ESP_FAIL: Failure due to other reasons + * - ESP_GATT_NOT_FOUND: No GATT database element found in the given handle range * */ esp_gatt_status_t esp_ble_gattc_get_db(esp_gatt_if_t gattc_if, uint16_t conn_id, uint16_t start_handle, uint16_t end_handle, @@ -768,7 +811,9 @@ esp_err_t esp_ble_gattc_read_char (esp_gatt_if_t gattc_if, * @note * 1. This function triggers `ESP_GATTC_READ_CHAR_EVT`. * 2. This function should be called only after the connection has been established. - * 3. `start_handle` must be greater than 0, and smaller than `end_handle`. + * 3. `start_handle` must not be greater than `end_handle`, and `start_handle` and + * `end_handle` must not both be 0. 0 is allowed for `start_handle` alone and is + * treated as 1 on air. * * @return * - ESP_OK: Success @@ -858,12 +903,14 @@ esp_err_t esp_ble_gattc_read_char_descr (esp_gatt_if_t gattc_if, * @param[in] value_len The length of the value to write in bytes * @param[in] value The value to write * @param[in] write_type The type of Attribute write operation - * @param[in] auth_req Authentication request type + * @param[in] auth_req Authenticate request type * * @note * 1. This function triggers `ESP_GATTC_WRITE_CHAR_EVT`. * 2. This function should be called only after the connection has been established. * 3. `handle` must be greater than 0. + * 4. If `auth_req` is not `ESP_GATT_AUTH_REQ_NONE`, the stack may start encryption + * or SMP pairing before sending the ATT write. * * @return * - ESP_OK: Success @@ -997,6 +1044,22 @@ esp_err_t esp_ble_gattc_execute_write (esp_gatt_if_t gattc_if, uint16_t conn_id, * 1. This function triggers `ESP_GATTC_REG_FOR_NOTIFY_EVT`. * 2. You should call `esp_ble_gattc_write_char_descr()` after this API to write Client Characteristic Configuration (CCC) descriptor to the value of 1 (Enable Notification) or 2 (Enable Indication). * 3. `handle` must be greater than 0. + * 4. This API should be invoked only after service discovery for the target + * peer has completed (i.e. after `ESP_GATTC_SEARCH_CMPL_EVT` has been + * received, or after a previously persisted cache has been loaded). The + * recommended way to obtain `handle` is via the cache accessor APIs such + * as `esp_ble_gattc_get_all_char` or `esp_ble_gattc_get_char_by_uuid`. + * 5. When the GATT cache for `server_bda` is ready, the stack will validate + * `handle` against the cache: it must refer to a characteristic value + * whose properties include Notify or Indicate. If this validation fails, + * `ESP_GATTC_REG_FOR_NOTIFY_EVT` reports `ESP_GATT_ILLEGAL_PARAMETER` + * and the registration is rejected. If the cache is not yet ready (for + * example the API is called before service discovery completes), the + * stack skips this validation for backward compatibility, but in that + * case any incoming notification whose handle does not match a prior + * registration will be silently dropped by the host. Calling the API + * with an invalid handle in that window is therefore strongly + * discouraged. * * @return * - ESP_OK: Success diff --git a/components/bt/host/bluedroid/api/include/api/esp_gatts_api.h b/components/bt/host/bluedroid/api/include/api/esp_gatts_api.h index 642222104ad..09e49b8733e 100644 --- a/components/bt/host/bluedroid/api/include/api/esp_gatts_api.h +++ b/components/bt/host/bluedroid/api/include/api/esp_gatts_api.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2024 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -283,7 +283,11 @@ typedef void (* esp_gatts_cb_t)(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_ * * @param[in] callback The pointer to the application callback function * - * @note Avoid performing time-consuming operations within the callback functions. + * @note Do NOT perform time-consuming operations in the callback. Time-consuming operations + * include: taking semaphores that may block for a long time (e.g. xSemaphoreTake with + * long timeout or portMAX_DELAY), blocking delays (e.g. vTaskDelay), and flash + * read/write/erase. Such operations may block the Bluetooth stack and lead to + * instability or deadlock. Defer heavy work to a separate task if needed. * * @return * - ESP_OK: Success @@ -544,9 +548,11 @@ esp_err_t esp_ble_gatts_set_attr_value(uint16_t attr_handle, uint16_t length, co * 2. `attr_handle` must be greater than 0. * * @return - * - ESP_OK: Success + * - ESP_GATT_OK: Success + * - ESP_GATT_WRONG_STATE: Bluedroid stack is not enabled + * - ESP_GATT_INVALID_PDU: NULL pointer to `length` or `value` * - ESP_GATT_INVALID_HANDLE: Invalid `attr_handle` - * - ESP_FAIL: Failure due to other reasons + * - Other `esp_gatt_status_t` values: Failure due to other reasons */ esp_gatt_status_t esp_ble_gatts_get_attr_value(uint16_t attr_handle, uint16_t *length, const uint8_t **value); diff --git a/components/bt/host/bluedroid/api/include/api/esp_hf_client_legacy_api.h b/components/bt/host/bluedroid/api/include/api/esp_hf_client_legacy_api.h index 6441bfc4b4a..bd934bd36be 100644 --- a/components/bt/host/bluedroid/api/include/api/esp_hf_client_legacy_api.h +++ b/components/bt/host/bluedroid/api/include/api/esp_hf_client_legacy_api.h @@ -57,7 +57,7 @@ typedef uint32_t (* esp_hf_client_outgoing_data_cb_t)(uint8_t *buf, uint32_t len * @return * - ESP_OK: success * - ESP_ERR_INVALID_STATE: if bluetooth stack is not yet enabled - * - ESP_FAIL: if callback is a NULL function pointer + * - ESP_FAIL: others * */ esp_err_t esp_hf_client_register_data_callback(esp_hf_client_incoming_data_cb_t recv, diff --git a/components/bt/host/bluedroid/bta/ar/bta_ar.c b/components/bt/host/bluedroid/bta/ar/bta_ar.c index f1b270036cc..d96f9606cc6 100644 --- a/components/bt/host/bluedroid/bta/ar/bta_ar.c +++ b/components/bt/host/bluedroid/bta/ar/bta_ar.c @@ -246,8 +246,12 @@ void bta_ar_reg_avrc(UINT16 service_uuid, char *service_name, char *provider_nam if (service_uuid == UUID_SERVCLASS_AV_REM_CTRL_TARGET) { if (bta_ar_cb.sdp_tg_handle == 0) { + UINT32 sdp_handle = SDP_CreateRecord(); + if (sdp_handle == 0) { + return; + } bta_ar_cb.tg_registered = mask; - bta_ar_cb.sdp_tg_handle = SDP_CreateRecord(); + bta_ar_cb.sdp_tg_handle = sdp_handle; AVRC_AddRecord(service_uuid, service_name, provider_name, categories, bta_ar_cb.sdp_tg_handle, browsing_en); bta_sys_add_uuid(service_uuid); } @@ -257,7 +261,12 @@ void bta_ar_reg_avrc(UINT16 service_uuid, char *service_name, char *provider_nam bta_ar_cb.ct_categories [mask - 1] = categories; categories = bta_ar_cb.ct_categories[0] | bta_ar_cb.ct_categories[1]; if (bta_ar_cb.sdp_ct_handle == 0) { - bta_ar_cb.sdp_ct_handle = SDP_CreateRecord(); + UINT32 sdp_handle = SDP_CreateRecord(); + if (sdp_handle == 0) { + bta_ar_cb.ct_categories[mask - 1] = 0; + return; + } + bta_ar_cb.sdp_ct_handle = sdp_handle; AVRC_AddRecord(service_uuid, service_name, provider_name, categories, bta_ar_cb.sdp_ct_handle, browsing_en); bta_sys_add_uuid(service_uuid); } else { diff --git a/components/bt/host/bluedroid/bta/av/bta_av_act.c b/components/bt/host/bluedroid/bta/av/bta_av_act.c index 13d0901e989..2d8df0599ee 100644 --- a/components/bt/host/bluedroid/bta/av/bta_av_act.c +++ b/components/bt/host/bluedroid/bta/av/bta_av_act.c @@ -262,8 +262,8 @@ static void bta_av_rc_msg_cback(UINT8 handle, UINT8 label, UINT8 opcode, tAVRC_M } /* Create a copy of the message */ - tBTA_AV_RC_MSG *p_buf = - (tBTA_AV_RC_MSG *)osi_malloc((UINT16)(sizeof(tBTA_AV_RC_MSG) + data_len)); + size_t buf_size = sizeof(tBTA_AV_RC_MSG) + data_len; + tBTA_AV_RC_MSG *p_buf = (tBTA_AV_RC_MSG *)osi_malloc(buf_size); if (p_buf != NULL) { p_buf->hdr.event = BTA_AV_AVRC_MSG_EVT; p_buf->handle = handle; diff --git a/components/bt/host/bluedroid/bta/av/bta_av_api.c b/components/bt/host/bluedroid/bta/av/bta_av_api.c index b95f7d16c66..f65966e5479 100644 --- a/components/bt/host/bluedroid/bta/av/bta_av_api.c +++ b/components/bt/host/bluedroid/bta/av/bta_av_api.c @@ -60,16 +60,18 @@ void BTA_AvEnable(tBTA_SEC sec_mask, tBTA_AV_FEAT features, tBTA_AV_CBACK *p_cba { tBTA_AV_API_ENABLE *p_buf; + if ((p_buf = (tBTA_AV_API_ENABLE *) osi_malloc(sizeof(tBTA_AV_API_ENABLE))) == NULL) { + return; + } + /* register with BTA system manager */ bta_sys_register(BTA_ID_AV, &bta_av_reg); - if ((p_buf = (tBTA_AV_API_ENABLE *) osi_malloc(sizeof(tBTA_AV_API_ENABLE))) != NULL) { - p_buf->hdr.event = BTA_AV_API_ENABLE_EVT; - p_buf->p_cback = p_cback; - p_buf->features = features; - p_buf->sec_mask = sec_mask; - bta_sys_sendmsg(p_buf); - } + p_buf->hdr.event = BTA_AV_API_ENABLE_EVT; + p_buf->p_cback = p_cback; + p_buf->features = features; + p_buf->sec_mask = sec_mask; + bta_sys_sendmsg(p_buf); } /******************************************************************************* diff --git a/components/bt/host/bluedroid/bta/av/bta_av_ca_act.c b/components/bt/host/bluedroid/bta/av/bta_av_ca_act.c index bdb1cd6f851..86f1ff507ad 100644 --- a/components/bt/host/bluedroid/bta/av/bta_av_ca_act.c +++ b/components/bt/host/bluedroid/bta/av/bta_av_ca_act.c @@ -298,7 +298,6 @@ void bta_av_ca_api_get(tBTA_AV_RCB *p_rcb, tBTA_AV_DATA *p_data) GOEPC_RequestAddHeader(p_rcb->cover_art_goep_hdl, COVER_ART_HEADER_ID_IMG_HANDLE, (UINT8 *)image_handle_utf16, BTA_AV_CA_IMG_HDL_UTF16_LEN); if (p_data->api_ca_get.type == BTA_AV_CA_GET_IMAGE) { GOEPC_RequestAddHeader(p_rcb->cover_art_goep_hdl, COVER_ART_HEADER_ID_IMG_DESCRIPTOR, (UINT8 *)p_data->api_ca_get.image_descriptor, p_data->api_ca_get.image_descriptor_len); - osi_free(p_data->api_ca_get.image_descriptor); } /* always request to enable srm */ GOEPC_RequestSetSRM(p_rcb->cover_art_goep_hdl, TRUE, FALSE); @@ -314,7 +313,10 @@ error: void bta_av_ca_response(tBTA_AV_RCB *p_rcb, tBTA_AV_DATA *p_data) { tOBEX_PARSE_INFO info; - OBEX_ParseResponse(p_data->ca_response.pkt, p_data->ca_response.opcode, &info); + if (OBEX_ParseResponse(p_data->ca_response.pkt, p_data->ca_response.opcode, &info) != OBEX_SUCCESS) { + osi_free(p_data->ca_response.pkt); + goto error; + } /* we always use a final get */ if (p_data->ca_response.opcode == OBEX_OPCODE_GET_FINAL && (info.response_code == OBEX_RESPONSE_CODE_CONTINUE || info.response_code == (OBEX_RESPONSE_CODE_CONTINUE | OBEX_FINAL_BIT_MASK))) @@ -322,13 +324,15 @@ void bta_av_ca_response(tBTA_AV_RCB *p_rcb, tBTA_AV_DATA *p_data) UINT8 *header = NULL; UINT8 *body_data = NULL; UINT16 body_data_len = 0; + UINT8 *pkt_data = (UINT8 *)(p_data->ca_response.pkt + 1) + p_data->ca_response.pkt->offset; + UINT8 *pkt_end = pkt_data + p_data->ca_response.pkt->len; while((header = OBEX_GetNextHeader(p_data->ca_response.pkt, &info)) != NULL) { switch (*header) { case OBEX_HEADER_ID_BODY: /* actually,END_OF_BODY should not in this continue response */ case OBEX_HEADER_ID_END_OF_BODY: { - UINT16 hdr_len = OBEX_GetHeaderLength(header); + UINT16 hdr_len = OBEX_GetHeaderLength(header, pkt_end); UINT16 seg_len = (hdr_len >= 3) ? (UINT16)(hdr_len - 3) : 0; if (body_data == NULL) { /* first body header */ @@ -373,7 +377,10 @@ error: void bta_av_ca_response_final(tBTA_AV_RCB *p_rcb, tBTA_AV_DATA *p_data) { tOBEX_PARSE_INFO info; - OBEX_ParseResponse(p_data->ca_response.pkt, p_data->ca_response.opcode, &info); + if (OBEX_ParseResponse(p_data->ca_response.pkt, p_data->ca_response.opcode, &info) != OBEX_SUCCESS) { + osi_free(p_data->ca_response.pkt); + goto error; + } UINT8 *header = NULL; if (p_data->ca_response.opcode == OBEX_OPCODE_CONNECT) { /* we expect a success response code with final bit set */ @@ -385,14 +392,21 @@ void bta_av_ca_response_final(tBTA_AV_RCB *p_rcb, tBTA_AV_DATA *p_data) p_rcb->cover_art_max_tx = info.max_packet_length; } BOOLEAN cid_found = false; + UINT8 *pkt_data = (UINT8 *)(p_data->ca_response.pkt + 1) + p_data->ca_response.pkt->offset; + UINT8 *pkt_end = pkt_data + p_data->ca_response.pkt->len; while((header = OBEX_GetNextHeader(p_data->ca_response.pkt, &info)) != NULL) { if (*header == OBEX_HEADER_ID_CONNECTION_ID) { + if (OBEX_GetHeaderLength(header, pkt_end) != 5) { + osi_free(p_data->ca_response.pkt); + goto error; + } cid_found = true; memcpy((UINT8 *)(&p_rcb->cover_art_cid), header + 1, 4); break; } } if (!cid_found) { + osi_free(p_data->ca_response.pkt); goto error; } tBTA_AV_CA_STATUS ca_status; @@ -412,13 +426,15 @@ void bta_av_ca_response_final(tBTA_AV_RCB *p_rcb, tBTA_AV_DATA *p_data) UINT16 body_data_len = 0; /* check response code is success */ if (info.response_code == (OBEX_RESPONSE_CODE_OK | OBEX_FINAL_BIT_MASK)) { + UINT8 *pkt_data = (UINT8 *)(p_data->ca_response.pkt + 1) + p_data->ca_response.pkt->offset; + UINT8 *pkt_end = pkt_data + p_data->ca_response.pkt->len; while((header = OBEX_GetNextHeader(p_data->ca_response.pkt, &info)) != NULL) { switch (*header) { /* actually, BODY should not in this final response */ case OBEX_HEADER_ID_BODY: case OBEX_HEADER_ID_END_OF_BODY: { - UINT16 hdr_len = OBEX_GetHeaderLength(header); + UINT16 hdr_len = OBEX_GetHeaderLength(header, pkt_end); UINT16 seg_len = (hdr_len >= 3) ? (UINT16)(hdr_len - 3) : 0; if (body_data == NULL) { /* first body header */ diff --git a/components/bt/host/bluedroid/bta/av/bta_av_main.c b/components/bt/host/bluedroid/bta/av/bta_av_main.c index 5e08ef5d947..589c02d2389 100644 --- a/components/bt/host/bluedroid/bta/av/bta_av_main.c +++ b/components/bt/host/bluedroid/bta/av/bta_av_main.c @@ -755,7 +755,9 @@ static void bta_av_api_register(tBTA_AV_DATA *p_data) } while (0); /* call callback with register event */ - (*bta_av_cb.p_cback)(BTA_AV_REGISTER_EVT, (tBTA_AV *)®istr); + if (bta_av_cb.p_cback != NULL) { + (*bta_av_cb.p_cback)(BTA_AV_REGISTER_EVT, (tBTA_AV *)®istr); + } } static void bta_av_api_reg_sep(tBTA_AV_DATA *p_data) diff --git a/components/bt/host/bluedroid/bta/dm/bta_dm_act.c b/components/bt/host/bluedroid/bta/dm/bta_dm_act.c index 8b44f27a28d..9b51c486c06 100644 --- a/components/bt/host/bluedroid/bta/dm/bta_dm_act.c +++ b/components/bt/host/bluedroid/bta/dm/bta_dm_act.c @@ -5402,10 +5402,11 @@ void bta_dm_ble_scan (tBTA_DM_MSG *p_data) if ((status = BTM_BleScan(TRUE, p_data->ble_scan.duration, bta_dm_scan_results_cb, bta_dm_scan_cmpl_cb, bta_dm_scan_discard_cb)) != BTM_CMD_STARTED) { APPL_TRACE_WARNING(" %s start scan failed. status=0x%x\n", __FUNCTION__, status); + } else { + status = BTM_SUCCESS; } memset(&cb_params, 0, sizeof(cb_params)); - status = (status == BTM_CMD_STARTED ? BTA_SUCCESS : BTA_FAILURE); cb_params.status = status; BTM_LegacyBleCallbackTrigger(BTM_BLE_LEGACY_GAP_SCAN_START_COMPLETE_EVT, &cb_params); @@ -5415,10 +5416,11 @@ void bta_dm_ble_scan (tBTA_DM_MSG *p_data) if (status != BTM_CMD_STARTED){ APPL_TRACE_WARNING(" %s stop scan failed, status=0x%x\n", __FUNCTION__, status); + } else { + status = BTM_SUCCESS; } memset(&cb_params, 0, sizeof(cb_params)); - status = (status == BTM_CMD_STARTED ? BTA_SUCCESS : BTA_FAILURE); cb_params.status = status; BTM_LegacyBleCallbackTrigger(BTM_BLE_LEGACY_GAP_SCAN_STOP_COMPLETE_EVT, &cb_params); #if (BLE_TOPOLOGY_CHECK == TRUE) @@ -5994,6 +5996,96 @@ void bta_dm_ble_gap_enable_monitor_adv(tBTA_DM_MSG *p_data) } #endif // #if (BLE_FEAT_ADV_MONITOR == TRUE) +#if (BLE_FEAT_DBAF == TRUE) +void bta_dm_ble_gap_set_decision_data(tBTA_DM_MSG *p_data) +{ + BTM_BleSetDecisionData(p_data->ble_set_decision_data.adv_handle, + p_data->ble_set_decision_data.decision_type_flags, + p_data->ble_set_decision_data.data_len, + p_data->ble_set_decision_data.data); +} + +void bta_dm_ble_gap_set_decision_instructions(tBTA_DM_MSG *p_data) +{ + BTM_BleSetDecisionInstructions(p_data->ble_set_decision_instructions.num_tests, + p_data->ble_set_decision_instructions.test_flags, + p_data->ble_set_decision_instructions.test_fields, + p_data->ble_set_decision_instructions.test_params); +} +#endif // #if (BLE_FEAT_DBAF == TRUE) + +#if (BLE_FEAT_FRAME_SPACE_UPDATE == TRUE) +void bta_dm_ble_gap_frame_space_update(tBTA_DM_MSG *p_data) +{ + BTM_BleFrameSpaceUpdate(p_data->ble_frame_space_update.conn_handle, + p_data->ble_frame_space_update.frame_space_min, + p_data->ble_frame_space_update.frame_space_max, + p_data->ble_frame_space_update.phys, + p_data->ble_frame_space_update.spacing_types); +} +#endif // #if (BLE_FEAT_FRAME_SPACE_UPDATE == TRUE) + +#if (BLE_FEAT_LL_EXT_FEAT == TRUE) +void bta_dm_ble_gap_read_all_local_supp_features(tBTA_DM_MSG *p_data) +{ + (void)p_data; + BTM_BleReadAllLocalSuppFeatures(); +} + +void bta_dm_ble_gap_read_all_remote_features(tBTA_DM_MSG *p_data) +{ + BTM_BleReadAllRemoteFeatures(p_data->ble_read_all_remote_feat.conn_handle, + p_data->ble_read_all_remote_feat.page_requested); +} +#endif // #if (BLE_FEAT_LL_EXT_FEAT == TRUE) + +#if (BLE_FEAT_SHORTER_CONN_INTERVALS == TRUE) +void bta_dm_ble_gap_connection_rate_request(tBTA_DM_MSG *p_data) +{ + BTM_BleConnectionRateRequest(p_data->ble_connection_rate_request.conn_handle, + p_data->ble_connection_rate_request.conn_interval_min, + p_data->ble_connection_rate_request.conn_interval_max, + p_data->ble_connection_rate_request.subrate_min, + p_data->ble_connection_rate_request.subrate_max, + p_data->ble_connection_rate_request.max_latency, + p_data->ble_connection_rate_request.continuation_number, + p_data->ble_connection_rate_request.supervision_timeout, + p_data->ble_connection_rate_request.min_ce_len, + p_data->ble_connection_rate_request.max_ce_len); +} + +void bta_dm_ble_gap_set_default_rate_parameters(tBTA_DM_MSG *p_data) +{ + BTM_BleSetDefaultRateParameters(p_data->ble_set_default_rate_parameters.conn_interval_min, + p_data->ble_set_default_rate_parameters.conn_interval_max, + p_data->ble_set_default_rate_parameters.subrate_min, + p_data->ble_set_default_rate_parameters.subrate_max, + p_data->ble_set_default_rate_parameters.max_latency, + p_data->ble_set_default_rate_parameters.continuation_number, + p_data->ble_set_default_rate_parameters.supervision_timeout, + p_data->ble_set_default_rate_parameters.min_ce_len, + p_data->ble_set_default_rate_parameters.max_ce_len); +} + +void bta_dm_ble_gap_read_min_supp_conn_interval(tBTA_DM_MSG *p_data) +{ + (void)p_data; + BTM_BleReadMinSuppConnInterval(); +} +#endif // #if (BLE_FEAT_SHORTER_CONN_INTERVALS == TRUE) + +#if (BLE_FEAT_LE_UTP == TRUE) +void bta_dm_ble_gap_enable_utp_ota_mode(tBTA_DM_MSG *p_data) +{ + BTM_BleEnableUtpOtaMode(p_data->ble_enable_utp_ota_mode.enable); +} + +void bta_dm_ble_gap_utp_send(tBTA_DM_MSG *p_data) +{ + BTM_BleUtpSend(p_data->ble_utp_send.data_len, p_data->ble_utp_send.data); +} +#endif // #if (BLE_FEAT_LE_UTP == TRUE) + #if (BLE_FEAT_ISO_EN == TRUE) #if (BLE_FEAT_ISO_BIG_BROADCASTER_EN == TRUE) void bta_dm_ble_big_create(tBTA_DM_MSG *p_data) @@ -6326,6 +6418,19 @@ void bta_dm_api_cs_procedure_enable(tBTA_DM_MSG *p_data) } #endif // (BT_BLE_FEAT_CHANNEL_SOUNDING == TRUE) +#if (BT_BLE_FEAT_CS_SECURITY_REQUIREMENTS == TRUE) +void bta_dm_api_cs_set_security_requirements(tBTA_DM_MSG *p_data) +{ + BTM_BleGapCsSetSecurityRequirements(p_data->set_security_requirements_params.conn_handle, + p_data->set_security_requirements_params.cs_security_requirements); +} + +void bta_dm_api_cs_set_default_security_requirements(tBTA_DM_MSG *p_data) +{ + BTM_BleGapCsSetDefaultSecurityRequirements(p_data->set_default_security_requirements_params.cs_security_requirements); +} +#endif // (BT_BLE_FEAT_CS_SECURITY_REQUIREMENTS == TRUE) + #if ((defined BTA_GATT_INCLUDED) && (BTA_GATT_INCLUDED == TRUE) && SDP_INCLUDED == TRUE) #ifndef BTA_DM_GATT_CLOSE_DELAY_TOUT #define BTA_DM_GATT_CLOSE_DELAY_TOUT 1000 diff --git a/components/bt/host/bluedroid/bta/dm/bta_dm_api.c b/components/bt/host/bluedroid/bta/dm/bta_dm_api.c index d9eba75854f..57f816f913f 100644 --- a/components/bt/host/bluedroid/bta/dm/bta_dm_api.c +++ b/components/bt/host/bluedroid/bta/dm/bta_dm_api.c @@ -2647,6 +2647,34 @@ void BTA_DmBleGapCsProcEnable(uint16_t conn_handle, uint8_t config_id, uint8_t e #endif // (BT_BLE_FEAT_CHANNEL_SOUNDING == TRUE) +#if (BT_BLE_FEAT_CS_SECURITY_REQUIREMENTS == TRUE) +void BTA_DmBleGapCsSetSecurityRequirements(uint16_t conn_handle, uint64_t cs_security_requirements) +{ + tBTA_DM_API_CS_SET_SECURITY_REQUIREMENTS_PARAMS *p_msg; + + if ((p_msg = (tBTA_DM_API_CS_SET_SECURITY_REQUIREMENTS_PARAMS *) + osi_malloc(sizeof(tBTA_DM_API_CS_SET_SECURITY_REQUIREMENTS_PARAMS))) != NULL) { + p_msg->hdr.event = BTA_DM_API_CS_SET_SECURITY_REQUIREMENTS; + p_msg->conn_handle = conn_handle; + p_msg->cs_security_requirements = cs_security_requirements; + bta_sys_sendmsg(p_msg); + } +} + +void BTA_DmBleGapCsSetDefaultSecurityRequirements(uint64_t cs_security_requirements) +{ + tBTA_DM_API_CS_SET_DEFAULT_SECURITY_REQUIREMENTS_PARAMS *p_msg; + + if ((p_msg = (tBTA_DM_API_CS_SET_DEFAULT_SECURITY_REQUIREMENTS_PARAMS *) + osi_malloc(sizeof(tBTA_DM_API_CS_SET_DEFAULT_SECURITY_REQUIREMENTS_PARAMS))) != NULL) { + p_msg->hdr.event = BTA_DM_API_CS_SET_DEFAULT_SECURITY_REQUIREMENTS; + p_msg->cs_security_requirements = cs_security_requirements; + bta_sys_sendmsg(p_msg); + } +} + +#endif // (BT_BLE_FEAT_CS_SECURITY_REQUIREMENTS == TRUE) + /******************************************************************************* ** ** Function BTA_VendorInit @@ -3258,6 +3286,211 @@ void BTA_DmBleGapEnableMonitorAdv(UINT8 enable) } #endif // #if (BLE_FEAT_ADV_MONITOR == TRUE) +#if (BLE_FEAT_DBAF == TRUE) +void BTA_DmBleGapSetDecisionData(UINT8 adv_handle, UINT8 decision_type_flags, + UINT8 data_len, const UINT8 *p_data) +{ + tBTA_DM_API_SET_DECISION_DATA *p_msg; + APPL_TRACE_API("%s", __func__); + if ((p_msg = (tBTA_DM_API_SET_DECISION_DATA *) osi_malloc(sizeof(tBTA_DM_API_SET_DECISION_DATA))) != NULL) { + memset(p_msg, 0, sizeof(tBTA_DM_API_SET_DECISION_DATA)); + p_msg->hdr.event = BTA_DM_API_SET_DECISION_DATA_EVT; + p_msg->adv_handle = adv_handle; + p_msg->decision_type_flags = decision_type_flags; + p_msg->data_len = data_len; + if (data_len > 0 && p_data != NULL) { + memcpy(p_msg->data, p_data, data_len); + } + bta_sys_sendmsg(p_msg); + } else { + APPL_TRACE_ERROR("%s malloc failed", __func__); + } +} + +void BTA_DmBleGapSetDecisionInstructions(UINT8 num_tests, const UINT8 *test_flags, + const UINT8 *test_fields, const UINT8 *test_params) +{ + tBTA_DM_API_SET_DECISION_INSTRUCTIONS *p_msg; + APPL_TRACE_API("%s", __func__); + if ((p_msg = (tBTA_DM_API_SET_DECISION_INSTRUCTIONS *) osi_malloc(sizeof(tBTA_DM_API_SET_DECISION_INSTRUCTIONS))) != NULL) { + memset(p_msg, 0, sizeof(tBTA_DM_API_SET_DECISION_INSTRUCTIONS)); + p_msg->hdr.event = BTA_DM_API_SET_DECISION_INSTRUCTIONS_EVT; + p_msg->num_tests = num_tests; + if (num_tests > 0 && test_flags != NULL) { + memcpy(p_msg->test_flags, test_flags, num_tests); + } + if (num_tests > 0 && test_fields != NULL) { + memcpy(p_msg->test_fields, test_fields, num_tests); + } + if (num_tests > 0 && test_params != NULL) { + memcpy(p_msg->test_params, test_params, num_tests * BLE_DECISION_TEST_PARAM_LEN); + } + bta_sys_sendmsg(p_msg); + } else { + APPL_TRACE_ERROR("%s malloc failed", __func__); + } +} +#endif // #if (BLE_FEAT_DBAF == TRUE) + +#if (BLE_FEAT_FRAME_SPACE_UPDATE == TRUE) +void BTA_DmBleGapFrameSpaceUpdate(UINT16 conn_handle, UINT16 frame_space_min, + UINT16 frame_space_max, UINT8 phys, UINT16 spacing_types) +{ + tBTA_DM_API_FRAME_SPACE_UPDATE *p_msg; + APPL_TRACE_API("%s", __func__); + if ((p_msg = (tBTA_DM_API_FRAME_SPACE_UPDATE *) osi_malloc(sizeof(tBTA_DM_API_FRAME_SPACE_UPDATE))) != NULL) { + memset(p_msg, 0, sizeof(tBTA_DM_API_FRAME_SPACE_UPDATE)); + p_msg->hdr.event = BTA_DM_API_FRAME_SPACE_UPDATE_EVT; + p_msg->conn_handle = conn_handle; + p_msg->frame_space_min = frame_space_min; + p_msg->frame_space_max = frame_space_max; + p_msg->phys = phys; + p_msg->spacing_types = spacing_types; + bta_sys_sendmsg(p_msg); + } else { + APPL_TRACE_ERROR("%s malloc failed", __func__); + } +} +#endif // #if (BLE_FEAT_FRAME_SPACE_UPDATE == TRUE) + +#if (BLE_FEAT_LL_EXT_FEAT == TRUE) +void BTA_DmBleGapReadAllLocalSuppFeatures(void) +{ + tBTA_DM_API_READ_ALL_LOCAL_SUPP_FEAT *p_msg; + APPL_TRACE_API("%s", __func__); + if ((p_msg = (tBTA_DM_API_READ_ALL_LOCAL_SUPP_FEAT *) osi_malloc(sizeof(tBTA_DM_API_READ_ALL_LOCAL_SUPP_FEAT))) != NULL) { + memset(p_msg, 0, sizeof(tBTA_DM_API_READ_ALL_LOCAL_SUPP_FEAT)); + p_msg->hdr.event = BTA_DM_API_READ_ALL_LOCAL_SUPP_FEAT_EVT; + bta_sys_sendmsg(p_msg); + } else { + APPL_TRACE_ERROR("%s malloc failed", __func__); + } +} + +void BTA_DmBleGapReadAllRemoteFeatures(UINT16 conn_handle, UINT8 page_requested) +{ + tBTA_DM_API_READ_ALL_REMOTE_FEAT *p_msg; + APPL_TRACE_API("%s", __func__); + if ((p_msg = (tBTA_DM_API_READ_ALL_REMOTE_FEAT *) osi_malloc(sizeof(tBTA_DM_API_READ_ALL_REMOTE_FEAT))) != NULL) { + memset(p_msg, 0, sizeof(tBTA_DM_API_READ_ALL_REMOTE_FEAT)); + p_msg->hdr.event = BTA_DM_API_READ_ALL_REMOTE_FEAT_EVT; + p_msg->conn_handle = conn_handle; + p_msg->page_requested = page_requested; + bta_sys_sendmsg(p_msg); + } else { + APPL_TRACE_ERROR("%s malloc failed", __func__); + } +} +#endif // #if (BLE_FEAT_LL_EXT_FEAT == TRUE) + +#if (BLE_FEAT_SHORTER_CONN_INTERVALS == TRUE) +void BTA_DmBleGapConnectionRateRequest(UINT16 conn_handle, UINT16 conn_interval_min, + UINT16 conn_interval_max, UINT16 subrate_min, + UINT16 subrate_max, UINT16 max_latency, + UINT16 continuation_number, UINT16 supervision_timeout, + UINT16 min_ce_len, UINT16 max_ce_len) +{ + tBTA_DM_API_BLE_CONNECTION_RATE_REQUEST *p_msg; + APPL_TRACE_API("%s", __func__); + if ((p_msg = (tBTA_DM_API_BLE_CONNECTION_RATE_REQUEST *) + osi_malloc(sizeof(tBTA_DM_API_BLE_CONNECTION_RATE_REQUEST))) != NULL) { + memset(p_msg, 0, sizeof(tBTA_DM_API_BLE_CONNECTION_RATE_REQUEST)); + p_msg->hdr.event = BTA_DM_API_CONNECTION_RATE_REQUEST_EVT; + p_msg->conn_handle = conn_handle; + p_msg->conn_interval_min = conn_interval_min; + p_msg->conn_interval_max = conn_interval_max; + p_msg->subrate_min = subrate_min; + p_msg->subrate_max = subrate_max; + p_msg->max_latency = max_latency; + p_msg->continuation_number = continuation_number; + p_msg->supervision_timeout = supervision_timeout; + p_msg->min_ce_len = min_ce_len; + p_msg->max_ce_len = max_ce_len; + bta_sys_sendmsg(p_msg); + } else { + APPL_TRACE_ERROR("%s malloc failed", __func__); + } +} +#endif // #if (BLE_FEAT_SHORTER_CONN_INTERVALS == TRUE) + +#if (BLE_FEAT_SHORTER_CONN_INTERVALS == TRUE) +void BTA_DmBleGapSetDefaultRateParameters(UINT16 conn_interval_min, UINT16 conn_interval_max, + UINT16 subrate_min, UINT16 subrate_max, UINT16 max_latency, + UINT16 continuation_number, UINT16 supervision_timeout, + UINT16 min_ce_len, UINT16 max_ce_len) +{ + tBTA_DM_API_BLE_SET_DEFAULT_RATE_PARAMETERS *p_msg; + APPL_TRACE_API("%s", __func__); + if ((p_msg = (tBTA_DM_API_BLE_SET_DEFAULT_RATE_PARAMETERS *) + osi_malloc(sizeof(tBTA_DM_API_BLE_SET_DEFAULT_RATE_PARAMETERS))) != NULL) { + memset(p_msg, 0, sizeof(tBTA_DM_API_BLE_SET_DEFAULT_RATE_PARAMETERS)); + p_msg->hdr.event = BTA_DM_API_SET_DEFAULT_RATE_PARAMETERS_EVT; + p_msg->conn_interval_min = conn_interval_min; + p_msg->conn_interval_max = conn_interval_max; + p_msg->subrate_min = subrate_min; + p_msg->subrate_max = subrate_max; + p_msg->max_latency = max_latency; + p_msg->continuation_number = continuation_number; + p_msg->supervision_timeout = supervision_timeout; + p_msg->min_ce_len = min_ce_len; + p_msg->max_ce_len = max_ce_len; + bta_sys_sendmsg(p_msg); + } else { + APPL_TRACE_ERROR("%s malloc failed", __func__); + } +} + +void BTA_DmBleGapReadMinSuppConnInterval(void) +{ + tBTA_DM_API_BLE_READ_MIN_SUPP_CONN_INTERVAL *p_msg; + APPL_TRACE_API("%s", __func__); + if ((p_msg = (tBTA_DM_API_BLE_READ_MIN_SUPP_CONN_INTERVAL *) + osi_malloc(sizeof(tBTA_DM_API_BLE_READ_MIN_SUPP_CONN_INTERVAL))) != NULL) { + memset(p_msg, 0, sizeof(tBTA_DM_API_BLE_READ_MIN_SUPP_CONN_INTERVAL)); + p_msg->hdr.event = BTA_DM_API_READ_MIN_SUPP_CONN_INTERVAL_EVT; + bta_sys_sendmsg(p_msg); + } else { + APPL_TRACE_ERROR("%s malloc failed", __func__); + } +} +#endif // #if (BLE_FEAT_SHORTER_CONN_INTERVALS == TRUE) + +#if (BLE_FEAT_LE_UTP == TRUE) +void BTA_DmBleGapEnableUtpOtaMode(UINT8 enable) +{ + tBTA_DM_API_BLE_ENABLE_UTP_OTA_MODE *p_msg; + APPL_TRACE_API("%s", __func__); + if ((p_msg = (tBTA_DM_API_BLE_ENABLE_UTP_OTA_MODE *) + osi_malloc(sizeof(tBTA_DM_API_BLE_ENABLE_UTP_OTA_MODE))) != NULL) { + memset(p_msg, 0, sizeof(tBTA_DM_API_BLE_ENABLE_UTP_OTA_MODE)); + p_msg->hdr.event = BTA_DM_API_ENABLE_UTP_OTA_MODE_EVT; + p_msg->enable = enable; + bta_sys_sendmsg(p_msg); + } else { + APPL_TRACE_ERROR("%s malloc failed", __func__); + } +} + +void BTA_DmBleGapUtpSend(UINT8 data_len, const UINT8 *p_data) +{ + tBTA_DM_API_BLE_UTP_SEND *p_msg; + APPL_TRACE_API("%s", __func__); + if (data_len == 0 || data_len > BLE_UTP_DATA_MAX_LEN || p_data == NULL) { + APPL_TRACE_ERROR("%s invalid params", __func__); + return; + } + if ((p_msg = (tBTA_DM_API_BLE_UTP_SEND *) osi_malloc(sizeof(tBTA_DM_API_BLE_UTP_SEND))) != NULL) { + memset(p_msg, 0, sizeof(tBTA_DM_API_BLE_UTP_SEND)); + p_msg->hdr.event = BTA_DM_API_UTP_SEND_EVT; + p_msg->data_len = data_len; + memcpy(p_msg->data, p_data, data_len); + bta_sys_sendmsg(p_msg); + } else { + APPL_TRACE_ERROR("%s malloc failed", __func__); + } +} +#endif // #if (BLE_FEAT_LE_UTP == TRUE) + #if (BLE_FEAT_ISO_EN == TRUE) #if (BLE_FEAT_ISO_BIG_BROADCASTER_EN == TRUE) void BTA_DmBleGapIsoBigCreate(tBTA_DM_BLE_BIG_CREATE_PARAMS *p_big_creat_param) diff --git a/components/bt/host/bluedroid/bta/dm/bta_dm_main.c b/components/bt/host/bluedroid/bta/dm/bta_dm_main.c index 5b7461120df..2ee25451264 100644 --- a/components/bt/host/bluedroid/bta/dm/bta_dm_main.c +++ b/components/bt/host/bluedroid/bta/dm/bta_dm_main.c @@ -328,6 +328,30 @@ const tBTA_DM_ACTION bta_dm_action[BTA_DM_MAX_EVT] = { bta_dm_api_cs_set_procedure_params, /* BTA_DM_API_CS_SET_PROCEDURE_PARAMS */ bta_dm_api_cs_procedure_enable, /* BTA_DM_API_CS_PROCEDURE_ENABLE */ #endif // (BT_BLE_FEAT_CHANNEL_SOUNDING == TRUE) +#if (BT_BLE_FEAT_CS_SECURITY_REQUIREMENTS == TRUE) + bta_dm_api_cs_set_security_requirements, /* BTA_DM_API_CS_SET_SECURITY_REQUIREMENTS */ + bta_dm_api_cs_set_default_security_requirements, /* BTA_DM_API_CS_SET_DEFAULT_SECURITY_REQUIREMENTS */ +#endif // (BT_BLE_FEAT_CS_SECURITY_REQUIREMENTS == TRUE) +#if (BLE_FEAT_DBAF == TRUE) + bta_dm_ble_gap_set_decision_data, /* BTA_DM_API_SET_DECISION_DATA_EVT */ + bta_dm_ble_gap_set_decision_instructions, /* BTA_DM_API_SET_DECISION_INSTRUCTIONS_EVT */ +#endif // #if (BLE_FEAT_DBAF == TRUE) +#if (BLE_FEAT_FRAME_SPACE_UPDATE == TRUE) + bta_dm_ble_gap_frame_space_update, /* BTA_DM_API_FRAME_SPACE_UPDATE_EVT */ +#endif // #if (BLE_FEAT_FRAME_SPACE_UPDATE == TRUE) +#if (BLE_FEAT_LL_EXT_FEAT == TRUE) + bta_dm_ble_gap_read_all_local_supp_features, /* BTA_DM_API_READ_ALL_LOCAL_SUPP_FEAT_EVT */ + bta_dm_ble_gap_read_all_remote_features, /* BTA_DM_API_READ_ALL_REMOTE_FEAT_EVT */ +#endif // #if (BLE_FEAT_LL_EXT_FEAT == TRUE) +#if (BLE_FEAT_SHORTER_CONN_INTERVALS == TRUE) + bta_dm_ble_gap_connection_rate_request, /* BTA_DM_API_CONNECTION_RATE_REQUEST_EVT */ + bta_dm_ble_gap_set_default_rate_parameters, /* BTA_DM_API_SET_DEFAULT_RATE_PARAMETERS_EVT */ + bta_dm_ble_gap_read_min_supp_conn_interval, /* BTA_DM_API_READ_MIN_SUPP_CONN_INTERVAL_EVT */ +#endif // #if (BLE_FEAT_SHORTER_CONN_INTERVALS == TRUE) +#if (BLE_FEAT_LE_UTP == TRUE) + bta_dm_ble_gap_enable_utp_ota_mode, /* BTA_DM_API_ENABLE_UTP_OTA_MODE_EVT */ + bta_dm_ble_gap_utp_send, /* BTA_DM_API_UTP_SEND_EVT */ +#endif // #if (BLE_FEAT_LE_UTP == TRUE) }; diff --git a/components/bt/host/bluedroid/bta/dm/bta_dm_sco.c b/components/bt/host/bluedroid/bta/dm/bta_dm_sco.c index c75e6476a21..513376ca5ef 100644 --- a/components/bt/host/bluedroid/bta/dm/bta_dm_sco.c +++ b/components/bt/host/bluedroid/bta/dm/bta_dm_sco.c @@ -662,6 +662,9 @@ INT32 BTA_DmPcmResample (void *p_src, UINT32 in_bytes, void *p_dst) APPL_TRACE_DEBUG("bta_pcm_resample : insamples %d", (in_bytes / p_bta_dm_pcm_cb->divisor)); #endif if (p_bta_dm_pcm_cb->can_be_filtered) { + if (in_bytes < BTA_DM_PCM_OVERLAP_SIZE * 2) { + return 0; + } out_sample = (*p_bta_dm_pcm_cb->filter) (p_src, p_dst, (in_bytes / p_bta_dm_pcm_cb->divisor), p_bta_dm_pcm_cb->src_sps, (INT32 *) &(p_bta_dm_pcm_cb->cur_pos), p_bta_dm_pcm_cb->overlap_area); } else { diff --git a/components/bt/host/bluedroid/bta/dm/include/bta_dm_int.h b/components/bt/host/bluedroid/bta/dm/include/bta_dm_int.h index 5a759a7417a..e2e61e0f1af 100644 --- a/components/bt/host/bluedroid/bta/dm/include/bta_dm_int.h +++ b/components/bt/host/bluedroid/bta/dm/include/bta_dm_int.h @@ -25,6 +25,9 @@ #define BTA_DM_INT_H #include "common/bt_target.h" +#if (BLE_FEAT_DBAF == TRUE) || (BLE_FEAT_LL_EXT_FEAT == TRUE) || (BLE_FEAT_LE_UTP == TRUE) +#include "stack/hcimsgs.h" +#endif #include "freertos/semphr.h" #include "bta/bta_sys.h" #if (BLE_INCLUDED == TRUE && (defined BTA_GATT_INCLUDED) && (BTA_GATT_INCLUDED == TRUE)) @@ -322,6 +325,30 @@ enum { BTA_DM_API_CS_SET_PROCEDURE_PARAMS, BTA_DM_API_CS_PROCEDURE_ENABLE, #endif // (BT_BLE_FEAT_CHANNEL_SOUNDING == TRUE) +#if (BT_BLE_FEAT_CS_SECURITY_REQUIREMENTS == TRUE) + BTA_DM_API_CS_SET_SECURITY_REQUIREMENTS, + BTA_DM_API_CS_SET_DEFAULT_SECURITY_REQUIREMENTS, +#endif // (BT_BLE_FEAT_CS_SECURITY_REQUIREMENTS == TRUE) +#if (BLE_FEAT_DBAF == TRUE) + BTA_DM_API_SET_DECISION_DATA_EVT, + BTA_DM_API_SET_DECISION_INSTRUCTIONS_EVT, +#endif // #if (BLE_FEAT_DBAF == TRUE) +#if (BLE_FEAT_FRAME_SPACE_UPDATE == TRUE) + BTA_DM_API_FRAME_SPACE_UPDATE_EVT, +#endif // #if (BLE_FEAT_FRAME_SPACE_UPDATE == TRUE) +#if (BLE_FEAT_LL_EXT_FEAT == TRUE) + BTA_DM_API_READ_ALL_LOCAL_SUPP_FEAT_EVT, + BTA_DM_API_READ_ALL_REMOTE_FEAT_EVT, +#endif // #if (BLE_FEAT_LL_EXT_FEAT == TRUE) +#if (BLE_FEAT_SHORTER_CONN_INTERVALS == TRUE) + BTA_DM_API_CONNECTION_RATE_REQUEST_EVT, + BTA_DM_API_SET_DEFAULT_RATE_PARAMETERS_EVT, + BTA_DM_API_READ_MIN_SUPP_CONN_INTERVAL_EVT, +#endif // #if (BLE_FEAT_SHORTER_CONN_INTERVALS == TRUE) +#if (BLE_FEAT_LE_UTP == TRUE) + BTA_DM_API_ENABLE_UTP_OTA_MODE_EVT, + BTA_DM_API_UTP_SEND_EVT, +#endif // #if (BLE_FEAT_LE_UTP == TRUE) BTA_DM_MAX_EVT }; @@ -1204,6 +1231,19 @@ typedef struct { UINT8 config_id; UINT8 enable; } tBTA_DM_API_CS_PROC_ENABLE_PARAMS; + +#if (BT_BLE_FEAT_CS_SECURITY_REQUIREMENTS == TRUE) +typedef struct { + BT_HDR hdr; + UINT16 conn_handle; + UINT64 cs_security_requirements; +} tBTA_DM_API_CS_SET_SECURITY_REQUIREMENTS_PARAMS; + +typedef struct { + BT_HDR hdr; + UINT64 cs_security_requirements; +} tBTA_DM_API_CS_SET_DEFAULT_SECURITY_REQUIREMENTS_PARAMS; +#endif // (BT_BLE_FEAT_CS_SECURITY_REQUIREMENTS == TRUE) #endif // (BT_BLE_FEAT_CHANNEL_SOUNDING == TRUE) #endif /* BLE_INCLUDED */ @@ -1377,6 +1417,93 @@ typedef struct { } tBTA_DM_API_ENABLE_MONITOR_ADV; #endif // #if (BLE_FEAT_ADV_MONITOR == TRUE) +#if (BLE_FEAT_DBAF == TRUE) +typedef struct { + BT_HDR hdr; + UINT8 adv_handle; + UINT8 decision_type_flags; + UINT8 data_len; + UINT8 data[BLE_DECISION_DATA_MAX_LEN]; +} tBTA_DM_API_SET_DECISION_DATA; + +typedef struct { + BT_HDR hdr; + UINT8 num_tests; + UINT8 test_flags[BLE_DECISION_MAX_TESTS]; + UINT8 test_fields[BLE_DECISION_MAX_TESTS]; + UINT8 test_params[BLE_DECISION_TEST_PARAMS_MAX_LEN]; +} tBTA_DM_API_SET_DECISION_INSTRUCTIONS; +#endif // #if (BLE_FEAT_DBAF == TRUE) + +#if (BLE_FEAT_FRAME_SPACE_UPDATE == TRUE) +typedef struct { + BT_HDR hdr; + UINT16 conn_handle; + UINT16 frame_space_min; + UINT16 frame_space_max; + UINT8 phys; + UINT16 spacing_types; +} tBTA_DM_API_FRAME_SPACE_UPDATE; +#endif // #if (BLE_FEAT_FRAME_SPACE_UPDATE == TRUE) + +#if (BLE_FEAT_LL_EXT_FEAT == TRUE) +typedef struct { + BT_HDR hdr; +} tBTA_DM_API_READ_ALL_LOCAL_SUPP_FEAT; + +typedef struct { + BT_HDR hdr; + UINT16 conn_handle; + UINT8 page_requested; +} tBTA_DM_API_READ_ALL_REMOTE_FEAT; +#endif // #if (BLE_FEAT_LL_EXT_FEAT == TRUE) + +#if (BLE_FEAT_SHORTER_CONN_INTERVALS == TRUE) +typedef struct { + BT_HDR hdr; + UINT16 conn_handle; + UINT16 conn_interval_min; + UINT16 conn_interval_max; + UINT16 subrate_min; + UINT16 subrate_max; + UINT16 max_latency; + UINT16 continuation_number; + UINT16 supervision_timeout; + UINT16 min_ce_len; + UINT16 max_ce_len; +} tBTA_DM_API_BLE_CONNECTION_RATE_REQUEST; + +typedef struct { + BT_HDR hdr; + UINT16 conn_interval_min; + UINT16 conn_interval_max; + UINT16 subrate_min; + UINT16 subrate_max; + UINT16 max_latency; + UINT16 continuation_number; + UINT16 supervision_timeout; + UINT16 min_ce_len; + UINT16 max_ce_len; +} tBTA_DM_API_BLE_SET_DEFAULT_RATE_PARAMETERS; + +typedef struct { + BT_HDR hdr; +} tBTA_DM_API_BLE_READ_MIN_SUPP_CONN_INTERVAL; +#endif // #if (BLE_FEAT_SHORTER_CONN_INTERVALS == TRUE) + +#if (BLE_FEAT_LE_UTP == TRUE) +typedef struct { + BT_HDR hdr; + UINT8 enable; +} tBTA_DM_API_BLE_ENABLE_UTP_OTA_MODE; + +typedef struct { + BT_HDR hdr; + UINT8 data_len; + UINT8 data[BLE_UTP_DATA_MAX_LEN]; +} tBTA_DM_API_BLE_UTP_SEND; +#endif // #if (BLE_FEAT_LE_UTP == TRUE) + typedef struct { BT_HDR hdr; tBTA_DM_BLE_EXT_SCAN_PARAMS params; @@ -1813,6 +1940,26 @@ typedef union { tBTA_DM_API_READ_MONITOR_ADV_LIST_SIZE ble_read_monitor_adv_list_size; tBTA_DM_API_ENABLE_MONITOR_ADV ble_enable_monitor_adv; #endif // #if (BLE_FEAT_ADV_MONITOR == TRUE) +#if (BLE_FEAT_DBAF == TRUE) + tBTA_DM_API_SET_DECISION_DATA ble_set_decision_data; + tBTA_DM_API_SET_DECISION_INSTRUCTIONS ble_set_decision_instructions; +#endif // #if (BLE_FEAT_DBAF == TRUE) +#if (BLE_FEAT_FRAME_SPACE_UPDATE == TRUE) + tBTA_DM_API_FRAME_SPACE_UPDATE ble_frame_space_update; +#endif // #if (BLE_FEAT_FRAME_SPACE_UPDATE == TRUE) +#if (BLE_FEAT_LL_EXT_FEAT == TRUE) + tBTA_DM_API_READ_ALL_LOCAL_SUPP_FEAT ble_read_all_local_supp_feat; + tBTA_DM_API_READ_ALL_REMOTE_FEAT ble_read_all_remote_feat; +#endif // #if (BLE_FEAT_LL_EXT_FEAT == TRUE) +#if (BLE_FEAT_SHORTER_CONN_INTERVALS == TRUE) + tBTA_DM_API_BLE_CONNECTION_RATE_REQUEST ble_connection_rate_request; + tBTA_DM_API_BLE_SET_DEFAULT_RATE_PARAMETERS ble_set_default_rate_parameters; + tBTA_DM_API_BLE_READ_MIN_SUPP_CONN_INTERVAL ble_read_min_supp_conn_interval; +#endif // #if (BLE_FEAT_SHORTER_CONN_INTERVALS == TRUE) +#if (BLE_FEAT_LE_UTP == TRUE) + tBTA_DM_API_BLE_ENABLE_UTP_OTA_MODE ble_enable_utp_ota_mode; + tBTA_DM_API_BLE_UTP_SEND ble_utp_send; +#endif // #if (BLE_FEAT_LE_UTP == TRUE) #if (BLE_42_DTM_TEST_EN == TRUE) tBTA_DM_API_BLE_DTM_TX_START dtm_tx_start; tBTA_DM_API_BLE_DTM_RX_START dtm_rx_start; @@ -1899,6 +2046,10 @@ typedef union { tBTA_DM_API_CS_SET_PROC_PARAMS set_proc_params; tBTA_DM_API_CS_PROC_ENABLE_PARAMS proc_enable_params; #endif +#if (BT_BLE_FEAT_CS_SECURITY_REQUIREMENTS == TRUE) + tBTA_DM_API_CS_SET_SECURITY_REQUIREMENTS_PARAMS set_security_requirements_params; + tBTA_DM_API_CS_SET_DEFAULT_SECURITY_REQUIREMENTS_PARAMS set_default_security_requirements_params; +#endif // (BT_BLE_FEAT_CS_SECURITY_REQUIREMENTS == TRUE) } tBTA_DM_MSG; @@ -2492,6 +2643,31 @@ extern void bta_dm_ble_gap_read_monitor_adv_list_size(tBTA_DM_MSG *p_data); extern void bta_dm_ble_gap_enable_monitor_adv(tBTA_DM_MSG *p_data); #endif // #if (BLE_FEAT_ADV_MONITOR == TRUE) +#if (BLE_FEAT_DBAF == TRUE) +extern void bta_dm_ble_gap_set_decision_data(tBTA_DM_MSG *p_data); +extern void bta_dm_ble_gap_set_decision_instructions(tBTA_DM_MSG *p_data); +#endif // #if (BLE_FEAT_DBAF == TRUE) + +#if (BLE_FEAT_FRAME_SPACE_UPDATE == TRUE) +extern void bta_dm_ble_gap_frame_space_update(tBTA_DM_MSG *p_data); +#endif // #if (BLE_FEAT_FRAME_SPACE_UPDATE == TRUE) + +#if (BLE_FEAT_LL_EXT_FEAT == TRUE) +extern void bta_dm_ble_gap_read_all_local_supp_features(tBTA_DM_MSG *p_data); +extern void bta_dm_ble_gap_read_all_remote_features(tBTA_DM_MSG *p_data); +#endif // #if (BLE_FEAT_LL_EXT_FEAT == TRUE) + +#if (BLE_FEAT_SHORTER_CONN_INTERVALS == TRUE) +extern void bta_dm_ble_gap_connection_rate_request(tBTA_DM_MSG *p_data); +extern void bta_dm_ble_gap_set_default_rate_parameters(tBTA_DM_MSG *p_data); +extern void bta_dm_ble_gap_read_min_supp_conn_interval(tBTA_DM_MSG *p_data); +#endif // #if (BLE_FEAT_SHORTER_CONN_INTERVALS == TRUE) + +#if (BLE_FEAT_LE_UTP == TRUE) +extern void bta_dm_ble_gap_enable_utp_ota_mode(tBTA_DM_MSG *p_data); +extern void bta_dm_ble_gap_utp_send(tBTA_DM_MSG *p_data); +#endif // #if (BLE_FEAT_LE_UTP == TRUE) + #if (BLE_FEAT_ISO_EN == TRUE) #if (BLE_FEAT_ISO_BIG_BROADCASTER_EN == TRUE) extern void bta_dm_ble_big_create(tBTA_DM_MSG *p_data); @@ -2571,4 +2747,8 @@ void bta_dm_api_cs_set_channel_classification(tBTA_DM_MSG *p_data); void bta_dm_api_cs_set_procedure_params(tBTA_DM_MSG *p_data); void bta_dm_api_cs_procedure_enable(tBTA_DM_MSG *p_data); #endif // (BT_BLE_FEAT_CHANNEL_SOUNDING == TRUE) +#if (BT_BLE_FEAT_CS_SECURITY_REQUIREMENTS == TRUE) +void bta_dm_api_cs_set_security_requirements(tBTA_DM_MSG *p_data); +void bta_dm_api_cs_set_default_security_requirements(tBTA_DM_MSG *p_data); +#endif // (BT_BLE_FEAT_CS_SECURITY_REQUIREMENTS == TRUE) #endif /* BTA_DM_INT_H */ diff --git a/components/bt/host/bluedroid/bta/gatt/bta_gattc_act.c b/components/bt/host/bluedroid/bta/gatt/bta_gattc_act.c index 9aa81e375e2..2fcf2bdcb8e 100644 --- a/components/bt/host/bluedroid/bta/gatt/bta_gattc_act.c +++ b/components/bt/host/bluedroid/bta/gatt/bta_gattc_act.c @@ -189,7 +189,7 @@ void bta_gattc_disable(tBTA_GATTC_CB *p_cb) *******************************************************************************/ void bta_gattc_register(tBTA_GATTC_CB *p_cb, tBTA_GATTC_DATA *p_data) { - tBTA_GATTC cb_data; + tBTA_GATTC cb_data = {0}; UINT8 i; tBT_UUID *p_app_uuid = &p_data->api_reg.app_uuid; tBTA_GATTC_INT_START_IF *p_buf; @@ -383,7 +383,7 @@ void bta_gattc_process_api_open_cancel (tBTA_GATTC_CB *p_cb, tBTA_GATTC_DATA *p_ UINT16 event = ((BT_HDR *)p_msg)->event; tBTA_GATTC_CLCB *p_clcb = NULL; tBTA_GATTC_RCB *p_clreg; - tBTA_GATTC cb_data; + tBTA_GATTC cb_data = {0}; UNUSED(p_cb); if (p_msg->api_cancel_conn.is_direct) { @@ -425,7 +425,7 @@ void bta_gattc_process_api_open_cancel (tBTA_GATTC_CB *p_cb, tBTA_GATTC_DATA *p_ void bta_gattc_process_enc_cmpl(tBTA_GATTC_CB *p_cb, tBTA_GATTC_DATA *p_msg) { tBTA_GATTC_RCB *p_clreg; - tBTA_GATTC cb_data; + tBTA_GATTC cb_data = {0}; UNUSED(p_cb); p_clreg = bta_gattc_cl_get_regcb(p_msg->enc_cmpl.client_if); @@ -451,7 +451,7 @@ void bta_gattc_process_enc_cmpl(tBTA_GATTC_CB *p_cb, tBTA_GATTC_DATA *p_msg) *******************************************************************************/ void bta_gattc_cancel_open_error(tBTA_GATTC_CLCB *p_clcb, tBTA_GATTC_DATA *p_data) { - tBTA_GATTC cb_data; + tBTA_GATTC cb_data = {0}; UNUSED(p_data); memset(&cb_data, 0, sizeof(cb_data)); @@ -513,6 +513,8 @@ void bta_gattc_open(tBTA_GATTC_CLCB *p_clcb, tBTA_GATTC_DATA *p_data) return; } + memset(&gattc_data, 0, sizeof(gattc_data)); + p_tcb = gatt_find_tcb_by_addr(p_data->api_conn.remote_bda, BT_TRANSPORT_LE); if(p_tcb) { found_app = gatt_find_specific_app_in_hold_link(p_tcb, p_clcb->p_rcb->client_if); @@ -584,6 +586,8 @@ void bta_gattc_init_bk_conn(tBTA_GATTC_API_OPEN *p_data, tBTA_GATTC_RCB *p_clreg tBTA_GATTC_CLCB *p_clcb; tBTA_GATTC_DATA gattc_data; + memset(&gattc_data, 0, sizeof(gattc_data)); + if (bta_gattc_mark_bg_conn(p_data->client_if, p_data->remote_bda, TRUE, FALSE)) { /* always call open to hold a connection */ if (!GATT_Connect(p_data->client_if, p_data->remote_bda, @@ -634,8 +638,7 @@ void bta_gattc_init_bk_conn(tBTA_GATTC_API_OPEN *p_data, tBTA_GATTC_RCB *p_clreg void bta_gattc_cancel_bk_conn(tBTA_GATTC_API_CANCEL_OPEN *p_data) { tBTA_GATTC_RCB *p_clreg; - tBTA_GATTC cb_data; - + tBTA_GATTC cb_data = {0}; memset(&cb_data, 0, sizeof(cb_data)); cb_data.cancel_open.status = BTA_GATT_ERROR; cb_data.cancel_open.client_if = p_data->client_if; @@ -667,7 +670,7 @@ void bta_gattc_cancel_bk_conn(tBTA_GATTC_API_CANCEL_OPEN *p_data) *******************************************************************************/ void bta_gattc_cancel_open_ok(tBTA_GATTC_CLCB *p_clcb, tBTA_GATTC_DATA *p_data) { - tBTA_GATTC cb_data; + tBTA_GATTC cb_data = {0}; UNUSED(p_data); if ( p_clcb->p_rcb->p_cback ) { @@ -691,8 +694,7 @@ void bta_gattc_cancel_open_ok(tBTA_GATTC_CLCB *p_clcb, tBTA_GATTC_DATA *p_data) *******************************************************************************/ void bta_gattc_cancel_open(tBTA_GATTC_CLCB *p_clcb, tBTA_GATTC_DATA *p_data) { - tBTA_GATTC cb_data; - + tBTA_GATTC cb_data = {0}; if (GATT_CancelConnect(p_clcb->p_rcb->client_if, p_data->api_cancel_conn.remote_bda, TRUE)) { bta_gattc_sm_execute(p_clcb, BTA_GATTC_INT_CANCEL_OPEN_OK_EVT, p_data); } else { @@ -759,12 +761,18 @@ void bta_gattc_conn(tBTA_GATTC_CLCB *p_clcb, tBTA_GATTC_DATA *p_data) bta_gattc_register_service_change_notify(p_clcb->bta_conn_id, p_clcb->bda); } else #endif - { /* cache is building */ - APPL_TRACE_DEBUG("%s cache not found, start discovery %u", __func__, bta_gattc_cb.auto_disc); + { /* cache miss or cache load failed */ + APPL_TRACE_DEBUG("%s cache not found, auto_disc=%u", __func__, bta_gattc_cb.auto_disc); if (bta_gattc_cb.auto_disc) { p_clcb->p_srcb->state = BTA_GATTC_SERV_DISC; /* cache load failure, start discovery */ bta_gattc_start_discover(p_clcb, NULL); + } else { + /* Auto discovery is disabled: roll the SRCB back to + * SERV_IDLE so it is not stuck in SERV_LOAD. The app is + * expected to drive service discovery explicitly via + * BTA_GATTC_ServiceSearchRequest() once it is ready. */ + p_clcb->p_srcb->state = BTA_GATTC_SERV_IDLE; } } } else { /* cache is building */ @@ -852,8 +860,7 @@ void bta_gattc_disconncback(tBTA_GATTC_RCB *p_rcb, tBTA_GATTC_DATA *p_data) *******************************************************************************/ void bta_gattc_close_fail(tBTA_GATTC_CLCB *p_clcb, tBTA_GATTC_DATA *p_data) { - tBTA_GATTC cb_data; - + tBTA_GATTC cb_data = {0}; if ( p_clcb->p_rcb->p_cback ) { memset(&cb_data, 0, sizeof(tBTA_GATTC)); cb_data.close.client_if = p_clcb->p_rcb->client_if; @@ -883,8 +890,7 @@ void bta_gattc_close(tBTA_GATTC_CLCB *p_clcb, tBTA_GATTC_DATA *p_data) } tBTA_GATTC_CBACK *p_cback = p_clcb->p_rcb->p_cback; tBTA_GATTC_RCB *p_clreg = p_clcb->p_rcb; - tBTA_GATTC cb_data; - + tBTA_GATTC cb_data = {0}; APPL_TRACE_DEBUG("bta_gattc_close conn_id=%d", p_clcb->bta_conn_id); cb_data.close.client_if = p_clcb->p_rcb->client_if; @@ -908,9 +914,13 @@ void bta_gattc_close(tBTA_GATTC_CLCB *p_clcb, tBTA_GATTC_DATA *p_data) (* p_cback)(BTA_GATTC_CLOSE_EVT, (tBTA_GATTC *)&cb_data); } - // Please note that BTA_GATTC_CLOSE_EVT will run in the BTC task. - // because bta_gattc_deregister_cmpl did not execute as expected(this is a known issue), - // we will run it again in bta_gattc_clcb_dealloc_by_conn_id. + /* + * Free the CLCB in the BTA task after the close callback has been copied to + * BTC. This keeps the original close-callback semantics while avoiding BTC + * task access to core stack control blocks without locking. + */ + bta_gattc_clcb_dealloc(p_clcb); + if (p_clreg->num_clcb == 0 && p_clreg->dereg_pending) { bta_gattc_deregister_cmpl(p_clreg); } @@ -1078,7 +1088,7 @@ void bta_gattc_start_discover(tBTA_GATTC_CLCB *p_clcb, tBTA_GATTC_DATA *p_data) APPL_TRACE_ERROR("discovery on server failed"); bta_gattc_reset_discover_st(p_clcb->p_srcb, p_clcb->status); //discover service complete, trigger callback - tBTA_GATTC cb_data; + tBTA_GATTC cb_data = {0}; cb_data.dis_cmpl.status = p_clcb->status; cb_data.dis_cmpl.conn_id = p_clcb->bta_conn_id; ( *p_clcb->p_rcb->p_cback)(BTA_GATTC_DIS_SRVC_CMPL_EVT, &cb_data); @@ -1143,7 +1153,9 @@ void bta_gattc_disc_cmpl(tBTA_GATTC_CLCB *p_clcb, tBTA_GATTC_DATA *p_data) } if (p_clcb->auto_update == BTA_GATTC_DISC_WAITING) { - /* start discovery again */ + /* Service change arrived during discovery; restart even if p_q_cmd is set. + * Mirrors bta_gattc_op_cmpl(). */ + p_clcb->auto_update = BTA_GATTC_REQ_WAITING; bta_gattc_sm_execute(p_clcb, BTA_GATTC_INT_DISCOVER_EVT, NULL); } /* get any queued command to proceed */ @@ -1380,7 +1392,7 @@ void bta_gattc_confirm(tBTA_GATTC_CLCB *p_clcb, tBTA_GATTC_DATA *p_data) void bta_gattc_read_cmpl(tBTA_GATTC_CLCB *p_clcb, tBTA_GATTC_OP_CMPL *p_data) { UINT8 event; - tBTA_GATTC cb_data; + tBTA_GATTC cb_data = {0}; tBTA_GATT_UNFMT read_value; if (p_clcb->p_q_cmd == NULL) { @@ -1434,8 +1446,21 @@ void bta_gattc_write_cmpl(tBTA_GATTC_CLCB *p_clcb, tBTA_GATTC_OP_CMPL *p_data) APPL_TRACE_ERROR("%s, p_data->p_cmpl is NULL", __func__); UINT16 handle = p_clcb->p_q_cmd->api_write.handle; tBTA_GATTC_EVT cmpl_evt = p_clcb->p_q_cmd->api_write.cmpl_evt; + tBTA_GATTC_CONN *p_conn = bta_gattc_conn_find(p_clcb->bda); bta_gattc_free_command_data(p_clcb); bta_gattc_pop_command_to_send(p_clcb); + /* If this completion belongs to the internal service-change CCC write, + * clear the in-progress flag and swallow the event so the application + * does not see a spurious BTA_GATTC_WRITE_DESCR_EVT, and a future + * bta_gattc_register_service_change_notify() can retry. */ + if (p_conn && + p_conn->write_remote_svc_change_ccc_in_progress && + p_conn->svc_change_descr_handle == handle) { + p_conn->write_remote_svc_change_ccc_in_progress = FALSE; + p_conn->write_remote_svc_change_ccc_done = FALSE; + APPL_TRACE_ERROR("svc chg ccc: p_cmpl NULL"); + return; + } cb_data.write.status = BTA_GATT_ERROR; cb_data.write.handle = handle; cb_data.write.conn_id = p_clcb->bta_conn_id; @@ -1452,7 +1477,7 @@ void bta_gattc_write_cmpl(tBTA_GATTC_CLCB *p_clcb, tBTA_GATTC_OP_CMPL *p_data) p_clcb->p_q_cmd->api_write.write_type == BTA_GATTC_WRITE_PREPARE) { // Check if the parameters are valid // should not happen, but just in case - if (p_clcb->p_q_cmd->api_write.p_value == NULL) { + if (p_clcb->p_q_cmd->api_write.p_value == NULL && p_clcb->p_q_cmd->api_write.len != 0) { APPL_TRACE_ERROR("%s, p_clcb->p_q_cmd->api_write.p_value is NULL", __func__); bta_gattc_free_command_data(p_clcb); bta_gattc_pop_command_to_send(p_clcb); @@ -1461,10 +1486,22 @@ void bta_gattc_write_cmpl(tBTA_GATTC_CLCB *p_clcb, tBTA_GATTC_OP_CMPL *p_data) ( *p_clcb->p_rcb->p_cback)(BTA_GATTC_PREP_WRITE_EVT, (tBTA_GATTC *)&cb_data); return; } - // Should check the value received from the peer device is correct or not. - if (memcmp(p_clcb->p_q_cmd->api_write.p_value, p_data->p_cmpl->att_value.value, - p_data->p_cmpl->att_value.len) != 0) { - cb_data.write.status = BTA_GATT_INVALID_PDU; + /* Rsp value is one ATT chunk (<= MTU-5), not necessarily full api_write.len. + * Only validate echo on success; ATT Error Response has no prepare-write body. */ + if (p_data->status == BTA_GATT_OK) { + UINT16 rsp_len = p_data->p_cmpl->att_value.len; + UINT16 req_len = p_clcb->p_q_cmd->api_write.len; + tGATT_VALUE *a = &p_data->p_cmpl->att_value; + tBTA_GATTC_API_WRITE *w = &p_clcb->p_q_cmd->api_write; + + if (a->handle != w->handle || a->offset != w->offset || rsp_len > req_len || + (req_len > 0 && rsp_len == 0) || + (rsp_len > 0 && w->p_value != NULL && + memcmp(w->p_value, a->value, rsp_len) != 0)) { + APPL_TRACE_ERROR("%s prep_write rsp bad h %u/%u o %u/%u len %u/%u", __func__, + a->handle, w->handle, a->offset, w->offset, rsp_len, req_len); + cb_data.write.status = BTA_GATT_INVALID_PDU; + } } event = BTA_GATTC_PREP_WRITE_EVT; @@ -1475,12 +1512,17 @@ void bta_gattc_write_cmpl(tBTA_GATTC_CLCB *p_clcb, tBTA_GATTC_OP_CMPL *p_data) bta_gattc_free_command_data(p_clcb); bta_gattc_pop_command_to_send(p_clcb); cb_data.write.conn_id = p_clcb->bta_conn_id; - if (p_conn && p_conn->svc_change_descr_handle == cb_data.write.handle) { - if(cb_data.write.status != BTA_GATT_OK) { + if (p_conn && + p_conn->write_remote_svc_change_ccc_in_progress && + p_conn->svc_change_descr_handle == cb_data.write.handle) { + p_conn->write_remote_svc_change_ccc_in_progress = FALSE; + if (cb_data.write.status == BTA_GATT_OK) { + p_conn->write_remote_svc_change_ccc_done = TRUE; + } else { p_conn->write_remote_svc_change_ccc_done = FALSE; APPL_TRACE_ERROR("service change write ccc failed"); } - return; + return; /* internal CCC write: don't forward to application */ } /* write complete, callback */ ( *p_clcb->p_rcb->p_cback)(event, (tBTA_GATTC *)&cb_data); @@ -1497,7 +1539,7 @@ void bta_gattc_write_cmpl(tBTA_GATTC_CLCB *p_clcb, tBTA_GATTC_OP_CMPL *p_data) *******************************************************************************/ void bta_gattc_exec_cmpl(tBTA_GATTC_CLCB *p_clcb, tBTA_GATTC_OP_CMPL *p_data) { - tBTA_GATTC cb_data; + tBTA_GATTC cb_data = {0}; //free the command data store in the queue. bta_gattc_free_command_data(p_clcb); bta_gattc_pop_command_to_send(p_clcb); @@ -1522,7 +1564,7 @@ void bta_gattc_exec_cmpl(tBTA_GATTC_CLCB *p_clcb, tBTA_GATTC_OP_CMPL *p_data) *******************************************************************************/ void bta_gattc_cfg_mtu_cmpl(tBTA_GATTC_CLCB *p_clcb, tBTA_GATTC_OP_CMPL *p_data) { - tBTA_GATTC cb_data; + tBTA_GATTC cb_data = {0}; //free the command data store in the queue. bta_gattc_free_command_data(p_clcb); bta_gattc_pop_command_to_send(p_clcb); @@ -1650,7 +1692,7 @@ void bta_gattc_ignore_op_cmpl(tBTA_GATTC_CLCB *p_clcb, tBTA_GATTC_DATA *p_data) void bta_gattc_search(tBTA_GATTC_CLCB *p_clcb, tBTA_GATTC_DATA *p_data) { tBTA_GATT_STATUS status = GATT_INTERNAL_ERROR; - tBTA_GATTC cb_data; + tBTA_GATTC cb_data = {0}; APPL_TRACE_DEBUG("bta_gattc_search conn_id=%d", p_clcb->bta_conn_id); if (p_clcb->p_srcb && p_clcb->p_srcb->p_srvc_cache) { status = BTA_GATT_OK; @@ -1782,7 +1824,7 @@ void bta_gattc_deregister_cmpl(tBTA_GATTC_RCB *p_clreg) { tBTA_GATTC_CB *p_cb = &bta_gattc_cb; tBTA_GATTC_IF client_if = p_clreg->client_if; - tBTA_GATTC cb_data; + tBTA_GATTC cb_data = {0}; tBTA_GATTC_CBACK *p_cback = p_clreg->p_cback; memset(&cb_data, 0, sizeof(tBTA_GATTC)); @@ -1910,7 +1952,7 @@ static void bta_gattc_enc_cmpl_cback(tGATT_IF gattc_if, BD_ADDR bda) *******************************************************************************/ void bta_gattc_process_api_refresh(tBTA_GATTC_CB *p_cb, tBTA_GATTC_DATA *p_msg) { - tBTA_GATTC_SERV *p_srvc_cb = bta_gattc_find_srvr_cache(p_msg->api_refresh.remote_bda); + tBTA_GATTC_SERV *p_srvc_cb; tBTA_GATTC_CLCB *p_clcb = &bta_gattc_cb.clcb[0]; BOOLEAN found = FALSE; UINT8 i; @@ -1918,6 +1960,14 @@ void bta_gattc_process_api_refresh(tBTA_GATTC_CB *p_cb, tBTA_GATTC_DATA *p_msg) APPL_TRACE_DEBUG("%s", __func__); +#if (GATTC_CACHE_NVS == TRUE) + if (p_msg->api_refresh.erase_flash) { + bta_gattc_cache_reset(p_msg->api_refresh.remote_bda); + } +#endif + + p_srvc_cb = bta_gattc_find_srvr_cache(p_msg->api_refresh.remote_bda); + if (p_srvc_cb != NULL) { /* try to find a CLCB */ if (p_srvc_cb->connected && p_srvc_cb->num_clcb != 0) { @@ -2014,11 +2064,17 @@ void bta_gattc_process_api_cache_get_addr_list(tBTA_GATTC_CB *p_cb, tBTA_GATTC_D *******************************************************************************/ void bta_gattc_process_api_cache_clean(tBTA_GATTC_CB *p_cb, tBTA_GATTC_DATA *p_msg) { - tBTA_GATTC_SERV *p_srvc_cb = bta_gattc_find_srvr_cache(p_msg->api_clean.remote_bda); + tBTA_GATTC_SERV *p_srvc_cb; UNUSED(p_cb); APPL_TRACE_DEBUG("%s", __func__); +#if (GATTC_CACHE_NVS == TRUE) + bta_gattc_cache_reset(p_msg->api_clean.remote_bda); +#endif + + p_srvc_cb = bta_gattc_find_srvr_cache(p_msg->api_clean.remote_bda); + if (p_srvc_cb != NULL && p_srvc_cb->p_srvc_cache != NULL) { //mark it and delete the cache */ list_free(p_srvc_cb->p_srvc_cache); @@ -2094,6 +2150,8 @@ BOOLEAN bta_gattc_process_srvc_chg_ind(UINT16 conn_id, tBTA_GATTC_CONN *p_conn = bta_gattc_conn_find(p_clcb->bda); if(p_conn) { p_conn->write_remote_svc_change_ccc_done = FALSE; + p_conn->write_remote_svc_change_ccc_in_progress = FALSE; + p_conn->svc_change_descr_handle = 0; } bta_gattc_sm_execute(p_clcb, BTA_GATTC_INT_DISCOVER_EVT, NULL); } @@ -2208,10 +2266,18 @@ void bta_gattc_process_indicate(UINT16 conn_id, tGATTC_OPTYPE op, tGATT_CL_COMPL if (p_clcb != NULL) { bta_gattc_proc_other_indication(p_clcb, op, p_data, ¬ify); } - } else if (op == GATTC_OPTYPE_INDICATION) { - /* no one interested and need ack? */ - APPL_TRACE_DEBUG("%s no one interested, ack now", __func__); - GATTC_SendHandleValueConfirm(conn_id, handle); + } else { + /* GATT stack broadcasts notif/ind to every client app; drop here + * when this gatt_if did not register. Only warn if no app at all + * registered for the handle (likely wrong handle). */ + if (!bta_gattc_any_notif_registry(p_srcb, ¬ify)) { + APPL_TRACE_WARNING("drop %s, handle 0x%04x not registered", + (op == GATTC_OPTYPE_INDICATION) ? "ind" : "notif", + handle); + } + if (op == GATTC_OPTYPE_INDICATION) { + GATTC_SendHandleValueConfirm(conn_id, handle); + } } } } @@ -2294,7 +2360,7 @@ static void bta_gattc_cmpl_sendmsg(UINT16 conn_id, tGATTC_OPTYPE op, ********************************************************************************/ static void bta_gattc_cong_cback (UINT16 conn_id, BOOLEAN congested) { - tBTA_GATTC cb_data; + tBTA_GATTC cb_data = {0}; cb_data.congest.conn_id = conn_id; cb_data.congest.congested = congested; btc_gattc_congest_callback(&cb_data); @@ -2337,6 +2403,8 @@ void bta_gattc_init_clcb_conn(UINT8 cif, BD_ADDR remote_bda) tBTA_GATTC_DATA gattc_data; UINT16 conn_id; + memset(&gattc_data, 0, sizeof(gattc_data)); + /* should always get the connection ID */ if (GATT_GetConnIdIfConnected(cif, remote_bda, &conn_id, BTA_GATT_TRANSPORT_LE) == FALSE) { APPL_TRACE_ERROR("bta_gattc_init_clcb_conn ERROR: not a connected device"); @@ -2408,7 +2476,8 @@ tBTA_GATTC_FIND_SERVICE_CB bta_gattc_register_service_change_notify(UINT16 conn_ tBT_UUID gatt_service_change_uuid = {LEN_UUID_16, {GATT_UUID_GATT_SRV_CHGD}}; tBT_UUID gatt_ccc_uuid = {LEN_UUID_16, {GATT_UUID_CHAR_CLIENT_CONFIG}}; tBTA_GATTC_CONN *p_conn = bta_gattc_conn_find_alloc(remote_bda); - if(p_conn && p_conn->write_remote_svc_change_ccc_done) { + if (p_conn && (p_conn->write_remote_svc_change_ccc_done || + p_conn->write_remote_svc_change_ccc_in_progress)) { return SERVICE_CHANGE_CCC_WRITTEN_SUCCESS; } @@ -2475,17 +2544,26 @@ tBTA_GATTC_FIND_SERVICE_CB bta_gattc_register_service_change_notify(UINT16 conn_ } if (gatt_ccc_found == TRUE){ - if (p_conn) { + /* + * The in-progress flag is required so that bta_gattc_write_cmpl can + * recognize this internal CCC write and avoid forwarding the response + * to the application as a spurious BTA_GATTC_WRITE_DESCR_EVT. If the + * connection tracking slot could not be obtained (e.g. conn_track is + * full), skip the write entirely instead of issuing an untracked one. + */ + if (p_conn == NULL) { + APPL_TRACE_ERROR("%s: no conn_track, skip ccc", __func__); + result = SERVICE_CHANGE_WRITE_CCC_FAILED; + } else { p_conn->svc_change_descr_handle = p_desc->handle; - p_conn->write_remote_svc_change_ccc_done = TRUE; + p_conn->write_remote_svc_change_ccc_in_progress = TRUE; + result = SERVICE_CHANGE_CCC_WRITTEN_SUCCESS; + uint16_t indicate_value = GATT_CLT_CONFIG_INDICATION; + tBTA_GATT_UNFMT indicate_v; + indicate_v.len = 2; + indicate_v.p_value = (uint8_t *)&indicate_value; + BTA_GATTC_WriteCharDescr (conn_id, p_desc->handle, BTA_GATTC_TYPE_WRITE, &indicate_v, BTA_GATT_AUTH_REQ_NONE); } - result = SERVICE_CHANGE_CCC_WRITTEN_SUCCESS; - uint16_t indicate_value = GATT_CLT_CONFIG_INDICATION; - tBTA_GATT_UNFMT indicate_v; - indicate_v.len = 2; - indicate_v.p_value = (uint8_t *)&indicate_value; - BTA_GATTC_WriteCharDescr (conn_id, p_desc->handle, BTA_GATTC_TYPE_WRITE, &indicate_v, BTA_GATT_AUTH_REQ_NONE); - } else if (gatt_service_change_found == TRUE) { /* Gatt service char found, but service change char ccc not found, diff --git a/components/bt/host/bluedroid/bta/gatt/bta_gattc_api.c b/components/bt/host/bluedroid/bta/gatt/bta_gattc_api.c index 29df6772748..c0de44c2b44 100644 --- a/components/bt/host/bluedroid/bta/gatt/bta_gattc_api.c +++ b/components/bt/host/bluedroid/bta/gatt/bta_gattc_api.c @@ -91,6 +91,7 @@ void BTA_GATTC_AppRegister(tBT_UUID *p_app_uuid, tBTA_GATTC_CBACK *p_client_cb) } if ((p_buf = (tBTA_GATTC_API_REG *) osi_malloc(sizeof(tBTA_GATTC_API_REG))) != NULL) { + memset(p_buf, 0, sizeof(*p_buf)); p_buf->hdr.event = BTA_GATTC_API_REG_EVT; if (p_app_uuid != NULL) { memcpy(&p_buf->app_uuid, p_app_uuid, sizeof(tBT_UUID)); @@ -206,6 +207,7 @@ void BTA_GATTC_CancelOpen(tBTA_GATTC_IF client_if, BD_ADDR remote_bda, BOOLEAN i tBTA_GATTC_API_CANCEL_OPEN *p_buf; if ((p_buf = (tBTA_GATTC_API_CANCEL_OPEN *) osi_malloc(sizeof(tBTA_GATTC_API_CANCEL_OPEN))) != NULL) { + memset(p_buf, 0, sizeof(tBTA_GATTC_API_CANCEL_OPEN)); p_buf->hdr.event = BTA_GATTC_API_CANCEL_OPEN_EVT; p_buf->client_if = client_if; @@ -780,6 +782,7 @@ void BTA_GATTC_PrepareWrite (UINT16 conn_id, UINT16 handle, p_buf->handle = handle; p_buf->write_type = BTA_GATTC_WRITE_PREPARE; + p_buf->cmpl_evt = BTA_GATTC_PREP_WRITE_EVT; p_buf->offset = offset; p_buf->len = len; @@ -827,6 +830,7 @@ void BTA_GATTC_PrepareWriteCharDescr (UINT16 conn_id, UINT16 handle, p_buf->auth_req = auth_req; p_buf->handle = handle; p_buf->write_type = BTA_GATTC_WRITE_PREPARE; + p_buf->cmpl_evt = BTA_GATTC_PREP_WRITE_EVT; p_buf->offset = offset; if (p_data && p_data->len != 0) { @@ -917,6 +921,7 @@ tBTA_GATT_STATUS BTA_GATTC_RegisterForNotifications (tBTA_GATTC_IF client_if, BD_ADDR bda, UINT16 handle) { tBTA_GATTC_RCB *p_clreg; + tBTA_GATTC_SERV *p_srcb; tBTA_GATT_STATUS status = BTA_GATT_ILLEGAL_PARAMETER; UINT8 i; @@ -926,6 +931,32 @@ tBTA_GATT_STATUS BTA_GATTC_RegisterForNotifications (tBTA_GATTC_IF client_if, return status; } + /* If cache is ready, validate handle is a notify/indicate-capable + * characteristic value handle; otherwise skip (legacy behaviour). */ + p_srcb = bta_gattc_find_srcb(bda); + if (p_srcb != NULL && p_srcb->p_srvc_cache != NULL && + p_srcb->state == BTA_GATTC_SERV_IDLE) { + tBTA_GATTC_CHARACTERISTIC *p_char = + bta_gattc_get_characteristic_srcb(p_srcb, handle); + + if (p_char == NULL) { + APPL_TRACE_ERROR("reg notif: bad handle 0x%04x", handle); + return BTA_GATT_ILLEGAL_PARAMETER; + } + + if ((p_char->properties & (BTA_GATT_CHAR_PROP_BIT_NOTIFY | + BTA_GATT_CHAR_PROP_BIT_INDICATE)) == 0) { + APPL_TRACE_ERROR("reg notif: handle 0x%04x prop 0x%02x not notif/ind", + handle, p_char->properties); + return BTA_GATT_ILLEGAL_PARAMETER; + } + } else { + APPL_TRACE_WARNING("reg notif: cache not ready, skip check, client_if=%d handle=0x%04x bd_addr:%02x:%02x:%02x:%02x:%02x:%02x state=%d", + client_if, handle, + bda[0], bda[1], bda[2], bda[3], bda[4], bda[5], + p_srcb ? p_srcb->state : 0xff); + } + if ((p_clreg = bta_gattc_cl_get_regcb(client_if)) != NULL) { for (i = 0; i < BTA_GATTC_NOTIF_REG_MAX; i ++) { if ( p_clreg->notif_reg[i].in_use && @@ -1019,12 +1050,6 @@ tBTA_GATT_STATUS BTA_GATTC_DeregisterForNotifications (tBTA_GATTC_IF client_if, *******************************************************************************/ void BTA_GATTC_Refresh(BD_ADDR remote_bda, bool erase_flash) { -#if(GATTC_CACHE_NVS == TRUE) - if(erase_flash) { - /* used to reset cache in application */ - bta_gattc_cache_reset(remote_bda); - } -#endif //If the registration callback is NULL, return if(bta_sys_is_register(BTA_ID_GATTC) == FALSE) { return; @@ -1032,8 +1057,10 @@ void BTA_GATTC_Refresh(BD_ADDR remote_bda, bool erase_flash) tBTA_GATTC_API_CACHE_REFRESH *p_buf; if ((p_buf = (tBTA_GATTC_API_CACHE_REFRESH *) osi_malloc(sizeof(tBTA_GATTC_API_CACHE_REFRESH))) != NULL) { + memset(p_buf, 0, sizeof(tBTA_GATTC_API_CACHE_REFRESH)); p_buf->hdr.event = BTA_GATTC_API_REFRESH_EVT; memcpy(p_buf->remote_bda, remote_bda, BD_ADDR_LEN); + p_buf->erase_flash = erase_flash ? TRUE : FALSE; bta_sys_sendmsg(p_buf); } @@ -1082,11 +1109,6 @@ void BTA_GATTC_CacheGetAddrList(tBTA_GATTC_IF client_if) *******************************************************************************/ void BTA_GATTC_Clean(BD_ADDR remote_bda) { -#if(GATTC_CACHE_NVS == TRUE) - /* used to reset cache in application */ - bta_gattc_cache_reset(remote_bda); -#endif - tBTA_GATTC_API_CACHE_CLEAN *p_buf; if ((p_buf = (tBTA_GATTC_API_CACHE_CLEAN *) osi_malloc(sizeof(tBTA_GATTC_API_CACHE_CLEAN))) != NULL) { diff --git a/components/bt/host/bluedroid/bta/gatt/bta_gattc_cache.c b/components/bt/host/bluedroid/bta/gatt/bta_gattc_cache.c index aebffce50a8..76da820b701 100644 --- a/components/bt/host/bluedroid/bta/gatt/bta_gattc_cache.c +++ b/components/bt/host/bluedroid/bta/gatt/bta_gattc_cache.c @@ -29,6 +29,7 @@ //#if( defined GATTC_CACHE_NVS ) && (GATTC_CACHE_NVS == TRUE) #include +#include #include "bta/utl.h" #include "bta/bta_sys.h" #include "stack/sdp_api.h" @@ -45,6 +46,7 @@ // #include "osi/include/log.h" static void bta_gattc_char_dscpt_disc_cmpl(UINT16 conn_id, tBTA_GATTC_SERV *p_srvc_cb); +static tBTA_GATT_STATUS bta_gattc_incl_srvc_disc_cmpl(UINT16 conn_id, tBTA_GATTC_SERV *p_srvc_cb); extern void bta_to_btif_uuid(bt_uuid_t *p_dest, tBT_UUID *p_src); static size_t bta_gattc_get_db_size_with_type(list_t *services, bt_gatt_db_attribute_type_t type, @@ -66,15 +68,8 @@ void bta_gattc_fill_gatt_db_el(btgatt_db_element_t *p_attr, static tBTA_GATT_STATUS bta_gattc_sdp_service_disc(UINT16 conn_id, tBTA_GATTC_SERV *p_server_cb); #define BTA_GATT_SDP_DB_SIZE 4096 #endif ///SDP_INCLUDED == TRUE -#define GATT_CACHE_PREFIX "/data/misc/bluetooth/gatt_cache_" #define GATT_CACHE_VERSION 2 -static void bta_gattc_generate_cache_file_name(char *buffer, BD_ADDR bda) -{ - sprintf(buffer, "%s%02x%02x%02x%02x%02x%02x", GATT_CACHE_PREFIX, - bda[0], bda[1], bda[2], bda[3], bda[4], bda[5]); -} - /***************************************************************************** ** Constants and data types *****************************************************************************/ @@ -131,6 +126,10 @@ bool display_cache_service(void *data, void *context) *******************************************************************************/ static void bta_gattc_display_cache_server(list_t *p_cache) { + if (p_cache == NULL) { + APPL_TRACE_ERROR("Server Cache: (null)"); + return; + } APPL_TRACE_ERROR("<================Start Server Cache =============>"); list_foreach(p_cache, display_cache_service, NULL); APPL_TRACE_ERROR("<================End Server Cache =============>"); @@ -146,15 +145,57 @@ static void bta_gattc_display_cache_server(list_t *p_cache) ** Returns none. ** *******************************************************************************/ -static void bta_gattc_display_explore_record(tBTA_GATTC_ATTR_REC *p_rec, UINT8 num_rec) +static void bta_gattc_uuid_to_str(const tBT_UUID *uuid, char *buf, size_t buf_len) { - UINT8 i; + int x = 0; + + if (buf == NULL || buf_len == 0) { + return; + } + + if (uuid == NULL) { + snprintf(buf, buf_len, "NULL"); + return; + } + + if (uuid->len == LEN_UUID_16) { + snprintf(buf, buf_len, "0x%04x", uuid->uu.uuid16); + } else if (uuid->len == LEN_UUID_32) { + snprintf(buf, buf_len, "0x%08x", (unsigned int)uuid->uu.uuid32); + } else if (uuid->len == LEN_UUID_128) { + x += snprintf(&buf[x], buf_len - (size_t)x, + "0x%02x%02x%02x%02x%02x%02x%02x%02x", + uuid->uu.uuid128[15], uuid->uu.uuid128[14], + uuid->uu.uuid128[13], uuid->uu.uuid128[12], + uuid->uu.uuid128[11], uuid->uu.uuid128[10], + uuid->uu.uuid128[9], uuid->uu.uuid128[8]); + if ((size_t)x < buf_len) { + (void)snprintf(&buf[x], buf_len - (size_t)x, + "%02x%02x%02x%02x%02x%02x%02x%02x", + uuid->uu.uuid128[7], uuid->uu.uuid128[6], + uuid->uu.uuid128[5], uuid->uu.uuid128[4], + uuid->uu.uuid128[3], uuid->uu.uuid128[2], + uuid->uu.uuid128[1], uuid->uu.uuid128[0]); + } + } else { + snprintf(buf, buf_len, "Unknown UUID len=%u", (unsigned)uuid->len); + } +} + +static void bta_gattc_display_explore_record(tBTA_GATTC_ATTR_REC *p_rec, UINT16 num_rec) +{ + UINT16 i; tBTA_GATTC_ATTR_REC *pp = p_rec; APPL_TRACE_ERROR("<================Start Explore Queue =============>"); for (i = 0; i < num_rec; i ++, pp ++) { - APPL_TRACE_ERROR("\t rec[%d] uuid[0x%04x] s_handle[%d] e_handle[%d] is_primary[%d]", - i + 1, pp->uuid.uu.uuid16, pp->s_handle, pp->e_handle, pp->is_primary); + char uuid_buf[50]; + bta_gattc_uuid_to_str(&pp->uuid, uuid_buf, sizeof(uuid_buf)); + + APPL_TRACE_ERROR("\t rec[%u] uuid[%s] s_handle[%u] e_handle[%u] is_primary[%u]", + (unsigned)(i + 1), uuid_buf, + (unsigned)pp->s_handle, (unsigned)pp->e_handle, + (unsigned)pp->is_primary); } APPL_TRACE_ERROR("<================ End Explore Queue =============>"); APPL_TRACE_ERROR(" "); @@ -215,38 +256,66 @@ static void bta_gattc_free(void *ptr) osi_free(ptr); } -void bta_gattc_insert_sec_service_to_cache(list_t *services, tBTA_GATTC_SERVICE *p_new_srvc) +/* Bluedroid host operates under the design assumption that it must not run + * out of memory; OOM is treated as an unrecoverable condition handled + * upstream by the allocator policy. The list_append / list_prepend / + * list_insert_after results are therefore forwarded as-is via the bool + * return value (NULL preconditions also surface as `false`), leaving the + * decision to roll back the orphaned service or to propagate the failure + * up the call stack to the caller. */ +bool bta_gattc_insert_sec_service_to_cache(list_t *services, tBTA_GATTC_SERVICE *p_new_srvc) { - // services/p_new_srvc is NULL if (!services || !p_new_srvc) { APPL_TRACE_ERROR("%s services/p_new_srvc is NULL", __func__); - return; + return false; } - //list is empty + + /* Order by s_handle so traversals (e.g. bta_gattc_get_db_with_operation) may rely on + * monotonic s_handle; gap-based insert failed when ranges overlapped or nested. */ if (list_is_empty(services)) { - list_append(services, p_new_srvc); - } else { - //check the first service - list_node_t *sn = list_begin(services); + return list_append(services, p_new_srvc); + } + + list_node_t *prev = NULL; + for (list_node_t *sn = list_begin(services); sn != list_end(services); sn = list_next(sn)) { tBTA_GATTC_SERVICE *service = list_node(sn); - if(service && p_new_srvc->e_handle < service->s_handle) { - list_prepend(services, p_new_srvc); - } else { - for (list_node_t *sn = list_begin(services); sn != list_end(services); sn = list_next(sn)) { - list_node_t *next_sn = list_next(sn); - if(next_sn == list_end(services)) { - list_append(services, p_new_srvc); - return; - } - tBTA_GATTC_SERVICE *service = list_node(sn); - tBTA_GATTC_SERVICE *next_service = list_node(next_sn); - if (p_new_srvc->s_handle > service->e_handle && p_new_srvc->e_handle < next_service->s_handle) { - list_insert_after(services, sn, p_new_srvc); - return; - } + if (p_new_srvc->s_handle < service->s_handle) { + if (prev == NULL) { + return list_prepend(services, p_new_srvc); + } else { + return list_insert_after(services, prev, p_new_srvc); } } + prev = sn; } + return list_append(services, p_new_srvc); +} + +/******************************************************************************* +** +** Function bta_gattc_next_service_in_list +** +** Description Return the service node after |cur| in |services| list order. +** +** Returns Next service, or NULL if none. +** +*******************************************************************************/ +static tBTA_GATTC_SERVICE *bta_gattc_next_service_in_list(const list_t *services, + const tBTA_GATTC_SERVICE *cur) +{ + if (!services) { + return NULL; + } + for (list_node_t *sn = list_begin(services); sn != list_end(services); sn = list_next(sn)) { + if (list_node(sn) == cur) { + list_node_t *next_sn = list_next(sn); + if (next_sn == list_end(services)) { + return NULL; + } + return (tBTA_GATTC_SERVICE *)list_node(next_sn); + } + } + return NULL; } /******************************************************************************* @@ -282,6 +351,18 @@ static tBTA_GATT_STATUS bta_gattc_add_srvc_to_cache(tBTA_GATTC_SERV *p_srvc_cb, p_new_srvc->characteristics = list_new(characteristic_free); p_new_srvc->included_svc = list_new(bta_gattc_free); + if (!p_new_srvc->characteristics || !p_new_srvc->included_svc) { + APPL_TRACE_WARNING("%s(), no resource.", __func__); + if (p_new_srvc->characteristics) { + list_free(p_new_srvc->characteristics); + } + if (p_new_srvc->included_svc) { + list_free(p_new_srvc->included_svc); + } + osi_free(p_new_srvc); + return BTA_GATT_NO_RESOURCES; + } + if (p_srvc_cb->p_srvc_cache == NULL) { p_srvc_cb->p_srvc_cache = list_new(service_free); } @@ -298,11 +379,22 @@ static tBTA_GATT_STATUS bta_gattc_add_srvc_to_cache(tBTA_GATTC_SERV *p_srvc_cb, return BTA_GATT_NO_RESOURCES; } - if(is_primary) { - list_append(p_srvc_cb->p_srvc_cache, p_new_srvc); + bool inserted; + if (is_primary) { + inserted = list_append(p_srvc_cb->p_srvc_cache, p_new_srvc); } else { //add secondary service into list - bta_gattc_insert_sec_service_to_cache(p_srvc_cb->p_srvc_cache, p_new_srvc); + inserted = bta_gattc_insert_sec_service_to_cache(p_srvc_cb->p_srvc_cache, p_new_srvc); + } + if (!inserted) { + /* p_new_srvc still owns its characteristics/included_svc lists; release + * them here since service_free will not run for a node that never + * entered p_srvc_cache. */ + APPL_TRACE_WARNING("%s(), no resource.", __func__); + list_free(p_new_srvc->characteristics); + list_free(p_new_srvc->included_svc); + osi_free(p_new_srvc); + return BTA_GATT_NO_RESOURCES; } return BTA_GATT_OK; } @@ -329,7 +421,15 @@ static tBTA_GATT_STATUS bta_gattc_add_char_to_cache(tBTA_GATTC_SERV *p_srvc_cb, This is just a temporary workaround. */ if (service->e_handle < value_handle) { - service->e_handle = value_handle; + UINT16 new_e = value_handle; + tBTA_GATTC_SERVICE *next_srvc = bta_gattc_next_service_in_list(p_srvc_cb->p_srvc_cache, service); + if (next_srvc != NULL && next_srvc->s_handle > service->s_handle + && new_e >= next_srvc->s_handle && next_srvc->s_handle > 0) { + new_e = next_srvc->s_handle - 1; + } + if (new_e > service->e_handle) { + service->e_handle = new_e; + } } tBTA_GATTC_CHARACTERISTIC *characteristic = osi_malloc(sizeof(tBTA_GATTC_CHARACTERISTIC)); @@ -343,6 +443,11 @@ static tBTA_GATT_STATUS bta_gattc_add_char_to_cache(tBTA_GATTC_SERV *p_srvc_cb, memcpy(&characteristic->uuid, p_uuid, sizeof(tBT_UUID)); characteristic->service = service; + if (!characteristic->descriptors) { + APPL_TRACE_WARNING("%s(), no resource.", __func__); + osi_free(characteristic); + return BTA_GATT_NO_RESOURCES; + } if (!list_append(service->characteristics, characteristic)) { APPL_TRACE_WARNING("%s(), no resource.", __func__); if (characteristic->descriptors) { @@ -402,7 +507,11 @@ static tBTA_GATT_STATUS bta_gattc_add_attr_to_cache(tBTA_GATTC_SERV *p_srvc_cb, p_srvc_cb->update_incl_srvc = true; } - list_append(service->included_svc, isvc); + if (!service->included_svc || !list_append(service->included_svc, isvc)) { + APPL_TRACE_WARNING("%s(), no resource.", __func__); + osi_free(isvc); + return BTA_GATT_NO_RESOURCES; + } } else if (type == BTA_GATTC_ATTR_TYPE_CHAR_DESCR) { tBTA_GATTC_DESCRIPTOR *descriptor = osi_malloc(sizeof(tBTA_GATTC_DESCRIPTOR)); @@ -424,7 +533,11 @@ static tBTA_GATT_STATUS bta_gattc_add_attr_to_cache(tBTA_GATTC_SERV *p_srvc_cb, tBTA_GATTC_CHARACTERISTIC *char_node = list_back(service->characteristics); descriptor->characteristic = char_node; - list_append(char_node->descriptors, descriptor); + if (!char_node->descriptors || !list_append(char_node->descriptors, descriptor)) { + APPL_TRACE_WARNING("%s(), no resource.", __func__); + osi_free(descriptor); + return BTA_GATT_NO_RESOURCES; + } } return BTA_GATT_OK; } @@ -447,6 +560,17 @@ void bta_gattc_get_disc_range(tBTA_GATTC_SERV *p_srvc_cb, UINT16 *p_s_hdl, UINT1 *p_s_hdl = p_rec->s_handle; } else { p_rec = p_srvc_cb->p_srvc_list + p_srvc_cb->cur_char_idx; + if (p_rec->s_handle == 0xFFFF) { + /* Per BT Core Spec (Vol 3, Part G, 3.3): a Characteristic + * Declaration cannot reside at handle 0xFFFF because its + * mandatory Value Declaration must immediately follow it. + * Treat this as corrupted/unexpected cache data and signal + * an invalid range to the caller. */ + APPL_TRACE_ERROR("%s: char s_handle 0xFFFF", __func__); + *p_s_hdl = 0; + *p_e_hdl = 0; + return; + } *p_s_hdl = p_rec->s_handle + 1; } @@ -510,7 +634,9 @@ tBTA_GATT_STATUS bta_gattc_discover_procedure(UINT16 conn_id, tBTA_GATTC_SERV *p bta_gattc_get_disc_range(p_server_cb, ¶m.s_handle, ¶m.e_handle, is_service); - if (param.s_handle > param.e_handle) { + if (!GATT_HANDLE_IS_VALID(param.s_handle) || + !GATT_HANDLE_IS_VALID(param.e_handle) || + (param.s_handle > param.e_handle)) { return GATT_ERROR; } } @@ -569,7 +695,9 @@ void bta_gattc_update_include_service(const list_t *services) { } for (list_node_t *sn = list_begin(services); sn != list_end(services); sn = list_next(sn)) { tBTA_GATTC_SERVICE *service = list_node(sn); - if(!service || !service->included_svc || list_is_empty(service->included_svc)) break; + if (!service || !service->included_svc || list_is_empty(service->included_svc)) { + continue; + } for (list_node_t *sn = list_begin(service->included_svc); sn != list_end(service->included_svc);) { list_node_t *sn_next = list_next(sn); tBTA_GATTC_INCLUDED_SVC *include_service = list_node(sn); @@ -619,7 +747,38 @@ static void bta_gattc_explore_srvc(UINT16 conn_id, tBTA_GATTC_SERV *p_srvc_cb) &p_rec->uuid, p_rec->is_primary) == 0) { /* start discovering included services */ - bta_gattc_start_disc_include_srvc(conn_id, p_srvc_cb); + tBTA_GATT_STATUS status = bta_gattc_start_disc_include_srvc(conn_id, p_srvc_cb); + if (status == 0) { + return; + } + + /* + * If included service discovery cannot be started (e.g. busy/error), + * try to advance the state machine by directly kicking off + * characteristic discovery. If that also succeeds, the discovery + * will be driven forward by its async completion callback and we + * MUST NOT fall through to the finalization code below (doing so + * would reset the state machine while an async procedure is still + * pending). + */ + tBTA_GATT_STATUS disc_status = bta_gattc_incl_srvc_disc_cmpl(conn_id, p_srvc_cb); + if (disc_status == BTA_GATT_OK) { + return; + } + + /* + * Both included-service and characteristic discovery failed to + * start synchronously. Falling through to the finalization path + * below would (a) deliver BTA_GATTC_DIS_SRVC_CMPL_EVT with a + * stale (likely SUCCESS) status, (b) persist an incomplete cache + * to NVS when GATTC_CACHE_NVS is enabled, and (c) reset the SRCB + * with BTA_GATT_OK, masking the real failure. Mirror the handling + * in bta_gattc_disc_cmpl_cback() and surface the actual error to + * the upper layer immediately. + */ + APPL_TRACE_ERROR("%s: incl/char disc start failed, st=%d/%d", + __func__, status, disc_status); + bta_gattc_reset_discover_st(p_clcb->p_srcb, disc_status); return; } } @@ -661,15 +820,20 @@ static void bta_gattc_explore_srvc(UINT16 conn_id, tBTA_GATTC_SERV *p_srvc_cb) ** ** Description process the relationship discovery complete event ** -** Returns status +** Returns GATT_SUCCESS if the next discovery step (characteristic +** discovery) was successfully kicked off; otherwise the +** error returned by bta_gattc_start_disc_char so that the +** caller can decide whether to fall back to the discovery +** finalization path instead of stalling on a callback that +** will never come. ** *******************************************************************************/ -static void bta_gattc_incl_srvc_disc_cmpl(UINT16 conn_id, tBTA_GATTC_SERV *p_srvc_cb) +static tBTA_GATT_STATUS bta_gattc_incl_srvc_disc_cmpl(UINT16 conn_id, tBTA_GATTC_SERV *p_srvc_cb) { p_srvc_cb->cur_char_idx = p_srvc_cb->total_srvc; /* start discoverying characteristic */ - bta_gattc_start_disc_char(conn_id, p_srvc_cb); + return bta_gattc_start_disc_char(conn_id, p_srvc_cb); } /******************************************************************************* ** @@ -687,11 +851,24 @@ static void bta_gattc_char_disc_cmpl(UINT16 conn_id, tBTA_GATTC_SERV *p_srvc_cb) /* if there are characteristic needs to be explored */ if (p_srvc_cb->total_char > 0) { /* add the first characteristic into cache */ - bta_gattc_add_char_to_cache (p_srvc_cb, - p_rec->char_decl_handle, - p_rec->s_handle, - &p_rec->uuid, - p_rec->property); + tBTA_GATT_STATUS add_status = bta_gattc_add_char_to_cache(p_srvc_cb, + p_rec->char_decl_handle, + p_rec->s_handle, + &p_rec->uuid, + p_rec->property); + if (add_status != BTA_GATT_OK) { + /* Failed to cache the characteristic (e.g. OOM). Kicking off + * descriptor discovery now would either silently drop the + * descriptors or attach them to the previous characteristic + * via list_back() in bta_gattc_add_attr_to_cache(), leaving the + * cache structurally inconsistent. Abort the procedure so the + * application sees the failure rather than a partially + * populated cache. */ + APPL_TRACE_ERROR("%s: add_char_to_cache fail st=%d", + __func__, add_status); + bta_gattc_reset_discover_st(p_srvc_cb, add_status); + return; + } /* start discoverying characteristic descriptor , if failed, disc for next char */ bta_gattc_start_disc_char_dscp(conn_id, p_srvc_cb); @@ -713,17 +890,31 @@ static void bta_gattc_char_disc_cmpl(UINT16 conn_id, tBTA_GATTC_SERV *p_srvc_cb) static void bta_gattc_char_dscpt_disc_cmpl(UINT16 conn_id, tBTA_GATTC_SERV *p_srvc_cb) { tBTA_GATTC_ATTR_REC *p_rec = NULL; + tBTA_GATT_STATUS add_status = BTA_GATT_OK; /* Recursive function will cause BTU stack overflow when there are a large number of characteristic * without descriptor to discover. So replace it with while function */ while (--p_srvc_cb->total_char > 0) { p_rec = p_srvc_cb->p_srvc_list + (++ p_srvc_cb->cur_char_idx); /* add the next characteristic into cache */ - bta_gattc_add_char_to_cache (p_srvc_cb, - p_rec->char_decl_handle, - p_rec->s_handle, - &p_rec->uuid, - p_rec->property); + add_status = bta_gattc_add_char_to_cache(p_srvc_cb, + p_rec->char_decl_handle, + p_rec->s_handle, + &p_rec->uuid, + p_rec->property); + if (add_status != BTA_GATT_OK) { + /* If we failed to cache the current characteristic, any + * descriptors discovered for it would be appended to the + * previous characteristic (bta_gattc_add_attr_to_cache uses + * list_back() on the service's characteristic list), corrupting + * the cache. Stop the discovery state machine and surface the + * error to the upper layer so the application can react + * instead of silently using an inconsistent cache. */ + APPL_TRACE_ERROR("%s: add_char_to_cache fail st=%d", + __func__, add_status); + bta_gattc_reset_discover_st(p_srvc_cb, add_status); + return; + } /* start to discover next characteristic for descriptor */ if (bta_gattc_discover_procedure(conn_id, p_srvc_cb, GATT_DISC_CHAR_DSCPT) == 0) { /* send att req and wait for att rsp */ @@ -992,7 +1183,10 @@ void bta_gattc_disc_res_cback (UINT16 conn_id, tGATT_DISC_TYPE disc_type, tGATT_ p_srvc_cb = bta_gattc_find_scb_by_cid(conn_id); if (p_srvc_cb != NULL && p_clcb != NULL && p_clcb->state == BTA_GATTC_DISCOVER_ST) { - p_srvc_cb->total_attr++; + /* total_attr is UINT16; saturate so discovery cannot wrap the counter */ + if (p_srvc_cb->total_attr < UINT16_MAX) { + p_srvc_cb->total_attr++; + } switch (disc_type) { case GATT_DISC_SRVC_ALL: /* discover services result, add services into a service list */ @@ -1085,10 +1279,24 @@ void bta_gattc_disc_cmpl_cback (UINT16 conn_id, tGATT_DISC_TYPE disc_type, tGATT bta_gattc_explore_srvc(conn_id, p_srvc_cb); break; - case GATT_DISC_INC_SRVC: - bta_gattc_incl_srvc_disc_cmpl(conn_id, p_srvc_cb); - + case GATT_DISC_INC_SRVC: { + /* + * If chained characteristic discovery cannot be started + * synchronously (e.g. GATT_BUSY / GATT_NO_RESOURCES, or invalid + * handle range returned by bta_gattc_discover_procedure), no + * async completion will ever come back to drive the state + * machine forward. Mirror the handling in bta_gattc_explore_srvc() + * and finalize discovery now (propagating the actual error code) + * so the SRCB does not get stuck in BTA_GATTC_SERV_DISC_ACT until + * the link supervision timeout fires. + */ + tBTA_GATT_STATUS disc_status = bta_gattc_incl_srvc_disc_cmpl(conn_id, p_srvc_cb); + if (disc_status != GATT_SUCCESS) { + APPL_TRACE_ERROR("%s: char disc fail after incl, st=%d", __func__, disc_status); + bta_gattc_reset_discover_st(p_srvc_cb, disc_status); + } break; + } case GATT_DISC_CHAR: #if (defined BTA_GATT_DEBUG && BTA_GATT_DEBUG == TRUE) @@ -1209,6 +1417,9 @@ tBTA_GATTC_CHARACTERISTIC* bta_gattc_get_characteristic_srcb(tBTA_GATTC_SERV *p return NULL; } + if (!service->characteristics) { + return NULL; + } for (list_node_t *cn = list_begin(service->characteristics); cn != list_end(service->characteristics); cn = list_next(cn)) { tBTA_GATTC_CHARACTERISTIC *p_char = list_node(cn); @@ -1240,9 +1451,15 @@ tBTA_GATTC_DESCRIPTOR* bta_gattc_get_descriptor_srcb(tBTA_GATTC_SERV *p_srcb, U return NULL; } + if (!service->characteristics) { + return NULL; + } for (list_node_t *cn = list_begin(service->characteristics); cn != list_end(service->characteristics); cn = list_next(cn)) { tBTA_GATTC_CHARACTERISTIC *p_char = list_node(cn); + if (!p_char->descriptors) { + continue; + } for (list_node_t *dn = list_begin(p_char->descriptors); dn != list_end(p_char->descriptors); dn = list_next(dn)) { tBTA_GATTC_DESCRIPTOR *p_desc = list_node(dn); @@ -1335,6 +1552,39 @@ void bta_gattc_fill_gatt_db_el(btgatt_db_element_t *p_attr, bta_to_btif_uuid(&p_attr->uuid, &uuid); } +/******************************************************************************* +** +** Function bta_gattc_get_db_chk_room +** +** Description bta_gattc_get_db_with_operation allocates using a heuristic +** upper bound; the walk can still emit more elements than that +** bound. If filled >= max_elems, free the buffer and clear +** outputs so the caller returns an empty DB (same shape as no +** matches) instead of heap overflow. +** +** Returns TRUE if there is room for one more element, else FALSE. +** +*******************************************************************************/ +/* Static trip counter: incremented every time the DB element buffer would + * have overflowed. Useful as a "did this ever happen in the field?" smoke + * signal in addition to the ERROR log emitted on each trip. Not an API. */ +static unsigned int s_get_db_chk_room_trips = 0; + +static BOOLEAN bta_gattc_get_db_chk_room(size_t filled, size_t max_elems, void *buffer, + btgatt_db_element_t **char_db, UINT16 *count) +{ + if (filled >= max_elems) { + s_get_db_chk_room_trips++; + APPL_TRACE_ERROR("%s: cap exceeded filled=%u cap=%u trips=%u", + __func__, (unsigned)filled, (unsigned)max_elems, s_get_db_chk_room_trips); + osi_free(buffer); + *char_db = NULL; + *count = 0; + return FALSE; + } + return TRUE; +} + void bta_gattc_get_db_with_operation(UINT16 conn_id, bt_gatt_get_db_op_t op, UINT16 char_handle, @@ -1361,15 +1611,19 @@ void bta_gattc_get_db_with_operation(UINT16 conn_id, return; } - size_t db_size = ((end_handle - start_handle + 1) < p_srcb->total_attr) ? (end_handle - start_handle + 1) : p_srcb->total_attr; - if (!db_size) { + /* Allocation cap: min(handle span, total_attr). May be lower than actual + * matching rows; bta_gattc_get_db_chk_room guards each append. */ + const size_t max_elems = ((end_handle - start_handle + 1) < p_srcb->total_attr) + ? (end_handle - start_handle + 1) + : p_srcb->total_attr; + if (!max_elems) { APPL_TRACE_DEBUG("the db size is 0."); *count = 0; *char_db = NULL; return; } - void *buffer = osi_malloc(db_size*sizeof(btgatt_db_element_t)); + void *buffer = osi_malloc(max_elems * sizeof(btgatt_db_element_t)); if (!buffer) { APPL_TRACE_DEBUG("the buffer is NULL."); @@ -1378,7 +1632,7 @@ void bta_gattc_get_db_with_operation(UINT16 conn_id, return; } btgatt_db_element_t *curr_db_attr = buffer; - db_size = 0; + size_t filled = 0; /* number of elements written into buffer so far */ for (list_node_t *sn = list_begin(p_srcb->p_srvc_cache); sn != list_end(p_srcb->p_srvc_cache); sn = list_next(sn)) { tBTA_GATTC_SERVICE *p_cur_srvc = list_node(sn); @@ -1405,10 +1659,13 @@ void bta_gattc_get_db_with_operation(UINT16 conn_id, if (p_isvc->handle > end_handle) { *char_db = buffer; - *count = db_size; + *count = filled; return; } if (!incl_uuid || bta_gattc_uuid_compare(&p_isvc->uuid, incl_uuid, TRUE)) { + if (!bta_gattc_get_db_chk_room(filled, max_elems, buffer, char_db, count)) { + return; + } bta_gattc_fill_gatt_db_el(curr_db_attr, BTGATT_DB_INCLUDED_SERVICE, p_isvc->handle, @@ -1418,7 +1675,7 @@ void bta_gattc_get_db_with_operation(UINT16 conn_id, p_isvc->uuid, 0 /* property */); curr_db_attr++; - db_size++; + filled++; } } continue; @@ -1439,12 +1696,15 @@ void bta_gattc_get_db_with_operation(UINT16 conn_id, if (p_char->handle > end_handle) { *char_db = buffer; - *count = db_size; + *count = filled; return; } if ((op == GATT_OP_GET_ALL_CHAR || op == GATT_OP_GET_CHAR_BY_UUID) && (char_uuid == NULL || bta_gattc_uuid_compare(&p_char->uuid, char_uuid, TRUE))) { APPL_TRACE_DEBUG("%s(), uuid match.", __func__); + if (!bta_gattc_get_db_chk_room(filled, max_elems, buffer, char_db, count)) { + return; + } bta_gattc_fill_gatt_db_el(curr_db_attr, BTGATT_DB_CHARACTERISTIC, p_char->handle, @@ -1454,7 +1714,7 @@ void bta_gattc_get_db_with_operation(UINT16 conn_id, p_char->uuid, p_char->properties); curr_db_attr++; - db_size++; + filled++; continue; } @@ -1481,12 +1741,15 @@ void bta_gattc_get_db_with_operation(UINT16 conn_id, } if (p_desc->handle > end_handle) { *char_db = buffer; - *count = db_size; + *count = filled; return; } if (((op == GATT_OP_GET_ALL_DESCRI || op == GATT_OP_GET_DESCRI_BY_UUID) && (descr_uuid == NULL || bta_gattc_uuid_compare(&p_desc->uuid, descr_uuid, TRUE))) || (op == GATT_OP_GET_DESCRI_BY_HANDLE && bta_gattc_uuid_compare(&p_desc->uuid, descr_uuid, TRUE))) { + if (!bta_gattc_get_db_chk_room(filled, max_elems, buffer, char_db, count)) { + return; + } bta_gattc_fill_gatt_db_el(curr_db_attr, BTGATT_DB_DESCRIPTOR, p_desc->handle, @@ -1496,7 +1759,7 @@ void bta_gattc_get_db_with_operation(UINT16 conn_id, p_desc->uuid, 0 /* property */); curr_db_attr++; - db_size++; + filled++; } } } @@ -1505,7 +1768,7 @@ void bta_gattc_get_db_with_operation(UINT16 conn_id, } *char_db = buffer; - *count = db_size; + *count = filled; } static size_t bta_gattc_get_db_size_with_type(list_t *services, @@ -1568,19 +1831,23 @@ static size_t bta_gattc_get_db_size_with_type(list_t *services, cn != list_end(p_cur_srvc->characteristics); cn = list_next(cn)) { tBTA_GATTC_CHARACTERISTIC *p_char = list_node(cn); - if (p_char->handle < start_handle) { - continue; - } - if (p_char->handle > end_handle) { return db_size; } - if ((type == BTGATT_DB_CHARACTERISTIC) && bta_gattc_uuid_compare(&p_char->uuid, char_uuid, TRUE)) { + /* Count the characteristic only when its handle is in range. */ + if ((p_char->handle >= start_handle) && + (type == BTGATT_DB_CHARACTERISTIC) && + bta_gattc_uuid_compare(&p_char->uuid, char_uuid, TRUE)) { db_size++; continue; } + /* Descriptor handles are strictly greater than their parent characteristic + * declaration handle, so an in-range descriptor may still exist when the + * parent's handle is below start_handle. Iterate descriptors regardless of + * p_char->handle vs start_handle to stay consistent with the paired filler + * (bta_gattc_get_db_with_operation) which already iterates descriptors in that case. */ if (p_char->descriptors && (type == BTGATT_DB_DESCRIPTOR) && bta_gattc_uuid_compare(&p_char->uuid, char_uuid, TRUE)) { for (list_node_t *dn = list_begin(p_char->descriptors); dn != list_end(p_char->descriptors); dn = list_next(dn)) { @@ -1643,43 +1910,14 @@ static size_t bta_gattc_get_db_size(list_t *services, break; } - /* Count service only when declaration handle s_handle is within [start_handle, end_handle] (GATT spec). - * Skip counting this service and all its contents when s_handle < start_handle (consistent with bta_gattc_get_gatt_db_impl). */ - if (p_cur_srvc->s_handle < start_handle) { - continue; - } - db_size++; - - if (p_cur_srvc->characteristics && !list_is_empty(p_cur_srvc->characteristics)) { - for (list_node_t *cn = list_begin(p_cur_srvc->characteristics); - cn != list_end(p_cur_srvc->characteristics); cn = list_next(cn)) { - tBTA_GATTC_CHARACTERISTIC *p_char = list_node(cn); - - if (p_char->handle < start_handle) { - continue; - } - if (p_char->handle > end_handle) { - return db_size; - } - db_size++; - - if (p_char->descriptors) { - for (list_node_t *dn = list_begin(p_char->descriptors); - dn != list_end(p_char->descriptors); dn = list_next(dn)) { - tBTA_GATTC_DESCRIPTOR *p_desc = list_node(dn); - if (p_desc->handle < start_handle) { - continue; - } - if (p_desc->handle > end_handle) { - return db_size; - } - db_size++; - } - } - } + /* Count service declaration only when its handle lies in the requested range. Still count + * included services, characteristics, and descriptors in-range when s_handle is before start_handle. */ + if (p_cur_srvc->s_handle >= start_handle && p_cur_srvc->s_handle <= end_handle) { + db_size++; } - if (p_cur_srvc->included_svc) { + /* GATT attribute order: included service declarations before characteristic declarations. */ + if (p_cur_srvc->included_svc && !list_is_empty(p_cur_srvc->included_svc)) { for (list_node_t *isn = list_begin(p_cur_srvc->included_svc); isn != list_end(p_cur_srvc->included_svc); isn = list_next(isn)) { tBTA_GATTC_INCLUDED_SVC *p_isvc = list_node(isn); @@ -1689,11 +1927,45 @@ static size_t bta_gattc_get_db_size(list_t *services, } if (p_isvc->handle > end_handle) { - return db_size; + break; } db_size++; } } + + if (p_cur_srvc->characteristics && !list_is_empty(p_cur_srvc->characteristics)) { + for (list_node_t *cn = list_begin(p_cur_srvc->characteristics); + cn != list_end(p_cur_srvc->characteristics); cn = list_next(cn)) { + tBTA_GATTC_CHARACTERISTIC *p_char = list_node(cn); + + if (p_char->handle > end_handle) { + break; + } + + /* Count the characteristic declaration only when its handle is in range. + * Descriptors must still be examined when the parent characteristic's + * handle is below start_handle, because descriptor handles are strictly + * greater than the characteristic declaration handle and may themselves + * fall inside [start_handle, end_handle]. */ + if (p_char->handle >= start_handle) { + db_size++; + } + + if (p_char->descriptors) { + for (list_node_t *dn = list_begin(p_char->descriptors); + dn != list_end(p_char->descriptors); dn = list_next(dn)) { + tBTA_GATTC_DESCRIPTOR *p_desc = list_node(dn); + if (p_desc->handle < start_handle) { + continue; + } + if (p_desc->handle > end_handle) { + break; + } + db_size++; + } + } + } + } } return db_size; @@ -1778,6 +2050,15 @@ static void bta_gattc_get_gatt_db_impl(tBTA_GATTC_SERV *p_srvc_cb, size_t db_size = bta_gattc_get_db_size(p_srvc_cb->p_srvc_cache, start_handle, end_handle); + /* No attribute falls in the requested handle range. Return an empty result + * without invoking osi_malloc(0), which would yield NULL on ESP-IDF and + * be misreported as an allocation failure. */ + if (db_size == 0) { + *db = NULL; + *count = 0; + return; + } + void* buffer = osi_malloc(db_size * sizeof(btgatt_db_element_t)); if (!buffer) { APPL_TRACE_WARNING("%s(), no resource.", __func__); @@ -1799,47 +2080,69 @@ static void bta_gattc_get_gatt_db_impl(tBTA_GATTC_SERV *p_srvc_cb, break; } - /* Output service only when declaration handle s_handle is within [start_handle, end_handle] (consistent with count logic). */ - if (p_cur_srvc->s_handle < start_handle) { - continue; + /* Emit service declaration only when its handle lies in the requested range. */ + if (p_cur_srvc->s_handle >= start_handle && p_cur_srvc->s_handle <= end_handle) { + bta_gattc_fill_gatt_db_el(curr_db_attr, + p_cur_srvc->is_primary ? + BTGATT_DB_PRIMARY_SERVICE : + BTGATT_DB_SECONDARY_SERVICE, + 0 /* att_handle */, + p_cur_srvc->s_handle, + p_cur_srvc->e_handle, + p_cur_srvc->s_handle, + p_cur_srvc->uuid, + 0 /* prop */); + curr_db_attr++; } - bta_gattc_fill_gatt_db_el(curr_db_attr, - p_cur_srvc->is_primary ? - BTGATT_DB_PRIMARY_SERVICE : - BTGATT_DB_SECONDARY_SERVICE, - 0 /* att_handle */, - p_cur_srvc->s_handle, - p_cur_srvc->e_handle, - p_cur_srvc->s_handle, - p_cur_srvc->uuid, - 0 /* prop */); - curr_db_attr++; + /* GATT attribute order: included service declarations before characteristic declarations. */ + if (p_cur_srvc->included_svc && !list_is_empty(p_cur_srvc->included_svc)) { + for (list_node_t *isn = list_begin(p_cur_srvc->included_svc); + isn != list_end(p_cur_srvc->included_svc); isn = list_next(isn)) { + tBTA_GATTC_INCLUDED_SVC *p_isvc = list_node(isn); + + if (p_isvc->handle < start_handle) { + continue; + } + + if (p_isvc->handle > end_handle) { + break; + } + bta_gattc_fill_gatt_db_el(curr_db_attr, + BTGATT_DB_INCLUDED_SERVICE, + p_isvc->handle, + p_isvc->incl_srvc_s_handle, + p_isvc->incl_srvc_e_handle, + p_isvc->handle, + p_isvc->uuid, + 0 /* property */); + curr_db_attr++; + } + } if (p_cur_srvc->characteristics && !list_is_empty(p_cur_srvc->characteristics)) { - for (list_node_t *cn = list_begin(p_cur_srvc->characteristics); cn != list_end(p_cur_srvc->characteristics); cn = list_next(cn)) { tBTA_GATTC_CHARACTERISTIC *p_char = list_node(cn); - if (p_char->handle < start_handle) { - continue; + if (p_char->handle > end_handle) { + break; } - if (p_char->handle > end_handle) { - *db = buffer; - *count = db_size; - return; + /* Emit the characteristic declaration only when its handle is in range, + * but still emit its in-range descriptors. This mirrors bta_gattc_get_db_size + * so allocated db_size and emitted entries stay consistent. */ + if (p_char->handle >= start_handle) { + bta_gattc_fill_gatt_db_el(curr_db_attr, + BTGATT_DB_CHARACTERISTIC, + p_char->handle, + 0 /* s_handle */, + 0 /* e_handle */, + p_char->handle, + p_char->uuid, + p_char->properties); + curr_db_attr++; } - bta_gattc_fill_gatt_db_el(curr_db_attr, - BTGATT_DB_CHARACTERISTIC, - p_char->handle, - 0 /* s_handle */, - 0 /* e_handle */, - p_char->handle, - p_char->uuid, - p_char->properties); - curr_db_attr++; if (!p_char->descriptors || list_is_empty(p_char->descriptors)) { continue; @@ -1854,9 +2157,7 @@ static void bta_gattc_get_gatt_db_impl(tBTA_GATTC_SERV *p_srvc_cb, } if (p_desc->handle > end_handle) { - *db = buffer; - *count = db_size; - return; + break; } bta_gattc_fill_gatt_db_el(curr_db_attr, BTGATT_DB_DESCRIPTOR, @@ -1870,34 +2171,6 @@ static void bta_gattc_get_gatt_db_impl(tBTA_GATTC_SERV *p_srvc_cb, } } } - - if (!p_cur_srvc->included_svc || list_is_empty(p_cur_srvc->included_svc)) { - continue; - } - - for (list_node_t *isn = list_begin(p_cur_srvc->included_svc); - isn != list_end(p_cur_srvc->included_svc); isn = list_next(isn)) { - tBTA_GATTC_INCLUDED_SVC *p_isvc = list_node(isn); - - if (p_isvc->handle < start_handle) { - continue; - } - - if (p_isvc->handle > end_handle) { - *db = buffer; - *count = db_size; - return; - } - bta_gattc_fill_gatt_db_el(curr_db_attr, - BTGATT_DB_INCLUDED_SERVICE, - p_isvc->handle, - p_isvc->incl_srvc_s_handle, - p_isvc->incl_srvc_e_handle, - p_isvc->handle, - p_isvc->uuid, - 0 /* property */); - curr_db_attr++; - } } *db = buffer; @@ -1959,12 +2232,19 @@ void bta_gattc_get_gatt_db(UINT16 conn_id, UINT16 start_handle, UINT16 end_handl ** ** Parameters ** -** Returns None. +** Returns BTA_GATT_OK on success. On the first failure of any +** bta_gattc_add_*_to_cache() call (e.g. BTA_GATT_NO_RESOURCES +** on OOM, or GATT_WRONG_STATE on inconsistent NV layout), +** the partially built in-memory cache is freed and the +** underlying error is returned so the caller can fall back +** to a fresh discovery. ** *******************************************************************************/ -void bta_gattc_rebuild_cache(tBTA_GATTC_SERV *p_srvc_cb, UINT16 num_attr, - tBTA_GATTC_NV_ATTR *p_attr) +tBTA_GATT_STATUS bta_gattc_rebuild_cache(tBTA_GATTC_SERV *p_srvc_cb, UINT16 num_attr, + tBTA_GATTC_NV_ATTR *p_attr) { + tBTA_GATT_STATUS status = BTA_GATT_OK; + /* first attribute loading, initialize buffer */ APPL_TRACE_DEBUG("%s: bta_gattc_rebuild_cache, num_attr = %d", __func__, num_attr); @@ -1974,44 +2254,58 @@ void bta_gattc_rebuild_cache(tBTA_GATTC_SERV *p_srvc_cb, UINT16 num_attr, while (num_attr > 0 && p_attr != NULL) { switch (p_attr->attr_type) { case BTA_GATTC_ATTR_TYPE_SRVC: - bta_gattc_add_srvc_to_cache(p_srvc_cb, - p_attr->s_handle, - p_attr->e_handle, - &p_attr->uuid, - p_attr->is_primary); + status = bta_gattc_add_srvc_to_cache(p_srvc_cb, + p_attr->s_handle, + p_attr->e_handle, + &p_attr->uuid, + p_attr->is_primary); break; case BTA_GATTC_ATTR_TYPE_CHAR: //TODO(jpawlowski): store decl_handle properly. - bta_gattc_add_char_to_cache(p_srvc_cb, - p_attr->s_handle, - p_attr->s_handle, - &p_attr->uuid, - p_attr->prop); + status = bta_gattc_add_char_to_cache(p_srvc_cb, + p_attr->s_handle, + p_attr->s_handle, + &p_attr->uuid, + p_attr->prop); break; case BTA_GATTC_ATTR_TYPE_CHAR_DESCR: - bta_gattc_add_attr_to_cache(p_srvc_cb, - p_attr->s_handle, - &p_attr->uuid, - p_attr->prop, - p_attr->incl_srvc_s_handle, - p_attr->incl_srvc_e_handle, - p_attr->attr_type); + status = bta_gattc_add_attr_to_cache(p_srvc_cb, + p_attr->s_handle, + &p_attr->uuid, + p_attr->prop, + p_attr->incl_srvc_s_handle, + p_attr->incl_srvc_e_handle, + p_attr->attr_type); break; case BTA_GATTC_ATTR_TYPE_INCL_SRVC: - bta_gattc_add_attr_to_cache(p_srvc_cb, - p_attr->s_handle, - &p_attr->uuid, - p_attr->prop, - p_attr->incl_srvc_s_handle, - p_attr->incl_srvc_e_handle, - p_attr->attr_type); + status = bta_gattc_add_attr_to_cache(p_srvc_cb, + p_attr->s_handle, + &p_attr->uuid, + p_attr->prop, + p_attr->incl_srvc_s_handle, + p_attr->incl_srvc_e_handle, + p_attr->attr_type); break; } + if (status != BTA_GATT_OK) { + /* Partial rebuild leaves the cache in an inconsistent state + * (e.g. a service without its characteristics, or a char whose + * descriptors would now attach to the wrong parent). Drop the + * partially built cache and let the caller fall back to a fresh + * discovery instead of exposing a corrupted view to the app. */ + APPL_TRACE_ERROR("%s: rebuild abort t=%d st=%d", + __func__, p_attr->attr_type, status); + list_free(p_srvc_cb->p_srvc_cache); + p_srvc_cb->p_srvc_cache = NULL; + return status; + } p_attr ++; num_attr --; } + + return BTA_GATT_OK; } /******************************************************************************* @@ -2056,6 +2350,11 @@ void bta_gattc_cache_save(tBTA_GATTC_SERV *p_srvc_cb, UINT16 conn_id) /* i: current write index, equals actual count after loops; db_size: allocated slots from cache count */ size_t i = 0; size_t db_size = bta_gattc_get_db_size(p_srvc_cb->p_srvc_cache, 0x0000, 0xFFFF); + /* Nothing to persist; skip allocating a zero-byte buffer (osi_malloc(0) + * returns NULL on ESP-IDF and would be misreported as "no resource"). */ + if (db_size == 0) { + return; + } tBTA_GATTC_NV_ATTR *nv_attr = osi_malloc(db_size * sizeof(tBTA_GATTC_NV_ATTR)); // This step is very important, if not clear the memory, the hasy key base on the attribute case will be not correct. if (nv_attr != NULL) { @@ -2203,9 +2502,15 @@ bool bta_gattc_cache_load(tBTA_GATTC_CLCB *p_clcb) } size_t num_attr = length / sizeof(tBTA_GATTC_NV_ATTR); - //don't forget to set the total attribute number. - p_clcb->p_srcb->total_attr = num_attr; - APPL_TRACE_DEBUG("%s(), index = %x, num_attr = %d", __func__, index, num_attr); + /* total_attr and rebuild path are UINT16-wide; refuse oversize NV blobs + * (NVS reads the whole blob, and UINT16 cannot represent the count). */ + if (num_attr > UINT16_MAX) { + APPL_TRACE_ERROR("%s: NV attr cnt>%u", + __func__, (unsigned)UINT16_MAX); + return false; + } + p_clcb->p_srcb->total_attr = (UINT16)num_attr; + APPL_TRACE_DEBUG("%s(), index = %x, num_attr = %u", __func__, index, (unsigned)num_attr); if ((attr = osi_malloc(sizeof(tBTA_GATTC_NV_ATTR) * num_attr)) == NULL) { APPL_TRACE_ERROR("%s, No Memory.", __func__); return false; @@ -2216,9 +2521,21 @@ bool bta_gattc_cache_load(tBTA_GATTC_CLCB *p_clcb) return false; } p_clcb->searched_service_source = BTA_GATTC_SERVICE_INFO_FROM_NVS_FLASH; - bta_gattc_rebuild_cache(p_clcb->p_srcb, num_attr, attr); + status = bta_gattc_rebuild_cache(p_clcb->p_srcb, (UINT16)num_attr, attr); //free the attr buffer after used. osi_free(attr); + if (status != BTA_GATT_OK) { + /* The NV cache is either corrupted or we ran out of memory while + * rebuilding it. Either way the in-memory cache has already been + * cleared by bta_gattc_rebuild_cache(); drop the on-flash copy too + * so the next connection re-discovers from scratch instead of + * looping on a bad cache. */ + APPL_TRACE_ERROR("%s: rebuild fail st=%d, reset NV", + __func__, status); + bta_gattc_co_cache_reset(p_clcb->p_srcb->server_bda); + p_clcb->searched_service_source = BTA_GATTC_SERVICE_INFO_FROM_UNKNOWN; + return false; + } return true; } @@ -2256,10 +2573,7 @@ static void bta_gattc_cache_write(BD_ADDR server_bda, UINT16 num_attr, void bta_gattc_cache_reset(BD_ADDR server_bda) { BTIF_TRACE_DEBUG("%s", __func__); - char fname[255] = {0}; - bta_gattc_generate_cache_file_name(fname, server_bda); bta_gattc_co_cache_reset(server_bda); - //unlink(fname); } //#endif /* GATTC_CACHE_NVS */ diff --git a/components/bt/host/bluedroid/bta/gatt/bta_gattc_co.c b/components/bt/host/bluedroid/bta/gatt/bta_gattc_co.c index 80a10a8f2ad..9ebac8a30b8 100644 --- a/components/bt/host/bluedroid/bta/gatt/bta_gattc_co.c +++ b/components/bt/host/bluedroid/bta/gatt/bta_gattc_co.c @@ -15,9 +15,6 @@ * limitations under the License. * ******************************************************************************/ -#ifdef BT_SUPPORT_NVM -#include -#endif /* BT_SUPPORT_NVM */ #include #include #include "bta/bta_gattc_co.h" @@ -30,6 +27,8 @@ #include "osi/list.h" #include "esp_err.h" #include "osi/allocator.h" +#include "freertos/FreeRTOS.h" +#include "freertos/semphr.h" #if( defined BLE_INCLUDED ) && (BLE_INCLUDED == TRUE) #if( defined BTA_GATT_INCLUDED ) && (GATTC_INCLUDED == TRUE) @@ -40,42 +39,6 @@ #define MAX_DEVICE_IN_CACHE 50 #define MAX_ADDR_LIST_CACHE_BUF 2048 -#ifdef BT_SUPPORT_NVM -static FILE *sCacheFD = 0; -static void getFilename(char *buffer, BD_ADDR bda) -{ - sprintf(buffer, "%s%02x%02x%02x%02x%02x%02x", GATT_CACHE_PREFIX - , bda[0], bda[1], bda[2], bda[3], bda[4], bda[5]); -} - -static void cacheClose(void) -{ - if (sCacheFD != 0) { - fclose(sCacheFD); - sCacheFD = 0; - } -} - -static bool cacheOpen(BD_ADDR bda, bool to_save) -{ - char fname[255] = {0}; - getFilename(fname, bda); - - cacheClose(); - sCacheFD = fopen(fname, to_save ? "w" : "r"); - - return (sCacheFD != 0); -} - -static void cacheReset(BD_ADDR bda) -{ - char fname[255] = {0}; - getFilename(fname, bda); - unlink(fname); -} - -#else - static const char *cache_key = "gattc_cache_key"; static const char *cache_addr = "cache_addr_tab"; @@ -98,6 +61,60 @@ typedef struct { static cache_env_t *cache_env = NULL; +/* Protect |cache_env|; exported callouts re-enter each other (e.g. cacheOpen -> find_addr). + * + * The mutex is created exactly once from bta_gattc_co_cache_addr_init() on the + * BT host startup task before any other callout becomes reachable, so we do + * NOT need any spinlock around the creation. The *Static variant places the + * storage in .bss and avoids any allocation. */ +static StaticSemaphore_t s_cache_env_mutex_buf; +static SemaphoreHandle_t s_cache_env_mutex = NULL; + +/* Must be called from the BT host startup task (single-threaded context) + * before any other cache_env_* user becomes reachable. Idempotent. */ +static void cache_env_mutex_init_once(void) +{ + if (s_cache_env_mutex == NULL) { + s_cache_env_mutex = xSemaphoreCreateRecursiveMutexStatic(&s_cache_env_mutex_buf); + } +} + +static void cache_env_lock(void) +{ + if (s_cache_env_mutex != NULL) { + (void)xSemaphoreTakeRecursive(s_cache_env_mutex, portMAX_DELAY); + } +} + +static void cache_env_unlock(void) +{ + if (s_cache_env_mutex != NULL) { + xSemaphoreGiveRecursive(s_cache_env_mutex); + } +} + +/* Format a per-device GATT cache NVS namespace name into |buffer|. + * + * Output layout: GATT_CACHE_PREFIX (5 chars: "gatt_") + sizeof(hash_key_t)*2 + * hex chars (currently 8) + NUL = 14 bytes total. + * + * Contract: + * |buffer| MUST be at least NVS_KEY_NAME_MAX_SIZE (16) bytes, which is also + * the NVS limit for a namespace name. Today's 14-byte output leaves only + * 2 bytes of headroom -- if GATT_CACHE_PREFIX is ever lengthened or + * sizeof(hash_key_t) is ever increased so that + * + * strlen(GATT_CACHE_PREFIX) + 2*sizeof(hash_key_t) + 1 > NVS_KEY_NAME_MAX_SIZE + * + * this sprintf() will silently overflow the caller's stack buffer AND + * produce a namespace name that nvs_open() will reject. In that case + * switch to snprintf(buffer, NVS_KEY_NAME_MAX_SIZE, ...) and propagate + * the truncation as an error to callers. + * + * Callers (keep this list in sync if a new one is added): + * - cacheOpen() [open by current hash] + * - bta_gattc_co_cache_addr_save() [erase old namespace on hash change] + */ static void getFilename(char *buffer, hash_key_t hash) { sprintf(buffer, "%s%02x%02x%02x%02x", GATT_CACHE_PREFIX, @@ -107,6 +124,9 @@ static void getFilename(char *buffer, hash_key_t hash) static void cacheClose(BD_ADDR bda) { UINT8 index = 0; + if (cache_env == NULL) { + return; + } if ((index = bta_gattc_co_find_addr_in_cache(bda)) != INVALID_ADDR_NUM) { if (cache_env->cache_addr[index].is_open) { nvs_close(cache_env->cache_addr[index].cache_fp); @@ -117,8 +137,13 @@ static void cacheClose(BD_ADDR bda) static bool cacheOpen(BD_ADDR bda, bool to_save, UINT8 *index) { + if (cache_env == NULL) { + return false; + } UNUSED(to_save); - char fname[255] = {0}; + /* NVS namespace name is limited to (NVS_KEY_NAME_MAX_SIZE - 1) characters. + * getFilename() produces GATT_CACHE_PREFIX (5) + 8 hex chars + NUL = 14 bytes. */ + char fname[NVS_KEY_NAME_MAX_SIZE] = {0}; UINT8 *assoc_addr = NULL; esp_err_t status = ESP_FAIL; hash_key_t hash_key = {0}; @@ -141,8 +166,9 @@ static bool cacheOpen(BD_ADDR bda, bool to_save, UINT8 *index) static void cacheReset(BD_ADDR bda, BOOLEAN update) { - char fname[255] = {0}; - getFilename(fname, bda); + if (cache_env == NULL) { + return; + } UINT8 index = 0; //cache_env->cache_addr if ((index = bta_gattc_co_find_addr_in_cache(bda)) != INVALID_ADDR_NUM) { @@ -153,18 +179,21 @@ static void cacheReset(BD_ADDR bda, BOOLEAN update) nvs_close(cache_env->cache_addr[index].cache_fp); cache_env->cache_addr[index].is_open = FALSE; } else { - cacheOpen(bda, false, &index); - if (index == INVALID_ADDR_NUM) { - APPL_TRACE_ERROR("%s INVALID ADDR NUM", __func__); - return; - } + /* The entry exists but is not currently held open in this session; + * try to (re)open it best-effort so we can erase the on-flash blob. + * The recursive cache_env lock is held throughout, so cacheOpen()'s + * internal find_addr_in_cache(bda) cannot disagree with the outer + * find above, i.e. *index will remain valid here. We deliberately + * do NOT bail out on cacheOpen() failure: the entry must still be + * removed from RAM to keep num_addr/cache_addr[] consistent and to + * avoid OOB in callers. */ + (void)cacheOpen(bda, false, &index); if (cache_env->cache_addr[index].is_open) { nvs_erase_all(cache_env->cache_addr[index].cache_fp); nvs_close(cache_env->cache_addr[index].cache_fp); cache_env->cache_addr[index].is_open = FALSE; } else { - APPL_TRACE_ERROR("%s cacheOpen failed", __func__); - return; + APPL_TRACE_ERROR("%s: cacheOpen fail, evict RAM", __func__); } } if(cache_env->num_addr == 0) { @@ -180,6 +209,10 @@ static void cacheReset(BD_ADDR bda, BOOLEAN update) cache_env->cache_addr[index].assoc_addr = NULL; } + if (cache_env->num_addr > MAX_DEVICE_IN_CACHE) { + APPL_TRACE_WARNING("%s: num_addr %u exceeds max, clamping", __func__, cache_env->num_addr); + cache_env->num_addr = MAX_DEVICE_IN_CACHE; + } UINT8 num = cache_env->num_addr; //delete the server_bda in the addr_info list. for(UINT8 i = index; i < (num - 1); i++) { @@ -231,7 +264,6 @@ static void cacheReset(BD_ADDR bda, BOOLEAN update) } } -#endif /* BT_SUPPORT_NVM */ /***************************************************************************** ** Function Declarations *****************************************************************************/ @@ -253,13 +285,19 @@ static void cacheReset(BD_ADDR bda, BOOLEAN update) *******************************************************************************/ tBTA_GATT_STATUS bta_gattc_co_cache_open(BD_ADDR server_bda, BOOLEAN to_save, UINT8 *index) { + cache_env_lock(); /* open NV cache and send call in */ tBTA_GATT_STATUS status = BTA_GATT_OK; + if (cache_env == NULL) { + cache_env_unlock(); + return BTA_GATT_ERROR; + } if (!cacheOpen(server_bda, to_save, index)) { status = BTA_GATT_ERROR; } APPL_TRACE_DEBUG("%s() - status=%d", __func__, status); + cache_env_unlock(); return status; } @@ -280,11 +318,22 @@ tBTA_GATT_STATUS bta_gattc_co_cache_open(BD_ADDR server_bda, BOOLEAN to_save, UI *******************************************************************************/ tBTA_GATT_STATUS bta_gattc_co_cache_load(tBTA_GATTC_NV_ATTR *attr, UINT8 index) { + cache_env_lock(); #if (!CONFIG_BT_STACK_NO_LOG) UINT16 num_attr = 0; #endif tBTA_GATT_STATUS status = BTA_GATT_ERROR; size_t length = 0; + + if (cache_env == NULL) { + cache_env_unlock(); + return BTA_GATT_ERROR; + } + + if (index >= MAX_DEVICE_IN_CACHE) { + cache_env_unlock(); + return BTA_GATT_ERROR; + } // Read the size of memory space required for blob nvs_get_blob(cache_env->cache_addr[index].cache_fp, cache_key, NULL, &length); // Read previously saved blob if available @@ -296,18 +345,28 @@ tBTA_GATT_STATUS bta_gattc_co_cache_load(tBTA_GATTC_NV_ATTR *attr, UINT8 index) APPL_TRACE_DEBUG("%s() - read=%d, status=%d, err_code = %d", __func__, num_attr, status, err_code); + cache_env_unlock(); return status; } size_t bta_gattc_get_cache_attr_length(UINT8 index) { + cache_env_lock(); size_t length = 0; - if (index == INVALID_ADDR_NUM) { + + if (cache_env == NULL) { + cache_env_unlock(); + return 0; + } + + if (index == INVALID_ADDR_NUM || index >= MAX_DEVICE_IN_CACHE) { + cache_env_unlock(); return 0; } // Read the size of memory space required for blob nvs_get_blob(cache_env->cache_addr[index].cache_fp, cache_key, NULL, &length); + cache_env_unlock(); return length; } @@ -330,6 +389,13 @@ size_t bta_gattc_get_cache_attr_length(UINT8 index) void bta_gattc_co_cache_save (BD_ADDR server_bda, UINT16 num_attr, tBTA_GATTC_NV_ATTR *p_attr_list) { + cache_env_lock(); + + if (cache_env == NULL) { + cache_env_unlock(); + return; + } + tBTA_GATT_STATUS status = BTA_GATT_OK; hash_key_t hash_key = {0}; UINT8 index = INVALID_ADDR_NUM; @@ -350,6 +416,7 @@ void bta_gattc_co_cache_save (BD_ADDR server_bda, UINT16 num_attr, (void) status; #endif APPL_TRACE_DEBUG("%s() wrote hash_key = %x%x%x%x, num_attr = %d, status = %d.", __func__, hash_key[0], hash_key[1], hash_key[2], hash_key[3], num_attr, status); + cache_env_unlock(); } /******************************************************************************* @@ -367,14 +434,14 @@ void bta_gattc_co_cache_save (BD_ADDR server_bda, UINT16 num_attr, *******************************************************************************/ void bta_gattc_co_cache_close(BD_ADDR server_bda, UINT16 conn_id) { + cache_env_lock(); UNUSED(conn_id); -//#ifdef BT_SUPPORT_NVM cacheClose(server_bda); -//#endif /* BT_SUPPORT_NVM */ /* close NV when server cache is done saving or loading, does not need to do anything for now on Insight */ BTIF_TRACE_DEBUG("%s()", __FUNCTION__); + cache_env_unlock(); } /******************************************************************************* @@ -391,18 +458,22 @@ void bta_gattc_co_cache_close(BD_ADDR server_bda, UINT16 conn_id) *******************************************************************************/ void bta_gattc_co_cache_reset(BD_ADDR server_bda) { + cache_env_lock(); cacheReset(server_bda, TRUE); + cache_env_unlock(); } void bta_gattc_co_cache_addr_init(void) { + cache_env_mutex_init_once(); + cache_env_lock(); nvs_handle_t fp; esp_err_t err_code; - UINT8 num_addr; size_t length = MAX_ADDR_LIST_CACHE_BUF; UINT8 *p_buf = osi_malloc(MAX_ADDR_LIST_CACHE_BUF); if (p_buf == NULL) { APPL_TRACE_ERROR("%s malloc failed!", __func__); + cache_env_unlock(); return; } @@ -410,6 +481,7 @@ void bta_gattc_co_cache_addr_init(void) if (cache_env == NULL) { APPL_TRACE_ERROR("%s malloc failed!", __func__); osi_free(p_buf); + cache_env_unlock(); return; } @@ -420,19 +492,53 @@ void bta_gattc_co_cache_addr_init(void) cache_env->is_open = TRUE; // Read previously saved blob if available if ((err_code = nvs_get_blob(fp, cache_key, p_buf, &length)) != ESP_OK) { - if(err_code != ESP_ERR_NVS_NOT_FOUND) { + if (err_code == ESP_ERR_NVS_NOT_FOUND) { + length = 0; + } else { APPL_TRACE_ERROR("%s, Line = %d, nvs flash get blob data fail, err_code = 0x%x", __func__, __LINE__, err_code); + osi_free(p_buf); + if (cache_env->is_open) { + nvs_close(cache_env->addr_fp); + cache_env->is_open = FALSE; + } + osi_free(cache_env); + cache_env = NULL; + cache_env_unlock(); + return; } + } + const size_t rec_sz = sizeof(BD_ADDR) + sizeof(hash_key_t); + if (length == 0) { + cache_env->num_addr = 0; osi_free(p_buf); + cache_env_unlock(); return; } - num_addr = length / (sizeof(BD_ADDR) + sizeof(hash_key_t)); - cache_env->num_addr = num_addr; + if ((length % rec_sz) != 0) { + APPL_TRACE_ERROR("%s: bad blob len %zu", __func__, length); + (void)nvs_erase_key(fp, cache_key); + cache_env->num_addr = 0; + osi_free(p_buf); + if (cache_env->is_open) { + nvs_close(cache_env->addr_fp); + cache_env->is_open = FALSE; + } + osi_free(cache_env); + cache_env = NULL; + cache_env_unlock(); + return; + } + size_t n_entries = length / rec_sz; + const BOOLEAN truncated = (n_entries > (size_t)MAX_DEVICE_IN_CACHE) ? TRUE : FALSE; + if (truncated) { + APPL_TRACE_WARNING("%s: trunc %zu->%d", __func__, n_entries, MAX_DEVICE_IN_CACHE); + n_entries = MAX_DEVICE_IN_CACHE; + } + cache_env->num_addr = (UINT8)n_entries; //read the address from nvs flash to cache address list. - for (UINT8 i = 0; i < num_addr; i++) { - memcpy(cache_env->cache_addr[i].addr, p_buf + i*(sizeof(BD_ADDR) + sizeof(hash_key_t)), sizeof(BD_ADDR)); - memcpy(cache_env->cache_addr[i].hash_key, - p_buf + i*(sizeof(BD_ADDR) + sizeof(hash_key_t)) + sizeof(BD_ADDR), sizeof(hash_key_t)); + for (UINT8 i = 0; i < cache_env->num_addr; i++) { + memcpy(cache_env->cache_addr[i].addr, p_buf + i * rec_sz, sizeof(BD_ADDR)); + memcpy(cache_env->cache_addr[i].hash_key, p_buf + i * rec_sz + sizeof(BD_ADDR), sizeof(hash_key_t)); APPL_TRACE_DEBUG("cache_addr[%x] = %x:%x:%x:%x:%x:%x", i, cache_env->cache_addr[i].addr[0], cache_env->cache_addr[i].addr[1], cache_env->cache_addr[i].addr[2], cache_env->cache_addr[i].addr[3], cache_env->cache_addr[i].addr[4], cache_env->cache_addr[i].addr[5]); @@ -440,21 +546,30 @@ void bta_gattc_co_cache_addr_init(void) cache_env->cache_addr[i].hash_key[2], cache_env->cache_addr[i].hash_key[3]); bta_gattc_co_cache_new_assoc_list(cache_env->cache_addr[i].addr, i); } + if (truncated) { + UINT16 out_len = (UINT16)(cache_env->num_addr * rec_sz); + if (nvs_set_blob(fp, cache_key, p_buf, out_len) != ESP_OK) { + APPL_TRACE_WARNING("%s: nvs trunc fail", __func__); + } + } } else { APPL_TRACE_ERROR("%s, Line = %d, nvs flash open fail, err_code = %x", __func__, __LINE__, err_code); osi_free(p_buf); osi_free(cache_env); cache_env = NULL; + cache_env_unlock(); return; } osi_free(p_buf); - return; + cache_env_unlock(); } void bta_gattc_co_cache_addr_deinit(void) { + cache_env_lock(); if(cache_env == NULL) { + cache_env_unlock(); return; } @@ -463,112 +578,222 @@ void bta_gattc_co_cache_addr_deinit(void) if (!cache_env->is_open) { osi_free(cache_env); cache_env = NULL; + cache_env_unlock(); return; } nvs_close(cache_env->addr_fp); cache_env->is_open = false; - for(UINT8 i = 0; i< cache_env->num_addr; i++) { + UINT8 num = cache_env->num_addr; + if (num > MAX_DEVICE_IN_CACHE) { + num = MAX_DEVICE_IN_CACHE; + } + for (UINT8 i = 0; i < num; i++) { cache_addr_info_t *addr_info = &cache_env->cache_addr[i]; - if(addr_info) { + if (addr_info->is_open) { nvs_close(addr_info->cache_fp); addr_info->is_open = false; - if(addr_info->assoc_addr) { - list_free(addr_info->assoc_addr); - } + } + if (addr_info->assoc_addr != NULL) { + list_free(addr_info->assoc_addr); + addr_info->assoc_addr = NULL; } } osi_free(cache_env); cache_env = NULL; + cache_env_unlock(); } BOOLEAN bta_gattc_co_addr_in_cache(BD_ADDR bda) { + cache_env_lock(); + if (cache_env == NULL) { + cache_env_unlock(); + return FALSE; + } UINT8 addr_index = 0; UINT8 num = cache_env->num_addr; + if (num > MAX_DEVICE_IN_CACHE) { + num = MAX_DEVICE_IN_CACHE; + } cache_addr_info_t *addr_info = &cache_env->cache_addr[0]; - for (addr_index = 0; addr_index < num; addr_index++) { + for (addr_index = 0; addr_index < num; addr_index++, addr_info++) { if (!memcmp(addr_info->addr, bda, sizeof(BD_ADDR))) { + cache_env_unlock(); return TRUE; } } + cache_env_unlock(); return FALSE; } UINT8 bta_gattc_co_find_addr_in_cache(BD_ADDR bda) { + cache_env_lock(); + if (cache_env == NULL) { + cache_env_unlock(); + return INVALID_ADDR_NUM; + } UINT8 addr_index = 0; UINT8 num = cache_env->num_addr; + if (num > MAX_DEVICE_IN_CACHE) { + num = MAX_DEVICE_IN_CACHE; + } cache_addr_info_t *addr_info = &cache_env->cache_addr[0]; for (addr_index = 0; addr_index < num; addr_index++, addr_info++) { if (!memcmp(addr_info->addr, bda, sizeof(BD_ADDR))) { + cache_env_unlock(); return addr_index; } } + cache_env_unlock(); return INVALID_ADDR_NUM; } UINT8 bta_gattc_co_find_hash_in_cache(hash_key_t hash_key) { + cache_env_lock(); + if (cache_env == NULL) { + cache_env_unlock(); + return INVALID_ADDR_NUM; + } UINT8 index = 0; UINT8 num = cache_env->num_addr; + if (num > MAX_DEVICE_IN_CACHE) { + num = MAX_DEVICE_IN_CACHE; + } cache_addr_info_t *addr_info = &cache_env->cache_addr[0]; - for (index = 0; index < num; index++) { + for (index = 0; index < num; index++, addr_info++) { if (!memcmp(addr_info->hash_key, hash_key, sizeof(hash_key_t))) { + cache_env_unlock(); return index; } } + cache_env_unlock(); return INVALID_ADDR_NUM; } UINT8 bta_gattc_co_get_addr_num(void) { + cache_env_lock(); if (cache_env == NULL) { + cache_env_unlock(); return 0; } - return cache_env->num_addr; + if (cache_env->num_addr > MAX_DEVICE_IN_CACHE) { + cache_env_unlock(); + return MAX_DEVICE_IN_CACHE; + } + UINT8 n = cache_env->num_addr; + cache_env_unlock(); + return n; } void bta_gattc_co_get_addr_list(BD_ADDR *addr_list) { + cache_env_lock(); + if (cache_env == NULL || addr_list == NULL) { + cache_env_unlock(); + return; + } UINT8 num = cache_env->num_addr; + if (num > MAX_DEVICE_IN_CACHE) { + num = MAX_DEVICE_IN_CACHE; + } for (UINT8 i = 0; i < num; i++) { memcpy(addr_list[i], cache_env->cache_addr[i].addr, sizeof(BD_ADDR)); } + cache_env_unlock(); } void bta_gattc_co_cache_addr_save(BD_ADDR bd_addr, hash_key_t hash_key) { + cache_env_lock(); + if (cache_env == NULL) { + cache_env_unlock(); + return; + } esp_err_t err_code; UINT8 index = 0; UINT8 new_index = cache_env->num_addr; UINT8 *p_buf = osi_malloc(MAX_ADDR_LIST_CACHE_BUF); if (p_buf == NULL) { APPL_TRACE_ERROR("%s malloc failed!", __func__); + cache_env_unlock(); return; } + if (cache_env->num_addr > MAX_DEVICE_IN_CACHE) { + APPL_TRACE_WARNING("%s: num_addr %u exceeds max, clamping", __func__, cache_env->num_addr); + cache_env->num_addr = MAX_DEVICE_IN_CACHE; + } + // check the address list has the same address or not // for the same address, it's hash key may be change due to service change if ((index = bta_gattc_co_find_addr_in_cache(bd_addr)) != INVALID_ADDR_NUM) { APPL_TRACE_DEBUG("%s the bd_addr already in the cache list, index = %x", __func__, index); + /* If the hash key changed (service change on the same peer), the + * per-device NVS namespace is keyed by the OLD hash. Just overwriting + * the in-RAM hash would leave cache_fp/is_open pointing at the old + * namespace, so a subsequent cacheOpen() would short-circuit on + * is_open==TRUE and the new attribute blob would be written into the + * stale namespace. After reboot the addr table maps bd_addr to the + * NEW hash and the load path would miss (and the old namespace is + * orphaned in NVS). Erase the old namespace and clear is_open so + * the next cacheOpen() reopens with the new hash-derived filename. */ + if (memcmp(cache_env->cache_addr[index].hash_key, hash_key, sizeof(hash_key_t)) != 0) { + APPL_TRACE_WARNING("%s: hash chg "MACSTR" %02x%02x%02x%02x->%02x%02x%02x%02x, erase NVS", + __func__, MAC2STR(bd_addr), + cache_env->cache_addr[index].hash_key[0], cache_env->cache_addr[index].hash_key[1], + cache_env->cache_addr[index].hash_key[2], cache_env->cache_addr[index].hash_key[3], + hash_key[0], hash_key[1], hash_key[2], hash_key[3]); + if (cache_env->cache_addr[index].is_open) { + (void)nvs_erase_all(cache_env->cache_addr[index].cache_fp); + nvs_close(cache_env->cache_addr[index].cache_fp); + cache_env->cache_addr[index].is_open = FALSE; + } else { + /* Not currently held open in this session; transiently open + * the old namespace just to erase its blob. Best-effort: + * ignore failures so a missing/corrupt old namespace does + * not block writing the new one. */ + char fname_old[NVS_KEY_NAME_MAX_SIZE] = {0}; + nvs_handle_t old_fp; + getFilename(fname_old, cache_env->cache_addr[index].hash_key); + if (nvs_open(fname_old, NVS_READWRITE, &old_fp) == ESP_OK) { + (void)nvs_erase_all(old_fp); + nvs_close(old_fp); + } + } + } //if the bd_addr already in the address list, update the hash key in it. memcpy(cache_env->cache_addr[index].addr, bd_addr, sizeof(BD_ADDR)); memcpy(cache_env->cache_addr[index].hash_key, hash_key, sizeof(hash_key_t)); } else { - if (cache_env->num_addr >= MAX_DEVICE_IN_CACHE) { + while (cache_env->num_addr >= MAX_DEVICE_IN_CACHE) { APPL_TRACE_WARNING("%s cache list full and remove the oldest addr info", __func__); + UINT8 before = cache_env->num_addr; cacheReset(cache_env->cache_addr[0].addr, FALSE); + if (cache_env->num_addr >= before) { + APPL_TRACE_ERROR("%s: cache eviction failed", __func__); + osi_free(p_buf); + cache_env_unlock(); + return; + } } new_index = cache_env->num_addr; - assert(new_index < MAX_DEVICE_IN_CACHE); + if (new_index >= MAX_DEVICE_IN_CACHE) { + APPL_TRACE_ERROR("%s: invalid new_index %u", __func__, new_index); + osi_free(p_buf); + cache_env_unlock(); + return; + } memcpy(cache_env->cache_addr[new_index].addr, bd_addr, sizeof(BD_ADDR)); memcpy(cache_env->cache_addr[new_index].hash_key, hash_key, sizeof(hash_key_t)); cache_env->num_addr++; @@ -603,22 +828,45 @@ void bta_gattc_co_cache_addr_save(BD_ADDR bd_addr, hash_key_t hash_key) //free the buffer after used. osi_free(p_buf); - return; + cache_env_unlock(); } BOOLEAN bta_gattc_co_cache_new_assoc_list(BD_ADDR src_addr, UINT8 index) { + cache_env_lock(); + if (cache_env == NULL) { + cache_env_unlock(); + return FALSE; + } + UNUSED(src_addr); + if (index >= MAX_DEVICE_IN_CACHE) { + cache_env_unlock(); + return FALSE; + } cache_addr_info_t *addr_info = &cache_env->cache_addr[index]; + /* Idempotent: if a list already exists at this slot (e.g. caller invoked + * us twice on the same index), free it first so we don't leak the prior + * list_t. The current sole caller is bta_gattc_co_cache_addr_init() which + * runs after a fresh memset, so this path is normally a no-op; the guard + * just keeps the function safe to reuse. */ + if (addr_info->assoc_addr != NULL) { + list_free(addr_info->assoc_addr); + addr_info->assoc_addr = NULL; + } addr_info->assoc_addr = list_new(osi_free_func); - return (addr_info->assoc_addr != NULL ? TRUE : FALSE); + BOOLEAN ok = (addr_info->assoc_addr != NULL ? TRUE : FALSE); + cache_env_unlock(); + return ok; } BOOLEAN bta_gattc_co_cache_append_assoc_addr(BD_ADDR src_addr, BD_ADDR assoc_addr) { + cache_env_lock(); UINT8 addr_index = 0; cache_addr_info_t *addr_info; UINT8 *p_assoc_buf = osi_malloc(sizeof(BD_ADDR)); if(!p_assoc_buf) { + cache_env_unlock(); return FALSE; } memcpy(p_assoc_buf, assoc_addr, sizeof(BD_ADDR)); @@ -630,6 +878,7 @@ BOOLEAN bta_gattc_co_cache_append_assoc_addr(BD_ADDR src_addr, BD_ADDR assoc_add if (addr_info->assoc_addr == NULL) { APPL_TRACE_ERROR("assoc_addr list creation failed"); osi_free(p_assoc_buf); + cache_env_unlock(); return FALSE; } @@ -638,6 +887,7 @@ BOOLEAN bta_gattc_co_cache_append_assoc_addr(BD_ADDR src_addr, BD_ADDR assoc_add if (!memcmp(list_node(sn), assoc_addr, sizeof(BD_ADDR))) { APPL_TRACE_WARNING("Association already exists"); osi_free(p_assoc_buf); + cache_env_unlock(); return TRUE; } } @@ -645,19 +895,23 @@ BOOLEAN bta_gattc_co_cache_append_assoc_addr(BD_ADDR src_addr, BD_ADDR assoc_add if (!list_append(addr_info->assoc_addr, p_assoc_buf)) { APPL_TRACE_ERROR("Failed to append to assoc_addr list"); osi_free(p_assoc_buf); + cache_env_unlock(); return FALSE; } + cache_env_unlock(); return TRUE; } else { osi_free(p_assoc_buf); } + cache_env_unlock(); return FALSE; } BOOLEAN bta_gattc_co_cache_remove_assoc_addr(BD_ADDR src_addr, BD_ADDR assoc_addr) { + cache_env_lock(); UINT8 addr_index = 0; cache_addr_info_t *addr_info; if ((addr_index = bta_gattc_co_find_addr_in_cache(src_addr)) != INVALID_ADDR_NUM) { @@ -667,20 +921,34 @@ BOOLEAN bta_gattc_co_cache_remove_assoc_addr(BD_ADDR src_addr, BD_ADDR assoc_add sn != list_end(addr_info->assoc_addr); sn = list_next(sn)) { void *addr = list_node(sn); if (!memcmp(addr, assoc_addr, sizeof(BD_ADDR))) { - return list_remove(addr_info->assoc_addr, addr); + BOOLEAN removed = list_remove(addr_info->assoc_addr, addr); + cache_env_unlock(); + return removed; } } //return list_remove(addr_info->assoc_addr, assoc_addr); } else { + cache_env_unlock(); return FALSE; } } + cache_env_unlock(); return FALSE; } UINT8* bta_gattc_co_cache_find_src_addr(BD_ADDR assoc_addr, UINT8 *index) { + cache_env_lock(); + if (index == NULL) { + cache_env_unlock(); + return NULL; + } + if (cache_env == NULL) { + *index = INVALID_ADDR_NUM; + cache_env_unlock(); + return NULL; + } UINT8 num = (cache_env->num_addr > MAX_DEVICE_IN_CACHE) ? MAX_DEVICE_IN_CACHE : cache_env->num_addr; cache_addr_info_t *addr_info = &cache_env->cache_addr[0]; UINT8 *addr_data; @@ -695,30 +963,37 @@ UINT8* bta_gattc_co_cache_find_src_addr(BD_ADDR assoc_addr, UINT8 *index) addr_data = (UINT8 *)list_node(node); if (!memcmp(addr_data, assoc_addr, sizeof(BD_ADDR))) { *index = i; - return (UINT8 *)addr_info->addr; + UINT8 *ret = (UINT8 *)addr_info->addr; + cache_env_unlock(); + return ret; } } addr_info++; } *index = INVALID_ADDR_NUM; + cache_env_unlock(); return NULL; } BOOLEAN bta_gattc_co_cache_clear_assoc_addr(BD_ADDR src_addr) { + cache_env_lock(); UINT8 addr_index = 0; cache_addr_info_t *addr_info; if ((addr_index = bta_gattc_co_find_addr_in_cache(src_addr)) != INVALID_ADDR_NUM) { addr_info = &cache_env->cache_addr[addr_index]; if (addr_info->assoc_addr != NULL) { list_clear(addr_info->assoc_addr); + cache_env_unlock(); + return TRUE; } else { + cache_env_unlock(); return FALSE; } - return TRUE; } + cache_env_unlock(); return FALSE; } diff --git a/components/bt/host/bluedroid/bta/gatt/bta_gattc_main.c b/components/bt/host/bluedroid/bta/gatt/bta_gattc_main.c index bbf2b346bff..a29f9bd462e 100644 --- a/components/bt/host/bluedroid/bta/gatt/bta_gattc_main.c +++ b/components/bt/host/bluedroid/bta/gatt/bta_gattc_main.c @@ -491,6 +491,12 @@ static char *gattc_evt_code(tBTA_GATTC_INT_EVT evt_code) return "BTA_GATTC_API_READ_BY_TYPE_EVT"; case BTA_GATTC_API_READ_MULTI_VAR_EVT: return "BTA_GATTC_API_READ_MULTI_VAR_EVT"; + case BTA_GATTC_ENC_CMPL_EVT: + return "BTA_GATTC_ENC_CMPL_EVT"; + case BTA_GATTC_API_CACHE_ASSOC_EVT: + return "BTA_GATTC_API_CACHE_ASSOC_EVT"; + case BTA_GATTC_API_CACHE_GET_ADDR_LIST_EVT: + return "BTA_GATTC_API_CACHE_GET_ADDR_LIST_EVT"; default: return "unknown GATTC event code"; } @@ -541,7 +547,8 @@ uint8_t bta_gattc_cl_rcb_active_count(void) for (uint8_t i = 0; i < BTA_GATTC_CL_MAX; i ++) { if (bta_gattc_cb.cl_rcb[i].in_use && - memcmp(bta_gattc_cb.cl_rcb[i].app_uuid.uu.uuid128, dm_gattc_uuid, 16)) { + (bta_gattc_cb.cl_rcb[i].app_uuid.len != LEN_UUID_128 || + memcmp(bta_gattc_cb.cl_rcb[i].app_uuid.uu.uuid128, dm_gattc_uuid, LEN_UUID_128))) { count++; } } diff --git a/components/bt/host/bluedroid/bta/gatt/bta_gattc_utils.c b/components/bt/host/bluedroid/bta/gatt/bta_gattc_utils.c index 1786835e330..332bf33e6da 100644 --- a/components/bt/host/bluedroid/bta/gatt/bta_gattc_utils.c +++ b/components/bt/host/bluedroid/bta/gatt/bta_gattc_utils.c @@ -154,9 +154,12 @@ tBTA_GATTC_CLCB *bta_gattc_find_clcb_by_conn_id (UINT16 conn_id) tBTA_GATTC_CLCB *p_clcb = &bta_gattc_cb.clcb[0]; UINT8 i; + if (conn_id == 0 || conn_id == GATT_INVALID_CONN_ID) { + return NULL; + } + for (i = 0; i < BTA_GATTC_CLCB_MAX; i ++, p_clcb ++) { - if (p_clcb->in_use && - p_clcb->bta_conn_id == conn_id) { + if (p_clcb->in_use && p_clcb->bta_conn_id == conn_id) { return p_clcb; } } @@ -342,7 +345,7 @@ tBTA_GATTC_SERV *bta_gattc_find_srvr_cache(BD_ADDR bda) UINT8 i; for (i = 0; i < BTA_GATTC_KNOWN_SR_MAX; i ++, p_srcb ++) { - if (bdcmp(p_srcb->server_bda, bda) == 0) { + if (p_srcb->in_use && bdcmp(p_srcb->server_bda, bda) == 0) { return p_srcb; } } @@ -387,7 +390,7 @@ tBTA_GATTC_SERV *bta_gattc_srcb_alloc(BD_ADDR bda) if (!p_tcb->in_use) { found = TRUE; break; - } else if (!p_tcb->connected) { + } else if (!p_tcb->connected && p_tcb->num_clcb == 0) { p_recycle = p_tcb; } } @@ -417,6 +420,38 @@ tBTA_GATTC_SERV *bta_gattc_srcb_alloc(BD_ADDR bda) return p_tcb; } +/****************************************************************************** + * + * Fixed-length prefix size of a GATTC API message in the heap buffer passed + * to bta_gattc_enqueue. Must match osi_malloc sizes in bta_gattc_api.c. + * Variable payloads (write value, search UUID) are copied separately. + * + ******************************************************************************/ +static size_t bta_gattc_enqueue_api_fixed_size(UINT16 event) +{ + switch (event) { + case BTA_GATTC_API_READ_EVT: + case BTA_GATTC_API_READ_BY_TYPE_EVT: + return sizeof(tBTA_GATTC_API_READ); + case BTA_GATTC_API_WRITE_EVT: + return sizeof(tBTA_GATTC_API_WRITE); + case BTA_GATTC_API_EXEC_EVT: + return sizeof(tBTA_GATTC_API_EXEC); + case BTA_GATTC_API_CFG_MTU_EVT: + return sizeof(tBTA_GATTC_API_CFG_MTU); + case BTA_GATTC_API_SEARCH_EVT: + return sizeof(tBTA_GATTC_API_SEARCH); + case BTA_GATTC_API_CONFIRM_EVT: + return sizeof(tBTA_GATTC_API_CONFIRM); + case BTA_GATTC_API_READ_MULTI_EVT: + case BTA_GATTC_API_READ_MULTI_VAR_EVT: + return sizeof(tBTA_GATTC_API_READ_MULTI); + default: + APPL_TRACE_ERROR("%s: unexpected event 0x%x", __func__, event); + return 0; + } +} + static BOOLEAN bta_gattc_has_prepare_command_in_queue(tBTA_GATTC_CLCB *p_clcb) { assert(p_clcb != NULL); @@ -485,14 +520,29 @@ BOOLEAN bta_gattc_enqueue(tBTA_GATTC_CLCB *p_clcb, tBTA_GATTC_DATA *p_data) if (p_data->hdr.event == BTA_GATTC_API_WRITE_EVT) { len = p_data->api_write.len; - if ((cmd_data = (tBTA_GATTC_DATA *)osi_malloc(sizeof(tBTA_GATTC_DATA) + len)) != NULL) { - memset(cmd_data, 0, sizeof(tBTA_GATTC_DATA) + len); - memcpy(cmd_data, p_data, sizeof(tBTA_GATTC_DATA)); - cmd_data->api_write.p_value = (UINT8 *)(cmd_data + 1); - memcpy(cmd_data->api_write.p_value, p_data->api_write.p_value, len); + if (len > 0) { + if (p_data->api_write.p_value == NULL) { + APPL_TRACE_ERROR("%s(), write len=%u but p_value is NULL", __func__, len); + return FALSE; + } + if ((cmd_data = (tBTA_GATTC_DATA *)osi_malloc(sizeof(tBTA_GATTC_DATA) + len)) != NULL) { + memset(cmd_data, 0, sizeof(tBTA_GATTC_DATA) + len); + memcpy(cmd_data, p_data, sizeof(tBTA_GATTC_API_WRITE)); + cmd_data->api_write.p_value = (UINT8 *)(cmd_data + 1); + memcpy(cmd_data->api_write.p_value, p_data->api_write.p_value, len); + } else { + APPL_TRACE_ERROR("%s(), line = %d, alloc fail, no memory.", __func__, __LINE__); + return FALSE; + } } else { - APPL_TRACE_ERROR("%s(), line = %d, alloc fail, no memory.", __func__, __LINE__); - return FALSE; + /* len == 0: no payload to copy; keep p_value NULL like BTA_GATTC_API_SEARCH_EVT without UUID */ + if ((cmd_data = (tBTA_GATTC_DATA *)osi_malloc(sizeof(tBTA_GATTC_DATA))) != NULL) { + memset(cmd_data, 0, sizeof(tBTA_GATTC_DATA)); + memcpy(cmd_data, p_data, sizeof(tBTA_GATTC_API_WRITE)); + } else { + APPL_TRACE_ERROR("%s(), line = %d, alloc fail, no memory.", __func__, __LINE__); + return FALSE; + } } } else if (p_data->hdr.event == BTA_GATTC_API_SEARCH_EVT) { /* @@ -547,7 +597,7 @@ BOOLEAN bta_gattc_enqueue(tBTA_GATTC_CLCB *p_clcb, tBTA_GATTC_DATA *p_data) if ((cmd_data = (tBTA_GATTC_DATA *)osi_malloc(len)) != NULL) { memset(cmd_data, 0, len); /* Copy the structure */ - memcpy(cmd_data, p_data, sizeof(tBTA_GATTC_DATA)); + memcpy(cmd_data, p_data, sizeof(tBTA_GATTC_API_SEARCH)); /* Update pointer to point to the space after the structure */ cmd_data->api_search.p_srvc_uuid = (tBT_UUID *)(cmd_data + 1); /* Copy the UUID data */ @@ -561,16 +611,22 @@ BOOLEAN bta_gattc_enqueue(tBTA_GATTC_CLCB *p_clcb, tBTA_GATTC_DATA *p_data) /* p_srvc_uuid is NULL, no extra space needed (search all services) */ if ((cmd_data = (tBTA_GATTC_DATA *)osi_malloc(sizeof(tBTA_GATTC_DATA))) != NULL) { memset(cmd_data, 0, sizeof(tBTA_GATTC_DATA)); - memcpy(cmd_data, p_data, sizeof(tBTA_GATTC_DATA)); + memcpy(cmd_data, p_data, sizeof(tBTA_GATTC_API_SEARCH)); } else { APPL_TRACE_ERROR("%s(), line = %d, alloc fail, no memory.", __func__, __LINE__); return FALSE; } } } else { + size_t copy_sz = bta_gattc_enqueue_api_fixed_size(p_data->hdr.event); + if (copy_sz == 0) { + APPL_TRACE_ERROR("%s(), line = %d, unknown event for queue copy 0x%x.", __func__, __LINE__, + p_data->hdr.event); + return FALSE; + } if ((cmd_data = (tBTA_GATTC_DATA *)osi_malloc(sizeof(tBTA_GATTC_DATA))) != NULL) { memset(cmd_data, 0, sizeof(tBTA_GATTC_DATA)); - memcpy(cmd_data, p_data, sizeof(tBTA_GATTC_DATA)); + memcpy(cmd_data, p_data, copy_sz); } else { APPL_TRACE_ERROR("%s(), line = %d, alloc fail, no memory.", __func__, __LINE__); return FALSE; @@ -612,6 +668,29 @@ BOOLEAN bta_gattc_check_notif_registry(tBTA_GATTC_RCB *p_clreg, tBTA_GATTC_SERV return FALSE; } + +/******************************************************************************* +** +** Function bta_gattc_any_notif_registry +** +** Description check if any GATT client app registered for the notification. +** +** Returns TRUE if any app registered, FALSE otherwise. +** +*******************************************************************************/ +BOOLEAN bta_gattc_any_notif_registry(tBTA_GATTC_SERV *p_srcb, tBTA_GATTC_NOTIFY *p_notify) +{ + UINT8 i; + + for (i = 0; i < BTA_GATTC_CL_MAX; i++) { + if (bta_gattc_cb.cl_rcb[i].in_use && + bta_gattc_check_notif_registry(&bta_gattc_cb.cl_rcb[i], p_srcb, p_notify)) { + return TRUE; + } + } + return FALSE; +} + /******************************************************************************* ** ** Function bta_gattc_clear_notif_registration @@ -888,7 +967,10 @@ tBTA_GATTC_CONN *bta_gattc_conn_alloc(BD_ADDR remote_bda) #if BTA_GATT_DEBUG == TRUE APPL_TRACE_DEBUG("bta_gattc_conn_alloc: found conn_track[%d] available", i_conn); #endif - p_conn->in_use = TRUE; + p_conn->in_use = TRUE; + p_conn->svc_change_descr_handle = 0; + p_conn->write_remote_svc_change_ccc_in_progress = FALSE; + p_conn->write_remote_svc_change_ccc_done = FALSE; bdcpy(p_conn->remote_bda, remote_bda); return p_conn; } @@ -956,6 +1038,9 @@ BOOLEAN bta_gattc_conn_dealloc(BD_ADDR remote_bda) if (p_conn != NULL) { p_conn->in_use = FALSE; + p_conn->svc_change_descr_handle = 0; + p_conn->write_remote_svc_change_ccc_in_progress = FALSE; + p_conn->write_remote_svc_change_ccc_done = FALSE; memset(p_conn->remote_bda, 0, BD_ADDR_LEN); return TRUE; } diff --git a/components/bt/host/bluedroid/bta/gatt/bta_gatts_act.c b/components/bt/host/bluedroid/bta/gatt/bta_gatts_act.c index 9688b5d2bc9..ec8129e6847 100644 --- a/components/bt/host/bluedroid/bta/gatt/bta_gatts_act.c +++ b/components/bt/host/bluedroid/bta/gatt/bta_gatts_act.c @@ -173,11 +173,12 @@ void bta_gatts_api_disable(tBTA_GATTS_CB *p_cb) void bta_gatts_register(tBTA_GATTS_CB *p_cb, tBTA_GATTS_DATA *p_msg) { tBTA_GATTS_INT_START_IF *p_buf; - tBTA_GATTS cb_data; + tBTA_GATTS cb_data = {0}; tBTA_GATT_STATUS status = BTA_GATT_OK; UINT8 i, first_unuse = 0xff; - memset(&cb_data, 0, sizeof(tBTA_GATTS)); + cb_data.reg_oper.server_if = BTA_GATTS_INVALID_IF; + memcpy(&cb_data.reg_oper.uuid, &p_msg->api_reg.app_uuid, sizeof(tBT_UUID)); if (p_cb->enabled == FALSE) { bta_gatts_enable(p_cb); @@ -188,6 +189,7 @@ void bta_gatts_register(tBTA_GATTS_CB *p_cb, tBTA_GATTS_DATA *p_msg) if (gatt_uuid_compare(p_cb->rcb[i].app_uuid, p_msg->api_reg.app_uuid)) { APPL_TRACE_ERROR("application already registered.\n"); status = BTA_GATT_DUP_REG; + cb_data.reg_oper.server_if = p_cb->rcb[i].gatt_if; break; } } @@ -201,8 +203,6 @@ void bta_gatts_register(tBTA_GATTS_CB *p_cb, tBTA_GATTS_DATA *p_msg) } } - cb_data.reg_oper.server_if = BTA_GATTS_INVALID_IF; - memcpy(&cb_data.reg_oper.uuid, &p_msg->api_reg.app_uuid, sizeof(tBT_UUID)); if (first_unuse != 0xff) { APPL_TRACE_VERBOSE("register application first_unuse rcb_idx = %d", first_unuse); @@ -274,7 +274,8 @@ void bta_gatts_deregister(tBTA_GATTS_CB *p_cb, tBTA_GATTS_DATA *p_msg) tBTA_GATT_STATUS status = BTA_GATT_ERROR; tBTA_GATTS_CBACK *p_cback = NULL; UINT8 i; - tBTA_GATTS cb_data; + UINT8 j; + tBTA_GATTS cb_data = {0}; cb_data.reg_oper.server_if = p_msg->api_dereg.server_if; cb_data.reg_oper.status = status; @@ -287,6 +288,12 @@ void bta_gatts_deregister(tBTA_GATTS_CB *p_cb, tBTA_GATTS_DATA *p_msg) /* deregister the app */ GATT_Deregister(p_cb->rcb[i].gatt_if); + for (j = 0; j < BTA_GATTS_MAX_SRVC_NUM; j ++) { + if (p_cb->srvc_cb[j].in_use && p_cb->srvc_cb[j].rcb_idx == i) { + memset(&p_cb->srvc_cb[j], 0, sizeof(tBTA_GATTS_SRVC_CB)); + } + } + /* reset cb */ memset(&p_cb->rcb[i], 0, sizeof(tBTA_GATTS_RCB)); cb_data.reg_oper.status = status; @@ -323,6 +330,16 @@ void bta_gatts_create_srvc(tBTA_GATTS_CB *p_cb, tBTA_GATTS_DATA *p_msg) APPL_TRACE_DEBUG("create service rcb_idx = %d", rcb_idx); if (rcb_idx != BTA_GATTS_INVALID_APP) { + /* + * Populate callback data with the request context early so the app can + * identify which create-service request failed even if resource + * allocation fails before we call into GATT. + */ + cb_data.create.server_if = p_cb->rcb[rcb_idx].gatt_if; + cb_data.create.is_primary = p_msg->api_create_svc.is_pri; + memcpy(&cb_data.create.uuid, &p_msg->api_create_svc.service_uuid, sizeof(tBT_UUID)); + cb_data.create.svc_instance = p_msg->api_create_svc.inst; + if ((srvc_idx = bta_gatts_alloc_srvc_cb(p_cb, rcb_idx)) != BTA_GATTS_INVALID_APP) { /* create the service now */ service_id = GATTS_CreateService (p_cb->rcb[rcb_idx].gatt_if, @@ -345,7 +362,7 @@ void bta_gatts_create_srvc(tBTA_GATTS_CB *p_cb, tBTA_GATTS_DATA *p_msg) cb_data.create.server_if = p_cb->rcb[rcb_idx].gatt_if; } else { - cb_data.status = BTA_GATT_ERROR; + cb_data.create.server_if = p_cb->rcb[rcb_idx].gatt_if; memset(&p_cb->srvc_cb[srvc_idx], 0, sizeof(tBTA_GATTS_SRVC_CB)); APPL_TRACE_ERROR("service creation failed."); } @@ -374,7 +391,7 @@ void bta_gatts_add_include_srvc(tBTA_GATTS_SRVC_CB *p_srvc_cb, tBTA_GATTS_DATA * { tBTA_GATTS_RCB *p_rcb = &bta_gatts_cb.rcb[p_srvc_cb->rcb_idx]; UINT16 attr_id = 0; - tBTA_GATTS cb_data; + tBTA_GATTS cb_data = {0}; attr_id = GATTS_AddIncludeService(p_msg->api_add_incl_srvc.hdr.layer_specific, p_msg->api_add_incl_srvc.included_service_id); @@ -406,7 +423,7 @@ void bta_gatts_add_char(tBTA_GATTS_SRVC_CB *p_srvc_cb, tBTA_GATTS_DATA *p_msg) { tBTA_GATTS_RCB *p_rcb = &bta_gatts_cb.rcb[p_srvc_cb->rcb_idx]; UINT16 attr_id = 0; - tBTA_GATTS cb_data; + tBTA_GATTS cb_data = {0}; tGATT_ATTR_VAL *p_attr_val = NULL; tGATTS_ATTR_CONTROL *p_control = NULL; @@ -436,8 +453,9 @@ void bta_gatts_add_char(tBTA_GATTS_SRVC_CB *p_srvc_cb, tBTA_GATTS_DATA *p_msg) } else { cb_data.add_result.status = BTA_GATT_ERROR; } - if((p_attr_val != NULL) && (p_attr_val->attr_val != NULL)){ - osi_free(p_attr_val->attr_val); + if (p_msg->api_add_char.attr_val.attr_val != NULL) { + osi_free(p_msg->api_add_char.attr_val.attr_val); + p_msg->api_add_char.attr_val.attr_val = NULL; } if (p_rcb->p_cback) { @@ -458,7 +476,7 @@ void bta_gatts_add_char_descr(tBTA_GATTS_SRVC_CB *p_srvc_cb, tBTA_GATTS_DATA *p_ { tBTA_GATTS_RCB *p_rcb = &bta_gatts_cb.rcb[p_srvc_cb->rcb_idx]; UINT16 attr_id = 0; - tBTA_GATTS cb_data; + tBTA_GATTS cb_data = {0}; tGATT_ATTR_VAL *p_attr_val = NULL; tGATTS_ATTR_CONTROL *p_control = NULL; @@ -486,8 +504,9 @@ void bta_gatts_add_char_descr(tBTA_GATTS_SRVC_CB *p_srvc_cb, tBTA_GATTS_DATA *p_ } else { cb_data.add_result.status = BTA_GATT_ERROR; } - if((p_attr_val != NULL) && (p_attr_val->attr_val != NULL)){ - osi_free(p_attr_val->attr_val); + if (p_msg->api_add_char_descr.attr_val.attr_val != NULL) { + osi_free(p_msg->api_add_char_descr.attr_val.attr_val); + p_msg->api_add_char_descr.attr_val.attr_val = NULL; } if (p_rcb->p_cback) { @@ -509,7 +528,7 @@ void bta_gatts_set_attr_value(tBTA_GATTS_SRVC_CB *p_srvc_cb, tBTA_GATTS_DATA *p_ { tBTA_GATTS_RCB *p_rcb = &bta_gatts_cb.rcb[p_srvc_cb->rcb_idx]; UINT16 service_id = p_srvc_cb->service_id; - tBTA_GATTS cb_data; + tBTA_GATTS cb_data = {0}; tBTA_GATT_STATUS gatts_status; gatts_status = GATTS_SetAttributeValue(p_msg->api_set_val.hdr.layer_specific, p_msg->api_set_val.length, @@ -543,11 +562,27 @@ void bta_gatts_set_attr_value(tBTA_GATTS_SRVC_CB *p_srvc_cb, tBTA_GATTS_DATA *p_ tGATT_STATUS bta_gatts_get_attr_value(UINT16 attr_handle, UINT16 *length, UINT8 **value) { - if (GATTS_GetAttributeValueInternal(attr_handle, length, value) == 0) { - return 0; + tGATT_STATUS status = GATTS_GetAttributeValueInternal(attr_handle, length, value); + if (status == GATT_SUCCESS) { + return GATT_SUCCESS; } - return GATTS_GetAttributeValue(attr_handle, length, value); + /* + * Only fall back to the service database when the handle is not part of + * internal GAP/GATT services. For any other internal read error, preserve + * the original status to avoid masking failures as "success with len=0". + */ + if (status != GATT_NOT_FOUND) { + if (length) { + *length = 0; + } + if (value) { + *value = NULL; + } + return status; + } + + return GATTS_GetAttributeValue(attr_handle, length, value); } /******************************************************************************* @@ -562,10 +597,10 @@ tGATT_STATUS bta_gatts_get_attr_value(UINT16 attr_handle, UINT16 *length, UINT8 void bta_gatts_delete_service(tBTA_GATTS_SRVC_CB *p_srvc_cb, tBTA_GATTS_DATA *p_msg) { tBTA_GATTS_RCB *p_rcb = &bta_gatts_cb.rcb[p_srvc_cb->rcb_idx]; - tBTA_GATTS cb_data; + tBTA_GATTS cb_data = {0}; cb_data.srvc_oper.server_if = p_rcb->gatt_if; - cb_data.srvc_oper.service_id = p_msg->api_add_incl_srvc.hdr.layer_specific; + cb_data.srvc_oper.service_id = p_srvc_cb->service_id; if (GATTS_DeleteService(p_rcb->gatt_if, &p_srvc_cb->service_uuid, @@ -574,6 +609,12 @@ void bta_gatts_delete_service(tBTA_GATTS_SRVC_CB *p_srvc_cb, tBTA_GATTS_DATA *p_ memset(p_srvc_cb, 0, sizeof(tBTA_GATTS_SRVC_CB)); } else { cb_data.srvc_oper.status = BTA_GATT_ERROR; + /* + * GATTS_DeleteService() only fails when the service (or app registration) + * cannot be found in the stack. Keeping srvc_cb "in_use" would permanently + * leak a slot and eventually prevent creating new services. + */ + memset(p_srvc_cb, 0, sizeof(tBTA_GATTS_SRVC_CB)); } if (p_rcb->p_cback) { @@ -593,10 +634,10 @@ void bta_gatts_delete_service(tBTA_GATTS_SRVC_CB *p_srvc_cb, tBTA_GATTS_DATA *p_ void bta_gatts_start_service(tBTA_GATTS_SRVC_CB *p_srvc_cb, tBTA_GATTS_DATA *p_msg) { tBTA_GATTS_RCB *p_rcb = &bta_gatts_cb.rcb[p_srvc_cb->rcb_idx]; - tBTA_GATTS cb_data; + tBTA_GATTS cb_data = {0}; cb_data.srvc_oper.server_if = p_rcb->gatt_if; - cb_data.srvc_oper.service_id = p_msg->api_add_incl_srvc.hdr.layer_specific; + cb_data.srvc_oper.service_id = p_srvc_cb->service_id; if (GATTS_StartService(p_rcb->gatt_if, p_srvc_cb->service_id, @@ -624,7 +665,7 @@ void bta_gatts_start_service(tBTA_GATTS_SRVC_CB *p_srvc_cb, tBTA_GATTS_DATA *p_m void bta_gatts_stop_service(tBTA_GATTS_SRVC_CB *p_srvc_cb, tBTA_GATTS_DATA *p_msg) { tBTA_GATTS_RCB *p_rcb = &bta_gatts_cb.rcb[p_srvc_cb->rcb_idx]; - tBTA_GATTS cb_data; + tBTA_GATTS cb_data = {0}; UNUSED(p_msg); GATTS_StopService(p_srvc_cb->service_id); @@ -651,14 +692,65 @@ void bta_gatts_send_rsp (tBTA_GATTS_CB *p_cb, tBTA_GATTS_DATA *p_msg) { UNUSED(p_cb); - if (GATTS_SendRsp (p_msg->api_rsp.hdr.layer_specific, - p_msg->api_rsp.trans_id, - p_msg->api_rsp.status, - (tGATTS_RSP *)p_msg->api_rsp.p_rsp) != GATT_SUCCESS) { - APPL_TRACE_ERROR("Sending response failed\n"); + tGATT_STATUS ret = GATTS_SendRsp(p_msg->api_rsp.hdr.layer_specific, + p_msg->api_rsp.trans_id, + p_msg->api_rsp.status, + (tGATTS_RSP *)p_msg->api_rsp.p_rsp); + if (ret == GATT_CONGESTED) { + APPL_TRACE_WARNING("%s: rsp ok but congested", __func__); + } else if (ret != GATT_SUCCESS) { + APPL_TRACE_ERROR("%s: send rsp fail 0x%02x", __func__, ret); } } +/******************************************************************************* +** +** Function bta_gatts_send_conf_evt_to_app +** +** Description Build a BTA_GATTS_CONF_EVT and dispatch it to the application +** via the supplied RCB callback. The RCB callback is +** btc_gatts_inter_cb (registered by BTA_GATTS_AppRegister), which +** internally posts to the BTC task via btc_transfer_context, so +** delivery is asynchronous w.r.t. the BTA task. +** +** If |value| is non-NULL and |value_len| > 0, the buffer is +** duplicated for the duration of the callback and freed before +** this helper returns. On allocation failure the callback is +** still dispatched with value=NULL/data_len=0 so the application +** does not stall waiting for CONF_EVT. +** +** Returns void +** +*******************************************************************************/ +static void bta_gatts_send_conf_evt_to_app(tBTA_GATTS_RCB *p_rcb, + UINT16 conn_id, UINT16 handle, + tBTA_GATT_STATUS status, + const UINT8 *value, UINT16 value_len) +{ + if (p_rcb == NULL || p_rcb->p_cback == NULL) { + return; + } + tBTA_GATTS cb_data = {0}; + cb_data.req_data.status = status; + cb_data.req_data.conn_id = conn_id; + cb_data.req_data.handle = handle; + cb_data.req_data.value = NULL; + cb_data.req_data.data_len = 0; + if (value != NULL && value_len > 0) { + cb_data.req_data.value = (uint8_t *)osi_malloc(value_len); + if (cb_data.req_data.value != NULL) { + memcpy(cb_data.req_data.value, value, value_len); + cb_data.req_data.data_len = value_len; + } else { + APPL_TRACE_ERROR("%s, malloc(%u) failed", __func__, (unsigned)value_len); + } + } + (*p_rcb->p_cback)(BTA_GATTS_CONF_EVT, &cb_data); + if (cb_data.req_data.value != NULL) { + osi_free(cb_data.req_data.value); + } +} + /******************************************************************************* ** ** Function bta_gatts_indicate_handle @@ -672,71 +764,108 @@ void bta_gatts_indicate_handle (tBTA_GATTS_CB *p_cb, tBTA_GATTS_DATA *p_msg) { tBTA_GATTS_SRVC_CB *p_srvc_cb; tBTA_GATTS_RCB *p_rcb = NULL; + tBTA_GATTS_RCB *p_srvc_rcb = NULL; tBTA_GATT_STATUS status = BTA_GATT_ILLEGAL_PARAMETER; tGATT_IF gatt_if; BD_ADDR remote_bda; tBTA_TRANSPORT transport; - tBTA_GATTS cb_data; p_srvc_cb = bta_gatts_find_srvc_cb_by_attr_id (p_cb, p_msg->api_indicate.attr_id); if (p_srvc_cb ) { + p_srvc_rcb = &p_cb->rcb[p_srvc_cb->rcb_idx]; + if (GATT_GetConnectionInfor(p_msg->api_indicate.hdr.layer_specific, &gatt_if, remote_bda, &transport)) { p_rcb = bta_gatts_find_app_rcb_by_app_if(gatt_if); - if (p_msg->api_indicate.need_confirm) { - - status = GATTS_HandleValueIndication (p_msg->api_indicate.hdr.layer_specific, - p_msg->api_indicate.attr_id, - p_msg->api_indicate.len, - p_msg->api_indicate.value); + if (p_rcb != p_srvc_rcb) { + if (p_rcb == NULL) { + APPL_TRACE_ERROR("%s: no RCB for gatt_if %d", __func__, gatt_if); + } else { + APPL_TRACE_ERROR("%s: if mismatch svc owner", __func__); + } + if (!p_msg->api_indicate.need_confirm) { + l2ble_update_att_acl_pkt_num(L2CA_DECREASE_BTU_NUM, NULL); + } + status = BTA_GATT_ILLEGAL_PARAMETER; } else { - l2ble_update_att_acl_pkt_num(L2CA_DECREASE_BTU_NUM, NULL); - status = GATTS_HandleValueNotification (p_msg->api_indicate.hdr.layer_specific, - p_msg->api_indicate.attr_id, - p_msg->api_indicate.len, - p_msg->api_indicate.value); - } + + if (p_msg->api_indicate.need_confirm) { + + status = GATTS_HandleValueIndication (p_msg->api_indicate.hdr.layer_specific, + p_msg->api_indicate.attr_id, + p_msg->api_indicate.len, + p_msg->api_indicate.value); + } else { + l2ble_update_att_acl_pkt_num(L2CA_DECREASE_BTU_NUM, NULL); + status = GATTS_HandleValueNotification (p_msg->api_indicate.hdr.layer_specific, + p_msg->api_indicate.attr_id, + p_msg->api_indicate.len, + p_msg->api_indicate.value); + } #if (CLASSIC_BT_INCLUDED == TRUE) - /* if over BR_EDR, inform PM for mode change */ - if (transport == BTA_TRANSPORT_BR_EDR) { - bta_sys_busy(BTA_ID_GATTS, BTA_ALL_APP_ID, remote_bda); - bta_sys_idle(BTA_ID_GATTS, BTA_ALL_APP_ID, remote_bda); - } + /* if over BR_EDR, inform PM for mode change */ + if (transport == BTA_TRANSPORT_BR_EDR) { + bta_sys_busy(BTA_ID_GATTS, BTA_ALL_APP_ID, remote_bda); + bta_sys_idle(BTA_ID_GATTS, BTA_ALL_APP_ID, remote_bda); + } #endif // #if (CLASSIC_BT_INCLUDED == TRUE) + } } else { APPL_TRACE_ERROR("Unknown connection ID: %d fail sending notification", p_msg->api_indicate.hdr.layer_specific); + if (!p_msg->api_indicate.need_confirm) { + l2ble_update_att_acl_pkt_num(L2CA_DECREASE_BTU_NUM, NULL); + } } - if ((status != GATT_SUCCESS || !p_msg->api_indicate.need_confirm) && - p_rcb && p_cb->rcb[p_srvc_cb->rcb_idx].p_cback) { - cb_data.req_data.status = status; - cb_data.req_data.conn_id = p_msg->api_indicate.hdr.layer_specific; - cb_data.req_data.value = NULL; - cb_data.req_data.data_len = 0; - cb_data.req_data.handle = p_msg->api_indicate.attr_id; - - if (p_msg->api_indicate.len > 0) { - cb_data.req_data.value = (uint8_t *) osi_malloc(p_msg->api_indicate.len); - if (cb_data.req_data.value != NULL) { - memset(cb_data.req_data.value, 0, p_msg->api_indicate.len); - cb_data.req_data.data_len = p_msg->api_indicate.len; - memcpy(cb_data.req_data.value, p_msg->api_indicate.value, p_msg->api_indicate.len); - } else { - APPL_TRACE_ERROR("%s, malloc failed", __func__); - } - } - (*p_rcb->p_cback)(BTA_GATTS_CONF_EVT, &cb_data); - if (cb_data.req_data.value != NULL) { - osi_free(cb_data.req_data.value); - cb_data.req_data.value = NULL; - } + if (status != GATT_SUCCESS || !p_msg->api_indicate.need_confirm) { + /* BTA_GATTS_CONF_EVT must be delivered to the application that + * initiated the indicate/notify, i.e. the owner of the connection + * (p_rcb resolved from conn_id's gatt_if). Fall back to the + * service-owning RCB only when the conn_id could not be resolved + * (link already torn down) so the application does not stall + * waiting for CONF_EVT. */ + tBTA_GATTS_RCB *p_target_rcb = (p_rcb != NULL) ? p_rcb : p_srvc_rcb; + bta_gatts_send_conf_evt_to_app(p_target_rcb, + p_msg->api_indicate.hdr.layer_specific, + p_msg->api_indicate.attr_id, + status, + p_msg->api_indicate.value, + p_msg->api_indicate.len); } } else { APPL_TRACE_ERROR("Not a registered service attribute ID: 0x%04x", p_msg->api_indicate.attr_id); + if (!p_msg->api_indicate.need_confirm) { + /* Notifications increment the BTU counter in BTA_GATTS_HandleValueIndication(); + * indications do not. So only balance the counter on the notification path. */ + l2ble_update_att_acl_pkt_num(L2CA_DECREASE_BTU_NUM, NULL); + } else { + /* Indication path: the application is waiting for BTA_GATTS_CONF_EVT to know + * the request is finished. Make a best-effort attempt to deliver an error event + * so the app does not stall. Do NOT touch the BTU counter here, since indications + * never incremented it. */ + if (GATT_GetConnectionInfor(p_msg->api_indicate.hdr.layer_specific, + &gatt_if, remote_bda, &transport)) { + p_rcb = bta_gatts_find_app_rcb_by_app_if(gatt_if); + if (p_rcb && p_rcb->p_cback) { + bta_gatts_send_conf_evt_to_app(p_rcb, + p_msg->api_indicate.hdr.layer_specific, + p_msg->api_indicate.attr_id, + BTA_GATT_ILLEGAL_PARAMETER, + NULL, 0); + } else { + APPL_TRACE_ERROR("%s: no RCB if=%d, drop CONF", __func__, gatt_if); + } + } else { + /* conn_id is invalid (e.g. link already torn down). We have no gatt_if, so we + * cannot locate the owning RCB to deliver the callback. The application is + * expected to clean up pending indications on BTA_GATTS_DISCONNECT_EVT. */ + APPL_TRACE_ERROR("%s: bad conn_id %d, drop CONF", __func__, p_msg->api_indicate.hdr.layer_specific); + } + } } } @@ -916,13 +1045,11 @@ static void bta_gatts_send_request_cback (UINT16 conn_id, UINT32 trans_id, tGATTS_REQ_TYPE req_type, tGATTS_DATA *p_data) { - tBTA_GATTS cb_data; + tBTA_GATTS cb_data = {0}; tBTA_GATTS_RCB *p_rcb; tGATT_IF gatt_if; tBTA_GATT_TRANSPORT transport; - memset(&cb_data, 0 , sizeof(tBTA_GATTS)); - if (GATT_GetConnectionInfor(conn_id, &gatt_if, cb_data.req_data.remote_bda, &transport)) { p_rcb = bta_gatts_find_app_rcb_by_app_if(gatt_if); @@ -974,10 +1101,10 @@ static void bta_gatts_conn_cback (tGATT_IF gatt_if, BD_ADDR bda, UINT16 conn_id, gatt_if, conn_id, connected, reason); APPL_TRACE_DEBUG("bta_gatts_conn_cback bda :%02x-%02x-%02x-%02x-%02x-%02x ", bda[0], bda[1], bda[2], bda[3], bda[4], bda[5]); - + /* bt_bdaddr_t bdaddr; bdcpy(bdaddr.address, bda); - /* + if (connected) btif_debug_conn_state(bdaddr, BTIF_DEBUG_CONNECTED, GATT_CONN_UNKNOWN); else @@ -1033,7 +1160,7 @@ static void bta_gatts_conn_cback (tGATT_IF gatt_if, BD_ADDR bda, UINT16 conn_id, *******************************************************************************/ static void bta_gatts_cong_cback (UINT16 conn_id, BOOLEAN congested) { - tBTA_GATTS cb_data; + tBTA_GATTS cb_data = {0}; cb_data.congest.conn_id = conn_id; cb_data.congest.congested = congested; btc_congest_callback(&cb_data); 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 358b6939913..f8774de77c5 100644 --- a/components/bt/host/bluedroid/bta/gatt/bta_gatts_api.c +++ b/components/bt/host/bluedroid/bta/gatt/bta_gatts_api.c @@ -93,6 +93,7 @@ void BTA_GATTS_AppRegister(const tBT_UUID * p_app_uuid, tBTA_GATTS_CBACK *p_cbac } if ((p_buf = (tBTA_GATTS_API_REG *) osi_malloc(sizeof(tBTA_GATTS_API_REG))) != NULL) { + memset(p_buf, 0, sizeof(*p_buf)); p_buf->hdr.event = BTA_GATTS_API_REG_EVT; if (p_app_uuid != NULL) { @@ -237,16 +238,23 @@ void BTA_GATTS_AddCharacteristic (UINT16 service_id, const tBT_UUID * p_char_u } if(attr_val != NULL){ - p_buf->attr_val.attr_len = attr_val->attr_len; p_buf->attr_val.attr_max_len = attr_val->attr_max_len; - if(len != 0){ - 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, len); - } else { - p_buf->attr_val.attr_len = 0; - p_buf->attr_val.attr_max_len = 0; - APPL_TRACE_ERROR("Allocate fail for %s\n", __func__); + if (attr_val->attr_max_len == 0) { + p_buf->attr_val.attr_len = 0; + if (len != 0) { + APPL_TRACE_WARNING("%s: max_len 0, drop len %u", __func__, len); + } + } else { + p_buf->attr_val.attr_len = attr_val->attr_len; + if(len != 0){ + 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, len); + } else { + p_buf->attr_val.attr_len = 0; + p_buf->attr_val.attr_max_len = 0; + APPL_TRACE_ERROR("alloc fail %s", __func__); + } } } } @@ -299,19 +307,27 @@ void BTA_GATTS_AddCharDescriptor (UINT16 service_id, } if(attr_val != NULL){ - p_buf->attr_val.attr_len = attr_val->attr_len; p_buf->attr_val.attr_max_len = attr_val->attr_max_len; - value_len = attr_val->attr_len; - if (value_len != 0){ - p_buf->attr_val.attr_val = (uint8_t*)osi_malloc(value_len); - if(p_buf->attr_val.attr_val != NULL){ - memcpy(p_buf->attr_val.attr_val, attr_val->attr_val, value_len); + if (attr_val->attr_max_len == 0) { + p_buf->attr_val.attr_len = 0; + value_len = attr_val->attr_len; + if (value_len != 0) { + APPL_TRACE_WARNING("%s: max_len 0, drop len %u", __func__, value_len); } - else{ - p_buf->attr_val.attr_len = 0; - p_buf->attr_val.attr_max_len = 0; - APPL_TRACE_ERROR("Allocate fail for %s\n", __func__); + } else { + p_buf->attr_val.attr_len = attr_val->attr_len; + value_len = attr_val->attr_len; + if (value_len != 0){ + p_buf->attr_val.attr_val = (uint8_t*)osi_malloc(value_len); + if(p_buf->attr_val.attr_val != NULL){ + memcpy(p_buf->attr_val.attr_val, attr_val->attr_val, value_len); + } + else{ + p_buf->attr_val.attr_len = 0; + p_buf->attr_val.attr_max_len = 0; + APPL_TRACE_ERROR("alloc fail %s", __func__); + } } } } @@ -501,8 +517,10 @@ void BTA_SetAttributeValue(UINT16 attr_handle, UINT16 length, UINT8 *value) memset(p_buf, 0, len); p_buf->hdr.event = BTA_GATTS_API_SET_ATTR_VAL_EVT; p_buf->hdr.layer_specific = attr_handle; - p_buf->length = length; - if(value != NULL){ + if (value == NULL) { + p_buf->length = 0; + } else { + p_buf->length = length; if((p_buf->value = (UINT8 *)osi_malloc(length)) != NULL){ memcpy(p_buf->value, value, length); } else { diff --git a/components/bt/host/bluedroid/bta/gatt/bta_gatts_main.c b/components/bt/host/bluedroid/bta/gatt/bta_gatts_main.c index 6444321437a..2c78d2df8ef 100644 --- a/components/bt/host/bluedroid/bta/gatt/bta_gatts_main.c +++ b/components/bt/host/bluedroid/bta/gatt/bta_gatts_main.c @@ -132,6 +132,16 @@ BOOLEAN bta_gatts_hdl_event(BT_HDR *p_msg) if (p_srvc_cb != NULL) { bta_gatts_srvc_build_act[p_msg->event - BTA_GATTS_API_ADD_INCL_SRVC_EVT](p_srvc_cb, (tBTA_GATTS_DATA *) p_msg); } else { + tBTA_GATTS_DATA *p_data = (tBTA_GATTS_DATA *)p_msg; + if (p_msg->event == BTA_GATTS_API_ADD_CHAR_EVT && + p_data->api_add_char.attr_val.attr_val != NULL) { + osi_free(p_data->api_add_char.attr_val.attr_val); + p_data->api_add_char.attr_val.attr_val = NULL; + } else if (p_msg->event == BTA_GATTS_API_ADD_DESCR_EVT && + p_data->api_add_char_descr.attr_val.attr_val != NULL) { + osi_free(p_data->api_add_char_descr.attr_val.attr_val); + p_data->api_add_char_descr.attr_val.attr_val = NULL; + } APPL_TRACE_ERROR("service not created\n"); } break; diff --git a/components/bt/host/bluedroid/bta/gatt/include/bta_gattc_int.h b/components/bt/host/bluedroid/bta/gatt/include/bta_gattc_int.h index e74b5759ee7..770d38456fa 100644 --- a/components/bt/host/bluedroid/bta/gatt/include/bta_gattc_int.h +++ b/components/bt/host/bluedroid/bta/gatt/include/bta_gattc_int.h @@ -211,6 +211,7 @@ typedef struct { typedef struct { BT_HDR hdr; BD_ADDR remote_bda; + BOOLEAN erase_flash; } tBTA_GATTC_API_CACHE_REFRESH; typedef struct { @@ -399,6 +400,8 @@ typedef struct { BOOLEAN in_use; BD_ADDR remote_bda; UINT16 svc_change_descr_handle; + /* Tracks the in-flight internal write to the Service Changed CCC descriptor */ + BOOLEAN write_remote_svc_change_ccc_in_progress; BOOLEAN write_remote_svc_change_ccc_done; } tBTA_GATTC_CONN; @@ -520,6 +523,7 @@ extern BOOLEAN bta_gattc_enqueue(tBTA_GATTC_CLCB *p_clcb, tBTA_GATTC_DATA *p_dat extern BOOLEAN bta_gattc_uuid_compare (const tBT_UUID *p_src, const tBT_UUID *p_tar, BOOLEAN is_precise); extern BOOLEAN bta_gattc_check_notif_registry(tBTA_GATTC_RCB *p_clreg, tBTA_GATTC_SERV *p_srcb, tBTA_GATTC_NOTIFY *p_notify); +extern BOOLEAN bta_gattc_any_notif_registry(tBTA_GATTC_SERV *p_srcb, tBTA_GATTC_NOTIFY *p_notify); extern BOOLEAN bta_gattc_mark_bg_conn (tBTA_GATTC_IF client_if, BD_ADDR_PTR remote_bda, BOOLEAN add, BOOLEAN is_listen); extern BOOLEAN bta_gattc_check_bg_conn (tBTA_GATTC_IF client_if, BD_ADDR remote_bda, UINT8 role); extern UINT8 bta_gattc_num_reg_app(void); @@ -558,7 +562,7 @@ extern void bta_gattc_get_db_with_operation(UINT16 conn_id, extern void bta_gattc_get_gatt_db(UINT16 conn_id, UINT16 start_handle, UINT16 end_handle, btgatt_db_element_t **db, UINT16 *count); extern tBTA_GATT_STATUS bta_gattc_init_cache(tBTA_GATTC_SERV *p_srvc_cb); -extern void bta_gattc_rebuild_cache(tBTA_GATTC_SERV *p_srcv, UINT16 num_attr, tBTA_GATTC_NV_ATTR *attr); +extern tBTA_GATT_STATUS bta_gattc_rebuild_cache(tBTA_GATTC_SERV *p_srcv, UINT16 num_attr, tBTA_GATTC_NV_ATTR *attr); extern void bta_gattc_cache_save(tBTA_GATTC_SERV *p_srvc_cb, UINT16 conn_id); extern void bta_gattc_reset_discover_st(tBTA_GATTC_SERV *p_srcb, tBTA_GATT_STATUS status); diff --git a/components/bt/host/bluedroid/bta/hf_ag/bta_ag_cmd.c b/components/bt/host/bluedroid/bta/hf_ag/bta_ag_cmd.c index 499b1435e67..67fa725c017 100644 --- a/components/bt/host/bluedroid/bta/hf_ag/bta_ag_cmd.c +++ b/components/bt/host/bluedroid/bta/hf_ag/bta_ag_cmd.c @@ -696,7 +696,7 @@ static tBTA_AG_PEER_CODEC bta_ag_parse_bac(tBTA_AG_SCB *p_scb, char *p_s) default: APPL_TRACE_ERROR("Unknown Codec UUID(%d) received", uuid_codec); - return BTA_AG_CODEC_NONE; + break; } if (cont) { p_s = p + 1; @@ -917,7 +917,7 @@ void bta_ag_at_hfp_cback(tBTA_AG_SCB *p_scb, UINT16 cmd, UINT8 arg_type, val.value = BTA_AG_HF_DIAL_NUM; } if (event != 0) { - while ((val.str[dst] = p_arg[src]) != '\0') { + while (dst < BTA_AG_AT_MAX_LEN && (val.str[dst] = p_arg[src]) != '\0') { if (val.str[dst] == ';') { val.str[dst] = '\0'; break; @@ -925,6 +925,9 @@ void bta_ag_at_hfp_cback(tBTA_AG_SCB *p_scb, UINT16 cmd, UINT8 arg_type, src++; dst++; } + if (dst >= BTA_AG_AT_MAX_LEN) { + val.str[BTA_AG_AT_MAX_LEN] = '\0'; + } } break; } diff --git a/components/bt/host/bluedroid/bta/hf_ag/bta_ag_sco.c b/components/bt/host/bluedroid/bta/hf_ag/bta_ag_sco.c index b77b002e140..5fb9b0810ba 100644 --- a/components/bt/host/bluedroid/bta/hf_ag/bta_ag_sco.c +++ b/components/bt/host/bluedroid/bta/hf_ag/bta_ag_sco.c @@ -846,6 +846,7 @@ static void bta_ag_sco_event(tBTA_AG_SCB *p_scb, UINT8 event) } } else { osi_free(p_buf); + break; } } else { osi_free(p_buf); @@ -1997,9 +1998,10 @@ static void bta_ag_sco_data_send_msbc(tBTA_AG_SCB *p_scb, BT_HDR *p_buf) p_buf3->offset = BTA_AG_BUFF_OFFSET_MIN; p_buf3->len = BTA_AG_SCO_OUT_PKT_LEN_EV3; UINT8 *p_data3 = (UINT8 *)(p_buf3 + 1) + p_buf3->offset; - memcpy(p_data3, p_data, BTA_AG_MSBC_FRAME_SIZE - BTA_AG_H2_HEADER_LEN - BTA_AG_SCO_OUT_PKT_LEN_EV3); - p_data += BTA_AG_MSBC_FRAME_SIZE - BTA_AG_H2_HEADER_LEN - BTA_AG_SCO_OUT_PKT_LEN_EV3; - total_len -= BTA_AG_MSBC_FRAME_SIZE - BTA_AG_H2_HEADER_LEN - BTA_AG_SCO_OUT_PKT_LEN_EV3; + UINT16 rem_payload = BTA_AG_MSBC_FRAME_SIZE - (BTA_AG_SCO_OUT_PKT_LEN_EV3 - BTA_AG_H2_HEADER_LEN); + memcpy(p_data3, p_data, rem_payload); + p_data += rem_payload; + total_len -= rem_payload; bta_ag_write_sco_data(p_scb, p_buf2, p_buf3); } } diff --git a/components/bt/host/bluedroid/bta/hf_ag/bta_ag_sdp.c b/components/bt/host/bluedroid/bta/hf_ag/bta_ag_sdp.c index 9864f663908..1d6aa34b095 100644 --- a/components/bt/host/bluedroid/bta/hf_ag/bta_ag_sdp.c +++ b/components/bt/host/bluedroid/bta/hf_ag/bta_ag_sdp.c @@ -266,7 +266,10 @@ void bta_ag_del_records(tBTA_AG_SCB *p_scb, tBTA_AG_DATA *p_data) SDP_DeleteRecord(bta_ag_cb.profile[i].sdp_handle); bta_ag_cb.profile[i].sdp_handle = 0; } - BTM_FreeSCN(bta_ag_cb.profile[i].scn); + if (bta_ag_cb.profile[i].scn != 0) { + BTM_FreeSCN(bta_ag_cb.profile[i].scn); + bta_ag_cb.profile[i].scn = 0; + } BTM_SecClrService(bta_ag_sec_id[i]); bta_sys_remove_uuid(bta_ag_uuid[i]); } diff --git a/components/bt/host/bluedroid/bta/hf_client/bta_hf_client_main.c b/components/bt/host/bluedroid/bta/hf_client/bta_hf_client_main.c index e72bd2b9452..d811fa51dfd 100644 --- a/components/bt/host/bluedroid/bta/hf_client/bta_hf_client_main.c +++ b/components/bt/host/bluedroid/bta/hf_client/bta_hf_client_main.c @@ -314,6 +314,14 @@ void bta_hf_client_scb_disable(void) bta_hf_client_cb.scb.p_sco_data = NULL; } + if (bta_hf_client_cb.scb.p_disc_db != NULL) { + (void)SDP_CancelServiceSearch(bta_hf_client_cb.scb.p_disc_db); + bta_hf_client_free_db(NULL); + } + bta_hf_client_cb.scb.colli_tmr_on = FALSE; + bta_sys_free_timer(&bta_hf_client_cb.scb.colli_timer); + bta_hf_client_at_reset(); + bta_hf_client_scb_init(); if (bta_hf_client_cb.p_cback) { diff --git a/components/bt/host/bluedroid/bta/hf_client/bta_hf_client_sco.c b/components/bt/host/bluedroid/bta/hf_client/bta_hf_client_sco.c index e8b40aa15ea..83406836ee1 100644 --- a/components/bt/host/bluedroid/bta/hf_client/bta_hf_client_sco.c +++ b/components/bt/host/bluedroid/bta/hf_client/bta_hf_client_sco.c @@ -557,9 +557,10 @@ static void bta_hf_client_sco_data_send_msbc(BT_HDR *p_buf, UINT16 out_pkt_len) p_buf3->offset = BTA_HF_CLIENT_BUFF_OFFSET_MIN; p_buf3->len = BTA_HF_CLIENT_SCO_OUT_PKT_LEN_EV3; UINT8 *p_data3 = (UINT8 *)(p_buf3 + 1) + p_buf3->offset; - memcpy(p_data3, p_data, BTA_HF_CLIENT_MSBC_FRAME_SIZE - BTA_HF_CLIENT_H2_HEADER_LEN - BTA_HF_CLIENT_SCO_OUT_PKT_LEN_EV3); - p_data += BTA_HF_CLIENT_MSBC_FRAME_SIZE - BTA_HF_CLIENT_H2_HEADER_LEN - BTA_HF_CLIENT_SCO_OUT_PKT_LEN_EV3; - total_len -= BTA_HF_CLIENT_MSBC_FRAME_SIZE - BTA_HF_CLIENT_H2_HEADER_LEN - BTA_HF_CLIENT_SCO_OUT_PKT_LEN_EV3; + UINT16 rem_payload = BTA_HF_CLIENT_MSBC_FRAME_SIZE - (BTA_HF_CLIENT_SCO_OUT_PKT_LEN_EV3 - BTA_HF_CLIENT_H2_HEADER_LEN); + memcpy(p_data3, p_data, rem_payload); + p_data += rem_payload; + total_len -= rem_payload; bta_hf_client_write_sco_data(p_buf2, p_buf3); } } @@ -875,6 +876,7 @@ static void bta_hf_client_sco_event(UINT8 event) } } else { osi_free(p_buf); + break; } } else { osi_free(p_buf); diff --git a/components/bt/host/bluedroid/bta/hf_client/bta_hf_client_sdp.c b/components/bt/host/bluedroid/bta/hf_client/bta_hf_client_sdp.c index 5ed7e443a7e..7faebb674ed 100644 --- a/components/bt/host/bluedroid/bta/hf_client/bta_hf_client_sdp.c +++ b/components/bt/host/bluedroid/bta/hf_client/bta_hf_client_sdp.c @@ -204,7 +204,10 @@ void bta_hf_client_del_record(tBTA_HF_CLIENT_DATA *p_data) if (bta_hf_client_cb.sdp_handle != 0) { SDP_DeleteRecord(bta_hf_client_cb.sdp_handle); bta_hf_client_cb.sdp_handle = 0; - BTM_FreeSCN(bta_hf_client_cb.scn); + if (bta_hf_client_cb.scn != 0) { + BTM_FreeSCN(bta_hf_client_cb.scn); + bta_hf_client_cb.scn = 0; + } BTM_SecClrService(BTM_SEC_SERVICE_HF_HANDSFREE); bta_sys_remove_uuid(UUID_SERVCLASS_HF_HANDSFREE); } diff --git a/components/bt/host/bluedroid/bta/include/bta/bta_api.h b/components/bt/host/bluedroid/bta/include/bta/bta_api.h index 7a6594c2dd6..d6400b38cf0 100644 --- a/components/bt/host/bluedroid/bta/include/bta/bta_api.h +++ b/components/bt/host/bluedroid/bta/include/bta/bta_api.h @@ -1605,6 +1605,33 @@ typedef struct { #define BTA_DM_BLE_5_GAP_ENABLE_MONITOR_ADV_COMPLETE_EVT BTM_BLE_5_GAP_ENABLE_MONITOR_ADV_COMPLETE_EVT #endif // #if (BLE_FEAT_ADV_MONITOR == TRUE) +#if (BLE_FEAT_DBAF == TRUE) +#define BTA_DM_BLE_5_GAP_SET_DECISION_DATA_COMPLETE_EVT BTM_BLE_5_GAP_SET_DECISION_DATA_COMPLETE_EVT +#define BTA_DM_BLE_5_GAP_SET_DECISION_INSTRUCTIONS_COMPLETE_EVT BTM_BLE_5_GAP_SET_DECISION_INSTRUCTIONS_COMPLETE_EVT +#endif // #if (BLE_FEAT_DBAF == TRUE) + +#if (BLE_FEAT_FRAME_SPACE_UPDATE == TRUE) +#define BTA_DM_BLE_5_GAP_FRAME_SPACE_UPDATE_COMPLETE_EVT BTM_BLE_5_GAP_FRAME_SPACE_UPDATE_COMPLETE_EVT +#endif // #if (BLE_FEAT_FRAME_SPACE_UPDATE == TRUE) + +#if (BLE_FEAT_LL_EXT_FEAT == TRUE) +#define BTA_DM_BLE_5_GAP_READ_ALL_LOCAL_SUPP_FEAT_COMPLETE_EVT BTM_BLE_5_GAP_READ_ALL_LOCAL_SUPP_FEAT_COMPLETE_EVT +#define BTA_DM_BLE_5_GAP_READ_ALL_REMOTE_FEAT_COMPLETE_EVT BTM_BLE_5_GAP_READ_ALL_REMOTE_FEAT_COMPLETE_EVT +#endif // #if (BLE_FEAT_LL_EXT_FEAT == TRUE) + +#if (BLE_FEAT_SHORTER_CONN_INTERVALS == TRUE) +#define BTA_DM_BLE_5_GAP_CONNECTION_RATE_REQUEST_COMPLETE_EVT BTM_BLE_5_GAP_CONNECTION_RATE_REQUEST_COMPLETE_EVT +#define BTA_DM_BLE_5_GAP_CONN_RATE_CHANGE_EVT BTM_BLE_5_GAP_CONN_RATE_CHANGE_EVT +#define BTA_DM_BLE_5_GAP_SET_DEFAULT_RATE_PARAMETERS_COMPLETE_EVT BTM_BLE_5_GAP_SET_DEFAULT_RATE_PARAMETERS_COMPLETE_EVT +#define BTA_DM_BLE_5_GAP_READ_MIN_SUPP_CONN_INTERVAL_COMPLETE_EVT BTM_BLE_5_GAP_READ_MIN_SUPP_CONN_INTERVAL_COMPLETE_EVT +#endif // #if (BLE_FEAT_SHORTER_CONN_INTERVALS == TRUE) + +#if (BLE_FEAT_LE_UTP == TRUE) +#define BTA_DM_BLE_5_GAP_ENABLE_UTP_OTA_MODE_COMPLETE_EVT BTM_BLE_5_GAP_ENABLE_UTP_OTA_MODE_COMPLETE_EVT +#define BTA_DM_BLE_5_GAP_UTP_SEND_COMPLETE_EVT BTM_BLE_5_GAP_UTP_SEND_COMPLETE_EVT +#define BTA_DM_BLE_5_GAP_UTP_RECEIVE_EVT BTM_BLE_5_GAP_UTP_RECEIVE_EVT +#endif // #if (BLE_FEAT_LE_UTP == TRUE) + #if (BT_BLE_FEAT_PAWR_EN == TRUE) #define BTA_BLE_GAP_SET_PERIODIC_ADV_SUBEVT_DATA_EVT BTM_BLE_GAP_SET_PERIODIC_ADV_SUBEVT_DATA_EVT #define BTA_BLE_GAP_SET_PERIODIC_ADV_RESPONSE_DATA_EVT BTM_BLE_GAP_SET_PERIODIC_ADV_RESPONSE_DATA_EVT @@ -1628,6 +1655,10 @@ typedef struct { #define BTA_BLE_GAP_CS_SUBEVENT_RESULT_EVT BTM_BLE_GAP_CS_SUBEVENT_RESULT_EVT #define BTA_BLE_GAP_CS_SUBEVENT_RESULT_CONTINUE_EVT BTM_BLE_GAP_CS_SUBEVENT_RESULT_CONTINUE_EVT #endif // (BT_BLE_FEAT_CHANNEL_SOUNDING == TRUE) +#if (BT_BLE_FEAT_CS_SECURITY_REQUIREMENTS == TRUE) +#define BTA_BLE_GAP_CS_SET_SECURITY_REQUIREMENTS_CMPL_EVT BTM_BLE_GAP_CS_SET_SECURITY_REQUIREMENTS_CMPL_EVT +#define BTA_BLE_GAP_CS_SET_DEFAULT_SECURITY_REQUIREMENTS_CMPL_EVT BTM_BLE_GAP_CS_SET_DEFAULT_SECURITY_REQUIREMENTS_CMPL_EVT +#endif // (BT_BLE_FEAT_CS_SECURITY_REQUIREMENTS == TRUE) #define BTA_DM_BLE_5_GAP_UNKNOWN_EVT BTM_BLE_5_GAP_UNKNOWN_EVT typedef tBTM_BLE_5_GAP_EVENT tBTA_DM_BLE_5_GAP_EVENT; @@ -2960,6 +2991,10 @@ void BTA_DmBleGapCsSetChannelClass(uint8_t *channel_class, uint8_t channl_len); void BTA_DmBleGapCsSetProcPatams(tBTA_DM_CS_SET_PROC_PARAMS *set_proc_params); void BTA_DmBleGapCsProcEnable(uint16_t conn_handle, uint8_t config_id, uint8_t enable); #endif // (BT_BLE_FEAT_CHANNEL_SOUNDING == TRUE) +#if (BT_BLE_FEAT_CS_SECURITY_REQUIREMENTS == TRUE) +void BTA_DmBleGapCsSetSecurityRequirements(uint16_t conn_handle, uint64_t cs_security_requirements); +void BTA_DmBleGapCsSetDefaultSecurityRequirements(uint64_t cs_security_requirements); +#endif // (BT_BLE_FEAT_CS_SECURITY_REQUIREMENTS == TRUE) /******************************************************************************* ** @@ -3061,6 +3096,42 @@ extern void BTA_DmBleGapReadMonitorAdvListSize(void); extern void BTA_DmBleGapEnableMonitorAdv(UINT8 enable); #endif // #if (BLE_FEAT_ADV_MONITOR == TRUE) +#if (BLE_FEAT_DBAF == TRUE) +extern void BTA_DmBleGapSetDecisionData(UINT8 adv_handle, UINT8 decision_type_flags, + UINT8 data_len, const UINT8 *p_data); +extern void BTA_DmBleGapSetDecisionInstructions(UINT8 num_tests, const UINT8 *test_flags, + const UINT8 *test_fields, const UINT8 *test_params); +#endif // #if (BLE_FEAT_DBAF == TRUE) + +#if (BLE_FEAT_FRAME_SPACE_UPDATE == TRUE) +extern void BTA_DmBleGapFrameSpaceUpdate(UINT16 conn_handle, UINT16 frame_space_min, + UINT16 frame_space_max, UINT8 phys, UINT16 spacing_types); +#endif // #if (BLE_FEAT_FRAME_SPACE_UPDATE == TRUE) + +#if (BLE_FEAT_LL_EXT_FEAT == TRUE) +extern void BTA_DmBleGapReadAllLocalSuppFeatures(void); +extern void BTA_DmBleGapReadAllRemoteFeatures(UINT16 conn_handle, UINT8 page_requested); +#endif // #if (BLE_FEAT_LL_EXT_FEAT == TRUE) + +#if (BLE_FEAT_SHORTER_CONN_INTERVALS == TRUE) +extern void BTA_DmBleGapConnectionRateRequest(UINT16 conn_handle, UINT16 conn_interval_min, + UINT16 conn_interval_max, UINT16 subrate_min, + UINT16 subrate_max, UINT16 max_latency, + UINT16 continuation_number, UINT16 supervision_timeout, + UINT16 min_ce_len, UINT16 max_ce_len); +extern void BTA_DmBleGapSetDefaultRateParameters(UINT16 conn_interval_min, UINT16 conn_interval_max, + UINT16 subrate_min, UINT16 subrate_max, + UINT16 max_latency, UINT16 continuation_number, + UINT16 supervision_timeout, UINT16 min_ce_len, + UINT16 max_ce_len); +extern void BTA_DmBleGapReadMinSuppConnInterval(void); +#endif // #if (BLE_FEAT_SHORTER_CONN_INTERVALS == TRUE) + +#if (BLE_FEAT_LE_UTP == TRUE) +extern void BTA_DmBleGapEnableUtpOtaMode(UINT8 enable); +extern void BTA_DmBleGapUtpSend(UINT8 data_len, const UINT8 *p_data); +#endif // #if (BLE_FEAT_LE_UTP == TRUE) + #if (BLE_FEAT_ISO_EN == TRUE) #if (BLE_FEAT_ISO_BIG_BROADCASTER_EN == TRUE) extern void BTA_DmBleGapIsoBigCreate(tBTA_DM_BLE_BIG_CREATE_PARAMS *p_big_creat_param); diff --git a/components/bt/host/bluedroid/bta/include/bta/bta_gatt_api.h b/components/bt/host/bluedroid/bta/include/bta/bta_gatt_api.h index d18badf2d51..6aa59059f68 100644 --- a/components/bt/host/bluedroid/bta/include/bta/bta_gatt_api.h +++ b/components/bt/host/bluedroid/bta/include/bta/bta_gatt_api.h @@ -676,10 +676,17 @@ typedef union { add char : BTA_GATTS_ADD_CHAR_EVT add char descriptor: BTA_GATTS_ADD_CHAR_DESCR_EVT */ tBAT_GATTS_ATTR_VAL_RESULT attr_val; - tBTA_GATTS_REQ req_data; + tBTA_GATTS_REQ req_data; /* BTA_GATTS_READ_EVT, BTA_GATTS_WRITE_EVT, + BTA_GATTS_EXEC_WRITE_EVT, BTA_GATTS_MTU_EVT, + BTA_GATTS_CONF_EVT (handle/value/data_len + are carried here, not in `confirm`) */ tBTA_GATTS_CONN conn; /* BTA_GATTS_CONN_EVT */ tBTA_GATTS_CONGEST congest; /* BTA_GATTS_CONGEST_EVT callback data */ - tBTA_GATTS_CONF confirm; /* BTA_GATTS_CONF_EVT callback data */ + tBTA_GATTS_CONF confirm; /* Deprecated: retained for source/ABI compatibility + only. BTA_GATTS_CONF_EVT actually uses `req_data` + because handle/value/data_len are required by the + public API. Do NOT add new producers/consumers + that read or write this member. */ tBTA_GATTS_CLOSE close; /* BTA_GATTS_CLOSE_EVT callback data */ tBTA_GATTS_OPEN open; /* BTA_GATTS_OPEN_EVT callback data */ tBTA_GATTS_CANCEL_OPEN cancel_open; /* tBTA_GATTS_CANCEL_OPEN callback data */ diff --git a/components/bt/host/bluedroid/bta/pba/bta_pba_client_act.c b/components/bt/host/bluedroid/bta/pba/bta_pba_client_act.c index 7786a610e47..af220475342 100644 --- a/components/bt/host/bluedroid/bta/pba/bta_pba_client_act.c +++ b/components/bt/host/bluedroid/bta/pba/bta_pba_client_act.c @@ -463,7 +463,9 @@ void bta_pba_client_response(tBTA_PBA_CLIENT_CCB *p_ccb, tBTA_PBA_CLIENT_DATA *p tOBEX_PARSE_INFO info; tBTA_PBA_CLIENT_ERR reason = BTA_PBA_CLIENT_GOEP_ERROR; - OBEX_ParseResponse(p_data->goep_response.pkt, p_data->goep_response.opcode, &info); + if (OBEX_ParseResponse(p_data->goep_response.pkt, p_data->goep_response.opcode, &info) != OBEX_SUCCESS) { + goto error; + } if (p_data->goep_response.opcode == OBEX_OPCODE_GET_FINAL && (info.response_code == OBEX_RESPONSE_CODE_CONTINUE || info.response_code == (OBEX_RESPONSE_CODE_CONTINUE | OBEX_FINAL_BIT_MASK))) { UINT8 *header = NULL; @@ -471,13 +473,15 @@ void bta_pba_client_response(tBTA_PBA_CLIENT_CCB *p_ccb, tBTA_PBA_CLIENT_DATA *p UINT16 body_data_len = 0; UINT8 *app_param = NULL; UINT16 app_param_len = 0; + UINT8 *pkt_data = (UINT8 *)(p_data->goep_response.pkt + 1) + p_data->goep_response.pkt->offset; + UINT8 *pkt_end = pkt_data + p_data->goep_response.pkt->len; while((header = OBEX_GetNextHeader(p_data->goep_response.pkt, &info)) != NULL) { switch (*header) { case OBEX_HEADER_ID_BODY: case OBEX_HEADER_ID_END_OF_BODY: { - UINT16 hi_len = OBEX_GetHeaderLength(header); + UINT16 hi_len = OBEX_GetHeaderLength(header, pkt_end); if (hi_len < 3) { reason = BTA_PBA_CLIENT_BAD_REQUEST; goto error; @@ -497,7 +501,7 @@ void bta_pba_client_response(tBTA_PBA_CLIENT_CCB *p_ccb, tBTA_PBA_CLIENT_DATA *p } case OBEX_HEADER_ID_APP_PARAM: { - UINT16 hi_len = OBEX_GetHeaderLength(header); + UINT16 hi_len = OBEX_GetHeaderLength(header, pkt_end); if (hi_len < 3) { reason = BTA_PBA_CLIENT_BAD_REQUEST; goto error; @@ -544,7 +548,9 @@ void bta_pba_client_response_final(tBTA_PBA_CLIENT_CCB *p_ccb, tBTA_PBA_CLIENT_D UINT8 *header = NULL; tBTA_PBA_CLIENT_ERR reason = BTA_PBA_CLIENT_FAIL; - OBEX_ParseResponse(p_data->goep_response.pkt, p_data->goep_response.opcode, &info); + if (OBEX_ParseResponse(p_data->goep_response.pkt, p_data->goep_response.opcode, &info) != OBEX_SUCCESS) { + goto error; + } if (p_data->goep_response.opcode == OBEX_OPCODE_CONNECT) { if (info.response_code == (OBEX_RESPONSE_CODE_OK | OBEX_FINAL_BIT_MASK)) { /* obex connect success */ @@ -555,8 +561,13 @@ void bta_pba_client_response_final(tBTA_PBA_CLIENT_CCB *p_ccb, tBTA_PBA_CLIENT_D p_ccb->max_tx = info.max_packet_length; } BOOLEAN cid_found = false; + UINT8 *pkt_data = (UINT8 *)(p_data->goep_response.pkt + 1) + p_data->goep_response.pkt->offset; + UINT8 *pkt_end = pkt_data + p_data->goep_response.pkt->len; while((header = OBEX_GetNextHeader(p_data->goep_response.pkt, &info)) != NULL) { if (*header == OBEX_HEADER_ID_CONNECTION_ID) { + if (OBEX_GetHeaderLength(header, pkt_end) != 5) { + goto error; + } cid_found = true; memcpy((UINT8 *)(&p_ccb->goep_cid), header + 1, 4); break; @@ -600,6 +611,8 @@ void bta_pba_client_response_final(tBTA_PBA_CLIENT_CCB *p_ccb, tBTA_PBA_CLIENT_D UINT16 body_data_len = 0; UINT8 *app_param = NULL; UINT16 app_param_len = 0; + UINT8 *pkt_data = (UINT8 *)(p_data->goep_response.pkt + 1) + p_data->goep_response.pkt->offset; + UINT8 *pkt_end = pkt_data + p_data->goep_response.pkt->len; while((header = OBEX_GetNextHeader(p_data->goep_response.pkt, &info)) != NULL) { switch (*header) { @@ -607,7 +620,7 @@ void bta_pba_client_response_final(tBTA_PBA_CLIENT_CCB *p_ccb, tBTA_PBA_CLIENT_D case OBEX_HEADER_ID_BODY: case OBEX_HEADER_ID_END_OF_BODY: { - UINT16 hi_len = OBEX_GetHeaderLength(header); + UINT16 hi_len = OBEX_GetHeaderLength(header, pkt_end); if (hi_len < 3) { reason = BTA_PBA_CLIENT_BAD_REQUEST; goto error; @@ -627,7 +640,7 @@ void bta_pba_client_response_final(tBTA_PBA_CLIENT_CCB *p_ccb, tBTA_PBA_CLIENT_D } case OBEX_HEADER_ID_APP_PARAM: { - UINT16 hi_len = OBEX_GetHeaderLength(header); + UINT16 hi_len = OBEX_GetHeaderLength(header, pkt_end); if (hi_len < 3) { reason = BTA_PBA_CLIENT_BAD_REQUEST; goto error; diff --git a/components/bt/host/bluedroid/bta/sys/bta_sys_main.c b/components/bt/host/bluedroid/bta/sys/bta_sys_main.c index 3bf41d9dabc..1dc09dabcf5 100644 --- a/components/bt/host/bluedroid/bta/sys/bta_sys_main.c +++ b/components/bt/host/bluedroid/bta/sys/bta_sys_main.c @@ -636,11 +636,11 @@ void bta_sys_start_timer(TIMER_LIST_ENT *p_tle, UINT16 type, INT32 timeout_ms) return; } } - osi_mutex_unlock(&bta_alarm_lock); alarm = hash_map_get(bta_alarm_hash_map, p_tle); if (alarm == NULL) { APPL_TRACE_ERROR("%s unable to create alarm.", __func__); + osi_mutex_unlock(&bta_alarm_lock); return; } @@ -648,6 +648,7 @@ void bta_sys_start_timer(TIMER_LIST_ENT *p_tle, UINT16 type, INT32 timeout_ms) p_tle->ticks = timeout_ms; //osi_alarm_set(alarm, (period_ms_t)timeout_ms, bta_alarm_cb, p_tle); osi_alarm_set(alarm, (period_ms_t)timeout_ms); + osi_mutex_unlock(&bta_alarm_lock); } bool hash_iter_ro_cb(hash_map_entry_t *hash_map_entry, void *context) @@ -682,12 +683,12 @@ BOOLEAN bta_sys_timer_is_active(TIMER_LIST_ENT *p_tle) { assert(p_tle != NULL); + osi_mutex_lock(&bta_alarm_lock, OSI_MUTEX_MAX_TIMEOUT); osi_alarm_t *alarm = hash_map_get(bta_alarm_hash_map, p_tle); - if (alarm != NULL && osi_alarm_is_active(alarm)) { - return TRUE; - } + BOOLEAN active = (alarm != NULL && osi_alarm_is_active(alarm)); + osi_mutex_unlock(&bta_alarm_lock); - return FALSE; + return active; } /******************************************************************************* @@ -703,12 +704,15 @@ void bta_sys_stop_timer(TIMER_LIST_ENT *p_tle) { assert(p_tle != NULL); + osi_mutex_lock(&bta_alarm_lock, OSI_MUTEX_MAX_TIMEOUT); osi_alarm_t *alarm = hash_map_get(bta_alarm_hash_map, p_tle); if (alarm == NULL) { APPL_TRACE_DEBUG("%s expected alarm was not in bta alarm hash map.", __func__); + osi_mutex_unlock(&bta_alarm_lock); return; } osi_alarm_cancel(alarm); + osi_mutex_unlock(&bta_alarm_lock); } /******************************************************************************* @@ -724,13 +728,16 @@ void bta_sys_free_timer(TIMER_LIST_ENT *p_tle) { assert(p_tle != NULL); + osi_mutex_lock(&bta_alarm_lock, OSI_MUTEX_MAX_TIMEOUT); osi_alarm_t *alarm = hash_map_get(bta_alarm_hash_map, p_tle); if (alarm == NULL) { APPL_TRACE_DEBUG("%s expected alarm was not in bta alarm hash map.", __func__); + osi_mutex_unlock(&bta_alarm_lock); return; } osi_alarm_cancel(alarm); hash_map_erase(bta_alarm_hash_map, p_tle); + osi_mutex_unlock(&bta_alarm_lock); } /******************************************************************************* 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 ac810a94bc3..a833a5ac91a 100644 --- a/components/bt/host/bluedroid/btc/core/btc_ble_storage.c +++ b/components/bt/host/bluedroid/btc/core/btc_ble_storage.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2024 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -90,6 +90,17 @@ void btc_storage_save(void) } #if (BLE_INCLUDED == TRUE) +static bool btc_storage_is_all_zeros(const void *buf, size_t len) +{ + const uint8_t *p = (const uint8_t *)buf; + for (size_t i = 0; i < len; i++) { + if (p[i] != 0) { + return false; + } + } + return true; +} + static bt_status_t _btc_storage_add_ble_bonding_key(bt_bdaddr_t *remote_bd_addr, char *key, uint8_t key_type, @@ -158,33 +169,48 @@ static bt_status_t _btc_storage_get_ble_bonding_key(bt_bdaddr_t *remote_bd_addr, { bdstr_t bdstr; bdaddr_to_string(remote_bd_addr, bdstr, sizeof(bdstr)); - const char* name; + const char *name; + size_t expected_len; switch (key_type) { case BTM_LE_KEY_PENC: name = BTC_BLE_STORAGE_LE_KEY_PENC_STR; + expected_len = sizeof(tBTM_LE_PENC_KEYS); break; case BTM_LE_KEY_PID: name = BTC_BLE_STORAGE_LE_KEY_PID_STR; + expected_len = sizeof(tBTM_LE_PID_KEYS); break; case BTM_LE_KEY_PCSRK: name = BTC_BLE_STORAGE_LE_KEY_PCSRK_STR; + expected_len = sizeof(tBTM_LE_PCSRK_KEYS); break; case BTM_LE_KEY_LENC: name = BTC_BLE_STORAGE_LE_KEY_LENC_STR; + expected_len = sizeof(tBTM_LE_LENC_KEYS); break; case BTM_LE_KEY_LCSRK: name = BTC_BLE_STORAGE_LE_KEY_LCSRK_STR; + expected_len = sizeof(tBTM_LE_LCSRK_KEYS); break; case BTM_LE_KEY_LID: - name = BTC_BLE_STORAGE_LE_KEY_LID_STR; + /* LID is a flag-only key; no payload is persisted. */ + name = BTC_BLE_STORAGE_LE_KEY_LID_STR; + expected_len = 0; break; default: return BT_STATUS_FAIL; } - size_t length = key_length; - int ret = btc_config_get_bin(bdstr, name, (uint8_t *)key_value, &length); - return ret ? BT_STATUS_SUCCESS : BT_STATUS_FAIL; + if (key_length < 0 || (size_t)key_length < expected_len) { + return BT_STATUS_FAIL; + } + + size_t length = (size_t)key_length; + bool ret = btc_config_get_bin(bdstr, name, (uint8_t *)key_value, &length); + if (!ret || length != expected_len) { + return BT_STATUS_FAIL; + } + return BT_STATUS_SUCCESS; } bt_status_t btc_storage_get_ble_bonding_key(bt_bdaddr_t *remote_bd_addr, @@ -237,17 +263,22 @@ static bt_status_t _btc_storage_remove_all_ble_keys(const char *name) void btc_storage_remove_unused_sections(uint8_t *cur_addr, tBTM_LE_PID_KEYS *del_pid_key) { bt_bdaddr_t bd_addr; - uint32_t device_type = 0; if (del_pid_key == NULL) { return; } + // Only use valid static address for de-duplication. + if (btc_storage_is_all_zeros(del_pid_key->static_addr, sizeof(del_pid_key->static_addr))) { + return; + } + btc_config_lock(); const btc_config_section_iter_t *iter = btc_config_section_begin(); while (iter != btc_config_section_end()) { + uint32_t device_type = 0; //store the next iter, if remove section, then will not loss the point const char *section = btc_config_section_name(iter); @@ -276,13 +307,19 @@ void btc_storage_remove_unused_sections(uint8_t *cur_addr, tBTM_LE_PID_KEYS *del char buffer[sizeof(tBTM_LE_KEY_VALUE)] = {0}; - if (_btc_storage_get_ble_bonding_key(&bd_addr, BTM_LE_KEY_PID, buffer, sizeof(tBTM_LE_PID_KEYS)) == BT_STATUS_SUCCESS) { + size_t pid_len = sizeof(tBTM_LE_PID_KEYS); + bool pid_ok = btc_config_get_bin(section, BTC_BLE_STORAGE_LE_KEY_PID_STR, (uint8_t *)buffer, &pid_len); + + if (pid_ok && pid_len >= sizeof(tBTM_LE_PID_KEYS)) { tBTM_LE_PID_KEYS *pid_key = (tBTM_LE_PID_KEYS *) buffer; iter = btc_config_section_next(iter); - if (memcmp(del_pid_key->static_addr, pid_key->static_addr, 6) == 0 && memcmp(cur_addr, bd_addr.address, 6) != 0 && del_pid_key->addr_type == pid_key->addr_type) { + if (del_pid_key->addr_type == pid_key->addr_type && + !btc_storage_is_all_zeros(pid_key->static_addr, sizeof(pid_key->static_addr)) && + memcmp(del_pid_key->static_addr, pid_key->static_addr, sizeof(pid_key->static_addr)) == 0 && + memcmp(cur_addr, bd_addr.address, sizeof(bd_addr.address)) != 0) { if (device_type == BT_DEVICE_TYPE_DUMO) { btc_config_set_int(section, BTC_BLE_STORAGE_DEV_TYPE_STR, BT_DEVICE_TYPE_BREDR); _btc_storage_remove_all_ble_keys(section); @@ -308,13 +345,13 @@ void btc_storage_delete_duplicate_ble_devices(void) char temp_buffer[sizeof(tBTM_LE_KEY_VALUE)] = {0}; tBTM_LE_PID_KEYS *pid_key; tBTM_LE_PID_KEYS *temp_pid_key; - uint32_t device_type = 0; bt_bdaddr_t temp_bd_addr; btc_config_lock(); for (const btc_config_section_iter_t *iter = btc_config_section_begin(); iter != btc_config_section_end(); iter = btc_config_section_next(iter)) { + uint32_t device_type = 0; const char *name = btc_config_section_name(iter); if (!string_is_bdaddr(name) || @@ -324,27 +361,36 @@ void btc_storage_delete_duplicate_ble_devices(void) } string_to_bdaddr(name, &bd_addr); - if (_btc_storage_get_ble_bonding_key(&bd_addr, BTM_LE_KEY_PID, buffer, sizeof(tBTM_LE_PID_KEYS)) == BT_STATUS_SUCCESS) + size_t pid_len = sizeof(tBTM_LE_PID_KEYS); + bool pid_ok = btc_config_get_bin(name, BTC_BLE_STORAGE_LE_KEY_PID_STR, (uint8_t *)buffer, &pid_len); + if (pid_ok && pid_len >= sizeof(tBTM_LE_PID_KEYS)) { pid_key = (tBTM_LE_PID_KEYS *) buffer; + if (btc_storage_is_all_zeros(pid_key->static_addr, sizeof(pid_key->static_addr))) { + continue; + } const btc_config_section_iter_t *temp_iter = btc_config_section_next(iter); while (temp_iter != NULL) { + uint32_t temp_device_type = 0; const char *temp_name = btc_config_section_name(temp_iter); - if (!string_is_bdaddr(temp_name) || !btc_config_get_int(temp_name, BTC_BLE_STORAGE_DEV_TYPE_STR, (int *)&device_type) || - ((device_type & BT_DEVICE_TYPE_BLE) != BT_DEVICE_TYPE_BLE)) { + if (!string_is_bdaddr(temp_name) || !btc_config_get_int(temp_name, BTC_BLE_STORAGE_DEV_TYPE_STR, (int *)&temp_device_type) || + ((temp_device_type & BT_DEVICE_TYPE_BLE) != BT_DEVICE_TYPE_BLE)) { temp_iter = btc_config_section_next(temp_iter); continue; } string_to_bdaddr(temp_name, &temp_bd_addr); - if (_btc_storage_get_ble_bonding_key(&temp_bd_addr, BTM_LE_KEY_PID, temp_buffer, sizeof(tBTM_LE_PID_KEYS)) == BT_STATUS_SUCCESS) + size_t temp_pid_len = sizeof(tBTM_LE_PID_KEYS); + bool temp_pid_ok = btc_config_get_bin(temp_name, BTC_BLE_STORAGE_LE_KEY_PID_STR, (uint8_t *)temp_buffer, &temp_pid_len); + if (temp_pid_ok && temp_pid_len >= sizeof(tBTM_LE_PID_KEYS)) { temp_pid_key = (tBTM_LE_PID_KEYS *) temp_buffer; - if (memcmp(pid_key->static_addr, temp_pid_key->static_addr, 6) == 0 && pid_key->addr_type == temp_pid_key->addr_type) { - const char *temp_name = btc_config_section_name(temp_iter); + if (pid_key->addr_type == temp_pid_key->addr_type && + !btc_storage_is_all_zeros(temp_pid_key->static_addr, sizeof(temp_pid_key->static_addr)) && + memcmp(pid_key->static_addr, temp_pid_key->static_addr, sizeof(pid_key->static_addr)) == 0) { temp_iter = btc_config_section_next(temp_iter); - if (device_type == BT_DEVICE_TYPE_DUMO) { + if (temp_device_type == BT_DEVICE_TYPE_DUMO) { btc_config_set_int(temp_name, BTC_BLE_STORAGE_DEV_TYPE_STR, BT_DEVICE_TYPE_BREDR); _btc_storage_remove_all_ble_keys(temp_name); } else { @@ -484,9 +530,11 @@ static bt_status_t _btc_storage_get_ble_local_key(uint8_t key_type, } size_t length = key_length; - int ret = btc_config_get_bin(BTC_BLE_STORAGE_LOCAL_ADAPTER_STR, name, (uint8_t *)key_value, &length); - - return ret ? BT_STATUS_SUCCESS : BT_STATUS_FAIL; + bool ret = btc_config_get_bin(BTC_BLE_STORAGE_LOCAL_ADAPTER_STR, name, (uint8_t *)key_value, &length); + if (!ret || length != (size_t)key_length) { + return BT_STATUS_FAIL; + } + return BT_STATUS_SUCCESS; } bt_status_t btc_storage_get_ble_local_key(uint8_t key_type, @@ -905,8 +953,8 @@ static void _btc_read_le_key(const uint8_t key_type, const size_t key_len, bt_bd } bt_status_t _btc_storage_in_fetch_bonded_ble_device(const char *remote_bd_addr, int add) { - uint32_t device_type; - int addr_type; + uint32_t device_type = 0; + int addr_type = BLE_ADDR_PUBLIC; bt_bdaddr_t bd_addr; BD_ADDR bta_bd_addr; bool device_added = false; @@ -1015,11 +1063,11 @@ bt_status_t btc_storage_get_bonded_ble_devices_list(esp_ble_bond_dev_t *bond_dev int btc_storage_get_num_ble_bond_devices(void) { int num_dev = 0; - uint32_t device_type = 0; btc_config_lock(); for (const btc_config_section_iter_t *iter = btc_config_section_begin(); iter != btc_config_section_end(); iter = btc_config_section_next(iter)) { + uint32_t device_type = 0; const char *name = btc_config_section_name(iter); if (!string_is_bdaddr(name) || !btc_config_get_int(name, BTC_BLE_STORAGE_DEV_TYPE_STR, (int *)&device_type) || @@ -1037,18 +1085,30 @@ int btc_storage_get_num_ble_bond_devices(void) bt_status_t btc_storage_get_gatt_cl_supp_feat(bt_bdaddr_t *remote_bd_addr, uint8_t *value, int len) { bdstr_t bdstr; - bdaddr_to_string(remote_bd_addr, bdstr, sizeof(bdstr)); - int ret = btc_config_get_bin(bdstr, BTC_BLE_STORAGE_GATT_CL_SUPP_FEAT_STR, value, (size_t *)&len); - return ret ? BT_STATUS_SUCCESS : BT_STATUS_FAIL; + bool ret; + size_t length = len; + + btc_config_lock(); + bdaddr_to_string(remote_bd_addr, bdstr, sizeof(bdstr_t)); + ret = btc_config_get_bin(bdstr, BTC_BLE_STORAGE_GATT_CL_SUPP_FEAT_STR, value, &length); + btc_config_unlock(); + + if (!ret || length != (size_t)len) { + return BT_STATUS_FAIL; + } + return BT_STATUS_SUCCESS; } bt_status_t btc_storage_set_gatt_cl_supp_feat(bt_bdaddr_t *remote_bd_addr, uint8_t *value, int len) { - int ret; + bool ret; bdstr_t bdstr; btc_config_lock(); bdaddr_to_string(remote_bd_addr, bdstr, sizeof(bdstr_t)); ret = btc_config_set_bin(bdstr, BTC_BLE_STORAGE_GATT_CL_SUPP_FEAT_STR, value, (size_t)len); + if (ret) { + _btc_storage_save(); + } btc_config_unlock(); if (ret == false) { return BT_STATUS_FAIL; @@ -1060,18 +1120,33 @@ bt_status_t btc_storage_set_gatt_cl_supp_feat(bt_bdaddr_t *remote_bd_addr, uint8 bt_status_t btc_storage_get_gatt_db_hash(bt_bdaddr_t *remote_bd_addr, uint8_t *value, int len) { bdstr_t bdstr; - bdaddr_to_string(remote_bd_addr, bdstr, sizeof(bdstr)); - int ret = btc_config_get_bin(bdstr, BTC_BLE_STORAGE_GATT_DB_HASH_STR, value, (size_t *)&len); - return ret ? BT_STATUS_SUCCESS : BT_STATUS_FAIL; + bool ret; + size_t length = len; + + btc_config_lock(); + bdaddr_to_string(remote_bd_addr, bdstr, sizeof(bdstr_t)); + ret = btc_config_get_bin(bdstr, BTC_BLE_STORAGE_GATT_DB_HASH_STR, value, &length); + btc_config_unlock(); + + if (!ret || length != (size_t)len) { + return BT_STATUS_FAIL; + } + return BT_STATUS_SUCCESS; } bt_status_t btc_storage_set_gatt_db_hash(bt_bdaddr_t *remote_bd_addr, uint8_t *value, int len) { - int ret; + bool ret; bdstr_t bdstr; + btc_config_lock(); bdaddr_to_string(remote_bd_addr, bdstr, sizeof(bdstr_t)); ret = btc_config_set_bin(bdstr, BTC_BLE_STORAGE_GATT_DB_HASH_STR, value, (size_t)len); + if (ret) { + _btc_storage_save(); + } + btc_config_unlock(); + if (ret == false) { return BT_STATUS_FAIL; } @@ -1084,9 +1159,11 @@ bt_status_t btc_storage_remove_gatt_cl_supp_feat(bt_bdaddr_t *remote_bd_addr) bool ret = true; bdstr_t bdstr; + btc_config_lock(); bdaddr_to_string(remote_bd_addr, bdstr, sizeof(bdstr)); - ret = btc_config_remove(bdstr, BTC_BLE_STORAGE_GATT_CL_SUPP_FEAT_STR); + btc_config_unlock(); + if (ret == false) { return BT_STATUS_FAIL; } @@ -1099,9 +1176,11 @@ bt_status_t btc_storage_remove_gatt_db_hash(bt_bdaddr_t *remote_bd_addr) bool ret = true; bdstr_t bdstr; + btc_config_lock(); bdaddr_to_string(remote_bd_addr, bdstr, sizeof(bdstr)); - ret = btc_config_remove(bdstr, BTC_BLE_STORAGE_GATT_DB_HASH_STR); + btc_config_unlock(); + if (ret == false) { return BT_STATUS_FAIL; } diff --git a/components/bt/host/bluedroid/btc/core/btc_config.c b/components/bt/host/bluedroid/btc/core/btc_config.c index c55b80023d5..d78615a7dfd 100644 --- a/components/bt/host/bluedroid/btc/core/btc_config.c +++ b/components/bt/host/bluedroid/btc/core/btc_config.c @@ -277,6 +277,7 @@ bool btc_config_set_bin(const char *section, const char *key, const uint8_t *val config_set_string(config, section, key, str, false); + memset(str, 0, length * 2 + 1); osi_free(str); return true; } diff --git a/components/bt/host/bluedroid/btc/core/btc_dm.c b/components/bt/host/bluedroid/btc/core/btc_dm.c index 5f91481b6fb..416c9f4c2c3 100644 --- a/components/bt/host/bluedroid/btc/core/btc_dm.c +++ b/components/bt/host/bluedroid/btc/core/btc_dm.c @@ -269,7 +269,7 @@ static void btc_dm_ble_auth_cmpl_evt (tBTA_DM_AUTH_CMPL *p_auth_cmpl) } #if BLE_SMP_BOND_NVS_FLASH - int addr_type; + int addr_type = BLE_ADDR_PUBLIC; if (btc_dm_cb.pairing_cb.ble.is_pid_key_rcvd) { // delete unused section in NVS diff --git a/components/bt/host/bluedroid/btc/core/btc_main.c b/components/bt/host/bluedroid/btc/core/btc_main.c index bbcb17b670b..e132391e23b 100644 --- a/components/bt/host/bluedroid/btc/core/btc_main.c +++ b/components/bt/host/bluedroid/btc/core/btc_main.c @@ -18,6 +18,8 @@ #include "bta_dm_int.h" static future_t *main_future[BTC_MAIN_FUTURE_NUM]; +static SemaphoreHandle_t s_init_done_sem = NULL; +static bool s_init_clean = false; extern int bte_main_boot_entry(void *cb); extern int bte_main_shutdown(void); @@ -44,18 +46,46 @@ static void btc_disable_bluetooth(void) } } -void btc_init_callback(void) +void btc_init_callback(bt_status_t status) { - future_ready(*btc_main_get_future_p(BTC_MAIN_INIT_FUTURE), FUTURE_SUCCESS); + s_init_clean = (status == BT_STATUS_SUCCESS) ? false : true; + future_ready(*btc_main_get_future_p(BTC_MAIN_INIT_FUTURE), + (status == BT_STATUS_SUCCESS) ? FUTURE_SUCCESS : FUTURE_FAIL); +} + +void btc_cleanup_partial_init(void) +{ + if (s_init_clean) { + xSemaphoreTake(s_init_done_sem, portMAX_DELAY); + bte_main_shutdown(); +#if (SMP_INCLUDED) + btc_config_clean_up(); +#endif + osi_alarm_deinit(); + osi_alarm_delete_mux(); +#if BTA_DYNAMIC_MEMORY + vSemaphoreDelete(deinit_semaphore); + deinit_semaphore = NULL; +#endif /* #if BTA_DYNAMIC_MEMORY */ + vSemaphoreDelete(s_init_done_sem); + s_init_done_sem = NULL; + s_init_clean = false; + } else { + if (s_init_done_sem) { + vSemaphoreDelete(s_init_done_sem); + s_init_done_sem = NULL; + } + osi_alarm_deinit(); + osi_alarm_delete_mux(); + } } static void btc_init_bluetooth(void) { osi_alarm_create_mux(); osi_alarm_init(); - if (bte_main_boot_entry(btc_init_callback) != 0) { - osi_alarm_deinit(); - osi_alarm_delete_mux(); + s_init_done_sem = xSemaphoreCreateBinary(); + if ((s_init_done_sem == NULL) || (bte_main_boot_entry(btc_init_callback) != 0)) { future_ready(*btc_main_get_future_p(BTC_MAIN_INIT_FUTURE), FUTURE_FAIL); return; } @@ -71,6 +101,7 @@ static void btc_init_bluetooth(void) #if BTA_DYNAMIC_MEMORY deinit_semaphore = xSemaphoreCreateBinary(); #endif /* #if BTA_DYNAMIC_MEMORY */ + xSemaphoreGive(s_init_done_sem); } @@ -98,6 +129,10 @@ static void btc_deinit_bluetooth(void) vSemaphoreDelete(deinit_semaphore); deinit_semaphore = NULL; #endif /* #if BTA_DYNAMIC_MEMORY */ + if (s_init_done_sem) { + vSemaphoreDelete(s_init_done_sem); + s_init_done_sem = NULL; + } } void btc_main_call_handler(btc_msg_t *msg) @@ -183,7 +218,6 @@ uint32_t btc_get_ble_status(void) } #endif // #if ((SMP_INCLUDED == TRUE) || (BLE_PRIVACY_SPT == TRUE)) -#if (SMP_INCLUDED == TRUE) // Number of recorded devices extern uint8_t btm_ble_sec_dev_record_count(void); uint8_t sec_dev_cnt = btm_ble_sec_dev_record_count(); @@ -191,14 +225,14 @@ uint32_t btc_get_ble_status(void) BTC_TRACE_WARNING("%s security device record count %d", __func__, sec_dev_cnt); status |= BIT(BTC_BLE_STATUS_DEVICE_REC); } - +#if SMP_INCLUDED == TRUE // Number of saved bonded devices int bond_cnt = btc_storage_get_num_ble_bond_devices(); if (bond_cnt) { BTC_TRACE_WARNING("%s bonded devices count %d", __func__, bond_cnt); status |= BIT(BTC_BLE_STATUS_BOND); } -#endif // SMP_INCLUDED +#endif // SMP_INCLUDED == TRUE #if (BLE_PRIVACY_SPT == TRUE) // Privacy enabled diff --git a/components/bt/host/bluedroid/btc/core/btc_profile_queue.c b/components/bt/host/bluedroid/btc/core/btc_profile_queue.c index 5e63dfe54c4..231948cff8f 100644 --- a/components/bt/host/bluedroid/btc/core/btc_profile_queue.c +++ b/components/bt/host/bluedroid/btc/core/btc_profile_queue.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -144,7 +144,11 @@ bt_status_t btc_queue_connect_next(void) } p_head->busy = true; - return p_head->connect_cb(&p_head->bda, p_head->uuid); + bt_status_t status = p_head->connect_cb(&p_head->bda, p_head->uuid); + if (status != BT_STATUS_SUCCESS) { + btc_queue_advance(); + } + return status; } diff --git a/components/bt/host/bluedroid/btc/include/btc/btc_main.h b/components/bt/host/bluedroid/btc/include/btc/btc_main.h index 83f87b56a3b..474fa00bc6e 100644 --- a/components/bt/host/bluedroid/btc/include/btc/btc_main.h +++ b/components/bt/host/bluedroid/btc/include/btc/btc_main.h @@ -69,4 +69,5 @@ void btc_deinit_bluetooth(future_t *future); #endif void btc_main_call_handler(btc_msg_t *msg); +void btc_cleanup_partial_init(void); #endif /* __BTC_BT_MAIN_H__ */ diff --git a/components/bt/host/bluedroid/btc/profile/std/a2dp/btc_a2dp_sink.c b/components/bt/host/bluedroid/btc/profile/std/a2dp/btc_a2dp_sink.c index 46b343a542e..2d5b009f12e 100644 --- a/components/bt/host/bluedroid/btc/profile/std/a2dp/btc_a2dp_sink.c +++ b/components/bt/host/bluedroid/btc/profile/std/a2dp/btc_a2dp_sink.c @@ -230,6 +230,11 @@ error_exit:; void btc_a2dp_sink_shutdown(void) { + if (btc_a2dp_sink_state != BTC_A2DP_SINK_STATE_ON) { + APPL_TRACE_ERROR("a2dp sink already shutdown"); + return; + } + APPL_TRACE_EVENT("## A2DP SINK STOP MEDIA THREAD ##\n"); // Exit thread diff --git a/components/bt/host/bluedroid/btc/profile/std/a2dp/btc_a2dp_source.c b/components/bt/host/bluedroid/btc/profile/std/a2dp/btc_a2dp_source.c index a87bd28d82a..8313a65f2ab 100644 --- a/components/bt/host/bluedroid/btc/profile/std/a2dp/btc_a2dp_source.c +++ b/components/bt/host/bluedroid/btc/profile/std/a2dp/btc_a2dp_source.c @@ -406,6 +406,11 @@ static void log_tstamps_us(char *comment) { static UINT64 prev_us = 0; UINT64 now_us = time_now_us(); +#if A2D_DYNAMIC_MEMORY == TRUE + if (a2dp_source_local_param_ptr == NULL) { + return; + } +#endif APPL_TRACE_DEBUG("[%s] ts %08llu, diff : %08llu, queue sz %d", comment, now_us, now_us - prev_us, fixed_queue_length(a2dp_source_local_param.btc_aa_src_cb.TxAaQ)); prev_us = now_us; @@ -1525,6 +1530,11 @@ static void btc_a2dp_source_handle_timer(UNUSED_ATTR void *context) if (btc_a2dp_source_state != BTC_A2DP_SOURCE_STATE_ON || g_a2dp_source_ongoing_deinit){ return; } +#if A2D_DYNAMIC_MEMORY == TRUE + if (a2dp_source_local_param_ptr == NULL) { + return; + } +#endif if (a2dp_source_local_param.btc_aa_src_cb.is_tx_timer == TRUE) { btc_a2dp_source_send_aa_frame(); diff --git a/components/bt/host/bluedroid/btc/profile/std/a2dp/btc_av.c b/components/bt/host/bluedroid/btc/profile/std/a2dp/btc_av.c index ed7c81e7489..1b3f77dde51 100644 --- a/components/bt/host/bluedroid/btc/profile/std/a2dp/btc_av.c +++ b/components/bt/host/bluedroid/btc/profile/std/a2dp/btc_av.c @@ -40,7 +40,7 @@ bool g_av_with_rc; // global variable to indicate a2dp is initialized bool g_a2dp_on_init; // global variable to indicate a2dp is deinitialized -bool g_a2dp_on_deinit; +bool g_a2dp_on_deinit = true; // global variable to indicate a2dp source deinitialization is ongoing bool g_a2dp_source_ongoing_deinit; // global variable to indicate a2dp sink deinitialization is ongoing diff --git a/components/bt/host/bluedroid/btc/profile/std/avrc/btc_avrc.c b/components/bt/host/bluedroid/btc/profile/std/avrc/btc_avrc.c index df5f63e1146..0f5e229885e 100644 --- a/components/bt/host/bluedroid/btc/profile/std/avrc/btc_avrc.c +++ b/components/bt/host/bluedroid/btc/profile/std/avrc/btc_avrc.c @@ -487,11 +487,15 @@ static void handle_rc_connect (tBTA_AV_RC_OPEN *p_rc_open) if (btc_rc_cb.rc_connected) { BTC_TRACE_ERROR("Got RC OPEN in connected state, Connected RC: %d \ and Current RC: %d", btc_rc_cb.rc_handle, p_rc_open->rc_handle ); - if ((btc_rc_cb.rc_handle != p_rc_open->rc_handle) - && (bdcmp(btc_rc_cb.rc_addr, p_rc_open->peer_addr))) { - BTC_TRACE_DEBUG("Got RC connected for some other handle"); - BTA_AvCloseRc(p_rc_open->rc_handle); - return; + if (btc_rc_cb.rc_handle != p_rc_open->rc_handle) { + if (bdcmp(btc_rc_cb.rc_addr, p_rc_open->peer_addr) == 0) { + /* same device reconnected with new handle - close stale one */ + BTA_AvCloseRc(btc_rc_cb.rc_handle); + } else { + BTC_TRACE_DEBUG("Got RC connected for some other handle"); + BTA_AvCloseRc(p_rc_open->rc_handle); + return; + } } } memcpy(btc_rc_cb.rc_addr, p_rc_open->peer_addr, sizeof(BD_ADDR)); @@ -526,7 +530,11 @@ static void handle_rc_connect (tBTA_AV_RC_OPEN *p_rc_open) } else { BTC_TRACE_ERROR("%s Connect failed with error code: %d", __FUNCTION__, p_rc_open->status); - btc_rc_cb.rc_connected = FALSE; + /* only clear state if the failure belongs to the active connection */ + if (p_rc_open->rc_handle == btc_rc_cb.rc_handle || + bdcmp(btc_rc_cb.rc_addr, p_rc_open->peer_addr) == 0) { + btc_rc_cb.rc_connected = FALSE; + } } } @@ -541,9 +549,18 @@ static void handle_rc_connect (tBTA_AV_RC_OPEN *p_rc_open) static void handle_rc_disconnect (tBTA_AV_RC_CLOSE *p_rc_close) { BTC_TRACE_DEBUG("%s: rc_handle: %d", __FUNCTION__, p_rc_close->rc_handle); - if ((p_rc_close->rc_handle != btc_rc_cb.rc_handle) - && (bdcmp(btc_rc_cb.rc_addr, p_rc_close->peer_addr))) { - BTC_TRACE_ERROR("Got disconnect of unknown device"); + if (!btc_rc_cb.rc_connected) { + BTC_TRACE_WARNING("Got disconnect when not connected"); + return; + } + if (p_rc_close->rc_handle != btc_rc_cb.rc_handle) { + if (bdcmp(btc_rc_cb.rc_addr, p_rc_close->peer_addr) != 0) { + BTC_TRACE_ERROR("Got disconnect of unknown device"); + return; + } + /* ignore disconnect for superseded handle on same device */ + BTC_TRACE_DEBUG("Ignoring disconnect for stale rc_handle %d (current %d)", + p_rc_close->rc_handle, btc_rc_cb.rc_handle); return; } @@ -640,6 +657,12 @@ static void handle_rc_attributes_rsp (tAVRC_MSG_VENDOR *vendor_msg) ((uint32_t)vendor_msg->p_vendor_data[1 + attr_index] << 16) | ((uint32_t)vendor_msg->p_vendor_data[attr_index] << 24); + if (!AVRC_IS_VALID_MEDIA_ATTRIBUTE(attr_id)) { + BTC_TRACE_WARNING("invalid attr_id 0x%x, skip", attr_id); + attr_index += attr_length + 8; + continue; + } + //Convert to mask id param[i].meta_rsp.attr_id = (1 << (attr_id - 1)); @@ -1153,6 +1176,11 @@ void btc_rc_handler(tBTA_AV_EVT event, tBTA_AV *p_data) ***************************************************************************/ BOOLEAN btc_rc_get_connected_peer(BD_ADDR peer_addr) { +#if AVRC_DYNAMIC_MEMORY == TRUE + if (btc_rc_cb_ptr == NULL) { + return FALSE; + } +#endif if (btc_rc_cb.rc_connected == TRUE) { bdcpy(peer_addr, btc_rc_cb.rc_addr); return TRUE; @@ -1257,7 +1285,7 @@ static void btc_avrc_ct_deinit(void) static bt_status_t btc_avrc_ct_send_set_player_value_cmd(uint8_t tl, uint8_t attr_id, uint8_t value_id) { - tAVRC_STS status = BT_STATUS_UNSUPPORTED; + bt_status_t ret = BT_STATUS_UNSUPPORTED; #if (AVRC_METADATA_INCLUDED == TRUE) CHECK_ESP_RC_CONNECTED; @@ -1265,6 +1293,7 @@ static bt_status_t btc_avrc_ct_send_set_player_value_cmd(uint8_t tl, uint8_t att tAVRC_COMMAND avrc_cmd = {0}; BT_HDR *p_msg = NULL; tAVRC_APP_SETTING values = {0}; + tAVRC_STS cmd_status; values.attr_id = attr_id; values.attr_val = value_id; @@ -1276,13 +1305,15 @@ static bt_status_t btc_avrc_ct_send_set_player_value_cmd(uint8_t tl, uint8_t att avrc_cmd.set_app_val.pdu = AVRC_PDU_SET_PLAYER_APP_VALUE; if (btc_rc_cb.rc_features & BTA_AV_FEAT_METADATA) { - status = AVRC_BldCommand(&avrc_cmd, &p_msg); - if (status == AVRC_STS_NO_ERROR) { + cmd_status = AVRC_BldCommand(&avrc_cmd, &p_msg); + if (cmd_status == AVRC_STS_NO_ERROR) { BTA_AvMetaCmd(btc_rc_cb.rc_handle, tl, BTA_AV_CMD_CTRL, p_msg); - status = BT_STATUS_SUCCESS; + ret = BT_STATUS_SUCCESS; + } else { + ret = BT_STATUS_FAIL; } } else { - status = BT_STATUS_FAIL; + ret = BT_STATUS_FAIL; BTC_TRACE_DEBUG("%s: feature not supported", __FUNCTION__); } @@ -1290,18 +1321,19 @@ static bt_status_t btc_avrc_ct_send_set_player_value_cmd(uint8_t tl, uint8_t att BTC_TRACE_DEBUG("%s: feature not enabled", __FUNCTION__); #endif - return status; + return ret; } static bt_status_t btc_avrc_ct_send_get_rn_caps_cmd(uint8_t tl) { - tAVRC_STS status = BT_STATUS_UNSUPPORTED; + bt_status_t ret = BT_STATUS_UNSUPPORTED; #if (AVRC_METADATA_INCLUDED == TRUE) CHECK_ESP_RC_CONNECTED; tAVRC_COMMAND avrc_cmd = {0}; BT_HDR *p_msg = NULL; + tAVRC_STS cmd_status; avrc_cmd.get_caps.opcode = AVRC_OP_VENDOR; avrc_cmd.get_caps.status = AVRC_STS_NO_ERROR; @@ -1309,13 +1341,15 @@ static bt_status_t btc_avrc_ct_send_get_rn_caps_cmd(uint8_t tl) avrc_cmd.get_caps.capability_id = AVRC_CAP_EVENTS_SUPPORTED; if (btc_rc_cb.rc_features & BTA_AV_FEAT_METADATA) { - status = AVRC_BldCommand(&avrc_cmd, &p_msg); - if (status == AVRC_STS_NO_ERROR) { + cmd_status = AVRC_BldCommand(&avrc_cmd, &p_msg); + if (cmd_status == AVRC_STS_NO_ERROR) { BTA_AvMetaCmd(btc_rc_cb.rc_handle, tl, AVRC_CMD_STATUS, p_msg); - status = BT_STATUS_SUCCESS; + ret = BT_STATUS_SUCCESS; + } else { + ret = BT_STATUS_FAIL; } } else { - status = BT_STATUS_FAIL; + ret = BT_STATUS_FAIL; BTC_TRACE_DEBUG("%s: feature not supported", __FUNCTION__); } @@ -1323,18 +1357,19 @@ static bt_status_t btc_avrc_ct_send_get_rn_caps_cmd(uint8_t tl) BTC_TRACE_DEBUG("%s: feature not enabled", __FUNCTION__); #endif - return status; + return ret; } static bt_status_t btc_avrc_ct_send_register_notification_cmd(uint8_t tl, uint8_t event_id, uint32_t event_parameter) { - tAVRC_STS status = BT_STATUS_UNSUPPORTED; + bt_status_t ret = BT_STATUS_UNSUPPORTED; #if (AVRC_METADATA_INCLUDED == TRUE) CHECK_ESP_RC_CONNECTED; tAVRC_COMMAND avrc_cmd = {0}; BT_HDR *p_msg = NULL; + tAVRC_STS cmd_status; avrc_cmd.reg_notif.opcode = AVRC_OP_VENDOR; avrc_cmd.reg_notif.status = AVRC_STS_NO_ERROR; @@ -1343,13 +1378,15 @@ static bt_status_t btc_avrc_ct_send_register_notification_cmd(uint8_t tl, uint8_ avrc_cmd.reg_notif.pdu = AVRC_PDU_REGISTER_NOTIFICATION; if (btc_rc_cb.rc_features & BTA_AV_FEAT_METADATA) { - status = AVRC_BldCommand(&avrc_cmd, &p_msg); - if (status == AVRC_STS_NO_ERROR) { + cmd_status = AVRC_BldCommand(&avrc_cmd, &p_msg); + if (cmd_status == AVRC_STS_NO_ERROR) { BTA_AvMetaCmd(btc_rc_cb.rc_handle, tl, AVRC_CMD_NOTIF, p_msg); - status = BT_STATUS_SUCCESS; + ret = BT_STATUS_SUCCESS; + } else { + ret = BT_STATUS_FAIL; } } else { - status = BT_STATUS_FAIL; + ret = BT_STATUS_FAIL; BTC_TRACE_DEBUG("%s: feature not supported", __FUNCTION__); } @@ -1357,18 +1394,19 @@ static bt_status_t btc_avrc_ct_send_register_notification_cmd(uint8_t tl, uint8_ BTC_TRACE_DEBUG("%s: feature not enabled", __FUNCTION__); #endif - return status; + return ret; } static bt_status_t btc_avrc_ct_send_set_absolute_volume_cmd(uint8_t tl, uint8_t volume) { - tAVRC_STS status = BT_STATUS_UNSUPPORTED; + bt_status_t ret = BT_STATUS_UNSUPPORTED; #if (AVRC_METADATA_INCLUDED == TRUE) CHECK_ESP_RC_CONNECTED; tAVRC_COMMAND avrc_cmd = {0}; BT_HDR *p_msg = NULL; + tAVRC_STS cmd_status; avrc_cmd.volume.opcode = AVRC_OP_VENDOR; avrc_cmd.volume.status = AVRC_STS_NO_ERROR; @@ -1376,13 +1414,15 @@ static bt_status_t btc_avrc_ct_send_set_absolute_volume_cmd(uint8_t tl, uint8_t avrc_cmd.volume.pdu = AVRC_PDU_SET_ABSOLUTE_VOLUME; if (btc_rc_cb.rc_features & BTA_AV_FEAT_METADATA) { - status = AVRC_BldCommand(&avrc_cmd, &p_msg); - if (status == AVRC_STS_NO_ERROR) { + cmd_status = AVRC_BldCommand(&avrc_cmd, &p_msg); + if (cmd_status == AVRC_STS_NO_ERROR) { BTA_AvMetaCmd(btc_rc_cb.rc_handle, tl, AVRC_CMD_CTRL, p_msg); - status = BT_STATUS_SUCCESS; + ret = BT_STATUS_SUCCESS; + } else { + ret = BT_STATUS_FAIL; } } else { - status = BT_STATUS_FAIL; + ret = BT_STATUS_FAIL; BTC_TRACE_DEBUG("%s: feature not supported", __FUNCTION__); } @@ -1390,12 +1430,12 @@ static bt_status_t btc_avrc_ct_send_set_absolute_volume_cmd(uint8_t tl, uint8_t BTC_TRACE_DEBUG("%s: feature not enabled", __FUNCTION__); #endif - return status; + return ret; } static bt_status_t btc_avrc_ct_send_metadata_cmd (uint8_t tl, uint8_t attr_mask) { - tAVRC_STS status = BT_STATUS_UNSUPPORTED; + bt_status_t ret = BT_STATUS_UNSUPPORTED; #if (AVRC_METADATA_INCLUDED == TRUE) CHECK_ESP_RC_CONNECTED; @@ -1403,6 +1443,7 @@ static bt_status_t btc_avrc_ct_send_metadata_cmd (uint8_t tl, uint8_t attr_mask) tAVRC_COMMAND avrc_cmd = {0}; BT_HDR *p_msg = NULL; + tAVRC_STS cmd_status; avrc_cmd.get_elem_attrs.opcode = AVRC_OP_VENDOR; avrc_cmd.get_elem_attrs.status = AVRC_STS_NO_ERROR; @@ -1418,13 +1459,15 @@ static bt_status_t btc_avrc_ct_send_metadata_cmd (uint8_t tl, uint8_t attr_mask) avrc_cmd.get_elem_attrs.num_attr = index; if (btc_rc_cb.rc_features & BTA_AV_FEAT_METADATA) { - status = AVRC_BldCommand(&avrc_cmd, &p_msg); - if (status == AVRC_STS_NO_ERROR) { + cmd_status = AVRC_BldCommand(&avrc_cmd, &p_msg); + if (cmd_status == AVRC_STS_NO_ERROR) { BTA_AvMetaCmd(btc_rc_cb.rc_handle, tl, AVRC_CMD_STATUS, p_msg); - status = BT_STATUS_SUCCESS; + ret = BT_STATUS_SUCCESS; + } else { + ret = BT_STATUS_FAIL; } } else { - status = BT_STATUS_FAIL; + ret = BT_STATUS_FAIL; BTC_TRACE_DEBUG("%s: feature not supported", __FUNCTION__); } @@ -1432,31 +1475,34 @@ static bt_status_t btc_avrc_ct_send_metadata_cmd (uint8_t tl, uint8_t attr_mask) BTC_TRACE_DEBUG("%s: feature not enabled", __FUNCTION__); #endif - return status; + return ret; } static bt_status_t btc_avrc_ct_send_get_play_status_cmd(uint8_t tl) { - tAVRC_STS status = BT_STATUS_UNSUPPORTED; + bt_status_t ret = BT_STATUS_UNSUPPORTED; #if (AVRC_METADATA_INCLUDED == TRUE) CHECK_ESP_RC_CONNECTED; tAVRC_COMMAND avrc_cmd = {0}; BT_HDR *p_msg = NULL; + tAVRC_STS cmd_status; avrc_cmd.get_play_status.opcode = AVRC_OP_VENDOR; avrc_cmd.get_play_status.status = AVRC_STS_NO_ERROR; avrc_cmd.get_play_status.pdu = AVRC_PDU_GET_PLAY_STATUS; if (btc_rc_cb.rc_features & BTA_AV_FEAT_METADATA) { - status = AVRC_BldCommand(&avrc_cmd, &p_msg); - if (status == AVRC_STS_NO_ERROR) { + cmd_status = AVRC_BldCommand(&avrc_cmd, &p_msg); + if (cmd_status == AVRC_STS_NO_ERROR) { BTA_AvMetaCmd(btc_rc_cb.rc_handle, tl, AVRC_CMD_STATUS, p_msg); - status = BT_STATUS_SUCCESS; + ret = BT_STATUS_SUCCESS; + } else { + ret = BT_STATUS_FAIL; } } else { - status = BT_STATUS_FAIL; + ret = BT_STATUS_FAIL; BTC_TRACE_DEBUG("%s: feature not supported", __FUNCTION__); } @@ -1464,7 +1510,7 @@ static bt_status_t btc_avrc_ct_send_get_play_status_cmd(uint8_t tl) BTC_TRACE_DEBUG("%s: feature not enabled", __FUNCTION__); #endif - return status; + return ret; } static bt_status_t btc_avrc_ct_send_passthrough_cmd(uint8_t tl, uint8_t key_code, uint8_t key_state) @@ -1758,6 +1804,8 @@ void btc_avrc_ct_call_handler(btc_msg_t *msg) default: BTC_TRACE_WARNING("%s : unhandled event: %d\n", __FUNCTION__, msg->act); } + + btc_avrc_arg_deep_free(msg); } void btc_avrc_tg_call_handler(btc_msg_t *msg) diff --git a/components/bt/host/bluedroid/btc/profile/std/cte/btc_ble_cte.c b/components/bt/host/bluedroid/btc/profile/std/cte/btc_ble_cte.c index 6bdfc1b9dca..ab38419dfaa 100644 --- a/components/bt/host/bluedroid/btc/profile/std/cte/btc_ble_cte.c +++ b/components/bt/host/bluedroid/btc/profile/std/cte/btc_ble_cte.c @@ -28,7 +28,7 @@ static void btc_ble_cte_callback(tBTM_BLE_CTE_EVENT event, { esp_ble_cte_cb_param_t param = {0}; bt_status_t ret; - btc_msg_t msg; + btc_msg_t msg = {0}; msg.sig = BTC_SIG_API_CB; msg.pid = BTC_PID_BLE_CTE; @@ -42,7 +42,7 @@ static void btc_ble_cte_callback(tBTM_BLE_CTE_EVENT event, break; case BTA_BLE_CTE_SET_TRANS_ENABLE_EVT: msg.act = ESP_BLE_CTE_SET_CONNLESS_TRANS_ENABLE_CMPL_EVT; - param.set_trans_enable_cmpl.status = btc_btm_status_to_esp_status(params->cte_trans_params_cmpl.status); + param.set_trans_enable_cmpl.status = btc_btm_status_to_esp_status(params->cte_trans_en_cmpl.status); break; case BTA_BLE_CTE_SET_IQ_SAMP_ENABLE_EVT: msg.act = ESP_BLE_CTE_SET_CONNLESS_IQ_SAMPLING_ENABLE_CMPL_EVT; @@ -100,6 +100,7 @@ static void btc_ble_cte_callback(tBTM_BLE_CTE_EVENT event, case BTA_BLE_CTE_CONN_IQ_REPORT_EVT: msg.act = ESP_BLE_CTE_CONN_IQ_REPORT_EVT; param.conn_iq_rpt.conn_handle = params->cte_conn_iq_rpt.conn_handle; + param.conn_iq_rpt.rx_phy = params->cte_conn_iq_rpt.rx_phy; param.conn_iq_rpt.data_channel_idx = params->cte_conn_iq_rpt.data_channel_idx; param.conn_iq_rpt.rssi = params->cte_conn_iq_rpt.rssi; param.conn_iq_rpt.rssi_ant_id = params->cte_conn_iq_rpt.rssi_ant_id; @@ -163,6 +164,7 @@ void btc_ble_cte_arg_deep_copy(btc_msg_t *msg, void *p_dest, void *p_src) dst->cte_trans_params.antenna_ids = NULL; } } else { + dst->cte_trans_params.switching_pattern_len = 0; dst->cte_trans_params.antenna_ids = NULL; } break; @@ -177,6 +179,7 @@ void btc_ble_cte_arg_deep_copy(btc_msg_t *msg, void *p_dest, void *p_src) BTC_TRACE_ERROR("%s %d no mem\n",__func__, msg->act); } } else { + dst->cte_iq_sampling_en.switching_pattern_len = 0; dst->cte_iq_sampling_en.antenna_ids = NULL; } break; @@ -194,6 +197,7 @@ void btc_ble_cte_arg_deep_copy(btc_msg_t *msg, void *p_dest, void *p_src) BTC_TRACE_ERROR("%s %d no mem\n",__func__, msg->act); } } else { + dst->cte_recv_params.switching_pattern_len = 0; dst->cte_recv_params.antenna_ids = NULL; } break; @@ -208,6 +212,7 @@ void btc_ble_cte_arg_deep_copy(btc_msg_t *msg, void *p_dest, void *p_src) BTC_TRACE_ERROR("%s %d no mem\n",__func__, msg->act); } } else { + dst->cte_conn_trans_params.switching_pattern_len = 0; dst->cte_conn_trans_params.antenna_ids = NULL; } break; diff --git a/components/bt/host/bluedroid/btc/profile/std/gap/btc_gap_ble.c b/components/bt/host/bluedroid/btc/profile/std/gap/btc_gap_ble.c index 057a1248b60..7e4a11e3898 100644 --- a/components/bt/host/bluedroid/btc/profile/std/gap/btc_gap_ble.c +++ b/components/bt/host/bluedroid/btc/profile/std/gap/btc_gap_ble.c @@ -490,7 +490,8 @@ static void btc_ble_start_advertising (esp_ble_adv_params_t *ble_adv_params) tBLE_BD_ADDR peer_addr; esp_bt_status_t status = ESP_BT_STATUS_SUCCESS; if (!BLE_ISVALID_PARAM(ble_adv_params->adv_int_min, BTM_BLE_ADV_INT_MIN, BTM_BLE_ADV_INT_MAX) || - !BLE_ISVALID_PARAM(ble_adv_params->adv_int_max, BTM_BLE_ADV_INT_MIN, BTM_BLE_ADV_INT_MAX)) { + !BLE_ISVALID_PARAM(ble_adv_params->adv_int_max, BTM_BLE_ADV_INT_MIN, BTM_BLE_ADV_INT_MAX) || + ble_adv_params->adv_int_min > ble_adv_params->adv_int_max) { status = ESP_BT_STATUS_PARM_INVALID; BTC_TRACE_ERROR("Invalid advertisting interval parameters.\n"); } @@ -563,7 +564,8 @@ static void btc_ble_set_scan_params(esp_ble_scan_params_t *scan_params) BLE_ISVALID_PARAM(scan_params->own_addr_type, BLE_ADDR_TYPE_PUBLIC, BLE_ADDR_TYPE_RPA_RANDOM) && BLE_ISVALID_PARAM(scan_params->scan_filter_policy, BLE_SCAN_FILTER_ALLOW_ALL, BLE_SCAN_FILTER_ALLOW_WLIST_RPA_DIR) && BLE_ISVALID_PARAM(scan_params->scan_duplicate, BLE_SCAN_DUPLICATE_DISABLE, BLE_SCAN_DUPLICATE_MAX -1) && - (scan_params->scan_type == BTM_BLE_SCAN_MODE_ACTI || scan_params->scan_type == BTM_BLE_SCAN_MODE_PASS)) { + (scan_params->scan_type == BTM_BLE_SCAN_MODE_ACTI || scan_params->scan_type == BTM_BLE_SCAN_MODE_PASS) && + scan_params->scan_window <= scan_params->scan_interval) { BTA_DmSetBleScanFilterParams(ESP_DEFAULT_GATT_IF, /*client_if*/ scan_params->scan_interval, scan_params->scan_window, @@ -936,7 +938,7 @@ static void btc_read_ble_rssi_cmpl_callback(void *p_data) static void btc_ble_read_channel_map_callback(void *p_data) { tBTA_BLE_CH_MAP_RESULTS *result = (tBTA_BLE_CH_MAP_RESULTS *)p_data; - esp_ble_gap_cb_param_t param; + esp_ble_gap_cb_param_t param = {0}; bt_status_t ret; btc_msg_t msg = {0}; @@ -1229,7 +1231,14 @@ void btc_ble_5_gap_callback(tBTA_DM_BLE_5_GAP_EVENT event, #if (BLE_50_EXTEND_SCAN_EN == TRUE) case BTA_DM_BLE_5_GAP_EXT_ADV_REPORT_EVT: msg.act = ESP_GAP_BLE_EXT_ADV_REPORT_EVT; - memcpy(¶m.ext_adv_report.params, ¶ms->ext_adv_report, sizeof(esp_ble_gap_ext_adv_report_t)); + memcpy(¶m.ext_adv_report.params, ¶ms->ext_adv_report, sizeof(tBTM_BLE_EXT_ADV_REPORT)); + /* The source struct ends with a pointer (UINT8 *adv_data) while the destination + * ends with a fixed array (uint8_t adv_data[251]). The memcpy above leaves the + * raw pointer bytes at the start of adv_data[]. Clear it before copying the real + * advertising payload to avoid leaking stale pointer bytes when adv_data is NULL + * or adv_data_len is smaller than sizeof(void *). */ + memset(param.ext_adv_report.params.adv_data, 0, + sizeof(param.ext_adv_report.params.adv_data)); if (params->ext_adv_report.adv_data) { memcpy(param.ext_adv_report.params.adv_data, params->ext_adv_report.adv_data, params->ext_adv_report.adv_data_len); @@ -1274,6 +1283,128 @@ void btc_ble_5_gap_callback(tBTA_DM_BLE_5_GAP_EVENT event, break; } #endif // #if (BLE_FEAT_ADV_MONITOR == TRUE) +#if (BLE_FEAT_DBAF == TRUE) + case BTA_DM_BLE_5_GAP_SET_DECISION_DATA_COMPLETE_EVT: { + msg.act = ESP_GAP_BLE_SET_DECISION_DATA_COMPLETE_EVT; + param.set_decision_data.status = btc_btm_status_to_esp_status(params->status); + break; + } + case BTA_DM_BLE_5_GAP_SET_DECISION_INSTRUCTIONS_COMPLETE_EVT: { + msg.act = ESP_GAP_BLE_SET_DECISION_INSTRUCTIONS_COMPLETE_EVT; + param.set_decision_instructions.status = btc_btm_status_to_esp_status(params->status); + break; + } +#endif // #if (BLE_FEAT_DBAF == TRUE) +#if (BLE_FEAT_FRAME_SPACE_UPDATE == TRUE) + case BTA_DM_BLE_5_GAP_FRAME_SPACE_UPDATE_COMPLETE_EVT: { + msg.act = ESP_GAP_BLE_FRAME_SPACE_UPDATE_COMPLETE_EVT; + param.frame_space_update.status = btc_btm_status_to_esp_status(params->frame_space_update.status); + param.frame_space_update.conn_handle = params->frame_space_update.conn_handle; + param.frame_space_update.initiator = params->frame_space_update.initiator; + param.frame_space_update.frame_space = params->frame_space_update.frame_space; + param.frame_space_update.phys = params->frame_space_update.phys; + param.frame_space_update.spacing_types = params->frame_space_update.spacing_types; + break; + } +#endif // #if (BLE_FEAT_FRAME_SPACE_UPDATE == TRUE) +#if (BLE_FEAT_LL_EXT_FEAT == TRUE) + case BTA_DM_BLE_5_GAP_READ_ALL_LOCAL_SUPP_FEAT_COMPLETE_EVT: { + msg.act = ESP_GAP_BLE_READ_ALL_LOCAL_SUPP_FEAT_COMPLETE_EVT; + param.read_all_local_supp_feat.status = btc_btm_status_to_esp_status(params->read_all_local_supp_feat.status); + param.read_all_local_supp_feat.max_page = params->read_all_local_supp_feat.max_page; + if (params->read_all_local_supp_feat.le_features != NULL) { + memcpy(param.read_all_local_supp_feat.le_features, params->read_all_local_supp_feat.le_features, + ESP_BLE_GAP_LL_EXT_FEAT_DATA_LEN); + } else { + memset(param.read_all_local_supp_feat.le_features, 0, ESP_BLE_GAP_LL_EXT_FEAT_DATA_LEN); + } + break; + } + case BTA_DM_BLE_5_GAP_READ_ALL_REMOTE_FEAT_COMPLETE_EVT: { + msg.act = ESP_GAP_BLE_READ_ALL_REMOTE_FEAT_COMPLETE_EVT; + param.read_all_remote_feat.status = btc_btm_status_to_esp_status(params->read_all_remote_feat.status); + param.read_all_remote_feat.conn_handle = params->read_all_remote_feat.conn_handle; + param.read_all_remote_feat.max_remote_page = params->read_all_remote_feat.max_remote_page; + param.read_all_remote_feat.max_valid_page = params->read_all_remote_feat.max_valid_page; + if (params->read_all_remote_feat.le_features != NULL) { + memcpy(param.read_all_remote_feat.le_features, params->read_all_remote_feat.le_features, + ESP_BLE_GAP_LL_EXT_FEAT_DATA_LEN); + } else { + memset(param.read_all_remote_feat.le_features, 0, ESP_BLE_GAP_LL_EXT_FEAT_DATA_LEN); + } + break; + } +#endif // #if (BLE_FEAT_LL_EXT_FEAT == TRUE) +#if (BLE_FEAT_SHORTER_CONN_INTERVALS == TRUE) + case BTA_DM_BLE_5_GAP_CONNECTION_RATE_REQUEST_COMPLETE_EVT: { + msg.act = ESP_GAP_BLE_CONNECTION_RATE_REQUEST_COMPLETE_EVT; + param.connection_rate_req_cmpl.status = btc_btm_status_to_esp_status(params->conn_rate_request.status); + param.connection_rate_req_cmpl.conn_handle = params->conn_rate_request.conn_handle; + break; + } + case BTA_DM_BLE_5_GAP_CONN_RATE_CHANGE_EVT: { + msg.act = ESP_GAP_BLE_CONN_RATE_CHANGE_EVT; + param.conn_rate_change_evt.status = btc_btm_status_to_esp_status(params->conn_rate_change.status); + param.conn_rate_change_evt.conn_handle = params->conn_rate_change.conn_handle; + param.conn_rate_change_evt.conn_interval = params->conn_rate_change.conn_interval; + param.conn_rate_change_evt.subrate_factor = params->conn_rate_change.subrate_factor; + param.conn_rate_change_evt.peripheral_latency = params->conn_rate_change.peripheral_latency; + param.conn_rate_change_evt.continuation_number = params->conn_rate_change.continuation_number; + param.conn_rate_change_evt.supervision_timeout = params->conn_rate_change.supervision_timeout; + break; + } + case BTA_DM_BLE_5_GAP_SET_DEFAULT_RATE_PARAMETERS_COMPLETE_EVT: { + msg.act = ESP_GAP_BLE_SET_DEFAULT_RATE_PARAMETERS_COMPLETE_EVT; + param.set_default_rate_parameters_cmpl.status = btc_btm_status_to_esp_status(params->status); + break; + } + case BTA_DM_BLE_5_GAP_READ_MIN_SUPP_CONN_INTERVAL_COMPLETE_EVT: { + msg.act = ESP_GAP_BLE_READ_MIN_SUPP_CONN_INTERVAL_COMPLETE_EVT; + param.read_min_supp_conn_interval.status = + btc_btm_status_to_esp_status(params->read_min_supp_conn_interval.status); + param.read_min_supp_conn_interval.min_supported_conn_interval = + params->read_min_supp_conn_interval.min_supported_conn_interval; + param.read_min_supp_conn_interval.num_groups = + params->read_min_supp_conn_interval.num_groups; + if (params->read_min_supp_conn_interval.groups != NULL) { + for (uint8_t i = 0; i < params->read_min_supp_conn_interval.num_groups; i++) { + param.read_min_supp_conn_interval.groups[i].min_125us = + params->read_min_supp_conn_interval.groups[i].min_125us; + param.read_min_supp_conn_interval.groups[i].max_125us = + params->read_min_supp_conn_interval.groups[i].max_125us; + param.read_min_supp_conn_interval.groups[i].stride_125us = + params->read_min_supp_conn_interval.groups[i].stride_125us; + } + } else { + memset(param.read_min_supp_conn_interval.groups, 0, + sizeof(param.read_min_supp_conn_interval.groups)); + } + break; + } +#endif // #if (BLE_FEAT_SHORTER_CONN_INTERVALS == TRUE) +#if (BLE_FEAT_LE_UTP == TRUE) + case BTA_DM_BLE_5_GAP_ENABLE_UTP_OTA_MODE_COMPLETE_EVT: { + msg.act = ESP_GAP_BLE_ENABLE_UTP_OTA_MODE_COMPLETE_EVT; + param.enable_utp_ota_mode_cmpl.status = btc_btm_status_to_esp_status(params->status); + break; + } + case BTA_DM_BLE_5_GAP_UTP_SEND_COMPLETE_EVT: { + msg.act = ESP_GAP_BLE_UTP_SEND_COMPLETE_EVT; + param.utp_send_cmpl.status = btc_btm_status_to_esp_status(params->status); + break; + } + case BTA_DM_BLE_5_GAP_UTP_RECEIVE_EVT: { + msg.act = ESP_GAP_BLE_UTP_RECEIVE_EVT; + param.utp_receive.len = params->utp_receive.len; + if (params->utp_receive.data && params->utp_receive.len > 0) { + if (params->utp_receive.len > ESP_BLE_GAP_UTP_DATA_MAX_LEN) { + param.utp_receive.len = ESP_BLE_GAP_UTP_DATA_MAX_LEN; + } + memcpy(param.utp_receive.data, params->utp_receive.data, param.utp_receive.len); + } + break; + } +#endif // #if (BLE_FEAT_LE_UTP == TRUE) #if (BLE_50_EXTEND_ADV_EN == TRUE) case BTA_DM_BLE_5_GAP_ADV_TERMINATED_EVT: { param.adv_terminate.status = params->adv_term.status; @@ -1483,7 +1614,6 @@ void btc_ble_5_gap_callback(tBTA_DM_BLE_5_GAP_EVENT event, case BTA_BLE_GAP_CS_READ_LOCAL_SUPP_CAPS_EVT: msg.act = ESP_GAP_BLE_CS_READ_LOCAL_SUPP_CAPS_EVT; param.cs_read_local_supp_caps.status = btc_btm_status_to_esp_status(params->cs_read_local_supp_caps.status); - param.cs_read_local_supp_caps.conn_handle = params->cs_read_local_supp_caps.conn_handle; param.cs_read_local_supp_caps.num_config_supported = params->cs_read_local_supp_caps.num_config_supported; param.cs_read_local_supp_caps.max_consecutive_proc_supported = params->cs_read_local_supp_caps.max_consecutive_proc_supported; param.cs_read_local_supp_caps.num_ant_supported = params->cs_read_local_supp_caps.num_ant_supported; @@ -1581,6 +1711,17 @@ void btc_ble_5_gap_callback(tBTA_DM_BLE_5_GAP_EVENT event, param.cs_security_enable.status = btc_btm_status_to_esp_status(params->cs_security_enable.status); param.cs_security_enable.conn_handle = params->cs_security_enable.conn_handle; break; +#if (BT_BLE_FEAT_CS_SECURITY_REQUIREMENTS == TRUE) + case BTA_BLE_GAP_CS_SET_SECURITY_REQUIREMENTS_CMPL_EVT: + msg.act = ESP_GAP_BLE_CS_SET_SECURITY_REQUIREMENTS_CMPL_EVT; + param.cs_set_security_requirements.status = btc_btm_status_to_esp_status(params->cs_set_security_requirements.status); + param.cs_set_security_requirements.conn_handle = params->cs_set_security_requirements.conn_handle; + break; + case BTA_BLE_GAP_CS_SET_DEFAULT_SECURITY_REQUIREMENTS_CMPL_EVT: + msg.act = ESP_GAP_BLE_CS_SET_DEFAULT_SECURITY_REQUIREMENTS_CMPL_EVT; + param.cs_set_default_security_requirements.status = btc_btm_status_to_esp_status(params->cs_set_default_security_requirements.status); + break; +#endif // (BT_BLE_FEAT_CS_SECURITY_REQUIREMENTS == TRUE) case BTA_BLE_GAP_CS_CONFIG_CMPL_EVT: msg.act = ESP_GAP_BLE_CS_CONFIG_CMPL_EVT; param.cs_config_update.status = btc_btm_status_to_esp_status(params->cs_config_update.status); @@ -1648,12 +1789,12 @@ void btc_ble_5_gap_callback(tBTA_DM_BLE_5_GAP_EVENT event, #endif // #if (BLE_50_FEATURE_SUPPORT == TRUE) #if ((BLE_42_DTM_TEST_EN == TRUE) || (BLE_50_DTM_TEST_EN == TRUE)) -void btc_dtm_tx_start_callback(void *p1) +void btc_dtm_tx_start_callback(UINT8 *p1, UINT16 len) { UINT8 status; UINT8 *p; p = (UINT8*) p1; - if (p1) { + if (p1 && len >= 1) { STREAM_TO_UINT8(status, p); BTC_TRACE_DEBUG("DTM TX start, status 0x%x\n", status); esp_ble_gap_cb_param_t param; @@ -1675,13 +1816,13 @@ void btc_dtm_tx_start_callback(void *p1) } } -void btc_dtm_rx_start_callback(void *p1) +void btc_dtm_rx_start_callback(UINT8 *p1, UINT16 len) { UINT8 status; UINT8 *p; p = (UINT8*) p1; - if (p1) { + if (p1 && len >= 1) { STREAM_TO_UINT8(status, p); BTC_TRACE_DEBUG("DTM RX start, status 0x%x\n", status); esp_ble_gap_cb_param_t param; @@ -1705,13 +1846,13 @@ void btc_dtm_rx_start_callback(void *p1) #endif // #if ((BLE_42_DTM_TEST_EN == TRUE) || (BLE_50_DTM_TEST_EN == TRUE)) #if ((BLE_42_DTM_TEST_EN == TRUE) || (BLE_50_DTM_TEST_EN == TRUE)) -void btc_dtm_stop_callback(void *p1) +void btc_dtm_stop_callback(UINT8 *p1, UINT16 len) { UINT8 status; UINT16 num_pkt; UINT8 *p; p = (UINT8*) p1; - if (p1) { + if (p1 && len >= 3) { STREAM_TO_UINT8(status, p); STREAM_TO_UINT16(num_pkt, p); BTC_TRACE_DEBUG("DTM stop, status 0x%x num_pkt %d\n", status, num_pkt); @@ -1876,6 +2017,13 @@ static void btc_ble_vendor_hci_event_callback(UINT8 subevt_code, UINT8 param_len btc_msg_t msg = {0}; esp_ble_vendor_evt_param_t *evt_param = ¶m.vendor_hci_evt.param; bool copy_param = false; + bool parse_ok = true; + + if (param_len && params == NULL) { + BTC_TRACE_WARNING("%s vendor evt NULL: sub=0x%02x len=%u", + __func__, subevt_code, param_len); + return; + } msg.sig = BTC_SIG_API_CB; msg.pid = BTC_PID_GAP_BLE; @@ -1887,6 +2035,12 @@ static void btc_ble_vendor_hci_event_callback(UINT8 subevt_code, UINT8 param_len switch (subevt_code) { case BLE_VENDOR_PDU_RECV_EVT: param.vendor_hci_evt.subevt_code = ESP_BLE_VENDOR_PDU_RECV_EVT; + if (param_len < (UINT8)(1 + 1 + 1 + BD_ADDR_LEN)) { + BTC_TRACE_WARNING("%s vendor trunc: sub=0x%02x len=%u<%u", + __func__, subevt_code, param_len, (unsigned)(1 + 1 + 1 + BD_ADDR_LEN)); + parse_ok = false; + break; + } STREAM_TO_UINT8(evt_param->pdu_recv.type, params); STREAM_TO_UINT8(evt_param->pdu_recv.handle, params); STREAM_TO_UINT8(evt_param->pdu_recv.addr_type, params); @@ -1894,6 +2048,12 @@ static void btc_ble_vendor_hci_event_callback(UINT8 subevt_code, UINT8 param_len break; case BLE_VENDOR_CHMAP_UPDATE_EVT: param.vendor_hci_evt.subevt_code = ESP_BLE_VENDOR_CHAN_MAP_UPDATE_EVT; + if (param_len < (UINT8)(1 + 2 + ESP_GAP_BLE_CHANNELS_LEN)) { + BTC_TRACE_WARNING("%s vendor trunc: sub=0x%02x len=%u<%u", + __func__, subevt_code, param_len, (unsigned)(1 + 2 + ESP_GAP_BLE_CHANNELS_LEN)); + parse_ok = false; + break; + } STREAM_TO_UINT8(evt_param->chan_map_update.status, params); STREAM_TO_UINT16(evt_param->chan_map_update.conn_handle, params); REVERSE_STREAM_TO_ARRAY(evt_param->chan_map_update.ch_map, params, ESP_GAP_BLE_CHANNELS_LEN); @@ -1907,6 +2067,17 @@ static void btc_ble_vendor_hci_event_callback(UINT8 subevt_code, UINT8 param_len break; } + if (!parse_ok) { + /* Malformed/truncated parameters: keep the mapped esp_ble_vendor_evt_t + * already assigned in the switch (do NOT restore the raw HCI subevent + * code, which is outside esp_ble_vendor_evt_t and would break the + * public API contract). Zero the structured fields and suppress the + * internal raw buffer so the callback delivers a deterministic event; + * the truncation has already been logged above. */ + memset(evt_param, 0, sizeof(*evt_param)); + copy_param = false; + } + if (copy_param) { param.vendor_hci_evt.param_len = param_len; param.vendor_hci_evt.param_buf = (param_len) ? params : NULL; @@ -2140,20 +2311,38 @@ void btc_gap_ble_arg_deep_copy(btc_msg_t *msg, void *p_dest, void *p_src) btc_ble_gap_args_t *src = (btc_ble_gap_args_t *)p_src; btc_ble_gap_args_t *dst = (btc_ble_gap_args_t *) p_dest; - if (src->cfg_adv_data.adv_data.p_manufacturer_data) { + /* btc_transfer_context() does a shallow memcpy first; always null-out + * pointer fields so deep_free() never frees non-owned memory on OOM. */ + dst->cfg_adv_data.adv_data.p_manufacturer_data = NULL; + dst->cfg_adv_data.adv_data.p_service_data = NULL; + dst->cfg_adv_data.adv_data.p_service_uuid = NULL; + + if (src->cfg_adv_data.adv_data.p_manufacturer_data && src->cfg_adv_data.adv_data.manufacturer_len) { dst->cfg_adv_data.adv_data.p_manufacturer_data = osi_malloc(src->cfg_adv_data.adv_data.manufacturer_len); - memcpy(dst->cfg_adv_data.adv_data.p_manufacturer_data, src->cfg_adv_data.adv_data.p_manufacturer_data, - src->cfg_adv_data.adv_data.manufacturer_len); + if (dst->cfg_adv_data.adv_data.p_manufacturer_data) { + memcpy(dst->cfg_adv_data.adv_data.p_manufacturer_data, src->cfg_adv_data.adv_data.p_manufacturer_data, + src->cfg_adv_data.adv_data.manufacturer_len); + } else { + BTC_TRACE_WARNING("%s no mem, manu drop %u", __func__, src->cfg_adv_data.adv_data.manufacturer_len); + } } - if (src->cfg_adv_data.adv_data.p_service_data) { + if (src->cfg_adv_data.adv_data.p_service_data && src->cfg_adv_data.adv_data.service_data_len) { dst->cfg_adv_data.adv_data.p_service_data = osi_malloc(src->cfg_adv_data.adv_data.service_data_len); - memcpy(dst->cfg_adv_data.adv_data.p_service_data, src->cfg_adv_data.adv_data.p_service_data, src->cfg_adv_data.adv_data.service_data_len); + if (dst->cfg_adv_data.adv_data.p_service_data) { + memcpy(dst->cfg_adv_data.adv_data.p_service_data, src->cfg_adv_data.adv_data.p_service_data, src->cfg_adv_data.adv_data.service_data_len); + } else { + BTC_TRACE_WARNING("%s no mem, svc_data drop %u", __func__, src->cfg_adv_data.adv_data.service_data_len); + } } - if (src->cfg_adv_data.adv_data.p_service_uuid) { + if (src->cfg_adv_data.adv_data.p_service_uuid && src->cfg_adv_data.adv_data.service_uuid_len) { dst->cfg_adv_data.adv_data.p_service_uuid = osi_malloc(src->cfg_adv_data.adv_data.service_uuid_len); - memcpy(dst->cfg_adv_data.adv_data.p_service_uuid, src->cfg_adv_data.adv_data.p_service_uuid, src->cfg_adv_data.adv_data.service_uuid_len); + if (dst->cfg_adv_data.adv_data.p_service_uuid) { + memcpy(dst->cfg_adv_data.adv_data.p_service_uuid, src->cfg_adv_data.adv_data.p_service_uuid, src->cfg_adv_data.adv_data.service_uuid_len); + } else { + BTC_TRACE_WARNING("%s no mem, svc_uuid drop %u", __func__, src->cfg_adv_data.adv_data.service_uuid_len); + } } break; } @@ -2161,10 +2350,13 @@ void btc_gap_ble_arg_deep_copy(btc_msg_t *msg, void *p_dest, void *p_src) btc_ble_gap_args_t *src = (btc_ble_gap_args_t *)p_src; btc_ble_gap_args_t *dst = (btc_ble_gap_args_t *) p_dest; + dst->cfg_adv_data_raw.raw_adv = NULL; + dst->cfg_adv_data_raw.raw_adv_len = 0; if (src && src->cfg_adv_data_raw.raw_adv && src->cfg_adv_data_raw.raw_adv_len > 0) { dst->cfg_adv_data_raw.raw_adv = osi_malloc(src->cfg_adv_data_raw.raw_adv_len); if (dst->cfg_adv_data_raw.raw_adv) { memcpy(dst->cfg_adv_data_raw.raw_adv, src->cfg_adv_data_raw.raw_adv, src->cfg_adv_data_raw.raw_adv_len); + dst->cfg_adv_data_raw.raw_adv_len = src->cfg_adv_data_raw.raw_adv_len; } } break; @@ -2173,10 +2365,13 @@ void btc_gap_ble_arg_deep_copy(btc_msg_t *msg, void *p_dest, void *p_src) btc_ble_gap_args_t *src = (btc_ble_gap_args_t *)p_src; btc_ble_gap_args_t *dst = (btc_ble_gap_args_t *) p_dest; + dst->cfg_scan_rsp_data_raw.raw_scan_rsp = NULL; + dst->cfg_scan_rsp_data_raw.raw_scan_rsp_len = 0; if (src && src->cfg_scan_rsp_data_raw.raw_scan_rsp && src->cfg_scan_rsp_data_raw.raw_scan_rsp_len > 0) { dst->cfg_scan_rsp_data_raw.raw_scan_rsp = osi_malloc(src->cfg_scan_rsp_data_raw.raw_scan_rsp_len); if (dst->cfg_scan_rsp_data_raw.raw_scan_rsp) { memcpy(dst->cfg_scan_rsp_data_raw.raw_scan_rsp, src->cfg_scan_rsp_data_raw.raw_scan_rsp, src->cfg_scan_rsp_data_raw.raw_scan_rsp_len); + dst->cfg_scan_rsp_data_raw.raw_scan_rsp_len = src->cfg_scan_rsp_data_raw.raw_scan_rsp_len; } } break; @@ -2187,6 +2382,7 @@ void btc_gap_ble_arg_deep_copy(btc_msg_t *msg, void *p_dest, void *p_src) btc_ble_gap_args_t *src = (btc_ble_gap_args_t *)p_src; btc_ble_gap_args_t *dst = (btc_ble_gap_args_t *) p_dest; uint8_t length = 0; + dst->set_security_param.value = NULL; if (src->set_security_param.value) { length = dst->set_security_param.len; dst->set_security_param.value = osi_malloc(length); @@ -2194,6 +2390,7 @@ void btc_gap_ble_arg_deep_copy(btc_msg_t *msg, void *p_dest, void *p_src) memcpy(dst->set_security_param.value, src->set_security_param.value, length); } else { BTC_TRACE_ERROR("%s %d no mem\n",__func__, msg->act); + dst->set_security_param.len = 0; } } break; @@ -2202,6 +2399,7 @@ void btc_gap_ble_arg_deep_copy(btc_msg_t *msg, void *p_dest, void *p_src) btc_ble_gap_args_t *src = (btc_ble_gap_args_t *)p_src; btc_ble_gap_args_t *dst = (btc_ble_gap_args_t *) p_dest; uint8_t length = 0; + dst->oob_req_reply.p_value = NULL; if (src->oob_req_reply.p_value) { length = dst->oob_req_reply.len; dst->oob_req_reply.p_value = osi_malloc(length); @@ -2209,6 +2407,7 @@ void btc_gap_ble_arg_deep_copy(btc_msg_t *msg, void *p_dest, void *p_src) memcpy(dst->oob_req_reply.p_value, src->oob_req_reply.p_value, length); } else { BTC_TRACE_ERROR("%s %d no mem\n",__func__, msg->act); + dst->oob_req_reply.len = 0; } } break; @@ -2216,6 +2415,8 @@ void btc_gap_ble_arg_deep_copy(btc_msg_t *msg, void *p_dest, void *p_src) case BTC_GAP_BLE_SC_OOB_REQ_REPLY_EVT: { btc_ble_gap_args_t *src = (btc_ble_gap_args_t *)p_src; btc_ble_gap_args_t *dst = (btc_ble_gap_args_t *)p_dest; + dst->sc_oob_req_reply.p_c = NULL; + dst->sc_oob_req_reply.p_r = NULL; if (src->sc_oob_req_reply.p_c) { dst->sc_oob_req_reply.p_c = osi_malloc(BT_OCTET16_LEN); if (dst->sc_oob_req_reply.p_c) { @@ -2236,17 +2437,38 @@ void btc_gap_ble_arg_deep_copy(btc_msg_t *msg, void *p_dest, void *p_src) } #if (BLE_50_FEATURE_SUPPORT == TRUE) #if (BLE_50_EXTEND_ADV_EN == TRUE) - case BTC_GAP_BLE_CFG_EXT_ADV_DATA_RAW: - case BTC_GAP_BLE_CFG_EXT_SCAN_RSP_DATA_RAW: { + case BTC_GAP_BLE_CFG_EXT_ADV_DATA_RAW: { btc_ble_5_gap_args_t *src = (btc_ble_5_gap_args_t *)p_src; btc_ble_5_gap_args_t *dst = (btc_ble_5_gap_args_t *)p_dest; uint16_t length = 0; + dst->ext_adv_cfg_data.data = NULL; + dst->ext_adv_cfg_data.length = 0; if (src->ext_adv_cfg_data.data) { length = src->ext_adv_cfg_data.length; dst->ext_adv_cfg_data.data = osi_malloc(length); if (dst->ext_adv_cfg_data.data) { memcpy(dst->ext_adv_cfg_data.data, src->ext_adv_cfg_data.data, length); + dst->ext_adv_cfg_data.length = length; + } else { + BTC_TRACE_ERROR("%s %d no mem\n",__func__, msg->act); + } + } + break; + } + case BTC_GAP_BLE_CFG_EXT_SCAN_RSP_DATA_RAW: { + btc_ble_5_gap_args_t *src = (btc_ble_5_gap_args_t *)p_src; + btc_ble_5_gap_args_t *dst = (btc_ble_5_gap_args_t *)p_dest; + uint16_t length = 0; + + dst->cfg_scan_rsp.data = NULL; + dst->cfg_scan_rsp.length = 0; + if (src->cfg_scan_rsp.data) { + length = src->cfg_scan_rsp.length; + dst->cfg_scan_rsp.data = osi_malloc(length); + if (dst->cfg_scan_rsp.data) { + memcpy(dst->cfg_scan_rsp.data, src->cfg_scan_rsp.data, length); + dst->cfg_scan_rsp.length = length; } else { BTC_TRACE_ERROR("%s %d no mem\n",__func__, msg->act); } @@ -2260,17 +2482,17 @@ void btc_gap_ble_arg_deep_copy(btc_msg_t *msg, void *p_dest, void *p_src) btc_ble_5_gap_args_t *dst = (btc_ble_5_gap_args_t *)p_dest; uint16_t length = 0; + dst->periodic_adv_cfg_data.data = NULL; + dst->periodic_adv_cfg_data.len = 0; if (src->periodic_adv_cfg_data.data) { length = src->periodic_adv_cfg_data.len; dst->periodic_adv_cfg_data.data = osi_malloc(length); if (dst->periodic_adv_cfg_data.data) { memcpy(dst->periodic_adv_cfg_data.data, src->periodic_adv_cfg_data.data, length); + dst->periodic_adv_cfg_data.len = length; } else { BTC_TRACE_ERROR("%s %d no mem\n",__func__, msg->act); } - } else { - dst->periodic_adv_cfg_data.data = NULL; - dst->periodic_adv_cfg_data.len = 0; } break; } @@ -2308,10 +2530,13 @@ void btc_gap_ble_arg_deep_copy(btc_msg_t *msg, void *p_dest, void *p_src) case BTC_GAP_BLE_ACT_VENDOR_HCI_CMD_EVT: { btc_ble_gap_args_t *src = (btc_ble_gap_args_t *)p_src; btc_ble_gap_args_t *dst = (btc_ble_gap_args_t *)p_dest; + dst->vendor_cmd_send.p_param_buf = NULL; + dst->vendor_cmd_send.param_len = 0; if (src->vendor_cmd_send.param_len) { dst->vendor_cmd_send.p_param_buf = osi_malloc(src->vendor_cmd_send.param_len); if (dst->vendor_cmd_send.p_param_buf) { memcpy(dst->vendor_cmd_send.p_param_buf, src->vendor_cmd_send.p_param_buf, src->vendor_cmd_send.param_len); + dst->vendor_cmd_send.param_len = src->vendor_cmd_send.param_len; } else { BTC_TRACE_ERROR("%s %d no mem\n",__func__, msg->act); } @@ -2385,6 +2610,102 @@ void btc_gap_ble_arg_deep_copy(btc_msg_t *msg, void *p_dest, void *p_src) break; } #endif // #if (BT_BLE_FEAT_PAWR_EN == TRUE) +#if (BLE_FEAT_DBAF == TRUE) + case BTC_GAP_BLE_SET_DECISION_DATA: { + btc_ble_5_gap_args_t *src = (btc_ble_5_gap_args_t *)p_src; + btc_ble_5_gap_args_t *dst = (btc_ble_5_gap_args_t *)p_dest; + + dst->set_decision_data.data = NULL; + if (src->set_decision_data.data_len > 0 && src->set_decision_data.data) { + dst->set_decision_data.data = osi_malloc(src->set_decision_data.data_len); + if (dst->set_decision_data.data) { + memcpy(dst->set_decision_data.data, src->set_decision_data.data, + src->set_decision_data.data_len); + } else { + BTC_TRACE_WARNING("%s no mem, decision data drop %u", __func__, src->set_decision_data.data_len); + dst->set_decision_data.data_len = 0; + } + } + break; + } + case BTC_GAP_BLE_SET_DECISION_INSTRUCTIONS: { + btc_ble_5_gap_args_t *src = (btc_ble_5_gap_args_t *)p_src; + btc_ble_5_gap_args_t *dst = (btc_ble_5_gap_args_t *)p_dest; + bool oom = false; + + dst->set_decision_instructions.test_flags = NULL; + dst->set_decision_instructions.test_fields = NULL; + dst->set_decision_instructions.test_params = NULL; + if (src->set_decision_instructions.num_tests > ESP_BLE_GAP_DECISION_MAX_TESTS) { + BTC_TRACE_WARNING("%s invalid num_tests %u", __func__, + src->set_decision_instructions.num_tests); + oom = true; + } + if (!oom && src->set_decision_instructions.num_tests > 0) { + if (src->set_decision_instructions.test_flags) { + dst->set_decision_instructions.test_flags = osi_malloc(src->set_decision_instructions.num_tests); + if (dst->set_decision_instructions.test_flags) { + memcpy(dst->set_decision_instructions.test_flags, src->set_decision_instructions.test_flags, + src->set_decision_instructions.num_tests); + } else { + oom = true; + } + } + if (!oom && src->set_decision_instructions.test_fields) { + dst->set_decision_instructions.test_fields = osi_malloc(src->set_decision_instructions.num_tests); + if (dst->set_decision_instructions.test_fields) { + memcpy(dst->set_decision_instructions.test_fields, src->set_decision_instructions.test_fields, + src->set_decision_instructions.num_tests); + } else { + oom = true; + } + } + } + if (!oom && src->set_decision_instructions.num_tests > 0 && + src->set_decision_instructions.test_params) { + size_t test_params_len = (size_t)src->set_decision_instructions.num_tests * + ESP_BLE_GAP_DECISION_TEST_PARAM_LEN; + dst->set_decision_instructions.test_params = osi_malloc(test_params_len); + if (dst->set_decision_instructions.test_params) { + memcpy(dst->set_decision_instructions.test_params, src->set_decision_instructions.test_params, + test_params_len); + } else { + oom = true; + } + } + if (oom) { + BTC_TRACE_WARNING("%s no mem, decision instructions drop", __func__); + if (dst->set_decision_instructions.test_flags) { + osi_free(dst->set_decision_instructions.test_flags); + dst->set_decision_instructions.test_flags = NULL; + } + if (dst->set_decision_instructions.test_fields) { + osi_free(dst->set_decision_instructions.test_fields); + dst->set_decision_instructions.test_fields = NULL; + } + dst->set_decision_instructions.num_tests = 0; + } + break; + } +#endif // #if (BLE_FEAT_DBAF == TRUE) +#if (BLE_FEAT_LE_UTP == TRUE) + case BTC_GAP_BLE_UTP_SEND: { + btc_ble_5_gap_args_t *src = (btc_ble_5_gap_args_t *)p_src; + btc_ble_5_gap_args_t *dst = (btc_ble_5_gap_args_t *)p_dest; + + dst->utp_send.data = NULL; + if (src->utp_send.data_len > 0 && src->utp_send.data) { + dst->utp_send.data = osi_malloc(src->utp_send.data_len); + if (dst->utp_send.data) { + memcpy(dst->utp_send.data, src->utp_send.data, src->utp_send.data_len); + } else { + BTC_TRACE_WARNING("%s no mem, utp data drop %u", __func__, src->utp_send.data_len); + dst->utp_send.data_len = 0; + } + } + break; + } +#endif // #if (BLE_FEAT_LE_UTP == TRUE) default: BTC_TRACE_ERROR("Unhandled deep copy %d\n", msg->act); break; @@ -2398,11 +2719,14 @@ void btc_gap_ble_cb_deep_copy(btc_msg_t *msg, void *p_dest, void *p_src) switch (msg->act) { case ESP_GAP_BLE_VENDOR_CMD_COMPLETE_EVT: { + dst->vendor_cmd_cmpl.p_param_buf = NULL; + dst->vendor_cmd_cmpl.param_len = 0; if (src->vendor_cmd_cmpl.param_len) { dst->vendor_cmd_cmpl.p_param_buf = osi_malloc(src->vendor_cmd_cmpl.param_len); if (dst->vendor_cmd_cmpl.p_param_buf) { memcpy(dst->vendor_cmd_cmpl.p_param_buf, src->vendor_cmd_cmpl.p_param_buf, src->vendor_cmd_cmpl.param_len); + dst->vendor_cmd_cmpl.param_len = src->vendor_cmd_cmpl.param_len; } else { BTC_TRACE_ERROR("%s, malloc failed\n", __func__); } @@ -2410,11 +2734,14 @@ void btc_gap_ble_cb_deep_copy(btc_msg_t *msg, void *p_dest, void *p_src) break; } case ESP_GAP_BLE_VENDOR_HCI_EVT: { + dst->vendor_hci_evt.param_buf = NULL; + dst->vendor_hci_evt.param_len = 0; if (src->vendor_hci_evt.param_len) { dst->vendor_hci_evt.param_buf = osi_malloc(src->vendor_hci_evt.param_len); if (dst->vendor_hci_evt.param_buf) { memcpy(dst->vendor_hci_evt.param_buf, src->vendor_hci_evt.param_buf, src->vendor_hci_evt.param_len); + dst->vendor_hci_evt.param_len = src->vendor_hci_evt.param_len; } else { BTC_TRACE_ERROR("%s, malloc failed\n", __func__); } @@ -2476,7 +2803,7 @@ void btc_gap_ble_cb_deep_copy(btc_msg_t *msg, void *p_dest, void *p_src) } } } else { - BTC_TRACE_ERROR("%s, pa_rsp_info, no enough memory.", __func__); + BTC_TRACE_ERROR("%s, step_info, no enough memory.", __func__); } } break; @@ -2499,7 +2826,7 @@ void btc_gap_ble_cb_deep_copy(btc_msg_t *msg, void *p_dest, void *p_src) } } } else { - BTC_TRACE_ERROR("%s, pa_rsp_info, no enough memory.", __func__); + BTC_TRACE_ERROR("%s, continue step_info, no enough memory.", __func__); } } break; @@ -2574,14 +2901,20 @@ void btc_gap_ble_arg_deep_free(btc_msg_t *msg) } #if (BLE_50_FEATURE_SUPPORT == TRUE) #if (BLE_50_EXTEND_ADV_EN == TRUE) - case BTC_GAP_BLE_CFG_EXT_ADV_DATA_RAW: - case BTC_GAP_BLE_CFG_EXT_SCAN_RSP_DATA_RAW: { + case BTC_GAP_BLE_CFG_EXT_ADV_DATA_RAW: { uint8_t *value = ((btc_ble_5_gap_args_t *)msg->arg)->ext_adv_cfg_data.data; if (value) { osi_free(value); } break; } + case BTC_GAP_BLE_CFG_EXT_SCAN_RSP_DATA_RAW: { + uint8_t *value = ((btc_ble_5_gap_args_t *)msg->arg)->cfg_scan_rsp.data; + if (value) { + osi_free(value); + } + break; + } #endif // #if (BLE_50_EXTEND_ADV_EN == TRUE) #if (BLE_50_PERIODIC_ADV_EN == TRUE) case BTC_GAP_BLE_CFG_PERIODIC_ADV_DATA_RAW: { @@ -2655,6 +2988,37 @@ void btc_gap_ble_arg_deep_free(btc_msg_t *msg) break; } #endif // #if (BT_BLE_FEAT_PAWR_EN == TRUE) +#if (BLE_FEAT_DBAF == TRUE) + case BTC_GAP_BLE_SET_DECISION_DATA: { + uint8_t *data = ((btc_ble_5_gap_args_t *)msg->arg)->set_decision_data.data; + if (data) { + osi_free(data); + } + break; + } + case BTC_GAP_BLE_SET_DECISION_INSTRUCTIONS: { + btc_ble_5_gap_args_t *args = (btc_ble_5_gap_args_t *)msg->arg; + if (args->set_decision_instructions.test_flags) { + osi_free(args->set_decision_instructions.test_flags); + } + if (args->set_decision_instructions.test_fields) { + osi_free(args->set_decision_instructions.test_fields); + } + if (args->set_decision_instructions.test_params) { + osi_free(args->set_decision_instructions.test_params); + } + break; + } +#endif // #if (BLE_FEAT_DBAF == TRUE) +#if (BLE_FEAT_LE_UTP == TRUE) + case BTC_GAP_BLE_UTP_SEND: { + uint8_t *data = ((btc_ble_5_gap_args_t *)msg->arg)->utp_send.data; + if (data) { + osi_free(data); + } + break; + } +#endif // #if (BLE_FEAT_LE_UTP == TRUE) default: BTC_TRACE_DEBUG("Unhandled deep free %d\n", msg->act); break; @@ -3033,9 +3397,9 @@ void btc_gap_ble_call_handler(btc_msg_t *msg) break; case BTC_GAP_BLE_CFG_EXT_SCAN_RSP_DATA_RAW: BTC_TRACE_DEBUG("BTC_GAP_BLE_CFG_EXT_SCAN_RSP_DATA_RAW"); - BTA_DmBleGapConfigExtAdvDataRaw(TRUE, arg_5->ext_adv_cfg_data.instance, - arg_5->ext_adv_cfg_data.length, - (const UINT8 *)arg_5->ext_adv_cfg_data.data); + BTA_DmBleGapConfigExtAdvDataRaw(TRUE, arg_5->cfg_scan_rsp.instance, + arg_5->cfg_scan_rsp.length, + (const UINT8 *)arg_5->cfg_scan_rsp.data); break; case BTC_GAP_BLE_EXT_ADV_START: { BTC_TRACE_DEBUG("BTC_GAP_BLE_EXT_ADV_START"); @@ -3315,6 +3679,74 @@ void btc_gap_ble_call_handler(btc_msg_t *msg) BTA_DmBleGapEnableMonitorAdv(arg_5->enable_monitor_adv.enable); break; #endif // #if (BLE_FEAT_ADV_MONITOR == TRUE) +#if (BLE_FEAT_DBAF == TRUE) + case BTC_GAP_BLE_SET_DECISION_DATA: + BTA_DmBleGapSetDecisionData(arg_5->set_decision_data.adv_handle, + arg_5->set_decision_data.decision_type_flags, + arg_5->set_decision_data.data_len, + arg_5->set_decision_data.data); + break; + case BTC_GAP_BLE_SET_DECISION_INSTRUCTIONS: + BTA_DmBleGapSetDecisionInstructions(arg_5->set_decision_instructions.num_tests, + arg_5->set_decision_instructions.test_flags, + arg_5->set_decision_instructions.test_fields, + arg_5->set_decision_instructions.test_params); + break; +#endif // #if (BLE_FEAT_DBAF == TRUE) +#if (BLE_FEAT_FRAME_SPACE_UPDATE == TRUE) + case BTC_GAP_BLE_FRAME_SPACE_UPDATE: + BTA_DmBleGapFrameSpaceUpdate(arg_5->frame_space_update.conn_handle, + arg_5->frame_space_update.frame_space_min, + arg_5->frame_space_update.frame_space_max, + arg_5->frame_space_update.phys, + arg_5->frame_space_update.spacing_types); + break; +#endif // #if (BLE_FEAT_FRAME_SPACE_UPDATE == TRUE) +#if (BLE_FEAT_LL_EXT_FEAT == TRUE) + case BTC_GAP_BLE_READ_ALL_LOCAL_SUPP_FEAT: + BTA_DmBleGapReadAllLocalSuppFeatures(); + break; + case BTC_GAP_BLE_READ_ALL_REMOTE_FEAT: + BTA_DmBleGapReadAllRemoteFeatures(arg_5->read_all_remote_feat.conn_handle, + arg_5->read_all_remote_feat.page_requested); + break; +#endif // #if (BLE_FEAT_LL_EXT_FEAT == TRUE) +#if (BLE_FEAT_SHORTER_CONN_INTERVALS == TRUE) + case BTC_GAP_BLE_CONNECTION_RATE_REQUEST: + BTA_DmBleGapConnectionRateRequest(arg_5->connection_rate_request.conn_handle, + arg_5->connection_rate_request.conn_interval_min, + arg_5->connection_rate_request.conn_interval_max, + arg_5->connection_rate_request.subrate_min, + arg_5->connection_rate_request.subrate_max, + arg_5->connection_rate_request.max_latency, + arg_5->connection_rate_request.continuation_number, + arg_5->connection_rate_request.supervision_timeout, + arg_5->connection_rate_request.min_ce_len, + arg_5->connection_rate_request.max_ce_len); + break; + case BTC_GAP_BLE_SET_DEFAULT_RATE_PARAMETERS: + BTA_DmBleGapSetDefaultRateParameters(arg_5->set_default_rate_parameters.conn_interval_min, + arg_5->set_default_rate_parameters.conn_interval_max, + arg_5->set_default_rate_parameters.subrate_min, + arg_5->set_default_rate_parameters.subrate_max, + arg_5->set_default_rate_parameters.max_latency, + arg_5->set_default_rate_parameters.continuation_number, + arg_5->set_default_rate_parameters.supervision_timeout, + arg_5->set_default_rate_parameters.min_ce_len, + arg_5->set_default_rate_parameters.max_ce_len); + break; + case BTC_GAP_BLE_READ_MIN_SUPP_CONN_INTERVAL: + BTA_DmBleGapReadMinSuppConnInterval(); + break; +#endif // #if (BLE_FEAT_SHORTER_CONN_INTERVALS == TRUE) +#if (BLE_FEAT_LE_UTP == TRUE) + case BTC_GAP_BLE_ENABLE_UTP_OTA_MODE: + BTA_DmBleGapEnableUtpOtaMode(arg_5->enable_utp_ota_mode.enable); + break; + case BTC_GAP_BLE_UTP_SEND: + BTA_DmBleGapUtpSend(arg_5->utp_send.data_len, arg_5->utp_send.data); + break; +#endif // #if (BLE_FEAT_LE_UTP == TRUE) #if (BT_BLE_FEAT_PAWR_EN == TRUE) case BTC_GAP_BLE_SET_PA_SUBEVT_DATA: BTA_DmBleGapSetPASubevtData(arg_5->per_adv_subevent_data_params.adv_handle, arg_5->per_adv_subevent_data_params.num_subevents_with_data, (uint8_t *)(arg_5->per_adv_subevent_data_params.subevent_params)); @@ -3366,6 +3798,15 @@ void btc_gap_ble_call_handler(btc_msg_t *msg) BTA_DmBleGapCsProcEnable(arg_5->cs_procedure_enable_params.conn_handle, arg_5->cs_procedure_enable_params.config_id, arg_5->cs_procedure_enable_params.enable); break; #endif // (BT_BLE_FEAT_CHANNEL_SOUNDING == TRUE) +#if (BT_BLE_FEAT_CS_SECURITY_REQUIREMENTS == TRUE) + case BTC_GAP_BLE_CS_SET_SECURITY_REQUIREMENTS: + BTA_DmBleGapCsSetSecurityRequirements(arg_5->cs_set_security_requirements_params.conn_handle, + arg_5->cs_set_security_requirements_params.cs_security_requirements); + break; + case BTC_GAP_BLE_CS_SET_DEFAULT_SECURITY_REQUIREMENTS: + BTA_DmBleGapCsSetDefaultSecurityRequirements(arg_5->cs_set_default_security_requirements_params.cs_security_requirements); + break; +#endif // (BT_BLE_FEAT_CS_SECURITY_REQUIREMENTS == TRUE) #if (BT_GATTS_KEY_MATERIAL_CHAR == TRUE) case BTC_GAP_BLE_ACT_SET_KEY_MATERIAL: BTA_DmBleSetKeyMaterial(arg->set_key_material.session_key, arg->set_key_material.iv); diff --git a/components/bt/host/bluedroid/btc/profile/std/gatt/btc_gattc.c b/components/bt/host/bluedroid/btc/profile/std/gatt/btc_gattc.c index 4882584baa4..21f1c8cbf52 100644 --- a/components/bt/host/bluedroid/btc/profile/std/gatt/btc_gattc.c +++ b/components/bt/host/bluedroid/btc/profile/std/gatt/btc_gattc.c @@ -203,7 +203,7 @@ static void btc_gattc_cback(tBTA_GATTC_EVT event, tBTA_GATTC *p_data) static void btc_gattc_app_register(btc_ble_gattc_args_t *arg) { - tBT_UUID app_uuid; + tBT_UUID app_uuid = {0}; app_uuid.len = 2; app_uuid.uu.uuid16 = arg->app_reg.app_id; BTA_GATTC_AppRegister(&app_uuid, btc_gattc_cback); @@ -991,8 +991,6 @@ void btc_gattc_cb_handler(btc_msg_t *msg) case BTA_GATTC_CLOSE_EVT: { tBTA_GATTC_CLOSE *close = &arg->close; - // Free gattc clcb in BTC task to avoid race condition - bta_gattc_clcb_dealloc_by_conn_id(close->conn_id); gattc_if = close->client_if; param.close.status = close->status; param.close.conn_id = BTC_GATT_GET_CONN_ID(close->conn_id); diff --git a/components/bt/host/bluedroid/btc/profile/std/gatt/btc_gatts.c b/components/bt/host/bluedroid/btc/profile/std/gatt/btc_gatts.c index 6f9f50d3bd4..b816eb3d6d2 100644 --- a/components/bt/host/bluedroid/btc/profile/std/gatt/btc_gatts.c +++ b/components/bt/host/bluedroid/btc/profile/std/gatt/btc_gatts.c @@ -244,7 +244,7 @@ static void btc_gatts_act_create_attr_tab(esp_gatts_attr_db_t *gatts_attr_db, { uint16_t uuid = 0; future_t *future_p; - esp_ble_gatts_cb_param_t param; + esp_ble_gatts_cb_param_t param = {0}; param.add_attr_tab.status = ESP_GATT_OK; param.add_attr_tab.num_handle = max_nb_attr; @@ -509,11 +509,43 @@ static esp_gatt_status_t btc_gatts_check_valid_attr_tab(esp_gatts_attr_db_t *gat uint16_t uuid = 0; for(int i = 0; i < max_nb_attr; i++) { - if(gatts_attr_db[i].att_desc.uuid_length != ESP_UUID_LEN_16) { + const esp_attr_desc_t *desc = &gatts_attr_db[i].att_desc; + + /* Reject absurd attribute sizes regardless of row type. Mirrors the per-call + * guard in esp_ble_gatts_add_char_desc_param_check() so that the attribute + * table API enforces the same upper bound. */ + if (desc->max_length > ESP_GATT_MAX_ATTR_LEN || + desc->length > ESP_GATT_MAX_ATTR_LEN) { + BTC_TRACE_ERROR("%s attr[%d] len %u/max %u>%u", + __func__, i, + (unsigned)desc->length, + (unsigned)desc->max_length, + (unsigned)ESP_GATT_MAX_ATTR_LEN); + return ESP_GATT_INVALID_ATTR_LEN; + } + + /* When max_length is non-zero the row carries a stack-stored attribute + * value: gatts_add_char_descr()/gatts_add_characteristic() will allocate + * max_length bytes and memcpy length bytes into it. Reject length > max_length + * here so the table API matches esp_ble_gatts_add_char_desc_param_check(), + * fails fast with a clear error to the app, and also avoids the BTA-layer + * out-of-bounds read in BTA_GATTS_AddCharDescriptor() when the caller's + * source buffer is sized to max_length. Rows with max_length == 0 carry + * declarations (service UUID, char property byte, include-service descriptor) + * whose length is unrelated to max_length, so they are skipped. */ + if (desc->max_length != 0 && desc->length > desc->max_length) { + BTC_TRACE_ERROR("%s attr[%d] len %u>max %u", + __func__, i, + (unsigned)desc->length, + (unsigned)desc->max_length); + return ESP_GATT_INVALID_ATTR_LEN; + } + + if(desc->uuid_length != ESP_UUID_LEN_16) { continue; } - uuid = (gatts_attr_db[i].att_desc.uuid_p[1] << 8) + (gatts_attr_db[i].att_desc.uuid_p[0]); + uuid = (desc->uuid_p[1] << 8) + (desc->uuid_p[0]); switch(uuid) { case ESP_GATT_UUID_PRI_SERVICE: case ESP_GATT_UUID_SEC_SERVICE: @@ -560,6 +592,19 @@ static esp_gatt_status_t btc_gatts_check_valid_attr_tab(esp_gatts_attr_db_t *gat return ESP_GATT_INVALID_PDU; } } + + /* Same rule as GATTS_AddCharacteristic(): signed-write perm requires AUTH property */ + { + uint8_t char_property = (uint8_t)(*(uint8_t *)(gatts_attr_db[i].att_desc.value)); + uint16_t perm = gatts_attr_db[i + 1].att_desc.perm; + + if (((char_property & GATT_CHAR_PROP_BIT_AUTH) && !(perm & GATT_WRITE_SIGNED_PERM)) || + ((perm & GATT_WRITE_SIGNED_PERM) && !(char_property & GATT_CHAR_PROP_BIT_AUTH))) { + BTC_TRACE_ERROR("%s, Invalid char property=0x%02x perm=0x%04x at table index %d", + __func__, char_property, perm, i); + return ESP_GATT_ILLEGAL_PARAMETER; + } + } break; default: break; @@ -581,9 +626,56 @@ esp_gatt_status_t btc_gatts_show_local_database(void) return ESP_GATT_OK; } +/* BTA passes a pointer to the active tBTA_GATTS union member (often a small stack + * object). Only copy that member's size — sizeof(tBTA_GATTS) would read past it. */ +static int btc_gatts_cb_event_param_len(tBTA_GATTS_EVT event) +{ + switch (event) { + case BTA_GATTS_REG_EVT: + case BTA_GATTS_DEREG_EVT: + return (int)sizeof(tBTA_GATTS_REG_OPER); + case BTA_GATTS_READ_EVT: + case BTA_GATTS_WRITE_EVT: + case BTA_GATTS_EXEC_WRITE_EVT: + case BTA_GATTS_MTU_EVT: + case BTA_GATTS_CONF_EVT: + return (int)sizeof(tBTA_GATTS_REQ); + case BTA_GATTS_CREATE_EVT: + return (int)sizeof(tBTA_GATTS_CREATE); + case BTA_GATTS_ADD_INCL_SRVC_EVT: + case BTA_GATTS_ADD_CHAR_EVT: + case BTA_GATTS_ADD_CHAR_DESCR_EVT: + return (int)sizeof(tBTA_GATTS_ADD_RESULT); + case BTA_GATTS_DELELTE_EVT: + case BTA_GATTS_START_EVT: + case BTA_GATTS_STOP_EVT: + return (int)sizeof(tBTA_GATTS_SRVC_OPER); + case BTA_GATTS_SET_ATTR_VAL_EVT: + return (int)sizeof(tBAT_GATTS_ATTR_VAL_RESULT); + case BTA_GATTS_CONNECT_EVT: + case BTA_GATTS_DISCONNECT_EVT: + return (int)sizeof(tBTA_GATTS_CONN); + case BTA_GATTS_OPEN_EVT: + return (int)sizeof(tBTA_GATTS_OPEN); + case BTA_GATTS_CANCEL_OPEN_EVT: + return (int)sizeof(tBTA_GATTS_CANCEL_OPEN); + case BTA_GATTS_CLOSE_EVT: + return (int)sizeof(tBTA_GATTS_CLOSE); + case BTA_GATTS_LISTEN_EVT: + return (int)sizeof(tBTA_GATT_STATUS); + case BTA_GATTS_CONGEST_EVT: + return (int)sizeof(tBTA_GATTS_CONGEST); + case BTA_GATTS_SEND_SERVICE_CHANGE_EVT: + return (int)sizeof(tBTA_GATTS_SERVICE_CHANGE); + default: + return (int)sizeof(tBTA_GATTS); + } +} + static void btc_gatts_cb_param_copy_req(btc_msg_t *msg, void *p_dest, void *p_src) { uint16_t event = msg->act; + int copy_len = btc_gatts_cb_event_param_len((tBTA_GATTS_EVT)event); tBTA_GATTS *p_dest_data = (tBTA_GATTS *) p_dest; tBTA_GATTS *p_src_data = (tBTA_GATTS *) p_src; @@ -592,8 +684,7 @@ static void btc_gatts_cb_param_copy_req(btc_msg_t *msg, void *p_dest, void *p_sr return; } - // Copy basic structure first - memcpy(p_dest_data, p_src_data, sizeof(tBTA_GATTS)); + memcpy(p_dest_data, p_src_data, (size_t)copy_len); // Allocate buffer for request data if necessary switch (event) { @@ -609,6 +700,21 @@ static void btc_gatts_cb_param_copy_req(btc_msg_t *msg, void *p_dest, void *p_sr BTC_TRACE_ERROR("%s %d no mem\n", __func__, msg->act); } break; + case BTA_GATTS_CONF_EVT: + /* bta_gatts_indicate_handle frees req_data.value after returning from this + * callback; duplicate the buffer so the queued BTC handler does not UAF. */ + if (p_src_data->req_data.value != NULL && p_src_data->req_data.data_len > 0) { + p_dest_data->req_data.value = (uint8_t *)osi_malloc(p_src_data->req_data.data_len); + if (p_dest_data->req_data.value != NULL) { + memcpy(p_dest_data->req_data.value, p_src_data->req_data.value, + p_src_data->req_data.data_len); + } else { + BTC_TRACE_ERROR("%s CONF_EVT no mem", __func__); + p_dest_data->req_data.value = NULL; + p_dest_data->req_data.data_len = 0; + } + } + break; default: break; @@ -630,6 +736,10 @@ static void btc_gatts_cb_param_copy_free(btc_msg_t *msg) } break; case BTA_GATTS_CONF_EVT: + if (p_data && p_data->req_data.value) { + osi_free(p_data->req_data.value); + p_data->req_data.value = NULL; + } break; default: break; @@ -645,46 +755,58 @@ static void btc_gatts_inter_cb(tBTA_GATTS_EVT event, tBTA_GATTS *p_data) msg.sig = BTC_SIG_API_CB; msg.pid = BTC_PID_GATTS; msg.act = event; - if(btc_creat_tab_env.is_tab_creat_svc && btc_creat_tab_env.complete_future) { - switch(event) { - case BTA_GATTS_CREATE_EVT: { - //save the service handle to the btc module after used - //the attribute table method to creat a service + if (btc_creat_tab_env.is_tab_creat_svc && btc_creat_tab_env.complete_future) { + void *result = FUTURE_SUCCESS; + uint16_t index = btc_creat_tab_env.handle_idx; + + switch (event) { + case BTA_GATTS_CREATE_EVT: + if (p_data->create.status != BTA_GATT_OK || p_data->create.service_id == 0) { + result = FUTURE_FAIL; + } else { + /* save the service handle after the attribute table method creates a service */ bta_to_btc_uuid(&btc_creat_tab_env.svc_uuid, &p_data->create.uuid); - uint16_t index = btc_creat_tab_env.handle_idx; btc_creat_tab_env.svc_start_hdl = p_data->create.service_id; btc_creat_tab_env.handles[index] = p_data->create.service_id; - break; } - case BTA_GATTS_ADD_INCL_SRVC_EVT: { - uint16_t index = btc_creat_tab_env.handle_idx; + break; + case BTA_GATTS_ADD_INCL_SRVC_EVT: + if (p_data->add_result.status != BTA_GATT_OK || p_data->add_result.attr_id == 0) { + result = FUTURE_FAIL; + } else { btc_creat_tab_env.handles[index] = p_data->add_result.attr_id; - break; } - case BTA_GATTS_ADD_CHAR_EVT: { - uint16_t index = btc_creat_tab_env.handle_idx; + break; + case BTA_GATTS_ADD_CHAR_EVT: + if (p_data->add_result.status != BTA_GATT_OK || p_data->add_result.attr_id == 0) { + result = FUTURE_FAIL; + } else { btc_creat_tab_env.handles[index] = p_data->add_result.attr_id - 1; if (index + 1 < btc_creat_tab_env.num_handle) { - btc_creat_tab_env.handles[index+1] = p_data->add_result.attr_id; + btc_creat_tab_env.handles[index + 1] = p_data->add_result.attr_id; } else { - BTC_TRACE_ERROR("%s handles[%d+1] out of bounds (num_handle=%d)", - __func__, index, btc_creat_tab_env.num_handle); + result = FUTURE_FAIL; } - break; } - case BTA_GATTS_ADD_CHAR_DESCR_EVT: { - uint16_t index = btc_creat_tab_env.handle_idx; + break; + case BTA_GATTS_ADD_CHAR_DESCR_EVT: + if (p_data->add_result.status != BTA_GATT_OK || p_data->add_result.attr_id == 0) { + result = FUTURE_FAIL; + } else { btc_creat_tab_env.handles[index] = p_data->add_result.attr_id; - break; } - default: - break; + break; + default: + break; } - future_ready(btc_creat_tab_env.complete_future, FUTURE_SUCCESS); + if (result == FUTURE_FAIL) { + BTC_TRACE_ERROR("%s create_attr_tab failed, event=%d", __func__, event); + } + future_ready(btc_creat_tab_env.complete_future, result); return; } - status = btc_transfer_context(&msg, p_data, sizeof(tBTA_GATTS), + status = btc_transfer_context(&msg, p_data, btc_gatts_cb_event_param_len(event), btc_gatts_cb_param_copy_req, btc_gatts_cb_param_copy_free); if (status != BT_STATUS_SUCCESS) { @@ -761,7 +883,7 @@ void btc_gatts_call_handler(btc_msg_t *msg) arg->send_ind.value_len, arg->send_ind.value, arg->send_ind.need_confirm); break; case BTC_GATTS_ACT_SEND_RESPONSE: { - esp_ble_gatts_cb_param_t param; + esp_ble_gatts_cb_param_t param = {0}; esp_gatt_rsp_t *p_rsp = arg->send_rsp.rsp; if (p_rsp) { @@ -1074,7 +1196,7 @@ void btc_gatts_cb_handler(btc_msg_t *msg) void btc_congest_callback(tBTA_GATTS *param) { - esp_ble_gatts_cb_param_t esp_param; + esp_ble_gatts_cb_param_t esp_param = {0}; esp_gatt_if_t gatts_if = BTC_GATT_GET_GATT_IF(param->congest.conn_id); esp_param.congest.conn_id = BTC_GATT_GET_CONN_ID(param->congest.conn_id); esp_param.congest.congested = param->congest.congested; diff --git a/components/bt/host/bluedroid/btc/profile/std/hf_ag/bta_ag_co.c b/components/bt/host/bluedroid/btc/profile/std/hf_ag/bta_ag_co.c index 117a1c5ce3b..a127b2ff7d9 100644 --- a/components/bt/host/bluedroid/btc/profile/std/hf_ag/bta_ag_co.c +++ b/components/bt/host/bluedroid/btc/profile/std/hf_ag/bta_ag_co.c @@ -293,16 +293,18 @@ static void bta_ag_decode_msbc_frame(UINT8 **data, UINT8 *length, BOOLEAN is_bad { OI_STATUS status; const OI_BYTE *zero_signal_frame_data; - UINT8 zero_signal_frame_len = BTM_MSBC_FRAME_DATA_SIZE; + OI_UINT32 frame_len = *length; + OI_UINT32 zero_signal_frame_len = BTM_MSBC_FRAME_DATA_SIZE; UINT32 sbc_raw_data_size = HF_SBC_DEC_RAW_DATA_SIZE; if (is_bad_frame) { status = OI_CODEC_SBC_CHECKSUM_MISMATCH; } else { status = OI_CODEC_SBC_DecodeFrame(&bta_ag_co_cb.decoder_context, (const OI_BYTE **)data, - (OI_UINT32 *)length, + &frame_len, (OI_INT16 *)bta_ag_co_cb.decode_raw_data, (OI_UINT32 *)&sbc_raw_data_size); + *length = (UINT8)frame_len; } // PLC_INCLUDED will be set to TRUE when enabling Wide Band Speech @@ -329,7 +331,7 @@ static void bta_ag_decode_msbc_frame(UINT8 **data, UINT8 *length, BOOLEAN is_bad zero_signal_frame_data = sbc_plc_zero_signal_frame(); sbc_raw_data_size = HF_SBC_DEC_RAW_DATA_SIZE; status = OI_CODEC_SBC_DecodeFrame(&bta_ag_co_cb.decoder_context, &zero_signal_frame_data, - (OI_UINT32 *)&zero_signal_frame_len, + &zero_signal_frame_len, (OI_INT16 *)bta_ag_co_cb.decode_raw_data, (OI_UINT32 *)&sbc_raw_data_size); sbc_plc_bad_frame(&(bta_hf_ct_plc.plc_state), bta_ag_co_cb.decode_raw_data, bta_hf_ct_plc.sbc_plc_out); @@ -693,6 +695,7 @@ void bta_ag_sco_co_in_data(BT_HDR *p_buf, tBTM_SCO_DATA_FLAG status) memcpy(bta_ag_co_cb.decode_msbc_data + BTM_MSBC_FRAME_SIZE / 2, p, pkt_size); } data = bta_ag_co_cb.decode_msbc_data; + pkt_size += BTM_MSBC_FRAME_SIZE / 2; bta_ag_decode_msbc_frame(&data, &pkt_size, bta_ag_co_cb.is_bad_frame); bta_ag_co_cb.is_bad_frame = false; } diff --git a/components/bt/host/bluedroid/btc/profile/std/hf_ag/btc_hf_ag.c b/components/bt/host/bluedroid/btc/profile/std/hf_ag/btc_hf_ag.c index 1bea764c4b5..73d048f8139 100644 --- a/components/bt/host/bluedroid/btc/profile/std/hf_ag/btc_hf_ag.c +++ b/components/bt/host/bluedroid/btc/profile/std/hf_ag/btc_hf_ag.c @@ -159,6 +159,14 @@ do { ************************************************************************************/ static int btc_hf_idx_by_bdaddr(bt_bdaddr_t *bd_addr) { +#if HFP_DYNAMIC_MEMORY == TRUE + if (hf_local_param_ptr == NULL) { + return BTC_HF_INVALID_IDX; + } +#endif + if (bd_addr == NULL || !hf_local_param.initialized || hf_local_param.btc_hf_cb == NULL) { + return BTC_HF_INVALID_IDX; + } for (int i = 0; i < btc_max_hf_clients; ++i) { if (bdcmp(bd_addr->address, hf_local_param.btc_hf_cb[i].connected_bda.address) == 0) { return i; @@ -169,9 +177,16 @@ static int btc_hf_idx_by_bdaddr(bt_bdaddr_t *bd_addr) static int btc_hf_find_free_idx(void) { +#if HFP_DYNAMIC_MEMORY == TRUE + if (hf_local_param_ptr == NULL) { + return BTC_HF_INVALID_IDX; + } +#endif + if (!hf_local_param.initialized || hf_local_param.btc_hf_cb == NULL) { + return BTC_HF_INVALID_IDX; + } for (int idx = 0; idx < btc_max_hf_clients; ++idx) { - if (hf_local_param.initialized && - hf_local_param.btc_hf_cb[idx].connection_state == ESP_HF_CONNECTION_STATE_DISCONNECTED) { + if (hf_local_param.btc_hf_cb[idx].connection_state == ESP_HF_CONNECTION_STATE_DISCONNECTED) { return idx; } } @@ -192,6 +207,14 @@ static int btc_hf_latest_connected_idx(void) { struct timespec now, conn_time_delta; int latest_conn_idx = BTC_HF_INVALID_IDX; +#if HFP_DYNAMIC_MEMORY == TRUE + if (hf_local_param_ptr == NULL) { + return BTC_HF_INVALID_IDX; + } +#endif + if (!hf_local_param.initialized || hf_local_param.btc_hf_cb == NULL) { + return BTC_HF_INVALID_IDX; + } clock_gettime(CLOCK_MONOTONIC, &now); conn_time_delta.tv_sec = now.tv_sec; @@ -938,7 +961,7 @@ bt_status_t btc_hf_ci_sco_data(void) return status; } -bool btc_hf_ag_audio_data_send(uint16_t sync_conn_hdl, uint8_t *p_buff_start, uint8_t *p_data, uint8_t data_len) +bt_status_t btc_hf_ag_audio_data_send(uint16_t sync_conn_hdl, uint8_t *p_buff_start, uint8_t *p_data, uint8_t data_len) { #if (BTM_SCO_HCI_INCLUDED == TRUE) && (BTA_HFP_EXT_CODEC == TRUE) /* currently, sync_conn_hdl is not used */ @@ -946,10 +969,10 @@ bool btc_hf_ag_audio_data_send(uint16_t sync_conn_hdl, uint8_t *p_buff_start, ui CHECK_HF_SLC_CONNECTED(idx); if (idx != BTC_HF_INVALID_IDX) { BTA_AgAudioDataSend(hf_local_param.btc_hf_cb[idx].handle, p_buff_start, p_data, data_len); - return true; + return BT_STATUS_SUCCESS; } #endif - return false; + return BT_STATUS_FAIL; } /************************************************************************************ diff --git a/components/bt/host/bluedroid/btc/profile/std/hf_client/bta_hf_client_co.c b/components/bt/host/bluedroid/btc/profile/std/hf_client/bta_hf_client_co.c index d02d68d5929..81fc431d090 100644 --- a/components/bt/host/bluedroid/btc/profile/std/hf_client/bta_hf_client_co.c +++ b/components/bt/host/bluedroid/btc/profile/std/hf_client/bta_hf_client_co.c @@ -419,16 +419,18 @@ uint32_t bta_hf_client_sco_co_out_data(UINT8 *p_buf) static void bta_hf_client_decode_msbc_frame(UINT8 **data, UINT8 *length, BOOLEAN is_bad_frame){ OI_STATUS status; const OI_BYTE *zero_signal_frame_data; - UINT8 zero_signal_frame_len = BTM_MSBC_FRAME_DATA_SIZE; + OI_UINT32 frame_len = *length; + OI_UINT32 zero_signal_frame_len = BTM_MSBC_FRAME_DATA_SIZE; UINT32 sbc_raw_data_size = HF_SBC_DEC_RAW_DATA_SIZE; if (is_bad_frame){ status = OI_CODEC_SBC_CHECKSUM_MISMATCH; } else { status = OI_CODEC_SBC_DecodeFrame(&bta_hf_client_co_cb.decoder_context, (const OI_BYTE **)data, - (OI_UINT32 *)length, + &frame_len, (OI_INT16 *)bta_hf_client_co_cb.decode_raw_data, (OI_UINT32 *)&sbc_raw_data_size); + *length = (UINT8)frame_len; } // PLC_INCLUDED will be set to TRUE when enabling Wide Band Speech @@ -450,7 +452,7 @@ static void bta_hf_client_decode_msbc_frame(UINT8 **data, UINT8 *length, BOOLEAN zero_signal_frame_data = sbc_plc_zero_signal_frame(); sbc_raw_data_size = HF_SBC_DEC_RAW_DATA_SIZE; status = OI_CODEC_SBC_DecodeFrame(&bta_hf_client_co_cb.decoder_context, &zero_signal_frame_data, - (OI_UINT32 *)&zero_signal_frame_len, + &zero_signal_frame_len, (OI_INT16 *)bta_hf_client_co_cb.decode_raw_data, (OI_UINT32 *)&sbc_raw_data_size); sbc_plc_bad_frame(&(bta_hf_ct_plc.plc_state), bta_hf_client_co_cb.decode_raw_data, bta_hf_ct_plc.sbc_plc_out); @@ -523,6 +525,13 @@ void bta_hf_client_sco_co_in_data(BT_HDR *p_buf, tBTM_SCO_DATA_FLAG status) osi_free(p_buf); } else { BT_HDR *p_new_buf = osi_calloc(sizeof(BT_HDR) + BTM_MSBC_FRAME_SIZE); + if (p_new_buf == NULL) { + APPL_TRACE_ERROR("bta_hf_client_sco_co_in_data ENOMEM"); + osi_free(p_buf); + bta_hf_client_co_cb.rx_first_pkt = !bta_hf_client_co_cb.rx_first_pkt; + bta_hf_client_co_cb.is_bad_frame = false; + return; + } p_new_buf->offset = 0; UINT8 *p_data = (UINT8 *)(p_new_buf + 1) + p_new_buf->offset; memcpy(p_data, bta_hf_client_co_cb.rx_half_msbc_data, BTM_MSBC_FRAME_SIZE / 2); @@ -589,6 +598,7 @@ void bta_hf_client_sco_co_in_data(BT_HDR *p_buf, tBTM_SCO_DATA_FLAG status) } data = bta_hf_client_co_cb.decode_msbc_data; + pkt_size += BTM_MSBC_FRAME_SIZE / 2; bta_hf_client_decode_msbc_frame(&data, &pkt_size, bta_hf_client_co_cb.is_bad_frame); bta_hf_client_co_cb.is_bad_frame = false; } diff --git a/components/bt/host/bluedroid/btc/profile/std/include/btc_gap_ble.h b/components/bt/host/bluedroid/btc/profile/std/include/btc_gap_ble.h index 306adb09a14..41fc24c7a74 100644 --- a/components/bt/host/bluedroid/btc/profile/std/include/btc_gap_ble.h +++ b/components/bt/host/bluedroid/btc/profile/std/include/btc_gap_ble.h @@ -147,6 +147,26 @@ typedef enum { BTC_GAP_BLE_READ_MONITOR_ADV_LIST_SIZE, BTC_GAP_BLE_ENABLE_MONITOR_ADV, #endif // #if (BLE_FEAT_ADV_MONITOR == TRUE) +#if (BLE_FEAT_DBAF == TRUE) + BTC_GAP_BLE_SET_DECISION_DATA, + BTC_GAP_BLE_SET_DECISION_INSTRUCTIONS, +#endif // #if (BLE_FEAT_DBAF == TRUE) +#if (BLE_FEAT_FRAME_SPACE_UPDATE == TRUE) + BTC_GAP_BLE_FRAME_SPACE_UPDATE, +#endif // #if (BLE_FEAT_FRAME_SPACE_UPDATE == TRUE) +#if (BLE_FEAT_LL_EXT_FEAT == TRUE) + BTC_GAP_BLE_READ_ALL_LOCAL_SUPP_FEAT, + BTC_GAP_BLE_READ_ALL_REMOTE_FEAT, +#endif // #if (BLE_FEAT_LL_EXT_FEAT == TRUE) +#if (BLE_FEAT_SHORTER_CONN_INTERVALS == TRUE) + BTC_GAP_BLE_CONNECTION_RATE_REQUEST, + BTC_GAP_BLE_SET_DEFAULT_RATE_PARAMETERS, + BTC_GAP_BLE_READ_MIN_SUPP_CONN_INTERVAL, +#endif // #if (BLE_FEAT_SHORTER_CONN_INTERVALS == TRUE) +#if (BLE_FEAT_LE_UTP == TRUE) + BTC_GAP_BLE_ENABLE_UTP_OTA_MODE, + BTC_GAP_BLE_UTP_SEND, +#endif // #if (BLE_FEAT_LE_UTP == TRUE) BTC_GAP_BLE_READ_CHANNEL_MAP, #if (BT_BLE_FEAT_PAWR_EN == TRUE) BTC_GAP_BLE_SET_PA_SUBEVT_DATA, @@ -167,6 +187,10 @@ typedef enum { BTC_GAP_BLE_CS_SET_PROCEDURE_PARAMS, BTC_GAP_BLE_CS_PROCEDURE_ENABLE, #endif // (BT_BLE_FEAT_CHANNEL_SOUNDING == TRUE) +#if (BT_BLE_FEAT_CS_SECURITY_REQUIREMENTS == TRUE) + BTC_GAP_BLE_CS_SET_SECURITY_REQUIREMENTS, + BTC_GAP_BLE_CS_SET_DEFAULT_SECURITY_REQUIREMENTS, +#endif // (BT_BLE_FEAT_CS_SECURITY_REQUIREMENTS == TRUE) #if (BT_GATTS_KEY_MATERIAL_CHAR == TRUE) BTC_GAP_BLE_ACT_SET_KEY_MATERIAL, #endif @@ -588,6 +612,69 @@ typedef union { uint8_t enable; } enable_monitor_adv; #endif // #if (BLE_FEAT_ADV_MONITOR == TRUE) +#if (BLE_FEAT_DBAF == TRUE) + struct set_decision_data_args { + uint8_t adv_handle; + uint8_t decision_type_flags; + uint8_t data_len; + uint8_t *data; + } set_decision_data; + struct set_decision_instructions_args { + uint8_t num_tests; + uint8_t *test_flags; + uint8_t *test_fields; + uint8_t *test_params; + } set_decision_instructions; +#endif // #if (BLE_FEAT_DBAF == TRUE) +#if (BLE_FEAT_FRAME_SPACE_UPDATE == TRUE) + struct frame_space_update_args { + uint16_t conn_handle; + uint16_t frame_space_min; + uint16_t frame_space_max; + uint8_t phys; + uint16_t spacing_types; + } frame_space_update; +#endif // #if (BLE_FEAT_FRAME_SPACE_UPDATE == TRUE) +#if (BLE_FEAT_LL_EXT_FEAT == TRUE) + struct read_all_remote_feat_args { + uint16_t conn_handle; + uint8_t page_requested; + } read_all_remote_feat; +#endif // #if (BLE_FEAT_LL_EXT_FEAT == TRUE) +#if (BLE_FEAT_SHORTER_CONN_INTERVALS == TRUE) + struct connection_rate_request_args { + uint16_t conn_handle; + uint16_t conn_interval_min; + uint16_t conn_interval_max; + uint16_t subrate_min; + uint16_t subrate_max; + uint16_t max_latency; + uint16_t continuation_number; + uint16_t supervision_timeout; + uint16_t min_ce_len; + uint16_t max_ce_len; + } connection_rate_request; + struct set_default_rate_parameters_args { + uint16_t conn_interval_min; + uint16_t conn_interval_max; + uint16_t subrate_min; + uint16_t subrate_max; + uint16_t max_latency; + uint16_t continuation_number; + uint16_t supervision_timeout; + uint16_t min_ce_len; + uint16_t max_ce_len; + } set_default_rate_parameters; +#endif // #if (BLE_FEAT_SHORTER_CONN_INTERVALS == TRUE) +#if (BLE_FEAT_LE_UTP == TRUE) + struct enable_utp_ota_mode_args { + uint8_t enable; + } enable_utp_ota_mode; + struct utp_send_args { + uint8_t data_len; + uint8_t *data; + } utp_send; +#endif // #if (BLE_FEAT_LE_UTP == TRUE) #if (BT_BLE_FEAT_PAWR_EN == TRUE) // BTC_GAP_BLE_SET_PA_SUBEVT_DATA struct per_adv_subevent_data_params_args { @@ -715,6 +802,16 @@ typedef union { uint8_t enable; } cs_procedure_enable_params; #endif // (BT_BLE_FEAT_CHANNEL_SOUNDING == TRUE) +#if (BT_BLE_FEAT_CS_SECURITY_REQUIREMENTS == TRUE) + struct cs_set_security_requirements_params_args { + uint16_t conn_handle; + uint64_t cs_security_requirements; + } cs_set_security_requirements_params; + + struct cs_set_default_security_requirements_params_args { + uint64_t cs_security_requirements; + } cs_set_default_security_requirements_params; +#endif // (BT_BLE_FEAT_CS_SECURITY_REQUIREMENTS == TRUE) } btc_ble_5_gap_args_t; #endif // #if (BLE_50_FEATURE_SUPPORT == TRUE) diff --git a/components/bt/host/bluedroid/btc/profile/std/include/btc_hf_ag.h b/components/bt/host/bluedroid/btc/profile/std/include/btc_hf_ag.h index 8a11f5b9fa8..aae9356b87f 100644 --- a/components/bt/host/bluedroid/btc/profile/std/include/btc_hf_ag.h +++ b/components/bt/host/bluedroid/btc/profile/std/include/btc_hf_ag.h @@ -254,7 +254,7 @@ void btc_hf_arg_deep_free(btc_msg_t *msg); bt_status_t btc_hf_ci_sco_data(void); -bool btc_hf_ag_audio_data_send(uint16_t sync_conn_hdl, uint8_t *p_buff_start, uint8_t *p_data, uint8_t data_len); +bt_status_t btc_hf_ag_audio_data_send(uint16_t sync_conn_hdl, uint8_t *p_buff_start, uint8_t *p_data, uint8_t data_len); void btc_hf_get_profile_status(esp_hf_profile_status_t *param); #endif // BTC_HF_INCLUDED == TRUE diff --git a/components/bt/host/bluedroid/btc/profile/std/iso/btc_iso_ble.c b/components/bt/host/bluedroid/btc/profile/std/iso/btc_iso_ble.c index 6376a3ac1fb..60384a56860 100644 --- a/components/bt/host/bluedroid/btc/profile/std/iso/btc_iso_ble.c +++ b/components/bt/host/bluedroid/btc/profile/std/iso/btc_iso_ble.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -29,9 +29,10 @@ static void btc_ble_iso_callback(tBTM_BLE_ISO_EVENT event, { esp_ble_iso_cb_param_t param = {0}; bt_status_t ret; - btc_msg_t msg; + btc_msg_t msg = {0}; msg.sig = BTC_SIG_API_CB; msg.pid = BTC_PID_ISO_BLE; + msg.act = ESP_BLE_ISO_EVT_MAX; switch(event) { #if (BLE_FEAT_ISO_BIG_BROADCASTER_EN == TRUE) @@ -208,6 +209,11 @@ static void btc_ble_iso_callback(tBTM_BLE_ISO_EVENT event, break; } + if (msg.act == ESP_BLE_ISO_EVT_MAX) { + BTC_TRACE_ERROR("%s unk ISO evt %d", __func__, event); + return; + } + ret = btc_transfer_context(&msg, ¶m, sizeof(esp_ble_iso_cb_param_t), NULL, NULL); @@ -341,13 +347,14 @@ void btc_iso_ble_call_handler(btc_msg_t *msg) (uint8_t *)&set_cig_params->cis_params[0]); break; } - case BTC_ISO_ACT_SET_CIG_PARAMS_TEST: + case BTC_ISO_ACT_SET_CIG_PARAMS_TEST: { struct set_cig_params_test_arg *set_cig_params_test = (struct set_cig_params_test_arg *)arg; BTA_DmBleIsoSetCigParamsTest(set_cig_params_test->cig_id, set_cig_params_test->sdu_int_c_to_p, set_cig_params_test->sdu_int_p_to_c, set_cig_params_test->ft_c_to_p, set_cig_params_test->ft_p_to_c, set_cig_params_test->iso_interval, set_cig_params_test->worse_case_SCA, set_cig_params_test->packing, set_cig_params_test->framing, set_cig_params_test->cis_cnt, (uint8_t *)&set_cig_params_test->cis_params_test[0]); break; + } case BTC_ISO_ACT_CREATE_CIS: { struct creat_cis_arg * create_cis = (struct creat_cis_arg *)arg; BTA_DmBleIsoCreateCis(create_cis->cis_count, (uint8_t *)&create_cis->cis_hdls[0]); diff --git a/components/bt/host/bluedroid/common/include/common/bluedroid_user_config.h b/components/bt/host/bluedroid/common/include/common/bluedroid_user_config.h index 8434e93ac7d..112bddc8b23 100644 --- a/components/bt/host/bluedroid/common/include/common/bluedroid_user_config.h +++ b/components/bt/host/bluedroid/common/include/common/bluedroid_user_config.h @@ -32,6 +32,12 @@ #define UC_BT_CLASSIC_ENABLED FALSE #endif +#ifdef CONFIG_BT_CLASSIC_MAX_RECONNECT_ON_COLLISION +#define UC_BT_CLASSIC_MAX_RECONNECT_ON_COLLISION CONFIG_BT_CLASSIC_MAX_RECONNECT_ON_COLLISION +#else +#define UC_BT_CLASSIC_MAX_RECONNECT_ON_COLLISION 5 +#endif + //A2DP #ifdef CONFIG_BT_A2DP_ENABLE #define UC_BT_A2DP_ENABLED CONFIG_BT_A2DP_ENABLE @@ -395,12 +401,48 @@ #define UC_BT_BLE_FEAT_CHANNEL_SOUNDING FALSE #endif +#ifdef CONFIG_BT_BLE_FEAT_CS_SECURITY_REQUIREMENTS +#define UC_BT_BLE_FEAT_CS_SECURITY_REQUIREMENTS CONFIG_BT_BLE_FEAT_CS_SECURITY_REQUIREMENTS +#else +#define UC_BT_BLE_FEAT_CS_SECURITY_REQUIREMENTS FALSE +#endif + #ifdef CONFIG_BT_BLE_FEAT_ADV_MONITOR #define UC_BT_BLE_FEAT_ADV_MONITOR CONFIG_BT_BLE_FEAT_ADV_MONITOR #else #define UC_BT_BLE_FEAT_ADV_MONITOR FALSE #endif +#ifdef CONFIG_BT_BLE_FEAT_DBAF +#define UC_BT_BLE_FEAT_DBAF CONFIG_BT_BLE_FEAT_DBAF +#else +#define UC_BT_BLE_FEAT_DBAF FALSE +#endif + +#ifdef CONFIG_BT_BLE_FEAT_FRAME_SPACE_UPDATE +#define UC_BT_BLE_FEAT_FRAME_SPACE_UPDATE CONFIG_BT_BLE_FEAT_FRAME_SPACE_UPDATE +#else +#define UC_BT_BLE_FEAT_FRAME_SPACE_UPDATE FALSE +#endif + +#ifdef CONFIG_BT_BLE_FEAT_LL_EXT_FEAT +#define UC_BT_BLE_FEAT_LL_EXT_FEAT CONFIG_BT_BLE_FEAT_LL_EXT_FEAT +#else +#define UC_BT_BLE_FEAT_LL_EXT_FEAT FALSE +#endif + +#ifdef CONFIG_BT_BLE_FEAT_SHORTER_CONN_INTERVALS +#define UC_BT_BLE_FEAT_SHORTER_CONN_INTERVALS CONFIG_BT_BLE_FEAT_SHORTER_CONN_INTERVALS +#else +#define UC_BT_BLE_FEAT_SHORTER_CONN_INTERVALS FALSE +#endif + +#ifdef CONFIG_BT_BLE_FEAT_LE_UTP +#define UC_BT_BLE_FEAT_LE_UTP CONFIG_BT_BLE_FEAT_LE_UTP +#else +#define UC_BT_BLE_FEAT_LE_UTP FALSE +#endif + #ifdef CONFIG_BT_BLE_VENDOR_HCI_EN #define UC_BT_BLE_VENDOR_HCI_EN CONFIG_BT_BLE_VENDOR_HCI_EN #else diff --git a/components/bt/host/bluedroid/common/include/common/bt_common_types.h b/components/bt/host/bluedroid/common/include/common/bt_common_types.h index 7b96579e1df..eeadbde8d4f 100644 --- a/components/bt/host/bluedroid/common/include/common/bt_common_types.h +++ b/components/bt/host/bluedroid/common/include/common/bt_common_types.h @@ -14,7 +14,7 @@ #include "common/bt_defs.h" #include "osi/thread.h" -typedef void (* bluedroid_init_done_cb_t)(void); +typedef void (* bluedroid_init_done_cb_t)(bt_status_t status); typedef struct { uint8_t client_if; 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 cf3f8490b15..7f5a6df6bed 100644 --- a/components/bt/host/bluedroid/common/include/common/bt_target.h +++ b/components/bt/host/bluedroid/common/include/common/bt_target.h @@ -73,6 +73,8 @@ #define SDP_INCLUDED TRUE #define BTA_DM_QOS_INCLUDED TRUE +#define BR_EDR_MAX_RECONNECT_ON_COLLISION UC_BT_CLASSIC_MAX_RECONNECT_ON_COLLISION + #define ENC_KEY_SIZE_CTRL_MODE_NONE 0 #define ENC_KEY_SIZE_CTRL_MODE_STD 1 #define ENC_KEY_SIZE_CTRL_MODE_VSC 2 @@ -441,6 +443,12 @@ #define BT_BLE_FEAT_CHANNEL_SOUNDING FALSE #endif +#if (BT_BLE_FEAT_CHANNEL_SOUNDING == TRUE) && (UC_BT_BLE_FEAT_CS_SECURITY_REQUIREMENTS == TRUE) +#define BT_BLE_FEAT_CS_SECURITY_REQUIREMENTS TRUE +#else +#define BT_BLE_FEAT_CS_SECURITY_REQUIREMENTS FALSE +#endif + /* LE Monitor Advertisement (Bluetooth Core 6.0) */ #if (BLE_50_FEATURE_SUPPORT == TRUE) && (defined UC_BT_BLE_FEAT_ADV_MONITOR) && (UC_BT_BLE_FEAT_ADV_MONITOR == TRUE) #define BLE_FEAT_ADV_MONITOR TRUE @@ -448,6 +456,36 @@ #define BLE_FEAT_ADV_MONITOR FALSE #endif +#if (BLE_50_FEATURE_SUPPORT == TRUE) && (UC_BT_BLE_FEAT_DBAF == TRUE) +#define BLE_FEAT_DBAF TRUE +#else +#define BLE_FEAT_DBAF FALSE +#endif + +#if (BLE_50_FEATURE_SUPPORT == TRUE) && (UC_BT_BLE_FEAT_FRAME_SPACE_UPDATE == TRUE) +#define BLE_FEAT_FRAME_SPACE_UPDATE TRUE +#else +#define BLE_FEAT_FRAME_SPACE_UPDATE FALSE +#endif + +#if (BLE_50_FEATURE_SUPPORT == TRUE) && (UC_BT_BLE_FEAT_LL_EXT_FEAT == TRUE) +#define BLE_FEAT_LL_EXT_FEAT TRUE +#else +#define BLE_FEAT_LL_EXT_FEAT FALSE +#endif + +#if (BLE_50_FEATURE_SUPPORT == TRUE) && (UC_BT_BLE_FEAT_SHORTER_CONN_INTERVALS == TRUE) +#define BLE_FEAT_SHORTER_CONN_INTERVALS TRUE +#else +#define BLE_FEAT_SHORTER_CONN_INTERVALS FALSE +#endif + +#if (BLE_50_FEATURE_SUPPORT == TRUE) && (UC_BT_BLE_FEAT_LE_UTP == TRUE) +#define BLE_FEAT_LE_UTP TRUE +#else +#define BLE_FEAT_LE_UTP FALSE +#endif + #if (UC_BT_BLE_VENDOR_HCI_EN == TRUE) #define BLE_VENDOR_HCI_EN TRUE #else diff --git a/components/bt/host/bluedroid/hci/ble_hci_iso.c b/components/bt/host/bluedroid/hci/ble_hci_iso.c index 92db66d3110..8032f378793 100644 --- a/components/bt/host/bluedroid/hci/ble_hci_iso.c +++ b/components/bt/host/bluedroid/hci/ble_hci_iso.c @@ -17,6 +17,22 @@ * under the License. */ +/* + * ============================================================================ + * WARNING + * ============================================================================ + * NOTE: The code in this file is for INTERNAL TEMPORARY TESTING ONLY. + * + * - DO NOT use it in any product code or user application. + * - It is not part of the public/stable API and provides NO compatibility + * guarantees of any kind (behavior, ABI, function signatures, side effects). + * - It may be changed, refactored, or REMOVED at any time without notice. + * + * This file will be deleted in a future release. Any external dependency on + * the symbols defined here is unsupported and will break. + * ============================================================================ + */ + #include #include #include "hci/ble_hci_iso.h" diff --git a/components/bt/host/bluedroid/hci/hci_hal_h4.c b/components/bt/host/bluedroid/hci/hci_hal_h4.c index 5e7c116d545..abd7e75ccc5 100644 --- a/components/bt/host/bluedroid/hci/hci_hal_h4.c +++ b/components/bt/host/bluedroid/hci/hci_hal_h4.c @@ -34,6 +34,7 @@ #include "esp_bt.h" #endif #include "esp_bluedroid_hci.h" +#include "bt_common.h" #if (C2H_FLOW_CONTROL_INCLUDED == TRUE) #include "l2c_int.h" @@ -601,14 +602,20 @@ void bt_record_hci_data(uint8_t *data, uint16_t len) #endif // (BLE_50_FEATURE_SUPPORT == TRUE) )) { bt_hci_log_record_hci_adv(HCI_LOG_DATA_TYPE_ADV, &data[2], len - 2); +#if BT_HCI_INSIGHTS_INCLUDED + bt_hci_log_record_insights(HCI_LOG_DATA_TYPE_ADV, &data[2], len - 2); +#endif } else { uint8_t data_type; - if (data[0] == HCI_LOG_DATA_TYPE_ISO_DATA) { + if (data[0] == DATA_TYPE_ISO) { data_type = HCI_LOG_DATA_TYPE_ISO_DATA; } else { data_type = ((data[0] == 2) ? HCI_LOG_DATA_TYPE_C2H_ACL : data[0]); } bt_hci_log_record_hci_data(data_type, &data[1], len - 1); +#if BT_HCI_INSIGHTS_INCLUDED + bt_hci_log_record_insights(data_type, &data[1], len - 1); +#endif } #endif // (BT_HCI_LOG_INCLUDED == TRUE) } diff --git a/components/bt/host/bluedroid/hci/hci_layer.c b/components/bt/host/bluedroid/hci/hci_layer.c index 4bf553d0ce6..5c4a904b249 100644 --- a/components/bt/host/bluedroid/hci/hci_layer.c +++ b/components/bt/host/bluedroid/hci/hci_layer.c @@ -518,6 +518,18 @@ static bool filter_incoming_event(BT_HDR *packet) metadata = (hci_cmd_metadata_t *)(wait_entry->data); if (metadata->command_status_cb) { metadata->command_status_cb(status, &metadata->command, metadata->context); +#if ((BLE_50_FEATURE_SUPPORT == TRUE) || (BLE_42_FEATURE_SUPPORT == TRUE)) + /* No Command Complete follows a failed Command Status (Core Spec Vol 4 Part E). */ + if (status != HCI_SUCCESS) { + BlE_SYNC *sync_info = btsnd_hcic_ble_get_sync_info(); + if (!sync_info) { + HCI_TRACE_WARNING("%s sync_info is NULL. opcode = 0x%x", __func__, opcode); + } else if (sync_info->sync_sem && sync_info->opcode == opcode) { + osi_sem_give(&sync_info->sync_sem); + sync_info->opcode = 0; + } + } +#endif // #if ((BLE_50_FEATURE_SUPPORT == TRUE) || (BLE_42_FEATURE_SUPPORT == TRUE)) } goto intercepted; diff --git a/components/bt/host/bluedroid/main/bte_main.c b/components/bt/host/bluedroid/main/bte_main.c index d50544ced4f..4b4d7da6716 100644 --- a/components/bt/host/bluedroid/main/bte_main.c +++ b/components/bt/host/bluedroid/main/bte_main.c @@ -55,7 +55,7 @@ static const hci_t *hci; ** Static functions *******************************************************************************/ static void bte_main_disable(void); -static void bte_main_enable(void); +static bool bte_main_enable(void); /******************************************************************************* ** Externs @@ -92,7 +92,11 @@ int bte_main_boot_entry(bluedroid_init_done_cb_t cb) } //Enable HCI - bte_main_enable(); + if (!bte_main_enable()) { + osi_deinit(); + bluedroid_init_done_cb = NULL; + return -3; + } return 0; } @@ -108,13 +112,12 @@ int bte_main_boot_entry(bluedroid_init_done_cb_t cb) ******************************************************************************/ void bte_main_shutdown(void) { + bte_main_disable(); #if (BT_BLE_DYNAMIC_ENV_MEMORY == TRUE) free_controller_param(); #endif - bte_main_disable(); - osi_deinit(); } @@ -125,19 +128,23 @@ void bte_main_shutdown(void) ** Description BTE MAIN API - Creates all the BTE tasks. Should be called ** part of the Bluetooth stack enable sequence ** -** Returns None +** Returns true for success, otherwise false ** ******************************************************************************/ -static void bte_main_enable(void) +static bool bte_main_enable(void) { APPL_TRACE_DEBUG("Enable HCI\n"); if (hci_start_up()) { APPL_TRACE_ERROR("Start HCI Host Layer Failure\n"); - return; + return false; } //Now Test Case Not Supported BTU - BTU_StartUp(); + if (!BTU_StartUp()) { + hci_shut_down(); + return false; + } + return true; } /****************************************************************************** diff --git a/components/bt/host/bluedroid/stack/avct/avct_api.c b/components/bt/host/bluedroid/stack/avct/avct_api.c index 09ce64e8184..72cba350e41 100644 --- a/components/bt/host/bluedroid/stack/avct/avct_api.c +++ b/components/bt/host/bluedroid/stack/avct/avct_api.c @@ -118,6 +118,9 @@ void AVCT_Deregister(void) /* deregister PSM with L2CAP */ L2CA_Deregister(AVCT_PSM); +#if (AVCT_BROWSE_INCLUDED == TRUE) + L2CA_Deregister(AVCT_BR_PSM); +#endif } /******************************************************************************* @@ -173,9 +176,14 @@ UINT16 AVCT_CreateConn(UINT8 *p_handle, tAVCT_CC *p_cc, BD_ADDR peer_addr) if (result == AVCT_SUCCESS) { /* bind lcb to ccb */ + tAVCT_LCB_EVT evt; p_ccb->p_lcb = p_lcb; AVCT_TRACE_DEBUG("ch_state: %d", p_lcb->ch_state); - avct_lcb_event(p_lcb, AVCT_LCB_UL_BIND_EVT, (tAVCT_LCB_EVT *) &p_ccb); + evt.p_ccb = p_ccb; + avct_lcb_event(p_lcb, AVCT_LCB_UL_BIND_EVT, &evt); + if (!p_ccb->allocated) { + result = AVCT_NOT_OPEN; + } } } } @@ -212,7 +220,9 @@ UINT16 AVCT_RemoveConn(UINT8 handle) } /* send unbind event to lcb */ else { - avct_lcb_event(p_ccb->p_lcb, AVCT_LCB_UL_UNBIND_EVT, (tAVCT_LCB_EVT *) &p_ccb); + tAVCT_LCB_EVT evt; + evt.p_ccb = p_ccb; + avct_lcb_event(p_ccb->p_lcb, AVCT_LCB_UL_UNBIND_EVT, &evt); } return result; } diff --git a/components/bt/host/bluedroid/stack/avct/avct_l2c.c b/components/bt/host/bluedroid/stack/avct/avct_l2c.c index d6896e18095..f67c745f203 100644 --- a/components/bt/host/bluedroid/stack/avct/avct_l2c.c +++ b/components/bt/host/bluedroid/stack/avct/avct_l2c.c @@ -234,6 +234,9 @@ void avct_l2c_config_cfm_cback(UINT16 lcid, tL2CAP_CFG_INFO *p_cfg) if ((p_lcb = avct_lcb_by_lcid(lcid)) != NULL) { AVCT_TRACE_DEBUG("avct_l2c_config_cfm_cback: 0x%x, ch_state: %d, res: %d", lcid, p_lcb->ch_state, p_cfg->result); + if (p_lcb->conflict_lcid == lcid) { + return; + } /* if in correct state */ if (p_lcb->ch_state == AVCT_CH_CFG) { /* if result successful */ @@ -294,6 +297,10 @@ void avct_l2c_config_ind_cback(UINT16 lcid, tL2CAP_CFG_INFO *p_cfg) p_cfg->result = L2CAP_CFG_OK; L2CA_ConfigRsp(lcid, p_cfg); + if (p_lcb->conflict_lcid == lcid) { + return; + } + /* if first config ind */ if ((p_lcb->ch_flags & AVCT_L2C_CFG_IND_DONE) == 0) { /* update flags */ @@ -322,7 +329,7 @@ void avct_l2c_config_ind_cback(UINT16 lcid, tL2CAP_CFG_INFO *p_cfg) void avct_l2c_disconnect_ind_cback(UINT16 lcid, BOOLEAN ack_needed) { tAVCT_LCB *p_lcb; - UINT16 result = AVCT_RESULT_FAIL; + tAVCT_LCB_EVT evt; /* look up lcb for this channel */ if ((p_lcb = avct_lcb_by_lcid(lcid)) != NULL) { @@ -332,7 +339,13 @@ void avct_l2c_disconnect_ind_cback(UINT16 lcid, BOOLEAN ack_needed) L2CA_DisconnectRsp(lcid); } - avct_lcb_event(p_lcb, AVCT_LCB_LL_CLOSE_EVT, (tAVCT_LCB_EVT *) &result); + if (p_lcb->conflict_lcid == lcid) { + p_lcb->conflict_lcid = 0; + return; + } + + evt.result = AVCT_RESULT_FAIL; + avct_lcb_event(p_lcb, AVCT_LCB_LL_CLOSE_EVT, &evt); AVCT_TRACE_DEBUG("ch_state di: %d ", p_lcb->ch_state); } } @@ -356,6 +369,10 @@ void avct_l2c_disconnect_cfm_cback(UINT16 lcid, UINT16 result) if ((p_lcb = avct_lcb_by_lcid(lcid)) != NULL) { AVCT_TRACE_DEBUG("avct_l2c_disconnect_cfm_cback: 0x%x, ch_state: %d, res: %d", lcid, p_lcb->ch_state, result); + if (p_lcb->conflict_lcid == lcid) { + p_lcb->conflict_lcid = 0; + return; + } /* result value may be previously stored */ res = (p_lcb->ch_result != 0) ? p_lcb->ch_result : result; p_lcb->ch_result = 0; diff --git a/components/bt/host/bluedroid/stack/avct/avct_lcb.c b/components/bt/host/bluedroid/stack/avct/avct_lcb.c index ab19b1b4ce7..bac7922c164 100644 --- a/components/bt/host/bluedroid/stack/avct/avct_lcb.c +++ b/components/bt/host/bluedroid/stack/avct/avct_lcb.c @@ -364,7 +364,7 @@ void avct_lcb_dealloc(tAVCT_LCB *p_lcb, tAVCT_LCB_EVT *p_data) AVCT_TRACE_DEBUG("%s Freeing LCB", __func__); osi_free(p_lcb->p_rx_msg); - fixed_queue_free(p_lcb->tx_q, NULL); + fixed_queue_free(p_lcb->tx_q, osi_free_func); memset(p_lcb, 0, sizeof(tAVCT_LCB)); } diff --git a/components/bt/host/bluedroid/stack/avct/avct_lcb_act.c b/components/bt/host/bluedroid/stack/avct/avct_lcb_act.c index 97d81b497ee..41401f90cee 100644 --- a/components/bt/host/bluedroid/stack/avct/avct_lcb_act.c +++ b/components/bt/host/bluedroid/stack/avct/avct_lcb_act.c @@ -425,7 +425,12 @@ void avct_lcb_chnl_disc(tAVCT_LCB *p_lcb, tAVCT_LCB_EVT *p_data) { UNUSED(p_data); - L2CA_DisconnectReq(p_lcb->ch_lcid); + tAVCT_LCB_EVT evt; + + if (!L2CA_DisconnectReq(p_lcb->ch_lcid)) { + evt.result = AVCT_RESULT_FAIL; + avct_lcb_event(p_lcb, AVCT_LCB_LL_CLOSE_EVT, &evt); + } } /******************************************************************************* @@ -474,6 +479,7 @@ void avct_lcb_cong_ind(tAVCT_LCB *p_lcb, tAVCT_LCB_EVT *p_data) if (L2CA_DataWrite(p_lcb->ch_lcid, p_buf) == L2CAP_DW_CONGESTED) { p_lcb->cong = TRUE; + event = AVCT_CONG_IND_EVT; } } } diff --git a/components/bt/host/bluedroid/stack/avdt/avdt_ad.c b/components/bt/host/bluedroid/stack/avdt/avdt_ad.c index d6911e1f7cf..fbdaf6dd6ba 100644 --- a/components/bt/host/bluedroid/stack/avdt/avdt_ad.c +++ b/components/bt/host/bluedroid/stack/avdt/avdt_ad.c @@ -110,6 +110,11 @@ void avdt_ad_init(void) tAVDT_TC_TBL *p_tbl = avdt_cb.ad.tc_tbl; memset(&avdt_cb.ad, 0, sizeof(tAVDT_AD)); + /* 0 is a valid tc_tbl index; use invalid marker for unassigned LCIDs */ + for (i = 0; i < MAX_L2CAP_CHANNELS; i++) { + avdt_cb.ad.lcid_tbl[i] = 0xFF; + } + /* make sure the peer_mtu is a valid value */ for (i = 0; i < AVDT_NUM_TC_TBL; i++, p_tbl++) { p_tbl->peer_mtu = L2CAP_DEFAULT_MTU; diff --git a/components/bt/host/bluedroid/stack/avdt/avdt_api.c b/components/bt/host/bluedroid/stack/avdt/avdt_api.c index 81c0fa2e8b9..8d2272d67d9 100644 --- a/components/bt/host/bluedroid/stack/avdt/avdt_api.c +++ b/components/bt/host/bluedroid/stack/avdt/avdt_api.c @@ -940,6 +940,7 @@ UINT16 AVDT_WriteReqOpt(UINT8 handle, BT_HDR *p_pkt, UINT32 time_stamp, UINT8 m_ } /* map handle to scb */ if ((p_scb = avdt_scb_by_hdl(handle)) == NULL) { + osi_free(p_pkt); result = AVDT_BAD_HANDLE; } else { evt.apiwrite.p_buf = p_pkt; diff --git a/components/bt/host/bluedroid/stack/avdt/avdt_ccb.c b/components/bt/host/bluedroid/stack/avdt/avdt_ccb.c index bf8943aa54c..a18535d2093 100644 --- a/components/bt/host/bluedroid/stack/avdt/avdt_ccb.c +++ b/components/bt/host/bluedroid/stack/avdt/avdt_ccb.c @@ -383,6 +383,12 @@ tAVDT_CCB *avdt_ccb_alloc(BD_ADDR bd_addr) memcpy(p_ccb->peer_addr, bd_addr, BD_ADDR_LEN); p_ccb->cmd_q = fixed_queue_new(QUEUE_SIZE_MAX); p_ccb->rsp_q = fixed_queue_new(QUEUE_SIZE_MAX); + if (p_ccb->cmd_q == NULL || p_ccb->rsp_q == NULL) { + AVDT_TRACE_ERROR("avdt_ccb_alloc: queue alloc failed"); + avdt_ccb_dealloc(p_ccb, NULL); + p_ccb = NULL; + break; + } p_ccb->timer_entry.param = (UINT32) p_ccb; AVDT_TRACE_DEBUG("avdt_ccb_alloc %d\n", i); break; diff --git a/components/bt/host/bluedroid/stack/avdt/avdt_ccb_act.c b/components/bt/host/bluedroid/stack/avdt/avdt_ccb_act.c index dce084aa7c5..386b5fa40c6 100644 --- a/components/bt/host/bluedroid/stack/avdt/avdt_ccb_act.c +++ b/components/bt/host/bluedroid/stack/avdt/avdt_ccb_act.c @@ -712,6 +712,8 @@ void avdt_ccb_cmd_fail(tAVDT_CCB *p_ccb, tAVDT_CCB_EVT *p_data) UINT8 evt; tAVDT_SCB *p_scb; + memset(&msg, 0, sizeof(msg)); + if (p_ccb->p_curr_cmd != NULL) { if (p_ccb->p_curr_cmd->event < 1 || p_ccb->p_curr_cmd->event > AVDT_SIG_MAX) { osi_free(p_ccb->p_curr_cmd); diff --git a/components/bt/host/bluedroid/stack/avrc/avrc_api.c b/components/bt/host/bluedroid/stack/avrc/avrc_api.c index 004613b3508..a1099364ea0 100644 --- a/components/bt/host/bluedroid/stack/avrc/avrc_api.c +++ b/components/bt/host/bluedroid/stack/avrc/avrc_api.c @@ -64,6 +64,31 @@ static const UINT8 avrc_ctrl_event_map[] = { #define AVRC_OP_SUB_UNIT_INFO_RSP_LEN 8 #define AVRC_OP_REJ_MSG_LEN 11 +#if (AVRC_METADATA_INCLUDED == TRUE) +/****************************************************************************** +** +** Function avrc_free_far_cb +** +** Description Free fragmentation/reassembly buffers for a connection. +** +******************************************************************************/ +static void avrc_free_far_cb(UINT8 handle) +{ + if (handle >= AVCT_NUM_CONN) { + return; + } + if (avrc_cb.fcb[handle].p_fmsg) { + osi_free(avrc_cb.fcb[handle].p_fmsg); + avrc_cb.fcb[handle].p_fmsg = NULL; + } + avrc_cb.fcb[handle].frag_enabled = FALSE; + if (avrc_cb.rcb[handle].p_rmsg) { + osi_free(avrc_cb.rcb[handle].p_rmsg); + avrc_cb.rcb[handle].p_rmsg = NULL; + } +} +#endif /* (AVRC_METADATA_INCLUDED == TRUE) */ + /****************************************************************************** ** ** Function avrc_ctrl_cback @@ -83,6 +108,13 @@ static void avrc_ctrl_cback(UINT8 handle, UINT8 event, UINT16 result, return; } + /* Release pending fragment/reassembly buffers on disconnect */ + if (event == AVCT_DISCONNECT_CFM_EVT || event == AVCT_DISCONNECT_IND_EVT) { +#if (AVRC_METADATA_INCLUDED == TRUE) + avrc_free_far_cb(handle); +#endif + } + if (event <= AVRC_MAX_RCV_CTRL_EVT && avrc_cb.ccb[handle].p_ctrl_cback) { avrc_event = avrc_ctrl_event_map[event]; if (event == AVCT_CONNECT_CFM_EVT) { @@ -298,6 +330,11 @@ static BT_HDR *avrc_proc_vendor_command(UINT8 handle, UINT8 label, if (p_msg->company_id == AVRC_CO_METADATA) { switch (*p_data) { case AVRC_PDU_ABORT_CONTINUATION_RSP: + if (p_pkt->len < (AVRC_VENDOR_HDR_SIZE + AVRC_ABORT_CONTINUATION_RSP_CMD_SIZE)) { + status = AVRC_STS_INTERNAL_ERR; + abort_frag = TRUE; + break; + } /* aborted by CT - send accept response */ abort_frag = TRUE; p_begin = (UINT8 *)(p_pkt + 1) + p_pkt->offset; @@ -315,6 +352,11 @@ static BT_HDR *avrc_proc_vendor_command(UINT8 handle, UINT8 label, break; case AVRC_PDU_REQUEST_CONTINUATION_RSP: + if (p_pkt->len < (AVRC_VENDOR_HDR_SIZE + AVRC_REQUEST_CONTINUATION_RSP_CMD_SIZE)) { + status = AVRC_STS_INTERNAL_ERR; + abort_frag = TRUE; + break; + } if (*(p_data + 4) == p_fcb->frag_pdu) { avrc_send_continue_frag(handle, label); p_msg->hdr.opcode = AVRC_OP_DROP_N_FREE; @@ -435,18 +477,17 @@ static UINT8 avrc_proc_far_msg(UINT8 handle, UINT8 label, UINT8 cr, BT_HDR **pp_ /* Free original START packet, replace with pointer to reassembly buffer */ osi_free(p_pkt); *pp_pkt = p_rcb->p_rmsg; - } else { - /* Unable to allocate buffer for fragmented avrc message. Reuse START - buffer for reassembly (re-assembled message may fit into ACL buf) */ - AVRC_TRACE_DEBUG ("Unable to allocate buffer for fragmented avrc message, \ - reusing START buffer for reassembly"); - p_rcb->rasm_offset = p_pkt->offset; - p_rcb->p_rmsg = p_pkt; - } - /* set offset to point to where to copy next - use the same re-asm logic as AVCT */ - p_rcb->p_rmsg->offset += p_rcb->p_rmsg->len; - req_continue = TRUE; + /* set offset to point to where to copy next - use the same re-asm logic as AVCT */ + p_rcb->p_rmsg->offset += p_rcb->p_rmsg->len; + req_continue = TRUE; + } else { + /* do not reuse START buffer; it is smaller than BT_DEFAULT_BUFFER_SIZE */ + AVRC_TRACE_ERROR("Unable to allocate buffer for fragmented avrc message"); + drop_code = 5; + osi_free(p_pkt); + *pp_pkt = NULL; + } } else if (p_rcb->p_rmsg == NULL) { /* Received a CONTINUE/END, but no corresponding START (or previous fragmented response was dropped) */ @@ -521,6 +562,14 @@ static UINT8 avrc_proc_far_msg(UINT8 handle, UINT8 label, UINT8 cr, BT_HDR **pp_ if (AVRC_BldCommand ((tAVRC_COMMAND *)&avrc_cmd, &p_cmd) == AVRC_STS_NO_ERROR) { drop_code = 2; AVRC_MsgReq (handle, (UINT8)(label), AVRC_CMD_CTRL, p_cmd); + } else { + AVRC_TRACE_ERROR("Failed to build continuation command"); + if (p_rcb->p_rmsg) { + osi_free(p_rcb->p_rmsg); + p_rcb->p_rmsg = NULL; + *pp_pkt = NULL; + } + drop_code = 5; } } } @@ -953,6 +1002,8 @@ UINT16 AVRC_Open(UINT8 *p_handle, tAVRC_CONN_CB *p_ccb, BD_ADDR_PTR peer_addr) if (status == AVCT_SUCCESS) { memcpy(&avrc_cb.ccb[*p_handle], p_ccb, sizeof(tAVRC_CONN_CB)); #if (AVRC_METADATA_INCLUDED == TRUE) + /* free fragmentation/reassembly buffers before memset clears pointers */ + avrc_free_far_cb(*p_handle); memset(&avrc_cb.fcb[*p_handle], 0, sizeof(tAVRC_FRAG_CB)); memset(&avrc_cb.rcb[*p_handle], 0, sizeof(tAVRC_RASM_CB)); #endif @@ -984,6 +1035,10 @@ UINT16 AVRC_Open(UINT8 *p_handle, tAVRC_CONN_CB *p_ccb, BD_ADDR_PTR peer_addr) UINT16 AVRC_Close(UINT8 handle) { AVRC_TRACE_DEBUG("AVRC_Close handle:%d", handle); +#if (AVRC_METADATA_INCLUDED == TRUE) + /* release pending fragment/reassembly buffers before removing connection */ + avrc_free_far_cb(handle); +#endif return AVCT_RemoveConn(handle); } @@ -1067,7 +1122,7 @@ UINT16 AVRC_MsgReq (UINT8 handle, UINT8 label, UINT8 ctype, BT_HDR *p_pkt) /* AVRCP spec has not defined any control channel commands that needs fragmentation at this level * check for fragmentation only on the response */ - if ((cr == AVCT_RSP) && (chk_frag == TRUE)) { + if ((cr == AVCT_RSP) && (chk_frag == TRUE) && (p_pkt->event == AVRC_OP_VENDOR)) { if (p_pkt->len > AVRC_MAX_CTRL_DATA_LEN) { int offset_len = MAX(AVCT_MSG_OFFSET, p_pkt->offset); p_pkt_new = (BT_HDR *)osi_malloc((UINT16)(AVRC_PACKET_LEN + offset_len @@ -1099,6 +1154,9 @@ UINT16 AVRC_MsgReq (UINT8 handle, UINT8 label, UINT8 ctype, BT_HDR *p_pkt) p_pkt->len, len, p_fcb->p_fmsg->len ); } else { AVRC_TRACE_ERROR ("AVRC_MsgReq no buffers for fragmentation" ); + if (p_pkt_new) { + osi_free(p_pkt_new); + } osi_free(p_pkt); return AVRC_NO_RESOURCES; } diff --git a/components/bt/host/bluedroid/stack/avrc/avrc_bld_ct.c b/components/bt/host/bluedroid/stack/avrc/avrc_bld_ct.c index 4c260cbec3d..99fcb7314f4 100644 --- a/components/bt/host/bluedroid/stack/avrc/avrc_bld_ct.c +++ b/components/bt/host/bluedroid/stack/avrc/avrc_bld_ct.c @@ -336,9 +336,11 @@ tAVRC_STS AVRC_BldCommand( tAVRC_COMMAND *p_cmd, BT_HDR **pp_pkt) status = avrc_bld_get_play_status_cmd(&p_cmd->get_play_status, p_pkt); break; +#if (AVRC_ADV_CTRL_INCLUDED == TRUE) case AVRC_PDU_REGISTER_NOTIFICATION: /* 0x31 */ status = avrc_bld_register_change_notfn(p_cmd->reg_notif.event_id, p_cmd->reg_notif.param, p_pkt); break; +#endif case AVRC_PDU_GET_CAPABILITIES: status = avrc_bld_get_caps_cmd(&p_cmd->get_caps, p_pkt); break; diff --git a/components/bt/host/bluedroid/stack/avrc/avrc_bld_tg.c b/components/bt/host/bluedroid/stack/avrc/avrc_bld_tg.c index e82a0512723..abf34f44989 100644 --- a/components/bt/host/bluedroid/stack/avrc/avrc_bld_tg.c +++ b/components/bt/host/bluedroid/stack/avrc/avrc_bld_tg.c @@ -87,15 +87,17 @@ static tAVRC_STS avrc_bld_get_capability_rsp (tAVRC_GET_CAPS_RSP *p_rsp, BT_HDR } len += count * 3; } else { + UINT8 valid_count = 0; p_event_id = p_rsp->param.event_id; - *p_count = 0; + *p_count -= count; for (xx = 0; xx < count; xx++) { if (AVRC_IS_VALID_EVENT_ID(p_event_id[xx])) { - (*p_count)++; + valid_count++; UINT8_TO_BE_STREAM(p_data, p_event_id[xx]); } } - len += (*p_count); + *p_count += valid_count; + len += valid_count; } UINT16_TO_BE_STREAM(p_len, len); p_pkt->len = (p_data - p_start); @@ -314,12 +316,6 @@ static tAVRC_STS avrc_bld_app_setting_text_rsp (tAVRC_GET_APP_ATTR_TXT_RSP *p_rs p_start = (UINT8 *)(p_pkt + 1) + p_pkt->offset; p_data = p_len = p_start + 2; /* pdu + rsvd */ - /* - * NOTE: The buffer is allocated within avrc_bld_init_rsp_buffer(), and is - * always of size BT_DEFAULT_BUFFER_SIZE. - */ - len_left = BT_DEFAULT_BUFFER_SIZE - BT_HDR_SIZE - p_pkt->offset - p_pkt->len; - BE_STREAM_TO_UINT16(len, p_data); p_count = p_data; @@ -331,6 +327,7 @@ static tAVRC_STS avrc_bld_app_setting_text_rsp (tAVRC_GET_APP_ATTR_TXT_RSP *p_rs } for (xx = 0; xx < p_rsp->num_attr; xx++) { + len_left = (UINT16)(((UINT8 *)p_pkt + BT_DEFAULT_BUFFER_SIZE) - p_data); if (len_left < (p_rsp->p_attrs[xx].str_len + 4)) { AVRC_TRACE_ERROR("avrc_bld_app_setting_text_rsp out of room %d(str_len:%d, left:%d)", xx, p_rsp->p_attrs[xx].str_len, len_left); @@ -926,6 +923,10 @@ tAVRC_STS AVRC_BldResponse( UINT8 handle, tAVRC_RESPONSE *p_rsp, BT_HDR **pp_pkt case AVRC_PDU_SET_ABSOLUTE_VOLUME: /* 0x50 */ status = avrc_bld_set_absolute_volume_rsp(&p_rsp->volume, p_pkt); break; + + default: + status = AVRC_STS_BAD_PARAM; + break; } if (alloc && (status != AVRC_STS_NO_ERROR) ) { diff --git a/components/bt/host/bluedroid/stack/avrc/avrc_pars_tg.c b/components/bt/host/bluedroid/stack/avrc/avrc_pars_tg.c index 840f4c98559..1ffe9f29500 100644 --- a/components/bt/host/bluedroid/stack/avrc/avrc_pars_tg.c +++ b/components/bt/host/bluedroid/stack/avrc/avrc_pars_tg.c @@ -52,9 +52,6 @@ static tAVRC_STS avrc_pars_vendor_cmd(tAVRC_MSG_VENDOR *p_msg, tAVRC_COMMAND *p_ tAVRC_APP_SETTING *p_app_set; /* Check the vendor data */ - if (p_msg->vendor_len == 0) { - return AVRC_STS_NO_ERROR; - } if ((p_msg->p_vendor_data == NULL) || (p_msg->vendor_len < AVRC_CMD_FIXED_SIZE)) { return AVRC_STS_INTERNAL_ERR; } diff --git a/components/bt/host/bluedroid/stack/avrc/avrc_sdp.c b/components/bt/host/bluedroid/stack/avrc/avrc_sdp.c index 5eb3e0031b2..6f76323b213 100644 --- a/components/bt/host/bluedroid/stack/avrc/avrc_sdp.c +++ b/components/bt/host/bluedroid/stack/avrc/avrc_sdp.c @@ -199,6 +199,11 @@ UINT16 AVRC_FindService(UINT16 service_uuid, BD_ADDR bd_addr, /* perform service search */ result = SDP_ServiceSearchAttributeRequest(bd_addr, p_db->p_db, avrc_sdp_cback); + if (!result) { + avrc_cb.service_uuid = 0; + avrc_cb.p_db = NULL; + avrc_cb.p_cback = NULL; + } } return (result ? AVRC_SUCCESS : AVRC_FAIL); @@ -413,6 +418,17 @@ void AVRC_Deinit(void) { #if AVRC_DYNAMIC_MEMORY if (avrc_cb_ptr){ +#if (AVRC_METADATA_INCLUDED == TRUE) + UINT8 i; + for (i = 0; i < AVCT_NUM_CONN; i++) { + if (avrc_cb_ptr->fcb[i].p_fmsg) { + osi_free(avrc_cb_ptr->fcb[i].p_fmsg); + } + if (avrc_cb_ptr->rcb[i].p_rmsg) { + osi_free(avrc_cb_ptr->rcb[i].p_rmsg); + } + } +#endif osi_free(avrc_cb_ptr); avrc_cb_ptr = NULL; } diff --git a/components/bt/host/bluedroid/stack/btm/btm_acl.c b/components/bt/host/bluedroid/stack/btm/btm_acl.c index 5b7b2618680..cc22fb5e4e8 100644 --- a/components/bt/host/bluedroid/stack/btm/btm_acl.c +++ b/components/bt/host/bluedroid/stack/btm/btm_acl.c @@ -571,6 +571,12 @@ void btm_acl_removed (BD_ADDR bda, tBT_TRANSPORT transport) btm_cb.ble_ctr_cb.inq_var.connectable_mode, p->link_role); + if (p->transport == BT_TRANSPORT_LE) { +#if (BLE_50_FEATURE_SUPPORT == TRUE) && (BLE_50_EXTEND_ADV_EN == TRUE) + btm_ble_clear_ext_adv_ter_con_handle(p->hci_handle); +#endif + } + p_dev_rec = btm_find_dev(bda); if ( p_dev_rec) { BTM_TRACE_DEBUG("before update p_dev_rec->sec_flags=0x%x\n", p_dev_rec->sec_flags); @@ -2279,7 +2285,7 @@ void btm_acl_pkt_types_changed(UINT8 status, UINT16 handle, UINT16 pkt_types) tBTM_STATUS BTM_ReadChannelMap(BD_ADDR remote_bda) { tACL_CONN *p; - tBTM_BLE_CH_MAP_RESULTS result; + tBTM_BLE_CH_MAP_RESULTS result = {0}; tBTM_BLE_LEGACY_GAP_CB_PARAMS cb_params; UINT8 status; @@ -2322,7 +2328,7 @@ void BTM_BleGetWhiteListSize(uint16_t *length) { tBTM_BLE_CB *p_cb = &btm_cb.ble_ctr_cb; if (p_cb->white_list_avail_size == 0) { - BTM_TRACE_WARNING("%s Whitelist full.", __func__); + BTM_TRACE_WARNING("%s Whitelist size is 0.", __func__); } *length = p_cb->white_list_avail_size; return; @@ -2356,7 +2362,7 @@ void BTM_BleGetPeriodicAdvListSize(uint8_t *size) *******************************************************************************/ void btm_read_channel_map_complete(UINT8 *p) { - tBTM_BLE_CH_MAP_RESULTS results; + tBTM_BLE_CH_MAP_RESULTS results = {0}; UINT16 handle; tACL_CONN *p_acl_cb = NULL; @@ -2387,7 +2393,7 @@ void btm_read_channel_map_complete(UINT8 *p) memcpy(results.rem_bda, p_acl_cb->remote_addr, BD_ADDR_LEN); } } else { - results.status = BTM_ERR_PROCESSING; + results.status = BTM_HCI_ERROR | results.hci_status; BTM_TRACE_ERROR("BTM Channel Map Read Failed: hci status 0x%02x", results.hci_status); } diff --git a/components/bt/host/bluedroid/stack/btm/btm_ble.c b/components/bt/host/bluedroid/stack/btm/btm_ble.c index 95b44c35889..7bc86d38e70 100644 --- a/components/bt/host/bluedroid/stack/btm/btm_ble.c +++ b/components/bt/host/bluedroid/stack/btm/btm_ble.c @@ -736,7 +736,7 @@ BOOLEAN BTM_ReadConnectedTransportAddress(BD_ADDR remote_bda, tBT_TRANSPORT tran ** p_cmd_cmpl_cback - Command Complete callback ** *******************************************************************************/ -void BTM_BleReceiverTest(UINT8 rx_freq, tBTM_CMPL_CB *p_cmd_cmpl_cback) +void BTM_BleReceiverTest(UINT8 rx_freq, tBTM_DTM_CMD_CMPL_CBACK *p_cmd_cmpl_cback) { btm_cb.devcb.p_le_test_cmd_cmpl_cb = p_cmd_cmpl_cback; @@ -758,7 +758,7 @@ void BTM_BleReceiverTest(UINT8 rx_freq, tBTM_CMPL_CB *p_cmd_cmpl_cback) ** *******************************************************************************/ void BTM_BleTransmitterTest(UINT8 tx_freq, UINT8 test_data_len, - UINT8 packet_payload, tBTM_CMPL_CB *p_cmd_cmpl_cback) + UINT8 packet_payload, tBTM_DTM_CMD_CMPL_CBACK *p_cmd_cmpl_cback) { btm_cb.devcb.p_le_test_cmd_cmpl_cb = p_cmd_cmpl_cback; if (btsnd_hcic_ble_transmitter_test(tx_freq, test_data_len, packet_payload) == FALSE) { @@ -776,7 +776,7 @@ void BTM_BleTransmitterTest(UINT8 tx_freq, UINT8 test_data_len, ** Parameter p_cmd_cmpl_cback - Command complete callback ** *******************************************************************************/ -void BTM_BleTestEnd(tBTM_CMPL_CB *p_cmd_cmpl_cback) +void BTM_BleTestEnd(tBTM_DTM_CMD_CMPL_CBACK *p_cmd_cmpl_cback) { btm_cb.devcb.p_le_test_cmd_cmpl_cb = p_cmd_cmpl_cback; @@ -788,14 +788,14 @@ void BTM_BleTestEnd(tBTM_CMPL_CB *p_cmd_cmpl_cback) /******************************************************************************* ** Internal Functions *******************************************************************************/ -void btm_ble_test_command_complete(UINT8 *p) +void btm_ble_test_command_complete(UINT8 *p, UINT16 len) { - tBTM_CMPL_CB *p_cb = btm_cb.devcb.p_le_test_cmd_cmpl_cb; + tBTM_DTM_CMD_CMPL_CBACK *p_cb = btm_cb.devcb.p_le_test_cmd_cmpl_cb; btm_cb.devcb.p_le_test_cmd_cmpl_cb = NULL; if (p_cb) { - (*p_cb)(p); + (*p_cb)(p, len); } } #endif // #if ((BLE_42_DTM_TEST_EN == TRUE) || (BLE_50_DTM_TEST_EN == TRUE)) @@ -813,7 +813,7 @@ void btm_ble_test_command_complete(UINT8 *p) ** p_cmd_cmpl_cback - Command Complete callback ** *******************************************************************************/ -void BTM_BleEnhancedReceiverTest(UINT8 rx_freq, UINT8 phy, UINT8 modulation_index, tBTM_CMPL_CB *p_cmd_cmpl_cback) +void BTM_BleEnhancedReceiverTest(UINT8 rx_freq, UINT8 phy, UINT8 modulation_index, tBTM_DTM_CMD_CMPL_CBACK *p_cmd_cmpl_cback) { btm_cb.devcb.p_le_test_cmd_cmpl_cb = p_cmd_cmpl_cback; @@ -836,7 +836,7 @@ void BTM_BleEnhancedReceiverTest(UINT8 rx_freq, UINT8 phy, UINT8 modulation_inde ** *******************************************************************************/ void BTM_BleEnhancedTransmitterTest(UINT8 tx_freq, UINT8 test_data_len, - UINT8 packet_payload, UINT8 phy, tBTM_CMPL_CB *p_cmd_cmpl_cback) + UINT8 packet_payload, UINT8 phy, tBTM_DTM_CMD_CMPL_CBACK *p_cmd_cmpl_cback) { btm_cb.devcb.p_le_test_cmd_cmpl_cb = p_cmd_cmpl_cback; if (btsnd_hcic_ble_enhand_tx_test(tx_freq, test_data_len, packet_payload, phy) == FALSE) { @@ -1846,6 +1846,81 @@ UINT8 btm_ble_br_keys_req(tBTM_SEC_DEV_REC *p_dev_rec, tBTM_LE_IO_REQ *p_data) #endif ///SMP_INCLUDED +#if (BLE_50_FEATURE_SUPPORT == TRUE) && (BLE_50_EXTEND_ADV_EN == TRUE) && (CONTROLLER_RPA_LIST_ENABLE == TRUE) +/******************************************************************************* +** +** Function btm_ble_adjust_conn_addr_for_ext_adv +** +** Description Rewrite p_acl->conn_addr / conn_addr_type from the +** per-set state in extend_adv_cb.inst[] for the ext-adv +** instance that produced this connection. +** +** The defaults written by btm_acl_created() and +** btm_ble_refresh_local_resolvable_private_addr() come +** from the global addr_mgnt_cb single slot, which in +** multi-ADV may not reflect the policy actually used on +** air for THIS connection and causes SMP c1 / f5 / f6 +** to compute the wrong local address (pair fail 0x04). +** +** RPA paths (own_addr_type 0x02, or 0x03 with a valid +** local RPA in the LE Enhanced Connection Complete event) +** are left untouched. For 0x03 when the controller falls +** back to per-set identity (zero local_rpa), replace the +** global private_addr written by +** btm_ble_refresh_local_resolvable_private_addr(). +** +** No-op when no ext-adv instance matches the handle +** (initiator role or legacy adv). +** +** Returns void +** +*******************************************************************************/ +void btm_ble_adjust_conn_addr_for_ext_adv(UINT16 handle) +{ + UINT8 inst; + tACL_CONN *p_acl; + tBLE_ADDR_TYPE on_air_type; + + inst = BTM_BleGetExtAdvInstByConHandle(handle); + if (inst >= MAX_BLE_ADV_INSTANCE) { + return; + } + + p_acl = btm_handle_to_acl(handle); + if (p_acl == NULL) { + BTM_TRACE_WARNING("%s: no ACL for handle 0x%04x, skip", __func__, handle); + return; + } + + on_air_type = extend_adv_cb.inst[inst].own_addr_type; + if (on_air_type == BLE_ADDR_PUBLIC) { + p_acl->conn_addr_type = BLE_ADDR_PUBLIC; + memcpy(p_acl->conn_addr, + controller_get_interface()->get_address()->address, + BD_ADDR_LEN); + } else if (on_air_type == BLE_ADDR_RANDOM && + extend_adv_cb.inst[inst].rand_addr_set) { + p_acl->conn_addr_type = BLE_ADDR_RANDOM; + memcpy(p_acl->conn_addr, + extend_adv_cb.inst[inst].rand_addr, + BD_ADDR_LEN); + } else if (on_air_type == BLE_ADDR_RANDOM_ID && + extend_adv_cb.inst[inst].rand_addr_set && + !BTM_BLE_IS_RESOLVE_BDA(p_acl->conn_addr)) { + /* Identity fallback: controller used per-set static random, not RPA. */ + p_acl->conn_addr_type = BLE_ADDR_RANDOM; + memcpy(p_acl->conn_addr, + extend_adv_cb.inst[inst].rand_addr, + BD_ADDR_LEN); + } + + BTM_TRACE_DEBUG("%s: handle=0x%04x inst=%u type=%u addr=%02x:%02x:%02x:%02x:%02x:%02x", + __func__, handle, inst, p_acl->conn_addr_type, + p_acl->conn_addr[0], p_acl->conn_addr[1], p_acl->conn_addr[2], + p_acl->conn_addr[3], p_acl->conn_addr[4], p_acl->conn_addr[5]); +} +#endif /* (BLE_50_FEATURE_SUPPORT == TRUE) && (BLE_50_EXTEND_ADV_EN == TRUE) && (CONTROLLER_RPA_LIST_ENABLE == TRUE) */ + #if (BLE_PRIVACY_SPT == TRUE ) /******************************************************************************* ** @@ -1904,6 +1979,11 @@ static void btm_ble_resolve_random_addr_on_conn_cmpl(void *p_rec, void *p_data) l2cble_conn_comp (handle, role, bda, bda_type, conn_interval, conn_latency, conn_timeout); +#if (BLE_50_FEATURE_SUPPORT == TRUE) && (BLE_50_EXTEND_ADV_EN == TRUE) && (CONTROLLER_RPA_LIST_ENABLE == TRUE) + /* Multi-ADV: fix up p_acl->conn_addr / conn_addr_type from per-set state. */ + btm_ble_adjust_conn_addr_for_ext_adv(handle); +#endif /* (BLE_50_FEATURE_SUPPORT == TRUE) && (BLE_50_EXTEND_ADV_EN == TRUE) && (CONTROLLER_RPA_LIST_ENABLE == TRUE) */ + return; } #endif @@ -2067,6 +2147,12 @@ void btm_ble_conn_complete(UINT8 *p, UINT16 evt_len, BOOLEAN enhanced) } } #endif + +#if (BLE_50_FEATURE_SUPPORT == TRUE) && (BLE_50_EXTEND_ADV_EN == TRUE) && (CONTROLLER_RPA_LIST_ENABLE == TRUE) + /* Multi-ADV: must run AFTER the global-addr_mgnt_cb defaults above + * so per-set state wins for connections produced by an ext-adv set. */ + btm_ble_adjust_conn_addr_for_ext_adv(handle); +#endif /* (BLE_50_FEATURE_SUPPORT == TRUE) && (BLE_50_EXTEND_ADV_EN == TRUE) && (CONTROLLER_RPA_LIST_ENABLE == TRUE) */ } } else { role = HCI_ROLE_UNKNOWN; @@ -2903,26 +2989,47 @@ uint8_t btm_ble_scan_active_count(void) return count; } +#if (BLE_INCLUDED == TRUE) #if (SMP_INCLUDED == TRUE) +extern bool btc_config_has_section(const char *section); +#endif + uint8_t btm_ble_sec_dev_record_count(void) { tBTM_SEC_DEV_REC *p_dev_rec = NULL; list_node_t *p_node = NULL; uint8_t count = 0; - /* First look for the non-paired devices for the oldest entry */ for (p_node = list_begin(btm_cb.p_sec_dev_rec_list); p_node; p_node = list_next(p_node)) { p_dev_rec = list_node(p_node); +#if (SMP_INCLUDED == TRUE) if (p_dev_rec && (p_dev_rec->sec_flags & BTM_SEC_IN_USE) && (p_dev_rec->ble.key_type != BTM_LE_KEY_NONE)) { - BTM_TRACE_DEBUG("%s BLE security device #%d: bd_addr=%02X:%02X:%02X:%02X:%02X:%02X", +#else + if (p_dev_rec && (p_dev_rec->sec_flags & BTM_SEC_IN_USE)) { +#endif +#if (SMP_INCLUDED == TRUE) + /* Check if device exists in NVS */ + char bdstr[18] = {0}; + bdaddr_to_string((bt_bdaddr_t *)p_dev_rec->bd_addr, bdstr, sizeof(bdstr)); + + BTM_TRACE_WARNING("%s device #%d: "MACSTR", key_type=0x%02x (PENC:%d PID:%d PCSRK:%d LENC:%d LID:%d LCSRK:%d), in_nvs=%d", __func__, count, - p_dev_rec->bd_addr[0], - p_dev_rec->bd_addr[1], - p_dev_rec->bd_addr[2], - p_dev_rec->bd_addr[3], - p_dev_rec->bd_addr[4], - p_dev_rec->bd_addr[5]); + MAC2STR(p_dev_rec->bd_addr), + p_dev_rec->ble.key_type, + (p_dev_rec->ble.key_type & BTM_LE_KEY_PENC) ? 1 : 0, + (p_dev_rec->ble.key_type & BTM_LE_KEY_PID) ? 1 : 0, + (p_dev_rec->ble.key_type & BTM_LE_KEY_PCSRK) ? 1 : 0, + (p_dev_rec->ble.key_type & BTM_LE_KEY_LENC) ? 1 : 0, + (p_dev_rec->ble.key_type & BTM_LE_KEY_LID) ? 1 : 0, + (p_dev_rec->ble.key_type & BTM_LE_KEY_LCSRK) ? 1 : 0, + btc_config_has_section(bdstr)); +#else + BTM_TRACE_WARNING("%s device #%d: "MACSTR, + __func__, + count, + MAC2STR(p_dev_rec->bd_addr)); +#endif count++; } } 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 40498baddc3..f9fa9ebec74 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 @@ -6,6 +6,7 @@ #include "btm_int.h" #include "stack/hcimsgs.h" +#include "stack/hcidefs.h" #include "osi/allocator.h" #include "device/controller.h" #include @@ -16,6 +17,15 @@ tBTM_BLE_EXTENDED_CB extend_adv_cb; tBTM_BLE_5_HCI_CBACK ble_5_hci_cb; +#if (BLE_FEAT_LL_EXT_FEAT == TRUE) +static UINT8 btm_ble_local_supp_le_features[BLE_LL_EXT_FEAT_DATA_LEN]; +static UINT8 btm_ble_remote_supp_le_features[BLE_LL_EXT_FEAT_DATA_LEN]; +#endif // #if (BLE_FEAT_LL_EXT_FEAT == TRUE) + +#if (BLE_FEAT_SHORTER_CONN_INTERVALS == TRUE) +static tBTM_BLE_MIN_CONN_INTERVAL_GROUP btm_ble_min_conn_interval_groups[BTM_BLE_MAX_CONN_INTERVAL_GROUPS]; +#endif // #if (BLE_FEAT_SHORTER_CONN_INTERVALS == TRUE) + #define INVALID_VALUE_8BIT 0XFF #define INVALID_VALUE_16BIT 0XFFFF #define INVALID_VALUE_32BIT 0XFFFFFFFF @@ -63,7 +73,15 @@ void btm_ble_extendadvcb_init(void) #if (BLE_50_EXTEND_ADV_EN == TRUE) void btm_ble_advrecod_init(void) { - memset(&adv_record[0], 0, sizeof(tBTM_EXT_ADV_RECORD)*MAX_BLE_ADV_INSTANCE); + for (uint8_t i = 0; i < MAX_BLE_ADV_INSTANCE; i++) { + adv_record[i].ter_con_handle = INVALID_VALUE_16BIT; + adv_record[i].invalid = false; + adv_record[i].enabled = false; + adv_record[i].instance = INVALID_VALUE_8BIT; + adv_record[i].duration = INVALID_VALUE_32BIT; + adv_record[i].max_events = INVALID_VALUE_32BIT; + adv_record[i].retry_count = 0; + } } #endif // #if (BLE_50_EXTEND_ADV_EN == TRUE) @@ -197,6 +215,8 @@ tBTM_STATUS BTM_BleSetExtendedAdvRandaddr(UINT8 instance, BD_ADDR rand_addr) __func__, err); status = BTM_HCI_ERROR | err; } else { + memcpy(extend_adv_cb.inst[instance].rand_addr, rand_addr, BD_ADDR_LEN); + extend_adv_cb.inst[instance].rand_addr_set = TRUE; // set random address success, update address info if(extend_adv_cb.inst[instance].configured && extend_adv_cb.inst[instance].connetable) { BTM_BleSetStaticAddr(rand_addr); @@ -249,6 +269,13 @@ tBTM_STATUS BTM_BleSetExtendedAdvParams(UINT8 instance, tBTM_BLE_GAP_EXT_ADV_PAR extend_adv_cb.inst[instance].legacy_pdu = false; } + if (params->type & (BTM_BLE_GAP_SET_EXT_ADV_PROP_DIRECTED | + BTM_BLE_GAP_SET_EXT_ADV_PROP_HD_DIRECTED)) { + extend_adv_cb.inst[instance].directed = true; + } else { + extend_adv_cb.inst[instance].directed = false; + } + #if (CONTROLLER_RPA_LIST_ENABLE == FALSE) // if own_addr_type == BLE_ADDR_PUBLIC_ID or BLE_ADDR_RANDOM_ID, if((params->own_addr_type == BLE_ADDR_PUBLIC_ID || params->own_addr_type == BLE_ADDR_RANDOM_ID) && BTM_GetLocalResolvablePrivateAddr(rand_addr)) { @@ -286,6 +313,8 @@ tBTM_STATUS BTM_BleSetExtendedAdvParams(UINT8 instance, tBTM_BLE_GAP_EXT_ADV_PAR #endif // (BT_BLE_FEAT_ADV_CODING_SELECTION == TRUE) extend_adv_cb.inst[instance].configured = true; + /* Record the post-fallback on-air address type for per-set conn_addr fixup. */ + extend_adv_cb.inst[instance].own_addr_type = params->own_addr_type; end: if(use_rpa_addr) { @@ -296,6 +325,7 @@ end: } else { // set addr success, update address info BTM_UpdateAddrInfor(BLE_ADDR_RANDOM, rand_addr); + extend_adv_cb.inst[instance].rand_addr_set = FALSE; } } cb_params.set_params.status = status; @@ -334,13 +364,13 @@ tBTM_STATUS BTM_BleConfigExtendedAdvDataRaw(BOOLEAN is_scan_rsp, UINT8 instance, } } if (!is_scan_rsp) { - if ((err = btsnd_hcic_ble_set_ext_adv_data(instance, operation, 0, send_data_len, &data[data_offset])) != HCI_SUCCESS) { + if ((err = btsnd_hcic_ble_set_ext_adv_data(instance, operation, 0, send_data_len, (data == NULL) ? NULL : &data[data_offset])) != HCI_SUCCESS) { BTM_TRACE_ERROR("LE EA SetAdvData: cmd err=0x%x", err); status = BTM_HCI_ERROR | err; break; } } else { - if ((err = btsnd_hcic_ble_set_ext_adv_scan_rsp_data(instance, operation, 0, send_data_len, &data[data_offset])) != HCI_SUCCESS) { + if ((err = btsnd_hcic_ble_set_ext_adv_scan_rsp_data(instance, operation, 0, send_data_len, (data == NULL) ? NULL : &data[data_offset])) != HCI_SUCCESS) { BTM_TRACE_ERROR("LE EA SetScanRspData: cmd err=0x%x", err); status = BTM_HCI_ERROR | err; break; @@ -441,6 +471,7 @@ end: for (uint8_t i = 0; i < MAX_BLE_ADV_INSTANCE; i++) { + adv_record[i].ter_con_handle = INVALID_VALUE_16BIT; adv_record[i].invalid = false; adv_record[i].enabled = false; adv_record[i].instance = INVALID_VALUE_8BIT; @@ -455,6 +486,7 @@ end: if (index >= MAX_BLE_ADV_INSTANCE) { continue; } + adv_record[index].ter_con_handle = INVALID_VALUE_16BIT; adv_record[index].invalid = false; adv_record[index].enabled = false; adv_record[index].instance = INVALID_VALUE_8BIT; @@ -472,6 +504,7 @@ end: if (index >= MAX_BLE_ADV_INSTANCE) { continue; } + adv_record[index].ter_con_handle = INVALID_VALUE_16BIT; adv_record[index].invalid = true; adv_record[index].enabled = true; adv_record[index].instance = ext_adv[i].instance; @@ -520,6 +553,54 @@ tBTM_STATUS BTM_BleStartExtAdvRestart(uint16_t con_handle) return BTM_BleStartExtAdv(true, 1, &ext_adv); } +/******************************************************************************* +** +** Function BTM_BleGetExtAdvInstByConHandle +** +** Description Map an LE connection handle to the ext-adv instance +** whose adv-set-terminated event reported it. +** +** Returns instance index on success, 0xFF if no match. +** +*******************************************************************************/ +UINT8 BTM_BleGetExtAdvInstByConHandle(UINT16 con_handle) +{ + if (con_handle == INVALID_VALUE_16BIT) { + return 0xFF; + } + for (UINT8 i = 0; i < MAX_BLE_ADV_INSTANCE; i++) { + /* configured + connetable guard prevents an all-zero slot from + * spuriously matching a real conn_handle == 0. */ + if (adv_record[i].ter_con_handle == con_handle && + extend_adv_cb.inst[i].configured && + extend_adv_cb.inst[i].connetable) { + return i; + } + } + return 0xFF; +} + +/******************************************************************************* +** +** Function btm_ble_clear_ext_adv_ter_con_handle +** +** Description Clear stale ter_con_handle entries when an ACL link goes +** down so a reused connection handle cannot map to the +** wrong ext-adv instance. +** +** Returns void +** +*******************************************************************************/ +void btm_ble_clear_ext_adv_ter_con_handle(UINT16 con_handle) +{ + con_handle = HCID_GET_HANDLE(con_handle); + for (UINT8 i = 0; i < MAX_BLE_ADV_INSTANCE; i++) { + if (adv_record[i].ter_con_handle == con_handle) { + adv_record[i].ter_con_handle = INVALID_VALUE_16BIT; + } + } +} + tBTM_STATUS BTM_BleExtAdvSetRemove(UINT8 instance) { tBTM_STATUS status = BTM_SUCCESS; @@ -541,6 +622,10 @@ tBTM_STATUS BTM_BleExtAdvSetRemove(UINT8 instance) extend_adv_cb.inst[instance].directed = false; extend_adv_cb.inst[instance].scannable = false; extend_adv_cb.inst[instance].connetable = false; + extend_adv_cb.inst[instance].own_addr_type = BLE_ADDR_PUBLIC; + extend_adv_cb.inst[instance].rand_addr_set = FALSE; + memset(extend_adv_cb.inst[instance].rand_addr, 0, BD_ADDR_LEN); + adv_record[instance].ter_con_handle = INVALID_VALUE_16BIT; } end: @@ -570,6 +655,10 @@ tBTM_STATUS BTM_BleExtAdvSetClear(void) extend_adv_cb.inst[i].directed = false; extend_adv_cb.inst[i].scannable = false; extend_adv_cb.inst[i].connetable = false; + extend_adv_cb.inst[i].own_addr_type = BLE_ADDR_PUBLIC; + extend_adv_cb.inst[i].rand_addr_set = FALSE; + memset(extend_adv_cb.inst[i].rand_addr, 0, BD_ADDR_LEN); + adv_record[i].ter_con_handle = INVALID_VALUE_16BIT; } } @@ -1177,7 +1266,13 @@ void btm_ble_adv_set_terminated_evt(tBTM_BLE_ADV_TERMINAT *params) // 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; + /* Store the masked handle to match what btm_ble_conn_complete() looks up. */ + adv_record[params->adv_handle].ter_con_handle = HCID_GET_HANDLE(params->conn_handle); + /* Re-run the per-set conn_addr fixup in case this event arrives + * after LE (Enhanced) Connection Complete. */ +#if (CONTROLLER_RPA_LIST_ENABLE == TRUE) + btm_ble_adjust_conn_addr_for_ext_adv(adv_record[params->adv_handle].ter_con_handle); +#endif } else { adv_record[params->adv_handle].ter_con_handle = INVALID_VALUE_16BIT; adv_record[params->adv_handle].invalid = false; @@ -1332,6 +1427,352 @@ tBTM_STATUS BTM_BleEnableMonitorAdv(UINT8 enable) } #endif // #if (BLE_FEAT_ADV_MONITOR == TRUE) +#if (BLE_FEAT_DBAF == TRUE) +tBTM_STATUS BTM_BleSetDecisionData(UINT8 adv_handle, UINT8 decision_type_flags, + UINT8 data_len, const UINT8 *p_data) +{ + tHCI_STATUS err; + tBTM_STATUS status = BTM_SUCCESS; + tBTM_BLE_5_GAP_CB_PARAMS cb_params = {0}; + + if ((err = btsnd_hcic_ble_set_decision_data(adv_handle, decision_type_flags, data_len, p_data)) != HCI_SUCCESS) { + BTM_TRACE_ERROR("LE SetDecisionData: cmd err=0x%x", err); + status = BTM_HCI_ERROR | err; + } + + cb_params.status = status; + BTM_ExtBleCallbackTrigger(BTM_BLE_5_GAP_SET_DECISION_DATA_COMPLETE_EVT, &cb_params); + return status; +} + +tBTM_STATUS BTM_BleSetDecisionInstructions(UINT8 num_tests, const UINT8 *test_flags, + const UINT8 *test_fields, const UINT8 *test_params) +{ + tHCI_STATUS err; + tBTM_STATUS status = BTM_SUCCESS; + tBTM_BLE_5_GAP_CB_PARAMS cb_params = {0}; + + if ((err = btsnd_hcic_ble_set_decision_instructions(num_tests, test_flags, test_fields, + test_params)) != HCI_SUCCESS) { + BTM_TRACE_ERROR("LE SetDecisionInstructions: cmd err=0x%x", err); + status = BTM_HCI_ERROR | err; + } + + cb_params.status = status; + BTM_ExtBleCallbackTrigger(BTM_BLE_5_GAP_SET_DECISION_INSTRUCTIONS_COMPLETE_EVT, &cb_params); + return status; +} +#endif // #if (BLE_FEAT_DBAF == TRUE) + +#if (BLE_FEAT_FRAME_SPACE_UPDATE == TRUE) +tBTM_STATUS BTM_BleFrameSpaceUpdate(UINT16 conn_handle, UINT16 frame_space_min, + UINT16 frame_space_max, UINT8 phys, UINT16 spacing_types) +{ + tHCI_STATUS err; + + err = btsnd_hcic_ble_frame_space_update(conn_handle, frame_space_min, frame_space_max, + phys, spacing_types); + if (err != HCI_SUCCESS) { + tBTM_BLE_5_GAP_CB_PARAMS cb_params = {0}; + cb_params.frame_space_update.status = BTM_HCI_ERROR | err; + cb_params.frame_space_update.conn_handle = conn_handle; + BTM_ExtBleCallbackTrigger(BTM_BLE_5_GAP_FRAME_SPACE_UPDATE_COMPLETE_EVT, &cb_params); + return BTM_NO_RESOURCES; + } + return BTM_CMD_STARTED; +} + +void btm_frame_space_update_cmd_status(UINT8 status, UINT16 conn_handle) +{ + tBTM_BLE_5_GAP_CB_PARAMS cb_params = {0}; + + if (status == HCI_SUCCESS) { + return; + } + + cb_params.frame_space_update.status = status | BTM_HCI_ERROR; + cb_params.frame_space_update.conn_handle = conn_handle; + BTM_ExtBleCallbackTrigger(BTM_BLE_5_GAP_FRAME_SPACE_UPDATE_COMPLETE_EVT, &cb_params); +} + +void btm_ble_frame_space_update_complete_evt(UINT8 *p) +{ + tBTM_BLE_5_GAP_CB_PARAMS cb_params = {0}; + + STREAM_TO_UINT8(cb_params.frame_space_update.status, p); + STREAM_TO_UINT16(cb_params.frame_space_update.conn_handle, p); + STREAM_TO_UINT8(cb_params.frame_space_update.initiator, p); + STREAM_TO_UINT16(cb_params.frame_space_update.frame_space, p); + STREAM_TO_UINT8(cb_params.frame_space_update.phys, p); + STREAM_TO_UINT16(cb_params.frame_space_update.spacing_types, p); + + if (cb_params.frame_space_update.status != HCI_SUCCESS) { + cb_params.frame_space_update.status |= BTM_HCI_ERROR; + } + + BTM_ExtBleCallbackTrigger(BTM_BLE_5_GAP_FRAME_SPACE_UPDATE_COMPLETE_EVT, &cb_params); +} +#endif // #if (BLE_FEAT_FRAME_SPACE_UPDATE == TRUE) + +#if (BLE_FEAT_LL_EXT_FEAT == TRUE) +tBTM_STATUS BTM_BleReadAllLocalSuppFeatures(void) +{ + if (!btsnd_hcic_ble_read_all_local_supp_features()) { + tBTM_BLE_5_GAP_CB_PARAMS cb_params = {0}; + cb_params.read_all_local_supp_feat.status = BTM_HCI_ERROR | HCI_ERR_MEMORY_FULL; + BTM_ExtBleCallbackTrigger(BTM_BLE_5_GAP_READ_ALL_LOCAL_SUPP_FEAT_COMPLETE_EVT, &cb_params); + return BTM_NO_RESOURCES; + } + return BTM_CMD_STARTED; +} + +void btm_ble_read_all_local_supp_features_complete(UINT8 *p) +{ + tBTM_BLE_5_GAP_CB_PARAMS cb_params = {0}; + + STREAM_TO_UINT8(cb_params.read_all_local_supp_feat.status, p); + if (cb_params.read_all_local_supp_feat.status == HCI_SUCCESS) { + STREAM_TO_UINT8(cb_params.read_all_local_supp_feat.max_page, p); + cb_params.read_all_local_supp_feat.le_features = btm_ble_local_supp_le_features; + STREAM_TO_ARRAY(btm_ble_local_supp_le_features, p, BLE_LL_EXT_FEAT_DATA_LEN); + } else { + cb_params.read_all_local_supp_feat.status |= BTM_HCI_ERROR; + } + + BTM_ExtBleCallbackTrigger(BTM_BLE_5_GAP_READ_ALL_LOCAL_SUPP_FEAT_COMPLETE_EVT, &cb_params); +} + +tBTM_STATUS BTM_BleReadAllRemoteFeatures(UINT16 conn_handle, UINT8 page_requested) +{ + tHCI_STATUS err; + + err = btsnd_hcic_ble_read_all_remote_features(conn_handle, page_requested); + if (err != HCI_SUCCESS) { + tBTM_BLE_5_GAP_CB_PARAMS cb_params = {0}; + cb_params.read_all_remote_feat.status = BTM_HCI_ERROR | err; + cb_params.read_all_remote_feat.conn_handle = conn_handle; + BTM_ExtBleCallbackTrigger(BTM_BLE_5_GAP_READ_ALL_REMOTE_FEAT_COMPLETE_EVT, &cb_params); + return BTM_NO_RESOURCES; + } + return BTM_CMD_STARTED; +} + +void btm_read_all_remote_feat_cmd_status(UINT8 status) +{ + tBTM_BLE_5_GAP_CB_PARAMS cb_params = {0}; + + if (status == HCI_SUCCESS) { + return; + } + + cb_params.read_all_remote_feat.status = status | BTM_HCI_ERROR; + BTM_ExtBleCallbackTrigger(BTM_BLE_5_GAP_READ_ALL_REMOTE_FEAT_COMPLETE_EVT, &cb_params); +} + +void btm_ble_read_all_remote_features_complete_evt(UINT8 *p) +{ + tBTM_BLE_5_GAP_CB_PARAMS cb_params = {0}; + + STREAM_TO_UINT8(cb_params.read_all_remote_feat.status, p); + STREAM_TO_UINT16(cb_params.read_all_remote_feat.conn_handle, p); + if (cb_params.read_all_remote_feat.status == HCI_SUCCESS) { + STREAM_TO_UINT8(cb_params.read_all_remote_feat.max_remote_page, p); + STREAM_TO_UINT8(cb_params.read_all_remote_feat.max_valid_page, p); + cb_params.read_all_remote_feat.le_features = btm_ble_remote_supp_le_features; + STREAM_TO_ARRAY(btm_ble_remote_supp_le_features, p, BLE_LL_EXT_FEAT_DATA_LEN); + } else { + cb_params.read_all_remote_feat.status |= BTM_HCI_ERROR; + } + + BTM_ExtBleCallbackTrigger(BTM_BLE_5_GAP_READ_ALL_REMOTE_FEAT_COMPLETE_EVT, &cb_params); +} +#endif // #if (BLE_FEAT_LL_EXT_FEAT == TRUE) + +#if (BLE_FEAT_SHORTER_CONN_INTERVALS == TRUE) +tBTM_STATUS BTM_BleConnectionRateRequest(UINT16 conn_handle, UINT16 conn_interval_min, + UINT16 conn_interval_max, UINT16 subrate_min, + UINT16 subrate_max, UINT16 max_latency, + UINT16 continuation_number, UINT16 supervision_timeout, + UINT16 min_ce_len, UINT16 max_ce_len) +{ + tHCI_STATUS err; + + err = btsnd_hcic_ble_connection_rate_request(conn_handle, conn_interval_min, conn_interval_max, + subrate_min, subrate_max, max_latency, + continuation_number, supervision_timeout, + min_ce_len, max_ce_len); + if (err != HCI_SUCCESS) { + tBTM_BLE_5_GAP_CB_PARAMS cb_params = {0}; + cb_params.conn_rate_request.status = BTM_HCI_ERROR | err; + cb_params.conn_rate_request.conn_handle = conn_handle; + BTM_ExtBleCallbackTrigger(BTM_BLE_5_GAP_CONNECTION_RATE_REQUEST_COMPLETE_EVT, &cb_params); + return BTM_NO_RESOURCES; + } + return BTM_CMD_STARTED; +} + +void btm_conn_rate_req_cmd_status(UINT8 status, UINT16 conn_handle) +{ + tBTM_BLE_5_GAP_CB_PARAMS cb_params = {0}; + + if (status != HCI_SUCCESS) { + status = (status | BTM_HCI_ERROR); + } + cb_params.conn_rate_request.status = status; + cb_params.conn_rate_request.conn_handle = conn_handle; + BTM_ExtBleCallbackTrigger(BTM_BLE_5_GAP_CONNECTION_RATE_REQUEST_COMPLETE_EVT, &cb_params); +} + +void btm_ble_conn_rate_change_evt(tBTM_BLE_CONN_RATE_CHANGE *params) +{ + tBTM_BLE_5_GAP_CB_PARAMS cb_params = {0}; + + if (!params) { + return; + } + + /* Like btm_ble_subrate_change_evt(), notify the application only. + * Do not fold interval*subrate into L2CAP current_used_conn_interval: + * that field is UINT16 in 1.25 ms units and cannot represent SCI + * effective intervals; apps use ESP_GAP_BLE_CONN_RATE_CHANGE_EVT and + * ESP_BLE_GAP_CONN_RATE_EFF_INTERVAL_US() instead. */ + cb_params.conn_rate_change = *params; + if (cb_params.conn_rate_change.status != HCI_SUCCESS) { + cb_params.conn_rate_change.status |= BTM_HCI_ERROR; + } + BTM_ExtBleCallbackTrigger(BTM_BLE_5_GAP_CONN_RATE_CHANGE_EVT, &cb_params); +} + +void BTM_BleSetDefaultRateParameters(UINT16 conn_interval_min, UINT16 conn_interval_max, + UINT16 subrate_min, UINT16 subrate_max, UINT16 max_latency, + UINT16 continuation_number, UINT16 supervision_timeout, + UINT16 min_ce_len, UINT16 max_ce_len) +{ + tBTM_STATUS status = BTM_SUCCESS; + tHCI_STATUS err = HCI_SUCCESS; + tBTM_BLE_5_GAP_CB_PARAMS cb_params = {0}; + + if ((err = btsnd_hcic_ble_set_default_rate_parameters(conn_interval_min, conn_interval_max, + subrate_min, subrate_max, max_latency, + continuation_number, supervision_timeout, + min_ce_len, max_ce_len)) != HCI_SUCCESS) { + BTM_TRACE_ERROR("%s cmd err=0x%x", __func__, err); + status = BTM_HCI_ERROR | err; + } + + cb_params.status = status; + BTM_ExtBleCallbackTrigger(BTM_BLE_5_GAP_SET_DEFAULT_RATE_PARAMETERS_COMPLETE_EVT, &cb_params); +} + +tBTM_STATUS BTM_BleReadMinSuppConnInterval(void) +{ + if (!btsnd_hcic_ble_read_min_supp_conn_interval()) { + tBTM_BLE_5_GAP_CB_PARAMS cb_params = {0}; + cb_params.read_min_supp_conn_interval.status = BTM_HCI_ERROR | HCI_ERR_MEMORY_FULL; + BTM_ExtBleCallbackTrigger(BTM_BLE_5_GAP_READ_MIN_SUPP_CONN_INTERVAL_COMPLETE_EVT, &cb_params); + return BTM_NO_RESOURCES; + } + return BTM_CMD_STARTED; +} + +void btm_ble_read_min_supp_conn_interval_cmd_status(UINT8 status) +{ + tBTM_BLE_5_GAP_CB_PARAMS cb_params = {0}; + + if (status == HCI_SUCCESS) { + return; + } + + cb_params.read_min_supp_conn_interval.status = status | BTM_HCI_ERROR; + BTM_ExtBleCallbackTrigger(BTM_BLE_5_GAP_READ_MIN_SUPP_CONN_INTERVAL_COMPLETE_EVT, &cb_params); +} + +void btm_ble_read_min_supp_conn_interval_complete(UINT8 *p) +{ + tBTM_BLE_5_GAP_CB_PARAMS cb_params = {0}; + UINT8 num_groups; + + if (!p) { + return; + } + + STREAM_TO_UINT8(cb_params.read_min_supp_conn_interval.status, p); + if (cb_params.read_min_supp_conn_interval.status == HCI_SUCCESS) { + STREAM_TO_UINT8(cb_params.read_min_supp_conn_interval.min_supported_conn_interval, p); + STREAM_TO_UINT8(num_groups, p); + if (num_groups > BTM_BLE_MAX_CONN_INTERVAL_GROUPS) { + BTM_TRACE_WARNING("%s num_groups %u exceeds max %u", __func__, num_groups, + BTM_BLE_MAX_CONN_INTERVAL_GROUPS); + num_groups = BTM_BLE_MAX_CONN_INTERVAL_GROUPS; + } + cb_params.read_min_supp_conn_interval.num_groups = num_groups; + cb_params.read_min_supp_conn_interval.groups = btm_ble_min_conn_interval_groups; + for (UINT8 i = 0; i < num_groups; i++) { + STREAM_TO_UINT16(btm_ble_min_conn_interval_groups[i].min_125us, p); + STREAM_TO_UINT16(btm_ble_min_conn_interval_groups[i].max_125us, p); + STREAM_TO_UINT16(btm_ble_min_conn_interval_groups[i].stride_125us, p); + } + } else { + cb_params.read_min_supp_conn_interval.status |= BTM_HCI_ERROR; + } + + BTM_ExtBleCallbackTrigger(BTM_BLE_5_GAP_READ_MIN_SUPP_CONN_INTERVAL_COMPLETE_EVT, &cb_params); +} +#endif // #if (BLE_FEAT_SHORTER_CONN_INTERVALS == TRUE) + +#if (BLE_FEAT_LE_UTP == TRUE) +tBTM_STATUS BTM_BleEnableUtpOtaMode(UINT8 enable) +{ + tHCI_STATUS err; + tBTM_STATUS status = BTM_SUCCESS; + tBTM_BLE_5_GAP_CB_PARAMS cb_params = {0}; + + if ((err = btsnd_hcic_ble_enable_utp_ota_mode(enable)) != HCI_SUCCESS) { + BTM_TRACE_ERROR("LE EnableUtpOtaMode: cmd err=0x%x", err); + status = BTM_HCI_ERROR | err; + } + + cb_params.status = status; + BTM_ExtBleCallbackTrigger(BTM_BLE_5_GAP_ENABLE_UTP_OTA_MODE_COMPLETE_EVT, &cb_params); + return status; +} + +tBTM_STATUS BTM_BleUtpSend(UINT8 data_len, const UINT8 *p_data) +{ + tHCI_STATUS err; + tBTM_STATUS status = BTM_SUCCESS; + tBTM_BLE_5_GAP_CB_PARAMS cb_params = {0}; + + if ((err = btsnd_hcic_ble_utp_send(data_len, p_data)) != HCI_SUCCESS) { + BTM_TRACE_ERROR("LE UtpSend: cmd err=0x%x", err); + status = BTM_HCI_ERROR | err; + } + + cb_params.status = status; + BTM_ExtBleCallbackTrigger(BTM_BLE_5_GAP_UTP_SEND_COMPLETE_EVT, &cb_params); + return status; +} + +void btm_ble_utp_receive_evt(UINT8 *p, UINT16 len) +{ + tBTM_BLE_5_GAP_CB_PARAMS cb_params = {0}; + UINT8 data_len; + + if (!p || len < 1) { + return; + } + + STREAM_TO_UINT8(data_len, p); + if (data_len == 0 || len < (UINT16)(1 + data_len)) { + return; + } + + cb_params.utp_receive.len = data_len; + cb_params.utp_receive.data = p; + BTM_ExtBleCallbackTrigger(BTM_BLE_5_GAP_UTP_RECEIVE_EVT, &cb_params); +} +#endif // #if (BLE_FEAT_LE_UTP == TRUE) + #if (BLE_50_EXTEND_ADV_EN == TRUE) void btm_ble_scan_req_received_evt(tBTM_BLE_SCAN_REQ_RECEIVED *params) { @@ -1900,7 +2341,6 @@ void btm_ble_cs_read_local_supp_caps_cmpl_evt(uint8_t *p) goto _error; } - STREAM_TO_UINT16(cb_params.cs_read_local_supp_caps.conn_handle, p); STREAM_TO_UINT8(cb_params.cs_read_local_supp_caps.num_config_supported, p); STREAM_TO_UINT16(cb_params.cs_read_local_supp_caps.max_consecutive_proc_supported, p); STREAM_TO_UINT8(cb_params.cs_read_local_supp_caps.num_ant_supported, p); @@ -2011,6 +2451,7 @@ void btm_ble_cs_read_remote_fae_table_cmd_status(UINT8 status) tBTM_BLE_CS_READ_REMOTE_FAE_TAB_CMPL_EVT cs_read_remote_fae_tab = {0}; if (status != HCI_SUCCESS) { cs_read_remote_fae_tab.status = (status | BTM_HCI_ERROR); + cs_read_remote_fae_tab.conn_handle = 0xFFFF; BTM_ExtBleCallbackTrigger(BTM_BLE_GAP_CS_READ_REMOTE_FAE_TABLE_CMPL_EVT, (tBTM_BLE_5_GAP_CB_PARAMS *)&cs_read_remote_fae_tab); } } @@ -2168,3 +2609,39 @@ void btm_ble_cs_subevt_continue_result_evt(tBTM_BLE_CS_SUBEVT_RESULT_CONTINUE_EV } #endif // (BT_BLE_FEAT_CHANNEL_SOUNDING == TRUE) + +#if (BT_BLE_FEAT_CS_SECURITY_REQUIREMENTS == TRUE) +/* Host does not validate conn_handle range or CS_Security_Requirements reserved bits; Controller checks. */ +void BTM_BleGapCsSetSecurityRequirements(UINT16 conn_handle, UINT64 cs_security_requirements) +{ + tBTM_STATUS status = BTM_SUCCESS; + tHCI_STATUS err = HCI_SUCCESS; + tBTM_BLE_5_GAP_CB_PARAMS cb_params = {0}; + + if ((err = btsnd_hcic_ble_cs_set_security_requirements(conn_handle, cs_security_requirements)) != HCI_SUCCESS) { + BTM_TRACE_ERROR("cs set security requirements, cmd err=0x%x", err); + status = BTM_HCI_ERROR | err; + } + + cb_params.cs_set_security_requirements.status = status; + cb_params.cs_set_security_requirements.conn_handle = conn_handle; + BTM_ExtBleCallbackTrigger(BTM_BLE_GAP_CS_SET_SECURITY_REQUIREMENTS_CMPL_EVT, &cb_params); +} + +/* Host does not validate CS_Security_Requirements reserved bits; Controller checks. */ +void BTM_BleGapCsSetDefaultSecurityRequirements(UINT64 cs_security_requirements) +{ + tBTM_STATUS status = BTM_SUCCESS; + tHCI_STATUS err = HCI_SUCCESS; + tBTM_BLE_5_GAP_CB_PARAMS cb_params = {0}; + + if ((err = btsnd_hcic_ble_cs_set_default_security_requirements(cs_security_requirements)) != HCI_SUCCESS) { + BTM_TRACE_ERROR("cs set default security requirements, cmd err=0x%x", err); + status = BTM_HCI_ERROR | err; + } + + cb_params.cs_set_default_security_requirements.status = status; + BTM_ExtBleCallbackTrigger(BTM_BLE_GAP_CS_SET_DEFAULT_SECURITY_REQUIREMENTS_CMPL_EVT, &cb_params); +} + +#endif // (BT_BLE_FEAT_CS_SECURITY_REQUIREMENTS == TRUE) 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 2446251dd54..5d317fe4db9 100644 --- a/components/bt/host/bluedroid/stack/btm/btm_ble_gap.c +++ b/components/bt/host/bluedroid/stack/btm/btm_ble_gap.c @@ -1117,15 +1117,17 @@ tBTM_STATUS BTM_BleStartAdvWithParams(UINT16 adv_int_min, UINT16 adv_int_max, UI tBTM_STATUS status = BTM_SUCCESS; /* update adv params */ - if (btsnd_hcic_ble_write_adv_params (adv_int_min, + UINT8 hci_status = btsnd_hcic_ble_write_adv_params (adv_int_min, adv_int_max, adv_type, own_bda_type, p_dir_bda->type, p_dir_bda->bda, chnl_map, - p_cb->afp) != HCI_SUCCESS) { - status = BTM_NO_RESOURCES; + p_cb->afp); + if (hci_status != HCI_SUCCESS) { + BTM_BLE_TRACE_HCI_CMD_FAIL(__func__, hci_status); + status = btm_ble_status_from_hci(hci_status); } osi_mutex_unlock(&btm_lock); @@ -1175,13 +1177,13 @@ tBTM_STATUS BTM_BleSetScanFilterParams(tGATT_IF client_if, UINT32 scan_interval, (scan_mode == BTM_BLE_SCAN_MODE_ACTI || scan_mode == BTM_BLE_SCAN_MODE_PASS) && (scan_duplicate_filter < BTM_BLE_SCAN_DUPLICATE_MAX) && (scan_window <= scan_interval)) { - if ((btsnd_hcic_ble_set_scan_params(scan_mode, (UINT16)scan_interval, - (UINT16)scan_window, - addr_type_own, - scan_filter_policy)) != HCI_SUCCESS) { - ret = BTM_ILLEGAL_VALUE; - BTM_TRACE_ERROR("Illegal params: scan_interval = %d scan_window = %d\n", - scan_interval, scan_window); + UINT8 hci_status = btsnd_hcic_ble_set_scan_params(scan_mode, (UINT16)scan_interval, + (UINT16)scan_window, + addr_type_own, + scan_filter_policy); + if (hci_status != HCI_SUCCESS) { + BTM_BLE_TRACE_HCI_CMD_FAIL(__func__, hci_status); + ret = btm_ble_status_from_hci(hci_status); } else { p_cb->scan_type = scan_mode; p_cb->scan_interval = scan_interval; @@ -1232,8 +1234,10 @@ tBTM_STATUS BTM_BleWriteScanRsp(tBTM_BLE_AD_MASK data_mask, tBTM_BLE_ADV_DATA *p BTM_TRACE_WARNING("%s, Partial data write into ADV", __func__); } - if (btsnd_hcic_ble_set_scan_rsp_data((UINT8)(p - rsp_data), rsp_data) != HCI_SUCCESS) { - ret = BTM_ILLEGAL_VALUE; + UINT8 hci_status = btsnd_hcic_ble_set_scan_rsp_data((UINT8)(p - rsp_data), rsp_data); + if (hci_status != HCI_SUCCESS) { + BTM_BLE_TRACE_HCI_CMD_FAIL(__func__, hci_status); + ret = btm_ble_status_from_hci(hci_status); btm_cb.ble_ctr_cb.inq_var.scan_rsp = FALSE; } else { ret = BTM_SUCCESS; @@ -1265,8 +1269,10 @@ tBTM_STATUS BTM_BleWriteScanRspRaw(UINT8 *p_raw_scan_rsp, UINT32 raw_scan_rsp_le tBTM_STATUS ret = BTM_SUCCESS; osi_mutex_lock(&btm_lock, OSI_MUTEX_MAX_TIMEOUT); - if (btsnd_hcic_ble_set_scan_rsp_data((UINT8)raw_scan_rsp_len, p_raw_scan_rsp) != HCI_SUCCESS) { - ret = BTM_NO_RESOURCES; + UINT8 hci_status = btsnd_hcic_ble_set_scan_rsp_data((UINT8)raw_scan_rsp_len, p_raw_scan_rsp); + if (hci_status != HCI_SUCCESS) { + BTM_BLE_TRACE_HCI_CMD_FAIL(__func__, hci_status); + ret = btm_ble_status_from_hci(hci_status); } osi_mutex_unlock(&btm_lock); @@ -1377,9 +1383,11 @@ tBTM_STATUS BTM_BleWriteAdvData(tBTM_BLE_AD_MASK data_mask, tBTM_BLE_ADV_DATA *p p_cb_data->data_mask &= ~mask; - if ((btsnd_hcic_ble_set_adv_data((UINT8)(p_cb_data->p_pad - p_cb_data->ad_data), - p_cb_data->ad_data)) != HCI_SUCCESS) { - ret = BTM_NO_RESOURCES; + UINT8 hci_status = btsnd_hcic_ble_set_adv_data((UINT8)(p_cb_data->p_pad - p_cb_data->ad_data), + p_cb_data->ad_data); + if (hci_status != HCI_SUCCESS) { + BTM_BLE_TRACE_HCI_CMD_FAIL(__func__, hci_status); + ret = btm_ble_status_from_hci(hci_status); } osi_mutex_unlock(&btm_lock); return ret; @@ -1400,8 +1408,10 @@ tBTM_STATUS BTM_BleWriteAdvDataRaw(UINT8 *p_raw_adv, UINT32 raw_adv_len) { tBTM_STATUS ret = BTM_SUCCESS; osi_mutex_lock(&btm_lock, OSI_MUTEX_MAX_TIMEOUT); - if ((btsnd_hcic_ble_set_adv_data((UINT8)raw_adv_len, p_raw_adv)) != HCI_SUCCESS) { - ret = BTM_NO_RESOURCES; + UINT8 hci_status = btsnd_hcic_ble_set_adv_data((UINT8)raw_adv_len, p_raw_adv); + if (hci_status != HCI_SUCCESS) { + BTM_BLE_TRACE_HCI_CMD_FAIL(__func__, hci_status); + ret = btm_ble_status_from_hci(hci_status); } osi_mutex_unlock(&btm_lock); @@ -2065,15 +2075,17 @@ tBTM_STATUS btm_ble_set_discoverability(UINT16 combined_mode) #endif // #if (BLE_42_ADV_EN == TRUE) /* update adv params */ - if (btsnd_hcic_ble_write_adv_params (adv_int_min, + UINT8 hci_status = btsnd_hcic_ble_write_adv_params (adv_int_min, adv_int_max, evt_type, own_addr_type, init_addr_type, p_addr_ptr, p_cb->adv_chnl_map, - p_cb->afp) != HCI_SUCCESS) { - status = BTM_NO_RESOURCES; + p_cb->afp); + if (hci_status != HCI_SUCCESS) { + BTM_BLE_TRACE_HCI_CMD_FAIL(__func__, hci_status); + status = btm_ble_status_from_hci(hci_status); } else { p_cb->evt_type = evt_type; p_cb->adv_addr_type = own_addr_type; @@ -2163,15 +2175,17 @@ tBTM_STATUS btm_ble_set_connectability(UINT16 combined_mode) btm_ble_stop_adv(); #endif // #if (BLE_42_ADV_EN == TRUE) - if (btsnd_hcic_ble_write_adv_params (adv_int_min, + UINT8 hci_status = btsnd_hcic_ble_write_adv_params (adv_int_min, adv_int_max, evt_type, own_addr_type, peer_addr_type, p_addr_ptr, p_cb->adv_chnl_map, - p_cb->afp) != HCI_SUCCESS) { - status = BTM_NO_RESOURCES; + p_cb->afp); + if (hci_status != HCI_SUCCESS) { + BTM_BLE_TRACE_HCI_CMD_FAIL(__func__, hci_status); + status = btm_ble_status_from_hci(hci_status); } else { p_cb->evt_type = evt_type; p_cb->adv_addr_type = own_addr_type; @@ -3241,8 +3255,10 @@ tBTM_STATUS btm_ble_start_scan(void) p_inq->scan_duplicate_filter = BTM_BLE_DUPLICATE_DISABLE; } /* start scan, disable duplicate filtering */ - if ((btsnd_hcic_ble_set_scan_enable (BTM_BLE_SCAN_ENABLE, p_inq->scan_duplicate_filter)) != HCI_SUCCESS) { - status = BTM_NO_RESOURCES; + UINT8 hci_status = btsnd_hcic_ble_set_scan_enable (BTM_BLE_SCAN_ENABLE, p_inq->scan_duplicate_filter); + if (hci_status != HCI_SUCCESS) { + BTM_BLE_TRACE_HCI_CMD_FAIL(__func__, hci_status); + status = btm_ble_status_from_hci(hci_status); } else { btm_cb.ble_ctr_cb.inq_var.state |= BTM_BLE_SCANNING; #if (BLE_TOPOLOGY_CHECK == TRUE) @@ -3312,8 +3328,10 @@ static tBTM_STATUS btm_ble_stop_discover(void) /* Clear the inquiry callback if set */ btm_cb.ble_ctr_cb.inq_var.state &= ~BTM_BLE_SCANNING; /* stop discovery now */ - if (btsnd_hcic_ble_set_scan_enable (BTM_BLE_SCAN_DISABLE, BTM_BLE_DUPLICATE_ENABLE) != HCI_SUCCESS) { - status = BTM_NO_RESOURCES; + UINT8 hci_status = btsnd_hcic_ble_set_scan_enable (BTM_BLE_SCAN_DISABLE, BTM_BLE_DUPLICATE_ENABLE); + if (hci_status != HCI_SUCCESS) { + BTM_BLE_TRACE_HCI_CMD_FAIL(__func__, hci_status); + status = btm_ble_status_from_hci(hci_status); } #if (BLE_TOPOLOGY_CHECK == TRUE) /* reset status */ @@ -3428,8 +3446,10 @@ tBTM_STATUS btm_ble_start_adv(void) #if (BLE_TOPOLOGY_CHECK == TRUE) btm_ble_adv_states_operation(btm_ble_set_topology_mask, p_cb->evt_type); #endif // (BLE_TOPOLOGY_CHECK == TRUE) - if (btsnd_hcic_ble_set_adv_enable (BTM_BLE_ADV_ENABLE) != HCI_SUCCESS) { - rt = BTM_NO_RESOURCES; + UINT8 hci_status = btsnd_hcic_ble_set_adv_enable (BTM_BLE_ADV_ENABLE); + if (hci_status != HCI_SUCCESS) { + BTM_BLE_TRACE_HCI_CMD_FAIL(__func__, hci_status); + rt = btm_ble_status_from_hci(hci_status); p_cb->state = temp_state; p_cb->adv_mode = adv_mode; #if (BLE_TOPOLOGY_CHECK == TRUE) @@ -3472,7 +3492,8 @@ tBTM_STATUS btm_ble_stop_adv(void) /* clear all adv states */ btm_ble_clear_topology_mask (BTM_BLE_STATE_ALL_ADV_MASK); #endif // (BLE_TOPOLOGY_CHECK == TRUE) - if (btsnd_hcic_ble_set_adv_enable (BTM_BLE_ADV_DISABLE) != HCI_SUCCESS) { + UINT8 hci_status = btsnd_hcic_ble_set_adv_enable (BTM_BLE_ADV_DISABLE); + if (hci_status != HCI_SUCCESS) { // reset state p_cb->fast_adv_on = temp_fast_adv_on; p_cb->adv_mode = temp_adv_mode; @@ -3481,7 +3502,8 @@ tBTM_STATUS btm_ble_stop_adv(void) #if (BLE_TOPOLOGY_CHECK == TRUE) btm_ble_set_topology_mask (temp_mask); #endif // (BLE_TOPOLOGY_CHECK == TRUE) - rt = BTM_NO_RESOURCES; + BTM_BLE_TRACE_HCI_CMD_FAIL(__func__, hci_status); + rt = btm_ble_status_from_hci(hci_status); } if(rt != HCI_SUCCESS) { p_cb->adv_mode = temp_adv_mode; diff --git a/components/bt/host/bluedroid/stack/btm/btm_ble_privacy.c b/components/bt/host/bluedroid/stack/btm/btm_ble_privacy.c index 45f3af5bcd3..2a3fab1b09b 100644 --- a/components/bt/host/bluedroid/stack/btm/btm_ble_privacy.c +++ b/components/bt/host/bluedroid/stack/btm/btm_ble_privacy.c @@ -1180,6 +1180,9 @@ void btm_ble_resolving_list_cleanup(void) { tBTM_BLE_RESOLVE_Q *p_q = &btm_cb.ble_ctr_cb.resolving_list_pend_q; + p_q->q_next = 0; + p_q->q_pending = 0; + if (p_q->resolve_q_random_pseudo) { osi_free(p_q->resolve_q_random_pseudo); p_q->resolve_q_random_pseudo = NULL; diff --git a/components/bt/host/bluedroid/stack/btm/btm_devctl.c b/components/bt/host/bluedroid/stack/btm/btm_devctl.c index 1cc41f7e688..854f4778d1d 100644 --- a/components/bt/host/bluedroid/stack/btm/btm_devctl.c +++ b/components/bt/host/bluedroid/stack/btm/btm_devctl.c @@ -120,13 +120,11 @@ void btm_dev_init (void) *******************************************************************************/ static void btm_db_reset (void) { - tBTM_CMPL_CB *p_cb; - tBTM_STATUS status = BTM_DEV_RESET; - btm_inq_db_reset(); #if (CLASSIC_BT_INCLUDED == TRUE) if (btm_cb.devcb.p_rln_cmpl_cb) { + tBTM_CMPL_CB *p_cb; p_cb = btm_cb.devcb.p_rln_cmpl_cb; btm_cb.devcb.p_rln_cmpl_cb = NULL; @@ -137,12 +135,14 @@ static void btm_db_reset (void) #endif // (CLASSIC_BT_INCLUDED == TRUE) if (btm_cb.devcb.p_rssi_cmpl_cb) { - p_cb = btm_cb.devcb.p_rssi_cmpl_cb; - btm_cb.devcb.p_rssi_cmpl_cb = NULL; + tBTM_CMPL_CB *p_cb = btm_cb.devcb.p_rssi_cmpl_cb; + tBTM_RSSI_RESULTS results = {0}; - if (p_cb) { - (*p_cb)((tBTM_RSSI_RESULTS *) &status); - } + results.status = BTM_DEV_RESET; + btm_cb.devcb.p_rssi_cmpl_cb = NULL; + btu_stop_timer(&btm_cb.devcb.rssi_timer); + + (*p_cb)(&results); } } @@ -1327,12 +1327,8 @@ void btm_ble_set_channels_complete (UINT8 *p) case HCI_SUCCESS: cb_params.set_channels.status = BTM_SUCCESS; break; - case HCI_ERR_UNSUPPORTED_VALUE: - case HCI_ERR_ILLEGAL_PARAMETER_FMT: - cb_params.set_channels.status = BTM_ILLEGAL_VALUE; - break; default: - cb_params.set_channels.status = BTM_ERR_PROCESSING; + cb_params.set_channels.status = BTM_HCI_ERROR | cb_params.set_channels.hci_status; break; } BTM_LegacyBleCallbackTrigger(BTM_BLE_LEGACY_GAP_SET_CHANNELS_COMPLETE_EVT, &cb_params); diff --git a/components/bt/host/bluedroid/stack/btm/include/btm_ble_int.h b/components/bt/host/bluedroid/stack/btm/include/btm_ble_int.h index 02fa86b47e2..6057f7951f5 100644 --- a/components/bt/host/bluedroid/stack/btm/include/btm_ble_int.h +++ b/components/bt/host/bluedroid/stack/btm/include/btm_ble_int.h @@ -444,7 +444,7 @@ void btm_ble_increment_sign_ctr(BD_ADDR bd_addr, BOOLEAN is_local ); BOOLEAN btm_get_local_div (BD_ADDR bd_addr, UINT16 *p_div); BOOLEAN btm_ble_get_enc_key_type(BD_ADDR bd_addr, UINT8 *p_key_types); -void btm_ble_test_command_complete(UINT8 *p); +void btm_ble_test_command_complete(UINT8 *p, UINT16 len); void btm_ble_rand_enc_complete (UINT8 *p, UINT16 op_code, tBTM_RAND_ENC_CB *p_enc_cplt_cback); void btm_sec_save_le_key(BD_ADDR bd_addr, tBTM_LE_KEY_TYPE key_type, tBTM_LE_KEY_VALUE *p_keys, BOOLEAN pass_to_application); @@ -510,6 +510,10 @@ void btm_ble_add_default_entry_to_resolving_list(void); void btm_ble_set_privacy_mode_complete(UINT8 *p, UINT16 evt_len); #endif +#if (BLE_50_FEATURE_SUPPORT == TRUE) && (BLE_50_EXTEND_ADV_EN == TRUE) && (CONTROLLER_RPA_LIST_ENABLE == TRUE) +void btm_ble_adjust_conn_addr_for_ext_adv(UINT16 handle); +#endif + char btm_ble_map_adv_tx_power(int tx_power_index); #if (BLE_TOPOLOGY_CHECK == TRUE) BOOLEAN btm_ble_topology_check(tBTM_BLE_STATE_MASK request); @@ -531,6 +535,9 @@ BOOLEAN btm_get_current_conn_params(BD_ADDR bda, UINT16 *interval, UINT16 *laten #if (BLE_50_FEATURE_SUPPORT == TRUE) void btm_ble_update_phy_evt(tBTM_BLE_UPDATE_PHY *params); void btm_ble_scan_timeout_evt(void); +#if (BLE_50_EXTEND_ADV_EN == TRUE) +void btm_ble_clear_ext_adv_ter_con_handle(UINT16 con_handle); +#endif void btm_ble_adv_set_terminated_evt(tBTM_BLE_ADV_TERMINAT *params); void btm_ble_ext_adv_report_evt(tBTM_BLE_EXT_ADV_REPORT *params); void btm_ble_scan_req_received_evt(tBTM_BLE_SCAN_REQ_RECEIVED *params); @@ -592,6 +599,20 @@ void btm_ble_transmit_power_report_evt(tBTM_BLE_TRANS_POWER_REPORT_EVT *params); #if (BLE_FEAT_CONN_SUBRATING == TRUE) void btm_ble_subrate_change_evt(tBTM_BLE_SUBRATE_CHANGE_EVT *params); #endif // #if (BLE_FEAT_CONN_SUBRATING == TRUE) +#if (BLE_FEAT_FRAME_SPACE_UPDATE == TRUE) +void btm_ble_frame_space_update_complete_evt(UINT8 *p); +#endif // #if (BLE_FEAT_FRAME_SPACE_UPDATE == TRUE) +#if (BLE_FEAT_LL_EXT_FEAT == TRUE) +void btm_ble_read_all_local_supp_features_complete(UINT8 *p); +void btm_ble_read_all_remote_features_complete_evt(UINT8 *p); +#endif // #if (BLE_FEAT_LL_EXT_FEAT == TRUE) +#if (BLE_FEAT_SHORTER_CONN_INTERVALS == TRUE) +void btm_ble_conn_rate_change_evt(tBTM_BLE_CONN_RATE_CHANGE *params); +void btm_ble_read_min_supp_conn_interval_complete(UINT8 *p); +#endif // #if (BLE_FEAT_SHORTER_CONN_INTERVALS == TRUE) +#if (BLE_FEAT_LE_UTP == TRUE) +void btm_ble_utp_receive_evt(UINT8 *p, UINT16 len); +#endif // #if (BLE_FEAT_LE_UTP == TRUE) #if (BT_BLE_FEAT_PAWR_EN == TRUE) void btm_ble_pa_subevt_data_req_evt(tBTM_BLE_PA_SUBEVT_DATA_REQ_EVT *params); void btm_ble_pa_rsp_rpt_evt(tBTM_BLE_PA_RSP_REPORT_EVT *params); @@ -607,6 +628,14 @@ void btm_ble_cs_subevt_result_evt(tBTM_BLE_CS_SUBEVT_RESULT_CMPL_EVT *subevt_res void btm_ble_cs_subevt_continue_result_evt(tBTM_BLE_CS_SUBEVT_RESULT_CONTINUE_EVT *subevt_continue_result); #endif // (BT_BLE_FEAT_CHANNEL_SOUNDING == TRUE) +static inline tBTM_STATUS btm_ble_status_from_hci(UINT8 hci_status) +{ + return (hci_status == HCI_SUCCESS) ? BTM_SUCCESS : (tBTM_STATUS)(BTM_HCI_ERROR | hci_status); +} + +#define BTM_BLE_TRACE_HCI_CMD_FAIL(func, hci_status) \ + BTM_TRACE_ERROR("%s, fail to send the hci command, the error code = 0x%x", (func), (hci_status)) + /* #ifdef __cplusplus diff --git a/components/bt/host/bluedroid/stack/btm/include/btm_int.h b/components/bt/host/bluedroid/stack/btm/include/btm_int.h index c0a1e2f2e24..0d0120051e3 100644 --- a/components/bt/host/bluedroid/stack/btm/include/btm_int.h +++ b/components/bt/host/bluedroid/stack/btm/include/btm_int.h @@ -291,8 +291,8 @@ DEV_CLASS dev_class; /* Local device class TIMER_LIST_ENT ble_channels_timer; -tBTM_CMPL_CB *p_le_test_cmd_cmpl_cb; /* Callback function to be called when - LE test mode command has been sent successfully */ +tBTM_DTM_CMD_CMPL_CBACK *p_le_test_cmd_cmpl_cb; /* Callback function to be called when + LE test mode command has been sent successfully */ BD_ADDR read_tx_pwr_addr; /* read TX power target address */ @@ -1201,6 +1201,19 @@ void btm_read_remote_trans_pwr_level_cmpl(UINT8 status); void btm_subrate_req_cmd_status(UINT8 status); #endif // #if (BLE_FEAT_CONN_SUBRATING == TRUE) +#if (BLE_FEAT_FRAME_SPACE_UPDATE == TRUE) +void btm_frame_space_update_cmd_status(UINT8 status, UINT16 conn_handle); +#endif // #if (BLE_FEAT_FRAME_SPACE_UPDATE == TRUE) + +#if (BLE_FEAT_LL_EXT_FEAT == TRUE) +void btm_read_all_remote_feat_cmd_status(UINT8 status); +#endif // #if (BLE_FEAT_LL_EXT_FEAT == TRUE) + +#if (BLE_FEAT_SHORTER_CONN_INTERVALS == TRUE) +void btm_conn_rate_req_cmd_status(UINT8 status, UINT16 conn_handle); +void btm_ble_read_min_supp_conn_interval_cmd_status(UINT8 status); +#endif // #if (BLE_FEAT_SHORTER_CONN_INTERVALS == TRUE) + #if (BT_BLE_FEAT_CHANNEL_SOUNDING == TRUE) void btm_ble_cs_read_local_supp_caps_cmpl_evt(UINT8 *p); void btm_ble_cs_read_remote_supp_caps_cmd_status(UINT8 status); diff --git a/components/bt/host/bluedroid/stack/btu/btu_hcif.c b/components/bt/host/bluedroid/stack/btu/btu_hcif.c index 6b3a0758f12..d7015eaf32a 100644 --- a/components/bt/host/bluedroid/stack/btu/btu_hcif.c +++ b/components/bt/host/bluedroid/stack/btu/btu_hcif.c @@ -50,7 +50,7 @@ #include "stack/btu.h" extern void btm_process_cancel_complete(UINT8 status, UINT8 mode); -extern void btm_ble_test_command_complete(UINT8 *p); +extern void btm_ble_test_command_complete(UINT8 *p, UINT16 len); #if (BT_BLE_FEAT_CHANNEL_SOUNDING == TRUE) // BLE Channel Sounding parameter validation macros per BLE spec @@ -174,6 +174,18 @@ static void btu_ble_ext_adv_report_evt(UINT8 *p, UINT16 evt_len); #if (BLE_FEAT_ADV_MONITOR == TRUE) static void btu_ble_monitor_adv_report_evt(UINT8 *p); #endif // #if (BLE_FEAT_ADV_MONITOR == TRUE) +#if (BLE_FEAT_LL_EXT_FEAT == TRUE) +static void btu_ble_read_all_remote_feat_complete_evt(UINT8 *p); +#endif // #if (BLE_FEAT_LL_EXT_FEAT == TRUE) +#if (BLE_FEAT_FRAME_SPACE_UPDATE == TRUE) +static void btu_ble_frame_space_update_complete_evt(UINT8 *p); +#endif // #if (BLE_FEAT_FRAME_SPACE_UPDATE == TRUE) +#if (BLE_FEAT_LE_UTP == TRUE) +static void btu_ble_utp_receive_evt(UINT8 *p, UINT16 evt_len); +#endif // #if (BLE_FEAT_LE_UTP == TRUE) +#if (BLE_FEAT_SHORTER_CONN_INTERVALS == TRUE) +static void btu_ble_conn_rate_change_evt(UINT8 *p); +#endif // #if (BLE_FEAT_SHORTER_CONN_INTERVALS == TRUE) #if (BLE_50_EXTEND_SYNC_EN == TRUE) static void btu_ble_periodic_adv_sync_establish_evt(UINT8 *p, bool v2_evt); static void btu_ble_periodic_adv_report_evt(UINT8 *p, UINT8 evt_len, bool v2_evt); @@ -560,6 +572,16 @@ void btu_hcif_process_event (UNUSED_ATTR UINT8 controller_id, BT_HDR *p_msg) btu_ble_monitor_adv_report_evt(p); break; #endif // #if (BLE_FEAT_ADV_MONITOR == TRUE) +#if (BLE_FEAT_LL_EXT_FEAT == TRUE) + case HCI_BLE_READ_ALL_REMOTE_FEAT_COMPLETE_EVT: + btu_ble_read_all_remote_feat_complete_evt(p); + break; +#endif // #if (BLE_FEAT_LL_EXT_FEAT == TRUE) +#if (BLE_FEAT_FRAME_SPACE_UPDATE == TRUE) + case HCI_BLE_FRAME_SPACE_UPDATE_COMPLETE_EVT: + btu_ble_frame_space_update_complete_evt(p); + break; +#endif // #if (BLE_FEAT_FRAME_SPACE_UPDATE == TRUE) #if (BLE_FEAT_PERIODIC_ADV_SYNC_TRANSFER == TRUE) case HCI_BLE_PERIOD_ADV_SYNC_TRANS_RECV_EVT: btu_ble_periodic_adv_sync_trans_recv(p); @@ -629,6 +651,16 @@ void btu_hcif_process_event (UNUSED_ATTR UINT8 controller_id, BT_HDR *p_msg) btu_ble_subrate_change_evt(p); break; #endif // #if (BLE_FEAT_CONN_SUBRATING == TRUE) +#if (BLE_FEAT_LE_UTP == TRUE) + case HCI_BLE_UTP_RECEIVE_EVT: + btu_ble_utp_receive_evt(p, hci_evt_len); + break; +#endif // #if (BLE_FEAT_LE_UTP == TRUE) +#if (BLE_FEAT_SHORTER_CONN_INTERVALS == TRUE) + case HCI_BLE_CONN_RATE_CHANGE_EVT: + btu_ble_conn_rate_change_evt(p); + break; +#endif // #if (BLE_FEAT_SHORTER_CONN_INTERVALS == TRUE) #if (BT_BLE_FEAT_PAWR_EN == TRUE) case HCI_BLE_PA_SUBEVT_DATA_REQUEST_EVT: btu_ble_pa_subevt_data_request_evt(p); @@ -951,6 +983,20 @@ static void btu_hcif_disconnection_comp_evt (UINT8 *p) handle = HCID_GET_HANDLE (handle); +#if BLE_INCLUDED == TRUE + /* Capture the disconnecting device's address before btm_acl_disconnected() + * clears the matched connection handle. The record itself is re-looked-up + * afterwards (by address) because callbacks fired during disconnection may + * have already freed it. */ + BD_ADDR disc_bda; + BOOLEAN have_disc_bda = FALSE; + tBTM_SEC_DEV_REC *p_dev_rec = btm_find_dev_by_handle(handle); + if (p_dev_rec) { + memcpy(disc_bda, p_dev_rec->bd_addr, BD_ADDR_LEN); + have_disc_bda = TRUE; + } +#endif + dev_find = btm_acl_disconnected(handle, reason); #if (BLE_FEAT_ISO_CIG_EN == TRUE) @@ -963,6 +1009,48 @@ static void btu_hcif_disconnection_comp_evt (UINT8 *p) HCI_TRACE_WARNING("hcif disc complete: hdl 0x%x, rsn 0x%x dev_find %d", handle, reason, dev_find); UNUSED(dev_find); + +#if BLE_INCLUDED == TRUE + /* Delete unpaired device records to free memory (~356B per device). + * + * Re-find the record by address: callbacks invoked during + * btm_acl_disconnected() may already have freed it, so the pointer captured + * before the call cannot be trusted. + * + * Only delete when the device is fully idle and unpaired: + * 1. No active BR/EDR connection (hci_handle invalid) + * 2. No active LE connection (ble_hci_handle invalid) - protects the still + * connected transport of a dual-mode device when the other one drops + * 3. No BLE security keys (unpaired) - when SMP is enabled + * + * BT_TRANSPORT_LE is used so that any retained BR/EDR link key keeps a + * BR/EDR-bonded record alive; an LE-unpaired record that has no BR/EDR key + * collapses to BTM_SEC_IN_USE only and is removed from the list. + * + * Skip deletion on HCI_ERR_CONN_FAILED_ESTABLISHMENT when connect + * retry is enabled. + */ + if (have_disc_bda +#if (GATTC_CONNECT_RETRY_EN == TRUE) + && reason != HCI_ERR_CONN_FAILED_ESTABLISHMENT +#endif + ) { + p_dev_rec = btm_find_dev(disc_bda); + if (p_dev_rec + && p_dev_rec->hci_handle == BTM_SEC_INVALID_HANDLE /* No active BR/EDR connection */ + && p_dev_rec->ble_hci_handle == BTM_SEC_INVALID_HANDLE /* No active LE connection */ +#if SMP_INCLUDED == TRUE + && !p_dev_rec->ble.key_type /* No BLE security keys */ +#endif + ) { + BTM_TRACE_WARNING( + "Deleting unpaired device %02X:%02X:%02X:%02X:%02X:%02X", + p_dev_rec->bd_addr[0], p_dev_rec->bd_addr[1], p_dev_rec->bd_addr[2], + p_dev_rec->bd_addr[3], p_dev_rec->bd_addr[4], p_dev_rec->bd_addr[5]); + btm_sec_free_dev(p_dev_rec, BT_TRANSPORT_LE); + } + } +#endif // BLE_INCLUDED == TRUE } /******************************************************************************* @@ -1002,6 +1090,11 @@ static void btu_hcif_rmt_name_request_comp_evt (UINT8 *p, UINT16 evt_len) UINT8 status; BD_ADDR bd_addr; + if (evt_len < (1 + BD_ADDR_LEN)) { + HCI_TRACE_ERROR("HCI_RMT_NAME_REQUEST_COMP_EVT param too short (len=%u)", evt_len); + return; + } + STREAM_TO_UINT8 (status, p); STREAM_TO_BDADDR (bd_addr, p); @@ -1331,11 +1424,17 @@ static void btu_hcif_hdl_command_complete (UINT16 opcode, UINT8 *p, UINT16 evt_l btm_ble_create_ll_conn_complete(*p); break; +#if ((BLE_42_DTM_TEST_EN == TRUE) || (BLE_50_DTM_TEST_EN == TRUE)) + case HCI_BLE_TRANSMITTER_TEST: + case HCI_BLE_RECEIVER_TEST: + case HCI_BLE_TEST_END: + /* Forward raw parameters + length; upper layers validate before parsing. */ + btm_ble_test_command_complete(p, evt_len); + break; +#else case HCI_BLE_TRANSMITTER_TEST: case HCI_BLE_RECEIVER_TEST: -#if ((BLE_42_DTM_TEST_EN == TRUE) || (BLE_50_DTM_TEST_EN == TRUE)) case HCI_BLE_TEST_END: - btm_ble_test_command_complete(p); break; #endif // #if ((BLE_42_DTM_TEST_EN == TRUE) || (BLE_50_DTM_TEST_EN == TRUE)) case HCI_BLE_CREATE_CONN_CANCEL: @@ -1397,7 +1496,7 @@ static void btu_hcif_hdl_command_complete (UINT16 opcode, UINT8 *p, UINT16 evt_l #if (BLE_50_DTM_TEST_EN == TRUE) case HCI_BLE_ENH_RX_TEST: case HCI_BLE_ENH_TX_TEST: - btm_ble_test_command_complete(p); + btm_ble_test_command_complete(p, evt_len); break; #endif // #if (BLE_50_DTM_TEST_EN == TRUE) @@ -1477,6 +1576,16 @@ static void btu_hcif_hdl_command_complete (UINT16 opcode, UINT8 *p, UINT16 evt_l btm_ble_read_monitor_adv_list_size_complete(p); break; #endif // #if (BLE_FEAT_ADV_MONITOR == TRUE) +#if (BLE_FEAT_LL_EXT_FEAT == TRUE) + case HCI_BLE_READ_ALL_LOCAL_SUPP_FEATURES: + btm_ble_read_all_local_supp_features_complete(p); + break; +#endif // #if (BLE_FEAT_LL_EXT_FEAT == TRUE) +#if (BLE_FEAT_SHORTER_CONN_INTERVALS == TRUE) + case HCI_BLE_READ_MIN_SUPP_CONN_INTERVAL: + btm_ble_read_min_supp_conn_interval_complete(p); + break; +#endif // #if (BLE_FEAT_SHORTER_CONN_INTERVALS == TRUE) #endif /* (BLE_INCLUDED == TRUE) */ default: { @@ -1646,6 +1755,38 @@ static void btu_hcif_hdl_command_status (UINT16 opcode, UINT8 status, UINT8 *p_c btm_subrate_req_cmd_status(status); break; #endif // #if (BLE_FEAT_CONN_SUBRATING == TRUE) +#if (BLE_FEAT_FRAME_SPACE_UPDATE == TRUE) + case HCI_BLE_FRAME_SPACE_UPDATE: + { + UINT16 conn_handle = HCI_INVALID_HANDLE; + if (p_cmd != NULL) { + p_cmd++; /* skip param length */ + STREAM_TO_UINT16(conn_handle, p_cmd); + } + btm_frame_space_update_cmd_status(status, conn_handle); + break; + } +#endif // #if (BLE_FEAT_FRAME_SPACE_UPDATE == TRUE) +#if (BLE_FEAT_LL_EXT_FEAT == TRUE) + case HCI_BLE_READ_ALL_REMOTE_FEATURES: + btm_read_all_remote_feat_cmd_status(status); + break; +#endif // #if (BLE_FEAT_LL_EXT_FEAT == TRUE) +#if (BLE_FEAT_SHORTER_CONN_INTERVALS == TRUE) + case HCI_BLE_CONNECTION_RATE_REQUEST: + { + UINT16 conn_handle = HCI_INVALID_HANDLE; + if (p_cmd != NULL) { + p_cmd++; /* skip param length */ + STREAM_TO_UINT16(conn_handle, p_cmd); + } + btm_conn_rate_req_cmd_status(status, conn_handle); + break; + } + case HCI_BLE_READ_MIN_SUPP_CONN_INTERVAL: + btm_ble_read_min_supp_conn_interval_cmd_status(status); + break; +#endif // #if (BLE_FEAT_SHORTER_CONN_INTERVALS == TRUE) #if (BT_BLE_FEAT_CHANNEL_SOUNDING == TRUE) case HCI_BLE_CS_READ_REMOTE_SUPP_CAPS: btm_ble_cs_read_remote_supp_caps_cmd_status(status); @@ -1829,6 +1970,11 @@ static void btu_hcif_command_status_evt(uint8_t status, BT_HDR *command, void *c { BT_HDR *event = osi_calloc(sizeof(BT_HDR) + sizeof(command_status_hack_t)); command_status_hack_t *hack = (command_status_hack_t *)&event->data[0]; +#if ((BLE_50_FEATURE_SUPPORT == TRUE) || (BLE_42_FEATURE_SUPPORT == TRUE)) + if (status != HCI_SUCCESS) { + btsnd_hci_ble_set_status(status); + } +#endif // #if ((BLE_50_FEATURE_SUPPORT == TRUE) || (BLE_42_FEATURE_SUPPORT == TRUE)) hack->callback = btu_hcif_command_status_evt_on_task; hack->status = status; @@ -2655,6 +2801,20 @@ static void btu_ble_monitor_adv_report_evt(UINT8 *p) } #endif // #if (BLE_FEAT_ADV_MONITOR == TRUE) +#if (BLE_FEAT_LL_EXT_FEAT == TRUE) +static void btu_ble_read_all_remote_feat_complete_evt(UINT8 *p) +{ + btm_ble_read_all_remote_features_complete_evt(p); +} +#endif // #if (BLE_FEAT_LL_EXT_FEAT == TRUE) + +#if (BLE_FEAT_FRAME_SPACE_UPDATE == TRUE) +static void btu_ble_frame_space_update_complete_evt(UINT8 *p) +{ + btm_ble_frame_space_update_complete_evt(p); +} +#endif // #if (BLE_FEAT_FRAME_SPACE_UPDATE == TRUE) + #if (BLE_50_EXTEND_SYNC_EN == TRUE) static void btu_ble_periodic_adv_sync_establish_evt(UINT8 *p, bool v2_evt) { @@ -3329,6 +3489,40 @@ static void btu_ble_subrate_change_evt(UINT8 *p) } #endif // #if (BLE_FEAT_CONN_SUBRATING == TRUE) +#if (BLE_FEAT_SHORTER_CONN_INTERVALS == TRUE) +static void btu_ble_conn_rate_change_evt(UINT8 *p) +{ + tBTM_BLE_CONN_RATE_CHANGE conn_rate_change = {0}; + + if (!p) { + HCI_TRACE_ERROR("%s, Invalid params.", __func__); + return; + } + + STREAM_TO_UINT8(conn_rate_change.status, p); + STREAM_TO_UINT16(conn_rate_change.conn_handle, p); + STREAM_TO_UINT16(conn_rate_change.conn_interval, p); + STREAM_TO_UINT16(conn_rate_change.subrate_factor, p); + STREAM_TO_UINT16(conn_rate_change.peripheral_latency, p); + STREAM_TO_UINT16(conn_rate_change.continuation_number, p); + STREAM_TO_UINT16(conn_rate_change.supervision_timeout, p); + + btm_ble_conn_rate_change_evt(&conn_rate_change); +} +#endif // #if (BLE_FEAT_SHORTER_CONN_INTERVALS == TRUE) + +#if (BLE_FEAT_LE_UTP == TRUE) +static void btu_ble_utp_receive_evt(UINT8 *p, UINT16 evt_len) +{ + if (!p || evt_len < 1) { + HCI_TRACE_ERROR("%s, Invalid params.", __func__); + return; + } + + btm_ble_utp_receive_evt(p, evt_len); +} +#endif // #if (BLE_FEAT_LE_UTP == TRUE) + #if (BT_BLE_FEAT_PAWR_EN == TRUE) static void btu_ble_pa_subevt_data_request_evt(UINT8 *p) { diff --git a/components/bt/host/bluedroid/stack/btu/btu_init.c b/components/bt/host/bluedroid/stack/btu/btu_init.c index a00b3243982..14747a03fbc 100644 --- a/components/bt/host/bluedroid/stack/btu/btu_init.c +++ b/components/bt/host/bluedroid/stack/btu/btu_init.c @@ -148,10 +148,10 @@ void btu_free_core(void) ** NOTE: Must be called before creating any tasks ** (RPC, BTU, HCIT, APPL, etc.) ** -** Returns void +** Returns true for success, otherwise false ** ******************************************************************************/ -void BTU_StartUp(void) +bool BTU_StartUp(void) { #if BTU_DYNAMIC_MEMORY btu_cb_ptr = (tBTU_CB *)osi_malloc(sizeof(tBTU_CB)); @@ -194,11 +194,12 @@ void BTU_StartUp(void) goto error_exit; } - return; + return true; error_exit:; LOG_ERROR("%s Unable to allocate resources for bt_workqueue", __func__); BTU_ShutDown(); + return false; } /***************************************************************************** diff --git a/components/bt/host/bluedroid/stack/btu/btu_task.c b/components/bt/host/bluedroid/stack/btu/btu_task.c index 27d3ca79fa5..234dbecaa77 100644 --- a/components/bt/host/bluedroid/stack/btu/btu_task.c +++ b/components/bt/host/bluedroid/stack/btu/btu_task.c @@ -272,7 +272,13 @@ void btu_task_start_up(void *param) btu_init_core(); /* Initialize any optional stack components */ - BTE_InitStack(); + if (BTE_InitStack() != BT_STATUS_SUCCESS) { + HCI_TRACE_ERROR("BTE_InitStack failed"); + if (bluedroid_init_done_cb) { + bluedroid_init_done_cb(BT_STATUS_NOMEM); + } + return; + } #if (defined(BTA_INCLUDED) && BTA_INCLUDED == TRUE) bta_sys_init(); @@ -280,11 +286,9 @@ void btu_task_start_up(void *param) // Inform the bt jni thread initialization is ok. // btif_transfer_context(btif_init_ok, 0, NULL, 0, NULL); -#if(defined(BT_APP_DEMO) && BT_APP_DEMO == TRUE) if (bluedroid_init_done_cb) { - bluedroid_init_done_cb(); + bluedroid_init_done_cb(BT_STATUS_SUCCESS); } -#endif } void btu_task_shut_down(void) @@ -321,6 +325,7 @@ static void btu_general_alarm_process(void *param) break; case BTU_TTYPE_L2CAP_LINK: + case BTU_TTYPE_L2CAP_LINK_RETRY: case BTU_TTYPE_L2CAP_CHNL: case BTU_TTYPE_L2CAP_HOLD: case BTU_TTYPE_L2CAP_INFO: @@ -550,6 +555,13 @@ static void btu_l2cap_alarm_process(void *param) TIMER_LIST_ENT *p_tle = (TIMER_LIST_ENT *)param; assert(p_tle != NULL); + osi_mutex_lock(&btu_l2cap_alarm_lock, OSI_MUTEX_MAX_TIMEOUT); + if (!hash_map_has_key(btu_l2cap_alarm_hash_map, p_tle) || p_tle->in_use == FALSE) { + osi_mutex_unlock(&btu_l2cap_alarm_lock); + return; + } + osi_mutex_unlock(&btu_l2cap_alarm_lock); + switch (p_tle->event) { case BTU_TTYPE_L2CAP_CHNL: /* monitor or retransmission timer */ case BTU_TTYPE_L2CAP_FCR_ACK: /* ack timer */ diff --git a/components/bt/host/bluedroid/stack/gatt/att_protocol.c b/components/bt/host/bluedroid/stack/gatt/att_protocol.c index 686820071a8..704e1e969f0 100644 --- a/components/bt/host/bluedroid/stack/gatt/att_protocol.c +++ b/components/bt/host/bluedroid/stack/gatt/att_protocol.c @@ -390,6 +390,18 @@ tGATT_STATUS attp_send_msg_to_l2cap(tGATT_TCB *p_tcb, BT_HDR *p_toL2CAP) if (p_tcb->att_lcid == L2CAP_ATT_CID) { + /* L2CA_SendFixedChnlData() silently drops (osi_free) the buffer when the + * ATT fixed channel is already in cong_sent state, yet still returns + * L2CAP_DW_CONGESTED. Without distinguishing this from the post-enqueue + * congestion case, the upper layer would treat a dropped PDU as "sent" + * and wait for a response that never arrives. Detect the drop path + * up-front, release the buffer here and surface it as GATT_BUSY so that + * callers go through their failure path instead. */ + if (L2CA_CheckIsCongest(L2CAP_ATT_CID, p_tcb->peer_bda)) { + GATT_TRACE_WARNING("ATT fixed channel already congested, drop PDU"); + osi_free(p_toL2CAP); + return GATT_BUSY; + } l2cap_ret = L2CA_SendFixedChnlData (L2CAP_ATT_CID, p_tcb->peer_bda, p_toL2CAP); } else { #if (CLASSIC_BT_INCLUDED == TRUE) @@ -403,6 +415,8 @@ tGATT_STATUS attp_send_msg_to_l2cap(tGATT_TCB *p_tcb, BT_HDR *p_toL2CAP) GATT_TRACE_DEBUG("ATT failed to pass msg to L2CAP"); return GATT_INTERNAL_ERROR; } else if (l2cap_ret == L2CAP_DW_CONGESTED) { + /* Buffer was enqueued by L2CAP before congestion was reported; + * L2CAP retains ownership of it. */ GATT_TRACE_DEBUG("ATT congested, message accepted"); return GATT_CONGESTED; } diff --git a/components/bt/host/bluedroid/stack/gatt/gatt_api.c b/components/bt/host/bluedroid/stack/gatt/gatt_api.c index ddab5b986f8..3ade9f7f388 100644 --- a/components/bt/host/bluedroid/stack/gatt/gatt_api.c +++ b/components/bt/host/bluedroid/stack/gatt/gatt_api.c @@ -1750,9 +1750,22 @@ tGATT_STATUS GATTS_HandleMultiValueNotification (UINT16 conn_id, tGATT_HLV *tupl return GATT_ILLEGAL_PARAMETER; } + { + UINT32 new_len = (UINT32)notif.len + 4U + (UINT32)p_hlv->length; + if (new_len > (UINT32)GATT_MAX_ATTR_LEN) { + GATT_TRACE_ERROR("%s: len %u>MAX_ATTR_LEN", __func__, (unsigned)new_len); + return GATT_ILLEGAL_PARAMETER; + } + } + UINT16_TO_STREAM(p, p_hlv->handle); //handle UINT16_TO_STREAM(p, p_hlv->length); //length - memcpy (p, p_hlv->value, p_hlv->length); //value + if (p_hlv->length > 0) { + if (p_hlv->value == NULL) { + return GATT_ILLEGAL_PARAMETER; + } + memcpy (p, p_hlv->value, p_hlv->length); //value + } GATT_TRACE_DEBUG("%s handle %x, length %u", __func__, p_hlv->handle, p_hlv->length); p += p_hlv->length; notif.len += 4 + p_hlv->length; diff --git a/components/bt/host/bluedroid/stack/gatt/gatt_auth.c b/components/bt/host/bluedroid/stack/gatt/gatt_auth.c index 0a033e1ae04..19ad2bc4cad 100644 --- a/components/bt/host/bluedroid/stack/gatt/gatt_auth.c +++ b/components/bt/host/bluedroid/stack/gatt/gatt_auth.c @@ -171,7 +171,7 @@ void gatt_sec_check_complete(BOOLEAN sec_check_ok, tGATT_CLCB *p_clcb, UINT8 s void gatt_enc_cmpl_cback(BD_ADDR bd_addr, tBT_TRANSPORT transport, void *p_ref_data, tBTM_STATUS result) { tGATT_TCB *p_tcb; - UINT8 sec_flag; + UINT8 sec_flag = 0; BOOLEAN status = FALSE; UNUSED(p_ref_data); @@ -185,9 +185,8 @@ void gatt_enc_cmpl_cback(BD_ADDR bd_addr, tBT_TRANSPORT transport, void *p_ref_d if (p_buf != NULL) { if (result == BTM_SUCCESS) { if (gatt_get_sec_act(p_tcb) == GATT_SEC_ENCRYPT_MITM ) { - BTM_GetSecurityFlagsByTransport(bd_addr, &sec_flag, transport); - - if (sec_flag & BTM_SEC_FLAG_LKEY_AUTHED) { + if (BTM_GetSecurityFlagsByTransport(bd_addr, &sec_flag, transport) && + (sec_flag & BTM_SEC_FLAG_LKEY_AUTHED)) { status = TRUE; } } else { @@ -305,7 +304,7 @@ tGATT_SEC_ACTION gatt_get_sec_act(tGATT_TCB *p_tcb) tGATT_SEC_ACTION gatt_determine_sec_act(tGATT_CLCB *p_clcb ) { tGATT_SEC_ACTION act = GATT_SEC_OK; - UINT8 sec_flag; + UINT8 sec_flag = 0; tGATT_TCB *p_tcb = p_clcb->p_tcb; tGATT_AUTH_REQ auth_req = p_clcb->auth_req; BOOLEAN is_link_encrypted = FALSE; diff --git a/components/bt/host/bluedroid/stack/gatt/gatt_cl.c b/components/bt/host/bluedroid/stack/gatt/gatt_cl.c index 5d7c7e2bb39..1fc86945659 100644 --- a/components/bt/host/bluedroid/stack/gatt/gatt_cl.c +++ b/components/bt/host/bluedroid/stack/gatt/gatt_cl.c @@ -891,7 +891,11 @@ void gatt_process_read_by_type_rsp (tGATT_TCB *p_tcb, tGATT_CLCB *p_clcb, UINT8 /* value_len is the length of current record's value; use it to avoid overread when multiple records present */ p_clcb->counter = value_len; p_clcb->s_handle = handle; - if ( p_clcb->counter == (p_clcb->p_tcb->payload_size - 4)) { + UINT16 max_rbtype_val_len = (p_clcb->p_tcb->payload_size - 4); + if (max_rbtype_val_len > GATT_MAX_READ_BY_TYPE_VALUE_LEN) { + max_rbtype_val_len = GATT_MAX_READ_BY_TYPE_VALUE_LEN; + } + if (p_clcb->counter == max_rbtype_val_len) { p_clcb->op_subtype = GATT_READ_BY_HANDLE; if (!p_clcb->p_attr_buf) { p_clcb->p_attr_buf = (UINT8 *)osi_malloc(GATT_MAX_ATTR_LEN); @@ -1124,10 +1128,14 @@ BOOLEAN gatt_cl_send_next_cmd_inq(tGATT_TCB *p_tcb) if (att_ret == GATT_SUCCESS || att_ret == GATT_CONGESTED) { sent = TRUE; p_cmd->to_send = FALSE; - if(p_cmd->p_cmd) { - osi_free(p_cmd->p_cmd); - p_cmd->p_cmd = NULL; - } + /* On GATT_SUCCESS / GATT_CONGESTED, L2CAP has taken ownership of + * p_cmd->p_cmd (it was either accepted normally, or enqueued just + * before the channel turned congested). The "already congested" + * drop path inside L2CA_SendFixedChnlData() is filtered out earlier + * by attp_send_msg_to_l2cap() and returned as GATT_BUSY, which + * falls into the error branch below. So we must not free the + * buffer here. */ + p_cmd->p_cmd = NULL; /* dequeue the request if is write command or sign write */ if (p_cmd->op_code != GATT_CMD_WRITE && p_cmd->op_code != GATT_SIGN_CMD_WRITE) { @@ -1145,13 +1153,22 @@ BOOLEAN gatt_cl_send_next_cmd_inq(tGATT_TCB *p_tcb) gatt_end_operation(p_clcb, att_ret, NULL); } } else { - GATT_TRACE_ERROR("gatt_cl_send_next_cmd_inq: L2CAP sent error"); + GATT_TRACE_ERROR("gatt_cl_send_next_cmd_inq: L2CAP sent error, status=%d", att_ret); /* attp_send_msg_to_l2cap() already freed p_cmd->p_cmd on failure */ p_cmd->p_cmd = NULL; - memset(p_cmd, 0, sizeof(tGATT_CMD_Q)); - p_tcb->pending_cl_req ++; - p_tcb->pending_cl_req %= GATT_CL_MAX_LCB; + p_cmd->to_send = FALSE; + /* Dequeue the failing command so pending_cl_req is advanced and the + * associated p_clcb can be retrieved. */ + p_clcb = gatt_cmd_dequeue(p_tcb, &rsp_code); p_cmd = &p_tcb->cl_cmd_q[p_tcb->pending_cl_req]; + /* Notify the upper layer about the failure. Without this the + * response timer is never armed (non-write ops) and the write + * completion callback is never fired, leaving the application + * stuck waiting for a callback that will never come. The p_clcb + * would also leak. */ + if (p_clcb != NULL) { + gatt_end_operation(p_clcb, att_ret, NULL); + } } } diff --git a/components/bt/host/bluedroid/stack/gatt/gatt_db.c b/components/bt/host/bluedroid/stack/gatt/gatt_db.c index 73037155f27..299eb80927d 100644 --- a/components/bt/host/bluedroid/stack/gatt/gatt_db.c +++ b/components/bt/host/bluedroid/stack/gatt/gatt_db.c @@ -370,7 +370,13 @@ tGATT_STATUS gatts_db_read_attr_value_by_type (tGATT_TCB *p_tcb, UINT16_TO_STREAM (p, p_attr->handle); - status = read_attr_value ((void *)p_attr, 0, &p, FALSE, (UINT16)(*p_len - 2), &len, sec_flag, key_size); + { + UINT16 max_val_len = (UINT16)(*p_len - 2); + if (max_val_len > GATT_MAX_READ_BY_TYPE_VALUE_LEN) { + max_val_len = GATT_MAX_READ_BY_TYPE_VALUE_LEN; + } + status = read_attr_value ((void *)p_attr, 0, &p, FALSE, max_val_len, &len, sec_flag, key_size); + } if (status == GATT_PENDING) { diff --git a/components/bt/host/bluedroid/stack/gatt/gatt_sr.c b/components/bt/host/bluedroid/stack/gatt/gatt_sr.c index 62b170b1a26..41b6db2d184 100644 --- a/components/bt/host/bluedroid/stack/gatt/gatt_sr.c +++ b/components/bt/host/bluedroid/stack/gatt/gatt_sr.c @@ -414,6 +414,27 @@ tGATT_STATUS gatt_sr_process_app_rsp (tGATT_TCB *p_tcb, tGATT_IF gatt_if, UINT32 trans_id, UINT8 op_code, tGATT_STATUS status, tGATTS_RSP *p_msg) { + if ((p_tcb->exec_write_rsp_trans_id == trans_id) && (op_code == GATT_REQ_EXEC_WRITE)) { + /* + * Execute Write is a special case without a handle, so both stack and application + * may try to send a response. + * - Stack: may have already sent an automatic Execute Write Response. + * - App: may call esp_gatts_send_response() with the same trans_id. + * + * To prevent sending two responses for the same Execute Write request, + * we check if this trans_id has already been auto-responded by stack. + * If so, ignore the application response without sending another ATT packet. + * Still update cback_cnt/dequeue sr_cmd so state stays consistent when multiple + * apps are registered; only clear exec_write_rsp_trans_id after all apps respond. + */ + gatt_sr_update_cback_cnt(p_tcb, gatt_if, FALSE, FALSE); + if (gatt_sr_is_cback_cnt_zero(p_tcb)) { + gatt_dequeue_sr_cmd(p_tcb); + p_tcb->exec_write_rsp_trans_id = 0; + } + return GATT_SUCCESS; + } + tGATT_STATUS ret_code = GATT_SUCCESS; UNUSED(trans_id); @@ -481,6 +502,7 @@ tGATT_STATUS gatt_sr_process_app_rsp (tGATT_TCB *p_tcb, tGATT_IF gatt_if, *******************************************************************************/ void gatt_process_exec_write_req (tGATT_TCB *p_tcb, UINT8 op_code, UINT16 len, UINT8 *p_data) { + BOOLEAN response_sent = false; UINT8 *p = p_data, flag, i = 0; UINT32 trans_id = 0; tGATT_IF gatt_if; @@ -536,6 +558,7 @@ void gatt_process_exec_write_req (tGATT_TCB *p_tcb, UINT8 op_code, UINT16 len, U is_prepare_write_valid = TRUE; } GATT_TRACE_DEBUG("Send execute_write_rsp\n"); + response_sent = TRUE; } else if ((prepare_record->error_code_app == GATT_SUCCESS) && (prepare_record->total_num > queue_num)){ //No error for stack_rsp's handles and there exist some app_rsp's handles, @@ -580,6 +603,10 @@ void gatt_process_exec_write_req (tGATT_TCB *p_tcb, UINT8 op_code, UINT16 len, U trans_id = gatt_sr_enqueue_cmd(p_tcb, op_code, 0); gatt_sr_copy_prep_cnt_to_cback_cnt(p_tcb); } + /* Record trans_id if stack already sent response, to prevent app from sending duplicate */ + if (response_sent) { + p_tcb->exec_write_rsp_trans_id = trans_id; + } for (i = 0; i < GATT_MAX_APPS; i++) { if (p_tcb->prep_cnt[i]) { gatt_if = (tGATT_IF) (i + 1); @@ -657,6 +684,11 @@ void gatt_process_exec_write_req (tGATT_TCB *p_tcb, UINT8 op_code, UINT16 len, U gatt_sr_copy_prep_cnt_to_cback_cnt(p_tcb); } + /* Record trans_id if stack already sent response, to prevent app from sending duplicate */ + if (response_sent) { + p_tcb->exec_write_rsp_trans_id = trans_id; + } + for (i = 0; i < GATT_MAX_APPS; i++) { if (p_tcb->prep_cnt[i]) { gatt_if = (tGATT_IF) (i + 1); @@ -754,8 +786,7 @@ void gatt_process_read_multi_req (tGATT_TCB *p_tcb, UINT8 op_code, UINT16 len, U for (ll = 0; ll < p_tcb->sr_cmd.multi_req.num_handles; ll ++) { if ((p_msg = (tGATTS_RSP *)osi_malloc(sizeof(tGATTS_RSP))) != NULL) { - memset(p_msg, 0, sizeof(tGATTS_RSP)) - ; + memset(p_msg, 0, sizeof(tGATTS_RSP)); handle = p_tcb->sr_cmd.multi_req.handles[ll]; i_rcb = gatt_sr_find_i_rcb_by_handle(handle); @@ -1496,10 +1527,17 @@ void gatt_attr_process_prepare_write (tGATT_TCB *p_tcb, UINT8 i_rcb, UINT16 hand } } + /* sr_cmd enqueued at handle but no attribute branch ran (null DB/list or no exact handle). */ + if (trans_id != 0 && !is_need_prepare_write_rsp && !is_need_queue_data && + status == GATT_SUCCESS) { + status = GATT_INVALID_HANDLE; + } + if (is_need_queue_data){ queue_data = (tGATT_PREPARE_WRITE_QUEUE_DATA *)osi_malloc(len + sizeof(tGATT_PREPARE_WRITE_QUEUE_DATA)); if (queue_data == NULL){ status = GATT_PREPARE_Q_FULL; + is_need_prepare_write_rsp = FALSE; } else { queue_data->p_attr = p_attr_temp; queue_data->len = len; @@ -1509,7 +1547,16 @@ void gatt_attr_process_prepare_write (tGATT_TCB *p_tcb, UINT8 i_rcb, UINT16 hand if (prepare_record->queue == NULL) { prepare_record->queue = fixed_queue_new(QUEUE_SIZE_MAX); } - fixed_queue_enqueue(prepare_record->queue, queue_data, FIXED_QUEUE_MAX_TIMEOUT); + if (prepare_record->queue == NULL || + fixed_queue_length(prepare_record->queue) >= + fixed_queue_capacity(prepare_record->queue)) { + osi_free(queue_data); + queue_data = NULL; + status = GATT_PREPARE_Q_FULL; + is_need_prepare_write_rsp = FALSE; + } else { + fixed_queue_enqueue(prepare_record->queue, queue_data, FIXED_QUEUE_MAX_TIMEOUT); + } } } diff --git a/components/bt/host/bluedroid/stack/gatt/gatt_utils.c b/components/bt/host/bluedroid/stack/gatt/gatt_utils.c index 0782a32c16e..552177df82c 100644 --- a/components/bt/host/bluedroid/stack/gatt/gatt_utils.c +++ b/components/bt/host/bluedroid/stack/gatt/gatt_utils.c @@ -690,6 +690,9 @@ BOOLEAN gatt_add_an_item_to_list(tGATT_HDL_LIST_INFO *p_list, tGATT_HDL_LIST_ELE p_new->p_prev = p_old->p_prev; p_new->p_next = p_old; + if (p_old->p_prev != NULL) { + p_old->p_prev->p_next = p_new; + } p_old->p_prev = p_new; break; @@ -1231,6 +1234,10 @@ BOOLEAN gatt_parse_uuid_from_cmd(tBT_UUID *p_uuid_rec, UINT16 uuid_size, UINT8 * void gatt_start_rsp_timer(UINT16 clcb_idx) { tGATT_CLCB *p_clcb = gatt_clcb_find_by_idx(clcb_idx); + if (p_clcb == NULL) { + GATT_TRACE_ERROR("%s: no CLCB for clcb_idx=0x%x", __func__, clcb_idx); + return; + } p_clcb->rsp_timer_ent.param = (TIMER_PARAM_TYPE)p_clcb; btu_start_timer (&p_clcb->rsp_timer_ent, BTU_TTYPE_ATT_WAIT_FOR_RSP, GATT_WAIT_FOR_RSP_TOUT); @@ -2305,10 +2312,17 @@ void gatt_cleanup_upon_disc(BD_ADDR bda, UINT16 reason, tBT_TRANSPORT transport) UINT8 i; UINT16 conn_id; tGATT_REG *p_reg = NULL; - +#if (GATTS_INCLUDED == TRUE) + BD_ADDR bda_local; +#endif GATT_TRACE_DEBUG ("gatt_cleanup_upon_disc "); +#if (GATTS_INCLUDED == TRUE) + /* Copy in case bda points into p_tcb->peer_bda, which is invalid after gatt_tcb_free. */ + memcpy(bda_local, bda, BD_ADDR_LEN); +#endif + if ((p_tcb = gatt_find_tcb_by_addr(bda, transport)) != NULL) { GATT_TRACE_DEBUG ("found p_tcb "); gatt_set_ch_state(p_tcb, GATT_CH_CLOSE); @@ -2357,7 +2371,7 @@ void gatt_cleanup_upon_disc(BD_ADDR bda, UINT16 reason, tBT_TRANSPORT transport) BTM_Recovery_Pre_State(); } #if (GATTS_INCLUDED == TRUE) - gatt_delete_dev_from_srv_chg_clt_list(bda); + gatt_delete_dev_from_srv_chg_clt_list(bda_local); #endif // (GATTS_INCLUDED == TRUE) } /******************************************************************************* diff --git a/components/bt/host/bluedroid/stack/gatt/include/gatt_int.h b/components/bt/host/bluedroid/stack/gatt/include/gatt_int.h index 76432c4f1b6..0f2a7b08831 100644 --- a/components/bt/host/bluedroid/stack/gatt/include/gatt_int.h +++ b/components/bt/host/bluedroid/stack/gatt/include/gatt_int.h @@ -76,6 +76,10 @@ typedef UINT8 tGATT_SEC_ACTION; #define GATT_HDR_SIZE 3 /* 1B opcode + 2B handle */ +/* ATT Read By Type Response: Length field is 1 octet (max 255). */ +#define GATT_MAX_READ_BY_TYPE_PAIR_LEN 255 +#define GATT_MAX_READ_BY_TYPE_VALUE_LEN (GATT_MAX_READ_BY_TYPE_PAIR_LEN - 2) + /** * Wait for ATT cmd response timeout value (40 seconds). * The max connection supervision timeout is 32 seconds, @@ -421,6 +425,7 @@ typedef struct { UINT8 tcb_idx; #if (GATTS_INCLUDED == TRUE) tGATT_PREPARE_WRITE_RECORD prepare_write_record; /* prepare write packets record */ + UINT32 exec_write_rsp_trans_id; /* trans_id of auto-responded execute write */ #endif // (GATTS_INCLUDED == TRUE) } tGATT_TCB; diff --git a/components/bt/host/bluedroid/stack/hcic/hciblecmds.c b/components/bt/host/bluedroid/stack/hcic/hciblecmds.c index 901c672d20e..1dbe71b2886 100644 --- a/components/bt/host/bluedroid/stack/hcic/hciblecmds.c +++ b/components/bt/host/bluedroid/stack/hcic/hciblecmds.c @@ -3054,12 +3054,269 @@ UINT8 btsnd_hcic_ble_enable_monitor_adv(UINT8 enable) } #endif // #if (BLE_FEAT_ADV_MONITOR == TRUE) +#if (BLE_FEAT_DBAF == TRUE) +UINT8 btsnd_hcic_ble_set_decision_data(UINT8 adv_handle, UINT8 decision_type_flags, + UINT8 data_len, const UINT8 *p_data) +{ + BT_HDR *p; + UINT8 *pp; + UINT8 param_len; + + if (data_len > BLE_DECISION_DATA_MAX_LEN) { + return HCI_ERR_ILLEGAL_PARAMETER_FMT; + } + if (data_len > 0 && p_data == NULL) { + return HCI_ERR_ILLEGAL_PARAMETER_FMT; + } + + param_len = HCIC_PARAM_SIZE_SET_DECISION_DATA_HDR + data_len; + if (param_len > HCIC_PARAM_SIZE_SET_DECISION_DATA_MAX) { + return HCI_ERR_ILLEGAL_PARAMETER_FMT; + } + + HCIC_BLE_CMD_CREATED_U8(p, pp, param_len); + + UINT16_TO_STREAM(pp, HCI_BLE_SET_DECISION_DATA); + UINT8_TO_STREAM(pp, param_len); + UINT8_TO_STREAM(pp, adv_handle); + UINT8_TO_STREAM(pp, decision_type_flags); + UINT8_TO_STREAM(pp, data_len); + if (data_len > 0) { + ARRAY_TO_STREAM(pp, p_data, data_len); + } + + return btu_hcif_send_cmd_sync(LOCAL_BR_EDR_CONTROLLER_ID, p); +} + +UINT8 btsnd_hcic_ble_set_decision_instructions(UINT8 num_tests, const UINT8 *test_flags, + const UINT8 *test_fields, const UINT8 *test_params) +{ + BT_HDR *p; + UINT8 *pp; + UINT8 param_len; + + if (num_tests == 0 || num_tests > BLE_DECISION_MAX_TESTS) { + return HCI_ERR_ILLEGAL_PARAMETER_FMT; + } + if (test_flags == NULL || test_fields == NULL || test_params == NULL) { + return HCI_ERR_ILLEGAL_PARAMETER_FMT; + } + + param_len = HCIC_PARAM_SIZE_SET_DECISION_INSTRUCTIONS(num_tests); + if (param_len > HCIC_PARAM_SIZE_SET_DECISION_INSTRUCTIONS_MAX) { + return HCI_ERR_ILLEGAL_PARAMETER_FMT; + } + + HCIC_BLE_CMD_CREATED_U8(p, pp, param_len); + + UINT16_TO_STREAM(pp, HCI_BLE_SET_DECISION_INSTRUCTIONS); + UINT8_TO_STREAM(pp, param_len); + UINT8_TO_STREAM(pp, num_tests); + for (UINT8 i = 0; i < num_tests; i++) { + UINT8_TO_STREAM(pp, test_flags[i]); + UINT8_TO_STREAM(pp, test_fields[i]); + ARRAY_TO_STREAM(pp, test_params + i * BLE_DECISION_TEST_PARAM_LEN, + BLE_DECISION_TEST_PARAM_LEN); + } + + return btu_hcif_send_cmd_sync(LOCAL_BR_EDR_CONTROLLER_ID, p); +} +#endif // #if (BLE_FEAT_DBAF == TRUE) + +#if (BLE_FEAT_FRAME_SPACE_UPDATE == TRUE) +UINT8 btsnd_hcic_ble_frame_space_update(UINT16 conn_handle, UINT16 frame_space_min, + UINT16 frame_space_max, UINT8 phys, UINT16 spacing_types) +{ + BT_HDR *p; + UINT8 *pp; + + HCIC_BLE_CMD_CREATED_U8(p, pp, HCIC_PARAM_SIZE_FRAME_SPACE_UPDATE); + + UINT16_TO_STREAM(pp, HCI_BLE_FRAME_SPACE_UPDATE); + UINT8_TO_STREAM(pp, HCIC_PARAM_SIZE_FRAME_SPACE_UPDATE); + UINT16_TO_STREAM(pp, conn_handle); + UINT16_TO_STREAM(pp, frame_space_min); + UINT16_TO_STREAM(pp, frame_space_max); + UINT8_TO_STREAM(pp, phys); + UINT16_TO_STREAM(pp, spacing_types); + + btu_hcif_send_cmd(LOCAL_BR_EDR_CONTROLLER_ID, p); + return HCI_SUCCESS; +} +#endif // #if (BLE_FEAT_FRAME_SPACE_UPDATE == TRUE) + +#if (BLE_FEAT_LL_EXT_FEAT == TRUE) +BOOLEAN btsnd_hcic_ble_read_all_local_supp_features(void) +{ + BT_HDR *p; + UINT8 *pp; + + if ((p = HCI_GET_CMD_BUF(0)) == NULL) { + return FALSE; + } + pp = (UINT8 *)(p + 1); + p->len = HCIC_PREAMBLE_SIZE; + p->offset = 0; + + UINT16_TO_STREAM(pp, HCI_BLE_READ_ALL_LOCAL_SUPP_FEATURES); + UINT8_TO_STREAM(pp, 0); + + btu_hcif_send_cmd(LOCAL_BR_EDR_CONTROLLER_ID, p); + return TRUE; +} + +UINT8 btsnd_hcic_ble_read_all_remote_features(UINT16 conn_handle, UINT8 page_requested) +{ + BT_HDR *p; + UINT8 *pp; + + if (page_requested > BLE_LL_EXT_FEAT_MAX_PAGE) { + return HCI_ERR_ILLEGAL_PARAMETER_FMT; + } + + HCIC_BLE_CMD_CREATED_U8(p, pp, HCIC_PARAM_SIZE_READ_ALL_REMOTE_FEATURES); + + UINT16_TO_STREAM(pp, HCI_BLE_READ_ALL_REMOTE_FEATURES); + UINT8_TO_STREAM(pp, HCIC_PARAM_SIZE_READ_ALL_REMOTE_FEATURES); + UINT16_TO_STREAM(pp, conn_handle); + UINT8_TO_STREAM(pp, page_requested); + + btu_hcif_send_cmd(LOCAL_BR_EDR_CONTROLLER_ID, p); + return HCI_SUCCESS; +} +#endif // #if (BLE_FEAT_LL_EXT_FEAT == TRUE) + +#if (BLE_FEAT_LE_UTP == TRUE) +UINT8 btsnd_hcic_ble_enable_utp_ota_mode(UINT8 enable) +{ + BT_HDR *p; + UINT8 *pp; + + if (enable > 1) { + return HCI_ERR_ILLEGAL_PARAMETER_FMT; + } + + HCIC_BLE_CMD_CREATED_U8(p, pp, HCIC_PARAM_SIZE_ENABLE_UTP_OTA_MODE); + UINT16_TO_STREAM(pp, HCI_BLE_ENABLE_UTP_OTA_MODE); + UINT8_TO_STREAM(pp, HCIC_PARAM_SIZE_ENABLE_UTP_OTA_MODE); + UINT8_TO_STREAM(pp, enable); + + return btu_hcif_send_cmd_sync(LOCAL_BR_EDR_CONTROLLER_ID, p); +} + +UINT8 btsnd_hcic_ble_utp_send(UINT8 data_len, const UINT8 *p_data) +{ + BT_HDR *p; + UINT8 *pp; + UINT16 param_len; + + if (data_len == 0 || data_len > BLE_UTP_DATA_MAX_LEN || p_data == NULL) { + return HCI_ERR_ILLEGAL_PARAMETER_FMT; + } + + param_len = HCIC_PARAM_SIZE_UTP_SEND_HDR + data_len; + if (param_len > HCIC_PARAM_SIZE_UTP_SEND_MAX) { + return HCI_ERR_ILLEGAL_PARAMETER_FMT; + } + + HCIC_BLE_CMD_CREATED_U8(p, pp, param_len); + UINT16_TO_STREAM(pp, HCI_BLE_UTP_SEND); + UINT8_TO_STREAM(pp, param_len); + UINT8_TO_STREAM(pp, data_len); + ARRAY_TO_STREAM(pp, p_data, data_len); + + return btu_hcif_send_cmd_sync(LOCAL_BR_EDR_CONTROLLER_ID, p); +} +#endif // #if (BLE_FEAT_LE_UTP == TRUE) + +#if (BLE_FEAT_SHORTER_CONN_INTERVALS == TRUE) +UINT8 btsnd_hcic_ble_connection_rate_request(UINT16 conn_handle, UINT16 conn_interval_min, + UINT16 conn_interval_max, UINT16 subrate_min, + UINT16 subrate_max, UINT16 max_latency, + UINT16 continuation_number, UINT16 supervision_timeout, + UINT16 min_ce_len, UINT16 max_ce_len) +{ + BT_HDR *p; + UINT8 *pp; + + HCI_TRACE_DEBUG("hci conn rate req, handle %u int [%u, %u] subrate [%u, %u] latency %u cont %u timeout %u ce [%u, %u]", + conn_handle, conn_interval_min, conn_interval_max, subrate_min, subrate_max, + max_latency, continuation_number, supervision_timeout, min_ce_len, max_ce_len); + + HCIC_BLE_CMD_CREATED_U8(p, pp, HCIC_PARAM_SIZE_CONNECTION_RATE_REQUEST); + UINT16_TO_STREAM(pp, HCI_BLE_CONNECTION_RATE_REQUEST); + UINT8_TO_STREAM(pp, HCIC_PARAM_SIZE_CONNECTION_RATE_REQUEST); + UINT16_TO_STREAM(pp, conn_handle); + UINT16_TO_STREAM(pp, conn_interval_min); + UINT16_TO_STREAM(pp, conn_interval_max); + UINT16_TO_STREAM(pp, subrate_min); + UINT16_TO_STREAM(pp, subrate_max); + UINT16_TO_STREAM(pp, max_latency); + UINT16_TO_STREAM(pp, continuation_number); + UINT16_TO_STREAM(pp, supervision_timeout); + UINT16_TO_STREAM(pp, min_ce_len); + UINT16_TO_STREAM(pp, max_ce_len); + + btu_hcif_send_cmd(LOCAL_BR_EDR_CONTROLLER_ID, p); + return HCI_SUCCESS; +} + +UINT8 btsnd_hcic_ble_set_default_rate_parameters(UINT16 conn_interval_min, UINT16 conn_interval_max, + UINT16 subrate_min, UINT16 subrate_max, + UINT16 max_latency, UINT16 continuation_number, + UINT16 supervision_timeout, UINT16 min_ce_len, + UINT16 max_ce_len) +{ + BT_HDR *p; + UINT8 *pp; + + HCI_TRACE_DEBUG("hci set default rate, int [%u, %u] subrate [%u, %u] latency %u cont %u timeout %u ce [%u, %u]", + conn_interval_min, conn_interval_max, subrate_min, subrate_max, + max_latency, continuation_number, supervision_timeout, min_ce_len, max_ce_len); + + HCIC_BLE_CMD_CREATED_U8(p, pp, HCIC_PARAM_SIZE_SET_DEFAULT_RATE_PARAMETERS); + UINT16_TO_STREAM(pp, HCI_BLE_SET_DEFAULT_RATE_PARAMETERS); + UINT8_TO_STREAM(pp, HCIC_PARAM_SIZE_SET_DEFAULT_RATE_PARAMETERS); + UINT16_TO_STREAM(pp, conn_interval_min); + UINT16_TO_STREAM(pp, conn_interval_max); + UINT16_TO_STREAM(pp, subrate_min); + UINT16_TO_STREAM(pp, subrate_max); + UINT16_TO_STREAM(pp, max_latency); + UINT16_TO_STREAM(pp, continuation_number); + UINT16_TO_STREAM(pp, supervision_timeout); + UINT16_TO_STREAM(pp, min_ce_len); + UINT16_TO_STREAM(pp, max_ce_len); + + return btu_hcif_send_cmd_sync(LOCAL_BR_EDR_CONTROLLER_ID, p); +} + +BOOLEAN btsnd_hcic_ble_read_min_supp_conn_interval(void) +{ + BT_HDR *p; + UINT8 *pp; + + if ((p = HCI_GET_CMD_BUF(0)) == NULL) { + return FALSE; + } + pp = (UINT8 *)(p + 1); + p->len = HCIC_PREAMBLE_SIZE; + p->offset = 0; + + UINT16_TO_STREAM(pp, HCI_BLE_READ_MIN_SUPP_CONN_INTERVAL); + UINT8_TO_STREAM(pp, 0); + + btu_hcif_send_cmd(LOCAL_BR_EDR_CONTROLLER_ID, p); + return TRUE; +} +#endif // #if (BLE_FEAT_SHORTER_CONN_INTERVALS == TRUE) + #if (BT_BLE_FEAT_PAWR_EN == TRUE) UINT8 btsnd_hcic_ble_set_periodic_adv_subevt_data(UINT8 adv_handle, UINT8 num_subevents_with_data, ble_subevent_params *subevent_params) { BT_HDR *p; UINT8 *pp; - uint8_t param_len = 0; + unsigned total_len; + UINT8 param_len; HCI_TRACE_DEBUG("hci set PA subevent data, adv_handle %d num_subevents_with_data %d", adv_handle, num_subevents_with_data); @@ -3067,10 +3324,19 @@ UINT8 btsnd_hcic_ble_set_periodic_adv_subevt_data(UINT8 adv_handle, UINT8 num_su HCI_TRACE_ERROR("%s error\n", __func__); return HCI_ERR_ILLEGAL_PARAMETER_FMT; } - param_len += HCIC_PARAM_SIZE_SET_PA_SUBEVT_DATA_PARAMS_LEN; + total_len = HCIC_PARAM_SIZE_SET_PA_SUBEVT_DATA_PARAMS_LEN; for (UINT8 i = 0; i < num_subevents_with_data; i++) { + if (subevent_params[i].subevent_data_len > sizeof(subevent_params[i].data)) { + HCI_TRACE_ERROR("%s sub_data_len %u>%u", __func__, + (unsigned)subevent_params[i].subevent_data_len, + (unsigned)sizeof(subevent_params[i].data)); + return HCI_ERR_ILLEGAL_PARAMETER_FMT; + } + + unsigned add_len = 4u + (unsigned)subevent_params[i].subevent_data_len; + HCI_TRACE_DEBUG("subevent_params: subevent %d response_slot_start %d response_slot_count %d subevent_data_len %d", subevent_params[i].subevent, subevent_params[i].response_slot_start, subevent_params[i].response_slot_count, subevent_params[i].subevent_data_len); @@ -3079,9 +3345,17 @@ UINT8 btsnd_hcic_ble_set_periodic_adv_subevt_data(UINT8 adv_handle, UINT8 num_su esp_log_buffer_hex_internal("data", subevent_params[i].data, subevent_params[i].subevent_data_len, ESP_LOG_DEBUG); } - param_len += (4 + subevent_params[i].subevent_data_len); + /* Avoid unsigned wrap when add_len > HCI_COMMAND_SIZE. */ + if (total_len > (unsigned)HCI_COMMAND_SIZE || + add_len > (unsigned)HCI_COMMAND_SIZE - total_len) { + HCI_TRACE_ERROR("%s total>HCI_CMD_SZ", __func__); + return HCI_ERR_ILLEGAL_PARAMETER_FMT; + } + total_len += add_len; } + param_len = (UINT8)total_len; + HCIC_BLE_CMD_CREATED_U8(p, pp, param_len); pp = (UINT8 *)(p + 1); @@ -3113,6 +3387,11 @@ UINT8 btsnd_hcic_ble_set_periodic_adv_rsp_data(UINT16 sync_handle, UINT16 req_ev HCI_TRACE_DEBUG("hci set PA rsp data, sync_handle %d req_evt %d req_subevt %d rsp_subevt %d rsp_slot %d rsp_data_len %d", sync_handle, req_evt, req_subevt, rsp_subevt, rsp_slot, rsp_data_len); + if (rsp_data_len > HCIC_PA_RSP_DATA_PAYLOAD_MAX) { + HCI_TRACE_ERROR("%s rsp_len %u>%u", __func__, rsp_data_len, HCIC_PA_RSP_DATA_PAYLOAD_MAX); + return HCI_ERR_ILLEGAL_PARAMETER_FMT; + } + HCIC_BLE_CMD_CREATED_U8(p, pp, HCIC_PARAM_SIZE_SET_PA_RESPONSE_DATA_PARAMS_LEN + rsp_data_len); pp = (UINT8 *)(p + 1); @@ -3141,6 +3420,12 @@ UINT8 btsnd_hcic_ble_set_periodic_sync_subevt(UINT16 sync_handle, UINT16 periodi HCI_TRACE_DEBUG("hci set PA sync subevent, sync_handle %d periodic_adv_properties %d num_subevents_to_sync %d", sync_handle, periodic_adv_properties, num_subevents_to_sync); + + if (num_subevents_to_sync > HCIC_PA_SYNC_SUBEVT_NUM_MAX) { + HCI_TRACE_ERROR("%s n_sync %u>%u", __func__, num_subevents_to_sync, HCIC_PA_SYNC_SUBEVT_NUM_MAX); + return HCI_ERR_ILLEGAL_PARAMETER_FMT; + } + for (UINT8 i = 0; i < num_subevents_to_sync; i++) { HCI_TRACE_DEBUG("subevt[%d] = %d", i, subevt[i]); @@ -3485,3 +3770,42 @@ UINT8 btsnd_hcic_ble_cs_procedure_enable(UINT16 conn_handle, UINT8 config_id, UI return TRUE; } #endif // (BT_BLE_FEAT_CHANNEL_SOUNDING == TRUE) + +#if (BT_BLE_FEAT_CS_SECURITY_REQUIREMENTS == TRUE) +UINT8 btsnd_hcic_ble_cs_set_security_requirements(UINT16 conn_handle, UINT64 cs_security_requirements) +{ + BT_HDR *p; + UINT8 *pp; + + HCI_TRACE_DEBUG("cs set security requirements conn_handle %d", conn_handle); + + HCIC_BLE_CMD_CREATED_U8(p, pp, HCIC_PARAM_SIZE_CS_SET_SECURITY_REQUIREMENTS_LEN); + + pp = (UINT8 *)(p + 1); + + UINT16_TO_STREAM(pp, HCI_BLE_CS_SET_SECURITY_REQUIREMENTS); + UINT8_TO_STREAM(pp, HCIC_PARAM_SIZE_CS_SET_SECURITY_REQUIREMENTS_LEN); + UINT16_TO_STREAM(pp, conn_handle); + ARRAY_TO_STREAM(pp, (UINT8 *)&cs_security_requirements, 8); + + return btu_hcif_send_cmd_sync(LOCAL_BR_EDR_CONTROLLER_ID, p); +} + +UINT8 btsnd_hcic_ble_cs_set_default_security_requirements(UINT64 cs_security_requirements) +{ + BT_HDR *p; + UINT8 *pp; + + HCI_TRACE_DEBUG("cs set default security requirements"); + + HCIC_BLE_CMD_CREATED_U8(p, pp, HCIC_PARAM_SIZE_CS_SET_DEFAULT_SECURITY_REQUIREMENTS_LEN); + + pp = (UINT8 *)(p + 1); + + UINT16_TO_STREAM(pp, HCI_BLE_CS_SET_DEFAULT_SECURITY_REQUIREMENTS); + UINT8_TO_STREAM(pp, HCIC_PARAM_SIZE_CS_SET_DEFAULT_SECURITY_REQUIREMENTS_LEN); + ARRAY_TO_STREAM(pp, (UINT8 *)&cs_security_requirements, 8); + + return btu_hcif_send_cmd_sync(LOCAL_BR_EDR_CONTROLLER_ID, p); +} +#endif // (BT_BLE_FEAT_CS_SECURITY_REQUIREMENTS == TRUE) diff --git a/components/bt/host/bluedroid/stack/hid/hidd_conn.c b/components/bt/host/bluedroid/stack/hid/hidd_conn.c index 6984bc9a1c0..576d82bdbfb 100644 --- a/components/bt/host/bluedroid/stack/hid/hidd_conn.c +++ b/components/bt/host/bluedroid/stack/hid/hidd_conn.c @@ -507,6 +507,11 @@ static void hidd_l2cif_data_ind(uint16_t cid, BT_HDR *p_msg) osi_free(p_msg); return; } + if (p_msg->len < 1) { + HIDD_TRACE_WARNING ("HID-Device Rcvd Empty L2CAP data"); + osi_free(p_msg); + return; + } msg_type = HID_GET_TRANS_FROM_HDR(*p_data); param = HID_GET_PARAM_FROM_HDR(*p_data); if (msg_type == HID_TRANS_DATA && cid == p_hcon->intr_cid) { diff --git a/components/bt/host/bluedroid/stack/hid/hidh_conn.c b/components/bt/host/bluedroid/stack/hid/hidh_conn.c index da880a1ebc3..170bfce7e92 100644 --- a/components/bt/host/bluedroid/stack/hid/hidh_conn.c +++ b/components/bt/host/bluedroid/stack/hid/hidh_conn.c @@ -766,6 +766,11 @@ static void hidh_l2cif_data_ind (UINT16 l2cap_cid, BT_HDR *p_msg) return; } + if (p_msg->len < 1) { + HIDH_TRACE_WARNING ("HID-Host Rcvd Empty L2CAP data"); + osi_free (p_msg); + return; + } ttype = HID_GET_TRANS_FROM_HDR(*p_data); param = HID_GET_PARAM_FROM_HDR(*p_data); diff --git a/components/bt/host/bluedroid/stack/include/stack/btm_api.h b/components/bt/host/bluedroid/stack/include/stack/btm_api.h index 15dccaa115b..b90baf478e8 100644 --- a/components/bt/host/bluedroid/stack/include/stack/btm_api.h +++ b/components/bt/host/bluedroid/stack/include/stack/btm_api.h @@ -173,7 +173,14 @@ typedef void (tBTM_VSC_CMPL_CB) (tBTM_VSC_CMPL *p1); */ // typedef UINT8 (tBTM_FILTER_CB) (BD_ADDR bd_addr, DEV_CLASS dc); -typedef void (tBTM_DTM_CMD_CMPL_CBACK) (void *p1); +/* + * DTM (Direct Test Mode) command complete callback. + * + * The controller returns a variable-length parameter block depending on the + * specific LE test command. Propagate the parameter length so upper layers can + * validate before parsing and avoid OOB reads on malformed/truncated responses. + */ +typedef void (tBTM_DTM_CMD_CMPL_CBACK) (UINT8 *p, UINT16 len); typedef void (tBTM_SET_RAND_ADDR_CBACK) (UINT8 status); diff --git a/components/bt/host/bluedroid/stack/include/stack/btm_ble_api.h b/components/bt/host/bluedroid/stack/include/stack/btm_ble_api.h index 6084ffa6afa..eae0a71b2ee 100644 --- a/components/bt/host/bluedroid/stack/include/stack/btm_ble_api.h +++ b/components/bt/host/bluedroid/stack/include/stack/btm_ble_api.h @@ -624,6 +624,14 @@ typedef struct { BOOLEAN directed; BOOLEAN scannable; BOOLEAN connetable; + /* Per-set on-air address policy, captured at BTM_BleSetExtendedAdvParams() + * time. For now we only consider CONTROLLER_RPA_LIST_ENABLE == TRUE; + * CONTROLLER_RPA_LIST_ENABLE == FALSE is out of scope temporarily. The global + * addr_mgnt_cb is a single slot shared across all sets, so in multi-ADV + * it does not necessarily reflect the policy used for a given connection. */ + tBLE_ADDR_TYPE own_addr_type; + BOOLEAN rand_addr_set; + BD_ADDR rand_addr; } tBTM_BLE_EXTENDED_INST; typedef struct { @@ -631,6 +639,10 @@ typedef struct { UINT8 scan_duplicate; } tBTM_BLE_EXTENDED_CB; +/* Defined in btm_ble_5_gap.c. Exposed for per-set address lookup from + * SMP / BTM at LE connection complete. */ +extern tBTM_BLE_EXTENDED_CB extend_adv_cb; + #define BTM_BLE_GAP_SET_EXT_ADV_PROP_CONNECTABLE (1 << 0) #define BTM_BLE_GAP_SET_EXT_ADV_PROP_SCANNABLE (1 << 1) #define BTM_BLE_GAP_SET_EXT_ADV_PROP_DIRECTED (1 << 2) @@ -978,7 +990,33 @@ typedef void (tBTM_UPDATE_DUPLICATE_EXCEPTIONAL_LIST_CMPL_CBACK) (tBTM_STATUS st #define BTM_BLE_5_GAP_READ_MONITOR_ADV_LIST_SIZE_COMPLETE_EVT 74 #define BTM_BLE_5_GAP_ENABLE_MONITOR_ADV_COMPLETE_EVT 75 #endif // #if (BLE_FEAT_ADV_MONITOR == TRUE) -#define BTM_BLE_5_GAP_UNKNOWN_EVT 76 +#if (BLE_FEAT_LL_EXT_FEAT == TRUE) +#define BTM_BLE_5_GAP_READ_ALL_LOCAL_SUPP_FEAT_COMPLETE_EVT 76 +#define BTM_BLE_5_GAP_READ_ALL_REMOTE_FEAT_COMPLETE_EVT 77 +#endif // #if (BLE_FEAT_LL_EXT_FEAT == TRUE) +#if (BLE_FEAT_FRAME_SPACE_UPDATE == TRUE) +#define BTM_BLE_5_GAP_FRAME_SPACE_UPDATE_COMPLETE_EVT 78 +#endif // #if (BLE_FEAT_FRAME_SPACE_UPDATE == TRUE) +#if (BLE_FEAT_DBAF == TRUE) +#define BTM_BLE_5_GAP_SET_DECISION_DATA_COMPLETE_EVT 79 +#define BTM_BLE_5_GAP_SET_DECISION_INSTRUCTIONS_COMPLETE_EVT 80 +#endif // #if (BLE_FEAT_DBAF == TRUE) +#if (BLE_FEAT_SHORTER_CONN_INTERVALS == TRUE) +#define BTM_BLE_5_GAP_CONNECTION_RATE_REQUEST_COMPLETE_EVT 81 +#define BTM_BLE_5_GAP_CONN_RATE_CHANGE_EVT 82 +#define BTM_BLE_5_GAP_SET_DEFAULT_RATE_PARAMETERS_COMPLETE_EVT 86 +#define BTM_BLE_5_GAP_READ_MIN_SUPP_CONN_INTERVAL_COMPLETE_EVT 87 +#endif // #if (BLE_FEAT_SHORTER_CONN_INTERVALS == TRUE) +#if (BLE_FEAT_LE_UTP == TRUE) +#define BTM_BLE_5_GAP_ENABLE_UTP_OTA_MODE_COMPLETE_EVT 83 +#define BTM_BLE_5_GAP_UTP_SEND_COMPLETE_EVT 84 +#define BTM_BLE_5_GAP_UTP_RECEIVE_EVT 85 +#endif // #if (BLE_FEAT_LE_UTP == TRUE) +#if (BT_BLE_FEAT_CS_SECURITY_REQUIREMENTS == TRUE) +#define BTM_BLE_GAP_CS_SET_SECURITY_REQUIREMENTS_CMPL_EVT 88 +#define BTM_BLE_GAP_CS_SET_DEFAULT_SECURITY_REQUIREMENTS_CMPL_EVT 89 +#endif // (BT_BLE_FEAT_CS_SECURITY_REQUIREMENTS == TRUE) +#define BTM_BLE_5_GAP_UNKNOWN_EVT 90 typedef UINT8 tBTM_BLE_5_GAP_EVENT; #if (BLE_FEAT_ISO_EN == TRUE) @@ -1228,6 +1266,76 @@ typedef struct { } tBTM_BLE_MONITOR_ADV_LIST_SIZE; #endif // #if (BLE_FEAT_ADV_MONITOR == TRUE) +#if (BLE_FEAT_LL_EXT_FEAT == TRUE) +#ifndef BLE_LL_EXT_FEAT_DATA_LEN +#define BLE_LL_EXT_FEAT_DATA_LEN 248 +#endif + +typedef struct { + tBTM_STATUS status; + UINT8 max_page; + UINT8 *le_features; +} tBTM_BLE_READ_ALL_LOCAL_SUPP_FEAT; + +typedef struct { + tBTM_STATUS status; + UINT16 conn_handle; + UINT8 max_remote_page; + UINT8 max_valid_page; + UINT8 *le_features; +} tBTM_BLE_READ_ALL_REMOTE_FEAT; +#endif // #if (BLE_FEAT_LL_EXT_FEAT == TRUE) + +#if (BLE_FEAT_FRAME_SPACE_UPDATE == TRUE) +typedef struct { + tBTM_STATUS status; + UINT16 conn_handle; + UINT8 initiator; + UINT16 frame_space; + UINT8 phys; + UINT16 spacing_types; +} tBTM_BLE_FRAME_SPACE_UPDATE; +#endif // #if (BLE_FEAT_FRAME_SPACE_UPDATE == TRUE) + +#if (BLE_FEAT_SHORTER_CONN_INTERVALS == TRUE) +typedef struct { + tBTM_STATUS status; + UINT16 conn_handle; +} tBTM_BLE_CONNECTION_RATE_REQUEST_CMPL; + +typedef struct { + tBTM_STATUS status; + UINT16 conn_handle; + UINT16 conn_interval; + UINT16 subrate_factor; + UINT16 peripheral_latency; + UINT16 continuation_number; + UINT16 supervision_timeout; +} tBTM_BLE_CONN_RATE_CHANGE; + +#define BTM_BLE_MAX_CONN_INTERVAL_GROUPS 41 + +typedef struct { + UINT16 min_125us; + UINT16 max_125us; + UINT16 stride_125us; +} tBTM_BLE_MIN_CONN_INTERVAL_GROUP; + +typedef struct { + tBTM_STATUS status; + UINT8 min_supported_conn_interval; + UINT8 num_groups; + tBTM_BLE_MIN_CONN_INTERVAL_GROUP *groups; +} tBTM_BLE_READ_MIN_SUPP_CONN_INTERVAL; +#endif // #if (BLE_FEAT_SHORTER_CONN_INTERVALS == TRUE) + +#if (BLE_FEAT_LE_UTP == TRUE) +typedef struct { + UINT8 len; + UINT8 *data; +} tBTM_BLE_UTP_RECEIVE; +#endif // #if (BLE_FEAT_LE_UTP == TRUE) + typedef struct { UINT16 sync_handle; UINT8 tx_power; @@ -1451,6 +1559,17 @@ typedef struct { UINT16 conn_handle; } tBTM_BLE_CS_SEC_ENABLE_CMPL_EVT; +#if (BT_BLE_FEAT_CS_SECURITY_REQUIREMENTS == TRUE) +typedef struct { + UINT8 status; + UINT16 conn_handle; +} tBTM_BLE_CS_SET_SECURITY_REQUIREMENTS_CMPL_EVT; + +typedef struct { + UINT8 status; +} tBTM_BLE_CS_SET_DEFAULT_SECURITY_REQUIREMENTS_CMPL_EVT; +#endif // (BT_BLE_FEAT_CS_SECURITY_REQUIREMENTS == TRUE) + typedef struct { UINT8 status; UINT16 conn_handle; @@ -1528,7 +1647,6 @@ typedef struct { typedef struct { UINT8 status; - UINT16 conn_handle; UINT8 num_config_supported; UINT16 max_consecutive_proc_supported; UINT8 num_ant_supported; @@ -1938,6 +2056,25 @@ typedef union { tBTM_BLE_MONITOR_ADV_REPORT monitor_adv_report; tBTM_BLE_MONITOR_ADV_LIST_SIZE monitor_adv_list_size; #endif // #if (BLE_FEAT_ADV_MONITOR == TRUE) +#if (BLE_FEAT_LL_EXT_FEAT == TRUE) + tBTM_BLE_READ_ALL_LOCAL_SUPP_FEAT read_all_local_supp_feat; + tBTM_BLE_READ_ALL_REMOTE_FEAT read_all_remote_feat; +#endif // #if (BLE_FEAT_LL_EXT_FEAT == TRUE) +#if (BLE_FEAT_FRAME_SPACE_UPDATE == TRUE) + tBTM_BLE_FRAME_SPACE_UPDATE frame_space_update; +#endif // #if (BLE_FEAT_FRAME_SPACE_UPDATE == TRUE) +#if (BLE_FEAT_SHORTER_CONN_INTERVALS == TRUE) + tBTM_BLE_CONNECTION_RATE_REQUEST_CMPL conn_rate_request; + tBTM_BLE_CONN_RATE_CHANGE conn_rate_change; + tBTM_BLE_READ_MIN_SUPP_CONN_INTERVAL read_min_supp_conn_interval; +#endif // #if (BLE_FEAT_SHORTER_CONN_INTERVALS == TRUE) +#if (BLE_FEAT_LE_UTP == TRUE) + tBTM_BLE_UTP_RECEIVE utp_receive; +#endif // #if (BLE_FEAT_LE_UTP == TRUE) +#if (BT_BLE_FEAT_CS_SECURITY_REQUIREMENTS == TRUE) + tBTM_BLE_CS_SET_SECURITY_REQUIREMENTS_CMPL_EVT cs_set_security_requirements; + tBTM_BLE_CS_SET_DEFAULT_SECURITY_REQUIREMENTS_CMPL_EVT cs_set_default_security_requirements; +#endif // (BT_BLE_FEAT_CS_SECURITY_REQUIREMENTS == TRUE) } tBTM_BLE_5_GAP_CB_PARAMS; typedef struct { @@ -2726,7 +2863,7 @@ void BTM_BleClearWhitelist(void); ** p_cmd_cmpl_cback - Command Complete callback ** *******************************************************************************/ -void BTM_BleReceiverTest(UINT8 rx_freq, tBTM_CMPL_CB *p_cmd_cmpl_cback); +void BTM_BleReceiverTest(UINT8 rx_freq, tBTM_DTM_CMD_CMPL_CBACK *p_cmd_cmpl_cback); /******************************************************************************* @@ -2742,7 +2879,7 @@ void BTM_BleReceiverTest(UINT8 rx_freq, tBTM_CMPL_CB *p_cmd_cmpl_cback); ** *******************************************************************************/ void BTM_BleTransmitterTest(UINT8 tx_freq, UINT8 test_data_len, - UINT8 packet_payload, tBTM_CMPL_CB *p_cmd_cmpl_cback); + UINT8 packet_payload, tBTM_DTM_CMD_CMPL_CBACK *p_cmd_cmpl_cback); /******************************************************************************* ** @@ -2753,7 +2890,7 @@ void BTM_BleTransmitterTest(UINT8 tx_freq, UINT8 test_data_len, ** Parameter p_cmd_cmpl_cback - Command complete callback ** *******************************************************************************/ -void BTM_BleTestEnd(tBTM_CMPL_CB *p_cmd_cmpl_cback); +void BTM_BleTestEnd(tBTM_DTM_CMD_CMPL_CBACK *p_cmd_cmpl_cback); /******************************************************************************* ** @@ -3053,6 +3190,20 @@ tBTM_STATUS BTM_BleExtAdvSetRemove(UINT8 instance); tBTM_STATUS BTM_BleExtAdvSetClear(void); +/******************************************************************************* +** +** Function BTM_BleGetExtAdvInstByConHandle +** +** Description Map an LE connection handle to the ext-adv instance +** that produced it. +** +** Returns Instance index on success, 0xFF if no match (e.g. +** initiator role or legacy advertising). +** +*******************************************************************************/ +UINT8 BTM_BleGetExtAdvInstByConHandle(UINT16 con_handle); + + tBTM_STATUS BTM_BlePeriodicAdvSetParams(UINT8 instance, tBTM_BLE_Periodic_Adv_Params *params); tBTM_STATUS BTM_BlePeriodicAdvCfgDataRaw(UINT8 instance, UINT16 len, UINT8 *data, BOOLEAN only_update_did); @@ -3084,12 +3235,47 @@ tBTM_STATUS BTM_BleClearMonitorAdvList(void); tBTM_STATUS BTM_BleReadMonitorAdvListSize(void); tBTM_STATUS BTM_BleEnableMonitorAdv(UINT8 enable); #endif // #if (BLE_FEAT_ADV_MONITOR == TRUE) + +#if (BLE_FEAT_DBAF == TRUE) +tBTM_STATUS BTM_BleSetDecisionData(UINT8 adv_handle, UINT8 decision_type_flags, + UINT8 data_len, const UINT8 *p_data); +tBTM_STATUS BTM_BleSetDecisionInstructions(UINT8 num_tests, const UINT8 *test_flags, + const UINT8 *test_fields, const UINT8 *test_params); +#endif // #if (BLE_FEAT_DBAF == TRUE) + +#if (BLE_FEAT_FRAME_SPACE_UPDATE == TRUE) +tBTM_STATUS BTM_BleFrameSpaceUpdate(UINT16 conn_handle, UINT16 frame_space_min, + UINT16 frame_space_max, UINT8 phys, UINT16 spacing_types); +#endif // #if (BLE_FEAT_FRAME_SPACE_UPDATE == TRUE) + +#if (BLE_FEAT_LL_EXT_FEAT == TRUE) +tBTM_STATUS BTM_BleReadAllLocalSuppFeatures(void); +tBTM_STATUS BTM_BleReadAllRemoteFeatures(UINT16 conn_handle, UINT8 page_requested); +#endif // #if (BLE_FEAT_LL_EXT_FEAT == TRUE) + +#if (BLE_FEAT_SHORTER_CONN_INTERVALS == TRUE) +tBTM_STATUS BTM_BleConnectionRateRequest(UINT16 conn_handle, UINT16 conn_interval_min, + UINT16 conn_interval_max, UINT16 subrate_min, + UINT16 subrate_max, UINT16 max_latency, + UINT16 continuation_number, UINT16 supervision_timeout, + UINT16 min_ce_len, UINT16 max_ce_len); +void BTM_BleSetDefaultRateParameters(UINT16 conn_interval_min, UINT16 conn_interval_max, + UINT16 subrate_min, UINT16 subrate_max, UINT16 max_latency, + UINT16 continuation_number, UINT16 supervision_timeout, + UINT16 min_ce_len, UINT16 max_ce_len); +tBTM_STATUS BTM_BleReadMinSuppConnInterval(void); +#endif // #if (BLE_FEAT_SHORTER_CONN_INTERVALS == TRUE) + +#if (BLE_FEAT_LE_UTP == TRUE) +tBTM_STATUS BTM_BleEnableUtpOtaMode(UINT8 enable); +tBTM_STATUS BTM_BleUtpSend(UINT8 data_len, const UINT8 *p_data); +#endif // #if (BLE_FEAT_LE_UTP == TRUE) #endif // #if (BLE_50_FEATURE_SUPPORT == TRUE) #if (BLE_50_DTM_TEST_EN == TRUE) -void BTM_BleEnhancedReceiverTest(UINT8 rx_freq, UINT8 phy, UINT8 modulation_index, tBTM_CMPL_CB *p_cmd_cmpl_cback); +void BTM_BleEnhancedReceiverTest(UINT8 rx_freq, UINT8 phy, UINT8 modulation_index, tBTM_DTM_CMD_CMPL_CBACK *p_cmd_cmpl_cback); -void BTM_BleEnhancedTransmitterTest(UINT8 tx_freq, UINT8 test_data_len, UINT8 packet_payload, UINT8 phy, tBTM_CMPL_CB *p_cmd_cmpl_cback); +void BTM_BleEnhancedTransmitterTest(UINT8 tx_freq, UINT8 test_data_len, UINT8 packet_payload, UINT8 phy, tBTM_DTM_CMD_CMPL_CBACK *p_cmd_cmpl_cback); #endif // #if (BLE_50_DTM_TEST_EN == TRUE) #if (BLE_FEAT_PERIODIC_ADV_SYNC_TRANSFER == TRUE) @@ -3229,5 +3415,9 @@ void BTM_BleGapCsSetProcPatams(UINT16 conn_handle, UINT8 config_id, UINT16 max_p UINT8 SNR_control_initiator, UINT8 SNR_control_reflector); void BTM_BleGapCsProcEnable(UINT16 conn_handle, UINT8 config_id, UINT8 enable); #endif // (BT_BLE_FEAT_CHANNEL_SOUNDING == TRUE) +#if (BT_BLE_FEAT_CS_SECURITY_REQUIREMENTS == TRUE) +void BTM_BleGapCsSetSecurityRequirements(UINT16 conn_handle, UINT64 cs_security_requirements); +void BTM_BleGapCsSetDefaultSecurityRequirements(UINT64 cs_security_requirements); +#endif // (BT_BLE_FEAT_CS_SECURITY_REQUIREMENTS == TRUE) #endif diff --git a/components/bt/host/bluedroid/stack/include/stack/btu.h b/components/bt/host/bluedroid/stack/include/stack/btu.h index c2a80916d37..5d2a821d8f5 100644 --- a/components/bt/host/bluedroid/stack/include/stack/btu.h +++ b/components/bt/host/bluedroid/stack/include/stack/btu.h @@ -172,6 +172,9 @@ typedef void (*tBTU_EVENT_CALLBACK)(BT_HDR *p_hdr); /* BTU internal timer for BR/EDR power control*/ #define BTU_TTYPE_BTM_BREDR_PWR_CTRL 112 +/* L2CAP host-driven Create_Connection retry back-off timer */ +#define BTU_TTYPE_L2CAP_LINK_RETRY 113 + /* BTU Task Signal */ typedef enum { SIG_BTU_START_UP = 0, @@ -290,7 +293,7 @@ void btu_hcif_cmd_timeout (UINT8 controller_id); void btu_init_core(void); void btu_free_core(void); -void BTU_StartUp(void); +bool BTU_StartUp(void); void BTU_ShutDown(void); void btu_task_start_up(void *param); diff --git a/components/bt/host/bluedroid/stack/include/stack/hcidefs.h b/components/bt/host/bluedroid/stack/include/stack/hcidefs.h index 43eb352bb11..6622e6bd434 100644 --- a/components/bt/host/bluedroid/stack/include/stack/hcidefs.h +++ b/components/bt/host/bluedroid/stack/include/stack/hcidefs.h @@ -439,6 +439,10 @@ #define HCI_BLE_ENABLE_MONITOR_ADV (0x009C | HCI_GRP_BLE_CMDS) #endif // #if (BLE_FEAT_ADV_MONITOR == TRUE) +#if (BLE_FEAT_FRAME_SPACE_UPDATE == TRUE) +#define HCI_BLE_FRAME_SPACE_UPDATE (0x009D | HCI_GRP_BLE_CMDS) +#endif // #if (BLE_FEAT_FRAME_SPACE_UPDATE == TRUE) + #if (BLE_FEAT_POWER_CONTROL_EN == TRUE) #define HCI_BLE_ENH_READ_TRANS_POWER_LEVEL (0x0076 | HCI_GRP_BLE_CMDS) #define HCI_BLE_READ_REMOTE_TRANS_POWER_LEVEL (0x0077 | HCI_GRP_BLE_CMDS) @@ -464,6 +468,16 @@ #define HCI_BLE_SET_PERIOD_ADV_PARAMS_V2 (0x0086 | HCI_GRP_BLE_CMDS) #endif // #if (BT_BLE_FEAT_PAWR_EN == TRUE) +#if (BLE_FEAT_LL_EXT_FEAT == TRUE) +#define HCI_BLE_READ_ALL_LOCAL_SUPP_FEATURES (0x0087 | HCI_GRP_BLE_CMDS) +#define HCI_BLE_READ_ALL_REMOTE_FEATURES (0x0088 | HCI_GRP_BLE_CMDS) +#endif // #if (BLE_FEAT_LL_EXT_FEAT == TRUE) + +#if (BLE_FEAT_DBAF == TRUE) +#define HCI_BLE_SET_DECISION_DATA (0x0080 | HCI_GRP_BLE_CMDS) +#define HCI_BLE_SET_DECISION_INSTRUCTIONS (0x0081 | HCI_GRP_BLE_CMDS) +#endif // #if (BLE_FEAT_DBAF == TRUE) + #if (BT_BLE_FEAT_CHANNEL_SOUNDING == TRUE) #define HCI_BLE_CS_READ_LOCAL_SUPP_CAPS (0x0089 | HCI_GRP_BLE_CMDS) #define HCI_BLE_CS_READ_REMOTE_SUPP_CAPS (0x008A | HCI_GRP_BLE_CMDS) @@ -479,6 +493,22 @@ #define HCI_BLE_CS_SET_PROCEDURE_ENABLE (0x0094 | HCI_GRP_BLE_CMDS) #endif // (BT_BLE_FEAT_CHANNEL_SOUNDING == TRUE) +#if (BT_BLE_FEAT_CS_SECURITY_REQUIREMENTS == TRUE) +#define HCI_BLE_CS_SET_SECURITY_REQUIREMENTS (0x00A7 | HCI_GRP_BLE_CMDS) +#define HCI_BLE_CS_SET_DEFAULT_SECURITY_REQUIREMENTS (0x00A8 | HCI_GRP_BLE_CMDS) +#endif // (BT_BLE_FEAT_CS_SECURITY_REQUIREMENTS == TRUE) + +#if (BLE_FEAT_LE_UTP == TRUE) +#define HCI_BLE_ENABLE_UTP_OTA_MODE (0x009F | HCI_GRP_BLE_CMDS) +#define HCI_BLE_UTP_SEND (0x00A0 | HCI_GRP_BLE_CMDS) +#endif // #if (BLE_FEAT_LE_UTP == TRUE) + +#if (BLE_FEAT_SHORTER_CONN_INTERVALS == TRUE) +#define HCI_BLE_CONNECTION_RATE_REQUEST (0x00A1 | HCI_GRP_BLE_CMDS) +#define HCI_BLE_SET_DEFAULT_RATE_PARAMETERS (0x00A2 | HCI_GRP_BLE_CMDS) +#define HCI_BLE_READ_MIN_SUPP_CONN_INTERVAL (0x00A3 | HCI_GRP_BLE_CMDS) +#endif // #if (BLE_FEAT_SHORTER_CONN_INTERVALS == TRUE) + // Vendor OGF define #define HCI_VENDOR_OGF 0x3F @@ -970,10 +1000,26 @@ #define HCI_BLE_PA_RESPONSE_REPORT_EVT 0x28 #endif // #if (BT_BLE_FEAT_PAWR_EN == TRUE) +#if (BLE_FEAT_LL_EXT_FEAT == TRUE) +#define HCI_BLE_READ_ALL_REMOTE_FEAT_COMPLETE_EVT 0x2B +#endif // #if (BLE_FEAT_LL_EXT_FEAT == TRUE) + #if (BLE_FEAT_ADV_MONITOR == TRUE) #define HCI_BLE_MONITOR_ADV_REPORT_EVT 0x34 #endif // #if (BLE_FEAT_ADV_MONITOR == TRUE) +#if (BLE_FEAT_FRAME_SPACE_UPDATE == TRUE) +#define HCI_BLE_FRAME_SPACE_UPDATE_COMPLETE_EVT 0x35 +#endif // #if (BLE_FEAT_FRAME_SPACE_UPDATE == TRUE) + +#if (BLE_FEAT_LE_UTP == TRUE) +#define HCI_BLE_UTP_RECEIVE_EVT 0x36 +#endif // #if (BLE_FEAT_LE_UTP == TRUE) + +#if (BLE_FEAT_SHORTER_CONN_INTERVALS == TRUE) +#define HCI_BLE_CONN_RATE_CHANGE_EVT 0x37 +#endif // #if (BLE_FEAT_SHORTER_CONN_INTERVALS == TRUE) + #if (BT_BLE_FEAT_CHANNEL_SOUNDING == TRUE) #define HCI_BLE_CS_READ_REMOTE_SUPP_CAPS_CMPL_EVT 0x2C #define HCI_BLE_CS_READ_REMOTE_FAE_TAB_CMPL_EVT 0x2D diff --git a/components/bt/host/bluedroid/stack/include/stack/hcimsgs.h b/components/bt/host/bluedroid/stack/include/stack/hcimsgs.h index e82a3178abf..30b2f5794e8 100644 --- a/components/bt/host/bluedroid/stack/include/stack/hcimsgs.h +++ b/components/bt/host/bluedroid/stack/include/stack/hcimsgs.h @@ -825,6 +825,66 @@ BOOLEAN btsnd_hcic_ble_read_monitor_adv_list_size(void); UINT8 btsnd_hcic_ble_enable_monitor_adv(UINT8 enable); #endif // #if (BLE_FEAT_ADV_MONITOR == TRUE) +#if (BLE_FEAT_DBAF == TRUE) +#define HCIC_PARAM_SIZE_SET_DECISION_DATA_HDR 3 +#define HCIC_PARAM_SIZE_SET_DECISION_DATA_MAX 251 +#define HCIC_PARAM_SIZE_SET_DECISION_INSTRUCTIONS_HDR 1 +#define HCIC_PARAM_SIZE_SET_DECISION_INSTRUCTIONS_MAX 251 +#define BLE_DECISION_DATA_MAX_LEN 248 +#define BLE_DECISION_MAX_TESTS 8 +#define BLE_DECISION_TEST_PARAM_LEN 16 +#define BLE_DECISION_TEST_PARAMS_MAX_LEN (BLE_DECISION_MAX_TESTS * BLE_DECISION_TEST_PARAM_LEN) +#define HCIC_PARAM_SIZE_SET_DECISION_INSTRUCTIONS(n) (HCIC_PARAM_SIZE_SET_DECISION_INSTRUCTIONS_HDR + (n) * 18) + +UINT8 btsnd_hcic_ble_set_decision_data(UINT8 adv_handle, UINT8 decision_type_flags, + UINT8 data_len, const UINT8 *p_data); +UINT8 btsnd_hcic_ble_set_decision_instructions(UINT8 num_tests, const UINT8 *test_flags, + const UINT8 *test_fields, const UINT8 *test_params); +#endif // #if (BLE_FEAT_DBAF == TRUE) + +#if (BLE_FEAT_FRAME_SPACE_UPDATE == TRUE) +#define HCIC_PARAM_SIZE_FRAME_SPACE_UPDATE 9 + +UINT8 btsnd_hcic_ble_frame_space_update(UINT16 conn_handle, UINT16 frame_space_min, + UINT16 frame_space_max, UINT8 phys, UINT16 spacing_types); +#endif // #if (BLE_FEAT_FRAME_SPACE_UPDATE == TRUE) + +#if (BLE_FEAT_LL_EXT_FEAT == TRUE) +#define HCIC_PARAM_SIZE_READ_ALL_REMOTE_FEATURES 3 +#define BLE_LL_EXT_FEAT_DATA_LEN 248 +#define BLE_LL_EXT_FEAT_MAX_PAGE 10 + +BOOLEAN btsnd_hcic_ble_read_all_local_supp_features(void); +UINT8 btsnd_hcic_ble_read_all_remote_features(UINT16 conn_handle, UINT8 page_requested); +#endif // #if (BLE_FEAT_LL_EXT_FEAT == TRUE) + +#if (BLE_FEAT_LE_UTP == TRUE) +#define HCIC_PARAM_SIZE_ENABLE_UTP_OTA_MODE 1 +#define HCIC_PARAM_SIZE_UTP_SEND_HDR 1 +#define HCIC_PARAM_SIZE_UTP_SEND_MAX 255 +#define BLE_UTP_DATA_MAX_LEN 254 + +UINT8 btsnd_hcic_ble_enable_utp_ota_mode(UINT8 enable); +UINT8 btsnd_hcic_ble_utp_send(UINT8 data_len, const UINT8 *p_data); +#endif // #if (BLE_FEAT_LE_UTP == TRUE) + +#if (BLE_FEAT_SHORTER_CONN_INTERVALS == TRUE) +#define HCIC_PARAM_SIZE_CONNECTION_RATE_REQUEST 20 +#define HCIC_PARAM_SIZE_SET_DEFAULT_RATE_PARAMETERS 18 + +UINT8 btsnd_hcic_ble_connection_rate_request(UINT16 conn_handle, UINT16 conn_interval_min, + UINT16 conn_interval_max, UINT16 subrate_min, + UINT16 subrate_max, UINT16 max_latency, + UINT16 continuation_number, UINT16 supervision_timeout, + UINT16 min_ce_len, UINT16 max_ce_len); +UINT8 btsnd_hcic_ble_set_default_rate_parameters(UINT16 conn_interval_min, UINT16 conn_interval_max, + UINT16 subrate_min, UINT16 subrate_max, + UINT16 max_latency, UINT16 continuation_number, + UINT16 supervision_timeout, UINT16 min_ce_len, + UINT16 max_ce_len); +BOOLEAN btsnd_hcic_ble_read_min_supp_conn_interval(void); +#endif // #if (BLE_FEAT_SHORTER_CONN_INTERVALS == TRUE) + /* ULP HCI command */ BOOLEAN btsnd_hcic_ble_set_evt_mask (BT_EVENT_MASK event_mask); @@ -1285,6 +1345,11 @@ typedef struct { #define HCIC_PARAM_SIZE_SET_PA_RESPONSE_DATA_PARAMS_LEN 8 #define HCIC_PARAM_SIZE_SET_PA_SYNC_SUBEVT_PARAMS_LEN 5 +/** Max rsp_data octets in LE Set Periodic Advertising Response Data (HCI command param total ≤ HCI_COMMAND_SIZE). */ +#define HCIC_PA_RSP_DATA_PAYLOAD_MAX (HCI_COMMAND_SIZE - HCIC_PARAM_SIZE_SET_PA_RESPONSE_DATA_PARAMS_LEN) +/** Max Num_Subevents_To_Sync in LE Set Periodic Advertising Sync Subevents (BT Core Spec §7.8.127: 0x01–0x80). */ +#define HCIC_PA_SYNC_SUBEVT_NUM_MAX 128 + #define HCIC_PARAM_SIZE_SET_PERIODIC_ADV_PARAMS_V2 12 UINT8 btsnd_hcic_ble_set_periodic_adv_params_v2(UINT8 adv_handle, UINT16 interval_min, UINT16 interval_max, UINT16 propertics, UINT8 num_subevents, UINT8 subevent_interval, @@ -1350,4 +1415,12 @@ UINT8 btsnd_hcic_ble_cs_set_procedure_params(UINT16 conn_handle, UINT8 config_id UINT8 btsnd_hcic_ble_cs_procedure_enable(UINT16 conn_handle, UINT8 config_id, UINT8 enable); #endif // (BT_BLE_FEAT_CHANNEL_SOUNDING == TRUE) +#if (BT_BLE_FEAT_CS_SECURITY_REQUIREMENTS == TRUE) +#define HCIC_PARAM_SIZE_CS_SET_SECURITY_REQUIREMENTS_LEN 10 +#define HCIC_PARAM_SIZE_CS_SET_DEFAULT_SECURITY_REQUIREMENTS_LEN 8 + +UINT8 btsnd_hcic_ble_cs_set_security_requirements(UINT16 conn_handle, UINT64 cs_security_requirements); +UINT8 btsnd_hcic_ble_cs_set_default_security_requirements(UINT64 cs_security_requirements); +#endif // (BT_BLE_FEAT_CS_SECURITY_REQUIREMENTS == TRUE) + #endif diff --git a/components/bt/host/bluedroid/stack/include/stack/obex_api.h b/components/bt/host/bluedroid/stack/include/stack/obex_api.h index d22cd33c527..3332b1ac29b 100644 --- a/components/bt/host/bluedroid/stack/include/stack/obex_api.h +++ b/components/bt/host/bluedroid/stack/include/stack/obex_api.h @@ -266,6 +266,6 @@ extern BOOLEAN OBEX_CheckContinueResponse(BT_HDR *pkt); extern UINT8 *OBEX_GetNextHeader(BT_HDR *pkt, tOBEX_PARSE_INFO *info); -extern UINT16 OBEX_GetHeaderLength(UINT8 *header); +extern UINT16 OBEX_GetHeaderLength(UINT8 *header, UINT8 *pkt_end); #endif /* #if (OBEX_INCLUDED == TRUE) */ diff --git a/components/bt/host/bluedroid/stack/l2cap/include/l2c_int.h b/components/bt/host/bluedroid/stack/l2cap/include/l2c_int.h index cfc51636649..705b11048bd 100644 --- a/components/bt/host/bluedroid/stack/l2cap/include/l2c_int.h +++ b/components/bt/host/bluedroid/stack/l2cap/include/l2c_int.h @@ -25,6 +25,7 @@ #define L2C_INT_H #include +#include "common/bt_target.h" #include "stack/btm_api.h" #include "stack/l2c_api.h" #include "stack/l2cdefs.h" @@ -75,6 +76,12 @@ #define L2CAP_CACHE_ATT_ACL_NUM 10 +/* Maximum number of host-driven Create_Connection retries before reporting + * connection_exist failure to the application layer. */ +#define L2CAP_MAX_RECONNECT_ON_COLLISION BR_EDR_MAX_RECONNECT_ON_COLLISION +/* Back-off (in seconds) between two host-driven Create_Connection retries. */ +#define L2C_LP_CONN_RETRY_DELAY_TOUT 1 /* 1 second */ + /* Define the possible L2CAP channel states. The names of ** the states may seem a bit strange, but they are taken from ** the Bluetooth specification. @@ -393,6 +400,13 @@ typedef struct t_l2c_linkcb { TIMER_LIST_ENT info_timer_entry; /* Timer entry for info resp timeout evt */ TIMER_LIST_ENT upda_con_timer; /* Timer entry for update connection parameter */ BD_ADDR remote_bd_addr; /* The BD address of the remote */ +#if (CLASSIC_BT_INCLUDED == TRUE) + UINT8 br_edr_create_con_retries; /* Host-driven Create_Connection retry counter, + * incremented for each non-success Connection + * Complete that keeps CCBs on this LCB. */ + TIMER_LIST_ENT retry_timer_entry; /* Back-off timer between host-driven + * Create_Connection retries. */ +#endif UINT8 link_role; /* Master or slave */ UINT8 id; @@ -733,6 +747,9 @@ extern BOOLEAN l2c_link_hci_conn_comp (UINT8 status, UINT16 handle, BD_ADDR p_b extern BOOLEAN l2c_link_hci_disc_comp (UINT16 handle, UINT8 reason); extern BOOLEAN l2c_link_hci_qos_violation (UINT16 handle); extern void l2c_link_timeout (tL2C_LCB *p_lcb); +#if (CLASSIC_BT_INCLUDED == TRUE) +extern void l2c_link_create_conn_retry (tL2C_LCB *p_lcb); +#endif extern void l2c_info_timeout (tL2C_LCB *p_lcb); extern void l2c_link_check_send_pkts (tL2C_LCB *p_lcb, tL2C_CCB *p_ccb, BT_HDR *p_buf); extern void l2c_link_adjust_allocation (void); diff --git a/components/bt/host/bluedroid/stack/l2cap/l2c_ble.c b/components/bt/host/bluedroid/stack/l2cap/l2c_ble.c index 65d4e643b06..3dfd210cc24 100644 --- a/components/bt/host/bluedroid/stack/l2cap/l2c_ble.c +++ b/components/bt/host/bluedroid/stack/l2cap/l2c_ble.c @@ -147,40 +147,42 @@ BOOLEAN L2CA_UpdateBleConnParams (BD_ADDR rem_bda, UINT16 min_int, UINT16 max_in /* See if we have a link control block for the remote device */ p_lcb = l2cu_find_lcb_by_bd_addr (rem_bda, BT_TRANSPORT_LE); - /* If we don't have one, create one and accept the connection. */ if (!p_lcb || !p_acl_cb) { L2CAP_TRACE_WARNING ("L2CA_UpdateBleConnParams - unknown BD_ADDR "MACSTR"", MAC2STR(rem_bda)); - return (FALSE); - } - - if (p_lcb->transport != BT_TRANSPORT_LE) { + status = HCI_ERR_NO_CONNECTION; + need_cb = true; + } else if (p_lcb->transport != BT_TRANSPORT_LE) { L2CAP_TRACE_WARNING ("L2CA_UpdateBleConnParams - BD_ADDR "MACSTR" not LE", MAC2STR(rem_bda)); - return (FALSE); - } - - /* Check whether the request conn params is already set */ - if ((max_int == p_lcb->current_used_conn_interval) && (latency == p_lcb->current_used_conn_latency) && - (timeout == p_lcb->current_used_conn_timeout)) { - status = HCI_SUCCESS; + status = HCI_ERR_NO_CONNECTION; need_cb = true; - L2CAP_TRACE_WARNING("%s connection parameter already set", __func__); - } + } else { + /* Check whether the request conn params is already set */ + if ((max_int == p_lcb->current_used_conn_interval) && (latency == p_lcb->current_used_conn_latency) && + (timeout == p_lcb->current_used_conn_timeout)) { + status = HCI_SUCCESS; + need_cb = true; + L2CAP_TRACE_WARNING("%s connection parameter already set", __func__); + } - if (p_lcb->conn_update_mask & L2C_BLE_UPDATE_PARAM_FULL){ - status = HCI_ERR_ILLEGAL_COMMAND; - need_cb = true; - L2CAP_TRACE_ERROR("%s connection parameter update in progress, please try later", __func__); + if (p_lcb->conn_update_mask & L2C_BLE_UPDATE_PARAM_FULL){ + status = HCI_ERR_ILLEGAL_COMMAND; + need_cb = true; + L2CAP_TRACE_ERROR("%s connection parameter update in progress, please try later", __func__); + } } if (need_cb) { tBTM_BLE_LEGACY_GAP_CB_PARAMS cb_params = {0}; cb_params.conn_params_update.status = status; - memcpy(cb_params.conn_params_update.remote_bd_addr, p_lcb->remote_bd_addr, BD_ADDR_LEN); + memcpy(cb_params.conn_params_update.remote_bd_addr, + p_lcb ? p_lcb->remote_bd_addr : rem_bda, BD_ADDR_LEN); cb_params.conn_params_update.min_conn_int = min_int; cb_params.conn_params_update.max_conn_int = max_int; - cb_params.conn_params_update.conn_int = p_lcb->current_used_conn_interval; - cb_params.conn_params_update.slave_latency = p_lcb->current_used_conn_latency; - cb_params.conn_params_update.supervision_tout = p_lcb->current_used_conn_timeout; + if (p_lcb) { + cb_params.conn_params_update.conn_int = p_lcb->current_used_conn_interval; + cb_params.conn_params_update.slave_latency = p_lcb->current_used_conn_latency; + cb_params.conn_params_update.supervision_tout = p_lcb->current_used_conn_timeout; + } BTM_LegacyBleCallbackTrigger(BTM_BLE_LEGACY_GAP_CONNECTION_PARAMS_UPDATE_EVT, &cb_params); @@ -993,12 +995,14 @@ BOOLEAN l2cble_init_direct_conn (tL2C_LCB *p_lcb) #if (CONTROLLER_RPA_LIST_ENABLE) if (p_dev_rec->ble.in_controller_list & BTM_RESOLVING_LIST_BIT) { - if (btm_cb.ble_ctr_cb.privacy_mode >= BTM_PRIVACY_1_2) { - own_addr_type |= BLE_ADDR_TYPE_ID_BIT; - } + if (!(peer_addr_type == BLE_ADDR_RANDOM && !BTM_BLE_IS_RESOLVE_BDA(peer_addr))) { + if (btm_cb.ble_ctr_cb.privacy_mode >= BTM_PRIVACY_1_2) { + own_addr_type |= BLE_ADDR_TYPE_ID_BIT; + } - //btm_ble_enable_resolving_list(BTM_BLE_RL_INIT); - btm_random_pseudo_to_identity_addr(peer_addr, &peer_addr_type); + //btm_ble_enable_resolving_list(BTM_BLE_RL_INIT); + btm_random_pseudo_to_identity_addr(peer_addr, &peer_addr_type); + } } else { btm_ble_disable_resolving_list(BTM_BLE_RL_INIT, TRUE); } @@ -1034,7 +1038,27 @@ BOOLEAN l2cble_init_direct_conn (tL2C_LCB *p_lcb) } } - if (!p_lcb->is_aux) { + // Auto-set is_aux based on BLE feature support + bool is_aux = p_lcb->is_aux; +#if (BLE_42_FEATURE_SUPPORT == TRUE) && (BLE_50_FEATURE_SUPPORT == FALSE) + if (is_aux) { + L2CAP_TRACE_WARNING("is_aux auto-set to false (BLE 4.2 only)"); + is_aux = false; + } +#elif (BLE_42_FEATURE_SUPPORT == FALSE) && (BLE_50_FEATURE_SUPPORT == TRUE) + if (!is_aux) { + L2CAP_TRACE_WARNING("is_aux auto-set to true (BLE 5.0 only)"); + is_aux = true; + } +#else + extern bool btm_ble_inter_get(void); + if (btm_ble_inter_get() && (!is_aux)) { + L2CAP_TRACE_WARNING("is_aux auto-set to true (BLE 5.0 API used)"); + is_aux = true; + } +#endif + + if (!is_aux) { if (!btsnd_hcic_ble_create_ll_conn (scan_int, /* UINT16 scan_int */ scan_win, /* UINT16 scan_win */ FALSE, /* UINT8 white_list */ diff --git a/components/bt/host/bluedroid/stack/l2cap/l2c_csm.c b/components/bt/host/bluedroid/stack/l2cap/l2c_csm.c index 89d74b998fb..9f6192102f4 100644 --- a/components/bt/host/bluedroid/stack/l2cap/l2c_csm.c +++ b/components/bt/host/bluedroid/stack/l2cap/l2c_csm.c @@ -165,16 +165,28 @@ static void l2c_csm_closed (tL2C_CCB *p_ccb, UINT16 event, void *p_data) p_ccb->p_lcb->handle, TRUE, &l2c_link_sec_comp, p_ccb); break; - case L2CEVT_LP_CONNECT_CFM_NEG: /* Link failed */ + case L2CEVT_LP_CONNECT_CFM_NEG: { /* Link failed */ tL2C_CONN_INFO *p_ci = (tL2C_CONN_INFO *)p_data; - /* Disconnect unless ACL collision and upper layer wants to handle it */ - if (p_ci->status != HCI_ERR_CONNECTION_EXISTS - || !btm_acl_notif_conn_collision(p_ccb->p_lcb->remote_bd_addr)) { + BOOLEAN keep_for_collision = FALSE; +#if (CLASSIC_BT_INCLUDED == TRUE) + if (p_ci->status == HCI_ERR_CONNECTION_EXISTS + && p_ccb->p_lcb->br_edr_create_con_retries <= L2CAP_MAX_RECONNECT_ON_COLLISION + && btm_acl_notif_conn_collision(p_ccb->p_lcb->remote_bd_addr)) { + keep_for_collision = TRUE; + } +#else + if (p_ci->status == HCI_ERR_CONNECTION_EXISTS + && btm_acl_notif_conn_collision(p_ccb->p_lcb->remote_bd_addr)) { + keep_for_collision = TRUE; + } +#endif + if (!keep_for_collision) { L2CAP_TRACE_API ("L2CAP - Calling ConnectCfm_Cb(), CID: 0x%04x Status: %d", p_ccb->local_cid, p_ci->status); l2cu_release_ccb (p_ccb); (*connect_cfm)(local_cid, p_ci->status); } break; + } case L2CEVT_L2CA_CONNECT_REQ: /* API connect request */ /* Cancel sniff mode if needed */ diff --git a/components/bt/host/bluedroid/stack/l2cap/l2c_link.c b/components/bt/host/bluedroid/stack/l2cap/l2c_link.c index 347e220a41a..1a74d33dcec 100644 --- a/components/bt/host/bluedroid/stack/l2cap/l2c_link.c +++ b/components/bt/host/bluedroid/stack/l2cap/l2c_link.c @@ -219,6 +219,10 @@ BOOLEAN l2c_link_hci_conn_comp (UINT8 status, UINT16 handle, BD_ADDR p_bda) btu_stop_timer (&p_lcb->timer_entry); #if (CLASSIC_BT_INCLUDED == TRUE) + /* Link came up successfully; reset host-driven Create_Connection + * retry counter so a future failure on this BDA starts fresh. */ + p_lcb->br_edr_create_con_retries = 0; + btu_stop_timer(&p_lcb->retry_timer_entry); /* For all channels, send the event through their FSMs */ for (p_ccb = p_lcb->ccb_queue.p_first_ccb; p_ccb; p_ccb = p_ccb->p_next_ccb) { l2c_csm_execute (p_ccb, L2CEVT_LP_CONNECT_CFM, &ci); @@ -258,6 +262,33 @@ BOOLEAN l2c_link_hci_conn_comp (UINT8 status, UINT16 handle, BD_ADDR p_bda) if (ci.status == HCI_ERR_CONNECTION_EXISTS) { /* we are in collision situation, wait for connection request from controller */ p_lcb->link_state = LST_CONNECTING; +#if (CLASSIC_BT_INCLUDED == TRUE) + if (++p_lcb->br_edr_create_con_retries <= L2CAP_MAX_RECONNECT_ON_COLLISION) { + L2CAP_TRACE_WARNING("L2CAP - Conn Comp status: 0x%02x, retry " + "Create_Connection (%u/%u) in %u sec", + status, + p_lcb->br_edr_create_con_retries, + L2CAP_MAX_RECONNECT_ON_COLLISION, + L2C_LP_CONN_RETRY_DELAY_TOUT); + btu_stop_timer(&p_lcb->timer_entry); + p_lcb->retry_timer_entry.param = (TIMER_PARAM_TYPE)p_lcb; + btu_start_timer(&p_lcb->retry_timer_entry, + BTU_TTYPE_L2CAP_LINK_RETRY, + L2C_LP_CONN_RETRY_DELAY_TOUT); + } else { + L2CAP_TRACE_WARNING("L2CAP - giving up Create_Connection " + "after %u retries, last status: 0x%02x", + p_lcb->br_edr_create_con_retries, status); + for (p_ccb = p_lcb->ccb_queue.p_first_ccb; p_ccb; ) { + tL2C_CCB *pn = p_ccb->p_next_ccb; + l2c_csm_execute (p_ccb, L2CEVT_LP_CONNECT_CFM_NEG, &ci); + p_ccb = pn; + } + btu_stop_timer(&p_lcb->timer_entry); + btu_stop_timer(&p_lcb->retry_timer_entry); + l2cu_release_lcb(p_lcb); + } +#endif ///CLASSIC_BT_INCLUDED == TRUE } else { l2cu_create_conn(p_lcb, BT_TRANSPORT_BR_EDR); } @@ -584,7 +615,43 @@ BOOLEAN l2c_link_hci_qos_violation (UINT16 handle) return (TRUE); } +#if (CLASSIC_BT_INCLUDED == TRUE) +/******************************************************************************* +** +** Function l2c_link_create_conn_retry +** +** Description Back-off timer between two host-driven Create_Connection +** retries fired. Re-issue the connection attempt now. If +** the LCB has been torn down (no CCBs left, link no longer +** in a connecting state) we silently drop the retry. +** +** Returns void +** +*******************************************************************************/ +void l2c_link_create_conn_retry (tL2C_LCB *p_lcb) +{ + if (p_lcb == NULL || !p_lcb->in_use) { + return; + } + /* If the application/upper layer already tore everything down while we + * were waiting for the back-off, do nothing. */ + if (p_lcb->ccb_queue.p_first_ccb == NULL) { + L2CAP_TRACE_WARNING("L2CAP - retry timer fired but no CCB left, " + "dropping retry"); + return; + } + + L2CAP_TRACE_EVENT("L2CAP - back-off elapsed, re-issuing Create_Connection " + "(retry %u/%u)", + p_lcb->br_edr_create_con_retries, + L2CAP_MAX_RECONNECT_ON_COLLISION); + + /* l2cu_create_conn() will (re)set link_state and arm the 60s + * BTU_TTYPE_L2CAP_LINK timer on p_lcb->timer_entry. */ + l2cu_create_conn(p_lcb, BT_TRANSPORT_BR_EDR); +} +#endif ///CLASSIC_BT_INCLUDED == TRUE /******************************************************************************* ** diff --git a/components/bt/host/bluedroid/stack/l2cap/l2c_main.c b/components/bt/host/bluedroid/stack/l2cap/l2c_main.c index 1a58b69277a..f59f185c90e 100644 --- a/components/bt/host/bluedroid/stack/l2cap/l2c_main.c +++ b/components/bt/host/bluedroid/stack/l2cap/l2c_main.c @@ -352,7 +352,7 @@ void l2c_rcv_acl_data (BT_HDR *p_msg) #if (CLASSIC_BT_INCLUDED == TRUE) static void process_l2cap_cmd (tL2C_LCB *p_lcb, UINT8 *p, UINT16 pkt_len) { - UINT8 *p_pkt_end, *p_next_cmd, *p_cfg_end, *p_cfg_start; + UINT8 *p_pkt_end, *p_next_cmd, *p_cfg_end, *p_cfg_start, *p_cfg_opt_end; UINT8 cmd_code, cfg_code, cfg_len, id; tL2C_CONN_INFO con_info; tL2CAP_CFG_INFO cfg_info; @@ -526,6 +526,10 @@ static void process_l2cap_cmd (tL2C_LCB *p_lcb, UINT8 *p, UINT16 pkt_len) break; case L2CAP_CMD_CONFIG_REQ: + if (cmd_len < L2CAP_CONFIG_REQ_LEN) { + L2CAP_TRACE_WARNING ("L2CAP - cfg req too short, cmd_len: %d", cmd_len); + break; + } p_cfg_end = p + cmd_len; cfg_rej = FALSE; cfg_rej_len = 0; @@ -539,21 +543,47 @@ static void process_l2cap_cmd (tL2C_LCB *p_lcb, UINT8 *p, UINT16 pkt_len) cfg_info.fcr_present = cfg_info.fcs_present = FALSE; while (p < p_cfg_end) { + if ((p_cfg_end - p) < L2CAP_CFG_OPTION_OVERHEAD) { + cfg_rej = TRUE; + break; + } + STREAM_TO_UINT8 (cfg_code, p); STREAM_TO_UINT8 (cfg_len, p); + if (cfg_len > (p_cfg_end - p)) { + p = p_cfg_end; + cfg_rej = TRUE; + break; + } + p_cfg_opt_end = p + cfg_len; switch (cfg_code & 0x7F) { case L2CAP_CFG_TYPE_MTU: + if (cfg_len != L2CAP_CFG_MTU_OPTION_LEN) { + p = p_cfg_end; + cfg_rej = TRUE; + break; + } cfg_info.mtu_present = TRUE; STREAM_TO_UINT16 (cfg_info.mtu, p); break; case L2CAP_CFG_TYPE_FLUSH_TOUT: + if (cfg_len != L2CAP_CFG_FLUSH_OPTION_LEN) { + p = p_cfg_end; + cfg_rej = TRUE; + break; + } cfg_info.flush_to_present = TRUE; STREAM_TO_UINT16 (cfg_info.flush_to, p); break; case L2CAP_CFG_TYPE_QOS: + if (cfg_len != L2CAP_CFG_QOS_OPTION_LEN) { + p = p_cfg_end; + cfg_rej = TRUE; + break; + } cfg_info.qos_present = TRUE; STREAM_TO_UINT8 (cfg_info.qos.qos_flags, p); STREAM_TO_UINT8 (cfg_info.qos.service_type, p); @@ -565,6 +595,11 @@ static void process_l2cap_cmd (tL2C_LCB *p_lcb, UINT8 *p, UINT16 pkt_len) break; case L2CAP_CFG_TYPE_FCR: + if (cfg_len != L2CAP_CFG_FCR_OPTION_LEN) { + p = p_cfg_end; + cfg_rej = TRUE; + break; + } cfg_info.fcr_present = TRUE; STREAM_TO_UINT8 (cfg_info.fcr.mode, p); STREAM_TO_UINT8 (cfg_info.fcr.tx_win_sz, p); @@ -575,11 +610,21 @@ static void process_l2cap_cmd (tL2C_LCB *p_lcb, UINT8 *p, UINT16 pkt_len) break; case L2CAP_CFG_TYPE_FCS: + if (cfg_len != L2CAP_CFG_FCS_OPTION_LEN) { + p = p_cfg_end; + cfg_rej = TRUE; + break; + } cfg_info.fcs_present = TRUE; STREAM_TO_UINT8 (cfg_info.fcs, p); break; case L2CAP_CFG_TYPE_EXT_FLOW: + if (cfg_len != L2CAP_CFG_EXT_FLOW_OPTION_LEN) { + p = p_cfg_end; + cfg_rej = TRUE; + break; + } cfg_info.ext_flow_spec_present = TRUE; STREAM_TO_UINT8 (cfg_info.ext_flow_spec.id, p); STREAM_TO_UINT8 (cfg_info.ext_flow_spec.stype, p); @@ -590,17 +635,9 @@ static void process_l2cap_cmd (tL2C_LCB *p_lcb, UINT8 *p, UINT16 pkt_len) break; default: - /* sanity check option length */ - if ((cfg_len + L2CAP_CFG_OPTION_OVERHEAD) <= cmd_len) { - p += cfg_len; - if ((cfg_code & 0x80) == 0) { - cfg_rej_len += cfg_len + L2CAP_CFG_OPTION_OVERHEAD; - cfg_rej = TRUE; - } - } - /* bad length; force loop exit */ - else { - p = p_cfg_end; + p = p_cfg_opt_end; + if ((cfg_code & 0x80) == 0) { + cfg_rej_len += cfg_len + L2CAP_CFG_OPTION_OVERHEAD; cfg_rej = TRUE; } break; @@ -621,6 +658,10 @@ static void process_l2cap_cmd (tL2C_LCB *p_lcb, UINT8 *p, UINT16 pkt_len) break; case L2CAP_CMD_CONFIG_RSP: + if (cmd_len < L2CAP_CONFIG_RSP_LEN) { + L2CAP_TRACE_WARNING ("L2CAP - cfg rsp too short, cmd_len: %d", cmd_len); + break; + } p_cfg_end = p + cmd_len; STREAM_TO_UINT16 (lcid, p); STREAM_TO_UINT16 (cfg_info.flags, p); @@ -630,21 +671,42 @@ static void process_l2cap_cmd (tL2C_LCB *p_lcb, UINT8 *p, UINT16 pkt_len) cfg_info.fcr_present = cfg_info.fcs_present = FALSE; while (p < p_cfg_end) { + if ((p_cfg_end - p) < L2CAP_CFG_OPTION_OVERHEAD) { + break; + } + STREAM_TO_UINT8 (cfg_code, p); STREAM_TO_UINT8 (cfg_len, p); + if (cfg_len > (p_cfg_end - p)) { + p = p_cfg_end; + break; + } + p_cfg_opt_end = p + cfg_len; switch (cfg_code & 0x7F) { case L2CAP_CFG_TYPE_MTU: + if (cfg_len != L2CAP_CFG_MTU_OPTION_LEN) { + p = p_cfg_end; + break; + } cfg_info.mtu_present = TRUE; STREAM_TO_UINT16 (cfg_info.mtu, p); break; case L2CAP_CFG_TYPE_FLUSH_TOUT: + if (cfg_len != L2CAP_CFG_FLUSH_OPTION_LEN) { + p = p_cfg_end; + break; + } cfg_info.flush_to_present = TRUE; STREAM_TO_UINT16 (cfg_info.flush_to, p); break; case L2CAP_CFG_TYPE_QOS: + if (cfg_len != L2CAP_CFG_QOS_OPTION_LEN) { + p = p_cfg_end; + break; + } cfg_info.qos_present = TRUE; STREAM_TO_UINT8 (cfg_info.qos.qos_flags, p); STREAM_TO_UINT8 (cfg_info.qos.service_type, p); @@ -656,6 +718,10 @@ static void process_l2cap_cmd (tL2C_LCB *p_lcb, UINT8 *p, UINT16 pkt_len) break; case L2CAP_CFG_TYPE_FCR: + if (cfg_len != L2CAP_CFG_FCR_OPTION_LEN) { + p = p_cfg_end; + break; + } cfg_info.fcr_present = TRUE; STREAM_TO_UINT8 (cfg_info.fcr.mode, p); STREAM_TO_UINT8 (cfg_info.fcr.tx_win_sz, p); @@ -666,11 +732,19 @@ static void process_l2cap_cmd (tL2C_LCB *p_lcb, UINT8 *p, UINT16 pkt_len) break; case L2CAP_CFG_TYPE_FCS: + if (cfg_len != L2CAP_CFG_FCS_OPTION_LEN) { + p = p_cfg_end; + break; + } cfg_info.fcs_present = TRUE; STREAM_TO_UINT8 (cfg_info.fcs, p); break; case L2CAP_CFG_TYPE_EXT_FLOW: + if (cfg_len != L2CAP_CFG_EXT_FLOW_OPTION_LEN) { + p = p_cfg_end; + break; + } cfg_info.ext_flow_spec_present = TRUE; STREAM_TO_UINT8 (cfg_info.ext_flow_spec.id, p); STREAM_TO_UINT8 (cfg_info.ext_flow_spec.stype, p); @@ -679,6 +753,9 @@ static void process_l2cap_cmd (tL2C_LCB *p_lcb, UINT8 *p, UINT16 pkt_len) STREAM_TO_UINT32 (cfg_info.ext_flow_spec.access_latency, p); STREAM_TO_UINT32 (cfg_info.ext_flow_spec.flush_timeout, p); break; + default: + p = p_cfg_opt_end; + break; } } @@ -988,6 +1065,12 @@ void l2c_process_timeout (TIMER_LIST_ENT *p_tle) l2c_link_timeout ((tL2C_LCB *)p_tle->param); break; #if (CLASSIC_BT_INCLUDED == TRUE) + case BTU_TTYPE_L2CAP_LINK_RETRY: + /* Back-off between host-driven Create_Connection retries expired: + * re-issue the connection attempt now. */ + l2c_link_create_conn_retry ((tL2C_LCB *)p_tle->param); + break; + case BTU_TTYPE_L2CAP_CHNL: l2c_csm_execute (((tL2C_CCB *)p_tle->param), L2CEVT_TIMEOUT, NULL); break; diff --git a/components/bt/host/bluedroid/stack/l2cap/l2c_utils.c b/components/bt/host/bluedroid/stack/l2cap/l2c_utils.c index ad996c18da9..2cf6443dccb 100644 --- a/components/bt/host/bluedroid/stack/l2cap/l2c_utils.c +++ b/components/bt/host/bluedroid/stack/l2cap/l2c_utils.c @@ -88,6 +88,9 @@ tL2C_LCB *l2cu_allocate_lcb (BD_ADDR p_bd_addr, BOOLEAN is_bonding, tBT_TRANSPOR btu_free_timer(&p_lcb->timer_entry); btu_free_timer(&p_lcb->info_timer_entry); btu_free_timer(&p_lcb->upda_con_timer); +#if (CLASSIC_BT_INCLUDED == TRUE) + btu_free_timer(&p_lcb->retry_timer_entry); +#endif memset (p_lcb, 0, sizeof (tL2C_LCB)); memcpy (p_lcb->remote_bd_addr, p_bd_addr, BD_ADDR_LEN); @@ -114,6 +117,7 @@ tL2C_LCB *l2cu_allocate_lcb (BD_ADDR p_bd_addr, BOOLEAN is_bonding, tBT_TRANSPOR #endif { #if (CLASSIC_BT_INCLUDED == TRUE) + p_lcb->retry_timer_entry.param = (TIMER_PARAM_TYPE)p_lcb; l2cb.num_links_active++; l2c_link_adjust_allocation(); #endif // #if (CLASSIC_BT_INCLUDED == TRUE) @@ -181,6 +185,10 @@ void l2cu_release_lcb (tL2C_LCB *p_lcb) memset(&p_lcb->info_timer_entry, 0, sizeof(TIMER_LIST_ENT)); btu_free_timer(&p_lcb->upda_con_timer); memset(&p_lcb->upda_con_timer, 0, sizeof(TIMER_LIST_ENT)); +#if (CLASSIC_BT_INCLUDED == TRUE) + btu_free_timer(&p_lcb->retry_timer_entry); + memset(&p_lcb->retry_timer_entry, 0, sizeof(TIMER_LIST_ENT)); +#endif /* Release any unfinished L2CAP packet on this link */ if (p_lcb->p_hcit_rcv_acl) { @@ -871,7 +879,7 @@ void l2cu_send_peer_config_rsp (tL2C_CCB *p_ccb, tL2CAP_CFG_INFO *p_cfg) void l2cu_send_peer_config_rej (tL2C_CCB *p_ccb, UINT8 *p_data, UINT16 data_len, UINT16 rej_len) { BT_HDR *p_buf; - UINT16 len, cfg_len, buf_space, len1; + UINT16 len, cfg_len, buf_space, len1, opt_len; UINT8 *p, *p_hci_len, *p_data_end; UINT8 cfg_code; @@ -928,38 +936,42 @@ void l2cu_send_peer_config_rej (tL2C_CCB *p_ccb, UINT8 *p_data, UINT16 data_len, /* Now, put the rejected options */ p_data_end = p_data + data_len; while (p_data < p_data_end) { + if ((p_data_end - p_data) < L2CAP_CFG_OPTION_OVERHEAD) { + break; + } cfg_code = *p_data; cfg_len = *(p_data + 1); + opt_len = cfg_len + L2CAP_CFG_OPTION_OVERHEAD; + if (opt_len > (UINT16)(p_data_end - p_data)) { + p_data = p_data_end; + break; + } switch (cfg_code & 0x7F) { /* skip known options */ case L2CAP_CFG_TYPE_MTU: case L2CAP_CFG_TYPE_FLUSH_TOUT: case L2CAP_CFG_TYPE_QOS: - p_data += cfg_len + L2CAP_CFG_OPTION_OVERHEAD; + case L2CAP_CFG_TYPE_FCR: + case L2CAP_CFG_TYPE_FCS: + case L2CAP_CFG_TYPE_EXT_FLOW: + p_data += opt_len; break; /* unknown options; copy into rsp if not hints */ default: - /* sanity check option length */ - if ((cfg_len + L2CAP_CFG_OPTION_OVERHEAD) <= data_len) { - if ((cfg_code & 0x80) == 0) { - if (buf_space >= (cfg_len + L2CAP_CFG_OPTION_OVERHEAD)) { - memcpy(p, p_data, cfg_len + L2CAP_CFG_OPTION_OVERHEAD); - p += cfg_len + L2CAP_CFG_OPTION_OVERHEAD; - buf_space -= (cfg_len + L2CAP_CFG_OPTION_OVERHEAD); - } else { - L2CAP_TRACE_WARNING("L2CAP - cfg_rej exceeds allocated buffer"); - p_data = p_data_end; /* force loop exit */ - break; - } + if ((cfg_code & 0x80) == 0) { + if (buf_space >= opt_len) { + memcpy(p, p_data, opt_len); + p += opt_len; + buf_space -= opt_len; + } else { + L2CAP_TRACE_WARNING("L2CAP - cfg_rej exceeds allocated buffer"); + p_data = p_data_end; /* force loop exit */ + break; } - p_data += cfg_len + L2CAP_CFG_OPTION_OVERHEAD; - } - /* bad length; force loop exit */ - else { - p_data = p_data_end; } + p_data += opt_len; break; } } diff --git a/components/bt/host/bluedroid/stack/obex/obex_api.c b/components/bt/host/bluedroid/stack/obex/obex_api.c index bfbe61c6290..f8499f2d171 100644 --- a/components/bt/host/bluedroid/stack/obex/obex_api.c +++ b/components/bt/host/bluedroid/stack/obex/obex_api.c @@ -680,17 +680,29 @@ UINT16 OBEX_ParseResponse(BT_HDR *pkt, UINT8 opcode, tOBEX_PARSE_INFO *info) } UINT8 *p_data = (UINT8 *)(pkt + 1) + pkt->offset; + UINT16 len = pkt->len; + + if (len < 1) { + return OBEX_FAILURE; + } + info->opcode = opcode; info->response_code = *p_data; switch (opcode) { case OBEX_OPCODE_CONNECT: + if (len < 7) { + return OBEX_FAILURE; + } info->obex_version_number = p_data[3]; info->flags = p_data[4]; info->max_packet_length = (p_data[5] << 8) + p_data[6]; info->next_header_pos = 7; break; default: + if (len < 3) { + return OBEX_FAILURE; + } info->next_header_pos = 3; break; } @@ -708,7 +720,7 @@ UINT16 OBEX_ParseResponse(BT_HDR *pkt, UINT8 opcode, tOBEX_PARSE_INFO *info) *******************************************************************************/ BOOLEAN OBEX_CheckFinalBit(BT_HDR *pkt) { - if (pkt == NULL) { + if (pkt == NULL || pkt->len < 1) { return FALSE; } UINT8 *p_data = (UINT8 *)(pkt + 1) + pkt->offset; @@ -726,7 +738,7 @@ BOOLEAN OBEX_CheckFinalBit(BT_HDR *pkt) *******************************************************************************/ BOOLEAN OBEX_CheckContinueResponse(BT_HDR *pkt) { - if (pkt == NULL) { + if (pkt == NULL || pkt->len < 1) { return FALSE; } UINT8 *p_data = (UINT8 *)(pkt + 1) + pkt->offset; @@ -742,15 +754,26 @@ BOOLEAN OBEX_CheckContinueResponse(BT_HDR *pkt) ** Returns header length ** *******************************************************************************/ -UINT16 OBEX_GetHeaderLength(UINT8 *header) +UINT16 OBEX_GetHeaderLength(UINT8 *header, UINT8 *pkt_end) { + if (header == NULL || pkt_end == NULL || header >= pkt_end) { + return 0; + } + + UINT16 remaining_len = (UINT16)(pkt_end - header); UINT16 header_len = 0; UINT8 header_id = *header; + switch (header_id & OBEX_HEADER_ID_U2B_MASK) { case OBEX_HEADER_ID_U2B_TYPE1: case OBEX_HEADER_ID_U2B_TYPE2: - header_len = (header[1] << 8) + header[2]; + if (remaining_len >= 3) { + header_len = (header[1] << 8) + header[2]; + } + if (header_len < 3) { + header_len = 0; + } break; case OBEX_HEADER_ID_U2B_TYPE3: header_len = 2; @@ -762,6 +785,11 @@ UINT16 OBEX_GetHeaderLength(UINT8 *header) /* unreachable */ break; } + + if (header_len > remaining_len) { + return 0; + } + return header_len; } @@ -785,7 +813,11 @@ UINT8 *OBEX_GetNextHeader(BT_HDR *pkt, tOBEX_PARSE_INFO *info) } UINT8 *p_data = (UINT8 *)(pkt + 1) + pkt->offset; UINT8 *header = p_data + info->next_header_pos; - UINT16 header_len = OBEX_GetHeaderLength(header); + UINT8 *pkt_end = p_data + pkt->len; + UINT16 header_len = OBEX_GetHeaderLength(header, pkt_end); + if (header_len == 0) { + return NULL; + } info->next_header_pos += header_len; return header; } diff --git a/components/bt/host/bluedroid/stack/smp/smp_keys.c b/components/bt/host/bluedroid/stack/smp/smp_keys.c index ba6b7fd3092..7212aec08fa 100644 --- a/components/bt/host/bluedroid/stack/smp/smp_keys.c +++ b/components/bt/host/bluedroid/stack/smp/smp_keys.c @@ -2281,11 +2281,8 @@ BOOLEAN smp_calculate_link_key_from_long_term_key(tSMP_CB *p_cb) SMP_TRACE_ERROR("%s failed", __func__); } else { UINT8 link_key_type; - if (btm_cb.security_mode == BTM_SEC_MODE_SC) { - /* Secure Connections Only Mode */ - link_key_type = BTM_LKEY_TYPE_AUTH_COMB_P_256; - } else if (controller_get_interface()->supports_secure_connections()) { - /* both transports are SC capable */ + if ((btm_cb.security_mode == BTM_SEC_MODE_SC) || + (controller_get_interface()->supports_secure_connections())) { if (p_cb->sec_level == SMP_SEC_AUTHENTICATED) { link_key_type = BTM_LKEY_TYPE_AUTH_COMB_P_256; } else { diff --git a/components/bt/host/bluedroid/stack/smp/smp_l2c.c b/components/bt/host/bluedroid/stack/smp/smp_l2c.c index 9b019ae73fa..d33e010d850 100644 --- a/components/bt/host/bluedroid/stack/smp/smp_l2c.c +++ b/components/bt/host/bluedroid/stack/smp/smp_l2c.c @@ -335,13 +335,15 @@ static void smp_br_data_received(UINT16 channel, BD_ADDR bd_addr, BT_HDR *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 received command 0x%02x with invalid length %d", cmd, p_buf->len); + osi_free(p_buf); + return; + } + /* reject the pairing request if there is an on-going SMP pairing */ if (SMP_OPCODE_PAIRING_REQ == cmd) { - if (p_buf->len != smp_cmd_size_per_spec[cmd]) { - SMP_TRACE_WARNING( "Ignore received command 0x%02x with invalid length %d", cmd, p_buf->len); - osi_free(p_buf); - return; - } if ((p_cb->state == SMP_STATE_IDLE) && (p_cb->br_state == SMP_BR_STATE_IDLE)) { p_cb->role = HCI_ROLE_SLAVE; p_cb->smp_over_br = TRUE; diff --git a/components/bt/host/bluedroid/stack/smp/smp_utils.c b/components/bt/host/bluedroid/stack/smp/smp_utils.c index 0276f8b7e2b..09b62119a3d 100644 --- a/components/bt/host/bluedroid/stack/smp/smp_utils.c +++ b/components/bt/host/bluedroid/stack/smp/smp_utils.c @@ -582,37 +582,89 @@ static BT_HDR *smp_build_identity_info_cmd(UINT8 cmd_code, tSMP_CB *p_cb) ** ** Function smp_build_id_addr_cmd ** -** Description Build identity address information command. +** Description Build SMP Identity Address Information command +** (opcode 0x09). The address distributed here is the +** local device's permanent identity; an on-air RPA must +** never be sent. +** +** In BLE 5.0 multi-ADV, each set has its own +** own_addr_type / Static Random and the global +** addr_mgnt_cb may reflect a different set, so we look +** up the ext-adv instance that produced this connection +** and use ITS per-set state. Fall back to addr_mgnt_cb +** for initiator / legacy advertising paths. ** *******************************************************************************/ static BT_HDR *smp_build_id_addr_cmd(UINT8 cmd_code, tSMP_CB *p_cb) { - BT_HDR *p_buf = NULL; - UINT8 *p; + BT_HDR *p_buf = NULL; + UINT8 *p; + tBLE_ADDR_TYPE id_type = BLE_ADDR_PUBLIC; + BD_ADDR id_addr = {0}; UNUSED(cmd_code); UNUSED(p_cb); SMP_TRACE_EVENT("smp_build_id_addr_cmd\n"); + + + { +#if (BLE_INCLUDED == TRUE) + tBLE_ADDR_TYPE policy_type = btm_cb.ble_ctr_cb.addr_mgnt_cb.own_addr_type; + const UINT8 *policy_rand = NULL; + BOOLEAN policy_resolved = FALSE; + const BD_ADDR zero = {0}; +#if (BLE_50_FEATURE_SUPPORT == TRUE) && (BLE_50_EXTEND_ADV_EN == TRUE) && (CONTROLLER_RPA_LIST_ENABLE == TRUE) + { + tACL_CONN *p_acl = btm_bda_to_acl(p_cb->pairing_bda, BT_TRANSPORT_LE); + if (p_acl != NULL) { + UINT8 inst = BTM_BleGetExtAdvInstByConHandle(p_acl->hci_handle); + if (inst < MAX_BLE_ADV_INSTANCE) { + policy_type = extend_adv_cb.inst[inst].own_addr_type; + /* Only a host-set Static Random may be sent as identity; + * a stack-generated RPA stored in rand_addr must not. */ + if (extend_adv_cb.inst[inst].rand_addr_set) { + policy_rand = extend_adv_cb.inst[inst].rand_addr; + } + policy_resolved = TRUE; + } + } + } +#endif /* (BLE_50_FEATURE_SUPPORT == TRUE) && (BLE_50_EXTEND_ADV_EN == TRUE) && (CONTROLLER_RPA_LIST_ENABLE == TRUE) */ + + if (!policy_resolved) { + if (memcmp(btm_cb.ble_ctr_cb.addr_mgnt_cb.static_rand_addr, + zero, BD_ADDR_LEN) != 0) { + policy_rand = btm_cb.ble_ctr_cb.addr_mgnt_cb.static_rand_addr; + } + } + + /* LSB(own_addr_type) selects Public (0) vs Static Random (1). + * If Random is required but unavailable, emit Public rather than + * leak an RPA or send all-zero. */ + if ((policy_type & 0x01) && (policy_rand != NULL) && memcmp(policy_rand, zero, BD_ADDR_LEN) != 0) { + id_type = BLE_ADDR_RANDOM; + memcpy(id_addr, policy_rand, BD_ADDR_LEN); + } else if (policy_type & 0x01) { + SMP_TRACE_WARNING("%s: no static rand, fallback public (type=%u)", + __func__, policy_type); + } +#endif ///BLE_INCLUDED == TRUE + if (id_type == BLE_ADDR_PUBLIC) { + memcpy(id_addr, + controller_get_interface()->get_address()->address, + BD_ADDR_LEN); + } + } + if ((p_buf = (BT_HDR *)osi_malloc(sizeof(BT_HDR) + SMP_ID_ADDR_SIZE + L2CAP_MIN_OFFSET)) != NULL) { p = (UINT8 *)(p_buf + 1) + L2CAP_MIN_OFFSET; - UINT8_TO_STREAM (p, SMP_OPCODE_ID_ADDR); - /* Identity Address Information is used in the Transport Specific Key Distribution phase to distribute - its public device address or static random address. if slave using static random address is encrypted, - it should distribute its static random address */ -#if (BLE_INCLUDED == TRUE) - if(btm_cb.ble_ctr_cb.addr_mgnt_cb.own_addr_type == BLE_ADDR_RANDOM && memcmp(btm_cb.ble_ctr_cb.addr_mgnt_cb.static_rand_addr, btm_cb.ble_ctr_cb.addr_mgnt_cb.private_addr,6) == 0) { - UINT8_TO_STREAM (p, 0x01); - BDADDR_TO_STREAM (p, btm_cb.ble_ctr_cb.addr_mgnt_cb.static_rand_addr); - } else -#endif ///BLE_INCLUDED == TRUE - { - UINT8_TO_STREAM (p, 0); - BDADDR_TO_STREAM (p, controller_get_interface()->get_address()->address); - } + UINT8_TO_STREAM(p, SMP_OPCODE_ID_ADDR); + UINT8_TO_STREAM(p, id_type); + BDADDR_TO_STREAM(p, id_addr); p_buf->offset = L2CAP_MIN_OFFSET; - p_buf->len = SMP_ID_ADDR_SIZE; + p_buf->len = SMP_ID_ADDR_SIZE; } return p_buf; diff --git a/components/bt/host/nimble/CMakeLists.txt b/components/bt/host/nimble/CMakeLists.txt new file mode 100644 index 00000000000..74c10a6e816 --- /dev/null +++ b/components/bt/host/nimble/CMakeLists.txt @@ -0,0 +1,282 @@ +function(set_nimble_host_compile_flags) + if(NOT CONFIG_BT_NIMBLE_ENABLED) + return() + endif() + + # TODO: These warnings should be resolved in the NimBLE code + if(CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_DISABLE) + # some variables in NimBLE are only used by asserts + target_compile_options(${COMPONENT_LIB} PRIVATE -Wno-unused-but-set-variable -Wno-unused-variable) + endif() + + if(CMAKE_C_COMPILER_ID MATCHES "GNU" AND CMAKE_C_COMPILER_VERSION VERSION_GREATER 15.0) + if(CONFIG_BT_NIMBLE_MESH) + set_source_files_properties("${CMAKE_CURRENT_FUNCTION_LIST_DIR}/nimble/nimble/host/mesh/src/prov.c" + PROPERTIES COMPILE_FLAGS "-Wno-unterminated-string-initialization") + endif() + endif() +endfunction() + +set(nimble_host_srcs "" PARENT_SCOPE) +set(nimble_host_include_dirs "" PARENT_SCOPE) + +# API headers that are used in the docs are also compiled +# even if CONFIG_BT_ENABLED=n as long as CONFIG_IDF_DOC_BUILD=y +if(CONFIG_IDF_DOC_BUILD) + set(nimble_host_include_dirs + "${CMAKE_CURRENT_LIST_DIR}/esp-hci/include" + PARENT_SCOPE + ) + return() +endif() + +if(NOT CONFIG_BT_NIMBLE_ENABLED) + return() +endif() + +list(APPEND nimble_host_srcs + "${CMAKE_CURRENT_LIST_DIR}/nimble/nimble/transport/src/transport.c" + "${CMAKE_CURRENT_LIST_DIR}/nimble/nimble/host/util/src/addr.c" + "${CMAKE_CURRENT_LIST_DIR}/nimble/nimble/host/services/gatt/src/ble_svc_gatt.c" + "${CMAKE_CURRENT_LIST_DIR}/nimble/nimble/host/services/tps/src/ble_svc_tps.c" + "${CMAKE_CURRENT_LIST_DIR}/nimble/nimble/host/services/ias/src/ble_svc_ias.c" + "${CMAKE_CURRENT_LIST_DIR}/nimble/nimble/host/services/ipss/src/ble_svc_ipss.c" + "${CMAKE_CURRENT_LIST_DIR}/nimble/nimble/host/services/ans/src/ble_svc_ans.c" + "${CMAKE_CURRENT_LIST_DIR}/nimble/nimble/host/services/hr/src/ble_svc_hr.c" + "${CMAKE_CURRENT_LIST_DIR}/nimble/nimble/host/services/htp/src/ble_svc_htp.c" + "${CMAKE_CURRENT_LIST_DIR}/nimble/nimble/host/services/gap/src/ble_svc_gap.c" + "${CMAKE_CURRENT_LIST_DIR}/nimble/nimble/host/services/bas/src/ble_svc_bas.c" + "${CMAKE_CURRENT_LIST_DIR}/nimble/nimble/host/services/dis/src/ble_svc_dis.c" + "${CMAKE_CURRENT_LIST_DIR}/nimble/nimble/host/services/lls/src/ble_svc_lls.c" + "${CMAKE_CURRENT_LIST_DIR}/nimble/nimble/host/services/prox/src/ble_svc_prox.c" + "${CMAKE_CURRENT_LIST_DIR}/nimble/nimble/host/services/cts/src/ble_svc_cts.c" + "${CMAKE_CURRENT_LIST_DIR}/nimble/nimble/host/services/hid/src/ble_svc_hid.c" + "${CMAKE_CURRENT_LIST_DIR}/nimble/nimble/host/services/sps/src/ble_svc_sps.c" + "${CMAKE_CURRENT_LIST_DIR}/nimble/nimble/host/services/cte/src/ble_svc_cte.c" + "${CMAKE_CURRENT_LIST_DIR}/nimble/nimble/host/services/ras/src/ble_svc_ras.c" + "${CMAKE_CURRENT_LIST_DIR}/nimble/nimble/host/src/ble_cs.c" + "${CMAKE_CURRENT_LIST_DIR}/nimble/nimble/host/src/ble_hs_conn.c" + "${CMAKE_CURRENT_LIST_DIR}/nimble/nimble/host/src/ble_store_util.c" + "${CMAKE_CURRENT_LIST_DIR}/nimble/nimble/host/src/ble_sm.c" + "${CMAKE_CURRENT_LIST_DIR}/nimble/nimble/host/src/ble_hs_shutdown.c" + "${CMAKE_CURRENT_LIST_DIR}/nimble/nimble/host/src/ble_l2cap_sig_cmd.c" + "${CMAKE_CURRENT_LIST_DIR}/nimble/nimble/host/src/ble_hs_hci_cmd.c" + "${CMAKE_CURRENT_LIST_DIR}/nimble/nimble/host/src/ble_hs_id.c" + "${CMAKE_CURRENT_LIST_DIR}/nimble/nimble/host/src/ble_att_svr.c" + "${CMAKE_CURRENT_LIST_DIR}/nimble/nimble/host/src/ble_gatts_lcl.c" + "${CMAKE_CURRENT_LIST_DIR}/nimble/nimble/host/src/ble_ibeacon.c" + "${CMAKE_CURRENT_LIST_DIR}/nimble/nimble/host/src/ble_hs_atomic.c" + "${CMAKE_CURRENT_LIST_DIR}/nimble/nimble/host/src/ble_sm_alg.c" + "${CMAKE_CURRENT_LIST_DIR}/nimble/nimble/host/src/ble_hs_stop.c" + "${CMAKE_CURRENT_LIST_DIR}/nimble/nimble/host/src/ble_hs.c" + "${CMAKE_CURRENT_LIST_DIR}/nimble/nimble/host/src/ble_hs_hci_evt.c" + "${CMAKE_CURRENT_LIST_DIR}/nimble/nimble/host/src/ble_hs_mqueue.c" + "${CMAKE_CURRENT_LIST_DIR}/nimble/nimble/host/src/ble_hs_periodic_sync.c" + "${CMAKE_CURRENT_LIST_DIR}/nimble/nimble/host/src/ble_att.c" + "${CMAKE_CURRENT_LIST_DIR}/nimble/nimble/host/src/ble_ead.c" + "${CMAKE_CURRENT_LIST_DIR}/nimble/nimble/host/src/ble_aes_ccm.c" + "${CMAKE_CURRENT_LIST_DIR}/nimble/nimble/host/src/ble_gattc.c" + "${CMAKE_CURRENT_LIST_DIR}/nimble/nimble/host/src/ble_store.c" + "${CMAKE_CURRENT_LIST_DIR}/nimble/nimble/host/src/ble_sm_lgcy.c" + "${CMAKE_CURRENT_LIST_DIR}/nimble/nimble/host/src/ble_hs_cfg.c" + "${CMAKE_CURRENT_LIST_DIR}/nimble/nimble/host/src/ble_att_clt.c" + "${CMAKE_CURRENT_LIST_DIR}/nimble/nimble/host/src/ble_l2cap_coc.c" + "${CMAKE_CURRENT_LIST_DIR}/nimble/nimble/host/src/ble_hs_mbuf.c" + "${CMAKE_CURRENT_LIST_DIR}/nimble/nimble/host/src/ble_att_cmd.c" + "${CMAKE_CURRENT_LIST_DIR}/nimble/nimble/host/src/ble_hs_log.c" + "${CMAKE_CURRENT_LIST_DIR}/nimble/nimble/host/src/ble_eddystone.c" + "${CMAKE_CURRENT_LIST_DIR}/nimble/nimble/host/src/ble_hs_startup.c" + "${CMAKE_CURRENT_LIST_DIR}/nimble/nimble/host/src/ble_l2cap_sig.c" + "${CMAKE_CURRENT_LIST_DIR}/nimble/nimble/host/src/ble_gap.c" + "${CMAKE_CURRENT_LIST_DIR}/nimble/nimble/host/src/ble_sm_cmd.c" + "${CMAKE_CURRENT_LIST_DIR}/nimble/nimble/host/src/ble_uuid.c" + "${CMAKE_CURRENT_LIST_DIR}/nimble/nimble/host/src/ble_hs_pvcy.c" + "${CMAKE_CURRENT_LIST_DIR}/nimble/nimble/host/src/ble_hs_flow.c" + "${CMAKE_CURRENT_LIST_DIR}/nimble/nimble/host/src/ble_l2cap.c" + "${CMAKE_CURRENT_LIST_DIR}/nimble/nimble/host/src/ble_sm_sc.c" + "${CMAKE_CURRENT_LIST_DIR}/nimble/nimble/host/src/ble_hs_misc.c" + "${CMAKE_CURRENT_LIST_DIR}/nimble/nimble/host/src/ble_gatts.c" + "${CMAKE_CURRENT_LIST_DIR}/nimble/nimble/host/src/ble_hs_adv.c" + "${CMAKE_CURRENT_LIST_DIR}/nimble/nimble/host/src/ble_hs_hci.c" + "${CMAKE_CURRENT_LIST_DIR}/nimble/nimble/host/src/ble_hs_hci_util.c" + "${CMAKE_CURRENT_LIST_DIR}/nimble/nimble/host/src/ble_hs_resolv.c" + "${CMAKE_CURRENT_LIST_DIR}/nimble/nimble/host/store/ram/src/ble_store_ram.c" + "${CMAKE_CURRENT_LIST_DIR}/nimble/nimble/host/store/config/src/ble_store_config.c" + "${CMAKE_CURRENT_LIST_DIR}/nimble/nimble/host/store/config/src/ble_store_nvs.c" + "${CMAKE_CURRENT_LIST_DIR}/nimble/nimble/host/src/ble_gattc_cache.c" + "${CMAKE_CURRENT_LIST_DIR}/nimble/nimble/host/src/ble_gattc_cache_conn.c" + "${CMAKE_CURRENT_LIST_DIR}/nimble/nimble/host/src/ble_eatt.c" + # NimBLE Porting + "${CMAKE_CURRENT_LIST_DIR}/nimble/porting/nimble/src/nimble_port.c" + "${CMAKE_CURRENT_LIST_DIR}/port/src/nvs_port.c" + "${CMAKE_CURRENT_LIST_DIR}/port/src/esp_nimble_mem.c" + # TODO: Add this file in the blufi cmake file + "${CMAKE_CURRENT_LIST_DIR}/../../common/btc/profile/esp/blufi/nimble_host/esp_blufi.c" +) + +# TODO: Added this file in the ble mesh cmake file +if(CONFIG_BLE_MESH) + list(APPEND nimble_host_srcs "${CMAKE_CURRENT_LIST_DIR}/../../esp_ble_mesh/core/nimble_host/adapter.c") +endif() + +if(CONFIG_BT_NIMBLE_ISO) + list(APPEND nimble_host_srcs + "${CMAKE_CURRENT_LIST_DIR}/nimble/nimble/host/src/ble_hs_iso_hci.c" + "${CMAKE_CURRENT_LIST_DIR}/nimble/nimble/host/src/ble_hs_iso.c" + ) +endif() + +# Nimble porting layer for FreeRTOS +if(CONFIG_BT_DUAL_MODE_ARCH) + list(APPEND nimble_host_srcs + "${CMAKE_CURRENT_LIST_DIR}/nimble/porting/npl/esp-idf/src/nimble_port_freertos.c" + ) + list(APPEND nimble_host_include_dirs + "${CMAKE_CURRENT_LIST_DIR}/nimble/porting/npl/esp-idf/include" + ) +else() + list(APPEND nimble_host_srcs + "${CMAKE_CURRENT_LIST_DIR}/nimble/porting/npl/freertos/src/nimble_port_freertos.c" + ) +endif() + +if(CONFIG_BT_CONTROLLER_DISABLED) + # UART HCI driver for host only mode + if(CONFIG_BT_NIMBLE_TRANSPORT_UART) + list(APPEND nimble_host_srcs + "${CMAKE_CURRENT_LIST_DIR}/nimble/nimble/transport/uart_ll/src/hci_uart.c" + "${CMAKE_CURRENT_LIST_DIR}/nimble/nimble/transport/common/hci_h4/src/hci_h4.c" + "${CMAKE_CURRENT_LIST_DIR}/nimble/porting/nimble/src/hal_uart.c" + ) + endif() +else() + # Inter-Process Communication (VHCI) + if(CONFIG_BT_NIMBLE_LEGACY_VHCI_ENABLE) + list(APPEND nimble_host_srcs + "${CMAKE_CURRENT_LIST_DIR}/esp-hci/src/esp_nimble_hci.c" + "${CMAKE_CURRENT_LIST_DIR}/nimble/nimble/transport/esp_ipc_legacy/src/hci_esp_ipc_legacy.c" + ) + list(APPEND nimble_host_include_dirs + ${CMAKE_CURRENT_LIST_DIR}/esp-hci/include + ) + elseif(CONFIG_BT_DUAL_MODE_ARCH) + list(APPEND nimble_host_srcs + "${CMAKE_CURRENT_LIST_DIR}/nimble/nimble/transport/esp_ipc_btdm/src/hci_esp_ipc.c" + ) + else() + list(APPEND nimble_host_srcs + "${CMAKE_CURRENT_LIST_DIR}/nimble/nimble/transport/esp_ipc/src/hci_esp_ipc.c" + ) + endif() +endif() + +# Reuse the code from the controller to reduce flash usage +if(CONFIG_BT_CONTROLLER_DISABLED OR NOT CONFIG_SOC_ESP_NIMBLE_CONTROLLER) + list(APPEND nimble_host_srcs + "${CMAKE_CURRENT_LIST_DIR}/nimble/porting/nimble/src/endian.c" + #TODO: Split this into a separate file for the controller and the host + "${CMAKE_CURRENT_LIST_DIR}/../../porting/mem/os_mempool.c" + "${CMAKE_CURRENT_LIST_DIR}/nimble/porting/nimble/src/mem.c" + "${CMAKE_CURRENT_LIST_DIR}/nimble/porting/nimble/src/os_mbuf.c" + "${CMAKE_CURRENT_LIST_DIR}/nimble/porting/nimble/src/os_msys_init.c" + "${CMAKE_CURRENT_LIST_DIR}/nimble/porting/npl/freertos/src/npl_os_freertos.c" + ) +endif() + +list(APPEND nimble_host_include_dirs + ${CMAKE_CURRENT_LIST_DIR}/nimble/nimble/host/include + ${CMAKE_CURRENT_LIST_DIR}/nimble/nimble/include + ${CMAKE_CURRENT_LIST_DIR}/nimble/nimble/host/services/ans/include + ${CMAKE_CURRENT_LIST_DIR}/nimble/nimble/host/services/bas/include + ${CMAKE_CURRENT_LIST_DIR}/nimble/nimble/host/services/dis/include + ${CMAKE_CURRENT_LIST_DIR}/nimble/nimble/host/services/gap/include + ${CMAKE_CURRENT_LIST_DIR}/nimble/nimble/host/services/gatt/include + ${CMAKE_CURRENT_LIST_DIR}/nimble/nimble/host/services/hr/include + ${CMAKE_CURRENT_LIST_DIR}/nimble/nimble/host/services/htp/include + ${CMAKE_CURRENT_LIST_DIR}/nimble/nimble/host/services/ias/include + ${CMAKE_CURRENT_LIST_DIR}/nimble/nimble/host/services/ipss/include + ${CMAKE_CURRENT_LIST_DIR}/nimble/nimble/host/services/lls/include + ${CMAKE_CURRENT_LIST_DIR}/nimble/nimble/host/services/prox/include + ${CMAKE_CURRENT_LIST_DIR}/nimble/nimble/host/services/cts/include + ${CMAKE_CURRENT_LIST_DIR}/nimble/nimble/host/services/tps/include + ${CMAKE_CURRENT_LIST_DIR}/nimble/nimble/host/services/hid/include + ${CMAKE_CURRENT_LIST_DIR}/nimble/nimble/host/services/sps/include + ${CMAKE_CURRENT_LIST_DIR}/nimble/nimble/host/services/cte/include + ${CMAKE_CURRENT_LIST_DIR}/nimble/nimble/host/util/include + ${CMAKE_CURRENT_LIST_DIR}/nimble/nimble/host/store/ram/include + ${CMAKE_CURRENT_LIST_DIR}/nimble/nimble/host/store/config/include + ${CMAKE_CURRENT_LIST_DIR}/nimble/nimble/host/services/ras/include + + ${CMAKE_CURRENT_LIST_DIR}/nimble/porting/nimble/include + ${CMAKE_CURRENT_LIST_DIR}/port/include + ${CMAKE_CURRENT_LIST_DIR}/nimble/nimble/transport/include +) + +if(CONFIG_BT_CONTROLLER_DISABLED) + list(APPEND nimble_host_include_dirs + ${CMAKE_CURRENT_LIST_DIR}/nimble/nimble/transport/common/hci_h4/include + ) +endif() + +if(CONFIG_BT_CONTROLLER_DISABLED OR NOT CONFIG_SOC_ESP_NIMBLE_CONTROLLER) + list(APPEND nimble_host_include_dirs + ${CMAKE_CURRENT_LIST_DIR}/../../porting/include + ${CMAKE_CURRENT_LIST_DIR}/nimble/porting/npl/freertos/include + ) +endif() + +# BLE NIMBLE MESH (Deprecated) +if(CONFIG_BT_NIMBLE_MESH) + list(APPEND nimble_host_srcs + "${CMAKE_CURRENT_LIST_DIR}/nimble/nimble/host/mesh/src/shell.c" + "${CMAKE_CURRENT_LIST_DIR}/nimble/nimble/host/mesh/src/friend.c" + "${CMAKE_CURRENT_LIST_DIR}/nimble/nimble/host/mesh/src/crypto.c" + "${CMAKE_CURRENT_LIST_DIR}/nimble/nimble/host/mesh/src/settings.c" + "${CMAKE_CURRENT_LIST_DIR}/nimble/nimble/host/mesh/src/adv.c" + "${CMAKE_CURRENT_LIST_DIR}/nimble/nimble/host/mesh/src/adv_ext.c" + "${CMAKE_CURRENT_LIST_DIR}/nimble/nimble/host/mesh/src/adv_legacy.c" + "${CMAKE_CURRENT_LIST_DIR}/nimble/nimble/host/mesh/src/model_srv.c" + "${CMAKE_CURRENT_LIST_DIR}/nimble/nimble/host/mesh/src/msg.c" + "${CMAKE_CURRENT_LIST_DIR}/nimble/nimble/host/mesh/src/beacon.c" + "${CMAKE_CURRENT_LIST_DIR}/nimble/nimble/host/mesh/src/glue.c" + "${CMAKE_CURRENT_LIST_DIR}/nimble/nimble/host/mesh/src/model_cli.c" + "${CMAKE_CURRENT_LIST_DIR}/nimble/nimble/host/mesh/src/transport.c" + "${CMAKE_CURRENT_LIST_DIR}/nimble/nimble/host/mesh/src/prov.c" + "${CMAKE_CURRENT_LIST_DIR}/nimble/nimble/host/mesh/src/mesh.c" + "${CMAKE_CURRENT_LIST_DIR}/nimble/nimble/host/mesh/src/access.c" + "${CMAKE_CURRENT_LIST_DIR}/nimble/nimble/host/mesh/src/cfg_srv.c" + "${CMAKE_CURRENT_LIST_DIR}/nimble/nimble/host/mesh/src/cfg_cli.c" + "${CMAKE_CURRENT_LIST_DIR}/nimble/nimble/host/mesh/src/light_model.c" + "${CMAKE_CURRENT_LIST_DIR}/nimble/nimble/host/mesh/src/health_cli.c" + "${CMAKE_CURRENT_LIST_DIR}/nimble/nimble/host/mesh/src/lpn.c" + "${CMAKE_CURRENT_LIST_DIR}/nimble/nimble/host/mesh/src/health_srv.c" + "${CMAKE_CURRENT_LIST_DIR}/nimble/nimble/host/mesh/src/testing.c" + "${CMAKE_CURRENT_LIST_DIR}/nimble/nimble/host/mesh/src/aes-ccm.c" + "${CMAKE_CURRENT_LIST_DIR}/nimble/nimble/host/mesh/src/app_keys.c" + "${CMAKE_CURRENT_LIST_DIR}/nimble/nimble/host/mesh/src/cdb.c" + "${CMAKE_CURRENT_LIST_DIR}/nimble/nimble/host/mesh/src/cfg.c" + "${CMAKE_CURRENT_LIST_DIR}/nimble/nimble/host/mesh/src/pb_adv.c" + "${CMAKE_CURRENT_LIST_DIR}/nimble/nimble/host/mesh/src/pb_gatt.c" + "${CMAKE_CURRENT_LIST_DIR}/nimble/nimble/host/mesh/src/pb_gatt_srv.c" + "${CMAKE_CURRENT_LIST_DIR}/nimble/nimble/host/mesh/src/prov_device.c" + "${CMAKE_CURRENT_LIST_DIR}/nimble/nimble/host/mesh/src/provisioner.c" + "${CMAKE_CURRENT_LIST_DIR}/nimble/nimble/host/mesh/src/heartbeat.c" + "${CMAKE_CURRENT_LIST_DIR}/nimble/nimble/host/mesh/src/rpl.c" + "${CMAKE_CURRENT_LIST_DIR}/nimble/nimble/host/mesh/src/subnet.c" + "${CMAKE_CURRENT_LIST_DIR}/nimble/nimble/host/mesh/src/proxy_msg.c" + "${CMAKE_CURRENT_LIST_DIR}/nimble/nimble/host/mesh/src/proxy_srv.c" + "${CMAKE_CURRENT_LIST_DIR}/nimble/nimble/host/mesh/src/net.c" + ) + + list(APPEND nimble_host_include_dirs + ${CMAKE_CURRENT_LIST_DIR}/nimble/nimble/host/mesh/include + ${CMAKE_CURRENT_LIST_DIR}/nimble/nimble/host/include/host + ) + + set_source_files_properties("${CMAKE_CURRENT_LIST_DIR}/nimble/nimble/host/mesh/src/net.c" + PROPERTIES COMPILE_FLAGS -Wno-type-limits) +endif() + +# Export the variables to the parent scope +set(nimble_host_srcs "${nimble_host_srcs}" PARENT_SCOPE) +set(nimble_host_include_dirs "${nimble_host_include_dirs}" PARENT_SCOPE) diff --git a/components/bt/host/nimble/Kconfig.in b/components/bt/host/nimble/Kconfig.in index 539482b93fc..6749440bc06 100644 --- a/components/bt/host/nimble/Kconfig.in +++ b/components/bt/host/nimble/Kconfig.in @@ -917,6 +917,14 @@ menu "Services" help Defines maximum number of report characteristics per service instance + config BT_NIMBLE_RAS_SERVICE + depends on BT_NIMBLE_CHANNEL_SOUNDING + bool "RAS service" + default y + help + Enable RAS Service + + menuconfig BT_NIMBLE_BAS_SERVICE bool "Battery service" default y diff --git a/components/bt/host/nimble/esp-hci/src/esp_nimble_hci.c b/components/bt/host/nimble/esp-hci/src/esp_nimble_hci.c index 02065c08538..125486965d9 100644 --- a/components/bt/host/nimble/esp-hci/src/esp_nimble_hci.c +++ b/components/bt/host/nimble/esp-hci/src/esp_nimble_hci.c @@ -78,7 +78,11 @@ void ble_hci_trans_cfg_hs(ble_hci_trans_rx_cmd_fn *cmd_cb, void esp_vhci_host_send_packet_wrapper(uint8_t *data, uint16_t len) { #if (BT_HCI_LOG_INCLUDED == TRUE) - bt_hci_log_record_hci_data(data[0], &data[1], len - 1); + uint8_t data_type = bt_hci_log_h4_type_to_data_type(data[0]); + bt_hci_log_record_hci_data(data_type, &data[1], len - 1); +#if BT_HCI_INSIGHTS_INCLUDED + bt_hci_log_record_insights(data_type, &data[1], len - 1); +#endif #endif #if CONFIG_BT_BLE_LOG_SPI_OUT_HCI_ENABLED ble_log_spi_out_hci_write(BLE_LOG_SPI_OUT_SOURCE_HCI_DOWNSTREAM, data, len); @@ -178,7 +182,6 @@ static void ble_hci_rx_acl(uint8_t *data, uint16_t len) { struct os_mbuf *m = NULL; int rc; - int sr; int retry_count = 1; @@ -212,9 +215,7 @@ static void ble_hci_rx_acl(uint8_t *data, uint16_t len) os_mbuf_free_chain(m); return; } - OS_ENTER_CRITICAL(sr); ble_transport_to_hs_acl(m); - OS_EXIT_CRITICAL(sr); } #endif @@ -237,18 +238,24 @@ static void dummy_controller_rcv_pkt_ready(void) void bt_record_hci_data(uint8_t *data, uint16_t len) { #if (BT_HCI_LOG_INCLUDED == TRUE) - if ((data[0] == BLE_HCI_UART_H4_EVT) && (data[1] == BLE_HCI_EVCODE_LE_META) && ((data[3] == BLE_HCI_LE_SUBEV_ADV_RPT) || (data[3] == BLE_HCI_LE_SUBEV_DIRECT_ADV_RPT) + if (len < 2) { + return; + } + if ((len >= 4) && (data[0] == BLE_HCI_UART_H4_EVT) && (data[1] == BLE_HCI_EVCODE_LE_META) && ((data[3] == BLE_HCI_LE_SUBEV_ADV_RPT) || (data[3] == BLE_HCI_LE_SUBEV_DIRECT_ADV_RPT) || (data[3] == BLE_HCI_LE_SUBEV_EXT_ADV_RPT) || (data[3] == BLE_HCI_LE_SUBEV_PERIODIC_ADV_RPT))) { bt_hci_log_record_hci_adv(HCI_LOG_DATA_TYPE_ADV, &data[2], len - 2); +#if BT_HCI_INSIGHTS_INCLUDED + bt_hci_log_record_insights(HCI_LOG_DATA_TYPE_ADV, &data[2], len - 2); +#endif } else { uint8_t data_type; - if (data[0] == HCI_LOG_DATA_TYPE_ISO_DATA) { - data_type = HCI_LOG_DATA_TYPE_ISO_DATA; - } else { - data_type = ((data[0] == 2) ? HCI_LOG_DATA_TYPE_C2H_ACL : data[0]); - } + data_type = ((data[0] == 2) ? HCI_LOG_DATA_TYPE_C2H_ACL : bt_hci_log_h4_type_to_data_type(data[0])); bt_hci_log_record_hci_data(data_type, &data[1], len - 1); +#if BT_HCI_INSIGHTS_INCLUDED + bt_hci_log_record_insights(data_type, &data[1], len - 1); +#endif } + #endif // (BT_HCI_LOG_INCLUDED == TRUE) } diff --git a/components/bt/host/nimble/nimble b/components/bt/host/nimble/nimble index 5d8b70207a6..bdc5010548e 160000 --- a/components/bt/host/nimble/nimble +++ b/components/bt/host/nimble/nimble @@ -1 +1 @@ -Subproject commit 5d8b70207a6324f45d9f548ff57ec3b154afccdd +Subproject commit bdc5010548e988a770adb8af107d01feb850c0ca diff --git a/components/bt/host/nimble/port/include/esp_nimble_cfg.h b/components/bt/host/nimble/port/include/esp_nimble_cfg.h index 54c854d66e2..a0f62f36aa2 100644 --- a/components/bt/host/nimble/port/include/esp_nimble_cfg.h +++ b/components/bt/host/nimble/port/include/esp_nimble_cfg.h @@ -2382,4 +2382,12 @@ #endif #endif +#ifndef MYNEWT_VAL_BT_NIMBLE_INSIGHTS_ENABLE +#ifdef CONFIG_BT_NIMBLE_INSIGHTS_ENABLE +#define MYNEWT_VAL_BT_NIMBLE_INSIGHTS_ENABLE CONFIG_BT_NIMBLE_INSIGHTS_ENABLE +#else +#define MYNEWT_VAL_BT_NIMBLE_INSIGHTS_ENABLE (0) +#endif +#endif + #endif diff --git a/components/bt/include/esp32c3/include/esp_bt.h b/components/bt/include/esp32c3/include/esp_bt.h index 48e939be06b..64074c1b717 100644 --- a/components/bt/include/esp32c3/include/esp_bt.h +++ b/components/bt/include/esp32c3/include/esp_bt.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -268,7 +268,21 @@ typedef void (* esp_bt_hci_tl_callback_t) (void *arg, uint8_t status); #define BT_CTRL_RUN_IN_FLASH_ONLY (0) #endif - +/* + * Only when CONFIG_BT_CTRL_CHECK_CONFIG_EFF is absent: this build does not + * run controller Kconfig, so missing CONFIG_BT_CTRL_BLE_* means "use default", + * not "disabled". When the symbol is defined (normal IDF sdkconfig), Kconfig + * is authoritative: bool=n leaves CONFIG_BT_CTRL_BLE_* undefined and must + * not be overridden here. + */ +#ifndef CONFIG_BT_CTRL_CHECK_CONFIG_EFF +#define CONFIG_BT_CTRL_BLE_ADV 1 +#define CONFIG_BT_CTRL_BLE_SCAN 1 +#define CONFIG_BT_CTRL_DTM_ENABLE 1 +#define CONFIG_BT_CTRL_BLE_MASTER 1 +#define CONFIG_BT_CTRL_BLE_SECURITY_ENABLE 1 +#define CONFIG_BT_CTRL_BLE_MIN_CONN_INTERVAL_ENABLE 1 +#endif /* !CONFIG_BT_CTRL_CHECK_CONFIG_EFF */ #if defined(CONFIG_BT_CTRL_DTM_ENABLE) #define BT_CTRL_DTM_ENABLE CONFIG_BT_CTRL_DTM_ENABLE @@ -525,7 +539,8 @@ typedef enum { * After disconnecting, the corresponding TX power will not be affected. * 2. `ESP_BLE_PWR_TYPE_DEFAULT` can be used to set the TX power for power types that have not been set before. * It will not affect the TX power values which have been set for the ADV/SCAN/CONN0-8 power types. - * 3. If none of power type is set, the system will use `ESP_PWR_LVL_P3` as default for all power types. + * 3. If no runtime TX power is configured, the system uses the menuconfig default + * TX power (`CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL`) for all power types. */ typedef enum { ESP_BLE_PWR_TYPE_CONN_HDL0 = 0, /*!< TX power for Connection state handle 0 */ @@ -788,7 +803,19 @@ void esp_bt_controller_wakeup_request(void); * * It is recommended to use `esp_ble_tx_power_set_enhanced` to set TX power for individual advertising and connection handle. * - * @note Connection TX power should only be set after the connection is established. + * @note + * 1. Connection TX power should only be set after the connection is established. + * 2. Priority from high to low: + * - ADV TX power in `esp_ble_gap_ext_adv_set_params()`. + * - TX power configured by `esp_ble_tx_power_set_enhanced()` / `esp_ble_tx_power_set()`. + * - Menuconfig default TX power (`CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL`). + * 3. If TX power is not configured through `esp_ble_gap_ext_adv_set_params()`, + * `esp_ble_tx_power_set_enhanced()`, or `esp_ble_tx_power_set()`, + * the menuconfig default TX power is applied globally. + * 4. On ESP32-C3/ESP32-S3, Controller TX power resolution is 3 dBm per step. + * The actual applied TX power may be 0 to 2 dBm lower than requested. + * For example, request 0 dBm -> apply 0 dBm; request 1/2 dBm -> apply 0 dBm; + * request 3 dBm -> apply 3 dBm. * * @param[in] power_type The type of TX power. It could be Advertising, Connection, or Default. * @param[in] power_level Power level (index) corresponding to the absolute value (dBm) @@ -828,6 +855,17 @@ esp_power_level_t esp_ble_tx_power_get(esp_ble_power_type_t power_type); * * @note * 1. Connection TX power should only be set after connection created. + * 2. Priority from high to low: + * - ADV TX power in `esp_ble_gap_ext_adv_set_params()`. + * - TX power configured by `esp_ble_tx_power_set_enhanced()` / `esp_ble_tx_power_set()`. + * - Menuconfig default TX power (`CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL`). + * 3. If TX power is not configured through `esp_ble_gap_ext_adv_set_params()`, + * `esp_ble_tx_power_set_enhanced()`, or `esp_ble_tx_power_set()`, + * the menuconfig default TX power is applied globally. + * 4. On ESP32-C3/ESP32-S3, Controller TX power resolution is 3 dBm per step. + * The actual applied TX power may be 0 to 2 dBm lower than requested. + * For example, request 0 dBm -> apply 0 dBm; request 1/2 dBm -> apply 0 dBm; + * request 3 dBm -> apply 3 dBm. * * @param[in] power_type The type of TX power * @param[in] handle The handle of Advertising or Connection diff --git a/components/bt/porting/CMakeLists.txt b/components/bt/porting/CMakeLists.txt new file mode 100644 index 00000000000..b8b0ff0097f --- /dev/null +++ b/components/bt/porting/CMakeLists.txt @@ -0,0 +1,58 @@ +set(porting_srcs "" PARENT_SCOPE) +set(porting_include_dirs "" PARENT_SCOPE) +set(porting_priv_include_dirs "" PARENT_SCOPE) + +if(NOT CONFIG_BT_CONTROLLER_ENABLED) + return() +endif() + +list(APPEND porting_priv_include_dirs + ${CMAKE_CURRENT_SOURCE_DIR}/mem/ + ${CMAKE_CURRENT_SOURCE_DIR}/include +) + +list(APPEND porting_srcs + "${CMAKE_CURRENT_SOURCE_DIR}/mem/bt_osi_mem.c" + "${CMAKE_CURRENT_SOURCE_DIR}/mem/os_msys_init.c" + "${CMAKE_CURRENT_SOURCE_DIR}/mem/os_mempool.c" + "${CMAKE_CURRENT_SOURCE_DIR}/npl/freertos/src/npl_os_freertos.c" + "${CMAKE_CURRENT_SOURCE_DIR}/transport/src/hci_transport.c" +) + +if(CONFIG_BT_LE_HCI_INTERFACE_USE_RAM) + if(CONFIG_BT_NIMBLE_ENABLED) + list(APPEND porting_srcs + "${CMAKE_CURRENT_SOURCE_DIR}/transport/driver/vhci/hci_driver_nimble.c" + ) + else() + list(APPEND porting_srcs + "${CMAKE_CURRENT_SOURCE_DIR}/transport/driver/vhci/hci_driver_standard.c" + ) + endif() +elseif(CONFIG_BT_LE_HCI_INTERFACE_USE_UART) + list(APPEND porting_srcs + "${CMAKE_CURRENT_SOURCE_DIR}/transport/driver/common/hci_driver_util.c" + "${CMAKE_CURRENT_SOURCE_DIR}/transport/driver/common/hci_driver_h4.c" + "${CMAKE_CURRENT_SOURCE_DIR}/transport/driver/common/hci_driver_mem.c" + "${CMAKE_CURRENT_SOURCE_DIR}/transport/driver/uart/hci_driver_uart_config.c" + ) + if(CONFIG_BT_LE_UART_HCI_DMA_MODE) + list(APPEND porting_srcs + "${CMAKE_CURRENT_SOURCE_DIR}/transport/driver/uart/hci_driver_uart_dma.c" + ) + else() + list(APPEND porting_srcs + "${CMAKE_CURRENT_SOURCE_DIR}/transport/driver/uart/hci_driver_uart.c" + ) + endif() +endif() + +list(APPEND porting_include_dirs + "${CMAKE_CURRENT_SOURCE_DIR}/include" + "${CMAKE_CURRENT_SOURCE_DIR}/npl/freertos/include" + "${CMAKE_CURRENT_SOURCE_DIR}/transport/include" +) + +set(porting_srcs ${porting_srcs} PARENT_SCOPE) +set(porting_include_dirs ${porting_include_dirs} PARENT_SCOPE) +set(porting_priv_include_dirs ${porting_priv_include_dirs} PARENT_SCOPE) diff --git a/components/bt/porting/npl/freertos/src/npl_os_freertos.c b/components/bt/porting/npl/freertos/src/npl_os_freertos.c index d4b8fc883c5..dcc09ce2628 100644 --- a/components/bt/porting/npl/freertos/src/npl_os_freertos.c +++ b/components/bt/porting/npl/freertos/src/npl_os_freertos.c @@ -25,6 +25,8 @@ portMUX_TYPE ble_port_mutex = portMUX_INITIALIZER_UNLOCKED; +static SemaphoreHandle_t npl_eventq_sync; + #if BLE_NPL_USE_ESP_TIMER static const char *TAG = "Timer"; #endif @@ -197,6 +199,115 @@ IRAM_ATTR in_isr(void) return xPortInIsrContext() != 0; } +static void +npl_eventq_sync_init(void) +{ + if (npl_eventq_sync == NULL) { + npl_eventq_sync = xSemaphoreCreateMutex(); + BLE_LL_ASSERT(npl_eventq_sync); + } +} + +static void +npl_eventq_lock(void) +{ + if (!in_isr()) { + BLE_LL_ASSERT(npl_eventq_sync); + xSemaphoreTake(npl_eventq_sync, portMAX_DELAY); + } +} + +static void +npl_eventq_unlock(void) +{ + if (!in_isr()) { + xSemaphoreGive(npl_eventq_sync); + } +} + +static bool IRAM_ATTR +npl_eventq_queued_get_isr(struct ble_npl_event_freertos *event) +{ + bool queued; + + portENTER_CRITICAL_ISR(&ble_port_mutex); + queued = event->queued; + portEXIT_CRITICAL_ISR(&ble_port_mutex); + return queued; +} + +static void IRAM_ATTR +npl_eventq_queued_set_isr(struct ble_npl_event_freertos *event, bool queued) +{ + portENTER_CRITICAL_ISR(&ble_port_mutex); + event->queued = queued; + portEXIT_CRITICAL_ISR(&ble_port_mutex); +} + +static bool IRAM_ATTR +npl_eventq_queued_claim_isr(struct ble_npl_event_freertos *event) +{ + bool already; + + portENTER_CRITICAL_ISR(&ble_port_mutex); + already = event->queued; + if (!already) { + event->queued = true; + } + portEXIT_CRITICAL_ISR(&ble_port_mutex); + return already; +} + +static void IRAM_ATTR +npl_eventq_queued_set_task(struct ble_npl_event_freertos *event, bool queued) +{ + portENTER_CRITICAL(&ble_port_mutex); + event->queued = queued; + portEXIT_CRITICAL(&ble_port_mutex); +} + +static bool IRAM_ATTR +npl_eventq_queued_get_task(struct ble_npl_event_freertos *event) +{ + bool queued; + + portENTER_CRITICAL(&ble_port_mutex); + queued = event->queued; + portEXIT_CRITICAL(&ble_port_mutex); + return queued; +} + +static bool IRAM_ATTR +npl_eventq_queued_claim(struct ble_npl_event_freertos *event) +{ + bool already; + + portENTER_CRITICAL(&ble_port_mutex); + already = event->queued; + if (!already) { + event->queued = true; + } + portEXIT_CRITICAL(&ble_port_mutex); + return already; +} + +static void IRAM_ATTR +npl_eventq_lost_event_clear(struct ble_npl_event *ev) +{ + struct ble_npl_event_freertos *lost; + + if (ev == NULL) { + return; + } + + lost = (struct ble_npl_event_freertos *)ev->event; + if (lost == NULL) { + return; + } + + lost->queued = false; +} + struct ble_npl_event * IRAM_ATTR npl_freertos_eventq_get(struct ble_npl_eventq *evq, ble_npl_time_t tmo) { @@ -211,16 +322,63 @@ IRAM_ATTR npl_freertos_eventq_get(struct ble_npl_eventq *evq, ble_npl_time_t tmo if( woken == pdTRUE ) { portYIELD_FROM_ISR(); } - } else { - ret = xQueueReceive(eventq->q, &ev, tmo); - } - BLE_LL_ASSERT(ret == pdPASS || ret == errQUEUE_EMPTY); + BLE_LL_ASSERT(ret == pdPASS || ret == errQUEUE_EMPTY); - if (ev) { - struct ble_npl_event_freertos *event = (struct ble_npl_event_freertos *)ev->event; - if (event) { - event->queued = false; - } + if (ev) { + struct ble_npl_event_freertos *event = (struct ble_npl_event_freertos *)ev->event; + if (event) { + npl_eventq_queued_set_isr(event, false); + } + } + } else if (tmo == 0) { + npl_eventq_lock(); + portENTER_CRITICAL(&ble_port_mutex); + ret = xQueueReceive(eventq->q, &ev, 0); + if (ret == pdPASS && ev != NULL) { + struct ble_npl_event_freertos *event = (struct ble_npl_event_freertos *)ev->event; + if (event) { + event->queued = false; + } + } + portEXIT_CRITICAL(&ble_port_mutex); + npl_eventq_unlock(); + } else { + TickType_t deadline = 0; + TickType_t remaining; + + if (tmo != portMAX_DELAY) { + deadline = xTaskGetTickCount() + tmo; + } + + for (;;) { + if (tmo == portMAX_DELAY) { + ret = xQueuePeek(eventq->q, &ev, portMAX_DELAY); + } else { + remaining = deadline - xTaskGetTickCount(); + if (remaining > tmo) { + return NULL; + } + ret = xQueuePeek(eventq->q, &ev, remaining); + } + if (ret != pdPASS) { + return NULL; + } + + npl_eventq_lock(); + portENTER_CRITICAL(&ble_port_mutex); + ret = xQueueReceive(eventq->q, &ev, 0); + if (ret == pdPASS && ev != NULL) { + struct ble_npl_event_freertos *event = (struct ble_npl_event_freertos *)ev->event; + if (event) { + event->queued = false; + } + portEXIT_CRITICAL(&ble_port_mutex); + npl_eventq_unlock(); + break; + } + portEXIT_CRITICAL(&ble_port_mutex); + npl_eventq_unlock(); + } } return ev; @@ -234,22 +392,35 @@ IRAM_ATTR npl_freertos_eventq_put(struct ble_npl_eventq *evq, struct ble_npl_eve struct ble_npl_eventq_freertos *eventq = (struct ble_npl_eventq_freertos *)evq->eventq; struct ble_npl_event_freertos *event = (struct ble_npl_event_freertos *)ev->event; - if (event->queued) { - return; - } - - event->queued = true; - if (in_isr()) { + if (npl_eventq_queued_claim_isr(event)) { + return; + } + ret = xQueueSendToBackFromISR(eventq->q, &ev, &woken); + if (ret != pdPASS) { + npl_eventq_queued_set_isr(event, false); + return; + } if( woken == pdTRUE ) { portYIELD_FROM_ISR(); } + return; } else { - ret = xQueueSendToBack(eventq->q, &ev, portMAX_DELAY); - } + npl_eventq_lock(); - BLE_LL_ASSERT(ret == pdPASS); + if (npl_eventq_queued_claim(event)) { + npl_eventq_unlock(); + return; + } + + ret = xQueueSendToBack(eventq->q, &ev, 0); + if (ret != pdPASS) { + ESP_LOGW("NimBLE", "eventq put: queue full, event dropped"); + npl_eventq_queued_set_task(event, false); + } + npl_eventq_unlock(); + } } void @@ -260,22 +431,35 @@ IRAM_ATTR npl_freertos_eventq_put_to_front(struct ble_npl_eventq *evq, struct bl struct ble_npl_eventq_freertos *eventq = (struct ble_npl_eventq_freertos *)evq->eventq; struct ble_npl_event_freertos *event = (struct ble_npl_event_freertos *)ev->event; - if (event->queued) { - return; - } - - event->queued = true; - if (in_isr()) { + if (npl_eventq_queued_claim_isr(event)) { + return; + } + ret = xQueueSendToFrontFromISR(eventq->q, &ev, &woken); + if (ret != pdPASS) { + npl_eventq_queued_set_isr(event, false); + return; + } if( woken == pdTRUE ) { portYIELD_FROM_ISR(); } + return; } else { - ret = xQueueSendToFront(eventq->q, &ev, portMAX_DELAY); - } + npl_eventq_lock(); - BLE_LL_ASSERT(ret == pdPASS); + if (npl_eventq_queued_claim(event)) { + npl_eventq_unlock(); + return; + } + + ret = xQueueSendToFront(eventq->q, &ev, 0); + if (ret != pdPASS) { + ESP_LOGW("NimBLE", "eventq put_to_front: queue full, event dropped"); + npl_eventq_queued_set_task(event, false); + } + npl_eventq_unlock(); + } } void @@ -286,14 +470,11 @@ IRAM_ATTR npl_freertos_eventq_remove(struct ble_npl_eventq *evq, BaseType_t ret; int i; int count; + bool removed; BaseType_t woken, woken2; struct ble_npl_eventq_freertos *eventq = (struct ble_npl_eventq_freertos *)evq->eventq; struct ble_npl_event_freertos *event = (struct ble_npl_event_freertos *)ev->event; - if (!event->queued) { - return; - } - /* * XXX We cannot extract element from inside FreeRTOS queue so as a quick * workaround we'll just remove all elements and add them back except the @@ -302,46 +483,77 @@ IRAM_ATTR npl_freertos_eventq_remove(struct ble_npl_eventq *evq, */ if (in_isr()) { + if (!npl_eventq_queued_get_isr(event)) { + return; + } + + removed = false; woken = pdFALSE; + portENTER_CRITICAL_ISR(&ble_port_mutex); count = uxQueueMessagesWaitingFromISR(eventq->q); for (i = 0; i < count; i++) { ret = xQueueReceiveFromISR(eventq->q, &tmp_ev, &woken2); - BLE_LL_ASSERT(ret == pdPASS); + if (ret != pdPASS) { + break; + } woken |= woken2; if (tmp_ev == ev) { + removed = true; continue; } ret = xQueueSendToBackFromISR(eventq->q, &tmp_ev, &woken2); - BLE_LL_ASSERT(ret == pdPASS); + if (ret != pdPASS) { + npl_eventq_lost_event_clear(tmp_ev); + break; + } woken |= woken2; } + if (removed) { + event->queued = false; + } + portEXIT_CRITICAL_ISR(&ble_port_mutex); if( woken == pdTRUE ) { portYIELD_FROM_ISR(); } } else { - portENTER_CRITICAL(&ble_port_mutex); + removed = false; + npl_eventq_lock(); + if (!npl_eventq_queued_get_task(event)) { + npl_eventq_unlock(); + return; + } + + portENTER_CRITICAL(&ble_port_mutex); count = uxQueueMessagesWaiting(eventq->q); for (i = 0; i < count; i++) { ret = xQueueReceive(eventq->q, &tmp_ev, 0); - BLE_LL_ASSERT(ret == pdPASS); + if (ret != pdPASS) { + break; + } if (tmp_ev == ev) { + removed = true; continue; } ret = xQueueSendToBack(eventq->q, &tmp_ev, 0); - BLE_LL_ASSERT(ret == pdPASS); + if (ret != pdPASS) { + npl_eventq_lost_event_clear(tmp_ev); + break; + } + } + if (removed) { + event->queued = 0; } - portEXIT_CRITICAL(&ble_port_mutex); - } - event->queued = 0; + npl_eventq_unlock(); + } } ble_npl_error_t @@ -1116,6 +1328,9 @@ int npl_freertos_set_controller_npl_info(ble_npl_count_info_t *ctrl_npl_info) int npl_freertos_mempool_init(void) { int rc = -1; + + npl_eventq_sync_init(); + uint16_t ble_total_evt_count = 0; uint16_t ble_total_co_count = 0; uint16_t ble_total_evtq_count = 0; @@ -1205,6 +1420,11 @@ int npl_freertos_mempool_init(void) return 0; _error: + if (npl_eventq_sync) { + vSemaphoreDelete(npl_eventq_sync); + npl_eventq_sync = NULL; + } + if (ble_freertos_ev_buf) { bt_osi_mem_free_internal(ble_freertos_ev_buf); ble_freertos_ev_buf = NULL; @@ -1234,6 +1454,11 @@ _error: void npl_freertos_mempool_deinit(void) { + if (npl_eventq_sync) { + vSemaphoreDelete(npl_eventq_sync); + npl_eventq_sync = NULL; + } + if (ble_freertos_ev_buf) { bt_osi_mem_free_internal(ble_freertos_ev_buf); ble_freertos_ev_buf = NULL; diff --git a/components/driver/test_apps/legacy_twai/pytest_twai.py b/components/driver/test_apps/legacy_twai/pytest_twai.py index f8d4b70a7aa..b8103449311 100644 --- a/components/driver/test_apps/legacy_twai/pytest_twai.py +++ b/components/driver/test_apps/legacy_twai/pytest_twai.py @@ -1,6 +1,7 @@ -# SPDX-FileCopyrightText: 2021-2025 Espressif Systems (Shanghai) CO LTD +# SPDX-FileCopyrightText: 2021-2026 Espressif Systems (Shanghai) CO LTD # SPDX-License-Identifier: CC0-1.0 import logging +import os import subprocess import time @@ -14,6 +15,9 @@ from pytest_embedded_idf.utils import idf_parametrize # Loop Back Tests # --------------------------------------------------------------------------- +can_env = os.getenv('CAN_PORT', 'can0') +print(f'CAN_PORT={can_env}') + @pytest.mark.generic @pytest.mark.parametrize( @@ -54,28 +58,26 @@ def esp_reset_and_wait_ready(dut: Dut) -> None: @pytest.fixture(name='socket_can') def fixture_create_socket_can() -> Bus: # Set up the socket CAN with the bitrate - start_command = 'sudo -n ip link set can0 up type can bitrate 250000 restart-ms 100' - stop_command = 'sudo -n ip link set can0 down' - status_command = 'sudo -n ip -details link show can0' + start_command = f'sudo -n ip link set {can_env} up type can bitrate 250000' + stop_command = f'sudo -n ip link set {can_env} down' + status_command = f'sudo -n ip -details link show {can_env}' try: - result = subprocess.run(status_command, shell=True, capture_output=True, text=True) + result = subprocess.run(status_command, shell=True, capture_output=True, text=True, timeout=2) if result.returncode != 0: - raise Exception('CAN interface "can0" not found') + raise Exception(f'CAN interface "{can_env}" not found') if 'UP' in result.stdout: # Close the bus anyway if it is already up - subprocess.run(stop_command, shell=True, capture_output=True, text=True) - subprocess.run(start_command, shell=True, capture_output=True, text=True) + subprocess.run(stop_command, shell=True, capture_output=True, text=True, timeout=2) + subprocess.run(start_command, shell=True, capture_output=True, text=True, timeout=2) time.sleep(0.5) - bus = Bus(interface='socketcan', channel='can0', bitrate=250000) + bus = Bus(interface='socketcan', channel=f'{can_env}', bitrate=250000) yield bus # test invoked here bus.shutdown() - except Exception as e: - pytest.skip(f'Open usb-can bus Error: {str(e)}') finally: - subprocess.run(stop_command, shell=True, capture_output=True, text=True) + subprocess.run(stop_command, shell=True, capture_output=True, text=True, timeout=2) # --------------------------------------------------------------------------- @@ -83,7 +85,7 @@ def fixture_create_socket_can() -> Bus: # --------------------------------------------------------------------------- -@pytest.mark.twai_std +@pytest.mark.twai_adapter @pytest.mark.parametrize( 'config', [ @@ -95,25 +97,27 @@ def fixture_create_socket_can() -> Bus: 'target', ['esp32', 'esp32c3', 'esp32c6', 'esp32h2', 'esp32s2', 'esp32s3', 'esp32p4'], indirect=['target'] ) def test_legacy_twai_listen_only(dut: Dut, socket_can: Bus) -> None: - esp_reset_and_wait_ready(dut) + try: + esp_reset_and_wait_ready(dut) - # TEST_CASE("twai_listen_only", "[twai]") - dut.write('"twai_listen_only"') + # TEST_CASE("twai_listen_only", "[twai]") + dut.write('"twai_listen_only"') - # wait the DUT to start listening - time.sleep(0.1) + # wait the DUT to start listening + time.sleep(0.1) - message = Message( - arbitration_id=0x123, - is_extended_id=False, - data=[0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88], - ) - socket_can.send(message, timeout=0.2) - dut.expect_unity_test_output() - esp_enter_flash_mode(dut) + message = Message( + arbitration_id=0x123, + is_extended_id=False, + data=[0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88], + ) + socket_can.send(message, timeout=0.2) + dut.expect_unity_test_output() + finally: + esp_enter_flash_mode(dut) -@pytest.mark.twai_std +@pytest.mark.twai_adapter @pytest.mark.parametrize( 'config', [ @@ -125,26 +129,31 @@ def test_legacy_twai_listen_only(dut: Dut, socket_can: Bus) -> None: 'target', ['esp32', 'esp32c3', 'esp32c6', 'esp32h2', 'esp32s2', 'esp32s3', 'esp32p4'], indirect=['target'] ) def test_legacy_twai_remote_request(dut: Dut, socket_can: Bus) -> None: - esp_reset_and_wait_ready(dut) + try: + esp_reset_and_wait_ready(dut) - # TEST_CASE("twai_remote_request", "[twai]") - dut.write('"twai_remote_request"') + # TEST_CASE("twai_remote_request", "[twai]") + dut.write('"twai_remote_request"') - while True: - req = socket_can.recv(timeout=0.2) - # wait for the remote request frame - if req is not None and req.is_remote_frame: - break + deadline = time.time() + 2.0 + req = None + while time.time() < deadline: + req = socket_can.recv(timeout=0.2) + if req is not None and req.is_remote_frame: + break - logging.info(f'Received message: {req}') + if req is None: + raise Exception('Remote frame not received') + logging.info(f'Received message: {req}') - reply = Message( - arbitration_id=req.arbitration_id, - is_extended_id=req.is_extended_id, - data=[0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70, 0x80], - ) - socket_can.send(reply, timeout=0.2) - print('send', reply) + reply = Message( + arbitration_id=req.arbitration_id, + is_extended_id=req.is_extended_id, + data=[0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70, 0x80], + ) + socket_can.send(reply, timeout=0.2) + print('send', reply) - dut.expect_unity_test_output() - esp_enter_flash_mode(dut) + dut.expect_unity_test_output() + finally: + esp_enter_flash_mode(dut) diff --git a/components/driver/twai/twai.c b/components/driver/twai/twai.c index 4243f0a23f7..05002726ed4 100644 --- a/components/driver/twai/twai.c +++ b/components/driver/twai/twai.c @@ -243,8 +243,8 @@ static void twai_intr_handler_main(void *arg) //Note: This event will never occur if there is a periph reset event twai_handle_rx_buffer_frames(p_twai_obj, &task_woken, &alert_req); } - if (events & TWAI_HAL_EVENT_TX_BUFF_FREE) { - twai_handle_tx_buffer_frame(p_twai_obj, (events & TWAI_HAL_EVENT_TX_SUCCESS), &task_woken, &alert_req); + if (events & TWAI_HAL_EVENT_TX0_DONE) { + twai_handle_tx_buffer_frame(p_twai_obj, (events & TWAI_HAL_EVENT_TX0_SUCCESS), &task_woken, &alert_req); } //Handle events that only require alerting (i.e. no handler) diff --git a/components/efuse/esp32c61/include/esp_efuse_chip.h b/components/efuse/esp32c61/include/esp_efuse_chip.h index 55f71d09c56..91c80fd0c22 100644 --- a/components/efuse/esp32c61/include/esp_efuse_chip.h +++ b/components/efuse/esp32c61/include/esp_efuse_chip.h @@ -64,10 +64,6 @@ typedef enum { ESP_EFUSE_KEY_PURPOSE_USER = 0, /**< User purposes (software-only use) */ ESP_EFUSE_KEY_PURPOSE_ECDSA_KEY = 1, /**< ECDSA private key (Expected in little endian order)*/ ESP_EFUSE_KEY_PURPOSE_XTS_AES_128_KEY = 4, /**< XTS_AES_128_KEY (flash/PSRAM encryption) */ - ESP_EFUSE_KEY_PURPOSE_HMAC_DOWN_ALL = 5, /**< HMAC Downstream mode */ - ESP_EFUSE_KEY_PURPOSE_HMAC_DOWN_JTAG = 6, /**< JTAG soft enable key (uses HMAC Downstream mode) */ - ESP_EFUSE_KEY_PURPOSE_HMAC_DOWN_DIGITAL_SIGNATURE = 7, /**< Digital Signature peripheral key (uses HMAC Downstream mode) */ - ESP_EFUSE_KEY_PURPOSE_HMAC_UP = 8, /**< HMAC Upstream mode */ ESP_EFUSE_KEY_PURPOSE_SECURE_BOOT_DIGEST0 = 9, /**< SECURE_BOOT_DIGEST0 (Secure Boot key digest) */ ESP_EFUSE_KEY_PURPOSE_SECURE_BOOT_DIGEST1 = 10, /**< SECURE_BOOT_DIGEST1 (Secure Boot key digest) */ ESP_EFUSE_KEY_PURPOSE_SECURE_BOOT_DIGEST2 = 11, /**< SECURE_BOOT_DIGEST2 (Secure Boot key digest) */ diff --git a/components/efuse/src/efuse_controller/keys/with_key_purposes/esp_efuse_api_key.c b/components/efuse/src/efuse_controller/keys/with_key_purposes/esp_efuse_api_key.c index 44cce723215..27b8f8797a2 100644 --- a/components/efuse/src/efuse_controller/keys/with_key_purposes/esp_efuse_api_key.c +++ b/components/efuse/src/efuse_controller/keys/with_key_purposes/esp_efuse_api_key.c @@ -300,35 +300,38 @@ esp_err_t esp_efuse_write_key(esp_efuse_block_t block, esp_efuse_purpose_t purpo } #endif // SOC_EFUSE_BLOCK9_KEY_PURPOSE_QUIRK - if (purpose == ESP_EFUSE_KEY_PURPOSE_XTS_AES_128_KEY || + if (purpose == ESP_EFUSE_KEY_PURPOSE_XTS_AES_128_KEY #ifdef SOC_EFUSE_XTS_AES_KEY_256 - purpose == ESP_EFUSE_KEY_PURPOSE_XTS_AES_256_KEY_1 || - purpose == ESP_EFUSE_KEY_PURPOSE_XTS_AES_256_KEY_2 || + || purpose == ESP_EFUSE_KEY_PURPOSE_XTS_AES_256_KEY_1 + || purpose == ESP_EFUSE_KEY_PURPOSE_XTS_AES_256_KEY_2 #endif //#ifdef SOC_EFUSE_XTS_AES_KEY_256 #if SOC_EFUSE_ECDSA_KEY - purpose == ESP_EFUSE_KEY_PURPOSE_ECDSA_KEY || + || purpose == ESP_EFUSE_KEY_PURPOSE_ECDSA_KEY #endif #if (!defined(CONFIG_IDF_TARGET_ESP32P4) && SOC_EFUSE_ECDSA_KEY_P192) || EFUSE_LL_HAS_ECDSA_KEY_P192 - purpose == ESP_EFUSE_KEY_PURPOSE_ECDSA_KEY_P192 || + || purpose == ESP_EFUSE_KEY_PURPOSE_ECDSA_KEY_P192 #endif #if (!defined(CONFIG_IDF_TARGET_ESP32P4) && SOC_EFUSE_ECDSA_KEY_P384) || EFUSE_LL_HAS_ECDSA_KEY_P384 - purpose == ESP_EFUSE_KEY_PURPOSE_ECDSA_KEY_P384_L || - purpose == ESP_EFUSE_KEY_PURPOSE_ECDSA_KEY_P384_H || + || purpose == ESP_EFUSE_KEY_PURPOSE_ECDSA_KEY_P384_L + || purpose == ESP_EFUSE_KEY_PURPOSE_ECDSA_KEY_P384_H #endif #if SOC_PSRAM_ENCRYPTION_XTS_AES_128 || EFUSE_LL_HAS_PSRAM_ENCRYPTION_XTS_AES_128 - purpose == ESP_EFUSE_KEY_PURPOSE_XTS_AES_128_PSRAM_KEY || + || purpose == ESP_EFUSE_KEY_PURPOSE_XTS_AES_128_PSRAM_KEY #endif #if SOC_PSRAM_ENCRYPTION_XTS_AES_256 || EFUSE_LL_HAS_PSRAM_ENCRYPTION_XTS_AES_256 - purpose == ESP_EFUSE_KEY_PURPOSE_XTS_AES_256_PSRAM_KEY_1 || - purpose == ESP_EFUSE_KEY_PURPOSE_XTS_AES_256_PSRAM_KEY_2 || + || purpose == ESP_EFUSE_KEY_PURPOSE_XTS_AES_256_PSRAM_KEY_1 + || purpose == ESP_EFUSE_KEY_PURPOSE_XTS_AES_256_PSRAM_KEY_2 #endif #if SOC_KEY_MANAGER_SUPPORTED - purpose == ESP_EFUSE_KEY_PURPOSE_KM_INIT_KEY || + || purpose == ESP_EFUSE_KEY_PURPOSE_KM_INIT_KEY #endif - purpose == ESP_EFUSE_KEY_PURPOSE_HMAC_DOWN_ALL || - purpose == ESP_EFUSE_KEY_PURPOSE_HMAC_DOWN_JTAG || - purpose == ESP_EFUSE_KEY_PURPOSE_HMAC_DOWN_DIGITAL_SIGNATURE || - purpose == ESP_EFUSE_KEY_PURPOSE_HMAC_UP) { +#if SOC_HMAC_SUPPORTED + || purpose == ESP_EFUSE_KEY_PURPOSE_HMAC_DOWN_ALL + || purpose == ESP_EFUSE_KEY_PURPOSE_HMAC_DOWN_JTAG + || purpose == ESP_EFUSE_KEY_PURPOSE_HMAC_DOWN_DIGITAL_SIGNATURE + || purpose == ESP_EFUSE_KEY_PURPOSE_HMAC_UP +#endif + ) { ESP_EFUSE_CHK(esp_efuse_set_key_dis_read(block)); } #if SOC_EFUSE_ECDSA_USE_HARDWARE_K diff --git a/components/efuse/test_apps/main/with_key_purposes/test_efuse_keys.c b/components/efuse/test_apps/main/with_key_purposes/test_efuse_keys.c index 2724bf94339..d2229512dc1 100644 --- a/components/efuse/test_apps/main/with_key_purposes/test_efuse_keys.c +++ b/components/efuse/test_apps/main/with_key_purposes/test_efuse_keys.c @@ -86,35 +86,38 @@ static esp_err_t s_check_key(esp_efuse_block_t num_key, void* wr_key) #endif // not CONFIG_EFUSE_FPGA_TEST TEST_ASSERT_TRUE(esp_efuse_get_key_dis_write(num_key)); - if (purpose == ESP_EFUSE_KEY_PURPOSE_XTS_AES_128_KEY || + if (purpose == ESP_EFUSE_KEY_PURPOSE_XTS_AES_128_KEY #ifdef SOC_EFUSE_XTS_AES_KEY_256 - purpose == ESP_EFUSE_KEY_PURPOSE_XTS_AES_256_KEY_1 || - purpose == ESP_EFUSE_KEY_PURPOSE_XTS_AES_256_KEY_2 || + || purpose == ESP_EFUSE_KEY_PURPOSE_XTS_AES_256_KEY_1 + || purpose == ESP_EFUSE_KEY_PURPOSE_XTS_AES_256_KEY_2 #endif #if SOC_EFUSE_ECDSA_KEY - purpose == ESP_EFUSE_KEY_PURPOSE_ECDSA_KEY || + || purpose == ESP_EFUSE_KEY_PURPOSE_ECDSA_KEY #endif #if (!defined(CONFIG_IDF_TARGET_ESP32P4) && SOC_EFUSE_ECDSA_KEY_P192) || EFUSE_LL_HAS_ECDSA_KEY_P192 - purpose == ESP_EFUSE_KEY_PURPOSE_ECDSA_KEY_P192 || + || purpose == ESP_EFUSE_KEY_PURPOSE_ECDSA_KEY_P192 #endif #if (!defined(CONFIG_IDF_TARGET_ESP32P4) && SOC_EFUSE_ECDSA_KEY_P384) || EFUSE_LL_HAS_ECDSA_KEY_P384 - purpose == ESP_EFUSE_KEY_PURPOSE_ECDSA_KEY_P384_L || - purpose == ESP_EFUSE_KEY_PURPOSE_ECDSA_KEY_P384_H || + || purpose == ESP_EFUSE_KEY_PURPOSE_ECDSA_KEY_P384_L + || purpose == ESP_EFUSE_KEY_PURPOSE_ECDSA_KEY_P384_H #endif #if SOC_PSRAM_ENCRYPTION_XTS_AES_128 || EFUSE_LL_HAS_PSRAM_ENCRYPTION_XTS_AES_128 - purpose == ESP_EFUSE_KEY_PURPOSE_XTS_AES_128_PSRAM_KEY || + || purpose == ESP_EFUSE_KEY_PURPOSE_XTS_AES_128_PSRAM_KEY #endif #if SOC_PSRAM_ENCRYPTION_XTS_AES_256 || EFUSE_LL_HAS_PSRAM_ENCRYPTION_XTS_AES_256 - purpose == ESP_EFUSE_KEY_PURPOSE_XTS_AES_256_PSRAM_KEY_1 || - purpose == ESP_EFUSE_KEY_PURPOSE_XTS_AES_256_PSRAM_KEY_2 || + || purpose == ESP_EFUSE_KEY_PURPOSE_XTS_AES_256_PSRAM_KEY_1 + || purpose == ESP_EFUSE_KEY_PURPOSE_XTS_AES_256_PSRAM_KEY_2 +#endif +#if SOC_HMAC_SUPPORTED + || purpose == ESP_EFUSE_KEY_PURPOSE_HMAC_DOWN_ALL + || purpose == ESP_EFUSE_KEY_PURPOSE_HMAC_DOWN_JTAG + || purpose == ESP_EFUSE_KEY_PURPOSE_HMAC_DOWN_DIGITAL_SIGNATURE + || purpose == ESP_EFUSE_KEY_PURPOSE_HMAC_UP #endif - purpose == ESP_EFUSE_KEY_PURPOSE_HMAC_DOWN_ALL || - purpose == ESP_EFUSE_KEY_PURPOSE_HMAC_DOWN_JTAG || - purpose == ESP_EFUSE_KEY_PURPOSE_HMAC_DOWN_DIGITAL_SIGNATURE || #if SOC_KEY_MANAGER_SUPPORTED - purpose == ESP_EFUSE_KEY_PURPOSE_KM_INIT_KEY || + || purpose == ESP_EFUSE_KEY_PURPOSE_KM_INIT_KEY #endif - purpose == ESP_EFUSE_KEY_PURPOSE_HMAC_UP) { + ) { TEST_ASSERT_TRUE(esp_efuse_get_key_dis_read(num_key)); #if CONFIG_EFUSE_FPGA_TEST && !CONFIG_EFUSE_VIRTUAL TEST_ASSERT_EACH_EQUAL_HEX8(0, rd_key, sizeof(rd_key)); @@ -236,7 +239,11 @@ TEST_CASE("Test 1 esp_efuse_write_key for FPGA", "[efuse]") ESP_EFUSE_KEY_PURPOSE_XTS_AES_128_KEY, #endif ESP_EFUSE_KEY_PURPOSE_XTS_AES_128_KEY, +#if SOC_HMAC_SUPPORTED ESP_EFUSE_KEY_PURPOSE_HMAC_DOWN_ALL, +#else + ESP_EFUSE_KEY_PURPOSE_XTS_AES_128_KEY, +#endif }; int max_keys = EFUSE_BLK_KEY_MAX - EFUSE_BLK_KEY0; @@ -264,9 +271,15 @@ TEST_CASE("Test 2 esp_efuse_write_key for FPGA", "[efuse]") TEST_ASSERT_EQUAL_MESSAGE(EFUSE_BLK_KEY_MAX - EFUSE_BLK_KEY0, esp_efuse_count_unused_key_blocks(), "Efuses should be in initial state"); esp_efuse_purpose_t purpose [] = { +#if SOC_HMAC_SUPPORTED ESP_EFUSE_KEY_PURPOSE_HMAC_DOWN_JTAG, ESP_EFUSE_KEY_PURPOSE_HMAC_DOWN_DIGITAL_SIGNATURE, ESP_EFUSE_KEY_PURPOSE_HMAC_UP, +#else + ESP_EFUSE_KEY_PURPOSE_XTS_AES_128_KEY, + ESP_EFUSE_KEY_PURPOSE_XTS_AES_128_KEY, + ESP_EFUSE_KEY_PURPOSE_XTS_AES_128_KEY, +#endif ESP_EFUSE_KEY_PURPOSE_SECURE_BOOT_DIGEST0, ESP_EFUSE_KEY_PURPOSE_SECURE_BOOT_DIGEST1, ESP_EFUSE_KEY_PURPOSE_SECURE_BOOT_DIGEST2, @@ -312,7 +325,11 @@ TEST_CASE("Test esp_efuse_write_keys", "[efuse]") #else esp_efuse_purpose_t purpose1[BLOCKS_NEEDED1] = { ESP_EFUSE_KEY_PURPOSE_XTS_AES_128_KEY, +#if SOC_HMAC_SUPPORTED ESP_EFUSE_KEY_PURPOSE_HMAC_DOWN_ALL +#else + ESP_EFUSE_KEY_PURPOSE_USER +#endif }; #endif uint8_t keys1[BLOCKS_NEEDED1][32] = {{0xEE}}; diff --git a/components/esp-tls/esp_tls.h b/components/esp-tls/esp_tls.h index 324007126d2..c7802b94185 100644 --- a/components/esp-tls/esp_tls.h +++ b/components/esp-tls/esp_tls.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2017-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2017-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -198,8 +198,14 @@ typedef struct esp_tls_cfg { const char *common_name; /*!< If non-NULL, server certificate CN must match this name. If NULL, server certificate CN must match hostname. */ - bool skip_common_name; /*!< Skip any validation of server certificate CN field. - This field should be set to false for SNI to function correctly. */ + bool skip_common_name; /*!< When true, esp-tls skips the call to + mbedtls_ssl_set_hostname(). This disables BOTH + server-hostname matching against the certificate + (CN/SAN) and Server Name Indication (SNI), not just + the legacy CN field. Only set on loopback / debug + clients that can tolerate the loss of hostname + authentication. Must be false for SNI to function + correctly. */ tls_keep_alive_cfg_t *keep_alive_cfg; /*!< Enable TCP keep-alive timeout for SSL connection */ diff --git a/components/esp-tls/esp_tls_mbedtls.c b/components/esp-tls/esp_tls_mbedtls.c index 1afdcb13bbb..901d4ba713b 100644 --- a/components/esp-tls/esp_tls_mbedtls.c +++ b/components/esp-tls/esp_tls_mbedtls.c @@ -495,36 +495,17 @@ void esp_mbedtls_cleanup(esp_tls_t *tls) /* For opaque keys (DS peripheral, hardware ECDSA), mbedtls_pk_free() does * not destroy the PSA key — ownership is external. Destroy it manually - * before calling mbedtls_pk_free(). */ -#ifdef CONFIG_ESP_TLS_USE_DS_PERIPHERAL - if (mbedtls_pk_get_type(&tls->clientkey) == MBEDTLS_PK_RSASSA_PSS) { + * before calling mbedtls_pk_free(). mbedtls_pk_wrap_psa() sets the pk_info + * to mbedtls_{rsa,ecdsa}_opaque_info, both of which have type + * MBEDTLS_PK_OPAQUE — so a single check covers both DS and ECDSA paths. + * clientkey and serverkey share storage via union, so one branch suffices. */ +#if defined(CONFIG_ESP_TLS_USE_DS_PERIPHERAL) || defined(CONFIG_MBEDTLS_HARDWARE_ECDSA_SIGN) + if (mbedtls_pk_get_type(&tls->clientkey) == MBEDTLS_PK_OPAQUE) { if (tls->clientkey.MBEDTLS_PRIVATE(priv_id) != PSA_KEY_ID_NULL) { psa_destroy_key(tls->clientkey.MBEDTLS_PRIVATE(priv_id)); tls->clientkey.MBEDTLS_PRIVATE(priv_id) = PSA_KEY_ID_NULL; } } - if (mbedtls_pk_get_type(&tls->serverkey) == MBEDTLS_PK_RSASSA_PSS) { - if (tls->serverkey.MBEDTLS_PRIVATE(priv_id) != PSA_KEY_ID_NULL) { - psa_destroy_key(tls->serverkey.MBEDTLS_PRIVATE(priv_id)); - tls->serverkey.MBEDTLS_PRIVATE(priv_id) = PSA_KEY_ID_NULL; - } - } -#endif - -#ifdef CONFIG_MBEDTLS_HARDWARE_ECDSA_SIGN - if (mbedtls_pk_get_type(&tls->clientkey) == MBEDTLS_PK_ECDSA) { - ESP_LOGD(TAG, "Cleaning up client key"); - if (tls->clientkey.MBEDTLS_PRIVATE(priv_id) != PSA_KEY_ID_NULL) { - psa_destroy_key(tls->clientkey.MBEDTLS_PRIVATE(priv_id)); - tls->clientkey.MBEDTLS_PRIVATE(priv_id) = PSA_KEY_ID_NULL; - } - } - if (mbedtls_pk_get_type(&tls->serverkey) == MBEDTLS_PK_ECDSA) { - if (tls->serverkey.MBEDTLS_PRIVATE(priv_id) != PSA_KEY_ID_NULL) { - psa_destroy_key(tls->serverkey.MBEDTLS_PRIVATE(priv_id)); - tls->serverkey.MBEDTLS_PRIVATE(priv_id) = PSA_KEY_ID_NULL; - } - } #endif mbedtls_pk_free(&tls->clientkey); @@ -624,6 +605,7 @@ static esp_err_t set_pki_context(esp_tls_t *tls, const esp_tls_pki_t *pki) // Import opaque key reference psa_status_t status = psa_import_key(&key_attr, (uint8_t*) &opaque_key, sizeof(opaque_key), &priv_key_id); + psa_reset_key_attributes(&key_attr); if (status != PSA_SUCCESS) { ESP_LOGE(TAG, "Failed to import opaque key reference"); return ESP_ERR_MBEDTLS_PK_PARSE_KEY_FAILED; @@ -632,10 +614,9 @@ static esp_err_t set_pki_context(esp_tls_t *tls, const esp_tls_pki_t *pki) ret = mbedtls_pk_wrap_psa(pki->pk_key, priv_key_id); if (ret != 0) { ESP_LOGE(TAG, "Failed to wrap opaque key reference"); + psa_destroy_key(priv_key_id); return ret; } - - psa_reset_key_attributes(&key_attr); } else #endif if (pki->privkey_pem_buf != NULL) { @@ -931,6 +912,9 @@ esp_err_t set_client_config(const char *hostname, size_t hostlen, esp_tls_cfg_t } free(use_host); } else { + ESP_LOGW(TAG, "skip_common_name=true: hostname matching and SNI disabled. " + "This disables ALL server-name authentication (CN/SAN/SNI), not just CN. " + "Only intended for loopback / debug clients."); mbedtls_ssl_set_hostname(&tls->ssl, NULL); } @@ -1003,10 +987,6 @@ esp_err_t set_client_config(const char *hostname, size_t hostlen, esp_tls_cfg_t ESP_INT_EVENT_TRACKER_CAPTURE(tls->error_handle, ESP_TLS_ERR_TYPE_MBEDTLS, -ret); return ESP_ERR_MBEDTLS_SSL_CONF_PSK_FAILED; } -#endif -#ifdef CONFIG_ESP_TLS_CLIENT_SESSION_TICKETS - } else if (cfg->client_session != NULL) { - ESP_LOGD(TAG, "Reusing the saved client session"); #endif } else { #ifdef CONFIG_ESP_TLS_SKIP_SERVER_CERT_VERIFY @@ -1385,46 +1365,73 @@ static esp_err_t esp_set_atecc608a_pki_context(esp_tls_t *tls, const void *pki) #endif /* CONFIG_ESP_TLS_USE_SECURE_ELEMENT */ #ifdef CONFIG_ESP_TLS_USE_DS_PERIPHERAL +/* + * tf-psa-crypto 1.1 made mbedtls_pk_wrap_psa() call psa_export_public_key() on + * the imported PSA key. The DS peripheral cannot expose any key material + * (private or public), so the export fails with PK_INVALID_ALG. Copy the + * already-parsed raw public key from the device certificate so wrap_psa()'s + * export step short-circuits (pk.c:145-148, returns early when pub_raw_len>0). + */ +static esp_err_t inject_rsa_pubkey_from_cert(mbedtls_pk_context *dst, const mbedtls_x509_crt *cert) +{ + const mbedtls_pk_context *src = &cert->pk; + size_t src_len = src->MBEDTLS_PRIVATE(pub_raw_len); + if (src_len == 0 || src_len > sizeof(dst->MBEDTLS_PRIVATE(pub_raw))) { + ESP_LOGE(TAG, "Invalid cert pubkey length: %zu", src_len); + return ESP_ERR_INVALID_STATE; + } + memcpy(dst->MBEDTLS_PRIVATE(pub_raw), src->MBEDTLS_PRIVATE(pub_raw), src_len); + dst->MBEDTLS_PRIVATE(pub_raw_len) = src_len; + dst->MBEDTLS_PRIVATE(bits) = src->MBEDTLS_PRIVATE(bits); + dst->MBEDTLS_PRIVATE(psa_type) = PSA_KEY_TYPE_RSA_PUBLIC_KEY; + return ESP_OK; +} + +static psa_status_t import_ds_key(const esp_ds_data_ctx_t *ds_data, psa_key_id_t *out_key_id) +{ + esp_rsa_ds_opaque_key_t rsa_ds_opaque_key = {0}; + rsa_ds_opaque_key.ds_data_ctx = (esp_ds_data_ctx_t *)ds_data; + + psa_key_attributes_t attrs = PSA_KEY_ATTRIBUTES_INIT; + psa_set_key_type(&attrs, PSA_KEY_TYPE_RSA_KEY_PAIR); + psa_set_key_bits(&attrs, ds_data->rsa_length_bits); + psa_set_key_usage_flags(&attrs, PSA_KEY_USAGE_SIGN_HASH); + psa_set_key_algorithm(&attrs, PSA_ALG_RSA_PKCS1V15_SIGN(PSA_ALG_ANY_HASH)); +#ifdef CONFIG_MBEDTLS_SSL_PROTO_TLS1_3 + psa_set_key_enrollment_algorithm(&attrs, PSA_ALG_RSA_PSS(PSA_ALG_ANY_HASH)); +#endif + psa_set_key_lifetime(&attrs, PSA_KEY_LIFETIME_ESP_RSA_DS_VOLATILE); + + psa_status_t status = psa_import_key(&attrs, + (const uint8_t *)&rsa_ds_opaque_key, + sizeof(rsa_ds_opaque_key), + out_key_id); + psa_reset_key_attributes(&attrs); + return status; +} + static esp_err_t esp_mbedtls_init_pk_ctx_for_ds(const void *pki) { - esp_ds_data_ctx_t *ds_data = ((const esp_tls_pki_t*)pki)->esp_ds_data; - if (ds_data == NULL) { - ESP_LOGE(TAG, "DS data context is NULL"); - return ESP_ERR_INVALID_ARG; - } - - esp_tls_pki_t *pki_l = (esp_tls_pki_t *) pki; - if (pki_l->pk_key == NULL) { - ESP_LOGE(TAG, "PK key context is NULL"); + const esp_tls_pki_t *pki_l = (const esp_tls_pki_t *)pki; + if (pki_l->esp_ds_data == NULL || pki_l->pk_key == NULL || pki_l->public_cert == NULL) { + ESP_LOGE(TAG, "DS pki context missing required fields"); return ESP_ERR_INVALID_ARG; } psa_key_id_t ds_key_id = 0; - psa_status_t status = PSA_ERROR_GENERIC_ERROR; - - esp_rsa_ds_opaque_key_t rsa_ds_opaque_key = {0}; - rsa_ds_opaque_key.ds_data_ctx = ds_data; - - psa_key_attributes_t ds_key_attributes = PSA_KEY_ATTRIBUTES_INIT; - psa_algorithm_t alg = PSA_ALG_RSA_PKCS1V15_SIGN(PSA_ALG_ANY_HASH); -#ifdef CONFIG_MBEDTLS_SSL_PROTO_TLS1_3 - psa_set_key_enrollment_algorithm(&ds_key_attributes, PSA_ALG_RSA_PSS(PSA_ALG_ANY_HASH)); -#endif /* CONFIG_MBEDTLS_SSL_PROTO_TLS1_3 */ - - psa_set_key_type(&ds_key_attributes, PSA_KEY_TYPE_RSA_KEY_PAIR); - psa_set_key_bits(&ds_key_attributes, rsa_ds_opaque_key.ds_data_ctx->rsa_length_bits); - psa_set_key_usage_flags(&ds_key_attributes, PSA_KEY_USAGE_SIGN_HASH); - psa_set_key_algorithm(&ds_key_attributes, alg); - psa_set_key_lifetime(&ds_key_attributes, PSA_KEY_LIFETIME_ESP_RSA_DS_VOLATILE); - status = psa_import_key(&ds_key_attributes, - (const uint8_t *)&rsa_ds_opaque_key, - sizeof(rsa_ds_opaque_key), - &ds_key_id); - psa_reset_key_attributes(&ds_key_attributes); + psa_status_t status = import_ds_key(pki_l->esp_ds_data, &ds_key_id); if (status != PSA_SUCCESS) { ESP_LOGE(TAG, "Failed to import DS key to PSA, status = %d", status); return ESP_ERR_INVALID_STATE; } + + /* Pre-populate pub_raw so wrap_psa() does not attempt to export it. */ + esp_err_t esp_ret = inject_rsa_pubkey_from_cert(pki_l->pk_key, pki_l->public_cert); + if (esp_ret != ESP_OK) { + psa_destroy_key(ds_key_id); + return esp_ret; + } + int ret = mbedtls_pk_wrap_psa(pki_l->pk_key, ds_key_id); if (ret != 0) { ESP_LOGE(TAG, "mbedtls_pk_wrap_psa failed with -0x%04X", -ret); diff --git a/components/esp_adc/test_apps/adc/main/test_common_adc.h b/components/esp_adc/test_apps/adc/main/test_common_adc.h index f8f29eeee15..f544de49a4b 100644 --- a/components/esp_adc/test_apps/adc/main/test_common_adc.h +++ b/components/esp_adc/test_apps/adc/main/test_common_adc.h @@ -42,7 +42,7 @@ extern "C" { #elif CONFIG_IDF_TARGET_ESP32S2 #define ADC_TEST_LOW_VAL 0 -#define ADC_TEST_LOW_THRESH 35 +#define ADC_TEST_LOW_THRESH 80 #define ADC_TEST_HIGH_VAL 8191 #define ADC_TEST_HIGH_VAL_DMA 4095 @@ -66,7 +66,7 @@ extern "C" { #elif CONFIG_IDF_TARGET_ESP32C2 #define ADC_TEST_LOW_VAL 0 -#define ADC_TEST_LOW_THRESH 15 +#define ADC_TEST_LOW_THRESH 30 #define ADC_TEST_HIGH_VAL 3400 #define ADC_TEST_HIGH_THRESH 200 diff --git a/components/esp_adc/test_apps/adc/pytest_adc.py b/components/esp_adc/test_apps/adc/pytest_adc.py index 0d204a10a2b..0fa79317d92 100644 --- a/components/esp_adc/test_apps/adc/pytest_adc.py +++ b/components/esp_adc/test_apps/adc/pytest_adc.py @@ -1,11 +1,11 @@ -# SPDX-FileCopyrightText: 2021-2025 Espressif Systems (Shanghai) CO LTD +# SPDX-FileCopyrightText: 2021-2026 Espressif Systems (Shanghai) CO LTD # SPDX-License-Identifier: CC0-1.0 import pytest from pytest_embedded import Dut from pytest_embedded_idf.utils import idf_parametrize -@pytest.mark.adc +@pytest.mark.generic @pytest.mark.parametrize('config', ['iram_safe', 'release', 'pm_enable'], indirect=True) @idf_parametrize( 'target', @@ -17,7 +17,7 @@ def test_adc(dut: Dut) -> None: # No PM test, as C2 doesn't support ADC continuous mode -@pytest.mark.adc +@pytest.mark.generic @pytest.mark.xtal_26mhz @pytest.mark.parametrize( 'config, baud', @@ -34,7 +34,7 @@ def test_adc_esp32c2_xtal_26mhz(dut: Dut) -> None: # TODO: IDF-15005 # P4 REV2 adc -# @pytest.mark.adc +# @pytest.mark.generic # @pytest.mark.esp32p4_rev1 # @pytest.mark.parametrize('config', ['esp32p4_rev1'], indirect=True) # @idf_parametrize( diff --git a/components/esp_blockdev/README.md b/components/esp_blockdev/README.md index a869b63f6cb..d90e22d751f 100644 --- a/components/esp_blockdev/README.md +++ b/components/esp_blockdev/README.md @@ -28,7 +28,7 @@ The BDL interface follows the Open-Closed Principle (open for extension, closed The BDL interface structure is described by the following pseudo-code: ``` -struct esp_blockdev_t { +typedef struct esp_blockdev { //DEVICE FLAGS esp_blockdev_flags_t device_flags diff --git a/components/esp_common/include/esp_fault.h b/components/esp_common/include/esp_fault.h index 81c47741aa5..7ddd6ce43e9 100644 --- a/components/esp_common/include/esp_fault.h +++ b/components/esp_common/include/esp_fault.h @@ -1,13 +1,14 @@ /* - * SPDX-FileCopyrightText: 2020-2024 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2020-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ +#pragma once + +#include #include "sdkconfig.h" #include "esp_rom_sys.h" -#pragma once - #ifdef __cplusplus extern "C" { #endif @@ -19,6 +20,10 @@ extern "C" { * - Expands CONDITION multiple times (condition must have no side effects) * - Compiler is told all registers are invalid before evaluating CONDITION each time, to avoid a fault * causing a misread of a register used in all three evaluations of CONDITION. + * - The result of each evaluation is stored into a volatile variable and re-read before the branch. + * This prevents the compiler from constant-folding CONDITION and deleting the whole check when it + * has already proven the value - e.g. when this macro follows a normal "if (!cond) { ... }" check + * of the same value, which would otherwise silently remove the fault-injection protection. * - If CONDITION is ever false, a system reset is triggered. * * @note Place this macro after a "normal" check of CONDITION that will fail with a normal error @@ -40,13 +45,20 @@ extern "C" { * @param CONDITION A condition which will evaluate true unless an attacker used fault injection to skip or corrupt some other critical system calculation. * */ -#define ESP_FAULT_ASSERT(CONDITION) do { \ - asm volatile ("" ::: "memory"); \ - if(!(CONDITION)) _ESP_FAULT_RESET(); \ - asm volatile ("" ::: "memory"); \ - if(!(CONDITION)) _ESP_FAULT_RESET(); \ - asm volatile ("" ::: "memory"); \ - if(!(CONDITION)) _ESP_FAULT_RESET(); \ +#define ESP_FAULT_ASSERT(CONDITION) do { \ + bool esp_fault_assert_chk; \ + asm volatile ("" ::: "memory"); \ + esp_fault_assert_chk = (CONDITION); \ + asm volatile ("" : "+r"(esp_fault_assert_chk)); \ + if(!esp_fault_assert_chk) _ESP_FAULT_RESET(); \ + asm volatile ("" ::: "memory"); \ + esp_fault_assert_chk = (CONDITION); \ + asm volatile ("" : "+r"(esp_fault_assert_chk)); \ + if(!esp_fault_assert_chk) _ESP_FAULT_RESET(); \ + asm volatile ("" ::: "memory"); \ + esp_fault_assert_chk = (CONDITION); \ + asm volatile ("" : "+r"(esp_fault_assert_chk)); \ + if(!esp_fault_assert_chk) _ESP_FAULT_RESET(); \ } while(0) #if CONFIG_IDF_TARGET_ARCH_XTENSA diff --git a/components/esp_common/include/esp_idf_version.h b/components/esp_common/include/esp_idf_version.h index 19e86712234..7456adac408 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 0 /** 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/components/esp_common/src/esp_err_to_name.c b/components/esp_common/src/esp_err_to_name.c index 7c084e2e5ac..7503711c87b 100644 --- a/components/esp_common/src/esp_err_to_name.c +++ b/components/esp_common/src/esp_err_to_name.c @@ -670,6 +670,10 @@ static const esp_err_msg_t esp_err_msg_table[] = { # ifdef ESP_ERR_HTTP_INCOMPLETE_DATA ERR_TBL_IT(ESP_ERR_HTTP_INCOMPLETE_DATA), /* 28684 0x700c Incomplete data received, less than Content-Length or last chunk */ +# endif +# ifdef ESP_ERR_HTTP_REDIRECT_DOWNGRADE + ERR_TBL_IT(ESP_ERR_HTTP_REDIRECT_DOWNGRADE), /* 28685 0x700d HTTPS origin redirected to a non-HTTPS + scheme (downgrade blocked) */ # endif // components/esp-tls/esp_tls_errors.h # ifdef ESP_ERR_ESP_TLS_BASE diff --git a/components/esp_driver_dma/CMakeLists.txt b/components/esp_driver_dma/CMakeLists.txt index aa58bebbfdd..3d520c3e9f4 100644 --- a/components/esp_driver_dma/CMakeLists.txt +++ b/components/esp_driver_dma/CMakeLists.txt @@ -35,7 +35,7 @@ if(CONFIG_SOC_DW_GDMA_SUPPORTED) endif() if(CONFIG_SOC_DMA2D_SUPPORTED) - list(APPEND srcs "src/dma2d.c") + list(APPEND srcs "src/dma2d.c" "src/esp_async_color_convert.c" "src/async_color_convert_dma2d.c") endif() idf_component_register(SRCS ${srcs} diff --git a/components/esp_driver_dma/include/esp_async_color_convert.h b/components/esp_driver_dma/include/esp_async_color_convert.h new file mode 100644 index 00000000000..18399c2f15c --- /dev/null +++ b/components/esp_driver_dma/include/esp_async_color_convert.h @@ -0,0 +1,164 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include +#include +#include "esp_err.h" +#include "hal/color_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Opaque handle of async color conversion driver instance + */ +typedef struct async_color_convert_context_t *async_color_convert_handle_t; + +/** + * @brief Async color conversion event data + */ +typedef struct { +} async_color_convert_event_data_t; + +/** + * @brief Async color conversion callback type + * + * @note This callback runs in ISR context. + * + * @param[in] conv_hdl Driver handle that produced this event + * @param[in] edata Event data for the completed request + * @param[in] cb_args User context passed to :cpp:func:`esp_async_color_convert` + * + * @return + * - true: a higher-priority task was woken and a yield is requested + * - false: no yield request + */ +typedef bool (*async_color_convert_isr_cb_t)(async_color_convert_handle_t conv_hdl, + async_color_convert_event_data_t *edata, + void *cb_args); + +/** + * @brief Async color conversion driver configuration + */ +typedef struct { + uint32_t backlog; /*!< Number of in-flight/pending requests. 0 means driver default. */ + size_t dma_burst_size; /*!< DMA burst length in bytes. 0 means driver default. */ + uint32_t intr_priority; /*!< Interrupt priority. 0 means default low/medium priority. */ +} async_color_convert_config_t; + +/** + * @brief Install async color conversion driver with the DMA2D backend + * + * This API allocates internal resources and creates a conversion context. + * + * @param[in] config Driver configuration + * @param[out] ret_hdl Returned driver handle + * + * @return + * - ESP_OK: Driver installed successfully + * - ESP_ERR_INVALID_ARG: Invalid argument + * - ESP_ERR_NO_MEM: Out of memory + * - ESP_ERR_NOT_FOUND: Required DMA2D resource is unavailable + * - others: Error from lower-level DMA2D driver + */ +esp_err_t esp_async_color_convert_install_dma2d(const async_color_convert_config_t *config, + async_color_convert_handle_t *ret_hdl); + +/** + * @brief Uninstall async color conversion driver + * + * @param[in] conv_hdl Driver handle returned by :cpp:func:`esp_async_color_convert_install_dma2d` + * + * @return + * - ESP_OK: Driver uninstalled successfully + * - ESP_ERR_INVALID_ARG: Invalid argument + * - ESP_ERR_INVALID_STATE: There are pending requests in the queue + */ +esp_err_t esp_async_color_convert_uninstall(async_color_convert_handle_t conv_hdl); + +/** + * @brief Async color conversion request + * + * Coordinates and size are in pixels. + * + * The source and destination windows are: + * - source: [src_x, src_x + copy_width) x [src_y, src_y + copy_height) + * - destination: [dst_x, dst_x + copy_width) x [dst_y, dst_y + copy_height) + * + * Both windows must be fully inside their corresponding image bounds. + * + * Conversion rule is inferred from source and destination formats: + * - If source and destination are the same format, it performs 2D copy only. + */ +typedef struct { + const void *src_buffer; /*!< Source picture base address */ + uint32_t src_stride; /*!< Source picture row stride in pixels */ + uint32_t src_height; /*!< Source picture height in pixels */ + uint32_t src_x; /*!< Source window x offset in pixels */ + uint32_t src_y; /*!< Source window y offset in pixels */ + + void *dst_buffer; /*!< Destination picture base address */ + uint32_t dst_stride; /*!< Destination picture row stride in pixels */ + uint32_t dst_height; /*!< Destination picture height in pixels */ + uint32_t dst_x; /*!< Destination window x offset in pixels */ + uint32_t dst_y; /*!< Destination window y offset in pixels */ + + uint32_t copy_width; /*!< Conversion window width in pixels */ + uint32_t copy_height; /*!< Conversion window height in pixels */ + + esp_color_fourcc_t src_color_format; /*!< Source pixel format */ + esp_color_fourcc_t dst_color_format; /*!< Destination pixel format */ + color_conv_std_rgb_yuv_t color_conv_std; /*!< RGB/YUV conversion standard for RGB888<->UYVY422 */ +} async_color_convert_request_t; + +/** + * @brief Submit an asynchronous 2D color conversion request + * + * The request is enqueued and completed later in DMA2D interrupt context. + * The callback can be NULL if no completion notification is needed. + * + * @param[in] conv_hdl Driver handle returned by :cpp:func:`esp_async_color_convert_install_dma2d` + * @param[in] request Color conversion request + * @param[in] cb_isr ISR callback invoked on conversion completion, can be NULL + * @param[in] cb_args User context passed to @p cb_isr + * + * @return + * - ESP_OK: Request accepted + * - ESP_ERR_INVALID_ARG: Invalid argument or invalid request fields + * - ESP_ERR_INVALID_STATE: No free internal transaction slot (queue full) + * - others: Error from lower-level DMA2D driver + */ +esp_err_t esp_async_color_convert(async_color_convert_handle_t conv_hdl, + const async_color_convert_request_t *request, + async_color_convert_isr_cb_t cb_isr, + void *cb_args); + +/** + * @brief Blocking 2D color conversion API built on async request path + * + * @note This API must not be called from ISR context. + * + * @param[in] conv_hdl Driver handle returned by :cpp:func:`esp_async_color_convert_install_dma2d` + * @param[in] request Color conversion request + * @param[in] timeout_ms Timeout in milliseconds. Currently only ``-1`` is supported, which waits forever. + * + * @return + * - ESP_OK: Conversion completed successfully + * - ESP_ERR_INVALID_ARG: Invalid argument, unsupported timeout, or invalid request fields + * - ESP_ERR_INVALID_STATE: Called from ISR context, or queue unavailable + * - others: Error from lower-level DMA2D driver + */ +esp_err_t esp_color_convert_blocking(async_color_convert_handle_t conv_hdl, + const async_color_convert_request_t *request, + int32_t timeout_ms); + +#ifdef __cplusplus +} +#endif diff --git a/components/esp_driver_dma/include/esp_private/gdma.h b/components/esp_driver_dma/include/esp_private/gdma.h index 1dca3df9e2a..62cda3150c2 100644 --- a/components/esp_driver_dma/include/esp_private/gdma.h +++ b/components/esp_driver_dma/include/esp_private/gdma.h @@ -64,6 +64,7 @@ typedef bool (*gdma_event_callback_t)(gdma_channel_handle_t dma_chan, gdma_event typedef struct { gdma_event_callback_t on_trans_eof; /*!< Invoked when TX engine meets EOF descriptor */ gdma_event_callback_t on_descr_err; /*!< Invoked when DMA encounters a descriptor error */ + gdma_event_callback_t on_link_switch; /*!< Invoked when TX link list switches to a new descriptor chain */ } gdma_tx_event_callbacks_t; /** @@ -284,6 +285,26 @@ esp_err_t gdma_get_group_channel_id(gdma_channel_handle_t dma_chan, int *group_i */ esp_err_t gdma_register_tx_event_callbacks(gdma_channel_handle_t dma_chan, gdma_tx_event_callbacks_t *cbs, void *user_data); +/** + * @brief Request an interrupt when the TX channel switches to a new descriptor chain + * + * @note This API is only available on targets that support TX link switch interrupt. + * @note Register the `on_link_switch` callback by `gdma_register_tx_event_callbacks()` before calling this API. + * @note The TX EOF event indicates that GDMA has reached an EOF descriptor. By contrast, the TX link switch + * event indicates that GDMA has started using the next descriptor chain after a link update. + * GDMA can prefetch descriptors from the updated link before the EOF event, so EOF doesn't always mean + * the previous descriptor chain is no longer referenced by GDMA. The link switch event is needed when + * the caller must know that the previous descriptor chain can be reused safely. + * + * @param[in] dma_tx_chan GDMA TX channel handle + * @return + * - ESP_OK: Request link switch event successfully + * - ESP_ERR_INVALID_ARG: Invalid argument + * - ESP_ERR_NOT_SUPPORTED: Link switch event is not supported + * - ESP_ERR_INVALID_STATE: Link switch callback is not registered + */ +esp_err_t gdma_request_link_switch_event(gdma_channel_handle_t dma_tx_chan); + /** * @brief Set GDMA event callbacks for RX channel * @note This API will install GDMA interrupt service for the channel internally diff --git a/components/esp_driver_dma/linker.lf b/components/esp_driver_dma/linker.lf index d21636fd89e..1f3f199872f 100644 --- a/components/esp_driver_dma/linker.lf +++ b/components/esp_driver_dma/linker.lf @@ -20,24 +20,28 @@ entries: gdma_hal_top: gdma_hal_clear_intr (noflash) gdma_hal_top: gdma_hal_read_intr_status (noflash) gdma_hal_top: gdma_hal_get_eof_desc_addr (noflash) + gdma_hal_top: gdma_hal_is_tx_link_switch_event_supported (noflash) # GDMA implementation layer for AHB-DMA version 1 if SOC_AHB_GDMA_VERSION = 1: gdma_hal_ahb_v1: gdma_ahb_hal_clear_intr (noflash) gdma_hal_ahb_v1: gdma_ahb_hal_read_intr_status (noflash) gdma_hal_ahb_v1: gdma_ahb_hal_get_eof_desc_addr (noflash) + gdma_hal_ahb_v1: gdma_ahb_hal_is_tx_link_switch_event_supported (noflash) # GDMA implementation layer for AHB-DMA version 2 if SOC_AHB_GDMA_VERSION = 2: gdma_hal_ahb_v2: gdma_ahb_hal_clear_intr (noflash) gdma_hal_ahb_v2: gdma_ahb_hal_read_intr_status (noflash) gdma_hal_ahb_v2: gdma_ahb_hal_get_eof_desc_addr (noflash) + gdma_hal_ahb_v2: gdma_ahb_hal_is_tx_link_switch_event_supported (noflash) # GDMA implementation layer for AXI-DMA if SOC_AXI_GDMA_SUPPORTED = y: gdma_hal_axi: gdma_axi_hal_clear_intr (noflash) gdma_hal_axi: gdma_axi_hal_read_intr_status (noflash) gdma_hal_axi: gdma_axi_hal_get_eof_desc_addr (noflash) + gdma_hal_axi: gdma_axi_hal_is_tx_link_switch_event_supported (noflash) # put GDMA control HAL functions in IRAM if GDMA_CTRL_FUNC_IN_IRAM = y: diff --git a/components/esp_driver_dma/src/async_color_convert_dma2d.c b/components/esp_driver_dma/src/async_color_convert_dma2d.c new file mode 100644 index 00000000000..976a962401e --- /dev/null +++ b/components/esp_driver_dma/src/async_color_convert_dma2d.c @@ -0,0 +1,507 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_check.h" +#include "esp_cache.h" +#include "esp_private/esp_cache_private.h" +#include "esp_heap_caps.h" +#include "esp_memory_utils.h" +#include "esp_async_color_convert_priv.h" +#include "soc/dma2d_channel.h" +#include "hal/dma2d_types.h" +#include "hal/dma2d_ll.h" +#include "hal/color_hal.h" + +ESP_LOG_ATTR_TAG(TAG, "async_color_dma2d"); + +typedef struct async_color_convert_dma2d_context_t async_color_convert_dma2d_context_t; +typedef struct async_color_convert_transaction async_color_convert_transaction_t; + +struct async_color_convert_transaction { + // Keep descriptors in dedicated cache-line-sized allocations so cache sync only + // touches descriptor state, not the surrounding transaction metadata. + dma2d_descriptor_t *tx_desc; // TX descriptor used by DMA2D source channel + dma2d_descriptor_t *rx_desc; // RX descriptor used by DMA2D destination channel + dma2d_trans_t *dma2d_trans_placeholder; // Opaque DMA2D transaction object storage + dma2d_trans_config_t dma2d_trans_config; // Per-request DMA2D transaction configuration + + async_color_convert_request_t request; // Cached user request used to build DMA2D transaction + dma2d_csc_config_t tx_csc; // Cached DMA2D TX CSC configuration resolved in task context + dma2d_csc_config_t rx_csc; // Cached DMA2D RX CSC configuration resolved in task context + async_color_convert_isr_cb_t cb_isr; // User ISR callback for this request + void *cb_args; // User callback argument + async_color_convert_dma2d_context_t *ctx; // Back pointer to parent context + + STAILQ_ENTRY(async_color_convert_transaction) queue_entry; // Node in idle transaction queue +}; + +struct async_color_convert_dma2d_context_t { + async_color_convert_context_t parent; // Base interface used by common API wrappers + dma2d_pool_handle_t pool; // DMA2D pool handle for enqueue/dequeue scheduling + size_t dma_burst_size; // DMA burst size applied on job picked + size_t desc_alloc_size; // Cache-line-sized DMA2D descriptor allocation size + portMUX_TYPE spinlock; // Protects idle_queue + uint32_t num_trans_objs; // Total number of transaction objects in trans_pool + _Atomic uint32_t idle_num; // Number of currently available transaction objects + _Atomic bool deleting; // Whether uninstall is in progress + async_color_convert_transaction_t *trans_pool; // Pre-allocated transaction object pool + STAILQ_HEAD(, async_color_convert_transaction) idle_queue; // Queue of available transaction objects +}; + +static esp_err_t async_color_convert_dma2d_del(async_color_convert_context_t *ctx); +static esp_err_t async_color_convert_dma2d_convert(async_color_convert_context_t *ctx, + const async_color_convert_request_t *request, + async_color_convert_isr_cb_t cb_isr, + void *cb_args); + +static bool is_rgb24_or_bgr24_fourcc(esp_color_fourcc_t fourcc) +{ + return fourcc == ESP_COLOR_FOURCC_BGR24 || fourcc == ESP_COLOR_FOURCC_RGB24; +} + +static dma2d_csc_config_t default_tx_csc_config(void) +{ + return (dma2d_csc_config_t) { + .tx_csc_option = DMA2D_CSC_TX_NONE, + .pre_scramble = DMA2D_SCRAMBLE_ORDER_BYTE2_1_0, + .post_scramble = DMA2D_SCRAMBLE_ORDER_BYTE2_1_0, + }; +} + +static dma2d_csc_config_t default_rx_csc_config(void) +{ + return (dma2d_csc_config_t) { + .rx_csc_option = DMA2D_CSC_RX_NONE, + .pre_scramble = DMA2D_SCRAMBLE_ORDER_BYTE2_1_0, + .post_scramble = DMA2D_SCRAMBLE_ORDER_BYTE2_1_0, + }; +} + +static bool resolve_dma2d_csc_configs(const async_color_convert_request_t *request, + dma2d_csc_config_t *out_tx_csc, + dma2d_csc_config_t *out_rx_csc) +{ + esp_color_fourcc_t src_fourcc = request->src_color_format; + esp_color_fourcc_t dst_fourcc = request->dst_color_format; + bool src_is_rgb24_or_bgr24 = is_rgb24_or_bgr24_fourcc(src_fourcc); + bool dst_is_rgb24_or_bgr24 = is_rgb24_or_bgr24_fourcc(dst_fourcc); + + *out_tx_csc = default_tx_csc_config(); + *out_rx_csc = default_rx_csc_config(); + + if (src_fourcc == dst_fourcc) { + return true; + } + + if (src_is_rgb24_or_bgr24 && dst_is_rgb24_or_bgr24) { + out_tx_csc->tx_csc_option = DMA2D_CSC_TX_SCRAMBLE; // RGB<->BGR conversion is just a scramble operation + out_tx_csc->pre_scramble = DMA2D_SCRAMBLE_ORDER_BYTE0_1_2; + return true; + } + + if (src_fourcc == ESP_COLOR_FOURCC_RGB16 && dst_fourcc == ESP_COLOR_FOURCC_BGR24) { + out_tx_csc->tx_csc_option = DMA2D_CSC_TX_RGB565_TO_RGB888; + return true; + } + + if (src_is_rgb24_or_bgr24 && dst_fourcc == ESP_COLOR_FOURCC_RGB16) { + out_tx_csc->tx_csc_option = DMA2D_CSC_TX_RGB888_TO_RGB565; + if (src_fourcc == ESP_COLOR_FOURCC_RGB24) { + out_tx_csc->pre_scramble = DMA2D_SCRAMBLE_ORDER_BYTE0_1_2; + } + return true; + } + + if (src_is_rgb24_or_bgr24 && dst_fourcc == ESP_COLOR_FOURCC_UYVY) { + if (request->color_conv_std == COLOR_CONV_STD_RGB_YUV_BT601) { + out_tx_csc->tx_csc_option = DMA2D_CSC_TX_RGB888_TO_YUV422_601; + } else if (request->color_conv_std == COLOR_CONV_STD_RGB_YUV_BT709) { + out_tx_csc->tx_csc_option = DMA2D_CSC_TX_RGB888_TO_YUV422_709; + } else { + return false; + } + if (src_fourcc == ESP_COLOR_FOURCC_RGB24) { + out_tx_csc->pre_scramble = DMA2D_SCRAMBLE_ORDER_BYTE0_1_2; + } + return true; + } + + if (src_fourcc == ESP_COLOR_FOURCC_UYVY && dst_fourcc == ESP_COLOR_FOURCC_BGR24) { + if (request->color_conv_std == COLOR_CONV_STD_RGB_YUV_BT601) { + out_tx_csc->tx_csc_option = DMA2D_CSC_TX_YUV422_TO_RGB888_601; + } else if (request->color_conv_std == COLOR_CONV_STD_RGB_YUV_BT709) { + out_tx_csc->tx_csc_option = DMA2D_CSC_TX_YUV422_TO_RGB888_709; + } else { + return false; + } + return true; + } + + return false; +} + +static inline bool needs_tx_csc(const dma2d_csc_config_t *tx_csc) +{ + return tx_csc->tx_csc_option != DMA2D_CSC_TX_NONE; +} + +static inline bool needs_rx_csc(const dma2d_csc_config_t *rx_csc) +{ + return rx_csc->rx_csc_option != DMA2D_CSC_RX_NONE; +} + +static esp_err_t sync_if_cacheable(void *addr, size_t size, int flags) +{ + return esp_cache_get_line_size_by_addr(addr) > 0 ? esp_cache_msync(addr, size, flags) : ESP_OK; +} + +static size_t get_picture_size_bytes(uint32_t stride, uint32_t height, uint32_t bit_depth) +{ + return (((size_t)stride * height * bit_depth) + 7) / 8; +} + +static esp_err_t validate_request(const async_color_convert_request_t *request) +{ + ESP_RETURN_ON_FALSE(request->src_color_format != 0 && request->dst_color_format != 0, + ESP_ERR_INVALID_ARG, TAG, "invalid color format"); + ESP_RETURN_ON_FALSE(request->src_buffer && request->dst_buffer, ESP_ERR_INVALID_ARG, TAG, "invalid buffer"); + ESP_RETURN_ON_FALSE(request->copy_width > 0 && request->copy_height > 0, ESP_ERR_INVALID_ARG, TAG, "invalid copy window"); + + uint64_t src_x_end = (uint64_t)request->src_x + request->copy_width; + uint64_t src_y_end = (uint64_t)request->src_y + request->copy_height; + uint64_t dst_x_end = (uint64_t)request->dst_x + request->copy_width; + uint64_t dst_y_end = (uint64_t)request->dst_y + request->copy_height; + + ESP_RETURN_ON_FALSE(src_x_end <= request->src_stride, ESP_ERR_INVALID_ARG, TAG, "source window out of width"); + ESP_RETURN_ON_FALSE(src_y_end <= request->src_height, ESP_ERR_INVALID_ARG, TAG, "source window out of height"); + ESP_RETURN_ON_FALSE(dst_x_end <= request->dst_stride, ESP_ERR_INVALID_ARG, TAG, "destination window out of width"); + ESP_RETURN_ON_FALSE(dst_y_end <= request->dst_height, ESP_ERR_INVALID_ARG, TAG, "destination window out of height"); + + ESP_RETURN_ON_FALSE(request->src_stride <= DMA2D_LL_DESC_2D_FIELD_MAX && + request->src_height <= DMA2D_LL_DESC_2D_FIELD_MAX && + request->dst_stride <= DMA2D_LL_DESC_2D_FIELD_MAX && + request->dst_height <= DMA2D_LL_DESC_2D_FIELD_MAX, + ESP_ERR_INVALID_ARG, TAG, "dimension exceeds DMA2D descriptor field limit"); + + return ESP_OK; +} + +static async_color_convert_transaction_t *try_acquire_trans(async_color_convert_dma2d_context_t *ctx) +{ + async_color_convert_transaction_t *trans = NULL; + portENTER_CRITICAL(&ctx->spinlock); + if (!atomic_load(&ctx->deleting)) { + trans = STAILQ_FIRST(&ctx->idle_queue); + if (trans) { + STAILQ_REMOVE_HEAD(&ctx->idle_queue, queue_entry); + atomic_fetch_sub(&ctx->idle_num, 1); + } + } + portEXIT_CRITICAL(&ctx->spinlock); + return trans; +} + +static void recycle_trans(async_color_convert_dma2d_context_t *ctx, + async_color_convert_transaction_t *trans) +{ + portENTER_CRITICAL_SAFE(&ctx->spinlock); + STAILQ_INSERT_TAIL(&ctx->idle_queue, trans, queue_entry); + atomic_fetch_add(&ctx->idle_num, 1); + portEXIT_CRITICAL_SAFE(&ctx->spinlock); +} + +static bool async_color_convert_done_cb(dma2d_channel_handle_t dma2d_chan, + dma2d_event_data_t *event_data, + void *user_data) +{ + bool need_yield = false; + async_color_convert_transaction_t *trans = (async_color_convert_transaction_t *)user_data; + async_color_convert_dma2d_context_t *ctx = trans->ctx; + (void)dma2d_chan; + (void)event_data; + + if (trans->cb_isr) { + async_color_convert_event_data_t edata = {}; + need_yield = trans->cb_isr(&ctx->parent, &edata, trans->cb_args); + } + trans->cb_isr = NULL; + trans->cb_args = NULL; + + recycle_trans(ctx, trans); + return need_yield; +} + +static bool async_color_convert_on_job_picked(uint32_t channel_num, + const dma2d_trans_channel_info_t *dma2d_chans, + void *user_config) +{ + async_color_convert_transaction_t *trans = (async_color_convert_transaction_t *)user_config; + + dma2d_channel_handle_t tx_chan = NULL; + dma2d_channel_handle_t rx_chan = NULL; + + for (uint32_t i = 0; i < channel_num; i++) { + if (dma2d_chans[i].dir == DMA2D_CHANNEL_DIRECTION_TX) { + tx_chan = dma2d_chans[i].chan; + } else { + rx_chan = dma2d_chans[i].chan; + } + } + + dma2d_trigger_t trig_periph = { + .periph = DMA2D_TRIG_PERIPH_M2M, + .periph_sel_id = SOC_DMA2D_TRIG_PERIPH_M2M_TX, + }; + dma2d_connect(tx_chan, &trig_periph); + trig_periph.periph_sel_id = SOC_DMA2D_TRIG_PERIPH_M2M_RX; + dma2d_connect(rx_chan, &trig_periph); + + async_color_convert_dma2d_context_t *ctx = trans->ctx; + dma2d_transfer_ability_t transfer_ability = { + .desc_burst_en = true, + .data_burst_length = ctx->dma_burst_size, + .access_ext_mem = true, + .mb_size = DMA2D_MACRO_BLOCK_SIZE_NONE, + }; + dma2d_set_transfer_ability(tx_chan, &transfer_ability); + dma2d_set_transfer_ability(rx_chan, &transfer_ability); + + dma2d_configure_color_space_conversion(tx_chan, &trans->tx_csc); + dma2d_configure_color_space_conversion(rx_chan, &trans->rx_csc); + + dma2d_rx_event_callbacks_t cbs = { + .on_recv_eof = async_color_convert_done_cb, + }; + dma2d_register_rx_event_callbacks(rx_chan, &cbs, trans); + + dma2d_set_desc_addr(rx_chan, (intptr_t)trans->rx_desc); + dma2d_set_desc_addr(tx_chan, (intptr_t)trans->tx_desc); + + dma2d_start(rx_chan); + dma2d_start(tx_chan); + return false; +} + +static void setup_desc(dma2d_descriptor_t *desc, + void *buffer, + uint32_t pic_w, + uint32_t pic_h, + uint32_t win_w, + uint32_t win_h, + uint32_t x, + uint32_t y, + uint32_t pbyte) +{ + memset(desc, 0, sizeof(*desc)); + desc->owner = DMA2D_DESCRIPTOR_BUFFER_OWNER_DMA; + desc->suc_eof = 1; + desc->dma2d_en = 1; + desc->ha_length = pic_w; + desc->va_size = pic_h; + desc->hb_length = win_w; + desc->vb_size = win_h; + desc->x = x; + desc->y = y; + desc->pbyte = pbyte; + desc->mode = DMA2D_DESCRIPTOR_BLOCK_RW_MODE_SINGLE; + desc->buffer = buffer; + desc->next = NULL; +} + +static esp_err_t async_color_convert_dma2d_convert(async_color_convert_context_t *ctx, + const async_color_convert_request_t *request, + async_color_convert_isr_cb_t cb_isr, + void *cb_args) +{ + esp_err_t ret = ESP_OK; + ESP_RETURN_ON_FALSE(ctx && request, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); + ESP_RETURN_ON_ERROR(validate_request(request), TAG, "invalid request"); + + async_color_convert_dma2d_context_t *color_ctx = __containerof(ctx, async_color_convert_dma2d_context_t, parent); + async_color_convert_transaction_t *trans = try_acquire_trans(color_ctx); + ESP_RETURN_ON_FALSE(trans, ESP_ERR_INVALID_STATE, TAG, "no free transaction in pool"); + + trans->request = *request; // copy request to transaction object + trans->cb_isr = cb_isr; + trans->cb_args = cb_args; + + esp_color_fourcc_t src_fourcc = request->src_color_format; + esp_color_fourcc_t dst_fourcc = request->dst_color_format; + trans->tx_csc = default_tx_csc_config(); + trans->rx_csc = default_rx_csc_config(); + ESP_GOTO_ON_FALSE(resolve_dma2d_csc_configs(request, &trans->tx_csc, &trans->rx_csc), + ESP_ERR_INVALID_ARG, recycle_and_out, TAG, "unsupported color conversion mode"); + + trans->dma2d_trans_config.channel_flags = DMA2D_CHANNEL_FUNCTION_FLAG_SIBLING; + if (needs_tx_csc(&trans->tx_csc)) { + trans->dma2d_trans_config.channel_flags |= DMA2D_CHANNEL_FUNCTION_FLAG_TX_CSC; + } + if (needs_rx_csc(&trans->rx_csc)) { + trans->dma2d_trans_config.channel_flags |= DMA2D_CHANNEL_FUNCTION_FLAG_RX_CSC; + } + setup_desc(trans->tx_desc, + (void *)request->src_buffer, + request->src_stride, + request->src_height, + request->copy_width, + request->copy_height, + request->src_x, + request->src_y, + dma2d_desc_pixel_format_to_pbyte_value(src_fourcc)); + + setup_desc(trans->rx_desc, + request->dst_buffer, + request->dst_stride, + request->dst_height, + request->copy_width, + request->copy_height, + request->dst_x, + request->dst_y, + dma2d_desc_pixel_format_to_pbyte_value(dst_fourcc)); + + uint32_t src_bpp = color_hal_pixel_format_fourcc_get_bit_depth(src_fourcc); + uint32_t dst_bpp = color_hal_pixel_format_fourcc_get_bit_depth(dst_fourcc); + + size_t src_total_size = get_picture_size_bytes(request->src_stride, request->src_height, src_bpp); + size_t dst_total_size = get_picture_size_bytes(request->dst_stride, request->dst_height, dst_bpp); + + ESP_GOTO_ON_ERROR(sync_if_cacheable((void *)request->src_buffer, src_total_size, + ESP_CACHE_MSYNC_FLAG_DIR_C2M | ESP_CACHE_MSYNC_FLAG_UNALIGNED), + recycle_and_out, TAG, "source cache sync failed"); + + ESP_GOTO_ON_ERROR(sync_if_cacheable(request->dst_buffer, dst_total_size, + ESP_CACHE_MSYNC_FLAG_DIR_C2M | ESP_CACHE_MSYNC_FLAG_INVALIDATE | ESP_CACHE_MSYNC_FLAG_UNALIGNED), + recycle_and_out, TAG, "destination cache sync failed"); + + ESP_GOTO_ON_ERROR(sync_if_cacheable(trans->tx_desc, color_ctx->desc_alloc_size, + ESP_CACHE_MSYNC_FLAG_DIR_C2M | ESP_CACHE_MSYNC_FLAG_INVALIDATE), + recycle_and_out, TAG, "tx descriptor cache sync failed"); + + ESP_GOTO_ON_ERROR(sync_if_cacheable(trans->rx_desc, color_ctx->desc_alloc_size, + ESP_CACHE_MSYNC_FLAG_DIR_C2M | ESP_CACHE_MSYNC_FLAG_INVALIDATE), + recycle_and_out, TAG, "rx descriptor cache sync failed"); + + ESP_GOTO_ON_ERROR(dma2d_enqueue(color_ctx->pool, + &trans->dma2d_trans_config, + trans->dma2d_trans_placeholder), + recycle_and_out, TAG, "enqueue dma2d transaction failed"); + + return ESP_OK; + +recycle_and_out: + recycle_trans(color_ctx, trans); + return ret; +} + +static esp_err_t async_color_convert_dma2d_destroy(async_color_convert_dma2d_context_t *ctx) +{ + if (ctx->pool) { + dma2d_release_pool(ctx->pool); + } + if (ctx->trans_pool) { + for (uint32_t i = 0; i < ctx->num_trans_objs; i++) { + free(ctx->trans_pool[i].tx_desc); + free(ctx->trans_pool[i].rx_desc); + free(ctx->trans_pool[i].dma2d_trans_placeholder); + } + free(ctx->trans_pool); + } + free(ctx); + return ESP_OK; +} + +static esp_err_t async_color_convert_dma2d_del(async_color_convert_context_t *ctx) +{ + async_color_convert_dma2d_context_t *color_ctx = __containerof(ctx, async_color_convert_dma2d_context_t, parent); + bool can_destroy = false; + portENTER_CRITICAL(&color_ctx->spinlock); + atomic_store(&color_ctx->deleting, true); + can_destroy = (atomic_load(&color_ctx->idle_num) == color_ctx->num_trans_objs); + if (!can_destroy) { + atomic_store(&color_ctx->deleting, false); + } + portEXIT_CRITICAL(&color_ctx->spinlock); + ESP_RETURN_ON_FALSE(can_destroy, + ESP_ERR_INVALID_STATE, TAG, "pending transactions exist"); + return async_color_convert_dma2d_destroy(color_ctx); +} + +esp_err_t esp_async_color_convert_install_dma2d(const async_color_convert_config_t *config, + async_color_convert_handle_t *ret_hdl) +{ + esp_err_t ret = ESP_OK; + ESP_RETURN_ON_FALSE(config && ret_hdl, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); + uint32_t trans_queue_len = config->backlog ? config->backlog : DEFAULT_COLOR_CONVERT_BACKLOG; + + async_color_convert_dma2d_context_t *ctx = heap_caps_calloc(1, sizeof(async_color_convert_dma2d_context_t), + MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT); + ESP_RETURN_ON_FALSE(ctx, ESP_ERR_NO_MEM, TAG, "no mem for color convert context"); + + ctx->trans_pool = heap_caps_calloc(trans_queue_len, sizeof(async_color_convert_transaction_t), + MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT); + ESP_GOTO_ON_FALSE(ctx->trans_pool, ESP_ERR_NO_MEM, err, TAG, "no mem for transaction pool"); + + esp_cache_get_alignment(MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA, &ctx->desc_alloc_size); + if (ctx->desc_alloc_size < sizeof(dma2d_descriptor_t)) { + ctx->desc_alloc_size = sizeof(dma2d_descriptor_t); + } + + ctx->num_trans_objs = trans_queue_len; + ctx->dma_burst_size = config->dma_burst_size ? config->dma_burst_size : 32; + portMUX_INITIALIZE(&ctx->spinlock); + STAILQ_INIT(&ctx->idle_queue); + atomic_init(&ctx->idle_num, trans_queue_len); + atomic_init(&ctx->deleting, false); + + dma2d_pool_config_t pool_cfg = { + .pool_id = 0, + .intr_priority = config->intr_priority, + }; + ESP_GOTO_ON_ERROR(dma2d_acquire_pool(&pool_cfg, &ctx->pool), err, TAG, "acquire dma2d pool failed"); + + for (uint32_t i = 0; i < trans_queue_len; i++) { + async_color_convert_transaction_t *trans = &ctx->trans_pool[i]; + trans->ctx = ctx; + // one DMA descriptor is enough for one color-conversion job since + // the driver will configure the descriptors in single-block mode and won't split the block into multiple tiles + trans->tx_desc = heap_caps_aligned_calloc(DMA2D_LL_DESC_ALIGNMENT, 1, ctx->desc_alloc_size, + MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA | MALLOC_CAP_8BIT); + ESP_GOTO_ON_FALSE(trans->tx_desc, ESP_ERR_NO_MEM, err, TAG, "no memory for tx descriptor"); + trans->rx_desc = heap_caps_aligned_calloc(DMA2D_LL_DESC_ALIGNMENT, 1, ctx->desc_alloc_size, + MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA | MALLOC_CAP_8BIT); + ESP_GOTO_ON_FALSE(trans->rx_desc, ESP_ERR_NO_MEM, err, TAG, "no memory for rx descriptor"); + trans->dma2d_trans_placeholder = heap_caps_calloc(1, dma2d_get_trans_elm_size(), MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT); + ESP_GOTO_ON_FALSE(trans->dma2d_trans_placeholder, ESP_ERR_NO_MEM, err, TAG, "no memory for dma2d transaction placeholder"); + // dma2d_enqueue requires a long-lived transaction config, so we prepare it here with the common part configured. + // The per-request specific part will be filled in on job picked. + trans->dma2d_trans_config = (dma2d_trans_config_t) { + .tx_channel_num = 1, + .rx_channel_num = 1, + .channel_flags = DMA2D_CHANNEL_FUNCTION_FLAG_SIBLING, + .specified_tx_channel_mask = 0, + .specified_rx_channel_mask = 0, + .on_job_picked = async_color_convert_on_job_picked, + .user_config = trans, + }; + STAILQ_INSERT_TAIL(&ctx->idle_queue, trans, queue_entry); + } + + ctx->parent.convert = async_color_convert_dma2d_convert; + ctx->parent.del = async_color_convert_dma2d_del; + + *ret_hdl = &ctx->parent; + return ESP_OK; + +err: + async_color_convert_dma2d_destroy(ctx); + return ret; +} diff --git a/components/esp_driver_dma/src/esp_async_color_convert.c b/components/esp_driver_dma/src/esp_async_color_convert.c new file mode 100644 index 00000000000..383e5fb5ad6 --- /dev/null +++ b/components/esp_driver_dma/src/esp_async_color_convert.c @@ -0,0 +1,63 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "freertos/FreeRTOS.h" +#include "freertos/semphr.h" +#include "freertos/task.h" +#include "esp_check.h" +#include "esp_async_color_convert.h" +#include "esp_async_color_convert_priv.h" + +ESP_LOG_ATTR_TAG(TAG, "async_color_conv"); + +esp_err_t esp_async_color_convert_uninstall(async_color_convert_handle_t conv_hdl) +{ + ESP_RETURN_ON_FALSE(conv_hdl, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); + return conv_hdl->del(conv_hdl); +} + +esp_err_t esp_async_color_convert(async_color_convert_handle_t conv_hdl, + const async_color_convert_request_t *request, + async_color_convert_isr_cb_t cb_isr, + void *cb_args) +{ + ESP_RETURN_ON_FALSE(conv_hdl && request, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); + return conv_hdl->convert(conv_hdl, request, cb_isr, cb_args); +} + +typedef struct { + SemaphoreHandle_t done_sem; + StaticSemaphore_t done_sem_buffer; +} color_convert_blocking_context_t; + +static bool color_convert_blocking_cb(async_color_convert_handle_t conv_hdl, + async_color_convert_event_data_t *edata, + void *cb_args) +{ + BaseType_t high_task_woken = pdFALSE; + color_convert_blocking_context_t *ctx = (color_convert_blocking_context_t *)cb_args; + (void)conv_hdl; + (void)edata; + xSemaphoreGiveFromISR(ctx->done_sem, &high_task_woken); + return (high_task_woken == pdTRUE); +} + +esp_err_t esp_color_convert_blocking(async_color_convert_handle_t conv_hdl, + const async_color_convert_request_t *request, + int32_t timeout_ms) +{ + ESP_RETURN_ON_FALSE(conv_hdl && request, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); + ESP_RETURN_ON_FALSE(!xPortInIsrContext(), ESP_ERR_INVALID_STATE, TAG, "called from ISR context is not allowed"); + ESP_RETURN_ON_FALSE(timeout_ms == -1, ESP_ERR_INVALID_ARG, TAG, "only timeout -1 is supported"); + + color_convert_blocking_context_t ctx = {}; + ctx.done_sem = xSemaphoreCreateBinaryStatic(&ctx.done_sem_buffer); + + ESP_RETURN_ON_ERROR(esp_async_color_convert(conv_hdl, request, color_convert_blocking_cb, &ctx), TAG, "fail to start async color conversion"); + // Wait for the conversion to complete + xSemaphoreTake(ctx.done_sem, portMAX_DELAY); + return ESP_OK; +} diff --git a/components/esp_driver_dma/src/esp_async_color_convert_priv.h b/components/esp_driver_dma/src/esp_async_color_convert_priv.h new file mode 100644 index 00000000000..9fccdf930a1 --- /dev/null +++ b/components/esp_driver_dma/src/esp_async_color_convert_priv.h @@ -0,0 +1,30 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include "esp_private/dma2d.h" +#include "esp_async_color_convert.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define DEFAULT_COLOR_CONVERT_BACKLOG 8 + +typedef struct async_color_convert_context_t async_color_convert_context_t; + +struct async_color_convert_context_t { + esp_err_t (*convert)(async_color_convert_context_t *ctx, + const async_color_convert_request_t *request, + async_color_convert_isr_cb_t cb_isr, + void *cb_args); + esp_err_t (*del)(async_color_convert_context_t *ctx); +}; + +#ifdef __cplusplus +} +#endif diff --git a/components/esp_driver_dma/src/gdma.c b/components/esp_driver_dma/src/gdma.c index 485f200edcd..740f474454b 100644 --- a/components/esp_driver_dma/src/gdma.c +++ b/components/esp_driver_dma/src/gdma.c @@ -420,15 +420,19 @@ esp_err_t gdma_config_transfer(gdma_channel_handle_t dma_chan, const gdma_transf // There's auto alignment for AHB GDMA version 1, so we don't need to do anything here // While, for AHB GDMA version 2 and AXI GDMA, we need to ensure the alignment by software #if (SOC_PSRAM_DMA_CAPABLE || SOC_DMA_CAN_ACCESS_FLASH) && SOC_AHB_GDMA_VERSION != 1 - // if MSPI encryption is enabled, and DMA wants to read/write external memory - if (esp_efuse_is_flash_encryption_enabled() && config->access_ext_mem) { - uint32_t enc_mem_alignment = SOC_MEMSPI_ENCRYPTION_ALIGNMENT; - // when DMA access the encrypted external memory, extra alignment is needed for external memory - ext_mem_alignment = MAX(ext_mem_alignment, enc_mem_alignment); - if (max_data_burst_size < enc_mem_alignment) { - ESP_LOGW(TAG, "GDMA channel access encrypted external memory, adjust burst size to %d", enc_mem_alignment); + bool ext_mem_needs_mspi_alignment = esp_efuse_is_flash_encryption_enabled(); +#if CONFIG_SPIRAM_ECC_ENABLE + ext_mem_needs_mspi_alignment = true; +#endif + // When MSPI encryption or PSRAM ECC address conversion is enabled, DMA accesses to + // external memory need to follow the MSPI encryption alignment restriction. + if (ext_mem_needs_mspi_alignment && config->access_ext_mem) { + uint32_t mspi_mem_alignment = SOC_MEMSPI_ENCRYPTION_ALIGNMENT; + ext_mem_alignment = MAX(ext_mem_alignment, mspi_mem_alignment); + if (max_data_burst_size < mspi_mem_alignment) { + ESP_LOGW(TAG, "GDMA channel access encrypted/ECC external memory, adjust burst size to %d", mspi_mem_alignment); en_data_burst = true; - max_data_burst_size = enc_mem_alignment; + max_data_burst_size = mspi_mem_alignment; } } #endif // SOC_PSRAM_DMA_CAPABLE || SOC_DMA_CAN_ACCESS_FLASH @@ -526,6 +530,12 @@ esp_err_t gdma_register_tx_event_callbacks(gdma_channel_handle_t dma_chan, gdma_ gdma_group_t *group = pair->group; gdma_hal_context_t *hal = &group->hal; gdma_tx_channel_t *tx_chan = __containerof(dma_chan, gdma_tx_channel_t, base); + bool link_switch_event_supported = gdma_hal_is_tx_link_switch_event_supported(hal); + + if (cbs->on_link_switch) { + ESP_RETURN_ON_FALSE(link_switch_event_supported, + ESP_ERR_NOT_SUPPORTED, TAG, "on_link_switch not supported"); + } if (dma_chan->flags.isr_cache_safe) { if (cbs->on_trans_eof) { @@ -536,6 +546,10 @@ esp_err_t gdma_register_tx_event_callbacks(gdma_channel_handle_t dma_chan, gdma_ ESP_RETURN_ON_FALSE(esp_ptr_in_iram(cbs->on_descr_err), ESP_ERR_INVALID_ARG, TAG, "on_descr_err not in IRAM"); } + if (cbs->on_link_switch) { + ESP_RETURN_ON_FALSE(esp_ptr_in_iram(cbs->on_link_switch), ESP_ERR_INVALID_ARG, + TAG, "on_link_switch not in IRAM"); + } if (user_data) { ESP_RETURN_ON_FALSE(esp_ptr_internal(user_data), ESP_ERR_INVALID_ARG, TAG, "user context not in internal RAM"); @@ -547,8 +561,16 @@ esp_err_t gdma_register_tx_event_callbacks(gdma_channel_handle_t dma_chan, gdma_ // enable/disable GDMA interrupt events for TX channel esp_os_enter_critical(&pair->spinlock); - gdma_hal_enable_intr(hal, pair->pair_id, GDMA_CHANNEL_DIRECTION_TX, GDMA_LL_EVENT_TX_EOF, cbs->on_trans_eof != NULL); - gdma_hal_enable_intr(hal, pair->pair_id, GDMA_CHANNEL_DIRECTION_TX, GDMA_LL_EVENT_TX_DESC_ERROR, cbs->on_descr_err != NULL); + gdma_hal_enable_intr(hal, pair->pair_id, GDMA_CHANNEL_DIRECTION_TX, GDMA_LL_EVENT_TX_EOF, + cbs->on_trans_eof != NULL); + gdma_hal_enable_intr(hal, pair->pair_id, GDMA_CHANNEL_DIRECTION_TX, GDMA_LL_EVENT_TX_DESC_ERROR, + cbs->on_descr_err != NULL); +#if GDMA_LL_EVENT_TX_LINK_SWITCH + if (link_switch_event_supported) { + gdma_hal_enable_intr(hal, pair->pair_id, GDMA_CHANNEL_DIRECTION_TX, GDMA_LL_EVENT_TX_LINK_SWITCH, + cbs->on_link_switch != NULL); + } +#endif // GDMA_LL_EVENT_TX_LINK_SWITCH esp_os_exit_critical(&pair->spinlock); memcpy(&tx_chan->cbs, cbs, sizeof(gdma_tx_event_callbacks_t)); @@ -674,6 +696,30 @@ esp_err_t gdma_reset(gdma_channel_handle_t dma_chan) return ESP_OK; } +esp_err_t gdma_request_link_switch_event(gdma_channel_handle_t dma_tx_chan) +{ + if (!dma_tx_chan || dma_tx_chan->direction != GDMA_CHANNEL_DIRECTION_TX) { + return ESP_ERR_INVALID_ARG; + } + gdma_pair_t *pair = dma_tx_chan->pair; + gdma_group_t *group = pair->group; + gdma_hal_context_t *hal = &group->hal; + gdma_tx_channel_t *tx_chan = __containerof(dma_tx_chan, gdma_tx_channel_t, base); + + if (!gdma_hal_is_tx_link_switch_event_supported(hal)) { + return ESP_ERR_NOT_SUPPORTED; + } + if (!tx_chan->cbs.on_link_switch) { + return ESP_ERR_INVALID_STATE; + } + + esp_os_enter_critical_safe(&dma_tx_chan->spinlock); + gdma_hal_request_link_switch_event(hal, pair->pair_id, dma_tx_chan->direction); + esp_os_exit_critical_safe(&dma_tx_chan->spinlock); + + return ESP_OK; +} + static void gdma_try_free_group_handle(gdma_group_t *group) { int group_id = group->group_id; @@ -965,6 +1011,12 @@ void gdma_default_tx_isr(void *args) if ((intr_status & GDMA_LL_EVENT_TX_DESC_ERROR) && tx_chan->cbs.on_descr_err) { need_yield |= tx_chan->cbs.on_descr_err(&tx_chan->base, NULL, tx_chan->user_data); } +#if GDMA_LL_EVENT_TX_LINK_SWITCH + if (gdma_hal_is_tx_link_switch_event_supported(hal) && + (intr_status & GDMA_LL_EVENT_TX_LINK_SWITCH) && tx_chan->cbs.on_link_switch) { + need_yield |= tx_chan->cbs.on_link_switch(&tx_chan->base, NULL, tx_chan->user_data); + } +#endif // GDMA_LL_EVENT_TX_LINK_SWITCH if (need_yield) { portYIELD_FROM_ISR(); } @@ -987,7 +1039,7 @@ static esp_err_t gdma_install_rx_interrupt(gdma_rx_channel_t *rx_chan) #endif intr_handle_t intr = NULL; ret = esp_intr_alloc_intrstatus(gdma_periph_signals.groups[group->group_id].pairs[pair_id].rx_irq_id, isr_flags, - gdma_hal_get_intr_status_reg(hal, pair_id, GDMA_CHANNEL_DIRECTION_RX), GDMA_LL_RX_EVENT_MASK, + gdma_hal_get_intr_status_reg(hal, pair_id, GDMA_CHANNEL_DIRECTION_RX), hal->priv_data->rx_event_mask, gdma_default_rx_isr, rx_chan, &intr); ESP_GOTO_ON_ERROR(ret, err, TAG, "alloc interrupt failed"); rx_chan->base.intr = intr; @@ -1019,7 +1071,7 @@ static esp_err_t gdma_install_tx_interrupt(gdma_tx_channel_t *tx_chan) #endif intr_handle_t intr = NULL; ret = esp_intr_alloc_intrstatus(gdma_periph_signals.groups[group->group_id].pairs[pair_id].tx_irq_id, isr_flags, - gdma_hal_get_intr_status_reg(hal, pair_id, GDMA_CHANNEL_DIRECTION_TX), GDMA_LL_TX_EVENT_MASK, + gdma_hal_get_intr_status_reg(hal, pair_id, GDMA_CHANNEL_DIRECTION_TX), hal->priv_data->tx_event_mask, gdma_default_tx_isr, tx_chan, &intr); ESP_GOTO_ON_ERROR(ret, err, TAG, "alloc interrupt failed"); tx_chan->base.intr = intr; diff --git a/components/esp_driver_dma/test_apps/dma/main/test_gdma_crc.c b/components/esp_driver_dma/test_apps/dma/main/test_gdma_crc.c index 2a8233f6906..a1103017986 100644 --- a/components/esp_driver_dma/test_apps/dma/main/test_gdma_crc.c +++ b/components/esp_driver_dma/test_apps/dma/main/test_gdma_crc.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2023-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2023-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -33,33 +33,43 @@ static test_crc_case_t crc_test_cases[] = { .crc_bit_width = 8, .init_value = 0x00, .poly_hex = 0x07, - .expected_result = 0xB8, + .expected_result = 0x08, }, [1] = { .crc_bit_width = 8, .init_value = 0x00, .poly_hex = 0x07, .reverse_data_mask = true, // refin = true - .expected_result = 0xF0, + .expected_result = 0xCE, }, // CRC16, x^16+x^12+x^5+1 [2] = { .crc_bit_width = 16, .init_value = 0xFFFF, .poly_hex = 0x1021, - .expected_result = 0xA9B2, + .expected_result = 0x0ED7, }, // CRC32, x32+x26+x23+x22+x16+x12+x11+x10+x8+x7+x5+x4+x2+x+1 [3] = { .crc_bit_width = 32, .init_value = 0xFFFFFFFF, .poly_hex = 0x04C11DB7, - .expected_result = 0x692F6C7E, + .expected_result = 0x6D9BD7D5, } }; +static bool test_gdma_crc_calculation_callback(gdma_channel_handle_t dma_chan, gdma_event_data_t *event_data, void *user_data) +{ + BaseType_t high_task_wakeup = pdFALSE; + SemaphoreHandle_t semaphore = (SemaphoreHandle_t)user_data; + if (event_data->flags.normal_eof) { + xSemaphoreGiveFromISR(semaphore, &high_task_wakeup); + } + return high_task_wakeup; +} + // CRC online: https://www.lddgo.net/en/encrypt/crc -static void test_gdma_crc_calculation(gdma_channel_handle_t tx_chan, int test_num_crc_algorithm) +static void test_gdma_crc_calculation(gdma_channel_handle_t tx_chan, gdma_channel_handle_t rx_chan, int test_num_crc_algorithm) { // Note, burst size should be at least 16 when accessing encrypted external memory gdma_transfer_config_t transfer_cfg = { @@ -67,30 +77,41 @@ static void test_gdma_crc_calculation(gdma_channel_handle_t tx_chan, int test_nu .access_ext_mem = true, }; TEST_ESP_OK(gdma_config_transfer(tx_chan, &transfer_cfg)); + TEST_ESP_OK(gdma_config_transfer(rx_chan, &transfer_cfg)); + SemaphoreHandle_t semaphore = xSemaphoreCreateBinary(); uint32_t crc_result = 0; - static const char test_input_string[] __attribute__((aligned(SOC_MEMSPI_ENCRYPTION_ALIGNMENT))) = "GDMACRC Share::Connect::Innovate"; + static const char test_input_string[] __attribute__((aligned(SOC_MEMSPI_ENCRYPTION_ALIGNMENT))) = "GDMACRC::TEST::LONGSTRING::REPEAT::GDMACRC::TEST::LONGSTRING::REPEAT::GDMACRC::TEST::LONGSTRING::REPEAT::GDMACRC::TEST::LONGSTRING::REPEAT::END!"; size_t input_data_size = strlen(test_input_string); - TEST_ASSERT_EQUAL((uintptr_t)test_input_string % SOC_MEMSPI_ENCRYPTION_ALIGNMENT, 0); // this test case also test the GDMA can fetch data from MSPI Flash TEST_ASSERT_TRUE(esp_ptr_in_drom(test_input_string)); printf("Calculate CRC value for string: \"%s\"\r\n", test_input_string); + uint8_t *rx_buffer = NULL; + rx_buffer = heap_caps_calloc(1, strlen(test_input_string), MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT); + TEST_ASSERT_NOT_NULL(rx_buffer); + gdma_trigger_t m2m_trigger = GDMA_MAKE_TRIGGER(GDMA_TRIG_PERIPH_M2M, 0); // get a free DMA trigger ID uint32_t free_m2m_id_mask = 0; gdma_get_free_m2m_trig_id_mask(tx_chan, &free_m2m_id_mask); + m2m_trigger.instance_id = __builtin_ctz(free_m2m_id_mask); TEST_ESP_OK(gdma_connect(tx_chan, m2m_trigger)); + TEST_ESP_OK(gdma_connect(rx_chan, m2m_trigger)); + + gdma_tx_event_callbacks_t tx_cbs = { + .on_trans_eof = test_gdma_crc_calculation_callback, + }; + TEST_ESP_OK(gdma_register_tx_event_callbacks(tx_chan, &tx_cbs, semaphore)); size_t sram_cache_line_size = cache_hal_get_cache_line_size(CACHE_LL_LEVEL_INT_MEM, CACHE_TYPE_DATA); size_t alignment = MAX(sram_cache_line_size, 8); dma_descriptor_align8_t *tx_descs = heap_caps_aligned_calloc(alignment, 1, sizeof(dma_descriptor_align8_t), MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT); TEST_ASSERT_NOT_NULL(tx_descs); - tx_descs->buffer = (void *)test_input_string; tx_descs->dw0.size = input_data_size + 1; // +1 for '\0' tx_descs->dw0.length = input_data_size; @@ -98,9 +119,18 @@ static void test_gdma_crc_calculation(gdma_channel_handle_t tx_chan, int test_nu tx_descs->dw0.suc_eof = 1; tx_descs->next = NULL; + dma_descriptor_align8_t *rx_descs = heap_caps_aligned_calloc(alignment, 1, sizeof(dma_descriptor_align8_t), + MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT); + TEST_ASSERT_NOT_NULL(rx_descs); + rx_descs->buffer = (void *)rx_buffer; + rx_descs->dw0.size = input_data_size; + rx_descs->dw0.owner = DMA_DESCRIPTOR_BUFFER_OWNER_DMA; + rx_descs->next = NULL; + if (sram_cache_line_size) { // do write-back for the buffer because it's in the cache TEST_ESP_OK(esp_cache_msync((void *)tx_descs, sizeof(dma_descriptor_align8_t), ESP_CACHE_MSYNC_FLAG_DIR_C2M | ESP_CACHE_MSYNC_FLAG_UNALIGNED)); + TEST_ESP_OK(esp_cache_msync((void *)rx_descs, sizeof(dma_descriptor_align8_t), ESP_CACHE_MSYNC_FLAG_DIR_C2M | ESP_CACHE_MSYNC_FLAG_UNALIGNED)); } for (int i = 0; i < test_num_crc_algorithm; i++) { @@ -111,34 +141,42 @@ static void test_gdma_crc_calculation(gdma_channel_handle_t tx_chan, int test_nu .reverse_data_mask = crc_test_cases[i].reverse_data_mask, }; TEST_ESP_OK(gdma_config_crc_calculator(tx_chan, &crc_config)); + TEST_ESP_OK(gdma_reset(rx_chan)); + TEST_ESP_OK(gdma_start(rx_chan, (intptr_t)rx_descs)); TEST_ESP_OK(gdma_reset(tx_chan)); TEST_ESP_OK(gdma_start(tx_chan, (intptr_t)tx_descs)); - // simply wait for the transfer done - vTaskDelay(pdMS_TO_TICKS(100)); + // wait for the transfer done + xSemaphoreTake(semaphore, pdMS_TO_TICKS(100)); TEST_ESP_OK(gdma_crc_get_result(tx_chan, &crc_result)); printf("CRC Result: 0x%"PRIx32"\r\n", crc_result); TEST_ASSERT_EQUAL(crc_test_cases[i].expected_result, crc_result); } free(tx_descs); + free(rx_descs); + free(rx_buffer); + vSemaphoreDelete(semaphore); } TEST_CASE("GDMA CRC Calculation", "[GDMA][CRC]") { gdma_channel_handle_t tx_chan = NULL; + gdma_channel_handle_t rx_chan = NULL; gdma_channel_alloc_config_t tx_chan_alloc_config = { }; #if SOC_HAS(AHB_GDMA) printf("Test CRC calculation for AHB GDMA\r\n"); - TEST_ESP_OK(gdma_new_ahb_channel(&tx_chan_alloc_config, &tx_chan, NULL)); - test_gdma_crc_calculation(tx_chan, 4); + TEST_ESP_OK(gdma_new_ahb_channel(&tx_chan_alloc_config, &tx_chan, &rx_chan)); + test_gdma_crc_calculation(tx_chan, rx_chan, 4); TEST_ESP_OK(gdma_del_channel(tx_chan)); + TEST_ESP_OK(gdma_del_channel(rx_chan)); #endif // SOC_HAS(AHB_GDMA) #if SOC_HAS(AXI_GDMA) printf("Test CRC calculation for AXI GDMA\r\n"); - TEST_ESP_OK(gdma_new_axi_channel(&tx_chan_alloc_config, &tx_chan, NULL)); - test_gdma_crc_calculation(tx_chan, 3); + TEST_ESP_OK(gdma_new_axi_channel(&tx_chan_alloc_config, &tx_chan, &rx_chan)); + test_gdma_crc_calculation(tx_chan, rx_chan, 3); TEST_ESP_OK(gdma_del_channel(tx_chan)); + TEST_ESP_OK(gdma_del_channel(rx_chan)); #endif // SOC_HAS(AXI_GDMA) } diff --git a/components/esp_driver_dma/test_apps/dma/pytest_dma.py b/components/esp_driver_dma/test_apps/dma/pytest_dma.py index 6c73c61215e..3f53bbccd6a 100644 --- a/components/esp_driver_dma/test_apps/dma/pytest_dma.py +++ b/components/esp_driver_dma/test_apps/dma/pytest_dma.py @@ -20,7 +20,21 @@ from pytest_embedded_idf.utils import soc_filtered_targets indirect=['target'], ) def test_dma(dut: Dut) -> None: - dut.run_all_single_board_cases(reset=True) + dut.run_all_single_board_cases() + + +@pytest.mark.generic +@pytest.mark.esp32p4_rev1 +@pytest.mark.parametrize( + 'config', + [ + 'esp32p4_rev1', + ], + indirect=True, +) +@idf_parametrize('target', ['esp32p4'], indirect=['target']) +def test_dma_esp32p4_rev1(dut: Dut) -> None: + dut.run_all_single_board_cases() @pytest.mark.octal_psram @@ -33,7 +47,7 @@ def test_dma(dut: Dut) -> None: ) @idf_parametrize('target', ['esp32s3'], indirect=['target']) def test_dma_psram(dut: Dut) -> None: - dut.run_all_single_board_cases(reset=True) + dut.run_all_single_board_cases() @pytest.mark.generic @@ -46,7 +60,7 @@ def test_dma_psram(dut: Dut) -> None: ) @idf_parametrize('target', soc_filtered_targets('SOC_GDMA_SUPPORT_WEIGHTED_ARBITRATION == 1'), indirect=['target']) def test_dma_weighted_arbitration(dut: Dut) -> None: - dut.run_all_single_board_cases(reset=True) + dut.run_all_single_board_cases() @pytest.mark.flash_encryption @@ -59,7 +73,7 @@ def test_dma_weighted_arbitration(dut: Dut) -> None: ) @idf_parametrize('target', ['esp32p4', 'esp32c5'], indirect=['target']) def test_dma_flash_encryption(dut: Dut) -> None: - dut.run_all_single_board_cases(reset=True) + dut.run_all_single_board_cases() @pytest.mark.flash_encryption_f4r8 @@ -72,4 +86,4 @@ def test_dma_flash_encryption(dut: Dut) -> None: ) @idf_parametrize('target', ['esp32s3'], indirect=['target']) def test_dma_flash_encryption_s3_f4r8(dut: Dut) -> None: - dut.run_all_single_board_cases(reset=True) + dut.run_all_single_board_cases() diff --git a/examples/peripherals/jpeg/jpeg_encode/sdkconfig.defaults.esp32p4 b/components/esp_driver_dma/test_apps/dma/sdkconfig.ci.esp32p4_rev1 similarity index 51% rename from examples/peripherals/jpeg/jpeg_encode/sdkconfig.defaults.esp32p4 rename to components/esp_driver_dma/test_apps/dma/sdkconfig.ci.esp32p4_rev1 index 38db6f4e61e..d0fb3a327e7 100644 --- a/examples/peripherals/jpeg/jpeg_encode/sdkconfig.defaults.esp32p4 +++ b/components/esp_driver_dma/test_apps/dma/sdkconfig.ci.esp32p4_rev1 @@ -1,6 +1,6 @@ -# SPIRAM configurations +CONFIG_IDF_TARGET="esp32p4" +CONFIG_ESP32P4_SELECTS_REV_LESS_V3=y -CONFIG_IDF_EXPERIMENTAL_FEATURES=y CONFIG_SPIRAM=y CONFIG_SPIRAM_MODE_HEX=y CONFIG_SPIRAM_SPEED_200M=y diff --git a/components/esp_driver_dma/test_apps/dma2d/main/CMakeLists.txt b/components/esp_driver_dma/test_apps/dma2d/main/CMakeLists.txt index bd6d24b9d8f..45b05834453 100644 --- a/components/esp_driver_dma/test_apps/dma2d/main/CMakeLists.txt +++ b/components/esp_driver_dma/test_apps/dma2d/main/CMakeLists.txt @@ -1,5 +1,6 @@ set(srcs "test_app_main.c" "test_dma2d.c" + "test_async_color_convert.c" "dma2d_test_utils.c") # In order for the cases defined by `TEST_CASE` to be linked into the final elf, diff --git a/components/esp_driver_dma/test_apps/dma2d/main/test_async_color_convert.c b/components/esp_driver_dma/test_apps/dma2d/main/test_async_color_convert.c new file mode 100644 index 00000000000..01dd14ae605 --- /dev/null +++ b/components/esp_driver_dma/test_apps/dma2d/main/test_async_color_convert.c @@ -0,0 +1,561 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include "unity.h" +#include "freertos/FreeRTOS.h" +#include "freertos/semphr.h" +#include "soc/soc_caps.h" +#include "esp_heap_caps.h" +#include "hal/color_types.h" +#include "hal/color_hal.h" +#include "esp_async_color_convert.h" + +typedef struct { + SemaphoreHandle_t sem; + int cb_called; +} async_color_convert_user_ctx_t; + +static void fill_pattern(uint8_t *buf, size_t len, uint8_t seed) +{ + for (size_t i = 0; i < len; i++) { + buf[i] = (uint8_t)(seed + i * 13); + } +} + +static bool test_async_color_convert_cb(async_color_convert_handle_t conv_hdl, + async_color_convert_event_data_t *edata, + void *cb_args) +{ + (void)conv_hdl; + (void)edata; + async_color_convert_user_ctx_t *user_ctx = (async_color_convert_user_ctx_t *)cb_args; + user_ctx->cb_called++; + BaseType_t high_task_wakeup = pdFALSE; + xSemaphoreGiveFromISR(user_ctx->sem, &high_task_wakeup); + return (high_task_wakeup == pdTRUE); +} + +TEST_CASE("async color convert basic callback", "[async_color_convert]") +{ + const uint32_t width = 32; + const uint32_t height = 24; + const uint32_t pixel_num = width * height; + + uint16_t *src565 = heap_caps_aligned_calloc(64, pixel_num, sizeof(uint16_t), + MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA | MALLOC_CAP_8BIT); + uint8_t *dst_bgr24 = heap_caps_aligned_calloc(64, pixel_num, 3, + MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA | MALLOC_CAP_8BIT); + TEST_ASSERT_NOT_NULL(src565); + TEST_ASSERT_NOT_NULL(dst_bgr24); + + for (uint32_t i = 0; i < pixel_num; i++) { + src565[i] = (uint16_t)((i * 13) ^ 0x5AA5); + } + + async_color_convert_config_t config = { + .backlog = 2, + .intr_priority = 0, + .dma_burst_size = 16, + }; + async_color_convert_handle_t conv_hdl = NULL; + TEST_ESP_OK(esp_async_color_convert_install_dma2d(&config, &conv_hdl)); + + async_color_convert_request_t req = { + .src_buffer = src565, + .src_stride = width, + .src_height = height, + .src_x = 0, + .src_y = 0, + .dst_buffer = dst_bgr24, + .dst_stride = width, + .dst_height = height, + .dst_x = 0, + .dst_y = 0, + .copy_width = width, + .copy_height = height, + .src_color_format = ESP_COLOR_FOURCC_RGB16, + .dst_color_format = ESP_COLOR_FOURCC_BGR24, + }; + + async_color_convert_user_ctx_t user_ctx = { + .sem = xSemaphoreCreateBinary(), + .cb_called = 0, + }; + TEST_ASSERT_NOT_NULL(user_ctx.sem); + + TEST_ESP_OK(esp_async_color_convert(conv_hdl, &req, test_async_color_convert_cb, &user_ctx)); + TEST_ASSERT_EQUAL(pdTRUE, xSemaphoreTake(user_ctx.sem, pdMS_TO_TICKS(200))); + TEST_ASSERT_EQUAL(1, user_ctx.cb_called); + + vSemaphoreDelete(user_ctx.sem); + TEST_ESP_OK(esp_async_color_convert_uninstall(conv_hdl)); + + free(src565); + free(dst_bgr24); +} + +TEST_CASE("async color convert roundtrip: RGB16<->BGR24", "[async_color_convert]") +{ + const uint32_t width = 32; + const uint32_t height = 20; + const uint32_t pixel_num = width * height; + + uint16_t *src565 = heap_caps_aligned_calloc(64, pixel_num, sizeof(uint16_t), + MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA | MALLOC_CAP_8BIT); + uint8_t *mid_bgr24 = heap_caps_aligned_calloc(64, pixel_num, 3, + MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA | MALLOC_CAP_8BIT); + uint16_t *dst565 = heap_caps_aligned_calloc(64, pixel_num, sizeof(uint16_t), + MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA | MALLOC_CAP_8BIT); + TEST_ASSERT_NOT_NULL(src565); + TEST_ASSERT_NOT_NULL(mid_bgr24); + TEST_ASSERT_NOT_NULL(dst565); + + for (uint32_t i = 0; i < pixel_num; i++) { + src565[i] = (uint16_t)((i * 37) ^ 0xA55A); + } + + async_color_convert_config_t config = { + .backlog = 4, + .intr_priority = 0, + .dma_burst_size = 32, + }; + async_color_convert_handle_t conv_hdl = NULL; + TEST_ESP_OK(esp_async_color_convert_install_dma2d(&config, &conv_hdl)); + + async_color_convert_request_t req_565_to_bgr24 = { + .src_buffer = src565, + .src_stride = width, + .src_height = height, + .src_x = 0, + .src_y = 0, + .dst_buffer = mid_bgr24, + .dst_stride = width, + .dst_height = height, + .dst_x = 0, + .dst_y = 0, + .copy_width = width, + .copy_height = height, + .src_color_format = ESP_COLOR_FOURCC_RGB16, + .dst_color_format = ESP_COLOR_FOURCC_BGR24, + }; + + async_color_convert_request_t req_bgr24_to_565 = { + .src_buffer = mid_bgr24, + .src_stride = width, + .src_height = height, + .src_x = 0, + .src_y = 0, + .dst_buffer = dst565, + .dst_stride = width, + .dst_height = height, + .dst_x = 0, + .dst_y = 0, + .copy_width = width, + .copy_height = height, + .src_color_format = ESP_COLOR_FOURCC_BGR24, + .dst_color_format = ESP_COLOR_FOURCC_RGB16, + }; + + TEST_ESP_OK(esp_color_convert_blocking(conv_hdl, &req_565_to_bgr24, -1)); + TEST_ESP_OK(esp_color_convert_blocking(conv_hdl, &req_bgr24_to_565, -1)); + + // The final dst565 should be the same as the original src565 after round-trip conversion + TEST_ASSERT_EQUAL_MEMORY(src565, dst565, pixel_num * sizeof(uint16_t)); + + TEST_ESP_OK(esp_async_color_convert_uninstall(conv_hdl)); + + free(src565); + free(mid_bgr24); + free(dst565); +} + +TEST_CASE("async color convert bypasses color convert for 2D copy", "[async_color_convert]") +{ + const uint32_t src_stride = 48; + const uint32_t src_height = 28; + const uint32_t dst_stride = 64; + const uint32_t dst_height = 30; + const uint32_t copy_width = 32; + const uint32_t copy_height = 18; + const uint32_t src_x = 5; + const uint32_t src_y = 4; + const uint32_t dst_x = 7; + const uint32_t dst_y = 6; + const esp_color_fourcc_t fourcc = ESP_COLOR_FOURCC_RGB16; + const size_t bytes_per_pixel = color_hal_pixel_format_fourcc_get_bit_depth(fourcc) / 8; + const size_t src_size = (size_t)src_stride * src_height * bytes_per_pixel; + const size_t dst_size = (size_t)dst_stride * dst_height * bytes_per_pixel; + const size_t row_size = (size_t)copy_width * bytes_per_pixel; + + uint8_t *src = heap_caps_aligned_calloc(64, 1, src_size, + MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA | MALLOC_CAP_8BIT); + uint8_t *dst = heap_caps_aligned_calloc(64, 1, dst_size, + MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA | MALLOC_CAP_8BIT); + uint8_t *expected = heap_caps_aligned_calloc(64, 1, dst_size, + MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA | MALLOC_CAP_8BIT); + TEST_ASSERT_NOT_NULL(src); + TEST_ASSERT_NOT_NULL(dst); + TEST_ASSERT_NOT_NULL(expected); + + fill_pattern(src, src_size, 0x3C); + memset(dst, 0xA5, dst_size); + memset(expected, 0xA5, dst_size); + + for (uint32_t row = 0; row < copy_height; row++) { + const size_t src_offset = ((size_t)(src_y + row) * src_stride + src_x) * bytes_per_pixel; + const size_t dst_offset = ((size_t)(dst_y + row) * dst_stride + dst_x) * bytes_per_pixel; + memcpy(expected + dst_offset, src + src_offset, row_size); + } + + async_color_convert_config_t config = { + .backlog = 2, + .intr_priority = 0, + .dma_burst_size = 16, + }; + async_color_convert_handle_t conv_hdl = NULL; + TEST_ESP_OK(esp_async_color_convert_install_dma2d(&config, &conv_hdl)); + + async_color_convert_request_t req = { + .src_buffer = src, + .src_stride = src_stride, + .src_height = src_height, + .src_x = src_x, + .src_y = src_y, + .dst_buffer = dst, + .dst_stride = dst_stride, + .dst_height = dst_height, + .dst_x = dst_x, + .dst_y = dst_y, + .copy_width = copy_width, + .copy_height = copy_height, + .src_color_format = fourcc, + .dst_color_format = fourcc, + }; + + TEST_ESP_OK(esp_color_convert_blocking(conv_hdl, &req, -1)); + TEST_ASSERT_EQUAL_MEMORY(expected, dst, dst_size); + + TEST_ESP_OK(esp_async_color_convert_uninstall(conv_hdl)); + + free(src); + free(dst); + free(expected); +} + +static uint8_t clamp_to_u8(int value) +{ + if (value < 0) { + return 0; + } + if (value > 255) { + return 255; + } + return (uint8_t)value; +} + +static void uyvy_to_bgr24_reference_pixel(uint8_t y, uint8_t u, uint8_t v, + color_conv_std_rgb_yuv_t color_conv_std, + uint8_t *out_bgr) +{ + static const int bt601[3][4] = { + { 298, 0, 409, -56906 }, + { 298, -100, -208, 34707 }, + { 298, 516, 0, -70836 }, + }; + static const int bt709[3][4] = { + { 298, 0, 459, -63367 }, + { 298, -55, -136, 19681 }, + { 298, 541, 0, -73918 }, + }; + + const int (*coeff)[4] = (color_conv_std == COLOR_CONV_STD_RGB_YUV_BT709) ? bt709 : bt601; + int r = (coeff[0][0] * y + coeff[0][1] * u + coeff[0][2] * v + coeff[0][3] + 128) >> 8; + int g = (coeff[1][0] * y + coeff[1][1] * u + coeff[1][2] * v + coeff[1][3] + 128) >> 8; + int b = (coeff[2][0] * y + coeff[2][1] * u + coeff[2][2] * v + coeff[2][3] + 128) >> 8; + out_bgr[0] = clamp_to_u8(b); + out_bgr[1] = clamp_to_u8(g); + out_bgr[2] = clamp_to_u8(r); +} + +static void uyvy_to_bgr24_reference_image(const uint8_t *src_uyvy, uint8_t *dst_bgr24, + uint32_t src_stride, uint32_t dst_stride, + uint32_t copy_width, uint32_t copy_height, + color_conv_std_rgb_yuv_t color_conv_std) +{ + TEST_ASSERT_EQUAL_UINT32_MESSAGE(0, copy_width % 2, "UYVY width must be even"); + + for (uint32_t y = 0; y < copy_height; y++) { + for (uint32_t x = 0; x < copy_width; x += 2) { + size_t src_idx = ((size_t)y * src_stride + x) * 2; + size_t dst_idx0 = ((size_t)y * dst_stride + x) * 3; + size_t dst_idx1 = ((size_t)y * dst_stride + x + 1) * 3; + uint8_t u = src_uyvy[src_idx + 0]; + uint8_t y0 = src_uyvy[src_idx + 1]; + uint8_t v = src_uyvy[src_idx + 2]; + uint8_t y1 = src_uyvy[src_idx + 3]; + + uyvy_to_bgr24_reference_pixel(y0, u, v, color_conv_std, &dst_bgr24[dst_idx0]); + uyvy_to_bgr24_reference_pixel(y1, u, v, color_conv_std, &dst_bgr24[dst_idx1]); + } + } +} + +TEST_CASE("async color convert swaps RGB24 and BGR24 byte order", "[async_color_convert]") +{ + const uint32_t width = 4; + const uint32_t height = 2; + const size_t pixel_count = width * height; + const size_t buf_size = pixel_count * 3; + static const uint8_t src_rgb24[] = { + 0x10, 0x20, 0x30, 0x7F, 0x80, 0x81, 0xAA, 0x55, 0xFE, 0x01, 0xC0, 0x99, + 0xDE, 0xAD, 0xBE, 0x00, 0x11, 0x22, 0x44, 0x88, 0xCC, 0xF0, 0x0D, 0x42, + }; + static const uint8_t src_bgr24[] = { + 0x30, 0x20, 0x10, 0x81, 0x80, 0x7F, 0xFE, 0x55, 0xAA, 0x99, 0xC0, 0x01, + 0xBE, 0xAD, 0xDE, 0x22, 0x11, 0x00, 0xCC, 0x88, 0x44, 0x42, 0x0D, 0xF0, + }; + + TEST_ASSERT_EQUAL(sizeof(src_rgb24), buf_size); + TEST_ASSERT_EQUAL(sizeof(src_bgr24), buf_size); + + uint8_t *rgb24 = heap_caps_aligned_calloc(64, 1, buf_size, + MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA | MALLOC_CAP_8BIT); + uint8_t *bgr24 = heap_caps_aligned_calloc(64, 1, buf_size, + MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA | MALLOC_CAP_8BIT); + uint8_t *dst_bgr24 = heap_caps_aligned_calloc(64, 1, buf_size, + MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA | MALLOC_CAP_8BIT); + uint8_t *dst_rgb24 = heap_caps_aligned_calloc(64, 1, buf_size, + MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA | MALLOC_CAP_8BIT); + TEST_ASSERT_NOT_NULL(rgb24); + TEST_ASSERT_NOT_NULL(bgr24); + TEST_ASSERT_NOT_NULL(dst_bgr24); + TEST_ASSERT_NOT_NULL(dst_rgb24); + memcpy(rgb24, src_rgb24, buf_size); + memcpy(bgr24, src_bgr24, buf_size); + memset(dst_bgr24, 0xA5, buf_size); + memset(dst_rgb24, 0x5A, buf_size); + + async_color_convert_config_t config = { + .backlog = 1, + .intr_priority = 0, + .dma_burst_size = 16, + }; + async_color_convert_handle_t conv_hdl = NULL; + TEST_ESP_OK(esp_async_color_convert_install_dma2d(&config, &conv_hdl)); + + async_color_convert_request_t req_rgb_to_bgr = { + .src_buffer = rgb24, + .src_stride = width, + .src_height = height, + .src_x = 0, + .src_y = 0, + .dst_buffer = dst_bgr24, + .dst_stride = width, + .dst_height = height, + .dst_x = 0, + .dst_y = 0, + .copy_width = width, + .copy_height = height, + .src_color_format = ESP_COLOR_FOURCC_RGB24, + .dst_color_format = ESP_COLOR_FOURCC_BGR24, + }; + async_color_convert_request_t req_bgr_to_rgb = { + .src_buffer = bgr24, + .src_stride = width, + .src_height = height, + .src_x = 0, + .src_y = 0, + .dst_buffer = dst_rgb24, + .dst_stride = width, + .dst_height = height, + .dst_x = 0, + .dst_y = 0, + .copy_width = width, + .copy_height = height, + .src_color_format = ESP_COLOR_FOURCC_BGR24, + .dst_color_format = ESP_COLOR_FOURCC_RGB24, + }; + + TEST_ESP_OK(esp_color_convert_blocking(conv_hdl, &req_rgb_to_bgr, -1)); + TEST_ASSERT_EQUAL_MEMORY(src_bgr24, dst_bgr24, buf_size); + + TEST_ESP_OK(esp_color_convert_blocking(conv_hdl, &req_bgr_to_rgb, -1)); + TEST_ASSERT_EQUAL_MEMORY(src_rgb24, dst_rgb24, buf_size); + + TEST_ESP_OK(esp_async_color_convert_uninstall(conv_hdl)); + free(rgb24); + free(bgr24); + free(dst_bgr24); + free(dst_rgb24); +} + +// Verifies the scramble route and BGR24/RGB24->UYVY conversion compose correctly. +TEST_CASE("async color convert RGB24 and BGR24 inputs produce identical UYVY output", "[async_color_convert]") +{ + const uint32_t width = 4; + const uint32_t height = 2; + const size_t pixel_count = width * height; + const size_t rgb_size = pixel_count * 3; + const size_t uyvy_size = pixel_count * 2; + static const uint8_t src_rgb24[] = { + 0x10, 0x20, 0x30, 0x7F, 0x80, 0x81, 0xAA, 0x55, 0xFE, 0x01, 0xC0, 0x99, + 0xDE, 0xAD, 0xBE, 0x00, 0x11, 0x22, 0x44, 0x88, 0xCC, 0xF0, 0x0D, 0x42, + }; + static const uint8_t src_bgr24[] = { + 0x30, 0x20, 0x10, 0x81, 0x80, 0x7F, 0xFE, 0x55, 0xAA, 0x99, 0xC0, 0x01, + 0xBE, 0xAD, 0xDE, 0x22, 0x11, 0x00, 0xCC, 0x88, 0x44, 0x42, 0x0D, 0xF0, + }; + const color_conv_std_rgb_yuv_t conv_std = COLOR_CONV_STD_RGB_YUV_BT601; + + TEST_ASSERT_EQUAL(sizeof(src_rgb24), rgb_size); + TEST_ASSERT_EQUAL(sizeof(src_bgr24), rgb_size); + + uint8_t *rgb24 = heap_caps_aligned_calloc(64, 1, rgb_size, + MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA | MALLOC_CAP_8BIT); + uint8_t *bgr24 = heap_caps_aligned_calloc(64, 1, rgb_size, + MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA | MALLOC_CAP_8BIT); + uint8_t *dst_from_rgb24 = heap_caps_aligned_calloc(64, 1, uyvy_size, + MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA | MALLOC_CAP_8BIT); + uint8_t *dst_from_bgr24 = heap_caps_aligned_calloc(64, 1, uyvy_size, + MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA | MALLOC_CAP_8BIT); + TEST_ASSERT_NOT_NULL(rgb24); + TEST_ASSERT_NOT_NULL(bgr24); + TEST_ASSERT_NOT_NULL(dst_from_rgb24); + TEST_ASSERT_NOT_NULL(dst_from_bgr24); + memcpy(rgb24, src_rgb24, rgb_size); + memcpy(bgr24, src_bgr24, rgb_size); + + async_color_convert_config_t config = { + .backlog = 2, + .intr_priority = 0, + .dma_burst_size = 16, + }; + async_color_convert_handle_t conv_hdl = NULL; + TEST_ESP_OK(esp_async_color_convert_install_dma2d(&config, &conv_hdl)); + + memset(dst_from_rgb24, 0xA5, uyvy_size); + memset(dst_from_bgr24, 0x5A, uyvy_size); + + async_color_convert_request_t req_rgb24_to_uyvy = { + .src_buffer = rgb24, + .src_stride = width, + .src_height = height, + .src_x = 0, + .src_y = 0, + .dst_buffer = dst_from_rgb24, + .dst_stride = width, + .dst_height = height, + .dst_x = 0, + .dst_y = 0, + .copy_width = width, + .copy_height = height, + .src_color_format = ESP_COLOR_FOURCC_RGB24, + .dst_color_format = ESP_COLOR_FOURCC_UYVY, + .color_conv_std = conv_std, + }; + async_color_convert_request_t req_bgr24_to_uyvy = { + .src_buffer = bgr24, + .src_stride = width, + .src_height = height, + .src_x = 0, + .src_y = 0, + .dst_buffer = dst_from_bgr24, + .dst_stride = width, + .dst_height = height, + .dst_x = 0, + .dst_y = 0, + .copy_width = width, + .copy_height = height, + .src_color_format = ESP_COLOR_FOURCC_BGR24, + .dst_color_format = ESP_COLOR_FOURCC_UYVY, + .color_conv_std = conv_std, + }; + + TEST_ESP_OK(esp_color_convert_blocking(conv_hdl, &req_rgb24_to_uyvy, -1)); + TEST_ESP_OK(esp_color_convert_blocking(conv_hdl, &req_bgr24_to_uyvy, -1)); + TEST_ASSERT_EQUAL_MEMORY(dst_from_bgr24, dst_from_rgb24, uyvy_size); + + TEST_ESP_OK(esp_async_color_convert_uninstall(conv_hdl)); + free(rgb24); + free(bgr24); + free(dst_from_rgb24); + free(dst_from_bgr24); +} + +TEST_CASE("async color convert UYVY->BGR24 matches reference", "[async_color_convert]") +{ + const uint32_t src_stride = 32; + const uint32_t dst_stride = 64; + const uint32_t height = 2; + const uint32_t copy_width = 6; + const uint32_t copy_height = 2; + const size_t src_size = src_stride * height * 2; + const size_t dst_size = dst_stride * height * 3; + static const uint8_t sample_uyvy[] = { + 128, 16, 128, 235, 90, 81, 240, 145, 240, 200, 16, 54, + 54, 32, 200, 210, 180, 100, 90, 180, 16, 235, 240, 16, + }; + TEST_ASSERT_EQUAL(sizeof(sample_uyvy), copy_width * copy_height * 2); + + uint8_t *src = heap_caps_aligned_calloc(64, 1, src_size, + MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA | MALLOC_CAP_8BIT); + uint8_t *dst = heap_caps_aligned_calloc(64, 1, dst_size, + MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA | MALLOC_CAP_8BIT); + uint8_t *expected = heap_caps_aligned_calloc(64, 1, dst_size, + MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA | MALLOC_CAP_8BIT); + TEST_ASSERT_NOT_NULL(src); + TEST_ASSERT_NOT_NULL(dst); + TEST_ASSERT_NOT_NULL(expected); + for (uint32_t row = 0; row < copy_height; row++) { + memcpy(src + row * src_stride * 2, sample_uyvy + row * copy_width * 2, copy_width * 2); + } + + async_color_convert_config_t config = { + .backlog = 2, + .intr_priority = 0, + .dma_burst_size = 16, + }; + async_color_convert_handle_t conv_hdl = NULL; + TEST_ESP_OK(esp_async_color_convert_install_dma2d(&config, &conv_hdl)); + + const color_conv_std_rgb_yuv_t conv_stds[] = { + COLOR_CONV_STD_RGB_YUV_BT601, + COLOR_CONV_STD_RGB_YUV_BT709, + }; + for (size_t i = 0; i < sizeof(conv_stds) / sizeof(conv_stds[0]); i++) { + memset(dst, 0xA5, dst_size); + memset(expected, 0xA5, dst_size); + uyvy_to_bgr24_reference_image(src, expected, src_stride, dst_stride, copy_width, copy_height, conv_stds[i]); + + async_color_convert_request_t req = { + .src_buffer = src, + .src_stride = src_stride, + .src_height = height, + .src_x = 0, + .src_y = 0, + .dst_buffer = dst, + .dst_stride = dst_stride, + .dst_height = height, + .dst_x = 0, + .dst_y = 0, + .copy_width = copy_width, + .copy_height = copy_height, + .src_color_format = ESP_COLOR_FOURCC_UYVY, + .dst_color_format = ESP_COLOR_FOURCC_BGR24, + .color_conv_std = conv_stds[i], + }; + + TEST_ESP_OK(esp_color_convert_blocking(conv_hdl, &req, -1)); + TEST_ASSERT_EQUAL_MEMORY(expected, dst, dst_size); + } + + TEST_ESP_OK(esp_async_color_convert_uninstall(conv_hdl)); + free(src); + free(dst); + free(expected); +} diff --git a/components/esp_driver_dma/test_apps/dma2d/pytest_dma2d.py b/components/esp_driver_dma/test_apps/dma2d/pytest_dma2d.py index ee1cb98f3c1..a8405d71086 100644 --- a/components/esp_driver_dma/test_apps/dma2d/pytest_dma2d.py +++ b/components/esp_driver_dma/test_apps/dma2d/pytest_dma2d.py @@ -19,6 +19,20 @@ def test_dma2d(dut: Dut) -> None: dut.run_all_single_board_cases() +@pytest.mark.generic +@pytest.mark.esp32p4_rev1 +@pytest.mark.parametrize( + 'config', + [ + 'esp32p4_rev1', + ], + indirect=True, +) +@idf_parametrize('target', ['esp32p4'], indirect=['target']) +def test_dma2d_esp32p4_rev1(dut: Dut) -> None: + dut.run_all_single_board_cases() + + @pytest.mark.flash_encryption @pytest.mark.parametrize( 'config', diff --git a/components/esp_driver_dma/test_apps/dma2d/sdkconfig.ci.esp32p4_rev1 b/components/esp_driver_dma/test_apps/dma2d/sdkconfig.ci.esp32p4_rev1 new file mode 100644 index 00000000000..d0fb3a327e7 --- /dev/null +++ b/components/esp_driver_dma/test_apps/dma2d/sdkconfig.ci.esp32p4_rev1 @@ -0,0 +1,6 @@ +CONFIG_IDF_TARGET="esp32p4" +CONFIG_ESP32P4_SELECTS_REV_LESS_V3=y + +CONFIG_SPIRAM=y +CONFIG_SPIRAM_MODE_HEX=y +CONFIG_SPIRAM_SPEED_200M=y diff --git a/components/esp_driver_i2s/i2s_common.c b/components/esp_driver_i2s/i2s_common.c index 4cb23d61b39..ecdceb39958 100644 --- a/components/esp_driver_i2s/i2s_common.c +++ b/components/esp_driver_i2s/i2s_common.c @@ -948,7 +948,7 @@ esp_err_t i2s_check_set_mclk(i2s_chan_handle_t handle, int id, int gpio_num, i2s if (g_i2s.controller[id]->mclk_out_hdl == NULL) { i2s_output_gpio_reserve(handle, gpio_num); soc_clkout_sig_id_t clkout_sig = is_apll ? CLKOUT_SIG_APLL : (is_i2s0 ? CLKOUT_SIG_I2S0 : CLKOUT_SIG_I2S1); - ESP_RETURN_ON_ERROR(esp_clock_output_start(clkout_sig, gpio_num, &(g_i2s.controller[id]->mclk_out_hdl)), TAG, "mclk configure failed"); + ESP_RETURN_ON_ERROR(esp_clock_output_start(clkout_sig, gpio_num, &(g_i2s.controller[id]->mclk_out_hdl)), TAG, "mclk configure failed, note: only gpio 0/1/3 are supported on esp32"); } #else ESP_RETURN_ON_FALSE(GPIO_IS_VALID_GPIO(gpio_num), ESP_ERR_INVALID_ARG, TAG, "mck_io_num invalid"); diff --git a/components/esp_driver_isp/include/driver/isp_ae.h b/components/esp_driver_isp/include/driver/isp_ae.h index be8c471bdfd..16ff710705d 100644 --- a/components/esp_driver_isp/include/driver/isp_ae.h +++ b/components/esp_driver_isp/include/driver/isp_ae.h @@ -194,7 +194,7 @@ typedef bool (*esp_isp_ae_env_detector_callback_t)(isp_ae_ctlr_t ae_ctlr, const /** * @brief Group of ISP AE env_detector * @note These callbacks are all running in an ISR environment. - * @note When CONFIG_ISP_ISR_IRAM_SAEE is enabled, the callback itself and functions called by it should be placed in IRAM. + * @note When CONFIG_ISP_ISR_IRAM_SAFE is enabled, the callback itself and functions called by it should be placed in IRAM. * Involved variables should be in internal RAM as well. */ typedef struct { @@ -207,7 +207,7 @@ typedef struct { * * @note User can deregister a previously registered callback by calling this function and setting the to-be-deregistered callback member in * the `cbs` structure to NULL. - * @note When CONFIG_ISP_ISR_IRAM_SAEE is enabled, the callback itself and functions called by it should be placed in IRAM. + * @note When CONFIG_ISP_ISR_IRAM_SAFE is enabled, the callback itself and functions called by it should be placed in IRAM. * Involved variables (including `user_data`) should be in internal RAM as well. * * @param[in] ae_ctlr AE controller handle diff --git a/components/esp_driver_isp/src/isp_ae.c b/components/esp_driver_isp/src/isp_ae.c index 26799ae7e66..9033b80d544 100644 --- a/components/esp_driver_isp/src/isp_ae.c +++ b/components/esp_driver_isp/src/isp_ae.c @@ -233,7 +233,7 @@ esp_err_t esp_isp_ae_env_detector_register_event_callbacks(isp_ae_ctlr_t ae_ctlr ESP_RETURN_ON_FALSE(ae_ctlr && cbs, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); ESP_RETURN_ON_FALSE(atomic_load(&ae_ctlr->fsm) == ISP_FSM_INIT, ESP_ERR_INVALID_STATE, TAG, "controller not in init state"); -#if CONFIG_ISP_ISR_IRAM_SAEE +#if CONFIG_ISP_ISR_IRAM_SAFE if (cbs->on_env_statistics_done) { ESP_RETURN_ON_FALSE(esp_ptr_in_iram(cbs->on_env_statistics_done), ESP_ERR_INVALID_ARG, TAG, "on_env_statistics_done callback not in IRAM"); } diff --git a/components/esp_driver_isp/test_apps/isp/main/test_isp_driver.c b/components/esp_driver_isp/test_apps/isp/main/test_isp_driver.c index cf30bfde459..3e582cd8660 100644 --- a/components/esp_driver_isp/test_apps/isp/main/test_isp_driver.c +++ b/components/esp_driver_isp/test_apps/isp/main/test_isp_driver.c @@ -215,7 +215,7 @@ TEST_CASE("ISP CCM basic function", "[isp]") esp_isp_ccm_config_t ccm_cfg = { .matrix = { - {16.0, 0.0, 0.0}, + {17.0, 0.0, 0.0}, {0.0, 1.0, 0.0}, {0.0, 0.0, 1.0} }, @@ -228,7 +228,7 @@ TEST_CASE("ISP CCM basic function", "[isp]") TEST_ESP_ERR(ESP_ERR_INVALID_ARG, esp_isp_ccm_configure(isp_proc, &ccm_cfg)); // saturation case - ccm_cfg.matrix[0][0] = 5.0; + ccm_cfg.matrix[0][0] = 3.0; ccm_cfg.saturation = true; TEST_ESP_OK(esp_isp_ccm_configure(isp_proc, &ccm_cfg)); TEST_ESP_OK(esp_isp_ccm_enable(isp_proc)); diff --git a/components/esp_driver_isp/test_apps/isp/pytest_isp.py b/components/esp_driver_isp/test_apps/isp/pytest_isp.py index 3276e4112a4..a027a9357b0 100644 --- a/components/esp_driver_isp/test_apps/isp/pytest_isp.py +++ b/components/esp_driver_isp/test_apps/isp/pytest_isp.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD +# SPDX-FileCopyrightText: 2024-2026 Espressif Systems (Shanghai) CO LTD # SPDX-License-Identifier: CC0-1.0 import pytest from pytest_embedded import Dut @@ -20,16 +20,16 @@ def test_isp(dut: Dut) -> None: dut.run_all_single_board_cases() -# TODO: IDF-15006 -# @pytest.mark.generic -# @pytest.mark.esp32p4_rev1 -# @pytest.mark.parametrize( -# 'config', -# [ -# ('esp32p4_rev1'), -# ], -# indirect=True, -# ) -# @idf_parametrize('target', ['esp32p4'], indirect=['target']) -# def test_isp_esp32p4_rev2(dut: Dut) -> None: -# dut.run_all_single_board_cases() +@pytest.mark.camera +@pytest.mark.ov5647 +@pytest.mark.esp32p4_rev1 +@pytest.mark.parametrize( + 'config', + [ + ('esp32p4_rev1'), + ], + indirect=True, +) +@idf_parametrize('target', ['esp32p4'], indirect=['target']) +def test_isp_esp32p4_rev1(dut: Dut) -> None: + dut.run_all_single_board_cases() diff --git a/components/esp_driver_jpeg/jpeg_decode.c b/components/esp_driver_jpeg/jpeg_decode.c index ee161d29cd0..f894339f11c 100644 --- a/components/esp_driver_jpeg/jpeg_decode.c +++ b/components/esp_driver_jpeg/jpeg_decode.c @@ -140,6 +140,7 @@ esp_err_t jpeg_decoder_get_info(const uint8_t *in_buf, uint32_t inbuf_len, jpeg_ { ESP_RETURN_ON_FALSE(in_buf, ESP_ERR_INVALID_ARG, TAG, "jpeg decode input buffer is NULL"); ESP_RETURN_ON_FALSE(inbuf_len != 0, ESP_ERR_INVALID_ARG, TAG, "jpeg decode input buffer length is 0"); + ESP_RETURN_ON_FALSE(picture_info, ESP_ERR_INVALID_ARG, TAG, "jpeg decode picture_info is NULL"); jpeg_dec_header_info_t* header_info = (jpeg_dec_header_info_t*)heap_caps_calloc(1, sizeof(jpeg_dec_header_info_t), JPEG_MEM_ALLOC_CAPS); ESP_RETURN_ON_FALSE(header_info, ESP_ERR_NO_MEM, TAG, "no memory for picture info"); @@ -148,33 +149,85 @@ esp_err_t jpeg_decoder_get_info(const uint8_t *in_buf, uint32_t inbuf_len, jpeg_ header_info->header_size = 0; uint16_t height = 0; uint16_t width = 0; - uint8_t thischar = 0; - uint8_t lastchar = 0; uint8_t hivi = 0; uint8_t nf = 0; + bool sof_found = false; + esp_err_t ret = ESP_OK; - while (header_info->buffer_left) { - lastchar = thischar; - thischar = jpeg_get_bytes(header_info, 1); - uint16_t marker = (lastchar << 8 | thischar); - switch (marker) { - case JPEG_M_SOF0: + while (header_info->buffer_left >= 2) { + uint8_t b0 = jpeg_get_bytes(header_info, 1); + if (b0 != 0xFF) { + continue; + } + uint8_t b1 = jpeg_get_bytes(header_info, 1); + // A marker may be preceded by any number of 0xFF fill bytes + // (T.81 B.1.1.2). Consume the run of fill bytes so b1 lands on + // the marker code; e.g. "FF FF C0" must parse as marker 0xFFC0. + while (b1 == 0xFF) { + if (header_info->buffer_left < 1) { + break; + } + b1 = jpeg_get_bytes(header_info, 1); + } + if (b1 == 0x00 || b1 == 0xFF) { + continue; + } + uint16_t marker = (uint16_t)0xFF00u | b1; + + if (marker == JPEG_M_SOF0) { + // Need Lf(2)+P(1)+Y(2)+X(2)+Nf(1)+at least 3 bytes of the first component + if (header_info->buffer_left < 11) { + ESP_LOGE(TAG, "Truncated SOF0 segment"); + ret = ESP_ERR_INVALID_ARG; + goto out; + } jpeg_get_bytes(header_info, 2); jpeg_get_bytes(header_info, 1); height = jpeg_get_bytes(header_info, 2); width = jpeg_get_bytes(header_info, 2); - nf = jpeg_get_bytes(header_info, 1); - jpeg_get_bytes(header_info, 1); hivi = jpeg_get_bytes(header_info, 1); + sof_found = true; break; } - // This function only used for get width and height. So only read SOF marker is enough. - // Can be extended if picture information is extended. - if (marker == JPEG_M_SOF0) { + + // Standalone markers (no length field): SOI, EOI, RST0..RST7. + if (marker == JPEG_M_SOI || (b1 >= 0xD0 && b1 <= 0xD7)) { + continue; + } + if (marker == JPEG_M_EOI) { + // Reached end of image without SOF0; bail out. break; } + // SOS appearing before SOF0 is malformed for this query API. + if (marker == JPEG_M_SOS) { + ESP_LOGE(TAG, "SOS encountered before SOF0"); + ret = ESP_ERR_NOT_FOUND; + goto out; + } + + // All remaining markers (SOFx variants, DHT, DQT, DRI, APPn, COM, ...) + // carry a 2-byte big-endian length immediately after the marker byte. + if (header_info->buffer_left < 2) { + break; + } + uint16_t seg_len = jpeg_get_bytes(header_info, 2); + if (seg_len < 2 || header_info->buffer_left < (uint32_t)(seg_len - 2)) { + ESP_LOGE(TAG, "Truncated/invalid segment for marker 0x%04x, len=%u", marker, seg_len); + ret = ESP_ERR_INVALID_ARG; + goto out; + } + uint16_t to_skip = seg_len - 2; + header_info->buffer_offset += to_skip; + header_info->header_size += to_skip; + header_info->buffer_left -= to_skip; + } + + if (!sof_found) { + ESP_LOGE(TAG, "SOF0 marker not found"); + ret = ESP_ERR_NOT_FOUND; + goto out; } picture_info->height = height; @@ -193,15 +246,20 @@ esp_err_t jpeg_decoder_get_info(const uint8_t *in_buf, uint32_t inbuf_len, jpeg_ break; default: ESP_LOGE(TAG, "Sampling factor cannot be recognized"); - return ESP_ERR_INVALID_STATE; + ret = ESP_ERR_INVALID_STATE; + goto out; } - } - if (nf == 1) { + } else if (nf == 1) { picture_info->sample_method = JPEG_DOWN_SAMPLING_GRAY; + } else { + ESP_LOGE(TAG, "Unsupported number of frame components: %u", nf); + ret = ESP_ERR_INVALID_STATE; + goto out; } +out: free(header_info); - return ESP_OK; + return ret; } static bool _check_buffer_alignment(void *buffer, uint32_t buffer_size, uint32_t alignment) @@ -251,9 +309,13 @@ esp_err_t jpeg_decoder_process(jpeg_decoder_handle_t decoder_engine, const jpeg_ ESP_GOTO_ON_ERROR(jpeg_parse_header_info_to_hw(decoder_engine), err2, TAG, "write header info to hw failed"); ESP_GOTO_ON_ERROR(jpeg_dec_config_dma_descriptor(decoder_engine), err2, TAG, "config dma descriptor failed"); + // Validate the decoded size against the output buffer unconditionally. Computed in + // 64-bit to avoid uint32_t wrap-around, and not gated on out_size, otherwise a NULL + // out_size would skip the check and let the DMA write past decode_outbuf. + uint64_t real_size = (uint64_t)decoder_engine->header_info->process_h * decoder_engine->header_info->process_v * decoder_engine->bit_per_pixel / 8; + ESP_GOTO_ON_FALSE((real_size <= outbuf_size), ESP_ERR_INVALID_ARG, err2, TAG, "Given buffer size %" PRIu32 " is smaller than actual jpeg decode output size %" PRIu64, outbuf_size, real_size); if (out_size) { - *out_size = decoder_engine->header_info->process_h * decoder_engine->header_info->process_v * decoder_engine->bit_per_pixel / 8; - ESP_GOTO_ON_FALSE((*out_size <= outbuf_size), ESP_ERR_INVALID_ARG, err2, TAG, "Given buffer size % " PRId32 " is smaller than actual jpeg decode output size % " PRId32 "the height and width of output picture size will be adjusted to 16 bytes aligned automatically", outbuf_size, *out_size); + *out_size = (uint32_t)real_size; } dma2d_trans_config_t trans_desc = { @@ -728,7 +790,10 @@ static esp_err_t jpeg_parse_marker(jpeg_decoder_handle_t decoder_engine, const u jpeg_ll_set_picture_height(hal->dev, 0); jpeg_ll_set_picture_width(hal->dev, 0); - while (header_info->buffer_left) { + // Loop guard requires >=2 bytes so the 2-byte marker read can never underflow. + bool sof_found = false; + bool sos_found = false; + while (header_info->buffer_left >= 2) { uint8_t lastchar = jpeg_get_bytes(header_info, 1); uint8_t thischar = jpeg_get_bytes(header_info, 1); uint16_t marker = (lastchar << 8 | thischar); @@ -761,6 +826,7 @@ static esp_err_t jpeg_parse_marker(jpeg_decoder_handle_t decoder_engine, const u break; case JPEG_M_SOF0: ESP_RETURN_ON_ERROR(jpeg_parse_sof_marker(header_info), TAG, "deal sof marker failed"); + sof_found = true; break; case JPEG_M_SOF1: case JPEG_M_SOF2: @@ -784,16 +850,27 @@ static esp_err_t jpeg_parse_marker(jpeg_decoder_handle_t decoder_engine, const u break; case JPEG_M_SOS: ESP_RETURN_ON_ERROR(jpeg_parse_sos_marker(header_info), TAG, "deal sos marker failed"); + sos_found = true; break; case JPEG_M_INV: ESP_RETURN_ON_ERROR(jpeg_parse_inv_marker(header_info), TAG, "deal invalid marker failed"); break; + default: + // Reject unknown/unsupported markers instead of silently continuing. + ESP_LOGE(TAG, "Unsupported or unknown marker 0x%04x", marker); + return ESP_ERR_INVALID_ARG; } - if (marker == JPEG_M_SOS) { + if (sos_found) { break; } } + // SOF0 must precede SOS; without it nf/dimensions/mcu fields would remain zero + // from memset and the hardware would be configured with garbage. + ESP_RETURN_ON_FALSE(sof_found, ESP_ERR_INVALID_ARG, TAG, "SOF0 marker not found in JPEG stream"); + // The dispatcher must terminate via SOS; otherwise the stream is truncated/invalid. + ESP_RETURN_ON_FALSE(sos_found, ESP_ERR_INVALID_ARG, TAG, "SOS marker not found before end of stream"); + // Update information after parse marker finishes decoder_engine->header_info->buffer_left = decoder_engine->total_size - decoder_engine->header_info->header_size; diff --git a/components/esp_driver_jpeg/jpeg_parse_marker.c b/components/esp_driver_jpeg/jpeg_parse_marker.c index 5dd410fe21b..1cb041aea2f 100644 --- a/components/esp_driver_jpeg/jpeg_parse_marker.c +++ b/components/esp_driver_jpeg/jpeg_parse_marker.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2024-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -44,8 +44,10 @@ uint32_t jpeg_get_bytes(jpeg_dec_header_info_t *header_info, uint8_t num_bytes) esp_err_t jpeg_parse_appn_marker(jpeg_dec_header_info_t *header_info) { + // Guarantee the 2-byte length field is in-buffer before reading it. + ESP_RETURN_ON_FALSE(header_info->buffer_left >= 2, ESP_ERR_INVALID_ARG, TAG, "APPn marker truncated: missing length"); uint16_t skip_num = jpeg_get_bytes(header_info, 2); - ESP_RETURN_ON_FALSE(skip_num >= 2, ESP_ERR_INVALID_ARG, TAG, "Invalid APPn marker length: %"PRIu32, skip_num); + ESP_RETURN_ON_FALSE(skip_num >= 2, ESP_ERR_INVALID_ARG, TAG, "Invalid APPn marker length: %"PRIu16, skip_num); uint16_t bytes_to_skip = skip_num - 2; ESP_RETURN_ON_FALSE(header_info->buffer_left >= bytes_to_skip, ESP_ERR_INVALID_ARG, TAG, "APPn marker data underflow for buffer_left: %"PRIu32, header_info->buffer_left); header_info->buffer_offset += bytes_to_skip; @@ -57,10 +59,12 @@ esp_err_t jpeg_parse_appn_marker(jpeg_dec_header_info_t *header_info) esp_err_t jpeg_parse_com_marker(jpeg_dec_header_info_t *header_info) { + // Guarantee the 2-byte length field is in-buffer before reading it. + ESP_RETURN_ON_FALSE(header_info->buffer_left >= 2, ESP_ERR_INVALID_ARG, TAG, "COM marker truncated: missing length"); uint16_t skip_num = jpeg_get_bytes(header_info, 2); - ESP_RETURN_ON_FALSE(skip_num >= 2, ESP_ERR_INVALID_ARG, TAG, "Invalid COM marker length: %"PRIu32, skip_num); + ESP_RETURN_ON_FALSE(skip_num >= 2, ESP_ERR_INVALID_ARG, TAG, "Invalid COM marker length: %"PRIu16, skip_num); uint32_t bytes_to_skip = skip_num - 2; - ESP_RETURN_ON_FALSE(header_info->buffer_left >= bytes_to_skip, ESP_ERR_INVALID_ARG, TAG, "COM marker data underflow for header_size: %"PRIu32, header_info->buffer_left); + ESP_RETURN_ON_FALSE(header_info->buffer_left >= bytes_to_skip, ESP_ERR_INVALID_ARG, TAG, "COM marker data underflow for buffer_left: %"PRIu32, header_info->buffer_left); header_info->buffer_offset += bytes_to_skip; header_info->header_size += bytes_to_skip; header_info->buffer_left -= bytes_to_skip; @@ -72,30 +76,49 @@ esp_err_t jpeg_parse_dqt_marker(jpeg_dec_header_info_t *header_info) uint32_t n = 0, i = 0, prec = 0; uint32_t temp = 0; + // Guarantee the 2-byte length field is in-buffer before reading it. + ESP_RETURN_ON_FALSE(header_info->buffer_left >= 2, ESP_ERR_INVALID_ARG, TAG, "DQT marker truncated: missing length"); uint16_t length_num = jpeg_get_bytes(header_info, 2); - ESP_RETURN_ON_FALSE(length_num >= 2, ESP_ERR_INVALID_ARG, TAG, "Invalid DQT marker length: %"PRIu32, length_num); + ESP_RETURN_ON_FALSE(length_num >= 2, ESP_ERR_INVALID_ARG, TAG, "Invalid DQT marker length: %"PRIu16, length_num); length_num -= 2; + ESP_RETURN_ON_FALSE(header_info->buffer_left >= length_num, ESP_ERR_INVALID_ARG, TAG, "DQT marker truncated: buffer_left=%"PRIu32" needed=%"PRIu16, header_info->buffer_left, length_num); while (length_num) { + // Need at least 1 byte for the table identifier (Pq | Tq) + ESP_RETURN_ON_FALSE(length_num >= 1, ESP_ERR_INVALID_ARG, TAG, "DQT marker length error before reading id"); n = jpeg_get_bytes(header_info, 1); + length_num -= 1; prec = n >> 4; n &= 0x0F; - ESP_RETURN_ON_FALSE(length_num >= 1, ESP_ERR_INVALID_ARG, TAG, "DQT marker length error: %"PRIu32, length_num); - length_num -= 1; - // read quantization entries, in zig-zag order + ESP_RETURN_ON_FALSE(n < JPEG_COMPONENT_NUMBER_MAX, ESP_ERR_INVALID_ARG, TAG, "DQT marker: invalid quantization table id %"PRIu32, n); + // Pq must be 0 (8-bit) or 1 (16-bit); other values are reserved and unsupported + ESP_RETURN_ON_FALSE(prec <= 1, ESP_ERR_INVALID_ARG, TAG, "DQT marker: invalid precision %"PRIu32, prec); + + // Each table needs 64 * (prec+1) bytes; verify upfront before consuming + const uint32_t entry_bytes = 64 * (prec + 1); + ESP_RETURN_ON_FALSE(length_num >= entry_bytes, ESP_ERR_INVALID_ARG, TAG, "DQT marker truncated: need %"PRIu32" remaining %"PRIu16, entry_bytes, length_num); + + // Read quantization entries, in zig-zag order for (i = 0; i < 64; i++) { temp = jpeg_get_bytes(header_info, 1); - ESP_RETURN_ON_FALSE(length_num >= 1, ESP_ERR_INVALID_ARG, TAG, "DQT marker length error: %"PRIu32, length_num); length_num -= 1; if (prec) { temp = (temp << 8) + jpeg_get_bytes(header_info, 1); - ESP_RETURN_ON_FALSE(length_num >= 1, ESP_ERR_INVALID_ARG, TAG, "DQT marker length error: %"PRIu32, length_num); length_num -= 1; } header_info->qt_tbl[n][zigzag_arr[i]] = temp; } - header_info->qt_tbl_num++; + + // Only count distinct Tq IDs; a repeated ID (in this segment or an + // earlier DQT segment) rewrites qt_tbl[n] but must not advance + // qt_tbl_num past the number of populated slots. + const uint8_t tq_bit = (uint8_t)(1 << n); + if ((header_info->qt_tbl_seen_mask & tq_bit) == 0) { + ESP_RETURN_ON_FALSE(header_info->qt_tbl_num < JPEG_COMPONENT_NUMBER_MAX, ESP_ERR_INVALID_ARG, TAG, "DQT marker: too many quantization tables"); + header_info->qt_tbl_seen_mask |= tq_bit; + header_info->qt_tbl_num++; + } } return ESP_OK; @@ -104,7 +127,15 @@ esp_err_t jpeg_parse_dqt_marker(jpeg_dec_header_info_t *header_info) esp_err_t jpeg_parse_sof_marker(jpeg_dec_header_info_t *header_info) { - jpeg_get_bytes(header_info, 2); + // SOF segment layout (excluding the 0xFFCx marker bytes): + // Lf(2) + P(1) + Y(2) + X(2) + Nf(1) + Nf * [Ci(1) + Hi|Vi(1) + Tqi(1)] + // Guarantee the 2-byte length field is in-buffer before reading it. + ESP_RETURN_ON_FALSE(header_info->buffer_left >= 2, ESP_ERR_INVALID_ARG, TAG, "SOF marker truncated: missing length"); + uint16_t lf = jpeg_get_bytes(header_info, 2); + ESP_RETURN_ON_FALSE(lf >= 8, ESP_ERR_INVALID_ARG, TAG, "Invalid SOF marker length: %"PRIu16, lf); + uint16_t remaining = lf - 2; + ESP_RETURN_ON_FALSE(header_info->buffer_left >= remaining, ESP_ERR_INVALID_ARG, TAG, "SOF marker truncated: buffer_left=%"PRIu32" need=%"PRIu16, header_info->buffer_left, remaining); + if (jpeg_get_bytes(header_info, 1) != 8) { ESP_LOGE(TAG, "Sample precision is not 8"); return ESP_ERR_INVALID_STATE; @@ -118,17 +149,27 @@ esp_err_t jpeg_parse_sof_marker(jpeg_dec_header_info_t *header_info) header_info->origin_h = width; header_info->process_h = width; + // Reject zero dimensions to avoid division-by-zero / degenerate buffers downstream + ESP_RETURN_ON_FALSE(width != 0 && height != 0, ESP_ERR_INVALID_ARG, TAG, "Invalid picture size %ux%u", (unsigned)width, (unsigned)height); + + // The 2D-DMA address fields are 14-bit, so each dimension must fit in JPEG_DMA2D_MAX_SIZE. + // This also bounds process_h * process_v and prevents the output-size integer overflow downstream. + ESP_RETURN_ON_FALSE(width <= JPEG_DMA2D_MAX_SIZE && height <= JPEG_DMA2D_MAX_SIZE, ESP_ERR_INVALID_ARG, TAG, "Picture length or height size %ux%u exceeds max %u", (unsigned)width, (unsigned)height, JPEG_DMA2D_MAX_SIZE); + if ((width * height % 8) != 0) { ESP_LOGE(TAG, "Picture sizes not divisible by 8 are not supported"); - return ESP_ERR_INVALID_STATE; + return ESP_ERR_NOT_SUPPORTED; } uint8_t nf = jpeg_get_bytes(header_info, 1); - if (nf >= 4 || nf == 0) { - ESP_LOGE(TAG, "Only frame less or equal than 4 is supported."); - return ESP_ERR_INVALID_STATE; + // Hardware supports 1..3 components (gray / YUV). nf must fit JPEG_COMPONENT_NUMBER_MAX-sized arrays. + if (nf == 0 || nf >= JPEG_COMPONENT_NUMBER_MAX) { + ESP_LOGE(TAG, "Only frame less than %d (and non-zero) is supported, got %u", JPEG_COMPONENT_NUMBER_MAX, nf); + return ESP_ERR_NOT_SUPPORTED; } + ESP_RETURN_ON_FALSE(lf >= 8 + 3 * nf, ESP_ERR_INVALID_ARG, TAG, "SOF length %"PRIu16" too small for %u components", lf, nf); + header_info->nf = nf; for (int i = 0; i < nf; i++) { @@ -137,8 +178,13 @@ esp_err_t jpeg_parse_sof_marker(jpeg_dec_header_info_t *header_info) header_info->vi[i] = header_info->hivi[i] & 0x0f; header_info->hi[i] = (header_info->hivi[i] & 0xf0) >> 4; header_info->qtid[i] = jpeg_get_bytes(header_info, 1); + // qtid selects the quantization table that the hardware will use; bound it. + ESP_RETURN_ON_FALSE(header_info->qtid[i] < JPEG_COMPONENT_NUMBER_MAX, ESP_ERR_INVALID_ARG, TAG, "SOF: invalid Tq[%d]=%u", i, header_info->qtid[i]); } + // Guard against zero sampling factors before we use them as a divisor below. + ESP_RETURN_ON_FALSE(header_info->hi[0] != 0 && header_info->vi[0] != 0, ESP_ERR_INVALID_ARG, TAG, "SOF: zero sampling factor H=%u V=%u", header_info->hi[0], header_info->vi[0]); + // Set MCU block pixel according to factor. (For 3 components, we only use Y factor) header_info->mcux = header_info->hi[0] * 8; header_info->mcuy = header_info->vi[0] * 8; @@ -151,34 +197,52 @@ esp_err_t jpeg_parse_sof_marker(jpeg_dec_header_info_t *header_info) header_info->process_h = ((header_info->origin_h / header_info->mcux) + 1) * header_info->mcux; } + // process_h/process_v are written into the 14-bit 2D-DMA descriptor fields, so the + // MCU-rounded values (which may exceed the raw dimensions) must also fit the limit. + ESP_RETURN_ON_FALSE(header_info->process_h <= JPEG_DMA2D_MAX_SIZE && header_info->process_v <= JPEG_DMA2D_MAX_SIZE, ESP_ERR_INVALID_ARG, TAG, "MCU-aligned size %"PRIu32"x%"PRIu32" exceeds max %u", header_info->process_h, header_info->process_v, JPEG_DMA2D_MAX_SIZE); + return ESP_OK; } esp_err_t jpeg_parse_dht_marker(jpeg_dec_header_info_t *header_info) { // Recording num_left in DHT sector, not including length bytes (2 bytes). + // Guarantee the 2-byte length field is in-buffer before reading it. + ESP_RETURN_ON_FALSE(header_info->buffer_left >= 2, ESP_ERR_INVALID_ARG, TAG, "DHT marker truncated: missing length"); uint16_t raw_length = jpeg_get_bytes(header_info, 2); // Check for integer underflow before subtraction - ESP_RETURN_ON_FALSE(raw_length >= 2, ESP_ERR_INVALID_ARG, TAG, "Invalid DHT marker length: %"PRIu32, raw_length); + ESP_RETURN_ON_FALSE(raw_length >= 2, ESP_ERR_INVALID_ARG, TAG, "Invalid DHT marker length: %"PRIu16, raw_length); uint16_t num_left = raw_length - 2; + ESP_RETURN_ON_FALSE(header_info->buffer_left >= num_left, ESP_ERR_INVALID_ARG, TAG, "DHT marker truncated: buffer_left=%"PRIu32" needed=%"PRIu16, header_info->buffer_left, num_left); + while (num_left) { uint32_t np = 0; + uint8_t tc = 0; + uint8_t th = 0; + + // Need 1 byte (Tc | Th) + 16 bytes (Li) at minimum before reading values + ESP_RETURN_ON_FALSE(num_left >= (1 + JPEG_HUFFMAN_BITS_LEN_TABLE_LEN), ESP_ERR_INVALID_ARG, TAG, "DHT marker truncated: num_left=%"PRIu16, num_left); // Get information of huffman table header_info->huffinfo.info = jpeg_get_bytes(header_info, 1); + tc = header_info->huffinfo.type; + th = header_info->huffinfo.id; + + ESP_RETURN_ON_FALSE(tc < DHT_TC_NUM, ESP_ERR_INVALID_ARG, TAG, "DHT marker: invalid table class Tc=%u", tc); + ESP_RETURN_ON_FALSE(th < DHT_TH_NUM, ESP_ERR_INVALID_ARG, TAG, "DHT marker: invalid table id Th=%u", th); for (int i = 0; i < JPEG_HUFFMAN_BITS_LEN_TABLE_LEN; i++) { - header_info->huffbits[header_info->huffinfo.type][header_info->huffinfo.id][i] = jpeg_get_bytes(header_info, 1); + header_info->huffbits[tc][th][i] = jpeg_get_bytes(header_info, 1); // Record number of patterns. - np += header_info->huffbits[header_info->huffinfo.type][header_info->huffinfo.id][i]; + np += header_info->huffbits[tc][th][i]; + } + ESP_RETURN_ON_FALSE(np <= JPEG_HUFFMAN_AC_VALUE_TABLE_LEN, ESP_ERR_INVALID_ARG, TAG, "DHT marker: huffcode count %"PRIu32" exceeds table size", np); + ESP_RETURN_ON_FALSE(num_left >= (1u + JPEG_HUFFMAN_BITS_LEN_TABLE_LEN + np), ESP_ERR_INVALID_ARG, TAG, "DHT marker truncated for huffcode: num_left=%"PRIu16" need=%"PRIu32, num_left, (uint32_t)(1u + JPEG_HUFFMAN_BITS_LEN_TABLE_LEN + np)); + + for (uint32_t i = 0; i < np; i++) { + header_info->huffcode[tc][th][i] = jpeg_get_bytes(header_info, 1); } - for (int i = 0; i < np; i++) { - header_info->huffcode[header_info->huffinfo.type][header_info->huffinfo.id][i] = jpeg_get_bytes(header_info, 1); - } - - // Check for integer underflow before subtraction - ESP_RETURN_ON_FALSE(num_left >= (JPEG_HUFFMAN_BITS_LEN_TABLE_LEN + np + 1), ESP_ERR_INVALID_ARG, TAG, "DHT marker data underflow after parsing huffcode: %"PRIu32, num_left); num_left -= (1 + JPEG_HUFFMAN_BITS_LEN_TABLE_LEN + np); } @@ -189,11 +253,14 @@ esp_err_t jpeg_parse_dht_marker(jpeg_dec_header_info_t *header_info) esp_err_t jpeg_parse_dri_marker(jpeg_dec_header_info_t *header_info) { + // Guarantee the 2-byte length field is in-buffer before reading it. + ESP_RETURN_ON_FALSE(header_info->buffer_left >= 2, ESP_ERR_INVALID_ARG, TAG, "DRI marker truncated: missing length"); uint16_t lr = jpeg_get_bytes(header_info, 2); if (lr != 4) { ESP_LOGE(TAG, "DRI marker got but stream length is insufficient, the length you got is %" PRIu16, lr); return ESP_ERR_INVALID_SIZE; } + ESP_RETURN_ON_FALSE(header_info->buffer_left >= 2, ESP_ERR_INVALID_ARG, TAG, "DRI marker truncated, buffer_left=%"PRIu32, header_info->buffer_left); header_info->ri = jpeg_get_bytes(header_info, 2); return ESP_OK; } diff --git a/components/esp_driver_jpeg/jpeg_private.h b/components/esp_driver_jpeg/jpeg_private.h index 741c380164c..3a038ce321b 100644 --- a/components/esp_driver_jpeg/jpeg_private.h +++ b/components/esp_driver_jpeg/jpeg_private.h @@ -82,6 +82,7 @@ typedef struct { uint8_t mcux; // the best value of minimum coding unit horizontal unit uint8_t mcuy; // minimum coding unit vertical unit uint8_t qt_tbl_num; // quantization table number + uint8_t qt_tbl_seen_mask; // bit i set => qt_tbl[i] populated by a DQT entry uint32_t qt_tbl[JPEG_COMPONENT_NUMBER_MAX][JPEG_QUANTIZATION_TABLE_LEN]; // quantization table content [id] uint8_t nf; // number of frames uint8_t ci[JPEG_COMPONENT_NUMBER_MAX]; // Component identifier. diff --git a/components/esp_driver_jpeg/test_apps/jpeg_test_apps/main/test_jpeg_decode.c b/components/esp_driver_jpeg/test_apps/jpeg_test_apps/main/test_jpeg_decode.c index 19a2bcc0855..05793930afe 100644 --- a/components/esp_driver_jpeg/test_apps/jpeg_test_apps/main/test_jpeg_decode.c +++ b/components/esp_driver_jpeg/test_apps/jpeg_test_apps/main/test_jpeg_decode.c @@ -133,3 +133,112 @@ TEST_CASE("JPEG decode image without Huffman table JPEG->RGB picture", "[jpeg]") free(tx_buf_no_huff); TEST_ESP_OK(jpeg_del_decoder_engine(jpgd_handle)); } + +// Malformed JPEG used as a regression guard for the DQT index OOB write. +// Layout: SOI, then a DQT segment whose table id (Tq) is 4 (> 3). A +// non-hardened parser would index qt_tbl[4] and smash 256 bytes of stack; +// the hardened parser must reject it and return an error without crashing. +static const uint8_t s_malformed_dqt_jpg[] = { + 0xFF, 0xD8, // SOI + 0xFF, 0xDB, // DQT + 0x00, 0x43, // Lq = 67 (2 + 1 id + 64 table bytes) + 0x04, // Pq=0, Tq=4 -> out-of-range table id + // 64 quantization-table bytes (content irrelevant; never consumed + // because the id is rejected first, but kept so the segment length + // is internally consistent and passes the buffer_left check). + [7 ... 70] = 0x01, +}; + +TEST_CASE("JPEG decode rejects malformed DQT index without crashing", "[jpeg]") +{ + jpeg_decoder_handle_t jpgd_handle; + + jpeg_decode_engine_cfg_t decode_eng_cfg = { + .intr_priority = 0, + .timeout_ms = 80, + }; + + jpeg_decode_cfg_t decode_cfg = { + .output_format = JPEG_DECODE_OUT_FORMAT_RGB565, + }; + + jpeg_decode_memory_alloc_cfg_t rx_mem_cfg = { + .buffer_direction = JPEG_DEC_ALLOC_OUTPUT_BUFFER, + }; + + jpeg_decode_memory_alloc_cfg_t tx_mem_cfg = { + .buffer_direction = JPEG_DEC_ALLOC_INPUT_BUFFER, + }; + + size_t rx_buffer_size; + uint8_t *rx_buf = (uint8_t*)jpeg_alloc_decoder_mem(64 * 64 * 2, &rx_mem_cfg, &rx_buffer_size); + + size_t tx_buffer_size; + uint8_t *tx_buf = (uint8_t*)jpeg_alloc_decoder_mem(sizeof(s_malformed_dqt_jpg), &tx_mem_cfg, &tx_buffer_size); + memcpy(tx_buf, s_malformed_dqt_jpg, sizeof(s_malformed_dqt_jpg)); + + TEST_ESP_OK(jpeg_new_decoder_engine(&decode_eng_cfg, &jpgd_handle)); + + uint32_t out_size = 0; + esp_err_t ret = jpeg_decoder_process(jpgd_handle, &decode_cfg, tx_buf, + sizeof(s_malformed_dqt_jpg), rx_buf, + rx_buffer_size, &out_size); + TEST_ASSERT_NOT_EQUAL(ESP_OK, ret); + + free(rx_buf); + free(tx_buf); + TEST_ESP_OK(jpeg_del_decoder_engine(jpgd_handle)); +} + +// Avoiding picture size is so large that exceeds the jpeg&dma limit. +static const uint8_t s_malformed_sof_mcu_round_jpg[] = { + 0xFF, 0xD8, // SOI + 0xFF, 0xC0, // SOF0 + 0x00, 0x0B, // Lf = 11 + 0x08, // P = 8 + 0x00, 0x10, // Y (height) = 16 + 0x3F, 0xFF, // X (width) = 16383 + 0x01, // Nf = 1 + 0x01, 0x22, 0x00, // component 1: Ci=1, H=2 V=2, Tq=0 +}; + +TEST_CASE("JPEG decode rejects MCU-rounded dimensions exceeding DMA limit", "[jpeg]") +{ + jpeg_decoder_handle_t jpgd_handle; + + jpeg_decode_engine_cfg_t decode_eng_cfg = { + .intr_priority = 0, + .timeout_ms = 80, + }; + + jpeg_decode_cfg_t decode_cfg = { + .output_format = JPEG_DECODE_OUT_FORMAT_RGB565, + }; + + jpeg_decode_memory_alloc_cfg_t rx_mem_cfg = { + .buffer_direction = JPEG_DEC_ALLOC_OUTPUT_BUFFER, + }; + + jpeg_decode_memory_alloc_cfg_t tx_mem_cfg = { + .buffer_direction = JPEG_DEC_ALLOC_INPUT_BUFFER, + }; + + size_t rx_buffer_size; + uint8_t *rx_buf = (uint8_t*)jpeg_alloc_decoder_mem(128, &rx_mem_cfg, &rx_buffer_size); + + size_t tx_buffer_size; + uint8_t *tx_buf = (uint8_t*)jpeg_alloc_decoder_mem(sizeof(s_malformed_sof_mcu_round_jpg), &tx_mem_cfg, &tx_buffer_size); + memcpy(tx_buf, s_malformed_sof_mcu_round_jpg, sizeof(s_malformed_sof_mcu_round_jpg)); + + TEST_ESP_OK(jpeg_new_decoder_engine(&decode_eng_cfg, &jpgd_handle)); + + uint32_t out_size = 0; + esp_err_t ret = jpeg_decoder_process(jpgd_handle, &decode_cfg, tx_buf, + sizeof(s_malformed_sof_mcu_round_jpg), rx_buf, + rx_buffer_size, &out_size); + TEST_ASSERT_NOT_EQUAL(ESP_OK, ret); + + free(rx_buf); + free(tx_buf); + TEST_ESP_OK(jpeg_del_decoder_engine(jpgd_handle)); +} diff --git a/components/esp_driver_jpeg/test_apps/jpeg_test_apps/main/test_jpeg_encode.c b/components/esp_driver_jpeg/test_apps/jpeg_test_apps/main/test_jpeg_encode.c index f1612fe7c54..fc02c785e1f 100644 --- a/components/esp_driver_jpeg/test_apps/jpeg_test_apps/main/test_jpeg_encode.c +++ b/components/esp_driver_jpeg/test_apps/jpeg_test_apps/main/test_jpeg_encode.c @@ -57,11 +57,11 @@ TEST_CASE("JPEG encode performance test for 480*640 RGB->YUV picture", "[jpeg]") }; jpeg_encode_memory_alloc_cfg_t rx_mem_cfg = { - .buffer_direction = JPEG_DEC_ALLOC_OUTPUT_BUFFER, + .buffer_direction = JPEG_ENC_ALLOC_OUTPUT_BUFFER, }; jpeg_encode_memory_alloc_cfg_t tx_mem_cfg = { - .buffer_direction = JPEG_DEC_ALLOC_INPUT_BUFFER, + .buffer_direction = JPEG_ENC_ALLOC_INPUT_BUFFER, }; size_t rx_buffer_size = 0; diff --git a/components/esp_driver_ppa/src/ppa_blend.c b/components/esp_driver_ppa/src/ppa_blend.c index e3cd6816c04..44a1470b427 100644 --- a/components/esp_driver_ppa/src/ppa_blend.c +++ b/components/esp_driver_ppa/src/ppa_blend.c @@ -152,6 +152,8 @@ bool ppa_blend_transaction_on_picked(uint32_t num_chans, const dma2d_trans_chann ppa_ll_blend_set_tx_yuv_range(platform->hal.dev, blend_trans_desc->out.yuv_range); ppa_ll_blend_set_tx_rgb2yuv_std(platform->hal.dev, blend_trans_desc->out.yuv_std); } + // For YUV420/YUV422 background input and output, blend_tx_size.blend_hb/vb must be set to make the Blending engine and 2D-DMA work properly + ppa_ll_blend_set_block_size(platform->hal.dev, blend_trans_desc->in_bg.block_w, blend_trans_desc->in_bg.block_h); // in_bg.block_w == in_fg.block_w && in_bg.block_h == in_fg.block_h // Color keying color_pixel_rgb888_data_t rgb888_min = {.b = 0x00, .g = 0x00, .r = 0x00}; diff --git a/components/esp_driver_ppa/src/ppa_fill.c b/components/esp_driver_ppa/src/ppa_fill.c index 70fe4278d91..29fc939d8d8 100644 --- a/components/esp_driver_ppa/src/ppa_fill.c +++ b/components/esp_driver_ppa/src/ppa_fill.c @@ -74,8 +74,9 @@ bool ppa_fill_transaction_on_picked(uint32_t num_chans, const dma2d_trans_channe dma2d_start(dma2d_rx_chan); // Configure PPA Blending engine - ppa_ll_blend_configure_filling_block(platform->hal.dev, fill_trans_desc->out.fill_cm, (void *)&fill_trans_desc->fill_color_val, fill_trans_desc->fill_block_w, fill_trans_desc->fill_block_h); + ppa_ll_blend_configure_filling_block_color(platform->hal.dev, fill_trans_desc->out.fill_cm, (void *)&fill_trans_desc->fill_color_val); ppa_ll_blend_set_tx_color_mode(platform->hal.dev, fill_trans_desc->out.fill_cm); + ppa_ll_blend_set_block_size(platform->hal.dev, fill_trans_desc->fill_block_w, fill_trans_desc->fill_block_h); ppa_ll_blend_start(platform->hal.dev, PPA_LL_BLEND_TRANS_MODE_FILL); diff --git a/components/esp_driver_ppa/test_apps/main/CMakeLists.txt b/components/esp_driver_ppa/test_apps/main/CMakeLists.txt index eea601d8b18..7bfd522225f 100644 --- a/components/esp_driver_ppa/test_apps/main/CMakeLists.txt +++ b/components/esp_driver_ppa/test_apps/main/CMakeLists.txt @@ -1,5 +1,5 @@ set(srcs "test_app_main.c" - "test_ppa.c") + "test_ppa.cpp") # In order for the cases defined by `TEST_CASE` to be linked into the final elf, # the component can be registered as WHOLE_ARCHIVE diff --git a/components/esp_driver_ppa/test_apps/main/test_ppa.c b/components/esp_driver_ppa/test_apps/main/test_ppa.cpp similarity index 56% rename from components/esp_driver_ppa/test_apps/main/test_ppa.c rename to components/esp_driver_ppa/test_apps/main/test_ppa.cpp index b9093157b02..77a7a01555e 100644 --- a/components/esp_driver_ppa/test_apps/main/test_ppa.c +++ b/components/esp_driver_ppa/test_apps/main/test_ppa.cpp @@ -32,9 +32,9 @@ TEST_CASE("ppa_client_do_ppa_operation", "[PPA]") uint32_t buf_1_size = ALIGN_UP(w * h * color_hal_pixel_format_fourcc_get_bit_depth(buf_1_color_type_id) / 8, 64); uint32_t buf_2_size = ALIGN_UP(w * h * color_hal_pixel_format_fourcc_get_bit_depth(buf_2_color_type_id) / 8, 64); - uint8_t *buf_1 = heap_caps_aligned_calloc(4, buf_1_size, sizeof(uint8_t), MALLOC_CAP_SPIRAM | MALLOC_CAP_DMA); // cache alignment is implicited by MALLOC_CAP_DMA + uint8_t *buf_1 = static_cast(heap_caps_aligned_calloc(4, buf_1_size, sizeof(uint8_t), MALLOC_CAP_SPIRAM | MALLOC_CAP_DMA)); // cache alignment is implicited by MALLOC_CAP_DMA TEST_ASSERT_NOT_NULL(buf_1); - uint8_t *buf_2 = heap_caps_aligned_calloc(4, buf_2_size, sizeof(uint8_t), MALLOC_CAP_SPIRAM | MALLOC_CAP_DMA); + uint8_t *buf_2 = static_cast(heap_caps_aligned_calloc(4, buf_2_size, sizeof(uint8_t), MALLOC_CAP_SPIRAM | MALLOC_CAP_DMA)); TEST_ASSERT_NOT_NULL(buf_2); // Register different types of PPA clients @@ -42,9 +42,8 @@ TEST_CASE("ppa_client_do_ppa_operation", "[PPA]") ppa_client_handle_t ppa_client_blend_handle; ppa_client_handle_t ppa_client_fill_handle_a; ppa_client_handle_t ppa_client_fill_handle_b; - ppa_client_config_t ppa_client_config = { - .oper_type = PPA_OPERATION_SRM, - }; + ppa_client_config_t ppa_client_config = {}; + ppa_client_config.oper_type = PPA_OPERATION_SRM; TEST_ESP_OK(ppa_register_client(&ppa_client_config, &ppa_client_srm_handle)); ppa_client_config.oper_type = PPA_OPERATION_BLEND; TEST_ESP_OK(ppa_register_client(&ppa_client_config, &ppa_client_blend_handle)); @@ -52,30 +51,26 @@ TEST_CASE("ppa_client_do_ppa_operation", "[PPA]") TEST_ESP_OK(ppa_register_client(&ppa_client_config, &ppa_client_fill_handle_a)); TEST_ESP_OK(ppa_register_client(&ppa_client_config, &ppa_client_fill_handle_b)); - ppa_srm_oper_config_t srm_oper_config = { - .in.buffer = buf_1, - .in.pic_w = w, - .in.pic_h = h, - .in.block_w = w, - .in.block_h = h, - .in.block_offset_x = 0, - .in.block_offset_y = 0, - .in.srm_cm = buf_1_color_type_id, - - .out.buffer = buf_2, - .out.buffer_size = buf_2_size, - .out.pic_w = w, - .out.pic_h = h, - .out.block_offset_x = 0, - .out.block_offset_y = 0, - .out.srm_cm = buf_2_color_type_id, - - .rotation_angle = PPA_SRM_ROTATION_ANGLE_0, - .scale_x = 1.0, - .scale_y = 1.0, - - .mode = PPA_TRANS_MODE_BLOCKING, - }; + ppa_srm_oper_config_t srm_oper_config = {}; + srm_oper_config.in.buffer = buf_1; + srm_oper_config.in.pic_w = w; + srm_oper_config.in.pic_h = h; + srm_oper_config.in.block_w = w; + srm_oper_config.in.block_h = h; + srm_oper_config.in.block_offset_x = 0; + srm_oper_config.in.block_offset_y = 0; + srm_oper_config.in.srm_cm = static_cast(buf_1_color_type_id); + srm_oper_config.out.buffer = buf_2; + srm_oper_config.out.buffer_size = buf_2_size; + srm_oper_config.out.pic_w = w; + srm_oper_config.out.pic_h = h; + srm_oper_config.out.block_offset_x = 0; + srm_oper_config.out.block_offset_y = 0; + srm_oper_config.out.srm_cm = static_cast(buf_2_color_type_id); + srm_oper_config.rotation_angle = PPA_SRM_ROTATION_ANGLE_0; + srm_oper_config.scale_x = 1.0; + srm_oper_config.scale_y = 1.0; + srm_oper_config.mode = PPA_TRANS_MODE_BLOCKING; // A SRM client can request to do a SRM operation if (!esp_efuse_is_flash_encryption_enabled()) { TEST_ESP_OK(ppa_do_scale_rotate_mirror(ppa_client_srm_handle, &srm_oper_config)); @@ -83,57 +78,48 @@ TEST_CASE("ppa_client_do_ppa_operation", "[PPA]") // A non-SRM client can not request to do a SRM operation TEST_ESP_ERR(ESP_ERR_INVALID_ARG, ppa_do_scale_rotate_mirror(ppa_client_blend_handle, &srm_oper_config)); - ppa_blend_oper_config_t blend_oper_config = { - .in_bg.buffer = buf_1, - .in_bg.pic_w = w, - .in_bg.pic_h = h, - .in_bg.block_w = w, - .in_bg.block_h = h, - .in_bg.block_offset_x = 0, - .in_bg.block_offset_y = 0, - .in_bg.blend_cm = buf_1_color_type_id, - - .in_fg.buffer = buf_2, - .in_fg.pic_w = w, - .in_fg.pic_h = h, - .in_fg.block_w = w, - .in_fg.block_h = h, - .in_fg.block_offset_x = 0, - .in_fg.block_offset_y = 0, - .in_fg.blend_cm = buf_2_color_type_id, - - .out.buffer = buf_1, - .out.buffer_size = buf_1_size, - .out.pic_w = w, - .out.pic_h = h, - .out.block_offset_x = 0, - .out.block_offset_y = 0, - .out.blend_cm = buf_1_color_type_id, - - .mode = PPA_TRANS_MODE_BLOCKING, - }; + ppa_blend_oper_config_t blend_oper_config = {}; + blend_oper_config.in_bg.buffer = buf_1; + blend_oper_config.in_bg.pic_w = w; + blend_oper_config.in_bg.pic_h = h; + blend_oper_config.in_bg.block_w = w; + blend_oper_config.in_bg.block_h = h; + blend_oper_config.in_bg.block_offset_x = 0; + blend_oper_config.in_bg.block_offset_y = 0; + blend_oper_config.in_bg.blend_cm = static_cast(buf_1_color_type_id); + blend_oper_config.in_fg.buffer = buf_2; + blend_oper_config.in_fg.pic_w = w; + blend_oper_config.in_fg.pic_h = h; + blend_oper_config.in_fg.block_w = w; + blend_oper_config.in_fg.block_h = h; + blend_oper_config.in_fg.block_offset_x = 0; + blend_oper_config.in_fg.block_offset_y = 0; + blend_oper_config.in_fg.blend_cm = static_cast(buf_2_color_type_id); + blend_oper_config.out.buffer = buf_1; + blend_oper_config.out.buffer_size = buf_1_size; + blend_oper_config.out.pic_w = w; + blend_oper_config.out.pic_h = h; + blend_oper_config.out.block_offset_x = 0; + blend_oper_config.out.block_offset_y = 0; + blend_oper_config.out.blend_cm = static_cast(buf_1_color_type_id); + blend_oper_config.mode = PPA_TRANS_MODE_BLOCKING; // A blend client can request to do a blend operation TEST_ESP_OK(ppa_do_blend(ppa_client_blend_handle, &blend_oper_config)); // A non-blend client can not request to do a blend operation TEST_ESP_ERR(ESP_ERR_INVALID_ARG, ppa_do_blend(ppa_client_fill_handle_b, &blend_oper_config)); - ppa_fill_oper_config_t fill_oper_config = { - .out.buffer = buf_1, - .out.buffer_size = buf_1_size, - .out.pic_w = w, - .out.pic_h = h, - .out.block_offset_x = 0, - .out.block_offset_y = 0, - .out.fill_cm = buf_1_color_type_id, - - .fill_block_w = w, - .fill_block_h = h, - .fill_argb_color = { - .val = 0xFF00FF00, - }, - - .mode = PPA_TRANS_MODE_NON_BLOCKING, - }; + ppa_fill_oper_config_t fill_oper_config = {}; + fill_oper_config.out.buffer = buf_1; + fill_oper_config.out.buffer_size = buf_1_size; + fill_oper_config.out.pic_w = w; + fill_oper_config.out.pic_h = h; + fill_oper_config.out.block_offset_x = 0; + fill_oper_config.out.block_offset_y = 0; + fill_oper_config.out.fill_cm = static_cast(buf_1_color_type_id); + fill_oper_config.fill_block_w = w; + fill_oper_config.fill_block_h = h; + fill_oper_config.fill_argb_color.val = 0xFF00FF00; + fill_oper_config.mode = PPA_TRANS_MODE_NON_BLOCKING; // A fill client can request to do a fill operation TEST_ESP_OK(ppa_do_fill(ppa_client_fill_handle_a, &fill_oper_config)); // Another fill client can also request another fill operation at the same time @@ -175,53 +161,47 @@ TEST_CASE("ppa_pending_transactions_in_queue", "[PPA]") uint32_t buf_1_size = w * h * color_hal_pixel_format_fourcc_get_bit_depth(buf_1_color_type_id) / 8; uint32_t buf_2_size = ALIGN_UP(w * h * color_hal_pixel_format_fourcc_get_bit_depth(buf_2_color_type_id) / 8, 64); - uint8_t *buf_1 = heap_caps_aligned_calloc(4, buf_1_size, sizeof(uint8_t), MALLOC_CAP_SPIRAM | MALLOC_CAP_DMA); + uint8_t *buf_1 = static_cast(heap_caps_aligned_calloc(4, buf_1_size, sizeof(uint8_t), MALLOC_CAP_SPIRAM | MALLOC_CAP_DMA)); TEST_ASSERT_NOT_NULL(buf_1); - uint8_t *buf_2 = heap_caps_aligned_calloc(4, buf_2_size, sizeof(uint8_t), MALLOC_CAP_SPIRAM | MALLOC_CAP_DMA); + uint8_t *buf_2 = static_cast(heap_caps_aligned_calloc(4, buf_2_size, sizeof(uint8_t), MALLOC_CAP_SPIRAM | MALLOC_CAP_DMA)); TEST_ASSERT_NOT_NULL(buf_2); // Register two PPA SRM clients with different max_pending_trans_num ppa_client_handle_t ppa_client_a_handle; ppa_client_handle_t ppa_client_b_handle; - ppa_client_config_t ppa_client_config = { - .oper_type = PPA_OPERATION_SRM, - }; + ppa_client_config_t ppa_client_config = {}; + ppa_client_config.oper_type = PPA_OPERATION_SRM; TEST_ESP_OK(ppa_register_client(&ppa_client_config, &ppa_client_a_handle)); ppa_client_config.max_pending_trans_num = 3; TEST_ESP_OK(ppa_register_client(&ppa_client_config, &ppa_client_b_handle)); - ppa_event_callbacks_t cbs = { - .on_trans_done = ppa_trans_done_cb, - }; + ppa_event_callbacks_t cbs = {}; + cbs.on_trans_done = ppa_trans_done_cb; ppa_client_register_event_callbacks(ppa_client_a_handle, &cbs); SemaphoreHandle_t sem = xSemaphoreCreateBinary(); - ppa_srm_oper_config_t oper_config = { - .in.buffer = buf_1, - .in.pic_w = w, - .in.pic_h = h, - .in.block_w = w, - .in.block_h = h, - .in.block_offset_x = 0, - .in.block_offset_y = 0, - .in.srm_cm = buf_1_color_type_id, - - .out.buffer = buf_2, - .out.buffer_size = buf_2_size, - .out.pic_w = w, - .out.pic_h = h, - .out.block_offset_x = 0, - .out.block_offset_y = 0, - .out.srm_cm = buf_2_color_type_id, - - .rotation_angle = PPA_SRM_ROTATION_ANGLE_0, - .scale_x = 1.0, - .scale_y = 1.0, - - .user_data = (void *)sem, - .mode = PPA_TRANS_MODE_NON_BLOCKING, - }; + ppa_srm_oper_config_t oper_config = {}; + oper_config.in.buffer = buf_1; + oper_config.in.pic_w = w; + oper_config.in.pic_h = h; + oper_config.in.block_w = w; + oper_config.in.block_h = h; + oper_config.in.block_offset_x = 0; + oper_config.in.block_offset_y = 0; + oper_config.in.srm_cm = static_cast(buf_1_color_type_id); + oper_config.out.buffer = buf_2; + oper_config.out.buffer_size = buf_2_size; + oper_config.out.pic_w = w; + oper_config.out.pic_h = h; + oper_config.out.block_offset_x = 0; + oper_config.out.block_offset_y = 0; + oper_config.out.srm_cm = static_cast(buf_2_color_type_id); + oper_config.rotation_angle = PPA_SRM_ROTATION_ANGLE_0; + oper_config.scale_x = 1.0; + oper_config.scale_y = 1.0; + oper_config.user_data = (void *)sem; + oper_config.mode = PPA_TRANS_MODE_NON_BLOCKING; TEST_ESP_OK(ppa_do_scale_rotate_mirror(ppa_client_a_handle, &oper_config)); // Another transaction cannot be accept since client_a can only hold one transaction @@ -275,7 +255,7 @@ TEST_CASE("ppa_srm_basic_data_correctness_check", "[PPA]") const uint32_t buf_len = w * h * color_hal_pixel_format_fourcc_get_bit_depth((esp_color_fourcc_t)cm) / 8; // 32 uint32_t out_buf_size = ALIGN_UP(buf_len, 64); - uint8_t *out_buf = heap_caps_aligned_calloc(4, out_buf_size, sizeof(uint8_t), MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT | MALLOC_CAP_DMA); // located in internal RAM so even w/ flash encrypted, it won't be affected + uint8_t *out_buf = static_cast(heap_caps_aligned_calloc(4, out_buf_size, sizeof(uint8_t), MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT | MALLOC_CAP_DMA)); // located in internal RAM so even w/ flash encrypted, it won't be affected TEST_ASSERT_NOT_NULL(out_buf); esp_cache_msync((void *)out_buf, out_buf_size, ESP_CACHE_MSYNC_FLAG_DIR_C2M); const uint16_t in_buf[16] = { @@ -297,39 +277,33 @@ TEST_CASE("ppa_srm_basic_data_correctness_check", "[PPA]") }; ppa_client_handle_t ppa_client_handle; - ppa_client_config_t ppa_client_config = { - .oper_type = PPA_OPERATION_SRM, - .max_pending_trans_num = 1, - }; + ppa_client_config_t ppa_client_config = {}; + ppa_client_config.oper_type = PPA_OPERATION_SRM; + ppa_client_config.max_pending_trans_num = 1; TEST_ESP_OK(ppa_register_client(&ppa_client_config, &ppa_client_handle)); - ppa_srm_oper_config_t oper_config = { - .in.buffer = in_buf, - .in.pic_w = w, - .in.pic_h = h, - .in.block_w = block_w, - .in.block_h = block_h, - .in.block_offset_x = in_block_offset_x, - .in.block_offset_y = in_block_offset_y, - .in.srm_cm = cm, - - .out.buffer = out_buf, - .out.buffer_size = out_buf_size, - .out.pic_w = w, - .out.pic_h = h, - .out.block_offset_x = out_block_offset_x, - .out.block_offset_y = out_block_offset_y, - .out.srm_cm = cm, - - .rotation_angle = rotation, - .scale_x = scale_x, - .scale_y = scale_y, - - .rgb_swap = 0, - .byte_swap = 0, - - .mode = PPA_TRANS_MODE_BLOCKING, - }; + ppa_srm_oper_config_t oper_config = {}; + oper_config.in.buffer = in_buf; + oper_config.in.pic_w = w; + oper_config.in.pic_h = h; + oper_config.in.block_w = block_w; + oper_config.in.block_h = block_h; + oper_config.in.block_offset_x = in_block_offset_x; + oper_config.in.block_offset_y = in_block_offset_y; + oper_config.in.srm_cm = cm; + oper_config.out.buffer = out_buf; + oper_config.out.buffer_size = out_buf_size; + oper_config.out.pic_w = w; + oper_config.out.pic_h = h; + oper_config.out.block_offset_x = out_block_offset_x; + oper_config.out.block_offset_y = out_block_offset_y; + oper_config.out.srm_cm = cm; + oper_config.rotation_angle = rotation; + oper_config.scale_x = scale_x; + oper_config.scale_y = scale_y; + oper_config.rgb_swap = 0; + oper_config.byte_swap = 0; + oper_config.mode = PPA_TRANS_MODE_BLOCKING; TEST_ESP_OK(ppa_do_scale_rotate_mirror(ppa_client_handle, &oper_config)); @@ -414,7 +388,7 @@ TEST_CASE("ppa_blend_basic_data_correctness_check", "[PPA]") 0xFF, 0xFF, 0xFF, 0xFF, /**/ 0x00, 0x80, 0x80, 0xC0, /**/ // /* (B) (G) (R) (A) */ // /*******************************/ - [16 ... 63] = 0, + // remaining bytes [16 ... 63] are value-initialized to 0 }; uint8_t *out_buf = in_fg_buf; // Expected blend output @@ -432,49 +406,41 @@ TEST_CASE("ppa_blend_basic_data_correctness_check", "[PPA]") }; ppa_client_handle_t ppa_client_handle; - ppa_client_config_t ppa_client_config = { - .oper_type = PPA_OPERATION_BLEND, - .max_pending_trans_num = 1, - }; + ppa_client_config_t ppa_client_config = {}; + ppa_client_config.oper_type = PPA_OPERATION_BLEND; + ppa_client_config.max_pending_trans_num = 1; TEST_ESP_OK(ppa_register_client(&ppa_client_config, &ppa_client_handle)); - ppa_blend_oper_config_t oper_config = { - .in_bg.buffer = in_bg_buf, - .in_bg.pic_w = w, - .in_bg.pic_h = h, - .in_bg.block_w = block_w, - .in_bg.block_h = block_h, - .in_bg.block_offset_x = block_offset_x, - .in_bg.block_offset_y = block_offset_y, - .in_bg.blend_cm = in_bg_cm, - - .in_fg.buffer = in_fg_buf, - .in_fg.pic_w = w, - .in_fg.pic_h = h, - .in_fg.block_w = block_w, - .in_fg.block_h = block_h, - .in_fg.block_offset_x = block_offset_x, - .in_fg.block_offset_y = block_offset_y, - .in_fg.blend_cm = in_fg_cm, - - .out.buffer = out_buf, - .out.buffer_size = out_buf_size, - .out.pic_w = w, - .out.pic_h = h, - .out.block_offset_x = block_offset_x, - .out.block_offset_y = block_offset_y, - .out.blend_cm = out_cm, - - .bg_alpha_update_mode = PPA_ALPHA_SCALE, - .bg_alpha_scale_ratio = bg_alpha_scale_ratio, - - .fg_alpha_update_mode = PPA_ALPHA_INVERT, - - .bg_ck_en = false, - .fg_ck_en = false, - - .mode = PPA_TRANS_MODE_BLOCKING, - }; + ppa_blend_oper_config_t oper_config = {}; + oper_config.in_bg.buffer = in_bg_buf; + oper_config.in_bg.pic_w = w; + oper_config.in_bg.pic_h = h; + oper_config.in_bg.block_w = block_w; + oper_config.in_bg.block_h = block_h; + oper_config.in_bg.block_offset_x = block_offset_x; + oper_config.in_bg.block_offset_y = block_offset_y; + oper_config.in_bg.blend_cm = in_bg_cm; + oper_config.in_fg.buffer = in_fg_buf; + oper_config.in_fg.pic_w = w; + oper_config.in_fg.pic_h = h; + oper_config.in_fg.block_w = block_w; + oper_config.in_fg.block_h = block_h; + oper_config.in_fg.block_offset_x = block_offset_x; + oper_config.in_fg.block_offset_y = block_offset_y; + oper_config.in_fg.blend_cm = in_fg_cm; + oper_config.out.buffer = out_buf; + oper_config.out.buffer_size = out_buf_size; + oper_config.out.pic_w = w; + oper_config.out.pic_h = h; + oper_config.out.block_offset_x = block_offset_x; + oper_config.out.block_offset_y = block_offset_y; + oper_config.out.blend_cm = out_cm; + oper_config.bg_alpha_update_mode = PPA_ALPHA_SCALE; + oper_config.bg_alpha_scale_ratio = bg_alpha_scale_ratio; + oper_config.fg_alpha_update_mode = PPA_ALPHA_INVERT; + oper_config.bg_ck_en = false; + oper_config.fg_ck_en = false; + oper_config.mode = PPA_TRANS_MODE_BLOCKING; TEST_ESP_OK(ppa_do_blend(ppa_client_handle, &oper_config)); @@ -488,6 +454,84 @@ TEST_CASE("ppa_blend_basic_data_correctness_check", "[PPA]") printf("\n"); TEST_ASSERT_EQUAL_UINT8_ARRAY((void *)out_buf_expected, (void *)out_buf, out_buf_len); +#if !(CONFIG_IDF_TARGET_ESP32P4 && CONFIG_ESP32P4_SELECTS_REV_LESS_V3) + // Test YUV422/YUV420 blend + + // A mid-grey image is achromatic (U = V = 128), so a correct RGB<->YUV path must round-trip + // it back to grey with R == G == B. For each format we run RGB->YUV (exercises YUV as the + // blend output) then YUV->RGB (exercises YUV as the blend background input); both must + // complete without hanging and yield achromatic grey + + // 2 x 2 is the smallest valid YUV block: even w/h satisfies YUV422 (2x1) & YUV420 (2x2) + const uint8_t grey = 0x80; // mid-grey: Y arbitrary, U = V = 128 -> R == G == B after decode + + // RGB888 mid-grey background (12B) and ARGB8888 foreground (16B). The foreground alpha is + // inverted to 0, so it contributes nothing and the blend output equals the background color. + uint8_t rgb_grey[12]; + memset(rgb_grey, grey, sizeof(rgb_grey)); + uint8_t fg_buf[16]; + memset(fg_buf, 0xFF, sizeof(fg_buf)); + // DMA outputs require cache-line alignment; yuv_mid is also reused as input for the YUV->RGB pass. + uint8_t yuv_mid[64] __attribute__((aligned(64))) = {}; + uint8_t rgb_back[64] __attribute__((aligned(64))); + memset(rgb_back, 0xCC, sizeof(rgb_back)); + const uint32_t yuv_buf_size = sizeof(yuv_mid); + + const ppa_blend_color_mode_t yuv_cms[] = { + PPA_BLEND_COLOR_MODE_YUV422_UYVY, + PPA_BLEND_COLOR_MODE_YUV420, + }; + for (int c = 0; c < 2; c++) { + const ppa_blend_color_mode_t yuv_cm = yuv_cms[c]; + + // Foreground contributes nothing: alpha 0xFF inverted to 0, so output == background. + ppa_blend_oper_config_t yuv_oper_config = {}; + yuv_oper_config.in_fg.buffer = fg_buf; + yuv_oper_config.in_fg.pic_w = w; + yuv_oper_config.in_fg.pic_h = h; + yuv_oper_config.in_fg.block_w = w; + yuv_oper_config.in_fg.block_h = h; + yuv_oper_config.in_fg.blend_cm = PPA_BLEND_COLOR_MODE_ARGB8888; + yuv_oper_config.bg_alpha_update_mode = PPA_ALPHA_NO_CHANGE; + yuv_oper_config.fg_alpha_update_mode = PPA_ALPHA_INVERT; + yuv_oper_config.mode = PPA_TRANS_MODE_BLOCKING; + + // 1) RGB888 grey -> YUV (exercises YUV as blend output; must not hang) + yuv_oper_config.in_bg.buffer = rgb_grey; + yuv_oper_config.in_bg.pic_w = w; + yuv_oper_config.in_bg.pic_h = h; + yuv_oper_config.in_bg.block_w = w; + yuv_oper_config.in_bg.block_h = h; + yuv_oper_config.in_bg.blend_cm = PPA_BLEND_COLOR_MODE_RGB888; + yuv_oper_config.out.buffer = yuv_mid; + yuv_oper_config.out.buffer_size = yuv_buf_size; + yuv_oper_config.out.pic_w = w; + yuv_oper_config.out.pic_h = h; + yuv_oper_config.out.blend_cm = yuv_cm; + TEST_ESP_OK(ppa_do_blend(ppa_client_handle, &yuv_oper_config)); + + // 2) YUV grey -> RGB888 (exercises YUV as blend background input; must not hang) + yuv_oper_config.in_bg.buffer = yuv_mid; + yuv_oper_config.in_bg.blend_cm = yuv_cm; + yuv_oper_config.out.buffer = rgb_back; + yuv_oper_config.out.blend_cm = PPA_BLEND_COLOR_MODE_RGB888; + TEST_ESP_OK(ppa_do_blend(ppa_client_handle, &yuv_oper_config)); + + // Check result + // Mid-grey is achromatic: every RGB888 pixel must stay near-grey, i.e. R ~= G ~= B + // (chroma preserved) and the level must remain mid-grey (luma not driven to + // black/white). A small tolerance covers the +-1 LSB rounding of two YUV conversions. + for (int p = 0; p < w * h; p++) { + const int b = rgb_back[p * 3 + 0]; + const int g = rgb_back[p * 3 + 1]; + const int r = rgb_back[p * 3 + 2]; + TEST_ASSERT_INT_WITHIN(2, b, g); // achromatic: channels stay together + TEST_ASSERT_INT_WITHIN(2, g, r); + TEST_ASSERT_INT_WITHIN(16, grey, b); // round-trips back to ~mid-grey + } + } +#endif // !(CONFIG_IDF_TARGET_ESP32P4 && CONFIG_ESP32P4_SELECTS_REV_LESS_V3) + TEST_ESP_OK(ppa_unregister_client(ppa_client_handle)); } @@ -500,52 +544,55 @@ TEST_CASE("ppa_fill_basic_data_correctness_check", "[PPA]") const uint32_t block_offset_x = 0; const uint32_t block_offset_y = 40; const ppa_fill_color_mode_t out_cm = PPA_FILL_COLOR_MODE_RGB565; - const color_pixel_argb8888_data_t fill_color = {.a = 0x80, .r = 0xFF, .g = 0x55, .b = 0xAA}; + color_pixel_argb8888_data_t fill_color = {}; + fill_color.a = 0x80; + fill_color.r = 0xFF; + fill_color.g = 0x55; + fill_color.b = 0xAA; uint32_t out_pixel_depth = color_hal_pixel_format_fourcc_get_bit_depth((esp_color_fourcc_t)out_cm); // bits uint32_t out_buf_len = w * h * out_pixel_depth / 8; uint32_t out_buf_size = ALIGN_UP(out_buf_len, 64); - uint8_t *out_buf = heap_caps_aligned_calloc(4, out_buf_size, sizeof(uint8_t), MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT | MALLOC_CAP_DMA); + uint8_t *out_buf = static_cast(heap_caps_aligned_calloc(4, out_buf_size, sizeof(uint8_t), MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT | MALLOC_CAP_DMA)); TEST_ASSERT_NOT_NULL(out_buf); memset(out_buf, 0xFF, out_buf_len); ppa_client_handle_t ppa_client_handle; - ppa_client_config_t ppa_client_config = { - .oper_type = PPA_OPERATION_FILL, - .max_pending_trans_num = 1, - }; + ppa_client_config_t ppa_client_config = {}; + ppa_client_config.oper_type = PPA_OPERATION_FILL; + ppa_client_config.max_pending_trans_num = 1; TEST_ESP_OK(ppa_register_client(&ppa_client_config, &ppa_client_handle)); - ppa_fill_oper_config_t oper_config = { - .out.buffer = out_buf, - .out.buffer_size = out_buf_size, - .out.pic_w = w, - .out.pic_h = h, - .out.block_offset_x = block_offset_x, - .out.block_offset_y = block_offset_y, - .out.fill_cm = out_cm, - - .fill_block_w = block_w, - .fill_block_h = block_h, - .fill_argb_color = fill_color, - - .mode = PPA_TRANS_MODE_BLOCKING, - }; + ppa_fill_oper_config_t oper_config = {}; + oper_config.out.buffer = out_buf; + oper_config.out.buffer_size = out_buf_size; + oper_config.out.pic_w = w; + oper_config.out.pic_h = h; + oper_config.out.block_offset_x = block_offset_x; + oper_config.out.block_offset_y = block_offset_y; + oper_config.out.fill_cm = out_cm; + oper_config.fill_block_w = block_w; + oper_config.fill_block_h = block_h; + oper_config.fill_argb_color = fill_color; + oper_config.mode = PPA_TRANS_MODE_BLOCKING; TEST_ESP_OK(ppa_do_fill(ppa_client_handle, &oper_config)); // Check result - const color_pixel_rgb565_data_t fill_pixel_expected = {.r = fill_color.r >> 3, - .g = fill_color.g >> 2, - .b = fill_color.b >> 3, - }; + color_pixel_rgb565_data_t fill_pixel_expected = {}; + fill_pixel_expected.r = fill_color.r >> 3; + fill_pixel_expected.g = fill_color.g >> 2; + fill_pixel_expected.b = fill_color.b >> 3; TEST_ASSERT_EACH_EQUAL_UINT16(fill_pixel_expected.val, (void *)((uint32_t)out_buf + w * block_offset_y * out_pixel_depth / 8), block_w * block_h); #if !(CONFIG_IDF_TARGET_ESP32P4 && CONFIG_ESP32P4_SELECTS_REV_LESS_V3) // Test a yuv color fill oper_config.out.fill_cm = PPA_FILL_COLOR_MODE_YUV422_UYVY; // output YUV422 is with UYVY packed order - const color_macroblock_yuv_data_t fill_yuv_color = {.y = 0xFF, .u = 0x55, .v = 0xAA}; + color_macroblock_yuv_data_t fill_yuv_color = {}; + fill_yuv_color.y = 0xFF; + fill_yuv_color.u = 0x55; + fill_yuv_color.v = 0xAA; oper_config.fill_yuv_color = fill_yuv_color; out_pixel_depth = color_hal_pixel_format_fourcc_get_bit_depth((esp_color_fourcc_t)PPA_FILL_COLOR_MODE_YUV422_UYVY); // bits TEST_ESP_OK(ppa_do_fill(ppa_client_handle, &oper_config)); @@ -591,9 +638,9 @@ TEST_CASE("ppa_srm_performance", "[PPA]") uint32_t in_buf_size = w * h * color_hal_pixel_format_fourcc_get_bit_depth((esp_color_fourcc_t)in_cm) / 8; uint32_t out_buf_size = ALIGN_UP(w * h * color_hal_pixel_format_fourcc_get_bit_depth((esp_color_fourcc_t)out_cm) / 8, 64); - uint8_t *out_buf = heap_caps_aligned_calloc(4, out_buf_size, sizeof(uint8_t), MALLOC_CAP_SPIRAM | MALLOC_CAP_DMA); + uint8_t *out_buf = static_cast(heap_caps_aligned_calloc(4, out_buf_size, sizeof(uint8_t), MALLOC_CAP_SPIRAM | MALLOC_CAP_DMA)); TEST_ASSERT_NOT_NULL(out_buf); - uint8_t *in_buf = heap_caps_aligned_calloc(4, in_buf_size, sizeof(uint8_t), MALLOC_CAP_SPIRAM | MALLOC_CAP_DMA); + uint8_t *in_buf = static_cast(heap_caps_aligned_calloc(4, in_buf_size, sizeof(uint8_t), MALLOC_CAP_SPIRAM | MALLOC_CAP_DMA)); TEST_ASSERT_NOT_NULL(in_buf); uint8_t *ptr = in_buf; @@ -602,41 +649,35 @@ TEST_CASE("ppa_srm_performance", "[PPA]") } ppa_client_handle_t ppa_client_handle; - ppa_client_config_t ppa_client_config = { - .oper_type = PPA_OPERATION_SRM, - .max_pending_trans_num = 1, - }; + ppa_client_config_t ppa_client_config = {}; + ppa_client_config.oper_type = PPA_OPERATION_SRM; + ppa_client_config.max_pending_trans_num = 1; TEST_ESP_OK(ppa_register_client(&ppa_client_config, &ppa_client_handle)); uint32_t out_pic_w = (rotation == PPA_SRM_ROTATION_ANGLE_0 || rotation == PPA_SRM_ROTATION_ANGLE_180) ? w : h; uint32_t out_pic_h = (rotation == PPA_SRM_ROTATION_ANGLE_0 || rotation == PPA_SRM_ROTATION_ANGLE_180) ? h : w; - ppa_srm_oper_config_t oper_config = { - .in.buffer = in_buf, - .in.pic_w = w, - .in.pic_h = h, - .in.block_w = block_w, - .in.block_h = block_h, - .in.block_offset_x = 0, - .in.block_offset_y = 0, - .in.srm_cm = in_cm, - - .out.buffer = out_buf, - .out.buffer_size = out_buf_size, - .out.pic_w = out_pic_w, - .out.pic_h = out_pic_h, - .out.block_offset_x = 0, - .out.block_offset_y = 0, - .out.srm_cm = out_cm, - - .rotation_angle = rotation, - .scale_x = scale_x, - .scale_y = scale_y, - - .rgb_swap = 0, - .byte_swap = 0, - - .mode = PPA_TRANS_MODE_BLOCKING, - }; + ppa_srm_oper_config_t oper_config = {}; + oper_config.in.buffer = in_buf; + oper_config.in.pic_w = w; + oper_config.in.pic_h = h; + oper_config.in.block_w = block_w; + oper_config.in.block_h = block_h; + oper_config.in.block_offset_x = 0; + oper_config.in.block_offset_y = 0; + oper_config.in.srm_cm = in_cm; + oper_config.out.buffer = out_buf; + oper_config.out.buffer_size = out_buf_size; + oper_config.out.pic_w = out_pic_w; + oper_config.out.pic_h = out_pic_h; + oper_config.out.block_offset_x = 0; + oper_config.out.block_offset_y = 0; + oper_config.out.srm_cm = out_cm; + oper_config.rotation_angle = rotation; + oper_config.scale_x = scale_x; + oper_config.scale_y = scale_y; + oper_config.rgb_swap = 0; + oper_config.byte_swap = 0; + oper_config.mode = PPA_TRANS_MODE_BLOCKING; ccomp_timer_start(); @@ -675,11 +716,11 @@ TEST_CASE("ppa_blend_performance", "[PPA]") uint32_t in_bg_buf_size = ALIGN_UP(w * h * color_hal_pixel_format_fourcc_get_bit_depth((esp_color_fourcc_t)in_bg_cm) / 8, in_buf_alignment); uint32_t in_fg_buf_size = ALIGN_UP(w * h * color_hal_pixel_format_fourcc_get_bit_depth((esp_color_fourcc_t)in_fg_cm) / 8, in_buf_alignment); uint32_t out_buf_size = ALIGN_UP(w * h * color_hal_pixel_format_fourcc_get_bit_depth((esp_color_fourcc_t)out_cm) / 8, 64); - uint8_t *out_buf = heap_caps_aligned_calloc(4, out_buf_size, sizeof(uint8_t), MALLOC_CAP_SPIRAM | MALLOC_CAP_DMA); + uint8_t *out_buf = static_cast(heap_caps_aligned_calloc(4, out_buf_size, sizeof(uint8_t), MALLOC_CAP_SPIRAM | MALLOC_CAP_DMA)); TEST_ASSERT_NOT_NULL(out_buf); - uint8_t *in_bg_buf = heap_caps_aligned_calloc(4, in_bg_buf_size, sizeof(uint8_t), MALLOC_CAP_SPIRAM | MALLOC_CAP_DMA); + uint8_t *in_bg_buf = static_cast(heap_caps_aligned_calloc(4, in_bg_buf_size, sizeof(uint8_t), MALLOC_CAP_SPIRAM | MALLOC_CAP_DMA)); TEST_ASSERT_NOT_NULL(in_bg_buf); - uint8_t *in_fg_buf = heap_caps_aligned_calloc(4, in_fg_buf_size, sizeof(uint8_t), MALLOC_CAP_SPIRAM | MALLOC_CAP_DMA); + uint8_t *in_fg_buf = static_cast(heap_caps_aligned_calloc(4, in_fg_buf_size, sizeof(uint8_t), MALLOC_CAP_SPIRAM | MALLOC_CAP_DMA)); TEST_ASSERT_NOT_NULL(in_fg_buf); uint8_t *ptr = in_bg_buf; @@ -692,44 +733,38 @@ TEST_CASE("ppa_blend_performance", "[PPA]") } ppa_client_handle_t ppa_client_handle; - ppa_client_config_t ppa_client_config = { - .oper_type = PPA_OPERATION_BLEND, - .max_pending_trans_num = 1, - }; + ppa_client_config_t ppa_client_config = {}; + ppa_client_config.oper_type = PPA_OPERATION_BLEND; + ppa_client_config.max_pending_trans_num = 1; TEST_ESP_OK(ppa_register_client(&ppa_client_config, &ppa_client_handle)); - ppa_blend_oper_config_t oper_config = { - .in_bg.buffer = in_bg_buf, - .in_bg.pic_w = w, - .in_bg.pic_h = h, - .in_bg.block_w = block_w, - .in_bg.block_h = block_h, - .in_bg.block_offset_x = 0, - .in_bg.block_offset_y = 0, - .in_bg.blend_cm = in_bg_cm, - - .in_fg.buffer = in_fg_buf, - .in_fg.pic_w = w, - .in_fg.pic_h = h, - .in_fg.block_w = block_w, - .in_fg.block_h = block_h, - .in_fg.block_offset_x = 0, - .in_fg.block_offset_y = 0, - .in_fg.blend_cm = in_fg_cm, - - .out.buffer = out_buf, - .out.buffer_size = out_buf_size, - .out.pic_w = w, - .out.pic_h = h, - .out.block_offset_x = 0, - .out.block_offset_y = 0, - .out.blend_cm = out_cm, - - .bg_ck_en = false, - .fg_ck_en = false, - - .mode = PPA_TRANS_MODE_BLOCKING, - }; + ppa_blend_oper_config_t oper_config = {}; + oper_config.in_bg.buffer = in_bg_buf; + oper_config.in_bg.pic_w = w; + oper_config.in_bg.pic_h = h; + oper_config.in_bg.block_w = block_w; + oper_config.in_bg.block_h = block_h; + oper_config.in_bg.block_offset_x = 0; + oper_config.in_bg.block_offset_y = 0; + oper_config.in_bg.blend_cm = in_bg_cm; + oper_config.in_fg.buffer = in_fg_buf; + oper_config.in_fg.pic_w = w; + oper_config.in_fg.pic_h = h; + oper_config.in_fg.block_w = block_w; + oper_config.in_fg.block_h = block_h; + oper_config.in_fg.block_offset_x = 0; + oper_config.in_fg.block_offset_y = 0; + oper_config.in_fg.blend_cm = in_fg_cm; + oper_config.out.buffer = out_buf; + oper_config.out.buffer_size = out_buf_size; + oper_config.out.pic_w = w; + oper_config.out.pic_h = h; + oper_config.out.block_offset_x = 0; + oper_config.out.block_offset_y = 0; + oper_config.out.blend_cm = out_cm; + oper_config.bg_ck_en = false; + oper_config.fg_ck_en = false; + oper_config.mode = PPA_TRANS_MODE_BLOCKING; ccomp_timer_start(); @@ -763,33 +798,27 @@ TEST_CASE("ppa_fill_performance", "[PPA]") const ppa_fill_color_mode_t out_cm = PPA_FILL_COLOR_MODE_RGB565; uint32_t out_buf_size = ALIGN_UP(w * h * color_hal_pixel_format_fourcc_get_bit_depth((esp_color_fourcc_t)out_cm) / 8, 64); - uint8_t *out_buf = heap_caps_aligned_calloc(4, out_buf_size, sizeof(uint8_t), MALLOC_CAP_SPIRAM | MALLOC_CAP_DMA); + uint8_t *out_buf = static_cast(heap_caps_aligned_calloc(4, out_buf_size, sizeof(uint8_t), MALLOC_CAP_SPIRAM | MALLOC_CAP_DMA)); TEST_ASSERT_NOT_NULL(out_buf); ppa_client_handle_t ppa_client_handle; - ppa_client_config_t ppa_client_config = { - .oper_type = PPA_OPERATION_FILL, - .max_pending_trans_num = 1, - }; + ppa_client_config_t ppa_client_config = {}; + ppa_client_config.oper_type = PPA_OPERATION_FILL; + ppa_client_config.max_pending_trans_num = 1; TEST_ESP_OK(ppa_register_client(&ppa_client_config, &ppa_client_handle)); - ppa_fill_oper_config_t oper_config = { - .out.buffer = out_buf, - .out.buffer_size = out_buf_size, - .out.pic_w = w, - .out.pic_h = h, - .out.block_offset_x = 0, - .out.block_offset_y = 0, - .out.fill_cm = out_cm, - - .fill_block_w = block_w, - .fill_block_h = block_h, - .fill_argb_color = { - .val = 0xFF00FFFF, - }, - - .mode = PPA_TRANS_MODE_BLOCKING, - }; + ppa_fill_oper_config_t oper_config = {}; + oper_config.out.buffer = out_buf; + oper_config.out.buffer_size = out_buf_size; + oper_config.out.pic_w = w; + oper_config.out.pic_h = h; + oper_config.out.block_offset_x = 0; + oper_config.out.block_offset_y = 0; + oper_config.out.fill_cm = out_cm; + oper_config.fill_block_w = block_w; + oper_config.fill_block_h = block_h; + oper_config.fill_argb_color.val = 0xFF00FFFF; + oper_config.mode = PPA_TRANS_MODE_BLOCKING; ccomp_timer_start(); @@ -829,16 +858,15 @@ TEST_CASE("ppa_srm_stress_test", "[PPA]") uint32_t in_buf_size = w * h * color_hal_pixel_format_fourcc_get_bit_depth((esp_color_fourcc_t)in_cm) / 8; uint32_t out_buf_size = ALIGN_UP(w * h * color_hal_pixel_format_fourcc_get_bit_depth((esp_color_fourcc_t)out_cm) / 8, 64); - uint8_t *out_buf = heap_caps_aligned_calloc(4, out_buf_size, sizeof(uint8_t), MALLOC_CAP_SPIRAM | MALLOC_CAP_DMA); + uint8_t *out_buf = static_cast(heap_caps_aligned_calloc(4, out_buf_size, sizeof(uint8_t), MALLOC_CAP_SPIRAM | MALLOC_CAP_DMA)); TEST_ASSERT_NOT_NULL(out_buf); - uint8_t *in_buf = heap_caps_aligned_calloc(4, in_buf_size, sizeof(uint8_t), MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT | MALLOC_CAP_DMA); + uint8_t *in_buf = static_cast(heap_caps_aligned_calloc(4, in_buf_size, sizeof(uint8_t), MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT | MALLOC_CAP_DMA)); TEST_ASSERT_NOT_NULL(in_buf); ppa_client_handle_t ppa_client_handle; - ppa_client_config_t ppa_client_config = { - .oper_type = PPA_OPERATION_SRM, - .max_pending_trans_num = 1, - }; + ppa_client_config_t ppa_client_config = {}; + ppa_client_config.oper_type = PPA_OPERATION_SRM; + ppa_client_config.max_pending_trans_num = 1; TEST_ESP_OK(ppa_register_client(&ppa_client_config, &ppa_client_handle)); // Test on different sizes of the block @@ -854,33 +882,28 @@ TEST_CASE("ppa_srm_stress_test", "[PPA]") block_w = block_w_initial + i; block_h = block_h_initial + i; // printf("block_w = %ld, block_h = %ld\n", block_w, block_h); - ppa_srm_oper_config_t oper_config = { - .in.buffer = in_buf, - .in.pic_w = w, - .in.pic_h = h, - .in.block_w = block_w, - .in.block_h = block_h, - .in.block_offset_x = 0, - .in.block_offset_y = 0, - .in.srm_cm = in_cm, - - .out.buffer = out_buf, - .out.buffer_size = out_buf_size, - .out.pic_w = block_w, - .out.pic_h = block_h, - .out.block_offset_x = 0, - .out.block_offset_y = 0, - .out.srm_cm = out_cm, - - .rotation_angle = rotation, - .scale_x = scale_x, - .scale_y = scale_y, - - .rgb_swap = 0, - .byte_swap = 0, - - .mode = PPA_TRANS_MODE_BLOCKING, - }; + ppa_srm_oper_config_t oper_config = {}; + oper_config.in.buffer = in_buf; + oper_config.in.pic_w = w; + oper_config.in.pic_h = h; + oper_config.in.block_w = block_w; + oper_config.in.block_h = block_h; + oper_config.in.block_offset_x = 0; + oper_config.in.block_offset_y = 0; + oper_config.in.srm_cm = in_cm; + oper_config.out.buffer = out_buf; + oper_config.out.buffer_size = out_buf_size; + oper_config.out.pic_w = block_w; + oper_config.out.pic_h = block_h; + oper_config.out.block_offset_x = 0; + oper_config.out.block_offset_y = 0; + oper_config.out.srm_cm = out_cm; + oper_config.rotation_angle = rotation; + oper_config.scale_x = scale_x; + oper_config.scale_y = scale_y; + oper_config.rgb_swap = 0; + oper_config.byte_swap = 0; + oper_config.mode = PPA_TRANS_MODE_BLOCKING; TEST_ESP_OK(ppa_do_scale_rotate_mirror(ppa_client_handle, &oper_config)); } diff --git a/components/esp_driver_sdio/include/driver/sdio_slave.h b/components/esp_driver_sdio/include/driver/sdio_slave.h index d9383d883db..548b7680092 100644 --- a/components/esp_driver_sdio/include/driver/sdio_slave.h +++ b/components/esp_driver_sdio/include/driver/sdio_slave.h @@ -27,7 +27,9 @@ typedef struct { ///< If buffer_size is too large, the space larger than the transaction length is left blank but still counts a buffer, and the buffers are easily run out. ///< Should be set according to length of data really transferred. ///< All data that do not fully fill a buffer is still counted as one buffer. E.g. 10 bytes data costs 2 buffers if the size is 8 bytes per buffer. - ///< Buffer size of the slave pre-defined between host and slave before communication. All receive buffer given to the driver should be larger than this. + ///< Buffer size of the slave pre-defined between host and slave before communication. It must not exceed the + ///< maximum size supported by a single SDIO slave DMA descriptor on the current chip, + ///< and all receive buffer given to the driver should be larger than this. sdio_event_cb_t event_cb; ///< when the host interrupts slave, this callback will be called with interrupt number (0-7). uint32_t flags; ///< Features to be enabled for the slave, combinations of ``SDIO_SLAVE_FLAG_*``. #define SDIO_SLAVE_FLAG_DAT2_DISABLED BIT(0) /**< It is required by the SD specification that all 4 data @@ -194,7 +196,7 @@ uint8_t* sdio_slave_recv_get_buf(sdio_slave_buf_handle_t handle, size_t *len_o); * ``sdio_slave_send_get_finished`` after the transaction is finished. * * @param addr Address for data to be sent. The buffer should be DMA capable and 32-bit aligned. - * @param len Length of the data, should not be longer than 4092 bytes (may support longer in the future). + * @param len Length of the data, should not exceed the maximum size supported by a single SDIO slave DMA descriptor on the current chip. * @param arg Argument to returned in ``sdio_slave_send_get_finished``. The argument can be used to indicate which transaction is done, * or as a parameter for a callback. Set to NULL if not needed. * @param wait Time to wait if the buffer is full. diff --git a/components/esp_driver_sdio/src/sdio_slave.c b/components/esp_driver_sdio/src/sdio_slave.c index a53f0b4fb1d..5201b838b67 100644 --- a/components/esp_driver_sdio/src/sdio_slave.c +++ b/components/esp_driver_sdio/src/sdio_slave.c @@ -94,6 +94,8 @@ The driver of FIFOs works as below: #include "driver/gpio.h" #include "driver/sdio_slave.h" +#define SDIO_SLAVE_DMA_DESC_MAX_BUF_SIZE_ALIGNED_DOWN (SDIO_SLAVE_LL_DMA_DESC_MAX_BUF_SIZE & ~0x3U) + #define SDIO_SLAVE_CHECK(res, str, ret_val) do { if(!(res)){\ SDIO_SLAVE_LOGE("%s", str);\ return ret_val;\ @@ -617,7 +619,8 @@ static void sdio_intr_send(void *arg) esp_err_t sdio_slave_send_queue(uint8_t *addr, size_t len, void *arg, uint32_t wait) { - SDIO_SLAVE_CHECK(len > 0 && len <= 4092, "length out of range: (0, 4092]", ESP_ERR_INVALID_ARG); + SDIO_SLAVE_CHECK(len > 0 && len <= SDIO_SLAVE_DMA_DESC_MAX_BUF_SIZE_ALIGNED_DOWN, + "length out of range for a single DMA descriptor", ESP_ERR_INVALID_ARG); SDIO_SLAVE_CHECK(esp_ptr_dma_capable(addr) && (uint32_t)addr % 4 == 0, "buffer to send should be DMA capable and 32-bit aligned", ESP_ERR_INVALID_ARG); diff --git a/components/esp_driver_sdmmc/include/driver/sd_host_sdmmc.h b/components/esp_driver_sdmmc/include/driver/sd_host_sdmmc.h index c6db874fe40..6f1d9a09b45 100644 --- a/components/esp_driver_sdmmc/include/driver/sd_host_sdmmc.h +++ b/components/esp_driver_sdmmc/include/driver/sd_host_sdmmc.h @@ -66,7 +66,7 @@ typedef struct { * @return * - ESP_OK: On success * - ESP_ERR_NO_MEM: Out of memory - * - ESP_ERR_NOT_FOUND: Controller not found + * - ESP_ERR_NOT_FOUND: No free controller * - ESP_ERR_INVALID_ARG: Invalid argument */ esp_err_t sd_host_create_sdmmc_controller(const sd_host_sdmmc_cfg_t *config, sd_host_ctlr_handle_t *ret_handle); diff --git a/components/esp_driver_sdmmc/legacy/src/sdmmc_host.c b/components/esp_driver_sdmmc/legacy/src/sdmmc_host.c index a532050003e..acb090bd709 100644 --- a/components/esp_driver_sdmmc/legacy/src/sdmmc_host.c +++ b/components/esp_driver_sdmmc/legacy/src/sdmmc_host.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -77,6 +77,11 @@ esp_err_t sdmmc_host_set_input_delayline(int slot, sdmmc_delay_line_t delay_line esp_err_t sdmmc_host_init(void) { + if (s_ctlr) { + ESP_LOGI(TAG, "SDMMC host controller already created, returning existing handle"); + return ESP_OK; + } + sd_host_sdmmc_cfg_t cfg = { .event_queue_items = SDMMC_EVENT_QUEUE_LENGTH, }; @@ -158,6 +163,8 @@ esp_err_t sdmmc_host_deinit_slot(int slot) //for backward compatibility, return ESP_OK when only slot is removed and host is still there if (ret == ESP_ERR_INVALID_STATE) { ret = ESP_OK; + } else if (ret == ESP_OK) { + s_ctlr = NULL; } return ret; @@ -176,6 +183,7 @@ esp_err_t sdmmc_host_deinit(void) } } ESP_RETURN_ON_ERROR(sd_host_del_controller(s_ctlr), TAG, "failed to delete controller"); + s_ctlr = NULL; return ESP_OK; } diff --git a/components/esp_driver_sdmmc/test_apps/sdmmc/sdkconfig.defaults.esp32p4 b/components/esp_driver_sdmmc/test_apps/sdmmc/sdkconfig.defaults.esp32p4 index bae8235e8d7..7b8ca4566cf 100644 --- a/components/esp_driver_sdmmc/test_apps/sdmmc/sdkconfig.defaults.esp32p4 +++ b/components/esp_driver_sdmmc/test_apps/sdmmc/sdkconfig.defaults.esp32p4 @@ -3,4 +3,3 @@ CONFIG_SDMMC_BOARD_ESP32P4_EV_BOARD=y CONFIG_SPIRAM=y CONFIG_IDF_EXPERIMENTAL_FEATURES=y CONFIG_SPIRAM_SPEED_200M=y -CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG=y diff --git a/components/esp_driver_twai/CMakeLists.txt b/components/esp_driver_twai/CMakeLists.txt index e4d24b813b2..c59921c987a 100644 --- a/components/esp_driver_twai/CMakeLists.txt +++ b/components/esp_driver_twai/CMakeLists.txt @@ -9,7 +9,7 @@ set(public_include "include") set(priv_req esp_driver_gpio esp_pm esp_timer) if(CONFIG_SOC_TWAI_SUPPORTED) - list(APPEND srcs "esp_twai_onchip.c") + list(APPEND srcs "esp_twai_onchip.c" "twai_frame_queue.c") endif() idf_component_register( diff --git a/components/esp_driver_twai/esp_twai.c b/components/esp_driver_twai/esp_twai.c index 9d2a086b4aa..26d86a5174b 100644 --- a/components/esp_driver_twai/esp_twai.c +++ b/components/esp_driver_twai/esp_twai.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2024-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -33,7 +33,7 @@ uint32_t twai_node_timing_calc_param(const uint32_t source_freq, const twai_timi if (total_div != tseg * pre_div) { continue; // no integer tseg } - if ((tseg <= (hw_limit->tseg1_max + hw_limit->tseg2_max + 1)) && (tseg >= (hw_limit->tseg1_min + hw_limit->tseg2_min))) { + if ((tseg <= (hw_limit->tseg1_max + hw_limit->tseg2_max + hw_limit->prop_max + 1)) && (tseg >= (hw_limit->tseg1_min + hw_limit->tseg2_min + 1))) { break; } } @@ -44,20 +44,21 @@ uint32_t twai_node_timing_calc_param(const uint32_t source_freq, const twai_timi uint16_t default_point = (in_param->bitrate >= 800000) ? 750 : ((in_param->bitrate >= 500000) ? 800 : 875); uint16_t sample_point = in_param->sp_permill ? in_param->sp_permill : default_point; // default sample point based on bitrate if not configured uint16_t tseg_1 = (tseg * sample_point) / 1000 - 1; - tseg_1 = MAX(hw_limit->tseg1_min, MIN(tseg_1, hw_limit->tseg1_max)); - uint16_t tseg_2 = tseg - tseg_1 - 1; - tseg_2 = MAX(hw_limit->tseg2_min, MIN(tseg_2, hw_limit->tseg2_max)); - uint16_t prop = MAX(1, tseg_1 / 4); // prop_seg is usually shorter than tseg_1 and at least 1 - tseg_1 -= prop; + tseg_1 = MAX(hw_limit->tseg1_min, MIN(tseg_1, hw_limit->tseg1_max + hw_limit->prop_max)); + uint16_t phase_seg2 = tseg - tseg_1 - 1; + phase_seg2 = MAX(hw_limit->tseg2_min, MIN(phase_seg2, hw_limit->tseg2_max)); + uint16_t phase_seg1 = (tseg_1 * 3) / 4; // phase_seg1 is usually larger than prop_seg + phase_seg1 = MAX(hw_limit->tseg1_min, MIN(phase_seg1, hw_limit->tseg1_max)); + uint16_t prop = tseg_1 - phase_seg1; out_param->brp = pre_div; out_param->prop_seg = prop; - out_param->tseg_1 = tseg_1; - out_param->tseg_2 = tseg_2; - out_param->sjw = MAX(1, MIN(tseg_2 >> 1, hw_limit->sjw_max)); + out_param->tseg_1 = phase_seg1; + out_param->tseg_2 = phase_seg2; + out_param->sjw = MAX(1, MIN(phase_seg2 >> 1, hw_limit->sjw_max)); out_param->ssp_offset = (tseg * in_param->ssp_permill) / 1000; // ssp is optional, default 0 if not configured - return source_freq / (pre_div * (prop + tseg_1 + tseg_2 + 1)); + return source_freq / (pre_div * (prop + phase_seg1 + phase_seg2 + 1)); } esp_err_t twai_node_enable(twai_node_handle_t node) diff --git a/components/esp_driver_twai/esp_twai_onchip.c b/components/esp_driver_twai/esp_twai_onchip.c index 4167cc5156f..e20ec5ab0fe 100644 --- a/components/esp_driver_twai/esp_twai_onchip.c +++ b/components/esp_driver_twai/esp_twai_onchip.c @@ -7,9 +7,10 @@ #include "esp_timer.h" #include "esp_twai.h" #include "esp_twai_onchip.h" +#include "twai_private.h" #include "esp_private/twai_interface.h" #include "esp_private/twai_utils.h" -#include "twai_private.h" +#include "esp_private/twai_frame_queue.h" #include "hal/twai_periph.h" #include "hal/twai_hal.h" #if SOC_HAS(TWAI_FD) @@ -54,7 +55,7 @@ typedef struct { twai_hal_context_t *hal; intr_handle_t intr_hdl; intr_handle_t timer_intr_hdl; - QueueHandle_t tx_mount_queue; + twai_frame_queue_t tx_queue; EventGroupHandle_t event_group; twai_clock_source_t curr_clk_src; uint32_t src_freq_hz; @@ -68,10 +69,11 @@ typedef struct { _Atomic twai_error_state_t state; twai_node_record_t history; + uint8_t tx_slot_num; atomic_bool hw_busy; atomic_bool rx_isr; - const twai_frame_t *p_curr_tx; + const twai_frame_t *p_curr_tx[TWAI_HAL_TX_BUFFER_SLOT_NUM]; twai_hal_frame_t rcv_buff; } twai_onchip_ctx_t; @@ -162,9 +164,9 @@ static void _node_release_io(twai_onchip_ctx_t *node) } } -static void _node_start_trans(twai_onchip_ctx_t *node) +static void _node_start_trans(twai_onchip_ctx_t *node, uint8_t buffer_idx) { - const twai_frame_t *frame = node->p_curr_tx; + const twai_frame_t *frame = node->p_curr_tx[buffer_idx]; twai_hal_context_t *hal = node->hal; twai_hal_frame_t hal_buf = {}; @@ -180,8 +182,40 @@ static void _node_start_trans(twai_onchip_ctx_t *node) }, }; twai_hal_format_frame(&hal_trans, &hal_buf); - //TODO: utilize all txt buffers - twai_hal_set_tx_buffer_and_transmit(hal, &hal_buf, 0); + twai_hal_set_tx_buffer_and_transmit(hal, &hal_buf, buffer_idx); +} + +static uint8_t _node_start_tx_batch_from_isr(twai_onchip_ctx_t *node, BaseType_t *yield_required) +{ + uint8_t tx_idx = 0; + // Note: hardware slot has default priority (like 0>1>2...), + // we should fill slot with same order to avoid data inverse + while (tx_idx < node->tx_slot_num) { // try best to fill all tx slots + const twai_frame_t *frame = NULL; + if (twai_frame_queue_pop_from_isr(node->tx_queue, &frame, (bool *)yield_required) != ESP_OK) { + break; + } + node->p_curr_tx[tx_idx] = frame; + _node_start_trans(node, tx_idx); + tx_idx++; + } + return tx_idx; +} + +static void _node_mark_tx_idle(twai_onchip_ctx_t *node) +{ + memset(node->p_curr_tx, 0, sizeof(node->p_curr_tx)); + atomic_store(&node->hw_busy, false); +} + +static inline bool _node_is_tx_all_done(twai_onchip_ctx_t *node) +{ + for (uint8_t tx_idx = 0; tx_idx < node->tx_slot_num; tx_idx++) { + if (node->p_curr_tx[tx_idx]) { + return false; + } + } + return true; } static void _node_isr_main(void *arg) @@ -229,11 +263,10 @@ static void _node_isr_main(void *arg) } // node recover from busoff, restart remain tx transaction if ((e_data.old_sta == TWAI_ERROR_BUS_OFF) && (e_data.new_sta == TWAI_ERROR_ACTIVE)) { - if (xQueueReceiveFromISR(twai_ctx->tx_mount_queue, &twai_ctx->p_curr_tx, &do_yield)) { + if (_node_start_tx_batch_from_isr(twai_ctx, &do_yield)) { atomic_store(&twai_ctx->hw_busy, true); - _node_start_trans(twai_ctx); } else { - atomic_store(&twai_ctx->hw_busy, false); + _node_mark_tx_idle(twai_ctx); xEventGroupSetBitsFromISR(twai_ctx->event_group, TWAI_IDLE_EVENT_BIT, &do_yield); } } @@ -265,23 +298,27 @@ static void _node_isr_main(void *arg) } // deal TX event - if (events & TWAI_HAL_EVENT_TX_BUFF_FREE) { - if (twai_ctx->cbs.on_tx_done) { - twai_tx_done_event_data_t tx_ev = { - .is_tx_success = (events & TWAI_HAL_EVENT_TX_SUCCESS), // find 'on_error_cb' if not success - .done_tx_frame = twai_ctx->p_curr_tx, - }; - do_yield |= twai_ctx->cbs.on_tx_done(&twai_ctx->api_base, &tx_ev, twai_ctx->user_data); + if (events & TWAI_HAL_EVENT_TX_DONE_MASK) { + uint32_t tx_done_events = (events & TWAI_HAL_EVENT_TX_DONE_MASK) >> __builtin_ctz(TWAI_HAL_EVENT_TX0_DONE); + while (tx_done_events) { + uint32_t slot_event = tx_done_events & -tx_done_events; // get the lowest event bit + uint8_t tx_idx = __builtin_ctz(slot_event) / 2; + assert((tx_idx < twai_ctx->tx_slot_num) && twai_ctx->p_curr_tx[tx_idx]); + if (twai_ctx->cbs.on_tx_done) { + twai_tx_done_event_data_t tx_ev = { + .is_tx_success = (events & TWAI_HAL_EVENT_TX_SUCC_SLOT(tx_idx)), // find 'on_error_cb' if not success + .done_tx_frame = twai_ctx->p_curr_tx[tx_idx], + }; + do_yield |= twai_ctx->cbs.on_tx_done(&twai_ctx->api_base, &tx_ev, twai_ctx->user_data); + } + twai_ctx->p_curr_tx[tx_idx] = NULL; + tx_done_events &= ~slot_event; } - // start a new TX - if ((atomic_load(&twai_ctx->state) != TWAI_ERROR_BUS_OFF) && xQueueReceiveFromISR(twai_ctx->tx_mount_queue, &twai_ctx->p_curr_tx, &do_yield)) { - // Sanity check, must in `hw_busy` here, otherwise logic bug is somewhere - assert(twai_ctx->hw_busy); - _node_start_trans(twai_ctx); - } else { - atomic_store(&twai_ctx->hw_busy, false); - if (atomic_load(&twai_ctx->state) != TWAI_ERROR_BUS_OFF) { - // only when node is not in busoff here, means tx is finished + + // start a new TX batch only when all hardware TX buffers from this batch are done + if (_node_is_tx_all_done(twai_ctx) && (atomic_load(&twai_ctx->state) != TWAI_ERROR_BUS_OFF)) { + if (!_node_start_tx_batch_from_isr(twai_ctx, &do_yield)) { + _node_mark_tx_idle(twai_ctx); xEventGroupSetBitsFromISR(twai_ctx->event_group, TWAI_IDLE_EVENT_BIT, &do_yield); } } @@ -315,9 +352,7 @@ static void _node_destroy(twai_onchip_ctx_t *twai_ctx) if (twai_ctx->timer_intr_hdl) { esp_intr_free(twai_ctx->timer_intr_hdl); } - if (twai_ctx->tx_mount_queue) { - vQueueDeleteWithCaps(twai_ctx->tx_mount_queue); - } + twai_frame_queue_del(twai_ctx->tx_queue); if (twai_ctx->event_group) { vEventGroupDeleteWithCaps(twai_ctx->event_group); } @@ -386,6 +421,7 @@ static esp_err_t _node_calc_set_bit_timing(twai_node_handle_t node, const twai_t twai_timing_constraint_t hw_const = { .brp_min = TWAI_LL_BRP_MIN, .brp_max = TWAI_LL_BRP_MAX, + .prop_max = TWAI_LL_PROP_MAX, .tseg1_min = TWAI_LL_TSEG1_MIN, .tseg1_max = TWAI_LL_TSEG1_MAX, .tseg2_min = TWAI_LL_TSEG2_MIN, @@ -404,6 +440,7 @@ static esp_err_t _node_calc_set_bit_timing(twai_node_handle_t node, const twai_t twai_timing_advanced_config_t timing_adv_fd = {}; if (timing_fd->bitrate) { hw_const.brp_max = TWAI_LL_BRP_MAX_FD; + hw_const.prop_max = TWAI_LL_PROP_MAX_FD; hw_const.tseg1_max = TWAI_LL_TSEG1_MAX_FD; hw_const.tseg2_max = TWAI_LL_TSEG2_MAX_FD; hw_const.sjw_max = TWAI_LL_SJW_MAX_FD; @@ -456,7 +493,11 @@ static esp_err_t _node_enable(twai_node_handle_t node) atomic_store(&twai_ctx->state, hw_state); // continuing the transaction if there be if (atomic_load(&twai_ctx->hw_busy) && hw_state != TWAI_ERROR_BUS_OFF) { - _node_start_trans(twai_ctx); + for (uint8_t tx_idx = 0; tx_idx < twai_ctx->tx_slot_num; tx_idx++) { + if (twai_ctx->p_curr_tx[tx_idx]) { + _node_start_trans(twai_ctx, tx_idx); + } + } } ESP_RETURN_ON_ERROR(esp_intr_enable(twai_ctx->intr_hdl), TAG, "enable interrupt failed"); return ESP_OK; @@ -547,7 +588,7 @@ static esp_err_t _node_get_status(twai_node_handle_t node, twai_node_status_t *s status_ret->state = atomic_load(&twai_ctx->state); status_ret->tx_error_count = twai_hal_get_tec(twai_ctx->hal); status_ret->rx_error_count = twai_hal_get_rec(twai_ctx->hal); - status_ret->tx_queue_remaining = uxQueueSpacesAvailable(twai_ctx->tx_mount_queue); + status_ret->tx_queue_remaining = twai_frame_queue_get_free_space(twai_ctx->tx_queue); } if (record_ret) { *record_ret = twai_ctx->history; @@ -571,46 +612,25 @@ static esp_err_t _node_queue_tx(twai_node_handle_t node, const twai_frame_t *fra ESP_RETURN_ON_FALSE_ISR((!frame->header.brs) || (twai_ctx->valid_fd_timing), ESP_ERR_INVALID_ARG, TAG, "brs can't be used without config data_timing"); ESP_RETURN_ON_FALSE_ISR(!twai_ctx->hal->enable_listen_only, ESP_ERR_NOT_SUPPORTED, TAG, "node is config as listen only"); ESP_RETURN_ON_FALSE_ISR(atomic_load(&twai_ctx->state) != TWAI_ERROR_BUS_OFF, ESP_ERR_INVALID_STATE, TAG, "node is bus off"); - TickType_t ticks_to_wait = (timeout == -1) ? portMAX_DELAY : pdMS_TO_TICKS(timeout); xEventGroupClearBits(twai_ctx->event_group, TWAI_IDLE_EVENT_BIT); //going to send, clear the idle event bool false_var = false; if (atomic_compare_exchange_strong(&twai_ctx->hw_busy, &false_var, true)) { - twai_ctx->p_curr_tx = frame; - _node_start_trans(twai_ctx); + twai_ctx->p_curr_tx[0] = frame; // here only has one frame, using slot 0 + _node_start_trans(twai_ctx, 0); } else { // Hardware busy, need to queue the frame - BaseType_t is_isr_context = xPortInIsrContext(); - BaseType_t yield_required = pdFALSE; - - if (is_isr_context) { - // In ISR context - use ISR-safe queue operations - ESP_RETURN_ON_FALSE_ISR(xQueueSendFromISR(twai_ctx->tx_mount_queue, &frame, &yield_required), ESP_ERR_TIMEOUT, TAG, "tx queue full"); - } else { - // In task context - use normal queue operations - ESP_RETURN_ON_FALSE(xQueueSend(twai_ctx->tx_mount_queue, &frame, ticks_to_wait), ESP_ERR_TIMEOUT, TAG, "tx queue full"); - } + ESP_RETURN_ON_ERROR_ISR(twai_frame_queue_push_safe(twai_ctx->tx_queue, frame, frame->tx_queue_priority, timeout), TAG, "tx queue full"); // Second chance check for hardware availability false_var = false; if (atomic_compare_exchange_strong(&twai_ctx->hw_busy, &false_var, true)) { - BaseType_t dequeue_result; - if (is_isr_context) { - dequeue_result = xQueueReceiveFromISR(twai_ctx->tx_mount_queue, &twai_ctx->p_curr_tx, &yield_required); + if (twai_frame_queue_pop_safe(twai_ctx->tx_queue, &twai_ctx->p_curr_tx[0]) == ESP_OK) { + _node_start_trans(twai_ctx, 0); } else { - dequeue_result = xQueueReceive(twai_ctx->tx_mount_queue, &twai_ctx->p_curr_tx, 0); + // any reason here means frame already taken and maybe finished by fast hardware, so back `hw_busy` to false + atomic_store(&twai_ctx->hw_busy, false); } - - if (dequeue_result == pdTRUE) { - _node_start_trans(twai_ctx); - } else { - assert(false && "should always get frame at this moment"); - } - } - - // Handle ISR yield if required - if (is_isr_context && yield_required) { - portYIELD_FROM_ISR(); } } return ESP_OK; @@ -622,9 +642,9 @@ static esp_err_t _node_wait_tx_all_done(twai_node_handle_t node, int timeout) TickType_t ticks_to_wait = (timeout == -1) ? portMAX_DELAY : pdMS_TO_TICKS(timeout); ESP_RETURN_ON_FALSE(atomic_load(&twai_ctx->state) != TWAI_ERROR_BUS_OFF, ESP_ERR_INVALID_STATE, TAG, "node is bus off"); - // either hw_busy or tx_mount_queue is not empty, means tx is not finished + // either hw_busy or tx_queue is not empty, means tx is not finished // otherwise, hardware is idle, return immediately - if (atomic_load(&twai_ctx->hw_busy) || uxQueueMessagesWaiting(twai_ctx->tx_mount_queue)) { + if (atomic_load(&twai_ctx->hw_busy) || twai_frame_queue_get_count(twai_ctx->tx_queue)) { //wait for idle event bit but without clear it, every tasks block here can be waked up if (TWAI_IDLE_EVENT_BIT != xEventGroupWaitBits(twai_ctx->event_group, TWAI_IDLE_EVENT_BIT, pdFALSE, pdFALSE, ticks_to_wait)) { return ESP_ERR_TIMEOUT; @@ -680,9 +700,11 @@ esp_err_t twai_new_node_onchip(const twai_onchip_node_config_t *node_config, twa // state is in bus_off before enabled atomic_store(&node->state, TWAI_ERROR_BUS_OFF); - node->tx_mount_queue = xQueueCreateWithCaps(node_config->tx_queue_depth, sizeof(twai_frame_t *), TWAI_MALLOC_CAPS); + if (!node_config->flags.enable_listen_only) { + ESP_GOTO_ON_ERROR(twai_frame_queue_new(&node->tx_queue, node_config->tx_queue_depth, TWAI_MALLOC_CAPS), err, TAG, "no_mem"); + } node->event_group = xEventGroupCreateWithCaps(TWAI_MALLOC_CAPS); - ESP_GOTO_ON_FALSE((node->tx_mount_queue && node->event_group) || node_config->flags.enable_listen_only, ESP_ERR_NO_MEM, err, TAG, "no_mem"); + ESP_GOTO_ON_FALSE(node->event_group, ESP_ERR_NO_MEM, err, TAG, "no_mem"); uint32_t intr_flags = TWAI_INTR_ALLOC_FLAGS; intr_flags |= (node_config->intr_priority > 0) ? BIT(node_config->intr_priority) : ESP_INTR_FLAG_LOWMED; _lock_acquire(&s_platform.intr_mutex); // lock to prevent twai_intr and timer_intr registered to different cpu then triggered at the same time @@ -756,6 +778,11 @@ esp_err_t twai_new_node_onchip(const twai_onchip_node_config_t *node_config, twa .enable_loopback = node_config->flags.enable_loopback, }; ESP_GOTO_ON_FALSE(twai_hal_init(node->hal, &hal_config), ESP_ERR_INVALID_STATE, err, TAG, "hardware not in reset state"); + node->tx_slot_num = twai_hal_get_tx_slot_num(node->hal); + if (node->tx_slot_num > TWAI_HAL_TX_BUFFER_SLOT_NUM) { + ESP_LOGW(TAG, "HW TX slot num (%d) is greater than supported, only using %d slots", node->tx_slot_num, TWAI_HAL_TX_BUFFER_SLOT_NUM); + node->tx_slot_num = TWAI_HAL_TX_BUFFER_SLOT_NUM; + } // Configure bus timing ESP_GOTO_ON_ERROR(_node_calc_set_bit_timing(&node->api_base, &node_config->bit_timing, &node_config->data_timing), err, TAG, "bitrate error"); // Configure GPIO diff --git a/components/esp_driver_twai/include/esp_private/twai_frame_queue.h b/components/esp_driver_twai/include/esp_private/twai_frame_queue.h new file mode 100644 index 00000000000..961b738b7d2 --- /dev/null +++ b/components/esp_driver_twai/include/esp_private/twai_frame_queue.h @@ -0,0 +1,112 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once + +#include +#include +#include +#include "esp_err.h" +#include "esp_twai_types.h" + +//////////////////////////////////////////////////////////////////// +// !! This queue is ONLY for TWAI driver internal use // +//////////////////////////////////////////////////////////////////// + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct twai_frame_queue_s *twai_frame_queue_t; + +/** + * @brief Initialize a TWAI frame priority queue. + * + * Items are ordered by priority first. For equal priority values, items are popped in push order. + */ +esp_err_t twai_frame_queue_new(twai_frame_queue_t *queue, size_t capacity, uint32_t mem_caps); + +/** + * @brief Release resources used by a TWAI frame priority queue. + * + * @return + * - ESP_OK: Queue was released successfully + * - ESP_ERR_INVALID_ARG: Queue is invalid + */ +esp_err_t twai_frame_queue_del(twai_frame_queue_t queue); + +/** + * @brief Push an item with priority from task context. + * + * @return + * - ESP_OK: Item was queued successfully + * - ESP_ERR_INVALID_ARG: Queue is invalid + * - ESP_ERR_TIMEOUT: No free slot became available before timeout + */ +esp_err_t twai_frame_queue_push(twai_frame_queue_t queue, const twai_frame_t *data, uint32_t priority, int timeout_ms); + +/** + * @brief Push an item with priority from ISR context. + * + * @return + * - ESP_OK: Item was queued successfully + * - ESP_ERR_INVALID_ARG: Queue is invalid + * - ESP_ERR_TIMEOUT: Queue is full + */ +esp_err_t twai_frame_queue_push_from_isr(twai_frame_queue_t queue, const twai_frame_t *data, uint32_t priority, bool *task_woken); + +/** + * @brief Push an item with priority from task or ISR context. + * + * @return + * - ESP_OK: Item was queued successfully + * - ESP_ERR_INVALID_ARG: Queue is invalid + * - ESP_ERR_TIMEOUT: Queue is full or no free slot became available before timeout + */ +esp_err_t twai_frame_queue_push_safe(twai_frame_queue_t queue, const twai_frame_t *data, uint32_t priority, int timeout_ms); + +/** + * @brief Pop the highest-priority item from task context. + * + * @return + * - ESP_OK: Item was popped successfully + * - ESP_ERR_INVALID_ARG: Queue is invalid + * - ESP_ERR_NOT_FOUND: Queue is empty + */ +esp_err_t twai_frame_queue_pop(twai_frame_queue_t queue, const twai_frame_t **data); + +/** + * @brief Pop the highest-priority item from ISR context. + * + * @return + * - ESP_OK: Item was popped successfully + * - ESP_ERR_INVALID_ARG: Queue is invalid + * - ESP_ERR_NOT_FOUND: Queue is empty + */ +esp_err_t twai_frame_queue_pop_from_isr(twai_frame_queue_t queue, const twai_frame_t **data, bool *task_woken); + +/** + * @brief Pop the highest-priority item from task or ISR context. + * + * @return + * - ESP_OK: Item was popped successfully + * - ESP_ERR_INVALID_ARG: Queue is invalid + * - ESP_ERR_NOT_FOUND: Queue is empty + */ +esp_err_t twai_frame_queue_pop_safe(twai_frame_queue_t queue, const twai_frame_t **data); + +/** + * @brief Get the current number of queued items. + */ +size_t twai_frame_queue_get_count(twai_frame_queue_t queue); + +/** + * @brief Get the number of available queue slots. + */ +size_t twai_frame_queue_get_free_space(twai_frame_queue_t queue); + +#ifdef __cplusplus +} +#endif diff --git a/components/esp_driver_twai/include/esp_private/twai_utils.h b/components/esp_driver_twai/include/esp_private/twai_utils.h index 0b49a66d89d..587fc8a45f6 100644 --- a/components/esp_driver_twai/include/esp_private/twai_utils.h +++ b/components/esp_driver_twai/include/esp_private/twai_utils.h @@ -19,6 +19,7 @@ extern "C" { typedef struct { uint32_t brp_min; /* Bit-rate prescaler */ uint32_t brp_max; + uint8_t prop_max; /* Propagation segment */ uint8_t tseg1_min; /* Time segment 1 = prop_seg + phase_seg1 */ uint8_t tseg1_max; uint8_t tseg2_min; /* Time segment 2 = phase_seg2 */ diff --git a/components/esp_driver_twai/include/esp_twai_types.h b/components/esp_driver_twai/include/esp_twai_types.h index 8690153556b..f09d642e039 100644 --- a/components/esp_driver_twai/include/esp_twai_types.h +++ b/components/esp_driver_twai/include/esp_twai_types.h @@ -33,6 +33,7 @@ typedef struct { twai_frame_header_t header; /**< message attribute/metadata, exclude data buffer*/ uint8_t *buffer; /**< buffer address for tx and rx message data*/ size_t buffer_len; /**< buffer length of provided data buffer pointer, in bytes.*/ + uint8_t tx_queue_priority; /**< Frame priority, range [0, 255], a frame with higher priority value will be picked first from the queue */ } twai_frame_t; /** diff --git a/components/esp_driver_twai/linker.lf b/components/esp_driver_twai/linker.lf index 2fcbcef5929..08b6d79d4fd 100644 --- a/components/esp_driver_twai/linker.lf +++ b/components/esp_driver_twai/linker.lf @@ -6,10 +6,20 @@ entries: esp_twai_onchip: _node_isr_main (noflash) esp_twai_onchip: _node_start_trans (noflash) esp_twai_onchip: _node_parse_rx (noflash) + esp_twai_onchip: _node_start_tx_batch_from_isr (noflash) + esp_twai_onchip: _node_mark_tx_idle (noflash) + esp_twai_onchip: _node_is_tx_all_done (noflash) + twai_frame_queue: twai_frame_queue_pop_from_isr (noflash) if TWAI_IO_FUNC_IN_IRAM = y: esp_twai_onchip: _node_queue_tx (noflash) esp_twai: twai_node_transmit (noflash) + twai_frame_queue: twai_frame_queue_push (noflash) + twai_frame_queue: twai_frame_queue_push_from_isr (noflash) + twai_frame_queue: twai_frame_queue_push_safe (noflash) + twai_frame_queue: twai_frame_queue_pop (noflash) + twai_frame_queue: twai_frame_queue_pop_from_isr (noflash) + twai_frame_queue: twai_frame_queue_pop_safe (noflash) [mapping:twai_hal] archive: libesp_hal_twai.a diff --git a/components/esp_driver_twai/test_apps/test_twai/main/CMakeLists.txt b/components/esp_driver_twai/test_apps/test_twai/main/CMakeLists.txt index 24c3db7b0fb..a5f4b242223 100644 --- a/components/esp_driver_twai/test_apps/test_twai/main/CMakeLists.txt +++ b/components/esp_driver_twai/test_apps/test_twai/main/CMakeLists.txt @@ -1,7 +1,7 @@ set(srcs "test_app_main.c") if(CONFIG_SOC_TWAI_SUPPORTED) - list(APPEND srcs "test_twai_common.cpp" "test_twai_network.cpp") + list(APPEND srcs "test_twai_common.cpp" "test_twai_network.cpp" "test_twai_queue.cpp") if(CONFIG_SOC_LIGHT_SLEEP_SUPPORTED) list(APPEND srcs "test_twai_sleep.c") endif() diff --git a/components/esp_driver_twai/test_apps/test_twai/main/test_twai_common.cpp b/components/esp_driver_twai/test_apps/test_twai/main/test_twai_common.cpp index 41984be27e9..f2476cff014 100644 --- a/components/esp_driver_twai/test_apps/test_twai/main/test_twai_common.cpp +++ b/components/esp_driver_twai/test_apps/test_twai/main/test_twai_common.cpp @@ -23,13 +23,8 @@ #include "esp_private/gpio.h" #include "driver/uart.h" // for baudrate detection -#if CONFIG_IDF_TARGET_ESP32H4 -#define TEST_TX_GPIO GPIO_NUM_2 -#define TEST_RX_GPIO GPIO_NUM_3 -#else #define TEST_TX_GPIO GPIO_NUM_4 #define TEST_RX_GPIO GPIO_NUM_5 -#endif #define TEST_TWAI_QUEUE_DEPTH 5 #define TEST_TRANS_LEN 100 #define TEST_FRAME_LEN 7 @@ -57,7 +52,7 @@ TEST_CASE("twai install uninstall (loopback)", "[twai]") node_config.io_cfg.rx = TEST_TX_GPIO; // Using same pin for test without transceiver node_config.io_cfg.quanta_clk_out = GPIO_NUM_NC; node_config.io_cfg.bus_off_indicator = GPIO_NUM_NC; - node_config.bit_timing.bitrate = 1000000; + node_config.bit_timing.bitrate = 100000; node_config.tx_queue_depth = TEST_TWAI_QUEUE_DEPTH; node_config.flags.enable_self_test = true; node_config.flags.enable_loopback = true; @@ -153,7 +148,7 @@ static void test_twai_baudrate_correctness(twai_clock_source_t clk_src, uint32_t TEST_ESP_OK(uart_detect_bitrate_stop(UART_NUM_1, true, &measure_result)); uint32_t bitrate_measured = measure_result.clk_freq_hz * 4 / (measure_result.pos_period + measure_result.neg_period); printf("TWAI bitrate measured: %" PRIu32 "\r\n", bitrate_measured); - TEST_ASSERT_INT_WITHIN(1000, test_bitrate, bitrate_measured); // 1k tolerance + TEST_ASSERT_INT_WITHIN((test_bitrate / 100), test_bitrate, bitrate_measured); // 1% tolerance TEST_ESP_OK(twai_node_disable(twai_node)); TEST_ESP_OK(twai_node_delete(twai_node)); @@ -875,18 +870,17 @@ TEST_CASE("twai rx timestamp", "[twai]") node_config.flags.enable_loopback = true; node_config.flags.enable_self_test = true; - bool hw_timer = false; -#if TWAI_LL_SUPPORT(TIMESTAMP) - hw_timer = true; -#endif for (uint32_t resolution = 1000; resolution <= 10000000; resolution *= 100) { +#if CONFIG_IDF_TARGET_ESP32H4 + if (resolution > 1000000) { + continue; // h4 clk_src [32M, 96M] can't accurate support resolutions > 1MHz + } +#endif node_config.timestamp_resolution_hz = resolution; printf("\nTesting resolution %ld\n", resolution); - if (((resolution < 2000) && hw_timer) || ((resolution > 1000000) && !hw_timer)) { - TEST_ESP_ERR(twai_new_node_onchip(&node_config, &node_hdl), ESP_ERR_INVALID_ARG); + if (ESP_OK != twai_new_node_onchip(&node_config, &node_hdl)) { continue; } - TEST_ESP_OK(twai_new_node_onchip(&node_config, &node_hdl)); uint8_t rx_buffer[TWAI_FRAME_MAX_LEN] = {0}; twai_frame_t rx_frame = {}; @@ -911,8 +905,8 @@ TEST_CASE("twai rx timestamp", "[twai]") time_now = MS_TO_TWAI_TICK(esp_timer_get_time() / 1000, resolution); printf("esp tick now %llu, diff %u\n", time_now, abs(time_now - rx_frame.header.timestamp)); - TEST_ASSERT_INT32_WITHIN(MAX(resolution / 100, 5), time_now, rx_frame.header.timestamp); - TEST_ASSERT_INT32_WITHIN(MAX(resolution / 100, 5), rx_frame.header.timestamp - time_last, MS_TO_TWAI_TICK(i * 100, resolution)); + TEST_ASSERT_INT32_WITHIN(MAX(resolution / 100, 5), rx_frame.header.timestamp, time_now); + TEST_ASSERT_INT32_WITHIN(MAX(resolution / 100, 5), MS_TO_TWAI_TICK(i * 100, resolution), rx_frame.header.timestamp - time_last); time_last = rx_frame.header.timestamp; } @@ -923,7 +917,7 @@ TEST_CASE("twai rx timestamp", "[twai]") TEST_ESP_OK(twai_node_enable(node_hdl)); TEST_ESP_OK(twai_node_transmit(node_hdl, &tx_frame, 100)); TEST_ESP_OK(twai_node_transmit_wait_all_done(node_hdl, 100)); - TEST_ASSERT_INT32_WITHIN(MAX(resolution / 100, 5), rx_frame.header.timestamp - time_last, MS_TO_TWAI_TICK(1000, resolution)); + TEST_ASSERT_INT32_WITHIN(MAX(resolution / 100, 5), MS_TO_TWAI_TICK(1000, resolution), rx_frame.header.timestamp - time_last); TEST_ESP_OK(twai_node_disable(node_hdl)); TEST_ESP_OK(twai_node_delete(node_hdl)); diff --git a/components/esp_driver_twai/test_apps/test_twai/main/test_twai_fd.cpp b/components/esp_driver_twai/test_apps/test_twai/main/test_twai_fd.cpp index bc2c9da921d..05d76c77bec 100644 --- a/components/esp_driver_twai/test_apps/test_twai/main/test_twai_fd.cpp +++ b/components/esp_driver_twai/test_apps/test_twai/main/test_twai_fd.cpp @@ -195,6 +195,12 @@ TEST_CASE("twai fd transmit time (loopback)", "[twai]") uint64_t predict_time_ms = (uint64_t)trans_num * arb_bits * 1000 / node_config.bit_timing.bitrate; predict_time_ms += (uint64_t)trans_num * data_bits * 1000 / node_config.data_timing.bitrate; predict_time_ms += (trans_num * 10) / 1000; // add about 10 us interrupt overhead per frame +#if CONFIG_COMPILER_OPTIMIZATION_NONE + predict_time_ms += (trans_num * 5) / 1000; // non optimized slow code +#endif +#if CONFIG_PM_DFS_INIT_AUTO + predict_time_ms += (trans_num * 10) / 1000; // slow cpu +#endif //waiting pkg receive finish TEST_ESP_OK(twai_node_transmit_wait_all_done(node_hdl, -1)); @@ -211,7 +217,7 @@ TEST_CASE("twai fd transmit time (loopback)", "[twai]") (unsigned long long)predict_time_ms, memcmp(recv_pkg_ptr, send_pkg_ptr, TEST_TRANS_TIME_BUF_LEN) ? "failed" : "ok"); TEST_ASSERT_EQUAL_HEX8_ARRAY(send_pkg_ptr, recv_pkg_ptr, TEST_TRANS_TIME_BUF_LEN); - TEST_ASSERT_LESS_THAN((predict_time_ms / 10), abs((time2 - time1) / 1000 - predict_time_ms)); + TEST_ASSERT_LESS_THAN((predict_time_ms * 15 / 100), abs((time2 - time1) / 1000 - predict_time_ms)); } printf("-----------------------------------------------------------------------------------------\n"); diff --git a/components/esp_driver_twai/test_apps/test_twai/main/test_twai_queue.cpp b/components/esp_driver_twai/test_apps/test_twai/main/test_twai_queue.cpp new file mode 100644 index 00000000000..53b3030515d --- /dev/null +++ b/components/esp_driver_twai/test_apps/test_twai/main/test_twai_queue.cpp @@ -0,0 +1,282 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include +#include +#include "esp_err.h" +#include "esp_heap_caps.h" +#include "esp_twai_types.h" +#include "freertos/FreeRTOS.h" +#include "freertos/semphr.h" +#include "freertos/task.h" +#include "unity.h" +#include "esp_private/twai_frame_queue.h" + +typedef struct { + int value; + uint32_t priority; +} twai_frame_queue_test_item_t; + +static void test_twai_frame_queue_print_header(const char *title) +{ + printf("\n%s\n", title); + printf("+-------+----------+\n"); + printf("| value | priority |\n"); + printf("+-------+----------+\n"); +} + +static void test_twai_frame_queue_print_row(const twai_frame_queue_test_item_t *item) +{ + printf("| %5d | %8" PRIu32 " |\n", item->value, item->priority); +} + +static void test_twai_frame_queue_print_footer(void) +{ + printf("+-------+----------+\n"); +} + +static void test_twai_frame_queue_pop_in_order(twai_frame_queue_t queue, size_t expected_count, const char *title) +{ + const twai_frame_t *item = NULL; + const twai_frame_queue_test_item_t *last_item = NULL; + + test_twai_frame_queue_print_header(title); + for (size_t i = 0; i < expected_count; i++) { + TEST_ESP_OK(twai_frame_queue_pop(queue, &item)); + const twai_frame_queue_test_item_t *cur_item = (const twai_frame_queue_test_item_t *) item; + + test_twai_frame_queue_print_row(cur_item); + if (last_item) { + TEST_ASSERT_LESS_OR_EQUAL(last_item->priority, cur_item->priority); + if (cur_item->priority == last_item->priority) { + TEST_ASSERT_GREATER_THAN(last_item->value, cur_item->value); + } + } + + TEST_ASSERT_EQUAL(expected_count - i - 1, twai_frame_queue_get_count(queue)); + last_item = cur_item; + } + test_twai_frame_queue_print_footer(); +} + +TEST_CASE("test twai frame priority queue", "[twai]") +{ + twai_frame_queue_t test_q = NULL; + const twai_frame_t *item = NULL; + twai_frame_queue_test_item_t test_items[] = { + { 0, 1 }, { 1, 4 }, { 2, 2 }, { 3, 5 }, + { 4, 3 }, { 5, 5 }, { 6, 1 }, { 7, 4 }, + { 8, 2 }, { 9, 3 }, { 10, 5 }, { 11, 0 }, + { 12, 4 }, { 13, 2 }, { 14, 3 }, { 15, 1 }, + }; + twai_frame_queue_test_item_t overflow = { 16, 6 }; + const size_t capacity = sizeof(test_items) / sizeof(test_items[0]); + + TEST_ESP_OK(twai_frame_queue_new(&test_q, capacity, MALLOC_CAP_DEFAULT)); + TEST_ASSERT_EQUAL(0, twai_frame_queue_get_count(test_q)); + TEST_ASSERT_EQUAL(capacity, twai_frame_queue_get_free_space(test_q)); + + test_twai_frame_queue_print_header("Push all test data"); + for (size_t i = 0; i < capacity; i++) { + TEST_ESP_OK(twai_frame_queue_push(test_q, (const twai_frame_t *) &test_items[i], test_items[i].priority, 0)); + test_twai_frame_queue_print_row(&test_items[i]); + TEST_ASSERT_EQUAL(i + 1, twai_frame_queue_get_count(test_q)); + TEST_ASSERT_EQUAL(capacity - i - 1, twai_frame_queue_get_free_space(test_q)); + } + test_twai_frame_queue_print_footer(); + + TEST_ASSERT_EQUAL(ESP_ERR_TIMEOUT, twai_frame_queue_push(test_q, (const twai_frame_t *) &overflow, 6, 0)); + TEST_ASSERT_EQUAL(capacity, twai_frame_queue_get_count(test_q)); + TEST_ASSERT_EQUAL(0, twai_frame_queue_get_free_space(test_q)); + + test_twai_frame_queue_pop_in_order(test_q, capacity, "Pop all test data"); + TEST_ASSERT_EQUAL(ESP_ERR_NOT_FOUND, twai_frame_queue_pop(test_q, &item)); + + TEST_ASSERT_EQUAL(0, twai_frame_queue_get_count(test_q)); + TEST_ASSERT_EQUAL(capacity, twai_frame_queue_get_free_space(test_q)); + TEST_ESP_OK(twai_frame_queue_del(test_q)); +} + +TEST_CASE("test twai queue mixed pop and push", "[twai]") +{ + twai_frame_queue_t test_q = NULL; + const twai_frame_t *item = NULL; + twai_frame_queue_test_item_t test_items[] = { + { 0, 1 }, + { 1, 5 }, + { 2, 3 }, + { 3, 5 }, + { 4, 2 }, + { 5, 4 }, + { 6, 6 }, + { 7, 3 }, + { 8, 4 }, + }; + const size_t first_push_count = 6; + const size_t capacity = 8; + const size_t remaining_count = sizeof(test_items) / sizeof(test_items[0]) - 2; + + TEST_ESP_OK(twai_frame_queue_new(&test_q, capacity, MALLOC_CAP_DEFAULT)); + + test_twai_frame_queue_print_header("Initial push before refill"); + for (size_t i = 0; i < first_push_count; i++) { + TEST_ESP_OK(twai_frame_queue_push(test_q, (const twai_frame_t *) &test_items[i], test_items[i].priority, 0)); + test_twai_frame_queue_print_row(&test_items[i]); + } + test_twai_frame_queue_print_footer(); + TEST_ASSERT_EQUAL(first_push_count, twai_frame_queue_get_count(test_q)); + TEST_ASSERT_EQUAL(capacity - first_push_count, twai_frame_queue_get_free_space(test_q)); + + test_twai_frame_queue_print_header("Pop before refill"); + TEST_ESP_OK(twai_frame_queue_pop(test_q, &item)); + test_twai_frame_queue_print_row((const twai_frame_queue_test_item_t *) item); + TEST_ASSERT_EQUAL_PTR(&test_items[1], item); + TEST_ESP_OK(twai_frame_queue_pop(test_q, &item)); + test_twai_frame_queue_print_row((const twai_frame_queue_test_item_t *) item); + TEST_ASSERT_EQUAL_PTR(&test_items[3], item); + test_twai_frame_queue_print_footer(); + TEST_ASSERT_EQUAL(4, twai_frame_queue_get_count(test_q)); + TEST_ASSERT_EQUAL(4, twai_frame_queue_get_free_space(test_q)); + + test_twai_frame_queue_print_header("Push after partial pop"); + for (size_t i = first_push_count; i < sizeof(test_items) / sizeof(test_items[0]); i++) { + TEST_ESP_OK(twai_frame_queue_push(test_q, (const twai_frame_t *) &test_items[i], test_items[i].priority, 0)); + test_twai_frame_queue_print_row(&test_items[i]); + } + test_twai_frame_queue_print_footer(); + TEST_ASSERT_EQUAL(remaining_count, twai_frame_queue_get_count(test_q)); + TEST_ASSERT_EQUAL(1, twai_frame_queue_get_free_space(test_q)); + + test_twai_frame_queue_pop_in_order(test_q, remaining_count, "Pop after refill"); + TEST_ASSERT_EQUAL(ESP_ERR_NOT_FOUND, twai_frame_queue_pop(test_q, &item)); + TEST_ASSERT_EQUAL(0, twai_frame_queue_get_count(test_q)); + TEST_ASSERT_EQUAL(capacity, twai_frame_queue_get_free_space(test_q)); + TEST_ESP_OK(twai_frame_queue_del(test_q)); +} + +typedef struct { + twai_frame_queue_t queue; + twai_frame_queue_test_item_t *items; + bool *seen; + size_t total; + atomic_int push_tasks_done; + atomic_int pop_count; + atomic_bool failed; +} twai_frame_queue_concurrent_ctx_t; + +typedef struct { + twai_frame_queue_concurrent_ctx_t *ctx; + size_t begin; + size_t end; +} twai_frame_queue_push_args_t; + +static void twai_frame_queue_push_task(void *arg) +{ + twai_frame_queue_push_args_t *args = (twai_frame_queue_push_args_t *) arg; + twai_frame_queue_concurrent_ctx_t *ctx = args->ctx; + + printf("%s started\n", pcTaskGetName(NULL)); + for (size_t i = args->begin; i < args->end; i++) { + if (atomic_load(&ctx->failed)) { + vTaskDelete(NULL); + } + TEST_ESP_OK(twai_frame_queue_push(ctx->queue, (const twai_frame_t *) &ctx->items[i], ctx->items[i].priority, portMAX_DELAY)); + vTaskDelay(1); + } + atomic_fetch_add(&ctx->push_tasks_done, 1); + vTaskDelete(NULL); +} + +static void twai_frame_queue_pop_task(void *arg) +{ + twai_frame_queue_concurrent_ctx_t *ctx = (twai_frame_queue_concurrent_ctx_t *) arg; + + printf("%s started\n", pcTaskGetName(NULL)); + const twai_frame_t *item = NULL; + + while (atomic_load(&ctx->pop_count) < (int) ctx->total) { + if (twai_frame_queue_pop(ctx->queue, &item) == ESP_OK) { + const twai_frame_queue_test_item_t *cur = (const twai_frame_queue_test_item_t *) item; + int value = cur->value; + + if (value < 0 || (size_t) value >= ctx->total || ctx->seen[value]) { + atomic_store(&ctx->failed, true); + vTaskDelete(NULL); + } + ctx->seen[value] = true; + atomic_fetch_add(&ctx->pop_count, 1); + } else if (atomic_load(&ctx->push_tasks_done) == 2 && twai_frame_queue_get_count(ctx->queue) == 0) { + break; + } else { + vTaskDelay(1); + } + } + vTaskDelete(NULL); +} + +TEST_CASE("test twai queue concurrent push and pop", "[twai]") +{ + twai_frame_queue_t test_q = NULL; + const size_t item_count = 500; + const size_t capacity = 4; + twai_frame_queue_test_item_t *test_items = (twai_frame_queue_test_item_t *) malloc(item_count * sizeof(twai_frame_queue_test_item_t)); + bool seen[item_count] = {}; + twai_frame_queue_concurrent_ctx_t ctx = {}; + twai_frame_queue_push_args_t push_args0 = {}; + twai_frame_queue_push_args_t push_args1 = {}; + + for (size_t i = 0; i < item_count; i++) { + test_items[i].value = (int) i; + test_items[i].priority = (uint32_t)((i * 7 + 3) % 6); + } + + ctx.items = test_items; + ctx.seen = seen; + ctx.total = item_count; + + push_args0.ctx = &ctx; + push_args0.begin = 0; + push_args0.end = item_count / 2; + + push_args1.ctx = &ctx; + push_args1.begin = item_count / 2; + push_args1.end = item_count; + + TEST_ESP_OK(twai_frame_queue_new(&test_q, capacity, MALLOC_CAP_DEFAULT)); + ctx.queue = test_q; + + TEST_ASSERT_EQUAL(pdPASS, xTaskCreate(twai_frame_queue_pop_task, "twai_q_pop", 4096, &ctx, 5, NULL)); + TEST_ASSERT_EQUAL(pdPASS, xTaskCreate(twai_frame_queue_push_task, "twai_q_push0", 4096, &push_args0, 4, NULL)); + TEST_ASSERT_EQUAL(pdPASS, xTaskCreate(twai_frame_queue_push_task, "twai_q_push1", 4096, &push_args1, 4, NULL)); + + TickType_t start = xTaskGetTickCount(); + while (atomic_load(&ctx.push_tasks_done) < 2 || atomic_load(&ctx.pop_count) < (int) item_count) { + TEST_ASSERT_FALSE(atomic_load(&ctx.failed)); + if ((xTaskGetTickCount() - start) > pdMS_TO_TICKS(5000)) { + TEST_FAIL_MESSAGE("concurrent push/pop timed out"); + } + vTaskDelay(1); + } + + TEST_ASSERT_FALSE(atomic_load(&ctx.failed)); + TEST_ASSERT_EQUAL(2, atomic_load(&ctx.push_tasks_done)); + printf("pop %d items\n", atomic_load(&ctx.pop_count)); + TEST_ASSERT_EQUAL((int) item_count, atomic_load(&ctx.pop_count)); + TEST_ASSERT_EQUAL(0, twai_frame_queue_get_count(test_q)); + TEST_ASSERT_EQUAL(capacity, twai_frame_queue_get_free_space(test_q)); + for (size_t i = 0; i < item_count; i++) { + TEST_ASSERT_TRUE(seen[i]); + } + + printf("test finished\n"); + free(test_items); + TEST_ESP_OK(twai_frame_queue_del(test_q)); + vTaskDelay(10); // wait for tasks to be deleted +} diff --git a/components/esp_driver_twai/test_apps/test_twai/pytest_driver_twai.py b/components/esp_driver_twai/test_apps/test_twai/pytest_driver_twai.py index 9ffe1a0513a..7133c929a70 100644 --- a/components/esp_driver_twai/test_apps/test_twai/pytest_driver_twai.py +++ b/components/esp_driver_twai/test_apps/test_twai/pytest_driver_twai.py @@ -1,6 +1,6 @@ # SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD # SPDX-License-Identifier: Apache-2.0 - +import os import subprocess import time @@ -11,6 +11,9 @@ from pytest_embedded import Dut from pytest_embedded_idf.utils import idf_parametrize from pytest_embedded_idf.utils import soc_filtered_targets +can_env = os.getenv('CAN_PORT', 'can0') +print(f'CAN_PORT={can_env}') + # --------------------------------------------------------------------------- # Loop Back Tests @@ -56,26 +59,24 @@ def esp_reset_and_wait_ready(dut: Dut) -> None: @pytest.fixture(name='socket_can') def fixture_create_socket_can() -> Bus: # Set up the socket CAN with the bitrate - start_command = 'sudo -n ip link set can0 up type can bitrate 250000 restart-ms 100' - stop_command = 'sudo -n ip link set can0 down' - status_command = 'sudo -n ip -details link show can0' + start_command = f'sudo -n ip link set {can_env} up type can bitrate 250000' + stop_command = f'sudo -n ip link set {can_env} down' + status_command = f'sudo -n ip -details link show {can_env}' try: result = subprocess.run(status_command, shell=True, capture_output=True, text=True) if result.returncode != 0: - raise Exception('CAN interface "can0" not found') + raise Exception(f'CAN interface "{can_env}" not found') if 'UP' in result.stdout: # Close the bus anyway if it is already up subprocess.run(stop_command, shell=True, capture_output=True, text=True) subprocess.run(start_command, shell=True, capture_output=True, text=True) time.sleep(0.5) - bus = Bus(interface='socketcan', channel='can0', bitrate=250000) + bus = Bus(interface='socketcan', channel=f'{can_env}', bitrate=250000) yield bus # test invoked here bus.shutdown() - except Exception as e: - pytest.skip(f'Open usb-can bus Error: {str(e)}') finally: subprocess.run(stop_command, shell=True, capture_output=True, text=True) @@ -83,51 +84,58 @@ def fixture_create_socket_can() -> Bus: # --------------------------------------------------------------------------- # Interactive Tests # --------------------------------------------------------------------------- -@pytest.mark.twai_std -@pytest.mark.temp_skip_ci(targets=['esp32h4'], reason='no runner') +@pytest.mark.twai_adapter +@pytest.mark.temp_skip_ci(targets=['esp32s31'], reason='no runner') @pytest.mark.parametrize('config', ['release'], indirect=True) @idf_parametrize('target', soc_filtered_targets('SOC_TWAI_SUPPORTED == 1'), indirect=['target']) def test_driver_twai_listen_only(dut: Dut, socket_can: Bus) -> None: - esp_reset_and_wait_ready(dut) + try: + esp_reset_and_wait_ready(dut) - dut.write('"twai_listen_only"') + dut.write('"twai_listen_only"') + # wait the DUT to finish initialize + time.sleep(0.1) - # wait the DUT to finish initialize - time.sleep(0.1) - - message = Message( - arbitration_id=0x6688, - is_extended_id=True, - data=[0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88], - ) - print('USB Socket CAN Send:', message, 'Return:', socket_can.send(message)) - dut.expect_unity_test_output(timeout=10) - esp_enter_flash_mode(dut) + message = Message( + arbitration_id=0x6688, + is_extended_id=True, + data=[0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88], + ) + print('USB Socket CAN Send:', message, 'Return:', socket_can.send(message)) + dut.expect_unity_test_output(timeout=10) + finally: + esp_enter_flash_mode(dut) -@pytest.mark.twai_std -@pytest.mark.temp_skip_ci(targets=['esp32h4'], reason='no runner') +@pytest.mark.twai_adapter +@pytest.mark.temp_skip_ci(targets=['esp32s31'], reason='no runner') @pytest.mark.parametrize('config', ['release'], indirect=True) @idf_parametrize('target', soc_filtered_targets('SOC_TWAI_SUPPORTED == 1'), indirect=['target']) def test_driver_twai_remote_request(dut: Dut, socket_can: Bus) -> None: - esp_reset_and_wait_ready(dut) + try: + esp_reset_and_wait_ready(dut) - dut.write('"twai_remote_request"') + dut.write('"twai_remote_request"') + print('Waiting remote frame ...') + deadline = time.time() + 5.0 + req = None + while time.time() < deadline: + req = socket_can.recv(timeout=0.2) + if req is not None and req.is_remote_frame: + break - print('Waiting remote frame ...') - while True: - req = socket_can.recv(timeout=0.2) - if req is not None and req.is_remote_frame: - break - print(f'USB Socket CAN Received: {req}') + if req is None: + raise Exception('Remote frame not received') + print(f'USB Socket CAN Received: {req}') - reply = Message( - arbitration_id=req.arbitration_id, - is_extended_id=req.is_extended_id, - data=[0x80, 0x70, 0x60, 0x50, 0x40, 0x30, 0x20, 0x10], - ) - socket_can.send(reply, timeout=0.2) - print('USB Socket CAN Replied:', reply) + reply = Message( + arbitration_id=req.arbitration_id, + is_extended_id=req.is_extended_id, + data=[0x80, 0x70, 0x60, 0x50, 0x40, 0x30, 0x20, 0x10], + ) + socket_can.send(reply, timeout=0.2) + print('USB Socket CAN Replied:', reply) - dut.expect_unity_test_output(timeout=10) - esp_enter_flash_mode(dut) + dut.expect_unity_test_output(timeout=10) + finally: + esp_enter_flash_mode(dut) diff --git a/components/esp_driver_twai/twai_frame_queue.c b/components/esp_driver_twai/twai_frame_queue.c new file mode 100644 index 00000000000..be453601de4 --- /dev/null +++ b/components/esp_driver_twai/twai_frame_queue.c @@ -0,0 +1,277 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include "esp_attr.h" +#include "esp_heap_caps.h" +#include "freertos/FreeRTOS.h" +#include "freertos/semphr.h" +#include "esp_private/twai_frame_queue.h" + +/** + * @brief Priority queue item for TWAI frame pointers. + * + * The queue stores TWAI frame pointers and orders them by priority. + * Higher priority values are popped first; equal priorities keep FIFO order. + */ +typedef struct { + const twai_frame_t *data; /**< TWAI frame pointer stored in the queue */ + uint32_t priority; /**< Higher value is dequeued first */ + uint64_t seq; /**< Sequence number used to keep FIFO order for equal priority */ +} twai_frame_queue_item_t; + +/** + * @brief Priority queue used by the TWAI driver to schedule queued frames. + */ +struct twai_frame_queue_s { + portMUX_TYPE spinlock; /**< Protects heap storage in task and ISR context */ + SemaphoreHandle_t spaces_sem; /**< Counts available queue slots */ + twai_frame_queue_item_t *items; /**< Binary heap storage */ + size_t capacity; /**< Maximum number of items */ + size_t count; /**< Current number of queued items */ + uint64_t next_seq; /**< Next sequence number assigned on push */ +}; + +static inline IRAM_ATTR bool twai_frame_queue_item_greater(const twai_frame_queue_item_t *a, const twai_frame_queue_item_t *b) +{ + if (a->priority != b->priority) { + return a->priority > b->priority; + } + return a->seq < b->seq; +} + +static inline IRAM_ATTR void twai_frame_queue_swap_item(twai_frame_queue_item_t *a, twai_frame_queue_item_t *b) +{ + twai_frame_queue_item_t tmp = *a; + *a = *b; + *b = tmp; +} + +static IRAM_ATTR void twai_frame_queue_sift_up(twai_frame_queue_t queue, size_t index) +{ + while (index > 0) { + size_t parent = (index - 1) / 2; + if (!twai_frame_queue_item_greater(&queue->items[index], &queue->items[parent])) { + break; + } + twai_frame_queue_swap_item(&queue->items[index], &queue->items[parent]); + index = parent; + } +} + +static IRAM_ATTR void twai_frame_queue_sift_down(twai_frame_queue_t queue, size_t index) +{ + while (true) { + size_t left = index * 2 + 1; + size_t right = left + 1; + size_t highest = index; + + if ((left < queue->count) && twai_frame_queue_item_greater(&queue->items[left], &queue->items[highest])) { + highest = left; + } + if ((right < queue->count) && twai_frame_queue_item_greater(&queue->items[right], &queue->items[highest])) { + highest = right; + } + if (highest == index) { + break; + } + twai_frame_queue_swap_item(&queue->items[index], &queue->items[highest]); + index = highest; + } +} + +static inline IRAM_ATTR void twai_frame_queue_push_locked(twai_frame_queue_t queue, const twai_frame_t *data, uint32_t priority) +{ + size_t index = queue->count++; + queue->items[index] = (twai_frame_queue_item_t) { + .data = data, + .priority = priority, + .seq = queue->next_seq++, + }; + twai_frame_queue_sift_up(queue, index); +} + +static inline IRAM_ATTR bool twai_frame_queue_pop_locked(twai_frame_queue_t queue, const twai_frame_t **data) +{ + if (queue->count == 0) { + return false; + } + + *data = queue->items[0].data; + queue->count--; + if (queue->count > 0) { + queue->items[0] = queue->items[queue->count]; + twai_frame_queue_sift_down(queue, 0); + } + return true; +} + +esp_err_t twai_frame_queue_new(twai_frame_queue_t *queue, size_t capacity, uint32_t mem_caps) +{ + if (!queue || !capacity) { + return ESP_ERR_INVALID_ARG; + } + + twai_frame_queue_t q_ctx = heap_caps_calloc(1, sizeof(struct twai_frame_queue_s), mem_caps); + if (!q_ctx) { + return ESP_ERR_NO_MEM; + } + q_ctx->items = heap_caps_calloc(capacity, sizeof(twai_frame_queue_item_t), mem_caps); + if (!q_ctx->items) { + heap_caps_free(q_ctx); + return ESP_ERR_NO_MEM; + } + q_ctx->spaces_sem = xSemaphoreCreateCountingWithCaps(capacity, capacity, mem_caps); + if (!q_ctx->spaces_sem) { + heap_caps_free(q_ctx->items); + heap_caps_free(q_ctx); + return ESP_ERR_NO_MEM; + } + q_ctx->spinlock = (portMUX_TYPE) portMUX_INITIALIZER_UNLOCKED; + q_ctx->capacity = capacity; + + *queue = q_ctx; + return ESP_OK; +} + +esp_err_t twai_frame_queue_del(twai_frame_queue_t queue) +{ + if (!queue) { + return ESP_ERR_INVALID_ARG; + } + if (queue->spaces_sem) { + vSemaphoreDeleteWithCaps(queue->spaces_sem); + } + if (queue->items) { + heap_caps_free(queue->items); + } + heap_caps_free(queue); + return ESP_OK; +} + +esp_err_t twai_frame_queue_push(twai_frame_queue_t queue, const twai_frame_t *data, uint32_t priority, int timeout_ms) +{ + if (!queue || !queue->spaces_sem) { + return ESP_ERR_INVALID_ARG; + } + + TickType_t ticks_to_wait = (timeout_ms == -1) ? portMAX_DELAY : pdMS_TO_TICKS(timeout_ms); + if (xSemaphoreTake(queue->spaces_sem, ticks_to_wait) != pdTRUE) { + return ESP_ERR_TIMEOUT; + } + + portENTER_CRITICAL(&queue->spinlock); + twai_frame_queue_push_locked(queue, data, priority); + portEXIT_CRITICAL(&queue->spinlock); + return ESP_OK; +} + +esp_err_t twai_frame_queue_push_from_isr(twai_frame_queue_t queue, const twai_frame_t *data, uint32_t priority, bool *task_woken) +{ + if (!queue || !queue->spaces_sem) { + return ESP_ERR_INVALID_ARG; + } + + if (xSemaphoreTakeFromISR(queue->spaces_sem, (BaseType_t *)task_woken) != pdTRUE) { + return ESP_ERR_TIMEOUT; + } + + portENTER_CRITICAL_ISR(&queue->spinlock); + twai_frame_queue_push_locked(queue, data, priority); + portEXIT_CRITICAL_ISR(&queue->spinlock); + return ESP_OK; +} + +esp_err_t twai_frame_queue_push_safe(twai_frame_queue_t queue, const twai_frame_t *data, uint32_t priority, int timeout_ms) +{ + if (xPortInIsrContext()) { + bool task_woken = false; + esp_err_t ret = twai_frame_queue_push_from_isr(queue, data, priority, &task_woken); + if (task_woken) { + portYIELD_FROM_ISR(); + } + return ret; + } + TickType_t ticks_to_wait = (timeout_ms == -1) ? portMAX_DELAY : pdMS_TO_TICKS(timeout_ms); + return twai_frame_queue_push(queue, data, priority, ticks_to_wait); +} + +esp_err_t twai_frame_queue_pop(twai_frame_queue_t queue, const twai_frame_t **data) +{ + bool ret; + + if (!queue || !queue->spaces_sem) { + return ESP_ERR_INVALID_ARG; + } + + portENTER_CRITICAL(&queue->spinlock); + ret = twai_frame_queue_pop_locked(queue, data); + portEXIT_CRITICAL(&queue->spinlock); + + if (ret) { + xSemaphoreGive(queue->spaces_sem); + } + return ret ? ESP_OK : ESP_ERR_NOT_FOUND; +} + +esp_err_t twai_frame_queue_pop_from_isr(twai_frame_queue_t queue, const twai_frame_t **data, bool *task_woken) +{ + bool ret; + + if (!queue || !queue->spaces_sem) { + return ESP_ERR_INVALID_ARG; + } + + portENTER_CRITICAL_ISR(&queue->spinlock); + ret = twai_frame_queue_pop_locked(queue, data); + portEXIT_CRITICAL_ISR(&queue->spinlock); + + if (ret) { + xSemaphoreGiveFromISR(queue->spaces_sem, (BaseType_t *)task_woken); + } + return ret ? ESP_OK : ESP_ERR_NOT_FOUND; +} + +esp_err_t twai_frame_queue_pop_safe(twai_frame_queue_t queue, const twai_frame_t **data) +{ + if (xPortInIsrContext()) { + bool task_woken = false; + esp_err_t ret = twai_frame_queue_pop_from_isr(queue, data, &task_woken); + if (task_woken) { + portYIELD_FROM_ISR(); + } + return ret; + } + return twai_frame_queue_pop(queue, data); +} + +size_t twai_frame_queue_get_count(twai_frame_queue_t queue) +{ + size_t count; + + if (!queue || !queue->spaces_sem) { + return 0; + } + + portENTER_CRITICAL(&queue->spinlock); + count = queue->count; + portEXIT_CRITICAL(&queue->spinlock); + return count; +} + +size_t twai_frame_queue_get_free_space(twai_frame_queue_t queue) +{ + size_t count; + + if (!queue || !queue->spaces_sem) { + return 0; + } + + portENTER_CRITICAL(&queue->spinlock); + count = queue->count; + portEXIT_CRITICAL(&queue->spinlock); + return queue->capacity - count; +} diff --git a/components/esp_driver_twai/twai_private.h b/components/esp_driver_twai/twai_private.h index db4364163c3..ad6470af9e1 100644 --- a/components/esp_driver_twai/twai_private.h +++ b/components/esp_driver_twai/twai_private.h @@ -10,6 +10,7 @@ #include #include #include +#include #include #include "sdkconfig.h" #if CONFIG_TWAI_ENABLE_DEBUG_LOG diff --git a/components/esp_driver_uart/include/driver/uhci_types.h b/components/esp_driver_uart/include/driver/uhci_types.h index 00c5960796a..4d7b19d56af 100644 --- a/components/esp_driver_uart/include/driver/uhci_types.h +++ b/components/esp_driver_uart/include/driver/uhci_types.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -48,11 +48,11 @@ typedef bool (*uhci_tx_done_callback_t)(uhci_controller_handle_t uhci_ctrl, cons * @brief UHCI RX Done Event Data Structure */ typedef struct { - uint8_t *data; /*!< Pointer to the received data buffer */ + const uint8_t *data; /*!< Pointer to the received data buffer. Data pointed to by this pointer is typically only guaranteed to be readable during the callback. If you need to use it after callback returns, copy it to external buffer first or refer to advanced zero-copy usage. */ size_t recv_size; /*!< Number of bytes received */ struct { uint32_t totally_received: 1; /*!< When callback is invoked, while this bit is not set, means the current event gives partial of whole data, the transaction has not been finished. If set, means the current event gives whole data, the transaction finished. */ - } flags; /*!< I2C master config flags */ + } flags; /*!< UHCI RX event flags */ } uhci_rx_event_data_t; /** @@ -60,7 +60,8 @@ typedef struct { * @param uhci_ctrl Handle to the UHCI controller that initiated the transmission. * @param edata Pointer to a structure containing event data related to receive event. * This structure provides details such as the number of bytes received and any - * status information relevant to the operation. + * status information relevant to the operation. The `edata` pointer is only valid + * during the callback. So do not save this pointer and use it outside the callback. * @param user_ctx User-defined context passed during the callback registration. * It can be used to maintain application-specific state or data. * diff --git a/components/esp_driver_uart/src/uhci.c b/components/esp_driver_uart/src/uhci.c index 7f5590a9f60..08f94691620 100644 --- a/components/esp_driver_uart/src/uhci.c +++ b/components/esp_driver_uart/src/uhci.c @@ -131,7 +131,7 @@ static bool uhci_gdma_rx_callback_done(gdma_channel_handle_t dma_chan, gdma_even if (cache_line > 0) { // The per-node buffer base is aligned to cache_line (see uhci_receive), and rx_size here // equals buffer_size_per_desc_node[] which is also a multiple of cache_line. - esp_cache_msync(evt_data.data, rx_size, ESP_CACHE_MSYNC_FLAG_DIR_M2C); + esp_cache_msync((void *)evt_data.data, rx_size, ESP_CACHE_MSYNC_FLAG_DIR_M2C); } if (uhci_ctrl->rx_dir.on_rx_trans_event) { need_yield |= uhci_ctrl->rx_dir.on_rx_trans_event(uhci_ctrl, &evt_data, uhci_ctrl->user_data); @@ -167,7 +167,7 @@ static bool uhci_gdma_rx_callback_done(gdma_channel_handle_t dma_chan, gdma_even // is harmless. if (cache_line > 0) { size_t sync_size = (rx_size + cache_line - 1) & ~(cache_line - 1); - esp_cache_msync(evt_data.data, sync_size, ESP_CACHE_MSYNC_FLAG_DIR_M2C); + esp_cache_msync((void *)evt_data.data, sync_size, ESP_CACHE_MSYNC_FLAG_DIR_M2C); } if (uhci_ctrl->rx_dir.on_rx_trans_event) { need_yield |= uhci_ctrl->rx_dir.on_rx_trans_event(uhci_ctrl, &evt_data, uhci_ctrl->user_data); diff --git a/components/esp_driver_usb_serial_jtag/test_apps/.build-test-rules.yml b/components/esp_driver_usb_serial_jtag/test_apps/.build-test-rules.yml index e66b8eb9e0a..daa1d1749cf 100644 --- a/components/esp_driver_usb_serial_jtag/test_apps/.build-test-rules.yml +++ b/components/esp_driver_usb_serial_jtag/test_apps/.build-test-rules.yml @@ -7,7 +7,7 @@ components/esp_driver_usb_serial_jtag/test_apps/usb_serial_jtag: temporary: true reason: p4 rev3 migration # TODO: IDF-14364 disable_test: - - if: IDF_TARGET in ["esp32c5", "esp32h4", "esp32h21"] + - if: IDF_TARGET in ["esp32h4", "esp32h21"] temporary: true reason: No runners. depends_components: @@ -24,7 +24,7 @@ components/esp_driver_usb_serial_jtag/test_apps/usb_serial_jtag_vfs: temporary: true reason: p4 rev3 migration # TODO: IDF-14364 disable_test: - - if: IDF_TARGET in ["esp32c5", "esp32h4", "esp32h21"] + - if: IDF_TARGET in ["esp32h4", "esp32h21"] temporary: true reason: No runners. depends_components: diff --git a/components/esp_driver_usb_serial_jtag/test_apps/usb_serial_jtag/pytest_usb_serial_jtag.py b/components/esp_driver_usb_serial_jtag/test_apps/usb_serial_jtag/pytest_usb_serial_jtag.py index c2309eea949..58327aed9d1 100644 --- a/components/esp_driver_usb_serial_jtag/test_apps/usb_serial_jtag/pytest_usb_serial_jtag.py +++ b/components/esp_driver_usb_serial_jtag/test_apps/usb_serial_jtag/pytest_usb_serial_jtag.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2023-2025 Espressif Systems (Shanghai) CO LTD +# SPDX-FileCopyrightText: 2023-2026 Espressif Systems (Shanghai) CO LTD # SPDX-License-Identifier: CC0-1.0 import pytest from pytest_embedded import Dut @@ -13,7 +13,9 @@ from pytest_embedded_idf.utils import idf_parametrize ], indirect=True, ) -@idf_parametrize('target', ['esp32s3', 'esp32c3', 'esp32c6', 'esp32h2', 'esp32c61', 'esp32p4'], indirect=['target']) +@idf_parametrize( + 'target', ['esp32s3', 'esp32c3', 'esp32c6', 'esp32h2', 'esp32c61', 'esp32p4', 'esp32c5'], indirect=['target'] +) def test_usb_serial_jtag_dev(dut: Dut) -> None: # type: ignore dut.expect_exact('Press ENTER to see the list of tests') dut.write('"test print via usb_serial_jtag driver multiple times in different tasks"') @@ -35,7 +37,9 @@ def test_usb_serial_jtag_dev(dut: Dut) -> None: # type: ignore ], indirect=True, ) -@idf_parametrize('target', ['esp32s3', 'esp32c3', 'esp32c6', 'esp32h2', 'esp32c61', 'esp32p4'], indirect=['target']) +@idf_parametrize( + 'target', ['esp32s3', 'esp32c3', 'esp32c6', 'esp32h2', 'esp32c61', 'esp32p4', 'esp32c5'], indirect=['target'] +) def test_usb_serial_jtag_rom_dev(dut: Dut) -> None: # type: ignore dut.expect_exact('Press ENTER to see the list of tests') dut.write('"test rom printf work after driver installed"') diff --git a/components/esp_driver_usb_serial_jtag/test_apps/usb_serial_jtag_vfs/pytest_usb_serial_jtag_vfs.py b/components/esp_driver_usb_serial_jtag/test_apps/usb_serial_jtag_vfs/pytest_usb_serial_jtag_vfs.py index 969b81d423f..737dc801429 100644 --- a/components/esp_driver_usb_serial_jtag/test_apps/usb_serial_jtag_vfs/pytest_usb_serial_jtag_vfs.py +++ b/components/esp_driver_usb_serial_jtag/test_apps/usb_serial_jtag_vfs/pytest_usb_serial_jtag_vfs.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD +# SPDX-FileCopyrightText: 2024-2026 Espressif Systems (Shanghai) CO LTD # SPDX-License-Identifier: CC0-1.0 import pytest from pytest_embedded import Dut @@ -13,7 +13,9 @@ from pytest_embedded_idf.utils import idf_parametrize ], indirect=True, ) -@idf_parametrize('target', ['esp32s3', 'esp32c3', 'esp32c6', 'esp32h2', 'esp32c61', 'esp32p4'], indirect=['target']) +@idf_parametrize( + 'target', ['esp32s3', 'esp32c3', 'esp32c6', 'esp32h2', 'esp32c61', 'esp32p4', 'esp32c5'], indirect=['target'] +) def test_usj_vfs_select(dut: Dut) -> None: test_message = 'test123456789!@#%^&*' @@ -33,7 +35,9 @@ def test_usj_vfs_select(dut: Dut) -> None: ], indirect=True, ) -@idf_parametrize('target', ['esp32s3', 'esp32c3', 'esp32c6', 'esp32h2', 'esp32c61', 'esp32p4'], indirect=['target']) +@idf_parametrize( + 'target', ['esp32s3', 'esp32c3', 'esp32c6', 'esp32h2', 'esp32c61', 'esp32p4', 'esp32c5'], indirect=['target'] +) def test_usj_vfs_read_return(dut: Dut) -> None: test_message = '!(@*#&(!*@&#((SDasdkjhad\nce' diff --git a/components/esp_hal_ana_conv/esp32s3/include/hal/adc_ll.h b/components/esp_hal_ana_conv/esp32s3/include/hal/adc_ll.h index b936ef2dd98..25065bbe758 100644 --- a/components/esp_hal_ana_conv/esp32s3/include/hal/adc_ll.h +++ b/components/esp_hal_ana_conv/esp32s3/include/hal/adc_ll.h @@ -490,11 +490,11 @@ static inline void adc_ll_digi_filter_enable(adc_digi_iir_filter_t idx, adc_unit static inline void adc_ll_digi_monitor_set_thres(adc_monitor_id_t monitor_id, adc_unit_t adc_n, uint8_t channel, int32_t h_thresh, int32_t l_thresh) { if (monitor_id == ADC_MONITOR_0) { - APB_SARADC.thres0_ctrl.thres0_channel = (adc_n << 3) | (channel & 0x7); + APB_SARADC.thres0_ctrl.thres0_channel = (adc_n << 4) | (channel & 0xF); APB_SARADC.thres0_ctrl.thres0_high = h_thresh; APB_SARADC.thres0_ctrl.thres0_low = l_thresh; } else { // ADC_MONITOR_1 - APB_SARADC.thres1_ctrl.thres1_channel = (adc_n << 3) | (channel & 0x7); + APB_SARADC.thres1_ctrl.thres1_channel = (adc_n << 4) | (channel & 0xF); APB_SARADC.thres1_ctrl.thres1_high = h_thresh; APB_SARADC.thres1_ctrl.thres1_low = l_thresh; } diff --git a/components/esp_hal_dma/esp32c5/include/hal/ahb_dma_ll.h b/components/esp_hal_dma/esp32c5/include/hal/ahb_dma_ll.h index acaeb488d9d..f757652c380 100644 --- a/components/esp_hal_dma/esp32c5/include/hal/ahb_dma_ll.h +++ b/components/esp_hal_dma/esp32c5/include/hal/ahb_dma_ll.h @@ -24,8 +24,8 @@ extern "C" { #define GDMA_LL_CHANNEL_MAX_PRIORITY 5 // supported priority levels: [0,5] #define GDMA_LL_CHANNEL_MAX_WEIGHT 15 // supported weight levels: [0,15] -#define GDMA_LL_RX_EVENT_MASK (0x7F) -#define GDMA_LL_TX_EVENT_MASK (0x3F) +#define AHB_DMA_LL_RX_EVENT_MASK (0x7F) +#define AHB_DMA_LL_TX_EVENT_MASK (0x3F) // for M2M mode, hardware will automatically assign peri_sel ID depends on the channel number (ch0: 10, ch1: 11, ch2: 12) #define AHB_DMA_LL_M2M_FREE_PERIPH_ID_MASK (0x1C00) diff --git a/components/esp_hal_dma/esp32c61/include/hal/ahb_dma_ll.h b/components/esp_hal_dma/esp32c61/include/hal/ahb_dma_ll.h index 0bde87a1fdc..1372aa552d4 100644 --- a/components/esp_hal_dma/esp32c61/include/hal/ahb_dma_ll.h +++ b/components/esp_hal_dma/esp32c61/include/hal/ahb_dma_ll.h @@ -24,8 +24,8 @@ extern "C" { #define GDMA_LL_CHANNEL_MAX_PRIORITY 5 // supported priority levels: [0,5] #define GDMA_LL_CHANNEL_MAX_WEIGHT 15 // supported weight levels: [0,15] -#define GDMA_LL_RX_EVENT_MASK (0x7F) -#define GDMA_LL_TX_EVENT_MASK (0x3F) +#define AHB_DMA_LL_RX_EVENT_MASK (0x7F) +#define AHB_DMA_LL_TX_EVENT_MASK (0x3F) // any "dummy" peripheral ID can be used for M2M mode #define AHB_DMA_LL_M2M_FREE_PERIPH_ID_MASK (0xFE75) diff --git a/components/esp_hal_dma/esp32h4/include/hal/ahb_dma_ll.h b/components/esp_hal_dma/esp32h4/include/hal/ahb_dma_ll.h index 2daf20ea895..30a97a2fc10 100644 --- a/components/esp_hal_dma/esp32h4/include/hal/ahb_dma_ll.h +++ b/components/esp_hal_dma/esp32h4/include/hal/ahb_dma_ll.h @@ -23,8 +23,8 @@ extern "C" { #define GDMA_LL_CHANNEL_MAX_PRIORITY 5 // supported priority levels: [0,5] -#define GDMA_LL_RX_EVENT_MASK (0x7F) -#define GDMA_LL_TX_EVENT_MASK (0x3F) +#define AHB_DMA_LL_RX_EVENT_MASK (0x7F) +#define AHB_DMA_LL_TX_EVENT_MASK (0x3F) // any "dummy" peripheral ID can be used for M2M mode #define AHB_DMA_LL_M2M_FREE_PERIPH_ID_MASK (0xFC00) diff --git a/components/esp_hal_dma/esp32p4/include/hal/ahb_dma_ll.h b/components/esp_hal_dma/esp32p4/include/hal/ahb_dma_ll.h index 9eab7826232..b96f2267e9e 100644 --- a/components/esp_hal_dma/esp32p4/include/hal/ahb_dma_ll.h +++ b/components/esp_hal_dma/esp32p4/include/hal/ahb_dma_ll.h @@ -24,6 +24,8 @@ extern "C" { // any "dummy" peripheral ID can be used for M2M mode #define AHB_DMA_LL_M2M_FREE_PERIPH_ID_MASK (0xFAC2) +#define AHB_DMA_LL_RX_EVENT_MASK (0x1F) +#define AHB_DMA_LL_TX_EVENT_MASK (0x0F) ///////////////////////////////////// Common ///////////////////////////////////////// /** diff --git a/components/esp_hal_dma/esp32p4/include/hal/axi_dma_ll.h b/components/esp_hal_dma/esp32p4/include/hal/axi_dma_ll.h index 9b8b32d0416..7717aad3371 100644 --- a/components/esp_hal_dma/esp32p4/include/hal/axi_dma_ll.h +++ b/components/esp_hal_dma/esp32p4/include/hal/axi_dma_ll.h @@ -13,6 +13,7 @@ #include "hal/hal_utils.h" #include "hal/gdma_types.h" #include "hal/gdma_ll.h" +#include "hal/config.h" #include "soc/axi_dma_struct.h" #include "soc/axi_dma_reg.h" @@ -21,9 +22,18 @@ extern "C" { #endif #define AXI_DMA_LL_GET_HW(id) (((id) == 0) ? (&AXI_DMA) : NULL) +#define AXI_DMA_LL_SUPPORT(_feat) AXI_DMA_LL_SUPPORT_ ## _feat // any "dummy" peripheral ID can be used for M2M mode #define AXI_DMA_LL_M2M_FREE_PERIPH_ID_MASK (0xFFC0) +#define AXI_DMA_LL_RX_EVENT_MASK (0x1F) +#if HAL_CONFIG(CHIP_SUPPORT_MIN_REV) >= 300 +#define AXI_DMA_LL_SUPPORT_TX_LINK_SWITCH 1 +#define AXI_DMA_LL_TX_EVENT_MASK (0x40F) +#else +#define AXI_DMA_LL_SUPPORT_TX_LINK_SWITCH 0 +#define AXI_DMA_LL_TX_EVENT_MASK (0x0F) +#endif ///////////////////////////////////// Common ///////////////////////////////////////// /** @@ -476,6 +486,42 @@ static inline void axi_dma_ll_tx_restart(axi_dma_dev_t *dev, uint32_t channel) dev->out[channel].conf.out_link1.outlink_restart_chn = 1; } +#if AXI_DMA_LL_SUPPORT(TX_LINK_SWITCH) +/** + * @brief Request link switch done indication for TX channel + */ +static inline void axi_dma_ll_tx_request_link_switch_event(axi_dma_dev_t *dev, uint32_t channel) +{ + switch (channel) { + case 0: + dev->link_switch_state.link_switch_state_ch0 = 1; + break; + case 1: + dev->link_switch_state.link_switch_state_ch1 = 1; + break; + case 2: + dev->link_switch_state.link_switch_state_ch2 = 1; + break; + default: + break; + } +} +#endif + +/** + * @brief Check if TX link switch done indication is supported + */ +__attribute__((always_inline)) +static inline bool axi_dma_ll_tx_is_link_switch_event_supported(axi_dma_dev_t *dev) +{ + (void)dev; +#if AXI_DMA_LL_SUPPORT(TX_LINK_SWITCH) + return true; +#else + return false; +#endif +} + /** * @brief Check if DMA TX descriptor FSM is in IDLE state */ diff --git a/components/esp_hal_dma/esp32p4/include/hal/dma2d_ll.h b/components/esp_hal_dma/esp32p4/include/hal/dma2d_ll.h index 7bc5d94d6b7..f9c94e80390 100644 --- a/components/esp_hal_dma/esp32p4/include/hal/dma2d_ll.h +++ b/components/esp_hal_dma/esp32p4/include/hal/dma2d_ll.h @@ -91,6 +91,7 @@ extern "C" { #define DMA2D_LL_CHANNEL_PERIPH_SEL_BIT_WIDTH (3) #define DMA2D_LL_DESC_ALIGNMENT 8 // Descriptor must be aligned to 8 bytes +#define DMA2D_LL_DESC_2D_FIELD_MAX 0x3FFFU // 2D descriptor width/height/coordinate fields are 14-bit ///////////////////////////////////// Common ///////////////////////////////////////// /** @@ -1008,7 +1009,7 @@ static inline void dma2d_ll_tx_configure_color_space_conv(dma2d_dev_t *dev, uint input_sel = 7; break; case DMA2D_CSC_TX_SCRAMBLE: - input_sel = 2; // Or 3 + input_sel = 3; // Other 3-byte/pixel input path proc_en = false; output_sel = 2; break; diff --git a/components/esp_hal_dma/esp32p4/include/hal/gdma_ll.h b/components/esp_hal_dma/esp32p4/include/hal/gdma_ll.h index db947098223..47a8f433882 100644 --- a/components/esp_hal_dma/esp32p4/include/hal/gdma_ll.h +++ b/components/esp_hal_dma/esp32p4/include/hal/gdma_ll.h @@ -26,9 +26,11 @@ #define GDMA_LL_CHANNEL_MAX_PRIORITY 5 // supported priority levels: [0,5] -#define GDMA_LL_RX_EVENT_MASK (0x1F) -#define GDMA_LL_TX_EVENT_MASK (0x0F) - +// the following event bits are only supported by axi-dma +#if HAL_CONFIG(CHIP_SUPPORT_MIN_REV) >= 300 +#define GDMA_LL_EVENT_TX_LINK_SWITCH (1<<10) +#endif +// the following event bits are identical for ahb-dma and axi-dma #define GDMA_LL_EVENT_TX_TOTAL_EOF (1<<3) #define GDMA_LL_EVENT_TX_DESC_ERROR (1<<2) #define GDMA_LL_EVENT_TX_EOF (1<<1) diff --git a/components/esp_hal_dma/gdma_hal_ahb_v1.c b/components/esp_hal_dma/gdma_hal_ahb_v1.c index 9485a6e2915..10b7c1a668e 100644 --- a/components/esp_hal_dma/gdma_hal_ahb_v1.c +++ b/components/esp_hal_dma/gdma_hal_ahb_v1.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2022-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -11,6 +11,8 @@ static gdma_hal_priv_data_t gdma_ahb_hal_priv_data = { .m2m_free_periph_mask = GDMA_LL_M2M_FREE_PERIPH_ID_MASK, + .tx_event_mask = GDMA_LL_TX_EVENT_MASK, + .rx_event_mask = GDMA_LL_RX_EVENT_MASK, }; void gdma_ahb_hal_start_with_desc(gdma_hal_context_t *hal, int chan_id, gdma_channel_direction_t dir, intptr_t desc_base_addr) @@ -182,6 +184,12 @@ void gdma_ahb_hal_enable_etm_task(gdma_hal_context_t *hal, int chan_id, gdma_cha } #endif // SOC_GDMA_SUPPORT_ETM +bool gdma_ahb_hal_is_tx_link_switch_event_supported(gdma_hal_context_t *hal) +{ + (void)hal; + return false; +} + void gdma_ahb_hal_init(gdma_hal_context_t *hal, const gdma_hal_config_t *config) { hal->dev = GDMA_LL_GET_HW(config->group_id - GDMA_LL_AHB_GROUP_START_ID); @@ -206,5 +214,6 @@ void gdma_ahb_hal_init(gdma_hal_context_t *hal, const gdma_hal_config_t *config) #if GDMA_LL_GET(AHB_BURST_SIZE_ADJUSTABLE) hal->set_burst_size = gdma_ahb_hal_set_burst_size; #endif // GDMA_LL_GET(AHB_BURST_SIZE_ADJUSTABLE) + hal->is_tx_link_switch_event_supported = gdma_ahb_hal_is_tx_link_switch_event_supported; hal->priv_data = &gdma_ahb_hal_priv_data; } diff --git a/components/esp_hal_dma/gdma_hal_ahb_v2.c b/components/esp_hal_dma/gdma_hal_ahb_v2.c index 41cf964f8aa..1bebc674a19 100644 --- a/components/esp_hal_dma/gdma_hal_ahb_v2.c +++ b/components/esp_hal_dma/gdma_hal_ahb_v2.c @@ -12,6 +12,8 @@ static gdma_hal_priv_data_t gdma_ahb_hal_priv_data = { .m2m_free_periph_mask = AHB_DMA_LL_M2M_FREE_PERIPH_ID_MASK, + .tx_event_mask = AHB_DMA_LL_TX_EVENT_MASK, + .rx_event_mask = AHB_DMA_LL_RX_EVENT_MASK, }; void gdma_ahb_hal_start_with_desc(gdma_hal_context_t *hal, int chan_id, gdma_channel_direction_t dir, intptr_t desc_base_addr) @@ -250,6 +252,12 @@ void gdma_ahb_hal_set_weight(gdma_hal_context_t *hal, int chan_id, gdma_channel_ } #endif // SOC_GDMA_SUPPORT_WEIGHTED_ARBITRATION +bool gdma_ahb_hal_is_tx_link_switch_event_supported(gdma_hal_context_t *hal) +{ + (void)hal; + return false; +} + void gdma_ahb_hal_init(gdma_hal_context_t *hal, const gdma_hal_config_t *config) { hal->ahb_dma_dev = AHB_DMA_LL_GET_HW(config->group_id - GDMA_LL_AHB_GROUP_START_ID); @@ -280,6 +288,7 @@ void gdma_ahb_hal_init(gdma_hal_context_t *hal, const gdma_hal_config_t *config) #if GDMA_LL_GET(AHB_BURST_SIZE_ADJUSTABLE) hal->set_burst_size = gdma_ahb_hal_set_burst_size; #endif // GDMA_LL_GET(AHB_BURST_SIZE_ADJUSTABLE) + hal->is_tx_link_switch_event_supported = gdma_ahb_hal_is_tx_link_switch_event_supported; #if SOC_GDMA_SUPPORT_WEIGHTED_ARBITRATION hal->set_weight = gdma_ahb_hal_set_weight; if (config->flags.enable_weighted_arbitration) { diff --git a/components/esp_hal_dma/gdma_hal_axi.c b/components/esp_hal_dma/gdma_hal_axi.c index c3a6b9ad116..d235964be3a 100644 --- a/components/esp_hal_dma/gdma_hal_axi.c +++ b/components/esp_hal_dma/gdma_hal_axi.c @@ -12,6 +12,8 @@ static gdma_hal_priv_data_t gdma_axi_hal_priv_data = { .m2m_free_periph_mask = AXI_DMA_LL_M2M_FREE_PERIPH_ID_MASK, + .tx_event_mask = AXI_DMA_LL_TX_EVENT_MASK, + .rx_event_mask = AXI_DMA_LL_RX_EVENT_MASK, }; void gdma_axi_hal_start_with_desc(gdma_hal_context_t *hal, int chan_id, gdma_channel_direction_t dir, intptr_t desc_base_addr) @@ -170,6 +172,15 @@ uint32_t gdma_axi_hal_get_eof_desc_addr(gdma_hal_context_t *hal, int chan_id, gd } } +#if AXI_DMA_LL_SUPPORT(TX_LINK_SWITCH) +void gdma_axi_hal_request_link_switch_event(gdma_hal_context_t *hal, int chan_id, gdma_channel_direction_t dir) +{ + if (dir == GDMA_CHANNEL_DIRECTION_TX) { + axi_dma_ll_tx_request_link_switch_event(hal->axi_dma_dev, chan_id); + } +} +#endif // AXI_DMA_LL_SUPPORT(TX_LINK_SWITCH) + #if SOC_GDMA_SUPPORT_CRC void gdma_axi_hal_clear_crc(gdma_hal_context_t *hal, int chan_id, gdma_channel_direction_t dir) { @@ -237,6 +248,11 @@ void gdma_axi_hal_enable_etm_task(gdma_hal_context_t *hal, int chan_id, gdma_cha } #endif // SOC_GDMA_SUPPORT_ETM +bool gdma_axi_hal_is_tx_link_switch_event_supported(gdma_hal_context_t *hal) +{ + return axi_dma_ll_tx_is_link_switch_event_supported(hal->axi_dma_dev); +} + void gdma_axi_hal_init(gdma_hal_context_t *hal, const gdma_hal_config_t *config) { hal->axi_dma_dev = AXI_DMA_LL_GET_HW(config->group_id - GDMA_LL_AXI_GROUP_START_ID); @@ -265,5 +281,10 @@ void gdma_axi_hal_init(gdma_hal_context_t *hal, const gdma_hal_config_t *config) #if SOC_GDMA_SUPPORT_ETM hal->enable_etm_task = gdma_axi_hal_enable_etm_task; #endif // SOC_GDMA_SUPPORT_ETM + + hal->is_tx_link_switch_event_supported = gdma_axi_hal_is_tx_link_switch_event_supported; +#if AXI_DMA_LL_SUPPORT(TX_LINK_SWITCH) + hal->request_link_switch_event = gdma_axi_hal_request_link_switch_event; +#endif // AXI_DMA_LL_SUPPORT(TX_LINK_SWITCH) axi_dma_ll_set_default_memory_range(hal->axi_dma_dev); } diff --git a/components/esp_hal_dma/gdma_hal_top.c b/components/esp_hal_dma/gdma_hal_top.c index b1ebc075b26..71bc6ef6de6 100644 --- a/components/esp_hal_dma/gdma_hal_top.c +++ b/components/esp_hal_dma/gdma_hal_top.c @@ -125,3 +125,13 @@ void gdma_hal_set_weight(gdma_hal_context_t *hal, int chan_id, gdma_channel_dire hal->set_weight(hal, chan_id, dir, weight); } #endif // SOC_GDMA_SUPPORT_WEIGHTED_ARBITRATION + +void gdma_hal_request_link_switch_event(gdma_hal_context_t *hal, int chan_id, gdma_channel_direction_t dir) +{ + hal->request_link_switch_event(hal, chan_id, dir); +} + +bool gdma_hal_is_tx_link_switch_event_supported(gdma_hal_context_t *hal) +{ + return hal->is_tx_link_switch_event_supported(hal); +} diff --git a/components/esp_hal_dma/include/hal/gdma_hal.h b/components/esp_hal_dma/include/hal/gdma_hal.h index 9c670c87d52..60fe7b970ed 100644 --- a/components/esp_hal_dma/include/hal/gdma_hal.h +++ b/components/esp_hal_dma/include/hal/gdma_hal.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2020-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2020-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -52,9 +52,9 @@ typedef struct { typedef struct { // The bitmap of the IDs that can be used by M2M are different between AXI DMA and AHB DMA, so we need to save a copy for each of them uint32_t m2m_free_periph_mask; - // TODO: we can add more private data here, e.g. the interrupt event mask of interest - // for now, the AXI DMA and AHB DMA are sharing the same interrupt mask, so we don't need to store it here - // If one day they become incompatible, we shall save a copy for each of them as a private data + // Supported interrupt events can vary across DMA instances (e.g. AHB vs AXI) + uint32_t tx_event_mask; + uint32_t rx_event_mask; } gdma_hal_priv_data_t; /** @@ -102,6 +102,8 @@ struct gdma_hal_context_t { #if SOC_GDMA_SUPPORT_WEIGHTED_ARBITRATION void (*set_weight)(gdma_hal_context_t *hal, int chan_id, gdma_channel_direction_t dir, uint32_t weight); /// Set the channel weight #endif // SOC_GDMA_SUPPORT_WEIGHTED_ARBITRATION + bool (*is_tx_link_switch_event_supported)(gdma_hal_context_t *hal); /// Check if TX link-switch interrupt is supported + void (*request_link_switch_event)(gdma_hal_context_t *hal, int chan_id, gdma_channel_direction_t dir); /// Raise the interrupt when tx link switch complete }; void gdma_hal_deinit(gdma_hal_context_t *hal); @@ -161,6 +163,10 @@ void gdma_hal_enable_etm_task(gdma_hal_context_t *hal, int chan_id, gdma_channel void gdma_hal_set_weight(gdma_hal_context_t *hal, int chan_id, gdma_channel_direction_t dir, uint32_t weight); #endif //SOC_GDMA_SUPPORT_WEIGHTED_ARBITRATION +void gdma_hal_request_link_switch_event(gdma_hal_context_t *hal, int chan_id, gdma_channel_direction_t dir); + +bool gdma_hal_is_tx_link_switch_event_supported(gdma_hal_context_t *hal); + #ifdef __cplusplus } #endif diff --git a/components/esp_hal_dma/include/hal/gdma_hal_ahb.h b/components/esp_hal_dma/include/hal/gdma_hal_ahb.h index a8f8ea2509c..d4fe2e1dd29 100644 --- a/components/esp_hal_dma/include/hal/gdma_hal_ahb.h +++ b/components/esp_hal_dma/include/hal/gdma_hal_ahb.h @@ -12,38 +12,6 @@ extern "C" { #endif -void gdma_ahb_hal_start_with_desc(gdma_hal_context_t *hal, int chan_id, gdma_channel_direction_t dir, intptr_t desc_base_addr); - -void gdma_ahb_hal_stop(gdma_hal_context_t *hal, int chan_id, gdma_channel_direction_t dir); - -void gdma_ahb_hal_append(gdma_hal_context_t *hal, int chan_id, gdma_channel_direction_t dir); - -void gdma_ahb_hal_reset(gdma_hal_context_t *hal, int chan_id, gdma_channel_direction_t dir); - -void gdma_ahb_hal_set_priority(gdma_hal_context_t *hal, int chan_id, gdma_channel_direction_t dir, uint32_t priority); - -void gdma_ahb_hal_connect_peri(gdma_hal_context_t *hal, int chan_id, gdma_channel_direction_t dir, int periph_id); - -void gdma_ahb_hal_connect_mem(gdma_hal_context_t *hal, int chan_id, gdma_channel_direction_t dir, int dummy_id); - -void gdma_ahb_hal_disconnect_all(gdma_hal_context_t *hal, int chan_id, gdma_channel_direction_t dir); - -void gdma_ahb_hal_enable_burst(gdma_hal_context_t *hal, int chan_id, gdma_channel_direction_t dir, bool en_data_burst, bool en_desc_burst); - -void gdma_ahb_hal_set_burst_size(gdma_hal_context_t *hal, int chan_id, gdma_channel_direction_t dir, uint32_t burst_sz); - -void gdma_ahb_hal_set_strategy(gdma_hal_context_t *hal, int chan_id, gdma_channel_direction_t dir, bool en_owner_check, bool en_desc_write_back, bool eof_till_popped); - -void gdma_ahb_hal_enable_intr(gdma_hal_context_t *hal, int chan_id, gdma_channel_direction_t dir, uint32_t intr_event_mask, bool en_or_dis); - -void gdma_ahb_hal_clear_intr(gdma_hal_context_t *hal, int chan_id, gdma_channel_direction_t dir, uint32_t intr_event_mask); - -uint32_t gdma_ahb_hal_read_intr_status(gdma_hal_context_t *hal, int chan_id, gdma_channel_direction_t dir, bool raw); - -uint32_t gdma_ahb_hal_get_intr_status_reg(gdma_hal_context_t *hal, int chan_id, gdma_channel_direction_t dir); - -uint32_t gdma_ahb_hal_get_eof_desc_addr(gdma_hal_context_t *hal, int chan_id, gdma_channel_direction_t dir, bool is_success); - void gdma_ahb_hal_init(gdma_hal_context_t *hal, const gdma_hal_config_t *config); #ifdef __cplusplus diff --git a/components/esp_hal_dma/include/hal/gdma_hal_axi.h b/components/esp_hal_dma/include/hal/gdma_hal_axi.h index 2e63e322da1..443cb87cd8f 100644 --- a/components/esp_hal_dma/include/hal/gdma_hal_axi.h +++ b/components/esp_hal_dma/include/hal/gdma_hal_axi.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2020-2023 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2020-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -12,38 +12,6 @@ extern "C" { #endif -void gdma_axi_hal_start_with_desc(gdma_hal_context_t *hal, int chan_id, gdma_channel_direction_t dir, intptr_t desc_base_addr); - -void gdma_axi_hal_stop(gdma_hal_context_t *hal, int chan_id, gdma_channel_direction_t dir); - -void gdma_axi_hal_append(gdma_hal_context_t *hal, int chan_id, gdma_channel_direction_t dir); - -void gdma_axi_hal_reset(gdma_hal_context_t *hal, int chan_id, gdma_channel_direction_t dir); - -void gdma_axi_hal_set_priority(gdma_hal_context_t *hal, int chan_id, gdma_channel_direction_t dir, uint32_t priority); - -void gdma_axi_hal_connect_peri(gdma_hal_context_t *hal, int chan_id, gdma_channel_direction_t dir, int periph_id); - -void gdma_axi_hal_connect_mem(gdma_hal_context_t *hal, int chan_id, gdma_channel_direction_t dir, int dummy_id); - -void gdma_axi_hal_disconnect_all(gdma_hal_context_t *hal, int chan_id, gdma_channel_direction_t dir); - -void gdma_axi_hal_enable_burst(gdma_hal_context_t *hal, int chan_id, gdma_channel_direction_t dir, bool en_data_burst, bool en_desc_burst); - -void gdma_axi_hal_set_burst_size(gdma_hal_context_t *hal, int chan_id, gdma_channel_direction_t dir, uint32_t burst_sz); - -void gdma_axi_hal_set_strategy(gdma_hal_context_t *hal, int chan_id, gdma_channel_direction_t dir, bool en_owner_check, bool en_desc_write_back, bool eof_till_popped); - -void gdma_axi_hal_enable_intr(gdma_hal_context_t *hal, int chan_id, gdma_channel_direction_t dir, uint32_t intr_event_mask, bool en_or_dis); - -void gdma_axi_hal_clear_intr(gdma_hal_context_t *hal, int chan_id, gdma_channel_direction_t dir, uint32_t intr_event_mask); - -uint32_t gdma_axi_hal_read_intr_status(gdma_hal_context_t *hal, int chan_id, gdma_channel_direction_t dir, bool raw); - -uint32_t gdma_axi_hal_get_intr_status_reg(gdma_hal_context_t *hal, int chan_id, gdma_channel_direction_t dir); - -uint32_t gdma_axi_hal_get_eof_desc_addr(gdma_hal_context_t *hal, int chan_id, gdma_channel_direction_t dir, bool is_success); - void gdma_axi_hal_init(gdma_hal_context_t *hal, const gdma_hal_config_t *config); #ifdef __cplusplus diff --git a/components/esp_hal_i2c/CMakeLists.txt b/components/esp_hal_i2c/CMakeLists.txt index 1c02433b162..0a7a86fa8cf 100644 --- a/components/esp_hal_i2c/CMakeLists.txt +++ b/components/esp_hal_i2c/CMakeLists.txt @@ -1,11 +1,13 @@ idf_build_get_property(target IDF_TARGET) set(includes) -list(APPEND includes "${target}/include") -list(APPEND includes "include") set(srcs) -set(include "include") +if(EXISTS "${CMAKE_CURRENT_LIST_DIR}/${target}/include") + list(APPEND includes "${target}/include") +endif() +list(APPEND includes "include") + set(target_folder "${target}") # I2C related source files diff --git a/components/esp_hal_mspi/esp32c2/include/hal/gpspi_flash_ll.h b/components/esp_hal_mspi/esp32c2/include/hal/gpspi_flash_ll.h index 56dd2431ca7..d81c10fb21f 100644 --- a/components/esp_hal_mspi/esp32c2/include/hal/gpspi_flash_ll.h +++ b/components/esp_hal_mspi/esp32c2/include/hal/gpspi_flash_ll.h @@ -29,7 +29,6 @@ extern "C" { #define gpspi_flash_ll_hw_get_id(dev) ( ((dev) == (void*)&GPSPI2) ? SPI2_HOST : -1 ) typedef typeof(GPSPI2.clock) gpspi_flash_ll_clock_reg_t; -#define GPSPI_FLASH_LL_PERIPHERAL_FREQUENCY_MHZ (40) /*------------------------------------------------------------------------------ * Control diff --git a/components/esp_hal_mspi/esp32c3/include/hal/gpspi_flash_ll.h b/components/esp_hal_mspi/esp32c3/include/hal/gpspi_flash_ll.h index 95fa20b9296..417848a0dc3 100644 --- a/components/esp_hal_mspi/esp32c3/include/hal/gpspi_flash_ll.h +++ b/components/esp_hal_mspi/esp32c3/include/hal/gpspi_flash_ll.h @@ -30,7 +30,6 @@ extern "C" { #define gpspi_flash_ll_hw_get_id(dev) ( ((dev) == (void*)&GPSPI2) ? SPI2_HOST : -1 ) typedef typeof(GPSPI2.clock.val) gpspi_flash_ll_clock_reg_t; -#define GPSPI_FLASH_LL_PERIPHERAL_FREQUENCY_MHZ (80) /*------------------------------------------------------------------------------ * Control diff --git a/components/esp_hal_mspi/esp32c5/include/hal/gpspi_flash_ll.h b/components/esp_hal_mspi/esp32c5/include/hal/gpspi_flash_ll.h index 503d3417ec8..2245972d0e6 100644 --- a/components/esp_hal_mspi/esp32c5/include/hal/gpspi_flash_ll.h +++ b/components/esp_hal_mspi/esp32c5/include/hal/gpspi_flash_ll.h @@ -32,7 +32,6 @@ extern "C" { #define gpspi_flash_ll_hw_get_id(dev) ( ((dev) == (void*)&GPSPI2) ? SPI2_HOST : -1 ) typedef typeof(GPSPI2.clock.val) gpspi_flash_ll_clock_reg_t; -#define GPSPI_FLASH_LL_PERIPHERAL_FREQUENCY_MHZ (80) #define GPSPI_FLASH_LL_SUPPORT_CLK_SRC_PRE_DIV (1) #define GPSPI_FLASH_LL_PERIPH_CLK_DIV_MAX ((SPI_CLKCNT_N + 1) * (SPI_CLKDIV_PRE + 1)) //peripheral internal maxmum clock divider diff --git a/components/esp_hal_mspi/esp32c6/include/hal/gpspi_flash_ll.h b/components/esp_hal_mspi/esp32c6/include/hal/gpspi_flash_ll.h index 08209651bc1..f9bff2ade2c 100644 --- a/components/esp_hal_mspi/esp32c6/include/hal/gpspi_flash_ll.h +++ b/components/esp_hal_mspi/esp32c6/include/hal/gpspi_flash_ll.h @@ -31,7 +31,6 @@ extern "C" { #define gpspi_flash_ll_hw_get_id(dev) ( ((dev) == (void*)&GPSPI2) ? SPI2_HOST : -1 ) typedef typeof(GPSPI2.clock.val) gpspi_flash_ll_clock_reg_t; -#define GPSPI_FLASH_LL_PERIPHERAL_FREQUENCY_MHZ (80) /*------------------------------------------------------------------------------ * Control diff --git a/components/esp_hal_mspi/esp32c61/include/hal/gpspi_flash_ll.h b/components/esp_hal_mspi/esp32c61/include/hal/gpspi_flash_ll.h index 3d3fb019c59..80782bc2984 100644 --- a/components/esp_hal_mspi/esp32c61/include/hal/gpspi_flash_ll.h +++ b/components/esp_hal_mspi/esp32c61/include/hal/gpspi_flash_ll.h @@ -31,7 +31,6 @@ extern "C" { #define gpspi_flash_ll_hw_get_id(dev) ( ((dev) == (void*)&GPSPI2) ? SPI2_HOST : -1 ) typedef typeof(GPSPI2.clock.val) gpspi_flash_ll_clock_reg_t; -#define GPSPI_FLASH_LL_PERIPHERAL_FREQUENCY_MHZ (80) /*------------------------------------------------------------------------------ * Control diff --git a/components/esp_hal_mspi/esp32h2/include/hal/gpspi_flash_ll.h b/components/esp_hal_mspi/esp32h2/include/hal/gpspi_flash_ll.h index 136b12aebf6..07b9a1ab027 100644 --- a/components/esp_hal_mspi/esp32h2/include/hal/gpspi_flash_ll.h +++ b/components/esp_hal_mspi/esp32h2/include/hal/gpspi_flash_ll.h @@ -31,7 +31,6 @@ extern "C" { #define gpspi_flash_ll_hw_get_id(dev) ( ((dev) == (void*)&GPSPI2) ? SPI2_HOST : -1 ) typedef typeof(GPSPI2.clock.val) gpspi_flash_ll_clock_reg_t; -#define GPSPI_FLASH_LL_PERIPHERAL_FREQUENCY_MHZ (80) /*------------------------------------------------------------------------------ * Control diff --git a/components/esp_hal_mspi/esp32h4/include/hal/gpspi_flash_ll.h b/components/esp_hal_mspi/esp32h4/include/hal/gpspi_flash_ll.h index bffcc612d43..718a4e7d5ac 100644 --- a/components/esp_hal_mspi/esp32h4/include/hal/gpspi_flash_ll.h +++ b/components/esp_hal_mspi/esp32h4/include/hal/gpspi_flash_ll.h @@ -33,7 +33,6 @@ extern "C" { #define gpspi_flash_ll_hw_get_id(dev) ( ((dev) == (void*)&GPSPI2) ? SPI2_HOST : -1 ) typedef typeof(GPSPI2.clock.val) gpspi_flash_ll_clock_reg_t; -#define GPSPI_FLASH_LL_PERIPHERAL_FREQUENCY_MHZ (80) /*------------------------------------------------------------------------------ * Control diff --git a/components/esp_hal_mspi/esp32p4/include/hal/gpspi_flash_ll.h b/components/esp_hal_mspi/esp32p4/include/hal/gpspi_flash_ll.h index a4db74e33c8..021edd197bd 100644 --- a/components/esp_hal_mspi/esp32p4/include/hal/gpspi_flash_ll.h +++ b/components/esp_hal_mspi/esp32p4/include/hal/gpspi_flash_ll.h @@ -38,7 +38,6 @@ extern "C" { )) ) typedef typeof(GPSPI2.clock.val) gpspi_flash_ll_clock_reg_t; -#define GPSPI_FLASH_LL_PERIPHERAL_FREQUENCY_MHZ (80) #define GPSPI_FLASH_LL_SUPPORT_CLK_SRC_PRE_DIV (1) #define GPSPI_FLASH_LL_PERIPH_CLK_DIV_MAX ((SPI_CLKCNT_N + 1) * (SPI_CLKDIV_PRE + 1)) //peripheral internal maxmum clock divider diff --git a/components/esp_hal_mspi/esp32s2/include/hal/gpspi_flash_ll.h b/components/esp_hal_mspi/esp32s2/include/hal/gpspi_flash_ll.h index 834faeb0fe6..c9e1a2c9837 100644 --- a/components/esp_hal_mspi/esp32s2/include/hal/gpspi_flash_ll.h +++ b/components/esp_hal_mspi/esp32s2/include/hal/gpspi_flash_ll.h @@ -36,7 +36,6 @@ extern "C" { )) ) typedef typeof(GPSPI2.clock.val) gpspi_flash_ll_clock_reg_t; -#define GPSPI_FLASH_LL_PERIPHERAL_FREQUENCY_MHZ 80 /*------------------------------------------------------------------------------ * Control diff --git a/components/esp_hal_mspi/esp32s3/include/hal/gpspi_flash_ll.h b/components/esp_hal_mspi/esp32s3/include/hal/gpspi_flash_ll.h index 71e3cfe1036..e4f57357167 100644 --- a/components/esp_hal_mspi/esp32s3/include/hal/gpspi_flash_ll.h +++ b/components/esp_hal_mspi/esp32s3/include/hal/gpspi_flash_ll.h @@ -36,7 +36,6 @@ extern "C" { )) ) typedef typeof(GPSPI2.clock.val) gpspi_flash_ll_clock_reg_t; -#define GPSPI_FLASH_LL_PERIPHERAL_FREQUENCY_MHZ 80 /*------------------------------------------------------------------------------ * Control diff --git a/components/esp_hal_parlio/esp32c5/include/hal/parlio_ll.h b/components/esp_hal_parlio/esp32c5/include/hal/parlio_ll.h index 67d15be0858..625682370ce 100644 --- a/components/esp_hal_parlio/esp32c5/include/hal/parlio_ll.h +++ b/components/esp_hal_parlio/esp32c5/include/hal/parlio_ll.h @@ -42,7 +42,7 @@ #define PARLIO_LL_EVENT_RX_MASK (PARLIO_LL_EVENT_RX_FIFO_FULL) #define PARLIO_LL_TX_DATA_LINE_AS_CLK_GATE 7 // TXD[7] can be used as clock gate signal -#define PARLIO_LL_TX_VALID_MAX_DELAY 32767 +#define PARLIO_LL_TX_VALID_MAX_DELAY 65535 #ifdef __cplusplus extern "C" { #endif diff --git a/components/esp_hal_parlio/esp32h4/include/hal/parlio_ll.h b/components/esp_hal_parlio/esp32h4/include/hal/parlio_ll.h index e48340ad3c7..cda1ef16e7b 100644 --- a/components/esp_hal_parlio/esp32h4/include/hal/parlio_ll.h +++ b/components/esp_hal_parlio/esp32h4/include/hal/parlio_ll.h @@ -42,7 +42,7 @@ #define PARLIO_LL_EVENT_RX_MASK (PARLIO_LL_EVENT_RX_FIFO_FULL) #define PARLIO_LL_TX_DATA_LINE_AS_CLK_GATE 7 // TXD[7] can be used as clock gate signal -#define PARLIO_LL_TX_VALID_MAX_DELAY 32767 +#define PARLIO_LL_TX_VALID_MAX_DELAY 65535 #ifdef __cplusplus extern "C" { #endif diff --git a/components/esp_hal_parlio/esp32p4/include/hal/parlio_ll.h b/components/esp_hal_parlio/esp32p4/include/hal/parlio_ll.h index 4017ea6623d..b7d48178258 100644 --- a/components/esp_hal_parlio/esp32p4/include/hal/parlio_ll.h +++ b/components/esp_hal_parlio/esp32p4/include/hal/parlio_ll.h @@ -49,7 +49,7 @@ #define PARLIO_LL_TX_DATA_LINE_AS_CLK_GATE 15 // TXD[15] can be used as clock gate signal #if HAL_CONFIG(CHIP_SUPPORT_MIN_REV) >= 300 -#define PARLIO_LL_TX_VALID_MAX_DELAY 32767 +#define PARLIO_LL_TX_VALID_MAX_DELAY 65535 #define PARLIO_LL_SUPPORT_TX_EOF_FROM_DMA 1 // Support to treat DMA EOF as TX unit EOF #endif @@ -136,6 +136,7 @@ static inline void _parlio_ll_rx_set_clock_source(parl_io_dev_t *dev, parlio_clo HAL_ASSERT(false); break; } + LP_AON_CLKRST.hp_clk_ctrl.hp_pad_parlio_rx_clk_en = (src == PARLIO_CLK_SRC_EXTERNAL); HP_SYS_CLKRST.peri_clk_ctrl117.reg_parlio_rx_clk_src_sel = clk_sel; } @@ -197,7 +198,6 @@ __attribute__((always_inline)) static inline void _parlio_ll_rx_enable_clock(parl_io_dev_t *dev, bool en) { (void)dev; - LP_AON_CLKRST.hp_clk_ctrl.hp_pad_parlio_rx_clk_en = en; HP_SYS_CLKRST.peri_clk_ctrl117.reg_parlio_rx_clk_en = en; } @@ -486,6 +486,7 @@ static inline void _parlio_ll_tx_set_clock_source(parl_io_dev_t *dev, parlio_clo HAL_ASSERT(false); break; } + LP_AON_CLKRST.hp_clk_ctrl.hp_pad_parlio_tx_clk_en = (src == PARLIO_CLK_SRC_EXTERNAL); HP_SYS_CLKRST.peri_clk_ctrl118.reg_parlio_tx_clk_src_sel = clk_sel; } @@ -548,7 +549,6 @@ __attribute__((always_inline)) static inline void _parlio_ll_tx_enable_clock(parl_io_dev_t *dev, bool en) { (void)dev; - LP_AON_CLKRST.hp_clk_ctrl.hp_pad_parlio_tx_clk_en = en; HP_SYS_CLKRST.peri_clk_ctrl118.reg_parlio_tx_clk_en = en; } diff --git a/components/esp_hal_pmu/esp32c5/include/hal/pmu_ll.h b/components/esp_hal_pmu/esp32c5/include/hal/pmu_ll.h index 0c331f5f729..36af3b94080 100644 --- a/components/esp_hal_pmu/esp32c5/include/hal/pmu_ll.h +++ b/components/esp_hal_pmu/esp32c5/include/hal/pmu_ll.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2023-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2023-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -519,6 +519,11 @@ FORCE_INLINE_ATTR void pmu_ll_hp_clear_reject_intr_status(pmu_dev_t *hw) hw->hp_ext.int_clr.reject = 1; } +FORCE_INLINE_ATTR void pmu_ll_hp_clear_lp_cpu_exc_intr_status(pmu_dev_t *hw) +{ + hw->hp_ext.int_clr.lp_cpu_exc = 1; +} + FORCE_INLINE_ATTR void pmu_ll_hp_enable_sw_intr(pmu_dev_t *hw, bool enable) { hw->hp_ext.int_ena.sw = enable; diff --git a/components/esp_hal_pmu/esp32c6/include/hal/pmu_ll.h b/components/esp_hal_pmu/esp32c6/include/hal/pmu_ll.h index 24f31334aa8..3f8ee6ac871 100644 --- a/components/esp_hal_pmu/esp32c6/include/hal/pmu_ll.h +++ b/components/esp_hal_pmu/esp32c6/include/hal/pmu_ll.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2023-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2023-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -519,6 +519,11 @@ FORCE_INLINE_ATTR void pmu_ll_hp_clear_reject_intr_status(pmu_dev_t *hw) hw->hp_ext.int_clr.reject = 1; } +FORCE_INLINE_ATTR void pmu_ll_hp_clear_lp_cpu_exc_intr_status(pmu_dev_t *hw) +{ + hw->hp_ext.int_clr.lp_cpu_exc = 1; +} + FORCE_INLINE_ATTR void pmu_ll_hp_enable_sw_intr(pmu_dev_t *hw, bool enable) { hw->hp_ext.int_ena.sw = enable; diff --git a/components/esp_hal_pmu/esp32p4/include/hal/pmu_ll.h b/components/esp_hal_pmu/esp32p4/include/hal/pmu_ll.h index b43ef8f40c4..1822b1b63de 100644 --- a/components/esp_hal_pmu/esp32p4/include/hal/pmu_ll.h +++ b/components/esp_hal_pmu/esp32p4/include/hal/pmu_ll.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2023-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2023-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -567,6 +567,11 @@ FORCE_INLINE_ATTR void pmu_ll_hp_clear_reject_intr_status(pmu_dev_t *hw) hw->hp_ext.int_clr.reject = 1; } +FORCE_INLINE_ATTR void pmu_ll_hp_clear_lp_cpu_exc_intr_status(pmu_dev_t *hw) +{ + hw->hp_ext.int_clr.lp_exception = 1; +} + FORCE_INLINE_ATTR uint32_t pmu_ll_hp_get_wakeup_cause(pmu_dev_t *hw) { return hw->wakeup.status0; diff --git a/components/esp_hal_ppa/esp32p4/include/hal/ppa_ll.h b/components/esp_hal_ppa/esp32p4/include/hal/ppa_ll.h index c45e66806ed..001946b47f4 100644 --- a/components/esp_hal_ppa/esp32p4/include/hal/ppa_ll.h +++ b/components/esp_hal_ppa/esp32p4/include/hal/ppa_ll.h @@ -1083,7 +1083,7 @@ static inline void ppa_ll_blend_configure_rx_fg_alpha(ppa_dev_t *dev, ppa_alpha_ } /** - * @brief Configure PPA blending pixel filling image block + * @brief Configure PPA blending pixel filling image block color * * The color to be filled is directly relying on the blend_tx_fix_pixel register field value. * For fill operation, the data does not go through any color space conversion in the blending engine. @@ -1091,12 +1091,9 @@ static inline void ppa_ll_blend_configure_rx_fg_alpha(ppa_dev_t *dev, ppa_alpha_ * @param dev Peripheral instance address * @param color_mode One of the values in ppa_fill_color_mode_t * @param data The point of the fix data to be filled to the image block pixels - * @param hb The horizontal width of image block that would be filled in fix pixel filling mode. The unit is pixel. - * @param vb The vertical height of image block that would be filled in fix pixel filling mode. The unit is pixel. */ -static inline void ppa_ll_blend_configure_filling_block(ppa_dev_t *dev, ppa_fill_color_mode_t color_mode, void *data, uint32_t hb, uint32_t vb) +static inline void ppa_ll_blend_configure_filling_block_color(ppa_dev_t *dev, ppa_fill_color_mode_t color_mode, void *data) { - HAL_ASSERT(hb <= PPA_BLEND_HB_V && vb <= PPA_BLEND_VB_V); uint32_t fill_color_data = 0; switch (color_mode) { case PPA_FILL_COLOR_MODE_ARGB8888: @@ -1123,6 +1120,18 @@ static inline void ppa_ll_blend_configure_filling_block(ppa_dev_t *dev, ppa_fill abort(); } dev->blend_fix_pixel.blend_tx_fix_pixel = fill_color_data; +} + +/** + * @brief Set PPA blending block size + * + * @param dev Peripheral instance address + * @param hb The horizontal width of image block that would be filled in fix pixel filling mode or blend mode. The unit is pixel. + * @param vb The vertical height of image block that would be filled in fix pixel filling mode or blend mode. The unit is pixel. + */ +static inline void ppa_ll_blend_set_block_size(ppa_dev_t *dev, uint32_t hb, uint32_t vb) +{ + HAL_ASSERT(hb <= PPA_BLEND_HB_V && vb <= PPA_BLEND_VB_V); dev->blend_tx_size.blend_hb = hb; dev->blend_tx_size.blend_vb = vb; } diff --git a/components/esp_hal_sd/esp32/include/hal/sdio_slave_ll.h b/components/esp_hal_sd/esp32/include/hal/sdio_slave_ll.h index aacecbd8576..2e96fb96110 100644 --- a/components/esp_hal_sd/esp32/include/hal/sdio_slave_ll.h +++ b/components/esp_hal_sd/esp32/include/hal/sdio_slave_ll.h @@ -67,6 +67,9 @@ typedef struct sdio_slave_ll_desc_s { }; } sdio_slave_ll_desc_t; +/* Maximum buffer size that a single SDIO slave DMA descriptor can point to. */ +#define SDIO_SLAVE_LL_DMA_DESC_MAX_BUF_SIZE ((1 << 12) - 1) + /// Mask of general purpose interrupts sending from the host. typedef enum { SDIO_SLAVE_LL_SLVINT_0 = BIT(0), ///< General purpose interrupt bit 0. diff --git a/components/esp_hal_sd/esp32c5/include/hal/sdio_slave_ll.h b/components/esp_hal_sd/esp32c5/include/hal/sdio_slave_ll.h index 069bf2ee106..73be6b5d8cc 100644 --- a/components/esp_hal_sd/esp32c5/include/hal/sdio_slave_ll.h +++ b/components/esp_hal_sd/esp32c5/include/hal/sdio_slave_ll.h @@ -67,6 +67,9 @@ typedef struct sdio_slave_ll_desc_s { }; } sdio_slave_ll_desc_t; +/* Maximum buffer size that a single SDIO slave DMA descriptor can point to. */ +#define SDIO_SLAVE_LL_DMA_DESC_MAX_BUF_SIZE ((1 << 14) - 1) + /// Mask of general purpose interrupts sending from the host. typedef enum { SDIO_SLAVE_LL_SLVINT_0 = BIT(0), ///< General purpose interrupt bit 0. diff --git a/components/esp_hal_sd/esp32c6/include/hal/sdio_slave_ll.h b/components/esp_hal_sd/esp32c6/include/hal/sdio_slave_ll.h index 1276518a899..c48ec070f7d 100644 --- a/components/esp_hal_sd/esp32c6/include/hal/sdio_slave_ll.h +++ b/components/esp_hal_sd/esp32c6/include/hal/sdio_slave_ll.h @@ -67,6 +67,9 @@ typedef struct sdio_slave_ll_desc_s { }; } sdio_slave_ll_desc_t; +/* Maximum buffer size that a single SDIO slave DMA descriptor can point to. */ +#define SDIO_SLAVE_LL_DMA_DESC_MAX_BUF_SIZE ((1 << 14) - 1) + /// Mask of general purpose interrupts sending from the host. typedef enum { SDIO_SLAVE_LL_SLVINT_0 = BIT(0), ///< General purpose interrupt bit 0. diff --git a/components/esp_hal_sd/esp32c61/include/hal/sdio_slave_ll.h b/components/esp_hal_sd/esp32c61/include/hal/sdio_slave_ll.h index f3d045f1e1e..edb4fbf467c 100644 --- a/components/esp_hal_sd/esp32c61/include/hal/sdio_slave_ll.h +++ b/components/esp_hal_sd/esp32c61/include/hal/sdio_slave_ll.h @@ -67,6 +67,9 @@ typedef struct sdio_slave_ll_desc_s { }; } sdio_slave_ll_desc_t; +/* Maximum buffer size that a single SDIO slave DMA descriptor can point to. */ +#define SDIO_SLAVE_LL_DMA_DESC_MAX_BUF_SIZE ((1 << 14) - 1) + /// Mask of general purpose interrupts sending from the host. typedef enum { SDIO_SLAVE_LL_SLVINT_0 = BIT(0), ///< General purpose interrupt bit 0. diff --git a/components/esp_hal_security/esp32c5/include/hal/ecc_ll.h b/components/esp_hal_security/esp32c5/include/hal/ecc_ll.h index b5a5bca2e27..c72dfd4e875 100644 --- a/components/esp_hal_security/esp32c5/include/hal/ecc_ll.h +++ b/components/esp_hal_security/esp32c5/include/hal/ecc_ll.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2023-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2023-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -12,6 +12,7 @@ #include "soc/ecc_mult_reg.h" #include "soc/pcr_struct.h" #include "soc/pcr_reg.h" +#include "esp_fault.h" #ifdef __cplusplus extern "C" { @@ -47,11 +48,22 @@ static inline void ecc_ll_reset_register(void) PCR.ecdsa_conf.ecdsa_rst_en = 0; } +static inline void ecc_ll_clear_force_pd(void) +{ + REG_CLR_BIT(PCR_ECC_PD_CTRL_REG, PCR_ECC_MEM_FORCE_PD); +} + static inline void ecc_ll_power_up(void) { /* Power up the ECC peripheral (default state is power-down) */ REG_CLR_BIT(PCR_ECC_PD_CTRL_REG, PCR_ECC_MEM_PD); REG_CLR_BIT(PCR_ECC_PD_CTRL_REG, PCR_ECC_MEM_FORCE_PD); + ESP_FAULT_ASSERT(REG_GET_BIT(PCR_ECC_PD_CTRL_REG, PCR_ECC_MEM_FORCE_PD) == 0); +} + +static inline bool ecc_ll_mem_force_pd_is_clear(void) +{ + return REG_GET_BIT(PCR_ECC_PD_CTRL_REG, PCR_ECC_MEM_FORCE_PD) == 0; } static inline void ecc_ll_power_down(void) diff --git a/components/esp_hal_security/esp32c6/include/hal/ecc_ll.h b/components/esp_hal_security/esp32c6/include/hal/ecc_ll.h index 264e89958ab..103ac78570a 100644 --- a/components/esp_hal_security/esp32c6/include/hal/ecc_ll.h +++ b/components/esp_hal_security/esp32c6/include/hal/ecc_ll.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2020-2024 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2020-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -12,6 +12,7 @@ #include "soc/ecc_mult_reg.h" #include "soc/pcr_struct.h" #include "soc/pcr_reg.h" +#include "esp_fault.h" #ifdef __cplusplus extern "C" { @@ -46,6 +47,7 @@ static inline void ecc_ll_power_up(void) { REG_CLR_BIT(PCR_ECC_PD_CTRL_REG, PCR_ECC_MEM_PD); REG_CLR_BIT(PCR_ECC_PD_CTRL_REG, PCR_ECC_MEM_FORCE_PD); + ESP_FAULT_ASSERT(REG_GET_BIT(PCR_ECC_PD_CTRL_REG, PCR_ECC_MEM_FORCE_PD) == 0); } static inline void ecc_ll_power_down(void) diff --git a/components/esp_hal_security/esp32c61/include/hal/ecc_ll.h b/components/esp_hal_security/esp32c61/include/hal/ecc_ll.h index 14c74e53168..c9f6447dc9a 100644 --- a/components/esp_hal_security/esp32c61/include/hal/ecc_ll.h +++ b/components/esp_hal_security/esp32c61/include/hal/ecc_ll.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2023-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -12,6 +12,7 @@ #include "soc/ecc_mult_reg.h" #include "soc/pcr_struct.h" #include "soc/pcr_reg.h" +#include "esp_fault.h" #ifdef __cplusplus extern "C" { @@ -49,9 +50,15 @@ static inline void ecc_ll_reset_register(void) static inline void ecc_ll_power_up(void) { - /* Power up the ECC peripheral (default state is power-down) */ + /* Power up the ECC peripheral (default state is power-up) */ REG_CLR_BIT(PCR_ECC_PD_CTRL_REG, PCR_ECC_MEM_PD); REG_CLR_BIT(PCR_ECC_PD_CTRL_REG, PCR_ECC_MEM_FORCE_PD); + ESP_FAULT_ASSERT(REG_GET_BIT(PCR_ECC_PD_CTRL_REG, PCR_ECC_MEM_FORCE_PD) == 0); +} + +static inline bool ecc_ll_mem_force_pd_is_clear(void) +{ + return REG_GET_BIT(PCR_ECC_PD_CTRL_REG, PCR_ECC_MEM_FORCE_PD) == 0; } static inline void ecc_ll_power_down(void) diff --git a/components/esp_hal_security/esp32c61/include/hal/sha_ll.h b/components/esp_hal_security/esp32c61/include/hal/sha_ll.h index 8ad73c14639..c1e90c4e0ef 100644 --- a/components/esp_hal_security/esp32c61/include/hal/sha_ll.h +++ b/components/esp_hal_security/esp32c61/include/hal/sha_ll.h @@ -32,9 +32,7 @@ static inline void sha_ll_reset_register(void) PCR.sha_conf.sha_rst_en = 1; PCR.sha_conf.sha_rst_en = 0; - // Clear reset on digital signature, hmac and ecdsa also, otherwise SHA is held in reset - PCR.ds_conf.ds_rst_en = 0; - PCR.hmac_conf.hmac_rst_en = 0; + // Clear reset on ecdsa also, otherwise SHA is held in reset PCR.ecdsa_conf.ecdsa_rst_en = 0; } diff --git a/components/esp_hal_security/esp32h2/include/hal/ecc_ll.h b/components/esp_hal_security/esp32h2/include/hal/ecc_ll.h index 3ad0a815b78..00a897f4c93 100644 --- a/components/esp_hal_security/esp32h2/include/hal/ecc_ll.h +++ b/components/esp_hal_security/esp32h2/include/hal/ecc_ll.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2023-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -12,6 +12,7 @@ #include "soc/ecc_mult_reg.h" #include "soc/pcr_struct.h" #include "soc/pcr_reg.h" +#include "esp_fault.h" #include "soc/chip_revision.h" #include "hal/efuse_hal.h" @@ -54,6 +55,12 @@ static inline void ecc_ll_power_up(void) { REG_CLR_BIT(PCR_ECC_PD_CTRL_REG, PCR_ECC_MEM_PD); REG_CLR_BIT(PCR_ECC_PD_CTRL_REG, PCR_ECC_MEM_FORCE_PD); + ESP_FAULT_ASSERT(REG_GET_BIT(PCR_ECC_PD_CTRL_REG, PCR_ECC_MEM_FORCE_PD) == 0); +} + +static inline bool ecc_ll_mem_force_pd_is_clear(void) +{ + return REG_GET_BIT(PCR_ECC_PD_CTRL_REG, PCR_ECC_MEM_FORCE_PD) == 0; } static inline void ecc_ll_power_down(void) diff --git a/components/esp_hal_security/esp32h21/include/hal/ecc_ll.h b/components/esp_hal_security/esp32h21/include/hal/ecc_ll.h index 6ac32c21aa4..98bc34e2854 100644 --- a/components/esp_hal_security/esp32h21/include/hal/ecc_ll.h +++ b/components/esp_hal_security/esp32h21/include/hal/ecc_ll.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -12,6 +12,7 @@ #include "soc/ecc_mult_reg.h" #include "soc/pcr_struct.h" #include "soc/pcr_reg.h" +#include "esp_fault.h" #ifdef __cplusplus extern "C" { @@ -52,6 +53,7 @@ static inline void ecc_ll_power_up(void) { REG_CLR_BIT(PCR_ECC_PD_CTRL_REG, PCR_ECC_MEM_PD); REG_CLR_BIT(PCR_ECC_PD_CTRL_REG, PCR_ECC_MEM_FORCE_PD); + ESP_FAULT_ASSERT(REG_GET_BIT(PCR_ECC_PD_CTRL_REG, PCR_ECC_MEM_FORCE_PD) == 0); } static inline void ecc_ll_power_down(void) diff --git a/components/esp_hal_security/esp32h4/include/hal/ecc_ll.h b/components/esp_hal_security/esp32h4/include/hal/ecc_ll.h index e5a89584970..3a1112f97d6 100644 --- a/components/esp_hal_security/esp32h4/include/hal/ecc_ll.h +++ b/components/esp_hal_security/esp32h4/include/hal/ecc_ll.h @@ -12,6 +12,7 @@ #include "soc/ecc_mult_reg.h" #include "soc/pcr_struct.h" #include "soc/pcr_reg.h" +#include "esp_fault.h" #ifdef __cplusplus extern "C" { @@ -50,9 +51,11 @@ static inline void ecc_ll_reset_register(void) static inline void ecc_ll_power_up(void) { - /* Power up the ECC peripheral (default state is power-down) */ + /* Power up the ECC peripheral (default state is power-up) */ REG_CLR_BIT(PCR_ECC_MEM_LP_CTRL_REG, PCR_ECC_MEM_LP_EN); - REG_CLR_BIT(PCR_ECC_MEM_LP_CTRL_REG, PCR_ECC_MEM_FORCE_CTRL); + REG_SET_BIT(PCR_ECC_MEM_LP_CTRL_REG, PCR_ECC_MEM_FORCE_CTRL); + ESP_FAULT_ASSERT(REG_GET_BIT(PCR_ECC_MEM_LP_CTRL_REG, PCR_ECC_MEM_LP_EN) == 0 && + REG_GET_BIT(PCR_ECC_MEM_LP_CTRL_REG, PCR_ECC_MEM_FORCE_CTRL) != 0); } static inline void ecc_ll_power_down(void) diff --git a/components/esp_hal_security/esp32p4/include/hal/ecc_ll.h b/components/esp_hal_security/esp32p4/include/hal/ecc_ll.h index ab4b65d187d..696c14b94a4 100644 --- a/components/esp_hal_security/esp32p4/include/hal/ecc_ll.h +++ b/components/esp_hal_security/esp32p4/include/hal/ecc_ll.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2023-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2023-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -12,8 +12,10 @@ #include "hal/efuse_hal.h" #include "soc/ecc_mult_reg.h" #include "soc/hp_sys_clkrst_struct.h" +#include "soc/hp_system_reg.h" #include "soc/chip_revision.h" #include "hal/config.h" +#include "esp_fault.h" #ifdef __cplusplus extern "C" { @@ -65,8 +67,25 @@ static inline void ecc_ll_reset_register(void) ecc_ll_reset_register(__VA_ARGS__); \ } while(0) -static inline void ecc_ll_power_up(void) {} -static inline void ecc_ll_power_down(void) {} +static inline void ecc_ll_power_up(void) +{ + /* Power up the ECC peripheral (default state is power-up) */ + REG_CLR_BIT(HP_SYSTEM_ECC_PD_CTRL_REG, HP_SYSTEM_ECC_MEM_PD); + REG_CLR_BIT(HP_SYSTEM_ECC_PD_CTRL_REG, HP_SYSTEM_ECC_MEM_FORCE_PD); + ESP_FAULT_ASSERT(REG_GET_BIT(HP_SYSTEM_ECC_PD_CTRL_REG, HP_SYSTEM_ECC_MEM_FORCE_PD) == 0); +} + +static inline bool ecc_ll_mem_force_pd_is_clear(void) +{ + return REG_GET_BIT(HP_SYSTEM_ECC_PD_CTRL_REG, HP_SYSTEM_ECC_MEM_FORCE_PD) == 0; +} + +static inline void ecc_ll_power_down(void) +{ + /* Power down the ECC peripheral */ + REG_CLR_BIT(HP_SYSTEM_ECC_PD_CTRL_REG, HP_SYSTEM_ECC_MEM_FORCE_PU); + REG_SET_BIT(HP_SYSTEM_ECC_PD_CTRL_REG, HP_SYSTEM_ECC_MEM_PD); +} static inline void ecc_ll_enable_interrupt(void) { diff --git a/components/esp_hal_security/test_apps/crypto/main/ecc/test_ecc.c b/components/esp_hal_security/test_apps/crypto/main/ecc/test_ecc.c index d2a4043aa66..f82cab91f85 100644 --- a/components/esp_hal_security/test_apps/crypto/main/ecc/test_ecc.c +++ b/components/esp_hal_security/test_apps/crypto/main/ecc/test_ecc.c @@ -207,6 +207,10 @@ static void test_ecc_point_mul_inner_constant_time(void) uint32_t max_time = 0, min_time = UINT32_MAX; int loop_count = 10; + /* Warm-up: the first call is otherwise an I-cache / branch-predictor + * outlier that dominates max_time and thus creating deviations */ + ecc_point_mul(scalar_le, x_le, y_le, ECC_P256_SIZE_BYTES, 0, x_res_le, y_res_le); + for (int i = 0; i < loop_count; i++) { ccomp_timer_start(); ecc_point_mul(scalar_le, x_le, y_le, ECC_P256_SIZE_BYTES, 0, x_res_le, y_res_le); @@ -230,6 +234,9 @@ static void test_ecc_point_mul_inner_constant_time(void) min_time = UINT32_MAX; total_elapsed_time = 0; + /* Warm-up — see comment on the P256 loop. */ + ecc_point_mul(scalar_le, x_le, y_le, ECC_P192_SIZE_BYTES, 0, x_res_le, y_res_le); + for (int i = 0; i < loop_count; i++) { ccomp_timer_start(); ecc_point_mul(scalar_le, x_le, y_le, ECC_P192_SIZE_BYTES, 0, x_res_le, y_res_le); @@ -255,6 +262,9 @@ static void test_ecc_point_mul_inner_constant_time(void) min_time = UINT32_MAX; total_elapsed_time = 0; + /* Warm-up — see comment on the P256 loop. */ + ecc_point_mul(scalar_le, x_le, y_le, ECC_P384_SIZE_BYTES, 0, x_res_le, y_res_le); + for (int i = 0; i < loop_count; i++) { ccomp_timer_start(); ecc_point_mul(scalar_le, x_le, y_le, ECC_P384_SIZE_BYTES, 0, x_res_le, y_res_le); diff --git a/components/esp_hal_twai/esp32/include/hal/twai_ll.h b/components/esp_hal_twai/esp32/include/hal/twai_ll.h index 286893afa37..b83d4cf8003 100644 --- a/components/esp_hal_twai/esp32/include/hal/twai_ll.h +++ b/components/esp_hal_twai/esp32/include/hal/twai_ll.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -61,6 +61,7 @@ static uint32_t twai_ll_get_brp_max(void); #define TWAI_LL_BRP_DIV_THRESH 128 #define TWAI_LL_TSEG1_MIN 1 #define TWAI_LL_TSEG2_MIN 1 +#define TWAI_LL_PROP_MAX 0 //hardware don't support prop_seg #define TWAI_LL_TSEG1_MAX 16 //the max register value #define TWAI_LL_TSEG2_MAX 8 #define TWAI_LL_SJW_MAX 4 @@ -700,6 +701,17 @@ static inline void twai_ll_set_acc_filter(twai_dev_t *hw, uint32_t code, uint32_ /* ------------------------- TX/RX Buffer Registers ------------------------- */ +/** + * @brief Get the number of TX buffers that are preset in the hardware. + * + * @param hw Pointer to the TWAI-FD device hardware. + * @return The number of TX buffers available. + */ +static inline uint32_t twai_ll_get_tx_buffer_total(twai_dev_t *hw) +{ + return 1; // only one TX buffer +} + /** * @brief Copy a formatted TWAI frame into TX buffer for transmission * diff --git a/components/esp_hal_twai/esp32c3/include/hal/twai_ll.h b/components/esp_hal_twai/esp32c3/include/hal/twai_ll.h index eb81fc4d42c..ba61a769617 100644 --- a/components/esp_hal_twai/esp32c3/include/hal/twai_ll.h +++ b/components/esp_hal_twai/esp32c3/include/hal/twai_ll.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2021-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2021-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -38,6 +38,7 @@ extern "C" { #define TWAI_LL_BRP_MAX 16384 #define TWAI_LL_TSEG1_MIN 1 #define TWAI_LL_TSEG2_MIN 1 +#define TWAI_LL_PROP_MAX 0 //hardware don't support prop_seg #define TWAI_LL_TSEG1_MAX 16 //the max register value #define TWAI_LL_TSEG2_MAX 8 #define TWAI_LL_SJW_MAX 4 @@ -626,6 +627,17 @@ static inline void twai_ll_set_acc_filter(twai_dev_t *hw, uint32_t code, uint32_ /* ------------------------- TX/RX Buffer Registers ------------------------- */ +/** + * @brief Get the number of TX buffers that are preset in the hardware. + * + * @param hw Pointer to the TWAI-FD device hardware. + * @return The number of TX buffers available. + */ +static inline uint32_t twai_ll_get_tx_buffer_total(twai_dev_t *hw) +{ + return 1; // only one TX buffer +} + /** * @brief Copy a formatted TWAI frame into TX buffer for transmission * diff --git a/components/esp_hal_twai/esp32c5/include/hal/twaifd_ll.h b/components/esp_hal_twai/esp32c5/include/hal/twaifd_ll.h index 3baad4e6f7e..62fcf6061d1 100644 --- a/components/esp_hal_twai/esp32c5/include/hal/twaifd_ll.h +++ b/components/esp_hal_twai/esp32c5/include/hal/twaifd_ll.h @@ -48,6 +48,10 @@ #define TWAIFD_LL_TX_CMD_READY TWAIFD_TXCR // Set tx buffer to "Ready" state #define TWAIFD_LL_TX_CMD_ABORT TWAIFD_TXCA // Set tx buffer to "Aborted" state +#define TWAIFD_LL_TX_STATUS_SUCCESS 0x4 // TX buffer transmitted successfully +#define TWAIFD_LL_TX_STATUS_FAILED 0x6 // TX buffer transmission failed +#define TWAIFD_LL_TX_STATUS_ABORTED 0x7 // TX buffer transmission aborted + #define TWAIFD_LL_HW_CMD_RST_ERR_CNT TWAIFD_ERCRST // Error Counters Reset #define TWAIFD_LL_HW_CMD_RST_RX_CNT TWAIFD_RXFCRST // Clear RX bus traffic counter #define TWAIFD_LL_HW_CMD_RST_TX_CNT TWAIFD_TXFCRST // Clear TX bus traffic counter @@ -665,11 +669,10 @@ static inline uint32_t twaifd_ll_get_tx_buffer_total(twaifd_dev_t *hw) * @param buffer_idx Index of the TX buffer (0-7). * @return The status of the selected TX buffer. */ +__attribute__((always_inline)) static inline uint32_t twaifd_ll_get_tx_buffer_status(twaifd_dev_t *hw, uint8_t buffer_idx) { - HAL_ASSERT(buffer_idx < twaifd_ll_get_tx_buffer_total(hw)); // Ensure buffer index is valid - uint32_t reg_val = hw->tx_status.val; - return reg_val & (TWAIFD_TX2S_V << (TWAIFD_TX2S_S * buffer_idx)); // Get status for buffer + return (hw->tx_status.val >> (TWAIFD_TX2S_S * buffer_idx)) & TWAIFD_TX2S_V; } /** @@ -700,7 +703,6 @@ static inline void twaifd_ll_set_tx_buffer_cmd(twaifd_dev_t *hw, uint8_t buffer_ */ static inline void twaifd_ll_set_tx_buffer_priority(twaifd_dev_t *hw, uint8_t buffer_idx, uint32_t priority) { - HAL_ASSERT(buffer_idx < twaifd_ll_get_tx_buffer_total(hw)); // Ensure buffer index is valid uint32_t reg_val = hw->tx_priority.val; reg_val &= ~(TWAIFD_TXT1P_V << (TWAIFD_TXT2P_S * buffer_idx)); // Clear old priority reg_val |= priority << (TWAIFD_TXT2P_S * buffer_idx); // Set new priority diff --git a/components/esp_hal_twai/esp32c6/include/hal/twai_ll.h b/components/esp_hal_twai/esp32c6/include/hal/twai_ll.h index 47b0fe786db..adc318e5261 100644 --- a/components/esp_hal_twai/esp32c6/include/hal/twai_ll.h +++ b/components/esp_hal_twai/esp32c6/include/hal/twai_ll.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2022-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -33,6 +33,7 @@ extern "C" { #define TWAI_LL_BRP_MAX 32768 #define TWAI_LL_TSEG1_MIN 1 #define TWAI_LL_TSEG2_MIN 1 +#define TWAI_LL_PROP_MAX 0 //hardware don't support prop_seg #define TWAI_LL_TSEG1_MAX (TWAI_TIME_SEGMENT1 + 1) #define TWAI_LL_TSEG2_MAX (TWAI_TIME_SEGMENT2 + 1) #define TWAI_LL_SJW_MAX (TWAI_SYNC_JUMP_WIDTH + 1) @@ -636,6 +637,17 @@ static inline void twai_ll_set_acc_filter(twai_dev_t *hw, uint32_t code, uint32_ /* ------------------------- TX/RX Buffer Registers ------------------------- */ +/** + * @brief Get the number of TX buffers that are preset in the hardware. + * + * @param hw Pointer to the TWAI-FD device hardware. + * @return The number of TX buffers available. + */ +static inline uint32_t twai_ll_get_tx_buffer_total(twai_dev_t *hw) +{ + return 1; // only one TX buffer +} + /** * @brief Copy a formatted TWAI frame into TX buffer for transmission * diff --git a/components/esp_hal_twai/esp32h2/include/hal/twai_ll.h b/components/esp_hal_twai/esp32h2/include/hal/twai_ll.h index 909e8db2e2c..4b868b55503 100644 --- a/components/esp_hal_twai/esp32h2/include/hal/twai_ll.h +++ b/components/esp_hal_twai/esp32h2/include/hal/twai_ll.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2021-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2021-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -33,6 +33,7 @@ extern "C" { #define TWAI_LL_BRP_MAX 32768 #define TWAI_LL_TSEG1_MIN 1 #define TWAI_LL_TSEG2_MIN 1 +#define TWAI_LL_PROP_MAX 0 //hardware don't support prop_seg #define TWAI_LL_TSEG1_MAX (TWAI_TIME_SEGMENT1 + 1) #define TWAI_LL_TSEG2_MAX (TWAI_TIME_SEGMENT2 + 1) #define TWAI_LL_SJW_MAX (TWAI_SYNC_JUMP_WIDTH + 1) @@ -614,6 +615,17 @@ static inline void twai_ll_set_acc_filter(twai_dev_t *hw, uint32_t code, uint32_ /* ------------------------- TX/RX Buffer Registers ------------------------- */ +/** + * @brief Get the number of TX buffers that are preset in the hardware. + * + * @param hw Pointer to the TWAI-FD device hardware. + * @return The number of TX buffers available. + */ +static inline uint32_t twai_ll_get_tx_buffer_total(twai_dev_t *hw) +{ + return 1; // only one TX buffer +} + /** * @brief Copy a formatted TWAI frame into TX buffer for transmission * diff --git a/components/esp_hal_twai/esp32h21/include/hal/twai_ll.h b/components/esp_hal_twai/esp32h21/include/hal/twai_ll.h index 76aa0ffbda7..3d3ded42300 100644 --- a/components/esp_hal_twai/esp32h21/include/hal/twai_ll.h +++ b/components/esp_hal_twai/esp32h21/include/hal/twai_ll.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -33,6 +33,7 @@ extern "C" { #define TWAI_LL_BRP_MAX 32768 #define TWAI_LL_TSEG1_MIN 1 #define TWAI_LL_TSEG2_MIN 1 +#define TWAI_LL_PROP_MAX 0 //hardware don't support prop_seg #define TWAI_LL_TSEG1_MAX (TWAI_TIME_SEGMENT1 + 1) #define TWAI_LL_TSEG2_MAX (TWAI_TIME_SEGMENT2 + 1) #define TWAI_LL_SJW_MAX (TWAI_SYNC_JUMP_WIDTH + 1) @@ -614,6 +615,17 @@ static inline void twai_ll_set_acc_filter(twai_dev_t *hw, uint32_t code, uint32_ /* ------------------------- TX/RX Buffer Registers ------------------------- */ +/** + * @brief Get the number of TX buffers that are preset in the hardware. + * + * @param hw Pointer to the TWAI-FD device hardware. + * @return The number of TX buffers available. + */ +static inline uint32_t twai_ll_get_tx_buffer_total(twai_dev_t *hw) +{ + return 1; // only one TX buffer +} + /** * @brief Copy a formatted TWAI frame into TX buffer for transmission * diff --git a/components/esp_hal_twai/esp32h4/include/hal/twaifd_ll.h b/components/esp_hal_twai/esp32h4/include/hal/twaifd_ll.h index 1874ae9fc6d..a3b3f757c7e 100644 --- a/components/esp_hal_twai/esp32h4/include/hal/twaifd_ll.h +++ b/components/esp_hal_twai/esp32h4/include/hal/twaifd_ll.h @@ -48,6 +48,10 @@ #define TWAIFD_LL_TX_CMD_READY TWAIFD_TXCR // Set tx buffer to "Ready" state #define TWAIFD_LL_TX_CMD_ABORT TWAIFD_TXCA // Set tx buffer to "Aborted" state +#define TWAIFD_LL_TX_STATUS_SUCCESS 0x4 // TX buffer transmitted successfully +#define TWAIFD_LL_TX_STATUS_FAILED 0x6 // TX buffer transmission failed +#define TWAIFD_LL_TX_STATUS_ABORTED 0x7 // TX buffer transmission aborted + #define TWAIFD_LL_HW_CMD_RST_ERR_CNT TWAIFD_ERCRST // Error Counters Reset #define TWAIFD_LL_HW_CMD_RST_RX_CNT TWAIFD_RXFCRST // Clear RX bus traffic counter #define TWAIFD_LL_HW_CMD_RST_TX_CNT TWAIFD_TXFCRST // Clear TX bus traffic counter @@ -664,11 +668,10 @@ static inline uint32_t twaifd_ll_get_tx_buffer_total(twaifd_dev_t *hw) * @param buffer_idx Index of the TX buffer (0-7). * @return The status of the selected TX buffer. */ +__attribute__((always_inline)) static inline uint32_t twaifd_ll_get_tx_buffer_status(twaifd_dev_t *hw, uint8_t buffer_idx) { - HAL_ASSERT(buffer_idx < twaifd_ll_get_tx_buffer_total(hw)); // Ensure buffer index is valid - uint32_t reg_val = hw->tx_status.val; - return reg_val & (TWAIFD_TX2S_V << (TWAIFD_TX2S_S * buffer_idx)); // Get status for buffer + return (hw->tx_status.val >> (TWAIFD_TX2S_S * buffer_idx)) & TWAIFD_TX2S_V; } /** @@ -699,7 +702,6 @@ static inline void twaifd_ll_set_tx_buffer_cmd(twaifd_dev_t *hw, uint8_t buffer_ */ static inline void twaifd_ll_set_tx_buffer_priority(twaifd_dev_t *hw, uint8_t buffer_idx, uint32_t priority) { - HAL_ASSERT(buffer_idx < twaifd_ll_get_tx_buffer_total(hw)); // Ensure buffer index is valid uint32_t reg_val = hw->tx_priority.val; reg_val &= ~(TWAIFD_TXT1P_V << (TWAIFD_TXT2P_S * buffer_idx)); // Clear old priority reg_val |= priority << (TWAIFD_TXT2P_S * buffer_idx); // Set new priority diff --git a/components/esp_hal_twai/esp32p4/include/hal/twai_ll.h b/components/esp_hal_twai/esp32p4/include/hal/twai_ll.h index 2e4b65b7a1f..1a2ebb7e3f0 100644 --- a/components/esp_hal_twai/esp32p4/include/hal/twai_ll.h +++ b/components/esp_hal_twai/esp32p4/include/hal/twai_ll.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2023-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2023-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -33,6 +33,7 @@ extern "C" { #define TWAI_LL_BRP_MAX 32768 #define TWAI_LL_TSEG1_MIN 1 #define TWAI_LL_TSEG2_MIN 1 +#define TWAI_LL_PROP_MAX 0 //hardware don't support prop_seg #define TWAI_LL_TSEG1_MAX (TWAI_TIME_SEGMENT1 + 1) #define TWAI_LL_TSEG2_MAX (TWAI_TIME_SEGMENT2 + 1) #define TWAI_LL_SJW_MAX (TWAI_SYNC_JUMP_WIDTH + 1) @@ -679,6 +680,17 @@ static inline void twai_ll_set_acc_filter(twai_dev_t *hw, uint32_t code, uint32_ /* ------------------------- TX/RX Buffer Registers ------------------------- */ +/** + * @brief Get the number of TX buffers that are preset in the hardware. + * + * @param hw Pointer to the TWAI-FD device hardware. + * @return The number of TX buffers available. + */ +static inline uint32_t twai_ll_get_tx_buffer_total(twai_dev_t *hw) +{ + return 1; // only one TX buffer +} + /** * @brief Copy a formatted TWAI frame into TX buffer for transmission * diff --git a/components/esp_hal_twai/esp32s2/include/hal/twai_ll.h b/components/esp_hal_twai/esp32s2/include/hal/twai_ll.h index bb670d63b06..d2f9d7f9ae1 100644 --- a/components/esp_hal_twai/esp32s2/include/hal/twai_ll.h +++ b/components/esp_hal_twai/esp32s2/include/hal/twai_ll.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -38,6 +38,7 @@ extern "C" { #define TWAI_LL_BRP_MAX 32768 #define TWAI_LL_TSEG1_MIN 1 #define TWAI_LL_TSEG2_MIN 1 +#define TWAI_LL_PROP_MAX 0 //hardware don't support prop_seg #define TWAI_LL_TSEG1_MAX 16 //the max register value #define TWAI_LL_TSEG2_MAX 8 #define TWAI_LL_SJW_MAX 4 @@ -629,6 +630,17 @@ static inline void twai_ll_set_acc_filter(twai_dev_t *hw, uint32_t code, uint32_ /* ------------------------- TX/RX Buffer Registers ------------------------- */ +/** + * @brief Get the number of TX buffers that are preset in the hardware. + * + * @param hw Pointer to the TWAI-FD device hardware. + * @return The number of TX buffers available. + */ +static inline uint32_t twai_ll_get_tx_buffer_total(twai_dev_t *hw) +{ + return 1; // only one TX buffer +} + /** * @brief Copy a formatted TWAI frame into TX buffer for transmission * diff --git a/components/esp_hal_twai/esp32s3/include/hal/twai_ll.h b/components/esp_hal_twai/esp32s3/include/hal/twai_ll.h index ddef2cbfc08..5906369caba 100644 --- a/components/esp_hal_twai/esp32s3/include/hal/twai_ll.h +++ b/components/esp_hal_twai/esp32s3/include/hal/twai_ll.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -38,6 +38,7 @@ extern "C" { #define TWAI_LL_BRP_MAX 16384 #define TWAI_LL_TSEG1_MIN 1 #define TWAI_LL_TSEG2_MIN 1 +#define TWAI_LL_PROP_MAX 0 //hardware don't support prop_seg #define TWAI_LL_TSEG1_MAX 16 //the max register value #define TWAI_LL_TSEG2_MAX 8 #define TWAI_LL_SJW_MAX 4 @@ -626,6 +627,17 @@ static inline void twai_ll_set_acc_filter(twai_dev_t *hw, uint32_t code, uint32_ /* ------------------------- TX/RX Buffer Registers ------------------------- */ +/** + * @brief Get the number of TX buffers that are preset in the hardware. + * + * @param hw Pointer to the TWAI-FD device hardware. + * @return The number of TX buffers available. + */ +static inline uint32_t twai_ll_get_tx_buffer_total(twai_dev_t *hw) +{ + return 1; // only one TX buffer +} + /** * @brief Copy a formatted TWAI frame into TX buffer for transmission * diff --git a/components/esp_hal_twai/include/hal/twai_hal.h b/components/esp_hal_twai/include/hal/twai_hal.h index 23bf080ab5a..5fc80557f1e 100644 --- a/components/esp_hal_twai/include/hal/twai_hal.h +++ b/components/esp_hal_twai/include/hal/twai_hal.h @@ -50,9 +50,29 @@ typedef union twai_ll_frame_buffer_t twai_hal_frame_t; #define TWAI_HAL_EVENT_BUS_ERR (1 << 7) #define TWAI_HAL_EVENT_ARB_LOST (1 << 8) #define TWAI_HAL_EVENT_RX_BUFF_FRAME (1 << 9) -#define TWAI_HAL_EVENT_TX_BUFF_FREE (1 << 10) -#define TWAI_HAL_EVENT_NEED_PERIPH_RESET (1 << 11) -#define TWAI_HAL_EVENT_TX_SUCCESS (1 << 12) +#define TWAI_HAL_EVENT_NEED_PERIPH_RESET (1 << 10) +#define TWAI_HAL_EVENT_TX0_DONE (1 << 11) +#define TWAI_HAL_EVENT_TX0_SUCCESS (1 << 12) +#define TWAI_HAL_EVENT_TX1_DONE (1 << 13) +#define TWAI_HAL_EVENT_TX1_SUCCESS (1 << 14) +#define TWAI_HAL_EVENT_TX2_DONE (1 << 15) +#define TWAI_HAL_EVENT_TX2_SUCCESS (1 << 16) +#define TWAI_HAL_EVENT_TX3_DONE (1 << 17) +#define TWAI_HAL_EVENT_TX3_SUCCESS (1 << 18) +#define TWAI_HAL_EVENT_TX4_DONE (1 << 19) +#define TWAI_HAL_EVENT_TX4_SUCCESS (1 << 20) +#define TWAI_HAL_EVENT_TX5_DONE (1 << 21) +#define TWAI_HAL_EVENT_TX5_SUCCESS (1 << 22) +#define TWAI_HAL_EVENT_TX6_DONE (1 << 23) +#define TWAI_HAL_EVENT_TX6_SUCCESS (1 << 24) +#define TWAI_HAL_EVENT_TX7_DONE (1 << 25) +#define TWAI_HAL_EVENT_TX7_SUCCESS (1 << 26) +#define TWAI_HAL_TX_BUFFER_SLOT_NUM 8 // support up to 8 TX slots in hal layer + +#define TWAI_HAL_EVENT_TX_DONE_MASK (TWAI_HAL_EVENT_TX0_DONE | TWAI_HAL_EVENT_TX1_DONE | TWAI_HAL_EVENT_TX2_DONE | TWAI_HAL_EVENT_TX3_DONE | \ + TWAI_HAL_EVENT_TX4_DONE | TWAI_HAL_EVENT_TX5_DONE | TWAI_HAL_EVENT_TX6_DONE | TWAI_HAL_EVENT_TX7_DONE) +#define TWAI_HAL_EVENT_TX_DONE_SLOT(buffer_idx) (TWAI_HAL_EVENT_TX0_DONE << ((buffer_idx) * 2)) +#define TWAI_HAL_EVENT_TX_SUCC_SLOT(buffer_idx) (TWAI_HAL_EVENT_TX0_SUCCESS << ((buffer_idx) * 2)) typedef struct { twai_soc_handle_t dev; // TWAI SOC layer handle (i.e. register base address) @@ -61,6 +81,7 @@ typedef struct { uint32_t timer_overflow_cnt; twai_error_flags_t errors; uint8_t sja1000_filter_id_type; // hardware don't check id type, check in software, 0:no_filter, 1: std_id_only, 2: ext_id_only + uint8_t tx_buffer_num; int8_t retry_cnt; bool enable_self_test; bool enable_loopback; @@ -296,6 +317,14 @@ static inline twai_error_flags_t twai_hal_get_err_flags(twai_hal_context_t *hal_ */ uint32_t twai_hal_get_rx_msg_count(twai_hal_context_t *hal_ctx); +/** + * @brief Get the number of TX buffers supported by the hardware + * + * @param hal_ctx Context of the HAL layer + * @return TX buffer count + */ +#define twai_hal_get_tx_slot_num(hal_ctx) (hal_ctx->tx_buffer_num) + /** * @brief TWAI hal transaction description type */ diff --git a/components/esp_hal_twai/twai_hal_v1.c b/components/esp_hal_twai/twai_hal_v1.c index 518f5a61c4c..8db40957686 100644 --- a/components/esp_hal_twai/twai_hal_v1.c +++ b/components/esp_hal_twai/twai_hal_v1.c @@ -50,6 +50,7 @@ bool twai_hal_init(twai_hal_context_t *hal_ctx, const twai_hal_config_t *config) if (!twai_ll_is_in_reset_mode(hal_ctx->dev)) { //Must enter reset mode to write to config registers return false; } + hal_ctx->tx_buffer_num = twai_ll_get_tx_buffer_total(hal_ctx->dev); #if TWAI_LL_HAS_RX_FRAME_ISSUE || TWAI_LL_HAS_RX_FIFO_ISSUE hal_ctx->errata_ctx = (twai_hal_errata_ctx_t *)(hal_ctx + 1); //errata context is place at end of hal_ctx #endif @@ -238,10 +239,10 @@ static inline uint32_t twai_hal_decode_interrupt(twai_hal_context_t *hal_ctx) #else if (interrupts & TWAI_LL_INTR_TI) { #endif - TWAI_HAL_SET_BITS(events, TWAI_HAL_EVENT_TX_BUFF_FREE); + TWAI_HAL_SET_BITS(events, TWAI_HAL_EVENT_TX0_DONE); TWAI_HAL_CLEAR_BITS(state_flags, TWAI_HAL_STATE_FLAG_TX_BUFF_OCCUPIED); if (status & TWAI_LL_STATUS_TCS) { - TWAI_HAL_SET_BITS(events, TWAI_HAL_EVENT_TX_SUCCESS); + TWAI_HAL_SET_BITS(events, TWAI_HAL_EVENT_TX0_SUCCESS); } } //Error Passive Interrupt on transition from error active to passive or vice versa diff --git a/components/esp_hal_twai/twai_hal_v2.c b/components/esp_hal_twai/twai_hal_v2.c index cba5b646577..05e54ee5f7a 100644 --- a/components/esp_hal_twai/twai_hal_v2.c +++ b/components/esp_hal_twai/twai_hal_v2.c @@ -22,6 +22,7 @@ bool twai_hal_init(twai_hal_context_t *hal_ctx, const twai_hal_config_t *config) hal_ctx->enable_listen_only = config->enable_listen_only; twaifd_ll_reset(hal_ctx->dev); + hal_ctx->tx_buffer_num = twaifd_ll_get_tx_buffer_total(hal_ctx->dev); twaifd_ll_enable_hw(hal_ctx->dev, false); //mode should be changed under disabled twaifd_ll_set_mode(hal_ctx->dev, config->enable_listen_only, config->enable_self_test, config->enable_loopback); twaifd_ll_set_tx_retrans_limit(hal_ctx->dev, config->retry_cnt); @@ -222,9 +223,20 @@ uint32_t twai_hal_get_events(twai_hal_context_t *hal_ctx) hal_ctx->timer_overflow_cnt ++; } if (int_stat & (TWAIFD_LL_INTR_TX_DONE)) { - hal_events |= TWAI_HAL_EVENT_TX_BUFF_FREE; - if (int_stat & TWAIFD_LL_INTR_TX_FRAME) { - hal_events |= TWAI_HAL_EVENT_TX_SUCCESS; + for (uint32_t i = 0; i < MIN(hal_ctx->tx_buffer_num, TWAI_HAL_TX_BUFFER_SLOT_NUM); i++) { + uint32_t tx_status = twaifd_ll_get_tx_buffer_status(hal_ctx->dev, i); + switch (tx_status) { + case TWAIFD_LL_TX_STATUS_SUCCESS: + hal_events |= TWAI_HAL_EVENT_TX_SUCC_SLOT(i); + __attribute__((fallthrough)); // success event must be a done event, just fallthrough + case TWAIFD_LL_TX_STATUS_FAILED: + case TWAIFD_LL_TX_STATUS_ABORTED: + hal_events |= TWAI_HAL_EVENT_TX_DONE_SLOT(i); + twaifd_ll_set_tx_buffer_cmd(hal_ctx->dev, i, TWAIFD_LL_TX_CMD_EMPTY); // clear buffer to empty state + break; + default: + break; + } } } if (int_stat & TWAIFD_LL_INTR_RX_NOT_EMPTY) { diff --git a/components/esp_hal_uart/esp32s3/include/hal/uart_ll.h b/components/esp_hal_uart/esp32s3/include/hal/uart_ll.h index 0048d64b57b..047b07466cc 100644 --- a/components/esp_hal_uart/esp32s3/include/hal/uart_ll.h +++ b/components/esp_hal_uart/esp32s3/include/hal/uart_ll.h @@ -71,14 +71,19 @@ typedef enum { */ FORCE_INLINE_ATTR bool uart_ll_is_enabled(uint32_t uart_num) { - uint32_t uart_rst_bit = ((uart_num == 0) ? SYSTEM_UART_RST : - (uart_num == 1) ? SYSTEM_UART1_RST : - (uart_num == 2) ? SYSTEM_UART2_RST : 0); - uint32_t uart_en_bit = ((uart_num == 0) ? SYSTEM_UART_CLK_EN : - (uart_num == 1) ? SYSTEM_UART1_CLK_EN : - (uart_num == 2) ? SYSTEM_UART2_CLK_EN : 0); - return DPORT_REG_GET_BIT(SYSTEM_PERIP_RST_EN0_REG, uart_rst_bit) == 0 && - DPORT_REG_GET_BIT(SYSTEM_PERIP_CLK_EN0_REG, uart_en_bit) != 0; + switch (uart_num) { + case 0: + return DPORT_REG_GET_BIT(SYSTEM_PERIP_RST_EN0_REG, SYSTEM_UART_RST) == 0 && + DPORT_REG_GET_BIT(SYSTEM_PERIP_CLK_EN0_REG, SYSTEM_UART_CLK_EN) != 0; + case 1: + return DPORT_REG_GET_BIT(SYSTEM_PERIP_RST_EN0_REG, SYSTEM_UART1_RST) == 0 && + DPORT_REG_GET_BIT(SYSTEM_PERIP_CLK_EN0_REG, SYSTEM_UART1_CLK_EN) != 0; + case 2: + return DPORT_REG_GET_BIT(SYSTEM_PERIP_RST_EN1_REG, SYSTEM_UART2_RST) == 0 && + DPORT_REG_GET_BIT(SYSTEM_PERIP_CLK_EN1_REG, SYSTEM_UART2_CLK_EN) != 0; + default: + abort(); + } } /** diff --git a/components/esp_hal_usb/esp32h4/include/hal/usb_wrap_ll.h b/components/esp_hal_usb/esp32h4/include/hal/usb_wrap_ll.h index d233ed52ffd..23367f09424 100644 --- a/components/esp_hal_usb/esp32h4/include/hal/usb_wrap_ll.h +++ b/components/esp_hal_usb/esp32h4/include/hal/usb_wrap_ll.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -172,6 +172,20 @@ FORCE_INLINE_ATTR void usb_wrap_ll_phy_set_tx_edge(usb_wrap_dev_t *hw, bool clk_ (void)clk_neg_edge; } +/** + * @brief Route internal FSLS PHY AHB/PHY clock gating to DWC2 + * + * Clears clock force-on bits so DWC2 can gate the internal PHY clocks during + * port suspend and internal clock gating. + * + * @param hw Start address of the USB Wrap registers + */ +FORCE_INLINE_ATTR void usb_wrap_ll_enable_automatic_phy_control(usb_wrap_dev_t *hw) +{ + hw->wrap_otg_conf.wrap_ahb_clk_force_on = 0; + hw->wrap_otg_conf.wrap_phy_clk_force_on = 0; +} + /* ------------------------------ USB PHY Test ------------------------------ */ /** diff --git a/components/esp_hal_usb/esp32p4/include/hal/usb_utmi_ll.h b/components/esp_hal_usb/esp32p4/include/hal/usb_utmi_ll.h index f64250a66e6..ea02eff9d1e 100644 --- a/components/esp_hal_usb/esp32p4/include/hal/usb_utmi_ll.h +++ b/components/esp_hal_usb/esp32p4/include/hal/usb_utmi_ll.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2024-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -11,7 +11,9 @@ #include "soc/lp_clkrst_struct.h" #include "soc/hp_sys_clkrst_struct.h" #include "soc/hp_system_struct.h" +#include "soc/lp_system_struct.h" #include "soc/usb_utmi_struct.h" +#include "hal/config.h" #ifdef __cplusplus extern "C" { @@ -81,6 +83,29 @@ FORCE_INLINE_ATTR void _usb_utmi_ll_reset_register(void) _usb_utmi_ll_reset_register(__VA_ARGS__); \ } while(0) +/** + * @brief Enable/disable 15k pulldown resistors on D+/D- lines + * + * In USB Host mode, 15k pulldown resistors must be connected on both D+ and D-. + * In USB Device mode, pulldown resistors must be disconnected. + * + * @note On ESP32-P4 v3+, pulldowns are no longer controlled by USB-OTG peripheral + * and must be controlled by software via LP_SYS registers. + * On earlier revisions, pulldowns are controlled by the USB-OTG hardware. + * + * @param[in] enable true to connect pulldowns (Host mode), false to disconnect (Device mode) + */ +FORCE_INLINE_ATTR void usb_utmi_ll_enable_data_pulldowns(bool enable) +{ +#if HAL_CONFIG(CHIP_SUPPORT_MIN_REV) >= 300 + LP_SYS.hp_usb_otghs_phy_ctrl.hp_utmiotg_dppulldown = enable; + LP_SYS.hp_usb_otghs_phy_ctrl.hp_utmiotg_dmpulldown = enable; +#else + // On pre-v3 ESP32-P4, pulldowns are controlled by the USB-OTG peripheral + (void)enable; +#endif +} + /** * @brief Enable precise detection of VBUS * @@ -92,6 +117,24 @@ FORCE_INLINE_ATTR void usb_utmi_ll_enable_precise_detection(bool enable) HP_SYSTEM.sys_usbotg20_ctrl.sys_otg_suspendm = enable; } +/** + * @brief Set USB OTG2.0 suspend state for PMU USB wakeup logic + * + * @param[in] in_suspend True if USB OTG2.0 is suspended + */ +FORCE_INLINE_ATTR void usb_utmi_ll_set_suspend_state(bool in_suspend) +{ + LP_SYS.usb_ctrl.usbotg20_in_suspend = in_suspend; +} + +/** + * @brief Clear USB OTG2.0 wakeup status sent to PMU + */ +FORCE_INLINE_ATTR void usb_utmi_ll_clear_wakeup_status(void) +{ + LP_SYS.usb_ctrl.usbotg20_wakeup_clr = 1; +} + #ifdef __cplusplus } #endif diff --git a/components/esp_hal_usb/esp32p4/include/hal/usb_wrap_ll.h b/components/esp_hal_usb/esp32p4/include/hal/usb_wrap_ll.h index 75d2c440d2f..90ff6fb6d10 100644 --- a/components/esp_hal_usb/esp32p4/include/hal/usb_wrap_ll.h +++ b/components/esp_hal_usb/esp32p4/include/hal/usb_wrap_ll.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2024-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -233,6 +233,20 @@ FORCE_INLINE_ATTR void usb_wrap_ll_phy_test_mode_set_signals(usb_wrap_dev_t *hw, hw->test_conf.val = test_conf.val; } +/** + * @brief Route internal FSLS PHY AHB/PHY clock gating to DWC2 + * + * Clears clock force-on bits so DWC2 can gate the internal PHY clocks during + * port suspend and internal clock gating. + * + * @param hw Start address of the USB Wrap registers + */ +FORCE_INLINE_ATTR void usb_wrap_ll_enable_automatic_phy_control(usb_wrap_dev_t *hw) +{ + hw->otg_conf.ahb_clk_force_on = 0; + hw->otg_conf.phy_clk_force_on = 0; +} + /* ----------------------------- RCC Functions ----------------------------- */ /** diff --git a/components/esp_hal_usb/esp32s2/include/hal/usb_wrap_ll.h b/components/esp_hal_usb/esp32s2/include/hal/usb_wrap_ll.h index a9619fcaa51..1768162c126 100644 --- a/components/esp_hal_usb/esp32s2/include/hal/usb_wrap_ll.h +++ b/components/esp_hal_usb/esp32s2/include/hal/usb_wrap_ll.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -200,6 +200,20 @@ FORCE_INLINE_ATTR void usb_wrap_ll_phy_test_mode_set_signals(usb_wrap_dev_t *hw, hw->test_conf.val = test_conf.val; } +/** + * @brief Route internal FSLS PHY AHB/PHY clock gating to DWC2 + * + * Clears clock force-on bits so DWC2 can gate the internal PHY clocks during + * port suspend and internal clock gating. + * + * @param hw Start address of the USB Wrap registers + */ +FORCE_INLINE_ATTR void usb_wrap_ll_enable_automatic_phy_control(usb_wrap_dev_t *hw) +{ + hw->otg_conf.ahb_clk_force_on = 0; + hw->otg_conf.phy_clk_force_on = 0; +} + /* ----------------------------- RCC Functions ----------------------------- */ /** diff --git a/components/esp_hal_usb/esp32s3/include/hal/usb_wrap_ll.h b/components/esp_hal_usb/esp32s3/include/hal/usb_wrap_ll.h index 42a557b2a22..23a02a83441 100644 --- a/components/esp_hal_usb/esp32s3/include/hal/usb_wrap_ll.h +++ b/components/esp_hal_usb/esp32s3/include/hal/usb_wrap_ll.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -209,6 +209,20 @@ FORCE_INLINE_ATTR void usb_wrap_ll_phy_test_mode_set_signals(usb_wrap_dev_t *hw, hw->test_conf.val = test_conf.val; } +/** + * @brief Route internal FSLS PHY AHB/PHY clock gating to DWC2 + * + * Clears clock force-on bits so DWC2 can gate the internal PHY clocks during + * port suspend and internal clock gating. + * + * @param hw Start address of the USB Wrap registers + */ +FORCE_INLINE_ATTR void usb_wrap_ll_enable_automatic_phy_control(usb_wrap_dev_t *hw) +{ + hw->otg_conf.ahb_clk_force_on = 0; + hw->otg_conf.phy_clk_force_on = 0; +} + /* ----------------------------- RCC Functions ----------------------------- */ /** diff --git a/components/esp_hal_usb/include/hal/usb_utmi_hal.h b/components/esp_hal_usb/include/hal/usb_utmi_hal.h index dcb859a21c3..1466c57b516 100644 --- a/components/esp_hal_usb/include/hal/usb_utmi_hal.h +++ b/components/esp_hal_usb/include/hal/usb_utmi_hal.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2024-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -57,6 +57,28 @@ void _usb_utmi_hal_disable(void); #define usb_utmi_hal_disable(...) do {(void)__DECLARE_RCC_ATOMIC_ENV; _usb_utmi_hal_disable(__VA_ARGS__);} while(0) #endif +/** + * @brief Enable/disable 15k pulldown resistors on D+/D- lines + * + * In USB Host mode, 15k pulldown resistors must be connected on both D+ and D-. + * In USB Device mode, pulldown resistors must be disconnected. + * + * @param[in] enable true to connect pulldowns (Host mode), false to disconnect (Device mode) + */ +void usb_utmi_hal_enable_data_pulldowns(bool enable); + +/** + * @brief Set USB OTG2.0 suspend state for PMU USB wakeup logic + * + * @param[in] in_suspend True if USB OTG2.0 is suspended + */ +void usb_utmi_hal_set_suspend_state(bool in_suspend); + +/** + * @brief Clear USB OTG2.0 wakeup status sent to PMU + */ +void usb_utmi_hal_clear_wakeup_status(void); + #endif // (SOC_USB_UTMI_PHY_NUM > 0) #ifdef __cplusplus diff --git a/components/esp_hal_usb/usb_utmi_hal.c b/components/esp_hal_usb/usb_utmi_hal.c index cb7ceb49ba2..76ba413852c 100644 --- a/components/esp_hal_usb/usb_utmi_hal.c +++ b/components/esp_hal_usb/usb_utmi_hal.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2024-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -23,6 +23,21 @@ void _usb_utmi_hal_init(usb_utmi_hal_context_t *hal) usb_utmi_ll_configure_ls(hal->dev, true); } +void usb_utmi_hal_enable_data_pulldowns(bool enable) +{ + usb_utmi_ll_enable_data_pulldowns(enable); +} + +void usb_utmi_hal_set_suspend_state(bool in_suspend) +{ + usb_utmi_ll_set_suspend_state(in_suspend); +} + +void usb_utmi_hal_clear_wakeup_status(void) +{ + usb_utmi_ll_clear_wakeup_status(); +} + void _usb_utmi_hal_disable(void) { _usb_utmi_ll_enable_bus_clock(false); diff --git a/components/esp_hal_usb/usb_wrap_hal.c b/components/esp_hal_usb/usb_wrap_hal.c index d0218ead6f9..de6175e0eb2 100644 --- a/components/esp_hal_usb/usb_wrap_hal.c +++ b/components/esp_hal_usb/usb_wrap_hal.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2024 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -13,6 +13,7 @@ void _usb_wrap_hal_init(usb_wrap_hal_context_t *hal) hal->dev = &USB_WRAP; _usb_wrap_ll_enable_bus_clock(true); _usb_wrap_ll_reset_register(); + usb_wrap_ll_enable_automatic_phy_control(hal->dev); #if !USB_WRAP_LL_EXT_PHY_SUPPORTED usb_wrap_ll_phy_set_defaults(hal->dev); #endif diff --git a/components/esp_hid/src/ble_hidh.c b/components/esp_hid/src/ble_hidh.c index b10119ed9b3..90ae8cb14e8 100644 --- a/components/esp_hid/src/ble_hidh.c +++ b/components/esp_hid/src/ble_hidh.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2017-2024 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2017-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -302,6 +302,21 @@ static void attach_report_listeners(esp_gatt_if_t gattc_if, esp_hidh_dev_t *dev) //subscribe to battery notifications if (dev->ble.battery_handle) { + uint8_t *rdata = NULL; + uint16_t rlen = 0; + + if (event_loop_handle && + read_char(gattc_if, dev->ble.conn_id, dev->ble.battery_handle, + ESP_GATT_AUTH_REQ_NO_MITM, &rdata, &rlen) == ESP_GATT_OK && + rlen >= 1 && rdata != NULL) { + esp_hidh_event_data_t p = {0}; + p.battery.dev = dev; + p.battery.level = rdata[0]; + esp_event_post_to(event_loop_handle, ESP_HIDH_EVENTS, ESP_HIDH_BATTERY_EVENT, + &p, sizeof(esp_hidh_event_data_t), portMAX_DELAY); + } + free(rdata); + register_for_notify(gattc_if, dev->addr.bda, dev->ble.battery_handle); if (dev->ble.battery_ccc_handle) { //Write CCC descr to enable notifications diff --git a/components/esp_hid/src/nimble_hidd.c b/components/esp_hid/src/nimble_hidd.c index 3324079afbd..aa4efda4bff 100644 --- a/components/esp_hid/src/nimble_hidd.c +++ b/components/esp_hid/src/nimble_hidd.c @@ -43,6 +43,9 @@ static void (*s_prev_sync_cb)(void) = NULL; static struct ble_gap_event_listener nimble_gap_event_listener; static void nimble_host_synced(void); void nimble_host_reset(int reason); +static void nimble_report_write_cb(uint16_t attr_handle, uint8_t report_type, uint8_t report_id, + const uint8_t *data, uint16_t len); +static void nimble_char_write_cb(uint16_t attr_handle, uint16_t char_uuid16, uint8_t value); static inline void lock_hidd(void) { @@ -349,6 +352,8 @@ static int nimble_hidd_dev_deinit(void *devp) ble_hs_cfg.sync_cb = s_prev_sync_cb; } ble_hs_cfg.gatts_register_cb = NULL; + ble_svc_hid_register_report_write_cb(NULL); + ble_svc_hid_register_char_write_cb(NULL); unlock_hidd(); /* Known timing issue: STOP_EVENT is posted here but ble_hid_free_config (called @@ -425,6 +430,129 @@ static hidd_le_report_item_t* find_report_by_usage_and_type(uint8_t dev_index, u return NULL; } +static void nimble_report_write_cb(uint16_t attr_handle, uint8_t report_type, uint8_t report_id, + const uint8_t *data, uint16_t len) +{ + lock_hidd(); + if (s_dev == NULL || s_dev->event_loop_handle == NULL || data == NULL) { + unlock_hidd(); + return; + } + + hidd_le_report_item_t *match = NULL; + uint8_t map_index = 0; + for (uint8_t d = 0; d < s_dev->devices_len && match == NULL; d++) { + for (uint8_t r = 0; r < s_dev->devices[d].reports_len; r++) { + hidd_le_report_item_t *item = &s_dev->devices[d].reports[r]; + if (item->handle == attr_handle) { + match = item; + map_index = d; + break; + } + } + } + + if (match == NULL) { + unlock_hidd(); + return; + } + + if (report_type != ESP_HID_REPORT_TYPE_OUTPUT && + report_type != ESP_HID_REPORT_TYPE_FEATURE) { + ESP_LOGD(TAG, "Ignoring host write for unsupported report type=%u, id=%u, handle=%u", + report_type, report_id, attr_handle); + unlock_hidd(); + return; + } + + size_t event_data_size = sizeof(esp_hidd_event_data_t); + if (len > 0) { + event_data_size += len; + } + esp_hidd_event_data_t *p_cb_param = (esp_hidd_event_data_t *)calloc(1, event_data_size); + if (p_cb_param == NULL) { + ESP_LOGE(TAG, "%s malloc event data failed!", __func__); + unlock_hidd(); + return; + } + + if (len > 0) { + memcpy(((uint8_t *)p_cb_param) + sizeof(esp_hidd_event_data_t), data, len); + } + + if (report_type == ESP_HID_REPORT_TYPE_OUTPUT) { + p_cb_param->output.dev = s_dev->dev; + p_cb_param->output.usage = match->usage; + p_cb_param->output.report_id = report_id; + p_cb_param->output.length = len; + p_cb_param->output.data = (len > 0) ? (uint8_t *)data : NULL; /* fixed by esp_hidd_process_event_data_handler */ + p_cb_param->output.map_index = map_index; + esp_event_post_to(s_dev->event_loop_handle, ESP_HIDD_EVENTS, ESP_HIDD_OUTPUT_EVENT, + p_cb_param, event_data_size, portMAX_DELAY); + } else if (report_type == ESP_HID_REPORT_TYPE_FEATURE) { + p_cb_param->feature.dev = s_dev->dev; + p_cb_param->feature.usage = match->usage; + p_cb_param->feature.report_id = report_id; + p_cb_param->feature.length = len; + p_cb_param->feature.data = (len > 0) ? (uint8_t *)data : NULL; /* fixed by esp_hidd_process_event_data_handler */ + p_cb_param->feature.map_index = map_index; + esp_event_post_to(s_dev->event_loop_handle, ESP_HIDD_EVENTS, ESP_HIDD_FEATURE_EVENT, + p_cb_param, event_data_size, portMAX_DELAY); + } + free(p_cb_param); + unlock_hidd(); +} + +static void nimble_char_write_cb(uint16_t attr_handle, uint16_t char_uuid16, uint8_t value) +{ + lock_hidd(); + if (s_dev == NULL || s_dev->event_loop_handle == NULL) { + unlock_hidd(); + return; + } + + uint8_t map_index = 0; + bool found = false; + + for (uint8_t d = 0; d < s_dev->devices_len; d++) { + if (char_uuid16 == BLE_SVC_HID_CHR_UUID16_PROTOCOL_MODE && + s_dev->devices[d].hid_protocol_handle == attr_handle) { + found = true; + map_index = d; + break; + } + if (char_uuid16 == BLE_SVC_HID_CHR_UUID16_HID_CTRL_PT && + s_dev->devices[d].hid_control_handle == attr_handle) { + found = true; + map_index = d; + break; + } + } + + if (!found) { + unlock_hidd(); + return; + } + + esp_hidd_event_data_t cb_param = {0}; + if (char_uuid16 == BLE_SVC_HID_CHR_UUID16_PROTOCOL_MODE) { + s_dev->protocol = value; + cb_param.protocol_mode.dev = s_dev->dev; + cb_param.protocol_mode.protocol_mode = value; + cb_param.protocol_mode.map_index = map_index; + esp_event_post_to(s_dev->event_loop_handle, ESP_HIDD_EVENTS, ESP_HIDD_PROTOCOL_MODE_EVENT, + &cb_param, sizeof(esp_hidd_event_data_t), portMAX_DELAY); + } else if (char_uuid16 == BLE_SVC_HID_CHR_UUID16_HID_CTRL_PT) { + s_dev->control = value; + cb_param.control.dev = s_dev->dev; + cb_param.control.control = value; + cb_param.control.map_index = map_index; + esp_event_post_to(s_dev->event_loop_handle, ESP_HIDD_EVENTS, ESP_HIDD_CONTROL_EVENT, + &cb_param, sizeof(esp_hidd_event_data_t), portMAX_DELAY); + } + unlock_hidd(); +} + static int nimble_hidd_dev_input_set(void *devp, size_t index, size_t id, uint8_t *data, size_t length) { hidd_le_report_item_t *p_rpt; @@ -843,6 +971,8 @@ esp_err_t esp_ble_hidd_dev_init(esp_hidd_dev_t *dev_p, const esp_hid_device_conf ble_hs_cfg.reset_cb = nimble_host_reset; ble_hs_cfg.sync_cb = nimble_host_synced; ble_hs_cfg.gatts_register_cb = nimble_gatt_svr_register_cb; + ble_svc_hid_register_report_write_cb(nimble_report_write_cb); + ble_svc_hid_register_char_write_cb(nimble_char_write_cb); rc = nimble_hid_start_gatts(); if (rc != ESP_OK) { if (ble_hs_cfg.reset_cb == nimble_host_reset) { @@ -852,6 +982,8 @@ esp_err_t esp_ble_hidd_dev_init(esp_hidd_dev_t *dev_p, const esp_hid_device_conf ble_hs_cfg.sync_cb = s_prev_sync_cb; } ble_hs_cfg.gatts_register_cb = NULL; + ble_svc_hid_register_report_write_cb(NULL); + ble_svc_hid_register_char_write_cb(NULL); ble_hidd_dev_free(); return rc; } diff --git a/components/esp_hid/src/nimble_hidh.c b/components/esp_hid/src/nimble_hidh.c index 4c63de9dc3d..a062ebd0b2f 100644 --- a/components/esp_hid/src/nimble_hidh.c +++ b/components/esp_hid/src/nimble_hidh.c @@ -70,14 +70,14 @@ static inline void SEND_CB(void) static inline void LOCK_OPS(void) { if (s_ble_hidh_op_mutex) { - xSemaphoreTake(s_ble_hidh_op_mutex, portMAX_DELAY); + xSemaphoreTakeRecursive(s_ble_hidh_op_mutex, portMAX_DELAY); } } static inline void UNLOCK_OPS(void) { if (s_ble_hidh_op_mutex) { - xSemaphoreGive(s_ble_hidh_op_mutex); + xSemaphoreGiveRecursive(s_ble_hidh_op_mutex); } } @@ -789,6 +789,20 @@ static void attach_report_listeners(esp_hidh_dev_t *dev) report = dev->reports; if (dev->ble.battery_handle) { + uint8_t *rdata = NULL; + uint16_t rlen = 0; + + if (event_loop_handle && + read_char(dev->ble.conn_id, dev->ble.battery_handle, &rdata, &rlen) == 0 && + rlen >= 1 && rdata != NULL) { + esp_hidh_event_data_t p = {0}; + p.battery.dev = dev; + p.battery.level = rdata[0]; + esp_event_post_to(event_loop_handle, ESP_HIDH_EVENTS, ESP_HIDH_BATTERY_EVENT, + &p, sizeof(esp_hidh_event_data_t), portMAX_DELAY); + } + free(rdata); + register_for_notify(dev->ble.conn_id, dev->ble.battery_handle); if (dev->ble.battery_ccc_handle && dev->ble.conn_id >= 0 && dev->connected) { write_char_descr(dev, dev->ble.battery_ccc_handle, 2, (uint8_t *)&ccc_data); @@ -1174,7 +1188,7 @@ esp_err_t esp_ble_hidh_init(const esp_hidh_config_t *config) s_ble_hidh_cb_semaphore = xSemaphoreCreateBinary(); ESP_RETURN_ON_FALSE(s_ble_hidh_cb_semaphore, ESP_ERR_NO_MEM, TAG, "Allocation failed"); - s_ble_hidh_op_mutex = xSemaphoreCreateMutex(); + s_ble_hidh_op_mutex = xSemaphoreCreateRecursiveMutex(); if (s_ble_hidh_op_mutex == NULL) { vSemaphoreDelete(s_ble_hidh_cb_semaphore); s_ble_hidh_cb_semaphore = NULL; diff --git a/components/esp_http_client/esp_http_client.c b/components/esp_http_client/esp_http_client.c index 69c3d4687b0..e10da4bd1f4 100644 --- a/components/esp_http_client/esp_http_client.c +++ b/components/esp_http_client/esp_http_client.c @@ -1151,6 +1151,19 @@ esp_err_t esp_http_client_set_redirection(esp_http_client_handle_t client) return ESP_ERR_INVALID_ARG; } ESP_LOGD(TAG, "Redirect to %s", client->location); + + /* On an HTTPS origin, only allow https:// redirect targets. Any other + * scheme (http, ftp, ws, ...) is rejected before client state is + * modified to prevent transport-layer downgrade attacks. */ + if (client->connection_info.scheme != NULL && + strcasecmp(client->connection_info.scheme, "https") == 0 && + strncasecmp(client->location, "https://", 8) != 0) { + ESP_LOGE(TAG, "HTTPS origin can only redirect to https:// targets (got %s). " + "Set disable_auto_redirect and handle manually if intended.", + client->location); + return ESP_ERR_HTTP_REDIRECT_DOWNGRADE; + } + esp_err_t err = esp_http_client_set_url(client, client->location); if (err == ESP_OK) { client->redirect_counter ++; @@ -1187,9 +1200,10 @@ static esp_err_t esp_http_check_response(esp_http_client_handle_t client) if (client->disable_auto_redirect) { http_dispatch_event(client, HTTP_EVENT_REDIRECT, NULL, 0); } else { - if (esp_http_client_set_redirection(client) != ESP_OK){ - return ESP_FAIL; - }; + esp_err_t redir_err = esp_http_client_set_redirection(client); + if (redir_err != ESP_OK) { + return redir_err; + } } esp_http_client_redirect_event_data_t evt_data = { .status_code = client->response->status_code, diff --git a/components/esp_http_client/include/esp_http_client.h b/components/esp_http_client/include/esp_http_client.h index ffafee00e4a..b3e235ade33 100644 --- a/components/esp_http_client/include/esp_http_client.h +++ b/components/esp_http_client/include/esp_http_client.h @@ -297,6 +297,7 @@ typedef enum { #define ESP_ERR_HTTP_RANGE_NOT_SATISFIABLE (ESP_ERR_HTTP_BASE + 10) /*!< HTTP 416 Range Not Satisfiable, requested range in header is incorrect */ #define ESP_ERR_HTTP_READ_TIMEOUT (ESP_ERR_HTTP_BASE + 11) /*!< HTTP data read timeout */ #define ESP_ERR_HTTP_INCOMPLETE_DATA (ESP_ERR_HTTP_BASE + 12) /*!< Incomplete data received, less than Content-Length or last chunk */ +#define ESP_ERR_HTTP_REDIRECT_DOWNGRADE (ESP_ERR_HTTP_BASE + 13) /*!< HTTPS origin redirected to a non-HTTPS scheme (downgrade blocked) */ /** * @brief Start a HTTP session diff --git a/components/esp_http_client/test_apps/README.md b/components/esp_http_client/test_apps/README.md index e0c2bb1e27e..8399cdd3676 100644 --- a/components/esp_http_client/test_apps/README.md +++ b/components/esp_http_client/test_apps/README.md @@ -1,3 +1,5 @@ | Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C5 | ESP32-C6 | ESP32-C61 | ESP32-H2 | ESP32-H21 | ESP32-H4 | ESP32-P4 | ESP32-S2 | ESP32-S3 | | ----------------- | ----- | -------- | -------- | -------- | -------- | --------- | -------- | --------- | -------- | -------- | -------- | -------- | +See `examples/protocols/esp_http_client_mutual_auth` for mutual TLS (mTLS) examples including DS peripheral support. + diff --git a/components/esp_http_server/Kconfig b/components/esp_http_server/Kconfig index 3913eb004c8..c7142e782fe 100644 --- a/components/esp_http_server/Kconfig +++ b/components/esp_http_server/Kconfig @@ -53,9 +53,18 @@ menu "HTTP Server" config HTTPD_QUEUE_WORK_BLOCKING bool "httpd_queue_work as blocking API" help - This makes httpd_queue_work() API to wait until a message space is available on UDP control socket. - It internally uses a counting semaphore with count set to `LWIP_UDP_RECVMBOX_SIZE` to achieve this. - This config will slightly change API behavior to block until message gets delivered on control socket. + Selects the wait policy for httpd_queue_work() when the UDP control-socket + mbox is at capacity. The HTTP server always uses a counting semaphore + (sized to LWIP_UDP_RECVMBOX_SIZE) to prevent silent mbox overflow drops + that would leak the caller's async-send callback context. + + When disabled (default): httpd_queue_work() returns ESP_FAIL immediately + if the mbox is full, so the caller can free its callback context. This + preserves the non-blocking semantics of httpd_ws_send_data_async(). + + When enabled: httpd_queue_work() blocks until a slot is available. Use + this only if your application can tolerate the call blocking and wants + guaranteed delivery instead of a fast-fail. config HTTPD_ENABLE_EVENTS bool "Enable ESP_HTTP_SERVER_EVENT" diff --git a/components/esp_http_server/src/esp_httpd_priv.h b/components/esp_http_server/src/esp_httpd_priv.h index dc3dd85c636..a787431dd2c 100644 --- a/components/esp_http_server/src/esp_httpd_priv.h +++ b/components/esp_http_server/src/esp_httpd_priv.h @@ -17,6 +17,7 @@ #include #include "osal.h" +#include "freertos/semphr.h" #include "sdkconfig.h" #ifdef __cplusplus @@ -130,9 +131,7 @@ struct httpd_data { httpd_config_t config; /*!< HTTPD server configuration */ int listen_fd; /*!< Server listener FD */ int ctrl_fd; /*!< Ctrl message receiver FD */ -#if CONFIG_HTTPD_QUEUE_WORK_BLOCKING - SemaphoreHandle_t ctrl_sock_semaphore; /*!< Ctrl socket semaphore */ -#endif + SemaphoreHandle_t ctrl_sock_semaphore; /*!< Ctrl mbox slot reservation (sized to LWIP_UDP_RECVMBOX_SIZE) */ int msg_fd; /*!< Ctrl message sender FD */ struct thread_data hd_td; /*!< Information for the HTTPD thread */ struct sock_db *hd_sd; /*!< The socket database */ diff --git a/components/esp_http_server/src/httpd_main.c b/components/esp_http_server/src/httpd_main.c index 74331d92464..ddb0fd81e5b 100644 --- a/components/esp_http_server/src/httpd_main.c +++ b/components/esp_http_server/src/httpd_main.c @@ -50,7 +50,7 @@ void esp_http_server_dispatch_event(int32_t event_id, const void* event_data, si } #endif // CONFIG_HTTPD_ENABLE_EVENTS -static esp_err_t httpd_accept_conn(struct httpd_data *hd, int listen_fd) +static esp_err_t httpd_accept_conn(struct httpd_data *hd) { /* If no space is available for new session, close the least recently used one */ if (hd->config.lru_purge_enable == true) { @@ -73,11 +73,13 @@ static esp_err_t httpd_accept_conn(struct httpd_data *hd, int listen_fd) struct sockaddr_storage addr_from; socklen_t addr_from_len = sizeof(addr_from); - int new_fd = accept(listen_fd, (struct sockaddr *)&addr_from, &addr_from_len); + + int new_fd = accept(hd->listen_fd, (struct sockaddr *)&addr_from, &addr_from_len); if (new_fd < 0) { ESP_LOGE(TAG, LOG_FMT("error in accept (%d)"), errno); return ESP_FAIL; } + ESP_LOGD(TAG, LOG_FMT("newfd = %d"), new_fd); struct timeval tv; @@ -153,24 +155,27 @@ esp_err_t httpd_queue_work(httpd_handle_t handle, httpd_work_fn_t work, void *ar .hc_work = work, .hc_work_arg = arg, }; + + /* Reserve a slot in the control mbox before sending. In blocking mode + * the caller waits for a slot; in the default non-blocking mode we + * fail fast so the caller knows the work was not queued. */ #if CONFIG_HTTPD_QUEUE_WORK_BLOCKING - // Semaphore is acquired here and released after work function is executed. - if (xSemaphoreTake(hd->ctrl_sock_semaphore, portMAX_DELAY) == pdTRUE) { + const TickType_t wait = portMAX_DELAY; +#else + const TickType_t wait = 0; #endif - int ret = cs_send_to_ctrl_sock(hd->msg_fd, hd->config.ctrl_port, &msg, sizeof(msg)); - if (ret < 0) { - ESP_LOGW(TAG, LOG_FMT("failed to queue work")); -#if CONFIG_HTTPD_QUEUE_WORK_BLOCKING - xSemaphoreGive(hd->ctrl_sock_semaphore); -#endif - return ESP_FAIL; - } - return ESP_OK; -#if CONFIG_HTTPD_QUEUE_WORK_BLOCKING + if (xSemaphoreTake(hd->ctrl_sock_semaphore, wait) != pdTRUE) { + ESP_LOGW(TAG, LOG_FMT("ctrl socket queue full, work not queued")); + return ESP_FAIL; } - ESP_LOGE(TAG, "Unable to acquire semaphore"); - return ESP_FAIL; -#endif + + int ret = cs_send_to_ctrl_sock(hd->msg_fd, hd->config.ctrl_port, &msg, sizeof(msg)); + if (ret < 0) { + ESP_LOGW(TAG, LOG_FMT("failed to queue work")); + xSemaphoreGive(hd->ctrl_sock_semaphore); + return ESP_FAIL; + } + return ESP_OK; } esp_err_t httpd_get_client_list(httpd_handle_t handle, size_t *fds, int *client_fds) @@ -210,16 +215,16 @@ static void httpd_process_ctrl_msg(struct httpd_data *hd) int ret = recv(hd->ctrl_fd, &msg, sizeof(msg), 0); if (ret <= 0) { ESP_LOGW(TAG, LOG_FMT("error in recv (%d)"), errno); -#if CONFIG_HTTPD_QUEUE_WORK_BLOCKING + /* No packet was actually consumed from the mbox here, so this give + * is unbalanced. It's tolerated because the counting semaphore is + * capped at its max — excess gives become no-ops. Spurious recv + * errors after select() are rare in practice. */ xSemaphoreGive(hd->ctrl_sock_semaphore); -#endif return; } if (ret != sizeof(msg)) { ESP_LOGW(TAG, LOG_FMT("incomplete msg")); -#if CONFIG_HTTPD_QUEUE_WORK_BLOCKING xSemaphoreGive(hd->ctrl_sock_semaphore); -#endif return; } @@ -237,9 +242,7 @@ static void httpd_process_ctrl_msg(struct httpd_data *hd) default: break; } -#if CONFIG_HTTPD_QUEUE_WORK_BLOCKING xSemaphoreGive(hd->ctrl_sock_semaphore); -#endif } // Called for each session from httpd_server @@ -319,7 +322,7 @@ static esp_err_t httpd_server(struct httpd_data *hd) * process? */ if (FD_ISSET(hd->listen_fd, &read_set)) { ESP_LOGD(TAG, LOG_FMT("processing listen socket %d"), hd->listen_fd); - if (httpd_accept_conn(hd, hd->listen_fd) != ESP_OK) { + if (httpd_accept_conn(hd) != ESP_OK) { ESP_LOGW(TAG, LOG_FMT("error accepting new connection")); } } @@ -472,6 +475,10 @@ static void httpd_delete(struct httpd_data *hd) free(hd->err_handler_fns); free(ra->resp_hdrs); free(hd->hd_sd); + if (hd->ctrl_sock_semaphore) { + vSemaphoreDelete(hd->ctrl_sock_semaphore); + hd->ctrl_sock_semaphore = NULL; + } /* Free registered URI handlers */ httpd_unregister_all_uri_handlers(hd); @@ -507,18 +514,18 @@ esp_err_t httpd_start(httpd_handle_t *handle, const httpd_config_t *config) /* Failed to allocate memory */ return ESP_ERR_HTTPD_ALLOC_MEM; } -#if CONFIG_HTTPD_QUEUE_WORK_BLOCKING - /* Using a Counting Semaphore with count equals CONFIG_LWIP_UDP_RECVMBOX_SIZE - * as the number of UDP messages which can be stored is equal to UDP mailbox size. - * Using this, we can make sure that the work function is always received by the ctrl socket. - */ + /* Counting semaphore sized to the UDP control-socket recv mbox. Each + * httpd_queue_work() take reserves one mbox slot; httpd_process_ctrl_msg() + * gives one back per drain. This bounds the producer to the mbox capacity + * and prevents silent lwIP-mbox overflow drops that would otherwise leak + * the caller's async-send context. Always created so the default + * (non-blocking) httpd_queue_work() path can also rely on it. */ hd->ctrl_sock_semaphore = xSemaphoreCreateCounting(CONFIG_LWIP_UDP_RECVMBOX_SIZE, CONFIG_LWIP_UDP_RECVMBOX_SIZE); if (hd->ctrl_sock_semaphore == NULL) { ESP_LOGE(TAG, "Failed to create Semaphore"); httpd_delete(hd); return ESP_ERR_HTTPD_ALLOC_MEM; } -#endif if (httpd_server_init(hd) != ESP_OK) { httpd_delete(hd); @@ -532,6 +539,12 @@ esp_err_t httpd_start(httpd_handle_t *handle, const httpd_config_t *config) httpd_thread, hd, hd->config.core_id, hd->config.task_caps) != ESP_OK) { + /* Close the open socket */ + close(hd->listen_fd); + /* Close the control socket */ + cs_free_ctrl_sock(hd->ctrl_fd); + /* Close the message socket */ + close(hd->msg_fd); /* Failed to launch task */ httpd_delete(hd); return ESP_ERR_HTTPD_TASK; @@ -554,9 +567,19 @@ esp_err_t httpd_stop(httpd_handle_t handle) struct httpd_ctrl_data msg; memset(&msg, 0, sizeof(msg)); msg.hc_msg = HTTPD_CTRL_SHUTDOWN; - int ret = 0; - if ((ret = cs_send_to_ctrl_sock(hd->msg_fd, hd->config.ctrl_port, &msg, sizeof(msg))) < 0) { + + /* Reserve a slot in the ctrl mbox before sending so we never push past + * its capacity. Blocking is safe: the httpd task is the consumer and + * keeps draining the mbox until it observes HTTPD_CTRL_SHUTDOWN. */ + if (xSemaphoreTake(hd->ctrl_sock_semaphore, portMAX_DELAY) != pdTRUE) { + ESP_LOGE(TAG, "Failed to acquire ctrl socket semaphore"); + return ESP_FAIL; + } + + int ret = cs_send_to_ctrl_sock(hd->msg_fd, hd->config.ctrl_port, &msg, sizeof(msg)); + if (ret < 0) { ESP_LOGE(TAG, "Failed to send shutdown signal err=%d", ret); + xSemaphoreGive(hd->ctrl_sock_semaphore); return ESP_FAIL; } @@ -586,9 +609,6 @@ esp_err_t httpd_stop(httpd_handle_t handle) } ESP_LOGD(TAG, LOG_FMT("server stopped")); -#if CONFIG_HTTPD_QUEUE_WORK_BLOCKING - vSemaphoreDelete(hd->ctrl_sock_semaphore); -#endif httpd_delete(hd); esp_http_server_dispatch_event(HTTP_SERVER_EVENT_STOP, NULL, 0); return ESP_OK; diff --git a/components/esp_http_server/src/httpd_txrx.c b/components/esp_http_server/src/httpd_txrx.c index b4dd58d53a7..ae2b42e8be4 100644 --- a/components/esp_http_server/src/httpd_txrx.c +++ b/components/esp_http_server/src/httpd_txrx.c @@ -738,9 +738,19 @@ esp_err_t httpd_req_async_handler_complete(httpd_req_t *r) // will now re-add this FD to its select() descriptor list. This ensures that subsequent requests // on the same FD are processed correctly struct httpd_ctrl_data msg = {.hc_msg = HTTPD_CTRL_MAX}; + + /* Reserve an mbox slot so we don't overrun ctrl_sock_semaphore's + * accounting and starve concurrent httpd_queue_work() producers. The + * httpd main task is the consumer and will drain the mbox shortly. */ + if (xSemaphoreTake(hd->ctrl_sock_semaphore, portMAX_DELAY) != pdTRUE) { + ESP_LOGW(TAG, LOG_FMT("failed to acquire ctrl socket semaphore")); + return ESP_FAIL; + } + int ret = cs_send_to_ctrl_sock(msg_fd, port, &msg, sizeof(msg)); if (ret < 0) { ESP_LOGW(TAG, LOG_FMT("failed to send socket notification")); + xSemaphoreGive(hd->ctrl_sock_semaphore); return ESP_FAIL; } diff --git a/components/esp_http_server/test_apps/main/test_http_server.c b/components/esp_http_server/test_apps/main/test_http_server.c index 0b4da34bc95..067c341e754 100644 --- a/components/esp_http_server/test_apps/main/test_http_server.c +++ b/components/esp_http_server/test_apps/main/test_http_server.c @@ -9,6 +9,10 @@ #include #include #include +#include +#include "esp_log.h" +#include "freertos/FreeRTOS.h" +#include "freertos/semphr.h" #ifdef CONFIG_HTTPD_WS_SUPPORT #include "../../src/esp_httpd_priv.h" @@ -348,6 +352,68 @@ TEST_CASE("httpd_resp_set_type rejects CRLF in content type", "[HTTP SERVER][sec httpd_resp_set_type(&fake_req, "text/html\nX-Injected: pwned")); } +/* ---- httpd_queue_work backpressure ---- */ + +static SemaphoreHandle_t s_qw_gate; +static volatile int s_qw_work_runs; + +static void qw_blocking_work(void *arg) +{ + /* Hold the httpd thread inside this work fn so the ctrl-socket mbox + * stops draining. Auto-release after 2 s as a safety net in case the + * test asserts mid-way and never reaches the explicit give. */ + xSemaphoreTake((SemaphoreHandle_t)arg, pdMS_TO_TICKS(2000)); +} + +static void qw_counting_work(void *arg) +{ + (void)arg; + s_qw_work_runs++; +} + +TEST_CASE("httpd_queue_work fast-fails on ctrl mbox saturation", "[HTTP SERVER]") +{ + test_case_uses_tcpip(); + + httpd_handle_t hd = NULL; + httpd_config_t config = HTTPD_DEFAULT_CONFIG(); + TEST_ASSERT_EQUAL(ESP_OK, httpd_start(&hd, &config)); + + s_qw_gate = xSemaphoreCreateBinary(); + TEST_ASSERT_NOT_NULL(s_qw_gate); + s_qw_work_runs = 0; + + /* Park the httpd thread in a blocked work item so the mbox can fill. */ + TEST_ASSERT_EQUAL(ESP_OK, httpd_queue_work(hd, qw_blocking_work, s_qw_gate)); + vTaskDelay(pdMS_TO_TICKS(100)); + + /* Spam queue_work past the mbox cap; first ones succeed, rest must + * return ESP_FAIL synchronously (default non-blocking behavior). */ + int ok_count = 0; + int fail_count = 0; + for (int i = 0; i < CONFIG_LWIP_UDP_RECVMBOX_SIZE * 2 + 4; i++) { + esp_err_t err = httpd_queue_work(hd, qw_counting_work, NULL); + if (err == ESP_OK) { + ok_count++; + } else { + TEST_ASSERT_EQUAL(ESP_FAIL, err); + fail_count++; + } + } + TEST_ASSERT_GREATER_THAN(0, ok_count); + TEST_ASSERT_LESS_OR_EQUAL(CONFIG_LWIP_UDP_RECVMBOX_SIZE, ok_count); + TEST_ASSERT_GREATER_THAN(0, fail_count); + + /* Release the parked work; every accepted item must now actually run. */ + xSemaphoreGive(s_qw_gate); + vTaskDelay(pdMS_TO_TICKS(300)); + TEST_ASSERT_EQUAL(ok_count, s_qw_work_runs); + + vSemaphoreDelete(s_qw_gate); + s_qw_gate = NULL; + TEST_ASSERT_EQUAL(ESP_OK, httpd_stop(hd)); +} + #ifdef CONFIG_HTTPD_WS_SUPPORT TEST_CASE("WS recv failure marks close without dispatching handler", "[HTTP SERVER][websocket]") { diff --git a/components/esp_https_ota/src/esp_https_ota.c b/components/esp_https_ota/src/esp_https_ota.c index 14c51afa02b..023b0d33909 100644 --- a/components/esp_https_ota/src/esp_https_ota.c +++ b/components/esp_https_ota/src/esp_https_ota.c @@ -411,24 +411,33 @@ esp_err_t esp_https_ota_begin(const esp_https_ota_config_t *ota_config, esp_http err = esp_http_client_perform(https_ota_handle->http_client); if (err == ESP_OK) { int status = esp_http_client_get_status_code(https_ota_handle->http_client); - if (status != HttpStatus_Ok) { - // If server doesn't support HEAD request, we need to get image length from GET request - // using Range header + if (status == HttpStatus_Ok) { + https_ota_handle->image_length = esp_http_client_get_content_length(https_ota_handle->http_client); + } else if (status == HttpStatus_NotModified) { + // No new image to download; report it from the HEAD response instead of issuing a redundant GET + err = ESP_ERR_HTTP_NOT_MODIFIED; + goto http_cleanup; + } else { + // HEAD not usable (e.g. 405/501, or 403 for GET-signed URLs); fall back to a ranged GET + ESP_LOGD(TAG, "HEAD request returned status %d, falling back to ranged GET", status); esp_http_client_set_header(https_ota_handle->http_client, "Range", "bytes=0-0"); esp_http_client_set_method(https_ota_handle->http_client, HTTP_METHOD_GET); err = esp_http_client_perform(https_ota_handle->http_client); - if (err == ESP_OK) { - status = esp_http_client_get_status_code(https_ota_handle->http_client); - if (status != HttpStatus_Ok && status != HttpStatus_PartialContent) { - ESP_LOGE(TAG, "Received incorrect http status %d", status); - err = ESP_FAIL; - goto http_cleanup; - } - } else { + if (err != ESP_OK) { ESP_LOGE(TAG, "ESP HTTP client perform failed: %d", err); goto http_cleanup; } + status = esp_http_client_get_status_code(https_ota_handle->http_client); + err = _http_handle_response_code(https_ota_handle, status); + if (err != ESP_OK) { + goto http_cleanup; + } + if (status != HttpStatus_Ok && status != HttpStatus_PartialContent) { + ESP_LOGE(TAG, "Received incorrect http status %d", status); + err = ESP_FAIL; + goto http_cleanup; + } esp_http_client_set_header(https_ota_handle->http_client, "Range", NULL); if (status == HttpStatus_Ok) { @@ -438,8 +447,6 @@ esp_err_t esp_https_ota_begin(const esp_https_ota_config_t *ota_config, esp_http // If server responds with 206 Partial Content, we can get image length from content-range header https_ota_handle->image_length = esp_http_client_get_content_range(https_ota_handle->http_client); } - } else { - https_ota_handle->image_length = esp_http_client_get_content_length(https_ota_handle->http_client); } } else { ESP_LOGE(TAG, "ESP HTTP client perform failed: %d", err); diff --git a/components/esp_hw_support/CMakeLists.txt b/components/esp_hw_support/CMakeLists.txt index b57f6891d31..1f2eac4556d 100644 --- a/components/esp_hw_support/CMakeLists.txt +++ b/components/esp_hw_support/CMakeLists.txt @@ -71,7 +71,8 @@ if(NOT non_os_build) ) if(CONFIG_SOC_PAU_SUPPORTED AND CONFIG_SOC_PM_SUPPORT_TOP_PD) list(APPEND srcs "sleep_system_peripheral.c") - list(APPEND srcs "port/${target}/peripheral_domain_pd.c") + list(APPEND srcs "port/${target}/peripheral_domain_pd.c" + "port/${target}/system_periph_retention.c") endif() endif() diff --git a/components/esp_hw_support/esp_memory_utils.c b/components/esp_hw_support/esp_memory_utils.c index fd79ef09ce9..b543b23265e 100644 --- a/components/esp_hw_support/esp_memory_utils.c +++ b/components/esp_hw_support/esp_memory_utils.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2010-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2010-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -12,7 +12,7 @@ #include "soc/soc_caps.h" #include "esp_attr.h" #include "esp_memory_utils.h" -#if CONFIG_SPIRAM +#if !BOOTLOADER_BUILD && CONFIG_SPIRAM #include "esp_private/esp_psram_extram.h" #endif @@ -22,7 +22,7 @@ bool esp_ptr_dma_ext_capable(const void *p) #if !SOC_PSRAM_DMA_CAPABLE return false; #endif //!SOC_PSRAM_DMA_CAPABLE -#if CONFIG_SPIRAM +#if !BOOTLOADER_BUILD && CONFIG_SPIRAM return esp_psram_check_ptr_addr(p); #else return false; @@ -35,7 +35,7 @@ bool esp_ptr_executable(const void *p) return (ip >= SOC_IROM_LOW && ip < SOC_IROM_HIGH) || (ip >= SOC_IRAM_LOW && ip < SOC_IRAM_HIGH) || (ip >= SOC_IROM_MASK_LOW && ip < SOC_IROM_MASK_HIGH) -#if SOC_SPIRAM_SUPPORTED && CONFIG_SPIRAM +#if !BOOTLOADER_BUILD && SOC_SPIRAM_SUPPORTED && CONFIG_SPIRAM || esp_ptr_external_ram(p) #endif #if defined(SOC_CACHE_APP_LOW) && defined(CONFIG_ESP_SYSTEM_SINGLE_CORE_MODE) @@ -61,7 +61,7 @@ bool esp_ptr_byte_accessible(const void *p) * additional check is required */ r |= (ip >= SOC_RTC_DRAM_LOW && ip < SOC_RTC_DRAM_HIGH); #endif -#if CONFIG_SPIRAM +#if !BOOTLOADER_BUILD && CONFIG_SPIRAM r |= esp_psram_check_ptr_addr(p); #endif #if CONFIG_ESP32S3_DATA_CACHE_16KB @@ -81,14 +81,14 @@ bool esp_ptr_external_ram(const void *p) #if !SOC_SPIRAM_SUPPORTED return false; #endif //!SOC_SPIRAM_SUPPORTED -#if CONFIG_SPIRAM +#if !BOOTLOADER_BUILD && CONFIG_SPIRAM return esp_psram_check_ptr_addr(p); #else return false; #endif //CONFIG_SPIRAM } -#if CONFIG_FREERTOS_TASK_CREATE_ALLOW_EXT_MEM +#if !BOOTLOADER_BUILD && CONFIG_FREERTOS_TASK_CREATE_ALLOW_EXT_MEM bool esp_stack_ptr_in_extram(uint32_t sp) { //Check if stack ptr is on PSRAM, and 16 byte aligned. diff --git a/components/esp_hw_support/include/esp_private/usb_phy.h b/components/esp_hw_support/include/esp_private/usb_phy.h index 7553b879bad..05dcddd7a84 100644 --- a/components/esp_hw_support/include/esp_private/usb_phy.h +++ b/components/esp_hw_support/include/esp_private/usb_phy.h @@ -1,11 +1,12 @@ /* - * SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #pragma once +#include #include #include "esp_err.h" #include "soc/soc_caps.h" @@ -164,6 +165,18 @@ esp_err_t usb_new_phy(const usb_phy_config_t *config, usb_phy_handle_t *handle_r */ esp_err_t usb_phy_otg_set_mode(usb_phy_handle_t handle, usb_otg_mode_t mode); +/** + * @brief Set the USB OTG suspend state for USB wakeup logic + * + * @param in_suspend True if the USB OTG bus is suspended + */ +void usb_phy_set_otg_suspend_state(bool in_suspend); + +/** + * @brief Clear USB OTG wakeup status from the USB wakeup logic + */ +void usb_phy_clear_otg_wakeup_status(void); + /** * @brief Delete a USB PHY * diff --git a/components/esp_hw_support/include/esp_sleep.h b/components/esp_hw_support/include/esp_sleep.h index 8fa0bc68b2d..75308b3e806 100644 --- a/components/esp_hw_support/include/esp_sleep.h +++ b/components/esp_hw_support/include/esp_sleep.h @@ -121,6 +121,7 @@ typedef enum { ESP_SLEEP_WAKEUP_BT, //!< Wakeup caused by BT (light sleep only) ESP_SLEEP_WAKEUP_VAD, //!< Wakeup caused by VAD ESP_SLEEP_WAKEUP_VBAT_UNDER_VOLT, //!< Wakeup caused by VDD_BAT under voltage. + ESP_SLEEP_WAKEUP_USB, //!< Wakeup caused by USB HS (light sleep only) } esp_sleep_source_t; /** @@ -133,7 +134,6 @@ typedef enum { /* Leave this type define for compatibility */ typedef esp_sleep_source_t esp_sleep_wakeup_cause_t; - enum { ESP_ERR_SLEEP_REJECT = ESP_ERR_INVALID_STATE, ESP_ERR_SLEEP_TOO_SHORT_SLEEP_DURATION = ESP_ERR_INVALID_ARG, @@ -489,8 +489,8 @@ esp_err_t esp_sleep_enable_gpio_wakeup_on_hp_periph_powerdown(uint64_t gpio_pin_ * @brief Enable wakeup from light sleep using GPIOs * * Each GPIO supports wakeup function, which can be triggered on either low level - * or high level. Unlike EXT0 and EXT1 wakeup sources, this method can be used - * both for all IOs: RTC IOs and digital IOs. It can only be used to wakeup from + * or high level. This method can be used with any IO (RTC or digital), whereas + * external RTC wakeup is limited to RTC GPIOs. It can only be used to wakeup from * light sleep though. * * To enable wakeup, first call gpio_wakeup_enable, specifying gpio number and @@ -545,6 +545,22 @@ esp_err_t esp_sleep_enable_bt_wakeup(void); */ esp_err_t esp_sleep_disable_bt_wakeup(void); +/** + * @brief Enable wakeup by High-Speed USB-OTG + * @return + * - ESP_OK on success + * - ESP_ERR_NOT_SUPPORTED if wakeup from USB is not supported + */ +esp_err_t esp_sleep_enable_usb_wakeup(void); + +/** + * @brief Disable wakeup by High-Speed USB-OTG + * @return + * - ESP_OK on success + * - ESP_ERR_NOT_SUPPORTED if wakeup from USB is not supported + */ +esp_err_t esp_sleep_disable_usb_wakeup(void); + /** * @brief Enable wakeup by WiFi MAC * @return diff --git a/components/esp_hw_support/port/esp32c5/cpu_region_protect.c b/components/esp_hw_support/port/esp32c5/cpu_region_protect.c index 8b8e5e6002c..d8f5bd38473 100644 --- a/components/esp_hw_support/port/esp32c5/cpu_region_protect.c +++ b/components/esp_hw_support/port/esp32c5/cpu_region_protect.c @@ -9,9 +9,9 @@ #include "esp_cpu.h" #include "esp_fault.h" #include "esp32c5/rom/rom_layout.h" -#if CONFIG_SPIRAM +#if !BOOTLOADER_BUILD && CONFIG_SPIRAM #include "esp_private/esp_psram_extram.h" -#endif /* CONFIG_SPIRAM */ +#endif /* !BOOTLOADER_BUILD && CONFIG_SPIRAM */ #ifdef BOOTLOADER_BUILD // Without L bit set diff --git a/components/soc/esp32c5/include/soc/system_periph_retention.h b/components/esp_hw_support/port/esp32c5/include/soc/system_periph_retention.h similarity index 100% rename from components/soc/esp32c5/include/soc/system_periph_retention.h rename to components/esp_hw_support/port/esp32c5/include/soc/system_periph_retention.h diff --git a/components/esp_hw_support/port/esp32c5/pmu_pvt.c b/components/esp_hw_support/port/esp32c5/pmu_pvt.c index 2609ec571b1..055b115aa98 100644 --- a/components/esp_hw_support/port/esp32c5/pmu_pvt.c +++ b/components/esp_hw_support/port/esp32c5/pmu_pvt.c @@ -88,6 +88,8 @@ void pvt_auto_dbias_init(void) if (pvt_enable_flag == true) { return; } + REG_SET_BIT(PCR_PVT_MONITOR_CONF_REG, PCR_PVT_MONITOR_RST_EN); // Must reset after pd_cpu + REG_CLR_BIT(PCR_PVT_MONITOR_CONF_REG, PCR_PVT_MONITOR_RST_EN); SET_PERI_REG_MASK(PCR_PVT_MONITOR_CONF_REG, PCR_PVT_MONITOR_CLK_EN); SET_PERI_REG_MASK(PCR_PVT_MONITOR_FUNC_CLK_CONF_REG, PCR_PVT_MONITOR_FUNC_CLK_EN); /*config for dbias func*/ diff --git a/components/soc/esp32c5/system_retention_periph.c b/components/esp_hw_support/port/esp32c5/system_periph_retention.c similarity index 100% rename from components/soc/esp32c5/system_retention_periph.c rename to components/esp_hw_support/port/esp32c5/system_periph_retention.c diff --git a/components/soc/esp32c6/include/soc/system_periph_retention.h b/components/esp_hw_support/port/esp32c6/include/soc/system_periph_retention.h similarity index 100% rename from components/soc/esp32c6/include/soc/system_periph_retention.h rename to components/esp_hw_support/port/esp32c6/include/soc/system_periph_retention.h diff --git a/components/esp_hw_support/port/esp32c6/pmu_pvt.c b/components/esp_hw_support/port/esp32c6/pmu_pvt.c index 96e8f526997..1d8d2f37b94 100644 --- a/components/esp_hw_support/port/esp32c6/pmu_pvt.c +++ b/components/esp_hw_support/port/esp32c6/pmu_pvt.c @@ -87,6 +87,8 @@ void pvt_auto_dbias_init(void) if (pvt_enable_flag == true) { return; } + REG_SET_BIT(PCR_PVT_MONITOR_CONF_REG, PCR_PVT_MONITOR_RST_EN); // Must reset after pd_cpu + REG_CLR_BIT(PCR_PVT_MONITOR_CONF_REG, PCR_PVT_MONITOR_RST_EN); SET_PERI_REG_MASK(PCR_PVT_MONITOR_CONF_REG, PCR_PVT_MONITOR_CLK_EN); SET_PERI_REG_MASK(PCR_PVT_MONITOR_FUNC_CLK_CONF_REG, PCR_PVT_MONITOR_FUNC_CLK_EN); /*config for dbias func*/ @@ -109,7 +111,7 @@ void pvt_auto_dbias_init(void) SET_PERI_REG_BITS(PVT_COMB_PD_SITE2_UNIT0_VT2_CONF2_REG, PVT_MONITOR_EDG_MOD_VT2_PD_SITE2_UNIT0, PVT_EDG_MODE, PVT_MONITOR_EDG_MOD_VT2_PD_SITE2_UNIT0_S); // Select edge_mode SET_PERI_REG_BITS(PVT_COMB_PD_SITE2_UNIT0_VT2_CONF1_REG, PVT_DELAY_LIMIT_VT2_PD_SITE2_UNIT0, PVT_DELAY_NUM_HIGH, PVT_DELAY_LIMIT_VT2_PD_SITE2_UNIT0_S); // The threshold for determining whether the voltage is too high SET_PERI_REG_BITS(PVT_COMB_PD_SITE2_UNIT1_VT2_CONF1_REG, PVT_DELAY_LIMIT_VT2_PD_SITE2_UNIT1, PVT_DELAY_NUM_LOW, PVT_DELAY_LIMIT_VT2_PD_SITE2_UNIT1_S); // The threshold for determining whether the voltage is too low - SET_PERI_REG_BITS(PVT_COMB_PD_SITE2_UNIT2_VT1_CONF1_REG, PVT_DELAY_LIMIT_VT1_PD_SITE2_UNIT2, PVT_DELAY_NUM_PUMP, PVT_DELAY_LIMIT_VT1_PD_SITE2_UNIT2_S); // The threshold for chargepump + SET_PERI_REG_BITS(PVT_COMB_PD_SITE2_UNIT2_VT2_CONF1_REG, PVT_DELAY_LIMIT_VT2_PD_SITE2_UNIT2, PVT_DELAY_NUM_PUMP, PVT_DELAY_LIMIT_VT2_PD_SITE2_UNIT2_S); // The threshold for chargepump /*config lp offset for pvt func*/ uint8_t lp_hp_gap = get_lp_hp_gap(); diff --git a/components/soc/esp32c6/system_retention_periph.c b/components/esp_hw_support/port/esp32c6/system_periph_retention.c similarity index 100% rename from components/soc/esp32c6/system_retention_periph.c rename to components/esp_hw_support/port/esp32c6/system_periph_retention.c diff --git a/components/esp_hw_support/port/esp32c61/cpu_region_protect.c b/components/esp_hw_support/port/esp32c61/cpu_region_protect.c index cc62bade13b..07ad769289e 100644 --- a/components/esp_hw_support/port/esp32c61/cpu_region_protect.c +++ b/components/esp_hw_support/port/esp32c61/cpu_region_protect.c @@ -10,9 +10,9 @@ #include "esp_cpu.h" #include "esp_fault.h" #include "esp32c61/rom/rom_layout.h" -#if CONFIG_SPIRAM +#if !BOOTLOADER_BUILD && CONFIG_SPIRAM #include "esp_private/esp_psram_extram.h" -#endif /* CONFIG_SPIRAM */ +#endif /* !BOOTLOADER_BUILD && CONFIG_SPIRAM */ #ifdef BOOTLOADER_BUILD // Without L bit set diff --git a/components/soc/esp32c61/include/soc/system_periph_retention.h b/components/esp_hw_support/port/esp32c61/include/soc/system_periph_retention.h similarity index 100% rename from components/soc/esp32c61/include/soc/system_periph_retention.h rename to components/esp_hw_support/port/esp32c61/include/soc/system_periph_retention.h diff --git a/components/esp_hw_support/port/esp32c61/pmu_pvt.c b/components/esp_hw_support/port/esp32c61/pmu_pvt.c index 7f429fe8ea7..441fd8b7d15 100644 --- a/components/esp_hw_support/port/esp32c61/pmu_pvt.c +++ b/components/esp_hw_support/port/esp32c61/pmu_pvt.c @@ -87,6 +87,8 @@ void pvt_auto_dbias_init(void) if (pvt_enable_flag == true) { return; } + REG_SET_BIT(PCR_PVT_MONITOR_CONF_REG, PCR_PVT_MONITOR_RST_EN); // Must reset after pd_cpu + REG_CLR_BIT(PCR_PVT_MONITOR_CONF_REG, PCR_PVT_MONITOR_RST_EN); SET_PERI_REG_MASK(PCR_PVT_MONITOR_CONF_REG, PCR_PVT_MONITOR_CLK_EN); SET_PERI_REG_MASK(PCR_PVT_MONITOR_FUNC_CLK_CONF_REG, PCR_PVT_MONITOR_FUNC_CLK_EN); /*config for dbias func*/ diff --git a/components/soc/esp32c61/system_retention_periph.c b/components/esp_hw_support/port/esp32c61/system_periph_retention.c similarity index 100% rename from components/soc/esp32c61/system_retention_periph.c rename to components/esp_hw_support/port/esp32c61/system_periph_retention.c diff --git a/components/soc/esp32h2/include/soc/system_periph_retention.h b/components/esp_hw_support/port/esp32h2/include/soc/system_periph_retention.h similarity index 100% rename from components/soc/esp32h2/include/soc/system_periph_retention.h rename to components/esp_hw_support/port/esp32h2/include/soc/system_periph_retention.h diff --git a/components/soc/esp32h2/system_retention_periph.c b/components/esp_hw_support/port/esp32h2/system_periph_retention.c similarity index 100% rename from components/soc/esp32h2/system_retention_periph.c rename to components/esp_hw_support/port/esp32h2/system_periph_retention.c diff --git a/components/soc/esp32h21/include/soc/system_periph_retention.h b/components/esp_hw_support/port/esp32h21/include/soc/system_periph_retention.h similarity index 100% rename from components/soc/esp32h21/include/soc/system_periph_retention.h rename to components/esp_hw_support/port/esp32h21/include/soc/system_periph_retention.h diff --git a/components/soc/esp32h21/system_retention_periph.c b/components/esp_hw_support/port/esp32h21/system_periph_retention.c similarity index 100% rename from components/soc/esp32h21/system_retention_periph.c rename to components/esp_hw_support/port/esp32h21/system_periph_retention.c diff --git a/components/soc/esp32h4/include/soc/system_periph_retention.h b/components/esp_hw_support/port/esp32h4/include/soc/system_periph_retention.h similarity index 100% rename from components/soc/esp32h4/include/soc/system_periph_retention.h rename to components/esp_hw_support/port/esp32h4/include/soc/system_periph_retention.h diff --git a/components/soc/esp32h4/system_retention_periph.c b/components/esp_hw_support/port/esp32h4/system_periph_retention.c similarity index 100% rename from components/soc/esp32h4/system_retention_periph.c rename to components/esp_hw_support/port/esp32h4/system_periph_retention.c diff --git a/components/esp_hw_support/port/esp32p4/cpu_region_protect.c b/components/esp_hw_support/port/esp32p4/cpu_region_protect.c index 54ee7637f5c..ab682877734 100644 --- a/components/esp_hw_support/port/esp32p4/cpu_region_protect.c +++ b/components/esp_hw_support/port/esp32p4/cpu_region_protect.c @@ -11,9 +11,9 @@ #include "esp_fault.h" #include "hal/cache_ll.h" #include "riscv/csr.h" -#if CONFIG_SPIRAM +#if !BOOTLOADER_BUILD && CONFIG_SPIRAM #include "esp_private/esp_psram_extram.h" -#endif /* CONFIG_SPIRAM */ +#endif /* !BOOTLOADER_BUILD && CONFIG_SPIRAM */ #include "soc/chip_revision.h" #include "hal/config.h" @@ -198,14 +198,14 @@ static void esp_cpu_configure_region_protection_rev_v3(void) PMP_ENTRY_SET_CACHED_AND_UNCACHED(22, 26, page_aligned_drom_resv_end, PMP_TOR | R); #else -#if CONFIG_SPIRAM +#if !BOOTLOADER_BUILD && CONFIG_SPIRAM const uint32_t pmpaddr10 = PMPADDR_NAPOT(SOC_EXTRAM_LOW, SOC_EXTRAM_HIGH); PMP_RESET_AND_ENTRY_SET(10, pmpaddr10, PMP_NAPOT | CONDITIONAL_RWX); const uint32_t pmpaddr11 = PMPADDR_NAPOT(CACHE_LL_L2MEM_NON_CACHE_ADDR(SOC_EXTRAM_LOW), CACHE_LL_L2MEM_NON_CACHE_ADDR(SOC_EXTRAM_HIGH)); PMP_RESET_AND_ENTRY_SET(11, pmpaddr11, PMP_NAPOT | CONDITIONAL_RWX); _Static_assert(SOC_EXTRAM_LOW < SOC_EXTRAM_HIGH, "Invalid I/D_EXTRAM region"); -#endif /* CONFIG_SPIRAM */ +#endif /* !BOOTLOADER_BUILD && CONFIG_SPIRAM */ const uint32_t pmpaddr12 = PMPADDR_NAPOT(SOC_IROM_LOW, SOC_IROM_HIGH); PMP_RESET_AND_ENTRY_SET(12, pmpaddr12, PMP_NAPOT | CONDITIONAL_RX); diff --git a/components/soc/esp32p4/include/soc/system_periph_retention.h b/components/esp_hw_support/port/esp32p4/include/soc/system_periph_retention.h similarity index 100% rename from components/soc/esp32p4/include/soc/system_periph_retention.h rename to components/esp_hw_support/port/esp32p4/include/soc/system_periph_retention.h diff --git a/components/soc/esp32p4/system_retention_periph.c b/components/esp_hw_support/port/esp32p4/system_periph_retention.c similarity index 99% rename from components/soc/esp32p4/system_retention_periph.c rename to components/esp_hw_support/port/esp32p4/system_periph_retention.c index 8b3aa288c4a..a7ce4f557f9 100644 --- a/components/soc/esp32p4/system_retention_periph.c +++ b/components/esp_hw_support/port/esp32p4/system_periph_retention.c @@ -4,6 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ +#include "sdkconfig.h" #include "soc/cache_reg.h" #include "soc/gpio_reg.h" #include "soc/hp_system_reg.h" diff --git a/components/esp_hw_support/sleep_modes.c b/components/esp_hw_support/sleep_modes.c index 41a45073cb9..54d135b0f74 100644 --- a/components/esp_hw_support/sleep_modes.c +++ b/components/esp_hw_support/sleep_modes.c @@ -74,6 +74,9 @@ #include "hal/touch_sens_hal.h" #endif #include "hal/mspi_ll.h" +#if SOC_LP_CORE_HW_AUTO_CLRWAKEUPCAUSE +#include "hal/lp_aon_hal.h" +#endif #include "sdkconfig.h" #include "esp_rom_serial_output.h" @@ -333,10 +336,6 @@ static sleep_config_t s_config = { expected when determining wakeup cause. */ static bool s_light_sleep_wakeup = false; -/* Updating RTC_MEMORY_CRC_REG register via set_rtc_memory_crc() - is not thread-safe, so we need to disable interrupts before going to deep sleep. */ -static portMUX_TYPE __attribute__((unused)) spinlock_rtc_deep_sleep = portMUX_INITIALIZER_UNLOCKED; - ESP_LOG_ATTR_TAG(TAG, "sleep"); /* APP core of esp32 can't access to RTC FAST MEMORY, do not define it with RTC_IRAM_ATTR, @@ -503,28 +502,28 @@ esp_err_t esp_deep_sleep_try(uint64_t time_in_us) static esp_err_t s_sleep_hook_register(esp_deep_sleep_cb_t new_cb, esp_deep_sleep_cb_t s_cb_array[MAX_DSLP_HOOKS]) { - esp_os_enter_critical(&spinlock_rtc_deep_sleep); + esp_os_enter_critical(&s_config.lock); for (int n = 0; n < MAX_DSLP_HOOKS; n++) { if (s_cb_array[n]==NULL || s_cb_array[n]==new_cb) { s_cb_array[n]=new_cb; - esp_os_exit_critical(&spinlock_rtc_deep_sleep); + esp_os_exit_critical(&s_config.lock); return ESP_OK; } } - esp_os_exit_critical(&spinlock_rtc_deep_sleep); + esp_os_exit_critical(&s_config.lock); ESP_LOGE(TAG, "Registered deepsleep callbacks exceeds MAX_DSLP_HOOKS"); return ESP_ERR_NO_MEM; } static void s_sleep_hook_deregister(esp_deep_sleep_cb_t old_cb, esp_deep_sleep_cb_t s_cb_array[MAX_DSLP_HOOKS]) { - esp_os_enter_critical(&spinlock_rtc_deep_sleep); + esp_os_enter_critical(&s_config.lock); for (int n = 0; n < MAX_DSLP_HOOKS; n++) { if(s_cb_array[n] == old_cb) { s_cb_array[n] = NULL; } } - esp_os_exit_critical(&spinlock_rtc_deep_sleep); + esp_os_exit_critical(&s_config.lock); } esp_err_t esp_deep_sleep_register_hook(esp_deep_sleep_cb_t new_dslp_cb) @@ -1005,6 +1004,7 @@ static esp_err_t SLEEP_FN_ATTR esp_sleep_start(uint32_t sleep_flags, uint32_t cl rtc_hal_ulp_wakeup_enable(); #elif CONFIG_ULP_COPROC_TYPE_LP_CORE pmu_ll_hp_clear_sw_intr_status(&PMU); + pmu_ll_hp_clear_lp_cpu_exc_intr_status(&PMU); #else rtc_hal_ulp_int_clear(); #endif @@ -1167,10 +1167,18 @@ static esp_err_t FORCE_IRAM_ATTR deep_sleep_start(bool allow_sleep_rejection) esp_sync_timekeeping_timers(); + // Must acquire all spinlocks which may be acquired during sleep process before stalling other core, + // otherwise deadlock may occur. + esp_os_enter_critical(&s_config.lock); +#if !CONFIG_FREERTOS_UNICORE + extern portMUX_TYPE rtc_spinlock; + esp_os_enter_critical_safe(&rtc_spinlock); // Maybe acquired from temp_sensor_get_raw_value by phy_close_rf callback + esp_clk_private_lock(); // Maybe acquired from esp_clk_slowclk_cal_set +#endif + /* Disable interrupts and stall another core in case another task writes * to RTC memory while we calculate RTC memory CRC. */ - esp_os_enter_critical(&spinlock_rtc_deep_sleep); esp_ipc_isr_stall_other_cpu(); esp_ipc_isr_stall_pause(); #if CONFIG_ESP_INT_WDT && CONFIG_ESP32_ECO3_CACHE_LOCK_FIX @@ -1250,7 +1258,11 @@ static esp_err_t FORCE_IRAM_ATTR deep_sleep_start(bool allow_sleep_rejection) #endif esp_ipc_isr_stall_resume(); esp_ipc_isr_release_other_cpu(); - esp_os_exit_critical(&spinlock_rtc_deep_sleep); +#if !CONFIG_FREERTOS_UNICORE + esp_clk_private_unlock(); + esp_os_exit_critical_safe(&rtc_spinlock); +#endif + esp_os_exit_critical(&s_config.lock); return err; } @@ -1674,6 +1686,11 @@ esp_err_t esp_sleep_disable_wakeup_source(esp_sleep_source_t source) else if (CHECK_SOURCE(source, ESP_SLEEP_WAKEUP_VBAT_UNDER_VOLT, RTC_VBAT_UNDER_VOLT_TRIG_EN)) { s_config.wakeup_triggers &= ~RTC_VBAT_UNDER_VOLT_TRIG_EN; } +#endif +#if SOC_PM_SUPPORT_USB_WAKEUP + else if (CHECK_SOURCE(source, ESP_SLEEP_WAKEUP_USB, RTC_USB_TRIG_EN)) { + s_config.wakeup_triggers &= ~RTC_USB_TRIG_EN; + } #endif else { ESP_LOGE(TAG, "Incorrect wakeup source (%d) to disable.", (int) source); @@ -2142,6 +2159,9 @@ esp_err_t esp_sleep_enable_gpio_wakeup_on_hp_periph_powerdown(uint64_t gpio_pin_ ESP_LOGE(TAG, "invalid mode"); return ESP_ERR_INVALID_ARG; } + if (gpio_pin_mask == 0) { + return ESP_ERR_INVALID_ARG; + } gpio_int_type_t intr_type = ((mode == ESP_GPIO_WAKEUP_GPIO_LOW) ? GPIO_INTR_LOW_LEVEL : GPIO_INTR_HIGH_LEVEL); esp_err_t err = ESP_OK; @@ -2272,6 +2292,26 @@ esp_err_t esp_sleep_disable_bt_wakeup(void) #endif } +esp_err_t esp_sleep_enable_usb_wakeup(void) +{ +#if SOC_PM_SUPPORT_USB_WAKEUP + s_config.wakeup_triggers |= RTC_USB_TRIG_EN; + return ESP_OK; +#else + return ESP_ERR_NOT_SUPPORTED; +#endif +} + +esp_err_t esp_sleep_disable_usb_wakeup(void) +{ +#if SOC_PM_SUPPORT_USB_WAKEUP + s_config.wakeup_triggers &= (~RTC_USB_TRIG_EN); + return ESP_OK; +#else + return ESP_ERR_NOT_SUPPORTED; +#endif +} + esp_sleep_wakeup_cause_t esp_sleep_get_wakeup_cause(void) { if (esp_rom_get_reset_reason(0) != RESET_REASON_CORE_DEEP_SLEEP && !s_light_sleep_wakeup) { @@ -2358,6 +2398,15 @@ uint32_t esp_sleep_get_wakeup_causes(void) uint32_t wakeup_cause_raw = rtc_cntl_ll_get_wakeup_cause(); #endif +#if SOC_LP_CORE_HW_AUTO_CLRWAKEUPCAUSE + /* LP store register to read wakeup cause saved by LP core. + * Must match the register used in lp_core_utils.c */ + uint32_t lp_core_wakeup_cause_status0 = lp_aon_hal_load_wakeup_cause(); + if ((wakeup_cause_raw == 0) && (lp_core_wakeup_cause_status0 != 0)) { + wakeup_cause_raw = lp_core_wakeup_cause_status0; + } +#endif + if (wakeup_cause_raw & RTC_TIMER_TRIG_EN) { wakeup_cause |= BIT(ESP_SLEEP_WAKEUP_TIMER); } @@ -2430,6 +2479,11 @@ uint32_t esp_sleep_get_wakeup_causes(void) if (wakeup_cause_raw & RTC_VBAT_UNDER_VOLT_TRIG_EN) { wakeup_cause |= BIT(ESP_SLEEP_WAKEUP_VBAT_UNDER_VOLT); } +#endif +#if SOC_PM_SUPPORT_USB_WAKEUP + if (wakeup_cause_raw & RTC_USB_TRIG_EN) { + wakeup_cause |= BIT(ESP_SLEEP_WAKEUP_USB); + } #endif if (wakeup_cause == 0) { wakeup_cause |= BIT(ESP_SLEEP_WAKEUP_UNDEFINED); diff --git a/components/esp_hw_support/usb_phy/usb_phy.c b/components/esp_hw_support/usb_phy/usb_phy.c index 898d35ac4ae..4570dfa0963 100644 --- a/components/esp_hw_support/usb_phy/usb_phy.c +++ b/components/esp_hw_support/usb_phy/usb_phy.c @@ -190,6 +190,22 @@ esp_err_t usb_phy_otg_set_mode(usb_phy_handle_t handle, usb_otg_mode_t mode) return ESP_OK; } +void usb_phy_set_otg_suspend_state(bool in_suspend) +{ +#if SOC_USB_UTMI_PHY_NUM + usb_utmi_hal_set_suspend_state(in_suspend); +#else + (void)in_suspend; +#endif +} + +void usb_phy_clear_otg_wakeup_status(void) +{ +#if SOC_USB_UTMI_PHY_NUM + usb_utmi_hal_clear_wakeup_status(); +#endif +} + static esp_err_t usb_phy_install(void) { PHY_ENTER_CRITICAL(); diff --git a/components/esp_lcd/CMakeLists.txt b/components/esp_lcd/CMakeLists.txt index 795856ff2fc..f5044ee4a5e 100644 --- a/components/esp_lcd/CMakeLists.txt +++ b/components/esp_lcd/CMakeLists.txt @@ -13,10 +13,6 @@ set(includes "include" "interface") set(priv_requires "esp_mm" "esp_psram" "esp_pm" "esp_driver_i2s" "esp_driver_dma") set(public_requires "esp_driver_gpio" "esp_driver_i2c" "esp_driver_spi" "esp_driver_parlio" "esp_hal_lcd") -if(CONFIG_SOC_DMA2D_SUPPORTED) - list(APPEND srcs "src/esp_async_fbcpy.c") -endif() - if(CONFIG_SOC_I2C_SUPPORTED) list(APPEND srcs "i2c/esp_lcd_panel_io_i2c.c") endif() diff --git a/components/esp_lcd/dsi/esp_lcd_panel_dpi.c b/components/esp_lcd/dsi/esp_lcd_panel_dpi.c index cba926b09f6..b2a94a529d6 100644 --- a/components/esp_lcd/dsi/esp_lcd_panel_dpi.c +++ b/components/esp_lcd/dsi/esp_lcd_panel_dpi.c @@ -6,11 +6,11 @@ #include #include "esp_lcd_panel_interface.h" #include "esp_lcd_mipi_dsi.h" +#include "esp_async_color_convert.h" #include "esp_intr_alloc.h" #include "esp_clk_tree.h" #include "esp_cache.h" #include "mipi_dsi_priv.h" -#include "esp_async_fbcpy.h" #include "esp_memory_utils.h" #include "esp_private/dw_gdma.h" #include "hal/color_hal.h" @@ -44,14 +44,14 @@ struct esp_lcd_dpi_panel_t { esp_lcd_panel_draw_bitmap_hook_t draw_bitmap_hook; // Draw bitmap hook function void* hook_ctx; // Hook context bool (*on_hook_end)(esp_lcd_panel_handle_t panel); // Callback to be invoked when the draw bitmap hook completes its operation - esp_async_fbcpy_handle_t fbcpy_handle; // Use DMA2D to do frame buffer copy (only when using DMA2D draw bitmap hook) - SemaphoreHandle_t draw_sem; // A semaphore used to synchronize the draw operations when DMA2D is used + async_color_convert_handle_t fbcpy_handle; // Async color convert handle used for same-format DMA2D frame buffer copy #if CONFIG_PM_ENABLE esp_pm_lock_handle_t pm_lock; // Power management lock #endif esp_lcd_dpi_panel_color_trans_done_cb_t on_color_trans_done; // Callback invoked when color data transfer has finished - esp_lcd_dpi_panel_refresh_done_cb_t on_refresh_done; // Callback invoked when one refresh operation finished (kinda like a vsync end) + esp_lcd_dpi_panel_frame_buf_complete_cb_t on_frame_buf_complete; // Callback invoked when the frame buffer can be reused safely + esp_lcd_dpi_panel_vsync_cb_t on_vsync; // VSYNC event callback void *user_ctx; // User context for the callback }; @@ -64,24 +64,18 @@ static bool dpi_panel_draw_bitmap_hook_end(esp_lcd_panel_t *panel) return false; } -static bool async_fbcpy_done_cb(esp_async_fbcpy_handle_t mcp, esp_async_fbcpy_event_data_t *event, void *cb_args) +static bool async_fbcpy_done_cb(async_color_convert_handle_t conv_hdl, async_color_convert_event_data_t *event, void *cb_args) { bool need_yield = false; esp_lcd_dpi_panel_t *dpi_panel = (esp_lcd_dpi_panel_t *)cb_args; - - // release the draw semaphore first - BaseType_t task_woken = pdFALSE; - xSemaphoreGiveFromISR(dpi_panel->draw_sem, &task_woken); - if (task_woken == pdTRUE) { - need_yield = true; - } + (void)conv_hdl; + (void)event; if (dpi_panel->on_hook_end) { if (dpi_panel->on_hook_end(&dpi_panel->base)) { need_yield = true; } } - return need_yield; } @@ -101,10 +95,16 @@ bool mipi_dsi_dma_trans_done_cb(dw_gdma_channel_handle_t chan, const dw_gdma_tra dw_gdma_channel_use_link_list(chan, link_list); dw_gdma_channel_enable_ctrl(chan, true); + if (dpi_panel->on_frame_buf_complete) { + if (dpi_panel->on_frame_buf_complete(&dpi_panel->base, NULL, dpi_panel->user_ctx)) { + yield_needed = true; + } + } + #if !MIPI_DSI_BRG_LL_EVENT_VSYNC // the DMA descriptor is large enough to carry a whole frame buffer, so this event can also be treated as a fake "vsync end" - if (dpi_panel->on_refresh_done) { - if (dpi_panel->on_refresh_done(&dpi_panel->base, NULL, dpi_panel->user_ctx)) { + if (dpi_panel->on_vsync) { + if (dpi_panel->on_vsync(&dpi_panel->base, NULL, dpi_panel->user_ctx)) { yield_needed = true; } } @@ -127,8 +127,8 @@ void mipi_dsi_bridge_isr_handler(void *args) ESP_DRAM_LOGE(TAG, "can't fetch data from external memory fast enough, underrun happens"); } if (intr_status & MIPI_DSI_BRG_LL_EVENT_VSYNC) { - if (dpi_panel->on_refresh_done) { - if (dpi_panel->on_refresh_done(&dpi_panel->base, NULL, dpi_panel->user_ctx)) { + if (dpi_panel->on_vsync) { + if (dpi_panel->on_vsync(&dpi_panel->base, NULL, dpi_panel->user_ctx)) { portYIELD_FROM_ISR(); } } @@ -468,28 +468,31 @@ static esp_err_t dpi_panel_draw_bitmap_dma2d_hook(esp_lcd_panel_t *panel, const { ESP_LOGV(TAG, "copy draw buffer by DMA2D"); esp_lcd_dpi_panel_t *dpi_panel = __containerof(panel, esp_lcd_dpi_panel_t, base); - // ensure the previous draw operation is finished - ESP_RETURN_ON_FALSE(xSemaphoreTake(dpi_panel->draw_sem, 0) == pdTRUE, ESP_ERR_INVALID_STATE, - TAG, "previous draw operation is not finished"); + (void)hook_ctx; - esp_async_fbcpy_trans_desc_t fbcpy_trans_config = { + async_color_convert_request_t fbcpy_trans_config = { .src_buffer = hook_data->src_data, .dst_buffer = hook_data->dst_data, - .src_buffer_size_x = hook_data->src_x_size, - .src_buffer_size_y = hook_data->src_y_size, - .dst_buffer_size_x = hook_data->dst_x_size, - .dst_buffer_size_y = hook_data->dst_y_size, - .src_offset_x = hook_data->src_x_start, - .src_offset_y = hook_data->src_y_start, - .dst_offset_x = hook_data->dst_x_start, - .dst_offset_y = hook_data->dst_y_start, - .copy_size_x = hook_data->src_x_end - hook_data->src_x_start, - .copy_size_y = hook_data->src_y_end - hook_data->src_y_start, - .pixel_format_fourcc_id = dpi_panel->in_color_format, + .src_stride = hook_data->src_x_size, + .src_height = hook_data->src_y_size, + .dst_stride = hook_data->dst_x_size, + .dst_height = hook_data->dst_y_size, + .src_x = hook_data->src_x_start, + .src_y = hook_data->src_y_start, + .dst_x = hook_data->dst_x_start, + .dst_y = hook_data->dst_y_start, + .copy_width = hook_data->src_x_end - hook_data->src_x_start, + .copy_height = hook_data->src_y_end - hook_data->src_y_start, + // For this DMA2D hook we only do window copy from draw buffer to frame buffer. + // Source and destination color formats are intentionally set to the same value to disable CSC. + .src_color_format = dpi_panel->in_color_format, + .dst_color_format = dpi_panel->in_color_format, }; - // save the on_hook_end callback, and invoke it when the async memcpy is done + // The async color convert backend owns source/destination cache sync for the + // DMA2D copy path, so the LCD driver should not perform extra cache sync here. + // Save the completion callback and invoke it when the async frame buffer copy finishes. dpi_panel->on_hook_end = hook_data->on_hook_end; - ESP_RETURN_ON_ERROR(esp_async_fbcpy(dpi_panel->fbcpy_handle, &fbcpy_trans_config, async_fbcpy_done_cb, dpi_panel), TAG, "async memcpy failed"); + ESP_RETURN_ON_ERROR(esp_async_color_convert(dpi_panel->fbcpy_handle, &fbcpy_trans_config, async_fbcpy_done_cb, dpi_panel), TAG, "async frame buffer copy failed"); return ESP_OK; } @@ -514,13 +517,13 @@ esp_err_t esp_lcd_dpi_panel_enable_dma2d(esp_lcd_panel_handle_t panel) // Check if built-in DMA2D draw bitmap hook is registered ESP_RETURN_ON_FALSE(!dpi_panel->fbcpy_handle, ESP_ERR_INVALID_STATE, TAG, "draw bitmap DMA2D hook is already registered"); - // Initialize DMA2D resources - esp_async_fbcpy_config_t fbcpy_config = {}; - ESP_RETURN_ON_ERROR(esp_async_fbcpy_install(&fbcpy_config, &dpi_panel->fbcpy_handle), TAG, "install async memcpy 2d failed"); + // Initialize the async color convert backend used by the built-in DMA2D copy hook. + // Use its default backlog to queue multiple frame buffer copy requests. + async_color_convert_config_t fbcpy_config = { + .dma_burst_size = 128, // for better performance + }; + ESP_RETURN_ON_ERROR(esp_async_color_convert_install_dma2d(&fbcpy_config, &dpi_panel->fbcpy_handle), TAG, "install async frame buffer copy backend failed"); - dpi_panel->draw_sem = xSemaphoreCreateBinaryWithCaps(DSI_MEM_ALLOC_CAPS); - ESP_GOTO_ON_FALSE(dpi_panel->draw_sem, ESP_ERR_NO_MEM, err, TAG, "no memory for draw semaphore"); - xSemaphoreGive(dpi_panel->draw_sem); // Register the DMA2D draw bitmap hook esp_lcd_panel_hooks_t hooks = { .draw_bitmap_hook = dpi_panel_draw_bitmap_dma2d_hook, @@ -531,13 +534,10 @@ esp_err_t esp_lcd_dpi_panel_enable_dma2d(esp_lcd_panel_handle_t panel) err: if (dpi_panel->fbcpy_handle) { - esp_async_fbcpy_uninstall(dpi_panel->fbcpy_handle); + esp_async_color_convert_uninstall(dpi_panel->fbcpy_handle); dpi_panel->fbcpy_handle = NULL; } - if (dpi_panel->draw_sem) { - vSemaphoreDeleteWithCaps(dpi_panel->draw_sem); - dpi_panel->draw_sem = NULL; - } + dpi_panel->on_hook_end = NULL; return ret; } @@ -554,13 +554,10 @@ esp_err_t esp_lcd_dpi_panel_disable_dma2d(esp_lcd_panel_handle_t panel) }; ESP_RETURN_ON_ERROR(esp_lcd_dpi_panel_register_hooks(panel, &hooks, NULL), TAG, "unregister DMA2D draw bitmap hook failed"); if (dpi_panel->fbcpy_handle) { - ESP_RETURN_ON_ERROR(esp_async_fbcpy_uninstall(dpi_panel->fbcpy_handle), TAG, "uninstall DMA2D failed"); + ESP_RETURN_ON_ERROR(esp_async_color_convert_uninstall(dpi_panel->fbcpy_handle), TAG, "uninstall DMA2D failed"); dpi_panel->fbcpy_handle = NULL; } - if (dpi_panel->draw_sem) { - vSemaphoreDeleteWithCaps(dpi_panel->draw_sem); - dpi_panel->draw_sem = NULL; - } + dpi_panel->on_hook_end = NULL; return ESP_OK; } @@ -626,7 +623,9 @@ static esp_err_t dpi_panel_draw_bitmap_2d(esp_lcd_panel_t *panel, int x_start, i } } else if (dpi_panel->draw_bitmap_hook) { // copy using draw bitmap hook ESP_LOGV(TAG, "copy draw buffer by draw bitmap hook"); - // Note, whether the previous draw operation is finished should be ensured by the hook + // Note, whether the previous draw operation is finished should be ensured by the hook. + // For the built-in DMA2D hook, cache maintenance of the source and destination + // buffers is handled inside the async color convert driver. esp_lcd_draw_bitmap_hook_data_t hook_data = { .dst_data = frame_buffer, @@ -719,19 +718,23 @@ esp_err_t esp_lcd_dpi_panel_register_event_callbacks(esp_lcd_panel_handle_t pane if (cbs->on_color_trans_done) { ESP_RETURN_ON_FALSE(esp_ptr_in_iram(cbs->on_color_trans_done), ESP_ERR_INVALID_ARG, TAG, "on_color_trans_done callback not in IRAM"); } - if (cbs->on_refresh_done) { - ESP_RETURN_ON_FALSE(esp_ptr_in_iram(cbs->on_refresh_done), ESP_ERR_INVALID_ARG, TAG, "on_refresh_done callback not in IRAM"); + if (cbs->on_vsync) { + ESP_RETURN_ON_FALSE(esp_ptr_in_iram(cbs->on_vsync), ESP_ERR_INVALID_ARG, TAG, "on_vsync callback not in IRAM"); + } + if (cbs->on_frame_buf_complete) { + ESP_RETURN_ON_FALSE(esp_ptr_in_iram(cbs->on_frame_buf_complete), ESP_ERR_INVALID_ARG, TAG, "on_frame_buf_complete callback not in IRAM"); } if (user_ctx) { ESP_RETURN_ON_FALSE(esp_ptr_internal(user_ctx), ESP_ERR_INVALID_ARG, TAG, "user context not in internal RAM"); } #endif // CONFIG_LCD_DSI_ISR_CACHE_SAFE dpi_panel->on_color_trans_done = cbs->on_color_trans_done; - dpi_panel->on_refresh_done = cbs->on_refresh_done; + dpi_panel->on_vsync = cbs->on_vsync; + dpi_panel->on_frame_buf_complete = cbs->on_frame_buf_complete; dpi_panel->user_ctx = user_ctx; // enable the vsync interrupt if the callback is provided - mipi_dsi_brg_ll_enable_interrupt(dpi_panel->bus->hal.bridge, MIPI_DSI_BRG_LL_EVENT_VSYNC, cbs->on_refresh_done != NULL); + mipi_dsi_brg_ll_enable_interrupt(dpi_panel->bus->hal.bridge, MIPI_DSI_BRG_LL_EVENT_VSYNC, cbs->on_vsync != NULL); return ESP_OK; } diff --git a/components/esp_lcd/dsi/include/esp_lcd_mipi_dsi.h b/components/esp_lcd/dsi/include/esp_lcd_mipi_dsi.h index 99887f93abe..e03b5cc6052 100644 --- a/components/esp_lcd/dsi/include/esp_lcd_mipi_dsi.h +++ b/components/esp_lcd/dsi/include/esp_lcd_mipi_dsi.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2023-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2023-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -177,18 +177,36 @@ typedef esp_lcd_dpi_panel_general_cb_t esp_lcd_dpi_panel_color_trans_done_cb_t; /** * @brief Declare the prototype of the function that will be invoked - * when driver finishes refreshing the frame buffer to the screen + * when the frame buffer can be reused safely + * + * @deprecated Use esp_lcd_dpi_panel_frame_buf_complete_cb_t instead. */ typedef esp_lcd_dpi_panel_general_cb_t esp_lcd_dpi_panel_refresh_done_cb_t; +/** + * @brief Declare the prototype of the function that will be invoked when the LCD controller sends the VSYNC signal. + */ +typedef esp_lcd_dpi_panel_general_cb_t esp_lcd_dpi_panel_vsync_cb_t; + +/** + * @brief Declare the prototype of the function that will be invoked + * when the frame buffer can be reused safely + */ +typedef esp_lcd_dpi_panel_general_cb_t esp_lcd_dpi_panel_frame_buf_complete_cb_t; + /** * @brief Type of LCD DPI panel callbacks */ typedef struct { - esp_lcd_dpi_panel_color_trans_done_cb_t on_color_trans_done; /*!< Invoked when user's color buffer copied to the internal frame buffer. + esp_lcd_dpi_panel_color_trans_done_cb_t on_color_trans_done; /*!< Invoked when user's draw buffer copied to the frame buffer. This is an indicator that the draw buffer can be recycled safely. But doesn't mean the draw buffer finishes the refreshing to the screen. */ - esp_lcd_dpi_panel_refresh_done_cb_t on_refresh_done; /*!< Invoked when the internal frame buffer finishes refreshing to the screen */ + union { + esp_lcd_dpi_panel_refresh_done_cb_t on_refresh_done __attribute__((deprecated("Deprecated, use on_frame_buf_complete instead"))); /*!< Deprecated, use on_frame_buf_complete instead */ + esp_lcd_dpi_panel_frame_buf_complete_cb_t on_frame_buf_complete; /*!< Invoked when the frame buffer can be reused safely + when the frame buffer is the draw buffer. */ + }; + esp_lcd_dpi_panel_vsync_cb_t on_vsync; /*!< VSYNC event callback */ } esp_lcd_dpi_panel_event_callbacks_t; /** diff --git a/components/esp_lcd/i2c/esp_lcd_panel_io_i2c.c b/components/esp_lcd/i2c/esp_lcd_panel_io_i2c.c index 68c4b461e05..b290602da1c 100644 --- a/components/esp_lcd/i2c/esp_lcd_panel_io_i2c.c +++ b/components/esp_lcd/i2c/esp_lcd_panel_io_i2c.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2023-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -46,6 +46,7 @@ typedef struct { uint32_t control_phase_data; // control byte when transferring data esp_lcd_panel_io_color_trans_done_cb_t on_color_trans_done; // User register's callback, invoked when color data trans done void *user_ctx; // User's private data, passed directly to callback on_color_trans_done() + int transaction_timeout_ms; // I2C xfer timeout passed to i2c_master_* (-1 = infinite) } lcd_panel_io_i2c_t; esp_err_t esp_lcd_new_panel_io_i2c(i2c_master_bus_handle_t bus, const esp_lcd_panel_io_i2c_config_t *io_config, esp_lcd_panel_io_handle_t *ret_io) @@ -58,6 +59,7 @@ esp_err_t esp_lcd_new_panel_io_i2c(i2c_master_bus_handle_t bus, const esp_lcd_pa i2c_master_dev_handle_t i2c_handle = NULL; ESP_GOTO_ON_FALSE(io_config && ret_io, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument"); ESP_GOTO_ON_FALSE(io_config->control_phase_bytes * 8 > io_config->dc_bit_offset, ESP_ERR_INVALID_ARG, err, TAG, "D/C bit exceeds control bytes"); + ESP_GOTO_ON_FALSE(io_config->transaction_timeout_ms >= -1, ESP_ERR_INVALID_ARG, err, TAG, "invalid transaction_timeout_ms"); // leak detection of i2c_panel_io because saving i2c_panel_io->base address ESP_COMPILER_DIAGNOSTIC_PUSH_IGNORE("-Wanalyzer-malloc-leak") i2c_panel_io = calloc(1, sizeof(lcd_panel_io_i2c_t)); @@ -78,6 +80,8 @@ esp_err_t esp_lcd_new_panel_io_i2c(i2c_master_bus_handle_t bus, const esp_lcd_pa i2c_panel_io->control_phase_data = (!io_config->flags.dc_low_on_data) << (io_config->dc_bit_offset); i2c_panel_io->control_phase_cmd = (io_config->flags.dc_low_on_data) << (io_config->dc_bit_offset); i2c_panel_io->dev_addr = io_config->dev_addr; + /* transaction_timeout_ms == 0: omitted or zero-init, keep legacy infinite wait (same as -1). */ + i2c_panel_io->transaction_timeout_ms = (io_config->transaction_timeout_ms == 0) ? -1 : io_config->transaction_timeout_ms; i2c_panel_io->base.del = panel_io_i2c_del; i2c_panel_io->base.rx_param = panel_io_i2c_rx_param; i2c_panel_io->base.tx_param = panel_io_i2c_tx_param; @@ -142,9 +146,9 @@ static esp_err_t panel_io_i2c_rx_buffer(esp_lcd_panel_io_t *io, int lcd_cmd, voi write_size += cmds_size; } - ESP_GOTO_ON_ERROR(i2c_master_transmit_receive(i2c_panel_io->i2c_handle, write_buffer, write_size, buffer, buffer_size, -1), err, TAG, "i2c transaction failed"); + ESP_GOTO_ON_ERROR(i2c_master_transmit_receive(i2c_panel_io->i2c_handle, write_buffer, write_size, buffer, buffer_size, i2c_panel_io->transaction_timeout_ms), err, TAG, "i2c transaction failed"); } else { - ESP_GOTO_ON_ERROR(i2c_master_receive(i2c_panel_io->i2c_handle, buffer, buffer_size, -1), err, TAG, "i2c transaction failed"); + ESP_GOTO_ON_ERROR(i2c_master_receive(i2c_panel_io->i2c_handle, buffer, buffer_size, i2c_panel_io->transaction_timeout_ms), err, TAG, "i2c transaction failed"); } return ESP_OK; @@ -190,7 +194,7 @@ static esp_err_t panel_io_i2c_tx_buffer(esp_lcd_panel_io_t *io, int lcd_cmd, con {.write_buffer = lcd_buffer, .buffer_size = lcd_buffer_size}, }; - ESP_GOTO_ON_ERROR(i2c_master_multi_buffer_transmit(i2c_panel_io->i2c_handle, lcd_i2c_buffer, sizeof(lcd_i2c_buffer) / sizeof(i2c_master_transmit_multi_buffer_info_t), -1), err, TAG, "i2c transaction failed"); + ESP_GOTO_ON_ERROR(i2c_master_multi_buffer_transmit(i2c_panel_io->i2c_handle, lcd_i2c_buffer, sizeof(lcd_i2c_buffer) / sizeof(i2c_master_transmit_multi_buffer_info_t), i2c_panel_io->transaction_timeout_ms), err, TAG, "i2c transaction failed"); if (!is_param) { // trans done callback if (i2c_panel_io->on_color_trans_done) { diff --git a/components/esp_lcd/include/esp_lcd_io_i2c.h b/components/esp_lcd/include/esp_lcd_io_i2c.h index 4a0c2bac0dc..cf3216a06ef 100644 --- a/components/esp_lcd/include/esp_lcd_io_i2c.h +++ b/components/esp_lcd/include/esp_lcd_io_i2c.h @@ -31,6 +31,7 @@ typedef struct { unsigned int dc_low_on_data: 1; /*!< If this flag is enabled, DC line = 0 means transfer data, DC line = 1 means transfer command; vice versa */ unsigned int disable_control_phase: 1; /*!< If this flag is enabled, the control phase isn't used */ } flags; /*!< Extra flags to fine-tune the I2C device */ + int transaction_timeout_ms; /*!< Timeout for each I2C transfer in ms, 0/-1: wait forever, >0: finite timeout */ } esp_lcd_panel_io_i2c_config_t; /** diff --git a/components/esp_lcd/priv_include/esp_async_fbcpy.h b/components/esp_lcd/priv_include/esp_async_fbcpy.h deleted file mode 100644 index 481b3aed2b7..00000000000 --- a/components/esp_lcd/priv_include/esp_async_fbcpy.h +++ /dev/null @@ -1,90 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD - * - * SPDX-License-Identifier: Apache-2.0 - */ - -#pragma once - -#include "esp_err.h" -#include "hal/color_types.h" - -/** - * @brief Async FrameBuffer copy context - */ -typedef struct esp_async_fbcpy_context_t *esp_async_fbcpy_handle_t; - -/** - * @brief Async FrameBuffer copy configuration - */ -typedef struct { -} esp_async_fbcpy_config_t; - -/** - * @brief Install Async FrameBuffer copy driver - * - * @param config Async FrameBuffer copy configuration - * @param mcp Returned Async FrameBuffer copy handle - * @return - * - ESP_OK: Install Async FrameBuffer copy driver successfully - * - ESP_ERR_INVALID_ARG: Install Async FrameBuffer copy driver failed because of invalid argument - * - ESP_ERR_NO_MEM: Install Async FrameBuffer copy driver failed because of out of memory - * - ESP_FAIL: Install Async FrameBuffer copy driver failed because of other error - */ -esp_err_t esp_async_fbcpy_install(const esp_async_fbcpy_config_t *config, esp_async_fbcpy_handle_t *mcp); - -/** - * @brief Uninstall Async FrameBuffer copy driver - * - * @param mcp Async FrameBuffer copy handle - * @return - * - ESP_OK: Uninstall Async FrameBuffer copy driver successfully - * - ESP_ERR_INVALID_ARG: Uninstall Async FrameBuffer copy driver failed because of invalid argument - * - ESP_FAIL: Uninstall Async FrameBuffer copy driver failed because of other error - */ -esp_err_t esp_async_fbcpy_uninstall(esp_async_fbcpy_handle_t mcp); - -/** - * @brief Async FrameBuffer copy transaction descriptor - */ -typedef struct { - const void *src_buffer; /*!< Source buffer */ - void *dst_buffer; /*!< Destination buffer */ - size_t src_buffer_size_x; /*!< Source buffer size in x direction, size count in the number of pixels */ - size_t src_buffer_size_y; /*!< Source buffer size in y direction, size count in the number of pixels */ - size_t dst_buffer_size_x; /*!< Destination buffer size in x direction, size count in the number of pixels */ - size_t dst_buffer_size_y; /*!< Destination buffer size in y direction, size count in the number of pixels */ - size_t src_offset_x; /*!< Copy action will start from this offset in source buffer in the x direction, offset count in the number of pixels */ - size_t src_offset_y; /*!< Copy action will start from this offset in source buffer in the y direction, offset count in the number of pixels */ - size_t dst_offset_x; /*!< Copy action will start from this offset in destination buffer in the x direction, offset count in the number of pixels */ - size_t dst_offset_y; /*!< Copy action will start from this offset in destination buffer in the y direction, offset count in the number of pixels */ - size_t copy_size_x; /*!< Copy size in the x direction, size count in the number of pixels */ - size_t copy_size_y; /*!< Copy size in the y direction, size count in the number of pixels */ - esp_color_fourcc_t pixel_format_fourcc_id; /*!< Pixel format unique ID */ -} esp_async_fbcpy_trans_desc_t; - -/** - * @brief Async FrameBuffer copy event data - */ -typedef struct { -} esp_async_fbcpy_event_data_t; - -/** - * @brief Async FrameBuffer copy event callback prototype - */ -typedef bool (*esp_async_fbcpy_event_callback_t)(esp_async_fbcpy_handle_t mcp, esp_async_fbcpy_event_data_t *event_data, void *cb_args); - -/** - * @brief Start Async FrameBuffer copy transaction - * - * @param mcp Async FrameBuffer copy handle - * @param transaction Async FrameBuffer copy transaction descriptor - * @param memcpy_done_cb Callback function that will be invoked when Async FrameBuffer copy transaction finishes - * @param cb_args User data - * @return - * - ESP_OK: Start Async FrameBuffer copy transaction successfully - * - ESP_ERR_INVALID_ARG: Start Async FrameBuffer copy transaction failed because of invalid argument - * - ESP_FAIL: Start Async FrameBuffer copy transaction failed because of other error - */ -esp_err_t esp_async_fbcpy(esp_async_fbcpy_handle_t mcp, esp_async_fbcpy_trans_desc_t* transaction, - esp_async_fbcpy_event_callback_t memcpy_done_cb, void *cb_args); diff --git a/components/esp_lcd/rgb/esp_lcd_panel_rgb.c b/components/esp_lcd/rgb/esp_lcd_panel_rgb.c index 2f64b761b59..15d267d59de 100644 --- a/components/esp_lcd/rgb/esp_lcd_panel_rgb.c +++ b/components/esp_lcd/rgb/esp_lcd_panel_rgb.c @@ -50,6 +50,13 @@ #include "rgb_lcd_rotation_sw.h" #include "esp_private/sleep_retention.h" +#if SOC_HAS(AXI_GDMA) +#include "hal/axi_dma_ll.h" +#if AXI_DMA_LL_SUPPORT(TX_LINK_SWITCH) +#define RGB_LCD_USE_GDMA_LINK_SWITCH_EVENT 1 +#endif +#endif + // hardware issue workaround #if CONFIG_IDF_TARGET_ESP32S3 #define RGB_LCD_NEEDS_SEPARATE_RESTART_LINK 1 @@ -95,6 +102,9 @@ static esp_err_t lcd_rgb_panel_configure_gpio(esp_rgb_panel_t *rgb_panel, const static void lcd_rgb_panel_release_gpio(esp_rgb_panel_t *rgb_panel); static void lcd_rgb_panel_start_transmission(esp_rgb_panel_t *rgb_panel); static void rgb_lcd_default_isr_handler(void *args); +#if RGB_LCD_USE_GDMA_LINK_SWITCH_EVENT +static bool lcd_rgb_panel_link_switch_handler(gdma_channel_handle_t dma_chan, gdma_event_data_t *event_data, void *user_data); +#endif // RGB_LCD_USE_GDMA_LINK_SWITCH_EVENT struct esp_rgb_panel_t { esp_lcd_panel_t base; // Base class of generic lcd panel @@ -139,7 +149,7 @@ struct esp_rgb_panel_t { size_t bb_eof_count; // record the number we received the DMA EOF event, compare with `expect_eof_count` in the VSYNC_END ISR size_t expect_eof_count; // record the number of DMA EOF event we expected to receive esp_lcd_rgb_panel_draw_buf_complete_cb_t on_color_trans_done; // draw buffer completes - esp_lcd_rgb_panel_frame_buf_complete_cb_t on_frame_buf_complete; // callback used to notify when the bounce buffer finish copying the entire frame + esp_lcd_rgb_panel_frame_buf_complete_cb_t on_frame_buf_complete; // callback used to notify when the buffer can be reused safely esp_lcd_rgb_panel_vsync_cb_t on_vsync; // VSYNC event callback esp_lcd_rgb_panel_bounce_buf_fill_cb_t on_bounce_empty; // callback used to fill a bounce buffer rather than copying from the frame buffer void *user_ctx; // Reserved user's data of callback functions @@ -738,7 +748,9 @@ static esp_err_t rgb_panel_draw_bitmap(esp_lcd_panel_t *panel, int x_start, int // it's hard to know the time when the new frame buffer starts gdma_link_concat(rgb_panel->dma_fb_links[i], -1, rgb_panel->dma_fb_links[rgb_panel->cur_fb_index], 0); } - +#if RGB_LCD_USE_GDMA_LINK_SWITCH_EVENT + ESP_RETURN_ON_ERROR(gdma_request_link_switch_event(rgb_panel->dma_chan), TAG, "request link switch event failed"); +#endif // RGB_LCD_USE_GDMA_LINK_SWITCH_EVENT } } return ESP_OK; @@ -977,7 +989,7 @@ static IRAM_ATTR bool lcd_rgb_panel_eof_handler(gdma_channel_handle_t dma_chan, portEXIT_CRITICAL_ISR(&rgb_panel->spinlock); need_yield = lcd_rgb_panel_fill_bounce_buffer(rgb_panel, rgb_panel->bounce_buffer[bb]); } else { - // if not bounce buffer, the DMA EOF event means the end of a frame has been sent out to the LCD controller + // Once the preload has already done, the buffer complete callback is not reliable. if (rgb_panel->on_frame_buf_complete) { if (rgb_panel->on_frame_buf_complete(&rgb_panel->base, NULL, rgb_panel->user_ctx)) { need_yield = true; @@ -987,6 +999,24 @@ static IRAM_ATTR bool lcd_rgb_panel_eof_handler(gdma_channel_handle_t dma_chan, return need_yield; } +#if RGB_LCD_USE_GDMA_LINK_SWITCH_EVENT +static IRAM_ATTR bool lcd_rgb_panel_link_switch_handler(gdma_channel_handle_t dma_chan, gdma_event_data_t *event_data, void *user_data) +{ + (void)dma_chan; + (void)event_data; + bool need_yield = false; + esp_rgb_panel_t *rgb_panel = (esp_rgb_panel_t *)user_data; + + if (rgb_panel->on_frame_buf_complete) { + if (rgb_panel->on_frame_buf_complete(&rgb_panel->base, NULL, rgb_panel->user_ctx)) { + need_yield = true; + } + } + + return need_yield; +} +#endif // RGB_LCD_USE_GDMA_LINK_SWITCH_EVENT + static esp_err_t lcd_rgb_create_dma_channel(esp_rgb_panel_t *rgb_panel) { // alloc DMA channel and connect to LCD peripheral @@ -1013,11 +1043,19 @@ static esp_err_t lcd_rgb_create_dma_channel(esp_rgb_panel_t *rgb_panel) // get the memory alignment required by the DMA gdma_get_alignment_constraints(rgb_panel->dma_chan, &rgb_panel->int_mem_align, &rgb_panel->ext_mem_align); - // register DMA EOF callback + // register DMA event callbacks gdma_tx_event_callbacks_t cbs = { +#if RGB_LCD_USE_GDMA_LINK_SWITCH_EVENT + // if no bounce buffer, the DMA EOF event means the end of a frame has been sent out to the LCD controller. + // But the dma link may have preloaded the next frame with the current buffer. + // So we need to wait for the GDMA link switch event to invoke the on_frame_buf_complete callback. + .on_trans_eof = (rgb_panel->flags.stream_mode && !rgb_panel->bb_size) ? NULL : lcd_rgb_panel_eof_handler, + .on_link_switch = lcd_rgb_panel_link_switch_handler, +#else .on_trans_eof = lcd_rgb_panel_eof_handler, +#endif // RGB_LCD_USE_GDMA_LINK_SWITCH_EVENT }; - ESP_RETURN_ON_ERROR(gdma_register_tx_event_callbacks(rgb_panel->dma_chan, &cbs, rgb_panel), TAG, "register DMA EOF callback failed"); + ESP_RETURN_ON_ERROR(gdma_register_tx_event_callbacks(rgb_panel->dma_chan, &cbs, rgb_panel), TAG, "register DMA event callbacks failed"); return ESP_OK; } diff --git a/components/esp_lcd/rgb/include/esp_lcd_panel_rgb.h b/components/esp_lcd/rgb/include/esp_lcd_panel_rgb.h index 2831052be49..7d9c8990bda 100644 --- a/components/esp_lcd/rgb/include/esp_lcd_panel_rgb.h +++ b/components/esp_lcd/rgb/include/esp_lcd_panel_rgb.h @@ -97,7 +97,7 @@ typedef bool (*esp_lcd_rgb_panel_general_cb_t)(esp_lcd_panel_handle_t panel, con typedef esp_lcd_rgb_panel_general_cb_t esp_lcd_rgb_panel_draw_buf_complete_cb_t; /** - * @brief Declare the prototype of the function that will be invoked when a whole frame buffer is sent to the LCD DMA. + * @brief Declare the prototype of the function that will be invoked when a whole frame buffer can be reused safely. * The LCD hardware may still need some blank time to finish the refresh. */ typedef esp_lcd_rgb_panel_general_cb_t esp_lcd_rgb_panel_frame_buf_complete_cb_t; @@ -132,7 +132,7 @@ typedef struct { But doesn't mean the draw buffer finishes the refreshing to the screen. */ esp_lcd_rgb_panel_vsync_cb_t on_vsync; /*!< VSYNC event callback */ esp_lcd_rgb_panel_bounce_buf_fill_cb_t on_bounce_empty; /*!< Bounce buffer empty callback. */ - esp_lcd_rgb_panel_frame_buf_complete_cb_t on_frame_buf_complete; /*!< A whole frame buffer was just sent to the LCD DMA */ + esp_lcd_rgb_panel_frame_buf_complete_cb_t on_frame_buf_complete; /*!< Invoked when the frame buffer can be reused safely */ } esp_lcd_rgb_panel_event_callbacks_t; /** diff --git a/components/esp_lcd/src/esp_async_fbcpy.c b/components/esp_lcd/src/esp_async_fbcpy.c deleted file mode 100644 index ae8e95ad217..00000000000 --- a/components/esp_lcd/src/esp_async_fbcpy.c +++ /dev/null @@ -1,214 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2023-2025 Espressif Systems (Shanghai) CO LTD - * - * SPDX-License-Identifier: Apache-2.0 - */ -#include "freertos/FreeRTOS.h" -#include "freertos/task.h" -#include "esp_check.h" -#include "esp_cache.h" -#include "esp_heap_caps.h" -#include "soc/dma2d_channel.h" -#include "hal/cache_hal.h" -#include "hal/cache_ll.h" -#include "hal/dma2d_ll.h" -#include "esp_private/dma2d.h" -#include "esp_async_fbcpy.h" - -#define ALIGN_UP(num, align) (((num) + ((align) - 1)) & ~((align) - 1)) - -static const char *TAG = "async_fbcpy"; - -typedef struct esp_async_fbcpy_context_t { - dma2d_pool_handle_t client; // DMA2D client - dma2d_descriptor_t* tx_desc; // DMA2D TX descriptor - dma2d_descriptor_t* rx_desc; // DMA2D RX descriptor - dma2d_trans_t* trans_desc; // DMA2D transaction descriptor - size_t dma_desc_size; // DMA2D descriptor size - esp_async_fbcpy_event_callback_t memcpy_done_cb; // memory copy done callback - void *cb_args; // callback arguments -} esp_async_fbcpy_context_t; - -static esp_err_t async_fbcpy_del_context(esp_async_fbcpy_context_t* ctx) -{ - if (ctx->tx_desc) { - free(ctx->tx_desc); - } - if (ctx->rx_desc) { - free(ctx->rx_desc); - } - if (ctx->trans_desc) { - free(ctx->trans_desc); - } - if (ctx->client) { - dma2d_release_pool(ctx->client); - } - free(ctx); - return ESP_OK; -} - -esp_err_t esp_async_fbcpy_install(const esp_async_fbcpy_config_t *config, esp_async_fbcpy_handle_t *mcp) -{ - esp_err_t ret = ESP_OK; - esp_async_fbcpy_context_t *ctx = NULL; - dma2d_trans_t* trans_desc = NULL; - dma2d_descriptor_t* dma_tx_desc = NULL; - dma2d_descriptor_t* dma_rx_desc = NULL; - dma2d_pool_handle_t dma2d_client = NULL; - - ESP_RETURN_ON_FALSE(config && mcp, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); - // allocate context memory - ctx = heap_caps_calloc(1, sizeof(esp_async_fbcpy_context_t), MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT); - ESP_GOTO_ON_FALSE(ctx, ESP_ERR_NO_MEM, err, TAG, "no mem for esp_async_fbcpy_context_t"); - // according to the dma2d design, the transaction descriptor is also saved by the user - trans_desc = heap_caps_calloc(1, dma2d_get_trans_elm_size(), MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT); - ESP_GOTO_ON_FALSE(trans_desc, ESP_ERR_NO_MEM, err, TAG, "no mem for trans_desc"); - ctx->trans_desc = trans_desc; - // allocate memory for DMA descriptor, the descriptor must be allocated from the internal memory, and alignment to the cache line size - uint32_t data_cache_line_size = cache_hal_get_cache_line_size(CACHE_LL_LEVEL_INT_MEM, CACHE_TYPE_DATA); - size_t alignment = MAX(DMA2D_LL_DESC_ALIGNMENT, data_cache_line_size); - size_t dma_desc_mem_size = ALIGN_UP(sizeof(dma2d_descriptor_align8_t), alignment); - dma_tx_desc = heap_caps_aligned_calloc(alignment, 1, dma_desc_mem_size, MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT); - dma_rx_desc = heap_caps_aligned_calloc(alignment, 1, dma_desc_mem_size, MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT); - ESP_GOTO_ON_FALSE(dma_tx_desc && dma_rx_desc, ESP_ERR_NO_MEM, err, TAG, "no memory for DMA2D descriptors"); - ctx->tx_desc = dma_tx_desc; - ctx->rx_desc = dma_rx_desc; - ctx->dma_desc_size = dma_desc_mem_size; - - // initialize DMA2D client - dma2d_pool_config_t dma2d_client_config = {}; // all follow default configurations - ESP_GOTO_ON_ERROR(dma2d_acquire_pool(&dma2d_client_config, &dma2d_client), err, TAG, "create DMA2D client failed"); - ctx->client = dma2d_client; - - *mcp = ctx; - return ESP_OK; - -err: - if (ctx) { - async_fbcpy_del_context(ctx); - } - return ret; -} - -esp_err_t esp_async_fbcpy_uninstall(esp_async_fbcpy_handle_t mcp) -{ - ESP_RETURN_ON_FALSE(mcp, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); - return async_fbcpy_del_context(mcp); -} - -static void async_memcpy_setup_dma2d_descriptor(esp_async_fbcpy_context_t* mcp_ctx, esp_async_fbcpy_trans_desc_t* transaction) -{ - dma2d_descriptor_t* tx_desc = mcp_ctx->tx_desc; - dma2d_descriptor_t* rx_desc = mcp_ctx->rx_desc; - size_t dma_desc_size = mcp_ctx->dma_desc_size; - uint8_t dma2d_pbyte = dma2d_desc_pixel_format_to_pbyte_value(transaction->pixel_format_fourcc_id); - - tx_desc->buffer = (void*)transaction->src_buffer; - tx_desc->next = NULL; - tx_desc->dma2d_en = 1; - tx_desc->suc_eof = 1; - tx_desc->ha_length = transaction->src_buffer_size_x; - tx_desc->va_size = transaction->src_buffer_size_y; - tx_desc->hb_length = transaction->copy_size_x; - tx_desc->vb_size = transaction->copy_size_y; - tx_desc->x = transaction->src_offset_x; - tx_desc->y = transaction->src_offset_y; - tx_desc->pbyte = dma2d_pbyte; - tx_desc->mode = DMA2D_DESCRIPTOR_BLOCK_RW_MODE_SINGLE; - tx_desc->owner = DMA2D_DESCRIPTOR_BUFFER_OWNER_DMA; - - rx_desc->buffer = transaction->dst_buffer; - rx_desc->next = NULL; - rx_desc->dma2d_en = 1; - rx_desc->suc_eof = 1; - rx_desc->ha_length = transaction->dst_buffer_size_x; - rx_desc->va_size = transaction->dst_buffer_size_y; - rx_desc->hb_length = transaction->copy_size_x; - rx_desc->vb_size = transaction->copy_size_y; - rx_desc->x = transaction->dst_offset_x; - rx_desc->y = transaction->dst_offset_y; - rx_desc->pbyte = dma2d_pbyte; - rx_desc->mode = DMA2D_DESCRIPTOR_BLOCK_RW_MODE_SINGLE; - rx_desc->owner = DMA2D_DESCRIPTOR_BUFFER_OWNER_DMA; - - esp_cache_msync(tx_desc, dma_desc_size, ESP_CACHE_MSYNC_FLAG_DIR_C2M | ESP_CACHE_MSYNC_FLAG_INVALIDATE); - esp_cache_msync(rx_desc, dma_desc_size, ESP_CACHE_MSYNC_FLAG_DIR_C2M | ESP_CACHE_MSYNC_FLAG_INVALIDATE); -} - -static bool dma2d_memcpy_done_cb(dma2d_channel_handle_t dma2d_chan, dma2d_event_data_t *event_data, void *user_data) -{ - bool need_yield = false; - esp_async_fbcpy_context_t* mcp = (esp_async_fbcpy_context_t*)user_data; - - if (mcp->memcpy_done_cb) { - need_yield = mcp->memcpy_done_cb(mcp, NULL, mcp->cb_args); - } - - return need_yield; -} - -static bool dma2d_job_picked_cb(uint32_t num_chans, const dma2d_trans_channel_info_t *dma2d_chans, void *user_data) -{ - esp_async_fbcpy_context_t* mcp = (esp_async_fbcpy_context_t*)user_data; - dma2d_channel_handle_t tx_chan = NULL; - dma2d_channel_handle_t rx_chan = NULL; - for (uint32_t i = 0; i < num_chans; i++) { - if (dma2d_chans[i].dir == DMA2D_CHANNEL_DIRECTION_TX) { - tx_chan = dma2d_chans[i].chan; - } - if (dma2d_chans[i].dir == DMA2D_CHANNEL_DIRECTION_RX) { - rx_chan = dma2d_chans[i].chan; - } - } - dma2d_trigger_t trig_periph = { - .periph = DMA2D_TRIG_PERIPH_M2M, - .periph_sel_id = SOC_DMA2D_TRIG_PERIPH_M2M_TX, - }; - dma2d_connect(tx_chan, &trig_periph); - trig_periph.periph_sel_id = SOC_DMA2D_TRIG_PERIPH_M2M_RX; - dma2d_connect(rx_chan, &trig_periph); - - dma2d_rx_event_callbacks_t dma_cbs = { - .on_recv_eof = dma2d_memcpy_done_cb, - }; - dma2d_register_rx_event_callbacks(rx_chan, &dma_cbs, mcp); - - // 2D-DMA channel data burst length is set to the maximum burst length by default, which meets the encryption alignment restriction - // so even if flash encryption is enabled, it can work properly - - dma2d_set_desc_addr(tx_chan, (intptr_t)(mcp->tx_desc)); - dma2d_set_desc_addr(rx_chan, (intptr_t)(mcp->rx_desc)); - - dma2d_start(tx_chan); - dma2d_start(rx_chan); - - return false; -} - -esp_err_t esp_async_fbcpy(esp_async_fbcpy_handle_t mcp, esp_async_fbcpy_trans_desc_t* transaction, esp_async_fbcpy_event_callback_t memcpy_done_cb, void *cb_args) -{ - ESP_RETURN_ON_FALSE(mcp && transaction, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); - mcp->memcpy_done_cb = memcpy_done_cb; - mcp->cb_args = cb_args; - - // write back the user's draw buffer, so that the DMA can see the correct data - // Note, the user src buffer may not be contiguous, writeback from the head to the tail anyways - size_t bits_per_pixel = color_hal_pixel_format_fourcc_get_bit_depth(transaction->pixel_format_fourcc_id); - size_t copy_head = (transaction->src_offset_x + transaction->src_offset_y * transaction->src_buffer_size_x) * bits_per_pixel / 8; - size_t copy_size = (transaction->copy_size_x + transaction->copy_size_y * transaction->src_buffer_size_x) * bits_per_pixel / 8; - ESP_RETURN_ON_ERROR(esp_cache_msync((void *)transaction->src_buffer + copy_head, copy_size, ESP_CACHE_MSYNC_FLAG_DIR_C2M | ESP_CACHE_MSYNC_FLAG_UNALIGNED), TAG, "writeback draw buffer failed"); - - // mount the data to the DMA descriptor - async_memcpy_setup_dma2d_descriptor(mcp, transaction); - - // submit the DMA2D request - static dma2d_trans_config_t dma2d_trans_conf = { - .tx_channel_num = 1, - .rx_channel_num = 1, - .channel_flags = DMA2D_CHANNEL_FUNCTION_FLAG_SIBLING, - .on_job_picked = dma2d_job_picked_cb, - }; - dma2d_trans_conf.user_config = mcp; - ESP_RETURN_ON_ERROR(dma2d_enqueue(mcp->client, &dma2d_trans_conf, mcp->trans_desc), TAG, "DMA2D enqueue failed"); - return ESP_OK; -} diff --git a/components/esp_lcd/test_apps/i2c_lcd/main/test_i2c_lcd_panel.cpp b/components/esp_lcd/test_apps/i2c_lcd/main/test_i2c_lcd_panel.cpp index fbe26049ac0..8de401b3c84 100644 --- a/components/esp_lcd/test_apps/i2c_lcd/main/test_i2c_lcd_panel.cpp +++ b/components/esp_lcd/test_apps/i2c_lcd/main/test_i2c_lcd_panel.cpp @@ -56,7 +56,8 @@ TEST_CASE("lcd_panel_with_i2c_interface_(ssd1306)", "[lcd]") .flags = { .dc_low_on_data = false, // According to SSD1306 datasheet, DC=0 means command, DC=1 means data .disable_control_phase = false, // Control phase is used - } + }, + .transaction_timeout_ms = 0, // 0 keeps the legacy infinite wait behavior }; TEST_ESP_OK(esp_lcd_new_panel_io_i2c(bus_handle, &io_config, &io_handle)); diff --git a/components/esp_lcd/test_apps/mipi_dsi_lcd/main/test_mipi_dsi_iram.c b/components/esp_lcd/test_apps/mipi_dsi_lcd/main/test_mipi_dsi_iram.c index 41c27348d79..2f3833a165c 100644 --- a/components/esp_lcd/test_apps/mipi_dsi_lcd/main/test_mipi_dsi_iram.c +++ b/components/esp_lcd/test_apps/mipi_dsi_lcd/main/test_mipi_dsi_iram.c @@ -91,7 +91,7 @@ TEST_CASE("MIPI DSI draw bitmap (EK79007) IRAM Safe", "[mipi_dsi]") uint32_t callback_calls = 0; esp_lcd_dpi_panel_event_callbacks_t cbs = { - .on_refresh_done = test_dpi_panel_count_in_callback, + .on_frame_buf_complete = test_dpi_panel_count_in_callback, }; TEST_ESP_OK(esp_lcd_dpi_panel_register_event_callbacks(mipi_dpi_panel, &cbs, &callback_calls)); diff --git a/components/esp_libc/project_include.cmake b/components/esp_libc/project_include.cmake index 42c0c49af10..998b5c968ca 100644 --- a/components/esp_libc/project_include.cmake +++ b/components/esp_libc/project_include.cmake @@ -1,6 +1,5 @@ -# Get picolibc specs option with --gc-sections flag removed -function(get_picolibc_specs_path out_var) - set(modified_specs_path "${CMAKE_CURRENT_BINARY_DIR}/specs/picolibc.specs") +function(_get_modified_specs_path out_var specs_filename modification) + set(modified_specs_path "${CMAKE_CURRENT_BINARY_DIR}/specs/${specs_filename}") set(${out_var} "${modified_specs_path}" PARENT_SCOPE) # Return if modified specs file already exists to avoid regeneration @@ -8,30 +7,50 @@ function(get_picolibc_specs_path out_var) return() endif() - # Find the original picolibc.specs file in the toolchain directory + # Find the original specs file in the toolchain directory execute_process( - COMMAND ${CMAKE_C_COMPILER} --print-file-name=picolibc.specs - OUTPUT_VARIABLE picolibc_specs_path + COMMAND ${CMAKE_C_COMPILER} --print-file-name=${specs_filename} + OUTPUT_VARIABLE specs_path OUTPUT_STRIP_TRAILING_WHITESPACE ERROR_QUIET ) - if(picolibc_specs_path AND EXISTS "${picolibc_specs_path}") - # Read the original specs file - file(READ "${picolibc_specs_path}" specs_content) + if(specs_path AND EXISTS "${specs_path}") + file(READ "${specs_path}" specs_content) - # Remove --gc-sections flag from the content - string(REGEX REPLACE "--gc-sections" "" specs_content "${specs_content}") + if(modification STREQUAL "remove_gc_sections") + string(REGEX REPLACE "--gc-sections" "" specs_content "${specs_content}") + elseif(modification STREQUAL "add_nosys_to_lib") + set(original_lib_specs [=[ +%{!shared:%{g*:-lg_nano} %{!p:%{!pg:-lc_nano}}%{p:-lc_p}%{pg:-lc_p}} +]=]) + set(modified_lib_specs [=[ +%{!shared:%{g*:-lg_nano} %{!p:%{!pg:-lc_nano -lnosys -lc_nano}}%{p:-lc_p}%{pg:-lc_p}} +]=]) + string(REPLACE "${original_lib_specs}" "${modified_lib_specs}" specs_content "${specs_content}") + endif() - # Write the modified specs file + file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/specs") file(WRITE "${modified_specs_path}" "${specs_content}") else() # Fallback: use original specs if not found - message(WARNING "picolibc.specs not found in toolchain, using default") - set(${out_var} "picolibc.specs" PARENT_SCOPE) + message(WARNING "${specs_filename} not found in toolchain, using default") + set(${out_var} "${specs_filename}" PARENT_SCOPE) endif() endfunction() +# Get picolibc specs option with --gc-sections flag removed +function(get_picolibc_specs_path out_var) + _get_modified_specs_path(specs_path picolibc.specs remove_gc_sections) + set(${out_var} "${specs_path}" PARENT_SCOPE) +endfunction() + +# Get nano specs option with nosys included between libc nano references +function(get_newlib_nano_specs_path out_var) + _get_modified_specs_path(specs_path nano.specs add_nosys_to_lib) + set(${out_var} "${specs_path}" PARENT_SCOPE) +endfunction() + if(CONFIG_IDF_TOOLCHAIN_GCC) if(CONFIG_STDATOMIC_S32C1I_SPIRAM_WORKAROUND) idf_toolchain_add_flags(COMPILE_OPTIONS "-mdisable-hardware-atomics") @@ -55,9 +74,10 @@ if(CONFIG_IDF_TOOLCHAIN_GCC) endif() if(CONFIG_LIBC_NEWLIB_NANO_FORMAT) - idf_toolchain_add_flags(LINK_OPTIONS "--specs=nano.specs") + get_newlib_nano_specs_path(nano_specs_path) + idf_toolchain_add_flags(LINK_OPTIONS "\"--specs=${nano_specs_path}\"") else() - idf_toolchain_remove_flags(LINK_OPTIONS "--specs=nano.specs") + idf_toolchain_remove_flags(LINK_OPTIONS "--specs=.*nano.*") endif() idf_toolchain_rerun_abi_detection() diff --git a/components/esp_partition/test_apps/pytest_esp_partition.py b/components/esp_partition/test_apps/pytest_esp_partition.py index 8c3587ce745..38ad47694a3 100644 --- a/components/esp_partition/test_apps/pytest_esp_partition.py +++ b/components/esp_partition/test_apps/pytest_esp_partition.py @@ -6,6 +6,7 @@ from pytest_embedded_idf.utils import idf_parametrize @pytest.mark.generic +@pytest.mark.flaky(reruns=2, reruns_delay=5) @idf_parametrize('target', ['esp32', 'esp32c3'], indirect=['target']) def test_esp_partition(dut: Dut) -> None: dut.expect_unity_test_output() diff --git a/components/esp_phy/CMakeLists.txt b/components/esp_phy/CMakeLists.txt index f86f3b84913..6830a87e50f 100644 --- a/components/esp_phy/CMakeLists.txt +++ b/components/esp_phy/CMakeLists.txt @@ -196,9 +196,17 @@ if(CONFIG_ESP_PHY_ENABLED) set(phy_name "phy") - esptool_py_flash_target(${phy_name}-flash "${main_args}" "${sub_args}") - esptool_py_flash_target_image(${phy_name}-flash ${phy_name} "${phy_partition_offset}" "${phy_init_data_bin}") - esptool_py_flash_target_image(flash ${phy_name} "${phy_partition_offset}" "${phy_init_data_bin}") + # When the multiple PHY init data bin is embedded into the application image + # (CONFIG_ESP_PHY_MULTIPLE_INIT_DATA_BIN_EMBED=y), the data is already part of + # app.bin and there is no need to flash it again to the `phy` data partition. + # Skip registering it as a flashable image in that case. + if(NOT CONFIG_ESP_PHY_MULTIPLE_INIT_DATA_BIN_EMBED) + esptool_py_flash_target(${phy_name}-flash "${main_args}" "${sub_args}") + esptool_py_flash_target_image(${phy_name}-flash ${phy_name} + "${phy_partition_offset}" "${phy_init_data_bin}") + esptool_py_flash_target_image(flash ${phy_name} + "${phy_partition_offset}" "${phy_init_data_bin}") + endif() endif() endif() diff --git a/components/esp_pm/pm_locks.c b/components/esp_pm/pm_locks.c index 2a422a0ce60..85d54def3f0 100644 --- a/components/esp_pm/pm_locks.c +++ b/components/esp_pm/pm_locks.c @@ -9,6 +9,7 @@ #include #include "esp_pm.h" #include "esp_system.h" +#include "esp_heap_caps.h" #include "sys/queue.h" #include "freertos/FreeRTOS.h" #include "esp_private/pm_impl.h" @@ -56,7 +57,7 @@ esp_err_t esp_pm_lock_create(esp_pm_lock_type_t lock_type, int arg, if (out_handle == NULL) { return ESP_ERR_INVALID_ARG; } - esp_pm_lock_t* new_lock = (esp_pm_lock_t*) calloc(1, sizeof(*new_lock)); + esp_pm_lock_t* new_lock = (esp_pm_lock_t*) heap_caps_calloc(1, sizeof(*new_lock), MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT); if (!new_lock) { return ESP_ERR_NO_MEM; } diff --git a/components/esp_psram/Kconfig.spiram.common b/components/esp_psram/Kconfig.spiram.common index 2d6c36831c7..dad17a34447 100644 --- a/components/esp_psram/Kconfig.spiram.common +++ b/components/esp_psram/Kconfig.spiram.common @@ -156,3 +156,42 @@ config SPIRAM_ALLOW_NOINIT_SEG_EXTERNAL_MEMORY Note the values placed into this section will not be initialized at startup and should keep its value after software restart. + +config SPIRAM_ENC_EXEMPT + bool "Reserve a PSRAM region exempt from encryption" + default n + depends on SPIRAM && SOC_PSRAM_ENCRYPTION_PAGE_CONFIGURABLE + help + !!! SECURITY WARNING !!! + + Enabling this option carves out a region at the upper end of PSRAM (highest + physical addresses) that is mapped WITHOUT encryption, even when flash + encryption is enabled. Memory allocated + from this region (via MALLOC_CAP_SPIRAM_NO_ENC) is stored in plaintext in + PSRAM and can be observed by an attacker with physical access to the PSRAM + interface. + + DO NOT enable this unless you have weighed the trade-off: + - You accept that any data placed in this region is unprotected at rest. + - You will only allocate workloads that are not security-sensitive (e.g. + video frame buffers, intermediate codec scratch buffers) into this region. + - You understand that PSRAM-resident TLS state, keys, or other secrets + MUST NOT be allocated with MALLOC_CAP_SPIRAM_NO_ENC. + + Use case: PSRAM encryption imposes alignment constraints on buffers that + cross PSRAM. Some DMA engines (e.g. 2D-DMA) cannot satisfy these alignment + requirements, so DMA into encrypted PSRAM fails. This option lets such + workloads place their buffers in an unencrypted PSRAM region while keeping + the rest of PSRAM (and flash) encrypted. + +config SPIRAM_ENC_EXEMPT_SIZE + int "Size of the unencrypted PSRAM region (KB)" + default 256 + range 64 65536 + depends on SPIRAM_ENC_EXEMPT + help + Size of the PSRAM region carved out at the upper end of PSRAM (highest + physical addresses) and mapped without encryption. Rounded up to a multiple + of the MMU page size (typically 64 KB). + The region is registered as a separate heap pool, accessible only via + MALLOC_CAP_SPIRAM_NO_ENC. diff --git a/components/esp_psram/include/esp_psram.h b/components/esp_psram/include/esp_psram.h index 27c7f7b4a55..562e851d1e9 100644 --- a/components/esp_psram/include/esp_psram.h +++ b/components/esp_psram/include/esp_psram.h @@ -42,6 +42,24 @@ bool esp_psram_is_initialized(void); */ size_t esp_psram_get_size(void); +/** + * @brief Check if the pointer falls inside the unencrypted PSRAM carve-out region + * + * When @c CONFIG_SPIRAM_ENC_EXEMPT is enabled, esp_psram reserves a region of PSRAM + * that is mapped without encryption and exposed through the @c MALLOC_CAP_SPIRAM_NO_ENC + * heap capability. This function lets drivers verify whether a buffer returned by the + * heap allocator actually lives in that unencrypted region — useful for example after + * a @c heap_caps_malloc_prefer() call that may have fallen back to encrypted PSRAM. + * + * @param[in] p The pointer to check + * + * @return + * - true: the pointer is within the unencrypted PSRAM carve-out + * - false: the pointer is not in the carve-out, PSRAM is not initialized, + * or @c CONFIG_SPIRAM_ENC_EXEMPT is disabled + */ +bool esp_psram_ptr_is_no_enc(const void *p); + #ifdef __cplusplus } #endif diff --git a/components/esp_psram/system_layer/esp_psram.c b/components/esp_psram/system_layer/esp_psram.c index 34d5a10c239..8c1c7e29545 100644 --- a/components/esp_psram/system_layer/esp_psram.c +++ b/components/esp_psram/system_layer/esp_psram.c @@ -49,13 +49,19 @@ #define BYTES_TO_MMU_PAGE(bytes) ((bytes) / MMU_PAGE_SIZE) /** - * Two types of PSRAM memory regions for now: + * PSRAM memory regions: * - 8bit aligned * - 32bit aligned + * - Optional: encryption-exempt carve-out (upper end of PSRAM, highest physical addresses) */ -#define PSRAM_MEM_TYPE_NUM 2 #define PSRAM_MEM_8BIT_ALIGNED 0 #define PSRAM_MEM_32BIT_ALIGNED 1 +#if CONFIG_SPIRAM_ENC_EXEMPT +#define PSRAM_MEM_ENC_EXEMPT 2 +#define PSRAM_MEM_TYPE_NUM 3 +#else +#define PSRAM_MEM_TYPE_NUM 2 +#endif #if CONFIG_SPIRAM_FLASH_LOAD_TO_PSRAM #define PSRAM_EARLY_LOGI ESP_DRAM_LOGI @@ -277,6 +283,16 @@ static void s_xip_psram_placement(uint32_t *psram_available_size, uint32_t *out_ static void s_psram_mapping(uint32_t psram_available_size, uint32_t start_page) { esp_err_t ret = ESP_FAIL; +#if CONFIG_SPIRAM_ENC_EXEMPT + size_t enc_exempt_size = ALIGN_UP_BY((size_t)CONFIG_SPIRAM_ENC_EXEMPT_SIZE * 1024, MMU_PAGE_SIZE); + if (enc_exempt_size >= psram_available_size) { + ESP_EARLY_LOGE(TAG, "SPIRAM_ENC_EXEMPT_SIZE (%dKB) >= available PSRAM (%dKB); disabling carve-out", + (int)(enc_exempt_size / 1024), (int)(psram_available_size / 1024)); + enc_exempt_size = 0; + } else { + psram_available_size -= enc_exempt_size; + } +#endif //----------------------------------Map the PSRAM physical range to MMU-----------------------------// /** * @note 2 @@ -366,6 +382,36 @@ static void s_psram_mapping(uint32_t psram_available_size, uint32_t start_page) ESP_EARLY_LOGW(TAG, "Virtual address not enough for PSRAM, map as much as we can. %dMB is mapped", total_mapped_size / 1024 / 1024); } +#if CONFIG_SPIRAM_ENC_EXEMPT + if (enc_exempt_size) { + const void *v_start_no_enc = NULL; + ret = esp_mmu_map_reserve_block_with_caps(enc_exempt_size, + MMU_MEM_CAP_READ | MMU_MEM_CAP_WRITE | MMU_MEM_CAP_8BIT | MMU_MEM_CAP_32BIT, + MMU_TARGET_PSRAM0, &v_start_no_enc); + if (ret != ESP_OK) { + ESP_EARLY_LOGE(TAG, "Virtual address pool exhausted; disabling SPIRAM_ENC_EXEMPT carve-out (%dKB)", + (int)(enc_exempt_size / 1024)); + } else { + mmu_hal_map_region_no_enc((uint32_t)v_start_no_enc, MMU_PAGE_TO_BYTES(start_page), enc_exempt_size); + + cache_bus_mask_t bus_mask = cache_ll_l1_get_bus(0, (uint32_t)v_start_no_enc, enc_exempt_size); + cache_ll_l1_enable_bus(0, bus_mask); +#if !CONFIG_ESP_SYSTEM_SINGLE_CORE_MODE + bus_mask = cache_ll_l1_get_bus(1, (uint32_t)v_start_no_enc, enc_exempt_size); + cache_ll_l1_enable_bus(1, bus_mask); +#endif + + s_psram_ctx.mapped_regions[PSRAM_MEM_ENC_EXEMPT].size = enc_exempt_size; + s_psram_ctx.mapped_regions[PSRAM_MEM_ENC_EXEMPT].vaddr_start = (intptr_t)v_start_no_enc; + s_psram_ctx.mapped_regions[PSRAM_MEM_ENC_EXEMPT].vaddr_end = (intptr_t)v_start_no_enc + enc_exempt_size; + s_psram_ctx.regions_to_heap[PSRAM_MEM_ENC_EXEMPT].size = enc_exempt_size; + s_psram_ctx.regions_to_heap[PSRAM_MEM_ENC_EXEMPT].vaddr_start = (intptr_t)v_start_no_enc; + s_psram_ctx.regions_to_heap[PSRAM_MEM_ENC_EXEMPT].vaddr_end = (intptr_t)v_start_no_enc + enc_exempt_size; + ESP_EARLY_LOGI(TAG, "PSRAM unencrypted region: 0x%x B at %p", (unsigned)enc_exempt_size, v_start_no_enc); + } + } +#endif /* CONFIG_SPIRAM_ENC_EXEMPT */ + /*------------------------------------------------------------------------------ * After mapping, we DON'T care about the PSRAM PHYSICAL ADDRESS ANYMORE! *----------------------------------------------------------------------------*/ @@ -490,6 +536,22 @@ esp_err_t esp_psram_extram_add_to_heap_allocator(void) ESP_EARLY_LOGI(TAG, "Adding pool of %dK of PSRAM memory to heap allocator", (s_psram_ctx.regions_to_heap[PSRAM_MEM_8BIT_ALIGNED].size + s_psram_ctx.regions_to_heap[PSRAM_MEM_32BIT_ALIGNED].size) / 1024); +#if CONFIG_SPIRAM_ENC_EXEMPT + if (s_psram_ctx.regions_to_heap[PSRAM_MEM_ENC_EXEMPT].size) { + // Only MALLOC_CAP_SPIRAM_NO_ENC: any other bit here would let generic 8BIT/32BIT + // allocations fall through to this region as a low-priority match. + uint32_t no_enc_caps[] = {MALLOC_CAP_SPIRAM_NO_ENC, 0, 0}; + ret = heap_caps_add_region_with_caps(no_enc_caps, + s_psram_ctx.regions_to_heap[PSRAM_MEM_ENC_EXEMPT].vaddr_start, + s_psram_ctx.regions_to_heap[PSRAM_MEM_ENC_EXEMPT].vaddr_end); + if (ret != ESP_OK) { + return ret; + } + ESP_EARLY_LOGI(TAG, "Adding pool of %dK of unencrypted PSRAM memory to heap allocator", + (int)(s_psram_ctx.regions_to_heap[PSRAM_MEM_ENC_EXEMPT].size / 1024)); + } +#endif + // To allow using the page alignment gaps created while mapping the flash segments, // the alignment gaps must be configured with correct memory protection configurations. #if CONFIG_SPIRAM_PRE_CONFIGURE_MEMORY_PROTECTION @@ -534,6 +596,13 @@ bool IRAM_ATTR esp_psram_check_ptr_addr(const void *p) return true; } +#if CONFIG_SPIRAM_ENC_EXEMPT + if ((intptr_t)p >= s_psram_ctx.mapped_regions[PSRAM_MEM_ENC_EXEMPT].vaddr_start && + (intptr_t)p < s_psram_ctx.mapped_regions[PSRAM_MEM_ENC_EXEMPT].vaddr_end) { + return true; + } +#endif + #if CONFIG_SPIRAM_RODATA if (mmu_psram_check_ptr_addr_in_xip_psram_rodata_region(p)) { return true; @@ -549,6 +618,21 @@ bool IRAM_ATTR esp_psram_check_ptr_addr(const void *p) return false; } +bool IRAM_ATTR esp_psram_ptr_is_no_enc(const void *p) +{ +#if CONFIG_SPIRAM_ENC_EXEMPT + if (!s_psram_ctx.is_initialised) { + return false; + } + + return ((intptr_t)p >= s_psram_ctx.mapped_regions[PSRAM_MEM_ENC_EXEMPT].vaddr_start && + (intptr_t)p < s_psram_ctx.mapped_regions[PSRAM_MEM_ENC_EXEMPT].vaddr_end); +#else + (void)p; + return false; +#endif +} + esp_err_t esp_psram_extram_reserve_dma_pool(size_t size) { if (size == 0) { @@ -666,6 +750,18 @@ bool esp_psram_extram_test(void) return false; } +#if CONFIG_SPIRAM_ENC_EXEMPT + if (s_psram_ctx.mapped_regions[PSRAM_MEM_ENC_EXEMPT].size) { + test_success = s_test_psram(s_psram_ctx.mapped_regions[PSRAM_MEM_ENC_EXEMPT].vaddr_start, + s_psram_ctx.mapped_regions[PSRAM_MEM_ENC_EXEMPT].size, + 0, + 0); + } + if (!test_success) { + return false; + } +#endif + return true; } @@ -712,7 +808,11 @@ static size_t esp_psram_get_effective_mapped_size(void) } if (s_psram_ctx.is_initialised) { - return s_psram_ctx.mapped_regions[PSRAM_MEM_8BIT_ALIGNED].size + s_psram_ctx.mapped_regions[PSRAM_MEM_32BIT_ALIGNED].size; + size_t mapped = 0; + for (int i = 0; i < PSRAM_MEM_TYPE_NUM; i++) { + mapped += s_psram_ctx.mapped_regions[i].size; + } + return mapped; } else { uint32_t psram_available_size = 0; esp_err_t ret = esp_psram_impl_get_available_size(&psram_available_size); @@ -751,7 +851,11 @@ size_t esp_psram_get_heap_size_to_protect(void) } if (s_psram_ctx.is_initialised) { - return s_psram_ctx.regions_to_heap[PSRAM_MEM_8BIT_ALIGNED].size + s_psram_ctx.regions_to_heap[PSRAM_MEM_32BIT_ALIGNED].size; + size_t heap = 0; + for (int i = 0; i < PSRAM_MEM_TYPE_NUM; i++) { + heap += s_psram_ctx.regions_to_heap[i].size; + } + return heap; } else { size_t effective_mapped_size = esp_psram_get_effective_mapped_size(); if (effective_mapped_size == 0) { diff --git a/components/esp_rom/CMakeLists.txt b/components/esp_rom/CMakeLists.txt index 2bde986745d..a794b549f2f 100644 --- a/components/esp_rom/CMakeLists.txt +++ b/components/esp_rom/CMakeLists.txt @@ -45,7 +45,7 @@ else() endif() endif() - list(APPEND private_required_comp soc hal esp_hal_uart) + list(APPEND private_required_comp soc hal esp_hal_uart esp_hal_security) endif() if(CONFIG_IDF_TARGET_ARCH_XTENSA) @@ -76,6 +76,18 @@ if(CONFIG_SECURE_ENABLE_TEE AND CONFIG_IDF_TARGET_ESP32C5 AND NOT ESP_TEE_BUILD) list(APPEND sources "patches/esp_rom_cache_esp32c5.c") endif() +if(CONFIG_ESP_ROM_CACHE_WRITEBACK_NEEDS_SYNC_TWICE_MAP) + list(APPEND sources "patches/esp_rom_cache_writeback_esp32p4.c") +endif() + +if(CONFIG_ESP_ROM_CACHE_WRITEBACK_NEEDS_SYNC_TWICE_NO_MAP) + list(APPEND sources "patches/esp_rom_cache_writeback_esp32c5_esp32c61_esp32h4.c") +endif() + +if(CONFIG_ESP_ROM_ECDSA_VERIFY_PATCH) + list(APPEND sources "patches/esp_rom_ecdsa.c") +endif() + idf_component_register(SRCS ${sources} INCLUDE_DIRS ${include_dirs} PRIV_REQUIRES ${private_required_comp} @@ -192,11 +204,8 @@ if(BOOTLOADER_BUILD) endif() if(CONFIG_MBEDTLS_USE_CRYPTO_ROM_IMPL_BOOTLOADER) - rom_linker_script("mbedtls") # For ESP32C2(ECO4), mbedTLS in ROM has been updated to v3.6.0-LTS - if(CONFIG_ESP32C2_REV_MIN_FULL GREATER_EQUAL 200) - rom_linker_script("mbedtls.eco4") - endif() + rom_linker_script("mbedtls.eco4") endif() else() # Regular app build @@ -408,11 +417,8 @@ else() # Regular app build endif() if(CONFIG_MBEDTLS_USE_CRYPTO_ROM_IMPL) - rom_linker_script("mbedtls") # For ESP32C2(ECO4), mbedTLS in ROM has been updated to v3.6.0-LTS - if(CONFIG_ESP32C2_REV_MIN_FULL GREATER_EQUAL 200) - rom_linker_script("mbedtls.eco4") - endif() + rom_linker_script("mbedtls.eco4") endif() if(CONFIG_ESP_ROM_DELAY_US_PATCH AND CONFIG_SECURE_ENABLE_TEE AND diff --git a/components/esp_rom/esp32c2/ld/esp32c2.rom.eco4.ld b/components/esp_rom/esp32c2/ld/esp32c2.rom.eco4.ld index a9758072119..6be5b333507 100644 --- a/components/esp_rom/esp32c2/ld/esp32c2.rom.eco4.ld +++ b/components/esp_rom/esp32c2/ld/esp32c2.rom.eco4.ld @@ -81,7 +81,7 @@ ieee80211_ampdu_reorder = 0x40001fb0; ieee80211_encap_esfbuf = 0x40001fb8; ieee80211_output_process = 0x40001fc4; //sta_input = 0x40001fcc; -ieee80211_classify = 0x40001fe0; +//ieee80211_classify = 0x40001fe0; ieee80211_crypto_decap = 0x40001ff8; //ieee80211_ccmp_decrypt = 0x4000200c; //ieee80211_ccmp_encrypt = 0x40002010; diff --git a/components/esp_rom/esp32c2/ld/esp32c2.rom.mbedtls.eco4.ld b/components/esp_rom/esp32c2/ld/esp32c2.rom.mbedtls.eco4.ld index fe893c335a0..842d468c858 100644 --- a/components/esp_rom/esp32c2/ld/esp32c2.rom.mbedtls.eco4.ld +++ b/components/esp_rom/esp32c2/ld/esp32c2.rom.mbedtls.eco4.ld @@ -1,9 +1,99 @@ /* - * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2024-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ +/*************************************** + Group rom_mbedtls + ***************************************/ + +/* Functions */ +mbedtls_aes_init = 0x40002664; +mbedtls_aes_free = 0x40002688; +mbedtls_aes_setkey_enc = 0x4000268c; +mbedtls_aes_setkey_dec = 0x40002690; +mbedtls_aes_crypt_ecb = 0x40002694; +mbedtls_aes_crypt_cbc = 0x40002698; +mbedtls_internal_aes_encrypt = 0x4000269c; +mbedtls_internal_aes_decrypt = 0x400026a0; +mbedtls_asn1_get_len = 0x400026a4; +mbedtls_asn1_get_tag = 0x400026a8; +mbedtls_asn1_get_bool = 0x400026ac; +mbedtls_asn1_get_int = 0x400026b0; +mbedtls_asn1_get_bitstring = 0x400026b4; +mbedtls_asn1_get_bitstring_null = 0x400026b8; +mbedtls_asn1_get_sequence_of = 0x400026bc; +mbedtls_asn1_get_mpi = 0x400026c0; +mbedtls_asn1_get_alg = 0x400026c4; +mbedtls_asn1_get_alg_null = 0x400026c8; +mbedtls_asn1_write_len = 0x400026cc; +mbedtls_asn1_write_tag = 0x400026d0; +mbedtls_asn1_write_mpi = 0x400026d4; +mbedtls_base64_decode = 0x400026d8; +mbedtls_ccm_star_encrypt_and_tag = 0x40002774; +mbedtls_ccm_star_auth_decrypt = 0x40002778; +mbedtls_ctr_drbg_init = 0x4000279c; +mbedtls_ctr_drbg_seed = 0x400027a0; +mbedtls_ctr_drbg_free = 0x400027a4; +mbedtls_ctr_drbg_reseed = 0x400027a8; +mbedtls_ctr_drbg_random_with_add = 0x400027ac; +mbedtls_ctr_drbg_random = 0x400027b0; +mbedtls_sha1_init = 0x40002a1c; +mbedtls_sha1_free = 0x40002a20; +mbedtls_sha1_clone = 0x40002a24; +mbedtls_sha1_starts = 0x40002a28; +mbedtls_sha1_finish = 0x40002a2c; +mbedtls_sha256_init = 0x40002a30; +mbedtls_sha256_free = 0x40002a34; +mbedtls_sha256_clone = 0x40002a38; +mbedtls_sha256_starts = 0x40002a3c; +mbedtls_sha256_finish = 0x40002a40; +mbedtls_sha256 = 0x40002a44; +mbedtls_sha512_init = 0x40002a48; +mbedtls_sha512_free = 0x40002a4c; +mbedtls_sha512_clone = 0x40002a50; +mbedtls_sha512_starts = 0x40002a54; +mbedtls_sha512_update = 0x40002a58; +mbedtls_sha512_finish = 0x40002a5c; +/*mbedtls_internal_sha512_process = 0x40002a60;*/ +mbedtls_sha512 = 0x40002a64; +mbedtls_aes_xts_init = 0x40002b68; +mbedtls_aes_xts_free = 0x40002b6c; +mbedtls_aes_xts_setkey_enc = 0x40002b70; +mbedtls_aes_xts_setkey_dec = 0x40002b74; +mbedtls_aes_crypt_xts = 0x40002b78; +mbedtls_aes_crypt_cfb128 = 0x40002b7c; +mbedtls_aes_crypt_ofb = 0x40002b80; +mbedtls_aes_crypt_ctr = 0x40002b84; +mbedtls_ccm_init = 0x40002b98; +mbedtls_ccm_setkey = 0x40002b9c; +mbedtls_ccm_free = 0x40002ba0; +mbedtls_ccm_encrypt_and_tag = 0x40002ba4; +mbedtls_ccm_auth_decrypt = 0x40002ba8; +mbedtls_md5_init = 0x40002bd8; +mbedtls_md5_free = 0x40002bdc; +mbedtls_md5_clone = 0x40002be0; +mbedtls_md5_starts = 0x40002be4; +mbedtls_md5_update = 0x40002be8; +mbedtls_md5_finish = 0x40002bec; +/*mbedtls_internal_md5_process = 0x40002bf0;*/ +mbedtls_md5 = 0x40002bf4; +mbedtls_sha1 = 0x40002c08; +/* Data (.data, .bss, .rodata) */ +mbedtls_rom_osi_funcs_ptr = 0x3fcdfaa0; +AES_FSb_ptr = 0x3fcdfa9c; +AES_RT0_ptr = 0x3fcdfa98; +AES_RT1_ptr = 0x3fcdfa94; +AES_RT2_ptr = 0x3fcdfa90; +AES_RT3_ptr = 0x3fcdfa8c; +AES_FT0_ptr = 0x3fcdfa88; +AES_FT1_ptr = 0x3fcdfa84; +AES_FT2_ptr = 0x3fcdfa80; +AES_FT3_ptr = 0x3fcdfa7c; +bignum_small_prime_ptr = 0x3fcdfa78; +sha512_K_ptr = 0x3fcdfa74; + /*************************************** Group eco4_rom_mbedtls ***************************************/ @@ -42,21 +132,20 @@ mbedtls_mpi_div_mpi = 0x40002750; mbedtls_mpi_div_int = 0x40002754; mbedtls_mpi_mod_mpi = 0x40002758; mbedtls_mpi_mod_int = 0x4000275c; -mbedtls_mpi_exp_mod = 0x40002760; +/*mbedtls_mpi_exp_mod = 0x40002760;*/ mbedtls_mpi_fill_random = 0x40002764; mbedtls_mpi_gcd = 0x40002768; mbedtls_mpi_inv_mod = 0x4000276c; mbedtls_mpi_is_prime_ext = 0x40002770; -/* Moved from mbedtls.ld to mbedtls.eco4 ld */ -mbedtls_cipher_init = 0x4000277c; -mbedtls_cipher_set_padding_mode = 0x40002780; -mbedtls_cipher_reset = 0x40002784; -mbedtls_cipher_finish = 0x40002788; -mbedtls_cipher_crypt = 0x4000278c; -mbedtls_cipher_cmac_starts = 0x40002790; -mbedtls_cipher_cmac_update = 0x40002794; -mbedtls_cipher_cmac_finish = 0x40002798; +/*mbedtls_cipher_init = 0x4000277c;*/ +/*mbedtls_cipher_set_padding_mode = 0x40002780;*/ +/*mbedtls_cipher_reset = 0x40002784;*/ +/*mbedtls_cipher_finish = 0x40002788;*/ +/*mbedtls_cipher_crypt = 0x4000278c;*/ +/*mbedtls_cipher_cmac_starts = 0x40002790;*/ +/*mbedtls_cipher_cmac_update = 0x40002794;*/ +/*mbedtls_cipher_cmac_finish = 0x40002798;*/ /*************************************** Group eco4_rom_mbedtls @@ -72,51 +161,51 @@ mbedtls_mpi_read_binary_le = 0x40002c20; mbedtls_mpi_write_binary_le = 0x40002c24; mbedtls_mpi_random = 0x40002c28; mbedtls_mpi_gen_prime = 0x40002c2c; -mbedtls_ecp_check_budget = 0x40002c30; -mbedtls_ecp_set_max_ops = 0x40002c34; -mbedtls_ecp_restart_is_enabled = 0x40002c38; +/*mbedtls_ecp_check_budget = 0x40002c30;*/ +/*mbedtls_ecp_set_max_ops = 0x40002c34;*/ +/*mbedtls_ecp_restart_is_enabled = 0x40002c38;*/ mbedtls_ecp_get_type = 0x40002c3c; mbedtls_ecp_curve_list = 0x40002c40; mbedtls_ecp_grp_id_list = 0x40002c44; mbedtls_ecp_curve_info_from_grp_id = 0x40002c48; mbedtls_ecp_curve_info_from_tls_id = 0x40002c4c; mbedtls_ecp_curve_info_from_name = 0x40002c50; -mbedtls_ecp_point_init = 0x40002c54; -mbedtls_ecp_group_init = 0x40002c58; -mbedtls_ecp_keypair_init = 0x40002c5c; -mbedtls_ecp_point_free = 0x40002c60; -mbedtls_ecp_group_free = 0x40002c64; -mbedtls_ecp_keypair_free = 0x40002c68; -mbedtls_ecp_restart_init = 0x40002c6c; -mbedtls_ecp_restart_free = 0x40002c70; -mbedtls_ecp_copy = 0x40002c74; -mbedtls_ecp_group_copy = 0x40002c78; -mbedtls_ecp_set_zero = 0x40002c7c; -mbedtls_ecp_is_zero = 0x40002c80; -mbedtls_ecp_point_cmp = 0x40002c84; -mbedtls_ecp_point_read_string = 0x40002c88; -mbedtls_ecp_point_write_binary = 0x40002c8c; -mbedtls_ecp_point_read_binary = 0x40002c90; -mbedtls_ecp_tls_read_point = 0x40002c94; -mbedtls_ecp_tls_write_point = 0x40002c98; -mbedtls_ecp_group_load = 0x40002c9c; -mbedtls_ecp_tls_read_group = 0x40002ca0; -mbedtls_ecp_tls_read_group_id = 0x40002ca4; -mbedtls_ecp_tls_write_group = 0x40002ca8; -mbedtls_ecp_mul = 0x40002cac; -mbedtls_ecp_mul_restartable = 0x40002cb0; -mbedtls_ecp_muladd = 0x40002cb4; -mbedtls_ecp_muladd_restartable = 0x40002cb8; -mbedtls_ecp_check_pubkey = 0x40002cbc; -mbedtls_ecp_check_privkey = 0x40002cc0; -mbedtls_ecp_gen_privkey = 0x40002cc4; -mbedtls_ecp_gen_keypair_base = 0x40002cc8; -mbedtls_ecp_gen_keypair = 0x40002ccc; -mbedtls_ecp_gen_key = 0x40002cd0; -mbedtls_ecp_read_key = 0x40002cd4; -mbedtls_ecp_write_key_ext = 0x40002cd8; -mbedtls_ecp_check_pub_priv = 0x40002cdc; -mbedtls_ecp_export = 0x40002ce0; +/*mbedtls_ecp_point_init = 0x40002c54;*/ +/*mbedtls_ecp_group_init = 0x40002c58;*/ +/*mbedtls_ecp_keypair_init = 0x40002c5c;*/ +/*mbedtls_ecp_point_free = 0x40002c60;*/ +/*mbedtls_ecp_group_free = 0x40002c64;*/ +/*mbedtls_ecp_keypair_free = 0x40002c68;*/ +/*mbedtls_ecp_restart_init = 0x40002c6c;*/ +/*mbedtls_ecp_restart_free = 0x40002c70;*/ +/*mbedtls_ecp_copy = 0x40002c74;*/ +/*mbedtls_ecp_group_copy = 0x40002c78;*/ +/*mbedtls_ecp_set_zero = 0x40002c7c;*/ +/*mbedtls_ecp_is_zero = 0x40002c80;*/ +/*mbedtls_ecp_point_cmp = 0x40002c84;*/ +/*mbedtls_ecp_point_read_string = 0x40002c88;*/ +/*mbedtls_ecp_point_write_binary = 0x40002c8c;*/ +/*mbedtls_ecp_point_read_binary = 0x40002c90;*/ +/*mbedtls_ecp_tls_read_point = 0x40002c94;*/ +/*mbedtls_ecp_tls_write_point = 0x40002c98;*/ +/*mbedtls_ecp_group_load = 0x40002c9c;*/ +/*mbedtls_ecp_tls_read_group = 0x40002ca0;*/ +/*mbedtls_ecp_tls_read_group_id = 0x40002ca4;*/ +/*mbedtls_ecp_tls_write_group = 0x40002ca8;*/ +/*mbedtls_ecp_mul = 0x40002cac;*/ +/*mbedtls_ecp_mul_restartable = 0x40002cb0;*/ +/*mbedtls_ecp_muladd = 0x40002cb4;*/ +/*mbedtls_ecp_muladd_restartable = 0x40002cb8;*/ +/*mbedtls_ecp_check_pubkey = 0x40002cbc;*/ +/*mbedtls_ecp_check_privkey = 0x40002cc0;*/ +/*mbedtls_ecp_gen_privkey = 0x40002cc4;*/ +/*mbedtls_ecp_gen_keypair_base = 0x40002cc8;*/ +/*mbedtls_ecp_gen_keypair = 0x40002ccc;*/ +/*mbedtls_ecp_gen_key = 0x40002cd0;*/ +/*mbedtls_ecp_read_key = 0x40002cd4;*/ +/*mbedtls_ecp_write_key_ext = 0x40002cd8;*/ +/*mbedtls_ecp_check_pub_priv = 0x40002cdc;*/ +/*mbedtls_ecp_export = 0x40002ce0;*/ mbedtls_asn1_get_enum = 0x40002ce4; mbedtls_asn1_sequence_free = 0x40002ce8; mbedtls_asn1_traverse_sequence_of = 0x40002cec; @@ -166,39 +255,39 @@ mbedtls_ctr_drbg_set_nonce_len = 0x40002d98; mbedtls_ctr_drbg_set_reseed_interval = 0x40002d9c; mbedtls_ctr_drbg_update = 0x40002da0; mbedtls_base64_encode = 0x40002da4; -mbedtls_rsa_init = 0x40002da8; -mbedtls_rsa_set_padding = 0x40002dac; -mbedtls_rsa_get_padding_mode = 0x40002db0; -mbedtls_rsa_get_md_alg = 0x40002db4; -mbedtls_rsa_import = 0x40002db8; -mbedtls_rsa_import_raw = 0x40002dbc; -mbedtls_rsa_complete = 0x40002dc0; -mbedtls_rsa_export = 0x40002dc4; -mbedtls_rsa_export_raw = 0x40002dc8; -mbedtls_rsa_export_crt = 0x40002dcc; -mbedtls_rsa_get_len = 0x40002dd0; -mbedtls_rsa_gen_key = 0x40002dd4; -mbedtls_rsa_check_pubkey = 0x40002dd8; -mbedtls_rsa_check_privkey = 0x40002ddc; -mbedtls_rsa_check_pub_priv = 0x40002de0; -mbedtls_rsa_public = 0x40002de4; -mbedtls_rsa_private = 0x40002de8; -mbedtls_rsa_pkcs1_encrypt = 0x40002dec; -mbedtls_rsa_rsaes_pkcs1_v15_encrypt = 0x40002df0; -mbedtls_rsa_rsaes_oaep_encrypt = 0x40002df4; -mbedtls_rsa_pkcs1_decrypt = 0x40002df8; -mbedtls_rsa_rsaes_pkcs1_v15_decrypt = 0x40002dfc; -mbedtls_rsa_rsaes_oaep_decrypt = 0x40002e00; -mbedtls_rsa_pkcs1_sign = 0x40002e04; -mbedtls_rsa_rsassa_pkcs1_v15_sign = 0x40002e08; -mbedtls_rsa_rsassa_pss_sign_ext = 0x40002e0c; -mbedtls_rsa_rsassa_pss_sign = 0x40002e10; -mbedtls_rsa_pkcs1_verify = 0x40002e14; -mbedtls_rsa_rsassa_pkcs1_v15_verify = 0x40002e18; -mbedtls_rsa_rsassa_pss_verify = 0x40002e1c; -mbedtls_rsa_rsassa_pss_verify_ext = 0x40002e20; -mbedtls_rsa_copy = 0x40002e24; -mbedtls_rsa_free = 0x40002e28; +/*mbedtls_rsa_init = 0x40002da8;*/ +/*mbedtls_rsa_set_padding = 0x40002dac;*/ +/*mbedtls_rsa_get_padding_mode = 0x40002db0;*/ +/*mbedtls_rsa_get_md_alg = 0x40002db4;*/ +/*mbedtls_rsa_import = 0x40002db8;*/ +/*mbedtls_rsa_import_raw = 0x40002dbc;*/ +/*mbedtls_rsa_complete = 0x40002dc0;*/ +/*mbedtls_rsa_export = 0x40002dc4;*/ +/*mbedtls_rsa_export_raw = 0x40002dc8;*/ +/*mbedtls_rsa_export_crt = 0x40002dcc;*/ +/*mbedtls_rsa_get_len = 0x40002dd0;*/ +/*mbedtls_rsa_gen_key = 0x40002dd4;*/ +/*mbedtls_rsa_check_pubkey = 0x40002dd8;*/ +/*mbedtls_rsa_check_privkey = 0x40002ddc;*/ +/*mbedtls_rsa_check_pub_priv = 0x40002de0;*/ +/*mbedtls_rsa_public = 0x40002de4;*/ +/*mbedtls_rsa_private = 0x40002de8;*/ +/*mbedtls_rsa_pkcs1_encrypt = 0x40002dec;*/ +/*mbedtls_rsa_rsaes_pkcs1_v15_encrypt = 0x40002df0;*/ +/*mbedtls_rsa_rsaes_oaep_encrypt = 0x40002df4;*/ +/*mbedtls_rsa_pkcs1_decrypt = 0x40002df8;*/ +/*mbedtls_rsa_rsaes_pkcs1_v15_decrypt = 0x40002dfc;*/ +/*mbedtls_rsa_rsaes_oaep_decrypt = 0x40002e00;*/ +/*mbedtls_rsa_pkcs1_sign = 0x40002e04;*/ +/*mbedtls_rsa_rsassa_pkcs1_v15_sign = 0x40002e08;*/ +/*mbedtls_rsa_rsassa_pss_sign_ext = 0x40002e0c;*/ +/*mbedtls_rsa_rsassa_pss_sign = 0x40002e10;*/ +/*mbedtls_rsa_pkcs1_verify = 0x40002e14;*/ +/*mbedtls_rsa_rsassa_pkcs1_v15_verify = 0x40002e18;*/ +/*mbedtls_rsa_rsassa_pss_verify = 0x40002e1c;*/ +/*mbedtls_rsa_rsassa_pss_verify_ext = 0x40002e20;*/ +/*mbedtls_rsa_copy = 0x40002e24;*/ +/*mbedtls_rsa_free = 0x40002e28;*/ mbedtls_ecdh_can_do = 0x40002e2c; mbedtls_ecdh_gen_public = 0x40002e30; mbedtls_ecdh_compute_shared = 0x40002e34; diff --git a/components/esp_rom/esp32c2/ld/esp32c2.rom.mbedtls.ld b/components/esp_rom/esp32c2/ld/esp32c2.rom.mbedtls.ld deleted file mode 100644 index 20182027d66..00000000000 --- a/components/esp_rom/esp32c2/ld/esp32c2.rom.mbedtls.ld +++ /dev/null @@ -1,105 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD - * - * SPDX-License-Identifier: Apache-2.0 - */ - -/* ROM function interface esp32c2.rom.mbedtls.ld for esp32c2 - * - * - * Generated from ./interface-esp32c2.yml md5sum c679b6ed5e9f0a9c3e7b93e5e0f2a1a3 - * - * Compatible with ROM where ECO version equal or greater to 1. - * - * THIS FILE WAS AUTOMATICALLY GENERATED. DO NOT EDIT. - */ - -/*************************************** - Group rom_mbedtls - ***************************************/ - -/* Functions */ -mbedtls_aes_init = 0x40002664; -mbedtls_aes_free = 0x40002688; -mbedtls_aes_setkey_enc = 0x4000268c; -mbedtls_aes_setkey_dec = 0x40002690; -mbedtls_aes_crypt_ecb = 0x40002694; -mbedtls_aes_crypt_cbc = 0x40002698; -mbedtls_internal_aes_encrypt = 0x4000269c; -mbedtls_internal_aes_decrypt = 0x400026a0; -mbedtls_asn1_get_len = 0x400026a4; -mbedtls_asn1_get_tag = 0x400026a8; -mbedtls_asn1_get_bool = 0x400026ac; -mbedtls_asn1_get_int = 0x400026b0; -mbedtls_asn1_get_bitstring = 0x400026b4; -mbedtls_asn1_get_bitstring_null = 0x400026b8; -mbedtls_asn1_get_sequence_of = 0x400026bc; -mbedtls_asn1_get_mpi = 0x400026c0; -mbedtls_asn1_get_alg = 0x400026c4; -mbedtls_asn1_get_alg_null = 0x400026c8; -mbedtls_asn1_write_len = 0x400026cc; -mbedtls_asn1_write_tag = 0x400026d0; -mbedtls_asn1_write_mpi = 0x400026d4; -mbedtls_base64_decode = 0x400026d8; -mbedtls_ccm_star_encrypt_and_tag = 0x40002774; -mbedtls_ccm_star_auth_decrypt = 0x40002778; -mbedtls_ctr_drbg_init = 0x4000279c; -mbedtls_ctr_drbg_seed = 0x400027a0; -mbedtls_ctr_drbg_free = 0x400027a4; -mbedtls_ctr_drbg_reseed = 0x400027a8; -mbedtls_ctr_drbg_random_with_add = 0x400027ac; -mbedtls_ctr_drbg_random = 0x400027b0; -mbedtls_sha1_init = 0x40002a1c; -mbedtls_sha1_free = 0x40002a20; -mbedtls_sha1_clone = 0x40002a24; -mbedtls_sha1_starts = 0x40002a28; -mbedtls_sha1_finish = 0x40002a2c; -mbedtls_sha256_init = 0x40002a30; -mbedtls_sha256_free = 0x40002a34; -mbedtls_sha256_clone = 0x40002a38; -mbedtls_sha256_starts = 0x40002a3c; -mbedtls_sha256_finish = 0x40002a40; -mbedtls_sha256 = 0x40002a44; -mbedtls_sha512_init = 0x40002a48; -mbedtls_sha512_free = 0x40002a4c; -mbedtls_sha512_clone = 0x40002a50; -mbedtls_sha512_starts = 0x40002a54; -mbedtls_sha512_update = 0x40002a58; -mbedtls_sha512_finish = 0x40002a5c; -/*mbedtls_internal_sha512_process = 0x40002a60;*/ -mbedtls_sha512 = 0x40002a64; -mbedtls_aes_xts_init = 0x40002b68; -mbedtls_aes_xts_free = 0x40002b6c; -mbedtls_aes_xts_setkey_enc = 0x40002b70; -mbedtls_aes_xts_setkey_dec = 0x40002b74; -mbedtls_aes_crypt_xts = 0x40002b78; -mbedtls_aes_crypt_cfb128 = 0x40002b7c; -mbedtls_aes_crypt_ofb = 0x40002b80; -mbedtls_aes_crypt_ctr = 0x40002b84; -mbedtls_ccm_init = 0x40002b98; -mbedtls_ccm_setkey = 0x40002b9c; -mbedtls_ccm_free = 0x40002ba0; -mbedtls_ccm_encrypt_and_tag = 0x40002ba4; -mbedtls_ccm_auth_decrypt = 0x40002ba8; -mbedtls_md5_init = 0x40002bd8; -mbedtls_md5_free = 0x40002bdc; -mbedtls_md5_clone = 0x40002be0; -mbedtls_md5_starts = 0x40002be4; -mbedtls_md5_update = 0x40002be8; -mbedtls_md5_finish = 0x40002bec; -/*mbedtls_internal_md5_process = 0x40002bf0;*/ -mbedtls_md5 = 0x40002bf4; -mbedtls_sha1 = 0x40002c08; -/* Data (.data, .bss, .rodata) */ -mbedtls_rom_osi_funcs_ptr = 0x3fcdfaa0; -AES_FSb_ptr = 0x3fcdfa9c; -AES_RT0_ptr = 0x3fcdfa98; -AES_RT1_ptr = 0x3fcdfa94; -AES_RT2_ptr = 0x3fcdfa90; -AES_RT3_ptr = 0x3fcdfa8c; -AES_FT0_ptr = 0x3fcdfa88; -AES_FT1_ptr = 0x3fcdfa84; -AES_FT2_ptr = 0x3fcdfa80; -AES_FT3_ptr = 0x3fcdfa7c; -bignum_small_prime_ptr = 0x3fcdfa78; -sha512_K_ptr = 0x3fcdfa74; diff --git a/components/esp_rom/esp32c3/ld/esp32c3.rom.eco7_bt_funcs.ld b/components/esp_rom/esp32c3/ld/esp32c3.rom.eco7_bt_funcs.ld index b04334e54bc..d746b0846bd 100644 --- a/components/esp_rom/esp32c3/ld/esp32c3.rom.eco7_bt_funcs.ld +++ b/components/esp_rom/esp32c3/ld/esp32c3.rom.eco7_bt_funcs.ld @@ -21,7 +21,6 @@ r_llc_rem_encrypt_proc_continue_eco = 0x40001cf0; r_lld_ext_adv_dynamic_aux_pti_process_eco = 0x40001cfc; r_lld_adv_start_eco = 0x40001d04; r_lld_con_evt_canceled_cbk_eco = 0x40001d08; -r_lld_con_start_eco = 0x40001d10; r_lld_ext_scan_dynamic_pti_process_eco = 0x40001d28; r_lld_scan_frm_eof_isr_eco = 0x40001d2c; r_lld_sync_start_eco = 0x40001d30; @@ -127,4 +126,5 @@ r_lld_con_tx_eco = 0x40001d18; r_lld_con_evt_time_update_eco = 0x40001d0c; r_lld_con_evt_start_cbk_eco = 0x40001d1c; r_lld_con_frm_isr_eco = 0x40001d14; +r_lld_con_start_eco = 0x40001d10; */ diff --git a/components/esp_rom/esp32c5/Kconfig.soc_caps.in b/components/esp_rom/esp32c5/Kconfig.soc_caps.in index f475909a2a7..f3bf00afd66 100644 --- a/components/esp_rom/esp32c5/Kconfig.soc_caps.in +++ b/components/esp_rom/esp32c5/Kconfig.soc_caps.in @@ -126,3 +126,11 @@ config ESP_ROM_DELAY_US_PATCH config ESP_ROM_SUPPORT_SECURE_BOOT_FAST_WAKEUP bool default y + +config ESP_ROM_ECDSA_VERIFY_PATCH + bool + default y + +config ESP_ROM_CACHE_WRITEBACK_NEEDS_SYNC_TWICE_NO_MAP + bool + default y diff --git a/components/esp_rom/esp32c5/esp_rom_caps.h b/components/esp_rom/esp32c5/esp_rom_caps.h index 1812656eb29..1bb7b29fbae 100644 --- a/components/esp_rom/esp32c5/esp_rom_caps.h +++ b/components/esp_rom/esp32c5/esp_rom_caps.h @@ -37,3 +37,5 @@ #define ESP_ROM_HAS_SUBOPTIMAL_NEWLIB_ON_MISALIGNED_MEMORY (1) // ROM mem/str functions are not optimized well for misaligned memory access. #define ESP_ROM_DELAY_US_PATCH (1) // ROM ets_delay_us needs patch for U-mode operation #define ESP_ROM_SUPPORT_SECURE_BOOT_FAST_WAKEUP (1) // ROM supports the secure boot fast wakeup feature +#define ESP_ROM_ECDSA_VERIFY_PATCH (1) // ROM ets_ecdsa_verify API requires a software patch +#define ESP_ROM_CACHE_WRITEBACK_NEEDS_SYNC_TWICE_NO_MAP (1) // ROM cache writeback related needs patch to avoid sync loss, no map parameter diff --git a/components/esp_rom/esp32c5/include/esp32c5/rom/rtc.h b/components/esp_rom/esp32c5/include/esp32c5/rom/rtc.h index a1cbfccc9ef..4d57760f0f8 100644 --- a/components/esp_rom/esp32c5/include/esp32c5/rom/rtc.h +++ b/components/esp_rom/esp32c5/include/esp32c5/rom/rtc.h @@ -50,8 +50,9 @@ extern "C" { * LP_AON_STORE5_REG FAST_RTC_MEMORY_LENGTH * LP_AON_STORE6_REG FAST_RTC_MEMORY_ENTRY * LP_AON_STORE7_REG FAST_RTC_MEMORY_CRC - * LP_AON_STORE8_REG Store light sleep wake stub addr - * LP_AON_STORE9_REG Store the sleep mode at bit[0] (0:light sleep 1:deep sleep) + * LP_AON_STORE8_REG Store light sleep wake stub addr (mask bit[1:0]) + * LP_AON_STORE8_REG Store the sleep mode at bit[0] (0:light sleep 1:deep sleep) + * LP_AON_STORE9_REG LP core store wakeup cause ************************************************************************************* */ @@ -64,6 +65,7 @@ extern "C" { #define RTC_MEMORY_CRC_REG LP_AON_STORE7_REG #define RTC_SLEEP_WAKE_STUB_ADDR_REG LP_AON_STORE8_REG #define RTC_SLEEP_MODE_REG LP_AON_STORE8_REG +#define RTC_LP_CORE_STORE_WAKEUP_REG LP_AON_STORE9_REG #define RTC_DISABLE_ROM_LOG ((1 << 0) | (1 << 16)) //!< Disable logging from the ROM code. diff --git a/components/esp_rom/esp32c5/ld/esp32c5.rom.eco3.ld b/components/esp_rom/esp32c5/ld/esp32c5.rom.eco3.ld index 71b74082e37..a3fa73a3bd9 100644 --- a/components/esp_rom/esp32c5/ld/esp32c5.rom.eco3.ld +++ b/components/esp_rom/esp32c5/ld/esp32c5.rom.eco3.ld @@ -39,7 +39,7 @@ pm_mac_disable_tsf_tbtt_soc_wakeup = 0x40000e10; pm_mac_enable_tsf_tbtt_soc_wakeup = 0x40000e18; //pm_mac_enable_tsf_tbtt_modem_wakeup = 0x40000e1c; //pm_mac_modem_params_rt_update = 0x40000e20; -ppMapTxQueue = 0x40000e64; +//ppMapTxQueue = 0x40000e64; //ppProcTxSecFrame = 0x40000e68; //ppProcessTxQ = 0x40000e70; ppRxPkt = 0x40000e8c; diff --git a/components/esp_rom/esp32c5/ld/esp32c5.rom.ld b/components/esp_rom/esp32c5/ld/esp32c5.rom.ld index 0a970952198..f7821cf87a3 100644 --- a/components/esp_rom/esp32c5/ld/esp32c5.rom.ld +++ b/components/esp_rom/esp32c5/ld/esp32c5.rom.ld @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -192,12 +192,12 @@ Cache_Sync_Items = 0x40000648; Cache_Op_Addr = 0x4000064c; Cache_Invalidate_Addr = 0x40000650; Cache_Clean_Addr = 0x40000654; -Cache_WriteBack_Addr = 0x40000658; -Cache_WriteBack_Invalidate_Addr = 0x4000065c; +PROVIDE( Cache_WriteBack_Addr = 0x40000658 ); +PROVIDE( Cache_WriteBack_Invalidate_Addr = 0x4000065c ); Cache_Invalidate_All = 0x40000660; Cache_Clean_All = 0x40000664; -Cache_WriteBack_All = 0x40000668; -Cache_WriteBack_Invalidate_All = 0x4000066c; +PROVIDE( Cache_WriteBack_All = 0x40000668 ); +PROVIDE( Cache_WriteBack_Invalidate_All = 0x4000066c ); Cache_Mask_All = 0x40000670; Cache_UnMask_Dram0 = 0x40000674; Cache_Suspend_Autoload = 0x40000678; @@ -412,9 +412,8 @@ esp_rom_km_huk_risk = 0x40000898; /* Functions */ ets_emsa_pss_verify = 0x4000089c; ets_rsa_pss_verify = 0x400008a0; -ets_ecdsa_verify = 0x400008a4; -ets_secure_boot_verify_bootloader_with_keys = 0x400008a8; -ets_secure_boot_verify_signature = 0x400008ac; +_rom_ets_ecdsa_verify = 0x400008a4; +_rom_ets_secure_boot_verify_signature = 0x400008ac; ets_secure_boot_read_key_digests = 0x400008b0; ets_mgf1_sha256 = 0x400008b4; ets_secure_boot_revoke_public_key_digest = 0x400008b8; diff --git a/components/esp_rom/esp32c5/ld/esp32c5.rom.net80211.ld b/components/esp_rom/esp32c5/ld/esp32c5.rom.net80211.ld index a1f9185cb01..80c973a7f63 100644 --- a/components/esp_rom/esp32c5/ld/esp32c5.rom.net80211.ld +++ b/components/esp_rom/esp32c5/ld/esp32c5.rom.net80211.ld @@ -41,7 +41,7 @@ ieee80211_is_tx_allowed = 0x40000b64; ieee80211_output_pending_eb = 0x40000b68; ieee80211_output_process = 0x40000b6c; /*ieee80211_set_tx_desc = 0x40000b70;*/ -ieee80211_classify = 0x40000b74; +//ieee80211_classify = 0x40000b74; ieee80211_copy_eb_header = 0x40000b78; ieee80211_recycle_cache_eb = 0x40000b7c; ieee80211_search_node = 0x40000b80; diff --git a/components/esp_rom/esp32c6/include/esp32c6/rom/rtc.h b/components/esp_rom/esp32c6/include/esp32c6/rom/rtc.h index 70a868df415..a8d34a0ecef 100644 --- a/components/esp_rom/esp32c6/include/esp32c6/rom/rtc.h +++ b/components/esp_rom/esp32c6/include/esp32c6/rom/rtc.h @@ -53,6 +53,7 @@ extern "C" { * LP_AON_STORE7_REG FAST_RTC_MEMORY_CRC * LP_AON_STORE8_REG Store light sleep wake stub addr * LP_AON_STORE9_REG Store the sleep mode at bit[0] (0:light sleep 1:deep sleep) + * LP_AON_STORE9_REG LP core store wakeup cause at bit[31:2] ************************************************************************************* */ @@ -66,6 +67,7 @@ extern "C" { #define RTC_MEMORY_CRC_REG LP_AON_STORE7_REG #define RTC_SLEEP_WAKE_STUB_ADDR_REG LP_AON_STORE8_REG #define RTC_SLEEP_MODE_REG LP_AON_STORE9_REG +#define RTC_LP_CORE_STORE_WAKEUP_REG LP_AON_STORE9_REG #define RTC_DISABLE_ROM_LOG ((1 << 0) | (1 << 16)) //!< Disable logging from the ROM code. diff --git a/components/esp_rom/esp32c61/Kconfig.soc_caps.in b/components/esp_rom/esp32c61/Kconfig.soc_caps.in index de114450fa4..3c44d87e25c 100644 --- a/components/esp_rom/esp32c61/Kconfig.soc_caps.in +++ b/components/esp_rom/esp32c61/Kconfig.soc_caps.in @@ -115,6 +115,14 @@ config ESP_ROM_HAS_SUBOPTIMAL_NEWLIB_ON_MISALIGNED_MEMORY bool default y +config ESP_ROM_ECDSA_VERIFY_PATCH + bool + default y + config ESP_ROM_DELAY_US_PATCH bool default y + +config ESP_ROM_CACHE_WRITEBACK_NEEDS_SYNC_TWICE_NO_MAP + bool + default y diff --git a/components/esp_rom/esp32c61/esp_rom_caps.h b/components/esp_rom/esp32c61/esp_rom_caps.h index 88c28b664dc..a6416c2794c 100644 --- a/components/esp_rom/esp32c61/esp_rom_caps.h +++ b/components/esp_rom/esp32c61/esp_rom_caps.h @@ -34,4 +34,6 @@ #define ESP_ROM_USB_OTG_NUM (-1) // No USB_OTG CDC in the ROM, set -1 for Kconfig usage. #define ESP_ROM_HAS_OUTPUT_PUTC_FUNC (1) // ROM has esp_rom_output_putc (or ets_write_char_uart) #define ESP_ROM_HAS_SUBOPTIMAL_NEWLIB_ON_MISALIGNED_MEMORY (1) // ROM mem/str functions are not optimized well for misaligned memory access. +#define ESP_ROM_ECDSA_VERIFY_PATCH (1) // ROM ets_ecdsa_verify API requires a software patch #define ESP_ROM_DELAY_US_PATCH (1) // ROM ets_delay_us needs patch for U-mode operation +#define ESP_ROM_CACHE_WRITEBACK_NEEDS_SYNC_TWICE_NO_MAP (1) // ROM cache writeback related needs patch to avoid sync loss, no map parameter diff --git a/components/esp_rom/esp32c61/include/esp32c61/rom/cache.h b/components/esp_rom/esp32c61/include/esp32c61/rom/cache.h index 1e0f1baa0e9..2ba42350934 100644 --- a/components/esp_rom/esp32c61/include/esp32c61/rom/cache.h +++ b/components/esp_rom/esp32c61/include/esp32c61/rom/cache.h @@ -49,6 +49,8 @@ typedef enum { CACHE_SYNC_WRITEBACK_INVALIDATE = BIT(3), } cache_sync_t; +#define CACHE_MAP_FLASH_CACHE BIT(4) + typedef enum { CACHE_SIZE_HALF = 0, /*!< 8KB for icache and dcache */ CACHE_SIZE_FULL = 1, /*!< 16KB for icache and dcache */ diff --git a/components/esp_rom/esp32c61/include/esp32c61/rom/efuse.h b/components/esp_rom/esp32c61/include/esp32c61/rom/efuse.h index be4bdf9c797..f394ab59f02 100644 --- a/components/esp_rom/esp32c61/include/esp32c61/rom/efuse.h +++ b/components/esp_rom/esp32c61/include/esp32c61/rom/efuse.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2024-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -29,10 +29,6 @@ typedef enum { ETS_EFUSE_KEY_PURPOSE_USER = 0, ETS_EFUSE_KEY_PURPOSE_RESERVED = 1, ETS_EFUSE_KEY_PURPOSE_XTS_AES_128_KEY = 4, - ETS_EFUSE_KEY_PURPOSE_HMAC_DOWN_ALL = 5, - ETS_EFUSE_KEY_PURPOSE_HMAC_DOWN_JTAG = 6, - ETS_EFUSE_KEY_PURPOSE_HMAC_DOWN_DIGITAL_SIGNATURE = 7, - ETS_EFUSE_KEY_PURPOSE_HMAC_UP = 8, ETS_EFUSE_KEY_PURPOSE_SECURE_BOOT_DIGEST0 = 9, ETS_EFUSE_KEY_PURPOSE_SECURE_BOOT_DIGEST1 = 10, ETS_EFUSE_KEY_PURPOSE_SECURE_BOOT_DIGEST2 = 11, @@ -246,21 +242,6 @@ uint32_t ets_efuse_get_flash_delay_us(void); #define EFUSE_SPICONFIG_RET_SPIHD_SHIFT 24 #define EFUSE_SPICONFIG_RET_SPIHD(ret) (((ret) >> EFUSE_SPICONFIG_RET_SPIHD_SHIFT) & EFUSE_SPICONFIG_RET_SPIHD_MASK) -/** - * @brief Enable JTAG temporarily by writing a JTAG HMAC "key" into - * the JTAG_CTRL registers. - * - * Works if JTAG has been "soft" disabled by burning the EFUSE_SOFT_DIS_JTAG efuse. - * - * Will enable the HMAC module to generate a "downstream" HMAC value from a key already saved in efuse, and then write the JTAG HMAC "key" which will enable JTAG if the two keys match. - * - * @param jtag_hmac_key Pointer to a 32 byte array containing a valid key. Supplied by user. - * @param key_block Index of a key block containing the source for this key. - * - * @return ETS_FAILED if HMAC operation fails or invalid parameter, ETS_OK otherwise. ETS_OK doesn't necessarily mean that JTAG was enabled. - */ -int ets_jtag_enable_temporarily(const uint8_t *jtag_hmac_key, ets_efuse_block_t key_block); - /** * @brief A crc8 algorithm used for MAC addresses in efuse * diff --git a/components/esp_rom/esp32c61/include/esp32c61/rom/rtc.h b/components/esp_rom/esp32c61/include/esp32c61/rom/rtc.h index a9f62e86a9f..a47dc54bb9d 100644 --- a/components/esp_rom/esp32c61/include/esp32c61/rom/rtc.h +++ b/components/esp_rom/esp32c61/include/esp32c61/rom/rtc.h @@ -51,8 +51,8 @@ extern "C" { * LP_AON_STORE5_REG FAST_RTC_MEMORY_LENGTH * LP_AON_STORE6_REG FAST_RTC_MEMORY_ENTRY * LP_AON_STORE7_REG RTC fix us, low 32 bits - * LP_AON_STORE8_REG Store light sleep wake stub addr - * LP_AON_STORE9_REG Store the sleep mode at bit[0] (0:light sleep 1:deep sleep) + * LP_AON_STORE8_REG Store light sleep wake stub addr (mask bit[1:0]) + * LP_AON_STORE8_REG Store the sleep mode at bit[0] (0:light sleep 1:deep sleep) ************************************************************************************* * * Since esp32c61 does not support RTC mem, so use LP_AON store regs to record rtc time: diff --git a/components/esp_rom/esp32c61/ld/esp32c61.rom.eco4.ld b/components/esp_rom/esp32c61/ld/esp32c61.rom.eco4.ld index 81b9ff497b5..4f5a17278ce 100644 --- a/components/esp_rom/esp32c61/ld/esp32c61.rom.eco4.ld +++ b/components/esp_rom/esp32c61/ld/esp32c61.rom.eco4.ld @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -39,7 +39,7 @@ pm_mac_enable_tsf_tbtt_soc_wakeup = 0x40000d84; //pm_mac_enable_tsf_tbtt_modem_wakeup = 0x40000d88; //pm_mac_modem_params_rt_update = 0x40000d8c; pm_coex_pwr_update = 0x40000d9c; -ppMapTxQueue = 0x40000ddc; +//ppMapTxQueue = 0x40000ddc; ppProcTxSecFrame = 0x40000de0; ppProcessTxQ = 0x40000de8; ppRxPkt = 0x40000e04; @@ -89,10 +89,10 @@ phy_reg_init = 0x400010e8; phy_xpd_rf = 0x400010ec; phy_get_mac_addr = 0x400010f4; phy_set_mac_data = 0x400010f8; -phy_rfcal_data_sub = 0x400010fc; -phy_rf_cal_data_recovery = 0x40001100; -phy_rf_cal_data_backup = 0x40001104; -phy_rfcal_data_check = 0x40001108; +//phy_rfcal_data_sub = 0x400010fc; +//phy_rf_cal_data_recovery = 0x40001100; +//phy_rf_cal_data_backup = 0x40001104; +//phy_rfcal_data_check = 0x40001108; phy_pwdet_reg_init = 0x4000110c; phy_pwdet_sar2_init = 0x40001110; phy_en_pwdet = 0x40001114; diff --git a/components/esp_rom/esp32c61/ld/esp32c61.rom.ld b/components/esp_rom/esp32c61/ld/esp32c61.rom.ld index f0cde7e9106..ad5d692bb44 100644 --- a/components/esp_rom/esp32c61/ld/esp32c61.rom.ld +++ b/components/esp_rom/esp32c61/ld/esp32c61.rom.ld @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2024-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -188,12 +188,12 @@ Cache_Sync_Items = 0x4000062c; Cache_Op_Addr = 0x40000630; Cache_Invalidate_Addr = 0x40000634; Cache_Clean_Addr = 0x40000638; -Cache_WriteBack_Addr = 0x4000063c; -Cache_WriteBack_Invalidate_Addr = 0x40000640; +PROVIDE( Cache_WriteBack_Addr = 0x4000063c ); +PROVIDE( Cache_WriteBack_Invalidate_Addr = 0x40000640 ); Cache_Invalidate_All = 0x40000644; Cache_Clean_All = 0x40000648; -Cache_WriteBack_All = 0x4000064c; -Cache_WriteBack_Invalidate_All = 0x40000650; +PROVIDE( Cache_WriteBack_All = 0x4000064c ); +PROVIDE( Cache_WriteBack_Invalidate_All = 0x40000650 ); Cache_Mask_All = 0x40000654; Cache_UnMask_Dram0 = 0x40000658; Cache_Suspend_Autoload = 0x4000065c; @@ -370,9 +370,8 @@ ets_efuse_usb_device_disabled = 0x40000808; ***************************************/ /* Functions */ -ets_ecdsa_verify = 0x40000810; -ets_secure_boot_verify_bootloader_with_keys = 0x40000814; -ets_secure_boot_verify_signature = 0x40000818; +_rom_ets_ecdsa_verify = 0x40000810; +_rom_ets_secure_boot_verify_signature = 0x40000818; ets_secure_boot_read_key_digests = 0x4000081c; ets_secure_boot_revoke_public_key_digest = 0x40000820; diff --git a/components/esp_rom/esp32c61/ld/esp32c61.rom.net80211.ld b/components/esp_rom/esp32c61/ld/esp32c61.rom.net80211.ld index 2aeaab617f8..2a2b423a460 100644 --- a/components/esp_rom/esp32c61/ld/esp32c61.rom.net80211.ld +++ b/components/esp_rom/esp32c61/ld/esp32c61.rom.net80211.ld @@ -37,7 +37,7 @@ ieee80211_is_tx_allowed = 0x40000acc; ieee80211_output_pending_eb = 0x40000ad0; ieee80211_output_process = 0x40000ad4; /*ieee80211_set_tx_desc = 0x40000ad8;*/ -ieee80211_classify = 0x40000adc; +//ieee80211_classify = 0x40000adc; ieee80211_copy_eb_header = 0x40000ae0; ieee80211_recycle_cache_eb = 0x40000ae4; ieee80211_search_node = 0x40000ae8; diff --git a/components/esp_rom/esp32h2/Kconfig.soc_caps.in b/components/esp_rom/esp32h2/Kconfig.soc_caps.in index 57dae80a537..cf17ea1c169 100644 --- a/components/esp_rom/esp32h2/Kconfig.soc_caps.in +++ b/components/esp_rom/esp32h2/Kconfig.soc_caps.in @@ -122,3 +122,7 @@ config ESP_ROM_HAS_SUBOPTIMAL_NEWLIB_ON_MISALIGNED_MEMORY config ESP_ROM_SUPPORT_SECURE_BOOT_FAST_WAKEUP bool default y + +config ESP_ROM_ECDSA_VERIFY_PATCH + bool + default y diff --git a/components/esp_rom/esp32h2/esp_rom_caps.h b/components/esp_rom/esp32h2/esp_rom_caps.h index cb9475c9ea1..df62c8741b1 100644 --- a/components/esp_rom/esp32h2/esp_rom_caps.h +++ b/components/esp_rom/esp32h2/esp_rom_caps.h @@ -36,3 +36,4 @@ #define ESP_ROM_NO_USB_SERIAL_OUTPUT_API (1) // ROM does not export the usb-serial-jtag write char function #define ESP_ROM_HAS_SUBOPTIMAL_NEWLIB_ON_MISALIGNED_MEMORY (1) // ROM mem/str functions are not optimized well for misaligned memory access. #define ESP_ROM_SUPPORT_SECURE_BOOT_FAST_WAKEUP (1) // ROM supports the secure boot fast wakeup feature +#define ESP_ROM_ECDSA_VERIFY_PATCH (1) // ROM ets_ecdsa_verify API requires a software patch diff --git a/components/esp_rom/esp32h2/ld/esp32h2.rom.ld b/components/esp_rom/esp32h2/ld/esp32h2.rom.ld index 854b22fb94d..47690d2af98 100644 --- a/components/esp_rom/esp32h2/ld/esp32h2.rom.ld +++ b/components/esp_rom/esp32h2/ld/esp32h2.rom.ld @@ -361,9 +361,8 @@ ets_efuse_secure_boot_fast_wake_enabled = 0x40000830; /* Functions */ ets_emsa_pss_verify = 0x40000834; ets_rsa_pss_verify = 0x40000838; -ets_ecdsa_verify = 0x4000083c; -ets_secure_boot_verify_bootloader_with_keys = 0x40000840; -ets_secure_boot_verify_signature = 0x40000844; +_rom_ets_ecdsa_verify = 0x4000083c; +_rom_ets_secure_boot_verify_signature = 0x40000844; ets_secure_boot_read_key_digests = 0x40000848; ets_secure_boot_revoke_public_key_digest = 0x4000084c; diff --git a/components/esp_rom/esp32h4/Kconfig.soc_caps.in b/components/esp_rom/esp32h4/Kconfig.soc_caps.in index 98589e1c651..0110f4b59f5 100644 --- a/components/esp_rom/esp32h4/Kconfig.soc_caps.in +++ b/components/esp_rom/esp32h4/Kconfig.soc_caps.in @@ -78,3 +78,7 @@ config ESP_ROM_WDT_INIT_PATCH config ESP_ROM_RAM_APP_NEEDS_MMU_INIT bool default y + +config ESP_ROM_CACHE_WRITEBACK_NEEDS_SYNC_TWICE_NO_MAP + bool + default y diff --git a/components/esp_rom/esp32h4/esp_rom_caps.h b/components/esp_rom/esp32h4/esp_rom_caps.h index eab18937769..3ff7bb70390 100644 --- a/components/esp_rom/esp32h4/esp_rom_caps.h +++ b/components/esp_rom/esp32h4/esp_rom_caps.h @@ -25,3 +25,4 @@ #define ESP_ROM_USB_OTG_NUM (-1) // No USB_OTG CDC in the ROM, set -1 for Kconfig usage. #define ESP_ROM_WDT_INIT_PATCH (1) // ROM version does not configure the clock #define ESP_ROM_RAM_APP_NEEDS_MMU_INIT (1) // ROM doesn't init cache MMU when it's a RAM APP, needs MMU hal to init +#define ESP_ROM_CACHE_WRITEBACK_NEEDS_SYNC_TWICE_NO_MAP (1) // ROM cache writeback related needs patch to avoid sync loss, no map parameter diff --git a/components/esp_rom/esp32h4/include/esp32h4/rom/rtc.h b/components/esp_rom/esp32h4/include/esp32h4/rom/rtc.h index 2157a2f54f8..a1c44c49730 100644 --- a/components/esp_rom/esp32h4/include/esp32h4/rom/rtc.h +++ b/components/esp_rom/esp32h4/include/esp32h4/rom/rtc.h @@ -50,8 +50,8 @@ extern "C" { * LP_AON_STORE5_REG FAST_RTC_MEMORY_LENGTH * LP_AON_STORE6_REG FAST_RTC_MEMORY_ENTRY * LP_AON_STORE7_REG RTC fix us, low 32 bits - * LP_AON_STORE8_REG Store light sleep wake stub addr - * LP_AON_STORE9_REG Store the sleep mode at bit[0] (0:light sleep 1:deep sleep) + * LP_AON_STORE8_REG Store light sleep wake stub addr (mask bit[1:0]) + * LP_AON_STORE8_REG Store the sleep mode at bit[0] (0:light sleep 1:deep sleep) ************************************************************************************* * * Since esp32h4 does not support RTC mem, so use LP_AON store regs to record rtc time: @@ -70,7 +70,7 @@ extern "C" { #define RTC_RESET_CAUSE_REG LP_AON_STORE6_REG #define RTC_FIX_US_LOW_REG LP_AON_STORE7_REG #define RTC_SLEEP_WAKE_STUB_ADDR_REG LP_AON_STORE8_REG -#define RTC_SLEEP_MODE_REG LP_AON_STORE9_REG +#define RTC_SLEEP_MODE_REG LP_AON_STORE8_REG #define RTC_DISABLE_ROM_LOG ((1 << 0) | (1 << 16)) //!< Disable logging from the ROM code. diff --git a/components/esp_rom/esp32h4/ld/esp32h4.rom.ld b/components/esp_rom/esp32h4/ld/esp32h4.rom.ld index bd8486d5d2b..78a1b8252b5 100644 --- a/components/esp_rom/esp32h4/ld/esp32h4.rom.ld +++ b/components/esp_rom/esp32h4/ld/esp32h4.rom.ld @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -182,12 +182,12 @@ Cache_Sync_Items = 0x400005b8; Cache_Op_Addr = 0x400005bc; Cache_Invalidate_Addr = 0x400005c0; Cache_Clean_Addr = 0x400005c4; -Cache_WriteBack_Addr = 0x400005c8; -Cache_WriteBack_Invalidate_Addr = 0x400005cc; +PROVIDE( Cache_WriteBack_Addr = 0x400005c8 ); +PROVIDE( Cache_WriteBack_Invalidate_Addr = 0x400005cc ); Cache_Invalidate_All = 0x400005d0; Cache_Clean_All = 0x400005d4; -Cache_WriteBack_All = 0x400005d8; -Cache_WriteBack_Invalidate_All = 0x400005dc; +PROVIDE( Cache_WriteBack_All = 0x400005d8 ); +PROVIDE( Cache_WriteBack_Invalidate_All = 0x400005dc ); Cache_Mask_All = 0x400005e0; Cache_UnMask_Dram0 = 0x400005e4; Cache_Suspend_Autoload = 0x400005e8; @@ -385,7 +385,7 @@ esp_rom_recover_key = 0x400007cc; ***************************************/ /* Functions */ -ets_ecdsa_verify = 0x400007d0; +_rom_ets_ecdsa_verify = 0x400007d0; ets_secure_boot_verify_bootloader_with_keys = 0x400007d4; ets_secure_boot_verify_signature = 0x400007d8; ets_secure_boot_read_key_digests = 0x400007dc; diff --git a/components/esp_rom/esp32p4/Kconfig.soc_caps.in b/components/esp_rom/esp32p4/Kconfig.soc_caps.in index 84c2a0f9bd0..48941ba4ac5 100644 --- a/components/esp_rom/esp32p4/Kconfig.soc_caps.in +++ b/components/esp_rom/esp32p4/Kconfig.soc_caps.in @@ -91,6 +91,14 @@ config ESP_ROM_HAS_SUBOPTIMAL_NEWLIB_ON_MISALIGNED_MEMORY bool default y +config ESP_ROM_ECDSA_VERIFY_PATCH + bool + default y + config ESP_ROM_BOOTLOADER_OFFSET_FLASH hex default 0x2000 + +config ESP_ROM_CACHE_WRITEBACK_NEEDS_SYNC_TWICE_MAP + bool + default y diff --git a/components/esp_rom/esp32p4/esp_rom_caps.h b/components/esp_rom/esp32p4/esp_rom_caps.h index 8092c48704b..5c58cd309ee 100644 --- a/components/esp_rom/esp32p4/esp_rom_caps.h +++ b/components/esp_rom/esp32p4/esp_rom_caps.h @@ -28,4 +28,6 @@ #define ESP_ROM_CLIC_INT_TYPE_PATCH (1) // ROM api esprv_intc_int_set_type configuring edge type interrupt (old revisions) #define ESP_ROM_HAS_OUTPUT_PUTC_FUNC (1) // ROM has esp_rom_output_putc (or ets_write_char_uart) #define ESP_ROM_HAS_SUBOPTIMAL_NEWLIB_ON_MISALIGNED_MEMORY (1) // ROM mem/str functions are not optimized well for misaligned memory access. +#define ESP_ROM_ECDSA_VERIFY_PATCH (1) // ROM ets_ecdsa_verify API requires a software patch #define ESP_ROM_BOOTLOADER_OFFSET_FLASH (0x2000) // Bootloader offset in flash determined by the ROM bootloader +#define ESP_ROM_CACHE_WRITEBACK_NEEDS_SYNC_TWICE_MAP (1) // ROM cache writeback related needs patch to avoid sync loss, need map parameter diff --git a/components/esp_rom/esp32p4/include/esp32p4/rom/cache.h b/components/esp_rom/esp32p4/include/esp32p4/rom/cache.h index 0c5ba82533b..8fbc22de785 100644 --- a/components/esp_rom/esp32p4/include/esp32p4/rom/cache.h +++ b/components/esp_rom/esp32p4/include/esp32p4/rom/cache.h @@ -233,6 +233,7 @@ typedef enum { #define CACHE_MAP_L1_ICACHE_MASK (CACHE_MAP_L1_ICACHE_0 | CACHE_MAP_L1_ICACHE_1) #define CACHE_MAP_L1_CACHE_MASK (CACHE_MAP_L1_ICACHE_MASK | CACHE_MAP_L1_DCACHE) #define CACHE_MAP_MASK (CACHE_MAP_L1_ICACHE_MASK | CACHE_MAP_L1_DCACHE | CACHE_MAP_L2_CACHE) +#define CACHE_MAP_DCACHE_MASK (CACHE_MAP_L1_DCACHE | CACHE_MAP_L2_CACHE) struct cache_internal_stub_table { uint32_t (*l1_icache_line_size)(void); diff --git a/components/esp_rom/esp32p4/include/esp32p4/rom/rtc.h b/components/esp_rom/esp32p4/include/esp32p4/rom/rtc.h index 6eccaa9ba5c..d3c39b0e1ee 100644 --- a/components/esp_rom/esp32p4/include/esp32p4/rom/rtc.h +++ b/components/esp_rom/esp32p4/include/esp32p4/rom/rtc.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2023-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -53,17 +53,19 @@ extern "C" { * LP_SYS_LP_STORE8_REG sleep mode and wake stub address * LP_SYS_LP_STORE9_REG LP_UART_INIT_CTRL * LP_SYS_LP_STORE10_REG LP_ROM_LOG_CTRL + * LP_SYS_LP_STORE11_REG LP core store wakeup cause ************************************************************************************* */ -#define RTC_SLOW_CLK_CAL_REG LP_SYSTEM_REG_LP_STORE1_REG -#define RTC_BOOT_TIME_LOW_REG LP_SYSTEM_REG_LP_STORE2_REG -#define RTC_BOOT_TIME_HIGH_REG LP_SYSTEM_REG_LP_STORE3_REG -#define RTC_XTAL_FREQ_REG LP_SYSTEM_REG_LP_STORE4_REG -#define RTC_APB_FREQ_REG LP_SYSTEM_REG_LP_STORE5_REG -#define RTC_ENTRY_ADDR_REG LP_SYSTEM_REG_LP_STORE6_REG -#define RTC_RESET_CAUSE_REG LP_SYSTEM_REG_LP_STORE6_REG -#define RTC_MEMORY_CRC_REG LP_SYSTEM_REG_LP_STORE7_REG +#define RTC_SLOW_CLK_CAL_REG LP_SYSTEM_REG_LP_STORE1_REG +#define RTC_BOOT_TIME_LOW_REG LP_SYSTEM_REG_LP_STORE2_REG +#define RTC_BOOT_TIME_HIGH_REG LP_SYSTEM_REG_LP_STORE3_REG +#define RTC_XTAL_FREQ_REG LP_SYSTEM_REG_LP_STORE4_REG +#define RTC_APB_FREQ_REG LP_SYSTEM_REG_LP_STORE5_REG +#define RTC_ENTRY_ADDR_REG LP_SYSTEM_REG_LP_STORE6_REG +#define RTC_RESET_CAUSE_REG LP_SYSTEM_REG_LP_STORE6_REG +#define RTC_MEMORY_CRC_REG LP_SYSTEM_REG_LP_STORE7_REG +#define RTC_LP_CORE_STORE_WAKEUP_REG LP_SYSTEM_REG_LP_STORE11_REG #define RTC_DISABLE_ROM_LOG ((1 << 0) | (1 << 16)) //!< Disable logging from the ROM code. diff --git a/components/esp_rom/esp32p4/ld/esp32p4.rom.eco5.ld b/components/esp_rom/esp32p4/ld/esp32p4.rom.eco5.ld index eb227ead1d7..db0c692dc14 100644 --- a/components/esp_rom/esp32p4/ld/esp32p4.rom.eco5.ld +++ b/components/esp_rom/esp32p4/ld/esp32p4.rom.eco5.ld @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -187,17 +187,17 @@ Cache_Invalidate_Addr = 0x4fc003e4; Cache_Invalidate_Addr_Gid = 0x4fc003e8; Cache_Clean_Addr = 0x4fc003ec; Cache_Clean_Addr_Gid = 0x4fc003f0; -Cache_WriteBack_Addr = 0x4fc003f4; +PROVIDE( Cache_WriteBack_Addr = 0x4fc003f4 ); Cache_WriteBack_Addr_Gid = 0x4fc003f8; -Cache_WriteBack_Invalidate_Addr = 0x4fc003fc; +PROVIDE( Cache_WriteBack_Invalidate_Addr = 0x4fc003fc ); Cache_WriteBack_Invalidate_Addr_Gid = 0x4fc00400; Cache_Invalidate_All = 0x4fc00404; Cache_Invalidate_All_Gid = 0x4fc00408; Cache_Clean_All = 0x4fc0040c; Cache_Clean_All_Gid = 0x4fc00410; -Cache_WriteBack_All = 0x4fc00414; +PROVIDE( Cache_WriteBack_All = 0x4fc00414 ); Cache_WriteBack_All_Gid = 0x4fc00418; -Cache_WriteBack_Invalidate_All = 0x4fc0041c; +PROVIDE( Cache_WriteBack_Invalidate_All = 0x4fc0041c ); Cache_WriteBack_Invalidate_All_Gid = 0x4fc00420; Cache_Mask_All = 0x4fc00424; Cache_Suspend_L1_CORE0_ICache_Autoload = 0x4fc00428; @@ -448,9 +448,8 @@ esp_rom_km_huk_risk = 0x4fc00710; /* Functions */ ets_emsa_pss_verify = 0x4fc00714; ets_rsa_pss_verify = 0x4fc00718; -ets_ecdsa_verify = 0x4fc0071c; -ets_secure_boot_verify_bootloader_with_keys = 0x4fc00720; -ets_secure_boot_verify_signature = 0x4fc00724; +_rom_ets_ecdsa_verify = 0x4fc0071c; +_rom_ets_secure_boot_verify_signature = 0x4fc00724; ets_secure_boot_read_key_digests = 0x4fc00728; ets_secure_boot_revoke_public_key_digest = 0x4fc0072c; diff --git a/components/esp_rom/esp32p4/ld/esp32p4.rom.ld b/components/esp_rom/esp32p4/ld/esp32p4.rom.ld index 428f076109b..1f8d230238b 100644 --- a/components/esp_rom/esp32p4/ld/esp32p4.rom.ld +++ b/components/esp_rom/esp32p4/ld/esp32p4.rom.ld @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2023-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2023-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -187,17 +187,17 @@ Cache_Invalidate_Addr = 0x4fc003e4; Cache_Invalidate_Addr_Gid = 0x4fc003e8; Cache_Clean_Addr = 0x4fc003ec; Cache_Clean_Addr_Gid = 0x4fc003f0; -Cache_WriteBack_Addr = 0x4fc003f4; +PROVIDE( Cache_WriteBack_Addr = 0x4fc003f4 ); Cache_WriteBack_Addr_Gid = 0x4fc003f8; -Cache_WriteBack_Invalidate_Addr = 0x4fc003fc; +PROVIDE( Cache_WriteBack_Invalidate_Addr = 0x4fc003fc ); Cache_WriteBack_Invalidate_Addr_Gid = 0x4fc00400; Cache_Invalidate_All = 0x4fc00404; Cache_Invalidate_All_Gid = 0x4fc00408; Cache_Clean_All = 0x4fc0040c; Cache_Clean_All_Gid = 0x4fc00410; -Cache_WriteBack_All = 0x4fc00414; +PROVIDE( Cache_WriteBack_All = 0x4fc00414 ); Cache_WriteBack_All_Gid = 0x4fc00418; -Cache_WriteBack_Invalidate_All = 0x4fc0041c; +PROVIDE( Cache_WriteBack_Invalidate_All = 0x4fc0041c ); Cache_WriteBack_Invalidate_All_Gid = 0x4fc00420; Cache_Mask_All = 0x4fc00424; Cache_Suspend_L1_CORE0_ICache_Autoload = 0x4fc00428; @@ -450,9 +450,8 @@ esp_rom_km_huk_risk = 0x4fc0071c; /* Functions */ ets_emsa_pss_verify = 0x4fc00720; ets_rsa_pss_verify = 0x4fc00724; -ets_ecdsa_verify = 0x4fc00728; -ets_secure_boot_verify_bootloader_with_keys = 0x4fc0072c; -ets_secure_boot_verify_signature = 0x4fc00730; +_rom_ets_ecdsa_verify = 0x4fc00728; +_rom_ets_secure_boot_verify_signature = 0x4fc00730; ets_secure_boot_read_key_digests = 0x4fc00734; ets_secure_boot_revoke_public_key_digest = 0x4fc00738; diff --git a/components/esp_rom/linker.lf b/components/esp_rom/linker.lf index e0c09c44e8f..d1e7ec88cc2 100644 --- a/components/esp_rom/linker.lf +++ b/components/esp_rom/linker.lf @@ -11,3 +11,7 @@ entries: esp_rom_cache_writeback_esp32s3 (noflash) if SOC_SYSTIMER_SUPPORTED = y: esp_rom_systimer (noflash) + if ESP_ROM_CACHE_WRITEBACK_NEEDS_SYNC_TWICE_MAP = y: + esp_rom_cache_writeback_esp32p4 (noflash) + if ESP_ROM_CACHE_WRITEBACK_NEEDS_SYNC_TWICE_NO_MAP = y: + esp_rom_cache_writeback_esp32c5_esp32c61_esp32h4 (noflash) diff --git a/components/esp_rom/patches/esp_rom_cache_writeback_esp32c5_esp32c61_esp32h4.c b/components/esp_rom/patches/esp_rom_cache_writeback_esp32c5_esp32c61_esp32h4.c new file mode 100644 index 00000000000..3c6051ae1c7 --- /dev/null +++ b/components/esp_rom/patches/esp_rom_cache_writeback_esp32c5_esp32c61_esp32h4.c @@ -0,0 +1,80 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include "soc/cache_reg.h" +#include "rom/cache.h" + +// esp32c5, esp32c61 and esp32h4 do not need msp parameters in apis. +int Cache_WriteBack_Addr(uint32_t addr, uint32_t size) +{ + uint32_t plus; + uint32_t cache_line_size = MIN_CACHE_LINE_SIZE; + + plus = addr & (cache_line_size - 1); + addr -= plus; + size += plus; + size = (size + cache_line_size - 1) & ~(cache_line_size - 1); + + REG_WRITE(CACHE_SYNC_MAP_REG, CACHE_MAP_FLASH_CACHE); + REG_WRITE(CACHE_SYNC_ADDR_REG, addr); + REG_WRITE(CACHE_SYNC_SIZE_REG, size); + REG_WRITE(CACHE_SYNC_CTRL_REG, CACHE_WRITEBACK_ENA); + while (!REG_GET_BIT(CACHE_SYNC_CTRL_REG, CACHE_SYNC_DONE)); + + REG_WRITE(CACHE_SYNC_CTRL_REG, CACHE_WRITEBACK_ENA); + while (!REG_GET_BIT(CACHE_SYNC_CTRL_REG, CACHE_SYNC_DONE)); + + return 0; +} + +#ifndef BOOTLOADER_BUILD +void Cache_WriteBack_All(void) +{ + REG_WRITE(CACHE_SYNC_MAP_REG, CACHE_MAP_FLASH_CACHE); + REG_WRITE(CACHE_SYNC_ADDR_REG, 0); + REG_WRITE(CACHE_SYNC_SIZE_REG, 0); + REG_WRITE(CACHE_SYNC_CTRL_REG, CACHE_WRITEBACK_ENA); + while (!REG_GET_BIT(CACHE_SYNC_CTRL_REG, CACHE_SYNC_DONE)); + + REG_WRITE(CACHE_SYNC_CTRL_REG, CACHE_WRITEBACK_ENA); + while (!REG_GET_BIT(CACHE_SYNC_CTRL_REG, CACHE_SYNC_DONE)); +} +#endif + +int Cache_WriteBack_Invalidate_Addr(uint32_t addr, uint32_t size) +{ + uint32_t plus; + uint32_t cache_line_size = MIN_CACHE_LINE_SIZE; + + plus = addr & (cache_line_size - 1); + addr -= plus; + size += plus; + size = (size + cache_line_size - 1) & ~(cache_line_size - 1); + + REG_WRITE(CACHE_SYNC_MAP_REG, CACHE_MAP_FLASH_CACHE); + REG_WRITE(CACHE_SYNC_ADDR_REG, addr); + REG_WRITE(CACHE_SYNC_SIZE_REG, size); + REG_WRITE(CACHE_SYNC_CTRL_REG, CACHE_WRITEBACK_INVALIDATE_ENA); + while (!REG_GET_BIT(CACHE_SYNC_CTRL_REG, CACHE_SYNC_DONE)); + + REG_WRITE(CACHE_SYNC_CTRL_REG, CACHE_WRITEBACK_INVALIDATE_ENA); + while (!REG_GET_BIT(CACHE_SYNC_CTRL_REG, CACHE_SYNC_DONE)); + + return 0; +} + +void Cache_WriteBack_Invalidate_All(void) +{ + REG_WRITE(CACHE_SYNC_MAP_REG, CACHE_MAP_FLASH_CACHE); + REG_WRITE(CACHE_SYNC_ADDR_REG, 0); + REG_WRITE(CACHE_SYNC_SIZE_REG, 0); + REG_WRITE(CACHE_SYNC_CTRL_REG, CACHE_WRITEBACK_INVALIDATE_ENA); + while (!REG_GET_BIT(CACHE_SYNC_CTRL_REG, CACHE_SYNC_DONE)); + + REG_WRITE(CACHE_SYNC_CTRL_REG, CACHE_WRITEBACK_INVALIDATE_ENA); + while (!REG_GET_BIT(CACHE_SYNC_CTRL_REG, CACHE_SYNC_DONE)); +} diff --git a/components/esp_rom/patches/esp_rom_cache_writeback_esp32p4.c b/components/esp_rom/patches/esp_rom_cache_writeback_esp32p4.c new file mode 100644 index 00000000000..cc67fa4f194 --- /dev/null +++ b/components/esp_rom/patches/esp_rom_cache_writeback_esp32p4.c @@ -0,0 +1,137 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include "soc/cache_reg.h" +#include "rom/cache.h" +#include "esp_attr.h" +#include "esp_rom_sys.h" + +// esp32p4 needs msp parameters in apis. +int Cache_WriteBack_Addr(uint32_t map, uint32_t addr, uint32_t size) +{ + uint32_t plus; + uint32_t cache_line_size = 0; + /* writeback readonly cache is invalid */ + if (map & CACHE_MAP_L1_ICACHE_MASK) { + return ESP_ROM_ERR_INVALID_ARG; + } + + if ((map & CACHE_MAP_DCACHE_MASK) == 0) { + return ESP_ROM_ERR_INVALID_ARG; + } + + /* esp32p4 will check l2 cache */ + if (map & CACHE_MAP_L1_DCACHE) { + cache_line_size = rom_cache_internal_table_ptr->l1_dcache_line_size(); + } + if (map & CACHE_MAP_L2_CACHE) { + cache_line_size = (cache_line_size > rom_cache_internal_table_ptr->l2_cache_line_size()) ? + cache_line_size : rom_cache_internal_table_ptr->l2_cache_line_size(); + } + + plus = addr & (cache_line_size - 1); + addr -= plus; + size += plus; + size = (size + cache_line_size - 1) & ~(cache_line_size - 1); + REG_WRITE(CACHE_SYNC_MAP_REG, map); + REG_WRITE(CACHE_SYNC_ADDR_REG, addr); + REG_WRITE(CACHE_SYNC_SIZE_REG, size); + REG_WRITE(CACHE_SYNC_CTRL_REG, CACHE_WRITEBACK_ENA); + while (!REG_GET_BIT(CACHE_SYNC_CTRL_REG, CACHE_SYNC_DONE)); + + REG_WRITE(CACHE_SYNC_CTRL_REG, CACHE_WRITEBACK_ENA); + while (!REG_GET_BIT(CACHE_SYNC_CTRL_REG, CACHE_SYNC_DONE)); + + return 0; +} + +#ifndef BOOTLOADER_BUILD +int Cache_WriteBack_All(uint32_t map) +{ + /* writeback readonly cache is invalid */ + if (map & CACHE_MAP_L1_ICACHE_MASK) { + return ESP_ROM_ERR_INVALID_ARG; + } + + if ((map & CACHE_MAP_DCACHE_MASK) == 0) { + return ESP_ROM_ERR_INVALID_ARG; + } + + REG_WRITE(CACHE_SYNC_MAP_REG, map); + REG_WRITE(CACHE_SYNC_ADDR_REG, 0); + REG_WRITE(CACHE_SYNC_SIZE_REG, 0); + REG_WRITE(CACHE_SYNC_CTRL_REG, CACHE_WRITEBACK_ENA); + while (!REG_GET_BIT(CACHE_SYNC_CTRL_REG, CACHE_SYNC_DONE)); + + REG_WRITE(CACHE_SYNC_CTRL_REG, CACHE_WRITEBACK_ENA); + while (!REG_GET_BIT(CACHE_SYNC_CTRL_REG, CACHE_SYNC_DONE)); + + return 0; +} +#endif + +int Cache_WriteBack_Invalidate_Addr(uint32_t map, uint32_t addr, uint32_t size) +{ + uint32_t plus; + uint32_t cache_line_size = 0; + /* writeback readonly cache is invalid */ + if (map & CACHE_MAP_L1_ICACHE_MASK) { + return ESP_ROM_ERR_INVALID_ARG; + } + + if ((map & CACHE_MAP_DCACHE_MASK) == 0) { + return ESP_ROM_ERR_INVALID_ARG; + } + + /* esp32p4 will check l2 cache */ + if (map & CACHE_MAP_L1_DCACHE) { + cache_line_size = rom_cache_internal_table_ptr->l1_dcache_line_size(); + } + if (map & CACHE_MAP_L2_CACHE) { + cache_line_size = (cache_line_size > rom_cache_internal_table_ptr->l2_cache_line_size()) ? + cache_line_size : rom_cache_internal_table_ptr->l2_cache_line_size(); + } + + plus = addr & (cache_line_size - 1); + addr -= plus; + size += plus; + size = (size + cache_line_size - 1) & ~(cache_line_size - 1); + + REG_WRITE(CACHE_SYNC_MAP_REG, map); + REG_WRITE(CACHE_SYNC_ADDR_REG, addr); + REG_WRITE(CACHE_SYNC_SIZE_REG, size); + REG_WRITE(CACHE_SYNC_CTRL_REG, CACHE_WRITEBACK_INVALIDATE_ENA); + while (!REG_GET_BIT(CACHE_SYNC_CTRL_REG, CACHE_SYNC_DONE)); + + REG_WRITE(CACHE_SYNC_CTRL_REG, CACHE_WRITEBACK_INVALIDATE_ENA); + while (!REG_GET_BIT(CACHE_SYNC_CTRL_REG, CACHE_SYNC_DONE)); + + return 0; +} + +int Cache_WriteBack_Invalidate_All(uint32_t map) +{ + /* writeback readonly cache is invalid */ + if (map & CACHE_MAP_L1_ICACHE_MASK) { + return ESP_ROM_ERR_INVALID_ARG; + } + + if ((map & CACHE_MAP_DCACHE_MASK) == 0) { + return ESP_ROM_ERR_INVALID_ARG; + } + + REG_WRITE(CACHE_SYNC_MAP_REG, map); + REG_WRITE(CACHE_SYNC_ADDR_REG, 0); + REG_WRITE(CACHE_SYNC_SIZE_REG, 0); + REG_WRITE(CACHE_SYNC_CTRL_REG, CACHE_WRITEBACK_INVALIDATE_ENA); + while (!REG_GET_BIT(CACHE_SYNC_CTRL_REG, CACHE_SYNC_DONE)); + + REG_WRITE(CACHE_SYNC_CTRL_REG, CACHE_WRITEBACK_INVALIDATE_ENA); + while (!REG_GET_BIT(CACHE_SYNC_CTRL_REG, CACHE_SYNC_DONE)); + + return 0; +} diff --git a/components/esp_rom/patches/esp_rom_ecdsa.c b/components/esp_rom/patches/esp_rom_ecdsa.c new file mode 100644 index 00000000000..e967c994eeb --- /dev/null +++ b/components/esp_rom/patches/esp_rom_ecdsa.c @@ -0,0 +1,231 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include + +#include "sdkconfig.h" +#include "esp_rom_caps.h" +#if ESP_ROM_ECDSA_VERIFY_PATCH +#include "soc/soc_caps.h" +#include "esp_fault.h" +#include "hal/ecc_ll.h" +#include "rom/ecdsa.h" + +#define VALID_MAGIC_OK 0x6A6A6A6AU +#define VALID_MAGIC_FAIL 0x95959595U + +static const uint32_t ecdsa_n_p192[6] = { + 0xb4d22831U, 0x146bc9b1U, 0x99def836U, 0xffffffffU, 0xffffffffU, 0xffffffffU, +}; + +static const uint32_t ecdsa_n_p256[8] = { + 0xfc632551U, 0xf3b9cac2U, 0xa7179e84U, 0xbce6faadU, 0xffffffffU, 0xffffffffU, 0x00000000U, 0xffffffffU, +}; + +#if SOC_ECDSA_SUPPORT_CURVE_P384 +static const uint32_t ecdsa_n_p384[12] = { + 0xccc52973U, 0xecec196aU, 0x48b0a77aU, 0x581a0db2U, 0xf4372ddfU, 0xc7634d81U, + 0xffffffffU, 0xffffffffU, 0xffffffffU, 0xffffffffU, 0xffffffffU, 0xffffffffU, +}; +#endif + +static uint32_t ecdsa_mpi_isZero(const uint32_t *mpi, int num_words) +{ + uint32_t bits = 0; + for (int i = 0; i < num_words; ++i) { + bits |= mpi[i]; + } + return (bits == 0); +} + +static int ecdsa_mpi_cmp_unsafe(const uint32_t *left, const uint32_t *right, int num_words) +{ + for (int i = num_words - 1; i >= 0; --i) { + if (left[i] > right[i]) { + return 1; + } else if (left[i] < right[i]) { + return -1; + } + } + return 0; +} + +static bool ecdsa_scalars_in_range(const uint32_t *r, const uint32_t *s, const uint32_t *n, int num_words, uint32_t *result) +{ + volatile uint32_t verdict = VALID_MAGIC_FAIL; + if (ecdsa_mpi_isZero(r, num_words) == 0 && ecdsa_mpi_cmp_unsafe(n, r, num_words) == 1 && ecdsa_mpi_isZero(s, num_words) == 0 + && ecdsa_mpi_cmp_unsafe(n, s, num_words) == 1) { + verdict = VALID_MAGIC_OK; + } + if (verdict != VALID_MAGIC_OK) { + return false; + } + ESP_FAULT_ASSERT(verdict == VALID_MAGIC_OK); + + *result = VALID_MAGIC_OK; + return true; +} + +// TODO: IDF-15721 +/* + * Runtime gate that decides whether the ROM ECDSA verification routines + * (ets_ecdsa_verify / ets_secure_boot_verify_signature) need the software patch + * in this file, or whether the ROM implementation is safe to call directly. + * + * When a future revision of one of these chips ships a ROM with these ECDSA + * verification issues fixed, add a ROM-version check here (e.g. compare the + * _rom_eco_version symbol against the first fixed ROM ECO version for that + * target) and return false for the fixed ROMs, so they skip the patch and jump + * straight to the _rom_ routine. + */ + +extern int _rom_ets_ecdsa_verify(const uint8_t *key, const uint8_t *sig, + ECDSA_CURVE curve_id, const uint8_t *image_digest, + uint8_t *verified_digest); + +int ets_ecdsa_verify(const uint8_t *key, const uint8_t *sig, + ECDSA_CURVE curve_id, const uint8_t *image_digest, + uint8_t *verified_digest) +{ + int words; + int bytes; + const uint32_t *n; + + if (curve_id == ECDSA_CURVE_P256) { + words = 8; + bytes = 32; + n = ecdsa_n_p256; + } +#if SOC_ECDSA_SUPPORT_CURVE_P384 + else if (curve_id == ECDSA_CURVE_P384) { + words = 12; + bytes = 48; + n = ecdsa_n_p384; + } +#endif + else { + // curve_id == ECDSA_CURVE_P192 + words = 6; + bytes = 24; + n = ecdsa_n_p192; + } + + uint32_t r[12] = { 0 }; + uint32_t s[12] = { 0 }; + memcpy(r, &sig[0], bytes); + memcpy(s, &sig[bytes], bytes); + + uint32_t ret_status = VALID_MAGIC_FAIL; + bool ok = ecdsa_scalars_in_range(r, s, n, words, &ret_status); + if (!ok || ret_status != VALID_MAGIC_OK) { + return 0; + } + + ESP_FAULT_ASSERT(ok && ret_status == VALID_MAGIC_OK); + + ecc_ll_power_up(); + ESP_FAULT_ASSERT(ecc_ll_mem_force_pd_is_clear()); + + int ret = _rom_ets_ecdsa_verify(key, sig, curve_id, image_digest, verified_digest); + + if (ret == 1) { + ESP_FAULT_ASSERT(ret_status == VALID_MAGIC_OK); + int sig_diff = (memcmp(r, &sig[0], bytes) | memcmp(s, &sig[bytes], bytes)); + ESP_FAULT_ASSERT(sig_diff == 0); + ESP_FAULT_ASSERT(ret == 1); + return ret; + } + + return 0; +} + +#if CONFIG_SECURE_BOOT_V2_ENABLED || CONFIG_SECURE_SIGNED_APPS_NO_SECURE_BOOT +#include "rom/secure_boot.h" + +#if CONFIG_SECURE_SIGNED_APPS_ECDSA_V2_SCHEME +static bool esp_rom_ecdsa_scalars_in_range(const uint8_t *r_le, const uint8_t *s_le, size_t component_len) +{ + const uint32_t *n; + int words; + switch (component_len) { + case 24: n = ecdsa_n_p192; words = 6; break; + case 32: n = ecdsa_n_p256; words = 8; break; +#if SOC_ECDSA_SUPPORT_CURVE_P384 + case 48: n = ecdsa_n_p384; words = 12; break; +#endif + default: return false; + } + + uint32_t r[12] = { 0 }; + uint32_t s[12] = { 0 }; + memcpy(r, r_le, component_len); + memcpy(s, s_le, component_len); + + uint32_t result = VALID_MAGIC_FAIL; + bool ok = ecdsa_scalars_in_range(r, s, n, words, &result); + if (!ok || result != VALID_MAGIC_OK) { + return false; + } + ESP_FAULT_ASSERT(ok && result == VALID_MAGIC_OK); + return true; +} + +static bool esp_rom_ecdsa_sig_block_in_range(const ets_secure_boot_sig_block_t *block) +{ + if (block->magic_byte != ETS_SECURE_BOOT_V2_SIGNATURE_MAGIC) { + return true; + } + size_t component_len; + switch (block->ecdsa.key.curve_id) { + case ECDSA_CURVE_P256: component_len = 32; break; +#if SOC_ECDSA_SUPPORT_CURVE_P384 + case ECDSA_CURVE_P384: component_len = 48; break; +#endif + default: return false; + } + return esp_rom_ecdsa_scalars_in_range(&block->ecdsa.signature[0], + &block->ecdsa.signature[component_len], + component_len); +} +#endif /* CONFIG_SECURE_SIGNED_APPS_ECDSA_V2_SCHEME */ + +extern ets_secure_boot_status_t _rom_ets_secure_boot_verify_signature(const ets_secure_boot_signature_t *sig, + const uint8_t *image_digest, + const ets_secure_boot_key_digests_t *trusted_keys, + uint8_t *verified_digest); + +ets_secure_boot_status_t ets_secure_boot_verify_signature(const ets_secure_boot_signature_t *sig, + const uint8_t *image_digest, + const ets_secure_boot_key_digests_t *trusted_keys, + uint8_t *verified_digest) +{ +#if CONFIG_SECURE_SIGNED_APPS_ECDSA_V2_SCHEME + volatile ets_secure_boot_status_t range_status = SB_FAILED; + unsigned blocks_in_range = 0; + for (unsigned i = 0; i < SECURE_BOOT_NUM_BLOCKS; i++) { + if (esp_rom_ecdsa_sig_block_in_range(&sig->block[i])) { + blocks_in_range++; + } + } + if (blocks_in_range == SECURE_BOOT_NUM_BLOCKS) { + range_status = SB_SUCCESS; + } + if (range_status != SB_SUCCESS) { + return SB_FAILED; + } + ESP_FAULT_ASSERT(range_status == SB_SUCCESS); + ESP_FAULT_ASSERT(blocks_in_range == SECURE_BOOT_NUM_BLOCKS); + + ecc_ll_power_up(); + ESP_FAULT_ASSERT(ecc_ll_mem_force_pd_is_clear()); +#endif /* CONFIG_SECURE_SIGNED_APPS_ECDSA_V2_SCHEME */ + return _rom_ets_secure_boot_verify_signature(sig, image_digest, trusted_keys, verified_digest); +} +#endif /* CONFIG_SECURE_BOOT_V2_ENABLED || CONFIG_SECURE_SIGNED_APPS_NO_SECURE_BOOT */ +#endif /* ESP_ROM_ECDSA_VERIFY_PATCH */ diff --git a/components/esp_system/Kconfig b/components/esp_system/Kconfig index 5288080acc8..fa46a259767 100644 --- a/components/esp_system/Kconfig +++ b/components/esp_system/Kconfig @@ -279,8 +279,8 @@ menu "ESP System Settings" config ESP_INT_WDT_TIMEOUT_MS int "Interrupt watchdog timeout (ms)" depends on ESP_INT_WDT - default 300 if !(SPIRAM && IDF_TARGET_ESP32) - default 800 if (SPIRAM && IDF_TARGET_ESP32) + default 300 if !SPIRAM + default 800 if SPIRAM range 10 10000 help The timeout of the watchdog, in milliseconds. Make this higher than the FreeRTOS tick rate. diff --git a/components/esp_system/include/esp_private/esp_int_wdt.h b/components/esp_system/include/esp_private/esp_int_wdt.h index 8c2305142ff..7166b2e2f05 100644 --- a/components/esp_system/include/esp_private/esp_int_wdt.h +++ b/components/esp_system/include/esp_private/esp_int_wdt.h @@ -36,7 +36,7 @@ void esp_int_wdt_init(void); */ void esp_int_wdt_cpu_init(void); -#if CONFIG_ESP32_ECO3_CACHE_LOCK_FIX +#if CONFIG_ESP_INT_WDT && CONFIG_ESP32_ECO3_CACHE_LOCK_FIX /** * @brief Reconfigure WDT stage timeouts (ticks). * diff --git a/components/esp_system/int_wdt.c b/components/esp_system/int_wdt.c index 7f532fdcaba..3bf22010654 100644 --- a/components/esp_system/int_wdt.c +++ b/components/esp_system/int_wdt.c @@ -29,7 +29,7 @@ #include "esp_private/sleep_retention.h" #endif -#if CONFIG_ESP32_ECO3_CACHE_LOCK_FIX +#if CONFIG_ESP_INT_WDT && CONFIG_ESP32_ECO3_CACHE_LOCK_FIX #include "esp_private/eco3_livelock_workaround.h" #endif @@ -87,23 +87,22 @@ static esp_err_t esp_int_wdt_retention_enable(uint32_t group_id) #endif static wdt_hal_context_t iwdt_context; -static portMUX_TYPE s_iwdt_configure_lock = portMUX_INITIALIZER_UNLOCKED; - static void ESP_SYSTEM_IRAM_ATTR reconfigure_ticks(uint32_t stage0_ticks, uint32_t stage1_ticks) { - portENTER_CRITICAL_SAFE(&s_iwdt_configure_lock); wdt_hal_write_protect_disable(&iwdt_context); wdt_hal_config_stage(&iwdt_context, WDT_STAGE0, stage0_ticks, WDT_STAGE_ACTION_INT); wdt_hal_config_stage(&iwdt_context, WDT_STAGE1, stage1_ticks, WDT_STAGE_ACTION_RESET_SYSTEM); wdt_hal_feed(&iwdt_context); wdt_hal_write_protect_enable(&iwdt_context); - portEXIT_CRITICAL_SAFE(&s_iwdt_configure_lock); } -#if CONFIG_ESP32_ECO3_CACHE_LOCK_FIX +#if CONFIG_ESP_INT_WDT && CONFIG_ESP32_ECO3_CACHE_LOCK_FIX +static portMUX_TYPE s_iwdt_configure_lock = portMUX_INITIALIZER_UNLOCKED; void ESP_SYSTEM_IRAM_ATTR esp_int_wdt_reconfigure_ticks(uint32_t stage0_ticks, uint32_t stage1_ticks) { + portENTER_CRITICAL_SAFE(&s_iwdt_configure_lock); reconfigure_ticks(stage0_ticks, stage1_ticks); + portEXIT_CRITICAL_SAFE(&s_iwdt_configure_lock); } #endif @@ -126,9 +125,9 @@ static void ESP_SYSTEM_IRAM_ATTR tick_hook(void) return; } #endif -#if CONFIG_ESP32_ECO3_CACHE_LOCK_FIX +#if CONFIG_ESP_INT_WDT && CONFIG_ESP32_ECO3_CACHE_LOCK_FIX esp_int_wdt_set_livelock_params(CONFIG_ESP_INT_WDT_TIMEOUT_MS); - reconfigure_ticks(esp_int_wdt_livelock_get_feed_stage0_ticks(), IWDT_STAGE1_TIMEOUT_US); + esp_int_wdt_reconfigure_ticks(esp_int_wdt_livelock_get_feed_stage0_ticks(), IWDT_STAGE1_TIMEOUT_US); #else reconfigure_ticks(IWDT_STAGE0_TIMEOUT_US, IWDT_STAGE1_TIMEOUT_US); #endif @@ -158,7 +157,7 @@ void esp_int_wdt_init(void) esp_int_wdt_retention_enable(IWDT_TIMER_GROUP); #endif -#if CONFIG_ESP32_ECO3_CACHE_LOCK_FIX +#if CONFIG_ESP_INT_WDT && CONFIG_ESP32_ECO3_CACHE_LOCK_FIX /* * This is a workaround for issue WDT-3.15 in "ESP32 ECO and workarounds for * Bugs" document. diff --git a/components/esp_system/ld/esp32p4/memory.ld.in b/components/esp_system/ld/esp32p4/memory.ld.in index 78770994f71..6ce7e651499 100644 --- a/components/esp_system/ld/esp32p4/memory.ld.in +++ b/components/esp_system/ld/esp32p4/memory.ld.in @@ -17,11 +17,11 @@ #if !CONFIG_ESP32P4_SELECTS_REV_LESS_V3 #define SRAM_START 0x4FF00000 + CONFIG_CACHE_L2_CACHE_SIZE -#define SRAM_END 0x4FFAEFC0 /* 2nd stage bootloader iram_loader_seg start address */ +#define SRAM_END 0x4FFADFC0 /* 2nd stage bootloader iram_loader_seg start address */ #define SRAM_SIZE SRAM_END - SRAM_START #else #define SRAM_LOW_START 0x4FF00000 -#define SRAM_LOW_END 0x4FF2CBD0 /* 2nd stage bootloader iram_loader_seg start address */ +#define SRAM_LOW_END 0x4FF2BBD0 /* 2nd stage bootloader iram_loader_seg start address */ #define SRAM_LOW_SIZE SRAM_LOW_END - SRAM_LOW_START /* If the cache size is less than 512KB, then there is a region of RAM diff --git a/components/esp_system/port/include/private/esp_private/eco3_livelock_workaround.h b/components/esp_system/port/include/private/esp_private/eco3_livelock_workaround.h index 3c92cdb9034..8df0eea32a0 100644 --- a/components/esp_system/port/include/private/esp_private/eco3_livelock_workaround.h +++ b/components/esp_system/port/include/private/esp_private/eco3_livelock_workaround.h @@ -14,7 +14,7 @@ extern "C" { #endif -#if CONFIG_ESP32_ECO3_CACHE_LOCK_FIX +#if CONFIG_ESP_INT_WDT && CONFIG_ESP32_ECO3_CACHE_LOCK_FIX /** * @brief Enable or disable the livelock workaround and reconfigure the interrupt WDT. @@ -49,7 +49,7 @@ void esp_int_wdt_set_livelock_params(uint32_t timeout_ms); */ void esp_int_wdt_reset_livelock_params(void); -#endif // CONFIG_ESP32_ECO3_CACHE_LOCK_FIX +#endif // (CONFIG_ESP_INT_WDT && CONFIG_ESP32_ECO3_CACHE_LOCK_FIX) #ifdef __cplusplus } diff --git a/components/esp_system/port/soc/esp32/CMakeLists.txt b/components/esp_system/port/soc/esp32/CMakeLists.txt index f6e8c890292..a9d2e82bc34 100644 --- a/components/esp_system/port/soc/esp32/CMakeLists.txt +++ b/components/esp_system/port/soc/esp32/CMakeLists.txt @@ -4,7 +4,7 @@ set(srcs "highint_hdl.S" "system_internal.c" "cache_err_int.c") -if(CONFIG_ESP32_ECO3_CACHE_LOCK_FIX) +if(CONFIG_ESP32_ECO3_CACHE_LOCK_FIX AND CONFIG_ESP_INT_WDT) list(APPEND srcs "eco3_livelock_workaround.c") endif() diff --git a/components/esp_system/port/soc/esp32/eco3_livelock_workaround.c b/components/esp_system/port/soc/esp32/eco3_livelock_workaround.c index 8305df7ff6b..4b4a71b0725 100644 --- a/components/esp_system/port/soc/esp32/eco3_livelock_workaround.c +++ b/components/esp_system/port/soc/esp32/eco3_livelock_workaround.c @@ -45,6 +45,9 @@ uint32_t ESP_SYSTEM_IRAM_ATTR esp_int_wdt_livelock_get_feed_stage0_ticks(void) void ESP_SYSTEM_IRAM_ATTR esp_int_wdt_livelock_workaround(bool enable) { + if (!soc_has_cache_lock_bug()) { + return; + } uint32_t stage0_ticks = IWDT_STAGE0_TIMEOUT_US; if (enable) { esp_int_wdt_set_livelock_params(CONFIG_ESP_INT_WDT_TIMEOUT_MS); diff --git a/components/esp_system/port/soc/esp32c5/clk.c b/components/esp_system/port/soc/esp32c5/clk.c index 55b80fc5219..c1691be2f87 100644 --- a/components/esp_system/port/soc/esp32c5/clk.c +++ b/components/esp_system/port/soc/esp32c5/clk.c @@ -251,5 +251,15 @@ __attribute__((weak)) void esp_perip_clk_init(void) clk_gate_config.disable_pvt_clk = true; #endif +#if defined(CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG) && CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG + /* ESP32-C5 rev <= 1.0: Do not disable UART0 sclk when USB Serial/JTAG is primary console. + * Disabling it would cause the chip to end in infinite loop on reset (workaround for rom code issue). + * See: IDFGH-17050 + */ + if (efuse_hal_chip_revision() <= 100) { + clk_gate_config.disable_uart0_clk = false; + } +#endif + periph_ll_clk_gate_set_default(rst_reason, &clk_gate_config); } diff --git a/components/esp_system/port/soc/esp32c5/system_internal.c b/components/esp_system/port/soc/esp32c5/system_internal.c index 4567cfe9913..ea641db79f1 100644 --- a/components/esp_system/port/soc/esp32c5/system_internal.c +++ b/components/esp_system/port/soc/esp32c5/system_internal.c @@ -84,20 +84,24 @@ void esp_system_reset_modules_on_exit(void) // all the peripherals are reset at the same time, which triggers a hardware SEC reset. The SEC reset // causes the crypto -> APB path to be reset, but the APB -> crypto path is not reset. This asymmetry // results in the crypto module hanging and refusing all access. +#if !CONFIG_SECURE_ENABLE_TEE + // Avoid resetting the TEE-protected crypto peripherals as it would lead to an APM fault SET_PERI_REG_MASK(PCR_AES_CONF_REG, PCR_AES_RST_EN); CLEAR_PERI_REG_MASK(PCR_AES_CONF_REG, PCR_AES_RST_EN); SET_PERI_REG_MASK(PCR_DS_CONF_REG, PCR_DS_RST_EN); CLEAR_PERI_REG_MASK(PCR_DS_CONF_REG, PCR_DS_RST_EN); SET_PERI_REG_MASK(PCR_ECC_CONF_REG, PCR_ECC_RST_EN); CLEAR_PERI_REG_MASK(PCR_ECC_CONF_REG, PCR_ECC_RST_EN); - SET_PERI_REG_MASK(PCR_ECDSA_CONF_REG, PCR_ECDSA_RST_EN); - CLEAR_PERI_REG_MASK(PCR_ECDSA_CONF_REG, PCR_ECDSA_RST_EN); SET_PERI_REG_MASK(PCR_HMAC_CONF_REG, PCR_HMAC_RST_EN); CLEAR_PERI_REG_MASK(PCR_HMAC_CONF_REG, PCR_HMAC_RST_EN); - SET_PERI_REG_MASK(PCR_RSA_CONF_REG, PCR_RSA_RST_EN); - CLEAR_PERI_REG_MASK(PCR_RSA_CONF_REG, PCR_RSA_RST_EN); SET_PERI_REG_MASK(PCR_SHA_CONF_REG, PCR_SHA_RST_EN); CLEAR_PERI_REG_MASK(PCR_SHA_CONF_REG, PCR_SHA_RST_EN); + CLEAR_PERI_REG_MASK(PCR_ECC_PD_CTRL_REG, PCR_ECC_MEM_FORCE_PD); +#endif // !CONFIG_SECURE_ENABLE_TEE + SET_PERI_REG_MASK(PCR_ECDSA_CONF_REG, PCR_ECDSA_RST_EN); + CLEAR_PERI_REG_MASK(PCR_ECDSA_CONF_REG, PCR_ECDSA_RST_EN); + SET_PERI_REG_MASK(PCR_RSA_CONF_REG, PCR_RSA_RST_EN); + CLEAR_PERI_REG_MASK(PCR_RSA_CONF_REG, PCR_RSA_RST_EN); // UART's sclk is controlled in the PCR register and does not reset with the UART module. The ROM missed enabling // it when initializing the ROM UART. If it is not turned on, it will trigger LP_WDT in the ROM. diff --git a/components/esp_system/port/soc/esp32c6/system_internal.c b/components/esp_system/port/soc/esp32c6/system_internal.c index a05a35c2ebd..34a802a85ff 100644 --- a/components/esp_system/port/soc/esp32c6/system_internal.c +++ b/components/esp_system/port/soc/esp32c6/system_internal.c @@ -68,18 +68,23 @@ void esp_system_reset_modules_on_exit(void) // Reset crypto peripherals. This ensures a clean state for the crypto peripherals after a CPU restart // and hence avoiding any possibility with crypto failure in ROM security workflows. +#if !CONFIG_SECURE_ENABLE_TEE + // Avoid resetting the TEE-protected crypto peripherals as it would lead to an APM fault SET_PERI_REG_MASK(PCR_AES_CONF_REG, PCR_AES_RST_EN); SET_PERI_REG_MASK(PCR_DS_CONF_REG, PCR_DS_RST_EN); SET_PERI_REG_MASK(PCR_ECC_CONF_REG, PCR_ECC_RST_EN); SET_PERI_REG_MASK(PCR_HMAC_CONF_REG, PCR_HMAC_RST_EN); - SET_PERI_REG_MASK(PCR_RSA_CONF_REG, PCR_RSA_RST_EN); SET_PERI_REG_MASK(PCR_SHA_CONF_REG, PCR_SHA_RST_EN); CLEAR_PERI_REG_MASK(PCR_AES_CONF_REG, PCR_AES_RST_EN); CLEAR_PERI_REG_MASK(PCR_DS_CONF_REG, PCR_DS_RST_EN); CLEAR_PERI_REG_MASK(PCR_ECC_CONF_REG, PCR_ECC_RST_EN); CLEAR_PERI_REG_MASK(PCR_HMAC_CONF_REG, PCR_HMAC_RST_EN); - CLEAR_PERI_REG_MASK(PCR_RSA_CONF_REG, PCR_RSA_RST_EN); CLEAR_PERI_REG_MASK(PCR_SHA_CONF_REG, PCR_SHA_RST_EN); + CLEAR_PERI_REG_MASK(PCR_ECC_PD_CTRL_REG, PCR_ECC_MEM_FORCE_PD); +#endif // !CONFIG_SECURE_ENABLE_TEE + SET_PERI_REG_MASK(PCR_RSA_CONF_REG, PCR_RSA_RST_EN); + CLEAR_PERI_REG_MASK(PCR_RSA_CONF_REG, PCR_RSA_RST_EN); + CLEAR_PERI_REG_MASK(PCR_REGDMA_CONF_REG, PCR_REGDMA_RST_EN); // UART's sclk is controlled in the PCR register and does not reset with the UART module. The ROM missed enabling diff --git a/components/esp_system/port/soc/esp32c61/system_internal.c b/components/esp_system/port/soc/esp32c61/system_internal.c index f27de72df12..2fe762e97a0 100644 --- a/components/esp_system/port/soc/esp32c61/system_internal.c +++ b/components/esp_system/port/soc/esp32c61/system_internal.c @@ -83,20 +83,16 @@ void esp_system_reset_modules_on_exit(void) // all the peripherals are reset at the same time, which triggers a hardware SEC reset. The SEC reset // causes the crypto -> APB path to be reset, but the APB -> crypto path is not reset. This asymmetry // results in the crypto module hanging and refusing all access. - SET_PERI_REG_MASK(PCR_AES_CONF_REG, PCR_AES_RST_EN); - CLEAR_PERI_REG_MASK(PCR_AES_CONF_REG, PCR_AES_RST_EN); - SET_PERI_REG_MASK(PCR_DS_CONF_REG, PCR_DS_RST_EN); - CLEAR_PERI_REG_MASK(PCR_DS_CONF_REG, PCR_DS_RST_EN); +#if !CONFIG_SECURE_ENABLE_TEE + // Avoid resetting the TEE-protected crypto peripherals as it would lead to an APM fault SET_PERI_REG_MASK(PCR_ECC_CONF_REG, PCR_ECC_RST_EN); CLEAR_PERI_REG_MASK(PCR_ECC_CONF_REG, PCR_ECC_RST_EN); - SET_PERI_REG_MASK(PCR_ECDSA_CONF_REG, PCR_ECDSA_RST_EN); - CLEAR_PERI_REG_MASK(PCR_ECDSA_CONF_REG, PCR_ECDSA_RST_EN); - SET_PERI_REG_MASK(PCR_HMAC_CONF_REG, PCR_HMAC_RST_EN); - CLEAR_PERI_REG_MASK(PCR_HMAC_CONF_REG, PCR_HMAC_RST_EN); - SET_PERI_REG_MASK(PCR_RSA_CONF_REG, PCR_RSA_RST_EN); - CLEAR_PERI_REG_MASK(PCR_RSA_CONF_REG, PCR_RSA_RST_EN); SET_PERI_REG_MASK(PCR_SHA_CONF_REG, PCR_SHA_RST_EN); CLEAR_PERI_REG_MASK(PCR_SHA_CONF_REG, PCR_SHA_RST_EN); + CLEAR_PERI_REG_MASK(PCR_ECC_PD_CTRL_REG, PCR_ECC_MEM_FORCE_PD); +#endif + SET_PERI_REG_MASK(PCR_ECDSA_CONF_REG, PCR_ECDSA_RST_EN); + CLEAR_PERI_REG_MASK(PCR_ECDSA_CONF_REG, PCR_ECDSA_RST_EN); // UART's sclk is controlled in the PCR register and does not reset with the UART module. The ROM missed enabling // it when initializing the ROM UART. If it is not turned on, it will trigger LP_WDT in the ROM. diff --git a/components/esp_system/port/soc/esp32h2/system_internal.c b/components/esp_system/port/soc/esp32h2/system_internal.c index e0923104119..6a6b01093c3 100644 --- a/components/esp_system/port/soc/esp32h2/system_internal.c +++ b/components/esp_system/port/soc/esp32h2/system_internal.c @@ -65,20 +65,24 @@ void esp_system_reset_modules_on_exit(void) // Reset crypto peripherals. This ensures a clean state for the crypto peripherals after a CPU restart // and hence avoiding any possibility with crypto failure in ROM security workflows. +#if !CONFIG_SECURE_ENABLE_TEE + // Avoid resetting the TEE-protected crypto peripherals as it would lead to an APM fault SET_PERI_REG_MASK(PCR_AES_CONF_REG, PCR_AES_RST_EN); SET_PERI_REG_MASK(PCR_DS_CONF_REG, PCR_DS_RST_EN); SET_PERI_REG_MASK(PCR_ECC_CONF_REG, PCR_ECC_RST_EN); - SET_PERI_REG_MASK(PCR_ECDSA_CONF_REG, PCR_ECDSA_RST_EN); SET_PERI_REG_MASK(PCR_HMAC_CONF_REG, PCR_HMAC_RST_EN); - SET_PERI_REG_MASK(PCR_RSA_CONF_REG, PCR_RSA_RST_EN); SET_PERI_REG_MASK(PCR_SHA_CONF_REG, PCR_SHA_RST_EN); CLEAR_PERI_REG_MASK(PCR_AES_CONF_REG, PCR_AES_RST_EN); CLEAR_PERI_REG_MASK(PCR_DS_CONF_REG, PCR_DS_RST_EN); CLEAR_PERI_REG_MASK(PCR_ECC_CONF_REG, PCR_ECC_RST_EN); - CLEAR_PERI_REG_MASK(PCR_ECDSA_CONF_REG, PCR_ECDSA_RST_EN); CLEAR_PERI_REG_MASK(PCR_HMAC_CONF_REG, PCR_HMAC_RST_EN); - CLEAR_PERI_REG_MASK(PCR_RSA_CONF_REG, PCR_RSA_RST_EN); CLEAR_PERI_REG_MASK(PCR_SHA_CONF_REG, PCR_SHA_RST_EN); + CLEAR_PERI_REG_MASK(PCR_ECC_PD_CTRL_REG, PCR_ECC_MEM_FORCE_PD); +#endif // !CONFIG_SECURE_ENABLE_TEE + SET_PERI_REG_MASK(PCR_ECDSA_CONF_REG, PCR_ECDSA_RST_EN); + SET_PERI_REG_MASK(PCR_RSA_CONF_REG, PCR_RSA_RST_EN); + CLEAR_PERI_REG_MASK(PCR_ECDSA_CONF_REG, PCR_ECDSA_RST_EN); + CLEAR_PERI_REG_MASK(PCR_RSA_CONF_REG, PCR_RSA_RST_EN); // UART's sclk is controlled in the PCR register and does not reset with the UART module. The ROM missed enabling // it when initializing the ROM UART. If it is not turned on, it will trigger LP_WDT in the ROM. diff --git a/components/esp_system/port/soc/esp32h21/system_internal.c b/components/esp_system/port/soc/esp32h21/system_internal.c index 6a3b75d2b64..83e730b1d5c 100644 --- a/components/esp_system/port/soc/esp32h21/system_internal.c +++ b/components/esp_system/port/soc/esp32h21/system_internal.c @@ -84,6 +84,7 @@ void esp_system_reset_modules_on_exit(void) CLEAR_PERI_REG_MASK(PCR_RSA_CONF_REG, PCR_RSA_RST_EN); SET_PERI_REG_MASK(PCR_SHA_CONF_REG, PCR_SHA_RST_EN); CLEAR_PERI_REG_MASK(PCR_SHA_CONF_REG, PCR_SHA_RST_EN); + CLEAR_PERI_REG_MASK(PCR_ECC_PD_CTRL_REG, PCR_ECC_MEM_FORCE_PD); // UART's sclk is controlled in the PCR register and does not reset with the UART module. The ROM missed enabling // it when initializing the ROM UART. If it is not turned on, it will trigger LP_WDT in the ROM. diff --git a/components/esp_system/port/soc/esp32h4/system_internal.c b/components/esp_system/port/soc/esp32h4/system_internal.c index bafb6d4f9f5..a6e1080d694 100644 --- a/components/esp_system/port/soc/esp32h4/system_internal.c +++ b/components/esp_system/port/soc/esp32h4/system_internal.c @@ -82,6 +82,8 @@ void esp_system_reset_modules_on_exit(void) CLEAR_PERI_REG_MASK(PCR_HMAC_CONF_REG, PCR_HMAC_RST_EN); SET_PERI_REG_MASK(PCR_SHA_CONF_REG, PCR_SHA_RST_EN); CLEAR_PERI_REG_MASK(PCR_SHA_CONF_REG, PCR_SHA_RST_EN); + CLEAR_PERI_REG_MASK(PCR_ECC_MEM_LP_CTRL_REG, PCR_ECC_MEM_LP_EN); + SET_PERI_REG_MASK(PCR_ECC_MEM_LP_CTRL_REG, PCR_ECC_MEM_FORCE_CTRL); // UART's sclk is controlled in the PCR register and does not reset with the UART module. The ROM missed enabling // it when initializing the ROM UART. If it is not turned on, it will trigger LP_WDT in the ROM. diff --git a/components/esp_system/port/soc/esp32p4/system_internal.c b/components/esp_system/port/soc/esp32p4/system_internal.c index a00c393264a..9b3c089b1e4 100644 --- a/components/esp_system/port/soc/esp32p4/system_internal.c +++ b/components/esp_system/port/soc/esp32p4/system_internal.c @@ -127,6 +127,7 @@ void esp_system_reset_modules_on_exit(void) CLEAR_PERI_REG_MASK(HP_SYS_CLKRST_HP_RST_EN2_REG, HP_SYS_CLKRST_REG_RST_EN_KM); CLEAR_PERI_REG_MASK(HP_SYS_CLKRST_HP_RST_EN2_REG, HP_SYS_CLKRST_REG_RST_EN_RSA); CLEAR_PERI_REG_MASK(HP_SYS_CLKRST_HP_RST_EN2_REG, HP_SYS_CLKRST_REG_RST_EN_SHA); + CLEAR_PERI_REG_MASK(HP_SYSTEM_ECC_PD_CTRL_REG, HP_SYSTEM_ECC_MEM_FORCE_PD); #if CONFIG_ESP32P4_REV_MIN_FULL < 101 if (efuse_hal_chip_revision() < 101) { diff --git a/components/esp_tee/include/esp_tee.h b/components/esp_tee/include/esp_tee.h index a529928897a..63fee82cb56 100644 --- a/components/esp_tee/include/esp_tee.h +++ b/components/esp_tee/include/esp_tee.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2024-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -43,7 +43,9 @@ typedef struct { uint32_t magic_word; uint32_t api_major_version; uint32_t api_minor_version; - uint32_t reserved[3]; + uint32_t reserved[2]; + /* Heap poisoning API */ + void *ns_heap_poison_fill; /* TEE-related fields */ void *s_int_handler; /* REE-related fields */ diff --git a/components/esp_tee/include/private/esp_tee_binary.h b/components/esp_tee/include/private/esp_tee_binary.h index 5196dad1107..aca66276090 100644 --- a/components/esp_tee/include/private/esp_tee_binary.h +++ b/components/esp_tee/include/private/esp_tee_binary.h @@ -20,6 +20,11 @@ extern "C" { #define TEE_SECURE_INUM (31) #if SOC_INT_CLIC_SUPPORTED #define TEE_PASS_INUM (30) +/* CLIC: 3 effective priority bits (NLBITS=3), max priority = 7 */ +#define TEE_SECURE_INUM_PRIO (7) +#else +/* PLIC: 4-bit priority field, max priority = 15 */ +#define TEE_SECURE_INUM_PRIO (15) #endif #define ESP_TEE_M2U_SWITCH_MAGIC 0xfedef @@ -112,6 +117,14 @@ void esp_tee_configure_region_protection(void); */ void esp_tee_configure_apm_protection(void); +/** + * @brief Reset the crypto peripherals to a clean state. + * + * Mirrors esp_system_reset_modules_on_exit() in the non-TEE path. + * Intended to be called from the TEE panic handler before a software reset. + */ +void esp_tee_soc_reset_crypto_peripherals(void); + /** * @brief Switch to the REE app after TEE initialization is complete * diff --git a/components/esp_tee/src/esp_secure_service_wrapper.c b/components/esp_tee/src/esp_secure_service_wrapper.c index 04f1cc02cf0..e01214439b1 100644 --- a/components/esp_tee/src/esp_secure_service_wrapper.c +++ b/components/esp_tee/src/esp_secure_service_wrapper.c @@ -256,10 +256,20 @@ esp_err_t __wrap_esp_ds_start_sign(const void *message, if (esp_ds_ctx != NULL) { *esp_ds_ctx = malloc(sizeof(esp_ds_context_t)); if (!*esp_ds_ctx) { + esp_crypto_ds_lock_release(); return ESP_ERR_NO_MEM; } } - return esp_tee_service_call(5, SS_ESP_DS_START_SIGN, message, data, key_id, esp_ds_ctx); + + esp_err_t err = esp_tee_service_call(5, SS_ESP_DS_START_SIGN, message, data, key_id, esp_ds_ctx); + if (err != ESP_OK) { + if (esp_ds_ctx != NULL) { + free(*esp_ds_ctx); + *esp_ds_ctx = NULL; + } + esp_crypto_ds_lock_release(); + } + return err; } bool __wrap_esp_ds_is_busy(void) diff --git a/components/esp_tee/src/esp_tee_config.c b/components/esp_tee/src/esp_tee_config.c index 22fd9f7f2ce..b4eeafdab83 100644 --- a/components/esp_tee/src/esp_tee_config.c +++ b/components/esp_tee/src/esp_tee_config.c @@ -1,11 +1,12 @@ /* - * SPDX-FileCopyrightText: 2021-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2021-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include #include "esp_tee.h" +#include "sdkconfig.h" /* U-mode interrupt handler */ extern int _tee_interrupt_handler(void); @@ -20,6 +21,13 @@ extern uint32_t _rodata_reserved_start; /* REE DROM end */ extern uint32_t _rodata_reserved_end; +#if CONFIG_HEAP_TLSF_USE_ROM_IMPL && (CONFIG_HEAP_POISONING_LIGHT || CONFIG_HEAP_POISONING_COMPREHENSIVE) +extern void multi_heap_internal_poison_fill_region(void *start, size_t size, bool is_free); +#define HEAP_POISON_FILL ((void *)&multi_heap_internal_poison_fill_region) +#else +#define HEAP_POISON_FILL NULL +#endif + esp_tee_config_t esp_tee_app_config __attribute__((section(".esp_tee_app_cfg"))) = { .magic_word = ESP_TEE_APP_CFG_MAGIC, .api_major_version = ESP_TEE_API_MAJOR_VER, @@ -35,4 +43,5 @@ esp_tee_config_t esp_tee_app_config __attribute__((section(".esp_tee_app_cfg"))) .ns_irom_end = &_instruction_reserved_end, .ns_drom_start = &_rodata_reserved_start, .ns_drom_end = &_rodata_reserved_end, + .ns_heap_poison_fill = HEAP_POISON_FILL, }; diff --git a/components/esp_tee/subproject/components/attestation/esp_att_utils_json.c b/components/esp_tee/subproject/components/attestation/esp_att_utils_json.c index c0810ff45f7..7fb2c91041b 100644 --- a/components/esp_tee/subproject/components/attestation/esp_att_utils_json.c +++ b/components/esp_tee/subproject/components/attestation/esp_att_utils_json.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2024-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -174,8 +174,31 @@ esp_err_t esp_att_utils_eat_data_to_json(struct esp_att_sw_claim_list *head, con free(auth_challenge_hexstr); json_gen_obj_set_int(&json_gen, "client_id", cfg->client_id); + json_gen_obj_set_int(&json_gen, "chip_id", cfg->chip_id); json_gen_obj_set_int(&json_gen, "device_ver", cfg->device_ver); + json_gen_push_object(&json_gen, "ueid"); + + char mac_hexstr[ESP_ATT_EAT_UEID_MAC_SZ * 2 + 1] = {0}; + err = esp_att_utils_hexbuf_to_hexstr(cfg->ueid_mac, sizeof(cfg->ueid_mac), + mac_hexstr, sizeof(mac_hexstr)); + if (err != ESP_OK) { + free(json_buf); + return err; + } + json_gen_obj_set_string(&json_gen, "mac", mac_hexstr); + + char opt_id_hexstr[ESP_ATT_EAT_UEID_OPT_ID_SZ * 2 + 1] = {0}; + err = esp_att_utils_hexbuf_to_hexstr(cfg->ueid_opt_id, sizeof(cfg->ueid_opt_id), + opt_id_hexstr, sizeof(opt_id_hexstr)); + if (err != ESP_OK) { + free(json_buf); + return err; + } + json_gen_obj_set_string(&json_gen, "optional_id", opt_id_hexstr); + + json_gen_pop_object(&json_gen); + char dev_id_hexstr[ESP_ATT_EAT_DEV_ID_SZ * 2 + 1] = {0}; err = esp_att_utils_hexbuf_to_hexstr(cfg->device_id, sizeof(cfg->device_id), dev_id_hexstr, sizeof(dev_id_hexstr)); if (err != ESP_OK) { @@ -201,6 +224,7 @@ esp_err_t esp_att_utils_eat_data_to_json(struct esp_att_sw_claim_list *head, con esp_err_t err = part_metadata_to_json(&claim->metadata, &claim_json); if (err != ESP_OK || claim_json == NULL) { ESP_LOGE(TAG, "Failed to format the FW metadata to JSON!"); + free(json_buf); return err; } diff --git a/components/esp_tee/subproject/components/attestation/esp_attestation.c b/components/esp_tee/subproject/components/attestation/esp_attestation.c index c239e826811..4f25ec5f614 100644 --- a/components/esp_tee/subproject/components/attestation/esp_attestation.c +++ b/components/esp_tee/subproject/components/attestation/esp_attestation.c @@ -48,44 +48,23 @@ static void free_sw_claim_list(void) } } -static esp_err_t fetch_device_id(uint8_t *devid_buf) +static esp_err_t fetch_ueids(esp_att_token_cfg_t *cfg) { - if (devid_buf == NULL) { - return ESP_ERR_INVALID_ARG; - } - - uint8_t mac_addr[6] = {0}; - esp_err_t err = esp_efuse_read_field_blob(ESP_EFUSE_MAC, mac_addr, sizeof(mac_addr) * 8); + /* UEID: raw eFuse MAC */ + esp_err_t err = esp_efuse_read_field_blob(ESP_EFUSE_MAC, cfg->ueid_mac, + ESP_ATT_EAT_UEID_MAC_SZ * 8); if (err != ESP_OK) { - ESP_LOGE(TAG, "Failed to read MAC from eFuse!"); - goto exit; + return err; } - psa_hash_operation_t hash_op = PSA_HASH_OPERATION_INIT; - psa_status_t status = psa_hash_setup(&hash_op, PSA_ALG_SHA_256); - if (status != PSA_SUCCESS) { - return ESP_FAIL; - } - - status = psa_hash_update(&hash_op, mac_addr, sizeof(mac_addr)); - if (status != PSA_SUCCESS) { - return ESP_FAIL; - } - - size_t digest_len = 0; - status = psa_hash_finish(&hash_op, devid_buf, SHA256_DIGEST_SZ, &digest_len); - if (status != PSA_SUCCESS) { - return ESP_FAIL; - } - - if (digest_len != SHA256_DIGEST_SZ) { - return ESP_ERR_INVALID_SIZE; + /* UEID: 128-bit OPTIONAL_UNIQUE_ID */ + err = esp_efuse_read_field_blob(ESP_EFUSE_OPTIONAL_UNIQUE_ID, cfg->ueid_opt_id, + ESP_ATT_EAT_UEID_OPT_ID_SZ * 8); + if (err != ESP_OK) { + return err; } return ESP_OK; - -exit: - return err; } static esp_err_t populate_att_token_cfg(esp_att_token_cfg_t *cfg, const esp_att_ecdsa_keypair_t *keypair) @@ -94,18 +73,31 @@ static esp_err_t populate_att_token_cfg(esp_att_token_cfg_t *cfg, const esp_att_ return ESP_ERR_INVALID_ARG; } - esp_err_t err = fetch_device_id(cfg->device_id); + esp_err_t err = fetch_ueids(cfg); if (err != ESP_OK) { - ESP_LOGE(TAG, "Failed to get the device ID!"); + ESP_LOGE(TAG, "Failed to get the UEIDs!"); return err; } + /* Device ID = SHA-256 of the MAC */ + size_t digest_len = 0; + psa_status_t status = psa_hash_compute(PSA_ALG_SHA_256, cfg->ueid_mac, sizeof(cfg->ueid_mac), + cfg->device_id, sizeof(cfg->device_id), &digest_len); + if (status != PSA_SUCCESS || digest_len != sizeof(cfg->device_id)) { + ESP_LOGE(TAG, "Failed to derive the device ID!"); + return ESP_FAIL; + } + err = esp_att_utils_ecdsa_get_pubkey_digest(keypair, cfg->instance_id, sizeof(cfg->instance_id)); if (err != ESP_OK) { ESP_LOGE(TAG, "Failed to get ECDSA public key hash!"); return err; } + /* Chip ID read from the ROM */ + extern const uint32_t _rom_chip_id; + cfg->chip_id = _rom_chip_id; + /* Chip revision read from eFuse */ cfg->device_ver = efuse_hal_chip_revision(); /* TODO: Decide what all fields we need here */ cfg->device_stat = 0xA5; @@ -191,6 +183,13 @@ esp_err_t esp_att_generate_token(const uint8_t *auth_challenge, size_t challenge } esp_att_ecdsa_keypair_t keypair = {}; + psa_hash_operation_t hash_op = PSA_HASH_OPERATION_INIT; + psa_status_t status; + char *hdr_json = NULL; + char *eat_json = NULL; + char *pubkey_json = NULL; + char *sign_json = NULL; + err = esp_att_utils_ecdsa_gen_keypair_secp256r1(&keypair); if (err != ESP_OK) { ESP_LOGE(TAG, "Failed to generate ECDSA key-pair!"); @@ -214,10 +213,10 @@ esp_err_t esp_att_generate_token(const uint8_t *auth_challenge, size_t challenge memset(token_buf, 0x00, token_buf_size); - psa_hash_operation_t hash_op = PSA_HASH_OPERATION_INIT; - psa_status_t status = psa_hash_setup(&hash_op, PSA_ALG_SHA_256); + status = psa_hash_setup(&hash_op, PSA_ALG_SHA_256); if (status != PSA_SUCCESS) { - return ESP_FAIL; + err = ESP_FAIL; + goto exit; } json_gen_str_t jstr; @@ -226,79 +225,84 @@ esp_err_t esp_att_generate_token(const uint8_t *auth_challenge, size_t challenge /* Pushing the Header object */ const esp_att_token_hdr_t tk_hdr = {}; - char *hdr_json = NULL; int hdr_len = -1; /* NOTE: Token header is not yet configurable */ err = esp_att_utils_header_to_json(&tk_hdr, &hdr_json, &hdr_len); - if (err != ESP_OK || hdr_json == NULL || hdr_len <= 0) { + if (err != ESP_OK) { ESP_LOGE(TAG, "Failed to format the token header as JSON!"); - return err; + goto exit; } json_gen_push_object_str(&jstr, "header", hdr_json); status = psa_hash_update(&hash_op, (const unsigned char *)hdr_json, hdr_len - 1); if (status != PSA_SUCCESS) { - psa_hash_abort(&hash_op); - return ESP_FAIL; + err = ESP_FAIL; + goto exit; } free(hdr_json); + hdr_json = NULL; /* Pushing the EAT object */ - char *eat_json = NULL; int eat_len = -1; err = esp_att_utils_eat_data_to_json(&sw_claim_data, &cfg, &eat_json, &eat_len); - if (err != ESP_OK || eat_json == NULL || eat_len <= 0) { + if (err != ESP_OK) { ESP_LOGE(TAG, "Failed to format the EAT data to JSON!"); - return err; + goto exit; } json_gen_push_object_str(&jstr, "eat", eat_json); status = psa_hash_update(&hash_op, (const unsigned char *)eat_json, eat_len - 1); if (status != PSA_SUCCESS) { - psa_hash_abort(&hash_op); - return ESP_FAIL; + err = ESP_FAIL; + goto exit; } free(eat_json); + eat_json = NULL; - char *pubkey_json = NULL; int pubkey_len = -1; err = esp_att_utils_pubkey_to_json(&keypair, &pubkey_json, &pubkey_len); - if (err != ESP_OK || pubkey_json == NULL || pubkey_len <= 0) { + if (err != ESP_OK) { ESP_LOGE(TAG, "Failed to format the public key data to JSON!"); - return err; + goto exit; } json_gen_push_object_str(&jstr, "public_key", pubkey_json); status = psa_hash_update(&hash_op, (const unsigned char *)pubkey_json, pubkey_len - 1); if (status != PSA_SUCCESS) { - psa_hash_abort(&hash_op); - return ESP_FAIL; + err = ESP_FAIL; + goto exit; } free(pubkey_json); + pubkey_json = NULL; uint8_t digest[SHA256_DIGEST_SZ] = {0}; size_t digest_len = 0; status = psa_hash_finish(&hash_op, digest, sizeof(digest), &digest_len); if (status != PSA_SUCCESS) { - psa_hash_abort(&hash_op); - return ESP_FAIL; + err = ESP_FAIL; + goto exit; } - char *sign_json = NULL; int sign_len = -1; err = esp_att_utils_sign_to_json(&keypair, digest, sizeof(digest), &sign_json, &sign_len); - if (err != ESP_OK || sign_json == NULL || sign_len <= 0) { + if (err != ESP_OK) { ESP_LOGE(TAG, "Failed to format the token signature to JSON!"); - return err; + goto exit; } json_gen_push_object_str(&jstr, "sign", sign_json); free(sign_json); + sign_json = NULL; json_gen_end_object(&jstr); *token_size = json_gen_str_end(&jstr); err = ESP_OK; exit: + psa_hash_abort(&hash_op); + free(hdr_json); + free(eat_json); + free(pubkey_json); + free(sign_json); free_sw_claim_list(); return err; } diff --git a/components/esp_tee/subproject/components/attestation/private_include/esp_attestation_utils.h b/components/esp_tee/subproject/components/attestation/private_include/esp_attestation_utils.h index 50c90e57608..a8043554333 100644 --- a/components/esp_tee/subproject/components/attestation/private_include/esp_attestation_utils.h +++ b/components/esp_tee/subproject/components/attestation/private_include/esp_attestation_utils.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2024-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -37,8 +37,10 @@ extern "C" { #define ESP_ATT_HDR_JSON_MAX_SZ (128) #define ESP_ATT_EAT_DEV_ID_SZ (32) +#define ESP_ATT_EAT_UEID_MAC_SZ (6) /* eFuse MAC */ +#define ESP_ATT_EAT_UEID_OPT_ID_SZ (16) /* eFuse OPTIONAL_UNIQUE_ID */ #define ESP_ATT_CLAIM_JSON_MAX_SZ (448) -#define ESP_ATT_EAT_JSON_MAX_SZ (1344) +#define ESP_ATT_EAT_JSON_MAX_SZ (1600) #define ESP_ATT_PUBKEY_JSON_MAX_SZ (128) #define ESP_ATT_SIGN_JSON_MAX_SZ (192) @@ -119,14 +121,17 @@ typedef struct { * @brief Structure to hold the Entity Attestation Token initial configuration */ typedef struct { - uint8_t *auth_challenge; /**< Authentication challenge */ - size_t challenge_size; /**< Challenge size */ - uint32_t client_id; /**< Client identifier (Attestation relying party) */ - uint32_t device_ver; /**< Device version */ - uint8_t device_id[SHA256_DIGEST_SZ]; /**< Device identifier */ - uint8_t instance_id[SHA256_DIGEST_SZ]; /**< Instance identifier */ - char psa_cert_ref[32]; /**< PSA certificate reference */ - uint8_t device_stat; /**< Flags indicating device status */ + uint8_t *auth_challenge; /**< Authentication challenge */ + size_t challenge_size; /**< Challenge size */ + uint32_t client_id; /**< Client identifier (Attestation relying party) */ + uint32_t chip_id; /**< Chip identifier */ + uint32_t device_ver; /**< Device version */ + uint8_t ueid_mac[ESP_ATT_EAT_UEID_MAC_SZ]; /**< Device UEID: MAC from eFuse*/ + uint8_t ueid_opt_id[ESP_ATT_EAT_UEID_OPT_ID_SZ]; /**< Device UEID: OPTIONAL_UNIQUE_ID from eFuse*/ + uint8_t device_id[SHA256_DIGEST_SZ]; /**< Device identifier (SHA-256 of MAC) */ + uint8_t instance_id[SHA256_DIGEST_SZ]; /**< Instance identifier */ + char psa_cert_ref[32]; /**< PSA certificate reference */ + uint8_t device_stat; /**< Flags indicating device status */ } esp_att_token_cfg_t; /** diff --git a/components/esp_tee/subproject/components/tee_flash_mgr/esp_tee_flash.c b/components/esp_tee/subproject/components/tee_flash_mgr/esp_tee_flash.c index 0f979de7011..f6badae0c1b 100644 --- a/components/esp_tee/subproject/components/tee_flash_mgr/esp_tee_flash.c +++ b/components/esp_tee/subproject/components/tee_flash_mgr/esp_tee_flash.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2024-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -10,6 +10,7 @@ #include "esp_err.h" #include "esp_log.h" +#include "bootloader_flash_priv.h" #include "bootloader_utility_tee.h" #include "esp_tee_ota_utils.h" @@ -17,6 +18,8 @@ #include "esp_tee_flash.h" #include "sdkconfig.h" +#define ALIGN_UP(num, align) (((num) + ((align) - 1)) & ~((align) - 1)) + static const char *TAG = "esp_tee_flash"; // Structure containing the valid flash address range for flash operations through TEE @@ -169,8 +172,8 @@ esp_err_t esp_tee_flash_setup_prot_ctx(uint8_t tee_boot_part) if (subtype == PART_SUBTYPE_DATA_TEE_OTA) { needs_protection = true; } else if (subtype == PART_SUBTYPE_DATA_WIFI) { - size_t label_len = strlen(ESP_TEE_SEC_STG_PART_LABEL); - if (memcmp(partition_entry->partition.label, ESP_TEE_SEC_STG_PART_LABEL, label_len) == 0) { + if (strncmp((const char *)partition_entry->partition.label, ESP_TEE_SEC_STG_PART_LABEL, + sizeof(partition_entry->partition.label)) == 0) { needs_protection = true; } } @@ -241,3 +244,30 @@ bool esp_tee_flash_check_prange_in_active_tee_part(const size_t paddr, const siz return ((paddr < tee_prot_ctx.active_part_end_paddr) && (paddr_end > tee_prot_ctx.active_part_start_paddr)); } + +bool esp_tee_flash_check_prange_write_protected(const size_t paddr, const size_t len) +{ + size_t paddr_start = paddr; + if (len == FLASH_SECTOR_SIZE || len == FLASH_BLOCK_SIZE) { + paddr_start &= ~(len - 1); + } + + size_t paddr_end = paddr_start + len; + if (paddr_end < paddr_start) { + return true; + } + + const size_t ptb_start = CONFIG_PARTITION_TABLE_OFFSET; + const size_t ptb_end = ALIGN_UP(CONFIG_PARTITION_TABLE_OFFSET + ESP_PARTITION_TABLE_MAX_LEN, FLASH_SECTOR_SIZE); + bool ptb_overlap = (paddr_start < ptb_end) && (paddr_end > ptb_start); + + /* Bootloader: write-protected unless dangerous writes are explicitly allowed. */ + bool btl_overlap = false; +#if !CONFIG_SPI_FLASH_DANGEROUS_WRITE_ALLOWED + const size_t btl_start = CONFIG_BOOTLOADER_OFFSET_IN_FLASH; + const size_t btl_end = CONFIG_PARTITION_TABLE_OFFSET; + btl_overlap = (paddr_start < btl_end) && (paddr_end > btl_start); +#endif + + return (ptb_overlap || btl_overlap); +} diff --git a/components/esp_tee/subproject/components/tee_flash_mgr/include/esp_tee_flash.h b/components/esp_tee/subproject/components/tee_flash_mgr/include/esp_tee_flash.h index 631cc3d6cb3..379818c0a6f 100644 --- a/components/esp_tee/subproject/components/tee_flash_mgr/include/esp_tee_flash.h +++ b/components/esp_tee/subproject/components/tee_flash_mgr/include/esp_tee_flash.h @@ -133,3 +133,15 @@ bool esp_tee_flash_check_prange_in_tee_region(const size_t paddr, const size_t l * @return bool true if any part of the range overlaps with active TEE partition, false otherwise */ bool esp_tee_flash_check_prange_in_active_tee_part(const size_t paddr, const size_t len); + +/** + * @brief Check if the given physical address range overlaps a write-protected flash region. + * The partition table is always protected; the bootloader is protected unless + * CONFIG_SPI_FLASH_DANGEROUS_WRITE_ALLOWED is set. Use only for write/erase operations. + * + * @param paddr Starting physical address of the range to check + * @param len Length of the address range in bytes + * + * @return bool true if the range overlaps the write-protected region, false otherwise + */ +bool esp_tee_flash_check_prange_write_protected(const size_t paddr, const size_t len); diff --git a/components/esp_tee/subproject/components/tee_sec_storage/tee_sec_storage.c b/components/esp_tee/subproject/components/tee_sec_storage/tee_sec_storage.c index f0f3742bf48..8f01c8e3313 100644 --- a/components/esp_tee/subproject/components/tee_sec_storage/tee_sec_storage.c +++ b/components/esp_tee/subproject/components/tee_sec_storage/tee_sec_storage.c @@ -22,6 +22,7 @@ #include "esp_hmac_pbkdf2.h" #include "psa/crypto.h" +#include "mbedtls/platform_util.h" #include "mbedtls/psa_util.h" #include "esp_rom_sys.h" @@ -101,7 +102,7 @@ static int buffer_hexdump(const char *label, const void *buffer, size_t length) const uint8_t *bytes = (const uint8_t *)buffer; const size_t max_bytes_per_line = 16; - char hexbuf[max_bytes_per_line * 3]; + char hexbuf[max_bytes_per_line * 3 + 2]; ESP_LOGD(TAG, "%s -", label); @@ -193,7 +194,7 @@ static esp_err_t compute_nvs_keys_with_hmac(esp_efuse_block_t key_blk, nvs_sec_c psa_reset_key_attributes(&attributes); // Zero out the key buffer after import - memset(key_buf, 0x00, sizeof(key_buf)); + mbedtls_platform_zeroize(key_buf, sizeof(key_buf)); if (status != PSA_SUCCESS) { ESP_LOGE(TAG, "Failed to import HMAC key: %d", status); @@ -208,7 +209,7 @@ static esp_err_t compute_nvs_keys_with_hmac(esp_efuse_block_t key_blk, nvs_sec_c (uint8_t *)cfg->eky, SHA256_DIGEST_SZ, &mac_length); if (status != PSA_SUCCESS) { psa_destroy_key(psa_key_id); - memset(cfg, 0x00, sizeof(nvs_sec_cfg_t)); + mbedtls_platform_zeroize(cfg, sizeof(nvs_sec_cfg_t)); return ESP_FAIL; } ESP_FAULT_ASSERT(status == PSA_SUCCESS); @@ -221,7 +222,7 @@ static esp_err_t compute_nvs_keys_with_hmac(esp_efuse_block_t key_blk, nvs_sec_c psa_destroy_key(psa_key_id); if (status != PSA_SUCCESS) { - memset(cfg, 0x00, sizeof(nvs_sec_cfg_t)); + mbedtls_platform_zeroize(cfg, sizeof(nvs_sec_cfg_t)); return ESP_FAIL; } ESP_FAULT_ASSERT(status == PSA_SUCCESS); @@ -322,20 +323,24 @@ esp_err_t esp_tee_sec_storage_clear_key(const char *key_id) esp_err_t err = secure_storage_read(key_id, (void *)&keyctx, &keyctx_len); if (err != ESP_OK) { - return err; + goto cleanup; } if (keyctx.flags & SEC_STORAGE_FLAG_WRITE_ONCE) { ESP_LOGE(TAG, "Key is write-once only and cannot be cleared!"); - return ESP_ERR_INVALID_STATE; + err = ESP_ERR_INVALID_STATE; + goto cleanup; } err = nvs_erase_key(tee_nvs_hdl, key_id); if (err != ESP_OK) { - return err; + goto cleanup; } err = nvs_commit(tee_nvs_hdl); + +cleanup: + mbedtls_platform_zeroize(&keyctx, sizeof(keyctx)); return err; } @@ -465,6 +470,7 @@ esp_err_t esp_tee_sec_storage_gen_key(const esp_tee_sec_storage_key_cfg_t *cfg) return ESP_ERR_INVALID_STATE; } + esp_err_t err; sec_stg_key_t keyctx = { .type = cfg->type, .flags = cfg->flags, @@ -477,21 +483,28 @@ esp_err_t esp_tee_sec_storage_gen_key(const esp_tee_sec_storage_key_cfg_t *cfg) #endif if (generate_ecdsa_key(&keyctx, cfg->type) != 0) { ESP_LOGE(TAG, "Failed to generate ECDSA keypair"); - return ESP_FAIL; + err = ESP_FAIL; + goto cleanup; } break; case ESP_SEC_STG_KEY_AES256: if (generate_aes256_key(&keyctx) != 0) { ESP_LOGE(TAG, "Failed to generate AES key"); - return ESP_FAIL; + err = ESP_FAIL; + goto cleanup; } break; default: ESP_LOGE(TAG, "Unsupported key-type!"); - return ESP_ERR_NOT_SUPPORTED; + err = ESP_ERR_NOT_SUPPORTED; + goto cleanup; } - return secure_storage_write(cfg->id, (void *)&keyctx, sizeof(keyctx)); + err = secure_storage_write(cfg->id, (void *)&keyctx, sizeof(keyctx)); + +cleanup: + mbedtls_platform_zeroize(&keyctx, sizeof(keyctx)); + return err; } esp_err_t esp_tee_sec_storage_ecdsa_sign(const esp_tee_sec_storage_key_cfg_t *cfg, const uint8_t *hash, size_t hlen, esp_tee_sec_storage_ecdsa_sign_t *out_sign) @@ -514,19 +527,21 @@ esp_err_t esp_tee_sec_storage_ecdsa_sign(const esp_tee_sec_storage_key_cfg_t *cf sec_stg_key_t keyctx; size_t keyctx_len = sizeof(keyctx); + psa_key_id_t key_id = 0; + psa_key_attributes_t key_attributes = PSA_KEY_ATTRIBUTES_INIT; + err = secure_storage_read(cfg->id, (void *)&keyctx, &keyctx_len); if (err != ESP_OK) { ESP_LOGE(TAG, "Failed to fetch key from storage"); - return err; + goto exit; } if (keyctx.type != cfg->type) { ESP_LOGE(TAG, "Key type mismatch"); - return ESP_ERR_INVALID_STATE; + err = ESP_ERR_INVALID_STATE; + goto exit; } - psa_key_id_t key_id = 0; - psa_key_attributes_t key_attributes = PSA_KEY_ATTRIBUTES_INIT; psa_set_key_type(&key_attributes, PSA_KEY_TYPE_ECC_KEY_PAIR(PSA_ECC_FAMILY_SECP_R1)); psa_set_key_usage_flags(&key_attributes, PSA_KEY_USAGE_SIGN_HASH | PSA_KEY_USAGE_EXPORT | PSA_KEY_USAGE_VERIFY_HASH); psa_algorithm_t ecdsa_alg = PSA_ALG_ECDSA(PSA_ALG_SHA_256); @@ -571,6 +586,7 @@ esp_err_t esp_tee_sec_storage_ecdsa_sign(const esp_tee_sec_storage_key_cfg_t *cf exit: psa_destroy_key(key_id); psa_reset_key_attributes(&key_attributes); + mbedtls_platform_zeroize(&keyctx, sizeof(keyctx)); return err; } @@ -594,12 +610,13 @@ esp_err_t esp_tee_sec_storage_ecdsa_get_pubkey(const esp_tee_sec_storage_key_cfg err = secure_storage_read(cfg->id, (void *)&keyctx, &keyctx_len); if (err != ESP_OK) { ESP_LOGE(TAG, "Failed to read key from secure storage"); - return err; + goto cleanup; } if (keyctx.type != cfg->type) { ESP_LOGE(TAG, "Key type mismatch"); - return ESP_ERR_INVALID_STATE; + err = ESP_ERR_INVALID_STATE; + goto cleanup; } /* Now determine the public key source and length based on key type */ @@ -619,13 +636,17 @@ esp_err_t esp_tee_sec_storage_ecdsa_get_pubkey(const esp_tee_sec_storage_key_cfg #endif default: ESP_LOGE(TAG, "Unsupported key-type"); - return ESP_ERR_INVALID_ARG; + err = ESP_ERR_INVALID_ARG; + goto cleanup; } memcpy(out_pubkey->pub_x, pub_key_src, pub_key_len); memcpy(out_pubkey->pub_y, pub_key_src + pub_key_len, pub_key_len); + err = ESP_OK; - return ESP_OK; +cleanup: + mbedtls_platform_zeroize(&keyctx, sizeof(keyctx)); + return err; } static esp_err_t tee_sec_storage_crypt_common(const char *key_id, const uint8_t *input, size_t len, const uint8_t *aad, @@ -648,17 +669,22 @@ static esp_err_t tee_sec_storage_crypt_common(const char *key_id, const uint8_t return err; } + psa_key_id_t psa_key_id = 0; + uint8_t *aead_buf = NULL; + size_t aead_buf_len = 0; + sec_stg_key_t keyctx; size_t keyctx_len = sizeof(keyctx); err = secure_storage_read(key_id, (void *)&keyctx, &keyctx_len); if (err != ESP_OK) { ESP_LOGE(TAG, "Failed to fetch key from storage"); - return err; + goto cleanup; } if (keyctx.type != ESP_SEC_STG_KEY_AES256) { ESP_LOGE(TAG, "Key type mismatch"); - return ESP_ERR_INVALID_STATE; + err = ESP_ERR_INVALID_STATE; + goto cleanup; } // Setup PSA key attributes @@ -670,70 +696,66 @@ static esp_err_t tee_sec_storage_crypt_common(const char *key_id, const uint8_t psa_set_key_lifetime(&attributes, PSA_KEY_LIFETIME_VOLATILE); // Import the AES key - psa_key_id_t key_id_psa = 0; - psa_status_t status = psa_import_key(&attributes, keyctx.aes256.key, AES256_KEY_LEN, &key_id_psa); + psa_status_t status = psa_import_key(&attributes, keyctx.aes256.key, AES256_KEY_LEN, &psa_key_id); psa_reset_key_attributes(&attributes); if (status != PSA_SUCCESS) { - return ESP_FAIL; + err = ESP_FAIL; + goto cleanup; + } + + /* PSA AEAD wants ciphertext+tag concatenated in a single buffer for both + * encrypt (output) and decrypt (input). */ + aead_buf_len = len + tag_len; + aead_buf = malloc(aead_buf_len); + if (!aead_buf) { + err = ESP_ERR_NO_MEM; + goto cleanup; } if (is_encrypt) { - // PSA AEAD encrypt outputs ciphertext+tag concatenated - uint8_t *output_with_tag = malloc(len + tag_len); - if (!output_with_tag) { - psa_destroy_key(key_id_psa); - return ESP_ERR_NO_MEM; - } - esp_fill_random(iv, iv_len); size_t output_length = 0; - status = psa_aead_encrypt(key_id_psa, PSA_ALG_AEAD_WITH_SHORTENED_TAG(PSA_ALG_GCM, tag_len), + status = psa_aead_encrypt(psa_key_id, PSA_ALG_AEAD_WITH_SHORTENED_TAG(PSA_ALG_GCM, tag_len), iv, iv_len, aad, aad_len, input, len, - output_with_tag, len + tag_len, &output_length); + aead_buf, aead_buf_len, &output_length); if (status != PSA_SUCCESS) { ESP_LOGE(TAG, "Error in encrypting data: %d", status); - memset(output_with_tag, 0x00, len + tag_len); - free(output_with_tag); - psa_destroy_key(key_id_psa); - return ESP_FAIL; + err = ESP_FAIL; + goto cleanup; } // Separate ciphertext and tag - memcpy(output, output_with_tag, len); - memcpy(tag, output_with_tag + len, tag_len); - - memset(output_with_tag, 0x00, len + tag_len); - free(output_with_tag); + memcpy(output, aead_buf, len); + memcpy(tag, aead_buf + len, tag_len); } else { - // For decryption, PSA expects ciphertext + tag concatenated - uint8_t *input_with_tag = malloc(len + tag_len); - if (!input_with_tag) { - psa_destroy_key(key_id_psa); - return ESP_ERR_NO_MEM; - } - - memcpy(input_with_tag, input, len); - memcpy(input_with_tag + len, tag, tag_len); + memcpy(aead_buf, input, len); + memcpy(aead_buf + len, tag, tag_len); size_t output_length = 0; - status = psa_aead_decrypt(key_id_psa, PSA_ALG_AEAD_WITH_SHORTENED_TAG(PSA_ALG_GCM, tag_len), - iv, iv_len, aad, aad_len, input_with_tag, len + tag_len, + status = psa_aead_decrypt(psa_key_id, PSA_ALG_AEAD_WITH_SHORTENED_TAG(PSA_ALG_GCM, tag_len), + iv, iv_len, aad, aad_len, aead_buf, aead_buf_len, output, len, &output_length); - - memset(input_with_tag, 0x00, len + tag_len); - free(input_with_tag); - if (status != PSA_SUCCESS) { ESP_LOGE(TAG, "Error in decrypting data: %d", status); - psa_destroy_key(key_id_psa); - return ESP_FAIL; + err = ESP_FAIL; + goto cleanup; } } - psa_destroy_key(key_id_psa); - return ESP_OK; + err = ESP_OK; + +cleanup: + if (aead_buf) { + mbedtls_platform_zeroize(aead_buf, aead_buf_len); + free(aead_buf); + } + if (psa_key_id != 0) { + psa_destroy_key(psa_key_id); + } + mbedtls_platform_zeroize(&keyctx, sizeof(keyctx)); + return err; } esp_err_t esp_tee_sec_storage_aead_encrypt(const esp_tee_sec_storage_aead_ctx_t *ctx, uint8_t *iv, size_t iv_len, uint8_t *tag, size_t tag_len, uint8_t *output) @@ -819,24 +841,20 @@ esp_err_t esp_tee_sec_storage_ecdsa_sign_pbkdf2(const esp_tee_sec_storage_pbkdf2 } // Sign the hash - memset(out_sign, 0x00, sizeof(esp_tee_sec_storage_ecdsa_sign_t)); size_t signature_length = 0; status = psa_sign_hash(psa_key_id, PSA_ALG_ECDSA(PSA_ALG_SHA_256), hash, hlen, out_sign->signature, sizeof(out_sign->signature), &signature_length); if (status != PSA_SUCCESS) { - memset(out_sign, 0x00, sizeof(esp_tee_sec_storage_ecdsa_sign_t)); err = ESP_FAIL; goto exit; } // Export public key - memset(out_pubkey, 0x00, sizeof(esp_tee_sec_storage_ecdsa_pubkey_t)); uint8_t public_key[PSA_EXPORT_PUBLIC_KEY_MAX_SIZE]; size_t public_key_length = 0; status = psa_export_public_key(psa_key_id, public_key, sizeof(public_key), &public_key_length); if (status != PSA_SUCCESS) { - memset(out_pubkey, 0x00, sizeof(esp_tee_sec_storage_ecdsa_pubkey_t)); err = ESP_FAIL; goto exit; } @@ -844,7 +862,6 @@ esp_err_t esp_tee_sec_storage_ecdsa_sign_pbkdf2(const esp_tee_sec_storage_pbkdf2 // PSA exports public key in uncompressed format: 0x04 || X || Y // Skip the first byte (0x04) and copy X and Y coordinates if (public_key_length != (1 + 2 * key_len) || public_key[0] != 0x04) { - memset(out_pubkey, 0x00, sizeof(esp_tee_sec_storage_ecdsa_pubkey_t)); err = ESP_FAIL; goto exit; } @@ -859,7 +876,7 @@ exit: psa_destroy_key(psa_key_id); } if (derived_key) { - memset(derived_key, 0x00, key_len); + mbedtls_platform_zeroize(derived_key, key_len); free(derived_key); } return err; diff --git a/components/esp_tee/subproject/main/CMakeLists.txt b/components/esp_tee/subproject/main/CMakeLists.txt index 1e6396a0049..18f121f7af4 100644 --- a/components/esp_tee/subproject/main/CMakeLists.txt +++ b/components/esp_tee/subproject/main/CMakeLists.txt @@ -27,7 +27,8 @@ list(APPEND srcs "soc/${target}/esp_tee_secure_sys_cfg.c" "soc/${target}/esp_tee_pmp_pma_prot_cfg.c" "soc/${target}/esp_tee_apm_prot_cfg.c") -list(APPEND srcs "soc/common/esp_tee_apm_intr.c") +list(APPEND srcs "soc/common/esp_tee_apm_intr.c" + "soc/common/esp_tee_crypto_reset.c") if(CONFIG_SOC_AES_SUPPORTED) list(APPEND srcs "soc/common/esp_tee_aes_intr.c") diff --git a/components/esp_tee/subproject/main/arch/riscv/esp_tee_asm_utils.inc b/components/esp_tee/subproject/main/arch/riscv/esp_tee_asm_utils.inc new file mode 100644 index 00000000000..6478f02e13a --- /dev/null +++ b/components/esp_tee/subproject/main/arch/riscv/esp_tee_asm_utils.inc @@ -0,0 +1,223 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* + * Shared assembly helpers (macros + assembler-time constants) for the TEE + * M-mode runtime. Included from esp_tee_vectors_{plic,clic}.S + */ + +#pragma once + +#include "sdkconfig.h" +#include "riscv/rvruntime-frames.h" + +#if CONFIG_ESP_SYSTEM_HW_STACK_GUARD +#include "esp_private/hw_stack_guard.h" +#endif + +/* Shared assembler-time constants used by the macros */ +.equ SAVE_REGS, 32 +.equ CONTEXT_SIZE, (SAVE_REGS * 4) +.equ MAGIC, 0x1f + +/* Macro which first allocates space on the stack to save general + * purpose registers, and then save them. GP register is excluded. + * The default size allocated on the stack is CONTEXT_SIZE, but it + * can be overridden. */ +.macro save_general_regs cxt_size=CONTEXT_SIZE + addi sp, sp, -\cxt_size + sw ra, RV_STK_RA(sp) + sw tp, RV_STK_TP(sp) + sw t0, RV_STK_T0(sp) + sw t1, RV_STK_T1(sp) + sw t2, RV_STK_T2(sp) + sw s0, RV_STK_S0(sp) + sw s1, RV_STK_S1(sp) + sw a0, RV_STK_A0(sp) + sw a1, RV_STK_A1(sp) + sw a2, RV_STK_A2(sp) + sw a3, RV_STK_A3(sp) + sw a4, RV_STK_A4(sp) + sw a5, RV_STK_A5(sp) + sw a6, RV_STK_A6(sp) + sw a7, RV_STK_A7(sp) + sw s2, RV_STK_S2(sp) + sw s3, RV_STK_S3(sp) + sw s4, RV_STK_S4(sp) + sw s5, RV_STK_S5(sp) + sw s6, RV_STK_S6(sp) + sw s7, RV_STK_S7(sp) + sw s8, RV_STK_S8(sp) + sw s9, RV_STK_S9(sp) + sw s10, RV_STK_S10(sp) + sw s11, RV_STK_S11(sp) + sw t3, RV_STK_T3(sp) + sw t4, RV_STK_T4(sp) + sw t5, RV_STK_T5(sp) + sw t6, RV_STK_T6(sp) +.endm + +.macro save_mepc + csrr t0, mepc + sw t0, RV_STK_MEPC(sp) +.endm + +.macro save_mcsr + csrr t0, mstatus + sw t0, RV_STK_MSTATUS(sp) + csrr t0, mtvec + sw t0, RV_STK_MTVEC(sp) + csrr t0, mtval + sw t0, RV_STK_MTVAL(sp) + csrr t0, mhartid + sw t0, RV_STK_MHARTID(sp) + csrr t0, mcause + sw t0, RV_STK_MCAUSE(sp) +.endm + +/* Restore the general purpose registers (excluding gp) from the context on + * the stack. The context is then deallocated. The default size is CONTEXT_SIZE + * but it can be overridden. */ +.macro restore_general_regs cxt_size=CONTEXT_SIZE + lw ra, RV_STK_RA(sp) + lw tp, RV_STK_TP(sp) + lw t0, RV_STK_T0(sp) + lw t1, RV_STK_T1(sp) + lw t2, RV_STK_T2(sp) + lw s0, RV_STK_S0(sp) + lw s1, RV_STK_S1(sp) + lw a0, RV_STK_A0(sp) + lw a1, RV_STK_A1(sp) + lw a2, RV_STK_A2(sp) + lw a3, RV_STK_A3(sp) + lw a4, RV_STK_A4(sp) + lw a5, RV_STK_A5(sp) + lw a6, RV_STK_A6(sp) + lw a7, RV_STK_A7(sp) + lw s2, RV_STK_S2(sp) + lw s3, RV_STK_S3(sp) + lw s4, RV_STK_S4(sp) + lw s5, RV_STK_S5(sp) + lw s6, RV_STK_S6(sp) + lw s7, RV_STK_S7(sp) + lw s8, RV_STK_S8(sp) + lw s9, RV_STK_S9(sp) + lw s10, RV_STK_S10(sp) + lw s11, RV_STK_S11(sp) + lw t3, RV_STK_T3(sp) + lw t4, RV_STK_T4(sp) + lw t5, RV_STK_T5(sp) + lw t6, RV_STK_T6(sp) + addi sp, sp, \cxt_size +.endm + +.macro restore_mepc + lw t0, RV_STK_MEPC(sp) + csrw mepc, t0 +.endm + +.macro store_magic_general_regs + lui ra, MAGIC + lui tp, MAGIC + lui t0, MAGIC + lui t1, MAGIC + lui t2, MAGIC + lui s0, MAGIC + lui s1, MAGIC + lui a0, MAGIC + lui a1, MAGIC + lui a2, MAGIC + lui a3, MAGIC + lui a4, MAGIC + lui a5, MAGIC + lui a6, MAGIC + lui a7, MAGIC + lui s2, MAGIC + lui s3, MAGIC + lui s4, MAGIC + lui s5, MAGIC + lui s6, MAGIC + lui s7, MAGIC + lui s8, MAGIC + lui s9, MAGIC + lui s10, MAGIC + lui s11, MAGIC + lui t3, MAGIC + lui t4, MAGIC + lui t5, MAGIC + lui t6, MAGIC +.endm + +/** + * STACK_GUARD_PRE_SWITCH + * Stops HW stack-guard monitoring and optionally saves current bounds. + * + * Args: + * op_reg – output register for "monitoring enabled" state (must be reused) + * to_save – 1=save bounds to memory, 0=skip saving + * sp_min – symbol to store lower bound (if to_save=1) + * sp_max – symbol to store upper bound (if to_save=1) + * + * Clobbers: t0, t1, t2, op_reg + */ +.macro STACK_GUARD_PRE_SWITCH op_reg, to_save, sp_min, sp_max +#if CONFIG_ESP_SYSTEM_HW_STACK_GUARD + /* Query if monitoring is enabled: result goes into \op_reg */ + ESP_HW_STACK_GUARD_MONITOR_QUERY_CUR_CORE t2 \op_reg + beqz \op_reg, 1f + + .if \to_save + /* Save current REE/U-mode stack bounds */ + ESP_HW_STACK_GUARD_GET_BOUNDS_CUR_CORE t2 t0 t1 + la t2, \sp_min + sw t0, 0(t2) + la t2, \sp_max + sw t1, 0(t2) + .endif + + /* Stop monitoring */ + ESP_HW_STACK_GUARD_MONITOR_STOP_CUR_CORE t0 t1 + fence + 1: +#endif +.endm + +/** + * STACK_GUARD_POST_SWITCH + * Restores or applies new bounds after switching stacks, then restarts monitoring. + * + * Args: + * op_reg – saved monitoring state from PRE_SWITCH + * to_restore – 1=restore from memory, 0=set static bounds + * sp_min – saved bound (restore) or static lower bound (S-mode) + * sp_max – saved bound (restore) or static upper bound (S-mode) + * + * Clobbers: t0, t1, t2 + */ +.macro STACK_GUARD_POST_SWITCH op_reg, to_restore, sp_min, sp_max +#if CONFIG_ESP_SYSTEM_HW_STACK_GUARD + /* Check if monitoring was enabled (using saved state from op_reg) */ + beqz \op_reg, 1f + + .if \to_restore + /* Restore saved REE/U-mode bounds from memory */ + la t2, \sp_min + lw t0, 0(t2) + la t2, \sp_max + lw t1, 0(t2) + .else + /* Use new TEE/S-mode stack bounds (static symbols) */ + la t0, \sp_min + la t1, \sp_max + .endif + + /* Apply bounds to hardware stack guard */ + ESP_HW_STACK_GUARD_SET_BOUNDS_CUR_CORE t2 t0 t1 + /* Restart monitoring */ + ESP_HW_STACK_GUARD_MONITOR_START_CUR_CORE t0 t1 + 1: +#endif +.endm diff --git a/components/esp_tee/subproject/main/arch/riscv/esp_tee_vectors_clic.S b/components/esp_tee/subproject/main/arch/riscv/esp_tee_vectors_clic.S index 5a5e266e189..bf64ea045d5 100644 --- a/components/esp_tee/subproject/main/arch/riscv/esp_tee_vectors_clic.S +++ b/components/esp_tee/subproject/main/arch/riscv/esp_tee_vectors_clic.S @@ -16,15 +16,10 @@ #include "esp_tee_intr_defs.h" #include "sdkconfig.h" -#if CONFIG_ESP_SYSTEM_HW_STACK_GUARD -#include "esp_private/hw_stack_guard.h" -#endif +#include "esp_tee_asm_utils.inc" - .equ SAVE_REGS, 32 - .equ CONTEXT_SIZE, (SAVE_REGS * 4) .equ panic_from_exception, tee_panic_from_exc .equ panic_from_isr, tee_panic_from_isr - .equ MAGIC, 0x1f .equ RTNVAL, 0xc0de .equ ECALL_U_MODE, 0x8 .equ ECALL_M_MODE, 0xb @@ -60,205 +55,6 @@ _ns_sp_min: _ns_sp_max: .word 0 -/** - * STACK_GUARD_PRE_SWITCH - * Stops HW stack-guard monitoring and optionally saves current bounds. - * - * Args: - * op_reg – output register for “monitoring enabled” state (must be reused) - * to_save – 1=save bounds to memory, 0=skip saving - * sp_min – symbol to store lower bound (if to_save=1) - * sp_max – symbol to store upper bound (if to_save=1) - * - * Clobbers: t0, t1, t2, op_reg - */ -.macro STACK_GUARD_PRE_SWITCH op_reg, to_save, sp_min, sp_max -#if CONFIG_ESP_SYSTEM_HW_STACK_GUARD - /* Query if monitoring is enabled: result goes into \op_reg */ - ESP_HW_STACK_GUARD_MONITOR_QUERY_CUR_CORE t2 \op_reg - beqz \op_reg, 1f - - .if \to_save - /* Save current REE/U-mode stack bounds */ - ESP_HW_STACK_GUARD_GET_BOUNDS_CUR_CORE t2 t0 t1 - la t2, \sp_min - sw t0, 0(t2) - la t2, \sp_max - sw t1, 0(t2) - .endif - - /* Stop monitoring */ - ESP_HW_STACK_GUARD_MONITOR_STOP_CUR_CORE t0 t1 - fence -1: -#endif -.endm - -/** - * STACK_GUARD_POST_SWITCH - * Restores or applies new bounds after switching stacks, then restarts monitoring. - * - * Args: - * op_reg – saved monitoring state from PRE_SWITCH - * to_restore – 1=restore from memory, 0=set static bounds - * sp_min – saved bound (restore) or static lower bound (S-mode) - * sp_max – saved bound (restore) or static upper bound (S-mode) - * - * Clobbers: t0, t1, t2 - */ -.macro STACK_GUARD_POST_SWITCH op_reg, to_restore, sp_min, sp_max -#if CONFIG_ESP_SYSTEM_HW_STACK_GUARD - /* Check if monitoring was enabled (using saved state from op_reg) */ - beqz \op_reg, 1f - - .if \to_restore - /* Restore saved REE/U-mode bounds from memory */ - la t2, \sp_min - lw t0, 0(t2) - la t2, \sp_max - lw t1, 0(t2) - .else - /* Use new TEE/S-mode stack bounds (static symbols) */ - la t0, \sp_min - la t1, \sp_max - .endif - - /* Apply bounds to hardware stack guard */ - ESP_HW_STACK_GUARD_SET_BOUNDS_CUR_CORE t2 t0 t1 - /* Restart monitoring */ - ESP_HW_STACK_GUARD_MONITOR_START_CUR_CORE t0 t1 -1: -#endif -.endm - -/* Macro which first allocates space on the stack to save general - * purpose registers, and then save them. GP register is excluded. - * The default size allocated on the stack is CONTEXT_SIZE, but it - * can be overridden. */ -.macro save_general_regs cxt_size=CONTEXT_SIZE - addi sp, sp, -\cxt_size - sw ra, RV_STK_RA(sp) - sw tp, RV_STK_TP(sp) - sw t0, RV_STK_T0(sp) - sw t1, RV_STK_T1(sp) - sw t2, RV_STK_T2(sp) - sw s0, RV_STK_S0(sp) - sw s1, RV_STK_S1(sp) - sw a0, RV_STK_A0(sp) - sw a1, RV_STK_A1(sp) - sw a2, RV_STK_A2(sp) - sw a3, RV_STK_A3(sp) - sw a4, RV_STK_A4(sp) - sw a5, RV_STK_A5(sp) - sw a6, RV_STK_A6(sp) - sw a7, RV_STK_A7(sp) - sw s2, RV_STK_S2(sp) - sw s3, RV_STK_S3(sp) - sw s4, RV_STK_S4(sp) - sw s5, RV_STK_S5(sp) - sw s6, RV_STK_S6(sp) - sw s7, RV_STK_S7(sp) - sw s8, RV_STK_S8(sp) - sw s9, RV_STK_S9(sp) - sw s10, RV_STK_S10(sp) - sw s11, RV_STK_S11(sp) - sw t3, RV_STK_T3(sp) - sw t4, RV_STK_T4(sp) - sw t5, RV_STK_T5(sp) - sw t6, RV_STK_T6(sp) -.endm - -.macro save_mepc - csrr t0, mepc - sw t0, RV_STK_MEPC(sp) -.endm - -.macro save_mcsr - csrr t0, mstatus - sw t0, RV_STK_MSTATUS(sp) - csrr t0, mtvec - sw t0, RV_STK_MTVEC(sp) - csrr t0, mtval - sw t0, RV_STK_MTVAL(sp) - csrr t0, mhartid - sw t0, RV_STK_MHARTID(sp) - csrr t0, mcause - sw t0, RV_STK_MCAUSE(sp) -.endm - -/* Restore the general purpose registers (excluding gp) from the context on - * the stack. The context is then deallocated. The default size is CONTEXT_SIZE - * but it can be overridden. */ -.macro restore_general_regs cxt_size=CONTEXT_SIZE - lw ra, RV_STK_RA(sp) - lw tp, RV_STK_TP(sp) - lw t0, RV_STK_T0(sp) - lw t1, RV_STK_T1(sp) - lw t2, RV_STK_T2(sp) - lw s0, RV_STK_S0(sp) - lw s1, RV_STK_S1(sp) - lw a0, RV_STK_A0(sp) - lw a1, RV_STK_A1(sp) - lw a2, RV_STK_A2(sp) - lw a3, RV_STK_A3(sp) - lw a4, RV_STK_A4(sp) - lw a5, RV_STK_A5(sp) - lw a6, RV_STK_A6(sp) - lw a7, RV_STK_A7(sp) - lw s2, RV_STK_S2(sp) - lw s3, RV_STK_S3(sp) - lw s4, RV_STK_S4(sp) - lw s5, RV_STK_S5(sp) - lw s6, RV_STK_S6(sp) - lw s7, RV_STK_S7(sp) - lw s8, RV_STK_S8(sp) - lw s9, RV_STK_S9(sp) - lw s10, RV_STK_S10(sp) - lw s11, RV_STK_S11(sp) - lw t3, RV_STK_T3(sp) - lw t4, RV_STK_T4(sp) - lw t5, RV_STK_T5(sp) - lw t6, RV_STK_T6(sp) - addi sp,sp, \cxt_size -.endm - -.macro restore_mepc - lw t0, RV_STK_MEPC(sp) - csrw mepc, t0 -.endm - -.macro store_magic_general_regs - lui ra, MAGIC - lui tp, MAGIC - lui t0, MAGIC - lui t1, MAGIC - lui t2, MAGIC - lui s0, MAGIC - lui s1, MAGIC - lui a0, MAGIC - lui a1, MAGIC - lui a2, MAGIC - lui a3, MAGIC - lui a4, MAGIC - lui a5, MAGIC - lui a6, MAGIC - lui a7, MAGIC - lui s2, MAGIC - lui s3, MAGIC - lui s4, MAGIC - lui s5, MAGIC - lui s6, MAGIC - lui s7, MAGIC - lui s8, MAGIC - lui s9, MAGIC - lui s10, MAGIC - lui s11, MAGIC - lui t3, MAGIC - lui t4, MAGIC - lui t5, MAGIC - lui t6, MAGIC -.endm - .section .exception_vectors.text, "ax" /* Exception handler. */ @@ -394,13 +190,14 @@ _skip_thresh_restore: STACK_GUARD_POST_SWITCH t3 1 _ns_sp_min _ns_sp_max fence - call syscall_exit_tee - /* Backup the A0 register * This point is reached after an ecall is triggered after executing the secure service. * The A0 register contains the return value of the corresponding service. * After restoring the entire register context, we assign A0 the value back to the return value. */ csrw mscratch, a0 + + call syscall_exit_tee + restore_general_regs csrrw a0, mscratch, zero diff --git a/components/esp_tee/subproject/main/arch/riscv/esp_tee_vectors_plic.S b/components/esp_tee/subproject/main/arch/riscv/esp_tee_vectors_plic.S index 52d4728dfaa..1274e5df3d6 100644 --- a/components/esp_tee/subproject/main/arch/riscv/esp_tee_vectors_plic.S +++ b/components/esp_tee/subproject/main/arch/riscv/esp_tee_vectors_plic.S @@ -16,15 +16,10 @@ #include "esp_tee_intr_defs.h" #include "sdkconfig.h" -#if CONFIG_ESP_SYSTEM_HW_STACK_GUARD -#include "esp_private/hw_stack_guard.h" -#endif +#include "esp_tee_asm_utils.inc" - .equ SAVE_REGS, 32 - .equ CONTEXT_SIZE, (SAVE_REGS * 4) .equ panic_from_exception, tee_panic_from_exc .equ panic_from_isr, tee_panic_from_isr - .equ MAGIC, 0x1f .equ RTNVAL, 0xc0de .equ ECALL_U_MODE, 0x8 .equ ECALL_M_MODE, 0xb @@ -65,205 +60,6 @@ _ns_sp_min: _ns_sp_max: .word 0 -/** - * STACK_GUARD_PRE_SWITCH - * Stops HW stack-guard monitoring and optionally saves current bounds. - * - * Args: - * op_reg – output register for “monitoring enabled” state (must be reused) - * to_save – 1=save bounds to memory, 0=skip saving - * sp_min – symbol to store lower bound (if to_save=1) - * sp_max – symbol to store upper bound (if to_save=1) - * - * Clobbers: t0, t1, t2, op_reg - */ -.macro STACK_GUARD_PRE_SWITCH op_reg, to_save, sp_min, sp_max -#if CONFIG_ESP_SYSTEM_HW_STACK_GUARD - /* Query if monitoring is enabled: result goes into \op_reg */ - ESP_HW_STACK_GUARD_MONITOR_QUERY_CUR_CORE t2 \op_reg - beqz \op_reg, 1f - - .if \to_save - /* Save current REE/U-mode stack bounds */ - ESP_HW_STACK_GUARD_GET_BOUNDS_CUR_CORE t2 t0 t1 - la t2, \sp_min - sw t0, 0(t2) - la t2, \sp_max - sw t1, 0(t2) - .endif - - /* Stop monitoring */ - ESP_HW_STACK_GUARD_MONITOR_STOP_CUR_CORE t0 t1 - fence -1: -#endif -.endm - -/** - * STACK_GUARD_POST_SWITCH - * Restores or applies new bounds after switching stacks, then restarts monitoring. - * - * Args: - * op_reg – saved monitoring state from PRE_SWITCH - * to_restore – 1=restore from memory, 0=set static bounds - * sp_min – saved bound (restore) or static lower bound (S-mode) - * sp_max – saved bound (restore) or static upper bound (S-mode) - * - * Clobbers: t0, t1, t2 - */ -.macro STACK_GUARD_POST_SWITCH op_reg, to_restore, sp_min, sp_max -#if CONFIG_ESP_SYSTEM_HW_STACK_GUARD - /* Check if monitoring was enabled (using saved state from op_reg) */ - beqz \op_reg, 1f - - .if \to_restore - /* Restore saved REE/U-mode bounds from memory */ - la t2, \sp_min - lw t0, 0(t2) - la t2, \sp_max - lw t1, 0(t2) - .else - /* Use new TEE/S-mode stack bounds (static symbols) */ - la t0, \sp_min - la t1, \sp_max - .endif - - /* Apply bounds to hardware stack guard */ - ESP_HW_STACK_GUARD_SET_BOUNDS_CUR_CORE t2 t0 t1 - /* Restart monitoring */ - ESP_HW_STACK_GUARD_MONITOR_START_CUR_CORE t0 t1 -1: -#endif -.endm - -/* Macro which first allocates space on the stack to save general - * purpose registers, and then save them. GP register is excluded. - * The default size allocated on the stack is CONTEXT_SIZE, but it - * can be overridden. */ -.macro save_general_regs cxt_size=CONTEXT_SIZE - addi sp, sp, -\cxt_size - sw ra, RV_STK_RA(sp) - sw tp, RV_STK_TP(sp) - sw t0, RV_STK_T0(sp) - sw t1, RV_STK_T1(sp) - sw t2, RV_STK_T2(sp) - sw s0, RV_STK_S0(sp) - sw s1, RV_STK_S1(sp) - sw a0, RV_STK_A0(sp) - sw a1, RV_STK_A1(sp) - sw a2, RV_STK_A2(sp) - sw a3, RV_STK_A3(sp) - sw a4, RV_STK_A4(sp) - sw a5, RV_STK_A5(sp) - sw a6, RV_STK_A6(sp) - sw a7, RV_STK_A7(sp) - sw s2, RV_STK_S2(sp) - sw s3, RV_STK_S3(sp) - sw s4, RV_STK_S4(sp) - sw s5, RV_STK_S5(sp) - sw s6, RV_STK_S6(sp) - sw s7, RV_STK_S7(sp) - sw s8, RV_STK_S8(sp) - sw s9, RV_STK_S9(sp) - sw s10, RV_STK_S10(sp) - sw s11, RV_STK_S11(sp) - sw t3, RV_STK_T3(sp) - sw t4, RV_STK_T4(sp) - sw t5, RV_STK_T5(sp) - sw t6, RV_STK_T6(sp) -.endm - -.macro save_mepc - csrr t0, mepc - sw t0, RV_STK_MEPC(sp) -.endm - -.macro save_mcsr - csrr t0, mstatus - sw t0, RV_STK_MSTATUS(sp) - csrr t0, mtvec - sw t0, RV_STK_MTVEC(sp) - csrr t0, mtval - sw t0, RV_STK_MTVAL(sp) - csrr t0, mhartid - sw t0, RV_STK_MHARTID(sp) - csrr t0, mcause - sw t0, RV_STK_MCAUSE(sp) -.endm - -/* Restore the general purpose registers (excluding gp) from the context on - * the stack. The context is then deallocated. The default size is CONTEXT_SIZE - * but it can be overridden. */ -.macro restore_general_regs cxt_size=CONTEXT_SIZE - lw ra, RV_STK_RA(sp) - lw tp, RV_STK_TP(sp) - lw t0, RV_STK_T0(sp) - lw t1, RV_STK_T1(sp) - lw t2, RV_STK_T2(sp) - lw s0, RV_STK_S0(sp) - lw s1, RV_STK_S1(sp) - lw a0, RV_STK_A0(sp) - lw a1, RV_STK_A1(sp) - lw a2, RV_STK_A2(sp) - lw a3, RV_STK_A3(sp) - lw a4, RV_STK_A4(sp) - lw a5, RV_STK_A5(sp) - lw a6, RV_STK_A6(sp) - lw a7, RV_STK_A7(sp) - lw s2, RV_STK_S2(sp) - lw s3, RV_STK_S3(sp) - lw s4, RV_STK_S4(sp) - lw s5, RV_STK_S5(sp) - lw s6, RV_STK_S6(sp) - lw s7, RV_STK_S7(sp) - lw s8, RV_STK_S8(sp) - lw s9, RV_STK_S9(sp) - lw s10, RV_STK_S10(sp) - lw s11, RV_STK_S11(sp) - lw t3, RV_STK_T3(sp) - lw t4, RV_STK_T4(sp) - lw t5, RV_STK_T5(sp) - lw t6, RV_STK_T6(sp) - addi sp,sp, \cxt_size -.endm - -.macro restore_mepc - lw t0, RV_STK_MEPC(sp) - csrw mepc, t0 -.endm - -.macro store_magic_general_regs - lui ra, MAGIC - lui tp, MAGIC - lui t0, MAGIC - lui t1, MAGIC - lui t2, MAGIC - lui s0, MAGIC - lui s1, MAGIC - lui a0, MAGIC - lui a1, MAGIC - lui a2, MAGIC - lui a3, MAGIC - lui a4, MAGIC - lui a5, MAGIC - lui a6, MAGIC - lui a7, MAGIC - lui s2, MAGIC - lui s3, MAGIC - lui s4, MAGIC - lui s5, MAGIC - lui s6, MAGIC - lui s7, MAGIC - lui s8, MAGIC - lui s9, MAGIC - lui s10, MAGIC - lui s11, MAGIC - lui t3, MAGIC - lui t4, MAGIC - lui t5, MAGIC - lui t6, MAGIC -.endm - .section .exception_vectors.text, "ax" /* Exception handler. */ @@ -386,13 +182,14 @@ _skip_thresh_restore: STACK_GUARD_POST_SWITCH t3 1 _ns_sp_min _ns_sp_max fence - call syscall_exit_tee - /* Backup the A0 register * This point is reached after an ecall is triggered after executing the secure service. * The A0 register contains the return value of the corresponding service. * After restoring the entire register context, we assign A0 the value back to the return value. */ csrw mscratch, a0 + + call syscall_exit_tee + restore_general_regs csrrw a0, mscratch, zero diff --git a/components/esp_tee/subproject/main/common/multi_heap.c b/components/esp_tee/subproject/main/common/multi_heap.c index ec0b1975155..04cbe324782 100644 --- a/components/esp_tee/subproject/main/common/multi_heap.c +++ b/components/esp_tee/subproject/main/common/multi_heap.c @@ -10,10 +10,16 @@ #include "esp_rom_sys.h" #include "tlsf_block_functions.h" #include "multi_heap.h" +#include "esp_tee.h" /* Handle to a registered TEE heap */ static multi_heap_handle_t tee_heap; +static inline void tee_heap_set_poison(bool enable) +{ + tlsf_poison_fill_pfunc_set(enable ? (poison_fill_pfunc_t)esp_tee_app_config.ns_heap_poison_fill : NULL); +} + inline static void multi_heap_assert(bool condition, const char *format, int line, intptr_t address) { /* Can't use libc assert() here as it calls printf() which can cause another malloc() for a newlib lock. @@ -142,7 +148,10 @@ void esp_tee_heap_free(void *p) tee_heap->free_bytes += tlsf_block_size(p); tee_heap->free_bytes += tlsf_alloc_overhead(); + + tee_heap_set_poison(false); tlsf_free(tee_heap->heap_data, p); + tee_heap_set_poison(true); } void *malloc(size_t size) @@ -166,7 +175,10 @@ void *realloc(void* ptr, size_t size) } size_t previous_block_size = tlsf_block_size(ptr); + tee_heap_set_poison(false); void *result = tlsf_realloc(tee_heap->heap_data, ptr, size); + tee_heap_set_poison(true); + if (result) { /* No need to subtract the tlsf_alloc_overhead() as it has already * been subtracted when allocating the block at first with malloc */ diff --git a/components/esp_tee/subproject/main/common/panic/esp_tee_panic.c b/components/esp_tee/subproject/main/common/panic/esp_tee_panic.c index 3fee6d506e3..ccf21e7c4d1 100644 --- a/components/esp_tee/subproject/main/common/panic/esp_tee_panic.c +++ b/components/esp_tee/subproject/main/common/panic/esp_tee_panic.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2024-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -18,6 +18,7 @@ #include "hal/cache_ll.h" #include "hal/cache_hal.h" #include "hal/apm_hal.h" +#include "soc/soc_caps.h" #if SOC_INT_PLIC_SUPPORTED #include "soc/plic_reg.h" @@ -56,6 +57,12 @@ static void tee_panic_end(void) esp_rom_output_tx_wait_idle(CONFIG_ESP_CONSOLE_UART_NUM); } + // Reset crypto peripherals before the panic-induced reset so the next boot + // sees them in a clean state. The SoC-specific implementation mirrors + // esp_system_reset_modules_on_exit() in the non-TEE path using register-level + // accesses. + esp_tee_soc_reset_crypto_peripherals(); + // Generate system reset esp_rom_software_reset_system(); } diff --git a/components/esp_tee/subproject/main/core/esp_secure_dispatcher.c b/components/esp_tee/subproject/main/core/esp_secure_dispatcher.c index 2b77e1fa7f5..43fe6117f10 100644 --- a/components/esp_tee/subproject/main/core/esp_secure_dispatcher.c +++ b/components/esp_tee/subproject/main/core/esp_secure_dispatcher.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -53,7 +53,7 @@ int esp_tee_service_dispatcher(int argc, va_list ap) argc--; const secure_service_entry_t *service = find_service_by_id(sid); - if (service == NULL) { + if (service == NULL || service->func == NULL) { ESP_LOGE(TAG, "Invalid service ID!"); return ret; } @@ -71,13 +71,16 @@ int esp_tee_service_dispatcher(int argc, va_list ap) uint32_t *argp = &argv[0]; asm volatile( + // Reserve outgoing-argument area for stack args 9+ + "addi sp, sp, -16 \n" + "mv t0, %1 \n" // t0 = argc "mv t1, %3 \n" // t1 = argp "li t2, 8 \n" // t2 = 8 (max register args) "ble t0, t2, load_regs \n" // If argc <= 8 (a0-a7), skip stack routine - // Store extra args (argc > 8) on stack + // Store extra args (argc > 8) on the reserved area "mv t3, sp \n" "addi t1, t1, 32 \n" @@ -87,7 +90,7 @@ int esp_tee_service_dispatcher(int argc, va_list ap) "addi t1, t1, 4 \n" "addi t3, t3, 4 \n" "addi t0, t0, -1 \n" - "bge t0, t2, stack_loop \n" + "bgt t0, t2, stack_loop \n" // Load the first 8 arguments into a0-a7 "load_regs: \n" @@ -104,10 +107,13 @@ int esp_tee_service_dispatcher(int argc, va_list ap) "mv t1, %2 \n" // Load function pointer "jalr 0(t1) \n" // Call function "mv %0, a0 \n" // Store return value + + // Restore the outgoing-argument area + "addi sp, sp, 16 \n" : "=r"(ret) : "r"(argc), "r"(fp_secure_service), "r"(argp) : "a0", "a1", "a2", "a3", "a4", "a5", "a6", "a7", - "t0", "t1", "t2", "t3", "t4" + "t0", "t1", "t2", "t3", "t4", "t5", "t6", "ra", "memory" ); return ret; diff --git a/components/esp_tee/subproject/main/core/esp_secure_services.c b/components/esp_tee/subproject/main/core/esp_secure_services.c index 1760d809515..b56b0c7c28c 100644 --- a/components/esp_tee/subproject/main/core/esp_secure_services.c +++ b/components/esp_tee/subproject/main/core/esp_secure_services.c @@ -379,10 +379,15 @@ esp_err_t _ss_esp_hmac_jtag_disable(void) #if SOC_DIG_SIGN_SUPPORTED static size_t get_ds_msg_sign_len(esp_digital_signature_length_t rsa_length) { - if (rsa_length != ESP_DS_RSA_1024 && rsa_length != ESP_DS_RSA_2048 && - rsa_length != ESP_DS_RSA_3072 && rsa_length != ESP_DS_RSA_4096) { + + if (rsa_length != ESP_DS_RSA_1024 && rsa_length != ESP_DS_RSA_2048 && rsa_length != ESP_DS_RSA_3072 +#if SOC_DS_SIGNATURE_MAX_BIT_LEN == 4096 + && rsa_length != ESP_DS_RSA_4096 +#endif + ) { return 0; } + return (size_t)(rsa_length + 1) * 4; } @@ -418,6 +423,7 @@ esp_err_t _ss_esp_ds_start_sign(const void *message, esp_ds_context_t **esp_ds_ctx) { bool valid_addr = (esp_tee_buf_in_ree(esp_ds_ctx, sizeof(esp_ds_context_t *)) && + esp_tee_buf_in_ree(*esp_ds_ctx, sizeof(esp_ds_context_t)) && esp_tee_buf_in_ree(data, sizeof(esp_ds_data_t))); if (!valid_addr) { return ESP_ERR_INVALID_ARG; @@ -446,8 +452,16 @@ bool _ss_esp_ds_is_busy(void) esp_err_t _ss_esp_ds_finish_sign(void *signature, esp_ds_context_t *esp_ds_ctx) { - const size_t max_sign = get_ds_msg_sign_len(ESP_DS_RSA_4096); - bool valid_addr = esp_tee_buf_in_ree(signature, max_sign); + const size_t max_sign = SOC_DS_SIGNATURE_MAX_BIT_LEN / 8; + bool valid_addr = (esp_tee_buf_in_ree(signature, max_sign) && + esp_tee_buf_in_ree(esp_ds_ctx, sizeof(esp_ds_context_t))); + if (!valid_addr) { + return ESP_ERR_INVALID_ARG; + } + + const esp_ds_data_t *data = (const esp_ds_data_t *)esp_ds_ctx->data; + valid_addr &= esp_tee_buf_in_ree(data, sizeof(esp_ds_data_t)) && + (get_ds_msg_sign_len(data->rsa_length) > 0); if (!valid_addr) { return ESP_ERR_INVALID_ARG; @@ -569,6 +583,13 @@ int _ss_esp_tee_ota_end(void) /* ---------------------------------------------- Secure Storage ------------------------------------------------- */ +/* NOTE: The key-name pointers here (cfg->id/ctx->key_id) are REE-supplied, NULL-terminated + * NVS key names used read-only for key lookup (NVS compares them with strncmp bounded to + * NVS_KEY_NAME_MAX_SIZE-1) — never written through, never used as a register base. + * Pointing one at TEE memory yields at most a load-fault DoS or a useless presence oracle, + * so they are left unchecked. Argument checks cost code size and add latency to every + * service call, so we keep only the ones that close a real REE->TEE read/write/control-flow gap. + */ esp_err_t _ss_esp_tee_sec_storage_clear_key(const char *key_id) { return esp_tee_sec_storage_clear_key(key_id); diff --git a/components/esp_tee/subproject/main/core/esp_secure_services_iram.c b/components/esp_tee/subproject/main/core/esp_secure_services_iram.c index c5b5092bc2c..b5030720fc4 100644 --- a/components/esp_tee/subproject/main/core/esp_secure_services_iram.c +++ b/components/esp_tee/subproject/main/core/esp_secure_services_iram.c @@ -4,6 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ #include +#include #include "esp_err.h" #include "esp_log.h" @@ -17,11 +18,14 @@ #include "hal/spi_flash_hal.h" #include "hal/spi_flash_types.h" #include "esp_flash_chips/spi_flash_chip_generic.h" +#include "esp_flash_chips/spi_flash_defs.h" #include "esp_private/memspi_host_driver.h" #include "esp_private/mspi_timing_tuning.h" #include "esp_flash.h" #include "esp_flash_chips/esp_flash_types.h" +#include "bootloader_flash_priv.h" #include "riscv/rv_utils.h" +#include "soc/soc.h" #include "esp_tee.h" #include "esp_tee_memory_utils.h" @@ -36,28 +40,49 @@ static __attribute__((unused)) const char *TAG = "esp_tee_sec_srv_iram"; /* ---------------------------------------------- Interrupts ------------------------------------------------- */ +#if SOC_INT_CLIC_SUPPORTED +#define TEE_RESERVED_INTR_MASK ((1U << TEE_SECURE_INUM) | (1U << TEE_PASS_INUM)) +#else +#define TEE_RESERVED_INTR_MASK ((1U << TEE_SECURE_INUM)) +#endif + +static inline bool is_intr_num_invalid(uint32_t intr_num) +{ + return (intr_num >= (uint32_t)SOC_CPU_INTR_NUM) || + (((1U << intr_num) & TEE_RESERVED_INTR_MASK) != 0U); +} + void _ss_esp_rom_route_intr_matrix(int cpu_no, uint32_t model_num, uint32_t intr_num) { + if (is_intr_num_invalid(intr_num)) { + return; + } return esp_tee_route_intr_matrix(cpu_no, model_num, intr_num); } void _ss_rv_utils_intr_enable(uint32_t intr_mask) { - rv_utils_tee_intr_enable(intr_mask); + rv_utils_tee_intr_enable(intr_mask & ~TEE_RESERVED_INTR_MASK); } void _ss_rv_utils_intr_disable(uint32_t intr_mask) { - rv_utils_tee_intr_disable(intr_mask); + rv_utils_tee_intr_disable(intr_mask & ~TEE_RESERVED_INTR_MASK); } void _ss_rv_utils_intr_set_priority(int rv_int_num, int priority) { + if (is_intr_num_invalid((uint32_t)rv_int_num)) { + return; + } rv_utils_tee_intr_set_priority(rv_int_num, priority); } void _ss_rv_utils_intr_set_type(int intr_num, enum intr_type type) { + if (is_intr_num_invalid((uint32_t)intr_num)) { + return; + } rv_utils_tee_intr_set_type(intr_num, type); } @@ -68,6 +93,9 @@ void _ss_rv_utils_intr_set_threshold(int priority_threshold) void _ss_rv_utils_intr_edge_ack(uint32_t intr_num) { + if (is_intr_num_invalid(intr_num)) { + return; + } rv_utils_intr_edge_ack(intr_num); } @@ -108,12 +136,24 @@ void _ss_rv_utils_wfe_mode_enable(bool en) #if SOC_INT_CLIC_SUPPORTED void _ss_esprv_int_set_vectored(int rv_int_num, bool vectored) { + if (is_intr_num_invalid((uint32_t)rv_int_num)) { + return; + } esprv_int_set_vectored(rv_int_num, vectored); } #endif /* ---------------------------------------------- RTC_WDT ------------------------------------------------- */ +static bool is_wdt_dev_valid(const void *dev) +{ + return (dev == (const void *)&TIMERG0) +#if TIMG_LL_GET(INST_NUM) >= 2 + || (dev == (const void *)&TIMERG1) +#endif + || (dev == (const void *)RWDT_DEV_GET()); +} + void _ss_wdt_hal_init(wdt_hal_context_t *hal, wdt_inst_t wdt_inst, uint32_t prescaler, bool enable_intr) { bool valid_addr = esp_tee_buf_in_ree(hal, sizeof(wdt_hal_context_t)); @@ -128,7 +168,8 @@ void _ss_wdt_hal_init(wdt_hal_context_t *hal, wdt_inst_t wdt_inst, uint32_t pres void _ss_wdt_hal_deinit(wdt_hal_context_t *hal) { - bool valid_addr = esp_tee_buf_in_ree(hal, sizeof(wdt_hal_context_t)); + bool valid_addr = (esp_tee_buf_in_ree(hal, sizeof(wdt_hal_context_t)) && + is_wdt_dev_valid(hal->mwdt_dev)); if (!valid_addr) { return; @@ -140,6 +181,14 @@ void _ss_wdt_hal_deinit(wdt_hal_context_t *hal) /* ---------------------------------------------- Secure Storage ------------------------------------------------- */ +/* NOTE: The key-name pointers here (cfg->id/ctx->key_id) are REE-supplied, NULL-terminated + * NVS key names used read-only for key lookup (NVS compares them with strncmp bounded to + * NVS_KEY_NAME_MAX_SIZE-1) — never written through, never used as a register base. + * Pointing one at TEE memory yields at most a load-fault DoS or a useless presence oracle, + * so they are left unchecked. Argument checks cost code size and add latency to every + * service call, so we keep only the ones that close a real REE->TEE read/write/control-flow gap. + * The buffers alongside these ARE validated, since the TEE reads/writes them. + */ esp_err_t _ss_esp_tee_sec_storage_ecdsa_sign(const esp_tee_sec_storage_key_cfg_t *cfg, const uint8_t *hash, size_t hlen, esp_tee_sec_storage_ecdsa_sign_t *out_sign) { bool valid_addr = (esp_tee_buf_in_ree(cfg, sizeof(esp_tee_sec_storage_key_cfg_t)) && @@ -291,16 +340,34 @@ void _ss_Cache_Set_IDROM_MMU_Size(uint32_t irom_size, uint32_t drom_size) #if CONFIG_SECURE_TEE_EXT_FLASH_MEMPROT_SPI1 /* ---------------------------------------------- SPI Flash HAL ------------------------------------------------- */ +#define FLASH_ADDR_MAX_24BIT (0xFFFFFFU) + +static bool is_flash_addr_writable(uint32_t paddr, uint32_t len) +{ + return !esp_tee_flash_check_prange_in_tee_region(paddr, len) && + !esp_tee_flash_check_prange_write_protected(paddr, len); +} + +static bool is_flash_addr_readable(uint32_t paddr, uint32_t len) +{ + return !esp_tee_flash_check_prange_in_tee_region(paddr, len); +} + static bool is_spi_host_in_ree(spi_flash_host_inst_t *host) { - return esp_tee_buf_in_ree(host, sizeof(spi_flash_host_inst_t)); + const spi_flash_hal_context_t *ctx = (const spi_flash_hal_context_t *)host; + + return (esp_tee_buf_in_ree(host, sizeof(spi_flash_hal_context_t)) && + ctx->spi == spi_flash_ll_get_hw(SPI1_HOST)); } static bool is_spi_trans_valid(spi_flash_host_inst_t *host, spi_flash_trans_t *trans) { - bool valid_addr = (is_spi_host_in_ree(host) && - esp_tee_buf_in_ree(trans, sizeof(spi_flash_trans_t))); + if (!is_spi_host_in_ree(host) || !esp_tee_buf_in_ree(trans, sizeof(spi_flash_trans_t))) { + return false; + } + bool valid_addr = true; if (trans->mosi_len != 0) { valid_addr &= esp_tee_buf_in_ree(trans->mosi_data, trans->mosi_len); } @@ -310,6 +377,37 @@ static bool is_spi_trans_valid(spi_flash_host_inst_t *host, spi_flash_trans_t *t return valid_addr; } +static bool is_spi_cmd_addr_ok(uint32_t addr_bitlen, uint32_t address, uint32_t mosi_len, uint32_t miso_len) +{ + if (addr_bitlen == 0) { + return true; + } + + if (addr_bitlen < 32U && address > ((1U << addr_bitlen) - 1U)) { + return false; + } + + uint32_t data_len = MAX(1, MAX(mosi_len, miso_len)); + return is_flash_addr_writable(address, data_len); +} + +extern void spi_flash_hal_poll_cmd_done(spi_flash_host_inst_t *host); +extern esp_err_t spi_flash_hal_configure_host_io_mode(spi_flash_host_inst_t *host, uint32_t command, + uint32_t addr_bitlen, int dummy_cyclelen_base, + esp_flash_io_mode_t io_mode); + +static const spi_flash_host_driver_t tee_host_driver = { + .poll_cmd_done = spi_flash_hal_poll_cmd_done, + .configure_host_io_mode = spi_flash_hal_configure_host_io_mode, +}; + +static inline const spi_flash_host_driver_t *tee_substitute_host_driver(spi_flash_host_inst_t *host) +{ + const spi_flash_host_driver_t *orig = host->driver; + host->driver = &tee_host_driver; + return orig; +} + uint32_t _ss_spi_flash_hal_check_status(spi_flash_host_inst_t *host) { bool valid_addr = is_spi_host_in_ree(host); @@ -324,17 +422,23 @@ uint32_t _ss_spi_flash_hal_check_status(spi_flash_host_inst_t *host) esp_err_t _ss_spi_flash_hal_common_command(spi_flash_host_inst_t *host, spi_flash_trans_t *trans) { - bool valid_addr = (is_spi_trans_valid(host, trans) && - !esp_tee_flash_check_prange_in_tee_region(trans->address, trans->mosi_len) && - !esp_tee_flash_check_prange_in_tee_region(trans->address, trans->miso_len)); + bool trans_valid = is_spi_trans_valid(host, trans); + if (!trans_valid) { + return ESP_ERR_INVALID_ARG; + } + ESP_FAULT_ASSERT(trans_valid); - if (!valid_addr) { + bool addr_ok = is_spi_cmd_addr_ok(trans->address_bitlen, trans->address, trans->mosi_len, trans->miso_len); + if (!addr_ok) { ESP_LOGD(TAG, "[%s] Illegal flash access at 0x%08x", __func__, trans->address); return ESP_ERR_INVALID_ARG; } - ESP_FAULT_ASSERT(valid_addr); + ESP_FAULT_ASSERT(addr_ok); - return spi_flash_hal_common_command(host, trans); + const spi_flash_host_driver_t *orig = tee_substitute_host_driver(host); + esp_err_t r = spi_flash_hal_common_command(host, trans); + host->driver = orig; + return r; } esp_err_t _ss_spi_flash_hal_device_config(spi_flash_host_inst_t *host) @@ -352,7 +456,8 @@ esp_err_t _ss_spi_flash_hal_device_config(spi_flash_host_inst_t *host) void _ss_spi_flash_hal_erase_block(spi_flash_host_inst_t *host, uint32_t start_address) { bool valid_addr = (is_spi_host_in_ree(host) && - !esp_tee_flash_check_paddr_in_tee_region(start_address)); + start_address <= FLASH_ADDR_MAX_24BIT && + is_flash_addr_writable(start_address, FLASH_BLOCK_SIZE)); if (!valid_addr) { ESP_LOGD(TAG, "[%s] Illegal flash access at 0x%08x", __func__, start_address); @@ -360,13 +465,16 @@ void _ss_spi_flash_hal_erase_block(spi_flash_host_inst_t *host, uint32_t start_a } ESP_FAULT_ASSERT(valid_addr); + const spi_flash_host_driver_t *orig = tee_substitute_host_driver(host); spi_flash_hal_erase_block(host, start_address); + host->driver = orig; } void _ss_spi_flash_hal_erase_sector(spi_flash_host_inst_t *host, uint32_t start_address) { bool valid_addr = (is_spi_host_in_ree(host) && - !esp_tee_flash_check_prange_in_tee_region(start_address, FLASH_SECTOR_SIZE)); + start_address <= FLASH_ADDR_MAX_24BIT && + is_flash_addr_writable(start_address, FLASH_SECTOR_SIZE)); if (!valid_addr) { ESP_LOGD(TAG, "[%s] Illegal flash access at 0x%08x", __func__, start_address); @@ -374,13 +482,16 @@ void _ss_spi_flash_hal_erase_sector(spi_flash_host_inst_t *host, uint32_t start_ } ESP_FAULT_ASSERT(valid_addr); + const spi_flash_host_driver_t *orig = tee_substitute_host_driver(host); spi_flash_hal_erase_sector(host, start_address); + host->driver = orig; } void _ss_spi_flash_hal_program_page(spi_flash_host_inst_t *host, const void *buffer, uint32_t address, uint32_t length) { bool valid_addr = (is_spi_host_in_ree(host) && - !esp_tee_flash_check_prange_in_tee_region(address, length) && + address <= FLASH_ADDR_MAX_24BIT && + is_flash_addr_writable(address, length) && esp_tee_buf_in_ree(buffer, length)); if (!valid_addr) { @@ -389,13 +500,15 @@ void _ss_spi_flash_hal_program_page(spi_flash_host_inst_t *host, const void *buf } ESP_FAULT_ASSERT(valid_addr); + const spi_flash_host_driver_t *orig = tee_substitute_host_driver(host); spi_flash_hal_program_page(host, buffer, address, length); + host->driver = orig; } esp_err_t _ss_spi_flash_hal_read(spi_flash_host_inst_t *host, void *buffer, uint32_t address, uint32_t read_len) { bool valid_addr = (is_spi_host_in_ree(host) && - !esp_tee_flash_check_prange_in_tee_region(address, read_len) && + is_flash_addr_readable(address, read_len) && esp_tee_buf_in_ree(buffer, read_len)); if (!valid_addr) { @@ -404,7 +517,10 @@ esp_err_t _ss_spi_flash_hal_read(spi_flash_host_inst_t *host, void *buffer, uint } ESP_FAULT_ASSERT(valid_addr); - return spi_flash_hal_read(host, buffer, address, read_len); + const spi_flash_host_driver_t *orig = tee_substitute_host_driver(host); + esp_err_t r = spi_flash_hal_read(host, buffer, address, read_len); + host->driver = orig; + return r; } void _ss_spi_flash_hal_resume(spi_flash_host_inst_t *host) @@ -416,7 +532,9 @@ void _ss_spi_flash_hal_resume(spi_flash_host_inst_t *host) } ESP_FAULT_ASSERT(valid_addr); + const spi_flash_host_driver_t *orig = tee_substitute_host_driver(host); spi_flash_hal_resume(host); + host->driver = orig; } esp_err_t _ss_spi_flash_hal_set_write_protect(spi_flash_host_inst_t *host, bool wp) @@ -428,7 +546,10 @@ esp_err_t _ss_spi_flash_hal_set_write_protect(spi_flash_host_inst_t *host, bool } ESP_FAULT_ASSERT(valid_addr); - return spi_flash_hal_set_write_protect(host, wp); + const spi_flash_host_driver_t *orig = tee_substitute_host_driver(host); + esp_err_t r = spi_flash_hal_set_write_protect(host, wp); + host->driver = orig; + return r; } esp_err_t _ss_spi_flash_hal_setup_read_suspend(spi_flash_host_inst_t *host, const spi_flash_sus_cmd_conf *sus_conf) @@ -477,7 +598,9 @@ void _ss_spi_flash_hal_suspend(spi_flash_host_inst_t *host) } ESP_FAULT_ASSERT(valid_addr); + const spi_flash_host_driver_t *orig = tee_substitute_host_driver(host); spi_flash_hal_suspend(host); + host->driver = orig; } /* ---------------------------------------------- SPI Flash Extras ------------------------------------------------- */ @@ -486,6 +609,22 @@ extern uint32_t bootloader_flash_execute_command_common(uint8_t command, uint32_ uint8_t dummy_len, uint8_t mosi_len, uint32_t mosi_data, uint8_t miso_len); +static inline bool ree_flash_cmd_allowed(uint8_t cmd) +{ + switch (cmd) { + case CMD_WRSR3: /* 0x11 - write status register 3 */ + case CMD_RDSR3: /* 0x15 - read status register 3 */ + case CMD_WRENVSR: /* 0x50 - write enable for volatile SR */ + case CMD_WRAP: /* 0x77 - flash wrap enable/clear (alt) */ + case CMD_RDID: /* 0x9F - read chip ID */ + case CMD_HPMEN: /* 0xA3 - HPM enable via command */ + case CMD_BURST_RD: /* 0xC0 - flash wrap enable/clear */ + return true; + default: + return false; + } +} + uint32_t _ss_bootloader_flash_execute_command_common( uint8_t command, uint32_t addr_len, uint32_t address, @@ -493,14 +632,18 @@ uint32_t _ss_bootloader_flash_execute_command_common( uint8_t mosi_len, uint32_t mosi_data, uint8_t miso_len) { - bool valid_addr = (!esp_tee_flash_check_prange_in_tee_region(address, mosi_len) && - !esp_tee_flash_check_prange_in_tee_region(address, miso_len)); + if (!ree_flash_cmd_allowed(command)) { + ESP_LOGD(TAG, "[%s] Disallowed flash command 0x%02x from REE", __func__, command); + return 0; + } + ESP_FAULT_ASSERT(ree_flash_cmd_allowed(command)); - if (!valid_addr) { + bool addr_ok = is_spi_cmd_addr_ok(addr_len, address, mosi_len / 8U, miso_len / 8U); + if (!addr_ok) { ESP_LOGD(TAG, "[%s] Illegal flash access at 0x%08x", __func__, address); return 0; } - ESP_FAULT_ASSERT(valid_addr); + ESP_FAULT_ASSERT(addr_ok); return bootloader_flash_execute_command_common(command, addr_len, address, dummy_len, mosi_len, mosi_data, miso_len); @@ -509,7 +652,7 @@ uint32_t _ss_bootloader_flash_execute_command_common( esp_err_t _ss_memspi_host_flush_cache(spi_flash_host_inst_t *host, uint32_t addr, uint32_t size) { bool valid_addr = (is_spi_host_in_ree(host) && - !esp_tee_flash_check_prange_in_tee_region(addr, size)); + is_flash_addr_readable(addr, size)); if (!valid_addr) { return ESP_ERR_INVALID_ARG; @@ -521,14 +664,25 @@ esp_err_t _ss_memspi_host_flush_cache(spi_flash_host_inst_t *host, uint32_t addr esp_err_t _ss_spi_flash_chip_generic_config_host_io_mode(esp_flash_t *chip, uint32_t flags) { - bool valid_addr = esp_tee_buf_in_ree(chip, sizeof(struct esp_flash_t)); + spi_flash_host_inst_t *host = NULL; + bool valid_addr = (esp_tee_buf_in_ree(chip, sizeof(struct esp_flash_t)) && + is_spi_host_in_ree((host = chip->host))); if (!valid_addr) { return ESP_ERR_INVALID_ARG; } ESP_FAULT_ASSERT(valid_addr); - return spi_flash_chip_generic_config_host_io_mode(chip, flags); + esp_flash_t chip_snap = { + .host = host, + .read_mode = chip->read_mode, + .hpm_dummy_ena = chip->hpm_dummy_ena, + }; + + const spi_flash_host_driver_t *orig = tee_substitute_host_driver(host); + esp_err_t r = spi_flash_chip_generic_config_host_io_mode(&chip_snap, flags); + host->driver = orig; + return r; } #if CONFIG_IDF_TARGET_ESP32C5 diff --git a/components/esp_tee/subproject/main/core/esp_tee_intr.c b/components/esp_tee/subproject/main/core/esp_tee_intr.c index db5a8f59154..3cf75328c40 100644 --- a/components/esp_tee/subproject/main/core/esp_tee_intr.c +++ b/components/esp_tee/subproject/main/core/esp_tee_intr.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2024-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -12,6 +12,7 @@ #include "soc/periph_defs.h" #include "soc/interrupts.h" #include "soc/interrupt_reg.h" +#include "soc/soc_caps.h" #include "esp_tee.h" #include "esp_tee_intr.h" @@ -34,6 +35,10 @@ static uint32_t protected_sources[INTR_SET_COUNT]; bool esp_tee_is_intr_src_protected(int source) { + if (source < 0 || source >= ETS_MAX_INTR_SOURCE) { + return false; + } + uint32_t base = source / INTR_SET_SIZE; uint32_t offset = source % INTR_SET_SIZE; @@ -57,14 +62,9 @@ void tee_unhandled_interrupt(void *arg) /* Interrupt Matrix configuration API to call from non-secure world */ void esp_tee_route_intr_matrix(int cpu_no, uint32_t model_num, uint32_t intr_num) { - if (esp_tee_is_intr_src_protected(model_num) || intr_num == TEE_SECURE_INUM) { + if (model_num >= ETS_MAX_INTR_SOURCE || esp_tee_is_intr_src_protected(model_num)) { return; } -#if SOC_INT_CLIC_SUPPORTED - if (intr_num == TEE_PASS_INUM) { - return; - } -#endif esp_rom_route_intr_matrix(cpu_no, model_num, intr_num); ESP_LOGV(TAG, "Connected src %d to int %d (cpu %d)", model_num, intr_num, cpu_no); diff --git a/components/esp_tee/subproject/main/include/esp_tee_memory_utils.h b/components/esp_tee/subproject/main/include/esp_tee_memory_utils.h index 7ddb6a0cfb3..e3ec2f50dfa 100644 --- a/components/esp_tee/subproject/main/include/esp_tee_memory_utils.h +++ b/components/esp_tee/subproject/main/include/esp_tee_memory_utils.h @@ -38,8 +38,13 @@ FORCE_INLINE_ATTR bool esp_tee_buf_in_ree(const void *p, size_t len) return false; } - return esp_tee_ptr_in_ree(p) && - esp_tee_ptr_in_ree((const char *)p + len - 1); + uintptr_t end = start + len; + return ((start >= SOC_NS_IDRAM_START && end <= SOC_NS_IDRAM_END) || + (start >= (uintptr_t)esp_tee_app_config.ns_drom_start && end <= SOC_S_MMU_MMAP_RESV_START_VADDR) +#if SOC_RTC_MEM_SUPPORTED + || (start >= SOC_RTC_DATA_LOW && end <= SOC_RTC_DATA_HIGH) +#endif + ); } #ifdef __cplusplus diff --git a/components/esp_tee/subproject/main/soc/common/esp_tee_crypto_reset.c b/components/esp_tee/subproject/main/soc/common/esp_tee_crypto_reset.c new file mode 100644 index 00000000000..a80429d6ecf --- /dev/null +++ b/components/esp_tee/subproject/main/soc/common/esp_tee_crypto_reset.c @@ -0,0 +1,78 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "soc/soc_caps.h" + +#if SOC_AES_SUPPORTED +#include "hal/aes_ll.h" +#endif +#if SOC_SHA_SUPPORTED +#include "hal/sha_ll.h" +#endif +#if SOC_MPI_SUPPORTED +#include "hal/mpi_ll.h" +#endif +#if SOC_ECC_SUPPORTED +#include "hal/ecc_ll.h" +#endif +#if SOC_HMAC_SUPPORTED +#include "hal/hmac_ll.h" +#endif +#if SOC_DIG_SIGN_SUPPORTED +#include "hal/ds_ll.h" +#endif +#if SOC_ECDSA_SUPPORTED +#include "hal/ecdsa_ll.h" +#endif + +#include "esp_tee.h" + +void esp_tee_soc_reset_crypto_peripherals(void) +{ + /* Reset the crypto peripherals to a clean state and leave their clocks disabled; drivers re-enable on demand */ +#if SOC_AES_SUPPORTED + aes_ll_enable_bus_clock(true); + aes_ll_reset_register(); + aes_ll_enable_bus_clock(false); +#endif + +#if SOC_SHA_SUPPORTED + sha_ll_enable_bus_clock(true); + sha_ll_reset_register(); + sha_ll_enable_bus_clock(false); +#endif + +#if SOC_MPI_SUPPORTED + mpi_ll_enable_bus_clock(true); + mpi_ll_reset_register(); + mpi_ll_enable_bus_clock(false); +#endif + +#if SOC_ECC_SUPPORTED + ecc_ll_enable_bus_clock(true); + ecc_ll_reset_register(); + ecc_ll_power_up(); + ecc_ll_enable_bus_clock(false); +#endif + +#if SOC_HMAC_SUPPORTED + hmac_ll_enable_bus_clock(true); + hmac_ll_reset_register(); + hmac_ll_enable_bus_clock(false); +#endif + +#if SOC_DIG_SIGN_SUPPORTED + ds_ll_enable_bus_clock(true); + ds_ll_reset_register(); + ds_ll_enable_bus_clock(false); +#endif + +#if SOC_ECDSA_SUPPORTED + ecdsa_ll_enable_bus_clock(true); + ecdsa_ll_reset_register(); + ecdsa_ll_enable_bus_clock(false); +#endif +} diff --git a/components/esp_tee/subproject/main/soc/esp32c5/esp_tee_secure_sys_cfg.c b/components/esp_tee/subproject/main/soc/esp32c5/esp_tee_secure_sys_cfg.c index 86e30ce1315..b9a3ec04f75 100644 --- a/components/esp_tee/subproject/main/soc/esp32c5/esp_tee_secure_sys_cfg.c +++ b/components/esp_tee/subproject/main/soc/esp32c5/esp_tee_secure_sys_cfg.c @@ -10,11 +10,6 @@ #include "riscv/encoding.h" #include "hal/apm_hal.h" -#include "hal/aes_ll.h" -#include "hal/sha_ll.h" -#include "hal/hmac_ll.h" -#include "hal/ds_ll.h" -#include "hal/ecc_ll.h" #include "soc/clic_reg.h" #include "soc/interrupts.h" @@ -70,14 +65,9 @@ void esp_tee_soc_secure_sys_init(void) REG_CLR_BIT(DR_REG_INTMTX_BASE + 4 * i, BIT(8)); } - /* TODO: IDF-8958 - * The values for the secure interrupt number and priority and - * the interrupt priority threshold (for both M and U mode) need - * to be investigated further - */ esprv_int_set_threshold(0); - esprv_int_set_priority(TEE_SECURE_INUM, 7); + esprv_int_set_priority(TEE_SECURE_INUM, TEE_SECURE_INUM_PRIO); esprv_int_set_type(TEE_SECURE_INUM, ESP_CPU_INTR_TYPE_LEVEL); esprv_int_enable(BIT(TEE_SECURE_INUM)); esprv_int_set_vectored(TEE_SECURE_INUM, true); @@ -114,12 +104,8 @@ void esp_tee_soc_secure_sys_init(void) esp_tee_protect_intr_src(ETS_SHA_INTR_SOURCE); // SHA esp_tee_protect_intr_src(ETS_ECC_INTR_SOURCE); // ECC - /* Disable protected crypto peripheral clocks; they will be toggled as needed when the peripheral is in use */ - aes_ll_enable_bus_clock(false); - sha_ll_enable_bus_clock(false); - hmac_ll_enable_bus_clock(false); - ds_ll_enable_bus_clock(false); - ecc_ll_enable_bus_clock(false); + /* Reset the protected crypto peripherals and leave their clocks disabled */ + esp_tee_soc_reset_crypto_peripherals(); } IRAM_ATTR inline void esp_tee_switch_to_ree(uint32_t ns_entry_addr) diff --git a/components/esp_tee/subproject/main/soc/esp32c6/esp_tee_secure_sys_cfg.c b/components/esp_tee/subproject/main/soc/esp32c6/esp_tee_secure_sys_cfg.c index 1da37191afa..8fc4c67aca1 100644 --- a/components/esp_tee/subproject/main/soc/esp32c6/esp_tee_secure_sys_cfg.c +++ b/components/esp_tee/subproject/main/soc/esp32c6/esp_tee_secure_sys_cfg.c @@ -16,11 +16,6 @@ #include "esp_cpu.h" #include "esp_log.h" #include "hal/apm_hal.h" -#include "hal/aes_ll.h" -#include "hal/sha_ll.h" -#include "hal/hmac_ll.h" -#include "hal/ds_ll.h" -#include "hal/ecc_ll.h" #include "esp_tee.h" #include "esp_tee_intr.h" @@ -68,13 +63,7 @@ void esp_tee_soc_secure_sys_init(void) esp_rom_route_intr_matrix(core_id, i, ETS_INVALID_INUM); } - /* TODO: IDF-8958 - * The values for the secure interrupt number and priority and - * the interrupt priority threshold (for both M and U mode) need - * to be investigated further - */ - /* TODO: Currently, we do not allow interrupts to be set up with a priority greater than 7, see intr_alloc.c */ - esprv_int_set_priority(TEE_SECURE_INUM, 7); + esprv_int_set_priority(TEE_SECURE_INUM, TEE_SECURE_INUM_PRIO); esprv_int_set_type(TEE_SECURE_INUM, ESP_CPU_INTR_TYPE_LEVEL); esprv_int_set_threshold(RVHAL_INTR_ENABLE_THRESH); esprv_int_enable(BIT(TEE_SECURE_INUM)); @@ -101,12 +90,8 @@ void esp_tee_soc_secure_sys_init(void) esp_tee_protect_intr_src(ETS_SHA_INTR_SOURCE); // SHA esp_tee_protect_intr_src(ETS_ECC_INTR_SOURCE); // ECC - /* Disable protected crypto peripheral clocks; they will be toggled as needed when the peripheral is in use */ - aes_ll_enable_bus_clock(false); - sha_ll_enable_bus_clock(false); - hmac_ll_enable_bus_clock(false); - ds_ll_enable_bus_clock(false); - ecc_ll_enable_bus_clock(false); + /* Reset the protected crypto peripherals and leave their clocks disabled */ + esp_tee_soc_reset_crypto_peripherals(); } IRAM_ATTR inline void esp_tee_switch_to_ree(uint32_t ree_entry_addr) diff --git a/components/esp_tee/subproject/main/soc/esp32c61/esp_tee_apm_prot_cfg.c b/components/esp_tee/subproject/main/soc/esp32c61/esp_tee_apm_prot_cfg.c index b3e963aca59..c0526d99b77 100644 --- a/components/esp_tee/subproject/main/soc/esp32c61/esp_tee_apm_prot_cfg.c +++ b/components/esp_tee/subproject/main/soc/esp32c61/esp_tee_apm_prot_cfg.c @@ -55,9 +55,9 @@ static const char *TAG = "esp_tee_apm_prot_cfg"; /* NOTE: Super-Watchdog and Brownout Detector protection */ #define LP_APM_SWD_REG_START (LP_WDT_SWD_CONFIG_REG) -#define LP_APM_SWD_REG_END (LP_WDT_INT_CLR_REG) +#define LP_APM_SWD_REG_END (LP_WDT_INT_CLR_REG + 4U) #define LP_APM_BOD_REG_START (LP_ANA_BOD_MODE0_CNTL_REG) -#define LP_APM_BOD_REG_END (LP_ANA_LP_INT_CLR_REG) +#define LP_APM_BOD_REG_END (LP_ANA_LP_INT_CLR_REG + 4U) /* NOTE: Following are the master IDs for setting the security mode and access through APM: * +---------+-------------+ diff --git a/components/esp_tee/subproject/main/soc/esp32c61/esp_tee_secure_sys_cfg.c b/components/esp_tee/subproject/main/soc/esp32c61/esp_tee_secure_sys_cfg.c index 46cba5c6dee..8bcafb52ece 100644 --- a/components/esp_tee/subproject/main/soc/esp32c61/esp_tee_secure_sys_cfg.c +++ b/components/esp_tee/subproject/main/soc/esp32c61/esp_tee_secure_sys_cfg.c @@ -10,9 +10,6 @@ #include "riscv/encoding.h" #include "hal/apm_hal.h" -#include "hal/sha_ll.h" -#include "hal/ecc_ll.h" -#include "hal/ecdsa_ll.h" #include "soc/clic_reg.h" #include "soc/interrupts.h" @@ -68,14 +65,9 @@ void esp_tee_soc_secure_sys_init(void) REG_CLR_BIT(DR_REG_INTMTX_BASE + 4 * i, BIT(8)); } - /* TODO: IDF-8958 - * The values for the secure interrupt number and priority and - * the interrupt priority threshold (for both M and U mode) need - * to be investigated further - */ esprv_int_set_threshold(0); - esprv_int_set_priority(TEE_SECURE_INUM, 7); + esprv_int_set_priority(TEE_SECURE_INUM, TEE_SECURE_INUM_PRIO); esprv_int_set_type(TEE_SECURE_INUM, ESP_CPU_INTR_TYPE_LEVEL); esprv_int_enable(BIT(TEE_SECURE_INUM)); esprv_int_set_vectored(TEE_SECURE_INUM, true); @@ -109,10 +101,8 @@ void esp_tee_soc_secure_sys_init(void) esp_tee_protect_intr_src(ETS_ECC_INTR_SOURCE); // ECC esp_tee_protect_intr_src(ETS_ECDSA_INTR_SOURCE); // ECDSA - /* Disable protected crypto peripheral clocks; they will be toggled as needed when the peripheral is in use */ - sha_ll_enable_bus_clock(false); - ecc_ll_enable_bus_clock(false); - ecdsa_ll_enable_bus_clock(false); + /* Reset the protected crypto peripherals and leave their clocks disabled */ + esp_tee_soc_reset_crypto_peripherals(); } IRAM_ATTR inline void esp_tee_switch_to_ree(uint32_t ns_entry_addr) diff --git a/components/esp_tee/subproject/main/soc/esp32h2/esp_tee_secure_sys_cfg.c b/components/esp_tee/subproject/main/soc/esp32h2/esp_tee_secure_sys_cfg.c index 397329461f1..1d7a86eacc4 100644 --- a/components/esp_tee/subproject/main/soc/esp32h2/esp_tee_secure_sys_cfg.c +++ b/components/esp_tee/subproject/main/soc/esp32h2/esp_tee_secure_sys_cfg.c @@ -16,11 +16,6 @@ #include "esp_cpu.h" #include "esp_log.h" #include "hal/apm_hal.h" -#include "hal/aes_ll.h" -#include "hal/sha_ll.h" -#include "hal/hmac_ll.h" -#include "hal/ds_ll.h" -#include "hal/ecc_ll.h" #include "esp_tee.h" #include "esp_tee_intr.h" @@ -68,18 +63,10 @@ void esp_tee_soc_secure_sys_init(void) esp_rom_route_intr_matrix(core_id, i, ETS_INVALID_INUM); } - /* TODO: IDF-8958 - * The values for the secure interrupt number and priority and - * the interrupt priority threshold (for both M and U mode) need - * to be investigated further - */ -#ifdef SOC_CPU_HAS_FLEXIBLE_INTC - /* TODO: Currently, we do not allow interrupts to be set up with a priority greater than 7, see intr_alloc.c */ - esprv_int_set_priority(TEE_SECURE_INUM, 7); + esprv_int_set_priority(TEE_SECURE_INUM, TEE_SECURE_INUM_PRIO); esprv_int_set_type(TEE_SECURE_INUM, ESP_CPU_INTR_TYPE_LEVEL); esprv_int_set_threshold(RVHAL_INTR_ENABLE_THRESH); esprv_int_enable(BIT(TEE_SECURE_INUM)); -#endif ESP_LOGD(TAG, "Initial interrupt config -"); ESP_LOGD(TAG, "mideleg: 0x%08x", RV_READ_CSR(mideleg)); @@ -101,12 +88,8 @@ void esp_tee_soc_secure_sys_init(void) esp_tee_protect_intr_src(ETS_SHA_INTR_SOURCE); // SHA esp_tee_protect_intr_src(ETS_ECC_INTR_SOURCE); // ECC - /* Disable protected crypto peripheral clocks; they will be toggled as needed when the peripheral is in use */ - aes_ll_enable_bus_clock(false); - sha_ll_enable_bus_clock(false); - hmac_ll_enable_bus_clock(false); - ds_ll_enable_bus_clock(false); - ecc_ll_enable_bus_clock(false); + /* Reset the protected crypto peripherals and leave their clocks disabled */ + esp_tee_soc_reset_crypto_peripherals(); } IRAM_ATTR inline void esp_tee_switch_to_ree(uint32_t ree_entry_addr) diff --git a/components/esp_tee/test_apps/.build-test-rules.yml b/components/esp_tee/test_apps/.build-test-rules.yml index 081d926e83a..b64c2f89e47 100644 --- a/components/esp_tee/test_apps/.build-test-rules.yml +++ b/components/esp_tee/test_apps/.build-test-rules.yml @@ -4,6 +4,8 @@ components/esp_tee/test_apps/tee_cli_app: disable: - if: IDF_TARGET not in ["esp32c6", "esp32c5", "esp32c61"] reason: only supported with c6, c5 and c61 + depends_components: + - esp_tee components/esp_tee/test_apps/tee_test_fw: disable: diff --git a/components/esp_tee/test_apps/tee_cli_app/README.md b/components/esp_tee/test_apps/tee_cli_app/README.md index 21e76c7d67d..9768291f558 100644 --- a/components/esp_tee/test_apps/tee_cli_app/README.md +++ b/components/esp_tee/test_apps/tee_cli_app/README.md @@ -138,9 +138,9 @@ help [] [-v <0|1>] ```log esp32c6> tee_att_info -I (8180) tee_attest: Attestation token - Length: 1587 +I (8180) tee_attest: Attestation token - Length: 1705 I (8180) tee_attest: Attestation token - Data: -'{"header":{"magic":"44fef7cc","encr_alg":"","sign_alg":"ecdsa_secp256r1_sha256","key_id":"tee_att_key0"},"eat":{"auth_challenge":"dcb9b53143ad6b081dad1a05c7ebda4e314d388762215799cf24ed52e9387678","client_id":262974944,"device_ver":0,"device_id":"cd9c173cb3675c7adfae243f0cd9841e4bce003237cb5321927a85a86cb4b32e","instance_id":"9616ef0ecf02cdc89a3749f8fc16b3103d5100bd42d9312fcd04593baa7bac64","psa_cert_ref":"0716053550477-10100","device_status":165,"sw_claims":{"tee":{"type":1,"ver":"v0.3.0","idf_ver":"v5.1.4-241-g7ff01fd46f-dirty","secure_ver":0,"part_chip_rev":{"min":0,"max":99},"part_digest":{"type":0,"calc_digest":"94536998e1dcb2a036477cb2feb01ed4fff67ba6208f30482346c62bca64b280","digest_validated":true,"sign_verified":true}},"app":{"type":2,"ver":"v0.1.0","idf_ver":"v5.1.4-241-g7ff01fd46f-dirty","secure_ver":0,"part_chip_rev":{"min":0,"max":99},"part_digest":{"type":0,"calc_digest":"3d4c038fcec76852b4d07acb9e94afaf5fca69fc2eb212a32032d09ce5b4f2b3","digest_validated":true,"sign_verified":true,"secure_padding":true}},"bootloader":{"type":0,"ver":"","idf_ver":"","secure_ver":-1,"part_chip_rev":{"min":0,"max":99},"part_digest":{"type":0,"calc_digest":"1bef421beb1a4642c6fcefb3e37fd4afad60cb4074e538f42605b012c482b946","digest_validated":true,"sign_verified":true}}}},"public_key":{"compressed":"02039c4bfab0762af1aff2fe5596b037f629cf839da8c4a9c0018afedfccf519a6"},"sign":{"r":"915e749f5a780bc21a2b21821cfeb54286dc742e9f12f2387e3de9b8b1a70bc9","s":"1e583236f2630b0fe8e291645ffa35d429f14035182e19868508d4dac0e1a441"}}' +'{"header":{"magic":"44fef7cc","encr_alg":"","sign_alg":"ecdsa_secp256r1_sha256","key_id":"tee_att_key0"},"eat":{"auth_challenge":"dcb9b53143ad6b081dad1a05c7ebda4e314d388762215799cf24ed52e9387678","client_id":262974944,"chip_id":13,"device_ver":0,"ueid":{"mac":"d885ac67c978","optional_id":"94fa4d7e305682714d48e7bbd710c961"},"device_id":"cd9c173cb3675c7adfae243f0cd9841e4bce003237cb5321927a85a86cb4b32e","instance_id":"9616ef0ecf02cdc89a3749f8fc16b3103d5100bd42d9312fcd04593baa7bac64","psa_cert_ref":"0716053550477-10100","device_status":165,"sw_claims":{"tee":{"type":1,"ver":"v0.3.0","idf_ver":"v5.1.4-241-g7ff01fd46f-dirty","secure_ver":0,"part_chip_rev":{"min":0,"max":99},"part_digest":{"type":0,"calc_digest":"94536998e1dcb2a036477cb2feb01ed4fff67ba6208f30482346c62bca64b280","digest_validated":true,"sign_verified":true}},"app":{"type":2,"ver":"v0.1.0","idf_ver":"v5.1.4-241-g7ff01fd46f-dirty","secure_ver":0,"part_chip_rev":{"min":0,"max":99},"part_digest":{"type":0,"calc_digest":"3d4c038fcec76852b4d07acb9e94afaf5fca69fc2eb212a32032d09ce5b4f2b3","digest_validated":true,"sign_verified":true,"secure_padding":true}},"bootloader":{"type":0,"ver":"","idf_ver":"","secure_ver":-1,"part_chip_rev":{"min":0,"max":99},"part_digest":{"type":0,"calc_digest":"1bef421beb1a4642c6fcefb3e37fd4afad60cb4074e538f42605b012c482b946","digest_validated":true,"sign_verified":true}}}},"public_key":{"compressed":"02039c4bfab0762af1aff2fe5596b037f629cf839da8c4a9c0018afedfccf519a6"},"sign":{"r":"915e749f5a780bc21a2b21821cfeb54286dc742e9f12f2387e3de9b8b1a70bc9","s":"1e583236f2630b0fe8e291645ffa35d429f14035182e19868508d4dac0e1a441"}}' ``` diff --git a/components/esp_tee/test_apps/tee_cli_app/sdkconfig.ci.default b/components/esp_tee/test_apps/tee_cli_app/sdkconfig.ci.default index e69de29bb2d..ccdf42de1af 100644 --- a/components/esp_tee/test_apps/tee_cli_app/sdkconfig.ci.default +++ b/components/esp_tee/test_apps/tee_cli_app/sdkconfig.ci.default @@ -0,0 +1,3 @@ +# Increasing TEE IRAM size +# 38KB +CONFIG_SECURE_TEE_IRAM_SIZE=0x9800 diff --git a/components/esp_tee/test_apps/tee_cli_app/sdkconfig.ci.release b/components/esp_tee/test_apps/tee_cli_app/sdkconfig.ci.release index b1a31c9631d..f4c39acfebb 100644 --- a/components/esp_tee/test_apps/tee_cli_app/sdkconfig.ci.release +++ b/components/esp_tee/test_apps/tee_cli_app/sdkconfig.ci.release @@ -1,3 +1,6 @@ +# NOTE: This sdkconfig is intended solely for CI build purposes - to verify ESP-TEE +# builds across various configurations - and is not intended for production use. + # Reducing TEE IRAM size # 30KB CONFIG_SECURE_TEE_IRAM_SIZE=0x7800 @@ -10,7 +13,11 @@ CONFIG_SECURE_TEE_SEC_STG_EFUSE_HMAC_KEY_ID=5 CONFIG_SECURE_TEE_EXT_FLASH_MEMPROT_SPI1=n # Secure Boot -CONFIG_PARTITION_TABLE_OFFSET=0xf000 +CONFIG_PARTITION_TABLE_OFFSET=0xF000 CONFIG_SECURE_BOOT=y +# ECDSA Secure Boot V2 is gated behind the insecure option on the affected SoCs +CONFIG_SECURE_BOOT_INSECURE=y +CONFIG_SECURE_BOOT_V2_FORCE_ENABLE_ECDSA=y +CONFIG_SECURE_SIGNED_APPS_ECDSA_V2_SCHEME=y CONFIG_SECURE_BOOT_BUILD_SIGNED_BINARIES=y -CONFIG_SECURE_BOOT_SIGNING_KEY="test_keys/secure_boot_signing_key.pem" +CONFIG_SECURE_BOOT_SIGNING_KEY="test_keys/secure_boot_signing_key_ecdsa_p256.pem" diff --git a/components/esp_tee/test_apps/tee_cli_app/sdkconfig.ci.sb_fe b/components/esp_tee/test_apps/tee_cli_app/sdkconfig.ci.sb_fe index fe40c08d00f..d7965f52905 100644 --- a/components/esp_tee/test_apps/tee_cli_app/sdkconfig.ci.sb_fe +++ b/components/esp_tee/test_apps/tee_cli_app/sdkconfig.ci.sb_fe @@ -1,6 +1,9 @@ +# NOTE: This sdkconfig is intended solely for CI build purposes - to verify ESP-TEE +# builds across various configurations - and is not intended for production use. + # Increasing TEE I/DRAM sizes -# 34KB -CONFIG_SECURE_TEE_IRAM_SIZE=0x8800 +# 38KB +CONFIG_SECURE_TEE_IRAM_SIZE=0x9800 # 22KB CONFIG_SECURE_TEE_DRAM_SIZE=0x5800 @@ -9,8 +12,12 @@ CONFIG_PARTITION_TABLE_OFFSET=0xf000 # Secure Boot CONFIG_SECURE_BOOT=y +# ECDSA Secure Boot V2 is gated behind the insecure option on the affected SoCs +CONFIG_SECURE_BOOT_INSECURE=y +CONFIG_SECURE_BOOT_V2_FORCE_ENABLE_ECDSA=y +CONFIG_SECURE_SIGNED_APPS_ECDSA_V2_SCHEME=y CONFIG_SECURE_BOOT_BUILD_SIGNED_BINARIES=y -CONFIG_SECURE_BOOT_SIGNING_KEY="test_keys/secure_boot_signing_key.pem" +CONFIG_SECURE_BOOT_SIGNING_KEY="test_keys/secure_boot_signing_key_ecdsa_p256.pem" # Flash Encryption CONFIG_SECURE_FLASH_ENC_ENABLED=y diff --git a/components/esp_tee/test_apps/tee_cli_app/sdkconfig.defaults b/components/esp_tee/test_apps/tee_cli_app/sdkconfig.defaults index c35d9943e4b..a0ae715a92c 100644 --- a/components/esp_tee/test_apps/tee_cli_app/sdkconfig.defaults +++ b/components/esp_tee/test_apps/tee_cli_app/sdkconfig.defaults @@ -20,3 +20,6 @@ CONFIG_EXAMPLE_OTA_RECV_TIMEOUT=30000 CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_DEFAULT_CMN=y CONFIG_MBEDTLS_CUSTOM_CERTIFICATE_BUNDLE=y CONFIG_MBEDTLS_CUSTOM_CERTIFICATE_BUNDLE_PATH="test_certs/server_cert.pem" + +# Takes effect only when Secure boot is enabled +CONFIG_SECURE_BOOT_FLASH_BOOTLOADER_DEFAULT=y diff --git a/components/esp_tee/test_apps/tee_cli_app/test_keys/secure_boot_signing_key_ecdsa_p256.pem b/components/esp_tee/test_apps/tee_cli_app/test_keys/secure_boot_signing_key_ecdsa_p256.pem new file mode 100644 index 00000000000..e9dd5863254 --- /dev/null +++ b/components/esp_tee/test_apps/tee_cli_app/test_keys/secure_boot_signing_key_ecdsa_p256.pem @@ -0,0 +1,5 @@ +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIFFwmnckyThKZQMV40ceAQm8OxwP1aI0dvWt3P9/4VAgoAoGCCqGSM49 +AwEHoUQDQgAEwMObAE6S2QjA4vYnifYGDO/Jd9Pr9p2CWKxQVTsziuqz2pJxzjcQ +zJT6Aj30auml+oIGvNwBnhoZ3v5SCyzqOw== +-----END EC PRIVATE KEY----- diff --git a/components/esp_tee/test_apps/tee_cli_app/test_keys/secure_boot_signing_key.pem b/components/esp_tee/test_apps/tee_cli_app/test_keys/secure_boot_signing_key_rsa_3072.pem similarity index 100% rename from components/esp_tee/test_apps/tee_cli_app/test_keys/secure_boot_signing_key.pem rename to components/esp_tee/test_apps/tee_cli_app/test_keys/secure_boot_signing_key_rsa_3072.pem diff --git a/components/esp_tee/test_apps/tee_test_fw/components/test_sec_srv/sec_srv_tbl_test.yml b/components/esp_tee/test_apps/tee_test_fw/components/test_sec_srv/sec_srv_tbl_test.yml index f0adf8dcad1..c7f37bba749 100644 --- a/components/esp_tee/test_apps/tee_test_fw/components/test_sec_srv/sec_srv_tbl_test.yml +++ b/components/esp_tee/test_apps/tee_test_fw/components/test_sec_srv/sec_srv_tbl_test.yml @@ -77,3 +77,7 @@ secure_services: type: custom function: esp_tee_test_stack_underflow args: 0 + - id: 219 + type: custom + function: esp_tee_test_read_sec_stg + args: 1 diff --git a/components/esp_tee/test_apps/tee_test_fw/components/test_sec_srv/src/test_sec_srv.c b/components/esp_tee/test_apps/tee_test_fw/components/test_sec_srv/src/test_sec_srv.c index 6978a657803..0547c79d54c 100644 --- a/components/esp_tee/test_apps/tee_test_fw/components/test_sec_srv/src/test_sec_srv.c +++ b/components/esp_tee/test_apps/tee_test_fw/components/test_sec_srv/src/test_sec_srv.c @@ -1,13 +1,21 @@ /* - * SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2024-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ +#include +#include + #include "esp_cpu.h" +#include "esp_err.h" #include "esp_log.h" #include "esp_tee.h" +#include "esp_tee_flash.h" +#include "esp_tee_memory_utils.h" #include "esp_tee_test.h" #include "esp_attr.h" +#include "esp_flash_partitions.h" + static const char *TAG = "test_sec_srv"; /* Sample Trusted App */ @@ -54,3 +62,23 @@ uint32_t _ss_esp_tee_test_priv_mode_switch(uint32_t *a, uint32_t *b) return c; } + +esp_err_t _ss_esp_tee_test_read_sec_stg(uint8_t *buf) +{ + if (!esp_tee_buf_in_ree(buf, FLASH_SECTOR_SIZE)) { + return ESP_ERR_INVALID_ARG; + } + + esp_partition_info_t pinfo; + esp_err_t err = esp_tee_flash_find_partition(PART_TYPE_DATA, PART_SUBTYPE_DATA_WIFI, + ESP_TEE_SEC_STG_PART_LABEL, &pinfo); + if (err != ESP_OK) { + return err; + } + + if (pinfo.pos.size < FLASH_SECTOR_SIZE) { + return ESP_ERR_INVALID_SIZE; + } + + return (esp_err_t)esp_tee_flash_read(pinfo.pos.offset, (uint32_t *)buf, FLASH_SECTOR_SIZE, false); +} diff --git a/components/esp_tee/test_apps/tee_test_fw/conftest.py b/components/esp_tee/test_apps/tee_test_fw/conftest.py index 66cdddb7d61..b025bc4db43 100644 --- a/components/esp_tee/test_apps/tee_test_fw/conftest.py +++ b/components/esp_tee/test_apps/tee_test_fw/conftest.py @@ -4,6 +4,7 @@ import base64 import csv import os +import re import shutil import subprocess import sys @@ -202,25 +203,6 @@ class TEESerial(IdfSerial): def _get_flash_size(self) -> Any: return self.app.sdkconfig.get('ESPTOOLPY_FLASHSIZE', '') - @EspSerial.use_esptool() - def bootloader_force_flash_if_req(self) -> None: - # Forcefully flash the bootloader only if security features are enabled - if any( - ( - self.app.sdkconfig.get('SECURE_BOOT', True), - self.app.sdkconfig.get('SECURE_FLASH_ENC_ENABLED', True), - ) - ): - offs = int(self.app.sdkconfig.get('BOOTLOADER_OFFSET_IN_FLASH', 0)) - bootloader_path = os.path.join(self.app.binary_path, 'bootloader', 'bootloader.bin') - encrypt = '--encrypt' if self.app.sdkconfig.get('SECURE_FLASH_ENC_ENABLED') else '' - flash_size = self._get_flash_size() - - esptool.main( - f'--no-stub write-flash {offs} {bootloader_path} --force {encrypt} --flash-size {flash_size}'.split(), - esp=self.esp, - ) - @EspSerial.use_esptool() def custom_erase_partition(self, partition: str) -> None: if self.app.sdkconfig.get('SECURE_ENABLE_SECURE_ROM_DL_MODE'): @@ -294,29 +276,133 @@ class TEESerial(IdfSerial): if os.path.exists(file): os.remove(file) - @EspSerial.use_esptool() - def custom_flash(self) -> None: - self.bootloader_force_flash_if_req() - self.flash() - @EspSerial.use_esptool() def custom_flash_w_test_tee_img_gen(self) -> None: - self.bootloader_force_flash_if_req() self.flash() self.copy_test_tee_img('ota_1', False) @EspSerial.use_esptool() def custom_flash_w_test_tee_img_rb(self) -> None: - self.bootloader_force_flash_if_req() self.flash() self.copy_test_tee_img('ota_1', True) @EspSerial.use_esptool() def custom_flash_with_empty_sec_stg(self) -> None: - self.bootloader_force_flash_if_req() self.flash() self.custom_erase_partition('secure_storage') + KEY_DEFS_ENCRYPTION_TEST: list[str] = [ + 'aes256_key0', + 'aes256_key1', + 'attest_key', + 'ecdsa_p256_key0', + ] + + # TEE Secure Storage Development mode + # NVS XTS-AES keys: E-key=0x33*32 || T-key=0xCC*32 + NVS_KEYS_DEV_B64 = 'MzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzPMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzA==' + + @property + def nvs_partition_gen(self) -> str: + return str( + Path(os.environ['IDF_PATH']) + / 'components' + / 'nvs_flash' + / 'nvs_partition_generator' + / 'nvs_partition_gen.py' + ) + + def derive_sec_stg_nvs_keys(self, out_path: Path) -> None: + out_path.parent.mkdir(parents=True, exist_ok=True) + if self.app.sdkconfig.get('SECURE_TEE_SEC_STG_MODE_RELEASE'): + hmac_key_src = self.TEST_KEYS_DIR / 'tee_sec_stg_hmac_key.bin' + self.run_command( + [ + sys.executable, + self.nvs_partition_gen, + 'generate-key', + '--key_protect_hmac', + '--kp_hmac_inputkey', + str(hmac_key_src), + '--keyfile', + out_path.name, + '--outdir', + str(out_path.parent), + ] + ) + (out_path.parent / 'keys' / out_path.name).replace(out_path) + else: + self.write_keys_to_file(self.NVS_KEYS_DEV_B64, out_path) + + def decrypt_sec_stg_partition(self, dump_path: Path, keys_path: Path, decrypted_path: Path) -> None: + self.run_command( + [sys.executable, self.nvs_partition_gen, 'decrypt', str(dump_path), str(keys_path), str(decrypted_path)] + ) + + _SEC_STG_HEX_LINE_RE = re.compile( + rb'test_esp_tee_sec_storage:\s+((?:[0-9a-f]{2} ){0,15}[0-9a-f]{2})', + ) + SEC_STG_DUMP_SZ = 4096 + + def capture_sec_stg_partition_dump(self, dut: Any, timeout: float = 60) -> bytes: + dut.expect_exact('SEC_STG_DUMP_BEGIN', timeout=timeout) + blob = dut.expect_exact('SEC_STG_DUMP_END', timeout=timeout, return_what_before_match=True) + + raw = bytearray() + for match in self._SEC_STG_HEX_LINE_RE.finditer(blob): + for tok in match.group(1).split(): + raw.append(int(tok, 16)) + + if len(raw) != self.SEC_STG_DUMP_SZ: + raise RuntimeError( + f'Hex dump parse mismatch: got {len(raw)} bytes, expected {self.SEC_STG_DUMP_SZ}.\n' + f'Blob (first 256 bytes): {blob[:256]!r}' + ) + + # Make sure the Unity case actually passed before we trust the dump. + m = dut.expect(re.compile(rb'(\d+) Tests (\d+) Failures (\d+) Ignored'), timeout=timeout) + if int(m.group(2)) != 0: + raise RuntimeError(f'Unity reported {m.group(2).decode()} failures while running encryption test') + + return bytes(raw) + + def _run_nvs_tool_minimal(self, partition_file: Path) -> subprocess.CompletedProcess: + nvs_tool = Path(os.environ['IDF_PATH']) / 'components' / 'nvs_flash' / 'nvs_partition_tool' / 'nvs_tool.py' + return subprocess.run( + [sys.executable, str(nvs_tool), '-d', 'minimal', '--color', 'never', str(partition_file)], + capture_output=True, + text=True, + ) + + def verify_tee_sec_stg_encryption(self, dut: Any) -> None: + tmp_dir = self.TMP_DIR / 'sec_stg_encryption' + tmp_dir.mkdir(parents=True, exist_ok=True) + raw_path = tmp_dir / 'tee_sec_stg_dump.bin' + keys_path = tmp_dir / self.NVS_KEYS_FILE + decrypted_path = tmp_dir / 'tee_sec_stg_decr.bin' + expected_key_ids = self.KEY_DEFS_ENCRYPTION_TEST + + print('Verifying TEE Secure Storage NVS partition encryption (XTS-AES-512: 256-bit AES, 512-bit total key)') + try: + raw_bytes = self.capture_sec_stg_partition_dump(dut) + raw_path.write_bytes(raw_bytes) + + self.derive_sec_stg_nvs_keys(keys_path) + self.decrypt_sec_stg_partition(raw_path, keys_path, decrypted_path) + + print('Confirming key IDs are NOT present in the raw (encrypted) NVS dump') + raw_parse = self._run_nvs_tool_minimal(raw_path) + for key_id in expected_key_ids: + assert key_id not in raw_parse.stdout, f'{key_id!r} surfaced in raw dump (not encrypted)' + + print('Confirming key IDs ARE present after XTS-AES decrypt with the derived NVS keys') + decrypted_parse = self._run_nvs_tool_minimal(decrypted_path) + assert decrypted_parse.returncode == 0, f'nvs_tool exit {decrypted_parse.returncode} on decrypted dump' + for key_id in expected_key_ids: + assert key_id in decrypted_parse.stdout, f'{key_id!r} missing after decrypt (wrong XTS-AES key?)' + finally: + shutil.rmtree(tmp_dir, ignore_errors=True) + KEY_DEFS: list[dict[str, Any]] = [ {'key': 'aes256_key0', 'type': 'aes256', 'input': None, 'write_once': True}, { @@ -354,12 +440,11 @@ class TEESerial(IdfSerial): }, ] - NVS_KEYS_B64 = 'MzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzPMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzA==' - + TEST_KEYS_DIR = Path(__file__).parent / 'test_keys' TMP_DIR = Path('tmp') - NVS_KEYS_PATH = TMP_DIR / 'nvs_keys.bin' NVS_CSV_PATH = TMP_DIR / 'tee_sec_stg_val.csv' NVS_BIN_PATH = TMP_DIR / 'tee_sec_stg_nvs.bin' + NVS_KEYS_FILE = 'tee_sec_stg_nvs_keys.bin' def run_command(self, command: list[str]) -> None: try: @@ -391,16 +476,19 @@ class TEESerial(IdfSerial): input_path = tmp_dir / entry['input'] self.write_keys_to_file(entry['b64'], input_path) entry['input'] = str(input_path) - self.write_keys_to_file(self.NVS_KEYS_B64, self.NVS_KEYS_PATH) - idf_path = Path(os.environ['IDF_PATH']) ESP_TEE_SEC_STG_KEYGEN = os.path.join( - idf_path, 'components', 'esp_tee', 'scripts', 'esp_tee_sec_stg_keygen', 'esp_tee_sec_stg_keygen.py' - ) - NVS_PARTITION_GEN = os.path.join( - idf_path, 'components', 'nvs_flash', 'nvs_partition_generator', 'nvs_partition_gen.py' + os.environ['IDF_PATH'], + 'components', + 'esp_tee', + 'scripts', + 'esp_tee_sec_stg_keygen', + 'esp_tee_sec_stg_keygen.py', ) + nvs_keys = tmp_dir / self.NVS_KEYS_FILE + self.derive_sec_stg_nvs_keys(nvs_keys) + cmds = [ [sys.executable, ESP_TEE_SEC_STG_KEYGEN, '-k', entry['type'], '-o', str(tmp_dir / f'{entry["key"]}.bin')] + (['-i', entry['input']] if entry['input'] else []) @@ -410,13 +498,12 @@ class TEESerial(IdfSerial): csv_path = self.create_tee_sec_stg_csv(tmp_dir) nvs_bin = self.NVS_BIN_PATH - nvs_keys = self.NVS_KEYS_PATH size = self.app.partition_table['secure_storage']['size'] cmds.append( [ sys.executable, - NVS_PARTITION_GEN, + self.nvs_partition_gen, 'encrypt', str(csv_path), str(nvs_bin), @@ -430,10 +517,9 @@ class TEESerial(IdfSerial): for cmd in cmds: self.run_command(cmd) - self.bootloader_force_flash_if_req() self.flash() self.custom_erase_partition('secure_storage') - self.custom_write_partition('secure_storage', nvs_bin) + self.custom_write_partition('secure_storage', str(nvs_bin)) finally: shutil.rmtree(tmp_dir) diff --git a/components/esp_tee/test_apps/tee_test_fw/main/test_esp_tee_ota.c b/components/esp_tee/test_apps/tee_test_fw/main/test_esp_tee_ota.c index 6096e49ac3e..5e305af3839 100644 --- a/components/esp_tee/test_apps/tee_test_fw/main/test_esp_tee_ota.c +++ b/components/esp_tee/test_apps/tee_test_fw/main/test_esp_tee_ota.c @@ -127,8 +127,8 @@ TEST_CASE("Test TEE OTA - Corrupted image", "[ota_neg_2]") /* Corrupting the image */ ESP_LOGI(TAG, "Corrupting the image at some offset..."); uint32_t corrupt[8] = {[0 ... 7] = 0x0BADC0DE}; - curr_write_offset -= (2 * FLASH_SECTOR_SIZE + sizeof(corrupt)); - TEST_ESP_OK(esp_tee_ota_write(curr_write_offset, (const void *)corrupt, sizeof(corrupt))); + uint32_t offs = SOC_MMU_PAGE_SIZE + 0x200; + TEST_ESP_OK(esp_tee_ota_write(offs, (const void *)corrupt, sizeof(corrupt))); TEST_ESP_ERR(ESP_ERR_IMAGE_INVALID, esp_tee_ota_end()); } diff --git a/components/esp_tee/test_apps/tee_test_fw/main/test_esp_tee_sec_stg.c b/components/esp_tee/test_apps/tee_test_fw/main/test_esp_tee_sec_stg.c index 06860307328..c5ea5f79781 100644 --- a/components/esp_tee/test_apps/tee_test_fw/main/test_esp_tee_sec_stg.c +++ b/components/esp_tee/test_apps/tee_test_fw/main/test_esp_tee_sec_stg.c @@ -3,6 +3,7 @@ * * SPDX-License-Identifier: Apache-2.0 */ +#include #include #include "esp_log.h" @@ -364,6 +365,45 @@ TEST_CASE("Test TEE Secure Storage - Null Pointer and Zero Length", "[sec_storag TEST_ESP_OK(esp_tee_sec_storage_clear_key(key_cfg.id)); } +TEST_CASE("Test TEE Secure Storage - Verify data encryption", "[sec_storage_encr]") +{ + ESP_LOGI(TAG, "Populating NVS-based TEE Secure Storage; encrypted with XTS-AES-512"); + static const struct { + const char *id; + esp_tee_sec_storage_type_t type; + uint32_t flags; + } key_cfgs[] = { + { "aes256_key0", ESP_SEC_STG_KEY_AES256, SEC_STORAGE_FLAG_WRITE_ONCE }, + { "aes256_key1", ESP_SEC_STG_KEY_AES256, SEC_STORAGE_FLAG_NONE }, + { "attest_key", ESP_SEC_STG_KEY_ECDSA_SECP256R1, SEC_STORAGE_FLAG_WRITE_ONCE }, + { "ecdsa_p256_key0", ESP_SEC_STG_KEY_ECDSA_SECP256R1, SEC_STORAGE_FLAG_NONE }, + }; + + for (size_t i = 0; i < sizeof(key_cfgs) / sizeof(key_cfgs[0]); i++) { + esp_tee_sec_storage_key_cfg_t cfg = { + .id = key_cfgs[i].id, + .type = key_cfgs[i].type, + .flags = key_cfgs[i].flags, + }; + if ((cfg.flags & SEC_STORAGE_FLAG_WRITE_ONCE) == 0) { + esp_err_t err = esp_tee_sec_storage_clear_key(cfg.id); + TEST_ASSERT_TRUE(err == ESP_OK || err == ESP_ERR_NOT_FOUND); + } + TEST_ESP_OK(esp_tee_sec_storage_gen_key(&cfg)); + } + + const size_t dump_sz = 4096; + uint8_t *buf = heap_caps_malloc(dump_sz, MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL); + TEST_ASSERT_NOT_NULL(buf); + + TEST_ESP_OK((esp_err_t)esp_tee_service_call(2, SS_ESP_TEE_TEST_READ_SEC_STG, buf)); + printf("\nSEC_STG_DUMP_BEGIN\n"); + ESP_LOG_BUFFER_HEX(TAG, buf, dump_sz); + printf("SEC_STG_DUMP_END\n"); + + free(buf); +} + TEST_CASE("Test TEE Secure Storage - WRITE_ONCE keys", "[sec_storage]") { const char *key_id = "key_id_test_wo"; @@ -425,6 +465,10 @@ static void do_ecdsa_sign_and_verify(const esp_tee_sec_storage_key_cfg_t *cfg, c TEST_ESP_OK(verify_ecdsa_sign(cfg->type, digest, digest_len, &pubkey, &sign)); } +/* NOTE: In release mode (CONFIG_SECURE_TEE_SEC_STG_MODE_RELEASE), the test expects + * the eFuse-burned HMAC key used for TEE secure storage to be available at + * the path "test_keys/tee_sec_stg_hmac_key.bin" + */ TEST_CASE("Test TEE Secure Storage - Host-generated keys", "[sec_storage_host_keygen]") { const char *aes_key_ids[] = { "aes256_key0", "aes256_key1" }; diff --git a/components/esp_tee/test_apps/tee_test_fw/pytest_esp_tee_ut.py b/components/esp_tee/test_apps/tee_test_fw/pytest_esp_tee_ut.py index 73590e48d2a..791bc61d1cd 100644 --- a/components/esp_tee/test_apps/tee_test_fw/pytest_esp_tee_ut.py +++ b/components/esp_tee/test_apps/tee_test_fw/pytest_esp_tee_ut.py @@ -6,7 +6,7 @@ from enum import Enum import pytest from pytest_embedded_idf import IdfDut from pytest_embedded_idf.utils import idf_parametrize -from tee_exception_cfg import TEE_EXCEPTION_TEST_MAP +from tee_exception_test_map import TEE_EXCEPTION_TEST_MAP # ---------------- Pytest build parameters ---------------- @@ -20,6 +20,12 @@ CONFIG_DEFAULT = [ ] CONFIG_OTA = [ + # 'config, target, markers', + ('tee_ota', target, (pytest.mark.generic,)) + for target in TESTING_TARGETS +] + +CONFIG_OTA_NO_AUTOFLASH = [ # 'config, target, skip_autoflash, markers', ('tee_ota', target, 'y', (pytest.mark.generic,)) for target in TESTING_TARGETS @@ -50,8 +56,10 @@ def test_esp_tee(dut: IdfDut) -> None: CONFIG_ALL, indirect=['config', 'target'], ) -@pytest.mark.skipif(targets=['esp32c61'], reason='Not supported') def test_esp_tee_crypto_aes(dut: IdfDut) -> None: + if dut.target == 'esp32c61': + pytest.skip(f'AES not supported on {dut.target}') + dut.run_all_single_board_cases(group='aes') dut.run_all_single_board_cases(group='aes-gcm') @@ -72,8 +80,10 @@ def test_esp_tee_crypto_sha(dut: IdfDut) -> None: CONFIG_ALL, indirect=['config', 'target'], ) -@pytest.mark.skipif(targets=['esp32c61'], reason='Not supported') def test_esp_tee_aes_perf(dut: IdfDut) -> None: + if dut.target == 'esp32c61': + pytest.skip(f'AES not supported on {dut.target}') + for i in range(10): dut.run_all_single_board_cases(name=['mbedtls AES performance']) @@ -121,6 +131,13 @@ def test_esp_tee_isolation_checks(dut: IdfDut) -> None: for test_name, expected in cfg.items(): run_exception_case(dut, 'Test REE-TEE isolation', test_name, expected, check_origin=True) + # ESP32-C61: MMU-spillover gracefully reboots instead of panicking + if dut.target == 'esp32c61': + dut.skip_decode_panic = True + dut.expect_exact('Press ENTER to see the list of tests') + dut.write('"Test REE-TEE isolation: MMU-spillover"') + dut.expect_exact('Failed MMU operation, rebooting!', timeout=10) + @idf_parametrize( 'config, target, markers', @@ -238,8 +255,6 @@ def run_flash_access_test(dut: IdfDut, api: TeeFlashAccessApi, test_name: str) - # Panics are expected during these tests dut.skip_decode_panic = True - dut.serial.custom_flash() - extra_data = dut._parse_test_menu() test_case = next((tc for tc in extra_data if tc.name == test_name), None) @@ -250,9 +265,9 @@ def run_flash_access_test(dut: IdfDut, api: TeeFlashAccessApi, test_name: str) - @idf_parametrize( - 'config, target, skip_autoflash, markers', + 'config, target, markers', CONFIG_OTA, - indirect=['config', 'target', 'skip_autoflash'], + indirect=['config', 'target'], ) def test_esp_tee_flash_prot_esp_partition_mmap(dut: IdfDut) -> None: run_flash_access_test( @@ -261,9 +276,9 @@ def test_esp_tee_flash_prot_esp_partition_mmap(dut: IdfDut) -> None: @idf_parametrize( - 'config, target, skip_autoflash, markers', + 'config, target, markers', CONFIG_OTA, - indirect=['config', 'target', 'skip_autoflash'], + indirect=['config', 'target'], ) def test_esp_tee_flash_prot_spi_flash_mmap(dut: IdfDut) -> None: run_flash_access_test( @@ -272,9 +287,9 @@ def test_esp_tee_flash_prot_spi_flash_mmap(dut: IdfDut) -> None: @idf_parametrize( - 'config, target, skip_autoflash, markers', + 'config, target, markers', CONFIG_OTA, - indirect=['config', 'target', 'skip_autoflash'], + indirect=['config', 'target'], ) def test_esp_tee_flash_prot_esp_rom_spiflash(dut: IdfDut) -> None: run_flash_access_test( @@ -283,18 +298,18 @@ def test_esp_tee_flash_prot_esp_rom_spiflash(dut: IdfDut) -> None: @idf_parametrize( - 'config, target, skip_autoflash, markers', + 'config, target, markers', CONFIG_OTA, - indirect=['config', 'target', 'skip_autoflash'], + indirect=['config', 'target'], ) def test_esp_tee_flash_prot_esp_partition(dut: IdfDut) -> None: run_flash_access_test(dut, TeeFlashAccessApi.ESP_PARTITION, 'Test REE-TEE isolation: Flash - SPI1 (esp_partition)') @idf_parametrize( - 'config, target, skip_autoflash, markers', + 'config, target, markers', CONFIG_OTA, - indirect=['config', 'target', 'skip_autoflash'], + indirect=['config', 'target'], ) def test_esp_tee_flash_prot_esp_flash(dut: IdfDut) -> None: run_flash_access_test(dut, TeeFlashAccessApi.ESP_FLASH, 'Test REE-TEE isolation: Flash - SPI1 (esp_flash)') @@ -303,9 +318,11 @@ def test_esp_tee_flash_prot_esp_flash(dut: IdfDut) -> None: # ---------------- TEE Local OTA tests ---------------- -@pytest.mark.generic -@idf_parametrize('config', ['tee_ota'], indirect=['config']) -@idf_parametrize('target', TESTING_TARGETS, indirect=['target']) +@idf_parametrize( + 'config, target, markers', + CONFIG_OTA, + indirect=['config', 'target'], +) def test_esp_tee_ota_negative(dut: IdfDut) -> None: # start test dut.run_all_single_board_cases(group='ota_neg_1', timeout=10) @@ -313,7 +330,7 @@ def test_esp_tee_ota_negative(dut: IdfDut) -> None: @idf_parametrize( 'config, target, skip_autoflash, markers', - CONFIG_OTA, + CONFIG_OTA_NO_AUTOFLASH, indirect=['config', 'target', 'skip_autoflash'], ) def test_esp_tee_ota_corrupted_img(dut: IdfDut) -> None: @@ -347,7 +364,7 @@ def tee_ota_stage_checks(dut: IdfDut, stage: TeeOtaStage, offset: str) -> None: @idf_parametrize( 'config, target, skip_autoflash, markers', - CONFIG_OTA, + CONFIG_OTA_NO_AUTOFLASH, indirect=['config', 'target', 'skip_autoflash'], ) def test_esp_tee_ota_reboot_without_ota_end(dut: IdfDut) -> None: @@ -370,7 +387,7 @@ def test_esp_tee_ota_reboot_without_ota_end(dut: IdfDut) -> None: @idf_parametrize( 'config, target, skip_autoflash, markers', - CONFIG_OTA, + CONFIG_OTA_NO_AUTOFLASH, indirect=['config', 'target', 'skip_autoflash'], ) def test_esp_tee_ota_valid_img(dut: IdfDut) -> None: @@ -401,7 +418,7 @@ def test_esp_tee_ota_valid_img(dut: IdfDut) -> None: @idf_parametrize( 'config, target, skip_autoflash, markers', - CONFIG_OTA, + CONFIG_OTA_NO_AUTOFLASH, indirect=['config', 'target', 'skip_autoflash'], ) def test_esp_tee_ota_rollback(dut: IdfDut) -> None: @@ -440,7 +457,7 @@ def test_esp_tee_ota_rollback(dut: IdfDut) -> None: @idf_parametrize( 'config, target, skip_autoflash, markers', - CONFIG_OTA, + CONFIG_OTA_NO_AUTOFLASH, indirect=['config', 'target', 'skip_autoflash'], ) def test_esp_tee_secure_storage(dut: IdfDut) -> None: @@ -452,22 +469,43 @@ def test_esp_tee_secure_storage(dut: IdfDut) -> None: @idf_parametrize( 'config, target, skip_autoflash, markers', - CONFIG_OTA, + CONFIG_OTA_NO_AUTOFLASH, indirect=['config', 'target', 'skip_autoflash'], ) def test_esp_tee_secure_storage_with_host_img(dut: IdfDut) -> None: # Flash image and write the secure_storage partition with host-generated keys + + # NOTE: In release mode (CONFIG_SECURE_TEE_SEC_STG_MODE_RELEASE), the test + # expects the eFuse-burned HMAC key used for TEE secure storage to be available + # at the path "test_keys/tee_sec_stg_hmac_key.bin" dut.serial.custom_flash_with_host_gen_sec_stg_img() dut.run_all_single_board_cases(group='sec_storage_host_keygen') +@idf_parametrize( + 'config, target, skip_autoflash, markers', + CONFIG_OTA_NO_AUTOFLASH, + indirect=['config', 'target', 'skip_autoflash'], +) +def test_esp_tee_secure_storage_encryption(dut: IdfDut) -> None: + dut.serial.custom_flash_with_empty_sec_stg() + + # NOTE: In release mode (CONFIG_SECURE_TEE_SEC_STG_MODE_RELEASE), the test + # expects the eFuse-burned HMAC key used for TEE secure storage to be available + # at the path "test_keys/tee_sec_stg_hmac_key.bin" + dut.expect_exact('Press ENTER to see the list of tests') + dut.write('"Test TEE Secure Storage - Verify data encryption"') + + dut.serial.verify_tee_sec_stg_encryption(dut) + + # ---------------- TEE Attestation tests ---------------- @idf_parametrize( 'config, target, skip_autoflash, markers', - CONFIG_OTA, + CONFIG_OTA_NO_AUTOFLASH, indirect=['config', 'target', 'skip_autoflash'], ) def test_esp_tee_attestation(dut: IdfDut) -> None: diff --git a/components/esp_tee/test_apps/tee_test_fw/sdkconfig.ci.tee_ota b/components/esp_tee/test_apps/tee_test_fw/sdkconfig.ci.tee_ota index 71e5dddc2d7..a9ce6c4279e 100644 --- a/components/esp_tee/test_apps/tee_test_fw/sdkconfig.ci.tee_ota +++ b/components/esp_tee/test_apps/tee_test_fw/sdkconfig.ci.tee_ota @@ -16,3 +16,7 @@ CONFIG_SECURE_TEE_ATT_KEY_STR_ID="tee_att_keyN" # Enabling flash protection over SPI1 CONFIG_SECURE_TEE_EXT_FLASH_MEMPROT_SPI1=y + +# Increasing TEE IRAM size +# 38KB +CONFIG_SECURE_TEE_IRAM_SIZE=0x9800 diff --git a/components/esp_tee/test_apps/tee_test_fw/sdkconfig.defaults b/components/esp_tee/test_apps/tee_test_fw/sdkconfig.defaults index 9abd3154f4c..ad47c1b6908 100644 --- a/components/esp_tee/test_apps/tee_test_fw/sdkconfig.defaults +++ b/components/esp_tee/test_apps/tee_test_fw/sdkconfig.defaults @@ -15,3 +15,6 @@ CONFIG_PARTITION_TABLE_OFFSET=0xF000 # Increasing TEE I/DRAM size CONFIG_SECURE_TEE_IRAM_SIZE=0x8800 CONFIG_SECURE_TEE_DRAM_SIZE=0x5800 + +# Takes effect only when Secure boot is enabled +CONFIG_SECURE_BOOT_FLASH_BOOTLOADER_DEFAULT=y diff --git a/components/esp_tee/test_apps/tee_test_fw/tee_exception_test_map.py b/components/esp_tee/test_apps/tee_test_fw/tee_exception_test_map.py index 2527d4df93f..c25fce29110 100644 --- a/components/esp_tee/test_apps/tee_test_fw/tee_exception_test_map.py +++ b/components/esp_tee/test_apps/tee_test_fw/tee_exception_test_map.py @@ -78,6 +78,12 @@ _TARGET_OVERRIDES: dict[str, dict[str, Any]] = { }, }, 'esp32c61': { + # NOTE: On ESP32-C61, MMU-spillover does not raise a CPU panic — the TEE + # test fills the bad mapping with a poison pattern and calls esp_restart(). + # Verified separately in the pytest, so drop it from the panic-driven map. + 'ree_isolation': { + '_remove': ['MMU-spillover'], + }, # NOTE: ESP32-C61 does not support the following peripherals 'apm_violation': { '_remove': ['AES', 'HMAC', 'DS'], diff --git a/components/esp_tee/test_apps/tee_test_fw/test_keys/tee_sec_stg_hmac_key.bin b/components/esp_tee/test_apps/tee_test_fw/test_keys/tee_sec_stg_hmac_key.bin new file mode 100644 index 00000000000..9868f801a9a Binary files /dev/null and b/components/esp_tee/test_apps/tee_test_fw/test_keys/tee_sec_stg_hmac_key.bin differ diff --git a/components/esp_timer/test_apps/main/test_esp_timer.c b/components/esp_timer/test_apps/main/test_esp_timer.c index 31976d26feb..4f534a08ecd 100644 --- a/components/esp_timer/test_apps/main/test_esp_timer.c +++ b/components/esp_timer/test_apps/main/test_esp_timer.c @@ -885,11 +885,17 @@ TEST_CASE("Test a latency between a call of callback and real event", "[esp_time } int diff = callback_time - expected_time; esp_rom_printf(DRAM_STR("%d us\n"), diff); + #ifndef CONFIG_IDF_ENV_FPGA if (i != 0) { +#if CONFIG_IDF_TARGET_ESP32H4 + // H4 runs this test at 96 MHz, and multicore task dispatch adds extra latency. + const int max_latency_us = 60; +#else + const int max_latency_us = 50; +#endif // skip the first measurement - // if CPU_FREQ = 240MHz. 14 - 16us - TEST_ASSERT_LESS_OR_EQUAL(50, diff); + TEST_ASSERT_LESS_OR_EQUAL(max_latency_us, diff); } #endif // not CONFIG_IDF_ENV_FPGA } diff --git a/components/esp_trace/CMakeLists.txt b/components/esp_trace/CMakeLists.txt index 1e26e6ce7fb..876cdf00e77 100644 --- a/components/esp_trace/CMakeLists.txt +++ b/components/esp_trace/CMakeLists.txt @@ -14,13 +14,22 @@ if(CONFIG_ESP_TRACE_ENABLE) if(CONFIG_ESP_TRACE_TRANSPORT_APPTRACE) list(APPEND srcs "adapters/transport/adapter_transport_apptrace.c") endif() + + if(CONFIG_ESP_TRACE_TRANSPORT_USB_SERIAL_JTAG) + list(APPEND srcs "adapters/transport/adapter_transport_usb_serial_jtag.c") + endif() endif() set(includes "include" ) -set(priv_requires "esp_driver_gptimer") +set(priv_requires + "esp_driver_gptimer" + "esp_hal_usb" + "esp_driver_usb_serial_jtag" + "esp_timer" +) set(priv_includes "") set(requires "app_trace") diff --git a/components/esp_trace/Kconfig b/components/esp_trace/Kconfig index 944ac322fb0..f37761be603 100644 --- a/components/esp_trace/Kconfig +++ b/components/esp_trace/Kconfig @@ -34,6 +34,16 @@ menu "ESP Trace Configuration" config ESP_TRACE_TRANSPORT_APPTRACE bool "ESP-IDF apptrace" + config ESP_TRACE_TRANSPORT_USB_SERIAL_JTAG + bool "USB Serial JTAG" + depends on SOC_USB_SERIAL_JTAG_SUPPORTED && !ESP_CONSOLE_USB_SERIAL_JTAG_ENABLED + help + Use USB Serial JTAG peripheral as trace transport. + + Note: This option is not available when USB Serial JTAG is used as + primary or secondary console (ESP_CONSOLE_USB_SERIAL_JTAG or + ESP_CONSOLE_SECONDARY_USB_SERIAL_JTAG). + config ESP_TRACE_TRANSPORT_EXTERNAL bool "External transport from component registry" depends on !ESP_TRACE_LIB_NONE @@ -48,6 +58,7 @@ menu "ESP Trace Configuration" config ESP_TRACE_TRANSPORT_NAME string default "apptrace" if ESP_TRACE_TRANSPORT_APPTRACE + default "usb_serial_jtag" if ESP_TRACE_TRANSPORT_USB_SERIAL_JTAG default "ext" if ESP_TRACE_TRANSPORT_EXTERNAL default "none" if ESP_TRACE_TRANSPORT_NONE @@ -59,6 +70,21 @@ menu "ESP Trace Configuration" rsource "$IDF_PATH/components/app_trace/Kconfig.apptrace" + menu "USB Serial JTAG Trace Configuration" + depends on ESP_TRACE_TRANSPORT_USB_SERIAL_JTAG + + config ESP_TRACE_USJ_TX_BUFFER_SIZE + int "TX buffer size" + default 2048 + range 256 32768 + help + Size of the TX ring buffer for USB Serial JTAG trace transport. + Larger buffer allows more trace data to be queued before blocking. + + Note: Buffer size must be a power of 2. + + endmenu + choice ESP_TRACE_TIMESTAMP_SOURCE depends on ESP_TRACE_ENABLE prompt "Trace timestamp source" diff --git a/components/esp_trace/README.md b/components/esp_trace/README.md index 45196dd0985..7ec47c6bf8d 100644 --- a/components/esp_trace/README.md +++ b/components/esp_trace/README.md @@ -33,11 +33,12 @@ end %% ======================= subgraph PRIMARY["🔌 PUBLIC INTERFACE"] api["- esp_trace.h - - esp_trace_init() - - esp_trace_record() - esp_trace_write() + - esp_trace_start() + - esp_trace_stop() - esp_trace_flush() - - esp_trace_print()"] + - esp_trace_is_host_connected() + - esp_trace_get_link_type()"] end %% wiring: App uses API (labels land on the short pre-edges to api_in) @@ -203,7 +204,7 @@ idf_component_register( ) ``` -This means you can directly use both the trace library APIs (e.g., SystemView) and `esp_trace` APIs (like `esp_trace_get_user_params()`, `esp_trace_is_host_connected()`, etc.) without explicitly declaring the dependency. +This means you can directly use both the trace library APIs (e.g., SystemView) and `esp_trace` APIs (like `esp_trace_get_user_params()`, `esp_trace_is_host_connected()`, `esp_trace_start()` / `esp_trace_stop()` / `esp_trace_flush()`, etc.) without explicitly declaring the dependency. ### When Using Standalone Apptrace @@ -226,6 +227,20 @@ The `esp_trace` component supports integration of external trace libraries throu - **Transport Adapters**: Handle the physical transport layer (e.g., JTAG, UART) - **Encoder Adapters**: Handle the trace encoding/formatting (e.g., SystemView, custom formats) +> ⚠️ **Reentrancy constraint for adapter runtime callbacks** +> +> Encoder and transport callbacks invoked from the hot path — `write`, `flush` / `flush_nolock`, `read`, `take_lock` / `give_lock`, `panic_handler` — run from inside FreeRTOS trace hooks (and from ISR context for `traceISR_ENTER` / `traceISR_EXIT`). They are also called while the encoder's lock is held. +> +> Do **not** call FreeRTOS / IDF APIs that themselves trigger trace hooks from these callbacks. Anything that would emit a `trace*()` macro re-enters the tracing path: it can recurse into your own encoder, deadlock on the encoder's non-recursive spinlock, or call a task-only API from ISR context. +> +> Specifically avoid: +> - Task APIs: `vTaskDelay`, `vTaskSuspend`, `xTaskNotify*`, anything that yields. +> - Queue / semaphore / mutex APIs: `xQueueSend/Receive`, `xSemaphoreTake/Give`, `xQueueSemaphoreTake`. +> - Stream / message buffer APIs. +> - Heap allocations that may take an internal mutex. +> +> Safe building blocks for adapter code: lock-free or spinlock-only primitives (e.g. `esp_trace_lock_*`, `esp_trace_rb_*`), low-level peripheral register access, atomic operations, and `esp_rom_*` helpers. Do any heavier work (FreeRTOS APIs, allocations) only at adapter `init()` time, before the trace session is in steady state. + ### Creating a Transport Adapter Transport adapters provide the physical communication layer for trace data. @@ -407,4 +422,5 @@ For detailed usage instructions, see: Examples demonstrating trace usage can be found in: - `examples/system/app_trace_basic/` - Basic application tracing - `examples/system/sysview_tracing/` - SystemView tracing example +- `examples/system/esp_trace/` - Minimal template for integrating an external trace library (encoder + FreeRTOS hooks + vtable lock) - `examples/system/sysview_tracing_heap_log/` - SystemView heap and log tracing example diff --git a/components/esp_trace/adapters/transport/adapter_transport_usb_serial_jtag.c b/components/esp_trace/adapters/transport/adapter_transport_usb_serial_jtag.c new file mode 100644 index 00000000000..b6d2415b854 --- /dev/null +++ b/components/esp_trace/adapters/transport/adapter_transport_usb_serial_jtag.c @@ -0,0 +1,361 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @brief USB-Serial-JTAG transport adapter for esp_trace + * + * This implementation uses LL (Low-Level) functions directly instead of the + * high-level USB-Serial-JTAG driver to avoid FreeRTOS primitives, which would + * cause deadlocks when tracing FreeRTOS operations with SystemView. + * + * Locking: This transport does not implement its own lock. The encoder (e.g. sysview) + * is responsible for serializing access using esp_trace_lock_init(), esp_trace_lock_take(), + * and esp_trace_lock_give(). All transport operations (read, write, flush_nolock) are + * invoked while the encoder holds the lock, so no transport-level locking is required. + */ + +#include +#include +#include +#include +#include + +#include "sdkconfig.h" +#include "esp_err.h" +#include "esp_log.h" +#include "esp_cpu.h" +#include "esp_attr.h" +#include "esp_rom_caps.h" +#include "esp_heap_caps.h" +#include "esp_private/periph_ctrl.h" +#include "hal/usb_serial_jtag_ll.h" +#include "soc/usb_serial_jtag_struct.h" +#include "driver/usb_serial_jtag.h" +#include "esp_trace_registry.h" +#include "esp_trace_port_transport.h" +#include "esp_trace_types.h" +#include "esp_trace_util.h" + +static const char *TAG = "usj_transport"; + +/* Transport context */ +typedef struct { + int inited; ///< Initialization flag (bitmask per core) + esp_trace_rb_t tx_ring; ///< TX ring buffer + esp_trace_rb_t rx_ring; ///< RX ring buffer + + /* Flush configuration */ + uint32_t flush_tmo; ///< Flush timeout in microseconds + uint32_t flush_thresh; ///< Flush threshold in bytes +} usj_ctx_t; + +#define USJ_FLUSH_TIMEOUT_US (1000000) // 1 second +#define USJ_FLUSH_THRESH_BYTES (0) // 0 bytes + +/* USB Serial JTAG hardware FIFO size (RX and TX) is 64 bytes (USB FS bulk endpoint max packet size) */ +#define USJ_HW_FIFO_SIZE (64) +#define USJ_RX_BUFFER_SIZE USJ_HW_FIFO_SIZE + +/* ----------------------- HW FIFO Helpers ----------------------- */ +static uint32_t usj_write_fifo(usj_ctx_t *ctx, esp_trace_rb_t *rb) +{ + if (!usb_serial_jtag_ll_txfifo_writable()) { + /* FIFO is full, no blocking */ + return 0; + } + + const uint8_t *ptr; + uint32_t to_send = esp_trace_rb_peek_contiguous(rb, &ptr); + if (to_send == 0) { + return 0; + } + + uint32_t written = usb_serial_jtag_ll_write_txfifo(ptr, to_send); + esp_trace_rb_consume(rb, written); + + /* Flush to send data or zero-byte packet to end USB transfer */ + usb_serial_jtag_ll_txfifo_flush(); + + return written; +} + +static void usj_read_rx_fifo(usj_ctx_t *ctx) +{ + uint8_t tmp[USJ_HW_FIFO_SIZE]; + while (usb_serial_jtag_ll_rxfifo_data_available()) { + uint32_t n = usb_serial_jtag_ll_read_rxfifo(tmp, sizeof(tmp)); + if (n == 0) { + break; + } + esp_trace_rb_put(&ctx->rx_ring, tmp, n); + } +} + +/* ----------------------- Transport Functions ----------------------- */ +_Static_assert((CONFIG_ESP_TRACE_USJ_TX_BUFFER_SIZE & (CONFIG_ESP_TRACE_USJ_TX_BUFFER_SIZE - 1)) == 0, + "CONFIG_ESP_TRACE_USJ_TX_BUFFER_SIZE must be a power of 2"); + +static esp_err_t usj_init(esp_trace_transport_t *tp, const void *tp_cfg) +{ + (void)tp_cfg; + + if (!tp) { + return ESP_ERR_INVALID_ARG; + } + + /* Create context if not already done */ + if (!tp->ctx) { + usj_ctx_t *ctx = heap_caps_calloc(1, sizeof(*ctx), MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT); + if (!ctx) { + return ESP_ERR_NO_MEM; + } + tp->ctx = ctx; + } + + usj_ctx_t *ctx = (usj_ctx_t *)tp->ctx; + int core_id = esp_cpu_get_core_id(); + + /* Only do main setup on core 0 */ + if (core_id == 0) { + /* Set default flush configuration */ + ctx->flush_tmo = USJ_FLUSH_TIMEOUT_US; + ctx->flush_thresh = USJ_FLUSH_THRESH_BYTES; + + /* Enable USB-Serial-JTAG peripheral module clock */ + PERIPH_RCC_ATOMIC() { + usb_serial_jtag_ll_enable_bus_clock(true); + } + + /* Configure USB PHY */ +#if USB_SERIAL_JTAG_LL_EXT_PHY_SUPPORTED + usb_serial_jtag_ll_phy_enable_external(false); /* Use internal PHY */ + usb_serial_jtag_ll_phy_enable_pad(true); /* Enable USB PHY pads */ +#else + usb_serial_jtag_ll_phy_set_defaults(); /* Set default PHY values */ +#endif + + /* Disable RX and TX interrupts */ + usb_serial_jtag_ll_disable_intr_mask(USB_SERIAL_JTAG_INTR_SERIAL_IN_EMPTY + | USB_SERIAL_JTAG_INTR_SERIAL_OUT_RECV_PKT); + + /* Initialize TX ring buffer */ + esp_err_t ret = esp_trace_rb_init(&ctx->tx_ring, CONFIG_ESP_TRACE_USJ_TX_BUFFER_SIZE); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Failed to initialize TX ring buffer"); + goto err_ctx; + } + + /* Initialize RX ring buffer to capture host commands */ + ret = esp_trace_rb_init(&ctx->rx_ring, USJ_RX_BUFFER_SIZE); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Failed to initialize RX ring buffer"); + goto err_tx_ring; + } + +#if ESP_ROM_HAS_ETS_PRINTF_BUG + /* Make sure no printf output is sent to USB-Serial-JTAG */ + extern bool g_usb_print; + g_usb_print = false; +#endif + + } + + ctx->inited |= (1 << core_id); + + return ESP_OK; + +err_tx_ring: + heap_caps_free(ctx->tx_ring.buffer); +err_ctx: + heap_caps_free(ctx); + tp->ctx = NULL; + return ESP_FAIL; +} + +static esp_err_t usj_read(esp_trace_transport_t *tp, void *data, size_t *size, uint32_t tmo) +{ + usj_ctx_t *ctx = (usj_ctx_t *)tp->ctx; + + if (!data || !size || *size == 0) { + return ESP_ERR_INVALID_ARG; + } + + uint8_t *buf = data; + uint32_t req_size = *size; + uint32_t total_read = 0; + + esp_trace_tmo_t timeout; + esp_trace_tmo_init(&timeout, tmo); + + /* First read any pending RX data from the RX ring buffer */ + total_read = esp_trace_rb_get(&ctx->rx_ring, buf, req_size); + + while (total_read < req_size) { + /* Try to read from HW RX FIFO directly */ + if (usb_serial_jtag_ll_rxfifo_data_available()) { + uint32_t to_read = req_size - total_read; + uint32_t read = usb_serial_jtag_ll_read_rxfifo(buf + total_read, to_read); + total_read += read; + continue; + } + + if (esp_trace_tmo_check(&timeout) != ESP_OK) { + break; + } + esp_rom_delay_us(100); + } + + *size = total_read; + return (total_read > 0) ? ESP_OK : ESP_ERR_TIMEOUT; +} + +static esp_err_t usj_write(esp_trace_transport_t *tp, const void *data, size_t size, uint32_t tmo) +{ + (void)tmo; /* Write is non-blocking via ring buffer */ + + usj_ctx_t *ctx = (usj_ctx_t *)tp->ctx; + esp_trace_rb_t *rb = &ctx->tx_ring; + + if (!data || size == 0) { + return ESP_ERR_INVALID_ARG; + } + + /* Read any new RX data into the RX ring buffer to avoid losing host commands in case of heavy trace output */ + usj_read_rx_fifo(ctx); + + /* Add data to TX ring buffer */ + esp_trace_rb_put(rb, (const uint8_t *)data, size); + + /* Try to flush some data to HW FIFO immediately (non-blocking) */ + while (esp_trace_rb_data_len(rb) > 0) { + if (usj_write_fifo(ctx, rb) == 0) { + break; /* FIFO full, will be drained on next write or flush */ + } + } + + return ESP_OK; +} + +static esp_err_t usj_down_buffer_config(esp_trace_transport_t *tp, uint8_t *buf, uint32_t size) +{ + (void)tp; + (void)buf; + (void)size; + + /* No action needed - data was already read in get function */ + + return ESP_OK; +} + +static esp_err_t usj_flush_nolock(esp_trace_transport_t *tp) +{ + usj_ctx_t *ctx = (usj_ctx_t *)tp->ctx; + esp_trace_rb_t *rb = &ctx->tx_ring; + + uint32_t pending = esp_trace_rb_data_len(rb); + if (pending < ctx->flush_thresh) { + return ESP_OK; + } + + esp_trace_tmo_t timeout; + esp_trace_tmo_init(&timeout, ctx->flush_tmo); + + /* Drain ring buffer to HW FIFO */ + while (esp_trace_rb_data_len(rb) > 0) { + usj_write_fifo(ctx, rb); + if (esp_trace_tmo_check(&timeout) != ESP_OK) { + return ESP_ERR_TIMEOUT; + } + esp_rom_delay_us(100); + } + + return ESP_OK; +} + +static bool usj_is_host_connected(esp_trace_transport_t *tp) +{ + (void)tp; + return usb_serial_jtag_is_connected(); +} + +static esp_trace_link_types_t usj_get_link_type(esp_trace_transport_t *tp) +{ + (void)tp; + return ESP_TRACE_LINK_USB_SERIAL_JTAG; +} + +static esp_err_t usj_set_config(esp_trace_transport_t *tp, esp_trace_transport_cfg_key_t key, const void *value) +{ + if (!value) { + return ESP_ERR_INVALID_ARG; + } + + usj_ctx_t *ctx = (usj_ctx_t *)tp->ctx; + if (!ctx) { + return ESP_ERR_INVALID_STATE; + } + + switch (key) { + case ESP_TRACE_TRANSPORT_CFG_HEADER_SIZE: + /* USB-Serial-JTAG doesn't need header size configuration */ + return ESP_OK; + case ESP_TRACE_TRANSPORT_CFG_FLUSH_TMO: + ctx->flush_tmo = *(const uint32_t *)value; + return ESP_OK; + case ESP_TRACE_TRANSPORT_CFG_FLUSH_THRESH: + ctx->flush_thresh = *(const uint32_t *)value; + return ESP_OK; + default: + ESP_LOGE(TAG, "Key %d is not supported", key); + return ESP_ERR_NOT_SUPPORTED; + } +} + +static esp_err_t usj_get_config(esp_trace_transport_t *tp, esp_trace_transport_cfg_key_t key, void *value) +{ + if (!value) { + return ESP_ERR_INVALID_ARG; + } + + usj_ctx_t *ctx = (usj_ctx_t *)tp->ctx; + if (!ctx) { + return ESP_ERR_INVALID_STATE; + } + + switch (key) { + case ESP_TRACE_TRANSPORT_CFG_FLUSH_TMO: + *(uint32_t *)value = ctx->flush_tmo; + return ESP_OK; + case ESP_TRACE_TRANSPORT_CFG_FLUSH_THRESH: + *(uint32_t *)value = ctx->flush_thresh; + return ESP_OK; + default: + ESP_LOGE(TAG, "Key %d is not supported", key); + return ESP_ERR_NOT_SUPPORTED; + } +} + +static void usj_panic_handler(esp_trace_transport_t *tp, const void *info) +{ + (void)info; + usj_flush_nolock(tp); +} + +/* ----------------------- Transport Registration ----------------------- */ +static const esp_trace_transport_vtable_t s_usb_serial_jtag_vt = { + .init = usj_init, + .set_config = usj_set_config, + .get_config = usj_get_config, + .read = usj_read, + .write = usj_write, + .flush_nolock = usj_flush_nolock, + .down_buffer_config = usj_down_buffer_config, + .is_host_connected = usj_is_host_connected, + .get_link_type = usj_get_link_type, + .panic_handler = usj_panic_handler, +}; + +ESP_TRACE_REGISTER_TRANSPORT("usb_serial_jtag", &s_usb_serial_jtag_vt); diff --git a/components/esp_trace/include/esp_trace.h b/components/esp_trace/include/esp_trace.h index 5ed7ccb3247..d17f7f60738 100644 --- a/components/esp_trace/include/esp_trace.h +++ b/components/esp_trace/include/esp_trace.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -100,6 +100,27 @@ esp_trace_handle_t esp_trace_get_active_handle(void); */ esp_err_t esp_trace_write(esp_trace_handle_t handle, const void *data, size_t size, unsigned long tmo); +/** + * @brief Resume trace event emission on the active session. + * + * @return ESP_OK on success, otherwise see esp_err_t + */ +esp_err_t esp_trace_start(void); + +/** + * @brief Pause trace event emission on the active session. + * + * @return ESP_OK on success, otherwise see esp_err_t + */ +esp_err_t esp_trace_stop(void); + +/** + * @brief Flush pending trace data through the encoder + * + * @return ESP_OK on success, otherwise see esp_err_t + */ +esp_err_t esp_trace_flush(void); + /** * @brief Check if the host is connected * diff --git a/components/esp_trace/include/esp_trace_port_encoder.h b/components/esp_trace/include/esp_trace_port_encoder.h index 2aa7bd8b9f6..5f1365d51e6 100644 --- a/components/esp_trace/include/esp_trace_port_encoder.h +++ b/components/esp_trace/include/esp_trace_port_encoder.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -21,7 +21,13 @@ typedef struct esp_trace_transport esp_trace_transport_t; /** * @brief Encoder Virtual Table * - * Defines the interface for trace encoders (libraries) + * Defines the interface for trace encoders (libraries). + * + * @warning Runtime callbacks (write, flush, take_lock, give_lock, panic_handler) + * must not call FreeRTOS / IDF APIs that themselves emit trace hooks + * (e.g. vTaskDelay, xQueue*, xSemaphore*) — doing so re-enters the + * tracing path and can deadlock on the encoder lock or crash in ISR + * context. */ typedef struct { /** @@ -49,8 +55,20 @@ typedef struct { */ void (*panic_handler)(esp_trace_encoder_t *enc, const void *info); + /** @brief Resume trace event emission */ + esp_err_t (*start)(esp_trace_encoder_t *enc); + + /** @brief Pause trace event emission */ + esp_err_t (*stop)(esp_trace_encoder_t *enc); + + /** @brief Flush pending trace data through the encoder */ + esp_err_t (*flush)(esp_trace_encoder_t *enc); + /** - * @brief Take encoder lock + * @brief Take encoder lock. + * Callers should pass ESP_TRACE_TMO_INFINITE unless they explicitly + * check the return value — pairing a failed take with give_lock() + * causes a spinlock owner-mismatch assert. * @param enc Encoder instance * @param tmo Timeout in microseconds * @return Lock state (for recursive locking) or 0 on failure diff --git a/components/esp_trace/include/esp_trace_port_transport.h b/components/esp_trace/include/esp_trace_port_transport.h index 39d435e9242..8385ce4a396 100644 --- a/components/esp_trace/include/esp_trace_port_transport.h +++ b/components/esp_trace/include/esp_trace_port_transport.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -34,6 +34,11 @@ typedef enum { * * Defines the interface for trace transports. * + * @warning Runtime callbacks (read, write, flush_nolock, panic_handler) must + * not call FreeRTOS / IDF APIs that themselves emit trace hooks + * (e.g. vTaskDelay, xQueue*, xSemaphore*) — they are invoked from + * inside the encoder's lock and from ISR context, so re-entering + * the tracing path can deadlock or assert. */ typedef struct { /** diff --git a/components/esp_trace/include/esp_trace_types.h b/components/esp_trace/include/esp_trace_types.h index 5b1efae25ac..dae063aa79c 100644 --- a/components/esp_trace/include/esp_trace_types.h +++ b/components/esp_trace/include/esp_trace_types.h @@ -18,6 +18,7 @@ typedef enum { ESP_TRACE_LINK_UNKNOWN = 0, ESP_TRACE_LINK_DEBUG_PROBE, ESP_TRACE_LINK_UART, + ESP_TRACE_LINK_USB_SERIAL_JTAG, } esp_trace_link_types_t; /* Timeout constants for trace operations */ diff --git a/components/esp_trace/include/esp_trace_util.h b/components/esp_trace/include/esp_trace_util.h index a2c158b6c84..e95d5ceea37 100644 --- a/components/esp_trace/include/esp_trace_util.h +++ b/components/esp_trace/include/esp_trace_util.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -118,6 +118,87 @@ esp_err_t esp_trace_lock_take(esp_trace_lock_t *lock, uint32_t tmo_us); */ esp_err_t esp_trace_lock_give(esp_trace_lock_t *lock); +/////////////////////////////////////////////////////////////////////////////// +//////////////////////////////// RING BUFFER ////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// + +#include + +/** + * @brief Power-of-2 ring buffer for trace transports + * + * Lightweight, FreeRTOS-free ring buffer suitable for use in trace hot paths + * (ISR-safe contexts, critical sections). Size must be a power of 2. + * + */ +typedef struct { + uint8_t *buffer; ///< Heap-allocated data buffer + uint32_t max_size; ///< Buffer capacity (must be power of 2) + volatile uint32_t count; ///< Bytes currently stored + volatile uint32_t head; ///< Write index + volatile uint32_t tail; ///< Read index +} esp_trace_rb_t; + +/** + * @brief Initialize ring buffer (allocates internal memory) + * + * @param rb Pointer to ring buffer structure + * @param size Buffer size in bytes (must be power of 2) + * @return ESP_OK on success, ESP_ERR_NO_MEM on allocation failure + */ +esp_err_t esp_trace_rb_init(esp_trace_rb_t *rb, uint32_t size); + +/** + * @brief Get number of bytes currently stored in the ring buffer + */ +static inline uint32_t esp_trace_rb_data_len(const esp_trace_rb_t *rb) +{ + return rb->count; +} + +/** + * @brief Write data into the ring buffer (overwrites oldest data if full) + * + * @param rb Ring buffer + * @param data Source data + * @param len Number of bytes to write + * @return ESP_OK always (data is always accepted; oldest data may be dropped) + */ +esp_err_t esp_trace_rb_put(esp_trace_rb_t *rb, const uint8_t *data, uint32_t len); + +/** + * @brief Read and consume data from the ring buffer + * + * @param rb Ring buffer + * @param data Destination buffer + * @param len Maximum number of bytes to read + * @return Number of bytes actually read + */ +uint32_t esp_trace_rb_get(esp_trace_rb_t *rb, uint8_t *data, uint32_t len); + +/** + * @brief Peek at contiguous readable data without consuming + * + * Returns a pointer to the contiguous block of data starting at the tail. + * When the readable region wraps around the end of the buffer, only the + * first contiguous portion is returned. Call again after consume() for the rest. + * + * @param rb Ring buffer + * @param[out] data Set to point at the contiguous data (valid until next put/consume) + * @return Number of contiguous bytes available (0 if empty) + */ +uint32_t esp_trace_rb_peek_contiguous(const esp_trace_rb_t *rb, const uint8_t **data); + +/** + * @brief Consume (discard) bytes from the read side of the ring buffer + * + * Typically called after esp_trace_rb_peek_contiguous() + processing. + * + * @param rb Ring buffer + * @param len Number of bytes to consume (must be <= esp_trace_rb_data_len()) + */ +void esp_trace_rb_consume(esp_trace_rb_t *rb, uint32_t len); + #ifdef __cplusplus } #endif diff --git a/components/esp_trace/linker.lf b/components/esp_trace/linker.lf index b2dbeec9304..555ca4dad10 100644 --- a/components/esp_trace/linker.lf +++ b/components/esp_trace/linker.lf @@ -8,6 +8,8 @@ entries: port_utils (noflash) if ESP_TRACE_TRANSPORT_APPTRACE: adapter_transport_apptrace (noflash) + if ESP_TRACE_TRANSPORT_USB_SERIAL_JTAG: + adapter_transport_usb_serial_jtag (noflash) [mapping:esp_trace_driver] archive: libesp_driver_gptimer.a diff --git a/components/esp_trace/src/core/esp_trace_core.c b/components/esp_trace/src/core/esp_trace_core.c index 13fd193e8cb..7fd81c5a544 100644 --- a/components/esp_trace/src/core/esp_trace_core.c +++ b/components/esp_trace/src/core/esp_trace_core.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -53,8 +53,9 @@ static esp_err_t esp_trace_create(const esp_trace_open_params_t *params) const esp_trace_encoder_vtable_t *enc_vt = esp_trace_find_encoder(params->encoder_name); const esp_trace_transport_vtable_t *tp_vt = esp_trace_find_transport(params->transport_name); - if (!enc_vt || !tp_vt) { - ESP_EARLY_LOGE(TAG, "Encoder '%s' or transport '%s' not found", params->encoder_name, params->transport_name); + // Encoder must be found but transport is optional + if (!enc_vt) { + ESP_EARLY_LOGE(TAG, "Encoder '%s' not found", params->encoder_name); return ESP_ERR_NOT_FOUND; } @@ -103,7 +104,7 @@ static esp_err_t esp_trace_init(const esp_trace_open_params_t *params) portENTER_CRITICAL(&s_init_lock); /* Setup transport first (encoder depends on it) */ - if (h->transport.vt->init) { + if (h->transport.vt && h->transport.vt->init) { err = h->transport.vt->init(&h->transport, params->transport_cfg); if (err != ESP_OK) { ESP_EARLY_LOGE(TAG, "Transport open failed: %d", err); @@ -150,9 +151,51 @@ esp_err_t esp_trace_write(esp_trace_handle_t h, const void *data, size_t size, u return h->encoder.vt->write(&h->encoder, data, size, tmo); } +esp_err_t esp_trace_start(void) +{ + esp_trace_handle_t h = s_active_handle; + if (!h) { + return ESP_ERR_INVALID_STATE; + } + + if (!h->encoder.vt->start) { + return ESP_ERR_NOT_SUPPORTED; + } + + return h->encoder.vt->start(&h->encoder); +} + +esp_err_t esp_trace_stop(void) +{ + esp_trace_handle_t h = s_active_handle; + if (!h) { + return ESP_ERR_INVALID_STATE; + } + + if (!h->encoder.vt->stop) { + return ESP_ERR_NOT_SUPPORTED; + } + + return h->encoder.vt->stop(&h->encoder); +} + +esp_err_t esp_trace_flush(void) +{ + esp_trace_handle_t h = s_active_handle; + if (!h) { + return ESP_ERR_INVALID_STATE; + } + + if (!h->encoder.vt->flush) { + return ESP_ERR_NOT_SUPPORTED; + } + + return h->encoder.vt->flush(&h->encoder); +} + bool esp_trace_is_host_connected(esp_trace_handle_t h) { - if (!h || !h->transport.vt->is_host_connected) { + if (!h || !h->transport.vt || !h->transport.vt->is_host_connected) { return false; } @@ -161,7 +204,7 @@ bool esp_trace_is_host_connected(esp_trace_handle_t h) esp_trace_link_types_t esp_trace_get_link_type(esp_trace_handle_t h) { - if (!h || !h->transport.vt->get_link_type) { + if (!h || !h->transport.vt || !h->transport.vt->get_link_type) { return ESP_TRACE_LINK_UNKNOWN; } @@ -183,7 +226,7 @@ void esp_trace_panic_handler(const void *info) h->encoder.vt->panic_handler(&h->encoder, info); } - if (h->transport.vt->panic_handler) { + if (h->transport.vt && h->transport.vt->panic_handler) { h->transport.vt->panic_handler(&h->transport, info); } } diff --git a/components/esp_trace/src/ports/port_utils.c b/components/esp_trace/src/ports/port_utils.c index 30cad3d97c6..f2cc592c681 100644 --- a/components/esp_trace/src/ports/port_utils.c +++ b/components/esp_trace/src/ports/port_utils.c @@ -1,15 +1,17 @@ /* - * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include +#include #include "sdkconfig.h" #include "esp_timer.h" #include "esp_clk_tree.h" #include "esp_cpu.h" #include "esp_private/esp_clk.h" #include "esp_err.h" +#include "esp_heap_caps.h" #include "freertos/FreeRTOS.h" #include "freertos/semphr.h" @@ -152,3 +154,104 @@ esp_err_t esp_trace_lock_give(esp_trace_lock_t *lock) portEXIT_CRITICAL(&lock->mux); return ESP_OK; } + +/////////////////////////////////////////////////////////////////////////////// +//////////////////////////////// RING BUFFER ////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// + +#ifndef MIN +#define MIN(a, b) ((a) < (b) ? (a) : (b)) +#endif + +static inline uint32_t rb_mask(const esp_trace_rb_t *rb) +{ + return rb->max_size - 1; +} + +static inline void rb_advance_tail(esp_trace_rb_t *rb, uint32_t n) +{ + rb->tail = (rb->tail + n) & rb_mask(rb); + rb->count -= n; +} + +static inline void rb_advance_head(esp_trace_rb_t *rb, uint32_t n) +{ + rb->head = (rb->head + n) & rb_mask(rb); + rb->count += n; +} + +esp_err_t esp_trace_rb_init(esp_trace_rb_t *rb, uint32_t size) +{ + rb->buffer = heap_caps_malloc(size, MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT); + if (!rb->buffer) { + return ESP_ERR_NO_MEM; + } + rb->max_size = size; + rb->count = 0; + rb->head = 0; + rb->tail = 0; + return ESP_OK; +} + +esp_err_t esp_trace_rb_put(esp_trace_rb_t *rb, const uint8_t *data, uint32_t len) +{ + /* Drop oldest data if needed to make room */ + uint32_t free_len = rb->max_size - rb->count; + if (len > free_len) { + rb_advance_tail(rb, len - free_len); + } + + uint32_t head = rb->head; + uint32_t space_to_end = rb->max_size - head; + + if (len <= space_to_end) { + memcpy(&rb->buffer[head], data, len); + } else { + memcpy(&rb->buffer[head], data, space_to_end); + memcpy(&rb->buffer[0], &data[space_to_end], len - space_to_end); + } + + rb_advance_head(rb, len); + + return ESP_OK; +} + +uint32_t esp_trace_rb_get(esp_trace_rb_t *rb, uint8_t *data, uint32_t len) +{ + uint32_t available = rb->count; + if (available == 0 || len == 0) { + return 0; + } + + uint32_t to_read = MIN(len, available); + uint32_t tail = rb->tail; + uint32_t cont = rb->max_size - tail; + + if (to_read <= cont) { + memcpy(data, &rb->buffer[tail], to_read); + } else { + memcpy(data, &rb->buffer[tail], cont); + memcpy(&data[cont], &rb->buffer[0], to_read - cont); + } + + rb_advance_tail(rb, to_read); + + return to_read; +} + +uint32_t esp_trace_rb_peek_contiguous(const esp_trace_rb_t *rb, const uint8_t **data) +{ + uint32_t used = rb->count; + if (used == 0) { + *data = NULL; + return 0; + } + *data = &rb->buffer[rb->tail]; + uint32_t contiguous = rb->max_size - rb->tail; + return MIN(used, contiguous); +} + +void esp_trace_rb_consume(esp_trace_rb_t *rb, uint32_t len) +{ + rb_advance_tail(rb, len); +} diff --git a/components/esp_wifi/wifi_apps/roaming_app/src/roaming_app.c b/components/esp_wifi/wifi_apps/roaming_app/src/roaming_app.c index e55552a1aff..c774d69e692 100644 --- a/components/esp_wifi/wifi_apps/roaming_app/src/roaming_app.c +++ b/components/esp_wifi/wifi_apps/roaming_app/src/roaming_app.c @@ -121,6 +121,20 @@ static void roaming_app_free_tracked_timeout_user_data(void) } } +static int roaming_app_register_timeout_with_user_data(unsigned int secs, unsigned int usecs, + eloop_timeout_handler handler, void *eloop_data, + void *user_data) +{ + roaming_app_track_timeout_user_data(user_data); + + if (eloop_register_timeout(secs, usecs, handler, eloop_data, user_data) != 0) { + roaming_app_untrack_timeout_user_data(user_data); + return -1; + } + + return 0; +} + void esp_wifi_roaming_set_current_bssid(const uint8_t *bssid) { if (bssid) { @@ -1109,10 +1123,8 @@ void roam_sta_disconnected(void *data) return; } os_memcpy(disconn, data, sizeof(*disconn)); - if (eloop_register_timeout(0, 0, roaming_app_disconnected_event_handler, NULL, disconn) != 0) { + if (roaming_app_register_timeout_with_user_data(0, 0, roaming_app_disconnected_event_handler, NULL, disconn) != 0) { os_free(disconn); - } else { - roaming_app_track_timeout_user_data(disconn); } } @@ -1301,10 +1313,8 @@ static void roaming_app_neighbor_report_recv_handler(void* arg, esp_event_base_t } memcpy(event_copy, event, sizeof(wifi_event_neighbor_report_t) + event->report_len); - if (eloop_register_timeout(0, 0, roaming_app_neighbor_report_recv_internal_handler, NULL, event_copy) != 0) { + if (roaming_app_register_timeout_with_user_data(0, 0, roaming_app_neighbor_report_recv_internal_handler, NULL, event_copy) != 0) { os_free(event_copy); - } else { - roaming_app_track_timeout_user_data(event_copy); } } #endif /*PERIODIC_RRM_MONITORING*/ @@ -1362,10 +1372,8 @@ static void roaming_app_rssi_low_handler(void* arg, esp_event_base_t event_base, } memcpy(event_copy, event, sizeof(wifi_event_bss_rssi_low_t)); - if (eloop_register_timeout(0, 0, roaming_app_rssi_low_internal_handler, NULL, event_copy) != 0) { + if (roaming_app_register_timeout_with_user_data(0, 0, roaming_app_rssi_low_internal_handler, NULL, event_copy) != 0) { os_free(event_copy); - } else { - roaming_app_track_timeout_user_data(event_copy); } } #endif @@ -1483,6 +1491,7 @@ void roaming_app_trigger_roam(struct cand_bss *bss) ESP_LOGD(ROAMING_TAG, "Processing trigger roaming request."); if (g_roaming_app.pending_roam_bss) { eloop_cancel_timeout(roaming_app_trigger_roam_internal_handler, NULL, g_roaming_app.pending_roam_bss); + roaming_app_untrack_timeout_user_data(g_roaming_app.pending_roam_bss); os_free(g_roaming_app.pending_roam_bss); g_roaming_app.pending_roam_bss = NULL; } @@ -1493,8 +1502,8 @@ void roaming_app_trigger_roam(struct cand_bss *bss) ESP_LOGD(ROAMING_TAG, "Deferring roam during backoff (elapsed %ld s since anchor), reschedule in %ld s", elapsed_sec, remaining_sec); - if (eloop_register_timeout((unsigned int)remaining_sec, 0, - roaming_app_trigger_roam_internal_handler, NULL, (void *)bss)) { + if (roaming_app_register_timeout_with_user_data((unsigned int)remaining_sec, 0, + roaming_app_trigger_roam_internal_handler, NULL, (void *)bss)) { ESP_LOGE(ROAMING_TAG, "Could not register roaming event."); goto free_bss; } @@ -1568,12 +1577,11 @@ static int wifi_post_roam_event(struct cand_bss *bss) } os_memcpy(cand_bss, bss, sizeof(struct cand_bss)); /* trigger the roaming event */ - if (eloop_register_timeout(0, 0, roaming_app_trigger_roam_internal_handler, NULL, (void *)cand_bss)) { + if (roaming_app_register_timeout_with_user_data(0, 0, roaming_app_trigger_roam_internal_handler, NULL, (void *)cand_bss)) { ESP_LOGE(ROAMING_TAG, "Could not register roaming event."); os_free(cand_bss); return -1; } - roaming_app_track_timeout_user_data(cand_bss); } else { ESP_LOGE(ROAMING_TAG, "Cannot trigger roaming event without any candidate APs"); return -1; @@ -2177,6 +2185,7 @@ static void roaming_app_cancel_pending_events(void) eloop_cancel_timeout(roaming_app_disconnected_event_handler, ELOOP_ALL_CTX, ELOOP_ALL_CTX); eloop_cancel_timeout(roaming_app_trigger_roam_internal_handler, ELOOP_ALL_CTX, ELOOP_ALL_CTX); if (g_roaming_app.pending_roam_bss) { + roaming_app_untrack_timeout_user_data(g_roaming_app.pending_roam_bss); os_free(g_roaming_app.pending_roam_bss); g_roaming_app.pending_roam_bss = NULL; } @@ -2280,11 +2289,10 @@ esp_err_t esp_wifi_blacklist_add(const uint8_t *bssid) return ESP_ERR_NO_MEM; } memcpy(bssid_copy, bssid, ETH_ALEN); - if (eloop_register_timeout(0, 0, roaming_app_blacklist_add_handler, NULL, bssid_copy) != 0) { + if (roaming_app_register_timeout_with_user_data(0, 0, roaming_app_blacklist_add_handler, NULL, bssid_copy) != 0) { os_free(bssid_copy); return ESP_FAIL; } - roaming_app_track_timeout_user_data(bssid_copy); return ESP_OK; } @@ -2331,11 +2339,10 @@ esp_err_t esp_wifi_blacklist_remove(const uint8_t *bssid) return ESP_ERR_NO_MEM; } memcpy(bssid_copy, bssid, ETH_ALEN); - if (eloop_register_timeout(0, 0, roaming_app_blacklist_remove_handler, NULL, bssid_copy) != 0) { + if (roaming_app_register_timeout_with_user_data(0, 0, roaming_app_blacklist_remove_handler, NULL, bssid_copy) != 0) { os_free(bssid_copy); return ESP_FAIL; } - roaming_app_track_timeout_user_data(bssid_copy); return ESP_OK; } #endif diff --git a/components/fatfs/test_apps/flash_ro/pytest_fatfs_flash_ro.py b/components/fatfs/test_apps/flash_ro/pytest_fatfs_flash_ro.py index 0339f8b6b31..098e6223eb2 100644 --- a/components/fatfs/test_apps/flash_ro/pytest_fatfs_flash_ro.py +++ b/components/fatfs/test_apps/flash_ro/pytest_fatfs_flash_ro.py @@ -6,6 +6,7 @@ from pytest_embedded_idf.utils import idf_parametrize @pytest.mark.generic +@pytest.mark.flaky(reruns=2, reruns_delay=5) @idf_parametrize('target', ['esp32', 'esp32c3'], indirect=['target']) def test_fatfs_flash_ro(dut: Dut) -> None: dut.run_all_single_board_cases() diff --git a/components/fatfs/test_apps/flash_wl/pytest_fatfs_flash_wl.py b/components/fatfs/test_apps/flash_wl/pytest_fatfs_flash_wl.py index 0ffcc3a16ce..93a100891be 100644 --- a/components/fatfs/test_apps/flash_wl/pytest_fatfs_flash_wl.py +++ b/components/fatfs/test_apps/flash_wl/pytest_fatfs_flash_wl.py @@ -6,6 +6,7 @@ from pytest_embedded_idf.utils import idf_parametrize @pytest.mark.generic +@pytest.mark.flaky(reruns=2, reruns_delay=5) @pytest.mark.parametrize( 'config', [ @@ -19,3 +20,17 @@ from pytest_embedded_idf.utils import idf_parametrize @idf_parametrize('target', ['esp32', 'esp32c3'], indirect=['target']) def test_fatfs_flash_wl_generic(dut: Dut) -> None: dut.run_all_single_board_cases(timeout=240) + + +@pytest.mark.generic +@pytest.mark.flaky(reruns=2, reruns_delay=5) +@pytest.mark.psram +@pytest.mark.parametrize( + 'config', + [ + 'psram', + ], +) +@idf_parametrize('target', ['esp32'], indirect=['target']) +def test_fatfs_flash_wl_psram(dut: Dut) -> None: + dut.run_all_single_board_cases(timeout=180) diff --git a/components/fatfs/test_apps/flash_wl/sdkconfig.ci.psram b/components/fatfs/test_apps/flash_wl/sdkconfig.ci.psram new file mode 100644 index 00000000000..e69de29bb2d diff --git a/components/fatfs/test_apps/sdcard/pytest_fatfs_sdcard.py b/components/fatfs/test_apps/sdcard/pytest_fatfs_sdcard.py index 86291941f41..7e042ef9e1b 100644 --- a/components/fatfs/test_apps/sdcard/pytest_fatfs_sdcard.py +++ b/components/fatfs/test_apps/sdcard/pytest_fatfs_sdcard.py @@ -30,3 +30,30 @@ def test_fatfs_sdcard_generic_sdmmc(dut: Dut) -> None: @idf_parametrize('target', ['esp32', 'esp32c3'], indirect=['target']) def test_fatfs_sdcard_generic_sdspi(dut: Dut) -> None: dut.run_all_single_board_cases(group='sdspi', timeout=180) + + +@pytest.mark.sdcard_sdmode +@pytest.mark.psram +@pytest.mark.parametrize( + 'config', + [ + 'psram', + ], +) +@idf_parametrize('target', ['esp32'], indirect=['target']) +def test_fatfs_sdcard_psram_sdmmc(dut: Dut) -> None: + dut.run_all_single_board_cases(group='sdmmc', timeout=180) + + +@pytest.mark.temp_skip_ci(targets=['esp32'], reason='IDFCI-2058, temporary lack runner') +@pytest.mark.sdcard_spimode +@pytest.mark.psram +@pytest.mark.parametrize( + 'config', + [ + 'psram', + ], +) +@idf_parametrize('target', ['esp32'], indirect=['target']) +def test_fatfs_sdcard_psram_sdspi(dut: Dut) -> None: + dut.run_all_single_board_cases(group='sdspi', timeout=180) diff --git a/components/fatfs/test_apps/sdcard/sdkconfig.ci.psram b/components/fatfs/test_apps/sdcard/sdkconfig.ci.psram new file mode 100644 index 00000000000..e69de29bb2d diff --git a/components/fatfs/vfs/esp_vfs_fat.h b/components/fatfs/vfs/esp_vfs_fat.h index b8ac7e625c1..6e0dd495d5f 100644 --- a/components/fatfs/vfs/esp_vfs_fat.h +++ b/components/fatfs/vfs/esp_vfs_fat.h @@ -63,7 +63,9 @@ typedef struct { * @param[out] out_fs pointer to FATFS structure which can be used for FATFS f_mount call is returned via this argument. * @return * - ESP_OK on success - * - ESP_ERR_INVALID_STATE if esp_vfs_fat_register was already called + * - ESP_ERR_INVALID_STATE if a filesystem is already registered at this base path. + * If @p out_fs is not NULL, @p *out_fs is set to the existing FATFS object so callers + * can run f_mount (e.g. remount the same path). * - ESP_ERR_NO_MEM if not enough memory or too many VFSes already registered */ esp_err_t esp_vfs_fat_register(const esp_vfs_fat_conf_t* conf, FATFS** out_fs); diff --git a/components/fatfs/vfs/vfs_fat.c b/components/fatfs/vfs/vfs_fat.c index 4cac2facdb9..39e8d08a81d 100644 --- a/components/fatfs/vfs/vfs_fat.c +++ b/components/fatfs/vfs/vfs_fat.c @@ -179,6 +179,9 @@ esp_err_t esp_vfs_fat_register(const esp_vfs_fat_conf_t* conf, FATFS** out_fs) { size_t ctx = find_context_index_by_path(conf->base_path); if (ctx < FF_VOLUMES) { + if (out_fs) { + *out_fs = &s_fat_ctxs[ctx]->fs; + } return ESP_ERR_INVALID_STATE; } @@ -221,7 +224,9 @@ esp_err_t esp_vfs_fat_register(const esp_vfs_fat_conf_t* conf, FATFS** out_fs) //compatibility s_fat_ctx = fat_ctx; - *out_fs = &fat_ctx->fs; + if (out_fs) { + *out_fs = &fat_ctx->fs; + } return ESP_OK; } @@ -237,7 +242,7 @@ esp_err_t esp_vfs_fat_unregister_path(const char* base_path) vfs_fat_ctx_t* fat_ctx = s_fat_ctxs[ctx]; esp_err_t err = esp_vfs_unregister(fat_ctx->base_path); if (err != ESP_OK) { - return err; + ESP_LOGW(TAG, "esp_vfs_unregister failed (0x%x), cleaning up anyway", err); // should not happen, but if it does, we don't want to leak memory and keep the VFS in a broken state } _lock_close(&fat_ctx->lock); @@ -251,7 +256,7 @@ esp_err_t esp_vfs_fat_unregister_path(const char* base_path) free(fat_ctx->flags); free(fat_ctx); s_fat_ctxs[ctx] = NULL; - return ESP_OK; + return err; } esp_err_t esp_vfs_fat_info(const char* base_path, @@ -562,7 +567,12 @@ static ssize_t vfs_fat_pwrite(void *ctx, int fd, const void *src, size_t size, o f_res = f_write(file, src, size, &wr); if (((wr == 0) && (size != 0)) && (f_res == 0)) { errno = ENOSPC; - return -1; + ret = -1; + FRESULT seek_res = f_lseek(file, prev_pos); + if (seek_res != FR_OK) { + ESP_LOGE(TAG, "%s: f_lseek restore after ENOSPC write failed (fresult=%d)", __func__, seek_res); + } + goto pwrite_release; } if (f_res == FR_OK) { ret = wr; @@ -1009,7 +1019,7 @@ static void vfs_fat_seekdir(void* ctx, DIR* pdir, long offset) if (res != FR_OK) { ESP_LOGD(TAG, "%s: rewinddir fresult=%d", __func__, res); errno = fresult_to_errno(res); - return; + goto seekdir_done; } fat_dir->offset = 0; } @@ -1018,10 +1028,11 @@ static void vfs_fat_seekdir(void* ctx, DIR* pdir, long offset) if (res != FR_OK) { ESP_LOGD(TAG, "%s: f_readdir fresult=%d", __func__, res); errno = fresult_to_errno(res); - return; + goto seekdir_done; } fat_dir->offset++; } +seekdir_done: _lock_release(&fat_ctx->lock); } diff --git a/components/fatfs/vfs/vfs_fat_spiflash.c b/components/fatfs/vfs/vfs_fat_spiflash.c index b1764a049ac..8c8cf099af5 100644 --- a/components/fatfs/vfs/vfs_fat_spiflash.c +++ b/components/fatfs/vfs/vfs_fat_spiflash.c @@ -171,7 +171,7 @@ esp_err_t esp_vfs_fat_spiflash_mount_rw_wl(const char* base_path, char drv[3] = {(char)('0' + pdrv), ':', 0}; ESP_GOTO_ON_ERROR(ff_diskio_register_wl_partition(pdrv, *wl_handle), fail, TAG, "ff_diskio_register_wl_partition failed pdrv=%i, error - 0x(%x)", pdrv, ret); - FATFS *fs; + FATFS *fs = NULL; esp_vfs_fat_conf_t conf = { .base_path = base_path, .fat_drive = drv, @@ -366,7 +366,7 @@ esp_err_t esp_vfs_fat_spiflash_mount_ro(const char* base_path, char drv[3] = {(char)('0' + pdrv), ':', 0}; ESP_GOTO_ON_ERROR(ff_diskio_register_raw_partition(pdrv, data_partition), fail, TAG, "ff_diskio_register_raw_partition failed pdrv=%i, error - 0x(%x)", pdrv, ret); - FATFS *fs; + FATFS *fs = NULL; esp_vfs_fat_conf_t conf = { .base_path = base_path, .fat_drive = drv, diff --git a/components/hal/esp32c5/include/hal/lp_aon_hal.h b/components/hal/esp32c5/include/hal/lp_aon_hal.h index 2090a649287..9854ef3b0eb 100644 --- a/components/hal/esp32c5/include/hal/lp_aon_hal.h +++ b/components/hal/esp32c5/include/hal/lp_aon_hal.h @@ -15,3 +15,6 @@ #define rtc_hal_ext1_get_wakeup_pins() lp_aon_ll_ext1_get_wakeup_pins() #define lp_aon_hal_inform_wakeup_type(dslp) lp_aon_ll_inform_wakeup_type(dslp) + +#define lp_aon_hal_store_wakeup_cause(wakeup_cause) lp_aon_ll_store_wakeup_cause(wakeup_cause) +#define lp_aon_hal_load_wakeup_cause() lp_aon_ll_load_wakeup_cause() diff --git a/components/hal/esp32c5/include/hal/lp_aon_ll.h b/components/hal/esp32c5/include/hal/lp_aon_ll.h index e36779941a6..f077dc59c82 100644 --- a/components/hal/esp32c5/include/hal/lp_aon_ll.h +++ b/components/hal/esp32c5/include/hal/lp_aon_ll.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2023-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2023-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -85,6 +85,24 @@ static inline void lp_aon_ll_inform_wakeup_type(bool dslp) } } +/** + * @brief Set the wakeup cause stored by LP core + * @param wakeup_cause The wakeup cause in PMU register + */ +static inline void lp_aon_ll_store_wakeup_cause(uint32_t wakeup_cause) +{ + REG_WRITE(RTC_LP_CORE_STORE_WAKEUP_REG, wakeup_cause); +} + +/** + * @brief Get the wakeup cause stored by LP core + * @return The wakeup cause cleared before LP core sleep + */ +static inline uint32_t lp_aon_ll_load_wakeup_cause(void) +{ + return REG_READ(RTC_LP_CORE_STORE_WAKEUP_REG); +} + #ifdef __cplusplus } #endif diff --git a/components/hal/esp32c5/include/hal/mmu_ll.h b/components/hal/esp32c5/include/hal/mmu_ll.h index 79df1a70676..ccbd9c3ab9d 100644 --- a/components/hal/esp32c5/include/hal/mmu_ll.h +++ b/components/hal/esp32c5/include/hal/mmu_ll.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2022-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -237,6 +237,23 @@ __attribute__((always_inline)) static inline void mmu_ll_write_entry(uint32_t mm } } +#if SOC_PSRAM_ENCRYPTION_PAGE_CONFIGURABLE +/** + * Write a PSRAM MMU entry without the SENSITIVE bit, used only for the + * carved-out unencrypted region (see CONFIG_SPIRAM_ENC_EXEMPT). + * + * No anti-FI check: the SENSITIVE bit is intentionally clear, and an FI flip + * that sets it would force decryption of plaintext data (garbage, fails safe). + */ +__attribute__((always_inline)) static inline void mmu_ll_write_entry_no_enc(uint32_t mmu_id, uint32_t entry_id, uint32_t mmu_val) +{ + (void)mmu_id; + uint32_t mmu_raw_value = mmu_val | SOC_MMU_ACCESS_SPIRAM | SOC_MMU_VALID; + REG_WRITE(SPI_MEM_MMU_ITEM_INDEX_REG(0), entry_id); + REG_WRITE(SPI_MEM_MMU_ITEM_CONTENT_REG(0), mmu_raw_value); +} +#endif + /** * Read the raw value from MMU table * diff --git a/components/hal/esp32c6/include/hal/lp_aon_hal.h b/components/hal/esp32c6/include/hal/lp_aon_hal.h index 86a88492397..d409cdbb22e 100644 --- a/components/hal/esp32c6/include/hal/lp_aon_hal.h +++ b/components/hal/esp32c6/include/hal/lp_aon_hal.h @@ -13,6 +13,8 @@ extern "C" { #endif #define lp_aon_hal_inform_wakeup_type(dslp) lp_aon_ll_inform_wakeup_type(dslp) +#define lp_aon_hal_store_wakeup_cause(wakeup_cause) lp_aon_ll_store_wakeup_cause(wakeup_cause) +#define lp_aon_hal_load_wakeup_cause() lp_aon_ll_load_wakeup_cause() #ifdef __cplusplus } diff --git a/components/hal/esp32c6/include/hal/lp_aon_ll.h b/components/hal/esp32c6/include/hal/lp_aon_ll.h index 2283a0dff6e..d2ce8514029 100644 --- a/components/hal/esp32c6/include/hal/lp_aon_ll.h +++ b/components/hal/esp32c6/include/hal/lp_aon_ll.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2023-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -85,6 +85,24 @@ static inline void lp_aon_ll_inform_wakeup_type(bool dslp) } } +/** + * @brief Set the wakeup cause stored by LP core + * @param wakeup_cause The wakeup cause in PMU register + */ +static inline void lp_aon_ll_store_wakeup_cause(uint32_t wakeup_cause) +{ + REG_WRITE(RTC_LP_CORE_STORE_WAKEUP_REG, (REG_READ(RTC_LP_CORE_STORE_WAKEUP_REG) & 0x1) | ((wakeup_cause << 1) & 0xFFFFFFFE)); +} + +/** + * @brief Get the wakeup cause stored by LP core + * @return The wakeup cause cleared before LP core sleep + */ +static inline uint32_t lp_aon_ll_load_wakeup_cause(void) +{ + return (REG_READ(RTC_LP_CORE_STORE_WAKEUP_REG) & 0xFFFFFFFE) >> 1; +} + #ifdef __cplusplus } #endif diff --git a/components/hal/esp32c61/include/hal/mmu_ll.h b/components/hal/esp32c61/include/hal/mmu_ll.h index b0dc2d03b7e..f54f5b7ab68 100644 --- a/components/hal/esp32c61/include/hal/mmu_ll.h +++ b/components/hal/esp32c61/include/hal/mmu_ll.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2024-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -10,6 +10,7 @@ #include "soc/spi_mem_reg.h" #include "soc/ext_mem_defs.h" +#include "soc/soc_caps.h" #include "hal/assert.h" #include "hal/mmu_types.h" #include "hal/efuse_ll.h" @@ -239,6 +240,23 @@ __attribute__((always_inline)) static inline void mmu_ll_write_entry(uint32_t mm } } +#if SOC_PSRAM_ENCRYPTION_PAGE_CONFIGURABLE +/** + * Write a PSRAM MMU entry without the SENSITIVE bit, used only for the + * carved-out unencrypted region (see CONFIG_SPIRAM_ENC_EXEMPT). + * + * No anti-FI check: the SENSITIVE bit is intentionally clear, and an FI flip + * that sets it would force decryption of plaintext data (garbage, fails safe). + */ +__attribute__((always_inline)) static inline void mmu_ll_write_entry_no_enc(uint32_t mmu_id, uint32_t entry_id, uint32_t mmu_val) +{ + (void)mmu_id; + uint32_t mmu_raw_value = mmu_val | SOC_MMU_ACCESS_SPIRAM | SOC_MMU_VALID; + REG_WRITE(SPI_MEM_MMU_ITEM_INDEX_REG(0), entry_id); + REG_WRITE(SPI_MEM_MMU_ITEM_CONTENT_REG(0), mmu_raw_value); +} +#endif + /** * Read the raw value from MMU table * diff --git a/components/hal/esp32p4/include/hal/lp_aon_hal.h b/components/hal/esp32p4/include/hal/lp_aon_hal.h index 31d7c574858..6152d3b9527 100644 --- a/components/hal/esp32p4/include/hal/lp_aon_hal.h +++ b/components/hal/esp32p4/include/hal/lp_aon_hal.h @@ -13,6 +13,8 @@ extern "C" { #endif #define lp_aon_hal_inform_wakeup_type(dslp) lp_sys_ll_inform_wakeup_type(dslp) +#define lp_aon_hal_store_wakeup_cause(wakeup_cause) lp_sys_ll_store_wakeup_cause(wakeup_cause) +#define lp_aon_hal_load_wakeup_cause() lp_sys_ll_load_wakeup_cause() #ifdef __cplusplus } diff --git a/components/hal/esp32p4/include/hal/lp_sys_ll.h b/components/hal/esp32p4/include/hal/lp_sys_ll.h index 366edbd4d52..71d5371e9ac 100644 --- a/components/hal/esp32p4/include/hal/lp_sys_ll.h +++ b/components/hal/esp32p4/include/hal/lp_sys_ll.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2023-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2023-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -81,6 +81,25 @@ FORCE_INLINE_ATTR void lp_sys_ll_set_lp_mem_lowpower_mode(uint32_t mode) LP_SYS.lp_mem_aux_ctrl.lp_mem_lowpower_mode = mode; } #endif + +/** + * @brief Set the wakeup cause stored by LP core + * @param wakeup_cause The wakeup cause in PMU register + */ +static inline void lp_sys_ll_store_wakeup_cause(uint32_t wakeup_cause) +{ + REG_WRITE(RTC_LP_CORE_STORE_WAKEUP_REG, wakeup_cause); +} + +/** + * @brief Get the wakeup cause stored by LP core + * @return The wakeup cause cleared before LP core sleep + */ +static inline uint32_t lp_sys_ll_load_wakeup_cause(void) +{ + return REG_READ(RTC_LP_CORE_STORE_WAKEUP_REG); +} + #ifdef __cplusplus } #endif diff --git a/components/hal/esp32p4/include/hal/mmu_ll.h b/components/hal/esp32p4/include/hal/mmu_ll.h index e86d1a18fbd..aeebe6eed86 100644 --- a/components/hal/esp32p4/include/hal/mmu_ll.h +++ b/components/hal/esp32p4/include/hal/mmu_ll.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2022-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -11,6 +11,7 @@ #include "soc/spi_mem_c_reg.h" #include "soc/spi_mem_s_reg.h" #include "soc/ext_mem_defs.h" +#include "soc/soc_caps.h" #include "hal/assert.h" #include "hal/mmu_types.h" #include "hal/efuse_ll.h" @@ -301,6 +302,26 @@ __attribute__((always_inline)) static inline void mmu_ll_write_entry(uint32_t mm } } +#if SOC_PSRAM_ENCRYPTION_PAGE_CONFIGURABLE +/** + * Write a PSRAM MMU entry without the SENSITIVE bit, used only for the + * carved-out unencrypted region (see CONFIG_SPIRAM_ENC_EXEMPT). + * + * No anti-FI check: the SENSITIVE bit is intentionally clear, and an FI flip + * that sets it would force decryption of plaintext data (garbage, fails safe). + */ +__attribute__((always_inline)) static inline void mmu_ll_write_entry_no_enc(uint32_t mmu_id, uint32_t entry_id, uint32_t mmu_val) +{ + HAL_ASSERT(mmu_id == MMU_LL_PSRAM_MMU_ID); + + mmu_val |= SOC_MMU_PSRAM_VALID; + mmu_val |= SOC_MMU_ACCESS_PSRAM; + + REG_WRITE(SPI_MEM_S_MMU_ITEM_INDEX_REG, entry_id); + REG_WRITE(SPI_MEM_S_MMU_ITEM_CONTENT_REG, mmu_val); +} +#endif + /** * Read the raw value from MMU table * diff --git a/components/hal/include/hal/mmu_hal.h b/components/hal/include/hal/mmu_hal.h index 6d8563a1fef..779af2d068e 100644 --- a/components/hal/include/hal/mmu_hal.h +++ b/components/hal/include/hal/mmu_hal.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2010-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2010-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -81,6 +81,19 @@ uint32_t mmu_hal_bytes_to_pages(uint32_t mmu_id, uint32_t bytes); */ void mmu_hal_map_region(uint32_t mmu_id, mmu_target_t mem_type, uint32_t vaddr, uint32_t paddr, uint32_t len, uint32_t *out_len); +#if SOC_PSRAM_ENCRYPTION_PAGE_CONFIGURABLE +/** + * Map a PSRAM physical range to virtual memory without setting the encryption + * SENSITIVE bit on each MMU entry. Used only for the explicitly carved-out + * unencrypted PSRAM region (see CONFIG_SPIRAM_ENC_EXEMPT). + * + * @param vaddr start virtual address (MMU-page-aligned) + * @param paddr start physical address (MMU-page-aligned) + * @param len length in bytes + */ +void mmu_hal_map_region_no_enc(uint32_t vaddr, uint32_t paddr, uint32_t len); +#endif + /** * To unmap a virtual address block that is mapped to a physical memory block previously * diff --git a/components/hal/mmu_hal.c b/components/hal/mmu_hal.c index 95441f7463e..5c83a9681fd 100644 --- a/components/hal/mmu_hal.c +++ b/components/hal/mmu_hal.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2021-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2021-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -113,6 +113,30 @@ void mmu_hal_map_region(uint32_t mmu_id, mmu_target_t mem_type, uint32_t vaddr, } } +#if SOC_PSRAM_ENCRYPTION_PAGE_CONFIGURABLE +void mmu_hal_map_region_no_enc(uint32_t vaddr, uint32_t paddr, uint32_t len) +{ + uint32_t mmu_id = MMU_LL_PSRAM_MMU_ID; + uint32_t page_size_in_bytes = mmu_hal_pages_to_bytes(mmu_id, 1); + HAL_ASSERT(vaddr % page_size_in_bytes == 0); + HAL_ASSERT(paddr % page_size_in_bytes == 0); + HAL_ASSERT(mmu_ll_check_valid_paddr_region(mmu_id, paddr, len)); + // Restrict to data vaddr space — unencrypted PSRAM must never back code/rodata. + HAL_ASSERT(mmu_hal_check_valid_ext_vaddr_region(mmu_id, vaddr, len, MMU_VADDR_DATA)); + + uint32_t page_num = (len + page_size_in_bytes - 1) / page_size_in_bytes; + uint32_t mmu_val = mmu_ll_format_paddr(mmu_id, paddr, MMU_TARGET_PSRAM0); + + while (page_num) { + uint32_t entry_id = mmu_ll_get_entry_id(mmu_id, vaddr); + mmu_ll_write_entry_no_enc(mmu_id, entry_id, mmu_val); + vaddr += page_size_in_bytes; + mmu_val++; + page_num--; + } +} +#endif + void mmu_hal_unmap_region(uint32_t mmu_id, uint32_t vaddr, uint32_t len) { uint32_t page_size_in_bytes = mmu_hal_pages_to_bytes(mmu_id, 1); diff --git a/components/heap/heap_caps.c b/components/heap/heap_caps.c index 0b27b0b5237..b34a9b44082 100644 --- a/components/heap/heap_caps.c +++ b/components/heap/heap_caps.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -345,10 +345,12 @@ esp_err_t heap_caps_monitor_local_minimum_free_size_start(void) heap = SLIST_FIRST(®istered_heaps); for (size_t counter = 0; counter < min_free_bytes_monitoring.counter; counter++) { - size_t old_minimum = multi_heap_reset_minimum_free_bytes(heap->heap); + if (heap->heap != NULL) { + size_t old_minimum = multi_heap_reset_minimum_free_bytes(heap->heap); - if (min_free_bytes_monitoring.values[counter] > old_minimum) { - min_free_bytes_monitoring.values[counter] = old_minimum; + if (min_free_bytes_monitoring.values[counter] > old_minimum) { + min_free_bytes_monitoring.values[counter] = old_minimum; + } } heap = SLIST_NEXT(heap, next); @@ -367,7 +369,9 @@ esp_err_t heap_caps_monitor_local_minimum_free_size_stop(void) MULTI_HEAP_LOCK(&min_free_bytes_monitoring.mux); heap_t *heap = SLIST_FIRST(®istered_heaps); for (size_t counter = 0; counter < min_free_bytes_monitoring.counter; counter++) { - multi_heap_restore_minimum_free_bytes(heap->heap, min_free_bytes_monitoring.values[counter]); + if (heap->heap != NULL) { + multi_heap_restore_minimum_free_bytes(heap->heap, min_free_bytes_monitoring.values[counter]); + } heap = SLIST_NEXT(heap, next); } diff --git a/components/heap/heap_caps_init.c b/components/heap/heap_caps_init.c index 9408f7490bf..f2d0f90eac1 100644 --- a/components/heap/heap_caps_init.c +++ b/components/heap/heap_caps_init.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -19,6 +19,9 @@ static const char *TAG = "heap_init"; +/* Flag indicating if the system is in startup */ +static bool s_in_startup = true; + /* Linked-list of registered heaps */ struct registered_heap_ll registered_heaps; @@ -88,6 +91,11 @@ void heap_caps_enable_nonos_stack_heaps(void) } } } + + /* heap_caps_enable_nonos_stack_heaps is called from main_task right before + * app_main is called so setting this variable here is as close as we can get + * within the heap component to the actual start of the application */ + s_in_startup = false; } /* Initialize the heap allocator to use all of the memory not @@ -314,6 +322,15 @@ esp_err_t heap_caps_add_region_with_caps(const uint32_t caps[], intptr_t start, } multi_heap_set_lock(p_new->heap, &p_new->heap_mux); + if (!s_in_startup) { + /* Set minimum_free_bytes to 0 so the newly added heap does not + * artificially inflate minimum free size. Only perform this operation + * for heaps created after startup. The heaps created by IDF component + * before app_main is reached should be taken into account in the calculation + * of the minimum free size. */ + multi_heap_restore_minimum_free_bytes(p_new->heap, 0); + } + /* (This insertion is atomic to registered_heaps, so we don't need to worry about thread safety for readers, only for writers. */ diff --git a/components/heap/include/esp_heap_caps.h b/components/heap/include/esp_heap_caps.h index 08e87426446..235b02ca994 100644 --- a/components/heap/include/esp_heap_caps.h +++ b/components/heap/include/esp_heap_caps.h @@ -50,6 +50,7 @@ extern "C" { #define MALLOC_CAP_DMA_DESC_AXI (1<<18) ///< Memory must be capable of containing AXI DMA descriptors #define MALLOC_CAP_CACHE_ALIGNED (1<<19) ///< Memory must be aligned to the cache line size of any intermediate caches #define MALLOC_CAP_SIMD (1<<20) ///< Memory must be capable of being used for SIMD instructions (i.e. allow for SIMD-specific-bit data accesses) +#define MALLOC_CAP_SPIRAM_NO_ENC (1<<21) ///< Memory must be in the PSRAM region exempt from flash encryption (plaintext in PSRAM; see CONFIG_SPIRAM_ENC_EXEMPT) #define MALLOC_CAP_INVALID (1<<31) ///< Memory can't be used / list end marker @@ -229,6 +230,9 @@ size_t heap_caps_get_free_size( uint32_t caps ); * tracked per-region. Individual regions' heaps may have reached their "low watermarks" at different points in time. However, * this result still gives a "worst case" indication for all-time minimum free heap. * + * @note Heaps added at runtime using heap_caps_add_region_with_caps() (i.e., from app_main onwards) are not taken into + * account in the minimum free size calculation. + * * @param caps Bitwise OR of MALLOC_CAP_* flags indicating the type * of memory * diff --git a/components/heap/test_apps/heap_tests/main/test_malloc_caps.c b/components/heap/test_apps/heap_tests/main/test_malloc_caps.c index 5baf954a0d9..781a27e21ba 100644 --- a/components/heap/test_apps/heap_tests/main/test_malloc_caps.c +++ b/components/heap/test_apps/heap_tests/main/test_malloc_caps.c @@ -12,6 +12,8 @@ #include "unity.h" #include "esp_attr.h" #include "esp_heap_caps.h" +#include "esp_heap_caps_init.h" +#include "heap_memory_layout.h" #include "spi_flash_mmap.h" #include "esp_memory_utils.h" #include "esp_private/spi_flash_os.h" @@ -206,6 +208,41 @@ TEST_CASE("heap caps minimum free bytes monitoring", "[heap]") TEST_ASSERT(local_minimum_free_size >= free_size); } +extern void set_leak_threshold(int threshold); + +/* NOTE: This is not a well-formed unit test, it leaks memory */ +TEST_CASE("heap registered after startup should not affect minimum free size", "[heap]") +{ + printf("heap registered after startup should not affect minimum free size\n"); + + const uint32_t MALLOC_CAP_INVENTED = (1 << 29); /* unused capability, must differ from (1 << 30) used in test_runtime_heap_reg.c */ + const size_t BUF_SZ = 3500; + uint32_t caps[SOC_MEMORY_TYPE_NO_PRIOS] = { MALLOC_CAP_INVENTED }; + + // Record the minimum free size for default caps before adding a new heap + size_t minimum_before = heap_caps_get_minimum_free_size(MALLOC_CAP_DEFAULT); + + // Allocate a buffer and register it as a new heap region. + // Since we are past startup (app_main has been reached), the newly + // registered heap should NOT affect the minimum free size. + void *buffer = malloc(BUF_SZ); + TEST_ASSERT_NOT_NULL(buffer); + TEST_ESP_OK(heap_caps_add_region_with_caps(caps, (intptr_t)buffer, (intptr_t)buffer + BUF_SZ)); + + // The minimum free size for the invented capability should be 0 + // because the heap was registered after startup. + size_t minimum_invented = heap_caps_get_minimum_free_size(MALLOC_CAP_INVENTED); + TEST_ASSERT_EQUAL(0, minimum_invented); + + // The minimum free size for default caps should not have increased + // (it may have decreased slightly due to the malloc above). + size_t minimum_after = heap_caps_get_minimum_free_size(MALLOC_CAP_DEFAULT); + TEST_ASSERT(minimum_after <= minimum_before); + + // set the leak threshold to a bigger value as this test leaks memory + set_leak_threshold(-4000); +} + TEST_CASE("heap caps minimum free bytes fault cases", "[heap]") { printf("heap caps minimum free bytes fault cases\n"); diff --git a/components/ieee802154/driver/esp_ieee802154_dev.c b/components/ieee802154/driver/esp_ieee802154_dev.c index ee3ebe7bcf1..6fb12b0db89 100644 --- a/components/ieee802154/driver/esp_ieee802154_dev.c +++ b/components/ieee802154/driver/esp_ieee802154_dev.c @@ -1012,8 +1012,6 @@ esp_err_t ieee802154_transmit(const uint8_t *frame, bool cca) esp_err_t ieee802154_transmit_at(const uint8_t *frame, bool cca, uint32_t time) { - uint32_t rampup_time = cca ? IEEE802154_ED_TRIG_TX_RAMPUP_TIME_US : IEEE802154_TX_RAMPUP_TIME_US; - uint32_t tx_target_time = (time >= rampup_time) ? time - rampup_time : 0; IEEE802154_RF_ENABLE(); tx_init(frame); IEEE802154_SET_TXRX_PTI(IEEE802154_SCENE_TX_AT); @@ -1023,12 +1021,8 @@ esp_err_t ieee802154_transmit_at(const uint8_t *frame, bool cca, uint32_t time) ieee802154_set_state(cca ? IEEE802154_STATE_TX_CCA : IEEE802154_STATE_TX); ieee802154_enter_critical(); ieee802154_etm_set_event_task(IEEE802154_ETM_CHANNEL0, ETM_EVENT_TIMER0_OVERFLOW, cca ? ETM_TASK_ED_TRIG_TX : ETM_TASK_TX_START); - ieee802154_timer0_fire_at(tx_target_time); + ieee802154_timer0_fire_at(time - (cca ? IEEE802154_ED_TRIG_TX_RAMPUP_TIME_US : IEEE802154_TX_RAMPUP_TIME_US)); ieee802154_exit_critical(); - if (time < rampup_time) { - // First start the transmit at and then print some logs. - ESP_EARLY_LOGE(IEEE802154_TAG, "Time should be longer than %d us to account for the TX ramp-up", rampup_time); - } return ESP_OK; } @@ -1076,7 +1070,6 @@ IEEE802154_NOINLINE static void ieee802154_start_receive_at(void* ctx) esp_err_t ieee802154_receive_at(uint32_t time, uint32_t duration) { // TODO: Light sleep current optimization, TZ-1613. - uint32_t target_time = (time >= IEEE802154_RX_RAMPUP_TIME_US) ? time - IEEE802154_RX_RAMPUP_TIME_US : 0; IEEE802154_RF_ENABLE(); ieee802154_enter_critical(); rx_init(); @@ -1085,15 +1078,11 @@ esp_err_t ieee802154_receive_at(uint32_t time, uint32_t duration) ieee802154_set_state(IEEE802154_STATE_RX); ieee802154_etm_set_event_task(IEEE802154_ETM_CHANNEL1, ETM_EVENT_TIMER1_OVERFLOW, ETM_TASK_RX_START); if (duration) { - ieee802154_timer1_fire_at_with_callback(target_time, ieee802154_start_receive_at, (void*)(time + duration)); + ieee802154_timer1_fire_at_with_callback(time - IEEE802154_RX_RAMPUP_TIME_US, ieee802154_start_receive_at, (void*)(time + duration)); } else { - ieee802154_timer1_fire_at(target_time); + ieee802154_timer1_fire_at(time - IEEE802154_RX_RAMPUP_TIME_US); } ieee802154_exit_critical(); - if (time < IEEE802154_RX_RAMPUP_TIME_US) { - // First start the receive at and then print some logs. - ESP_EARLY_LOGE(IEEE802154_TAG, "Time should be longer than %d us to account for the RX ramp-up", IEEE802154_RX_RAMPUP_TIME_US); - } return ESP_OK; } diff --git a/components/ieee802154/private_include/esp_ieee802154_util.h b/components/ieee802154/private_include/esp_ieee802154_util.h index f2881607c23..fbbea32e3d2 100644 --- a/components/ieee802154/private_include/esp_ieee802154_util.h +++ b/components/ieee802154/private_include/esp_ieee802154_util.h @@ -53,10 +53,10 @@ static inline bool ieee802154_is_valid_channel(uint8_t channel) #define IEEE802154_RECORD_EVENT(a) do { \ g_ieee802154_probe.event[g_ieee802154_probe.event_index].event = a; \ g_ieee802154_probe.event[g_ieee802154_probe.event_index].state = ieee802154_get_state(); \ - if (a == IEEE802154_EVENT_RX_ABORT) { \ + if (a & IEEE802154_EVENT_RX_ABORT) { \ g_ieee802154_probe.event[g_ieee802154_probe.event_index].abort_reason.rx \ = ieee802154_ll_get_rx_abort_reason(); \ - } else if (a == IEEE802154_EVENT_TX_ABORT) { \ + } else if (a & IEEE802154_EVENT_TX_ABORT) { \ g_ieee802154_probe.event[g_ieee802154_probe.event_index].abort_reason.tx \ = ieee802154_ll_get_tx_abort_reason(); \ } \ @@ -128,14 +128,14 @@ typedef struct { #if CONFIG_IEEE802154_RECORD_ABORT #define IEEE802154_ASSERT_RECORD_ABORT_SIZE CONFIG_IEEE802154_RECORD_ABORT_SIZE #define IEEE802154_RECORD_ABORT(a) do { \ - if (a == IEEE802154_EVENT_RX_ABORT) { \ + if (a & IEEE802154_EVENT_RX_ABORT) { \ g_ieee802154_probe.abort[g_ieee802154_probe.abort_index].abort_reason.rx \ = ieee802154_ll_get_rx_abort_reason(); \ g_ieee802154_probe.abort[g_ieee802154_probe.abort_index].is_tx_abort = 0; \ g_ieee802154_probe.abort[g_ieee802154_probe.abort_index++].timestamp = esp_timer_get_time(); \ g_ieee802154_probe.abort_index = (g_ieee802154_probe.abort_index == IEEE802154_ASSERT_RECORD_ABORT_SIZE) ? \ 0 : g_ieee802154_probe.abort_index; \ - } else if (a == IEEE802154_EVENT_TX_ABORT) { \ + } else if (a & IEEE802154_EVENT_TX_ABORT) { \ g_ieee802154_probe.abort[g_ieee802154_probe.abort_index].abort_reason.tx \ = ieee802154_ll_get_tx_abort_reason();\ g_ieee802154_probe.abort[g_ieee802154_probe.abort_index].is_tx_abort = 1; \ diff --git a/components/lwip/lwip b/components/lwip/lwip index fd432e4ee2c..20f8b0739c2 160000 --- a/components/lwip/lwip +++ b/components/lwip/lwip @@ -1 +1 @@ -Subproject commit fd432e4ee2cfb7f7f1c7eb7227e0173412e7b84e +Subproject commit 20f8b0739c29c43e3030ea84ac3e0595c8c6d068 diff --git a/components/mbedtls/CMakeLists.txt b/components/mbedtls/CMakeLists.txt index 6d5c314338e..7ae5daadef6 100644 --- a/components/mbedtls/CMakeLists.txt +++ b/components/mbedtls/CMakeLists.txt @@ -7,14 +7,24 @@ if(esp_tee_build) return() elseif(BOOTLOADER_BUILD) # TODO: IDF-11673 if(CONFIG_MBEDTLS_USE_CRYPTO_ROM_IMPL_BOOTLOADER) - set(include_dirs "${COMPONENT_DIR}/mbedtls/include" + set(include_dirs "${COMPONENT_DIR}/port/include" + "${COMPONENT_DIR}/mbedtls/include" + "${COMPONENT_DIR}/mbedtls/tf-psa-crypto/include" + "${COMPONENT_DIR}/mbedtls/tf-psa-crypto/drivers/builtin/include" + "${COMPONENT_DIR}/port/psa_driver/include" "port/mbedtls_rom") set(srcs "port/mbedtls_rom/mbedtls_rom_osi_bootloader.c") + set(public_compile_definitions + -DMBEDTLS_CONFIG_FILE="mbedtls/esp_config.h" + MBEDTLS_CIPHER_MODE_XTS) endif() idf_component_register(SRCS "${srcs}" INCLUDE_DIRS "${include_dirs}" PRIV_REQUIRES esp_hal_dma) + if(public_compile_definitions) + target_compile_definitions(${COMPONENT_LIB} PUBLIC ${public_compile_definitions}) + endif() return() endif() @@ -458,10 +468,10 @@ if(CONFIG_MBEDTLS_HARDWARE_ECDSA_SIGN OR CONFIG_MBEDTLS_HARDWARE_ECDSA_VERIFY OR endif() endif() -# if(CONFIG_MBEDTLS_USE_CRYPTO_ROM_IMPL) -# target_sources(mbedcrypto PRIVATE "${COMPONENT_DIR}/port/mbedtls_rom/mbedtls_rom_osi.c") -# target_link_libraries(${COMPONENT_LIB} PRIVATE "-u mbedtls_rom_osi_functions_init") -# endif() +if(CONFIG_MBEDTLS_USE_CRYPTO_ROM_IMPL) + target_sources(tfpsacrypto PRIVATE "${COMPONENT_DIR}/port/mbedtls_rom/mbedtls_rom_osi.c") + target_link_libraries(${COMPONENT_LIB} INTERFACE "-u mbedtls_rom_osi_functions_init") +endif() if(CONFIG_COMPILER_STATIC_ANALYZER AND CMAKE_C_COMPILER_ID STREQUAL "GNU") target_compile_options(${COMPONENT_LIB} PRIVATE "-fno-analyzer") diff --git a/components/mbedtls/Kconfig b/components/mbedtls/Kconfig index 76084d006df..ea5f41dc2b5 100644 --- a/components/mbedtls/Kconfig +++ b/components/mbedtls/Kconfig @@ -37,19 +37,22 @@ menu "mbedTLS" If you do intend to use contexts between threads, you will need to enable this layer to prevent race conditions. - config MBEDTLS_THREADING_ALT - bool "Enable threading alternate implementation" + choice MBEDTLS_THREADING_IMPLEMENTATION + prompt "Threading implementation" depends on MBEDTLS_THREADING_C - default n - help - Enable threading alt to allow your own alternate threading implementation. + default MBEDTLS_THREADING_PTHREAD - config MBEDTLS_THREADING_PTHREAD - bool "Enable threading pthread implementation" - depends on MBEDTLS_THREADING_C - default y - help - Enable the pthread wrapper layer for the threading layer. + config MBEDTLS_THREADING_ALT + bool "Enable threading alternate implementation" + help + Enable threading alt to allow your own alternate threading implementation. + + config MBEDTLS_THREADING_PTHREAD + bool "Enable threading pthread implementation" + help + Enable the pthread wrapper layer for the threading layer. + + endchoice config MBEDTLS_ERROR_STRINGS bool "Enable error code to error string conversion" @@ -728,7 +731,7 @@ menu "mbedTLS" config MBEDTLS_KEY_EXCHANGE_ECJPAKE bool "Enable ECJPAKE based ciphersuite modes" - depends on MBEDTLS_ECP_DP_SECP256R1_ENABLED + depends on MBEDTLS_ECJPAKE_C && MBEDTLS_ECP_DP_SECP256R1_ENABLED default n help Enable to support ciphersuites with prefix TLS-ECJPAKE-WITH- @@ -1093,6 +1096,13 @@ menu "mbedTLS" help Enable ECDH. Needed to use ECDHE-xxx TLS ciphersuites. + config MBEDTLS_ECJPAKE_C + bool "Elliptic curve J-PAKE" + depends on MBEDTLS_ECP_C + default n + help + Enable ECJPAKE. Needed to use ECJPAKE-xxx TLS ciphersuites. + config MBEDTLS_ECDSA_C bool "Elliptic Curve DSA" depends on MBEDTLS_ECDH_C && MBEDTLS_ECP_C @@ -1601,8 +1611,7 @@ menu "mbedTLS" config MBEDTLS_USE_CRYPTO_ROM_IMPL_BOOTLOADER bool "Use ROM implementation of the crypto algorithm in the bootloader" - # TODO: IDF-15012 - depends on ESP_ROM_HAS_MBEDTLS_CRYPTO_LIB && !MBEDTLS_VER_4_X_SUPPORT + depends on ESP_ROM_HAS_MBEDTLS_CRYPTO_LIB && ESP32C2_REV_MIN_200 default "n" select MBEDTLS_AES_C help @@ -1613,8 +1622,7 @@ menu "mbedTLS" config MBEDTLS_USE_CRYPTO_ROM_IMPL bool "Use ROM implementation of the crypto algorithm" - # TODO: IDF-15012 - depends on ESP_ROM_HAS_MBEDTLS_CRYPTO_LIB && !MBEDTLS_VER_4_X_SUPPORT + depends on ESP_ROM_HAS_MBEDTLS_CRYPTO_LIB && ESP32C2_REV_MIN_200 default "n" select MBEDTLS_SHA512_C select MBEDTLS_AES_C @@ -1628,12 +1636,11 @@ menu "mbedTLS" Enable this flag to use mbedtls crypto algorithm from ROM instead of ESP-IDF. This configuration option saves flash footprint in the application binary. - Note that the version of mbedtls crypto algorithm library in ROM(ECO1~ECO3) is v2.16.12, - and the version of mbedtls crypto algorithm library in ROM(ECO4) is v3.6.0. - We have done the security analysis of the mbedtls revision in ROM (ECO1~ECO4) - and ensured that affected symbols have been patched (removed). If in the future - mbedtls revisions there are security issues that also affects the version in - ROM (ECO1~ECO4) then we shall patch the relevant symbols. This would increase - the flash footprint and hence care must be taken to keep some reserved space - for the application binary in flash layout. + It is available for ESP32-C2 rev2.0 and later, where the ECO4 ROM contains + the mbedtls crypto algorithm library v3.6.0. + We have done the security analysis of the mbedtls revision in ROM and ensured + that affected symbols have been patched (removed). If future mbedtls revisions + include security issues that also affect the version in ROM, then we shall patch + the relevant symbols. This would increase the flash footprint and hence care must + be taken to keep some reserved space for the application binary in flash layout. endmenu # mbedTLS diff --git a/components/mbedtls/config/mbedtls_preset_default.conf b/components/mbedtls/config/mbedtls_preset_default.conf index 69c12156b7e..11047ec88fe 100644 --- a/components/mbedtls/config/mbedtls_preset_default.conf +++ b/components/mbedtls/config/mbedtls_preset_default.conf @@ -115,6 +115,7 @@ CONFIG_MBEDTLS_ECP_C=y CONFIG_MBEDTLS_ECP_NIST_OPTIM=y CONFIG_MBEDTLS_ECP_FIXED_POINT_OPTIM=n CONFIG_MBEDTLS_ECDH_C=y +CONFIG_MBEDTLS_ECJPAKE_C=n CONFIG_MBEDTLS_ECDSA_C=y CONFIG_MBEDTLS_PK_PARSE_EC_EXTENDED=y CONFIG_MBEDTLS_PK_PARSE_EC_COMPRESSED=y diff --git a/components/mbedtls/esp_crt_bundle/esp_crt_bundle.c b/components/mbedtls/esp_crt_bundle/esp_crt_bundle.c index 05eff00b9cb..0b271b73dc0 100644 --- a/components/mbedtls/esp_crt_bundle/esp_crt_bundle.c +++ b/components/mbedtls/esp_crt_bundle/esp_crt_bundle.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2018-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2018-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -316,8 +316,33 @@ int esp_crt_verify_callback(void *buf, mbedtls_x509_crt* const crt, const int de { const mbedtls_x509_crt* const child = crt; - /* It's OK for a trusted cert to have a weak signature hash alg. - as we already trust this certificate */ +#if defined(CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_CROSS_SIGNED_VERIFY) + /* When cross-signed verification is enabled, the CA callback provides a + * synthetic bundle root containing only the subject name and public key. + * It has no meaningful validity window, so mbedtls may set EXPIRED/FUTURE + * on this generated cert. Clear those flags only for this synthetic bundle + * root so that cross-signed verification can continue. + * + * Real certificates must keep their time-based verification result and + * should not proceed to additional bundle signature checks once they are + * marked expired or not-yet-valid. */ + const uint32_t time_flags = *flags & + (MBEDTLS_X509_BADCERT_EXPIRED | MBEDTLS_X509_BADCERT_FUTURE); + if (time_flags && s_crt_bundle != NULL && child->raw.p == NULL && + child->valid_from.year == 0 && child->valid_to.year == 0) { + cert_t cert = esp_crt_find_cert(child->subject_raw.p, + child->subject_raw.len); + if (cert != NULL) { + *flags &= ~(MBEDTLS_X509_BADCERT_EXPIRED | + MBEDTLS_X509_BADCERT_FUTURE); + } + } +#endif /* CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_CROSS_SIGNED_VERIFY */ + + /* It's OK for a trusted bundle cert to have a weak signature hash alg, + * as we already trust this certificate. Do not ignore EXPIRED/FUTURE here: + * real certificates must fail on validity checks, and only the synthetic + * cross-signed bundle root has those flags cleared above. */ uint32_t flags_filtered = *flags & ~(MBEDTLS_X509_BADCERT_BAD_MD); if (flags_filtered != MBEDTLS_X509_BADCERT_NOT_TRUSTED) { @@ -339,6 +364,10 @@ int esp_crt_verify_callback(void *buf, mbedtls_x509_crt* const crt, const int de if (likely(ret == 0)) { ESP_LOGI(TAG, "Certificate validated"); + /* Bundle trust and signature verification succeeded. Real + * certificates with EXPIRED/FUTURE return earlier, and the + * synthetic cross-signed bundle root has those flags cleared + * above, so clear the remaining verification flags here. */ *flags = 0; return 0; } else { @@ -429,7 +458,10 @@ static esp_err_t esp_crt_bundle_init(const uint8_t* const x509_bundle, const siz } #if defined(CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_CROSS_SIGNED_VERIFY) -static int esp_crt_copy_asn1(const mbedtls_asn1_named_data *src, mbedtls_asn1_named_data *dst) +/* Reference ASN1 named data by pointing into src's buffers. + * The src data must outlive dst. */ +static int esp_crt_ref_asn1(const mbedtls_asn1_named_data *src, + mbedtls_asn1_named_data *dst) { if (src == NULL || dst == NULL) { return -1; @@ -437,21 +469,11 @@ static int esp_crt_copy_asn1(const mbedtls_asn1_named_data *src, mbedtls_asn1_na dst->oid.tag = src->oid.tag; dst->oid.len = src->oid.len; - dst->oid.p = calloc(1, src->oid.len); - if (dst->oid.p == NULL) { - ESP_LOGE(TAG, "Failed to allocate memory for OID"); - return -1; - } - memcpy(dst->oid.p, src->oid.p, src->oid.len); + dst->oid.p = src->oid.p; dst->val.tag = src->val.tag; dst->val.len = src->val.len; - dst->val.p = calloc(1, src->val.len); - if (dst->val.p == NULL) { - ESP_LOGE(TAG, "Failed to allocate memory for value"); - free(dst->oid.p); - return -1; - } - memcpy(dst->val.p, src->val.p, src->val.len); + dst->val.p = src->val.p; + dst->next_merged = src->next_merged; return 0; } @@ -484,16 +506,10 @@ static int esp_crt_ca_cb_callback(void *ctx, mbedtls_x509_crt const *child, mbed const uint8_t *cert_name = esp_crt_get_name(cert); uint16_t cert_name_len = esp_crt_get_name_len(cert); + /* Point into persistent bundle data */ new_cert->subject_raw.tag = MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE; new_cert->subject_raw.len = cert_name_len; - new_cert->subject_raw.p = calloc(1, cert_name_len); - if (new_cert->subject_raw.p == NULL) { - ESP_LOGE(TAG, "Failed to allocate memory for subject"); - mbedtls_x509_crt_free(new_cert); - free(new_cert); - return MBEDTLS_ERR_X509_ALLOC_FAILED; - } - memcpy(new_cert->subject_raw.p, cert_name, cert_name_len); + new_cert->subject_raw.p = (unsigned char *)cert_name; const uint8_t *cert_key = esp_crt_get_key(cert); uint16_t cert_key_len = esp_crt_get_key_len(cert); @@ -508,31 +524,33 @@ static int esp_crt_ca_cb_callback(void *ctx, mbedtls_x509_crt const *child, mbed return ret; } - // Loop through the child->issuer and copy the values to the new certificate + /* Populate parent->subject by referencing child->issuer data */ const mbedtls_asn1_named_data *child_issuer = &child->issuer; mbedtls_asn1_named_data *parent_subject = &new_cert->subject; + if (esp_crt_ref_asn1(child_issuer, parent_subject) != 0) { + ESP_LOGE(TAG, "Failed to reference ASN.1 data"); + mbedtls_x509_crt_free(new_cert); + free(new_cert); + return MBEDTLS_ERR_X509_ALLOC_FAILED; + } + + child_issuer = child_issuer->next; while (child_issuer != NULL) { - if (esp_crt_copy_asn1(child_issuer, parent_subject) != 0) { - ESP_LOGE(TAG, "Failed to copy ASN.1 data"); + parent_subject->next = calloc(1, sizeof(mbedtls_asn1_named_data)); + if (parent_subject->next == NULL) { + ESP_LOGE(TAG, "Failed to allocate memory for subject node"); + mbedtls_x509_crt_free(new_cert); + free(new_cert); + return MBEDTLS_ERR_X509_ALLOC_FAILED; + } + parent_subject = parent_subject->next; + if (esp_crt_ref_asn1(child_issuer, parent_subject) != 0) { + ESP_LOGE(TAG, "Failed to reference ASN.1 data"); mbedtls_x509_crt_free(new_cert); free(new_cert); return MBEDTLS_ERR_X509_ALLOC_FAILED; } child_issuer = child_issuer->next; - if (child_issuer == NULL) { - break; - } - - if (parent_subject->next == NULL) { - parent_subject->next = calloc(1, sizeof(mbedtls_asn1_named_data)); - if (parent_subject->next == NULL) { - ESP_LOGE(TAG, "Failed to allocate memory for next issuer"); - mbedtls_x509_crt_free(new_cert); - free(new_cert); - return MBEDTLS_ERR_X509_ALLOC_FAILED; - } - parent_subject = parent_subject->next; - } } // Set the parsed certificate as the candidate CA @@ -576,6 +594,11 @@ void esp_crt_bundle_detach(mbedtls_ssl_config *conf) s_crt_bundle = NULL; if (conf) { mbedtls_ssl_conf_verify(conf, NULL, NULL); +#if defined(CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_CROSS_SIGNED_VERIFY) + mbedtls_ssl_conf_ca_cb(conf, NULL, NULL); +#else + mbedtls_ssl_conf_ca_chain(conf, NULL, NULL); +#endif /* CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_CROSS_SIGNED_VERIFY */ } } diff --git a/components/mbedtls/esp_tee/esp_tee_mbedtls.cmake b/components/mbedtls/esp_tee/esp_tee_mbedtls.cmake index 63b771fbe8c..7a69f5a7db3 100644 --- a/components/mbedtls/esp_tee/esp_tee_mbedtls.cmake +++ b/components/mbedtls/esp_tee/esp_tee_mbedtls.cmake @@ -83,31 +83,29 @@ if(CONFIG_SOC_AES_SUPPORTED) "${COMPONENT_DIR}/port/aes/esp_aes_common.c" "${COMPONENT_DIR}/port/aes/esp_aes_xts.c") target_include_directories(tfpsacrypto PRIVATE "${COMPONENT_DIR}/port/include/aes") - if(CONFIG_MBEDTLS_HARDWARE_AES) - target_sources(tfpsacrypto PRIVATE - "${COMPONENT_DIR}/port/psa_driver/esp_aes/psa_crypto_driver_esp_aes.c" - "${COMPONENT_DIR}/port/psa_driver/esp_aes/psa_crypto_driver_esp_aes_gcm.c" - ) - endif() - + target_sources(tfpsacrypto PRIVATE + "${COMPONENT_DIR}/port/psa_driver/esp_aes/psa_crypto_driver_esp_aes.c" + "${COMPONENT_DIR}/port/psa_driver/esp_aes/psa_crypto_driver_esp_aes_gcm.c" + ) endif() # SHA implementation if(CONFIG_SOC_SHA_SUPPORTED) target_sources(tfpsacrypto PRIVATE "${COMPONENT_DIR}/port/psa_driver/esp_sha/psa_crypto_driver_esp_sha.c" - "${COMPONENT_DIR}/port/psa_driver/esp_sha/core/psa_crypto_driver_esp_sha1.c" "${COMPONENT_DIR}/port/psa_driver/esp_sha/core/psa_crypto_driver_esp_sha256.c" - "${COMPONENT_DIR}/port/psa_driver/esp_sha/core/psa_crypto_driver_esp_sha512.c" "${COMPONENT_DIR}/port/sha/core/sha.c" "${COMPONENT_DIR}/port/sha/esp_sha.c" - "${COMPONENT_DIR}/port/psa_driver/esp_mac/psa_crypto_driver_esp_hmac_transparent.c" - ) -endif() - -if(CONFIG_MBEDTLS_ROM_MD5) - target_sources(tfpsacrypto PRIVATE - "${COMPONENT_DIR}/port/psa_driver/esp_md/psa_crypto_driver_esp_md5.c" ) + if(CONFIG_MBEDTLS_SHA1_C) + target_sources(tfpsacrypto PRIVATE + "${COMPONENT_DIR}/port/psa_driver/esp_sha/core/psa_crypto_driver_esp_sha1.c" + ) + endif() + if(CONFIG_SOC_SHA_SUPPORT_SHA512 AND CONFIG_MBEDTLS_SHA512_C) + target_sources(tfpsacrypto PRIVATE + "${COMPONENT_DIR}/port/psa_driver/esp_sha/core/psa_crypto_driver_esp_sha512.c" + ) + endif() endif() if(CONFIG_SOC_ECC_SUPPORTED) @@ -120,6 +118,9 @@ if(CONFIG_SOC_HMAC_SUPPORTED) target_sources(tfpsacrypto PRIVATE "${COMPONENT_DIR}/port/psa_driver/esp_mac/psa_crypto_driver_esp_hmac_opaque.c") target_sources(tfpsacrypto PRIVATE "${COMPONENT_DIR}/port/esp_hmac_pbkdf2.c") target_link_libraries(tfpsacrypto PRIVATE idf::efuse) +else() + target_sources(tfpsacrypto PRIVATE + "${COMPONENT_DIR}/port/psa_driver/esp_mac/psa_crypto_driver_esp_hmac_transparent.c") endif() # PSA Attestation diff --git a/components/mbedtls/esp_tee/esp_tee_mbedtls_config.h b/components/mbedtls/esp_tee/esp_tee_mbedtls_config.h index 9c915f4f20c..ed14b4d095b 100644 --- a/components/mbedtls/esp_tee/esp_tee_mbedtls_config.h +++ b/components/mbedtls/esp_tee/esp_tee_mbedtls_config.h @@ -40,7 +40,7 @@ #undef MBEDTLS_TIMING_C #define MBEDTLS_PLATFORM_C -#if CONFIG_MBEDTLS_HARDWARE_AES +#if SOC_AES_SUPPORTED #define ESP_AES_DRIVER_ENABLED #define MBEDTLS_PSA_ACCEL_KEY_TYPE_AES #endif @@ -59,29 +59,41 @@ #define PSA_WANT_ALG_DETERMINISTIC_ECDSA 1 #else #undef PSA_WANT_ALG_DETERMINISTIC_ECDSA +#undef MBEDTLS_HMAC_DRBG_C #endif -#if CONFIG_MBEDTLS_SHA1_C -#define MBEDTLS_SHA1_C -#endif -#define MBEDTLS_SHA224_C -#define MBEDTLS_SHA256_C - #if SOC_SHA_SUPPORTED #define ESP_SHA_DRIVER_ENABLED -#define ESP_HMAC_TRANSPARENT_DRIVER_ENABLED -#undef MBEDTLS_PSA_BUILTIN_ALG_HMAC #if CONFIG_MBEDTLS_SHA1_C - #define MBEDTLS_PSA_ACCEL_ALG_SHA_1 - #undef MBEDTLS_PSA_BUILTIN_ALG_SHA_1 - #undef MBEDTLS_SHA1_C +#define MBEDTLS_PSA_ACCEL_ALG_SHA_1 +#undef MBEDTLS_PSA_BUILTIN_ALG_SHA_1 +#undef MBEDTLS_SHA1_C +#else +#undef PSA_WANT_ALG_SHA_1 #endif +#define MBEDTLS_PSA_ACCEL_ALG_SHA_224 #undef MBEDTLS_PSA_BUILTIN_ALG_SHA_224 #undef MBEDTLS_SHA224_C -#define MBEDTLS_PSA_ACCEL_ALG_SHA_224 #undef MBEDTLS_PSA_BUILTIN_ALG_SHA_256 #define MBEDTLS_PSA_ACCEL_ALG_SHA_256 #undef MBEDTLS_SHA256_C +#if SOC_SHA_SUPPORT_SHA512 && CONFIG_MBEDTLS_SHA512_C +#define MBEDTLS_PSA_ACCEL_ALG_SHA_384 +#undef MBEDTLS_PSA_BUILTIN_ALG_SHA_384 +#define MBEDTLS_PSA_ACCEL_ALG_SHA_512 +#undef MBEDTLS_PSA_BUILTIN_ALG_SHA_512 +#undef MBEDTLS_SHA384_C +#undef MBEDTLS_SHA512_C +#else +#undef PSA_WANT_ALG_SHA_384 +#undef PSA_WANT_ALG_SHA_512 +#undef MBEDTLS_SHA512_ALT +#endif +#if !SOC_HMAC_SUPPORTED +#define ESP_HMAC_TRANSPARENT_DRIVER_ENABLED +#define MBEDTLS_PSA_ACCEL_ALG_HMAC +#undef MBEDTLS_PSA_BUILTIN_ALG_HMAC +#endif #endif #if SOC_ECC_SUPPORTED @@ -91,17 +103,6 @@ #if SOC_HMAC_SUPPORTED #define ESP_HMAC_OPAQUE_DRIVER_ENABLED -#else -#undef MBEDTLS_PSA_ACCEL_KEY_TYPE_HMAC -#endif - -#if CONFIG_MBEDTLS_ROM_MD5 -#define ESP_MD5_DRIVER_ENABLED -#define MBEDTLS_PSA_ACCEL_ALG_MD5 -#undef MBEDTLS_PSA_BUILTIN_ALG_MD5 -#else -#undef PSA_WANT_ALG_MD5 -#undef MBEDTLS_MD5_C #endif #undef PSA_WANT_ECC_SECP_R1_192 @@ -122,21 +123,23 @@ #undef PSA_WANT_KEY_TYPE_DES #undef PSA_WANT_ALG_RIPEMD160 #undef PSA_WANT_ALG_CHACHA20 +#undef MBEDTLS_CHACHA20_C #undef PSA_WANT_ALG_CHACHA20_POLY1305 +#undef MBEDTLS_CHACHAPOLY_C #undef PSA_WANT_ALG_CCM #undef PSA_WANT_ALG_CMAC -#define MBEDTLS_AES_ROM_TABLES -#if SOC_AES_SUPPORTED -#define MBEDTLS_AES_FEWER_TABLES -#endif - /* Disable unused hash algorithms */ #undef PSA_WANT_ALG_MD5 +#undef MBEDTLS_MD5_C #undef PSA_WANT_ALG_SHA3_224 +#undef MBEDTLS_SHA3_224_C #undef PSA_WANT_ALG_SHA3_256 +#undef MBEDTLS_SHA3_256_C #undef PSA_WANT_ALG_SHA3_384 +#undef MBEDTLS_SHA3_384_C #undef PSA_WANT_ALG_SHA3_512 +#undef MBEDTLS_SHA3_512_C /* Disable RSA — not used by TEE */ #undef PSA_WANT_KEY_TYPE_RSA_KEY_PAIR_BASIC @@ -164,11 +167,9 @@ #undef MBEDTLS_SSL_CLI_C #undef MBEDTLS_SSL_SRV_C -#undef PSA_WANT_ALG_PBKDF2_HMAC #undef PSA_WANT_ALG_TLS12_PRF +#undef PSA_WANT_ALG_PBKDF2_HMAC #undef PSA_WANT_ALG_PBKDF2_AES_CMAC_PRF_128 -#undef PSA_WANT_ALG_CCM -#undef PSA_WANT_ALG_CMAC #undef MBEDTLS_AES_C #define MBEDTLS_AES_ROM_TABLES diff --git a/components/mbedtls/port/aes/dma/esp_aes_dma_core.c b/components/mbedtls/port/aes/dma/esp_aes_dma_core.c index ac120a33630..544bb3a4e58 100644 --- a/components/mbedtls/port/aes/dma/esp_aes_dma_core.c +++ b/components/mbedtls/port/aes/dma/esp_aes_dma_core.c @@ -244,6 +244,7 @@ static int esp_aes_process_dma_ext_ram(esp_aes_context *ctx, const unsigned char unsigned char *output_buf = NULL; const unsigned char *dma_input; chunk_len = MIN(AES_MAX_CHUNK_WRITE_SIZE, len); + const size_t alloc_chunk_len = chunk_len; size_t input_alignment = 1; size_t output_alignment = 1; @@ -313,10 +314,12 @@ static int esp_aes_process_dma_ext_ram(esp_aes_context *ctx, const unsigned char cleanup: - if (realloc_input) { + if (realloc_input && input_buf) { + mbedtls_platform_zeroize(input_buf, alloc_chunk_len); free(input_buf); } - if (realloc_output) { + if (realloc_output && output_buf) { + mbedtls_platform_zeroize(output_buf, alloc_chunk_len); free(output_buf); } @@ -459,7 +462,7 @@ static esp_err_t generate_descriptor_list(const uint8_t *buffer, const size_t le dma_descriptors = (crypto_dma_desc_t *) aes_dma_calloc(dma_descs_needed, sizeof(crypto_dma_desc_t), MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL, NULL); if (dma_descriptors == NULL) { ESP_LOGE(TAG, "Failed to allocate memory for the array of DMA descriptors"); - return ESP_FAIL; + goto err; } size_t populated_dma_descs = 0; @@ -468,7 +471,7 @@ static esp_err_t generate_descriptor_list(const uint8_t *buffer, const size_t le start_alignment_stream_buffer = aes_dma_calloc(alignment_buffer_size, sizeof(uint8_t), AES_DMA_ALLOC_CAPS | (esp_ptr_external_ram(buffer) ? MALLOC_CAP_SPIRAM : MALLOC_CAP_INTERNAL) , NULL); if (start_alignment_stream_buffer == NULL) { ESP_LOGE(TAG, "Failed to allocate memory for start alignment buffer"); - return ESP_FAIL; + goto err; } memset(start_alignment_stream_buffer, 0, unaligned_start_bytes); @@ -490,7 +493,7 @@ static esp_err_t generate_descriptor_list(const uint8_t *buffer, const size_t le end_alignment_stream_buffer = aes_dma_calloc(alignment_buffer_size, sizeof(uint8_t), AES_DMA_ALLOC_CAPS | (esp_ptr_external_ram(buffer) ? MALLOC_CAP_SPIRAM : MALLOC_CAP_INTERNAL), NULL); if (end_alignment_stream_buffer == NULL) { ESP_LOGE(TAG, "Failed to allocate memory for end alignment buffer"); - return ESP_FAIL; + goto err; } memset(end_alignment_stream_buffer, 0, unaligned_end_bytes); @@ -504,7 +507,7 @@ static esp_err_t generate_descriptor_list(const uint8_t *buffer, const size_t le if (dma_desc_link(dma_descriptors, dma_descs_needed, cache_line_size) != ESP_OK) { ESP_LOGE(TAG, "DMA descriptors cache sync C2M failed"); - return ESP_FAIL; + goto err; } ret: @@ -525,6 +528,18 @@ ret: *end_alignment_buffer = end_alignment_stream_buffer; return ESP_OK; + +err: + if (start_alignment_stream_buffer) { + mbedtls_platform_zeroize(start_alignment_stream_buffer, alignment_buffer_size); + free(start_alignment_stream_buffer); + } + if (end_alignment_stream_buffer) { + mbedtls_platform_zeroize(end_alignment_stream_buffer, alignment_buffer_size); + free(end_alignment_stream_buffer); + } + free(dma_descriptors); + return ESP_FAIL; } int esp_aes_process_dma(esp_aes_context *ctx, const unsigned char *input, unsigned char *output, size_t len, uint8_t *stream_out) @@ -589,19 +604,12 @@ int esp_aes_process_dma(esp_aes_context *ctx, const unsigned char *input, unsign } size_t input_alignment_buffer_size = MAX(2 * input_cache_line_size, AES_BLOCK_BYTES); + size_t output_alignment_buffer_size = MAX(2 * output_cache_line_size, AES_BLOCK_BYTES); crypto_dma_desc_t *input_desc = NULL; uint8_t *input_start_stream_buffer = NULL; uint8_t *input_end_stream_buffer = NULL; - if (generate_descriptor_list(input, len, &input_start_stream_buffer, &input_end_stream_buffer, input_alignment_buffer_size, input_cache_line_size, NULL, NULL, &input_desc, NULL, false) != ESP_OK) { - mbedtls_platform_zeroize(output, len); - ESP_LOGE(TAG, "Generating input DMA descriptors failed"); - return -1; - } - - size_t output_alignment_buffer_size = MAX(2 * output_cache_line_size, AES_BLOCK_BYTES); - crypto_dma_desc_t *output_desc = NULL; uint8_t *output_start_stream_buffer = NULL; uint8_t *output_end_stream_buffer = NULL; @@ -609,10 +617,16 @@ int esp_aes_process_dma(esp_aes_context *ctx, const unsigned char *input, unsign size_t output_end_alignment = 0; size_t output_dma_desc_num = 0; + if (generate_descriptor_list(input, len, &input_start_stream_buffer, &input_end_stream_buffer, input_alignment_buffer_size, input_cache_line_size, NULL, NULL, &input_desc, NULL, false) != ESP_OK) { + ESP_LOGE(TAG, "Generating input DMA descriptors failed"); + ret = -1; + goto cleanup; + } + if (generate_descriptor_list(output, len, &output_start_stream_buffer, &output_end_stream_buffer, output_alignment_buffer_size, output_cache_line_size, &output_start_alignment, &output_end_alignment, &output_desc, &output_dma_desc_num, true) != ESP_OK) { - mbedtls_platform_zeroize(output, len); ESP_LOGE(TAG, "Generating output DMA descriptors failed"); - return -1; + ret = -1; + goto cleanup; } crypto_dma_desc_t *out_desc_tail = &output_desc[output_dma_desc_num - 1]; @@ -705,11 +719,23 @@ cleanup: mbedtls_platform_zeroize(output, len); } - free(input_start_stream_buffer); - free(input_end_stream_buffer); + if (input_start_stream_buffer) { + mbedtls_platform_zeroize(input_start_stream_buffer, input_alignment_buffer_size); + free(input_start_stream_buffer); + } + if (input_end_stream_buffer) { + mbedtls_platform_zeroize(input_end_stream_buffer, input_alignment_buffer_size); + free(input_end_stream_buffer); + } - free(output_start_stream_buffer); - free(output_end_stream_buffer); + if (output_start_stream_buffer) { + mbedtls_platform_zeroize(output_start_stream_buffer, output_alignment_buffer_size); + free(output_start_stream_buffer); + } + if (output_end_stream_buffer) { + mbedtls_platform_zeroize(output_end_stream_buffer, output_alignment_buffer_size); + free(output_end_stream_buffer); + } free(input_desc); free(output_desc); @@ -918,12 +944,24 @@ cleanup: free(aad_end_stream_buffer); free(aad_desc); - free(input_start_stream_buffer); - free(input_end_stream_buffer); + if (input_start_stream_buffer) { + mbedtls_platform_zeroize(input_start_stream_buffer, input_alignment_buffer_size); + free(input_start_stream_buffer); + } + if (input_end_stream_buffer) { + mbedtls_platform_zeroize(input_end_stream_buffer, input_alignment_buffer_size); + free(input_end_stream_buffer); + } free(input_desc); - free(output_start_stream_buffer); - free(output_end_stream_buffer); + if (output_start_stream_buffer) { + mbedtls_platform_zeroize(output_start_stream_buffer, output_alignment_buffer_size); + free(output_start_stream_buffer); + } + if (output_end_stream_buffer) { + mbedtls_platform_zeroize(output_end_stream_buffer, output_alignment_buffer_size); + free(output_end_stream_buffer); + } free(output_desc); free(len_buf); diff --git a/components/mbedtls/port/aes/esp_aes_common.c b/components/mbedtls/port/aes/esp_aes_common.c index 976e2a71463..e5a37057dfa 100644 --- a/components/mbedtls/port/aes/esp_aes_common.c +++ b/components/mbedtls/port/aes/esp_aes_common.c @@ -6,7 +6,7 @@ * * SPDX-License-Identifier: Apache-2.0 * - * SPDX-FileContributor: 2016-2024 Espressif Systems (Shanghai) CO LTD + * SPDX-FileContributor: 2016-2026 Espressif Systems (Shanghai) CO LTD */ /* * The AES block cipher was designed by Vincent Rijmen and Joan Daemen. @@ -21,6 +21,7 @@ #include "hal/aes_types.h" #include "soc/soc_caps.h" #include "psa/crypto.h" +#include "mbedtls/platform_util.h" #include @@ -43,7 +44,7 @@ bool valid_key_length(const esp_aes_context *ctx) void esp_aes_init(esp_aes_context *ctx) { - bzero(ctx, sizeof(esp_aes_context)); + memset(ctx, 0, sizeof(esp_aes_context)); #if SOC_AES_SUPPORT_DMA && CONFIG_MBEDTLS_AES_USE_INTERRUPT esp_aes_intr_alloc(); #endif @@ -55,7 +56,7 @@ void esp_aes_free( esp_aes_context *ctx ) return; } - bzero( ctx, sizeof( esp_aes_context ) ); + mbedtls_platform_zeroize( ctx, sizeof( esp_aes_context ) ); } /* diff --git a/components/mbedtls/port/aes/esp_aes_gcm.c b/components/mbedtls/port/aes/esp_aes_gcm.c index b118817ee43..4ffb820c430 100644 --- a/components/mbedtls/port/aes/esp_aes_gcm.c +++ b/components/mbedtls/port/aes/esp_aes_gcm.c @@ -6,7 +6,7 @@ * * SPDX-License-Identifier: Apache-2.0 * - * SPDX-FileContributor: 2016-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileContributor: 2016-2026 Espressif Systems (Shanghai) CO LTD */ /* * The AES block cipher was designed by Vincent Rijmen and Joan Daemen. @@ -19,6 +19,7 @@ #include "aes/esp_aes_gcm.h" #include "esp_aes_internal.h" #include "hal/aes_hal.h" +#include "mbedtls/platform_util.h" #include "esp_heap_caps.h" #include "esp_log.h" @@ -321,7 +322,7 @@ void esp_aes_gcm_init( esp_gcm_context *ctx) return; } - bzero(ctx, sizeof(esp_gcm_context)); + memset(ctx, 0, sizeof(esp_gcm_context)); #if SOC_AES_SUPPORT_DMA && CONFIG_MBEDTLS_AES_USE_INTERRUPT esp_aes_intr_alloc(); @@ -336,7 +337,7 @@ void esp_aes_gcm_free( esp_gcm_context *ctx) if (ctx == NULL) { return; } - bzero(ctx, sizeof(esp_gcm_context)); + mbedtls_platform_zeroize(ctx, sizeof(esp_gcm_context)); } /* Setup AES-GCM */ @@ -719,7 +720,7 @@ int esp_aes_gcm_auth_decrypt( esp_gcm_context *ctx, } if ( diff != 0 ) { - bzero( output, length ); + mbedtls_platform_zeroize( output, length ); return ( PSA_ERROR_INVALID_SIGNATURE ); } diff --git a/components/mbedtls/port/bignum/esp_bignum.c b/components/mbedtls/port/bignum/esp_bignum.c index 50b60bddebf..42392c6e844 100644 --- a/components/mbedtls/port/bignum/esp_bignum.c +++ b/components/mbedtls/port/bignum/esp_bignum.c @@ -6,7 +6,7 @@ * * SPDX-License-Identifier: Apache-2.0 * - * SPDX-FileContributor: 2016-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileContributor: 2016-2026 Espressif Systems (Shanghai) CO LTD */ #include #include @@ -134,6 +134,7 @@ static int calculate_rinv(mbedtls_mpi *Rinv, const mbedtls_mpi *M, int num_words mbedtls_mpi_init(&RR); MBEDTLS_MPI_CHK(mbedtls_mpi_set_bit(&RR, num_bits * 2, 1)); MBEDTLS_MPI_CHK(mbedtls_mpi_mod_mpi(Rinv, &RR, M)); + MBEDTLS_MPI_CHK(mbedtls_mpi_shrink(Rinv, num_words)); cleanup: mbedtls_mpi_free(&RR); @@ -359,12 +360,44 @@ cleanup2: static int esp_mpi_exp_mod( mbedtls_mpi *Z, const mbedtls_mpi *X, const mbedtls_mpi *Y, const mbedtls_mpi *M, mbedtls_mpi *_Rinv ) { int ret = 0; + mbedtls_mpi X_temp; + const mbedtls_mpi *X_ptr = X; mbedtls_mpi Rinv_new; /* used if _Rinv == NULL */ mbedtls_mpi *Rinv; /* points to _Rinv (if not NULL) otherwise &RR_new */ mbedtls_mpi_uint Mprime; - size_t x_words = mpi_words(X); + mbedtls_mpi_init(&X_temp); + mbedtls_mpi_init(&Rinv_new); + + /* Validate modulus M and exponent Y first to avoid passing invalid inputs to reduction */ + if (mbedtls_mpi_cmp_int(M, 0) <= 0 || (M->MBEDTLS_PRIVATE(p[0]) & 1) == 0) { + ret = MBEDTLS_ERR_MPI_BAD_INPUT_DATA; + goto cleanup; + } + + if (mbedtls_mpi_cmp_int(Y, 0) < 0) { + ret = MBEDTLS_ERR_MPI_BAD_INPUT_DATA; + goto cleanup; + } + + if (mbedtls_mpi_cmp_int(Y, 0) == 0) { + ret = mbedtls_mpi_lset(Z, 1); + goto cleanup; + } + + /* Perform base reduction if absolute value of base X is larger than modulus M */ + if (mbedtls_mpi_cmp_abs(X, M) >= 0) { + MBEDTLS_MPI_CHK(mbedtls_mpi_copy(&X_temp, X)); + X_temp.MBEDTLS_PRIVATE(s) = 1; + MBEDTLS_MPI_CHK(mbedtls_mpi_mod_mpi(&X_temp, &X_temp, M)); + if (mbedtls_mpi_cmp_int(&X_temp, 0) != 0) { + X_temp.MBEDTLS_PRIVATE(s) = X->MBEDTLS_PRIVATE(s); + } + X_ptr = &X_temp; + } + + size_t x_words = mpi_words(X_ptr); size_t y_words = mpi_words(Y); size_t m_words = mpi_words(M); @@ -374,30 +407,21 @@ static int esp_mpi_exp_mod( mbedtls_mpi *Z, const mbedtls_mpi *X, const mbedtls_ size_t num_words = mpi_hal_calc_hardware_words(MAX(m_words, MAX(x_words, y_words))); if (num_words * 32 > SOC_RSA_MAX_BIT_LEN) { - return MBEDTLS_ERR_MPI_NOT_ACCEPTABLE; - } - - if (mbedtls_mpi_cmp_int(M, 0) <= 0 || (M->MBEDTLS_PRIVATE(p[0]) & 1) == 0) { - return MBEDTLS_ERR_MPI_BAD_INPUT_DATA; - } - - if (mbedtls_mpi_cmp_int(Y, 0) < 0) { - return MBEDTLS_ERR_MPI_BAD_INPUT_DATA; - } - - if (mbedtls_mpi_cmp_int(Y, 0) == 0) { - return mbedtls_mpi_lset(Z, 1); + ret = MBEDTLS_ERR_MPI_NOT_ACCEPTABLE; + goto cleanup; } /* Determine RR pointer, either _RR for cached value or local RR_new */ if (_Rinv == NULL) { - mbedtls_mpi_init(&Rinv_new); Rinv = &Rinv_new; } else { Rinv = _Rinv; } - if (Rinv->MBEDTLS_PRIVATE(p) == NULL) { + /* Rinv depends on num_words, which may vary with blinded exponents. + calculate_rinv() stores Rinv with exactly num_words limbs, so the + allocation size is used here as the cache tag. */ + if (Rinv->MBEDTLS_PRIVATE(p) == NULL || Rinv->MBEDTLS_PRIVATE(n) != num_words) { MBEDTLS_MPI_CHK(calculate_rinv(Rinv, M, num_words)); } @@ -405,7 +429,7 @@ static int esp_mpi_exp_mod( mbedtls_mpi *Z, const mbedtls_mpi *X, const mbedtls_ // Montgomery exponentiation: Z = X ^ Y mod M (HAC 14.94) #ifdef ESP_MPI_USE_MONT_EXP - ret = mpi_montgomery_exp_calc(Z, X, Y, M, Rinv, num_words, Mprime) ; + ret = mpi_montgomery_exp_calc(Z, X_ptr, Y, M, Rinv, num_words, Mprime) ; MBEDTLS_MPI_CHK(ret); #else esp_mpi_enable_hardware_hw_op(); @@ -418,7 +442,7 @@ static int esp_mpi_exp_mod( mbedtls_mpi *Z, const mbedtls_mpi *X, const mbedtls_ } #endif - esp_mpi_exp_mpi_mod_hw_op(X, Y, M, Rinv, Mprime, num_words); + esp_mpi_exp_mpi_mod_hw_op(X_ptr, Y, M, Rinv, Mprime, num_words); ret = mbedtls_mpi_grow(Z, m_words); if (ret != 0) { esp_mpi_disable_hardware_hw_op(); @@ -440,7 +464,7 @@ static int esp_mpi_exp_mod( mbedtls_mpi *Z, const mbedtls_mpi *X, const mbedtls_ #endif // Compensate for negative X - if (X->MBEDTLS_PRIVATE(s) == -1 && (Y->MBEDTLS_PRIVATE(p[0]) & 1) != 0) { + if (X_ptr->MBEDTLS_PRIVATE(s) == -1 && (Y->MBEDTLS_PRIVATE(p[0]) & 1) != 0) { Z->MBEDTLS_PRIVATE(s) = -1; MBEDTLS_MPI_CHK(mbedtls_mpi_add_mpi(Z, M, Z)); } else { @@ -448,9 +472,8 @@ static int esp_mpi_exp_mod( mbedtls_mpi *Z, const mbedtls_mpi *X, const mbedtls_ } cleanup: - if (_Rinv == NULL) { - mbedtls_mpi_free(&Rinv_new); - } + mbedtls_mpi_free(&Rinv_new); + mbedtls_mpi_free(&X_temp); return ret; } diff --git a/components/mbedtls/port/include/mbedtls/esp_config.h b/components/mbedtls/port/include/mbedtls/esp_config.h index fe0b0f344b6..f877145a555 100644 --- a/components/mbedtls/port/include/mbedtls/esp_config.h +++ b/components/mbedtls/port/include/mbedtls/esp_config.h @@ -810,8 +810,10 @@ #else #undef MBEDTLS_FS_IO #undef MBEDTLS_PSA_ITS_FILE_C +#if !defined(ESP_PSA_ITS_AVAILABLE) #undef MBEDTLS_PSA_CRYPTO_STORAGE_C #endif +#endif #ifndef CONFIG_IDF_TARGET_LINUX @@ -2093,6 +2095,18 @@ #undef PSA_WANT_ALG_ECDH #endif +/** + * \def MBEDTLS_ECJPAKE_C + * + * Enable the ECJPAKE based ciphersuites. + */ +#ifdef CONFIG_MBEDTLS_ECJPAKE_C +#define PSA_WANT_ALG_JPAKE 1 +#else +#undef PSA_WANT_ALG_JPAKE +#undef PSA_WANT_ALG_TLS12_ECJPAKE_TO_PMS +#endif + /** * \def MBEDTLS_ECDSA_C * diff --git a/components/mbedtls/port/mbedtls_rom/mbedtls_rom_osi.c b/components/mbedtls/port/mbedtls_rom/mbedtls_rom_osi.c index d29d03e966d..ac72ab87498 100644 --- a/components/mbedtls/port/mbedtls_rom/mbedtls_rom_osi.c +++ b/components/mbedtls/port/mbedtls_rom/mbedtls_rom_osi.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2023-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -10,436 +10,526 @@ #include MBEDTLS_CONFIG_FILE #endif -#include "soc/chip_revision.h" -#include "hal/efuse_hal.h" #include "mbedtls/platform.h" #include "mbedtls_rom_osi.h" +#ifndef MBEDTLS_ERR_THREADING_BAD_INPUT_DATA +#define MBEDTLS_ERR_THREADING_BAD_INPUT_DATA MBEDTLS_ERR_THREADING_USAGE_ERROR +#endif + +#define MBEDTLS_ROM_ECO4_FUNC_COUNT 221 +_Static_assert(sizeof(mbedtls_rom_eco4_funcs_t) == MBEDTLS_ROM_ECO4_FUNC_COUNT * sizeof(void (*)(void)), + "mbedtls_rom_eco4_funcs_t layout must match ROM"); + +#define ROM_TABLE_FN(table_type, field, fn) ((__typeof__(((table_type *)0)->field))(fn)) + void mbedtls_rom_osi_functions_init(void); -static void mbedtls_rom_mutex_init( mbedtls_threading_mutex_t *mutex ) -{ - if (mutex == NULL) { - return; - } +extern void mbedtls_internal_sha1_process(void) __attribute__((weak)); +extern void mbedtls_internal_sha256_process(void) __attribute__((weak)); +extern void mbedtls_internal_sha512_process(void) __attribute__((weak)); +extern void mbedtls_internal_aes_encrypt(void) __attribute__((weak)); +extern void mbedtls_internal_aes_decrypt(void) __attribute__((weak)); +extern int mbedtls_sha1_update(mbedtls_sha1_context *ctx, const unsigned char *input, size_t ilen) __attribute__((weak)); +extern int mbedtls_sha256_update(mbedtls_sha256_context *ctx, const unsigned char *input, size_t ilen) __attribute__((weak)); +extern void mbedtls_oid_get_cipher_alg(void) __attribute__((weak)); +extern void mbedtls_oid_get_ec_grp(void) __attribute__((weak)); +extern void mbedtls_oid_get_md_alg(void) __attribute__((weak)); +extern void mbedtls_oid_get_md_hmac(void) __attribute__((weak)); +extern void mbedtls_oid_get_oid_by_md(void) __attribute__((weak)); +extern void mbedtls_oid_get_pk_alg(void) __attribute__((weak)); +extern void mbedtls_oid_get_pkcs12_pbe_alg(void) __attribute__((weak)); +extern void mbedtls_oid_get_sig_alg(void) __attribute__((weak)); +extern void mbedtls_oid_get_x509_ext_type(void) __attribute__((weak)); + +extern void rom_mbedtls_threading_set_alt(void (*mutex_init)(mbedtls_threading_mutex_t *), + void (*mutex_free)(mbedtls_threading_mutex_t *), + int (*mutex_lock)(mbedtls_threading_mutex_t *), + int (*mutex_unlock)(mbedtls_threading_mutex_t *)); #if defined(MBEDTLS_THREADING_ALT) - mutex->mutex = xSemaphoreCreateMutex(); - assert(mutex->mutex != NULL); +static int mbedtls_rom_platform_mutex_init(mbedtls_platform_mutex_t *mutex); +static void mbedtls_rom_platform_mutex_free(mbedtls_platform_mutex_t *mutex); +static int mbedtls_rom_platform_mutex_lock(mbedtls_platform_mutex_t *mutex); +static int mbedtls_rom_platform_mutex_unlock(mbedtls_platform_mutex_t *mutex); +#endif + +static void mbedtls_rom_mutex_init(mbedtls_threading_mutex_t *mutex) +{ +#if defined(MBEDTLS_THREADING_ALT) + int ret = mbedtls_rom_platform_mutex_init(&mutex->MBEDTLS_PRIVATE(mutex)); + mutex->MBEDTLS_PRIVATE(initialized) = (ret == 0); #else mbedtls_mutex_init(mutex); #endif } -static void mbedtls_rom_mutex_free( mbedtls_threading_mutex_t *mutex ) +static void mbedtls_rom_mutex_free(mbedtls_threading_mutex_t *mutex) { - if (mutex == NULL) { +#if defined(MBEDTLS_THREADING_ALT) + if (!mutex->MBEDTLS_PRIVATE(initialized)) { return; } - -#if defined(MBEDTLS_THREADING_ALT) - vSemaphoreDelete(mutex->mutex); + mbedtls_rom_platform_mutex_free(&mutex->MBEDTLS_PRIVATE(mutex)); + mutex->MBEDTLS_PRIVATE(initialized) = 0; #else mbedtls_mutex_free(mutex); #endif } -static int mbedtls_rom_mutex_lock( mbedtls_threading_mutex_t *mutex ) +static int mbedtls_rom_mutex_lock(mbedtls_threading_mutex_t *mutex) { - if (mutex == NULL) { - return MBEDTLS_ERR_THREADING_BAD_INPUT_DATA; - } - #if defined(MBEDTLS_THREADING_ALT) - if (xSemaphoreTake(mutex->mutex, portMAX_DELAY) != pdTRUE) { - return MBEDTLS_ERR_THREADING_MUTEX_ERROR; + if (!mutex->MBEDTLS_PRIVATE(initialized)) { + return MBEDTLS_ERR_THREADING_USAGE_ERROR; } - return 0; + return mbedtls_rom_platform_mutex_lock(&mutex->MBEDTLS_PRIVATE(mutex)); #else return mbedtls_mutex_lock(mutex); #endif } -static int mbedtls_rom_mutex_unlock( mbedtls_threading_mutex_t *mutex ) +static int mbedtls_rom_mutex_unlock(mbedtls_threading_mutex_t *mutex) { - if (mutex == NULL) { - return MBEDTLS_ERR_THREADING_BAD_INPUT_DATA; - } - #if defined(MBEDTLS_THREADING_ALT) - if (xSemaphoreGive(mutex->mutex) != pdTRUE) { - return MBEDTLS_ERR_THREADING_MUTEX_ERROR; + if (!mutex->MBEDTLS_PRIVATE(initialized)) { + return MBEDTLS_ERR_THREADING_USAGE_ERROR; } - return 0; + return mbedtls_rom_platform_mutex_unlock(&mutex->MBEDTLS_PRIVATE(mutex)); #else return mbedtls_mutex_unlock(mutex); #endif } -/* This structure can be automatically generated by the script with rom.mbedtls.ld. */ -static const mbedtls_rom_funcs_t mbedtls_rom_funcs_table = { - /* Fill the ROM functions into mbedtls rom function table. */ - /* aes module */ - ._rom_mbedtls_aes_init = mbedtls_aes_init, - ._rom_mbedtls_aes_free = mbedtls_aes_free, - ._rom_mbedtls_aes_setkey_enc = mbedtls_aes_setkey_enc, - ._rom_mbedtls_aes_setkey_dec = mbedtls_aes_setkey_dec, - ._rom_mbedtls_aes_crypt_ecb = mbedtls_aes_crypt_ecb, - ._rom_mbedtls_aes_crypt_cbc = mbedtls_aes_crypt_cbc, - ._rom_mbedtls_internal_aes_encrypt = mbedtls_internal_aes_encrypt, - ._rom_mbedtls_internal_aes_decrypt = mbedtls_internal_aes_decrypt, - /* asn1 module */ - ._rom_mbedtls_asn1_get_len = mbedtls_asn1_get_len, - ._rom_mbedtls_asn1_get_tag = mbedtls_asn1_get_tag, - ._rom_mbedtls_asn1_get_bool = mbedtls_asn1_get_bool, - ._rom_mbedtls_asn1_get_int = mbedtls_asn1_get_int, - ._rom_mbedtls_asn1_get_bitstring = mbedtls_asn1_get_bitstring, - ._rom_mbedtls_asn1_get_bitstring_null = mbedtls_asn1_get_bitstring_null, - ._rom_mbedtls_asn1_get_sequence_of = mbedtls_asn1_get_sequence_of, - ._rom_mbedtls_asn1_get_mpi = mbedtls_asn1_get_mpi, - ._rom_mbedtls_asn1_get_alg = mbedtls_asn1_get_alg, - ._rom_mbedtls_asn1_get_alg_null = mbedtls_asn1_get_alg_null, - ._rom_mbedtls_asn1_write_len = mbedtls_asn1_write_len, - ._rom_mbedtls_asn1_write_tag = mbedtls_asn1_write_tag, - ._rom_mbedtls_asn1_write_mpi = mbedtls_asn1_write_mpi, - /* base64 module */ - ._rom_mbedtls_base64_decode = mbedtls_base64_decode, - /* bignum module */ - ._rom_mbedtls_mpi_init = mbedtls_mpi_init, - ._rom_mbedtls_mpi_free = mbedtls_mpi_free, - ._rom_mbedtls_mpi_grow = mbedtls_mpi_grow, - ._rom_mbedtls_mpi_shrink = mbedtls_mpi_shrink, - ._rom_mbedtls_mpi_copy = mbedtls_mpi_copy, - ._rom_mbedtls_mpi_safe_cond_assign = mbedtls_mpi_safe_cond_assign, - ._rom_mbedtls_mpi_safe_cond_swap = mbedtls_mpi_safe_cond_swap, - ._rom_mbedtls_mpi_lset = mbedtls_mpi_lset, - ._rom_mbedtls_mpi_get_bit = mbedtls_mpi_get_bit, - ._rom_mbedtls_mpi_set_bit = mbedtls_mpi_set_bit, - ._rom_mbedtls_mpi_lsb = mbedtls_mpi_lsb, - ._rom_mbedtls_mpi_bitlen = mbedtls_mpi_bitlen, - ._rom_mbedtls_mpi_size = mbedtls_mpi_size, - ._rom_mbedtls_mpi_read_binary = mbedtls_mpi_read_binary, - ._rom_mbedtls_mpi_write_binary = mbedtls_mpi_write_binary, - ._rom_mbedtls_mpi_shift_l = mbedtls_mpi_shift_l, - ._rom_mbedtls_mpi_shift_r = mbedtls_mpi_shift_r, - ._rom_mbedtls_mpi_cmp_abs = mbedtls_mpi_cmp_abs, - ._rom_mbedtls_mpi_cmp_mpi = mbedtls_mpi_cmp_mpi, - ._rom_mbedtls_mpi_lt_mpi_ct = mbedtls_mpi_lt_mpi_ct, - ._rom_mbedtls_mpi_cmp_int = mbedtls_mpi_cmp_int, - ._rom_mbedtls_mpi_add_abs = mbedtls_mpi_add_abs, - ._rom_mbedtls_mpi_sub_abs = mbedtls_mpi_sub_abs, - ._rom_mbedtls_mpi_add_mpi = mbedtls_mpi_add_mpi, - ._rom_mbedtls_mpi_sub_mpi = mbedtls_mpi_sub_mpi, - ._rom_mbedtls_mpi_add_int = mbedtls_mpi_add_int, - ._rom_mbedtls_mpi_sub_int = mbedtls_mpi_sub_int, - ._rom_mbedtls_mpi_mul_mpi = mbedtls_mpi_mul_mpi, - ._rom_mbedtls_mpi_mul_int = mbedtls_mpi_mul_int, - ._rom_mbedtls_mpi_div_mpi = mbedtls_mpi_div_mpi, - ._rom_mbedtls_mpi_div_int = mbedtls_mpi_div_int, - ._rom_mbedtls_mpi_mod_mpi = mbedtls_mpi_mod_mpi, - ._rom_mbedtls_mpi_mod_int = mbedtls_mpi_mod_int, - ._rom_mbedtls_mpi_exp_mod = mbedtls_mpi_exp_mod, - ._rom_mbedtls_mpi_fill_random = mbedtls_mpi_fill_random, - ._rom_mbedtls_mpi_gcd = mbedtls_mpi_gcd, - ._rom_mbedtls_mpi_inv_mod = mbedtls_mpi_inv_mod, - ._rom_mbedtls_mpi_is_prime_ext = mbedtls_mpi_is_prime_ext, - /* ccm module */ - ._rom_mbedtls_ccm_star_encrypt_and_tag = mbedtls_ccm_star_encrypt_and_tag, - ._rom_mbedtls_ccm_star_auth_decrypt = mbedtls_ccm_star_auth_decrypt, - /* cipher module */ - ._rom_mbedtls_cipher_init = mbedtls_cipher_init, - ._rom_mbedtls_cipher_set_padding_mode = mbedtls_cipher_set_padding_mode, - ._rom_mbedtls_cipher_reset = mbedtls_cipher_reset, - ._rom_mbedtls_cipher_finish = mbedtls_cipher_finish, - ._rom_mbedtls_cipher_crypt = mbedtls_cipher_crypt, - ._rom_mbedtls_cipher_cmac_starts = mbedtls_cipher_cmac_starts, - ._rom_mbedtls_cipher_cmac_update = mbedtls_cipher_cmac_update, - ._rom_mbedtls_cipher_cmac_finish = mbedtls_cipher_cmac_finish, - /* ctr drbg module */ - ._rom_mbedtls_ctr_drbg_init = mbedtls_ctr_drbg_init, - ._rom_mbedtls_ctr_drbg_seed = mbedtls_ctr_drbg_seed, - ._rom_mbedtls_ctr_drbg_free = mbedtls_ctr_drbg_free, - ._rom_mbedtls_ctr_drbg_reseed = mbedtls_ctr_drbg_reseed, - ._rom_mbedtls_ctr_drbg_random_with_add = mbedtls_ctr_drbg_random_with_add, - ._rom_mbedtls_ctr_drbg_random = mbedtls_ctr_drbg_random, - /* sha1 module */ - ._rom_mbedtls_sha1_init = mbedtls_sha1_init, - ._rom_mbedtls_sha1_free = mbedtls_sha1_free, - ._rom_mbedtls_sha1_clone = mbedtls_sha1_clone, - ._rom_mbedtls_sha1_starts = mbedtls_sha1_starts, - ._rom_mbedtls_sha1_finish = mbedtls_sha1_finish, - /* sha256 module */ - ._rom_mbedtls_sha256_init = mbedtls_sha256_init, - ._rom_mbedtls_sha256_free = mbedtls_sha256_free, - ._rom_mbedtls_sha256_clone = mbedtls_sha256_clone, - ._rom_mbedtls_sha256_starts = mbedtls_sha256_starts, - ._rom_mbedtls_sha256_finish = mbedtls_sha256_finish, - ._rom_mbedtls_sha256 = mbedtls_sha256, - /* sha512 module */ - ._rom_mbedtls_sha512_init = mbedtls_sha512_init, - ._rom_mbedtls_sha512_free = mbedtls_sha512_free, - ._rom_mbedtls_sha512_clone = mbedtls_sha512_clone, - ._rom_mbedtls_sha512_starts = mbedtls_sha512_starts, - ._rom_mbedtls_sha512_update = mbedtls_sha512_update, - ._rom_mbedtls_sha512_finish = mbedtls_sha512_finish, - ._rom_mbedtls_internal_sha512_process = mbedtls_internal_sha512_process, - ._rom_mbedtls_sha512 = mbedtls_sha512, +#if defined(MBEDTLS_THREADING_ALT) +static int mbedtls_rom_platform_mutex_init(mbedtls_platform_mutex_t *mutex) +{ + if (mutex == NULL) { + return MBEDTLS_ERR_THREADING_BAD_INPUT_DATA; + } - /* Fill the platform functions into mbedtls rom function table. */ - ._mbedtls_mutex_init = mbedtls_rom_mutex_init, - ._mbedtls_mutex_free = mbedtls_rom_mutex_free, - ._mbedtls_mutex_lock = mbedtls_rom_mutex_lock, - ._mbedtls_mutex_unlock = mbedtls_rom_mutex_unlock, - ._mbedtls_calloc = MBEDTLS_PLATFORM_STD_CALLOC, - ._mbedtls_free = MBEDTLS_PLATFORM_STD_FREE, + mutex->mutex = xSemaphoreCreateMutex(); + mutex->is_valid = (mutex->mutex != NULL); + assert(mutex->is_valid); + return mutex->is_valid ? 0 : MBEDTLS_ERR_THREADING_MUTEX_ERROR; +} - /* Fill the SHA functions into mbedtls rom function table, since these functions are not exported in the ROM interface. */ - ._mbedtls_sha1_update = mbedtls_sha1_update, - ._mbedtls_internal_sha1_process = mbedtls_internal_sha1_process, - ._mbedtls_sha256_update = mbedtls_sha256_update, - ._mbedtls_internal_sha256_process = mbedtls_internal_sha256_process, -}; +static void mbedtls_rom_platform_mutex_free(mbedtls_platform_mutex_t *mutex) +{ + if (mutex == NULL || !mutex->is_valid) { + return; + } + + vSemaphoreDelete(mutex->mutex); + mutex->mutex = NULL; + mutex->is_valid = 0; +} + +static int mbedtls_rom_platform_mutex_lock(mbedtls_platform_mutex_t *mutex) +{ + if (mutex == NULL || !mutex->is_valid) { + return MBEDTLS_ERR_THREADING_BAD_INPUT_DATA; + } + + if (xSemaphoreTake(mutex->mutex, portMAX_DELAY) != pdTRUE) { + return MBEDTLS_ERR_THREADING_MUTEX_ERROR; + } + return 0; +} + +static int mbedtls_rom_platform_mutex_unlock(mbedtls_platform_mutex_t *mutex) +{ + if (mutex == NULL || !mutex->is_valid) { + return MBEDTLS_ERR_THREADING_BAD_INPUT_DATA; + } + + if (xSemaphoreGive(mutex->mutex) != pdTRUE) { + return MBEDTLS_ERR_THREADING_MUTEX_ERROR; + } + return 0; +} + +typedef struct mbedtls_rom_cond_waiter { + SemaphoreHandle_t semaphore; + struct mbedtls_rom_cond_waiter *next; +} mbedtls_rom_cond_waiter_t; + +static void mbedtls_rom_cond_remove_waiter(mbedtls_platform_condition_variable_t *cond, + mbedtls_rom_cond_waiter_t *waiter) +{ + mbedtls_rom_cond_waiter_t **current = &cond->waiters; + + while (*current != NULL) { + if (*current == waiter) { + *current = waiter->next; + waiter->next = NULL; + return; + } + current = &(*current)->next; + } +} + +static int mbedtls_rom_cond_init(mbedtls_platform_condition_variable_t *cond) +{ + if (cond == NULL) { + return MBEDTLS_ERR_THREADING_BAD_INPUT_DATA; + } + + cond->mutex = xSemaphoreCreateMutex(); + cond->waiters = NULL; + cond->is_valid = (cond->mutex != NULL); + return cond->is_valid ? 0 : MBEDTLS_ERR_THREADING_MUTEX_ERROR; +} + +static void mbedtls_rom_cond_free(mbedtls_platform_condition_variable_t *cond) +{ + if (cond == NULL || !cond->is_valid) { + return; + } + + vSemaphoreDelete(cond->mutex); + cond->mutex = NULL; + cond->waiters = NULL; + cond->is_valid = 0; +} + +static int mbedtls_rom_cond_signal(mbedtls_platform_condition_variable_t *cond) +{ + mbedtls_rom_cond_waiter_t *waiter; + + if (cond == NULL || !cond->is_valid) { + return MBEDTLS_ERR_THREADING_BAD_INPUT_DATA; + } + + if (xSemaphoreTake(cond->mutex, portMAX_DELAY) != pdTRUE) { + return MBEDTLS_ERR_THREADING_MUTEX_ERROR; + } + + waiter = cond->waiters; + if (waiter != NULL) { + cond->waiters = waiter->next; + waiter->next = NULL; + if (xSemaphoreGive(waiter->semaphore) != pdTRUE) { + (void) xSemaphoreGive(cond->mutex); + return MBEDTLS_ERR_THREADING_MUTEX_ERROR; + } + } + + if (xSemaphoreGive(cond->mutex) != pdTRUE) { + return MBEDTLS_ERR_THREADING_MUTEX_ERROR; + } + + return 0; +} + +static int mbedtls_rom_cond_broadcast(mbedtls_platform_condition_variable_t *cond) +{ + mbedtls_rom_cond_waiter_t *waiter; + + if (cond == NULL || !cond->is_valid) { + return MBEDTLS_ERR_THREADING_BAD_INPUT_DATA; + } + + if (xSemaphoreTake(cond->mutex, portMAX_DELAY) != pdTRUE) { + return MBEDTLS_ERR_THREADING_MUTEX_ERROR; + } + + waiter = cond->waiters; + cond->waiters = NULL; + + while (waiter != NULL) { + mbedtls_rom_cond_waiter_t *next = waiter->next; + waiter->next = NULL; + if (xSemaphoreGive(waiter->semaphore) != pdTRUE) { + (void) xSemaphoreGive(cond->mutex); + return MBEDTLS_ERR_THREADING_MUTEX_ERROR; + } + waiter = next; + } + + return (xSemaphoreGive(cond->mutex) == pdTRUE) ? 0 : MBEDTLS_ERR_THREADING_MUTEX_ERROR; +} + +static int mbedtls_rom_cond_wait(mbedtls_platform_condition_variable_t *cond, + mbedtls_platform_mutex_t *mutex) +{ + int ret; + mbedtls_rom_cond_waiter_t waiter = { 0 }; + + if (cond == NULL || mutex == NULL || !cond->is_valid) { + return MBEDTLS_ERR_THREADING_BAD_INPUT_DATA; + } + + waiter.semaphore = xSemaphoreCreateBinary(); + if (waiter.semaphore == NULL) { + return MBEDTLS_ERR_THREADING_MUTEX_ERROR; + } + + if (xSemaphoreTake(cond->mutex, portMAX_DELAY) != pdTRUE) { + vSemaphoreDelete(waiter.semaphore); + return MBEDTLS_ERR_THREADING_MUTEX_ERROR; + } + + waiter.next = cond->waiters; + cond->waiters = &waiter; + + if (xSemaphoreGive(cond->mutex) != pdTRUE) { + cond->waiters = waiter.next; + waiter.next = NULL; + vSemaphoreDelete(waiter.semaphore); + return MBEDTLS_ERR_THREADING_MUTEX_ERROR; + } + + ret = mbedtls_rom_platform_mutex_unlock(mutex); + if (ret != 0) { + if (xSemaphoreTake(cond->mutex, portMAX_DELAY) == pdTRUE) { + mbedtls_rom_cond_remove_waiter(cond, &waiter); + (void) xSemaphoreGive(cond->mutex); + } + vSemaphoreDelete(waiter.semaphore); + return ret; + } + + if (xSemaphoreTake(waiter.semaphore, portMAX_DELAY) != pdTRUE) { + if (xSemaphoreTake(cond->mutex, portMAX_DELAY) == pdTRUE) { + mbedtls_rom_cond_remove_waiter(cond, &waiter); + (void) xSemaphoreGive(cond->mutex); + } + vSemaphoreDelete(waiter.semaphore); + (void) mbedtls_rom_platform_mutex_lock(mutex); + return MBEDTLS_ERR_THREADING_MUTEX_ERROR; + } + + vSemaphoreDelete(waiter.semaphore); + ret = mbedtls_rom_platform_mutex_lock(mutex); + /* The wakeup has already been consumed, so a re-lock failure is unrecoverable. */ + assert(ret == 0); + return ret; +} +#endif /* This structure can be automatically generated by the script with rom.mbedtls.ld. */ static const mbedtls_rom_eco4_funcs_t mbedtls_rom_eco4_funcs_table = { /* Fill the ROM functions into mbedtls rom function table. */ - /* aes module */ - ._rom_mbedtls_aes_init = mbedtls_aes_init, - ._rom_mbedtls_aes_free = mbedtls_aes_free, - ._rom_mbedtls_aes_setkey_enc = mbedtls_aes_setkey_enc, - ._rom_mbedtls_aes_setkey_dec = mbedtls_aes_setkey_dec, - ._rom_mbedtls_aes_crypt_ecb = mbedtls_aes_crypt_ecb, - ._rom_mbedtls_aes_crypt_cbc = mbedtls_aes_crypt_cbc, - ._rom_mbedtls_internal_aes_encrypt = mbedtls_internal_aes_encrypt, - ._rom_mbedtls_internal_aes_decrypt = mbedtls_internal_aes_decrypt, - /* asn1 module */ - ._rom_mbedtls_asn1_get_len = mbedtls_asn1_get_len, - ._rom_mbedtls_asn1_get_tag = mbedtls_asn1_get_tag, - ._rom_mbedtls_asn1_get_bool = mbedtls_asn1_get_bool, - ._rom_mbedtls_asn1_get_int = mbedtls_asn1_get_int, - ._rom_mbedtls_asn1_get_bitstring = mbedtls_asn1_get_bitstring, - ._rom_mbedtls_asn1_get_bitstring_null = mbedtls_asn1_get_bitstring_null, - ._rom_mbedtls_asn1_get_sequence_of = mbedtls_asn1_get_sequence_of, - ._rom_mbedtls_asn1_get_mpi = mbedtls_asn1_get_mpi, - ._rom_mbedtls_asn1_get_alg = mbedtls_asn1_get_alg, - ._rom_mbedtls_asn1_get_alg_null = mbedtls_asn1_get_alg_null, - ._rom_mbedtls_asn1_write_len = mbedtls_asn1_write_len, - ._rom_mbedtls_asn1_write_tag = mbedtls_asn1_write_tag, - ._rom_mbedtls_asn1_write_mpi = mbedtls_asn1_write_mpi, - /* base64 module */ - ._rom_mbedtls_base64_decode = mbedtls_base64_decode, - /* bignum module */ - ._rom_mbedtls_mpi_init = mbedtls_mpi_init, - ._rom_mbedtls_mpi_free = mbedtls_mpi_free, - ._rom_mbedtls_mpi_grow = mbedtls_mpi_grow, - ._rom_mbedtls_mpi_shrink = mbedtls_mpi_shrink, - ._rom_mbedtls_mpi_copy = mbedtls_mpi_copy, - ._rom_mbedtls_mpi_safe_cond_assign = mbedtls_mpi_safe_cond_assign, - ._rom_mbedtls_mpi_safe_cond_swap = mbedtls_mpi_safe_cond_swap, - ._rom_mbedtls_mpi_lset = mbedtls_mpi_lset, - ._rom_mbedtls_mpi_get_bit = mbedtls_mpi_get_bit, - ._rom_mbedtls_mpi_set_bit = mbedtls_mpi_set_bit, - ._rom_mbedtls_mpi_lsb = mbedtls_mpi_lsb, - ._rom_mbedtls_mpi_bitlen = mbedtls_mpi_bitlen, - ._rom_mbedtls_mpi_size = mbedtls_mpi_size, - ._rom_mbedtls_mpi_read_binary = mbedtls_mpi_read_binary, - ._rom_mbedtls_mpi_write_binary = mbedtls_mpi_write_binary, - ._rom_mbedtls_mpi_shift_l = mbedtls_mpi_shift_l, - ._rom_mbedtls_mpi_shift_r = mbedtls_mpi_shift_r, - ._rom_mbedtls_mpi_cmp_abs = mbedtls_mpi_cmp_abs, - ._rom_mbedtls_mpi_cmp_mpi = mbedtls_mpi_cmp_mpi, - ._rom_mbedtls_mpi_lt_mpi_ct = mbedtls_mpi_lt_mpi_ct, - ._rom_mbedtls_mpi_cmp_int = mbedtls_mpi_cmp_int, - ._rom_mbedtls_mpi_add_abs = mbedtls_mpi_add_abs, - ._rom_mbedtls_mpi_sub_abs = mbedtls_mpi_sub_abs, - ._rom_mbedtls_mpi_add_mpi = mbedtls_mpi_add_mpi, - ._rom_mbedtls_mpi_sub_mpi = mbedtls_mpi_sub_mpi, - ._rom_mbedtls_mpi_add_int = mbedtls_mpi_add_int, - ._rom_mbedtls_mpi_sub_int = mbedtls_mpi_sub_int, - ._rom_mbedtls_mpi_mul_mpi = mbedtls_mpi_mul_mpi, - ._rom_mbedtls_mpi_mul_int = mbedtls_mpi_mul_int, - ._rom_mbedtls_mpi_div_mpi = mbedtls_mpi_div_mpi, - ._rom_mbedtls_mpi_div_int = mbedtls_mpi_div_int, - ._rom_mbedtls_mpi_mod_mpi = mbedtls_mpi_mod_mpi, - ._rom_mbedtls_mpi_mod_int = mbedtls_mpi_mod_int, - ._rom_mbedtls_mpi_exp_mod = mbedtls_mpi_exp_mod, - ._rom_mbedtls_mpi_fill_random = mbedtls_mpi_fill_random, - ._rom_mbedtls_mpi_gcd = mbedtls_mpi_gcd, - ._rom_mbedtls_mpi_inv_mod = mbedtls_mpi_inv_mod, - ._rom_mbedtls_mpi_is_prime_ext = mbedtls_mpi_is_prime_ext, - /* ccm module */ - ._rom_mbedtls_ccm_star_encrypt_and_tag = mbedtls_ccm_star_encrypt_and_tag, - ._rom_mbedtls_ccm_star_auth_decrypt = mbedtls_ccm_star_auth_decrypt, - /* cipher module */ - ._rom_mbedtls_cipher_init = mbedtls_cipher_init, - ._rom_mbedtls_cipher_set_padding_mode = mbedtls_cipher_set_padding_mode, - ._rom_mbedtls_cipher_reset = mbedtls_cipher_reset, - ._rom_mbedtls_cipher_finish = mbedtls_cipher_finish, - ._rom_mbedtls_cipher_crypt = mbedtls_cipher_crypt, - ._rom_mbedtls_cipher_cmac_starts = mbedtls_cipher_cmac_starts, - ._rom_mbedtls_cipher_cmac_update = mbedtls_cipher_cmac_update, - ._rom_mbedtls_cipher_cmac_finish = mbedtls_cipher_cmac_finish, - /* ctr drbg module */ - ._rom_mbedtls_ctr_drbg_init = mbedtls_ctr_drbg_init, - ._rom_mbedtls_ctr_drbg_seed = mbedtls_ctr_drbg_seed, - ._rom_mbedtls_ctr_drbg_free = mbedtls_ctr_drbg_free, - ._rom_mbedtls_ctr_drbg_reseed = mbedtls_ctr_drbg_reseed, - ._rom_mbedtls_ctr_drbg_random_with_add = mbedtls_ctr_drbg_random_with_add, - ._rom_mbedtls_ctr_drbg_random = mbedtls_ctr_drbg_random, - /* sha1 module */ - ._rom_mbedtls_sha1_init = mbedtls_sha1_init, - ._rom_mbedtls_sha1_free = mbedtls_sha1_free, - ._rom_mbedtls_sha1_clone = mbedtls_sha1_clone, - ._rom_mbedtls_sha1_starts = mbedtls_sha1_starts, - ._rom_mbedtls_sha1_finish = mbedtls_sha1_finish, - /* sha256 module */ - ._rom_mbedtls_sha256_init = mbedtls_sha256_init, - ._rom_mbedtls_sha256_free = mbedtls_sha256_free, - ._rom_mbedtls_sha256_clone = mbedtls_sha256_clone, - ._rom_mbedtls_sha256_starts = mbedtls_sha256_starts, - ._rom_mbedtls_sha256_finish = mbedtls_sha256_finish, - ._rom_mbedtls_sha256 = mbedtls_sha256, - /* sha512 module */ - ._rom_mbedtls_sha512_init = mbedtls_sha512_init, - ._rom_mbedtls_sha512_free = mbedtls_sha512_free, - ._rom_mbedtls_sha512_clone = mbedtls_sha512_clone, - ._rom_mbedtls_sha512_starts = mbedtls_sha512_starts, - ._rom_mbedtls_sha512_update = mbedtls_sha512_update, - ._rom_mbedtls_sha512_finish = mbedtls_sha512_finish, - //._rom_mbedtls_internal_sha512_process = mbedtls_internal_sha512_process, - ._rom_mbedtls_sha512 = mbedtls_sha512, - - ._rom_mbedtls_aes_xts_init = mbedtls_aes_xts_init, - ._rom_mbedtls_aes_xts_free = mbedtls_aes_xts_free, - ._rom_mbedtls_aes_xts_setkey_enc = mbedtls_aes_xts_setkey_enc, - ._rom_mbedtls_aes_xts_setkey_dec = mbedtls_aes_xts_setkey_dec, - ._rom_mbedtls_aes_crypt_xts = mbedtls_aes_crypt_xts, - ._rom_mbedtls_aes_crypt_cfb128 = mbedtls_aes_crypt_cfb128, - ._rom_mbedtls_aes_crypt_ofb = mbedtls_aes_crypt_ofb, - ._rom_mbedtls_aes_crypt_ctr = mbedtls_aes_crypt_ctr, - ._rom_mbedtls_ccm_init = mbedtls_ccm_init, - ._rom_mbedtls_ccm_setkey = mbedtls_ccm_setkey, - ._rom_mbedtls_ccm_free = mbedtls_ccm_free, - ._rom_mbedtls_ccm_encrypt_and_tag = mbedtls_ccm_encrypt_and_tag, - ._rom_mbedtls_ccm_auth_decrypt = mbedtls_ccm_auth_decrypt, - ._rom_mbedtls_md5_init = mbedtls_md5_init, - ._rom_mbedtls_md5_free = mbedtls_md5_free, - ._rom_mbedtls_md5_clone = mbedtls_md5_clone, - ._rom_mbedtls_md5_starts = mbedtls_md5_starts, - ._rom_mbedtls_md5_update = mbedtls_md5_update, - ._rom_mbedtls_md5_finish = mbedtls_md5_finish, - ._rom_mbedtls_md5 = mbedtls_md5, - ._rom_mbedtls_sha1 = mbedtls_sha1, - - // eco4 rom mbedtls functions - ._rom_mbedtls_aes_crypt_cfb8 = mbedtls_aes_crypt_cfb8, - ._rom_mbedtls_mpi_swap = mbedtls_mpi_swap, - ._rom_mbedtls_mpi_read_string = mbedtls_mpi_read_string, - ._rom_mbedtls_mpi_write_string = mbedtls_mpi_write_string, - ._rom_mbedtls_mpi_read_binary_le = mbedtls_mpi_read_binary_le, - ._rom_mbedtls_mpi_write_binary_le = mbedtls_mpi_write_binary_le, - ._rom_mbedtls_mpi_random = mbedtls_mpi_random, - ._rom_mbedtls_mpi_gen_prime = mbedtls_mpi_gen_prime, - ._rom_mbedtls_ecp_check_budget = mbedtls_ecp_check_budget, - ._rom_mbedtls_ecp_set_max_ops = mbedtls_ecp_set_max_ops, - ._rom_mbedtls_ecp_restart_is_enabled = mbedtls_ecp_restart_is_enabled, - ._rom_mbedtls_ecp_get_type = mbedtls_ecp_get_type, - ._rom_mbedtls_ecp_curve_list = mbedtls_ecp_curve_list, - ._rom_mbedtls_ecp_grp_id_list = mbedtls_ecp_grp_id_list, - ._rom_mbedtls_ecp_curve_info_from_grp_id = mbedtls_ecp_curve_info_from_grp_id, - ._rom_mbedtls_ecp_curve_info_from_tls_id = mbedtls_ecp_curve_info_from_tls_id, - ._rom_mbedtls_ecp_curve_info_from_name = mbedtls_ecp_curve_info_from_name, - ._rom_mbedtls_ecp_point_init = mbedtls_ecp_point_init, - ._rom_mbedtls_ecp_group_init = mbedtls_ecp_group_init, - ._rom_mbedtls_ecp_keypair_init = mbedtls_ecp_keypair_init, - ._rom_mbedtls_ecp_point_free = mbedtls_ecp_point_free, - ._rom_mbedtls_ecp_group_free = mbedtls_ecp_group_free, - ._rom_mbedtls_ecp_keypair_free = mbedtls_ecp_keypair_free, - ._rom_mbedtls_ecp_restart_init = mbedtls_ecp_restart_init, - ._rom_mbedtls_ecp_restart_free = mbedtls_ecp_restart_free, - ._rom_mbedtls_ecp_copy = mbedtls_ecp_copy, - ._rom_mbedtls_ecp_group_copy = mbedtls_ecp_group_copy, - ._rom_mbedtls_ecp_set_zero = mbedtls_ecp_set_zero, - ._rom_mbedtls_ecp_is_zero = mbedtls_ecp_is_zero, - ._rom_mbedtls_ecp_point_cmp = mbedtls_ecp_point_cmp, - ._rom_mbedtls_ecp_point_read_string = mbedtls_ecp_point_read_string, - ._rom_mbedtls_ecp_point_write_binary = mbedtls_ecp_point_write_binary, - ._rom_mbedtls_ecp_point_read_binary = mbedtls_ecp_point_read_binary, - ._rom_mbedtls_ecp_tls_read_point = mbedtls_ecp_tls_read_point, - ._rom_mbedtls_ecp_tls_write_point = mbedtls_ecp_tls_write_point, - ._rom_mbedtls_ecp_group_load = mbedtls_ecp_group_load, - ._rom_mbedtls_ecp_tls_read_group = mbedtls_ecp_tls_read_group, - ._rom_mbedtls_ecp_tls_read_group_id = mbedtls_ecp_tls_read_group_id, - ._rom_mbedtls_ecp_tls_write_group = mbedtls_ecp_tls_write_group, - ._rom_mbedtls_ecp_mul = mbedtls_ecp_mul, - ._rom_mbedtls_ecp_mul_restartable = mbedtls_ecp_mul_restartable, - ._rom_mbedtls_ecp_muladd = mbedtls_ecp_muladd, - ._rom_mbedtls_ecp_muladd_restartable = mbedtls_ecp_muladd_restartable, - ._rom_mbedtls_ecp_check_pubkey = mbedtls_ecp_check_pubkey, - ._rom_mbedtls_ecp_check_privkey = mbedtls_ecp_check_privkey, - ._rom_mbedtls_ecp_gen_privkey = mbedtls_ecp_gen_privkey, - ._rom_mbedtls_ecp_gen_keypair_base = mbedtls_ecp_gen_keypair_base, - ._rom_mbedtls_ecp_gen_keypair = mbedtls_ecp_gen_keypair, - ._rom_mbedtls_ecp_gen_key = mbedtls_ecp_gen_key, - ._rom_mbedtls_ecp_read_key = mbedtls_ecp_read_key, - ._rom_mbedtls_ecp_write_key_ext = mbedtls_ecp_write_key_ext, - ._rom_mbedtls_ecp_check_pub_priv = mbedtls_ecp_check_pub_priv, - ._rom_mbedtls_ecp_export = mbedtls_ecp_export, - ._rom_mbedtls_asn1_get_enum = mbedtls_asn1_get_enum, - ._rom_mbedtls_asn1_sequence_free = mbedtls_asn1_sequence_free, - ._rom_mbedtls_asn1_traverse_sequence_of = mbedtls_asn1_traverse_sequence_of, - ._rom_mbedtls_asn1_find_named_data = mbedtls_asn1_find_named_data, - ._rom_mbedtls_asn1_free_named_data_list = mbedtls_asn1_free_named_data_list, - ._rom_mbedtls_asn1_free_named_data_list_shallow = mbedtls_asn1_free_named_data_list_shallow, - ._rom_mbedtls_asn1_write_raw_buffer = mbedtls_asn1_write_raw_buffer, - ._rom_mbedtls_asn1_write_null = mbedtls_asn1_write_null, - ._rom_mbedtls_asn1_write_oid = mbedtls_asn1_write_oid, - ._rom_mbedtls_asn1_write_algorithm_identifier = mbedtls_asn1_write_algorithm_identifier, - ._rom_mbedtls_asn1_write_bool = mbedtls_asn1_write_bool, - ._rom_mbedtls_asn1_write_int = mbedtls_asn1_write_int, - ._rom_mbedtls_asn1_write_enum = mbedtls_asn1_write_enum, - ._rom_mbedtls_asn1_write_tagged_string = mbedtls_asn1_write_tagged_string, - ._rom_mbedtls_asn1_write_printable_string = mbedtls_asn1_write_printable_string, - ._rom_mbedtls_asn1_write_utf8_string = mbedtls_asn1_write_utf8_string, - ._rom_mbedtls_asn1_write_ia5_string = mbedtls_asn1_write_ia5_string, - ._rom_mbedtls_asn1_write_bitstring = mbedtls_asn1_write_bitstring, - ._rom_mbedtls_asn1_write_named_bitstring = mbedtls_asn1_write_named_bitstring, - ._rom_mbedtls_asn1_write_octet_string = mbedtls_asn1_write_octet_string, - ._rom_mbedtls_asn1_store_named_data = mbedtls_asn1_store_named_data, - ._rom_mbedtls_ccm_starts = mbedtls_ccm_starts, - ._rom_mbedtls_ccm_set_lengths = mbedtls_ccm_set_lengths, - ._rom_mbedtls_ccm_update_ad = mbedtls_ccm_update_ad, - ._rom_mbedtls_ccm_update = mbedtls_ccm_update, - ._rom_mbedtls_ccm_finish = mbedtls_ccm_finish, - ._rom_mbedtls_cipher_list = mbedtls_cipher_list, - ._rom_mbedtls_cipher_info_from_string = mbedtls_cipher_info_from_string, - ._rom_mbedtls_cipher_info_from_type = mbedtls_cipher_info_from_type, - ._rom_mbedtls_cipher_info_from_values = mbedtls_cipher_info_from_values, - ._rom_mbedtls_cipher_free = mbedtls_cipher_free, - ._rom_mbedtls_cipher_setup = mbedtls_cipher_setup, - ._rom_mbedtls_cipher_setkey = mbedtls_cipher_setkey, - ._rom_mbedtls_cipher_set_iv = mbedtls_cipher_set_iv, - ._rom_mbedtls_cipher_update_ad = mbedtls_cipher_update_ad, - ._rom_mbedtls_cipher_update = mbedtls_cipher_update, - ._rom_mbedtls_cipher_write_tag = mbedtls_cipher_write_tag, - ._rom_mbedtls_cipher_check_tag = mbedtls_cipher_check_tag, - ._rom_mbedtls_cipher_auth_encrypt_ext = mbedtls_cipher_auth_encrypt_ext, - ._rom_mbedtls_cipher_auth_decrypt_ext = mbedtls_cipher_auth_decrypt_ext, - ._rom_mbedtls_cipher_cmac_reset = mbedtls_cipher_cmac_reset, - ._rom_mbedtls_cipher_cmac = mbedtls_cipher_cmac, - ._rom_mbedtls_aes_cmac_prf_128 = mbedtls_aes_cmac_prf_128, - ._rom_mbedtls_ctr_drbg_set_prediction_resistance = mbedtls_ctr_drbg_set_prediction_resistance, - ._rom_mbedtls_ctr_drbg_set_entropy_len = mbedtls_ctr_drbg_set_entropy_len, - ._rom_mbedtls_ctr_drbg_set_nonce_len = mbedtls_ctr_drbg_set_nonce_len, - ._rom_mbedtls_ctr_drbg_set_reseed_interval = mbedtls_ctr_drbg_set_reseed_interval, - ._rom_mbedtls_ctr_drbg_update = mbedtls_ctr_drbg_update, - ._rom_mbedtls_base64_encode = mbedtls_base64_encode, - - /* Fill the SHA hardware functions into mbedtls rom function table */ - ._rom_mbedtls_sha1_update = mbedtls_sha1_update, - ._rom_mbedtls_sha256_update = mbedtls_sha256_update, - - //memory calloc free + ._rom_mbedtls_aes_init = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_aes_init, mbedtls_aes_init), + ._rom_mbedtls_aes_free = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_aes_free, mbedtls_aes_free), + ._rom_mbedtls_aes_setkey_enc = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_aes_setkey_enc, mbedtls_aes_setkey_enc), + ._rom_mbedtls_aes_setkey_dec = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_aes_setkey_dec, mbedtls_aes_setkey_dec), + ._rom_mbedtls_aes_crypt_ecb = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_aes_crypt_ecb, mbedtls_aes_crypt_ecb), + ._rom_mbedtls_aes_crypt_cbc = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_aes_crypt_cbc, mbedtls_aes_crypt_cbc), + ._rom_mbedtls_internal_aes_encrypt = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_internal_aes_encrypt, mbedtls_internal_aes_encrypt), + ._rom_mbedtls_internal_aes_decrypt = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_internal_aes_decrypt, mbedtls_internal_aes_decrypt), + ._rom_mbedtls_asn1_get_len = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_asn1_get_len, mbedtls_asn1_get_len), + ._rom_mbedtls_asn1_get_tag = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_asn1_get_tag, mbedtls_asn1_get_tag), + ._rom_mbedtls_asn1_get_bool = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_asn1_get_bool, mbedtls_asn1_get_bool), + ._rom_mbedtls_asn1_get_int = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_asn1_get_int, mbedtls_asn1_get_int), + ._rom_mbedtls_asn1_get_bitstring = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_asn1_get_bitstring, mbedtls_asn1_get_bitstring), + ._rom_mbedtls_asn1_get_bitstring_null = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_asn1_get_bitstring_null, mbedtls_asn1_get_bitstring_null), + ._rom_mbedtls_asn1_get_sequence_of = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_asn1_get_sequence_of, mbedtls_asn1_get_sequence_of), + ._rom_mbedtls_asn1_get_mpi = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_asn1_get_mpi, mbedtls_asn1_get_mpi), + ._rom_mbedtls_asn1_get_alg = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_asn1_get_alg, mbedtls_asn1_get_alg), + ._rom_mbedtls_asn1_get_alg_null = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_asn1_get_alg_null, mbedtls_asn1_get_alg_null), + ._rom_mbedtls_asn1_write_len = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_asn1_write_len, mbedtls_asn1_write_len), + ._rom_mbedtls_asn1_write_tag = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_asn1_write_tag, mbedtls_asn1_write_tag), + ._rom_mbedtls_asn1_write_mpi = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_asn1_write_mpi, mbedtls_asn1_write_mpi), + ._rom_mbedtls_base64_decode = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_base64_decode, mbedtls_base64_decode), + ._rom_mbedtls_mpi_init = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_mpi_init, mbedtls_mpi_init), + ._rom_mbedtls_mpi_free = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_mpi_free, mbedtls_mpi_free), + ._rom_mbedtls_mpi_grow = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_mpi_grow, mbedtls_mpi_grow), + ._rom_mbedtls_mpi_shrink = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_mpi_shrink, mbedtls_mpi_shrink), + ._rom_mbedtls_mpi_copy = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_mpi_copy, mbedtls_mpi_copy), + ._rom_mbedtls_mpi_safe_cond_assign = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_mpi_safe_cond_assign, mbedtls_mpi_safe_cond_assign), + ._rom_mbedtls_mpi_safe_cond_swap = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_mpi_safe_cond_swap, mbedtls_mpi_safe_cond_swap), + ._rom_mbedtls_mpi_lset = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_mpi_lset, mbedtls_mpi_lset), + ._rom_mbedtls_mpi_get_bit = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_mpi_get_bit, mbedtls_mpi_get_bit), + ._rom_mbedtls_mpi_set_bit = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_mpi_set_bit, mbedtls_mpi_set_bit), + ._rom_mbedtls_mpi_lsb = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_mpi_lsb, mbedtls_mpi_lsb), + ._rom_mbedtls_mpi_bitlen = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_mpi_bitlen, mbedtls_mpi_bitlen), + ._rom_mbedtls_mpi_size = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_mpi_size, mbedtls_mpi_size), + ._rom_mbedtls_mpi_read_binary = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_mpi_read_binary, mbedtls_mpi_read_binary), + ._rom_mbedtls_mpi_write_binary = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_mpi_write_binary, mbedtls_mpi_write_binary), + ._rom_mbedtls_mpi_shift_l = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_mpi_shift_l, mbedtls_mpi_shift_l), + ._rom_mbedtls_mpi_shift_r = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_mpi_shift_r, mbedtls_mpi_shift_r), + ._rom_mbedtls_mpi_cmp_abs = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_mpi_cmp_abs, mbedtls_mpi_cmp_abs), + ._rom_mbedtls_mpi_cmp_mpi = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_mpi_cmp_mpi, mbedtls_mpi_cmp_mpi), + ._rom_mbedtls_mpi_lt_mpi_ct = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_mpi_lt_mpi_ct, mbedtls_mpi_lt_mpi_ct), + ._rom_mbedtls_mpi_cmp_int = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_mpi_cmp_int, mbedtls_mpi_cmp_int), + ._rom_mbedtls_mpi_add_abs = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_mpi_add_abs, mbedtls_mpi_add_abs), + ._rom_mbedtls_mpi_sub_abs = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_mpi_sub_abs, mbedtls_mpi_sub_abs), + ._rom_mbedtls_mpi_add_mpi = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_mpi_add_mpi, mbedtls_mpi_add_mpi), + ._rom_mbedtls_mpi_sub_mpi = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_mpi_sub_mpi, mbedtls_mpi_sub_mpi), + ._rom_mbedtls_mpi_add_int = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_mpi_add_int, mbedtls_mpi_add_int), + ._rom_mbedtls_mpi_sub_int = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_mpi_sub_int, mbedtls_mpi_sub_int), + ._rom_mbedtls_mpi_mul_mpi = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_mpi_mul_mpi, mbedtls_mpi_mul_mpi), + ._rom_mbedtls_mpi_mul_int = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_mpi_mul_int, mbedtls_mpi_mul_int), + ._rom_mbedtls_mpi_div_mpi = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_mpi_div_mpi, mbedtls_mpi_div_mpi), + ._rom_mbedtls_mpi_div_int = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_mpi_div_int, mbedtls_mpi_div_int), + ._rom_mbedtls_mpi_mod_mpi = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_mpi_mod_mpi, mbedtls_mpi_mod_mpi), + ._rom_mbedtls_mpi_mod_int = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_mpi_mod_int, mbedtls_mpi_mod_int), + ._rom_mbedtls_mpi_exp_mod = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_mpi_exp_mod, mbedtls_mpi_exp_mod), + ._rom_mbedtls_mpi_fill_random = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_mpi_fill_random, mbedtls_mpi_fill_random), + ._rom_mbedtls_mpi_gcd = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_mpi_gcd, mbedtls_mpi_gcd), + ._rom_mbedtls_mpi_inv_mod = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_mpi_inv_mod, mbedtls_mpi_inv_mod), + ._rom_mbedtls_mpi_is_prime_ext = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_mpi_is_prime_ext, mbedtls_mpi_is_prime_ext), + ._rom_mbedtls_ccm_star_encrypt_and_tag = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_ccm_star_encrypt_and_tag, mbedtls_ccm_star_encrypt_and_tag), + ._rom_mbedtls_ccm_star_auth_decrypt = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_ccm_star_auth_decrypt, mbedtls_ccm_star_auth_decrypt), + ._rom_mbedtls_cipher_init = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_cipher_init, mbedtls_cipher_init), + ._rom_mbedtls_cipher_set_padding_mode = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_cipher_set_padding_mode, mbedtls_cipher_set_padding_mode), + ._rom_mbedtls_cipher_reset = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_cipher_reset, mbedtls_cipher_reset), + ._rom_mbedtls_cipher_finish = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_cipher_finish, mbedtls_cipher_finish), + ._rom_mbedtls_cipher_crypt = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_cipher_crypt, mbedtls_cipher_crypt), + ._rom_mbedtls_cipher_cmac_starts = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_cipher_cmac_starts, mbedtls_cipher_cmac_starts), + ._rom_mbedtls_cipher_cmac_update = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_cipher_cmac_update, mbedtls_cipher_cmac_update), + ._rom_mbedtls_cipher_cmac_finish = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_cipher_cmac_finish, mbedtls_cipher_cmac_finish), + ._rom_mbedtls_ctr_drbg_init = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_ctr_drbg_init, mbedtls_ctr_drbg_init), + ._rom_mbedtls_ctr_drbg_seed = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_ctr_drbg_seed, mbedtls_ctr_drbg_seed), + ._rom_mbedtls_ctr_drbg_free = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_ctr_drbg_free, mbedtls_ctr_drbg_free), + ._rom_mbedtls_ctr_drbg_reseed = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_ctr_drbg_reseed, mbedtls_ctr_drbg_reseed), + ._rom_mbedtls_ctr_drbg_random_with_add = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_ctr_drbg_random_with_add, mbedtls_ctr_drbg_random_with_add), + ._rom_mbedtls_ctr_drbg_random = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_ctr_drbg_random, mbedtls_ctr_drbg_random), + ._rom_mbedtls_sha1_init = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_sha1_init, mbedtls_sha1_init), + ._rom_mbedtls_sha1_free = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_sha1_free, mbedtls_sha1_free), + ._rom_mbedtls_sha1_clone = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_sha1_clone, mbedtls_sha1_clone), + ._rom_mbedtls_sha1_starts = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_sha1_starts, mbedtls_sha1_starts), + ._rom_mbedtls_sha1_finish = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_sha1_finish, mbedtls_sha1_finish), + ._rom_mbedtls_sha256_init = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_sha256_init, mbedtls_sha256_init), + ._rom_mbedtls_sha256_free = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_sha256_free, mbedtls_sha256_free), + ._rom_mbedtls_sha256_clone = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_sha256_clone, mbedtls_sha256_clone), + ._rom_mbedtls_sha256_starts = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_sha256_starts, mbedtls_sha256_starts), + ._rom_mbedtls_sha256_finish = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_sha256_finish, mbedtls_sha256_finish), + ._rom_mbedtls_sha256 = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_sha256, mbedtls_sha256), + ._rom_mbedtls_sha512_init = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_sha512_init, mbedtls_sha512_init), + ._rom_mbedtls_sha512_free = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_sha512_free, mbedtls_sha512_free), + ._rom_mbedtls_sha512_clone = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_sha512_clone, mbedtls_sha512_clone), + ._rom_mbedtls_sha512_starts = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_sha512_starts, mbedtls_sha512_starts), + ._rom_mbedtls_sha512_update = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_sha512_update, mbedtls_sha512_update), + ._rom_mbedtls_sha512_finish = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_sha512_finish, mbedtls_sha512_finish), + ._rom_mbedtls_sha512 = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_sha512, mbedtls_sha512), + ._rom_mbedtls_aes_xts_init = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_aes_xts_init, mbedtls_aes_xts_init), + ._rom_mbedtls_aes_xts_free = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_aes_xts_free, mbedtls_aes_xts_free), + ._rom_mbedtls_aes_xts_setkey_enc = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_aes_xts_setkey_enc, mbedtls_aes_xts_setkey_enc), + ._rom_mbedtls_aes_xts_setkey_dec = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_aes_xts_setkey_dec, mbedtls_aes_xts_setkey_dec), + ._rom_mbedtls_aes_crypt_xts = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_aes_crypt_xts, mbedtls_aes_crypt_xts), + ._rom_mbedtls_aes_crypt_cfb128 = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_aes_crypt_cfb128, mbedtls_aes_crypt_cfb128), + ._rom_mbedtls_aes_crypt_ofb = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_aes_crypt_ofb, mbedtls_aes_crypt_ofb), + ._rom_mbedtls_aes_crypt_ctr = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_aes_crypt_ctr, mbedtls_aes_crypt_ctr), + ._rom_mbedtls_ccm_init = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_ccm_init, mbedtls_ccm_init), + ._rom_mbedtls_ccm_setkey = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_ccm_setkey, mbedtls_ccm_setkey), + ._rom_mbedtls_ccm_free = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_ccm_free, mbedtls_ccm_free), + ._rom_mbedtls_ccm_encrypt_and_tag = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_ccm_encrypt_and_tag, mbedtls_ccm_encrypt_and_tag), + ._rom_mbedtls_ccm_auth_decrypt = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_ccm_auth_decrypt, mbedtls_ccm_auth_decrypt), + ._rom_mbedtls_md5_init = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_md5_init, mbedtls_md5_init), + ._rom_mbedtls_md5_free = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_md5_free, mbedtls_md5_free), + ._rom_mbedtls_md5_clone = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_md5_clone, mbedtls_md5_clone), + ._rom_mbedtls_md5_starts = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_md5_starts, mbedtls_md5_starts), + ._rom_mbedtls_md5_update = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_md5_update, mbedtls_md5_update), + ._rom_mbedtls_md5_finish = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_md5_finish, mbedtls_md5_finish), + ._rom_mbedtls_md5 = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_md5, mbedtls_md5), + ._rom_mbedtls_sha1 = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_sha1, mbedtls_sha1), + ._rom_mbedtls_aes_crypt_cfb8 = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_aes_crypt_cfb8, mbedtls_aes_crypt_cfb8), + ._rom_mbedtls_mpi_swap = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_mpi_swap, mbedtls_mpi_swap), + ._rom_mbedtls_mpi_read_string = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_mpi_read_string, mbedtls_mpi_read_string), + ._rom_mbedtls_mpi_write_string = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_mpi_write_string, mbedtls_mpi_write_string), + ._rom_mbedtls_mpi_read_binary_le = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_mpi_read_binary_le, mbedtls_mpi_read_binary_le), + ._rom_mbedtls_mpi_write_binary_le = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_mpi_write_binary_le, mbedtls_mpi_write_binary_le), + ._rom_mbedtls_mpi_random = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_mpi_random, mbedtls_mpi_random), + ._rom_mbedtls_mpi_gen_prime = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_mpi_gen_prime, mbedtls_mpi_gen_prime), + ._rom_mbedtls_ecp_check_budget = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_ecp_check_budget, mbedtls_ecp_check_budget), + ._rom_mbedtls_ecp_set_max_ops = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_ecp_set_max_ops, mbedtls_ecp_set_max_ops), + ._rom_mbedtls_ecp_restart_is_enabled = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_ecp_restart_is_enabled, mbedtls_ecp_restart_is_enabled), + ._rom_mbedtls_ecp_get_type = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_ecp_get_type, mbedtls_ecp_get_type), + ._rom_mbedtls_ecp_curve_list = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_ecp_curve_list, mbedtls_ecp_curve_list), + ._rom_mbedtls_ecp_grp_id_list = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_ecp_grp_id_list, mbedtls_ecp_grp_id_list), + ._rom_mbedtls_ecp_curve_info_from_grp_id = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_ecp_curve_info_from_grp_id, mbedtls_ecp_curve_info_from_grp_id), + ._rom_mbedtls_ecp_curve_info_from_tls_id = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_ecp_curve_info_from_tls_id, mbedtls_ecp_curve_info_from_tls_id), + ._rom_mbedtls_ecp_curve_info_from_name = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_ecp_curve_info_from_name, mbedtls_ecp_curve_info_from_name), + ._rom_mbedtls_ecp_point_init = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_ecp_point_init, mbedtls_ecp_point_init), + ._rom_mbedtls_ecp_group_init = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_ecp_group_init, mbedtls_ecp_group_init), + ._rom_mbedtls_ecp_keypair_init = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_ecp_keypair_init, mbedtls_ecp_keypair_init), + ._rom_mbedtls_ecp_point_free = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_ecp_point_free, mbedtls_ecp_point_free), + ._rom_mbedtls_ecp_group_free = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_ecp_group_free, mbedtls_ecp_group_free), + ._rom_mbedtls_ecp_keypair_free = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_ecp_keypair_free, mbedtls_ecp_keypair_free), + ._rom_mbedtls_ecp_restart_init = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_ecp_restart_init, mbedtls_ecp_restart_init), + ._rom_mbedtls_ecp_restart_free = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_ecp_restart_free, mbedtls_ecp_restart_free), + ._rom_mbedtls_ecp_copy = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_ecp_copy, mbedtls_ecp_copy), + ._rom_mbedtls_ecp_group_copy = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_ecp_group_copy, mbedtls_ecp_group_copy), + ._rom_mbedtls_ecp_set_zero = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_ecp_set_zero, mbedtls_ecp_set_zero), + ._rom_mbedtls_ecp_is_zero = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_ecp_is_zero, mbedtls_ecp_is_zero), + ._rom_mbedtls_ecp_point_cmp = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_ecp_point_cmp, mbedtls_ecp_point_cmp), + ._rom_mbedtls_ecp_point_read_string = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_ecp_point_read_string, mbedtls_ecp_point_read_string), + ._rom_mbedtls_ecp_point_write_binary = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_ecp_point_write_binary, mbedtls_ecp_point_write_binary), + ._rom_mbedtls_ecp_point_read_binary = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_ecp_point_read_binary, mbedtls_ecp_point_read_binary), + ._rom_mbedtls_ecp_tls_read_point = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_ecp_tls_read_point, mbedtls_ecp_tls_read_point), + ._rom_mbedtls_ecp_tls_write_point = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_ecp_tls_write_point, mbedtls_ecp_tls_write_point), + ._rom_mbedtls_ecp_group_load = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_ecp_group_load, mbedtls_ecp_group_load), + ._rom_mbedtls_ecp_tls_read_group = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_ecp_tls_read_group, mbedtls_ecp_tls_read_group), + ._rom_mbedtls_ecp_tls_read_group_id = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_ecp_tls_read_group_id, mbedtls_ecp_tls_read_group_id), + ._rom_mbedtls_ecp_tls_write_group = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_ecp_tls_write_group, mbedtls_ecp_tls_write_group), + ._rom_mbedtls_ecp_mul = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_ecp_mul, mbedtls_ecp_mul), + ._rom_mbedtls_ecp_mul_restartable = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_ecp_mul_restartable, mbedtls_ecp_mul_restartable), + ._rom_mbedtls_ecp_muladd = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_ecp_muladd, mbedtls_ecp_muladd), + ._rom_mbedtls_ecp_muladd_restartable = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_ecp_muladd_restartable, mbedtls_ecp_muladd_restartable), + ._rom_mbedtls_ecp_check_pubkey = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_ecp_check_pubkey, mbedtls_ecp_check_pubkey), + ._rom_mbedtls_ecp_check_privkey = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_ecp_check_privkey, mbedtls_ecp_check_privkey), + ._rom_mbedtls_ecp_gen_privkey = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_ecp_gen_privkey, mbedtls_ecp_gen_privkey), + ._rom_mbedtls_ecp_gen_keypair_base = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_ecp_gen_keypair_base, mbedtls_ecp_gen_keypair_base), + ._rom_mbedtls_ecp_gen_keypair = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_ecp_gen_keypair, mbedtls_ecp_gen_keypair), + ._rom_mbedtls_ecp_gen_key = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_ecp_gen_key, mbedtls_ecp_gen_key), + ._rom_mbedtls_ecp_read_key = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_ecp_read_key, mbedtls_ecp_read_key), + ._rom_mbedtls_ecp_write_key_ext = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_ecp_write_key_ext, mbedtls_ecp_write_key_ext), + ._rom_mbedtls_ecp_check_pub_priv = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_ecp_check_pub_priv, mbedtls_ecp_check_pub_priv), + ._rom_mbedtls_ecp_export = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_ecp_export, mbedtls_ecp_export), + ._rom_mbedtls_asn1_get_enum = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_asn1_get_enum, mbedtls_asn1_get_enum), + ._rom_mbedtls_asn1_sequence_free = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_asn1_sequence_free, mbedtls_asn1_sequence_free), + ._rom_mbedtls_asn1_traverse_sequence_of = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_asn1_traverse_sequence_of, mbedtls_asn1_traverse_sequence_of), + ._rom_mbedtls_asn1_find_named_data = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_asn1_find_named_data, mbedtls_asn1_find_named_data), + ._rom_mbedtls_asn1_free_named_data_list = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_asn1_free_named_data_list, mbedtls_asn1_free_named_data_list), + ._rom_mbedtls_asn1_free_named_data_list_shallow = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_asn1_free_named_data_list_shallow, mbedtls_asn1_free_named_data_list_shallow), + ._rom_mbedtls_asn1_write_raw_buffer = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_asn1_write_raw_buffer, mbedtls_asn1_write_raw_buffer), + ._rom_mbedtls_asn1_write_null = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_asn1_write_null, mbedtls_asn1_write_null), + ._rom_mbedtls_asn1_write_oid = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_asn1_write_oid, mbedtls_asn1_write_oid), + ._rom_mbedtls_asn1_write_algorithm_identifier = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_asn1_write_algorithm_identifier, mbedtls_asn1_write_algorithm_identifier), + ._rom_mbedtls_asn1_write_bool = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_asn1_write_bool, mbedtls_asn1_write_bool), + ._rom_mbedtls_asn1_write_int = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_asn1_write_int, mbedtls_asn1_write_int), + ._rom_mbedtls_asn1_write_enum = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_asn1_write_enum, mbedtls_asn1_write_enum), + ._rom_mbedtls_asn1_write_tagged_string = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_asn1_write_tagged_string, mbedtls_asn1_write_tagged_string), + ._rom_mbedtls_asn1_write_printable_string = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_asn1_write_printable_string, mbedtls_asn1_write_printable_string), + ._rom_mbedtls_asn1_write_utf8_string = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_asn1_write_utf8_string, mbedtls_asn1_write_utf8_string), + ._rom_mbedtls_asn1_write_ia5_string = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_asn1_write_ia5_string, mbedtls_asn1_write_ia5_string), + ._rom_mbedtls_asn1_write_bitstring = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_asn1_write_bitstring, mbedtls_asn1_write_bitstring), + ._rom_mbedtls_asn1_write_named_bitstring = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_asn1_write_named_bitstring, mbedtls_asn1_write_named_bitstring), + ._rom_mbedtls_asn1_write_octet_string = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_asn1_write_octet_string, mbedtls_asn1_write_octet_string), + ._rom_mbedtls_asn1_store_named_data = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_asn1_store_named_data, mbedtls_asn1_store_named_data), + ._rom_mbedtls_ccm_starts = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_ccm_starts, mbedtls_ccm_starts), + ._rom_mbedtls_ccm_set_lengths = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_ccm_set_lengths, mbedtls_ccm_set_lengths), + ._rom_mbedtls_ccm_update_ad = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_ccm_update_ad, mbedtls_ccm_update_ad), + ._rom_mbedtls_ccm_update = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_ccm_update, mbedtls_ccm_update), + ._rom_mbedtls_ccm_finish = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_ccm_finish, mbedtls_ccm_finish), + ._rom_mbedtls_cipher_list = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_cipher_list, mbedtls_cipher_list), + ._rom_mbedtls_cipher_info_from_string = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_cipher_info_from_string, mbedtls_cipher_info_from_string), + ._rom_mbedtls_cipher_info_from_type = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_cipher_info_from_type, mbedtls_cipher_info_from_type), + ._rom_mbedtls_cipher_info_from_values = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_cipher_info_from_values, mbedtls_cipher_info_from_values), + ._rom_mbedtls_cipher_free = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_cipher_free, mbedtls_cipher_free), + ._rom_mbedtls_cipher_setup = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_cipher_setup, mbedtls_cipher_setup), + ._rom_mbedtls_cipher_setkey = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_cipher_setkey, mbedtls_cipher_setkey), + ._rom_mbedtls_cipher_set_iv = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_cipher_set_iv, mbedtls_cipher_set_iv), + ._rom_mbedtls_cipher_update_ad = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_cipher_update_ad, mbedtls_cipher_update_ad), + ._rom_mbedtls_cipher_update = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_cipher_update, mbedtls_cipher_update), + ._rom_mbedtls_cipher_write_tag = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_cipher_write_tag, mbedtls_cipher_write_tag), + ._rom_mbedtls_cipher_check_tag = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_cipher_check_tag, mbedtls_cipher_check_tag), + ._rom_mbedtls_cipher_auth_encrypt_ext = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_cipher_auth_encrypt_ext, mbedtls_cipher_auth_encrypt_ext), + ._rom_mbedtls_cipher_auth_decrypt_ext = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_cipher_auth_decrypt_ext, mbedtls_cipher_auth_decrypt_ext), + ._rom_mbedtls_cipher_cmac_reset = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_cipher_cmac_reset, mbedtls_cipher_cmac_reset), + ._rom_mbedtls_cipher_cmac = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_cipher_cmac, mbedtls_cipher_cmac), + ._rom_mbedtls_aes_cmac_prf_128 = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_aes_cmac_prf_128, mbedtls_aes_cmac_prf_128), + ._rom_mbedtls_ctr_drbg_set_prediction_resistance = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_ctr_drbg_set_prediction_resistance, mbedtls_ctr_drbg_set_prediction_resistance), + ._rom_mbedtls_ctr_drbg_set_entropy_len = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_ctr_drbg_set_entropy_len, mbedtls_ctr_drbg_set_entropy_len), + ._rom_mbedtls_ctr_drbg_set_nonce_len = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_ctr_drbg_set_nonce_len, mbedtls_ctr_drbg_set_nonce_len), + ._rom_mbedtls_ctr_drbg_set_reseed_interval = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_ctr_drbg_set_reseed_interval, mbedtls_ctr_drbg_set_reseed_interval), + ._rom_mbedtls_ctr_drbg_update = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_ctr_drbg_update, mbedtls_ctr_drbg_update), + ._rom_mbedtls_base64_encode = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_base64_encode, mbedtls_base64_encode), + ._rom_mbedtls_sha1_update = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_sha1_update, mbedtls_sha1_update), + ._rom_mbedtls_sha256_update = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_sha256_update, mbedtls_sha256_update), ._rom_mbedtls_mem_calloc = MBEDTLS_PLATFORM_STD_CALLOC, ._rom_mbedtls_mem_free = MBEDTLS_PLATFORM_STD_FREE, }; @@ -450,19 +540,20 @@ __attribute__((constructor)) void mbedtls_rom_osi_functions_init(void) extern void *mbedtls_rom_osi_funcs_ptr; #if defined(MBEDTLS_THREADING_ALT) - mbedtls_threading_set_alt(mbedtls_rom_mutex_init, mbedtls_rom_mutex_free, mbedtls_rom_mutex_lock, mbedtls_rom_mutex_unlock); + mbedtls_threading_set_alt(mbedtls_rom_platform_mutex_init, + mbedtls_rom_platform_mutex_free, + mbedtls_rom_platform_mutex_lock, + mbedtls_rom_platform_mutex_unlock, + mbedtls_rom_cond_init, + mbedtls_rom_cond_free, + mbedtls_rom_cond_signal, + mbedtls_rom_cond_broadcast, + mbedtls_rom_cond_wait); #endif - unsigned chip_version = efuse_hal_chip_revision(); - if ( ESP_CHIP_REV_ABOVE(chip_version, 200) ) { - /* Initialize the rom function mbedtls_threading_set_alt on chip rev2.0 with rom eco4 */ - _rom_mbedtls_threading_set_alt_t rom_mbedtls_threading_set_alt = (_rom_mbedtls_threading_set_alt_t)0x40002c0c; - rom_mbedtls_threading_set_alt(mbedtls_rom_mutex_init, mbedtls_rom_mutex_free, mbedtls_rom_mutex_lock, mbedtls_rom_mutex_unlock); + /* Initialize the rom function mbedtls_threading_set_alt on chip rev2.0 with rom eco4 */ + rom_mbedtls_threading_set_alt(mbedtls_rom_mutex_init, mbedtls_rom_mutex_free, mbedtls_rom_mutex_lock, mbedtls_rom_mutex_unlock); - /* Initialize the pointer of rom eco4 mbedtls functions table. */ - mbedtls_rom_osi_funcs_ptr = (mbedtls_rom_eco4_funcs_t *)&mbedtls_rom_eco4_funcs_table; - } else { - /* Initialize the pointer of rom mbedtls functions table. */ - mbedtls_rom_osi_funcs_ptr = (mbedtls_rom_funcs_t *)&mbedtls_rom_funcs_table; - } + /* Initialize the pointer of rom eco4 mbedtls functions table. */ + mbedtls_rom_osi_funcs_ptr = (mbedtls_rom_eco4_funcs_t *)&mbedtls_rom_eco4_funcs_table; } diff --git a/components/mbedtls/port/mbedtls_rom/mbedtls_rom_osi.h b/components/mbedtls/port/mbedtls_rom/mbedtls_rom_osi.h index 6408e1c5437..9d53887cecb 100644 --- a/components/mbedtls/port/mbedtls_rom/mbedtls_rom_osi.h +++ b/components/mbedtls/port/mbedtls_rom/mbedtls_rom_osi.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2023-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2023-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -7,45 +7,30 @@ #pragma once #include -#include "mbedtls/aes.h" +#include +#include "mbedtls/private/aes.h" #include "mbedtls/asn1.h" #include "mbedtls/asn1write.h" #include "mbedtls/base64.h" #include "mbedtls/bignum.h" -#include "mbedtls/ccm.h" -#include "mbedtls/cipher.h" -#include "mbedtls/cmac.h" -#include "mbedtls/ctr_drbg.h" -#include "mbedtls/dhm.h" -#include "mbedtls/ecdh.h" -#include "mbedtls/ecdsa.h" -#include "mbedtls/ecjpake.h" -#include "mbedtls/ecp.h" -#include "mbedtls/entropy.h" -#include "mbedtls/hmac_drbg.h" +#include "mbedtls/private/ccm.h" +#include "mbedtls/private/cipher.h" +#include "mbedtls/private/cmac.h" +#include "mbedtls/private/ctr_drbg.h" +#include "mbedtls/private/ecp.h" #include "mbedtls/md.h" -#include "mbedtls/md5.h" -#include "mbedtls/oid.h" -#include "mbedtls/pem.h" -#include "mbedtls/pkcs12.h" -#include "mbedtls/pkcs5.h" -#include "mbedtls/pk.h" +#include "mbedtls/private/md5.h" #include "mbedtls/platform.h" -#include "mbedtls/rsa.h" -#include "mbedtls/sha1.h" -#include "mbedtls/sha256.h" -#include "mbedtls/sha512.h" -#include "mbedtls/ssl_ciphersuites.h" -#include "mbedtls/ssl.h" -#include "mbedtls/x509_crt.h" -#include "mbedtls/x509.h" +#include "mbedtls/private/sha1.h" +#include "mbedtls/private/sha256.h" +#include "mbedtls/private/sha512.h" +#include "mbedtls/threading.h" #include "soc/soc_caps.h" #ifdef __cplusplus extern "C" { #endif -#ifndef BOOTLOADER_BUILD #if (!defined(CONFIG_MBEDTLS_THREADING_C)) #error CONFIG_MBEDTLS_THREADING_C #endif @@ -54,365 +39,6 @@ typedef void (*_rom_mbedtls_threading_set_alt_t)(void (*mutex_init)(mbedtls_thre void (*mutex_free)(mbedtls_threading_mutex_t *), int (*mutex_lock)(mbedtls_threading_mutex_t *), int (*mutex_unlock)(mbedtls_threading_mutex_t *)); -#else /* BOOTLOADER_BUILD */ -typedef void mbedtls_threading_mutex_t; -#endif /* BOOTLOADER_BUILD */ - -typedef struct mbedtls_rom_funcs { - void (*_rom_mbedtls_aes_init)( mbedtls_aes_context *ctx ); - int (*_rom_ssl_write_client_hello)( mbedtls_ssl_context *ssl ); - int (*_rom_ssl_parse_server_hello)( mbedtls_ssl_context *ssl ); - int (*_rom_ssl_parse_server_key_exchange)( mbedtls_ssl_context *ssl ); - int (*_rom_ssl_parse_certificate_request)( mbedtls_ssl_context *ssl ); - int (*_rom_ssl_parse_server_hello_done)( mbedtls_ssl_context *ssl ); - int (*_rom_ssl_write_client_key_exchange)( mbedtls_ssl_context *ssl ); - int (*_rom_ssl_write_certificate_verify)( mbedtls_ssl_context *ssl ); - int (*_rom_ssl_parse_new_session_ticket)( mbedtls_ssl_context *ssl ); - void (*_rom_mbedtls_aes_free)( mbedtls_aes_context *ctx ); - int (*_rom_mbedtls_aes_setkey_enc)( mbedtls_aes_context *ctx, const unsigned char *key, unsigned int keybits ); - int (*_rom_mbedtls_aes_setkey_dec)( mbedtls_aes_context *ctx, const unsigned char *key, unsigned int keybits ); - int (*_rom_mbedtls_aes_crypt_ecb)( mbedtls_aes_context *ctx, int mode, const unsigned char input[16], unsigned char output[16] ); - int (*_rom_mbedtls_aes_crypt_cbc)( mbedtls_aes_context *ctx, int mode, size_t length, unsigned char iv[16], const unsigned char *input, unsigned char *output ); - int (*_rom_mbedtls_internal_aes_encrypt)( mbedtls_aes_context *ctx, const unsigned char input[16], unsigned char output[16] ); - int (*_rom_mbedtls_internal_aes_decrypt)( mbedtls_aes_context *ctx, const unsigned char input[16], unsigned char output[16] ); - int (*_rom_mbedtls_asn1_get_len)( unsigned char **p, const unsigned char *end, size_t *len ); - int (*_rom_mbedtls_asn1_get_tag)( unsigned char **p, const unsigned char *end, size_t *len, int tag ); - int (*_rom_mbedtls_asn1_get_bool)( unsigned char **p, const unsigned char *end, int *val ); - int (*_rom_mbedtls_asn1_get_int)( unsigned char **p, const unsigned char *end, int *val ); - int (*_rom_mbedtls_asn1_get_bitstring)( unsigned char **p, const unsigned char *end, mbedtls_asn1_bitstring *bs); - int (*_rom_mbedtls_asn1_get_bitstring_null)( unsigned char **p, const unsigned char *end, size_t *len ); - int (*_rom_mbedtls_asn1_get_sequence_of)( unsigned char **p, const unsigned char *end, mbedtls_asn1_sequence *cur, int tag); - int (*_rom_mbedtls_asn1_get_mpi)( unsigned char **p, const unsigned char *end, mbedtls_mpi *X ); - int (*_rom_mbedtls_asn1_get_alg)( unsigned char **p, const unsigned char *end, mbedtls_asn1_buf *alg, mbedtls_asn1_buf *params ); - int (*_rom_mbedtls_asn1_get_alg_null)( unsigned char **p, const unsigned char *end, mbedtls_asn1_buf *alg ); - int (*_rom_mbedtls_asn1_write_len)( unsigned char **p, const unsigned char *start, size_t len ); - int (*_rom_mbedtls_asn1_write_tag)( unsigned char **p, const unsigned char *start, unsigned char tag ); - int (*_rom_mbedtls_asn1_write_mpi)( unsigned char **p, const unsigned char *start, const mbedtls_mpi *X ); - int (*_rom_mbedtls_base64_decode)( unsigned char *dst, size_t dlen, size_t *olen, const unsigned char *src, size_t slen ); - void (*_rom_mbedtls_mpi_init)( mbedtls_mpi *X ); - void (*_rom_mbedtls_mpi_free)( mbedtls_mpi *X ); - int (*_rom_mbedtls_mpi_grow)( mbedtls_mpi *X, size_t nblimbs ); - int (*_rom_mbedtls_mpi_shrink)( mbedtls_mpi *X, size_t nblimbs ); - int (*_rom_mbedtls_mpi_copy)( mbedtls_mpi *X, const mbedtls_mpi *Y ); - int (*_rom_mbedtls_mpi_safe_cond_assign)( mbedtls_mpi *X, const mbedtls_mpi *Y, unsigned char assign ); - int (*_rom_mbedtls_mpi_safe_cond_swap)( mbedtls_mpi *X, mbedtls_mpi *Y, unsigned char assign ); - int (*_rom_mbedtls_mpi_lset)( mbedtls_mpi *X, mbedtls_mpi_sint z ); - int (*_rom_mbedtls_mpi_get_bit)( const mbedtls_mpi *X, size_t pos ); - int (*_rom_mbedtls_mpi_set_bit)( mbedtls_mpi *X, size_t pos, unsigned char val ); - size_t (*_rom_mbedtls_mpi_lsb)( const mbedtls_mpi *X ); - size_t (*_rom_mbedtls_mpi_bitlen)( const mbedtls_mpi *X ); - size_t (*_rom_mbedtls_mpi_size)( const mbedtls_mpi *X ); - int (*_rom_mbedtls_mpi_read_binary)( mbedtls_mpi *X, const unsigned char *buf, size_t buflen ); - int (*_rom_mbedtls_mpi_write_binary)( const mbedtls_mpi *X, unsigned char *buf, size_t buflen ); - int (*_rom_mbedtls_mpi_shift_l)( mbedtls_mpi *X, size_t count ); - int (*_rom_mbedtls_mpi_shift_r)( mbedtls_mpi *X, size_t count ); - int (*_rom_mbedtls_mpi_cmp_abs)( const mbedtls_mpi *X, const mbedtls_mpi *Y ); - int (*_rom_mbedtls_mpi_cmp_mpi)( const mbedtls_mpi *X, const mbedtls_mpi *Y ); - int (*_rom_mbedtls_mpi_lt_mpi_ct)( const mbedtls_mpi *X, const mbedtls_mpi *Y, unsigned *ret ); - int (*_rom_mbedtls_mpi_cmp_int)( const mbedtls_mpi *X, mbedtls_mpi_sint z ); - int (*_rom_mbedtls_mpi_add_abs)( mbedtls_mpi *X, const mbedtls_mpi *A, const mbedtls_mpi *B ); - int (*_rom_mbedtls_mpi_sub_abs)( mbedtls_mpi *X, const mbedtls_mpi *A, const mbedtls_mpi *B ); - int (*_rom_mbedtls_mpi_add_mpi)( mbedtls_mpi *X, const mbedtls_mpi *A, const mbedtls_mpi *B ); - int (*_rom_mbedtls_mpi_sub_mpi)( mbedtls_mpi *X, const mbedtls_mpi *A, const mbedtls_mpi *B ); - int (*_rom_mbedtls_mpi_add_int)( mbedtls_mpi *X, const mbedtls_mpi *A, mbedtls_mpi_sint b ); - int (*_rom_mbedtls_mpi_sub_int)( mbedtls_mpi *X, const mbedtls_mpi *A, mbedtls_mpi_sint b ); - int (*_rom_mbedtls_mpi_mul_mpi)( mbedtls_mpi *X, const mbedtls_mpi *A, const mbedtls_mpi *B ); - int (*_rom_mbedtls_mpi_mul_int)( mbedtls_mpi *X, const mbedtls_mpi *A, mbedtls_mpi_uint b ); - int (*_rom_mbedtls_mpi_div_mpi)( mbedtls_mpi *Q, mbedtls_mpi *R, const mbedtls_mpi *A, const mbedtls_mpi *B ); - int (*_rom_mbedtls_mpi_div_int)( mbedtls_mpi *Q, mbedtls_mpi *R, const mbedtls_mpi *A, mbedtls_mpi_sint b ); - int (*_rom_mbedtls_mpi_mod_mpi)( mbedtls_mpi *R, const mbedtls_mpi *A, const mbedtls_mpi *B ); - int (*_rom_mbedtls_mpi_mod_int)( mbedtls_mpi_uint *r, const mbedtls_mpi *A, mbedtls_mpi_sint b ); - int (*_rom_mbedtls_mpi_exp_mod)( mbedtls_mpi *X, const mbedtls_mpi *A, const mbedtls_mpi *E, const mbedtls_mpi *N, mbedtls_mpi *_RR ); - int (*_rom_mbedtls_mpi_fill_random)( mbedtls_mpi *X, size_t size, int (*f_rng)(void *, unsigned char *, size_t), void *p_rng ); - int (*_rom_mbedtls_mpi_gcd)( mbedtls_mpi *G, const mbedtls_mpi *A, const mbedtls_mpi *B ); - int (*_rom_mbedtls_mpi_inv_mod)( mbedtls_mpi *X, const mbedtls_mpi *A, const mbedtls_mpi *N ); - int (*_rom_mbedtls_mpi_is_prime_ext)( const mbedtls_mpi *X, int rounds, int (*f_rng)(void *, unsigned char *, size_t), void *p_rng ); - int (*_rom_mbedtls_ccm_star_encrypt_and_tag)( mbedtls_ccm_context *ctx, size_t length, const unsigned char *iv, size_t iv_len, const unsigned char *add, size_t add_len, const unsigned char *input, unsigned char *output, unsigned char *tag, size_t tag_len ); - int (*_rom_mbedtls_ccm_star_auth_decrypt)( mbedtls_ccm_context *ctx, size_t length, const unsigned char *iv, size_t iv_len, const unsigned char *add, size_t add_len, const unsigned char *input, unsigned char *output, const unsigned char *tag, size_t tag_len ); - void (*_rom_mbedtls_cipher_init)( mbedtls_cipher_context_t *ctx ); - int (*_rom_mbedtls_cipher_set_padding_mode)( mbedtls_cipher_context_t *ctx, mbedtls_cipher_padding_t mode ); - int (*_rom_mbedtls_cipher_reset)( mbedtls_cipher_context_t *ctx ); - int (*_rom_mbedtls_cipher_finish)( mbedtls_cipher_context_t *ctx, unsigned char *output, size_t *olen ); - int (*_rom_mbedtls_cipher_crypt)( mbedtls_cipher_context_t *ctx, const unsigned char *iv, size_t iv_len, const unsigned char *input, size_t ilen, unsigned char *output, size_t *olen ); - int (*_rom_mbedtls_cipher_cmac_starts)( mbedtls_cipher_context_t *ctx, const unsigned char *key, size_t keybits ); - int (*_rom_mbedtls_cipher_cmac_update)( mbedtls_cipher_context_t *ctx, const unsigned char *input, size_t ilen ); - int (*_rom_mbedtls_cipher_cmac_finish)( mbedtls_cipher_context_t *ctx, unsigned char *output ); - void (*_rom_mbedtls_ctr_drbg_init)( mbedtls_ctr_drbg_context *ctx ); - int (*_rom_mbedtls_ctr_drbg_seed)( mbedtls_ctr_drbg_context *ctx, int (*f_entropy)(void *, unsigned char *, size_t), void *p_entropy, const unsigned char *custom, size_t len ); - void (*_rom_mbedtls_ctr_drbg_free)( mbedtls_ctr_drbg_context *ctx ); - int (*_rom_mbedtls_ctr_drbg_reseed)( mbedtls_ctr_drbg_context *ctx, const unsigned char *additional, size_t len ); - int (*_rom_mbedtls_ctr_drbg_random_with_add)( void *p_rng, unsigned char *output, size_t output_len, const unsigned char *additional, size_t add_len ); - int (*_rom_mbedtls_ctr_drbg_random)( void *p_rng, unsigned char *output, size_t output_len ); - void (*_rom_mbedtls_dhm_init)( mbedtls_dhm_context *ctx ); - int (*_rom_mbedtls_dhm_read_params)( mbedtls_dhm_context *ctx, unsigned char **p, const unsigned char *end ); - int (*_rom_mbedtls_dhm_make_public)( mbedtls_dhm_context *ctx, int x_size, unsigned char *output, size_t olen, int (*f_rng)(void *, unsigned char *, size_t), void *p_rng ); - int (*_rom_mbedtls_dhm_calc_secret)( mbedtls_dhm_context *ctx, unsigned char *output, size_t output_size, size_t *olen, int (*f_rng)(void *, unsigned char *, size_t), void *p_rng ); - void (*_rom_mbedtls_dhm_free)( mbedtls_dhm_context *ctx ); - void (*_rom_mbedtls_ecdh_init)( mbedtls_ecdh_context *ctx ); - int (*_rom_mbedtls_ecdh_setup)( mbedtls_ecdh_context *ctx, mbedtls_ecp_group_id grp_id ); - void (*_rom_mbedtls_ecdh_free)( mbedtls_ecdh_context *ctx ); - int (*_rom_mbedtls_ecdh_read_params)( mbedtls_ecdh_context *ctx, const unsigned char **buf, const unsigned char *end ); - int (*_rom_mbedtls_ecdh_get_params)( mbedtls_ecdh_context *ctx, const mbedtls_ecp_keypair *key, mbedtls_ecdh_side side ); - int (*_rom_mbedtls_ecdh_make_public)( mbedtls_ecdh_context *ctx, size_t *olen, unsigned char *buf, size_t blen, int (*f_rng)(void *, unsigned char *, size_t), void *p_rng ); - int (*_rom_mbedtls_ecdh_calc_secret)( mbedtls_ecdh_context *ctx, size_t *olen, unsigned char *buf, size_t blen, int (*f_rng)(void *, unsigned char *, size_t), void *p_rng ); - void (*_rom_mbedtls_ecdh_enable_restart)( mbedtls_ecdh_context *ctx ); - int (*_rom_mbedtls_ecdsa_write_signature)( mbedtls_ecdsa_context *ctx, mbedtls_md_type_t md_alg, const unsigned char *hash, size_t hlen, unsigned char *sig, size_t *slen, int (*f_rng)(void *, unsigned char *, size_t), void *p_rng ); - int (*_rom_mbedtls_ecdsa_write_signature_restartable)( mbedtls_ecdsa_context *ctx, mbedtls_md_type_t md_alg, const unsigned char *hash, size_t hlen, unsigned char *sig, size_t *slen, int (*f_rng)(void *, unsigned char *, size_t), void *p_rng, mbedtls_ecdsa_restart_ctx *rs_ctx ); - int (*_rom_mbedtls_ecdsa_read_signature)( mbedtls_ecdsa_context *ctx, const unsigned char *hash, size_t hlen, const unsigned char *sig, size_t slen ); - int (*_rom_mbedtls_ecdsa_read_signature_restartable)( mbedtls_ecdsa_context *ctx, const unsigned char *hash, size_t hlen, const unsigned char *sig, size_t slen, mbedtls_ecdsa_restart_ctx *rs_ctx ); - int (*_rom_mbedtls_ecdsa_from_keypair)( mbedtls_ecdsa_context *ctx, const mbedtls_ecp_keypair *key ); - void (*_rom_mbedtls_ecdsa_init)( mbedtls_ecdsa_context *ctx ); - void (*_rom_mbedtls_ecdsa_free)( mbedtls_ecdsa_context *ctx ); - void (*_rom_mbedtls_ecdsa_restart_init)( mbedtls_ecdsa_restart_ctx *ctx ); - void (*_rom_mbedtls_ecdsa_restart_free)( mbedtls_ecdsa_restart_ctx *ctx ); - void (*_rom_mbedtls_ecjpake_init)( mbedtls_ecjpake_context *ctx ); - int (*_rom_mbedtls_ecjpake_check)( const mbedtls_ecjpake_context *ctx ); - int (*_rom_mbedtls_ecjpake_write_round_one)( mbedtls_ecjpake_context *ctx, unsigned char *buf, size_t len, size_t *olen, int (*f_rng)(void *, unsigned char *, size_t), void *p_rng ); - int (*_rom_mbedtls_ecjpake_read_round_one)( mbedtls_ecjpake_context *ctx, const unsigned char *buf, size_t len ); - int (*_rom_mbedtls_ecjpake_write_round_two)( mbedtls_ecjpake_context *ctx, unsigned char *buf, size_t len, size_t *olen, int (*f_rng)(void *, unsigned char *, size_t), void *p_rng ); - int (*_rom_mbedtls_ecjpake_read_round_two)( mbedtls_ecjpake_context *ctx, const unsigned char *buf, size_t len ); - int (*_rom_mbedtls_ecjpake_derive_secret)( mbedtls_ecjpake_context *ctx, unsigned char *buf, size_t len, size_t *olen, int (*f_rng)(void *, unsigned char *, size_t), void *p_rng ); - void (*_rom_mbedtls_ecjpake_free)( mbedtls_ecjpake_context *ctx ); - int (*_rom_mbedtls_ecp_check_budget)( const mbedtls_ecp_group *grp, mbedtls_ecp_restart_ctx *rs_ctx, unsigned ops ); - int (*_rom_mbedtls_ecp_restart_is_enabled)( void ); - const mbedtls_ecp_curve_info *(*_rom_mbedtls_ecp_curve_list)( void ); - const mbedtls_ecp_group_id *(*_rom_mbedtls_ecp_grp_id_list)( void ); - const mbedtls_ecp_curve_info *(*_rom_mbedtls_ecp_curve_info_from_grp_id)( mbedtls_ecp_group_id grp_id ); - const mbedtls_ecp_curve_info *(*_rom_mbedtls_ecp_curve_info_from_tls_id)( uint16_t tls_id ); - void (*_rom_mbedtls_ecp_point_init)( mbedtls_ecp_point *pt ); - void (*_rom_mbedtls_ecp_group_init)( mbedtls_ecp_group *grp ); - void (*_rom_mbedtls_ecp_keypair_init)( mbedtls_ecp_keypair *key ); - void (*_rom_mbedtls_ecp_point_free)( mbedtls_ecp_point *pt ); - void (*_rom_mbedtls_ecp_group_free)( mbedtls_ecp_group *grp ); - void (*_rom_mbedtls_ecp_keypair_free)( mbedtls_ecp_keypair *key ); - void (*_rom_mbedtls_ecp_restart_init)( mbedtls_ecp_restart_ctx *ctx ); - void (*_rom_mbedtls_ecp_restart_free)( mbedtls_ecp_restart_ctx *ctx ); - int (*_rom_mbedtls_ecp_copy)( mbedtls_ecp_point *P, const mbedtls_ecp_point *Q ); - int (*_rom_mbedtls_ecp_group_copy)( mbedtls_ecp_group *dst, const mbedtls_ecp_group *src ); - int (*_rom_mbedtls_ecp_set_zero)( mbedtls_ecp_point *pt ); - int (*_rom_mbedtls_ecp_is_zero)( mbedtls_ecp_point *pt ); - int (*_rom_mbedtls_ecp_point_cmp)( const mbedtls_ecp_point *P, const mbedtls_ecp_point *Q ); - int (*_rom_mbedtls_ecp_point_write_binary)( const mbedtls_ecp_group *grp, const mbedtls_ecp_point *P, int format, size_t *olen, unsigned char *buf, size_t buflen ); - int (*_rom_mbedtls_ecp_point_read_binary)( const mbedtls_ecp_group *grp, mbedtls_ecp_point *P, const unsigned char *buf, size_t ilen ); - int (*_rom_mbedtls_ecp_tls_read_point)( const mbedtls_ecp_group *grp, mbedtls_ecp_point *pt, const unsigned char **buf, size_t len ); - int (*_rom_mbedtls_ecp_tls_write_point)( const mbedtls_ecp_group *grp, const mbedtls_ecp_point *pt, int format, size_t *olen, unsigned char *buf, size_t blen ); - int (*_rom_mbedtls_ecp_group_load)( mbedtls_ecp_group *grp, mbedtls_ecp_group_id id ); - int (*_rom_mbedtls_ecp_tls_read_group)( mbedtls_ecp_group *grp, const unsigned char **buf, size_t len ); - int (*_rom_mbedtls_ecp_tls_read_group_id)( mbedtls_ecp_group_id *grp, const unsigned char **buf, size_t len ); - int (*_rom_mbedtls_ecp_tls_write_group)( const mbedtls_ecp_group *grp, size_t *olen, unsigned char *buf, size_t blen ); - int (*_rom_mbedtls_ecp_mul)( mbedtls_ecp_group *grp, mbedtls_ecp_point *R, const mbedtls_mpi *m, const mbedtls_ecp_point *P, int (*f_rng)(void *, unsigned char *, size_t), void *p_rng ); - int (*_rom_mbedtls_ecp_mul_restartable)( mbedtls_ecp_group *grp, mbedtls_ecp_point *R, const mbedtls_mpi *m, const mbedtls_ecp_point *P, int (*f_rng)(void *, unsigned char *, size_t), void *p_rng, mbedtls_ecp_restart_ctx *rs_ctx ); - int (*_rom_mbedtls_ecp_muladd)( mbedtls_ecp_group *grp, mbedtls_ecp_point *R, const mbedtls_mpi *m, const mbedtls_ecp_point *P, const mbedtls_mpi *n, const mbedtls_ecp_point *Q ); - int (*_rom_mbedtls_ecp_muladd_restartable)( mbedtls_ecp_group *grp, mbedtls_ecp_point *R, const mbedtls_mpi *m, const mbedtls_ecp_point *P, const mbedtls_mpi *n, const mbedtls_ecp_point *Q, mbedtls_ecp_restart_ctx *rs_ctx ); - int (*_rom_mbedtls_ecp_check_pubkey)( const mbedtls_ecp_group *grp, const mbedtls_ecp_point *pt ); - int (*_rom_mbedtls_ecp_check_privkey)( const mbedtls_ecp_group *grp, const mbedtls_mpi *d ); - int (*_rom_mbedtls_ecp_gen_privkey)( const mbedtls_ecp_group *grp, mbedtls_mpi *d, int (*f_rng)(void *, unsigned char *, size_t), void *p_rng ); - int (*_rom_mbedtls_ecp_gen_keypair_base)( mbedtls_ecp_group *grp, const mbedtls_ecp_point *G, mbedtls_mpi *d, mbedtls_ecp_point *Q, int (*f_rng)(void *, unsigned char *, size_t), void *p_rng ); - int (*_rom_mbedtls_ecp_check_pub_priv)( const mbedtls_ecp_keypair *pub, const mbedtls_ecp_keypair *prv ); - int (*_rom_mbedtls_reserved0)(void); - int (*_rom_mbedtls_reserved1)(void); - int (*_rom_mbedtls_gcm_crypt_and_tag)( mbedtls_gcm_context *ctx, int mode, size_t length, const unsigned char *iv, size_t iv_len, const unsigned char *add, size_t add_len, const unsigned char *input, unsigned char *output, size_t tag_len, unsigned char *tag ); - int (*_rom_mbedtls_gcm_starts)( mbedtls_gcm_context *ctx, int mode, const unsigned char *iv, size_t iv_len, const unsigned char *add, size_t add_len ); - int (*_rom_mbedtls_gcm_update)( mbedtls_gcm_context *ctx, size_t length, const unsigned char *input, unsigned char *output ); - int (*_rom_mbedtls_gcm_finish)( mbedtls_gcm_context *ctx, unsigned char *tag, size_t tag_len ); - void (*_rom_mbedtls_hmac_drbg_init)( mbedtls_hmac_drbg_context *ctx ); - int (*_rom_mbedtls_hmac_drbg_seed_buf)( mbedtls_hmac_drbg_context *ctx, const mbedtls_md_info_t * md_info, const unsigned char *data, size_t data_len ); - int (*_rom_mbedtls_hmac_drbg_update_ret)( mbedtls_hmac_drbg_context *ctx, const unsigned char *additional, size_t add_len ); - int (*_rom_mbedtls_hmac_drbg_reseed)( mbedtls_hmac_drbg_context *ctx, const unsigned char *additional, size_t len ); - int (*_rom_mbedtls_hmac_drbg_random_with_add)( void *p_rng, unsigned char *output, size_t output_len, const unsigned char *additional, size_t add_len ); - int (*_rom_mbedtls_hmac_drbg_random)( void *p_rng, unsigned char *output, size_t out_len ); - void (*_rom_mbedtls_hmac_drbg_free)( mbedtls_hmac_drbg_context *ctx ); - const int *(*_rom_mbedtls_md_list)( void ); - void (*_rom_mbedtls_md_init)( mbedtls_md_context_t *ctx ); - void (*_rom_mbedtls_md_free)( mbedtls_md_context_t *ctx ); - int (*_rom_mbedtls_md_setup)( mbedtls_md_context_t *ctx, const mbedtls_md_info_t *md_info, int hmac ); - int (*_rom_mbedtls_md_clone)( mbedtls_md_context_t *dst, const mbedtls_md_context_t *src ); - unsigned char (*_rom_mbedtls_md_get_size)( const mbedtls_md_info_t *md_info ); - mbedtls_md_type_t (*_rom_mbedtls_md_get_type)( const mbedtls_md_info_t *md_info ); - int (*_rom_mbedtls_md_starts)( mbedtls_md_context_t *ctx ); - int (*_rom_mbedtls_md_update)( mbedtls_md_context_t *ctx, const unsigned char *input, size_t ilen ); - int (*_rom_mbedtls_md_finish)( mbedtls_md_context_t *ctx, unsigned char *output ); - int (*_rom_mbedtls_md)( const mbedtls_md_info_t *md_info, const unsigned char *input, size_t ilen, unsigned char *output ); - int (*_rom_mbedtls_md_hmac_starts)( mbedtls_md_context_t *ctx, const unsigned char *key, size_t keylen ); - int (*_rom_mbedtls_md_hmac_update)( mbedtls_md_context_t *ctx, const unsigned char *input, size_t ilen ); - int (*_rom_mbedtls_md_hmac_finish)( mbedtls_md_context_t *ctx, unsigned char *output); - int (*_rom_mbedtls_md_hmac_reset)( mbedtls_md_context_t *ctx ); - int (*_rom_mbedtls_oid_get_x509_ext_type)( const mbedtls_asn1_buf *oid, int *ext_type ); - int (*_rom_mbedtls_oid_get_pk_alg)( const mbedtls_asn1_buf *oid, mbedtls_pk_type_t *pk_alg ); - int (*_rom_mbedtls_oid_get_ec_grp)( const mbedtls_asn1_buf *oid, mbedtls_ecp_group_id *grp_id ); - int (*_rom_mbedtls_oid_get_sig_alg)( const mbedtls_asn1_buf *oid, mbedtls_md_type_t *md_alg, mbedtls_pk_type_t *pk_alg ); - int (*_rom_mbedtls_oid_get_md_alg)( const mbedtls_asn1_buf *oid, mbedtls_md_type_t *md_alg ); - int (*_rom_mbedtls_oid_get_md_hmac)( const mbedtls_asn1_buf *oid, mbedtls_md_type_t *md_hmac ); - int (*_rom_mbedtls_oid_get_oid_by_md)( mbedtls_md_type_t md_alg, const char **oid, size_t *olen ); - int (*_rom_mbedtls_oid_get_cipher_alg)( const mbedtls_asn1_buf *oid, mbedtls_cipher_type_t *cipher_alg ); - int (*_rom_mbedtls_oid_get_pkcs12_pbe_alg)( const mbedtls_asn1_buf *oid, mbedtls_md_type_t *md_alg, mbedtls_cipher_type_t *cipher_alg ); - void (*_rom_mbedtls_pem_init)( void *ctx ); - void (*_rom_mbedtls_pem_free)( void *ctx ); - int (*_rom_mbedtls_pkcs12_pbe_sha1_rc4_128)( mbedtls_asn1_buf *pbe_params, int mode, const unsigned char *pwd, size_t pwdlen, const unsigned char *input, size_t len, unsigned char *output ); - int (*_rom_mbedtls_pkcs12_pbe)( mbedtls_asn1_buf *pbe_params, int mode, mbedtls_cipher_type_t cipher_type, mbedtls_md_type_t md_type, const unsigned char *pwd, size_t pwdlen, const unsigned char *input, size_t len, unsigned char *output ); - int (*_rom_mbedtls_pkcs12_derivation)( unsigned char *data, size_t datalen, const unsigned char *pwd, size_t pwdlen, const unsigned char *salt, size_t saltlen, mbedtls_md_type_t mbedtls_md, int id, int iterations ); - int (*_rom_mbedtls_pkcs5_pbes2)( const mbedtls_asn1_buf *pbe_params, int mode, const unsigned char *pwd, size_t pwdlen, const unsigned char *data, size_t datalen, unsigned char *output ); - int (*_rom_mbedtls_pkcs5_pbkdf2_hmac)( mbedtls_md_context_t *ctx, const unsigned char *password, size_t plen, const unsigned char *salt, size_t slen, unsigned int iteration_count, uint32_t key_length, unsigned char *output ); - const mbedtls_pk_info_t *(*_rom_mbedtls_pk_info_from_type)( mbedtls_pk_type_t pk_type ); - void (*_rom_mbedtls_pk_init)( mbedtls_pk_context *ctx ); - void (*_rom_mbedtls_pk_free)( mbedtls_pk_context *ctx ); - void (*_rom_mbedtls_pk_restart_init)( mbedtls_pk_restart_ctx *ctx ); - void (*_rom_mbedtls_pk_restart_free)( mbedtls_pk_restart_ctx *ctx ); - int (*_rom_mbedtls_pk_setup)( mbedtls_pk_context *ctx, const mbedtls_pk_info_t *info ); - int (*_rom_mbedtls_pk_can_do)( const mbedtls_pk_context *ctx, mbedtls_pk_type_t type ); - int (*_rom_mbedtls_pk_verify)( mbedtls_pk_context *ctx, mbedtls_md_type_t md_alg, const unsigned char *hash, size_t hash_len, const unsigned char *sig, size_t sig_len ); - int (*_rom_mbedtls_pk_verify_restartable)( mbedtls_pk_context *ctx, mbedtls_md_type_t md_alg, const unsigned char *hash, size_t hash_len, const unsigned char *sig, size_t sig_len, mbedtls_pk_restart_ctx *rs_ctx ); - int (*_rom_mbedtls_pk_verify_ext)( mbedtls_pk_type_t type, const void *options, mbedtls_pk_context *ctx, mbedtls_md_type_t md_alg, const unsigned char *hash, size_t hash_len, const unsigned char *sig, size_t sig_len ); - int (*_rom_mbedtls_pk_sign_restartable)( mbedtls_pk_context *ctx, mbedtls_md_type_t md_alg, const unsigned char *hash, size_t hash_len, unsigned char *sig, size_t *sig_len, int (*f_rng)(void *, unsigned char *, size_t), void *p_rng, mbedtls_pk_restart_ctx *rs_ctx ); - int (*_rom_mbedtls_pk_encrypt)( mbedtls_pk_context *ctx, const unsigned char *input, size_t ilen, unsigned char *output, size_t *olen, size_t osize, int (*f_rng)(void *, unsigned char *, size_t), void *p_rng ); - mbedtls_pk_type_t (*_rom_mbedtls_pk_get_type)( const mbedtls_pk_context *ctx ); - int (*_rom_mbedtls_pk_parse_subpubkey)( unsigned char **p, const unsigned char *end, mbedtls_pk_context *pk ); - void (*_rom_mbedtls_rsa_init)( mbedtls_rsa_context *ctx ); - int (*_rom_mbedtls_rsa_import)( mbedtls_rsa_context *ctx, const mbedtls_mpi *N, const mbedtls_mpi *P, const mbedtls_mpi *Q, const mbedtls_mpi *D, const mbedtls_mpi *E ); - int (*_rom_mbedtls_rsa_import_raw)( mbedtls_rsa_context *ctx, unsigned char const *N, size_t N_len, unsigned char const *P, size_t P_len, unsigned char const *Q, size_t Q_len, unsigned char const *D, size_t D_len, unsigned char const *E, size_t E_len ); - int (*_rom_mbedtls_rsa_complete)( mbedtls_rsa_context *ctx ); - int (*_rom_mbedtls_rsa_set_padding)( mbedtls_rsa_context *ctx, int padding, mbedtls_md_type_t hash_id ); - size_t (*_rom_mbedtls_rsa_get_len)( const mbedtls_rsa_context *ctx ); - int (*_rom_mbedtls_rsa_check_pubkey)( const mbedtls_rsa_context *ctx ); - int (*_rom_mbedtls_rsa_check_privkey)( const mbedtls_rsa_context *ctx ); - int (*_rom_mbedtls_rsa_check_pub_priv)( const mbedtls_rsa_context *pub, const mbedtls_rsa_context *prv ); - int (*_rom_mbedtls_rsa_public)( mbedtls_rsa_context *ctx, const unsigned char *input, unsigned char *output ); - int (*_rom_mbedtls_rsa_private)( mbedtls_rsa_context *ctx, int (*f_rng)(void *, unsigned char *, size_t), void *p_rng, const unsigned char *input, unsigned char *output ); - int (*_rom_mbedtls_rsa_pkcs1_encrypt)( mbedtls_rsa_context *ctx, int (*f_rng)(void *, unsigned char *, size_t), void *p_rng, int mode, size_t ilen, const unsigned char *input, unsigned char *output ); - int (*_rom_mbedtls_rsa_rsaes_pkcs1_v15_encrypt)( mbedtls_rsa_context *ctx, int (*f_rng)(void *, unsigned char *, size_t), void *p_rng, int mode, size_t ilen, const unsigned char *input, unsigned char *output ); - int (*_rom_mbedtls_rsa_rsaes_oaep_encrypt)( mbedtls_rsa_context *ctx, int (*f_rng)(void *, unsigned char *, size_t), void *p_rng, int mode, const unsigned char *label, size_t label_len, size_t ilen, const unsigned char *input, unsigned char *output ); - int (*_rom_mbedtls_rsa_pkcs1_decrypt)( mbedtls_rsa_context *ctx, int (*f_rng)(void *, unsigned char *, size_t), void *p_rng, int mode, size_t *olen, const unsigned char *input, unsigned char *output, size_t output_max_len ); - int (*_rom_mbedtls_rsa_rsaes_pkcs1_v15_decrypt)( mbedtls_rsa_context *ctx, int (*f_rng)(void *, unsigned char *, size_t), void *p_rng, int mode, size_t *olen, const unsigned char *input, unsigned char *output, size_t output_max_len ); - int (*_rom_mbedtls_rsa_rsaes_oaep_decrypt)( mbedtls_rsa_context *ctx, int (*f_rng)(void *, unsigned char *, size_t), void *p_rng, int mode, const unsigned char *label, size_t label_len, size_t *olen, const unsigned char *input, unsigned char *output, size_t output_max_len ); - int (*_rom_mbedtls_rsa_pkcs1_sign)( mbedtls_rsa_context *ctx, int (*f_rng)(void *, unsigned char *, size_t), void *p_rng, int mode, mbedtls_md_type_t md_alg, unsigned int hashlen, const unsigned char *hash, unsigned char *sig ); - int (*_rom_mbedtls_rsa_rsassa_pkcs1_v15_sign)( mbedtls_rsa_context *ctx, int (*f_rng)(void *, unsigned char *, size_t), void *p_rng, int mode, mbedtls_md_type_t md_alg, unsigned int hashlen, const unsigned char *hash, unsigned char *sig ); - int (*_rom_mbedtls_rsa_rsassa_pss_sign)( mbedtls_rsa_context *ctx, int (*f_rng)(void *, unsigned char *, size_t), void *p_rng, int mode, mbedtls_md_type_t md_alg, unsigned int hashlen, const unsigned char *hash, unsigned char *sig ); - int (*_rom_mbedtls_rsa_pkcs1_verify)( mbedtls_rsa_context *ctx, int (*f_rng)(void *, unsigned char *, size_t), void *p_rng, int mode, mbedtls_md_type_t md_alg, unsigned int hashlen, const unsigned char *hash, const unsigned char *sig ); - int (*_rom_mbedtls_rsa_rsassa_pkcs1_v15_verify)( mbedtls_rsa_context *ctx, int (*f_rng)(void *, unsigned char *, size_t), void *p_rng, int mode, mbedtls_md_type_t md_alg, unsigned int hashlen, const unsigned char *hash, const unsigned char *sig ); - int (*_rom_mbedtls_rsa_rsassa_pss_verify)( mbedtls_rsa_context *ctx, int (*f_rng)(void *, unsigned char *, size_t), void *p_rng, int mode, mbedtls_md_type_t md_alg, unsigned int hashlen, const unsigned char *hash, const unsigned char *sig ); - int (*_rom_mbedtls_rsa_rsassa_pss_verify_ext)( mbedtls_rsa_context *ctx, int (*f_rng)(void *, unsigned char *, size_t), void *p_rng, int mode, mbedtls_md_type_t md_alg, unsigned int hashlen, const unsigned char *hash, mbedtls_md_type_t mgf1_hash_id, int expected_salt_len, const unsigned char *sig ); - void (*_rom_mbedtls_rsa_free)( mbedtls_rsa_context *ctx ); - int (*_rom_mbedtls_rsa_deduce_primes)( mbedtls_mpi const *N, mbedtls_mpi const *E, mbedtls_mpi const *D, mbedtls_mpi *P, mbedtls_mpi *Q ); - int (*_rom_mbedtls_rsa_deduce_private_exponent)( mbedtls_mpi const *P, mbedtls_mpi const *Q, mbedtls_mpi const *E, mbedtls_mpi *D ); - int (*_rom_mbedtls_rsa_deduce_crt)( const mbedtls_mpi *P, const mbedtls_mpi *Q, const mbedtls_mpi *D, mbedtls_mpi *DP, mbedtls_mpi *DQ, mbedtls_mpi *QP ); - int (*_rom_mbedtls_rsa_validate_params)( const mbedtls_mpi *N, const mbedtls_mpi *P, const mbedtls_mpi *Q, const mbedtls_mpi *D, const mbedtls_mpi *E, int (*f_rng)(void *, unsigned char *, size_t), void *p_rng ); - int (*_rom_mbedtls_rsa_validate_crt)( const mbedtls_mpi *P, const mbedtls_mpi *Q, const mbedtls_mpi *D, const mbedtls_mpi *DP, const mbedtls_mpi *DQ, const mbedtls_mpi *QP ); - void (*_rom_mbedtls_sha1_init)( mbedtls_sha1_context *ctx ); - void (*_rom_mbedtls_sha1_free)( mbedtls_sha1_context *ctx ); - void (*_rom_mbedtls_sha1_clone)( mbedtls_sha1_context *dst, const mbedtls_sha1_context *src ); - int (*_rom_mbedtls_sha1_starts)( mbedtls_sha1_context *ctx ); - int (*_rom_mbedtls_sha1_finish)( mbedtls_sha1_context *ctx, unsigned char output[20] ); - void (*_rom_mbedtls_sha256_init)( mbedtls_sha256_context *ctx ); - void (*_rom_mbedtls_sha256_free)( mbedtls_sha256_context *ctx ); - void (*_rom_mbedtls_sha256_clone)( mbedtls_sha256_context *dst, const mbedtls_sha256_context *src ); - int (*_rom_mbedtls_sha256_starts)( mbedtls_sha256_context *ctx, int is224 ); - int (*_rom_mbedtls_sha256_finish)( mbedtls_sha256_context *ctx, unsigned char output[32] ); - int (*_rom_mbedtls_sha256)( const unsigned char *input, size_t ilen, unsigned char output[32], int is224 ); - void (*_rom_mbedtls_sha512_init)( mbedtls_sha512_context *ctx ); - void (*_rom_mbedtls_sha512_free)( mbedtls_sha512_context *ctx ); - void (*_rom_mbedtls_sha512_clone)( mbedtls_sha512_context *dst, const mbedtls_sha512_context *src ); - int (*_rom_mbedtls_sha512_starts)( mbedtls_sha512_context *ctx, int is384 ); - int (*_rom_mbedtls_sha512_update)( mbedtls_sha512_context *ctx, const unsigned char *input, size_t ilen ); - int (*_rom_mbedtls_sha512_finish)( mbedtls_sha512_context *ctx, unsigned char output[64] ); - int (*_rom_mbedtls_internal_sha512_process)( mbedtls_sha512_context *ctx, const unsigned char data[128] ); - int (*_rom_mbedtls_sha512)( const unsigned char *input, size_t ilen, unsigned char output[64], int is384 ); - void (*_rom_mbedtls_ssl_conf_endpoint)( mbedtls_ssl_config *conf, int endpoint ); - void (*_rom_mbedtls_ssl_conf_transport)( mbedtls_ssl_config *conf, int transport ); - void (*_rom_mbedtls_ssl_set_bio)( mbedtls_ssl_context *ssl, void *p_bio, mbedtls_ssl_send_t *f_send, mbedtls_ssl_recv_t *f_recv, mbedtls_ssl_recv_timeout_t *f_recv_timeout ); - int (*_rom_mbedtls_ssl_conf_dh_param_bin)( mbedtls_ssl_config *conf, const unsigned char *dhm_P, size_t P_len, const unsigned char *dhm_G, size_t G_len ); - size_t (*_rom_mbedtls_ssl_get_max_frag_len)( const mbedtls_ssl_context *ssl ); - int (*_rom_mbedtls_ssl_get_max_out_record_payload)( const mbedtls_ssl_context *ssl ); - int (*_rom_mbedtls_ssl_handshake)( mbedtls_ssl_context *ssl ); - int (*_rom_mbedtls_ssl_handshake_step)( mbedtls_ssl_context *ssl ); - int (*_rom_mbedtls_ssl_renegotiate)( mbedtls_ssl_context *ssl ); - int (*_rom_mbedtls_ssl_send_alert_message)( mbedtls_ssl_context *ssl, unsigned char level, unsigned char message ); - int (*_rom_mbedtls_ssl_config_defaults)( mbedtls_ssl_config *conf, int endpoint, int transport, int preset ); - void (*_rom_mbedtls_ssl_session_init)( mbedtls_ssl_session *session ); - void (*_rom_mbedtls_ssl_session_free)( mbedtls_ssl_session *session ); - void (*_rom_mbedtls_ssl_transform_free)( mbedtls_ssl_transform *transform ); - void (*_rom_mbedtls_ssl_handshake_free)( mbedtls_ssl_context *ssl ); - int (*_rom_mbedtls_ssl_handshake_client_step)( mbedtls_ssl_context *ssl ); - void (*_rom_mbedtls_ssl_handshake_wrapup)( mbedtls_ssl_context *ssl ); - int (*_rom_mbedtls_ssl_derive_keys)( mbedtls_ssl_context *ssl ); - int (*_rom_mbedtls_ssl_handle_message_type)( mbedtls_ssl_context *ssl ); - int (*_rom_mbedtls_ssl_prepare_handshake_record)( mbedtls_ssl_context *ssl ); - void (*_rom_mbedtls_ssl_update_handshake_status)( mbedtls_ssl_context *ssl ); - int (*_rom_mbedtls_ssl_read_record)( mbedtls_ssl_context *ssl, unsigned update_hs_digest ); - int (*_rom_mbedtls_ssl_fetch_input)( mbedtls_ssl_context *ssl, size_t nb_want ); - int (*_rom_mbedtls_ssl_write_handshake_msg)( mbedtls_ssl_context *ssl ); - int (*_rom_mbedtls_ssl_write_record)( mbedtls_ssl_context *ssl, uint8_t force_flush ); - int (*_rom_mbedtls_ssl_flush_output)( mbedtls_ssl_context *ssl ); - int (*_rom_mbedtls_ssl_parse_certificate)( mbedtls_ssl_context *ssl ); - int (*_rom_mbedtls_ssl_write_certificate)( mbedtls_ssl_context *ssl ); - int (*_rom_mbedtls_ssl_parse_change_cipher_spec)( mbedtls_ssl_context *ssl ); - int (*_rom_mbedtls_ssl_write_change_cipher_spec)( mbedtls_ssl_context *ssl ); - int (*_rom_mbedtls_ssl_parse_finished)( mbedtls_ssl_context *ssl ); - int (*_rom_mbedtls_ssl_write_finished)( mbedtls_ssl_context *ssl ); - void (*_rom_mbedtls_ssl_optimize_checksum)( mbedtls_ssl_context *ssl, const mbedtls_ssl_ciphersuite_t *ciphersuite_info ); - int (*_rom_mbedtls_ssl_psk_derive_premaster)( mbedtls_ssl_context *ssl, mbedtls_key_exchange_type_t key_ex ); - unsigned char (*_rom_mbedtls_ssl_sig_from_pk)( mbedtls_pk_context *pk ); - mbedtls_pk_type_t (*_rom_mbedtls_ssl_pk_alg_from_sig)( unsigned char sig ); - mbedtls_md_type_t (*_rom_mbedtls_ssl_md_alg_from_hash)( unsigned char hash ); - unsigned char (*_rom_mbedtls_ssl_hash_from_md_alg)( int md ); - int (*_rom_mbedtls_ssl_check_curve)( const mbedtls_ssl_context *ssl, mbedtls_ecp_group_id grp_id ); - int (*_rom_mbedtls_ssl_check_sig_hash)( const mbedtls_ssl_context *ssl, mbedtls_md_type_t md ); - void (*_rom_mbedtls_ssl_write_version)( int major, int minor, int transport, unsigned char ver[2] ); - void (*_rom_mbedtls_ssl_read_version)( int *major, int *minor, int transport, const unsigned char ver[2] ); - int (*_rom_mbedtls_ssl_get_key_exchange_md_ssl_tls)( mbedtls_ssl_context *ssl, unsigned char *output, unsigned char *data, size_t data_len ); - int (*_rom_mbedtls_ssl_get_key_exchange_md_tls1_2)( mbedtls_ssl_context *ssl, unsigned char *hash, size_t *hashlen, unsigned char *data, size_t data_len, mbedtls_md_type_t md_alg ); - int (*_rom_mbedtls_ssl_cf_hmac)( mbedtls_md_context_t *ctx, const unsigned char *add_data, size_t add_data_len, const unsigned char *data, size_t data_len_secret, size_t min_data_len, size_t max_data_len, unsigned char *output ); - void (*_rom_mbedtls_ssl_cf_memcpy_offset)( unsigned char *dst, const unsigned char *src_base, size_t offset_secret, size_t offset_min, size_t offset_max, size_t len ); - int (*_rom_mbedtls_x509_crt_parse_der)( mbedtls_x509_crt *chain, const unsigned char *buf, size_t buflen ); - int (*_rom_mbedtls_x509_crt_verify_restartable)( mbedtls_x509_crt *crt, mbedtls_x509_crt *trust_ca, mbedtls_x509_crl *ca_crl, const mbedtls_x509_crt_profile *profile, const char *cn, uint32_t *flags, int (*f_vrfy)(void *, mbedtls_x509_crt *, int, uint32_t *), void *p_vrfy, mbedtls_x509_crt_restart_ctx *rs_ctx ); - int (*_rom_mbedtls_x509_crt_check_key_usage)( const mbedtls_x509_crt *crt, unsigned int usage ); - int (*_rom_mbedtls_x509_crt_check_extended_key_usage)( const mbedtls_x509_crt *crt, const char *usage_oid, size_t usage_len ); - int (*_rom_mbedtls_x509_crt_is_revoked)( const mbedtls_x509_crt *crt, const mbedtls_x509_crl *crl ); - void (*_rom_mbedtls_x509_crt_init)( mbedtls_x509_crt *crt ); - void (*_rom_mbedtls_x509_crt_free)( mbedtls_x509_crt *crt ); - void (*_rom_mbedtls_x509_crt_restart_init)( mbedtls_x509_crt_restart_ctx *ctx ); - void (*_rom_mbedtls_x509_crt_restart_free)( mbedtls_x509_crt_restart_ctx *ctx ); - int (*_rom_mbedtls_x509_get_name)( unsigned char **p, const unsigned char *end, mbedtls_x509_name *cur ); - int (*_rom_mbedtls_x509_get_alg_null)( unsigned char **p, const unsigned char *end, mbedtls_x509_buf *alg ); - int (*_rom_mbedtls_x509_get_alg)( unsigned char **p, const unsigned char *end, mbedtls_x509_buf *alg, mbedtls_x509_buf *params ); - int (*_rom_mbedtls_x509_get_rsassa_pss_params)( const mbedtls_x509_buf *params, mbedtls_md_type_t *md_alg, mbedtls_md_type_t *mgf_md, int *salt_len ); - int (*_rom_mbedtls_x509_get_sig)( unsigned char **p, const unsigned char *end, mbedtls_x509_buf *sig ); - int (*_rom_mbedtls_x509_get_sig_alg)( const mbedtls_x509_buf *sig_oid, const mbedtls_x509_buf *sig_params, mbedtls_md_type_t *md_alg, mbedtls_pk_type_t *pk_alg, void **sig_opts ); - int (*_rom_mbedtls_x509_get_time)( unsigned char **p, const unsigned char *end, mbedtls_x509_time *t ); - int (*_rom_mbedtls_x509_get_serial)( unsigned char **p, const unsigned char *end, mbedtls_x509_buf *serial ); - int (*_rom_mbedtls_x509_get_ext)( unsigned char **p, const unsigned char *end, mbedtls_x509_buf *ext, int tag ); - void (*_mbedtls_mutex_init)( mbedtls_threading_mutex_t *mutex ); - void (*_mbedtls_mutex_free)( mbedtls_threading_mutex_t *mutex ); - int (*_mbedtls_mutex_lock)( mbedtls_threading_mutex_t *mutex ); - int (*_mbedtls_mutex_unlock)( mbedtls_threading_mutex_t *mutex ); - bool (*_mbedtls_allow_unsupported_critical_ext)( void ); - const mbedtls_cipher_info_t *(*_mbedtls_cipher_info_from_type)( const mbedtls_cipher_type_t cipher_type ); - const mbedtls_cipher_info_t *(*_mbedtls_cipher_info_from_values)( const mbedtls_cipher_id_t cipher_id, int key_bitlen, const mbedtls_cipher_mode_t mode ); - void (*_mbedtls_cipher_free)( mbedtls_cipher_context_t *ctx ); - int (*_mbedtls_cipher_setup)( mbedtls_cipher_context_t *ctx, const mbedtls_cipher_info_t *cipher_info ); - int (*_mbedtls_cipher_setkey)( mbedtls_cipher_context_t *ctx, const unsigned char *key, int key_bitlen, const mbedtls_operation_t operation ); - int (*_mbedtls_cipher_set_iv)( mbedtls_cipher_context_t *ctx, const unsigned char *iv, size_t iv_len ); - int (*_mbedtls_cipher_update)( mbedtls_cipher_context_t *ctx, const unsigned char *input, size_t ilen, unsigned char *output, size_t *olen ); - int (*_mbedtls_cipher_auth_encrypt)( mbedtls_cipher_context_t *ctx, const unsigned char *iv, size_t iv_len, const unsigned char *ad, size_t ad_len, const unsigned char *input, size_t ilen, unsigned char *output, size_t *olen, unsigned char *tag, size_t tag_len ); - int (*_mbedtls_cipher_auth_decrypt)( mbedtls_cipher_context_t *ctx, const unsigned char *iv, size_t iv_len, const unsigned char *ad, size_t ad_len, const unsigned char *input, size_t ilen, unsigned char *output, size_t *olen, const unsigned char *tag, size_t tag_len ); - int (*_mbedtls_hardware_poll)( void *data, unsigned char *output, size_t len, size_t *olen ); - const mbedtls_md_info_t *(*_mbedtls_md_info_from_type)( mbedtls_md_type_t md_type ); - int (*_mbedtls_pem_read_buffer)( void *ctx, const char *header, const char *footer, const unsigned char *data, const unsigned char *pwd, size_t pwdlen, size_t *use_len ); - void *(*_mbedtls_calloc)( size_t n, size_t size ); - void (*_mbedtls_free)( void *ptr ); - int (*_mbedtls_sha1_update)( mbedtls_sha1_context *ctx, const unsigned char *input, size_t ilen ); - int (*_mbedtls_internal_sha1_process)( mbedtls_sha1_context *ctx, const unsigned char data[64] ); - int (*_mbedtls_sha256_update)( mbedtls_sha256_context *ctx, const unsigned char *input, size_t ilen ); - int (*_mbedtls_internal_sha256_process)( mbedtls_sha256_context *ctx, const unsigned char data[64] ); - const int *(*_mbedtls_ssl_list_ciphersuites)( void ); - const mbedtls_ssl_ciphersuite_t *(*_mbedtls_ssl_ciphersuite_from_id)( int ciphersuite_id ); - mbedtls_pk_type_t (*_mbedtls_ssl_get_ciphersuite_sig_pk_alg)( const mbedtls_ssl_ciphersuite_t *info ); - int (*_mbedtls_ssl_ciphersuite_uses_ec)( const mbedtls_ssl_ciphersuite_t *info ); - int (*_mbedtls_ssl_ciphersuite_uses_psk)( const mbedtls_ssl_ciphersuite_t *info ); - int (*_mbedtls_ssl_handshake_server_step)( mbedtls_ssl_context *ssl ); - int (*_mbedtls_ssl_check_cert_usage)( const mbedtls_x509_crt *cert, const mbedtls_ssl_ciphersuite_t *ciphersuite, int cert_endpoint, uint32_t *flags ); - int (*_mbedtls_x509_time_is_past)( const mbedtls_x509_time *to ); - int (*_mbedtls_x509_time_is_future)( const mbedtls_x509_time *from ); -} mbedtls_rom_funcs_t; typedef struct mbedtls_rom_eco4_funcs { // aes module @@ -541,7 +167,6 @@ typedef struct mbedtls_rom_eco4_funcs { int (*_rom_mbedtls_ctr_drbg_random)(void *p_rng, unsigned char *output, size_t output_len); // base64 module int (*_rom_mbedtls_base64_decode)(unsigned char *dst, size_t dlen, size_t *olen, const unsigned char *src, size_t slen); - //*******************************************************************************************************************************************************************// // aes module int (*_rom_mbedtls_aes_crypt_cfb8)(mbedtls_aes_context *ctx, int mode, size_t length, unsigned char iv[16], const unsigned char *input, unsigned char *output); // md5 module @@ -674,126 +299,6 @@ typedef struct mbedtls_rom_eco4_funcs { #error "MBEDTLS_PLATFORM_ZEROIZE_ALT" #endif -#ifndef BOOTLOADER_BUILD -/* sha1.c */ -STRUCT_OFFSET_CHECK(mbedtls_sha1_context, total, 0); -STRUCT_OFFSET_CHECK(mbedtls_sha1_context, state, 8); -STRUCT_OFFSET_CHECK(mbedtls_sha1_context, buffer, 28); -STRUCT_OFFSET_CHECK(mbedtls_sha1_context, first_block, 92); -STRUCT_OFFSET_CHECK(mbedtls_sha1_context, mode, 96); -STRUCT_OFFSET_CHECK(mbedtls_sha1_context, sha_state, 100); -STRUCT_SIZE_CHECK(mbedtls_sha1_context, 104); -#if !(defined(MBEDTLS_SHA1_C) || (defined(MBEDTLS_SHA1_ALT) && SOC_SHA_SUPPORT_SHA1)) -#error "MBEDTLS_SHA1_C" -#endif - -/* sha256.c */ -STRUCT_OFFSET_CHECK(mbedtls_sha256_context, total, 0); -STRUCT_OFFSET_CHECK(mbedtls_sha256_context, state, 8); -STRUCT_OFFSET_CHECK(mbedtls_sha256_context, buffer, 40); -STRUCT_OFFSET_CHECK(mbedtls_sha256_context, first_block, 104); -STRUCT_OFFSET_CHECK(mbedtls_sha256_context, mode, 108); -STRUCT_OFFSET_CHECK(mbedtls_sha256_context, sha_state, 112); -STRUCT_SIZE_CHECK(mbedtls_sha256_context, 116); -#if !(defined(MBEDTLS_SHA256_C) || (defined(MBEDTLS_SHA256_ALT) && SOC_SHA_SUPPORT_SHA256)) -#error "MBEDTLS_SHA256_C" -#endif - -/* sha512.c */ -STRUCT_OFFSET_CHECK(mbedtls_sha512_context, MBEDTLS_PRIVATE(total), 0); -STRUCT_OFFSET_CHECK(mbedtls_sha512_context, MBEDTLS_PRIVATE(state), 16); -STRUCT_OFFSET_CHECK(mbedtls_sha512_context, MBEDTLS_PRIVATE(buffer), 80); -STRUCT_OFFSET_CHECK(mbedtls_sha512_context, MBEDTLS_PRIVATE(is384), 208); -STRUCT_SIZE_CHECK(mbedtls_sha512_context, 216); -#if !(defined(MBEDTLS_SHA512_C) || (defined(MBEDTLS_SHA512_ALT) && SOC_SHA_SUPPORT_SHA512)) -#error "MBEDTLS_SHA512_C" -#endif - -/* aes.c */ -STRUCT_OFFSET_CHECK(mbedtls_aes_context, MBEDTLS_PRIVATE(nr), 0); -STRUCT_OFFSET_CHECK(mbedtls_aes_context, MBEDTLS_PRIVATE(rk_offset), 4); -STRUCT_OFFSET_CHECK(mbedtls_aes_context, MBEDTLS_PRIVATE(buf), 8); -STRUCT_SIZE_CHECK(mbedtls_aes_context, 280); -STRUCT_OFFSET_CHECK(mbedtls_aes_xts_context, MBEDTLS_PRIVATE(crypt), 0); -STRUCT_OFFSET_CHECK(mbedtls_aes_xts_context, MBEDTLS_PRIVATE(tweak), 280); -STRUCT_SIZE_CHECK(mbedtls_aes_xts_context, 560); -#if (defined(MBEDTLS_HAVE_X86)) || \ - (defined(MBEDTLS_HAVE_X86_64)) -#error "MBEDTLS_HAVE_X86" -#endif -#if (!defined(MBEDTLS_AES_C)) || \ - (defined(MBEDTLS_AES_ALT)) || \ - (defined(MBEDTLS_AES_ENCRYPT_ALT)) || \ - (defined(MBEDTLS_AES_DECRYPT_ALT)) || \ - (defined(MBEDTLS_AES_SETKEY_ENC_ALT)) || \ - (defined(MBEDTLS_AES_SETKEY_DEC_ALT)) -#error "MBEDTLS_AES_C" -#endif -#if (!defined(MBEDTLS_AES_ROM_TABLES)) || \ - (defined(MBEDTLS_AES_FEWER_TABLES)) -#error "MBEDTLS_AES_ROM_TABLES" -#endif -#if (!defined(MBEDTLS_CIPHER_MODE_XTS)) || \ - (!defined(MBEDTLS_CIPHER_MODE_CBC)) || \ - (!defined(MBEDTLS_CIPHER_MODE_CFB)) || \ - (!defined(MBEDTLS_CIPHER_MODE_OFB)) || \ - (!defined(MBEDTLS_CIPHER_MODE_CTR)) -#error "MBEDTLS_CIPHER_MODE" -#endif - -/* asn1parse.c asn1write.c */ -STRUCT_OFFSET_CHECK(mbedtls_asn1_buf, tag, 0); -STRUCT_OFFSET_CHECK(mbedtls_asn1_buf, len, 4); -STRUCT_OFFSET_CHECK(mbedtls_asn1_buf, p, 8); -STRUCT_SIZE_CHECK(mbedtls_asn1_buf, 12); -STRUCT_OFFSET_CHECK(mbedtls_asn1_bitstring, len, 0); -STRUCT_OFFSET_CHECK(mbedtls_asn1_bitstring, unused_bits, 4); -STRUCT_OFFSET_CHECK(mbedtls_asn1_bitstring, p, 8); -STRUCT_SIZE_CHECK(mbedtls_asn1_bitstring, 12); -STRUCT_OFFSET_CHECK(mbedtls_asn1_sequence, buf, 0); -STRUCT_OFFSET_CHECK(mbedtls_asn1_sequence, next, 12); -STRUCT_SIZE_CHECK(mbedtls_asn1_sequence, 16); -STRUCT_OFFSET_CHECK(mbedtls_asn1_named_data, oid, 0); -STRUCT_OFFSET_CHECK(mbedtls_asn1_named_data, val, 12); -STRUCT_OFFSET_CHECK(mbedtls_asn1_named_data, next, 24); -STRUCT_OFFSET_CHECK(mbedtls_asn1_named_data, MBEDTLS_PRIVATE(next_merged), 28); -STRUCT_SIZE_CHECK(mbedtls_asn1_named_data, 32); -#if (!defined(MBEDTLS_ASN1_PARSE_C)) -#error "MBEDTLS_ASN1_PARSE_C" -#endif -#if (!defined(MBEDTLS_ASN1_WRITE_C)) -#error "MBEDTLS_ASN1_PARSE_C" -#endif - -/* base64.c */ -#if (!defined(MBEDTLS_BASE64_C)) -#error "MBEDTLS_BASE64_C" -#endif - -/* md5.c */ -#if (defined(MBEDTLS_MD2_C)) || \ - (defined(MBEDTLS_MD4_C)) || \ - (!defined(MBEDTLS_MD5_C)) /* || \ - (defined(MBEDTLS_MD5_ALT)) */ -#error "MBEDTLS_MD_C" -#endif -#ifdef CONFIG_MBEDTLS_ROM_MD5 -STRUCT_OFFSET_CHECK(mbedtls_md5_context, total, 0); -STRUCT_OFFSET_CHECK(mbedtls_md5_context, state, 8); -STRUCT_OFFSET_CHECK(mbedtls_md5_context, buffer, 24); -STRUCT_SIZE_CHECK(mbedtls_md5_context, 88); -#else -STRUCT_OFFSET_CHECK(mbedtls_md5_context, MBEDTLS_PRIVATE(total), 0); -STRUCT_OFFSET_CHECK(mbedtls_md5_context, MBEDTLS_PRIVATE(state), 8); -STRUCT_OFFSET_CHECK(mbedtls_md5_context, MBEDTLS_PRIVATE(buffer), 24); -STRUCT_SIZE_CHECK(mbedtls_md5_context, 88); -#endif -#endif /* BOOTLOADER_BUILD */ - -#if BOOTLOADER_BUILD -void mbedtls_rom_osi_functions_init_bootloader(void); -#endif /* BOOTLOADER_BUILD */ - #ifdef __cplusplus } #endif diff --git a/components/mbedtls/port/mbedtls_rom/mbedtls_rom_osi_bootloader.c b/components/mbedtls/port/mbedtls_rom/mbedtls_rom_osi_bootloader.c index 2c730a9ff77..3dccaa768b0 100644 --- a/components/mbedtls/port/mbedtls_rom/mbedtls_rom_osi_bootloader.c +++ b/components/mbedtls/port/mbedtls_rom/mbedtls_rom_osi_bootloader.c @@ -1,58 +1,76 @@ /* - * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ -#include "soc/chip_revision.h" -#include "hal/efuse_hal.h" -#include "mbedtls_rom_osi.h" - -/* This structure can be automatically generated by the script with rom.mbedtls.ld. */ -static const mbedtls_rom_funcs_t mbedtls_rom_funcs_table = { - /* Fill the ROM functions into mbedtls rom function table. */ - /* aes module */ - ._rom_mbedtls_aes_init = mbedtls_aes_init, - ._rom_mbedtls_aes_free = mbedtls_aes_free, - ._rom_mbedtls_aes_setkey_enc = mbedtls_aes_setkey_enc, - ._rom_mbedtls_aes_setkey_dec = mbedtls_aes_setkey_dec, - ._rom_mbedtls_aes_crypt_ecb = mbedtls_aes_crypt_ecb, - ._rom_mbedtls_aes_crypt_cbc = mbedtls_aes_crypt_cbc, - ._rom_mbedtls_internal_aes_encrypt = mbedtls_internal_aes_encrypt, - ._rom_mbedtls_internal_aes_decrypt = mbedtls_internal_aes_decrypt, -}; +#include + +#define MBEDTLS_DECLARE_PRIVATE_IDENTIFIERS +#include "mbedtls/private/aes.h" + +int mbedtls_internal_aes_encrypt(mbedtls_aes_context *ctx, const unsigned char input[16], unsigned char output[16]); +int mbedtls_internal_aes_decrypt(mbedtls_aes_context *ctx, const unsigned char input[16], unsigned char output[16]); + +#define ROM_TABLE_FN(table_type, field, fn) ((__typeof__(((table_type *)0)->field))(fn)) + +#define MBEDTLS_ROM_ECO4_FUNC_COUNT 221 +#define MBEDTLS_ROM_ECO4_BOOTLOADER_FUNC_COUNT 16 + +typedef struct mbedtls_rom_eco4_funcs { + void (*_rom_mbedtls_aes_init)(mbedtls_aes_context *ctx); + void (*_rom_mbedtls_aes_free)(mbedtls_aes_context *ctx); + void (*_rom_mbedtls_aes_xts_init)(mbedtls_aes_xts_context *ctx); + void (*_rom_mbedtls_aes_xts_free)(mbedtls_aes_xts_context *ctx); + int (*_rom_mbedtls_aes_setkey_enc)(mbedtls_aes_context *ctx, const unsigned char *key, unsigned int keybits); + int (*_rom_mbedtls_aes_setkey_dec)(mbedtls_aes_context *ctx, const unsigned char *key, unsigned int keybits); + int (*_rom_mbedtls_aes_xts_setkey_enc)(mbedtls_aes_xts_context *ctx, const unsigned char *key, unsigned int keybits); + int (*_rom_mbedtls_aes_xts_setkey_dec)(mbedtls_aes_xts_context *ctx, const unsigned char *key, unsigned int keybits); + int (*_rom_mbedtls_aes_crypt_ecb)(mbedtls_aes_context *ctx, int mode, const unsigned char input[16], unsigned char output[16]); + int (*_rom_mbedtls_aes_crypt_cbc)(mbedtls_aes_context *ctx, int mode, size_t length, unsigned char iv[16], const unsigned char *input, unsigned char *output); + int (*_rom_mbedtls_aes_crypt_xts)(mbedtls_aes_xts_context *ctx, int mode, size_t length, const unsigned char data_unit[16], const unsigned char *input, unsigned char *output); + int (*_rom_mbedtls_aes_crypt_cfb128)(mbedtls_aes_context *ctx, int mode, size_t length, size_t *iv_off, unsigned char iv[16], const unsigned char *input, unsigned char *output); + int (*_rom_mbedtls_aes_crypt_ofb)(mbedtls_aes_context *ctx, size_t length, size_t *iv_off, unsigned char iv[16], const unsigned char *input, unsigned char *output); + int (*_rom_mbedtls_aes_crypt_ctr)(mbedtls_aes_context *ctx, size_t length, size_t *nc_off, unsigned char nonce_counter[16], unsigned char stream_block[16], const unsigned char *input, unsigned char *output); + int (*_rom_mbedtls_internal_aes_encrypt)(mbedtls_aes_context *ctx, const unsigned char input[16], unsigned char output[16]); + int (*_rom_mbedtls_internal_aes_decrypt)(mbedtls_aes_context *ctx, const unsigned char input[16], unsigned char output[16]); + void (*_rom_mbedtls_unused[MBEDTLS_ROM_ECO4_FUNC_COUNT - MBEDTLS_ROM_ECO4_BOOTLOADER_FUNC_COUNT])(void); +} mbedtls_rom_eco4_funcs_t; + +_Static_assert(sizeof(mbedtls_rom_eco4_funcs_t) == MBEDTLS_ROM_ECO4_FUNC_COUNT * sizeof(void (*)(void)), + "Bootloader ROM function table must cover the full ROM ECO4 table"); /* This structure can be automatically generated by the script with rom.mbedtls.ld. */ +/* Keep the bootloader table the full ROM ECO4 size. The bootloader only fills AES + * entries, but ROM code may index later slots internally; all non-AES entries must + * exist and remain zero-initialized. + */ static const mbedtls_rom_eco4_funcs_t mbedtls_rom_eco4_funcs_table = { /* Fill the ROM functions into mbedtls rom function table. */ /* aes module */ - ._rom_mbedtls_aes_init = mbedtls_aes_init, - ._rom_mbedtls_aes_free = mbedtls_aes_free, - ._rom_mbedtls_aes_setkey_enc = mbedtls_aes_setkey_enc, - ._rom_mbedtls_aes_setkey_dec = mbedtls_aes_setkey_dec, - ._rom_mbedtls_aes_crypt_ecb = mbedtls_aes_crypt_ecb, - ._rom_mbedtls_aes_crypt_cbc = mbedtls_aes_crypt_cbc, - ._rom_mbedtls_internal_aes_encrypt = mbedtls_internal_aes_encrypt, - ._rom_mbedtls_internal_aes_decrypt = mbedtls_internal_aes_decrypt, + ._rom_mbedtls_aes_init = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_aes_init, mbedtls_aes_init), + ._rom_mbedtls_aes_free = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_aes_free, mbedtls_aes_free), + ._rom_mbedtls_aes_setkey_enc = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_aes_setkey_enc, mbedtls_aes_setkey_enc), + ._rom_mbedtls_aes_setkey_dec = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_aes_setkey_dec, mbedtls_aes_setkey_dec), + ._rom_mbedtls_aes_crypt_ecb = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_aes_crypt_ecb, mbedtls_aes_crypt_ecb), + ._rom_mbedtls_aes_crypt_cbc = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_aes_crypt_cbc, mbedtls_aes_crypt_cbc), + ._rom_mbedtls_internal_aes_encrypt = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_internal_aes_encrypt, mbedtls_internal_aes_encrypt), + ._rom_mbedtls_internal_aes_decrypt = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_internal_aes_decrypt, mbedtls_internal_aes_decrypt), - ._rom_mbedtls_aes_xts_init = mbedtls_aes_xts_init, - ._rom_mbedtls_aes_xts_free = mbedtls_aes_xts_free, - ._rom_mbedtls_aes_xts_setkey_enc = mbedtls_aes_xts_setkey_enc, - ._rom_mbedtls_aes_xts_setkey_dec = mbedtls_aes_xts_setkey_dec, - ._rom_mbedtls_aes_crypt_xts = mbedtls_aes_crypt_xts, + ._rom_mbedtls_aes_xts_init = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_aes_xts_init, mbedtls_aes_xts_init), + ._rom_mbedtls_aes_xts_free = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_aes_xts_free, mbedtls_aes_xts_free), + ._rom_mbedtls_aes_xts_setkey_enc = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_aes_xts_setkey_enc, mbedtls_aes_xts_setkey_enc), + ._rom_mbedtls_aes_xts_setkey_dec = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_aes_xts_setkey_dec, mbedtls_aes_xts_setkey_dec), + ._rom_mbedtls_aes_crypt_xts = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_aes_crypt_xts, mbedtls_aes_crypt_xts), + ._rom_mbedtls_aes_crypt_cfb128 = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_aes_crypt_cfb128, mbedtls_aes_crypt_cfb128), + ._rom_mbedtls_aes_crypt_ofb = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_aes_crypt_ofb, mbedtls_aes_crypt_ofb), + ._rom_mbedtls_aes_crypt_ctr = ROM_TABLE_FN(mbedtls_rom_eco4_funcs_t, _rom_mbedtls_aes_crypt_ctr, mbedtls_aes_crypt_ctr), }; void mbedtls_rom_osi_functions_init_bootloader(void) { // /* Export the rom mbedtls functions table pointer */ extern void *mbedtls_rom_osi_funcs_ptr; - - unsigned chip_version = efuse_hal_chip_revision(); - if ( ESP_CHIP_REV_ABOVE(chip_version, 200) ) { - /* Initialize the pointer of rom eco4 mbedtls functions table. */ - mbedtls_rom_osi_funcs_ptr = (mbedtls_rom_eco4_funcs_t *)&mbedtls_rom_eco4_funcs_table; - } else { - /* Initialize the pointer of rom mbedtls functions table. */ - mbedtls_rom_osi_funcs_ptr = (mbedtls_rom_funcs_t *)&mbedtls_rom_funcs_table; - } + /* Initialize the pointer of rom eco4 mbedtls functions table. */ + mbedtls_rom_osi_funcs_ptr = (mbedtls_rom_eco4_funcs_t *)&mbedtls_rom_eco4_funcs_table; } diff --git a/components/mbedtls/port/mbedtls_rom/threading_alt.h b/components/mbedtls/port/mbedtls_rom/threading_alt.h index 6dc3349da1e..36367b98a10 100644 --- a/components/mbedtls/port/mbedtls_rom/threading_alt.h +++ b/components/mbedtls/port/mbedtls_rom/threading_alt.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2024-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -8,15 +8,20 @@ #include "freertos/FreeRTOS.h" #include "freertos/semphr.h" -typedef struct mbedtls_threading_mutex_t { +struct mbedtls_rom_cond_waiter; + +typedef struct mbedtls_platform_mutex_t { SemaphoreHandle_t mutex; /* is_valid is 0 after a failed init or a free, and nonzero after a * successful init. This field is not considered part of the public * API of Mbed TLS and may change without notice. */ char is_valid; -} mbedtls_threading_mutex_t; +} mbedtls_platform_mutex_t; -extern void mbedtls_threading_set_alt(void (*mutex_init)(mbedtls_threading_mutex_t *), - void (*mutex_free)(mbedtls_threading_mutex_t *), - int (*mutex_lock)(mbedtls_threading_mutex_t *), - int (*mutex_unlock)(mbedtls_threading_mutex_t *)); +typedef struct mbedtls_platform_condition_variable_t { + /* Protects the waiter list below. */ + SemaphoreHandle_t mutex; + /* Intrusive list of threads currently blocked on this condition variable. */ + struct mbedtls_rom_cond_waiter *waiters; + char is_valid; +} mbedtls_platform_condition_variable_t; diff --git a/components/mbedtls/port/psa_driver/esp_aes/psa_crypto_driver_esp_aes_gcm.c b/components/mbedtls/port/psa_driver/esp_aes/psa_crypto_driver_esp_aes_gcm.c index 59e3b9cb665..028c5a72e60 100644 --- a/components/mbedtls/port/psa_driver/esp_aes/psa_crypto_driver_esp_aes_gcm.c +++ b/components/mbedtls/port/psa_driver/esp_aes/psa_crypto_driver_esp_aes_gcm.c @@ -50,6 +50,7 @@ static psa_status_t esp_crypto_aes_gcm_setup( status = mbedtls_to_psa_error(esp_aes_gcm_setkey(ctx, 2, key_buffer, key_buffer_size * 8)); if (status != PSA_SUCCESS) { + esp_aes_gcm_free(ctx); free(ctx); goto exit; } diff --git a/components/mbedtls/port/psa_driver/esp_ecdsa/psa_crypto_driver_esp_ecdsa.c b/components/mbedtls/port/psa_driver/esp_ecdsa/psa_crypto_driver_esp_ecdsa.c index 01613cd84c4..eed84dce00d 100644 --- a/components/mbedtls/port/psa_driver/esp_ecdsa/psa_crypto_driver_esp_ecdsa.c +++ b/components/mbedtls/port/psa_driver/esp_ecdsa/psa_crypto_driver_esp_ecdsa.c @@ -14,7 +14,11 @@ #include "soc/soc_caps.h" #include "esp_log.h" +#include "mbedtls/ecp.h" +#include "mbedtls/bignum.h" + #include "esp_assert.h" +#include "esp_fault.h" #include "esp_crypto_lock.h" #include "esp_crypto_periph_clk.h" @@ -312,6 +316,70 @@ static psa_status_t validate_ecdsa_sha_alg(psa_algorithm_t alg, const esp_ecdsa_ } #if SOC_ECDSA_SUPPORTED + +static mbedtls_ecp_group_id ecdsa_curve_to_mbedtls_group(esp_ecdsa_curve_t curve) +{ + switch (curve) { + case ESP_ECDSA_CURVE_SECP256R1: return MBEDTLS_ECP_DP_SECP256R1; +#if SOC_ECDSA_SUPPORT_CURVE_P384 + case ESP_ECDSA_CURVE_SECP384R1: return MBEDTLS_ECP_DP_SECP384R1; +#endif + default: return MBEDTLS_ECP_DP_NONE; + } +} + +static psa_status_t check_ecdsa_signature_range(const uint8_t *signature, size_t key_len, + esp_ecdsa_curve_t curve) +{ + mbedtls_ecp_group_id grp_id = ecdsa_curve_to_mbedtls_group(curve); + if (grp_id == MBEDTLS_ECP_DP_NONE) { + return PSA_ERROR_NOT_SUPPORTED; + } + + mbedtls_ecp_group grp; + mbedtls_mpi r, s; + mbedtls_ecp_group_init(&grp); + mbedtls_mpi_init(&r); + mbedtls_mpi_init(&s); + + psa_status_t status = PSA_ERROR_INVALID_SIGNATURE; + + if (mbedtls_ecp_group_load(&grp, grp_id) != 0) { + status = PSA_ERROR_GENERIC_ERROR; + goto cleanup; + } + if (mbedtls_mpi_read_binary(&r, signature, key_len) != 0 || + mbedtls_mpi_read_binary(&s, signature + key_len, key_len) != 0) { + status = PSA_ERROR_GENERIC_ERROR; + goto cleanup; + } + + /* 1 <= scalar <= n-1: that is, scalar > 0 and scalar < n. */ + #define RANGE_OK 0x6A6A6A6AU + #define RANGE_FAIL 0x95959595U + volatile uint32_t verdict = RANGE_FAIL; + if (mbedtls_mpi_cmp_int(&r, 0) > 0 && + mbedtls_mpi_cmp_mpi(&r, &grp.N) < 0 && + mbedtls_mpi_cmp_int(&s, 0) > 0 && + mbedtls_mpi_cmp_mpi(&s, &grp.N) < 0) { + verdict = RANGE_OK; + } + if (verdict != RANGE_OK) { + goto cleanup; + } + ESP_FAULT_ASSERT(verdict == RANGE_OK); + #undef RANGE_OK + #undef RANGE_FAIL + + status = PSA_SUCCESS; + +cleanup: + mbedtls_mpi_free(&r); + mbedtls_mpi_free(&s); + mbedtls_ecp_group_free(&grp); + return status; +} + static void esp_ecdsa_acquire_hardware(void) { esp_crypto_ecdsa_lock_acquire(); @@ -409,6 +477,11 @@ psa_status_t esp_ecdsa_transparent_verify_hash_start( return PSA_ERROR_INVALID_SIGNATURE; } + status = check_ecdsa_signature_range(signature, key_len, curve); + if (status != PSA_SUCCESS) { + return status; + } + const uint8_t *public_key_buffer = NULL; size_t public_key_buffer_size = 0; uint8_t public_key[2 * MAX_ECDSA_COMPONENT_LEN + 1]; @@ -499,6 +572,8 @@ psa_status_t esp_ecdsa_transparent_verify_hash_complete(esp_ecdsa_transparent_ve return PSA_ERROR_INVALID_SIGNATURE; } + ESP_FAULT_ASSERT(ret == 0); + return PSA_SUCCESS; } diff --git a/components/mbedtls/port/psa_driver/esp_mac/psa_crypto_driver_esp_cmac.c b/components/mbedtls/port/psa_driver/esp_mac/psa_crypto_driver_esp_cmac.c index bd3f8bde0e4..0eaa6d1c1e5 100644 --- a/components/mbedtls/port/psa_driver/esp_mac/psa_crypto_driver_esp_cmac.c +++ b/components/mbedtls/port/psa_driver/esp_mac/psa_crypto_driver_esp_cmac.c @@ -392,12 +392,11 @@ psa_status_t esp_cmac_verify_finish( status = esp_cmac_finish(esp_cmac_ctx, actual_mac, sizeof(actual_mac), &actual_mac_length); if (status == PSA_SUCCESS) { - if (memcmp(actual_mac, mac, mac_length) == 0) { - return PSA_SUCCESS; - } else { - return PSA_ERROR_INVALID_SIGNATURE; + if (mbedtls_ct_memcmp(actual_mac, mac, mac_length) != 0) { + status = PSA_ERROR_INVALID_SIGNATURE; } } + mbedtls_platform_zeroize(actual_mac, sizeof(actual_mac)); return status; } diff --git a/components/mbedtls/port/psa_driver/esp_mac/psa_crypto_driver_esp_hmac_opaque.c b/components/mbedtls/port/psa_driver/esp_mac/psa_crypto_driver_esp_hmac_opaque.c index 6bf29357d27..db6185a6505 100644 --- a/components/mbedtls/port/psa_driver/esp_mac/psa_crypto_driver_esp_hmac_opaque.c +++ b/components/mbedtls/port/psa_driver/esp_mac/psa_crypto_driver_esp_hmac_opaque.c @@ -7,6 +7,7 @@ #include #include #include "psa/crypto.h" +#include "mbedtls/constant_time.h" #include "psa_crypto_driver_esp_hmac_opaque.h" #include "psa_crypto_driver_esp_opaque_common.h" #include "esp_efuse.h" @@ -219,7 +220,7 @@ psa_status_t esp_hmac_import_key_opaque( psa_status_t esp_hmac_abort_opaque(esp_hmac_opaque_operation_t *esp_hmac_ctx) { - if (!esp_hmac_ctx) { + if (esp_hmac_ctx != NULL) { mbedtls_platform_zeroize(esp_hmac_ctx, sizeof(esp_hmac_opaque_operation_t)); } return PSA_SUCCESS; @@ -404,15 +405,14 @@ psa_status_t esp_hmac_verify_finish_opaque( size_t actual_mac_length = 0; status = esp_hmac_finish_opaque(esp_hmac_ctx, actual_mac, sizeof(actual_mac), &actual_mac_length); - if (status != PSA_SUCCESS) { - return status; + if (status == PSA_SUCCESS) { + if (mbedtls_ct_memcmp(mac, actual_mac, mac_length) != 0) { + status = PSA_ERROR_INVALID_SIGNATURE; + } } - if (memcmp(mac, actual_mac, mac_length) != 0) { - return PSA_ERROR_INVALID_SIGNATURE; - } - - return PSA_SUCCESS; + mbedtls_platform_zeroize(actual_mac, sizeof(actual_mac)); + return status; } size_t esp_hmac_opaque_size_function( diff --git a/components/mbedtls/port/psa_driver/esp_mac/psa_crypto_driver_esp_hmac_transparent.c b/components/mbedtls/port/psa_driver/esp_mac/psa_crypto_driver_esp_hmac_transparent.c index 1ec7c8ea877..01bb37963d5 100644 --- a/components/mbedtls/port/psa_driver/esp_mac/psa_crypto_driver_esp_hmac_transparent.c +++ b/components/mbedtls/port/psa_driver/esp_mac/psa_crypto_driver_esp_hmac_transparent.c @@ -7,6 +7,7 @@ #include #include #include "psa/crypto.h" +#include "mbedtls/constant_time.h" #include "psa_crypto_driver_esp_hmac_transparent.h" #include "psa_crypto_driver_esp_sha.h" #include "psa_crypto_driver_esp_md5.h" @@ -16,12 +17,12 @@ psa_status_t esp_hmac_abort_transparent(esp_hmac_transparent_operation_t *esp_hm { psa_status_t status = PSA_ERROR_CORRUPTION_DETECTED; -#if CONFIG_MBEDTLS_ROM_MD5 +#if defined(ESP_MD5_DRIVER_ENABLED) psa_algorithm_t hash_alg = PSA_ALG_GET_HASH(esp_hmac_ctx->alg); if (hash_alg == PSA_ALG_MD5) { status = esp_md5_hash_abort(&esp_hmac_ctx->md5_ctx); } else -#endif // CONFIG_MBEDTLS_ROM_MD5 +#endif // defined(ESP_MD5_DRIVER_ENABLED) { status = esp_sha_hash_abort(&esp_hmac_ctx->esp_sha_ctx); } @@ -68,9 +69,9 @@ psa_status_t esp_hmac_setup_transparent(esp_hmac_transparent_operation_t *esp_hm memset(esp_hmac_ctx->opad, 0, PSA_HMAC_MAX_HASH_BLOCK_SIZE); if ( -#if CONFIG_MBEDTLS_ROM_MD5 +#if defined(ESP_MD5_DRIVER_ENABLED) hash_alg != PSA_ALG_MD5 && -#endif // CONFIG_MBEDTLS_ROM_MD5 +#endif // defined(ESP_MD5_DRIVER_ENABLED) (hash_alg < PSA_ALG_SHA_1 #if SOC_SHA_SUPPORT_SHA512 || hash_alg > PSA_ALG_SHA_512 @@ -95,12 +96,12 @@ psa_status_t esp_hmac_setup_transparent(esp_hmac_transparent_operation_t *esp_hm } if (key_buffer_size > block_size) { -#if CONFIG_MBEDTLS_ROM_MD5 +#if defined(ESP_MD5_DRIVER_ENABLED) if (hash_alg == PSA_ALG_MD5) { status = esp_md5_hash_compute(hash_alg, key_buffer, key_buffer_size, ipad, sizeof(ipad), &key_buffer_size); } else -#endif // CONFIG_MBEDTLS_ROM_MD5 +#endif // defined(ESP_MD5_DRIVER_ENABLED) { status = esp_sha_hash_compute(hash_alg, key_buffer, key_buffer_size, ipad, sizeof(ipad), &key_buffer_size); @@ -163,11 +164,11 @@ psa_status_t esp_hmac_setup_transparent(esp_hmac_transparent_operation_t *esp_hm memset(esp_hmac_ctx->opad + key_buffer_size, 0x5C, fill_size); } -#if CONFIG_MBEDTLS_ROM_MD5 +#if defined(ESP_MD5_DRIVER_ENABLED) if (hash_alg == PSA_ALG_MD5) { status = esp_md5_hash_setup(&esp_hmac_ctx->md5_ctx, hash_alg); } else -#endif // CONFIG_MBEDTLS_ROM_MD5 +#endif // defined(ESP_MD5_DRIVER_ENABLED) { status = esp_sha_hash_setup(&esp_hmac_ctx->esp_sha_ctx, hash_alg); } @@ -175,11 +176,11 @@ psa_status_t esp_hmac_setup_transparent(esp_hmac_transparent_operation_t *esp_hm goto error; } -#if CONFIG_MBEDTLS_ROM_MD5 +#if defined(ESP_MD5_DRIVER_ENABLED) if (hash_alg == PSA_ALG_MD5) { status = esp_md5_hash_update(&esp_hmac_ctx->md5_ctx, ipad, block_size); } else -#endif // CONFIG_MBEDTLS_ROM_MD5 +#endif // defined(ESP_MD5_DRIVER_ENABLED) { status = esp_sha_hash_update(&esp_hmac_ctx->esp_sha_ctx, ipad, block_size); } @@ -201,12 +202,12 @@ psa_status_t esp_hmac_update_transparent(esp_hmac_transparent_operation_t *esp_h return PSA_ERROR_INVALID_ARGUMENT; } -#if CONFIG_MBEDTLS_ROM_MD5 +#if defined(ESP_MD5_DRIVER_ENABLED) psa_algorithm_t hash_alg = PSA_ALG_GET_HASH(esp_hmac_ctx->alg); if (hash_alg == PSA_ALG_MD5) { return esp_md5_hash_update(&esp_hmac_ctx->md5_ctx, data, data_length); } else -#endif // CONFIG_MBEDTLS_ROM_MD5 +#endif // defined(ESP_MD5_DRIVER_ENABLED) { return esp_sha_hash_update(&esp_hmac_ctx->esp_sha_ctx, data, data_length); } @@ -230,11 +231,11 @@ psa_status_t esp_hmac_finish_transparent( size_t hash_size = 0; size_t block_size = PSA_HASH_BLOCK_LENGTH(hash_alg); -#if CONFIG_MBEDTLS_ROM_MD5 +#if defined(ESP_MD5_DRIVER_ENABLED) if (hash_alg == PSA_ALG_MD5) { status = esp_md5_hash_finish(&esp_hmac_ctx->md5_ctx, tmp, sizeof(tmp), &hash_size); } else -#endif // CONFIG_MBEDTLS_ROM_MD5 +#endif // defined(ESP_MD5_DRIVER_ENABLED) { status = esp_sha_hash_finish(&esp_hmac_ctx->esp_sha_ctx, tmp, sizeof(tmp), &hash_size); } @@ -243,11 +244,11 @@ psa_status_t esp_hmac_finish_transparent( } /* From here on, tmp needs to be wiped. */ -#if CONFIG_MBEDTLS_ROM_MD5 +#if defined(ESP_MD5_DRIVER_ENABLED) if (hash_alg == PSA_ALG_MD5) { status = esp_md5_hash_setup(&esp_hmac_ctx->md5_ctx, hash_alg); } else -#endif // CONFIG_MBEDTLS_ROM_MD5 +#endif // defined(ESP_MD5_DRIVER_ENABLED) { status = esp_sha_hash_setup(&esp_hmac_ctx->esp_sha_ctx, hash_alg); } @@ -255,11 +256,11 @@ psa_status_t esp_hmac_finish_transparent( goto exit; } -#if CONFIG_MBEDTLS_ROM_MD5 +#if defined(ESP_MD5_DRIVER_ENABLED) if (hash_alg == PSA_ALG_MD5) { status = esp_md5_hash_update(&esp_hmac_ctx->md5_ctx, esp_hmac_ctx->opad, block_size); } else -#endif // CONFIG_MBEDTLS_ROM_MD5 +#endif // defined(ESP_MD5_DRIVER_ENABLED) { status = esp_sha_hash_update(&esp_hmac_ctx->esp_sha_ctx, esp_hmac_ctx->opad, block_size); } @@ -267,11 +268,11 @@ psa_status_t esp_hmac_finish_transparent( goto exit; } -#if CONFIG_MBEDTLS_ROM_MD5 +#if defined(ESP_MD5_DRIVER_ENABLED) if (hash_alg == PSA_ALG_MD5) { status = esp_md5_hash_update(&esp_hmac_ctx->md5_ctx, tmp, hash_size); } else -#endif // CONFIG_MBEDTLS_ROM_MD5 +#endif // defined(ESP_MD5_DRIVER_ENABLED) { status = esp_sha_hash_update(&esp_hmac_ctx->esp_sha_ctx, tmp, hash_size); } @@ -279,11 +280,11 @@ psa_status_t esp_hmac_finish_transparent( goto exit; } -#if CONFIG_MBEDTLS_ROM_MD5 +#if defined(ESP_MD5_DRIVER_ENABLED) if (hash_alg == PSA_ALG_MD5) { status = esp_md5_hash_finish(&esp_hmac_ctx->md5_ctx, tmp, sizeof(tmp), &hash_size); } else -#endif // CONFIG_MBEDTLS_ROM_MD5 +#endif // defined(ESP_MD5_DRIVER_ENABLED) { status = esp_sha_hash_finish(&esp_hmac_ctx->esp_sha_ctx, tmp, sizeof(tmp), &hash_size); } @@ -364,7 +365,7 @@ psa_status_t esp_hmac_verify_finish_transparent( status = esp_hmac_finish_transparent(esp_hmac_ctx, actual_mac, sizeof(actual_mac), &actual_mac_length); if (status == PSA_SUCCESS) { - if (memcmp(actual_mac, mac, mac_length) != 0) { + if (mbedtls_ct_memcmp(actual_mac, mac, mac_length) != 0) { status = PSA_ERROR_INVALID_SIGNATURE; } } diff --git a/components/mbedtls/port/psa_driver/esp_rsa_ds/psa_crypto_driver_esp_rsa_ds.c b/components/mbedtls/port/psa_driver/esp_rsa_ds/psa_crypto_driver_esp_rsa_ds.c index 44d8c8c97dc..b91da62ac48 100644 --- a/components/mbedtls/port/psa_driver/esp_rsa_ds/psa_crypto_driver_esp_rsa_ds.c +++ b/components/mbedtls/port/psa_driver/esp_rsa_ds/psa_crypto_driver_esp_rsa_ds.c @@ -19,6 +19,8 @@ #include "esp_assert.h" #include "soc/soc_caps.h" +#include "mbedtls/platform_util.h" + #if SOC_KEY_MANAGER_SUPPORTED #include "esp_key_mgr.h" #endif /* SOC_KEY_MANAGER_SUPPORTED */ @@ -500,6 +502,7 @@ psa_status_t esp_rsa_ds_opaque_sign_hash_start( error: if (em) { + mbedtls_platform_zeroize(em, rsa_len_bytes); heap_caps_free(em); em = NULL; } @@ -830,6 +833,7 @@ psa_status_t esp_rsa_ds_opaque_asymmetric_decrypt( err = esp_key_mgr_activate_key(km_ri); if (err != ESP_OK) { ESP_LOGE(TAG, "Failed to activate key: 0x%x", err); + mbedtls_platform_zeroize(em_words, sizeof(uint32_t) * data_len); heap_caps_free(em_words); esp_rsa_ds_release_ds_lock(); return PSA_ERROR_INVALID_HANDLE; @@ -844,6 +848,7 @@ psa_status_t esp_rsa_ds_opaque_asymmetric_decrypt( hmac_key_id, &ds_ctx); if (err != ESP_OK) { + mbedtls_platform_zeroize(em_words, sizeof(uint32_t) * data_len); heap_caps_free(em_words); #if SOC_KEY_MANAGER_SUPPORTED if (is_km_key_active) { @@ -863,6 +868,7 @@ psa_status_t esp_rsa_ds_opaque_asymmetric_decrypt( #endif /* SOC_KEY_MANAGER_SUPPORTED */ if (err != ESP_OK) { + mbedtls_platform_zeroize(em_words, sizeof(uint32_t) * data_len); heap_caps_free(em_words); esp_rsa_ds_release_ds_lock(); return PSA_ERROR_GENERIC_ERROR; diff --git a/components/mbedtls/port/psa_driver/esp_rsa_ds/psa_crypto_driver_esp_rsa_ds_utilities.c b/components/mbedtls/port/psa_driver/esp_rsa_ds/psa_crypto_driver_esp_rsa_ds_utilities.c index 270a3fda127..fc19cfce2c3 100644 --- a/components/mbedtls/port/psa_driver/esp_rsa_ds/psa_crypto_driver_esp_rsa_ds_utilities.c +++ b/components/mbedtls/port/psa_driver/esp_rsa_ds/psa_crypto_driver_esp_rsa_ds_utilities.c @@ -9,6 +9,8 @@ #include "mbedtls/asn1.h" #include "mbedtls/psa_util.h" #include "esp_log.h" +#include "mbedtls/constant_time.h" +#include "constant_time_internal.h" typedef struct { psa_algorithm_t md_alg; @@ -167,63 +169,91 @@ psa_status_t esp_rsa_ds_pad_v15_unpad(unsigned char *input, size_t output_max_len, size_t *olen) { + /* This implementation mirrors mbedtls_ct_rsaes_pkcs1_v15_unpadding() + * in upstream rsa.c. Below the public-input length check, every + * operation must be constant-time w.r.t. the plaintext contents, + * the position of the 0x00 separator, and padding validity. Failing + * that opens a Bleichenbacher-style padding oracle. The function is + * deliberately longer than the project guideline because each step + * carries an audit comment that has to survive intact. */ + + /* ilen and output_max_len are public; this branch is safe. */ if (ilen < MIN_V15_PADDING_LEN) { return PSA_ERROR_INVALID_ARGUMENT; } - - unsigned char bad = 0; - size_t msg_len = 0; - size_t msg_max_len = 0; - unsigned char pad_done = 0; +#if defined(MBEDTLS_PKCS1_V15) && defined(MBEDTLS_RSA_C) size_t pad_count = 0; + size_t plaintext_size = 0; + size_t plaintext_max_size; + mbedtls_ct_condition_t bad; + mbedtls_ct_condition_t pad_done; + mbedtls_ct_condition_t output_too_large; - msg_max_len = (output_max_len > ilen - MIN_V15_PADDING_LEN) ? ilen - MIN_V15_PADDING_LEN : output_max_len; + plaintext_max_size = (output_max_len > ilen - MIN_V15_PADDING_LEN) + ? ilen - MIN_V15_PADDING_LEN : output_max_len; - /* Check the first byte (0x00) */ - bad |= input[0]; + /* EME-PKCS1-v1_5: 0x00 || 0x02 || PS (>= 8 non-zero) || 0x00 || M */ + bad = mbedtls_ct_bool(input[0]); + bad = mbedtls_ct_bool_or(bad, mbedtls_ct_uint_ne(input[1], 2 /* MBEDTLS_RSA_CRYPT */)); - /* Check the padding type */ - bad |= input[1] ^ 2; // MBEDTLS_RSA_CRYPT; - - /* Scan for separator (0x00) and count padding bytes in constant time */ + /* Scan the full buffer; pad_done latches at the first 0x00 found. */ + pad_done = MBEDTLS_CT_FALSE; for (size_t i = 2; i < ilen; i++) { - unsigned char found = (input[i] == 0x00); - pad_done = pad_done | found; - pad_count += (pad_done == 0) ? 1 : 0; + mbedtls_ct_condition_t found = mbedtls_ct_uint_eq(input[i], 0); + pad_done = mbedtls_ct_bool_or(pad_done, found); + pad_count += mbedtls_ct_uint_if_else_0(mbedtls_ct_bool_not(pad_done), 1); } - /* Check if we found a separator and padding is long enough */ - bad |= (pad_done == 0); /* No separator found */ - bad |= (pad_count < 8); /* Padding too short (need at least 8 non-zero bytes) */ + /* No separator found, or PS too short. */ + bad = mbedtls_ct_bool_or(bad, mbedtls_ct_bool_not(pad_done)); + bad = mbedtls_ct_bool_or(bad, mbedtls_ct_uint_gt(8, pad_count)); - /* Calculate message length */ - msg_len = ilen - pad_count - 3; + /* If invalid, substitute plaintext_max_size so the remaining cache + * and timing trace matches the good case. */ + plaintext_size = mbedtls_ct_uint_if(bad, + (unsigned) plaintext_max_size, + (unsigned) (ilen - pad_count - 3)); + output_too_large = mbedtls_ct_uint_gt(plaintext_size, plaintext_max_size); + plaintext_size = mbedtls_ct_uint_if(output_too_large, + (unsigned) plaintext_max_size, + (unsigned) plaintext_size); - /* Check if separator is not at the very end */ - bad |= (msg_len > output_max_len); - if (bad) { - msg_len = msg_max_len; + /* On any failure path (bad padding, or plaintext doesn't fit) zero + * the post-header region of `input` BEFORE the memmove_left + memcpy + * below. Those two operations execute unconditionally to keep the + * memory access trace fixed; this step ensures they propagate zeros + * rather than a failed-decryption plaintext attempt into the + * caller-visible output buffer. Mirrors upstream rsa.c. */ + mbedtls_ct_zeroize_if(mbedtls_ct_bool_or(bad, output_too_large), + input + MIN_V15_PADDING_LEN, + ilen - MIN_V15_PADDING_LEN); + + /* Slide the plaintext to a fixed in-buffer position, then read + * from that fixed position. The slide is CT in the secret offset. */ + mbedtls_ct_memmove_left(input + ilen - plaintext_max_size, + plaintext_max_size, + plaintext_max_size - plaintext_size); + if (output_max_len != 0) { + /* memmove handles input/output aliasing (callers may pass the + * same buffer for both). The length is the public bound, so + * the access pattern reveals nothing secret. */ + memmove(output, input + ilen - plaintext_max_size, plaintext_max_size); } - /* Verify padding bytes are non-zero in constant time */ -#if defined(__clang__) && defined(__xtensa__) - #pragma clang loop vectorize(disable) -#endif - for (size_t i = 2; i < ilen; i++) { - unsigned char in_padding = (i < pad_count + 2); - unsigned char is_zero = (input[i] == 0x00); - bad |= in_padding & is_zero; - } + *olen = plaintext_size; - if (bad) { - return PSA_ERROR_INVALID_ARGUMENT; - } - - *olen = msg_len; - if (*olen > 0) { - memcpy(output, input + ilen - msg_len, msg_len); - } - return PSA_SUCCESS; + /* Collapse both error conditions into the single status we already + * return (PSA_ERROR_INVALID_ARGUMENT); distinguishing them would + * give a Bleichenbacher attacker a finer oracle. */ + return (psa_status_t) mbedtls_ct_error_if_else_0( + mbedtls_ct_bool_or(bad, output_too_large), + PSA_ERROR_INVALID_ARGUMENT); +#else + /* PKCS#1 v1.5 padding is not configured; the driver should not be + * dispatched for this algorithm in the first place. */ + (void) input; (void) output; (void) output_max_len; (void) olen; + return PSA_ERROR_NOT_SUPPORTED; +#endif /* MBEDTLS_PKCS1_V15 && MBEDTLS_RSA_C */ } #if CONFIG_MBEDTLS_SSL_PROTO_TLS1_3 @@ -417,81 +447,91 @@ psa_status_t esp_rsa_ds_pad_oaep_unpad(unsigned char *input, size_t *olen, psa_algorithm_t hash_alg) { + /* This mirrors mbedtls_rsa_rsaes_oaep_decrypt() in upstream rsa.c. + * Below the public-input sanity check, the unpadding scan operates + * only through mbedtls_ct primitives, so its time and memory trace + * depend solely on ilen and hash_alg. The single branch on `bad` + * at the end leaks no more than the return value already does, + * which matches the upstream design and is acceptable for OAEP + * (the relevant side-channel attack relies on distinguishing + * failure modes, not on timing the success path). */ + unsigned int hlen = PSA_HASH_LENGTH(hash_alg); - unsigned char bad = 0; - size_t msg_len = 0; - - /* Validate input length */ - bad |= (ilen < 2 * hlen + 2); - - /* Apply MGF masks */ - bad |= esp_rsa_ds_mgf_mask(input + 1, hlen, input + hlen + 1, ilen - hlen - 1, hash_alg) != PSA_SUCCESS; - - bad |= esp_rsa_ds_mgf_mask(input + hlen + 1, ilen - hlen - 1, input + 1, hlen, hash_alg) != PSA_SUCCESS; - - /* Check first byte (should be 0x00) */ - bad |= input[0]; - - /* Skip the first byte and maskSeed */ - unsigned char *db = input + 1 + hlen; - size_t db_len = ilen - hlen - 1; - - /* Compute hash, label is NULL and label_len is 0 */ + mbedtls_ct_condition_t bad; + mbedtls_ct_condition_t in_padding; + size_t pad_len; + unsigned char *p; unsigned char lhash[PSA_HASH_MAX_SIZE]; - memset(lhash, 0, sizeof(lhash)); + + /* All lengths checked here are public. */ + if (hlen == 0 || hlen > PSA_HASH_MAX_SIZE || ilen < 2 * hlen + 2) { + return PSA_ERROR_INVALID_ARGUMENT; + } + + /* Unmask seed and DB. A failure here is an internal hash error, + * not a ciphertext-dependent condition, so an early return is + * safe. */ + if (esp_rsa_ds_mgf_mask(input + 1, hlen, + input + hlen + 1, ilen - hlen - 1, hash_alg) != PSA_SUCCESS || + esp_rsa_ds_mgf_mask(input + hlen + 1, ilen - hlen - 1, + input + 1, hlen, hash_alg) != PSA_SUCCESS) { + return PSA_ERROR_INVALID_ARGUMENT; + } + + /* lHash of the empty label, recomputed each call. */ size_t lhen = 0; - bad |= psa_hash_compute(hash_alg, NULL, 0, lhash, sizeof(lhash), &lhen) != PSA_SUCCESS; - - bad |= (lhen != hlen); - - /* Verify hash portion of db against lhash */ - for (size_t i = 0; i < hlen && i < db_len; i++) { - bad |= db[i] ^ lhash[i]; + if (psa_hash_compute(hash_alg, NULL, 0, lhash, sizeof(lhash), &lhen) != PSA_SUCCESS + || lhen != hlen) { + mbedtls_platform_zeroize(lhash, sizeof(lhash)); + return PSA_ERROR_INVALID_ARGUMENT; } - /* Skip past lhash in DB */ - unsigned char *p = db + hlen; - size_t remaining = db_len - hlen; + /* Constant-time padding check. */ + p = input; + bad = mbedtls_ct_bool(*p++); /* First byte must be 0x00 */ + p += hlen; /* Skip seed */ + bad = mbedtls_ct_bool_or(bad, + mbedtls_ct_bool(mbedtls_ct_memcmp(lhash, p, hlen))); + p += hlen; - /* - * Scan PS || 0x01 || M - */ - unsigned char seen_one = 0; - size_t msg_index = 0; - - for (size_t i = 0; i < remaining; i++) { - unsigned char is_zero = (p[i] == 0); - unsigned char is_one = (p[i] == 1); - - /* Before delimiter, only 0x00 allowed */ - bad |= (seen_one == 0) & !(is_zero | is_one); - - /* Record first 0x01 */ - msg_index |= (seen_one == 0 && is_one) * i; - seen_one |= is_one; + /* Count leading zeros in DB (between lHash and the 0x01 delimiter). + * The loop scans the full DB region every time; the latch via + * in_padding keeps pad_len from incrementing once a non-zero byte + * is seen. */ + pad_len = 0; + in_padding = MBEDTLS_CT_TRUE; + for (size_t i = 0; i < ilen - 2 * hlen - 2; i++) { + in_padding = mbedtls_ct_bool_and(in_padding, mbedtls_ct_uint_eq(p[i], 0)); + pad_len += mbedtls_ct_uint_if_else_0(in_padding, 1); } + p += pad_len; + bad = mbedtls_ct_bool_or(bad, mbedtls_ct_uint_ne(*p++, 0x01)); - /* Must see exactly one delimiter */ - bad |= (seen_one == 0); + mbedtls_platform_zeroize(lhash, sizeof(lhash)); - /* Calculate message length */ - msg_len = remaining - msg_index - 1; - bad |= (msg_len == 0); - - /* Check output buffer size */ - bad |= (output_max_len < msg_len); - - if (bad) { + /* Single decision point on the accumulated bit. */ + if (bad != MBEDTLS_CT_FALSE) { *olen = 0; return PSA_ERROR_INVALID_ARGUMENT; } - /* Copy message in constant time */ - *olen = msg_len; - if (*olen > 0) { - memcpy(output, p + msg_index + 1, msg_len); + /* Padding is valid; from here the plaintext length is no longer + * secret (it is returned to the caller via *olen). */ + size_t plaintext_size = ilen - (size_t) (p - input); + if (plaintext_size > output_max_len) { + *olen = 0; + return PSA_ERROR_INVALID_ARGUMENT; } + *olen = plaintext_size; + if (plaintext_size != 0) { + /* memmove (not memcpy): the driver's caller aliases input and + * output to the same buffer, so the source range [p, p+plaintext_size) + * overlaps the destination range [output, output+plaintext_size). + * Both pointers and the length are public, so memmove's access + * pattern stays fixed by public values -- CT property preserved. */ + memmove(output, p, plaintext_size); + } return PSA_SUCCESS; } #endif /* CONFIG_MBEDTLS_SSL_PROTO_TLS1_3 */ diff --git a/components/mbedtls/port/psa_driver/esp_sha/psa_crypto_driver_esp_sha.c b/components/mbedtls/port/psa_driver/esp_sha/psa_crypto_driver_esp_sha.c index e3d4cf5aba0..8c3feda61d4 100644 --- a/components/mbedtls/port/psa_driver/esp_sha/psa_crypto_driver_esp_sha.c +++ b/components/mbedtls/port/psa_driver/esp_sha/psa_crypto_driver_esp_sha.c @@ -12,6 +12,7 @@ #include "include/psa_crypto_driver_esp_sha512.h" #include "psa/crypto.h" #include "psa/crypto_sizes.h" +#include "mbedtls/platform_util.h" #include "esp_log.h" #include "esp_heap_caps.h" @@ -229,6 +230,7 @@ psa_status_t esp_sha_hash_finish( if (operation->sha_type == ESP_SHA_OPERATION_TYPE_SHA1) { esp_sha1_context *ctx = (esp_sha1_context *)operation->sha_ctx; int ret = esp_sha1_driver_finish(ctx, hash, hash_size, hash_length); + mbedtls_platform_zeroize(ctx, sizeof(esp_sha1_context)); free(ctx); // Free the context after use operation->sha_ctx = NULL; return ret; @@ -239,6 +241,7 @@ psa_status_t esp_sha_hash_finish( operation->sha_type == ESP_SHA_OPERATION_TYPE_SHA224) { esp_sha256_context *ctx = (esp_sha256_context *)operation->sha_ctx; int ret = esp_sha256_driver_finish(ctx, hash, hash_size, hash_length, operation->sha_type); + mbedtls_platform_zeroize(ctx, sizeof(esp_sha256_context)); free(ctx); // Free the context after use operation->sha_ctx = NULL; return ret; @@ -249,6 +252,7 @@ psa_status_t esp_sha_hash_finish( operation->sha_type == ESP_SHA_OPERATION_TYPE_SHA512) { esp_sha512_context *ctx = (esp_sha512_context *)operation->sha_ctx; int ret = esp_sha512_driver_finish(ctx, hash, hash_size, hash_length, operation->sha_type); + mbedtls_platform_zeroize(ctx, sizeof(esp_sha512_context)); free(ctx); // Free the context after use operation->sha_ctx = NULL; return ret; diff --git a/components/mbedtls/port/sha/core/sha.c b/components/mbedtls/port/sha/core/sha.c index 2e63b155df7..8544ddddb80 100644 --- a/components/mbedtls/port/sha/core/sha.c +++ b/components/mbedtls/port/sha/core/sha.c @@ -27,6 +27,7 @@ #include "esp_crypto_dma.h" #include "esp_heap_caps.h" #include "hal/dma_types.h" +#include "mbedtls/platform_util.h" #include "soc/ext_mem_defs.h" #include "soc/periph_defs.h" @@ -186,6 +187,10 @@ static esp_err_t esp_sha_dma_process_ext(esp_sha_type sha_type, const void *inpu buf_copy = heap_caps_aligned_alloc(SOC_GDMA_EXT_MEM_ENC_ALIGNMENT, buf_len, heap_caps); if (buf_copy == NULL) { ESP_LOGE(TAG, "Failed to allocate aligned internal memory"); + if (input_copy) { + mbedtls_platform_zeroize(input_copy, ilen); + free(input_copy); + } return ret; } memcpy(buf_copy, buf, buf_len); @@ -197,10 +202,12 @@ static esp_err_t esp_sha_dma_process_ext(esp_sha_type sha_type, const void *inpu ret = esp_sha_dma_process(sha_type, dma_input, ilen, dma_buf, buf_len, is_first_block); if (realloc_input) { + mbedtls_platform_zeroize(input_copy, ilen); free(input_copy); } if (realloc_buf) { + mbedtls_platform_zeroize(buf_copy, buf_len); free(buf_copy); } @@ -318,6 +325,7 @@ int esp_sha_dma(esp_sha_type sha_type, const void *input, uint32_t ilen, { int ret = 0; unsigned char *dma_cap_buf = NULL; + uint32_t dma_cap_buf_len = 0; if (buf_len > block_length(sha_type)) { ESP_LOGE(TAG, "SHA DMA buf_len cannot exceed max size for a single block"); @@ -339,6 +347,7 @@ int esp_sha_dma(esp_sha_type sha_type, const void *input, uint32_t ilen, goto cleanup; } memcpy(dma_cap_buf, buf, buf_len); + dma_cap_buf_len = buf_len; buf = dma_cap_buf; } @@ -375,7 +384,10 @@ int esp_sha_dma(esp_sha_type sha_type, const void *input, uint32_t ilen, } cleanup: - free(dma_cap_buf); + if (dma_cap_buf) { + mbedtls_platform_zeroize(dma_cap_buf, dma_cap_buf_len); + free(dma_cap_buf); + } return ret; } #endif /* SOC_SHA_SUPPORT_DMA */ diff --git a/components/mbedtls/test_apps/mbedtls_ut/main/crts/prvtkey.pem b/components/mbedtls/test_apps/mbedtls_ut/main/crts/prvtkey.pem index bb0a510a7cb..06aebb289be 100644 --- a/components/mbedtls/test_apps/mbedtls_ut/main/crts/prvtkey.pem +++ b/components/mbedtls/test_apps/mbedtls_ut/main/crts/prvtkey.pem @@ -1,27 +1,28 @@ ------BEGIN RSA PRIVATE KEY----- -MIIEpQIBAAKCAQEAySb2QFrQZjQ1EhfN7I+raKrWSWWeYGppqPk3E1sV2y6LSE3M -7cVZyXxcpnP4mcKos3D9k8sbkt05oKcHR2THpWdz5mJn8A7TfJYWnYRHcRuR85th -XC8Pjf3f+JfjXgr/2a2JqHb4fttxoDRwWP1+kbTHa4iERqTFOIhYB9wD8uzbBHJq -IlIGbBHO9J+JxZKVkgDWZfc7zik3YzvkuWju/PmF73BagGxxRDzodzfxhHq1f96J -w10YS9pkhVFLxzOz3O+buwL8plCQplVnpj6J+2MaY4JlUvCcosDkT0EORAtrYGwZ -KJBKn4dRftk8Wbtns7zoJ2SNUL5TZ2oyAb2dwQIDAQABAoIBAQCRmE3tTs5A69Du -A6TdcTAUVnM8NP1ptBw+XgRrUiaDuzC9aPLHt2zB1e4J3S83vBn3p/UjIIQYzV+E -1OED4AJRyoutWdT5gQG6z7gW00QSrm358aGK49VSZUvT17yOuU9u85kMAvDigVvB -JbOb9f/C3yLoxqtXprPJs4ZkSe/hyB0JtRzauDYZnK4JmtgDfGds1cykohbUaeCW -ExUSbi/EPuroowmjEPFmN4tH/C3GtQonwGfjP76GXm8u5Fg8VXYUmb5pnxYcFdvv -shoasbK5lksgK50VP2vA9Y3mIrThRvkWgcv0TZaQWAF/JtdSXIID5WzfsgLbtDQF -hZLk1dDxAoGBAPfAjPAqapNVl4GqSkUWofUHMzZG85fHoPN3INSA0aMr4X9wHFfQ -pQ0ACxuimQj66Vk4rWww+HrsjPfiNMZzoi1exS1tjbQVyTBffrHj8sSdWt8Gw6MB -Pp5ubnCy9pl4lWNHlJZJp2SwAd10LzrizzAQALeEtRmg8meYGZElVUy7AoGBAM/Z -REXLJgaad5V3A2xehrSnknKUwab4LFIrgirZ6h0RXYo+wEHGJpDvM5Vw3sZT+UaJ -Jdlb3cXbqOxWrKlqjKe/S2vNScP7V2Na8l/ySO93PYE0V1Q+EeuNGi41xWC4Dh7o -D7BX2nDm9YBZzNVxM/30/dTzFM+CKrCARsLIXvWzAoGBAOQ4GRv61qXVyHSHO1cd -HB+sfD5ZaXa9S8Q6TqGx8GrQty4/RbyW1BN/oLvaMgKVr3KixQ3OpnYFhW2qkFbm -mdQVYqkQK+Jh1yyaKwkPI8h98wFTJ8/2C4rByzZBhOumqmYDwBoYyvvzLiSjLAag -e56YfzCOLIzpN6K594M+0q6VAoGBALWR5D1gKRjNqbetHxV1QhHg7WMhJkaZOAaU -MYMDmKvJ9sAE72jGE/y6qYJb9pCk3PdMaf8GbKciq9/CG9Vn2fXUe6txy4XkNEP8 -OA2vFx3yOY18Tumty3PNcNh7arCCOPuw17vCE3ZbnI2CZRj0amnosjFsJHreCDLl -7GrOJX5XAoGASZXbGykpYJTTr5PGPL/eX0koU1RZ9f6fvVdkfeWNGZfJ4oGkxDcO -fJnzq9wC9YREy6f3eoMrix95RPv4Qo1Wwi2PmtyMFvUdsYckFEhxSN3p4Iqn/nQg -6I7VB0yNqw8ZdP1vBkRcg3kk+QO2tci+OTdpDSKmO5nGjuqpsdBM5/o= ------END RSA PRIVATE KEY----- +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCTJ5b+P/Qyla3v +ri/JphhHto/S39vE9GlnR3iP+7pKfmev89PmkQGvu1csGL4wHOhxtWh1KUqbgVGA +XXO0hVKGherY7XaugVg5td2kxrDeVVtx/mHf0cE8SqSdeTRNrIlBp43ogKb4QZdp +i019khCt6blB6Ky0vuFiud8ipDdZ+Iw1vokhpl2odjJ6wyMF6szDaWs3N/cVLAD5 +D8O8WgjWRvoQqjU/6UAaCXMAS7rQwolN2k4vjWuXAaq3TU6Kx8XQGS+3dqtWD6Vh +PetnnqPGrVZb8qC+ugN+Q9sNJlBlhaLDw3q6icS3wZT/x9B1sszZUzVl8L7dkvxd +A6SOuo8HAgMBAAECggEAM+P0BLf8npHVgf1EcLEXQsKHuO6jirI9MOQtCWjU6kvk +uFEc2eMsWxGOzcfz2Pd4qBA2Q4pJ+sgBO2i40mFpFnLGu7QQ87w8pi0ReXdvCxfC +ZVI0pT5MC7yui7Ef1nwO8o3pJqGnP0ex4s/yhWMqaolOgIVR0XK1+6BbpNWPhzPr +FqgvgLEnJWS0u2T2hOKFE37WFnM0pLuQl0oGwKOSpOaY9dFSUDMoPrmMCWEdAcuZ +qIz5GgmAwkHaA1ztnIec+Svm2sXSXTy0nrnyD/mcDOS0OCeCmxM5jZRJ1AkZIqm9 +PhHPVPEhHy+uR+jzvVE5lxUYPZman0t0dIaDuKqn0QKBgQDIwxpjJFjxkMVewZtD +wvFFVfkY7CVobZiTB1Tr7cL7/Bs6ge6ltnwG1sceuiTg23BzOgyt2cQpl+F7dDoU +QCXDWoO+Od3Qx7Sr0IQ3rwyycrez3DfcahmYieBiGRwIZx17ZcfpV8rsK8zg6enZ +4C/eV41RDG1dKJyDbXaYTzzY3QKBgQC7pJYfYKjyKwLuh5ORbt/OvnXSC99SrpMV +HaJRKgq6m6LPcCIz9CcZNLSj3coVojBOWWKC254GVghddrUxA77ntasQ9EYBAVmD +pBrMW62dJ+g9qZVLZw+3lF71nYmBAEIXulW5Do09hnmHLpQJQfRgfZQBsn0yryhH +h8+ZNOWXMwKBgCVxdfdtrQUIyjbdBxdBQXx8B2RljqiGYEFzyZvjEU2r/GwSFa9k +dIdWAXcyonQpmTR0bC70gYh8YjqDN93VBPYFaLLO2hb5WDH/RtmX1Vdm2+o4tVZv +l2yCso25Pyg2CyKbnghgLmGT3bdJCStwi5z9WUb3eWI5k89TWB4aETAlAoGBALsJ +7hJM2Vq7AOse/TtkV3bPZsX+y5axKS9NILTiwVsNNBat4YoD/s0jOkR1GbDCwH/4 +nTdvDm+mZiQz5Zx77VkuPtxhgT4TSFTtyUCWydHzK437cjN9Aa+uF5GgfKW3yim4 +tSYHmUYQuybCiRFJSvy7cELY1e8lpXLXr1k53vj9AoGBALNK7W6+f+y98/2e45Jd +SSEe1GF984hHAi4zRn64Zhg9gxKBM9CEyrsbL2BlP3UY+2xdnJvzKnmPtelhIvgH +FqG2XC6236PGzD9ETGyayNu/d612C82h/BeePzce/3o4WaMldbgMRdjniKl+nFJR +8EDuH1YACHhGCCE5BT24OtFs +-----END PRIVATE KEY----- diff --git a/components/mbedtls/test_apps/mbedtls_ut/main/crts/server_cert_bundle b/components/mbedtls/test_apps/mbedtls_ut/main/crts/server_cert_bundle index 5a0a2f139f5..16b7c3a6c69 100644 Binary files a/components/mbedtls/test_apps/mbedtls_ut/main/crts/server_cert_bundle and b/components/mbedtls/test_apps/mbedtls_ut/main/crts/server_cert_bundle differ diff --git a/components/mbedtls/test_apps/mbedtls_ut/main/crts/server_cert_chain.pem b/components/mbedtls/test_apps/mbedtls_ut/main/crts/server_cert_chain.pem index afc99a9e302..b16beb8d836 100644 --- a/components/mbedtls/test_apps/mbedtls_ut/main/crts/server_cert_chain.pem +++ b/components/mbedtls/test_apps/mbedtls_ut/main/crts/server_cert_chain.pem @@ -1,20 +1,44 @@ -----BEGIN CERTIFICATE----- -MIIDVTCCAj0CFG5WO5Ukqd/0PnrSPIlQnXNjrCUUMA0GCSqGSIb3DQEBCwUAMGIx -CzAJBgNVBAYTAkNOMRMwEQYDVQQIDApTb21lLVN0YXRlMREwDwYDVQQHDAhTaGFu -Z2hhaTESMBAGA1UECgwJRXNwcmVzc2lmMRcwFQYDVQQDDA5Fc3ByZXNzaWYgUm9v -dDAeFw0yMDAzMjYwNjQxMTlaFw0yMTAzMjEwNjQxMTlaMGwxCzAJBgNVBAYTAkNO -MRMwEQYDVQQIDApTb21lLVN0YXRlMREwDwYDVQQHDAhTaGFuZ2hhaTEhMB8GA1UE -CgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMRIwEAYDVQQDDAlsb2NhbGhvc3Qw -ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDJJvZAWtBmNDUSF83sj6to -qtZJZZ5gammo+TcTWxXbLotITcztxVnJfFymc/iZwqizcP2TyxuS3TmgpwdHZMel -Z3PmYmfwDtN8lhadhEdxG5Hzm2FcLw+N/d/4l+NeCv/ZrYmodvh+23GgNHBY/X6R -tMdriIRGpMU4iFgH3APy7NsEcmoiUgZsEc70n4nFkpWSANZl9zvOKTdjO+S5aO78 -+YXvcFqAbHFEPOh3N/GEerV/3onDXRhL2mSFUUvHM7Pc75u7AvymUJCmVWemPon7 -YxpjgmVS8JyiwORPQQ5EC2tgbBkokEqfh1F+2TxZu2ezvOgnZI1QvlNnajIBvZ3B -AgMBAAEwDQYJKoZIhvcNAQELBQADggEBAI2RzAwx1IiyWYPbSQOMjATKG1hiqNJF -fkkqJrSfu93iQyye3Umb/pdUf7v5xgN2NrW5VnRow19VR7uCU4VCCBfx77f0Zp2e -UA13qhT5zljoqgtkU9bHbRfTW/Hq30joKqQz8+Z0Yom6qZA7XjAhXXiHt7I4Noq6 -y+HwH08Xr1nII1c6Zc0cDqK9UV02w2v1RJrnGlq3v/CBpanA/nz4LdP5Jqbh79WW -bCe8+Y7WEYR7K4dKSkDugf8ROAaGuCYAbhRMU3tFjNlMRR/5HcBpy7MfUvX6GcI0 -QCfe4ugnHXQXNxS0rb2uM6yCHOTiQ5MJjBPh9tRYV9bSko5u/NmwsFU= +MIIDqTCCApGgAwIBAgIUeARDBjDWIDu3UOVT1V4TAsrQDs0wDQYJKoZIhvcNAQEL +BQAwYjELMAkGA1UEBhMCQ04xEzARBgNVBAgMClNvbWUtU3RhdGUxETAPBgNVBAcM +CFNoYW5naGFpMRIwEAYDVQQKDAlFc3ByZXNzaWYxFzAVBgNVBAMMDkVzcHJlc3Np +ZiBSb290MB4XDTI2MDQyODAzMjE1NVoXDTQxMDQyNDAzMjE1NVowbDELMAkGA1UE +BhMCQ04xEzARBgNVBAgMClNvbWUtU3RhdGUxETAPBgNVBAcMCFNoYW5naGFpMSEw +HwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQxEjAQBgNVBAMMCWxvY2Fs +aG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJMnlv4/9DKVre+u +L8mmGEe2j9Lf28T0aWdHeI/7ukp+Z6/z0+aRAa+7VywYvjAc6HG1aHUpSpuBUYBd +c7SFUoaF6tjtdq6BWDm13aTGsN5VW3H+Yd/RwTxKpJ15NE2siUGnjeiApvhBl2mL +TX2SEK3puUHorLS+4WK53yKkN1n4jDW+iSGmXah2MnrDIwXqzMNpazc39xUsAPkP +w7xaCNZG+hCqNT/pQBoJcwBLutDCiU3aTi+Na5cBqrdNTorHxdAZL7d2q1YPpWE9 +62eeo8atVlvyoL66A35D2w0mUGWFosPDerqJxLfBlP/H0HWyzNlTNWXwvt2S/F0D +pI66jwcCAwEAAaNNMEswCQYDVR0TBAIwADAdBgNVHQ4EFgQUyTHWaahNKgm67bDV +g/Z298Lgz44wHwYDVR0jBBgwFoAUaMEt9ST7r9G/w6zSZbZg0Y5XQOIwDQYJKoZI +hvcNAQELBQADggEBAB4Z4AG7cOyMrtql6CLgdadbjf8s49h8Z82/POcAb5tx73B3 +rX7QoI542hFoeeTNTIYRwyr7bANe/9IRp9/t0dOIUwltl3OWuKDgOjyopGc5uJnf +RVHntmMOO6DmnGWYEqjzVdp9ApFzef6SkjgJSGMiRHdH1fue21py6sEXvPNz9MZw +Fit/Oos3Pr49vJAcaJU5/cjJPeYZ84lWNV7ceJZGaMYdKXE0nbHnPa07v0/3mJBg +MCiSu5QyIDe0LMsDkiY3EgyqRAKyTgxitq1P/BbHWNwKMLV3AhmmP3CJoqes+CCL +HndeEtZ+M7arpUx29xtHBXhcN7WyjEbMYPI4wug= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDtTCCAp2gAwIBAgIUeCJ5NrL+dsyhqe2JktSfNwY+3/QwDQYJKoZIhvcNAQEL +BQAwYjELMAkGA1UEBhMCQ04xEzARBgNVBAgMClNvbWUtU3RhdGUxETAPBgNVBAcM +CFNoYW5naGFpMRIwEAYDVQQKDAlFc3ByZXNzaWYxFzAVBgNVBAMMDkVzcHJlc3Np +ZiBSb290MB4XDTI2MDQyODAzMjE1NVoXDTQxMDQyNDAzMjE1NVowYjELMAkGA1UE +BhMCQ04xEzARBgNVBAgMClNvbWUtU3RhdGUxETAPBgNVBAcMCFNoYW5naGFpMRIw +EAYDVQQKDAlFc3ByZXNzaWYxFzAVBgNVBAMMDkVzcHJlc3NpZiBSb290MIIBIjAN +BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA/DgvPVCOSy15DOmTQMq5AsT2FRsN +80bCpZSgeRFoPnvL1Gz6+xBB5/RUY/a1+F0lOjXVq8UTmh0tQT+Sr14boaN0Tzks +M7TY0AjaaQPkr55gLwHV1jHYDhlJPXLrHFiG5pDpIN+ml6K6bbyPP1r1jWQYHCfS +kGxgYBU5yXLvko34oBf/yKymAvrRjlCsB1nNFlT/FzXOd0S4vgotMpV6D8p/m+sR +9vAI2eIRfvykf21pwa2UWUzTpa5MLcKPU/IeELDw8zNYIM/16xZS/NBnEu8uSuPt +U/GkmOm2v2qGmLAl2d9HYZ8jn4KeoIpgEBrF/OqCGgMrrO3Vz5OZun4e2QIDAQAB +o2MwYTAdBgNVHQ4EFgQUaMEt9ST7r9G/w6zSZbZg0Y5XQOIwHwYDVR0jBBgwFoAU +aMEt9ST7r9G/w6zSZbZg0Y5XQOIwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8E +BAMCAQYwDQYJKoZIhvcNAQELBQADggEBAPdtnZgFIESYXscEpqBmtaO/J25buNaJ +vszF/aKzn0iiwXEC74xEiPs90Xc3UbIa22fqvOTKljm7l5BheEZ76gbyD1mxJ6T6 +6IbtfXcv7Nblqrl84AKC13+lrzgU/T8atv29qaiyqEaIkfFQbtGETD213NC1ubNn +5qOdRzMFUrHcgh9fnGyNicc/JFXrrXN6Gqvwzu36IaNUXjzXrE52zr9mkyFtePTI +2HstreAMLn54v7gqc0mHRB7bMInKp/+E312OnXmYk1tYHI00/TOAGNY4z+okyFJ9 +uW9nKnUIiZwByECmPTGhIwvTWJEgJeuW6n07g6VnmwTBKwKpX8fIuwQ= -----END CERTIFICATE----- diff --git a/components/mbedtls/test_apps/mbedtls_ut/main/crts/server_root.pem b/components/mbedtls/test_apps/mbedtls_ut/main/crts/server_root.pem index 5854747f9dc..2333a894b0a 100644 --- a/components/mbedtls/test_apps/mbedtls_ut/main/crts/server_root.pem +++ b/components/mbedtls/test_apps/mbedtls_ut/main/crts/server_root.pem @@ -1,22 +1,22 @@ -----BEGIN CERTIFICATE----- -MIIDpTCCAo2gAwIBAgIUduK+lv/MILT278PPIYz8HkFzhFUwDQYJKoZIhvcNAQEL +MIIDtTCCAp2gAwIBAgIUeCJ5NrL+dsyhqe2JktSfNwY+3/QwDQYJKoZIhvcNAQEL BQAwYjELMAkGA1UEBhMCQ04xEzARBgNVBAgMClNvbWUtU3RhdGUxETAPBgNVBAcM CFNoYW5naGFpMRIwEAYDVQQKDAlFc3ByZXNzaWYxFzAVBgNVBAMMDkVzcHJlc3Np -ZiBSb290MB4XDTIwMDMyNjA2NDAxMFoXDTI1MDMyNjA2NDAxMFowYjELMAkGA1UE +ZiBSb290MB4XDTI2MDQyODAzMjE1NVoXDTQxMDQyNDAzMjE1NVowYjELMAkGA1UE BhMCQ04xEzARBgNVBAgMClNvbWUtU3RhdGUxETAPBgNVBAcMCFNoYW5naGFpMRIw EAYDVQQKDAlFc3ByZXNzaWYxFzAVBgNVBAMMDkVzcHJlc3NpZiBSb290MIIBIjAN -BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAp9ILzOjsz7dZbABUIoDCBat3MPsv -qd20Jsk3GzkLjY/HjTCMBweq2zt0sRsa+YwCPtQyAsYPtgt/VzRY4TF8jqmSj7Ko -DKOWkbim0O0XDAT8DfkQ32pZC7DnAw/374Vmm/ZmN/yE4zNUjNbjO2weswczcSdL -B3ITsa+OquKYK8J2Pe5gZh/tC0f0I9ks3UplcLyEex8TQZivAK3RL4QWj4j4NJWn -wH5qdizuKStwWEo3FvTP4g95SQItw31HTA8mJcBzCZC0NOZyMckRSmK51XljQ0iU -G7KwK8GNbDC+VUZEt5aGB5QZhCFC2wo5An7u20UHRUWbv4MEgddPDoQ4EwIDAQAB -o1MwUTAdBgNVHQ4EFgQU3inIjbdXp/DgSnVAiJmTlAtKH08wHwYDVR0jBBgwFoAU -3inIjbdXp/DgSnVAiJmTlAtKH08wDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0B -AQsFAAOCAQEAOpLjyXj2e0IaUgJK3lGuJ9u6piehYK1WqPoCR7K7pUnFGyNLu0mq -yfTqNoXV8a7NmM8Vn8ZJ1Gep20JqgvR27o3OE87bT7E/JPUsvbu7MNdfiVxpFWi1 -HxdBrzHr+mcakbhRxI38s3GVNT9Y89Y7FZbE+dqT8SxILk2pVUExfZR/ItazDTxl -95ARCOj/bQPCEN+oLYzS31ORmkJfY2AuJAcJUTCyO4UfpKVFmQeAKlNmTq9Q0a6C -0RlbzZ/PJoB3d265A9fTjlANQ7XzE8GgIJVR7cz5OJzZVxfEr9ME9VfgNrjKyXS3 -FcFQvif6JqX6IbmTenEKi7IfgX2zu1nxtQ== +BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA/DgvPVCOSy15DOmTQMq5AsT2FRsN +80bCpZSgeRFoPnvL1Gz6+xBB5/RUY/a1+F0lOjXVq8UTmh0tQT+Sr14boaN0Tzks +M7TY0AjaaQPkr55gLwHV1jHYDhlJPXLrHFiG5pDpIN+ml6K6bbyPP1r1jWQYHCfS +kGxgYBU5yXLvko34oBf/yKymAvrRjlCsB1nNFlT/FzXOd0S4vgotMpV6D8p/m+sR +9vAI2eIRfvykf21pwa2UWUzTpa5MLcKPU/IeELDw8zNYIM/16xZS/NBnEu8uSuPt +U/GkmOm2v2qGmLAl2d9HYZ8jn4KeoIpgEBrF/OqCGgMrrO3Vz5OZun4e2QIDAQAB +o2MwYTAdBgNVHQ4EFgQUaMEt9ST7r9G/w6zSZbZg0Y5XQOIwHwYDVR0jBBgwFoAU +aMEt9ST7r9G/w6zSZbZg0Y5XQOIwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8E +BAMCAQYwDQYJKoZIhvcNAQELBQADggEBAPdtnZgFIESYXscEpqBmtaO/J25buNaJ +vszF/aKzn0iiwXEC74xEiPs90Xc3UbIa22fqvOTKljm7l5BheEZ76gbyD1mxJ6T6 +6IbtfXcv7Nblqrl84AKC13+lrzgU/T8atv29qaiyqEaIkfFQbtGETD213NC1ubNn +5qOdRzMFUrHcgh9fnGyNicc/JFXrrXN6Gqvwzu36IaNUXjzXrE52zr9mkyFtePTI +2HstreAMLn54v7gqc0mHRB7bMInKp/+E312OnXmYk1tYHI00/TOAGNY4z+okyFJ9 +uW9nKnUIiZwByECmPTGhIwvTWJEgJeuW6n07g6VnmwTBKwKpX8fIuwQ= -----END CERTIFICATE----- diff --git a/components/mbedtls/test_apps/mbedtls_ut/main/test_esp_crt_bundle.c b/components/mbedtls/test_apps/mbedtls_ut/main/test_esp_crt_bundle.c index ca8c918de15..8fbc02d4aac 100644 --- a/components/mbedtls/test_apps/mbedtls_ut/main/test_esp_crt_bundle.c +++ b/components/mbedtls/test_apps/mbedtls_ut/main/test_esp_crt_bundle.c @@ -9,6 +9,8 @@ * SPDX-FileContributor: 2019-2025 Espressif Systems (Shanghai) CO LTD */ #include +#include +#include #include "esp_err.h" #include "esp_log.h" @@ -89,6 +91,38 @@ static volatile bool exit_flag; esp_err_t endpoint_teardown(mbedtls_endpoint_t *endpoint); +#if defined(CONFIG_MBEDTLS_HAVE_TIME_DATE) +/* Set system time to compile time so that MBEDTLS_HAVE_TIME_DATE checks + * pass without network/NTP. __DATE__ gives "Mon DD YYYY", __TIME__ gives "HH:MM:SS". */ +static void set_system_time_to_compile_time(void) +{ + const char *months[] = {"Jan","Feb","Mar","Apr","May","Jun", + "Jul","Aug","Sep","Oct","Nov","Dec"}; + char mon_str[4]; + int day, year, hour, min, sec; + + sscanf(__DATE__, "%3s %d %d", mon_str, &day, &year); + sscanf(__TIME__, "%d:%d:%d", &hour, &min, &sec); + + int mon = 0; + for (int i = 0; i < 12; i++) { + if (strcmp(mon_str, months[i]) == 0) { + mon = i; + break; + } + } + + struct tm t = { + .tm_sec = sec, .tm_min = min, .tm_hour = hour, + .tm_mday = day, .tm_mon = mon, .tm_year = year - 1900, + }; + + struct timeval tv = { .tv_sec = mktime(&t), .tv_usec = 0 }; + settimeofday(&tv, NULL); + ESP_LOGI(TAG, "System time set to compile time: %s %s", __DATE__, __TIME__); +} +#endif // CONFIG_MBEDTLS_HAVE_TIME_DATE + esp_err_t server_setup(mbedtls_endpoint_t *server) { int ret; @@ -302,7 +336,7 @@ void client_task(void *pvParameters) size_t available_before_handshake = uxTaskGetStackHighWaterMark(NULL); ESP_LOGI(TAG, "Available stack before handshake: %d", available_before_handshake); - ESP_LOGI(TAG, "Performing the SSL/TLS handshake with bundle that is missing the server root certificate"); + ESP_LOGI(TAG, "Performing the SSL/TLS handshake with bundle that contains the server root certificate"); while ( ( ret = mbedtls_ssl_handshake( &client->ssl ) ) != 0 ) { if ( ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE ) { printf( "mbedtls_ssl_handshake failed with -0x%x\n", -ret ); @@ -343,6 +377,10 @@ TEST_CASE("custom certificate bundle", "[mbedtls]") { test_case_uses_tcpip(); +#if defined(CONFIG_MBEDTLS_HAVE_TIME_DATE) + set_system_time_to_compile_time(); +#endif + SemaphoreHandle_t signal_sem = xSemaphoreCreateBinary(); TEST_ASSERT_NOT_NULL(signal_sem); @@ -567,3 +605,146 @@ TEST_CASE("custom certificate bundle init API - bound checking - Incorrect certi esp_ret = esp_crt_bundle_set(test_bundle, sizeof(test_bundle)); TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, esp_ret); } + +#if defined(CONFIG_MBEDTLS_HAVE_TIME_DATE) +TEST_CASE("certificate bundle - expired cert rejected with time-date check", "[mbedtls]") +{ + /* With HAVE_TIME_DATE enabled, a genuinely expired certificate must still + * be rejected even though its issuer is in the bundle and signature is valid. + * correct_sig_crt_esp32_com.pem expired Feb 2025 — with system time set to + * compile time (2026+), the EXPIRED flag must persist after bundle verification. */ + set_system_time_to_compile_time(); + + mbedtls_x509_crt crt; + uint32_t flags = 0; + + esp_crt_bundle_attach(NULL); + + mbedtls_x509_crt_init(&crt); + mbedtls_x509_crt_parse(&crt, correct_sig_crt_pem_start, + correct_sig_crt_pem_end - correct_sig_crt_pem_start); + + int ret = mbedtls_x509_crt_verify(&crt, NULL, NULL, NULL, &flags, + esp_crt_verify_callback, NULL); + + /* Verification must fail — the cert is genuinely expired */ + TEST_ASSERT_NOT_EQUAL(0, ret); + /* The EXPIRED flag specifically must be set */ + TEST_ASSERT_BITS_HIGH(MBEDTLS_X509_BADCERT_EXPIRED, flags); + + mbedtls_x509_crt_free(&crt); + esp_crt_bundle_detach(NULL); +} +#endif /* CONFIG_MBEDTLS_HAVE_TIME_DATE */ + +#if defined(CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_CROSS_SIGNED_VERIFY) && defined(CONFIG_MBEDTLS_HAVE_TIME_DATE) +/* Client task for cross-signed verification with time-date checking. + * Exercises the esp_crt_ca_cb_callback path where synthetic root certs + * from the bundle have no validity dates. */ +void client_task_cross_signed(void *pvParameters) +{ + SemaphoreHandle_t *client_signal_sem = (SemaphoreHandle_t *) pvParameters; + int ret = ESP_FAIL; + + mbedtls_endpoint_t *client = calloc(1, sizeof(mbedtls_endpoint_t)); + if (client == NULL) { + ESP_LOGE(TAG, "Failed to allocate memory for client"); + vTaskSuspend(NULL); + } + esp_crt_validate_res_t res = ESP_CRT_VALIDATE_UNKNOWN; + + if (client_setup(client) != ESP_OK) { + ESP_LOGE(TAG, "SSL client setup failed"); + goto exit; + } + + /* Attach bundle — this registers both esp_crt_verify_callback and + * esp_crt_ca_cb_callback when CROSS_SIGNED_VERIFY is enabled */ + ret = esp_crt_bundle_attach(&client->conf); + TEST_ASSERT_EQUAL(ESP_OK, ret); + + ret = esp_crt_bundle_set(server_cert_bundle_start, + server_cert_bundle_end - server_cert_bundle_start); + TEST_ASSERT_EQUAL(ESP_OK, ret); + + ESP_LOGI(TAG, "Connecting to %s:%s...", SERVER_ADDRESS, SERVER_PORT); + if ((ret = mbedtls_net_connect(&client->client_fd, SERVER_ADDRESS, + SERVER_PORT, MBEDTLS_NET_PROTO_TCP)) != 0) { + ESP_LOGE(TAG, "mbedtls_net_connect returned -%x", -ret); + goto exit; + } + + mbedtls_ssl_set_bio(&client->ssl, &client->client_fd, + mbedtls_net_send, mbedtls_net_recv, NULL); + + ESP_LOGI(TAG, "Performing SSL/TLS handshake (cross-signed verify + time-date)"); + while ((ret = mbedtls_ssl_handshake(&client->ssl)) != 0) { + if (ret != MBEDTLS_ERR_SSL_WANT_READ && + ret != MBEDTLS_ERR_SSL_WANT_WRITE) { + ESP_LOGE(TAG, "mbedtls_ssl_handshake failed with -0x%x", -ret); + break; + } + } + + ESP_LOGI(TAG, "Verifying peer X.509 certificate..."); + ret = mbedtls_ssl_get_verify_result(&client->ssl); + res = (ret == 0) ? ESP_CRT_VALIDATE_OK : ESP_CRT_VALIDATE_FAIL; + + if (res == ESP_CRT_VALIDATE_OK) { + ESP_LOGI(TAG, "Certificate verification passed!"); + } else { + ESP_LOGE(TAG, "Certificate verification failed! flags=0x%x", ret); + } + TEST_ASSERT_EQUAL(ESP_CRT_VALIDATE_OK, res); + +exit: + mbedtls_ssl_close_notify(&client->ssl); + mbedtls_ssl_session_reset(&client->ssl); + esp_crt_bundle_detach(&client->conf); + endpoint_teardown(client); + xSemaphoreGive(*client_signal_sem); + free(client); + vTaskSuspend(NULL); +} + +TEST_CASE("cross-signed certificate bundle with time-date check", "[mbedtls]") +{ + test_case_uses_tcpip(); + + /* Set system time so that certificate validity checks pass */ + set_system_time_to_compile_time(); + + SemaphoreHandle_t signal_sem = xSemaphoreCreateBinary(); + TEST_ASSERT_NOT_NULL(signal_sem); + + exit_flag = false; + TaskHandle_t server_task_handle; + xTaskCreate(server_task, "server task", 8192, &signal_sem, 10, + &server_task_handle); + + if (!xSemaphoreTake(signal_sem, SEM_TIMEOUT / portTICK_PERIOD_MS)) { + TEST_FAIL_MESSAGE("signal_sem not released, server start failed"); + } + + SemaphoreHandle_t client_signal_sem = xSemaphoreCreateBinary(); + TEST_ASSERT_NOT_NULL(client_signal_sem); + + TaskHandle_t client_task_handle; + xTaskCreate(client_task_cross_signed, "client task", 8192, + &client_signal_sem, 10, &client_task_handle); + + if (!xSemaphoreTake(client_signal_sem, SEM_TIMEOUT / portTICK_PERIOD_MS)) { + TEST_FAIL_MESSAGE("client_signal_sem not released, client exit failed"); + } + unity_utils_task_delete(client_task_handle); + + exit_flag = true; + + if (!xSemaphoreTake(signal_sem, SEM_TIMEOUT / portTICK_PERIOD_MS)) { + TEST_FAIL_MESSAGE("signal_sem not released, server exit failed"); + } + unity_utils_task_delete(server_task_handle); + vSemaphoreDelete(client_signal_sem); + vSemaphoreDelete(signal_sem); +} +#endif /* CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_CROSS_SIGNED_VERIFY && CONFIG_MBEDTLS_HAVE_TIME_DATE */ diff --git a/components/mbedtls/test_apps/mbedtls_ut/main/test_psa_ecdsa.c b/components/mbedtls/test_apps/mbedtls_ut/main/test_psa_ecdsa.c index 770f00a2b48..0793361bb2b 100644 --- a/components/mbedtls/test_apps/mbedtls_ut/main/test_psa_ecdsa.c +++ b/components/mbedtls/test_apps/mbedtls_ut/main/test_psa_ecdsa.c @@ -10,6 +10,9 @@ #include #include +#define MBEDTLS_DECLARE_PRIVATE_IDENTIFIERS +#include "mbedtls/private/ecp.h" +#include "mbedtls/private/bignum.h" #include "psa/crypto.h" #include "psa_crypto_driver_esp_ecdsa_contexts.h" #include "psa_crypto_driver_esp_ecdsa.h" @@ -215,7 +218,7 @@ const uint8_t ecdsa384_pub_y_km[] = { #endif /* SOC_KEY_MANAGER_SUPPORTED */ void test_ecdsa_verify(esp_ecdsa_curve_t curve, const uint8_t *hash, const uint8_t *r_comp, const uint8_t *s_comp, - const uint8_t *pub_x, const uint8_t *pub_y) + const uint8_t *pub_x, const uint8_t *pub_y, psa_status_t expected_status) { size_t hash_len = 0; int64_t elapsed_time; @@ -264,19 +267,25 @@ void test_ecdsa_verify(esp_ecdsa_curve_t curve, const uint8_t *hash, const uint8 memcpy(signature, r_comp, plen_bytes); memcpy(signature + plen_bytes, s_comp, plen_bytes); - ccomp_timer_start(); - status = psa_verify_hash(key_id, PSA_ALG_ECDSA(sha_alg), hash, hash_len, signature, 2 * plen_bytes); - TEST_ASSERT_EQUAL(PSA_SUCCESS, status); - elapsed_time = ccomp_timer_stop(); + if (expected_status == PSA_SUCCESS) { + ccomp_timer_start(); + status = psa_verify_hash(key_id, PSA_ALG_ECDSA(sha_alg), hash, hash_len, signature, 2 * plen_bytes); + TEST_ASSERT_EQUAL(expected_status, status); + elapsed_time = ccomp_timer_stop(); - if (curve == ESP_ECDSA_CURVE_SECP256R1) { - TEST_PERFORMANCE_CCOMP_LESS_THAN(ECDSA_P256_VERIFY_OP, "%" NEWLIB_NANO_COMPAT_FORMAT" us", NEWLIB_NANO_COMPAT_CAST(elapsed_time)); - } + if (curve == ESP_ECDSA_CURVE_SECP256R1) { + TEST_PERFORMANCE_CCOMP_LESS_THAN(ECDSA_P256_VERIFY_OP, "%" NEWLIB_NANO_COMPAT_FORMAT" us", NEWLIB_NANO_COMPAT_CAST(elapsed_time)); + } #if SOC_ECDSA_SUPPORT_CURVE_P384 - else if (curve == ESP_ECDSA_CURVE_SECP384R1) { - TEST_PERFORMANCE_CCOMP_LESS_THAN(ECDSA_P384_VERIFY_OP, "%" NEWLIB_NANO_COMPAT_FORMAT" us", NEWLIB_NANO_COMPAT_CAST(elapsed_time)); - } + else if (curve == ESP_ECDSA_CURVE_SECP384R1) { + TEST_PERFORMANCE_CCOMP_LESS_THAN(ECDSA_P384_VERIFY_OP, "%" NEWLIB_NANO_COMPAT_FORMAT" us", NEWLIB_NANO_COMPAT_CAST(elapsed_time)); + } #endif + } else { + status = psa_verify_hash(key_id, PSA_ALG_ECDSA(sha_alg), hash, hash_len, signature, 2 * plen_bytes); + TEST_ASSERT_EQUAL(expected_status, status); + } + psa_destroy_key(key_id); psa_reset_key_attributes(&key_attr); } @@ -288,7 +297,7 @@ TEST_CASE("mbedtls ECDSA signature verification performance on SECP256R1", "[mbe TEST_IGNORE_MESSAGE("ECDSA is not supported"); } #endif - test_ecdsa_verify(ESP_ECDSA_CURVE_SECP256R1, sha, ecdsa256_r, ecdsa256_s, ecdsa256_pub_x, ecdsa256_pub_y); + test_ecdsa_verify(ESP_ECDSA_CURVE_SECP256R1, sha, ecdsa256_r, ecdsa256_s, ecdsa256_pub_x, ecdsa256_pub_y, PSA_SUCCESS); } #ifdef SOC_ECDSA_SUPPORT_CURVE_P384 @@ -299,7 +308,83 @@ TEST_CASE("mbedtls ECDSA signature verification performance on SECP384R1", "[mbe TEST_IGNORE_MESSAGE("ECDSA is not supported"); } #endif - test_ecdsa_verify(ESP_ECDSA_CURVE_SECP384R1, sha, ecdsa384_r, ecdsa384_s, ecdsa384_pub_x, ecdsa384_pub_y); + test_ecdsa_verify(ESP_ECDSA_CURVE_SECP384R1, sha, ecdsa384_r, ecdsa384_s, ecdsa384_pub_x, ecdsa384_pub_y, PSA_SUCCESS); +} +#endif /* SOC_ECDSA_SUPPORT_CURVE_P384 */ + +/* + * Range-check regression test for the esp_ecdsa PSA driver. + * + * The two cases below exercise both branches of the r,s range check: + * - r = 0, s = 0 (lower bound: r > 0 / s > 0) + * - r = N, s = valid (upper bound: r < N) + * Both must be rejected by the verifier. + * + * ROM mbedtls (e.g. ESP32-C2 with CONFIG_MBEDTLS_USE_CRYPTO_ROM_IMPL=y): + * The ROM was built against an older mbedtls where + * MBEDTLS_ERR_ECP_VERIFY_FAILED was the legacy high-level value + * -0x4E80 (-20096). The current mbedtls_to_psa_error() no longer + * has a case for that number (the macro name now resolves to + * -149 in the new tree), and -20096 falls outside the PSA + * pass-through window (-0x1000, -0x80) in psa_crypto.c, so it + * hits the default branch and is returned as + * PSA_ERROR_GENERIC_ERROR (-132). + */ +#if !SOC_ECDSA_SUPPORTED && CONFIG_MBEDTLS_USE_CRYPTO_ROM_IMPL +#define ECDSA_RANGE_CHECK_REJECT_STATUS PSA_ERROR_GENERIC_ERROR +#else +#define ECDSA_RANGE_CHECK_REJECT_STATUS PSA_ERROR_INVALID_SIGNATURE +#endif + +/* Curve order N in big-endian, taken from mbedtls instead of a hard-coded table. */ +static void ecdsa_get_curve_order_be(mbedtls_ecp_group_id id, uint8_t *n_be, size_t len) +{ + mbedtls_ecp_group grp; + mbedtls_ecp_group_init(&grp); + TEST_ASSERT_EQUAL(0, mbedtls_ecp_group_load(&grp, id)); + TEST_ASSERT_EQUAL(0, mbedtls_mpi_write_binary(&grp.N, n_be, len)); + mbedtls_ecp_group_free(&grp); +} + +TEST_CASE("mbedtls ECDSA signature verification rejects out-of-range r, s on SECP256R1", "[mbedtls]") +{ +#if SOC_ECDSA_SUPPORTED + if (!ecdsa_ll_is_supported()) { + TEST_IGNORE_MESSAGE("ECDSA is not supported"); + } +#endif + static const uint8_t zero32[32] = { 0 }; + uint8_t p256_n_be[32]; + ecdsa_get_curve_order_be(MBEDTLS_ECP_DP_SECP256R1, p256_n_be, sizeof(p256_n_be)); + + test_ecdsa_verify(ESP_ECDSA_CURVE_SECP256R1, sha, zero32, zero32, ecdsa256_pub_x, ecdsa256_pub_y, ECDSA_RANGE_CHECK_REJECT_STATUS); /* r=0, s=0 */ + test_ecdsa_verify(ESP_ECDSA_CURVE_SECP256R1, sha, zero32, p256_n_be, ecdsa256_pub_x, ecdsa256_pub_y, ECDSA_RANGE_CHECK_REJECT_STATUS); /* r=0, s=N */ + test_ecdsa_verify(ESP_ECDSA_CURVE_SECP256R1, sha, p256_n_be, zero32, ecdsa256_pub_x, ecdsa256_pub_y, ECDSA_RANGE_CHECK_REJECT_STATUS); /* r=N, s=0 */ + test_ecdsa_verify(ESP_ECDSA_CURVE_SECP256R1, sha, p256_n_be, p256_n_be, ecdsa256_pub_x, ecdsa256_pub_y, ECDSA_RANGE_CHECK_REJECT_STATUS); /* r=N, s=N */ + test_ecdsa_verify(ESP_ECDSA_CURVE_SECP256R1, sha, ecdsa256_r, zero32, ecdsa256_pub_x, ecdsa256_pub_y, ECDSA_RANGE_CHECK_REJECT_STATUS); /* r=valid, s=0 */ + test_ecdsa_verify(ESP_ECDSA_CURVE_SECP256R1, sha, ecdsa256_r, p256_n_be, ecdsa256_pub_x, ecdsa256_pub_y, ECDSA_RANGE_CHECK_REJECT_STATUS); /* r=valid, s=N */ + test_ecdsa_verify(ESP_ECDSA_CURVE_SECP256R1, sha, p256_n_be, ecdsa256_s, ecdsa256_pub_x, ecdsa256_pub_y, ECDSA_RANGE_CHECK_REJECT_STATUS); /* r=N, s=valid */ +} + +#ifdef SOC_ECDSA_SUPPORT_CURVE_P384 +TEST_CASE("mbedtls ECDSA signature verification rejects out-of-range r, s on SECP384R1", "[mbedtls]") +{ +#if SOC_ECDSA_SUPPORTED + if (!ecdsa_ll_is_supported()) { + TEST_IGNORE_MESSAGE("ECDSA is not supported"); + } +#endif + static const uint8_t zero48[48] = { 0 }; + uint8_t p384_n_be[48]; + ecdsa_get_curve_order_be(MBEDTLS_ECP_DP_SECP384R1, p384_n_be, sizeof(p384_n_be)); + + test_ecdsa_verify(ESP_ECDSA_CURVE_SECP384R1, sha, zero48, zero48, ecdsa384_pub_x, ecdsa384_pub_y, ECDSA_RANGE_CHECK_REJECT_STATUS); /* r=0, s=0 */ + test_ecdsa_verify(ESP_ECDSA_CURVE_SECP384R1, sha, zero48, p384_n_be, ecdsa384_pub_x, ecdsa384_pub_y, ECDSA_RANGE_CHECK_REJECT_STATUS); /* r=0, s=N */ + test_ecdsa_verify(ESP_ECDSA_CURVE_SECP384R1, sha, p384_n_be, zero48, ecdsa384_pub_x, ecdsa384_pub_y, ECDSA_RANGE_CHECK_REJECT_STATUS); /* r=N, s=0 */ + test_ecdsa_verify(ESP_ECDSA_CURVE_SECP384R1, sha, p384_n_be, p384_n_be, ecdsa384_pub_x, ecdsa384_pub_y, ECDSA_RANGE_CHECK_REJECT_STATUS); /* r=N, s=N */ + test_ecdsa_verify(ESP_ECDSA_CURVE_SECP384R1, sha, ecdsa384_r, zero48, ecdsa384_pub_x, ecdsa384_pub_y, ECDSA_RANGE_CHECK_REJECT_STATUS); /* r=valid, s=0 */ + test_ecdsa_verify(ESP_ECDSA_CURVE_SECP384R1, sha, ecdsa384_r, p384_n_be, ecdsa384_pub_x, ecdsa384_pub_y, ECDSA_RANGE_CHECK_REJECT_STATUS); /* r=valid, s=N */ + test_ecdsa_verify(ESP_ECDSA_CURVE_SECP384R1, sha, p384_n_be, ecdsa384_s, ecdsa384_pub_x, ecdsa384_pub_y, ECDSA_RANGE_CHECK_REJECT_STATUS); /* r=N, s=valid */ } #endif /* SOC_ECDSA_SUPPORT_CURVE_P384 */ @@ -402,7 +487,7 @@ void test_ecdsa_sign(esp_ecdsa_curve_t curve, const uint8_t *hash, const uint8_t TEST_ASSERT_EQUAL_HEX32(PSA_SUCCESS, status); TEST_ASSERT_TRUE(signature_len == 2 * plen_bytes); - test_ecdsa_verify(curve, sha, signature, signature + plen_bytes, pub_x, pub_y); + test_ecdsa_verify(curve, sha, signature, signature + plen_bytes, pub_x, pub_y, PSA_SUCCESS); psa_destroy_key(priv_key_id); psa_reset_key_attributes(&priv_attr); } diff --git a/components/mbedtls/test_apps/mbedtls_ut/pytest_mbedtls_ut.py b/components/mbedtls/test_apps/mbedtls_ut/pytest_mbedtls_ut.py index 78392bb6014..ac378b0171a 100644 --- a/components/mbedtls/test_apps/mbedtls_ut/pytest_mbedtls_ut.py +++ b/components/mbedtls/test_apps/mbedtls_ut/pytest_mbedtls_ut.py @@ -117,18 +117,19 @@ def test_mbedtls_hmac_opaque(dut: Dut) -> None: dut.run_all_single_board_cases(group='efuse_hmac_key') -# TODO: IDF-15012 -# @pytest.mark.generic -# @pytest.mark.parametrize( -# 'config', -# [ -# 'rom_impl', -# ], -# indirect=True, -# ) -# @idf_parametrize('target', ['esp32c2'], indirect=['target']) -# def test_mbedtls_rom_impl_esp32c2(dut: Dut) -> None: -# dut.run_all_single_board_cases() +@pytest.mark.esp32c2_rev2 +@pytest.mark.xtal_26mhz +@pytest.mark.generic +@pytest.mark.parametrize( + 'config, baud', + [ + ('rom_impl', '74880'), + ], + indirect=True, +) +@idf_parametrize('target', ['esp32c2'], indirect=['target']) +def test_mbedtls_rom_impl_esp32c2(dut: Dut) -> None: + dut.run_all_single_board_cases() @pytest.mark.generic @@ -155,3 +156,22 @@ def test_mbedtls_ds_rsa(dut: Dut) -> None: @idf_parametrize('target', ['esp32s3'], indirect=['target']) def test_mbedtls_aria(dut: Dut) -> None: dut.run_all_single_board_cases(group='aria') + + +@pytest.mark.generic +@pytest.mark.parametrize( + 'config', + [ + 'cross_signed', + ], + indirect=True, +) +@idf_parametrize('target', ['supported_targets'], indirect=['target']) +def test_mbedtls_cross_signed(dut: Dut) -> None: + dut.run_all_single_board_cases( + name=[ + 'cross-signed certificate bundle with time-date check', + 'custom certificate bundle', + 'certificate bundle - expired cert rejected with time-date check', + ] + ) diff --git a/components/mbedtls/test_apps/mbedtls_ut/sdkconfig.ci.cross_signed b/components/mbedtls/test_apps/mbedtls_ut/sdkconfig.ci.cross_signed new file mode 100644 index 00000000000..35866d2f491 --- /dev/null +++ b/components/mbedtls/test_apps/mbedtls_ut/sdkconfig.ci.cross_signed @@ -0,0 +1,2 @@ +CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_CROSS_SIGNED_VERIFY=y +CONFIG_MBEDTLS_HAVE_TIME_DATE=y diff --git a/components/mbedtls/test_apps/mbedtls_ut/sdkconfig.ci.rom_impl b/components/mbedtls/test_apps/mbedtls_ut/sdkconfig.ci.rom_impl index 53574d1c42d..44cf92ee55d 100644 --- a/components/mbedtls/test_apps/mbedtls_ut/sdkconfig.ci.rom_impl +++ b/components/mbedtls/test_apps/mbedtls_ut/sdkconfig.ci.rom_impl @@ -1,3 +1,4 @@ CONFIG_IDF_TARGET="esp32c2" -# TODO: IDF-15012 -# CONFIG_MBEDTLS_USE_CRYPTO_ROM_IMPL=y +CONFIG_XTAL_FREQ_26=y +CONFIG_ESP32C2_REV_MIN_200=y +CONFIG_MBEDTLS_USE_CRYPTO_ROM_IMPL=y diff --git a/components/nvs_flash/src/nvs_bootloader.c b/components/nvs_flash/src/nvs_bootloader.c index 9347cfd3ba2..a8c50d76536 100644 --- a/components/nvs_flash/src/nvs_bootloader.c +++ b/components/nvs_flash/src/nvs_bootloader.c @@ -16,7 +16,7 @@ #include #if CONFIG_MBEDTLS_USE_CRYPTO_ROM_IMPL_BOOTLOADER && BOOTLOADER_BUILD -#include "mbedtls_rom_osi.h" +void mbedtls_rom_osi_functions_init_bootloader(void); #endif static const char* TAG = "nvs_bootloader"; diff --git a/components/nvs_flash/test_apps/main/test_nvs.c b/components/nvs_flash/test_apps/main/test_nvs.c index 6fe7c2ab0fd..783456900c4 100644 --- a/components/nvs_flash/test_apps/main/test_nvs.c +++ b/components/nvs_flash/test_apps/main/test_nvs.c @@ -42,6 +42,7 @@ extern int32_t get_heap_free_difference(const bool nvs_active_pool); static const char* TAG = "test_nvs"; +#if CONFIG_SPIRAM TEST_CASE("Kconfig option controls heap capability allocator for NVS", "[nvs_ram]") { // number of keys used for test @@ -104,6 +105,7 @@ TEST_CASE("Kconfig option controls heap capability allocator for NVS", "[nvs_ram TEST_ASSERT_GREATER_THAN_INT32(0, get_heap_free_difference(true)); TEST_ASSERT_GREATER_OR_EQUAL_INT32(0, get_heap_free_difference(false)); } +#endif // CONFIG_SPIRAM TEST_CASE("Partition name no longer than 16 characters", "[nvs]") { diff --git a/components/nvs_flash/test_apps/pytest_nvs_flash.py b/components/nvs_flash/test_apps/pytest_nvs_flash.py index 4124d503360..d95056904ed 100644 --- a/components/nvs_flash/test_apps/pytest_nvs_flash.py +++ b/components/nvs_flash/test_apps/pytest_nvs_flash.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2023-2025 Espressif Systems (Shanghai) CO LTD +# SPDX-FileCopyrightText: 2023-2026 Espressif Systems (Shanghai) CO LTD # SPDX-License-Identifier: Apache-2.0 import pytest from pytest_embedded_idf.dut import IdfDut @@ -9,14 +9,14 @@ from pytest_embedded_idf.utils import idf_parametrize @pytest.mark.parametrize('config', ['default'], indirect=True) @idf_parametrize('target', ['esp32', 'esp32c3'], indirect=['target']) def test_nvs_flash(dut: IdfDut) -> None: - dut.run_all_single_board_cases(group='!nvs_encr_hmac', timeout=120) + dut.run_all_single_board_cases(group='!nvs_encr_hmac&!nvs_ram', timeout=120) @pytest.mark.generic @pytest.mark.parametrize('config', ['blockdev'], indirect=True) @idf_parametrize('target', ['esp32', 'esp32c3'], indirect=['target']) def test_nvs_flash_blockdev(dut: IdfDut) -> None: - dut.run_all_single_board_cases(group='!nvs_encr_hmac', timeout=120) + dut.run_all_single_board_cases(group='!nvs_encr_hmac&!nvs_ram', timeout=120) @pytest.mark.nvs_encr_hmac @@ -46,6 +46,7 @@ def test_nvs_flash_encr_flash_enc(dut: IdfDut) -> None: @pytest.mark.psram +@pytest.mark.parametrize('config', ['spiram'], indirect=True) @idf_parametrize('target', ['esp32'], indirect=['target']) def test_nvs_flash_ram(dut: IdfDut) -> None: dut.run_all_single_board_cases(group='nvs_ram') diff --git a/components/nvs_flash/test_apps_bootloader/sdkconfig.defaults.esp32c2 b/components/nvs_flash/test_apps_bootloader/sdkconfig.defaults.esp32c2 index 5e3a3c88f46..6b1f01f17c8 100644 --- a/components/nvs_flash/test_apps_bootloader/sdkconfig.defaults.esp32c2 +++ b/components/nvs_flash/test_apps_bootloader/sdkconfig.defaults.esp32c2 @@ -1,2 +1,3 @@ CONFIG_IDF_TARGET="esp32c2" +CONFIG_ESP32C2_REV_MIN_200=y CONFIG_MBEDTLS_USE_CRYPTO_ROM_IMPL_BOOTLOADER=y diff --git a/components/openthread/sbom.yml b/components/openthread/sbom.yml index c4f2ae6377c..d159e3f98b3 100644 --- a/components/openthread/sbom.yml +++ b/components/openthread/sbom.yml @@ -1,5 +1,5 @@ name: 'openthread component' -version: '2023-07-06' +version: '2025-06-12' supplier: 'Organization: Espressif Systems (Shanghai) CO LTD' description: Espressif fork of OpenThread project, used to maintain ESP-specific patches and release branches manifests: diff --git a/components/openthread/sbom_openthread.yml b/components/openthread/sbom_openthread.yml index ea5d2d11e7c..9d7a43b78f7 100644 --- a/components/openthread/sbom_openthread.yml +++ b/components/openthread/sbom_openthread.yml @@ -1,8 +1,11 @@ name: 'openthread' -version: '2023-07-06' +version: '2025-06-12' cpe: cpe:2.3:o:google:openthread:{}:*:*:*:*:*:*:* supplier: 'Organization: Espressif Systems (Shanghai) CO LTD' originator: 'Organization: Google LLC' description: OpenThread released by Google is an open-source implementation of the Thread networking url: https://github.com/espressif/openthread hash: a98813b30ae58f9a95ece680b9cc46c3874de6ea +cve-exclude-list: + - cve: CVE-2026-8369 + reason: We use Espressif’s NAT64 implementation and hence this CVE from the upstream NAT64 implementation is not applicable. diff --git a/components/openthread/src/port/esp_openthread_uart.c b/components/openthread/src/port/esp_openthread_uart.c index 685e2ee5586..26c9ed13de6 100644 --- a/components/openthread/src/port/esp_openthread_uart.c +++ b/components/openthread/src/port/esp_openthread_uart.c @@ -60,12 +60,10 @@ otError otPlatUartSend(const uint8_t *buf, uint16_t buf_length) usb_serial_jtag_ll_txfifo_flush(); #endif + otPlatUartSendDone(); if (rval != (int)buf_length) { return OT_ERROR_FAILED; } - - otPlatUartSendDone(); - return OT_ERROR_NONE; } #endif diff --git a/components/protocomm/src/crypto/srp6a/esp_srp.c b/components/protocomm/src/crypto/srp6a/esp_srp.c index 290ec330219..fb1f0d97c2d 100644 --- a/components/protocomm/src/crypto/srp6a/esp_srp.c +++ b/components/protocomm/src/crypto/srp6a/esp_srp.c @@ -636,6 +636,7 @@ esp_err_t esp_srp_get_session_key(esp_srp_handle_t *hd, char *bytes_A, int len_A char *bytes_S; int len_S; + psa_hash_operation_t hash_op = PSA_HASH_OPERATION_INIT; u = vu = avu = S = NULL; bytes_S = NULL; @@ -677,6 +678,11 @@ esp_err_t esp_srp_get_session_key(esp_srp_handle_t *hd, char *bytes_A, int len_A if (! u) { goto error; } + if (esp_mpi_cmp_int(u, 0) == 0) { + ESP_LOGE(TAG, "Rejected SRP scrambling parameter: u == 0 (RFC 5054 Section 2.5.3)"); + ret = ESP_ERR_INVALID_ARG; + goto error; + } hexdump_mpi("u", u); /* S = (A v^u)^b */ @@ -698,7 +704,6 @@ esp_err_t esp_srp_get_session_key(esp_srp_handle_t *hd, char *bytes_A, int len_A goto error; } - psa_hash_operation_t hash_op = PSA_HASH_OPERATION_INIT; psa_status_t status = psa_hash_setup(&hash_op, PSA_ALG_SHA_512); ESP_GOTO_ON_FALSE(status == PSA_SUCCESS, ESP_FAIL, error, TAG, "Failed to setup hash operation: %d", status); psa_hash_update(&hash_op, (unsigned char *)bytes_S, len_S); diff --git a/components/protocomm/src/security/security1.c b/components/protocomm/src/security/security1.c index 9afdcce6267..2a971752a4e 100644 --- a/components/protocomm/src/security/security1.c +++ b/components/protocomm/src/security/security1.c @@ -491,7 +491,7 @@ static esp_err_t sec1_new_session(protocomm_security_handle_t handle, uint32_t s if (cur_session->id != -1) { /* Only one session is allowed at a time */ ESP_LOGE(TAG, "Closing old session with id %" PRIu32, cur_session->id); - sec1_close_session(cur_session, session_id); + sec1_close_session(cur_session, cur_session->id); } cur_session->id = session_id; @@ -555,6 +555,7 @@ static esp_err_t sec1_crypt(protocomm_security_handle_t handle, if (status != PSA_SUCCESS) { ESP_LOGE(TAG, "psa_cipher_update failed with status=%d", status); free(*outbuf); + *outbuf = NULL; return ESP_FAIL; } return ESP_OK; diff --git a/components/protocomm/src/security/security2.c b/components/protocomm/src/security/security2.c index 705a8ba7c1f..4eb22e81df0 100644 --- a/components/protocomm/src/security/security2.c +++ b/components/protocomm/src/security/security2.c @@ -427,7 +427,7 @@ static esp_err_t sec2_new_session(protocomm_security_handle_t handle, uint32_t s if (cur_session->id != -1) { /* Only one session is allowed at a time */ ESP_LOGE(TAG, "Closing old session with id %" PRIu32, cur_session->id); - sec2_close_session(cur_session, session_id); + sec2_close_session(cur_session, cur_session->id); } cur_session->id = session_id; @@ -505,12 +505,14 @@ static esp_err_t sec2_encrypt(protocomm_security_handle_t handle, if (status != PSA_SUCCESS) { ESP_LOGE(TAG, "psa_aead_encrypt failed with status=%d", status); free(*outbuf); + *outbuf = NULL; return ESP_FAIL; } if (out_len != *outlen) { ESP_LOGE(TAG, "psa_aead_encrypt output length mismatch: expected %zd, got %zu", *outlen, out_len); free(*outbuf); + *outbuf = NULL; return ESP_FAIL; } @@ -566,12 +568,14 @@ static esp_err_t sec2_decrypt(protocomm_security_handle_t handle, if (status != PSA_SUCCESS) { ESP_LOGE(TAG, "psa_aead_decrypt failed with status=%d", status); free(*outbuf); + *outbuf = NULL; return ESP_FAIL; } if (out_len != *outlen) { ESP_LOGE(TAG, "psa_aead_decrypt output length mismatch: expected %zd, got %zu", *outlen, out_len); free(*outbuf); + *outbuf = NULL; return ESP_FAIL; } diff --git a/components/protocomm/src/simple_ble/simple_ble.c b/components/protocomm/src/simple_ble/simple_ble.c index 543a4a1cde1..b1a9fdcbfd6 100644 --- a/components/protocomm/src/simple_ble/simple_ble.c +++ b/components/protocomm/src/simple_ble/simple_ble.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2024 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -43,8 +43,15 @@ const uint8_t *simple_ble_get_uuid128(uint16_t handle) { const uint8_t *uuid128_ptr; + if (g_ble_cfg_p == NULL || g_gatt_table_map == NULL) { + return NULL; + } + for (int i = 0; i < g_ble_max_gatt_table_size; i++) { if (g_gatt_table_map[i] == handle) { + if (g_ble_cfg_p->gatt_db[i].att_desc.uuid_length != ESP_UUID_LEN_128) { + return NULL; + } uuid128_ptr = (const uint8_t *) g_ble_cfg_p->gatt_db[i].att_desc.uuid_p; return uuid128_ptr; } @@ -52,16 +59,31 @@ const uint8_t *simple_ble_get_uuid128(uint16_t handle) return NULL; } +static void simple_ble_set_random_addr_if_configured(void) +{ + if (g_ble_cfg_p->ble_addr == NULL) { + return; + } + + esp_err_t err = esp_ble_gap_set_rand_addr(g_ble_cfg_p->ble_addr); + if (err == ESP_OK) { + g_ble_cfg_p->adv_params.own_addr_type = BLE_ADDR_TYPE_RANDOM; + } else { + ESP_LOGW(TAG, "Failed to set random address, using configured address type"); + } +} + static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) { + if (g_ble_cfg_p == NULL) { + return; + } + switch (event) { case ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT: adv_config_done &= (~adv_config_flag); - if (g_ble_cfg_p->ble_addr) { - esp_ble_gap_set_rand_addr(g_ble_cfg_p->ble_addr); - g_ble_cfg_p->adv_params.own_addr_type = BLE_ADDR_TYPE_RANDOM; - } + simple_ble_set_random_addr_if_configured(); if (adv_config_done == 0) { esp_ble_gap_start_advertising(&g_ble_cfg_p->adv_params); @@ -70,10 +92,7 @@ static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param case ESP_GAP_BLE_SCAN_RSP_DATA_SET_COMPLETE_EVT: adv_config_done &= (~scan_rsp_config_flag); - if (g_ble_cfg_p->ble_addr) { - esp_ble_gap_set_rand_addr(g_ble_cfg_p->ble_addr); - g_ble_cfg_p->adv_params.own_addr_type = BLE_ADDR_TYPE_RANDOM; - } + simple_ble_set_random_addr_if_configured(); if (adv_config_done == 0) { esp_ble_gap_start_advertising(&g_ble_cfg_p->adv_params); @@ -96,7 +115,7 @@ static void gatts_profile_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_ if (param->reg.status == ESP_GATT_OK) { gatts_if = p_gatts_if; } else { - ESP_LOGE(TAG, "reg app failed, app_id 0x0x%x, status %d", + ESP_LOGE(TAG, "reg app failed, app_id 0x%x, status %d", param->reg.app_id, param->reg.status); return; @@ -107,8 +126,15 @@ static void gatts_profile_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_ return; } + if (g_ble_cfg_p == NULL) { + return; + } + switch (event) { case ESP_GATTS_REG_EVT: + if (g_ble_cfg_p == NULL) { + return; + } ret = esp_ble_gatts_create_attr_tab(g_ble_cfg_p->gatt_db, gatts_if, g_ble_cfg_p->gatt_db_count, service_instance_id); if (ret) { ESP_LOGE(TAG, "create attr table failed, error code = 0x%x", ret); @@ -133,17 +159,23 @@ static void gatts_profile_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_ adv_config_done |= scan_rsp_config_flag; break; case ESP_GATTS_READ_EVT: - g_ble_cfg_p->read_fn(event, gatts_if, param); + if (g_ble_cfg_p) { + g_ble_cfg_p->read_fn(event, gatts_if, param); + } break; case ESP_GATTS_WRITE_EVT: - g_ble_cfg_p->write_fn(event, gatts_if, param); + if (g_ble_cfg_p) { + g_ble_cfg_p->write_fn(event, gatts_if, param); + } break; case ESP_GATTS_EXEC_WRITE_EVT: - g_ble_cfg_p->exec_write_fn(event, gatts_if, param); + if (g_ble_cfg_p) { + g_ble_cfg_p->exec_write_fn(event, gatts_if, param); + } break; case ESP_GATTS_MTU_EVT: ESP_LOGD(TAG, "ESP_GATTS_MTU_EVT, MTU %d", param->mtu.mtu); - if (g_ble_cfg_p->set_mtu_fn) { + if (g_ble_cfg_p && g_ble_cfg_p->set_mtu_fn) { g_ble_cfg_p->set_mtu_fn(event, gatts_if, param); } break; @@ -155,7 +187,9 @@ static void gatts_profile_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_ break; case ESP_GATTS_CONNECT_EVT: ESP_LOGD(TAG, "ESP_GATTS_CONNECT_EVT, conn_id = %d", param->connect.conn_id); - g_ble_cfg_p->connect_fn(event, gatts_if, param); + if (g_ble_cfg_p) { + g_ble_cfg_p->connect_fn(event, gatts_if, param); + } esp_ble_conn_update_params_t conn_params = {0}; memcpy(conn_params.bda, param->connect.remote_bda, sizeof(esp_bd_addr_t)); memcpy(s_cached_remote_bda, param->connect.remote_bda, sizeof(esp_bd_addr_t)); @@ -168,17 +202,26 @@ static void gatts_profile_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_ break; case ESP_GATTS_DISCONNECT_EVT: ESP_LOGD(TAG, "ESP_GATTS_DISCONNECT_EVT, reason = %d", param->disconnect.reason); - g_ble_cfg_p->disconnect_fn(event, gatts_if, param); + if (g_ble_cfg_p) { + g_ble_cfg_p->disconnect_fn(event, gatts_if, param); + } memset(s_cached_remote_bda, 0, sizeof(esp_bd_addr_t)); - esp_ble_gap_start_advertising(&g_ble_cfg_p->adv_params); + if (g_ble_cfg_p) { + esp_ble_gap_start_advertising(&g_ble_cfg_p->adv_params); + } break; case ESP_GATTS_CREAT_ATTR_TAB_EVT: { if (param->add_attr_tab.status != ESP_GATT_OK) { ESP_LOGE(TAG, "creating the attribute table failed, error code=0x%x", param->add_attr_tab.status); + } else if (g_ble_cfg_p == NULL) { + ESP_LOGE(TAG, "BLE config unavailable for attribute table event"); } else if (param->add_attr_tab.num_handle != g_ble_cfg_p->gatt_db_count) { ESP_LOGE(TAG, "created attribute table abnormally "); } else { ESP_LOGD(TAG, "created attribute table successfully, the number handle = %d", param->add_attr_tab.num_handle); + free(g_gatt_table_map); + g_gatt_table_map = NULL; + g_ble_max_gatt_table_size = 0; g_gatt_table_map = (uint16_t *) calloc(param->add_attr_tab.num_handle, sizeof(uint16_t)); if (g_gatt_table_map == NULL) { ESP_LOGE(TAG, "Memory allocation for GATT_TABLE_MAP failed "); @@ -217,10 +260,13 @@ simple_ble_cfg_t *simple_ble_init(void) esp_err_t simple_ble_deinit(void) { - free(g_ble_cfg_p->gatt_db); - g_ble_cfg_p->gatt_db = NULL; - free(g_ble_cfg_p); + simple_ble_cfg_t *ble_cfg = g_ble_cfg_p; g_ble_cfg_p = NULL; + if (ble_cfg) { + free(ble_cfg->gatt_db); + ble_cfg->gatt_db = NULL; + free(ble_cfg); + } free(g_gatt_table_map); g_gatt_table_map = NULL; @@ -246,7 +292,8 @@ esp_err_t simple_ble_start(simple_ble_cfg_t *cfg) #ifdef CONFIG_BTDM_CTRL_MODE_BR_EDR_ONLY ESP_LOGE(TAG, "Configuration mismatch. Select BLE Only or BTDM mode from menuconfig"); - return ESP_FAIL; + ret = ESP_FAIL; + goto err_bt_deinit; #elif CONFIG_BTDM_CTRL_MODE_BTDM ret = esp_bt_controller_enable(ESP_BT_MODE_BTDM); #else //For all other chips supporting BLE Only @@ -255,7 +302,7 @@ esp_err_t simple_ble_start(simple_ble_cfg_t *cfg) if (ret) { ESP_LOGE(TAG, "%s enable controller failed %d", __func__, ret); - return ret; + goto err_bt_deinit; } #endif @@ -263,37 +310,38 @@ esp_err_t simple_ble_start(simple_ble_cfg_t *cfg) ret = esp_bluedroid_init_with_cfg(&bluedroid_cfg); if (ret) { ESP_LOGE(TAG, "%s init bluetooth failed %d", __func__, ret); - return ret; + goto err_bt_disable; } ret = esp_bluedroid_enable(); if (ret) { ESP_LOGE(TAG, "%s enable bluetooth failed %d", __func__, ret); - return ret; + goto err_bluedroid_deinit; } - ret = esp_ble_gatts_register_callback(gatts_profile_event_handler); if (ret) { ESP_LOGE(TAG, "gatts register error, error code = 0x%x", ret); - return ret; + goto err_bluedroid_disable; } ret = esp_ble_gap_register_callback(gap_event_handler); if (ret) { ESP_LOGE(TAG, "gap register error, error code = 0x%x", ret); - return ret; + goto err_bluedroid_disable; } uint16_t app_id = 0x55; ret = esp_ble_gatts_app_register(app_id); if (ret) { ESP_LOGE(TAG, "gatts app register error, error code = 0x%x", ret); - return ret; + goto err_bluedroid_disable; } esp_err_t local_mtu_ret = esp_ble_gatt_set_local_mtu(500); if (local_mtu_ret) { ESP_LOGE(TAG, "set local MTU failed, error code = 0x%x", local_mtu_ret); + ret = local_mtu_ret; + goto err_bluedroid_disable; } ESP_LOGD(TAG, "Free mem at end of simple_ble_init %" PRIu32, esp_get_free_heap_size()); @@ -317,6 +365,18 @@ esp_err_t simple_ble_start(simple_ble_cfg_t *cfg) esp_ble_gap_set_security_param(ESP_BLE_SM_SET_RSP_KEY, &rsp_key, sizeof(uint8_t)); return ESP_OK; + +err_bluedroid_disable: + esp_bluedroid_disable(); +err_bluedroid_deinit: + esp_bluedroid_deinit(); +err_bt_disable: +#ifdef CONFIG_BT_CONTROLLER_ENABLED + esp_bt_controller_disable(); +err_bt_deinit: + esp_bt_controller_deinit(); +#endif + return ret; } esp_err_t simple_ble_stop(void) @@ -357,3 +417,15 @@ esp_err_t simple_ble_disconnect(void) { return esp_ble_gap_disconnect(s_cached_remote_bda); } + +void simple_ble_gatts_clear_char_values(void) +{ + if (g_ble_cfg_p == NULL || g_gatt_table_map == NULL) { + return; + } + for (int i = 0; i < g_ble_max_gatt_table_size; i++) { + if (g_ble_cfg_p->gatt_db[i].att_desc.uuid_length == ESP_UUID_LEN_128) { + esp_ble_gatts_set_attr_value(g_gatt_table_map[i], 0, NULL); + } + } +} diff --git a/components/protocomm/src/simple_ble/simple_ble.h b/components/protocomm/src/simple_ble/simple_ble.h index dd3858096fa..e38290b6d3b 100644 --- a/components/protocomm/src/simple_ble/simple_ble.h +++ b/components/protocomm/src/simple_ble/simple_ble.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -126,4 +126,12 @@ const uint8_t *simple_ble_get_uuid128(uint16_t handle); * @return ESP_OK on success, and appropriate error code for failure */ esp_err_t simple_ble_disconnect(void); + +/** Clear all characteristic value attributes in the GATT table + * + * Resets the stored value of every 128-bit-UUID characteristic (i.e. every + * response written via esp_ble_gatts_set_attr_value) to zero length so that + * a new connection cannot read the previous session's response data. + */ +void simple_ble_gatts_clear_char_values(void); #endif /* _SIMPLE_BLE_ */ diff --git a/components/protocomm/src/transports/protocomm_ble.c b/components/protocomm/src/transports/protocomm_ble.c index 87ef813ae13..a64fd1d84a8 100644 --- a/components/protocomm/src/transports/protocomm_ble.c +++ b/components/protocomm/src/transports/protocomm_ble.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2018-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2018-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -9,6 +9,7 @@ #include #include #include +#include #include #include @@ -37,9 +38,11 @@ static const char *TAG = "protocomm_ble"; static const uint16_t primary_service_uuid = ESP_GATT_UUID_PRI_SERVICE; static const uint16_t character_declaration_uuid = ESP_GATT_UUID_CHAR_DECLARE; static const uint16_t character_user_description = ESP_GATT_UUID_CHAR_DESCRIPTION; +static const uint16_t character_client_config_uuid = ESP_GATT_UUID_CHAR_CLIENT_CONFIG; static const uint8_t character_prop_read_write = ESP_GATT_CHAR_PROP_BIT_READ | ESP_GATT_CHAR_PROP_BIT_WRITE; static const uint8_t character_prop_read_write_notify = ESP_GATT_CHAR_PROP_BIT_READ | ESP_GATT_CHAR_PROP_BIT_WRITE | \ ESP_GATT_CHAR_PROP_BIT_NOTIFY; +static const uint8_t character_cccd_value[2] = {0x00, 0x00}; typedef struct { uint8_t type; @@ -63,7 +66,7 @@ typedef struct name_uuid128 { typedef struct _protocomm_ble { protocomm_t *pc_ble; name_uuid128_t *g_nu_lookup; - ssize_t g_nu_lookup_count; + size_t g_nu_lookup_count; uint16_t gatt_mtu; uint8_t *service_uuid; unsigned ble_link_encryption:1; @@ -130,9 +133,11 @@ static void hexdump(const char *msg, uint8_t *buf, int len) ESP_LOG_BUFFER_HEX_LEVEL(TAG, buf, len, ESP_LOG_DEBUG); } -static const uint16_t *uuid128_to_16(const uint8_t *uuid128) +static uint16_t uuid128_to_16(const uint8_t *uuid128) { - return (const uint16_t *) &uuid128[12]; + uint16_t uuid16 = 0; + memcpy(&uuid16, &uuid128[12], sizeof(uuid16)); + return uuid16; } static const char *handle_to_handler(uint16_t handle) @@ -144,8 +149,9 @@ static const char *handle_to_handler(uint16_t handle) if (!uuid128) { return NULL; } - for (int i = 0; i < protoble_internal->g_nu_lookup_count; i++) { - if (*uuid128_to_16(protoble_internal->g_nu_lookup[i].uuid128) == *uuid128_to_16(uuid128)) { + uint16_t target_uuid16 = uuid128_to_16(uuid128); + for (size_t i = 0; i < protoble_internal->g_nu_lookup_count; i++) { + if (uuid128_to_16(protoble_internal->g_nu_lookup[i].uuid128) == target_uuid16) { return protoble_internal->g_nu_lookup[i].name; } } @@ -173,7 +179,7 @@ static void transport_simple_ble_read(esp_gatts_cb_event_t event, esp_gatt_if_t ESP_LOGD(TAG, "Inside read w/ session - %d on param %d %d", param->read.conn_id, param->read.handle, read_len); - if (!read_len && !param->read.offset) { + if (!param->read.offset) { ESP_LOGD(TAG, "Reading attr value first time"); status = esp_ble_gatts_get_attr_value(param->read.handle, &read_len, &read_buf); max_read_len = read_len; @@ -234,12 +240,17 @@ static esp_err_t prepare_write_event_env(esp_gatt_if_t gatts_if, /* If prepare buffer is allocated copy incoming data into it */ if (status == ESP_GATT_OK) { - memcpy(prepare_write_env.prepare_buf + param->write.offset, - param->write.value, - param->write.len); - int next_len = param->write.offset + param->write.len; - prepare_write_env.prepare_len = MAX(prepare_write_env.prepare_len, next_len); - prepare_write_env.handle = param->write.handle; + if (param->write.len && param->write.value) { + memcpy(prepare_write_env.prepare_buf + param->write.offset, + param->write.value, + param->write.len); + int next_len = param->write.offset + param->write.len; + prepare_write_env.prepare_len = MAX(prepare_write_env.prepare_len, next_len); + prepare_write_env.handle = param->write.handle; + } else if (param->write.len) { + ESP_LOGE(TAG, "NULL write value for non-zero length"); + status = ESP_GATT_ERROR; + } } /* Send write response if needed */ @@ -297,6 +308,16 @@ static void transport_simple_ble_write(esp_gatts_cb_event_t event, esp_gatt_if_t return; } + protocomm_t *pc_ble = protoble_internal->pc_ble; + if (pc_ble == NULL) { + ESP_LOGW(TAG, "Ignoring write on inactive protocomm transport"); + if (param->write.need_rsp) { + esp_ble_gatts_send_response(gatts_if, param->write.conn_id, + param->write.trans_id, ESP_GATT_ERROR, NULL); + } + return; + } + if (param->write.is_prep) { ret = prepare_write_event_env(gatts_if, param); if (ret != ESP_OK) { @@ -307,26 +328,71 @@ static void transport_simple_ble_write(esp_gatts_cb_event_t event, esp_gatt_if_t ESP_LOGD(TAG, "is_prep not set"); } - ret = protocomm_req_handle(protoble_internal->pc_ble, - handle_to_handler(param->write.handle), + if (param->write.len == 0 || param->write.len > CHAR_VAL_LEN_MAX) { + ESP_LOGE(TAG, "Invalid write length %d for handle %d", param->write.len, param->write.handle); + if (param->write.need_rsp) { + esp_ble_gatts_send_response(gatts_if, param->write.conn_id, + param->write.trans_id, ESP_GATT_INVALID_ATTR_LEN, NULL); + } + return; + } + + const char *ep_name = handle_to_handler(param->write.handle); + if (ep_name == NULL) { + ESP_LOGW(TAG, "No endpoint mapped for handle %d", param->write.handle); + if (param->write.need_rsp) { + esp_ble_gatts_send_response(gatts_if, param->write.conn_id, + param->write.trans_id, ESP_GATT_NOT_FOUND, NULL); + } + return; + } + + ret = protocomm_req_handle(pc_ble, + ep_name, param->write.conn_id, param->write.value, param->write.len, &outbuf, &outlen); if (ret == ESP_OK) { + if (outlen < 0 || outlen > CHAR_VAL_LEN_MAX) { + ESP_LOGE(TAG, "Invalid response length %d for handle %d", (int)outlen, param->write.handle); + if (outbuf) { + free(outbuf); + } + if (param->write.need_rsp) { + esp_ble_gatts_send_response(gatts_if, param->write.conn_id, + param->write.trans_id, ESP_GATT_INVALID_ATTR_LEN, NULL); + } + return; + } + if (outlen > 0 && outbuf == NULL) { + ESP_LOGE(TAG, "NULL response buffer for non-zero response length"); + if (param->write.need_rsp) { + esp_ble_gatts_send_response(gatts_if, param->write.conn_id, + param->write.trans_id, ESP_GATT_ERROR, NULL); + } + return; + } + ret = esp_ble_gatts_set_attr_value(param->write.handle, outlen, outbuf); if (ret != ESP_OK) { ESP_LOGE(TAG, "Failed to set the session attribute value"); } - ret = esp_ble_gatts_send_response(gatts_if, param->write.conn_id, - param->write.trans_id, ESP_GATT_OK, NULL); - if (ret != ESP_OK) { - ESP_LOGE(TAG, "Send response error in write"); + if (param->write.need_rsp) { + ret = esp_ble_gatts_send_response(gatts_if, param->write.conn_id, + param->write.trans_id, ESP_GATT_OK, NULL); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Send response error in write"); + } } hexdump("Response from write", outbuf, outlen); } else { ESP_LOGE(TAG, "Invalid content received, killing connection"); + if (param->write.need_rsp) { + esp_ble_gatts_send_response(gatts_if, param->write.conn_id, + param->write.trans_id, ESP_GATT_ERROR, NULL); + } esp_ble_gatts_close(gatts_if, param->write.conn_id); } if (outbuf) { @@ -350,11 +416,39 @@ static void transport_simple_ble_exec_write(esp_gatts_cb_event_t event, esp_gatt return; } + protocomm_t *pc_ble = protoble_internal->pc_ble; + if (pc_ble == NULL) { + ESP_LOGW(TAG, "Ignoring exec write on inactive protocomm transport"); + protocomm_ble_reset_prepare_write(); + esp_ble_gatts_send_response(gatts_if, param->exec_write.conn_id, + param->exec_write.trans_id, ESP_GATT_ERROR, NULL); + esp_ble_gatts_close(gatts_if, param->exec_write.conn_id); + return; + } + if ((param->exec_write.exec_write_flag == ESP_GATT_PREP_WRITE_EXEC) && prepare_write_env.prepare_buf) { - err = protocomm_req_handle(protoble_internal->pc_ble, - handle_to_handler(prepare_write_env.handle), + if (prepare_write_env.prepare_len <= 0 || prepare_write_env.prepare_len > PREPARE_BUF_MAX_SIZE) { + ESP_LOGE(TAG, "Invalid prepared write length: %d", prepare_write_env.prepare_len); + esp_ble_gatts_send_response(gatts_if, param->exec_write.conn_id, + param->exec_write.trans_id, ESP_GATT_INVALID_ATTR_LEN, NULL); + protocomm_ble_reset_prepare_write(); + return; + } + + const char *ep_name = handle_to_handler(prepare_write_env.handle); + if (ep_name == NULL) { + ESP_LOGE(TAG, "No endpoint mapped for prepared write handle %d", prepare_write_env.handle); + esp_ble_gatts_send_response(gatts_if, param->exec_write.conn_id, + param->exec_write.trans_id, ESP_GATT_NOT_FOUND, NULL); + esp_ble_gatts_close(gatts_if, param->exec_write.conn_id); + protocomm_ble_reset_prepare_write(); + return; + } + + err = protocomm_req_handle(pc_ble, + ep_name, param->exec_write.conn_id, prepare_write_env.prepare_buf, prepare_write_env.prepare_len, @@ -362,8 +456,33 @@ static void transport_simple_ble_exec_write(esp_gatts_cb_event_t event, esp_gatt if (err != ESP_OK) { ESP_LOGE(TAG, "Invalid content received, killing connection"); + esp_ble_gatts_send_response(gatts_if, param->exec_write.conn_id, + param->exec_write.trans_id, ESP_GATT_ERROR, NULL); esp_ble_gatts_close(gatts_if, param->exec_write.conn_id); + protocomm_ble_reset_prepare_write(); + if (outbuf) { + free(outbuf); + } + return; } else { + if (outlen < 0 || outlen > CHAR_VAL_LEN_MAX) { + ESP_LOGE(TAG, "Invalid response length %d in exec write", (int)outlen); + if (outbuf) { + free(outbuf); + outbuf = NULL; + } + esp_ble_gatts_send_response(gatts_if, param->exec_write.conn_id, + param->exec_write.trans_id, ESP_GATT_INVALID_ATTR_LEN, NULL); + protocomm_ble_reset_prepare_write(); + return; + } + if (outlen > 0 && outbuf == NULL) { + ESP_LOGE(TAG, "NULL response buffer for non-zero exec write response"); + esp_ble_gatts_send_response(gatts_if, param->exec_write.conn_id, + param->exec_write.trans_id, ESP_GATT_ERROR, NULL); + protocomm_ble_reset_prepare_write(); + return; + } hexdump("Response from exec write", outbuf, outlen); esp_ble_gatts_set_attr_value(prepare_write_env.handle, outlen, outbuf); } @@ -391,6 +510,10 @@ static void transport_simple_ble_disconnect(esp_gatts_cb_event_t event, esp_gatt /* Drop any staged prepare-write data when a connection ends */ protocomm_ble_reset_prepare_write(); + /* Clear GATT attribute values so a new connection cannot read the + * previous session's response data. */ + simple_ble_gatts_clear_char_values(); + /* Ignore BLE events received after protocomm layer is stopped */ if (protoble_internal == NULL) { ESP_LOGI(TAG,"Protocomm layer has already stopped"); @@ -402,9 +525,14 @@ static void transport_simple_ble_disconnect(esp_gatts_cb_event_t event, esp_gatt return; } - if (protoble_internal->pc_ble->sec && - protoble_internal->pc_ble->sec->close_transport_session) { - ret = protoble_internal->pc_ble->sec->close_transport_session(protoble_internal->pc_ble->sec_inst, + protocomm_t *pc_ble = protoble_internal->pc_ble; + if (pc_ble == NULL) { + ESP_LOGD(TAG, "Protocomm BLE inactive, ignoring disconnect"); + return; + } + + if (pc_ble->sec && pc_ble->sec->close_transport_session) { + ret = pc_ble->sec->close_transport_session(pc_ble->sec_inst, param->disconnect.conn_id); if (ret != ESP_OK) { ESP_LOGE(TAG, "error closing the session after disconnect"); @@ -414,6 +542,7 @@ static void transport_simple_ble_disconnect(esp_gatts_cb_event_t event, esp_gatt ble_event.evt_type = PROTOCOMM_TRANSPORT_BLE_DISCONNECTED; /* Set the Disconnection handle */ ble_event.conn_handle = param->disconnect.conn_id; + ble_event.disconnect_reason = param->disconnect.reason; if (esp_event_post(PROTOCOMM_TRANSPORT_BLE_EVENT, PROTOCOMM_TRANSPORT_BLE_DISCONNECTED, &ble_event, sizeof(protocomm_ble_event_t), portMAX_DELAY) != ESP_OK) { ESP_LOGE(TAG, "Failed to post transport disconnection event"); @@ -442,9 +571,14 @@ static void transport_simple_ble_connect(esp_gatts_cb_event_t event, esp_gatt_if return; } - if (protoble_internal->pc_ble->sec && - protoble_internal->pc_ble->sec->new_transport_session) { - ret = protoble_internal->pc_ble->sec->new_transport_session(protoble_internal->pc_ble->sec_inst, + protocomm_t *pc_ble = protoble_internal->pc_ble; + if (pc_ble == NULL) { + ESP_LOGD(TAG, "Protocomm BLE inactive, ignoring connect"); + return; + } + + if (pc_ble->sec && pc_ble->sec->new_transport_session) { + ret = pc_ble->sec->new_transport_session(pc_ble->sec_inst, param->connect.conn_id); if (ret != ESP_OK) { ESP_LOGE(TAG, "error creating the session"); @@ -488,17 +622,29 @@ static esp_err_t protocomm_ble_remove_endpoint(const char *ep_name) static ssize_t populate_gatt_db(esp_gatts_attr_db_t **gatt_db_generated) { int i; - /* Each endpoint requires 3 attributes: + int char_stride = protoble_internal->ble_notify ? 4 : 3; + /* Each endpoint requires 3 (or 4 if notify enabled) attributes: * 1) for Characteristic Declaration * 2) for Characteristic Value (for reading and writing to an endpoint) * 3) for Characteristic User Description (endpoint name) + * 4) for Client Characteristic Configuration Descriptor (if notify enabled) * - * Therefore, we need esp_gatts_attr_db_t of size 3 * number of endpoints + 1 for service + * Therefore, we need esp_gatts_attr_db_t of size char_stride * number of endpoints + 1 for service */ - ssize_t gatt_db_generated_entries = 3 * protoble_internal->g_nu_lookup_count + 1; + if (protoble_internal->g_nu_lookup_count > ((SIZE_MAX - 1) / char_stride)) { + ESP_LOGE(TAG, "gatt db entries overflow"); + return -1; + } + size_t gatt_db_generated_entries_sz = char_stride * protoble_internal->g_nu_lookup_count + 1; + if (gatt_db_generated_entries_sz > (size_t)INT_MAX || + gatt_db_generated_entries_sz > (SIZE_MAX / sizeof(esp_gatts_attr_db_t))) { + ESP_LOGE(TAG, "gatt db size overflow"); + return -1; + } + ssize_t gatt_db_generated_entries = (ssize_t)gatt_db_generated_entries_sz; *gatt_db_generated = (esp_gatts_attr_db_t *) malloc(sizeof(esp_gatts_attr_db_t) * - (gatt_db_generated_entries)); + gatt_db_generated_entries_sz); if ((*gatt_db_generated) == NULL) { ESP_LOGE(TAG, "Failed to assign memory to gatt_db"); return -1; @@ -515,9 +661,12 @@ static ssize_t populate_gatt_db(esp_gatts_attr_db_t **gatt_db_generated) /* Declare characteristics */ for (i = 1 ; i < gatt_db_generated_entries ; i++) { + int attr_idx = (i - 1) % char_stride; + int ep_idx = (i - 1) / char_stride; + (*gatt_db_generated)[i].attr_control.auto_rsp = ESP_GATT_RSP_BY_APP; - if (i % 3 == 1) { + if (attr_idx == 0) { /* Characteristic Declaration */ (*gatt_db_generated)[i].att_desc.perm = ESP_GATT_PERM_READ; (*gatt_db_generated)[i].att_desc.uuid_length = ESP_UUID_LEN_16; @@ -530,25 +679,34 @@ static ssize_t populate_gatt_db(esp_gatts_attr_db_t **gatt_db_generated) } else { (*gatt_db_generated)[i].att_desc.value = (uint8_t *) &character_prop_read_write; } - } else if (i % 3 == 2) { + } else if (attr_idx == 1) { /* Characteristic Value */ (*gatt_db_generated)[i].att_desc.perm = ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE ; if (protoble_internal->ble_link_encryption) { (*gatt_db_generated)[i].att_desc.perm |= ESP_GATT_PERM_READ_ENCRYPTED | ESP_GATT_PERM_WRITE_ENCRYPTED; } (*gatt_db_generated)[i].att_desc.uuid_length = ESP_UUID_LEN_128; - (*gatt_db_generated)[i].att_desc.uuid_p = protoble_internal->g_nu_lookup[i / 3].uuid128; + (*gatt_db_generated)[i].att_desc.uuid_p = protoble_internal->g_nu_lookup[ep_idx].uuid128; (*gatt_db_generated)[i].att_desc.max_length = CHAR_VAL_LEN_MAX; (*gatt_db_generated)[i].att_desc.length = 0; (*gatt_db_generated)[i].att_desc.value = NULL; - } else { + } else if (attr_idx == 2) { /* Characteristic User Description (for keeping endpoint names) */ (*gatt_db_generated)[i].att_desc.perm = ESP_GATT_PERM_READ; (*gatt_db_generated)[i].att_desc.uuid_length = ESP_UUID_LEN_16; (*gatt_db_generated)[i].att_desc.uuid_p = (uint8_t *) &character_user_description; - (*gatt_db_generated)[i].att_desc.max_length = strlen(protoble_internal->g_nu_lookup[i / 3 - 1].name); + (*gatt_db_generated)[i].att_desc.max_length = strlen(protoble_internal->g_nu_lookup[ep_idx].name); (*gatt_db_generated)[i].att_desc.length = (*gatt_db_generated)[i].att_desc.max_length; - (*gatt_db_generated)[i].att_desc.value = (uint8_t *) protoble_internal->g_nu_lookup[i / 3 - 1].name; + (*gatt_db_generated)[i].att_desc.value = (uint8_t *) protoble_internal->g_nu_lookup[ep_idx].name; + } else { + /* Client Characteristic Configuration Descriptor */ + (*gatt_db_generated)[i].attr_control.auto_rsp = ESP_GATT_AUTO_RSP; + (*gatt_db_generated)[i].att_desc.perm = ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE; + (*gatt_db_generated)[i].att_desc.uuid_length = ESP_UUID_LEN_16; + (*gatt_db_generated)[i].att_desc.uuid_p = (uint8_t *) &character_client_config_uuid; + (*gatt_db_generated)[i].att_desc.max_length = sizeof(uint16_t); + (*gatt_db_generated)[i].att_desc.length = sizeof(uint16_t); + (*gatt_db_generated)[i].att_desc.value = (uint8_t *) character_cccd_value; } } return gatt_db_generated_entries; @@ -558,8 +716,14 @@ static void protocomm_ble_cleanup(void) { protocomm_ble_reset_prepare_write(); if (protoble_internal) { + if (protoble_internal->service_uuid) { + free(protoble_internal->service_uuid); + protoble_internal->service_uuid = NULL; + } + adv_config.p_service_uuid = NULL; + adv_config.service_uuid_len = 0; if (protoble_internal->g_nu_lookup) { - for (unsigned i = 0; i < protoble_internal->g_nu_lookup_count; i++) { + for (size_t i = 0; i < protoble_internal->g_nu_lookup_count; i++) { if (protoble_internal->g_nu_lookup[i].name) { free((void *)protoble_internal->g_nu_lookup[i].name); } @@ -578,6 +742,10 @@ static void protocomm_ble_cleanup(void) protocomm_ble_mfg_data = NULL; protocomm_ble_mfg_data_len = 0; } + if (protocomm_ble_addr) { + free(protocomm_ble_addr); + protocomm_ble_addr = NULL; + } } esp_err_t protocomm_ble_start(protocomm_t *pc, const protocomm_ble_config_t *config) @@ -586,11 +754,34 @@ esp_err_t protocomm_ble_start(protocomm_t *pc, const protocomm_ble_config_t *con return ESP_ERR_INVALID_ARG; } + if (config->manufacturer_data_len > 0 && config->manufacturer_data == NULL) { + ESP_LOGE(TAG, "Manufacturer data length set without data"); + return ESP_ERR_INVALID_ARG; + } + + if (config->nu_lookup_count <= 0 || config->nu_lookup_count > (ssize_t)(INT_MAX - 1)) { + ESP_LOGE(TAG, "Invalid nu_lookup_count: %d", (int)config->nu_lookup_count); + return ESP_ERR_INVALID_ARG; + } + + if (config->manufacturer_data != NULL && + (config->manufacturer_data_len <= 0 || + config->manufacturer_data_len > MAX_BLE_MANUFACTURER_DATA_LEN)) { + ESP_LOGE(TAG, "Invalid manufacturer data length: %d", (int)config->manufacturer_data_len); + return ESP_ERR_INVALID_ARG; + } + if (protoble_internal) { ESP_LOGE(TAG, "Protocomm BLE already started"); return ESP_FAIL; } + size_t endpoint_count = (size_t)config->nu_lookup_count; + if (endpoint_count > (SIZE_MAX / sizeof(name_uuid128_t))) { + ESP_LOGE(TAG, "Name UUID table size overflow"); + return ESP_ERR_NO_MEM; + } + /* Store BLE device name internally */ protocomm_ble_device_name = strdup(config->device_name); if (protocomm_ble_device_name == NULL) { @@ -601,12 +792,24 @@ esp_err_t protocomm_ble_start(protocomm_t *pc, const protocomm_ble_config_t *con /* Store BLE manufacturer data pointer */ if (config->manufacturer_data != NULL) { - protocomm_ble_mfg_data = config->manufacturer_data; - protocomm_ble_mfg_data_len = config->manufacturer_data_len; + protocomm_ble_mfg_data = (uint8_t *)malloc((size_t)config->manufacturer_data_len); + if (protocomm_ble_mfg_data == NULL) { + ESP_LOGE(TAG, "Error allocating memory for manufacturer data"); + protocomm_ble_cleanup(); + return ESP_ERR_NO_MEM; + } + memcpy(protocomm_ble_mfg_data, config->manufacturer_data, (size_t)config->manufacturer_data_len); + protocomm_ble_mfg_data_len = (size_t)config->manufacturer_data_len; } if (config->ble_addr != NULL) { - protocomm_ble_addr = config->ble_addr; + protocomm_ble_addr = (uint8_t *)malloc(BLE_ADDR_LEN); + if (protocomm_ble_addr == NULL) { + ESP_LOGE(TAG, "Error allocating memory for BLE address"); + protocomm_ble_cleanup(); + return ESP_ERR_NO_MEM; + } + memcpy(protocomm_ble_addr, config->ble_addr, BLE_ADDR_LEN); } protoble_internal = (_protocomm_ble_internal_t *) calloc(1, sizeof(_protocomm_ble_internal_t)); @@ -616,19 +819,25 @@ esp_err_t protocomm_ble_start(protocomm_t *pc, const protocomm_ble_config_t *con return ESP_ERR_NO_MEM; } - protoble_internal->g_nu_lookup_count = config->nu_lookup_count; - protoble_internal->g_nu_lookup = malloc(config->nu_lookup_count * sizeof(name_uuid128_t)); + protoble_internal->g_nu_lookup_count = endpoint_count; + protoble_internal->g_nu_lookup = calloc(endpoint_count, sizeof(name_uuid128_t)); if (protoble_internal->g_nu_lookup == NULL) { ESP_LOGE(TAG, "Error allocating internal name UUID table"); protocomm_ble_cleanup(); return ESP_ERR_NO_MEM; } - for (unsigned i = 0; i < protoble_internal->g_nu_lookup_count; i++) { + for (size_t i = 0; i < protoble_internal->g_nu_lookup_count; i++) { memcpy(protoble_internal->g_nu_lookup[i].uuid128, config->service_uuid, ESP_UUID_LEN_128); - memcpy((uint8_t *)uuid128_to_16(protoble_internal->g_nu_lookup[i].uuid128), + memcpy((uint8_t *)&protoble_internal->g_nu_lookup[i].uuid128[12], &config->nu_lookup[i].uuid, ESP_UUID_LEN_16); + if (config->nu_lookup[i].name == NULL) { + ESP_LOGE(TAG, "Invalid endpoint name"); + protocomm_ble_cleanup(); + return ESP_ERR_INVALID_ARG; + } + protoble_internal->g_nu_lookup[i].name = strdup(config->nu_lookup[i].name); if (protoble_internal->g_nu_lookup[i].name == NULL) { ESP_LOGE(TAG, "Error allocating internal name UUID entry"); @@ -646,8 +855,14 @@ esp_err_t protocomm_ble_start(protocomm_t *pc, const protocomm_ble_config_t *con // Config adv data adv_config.service_uuid_len = ESP_UUID_LEN_128; - adv_config.p_service_uuid = (uint8_t *) config->service_uuid; - protoble_internal->service_uuid = (uint8_t *) config->service_uuid; + protoble_internal->service_uuid = (uint8_t *)malloc(ESP_UUID_LEN_128); + if (protoble_internal->service_uuid == NULL) { + ESP_LOGE(TAG, "Error allocating memory for service UUID"); + protocomm_ble_cleanup(); + return ESP_ERR_NO_MEM; + } + memcpy(protoble_internal->service_uuid, config->service_uuid, ESP_UUID_LEN_128); + adv_config.p_service_uuid = protoble_internal->service_uuid; // Config scan response data scan_rsp_config.manufacturer_len = protocomm_ble_mfg_data_len; @@ -689,7 +904,8 @@ esp_err_t protocomm_ble_start(protocomm_t *pc, const protocomm_ble_config_t *con if (ble_config->gatt_db_count == -1) { ESP_LOGE(TAG, "Invalid GATT database count"); - simple_ble_deinit(); + free(ble_config->gatt_db); + free(ble_config); protocomm_ble_cleanup(); return ESP_ERR_INVALID_STATE; } @@ -727,6 +943,8 @@ esp_err_t protocomm_ble_stop(protocomm_t *pc) ret = simple_ble_disconnect(); if (ret) { ESP_LOGE(TAG, "BLE disconnect failed"); + protoble_internal->pc_ble = pc; + return ret; } simple_ble_deinit(); ble_callbacks_active = false; @@ -740,6 +958,8 @@ esp_err_t protocomm_ble_stop(protocomm_t *pc) ret = simple_ble_stop(); if (ret) { ESP_LOGE(TAG, "BLE stop failed"); + protoble_internal->pc_ble = pc; + return ret; } simple_ble_deinit(); ble_callbacks_active = false; diff --git a/components/protocomm/src/transports/protocomm_console.c b/components/protocomm/src/transports/protocomm_console.c index d09386f0ea7..ac049a7af03 100644 --- a/components/protocomm/src/transports/protocomm_console.c +++ b/components/protocomm/src/transports/protocomm_console.c @@ -94,7 +94,7 @@ static void protocomm_console_task(void *arg) } } if (event.type == UART_DATA) { - while (uart_read_bytes(uart_num, (uint8_t *) &linebuf[i], 1, 0) && (i < LINE_BUF_SIZE)) { + while ((i < LINE_BUF_SIZE - 1) && uart_read_bytes(uart_num, (uint8_t *) &linebuf[i], 1, 0)) { if (linebuf[i] == '\r') { uart_write_bytes(uart_num, "\r\n", 2); } else { @@ -106,7 +106,7 @@ static void protocomm_console_task(void *arg) if ((i > 0) && (linebuf[i-1] == '\r')) { break; } - } while (i < LINE_BUF_SIZE); + } while (i < LINE_BUF_SIZE - 1); if (stopped()) { break; } diff --git a/components/protocomm/src/transports/protocomm_nimble.c b/components/protocomm/src/transports/protocomm_nimble.c index a07af70af84..2f46b6d01a5 100644 --- a/components/protocomm/src/transports/protocomm_nimble.c +++ b/components/protocomm/src/transports/protocomm_nimble.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2019-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2019-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -9,6 +9,7 @@ #include #include #include +#include #include #include @@ -35,6 +36,13 @@ static uint16_t s_cached_conn_handle; /* Standard 16 bit UUID for characteristic User Description*/ #define BLE_GATT_UUID_CHAR_DSC 0x2901 +/* NimBLE ATT attribute values are bounded; enforce the same bound locally. */ +#ifndef BLE_ATT_ATTR_MAX_LEN +#define BLE_ATT_ATTR_MAX_LEN 512 +#endif + +#define PROTOCOMM_NIMBLE_MAX_PAYLOAD_LEN BLE_ATT_ATTR_MAX_LEN + /******************************************************** * Maintain database for Attribute specific data * ********************************************************/ @@ -68,7 +76,7 @@ void ble_store_config_init(void); typedef struct _protocomm_ble { protocomm_t *pc_ble; protocomm_ble_name_uuid_t *g_nu_lookup; - ssize_t g_nu_lookup_count; + size_t g_nu_lookup_count; uint16_t gatt_mtu; unsigned ble_link_encryption:1; unsigned ble_notify:1; @@ -117,6 +125,7 @@ typedef void (simple_ble_cb_t)(struct ble_gap_event *event, void *arg); static void transport_simple_ble_connect(struct ble_gap_event *event, void *arg); static void transport_simple_ble_disconnect(struct ble_gap_event *event, void *arg); static void transport_simple_ble_set_mtu(struct ble_gap_event *event, void *arg); +static void simple_ble_gatts_clear_cached_values(void); typedef struct { /** Name to be displayed to devices scanning for ESP32 */ @@ -193,6 +202,11 @@ simple_ble_advertise(void) { int rc; + if (adv_data.uuids128 == NULL) { + ESP_LOGD(TAG, "Not advertising: UUID data already freed"); + return; + } + adv_data.flags = (BLE_HS_ADV_F_DISC_GEN | BLE_HS_ADV_F_BREDR_UNSUP); adv_data.num_uuids128 = 1; adv_data.uuids128_is_complete = 1; @@ -259,9 +273,6 @@ simple_ble_gap_event(struct ble_gap_event *event, void *arg) transport_simple_ble_disconnect(event, arg); /* Clear conn_handle value */ s_cached_conn_handle = 0; - if (esp_event_post(PROTOCOMM_TRANSPORT_BLE_EVENT, PROTOCOMM_TRANSPORT_BLE_DISCONNECTED, NULL, 0, portMAX_DELAY) != ESP_OK) { - ESP_LOGE(TAG, "Failed to post pairing event"); - } /* Connection terminated; resume advertising. */ simple_ble_advertise(); return 0; @@ -297,12 +308,14 @@ static const char *uuid128_to_handler(uint8_t *uuid) } /* Use it to convert 128 bit UUID to 16 bit UUID.*/ uint8_t *uuid16 = uuid + 12; - for (int i = 0; i < protoble_internal->g_nu_lookup_count; i++) { - if (protoble_internal->g_nu_lookup[i].uuid == *(uint16_t *)uuid16 ) { - ESP_LOGD(TAG, "UUID (0x%x) matched with proto-name = %s", *uuid16, protoble_internal->g_nu_lookup[i].name); + uint16_t short_uuid = 0; + memcpy(&short_uuid, uuid16, sizeof(short_uuid)); + for (size_t i = 0; i < protoble_internal->g_nu_lookup_count; i++) { + if (protoble_internal->g_nu_lookup[i].uuid == short_uuid) { + ESP_LOGD(TAG, "UUID (0x%x) matched with proto-name = %s", short_uuid, protoble_internal->g_nu_lookup[i].name); return protoble_internal->g_nu_lookup[i].name; } else { - ESP_LOGD(TAG, "UUID did not match... %x", *uuid16); + ESP_LOGD(TAG, "UUID did not match... %x", short_uuid); } } return NULL; @@ -323,11 +336,20 @@ gatt_svr_dsc_access(uint16_t conn_handle, uint16_t attr_handle, struct return BLE_ATT_ERR_UNLIKELY; } - int rc; - ssize_t temp_outlen = strlen(ctxt->dsc->arg); + if (ctxt->dsc == NULL || ctxt->dsc->arg == NULL) { + ESP_LOGE(TAG, "Descriptor argument is missing"); + return BLE_ATT_ERR_UNLIKELY; + } - rc = os_mbuf_append(ctxt->om, ctxt->dsc->arg, temp_outlen); - return rc; + int rc; + size_t desc_len = strlen(ctxt->dsc->arg); + if (desc_len > PROTOCOMM_NIMBLE_MAX_PAYLOAD_LEN) { + ESP_LOGE(TAG, "Descriptor value too long: %d", (int)desc_len); + return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN; + } + + rc = os_mbuf_append(ctxt->om, ctxt->dsc->arg, desc_len); + return rc == 0 ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES; } /* Callback to handle GATT characteristic value Read & Write */ @@ -363,6 +385,20 @@ gatt_svr_chr_access(uint16_t conn_handle, uint16_t attr_handle, return 0; } + if (temp_outlen < 0 || temp_outlen > PROTOCOMM_NIMBLE_MAX_PAYLOAD_LEN) { + ESP_LOGE(TAG, "Invalid response length for attr_handle=%d: %d", attr_handle, (int)temp_outlen); + return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN; + } + + if (temp_outlen > 0 && temp_outbuf == NULL) { + ESP_LOGE(TAG, "NULL response buffer for attr_handle=%d", attr_handle); + return BLE_ATT_ERR_UNLIKELY; + } + + if (temp_outlen == 0) { + return 0; + } + rc = os_mbuf_append(ctxt->om, temp_outbuf, temp_outlen); return rc == 0 ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES; @@ -388,6 +424,11 @@ gatt_svr_chr_access(uint16_t conn_handle, uint16_t attr_handle, /* Save the length of entire data */ data_len = OS_MBUF_PKTLEN(ctxt->om); + if (data_len == 0 || data_len > PROTOCOMM_NIMBLE_MAX_PAYLOAD_LEN) { + ESP_LOGE(TAG, "Invalid write length: %d", data_len); + free(uuid); + return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN; + } ESP_LOGD(TAG, "Write attempt for uuid = %s, attr_handle = %d, data_len = %d", ble_uuid_to_str(ctxt->chr->uuid, buf), attr_handle, data_len); @@ -405,9 +446,31 @@ gatt_svr_chr_access(uint16_t conn_handle, uint16_t attr_handle, free(data_buf); return BLE_ATT_ERR_UNLIKELY; } + if (data_buf_len != data_len) { + ESP_LOGE(TAG, "Mbuf flatten length mismatch: expected=%d actual=%d", data_len, data_buf_len); + free(uuid); + free(data_buf); + return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN; + } - ret = protocomm_req_handle(protoble_internal->pc_ble, - uuid128_to_handler(uuid), + const char *ep_name = uuid128_to_handler(uuid); + if (ep_name == NULL) { + ESP_LOGE(TAG, "No endpoint mapped for characteristic UUID"); + free(uuid); + free(data_buf); + return BLE_ATT_ERR_UNLIKELY; + } + + protocomm_t *pc_ble = protoble_internal->pc_ble; + if (pc_ble == NULL) { + ESP_LOGW(TAG, "Ignoring characteristic access on inactive protocomm transport"); + free(uuid); + free(data_buf); + return BLE_ATT_ERR_UNLIKELY; + } + + ret = protocomm_req_handle(pc_ble, + ep_name, conn_handle, data_buf, data_buf_len, @@ -416,6 +479,15 @@ gatt_svr_chr_access(uint16_t conn_handle, uint16_t attr_handle, free(uuid); free(data_buf); if (ret == ESP_OK) { + if (temp_outlen < 0 || temp_outlen > PROTOCOMM_NIMBLE_MAX_PAYLOAD_LEN) { + ESP_LOGE(TAG, "Invalid protocomm response length: %d", (int)temp_outlen); + free(temp_outbuf); + return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN; + } + if (temp_outlen > 0 && temp_outbuf == NULL) { + ESP_LOGE(TAG, "Protocomm response buffer is NULL for non-zero length"); + return BLE_ATT_ERR_UNLIKELY; + } /* Save data address and length outbuf and outlen internally */ rc = simple_ble_gatts_set_attr_value(attr_handle, temp_outlen, @@ -426,7 +498,7 @@ gatt_svr_chr_access(uint16_t conn_handle, uint16_t attr_handle, free(temp_outbuf); } - return rc; + return rc == 0 ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES; } else { ESP_LOGE(TAG, "Invalid content received, killing connection"); return BLE_ATT_ERR_INVALID_PDU; @@ -575,16 +647,17 @@ static int simple_ble_start(const simple_ble_cfg_t *cfg) rc = gatt_svr_init(cfg); if (rc != 0) { ESP_LOGE(TAG, "Error initializing GATT server"); - return rc; + goto err_deinit_port; } /* Set device name, configure response data to be sent while advertising */ rc = ble_svc_gap_device_name_set(cfg->device_name); if (rc != 0) { ESP_LOGE(TAG, "Error setting device name"); - return rc; + goto err_deinit_port; } + memset(&resp_data, 0, sizeof(resp_data)); resp_data.name = (void *) ble_svc_gap_device_name(); if (resp_data.name != NULL) { resp_data.name_len = strlen(ble_svc_gap_device_name()); @@ -604,6 +677,12 @@ static int simple_ble_start(const simple_ble_cfg_t *cfg) nimble_port_freertos_init(nimble_host_task); return 0; + +#if MYNEWT_VAL(BLE_GATTS) +err_deinit_port: + nimble_port_deinit(); + return rc; +#endif } /* transport_simple BLE Fn */ @@ -624,28 +703,47 @@ static void transport_simple_ble_disconnect(struct ble_gap_event *event, void *a return; } - if (protoble_internal->pc_ble->sec && - protoble_internal->pc_ble->sec->close_transport_session) { + /* Avoid stale response reuse across sessions. */ + simple_ble_gatts_clear_cached_values(); + + protocomm_t *pc_ble = protoble_internal->pc_ble; + if (pc_ble == NULL) { + ESP_LOGD(TAG, "Protocomm BLE inactive, ignoring disconnect"); + return; + } + + if (pc_ble->sec && pc_ble->sec->close_transport_session) { ret = - protoble_internal->pc_ble->sec->close_transport_session(protoble_internal->pc_ble->sec_inst, event->disconnect.conn.conn_handle); + pc_ble->sec->close_transport_session(pc_ble->sec_inst, event->disconnect.conn.conn_handle); if (ret != ESP_OK) { ESP_LOGE(TAG, "error closing the session after disconnect"); - } else { - protocomm_ble_event_t ble_event = {}; - /* Assign the event type */ - ble_event.evt_type = PROTOCOMM_TRANSPORT_BLE_DISCONNECTED; - /* Set the Disconnection handle */ - ble_event.conn_handle = event->disconnect.conn.conn_handle; - ble_event.disconnect_reason = event->disconnect.reason; - - if (esp_event_post(PROTOCOMM_TRANSPORT_BLE_EVENT, PROTOCOMM_TRANSPORT_BLE_DISCONNECTED, &ble_event, sizeof(protocomm_ble_event_t), portMAX_DELAY) != ESP_OK) { - ESP_LOGE(TAG, "Failed to post transport disconnection event"); - } } } + + protocomm_ble_event_t ble_event = {}; + /* Assign the event type */ + ble_event.evt_type = PROTOCOMM_TRANSPORT_BLE_DISCONNECTED; + /* Set the Disconnection handle */ + ble_event.conn_handle = event->disconnect.conn.conn_handle; + ble_event.disconnect_reason = event->disconnect.reason; + + if (esp_event_post(PROTOCOMM_TRANSPORT_BLE_EVENT, PROTOCOMM_TRANSPORT_BLE_DISCONNECTED, &ble_event, sizeof(protocomm_ble_event_t), portMAX_DELAY) != ESP_OK) { + ESP_LOGE(TAG, "Failed to post transport disconnection event"); + } + protoble_internal->gatt_mtu = BLE_ATT_MTU_DFLT; } +static void simple_ble_gatts_clear_cached_values(void) +{ + struct data_mbuf *cur; + SLIST_FOREACH(cur, &data_mbuf_list, node) { + free(cur->outbuf); + cur->outbuf = NULL; + cur->outlen = 0; + } +} + static void transport_simple_ble_connect(struct ble_gap_event *event, void *arg) { esp_err_t ret; @@ -662,10 +760,15 @@ static void transport_simple_ble_connect(struct ble_gap_event *event, void *arg) return; } - if (protoble_internal->pc_ble->sec && - protoble_internal->pc_ble->sec->new_transport_session) { + protocomm_t *pc_ble = protoble_internal->pc_ble; + if (pc_ble == NULL) { + ESP_LOGD(TAG, "Protocomm BLE inactive, ignoring connect"); + return; + } + + if (pc_ble->sec && pc_ble->sec->new_transport_session) { ret = - protoble_internal->pc_ble->sec->new_transport_session(protoble_internal->pc_ble->sec_inst, event->connect.conn_handle); + pc_ble->sec->new_transport_session(pc_ble->sec_inst, event->connect.conn_handle); if (ret != ESP_OK) { ESP_LOGE(TAG, "error creating the session"); } else { @@ -813,7 +916,7 @@ ble_gatt_add_primary_svcs(struct ble_gatt_svc_def *gatt_db_svcs, int char_count) } static int -populate_gatt_db(struct ble_gatt_svc_def **gatt_db_svcs, const protocomm_ble_config_t *config) +populate_gatt_db(struct ble_gatt_svc_def **gatt_db_svcs, const protocomm_ble_config_t *config, int char_count) { /* Allocate memory for 2 services, 2nd to be all NULL indicating end of * services */ @@ -837,13 +940,13 @@ populate_gatt_db(struct ble_gatt_svc_def **gatt_db_svcs, const protocomm_ble_con memcpy((void *) (*gatt_db_svcs)->uuid, &uuid128, sizeof(ble_uuid128_t)); /* GATT: Add primary service. */ - int rc = ble_gatt_add_primary_svcs(*gatt_db_svcs, config->nu_lookup_count); + int rc = ble_gatt_add_primary_svcs(*gatt_db_svcs, char_count); if (rc != 0) { ESP_LOGE(TAG, "Error adding primary service !!!"); return rc; } - for (int i = 0 ; i < config->nu_lookup_count; i++) { + for (int i = 0 ; i < char_count; i++) { /* GATT: Add characteristics to the service at index no. i*/ rc = ble_gatt_add_characteristics((void *) (*gatt_db_svcs)->characteristics, i); @@ -862,11 +965,30 @@ populate_gatt_db(struct ble_gatt_svc_def **gatt_db_svcs, const protocomm_ble_con return 0; } +static void free_uuid128_name_table(void) +{ + /* Free the uuid_name_table struct list if exists */ + struct uuid128_name_buf *cur; + while (!SLIST_EMPTY(&uuid128_name_list)) { + cur = SLIST_FIRST(&uuid128_name_list); + SLIST_REMOVE_HEAD(&uuid128_name_list, link); + if (cur->uuid128_name_table) { + if (adv_data.uuids128 == (void *)cur->uuid128_name_table) { + adv_data.uuids128 = NULL; + adv_data.num_uuids128 = 0; + } + free(cur->uuid128_name_table); + } + free(cur); + } +} + static void protocomm_ble_cleanup(void) { + free_uuid128_name_table(); if (protoble_internal) { if (protoble_internal->g_nu_lookup) { - for (unsigned i = 0; i < protoble_internal->g_nu_lookup_count; i++) { + for (size_t i = 0; i < protoble_internal->g_nu_lookup_count; i++) { if (protoble_internal->g_nu_lookup[i].name) { free((void *)protoble_internal->g_nu_lookup[i].name); } @@ -918,18 +1040,9 @@ static void free_gatt_ble_misc_memory(simple_ble_cfg_t *ble_config) } free(ble_config); - ble_config = NULL; + ble_cfg_p = NULL; - /* Free the uuid_name_table struct list if exists */ - struct uuid128_name_buf *cur; - while (!SLIST_EMPTY(&uuid128_name_list)) { - cur = SLIST_FIRST(&uuid128_name_list); - SLIST_REMOVE_HEAD(&uuid128_name_list, link); - if (cur->uuid128_name_table) { - free(cur->uuid128_name_table); - } - free(cur); - } + free_uuid128_name_table(); /* Free the data_mbuf list if exists */ struct data_mbuf *curr; @@ -947,11 +1060,34 @@ esp_err_t protocomm_ble_start(protocomm_t *pc, const protocomm_ble_config_t *con return ESP_ERR_INVALID_ARG; } + if (config->manufacturer_data_len > 0 && config->manufacturer_data == NULL) { + ESP_LOGE(TAG, "Manufacturer data length set without data"); + return ESP_ERR_INVALID_ARG; + } + + if (config->nu_lookup_count <= 0 || config->nu_lookup_count > (ssize_t)(INT_MAX - 1)) { + ESP_LOGE(TAG, "Invalid nu_lookup_count: %d", (int)config->nu_lookup_count); + return ESP_ERR_INVALID_ARG; + } + + if (config->manufacturer_data != NULL && + (config->manufacturer_data_len <= 0 || + config->manufacturer_data_len > MAX_BLE_MANUFACTURER_DATA_LEN)) { + ESP_LOGE(TAG, "Invalid manufacturer data length: %d", (int)config->manufacturer_data_len); + return ESP_ERR_INVALID_ARG; + } + if (protoble_internal) { ESP_LOGE(TAG, "Protocomm BLE already started"); return ESP_FAIL; } + size_t endpoint_count = (size_t)config->nu_lookup_count; + if (endpoint_count > (SIZE_MAX / sizeof(protocomm_ble_name_uuid_t))) { + ESP_LOGE(TAG, "Name UUID table size overflow"); + return ESP_ERR_NO_MEM; + } + /* copy the 128 bit service UUID into local buffer to use as base 128 bit * UUID. */ memcpy(ble_uuid_base, config->service_uuid, BLE_UUID128_VAL_LENGTH); @@ -973,17 +1109,14 @@ esp_err_t protocomm_ble_start(protocomm_t *pc, const protocomm_ble_config_t *con if (temp_uuid128_name_buf == NULL) { ESP_LOGE(TAG, "Error allocating memory for UUID128 address database"); + free(svc_uuid128); + adv_data.uuids128 = NULL; + adv_data.num_uuids128 = 0; return ESP_ERR_NO_MEM; } SLIST_INSERT_HEAD(&uuid128_name_list, temp_uuid128_name_buf, link); temp_uuid128_name_buf->uuid128_name_table = svc_uuid128; - if (adv_data.uuids128 == NULL) { - ESP_LOGE(TAG, "Error allocating memory for storing service UUID"); - protocomm_ble_cleanup(); - return ESP_ERR_NO_MEM; - } - /* Store BLE device name internally */ protocomm_ble_device_name = strdup(config->device_name); if (protocomm_ble_device_name == NULL) { @@ -994,8 +1127,14 @@ esp_err_t protocomm_ble_start(protocomm_t *pc, const protocomm_ble_config_t *con /* Store BLE manufacturer data pointer */ if (config->manufacturer_data != NULL) { - protocomm_ble_mfg_data = config->manufacturer_data; - protocomm_ble_mfg_data_len = config->manufacturer_data_len; + protocomm_ble_mfg_data = (uint8_t *)malloc((size_t)config->manufacturer_data_len); + if (protocomm_ble_mfg_data == NULL) { + ESP_LOGE(TAG, "Error allocating memory for manufacturer data"); + protocomm_ble_cleanup(); + return ESP_ERR_NO_MEM; + } + memcpy(protocomm_ble_mfg_data, config->manufacturer_data, (size_t)config->manufacturer_data_len); + protocomm_ble_mfg_data_len = (size_t)config->manufacturer_data_len; } protoble_internal = (_protocomm_ble_internal_t *) calloc(1, sizeof(_protocomm_ble_internal_t)); @@ -1005,16 +1144,22 @@ esp_err_t protocomm_ble_start(protocomm_t *pc, const protocomm_ble_config_t *con return ESP_ERR_NO_MEM; } - protoble_internal->g_nu_lookup_count = config->nu_lookup_count; - protoble_internal->g_nu_lookup = malloc(config->nu_lookup_count * sizeof(protocomm_ble_name_uuid_t)); + protoble_internal->g_nu_lookup_count = endpoint_count; + protoble_internal->g_nu_lookup = calloc(endpoint_count, sizeof(protocomm_ble_name_uuid_t)); if (protoble_internal->g_nu_lookup == NULL) { ESP_LOGE(TAG, "Error allocating internal name UUID table"); protocomm_ble_cleanup(); return ESP_ERR_NO_MEM; } - for (unsigned i = 0; i < protoble_internal->g_nu_lookup_count; i++) { + for (size_t i = 0; i < protoble_internal->g_nu_lookup_count; i++) { protoble_internal->g_nu_lookup[i].uuid = config->nu_lookup[i].uuid; + if (config->nu_lookup[i].name == NULL) { + ESP_LOGE(TAG, "Invalid endpoint name"); + protocomm_ble_cleanup(); + return ESP_ERR_INVALID_ARG; + } + protoble_internal->g_nu_lookup[i].name = strdup(config->nu_lookup[i].name); if (protoble_internal->g_nu_lookup[i].name == NULL) { ESP_LOGE(TAG, "Error allocating internal name UUID entry"); @@ -1051,12 +1196,20 @@ esp_err_t protocomm_ble_start(protocomm_t *pc, const protocomm_ble_config_t *con ble_config->ble_sm_sc = config->ble_sm_sc; if (config->ble_addr != NULL) { - protocomm_ble_addr = config->ble_addr; + protocomm_ble_addr = (uint8_t *)malloc(BLE_ADDR_LEN); + if (protocomm_ble_addr == NULL) { + ESP_LOGE(TAG, "Error allocating memory for BLE address"); + free_gatt_ble_misc_memory(ble_config); + protocomm_ble_cleanup(); + return ESP_ERR_NO_MEM; + } + memcpy(protocomm_ble_addr, config->ble_addr, BLE_ADDR_LEN); } - if (populate_gatt_db(&ble_config->gatt_db, config) != 0) { + if (populate_gatt_db(&ble_config->gatt_db, config, (int)endpoint_count) != 0) { ESP_LOGE(TAG, "Error populating GATT Database"); free_gatt_ble_misc_memory(ble_config); + protocomm_ble_cleanup(); return ESP_ERR_NO_MEM; } @@ -1100,7 +1253,7 @@ esp_err_t protocomm_ble_stop(protocomm_t *pc) /* Keep BT stack on, but terminate the connection after provisioning */ rc = ble_gap_terminate(s_cached_conn_handle, BLE_ERR_REM_USER_CONN_TERM); if (rc) { - ESP_LOGI(TAG, "Error in terminating connection rc = %d",rc); + ESP_LOGI(TAG, "Error in terminating connection rc = %d", rc); } free_gatt_ble_misc_memory(ble_cfg_p); ble_callbacks_active = false; @@ -1113,6 +1266,9 @@ esp_err_t protocomm_ble_stop(protocomm_t *pc) ret = nimble_port_stop(); if (ret == 0) { nimble_port_deinit(); + } else { + protoble_internal->pc_ble = pc; + return ret; } free_gatt_ble_misc_memory(ble_cfg_p); ble_callbacks_active = false; diff --git a/components/sdmmc/sdmmc_sd.c b/components/sdmmc/sdmmc_sd.c index 156e396643f..787b0da21d6 100644 --- a/components/sdmmc/sdmmc_sd.c +++ b/components/sdmmc/sdmmc_sd.c @@ -278,7 +278,7 @@ esp_err_t sdmmc_enter_higher_speed_mode(sdmmc_card_t* card) ESP_LOGE(TAG, "%s: failed to switch bus to DDR mode (0x%x)", __func__, err); return err; } - } else if (card->host.max_freq_khz == SDMMC_FREQ_SDR104) { + } else if (card->host.max_freq_khz >= SDMMC_FREQ_SDR104) { //UHS-I SDR104 ESP_LOGV(TAG, "%s: to switch to SDR104", __func__); if ((supported_mask & BIT(SD_ACCESS_MODE_SDR104)) == 0) { @@ -290,7 +290,7 @@ esp_err_t sdmmc_enter_higher_speed_mode(sdmmc_card_t* card) ESP_LOGD(TAG, "%s: sdmmc_send_cmd_switch_func (2) returned 0x%x", __func__, err); goto out; } - } else if (card->host.max_freq_khz == SDMMC_FREQ_SDR50) { + } else if (card->host.max_freq_khz >= SDMMC_FREQ_SDR50) { //UHS-I SDR50 ESP_LOGV(TAG, "%s: to switch to SDR50", __func__); if ((supported_mask & BIT(SD_ACCESS_MODE_SDR50)) == 0) { diff --git a/components/sdmmc/test_apps/pytest_sdmmc_extra.py b/components/sdmmc/test_apps/pytest_sdmmc_extra.py index cfcbfb4d6c8..51c186ee2dc 100644 --- a/components/sdmmc/test_apps/pytest_sdmmc_extra.py +++ b/components/sdmmc/test_apps/pytest_sdmmc_extra.py @@ -6,6 +6,7 @@ from pytest_embedded_idf.utils import idf_parametrize @pytest.mark.sdcard +@pytest.mark.flaky(reruns=2, reruns_delay=5) @idf_parametrize('config', ['default'], indirect=['config']) @idf_parametrize('target', ['esp32'], indirect=['target']) def test_sdmmc_extra(dut: Dut) -> None: diff --git a/components/soc/CMakeLists.txt b/components/soc/CMakeLists.txt index c075f731ecb..f67b3495361 100644 --- a/components/soc/CMakeLists.txt +++ b/components/soc/CMakeLists.txt @@ -80,10 +80,6 @@ if(CONFIG_SOC_MPI_SUPPORTED) list(APPEND srcs "${target_folder}/mpi_periph.c") endif() -if(CONFIG_SOC_PAU_SUPPORTED AND CONFIG_SOC_LIGHT_SLEEP_SUPPORTED AND CONFIG_SOC_PM_SUPPORT_TOP_PD) - list(APPEND srcs "${target_folder}/system_retention_periph.c") -endif() - if(CONFIG_SOC_BOD_SUPPORTED) list(APPEND srcs "${target_folder}/power_supply_periph.c") endif() diff --git a/components/soc/esp32c5/include/soc/Kconfig.soc_caps.in b/components/soc/esp32c5/include/soc/Kconfig.soc_caps.in index 124c29af58e..c4f2b17aaaf 100644 --- a/components/soc/esp32c5/include/soc/Kconfig.soc_caps.in +++ b/components/soc/esp32c5/include/soc/Kconfig.soc_caps.in @@ -1682,3 +1682,11 @@ config SOC_LP_CORE_SUPPORT_ETM config SOC_LP_CORE_SUPPORT_STORE_LOAD_EXCEPTIONS bool default y + +config SOC_LP_CORE_LP_UART_WAKEUP_KEEP_TRIGGERED + bool + default y + +config SOC_LP_CORE_HW_AUTO_CLRWAKEUPCAUSE + bool + default y diff --git a/components/soc/esp32c5/include/soc/soc_caps.h b/components/soc/esp32c5/include/soc/soc_caps.h index e48b06940fc..4d544ae9e49 100644 --- a/components/soc/esp32c5/include/soc/soc_caps.h +++ b/components/soc/esp32c5/include/soc/soc_caps.h @@ -673,3 +673,5 @@ #define SOC_LP_CORE_SINGLE_INTERRUPT_VECTOR (1) /*!< LP Core interrupts all map to a single entry in vector table */ #define SOC_LP_CORE_SUPPORT_ETM (1) /*!< LP Core supports ETM */ #define SOC_LP_CORE_SUPPORT_STORE_LOAD_EXCEPTIONS (1) /*!< LP Core will raise exceptions if accessing invalid addresses */ +#define SOC_LP_CORE_LP_UART_WAKEUP_KEEP_TRIGGERED (1) /*!< LP UART wakeup source is kept triggered */ +#define SOC_LP_CORE_HW_AUTO_CLRWAKEUPCAUSE (1) /*!< LP core requests sleep, PMU clears both HP and LP wakeup causes */ diff --git a/components/soc/esp32c6/include/soc/Kconfig.soc_caps.in b/components/soc/esp32c6/include/soc/Kconfig.soc_caps.in index 0ff6514c5ca..dd9257054c8 100644 --- a/components/soc/esp32c6/include/soc/Kconfig.soc_caps.in +++ b/components/soc/esp32c6/include/soc/Kconfig.soc_caps.in @@ -1431,6 +1431,14 @@ config SOC_LP_CORE_SUPPORT_ETM bool default y +config SOC_LP_CORE_LP_UART_WAKEUP_KEEP_TRIGGERED + bool + default y + +config SOC_LP_CORE_HW_AUTO_CLRWAKEUPCAUSE + bool + default y + config SOC_DEBUG_HAVE_OCD_STUB_BINS bool default y diff --git a/components/soc/esp32c6/include/soc/soc_caps.h b/components/soc/esp32c6/include/soc/soc_caps.h index c0289d7c42b..08257dbc5dc 100644 --- a/components/soc/esp32c6/include/soc/soc_caps.h +++ b/components/soc/esp32c6/include/soc/soc_caps.h @@ -588,6 +588,8 @@ /*------------------------------------- ULP CAPS -------------------------------------*/ #define SOC_LP_CORE_SINGLE_INTERRUPT_VECTOR (1) /*!< LP Core interrupts all map to a single entry in vector table */ #define SOC_LP_CORE_SUPPORT_ETM (1) /*!< LP Core supports ETM */ +#define SOC_LP_CORE_LP_UART_WAKEUP_KEEP_TRIGGERED (1) /*!< LP UART wakeup source is kept triggered */ +#define SOC_LP_CORE_HW_AUTO_CLRWAKEUPCAUSE (1) /*!< LP core requests sleep, PMU clears both HP and LP wakeup causes */ /*------------------------------------- DEBUG CAPS -------------------------------------*/ #define SOC_DEBUG_HAVE_OCD_STUB_BINS (1) diff --git a/components/soc/esp32c61/register/soc/pcr_reg.h b/components/soc/esp32c61/register/soc/pcr_reg.h index c6d3a60ba94..b46b97820e0 100644 --- a/components/soc/esp32c61/register/soc/pcr_reg.h +++ b/components/soc/esp32c61/register/soc/pcr_reg.h @@ -1,5 +1,5 @@ /** - * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -1166,32 +1166,6 @@ extern "C" { #define PCR_SPI2_CLKM_EN_V 0x00000001U #define PCR_SPI2_CLKM_EN_S 22 -/** PCR_AES_CONF_REG register - * AES configuration register - */ -#define PCR_AES_CONF_REG (DR_REG_PCR_BASE + 0x9c) -/** PCR_AES_CLK_EN : R/W; bitpos: [0]; default: 1; - * Set 1 to enable aes clock - */ -#define PCR_AES_CLK_EN (BIT(0)) -#define PCR_AES_CLK_EN_M (PCR_AES_CLK_EN_V << PCR_AES_CLK_EN_S) -#define PCR_AES_CLK_EN_V 0x00000001U -#define PCR_AES_CLK_EN_S 0 -/** PCR_AES_RST_EN : R/W; bitpos: [1]; default: 0; - * Set 1 to reset aes module - */ -#define PCR_AES_RST_EN (BIT(1)) -#define PCR_AES_RST_EN_M (PCR_AES_RST_EN_V << PCR_AES_RST_EN_S) -#define PCR_AES_RST_EN_V 0x00000001U -#define PCR_AES_RST_EN_S 1 -/** PCR_AES_READY : RO; bitpos: [2]; default: 1; - * Query this field after reset aes module - */ -#define PCR_AES_READY (BIT(2)) -#define PCR_AES_READY_M (PCR_AES_READY_V << PCR_AES_READY_S) -#define PCR_AES_READY_V 0x00000001U -#define PCR_AES_READY_S 2 - /** PCR_SHA_CONF_REG register * SHA configuration register */ @@ -1218,58 +1192,6 @@ extern "C" { #define PCR_SHA_READY_V 0x00000001U #define PCR_SHA_READY_S 2 -/** PCR_RSA_CONF_REG register - * RSA configuration register - */ -#define PCR_RSA_CONF_REG (DR_REG_PCR_BASE + 0xa4) -/** PCR_RSA_CLK_EN : R/W; bitpos: [0]; default: 1; - * Set 1 to enable rsa clock - */ -#define PCR_RSA_CLK_EN (BIT(0)) -#define PCR_RSA_CLK_EN_M (PCR_RSA_CLK_EN_V << PCR_RSA_CLK_EN_S) -#define PCR_RSA_CLK_EN_V 0x00000001U -#define PCR_RSA_CLK_EN_S 0 -/** PCR_RSA_RST_EN : R/W; bitpos: [1]; default: 0; - * Set 1 to reset rsa module - */ -#define PCR_RSA_RST_EN (BIT(1)) -#define PCR_RSA_RST_EN_M (PCR_RSA_RST_EN_V << PCR_RSA_RST_EN_S) -#define PCR_RSA_RST_EN_V 0x00000001U -#define PCR_RSA_RST_EN_S 1 -/** PCR_RSA_READY : RO; bitpos: [2]; default: 1; - * Query this field after reset rsa module - */ -#define PCR_RSA_READY (BIT(2)) -#define PCR_RSA_READY_M (PCR_RSA_READY_V << PCR_RSA_READY_S) -#define PCR_RSA_READY_V 0x00000001U -#define PCR_RSA_READY_S 2 - -/** PCR_RSA_PD_CTRL_REG register - * RSA power control register - */ -#define PCR_RSA_PD_CTRL_REG (DR_REG_PCR_BASE + 0xa8) -/** PCR_RSA_MEM_PD : R/W; bitpos: [0]; default: 0; - * Set this bit to power down rsa internal memory. - */ -#define PCR_RSA_MEM_PD (BIT(0)) -#define PCR_RSA_MEM_PD_M (PCR_RSA_MEM_PD_V << PCR_RSA_MEM_PD_S) -#define PCR_RSA_MEM_PD_V 0x00000001U -#define PCR_RSA_MEM_PD_S 0 -/** PCR_RSA_MEM_FORCE_PU : R/W; bitpos: [1]; default: 1; - * Set this bit to force power up rsa internal memory - */ -#define PCR_RSA_MEM_FORCE_PU (BIT(1)) -#define PCR_RSA_MEM_FORCE_PU_M (PCR_RSA_MEM_FORCE_PU_V << PCR_RSA_MEM_FORCE_PU_S) -#define PCR_RSA_MEM_FORCE_PU_V 0x00000001U -#define PCR_RSA_MEM_FORCE_PU_S 1 -/** PCR_RSA_MEM_FORCE_PD : R/W; bitpos: [2]; default: 0; - * Set this bit to force power down rsa internal memory. - */ -#define PCR_RSA_MEM_FORCE_PD (BIT(2)) -#define PCR_RSA_MEM_FORCE_PD_M (PCR_RSA_MEM_FORCE_PD_V << PCR_RSA_MEM_FORCE_PD_S) -#define PCR_RSA_MEM_FORCE_PD_V 0x00000001U -#define PCR_RSA_MEM_FORCE_PD_S 2 - /** PCR_ECC_CONF_REG register * ECC configuration register */ @@ -1322,58 +1244,6 @@ extern "C" { #define PCR_ECC_MEM_FORCE_PD_V 0x00000001U #define PCR_ECC_MEM_FORCE_PD_S 2 -/** PCR_DS_CONF_REG register - * DS configuration register - */ -#define PCR_DS_CONF_REG (DR_REG_PCR_BASE + 0xb4) -/** PCR_DS_CLK_EN : R/W; bitpos: [0]; default: 1; - * Set 1 to enable ds clock - */ -#define PCR_DS_CLK_EN (BIT(0)) -#define PCR_DS_CLK_EN_M (PCR_DS_CLK_EN_V << PCR_DS_CLK_EN_S) -#define PCR_DS_CLK_EN_V 0x00000001U -#define PCR_DS_CLK_EN_S 0 -/** PCR_DS_RST_EN : R/W; bitpos: [1]; default: 0; - * Set 1 to reset ds module - */ -#define PCR_DS_RST_EN (BIT(1)) -#define PCR_DS_RST_EN_M (PCR_DS_RST_EN_V << PCR_DS_RST_EN_S) -#define PCR_DS_RST_EN_V 0x00000001U -#define PCR_DS_RST_EN_S 1 -/** PCR_DS_READY : RO; bitpos: [2]; default: 1; - * Query this field after reset ds module - */ -#define PCR_DS_READY (BIT(2)) -#define PCR_DS_READY_M (PCR_DS_READY_V << PCR_DS_READY_S) -#define PCR_DS_READY_V 0x00000001U -#define PCR_DS_READY_S 2 - -/** PCR_HMAC_CONF_REG register - * HMAC configuration register - */ -#define PCR_HMAC_CONF_REG (DR_REG_PCR_BASE + 0xb8) -/** PCR_HMAC_CLK_EN : R/W; bitpos: [0]; default: 1; - * Set 1 to enable hmac clock - */ -#define PCR_HMAC_CLK_EN (BIT(0)) -#define PCR_HMAC_CLK_EN_M (PCR_HMAC_CLK_EN_V << PCR_HMAC_CLK_EN_S) -#define PCR_HMAC_CLK_EN_V 0x00000001U -#define PCR_HMAC_CLK_EN_S 0 -/** PCR_HMAC_RST_EN : R/W; bitpos: [1]; default: 0; - * Set 1 to reset hmac module - */ -#define PCR_HMAC_RST_EN (BIT(1)) -#define PCR_HMAC_RST_EN_M (PCR_HMAC_RST_EN_V << PCR_HMAC_RST_EN_S) -#define PCR_HMAC_RST_EN_V 0x00000001U -#define PCR_HMAC_RST_EN_S 1 -/** PCR_HMAC_READY : RO; bitpos: [2]; default: 1; - * Query this field after reset hmac module - */ -#define PCR_HMAC_READY (BIT(2)) -#define PCR_HMAC_READY_M (PCR_HMAC_READY_V << PCR_HMAC_READY_S) -#define PCR_HMAC_READY_V 0x00000001U -#define PCR_HMAC_READY_S 2 - /** PCR_ECDSA_CONF_REG register * ECDSA configuration register */ diff --git a/components/soc/esp32c61/register/soc/pcr_struct.h b/components/soc/esp32c61/register/soc/pcr_struct.h index 97b48ecdb5e..180c5c722bd 100644 --- a/components/soc/esp32c61/register/soc/pcr_struct.h +++ b/components/soc/esp32c61/register/soc/pcr_struct.h @@ -1,5 +1,5 @@ /** - * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -997,28 +997,6 @@ typedef union { uint32_t val; } pcr_spi2_clkm_conf_reg_t; -/** Type of aes_conf register - * AES configuration register - */ -typedef union { - struct { - /** aes_clk_en : R/W; bitpos: [0]; default: 1; - * Set 1 to enable aes clock - */ - uint32_t aes_clk_en:1; - /** aes_rst_en : R/W; bitpos: [1]; default: 0; - * Set 1 to reset aes module - */ - uint32_t aes_rst_en:1; - /** aes_ready : RO; bitpos: [2]; default: 1; - * Query this field after reset aes module - */ - uint32_t aes_ready:1; - uint32_t reserved_3:29; - }; - uint32_t val; -} pcr_aes_conf_reg_t; - /** Type of sha_conf register * SHA configuration register */ @@ -1041,50 +1019,6 @@ typedef union { uint32_t val; } pcr_sha_conf_reg_t; -/** Type of rsa_conf register - * RSA configuration register - */ -typedef union { - struct { - /** rsa_clk_en : R/W; bitpos: [0]; default: 1; - * Set 1 to enable rsa clock - */ - uint32_t rsa_clk_en:1; - /** rsa_rst_en : R/W; bitpos: [1]; default: 0; - * Set 1 to reset rsa module - */ - uint32_t rsa_rst_en:1; - /** rsa_ready : RO; bitpos: [2]; default: 1; - * Query this field after reset rsa module - */ - uint32_t rsa_ready:1; - uint32_t reserved_3:29; - }; - uint32_t val; -} pcr_rsa_conf_reg_t; - -/** Type of rsa_pd_ctrl register - * RSA power control register - */ -typedef union { - struct { - /** rsa_mem_pd : R/W; bitpos: [0]; default: 0; - * Set this bit to power down rsa internal memory. - */ - uint32_t rsa_mem_pd:1; - /** rsa_mem_force_pu : R/W; bitpos: [1]; default: 1; - * Set this bit to force power up rsa internal memory - */ - uint32_t rsa_mem_force_pu:1; - /** rsa_mem_force_pd : R/W; bitpos: [2]; default: 0; - * Set this bit to force power down rsa internal memory. - */ - uint32_t rsa_mem_force_pd:1; - uint32_t reserved_3:29; - }; - uint32_t val; -} pcr_rsa_pd_ctrl_reg_t; - /** Type of ecc_conf register * ECC configuration register */ @@ -1129,50 +1063,6 @@ typedef union { uint32_t val; } pcr_ecc_pd_ctrl_reg_t; -/** Type of ds_conf register - * DS configuration register - */ -typedef union { - struct { - /** ds_clk_en : R/W; bitpos: [0]; default: 1; - * Set 1 to enable ds clock - */ - uint32_t ds_clk_en:1; - /** ds_rst_en : R/W; bitpos: [1]; default: 0; - * Set 1 to reset ds module - */ - uint32_t ds_rst_en:1; - /** ds_ready : RO; bitpos: [2]; default: 1; - * Query this field after reset ds module - */ - uint32_t ds_ready:1; - uint32_t reserved_3:29; - }; - uint32_t val; -} pcr_ds_conf_reg_t; - -/** Type of hmac_conf register - * HMAC configuration register - */ -typedef union { - struct { - /** hmac_clk_en : R/W; bitpos: [0]; default: 1; - * Set 1 to enable hmac clock - */ - uint32_t hmac_clk_en:1; - /** hmac_rst_en : R/W; bitpos: [1]; default: 0; - * Set 1 to reset hmac module - */ - uint32_t hmac_rst_en:1; - /** hmac_ready : RO; bitpos: [2]; default: 1; - * Query this field after reset hmac module - */ - uint32_t hmac_ready:1; - uint32_t reserved_3:29; - }; - uint32_t val; -} pcr_hmac_conf_reg_t; - /** Type of ecdsa_conf register * ECDSA configuration register */ @@ -1952,14 +1842,14 @@ typedef struct { volatile pcr_gdma_conf_reg_t gdma_conf; volatile pcr_spi2_conf_reg_t spi2_conf; volatile pcr_spi2_clkm_conf_reg_t spi2_clkm_conf; - volatile pcr_aes_conf_reg_t aes_conf; + uint32_t reserved_9c; volatile pcr_sha_conf_reg_t sha_conf; - volatile pcr_rsa_conf_reg_t rsa_conf; - volatile pcr_rsa_pd_ctrl_reg_t rsa_pd_ctrl; + uint32_t reserved_a4; + uint32_t reserved_a8; volatile pcr_ecc_conf_reg_t ecc_conf; volatile pcr_ecc_pd_ctrl_reg_t ecc_pd_ctrl; - volatile pcr_ds_conf_reg_t ds_conf; - volatile pcr_hmac_conf_reg_t hmac_conf; + uint32_t reserved_b4; + uint32_t reserved_b8; volatile pcr_ecdsa_conf_reg_t ecdsa_conf; volatile pcr_iomux_conf_reg_t iomux_conf; volatile pcr_iomux_clk_conf_reg_t iomux_clk_conf; diff --git a/components/soc/esp32h21/include/soc/Kconfig.soc_caps.in b/components/soc/esp32h21/include/soc/Kconfig.soc_caps.in index 692dbefe851..c949f89f634 100644 --- a/components/soc/esp32h21/include/soc/Kconfig.soc_caps.in +++ b/components/soc/esp32h21/include/soc/Kconfig.soc_caps.in @@ -753,7 +753,7 @@ config SOC_SECURE_BOOT_V2_RSA config SOC_SECURE_BOOT_V2_ECC bool - default y + default n config SOC_EFUSE_SECURE_BOOT_KEY_DIGESTS int diff --git a/components/soc/esp32h21/include/soc/soc_caps.h b/components/soc/esp32h21/include/soc/soc_caps.h index d10d57a7ac1..90bff314944 100644 --- a/components/soc/esp32h21/include/soc/soc_caps.h +++ b/components/soc/esp32h21/include/soc/soc_caps.h @@ -430,7 +430,7 @@ /*-------------------------- Secure Boot CAPS----------------------------*/ #define SOC_SECURE_BOOT_V2_RSA 1 -#define SOC_SECURE_BOOT_V2_ECC 1 +#define SOC_SECURE_BOOT_V2_ECC 0 #define SOC_EFUSE_SECURE_BOOT_KEY_DIGESTS 3 #define SOC_EFUSE_REVOKE_BOOT_KEY_DIGESTS 1 #define SOC_SUPPORT_SECURE_BOOT_REVOKE_KEY 1 diff --git a/components/soc/esp32h4/include/soc/clk_tree_defs.h b/components/soc/esp32h4/include/soc/clk_tree_defs.h index 6eb3af27919..03406804cec 100644 --- a/components/soc/esp32h4/include/soc/clk_tree_defs.h +++ b/components/soc/esp32h4/include/soc/clk_tree_defs.h @@ -251,7 +251,7 @@ typedef enum { * @brief Type of SPI clock source. */ typedef enum { - SPI_CLK_SRC_DEFAULT = SOC_MOD_CLK_PLL_F48M, /*!< Select XTAL as SPI source clock */ + SPI_CLK_SRC_DEFAULT = SOC_MOD_CLK_PLL_F48M, /*!< Select PLL_F48M as SPI source clock */ SPI_CLK_SRC_XTAL = SOC_MOD_CLK_XTAL, /*!< Select XTAL as SPI source clock */ SPI_CLK_SRC_PLL_F48M = SOC_MOD_CLK_PLL_F48M, /*!< Select PLL_48M as SPI source clock */ SPI_CLK_SRC_RC_FAST = SOC_MOD_CLK_RC_FAST, /*!< Select RC_FAST as SPI source clock */ diff --git a/components/soc/esp32p4/include/soc/Kconfig.soc_caps.in b/components/soc/esp32p4/include/soc/Kconfig.soc_caps.in index 5b79c33908a..9229f10dd75 100644 --- a/components/soc/esp32p4/include/soc/Kconfig.soc_caps.in +++ b/components/soc/esp32p4/include/soc/Kconfig.soc_caps.in @@ -1175,6 +1175,10 @@ config SOC_USB_UTMI_PHY_NO_POWER_OFF_ISO bool default y +config SOC_PM_SUPPORT_USB_WAKEUP + int + default 1 + config SOC_PARLIO_TX_UNIT_MAX_DATA_WIDTH int default 16 @@ -1986,3 +1990,11 @@ config SOC_LP_CORE_SUPPORT_LP_ADC config SOC_LP_CORE_SUPPORT_STORE_LOAD_EXCEPTIONS bool default y + +config SOC_LP_CORE_LP_UART_WAKEUP_KEEP_TRIGGERED + bool + default y + +config SOC_LP_CORE_HW_AUTO_CLRWAKEUPCAUSE + bool + default y diff --git a/components/soc/esp32p4/include/soc/soc_caps.h b/components/soc/esp32p4/include/soc/soc_caps.h index 1695ac50a2a..8c236201355 100644 --- a/components/soc/esp32p4/include/soc/soc_caps.h +++ b/components/soc/esp32p4/include/soc/soc_caps.h @@ -435,6 +435,7 @@ // USB PHY Caps #define SOC_USB_UTMI_PHY_NUM (1U) #define SOC_USB_UTMI_PHY_NO_POWER_OFF_ISO 1 +#define SOC_PM_SUPPORT_USB_WAKEUP (1U) /*-------------------------- PARLIO CAPS --------------------------------------*/ #define SOC_PARLIO_TX_UNIT_MAX_DATA_WIDTH 16 /*!< Number of data lines of the TX unit */ @@ -769,3 +770,5 @@ #define SOC_LP_CORE_SUPPORT_ETM (1) /*!< LP Core supports ETM */ #define SOC_LP_CORE_SUPPORT_LP_ADC (1) /*!< LP ADC can be accessed from the LP-Core */ #define SOC_LP_CORE_SUPPORT_STORE_LOAD_EXCEPTIONS (1) /*!< LP Core will raise exceptions if accessing invalid addresses */ +#define SOC_LP_CORE_LP_UART_WAKEUP_KEEP_TRIGGERED (1) /*!< LP UART wakeup source is kept triggered */ +#define SOC_LP_CORE_HW_AUTO_CLRWAKEUPCAUSE (1) /*!< LP core requests sleep, PMU clears both HP and LP wakeup causes */ diff --git a/components/spi_flash/esp_flash_spi_init.c b/components/spi_flash/esp_flash_spi_init.c index f66a82ab03c..22db660c7ff 100644 --- a/components/spi_flash/esp_flash_spi_init.c +++ b/components/spi_flash/esp_flash_spi_init.c @@ -56,6 +56,8 @@ __attribute__((unused)) static const char TAG[] = "spi_flash"; esp_flash_t *esp_flash_default_chip = NULL; #endif +#define ESP_FLASH_GPSPI_PERIPH_SRC_FREQ_MAX (80*1000*1000) //peripheral hardware limitation for clock source into peripheral + #if defined CONFIG_ESPTOOLPY_FLASHFREQ_120M #define DEFAULT_FLASH_SPEED 120 #elif defined CONFIG_ESPTOOLPY_FLASHFREQ_80M @@ -262,13 +264,13 @@ static esp_err_t acquire_spi_device(const esp_flash_spi_device_config_t *config, #if GPSPI_FLASH_LL_SUPPORT_CLK_SRC_PRE_DIV static uint32_t s_spi_find_clock_src_pre_div(uint32_t src_freq, uint32_t target_freq) { - // pre division must be even and at least 2 - uint32_t min_div = ((src_freq / GPSPI_FLASH_LL_PERIPHERAL_FREQUENCY_MHZ) + 1) & (~0x01UL); - min_div = min_div < 2 ? 2 : min_div; + // no timing tuning, no need pre division to be even + uint32_t min_div = (src_freq / ESP_FLASH_GPSPI_PERIPH_SRC_FREQ_MAX); + min_div = min_div < 1 ? 1 : min_div; uint32_t total_div = src_freq / target_freq; // Loop the `div` to find a divisible value of `total_div` - for (uint32_t pre_div = min_div; pre_div <= total_div; pre_div += 2) { + for (uint32_t pre_div = min_div; pre_div <= total_div; pre_div += 1) { if ((total_div % pre_div) || (total_div / pre_div) > GPSPI_FLASH_LL_PERIPH_CLK_DIV_MAX) { continue; } @@ -306,9 +308,9 @@ static uint32_t init_gpspi_clock(esp_flash_t *chip, const esp_flash_spi_device_c // Calculate final clock source frequency uint32_t final_freq_mhz; #if GPSPI_FLASH_LL_SUPPORT_CLK_SRC_PRE_DIV - uint32_t pre_div = s_spi_find_clock_src_pre_div(clk_src_freq, GPSPI_FLASH_LL_PERIPHERAL_FREQUENCY_MHZ * 1000 * 1000); - gpspi_flash_ll_clk_source_pre_div(spi_flash_ll_get_hw(config->host_id), pre_div / 2, 2); - final_freq_mhz = clk_src_freq / (pre_div); + uint32_t pre_div = s_spi_find_clock_src_pre_div(clk_src_freq, config->freq_mhz * 1000 * 1000); + gpspi_flash_ll_clk_source_pre_div(spi_flash_ll_get_hw(config->host_id), pre_div, 1); + final_freq_mhz = clk_src_freq / (1000 * 1000) / pre_div; #else final_freq_mhz = clk_src_freq / (1 * 1000 * 1000); #endif diff --git a/components/spi_flash/test_apps/esp_flash/main/test_esp_flash_drv.c b/components/spi_flash/test_apps/esp_flash/main/test_esp_flash_drv.c index d32ee9649b7..1d50dc31893 100644 --- a/components/spi_flash/test_apps/esp_flash/main/test_esp_flash_drv.c +++ b/components/spi_flash/test_apps/esp_flash/main/test_esp_flash_drv.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2022-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Unlicense OR CC0-1.0 */ @@ -597,11 +597,11 @@ TEST_CASE_MULTI_FLASH_IGNORE("Test esp_flash_write can toggle QE bit", test_togg // This table could be chip specific in the future. #if CONFIG_IDF_TARGET_ESP32C2 -uint8_t flash_frequency_table[4] = {5, 10, 20, 40}; +uint8_t flash_frequency_table[] = {5, 10, 20, 40}; #elif CONFIG_IDF_TARGET_ESP32H2 || CONFIG_IDF_TARGET_ESP32H21 || CONFIG_IDF_TARGET_ESP32H4 -uint8_t flash_frequency_table[4] = {6, 12, 24, 48}; +uint8_t flash_frequency_table[] = {8, 16, 24, 48}; #else -uint8_t flash_frequency_table[6] = {5, 10, 20, 26, 40, 80}; +uint8_t flash_frequency_table[] = {5, 10, 20, 26, 40, 80}; #endif #define TEST_FLASH_SPEED_MIN 5 void test_permutations_part(const flashtest_config_t* config, esp_partition_t* part, void* source_buf, size_t length) diff --git a/components/spi_flash/test_apps/flash_encryption/partitions.csv b/components/spi_flash/test_apps/flash_encryption/partitions.csv index c941d8f4f1c..f5933e6f71f 100644 --- a/components/spi_flash/test_apps/flash_encryption/partitions.csv +++ b/components/spi_flash/test_apps/flash_encryption/partitions.csv @@ -1,5 +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 +nvs, data, nvs, , 0x6000, +factory, 0, 0, , 1M flash_test, data, fat, , 528K diff --git a/components/spi_flash/test_apps/flash_encryption/sdkconfig.defaults b/components/spi_flash/test_apps/flash_encryption/sdkconfig.defaults index d0ef86b66cf..c41595c93ec 100644 --- a/components/spi_flash/test_apps/flash_encryption/sdkconfig.defaults +++ b/components/spi_flash/test_apps/flash_encryption/sdkconfig.defaults @@ -1,4 +1,5 @@ CONFIG_ESP_TASK_WDT_EN=n +CONFIG_PARTITION_TABLE_OFFSET=0X9000 CONFIG_PARTITION_TABLE_CUSTOM=y CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv" CONFIG_SECURE_FLASH_ENC_ENABLED=y diff --git a/components/spiffs/test_apps/.build-test-rules.yml b/components/spiffs/test_apps/.build-test-rules.yml index 9dfa3edc933..9fac1f8066d 100644 --- a/components/spiffs/test_apps/.build-test-rules.yml +++ b/components/spiffs/test_apps/.build-test-rules.yml @@ -1,6 +1,6 @@ components/spiffs/test_apps: disable_test: - - if: IDF_TARGET not in ["esp32", "esp32c3"] + - if: IDF_TARGET not in ["esp32", "esp32c3", "esp32s3"] reason: These chips should be sufficient for test coverage (Xtensa and RISC-V, single and dual core) depends_components: diff --git a/components/spiffs/test_apps/pytest_spiffs.py b/components/spiffs/test_apps/pytest_spiffs.py index f0743781132..f8dea17564c 100644 --- a/components/spiffs/test_apps/pytest_spiffs.py +++ b/components/spiffs/test_apps/pytest_spiffs.py @@ -6,6 +6,7 @@ from pytest_embedded_idf.utils import idf_parametrize @pytest.mark.generic +@pytest.mark.flaky(reruns=2, reruns_delay=5) @pytest.mark.parametrize( 'config', [ @@ -17,3 +18,16 @@ from pytest_embedded_idf.utils import idf_parametrize @idf_parametrize('target', ['esp32', 'esp32c3'], indirect=['target']) def test_spiffs_generic(dut: Dut) -> None: dut.run_all_single_board_cases(timeout=120) + + +@pytest.mark.quad_psram +@pytest.mark.parametrize( + 'config', + [ + 'psram', + ], + indirect=True, +) +@idf_parametrize('target', ['esp32s3'], indirect=['target']) +def test_spiffs_psram(dut: Dut) -> None: + dut.run_all_single_board_cases(timeout=120) diff --git a/components/spiffs/test_apps/sdkconfig.ci.psram b/components/spiffs/test_apps/sdkconfig.ci.psram new file mode 100644 index 00000000000..e69de29bb2d diff --git a/components/tcp_transport/transport_ws.c b/components/tcp_transport/transport_ws.c index ad59360c5a8..b282f3fda04 100644 --- a/components/tcp_transport/transport_ws.c +++ b/components/tcp_transport/transport_ws.c @@ -626,12 +626,15 @@ static int ws_read_header(esp_transport_handle_t t, char *buffer, int len, int t return rlen; } - if (data_ptr[0] != 0 || data_ptr[1] != 0 || data_ptr[2] != 0 || data_ptr[3] != 0) { - // really too big! - payload_len = 0xFFFFFFFF; - } else { - payload_len = (uint8_t)data_ptr[4] << 24 | (uint8_t)data_ptr[5] << 16 | (uint8_t)data_ptr[6] << 8 | data_ptr[7]; + if (data_ptr[0] != 0 || data_ptr[1] != 0 || data_ptr[2] != 0 || data_ptr[3] != 0 || + ((uint8_t)data_ptr[4] & 0x80)) { + ESP_LOGE(TAG, "Payload length out of range"); + return -1; } + payload_len = (int)((uint32_t)(uint8_t)data_ptr[4] << 24 | + (uint32_t)(uint8_t)data_ptr[5] << 16 | + (uint32_t)(uint8_t)data_ptr[6] << 8 | + (uint32_t)(uint8_t)data_ptr[7]); } // RFC 6455 Section 5.5: Control frames MUST have payload length of 125 bytes or less if ((ws->frame_state.opcode & WS_OPCODE_CONTROL_FRAME) && payload_len > 125) { diff --git a/components/ulp/lp_core/lp_core.c b/components/ulp/lp_core/lp_core.c index f1cde8d7560..293a286271c 100644 --- a/components/ulp/lp_core/lp_core.c +++ b/components/ulp/lp_core/lp_core.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2023-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2023-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -35,6 +35,11 @@ extern uint32_t _rtc_ulp_memory_start; #endif //ESP_ROM_HAS_LP_ROM +#if SOC_LP_CORE_HW_AUTO_CLRWAKEUPCAUSE +#include "hal/lp_aon_hal.h" +#include "rom/rtc.h" +#endif + const static char* TAG = "ulp-lp-core"; #define WAKEUP_SOURCE_MAX_NUMBER 6 @@ -117,16 +122,9 @@ esp_err_t ulp_lp_core_run(ulp_lp_core_cfg_t* cfg) otherwise configured HW breakpoints and dcsr.ebreak* bits will be missed */ lp_core_ll_rst_at_sleep_enable(!(CONFIG_ULP_NORESET_UNDER_DEBUG && esp_cpu_dbgr_is_attached())); - /* Set wake-up sources */ - lp_core_ll_set_wakeup_source(lp_core_get_wakeup_source_hw_flags(cfg->wakeup_source)); - /* Enable JTAG debugging */ lp_core_ll_debug_module_enable(true); - if (cfg->wakeup_source & ULP_LP_CORE_WAKEUP_SOURCE_HP_CPU) { - lp_core_ll_hp_wake_lp(); - } - #if SOC_ULP_LP_UART_SUPPORTED if (cfg->wakeup_source & ULP_LP_CORE_WAKEUP_SOURCE_LP_UART) { lp_core_ll_enable_lp_uart_wakeup(true); @@ -148,6 +146,13 @@ esp_err_t ulp_lp_core_run(ulp_lp_core_cfg_t* cfg) } #endif + /* Set wake-up sources */ + lp_core_ll_set_wakeup_source(lp_core_get_wakeup_source_hw_flags(cfg->wakeup_source)); + + if (cfg->wakeup_source & ULP_LP_CORE_WAKEUP_SOURCE_HP_CPU) { + lp_core_ll_hp_wake_lp(); + } + return ESP_OK; } @@ -175,6 +180,19 @@ esp_err_t ulp_lp_core_load_binary(const uint8_t* program_binary, size_t program_ return ESP_OK; } +void ulp_lp_core_sleep_start(void) +{ +#if SOC_LP_CORE_HW_AUTO_CLRWAKEUPCAUSE + /* LP store register to save wakeup cause for HP core to query. + * Using a hardware register avoids symbol linking issues between + * the independently compiled HP and LP core binaries. + * Save PMU wakeup cause to LP store register for HP core to query */ + lp_aon_hal_store_wakeup_cause(pmu_ll_hp_get_wakeup_cause(&PMU)); +#endif + + lp_core_ll_request_sleep(); +} + void ulp_lp_core_stop(void) { if (esp_cpu_dbgr_is_attached()) { @@ -188,7 +206,7 @@ void ulp_lp_core_stop(void) } /* Disable wake-up source and put lp core to sleep */ lp_core_ll_set_wakeup_source(0); - lp_core_ll_request_sleep(); + ulp_lp_core_sleep_start(); } void ulp_lp_core_sw_intr_to_lp_trigger(void) diff --git a/components/ulp/lp_core/lp_core/include/ulp_lp_core_i2c.h b/components/ulp/lp_core/lp_core/include/ulp_lp_core_i2c.h index a98f5ebb1ae..caf732cccbd 100644 --- a/components/ulp/lp_core/lp_core/include/ulp_lp_core_i2c.h +++ b/components/ulp/lp_core/lp_core/include/ulp_lp_core_i2c.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2023-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -14,6 +14,7 @@ extern "C" { #include #include "hal/i2c_types.h" #include "esp_err.h" +#include "hal/i2c_ll.h" /** * @brief Read from I2C device @@ -99,6 +100,34 @@ esp_err_t lp_core_i2c_master_write_read_device(i2c_port_t lp_i2c_num, uint16_t d */ void lp_core_i2c_master_set_ack_check_en(i2c_port_t lp_i2c_num, bool ack_check_en); +#if SOC_LP_I2C_SUPPORTED +/** + * @brief Enable LP I2C master-related interrupts at the peripheral + * + * Enables the same interrupt sources used by the LP I2C master driver (see I2C_LL_MASTER_EVENT_INTR). + * + * @param lp_i2c_num Must be the LP I2C port (e.g. LP_I2C_NUM_0), not an HP I2C port. + * @param mask Interrupt mask needs to be enabled + */ +static inline void ulp_lp_core_lp_i2c_intr_enable(i2c_port_t lp_i2c_num, uint32_t mask) +{ + HAL_ASSERT(lp_i2c_num == LP_I2C_NUM_0); + i2c_ll_enable_intr_mask(I2C_LL_GET_HW(lp_i2c_num), mask); +} + +/** + * @brief Disable LP I2C master-related interrupts at the peripheral + * + * @param lp_i2c_num Must be the LP I2C port (e.g. LP_I2C_NUM_0), not an HP I2C port. + * @param mask Interrupt mask needs to be disabled + */ +static inline void ulp_lp_core_lp_i2c_intr_disable(i2c_port_t lp_i2c_num, uint32_t mask) +{ + HAL_ASSERT(lp_i2c_num == LP_I2C_NUM_0); + i2c_ll_disable_intr_mask(I2C_LL_GET_HW(lp_i2c_num), mask); +} +#endif /* SOC_LP_I2C_SUPPORTED */ + #ifdef __cplusplus } #endif diff --git a/components/ulp/lp_core/lp_core/include/ulp_lp_core_uart.h b/components/ulp/lp_core/lp_core/include/ulp_lp_core_uart.h index 5b125e5c0f4..f8f06e5040d 100644 --- a/components/ulp/lp_core/lp_core/include/ulp_lp_core_uart.h +++ b/components/ulp/lp_core/lp_core/include/ulp_lp_core_uart.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2023-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -13,6 +13,31 @@ extern "C" { #include #include "esp_err.h" #include "hal/uart_types.h" +#include "hal/uart_ll.h" + +#if SOC_LP_UART_SUPPORTED +/** + * @brief LP UART peripheral interrupt enable + * @param uart_num UART port number + * @param mask Interrupt mask needs to be enabled + */ +static inline void ulp_lp_core_lp_uart_intr_enable(uart_port_t uart_num, uint32_t mask) +{ + HAL_ASSERT(uart_num == LP_UART_NUM_0); + uart_ll_ena_intr_mask(UART_LL_GET_HW(uart_num), mask); +} + +/** + * @brief LP UART peripheral interrupt disable + * @param uart_num UART port number + * @param mask Interrupt mask needs to be disabled + */ +static inline void ulp_lp_core_lp_uart_intr_disable(uart_port_t uart_num, uint32_t mask) +{ + HAL_ASSERT(uart_num == LP_UART_NUM_0); + uart_ll_disable_intr_mask(UART_LL_GET_HW(uart_num), mask); +} +#endif /* SOC_LP_UART_SUPPORTED */ /** * @brief Send data to the LP UART port if there is space available in the Tx FIFO diff --git a/components/ulp/lp_core/lp_core/lp_core_utils.c b/components/ulp/lp_core/lp_core/lp_core_utils.c index c936956fca5..e976037af8f 100644 --- a/components/ulp/lp_core/lp_core/lp_core_utils.c +++ b/components/ulp/lp_core/lp_core/lp_core_utils.c @@ -25,9 +25,23 @@ #include "esp_cpu.h" #include "ulp_lp_core_cpu_freq_shared.h" +#include "ulp_lp_core_lp_uart_shared.h" +#include "ulp_lp_core_uart.h" +#if SOC_LP_CORE_HW_AUTO_CLRWAKEUPCAUSE +#include "hal/lp_aon_hal.h" +#include "rom/rtc.h" +#endif static uint32_t lp_wakeup_cause = 0; +#if SOC_ULP_LP_UART_SUPPORTED +void ulp_lp_core_lp_uart_reset_wakeup_en(void) +{ + lp_core_ll_enable_lp_uart_wakeup(false); + lp_core_ll_enable_lp_uart_wakeup(true); +} +#endif + void ulp_lp_core_update_wakeup_cause(void) { lp_wakeup_cause = 0; @@ -42,6 +56,13 @@ void ulp_lp_core_update_wakeup_cause(void) && (uart_ll_get_intraw_mask(&LP_UART) & LP_UART_WAKEUP_INT_RAW)) { lp_wakeup_cause |= LP_CORE_LL_WAKEUP_SOURCE_LP_UART; uart_ll_clr_intsts_mask(&LP_UART, LP_UART_WAKEUP_INT_CLR); +#if SOC_LP_CORE_LP_UART_WAKEUP_KEEP_TRIGGERED + // In these chips, the LP UART wakeup source is kept triggered, so we need to + // reset the wakeup register and flush the UART buffer manually after waking up. + lp_core_uart_tx_flush(LP_UART_NUM_0); + lp_core_uart_clear_buf(); + ulp_lp_core_lp_uart_reset_wakeup_en(); +#endif } #endif @@ -133,18 +154,22 @@ void ulp_lp_core_delay_cycles(uint32_t cycles) } } -#if SOC_ULP_LP_UART_SUPPORTED - -void ulp_lp_core_lp_uart_reset_wakeup_en(void) +void ulp_lp_core_sleep_start_lp_core(void) { - lp_core_ll_enable_lp_uart_wakeup(false); - lp_core_ll_enable_lp_uart_wakeup(true); -} +#if SOC_LP_CORE_HW_AUTO_CLRWAKEUPCAUSE + /* LP store register to save wakeup cause for HP core to query. + * Using a hardware register avoids symbol linking issues between + * the independently compiled HP and LP core binaries. + * Save PMU wakeup cause to LP store register for HP core to query */ + lp_aon_hal_store_wakeup_cause(pmu_ll_hp_get_wakeup_cause(&PMU)); #endif + lp_core_ll_request_sleep(); +} + void ulp_lp_core_halt(void) { - lp_core_ll_request_sleep(); + ulp_lp_core_sleep_start_lp_core(); while (1); } @@ -153,7 +178,7 @@ void ulp_lp_core_stop_lp_core(void) { /* Disable wake-up source and put lp core to sleep */ lp_core_ll_set_wakeup_source(0); - lp_core_ll_request_sleep(); + ulp_lp_core_sleep_start_lp_core(); } void __attribute__((noreturn)) abort(void) diff --git a/components/ulp/lp_core/shared/include/ulp_lp_core_cpu_freq_shared.h b/components/ulp/lp_core/shared/include/ulp_lp_core_cpu_freq_shared.h index 3c0970a6a77..5a7550af024 100644 --- a/components/ulp/lp_core/shared/include/ulp_lp_core_cpu_freq_shared.h +++ b/components/ulp/lp_core/shared/include/ulp_lp_core_cpu_freq_shared.h @@ -6,13 +6,14 @@ #pragma once #include "sdkconfig.h" +#include "soc/clk_tree_defs.h" #include "soc/soc_caps.h" /* LP_FAST_CLK is not very accurate, for now use a rough estimate */ #if CONFIG_RTC_FAST_CLK_SRC_RC_FAST -#define LP_CORE_CPU_FREQUENCY_HZ 16000000U /* For P4 TRM says 20 MHz by default, but we tune it closer to 16 MHz */ -#define LP_CORE_CYCLES_PER_US_NUM 16U -#define LP_CORE_CYCLES_PER_US_DENOM 1U +#define LP_CORE_CPU_FREQUENCY_HZ SOC_CLK_RC_FAST_FREQ_APPROX +#define LP_CORE_CYCLES_PER_US_NUM (SOC_CLK_RC_FAST_FREQ_APPROX / 500000U) +#define LP_CORE_CYCLES_PER_US_DENOM 2U #elif CONFIG_RTC_FAST_CLK_SRC_XTAL #if SOC_XTAL_SUPPORT_48M #define LP_CORE_CPU_FREQUENCY_HZ 48000000U @@ -24,9 +25,9 @@ #define LP_CORE_CYCLES_PER_US_DENOM 1U #endif #else // Default value in chip without rtc fast clock sel option -#define LP_CORE_CPU_FREQUENCY_HZ 16000000U -#define LP_CORE_CYCLES_PER_US_NUM 16U -#define LP_CORE_CYCLES_PER_US_DENOM 1U +#define LP_CORE_CPU_FREQUENCY_HZ SOC_CLK_RC_FAST_FREQ_APPROX +#define LP_CORE_CYCLES_PER_US_NUM (SOC_CLK_RC_FAST_FREQ_APPROX / 500000U) +#define LP_CORE_CYCLES_PER_US_DENOM 2U #endif /** diff --git a/components/ulp/lp_core/shared/include/ulp_lp_core_lp_adc_shared.h b/components/ulp/lp_core/shared/include/ulp_lp_core_lp_adc_shared.h index 310caa0b1a9..4899eb50ed9 100644 --- a/components/ulp/lp_core/shared/include/ulp_lp_core_lp_adc_shared.h +++ b/components/ulp/lp_core/shared/include/ulp_lp_core_lp_adc_shared.h @@ -1,11 +1,12 @@ /* - * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2024-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #pragma once #include +#include #ifdef __cplusplus extern "C" { @@ -14,6 +15,9 @@ extern "C" { #include "esp_err.h" #include "hal/adc_types.h" #include "esp_adc/adc_oneshot.h" +#if SOC_LP_ADC_SUPPORTED +#include "soc/lp_adc_struct.h" +#endif /** * @brief LP ADC channel configurations @@ -108,6 +112,23 @@ esp_err_t lp_core_lp_adc_read_channel_raw(adc_unit_t unit_id, adc_channel_t chan */ esp_err_t lp_core_lp_adc_read_channel_converted(adc_unit_t unit_id, adc_channel_t channel, int *voltage_mv); +#if SOC_LP_ADC_SUPPORTED +/** + * @brief Enable or disable the LP ADC conversion-done interrupt to the LP CPU (LP_ADC int_ena cocpu_saradc1/2_int_ena). + * + * @param unit_id ADC unit (ADC_UNIT_1 / ADC_UNIT_2) + * @param enable true to enable, false to disable + */ +static inline void ulp_lp_core_lp_adc_intr_enable(adc_unit_t unit_id, bool enable) +{ + if (unit_id == ADC_UNIT_1) { + LP_ADC.int_ena.cocpu_saradc1_int_ena = enable; + } else if (unit_id == ADC_UNIT_2) { + LP_ADC.int_ena.cocpu_saradc2_int_ena = enable; + } +} +#endif /* SOC_LP_ADC_SUPPORTED */ + #ifdef __cplusplus } #endif diff --git a/components/ulp/lp_core/shared/include/ulp_lp_core_lp_uart_shared.h b/components/ulp/lp_core/shared/include/ulp_lp_core_lp_uart_shared.h index 2c8e59bd255..a962c327c84 100644 --- a/components/ulp/lp_core/shared/include/ulp_lp_core_lp_uart_shared.h +++ b/components/ulp/lp_core/shared/include/ulp_lp_core_lp_uart_shared.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2024-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -18,9 +18,10 @@ extern "C" { * * @note This function configures the LP UART wakeup mode. Ensure that the UART has already been initialized * with the lp_core_uart_init() call. - * Once the LP Core wakes up due to the LP UART, the wakeup feature is disabled. - * To re-enable the wakeup from the LP UART, you must call - * ulp_lp_core_lp_uart_reset_wakeup_en() again before the LP core goes to sleep. + * Once the LP Core wakes up due to the LP UART, the wakeup will be kept triggered in the chips with + * SOC_LP_CORE_LP_UART_WAKEUP_KEEP_TRIGGERED. You need to call ulp_lp_core_lp_uart_reset_wakeup_en() + * to reset the wakeup signal and the UART buffer manually after waking up, which is done in ulp + * startup phase. * Also be aware of limitations in different modes mentioned in the uart_wakeup_cfg_t struct. * * diff --git a/components/ulp/test_apps/lp_core/lp_core_basic_tests/check_lp_core_no_soft_float.py b/components/ulp/test_apps/lp_core/lp_core_basic_tests/check_lp_core_no_soft_float.py new file mode 100644 index 00000000000..f4c694bf187 --- /dev/null +++ b/components/ulp/test_apps/lp_core/lp_core_basic_tests/check_lp_core_no_soft_float.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python3 +# SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: CC0-1.0 + +import argparse +from pathlib import Path + +SOFT_FLOAT_SYMBOLS = { + '__adddf3', + '__addsf3', + '__divdf3', + '__divsf3', + '__eqdf2', + '__eqsf2', + '__extendsfdf2', + '__fixdfsi', + '__fixsfsi', + '__fixunsdfsi', + '__fixunssfsi', + '__floatunsidf', + '__floatunsisf', + '__floatsidf', + '__floatsisf', + '__gedf2', + '__gesf2', + '__gtdf2', + '__gtsf2', + '__ledf2', + '__lesf2', + '__ltdf2', + '__ltsf2', + '__muldf3', + '__mulsf3', + '__nedf2', + '__nesf2', + '__subdf3', + '__subsf3', + '__truncdfsf2', +} + + +def main() -> None: + parser = argparse.ArgumentParser(description='Check that an LP core app does not link soft-float helpers') + parser.add_argument('symbol_file', type=Path) + args = parser.parse_args() + + linked_symbols = set() + for line in args.symbol_file.read_text().splitlines(): + fields = line.split() + if fields: + linked_symbols.add(fields[-1]) + + linked_soft_float = sorted(linked_symbols & SOFT_FLOAT_SYMBOLS) + if linked_soft_float: + symbols = ', '.join(linked_soft_float) + raise SystemExit(f'{args.symbol_file}: LP core app links soft-float helper symbols: {symbols}') + + +if __name__ == '__main__': + main() diff --git a/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/CMakeLists.txt b/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/CMakeLists.txt index 78ec8881201..4c82d5eb16e 100644 --- a/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/CMakeLists.txt +++ b/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/CMakeLists.txt @@ -59,6 +59,10 @@ if(CONFIG_SOC_LP_VAD_SUPPORTED) set(lp_core_sources_vad "lp_core/test_main_vad.c") endif() +if(CONFIG_SOC_LP_CORE_HW_AUTO_CLRWAKEUPCAUSE) + set(lp_core_sources_halt "lp_core/test_main_halt.c") +endif() + idf_component_register(SRCS ${app_sources} INCLUDE_DIRS "lp_core" REQUIRES ulp unity esp_timer test_utils @@ -68,19 +72,43 @@ idf_component_register(SRCS ${app_sources} set(lp_core_exp_dep_srcs ${app_sources}) +function(lp_core_test_app_checks) + idf_build_get_property(python PYTHON) + + foreach(app_name ${ARGN}) + set(symbol_file ${CMAKE_CURRENT_BINARY_DIR}/${app_name}/${app_name}.sym) + set(check_output ${CMAKE_CURRENT_BINARY_DIR}/${app_name}/${app_name}.no_soft_float) + add_custom_command(OUTPUT ${check_output} + COMMAND ${python} ${CMAKE_CURRENT_LIST_DIR}/../check_lp_core_no_soft_float.py ${symbol_file} + COMMAND ${CMAKE_COMMAND} -E touch ${check_output} + DEPENDS ${symbol_file} ${CMAKE_CURRENT_LIST_DIR}/../check_lp_core_no_soft_float.py + VERBATIM) + add_custom_target(${app_name}_no_soft_float DEPENDS ${check_output}) + add_dependencies(${COMPONENT_LIB} ${app_name}_no_soft_float) + endforeach() +endfunction() + +set(lp_core_test_apps "") + ulp_embed_binary(lp_core_test_app "${lp_core_sources}" "${lp_core_exp_dep_srcs}") +list(APPEND lp_core_test_apps lp_core_test_app) ulp_embed_binary(lp_core_test_app_counter "${lp_core_sources_counter}" "${lp_core_exp_dep_srcs}") +list(APPEND lp_core_test_apps lp_core_test_app_counter) ulp_embed_binary(lp_core_test_app_wake_stub "${lp_core_sources_wake_stub}" "${lp_core_exp_dep_srcs}") +list(APPEND lp_core_test_apps lp_core_test_app_wake_stub) ulp_embed_binary(lp_core_test_app_isr "lp_core/test_main_isr.c" "${lp_core_exp_dep_srcs}") +list(APPEND lp_core_test_apps lp_core_test_app_isr) if(CONFIG_SOC_RTC_TIMER_V2_SUPPORTED) ulp_embed_binary(lp_core_test_app_set_timer_wakeup "${lp_core_sources_set_timer_wakeup}" "${lp_core_exp_dep_srcs}") + list(APPEND lp_core_test_apps lp_core_test_app_set_timer_wakeup) endif() ulp_embed_binary(lp_core_test_app_gpio "${lp_core_sources_gpio}" "${lp_core_exp_dep_srcs}") if(CONFIG_SOC_LP_I2C_SUPPORTED) ulp_embed_binary(lp_core_test_app_i2c "${lp_core_sources_i2c}" "${lp_core_exp_dep_srcs}") + list(APPEND lp_core_test_apps lp_core_test_app_i2c) endif() if(CONFIG_SOC_ULP_LP_UART_SUPPORTED) @@ -90,7 +118,9 @@ endif() if(CONFIG_SOC_LP_SPI_SUPPORTED) ulp_embed_binary(lp_core_test_app_spi_master "${lp_core_sources_spi_master}" "${lp_core_exp_dep_srcs}") + list(APPEND lp_core_test_apps lp_core_test_app_spi_master) ulp_embed_binary(lp_core_test_app_spi_slave "${lp_core_sources_spi_slave}" "${lp_core_exp_dep_srcs}") + list(APPEND lp_core_test_apps lp_core_test_app_spi_slave) endif() if(CONFIG_SOC_LP_ADC_SUPPORTED) @@ -99,9 +129,19 @@ endif() if(CONFIG_SOC_LP_VAD_SUPPORTED) ulp_embed_binary(lp_core_test_app_vad "${lp_core_sources_vad}" "${lp_core_exp_dep_srcs}") + list(APPEND lp_core_test_apps lp_core_test_app_vad) endif() ulp_embed_binary(lp_core_test_app_prefix1 "lp_core/test_main_prefix1.c" "${lp_core_exp_dep_srcs}" PREFIX "ulp1_") +list(APPEND lp_core_test_apps lp_core_test_app_prefix1) ulp_embed_binary(lp_core_test_app_prefix2 "lp_core/test_main_prefix2.c" "${lp_core_exp_dep_srcs}" PREFIX "ulp2_") +list(APPEND lp_core_test_apps lp_core_test_app_prefix2) ulp_embed_binary(lp_core_test_app_exception "lp_core/test_main_exception.c" "${lp_core_exp_dep_srcs}") +list(APPEND lp_core_test_apps lp_core_test_app_exception) + +if(CONFIG_SOC_LP_CORE_HW_AUTO_CLRWAKEUPCAUSE) + ulp_embed_binary(lp_core_test_app_halt "lp_core/test_main_halt.c" "${lp_core_exp_dep_srcs}") +endif() + +lp_core_test_app_checks(${lp_core_test_apps}) diff --git a/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/lp_core/test_main_exception.c b/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/lp_core/test_main_exception.c index fc6b14ad0a2..df9177898b9 100644 --- a/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/lp_core/test_main_exception.c +++ b/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/lp_core/test_main_exception.c @@ -1,10 +1,16 @@ /* - * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ +#include "ulp_lp_core_utils.h" + int main(void) { + // Wait for 1 second to ensure the HP Core enters deep sleep + ulp_lp_core_delay_us(1000000); + + // Trigger an exception to wake up the HP Core asm volatile("unimp"); } diff --git a/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/lp_core/test_main_halt.c b/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/lp_core/test_main_halt.c new file mode 100644 index 00000000000..12b8ebfa268 --- /dev/null +++ b/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/lp_core/test_main_halt.c @@ -0,0 +1,20 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include "ulp_lp_core_utils.h" +#include "hal/lp_core_ll.h" + +int main(void) +{ + // Wait for 1 second to ensure the HP Core enters deep sleep + ulp_lp_core_delay_us(1000000); + + ulp_lp_core_wakeup_main_processor(); + /* Disable wake-up source and put lp core to sleep */ + lp_core_ll_set_wakeup_source(0); + ulp_lp_core_halt(); +} diff --git a/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/lp_core/test_main_uart_wakeup.c b/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/lp_core/test_main_uart_wakeup.c index 54e2f691881..aecb478f326 100644 --- a/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/lp_core/test_main_uart_wakeup.c +++ b/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/lp_core/test_main_uart_wakeup.c @@ -1,19 +1,17 @@ /* - * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2024-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include "ulp_lp_core_utils.h" #include "ulp_lp_core_print.h" -#include "ulp_lp_core_lp_uart_shared.h" -#include "lp_core_uart.h" #include "ulp_lp_core_uart.h" int main(void) { lp_core_printf("Hello world\r\n"); - ulp_lp_core_delay_us(5000); - lp_core_uart_clear_buf(); - ulp_lp_core_lp_uart_reset_wakeup_en(); + lp_core_uart_tx_flush(LP_UART_NUM_0); + + return 0; } diff --git a/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/lp_core/test_shared.h b/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/lp_core/test_shared.h index 68f0eb8676d..83b5d5747fb 100644 --- a/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/lp_core/test_shared.h +++ b/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/lp_core/test_shared.h @@ -56,6 +56,7 @@ typedef enum { LP_CORE_DELAY_US_CALIBRATION_TEST, LP_CORE_DEEP_SLEEP_WAKEUP_SHORT_DELAY_TEST, LP_CORE_DEEP_SLEEP_WAKEUP_LONG_DELAY_TEST, + LP_CORE_HALT_TEST, LP_CORE_LP_UART_WRITE_TEST, LP_CORE_LP_UART_READ_TEST, LP_CORE_LP_UART_MULTI_BYTE_READ_TEST, diff --git a/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/test_lp_core.c b/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/test_lp_core.c index 1f29a061664..53a4bb9a92e 100644 --- a/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/test_lp_core.c +++ b/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/test_lp_core.c @@ -40,6 +40,12 @@ #include "hal/lp_core_ll.h" #include "hal/rtc_io_ll.h" #include "driver/rtc_io.h" +#if SOC_LP_CORE_HW_AUTO_CLRWAKEUPCAUSE +#include "rom/rtc.h" +#include "esp_private/esp_pmu.h" +#include "lp_core_test_app_halt.h" +#include "hal/lp_aon_hal.h" +#endif extern const uint8_t lp_core_main_bin_start[] asm("_binary_lp_core_test_app_bin_start"); extern const uint8_t lp_core_main_bin_end[] asm("_binary_lp_core_test_app_bin_end"); @@ -64,6 +70,11 @@ extern const uint8_t lp_core_main_isr_bin_end[] asm("_binary_lp_core_test_app_ extern const uint8_t lp_core_main_exception_bin_start[] asm("_binary_lp_core_test_app_exception_bin_start"); extern const uint8_t lp_core_main_exception_bin_end[] asm("_binary_lp_core_test_app_exception_bin_end"); +#if SOC_LP_CORE_HW_AUTO_CLRWAKEUPCAUSE +extern const uint8_t lp_core_main_halt_bin_start[] asm("_binary_lp_core_test_app_halt_bin_start"); +extern const uint8_t lp_core_main_halt_bin_end[] asm("_binary_lp_core_test_app_halt_bin_end"); +#endif + static void load_and_start_lp_core_firmware(ulp_lp_core_cfg_t* cfg, const uint8_t* firmware_start, const uint8_t* firmware_end) { TEST_ASSERT(ulp_lp_core_load_binary(firmware_start, @@ -630,4 +641,43 @@ static void check_reset_reason_ulp_trap_wakeup(void) TEST_CASE_MULTIPLE_STAGES("LP-core exception can wakeup main cpu", "[ulp]", lp_core_prep_exception_wakeup, check_reset_reason_ulp_trap_wakeup); + +#if SOC_LP_CORE_HW_AUTO_CLRWAKEUPCAUSE +static void do_ulp_wakeup_with_lp_timer_deepsleep_and_halt(void) +{ + /* Load ULP firmware and start the coprocessor */ + ulp_lp_core_cfg_t cfg = { + .wakeup_source = ULP_LP_CORE_WAKEUP_SOURCE_LP_TIMER, + .lp_timer_sleep_duration_us = 1000000, // 1 second +#if ESP_ROM_HAS_LP_ROM + /* ROM Boot takes quite a bit longer, which skews the numbers of wake-ups. skip rom boot to keep the calculation simple */ + .skip_lp_rom_boot = true, +#endif + }; + + load_and_start_lp_core_firmware(&cfg, lp_core_main_halt_bin_start, lp_core_main_halt_bin_end); + + /* Setup wakeup triggers */ + TEST_ASSERT(esp_sleep_enable_ulp_wakeup() == ESP_OK); + + /* Enter Deep Sleep */ + esp_deep_sleep_start(); + + UNITY_TEST_FAIL(__LINE__, "Should not get here!"); +} + +static void check_hp_core_wakeup_cause_saved(void) +{ + uint32_t lp_core_wakeup_cause_status0 = lp_aon_hal_load_wakeup_cause(); + TEST_ASSERT_EQUAL(RTC_LP_CORE_TRIG_EN, lp_core_wakeup_cause_status0 & RTC_LP_CORE_TRIG_EN); + TEST_ASSERT_EQUAL(BIT(ESP_SLEEP_WAKEUP_ULP), esp_sleep_get_wakeup_causes() & BIT(ESP_SLEEP_WAKEUP_ULP)); + + clear_test_cmds(); +} + +TEST_CASE_MULTIPLE_STAGES("HP core wakeup causes are saved after LP core halt", "[ulp]", + do_ulp_wakeup_with_lp_timer_deepsleep_and_halt, + check_hp_core_wakeup_cause_saved); +#endif //SOC_LP_CORE_HW_AUTO_CLRWAKEUPCAUSE + #endif //SOC_DEEP_SLEEP_SUPPORTED diff --git a/components/ulp/ulp_riscv/ulp_core/ulp_riscv_i2c.c b/components/ulp/ulp_riscv/ulp_core/ulp_riscv_i2c.c index 65611628ee5..233cd9ccc12 100644 --- a/components/ulp/ulp_riscv/ulp_core/ulp_riscv_i2c.c +++ b/components/ulp/ulp_riscv/ulp_core/ulp_riscv_i2c.c @@ -75,19 +75,21 @@ static inline int32_t ulp_riscv_i2c_wait_for_interrupt(int32_t ticks_to_wait) while (1) { status = READ_PERI_REG(RTC_I2C_INT_ST_REG); - /* Return 0 if Tx or Rx data interrupt bits are set. */ + /* If a NAK, Timeout, or Arbitration Loss occurs, abort immediately. */ +#if CONFIG_IDF_TARGET_ESP32S2 + if ((status & RTC_I2C_TIMEOUT_INT_ST) || +#elif CONFIG_IDF_TARGET_ESP32S3 + if ((status & RTC_I2C_TIME_OUT_INT_ST) || +#endif // CONFIG_IDF_TARGET_ESP32S2 + (status & RTC_I2C_ACK_ERR_INT_ST) || + (status & RTC_I2C_ARBITRATION_LOST_INT_ST)) { + return -1; + } + + /* Return 0 ONLY if hardware channels are error-free and data bits are latched. */ if ((status & RTC_I2C_TX_DATA_INT_ST) || (status & RTC_I2C_RX_DATA_INT_ST)) { return 0; - /* In case of error status, break and return -1 */ -#if CONFIG_IDF_TARGET_ESP32S2 - } else if ((status & RTC_I2C_TIMEOUT_INT_ST) || -#elif CONFIG_IDF_TARGET_ESP32S3 - } else if ((status & RTC_I2C_TIME_OUT_INT_ST) || -#endif // CONFIG_IDF_TARGET_ESP32S2 - (status & RTC_I2C_ACK_ERR_INT_ST) || - (status & RTC_I2C_ARBITRATION_LOST_INT_ST)) { - return -1; } if (ticks_to_wait > -1) { diff --git a/components/ulp/ulp_riscv/ulp_riscv_i2c.c b/components/ulp/ulp_riscv/ulp_riscv_i2c.c index 12881ae57ac..1ab249c3304 100644 --- a/components/ulp/ulp_riscv/ulp_riscv_i2c.c +++ b/components/ulp/ulp_riscv/ulp_riscv_i2c.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2022-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -254,21 +254,23 @@ static inline esp_err_t ulp_riscv_i2c_wait_for_interrupt(int32_t ticks_to_wait) while (1) { status = READ_PERI_REG(RTC_I2C_INT_ST_REG); - /* Return ESP_OK if Tx or Rx data interrupt bits are set. */ + /* If a NAK, Timeout, or Arbitration Loss occurs, abort immediately. */ +#if CONFIG_IDF_TARGET_ESP32S2 + if ((status & RTC_I2C_TIMEOUT_INT_ST) || +#elif CONFIG_IDF_TARGET_ESP32S3 + if ((status & RTC_I2C_TIME_OUT_INT_ST) || +#endif // CONFIG_IDF_TARGET_ESP32S2 + (status & RTC_I2C_ACK_ERR_INT_ST) || + (status & RTC_I2C_ARBITRATION_LOST_INT_ST)) { + ret = ESP_FAIL; + break; + } + + /* Return ESP_OK only if hardware channels are error-free and data bits are latched. */ if ((status & RTC_I2C_TX_DATA_INT_ST) || (status & RTC_I2C_RX_DATA_INT_ST)) { ret = ESP_OK; break; - /* In case of error status, break and return ESP_FAIL */ -#if CONFIG_IDF_TARGET_ESP32S2 - } else if ((status & RTC_I2C_TIMEOUT_INT_ST) || -#elif CONFIG_IDF_TARGET_ESP32S3 - } else if ((status & RTC_I2C_TIME_OUT_INT_ST) || -#endif // CONFIG_IDF_TARGET_ESP32S2 - (status & RTC_I2C_ACK_ERR_INT_ST) || - (status & RTC_I2C_ARBITRATION_LOST_INT_ST)) { - ret = ESP_FAIL; - break; } if (ticks_to_wait > -1) { diff --git a/components/vfs/include/esp_vfs.h b/components/vfs/include/esp_vfs.h index 2b2c237a578..4a75b96a353 100644 --- a/components/vfs/include/esp_vfs.h +++ b/components/vfs/include/esp_vfs.h @@ -102,145 +102,145 @@ typedef struct { int flags; /*!< ESP_VFS_FLAG_CONTEXT_PTR and/or ESP_VFS_FLAG_READONLY_FS or ESP_VFS_FLAG_DEFAULT */ union { - ssize_t (*write_p)(void* p, int fd, const void * data, size_t size) __attribute__((deprecated("Context pointer-less API is deprecated"))); /*!< Write with context pointer */ - ssize_t (*write)(int fd, const void * data, size_t size); /*!< Write without context pointer */ + ssize_t (*write_p)(void* p, int fd, const void * data, size_t size); /*!< Write with context pointer */ + ssize_t (*write)(int fd, const void * data, size_t size) __attribute__((deprecated("Context pointer-less API is deprecated"))); /*!< Write without context pointer */ }; union { - off_t (*lseek_p)(void* p, int fd, off_t size, int mode) __attribute__((deprecated("Context pointer-less API is deprecated"))); /*!< Seek with context pointer */ - off_t (*lseek)(int fd, off_t size, int mode); /*!< Seek without context pointer */ + off_t (*lseek_p)(void* p, int fd, off_t size, int mode); /*!< Seek with context pointer */ + off_t (*lseek)(int fd, off_t size, int mode) __attribute__((deprecated("Context pointer-less API is deprecated"))); /*!< Seek without context pointer */ }; union { - ssize_t (*read_p)(void* ctx, int fd, void * dst, size_t size) __attribute__((deprecated("Context pointer-less API is deprecated"))); /*!< Read with context pointer */ - ssize_t (*read)(int fd, void * dst, size_t size); /*!< Read without context pointer */ + ssize_t (*read_p)(void* ctx, int fd, void * dst, size_t size); /*!< Read with context pointer */ + ssize_t (*read)(int fd, void * dst, size_t size) __attribute__((deprecated("Context pointer-less API is deprecated"))); /*!< Read without context pointer */ }; union { - ssize_t (*pread_p)(void *ctx, int fd, void * dst, size_t size, off_t offset) __attribute__((deprecated("Context pointer-less API is deprecated"))); /*!< pread with context pointer */ - ssize_t (*pread)(int fd, void * dst, size_t size, off_t offset); /*!< pread without context pointer */ + ssize_t (*pread_p)(void *ctx, int fd, void * dst, size_t size, off_t offset); /*!< pread with context pointer */ + ssize_t (*pread)(int fd, void * dst, size_t size, off_t offset) __attribute__((deprecated("Context pointer-less API is deprecated"))); /*!< pread without context pointer */ }; union { - ssize_t (*pwrite_p)(void *ctx, int fd, const void *src, size_t size, off_t offset) __attribute__((deprecated("Context pointer-less API is deprecated"))); /*!< pwrite with context pointer */ - ssize_t (*pwrite)(int fd, const void *src, size_t size, off_t offset); /*!< pwrite without context pointer */ + ssize_t (*pwrite_p)(void *ctx, int fd, const void *src, size_t size, off_t offset); /*!< pwrite with context pointer */ + ssize_t (*pwrite)(int fd, const void *src, size_t size, off_t offset) __attribute__((deprecated("Context pointer-less API is deprecated"))); /*!< pwrite without context pointer */ }; union { - int (*open_p)(void* ctx, const char * path, int flags, int mode) __attribute__((deprecated("Context pointer-less API is deprecated"))); /*!< open with context pointer */ - int (*open)(const char * path, int flags, int mode); /*!< open without context pointer */ + int (*open_p)(void* ctx, const char * path, int flags, int mode); /*!< open with context pointer */ + int (*open)(const char * path, int flags, int mode) __attribute__((deprecated("Context pointer-less API is deprecated"))); /*!< open without context pointer */ }; union { - int (*close_p)(void* ctx, int fd) __attribute__((deprecated("Context pointer-less API is deprecated"))); /*!< close with context pointer */ - int (*close)(int fd); /*!< close without context pointer */ + int (*close_p)(void* ctx, int fd); /*!< close with context pointer */ + int (*close)(int fd) __attribute__((deprecated("Context pointer-less API is deprecated"))); /*!< close without context pointer */ }; union { - int (*fstat_p)(void* ctx, int fd, struct stat * st) __attribute__((deprecated("Context pointer-less API is deprecated"))); /*!< fstat with context pointer */ - int (*fstat)(int fd, struct stat * st); /*!< fstat without context pointer */ + int (*fstat_p)(void* ctx, int fd, struct stat * st); /*!< fstat with context pointer */ + int (*fstat)(int fd, struct stat * st) __attribute__((deprecated("Context pointer-less API is deprecated"))); /*!< fstat without context pointer */ }; #ifdef CONFIG_VFS_SUPPORT_DIR union { - int (*stat_p)(void* ctx, const char * path, struct stat * st) __attribute__((deprecated("Context pointer-less API is deprecated"))); /*!< stat with context pointer */ - int (*stat)(const char * path, struct stat * st); /*!< stat without context pointer */ + int (*stat_p)(void* ctx, const char * path, struct stat * st); /*!< stat with context pointer */ + int (*stat)(const char * path, struct stat * st) __attribute__((deprecated("Context pointer-less API is deprecated"))); /*!< stat without context pointer */ }; union { - int (*link_p)(void* ctx, const char* n1, const char* n2) __attribute__((deprecated("Context pointer-less API is deprecated"))); /*!< link with context pointer */ - int (*link)(const char* n1, const char* n2); /*!< link without context pointer */ + int (*link_p)(void* ctx, const char* n1, const char* n2); /*!< link with context pointer */ + int (*link)(const char* n1, const char* n2) __attribute__((deprecated("Context pointer-less API is deprecated"))); /*!< link without context pointer */ }; union { - int (*unlink_p)(void* ctx, const char *path) __attribute__((deprecated("Context pointer-less API is deprecated"))); /*!< unlink with context pointer */ - int (*unlink)(const char *path); /*!< unlink without context pointer */ + int (*unlink_p)(void* ctx, const char *path); /*!< unlink with context pointer */ + int (*unlink)(const char *path) __attribute__((deprecated("Context pointer-less API is deprecated"))); /*!< unlink without context pointer */ }; union { - int (*rename_p)(void* ctx, const char *src, const char *dst) __attribute__((deprecated("Context pointer-less API is deprecated"))); /*!< rename with context pointer */ - int (*rename)(const char *src, const char *dst); /*!< rename without context pointer */ + int (*rename_p)(void* ctx, const char *src, const char *dst); /*!< rename with context pointer */ + int (*rename)(const char *src, const char *dst) __attribute__((deprecated("Context pointer-less API is deprecated"))); /*!< rename without context pointer */ }; union { - DIR* (*opendir_p)(void* ctx, const char* name) __attribute__((deprecated("Context pointer-less API is deprecated"))); /*!< opendir with context pointer */ - DIR* (*opendir)(const char* name); /*!< opendir without context pointer */ + DIR* (*opendir_p)(void* ctx, const char* name); /*!< opendir with context pointer */ + DIR* (*opendir)(const char* name) __attribute__((deprecated("Context pointer-less API is deprecated"))); /*!< opendir without context pointer */ }; union { - struct dirent* (*readdir_p)(void* ctx, DIR* pdir) __attribute__((deprecated("Context pointer-less API is deprecated"))); /*!< readdir with context pointer */ - struct dirent* (*readdir)(DIR* pdir); /*!< readdir without context pointer */ + struct dirent* (*readdir_p)(void* ctx, DIR* pdir); /*!< readdir with context pointer */ + struct dirent* (*readdir)(DIR* pdir) __attribute__((deprecated("Context pointer-less API is deprecated"))); /*!< readdir without context pointer */ }; union { - int (*readdir_r_p)(void* ctx, DIR* pdir, struct dirent* entry, struct dirent** out_dirent) __attribute__((deprecated("Context pointer-less API is deprecated"))); /*!< readdir_r with context pointer */ - int (*readdir_r)(DIR* pdir, struct dirent* entry, struct dirent** out_dirent); /*!< readdir_r without context pointer */ + int (*readdir_r_p)(void* ctx, DIR* pdir, struct dirent* entry, struct dirent** out_dirent); /*!< readdir_r with context pointer */ + int (*readdir_r)(DIR* pdir, struct dirent* entry, struct dirent** out_dirent) __attribute__((deprecated("Context pointer-less API is deprecated"))); /*!< readdir_r without context pointer */ }; union { - long (*telldir_p)(void* ctx, DIR* pdir) __attribute__((deprecated("Context pointer-less API is deprecated"))); /*!< telldir with context pointer */ - long (*telldir)(DIR* pdir); /*!< telldir without context pointer */ + long (*telldir_p)(void* ctx, DIR* pdir); /*!< telldir with context pointer */ + long (*telldir)(DIR* pdir) __attribute__((deprecated("Context pointer-less API is deprecated"))); /*!< telldir without context pointer */ }; union { - void (*seekdir_p)(void* ctx, DIR* pdir, long offset) __attribute__((deprecated("Context pointer-less API is deprecated"))); /*!< seekdir with context pointer */ - void (*seekdir)(DIR* pdir, long offset); /*!< seekdir without context pointer */ + void (*seekdir_p)(void* ctx, DIR* pdir, long offset); /*!< seekdir with context pointer */ + void (*seekdir)(DIR* pdir, long offset) __attribute__((deprecated("Context pointer-less API is deprecated"))); /*!< seekdir without context pointer */ }; union { - int (*closedir_p)(void* ctx, DIR* pdir) __attribute__((deprecated("Context pointer-less API is deprecated"))); /*!< closedir with context pointer */ - int (*closedir)(DIR* pdir); /*!< closedir without context pointer */ + int (*closedir_p)(void* ctx, DIR* pdir); /*!< closedir with context pointer */ + int (*closedir)(DIR* pdir) __attribute__((deprecated("Context pointer-less API is deprecated"))); /*!< closedir without context pointer */ }; union { - int (*mkdir_p)(void* ctx, const char* name, mode_t mode) __attribute__((deprecated("Context pointer-less API is deprecated"))); /*!< mkdir with context pointer */ - int (*mkdir)(const char* name, mode_t mode); /*!< mkdir without context pointer */ + int (*mkdir_p)(void* ctx, const char* name, mode_t mode); /*!< mkdir with context pointer */ + int (*mkdir)(const char* name, mode_t mode) __attribute__((deprecated("Context pointer-less API is deprecated"))); /*!< mkdir without context pointer */ }; union { - int (*rmdir_p)(void* ctx, const char* name) __attribute__((deprecated("Context pointer-less API is deprecated"))); /*!< rmdir with context pointer */ - int (*rmdir)(const char* name); /*!< rmdir without context pointer */ + int (*rmdir_p)(void* ctx, const char* name); /*!< rmdir with context pointer */ + int (*rmdir)(const char* name) __attribute__((deprecated("Context pointer-less API is deprecated"))); /*!< rmdir without context pointer */ }; #endif // CONFIG_VFS_SUPPORT_DIR union { - int (*fcntl_p)(void* ctx, int fd, int cmd, int arg) __attribute__((deprecated("Context pointer-less API is deprecated"))); /*!< fcntl with context pointer */ - int (*fcntl)(int fd, int cmd, int arg); /*!< fcntl without context pointer */ + int (*fcntl_p)(void* ctx, int fd, int cmd, int arg); /*!< fcntl with context pointer */ + int (*fcntl)(int fd, int cmd, int arg) __attribute__((deprecated("Context pointer-less API is deprecated"))); /*!< fcntl without context pointer */ }; union { - int (*ioctl_p)(void* ctx, int fd, int cmd, va_list args) __attribute__((deprecated("Context pointer-less API is deprecated"))); /*!< ioctl with context pointer */ - int (*ioctl)(int fd, int cmd, va_list args); /*!< ioctl without context pointer */ + int (*ioctl_p)(void* ctx, int fd, int cmd, va_list args); /*!< ioctl with context pointer */ + int (*ioctl)(int fd, int cmd, va_list args) __attribute__((deprecated("Context pointer-less API is deprecated"))); /*!< ioctl without context pointer */ }; union { - int (*fsync_p)(void* ctx, int fd) __attribute__((deprecated("Context pointer-less API is deprecated"))); /*!< fsync with context pointer */ - int (*fsync)(int fd); /*!< fsync without context pointer */ + int (*fsync_p)(void* ctx, int fd); /*!< fsync with context pointer */ + int (*fsync)(int fd) __attribute__((deprecated("Context pointer-less API is deprecated"))); /*!< fsync without context pointer */ }; #ifdef CONFIG_VFS_SUPPORT_DIR union { - int (*access_p)(void* ctx, const char *path, int amode) __attribute__((deprecated("Context pointer-less API is deprecated"))); /*!< access with context pointer */ - int (*access)(const char *path, int amode); /*!< access without context pointer */ + int (*access_p)(void* ctx, const char *path, int amode); /*!< access with context pointer */ + int (*access)(const char *path, int amode) __attribute__((deprecated("Context pointer-less API is deprecated"))); /*!< access without context pointer */ }; union { - int (*truncate_p)(void* ctx, const char *path, off_t length) __attribute__((deprecated("Context pointer-less API is deprecated"))); /*!< truncate with context pointer */ - int (*truncate)(const char *path, off_t length); /*!< truncate without context pointer */ + int (*truncate_p)(void* ctx, const char *path, off_t length); /*!< truncate with context pointer */ + int (*truncate)(const char *path, off_t length) __attribute__((deprecated("Context pointer-less API is deprecated"))); /*!< truncate without context pointer */ }; union { - int (*ftruncate_p)(void* ctx, int fd, off_t length) __attribute__((deprecated("Context pointer-less API is deprecated"))); /*!< ftruncate with context pointer */ - int (*ftruncate)(int fd, off_t length); /*!< ftruncate without context pointer */ + int (*ftruncate_p)(void* ctx, int fd, off_t length); /*!< ftruncate with context pointer */ + int (*ftruncate)(int fd, off_t length) __attribute__((deprecated("Context pointer-less API is deprecated"))); /*!< ftruncate without context pointer */ }; union { - int (*utime_p)(void* ctx, const char *path, const struct utimbuf *times) __attribute__((deprecated("Context pointer-less API is deprecated"))); /*!< utime with context pointer */ - int (*utime)(const char *path, const struct utimbuf *times); /*!< utime without context pointer */ + int (*utime_p)(void* ctx, const char *path, const struct utimbuf *times); /*!< utime with context pointer */ + int (*utime)(const char *path, const struct utimbuf *times) __attribute__((deprecated("Context pointer-less API is deprecated"))); /*!< utime without context pointer */ }; #endif // CONFIG_VFS_SUPPORT_DIR #ifdef CONFIG_VFS_SUPPORT_TERMIOS union { - int (*tcsetattr_p)(void *ctx, int fd, int optional_actions, const struct termios *p) __attribute__((deprecated("Context pointer-less API is deprecated"))); /*!< tcsetattr with context pointer */ - int (*tcsetattr)(int fd, int optional_actions, const struct termios *p); /*!< tcsetattr without context pointer */ + int (*tcsetattr_p)(void *ctx, int fd, int optional_actions, const struct termios *p); /*!< tcsetattr with context pointer */ + int (*tcsetattr)(int fd, int optional_actions, const struct termios *p) __attribute__((deprecated("Context pointer-less API is deprecated"))); /*!< tcsetattr without context pointer */ }; union { - int (*tcgetattr_p)(void *ctx, int fd, struct termios *p) __attribute__((deprecated("Context pointer-less API is deprecated"))); /*!< tcgetattr with context pointer */ - int (*tcgetattr)(int fd, struct termios *p); /*!< tcgetattr without context pointer */ + int (*tcgetattr_p)(void *ctx, int fd, struct termios *p); /*!< tcgetattr with context pointer */ + int (*tcgetattr)(int fd, struct termios *p) __attribute__((deprecated("Context pointer-less API is deprecated"))); /*!< tcgetattr without context pointer */ }; union { - int (*tcdrain_p)(void *ctx, int fd) __attribute__((deprecated("Context pointer-less API is deprecated"))); /*!< tcdrain with context pointer */ - int (*tcdrain)(int fd); /*!< tcdrain without context pointer */ + int (*tcdrain_p)(void *ctx, int fd); /*!< tcdrain with context pointer */ + int (*tcdrain)(int fd) __attribute__((deprecated("Context pointer-less API is deprecated"))); /*!< tcdrain without context pointer */ }; union { - int (*tcflush_p)(void *ctx, int fd, int select) __attribute__((deprecated("Context pointer-less API is deprecated"))); /*!< tcflush with context pointer */ - int (*tcflush)(int fd, int select); /*!< tcflush without context pointer */ + int (*tcflush_p)(void *ctx, int fd, int select); /*!< tcflush with context pointer */ + int (*tcflush)(int fd, int select) __attribute__((deprecated("Context pointer-less API is deprecated"))); /*!< tcflush without context pointer */ }; union { - int (*tcflow_p)(void *ctx, int fd, int action) __attribute__((deprecated("Context pointer-less API is deprecated"))); /*!< tcflow with context pointer */ - int (*tcflow)(int fd, int action); /*!< tcflow without context pointer */ + int (*tcflow_p)(void *ctx, int fd, int action); /*!< tcflow with context pointer */ + int (*tcflow)(int fd, int action) __attribute__((deprecated("Context pointer-less API is deprecated"))); /*!< tcflow without context pointer */ }; union { - pid_t (*tcgetsid_p)(void *ctx, int fd) __attribute__((deprecated("Context pointer-less API is deprecated"))); /*!< tcgetsid with context pointer */ - pid_t (*tcgetsid)(int fd); /*!< tcgetsid without context pointer */ + pid_t (*tcgetsid_p)(void *ctx, int fd); /*!< tcgetsid with context pointer */ + pid_t (*tcgetsid)(int fd) __attribute__((deprecated("Context pointer-less API is deprecated"))); /*!< tcgetsid without context pointer */ }; union { - int (*tcsendbreak_p)(void *ctx, int fd, int duration) __attribute__((deprecated("Context pointer-less API is deprecated"))); /*!< tcsendbreak with context pointer */ - int (*tcsendbreak)(int fd, int duration); /*!< tcsendbreak without context pointer */ + int (*tcsendbreak_p)(void *ctx, int fd, int duration); /*!< tcsendbreak with context pointer */ + int (*tcsendbreak)(int fd, int duration) __attribute__((deprecated("Context pointer-less API is deprecated"))); /*!< tcsendbreak without context pointer */ }; #endif // CONFIG_VFS_SUPPORT_TERMIOS #if CONFIG_VFS_SUPPORT_SELECT || defined __DOXYGEN__ diff --git a/components/vfs/linker.lf b/components/vfs/linker.lf index 16aaa57de36..d9e0e06f08a 100644 --- a/components/vfs/linker.lf +++ b/components/vfs/linker.lf @@ -3,5 +3,5 @@ archive: libvfs.a entries: if VFS_SELECT_IN_RAM = y: vfs_calls:esp_vfs_select_triggered_isr (noflash) - vfs:get_vfs_count (noflash) + vfs:get_vfs_upper_bound (noflash) vfs:start_select (noflash) diff --git a/components/vfs/private_include/esp_vfs_private.h b/components/vfs/private_include/esp_vfs_private.h index d599f3e90b7..3a796776e70 100644 --- a/components/vfs/private_include/esp_vfs_private.h +++ b/components/vfs/private_include/esp_vfs_private.h @@ -111,7 +111,7 @@ int get_local_fd(const vfs_entry_t *vfs, int fd); const fd_table_t *get_fd_entry(int fd); -size_t get_vfs_count(void); +size_t get_vfs_upper_bound(void); void close_pending(int nfds); diff --git a/components/vfs/test_apps/.build-test-rules.yml b/components/vfs/test_apps/.build-test-rules.yml index 14687091da4..b1dbaa0ac65 100644 --- a/components/vfs/test_apps/.build-test-rules.yml +++ b/components/vfs/test_apps/.build-test-rules.yml @@ -5,7 +5,7 @@ components/vfs/test_apps: reason: not support yet # TODO: [esp32h21] IDF-11593 [ESP32H4] IDF-12372 disable_test: - - if: IDF_TARGET not in ["esp32c2", "esp32c3", "esp32c6", "esp32h2"] + - if: IDF_TARGET not in ["esp32", "esp32c2", "esp32c3", "esp32c6", "esp32h2", "esp32s3"] temporary: true reason: lack of runners diff --git a/components/vfs/test_apps/main/test_vfs_paths.c b/components/vfs/test_apps/main/test_vfs_paths.c index 65fc624fcdb..d9cdc0015d4 100644 --- a/components/vfs/test_apps/main/test_vfs_paths.c +++ b/components/vfs/test_apps/main/test_vfs_paths.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -270,3 +270,186 @@ TEST_CASE("vfs checks mount point path", "[vfs]") test_register_ok("/23456789012345"); test_register_fail("/234567890123456"); } + +/* Regression test for a slot-accounting bug in esp_vfs_register_fs_common(). + * + * The registration code used to keep an ever-increasing counter (s_vfs_count/s_vfs_upper_bound) + * which was incremented when a VFS was registered into a new top slot, but was + * never decremented on unregister. The "is there room for another VFS?" check + * compared that counter against VFS_MAX_COUNT. As a result, after the VFS table + * had been filled once, repeatedly unregistering and re-registering a VFS would + * eventually (and permanently) fail with ESP_ERR_NO_MEM even though free slots + * were available. + * + * The bug was fixed by checking for an actually-free slot (esp_get_free_index()) + * instead of relying on the counter, and by lowering the upper bound when the + * topmost entry is removed. + * + * These tests register/unregister the dummy VFS many times to ensure the slot + * accounting stays correct over time. They are expected to fail on the + * pre-fix code and pass on the fixed code. + */ + +/* Number of VFS slots available for this test app (see sdkconfig.defaults). */ +#define TEST_VFS_MAX_COUNT CONFIG_VFS_MAX_COUNT + +/* Build a short, unique mount point ("/t") for the test VFS entries. */ +static void make_test_path(char *buf, size_t buf_len, int idx) +{ + snprintf(buf, buf_len, "/t%d", idx); +} + +TEST_CASE("vfs can re-register after the table has been filled", "[vfs]") +{ + /* Separate context per registered VFS, because ESP_VFS_FLAG_CONTEXT_PTR + * stores the pointer for the lifetime of the registration. */ + static dummy_vfs_t insts[TEST_VFS_MAX_COUNT]; + char paths[TEST_VFS_MAX_COUNT][8]; + + /* Fill every free VFS slot. Some slots may already be taken by VFSes that + * the system registered at startup (e.g. /dev/null), so we keep going until + * registration reports the table is full instead of assuming a fixed count. */ + int registered = 0; + for (int i = 0; i < TEST_VFS_MAX_COUNT; ++i) { + make_test_path(paths[i], sizeof(paths[i]), i); + insts[i] = (dummy_vfs_t) { .match_path = "", .called = false }; + esp_err_t err = esp_vfs_register_fs(paths[i], &s_dummy_vfs, + ESP_VFS_FLAG_CONTEXT_PTR, &insts[i]); + if (err == ESP_ERR_NO_MEM) { + break; // table is full + } + TEST_ESP_OK(err); + registered++; + } + + /* We must have been able to register at least one entry, and the table must + * actually be full now (the next registration must fail with NO_MEM). */ + TEST_ASSERT_GREATER_THAN(0, registered); + dummy_vfs_t overflow_inst = { .match_path = "", .called = false }; + TEST_ESP_ERR(ESP_ERR_NO_MEM, + esp_vfs_register_fs("/overflow", &s_dummy_vfs, + ESP_VFS_FLAG_CONTEXT_PTR, &overflow_inst)); + + /* Free the topmost entry we registered, then try to register again. + * On the buggy code the stale counter would still equal VFS_MAX_COUNT and + * this registration would incorrectly fail with ESP_ERR_NO_MEM. */ + int top = registered - 1; + TEST_ESP_OK(esp_vfs_unregister(paths[top])); + + insts[top] = (dummy_vfs_t) { .match_path = "", .called = false }; + TEST_ESP_OK(esp_vfs_register_fs(paths[top], &s_dummy_vfs, + ESP_VFS_FLAG_CONTEXT_PTR, &insts[top])); + + /* Clean up everything we registered so the leak check in tearDown passes. */ + for (int i = 0; i < registered; ++i) { + TEST_ESP_OK(esp_vfs_unregister(paths[i])); + } +} + +TEST_CASE("vfs can re-register into a hole in the middle of a full table", "[vfs]") +{ + /* This targets the case where an entry that is NOT the topmost one is + * unregistered while the table is full. That leaves a NULL "hole" in the + * middle of the s_vfs table and, crucially, does NOT lower s_vfs_upper_bound + * (the hole is below the upper bound). Registering again must reuse that + * hole. + * + * On the buggy code the stale counter stayed at VFS_MAX_COUNT, so this + * re-registration failed with ESP_ERR_NO_MEM even though the freed middle + * slot was available. */ + static dummy_vfs_t insts[TEST_VFS_MAX_COUNT]; + char paths[TEST_VFS_MAX_COUNT][8]; + + /* Fill the table completely. */ + int registered = 0; + for (int i = 0; i < TEST_VFS_MAX_COUNT; ++i) { + make_test_path(paths[i], sizeof(paths[i]), i); + insts[i] = (dummy_vfs_t) { .match_path = "", .called = false }; + esp_err_t err = esp_vfs_register_fs(paths[i], &s_dummy_vfs, + ESP_VFS_FLAG_CONTEXT_PTR, &insts[i]); + if (err == ESP_ERR_NO_MEM) { + break; // table is full + } + TEST_ESP_OK(err); + registered++; + } + + /* We need at least 3 entries so that there is a genuine middle entry that is + * neither the first nor the topmost slot. */ + TEST_ASSERT_GREATER_OR_EQUAL_INT(3, registered); + + /* The table must be full now. */ + dummy_vfs_t overflow_inst = { .match_path = "", .called = false }; + TEST_ESP_ERR(ESP_ERR_NO_MEM, + esp_vfs_register_fs("/overflow", &s_dummy_vfs, + ESP_VFS_FLAG_CONTEXT_PTR, &overflow_inst)); + + /* Unregister an entry in the middle (not the first, not the topmost). This + * creates a NULL hole below the upper bound. */ + int middle = registered / 2; + TEST_ASSERT_NOT_EQUAL(0, middle); + TEST_ASSERT_NOT_EQUAL(registered - 1, middle); + TEST_ESP_OK(esp_vfs_unregister(paths[middle])); + + /* Register again: with only the middle slot free, esp_get_free_index() must + * return exactly that slot and the registration must succeed. */ + insts[middle] = (dummy_vfs_t) { .match_path = "", .called = false }; + TEST_ESP_OK(esp_vfs_register_fs(paths[middle], &s_dummy_vfs, + ESP_VFS_FLAG_CONTEXT_PTR, &insts[middle])); + + /* The table must be full again - the hole was reused, not appended. */ + TEST_ESP_ERR(ESP_ERR_NO_MEM, + esp_vfs_register_fs("/overflow", &s_dummy_vfs, + ESP_VFS_FLAG_CONTEXT_PTR, &overflow_inst)); + + /* Clean up everything. */ + for (int i = 0; i < registered; ++i) { + TEST_ESP_OK(esp_vfs_unregister(paths[i])); + } +} + +TEST_CASE("vfs survives repeated register/unregister cycles", "[vfs]") +{ + /* Fill the whole VFS table and then drain it again, several times over. + * + * On the fixed code every round must be able to register exactly the same + * number of entries, because unregistering lowers the upper bound again. + * On the buggy code the stale counter never came back down, so the second + * and later rounds would be able to register fewer entries (and eventually + * none at all), which this test detects. */ + static dummy_vfs_t insts[TEST_VFS_MAX_COUNT]; + char paths[TEST_VFS_MAX_COUNT][8]; + for (int i = 0; i < TEST_VFS_MAX_COUNT; ++i) { + make_test_path(paths[i], sizeof(paths[i]), i); + } + + const int rounds = 5; + int first_round_count = -1; + for (int r = 0; r < rounds; ++r) { + /* Register until the table is full. */ + int count = 0; + for (int i = 0; i < TEST_VFS_MAX_COUNT; ++i) { + insts[i] = (dummy_vfs_t) { .match_path = "", .called = false }; + esp_err_t err = esp_vfs_register_fs(paths[i], &s_dummy_vfs, + ESP_VFS_FLAG_CONTEXT_PTR, &insts[i]); + if (err == ESP_ERR_NO_MEM) { + break; + } + TEST_ESP_OK(err); + count++; + } + + if (first_round_count < 0) { + first_round_count = count; + TEST_ASSERT_GREATER_THAN(0, first_round_count); + } else { + /* The capacity must not shrink between rounds. */ + TEST_ASSERT_EQUAL_INT(first_round_count, count); + } + + /* Drain the table again. */ + for (int i = 0; i < count; ++i) { + TEST_ESP_OK(esp_vfs_unregister(paths[i])); + } + } +} diff --git a/components/vfs/test_apps/pytest_vfs.py b/components/vfs/test_apps/pytest_vfs.py index 1220b7d6764..032a8bb83bf 100644 --- a/components/vfs/test_apps/pytest_vfs.py +++ b/components/vfs/test_apps/pytest_vfs.py @@ -6,6 +6,7 @@ from pytest_embedded_idf.utils import idf_parametrize @pytest.mark.generic +@pytest.mark.flaky(reruns=2, reruns_delay=5) @pytest.mark.parametrize( 'config', [ @@ -17,3 +18,29 @@ from pytest_embedded_idf.utils import idf_parametrize @idf_parametrize('target', ['esp32c2', 'esp32c3', 'esp32c6', 'esp32h2'], indirect=['target']) def test_vfs_default(dut: Dut) -> None: dut.run_all_single_board_cases() + + +@pytest.mark.generic +@pytest.mark.parametrize( + 'config', + [ + 'ccomp', + ], + indirect=True, +) +@idf_parametrize('target', ['esp32'], indirect=['target']) +def test_vfs_ccomp(dut: Dut) -> None: + dut.run_all_single_board_cases() + + +@pytest.mark.quad_psram +@pytest.mark.parametrize( + 'config', + [ + 'psram', + ], + indirect=True, +) +@idf_parametrize('target', ['esp32s3'], indirect=['target']) +def test_vfs_psram(dut: Dut) -> None: + dut.run_all_single_board_cases() diff --git a/components/vfs/test_apps/sdkconfig.ci.ccomp b/components/vfs/test_apps/sdkconfig.ci.ccomp new file mode 100644 index 00000000000..e69de29bb2d diff --git a/components/vfs/test_apps/sdkconfig.ci.psram b/components/vfs/test_apps/sdkconfig.ci.psram new file mode 100644 index 00000000000..e69de29bb2d diff --git a/components/vfs/vfs.c b/components/vfs/vfs.c index e15787c590d..5afc877a73a 100644 --- a/components/vfs/vfs.c +++ b/components/vfs/vfs.c @@ -56,7 +56,7 @@ _Static_assert((1 << (sizeof(vfs_index_t)*8)) >= VFS_MAX_COUNT, "VFS index type _Static_assert(((vfs_index_t) -1) < 0, "vfs_index_t must be a signed type"); static vfs_entry_t* s_vfs[VFS_MAX_COUNT] = { 0 }; -static size_t s_vfs_count = 0; +static size_t s_vfs_upper_bound = 0; // upper bound of indices in s_vfs which can be occupied by VFS entries; always equal to the index of the last non-NULL entry + 1 static fd_table_t s_fd_table[MAX_FDS] = { [0 ... MAX_FDS-1] = FD_TABLE_ENTRY_UNUSED }; static _lock_t s_fd_table_lock; @@ -394,8 +394,9 @@ static esp_err_t esp_vfs_register_fs_common( void *ctx, int *vfs_index) { - if (s_vfs_count >= VFS_MAX_COUNT) { - return ESP_ERR_NO_MEM; + ssize_t index = esp_get_free_index(); + if (index < 0) { // Check for free slot before doing any other work + return ESP_ERR_NO_MEM; } if (vfs == NULL) { @@ -416,17 +417,12 @@ static esp_err_t esp_vfs_register_fs_common( } } - ssize_t index = esp_get_free_index(); - if (index < 0) { - return ESP_ERR_NO_MEM; - } - if (s_vfs[index] != NULL) { return ESP_ERR_INVALID_STATE; } - if (index == s_vfs_count) { - s_vfs_count++; + if (index == s_vfs_upper_bound) { + s_vfs_upper_bound++; } vfs_entry_t *entry = heap_caps_malloc(sizeof(vfs_entry_t) + base_path_len + 1, VFS_MALLOC_FLAGS); @@ -604,6 +600,10 @@ esp_err_t esp_vfs_unregister_with_id(esp_vfs_id_t vfs_id) } _lock_release(&s_fd_table_lock); + while (s_vfs_upper_bound > 0 && s_vfs[s_vfs_upper_bound - 1] == NULL) { // Move the upper bound down if we just removed the last entry + s_vfs_upper_bound--; + } + return ESP_OK; } @@ -613,7 +613,7 @@ esp_err_t esp_vfs_unregister_fs_with_id(esp_vfs_id_t vfs_id) __attribute__((alia esp_err_t esp_vfs_unregister(const char* base_path) { const size_t base_path_len = strlen(base_path); - for (size_t i = 0; i < s_vfs_count; ++i) { + for (size_t i = 0; i < s_vfs_upper_bound; ++i) { vfs_entry_t* vfs = s_vfs[i]; if (vfs == NULL) { continue; @@ -635,7 +635,7 @@ esp_err_t esp_vfs_register_fd(esp_vfs_id_t vfs_id, int *fd) esp_err_t esp_vfs_register_fd_with_local_fd(esp_vfs_id_t vfs_id, int local_fd, bool permanent, int *fd) { - if (vfs_id < 0 || vfs_id >= s_vfs_count || fd == NULL) { + if (vfs_id < 0 || vfs_id >= s_vfs_upper_bound || fd == NULL) { ESP_LOGD(TAG, "Invalid arguments for esp_vfs_register_fd_with_local_fd(%d, %d, %d, 0x%p)", vfs_id, local_fd, permanent, fd); return ESP_ERR_INVALID_ARG; @@ -669,7 +669,7 @@ esp_err_t esp_vfs_unregister_fd(esp_vfs_id_t vfs_id, int fd) { esp_err_t ret = ESP_ERR_INVALID_ARG; - if (vfs_id < 0 || vfs_id >= s_vfs_count || fd < 0 || fd >= MAX_FDS) { + if (vfs_id < 0 || vfs_id >= s_vfs_upper_bound || fd < 0 || fd >= MAX_FDS) { ESP_LOGD(TAG, "Invalid arguments for esp_vfs_unregister_fd(%d, %d)", vfs_id, fd); return ret; } @@ -732,7 +732,7 @@ void esp_vfs_dump_registered_paths(FILE *fp) esp_err_t esp_vfs_set_readonly_flag(const char* base_path) { const size_t base_path_len = strlen(base_path); - for (size_t i = 0; i < s_vfs_count; ++i) { + for (size_t i = 0; i < s_vfs_upper_bound; ++i) { vfs_entry_t* vfs = s_vfs[i]; if (vfs == NULL) { continue; @@ -748,11 +748,10 @@ esp_err_t esp_vfs_set_readonly_flag(const char* base_path) const vfs_entry_t *get_vfs_for_index(int index) { - if (index < 0 || index >= s_vfs_count) { + if (index < 0 || index >= VFS_MAX_COUNT) { return NULL; - } else { - return s_vfs[index]; } + return s_vfs[index]; } int register_fd(int vfs_index, int local_fd, bool permanent) @@ -836,7 +835,7 @@ const vfs_entry_t* get_vfs_for_path(const char* path) const vfs_entry_t* best_match = NULL; ssize_t best_match_prefix_len = -1; size_t len = strlen(path); - for (size_t i = 0; i < s_vfs_count; ++i) { + for (size_t i = 0; i < s_vfs_upper_bound; ++i) { const vfs_entry_t* vfs = s_vfs[i]; if (vfs == NULL || vfs->path_prefix_len == LEN_PATH_PREFIX_IGNORED) { continue; @@ -860,7 +859,7 @@ const vfs_entry_t* get_vfs_for_path(const char* path) // Out of all matching path prefixes, select the longest one; // i.e. if "/dev" and "/dev/uart" both match, for "/dev/uart/1" path, // choose "/dev/uart", - // This causes all s_vfs_count VFS entries to be scanned when opening + // This causes all s_vfs_upper_bound VFS entries to be scanned when opening // a file by name. This can be optimized by introducing a table for // FS search order, sorted so that longer prefixes are checked first. if (best_match_prefix_len < (ssize_t) vfs->path_prefix_len) { @@ -871,9 +870,9 @@ const vfs_entry_t* get_vfs_for_path(const char* path) return best_match; } -size_t get_vfs_count(void) +size_t get_vfs_upper_bound(void) { - return s_vfs_count; + return s_vfs_upper_bound; } void close_pending(int nfds) diff --git a/components/vfs/vfs_calls.c b/components/vfs/vfs_calls.c index 4bd12fa3f99..85c5b25aaf5 100644 --- a/components/vfs/vfs_calls.c +++ b/components/vfs/vfs_calls.c @@ -565,10 +565,10 @@ int esp_vfs_select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *errorfds return -1; } - // Capture s_vfs_count to a local variable in case a new driver is registered or removed during this actual select() - // call. s_vfs_count cannot be protected with a mutex during a select() call (which can be one without a timeout) + // Capture s_vfs_upper_bound to a local variable in case a new driver is registered or removed during this actual select() + // call. s_vfs_upper_bound cannot be protected with a mutex during a select() call (which can be one without a timeout) // because that could block the registration of new driver. - const size_t vfs_count = get_vfs_count(); + const size_t vfs_count = get_vfs_upper_bound(); fds_triple_t *vfs_fds_triple; if ((vfs_fds_triple = heap_caps_calloc(vfs_count, sizeof(fds_triple_t), VFS_MALLOC_FLAGS)) == NULL) { __errno_r(r) = ENOMEM; @@ -764,7 +764,7 @@ void esp_vfs_select_triggered(esp_vfs_select_sem_t sem) // Another way would be to go through s_fd_table and find the VFS // which has a permanent FD. But in order to avoid to lock // s_fd_table_lock we go through the VFS table. - size_t vfs_count = get_vfs_count(); + size_t vfs_count = get_vfs_upper_bound(); for (int i = 0; i < vfs_count; ++i) { // Note: vfs_count could have changed since the start of vfs_select() call. However, that change doesn't // matter here stop_socket_select() will be called for only valid VFS drivers. @@ -788,9 +788,9 @@ void esp_vfs_select_triggered_isr(esp_vfs_select_sem_t sem, BaseType_t *woken) // Another way would be to go through s_fd_table and find the VFS // which has a permanent FD. But in order to avoid to lock // s_fd_table_lock we go through the VFS table. - size_t vfs_count = get_vfs_count(); + size_t vfs_count = get_vfs_upper_bound(); for (int i = 0; i < vfs_count; ++i) { - // Note: s_vfs_count could have changed since the start of vfs_select() call. However, that change doesn't + // Note: s_vfs_upper_bound could have changed since the start of vfs_select() call. However, that change doesn't // matter here stop_socket_select() will be called for only valid VFS drivers. const vfs_entry_t *vfs = get_vfs_for_index(i); if (vfs != NULL diff --git a/components/wpa_supplicant/esp_supplicant/src/crypto/crypto_mbedtls.c b/components/wpa_supplicant/esp_supplicant/src/crypto/crypto_mbedtls.c index 3ce59e457c2..e0c60da9869 100644 --- a/components/wpa_supplicant/esp_supplicant/src/crypto/crypto_mbedtls.c +++ b/components/wpa_supplicant/esp_supplicant/src/crypto/crypto_mbedtls.c @@ -114,13 +114,6 @@ int md5_vector(size_t num_elem, const u8 *addr[], const size_t *len, u8 *mac) return digest_vector(PSA_ALG_MD5, num_elem, addr, len, mac); } -#ifdef MBEDTLS_MD4_C -int md4_vector(size_t num_elem, const u8 *addr[], const size_t *len, u8 *mac) -{ - return digest_vector(MBEDTLS_MD_MD4, num_elem, addr, len, mac); -} -#endif - struct crypto_hash * crypto_hash_init(enum crypto_hash_alg alg, const u8 *key, size_t key_len) { @@ -421,10 +414,29 @@ int hmac_sha1(const u8 *key, size_t key_len, const u8 *data, size_t data_len, } #endif +static psa_status_t psa_import_aes_key(const u8 *key, size_t key_len, + psa_algorithm_t alg, + psa_key_usage_t usage, + psa_key_id_t *key_id) +{ + psa_key_attributes_t attributes = PSA_KEY_ATTRIBUTES_INIT; + psa_status_t status; + + psa_set_key_type(&attributes, PSA_KEY_TYPE_AES); + psa_set_key_bits(&attributes, key_len * 8); + psa_set_key_algorithm(&attributes, alg); + psa_set_key_usage_flags(&attributes, usage); + + status = psa_import_key(&attributes, key, key_len, key_id); + psa_reset_key_attributes(&attributes); + + return status; +} + static void *aes_crypt_init(int mode, const u8 *key, size_t len) { psa_status_t status; - psa_key_attributes_t attributes = PSA_KEY_ATTRIBUTES_INIT; + psa_key_usage_t usage = 0; psa_key_id_t *key_id = os_malloc(sizeof(psa_key_id_t)); if (key_id == NULL) { @@ -432,17 +444,15 @@ static void *aes_crypt_init(int mode, const u8 *key, size_t len) } if (mode == MBEDTLS_ENCRYPT) { - psa_set_key_usage_flags(&attributes, PSA_KEY_USAGE_ENCRYPT); + usage = PSA_KEY_USAGE_ENCRYPT; } else if (mode == MBEDTLS_DECRYPT) { - psa_set_key_usage_flags(&attributes, PSA_KEY_USAGE_DECRYPT); + usage = PSA_KEY_USAGE_DECRYPT; + } else { + os_free(key_id); + return NULL; } - psa_set_key_algorithm(&attributes, PSA_ALG_ECB_NO_PADDING); - psa_set_key_type(&attributes, PSA_KEY_TYPE_AES); - psa_set_key_bits(&attributes, len * 8); - - status = psa_import_key(&attributes, key, len, key_id); - psa_reset_key_attributes(&attributes); + status = psa_import_aes_key(key, len, PSA_ALG_ECB_NO_PADDING, usage, key_id); if (status != PSA_SUCCESS) { wpa_printf(MSG_ERROR, "%s: psa_import_key failed", __func__); os_free(key_id); @@ -537,16 +547,10 @@ void aes_decrypt_deinit(void *ctx) int aes_128_cbc_encrypt(const u8 *key, const u8 *iv, u8 *data, size_t data_len) { psa_status_t status; - psa_key_attributes_t attributes = PSA_KEY_ATTRIBUTES_INIT; psa_key_id_t key_id; - psa_set_key_usage_flags(&attributes, PSA_KEY_USAGE_ENCRYPT); - psa_set_key_algorithm(&attributes, PSA_ALG_CBC_NO_PADDING); - psa_set_key_type(&attributes, PSA_KEY_TYPE_AES); - psa_set_key_bits(&attributes, 128); - - status = psa_import_key(&attributes, key, 16, &key_id); - psa_reset_key_attributes(&attributes); + status = psa_import_aes_key(key, 16, PSA_ALG_CBC_NO_PADDING, + PSA_KEY_USAGE_ENCRYPT, &key_id); if (status != PSA_SUCCESS) { wpa_printf(MSG_ERROR, "%s: psa_import_key failed", __func__); return -1; @@ -596,16 +600,10 @@ int aes_128_cbc_encrypt(const u8 *key, const u8 *iv, u8 *data, size_t data_len) int aes_128_cbc_decrypt(const u8 *key, const u8 *iv, u8 *data, size_t data_len) { psa_status_t status; - psa_key_attributes_t attributes = PSA_KEY_ATTRIBUTES_INIT; psa_key_id_t key_id; - psa_set_key_usage_flags(&attributes, PSA_KEY_USAGE_DECRYPT); - psa_set_key_algorithm(&attributes, PSA_ALG_CBC_NO_PADDING); - psa_set_key_type(&attributes, PSA_KEY_TYPE_AES); - psa_set_key_bits(&attributes, 128); - - status = psa_import_key(&attributes, key, 16, &key_id); - psa_reset_key_attributes(&attributes); + status = psa_import_aes_key(key, 16, PSA_ALG_CBC_NO_PADDING, + PSA_KEY_USAGE_DECRYPT, &key_id); if (status != PSA_SUCCESS) { wpa_printf(MSG_ERROR, "%s: psa_import_key failed", __func__); return -1; @@ -894,25 +892,18 @@ int aes_ctr_encrypt(const u8 *key, size_t key_len, const u8 *nonce, u8 *data, size_t data_len) { psa_status_t status; - psa_key_attributes_t attributes = PSA_KEY_ATTRIBUTES_INIT; psa_key_id_t key_id = 0; psa_cipher_operation_t operation = PSA_CIPHER_OPERATION_INIT; int ret = -1; u8 *temp_buf = NULL; - psa_set_key_usage_flags(&attributes, PSA_KEY_USAGE_ENCRYPT); - psa_set_key_algorithm(&attributes, PSA_ALG_CTR); - psa_set_key_type(&attributes, PSA_KEY_TYPE_AES); - psa_set_key_bits(&attributes, key_len * 8); - - status = psa_import_key(&attributes, key, key_len, &key_id); + status = psa_import_aes_key(key, key_len, PSA_ALG_CTR, + PSA_KEY_USAGE_ENCRYPT, &key_id); if (status != PSA_SUCCESS) { wpa_printf(MSG_ERROR, "%s: psa_import_key failed", __func__); goto cleanup; } - psa_reset_key_attributes(&attributes); - status = psa_cipher_encrypt_setup(&operation, key_id, PSA_ALG_CTR); if (status != PSA_SUCCESS) { wpa_printf(MSG_ERROR, "%s: psa_cipher_encrypt_setup failed", __func__); @@ -986,43 +977,43 @@ int aes_128_ctr_encrypt(const u8 *key, const u8 *nonce, #ifdef MBEDTLS_NIST_KW_C int aes_wrap(const u8 *kek, size_t kek_len, int n, const u8 *plain, u8 *cipher) { - mbedtls_nist_kw_context ctx; - size_t olen; - int ret = 0; - mbedtls_nist_kw_init(&ctx); + psa_key_id_t key_id = 0; + psa_status_t status; + size_t olen = 0; - ret = mbedtls_nist_kw_setkey(&ctx, MBEDTLS_CIPHER_ID_AES, - kek, kek_len * 8, 1); - if (ret != 0) { - return ret; + status = psa_import_aes_key(kek, kek_len, PSA_ALG_ECB_NO_PADDING, + PSA_KEY_USAGE_ENCRYPT, &key_id); + if (status != PSA_SUCCESS) { + return -1; } - ret = mbedtls_nist_kw_wrap(&ctx, MBEDTLS_KW_MODE_KW, plain, - n * 8, cipher, &olen, (n + 1) * 8); + status = mbedtls_nist_kw_wrap(key_id, MBEDTLS_KW_MODE_KW, plain, + (size_t) n * 8, cipher, + (size_t)(n + 1) * 8, &olen); + psa_destroy_key(key_id); - mbedtls_nist_kw_free(&ctx); - return ret; + return status == PSA_SUCCESS ? 0 : -1; } int aes_unwrap(const u8 *kek, size_t kek_len, int n, const u8 *cipher, u8 *plain) { - mbedtls_nist_kw_context ctx; - size_t olen; - int ret = 0; - mbedtls_nist_kw_init(&ctx); + psa_key_id_t key_id = 0; + psa_status_t status; + size_t olen = 0; - ret = mbedtls_nist_kw_setkey(&ctx, MBEDTLS_CIPHER_ID_AES, - kek, kek_len * 8, 0); - if (ret != 0) { - return ret; + status = psa_import_aes_key(kek, kek_len, PSA_ALG_ECB_NO_PADDING, + PSA_KEY_USAGE_DECRYPT, &key_id); + if (status != PSA_SUCCESS) { + return -1; } - ret = mbedtls_nist_kw_unwrap(&ctx, MBEDTLS_KW_MODE_KW, cipher, - (n + 1) * 8, plain, &olen, (n * 8)); + status = mbedtls_nist_kw_unwrap(key_id, MBEDTLS_KW_MODE_KW, cipher, + (size_t)(n + 1) * 8, plain, + (size_t) n * 8, &olen); + psa_destroy_key(key_id); - mbedtls_nist_kw_free(&ctx); - return ret; + return status == PSA_SUCCESS ? 0 : -1; } #endif @@ -1177,22 +1168,15 @@ int aes_ccm_ae(const u8 *key, size_t key_len, const u8 *nonce, const u8 *aad, size_t aad_len, u8 *crypt, u8 *auth) { psa_status_t status; - psa_key_attributes_t attributes = PSA_KEY_ATTRIBUTES_INIT; psa_key_id_t key_id; - psa_set_key_usage_flags(&attributes, PSA_KEY_USAGE_ENCRYPT); - psa_set_key_algorithm(&attributes, PSA_ALG_CCM); - psa_set_key_type(&attributes, PSA_KEY_TYPE_AES); - psa_set_key_bits(&attributes, key_len * 8); - - status = psa_import_key(&attributes, key, key_len, &key_id); + status = psa_import_aes_key(key, key_len, PSA_ALG_CCM, + PSA_KEY_USAGE_ENCRYPT, &key_id); if (status != PSA_SUCCESS) { wpa_printf(MSG_ERROR, "%s: psa_import_key failed", __func__); return -1; } - psa_reset_key_attributes(&attributes); - psa_aead_operation_t operation = PSA_AEAD_OPERATION_INIT; status = psa_aead_encrypt_setup(&operation, key_id, PSA_ALG_CCM); @@ -1259,19 +1243,13 @@ int aes_ccm_ad(const u8 *key, size_t key_len, const u8 *nonce, u8 *plain) { psa_status_t status; - psa_key_attributes_t attributes = PSA_KEY_ATTRIBUTES_INIT; psa_key_id_t key_id; u8 *ciphertext_with_tag = NULL; size_t plaintext_length = 0; int ret = -1; - psa_set_key_usage_flags(&attributes, PSA_KEY_USAGE_DECRYPT); - psa_set_key_algorithm(&attributes, PSA_ALG_CCM); - psa_set_key_type(&attributes, PSA_KEY_TYPE_AES); - psa_set_key_bits(&attributes, key_len * 8); - - status = psa_import_key(&attributes, key, key_len, &key_id); - psa_reset_key_attributes(&attributes); + status = psa_import_aes_key(key, key_len, PSA_ALG_CCM, + PSA_KEY_USAGE_DECRYPT, &key_id); if (status != PSA_SUCCESS) { wpa_printf(MSG_ERROR, "%s: psa_import_key failed", __func__); return -1; @@ -1322,24 +1300,17 @@ int omac1_aes_vector(const u8 *key, size_t key_len, size_t num_elem, } psa_status_t status; - psa_key_attributes_t attributes = PSA_KEY_ATTRIBUTES_INIT; psa_key_id_t key_id = 0; psa_mac_operation_t operation = PSA_MAC_OPERATION_INIT; int ret = -1; - psa_set_key_usage_flags(&attributes, PSA_KEY_USAGE_SIGN_HASH); - psa_set_key_algorithm(&attributes, PSA_ALG_CMAC); - psa_set_key_type(&attributes, PSA_KEY_TYPE_AES); - psa_set_key_bits(&attributes, key_len * 8); - - status = psa_import_key(&attributes, key, key_len, &key_id); + status = psa_import_aes_key(key, key_len, PSA_ALG_CMAC, + PSA_KEY_USAGE_SIGN_HASH, &key_id); if (status != PSA_SUCCESS) { wpa_printf(MSG_ERROR, "%s: psa_import_key failed", __func__); goto cleanup; } - psa_reset_key_attributes(&attributes); - status = psa_mac_sign_setup(&operation, key_id, PSA_ALG_CMAC); if (status != PSA_SUCCESS) { wpa_printf(MSG_ERROR, "%s: psa_mac_sign_setup failed", __func__); diff --git a/components/wpa_supplicant/src/rsn_supp/wpa.c b/components/wpa_supplicant/src/rsn_supp/wpa.c index 254c23868fa..735fa6abcac 100644 --- a/components/wpa_supplicant/src/rsn_supp/wpa.c +++ b/components/wpa_supplicant/src/rsn_supp/wpa.c @@ -715,9 +715,11 @@ void wpa_supplicant_process_1_of_4(struct wpa_sm *sm, #ifdef CONFIG_ESP_WIFI_ENTERPRISE_SUPPORT if (is_wpa2_enterprise_connection()) { wpa2_ent_eap_state_t state = eap_client_get_eap_state(); - if (state != WPA2_ENT_EAP_STATE_SUCCESS) { - wpa_printf(MSG_INFO, "EAP not completed (state=%d)." - " Drop EAPOL message.", state); + + if (state == WPA2_ENT_EAP_STATE_IN_PROGRESS || sm->pmk_len == 0) { + wpa_printf(MSG_INFO, + "Drop EAPOL M1: EAP state=%d, pmk_len=%u.", + state, (unsigned int) sm->pmk_len); return; } } @@ -2701,6 +2703,7 @@ int wpa_set_bss(uint8_t *macddr, uint8_t *bssid, uint8_t pairwise_cipher, uint8_ if (pmksa) { pmksa_cache_flush(sm->pmksa, NULL, pmksa->pmk, pmksa->pmk_len); } + wpa_sm_drop_sa(sm); } #ifdef CONFIG_IEEE80211W diff --git a/components/wpa_supplicant/src/rsn_supp/wpa_ft.c b/components/wpa_supplicant/src/rsn_supp/wpa_ft.c index 97db4302c91..ac05a2fb4cc 100644 --- a/components/wpa_supplicant/src/rsn_supp/wpa_ft.c +++ b/components/wpa_supplicant/src/rsn_supp/wpa_ft.c @@ -695,7 +695,6 @@ static int wpa_ft_process_igtk_subelem(struct wpa_sm *sm, const u8 *igtk_elem, size_t igtk_elem_len) { u8 igtk[WPA_IGTK_LEN]; - wifi_wpa_igtk_t *_igtk = (wifi_wpa_igtk_t*)igtk_elem; if (sm->mgmt_group_cipher != WPA_CIPHER_AES_128_CMAC) return 0; @@ -731,17 +730,31 @@ static int wpa_ft_process_igtk_subelem(struct wpa_sm *sm, const u8 *igtk_elem, wpa_hexdump_key(MSG_DEBUG, "FT: IGTK from Reassoc Resp ", igtk, WPA_IGTK_LEN); #ifdef ESP_SUPPLICANT - if (esp_wifi_set_igtk_internal(WIFI_IF_STA, (wifi_wpa_igtk_t *)_igtk) < 0) { -#else - keyidx = WPA_GET_LE16(igtk_elem); - if (wpa_sm_set_key(&(sm->install_gtk), WIFI_WPA_ALG_IGTK, sm->bssid, keyidx, 0, - (u8 *)(igtk_elem + 2), 6, igtk, WPA_IGTK_LEN, sm->key_entry_valid) < 0) { -#endif + wifi_wpa_igtk_t igtk_drv; + + os_memset(&igtk_drv, 0, sizeof(igtk_drv)); + os_memcpy(igtk_drv.keyid, igtk_elem, 2); + os_memcpy(igtk_drv.pn, igtk_elem + 2, 6); + os_memcpy(igtk_drv.igtk, igtk, WPA_IGTK_LEN); + if (esp_wifi_set_igtk_internal(WIFI_IF_STA, &igtk_drv) < 0) { + forced_memzero(&igtk_drv, sizeof(igtk_drv)); + forced_memzero(igtk, sizeof(igtk)); wpa_printf(MSG_WARNING, "WPA: Failed to set IGTK to the " "driver."); return -1; } - + forced_memzero(&igtk_drv, sizeof(igtk_drv)); +#else + keyidx = WPA_GET_LE16(igtk_elem); + if (wpa_sm_set_key(&(sm->install_gtk), WIFI_WPA_ALG_IGTK, sm->bssid, keyidx, 0, + (u8 *)(igtk_elem + 2), 6, igtk, WPA_IGTK_LEN, sm->key_entry_valid) < 0) { + forced_memzero(igtk, sizeof(igtk)); + wpa_printf(MSG_WARNING, "WPA: Failed to set IGTK to the " + "driver."); + return -1; + } +#endif + forced_memzero(igtk, sizeof(igtk)); return 0; } #endif /* CONFIG_IEEE80211W */ diff --git a/components/xtensa/xtensa_vectors.S b/components/xtensa/xtensa_vectors.S index e3c6405785a..abd81f81c28 100644 --- a/components/xtensa/xtensa_vectors.S +++ b/components/xtensa/xtensa_vectors.S @@ -474,7 +474,7 @@ _DebugExceptionVector: .type _xt_debugexception,@function .align 4 _xt_debugexception: -#if (CONFIG_ESP32_ECO3_CACHE_LOCK_FIX && CONFIG_BTDM_CTRL_HLI) +#if (CONFIG_ESP_INT_WDT && CONFIG_ESP32_ECO3_CACHE_LOCK_FIX && CONFIG_BTDM_CTRL_HLI) s32i a0, sp, XT_STK_EXIT #define XT_DEBUGCAUSE_DI (5) @@ -489,7 +489,7 @@ _xt_debugexception: extui a0, a0, XT_DEBUGCAUSE_DI, 1 bnez a0, _xt_debug_di_exc 1: -#endif //(CONFIG_ESP32_ECO3_CACHE_LOCK_FIX && CONFIG_BTDM_CTRL_HLI) +#endif //(CONFIG_ESP_INT_WDT && CONFIG_ESP32_ECO3_CACHE_LOCK_FIX && CONFIG_BTDM_CTRL_HLI) movi a0,PANIC_RSN_DEBUGEXCEPTION wsr a0,XT_REG_EXCCAUSE @@ -507,7 +507,7 @@ _xt_debugexception: #endif // CONFIG_ESP_SYSTEM_GDBSTUB_RUNTIME rfi XCHAL_DEBUGLEVEL -#if (CONFIG_ESP32_ECO3_CACHE_LOCK_FIX && CONFIG_BTDM_CTRL_HLI) +#if (CONFIG_ESP_INT_WDT && CONFIG_ESP32_ECO3_CACHE_LOCK_FIX && CONFIG_BTDM_CTRL_HLI) .align 4 _xt_debug_di_exc: @@ -566,7 +566,7 @@ _xt_debug_di_exc: rsr a0, XT_REG_EXCSAVE+XCHAL_DEBUGLEVEL rfi XCHAL_DEBUGLEVEL -#endif //(CONFIG_ESP32_ECO3_CACHE_LOCK_FIX && CONFIG_BTDM_CTRL_HLI) +#endif //(CONFIG_ESP_INT_WDT && CONFIG_ESP32_ECO3_CACHE_LOCK_FIX && CONFIG_BTDM_CTRL_HLI) #endif // XCHAL_HAVE_DEBUG /* diff --git a/docs/conf_common.py b/docs/conf_common.py index 795ae95f295..6987149302a 100644 --- a/docs/conf_common.py +++ b/docs/conf_common.py @@ -368,6 +368,7 @@ conditional_include_dict = { 'SOC_ECDSA_SUPPORTED': ['api-reference/peripherals/ecdsa.rst'], 'SOC_HMAC_SUPPORTED': ['api-reference/peripherals/hmac.rst'], 'SOC_ASYNC_MEMCPY_SUPPORTED': ['api-reference/system/async_memcpy.rst'], + 'SOC_DMA2D_SUPPORTED': ['api-reference/peripherals/async_color_convert.rst'], 'SOC_KEY_MANAGER_SUPPORTED': ['api-reference/peripherals/key_manager.rst'], 'CONFIG_IDF_TARGET_ARCH_XTENSA': XTENSA_DOCS, 'CONFIG_IDF_TARGET_ARCH_RISCV': RISCV_DOCS, @@ -409,6 +410,7 @@ conditional_include_dict = { extensions += [ # noqa: F405 'sphinx_copybutton', 'sphinxcontrib.wavedrom', + 'sphinxcontrib.mermaid', # Note: order is important here, events must # be registered by one extension before they can be # connected to another extension diff --git a/docs/doxygen/Doxyfile b/docs/doxygen/Doxyfile index 69aee0bfabd..64bd2c103ad 100644 --- a/docs/doxygen/Doxyfile +++ b/docs/doxygen/Doxyfile @@ -82,6 +82,7 @@ INPUT = \ $(PROJECT_PATH)/components/esp_adc/include/esp_adc/adc_continuous.h \ $(PROJECT_PATH)/components/esp_adc/include/esp_adc/adc_oneshot.h \ $(PROJECT_PATH)/components/esp_app_format/include/esp_app_desc.h \ + $(PROJECT_PATH)/components/esp_blockdev/include/esp_blockdev.h \ $(PROJECT_PATH)/components/esp_bootloader_format/include/esp_bootloader_desc.h \ $(PROJECT_PATH)/components/esp_common/include/esp_check.h \ $(PROJECT_PATH)/components/esp_common/include/esp_err.h \ @@ -96,6 +97,7 @@ INPUT = \ $(PROJECT_PATH)/components/esp_driver_dac/include/driver/dac_cosine.h \ $(PROJECT_PATH)/components/esp_driver_dac/include/driver/dac_oneshot.h \ $(PROJECT_PATH)/components/esp_driver_dac/include/driver/dac_types.h \ + $(PROJECT_PATH)/components/esp_driver_dma/include/esp_async_color_convert.h \ $(PROJECT_PATH)/components/esp_driver_dma/include/esp_async_memcpy.h \ $(PROJECT_PATH)/components/esp_driver_gpio/include/driver/dedic_gpio.h \ $(PROJECT_PATH)/components/esp_driver_gpio/include/driver/gpio.h \ diff --git a/docs/en/api-guides/app_trace.rst b/docs/en/api-guides/app_trace.rst index c3d6456b171..5c4818c121c 100644 --- a/docs/en/api-guides/app_trace.rst +++ b/docs/en/api-guides/app_trace.rst @@ -557,9 +557,27 @@ Good instructions on how to install, configure, and visualize data in Impulse fr If you have problems with visualization (no data is shown or strange behaviors of zoom action are observed), you can try to delete current signal hierarchy and double-click on the necessary file or port. Eclipse will ask you to create a new signal hierarchy. +Application Examples +"""""""""""""""""""" + +- :example:`system/sysview_tracing` demonstrates how to trace FreeRTOS task and system events using SEGGER SystemView. +- :example:`system/sysview_tracing_heap_log` demonstrates heap allocation tracing alongside SystemView events. + .. _app_trace-gcov-source-code-coverage: Gcov (Source Code Coverage) ^^^^^^^^^^^^^^^^^^^^^^^^^^^ In ESP-IDF projects, code coverage analysis using gcov can be done with the help of `espressif/esp_gcov `_ managed component. + +.. _app_trace-integrating-a-custom-trace-library: + +Integrating a Custom Trace Library +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The ``esp_trace`` component exposes a stable extension point (``CONFIG_ESP_TRACE_LIB_EXTERNAL``) for plugging in a third-party trace recorder without patching ESP-IDF. An external component provides an encoder adapter (registered via ``ESP_TRACE_REGISTER_ENCODER()``) and a slim ``esp_trace_freertos_impl.h`` that injects the desired FreeRTOS trace hooks. The encoder vtable also offers optional ``start`` / ``stop`` / ``flush`` and ``take_lock`` / ``give_lock`` entries dispatched from the public :cpp:func:`esp_trace_start`, :cpp:func:`esp_trace_stop`, :cpp:func:`esp_trace_flush` API. + +Application Examples +"""""""""""""""""""" + +- :example:`system/esp_trace` is a minimal copy-paste template that wires up an external encoder, demonstrates the FreeRTOS trace-hook include-chain contract, and covers cross-core serialization through the encoder lock. diff --git a/docs/en/api-guides/build-system.rst b/docs/en/api-guides/build-system.rst index ededb11ac05..3ec4b034881 100644 --- a/docs/en/api-guides/build-system.rst +++ b/docs/en/api-guides/build-system.rst @@ -77,6 +77,14 @@ In the above list, the ``cmake`` command configures the project and generates bu It's not necessary to run ``cmake`` more than once. After the first build, you only need to run ``ninja`` each time. ``ninja`` will automatically re-invoke ``cmake`` if the project needs reconfiguration. +When using ``idf.py`` with the Ninja generator, you can cap the number of parallel build jobs by setting the ``IDF_PY_BUILD_JOBS`` environment variable. For example: + +.. code-block:: bash + + IDF_PY_BUILD_JOBS=6 idf.py build + +If you invoke CMake, ``ninja``, or ``make`` directly instead of ``idf.py``, use their native options or environment variables to control parallelism. + If using CMake with ``ninja`` or ``make``, there are also targets for more of the ``idf.py`` sub-commands. For example, running ``make menuconfig`` or ``ninja menuconfig`` in the build directory will work the same as ``idf.py menuconfig``. .. note:: diff --git a/docs/en/api-guides/external-ram.rst b/docs/en/api-guides/external-ram.rst index 4ecedce60e6..9f6a2a9017d 100644 --- a/docs/en/api-guides/external-ram.rst +++ b/docs/en/api-guides/external-ram.rst @@ -249,6 +249,30 @@ By default, failure to initialize external RAM will cause the ESP-IDF startup to On {IDF_TARGET_NAME}, PSRAM encryption can be controlled on a per-MMU-page basis, allowing individual PSRAM pages to be selectively encrypted or left unencrypted. However, in the default configuration, all PSRAM pages are encrypted when flash encryption is enabled. + Reserving an Unencrypted PSRAM Region + ------------------------------------- + + Enabling :ref:`CONFIG_SPIRAM_ENC_EXEMPT` reserves a region at the upper end of PSRAM (highest physical addresses; sized by :ref:`CONFIG_SPIRAM_ENC_EXEMPT_SIZE`, in KB, rounded up to the MMU page size) that is mapped without encryption. This region is registered as a separate heap pool reachable only via the ``MALLOC_CAP_SPIRAM_NO_ENC`` capability. The rest of PSRAM (and flash) remains encrypted. + + .. warning:: + + Memory allocated with ``MALLOC_CAP_SPIRAM_NO_ENC`` is stored as plaintext in PSRAM and can be observed by an attacker with physical access to the PSRAM interface. Never place TLS state, keys, or other secrets in this region. + + Typical use case: PSRAM encryption imposes alignment constraints on buffers that some DMA engines (for example, 2D-DMA) cannot satisfy. Buffers that need to be DMA-accessed from such engines can be allocated from this unencrypted region: + + .. code-block:: c + + #if CONFIG_SPIRAM_ENC_EXEMPT + uint32_t caps = MALLOC_CAP_SPIRAM_NO_ENC; + #else + uint32_t caps = MALLOC_CAP_SPIRAM; + #endif + uint8_t *buf = heap_caps_malloc(buf_size, caps); + + ``MALLOC_CAP_SPIRAM_NO_ENC`` must be requested explicitly. It is intentionally not combined with ``MALLOC_CAP_SPIRAM`` or ``MALLOC_CAP_DEFAULT``, so ordinary SPIRAM/heap allocations cannot accidentally land in the unencrypted region. + + To verify after the fact that a buffer was allocated from the unencrypted carve-out (for example after a ``heap_caps_malloc_prefer()`` call that may have fallen back to encrypted PSRAM), use ``esp_psram_ptr_is_no_enc()``. + .. only:: SOC_PSRAM_ENCRYPTION_SEPARATE_KEY On {IDF_TARGET_NAME}, PSRAM encryption can use an independent encryption key. If the PSRAM encryption key is not programmed, the flash encryption key will be used as the PSRAM encryption key. diff --git a/docs/en/api-guides/jtag-debugging/index.rst b/docs/en/api-guides/jtag-debugging/index.rst index f16944ceef6..2f7b04c83ab 100644 --- a/docs/en/api-guides/jtag-debugging/index.rst +++ b/docs/en/api-guides/jtag-debugging/index.rst @@ -29,6 +29,8 @@ The document is structured as follows: If you are not familiar with GDB, check this section for debugging examples provided from :ref:`jtag-debugging-examples-eclipse` as well as from :ref:`jtag-debugging-examples-command-line`. :ref:`jtag-debugging-building-openocd` Reference for OpenOCD build workflow when building from sources. +:ref:`jtag-debugging-semihosting` + Introduction to semihosting feature. :ref:`jtag-debugging-tips-and-quirks` This section provides collection of tips and quirks related to JTAG debugging of {IDF_TARGET_NAME} with OpenOCD and GDB. @@ -315,6 +317,18 @@ The examples in this document use the pre-built OpenOCD binary distribution desc If you need to build OpenOCD from sources for custom requirements, please refer to the `OpenOCD build workflow `_, which demonstrates how OpenOCD is built for different platforms (Windows, Linux, macOS). +.. _jtag-debugging-semihosting: + +Semihosting +----------- + +Semihosting is a mechanism that allows code running on the target {IDF_TARGET_NAME} to communicate with the host PC through the debugger connection, e.g., for printing debug messages or reading/writing files. + +.. toctree:: + :maxdepth: 1 + + semihosting + .. _jtag-debugging-tips-and-quirks: Tips and Quirks @@ -327,7 +341,6 @@ This section provides collection of links to all tips and quirks referred to fro tips-and-quirks - Related Documents ----------------- @@ -338,11 +351,13 @@ Related Documents using-debugger debugging-examples + semihosting tips-and-quirks ../app_trace - :doc:`using-debugger` - :doc:`debugging-examples` +- :doc:`semihosting` - :doc:`tips-and-quirks` - :doc:`../app_trace` - `Introduction to ESP-Prog Board `__ diff --git a/docs/en/api-guides/jtag-debugging/semihosting.rst b/docs/en/api-guides/jtag-debugging/semihosting.rst new file mode 100644 index 00000000000..774c9868aa7 --- /dev/null +++ b/docs/en/api-guides/jtag-debugging/semihosting.rst @@ -0,0 +1,71 @@ +Semihosting Feature +------------------- + +Semihosting is a mechanism that lets a program running on the target use I/O facilities on the host machine where the debugger runs. It is useful for debugging and testing embedded applications without having to implement hardware-specific I/O on the target side. + +OpenOCD implements an extended semihosting protocol for Espressif targets that goes beyond the standard ARM Semihosting specification. This allows embedded applications to interact with the host system for file operations, directory management, and other system calls. + +.. warning:: + + Each semihosting call is implemented as a sequence containing a software breakpoint instruction. If a build that contains semihosting calls runs **without** a debugger attached, an exception will be triggered instead. + +.. note:: + + Each semihosting call halts the CPU until the host returns a result, so semihosting is not suitable for latency-sensitive or real-time code paths. + + +.. _jtag-debugging-semihosting-available-operations: + +Available Operations +^^^^^^^^^^^^^^^^^^^^ + +Header :idf_file:`components/vfs/openocd_semihosting.h` declares the full set of available semihosting operations. Common ones include: + +* **File Operations**: ``open``, ``close``, ``read``, ``write``, ``lseek``, ``fsync``, ``link``, ``unlink`` +* **Directory Operations**: ``opendir``, ``readdir``, ``seekdir``, ``telldir``, ``closedir``, ``mkdir``, ``rmdir`` +* **File Attribute Operations**: ``rename``, ``truncate``, ``fstat``, ``stat``, ``utime``, ``access`` + +In addition, the target can use debugging hooks to trigger events that OpenOCD processes directly: + +* ``panic_reason``: notify user about detailed panic information directly in the debugger console. + +.. only:: CONFIG_IDF_TARGET_ARCH_RISCV + + * ``breakpoint_set``, ``watchpoint_set``: allow configuring breakpoints and watchpoints from the target side, without user interaction. + + +.. _jtag-debugging-semihosting-using-from-app: + +Using Semihosting From an Application +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The most convenient way to use semihosting from application code is through the Virtual File System (VFS) driver. Calling :cpp:func:`esp_vfs_semihost_register` mounts a host directory as a regular VFS path, so that ``fopen``, ``read``, ``write``, and similar standard calls work transparently: + +.. code-block:: c + + #include "esp_vfs_semihost.h" + + esp_vfs_semihost_register("/host"); + FILE *f = fopen("/host/log.txt", "w"); + +See the :doc:`Virtual File System Component API Reference <../../api-reference/storage/vfs>` and the :example:`storage/semihost_vfs` example for the full flow. + +See also `OpenOCD semihosting test application `_. + +.. _jtag-debugging-semihosting-configuration: + +Configuration +^^^^^^^^^^^^^ + +By default, semihosting file operations use the current directory (where OpenOCD is started) as the base directory. To specify a different base directory, add an extra argument ``-c 'set ESP_SEMIHOST_BASEDIR /path/to/semihost/root'`` to the start of the OpenOCD command line. See :ref:`jtag-debugging-tip-openocd-config-vars`. + +.. _jtag-debugging-semihosting-gdb-semihosting: + +GDB Semihosting +^^^^^^^^^^^^^^^ + +GDB also provides built-in support for semihosting, which complements OpenOCD's implementation. This is especially useful when GDB is connected to OpenOCD remotely and runs on a different machine — semihosting file operations then resolve against the GDB host rather than the OpenOCD host. + +To redirect semihosting requests to GDB, enter ``mon arm semihosting_fileio enable`` in GDB. For multi-core targets, this only enables semihosting for the current core; use ``mon arm semihosting_fileio enable`` per core if needed (targets can be listed with ``mon targets``). + +With file I/O enabled, OpenOCD does not process the system operation itself after intercepting a semihosting call. Instead, it sends a file I/O request packet to GDB and keeps the target halted until GDB responds with the result. This process is fully transparent to the code running on the target. diff --git a/docs/en/api-guides/performance/size.rst b/docs/en/api-guides/performance/size.rst index 3ed85bed7b0..88156f6cbda 100644 --- a/docs/en/api-guides/performance/size.rst +++ b/docs/en/api-guides/performance/size.rst @@ -242,6 +242,7 @@ The help text for each option has some more information for reference. .. only:: CONFIG_ESP_ROM_HAS_MBEDTLS_CRYPTO_LIB Enabling the config option :ref:`CONFIG_MBEDTLS_USE_CRYPTO_ROM_IMPL` will use the crypto algorithms from mbedTLS library inside the chip ROM. + This option is available only when the selected target and minimum chip revision support the ROM mbedTLS crypto library. Disabling the config option :ref:`CONFIG_MBEDTLS_USE_CRYPTO_ROM_IMPL` will use the crypto algorithms from the ESP-IDF mbedtls component library. This will increase the binary size (flash footprint). diff --git a/docs/en/api-guides/tools/idf-py.rst b/docs/en/api-guides/tools/idf-py.rst index deb28b4791a..ac7e6fac250 100644 --- a/docs/en/api-guides/tools/idf-py.rst +++ b/docs/en/api-guides/tools/idf-py.rst @@ -283,7 +283,7 @@ To use the MCP server with an AI assistant, configure your agent or IDE to start eim run "idf.py mcp-server" -2. Using ``idf.py`` directly: Run the MCP server with ``idf.py mcp-server`` from a shell where the ESP-IDF environment is already activated. The command must be executed from a valid ESP-IDF project directory, or use ``idf.py -C mcp-server`` to specify the project. +2. Using ``idf.py`` directly: Run the MCP server with ``idf.py mcp-server`` from a shell where the ESP-IDF environment is already activated. The server can be started from any directory. Use ``idf.py -C mcp-server`` or set the ``IDF_MCP_WORKSPACE_FOLDER`` environment variable to configure a default project. If no project is configured at startup, pass project directory explicitly in each tool call. .. code-block:: bash @@ -296,12 +296,15 @@ To use the MCP server with an AI assistant, configure your agent or IDE to start Available Tools and Resources ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -The MCP server provides the following commands you can use: +The MCP server provides the following tools: - ``set target``: Set the ESP-IDF target (esp32, esp32s3, esp32c6, etc.) - ``build project``: Build the ESP-IDF project with the current target -- ``flash project``: Flash the built project to a connected device. Specify it by port name. +- ``flash project``: Flash the built project to a connected device. Specify it by port name - ``clean project``: Clean build artifacts +- ``create project``: Create a new ESP-IDF project from the sample template. Can be used before any project exists + +All tools accept an optional ``project_dir`` argument. When omitted, the tool operates on the directory configured at startup (``-C`` flag or ``IDF_MCP_WORKSPACE_FOLDER``). You can instruct the AI model to use a specific project directory explicitly, for example when working with multiple projects or when no default project was configured at startup. The MCP server also provides these resources: diff --git a/docs/en/api-reference/peripherals/async_color_convert.rst b/docs/en/api-reference/peripherals/async_color_convert.rst new file mode 100644 index 00000000000..7980144afa6 --- /dev/null +++ b/docs/en/api-reference/peripherals/async_color_convert.rst @@ -0,0 +1,317 @@ +============================= +Asynchronous Color Conversion +============================= + +:link_to_translation:`zh_CN:[中文]` + +This document introduces the Async Color Convert driver in ESP-IDF. The table of contents is as follows: + +.. contents:: + :local: + :depth: 2 + +Overview +======== + +{IDF_TARGET_NAME} provides a DMA2D engine that can offload 2D copy and color conversion work from the CPU. + +This driver is useful when your application needs to: + +- convert an image from one pixel format to another +- copy only a window of a larger image +- queue multiple conversions without doing the work on the CPU +- move between RGB and UYVY formats while selecting the RGB/YUV conversion standard + +The Async Color Convert driver wraps DMA2D request preparation, queueing, and completion handling into a small API that supports both: + +- asynchronous submission with an ISR callback +- a simpler blocking API built on top of the same request path + +Quick Start +=========== + +If you are new to this driver, start with the simplest workflow: + +1. Install the driver +2. Prepare one :cpp:type:`async_color_convert_request_t` +3. Submit the conversion through either the blocking or non-blocking API +4. Consume the converted output buffer after the conversion completes +5. Either submit another request or uninstall the driver when finished + +The typical usage flow is: + +.. mermaid:: + + flowchart TD + install["Install driver
esp_async_color_convert_install_dma2d"] --> request["Prepare request
async_color_convert_request_t"] + request --> blocking["Blocking path
esp_color_convert_blocking"] + request --> nonBlocking["Non-blocking path
esp_async_color_convert"] + nonBlocking --> callback["Wait for callback or task notification"] + blocking --> result["Use converted buffer"] + callback --> result + result --> request + result --> uninstall["Optional cleanup
esp_async_color_convert_uninstall"] + +Scenario 1: Start with One Blocking Conversion +============================================== + +The easiest way to learn the API is to convert one image and wait until the conversion is complete. + +The following flow mirrors the :example:`peripherals/dma/async_color_convert` example. It converts one embedded UYVY422 image into BGR24 and then lets the application consume the converted output: + +.. code:: c + + async_color_convert_handle_t conv_hdl = NULL; // Driver handle returned by the install API + async_color_convert_config_t config = { + .backlog = 1, // One in-flight request is enough for this simple blocking example + .dma_burst_size = 16, // Start with the default burst size used by the example + }; + // Create one Async Color Convert driver instance backed by DMA2D. + ESP_ERROR_CHECK(esp_async_color_convert_install_dma2d(&config, &conv_hdl)); + + async_color_convert_request_t req = { + .src_buffer = sample_96x64_uyvy_yuv_start, // Source image can be in flash or RAM, as long as DMA can access it + .src_stride = 96, // Source image row stride, in pixels + .src_height = 64, // Source image height, in pixels + .src_x = 0, // Start from the left edge of the source image + .src_y = 0, + .dst_buffer = dst_bgr, // Destination buffer in DMA-capable RAM + .dst_stride = 96, // Destination image row stride, in pixels + .dst_height = 64, // Destination image height, in pixels + .dst_x = 0, // Write the converted output from the top-left corner + .dst_y = 0, + .copy_width = 96, // Convert the full image width, in pixels + .copy_height = 64, // Convert the full image height, in pixels + .src_color_format = ESP_COLOR_FOURCC_UYVY, // Source pixels are UYVY422 + .dst_color_format = ESP_COLOR_FOURCC_BGR24, // Destination pixels are BGR24 (used as RGB888 in this driver) + .color_conv_std = COLOR_CONV_STD_RGB_YUV_BT601, // RGB/YUV standard used for this conversion pair + }; + + // Wait until DMA2D finishes the conversion. -1 means wait forever. + ESP_ERROR_CHECK(esp_color_convert_blocking(conv_hdl, &req, -1)); + + // Release the driver after all conversions are done. + ESP_ERROR_CHECK(esp_async_color_convert_uninstall(conv_hdl)); + +This flow introduces the most important ideas: + +- :cpp:func:`esp_async_color_convert_install_dma2d` creates the driver instance +- :cpp:type:`async_color_convert_request_t` describes the source image, destination image, and conversion window +- :cpp:func:`esp_color_convert_blocking` waits until the hardware finishes the conversion +- :cpp:func:`esp_async_color_convert_uninstall` releases the driver resources + +For the blocking API, ``timeout_ms = -1`` means wait forever. Other timeout values are currently unsupported and return ``ESP_ERR_INVALID_ARG``. + +Understanding ``async_color_convert_request_t`` +----------------------------------------------- + +Most application issues come from building the request incorrectly, so it is worth understanding the structure carefully. + +.. important:: + + In :cpp:type:`async_color_convert_request_t`, all geometry fields are measured in **pixels**, not bytes. This includes ``src_stride``, ``src_height``, ``src_x``, ``src_y``, ``dst_stride``, ``dst_height``, ``dst_x``, ``dst_y``, ``copy_width``, and ``copy_height``. + + ``src_stride`` and ``dst_stride`` are row strides, not conversion widths. They describe how many pixels each full image row spans in memory, so they can be larger than ``copy_width`` when converting a window inside a larger image. + +The structure describes two things at the same time: + +- the full source and destination images in memory +- the rectangular window that should be converted + +The key fields are: + +- :cpp:member:`async_color_convert_request_t::src_buffer` + Base address of the source image +- :cpp:member:`async_color_convert_request_t::src_stride` + Source image row stride in pixels +- :cpp:member:`async_color_convert_request_t::src_height` + Source image height in pixels +- :cpp:member:`async_color_convert_request_t::src_x` and :cpp:member:`async_color_convert_request_t::src_y` + Top-left corner of the source window +- :cpp:member:`async_color_convert_request_t::dst_buffer` + Base address of the destination image +- :cpp:member:`async_color_convert_request_t::dst_stride` + Destination image row stride in pixels +- :cpp:member:`async_color_convert_request_t::dst_height` + Destination image height in pixels +- :cpp:member:`async_color_convert_request_t::dst_x` and :cpp:member:`async_color_convert_request_t::dst_y` + Top-left corner of where the converted window should be written +- :cpp:member:`async_color_convert_request_t::copy_width` and :cpp:member:`async_color_convert_request_t::copy_height` + Size of the rectangle to convert +- :cpp:member:`async_color_convert_request_t::src_color_format` and :cpp:member:`async_color_convert_request_t::dst_color_format` + Source and destination pixel formats +- :cpp:member:`async_color_convert_request_t::color_conv_std` + RGB/YUV conversion standard, used for RGB <-> YUV conversions + +Both the source window and destination window must stay within the bounds of their corresponding images. + +Supported Conversions +--------------------- + +The following format pairs are currently supported by this driver: + +.. list-table:: + :header-rows: 1 + + * - Source format + - Destination format + - Conversion standard + * - ``ESP_COLOR_FOURCC_RGB16`` + - ``ESP_COLOR_FOURCC_RGB16`` + - N/A + * - ``ESP_COLOR_FOURCC_BGR24`` + - ``ESP_COLOR_FOURCC_BGR24`` + - N/A + * - ``ESP_COLOR_FOURCC_RGB24`` + - ``ESP_COLOR_FOURCC_RGB24`` + - N/A + * - ``ESP_COLOR_FOURCC_UYVY`` + - ``ESP_COLOR_FOURCC_UYVY`` + - N/A + * - ``ESP_COLOR_FOURCC_BGR24`` + - ``ESP_COLOR_FOURCC_RGB24`` + - N/A + * - ``ESP_COLOR_FOURCC_RGB24`` + - ``ESP_COLOR_FOURCC_BGR24`` + - N/A + * - ``ESP_COLOR_FOURCC_RGB16`` + - ``ESP_COLOR_FOURCC_BGR24`` + - N/A + * - ``ESP_COLOR_FOURCC_BGR24`` + - ``ESP_COLOR_FOURCC_RGB16`` + - N/A + * - ``ESP_COLOR_FOURCC_RGB24`` + - ``ESP_COLOR_FOURCC_RGB16`` + - N/A + * - ``ESP_COLOR_FOURCC_BGR24`` + - ``ESP_COLOR_FOURCC_UYVY`` + - BT.601 + * - ``ESP_COLOR_FOURCC_BGR24`` + - ``ESP_COLOR_FOURCC_UYVY`` + - BT.709 + * - ``ESP_COLOR_FOURCC_RGB24`` + - ``ESP_COLOR_FOURCC_UYVY`` + - BT.601 + * - ``ESP_COLOR_FOURCC_RGB24`` + - ``ESP_COLOR_FOURCC_UYVY`` + - BT.709 + * - ``ESP_COLOR_FOURCC_UYVY`` + - ``ESP_COLOR_FOURCC_BGR24`` + - BT.601 + * - ``ESP_COLOR_FOURCC_UYVY`` + - ``ESP_COLOR_FOURCC_BGR24`` + - BT.709 + +.. note:: + + Always set :cpp:member:`async_color_convert_request_t::src_color_format` and + :cpp:member:`async_color_convert_request_t::dst_color_format`. + Set :cpp:member:`async_color_convert_request_t::color_conv_std` when converting between RGB and YUV. + +Scenario 2: Use the Asynchronous API with a Callback +==================================================== + +Once the blocking flow is clear, the next step is to queue a request and let the driver notify you from interrupt context when it is finished. + +.. code:: c + + static bool color_conv_done_cb(async_color_convert_handle_t conv_hdl, + async_color_convert_event_data_t *edata, + void *cb_args) + { + BaseType_t high_task_wakeup = pdFALSE; // Required by FreeRTOS when an ISR wakes a task + SemaphoreHandle_t sem = (SemaphoreHandle_t)cb_args; // User context passed at submit time + // Notify a waiting task that the conversion has finished. + xSemaphoreGiveFromISR(sem, &high_task_wakeup); + // Return true when the unblocked task should run immediately after the ISR. + return high_task_wakeup == pdTRUE; + } + + async_color_convert_request_t req = { + .src_buffer = src_buf, // Source image base address + .src_stride = src_width, // Source image row stride, in pixels + .src_height = src_height, + .src_x = 0, + .src_y = 0, + .dst_buffer = dst_buf, // Destination image base address + .dst_stride = dst_width, // Destination image row stride, in pixels + .dst_height = dst_height, + .dst_x = 0, + .dst_y = 0, + .copy_width = copy_width, + .copy_height = copy_height, + .src_color_format = ESP_COLOR_FOURCC_RGB16, + .dst_color_format = ESP_COLOR_FOURCC_BGR24, + }; + + // Queue one asynchronous request. The callback runs later in ISR context. + ESP_ERROR_CHECK(esp_async_color_convert(conv_hdl, &req, color_conv_done_cb, sem)); + // Wait in task context until the callback gives the semaphore. + xSemaphoreTake(sem, portMAX_DELAY); + +The callback runs in ISR context, so keep it short and only use ISR-safe APIs such as ``xSemaphoreGiveFromISR`` or ``xQueueSendFromISR``. + +Operational Notes +================= + +Driver Configuration +-------------------- + +The driver configuration fields are: + +- :cpp:member:`async_color_convert_config_t::backlog` + Maximum number of in-flight or pending requests. ``0`` uses a driver default. +- :cpp:member:`async_color_convert_config_t::dma_burst_size` + DMA burst size in bytes. ``0`` uses a driver default. +- :cpp:member:`async_color_convert_config_t::intr_priority` + DMA2D interrupt priority. ``0`` uses the default low/medium priority. + +DMA Burst Size +-------------- + +The ``dma_burst_size`` affects DMA transfer efficiency: + +- Larger burst sizes may improve throughput +- Larger burst sizes can also increase bus occupancy, so they are not always best for every workload +- Common starting values are 16, 32, and 64 bytes + +The best value depends on the chip's DMA controller capabilities and how much memory bandwidth is shared with other active components in the system. + +Thread Safety and ISR Rules +--------------------------- + +- The driver is thread-safe. Requests from different tasks are serialized through the internal queue. +- :cpp:func:`esp_async_color_convert` can be called from tasks to enqueue requests. +- The callback type :cpp:type:`async_color_convert_isr_cb_t` runs in ISR context. +- Do not call blocking APIs from the callback. +- :cpp:func:`esp_color_convert_blocking` must not be called from ISR context. + +Uninstalling the Driver +----------------------- + +When the driver is no longer needed: + +.. code:: c + + // Uninstall only after all queued conversions have completed. + ESP_ERROR_CHECK(esp_async_color_convert_uninstall(conv_hdl)); + +If requests are still pending, :cpp:func:`esp_async_color_convert_uninstall` returns :c:macro:`ESP_ERR_INVALID_STATE`. + +Application Example +=================== + +- :example:`peripherals/dma/async_color_convert` shows a beginner-friendly blocking conversion flow: + + - an embedded ``.yuv`` image is read directly from mapped flash + - DMA2D converts the image from UYVY422 to BGR24 + - the converted output is base64-encoded and printed to the console + - pytest reconstructs the image as a PNG artifact and compares it against a golden reference image + +API Reference +============= + +Async Color Convert Driver Functions +------------------------------------ + +.. include-build-file:: inc/esp_async_color_convert.inc diff --git a/docs/en/api-reference/peripherals/i2s.rst b/docs/en/api-reference/peripherals/i2s.rst index b3fe2e8660d..80d73064c2e 100644 --- a/docs/en/api-reference/peripherals/i2s.rst +++ b/docs/en/api-reference/peripherals/i2s.rst @@ -75,6 +75,12 @@ Clock Terminology Normally, MCLK should be the multiple of ``sample rate`` and BCLK at the same time. The field :cpp:member:`i2s_std_clk_config_t::mclk_multiple` indicates the multiple of MCLK to the ``sample rate``. In most cases, ``I2S_MCLK_MULTIPLE_256`` should be enough. However, if ``slot_bit_width`` is set to ``I2S_SLOT_BIT_WIDTH_24BIT``, to keep MCLK a multiple to the BCLK, :cpp:member:`i2s_std_clk_config_t::mclk_multiple` should be set to multiples that are divisible by 3 such as ``I2S_MCLK_MULTIPLE_384``. Otherwise, WS will be inaccurate. +.. only:: esp32 + + .. note:: + + On ESP32, the MCLK pin must use GPIO0, GPIO1, or GPIO3. The other clock pins (e.g., BCLK, WS) can use any valid GPIO. Note that GPIO0 is generally not recommended for other functions because it is a strapping pin. + .. _i2s-communication-mode: I2S Communication Mode @@ -256,7 +262,7 @@ Power Management When the power management is enabled (i.e., :ref:`CONFIG_PM_ENABLE` is on), the system will adjust or stop the source clock of I2S before entering Light-sleep, thus potentially changing the I2S signals and leading to transmitting or receiving invalid data. -The I2S driver can prevent the system from changing or stopping the source clock by acquiring a power management lock. When the source clock is generated from APB, the lock type will be set to :cpp:enumerator:`esp_pm_lock_type_t::ESP_PM_APB_FREQ_MAX` and when the source clock is APLL (if supported), it will be set to :cpp:enumerator:`esp_pm_lock_type_t::ESP_PM_NO_LIGHT_SLEEP`. Whenever the user is reading or writing via I2S (i.e., calling :cpp:func:`i2s_channel_read` or :cpp:func:`i2s_channel_write`), the driver guarantees that the power management lock is acquired. Likewise, the driver releases the lock after the reading or writing finishes. +The I2S driver can prevent the system from changing or stopping the source clock by acquiring a power management lock. When the source clock is generated from APB, the lock type will be set to :cpp:enumerator:`esp_pm_lock_type_t::ESP_PM_APB_FREQ_MAX` and when the source clock is APLL (if supported), it will be set to :cpp:enumerator:`esp_pm_lock_type_t::ESP_PM_NO_LIGHT_SLEEP`. The driver guarantees that the power management lock is acquired when the channel is enabled by :cpp:func:`i2s_channel_enable`. Likewise, the driver releases the lock when the channel is disabled by :cpp:func:`i2s_channel_disable`, which keeps the I2S source clock stable while the channel is running. .. only:: SOC_I2S_SUPPORT_SLEEP_RETENTION diff --git a/docs/en/api-reference/peripherals/index.rst b/docs/en/api-reference/peripherals/index.rst index 332735d0ba7..99a6baa9803 100644 --- a/docs/en/api-reference/peripherals/index.rst +++ b/docs/en/api-reference/peripherals/index.rst @@ -8,6 +8,7 @@ Peripherals API :SOC_ADC_SUPPORTED: adc/index :SOC_ANA_CMPR_SUPPORTED: ana_cmpr + :SOC_DMA2D_SUPPORTED: async_color_convert :SOC_BITSCRAMBLER_SUPPORTED: bitscrambler :SOC_MIPI_CSI_SUPPORTED: camera_driver :SOC_CLK_TREE_SUPPORTED: clk_tree diff --git a/docs/en/api-reference/peripherals/jpeg.rst b/docs/en/api-reference/peripherals/jpeg.rst index bc6660d7c36..c5f2ffc65e6 100644 --- a/docs/en/api-reference/peripherals/jpeg.rst +++ b/docs/en/api-reference/peripherals/jpeg.rst @@ -178,40 +178,44 @@ The format conversions supported by this driver are listed in the table below: - GRAY -Below is the example of code that encodes a 1080*1920 picture: +Below is the example of code that encodes a 1280x720 picture from an embedded raw buffer: .. code:: c - int raw_size_1080p = 0;/* Your raw image size */ + size_t raw_size_720p = EXAMPLE_WIDTH * EXAMPLE_HEIGHT * 3; /* 1280x720 bgr24 frame */ jpeg_encode_cfg_t enc_config = { .src_type = JPEG_ENCODE_IN_FORMAT_RGB888, .sub_sample = JPEG_DOWN_SAMPLING_YUV422, .image_quality = 80, - .width = 1920, - .height = 1080, + .width = 1280, + .height = 720, .pixel_reverse = false, // Whether to reverse the pixel order of the input image, or pixel order detail please refer to technical reference manual }; - uint8_t *raw_buf_1080p = (uint8_t*)jpeg_alloc_encoder_mem(raw_size_1080p); - if (raw_buf_1080p == NULL) { - ESP_LOGE(TAG, "alloc 1080p tx buffer error"); - return; - } - uint8_t *jpg_buf_1080p = (uint8_t*)jpeg_alloc_encoder_mem(raw_size_1080p / 10); // Assume that compression ratio of 10 to 1 - if (jpg_buf_1080p == NULL) { - ESP_LOGE(TAG, "alloc jpg_buf_1080p error"); + jpeg_encode_memory_alloc_cfg_t rx_mem_cfg = { + .buffer_direction = JPEG_ENC_ALLOC_OUTPUT_BUFFER, + }; + size_t jpg_buffer_size = 0; + uint8_t *jpg_buf_720p = (uint8_t*)jpeg_alloc_encoder_mem(raw_size_720p / 10, &rx_mem_cfg, &jpg_buffer_size); + if (jpg_buf_720p == NULL) { + ESP_LOGE(TAG, "alloc jpg_buf_720p error"); return; } - ESP_ERROR_CHECK(jpeg_encoder_process(jpeg_handle, &enc_config, raw_buf_1080p, raw_size_1080p, jpg_buf_1080p, &jpg_size_1080p);); + /* The current JPEG encoder input path expects BGR24-style raw bytes for + * JPEG_ENCODE_IN_FORMAT_RGB888. The embedded asset can be read directly + * from flash as long as it remains valid until this call returns. */ + ESP_ERROR_CHECK(jpeg_encoder_process(jpeg_handle, &enc_config, embedded_bgr24_start, raw_size_720p, jpg_buf_720p, jpg_buffer_size, &jpg_size_720p)); There are some tips that can help you use this driver more accurately: -1. In above code, you should make sure the `raw_buf_1080p` and `jpg_buf_1080p` should aligned by calling :cpp:func:`jpeg_alloc_encoder_mem`. +1. In the above code, the output buffer `jpg_buf_720p` should be allocated by calling :cpp:func:`jpeg_alloc_encoder_mem`, because the JPEG bitstream buffer must satisfy the driver's alignment requirements. -2. The content of `raw_buf_1080p` buffer should not be changed until :cpp:func:`jpeg_encoder_process` returns. +2. The content pointed to by `embedded_bgr24_start` should not be changed until :cpp:func:`jpeg_encoder_process` returns. This input buffer can come from flash-mapped embedded data or another memory region that stays readable for the full call. -3. The compression ratio depends on the chosen `image_quality` and the content of the image itself. Generally, a higher `image_quality` value obviously results in better image quality but a smaller compression ratio. As for the image content, it is hard to give any specific guidelines, so this question is out of the scope of this document. Generally, the baseline JPEG compression ratio can vary from 40:1 to 10:1. Please take the actual situation into account. +3. For :cpp:enumerator:`JPEG_ENCODE_IN_FORMAT_RGB888`, the current driver expects the raw input bytes in a BGR24-style layout. Supplying RGB24 raw data would swap the red and blue channels in the encoded JPEG. + +4. The compression ratio depends on the chosen `image_quality` and the content of the image itself. Generally, a higher `image_quality` value obviously results in better image quality but a smaller compression ratio. As for the image content, it is hard to give any specific guidelines, so this question is out of the scope of this document. Generally, the baseline JPEG compression ratio can vary from 40:1 to 10:1. Please take the actual situation into account. Performance Overview ^^^^^^^^^^^^^^^^^^^^ @@ -453,7 +457,7 @@ Application Examples - :example:`peripherals/jpeg/jpeg_decode` demonstrates how to use the JPEG hardware decoder to decode JPEG pictures of different sizes (1080p and 720p) into RGB format, showcasing the flexibility and speed of hardware decoding. -- :example:`peripherals/jpeg/jpeg_encode` demonstrates how to use the JPEG hardware encoder to encode a 1080p picture, specifically converting `*.rgb` files to `*.jpg` files. +- :example:`peripherals/jpeg/jpeg_encode` demonstrates how to use the JPEG hardware encoder to encode an embedded 720p raw picture, stream the JPEG as base64 over UART, and validate the result with pytest. API Reference diff --git a/docs/en/api-reference/peripherals/lcd/i2c_lcd.rst b/docs/en/api-reference/peripherals/lcd/i2c_lcd.rst index 8e71d8e897f..4da3738f063 100644 --- a/docs/en/api-reference/peripherals/lcd/i2c_lcd.rst +++ b/docs/en/api-reference/peripherals/lcd/i2c_lcd.rst @@ -23,6 +23,7 @@ I2C Interfaced LCD - :cpp:member:`esp_lcd_panel_io_i2c_config_t::dev_addr` sets the I2C device address of the LCD controller chip. The LCD driver uses this address to communicate with the LCD controller chip. - :cpp:member:`esp_lcd_panel_io_i2c_config_t::scl_speed_hz` sets the I2C clock frequency in Hz. The value should not exceed the range recommended in the LCD spec. - :cpp:member:`esp_lcd_panel_io_i2c_config_t::lcd_cmd_bits` and :cpp:member:`esp_lcd_panel_io_i2c_config_t::lcd_param_bits` set the bit width of the command and parameter recognized by the LCD controller chip. This is chip specific, you should refer to your LCD spec in advance. + - :cpp:member:`esp_lcd_panel_io_i2c_config_t::transaction_timeout_ms` sets the timeout (in milliseconds) for each underlying I2C transfer. Setting this to 0 or -1 means to wait indefinitely. If a positive value is specified, panel IO calls will return ``ESP_ERR_TIMEOUT`` when the timeout is reached. This is useful for cases like shared buses or when a slave device could potentially hang the bus. .. code-block:: c diff --git a/docs/en/api-reference/peripherals/sdio_slave.rst b/docs/en/api-reference/peripherals/sdio_slave.rst index 5bfb851ce8e..9af41af428f 100644 --- a/docs/en/api-reference/peripherals/sdio_slave.rst +++ b/docs/en/api-reference/peripherals/sdio_slave.rst @@ -223,7 +223,7 @@ Each time the slave has data to send, it raises an interrupt, and the host reque To avoid overhead from copying data, the driver itself does not have any buffer inside. Namely, the DMA takes data directly from the buffer provided by the application. The application should not touch the buffer until the sending is finished, so as to ensure that the data is transferred correctly. -The sending mode can be set in the ``sending_mode`` member of ``sdio_slave_config_t``, and the buffer numbers can be set in the ``send_queue_size``. All the buffers are restricted to be no larger than 4092 bytes. Though in the stream mode, several buffers can be sent in one transfer, each buffer is still counted as one in the queue. +The sending mode can be set in the ``sending_mode`` member of ``sdio_slave_config_t``, and the buffer numbers can be set in the ``send_queue_size``. Each buffer is restricted by the maximum size supported by a single SDIO slave DMA descriptor, which is chip-dependent. Though in the stream mode, several buffers can be sent in one transfer, each buffer is still counted as one in the queue. The application can call ``sdio_slave_transmit`` to send packets. In this case, the function returns when the transfer is successfully done, so the queue is not fully used. When higher efficiency is required, the application can use the following functions instead: diff --git a/docs/en/api-reference/peripherals/twai.rst b/docs/en/api-reference/peripherals/twai.rst index 7bae2f93fa6..95e5329ea99 100644 --- a/docs/en/api-reference/peripherals/twai.rst +++ b/docs/en/api-reference/peripherals/twai.rst @@ -134,6 +134,7 @@ The :cpp:type:`twai_frame_t` message structure also includes other configuration - :cpp:member:`twai_frame_t::header::fdf`: Marks the frame as an FD format frame, supporting up to 64 bytes of data. - :cpp:member:`twai_frame_t::header::brs`: Enables use of a separate data-phase baud rate when transmitting. - :cpp:member:`twai_frame_t::header::esi`: For received frames, indicates the error state of the transmitting node. +- :cpp:member:`twai_frame_t::tx_queue_priority`: Local transmit queue priority. See `Transmit Queue Priority`_ for details. Receiving Messages ------------------ @@ -214,6 +215,13 @@ The TWAI driver supports transmitting messages from an Interrupt Service Routine .. note:: When calling :cpp:func:`twai_node_transmit` from an ISR, the ``timeout`` parameter is ignored, and the function will not block. If the transmit queue is full, the function will return immediately with an error. It is the application's responsibility to handle cases where the queue is full. Similarly, the ``twai_frame_t`` structure and the memory pointed to by ``buffer`` must remain valid until the transmission is complete. You can get the completed frame by the :cpp:member:`twai_tx_done_event_data_t::done_tx_frame` pointer. +Transmit Queue Priority +----------------------- + +The TWAI driver supports local transmit queue prioritization through :cpp:member:`twai_frame_t::tx_queue_priority`. When multiple frames are pending in the driver's transmit queue, frames with a higher ``tx_queue_priority`` value are dequeued and started transmitting first. Frames with the same priority keep their enqueue order. + +This priority only affects the driver's local transmit queue. It is not transmitted on the TWAI bus and does not replace TWAI bus arbitration. If the controller has multiple hardware transmit buffers (for example, 4 hardware transmit buffers for esp32c5), the already cached frames will not be preempted by newly queued higher-priority frames. Once a frame reaches the bus, arbitration is still determined by the frame ID, where lower IDs have higher bus priority. + Bit Timing Customization ------------------------ diff --git a/docs/en/api-reference/peripherals/uhci.rst b/docs/en/api-reference/peripherals/uhci.rst index 36707243850..9d5a40555dc 100644 --- a/docs/en/api-reference/peripherals/uhci.rst +++ b/docs/en/api-reference/peripherals/uhci.rst @@ -87,14 +87,18 @@ The TX event data is defined in :cpp:type:`uhci_tx_done_event_data_t`: The RX event data is defined in :cpp:type:`uhci_rx_event_data_t`: -- :cpp:member:`uhci_rx_event_data_t::data` points to the received data. The data is saved in the ``buffer`` parameter of the :cpp:func:`uhci_receive` function. Users should not free this receive buffer before the callback returns. +- :cpp:member:`uhci_rx_event_data_t::data` points to the received data. The data is stored in the buffer specified by the ``buffer`` parameter of :cpp:func:`uhci_receive`, so users should not free this receive buffer before the callback returns. Data pointed to by ``edata->data`` is typically only guaranteed to be readable during the callback. If application code needs to use the received data after callback returns, copy it to a external buffer first. - :cpp:member:`uhci_rx_event_data_t::recv_size` indicates the number of received data. This value is not larger than the ``buffer_size`` parameter of :cpp:func:`uhci_receive` function. - :cpp:member:`uhci_rx_event_data_t::flags::totally_received` indicates whether the current received buffer is the last one in the transaction. +.. note:: + + Forwarding ``edata->data`` pointer to another task without copying is an advanced zero-copy usage. To keep it safe, user code must understand the chunking and overwrite behavior of the underlying circular DMA buffer, and guarantee the consumer can process data before it gets overwritten. + Initiating UHCI Transmission ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -:cpp:func:`uhci_transmit` is a non-blocking function, which means this function will immediately return after you call it. The related callback can be obtained via :cpp:member:`uhci_event_callbacks_t::on_tx_trans_done` to indicate that the transaction is done. The function :cpp:func:`uhci_wait_all_tx_transaction_done` can be used to indicate that all transactions are finished. +:cpp:func:`uhci_transmit` is a non-blocking function, which means this function will immediately return after you call it. The related callback can be obtained via :cpp:member:`uhci_event_callbacks_t::on_tx_trans_done` to indicate that the transaction is done. The function :cpp:func:`uhci_wait_all_tx_transaction_done` can be used to block the thread until all transactions are finished. Data can be transmitted via UHCI as follows: @@ -111,9 +115,9 @@ Data can be transmitted via UHCI as follows: Initiating UHCI Reception ^^^^^^^^^^^^^^^^^^^^^^^^^ -:cpp:func:`uhci_receive` is a non-blocking function, which means this function will immediately return after it is called. The related callback can be obtained via :cpp:member:`uhci_rx_event_data_t::recv_size` to indicate the receive event. It can be useful to determine if a transaction has been finished. +:cpp:func:`uhci_receive` is a non-blocking function, which means this function will immediately return after it is called. The related callback can be obtained via :cpp:member:`uhci_event_callbacks_t::on_rx_trans_event` to indicate the receive event. It can be useful to determine if a transaction has been finished. -Data can be transmitted via UHCI as follows: +Data can be received via UHCI as follows: .. code:: c @@ -137,7 +141,7 @@ Data can be transmitted via UHCI as follows: ctx->p_receive_data += edata->recv_size; } - xQueueSendFromISR(ctx->uhci_queue, &evt, &xTaskWoken); + xQueueSendFromISR(uhci_queue, &evt, &xTaskWoken); return xTaskWoken; } @@ -153,7 +157,7 @@ Data can be transmitted via UHCI as follows: uhci_event_t evt; while (1) { // A queue in task for receiving event triggered by UHCI. - if (xQueueReceive(ctx->uhci_queue, &evt, portMAX_DELAY) == pdTRUE) { + if (xQueueReceive(uhci_queue, &evt, portMAX_DELAY) == pdTRUE) { if (evt == UHCI_EVT_EOF) { printf("Received size: %d\n", ctx->receive_size); break; diff --git a/docs/en/api-reference/protocols/esp_http_client.rst b/docs/en/api-reference/protocols/esp_http_client.rst index 209980d5981..05f10753da9 100644 --- a/docs/en/api-reference/protocols/esp_http_client.rst +++ b/docs/en/api-reference/protocols/esp_http_client.rst @@ -17,6 +17,7 @@ Application Examples -------------------- - :example:`protocols/esp_http_client` demonstrates how to use the ESP HTTP Client to make HTTP/S requests. +- :example:`protocols/esp_http_client_mutual_auth` demonstrates how to configure mutual TLS authentication with the ESP HTTP Client. Basic HTTP Request diff --git a/docs/en/api-reference/storage/blockdev.rst b/docs/en/api-reference/storage/blockdev.rst new file mode 100644 index 00000000000..1204acbb1d8 --- /dev/null +++ b/docs/en/api-reference/storage/blockdev.rst @@ -0,0 +1,163 @@ +Block Device Layer +================== + +:link_to_translation:`zh_CN:[中文]` + +Overview +-------- + +The Block Device Layer (BDL) defines a C interface that lets storage-oriented components exchange data without custom-made adapters. Each block device exposes this interface in the form of an :cpp:type:`esp_blockdev_handle_t` handle giving access to device flags, geometry information, and a set of supported operations as defined in ``components/esp_blockdev/include/esp_blockdev.h``. Higher-level code inspects that metadata and may invoke the available callbacks to perform I/O operations. + +The unified interface makes it possible to compose BDL stacks supporting storage use-cases by chaining multiple universal components. A driver provides a handle representing access to the physical device; a middleware component consumes this handle, augments behaviour (for example splits the space on the device or adds wear levelling capability), and exposes a new handle to the next layer. The topmost components of the chain like file-systems are pure device consumers. This model allows filesystems, middleware, and physical drivers to be mixed and matched as long as every layer honours the interface contracts described below. + +.. blockdiag:: + :caption: Example Block Device Layer Stack + :align: center + + blockdiag blockdev-stack { + default_fontsize = 14; + node_height = 60; + orientation = portrait; + default_group_color = none; + + nvs [label = "NVS\n(nvs_flash)"]; + fatfs [label = "FATFS\n(fatfs)"]; + littlefs [label = "LittleFS\n(esp_littlefs)"]; + consumer [label = "Block device\nconsumers", shape = ellipse]; + + wl [label = "Wear Levelling\n(wear_levelling)"]; + middleware_1 [label = "Block device\nmiddleware", shape = ellipse]; + + nvs_part [label = "NVS Partition\n(esp_partition)"]; + littlefs_part [label = "LittleFS Partition\n(esp_partition)"]; + fat_part [label = "FAT Partition\n(esp_partition)"]; + middleware_2 [label = "Block device\nmiddleware", shape = ellipse]; + + spi [label = "Flash\n(spi_flash)"]; + provider [label = "Block device\nprovider", shape = ellipse]; + + d1 [shape = none, width = 1, height = 1]; + d2 [shape = none, width = 1, height = 1]; + d3 [shape = none, width = 1, height = 1]; + + nvs -> nvs_part -> spi; + fatfs -> wl -> fat_part -> spi; + littlefs -> littlefs_part -> spi; + + consumer -> middleware_1 -> middleware_2 -> provider; + + group { orientation = landscape; fatfs; littlefs; nvs; } + group { orientation = landscape; wl; } + group { orientation = landscape; fat_part; littlefs_part; nvs_part; } + group { orientation = landscape; d3; spi; } + } + + +Using Block Devices +------------------- + +Handles +^^^^^^^ + +Block devices are accessed through :cpp:type:`esp_blockdev_handle_t`. Handles are obtained from the owning component via the ``_get_blockdev()`` convention and must be released with the matching ``_release_blockdev()`` helper once the device is no longer needed. Treat handles as opaque: only use the public API in ``components/esp_blockdev/include/esp_blockdev.h``, and do not move or modify the memory they reference. + +Geometry and Flags +^^^^^^^^^^^^^^^^^^ + +Each device publishes an :cpp:type:`esp_blockdev_geometry_t` structure that reports capacity together with minimum read, write, and erase granularities. Optional recommended sizes act as performance hints but must not replace alignment checks against the mandatory values. The accompanying :cpp:type:`esp_blockdev_flags_t` structure advertises properties such as read-only media, encryption, or erase-before-write requirements. Middleware can change apparent geometry size, but must verify upon creation that the underlying layer fits its requirements, and ensure that the underlying device will only be accessed correctly. + +Operations +^^^^^^^^^^ + +The :cpp:type:`esp_blockdev_ops_t` structure defines callbacks for read, write, erase, sync, ioctl, and release. Before invoking a callback, callers must ensure that the given pointer is not ``NULL``; a ``NULL`` pointer indicates that the operation is not supported. Callers are responsible for validating alignment and bounds using the geometry data and for respecting flag-driven requirements such as issuing an erase before writing to NAND-like media. + +Typical Flow +^^^^^^^^^^^^ + +1. Acquire a handle from a driver or middleware provider. +2. Inspect geometry and flags to determine required alignment, available capacity, and special handling. +3. Issue read, write, erase, and sync requests through the operation table that the provider exposes. +4. Forward the handle to higher components or release it once all operations complete. + +Example +^^^^^^^ + +.. code-block:: c + + esp_blockdev_handle_t dev = my_component_get_blockdev(); + const esp_blockdev_geometry_t *geometry = dev->geometry; + if (dev->ops->read && (sizeof(buffer) % geometry->read_size) == 0) { + ESP_ERROR_CHECK(dev->ops->read(dev, buffer, sizeof(buffer), 0, sizeof(buffer))); + } + if (dev->ops->release) { + ESP_ERROR_CHECK(dev->ops->release(dev)); + } + +Contracts +--------- + +Flags (:cpp:type:`esp_blockdev_flags_t`) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +* Flags are initialised once during device creation and must remain immutable for the lifetime of the handle. +* ``read_only`` requires write, erase, and mutating ioctl commands to fail with an error such as :c:macro:`ESP_ERR_INVALID_STATE`. +* ``encrypted`` signals that on-media data is encrypted; higher layers must not assume plaintext visibility or transparent mapping. +* ``erase_before_write`` tells callers that a successful write requires an erase of the target range beforehand. If multiple write operations are issued to the same range without an erase operation in-between, the behavior is undefined, but will likely result in data corruption. +* ``and_type_write`` signals NAND/NOR-style behavior: programming only clears bits (1→0) and effectively stores ``existing_bits & new_bits``. Bits that are already zero remain zero even if the write request supplies ones; erasing first is the only way to restore them. +* ``default_val_after_erase`` identifies whether erased regions read as ``0x00`` or ``0xFF`` so middleware can keep sentinel values consistent. + +Geometry (:cpp:type:`esp_blockdev_geometry_t`) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +* ``disk_size`` is the total accessible capacity in bytes; operations must reject any request whose end offset exceeds this value. +* ``read_size``, ``write_size``, and ``erase_size`` are mandatory alignment units, in bytes; both offsets and lengths must align to the corresponding size before the operation runs. +* Recommended sizes improve throughput when callers honour them but cannot replace the minimum alignment checks; implementations must accept any request that respects the required granularity. +* When a user sees read-write and read-only variants of the same underlying device, the geometry must be identical aside from the ``read_only`` flag. In particular, ``read_size``, ``write_size``, and ``erase_size`` should match between the two variants; ``recommended_*`` values are expected to match and should differ only when there is a clear benefit to doing so (and should be documented in such cases). + +Operations (:cpp:type:`esp_blockdev_ops_t`) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +``read(dev_handle, dst_buf, dst_buf_size, src_addr, data_read_len)`` + * Behaviour: Upon success copies exactly ``data_read_len`` bytes into ``dst_buf``. + * Preconditions: ``dst_buf`` is valid, ``src_addr`` and ``data_read_len`` are aligned to ``read_size``, ``src_addr + data_read_len <= disk_size``, and ``data_read_len <= dst_buf_size``. + * Postcondition: Returns :c:macro:`ESP_OK` when the copy succeeds or propagates a relevant ``ESP_ERR_*`` code on failure. + +``write(dev_handle, src_buf, dst_addr, data_write_len)`` + * Preconditions: Device is not ``read_only``; ``src_buf`` spans at least ``data_write_len`` bytes; offset and length align to ``write_size`` and stay within ``disk_size``. + * Behaviour: When ``erase_before_write`` is set, callers must issue an ``erase`` beforehand. When ``and_type_write`` is set, the hardware applies a bitwise AND with the existing contents, so the stored result becomes ``old_value & new_value``; bits cleared by earlier writes stay cleared unless the range is erased first. + * Postcondition: Returns :c:macro:`ESP_OK` once the requested range has been accepted by the device (data may still reside in intermediate buffers until ``sync`` runs). Misaligned, out-of-range, or read-only attempts should surface :c:macro:`ESP_ERR_INVALID_ARG` or :c:macro:`ESP_ERR_INVALID_STATE`, and implementations must avoid leaving the range partially updated. + * Note: On devices with ``and_type_write`` writes depend on the existing contents, so reading the same (or overlapping) range immediately after a write may require a preceding ``sync`` to ensure that cached data reflects the fully merged value. + +``erase(dev_handle, start_addr, erase_len)`` + * Preconditions: Device permits erases; ``start_addr`` and ``erase_len`` align to ``erase_size``; the range remains inside ``disk_size``. + * Postcondition: The range reads back as ``default_val_after_erase`` on success. Misaligned or out-of-range requests should return :c:macro:`ESP_ERR_INVALID_ARG`; hardware failures are expected to bubble up using driver-specific ``ESP_ERR_*`` codes. + +``sync(dev_handle)`` + * Flushes pending writes. Devices that omit this callback operate with write-through semantics. + * Postcondition: All previously reported writes reach stable storage before :c:macro:`ESP_OK` is returned (this includes all underlying devices). Timeouts or transport issues should surface as :c:macro:`ESP_ERR_TIMEOUT` or another relevant ``ESP_ERR_*``. + +``ioctl(dev_handle, cmd, args)`` + * Command identifiers `0x00–0x7F` are reserved for ESP-IDF system use; `0x80–0xFF` are available for user-defined extensions. + * Each command defines its own payload layout; because ``args`` is a ``void *``, wrappers can only validate or reinterpret the buffer for commands they understand, and must otherwise treat the payload as opaque. + * Wrappers that cannot service a command should forward it unchanged to the next device in the stack when available; only the bottom device is expected to return :c:macro:`ESP_ERR_NOT_SUPPORTED` for unrecognised commands. + * When the stack contains non-transparent address mapping, forwarding commands that embed raw addresses is inherently unsafe: intermediate layers cannot translate opaque payloads, so behaviour is undefined and will typically fail. Such commands should either be blocked explicitly or documented as unsupported in stacked configurations. + +``release(dev_handle)`` + * Optional destructor that frees device resources. The function must be idempotent so that repeated calls either succeed or return a benign error like :c:macro:`ESP_ERR_INVALID_STATE`. + +Error Handling +^^^^^^^^^^^^^^ + +Callbacks return :c:macro:`ESP_OK` on success and should propagate ``ESP_ERR_*`` codes unchanged to help callers diagnose failures. Middleware and applications are expected to propagate errors from underlying devices rather than masking them inside the stack. NULL function pointers should be treated as "operation not supported". + +Validation +^^^^^^^^^^ + +Implementations should include tests that cover alignment checks, flag-driven behaviour (read-only, erase-before-write, NAND-style writes), and correct propagation of errors through stacked devices. Middleware that wraps lower handles must also verify that handle lifetime management remains consistent across the stack. + +.. _blockdev-apis: + +API Reference +------------- + +.. include-build-file:: inc/esp_blockdev.inc diff --git a/docs/en/api-reference/storage/index.rst b/docs/en/api-reference/storage/index.rst index f268e3e11f6..24b1b8a2cee 100644 --- a/docs/en/api-reference/storage/index.rst +++ b/docs/en/api-reference/storage/index.rst @@ -8,6 +8,7 @@ This section contains reference of the high-level storage APIs. They are based o - :doc:`Partitions API ` allow block based access to SPI flash according to the :doc:`/api-guides/partition-tables`. - :doc:`Non-Volatile Storage library (NVS) ` implements a fault-tolerant wear-levelled key-value storage in SPI NOR flash. - :doc:`Virtual File System (VFS) ` library provides an interface for registration of file system drivers. SPIFFS, FAT and various other file system libraries are based on the VFS. +- :doc:`Block Device Layer ` defines a common block-device abstraction so storage drivers, middleware, and filesystems can interoperate without bespoke adapters. - :doc:`SPIFFS ` is a wear-levelled file system optimized for SPI NOR flash, well suited for small partition sizes and low throughput - :doc:`FAT ` is a standard file system which can be used in SPI flash or on SD/MMC cards - :doc:`Wear Levelling ` library implements a flash translation layer (FTL) suitable for SPI NOR flash. It is used as a container for FAT partitions in flash. @@ -33,6 +34,7 @@ For information about storage security, please refer to :doc:`Storage Security < nvs_partition_parse.rst sdmmc partition + blockdev spiffs vfs wear-levelling diff --git a/docs/en/api-reference/system/heap_debug.rst b/docs/en/api-reference/system/heap_debug.rst index 287dda1979c..f923efb57f3 100644 --- a/docs/en/api-reference/system/heap_debug.rst +++ b/docs/en/api-reference/system/heap_debug.rst @@ -19,7 +19,7 @@ To obtain information about the state of the heap, call the following functions: - :cpp:func:`heap_caps_get_free_size` can be used to return the current free memory for different memory capabilities. - :cpp:func:`heap_caps_get_largest_free_block` can be used to return the largest free block in the heap, which is also the largest single allocation currently possible. Tracking this value and comparing it to the total free heap allows you to detect heap fragmentation. -- :cpp:func:`heap_caps_get_minimum_free_size` can be used to track the heap "low watermark" since boot. +- :cpp:func:`heap_caps_get_minimum_free_size` can be used to track the heap "low watermark" across heaps registered during startup. Heaps added at runtime using :cpp:func:`heap_caps_add_region_with_caps` (i.e., from ``app_main`` onwards) are not taken into account. - :cpp:func:`heap_caps_get_info` returns a :cpp:class:`multi_heap_info_t` structure, which contains the information from the above functions, plus some additional heap-specific data (number of allocations, etc.). - :cpp:func:`heap_caps_print_heap_info` prints a summary of the information returned by :cpp:func:`heap_caps_get_info` to stdout. - :cpp:func:`heap_caps_dump` and :cpp:func:`heap_caps_dump_all` output detailed information about the structure of each block in the heap. Note that this can be a large amount of output. diff --git a/docs/en/api-reference/system/power_management.rst b/docs/en/api-reference/system/power_management.rst index 179a655e5bf..6a3951df28c 100644 --- a/docs/en/api-reference/system/power_management.rst +++ b/docs/en/api-reference/system/power_management.rst @@ -48,10 +48,14 @@ Dynamic frequency scaling (DFS) and automatic Light-sleep can be enabled in an a In Light-sleep, peripherals are clock gated, and interrupts (from GPIOs and internal peripherals) will not be generated. A wakeup source described in the :doc:`sleep_modes` documentation can be used to trigger wakeup from the Light-sleep state. -.. only:: SOC_PM_SUPPORT_EXT0_WAKEUP or SOC_PM_SUPPORT_EXT1_WAKEUP +.. only:: SOC_PM_SUPPORT_EXT0_WAKEUP and SOC_PM_SUPPORT_EXT1_WAKEUP For example, the EXT0 and EXT1 wakeup sources can be used to wake up the chip via a GPIO. +.. only:: SOC_PM_SUPPORT_EXT1_WAKEUP and not SOC_PM_SUPPORT_EXT0_WAKEUP + + For example, the EXT1 wakeup source can be used to wake up the chip via a GPIO. + Power Management Locks ---------------------- diff --git a/docs/en/api-reference/system/sleep_modes.rst b/docs/en/api-reference/system/sleep_modes.rst index ec0637c93c8..567d81cfbc4 100644 --- a/docs/en/api-reference/system/sleep_modes.rst +++ b/docs/en/api-reference/system/sleep_modes.rst @@ -4,6 +4,7 @@ Sleep Modes :link_to_translation:`zh_CN:[中文]` {IDF_TARGET_SPI_POWER_DOMAIN:default="VDD_SPI", esp32="VDD_SDIO"} +{IDF_TARGET_RTC_POWER_DOMAIN:default="VDD3P3_RTC", esp32c5="VDDPST1", esp32c6="VDDPST1", esp32c61="VDDPST1", esp32p4="VDD_LP"} Overview -------- @@ -217,6 +218,8 @@ RTC peripherals or RTC memories do not need to be powered on during sleep in thi .. only:: SOC_PM_SUPPORT_EXT1_WAKEUP + .. _sleep-ext1-wakeup: + External Wakeup (``ext1``) ^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -232,7 +235,7 @@ RTC peripherals or RTC memories do not need to be powered on during sleep in thi - wake up if any of the selected pins is high (``ESP_EXT1_WAKEUP_ANY_HIGH``) - wake up if any of the selected pins is low (``ESP_EXT1_WAKEUP_ANY_LOW``) - This wakeup source is controlled by the RTC controller. Unlike ``ext0``, this wakeup source supports wakeup even when the RTC peripheral is powered down. Although the power domain of the RTC peripheral, where RTC IOs are located, is powered down during sleep modes, ESP-IDF will automatically lock the state of the wakeup pin before the system enters sleep modes and unlock upon exiting sleep modes. Therefore, the internal pull-up or pull-down resistors can still be configured for the wakeup pin:: + This wakeup source is controlled by the RTC controller. It supports wakeup even when the RTC peripheral is powered down. Although the power domain of the RTC peripheral, where RTC IOs are located, is powered down during sleep modes, ESP-IDF will automatically lock the state of the wakeup pin before the system enters sleep modes and unlock upon exiting sleep modes. Therefore, the internal pull-up or pull-down resistors can still be configured for the wakeup pin:: esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON); rtc_gpio_pullup_dis(gpio_num); @@ -286,10 +289,14 @@ RTC peripherals or RTC memories do not need to be powered on during sleep in thi GPIO Wakeup (Light-sleep Only) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - .. only:: (SOC_PM_SUPPORT_EXT0_WAKEUP or SOC_PM_SUPPORT_EXT1_WAKEUP) + .. only:: SOC_PM_SUPPORT_EXT0_WAKEUP and SOC_PM_SUPPORT_EXT1_WAKEUP In addition to EXT0 and EXT1 wakeup sources described above, one more method of wakeup from external inputs is available in Light-sleep mode. With this wakeup source, each pin can be individually configured to trigger wakeup on high or low level using :cpp:func:`gpio_wakeup_enable` function. Unlike EXT0 and EXT1 wakeup sources, which can only be used with RTC IOs, this wakeup source can be used with any IO (RTC or digital). + .. only:: SOC_PM_SUPPORT_EXT1_WAKEUP and not SOC_PM_SUPPORT_EXT0_WAKEUP + + In addition to the EXT1 wakeup source described above, one more method of wakeup from external inputs is available in Light-sleep mode. With this wakeup source, each pin can be individually configured to trigger wakeup on high or low level using :cpp:func:`gpio_wakeup_enable` function. Unlike the EXT1 wakeup source, which can only be used with RTC IOs, this wakeup source can be used with any IO (RTC or digital). + .. only:: not (SOC_PM_SUPPORT_EXT0_WAKEUP or SOC_PM_SUPPORT_EXT1_WAKEUP) One more method of wakeup from external inputs is available in Light-sleep mode. With this wakeup source, each pin can be individually configured to trigger wakeup on high or low level using :cpp:func:`gpio_wakeup_enable` function. This wakeup source can be used with any IO (RTC or digital). @@ -323,24 +330,43 @@ RTC peripherals or RTC memories do not need to be powered on during sleep in thi GPIO Wakeup ^^^^^^^^^^^ - There are two GPIO wakeup APIs available, each designed for different sleep scenarios: + .. only:: SOC_GPIO_SUPPORT_HP_PERIPH_PD_SLEEP_WAKEUP + + There are two GPIO wakeup APIs available, each designed for different sleep scenarios: + + .. only:: not SOC_GPIO_SUPPORT_HP_PERIPH_PD_SLEEP_WAKEUP + + On {IDF_TARGET_NAME}, :cpp:func:`esp_sleep_enable_gpio_wakeup` together with :cpp:func:`gpio_wakeup_enable` can wake the chip from Light-sleep. + + .. only:: SOC_PM_SUPPORT_EXT1_WAKEUP + + To wake from Deep-sleep using RTC GPIOs, use EXT1 wakeup (:cpp:func:`esp_sleep_enable_ext1_wakeup_io`); see :ref:`sleep-ext1-wakeup`. **1. :cpp:func:`esp_sleep_enable_gpio_wakeup` - For Light-sleep (GPIO module powered on)** Any IO can be used as the external input to wake up the chip from Light-sleep when the GPIO module remains powered on. Each pin can be individually configured to trigger wakeup on high or low level using the :cpp:func:`gpio_wakeup_enable` function. Then the :cpp:func:`esp_sleep_enable_gpio_wakeup` function should be called to enable this wakeup source. - .. note:: - This API is **not available** when :ref:`CONFIG_PM_POWER_DOWN_PERIPHERAL_IN_LIGHT_SLEEP` is enabled, because the GPIO module is powered down during sleep in this case. Use :cpp:func:`esp_sleep_enable_gpio_wakeup_on_hp_periph_powerdown` instead. + .. only:: SOC_GPIO_SUPPORT_HP_PERIPH_PD_SLEEP_WAKEUP - **2. :cpp:func:`esp_sleep_enable_gpio_wakeup_on_hp_periph_powerdown` - For Deep-sleep and Light-sleep (peripheral powerdown)** + .. note:: + This API is **not available** when :ref:`CONFIG_PM_POWER_DOWN_PERIPHERAL_IN_LIGHT_SLEEP` is enabled, because the GPIO module is powered down during sleep in this case. Use :cpp:func:`esp_sleep_enable_gpio_wakeup_on_hp_periph_powerdown` instead. - IOs that are powered by the VDD3P3_RTC power domain can be used to wake up the chip from Deep-sleep or Light-sleep when the peripheral power domain is powered down. The wakeup pin and wakeup trigger level can be configured by calling :cpp:func:`esp_sleep_enable_gpio_wakeup_on_hp_periph_powerdown`. This function works for: + .. only:: not SOC_GPIO_SUPPORT_HP_PERIPH_PD_SLEEP_WAKEUP - - Deep-sleep mode (always) - - Light-sleep mode when :ref:`CONFIG_PM_POWER_DOWN_PERIPHERAL_IN_LIGHT_SLEEP` is enabled + .. note:: + When :ref:`CONFIG_PM_POWER_DOWN_PERIPHERAL_IN_LIGHT_SLEEP` is enabled, to keep using :cpp:func:`gpio_wakeup_enable`, call :cpp:func:`rtc_gpio_init` and :cpp:func:`rtc_gpio_set_direction` so the pin is used as an RTC GPIO input. - .. note:: - Only GPIOs powered by the VDD3P3_RTC power domain (RTC IOs) can be used with this API. The exact set of supported pins can be checked in the `datasheet <{IDF_TARGET_DATASHEET_EN_URL}>`__ > Section IO Pins. + .. only:: SOC_GPIO_SUPPORT_HP_PERIPH_PD_SLEEP_WAKEUP + + **2. :cpp:func:`esp_sleep_enable_gpio_wakeup_on_hp_periph_powerdown` - For Deep-sleep and Light-sleep (peripheral powerdown)** + + IOs that are powered by the VDD3P3_RTC power domain can be used to wake up the chip from Deep-sleep or Light-sleep when the peripheral power domain is powered down. The wakeup pin and wakeup trigger level can be configured by calling :cpp:func:`esp_sleep_enable_gpio_wakeup_on_hp_periph_powerdown`. This function works for: + + - Deep-sleep mode (always) + - Light-sleep mode when :ref:`CONFIG_PM_POWER_DOWN_PERIPHERAL_IN_LIGHT_SLEEP` is enabled + + .. note:: + Only GPIOs powered by the VDD3P3_RTC power domain (RTC IOs) can be used with this API. The exact set of supported pins can be checked in the `datasheet <{IDF_TARGET_DATASHEET_EN_URL}>`__ > Section IO Pins. .. only:: esp32h2 @@ -365,6 +391,24 @@ After waking-up from UART, you should send some extra data through the UART port In Light-sleep mode, setting Kconfig option :ref:`CONFIG_PM_POWER_DOWN_PERIPHERAL_IN_LIGHT_SLEEP` will invalidate UART wakeup. +.. only:: SOC_ULP_LP_UART_SUPPORTED + + LP_UART can wake up the ULP LP core coprocessor. LP_UART supports the same wakeup modes as the HP UART described above, including active edge threshold wakeup, RX FIFO threshold wakeup, start bit detection wakeup, and character sequence detection wakeup. + + To use LP_UART to wake up the ULP LP core, follow these steps: + + #. Set the :c:macro:`ULP_LP_CORE_WAKEUP_SOURCE_LP_UART` flag in the ``wakeup_source`` field of the :cpp:type:`ulp_lp_core_cfg_t` structure. + #. Initialize the LP UART (call :cpp:func:`lp_core_uart_init`). + #. Configure the LP_UART wakeup mode using the :cpp:func:`lp_core_uart_wakeup_setup` function with a :cpp:type:`uart_wakeup_cfg_t` structure, using the same configuration method as HP UART. + + .. only:: SOC_LP_CORE_LP_UART_WAKEUP_KEEP_TRIGGERED + + .. note:: + + On chips with ``SOC_LP_CORE_LP_UART_WAKEUP_KEEP_TRIGGERED``, the LP UART wakeup signal remains triggered after a wakeup event. The LP core startup flow (:cpp:func:`ulp_lp_core_update_wakeup_cause`) automatically calls :cpp:func:`ulp_lp_core_lp_uart_reset_wakeup_en` and :cpp:func:`lp_core_uart_clear_buf` to clear this state. If the standard startup flow is not used, you must handle this manually; otherwise, repeated wakeups will occur. + + For example code on LP_UART wakeup, refer to :example:`system/ulp/lp_core/lp_uart/lp_uart_char_seq_wakeup`. + .. _disable_sleep_wakeup_source: Disable Sleep Wakeup Source @@ -526,10 +570,13 @@ Application Examples :SOC_WIFI_SUPPORTED: - :example:`wifi/power_save` demonstrates the usage of Wi-Fi Modem-sleep mode and automatic Light-sleep feature to maintain Wi-Fi connections. :SOC_BT_SUPPORTED: - :example:`bluetooth/nimble/power_save` demonstrates the usage of Bluetooth Modem-sleep mode and automatic Light-sleep feature to maintain Bluetooth connections. :SOC_ULP_SUPPORTED: - :example:`system/deep_sleep` demonstrates the usage of various Deep-sleep wakeup triggers and ULP coprocessor programming. - :not SOC_ULP_SUPPORTED and not esp32c3 and not esp32h2: - :example:`system/deep_sleep` demonstrates the usage of Deep-sleep wakeup triggered by various sources, such as the RTC timer, GPIOs, EXT0, EXT1, supported by {IDF_TARGET_NAME}. + :not SOC_ULP_SUPPORTED and not esp32c3 and not esp32h2 and SOC_PM_SUPPORT_EXT1_WAKEUP and SOC_GPIO_SUPPORT_HP_PERIPH_PD_SLEEP_WAKEUP: - :example:`system/deep_sleep` demonstrates the usage of Deep-sleep wakeup triggered by various sources, such as the RTC timer, GPIOs, EXT1, supported by {IDF_TARGET_NAME}. + :not SOC_ULP_SUPPORTED and not esp32c3 and not esp32h2 and SOC_PM_SUPPORT_EXT1_WAKEUP and not SOC_GPIO_SUPPORT_HP_PERIPH_PD_SLEEP_WAKEUP: - :example:`system/deep_sleep` demonstrates the usage of Deep-sleep wakeup triggered by various sources, such as the RTC timer and EXT1, supported by {IDF_TARGET_NAME}. + :not SOC_ULP_SUPPORTED and not esp32c3 and not esp32h2 and not SOC_PM_SUPPORT_EXT1_WAKEUP: - :example:`system/deep_sleep` demonstrates the usage of Deep-sleep wakeup triggered by various sources, such as the RTC timer, GPIOs, supported by {IDF_TARGET_NAME}. :esp32c3: - :example:`system/deep_sleep` demonstrates the usage of Deep-sleep wakeup triggered by various sources, such as the RTC timer, GPIOs, supported by ESP32-C3. - :esp32h2: - :example:`system/deep_sleep` demonstrates the usage of Deep-sleep wakeup triggered by various sources, such as the RTC timer, EXT0, EXT1, supported by ESP32-H2. + :esp32h2: - :example:`system/deep_sleep` demonstrates the usage of Deep-sleep wakeup triggered by various sources, such as the RTC timer and EXT1, supported by ESP32-H2. - :example:`system/light_sleep` demonstrates the usage of Light-sleep wakeup triggered by various sources, such as the timer, GPIOs, supported by {IDF_TARGET_NAME}. + :SOC_PM_SUPPORT_USB_WAKEUP: - :example:`peripherals/usb/device/tusb_cdc_acm_wakeup` demonstrates the usage of USB 2.0 wakeup from Light-sleep. :SOC_TOUCH_SENSOR_SUPPORTED and SOC_PM_SUPPORT_TOUCH_SENSOR_WAKEUP: - :example:`peripherals/touch_sensor/touch_sens_sleep` demonstrates the usage of Light-sleep and Deep-sleep wakeup triggered by the touch sensor. API Reference diff --git a/docs/en/api-reference/system/wdts.rst b/docs/en/api-reference/system/wdts.rst index ad325da9114..67f645692f9 100644 --- a/docs/en/api-reference/system/wdts.rst +++ b/docs/en/api-reference/system/wdts.rst @@ -68,8 +68,10 @@ Configuration - The IWDT is enabled by default via the :ref:`CONFIG_ESP_INT_WDT` option. - The IWDT's timeout is configured by setting the :ref:`CONFIG_ESP_INT_WDT_TIMEOUT_MS` option. - - Note that the default timeout is higher if PSRAM support is enabled, as a critical section or interrupt routine that accesses a large amount of PSRAM takes longer to complete in some circumstances. - - The timeout should always at least twice longer than the period between FreeRTOS ticks (see :ref:`CONFIG_FREERTOS_HZ`). + .. list:: + + :SOC_SPIRAM_SUPPORTED: - Note that the default timeout is higher if PSRAM support is enabled, as a critical section or interrupt routine that accesses a large amount of PSRAM takes longer to complete in some circumstances. + - The configured timeout duration for IWDT should always be at least twice longer than the period between two FreeRTOS ticks, e.g., if two FreeRTOS ticks occur 10 ms apart, then IWDT timeout duration should at least be more than 20 ms (see :ref:`CONFIG_FREERTOS_HZ`). Tuning ^^^^^^ diff --git a/docs/en/migration-guides/release-6.x/6.0/peripherals.rst b/docs/en/migration-guides/release-6.x/6.0/peripherals.rst index 87efad70b07..6adacbdce7c 100644 --- a/docs/en/migration-guides/release-6.x/6.0/peripherals.rst +++ b/docs/en/migration-guides/release-6.x/6.0/peripherals.rst @@ -311,7 +311,7 @@ LCD - The ``psram_trans_align`` and ``sram_trans_align`` members in the :cpp:type:`esp_lcd_rgb_panel_config_t` structure have also been replaced by the :cpp:member:`esp_lcd_rgb_panel_config_t::dma_burst_size` member for configuring the DMA burst transfer size. - The ``color_space`` and ``rgb_endian`` configuration options in the :cpp:type:`esp_lcd_panel_dev_config_t` structure have been replaced by the :cpp:member:`esp_lcd_panel_dev_config_t::rgb_ele_order` member, which sets the RGB element order. The corresponding types ``lcd_color_rgb_endian_t`` and ``esp_lcd_color_space_t`` have also been removed; use :cpp:type:`lcd_rgb_element_order_t` instead. - The ``esp_lcd_panel_disp_off`` function has been removed. Please use the :func:`esp_lcd_panel_disp_on_off` function to control display on/off. -- The ``on_bounce_frame_finish`` member in :cpp:type:`esp_lcd_rgb_panel_event_callbacks_t` has been replaced by :cpp:member:`esp_lcd_rgb_panel_event_callbacks_t::on_frame_buf_complete`, which indicates that a complete frame buffer has been sent to the LCD controller. +- The ``on_bounce_frame_finish`` member in :cpp:type:`esp_lcd_rgb_panel_event_callbacks_t` has been replaced by :cpp:member:`esp_lcd_rgb_panel_event_callbacks_t::on_frame_buf_complete`, which indicates that a complete frame buffer can be safely reused. - The LCD IO layer driver for the I2C interface previously had two implementations, based on the new and legacy I2C master bus drivers. As the legacy I2C driver is being deprecated, support for it in the LCD IO layer has been removed. Only the APIs provided in ``driver/i2c_master.h`` are now used. - ``pixel_format`` member in the :cpp:type:`esp_lcd_dpi_panel_config_t` structure has been removed. It is recommended to only use :cpp:member:`esp_lcd_dpi_panel_config_t::in_color_format` to set the MIPI DSI driver's input pixel data format. - ``bits_per_pixel`` member in the :cpp:type:`esp_lcd_rgb_panel_config_t` structure has been removed. The color depth of the internal framebuffer is now determined by the :cpp:member:`esp_lcd_rgb_panel_config_t::in_color_format` member. diff --git a/docs/en/migration-guides/release-6.x/6.0/protocols.rst b/docs/en/migration-guides/release-6.x/6.0/protocols.rst index 6644764ec5a..bc1539f38a3 100644 --- a/docs/en/migration-guides/release-6.x/6.0/protocols.rst +++ b/docs/en/migration-guides/release-6.x/6.0/protocols.rst @@ -104,6 +104,74 @@ The deprecated :cpp:func:`esp_tls_conn_http_new` function has been removed. Use The new API requires you to create the :cpp:type:`esp_tls_t` structure using :cpp:func:`esp_tls_init` and provides better control over the connection process. +ESP HTTP Server +--------------- + +WebSocket Handler No Longer Called During Handshake +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +From v6.0.1, the URI handler registered for a WebSocket endpoint is **no longer called** during the WebSocket handshake. + +Prior to this change, the handler was invoked with ``req->method == HTTP_GET`` immediately after the handshake completed, which applications used for connection-time initialization: + +.. code-block:: c + + /* Pre-v6.0.1 pattern — no longer works from v6.0.1 onwards */ + static esp_err_t ws_handler(httpd_req_t *req) + { + if (req->method == HTTP_GET) { + ESP_LOGI(TAG, "New WebSocket connection established"); + return ESP_OK; + } + /* Handle WebSocket frames ... */ + } + +From v6.0.1, the handler is invoked only for subsequent WebSocket data frames, so the ``HTTP_GET`` check is no longer needed in frame handlers. + +Migration Options +^^^^^^^^^^^^^^^^^ + +**Option 1 (Recommended)** — Move connection-time logic into a dedicated post-handshake callback: + +1. Enable :ref:`CONFIG_HTTPD_WS_POST_HANDSHAKE_CB_SUPPORT` in menuconfig. +2. Register a ``ws_post_handshake_cb`` on the ``httpd_uri_t`` struct. The frame handler remains clean with no ``HTTP_GET`` check. + +.. code-block:: c + + static esp_err_t ws_on_connect(httpd_req_t *req) + { + ESP_LOGI(TAG, "New WebSocket connection established"); + return ESP_OK; + } + + static esp_err_t ws_handler(httpd_req_t *req) + { + /* Handle WebSocket frames only */ + } + + static const httpd_uri_t ws_uri = { + .uri = "/ws", + .method = HTTP_GET, + .handler = ws_handler, + .is_websocket = true, + .ws_post_handshake_cb = ws_on_connect, + }; + +**Option 2 (Minimal change)** — Set ``.ws_post_handshake_cb`` to the same function as ``.handler``: + +1. Enable :ref:`CONFIG_HTTPD_WS_POST_HANDSHAKE_CB_SUPPORT` in menuconfig. +2. Set ``.ws_post_handshake_cb = ws_handler`` in the URI registration. The existing ``if (req->method == HTTP_GET)`` check inside the handler continues to work without any further code changes. + +.. code-block:: c + + static const httpd_uri_t ws_uri = { + .uri = "/ws", + .method = HTTP_GET, + .handler = ws_handler, + .is_websocket = true, + .ws_post_handshake_cb = ws_handler, /* same function restores old behavior */ + }; + ESP-Modbus ---------- diff --git a/docs/en/security/flash-encryption.rst b/docs/en/security/flash-encryption.rst index b7c2d75205d..3f9466e87f9 100644 --- a/docs/en/security/flash-encryption.rst +++ b/docs/en/security/flash-encryption.rst @@ -621,7 +621,7 @@ To use a host generated key and program it into the eFuses of the device, take t .. only:: SOC_KEY_MANAGER_SUPPORTED - To use a host generated key and deploy it into the device's Key Manager of the device, take the following steps: + To use a host generated key and deploy it into the device's Key Manager, take the following steps: 1. Ensure that you have an {IDF_TARGET_NAME} device with default flash encryption eFuse settings as shown in :ref:`flash-encryption-efuse`. @@ -1204,11 +1204,11 @@ Manually Encrypting Files .. only:: SOC_KEY_MANAGER_SUPPORTED - Manually encrypting or decrypting files require the flash encryption key to be deployed in the Key Manager or pre-burned in eFuses (see :ref:`pregenerated-flash-encryption-key`) and a copy to be kept on the host. If the flash encryption is configured in development mode, then it is not necessary to keep a copy of the key or follow these steps. The simpler :ref:`encrypt-partitions` steps can be used. + Manually encrypting or decrypting files requires the flash encryption key to be deployed in the Key Manager or pre-burned in eFuses (see :ref:`pregenerated-flash-encryption-key`) and a copy to be kept on the host. If the flash encryption is configured in development mode, then it is not necessary to keep a copy of the key or follow these steps. The simpler :ref:`encrypt-partitions` steps can be used. .. only:: not SOC_KEY_MANAGER_SUPPORTED - Manually encrypting or decrypting files require the flash encryption key to be pre-burned in eFuse (see :ref:`pregenerated-flash-encryption-key`) and a copy to be kept on the host. If the flash encryption is configured in development mode, then it is not necessary to keep a copy of the key or follow these steps. The simpler :ref:`encrypt-partitions` steps can be used. + Manually encrypting or decrypting files requires the flash encryption key to be pre-burned in eFuse (see :ref:`pregenerated-flash-encryption-key`) and a copy to be kept on the host. If the flash encryption is configured in development mode, then it is not necessary to keep a copy of the key or follow these steps. The simpler :ref:`encrypt-partitions` steps can be used. The key file should be a single raw binary file (example: ``key.bin``). diff --git a/docs/en/security/secure-boot-v2.rst b/docs/en/security/secure-boot-v2.rst index 25f74b365c7..100e02cc67c 100644 --- a/docs/en/security/secure-boot-v2.rst +++ b/docs/en/security/secure-boot-v2.rst @@ -5,11 +5,11 @@ Secure Boot v2 :link_to_translation:`zh_CN:[中文]` -{IDF_TARGET_SBV2_SCHEME:default="RSA-PSS", esp32c2, esp32c61="ECDSA", esp32c6, esp32h2, esp32p4, esp32c5, esp32h21="RSA-PSS or ECDSA"} +{IDF_TARGET_SBV2_SCHEME:default="RSA-PSS", esp32c2, esp32c61="ECDSA", esp32c6, esp32h2, esp32p4, esp32c5="RSA-PSS or ECDSA", esp32h21="RSA-PSS"} -{IDF_TARGET_SBV2_KEY:default="RSA-3072", esp32c2, esp32c61="ECDSA-256", esp32c6, esp32h2, esp32p4, esp32h21="RSA-3072, ECDSA-256", esp32c5="RSA-3072, ECDSA-384, ECDSA-256"} +{IDF_TARGET_SBV2_KEY:default="RSA-3072", esp32c2, esp32c61="ECDSA-256", esp32c6, esp32h2, esp32p4="RSA-3072, ECDSA-256", esp32h21="RSA-3072", esp32c5="RSA-3072, ECDSA-384, ECDSA-256"} -{IDF_TARGET_SECURE_BOOT_OPTION_TEXT:default="", esp32c6, esp32h2, esp32p4, esp32h21="RSA is recommended for faster verification. You can choose either the RSA or ECDSA scheme from the menu.", esp32c5="ECDSA is recommended for faster verification. You can choose either the RSA or ECDSA scheme from the menu."} +{IDF_TARGET_SECURE_BOOT_OPTION_TEXT:default="", esp32c6, esp32h2, esp32p4="RSA is recommended for faster verification. You can choose either the RSA or ECDSA scheme from the menu.", esp32c5="ECDSA is recommended for faster verification. You can choose either the RSA or ECDSA scheme from the menu."} {IDF_TARGET_SBV2_SCHEME_RECOMMENDATION:default="RSA is recommended for use cases where fast boot-up time is required whereas ECDSA is recommended for use cases where shorter key length is required.", esp32c5="ECDSA is recommended for use cases where fast boot-up time and shorter key length is required."} @@ -52,6 +52,18 @@ Secure Boot v2 In this guide, most used commands are in the form of ``idf.py secure-``, which is a wrapper around corresponding ``espsecure ``. The ``idf.py`` based commands provides more user-friendly experience, although may lack some of the advanced functionality of their ``espsecure`` based counterparts. +.. only:: CONFIG_SECURE_BOOT_V2_ECDSA_INSECURE and SOC_SECURE_BOOT_V2_RSA + + .. warning:: + + On {IDF_TARGET_NAME}, the ECDSA based Secure Boot V2 scheme is not functional for certain input vectors and is therefore **not recommended**. Please use the RSA based Secure Boot V2 scheme instead. To use the ECDSA based scheme regardless of this limitation, enable :ref:`CONFIG_SECURE_BOOT_INSECURE` and :ref:`CONFIG_SECURE_BOOT_V2_FORCE_ENABLE_ECDSA`. This issue will be fixed in a future hardware ECO revision; refer to the hardware errata document for details. + +.. only:: CONFIG_SECURE_BOOT_V2_ECDSA_INSECURE and not SOC_SECURE_BOOT_V2_RSA + + .. warning:: + + On {IDF_TARGET_NAME}, the ECDSA based Secure Boot V2 scheme is vulnerable for certain input vectors and is therefore **not recommended for production**. To use the ECDSA based Secure Boot V2 scheme regardless of this limitation, enable :ref:`CONFIG_SECURE_BOOT_INSECURE` and :ref:`CONFIG_SECURE_BOOT_V2_FORCE_ENABLE_ECDSA`. This issue will be fixed in a future hardware ECO revision; refer to the hardware errata document for details. + Background ---------- @@ -729,9 +741,7 @@ Secure Boot Best Practices .. note:: - Note that enabling the config :ref:`CONFIG_SECURE_BOOT_ALLOW_UNUSED_DIGEST_SLOTS` only makes sure that the **app** does not revoke the unused digest slots. - But if you plan to enable secure boot during the fist boot up, the bootloader will intentionally revoke the unused digest slots while enabling secure boot, even if the above config is enabled. Because keeping the unused key slots unrevoked would be a security hazard. - In case for any development workflow if you need to avoid this revocation, you should :ref:`enable-secure-boot-v2-externally`, rather than enabling it during the boot up, so that the bootloader would not need to enable secure boot, and thus you could avoid its revocation strategy. + Enabling the config :ref:`CONFIG_SECURE_BOOT_ALLOW_UNUSED_DIGEST_SLOTS` keeps the unused digest slots un-revoked in both cases: at runtime in the **app**, and in the **bootloader** when secure boot is enabled during the first boot up. Note that leaving unused key slots un-revoked could pose a security risk, unless the debug and download interfaces are completely disabled and remote interfaces are fully audited for security risks. Conservative Approach ~~~~~~~~~~~~~~~~~~~~~~ diff --git a/docs/en/security/security.rst b/docs/en/security/security.rst index c7fe012b9b2..3febaf3bc45 100644 --- a/docs/en/security/security.rst +++ b/docs/en/security/security.rst @@ -311,7 +311,7 @@ Please see more information to enable this feature in the :ref:`anti-rollback` g Encrypted Firmware Distribution ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Encrypted firmware distribution during over-the-air updates ensures that the application stays encrypted **in transit** from the server to the the device. This can act as an additional layer of protection on top of the TLS communication during OTA updates and protect the identity of the application. +Encrypted firmware distribution during over-the-air updates ensures that the application stays encrypted **in transit** from the server to the device. This can act as an additional layer of protection on top of the TLS communication during OTA updates and protect the identity of the application. Please see working example for this documented in :ref:`ota_updates_pre-encrypted-firmware` section. diff --git a/docs/en/security/tee/tee-attestation.rst b/docs/en/security/tee/tee-attestation.rst index 672067e74cb..2d45bcf9a18 100644 --- a/docs/en/security/tee/tee-attestation.rst +++ b/docs/en/security/tee/tee-attestation.rst @@ -107,6 +107,12 @@ EAT: Claim Table * - Client ID - Relying Party identification - + * - Chip ID + - SoC chip identifier + - + * - UEID + - Universal Entity Identifiers, factory-burnt in eFuse + - Device MAC address and the Optional Unique ID from eFuse * - Device ID - Device identification (should be unique) - SHA256 digest of the device MAC address @@ -176,9 +182,14 @@ Sample EAT in JSON format "key_id": "tee_att_key0" }, "eat": { - "auth_challenge":"dcb9b53143ad6b081dad1a05c7ebda4e314d388762215799cf24ed52e9387678" + "auth_challenge":"dcb9b53143ad6b081dad1a05c7ebda4e314d388762215799cf24ed52e9387678", "client_id": 262974944, + "chip_id": 13, "device_ver": 1, + "ueid": { + "mac": "d885ac67c978", + "optional_id": "94fa4d7e305682714d48e7bbd710c961" + }, "device_id": "e8cddb2a7f9a5a7c61735d6dda26e4bd153c6d772a9be6f26bd321dfe25e0ac8", "instance_id": "1adba85e0df997fd961f25a9e312430cef162b5c69466cd5b172f1e65ac7360c", "psa_cert_ref": "0716053550477-10100", diff --git a/docs/en/security/tee/tee.rst b/docs/en/security/tee/tee.rst index 7886577d9ee..6a8e2b5b3ba 100644 --- a/docs/en/security/tee/tee.rst +++ b/docs/en/security/tee/tee.rst @@ -76,7 +76,7 @@ ESP-TEE divides the memory into separate regions for the TEE and REE, allocating Internal Memory (SRAM) ^^^^^^^^^^^^^^^^^^^^^^ -Internal memory is allocated to the the TEE based on the Kconfig options that are available under the :ref:`Memory Configuration ` section in the :ref:`esp-tee-trusted-execution-environment-` menu. All remaining memory is allocated to the REE. +Internal memory is allocated to the TEE based on the Kconfig options that are available under the :ref:`Memory Configuration ` section in the :ref:`esp-tee-trusted-execution-environment-` menu. All remaining memory is allocated to the REE. .. warning:: diff --git a/docs/zh_CN/api-guides/app_trace.rst b/docs/zh_CN/api-guides/app_trace.rst index b4aa69dd8da..2d4965cc10d 100644 --- a/docs/zh_CN/api-guides/app_trace.rst +++ b/docs/zh_CN/api-guides/app_trace.rst @@ -557,9 +557,27 @@ Start 子命令语法: 如果你在可视化方面遇到了问题(未显示数据或者缩放操作异常),可以尝试删除当前的信号层次结构,再双击必要的文件或端口。Eclipse 会请求创建新的信号层次结构。 +应用示例 +"""""""" + +- :example:`system/sysview_tracing` 演示如何使用 SEGGER SystemView 记录 FreeRTOS 任务与系统事件。 +- :example:`system/sysview_tracing_heap_log` 演示如何在记录 SystemView 事件的同时,对堆内存分配进行跟踪。 + .. _app_trace-gcov-source-code-coverage: Gcov(源代码覆盖率) ^^^^^^^^^^^^^^^^^^^^^^^^^^^ 在 ESP-IDF 项目中,可以借助 `espressif/esp_gcov `_ 托管组件使用 gcov 进行代码覆盖率分析。 + +.. _app_trace-integrating-a-custom-trace-library: + +集成自定义跟踪库 +^^^^^^^^^^^^^^^^ + +``esp_trace`` 组件提供了稳定的扩展点 (``CONFIG_ESP_TRACE_LIB_EXTERNAL``),允许在不修改 ESP-IDF 的情况下接入第三方跟踪记录器。外部组件需提供一个编码器适配器(通过 ``ESP_TRACE_REGISTER_ENCODER()`` 注册)以及一个轻量的 ``esp_trace_freertos_impl.h``,用于注入所需的 FreeRTOS 跟踪钩子。编码器虚表还提供可选的 ``start`` / ``stop`` / ``flush`` 及 ``take_lock`` / ``give_lock`` 入口,由公共 API :cpp:func:`esp_trace_start`、:cpp:func:`esp_trace_stop`、:cpp:func:`esp_trace_flush` 调度。 + +应用示例 +"""""""" + +- :example:`system/esp_trace` 是一个最简的复制粘贴模板,演示如何接入外部编码器、说明 FreeRTOS 跟踪钩子头文件的包含链约束,以及通过编码器锁实现多核序列化。 diff --git a/docs/zh_CN/api-guides/build-system.rst b/docs/zh_CN/api-guides/build-system.rst index 65b2f7109ea..c51a3e34980 100644 --- a/docs/zh_CN/api-guides/build-system.rst +++ b/docs/zh_CN/api-guides/build-system.rst @@ -77,6 +77,14 @@ idf.py 没有必要多次运行 ``cmake``。第一次构建后,往后每次只需运行 ``ninja`` 即可。如果项目需要重新配置,``ninja`` 会自动重新调用 ``cmake``。 +使用 Ninja 生成器配合 ``idf.py`` 时,可以通过设置环境变量 ``IDF_PY_BUILD_JOBS`` 来限制并行构建任务数。例如: + +.. code-block:: bash + + IDF_PY_BUILD_JOBS=6 idf.py build + +如果不是通过 ``idf.py``,而是直接调用 CMake、``ninja`` 或 ``make``,则请使用它们各自原生的并行控制选项或环境变量。 + 若在 CMake 中使用 ``ninja`` 或 ``make``,则多数 ``idf.py`` 子命令也会有其对应的目标,例如在构建目录下运行 ``make menuconfig`` 或 ``ninja menuconfig`` 与运行 ``idf.py menuconfig`` 是相同的。 .. note:: diff --git a/docs/zh_CN/api-guides/external-ram.rst b/docs/zh_CN/api-guides/external-ram.rst index ff455087d7e..6087b7547f1 100644 --- a/docs/zh_CN/api-guides/external-ram.rst +++ b/docs/zh_CN/api-guides/external-ram.rst @@ -249,6 +249,30 @@ ESP-IDF 启动过程中,片外 RAM 被映射到数据虚拟地址空间,该 在 {IDF_TARGET_NAME} 上,PSRAM 加密可以按 MMU 页面粒度进行控制,允许对单个 PSRAM 页面选择性地加密或不加密。但在默认配置下,启用 flash 加密时所有 PSRAM 页面都会被加密。 + 预留未加密的 PSRAM 区域 + ----------------------- + + 启用 :ref:`CONFIG_SPIRAM_ENC_EXEMPT` 会在 PSRAM 上端(最高物理地址区,大小由 :ref:`CONFIG_SPIRAM_ENC_EXEMPT_SIZE` 指定,单位为 KB,向上取整到 MMU 页面大小)预留一段区域,该区域在映射时不启用加密。此区域被注册为一个独立的堆池,仅可通过 ``MALLOC_CAP_SPIRAM_NO_ENC`` 能力位访问。其余 PSRAM(以及 flash)仍然保持加密。 + + .. warning:: + + 通过 ``MALLOC_CAP_SPIRAM_NO_ENC`` 分配的内存以明文形式存储在 PSRAM 中,攻击者若能物理接触 PSRAM 接口即可读取其内容。切勿将 TLS 状态、密钥或其他敏感数据放入该区域。 + + 典型使用场景:PSRAM 加密会对缓冲区施加对齐约束,部分 DMA 引擎(如 2D-DMA)无法满足这些约束。需要被此类引擎进行 DMA 访问的缓冲区可以从该未加密区域分配: + + .. code-block:: c + + #if CONFIG_SPIRAM_ENC_EXEMPT + uint32_t caps = MALLOC_CAP_SPIRAM_NO_ENC; + #else + uint32_t caps = MALLOC_CAP_SPIRAM; + #endif + uint8_t *buf = heap_caps_malloc(buf_size, caps); + + 必须显式请求 ``MALLOC_CAP_SPIRAM_NO_ENC``。该能力位有意未与 ``MALLOC_CAP_SPIRAM`` 或 ``MALLOC_CAP_DEFAULT`` 组合,因此普通 SPIRAM/堆分配不会意外落入该未加密区域。 + + 如需在分配后验证缓冲区是否确实位于未加密的预留区域(例如调用 ``heap_caps_malloc_prefer()`` 后可能回退到加密的 PSRAM),可使用 ``esp_psram_ptr_is_no_enc()``。 + .. only:: SOC_PSRAM_ENCRYPTION_SEPARATE_KEY 在 {IDF_TARGET_NAME} 上,PSRAM 加密可以使用独立的加密密钥。如果未烧录 PSRAM 加密密钥,则会使用 flash 加密密钥作为 PSRAM 加密密钥。 diff --git a/docs/zh_CN/api-guides/jtag-debugging/index.rst b/docs/zh_CN/api-guides/jtag-debugging/index.rst index 07dee8e8616..932b4e631f9 100644 --- a/docs/zh_CN/api-guides/jtag-debugging/index.rst +++ b/docs/zh_CN/api-guides/jtag-debugging/index.rst @@ -29,6 +29,8 @@ JTAG 调试 如果你不熟悉 GDB,请查看此小节以获取 :ref:`Eclipse 集成开发环境 ` 以及 :ref:`命令行终端 ` 提供的调试示例。 :ref:`jtag-debugging-building-openocd` OpenOCD 源码构建流程参考。 +:ref:`jtag-debugging-semihosting` + 介绍 semihosting 功能。 :ref:`jtag-debugging-tips-and-quirks` 介绍使用 OpenOCD 和 GDB 通过 JTAG 接口调试 {IDF_TARGET_NAME} 时的注意事项和补充内容。 @@ -315,6 +317,18 @@ OpenOCD 烧录命令 ``program_esp_bins`` 格式如下: 如需根据特定需求从源码构建 OpenOCD,请参考 `OpenOCD 构建工作流程 `_。该工作流演示了如何在不同平台 (Windows, Linux, macOS) 上构建 OpenOCD。 +.. _jtag-debugging-semihosting: + +semihosting +----------- + +借助 semihosting 机制,运行在 {IDF_TARGET_NAME} 上的代码能够通过调试器与主机 PC 进行通信,例如打印调试信息或读写文件等。 + +.. toctree:: + :maxdepth: 1 + + semihosting + .. _jtag-debugging-tips-and-quirks: 注意事项和补充内容 @@ -338,11 +352,13 @@ OpenOCD 烧录命令 ``program_esp_bins`` 格式如下: using-debugger debugging-examples + semihosting tips-and-quirks ../app_trace - :doc:`using-debugger` - :doc:`debugging-examples` +- :doc:`semihosting` - :doc:`tips-and-quirks` - :doc:`../app_trace` - `ESP-Prog 调试板介绍 `__ diff --git a/docs/zh_CN/api-guides/jtag-debugging/semihosting.rst b/docs/zh_CN/api-guides/jtag-debugging/semihosting.rst new file mode 100644 index 00000000000..92294ac9e3e --- /dev/null +++ b/docs/zh_CN/api-guides/jtag-debugging/semihosting.rst @@ -0,0 +1,71 @@ +semihosting 功能 +---------------- + +借助 semihosting 机制,在目标设备上运行的程序可以使用调试器所在主机上的 I/O 功能。该机制适用于嵌入式应用的调试与测试,而无需在目标端实现特定硬件相关的 I/O 功能。 + +OpenOCD 为乐鑫目标芯片实现了扩展的 semihosting 协议,其功能超出了标准 ARM semihosting 规范。这使嵌入式应用能够与主机系统进行交互,以执行文件操作、目录管理以及其他系统调用。 + +.. warning:: + + 每个 semihosting 调用都通过包含软件断点指令的序列实现。如果包含 semihosting 调用的程序在 **未连接调试器** 的情况下运行,则会触发异常。 + +.. note:: + + 每个 semihosting 调用都会暂停 CPU,直到主机返回结果。因此,semihosting 不适用于对延迟敏感或对实时性要求较高的代码路径。 + + +.. _jtag-debugging-semihosting-available-operations: + +支持的操作 +^^^^^^^^^^ + +头文件 :idf_file:`components/vfs/openocd_semihosting.h` 声明了所有可用的 semihosting 操作。常见操作包括: + +* **文件操作**:``open``、``close``、``read``、``write``、``lseek``、``fsync``、``link``、``unlink`` +* **目录操作**:``opendir``、``readdir``、``seekdir``、``telldir``、``closedir``、``mkdir``、``rmdir`` +* **文件属性操作**:``rename``、``truncate``、``fstat``、``stat``、``utime``、``access`` + +此外,目标端还可以使用调试钩子触发由 OpenOCD 直接处理的事件: + +* ``panic_reason``:直接在调试器控制台中向用户输出详细的 panic 信息。 + +.. only:: CONFIG_IDF_TARGET_ARCH_RISCV + + * ``breakpoint_set``、``watchpoint_set``:允许在目标端配置断点和观察点,而无需用户手动操作。 + + +.. _jtag-debugging-semihosting-using-from-app: + +在应用中使用 semihosting +^^^^^^^^^^^^^^^^^^^^^^^^ + +在应用代码中使用 semihosting 最便捷的方法是通过虚拟文件系统 (VFS) 驱动。调用 :cpp:func:`esp_vfs_semihost_register` 可将主机目录挂载为普通 VFS 路径,从而无需额外适配即可使用 ``fopen``、``read``、``write`` 等标准接口: + +.. code-block:: c + + #include "esp_vfs_semihost.h" + + esp_vfs_semihost_register("/host"); + FILE *f = fopen("/host/log.txt", "w"); + +完整使用流程请参考 :doc:`虚拟文件系统组件 API 参考 <../../api-reference/storage/vfs>` 以及 :example:`storage/semihost_vfs` 示例。 + +也可以参阅 `OpenOCD semihosting 测试应用 `_。 + +.. _jtag-debugging-semihosting-configuration: + +配置 +^^^^ + +默认情况下,semihosting 文件操作会使用当前目录(即启动 OpenOCD 时所在的目录)作为基础目录。若需指定其他基础目录,请在 OpenOCD 启动命令开头添加额外参数 ``-c 'set ESP_SEMIHOST_BASEDIR /path/to/semihost/root'``,详见 :ref:`jtag-debugging-tip-openocd-config-vars`。 + +.. _jtag-debugging-semihosting-gdb-semihosting: + +GDB semihosting +^^^^^^^^^^^^^^^ + +GDB 也提供了内置的 semihosting 支持,可作为 OpenOCD 实现的补充。当 GDB 以远程方式连接 OpenOCD,并运行在另一台主机上时,这一功能尤其有用。因为此时 semihosting 文件操作会基于 GDB 所在主机,而不是 OpenOCD 所在主机进行解析。 + +若要将 semihosting 请求重定向到 GDB,请在 GDB 中输入 ``mon arm semihosting_fileio enable``。对于多核目标,该命令仅会为当前核心启用 semihosting;如有需要,可针对每个核心分别执行 ``mon arm semihosting_fileio enable`` (可通过 ``mon targets`` 查看目标列表)。 + +启用文件 I/O 功能后,OpenOCD 在截获系统调用后不会自行处理该操作,而是向 GDB 发送文件 I/O 请求数据包,并保持目标暂停状态,直到 GDB 返回结果。对于运行在目标端的代码而言,这一过程是完全透明的。 diff --git a/docs/zh_CN/api-guides/performance/size.rst b/docs/zh_CN/api-guides/performance/size.rst index bb1f9bb38da..83d13208e55 100644 --- a/docs/zh_CN/api-guides/performance/size.rst +++ b/docs/zh_CN/api-guides/performance/size.rst @@ -241,8 +241,9 @@ MbedTLS 功能 .. only:: CONFIG_ESP_ROM_HAS_MBEDTLS_CRYPTO_LIB 启用配置选项 :ref:`CONFIG_MBEDTLS_USE_CRYPTO_ROM_IMPL` 时 mbedtls 使用由 ROM 提供的加密算法。 + 该选项仅在所选目标芯片和最低芯片版本支持 ROM mbedTLS 加密算法库时可用。 - 禁用配置选项 :ref:`CONFIG_MBEDTLS_USE_CRYPTO_ROM_IMPL` 时mbedtls 完全使用由 ESP-IDF 中提供的加密算法。这会导致二进制文件大小增加。 + 禁用配置选项 :ref:`CONFIG_MBEDTLS_USE_CRYPTO_ROM_IMPL` 时,mbedtls 完全使用由 ESP-IDF 中提供的加密算法。这会导致二进制文件大小增加。 .. note:: diff --git a/docs/zh_CN/api-guides/tools/idf-py.rst b/docs/zh_CN/api-guides/tools/idf-py.rst index f45624786c3..3d55a477cf7 100644 --- a/docs/zh_CN/api-guides/tools/idf-py.rst +++ b/docs/zh_CN/api-guides/tools/idf-py.rst @@ -283,7 +283,7 @@ ESP-IDF 的 MCP(Model Context Protocol,模型上下文协议)服务器可 eim run "idf.py mcp-server" -2. 直接使用 ``idf.py``:在已激活 ESP-IDF 环境的 shell 中运行 ``idf.py mcp-server`` 命令启动 MCP 服务器。必须在有效的 ESP-IDF 项目目录中执行该命令,或者使用 ``idf.py -C mcp-server`` 指定项目路径。 +2. 直接使用 ``idf.py``:在已激活 ESP-IDF 环境的 shell 中运行 ``idf.py mcp-server`` 命令启动 MCP 服务器。该服务器可以在任何目录下启动。使用 ``idf.py -C mcp-server`` 或设置 ``IDF_MCP_WORKSPACE_FOLDER`` 环境变量来配置默认项目。如果在启动时未配置任何项目,则在每次调用工具时显式传递项目目录。 .. code-block:: bash @@ -296,12 +296,15 @@ ESP-IDF 的 MCP(Model Context Protocol,模型上下文协议)服务器可 可用工具与资源 ^^^^^^^^^^^^^^ -MCP 服务器提供以下可用的命令: +MCP 服务器提供以下工具: - ``set target``:设置 ESP-IDF 的目标芯片(esp32,esp32s3,esp32c6 等) - ``build project``:使用当前目标构建 ESP-IDF 项目 -- ``flash project``:将已构建的项目烧录到已连接的设备,通过端口名称进行指定。 +- ``flash project``:将已构建的项目烧录到已连接的设备,通过端口名称进行指定 - ``clean project``:清理构建产物 +- ``create project``:基于示例模板创建新的 ESP-IDF 项目,可在尚无项目时使用 + +所有工具都接受可选的 ``project_dir`` 参数。当省略该参数时,工具将默认使用启动时配置的目录(该目录可通过 ``-C`` 参数或 ``IDF_MCP_WORKSPACE_FOLDER`` 环境变量指定)。你可以要求 AI 模型明确指定某个项目目录,例如当同时处理多个项目,或启动时未配置默认项目的情况下。 同时提供以下资源: diff --git a/docs/zh_CN/api-reference/peripherals/async_color_convert.rst b/docs/zh_CN/api-reference/peripherals/async_color_convert.rst new file mode 100644 index 00000000000..32a62dd9aee --- /dev/null +++ b/docs/zh_CN/api-reference/peripherals/async_color_convert.rst @@ -0,0 +1,317 @@ +================ +异步色彩格式转换 +================ + +:link_to_translation:`en:[English]` + +本文介绍 ESP-IDF 中的异步色彩转换驱动。目录如下: + +.. contents:: + :local: + :depth: 2 + +概述 +==== + +{IDF_TARGET_NAME} 提供 DMA2D 引擎,可以把 2D 拷贝和色彩转换工作从 CPU 卸载到硬件执行。 + +这个驱动适合用于: + +- 将图像从一种像素格式转换为另一种像素格式 +- 只转换大图中的一个矩形窗口 +- 将多个转换请求排队,而不是让 CPU 自己做像素搬运 +- 在 RGB 和 UYVY 格式之间转换,并选择 RGB/YUV 转换标准 + +异步色彩转换驱动对 DMA2D 的请求准备、队列管理和完成通知做了封装,同时提供两种使用方式: + +- 带 ISR 回调通知的异步提交接口 +- 基于同一路径实现、对新手更友好的阻塞接口 + +快速开始 +======== + +如果你是第一次使用这个驱动,建议从最简单的流程开始: + +1. 安装驱动 +2. 准备一个 :cpp:type:`async_color_convert_request_t` +3. 通过阻塞或非阻塞 API 发起转换 +4. 在转换完成后使用输出 buffer +5. 继续提交新请求,或在结束时卸载驱动 + +典型使用流程如下: + +.. mermaid:: + + flowchart TD + install["安装驱动
esp_async_color_convert_install_dma2d"] --> request["准备请求
async_color_convert_request_t"] + request --> blocking["阻塞路径
esp_color_convert_blocking"] + request --> nonBlocking["非阻塞路径
esp_async_color_convert"] + nonBlocking --> callback["等待回调或任务通知"] + blocking --> result["使用转换结果 buffer"] + callback --> result + result --> request + result --> uninstall["可选清理
esp_async_color_convert_uninstall"] + +场景 1:先从一次阻塞转换开始 +============================ + +理解这个驱动的最简单方式,就是先完成一次转换,并在函数返回时直接拿到结果。 + +下面的流程与 :example:`peripherals/dma/async_color_convert` 示例一致。它把一个嵌入在 flash 中的 UYVY422 图像转换为 BGR24,然后由应用继续处理转换后的输出: + +.. code:: c + + async_color_convert_handle_t conv_hdl = NULL; // 安装驱动后返回的句柄,后续 API 都要用到它 + async_color_convert_config_t config = { + .backlog = 1, // 这个阻塞示例一次只处理一个请求,因此 1 就够了 + .dma_burst_size = 16, // 先使用示例里的默认 burst 大小即可 + }; + // 创建一个基于 DMA2D 后端的异步色彩转换驱动实例。 + ESP_ERROR_CHECK(esp_async_color_convert_install_dma2d(&config, &conv_hdl)); + + async_color_convert_request_t req = { + .src_buffer = sample_96x64_uyvy_yuv_start, // 源图像可以在 flash 或者 RAM 中,只要 DMA 可访问即可 + .src_stride = 96, // 源图像的行跨度,单位是像素 + .src_height = 64, // 源图像高度,单位是像素 + .src_x = 0, // 从源图像左上角开始取窗口 + .src_y = 0, + .dst_buffer = dst_bgr, // 目标 buffer 位于 DMA 可访问的 RAM 中 + .dst_stride = 96, // 目标图像的行跨度,单位是像素 + .dst_height = 64, // 目标图像高度,单位是像素 + .dst_x = 0, // 从目标图像左上角开始写入结果 + .dst_y = 0, + .copy_width = 96, // 转换整张图的宽度,单位是像素 + .copy_height = 64, // 转换整张图的高度,单位是像素 + .src_color_format = ESP_COLOR_FOURCC_UYVY, // 源像素格式为 UYVY422 + .dst_color_format = ESP_COLOR_FOURCC_BGR24, // 目标像素格式为 BGR24(本驱动里用它表示 RGB888) + .color_conv_std = COLOR_CONV_STD_RGB_YUV_BT601, // 该 RGB/YUV 转换使用的标准 + }; + + // 阻塞等待 DMA2D 完成转换。-1 表示一直等到完成为止。 + ESP_ERROR_CHECK(esp_color_convert_blocking(conv_hdl, &req, -1)); + + // 所有转换结束后,释放驱动资源。 + ESP_ERROR_CHECK(esp_async_color_convert_uninstall(conv_hdl)); + +这个流程里最重要的概念有: + +- :cpp:func:`esp_async_color_convert_install_dma2d` 创建驱动实例 +- :cpp:type:`async_color_convert_request_t` 描述源图像、目标图像以及要转换的窗口 +- :cpp:func:`esp_color_convert_blocking` 会一直等待,直到硬件完成转换 +- :cpp:func:`esp_async_color_convert_uninstall` 释放驱动资源 + +对于阻塞 API,``timeout_ms = -1`` 表示永久等待。其他 timeout 值目前不支持,会返回 ``ESP_ERR_INVALID_ARG``。 + +理解 ``async_color_convert_request_t`` +-------------------------------------- + +这个驱动最容易出错的地方,通常不是安装驱动,而是请求参数填写不正确,因此理解 :cpp:type:`async_color_convert_request_t` 很重要。 + +.. important:: + + 在 :cpp:type:`async_color_convert_request_t` 中,所有几何相关字段的单位都是 **像素**,不是字节。包括 ``src_stride``、``src_height``、``src_x``、``src_y``、``dst_stride``、``dst_height``、``dst_x``、``dst_y``、``copy_width`` 和 ``copy_height``。 + + ``src_stride`` 和 ``dst_stride`` 表示的是每一整行在内存中跨越多少像素,也就是行跨度,不是本次转换窗口的宽度。当你只转换大图中的一个窗口时,它们可以大于 ``copy_width``。 + +这个结构体同时描述了两件事: + +- 源图像和目标图像在内存中的完整布局 +- 本次实际要转换的矩形窗口 + +关键字段含义如下: + +- :cpp:member:`async_color_convert_request_t::src_buffer` + 源图像基地址 +- :cpp:member:`async_color_convert_request_t::src_stride` + 源图像的行跨度,单位为像素 +- :cpp:member:`async_color_convert_request_t::src_height` + 源图像高度,单位为像素 +- :cpp:member:`async_color_convert_request_t::src_x` 和 :cpp:member:`async_color_convert_request_t::src_y` + 源窗口左上角坐标 +- :cpp:member:`async_color_convert_request_t::dst_buffer` + 目标图像基地址 +- :cpp:member:`async_color_convert_request_t::dst_stride` + 目标图像的行跨度,单位为像素 +- :cpp:member:`async_color_convert_request_t::dst_height` + 目标图像高度,单位为像素 +- :cpp:member:`async_color_convert_request_t::dst_x` 和 :cpp:member:`async_color_convert_request_t::dst_y` + 转换结果写入目标图像时的左上角坐标 +- :cpp:member:`async_color_convert_request_t::copy_width` 和 :cpp:member:`async_color_convert_request_t::copy_height` + 本次要转换的矩形窗口尺寸 +- :cpp:member:`async_color_convert_request_t::src_color_format` 和 :cpp:member:`async_color_convert_request_t::dst_color_format` + 源和目标像素格式 +- :cpp:member:`async_color_convert_request_t::color_conv_std` + RGB/YUV 转换标准,用于 RGB 和 YUV 之间的转换 + +源窗口和目标窗口都必须完整落在各自图像的边界之内。 + +支持的转换格式 +-------------- + +本驱动当前支持以下格式组合: + +.. list-table:: + :header-rows: 1 + + * - 源格式 + - 目标格式 + - 转换标准 + * - ``ESP_COLOR_FOURCC_RGB16`` + - ``ESP_COLOR_FOURCC_RGB16`` + - 不适用 + * - ``ESP_COLOR_FOURCC_BGR24`` + - ``ESP_COLOR_FOURCC_BGR24`` + - 不适用 + * - ``ESP_COLOR_FOURCC_RGB24`` + - ``ESP_COLOR_FOURCC_RGB24`` + - 不适用 + * - ``ESP_COLOR_FOURCC_UYVY`` + - ``ESP_COLOR_FOURCC_UYVY`` + - 不适用 + * - ``ESP_COLOR_FOURCC_BGR24`` + - ``ESP_COLOR_FOURCC_RGB24`` + - 不适用 + * - ``ESP_COLOR_FOURCC_RGB24`` + - ``ESP_COLOR_FOURCC_BGR24`` + - 不适用 + * - ``ESP_COLOR_FOURCC_RGB16`` + - ``ESP_COLOR_FOURCC_BGR24`` + - 不适用 + * - ``ESP_COLOR_FOURCC_BGR24`` + - ``ESP_COLOR_FOURCC_RGB16`` + - 不适用 + * - ``ESP_COLOR_FOURCC_RGB24`` + - ``ESP_COLOR_FOURCC_RGB16`` + - 不适用 + * - ``ESP_COLOR_FOURCC_BGR24`` + - ``ESP_COLOR_FOURCC_UYVY`` + - BT.601 + * - ``ESP_COLOR_FOURCC_BGR24`` + - ``ESP_COLOR_FOURCC_UYVY`` + - BT.709 + * - ``ESP_COLOR_FOURCC_RGB24`` + - ``ESP_COLOR_FOURCC_UYVY`` + - BT.601 + * - ``ESP_COLOR_FOURCC_RGB24`` + - ``ESP_COLOR_FOURCC_UYVY`` + - BT.709 + * - ``ESP_COLOR_FOURCC_UYVY`` + - ``ESP_COLOR_FOURCC_BGR24`` + - BT.601 + * - ``ESP_COLOR_FOURCC_UYVY`` + - ``ESP_COLOR_FOURCC_BGR24`` + - BT.709 + +.. note:: + + 所有请求都需要设置 :cpp:member:`async_color_convert_request_t::src_color_format` 和 + :cpp:member:`async_color_convert_request_t::dst_color_format`。 + 当在 RGB 和 YUV 之间转换时,还需要设置 :cpp:member:`async_color_convert_request_t::color_conv_std`。 + +场景 2:使用异步接口和回调函数 +============================== + +理解了阻塞流程之后,下一步就是把请求排入队列,并在硬件完成后由中断上下文中的回调通知你。 + +.. code:: c + + static bool color_conv_done_cb(async_color_convert_handle_t conv_hdl, + async_color_convert_event_data_t *edata, + void *cb_args) + { + BaseType_t high_task_wakeup = pdFALSE; // FreeRTOS 在 ISR 中唤醒任务时需要这个变量 + SemaphoreHandle_t sem = (SemaphoreHandle_t)cb_args; // 提交请求时传进来的用户上下文 + // 用 ISR-safe 的方式通知等待中的任务:这次转换已经完成。 + xSemaphoreGiveFromISR(sem, &high_task_wakeup); + // 如果刚才唤醒了更高优先级任务,就请求在 ISR 退出后立刻切换过去。 + return high_task_wakeup == pdTRUE; + } + + async_color_convert_request_t req = { + .src_buffer = src_buf, // 源图像基地址 + .src_stride = src_width, // 源图像的行跨度,单位是像素 + .src_height = src_height, + .src_x = 0, + .src_y = 0, + .dst_buffer = dst_buf, // 目标图像基地址 + .dst_stride = dst_width, // 目标图像的行跨度,单位是像素 + .dst_height = dst_height, + .dst_x = 0, + .dst_y = 0, + .copy_width = copy_width, + .copy_height = copy_height, + .src_color_format = ESP_COLOR_FOURCC_RGB16, + .dst_color_format = ESP_COLOR_FOURCC_BGR24, + }; + + // 提交一个异步请求。函数返回时,硬件可能还在执行转换。 + ESP_ERROR_CHECK(esp_async_color_convert(conv_hdl, &req, color_conv_done_cb, sem)); + // 在任务上下文中等待回调释放信号量。 + xSemaphoreTake(sem, portMAX_DELAY); + +回调运行在 ISR 上下文中,因此应尽量保持简短,并且只调用 ISR-safe API,例如 ``xSemaphoreGiveFromISR`` 或 ``xQueueSendFromISR``。 + +运行注意事项 +============ + +驱动配置 +-------- + +驱动配置字段如下: + +- :cpp:member:`async_color_convert_config_t::backlog` + 最大待处理请求数。``0`` 表示使用驱动默认值。 +- :cpp:member:`async_color_convert_config_t::dma_burst_size` + DMA burst 大小,单位为字节。``0`` 表示使用驱动默认值。 +- :cpp:member:`async_color_convert_config_t::intr_priority` + DMA2D 中断优先级。``0`` 表示使用默认低/中优先级。 + +DMA 突发大小 +------------ + +``dma_burst_size`` 会影响 DMA 传输效率: + +- 较大的突发大小可能提高吞吐量 +- 较大的突发大小也可能增加总线占用,因此并不一定适合所有工作负载 +- 常见的起始取值有 16、32 和 64 字节 + +最佳取值取决于芯片的 DMA 控制器能力,以及系统中其他活跃组件对内存带宽的共享情况。 + +线程安全与 ISR 规则 +------------------- + +- 驱动是线程安全的。不同任务提交的请求会通过内部队列串行化。 +- :cpp:func:`esp_async_color_convert` 可以在任务上下文中调用,用于排队请求。 +- 回调类型 :cpp:type:`async_color_convert_isr_cb_t` 运行在 ISR 上下文中。 +- 不要在回调里调用阻塞 API。 +- :cpp:func:`esp_color_convert_blocking` 不能在 ISR 上下文中调用。 + +卸载驱动 +-------- + +当驱动不再需要时: + +.. code:: c + + // 只有在所有排队请求都完成后,才能安全卸载驱动。 + ESP_ERROR_CHECK(esp_async_color_convert_uninstall(conv_hdl)); + +如果仍有请求未完成,:cpp:func:`esp_async_color_convert_uninstall` 会返回 :c:macro:`ESP_ERR_INVALID_STATE`。 + +应用示例 +======== + +- :example:`peripherals/dma/async_color_convert` 展示了一个面向初学者的阻塞转换流程: + + - 从映射到 flash 的嵌入式 ``.yuv`` 图像直接读取输入 + - 使用 DMA2D 将图像从 UYVY422 转换为 BGR24 + - 将转换结果做 base64 编码后输出到控制台 + - 由 pytest 重建为 PNG 工件,并与 golden 参考图进行比对 + +API 参考 +======== + +异步颜色转换驱动程序函数 +------------------------ + +.. include-build-file:: inc/esp_async_color_convert.inc diff --git a/docs/zh_CN/api-reference/peripherals/i2s.rst b/docs/zh_CN/api-reference/peripherals/i2s.rst index 3d24d844570..aef17035695 100644 --- a/docs/zh_CN/api-reference/peripherals/i2s.rst +++ b/docs/zh_CN/api-reference/peripherals/i2s.rst @@ -75,6 +75,12 @@ I2S 时钟 通常,MCLK 应该同时是 ``采样率`` 和 BCLK 的倍数。字段 :cpp:member:`i2s_std_clk_config_t::mclk_multiple` 表示 MCLK 相对于 ``采样率`` 的倍数。在大多数情况下,将其设置为 ``I2S_MCLK_MULTIPLE_256`` 即可。但如果 ``slot_bit_width`` 被设置为 ``I2S_SLOT_BIT_WIDTH_24BIT``,为了保证 MCLK 是 BCLK 的整数倍,应该将 :cpp:member:`i2s_std_clk_config_t::mclk_multiple` 设置为能被 3 整除的倍数,如 ``I2S_MCLK_MULTIPLE_384``,否则 WS 会不精准。 +.. only:: esp32 + + .. note:: + + 在ESP32上,MCLK 管脚必须使用 GPIO0、GPIO1 或 GPIO3 管脚。其他的时钟管脚可以使用任意的 GPIO。注意,由于 GPIO0 为 Strapping 管脚,一般不推荐用作其他功能。 + .. _i2s-communication-mode: I2S 通信模式 @@ -256,7 +262,7 @@ I2S 驱动中的资源可分为三个级别: 电源管理启用(即开启 :ref:`CONFIG_PM_ENABLE`)时,系统将在进入 Light-sleep 前调整或停止 I2S 时钟源,这可能会影响 I2S 信号,从而导致传输或接收的数据无效。 -I2S 驱动可以获取电源管理锁,从而防止系统设置更改或时钟源被禁用。时钟源为 APB 时,锁的类型将被设置为 :cpp:enumerator:`esp_pm_lock_type_t::ESP_PM_APB_FREQ_MAX`。时钟源为 APLL(若支持)时,锁的类型将被设置为 :cpp:enumerator:`esp_pm_lock_type_t::ESP_PM_NO_LIGHT_SLEEP`。用户通过 I2S 读写时(即调用 :cpp:func:`i2s_channel_read` 或 :cpp:func:`i2s_channel_write`),驱动程序将获取电源管理锁,并在读写完成后释放锁。 +I2S 驱动可以获取电源管理锁,从而防止系统设置更改或时钟源被禁用。时钟源为 APB 时,锁的类型将被设置为 :cpp:enumerator:`esp_pm_lock_type_t::ESP_PM_APB_FREQ_MAX`。时钟源为 APLL(若支持)时,锁的类型将被设置为 :cpp:enumerator:`esp_pm_lock_type_t::ESP_PM_NO_LIGHT_SLEEP`。驱动程序将在调用 :cpp:func:`i2s_channel_enable` 启用通道时获取电源管理锁,并在调用 :cpp:func:`i2s_channel_disable` 禁用通道时释放锁,确保通道运行期间 I2S 时钟源保持稳定。 .. only:: SOC_I2S_SUPPORT_SLEEP_RETENTION diff --git a/docs/zh_CN/api-reference/peripherals/index.rst b/docs/zh_CN/api-reference/peripherals/index.rst index b57c7cfd400..ae93c728ede 100644 --- a/docs/zh_CN/api-reference/peripherals/index.rst +++ b/docs/zh_CN/api-reference/peripherals/index.rst @@ -8,6 +8,7 @@ :SOC_ADC_SUPPORTED: adc/index :SOC_ANA_CMPR_SUPPORTED: ana_cmpr + :SOC_DMA2D_SUPPORTED: async_color_convert :SOC_BITSCRAMBLER_SUPPORTED: bitscrambler :SOC_MIPI_CSI_SUPPORTED: camera_driver :SOC_CLK_TREE_SUPPORTED: clk_tree diff --git a/docs/zh_CN/api-reference/peripherals/jpeg.rst b/docs/zh_CN/api-reference/peripherals/jpeg.rst index 86b02af29ed..34d4ff1fd2e 100644 --- a/docs/zh_CN/api-reference/peripherals/jpeg.rst +++ b/docs/zh_CN/api-reference/peripherals/jpeg.rst @@ -178,40 +178,44 @@ JPEG 编码器引擎 - GRAY -可参考以下代码,为 1080*1920 大小的图片编码: +可参考以下代码,将一张嵌入到固件中的 1280x720 原始图片编码为 JPEG: .. code:: c - int raw_size_1080p = 0;/* Your raw image size */ + size_t raw_size_720p = EXAMPLE_WIDTH * EXAMPLE_HEIGHT * 3; /* 1280x720 bgr24 帧 */ jpeg_encode_cfg_t enc_config = { .src_type = JPEG_ENCODE_IN_FORMAT_RGB888, .sub_sample = JPEG_DOWN_SAMPLING_YUV422, .image_quality = 80, - .width = 1920, - .height = 1080, + .width = 1280, + .height = 720, .pixel_reverse = false, // 是否反转输入图像的像素顺序,或像素顺序细节请参考技术参考手册 }; - uint8_t *raw_buf_1080p = (uint8_t*)jpeg_alloc_encoder_mem(raw_size_1080p); - if (raw_buf_1080p == NULL) { - ESP_LOGE(TAG, "alloc 1080p tx buffer error"); - return; - } - uint8_t *jpg_buf_1080p = (uint8_t*)jpeg_alloc_encoder_mem(raw_size_1080p / 10); // Assume that compression ratio of 10 to 1 - if (jpg_buf_1080p == NULL) { - ESP_LOGE(TAG, "alloc jpg_buf_1080p error"); + jpeg_encode_memory_alloc_cfg_t rx_mem_cfg = { + .buffer_direction = JPEG_ENC_ALLOC_OUTPUT_BUFFER, + }; + size_t jpg_buffer_size = 0; + uint8_t *jpg_buf_720p = (uint8_t*)jpeg_alloc_encoder_mem(raw_size_720p / 10, &rx_mem_cfg, &jpg_buffer_size); + if (jpg_buf_720p == NULL) { + ESP_LOGE(TAG, "alloc jpg_buf_720p error"); return; } - ESP_ERROR_CHECK(jpeg_encoder_process(jpeg_handle, &enc_config, raw_buf_1080p, raw_size_1080p, jpg_buf_1080p, &jpg_size_1080p);); + /* 当前 JPEG 编码输入路径下,JPEG_ENCODE_IN_FORMAT_RGB888 实际要求 + * 原始字节按 BGR24 风格排列。只要数据在本次调用返回前保持可读, + * 就可以直接从 flash 中映射出来的嵌入资源读取。 */ + ESP_ERROR_CHECK(jpeg_encoder_process(jpeg_handle, &enc_config, embedded_bgr24_start, raw_size_720p, jpg_buf_720p, jpg_buffer_size, &jpg_size_720p)); 参考以下提示,可以更准确地使用该驱动程序: -1. 在上述代码中,应调用 :cpp:func:`jpeg_alloc_encoder_mem` 函数,确保 `raw_buf_1080p` 和 `jpg_buf_1080p` 对齐。 +1. 在上述代码中,应调用 :cpp:func:`jpeg_alloc_encoder_mem` 函数来分配 `jpg_buf_720p`,因为 JPEG 输出码流缓冲区需要满足驱动的对齐要求。 -2. 在 :cpp:func:`jpeg_encoder_process` 返回前, `raw_buf_1080p` 缓冲区的内容不应有更改。 +2. 在 :cpp:func:`jpeg_encoder_process` 返回前, `embedded_bgr24_start` 所指向的输入内容不应有更改。该输入缓冲区既可以来自嵌入到 flash 中的映射资源,也可以来自其他在整个调用期间保持可读的内存区域。 -3. 压缩比取决于所选择的 `image_quality` 和图像本身的内容。一般来说, `image_quality` 值越高,图像质量越好,相应的压缩比就越小。至于图像内容,则很难给出具体的指导方针,因此本文也就不再讨论。基准 JPEG 压缩比通常从 40:1 到 10:1 不等,请依实际情况而定。 +3. 对于 :cpp:enumerator:`JPEG_ENCODE_IN_FORMAT_RGB888`,当前驱动实际要求原始输入字节按 BGR24 风格排列。如果直接提供 RGB24 原始数据,则编码后的 JPEG 会出现红蓝通道互换。 + +4. 压缩比取决于所选择的 `image_quality` 和图像本身的内容。一般来说, `image_quality` 值越高,图像质量越好,相应的压缩比就越小。至于图像内容,则很难给出具体的指导方针,因此本文也就不再讨论。基准 JPEG 压缩比通常从 40:1 到 10:1 不等,请依实际情况而定。 性能概述 ^^^^^^^^ @@ -453,7 +457,7 @@ Kconfig 选项 - :example:`peripherals/jpeg/jpeg_decode` 演示了如何使用 JPEG 硬件解码器将不同大小的 JPEG 图片(1080p 和 720p)解码为 RGB 格式,展示了硬件解码的速度和灵活性。 -- :example:`peripherals/jpeg/jpeg_encode` 演示了如何使用 JPEG 硬件编码器编码一张 1080p 的图像,即将 `*.rgb` 文件转换为 `*.jpg` 文件。 +- :example:`peripherals/jpeg/jpeg_encode` 演示了如何使用 JPEG 硬件编码器对一张嵌入式 720p 原始图像进行编码,并通过 UART 输出 base64 JPEG,再用 pytest 做结果校验。 API 参考 diff --git a/docs/zh_CN/api-reference/peripherals/lcd/i2c_lcd.rst b/docs/zh_CN/api-reference/peripherals/lcd/i2c_lcd.rst index d21ae7b0875..d2132018dcc 100644 --- a/docs/zh_CN/api-reference/peripherals/lcd/i2c_lcd.rst +++ b/docs/zh_CN/api-reference/peripherals/lcd/i2c_lcd.rst @@ -23,6 +23,7 @@ I2C 接口的 LCD - :cpp:member:`esp_lcd_panel_io_i2c_config_t::dev_addr` 设置 LCD 控制器芯片的 I2C 设备地址。LCD 驱动程序使用此地址与 LCD 控制器芯片通信。 - :cpp:member:`esp_lcd_panel_io_i2c_config_t::scl_speed_hz` 设置 I2C 时钟频率 (Hz)。该值不应超过 LCD 规格书中推荐的范围。 - :cpp:member:`esp_lcd_panel_io_i2c_config_t::lcd_cmd_bits` 和 :cpp:member:`esp_lcd_panel_io_i2c_config_t::lcd_param_bits` 分别设置 LCD 控制器芯片可识别的命令及参数的位宽。不同芯片对位宽要求不同,请提前参阅 LCD 规格书。 + - :cpp:member:`esp_lcd_panel_io_i2c_config_t::transaction_timeout_ms` 设置每次底层 I2C 传输的超时时间(毫秒)。设为 0 或 -1 时表示无限等待;如指定为正值,则面板 IO 相关调用会在超时后返回 ``ESP_ERR_TIMEOUT``,适用于共享总线或总线可能被从设备挂死的场景。 .. code-block:: c diff --git a/docs/zh_CN/api-reference/peripherals/sdio_slave.rst b/docs/zh_CN/api-reference/peripherals/sdio_slave.rst index 91f50ccceda..bfbd53b4907 100644 --- a/docs/zh_CN/api-reference/peripherals/sdio_slave.rst +++ b/docs/zh_CN/api-reference/peripherals/sdio_slave.rst @@ -223,7 +223,7 @@ SDIO 从机驱动程序的相关术语如下: 为减少复制数据的开销,驱动程序本身没有内部缓冲区,DMA 直接从应用程序提供的缓冲区中获取数据。发送完成前,应用程序不应该访问缓冲区,以确保数据传输的正确性。 -结构体 ``sdio_slave_config_t`` 中的 ``sending_mode`` 可以设置发送模式,``send_queue_size`` 可以设置缓冲区数量。缓冲区大小均限制在 4092 字节内。尽管在流模式下,一次传输可以发送多个缓冲区,但每个缓冲区在队列中仍然计为一个。 +结构体 ``sdio_slave_config_t`` 中的 ``sending_mode`` 可以设置发送模式,``send_queue_size`` 可以设置缓冲区数量。每个缓冲区的大小都受单个 SDIO slave DMA 描述符可支持的最大长度限制,且该限制因芯片而异。尽管在流模式下,一次传输可以发送多个缓冲区,但每个缓冲区在队列中仍然计为一个。 应用程序可以调用 ``sdio_slave_transmit`` 函数发送数据包。此时,函数在传输完成后返回,因此队列并未完全占用。若需要更高效率,应用程序可以改用以下函数: diff --git a/docs/zh_CN/api-reference/peripherals/twai.rst b/docs/zh_CN/api-reference/peripherals/twai.rst index 30c4266471a..db519daa353 100644 --- a/docs/zh_CN/api-reference/peripherals/twai.rst +++ b/docs/zh_CN/api-reference/peripherals/twai.rst @@ -134,6 +134,7 @@ TWAI 报文有多种类型,由报头指定。一个典型的数据帧报文主 - :cpp:member:`twai_frame_t::header::fdf` 报文为 FD 格式,支持最大数据长度 64 字节。 - :cpp:member:`twai_frame_t::header::brs` 发送报文时在数据段使用独立的波特率。 - :cpp:member:`twai_frame_t::header::esi` 对于收到的报文,指示发送节点的错误状态。 +- :cpp:member:`twai_frame_t::tx_queue_priority` 本地发送队列优先级,详情请参阅 `发送队列优先级`_。 接收报文 -------- @@ -214,6 +215,13 @@ TWAI 驱动支持在中断服务程序 (ISR) 中发送报文。这对于需要 .. note:: 在 ISR 中调用 :cpp:func:`twai_node_transmit` 时,``timeout`` 参数将被忽略,函数不会阻塞。如果发送队列已满,函数将立即返回错误。应用程序需要自行处理队列已满的情况。同样,``twai_frame_t`` 及其 ``buffer`` 指向的内存必须在 **该传输** 完成之前保持有效。通过 :cpp:member:`twai_tx_done_event_data_t::done_tx_frame` 指针可得知该次完成的报文。 +发送队列优先级 +-------------- + +TWAI 驱动支持通过 :cpp:member:`twai_frame_t::tx_queue_priority` 设置本地发送队列优先级。当驱动发送队列中有多个待发送报文时,``tx_queue_priority`` 值更高的报文会优先出队开始发送。优先级相同的报文保持入队顺序发送。 + +该优先级只影响驱动的本地发送队列,不会被发送到 TWAI 总线上,也不会替代 TWAI 总线仲裁。若控制器有多个硬件发送缓存(例如 esp32c5 的 4 个硬件发送缓存),已经缓存的报文也不会被新入队的高优先级报文抢占。报文到达总线后,仲裁仍由帧 ID 决定,ID 越小,总线优先级越高。控制器已经开始发送的报文不会被新入队的高优先级报文抢占。 + 位时序自定义 ------------- diff --git a/docs/zh_CN/api-reference/peripherals/uhci.rst b/docs/zh_CN/api-reference/peripherals/uhci.rst index 6a7b5f62826..baabbc390d8 100644 --- a/docs/zh_CN/api-reference/peripherals/uhci.rst +++ b/docs/zh_CN/api-reference/peripherals/uhci.rst @@ -87,16 +87,20 @@ TX 事件数据在 :cpp:type:`uhci_tx_done_event_data_t` 中定义: RX 事件数据在 :cpp:type:`uhci_rx_event_data_t` 中定义: -- :cpp:member:`uhci_rx_event_data_t::data` 指向接收到的数据。数据保存在 :cpp:func:`uhci_receive` 函数的 ``buffer`` 参数中。用户在回调返回之前不应释放此接收缓冲区。 +- :cpp:member:`uhci_rx_event_data_t::data` 指向接收到的数据。数据保存在 :cpp:func:`uhci_receive` 函数 ``buffer`` 参数指定的缓冲区中,因此用户在回调返回之前不应释放此接收缓冲区。``edata->data`` 所指向的数据通常仅保证在回调期间可读。若回调返回后仍需使用该数据,请先拷贝到外部缓冲区。 - :cpp:member:`uhci_rx_event_data_t::recv_size` 表示接收到的数据大小。此值不会大于 :cpp:func:`uhci_receive` 函数的 ``buffer_size`` 参数。 - :cpp:member:`uhci_rx_event_data_t::flags::totally_received` 指示当前接收缓冲区是否是事务中的最后一个。 +.. note:: + + 如果希望不拷贝而在回调外继续使用 ``edata->data`` (例如把指针通过队列传给任务处理),属于高级零拷贝用法。用户需要理解底层 DMA 环形缓冲区的分块和覆盖行为,并保证消费者处理速度快于覆盖速度。 + 启动 UHCI 传输 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ :cpp:func:`uhci_transmit` 是一个非阻塞函数,这意味着在调用后会立即返回。您可以通过 :cpp:member:`uhci_event_callbacks_t::on_tx_trans_done` 相关回调指示事务完成。我们还提供了一个函数 :cpp:func:`uhci_wait_all_tx_transaction_done` 来阻塞线程,等待所有事务完成。 -以下代码显示了如何通过 UHCI 接收数据: +以下代码显示了如何通过 UHCI 传输数据: .. code:: c @@ -111,9 +115,9 @@ RX 事件数据在 :cpp:type:`uhci_rx_event_data_t` 中定义: 启动 UHCI 接收 ^^^^^^^^^^^^^^^^^^^^^^^^^ -:cpp:func:`uhci_receive` 是一个非阻塞函数,这意味着该函数在调用后会立即返回。用户可以通过 :cpp:member:`uhci_rx_event_data_t::recv_size` 获取相关的回调,以指示接收事件并判断事务是否完成。 +:cpp:func:`uhci_receive` 是一个非阻塞函数,这意味着该函数在调用后会立即返回。用户可以通过 :cpp:member:`uhci_event_callbacks_t::on_rx_trans_event` 获取相关的回调,以指示接收事件并判断事务是否完成。 -以下代码展示了如何通过 UHCI 传输数据: +以下代码展示了如何通过 UHCI 接收数据: .. code:: c @@ -146,13 +150,13 @@ RX 事件数据在 :cpp:type:`uhci_rx_event_data_t` 中定义: .on_rx_trans_event = s_uhci_rx_event_cbs, }; - // 注册回调,并开始启动回收 + // 注册回调,并启动接收 ESP_ERROR_CHECK(uhci_register_event_callbacks(uhci_ctrl, &uhci_cbs, ctx)); ESP_ERROR_CHECK(uhci_receive(uhci_ctrl, pdata, 100)); uhci_event_t evt; while (1) { - // 一个在任务中的队列用来接收 UHCI 抛出的事件 + // 在任务中,队列用于接收 UHCI 抛出的事件 if (xQueueReceive(uhci_queue, &evt, portMAX_DELAY) == pdTRUE) { if (evt == UHCI_EVT_EOF) { printf("Received size: %d\n", ctx->receive_size); @@ -199,7 +203,7 @@ RX 事件数据在 :cpp:type:`uhci_rx_event_data_t` 中定义: 通过启用 Kconfig 选项 :ref:`CONFIG_UHCI_ISR_CACHE_SAFE`,可实现以下功能: -1. 即使缓存被禁用,中断也能被服务。 +1. 即使缓存被禁用,中断也能被及时处理。 2. 将 ISR 使用的所有函数放入 IRAM [1]_ 3. 将驱动对象放入 DRAM,防止其意外映射到 PSRAM。 diff --git a/docs/zh_CN/api-reference/storage/blockdev.rst b/docs/zh_CN/api-reference/storage/blockdev.rst new file mode 100644 index 00000000000..e537ce770e6 --- /dev/null +++ b/docs/zh_CN/api-reference/storage/blockdev.rst @@ -0,0 +1,163 @@ +块设备层 +======== + +:link_to_translation:`en:[English]` + +概述 +---- + +块设备层 (BDL) 定义了一套 C 语言接口,使面向存储的组件可在无需专用适配器的情况下完成数据交换。每个块设备以 :cpp:type:`esp_blockdev_handle_t` 句柄的形式对外提供该接口,通过该句柄可访问设备标志、几何信息以及 ``components/esp_blockdev/include/esp_blockdev.h`` 中定义的一组受支持操作。上层代码可读取这些元数据,并调用可用的回调执行 I/O 操作。 + +统一的接口支持串联多个通用组件,灵活组合成可适配各类存储场景的 BDL 协议栈。驱动程序提供用于访问物理设备的句柄;中间件组件使用该句柄并扩展其能力(如划分设备空间、添加磨损均衡功能),同时向下一层暴露新的句柄。协议栈最顶层的组件(如文件系统)是纯设备使用者。只要每一层均遵循下文约定的接口规范,该模型即可实现文件系统、中间件与物理驱动的任意组合使用。 + +.. blockdiag:: + :caption: 块设备层堆栈示例 + :align: center + + blockdiag blockdev-stack { + default_fontsize = 14; + node_height = 60; + orientation = portrait; + default_group_color = none; + + nvs [label = "NVS\n(nvs_flash)"]; + fatfs [label = "FATFS\n(fatfs)"]; + littlefs [label = "LittleFS\n(esp_littlefs)"]; + consumer [label = "Block device\nconsumers", shape = ellipse]; + + wl [label = "Wear Levelling\n(wear_levelling)"]; + middleware_1 [label = "Block device\nmiddleware", shape = ellipse]; + + nvs_part [label = "NVS Partition\n(esp_partition)"]; + littlefs_part [label = "LittleFS Partition\n(esp_partition)"]; + fat_part [label = "FAT Partition\n(esp_partition)"]; + middleware_2 [label = "Block device\nmiddleware", shape = ellipse]; + + spi [label = "Flash\n(spi_flash)"]; + provider [label = "Block device\nprovider", shape = ellipse]; + + d1 [shape = none, width = 1, height = 1]; + d2 [shape = none, width = 1, height = 1]; + d3 [shape = none, width = 1, height = 1]; + + nvs -> nvs_part -> spi; + fatfs -> wl -> fat_part -> spi; + littlefs -> littlefs_part -> spi; + + consumer -> middleware_1 -> middleware_2 -> provider; + + group { orientation = landscape; fatfs; littlefs; nvs; } + group { orientation = landscape; wl; } + group { orientation = landscape; fat_part; littlefs_part; nvs_part; } + group { orientation = landscape; d3; spi; } + } + + +使用块设备 +---------- + +句柄 +^^^^ + +块设备通过 :cpp:type:`esp_blockdev_handle_t` 进行访问。可以从对应的组件获取该句柄,遵循 ``_get_blockdev()`` 的命名约定;当设备不再使用时,必须调用对应的 ``_release_blockdev()`` 辅助函数来释放该句柄。注意,应该将句柄视为黑盒对象:只能通过 ``components/esp_blockdev/include/esp_blockdev.h`` 中提供的公开 API 来使用,不要移动或修改其所引用的内存。 + +几何与标志 +^^^^^^^^^^ + +每个设备会发布一个 :cpp:type:`esp_blockdev_geometry_t` 结构体,用于报告容量以及最小读、写、擦除粒度。可选的推荐大小可作为性能提示,但不能替代针对强制取值的对齐检查。配套的 :cpp:type:`esp_blockdev_flags_t` 结构体声明只读介质、加密或先擦后写等属性。中间件可以改变表观几何大小,但在创建时必须验证底层是否满足其要求,并确保后续对底层设备的访问始终符合其约束。 + +操作 +^^^^ + +:cpp:type:`esp_blockdev_ops_t` 结构体定义读、写、擦除、同步、ioctl 与释放等回调。在调用回调之前,调用方必须确保对应函数指针非 ``NULL``; ``NULL`` 表示不支持该操作。调用方有责任根据几何数据校验对齐与边界,并遵守由标识位指定的约束要求(例如在类 NAND 介质上写之前先执行擦除)。 + +典型流程 +^^^^^^^^ + +1. 从驱动程序或中间件提供商处获取一个句柄。 +2. 检查几何结构和标志,以确定所需的对齐方式、可用容量和特殊处理方式。 +3. 通过提供商公开的操作表发出读取、写入、擦除和同步请求。 +4. 将句柄转发给上层的组件,或者在所有操作完成后释放该句柄。 + +示例 +^^^^ + +.. code-block:: c + + esp_blockdev_handle_t dev = my_component_get_blockdev(); + const esp_blockdev_geometry_t *geometry = dev->geometry; + if (dev->ops->read && (sizeof(buffer) % geometry->read_size) == 0) { + ESP_ERROR_CHECK(dev->ops->read(dev, buffer, sizeof(buffer), 0, sizeof(buffer))); + } + if (dev->ops->release) { + ESP_ERROR_CHECK(dev->ops->release(dev)); + } + +约定 +---- + +标志(:cpp:type:`esp_blockdev_flags_t`) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +* 标志在设备创建时初始化一次,且在句柄整个生命周期内必须保持不变。 +* ``read_only`` 要求写、擦除及会改变状态的 ioctl 命令失败,并返回诸如 :c:macro:`ESP_ERR_INVALID_STATE` 的错误。 +* ``encrypted`` 表示介质上的数据已加密;上层不得假定可见明文或透明映射。 +* ``erase_before_write`` 告知调用方:成功写入前必须先擦除目标范围。若在未插入擦除操作的情况下对同一范围多次写入,行为未定义,但很可能导致数据损坏。 +* ``and_type_write`` 表示 NAND/NOR 风格行为:编程仅将位清为 0(1→0),实际存储 ``existing_bits & new_bits``。已为 0 的位在写请求写入 1 时仍保持为 0;只有先擦除才能恢复为 1。 +* ``default_val_after_erase`` 标识擦除后区域读回为 ``0x00`` 还是 ``0xFF``,以便中间件保持哨兵值一致。 + +几何参数 (:cpp:type:`esp_blockdev_geometry_t`) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +* ``disk_size`` 为可访问的总容量(字节);任何结束偏移量超出该值的请求都必须被拒绝。 +* ``read_size``、 ``write_size`` 与 ``erase_size`` 为强制对齐单位(字节);在执行操作前,偏移与长度都必须和相应的大小对齐。 +* 调用方遵循推荐大小时可提高吞吐量,但不能替代最低对齐检查;实现方必须接受所有符合强制粒度要求的请求。 +* 当用户看到同一底层设备的可读写与只读两种变体时,除 ``read_only`` 标志外,两者的几何参数必须相同。特别是 ``read_size``、 ``write_size`` 与 ``erase_size`` 应保持一致; ``recommended_*`` 也应该保持一致,除非有特别的需要(此类情况应在文档中说明)。 + +操作 (:cpp:type:`esp_blockdev_ops_t`) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +``read(dev_handle, dst_buf, dst_buf_size, src_addr, data_read_len)`` + * 行为:成功时,将恰好 ``data_read_len`` 字节数据复制到 ``dst_buf``。 + * 前置条件: ``dst_buf`` 有效, ``src_addr`` 与 ``data_read_len`` 按 ``read_size`` 对齐, ``src_addr + data_read_len <= disk_size``;且 ``data_read_len <= dst_buf_size``。 + * 后置条件:复制成功则返回 :c:macro:`ESP_OK`,失败则透传相应的 ``ESP_ERR_*`` 错误码。 + +``write(dev_handle, src_buf, dst_addr, data_write_len)`` + * 前置条件:设备非 ``read_only``; ``src_buf`` 至少覆盖 ``data_write_len`` 字节;偏移与长度按 ``write_size`` 对齐且在 ``disk_size`` 范围内。 + * 行为:若设置了 ``erase_before_write``,调用方必须先执行 ``erase``。若设置了 ``and_type_write``,硬件会将新内容与现有内容进行按位与运算 (AND),存储结果变为 ``old_value & new_value``;除非先擦除该范围,否则先前写入操作清除的位将保持清除状态。 + * 后置条件:设备接受请求的范围后,将返回 :c:macro:`ESP_OK` (在 ``sync`` 运行之前,数据可能仍驻留在中间缓冲区中)。对齐错误、超出范围或只读尝试应会显现 :c:macro:`ESP_ERR_INVALID_ARG` 或 :c:macro:`ESP_ERR_INVALID_STATE`,且实现方必须避免使该范围处于部分更新状态。 + * 说明:在具有 ``and_type_write`` 的设备上,写操作依赖于现有内容,因此在写之后立即读取相同(或重叠)范围时,可能需要先执行 ``sync``,以确保缓存数据反映完全合并后的值。 + +``erase(dev_handle, start_addr, erase_len)`` + * 前置条件:设备允许擦除; ``start_addr`` 与 ``erase_len`` 按 ``erase_size`` 对齐;范围在 ``disk_size`` 内。 + * 后置条件:成功时该范围读回为 ``default_val_after_erase``。未对齐或越界请求应返回 :c:macro:`ESP_ERR_INVALID_ARG`;硬件故障应通过驱动相关的 ``ESP_ERR_*`` 码向上传递。 + +``sync(dev_handle)`` + * 刷新挂起的写入操作。省略此回调的设备将采用直写语义运行。 + * 后置条件:在返回 :c:macro:`ESP_OK` 之前,所有先前已报告的写操作均到达稳定存储(包括所有底层设备)。超时或传输问题应表现为 :c:macro:`ESP_ERR_TIMEOUT` 或其他相关 ``ESP_ERR_*``。 + +``ioctl(dev_handle, cmd, args)`` + * 命令标识 ``0x00–0x7F`` 保留给 ESP-IDF 系统使用; `0x80–0xFF` 可供用户自定义扩展。 + * 每个命令定义各自的载荷布局;由于 ``args`` 为 ``void *`` 类型,封装层只能对其理解的命令校验或重新解释缓冲区,否则必须将载荷视为不透明数据。 + * 无法处理某命令的封装层应在合适时机将其原样转发给协议栈中的下一设备;仅最底层设备应对无法识别的命令返回 :c:macro:`ESP_ERR_NOT_SUPPORTED`。 + * 当协议栈存在非透明地址映射时,转发嵌入原始地址的命令本质上不安全:中间层无法翻译不透明载荷,因此行为未定义且通常会失败。此类命令应被显式拦截,或在堆叠配置中注明为不支持。 + +``release(dev_handle)`` + * 可选的析构函数,用于释放设备资源。该函数必须具有幂等性,即确保重复调用时要么成功,要么返回诸如 :c:macro:`ESP_ERR_INVALID_STATE` 等无害错误。 + +错误处理 +^^^^^^^^ + +回调在成功时返回 :c:macro:`ESP_OK`,并应原样透传 ``ESP_ERR_*`` 错误码以帮助调用方诊断错误。中间件与应用程序须透传底层设备的错误,而不是在协议栈内掩盖这些错误。 ``NULL`` 函数指针视为“不支持该操作”。 + +验证 +^^^^ + +实现方应包含覆盖对齐检查、由标志驱动的行为(只读、先擦后写、NAND 风格写)以及错误在堆叠设备间正确透传等测试。封装较低层句柄的中间件还必须验证句柄生命周期管理在整个协议栈中保持一致。 + +.. _blockdev-apis: + +API 参考 +-------- + +.. include-build-file:: inc/esp_blockdev.inc diff --git a/docs/zh_CN/api-reference/storage/index.rst b/docs/zh_CN/api-reference/storage/index.rst index 31457506683..f36ac271c67 100644 --- a/docs/zh_CN/api-reference/storage/index.rst +++ b/docs/zh_CN/api-reference/storage/index.rst @@ -8,6 +8,7 @@ - :doc:`分区表 API ` 基于 :doc:`/api-guides/partition-tables` ,允许以块为单位访问 SPI flash。 - :doc:`非易失性存储库 (NVS) ` 在 SPI NOR flash 上实现了一个有容错性,和磨损均衡功能的键值对存储。 - :doc:`虚拟文件系统 (VFS) ` 库提供了一个用于注册文件系统驱动的接口。SPIFFS、FAT 以及多种其他的文件系统库都基于 VFS。 +- :doc:`块设备层 ` 定义了一个通用的块设备抽象,使得存储驱动、中间件和文件系统可以互操作,而不需要专门的适配器。 - :doc:`SPIFFS ` 是一个专为 SPI NOR flash 优化的磨损均衡的文件系统,非常适用于小分区和低吞吐率的应用。 - :doc:`FAT ` 是一个可用于 SPI flash 或者 SD/MMC 存储卡的标准文件系统。 - :doc:`磨损均衡 ` 库实现了一个适用于 SPI NOR flash 的 flash 翻译层 (FTL),用于 flash 中 FAT 分区的容器。 @@ -33,6 +34,7 @@ nvs_partition_parse.rst sdmmc partition + blockdev spiffs vfs wear-levelling diff --git a/docs/zh_CN/api-reference/system/power_management.rst b/docs/zh_CN/api-reference/system/power_management.rst index cc92a277b70..5bbc6ce6e84 100644 --- a/docs/zh_CN/api-reference/system/power_management.rst +++ b/docs/zh_CN/api-reference/system/power_management.rst @@ -48,10 +48,14 @@ ESP-IDF 中集成的电源管理算法可以根据应用程序组件的需求, Light-sleep 状态下,外设设有时钟门控,不会产生来自 GPIO 和内部外设的中断。:doc:`sleep_modes` 文档中所提到的唤醒源可用于从 Light-sleep 状态触发唤醒。 -.. only:: SOC_PM_SUPPORT_EXT0_WAKEUP or SOC_PM_SUPPORT_EXT1_WAKEUP +.. only:: SOC_PM_SUPPORT_EXT0_WAKEUP and SOC_PM_SUPPORT_EXT1_WAKEUP 例如,EXT0 和 EXT1 唤醒源可以通过 GPIO 唤醒芯片。 +.. only:: SOC_PM_SUPPORT_EXT1_WAKEUP and not SOC_PM_SUPPORT_EXT0_WAKEUP + + 例如,EXT1 唤醒源可以通过 GPIO 唤醒芯片。 + 电源管理锁 ---------------------- diff --git a/docs/zh_CN/api-reference/system/sleep_modes.rst b/docs/zh_CN/api-reference/system/sleep_modes.rst index d12de5aafd0..e9c50a540ab 100644 --- a/docs/zh_CN/api-reference/system/sleep_modes.rst +++ b/docs/zh_CN/api-reference/system/sleep_modes.rst @@ -4,6 +4,7 @@ :link_to_translation:`en:[English]` {IDF_TARGET_SPI_POWER_DOMAIN:default="VDD_SPI", esp32="VDD_SDIO"} +{IDF_TARGET_RTC_POWER_DOMAIN:default="VDD3P3_RTC", esp32c5="VDDPST1", esp32c6="VDDPST1", esp32c61="VDDPST1", esp32p4="VDD_LP"} 概述 -------- @@ -217,6 +218,8 @@ RTC 控制器中内嵌定时器,可用于在预定义的时间到达后唤醒 .. only:: SOC_PM_SUPPORT_EXT1_WAKEUP + .. _sleep-ext1-wakeup: + 外部唤醒 (``ext1``) ^^^^^^^^^^^^^^^^^^^^^^ @@ -232,7 +235,7 @@ RTC 控制器中内嵌定时器,可用于在预定义的时间到达后唤醒 - 当任意一个所选管脚为高电平时唤醒 (ESP_EXT1_WAKEUP_ANY_HIGH) - 当任意一个所选管脚为低电平时唤醒 (ESP_EXT1_WAKEUP_ANY_LOW) - 此唤醒源由 RTC 控制器实现。区别于 ``ext0`` 唤醒源,在 RTC 外设断电的情况下此唤醒源同样支持唤醒。虽然睡眠期间 RTC IO 所在的 RTC 外设电源域将会断电,但是 ESP-IDF 会自动在系统进入睡眠前锁定唤醒管脚的状态并在退出睡眠时解除锁定,所以仍然可为唤醒管脚配置内部上拉或下拉电阻:: + 此唤醒源由 RTC 控制器实现。即使在 RTC 外设断电的情况下仍支持唤醒。虽然睡眠期间 RTC IO 所在的 RTC 外设电源域将会断电,但是 ESP-IDF 会自动在系统进入睡眠前锁定唤醒管脚的状态并在退出睡眠时解除锁定,所以仍然可为唤醒管脚配置内部上拉或下拉电阻:: esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON); gpio_pullup_dis(gpio_num); @@ -286,10 +289,14 @@ RTC 控制器中内嵌定时器,可用于在预定义的时间到达后唤醒 GPIO 唤醒(仅适用于 Light-sleep 模式) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - .. only:: SOC_PM_SUPPORT_EXT0_WAKEUP or SOC_PM_SUPPORT_EXT1_WAKEUP + .. only:: SOC_PM_SUPPORT_EXT0_WAKEUP and SOC_PM_SUPPORT_EXT1_WAKEUP 除了上述 EXT0 和 EXT1 唤醒源之外,还有一种从外部唤醒 Light-sleep 模式的方法——使用函数 :cpp:func:`gpio_wakeup_enable`。启用该唤醒源后,可将每个管脚单独配置为在高电平或低电平时唤醒。EXT0 和 EXT1 唤醒源只能用于 RTC IO,但此唤醒源既可以用于 RTC IO,可也用于数字 IO。 + .. only:: SOC_PM_SUPPORT_EXT1_WAKEUP and not SOC_PM_SUPPORT_EXT0_WAKEUP + + 除了上述 EXT1 唤醒源之外,还有一种从外部唤醒 Light-sleep 模式的方法——使用函数 :cpp:func:`gpio_wakeup_enable`。启用该唤醒源后,可将每个管脚单独配置为在高电平或低电平时唤醒。EXT1 唤醒源只能用于 RTC IO,但此唤醒源既可以用于 RTC IO,可也用于数字 IO。 + .. only:: not (SOC_PM_SUPPORT_EXT0_WAKEUP or SOC_PM_SUPPORT_EXT1_WAKEUP) 此外,还有一种从外部唤醒 Light-sleep 模式的方法。启用该唤醒源后,可将每个管脚单独配置为在高电平或低电平时调用 :cpp:func:`gpio_wakeup_enable` 函数触发唤醒。此唤醒源既可以用于 RTC IO,可也用于数字 IO。 @@ -323,24 +330,43 @@ RTC 控制器中内嵌定时器,可用于在预定义的时间到达后唤醒 GPIO 唤醒 ^^^^^^^^^^^ - 有两种 GPIO 唤醒 API 可供使用,分别适用于不同的睡眠场景: + .. only:: SOC_GPIO_SUPPORT_HP_PERIPH_PD_SLEEP_WAKEUP + + 有两种 GPIO 唤醒 API 可供使用,分别适用于不同的睡眠场景: + + .. only:: not SOC_GPIO_SUPPORT_HP_PERIPH_PD_SLEEP_WAKEUP + + 在 {IDF_TARGET_NAME} 上,可使用 :cpp:func:`esp_sleep_enable_gpio_wakeup` 与 :cpp:func:`gpio_wakeup_enable` 从 Light-sleep 唤醒芯片。 + + .. only:: SOC_PM_SUPPORT_EXT1_WAKEUP + + 若需使用 RTC GPIO 从 Deep-sleep 唤醒,请使用 EXT1 唤醒(:cpp:func:`esp_sleep_enable_ext1_wakeup_io`);参见 :ref:`sleep-ext1-wakeup`。 **1. :cpp:func:`esp_sleep_enable_gpio_wakeup` - 适用于 Light-sleep(GPIO 模块保持上电)** 当 GPIO 模块在睡眠期间保持上电时,任何 IO 都可以用作外部输入管脚,将芯片从 Light-sleep 状态唤醒。调用 :cpp:func:`gpio_wakeup_enable` 函数可以将任意管脚单独配置为在高电平或低电平触发唤醒。此后,应调用 :cpp:func:`esp_sleep_enable_gpio_wakeup` 函数来启用此唤醒源。 - .. note:: - 当启用 :ref:`CONFIG_PM_POWER_DOWN_PERIPHERAL_IN_LIGHT_SLEEP` 时,此 API **不可用**,因为 GPIO 模块在睡眠期间会被断电。请使用 :cpp:func:`esp_sleep_enable_gpio_wakeup_on_hp_periph_powerdown` 替代。 + .. only:: SOC_GPIO_SUPPORT_HP_PERIPH_PD_SLEEP_WAKEUP - **2. :cpp:func:`esp_sleep_enable_gpio_wakeup_on_hp_periph_powerdown` - 适用于 Deep-sleep 和外设掉电的 Light-sleep** + .. note:: + 当启用 :ref:`CONFIG_PM_POWER_DOWN_PERIPHERAL_IN_LIGHT_SLEEP` 时,此 API **不可用**,因为 GPIO 模块在睡眠期间会被断电。请使用 :cpp:func:`esp_sleep_enable_gpio_wakeup_on_hp_periph_powerdown` 替代。 - 可将由 VDD3P3_RTC 电源域供电的 IO 用于芯片的 Deep-sleep 唤醒,或在外设电源域掉电时的 Light-sleep 唤醒。调用 :cpp:func:`esp_sleep_enable_gpio_wakeup_on_hp_periph_powerdown` 函数可以配置相应的唤醒管脚和唤醒触发电平。此函数适用于: + .. only:: not SOC_GPIO_SUPPORT_HP_PERIPH_PD_SLEEP_WAKEUP - - Deep-sleep 模式(始终可用) - - 启用 :ref:`CONFIG_PM_POWER_DOWN_PERIPHERAL_IN_LIGHT_SLEEP` 时的 Light-sleep 模式 + .. note:: + 当启用 :ref:`CONFIG_PM_POWER_DOWN_PERIPHERAL_IN_LIGHT_SLEEP` 时,若仍要使用 :cpp:func:`gpio_wakeup_enable`,请先调用 :cpp:func:`rtc_gpio_init` 与 :cpp:func:`rtc_gpio_set_direction`,将管脚配置为 RTC GPIO 输入。 - .. note:: - 只有由 VDD3P3_RTC 电源域供电的 GPIO(RTC IO)可以与此 API 一起使用。具体支持的管脚请参考 `datasheet <{IDF_TARGET_DATASHEET_CN_URL}>`__ > IO 管脚。 + .. only:: SOC_GPIO_SUPPORT_HP_PERIPH_PD_SLEEP_WAKEUP + + **2. :cpp:func:`esp_sleep_enable_gpio_wakeup_on_hp_periph_powerdown` - 适用于 Deep-sleep 和外设掉电的 Light-sleep** + + 可将由 VDD3P3_RTC 电源域供电的 IO 用于芯片的 Deep-sleep 唤醒,或在外设电源域掉电时的 Light-sleep 唤醒。调用 :cpp:func:`esp_sleep_enable_gpio_wakeup_on_hp_periph_powerdown` 函数可以配置相应的唤醒管脚和唤醒触发电平。此函数适用于: + + - Deep-sleep 模式(始终可用) + - 启用 :ref:`CONFIG_PM_POWER_DOWN_PERIPHERAL_IN_LIGHT_SLEEP` 时的 Light-sleep 模式 + + .. note:: + 只有由 VDD3P3_RTC 电源域供电的 GPIO(RTC IO)可以与此 API 一起使用。具体支持的管脚请参考 `datasheet <{IDF_TARGET_DATASHEET_CN_URL}>`__ > IO 管脚。 .. only:: esp32h2 @@ -365,6 +391,24 @@ UART 唤醒(仅适用于 Light-sleep 模式) 在 Light-sleep 模式下,设置 Kconfig 选项 :ref:`CONFIG_PM_POWER_DOWN_PERIPHERAL_IN_LIGHT_SLEEP` 将使 UART 唤醒失效。 +.. only:: SOC_ULP_LP_UART_SUPPORTED + + LP_UART 可以将 ULP LP 内核协处理器唤醒。LP_UART 支持的唤醒模式与上述 HP UART 唤醒模式相同,包括边沿阈值唤醒、RX FIFO 阈值唤醒、起始位检测唤醒和字符序列检测唤醒。 + + 要使用 LP_UART 唤醒 ULP LP 内核,需要执行以下步骤: + + #. 在 :cpp:type:`ulp_lp_core_cfg_t` 结构体的 ``wakeup_source`` 字段中设置 :c:macro:`ULP_LP_CORE_WAKEUP_SOURCE_LP_UART` 标志位。 + #. 初始化 LP UART(调用 :cpp:func:`lp_core_uart_init`)。 + #. 使用 :cpp:func:`lp_core_uart_wakeup_setup` 函数配置 LP_UART 的唤醒模式,参数使用 :cpp:type:`uart_wakeup_cfg_t` 结构体,配置方式与 HP UART 相同。 + + .. only:: SOC_LP_CORE_LP_UART_WAKEUP_KEEP_TRIGGERED + + .. note:: + + 在支持 ``SOC_LP_CORE_LP_UART_WAKEUP_KEEP_TRIGGERED`` 的芯片上,LP UART 唤醒后唤醒信号会保持触发状态。LP 核启动流程(:cpp:func:`ulp_lp_core_update_wakeup_cause`)会自动调用 :cpp:func:`ulp_lp_core_lp_uart_reset_wakeup_en` 和 :cpp:func:`lp_core_uart_clear_buf` 清除该状态。若未走标准启动流程,则需手动处理,否则会被重复唤醒。 + + 有关 LP_UART 唤醒的示例代码,请参考 :example:`system/ulp/lp_core/lp_uart/lp_uart_char_seq_wakeup`。 + .. _disable_sleep_wakeup_source: 禁用睡眠模式唤醒源 @@ -526,10 +570,13 @@ UART 输出处理 :SOC_WIFI_SUPPORTED: - :example:`wifi/power_save` 演示如何通过 Wi-Fi Modem-sleep 模式和自动 Light-sleep 模式保持 Wi-Fi 连接。 :SOC_BT_SUPPORTED: - :example:`bluetooth/nimble/power_save` 演示如何通过 Bluetooth Modem-sleep 模式和自动 Light-sleep 模式保持 Bluetooth 连接。 :SOC_ULP_SUPPORTED: - :example:`system/deep_sleep` 演示如何使用 Deep-sleep 唤醒触发器和 ULP 协处理器编程。 - :not SOC_ULP_SUPPORTED and not esp32c3 and not esp32h2: - :example:`system/deep_sleep` 演示如何通过 {IDF_TARGET_NAME} 的唤醒源,如 RTC 定时器、GPIO、EXT0、EXT1 等,触发 Deep-sleep 唤醒。 + :not SOC_ULP_SUPPORTED and not esp32c3 and not esp32h2 and SOC_PM_SUPPORT_EXT1_WAKEUP and SOC_GPIO_SUPPORT_HP_PERIPH_PD_SLEEP_WAKEUP: - :example:`system/deep_sleep` 演示如何通过 {IDF_TARGET_NAME} 的唤醒源,如 RTC 定时器、GPIO、EXT1 等,触发 Deep-sleep 唤醒。 + :not SOC_ULP_SUPPORTED and not esp32c3 and not esp32h2 and SOC_PM_SUPPORT_EXT1_WAKEUP and not SOC_GPIO_SUPPORT_HP_PERIPH_PD_SLEEP_WAKEUP: - :example:`system/deep_sleep` 演示如何通过 {IDF_TARGET_NAME} 的唤醒源,如 RTC 定时器、EXT1 等,触发 Deep-sleep 唤醒。 + :not SOC_ULP_SUPPORTED and not esp32c3 and not esp32h2 and not SOC_PM_SUPPORT_EXT1_WAKEUP: - :example:`system/deep_sleep` 演示如何通过 {IDF_TARGET_NAME} 的唤醒源,如 RTC 定时器、GPIO 等,触发 Deep-sleep 唤醒。 :esp32c3: - :example:`system/deep_sleep` 演示如何通过 ESP32-C3 的唤醒源,如 RTC 定时器、GPIO 等,触发 Deep-sleep 唤醒。 - :esp32h2: - :example:`system/deep_sleep` 演示如何通过 ESP32-H2 的唤醒源,如 RTC 定时器、EXT0、EXT1 等,触发 Deep-sleep 唤醒。 + :esp32h2: - :example:`system/deep_sleep` 演示如何通过 ESP32-H2 的唤醒源,如 RTC 定时器、EXT1 等,触发 Deep-sleep 唤醒。 - :example:`system/light_sleep` 演示如何使用 {IDF_TARGET_NAME} 的唤醒源,如定时器,GPIO 等,触发 Light-sleep 唤醒。 + :SOC_PM_SUPPORT_USB_WAKEUP: - :example:`peripherals/usb/device/tusb_cdc_acm_wakeup` 演示如何使用 USB 2.0 将芯片从 Light-sleep 唤醒。 :SOC_TOUCH_SENSOR_SUPPORTED and SOC_PM_SUPPORT_TOUCH_SENSOR_WAKEUP: - :example:`peripherals/touch_sensor/touch_sens_sleep` 演示如何使用触摸传感器唤醒 Light-sleep 或 Deep-sleep。 API 参考 diff --git a/docs/zh_CN/api-reference/system/wdts.rst b/docs/zh_CN/api-reference/system/wdts.rst index 482112b91ec..11f8108a1c8 100644 --- a/docs/zh_CN/api-reference/system/wdts.rst +++ b/docs/zh_CN/api-reference/system/wdts.rst @@ -68,8 +68,10 @@ IWDT 利用 {IDF_TARGET_IWDT_TIMER_GROUP} 中的 MWDT_WDT 看门狗定时器作 - IWDT 默认通过 :ref:`CONFIG_ESP_INT_WDT` 选项启用。 - 通过 :ref:`CONFIG_ESP_INT_WDT_TIMEOUT_MS` 选项设置 IWDT 超时。 - - 注意,如果启用了 PSRAM 支持,那么默认的超时时间会更长,因为在某些情况下,临界区或中断例程访问大量 PSRAM 需要更长时间。 - - 超时时间至少应是 FreeRTOS tick 周期的两倍时长(参见 :ref:`CONFIG_FREERTOS_HZ`)。 + .. list:: + + :SOC_SPIRAM_SUPPORTED: - 注意,如果启用了 PSRAM 支持,那么默认的超时时间会更长,因为在某些情况下,临界区或中断例程访问大量 PSRAM 需要更长时间。 + - IWDT 的配置超时时间应至少为 FreeRTOS tick 周期的两倍时长。例如,如果 FreeRTOS tick 周期间隔为 10 毫秒,则 IWDT 的超时时间应至少为 20 毫秒(参见 :ref:`CONFIG_FREERTOS_HZ`)。 调优 ^^^^^^ diff --git a/docs/zh_CN/migration-guides/release-6.x/6.0/peripherals.rst b/docs/zh_CN/migration-guides/release-6.x/6.0/peripherals.rst index 5cc32524f16..0e1af622d84 100644 --- a/docs/zh_CN/migration-guides/release-6.x/6.0/peripherals.rst +++ b/docs/zh_CN/migration-guides/release-6.x/6.0/peripherals.rst @@ -311,7 +311,7 @@ LCD - :cpp:type:`esp_lcd_rgb_panel_config_t` 结构体中的 ``psram_trans_align`` 和 ``sram_trans_align`` 均已被 :cpp:member:`esp_lcd_rgb_panel_config_t::dma_burst_size` 成员取代,用来设置 DMA 的突发传输大小。 - :cpp:type:`esp_lcd_panel_dev_config_t` 结构体中的 ``color_space`` 和 ``rgb_endian`` 配置均已被 :cpp:member:`esp_lcd_panel_dev_config_t::rgb_ele_order` 成员取代,用来设置 RGB 元素的排列顺序。对应的类型 ``lcd_color_rgb_endian_t`` 和 ``esp_lcd_color_space_t`` 也已被移除,请使用 :cpp:type:`lcd_rgb_element_order_t` 替代。 - ``esp_lcd_panel_disp_off`` 函数已被移除。请使用 :func:`esp_lcd_panel_disp_on_off` 函数来控制显示内容的开关。 -- :cpp:type:`esp_lcd_rgb_panel_event_callbacks_t` 中的 ``on_bounce_frame_finish`` 成员已被 :cpp:member:`esp_lcd_rgb_panel_event_callbacks_t::on_frame_buf_complete` 成员取代,用于指示一个完整的帧缓冲区已被发送给 LCD 控制器。 +- :cpp:type:`esp_lcd_rgb_panel_event_callbacks_t` 中的 ``on_bounce_frame_finish`` 成员已被 :cpp:member:`esp_lcd_rgb_panel_event_callbacks_t::on_frame_buf_complete` 成员取代,用于指示一个完整的帧缓冲区可以被安全复用。 - I2C 接口的 LCD IO 层驱动有两套实现,分别基于新、旧 I2C Master 总线驱动。由于旧版的 I2C Master 驱动逐渐被弃用,遂 LCD 的 IO 层也移除对旧版的支持,只使用 ``driver/i2c_master.h`` 中提供的 API。 - :cpp:type:`esp_lcd_dpi_panel_config_t` 结构体中的 ``pixel_format`` 成员已经被删除。建议仅使用 :cpp:member:`esp_lcd_dpi_panel_config_t::in_color_format` 来设定 MIPI DSI 驱动输入的像素数据格式。 - :cpp:type:`esp_lcd_rgb_panel_config_t` 结构体中的 ``bits_per_pixel`` 成员已经被删除。内部帧缓冲区的色彩深度现在由 :cpp:member:`esp_lcd_rgb_panel_config_t::in_color_format` 成员决定。 diff --git a/docs/zh_CN/migration-guides/release-6.x/6.0/protocols.rst b/docs/zh_CN/migration-guides/release-6.x/6.0/protocols.rst index b362580da7f..efca3171261 100644 --- a/docs/zh_CN/migration-guides/release-6.x/6.0/protocols.rst +++ b/docs/zh_CN/migration-guides/release-6.x/6.0/protocols.rst @@ -104,6 +104,74 @@ ESP-TLS 已移除内置的 wolfSSL TLS 协议栈支持。使用 wolfSSL 的用 新 API 需要您使用 :cpp:func:`esp_tls_init` 创建 :cpp:type:`esp_tls_t` 结构,并提供对连接过程的更好控制。 +ESP HTTP 服务器 +--------------- + +握手期间不再调用 WebSocket 处理器 +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +自 v6.0.1 起,已注册的 URI 处理程序在 WebSocket 握手期间,针对 WebSocket 端点的回调 **不再被调用**。 + +在此更改之前,处理程序会在握手完成后立即以 ``req->method == HTTP_GET`` 的状态被调用,常用于应用程序的连接初始化: + +.. code-block:: c + + /* v6.0.1 之前的模式 — 自 v6.0.1 起不再生效 */ + static esp_err_t ws_handler(httpd_req_t *req) + { + if (req->method == HTTP_GET) { + ESP_LOGI(TAG, "New WebSocket connection established"); + return ESP_OK; + } + /* 处理 WebSocket 帧 */ + } + +自 v6.0.1 起,仅会针对后续的 WebSocket 数据帧调用该处理器,因此,帧处理程序中不再需要进行 ``HTTP_GET`` 检查。 + +迁移选项 +^^^^^^^^^^^^^^^^^ + +**选项 1(推荐)** — 将连接阶段的逻辑移动到一个专用的握手后回调中: + +1. 在 menuconfig 中启用 :ref:`CONFIG_HTTPD_WS_POST_HANDSHAKE_CB_SUPPORT`。 +2. 在 ``httpd_uri_t`` 结构体中注册 ``ws_post_handshake_cb`` 回调,使帧处理程序保持简洁,无需再进行 `HTTP_GET` 状态检查。 + +.. code-block:: c + + static esp_err_t ws_on_connect(httpd_req_t *req) + { + ESP_LOGI(TAG, "New WebSocket connection established"); + return ESP_OK; + } + + static esp_err_t ws_handler(httpd_req_t *req) + { + /* 仅处理 WebSocket 帧 */ + } + + static const httpd_uri_t ws_uri = { + .uri = "/ws", + .method = HTTP_GET, + .handler = ws_handler, + .is_websocket = true, + .ws_post_handshake_cb = ws_on_connect, + }; + +**选项 2(改动最少)** — 将 ``.ws_post_handshake_cb`` 设置为与 ``.handler`` 相同的函数: + +1. 在 menuconfig 中启用 :ref:`CONFIG_HTTPD_WS_POST_HANDSHAKE_CB_SUPPORT`。 +2. 在 URI 注册中设置 ``.ws_post_handshake_cb = ws_handler``。现有的 ``if (req->method == HTTP_GET)`` 检查在处理程序内部仍然有效,无需额外修改代码。 + +.. code-block:: c + + static const httpd_uri_t ws_uri = { + .uri = "/ws", + .method = HTTP_GET, + .handler = ws_handler, + .is_websocket = true, + .ws_post_handshake_cb = ws_handler, /* 同一个函数可恢复原有行为 */ + }; + ESP-Modbus ---------- diff --git a/docs/zh_CN/security/secure-boot-v2.rst b/docs/zh_CN/security/secure-boot-v2.rst index d2853f8d271..c810b0b2609 100644 --- a/docs/zh_CN/security/secure-boot-v2.rst +++ b/docs/zh_CN/security/secure-boot-v2.rst @@ -5,11 +5,11 @@ :link_to_translation:`en:[English]` -{IDF_TARGET_SBV2_SCHEME:default="RSA-PSS", esp32c2, esp32c61="ECDSA", esp32c6, esp32h2, esp32p4, esp32c5, esp32h21="RSA-PSS 或 ECDSA"} +{IDF_TARGET_SBV2_SCHEME:default="RSA-PSS", esp32c2, esp32c61="ECDSA", esp32c6, esp32h2, esp32p4, esp32c5="RSA-PSS 或 ECDSA", esp32h21="RSA-PSS"} -{IDF_TARGET_SBV2_KEY:default="RSA-3072", esp32c2, esp32c61="ECDSA-256", esp32c6, esp32h2, esp32p4, esp32h21="RSA-3072、ECDSA-256", esp32c5="RSA-3072、ECDSA-384、ECDSA-256"} +{IDF_TARGET_SBV2_KEY:default="RSA-3072", esp32c2, esp32c61="ECDSA-256", esp32c6, esp32h2, esp32p4="RSA-3072、ECDSA-256", esp32h21="RSA-3072", esp32c5="RSA-3072、ECDSA-384、ECDSA-256"} -{IDF_TARGET_SECURE_BOOT_OPTION_TEXT:default="", esp32c6, esp32h2, esp32p4, esp32h21="推荐使用 RSA,其验证时间更短。可以在菜单中选择 RSA 或 ECDSA 方案。", esp32c5="推荐使用 ECDSA,其验证时间更短。可以在菜单中选择 RSA 或 ECDSA 方案。"} +{IDF_TARGET_SECURE_BOOT_OPTION_TEXT:default="", esp32c6, esp32h2, esp32p4="推荐使用 RSA,其验证时间更短。可以在菜单中选择 RSA 或 ECDSA 方案。", esp32c5="推荐使用 ECDSA,其验证时间更短。可以在菜单中选择 RSA 或 ECDSA 方案。"} {IDF_TARGET_SBV2_SCHEME_RECOMMENDATION:default="如果需要快速启动,推荐使用 RSA;如果需要较短的密钥长度,建议使用 ECDSA。", esp32c5="如果需要快速启动且需要较短的密钥长度,建议使用 ECDSA。"} @@ -52,6 +52,18 @@ 在本指南中,最常用的命令形式为 ``idf.py secure-``,这是对应 ``espsecure `` 的封装。基于 ``idf.py`` 的命令能提供更好的用户体验,但与基于 ``espsecure`` 的命令相比,可能会损失一部分高级功能。 +.. only:: CONFIG_SECURE_BOOT_V2_ECDSA_INSECURE and SOC_SECURE_BOOT_V2_RSA + + .. warning:: + + 在 {IDF_TARGET_NAME} 上,基于 ECDSA 的 Secure Boot V2 方案在某些输入向量下无法正常工作,因此**不推荐使用**。请改用基于 RSA 的 Secure Boot V2 方案。如果仍需使用基于 ECDSA 的方案,请启用 :ref:`CONFIG_SECURE_BOOT_INSECURE` 和 :ref:`CONFIG_SECURE_BOOT_V2_FORCE_ENABLE_ECDSA`。该问题将在未来的硬件 ECO 版本中修复,详情请参阅硬件勘误文档。 + +.. only:: CONFIG_SECURE_BOOT_V2_ECDSA_INSECURE and not SOC_SECURE_BOOT_V2_RSA + + .. warning:: + + 在 {IDF_TARGET_NAME} 上,基于 ECDSA 的 Secure Boot V2 方案在某些输入向量下存在漏洞,因此**不推荐用于量产**。如果仍需使用基于 ECDSA 的 Secure Boot V2 方案,请启用 :ref:`CONFIG_SECURE_BOOT_INSECURE` 和 :ref:`CONFIG_SECURE_BOOT_V2_FORCE_ENABLE_ECDSA`。该问题将在未来的硬件 ECO 版本中修复,详情请参阅硬件勘误文档。 + 背景 ---- @@ -729,9 +741,7 @@ Secure Boot v2 签名验证也可以在 OTA 更新期间验证数据分区镜像 .. note:: - 请注意,启用配置 :ref:`CONFIG_SECURE_BOOT_ALLOW_UNUSED_DIGEST_SLOTS` 只能确保 **应用程序** 不会撤销未使用的摘要槽。 - 若想在设备首次启动时启用安全启动,那么即使启用了上述配置,引导加载程序也会在启用安全启动时撤销未使用的摘要槽,因为保留未使用的密钥槽会构成安全隐患。 - 如果在开发流程中需要保留未使用摘要槽,则应从外部启用安全启动 (:ref:`enable-secure-boot-v2-externally`),而不是在启动设备时启用安全启动,这样引导加载程序就无需启用安全启动,从而避免安全隐患。 + 启用配置 :ref:`CONFIG_SECURE_BOOT_ALLOW_UNUSED_DIGEST_SLOTS` 后,未使用的摘要槽在两种情况下都将保持未撤销状态:在 **应用程序** 运行时,以及在设备首次启动时由 **引导加载程序** 启用安全启动时。请注意,除非调试接口和下载接口已完全禁用,且远程接口已针对安全风险进行全面审计,否则保留未使用的密钥槽可能构成安全风险。 保守方法 ~~~~~~~~ diff --git a/examples/bluetooth/.build-test-rules.yml b/examples/bluetooth/.build-test-rules.yml index 285a481ee1d..fd03e3410de 100644 --- a/examples/bluetooth/.build-test-rules.yml +++ b/examples/bluetooth/.build-test-rules.yml @@ -16,6 +16,14 @@ examples/bluetooth: disable: - if: SOC_BT_SUPPORTED != 1 +examples/bluetooth/ble_uart_service: + <<: *bt_default_depends + disable: + - if: SOC_BLE_SUPPORTED != 1 + depends_filepatterns: + - examples/bluetooth/common/ble_uart/**/* + - examples/bluetooth/ble_uart_service/**/* + examples/bluetooth/bluedroid/ble: <<: *bt_default_depends disable: @@ -428,14 +436,10 @@ examples/bluetooth/nimble/throughput_app: <<: *bt_default_depends disable: - if: SOC_BLE_SUPPORTED != 1 - depends_components+: - - esp_driver_gpio - - esp_driver_uart depends_filepatterns: - examples/bluetooth/nimble/common/**/* - - examples/bluetooth/nimble/throughput_app/blecent_throughput/components/**/* -examples/bluetooth/nimble/throughput_app/blecent_throughput: +examples/bluetooth/nimble/throughput_app/gatt/blecent_throughput: <<: *bt_default_depends disable: - if: SOC_BLE_SUPPORTED != 1 @@ -444,4 +448,20 @@ examples/bluetooth/nimble/throughput_app/blecent_throughput: - esp_driver_uart depends_filepatterns: - examples/bluetooth/nimble/common/**/* - - examples/bluetooth/nimble/throughput_app/blecent_throughput/components/**/* + - examples/bluetooth/nimble/throughput_app/gatt/blecent_throughput/components/**/* + +examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_cent: + <<: *bt_default_depends + disable: + - if: SOC_BLE_SUPPORTED != 1 + depends_filepatterns: + - examples/bluetooth/nimble/common/**/* + - examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_cent/**/* + +examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_prph: + <<: *bt_default_depends + disable: + - if: SOC_BLE_SUPPORTED != 1 + depends_filepatterns: + - examples/bluetooth/nimble/common/**/* + - examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_prph/**/* diff --git a/examples/bluetooth/ble_get_started/nimble/NimBLE_GATT_Server/README.md b/examples/bluetooth/ble_get_started/nimble/NimBLE_GATT_Server/README.md index 9be317a527c..37442fa97f0 100644 --- a/examples/bluetooth/ble_get_started/nimble/NimBLE_GATT_Server/README.md +++ b/examples/bluetooth/ble_get_started/nimble/NimBLE_GATT_Server/README.md @@ -169,9 +169,6 @@ The characteristic is binded with `led_chr_access` callback function, in which t ``` C static int led_chr_access(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt *ctxt, void *arg) { - /* Local variables */ - int rc; - /* Handle access events */ /* Note: LED characteristic is write only */ switch (ctxt->op) { @@ -203,7 +200,7 @@ static int led_chr_access(uint16_t conn_handle, uint16_t attr_handle, } else { goto error; } - return rc; + return 0; } goto error; diff --git a/examples/bluetooth/ble_get_started/nimble/NimBLE_GATT_Server/main/src/gatt_svc.c b/examples/bluetooth/ble_get_started/nimble/NimBLE_GATT_Server/main/src/gatt_svc.c index 4e521e348b5..bf03d4023f4 100644 --- a/examples/bluetooth/ble_get_started/nimble/NimBLE_GATT_Server/main/src/gatt_svc.c +++ b/examples/bluetooth/ble_get_started/nimble/NimBLE_GATT_Server/main/src/gatt_svc.c @@ -114,9 +114,6 @@ error: static int led_chr_access(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt *ctxt, void *arg) { - /* Local variables */ - int rc = 0; - /* Handle access events */ /* Note: LED characteristic is write only */ switch (ctxt->op) { @@ -148,7 +145,7 @@ static int led_chr_access(uint16_t conn_handle, uint16_t attr_handle, } else { goto error; } - return rc; + return 0; } goto error; diff --git a/examples/bluetooth/ble_get_started/nimble/NimBLE_Security/main/src/gatt_svc.c b/examples/bluetooth/ble_get_started/nimble/NimBLE_Security/main/src/gatt_svc.c index d819be8e5be..726a4f2bb60 100644 --- a/examples/bluetooth/ble_get_started/nimble/NimBLE_Security/main/src/gatt_svc.c +++ b/examples/bluetooth/ble_get_started/nimble/NimBLE_Security/main/src/gatt_svc.c @@ -117,9 +117,6 @@ error: static int led_chr_access(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt *ctxt, void *arg) { - /* Local variables */ - int rc = 0; - /* Handle access events */ /* Note: LED characteristic is write only */ switch (ctxt->op) { @@ -151,7 +148,7 @@ static int led_chr_access(uint16_t conn_handle, uint16_t attr_handle, } else { goto error; } - return rc; + return 0; } goto error; diff --git a/examples/bluetooth/ble_uart_service/OPENCODE_COMPANION.md b/examples/bluetooth/ble_uart_service/OPENCODE_COMPANION.md new file mode 100644 index 00000000000..7b39e318619 --- /dev/null +++ b/examples/bluetooth/ble_uart_service/OPENCODE_COMPANION.md @@ -0,0 +1,470 @@ + + + +# Building an OpenCode Companion with ESP-BLE-UART and ESP-VoCat + +## Introduction + +This document describes how to build a physical companion device for OpenCode using ESP-BLE-UART and ESP-VoCat. The companion device reflects the current session state on a display, presents permission requests for user approval, and returns permission decisions to OpenCode via single-key input. BLE UART serves as the transport layer between the device and the host-side editor session. + +The tutorial is organized in two parts. Part 1 uses **ESP-BLE-UART Console** with the `ble_uart_service` Echo Server (this example) to verify that the host can discover, connect to, and exchange data with a BLE UART device. Part 2 introduces the `ble_uart_service` example firmware for the ESP-VoCat board (maintained in [esp-iot-solution](https://github.com/espressif/esp-iot-solution)), the **ESP-BLE-UART Daemon**, and the **OpenCode Plugin**, which together enable the device to receive session status updates and return `once` / `reject` permission decisions to OpenCode. + +

+ ESP-VoCat Working With OpenCode +
ESP-VoCat Working With OpenCode +

+ +## Learning Objectives + +- Understand the BLE UART service and its GATT convention +- Learn how to build and flash the ESP-BLE-UART Echo Server +- Understand the JSON Lines protocol used over BLE UART +- Learn how to configure the ESP-BLE-UART Daemon and OpenCode Plugin + +## Prerequisites + +- A host machine with a Bluetooth adapter and scan/connect permissions. +- ESP-IDF environment exported. +- Any target supported by `ble_uart_service` for the Console echo-server smoke test. +- The full OpenCode UI demo requires: + - An [ESP-VoCat](https://docs.espressif.com/projects/esp-dev-kits/en/latest/esp32s3/esp-vocat/index.html) development board (based on ESP32-S3) with a circular touch display and single-key input. The BLE UART transport is reusable, but the display/touch/emote UI in this example is board-specific. The example is maintained in the [esp-iot-solution](https://github.com/espressif/esp-iot-solution) repository at `examples/bluetooth/ble_uart_service`; see its README for supported boards, dependency versions, and build instructions. + - The first CMake configuration of the `ble_uart_service` example requires network access to download `emote_assets.bin`. For offline or intranet environments, set `EMOTE_ASSETS_BIN` to a local path to override the download. + - OpenCode installed to run the plugin demo. + +Install the host-side ESP-BLE-UART Bridge dependencies: + +```bash +cd $IDF_PATH +. ./export.sh +python -m pip install -r tools/ble/ble_uart_bridge/requirements.txt +``` + +On Windows, use `export.bat` or `export.ps1` from the ESP-IDF root instead of `. ./export.sh`. + +## Part 1: ESP-BLE-UART Console + +### What BLE UART Is + +Bluetooth LE does not have a real UART peripheral in the classic serial-port sense. A BLE UART service is a GATT convention: one characteristic serves as the host-to-device RX channel, another as the device-to-host TX channel. The Echo Server in `ble_uart_service` uses Nordic UART Service-style UUIDs and sends received bytes back through TX notifications, which makes it suitable for verifying the host-side Console path. + +The transport layer only moves bytes. In Part 1, those bytes are simple echoed text. In Part 2, the `ble_uart_service` example firmware running on ESP-VoCat puts a JSONL protocol on top of the same BLE UART channel. + +### Build and Flash the ESP-BLE-UART Echo Server + +```bash +cd $IDF_PATH/examples/bluetooth/ble_uart_service +idf.py set-target esp32s3 # or another supported target +idf.py build flash monitor +``` + +Keep the monitor open during pairing. If the central asks for a passkey, use the six-digit value printed by the firmware log. The firmware console output should resemble the following log (the address and device name suffix will vary): + +``` +I (548) ble_uart: BLE host task started +I (548) ble_uart: registered service 0x1800 handle=1 +I (548) ble_uart: registered chr 0x2a00 def=2 val=3 +I (548) ble_uart: registered chr 0x2a01 def=4 val=5 +I (558) ble_uart: registered service 0x1801 handle=6 +I (558) ble_uart: registered chr 0x2a05 def=7 val=8 +I (568) ble_uart: registered chr 0x2b3a def=10 val=11 +I (568) ble_uart: registered chr 0x2b29 def=12 val=13 +I (578) ble_uart: registered service 6e400001-b5a3-f393-e0a9-e50e24dcca9e handle=14 +I (578) ble_uart: registered chr 6e400002-b5a3-f393-e0a9-e50e24dcca9e def=15 val=16 +I (588) ble_uart: registered chr 6e400003-b5a3-f393-e0a9-e50e24dcca9e def=17 val=18 +I (608) NimBLE: GAP procedure initiated: stop advertising. +I (608) NimBLE: GAP procedure initiated: stop advertising. +I (608) ble_uart: addr=74:4d:bd:a9:ed:72 +I (608) NimBLE: GAP procedure initiated: advertise; +I (618) NimBLE: disc_mode=2 +I (618) NimBLE: adv_channel_map=0 own_addr_type=0 adv_filter_policy=0 adv_itvl_min=0 adv_itvl_max=0 +I (628) NimBLE: +I (628) ble_uart: advertising as 'BleUart-ED72' +I (628) main_task: Returned from app_main() +``` + +The `ble_uart: addr=74:4d:bd:a9:ed:72` line shows the device Bluetooth MAC address (`74:4D:BD:A9:ED:72`). The device advertises under the name shown in the last `ble_uart: advertising as 'BleUart-XXXX'` line. + +When the central initiates a connection, the firmware logs a pairing passkey prompt. If you are prompted for a passkey by the system Bluetooth dialog or the `connection-check` command, enter the six-digit number shown in the monitor: + +``` +W (19298) ble_uart: +-----------------------------+ +W (19298) ble_uart: | BLE PAIRING PASSKEY: | +W (19298) ble_uart: | 617138 | +W (19298) ble_uart: +-----------------------------+ +``` + +### Find the BLE UART Device + +Open a second terminal: + +```bash +cd $IDF_PATH/tools/ble/ble_uart_bridge +python main.py list-devices +``` + +Use the printed device identifier as `DEVICE_ID`. You can check whether the target device has been discovered by matching the MAC address or device name in the output. On Linux, the device MAC address is printed directly: + +``` +> python main.py list-devices + +2026-06-05 11:19:56.728 | INFO | src.core.scanner:scan_devices:42 - Scanning for nearby BLE devices in 5.0s... +2026-06-05 11:19:57.108 | SUCCESS | src.core.scanner:on_detect:39 - Found: 74:4D:BD:A9:ED:72, with name BleUart-ED72, rssi=-46 +``` + +The `74:4D:BD:A9:ED:72` MAC address and `BleUart-ED72` device name in this output match the firmware log above. + +On macOS, system restrictions prevent the tool from displaying the real Bluetooth MAC address. Instead, macOS assigns a CoreBluetooth UUID as the device identifier. Match the device name (`BleUart-ED72` in this example) in the `list-devices` output with the name shown in the firmware log to find the corresponding UUID: + +``` +> python main.py list-devices + +2026-06-05 11:19:56.728 | INFO | src.core.scanner:scan_devices:42 - Scanning for nearby BLE devices in 5.0s... +2026-06-05 11:19:57.108 | SUCCESS | src.core.scanner:on_detect:39 - Found: 5BA2476C-CDD2-BF3F-F98C-252CFA45F8B5, with name BleUart-ED72, rssi=-46 +``` + +The `5BA2476C-CDD2-BF3F-F98C-252CFA45F8B5` string in this example is the CoreBluetooth UUID to use as `DEVICE_ID` on macOS. + +### Check the Bluetooth LE Link Before Opening Console + +```bash +python main.py connection-check "" +``` + +This command connects, discovers the BLE UART service and characteristics, then disconnects. On Linux or Windows, pass the device MAC address as `DEVICE_ID`: + +``` +> python main.py connection-check 74:4D:BD:A9:ED:72 + +2026-06-05 12:06:27.252 | INFO | src.core.bridge:connect:139 - Connecting to 74:4D:BD:A9:ED:72... +2026-06-05 12:06:37.460 | SUCCESS | src.core.bridge:connect:206 - Succeeded to connect to 74:4D:BD:A9:ED:72! +2026-06-05 12:06:37.461 | INFO | src.core.bridge:_disconnect_locked:86 - Disconnecting from 74:4D:BD:A9:ED:72... +2026-06-05 12:06:37.461 | INFO | src.core.bridge:_handle_disconnect:120 - Disconnected from 74:4D:BD:A9:ED:72 +``` + +On macOS, use the CoreBluetooth UUID instead: + +``` +> python main.py connection-check 5BA2476C-CDD2-BF3F-F98C-252CFA45F8B5 + +2026-06-05 12:06:27.252 | INFO | src.core.bridge:connect:139 - Connecting to 5BA2476C-CDD2-BF3F-F98C-252CFA45F8B5... +2026-06-05 12:06:37.460 | SUCCESS | src.core.bridge:connect:206 - Succeeded to connect to 5BA2476C-CDD2-BF3F-F98C-252CFA45F8B5! +2026-06-05 12:06:37.461 | INFO | src.core.bridge:_disconnect_locked:86 - Disconnecting from 5BA2476C-CDD2-BF3F-F98C-252CFA45F8B5... +2026-06-05 12:06:37.461 | INFO | src.core.bridge:_handle_disconnect:120 - Disconnected from 5BA2476C-CDD2-BF3F-F98C-252CFA45F8B5 +``` + +If this step fails, resolve scanning, pairing, permissions, or advertising issues before proceeding to the daemon or OpenCode integration. + +### Open ESP-BLE-UART Console + +```bash +python main.py console "" --terminator lf +``` + +In the Console, type a short line and press Enter: + +``` +hello from console +``` + +Expected result: + +``` +[INFO] Connected to 68:B6:B3:55:41:76 +[TX] hello from console +[RX] hello from console +``` + +- The Bluetooth LE address varies by device. +- Console shows `[TX]` lines for the input. +- The ESP-BLE-UART example echoes the same bytes back as `[RX]` output. + +At this point Bluetooth LE discovery, connection, host-to-device writes, and device-to-host notifications all work. The JSONL protocol (used by the ESP-VoCat example), daemon, and OpenCode Plugin are application layers on top of this path; they do not replace it. + +For more Console options such as hex mode, write-with-response, and alternate line endings, see [`tools/ble/ble_uart_bridge/docs/Quick-Start-BLE-UART-Console.md`](../../../tools/ble/ble_uart_bridge/docs/Quick-Start-BLE-UART-Console.md). + +## Part 2: ESP-VoCat OpenCode Companion + +### About ESP-VoCat + +[ESP-VoCat](https://docs.espressif.com/projects/esp-dev-kits/en/latest/esp32s3/esp-vocat/index.html) is an intelligent AI development kit based on the ESP32-S3 module, featuring a circular touch display and single-key input. + +The [esp-iot-solution](https://github.com/espressif/esp-iot-solution) repository contains a `ble_uart_service` example (at `examples/bluetooth/ble_uart_service`) that runs on the ESP-VoCat development board. This example firmware renders session status as emote expressions and presents permission requests for physical approval. See the example README in esp-iot-solution for supported boards, required component versions, and build details. + +### Why JSON Lines + +Bluetooth LE writes are packetized by the ATT MTU, not by application messages. The ESP-VoCat OpenCode flow uses JSON Lines (JSONL) on top of BLE UART. JSONL works here because it is readable in logs, easy to type into Console for manual testing, parsable with cJSON on firmware, and covers both request/response and fire-and-forget patterns. + +### Architecture + +The ESP-BLE-UART Bridge tools and OpenCode demo plugin are included in ESP-IDF master and release branches starting from `release/v5.2` under `tools/ble/ble_uart_bridge/`. The `ble_uart_service` example implements the device side of the protocol and is available in the [esp-iot-solution](https://github.com/espressif/esp-iot-solution) repository. + +```mermaid +flowchart LR + OC[OpenCode] -->|session.status / permission.asked| Plugin[OpenCode Plugin] + Plugin -->|POST /notify| Daemon[ESP-BLE-UART Daemon] + Plugin -->|POST /request| Daemon + Daemon -->|Bluetooth LE write: NUS RX JSONL| ESP[ble_uart_service example] + ESP -->|Bluetooth LE notify: NUS TX JSONL| Daemon + Daemon -->|HTTP response| Plugin + Plugin -->|permission reply| OC + ESP --> Display[Emote + Tip Text] + ESP --> Key[Single Key: once / reject] +``` + +Each layer can be replaced independently: + +- Console verifies the raw BLE UART path. +- Daemon keeps one Bluetooth LE connection open and exposes local HTTP endpoints. +- The OpenCode Plugin translates editor events into daemon requests. +- The `ble_uart_service` example renders status and permission prompts on the ESP-VoCat device. + +### Start the ESP-BLE-UART Daemon + +First flash the `ble_uart_service` example from the [esp-iot-solution](https://github.com/espressif/esp-iot-solution) repository onto the ESP-VoCat board. This is a different application from the Console Echo Server: + +```bash +# Clone esp-iot-solution if not already available +git clone https://github.com/espressif/esp-iot-solution.git +cd esp-iot-solution/examples/bluetooth/ble_uart_service +idf.py set-target esp32s3 +idf.py build flash monitor +``` + +See the example README in esp-iot-solution for dependency versions and board-specific configuration. + +Then scan again and use the ESP-VoCat device identifier as `VOCAT_DEVICE_ID`: + +```bash +cd $IDF_PATH/tools/ble/ble_uart_bridge +python main.py list-devices +python main.py connection-check "" +``` + +Start the daemon with the ESP-VoCat device: + +```bash +cd $IDF_PATH/tools/ble/ble_uart_bridge +python main.py daemon "" --host 127.0.0.1 --port 8888 +``` + +> **Note:** The daemon HTTP endpoints are unauthenticated. Keep the daemon bound to `127.0.0.1` unless you add your own access control. + +In another terminal, check daemon status: + +```bash +cd $IDF_PATH/tools/ble/ble_uart_bridge +python main.py daemon-status +``` + +### Verify ESP-VoCat Through the Daemon Before OpenCode + +Do not use a generic `echo` request for ESP-VoCat validation; this firmware does not implement an echo op. Use the operations defined in the [esp-iot-solution example's json_format.md](https://github.com/espressif/esp-iot-solution/blob/master/examples/bluetooth/ble_uart_service/json_format.md). + +Session status smoke test: + +```bash +python main.py daemon-notify --op session.status --json '{ + "v": 1, + "kind": "session.status", + "event_id": "evt_manual", + "session_id": "ses_manual", + "requires_reply": false, + "payload": { + "type": "busy" + } +}' + +python main.py daemon-notify --op session.status --json '{ + "v": 1, + "kind": "session.status", + "event_id": "evt_manual", + "session_id": "ses_manual", + "requires_reply": false, + "payload": { + "type": "idle" + } +}' +``` + +The CLI wraps each JSON object as the daemon envelope `data` field with `op: "session.status"` and `id: ""`. The firmware receives a complete JSONL envelope over Bluetooth LE and updates the display without replying. + +Permission request smoke test: + +```bash +python main.py daemon-send --op permission.request --timeout 35 --json '{ + "v": 1, + "kind": "permission.request", + "event_id": "evt_manual", + "session_id": "ses_manual", + "permission_id": "perm_manual", + "requires_reply": true, + "payload": { + "id": "perm_manual", + "sessionID": "ses_manual", + "type": "bash", + "title": "Run idf.py build", + "metadata": { + "command": "idf.py build" + } + } +}' +``` + +The ESP-VoCat device should display a permission prompt: + +| ESP-VoCat input | Device reply | +|-----------------|-----------------| +| Single click | `decision: "once"` | +| Long press | `decision: "reject"` | +| 30s timeout | `decision: "reject"` | + +This manual daemon test exercises the same request/response path that the OpenCode Plugin uses. + +### Install the OpenCode Demo Plugin + +The OpenCode demo plugin is included in ESP-IDF under `tools/ble/ble_uart_bridge/demos/opencode`. + +Project-local install: + +```bash +mkdir -p /.opencode/plugins/opencode-ble-uart-bridge +cp $IDF_PATH/tools/ble/ble_uart_bridge/demos/opencode/src/*.ts \ + /.opencode/plugins/opencode-ble-uart-bridge/ +``` + +User-level install: + +```bash +mkdir -p ~/.config/opencode/plugins/opencode-ble-uart-bridge +cp $IDF_PATH/tools/ble/ble_uart_bridge/demos/opencode/src/*.ts \ + ~/.config/opencode/plugins/opencode-ble-uart-bridge/ +``` + +Then configure OpenCode. For a project-local install, put the following in `/opencode.json` or merge it into an existing config: + +```json +{ + "$schema": "https://opencode.ai/config.json", + "plugin": [ + ".opencode/plugins/opencode-ble-uart-bridge/opencode-ble-uart-bridge.ts" + ], + "permission": { + "edit": "ask" + } +} +``` + +For a user-level install, point `plugin` at the installed file under `~/.config/opencode/plugins/opencode-ble-uart-bridge/`. Use an absolute home path if the config loader does not expand `~`. + +Useful plugin environment variables: + +```bash +export OPENCODE_BLE_DAEMON_URL="http://127.0.0.1:8888" +export OPENCODE_BLE_DECISION_TIMEOUT_SECONDS=60 +export OPENCODE_BLE_DEBUG=1 +``` + +Restart OpenCode after changing plugin files, `opencode.json`, or these environment variables. + +### Run the OpenCode Demo + +1. Keep the firmware running and advertising/connected. +2. Keep the ESP-BLE-UART Daemon running on `127.0.0.1:8888`. +3. Start OpenCode in the project where the plugin is configured. +4. Trigger a permission prompt, for example an edit operation when `permission.edit` is set to `ask`. + +Expected behavior: + +- OpenCode session status is forwarded as best-effort `session.status` updates. +- ESP-VoCat shows busy/idle/retry expressions. +- Permission prompts appear on ESP-VoCat with compact metadata such as command, path, or URL. +- Single click returns `once` to OpenCode. +- Long press or timeout returns `reject`. + +To demonstrate bash or tool execution permissions, ensure the OpenCode permission config is set to prompt for that tool category. Otherwise, use an edit permission as the primary trigger. + +

+ ESP-VoCat Asking For Permission +
ESP-VoCat Asking For Permission +

+ +> **Note:** For a comprehensive understanding of Bluetooth Low Energy, see the [Bluetooth LE Overview](../../../docs/en/api-guides/ble/overview.rst). For Bluetooth LE connection management and data exchange, refer to the [Bluetooth LE Multi-Connection Guide](../../../docs/en/api-guides/ble/ble-multiconnection-guide.rst). + +## Protocol Reference + +The firmware protocol is documented in the `ble_uart_service` example's `json_format.md` in the [esp-iot-solution](https://github.com/espressif/esp-iot-solution) repository (`examples/bluetooth/ble_uart_service/json_format.md`). The outer daemon envelope has the following format: + +``` +{"v":1,"id":"","op":"","data":{}} +``` + +- `id` is non-empty for request/response operations such as `permission.request`. +- `id` is empty for fire-and-forget notifications such as `session.status` and `permission.cancel`. +- Device replies echo the same non-empty `id` and return either `ok/data` or `ok:false/error`. + +Example permission request over JSONL on Bluetooth LE: + +```json +{ + "v": 1, + "id": "perm-001", + "op": "permission.request", + "data": { + "v": 1, + "kind": "permission.request", + "event_id": "evt_...", + "session_id": "ses_...", + "permission_id": "perm_...", + "requires_reply": true, + "payload": { + "id": "perm_...", + "sessionID": "ses_...", + "type": "bash", + "title": "Run idf.py build", + "metadata": { + "command": "idf.py build" + } + } + } +} +``` + +Example device response: + +```json +{ + "v": 1, + "id": "perm-001", + "ok": true, + "data": { + "decision": "once", + "message": "Approved from BLE device" + } +} +``` + +`permission.cancel` clears a stale prompt without sending a later decision. This covers the case where the user answers from the OpenCode TUI before interacting with ESP-VoCat. + +## Troubleshooting + +- **No devices found:** confirm host Bluetooth access, firmware advertising, and proximity. Start with `list-devices` and `connection-check`. +- **Console works but daemon does not:** ensure Console is closed; this firmware accepts only one Bluetooth LE connection at a time. +- **Daemon disconnects:** the daemon does not run a background reconnect loop. When the next `/request` or `/notify` HTTP call arrives, it attempts an on-demand reconnect. If the device is unreachable for several consecutive attempts, the daemon exits automatically. Use `daemon-status` to check the current connection state and reconnect failure count. +- **OpenCode does not forward events:** confirm `OPENCODE_BLE_DAEMON_URL`, run `daemon-status`, and restart OpenCode after config changes. +- **Permission request times out:** confirm the device received a non-empty request `id`, no older prompt is pending, and the key was pressed before the timeout. +- **Unexpected rejections:** the demo is designed to fail closed. If the Bluetooth LE link, daemon, plugin, or device decision handling fails, the OpenCode side rejects rather than silently approves. +- **Pairing fails:** check the passkey printed in firmware logs and confirm the same value on the central. +- **Insufficient authentication:** if the connection or characteristic access fails with an authentication error, pair the device through the system Bluetooth settings first and enter the six-digit passkey shown in the firmware monitor log. Some desktop Bluetooth LE stacks require explicit system-level pairing before GATT operations succeed. + +## Extension Ideas + +- Add more input gestures for `always`, `edit`, or `deny for session`. +- Add richer display layouts for command/path/URL metadata. +- Add device-side settings for prompt timeout. +- Add an allowlist for low-risk commands. +- Add integration tests with a mocked daemon and simulated firmware replies. +- Replace JSONL with a compact binary protocol if a production product requires lower overhead. + +## Summary + +Each layer in this demo is small and independently testable: Console validates the raw BLE UART path, the daemon turns one Bluetooth LE connection into a local HTTP bridge, the OpenCode Plugin maps editor events to daemon requests, and ESP-VoCat provides the physical UI. Any layer can be replaced without affecting the others. diff --git a/examples/bluetooth/ble_uart_service/OPENCODE_COMPANION_CN.md b/examples/bluetooth/ble_uart_service/OPENCODE_COMPANION_CN.md new file mode 100644 index 00000000000..52988d08d2b --- /dev/null +++ b/examples/bluetooth/ble_uart_service/OPENCODE_COMPANION_CN.md @@ -0,0 +1,472 @@ + + + +# 使用 ESP-BLE-UART 与 ESP-VoCat 构建 OpenCode 伴侣设备 + +> [English](OPENCODE_COMPANION.md) + +## 介绍 + +本文档介绍如何使用 ESP-BLE-UART 和 ESP-VoCat 构建一个 OpenCode 的物理伴侣设备。该设备在显示屏上反映当前会话 (Session) 状态,呈现权限请求 (Permission Request) 供用户审批,并通过单键输入将权限决策返回给 OpenCode。BLE UART 作为设备与主机侧编辑器会话之间的传输层。 + +本教程分为两个部分。第一部分使用 **ESP-BLE-UART 控制台 (Console)** 搭配 `ble_uart_service` 回显服务器 (Echo Server),验证主机是否能够发现、连接 BLE UART 设备并完成数据交换。第二部分引入 `ble_uart_service` 示例固件(运行于 ESP-VoCat 开发板)、**ESP-BLE-UART 守护进程 (Daemon)** 和 **OpenCode 插件 (Plugin)**,使设备能够接收会话状态更新,并将 `once` / `reject` 权限决策返回给 OpenCode。 + +

+ ESP-VoCat 与 OpenCode 协同工作 +
ESP-VoCat 与 OpenCode 协同工作 +

+ +## 学习目标 + +- 了解 BLE UART 服务及其 GATT 约定 +- 掌握构建和烧录 ESP-BLE-UART 回显服务器的方法 +- 理解在 BLE UART 上运行的 JSON Lines 协议 +- 掌握 ESP-BLE-UART 守护进程和 OpenCode 插件的配置方法 + +## 前置条件 + +- 主机具备可用的蓝牙适配器和扫描/连接权限。 +- ESP-IDF 环境已导出。 +- 回显服务器冒烟测试可使用 `ble_uart_service` 支持的任意目标芯片 (Target)。 +- 完整 OpenCode UI 演示需要以下环境: + - [ESP-VoCat](https://docs.espressif.com/projects/esp-dev-kits/en/latest/esp32s3/esp-vocat/index.html) 开发板(基于 ESP32-S3),配备圆形触摸显示屏和单键输入。BLE UART 传输层可以复用,但显示、触摸和表情 UI 为该示例的板级特性。该示例维护在 [esp-iot-solution](https://github.com/espressif/esp-iot-solution) 仓库的 `examples/bluetooth/ble_uart_service` 路径下,支持的板型、依赖版本和构建说明请参考示例 README。 + - `ble_uart_service` 示例首次编译配置时需要联网下载 `emote_assets.bin`;离线或内网环境下,请将 `EMOTE_ASSETS_BIN` 设置为本地路径以覆盖下载。 + - 安装 OpenCode 以运行插件演示。 + +安装主机侧 ESP-BLE-UART 桥接工具 (Bridge) 依赖: + +```bash +cd $IDF_PATH +. ./export.sh +python -m pip install -r tools/ble/ble_uart_bridge/requirements.txt +``` + +Windows 下请使用 ESP-IDF 根目录中的 `export.bat` 或 `export.ps1`,不要使用 `. ./export.sh`。 + +## 第一部分:ESP-BLE-UART 控制台验证 + +### BLE UART 简介 + +Bluetooth LE 协议中并没有传统串口意义上的 UART 外设。BLE UART 服务 (BLE UART Service) 是一种 GATT 约定:一个特征值 (Characteristic) 作为主机写入设备的 RX 通道,另一个特征值作为设备通知 (Notify) 给主机的 TX 通道。`ble_uart_service` 中的回显服务器使用 Nordic UART Service 风格的 UUID,将收到的字节通过 TX Notify 原样发回,适合用于验证主机侧控制台链路。 + +传输层只负责搬运字节。第一部分中,这些字节为普通回显文本;第二部分中,运行在 ESP-VoCat 上的 `ble_uart_service` 示例固件会在同一条 BLE UART 通道上叠加 JSONL 协议。 + +### 构建并烧录 ESP-BLE-UART 回显服务器 + +```bash +cd $IDF_PATH/examples/bluetooth/ble_uart_service +idf.py set-target esp32s3 # 或其他支持的 target +idf.py build flash monitor +``` + +配对期间请保持串口监视器打开。中央设备 (Central) 提示输入配对密钥 (Passkey) 时,输入固件日志中打印的六位数字即可。固件控制台输出应类似以下日志(地址和设备名后缀会有所不同): + +``` +I (548) ble_uart: BLE host task started +I (548) ble_uart: registered service 0x1800 handle=1 +I (548) ble_uart: registered chr 0x2a00 def=2 val=3 +I (548) ble_uart: registered chr 0x2a01 def=4 val=5 +I (558) ble_uart: registered service 0x1801 handle=6 +I (558) ble_uart: registered chr 0x2a05 def=7 val=8 +I (568) ble_uart: registered chr 0x2b3a def=10 val=11 +I (568) ble_uart: registered chr 0x2b29 def=12 val=13 +I (578) ble_uart: registered service 6e400001-b5a3-f393-e0a9-e50e24dcca9e handle=14 +I (578) ble_uart: registered chr 6e400002-b5a3-f393-e0a9-e50e24dcca9e def=15 val=16 +I (588) ble_uart: registered chr 6e400003-b5a3-f393-e0a9-e50e24dcca9e def=17 val=18 +I (608) NimBLE: GAP procedure initiated: stop advertising. +I (608) NimBLE: GAP procedure initiated: stop advertising. +I (608) ble_uart: addr=74:4d:bd:a9:ed:72 +I (608) NimBLE: GAP procedure initiated: advertise; +I (618) NimBLE: disc_mode=2 +I (618) NimBLE: adv_channel_map=0 own_addr_type=0 adv_filter_policy=0 adv_itvl_min=0 adv_itvl_max=0 +I (628) NimBLE: +I (628) ble_uart: advertising as 'BleUart-ED72' +I (628) main_task: Returned from app_main() +``` + +`ble_uart: addr=74:4d:bd:a9:ed:72` 这条日志指示了设备的蓝牙 MAC 地址为 `74:4D:BD:A9:ED:72`。设备以最后一行 `ble_uart: advertising as 'BleUart-XXXX'` 中显示的名称广播。 + +当中央设备发起连接时,固件会输出配对密钥提示。如果系统蓝牙对话框或 `connection-check` 命令要求输入配对密钥,请输入监视器中显示的六位数字: + +``` +W (19298) ble_uart: +-----------------------------+ +W (19298) ble_uart: | BLE PAIRING PASSKEY: | +W (19298) ble_uart: | 617138 | +W (19298) ble_uart: +-----------------------------+ +``` + +### 扫描 BLE UART 设备 + +打开第二个终端: + +```bash +cd $IDF_PATH/tools/ble/ble_uart_bridge +python main.py list-devices +``` + +将输出中的设备标识记为 `DEVICE_ID`。可以通过输出中的 MAC 地址或设备名来判断是否扫描到了目标设备。在 Linux 上,设备 MAC 地址会直接显示: + +``` +> python main.py list-devices + +2026-06-05 11:19:56.728 | INFO | src.core.scanner:scan_devices:42 - Scanning for nearby BLE devices in 5.0s... +2026-06-05 11:19:57.108 | SUCCESS | src.core.scanner:on_detect:39 - Found: 74:4D:BD:A9:ED:72, with name BleUart-ED72, rssi=-46 +``` + +此输出中的 MAC 地址 `74:4D:BD:A9:ED:72` 和设备名 `BleUart-ED72` 均与上述固件日志一致。 + +在 macOS 上,由于系统限制,工具无法显示设备的真实蓝牙 MAC 地址,而是分配一个 CoreBluetooth UUID 作为设备标识。需要通过匹配 `list-devices` 输出中的设备名(此例中为 `BleUart-ED72`)与固件日志中的广播名,找到对应的 UUID: + +``` +> python main.py list-devices + +2026-06-05 11:19:56.728 | INFO | src.core.scanner:scan_devices:42 - Scanning for nearby BLE devices in 5.0s... +2026-06-05 11:19:57.108 | SUCCESS | src.core.scanner:on_detect:39 - Found: 5BA2476C-CDD2-BF3F-F98C-252CFA45F8B5, with name BleUart-ED72, rssi=-46 +``` + +此例中的 `5BA2476C-CDD2-BF3F-F98C-252CFA45F8B5` 即 macOS 下用作 `DEVICE_ID` 的 CoreBluetooth UUID。 + +### 打开控制台前检查连接 + +```bash +python main.py connection-check "" +``` + +该命令会连接设备、发现 BLE UART 服务和特征值,然后断开。在 Linux 或 Windows 上,将设备 MAC 地址作为 `DEVICE_ID` 传入: + +``` +> python main.py connection-check 74:4D:BD:A9:ED:72 + +2026-06-05 12:06:27.252 | INFO | src.core.bridge:connect:139 - Connecting to 74:4D:BD:A9:ED:72... +2026-06-05 12:06:37.460 | SUCCESS | src.core.bridge:connect:206 - Succeeded to connect to 74:4D:BD:A9:ED:72! +2026-06-05 12:06:37.461 | INFO | src.core.bridge:_disconnect_locked:86 - Disconnecting from 74:4D:BD:A9:ED:72... +2026-06-05 12:06:37.461 | INFO | src.core.bridge:_handle_disconnect:120 - Disconnected from 74:4D:BD:A9:ED:72 +``` + +在 macOS 上,改用 CoreBluetooth UUID: + +``` +> python main.py connection-check 5BA2476C-CDD2-BF3F-F98C-252CFA45F8B5 + +2026-06-05 12:06:27.252 | INFO | src.core.bridge:connect:139 - Connecting to 5BA2476C-CDD2-BF3F-F98C-252CFA45F8B5... +2026-06-05 12:06:37.460 | SUCCESS | src.core.bridge:connect:206 - Succeeded to connect to 5BA2476C-CDD2-BF3F-F98C-252CFA45F8B5! +2026-06-05 12:06:37.461 | INFO | src.core.bridge:_disconnect_locked:86 - Disconnecting from 5BA2476C-CDD2-BF3F-F98C-252CFA45F8B5... +2026-06-05 12:06:37.461 | INFO | src.core.bridge:_handle_disconnect:120 - Disconnected from 5BA2476C-CDD2-BF3F-F98C-252CFA45F8B5 +``` + +如果此步骤失败,请先解决扫描、配对、权限或广播问题,再继续使用守护进程或 OpenCode。 + +### 打开 ESP-BLE-UART 控制台 + +```bash +python main.py console "" --terminator lf +``` + +在控制台中输入一行短文本并按 Enter: + +``` +hello from console +``` + +预期结果: + +``` +[INFO] Connected to 68:B6:B3:55:41:76 +[TX] hello from console +[RX] hello from console +``` + +- Bluetooth LE 地址因设备而异。 +- 控制台中显示输入内容对应的 `[TX]` 行。 +- ESP-BLE-UART 示例将相同字节回显,并显示为 `[RX]` 输出。 + +至此,Bluetooth LE 扫描、连接、主机到设备写入、设备到主机通知 (Notify) 均已验证通过。JSONL 协议(用于 ESP-VoCat 示例)、守护进程和 OpenCode 插件是叠加在该链路之上的应用层,不替代该链路本身。 + +更多控制台选项(如十六进制模式、带响应写入 (Write with Response)、不同换行符)请参考 [`tools/ble/ble_uart_bridge/docs/Quick-Start-BLE-UART-Console.md`](../../../tools/ble/ble_uart_bridge/docs/Quick-Start-BLE-UART-Console.md)。 + +## 第二部分:ESP-VoCat OpenCode 伴侣设备 + +### ESP-VoCat 简介 + +[ESP-VoCat](https://docs.espressif.com/projects/esp-dev-kits/en/latest/esp32s3/esp-vocat/index.html) 是基于 ESP32-S3 模组的智能 AI 开发套件,配备圆形触摸显示屏和单键输入。 + +[esp-iot-solution](https://github.com/espressif/esp-iot-solution) 仓库中包含一个 `ble_uart_service` 示例(位于 `examples/bluetooth/ble_uart_service`),该示例运行在 ESP-VoCat 开发板上。此示例固件将会话状态渲染为表情动画,并将权限请求呈现到屏幕上供用户物理审批。支持的板型、所需组件版本和构建说明请参考 esp-iot-solution 中的示例 README。 + +### 选择 JSON Lines 的原因 + +Bluetooth LE 写入按 ATT MTU 分包,不等同于应用层消息边界。ESP-VoCat 的 OpenCode 流程在 BLE UART 上使用 JSON Lines (JSONL)。选择 JSONL 的原因:日志和控制台中可直接阅读,便于手动测试时输入,固件侧可用 cJSON 解析,且同时支持请求/响应和即发即弃两种消息模式。 + +### 架构概述 + +ESP-BLE-UART 桥接工具和 OpenCode 演示插件已包含在 ESP-IDF 的 master 及 `release/v5.2` 及以上 release 分支中,位于 `tools/ble/ble_uart_bridge/` 目录下。`ble_uart_service` 示例实现设备侧协议,源码位于 [esp-iot-solution](https://github.com/espressif/esp-iot-solution) 仓库。 + +```mermaid +flowchart LR + OC[OpenCode] -->|session.status / permission.asked| Plugin[OpenCode Plugin] + Plugin -->|POST /notify| Daemon[ESP-BLE-UART Daemon] + Plugin -->|POST /request| Daemon + Daemon -->|Bluetooth LE write: NUS RX JSONL| ESP[ble_uart_service 示例] + ESP -->|Bluetooth LE notify: NUS TX JSONL| Daemon + Daemon -->|HTTP response| Plugin + Plugin -->|permission reply| OC + ESP --> Display[Emote + Tip Text] + ESP --> Key[Single Key: once / reject] +``` + +各层均可独立替换: + +- 控制台验证原始 BLE UART 链路; +- 守护进程维持一个 Bluetooth LE 连接并提供本地 HTTP API; +- OpenCode 插件将编辑器事件转换为守护进程请求/通知; +- `ble_uart_service` 示例在 ESP-VoCat 设备上显示状态和权限提示 (Permission Prompt)。 + +### 启动 ESP-BLE-UART 守护进程 + +首先从 [esp-iot-solution](https://github.com/espressif/esp-iot-solution) 仓库将 `ble_uart_service` 示例烧录到 ESP-VoCat 开发板上,该示例与第一部分的控制台回显服务器为不同应用: + +```bash +# 如尚未克隆 esp-iot-solution,先执行克隆 +git clone https://github.com/espressif/esp-iot-solution.git +cd esp-iot-solution/examples/bluetooth/ble_uart_service +idf.py set-target esp32s3 +idf.py build flash monitor +``` + +依赖版本和板级配置详见 esp-iot-solution 中的示例 README。 + +然后重新扫描设备,并将 ESP-VoCat 的设备标识记为 `VOCAT_DEVICE_ID`: + +```bash +cd $IDF_PATH/tools/ble/ble_uart_bridge +python main.py list-devices +python main.py connection-check "" +``` + +使用该 ESP-VoCat 设备启动守护进程: + +```bash +cd $IDF_PATH/tools/ble/ble_uart_bridge +python main.py daemon "" --host 127.0.0.1 --port 8888 +``` + +> **Note:** 守护进程 HTTP API 未内置认证机制。除非自行添加访问控制,否则请保持绑定在 `127.0.0.1`。 + +在另一个终端检查守护进程状态: + +```bash +cd $IDF_PATH/tools/ble/ble_uart_bridge +python main.py daemon-status +``` + +### 在接入 OpenCode 前通过守护进程验证 ESP-VoCat + +不要使用通用 `echo` 请求验证 ESP-VoCat,该固件未实现 echo 操作。请使用 [esp-iot-solution 示例中的 json_format.md](https://github.com/espressif/esp-iot-solution/blob/master/examples/bluetooth/ble_uart_service/json_format.md) 中定义的操作。 + +会话状态 (Session Status) 冒烟测试: + +```bash +python main.py daemon-notify --op session.status --json '{ + "v": 1, + "kind": "session.status", + "event_id": "evt_manual", + "session_id": "ses_manual", + "requires_reply": false, + "payload": { + "type": "busy" + } +}' + +python main.py daemon-notify --op session.status --json '{ + "v": 1, + "kind": "session.status", + "event_id": "evt_manual", + "session_id": "ses_manual", + "requires_reply": false, + "payload": { + "type": "idle" + } +}' +``` + +CLI 将每个 JSON 对象作为守护进程信封 (Envelope) 的 `data` 字段发送,并设置 `op: "session.status"` 和 `id: ""`。固件通过 Bluetooth LE 收到完整 JSONL 信封后,更新显示但不返回响应。 + +权限请求 (Permission Request) 冒烟测试: + +```bash +python main.py daemon-send --op permission.request --timeout 35 --json '{ + "v": 1, + "kind": "permission.request", + "event_id": "evt_manual", + "session_id": "ses_manual", + "permission_id": "perm_manual", + "requires_reply": true, + "payload": { + "id": "perm_manual", + "sessionID": "ses_manual", + "type": "bash", + "title": "Run idf.py build", + "metadata": { + "command": "idf.py build" + } + } +}' +``` + +ESP-VoCat 设备应显示一个权限提示: + +| ESP-VoCat 输入 | 设备响应 | +|----------------|---------------------| +| 单击 | `decision: "once"` | +| 长按 | `decision: "reject"` | +| 30 秒无输入 | `decision: "reject"` | + +该手动守护进程测试验证的是 OpenCode 插件后续使用的同一条请求/响应链路。 + +### 安装 OpenCode 演示插件 + +OpenCode 演示插件已包含在 ESP-IDF 中,位于 `tools/ble/ble_uart_bridge/demos/opencode`。 + +项目级安装: + +```bash +mkdir -p /.opencode/plugins/opencode-ble-uart-bridge +cp $IDF_PATH/tools/ble/ble_uart_bridge/demos/opencode/src/*.ts \ + /.opencode/plugins/opencode-ble-uart-bridge/ +``` + +用户级安装: + +```bash +mkdir -p ~/.config/opencode/plugins/opencode-ble-uart-bridge +cp $IDF_PATH/tools/ble/ble_uart_bridge/demos/opencode/src/*.ts \ + ~/.config/opencode/plugins/opencode-ble-uart-bridge/ +``` + +然后配置 OpenCode。项目级安装时,将以下内容放入 `/opencode.json`,或合并到已有配置中: + +```json +{ + "$schema": "https://opencode.ai/config.json", + "plugin": [ + ".opencode/plugins/opencode-ble-uart-bridge/opencode-ble-uart-bridge.ts" + ], + "permission": { + "edit": "ask" + } +} +``` + +用户级安装时,将 `plugin` 指向 `~/.config/opencode/plugins/opencode-ble-uart-bridge/` 下的入口文件。如果配置加载器不展开 `~`,请使用绝对路径。 + +常用插件环境变量: + +```bash +export OPENCODE_BLE_DAEMON_URL="http://127.0.0.1:8888" +export OPENCODE_BLE_DECISION_TIMEOUT_SECONDS=60 +export OPENCODE_BLE_DEBUG=1 +``` + +修改插件文件、`opencode.json` 或上述环境变量后,需要重启 OpenCode。 + +### 运行 OpenCode 演示 + +1. 保持固件运行,并处于广播或已连接状态。 +2. 保持 ESP-BLE-UART 守护进程运行在 `127.0.0.1:8888`。 +3. 在已配置插件的项目中启动 OpenCode。 +4. 触发一次权限提示,例如当 `permission.edit` 设置为 `ask` 时执行编辑操作。 + +预期结果: + +- OpenCode 会话状态以尽力传递 (Best-effort) 方式转发为 `session.status`; +- ESP-VoCat 显示 busy/idle/retry 表情; +- 权限提示显示到 ESP-VoCat,附带命令、路径、URL 等紧凑元数据 (Metadata); +- 单击返回 `once` 给 OpenCode; +- 长按或超时返回 `reject`。 + +如需演示 bash 命令或工具执行权限,请确认 OpenCode 权限配置确实会对该工具类别发起询问;否则建议使用编辑权限 (Edit Permission) 作为触发路径。 + +

+ ESP-VoCat 请求权限 +
ESP-VoCat 请求权限 +

+ +> **Note:** 如需全面了解 Bluetooth Low Energy,请参见 [Bluetooth LE 概览](../../../docs/zh_CN/api-guides/ble/overview.rst)。关于 Bluetooth LE 连接管理和数据交换,请参考 [Bluetooth LE 多连接指南](../../../docs/zh_CN/api-guides/ble/ble-multiconnection-guide.rst)。 + +## 协议参考 + +固件协议详见 `ble_uart_service` 示例中的 `json_format.md`,位于 [esp-iot-solution](https://github.com/espressif/esp-iot-solution) 仓库 (`examples/bluetooth/ble_uart_service/json_format.md`)。外层守护进程信封格式如下: + +``` +{"v":1,"id":"","op":"","data":{}} +``` + +- `id` 非空表示请求/响应操作,例如 `permission.request`; +- `id` 为空表示即发即弃通知,例如 `session.status` 和 `permission.cancel`; +- 设备响应会带回相同的非空 `id`,并返回 `ok/data` 或 `ok:false/error`。 + +Bluetooth LE 上的 JSONL 权限请求示例: + +```json +{ + "v": 1, + "id": "perm-001", + "op": "permission.request", + "data": { + "v": 1, + "kind": "permission.request", + "event_id": "evt_...", + "session_id": "ses_...", + "permission_id": "perm_...", + "requires_reply": true, + "payload": { + "id": "perm_...", + "sessionID": "ses_...", + "type": "bash", + "title": "Run idf.py build", + "metadata": { + "command": "idf.py build" + } + } + } +} +``` + +设备响应示例: + +```json +{ + "v": 1, + "id": "perm-001", + "ok": true, + "data": { + "decision": "once", + "message": "Approved from BLE device" + } +} +``` + +`permission.cancel` 用于清理过期的权限提示,不再发送迟到的决策。适用于用户已在 OpenCode 终端界面 (TUI) 中处理了权限请求、但 ESP-VoCat 仍在显示权限提示的情况。 + +## 故障排查 + +- **扫描不到设备:** 确认主机蓝牙权限、固件正在广播、设备距离足够近。先用 `list-devices` 和 `connection-check` 排查。 +- **控制台可用但守护进程不可用:** 确认控制台已关闭;该固件同一时间只接受一个 Bluetooth LE 连接。 +- **守护进程断开连接:** 守护进程没有后台自动重连循环。当下一次 `/request` 或 `/notify` HTTP 请求到达时,守护进程会尝试按需重连。如果设备连续多次不可达,守护进程将自动退出。可使用 `daemon-status` 查看当前连接状态和重连失败计数。 +- **OpenCode 未转发事件:** 确认 `OPENCODE_BLE_DAEMON_URL`,运行 `daemon-status`,修改配置后重启 OpenCode。 +- **权限请求超时:** 确认设备收到的是带非空 `id` 的 `permission.request`,没有旧的权限提示仍在等待中,且用户在超时前已按下按键。 +- **出现意外拒绝:** 演示采用失败即关闭 (Fail-closed) 设计。Bluetooth LE 链路、守护进程、插件或设备决策处理中任一环节失败,OpenCode 侧都会拒绝而非静默允许。 +- **配对失败:** 检查固件日志中打印的配对密钥 (Passkey),确认中央设备端输入的是同一个值。 +- **认证不足 (Insufficient Authentication):** 如果连接或特征值访问时报认证错误,请先通过系统蓝牙设置完成设备配对,并输入固件监视器日志中显示的六位数配对密钥。部分桌面 Bluetooth LE 协议栈要求在系统层面完成显式配对后,GATT 操作才能成功。 + +## 扩展方向 + +- 增加更多输入手势,支持 `always`、`edit` 或"本会话拒绝"。 +- 为命令/路径/URL 元数据设计更丰富的显示布局。 +- 在设备侧增加权限提示超时设置。 +- 为低风险命令增加允许列表 (Allowlist)。 +- 使用模拟守护进程和模拟固件响应进行集成测试。 +- 如需更低开销,将 JSONL 替换为紧凑二进制协议。 + +## 总结 + +本演示中的每一层均保持简单且可独立测试:控制台验证原始 BLE UART 链路,守护进程将一个 Bluetooth LE 连接转换为本地 HTTP 桥接,OpenCode 插件将编辑器事件映射为守护进程请求,ESP-VoCat 提供物理 UI。任意一层均可单独替换而不影响其余部分。 diff --git a/examples/bluetooth/ble_uart_service/README.md b/examples/bluetooth/ble_uart_service/README.md index 4945a72346b..ffedbe8f70a 100644 --- a/examples/bluetooth/ble_uart_service/README.md +++ b/examples/bluetooth/ble_uart_service/README.md @@ -1,4 +1,4 @@ -# BLE UART Service Example — NimBLE / Bluedroid +# ESP-BLE-UART Example — NimBLE / Bluedroid | Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C5 | ESP32-C6 | ESP32-C61 | ESP32-H2 | ESP32-S3 | | ----------------- | ----- | -------- | -------- | -------- | -------- | --------- | -------- | -------- | @@ -24,12 +24,19 @@ ble_uart_install(&cfg); // NimBLE host + BLE UART GATT service ble_uart_open(); // start advertising + auto-encrypt ``` -…and two matching tear-down calls if your app ever needs to power -BLE off at runtime: +If your app powers BLE off at runtime, use **one** of the release paths +in [PORTING.md §5.3](../common/ble_uart/PORTING.md#53-lifecycle--bring-up-and-release) +(this example uses Path A from `app_main`): + +| Path | When | Calls | +| --- | --- | --- | +| **A — sync** (default) | Shutdown from a normal task (button, Wi-Fi, `app_main`) | `ble_uart_close()` → `ble_uart_uninstall()` | +| **B — async** | Shutdown triggered inside `on_event` / `on_rx` | `close_async()` in callback → `CLOSED` sets flag → **`uninstall()` on a separate app task** (not inside `CLOSED`) | ```c -ble_uart_close(); // stop advertising / disconnect / halt host -ble_uart_uninstall(); // free the NimBLE port + reset state +/* Path A — this example style */ +ble_uart_close(); +ble_uart_uninstall(); ``` When a central connects, the firmware automatically initiates LE Secure @@ -47,49 +54,135 @@ back with `ble_uart_tx()`. | TX (out) | `6e400003-b5a3-f393-e0a9-e50e24dcca9e` | Notify (auto-CCCD) | encrypted, authenticated | The `_ENC | _AUTHEN` flags are turned on only when `cfg.encrypted = true` -(the default in this example). +(the default in this example). The two flags can be controlled +independently via `cfg.security.mitm` (drops `_AUTHEN`) and the +combined `cfg.security.{sc,bonding,mitm}` set (all OFF drops `_ENC` +too) — see PORTING.md §5.6. ## Files | File | Lines | Role | | --- | ---: | --- | -| `main/main.c` | ~70 | NVS init, MAC-derived device name, install + open, RX echo handler. Identical for both backends. | +| `main/main.c` | ~200 | NVS init, install + open with `-XXXX` device name (Kconfig prefix + BT MAC suffix), RX echo handler, lifecycle/link-state event sink, bonded-peer dump on boot. Identical for both backends. | +| `main/Kconfig.projbuild` | ~50 | Example-local `EXAMPLE_CUSTOM_ADV_DATA` switch — toggles the `ble_uart_config_t::adv_data` demo path in `main.c`. | | `CMakeLists.txt` (root) | ~15 | `list(APPEND EXTRA_COMPONENT_DIRS .../common/ble_uart)` before `project()` so `main` can `REQUIRES ble_uart`. | -| `../common/ble_uart/ble_uart.h` | ~155 | Stack-agnostic public API: 3-field config + 4 lifecycle functions + TX/status + UUID + `BLE_UART_E*` return codes. No NimBLE / Bluedroid types leak through. | -| `../common/ble_uart/ble_uart_nimble.c` | ~650 | NimBLE backend: host bring-up, BLE UART GATT service via `ble_gatts_add_svcs`, advertising, pairing, install/open/close/uninstall. Active when `CONFIG_BT_NIMBLE_ENABLED=y`. | -| `../common/ble_uart/ble_uart_bluedroid.c` | ~1020 | Bluedroid backend: controller + host enable, BLE UART GATT service via `esp_ble_gatts_create_attr_tab` (service-table API), advertising, pairing, full PREP/EXEC long-write reassembly, install/open/close/uninstall. Active when `CONFIG_BT_BLUEDROID_ENABLED=y`. | -| `../common/ble_uart/Kconfig` | ~30 | Device-name prefix + RX scratch size (`menuconfig → Component configuration → BLE UART library`). | -| `../common/ble_uart/PORTING.md` | ~724 | Porting and API guide (integration, CMake, sdkconfig, thread safety). | +| `../common/ble_uart/ble_uart.h` | ~640 | Stack-agnostic public API: configuration struct (preset + per-feature security overrides + custom adv payload + RX/event callbacks) + lifecycle (install/open/close/close_async/uninstall) + TX + pairing replies + bond-management + status + UUID + `BLE_UART_E*` return codes. No NimBLE / Bluedroid types leak through. | +| `../common/ble_uart/ble_uart_nimble.c` | ~1290 | NimBLE backend: host bring-up, BLE UART GATT service via `ble_gatts_add_svcs`, advertising (default + raw), pairing (incl. Passkey Entry / Numeric Comparison), bond store, async close, install/open/close/uninstall. Active when `CONFIG_BT_NIMBLE_ENABLED=y`. | +| `../common/ble_uart/ble_uart_bluedroid.c` | ~1660 | Bluedroid backend: controller + host enable, BLE UART GATT service via `esp_ble_gatts_create_attr_tab` (service-table API), advertising (default + raw), pairing (incl. Passkey Entry / Numeric Comparison), bond store, async close, full PREP/EXEC long-write reassembly, install/open/close/uninstall. Active when `CONFIG_BT_BLUEDROID_ENABLED=y`. | +| `../common/ble_uart/Kconfig` | ~30 | Device name prefix + RX scratch size (`menuconfig → Component configuration → ESP-BLE-UART library`). | +| `../common/ble_uart/PORTING.md` | ~1300 | Porting and API guide (integration, CMake, sdkconfig, security model, custom advertising, bond management, thread safety). | | `sdkconfig.defaults` | — | Default: NimBLE backend, MTU 512, SC + bonding + persistent NVS. | | `sdkconfig.bluedroid` | — | Overlay: switch to Bluedroid backend (used via `-D SDKCONFIG_DEFAULTS=...`, see "Choosing the host stack" below). | ## Public API ```c -typedef void (*ble_uart_rx_cb_t)(const uint8_t *data, size_t len); +typedef void (*ble_uart_rx_cb_t) (const uint8_t *data, size_t len); +typedef void (*ble_uart_evt_cb_t)(const ble_uart_evt_t *evt); typedef struct { - bool encrypted; /* SC + Bonding + MITM in one knob */ - const char *device_name; - ble_uart_rx_cb_t ble_uart_on_rx; + ble_uart_sec_t sc; /* AUTO / OFF / ON */ + ble_uart_sec_t bonding; + ble_uart_sec_t mitm; + ble_uart_io_cap_t io_cap; /* AUTO / NO_INPUT_OUTPUT / DISPLAY_ONLY / + KEYBOARD_ONLY / DISPLAY_YES_NO / + KEYBOARD_DISPLAY */ +} ble_uart_security_t; + +typedef struct { + bool encrypted; /* preset: SC + Bonding + MITM + DisplayOnly */ + ble_uart_security_t security; /* per-feature overrides; see PORTING.md §5.6 */ + + const char *device_name; /* ≤ BLE_UART_DEVICE_NAME_MAX (26) */ + /* Optional: raw advertising / scan-response bytes (NULL → defaults). + * Limits: adv_data_len ≤ BLE_UART_ADV_DATA_MAX (28), + * scan_rsp_data_len ≤ BLE_UART_SCAN_RSP_DATA_MAX (31). + * The 3-byte Flags AD element is prepended automatically — don't + * include it in adv_data. See PORTING.md §5.9 for examples. */ + const uint8_t *adv_data; + size_t adv_data_len; + const uint8_t *scan_rsp_data; + size_t scan_rsp_data_len; + ble_uart_rx_cb_t ble_uart_on_rx; + ble_uart_evt_cb_t on_event; /* lifecycle / link-state events; NULL drops */ } ble_uart_config_t; +typedef struct { + uint8_t bytes[6]; /* big-endian: bytes[0] is the MSB (AA:BB:CC:DD:EE:FF) */ + uint8_t type; /* BLE_UART_ADDR_TYPE_PUBLIC | _RANDOM */ +} ble_uart_addr_t; + /* Lifecycle */ -int ble_uart_install(const ble_uart_config_t *cfg); /* NimBLE host + GATT */ +int ble_uart_install(const ble_uart_config_t *cfg); /* host + GATT */ int ble_uart_open(void); /* host task + advertising */ int ble_uart_close(void); /* stop adv / disconnect / halt host */ -int ble_uart_uninstall(void); /* free NimBLE port + reset state */ +int ble_uart_close_async(void); /* same, fire-and-forget; safe from inside on_event/on_rx */ +int ble_uart_uninstall(void); /* free port + reset state */ /* Data path */ int ble_uart_tx(const uint8_t *data, size_t len); +/* Pairing replies (call from on_event for input-capable IO caps) */ +int ble_uart_passkey_reply(uint32_t passkey); /* answer PASSKEY_REQUEST */ +int ble_uart_compare_reply(bool match); /* answer NUMERIC_COMPARE */ + /* Status (best-effort snapshot) */ bool ble_uart_is_connected(void); bool ble_uart_is_subscribed(void); +/* Bond management (works after install()) */ +int ble_uart_get_bond_count(size_t *out_count); +int ble_uart_get_bonded_peers(ble_uart_addr_t *out, size_t cap, size_t *out_count); +int ble_uart_remove_peer(const ble_uart_addr_t *peer); +int ble_uart_clear_bonds(void); + extern const ble_uart_uuid128_t ble_uart_service_uuid; ``` +### Event callback + +`on_event` is invoked on the BLE host task (same context as `ble_uart_on_rx`) +with a tagged `ble_uart_evt_t`. Use `LINK_SECURE` — not `is_connected()` — +to gate any application logic that requires the channel to be encrypted / +authenticated: + +| `evt->id` | Payload | Fires when | +| ------------------------------- | ------------------------------------------------------- | ---------- | +| `BLE_UART_EVT_CONNECTED` | `connected.peer` | Physical link up | +| `BLE_UART_EVT_DISCONNECTED` | `disconnected.reason` (int, stack-specific) | Physical link down — Bluedroid: `esp_gatt_conn_reason_t`; NimBLE: BLE host return code (`BLE_HS_HCI_ERR()` for HCI) | +| `BLE_UART_EVT_SUBSCRIBED` | `subscribed.subscribed` | Central writes CCCD on TX (edge-triggered) | +| `BLE_UART_EVT_LINK_SECURE` | `link_secure.{encrypted,authenticated,bonded,key_size}` | Pairing or bonded reconnect succeeds | +| `BLE_UART_EVT_PASSKEY_DISPLAY` | `passkey.passkey` (0..999999) | SM asks the device to show a passkey | +| `BLE_UART_EVT_PASSKEY_REQUEST` | — | SM asks the user to enter a passkey shown by the central — answer with `ble_uart_passkey_reply()` | +| `BLE_UART_EVT_NUMERIC_COMPARE` | `numeric_compare.passkey` (0..999999) | SM asks the user to confirm both sides display the same value — answer with `ble_uart_compare_reply()` | +| `BLE_UART_EVT_PAIRING_FAILED` | `pairing_failed.reason` (stack-specific) | Pairing rejected or timed out | +| `BLE_UART_EVT_CLOSED` | `closed.status` (`BLE_UART_*`) | `ble_uart_close_async()` worker has finished; `BLE_UART_OK` means tear-down succeeded | + +The default passkey UART banner still prints; the callback is additive so +log-scraping tests stay compatible. Don't block in the callback. + +**Callback rules:** + +- Do **not** call `ble_uart_close()` or `ble_uart_uninstall()` from + `on_event` / `on_rx` (host task — deadlocks). +- To start teardown from a callback, call `ble_uart_close_async()` only. +- Call `ble_uart_uninstall()` from a **normal app task** after + `BLE_UART_EVT_CLOSED` with `closed.status == BLE_UART_OK` (see + [PORTING.md §5.3.2](../common/ble_uart/PORTING.md#532-path-b--release-after-a-ble-event-close_async)). + +Path B sketch (full code in PORTING.md): + +```c +case BLE_UART_EVT_PAIRING_FAILED: + ble_uart_close_async(); + break; +case BLE_UART_EVT_CLOSED: + if (e->closed.status == BLE_UART_OK) { + s_ble_closed_ok = true; /* app task calls uninstall */ + } + break; +``` + ## Choosing the host stack The same `ble_uart.h` API is implemented twice — once on top of NimBLE @@ -133,9 +226,11 @@ When neither is enabled the build fails up-front with a clear error. ```bash idf.py set-target esp32c3 # or esp32, esp32s3, esp32c6, esp32h2 ... idf.py menuconfig # optional -# Component configuration -> BLE UART library -# - BLE device name prefix (default: BleUart) +# Component configuration -> ESP-BLE-UART library +# - BLE device name prefix (default: BleUart; example appends -XXXX from BT MAC) # - RX scratch buffer size (default: 1024 bytes) +# BLE UART service example +# - Use custom advertising data (default: off) ``` Those `BLE_UART_*` options are defined in **`../common/ble_uart/Kconfig`** @@ -143,6 +238,27 @@ Those `BLE_UART_*` options are defined in **`../common/ble_uart/Kconfig`** build (this example pulls it in via `EXTRA_COMPONENT_DIRS` in the root `CMakeLists.txt`). +`EXAMPLE_CUSTOM_ADV_DATA` is example-local (`main/Kconfig.projbuild`) +and demonstrates `ble_uart_config_t::adv_data` — the field that lets +the application fully control the over-the-air advertising payload +instead of using the library default. + +When the option is on, `app_main` hands a static byte array +(`example_adv_payload[]`, top of `main.c`) to `ble_uart_install()`. +The array is just a sequence of `[length][AD type][value]` triplets; +edit it directly to advertise whatever you want — a different Local +Name, Manufacturer Specific Data, custom Service Data, additional +Service UUIDs, etc. The only hard rule is total length ≤ +`BLE_UART_ADV_DATA_MAX` (28); the 3-byte Flags AD is added by the +library and does not count against that budget. + +The GAP-service Device Name (set via `device_name` in the same +config struct) is independent and is what connected centrals read +post-pair, regardless of `adv_data`. + +With the option off the library default is used (Complete Local Name +in the primary packet, 128-bit Service UUID in the scan response). + The two security knobs are set in `sdkconfig.defaults`: ```ini @@ -154,6 +270,17 @@ Disable `cfg.encrypted` in `main.c` (set it to `false`) for plaintext operation in the lab — the GATT characteristics drop their `_ENC` flags accordingly. Production firmware should keep encryption on. +For finer control without going all-or-nothing — e.g. a displayless +gateway that wants encryption + bonding but no passkey UI, or a +device with a keypad that wants Passkey Entry / Numeric Comparison — +keep `cfg.encrypted = true` and override individual bits via +`cfg.security.{sc,bonding,mitm,io_cap}`. The input-capable IO caps +(`KEYBOARD_ONLY`, `DISPLAY_YES_NO`, `KEYBOARD_DISPLAY`) require an +`on_event` handler that wires `BLE_UART_EVT_PASSKEY_REQUEST` / +`NUMERIC_COMPARE` to `ble_uart_passkey_reply()` / +`ble_uart_compare_reply()`. See PORTING.md §5.6 for the full matrix +and worked examples. + ### Build & flash ```bash @@ -170,7 +297,7 @@ I (xxx) ble_uart: registered chr 6e400002-... def=15 val=16 I (xxx) ble_uart: registered chr 6e400003-... def=17 val=18 I (xxx) ble_uart: addr=80:7d:3a:11:22:33 I (xxx) ble_uart: BLE host task started -I (xxx) ble_uart: advertising as 'BleUart-XXXX' +I (xxx) ble_uart: advertising as 'BleUart-2233' ``` Expected boot log (Bluedroid backend): @@ -186,8 +313,10 @@ I (xxx) ble_uart: advertising started 1. On a phone, install **a BLE GATT client app** that supports scanning, pairing, characteristic write, and notify/CCCD (many mobile “BLE tools” or serial-over-BLE utilities qualify). -2. Scan, tap **Connect** on `BleUart-XXXX`. The phone prompts for a - 6-digit code. +2. Scan, tap **Connect** on `BleUart-XXXX` (prefix from + `CONFIG_BLE_UART_DEVICE_NAME_PREFIX`, `XXXX` = last two BT MAC + bytes). The phone prompts for a 6-digit + code. 3. The device prints a fresh code in a banner on UART: ``` @@ -204,8 +333,13 @@ I (xxx) ble_uart: advertising started 6. Disconnect and reconnect: no passkey prompt — the bond resumes automatically. -To wipe the bond and force a fresh passkey, run `idf.py erase-flash` -and re-flash. +To wipe the bond and force a fresh passkey there are three options: + +- Call `ble_uart_clear_bonds()` from your app (preserves the rest of NVS) +- Call `ble_uart_remove_peer(&addr)` to drop one peer (use the address + reported in `BLE_UART_EVT_CONNECTED`, or any address you happen to + have stored — Bluedroid matches by address only, NimBLE by identity) +- Run `idf.py erase-flash` and re-flash (also wipes WiFi creds, NVS, etc.) ## Adapting to your application @@ -249,6 +383,16 @@ ble_uart_open(); That's it — encrypted serial-over-BLE in 4 lines. +## OpenCode Companion + +This example serves as the transport layer for the [OpenCode Companion tutorial](OPENCODE_COMPANION.md), which walks through building a physical companion device for OpenCode using ESP-BLE-UART and ESP-VoCat. The tutorial covers: + +- **Part 1:** Using ESP-BLE-UART Console to verify the BLE UART data path (Echo Server mode). +- **Part 2:** Using the `ble_uart_service` example on ESP-VoCat with the ESP-BLE-UART Daemon and OpenCode Plugin for session status display and physical permission approval. + +See the full guide in English: [OPENCODE_COMPANION.md](OPENCODE_COMPANION.md) +Chinese version: [OPENCODE_COMPANION_CN.md](OPENCODE_COMPANION_CN.md) + ## Troubleshooting - **Phone shows "pairing failed"** — the central asked for "Just Works" diff --git a/examples/bluetooth/ble_uart_service/assets/ESP-VoCat-Asking-For-Permission.png b/examples/bluetooth/ble_uart_service/assets/ESP-VoCat-Asking-For-Permission.png new file mode 100644 index 00000000000..d3f5679ad6a Binary files /dev/null and b/examples/bluetooth/ble_uart_service/assets/ESP-VoCat-Asking-For-Permission.png differ diff --git a/examples/bluetooth/ble_uart_service/assets/ESP-VoCat-Working-With-OpenCode.png b/examples/bluetooth/ble_uart_service/assets/ESP-VoCat-Working-With-OpenCode.png new file mode 100644 index 00000000000..8d341c47e67 Binary files /dev/null and b/examples/bluetooth/ble_uart_service/assets/ESP-VoCat-Working-With-OpenCode.png differ diff --git a/examples/bluetooth/ble_uart_service/main/Kconfig.projbuild b/examples/bluetooth/ble_uart_service/main/Kconfig.projbuild new file mode 100644 index 00000000000..8ffa24ed029 --- /dev/null +++ b/examples/bluetooth/ble_uart_service/main/Kconfig.projbuild @@ -0,0 +1,47 @@ +menu "BLE UART service example" + + config EXAMPLE_CUSTOM_ADV_DATA + bool "Use custom advertising data" + default n + help + Demonstrates `ble_uart_config_t::adv_data` — the field that + lets the application fully control the advertising payload + instead of relying on the library default. + + When enabled, the example passes a static byte array + (`example_adv_payload[]` defined at the top of `main.c`) to + `ble_uart_install()`. Edit that array to broadcast anything + you want: a different Local Name, Manufacturer Specific + Data, custom Service Data, multiple Service UUIDs, etc. + + Format + The array is a sequence of standard Bluetooth Core "AD + structure" triplets: + + [length(1)] [AD type(1)] [value(length-1)] + + See the Bluetooth Assigned Numbers (Generic Access + Profile) document for the full type list. + + Length budget + Total bytes in the array must be + ≤ BLE_UART_ADV_DATA_MAX (28). The 3-byte mandatory + Flags AD element is prepended automatically by + ble_uart and does NOT count against this budget. An + oversized buffer makes `ble_uart_install()` fail with + BLE_UART_EINVAL. + + Scope + Only affects the over-the-air advertising payload. + The GAP-service Device Name (UUID 0x2A00, set via + `device_name` in the same struct) is independent and + stays whatever the application configured — connected + centrals read that name regardless of what is in + `adv_data`. + + Default value + Off. The library default is used (Complete Local Name + in the primary packet, 128-bit Service UUID in the + scan response). + +endmenu diff --git a/examples/bluetooth/ble_uart_service/main/main.c b/examples/bluetooth/ble_uart_service/main/main.c index 71cd8c506ab..b209bfb5782 100644 --- a/examples/bluetooth/ble_uart_service/main/main.c +++ b/examples/bluetooth/ble_uart_service/main/main.c @@ -3,11 +3,12 @@ * * SPDX-License-Identifier: Unlicense OR CC0-1.0 * - * BLE UART Service example. Backend (NimBLE / Bluedroid) is picked + * ESP-BLE-UART example. Backend (NimBLE / Bluedroid) is picked * by the host-stack Kconfig at compile time. Whatever the central * writes to the RX characteristic is echoed back over TX. */ +#include #include #include "esp_log.h" @@ -17,6 +18,41 @@ #include "ble_uart.h" +#if CONFIG_EXAMPLE_CUSTOM_ADV_DATA +/* Sample advertising payload demonstrating ble_uart_config_t::adv_data. + * Replace these bytes with whatever your product needs (a different + * Local Name, Manufacturer Specific Data, custom Service Data, + * additional Service UUIDs, ...) — ble_uart broadcasts them verbatim. + * + * Format: a sequence of standard BT Core "AD structure" triplets, + * [length(1)] [AD type(1)] [value(length-1)]. + * + * Length budget: total ≤ BLE_UART_ADV_DATA_MAX (28). The mandatory + * 3-byte Flags AD is prepended by ble_uart and does NOT count against + * this budget; oversize fails ble_uart_install() with EINVAL. + * + * The current contents (purely illustrative — edit freely): + * + * Layout bytes + * -------------------------------------- ----- + * Complete Local Name AD "BleUart" 1 + 1 + 7 = 9 + * Complete 128-bit UUID AD 1 + 1 + 16 = 18 + * -------------------------------------- ----- + * total 27 (≤ 28) + */ +static const uint8_t example_adv_payload[] = { + /* AD type 0x09: Complete Local Name */ + 0x08, 0x09, 'B', 'l', 'e', 'U', 'a', 'r', 't', + + /* AD type 0x07: Complete List of 128-bit Service UUIDs. + * UUID bytes are in over-the-air (little-endian) order, matching + * ble_uart_service_uuid.bytes[]. */ + 0x11, 0x07, + 0x9e, 0xca, 0xdc, 0x24, 0x0e, 0xe5, 0xa9, 0xe0, + 0x93, 0xf3, 0xa3, 0xb5, 0x01, 0x00, 0x40, 0x6e, +}; +#endif + static const char *TAG = "app"; static void ble_uart_on_rx(const uint8_t *data, size_t len) @@ -29,6 +65,83 @@ static void ble_uart_on_rx(const uint8_t *data, size_t len) ble_uart_tx(data, len); /* echo back */ } +/* Lifecycle / link-state event sink. Runs on the BLE host task — + * keep it short, never call ble_uart_close()/uninstall() from here. + * + * For production code: gate any sensitive TX on + * BLE_UART_EVT_LINK_SECURE (encrypted+authenticated) instead of just + * "connected"; ble_uart_is_connected() returns true while the link is + * still plaintext during the pairing window. */ +static void ble_uart_on_event(const ble_uart_evt_t *e) +{ + switch (e->id) { + case BLE_UART_EVT_CONNECTED: { + const uint8_t *b = e->connected.peer.bytes; + ESP_LOGI(TAG, + "evt: connected peer=%02x:%02x:%02x:%02x:%02x:%02x type=%u", + b[0], b[1], b[2], b[3], b[4], b[5], e->connected.peer.type); + break; + } + case BLE_UART_EVT_DISCONNECTED: + ESP_LOGI(TAG, "evt: disconnected reason=0x%x", + e->disconnected.reason); + break; + case BLE_UART_EVT_SUBSCRIBED: + ESP_LOGI(TAG, "evt: %ssubscribed", + e->subscribed.subscribed ? "" : "un"); + break; + case BLE_UART_EVT_LINK_SECURE: + ESP_LOGI(TAG, "evt: link_secure enc=%d auth=%d bond=%d ks=%u", + e->link_secure.encrypted, e->link_secure.authenticated, + e->link_secure.bonded, e->link_secure.key_size); + break; + case BLE_UART_EVT_PASSKEY_DISPLAY: + ESP_LOGI(TAG, "evt: passkey=%06" PRIu32, e->passkey.passkey); + break; + case BLE_UART_EVT_PASSKEY_REQUEST: + /* Fires only when cfg.security.io_cap is KEYBOARD_ONLY or + * KEYBOARD_DISPLAY (this example leaves io_cap at AUTO → + * DisplayOnly, so it should not fire). For a real keypad + * product, prompt the user for the 6 digits the central + * displayed and feed them in: + * + * ble_uart_passkey_reply(digits); + * + * See PORTING.md §5.6.1 for the full pattern. */ + ESP_LOGW(TAG, "evt: passkey entry requested — no UI wired in this " + "example (see PORTING.md §5.6.1)"); + break; + case BLE_UART_EVT_NUMERIC_COMPARE: + /* Fires only when cfg.security.io_cap is DISPLAY_YES_NO or + * KEYBOARD_DISPLAY (likewise dormant in this example). For a + * product with a yes/no control, surface the digits to the + * user and resolve the comparison: + * + * ble_uart_compare_reply(user_says_match); + * + * See PORTING.md §5.6.1. */ + ESP_LOGW(TAG, "evt: numeric compare %06" PRIu32 + " — no yes/no UI wired (see PORTING.md §5.6.1)", + e->numeric_compare.passkey); + break; + case BLE_UART_EVT_PAIRING_FAILED: + ESP_LOGW(TAG, "evt: pairing failed reason=0x%x", + e->pairing_failed.reason); + break; + case BLE_UART_EVT_CLOSED: + /* Only after ble_uart_close_async(). This example does not use + * close_async; do not ble_uart_uninstall() here — defer to an + * app task (PORTING.md §5.3.2). Kept for -Wswitch. */ + if (e->closed.status == BLE_UART_OK) { + ESP_LOGI(TAG, "evt: closed (async-close succeeded)"); + } else { + ESP_LOGW(TAG, "evt: closed async-close failed status=%d", + e->closed.status); + } + break; + } +} + void app_main(void) { /* NVS is required by the BT controller (PHY calibration) and the @@ -49,15 +162,51 @@ void app_main(void) ESP_LOGW(TAG, "esp_read_mac(BT) failed (%s); device name suffix will be 0000", esp_err_to_name(mac_err)); } - char name[24]; + char name[BLE_UART_DEVICE_NAME_MAX + 1]; snprintf(name, sizeof(name), "%s-%02X%02X", CONFIG_BLE_UART_DEVICE_NAME_PREFIX, mac[4], mac[5]); ESP_ERROR_CHECK(ble_uart_install(&(ble_uart_config_t){ .encrypted = true, .device_name = name, +#if CONFIG_EXAMPLE_CUSTOM_ADV_DATA + /* Hand the application-defined bytes to ble_uart. Whatever + * the array contains is broadcast verbatim; what `device_name` + * (above) holds is exposed via the GAP service for connected + * centrals to read — independent paths. */ + .adv_data = example_adv_payload, + .adv_data_len = sizeof(example_adv_payload), + /* scan_rsp_data is left at its default (NULL) → ble_uart still + * sends its built-in scan response. Override it the same way + * if you want to control those bytes too. */ +#endif .ble_uart_on_rx = ble_uart_on_rx, + .on_event = ble_uart_on_event, })); + /* Demonstrate the bond-management API: list every bonded peer + * already on flash. Replace the log with `ble_uart_clear_bonds()` + * to wipe them at boot (e.g. when a "factory reset" GPIO is held); + * use `ble_uart_remove_peer(&list[i])` to target one specifically. */ + size_t total = 0; + ble_uart_addr_t list[8]; + int rc = ble_uart_get_bonded_peers(list, sizeof(list) / sizeof(list[0]), + &total); + if (rc == 0) { + ESP_LOGI(TAG, "%u peer(s) currently bonded", (unsigned)total); + size_t shown = total < sizeof(list) / sizeof(list[0]) + ? total : sizeof(list) / sizeof(list[0]); + for (size_t i = 0; i < shown; i++) { + const uint8_t *b = list[i].bytes; + ESP_LOGI(TAG, " [%u] %02x:%02x:%02x:%02x:%02x:%02x type=%u", + (unsigned)i, + b[0], b[1], b[2], b[3], b[4], b[5], list[i].type); + } + if (total > shown) { + ESP_LOGI(TAG, " (%u more not shown)", + (unsigned)(total - shown)); + } + } + ESP_ERROR_CHECK(ble_uart_open()); } diff --git a/examples/bluetooth/ble_uart_service/sdkconfig.ci.bluedroid b/examples/bluetooth/ble_uart_service/sdkconfig.ci.bluedroid new file mode 100644 index 00000000000..fcb8571707e --- /dev/null +++ b/examples/bluetooth/ble_uart_service/sdkconfig.ci.bluedroid @@ -0,0 +1,24 @@ +# CI build overlay: Bluedroid host (sdkconfig.defaults selects NimBLE). +# Mirrors sdkconfig.bluedroid; kept in sync for idf-build-apps CONFIG_NAME=bluedroid. + +CONFIG_BT_NIMBLE_ENABLED=n +CONFIG_BT_ENABLED=y + +CONFIG_BT_NIMBLE_ENABLED=n +CONFIG_BT_BLUEDROID_ENABLED=y + +CONFIG_BT_BLE_SMP_ENABLE=y + + +CONFIG_BT_GATTS_ENABLE=y + +# CONFIG_BT_GATTC_ENABLE is not set + +# CONFIG_BT_BLE_50_FEATURES_SUPPORTED is not set +CONFIG_BT_BLE_42_FEATURES_SUPPORTED=y + +# CONFIG_BT_BLE_42_DTM_TEST_EN is not set + +CONFIG_BT_BLE_42_ADV_EN=y + +# CONFIG_BT_BLE_42_SCAN_EN is not set diff --git a/examples/bluetooth/ble_uart_service/sdkconfig.ci.nimble b/examples/bluetooth/ble_uart_service/sdkconfig.ci.nimble new file mode 100644 index 00000000000..b28da057406 --- /dev/null +++ b/examples/bluetooth/ble_uart_service/sdkconfig.ci.nimble @@ -0,0 +1,7 @@ +# CI build overlay: NimBLE host (sdkconfig.defaults is NimBLE-first). +# Explicit config so idf-build-apps builds both nimble and bluedroid in CI. + +CONFIG_BT_NIMBLE_ENABLED=y +CONFIG_BT_BLUEDROID_ENABLED=n +CONFIG_BT_NIMBLE_SM_SC=y +CONFIG_BT_NIMBLE_NVS_PERSIST=y diff --git a/examples/bluetooth/bluedroid/ble/ble_acl_latency/cent/main/gattc_latency.h b/examples/bluetooth/bluedroid/ble/ble_acl_latency/cent/main/gattc_latency.h index ddd9c5409a6..132a1129178 100644 --- a/examples/bluetooth/bluedroid/ble/ble_acl_latency/cent/main/gattc_latency.h +++ b/examples/bluetooth/bluedroid/ble/ble_acl_latency/cent/main/gattc_latency.h @@ -46,6 +46,7 @@ typedef struct { /* Latency test functions */ void latency_test_init(uint16_t conn_id, uint16_t char_handle); void latency_test_start(void); +void latency_test_stop(void); void latency_test_handle_notify(uint8_t *data, uint16_t len); void latency_test_print_results(void); diff --git a/examples/bluetooth/bluedroid/ble/ble_acl_latency/cent/main/gattc_latency_demo.c b/examples/bluetooth/bluedroid/ble/ble_acl_latency/cent/main/gattc_latency_demo.c index 09bd9ee5ee5..ac4a3800746 100644 --- a/examples/bluetooth/bluedroid/ble/ble_acl_latency/cent/main/gattc_latency_demo.c +++ b/examples/bluetooth/bluedroid/ble/ble_acl_latency/cent/main/gattc_latency_demo.c @@ -132,7 +132,7 @@ static void gattc_profile_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_ break; } ESP_LOGI(GATTC_TAG, "discover service complete conn_id %d", param->dis_srvc_cmpl.conn_id); - esp_ble_gattc_search_service(gattc_if, param->cfg_mtu.conn_id, &remote_filter_service_uuid); + esp_ble_gattc_search_service(gattc_if, param->dis_srvc_cmpl.conn_id, &remote_filter_service_uuid); break; case ESP_GATTC_CFG_MTU_EVT: if (param->cfg_mtu.status != ESP_GATT_OK){ @@ -311,6 +311,7 @@ static void gattc_profile_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_ case ESP_GATTC_DISCONNECT_EVT: connect = false; get_server = false; + latency_test_stop(); ESP_LOGI(GATTC_TAG, "ESP_GATTC_DISCONNECT_EVT, reason = %d", p_data->disconnect.reason); break; default: diff --git a/examples/bluetooth/bluedroid/ble/ble_acl_latency/cent/main/latency_test.c b/examples/bluetooth/bluedroid/ble/ble_acl_latency/cent/main/latency_test.c index 23743d9482e..87a14183afe 100644 --- a/examples/bluetooth/bluedroid/ble/ble_acl_latency/cent/main/latency_test.c +++ b/examples/bluetooth/bluedroid/ble/ble_acl_latency/cent/main/latency_test.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Unlicense OR CC0-1.0 */ @@ -22,9 +22,10 @@ static latency_record_t records[TEST_PACKET_COUNT]; static uint16_t test_conn_id = 0; static uint16_t char_handle = 0; static test_packet_t send_packets[TEST_PACKET_COUNT]; -static bool test_running = false; +/* Polled by latency_test_task and written by latency_test_stop() from another context. */ +static volatile bool test_running = false; static bool test_initialized = false; -static esp_gatt_if_t test_gattc_if = 0; +static esp_gatt_if_t test_gattc_if = ESP_GATT_IF_NONE; /** * Fill random data @@ -45,7 +46,6 @@ latency_test_init(uint16_t conn_id, uint16_t handle) { test_conn_id = conn_id; char_handle = handle; - test_running = false; test_initialized = true; memset(records, 0, sizeof(records)); @@ -69,7 +69,7 @@ latency_test_set_gattc_if(esp_gatt_if_t gattc_if) static int send_test_packet(uint16_t seq) { - if (char_handle == 0 || test_gattc_if == 0) { + if (char_handle == 0 || test_gattc_if == ESP_GATT_IF_NONE) { ESP_LOGE(TAG, "Test not initialized"); return -1; } @@ -113,21 +113,19 @@ latency_test_task(void *arg) test_running = true; - /* Send all test packets */ - for (int i = 0; i < TEST_PACKET_COUNT; i++) { + /* Send all test packets, abort early if latency_test_stop() is called */ + for (int i = 0; i < TEST_PACKET_COUNT && test_running; i++) { int rc = send_test_packet(i); if (rc != 0) { ESP_LOGW(TAG, "Send failed for seq=%d", i); } - - /* Wait interval */ vTaskDelay(pdMS_TO_TICKS(TEST_PACKET_INTERVAL_MS)); } - ESP_LOGI(TAG, "All packets sent, waiting for responses..."); - - /* Wait for all responses */ - vTaskDelay(pdMS_TO_TICKS(2000)); + if (test_running) { + ESP_LOGI(TAG, "All packets sent, waiting for responses..."); + vTaskDelay(pdMS_TO_TICKS(2000)); + } /* Print results */ latency_test_print_results(); @@ -152,9 +150,26 @@ latency_test_start(void) return; } + if (test_gattc_if == ESP_GATT_IF_NONE) { + ESP_LOGE(TAG, "GATT client interface not set"); + return; + } + xTaskCreate(latency_test_task, "latency_test", 4096, NULL, 5, NULL); } +/** + * Stop latency test + * + * Only raises the stop flag; the task observes it at the next loop iteration + * (or vTaskDelay boundary) and self-terminates via vTaskDelete(NULL). + */ +void +latency_test_stop(void) +{ + test_running = false; +} + /** * Handle notification */ @@ -189,6 +204,11 @@ latency_test_handle_notify(uint8_t *data, uint16_t len) return; } + if (records[seq].received) { + ESP_LOGD(TAG, "Duplicate notify for seq=%d, ignored", seq); + return; + } + /* Record receive time */ records[seq].recv_time_us = recv_time; records[seq].received = true; diff --git a/examples/bluetooth/bluedroid/ble/ble_acl_latency/cent/sdkconfig.defaults b/examples/bluetooth/bluedroid/ble/ble_acl_latency/cent/sdkconfig.defaults index a218a4da6a1..fa0bf673cfb 100644 --- a/examples/bluetooth/bluedroid/ble/ble_acl_latency/cent/sdkconfig.defaults +++ b/examples/bluetooth/bluedroid/ble/ble_acl_latency/cent/sdkconfig.defaults @@ -5,3 +5,10 @@ CONFIG_BT_ENABLED=y # CONFIG_BT_BLE_50_FEATURES_SUPPORTED is not set CONFIG_BT_BLE_42_FEATURES_SUPPORTED=y # CONFIG_BT_LE_50_FEATURE_SUPPORT is not set +CONFIG_PARTITION_TABLE_SINGLE_APP_LARGE=y + +# Disable unused Bluedroid host features for GATT client only example +# CONFIG_BT_GATTS_ENABLE is not set +# CONFIG_BT_BLE_SMP_ENABLE is not set +# CONFIG_BT_BLE_42_ADV_EN is not set +# CONFIG_BT_BLE_42_DTM_TEST_EN is not set diff --git a/examples/bluetooth/bluedroid/ble/ble_acl_latency/periph/main/gatts_latency_demo.c b/examples/bluetooth/bluedroid/ble/ble_acl_latency/periph/main/gatts_latency_demo.c index c0e9cab82c5..3de9a386980 100644 --- a/examples/bluetooth/bluedroid/ble/ble_acl_latency/periph/main/gatts_latency_demo.c +++ b/examples/bluetooth/bluedroid/ble/ble_acl_latency/periph/main/gatts_latency_demo.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Unlicense OR CC0-1.0 */ @@ -7,6 +7,7 @@ #include #include #include +#include #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "esp_system.h" @@ -18,6 +19,7 @@ #include "esp_gatts_api.h" #include "esp_bt_main.h" #include "esp_gatt_common_api.h" +#include "esp_gatt_defs.h" #include "gatts_latency.h" #define GATTS_TAG "BLE_ACL_LATENCY_PERIPH" @@ -191,16 +193,18 @@ static void gatts_profile_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_ } esp_err_t ret = esp_ble_gap_config_adv_data(&adv_data); - if (ret){ + if (ret != ESP_OK) { ESP_LOGE(GATTS_TAG, "Config adv data failed, error code = %x", ret); + } else { + adv_config_done |= adv_config_flag; } - adv_config_done |= adv_config_flag; ret = esp_ble_gap_config_adv_data(&scan_rsp_data); - if (ret){ + if (ret != ESP_OK) { ESP_LOGE(GATTS_TAG, "Config scan response data failed, error code = %x", ret); + } else { + adv_config_done |= scan_rsp_config_flag; } - adv_config_done |= scan_rsp_config_flag; esp_err_t create_attr_ret = esp_ble_gatts_create_attr_tab(gatt_db, gatts_if, LATENCY_IDX_NB, 0); if (create_attr_ret){ @@ -209,7 +213,41 @@ static void gatts_profile_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_ } break; case ESP_GATTS_READ_EVT: - ESP_LOGD(GATTS_TAG, "ESP_GATTS_READ_EVT"); + ESP_LOGD(GATTS_TAG, "ESP_GATTS_READ_EVT, handle=%d, need_rsp=%d", param->read.handle, param->read.need_rsp); + if (!param->read.need_rsp) { + break; + } + { + esp_gatt_rsp_t rsp; + memset(&rsp, 0, sizeof(rsp)); + rsp.attr_value.handle = param->read.handle; + rsp.attr_value.offset = param->read.offset; + + uint16_t vlen = 0; + const uint8_t *v = NULL; + esp_gatt_status_t gst = esp_ble_gatts_get_attr_value(param->read.handle, &vlen, &v); + + if (gst != ESP_GATT_OK || v == NULL) { + rsp.attr_value.len = 0; + esp_ble_gatts_send_response(gatts_if, param->read.conn_id, param->read.trans_id, + ESP_GATT_ERROR, &rsp); + break; + } + if (param->read.offset > vlen) { + rsp.attr_value.len = 0; + esp_ble_gatts_send_response(gatts_if, param->read.conn_id, param->read.trans_id, + ESP_GATT_INVALID_OFFSET, &rsp); + break; + } + + uint16_t remain = vlen - param->read.offset; + uint16_t copy_len = remain > ESP_GATT_MAX_ATTR_LEN ? ESP_GATT_MAX_ATTR_LEN : remain; + if (copy_len > 0) { + memcpy(rsp.attr_value.value, v + param->read.offset, copy_len); + } + rsp.attr_value.len = copy_len; + esp_ble_gatts_send_response(gatts_if, param->read.conn_id, param->read.trans_id, ESP_GATT_OK, &rsp); + } break; case ESP_GATTS_WRITE_EVT: if (!param->write.is_prep){ @@ -220,9 +258,35 @@ static void gatts_profile_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_ esp_ble_gatts_send_indicate(gatts_if, param->write.conn_id, handle_table[LATENCY_IDX_CHAR_VAL], param->write.len, param->write.value, false); } + if (param->write.need_rsp) { + esp_err_t sr = esp_ble_gatts_send_response(gatts_if, param->write.conn_id, param->write.trans_id, + ESP_GATT_OK, NULL); + if (sr != ESP_OK) { + ESP_LOGE(GATTS_TAG, "send_response failed: %s", esp_err_to_name(sr)); + } + } + } else { + /* This latency demo does not support Prepare/Long/Reliable Write. + * Reply with ATT Error 0x06 (Request Not Supported) instead of leaving + * the request unanswered, otherwise the peer would hit an ATT timeout. + */ + ESP_LOGW(GATTS_TAG, "Prepare write not supported, handle=%d, offset=%d, len=%d", + param->write.handle, param->write.offset, param->write.len); + if (param->write.need_rsp) { + esp_err_t sr = esp_ble_gatts_send_response(gatts_if, param->write.conn_id, param->write.trans_id, + ESP_GATT_REQ_NOT_SUPPORTED, NULL); + if (sr != ESP_OK) { + ESP_LOGE(GATTS_TAG, "send_response (prep reject) failed: %s", esp_err_to_name(sr)); + } + } } break; case ESP_GATTS_EXEC_WRITE_EVT: + ESP_LOGD(GATTS_TAG, "ESP_GATTS_EXEC_WRITE_EVT, conn_id=%d, trans_id=%" PRIu32 ", flag=0x%02x", + param->exec_write.conn_id, param->exec_write.trans_id, param->exec_write.exec_write_flag); + esp_ble_gatts_send_response(gatts_if, param->exec_write.conn_id, param->exec_write.trans_id, + ESP_GATT_OK, NULL); + break; case ESP_GATTS_MTU_EVT: ESP_LOGI(GATTS_TAG, "MTU exchange, MTU=%d", param->mtu.mtu); break; diff --git a/examples/bluetooth/bluedroid/ble/ble_acl_latency/periph/sdkconfig.defaults b/examples/bluetooth/bluedroid/ble/ble_acl_latency/periph/sdkconfig.defaults index a218a4da6a1..c9b1dccee9c 100644 --- a/examples/bluetooth/bluedroid/ble/ble_acl_latency/periph/sdkconfig.defaults +++ b/examples/bluetooth/bluedroid/ble/ble_acl_latency/periph/sdkconfig.defaults @@ -5,3 +5,10 @@ CONFIG_BT_ENABLED=y # CONFIG_BT_BLE_50_FEATURES_SUPPORTED is not set CONFIG_BT_BLE_42_FEATURES_SUPPORTED=y # CONFIG_BT_LE_50_FEATURE_SUPPORT is not set +CONFIG_PARTITION_TABLE_SINGLE_APP_LARGE=y + +# Disable unused Bluedroid host features for GATT server only example +# CONFIG_BT_GATTC_ENABLE is not set +# CONFIG_BT_BLE_SMP_ENABLE is not set +# CONFIG_BT_BLE_42_SCAN_EN is not set +# CONFIG_BT_BLE_42_DTM_TEST_EN is not set 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 813a677b51d..5eef3e6d9eb 100644 --- a/examples/bluetooth/bluedroid/ble/ble_ancs/main/ble_ancs.c +++ b/examples/bluetooth/bluedroid/ble/ble_ancs/main/ble_ancs.c @@ -29,9 +29,9 @@ A GATT notification delivered through the Notification Source characteristic con Control Point characteristic to interact with the iOS notification. */ -char *EventID_to_String(uint8_t EventID) +const char *EventID_to_String(uint8_t EventID) { - char *str = NULL; + const char *str = NULL; switch (EventID) { case EventIDNotificationAdded: @@ -50,9 +50,9 @@ char *EventID_to_String(uint8_t EventID) return str; } -char *CategoryID_to_String(uint8_t CategoryID) +const char *CategoryID_to_String(uint8_t CategoryID) { - char *Cidstr = NULL; + const char *Cidstr = NULL; switch(CategoryID) { case CategoryIDOther: Cidstr = "Other"; @@ -103,17 +103,20 @@ char *CategoryID_to_String(uint8_t CategoryID) void esp_receive_apple_notification_source(uint8_t *message, uint16_t message_len) { - if (!message || message_len < 5) { + if (!message || message_len < 8) { return; } uint8_t EventID = message[0]; - char *EventIDS = EventID_to_String(EventID); + const char *EventIDS = EventID_to_String(EventID); uint8_t EventFlags = message[1]; uint8_t CategoryID = message[2]; - char *Cidstr = CategoryID_to_String(CategoryID); + const char *Cidstr = CategoryID_to_String(CategoryID); uint8_t CategoryCount = message[3]; - uint32_t NotificationUID = (message[4]) | (message[5]<< 8) | (message[6]<< 16) | (message[7] << 24); + uint32_t NotificationUID = (uint32_t)message[4] + | ((uint32_t)message[5] << 8) + | ((uint32_t)message[6] << 16) + | ((uint32_t)message[7] << 24); ESP_LOGI(BLE_ANCS_TAG, "EventID:%s EventFlags:0x%x CategoryID:%s CategoryCount:%d NotificationUID:%" PRIu32, EventIDS, EventFlags, Cidstr, CategoryCount, NotificationUID); } @@ -131,7 +134,10 @@ void esp_receive_apple_data_source(uint8_t *message, uint16_t message_len) 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 NotificationUID = (uint32_t)message[1] + | ((uint32_t)message[2] << 8) + | ((uint32_t)message[3] << 16) + | ((uint32_t)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); @@ -142,7 +148,7 @@ void esp_receive_apple_data_source(uint8_t *message, uint16_t message_len) break; } uint8_t AttributeID = attrs[0]; - uint16_t len = attrs[1] | (attrs[2] << 8); + uint16_t len = (uint16_t)attrs[1] | ((uint16_t)attrs[2] << 8); if(len > (remian_attr_len - 3)) { ESP_LOGE(BLE_ANCS_TAG, "data error"); break; @@ -197,9 +203,9 @@ void esp_receive_apple_data_source(uint8_t *message, uint16_t message_len) } } -char *Errcode_to_String(uint16_t status) +const char *Errcode_to_String(uint16_t status) { - char *Errstr = NULL; + const char *Errstr = NULL; switch (status) { case Unknown_command: Errstr = "Unknown_command"; @@ -213,6 +219,9 @@ char *Errcode_to_String(uint16_t status) case Action_failed: Errstr = "Action_failed"; break; + case Internal_error: + Errstr = "Internal_error"; + break; default: Errstr = "unknown_failed"; break; diff --git a/examples/bluetooth/bluedroid/ble/ble_ancs/main/ble_ancs.h b/examples/bluetooth/bluedroid/ble/ble_ancs/main/ble_ancs.h index 6d310e21907..0509145a85d 100644 --- a/examples/bluetooth/bluedroid/ble/ble_ancs/main/ble_ancs.h +++ b/examples/bluetooth/bluedroid/ble/ble_ancs/main/ble_ancs.h @@ -95,6 +95,7 @@ typedef enum { Invalid_command = (0xA1), //The command was improperly formatted. Invalid_parameter = (0xA2), // One of the parameters (for example, the NotificationUID) does not refer to an existing object on the NP. Action_failed = (0xA3), //The action was not performed + Internal_error = (0xA4), //An internal error occurred on the NP (Apple ANCS specification). } esp_error_code; typedef enum { @@ -111,8 +112,8 @@ typedef enum { #define ESP_NOTIFICATIONUID_LEN 4 -char *EventID_to_String(uint8_t EventID); -char *CategoryID_to_String(uint8_t CategoryID); +const char *EventID_to_String(uint8_t EventID); +const char *CategoryID_to_String(uint8_t CategoryID); void esp_receive_apple_notification_source(uint8_t *message, uint16_t message_len); void esp_receive_apple_data_source(uint8_t *message, uint16_t message_len); -char *Errcode_to_String(uint16_t status); +const char *Errcode_to_String(uint16_t status); 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 0f97ea23fe8..750ae7b5e89 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-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2021-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Unlicense OR CC0-1.0 */ @@ -8,6 +8,7 @@ #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "freertos/event_groups.h" +#include "freertos/semphr.h" #include "esp_system.h" #include "esp_log.h" #include "nvs_flash.h" @@ -52,6 +53,7 @@ struct data_source_buffer { }; static struct data_source_buffer data_buffer = {0}; +static SemaphoreHandle_t data_buffer_mux; //In its basic form, the ANCS exposes three characteristics: // service UUID: 7905F431-B5CE-4E99-A40F-4B1E122D00D0 @@ -190,8 +192,8 @@ void esp_get_notification_attributes(uint8_t *notificationUID, uint8_t num_attr, 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); + cmd[index ++] = p_attr->attribute_len & 0xFF; + cmd[index ++] = (p_attr->attribute_len >> 8) & 0xFF; } p_attr ++; num_attr --; @@ -254,12 +256,23 @@ void esp_perform_notification_action(uint8_t *notificationUID, uint8_t ActionID) static void periodic_timer_callback(void* arg) { - esp_timer_stop(periodic_timer); + if (data_buffer_mux == NULL) { + return; + } + if (xSemaphoreTake(data_buffer_mux, 0) != pdTRUE) { + /* Mutex held by GATT handler; retry soon without blocking esp_timer task */ + esp_err_t tr = esp_timer_start_once(periodic_timer, 50000); + if (tr != ESP_OK) { + ESP_LOGE(BLE_ANCS_TAG, "Data source idle timer retry failed: %s", esp_err_to_name(tr)); + } + return; + } if (data_buffer.len > 0) { esp_receive_apple_data_source(data_buffer.buffer, data_buffer.len); memset(data_buffer.buffer, 0, data_buffer.len); data_buffer.len = 0; } + xSemaphoreGive(data_buffer_mux); } static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) @@ -525,6 +538,10 @@ static void gattc_profile_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_ } case ESP_GATTC_NOTIFY_EVT: if (param->notify.handle == gl_profile_tab[PROFILE_A_APP_ID].notification_source_handle) { + if (!param->notify.value || param->notify.value_len < 8) { + ESP_LOGW(BLE_ANCS_TAG, "Notification source too short (%u), need 8 bytes", param->notify.value_len); + break; + } esp_receive_apple_notification_source(param->notify.value, param->notify.value_len); uint8_t *notificationUID = ¶m->notify.value[4]; if (param->notify.value[0] == EventIDNotificationAdded && param->notify.value[2] == CategoryIDIncomingCall) { @@ -537,23 +554,35 @@ 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_mux == NULL) { + break; + } + if (xSemaphoreTake(data_buffer_mux, portMAX_DELAY) != pdTRUE) { + break; + } 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; + xSemaphoreGive(data_buffer_mux); 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)) { - // copy and wait next packet, start timer 500ms - esp_timer_start_periodic(periodic_timer, 500000); + /* Retriggerable idle timeout: stop then one-shot so each full fragment resets 500 ms */ + esp_timer_stop(periodic_timer); + esp_err_t tr = esp_timer_start_once(periodic_timer, 500000); + if (tr != ESP_OK) { + ESP_LOGE(BLE_ANCS_TAG, "Data source idle timer start failed: %s", esp_err_to_name(tr)); + } } else { esp_timer_stop(periodic_timer); esp_receive_apple_data_source(data_buffer.buffer, data_buffer.len); memset(data_buffer.buffer, 0, data_buffer.len); data_buffer.len = 0; } + xSemaphoreGive(data_buffer_mux); } else { ESP_LOGI(BLE_ANCS_TAG, "unknown handle, receive notify value:"); } @@ -572,7 +601,7 @@ static void gattc_profile_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_ } case ESP_GATTC_WRITE_CHAR_EVT: if (param->write.status != ESP_GATT_OK) { - char *Errstr = Errcode_to_String(param->write.status); + const char *Errstr = Errcode_to_String(param->write.status); if (Errstr) { ESP_LOGE(BLE_ANCS_TAG, "write control point error %s", Errstr); } @@ -582,6 +611,18 @@ static void gattc_profile_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_ break; case ESP_GATTC_DISCONNECT_EVT: ESP_LOGI(BLE_ANCS_TAG, "ESP_GATTC_DISCONNECT_EVT, reason = 0x%x", param->disconnect.reason); + if (data_buffer_mux != NULL && xSemaphoreTake(data_buffer_mux, portMAX_DELAY) == pdTRUE) { + esp_timer_stop(periodic_timer); + memset(data_buffer.buffer, 0, sizeof(data_buffer.buffer)); + data_buffer.len = 0; + xSemaphoreGive(data_buffer_mux); + } else { + /* Mutex unavailable: only stop the timer (esp_timer_stop is thread-safe). + * Skip buffer reset to avoid an unsynchronized write that could race with + * NOTIFY_EVT / periodic_timer_callback if the mutex were ever held elsewhere. */ + ESP_LOGE(BLE_ANCS_TAG, "data_buffer_mux unavailable on disconnect, skip buffer reset"); + esp_timer_stop(periodic_timer); + } get_service = false; esp_ble_gap_start_advertising(&adv_params); break; @@ -639,6 +680,8 @@ static void esp_gattc_cb(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp void init_timer(void) { ESP_ERROR_CHECK(esp_timer_create(&periodic_timer_args, &periodic_timer)); + data_buffer_mux = xSemaphoreCreateMutex(); + ESP_ERROR_CHECK(data_buffer_mux != NULL ? ESP_OK : ESP_ERR_NO_MEM); } void app_main(void) diff --git a/examples/bluetooth/bluedroid/ble/ble_ancs/sdkconfig.defaults b/examples/bluetooth/bluedroid/ble/ble_ancs/sdkconfig.defaults index 1bf8906ea7d..1602e1c68ab 100644 --- a/examples/bluetooth/bluedroid/ble/ble_ancs/sdkconfig.defaults +++ b/examples/bluetooth/bluedroid/ble/ble_ancs/sdkconfig.defaults @@ -6,3 +6,10 @@ CONFIG_BT_BLE_50_FEATURES_SUPPORTED=n CONFIG_BT_BLE_42_FEATURES_SUPPORTED=y # CONFIG_BT_LE_50_FEATURE_SUPPORT is not used on ESP32, ESP32-C3 and ESP32-S3. CONFIG_BT_LE_50_FEATURE_SUPPORT=n + +CONFIG_PARTITION_TABLE_SINGLE_APP_LARGE=y +# Disable unused Bluedroid host features for ANCS example +# (advertises as peripheral and acts as GATT client to ANCS service) +# CONFIG_BT_GATTS_ENABLE is not set +# CONFIG_BT_BLE_42_SCAN_EN is not set +# CONFIG_BT_BLE_42_DTM_TEST_EN is not set diff --git a/examples/bluetooth/bluedroid/ble/ble_compatibility_test/main/ble_compatibility_test.c b/examples/bluetooth/bluedroid/ble/ble_compatibility_test/main/ble_compatibility_test.c index 66c009ab044..e12a26936f0 100644 --- a/examples/bluetooth/bluedroid/ble/ble_compatibility_test/main/ble_compatibility_test.c +++ b/examples/bluetooth/bluedroid/ble/ble_compatibility_test/main/ble_compatibility_test.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2021-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2021-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Unlicense OR CC0-1.0 */ @@ -7,6 +7,7 @@ /******************************************************************************** * * This file is for gatt server. It can send adv data, and get connected by client. +* Only one BLE ACL connection is supported (see sdkconfig.defaults: CONFIG_BT_ACL_CONNECTIONS=1). * *********************************************************************************/ @@ -65,6 +66,7 @@ typedef struct { int prepare_len; } prepare_type_env_t; +/* This demo targets one connected client; a single prepare-write buffer is enough. */ static prepare_type_env_t prepare_write_env; //#define CONFIG_SET_RAW_ADV_DATA @@ -441,7 +443,11 @@ void example_prepare_write_event_env(esp_gatt_if_t gatts_if, prepare_type_env_t memcpy(prepare_write_env->prepare_buf + param->write.offset, param->write.value, param->write.len); - prepare_write_env->prepare_len += param->write.len; + /* Extent of prepared value: max(offset+len), not sum(len) — overlaps/retries must not inflate length. */ + uint32_t span_end = (uint32_t)param->write.offset + (uint32_t)param->write.len; + if (span_end > (uint32_t)prepare_write_env->prepare_len) { + prepare_write_env->prepare_len = (int)span_end; + } } uint8_t long_write[16] = {0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF}; @@ -562,7 +568,7 @@ static void gatts_profile_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_ break; case ESP_GATTS_EXEC_WRITE_EVT: // the length of gattc prepare write data must be less than GATTS_EXAMPLE_CHAR_VAL_LEN_MAX. - ESP_LOGI(EXAMPLE_TAG, "ESP_GATTS_EXEC_WRITE_EVT, Length=%d", prepare_write_env.prepare_len); + ESP_LOGI(EXAMPLE_TAG, "ESP_GATTS_EXEC_WRITE_EVT, Length=%d", prepare_write_env.prepare_len); example_exec_write_event_env(&prepare_write_env, param); break; case ESP_GATTS_MTU_EVT: @@ -581,6 +587,11 @@ static void gatts_profile_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_ break; case ESP_GATTS_DISCONNECT_EVT: ESP_LOGI(EXAMPLE_TAG, "ESP_GATTS_DISCONNECT_EVT, reason = %d", param->disconnect.reason); + if (prepare_write_env.prepare_buf) { + free(prepare_write_env.prepare_buf); + prepare_write_env.prepare_buf = NULL; + } + prepare_write_env.prepare_len = 0; esp_ble_gap_start_advertising(&adv_params); break; case ESP_GATTS_CREAT_ATTR_TAB_EVT:{ diff --git a/examples/bluetooth/bluedroid/ble/ble_compatibility_test/sdkconfig.defaults b/examples/bluetooth/bluedroid/ble/ble_compatibility_test/sdkconfig.defaults index 1bf8906ea7d..42586d5002a 100644 --- a/examples/bluetooth/bluedroid/ble/ble_compatibility_test/sdkconfig.defaults +++ b/examples/bluetooth/bluedroid/ble/ble_compatibility_test/sdkconfig.defaults @@ -6,3 +6,9 @@ CONFIG_BT_BLE_50_FEATURES_SUPPORTED=n CONFIG_BT_BLE_42_FEATURES_SUPPORTED=y # CONFIG_BT_LE_50_FEATURE_SUPPORT is not used on ESP32, ESP32-C3 and ESP32-S3. CONFIG_BT_LE_50_FEATURE_SUPPORT=n +CONFIG_PARTITION_TABLE_SINGLE_APP_LARGE=y + +# Disable unused Bluedroid host features for compatibility test (server with SMP) +# CONFIG_BT_GATTC_ENABLE is not set +# CONFIG_BT_BLE_42_SCAN_EN is not set +# CONFIG_BT_BLE_42_DTM_TEST_EN is not set diff --git a/examples/bluetooth/bluedroid/ble/ble_eddystone_receiver/sdkconfig.defaults b/examples/bluetooth/bluedroid/ble/ble_eddystone_receiver/sdkconfig.defaults index ad218785e70..9e523753f61 100644 --- a/examples/bluetooth/bluedroid/ble/ble_eddystone_receiver/sdkconfig.defaults +++ b/examples/bluetooth/bluedroid/ble/ble_eddystone_receiver/sdkconfig.defaults @@ -6,3 +6,11 @@ CONFIG_BT_ENABLED=y CONFIG_BT_BLE_42_FEATURES_SUPPORTED=y # CONFIG_BT_LE_50_FEATURE_SUPPORT is not used on ESP32, ESP32-C3 and ESP32-S3. # CONFIG_BT_LE_50_FEATURE_SUPPORT is not set +CONFIG_PARTITION_TABLE_SINGLE_APP_LARGE=y + +# Disable unused Bluedroid host features for Eddystone receiver (scanning only) +# CONFIG_BT_GATTS_ENABLE is not set +# CONFIG_BT_GATTC_ENABLE is not set +# CONFIG_BT_BLE_SMP_ENABLE is not set +# CONFIG_BT_BLE_42_ADV_EN is not set +# CONFIG_BT_BLE_42_DTM_TEST_EN is not set diff --git a/examples/bluetooth/bluedroid/ble/ble_eddystone_sender/main/esp_eddystone_demo.c b/examples/bluetooth/bluedroid/ble/ble_eddystone_sender/main/esp_eddystone_demo.c index 973dbe36d7b..4f21949b73d 100644 --- a/examples/bluetooth/bluedroid/ble/ble_eddystone_sender/main/esp_eddystone_demo.c +++ b/examples/bluetooth/bluedroid/ble/ble_eddystone_sender/main/esp_eddystone_demo.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2021-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Unlicense OR CC0-1.0 */ @@ -77,22 +77,24 @@ static void eddystone_send_raw(const esp_eddystone_result_t *res) } case EDDYSTONE_FRAME_TYPE_URL: { - size_t url_len = strlen((char*)res->inform.url.encoded_url); //encoded url length - if(url_len > EDDYSTONE_URL_MAX_LEN){ - url_len = EDDYSTONE_URL_MAX_LEN; + size_t url_len = strlen((char*)res->inform.url.encoded_url); // encoded url length + /* Eddystone: max 17 bytes encoded URL after scheme byte; also raw_adv_data[31] leaves + * only 20 bytes at index 11 (3 header + url_len), so url_len <= 17. */ + if (url_len > EDDYSTONE_URL_ENCODED_MAX_LEN) { + url_len = EDDYSTONE_URL_ENCODED_MAX_LEN; } - raw_adv_data[index++] = url_len+6; //length + raw_adv_data[index++] = url_len + 6; // length raw_adv_data[index++] = ESP_BLE_AD_TYPE_SERVICE_DATA; raw_adv_data[index++] = 0xAA; raw_adv_data[index++] = 0xFE; - uint8_t service_data[EDDYSTONE_URL_MAX_LEN+2] = {0}; + uint8_t service_data[EDDYSTONE_URL_ENCODED_MAX_LEN + 3] = {0}; service_data[0] = EDDYSTONE_FRAME_TYPE_URL; service_data[1] = res->inform.url.tx_power; service_data[2] = res->inform.url.url_scheme; memcpy(&service_data[3], res->inform.url.encoded_url, url_len); - memcpy(&raw_adv_data[index], service_data, url_len+3); - index += url_len+3; + memcpy(&raw_adv_data[index], service_data, url_len + 3); + index += url_len + 3; break; } @@ -107,7 +109,7 @@ static void eddystone_send_raw(const esp_eddystone_result_t *res) service_data[2] = (res->inform.tlm.battery_voltage >> 8) & 0xFF; service_data[3] = res->inform.tlm.battery_voltage & 0xFF; service_data[4] = (res->inform.tlm.temperature >> 8) & 0xFF; - service_data[4] = res->inform.tlm.temperature & 0xFF; + service_data[5] = res->inform.tlm.temperature & 0xFF; service_data[6] = (res->inform.tlm.adv_count >> 24) & 0xFF; service_data[7] = (res->inform.tlm.adv_count >> 16) & 0xFF; service_data[8] = (res->inform.tlm.adv_count >> 8) & 0xFF; @@ -167,8 +169,8 @@ void esp_eddystone_appRegister(void) void esp_eddystone_init(void) { esp_bluedroid_config_t cfg = BT_BLUEDROID_INIT_CONFIG_DEFAULT(); - esp_bluedroid_init_with_cfg(&cfg); - esp_bluedroid_enable(); + ESP_ERROR_CHECK(esp_bluedroid_init_with_cfg(&cfg)); + ESP_ERROR_CHECK(esp_bluedroid_enable()); esp_eddystone_appRegister(); } @@ -177,8 +179,8 @@ void app_main(void) ESP_ERROR_CHECK(nvs_flash_init()); ESP_ERROR_CHECK(esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT)); esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT(); - esp_bt_controller_init(&bt_cfg); - esp_bt_controller_enable(ESP_BT_MODE_BLE); + ESP_ERROR_CHECK(esp_bt_controller_init(&bt_cfg)); + ESP_ERROR_CHECK(esp_bt_controller_enable(ESP_BT_MODE_BLE)); esp_eddystone_init(); esp_eddystone_result_t eddystone_result; diff --git a/examples/bluetooth/bluedroid/ble/ble_eddystone_sender/sdkconfig.defaults b/examples/bluetooth/bluedroid/ble/ble_eddystone_sender/sdkconfig.defaults index 93b417c68f4..268532f2cc0 100644 --- a/examples/bluetooth/bluedroid/ble/ble_eddystone_sender/sdkconfig.defaults +++ b/examples/bluetooth/bluedroid/ble/ble_eddystone_sender/sdkconfig.defaults @@ -9,3 +9,11 @@ CONFIG_BT_BLE_42_FEATURES_SUPPORTED=y CONFIG_EXMAPLE_EDDYSTONE_SEND_UID=y CONFIG_EXMAPLE_EDDYSTONE_SEND_URL=n CONFIG_EXMAPLE_EDDYSTONE_SEND_TLM=n +CONFIG_PARTITION_TABLE_SINGLE_APP_LARGE=y + +# Disable unused Bluedroid host features for Eddystone sender (advertising only) +# CONFIG_BT_GATTS_ENABLE is not set +# CONFIG_BT_GATTC_ENABLE is not set +# CONFIG_BT_BLE_SMP_ENABLE is not set +# CONFIG_BT_BLE_42_SCAN_EN is not set +# CONFIG_BT_BLE_42_DTM_TEST_EN is not set diff --git a/examples/bluetooth/bluedroid/ble/ble_enc_adv_data/enc_adv_data_cent/main/ble_ead.c b/examples/bluetooth/bluedroid/ble/ble_enc_adv_data/enc_adv_data_cent/main/ble_ead.c index 7303c027b2b..65459a03905 100644 --- a/examples/bluetooth/bluedroid/ble/ble_enc_adv_data/enc_adv_data_cent/main/ble_ead.c +++ b/examples/bluetooth/bluedroid/ble/ble_enc_adv_data/enc_adv_data_cent/main/ble_ead.c @@ -190,7 +190,8 @@ static int ble_aes_ccm_encrypt(const uint8_t *key, const uint8_t *nonce, static int ble_aes_ccm_decrypt(const uint8_t *key, const uint8_t *nonce, const uint8_t *ciphertext, size_t ciphertext_len, const uint8_t *aad, size_t aad_len, - uint8_t *plaintext, size_t tag_len) + uint8_t *plaintext, size_t tag_len, + size_t plaintext_capacity) { #if defined(CONFIG_BT_SMP_CRYPTO_STACK_TINYCRYPT) struct tc_aes_key_sched_struct sched; @@ -213,6 +214,11 @@ static int ble_aes_ccm_decrypt(const uint8_t *key, const uint8_t *nonce, plaintext_len = ciphertext_len - tag_len; + if (plaintext_len > plaintext_capacity) { + ESP_LOGE(TAG, "plaintext_len (%zu) > plaintext_capacity (%zu)", plaintext_len, plaintext_capacity); + return -1; + } + /* Set AES encryption key */ ret = tc_aes128_set_encrypt_key(&sched, key); if (ret != TC_CRYPTO_SUCCESS) { @@ -275,6 +281,11 @@ static int ble_aes_ccm_decrypt(const uint8_t *key, const uint8_t *nonce, plaintext_len = ciphertext_len - tag_len; + if (plaintext_len > plaintext_capacity) { + ESP_LOGE(TAG, "plaintext_len (%zu) > plaintext_capacity (%zu)", plaintext_len, plaintext_capacity); + return -1; + } + /* Set key attributes */ psa_set_key_usage_flags(&attributes, PSA_KEY_USAGE_DECRYPT); psa_set_key_algorithm(&attributes, alg); @@ -369,13 +380,14 @@ int ble_ead_encrypt(const uint8_t session_key[BLE_EAD_KEY_SIZE], int ble_ead_decrypt(const uint8_t session_key[BLE_EAD_KEY_SIZE], const uint8_t iv[BLE_EAD_IV_SIZE], const uint8_t *encrypted_payload, size_t encrypted_payload_size, - uint8_t *payload) + uint8_t *payload, size_t payload_capacity) { int ret; uint8_t nonce[BLE_EAD_NONCE_SIZE]; const uint8_t *randomizer; const uint8_t *ciphertext; size_t ciphertext_len; + size_t expected_plaintext_len; if (session_key == NULL) { ESP_LOGE(TAG, "session_key is NULL"); @@ -402,6 +414,13 @@ int ble_ead_decrypt(const uint8_t session_key[BLE_EAD_KEY_SIZE], return -1; } + expected_plaintext_len = BLE_EAD_DECRYPTED_PAYLOAD_SIZE(encrypted_payload_size); + if (expected_plaintext_len > payload_capacity) { + ESP_LOGE(TAG, "EAD plaintext length %zu exceeds payload buffer %zu", + expected_plaintext_len, payload_capacity); + return -1; + } + /* Extract randomizer from the start of encrypted payload */ randomizer = encrypted_payload; @@ -420,7 +439,8 @@ int ble_ead_decrypt(const uint8_t session_key[BLE_EAD_KEY_SIZE], ret = ble_aes_ccm_decrypt(session_key, nonce, ciphertext, ciphertext_len, ble_ead_aad, BLE_EAD_AAD_SIZE, - payload, BLE_EAD_MIC_SIZE); + payload, BLE_EAD_MIC_SIZE, + payload_capacity); return ret; } diff --git a/examples/bluetooth/bluedroid/ble/ble_enc_adv_data/enc_adv_data_cent/main/ble_ead.h b/examples/bluetooth/bluedroid/ble/ble_enc_adv_data/enc_adv_data_cent/main/ble_ead.h index a9bf8954ebf..4994f6b994f 100644 --- a/examples/bluetooth/bluedroid/ble/ble_enc_adv_data/enc_adv_data_cent/main/ble_ead.h +++ b/examples/bluetooth/bluedroid/ble/ble_enc_adv_data/enc_adv_data_cent/main/ble_ead.h @@ -79,14 +79,15 @@ int ble_ead_encrypt(const uint8_t session_key[BLE_EAD_KEY_SIZE], * @param encrypted_payload Encrypted advertising data (includes randomizer and MIC) * @param encrypted_payload_size Size of encrypted data * @param payload Output buffer for decrypted data - * Size must be at least BLE_EAD_DECRYPTED_PAYLOAD_SIZE(encrypted_payload_size) + * @param payload_capacity Size of @a payload in bytes; must be >= + * BLE_EAD_DECRYPTED_PAYLOAD_SIZE(encrypted_payload_size) * * @return 0 on success, negative error code on failure */ int ble_ead_decrypt(const uint8_t session_key[BLE_EAD_KEY_SIZE], const uint8_t iv[BLE_EAD_IV_SIZE], const uint8_t *encrypted_payload, size_t encrypted_payload_size, - uint8_t *payload); + uint8_t *payload, size_t payload_capacity); #ifdef __cplusplus } diff --git a/examples/bluetooth/bluedroid/ble/ble_enc_adv_data/enc_adv_data_cent/main/enc_adv_data_cent.c b/examples/bluetooth/bluedroid/ble/ble_enc_adv_data/enc_adv_data_cent/main/enc_adv_data_cent.c index e03c3b86ad2..009346703db 100644 --- a/examples/bluetooth/bluedroid/ble/ble_enc_adv_data/enc_adv_data_cent/main/enc_adv_data_cent.c +++ b/examples/bluetooth/bluedroid/ble/ble_enc_adv_data/enc_adv_data_cent/main/enc_adv_data_cent.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Unlicense OR CC0-1.0 */ @@ -19,6 +19,7 @@ #include #include #include +#include #include "nvs.h" #include "nvs_flash.h" #include "esp_bt.h" @@ -57,12 +58,14 @@ static peer_info_t peers[MAX_PEERS] = {0}; /* GATT client state */ static bool is_connected = false; +static bool connect_pending = false; /* enh_open issued; CONNECT_EVT not yet received */ static bool get_server = false; static uint16_t conn_id_stored = 0; static uint16_t service_start_handle = 0; static uint16_t service_end_handle = 0; static uint16_t key_material_char_handle = INVALID_HANDLE; -static esp_bd_addr_t current_peer_addr = {0}; +/* BDA for the active GATT connection; set in CONNECT_EVT, cleared on disconnect */ +static esp_bd_addr_t gattc_remote_bda = {0}; /* GATT interface */ static esp_gatt_if_t gattc_if_stored = ESP_GATT_IF_NONE; @@ -146,27 +149,54 @@ static void decrypt_enc_adv_data(const uint8_t *adv_data, uint8_t adv_len, const uint8_t dec_data[32]; /* Buffer for decrypted data */ size_t dec_len = BLE_EAD_DECRYPTED_PAYLOAD_SIZE(enc_data_len); + if (dec_len > sizeof(dec_data)) { + ESP_LOGW(TAG, "Encrypted AD would yield %zu plaintext bytes; example buffer is %zu — skip", + dec_len, sizeof(dec_data)); + break; + } int rc = ble_ead_decrypt( peers[peer_idx].key_material.session_key, peers[peer_idx].key_material.iv, enc_data, enc_data_len, - dec_data); + dec_data, sizeof(dec_data)); if (rc == 0) { + size_t safe_dec_len = dec_len; + if (safe_dec_len > sizeof(dec_data)) { + ESP_LOGW(TAG, "dec_len %zu > buffer %zu, clamping for log/parse", + dec_len, sizeof(dec_data)); + safe_dec_len = sizeof(dec_data); + } ESP_LOGI(TAG, "Decryption successful!"); ESP_LOGI(TAG, "Decrypted data:"); - ESP_LOG_BUFFER_HEX(TAG, dec_data, dec_len); + ESP_LOG_BUFFER_HEX(TAG, dec_data, safe_dec_len); - /* Parse decrypted advertising structure */ - if (dec_len >= 2) { - uint8_t dec_type = dec_data[1]; - if (dec_type == ESP_BLE_AD_TYPE_NAME_CMPL || dec_type == ESP_BLE_AD_TYPE_NAME_SHORT) { - char name[32] = {0}; - size_t name_len = dec_data[0] - 1; - if (name_len < sizeof(name)) { - memcpy(name, &dec_data[2], name_len); - ESP_LOGI(TAG, "Decrypted device name: %s", name); + /* Parse decrypted advertising structure (do not trust length octet past plaintext) */ + if (safe_dec_len >= 2) { + if (dec_data[0] == 0) { + ESP_LOGW(TAG, "Malformed decrypted AD: zero inner length"); + } else { + /* BLE: octet 0 is L = len(type+data); element occupies 1+L octets */ + const size_t inner_total = 1U + (size_t)dec_data[0]; + if (inner_total > safe_dec_len) { + ESP_LOGW(TAG, "Malformed decrypted AD: inner len claims %zu octets, have %zu", + inner_total, safe_dec_len); + } else { + uint8_t dec_type = dec_data[1]; + if (dec_type == ESP_BLE_AD_TYPE_NAME_CMPL || + dec_type == ESP_BLE_AD_TYPE_NAME_SHORT) { + char name[32] = {0}; + size_t name_len = (size_t)dec_data[0] - 1U; + /* Name in dec_data[2 .. name_copy_end); last index is name_copy_end - 1 */ + const size_t name_copy_end = 2U + name_len; + if (name_len < sizeof(name) && + name_copy_end <= sizeof(dec_data) && + name_copy_end <= safe_dec_len) { + memcpy(name, &dec_data[2], name_len); + ESP_LOGI(TAG, "Decrypted device name: %s", name); + } + } } } } @@ -193,11 +223,14 @@ static bool should_connect(const uint8_t *adv_data, uint8_t adv_len) uint8_t type = adv_data[offset + 1]; if (type == ESP_BLE_AD_TYPE_16SRV_CMPL || type == ESP_BLE_AD_TYPE_16SRV_PART) { - /* Check for GAP service UUID */ - for (int i = 0; i < len - 1; i += 2) { - uint16_t uuid = adv_data[offset + 2 + i] | (adv_data[offset + 3 + i] << 8); - if (uuid == GAP_SERVICE_UUID) { - return true; + /* Octets after AD type = len - 1; each 16-bit UUID needs 2 payload bytes */ + int payload_len = (int)len - 1; + if (payload_len >= 2) { + for (int i = 0; i + 1 < payload_len; i += 2) { + uint16_t uuid = adv_data[offset + 2 + i] | (adv_data[offset + 3 + i] << 8); + if (uuid == GAP_SERVICE_UUID) { + return true; + } } } } @@ -250,26 +283,33 @@ static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param decrypt_enc_adv_data(adv_data, adv_len, scan_result->scan_rst.bda); } else { /* Need to connect and get key */ - if (!is_connected) { - ESP_LOGI(TAG, "Connecting to get key material..."); - add_peer(scan_result->scan_rst.bda); - memcpy(current_peer_addr, scan_result->scan_rst.bda, sizeof(esp_bd_addr_t)); + if (!is_connected && !connect_pending) { + int peer_slot = add_peer(scan_result->scan_rst.bda); + if (peer_slot < 0) { + ESP_LOGE(TAG, "Peer table full (max %d); cannot track key for " + ESP_BD_ADDR_STR " — skip connection (increase " + "MAX_PEERS or free a slot)", + MAX_PEERS, ESP_BD_ADDR_HEX(scan_result->scan_rst.bda)); + } else { + ESP_LOGI(TAG, "Connecting to get key material..."); + esp_ble_gap_stop_scanning(); - esp_ble_gap_stop_scanning(); - - esp_ble_gatt_creat_conn_params_t conn_params = {0}; - memcpy(conn_params.remote_bda, scan_result->scan_rst.bda, ESP_BD_ADDR_LEN); - conn_params.remote_addr_type = scan_result->scan_rst.ble_addr_type; - conn_params.own_addr_type = BLE_ADDR_TYPE_PUBLIC; - conn_params.is_direct = true; - conn_params.is_aux = false; - esp_ble_gattc_enh_open(gattc_if_stored, &conn_params); + esp_ble_gatt_creat_conn_params_t conn_params = {0}; + memcpy(conn_params.remote_bda, scan_result->scan_rst.bda, + ESP_BD_ADDR_LEN); + conn_params.remote_addr_type = scan_result->scan_rst.ble_addr_type; + conn_params.own_addr_type = BLE_ADDR_TYPE_PUBLIC; + conn_params.is_direct = true; + conn_params.is_aux = false; + connect_pending = true; + esp_ble_gattc_enh_open(gattc_if_stored, &conn_params); + } } } } } else if (scan_result->scan_rst.search_evt == ESP_GAP_SEARCH_INQ_CMPL_EVT) { ESP_LOGI(TAG, "Scan complete"); - if (!is_connected) { + if (!is_connected && !connect_pending) { start_scan(); /* Restart scanning */ } } @@ -314,6 +354,8 @@ static void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_ case ESP_GATTC_CONNECT_EVT: ESP_LOGI(TAG, "Connected, conn_id %d", param->connect.conn_id); conn_id_stored = param->connect.conn_id; + connect_pending = false; + memcpy(gattc_remote_bda, param->connect.remote_bda, sizeof(esp_bd_addr_t)); is_connected = true; /* Request MTU exchange */ @@ -324,6 +366,8 @@ static void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_ if (param->open.status != ESP_GATT_OK) { ESP_LOGE(TAG, "Open failed: %d", param->open.status); is_connected = false; + connect_pending = false; + memset(gattc_remote_bda, 0, sizeof(gattc_remote_bda)); start_scan(); } break; @@ -360,25 +404,29 @@ static void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_ if (get_server) { /* Get characteristics */ uint16_t count = 0; - esp_ble_gattc_get_attr_count(gattc_if, conn_id_stored, - ESP_GATT_DB_CHARACTERISTIC, - service_start_handle, - service_end_handle, - INVALID_HANDLE, &count); + esp_gatt_status_t gc_st = esp_ble_gattc_get_attr_count(gattc_if, conn_id_stored, + ESP_GATT_DB_CHARACTERISTIC, + service_start_handle, + service_end_handle, + INVALID_HANDLE, &count); - if (count > 0) { - esp_gattc_char_elem_t *char_elem = malloc(sizeof(esp_gattc_char_elem_t) * count); + if (gc_st != ESP_GATT_OK) { + ESP_LOGE(TAG, "get_attr_count failed: %d", gc_st); + } else if (count > 0) { + esp_gattc_char_elem_t *char_elem = calloc(count, sizeof(esp_gattc_char_elem_t)); if (char_elem) { esp_bt_uuid_t km_uuid = { .len = ESP_UUID_LEN_16, .uuid = {.uuid16 = KEY_MATERIAL_CHAR_UUID}, }; - esp_ble_gattc_get_char_by_uuid(gattc_if, conn_id_stored, - service_start_handle, - service_end_handle, - km_uuid, char_elem, &count); + gc_st = esp_ble_gattc_get_char_by_uuid(gattc_if, conn_id_stored, + service_start_handle, + service_end_handle, + km_uuid, char_elem, &count); - if (count > 0) { + if (gc_st != ESP_GATT_OK) { + ESP_LOGE(TAG, "get_char_by_uuid failed: %d", gc_st); + } else if (count > 0) { key_material_char_handle = char_elem[0].char_handle; ESP_LOGI(TAG, "Key Material characteristic found, handle %d", key_material_char_handle); @@ -402,7 +450,7 @@ static void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_ if (param->read.handle == key_material_char_handle && param->read.value_len == sizeof(ble_ead_key_material_t)) { /* Store key material */ - int peer_idx = find_peer(current_peer_addr); + int peer_idx = find_peer(gattc_remote_bda); if (peer_idx >= 0) { memcpy(&peers[peer_idx].key_material, param->read.value, sizeof(ble_ead_key_material_t)); @@ -424,6 +472,8 @@ static void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_ case ESP_GATTC_DISCONNECT_EVT: ESP_LOGI(TAG, "Disconnected, reason 0x%02x", param->disconnect.reason); is_connected = false; + connect_pending = false; + memset(gattc_remote_bda, 0, sizeof(gattc_remote_bda)); get_server = false; key_material_char_handle = INVALID_HANDLE; start_scan(); diff --git a/examples/bluetooth/bluedroid/ble/ble_enc_adv_data/enc_adv_data_cent/main/enc_adv_data_cent_no_connect.c b/examples/bluetooth/bluedroid/ble/ble_enc_adv_data/enc_adv_data_cent/main/enc_adv_data_cent_no_connect.c index 62416b1ecc1..7fd45def779 100644 --- a/examples/bluetooth/bluedroid/ble/ble_enc_adv_data/enc_adv_data_cent/main/enc_adv_data_cent_no_connect.c +++ b/examples/bluetooth/bluedroid/ble/ble_enc_adv_data/enc_adv_data_cent/main/enc_adv_data_cent_no_connect.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Unlicense OR CC0-1.0 */ @@ -67,10 +67,14 @@ static bool is_target_device(const uint8_t *adv_data, uint8_t adv_len) uint8_t type = adv_data[offset + 1]; if (type == ESP_BLE_AD_TYPE_16SRV_CMPL || type == ESP_BLE_AD_TYPE_16SRV_PART) { - for (int i = 0; i < len - 1; i += 2) { - uint16_t uuid = adv_data[offset + 2 + i] | (adv_data[offset + 3 + i] << 8); - if (uuid == CUSTOM_SERVICE_UUID) { - return true; + /* Octets after AD type = len - 1; each 16-bit UUID needs 2 payload bytes */ + int payload_len = (int)len - 1; + if (payload_len >= 2) { + for (int i = 0; i + 1 < payload_len; i += 2) { + uint16_t uuid = adv_data[offset + 2 + i] | (adv_data[offset + 3 + i] << 8); + if (uuid == CUSTOM_SERVICE_UUID) { + return true; + } } } } @@ -116,30 +120,53 @@ static void decrypt_adv_data_no_connect(const uint8_t *adv_data, uint8_t adv_len /* Decrypt using pre-shared key */ uint8_t dec_data[32]; size_t dec_len = BLE_EAD_DECRYPTED_PAYLOAD_SIZE(enc_data_len); + if (dec_len > sizeof(dec_data)) { + ESP_LOGW(TAG, "Encrypted AD would yield %zu plaintext bytes; example buffer is %zu — skip", + dec_len, sizeof(dec_data)); + return; + } int rc = ble_ead_decrypt( pre_shared_key.session_key, pre_shared_key.iv, enc_data, enc_data_len, - dec_data); + dec_data, sizeof(dec_data)); if (rc == 0) { + size_t safe_dec_len = dec_len; + if (safe_dec_len > sizeof(dec_data)) { + ESP_LOGW(TAG, "dec_len %zu > buffer %zu, clamping for log/parse", + dec_len, sizeof(dec_data)); + safe_dec_len = sizeof(dec_data); + } ESP_LOGI(TAG, "✅ Decryption successful (no connection needed!)"); - ESP_LOGI(TAG, "Decrypted data (%d bytes):", dec_len); - ESP_LOG_BUFFER_HEX(TAG, dec_data, dec_len); + ESP_LOGI(TAG, "Decrypted data (%zu bytes):", safe_dec_len); + ESP_LOG_BUFFER_HEX(TAG, dec_data, safe_dec_len); - /* Parse the decrypted advertising structure */ - if (dec_len >= 2) { - uint8_t inner_len = dec_data[0]; - uint8_t inner_type = dec_data[1]; - - if (inner_type == ESP_BLE_AD_TYPE_NAME_CMPL || - inner_type == ESP_BLE_AD_TYPE_NAME_SHORT) { - char name[32] = {0}; - size_t name_len = inner_len - 1; - if (name_len < sizeof(name) && name_len <= dec_len - 2) { - memcpy(name, &dec_data[2], name_len); - ESP_LOGI(TAG, "📛 Decrypted device name: \"%s\"", name); + /* Parse decrypted AD (do not trust length octet past plaintext) */ + if (safe_dec_len >= 2) { + if (dec_data[0] == 0) { + ESP_LOGW(TAG, "Malformed decrypted AD: zero inner length"); + } else { + const size_t inner_total = 1U + (size_t)dec_data[0]; + if (inner_total > safe_dec_len) { + ESP_LOGW(TAG, + "Malformed decrypted AD: inner len claims %zu octets, have %zu", + inner_total, safe_dec_len); + } else { + uint8_t inner_type = dec_data[1]; + if (inner_type == ESP_BLE_AD_TYPE_NAME_CMPL || + inner_type == ESP_BLE_AD_TYPE_NAME_SHORT) { + char name[32] = {0}; + size_t name_len = (size_t)dec_data[0] - 1U; + const size_t name_copy_end = 2U + name_len; + if (name_len < sizeof(name) && + name_copy_end <= sizeof(dec_data) && + name_copy_end <= safe_dec_len) { + memcpy(name, &dec_data[2], name_len); + ESP_LOGI(TAG, "📛 Decrypted device name: \"%s\"", name); + } + } } } } diff --git a/examples/bluetooth/bluedroid/ble/ble_enc_adv_data/enc_adv_data_cent/sdkconfig.defaults b/examples/bluetooth/bluedroid/ble/ble_enc_adv_data/enc_adv_data_cent/sdkconfig.defaults index ae0ee308988..7b5d67ee671 100644 --- a/examples/bluetooth/bluedroid/ble/ble_enc_adv_data/enc_adv_data_cent/sdkconfig.defaults +++ b/examples/bluetooth/bluedroid/ble/ble_enc_adv_data/enc_adv_data_cent/sdkconfig.defaults @@ -11,3 +11,9 @@ CONFIG_BT_BLE_SMP_ENABLE=y # Select crypto library for EAD (Encrypted Advertising Data) # Options: CONFIG_BT_SMP_CRYPTO_STACK_TINYCRYPT or CONFIG_BT_SMP_CRYPTO_STACK_MBEDTLS CONFIG_BT_SMP_CRYPTO_STACK_TINYCRYPT=y +CONFIG_PARTITION_TABLE_SINGLE_APP_LARGE=y + +# Disable unused Bluedroid host features for encrypted adv data central (client with SMP) +# CONFIG_BT_GATTS_ENABLE is not set +# CONFIG_BT_BLE_42_ADV_EN is not set +# CONFIG_BT_BLE_42_DTM_TEST_EN is not set diff --git a/examples/bluetooth/bluedroid/ble/ble_enc_adv_data/enc_adv_data_prph/main/ble_ead.c b/examples/bluetooth/bluedroid/ble/ble_enc_adv_data/enc_adv_data_prph/main/ble_ead.c index b3e1dcc8f87..59bb3c60103 100644 --- a/examples/bluetooth/bluedroid/ble/ble_enc_adv_data/enc_adv_data_prph/main/ble_ead.c +++ b/examples/bluetooth/bluedroid/ble/ble_enc_adv_data/enc_adv_data_prph/main/ble_ead.c @@ -184,7 +184,8 @@ static int ble_aes_ccm_encrypt(const uint8_t *key, const uint8_t *nonce, static int ble_aes_ccm_decrypt(const uint8_t *key, const uint8_t *nonce, const uint8_t *ciphertext, size_t ciphertext_len, const uint8_t *aad, size_t aad_len, - uint8_t *plaintext, size_t tag_len) + uint8_t *plaintext, size_t tag_len, + size_t plaintext_capacity) { #if defined(CONFIG_BT_SMP_CRYPTO_STACK_TINYCRYPT) struct tc_aes_key_sched_struct sched; @@ -207,6 +208,11 @@ static int ble_aes_ccm_decrypt(const uint8_t *key, const uint8_t *nonce, plaintext_len = ciphertext_len - tag_len; + if (plaintext_len > plaintext_capacity) { + ESP_LOGE(TAG, "plaintext_len (%zu) > plaintext_capacity (%zu)", plaintext_len, plaintext_capacity); + return -1; + } + /* Set AES encryption key */ ret = tc_aes128_set_encrypt_key(&sched, key); if (ret != TC_CRYPTO_SUCCESS) { @@ -269,6 +275,11 @@ static int ble_aes_ccm_decrypt(const uint8_t *key, const uint8_t *nonce, plaintext_len = ciphertext_len - tag_len; + if (plaintext_len > plaintext_capacity) { + ESP_LOGE(TAG, "plaintext_len (%zu) > plaintext_capacity (%zu)", plaintext_len, plaintext_capacity); + return -1; + } + /* Set key attributes */ psa_set_key_usage_flags(&attributes, PSA_KEY_USAGE_DECRYPT); psa_set_key_algorithm(&attributes, alg); @@ -362,13 +373,14 @@ int ble_ead_encrypt(const uint8_t session_key[BLE_EAD_KEY_SIZE], int ble_ead_decrypt(const uint8_t session_key[BLE_EAD_KEY_SIZE], const uint8_t iv[BLE_EAD_IV_SIZE], const uint8_t *encrypted_payload, size_t encrypted_payload_size, - uint8_t *payload) + uint8_t *payload, size_t payload_capacity) { int ret; uint8_t nonce[BLE_EAD_NONCE_SIZE]; const uint8_t *randomizer; const uint8_t *ciphertext; size_t ciphertext_len; + size_t expected_plaintext_len; if (session_key == NULL) { ESP_LOGE(TAG, "session_key is NULL"); @@ -395,6 +407,13 @@ int ble_ead_decrypt(const uint8_t session_key[BLE_EAD_KEY_SIZE], return -1; } + expected_plaintext_len = BLE_EAD_DECRYPTED_PAYLOAD_SIZE(encrypted_payload_size); + if (expected_plaintext_len > payload_capacity) { + ESP_LOGE(TAG, "EAD plaintext length %zu exceeds payload buffer %zu", + expected_plaintext_len, payload_capacity); + return -1; + } + /* Extract randomizer from the start of encrypted payload */ randomizer = encrypted_payload; @@ -413,7 +432,8 @@ int ble_ead_decrypt(const uint8_t session_key[BLE_EAD_KEY_SIZE], ret = ble_aes_ccm_decrypt(session_key, nonce, ciphertext, ciphertext_len, ble_ead_aad, BLE_EAD_AAD_SIZE, - payload, BLE_EAD_MIC_SIZE); + payload, BLE_EAD_MIC_SIZE, + payload_capacity); return ret; } diff --git a/examples/bluetooth/bluedroid/ble/ble_enc_adv_data/enc_adv_data_prph/main/ble_ead.h b/examples/bluetooth/bluedroid/ble/ble_enc_adv_data/enc_adv_data_prph/main/ble_ead.h index a9bf8954ebf..4994f6b994f 100644 --- a/examples/bluetooth/bluedroid/ble/ble_enc_adv_data/enc_adv_data_prph/main/ble_ead.h +++ b/examples/bluetooth/bluedroid/ble/ble_enc_adv_data/enc_adv_data_prph/main/ble_ead.h @@ -79,14 +79,15 @@ int ble_ead_encrypt(const uint8_t session_key[BLE_EAD_KEY_SIZE], * @param encrypted_payload Encrypted advertising data (includes randomizer and MIC) * @param encrypted_payload_size Size of encrypted data * @param payload Output buffer for decrypted data - * Size must be at least BLE_EAD_DECRYPTED_PAYLOAD_SIZE(encrypted_payload_size) + * @param payload_capacity Size of @a payload in bytes; must be >= + * BLE_EAD_DECRYPTED_PAYLOAD_SIZE(encrypted_payload_size) * * @return 0 on success, negative error code on failure */ int ble_ead_decrypt(const uint8_t session_key[BLE_EAD_KEY_SIZE], const uint8_t iv[BLE_EAD_IV_SIZE], const uint8_t *encrypted_payload, size_t encrypted_payload_size, - uint8_t *payload); + uint8_t *payload, size_t payload_capacity); #ifdef __cplusplus } diff --git a/examples/bluetooth/bluedroid/ble/ble_enc_adv_data/enc_adv_data_prph/main/enc_adv_data_prph.c b/examples/bluetooth/bluedroid/ble/ble_enc_adv_data/enc_adv_data_prph/main/enc_adv_data_prph.c index 7520bbc9d85..f6d910a1157 100644 --- a/examples/bluetooth/bluedroid/ble/ble_enc_adv_data/enc_adv_data_prph/main/enc_adv_data_prph.c +++ b/examples/bluetooth/bluedroid/ble/ble_enc_adv_data/enc_adv_data_prph/main/enc_adv_data_prph.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Unlicense OR CC0-1.0 */ @@ -59,7 +59,6 @@ static ble_ead_key_material_t key_material = { /* GATT state */ static esp_gatt_if_t gatts_if_stored = ESP_GATT_IF_NONE; static uint16_t conn_id_stored = 0; -static bool is_connected = false; /* Advertising parameters */ static esp_ble_adv_params_t adv_params = { @@ -158,8 +157,13 @@ static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param { switch (event) { case ESP_GAP_BLE_ADV_DATA_RAW_SET_COMPLETE_EVT: - ESP_LOGI(TAG, "Raw advertising data set complete"); - start_advertising(); + if (param->adv_data_raw_cmpl.status != ESP_BT_STATUS_SUCCESS) { + ESP_LOGE(TAG, "Raw advertising data set failed: %d", + param->adv_data_raw_cmpl.status); + } else { + ESP_LOGI(TAG, "Raw advertising data set complete"); + start_advertising(); + } break; case ESP_GAP_BLE_ADV_START_COMPLETE_EVT: @@ -236,7 +240,6 @@ static void gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_ ESP_LOGI(TAG, "Connected, conn_id %d, remote "ESP_BD_ADDR_STR"", param->connect.conn_id, ESP_BD_ADDR_HEX(param->connect.remote_bda)); conn_id_stored = param->connect.conn_id; - is_connected = true; /* Update connection parameters */ esp_ble_conn_update_params_t conn_params = {0}; @@ -251,7 +254,6 @@ static void gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_ case ESP_GATTS_DISCONNECT_EVT: ESP_LOGI(TAG, "Disconnected, remote "ESP_BD_ADDR_STR", reason 0x%02x", ESP_BD_ADDR_HEX(param->disconnect.remote_bda), param->disconnect.reason); - is_connected = false; /* Re-encrypt and restart advertising with new randomizer */ set_encrypted_adv_data(); diff --git a/examples/bluetooth/bluedroid/ble/ble_enc_adv_data/enc_adv_data_prph/sdkconfig.defaults b/examples/bluetooth/bluedroid/ble/ble_enc_adv_data/enc_adv_data_prph/sdkconfig.defaults index ae0ee308988..dafa5f42994 100644 --- a/examples/bluetooth/bluedroid/ble/ble_enc_adv_data/enc_adv_data_prph/sdkconfig.defaults +++ b/examples/bluetooth/bluedroid/ble/ble_enc_adv_data/enc_adv_data_prph/sdkconfig.defaults @@ -11,3 +11,9 @@ CONFIG_BT_BLE_SMP_ENABLE=y # Select crypto library for EAD (Encrypted Advertising Data) # Options: CONFIG_BT_SMP_CRYPTO_STACK_TINYCRYPT or CONFIG_BT_SMP_CRYPTO_STACK_MBEDTLS CONFIG_BT_SMP_CRYPTO_STACK_TINYCRYPT=y +CONFIG_PARTITION_TABLE_SINGLE_APP_LARGE=y + +# Disable unused Bluedroid host features for encrypted adv data peripheral (server with SMP) +# CONFIG_BT_GATTC_ENABLE is not set +# CONFIG_BT_BLE_42_SCAN_EN is not set +# CONFIG_BT_BLE_42_DTM_TEST_EN is not set diff --git a/examples/bluetooth/bluedroid/ble/ble_hid_device_demo/main/esp_hidd_prf_api.c b/examples/bluetooth/bluedroid/ble/ble_hid_device_demo/main/esp_hidd_prf_api.c index b7d1fbd68e5..36f4e56dcc5 100644 --- a/examples/bluetooth/bluedroid/ble/ble_hid_device_demo/main/esp_hidd_prf_api.c +++ b/examples/bluetooth/bluedroid/ble/ble_hid_device_demo/main/esp_hidd_prf_api.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2021-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Unlicense OR CC0-1.0 */ @@ -37,9 +37,11 @@ esp_err_t esp_hidd_register_callbacks(esp_hidd_event_cb_t callbacks) return hidd_status; } - esp_ble_gatts_app_register(BATTRAY_APP_ID); + if ((hidd_status = esp_ble_gatts_app_register(BATTRAY_APP_ID)) != ESP_OK) { + return hidd_status; + } - if((hidd_status = esp_ble_gatts_app_register(HIDD_APP_ID)) != ESP_OK) { + if ((hidd_status = esp_ble_gatts_app_register(HIDD_APP_ID)) != ESP_OK) { return hidd_status; } @@ -52,8 +54,10 @@ esp_err_t esp_hidd_profile_init(void) ESP_LOGE(HID_LE_PRF_TAG, "HID device profile already initialized"); return ESP_FAIL; } - // Reset the hid device target environment + /* Reset the hid device target environment */ memset(&hidd_le_env, 0, sizeof(hidd_le_env_t)); + hidd_le_env.gatt_if = ESP_GATT_IF_NONE; + hidd_le_env.bat_gatt_if = ESP_GATT_IF_NONE; hidd_le_env.enabled = true; return ESP_OK; } @@ -62,20 +66,32 @@ esp_err_t esp_hidd_profile_deinit(void) { uint16_t hidd_svc_hdl = hidd_le_env.hidd_inst.att_tbl[HIDD_LE_IDX_SVC]; if (!hidd_le_env.enabled) { - ESP_LOGE(HID_LE_PRF_TAG, "HID device profile already initialized"); + ESP_LOGW(HID_LE_PRF_TAG, "HID device profile not initialized, deinit skipped"); return ESP_OK; } - if(hidd_svc_hdl != 0) { - esp_ble_gatts_stop_service(hidd_svc_hdl); - esp_ble_gatts_delete_service(hidd_svc_hdl); + if (hidd_svc_hdl != 0) { + esp_ble_gatts_stop_service(hidd_svc_hdl); + esp_ble_gatts_delete_service(hidd_svc_hdl); } else { - return ESP_FAIL; - } + ESP_LOGW(HID_LE_PRF_TAG, "HID service handle unset, skip stop/delete"); + } - /* register the HID device profile to the BTA_GATTS module*/ - esp_ble_gatts_app_unregister(hidd_le_env.gatt_if); + /* Release both GATTS apps (battery registered first, then HID) */ + if (hidd_le_env.bat_gatt_if != ESP_GATT_IF_NONE) { + esp_ble_gatts_app_unregister(hidd_le_env.bat_gatt_if); + hidd_le_env.bat_gatt_if = ESP_GATT_IF_NONE; + } else { + ESP_LOGW(HID_LE_PRF_TAG, "Battery gatt_if invalid, app_unregister skipped (possible stack slot leak)"); + } + if (hidd_le_env.gatt_if != ESP_GATT_IF_NONE) { + esp_ble_gatts_app_unregister(hidd_le_env.gatt_if); + hidd_le_env.gatt_if = ESP_GATT_IF_NONE; + } else { + ESP_LOGW(HID_LE_PRF_TAG, "HID gatt_if invalid, app_unregister skipped (possible stack slot leak)"); + } + hidd_le_env.enabled = false; return ESP_OK; } @@ -112,7 +128,7 @@ void esp_hidd_send_keyboard_value(uint16_t conn_id, key_mask_t special_key_mask, buffer[i+2] = keyboard_cmd[i]; } - ESP_LOGD(HID_LE_PRF_TAG, "the key vaule = %d,%d,%d, %d, %d, %d,%d, %d", buffer[0], buffer[1], buffer[2], buffer[3], buffer[4], buffer[5], buffer[6], buffer[7]); + ESP_LOGD(HID_LE_PRF_TAG, "the key value = %d,%d,%d, %d, %d, %d,%d, %d", buffer[0], buffer[1], buffer[2], buffer[3], buffer[4], buffer[5], buffer[6], buffer[7]); hid_dev_send_report(hidd_le_env.gatt_if, conn_id, HID_RPT_ID_KEY_IN, HID_REPORT_TYPE_INPUT, HID_KEYBOARD_IN_RPT_LEN, buffer); return; diff --git a/examples/bluetooth/bluedroid/ble/ble_hid_device_demo/main/hid_device_le_prf.c b/examples/bluetooth/bluedroid/ble/ble_hid_device_demo/main/hid_device_le_prf.c index fc86ee30dc3..c99e20693fe 100644 --- a/examples/bluetooth/bluedroid/ble/ble_hid_device_demo/main/hid_device_le_prf.c +++ b/examples/bluetooth/bluedroid/ble/ble_hid_device_demo/main/hid_device_le_prf.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2021-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Unlicense OR CC0-1.0 */ @@ -170,7 +170,7 @@ static const uint8_t hidReportMap[] = { 0x09, 0xA6, // Usage(Vendor defined) 0x09, 0xA9, // Usage(Vendor defined) 0x75, 0x08, // Report Size - 0x95, 0x7F, // Report Count = 127 Btyes + 0x95, 0x7F, // Report Count = 127 Bytes 0x91, 0x02, // Output(Data, Variable, Absolute) 0xC0, // End Collection #endif @@ -285,7 +285,7 @@ static const uint8_t char_prop_read_notify = ESP_GATT_CHAR_PROP_BIT_READ|ESP_GAT static const uint8_t char_prop_read_write_notify = ESP_GATT_CHAR_PROP_BIT_READ|ESP_GATT_CHAR_PROP_BIT_WRITE|ESP_GATT_CHAR_PROP_BIT_NOTIFY; static const uint8_t char_prop_read_write_write_nr = ESP_GATT_CHAR_PROP_BIT_READ|ESP_GATT_CHAR_PROP_BIT_WRITE|ESP_GATT_CHAR_PROP_BIT_WRITE_NR; -/// battary Service +/// battery Service static const uint16_t battary_svc = ESP_GATT_UUID_BATTERY_SERVICE_SVC; static const uint16_t bat_lev_uuid = ESP_GATT_UUID_BATTERY_LEVEL; @@ -296,23 +296,23 @@ static uint8_t battary_lev = 50; /// Full HRS Database Description - Used to add attributes into the database static const esp_gatts_attr_db_t bas_att_db[BAS_IDX_NB] = { - // Battary Service Declaration + // Battery Service Declaration [BAS_IDX_SVC] = {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&primary_service_uuid, ESP_GATT_PERM_READ, sizeof(uint16_t), sizeof(battary_svc), (uint8_t *)&battary_svc}}, - // Battary level Characteristic Declaration + // Battery level Characteristic Declaration [BAS_IDX_BATT_LVL_CHAR] = {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&character_declaration_uuid, ESP_GATT_PERM_READ, CHAR_DECLARATION_SIZE,CHAR_DECLARATION_SIZE, (uint8_t *)&char_prop_read_notify}}, - // Battary level Characteristic Value + // Battery level Characteristic Value [BAS_IDX_BATT_LVL_VAL] = {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&bat_lev_uuid, ESP_GATT_PERM_READ, sizeof(uint8_t),sizeof(uint8_t), &battary_lev}}, - // Battary level Characteristic - Client Characteristic Configuration Descriptor + // Battery level Characteristic - Client Characteristic Configuration Descriptor [BAS_IDX_BATT_LVL_NTF_CFG] = {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&character_client_config_uuid, ESP_GATT_PERM_READ|ESP_GATT_PERM_WRITE, sizeof(uint16_t),sizeof(bat_lev_ccc), (uint8_t *)bat_lev_ccc}}, - // Battary level report Characteristic Declaration + // Battery level report Characteristic Declaration [BAS_IDX_BATT_LVL_PRES_FMT] = {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&char_format_uuid, ESP_GATT_PERM_READ, sizeof(struct prf_char_pres_fmt), 0, NULL}}, }; @@ -550,6 +550,7 @@ void esp_hidd_prf_cb_hdl(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, } } if(param->reg.app_id == BATTRAY_APP_ID) { + hidd_le_env.bat_gatt_if = gatts_if; hidd_param.init_finish.gatts_if = gatts_if; if(hidd_le_env.hidd_cb != NULL) { (hidd_le_env.hidd_cb)(ESP_BAT_EVENT_REG, &hidd_param); @@ -587,7 +588,8 @@ void esp_hidd_prf_cb_hdl(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, break; case ESP_GATTS_WRITE_EVT: { esp_hidd_cb_param_t cb_param = {0}; - if (param->write.handle == hidd_le_env.hidd_inst.att_tbl[HIDD_LE_IDX_REPORT_LED_OUT_VAL]) { + if (hidd_le_env.hidd_cb != NULL && + param->write.handle == hidd_le_env.hidd_inst.att_tbl[HIDD_LE_IDX_REPORT_LED_OUT_VAL]) { cb_param.led_write.conn_id = param->write.conn_id; cb_param.led_write.report_id = HID_RPT_ID_LED_OUT; cb_param.led_write.length = param->write.len; @@ -595,8 +597,8 @@ void esp_hidd_prf_cb_hdl(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, (hidd_le_env.hidd_cb)(ESP_HIDD_EVENT_BLE_LED_REPORT_WRITE_EVT, &cb_param); } #if (SUPPORT_REPORT_VENDOR == true) - if (param->write.handle == hidd_le_env.hidd_inst.att_tbl[HIDD_LE_IDX_REPORT_VENDOR_OUT_VAL] && - hidd_le_env.hidd_cb != NULL) { + if (hidd_le_env.hidd_cb != NULL && + param->write.handle == hidd_le_env.hidd_inst.att_tbl[HIDD_LE_IDX_REPORT_VENDOR_OUT_VAL]) { cb_param.vendor_write.conn_id = param->write.conn_id; cb_param.vendor_write.report_id = HID_RPT_ID_VENDOR_OUT; cb_param.vendor_write.length = param->write.len; @@ -607,24 +609,26 @@ void esp_hidd_prf_cb_hdl(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, break; } case ESP_GATTS_CREAT_ATTR_TAB_EVT: { - if (param->add_attr_tab.num_handle == BAS_IDX_NB && - param->add_attr_tab.svc_uuid.uuid.uuid16 == ESP_GATT_UUID_BATTERY_SERVICE_SVC && - param->add_attr_tab.status == ESP_GATT_OK) { + if (param->add_attr_tab.status != ESP_GATT_OK) { + ESP_LOGE(HID_LE_PRF_TAG, "ATTR_TAB_EVT failed: status=%d num_handle=%d", + param->add_attr_tab.status, param->add_attr_tab.num_handle); + } else if (param->add_attr_tab.num_handle == BAS_IDX_NB && + param->add_attr_tab.svc_uuid.uuid.uuid16 == ESP_GATT_UUID_BATTERY_SERVICE_SVC) { incl_svc.start_hdl = param->add_attr_tab.handles[BAS_IDX_SVC]; - incl_svc.end_hdl = incl_svc.start_hdl + BAS_IDX_NB -1; + incl_svc.end_hdl = incl_svc.start_hdl + BAS_IDX_NB - 1; ESP_LOGI(HID_LE_PRF_TAG, "%s(), start added the hid service to the stack database. incl_handle = %d", __func__, incl_svc.start_hdl); + esp_ble_gatts_start_service(param->add_attr_tab.handles[BAS_IDX_SVC]); esp_ble_gatts_create_attr_tab(hidd_le_gatt_db, gatts_if, HIDD_LE_IDX_NB, 0); - } - if (param->add_attr_tab.num_handle == HIDD_LE_IDX_NB && - param->add_attr_tab.status == ESP_GATT_OK) { + } else if (param->add_attr_tab.num_handle == HIDD_LE_IDX_NB) { memcpy(hidd_le_env.hidd_inst.att_tbl, param->add_attr_tab.handles, - HIDD_LE_IDX_NB*sizeof(uint16_t)); - ESP_LOGI(HID_LE_PRF_TAG, "hid svc handle = %x",hidd_le_env.hidd_inst.att_tbl[HIDD_LE_IDX_SVC]); + HIDD_LE_IDX_NB * sizeof(uint16_t)); + ESP_LOGI(HID_LE_PRF_TAG, "hid svc handle = %x", hidd_le_env.hidd_inst.att_tbl[HIDD_LE_IDX_SVC]); hid_add_id_tbl(); - esp_ble_gatts_start_service(hidd_le_env.hidd_inst.att_tbl[HIDD_LE_IDX_SVC]); + esp_ble_gatts_start_service(hidd_le_env.hidd_inst.att_tbl[HIDD_LE_IDX_SVC]); } else { - esp_ble_gatts_start_service(param->add_attr_tab.handles[0]); + ESP_LOGW(HID_LE_PRF_TAG, "ATTR_TAB_EVT unexpected num_handle=%d", + param->add_attr_tab.num_handle); } break; } @@ -645,8 +649,10 @@ void hidd_le_create_service(esp_gatt_if_t gatts_if) void hidd_le_init(void) { - // Reset the hid device target environment + /* Reset the hid device target environment */ memset(&hidd_le_env, 0, sizeof(hidd_le_env_t)); + hidd_le_env.gatt_if = ESP_GATT_IF_NONE; + hidd_le_env.bat_gatt_if = ESP_GATT_IF_NONE; } void hidd_clcb_alloc (uint16_t conn_id, esp_bd_addr_t bda) @@ -666,16 +672,15 @@ void hidd_clcb_alloc (uint16_t conn_id, esp_bd_addr_t bda) return; } -bool hidd_clcb_dealloc (uint16_t conn_id) +bool hidd_clcb_dealloc(uint16_t conn_id) { - uint8_t i_clcb = 0; - hidd_clcb_t *p_clcb = NULL; - - for (i_clcb = 0, p_clcb= hidd_le_env.hidd_clcb; i_clcb < HID_MAX_APPS; i_clcb++, p_clcb++) { + for (uint8_t i_clcb = 0; i_clcb < HID_MAX_APPS; i_clcb++) { + hidd_clcb_t *p_clcb = &hidd_le_env.hidd_clcb[i_clcb]; + if (p_clcb->in_use && p_clcb->conn_id == conn_id) { memset(p_clcb, 0, sizeof(hidd_clcb_t)); return true; + } } - return false; } @@ -735,12 +740,12 @@ void hidd_set_attr_value(uint16_t handle, uint16_t val_len, const uint8_t *value return; } -void hidd_get_attr_value(uint16_t handle, uint16_t *length, uint8_t **value) +void hidd_get_attr_value(uint16_t handle, uint16_t *length, const uint8_t **value) { hidd_inst_t *hidd_inst = &hidd_le_env.hidd_inst; if(hidd_inst->att_tbl[HIDD_LE_IDX_HID_INFO_VAL] <= handle && hidd_inst->att_tbl[HIDD_LE_IDX_REPORT_REP_REF] >= handle){ - esp_ble_gatts_get_attr_value(handle, length, (const uint8_t **)value); + esp_ble_gatts_get_attr_value(handle, length, value); } else { ESP_LOGE(HID_LE_PRF_TAG, "%s error:Invalid handle value.", __func__); } diff --git a/examples/bluetooth/bluedroid/ble/ble_hid_device_demo/main/hidd_le_prf_int.h b/examples/bluetooth/bluedroid/ble/ble_hid_device_demo/main/hidd_le_prf_int.h index 6b48ac43426..7abac8dcdea 100644 --- a/examples/bluetooth/bluedroid/ble/ble_hid_device_demo/main/hidd_le_prf_int.h +++ b/examples/bluetooth/bluedroid/ble/ble_hid_device_demo/main/hidd_le_prf_int.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2021-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Unlicense OR CC0-1.0 */ @@ -183,7 +183,7 @@ enum { HIDD_LE_CHAR_MAX //= HIDD_LE_REPORT_CHAR + HIDD_LE_NB_REPORT_INST_MAX, }; -///att read event table Indexs +///att read event table Indexes enum { HIDD_LE_READ_INFO_EVT, HIDD_LE_READ_CTNL_PT_EVT, @@ -306,7 +306,8 @@ typedef struct /* service engine control block */ typedef struct { hidd_clcb_t hidd_clcb[HID_MAX_APPS]; /* connection link*/ - esp_gatt_if_t gatt_if; + esp_gatt_if_t gatt_if; /* HIDD_APP_ID */ + esp_gatt_if_t bat_gatt_if; /* BATTRAY_APP_ID */ bool enabled; bool is_take; bool is_primery; @@ -327,7 +328,7 @@ void hidd_le_create_service(esp_gatt_if_t gatts_if); void hidd_set_attr_value(uint16_t handle, uint16_t val_len, const uint8_t *value); -void hidd_get_attr_value(uint16_t handle, uint16_t *length, uint8_t **value); +void hidd_get_attr_value(uint16_t handle, uint16_t *length, const uint8_t **value); esp_err_t hidd_register_cb(void); diff --git a/examples/bluetooth/bluedroid/ble/ble_hid_device_demo/sdkconfig.defaults b/examples/bluetooth/bluedroid/ble/ble_hid_device_demo/sdkconfig.defaults index ad218785e70..732748565ff 100644 --- a/examples/bluetooth/bluedroid/ble/ble_hid_device_demo/sdkconfig.defaults +++ b/examples/bluetooth/bluedroid/ble/ble_hid_device_demo/sdkconfig.defaults @@ -6,3 +6,9 @@ CONFIG_BT_ENABLED=y CONFIG_BT_BLE_42_FEATURES_SUPPORTED=y # CONFIG_BT_LE_50_FEATURE_SUPPORT is not used on ESP32, ESP32-C3 and ESP32-S3. # CONFIG_BT_LE_50_FEATURE_SUPPORT is not set +CONFIG_PARTITION_TABLE_SINGLE_APP_LARGE=y + +# Disable unused Bluedroid host features for HID device example (server with SMP) +# CONFIG_BT_GATTC_ENABLE is not set +# CONFIG_BT_BLE_42_SCAN_EN is not set +# CONFIG_BT_BLE_42_DTM_TEST_EN is not set diff --git a/examples/bluetooth/bluedroid/ble/ble_ibeacon/sdkconfig.defaults b/examples/bluetooth/bluedroid/ble/ble_ibeacon/sdkconfig.defaults index ad218785e70..dc2d132fbd4 100644 --- a/examples/bluetooth/bluedroid/ble/ble_ibeacon/sdkconfig.defaults +++ b/examples/bluetooth/bluedroid/ble/ble_ibeacon/sdkconfig.defaults @@ -6,3 +6,9 @@ CONFIG_BT_ENABLED=y CONFIG_BT_BLE_42_FEATURES_SUPPORTED=y # CONFIG_BT_LE_50_FEATURE_SUPPORT is not used on ESP32, ESP32-C3 and ESP32-S3. # CONFIG_BT_LE_50_FEATURE_SUPPORT is not set + +# Disable unused Bluedroid host features for iBeacon (advertising/scanning only, no GATT/SMP) +# CONFIG_BT_GATTS_ENABLE is not set +# CONFIG_BT_GATTC_ENABLE is not set +# CONFIG_BT_BLE_SMP_ENABLE is not set +# CONFIG_BT_BLE_42_DTM_TEST_EN is not set diff --git a/examples/bluetooth/bluedroid/ble/ble_multi_conn/ble_multi_conn_cent/main/ble_multiconn_cent_demo.c b/examples/bluetooth/bluedroid/ble/ble_multi_conn/ble_multi_conn_cent/main/ble_multiconn_cent_demo.c index 28d67e64b52..007c12f7fc3 100644 --- a/examples/bluetooth/bluedroid/ble/ble_multi_conn/ble_multi_conn_cent/main/ble_multiconn_cent_demo.c +++ b/examples/bluetooth/bluedroid/ble/ble_multi_conn/ble_multi_conn_cent/main/ble_multiconn_cent_demo.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2021-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2021-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Unlicense OR CC0-1.0 */ @@ -219,6 +219,22 @@ static const esp_gatts_attr_db_t gatt_db[HRS_IDX_NB] = }; static uint16_t profile_handle_table[HRS_IDX_NB]; +/** After a prepared write is executed, read committed value from GATT DB and relay to peers. */ +static void relay_demo_char_value_to_peers(void) +{ + uint16_t len = 0; + const uint8_t *value = NULL; + uint16_t h = profile_handle_table[IDX_CHAR_VAL_A]; + + if (h == 0) { + return; + } + if (esp_ble_gatts_get_attr_value(h, &len, &value) != ESP_GATT_OK || value == NULL || len == 0) { + return; + } + traverse_send_peer(len, (uint8_t *)value); +} + #if (BLE50_SUPPORTED == 1) static esp_ble_gap_ext_adv_t ext_adv[1] = { [0] = {ADV_HANDLE_INST, ADV_DURATION, ADV_MAX_EVTS}, @@ -238,8 +254,9 @@ static void gattc_profile_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_ break; case ESP_GATTC_CONNECT_EVT: if (param->connect.link_role == 0) { + ++multi_conn_num; ESP_LOGI(DEMO_TAG, "Connected, conn_id %d, remote "ESP_BD_ADDR_STR", total %u", param->connect.conn_id, - ESP_BD_ADDR_HEX(param->connect.remote_bda), ++multi_conn_num); + ESP_BD_ADDR_HEX(param->connect.remote_bda), (unsigned)multi_conn_num); Peer new_peer; new_peer.conn_id = param->connect.conn_id; new_peer.conn_handle = param->connect.conn_handle; @@ -261,20 +278,33 @@ static void gattc_profile_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_ } } break; - case ESP_GATTC_DISCONNECT_EVT: - ESP_LOGI(DEMO_TAG, "Disconnected, remote "ESP_BD_ADDR_STR", reason 0x%02x, total %u", - ESP_BD_ADDR_HEX(param->disconnect.remote_bda), param->disconnect.reason, - (multi_conn_num ? --multi_conn_num : multi_conn_num)); - if (param->disconnect.conn_id != prph_conn_id) { - Peer *peer = find_peer(param->disconnect.conn_id); - if (peer) { - peer_remove(peer->conn_id); + case ESP_GATTC_DISCONNECT_EVT: { + /* Only central-role connections are added to the peer list (see ESP_GATTC_CONNECT_EVT); + * peripheral-role connections are tracked separately via prph_conn_id and cleaned up + * entirely in ESP_GATTS_DISCONNECT_EVT. Use peer_lookup() as the role oracle here: + * a non-NULL result is the unambiguous proof that this disconnect belongs to a + * central-role peer, regardless of GATTC/GATTS event ordering. + * + * Note: Bluedroid posts ESP_GATTS_DISCONNECT_EVT to the BTC queue before + * ESP_GATTC_DISCONNECT_EVT (the GATTS path is delivered directly, while the GATTC + * path is relayed through the BTA queue), so by the time we get here for a + * peripheral disconnect, prph_conn_id has already been reset to 0xFFFF. Relying on + * (conn_id != prph_conn_id) would therefore mis-classify the peripheral disconnect + * as a central one and incorrectly decrement multi_conn_num / give restart_scan_sem. + * peer_lookup() is used instead of find_peer() so peripheral disconnects don't + * trigger a spurious "peer not found" ERROR log. */ + Peer *peer = peer_lookup(param->disconnect.conn_id); + if (peer) { + peer_remove(peer->conn_id); + if (multi_conn_num) { + --multi_conn_num; } + ESP_LOGI(DEMO_TAG, "Disconnected, remote "ESP_BD_ADDR_STR", reason 0x%02x, total %u", + ESP_BD_ADDR_HEX(param->disconnect.remote_bda), param->disconnect.reason, (unsigned)multi_conn_num); xSemaphoreGive(restart_scan_sem); - } else { - prph_conn_id = 0xFFFF; } break; + } case ESP_GATTC_OPEN_EVT: ESP_LOGI(DEMO_TAG, "Open, conn_id %d, status %d", param->open.conn_id, param->open.status); break; @@ -340,14 +370,27 @@ static void gatts_profile_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_ esp_ble_gatts_create_attr_tab(gatt_db, gatts_if, HRS_IDX_NB, SVC_INST_ID); break; case ESP_GATTS_WRITE_EVT: - ESP_LOGI(DEMO_TAG, "Characteristic write received, conn_id %u, value", param->write.conn_id); + ESP_LOGI(DEMO_TAG, "Characteristic write received, conn_id %u, prep=%d", param->write.conn_id, + (int)param->write.is_prep); ESP_LOG_BUFFER_HEX(DEMO_TAG, param->write.value, param->write.len); - traverse_send_peer(param->write.len, param->write.value); + /* Prepared writes must be relayed only after ESP_GATTS_EXEC_WRITE_EVT (EXEC). */ + if (param->write.is_prep) { + break; + } + if (param->write.handle == profile_handle_table[IDX_CHAR_VAL_A]) { + traverse_send_peer(param->write.len, param->write.value); + } + break; + case ESP_GATTS_EXEC_WRITE_EVT: + if (param->exec_write.exec_write_flag == ESP_GATT_PREP_WRITE_EXEC) { + relay_demo_char_value_to_peers(); + } break; case ESP_GATTS_CONNECT_EVT: if (param->connect.link_role == 1) { + ++multi_conn_num; ESP_LOGI(DEMO_TAG, "Connected, conn_id %u, remote "ESP_BD_ADDR_STR", total %u", - param->connect.conn_id, ESP_BD_ADDR_HEX(param->connect.remote_bda), ++multi_conn_num); + param->connect.conn_id, ESP_BD_ADDR_HEX(param->connect.remote_bda), (unsigned)multi_conn_num); prph_conn_id = param->connect.conn_id; advertising_state = DISABLED; #if (BLE50_SUPPORTED == 1) @@ -361,9 +404,17 @@ static void gatts_profile_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_ break; case ESP_GATTS_DISCONNECT_EVT: if (param->disconnect.conn_id == prph_conn_id) { + unsigned total_after; + if (multi_conn_num) { + --multi_conn_num; + total_after = (unsigned)multi_conn_num; + } else { + total_after = 0; + } ESP_LOGI(DEMO_TAG, "Disconnected, remote "ESP_BD_ADDR_STR", reason 0x%x, total %u", - ESP_BD_ADDR_HEX(param->disconnect.remote_bda), param->disconnect.reason, - (multi_conn_num ? --multi_conn_num : multi_conn_num)); + ESP_BD_ADDR_HEX(param->disconnect.remote_bda), param->disconnect.reason, total_after); + /* Clear before SET_STATIC_RAND_ADDR_EVT: esp_gap_cb requires 0xFFFF to restart advertising */ + prph_conn_id = 0xFFFF; advertising_state = PENDING; #if (BLE50_SUPPORTED == 1) esp_ble_gap_addr_create_static(adv_rand_addr); @@ -376,7 +427,7 @@ static void gatts_profile_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_ break; case ESP_GATTS_CREAT_ATTR_TAB_EVT: ESP_LOGI(DEMO_TAG, "The number handle = %x", param->add_attr_tab.num_handle); - if (param->create.status == ESP_GATT_OK) { + if (param->add_attr_tab.status == ESP_GATT_OK) { if (param->add_attr_tab.num_handle == HRS_IDX_NB) { memcpy(profile_handle_table, param->add_attr_tab.handles, sizeof(profile_handle_table)); @@ -386,7 +437,7 @@ static void gatts_profile_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_ param->add_attr_tab.num_handle, HRS_IDX_NB); } } else { - ESP_LOGE(DEMO_TAG, " Create attribute table failed, status %x", param->create.status); + ESP_LOGE(DEMO_TAG, "Create attribute table failed, status %x", param->add_attr_tab.status); } break; default: @@ -484,7 +535,7 @@ static void esp_gap_cb(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *par ESP_BLE_AD_TYPE_NAME_CMPL, &adv_name_len); // ESP_LOGI(DEMO_TAG, "Scan result, device "ESP_BD_ADDR_STR", name len %u", ESP_BD_ADDR_HEX(param->ext_adv_report.params.addr), adv_name_len); // ESP_LOG_BUFFER_CHAR(DEMO_TAG, adv_name, adv_name_len); - if (strlen(remote_target_name) == adv_name_len && strncmp((char *)adv_name, remote_target_name, adv_name_len) == 0) + if (adv_name != NULL && strlen(remote_target_name) == adv_name_len && strncmp((char *)adv_name, remote_target_name, adv_name_len) == 0) { esp_ble_gap_stop_ext_scan(); scan_state = DISABLED; @@ -532,13 +583,17 @@ static void esp_gap_cb(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *par if (scan_result->scan_rst.search_evt == ESP_GAP_SEARCH_INQ_RES_EVT) { uint8_t *adv_name = NULL; uint8_t adv_name_len = 0; + const unsigned buf_cap = sizeof(scan_result->scan_rst.ble_adv); + unsigned comb = (unsigned)scan_result->scan_rst.adv_data_len + + (unsigned)scan_result->scan_rst.scan_rsp_len; + uint16_t safe_adv_len = (comb > buf_cap) ? (uint16_t)buf_cap : (uint16_t)comb; adv_name = esp_ble_resolve_adv_data_by_type(scan_result->scan_rst.ble_adv, - scan_result->scan_rst.adv_data_len + scan_result->scan_rst.scan_rsp_len, + safe_adv_len, ESP_BLE_AD_TYPE_NAME_CMPL, &adv_name_len); // ESP_LOGI(DEMO_TAG, "Scan result, device "ESP_BD_ADDR_STR", name len %u", ESP_BD_ADDR_HEX(scan_result->scan_rst.bda), adv_name_len); // ESP_LOG_BUFFER_CHAR(DEMO_TAG, adv_name, adv_name_len); - if (strlen(remote_target_name) == adv_name_len && strncmp((char *)adv_name, remote_target_name, adv_name_len) == 0) { + if (adv_name != NULL && strlen(remote_target_name) == adv_name_len && strncmp((char *)adv_name, remote_target_name, adv_name_len) == 0) { esp_ble_gap_stop_scanning(); scan_state = DISABLED; @@ -712,10 +767,12 @@ void app_main(void) if (multi_conn_num < BLE_PEER_MAX_NUM && scan_state == DISABLED) { scan_state = PENDING; #if (BLE50_SUPPORTED == 0) + /* Legacy: cannot set scan random addr until adv stops; completion continues in + * ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT. Do not break out of while(1) or app_main exits. */ if (advertising_state != DISABLED) { advertising_state = DISABLED; esp_ble_gap_stop_advertising(); - break; + continue; } #endif esp_ble_gap_addr_create_static(new_rand_addr); diff --git a/examples/bluetooth/bluedroid/ble/ble_multi_conn/ble_multi_conn_cent/main/ble_multiconn_cent_demo.h b/examples/bluetooth/bluedroid/ble/ble_multi_conn/ble_multi_conn_cent/main/ble_multiconn_cent_demo.h index 3394dd33754..df5a2baaada 100644 --- a/examples/bluetooth/bluedroid/ble/ble_multi_conn/ble_multi_conn_cent/main/ble_multiconn_cent_demo.h +++ b/examples/bluetooth/bluedroid/ble/ble_multi_conn/ble_multi_conn_cent/main/ble_multiconn_cent_demo.h @@ -66,4 +66,6 @@ esp_err_t peer_remove(uint16_t conn_id); Peer *find_peer(uint16_t conn_id); +Peer *peer_lookup(uint16_t conn_id); + #endif diff --git a/examples/bluetooth/bluedroid/ble/ble_multi_conn/ble_multi_conn_cent/main/peer_manager.c b/examples/bluetooth/bluedroid/ble/ble_multi_conn/ble_multi_conn_cent/main/peer_manager.c index 6b22e1144f2..f20ffc4b2b6 100644 --- a/examples/bluetooth/bluedroid/ble/ble_multi_conn/ble_multi_conn_cent/main/peer_manager.c +++ b/examples/bluetooth/bluedroid/ble/ble_multi_conn/ble_multi_conn_cent/main/peer_manager.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Unlicense OR CC0-1.0 */ @@ -8,6 +8,19 @@ static Peer remote_peer_lst[MAX_CONN_NUM]; +/* Silent lookup: returns NULL without logging if conn_id is not in the peer list. + * Use this when the caller legitimately treats a miss as "not a central peer" + * (e.g. disconnect path where peripheral-role conn_ids are intentionally absent). */ +Peer *peer_lookup(uint16_t conn_id) +{ + for (int i = 0; i < MAX_CONN_NUM; i++) { + if (remote_peer_lst[i].conn_id == conn_id) { + return &remote_peer_lst[i]; + } + } + return NULL; +} + void peer_manager_init(void) { for (int i = 0; i < MAX_CONN_NUM; i++) { @@ -20,6 +33,18 @@ void peer_manager_init(void) esp_err_t peer_add(Peer *peer) { + if (peer == NULL) { + return ESP_ERR_INVALID_ARG; + } + Peer *existing = peer_lookup(peer->conn_id); + if (existing != NULL) { + /* Same conn_id already tracked — refresh metadata, do not consume a second slot */ + existing->conn_handle = peer->conn_handle; + existing->gattc_if = peer->gattc_if; + memcpy(&existing->peer_addr, &peer->peer_addr, sizeof(esp_bd_addr_t)); + ESP_LOGW(PEER_MANAGER_TAG, "peer_add: conn_id %u already in list, updated", peer->conn_id); + return ESP_OK; + } for (int i = 0; i < MAX_CONN_NUM; i++) { if (remote_peer_lst[i].conn_id == 0xFFFF) { remote_peer_lst[i].char_handle = 0xFFFF; @@ -51,14 +76,11 @@ esp_err_t peer_remove(uint16_t conn_id) Peer *find_peer(uint16_t conn_id) { - for (int i = 0; i < MAX_CONN_NUM; i++) { - if (remote_peer_lst[i].conn_id == conn_id) { - return &remote_peer_lst[i]; - } + Peer *p = peer_lookup(conn_id); + if (p == NULL) { + ESP_LOGE(PEER_MANAGER_TAG, "peer not found in list, conn_id %d", conn_id); } - - ESP_LOGE(PEER_MANAGER_TAG, "peer not found in list, conn_id %d", conn_id); - return NULL; + return p; } void traverse_send_peer(uint16_t len, uint8_t *value) diff --git a/examples/bluetooth/bluedroid/ble/ble_multi_conn/ble_multi_conn_cent/sdkconfig.defaults b/examples/bluetooth/bluedroid/ble/ble_multi_conn/ble_multi_conn_cent/sdkconfig.defaults index d6755c993c9..eda43c68c2e 100644 --- a/examples/bluetooth/bluedroid/ble/ble_multi_conn/ble_multi_conn_cent/sdkconfig.defaults +++ b/examples/bluetooth/bluedroid/ble/ble_multi_conn/ble_multi_conn_cent/sdkconfig.defaults @@ -6,3 +6,9 @@ CONFIG_BT_ENABLED=y CONFIG_BT_BLE_42_FEATURES_SUPPORTED=y # CONFIG_BT_BLE_50_FEATURES_SUPPORTED is not set CONFIG_BT_MULTI_CONNECTION_ENBALE=y +CONFIG_PARTITION_TABLE_SINGLE_APP_LARGE=y + +# Disable unused Bluedroid host features for multi-connection central +# (acts as both GATT server and GATT client, but no SMP) +# CONFIG_BT_BLE_SMP_ENABLE is not set +# CONFIG_BT_BLE_42_DTM_TEST_EN is not set diff --git a/examples/bluetooth/bluedroid/ble/ble_multi_conn/ble_multi_conn_prph/main/ble_multiconn_prph_demo.c b/examples/bluetooth/bluedroid/ble/ble_multi_conn/ble_multi_conn_prph/main/ble_multiconn_prph_demo.c index b1131432bff..186b2e1d516 100644 --- a/examples/bluetooth/bluedroid/ble/ble_multi_conn/ble_multi_conn_prph/main/ble_multiconn_prph_demo.c +++ b/examples/bluetooth/bluedroid/ble/ble_multi_conn/ble_multi_conn_prph/main/ble_multiconn_prph_demo.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2021-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2021-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Unlicense OR CC0-1.0 */ @@ -139,10 +139,14 @@ static void ble_prph_set_new_adv(void) is_advertising = true; esp_ble_gap_addr_create_static(new_rand_addr); #if (BLE50_SUPPORTED == 1) - esp_ble_gap_ext_adv_set_rand_addr(EXT_ADV_HANDLE, new_rand_addr); + esp_err_t err = esp_ble_gap_ext_adv_set_rand_addr(EXT_ADV_HANDLE, new_rand_addr); #else - esp_ble_gap_set_rand_addr(new_rand_addr); + esp_err_t err = esp_ble_gap_set_rand_addr(new_rand_addr); #endif + if (err != ESP_OK) { + is_advertising = false; + ESP_LOGE(DEMO_TAG, "Set random addr failed: %s", esp_err_to_name(err)); + } } } @@ -178,41 +182,67 @@ static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param case ESP_GAP_BLE_EXT_ADV_SET_RAND_ADDR_COMPLETE_EVT: ESP_LOGI(DEMO_TAG, "Extended adv random address set, status %d, "ESP_BD_ADDR_STR"", param->ext_adv_set_rand_addr.status, ESP_BD_ADDR_HEX(new_rand_addr)); + if (param->ext_adv_set_rand_addr.status != ESP_BT_STATUS_SUCCESS) { + is_advertising = false; + break; + } esp_ble_gap_ext_adv_start(NUM_EXT_ADV_SET, &ext_adv[0]); break; case ESP_GAP_BLE_EXT_ADV_SET_PARAMS_COMPLETE_EVT: ESP_LOGI(DEMO_TAG, "Extended advertising params set, status %d", param->ext_adv_set_params.status); + if (param->ext_adv_set_params.status != ESP_BT_STATUS_SUCCESS) { + break; + } esp_ble_gap_config_ext_adv_data_raw(EXT_ADV_HANDLE, sizeof(adv_data_raw), &adv_data_raw[0]); break; case ESP_GAP_BLE_EXT_ADV_DATA_SET_COMPLETE_EVT: ESP_LOGI(DEMO_TAG, "Extended advertising data set, status %d", param->ext_adv_data_set.status); + if (param->ext_adv_data_set.status != ESP_BT_STATUS_SUCCESS) { + break; + } ble_prph_restart_adv(); break; case ESP_GAP_BLE_EXT_ADV_START_COMPLETE_EVT: ESP_LOGI(DEMO_TAG, "Extended advertising start, status %d", param->ext_adv_start.status); - is_advertising = true; + if (param->ext_adv_start.status == ESP_BT_STATUS_SUCCESS) { + is_advertising = true; + } else { + is_advertising = false; + } break; case ESP_GAP_BLE_ADV_TERMINATED_EVT: ESP_LOGI(DEMO_TAG, "Extended advertising terminated, status = %d", param->adv_terminate.status); if (param->adv_terminate.status == 0x00) { ESP_LOGI(DEMO_TAG, "Advertising successfully ended with a connection being created"); - is_advertising = false; } + /* Any terminate reason means advertising has stopped; clear flag so restart can run */ + is_advertising = false; break; #else case ESP_GAP_BLE_ADV_DATA_RAW_SET_COMPLETE_EVT: ESP_LOGI(DEMO_TAG, "Advertising data set, status %d", param->adv_data_raw_cmpl.status); + if (param->adv_data_raw_cmpl.status != ESP_BT_STATUS_SUCCESS) { + break; + } esp_ble_gap_addr_create_static(new_rand_addr); esp_ble_gap_set_rand_addr(new_rand_addr); break; case ESP_GAP_BLE_SET_STATIC_RAND_ADDR_EVT: ESP_LOGI(DEMO_TAG, "Random address set, status %d, addr "ESP_BD_ADDR_STR"", param->set_rand_addr_cmpl.status, ESP_BD_ADDR_HEX(new_rand_addr)); + if (param->set_rand_addr_cmpl.status != ESP_BT_STATUS_SUCCESS) { + is_advertising = false; + break; + } esp_ble_gap_start_advertising(&legacy_adv_params); break; case ESP_GAP_BLE_ADV_START_COMPLETE_EVT: ESP_LOGI(DEMO_TAG, "Advertising start, status %d", param->adv_start_cmpl.status); - is_advertising = true; + if (param->adv_start_cmpl.status == ESP_BT_STATUS_SUCCESS) { + is_advertising = true; + } else { + is_advertising = false; + } break; case ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT: ESP_LOGI(DEMO_TAG, "Advertising stop, status %d", param->adv_stop_cmpl.status); @@ -248,16 +278,20 @@ static void gatts_profile_event_handler(esp_gatts_cb_event_t event, ESP_LOG_BUFFER_HEX(DEMO_TAG, param->write.value, param->write.len); break; case ESP_GATTS_CONNECT_EVT: + prph_conn_num++; ESP_LOGI(DEMO_TAG, "Connected, conn_id %u, remote "ESP_BD_ADDR_STR", total %u", - param->connect.conn_id, ESP_BD_ADDR_HEX(param->connect.remote_bda), ++prph_conn_num); + param->connect.conn_id, ESP_BD_ADDR_HEX(param->connect.remote_bda), prph_conn_num); is_advertising = false; #if CONFIG_EXAMPLE_RESTART_ADV_AFTER_CONNECTED ble_prph_restart_adv(); #endif break; case ESP_GATTS_DISCONNECT_EVT: + if (prph_conn_num > 0) { + prph_conn_num--; + } ESP_LOGI(DEMO_TAG, "Disconnected, remote "ESP_BD_ADDR_STR", reason 0x%x, total %d", - ESP_BD_ADDR_HEX(param->disconnect.remote_bda), param->disconnect.reason, (prph_conn_num ? --prph_conn_num : prph_conn_num)); + ESP_BD_ADDR_HEX(param->disconnect.remote_bda), param->disconnect.reason, prph_conn_num); /* start advertising again when disconnected */ ble_prph_restart_adv(); break; @@ -274,7 +308,7 @@ static void gatts_profile_event_handler(esp_gatts_cb_event_t event, case ESP_GATTS_CREAT_ATTR_TAB_EVT: { ESP_LOGI(DEMO_TAG, "The number handle = %x", param->add_attr_tab.num_handle); - if (param->create.status == ESP_GATT_OK) { + if (param->add_attr_tab.status == ESP_GATT_OK) { if (param->add_attr_tab.num_handle == HRS_IDX_NB) { memcpy(profile_handle_table, param->add_attr_tab.handles, sizeof(profile_handle_table)); @@ -289,7 +323,7 @@ static void gatts_profile_event_handler(esp_gatts_cb_event_t event, param->add_attr_tab.num_handle, HRS_IDX_NB); } } else { - ESP_LOGE(DEMO_TAG, " Create attribute table failed, error code = %x", param->create.status); + ESP_LOGE(DEMO_TAG, " Create attribute table failed, error code = %x", param->add_attr_tab.status); } break; } diff --git a/examples/bluetooth/bluedroid/ble/ble_multi_conn/ble_multi_conn_prph/sdkconfig.defaults b/examples/bluetooth/bluedroid/ble/ble_multi_conn/ble_multi_conn_prph/sdkconfig.defaults index 319fcc0996e..355af902721 100644 --- a/examples/bluetooth/bluedroid/ble/ble_multi_conn/ble_multi_conn_prph/sdkconfig.defaults +++ b/examples/bluetooth/bluedroid/ble/ble_multi_conn/ble_multi_conn_prph/sdkconfig.defaults @@ -8,3 +8,10 @@ CONFIG_BT_BLE_42_FEATURES_SUPPORTED=y CONFIG_BT_ACL_CONNECTIONS=50 CONFIG_BT_ALARM_MAX_NUM=150 CONFIG_BT_MULTI_CONNECTION_ENBALE=y +CONFIG_PARTITION_TABLE_SINGLE_APP_LARGE=y + +# Disable unused Bluedroid host features for GATT server only example +# CONFIG_BT_GATTC_ENABLE is not set +# CONFIG_BT_BLE_SMP_ENABLE is not set +# CONFIG_BT_BLE_42_SCAN_EN is not set +# CONFIG_BT_BLE_42_DTM_TEST_EN is not set diff --git a/examples/bluetooth/bluedroid/ble/ble_spp_client/main/spp_client_demo.c b/examples/bluetooth/bluedroid/ble/ble_spp_client/main/spp_client_demo.c index a7c3917d092..4320a33618a 100644 --- a/examples/bluetooth/bluedroid/ble/ble_spp_client/main/spp_client_demo.c +++ b/examples/bluetooth/bluedroid/ble/ble_spp_client/main/spp_client_demo.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2021-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2021-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Unlicense OR CC0-1.0 */ @@ -17,6 +17,7 @@ #include "driver/uart.h" #include "esp_bt.h" +#include "esp_err.h" #include "nvs_flash.h" #include "esp_bt_device.h" #include "esp_gap_ble_api.h" @@ -110,6 +111,7 @@ static bool connect = false; static char * notify_value_p = NULL; static int notify_value_offset = 0; static int notify_value_count = 0; +static size_t notify_value_alloc_size = 0; static bool start = false; static uint64_t notify_len = 0; static uint64_t start_time = 0; @@ -125,60 +127,71 @@ static esp_bt_uuid_t spp_service_uuid = { .uuid = {.uuid16 = ESP_GATT_SPP_SERVICE_UUID,}, }; +/* Drop any in-flight notify reassembly buffer/counters. Used both on errors and on disconnect. */ +static void reset_notify_reasm(void) +{ + if (notify_value_p != NULL) { + free(notify_value_p); + notify_value_p = NULL; + } + notify_value_offset = 0; + notify_value_count = 0; + notify_value_alloc_size = 0; +} + static void notify_event_handler(esp_ble_gattc_cb_param_t * p_data) { - uint8_t handle = 0; + uint16_t handle = p_data->notify.handle; - handle = p_data->notify.handle; if (db == NULL) { ESP_LOGE(GATTC_TAG, " %s db is NULL", __func__); return; } if (handle == db[SPP_IDX_SPP_DATA_NTY_VAL].attribute_handle) { - if ((p_data->notify.value[0] == '#') && (p_data->notify.value[1] == '#')) { - if ((++notify_value_count) != p_data->notify.value[3]) { - if(notify_value_p != NULL){ - free(notify_value_p); - } - notify_value_count = 0; - notify_value_p = NULL; - notify_value_offset = 0; - ESP_LOGE(GATTC_TAG,"notify value count is not continuous, %s", __func__); + /* Fragment header is 4 bytes: "##", total_frags, seq. */ + if (p_data->notify.value_len >= 4 && + p_data->notify.value[0] == '#' && p_data->notify.value[1] == '#') { + uint8_t total_frags = p_data->notify.value[2]; + uint8_t seq = p_data->notify.value[3]; + uint16_t payload_len = p_data->notify.value_len - 4; + + if ((++notify_value_count) != seq) { + ESP_LOGE(GATTC_TAG, "notify value count is not continuous, %s", __func__); + reset_notify_reasm(); return; } - if (p_data->notify.value[3] == 1) { - notify_value_p = (char *)malloc(((spp_mtu_size-7)*(p_data->notify.value[2]))*sizeof(char)); + if (seq == 1) { + notify_value_alloc_size = (size_t)(spp_mtu_size - 7) * total_frags; + notify_value_p = (char *)malloc(notify_value_alloc_size); if (notify_value_p == NULL) { ESP_LOGE(GATTC_TAG, "malloc failed, %s L#%d", __func__, __LINE__); - notify_value_count = 0; + reset_notify_reasm(); return; } - memcpy((notify_value_p + notify_value_offset), (p_data->notify.value + 4), (p_data->notify.value_len - 4)); - if (p_data->notify.value[2] == p_data->notify.value[3]) { - uart_write_bytes(UART_NUM_0, (char *)(notify_value_p), (p_data->notify.value_len - 4 + notify_value_offset)); - free(notify_value_p); - notify_value_p = NULL; - notify_value_offset = 0; - return; - } - notify_value_offset += (p_data->notify.value_len - 4); - } else if (p_data->notify.value[3] <= p_data->notify.value[2]) { - memcpy((notify_value_p + notify_value_offset), (p_data->notify.value + 4), (p_data->notify.value_len - 4)); - if (p_data->notify.value[3] == p_data->notify.value[2]) { - uart_write_bytes(UART_NUM_0, (char *)(notify_value_p), (p_data->notify.value_len - 4 + notify_value_offset)); - free(notify_value_p); - notify_value_count = 0; - notify_value_p = NULL; - notify_value_offset = 0; - return; - } - notify_value_offset += (p_data->notify.value_len - 4); + } else if (notify_value_p == NULL) { + ESP_LOGE(GATTC_TAG, "fragment %u without start, %s", seq, __func__); + reset_notify_reasm(); + return; } + /* Bound the write against the actually allocated reasm buffer to defeat + * malicious peers that send total_frags=0 or grow total_frags mid-stream. */ + if ((size_t)notify_value_offset + payload_len > notify_value_alloc_size) { + ESP_LOGE(GATTC_TAG, "fragment payload would overflow reasm buffer, %s", __func__); + reset_notify_reasm(); + return; + } + memcpy(notify_value_p + notify_value_offset, p_data->notify.value + 4, payload_len); + if (seq == total_frags) { + uart_write_bytes(UART_NUM_0, notify_value_p, payload_len + notify_value_offset); + reset_notify_reasm(); + return; + } + notify_value_offset += payload_len; } else { uart_write_bytes(UART_NUM_0, (char *)(p_data->notify.value), p_data->notify.value_len); } - } else if (handle == ((db+SPP_IDX_SPP_STATUS_VAL)->attribute_handle)) { + } else if (handle == db[SPP_IDX_SPP_STATUS_VAL].attribute_handle) { ESP_LOG_BUFFER_CHAR(GATTC_TAG, (char *)p_data->notify.value, p_data->notify.value_len); //TODO:server notify status characteristic } else { @@ -196,13 +209,42 @@ static void free_gattc_srv_db(void) cmd = 0; spp_srv_start_handle = 0; spp_srv_end_handle = 0; - notify_value_p = NULL; - notify_value_offset = 0; - notify_value_count = 0; + reset_notify_reasm(); if (db) { free(db); db = NULL; } + count = SPP_IDX_NB; +} + +/** + * Resolve the CCCD handle for a known notify-capable characteristic value index by looking up the + * adjacent ESP_GATT_UUID_CHAR_CLIENT_CONFIG descriptor. Returns 0 on mismatch. + * + * This is safer than `(db+cmd+1)->attribute_handle` because the descriptor slot is verified. + */ +static uint16_t spp_get_cccd_handle(uint16_t char_val_idx) +{ + size_t cfg_idx; + + if (db == NULL) { + return 0; + } + switch (char_val_idx) { + case SPP_IDX_SPP_DATA_NTY_VAL: cfg_idx = SPP_IDX_SPP_DATA_NTF_CFG; break; + case SPP_IDX_SPP_STATUS_VAL: cfg_idx = SPP_IDX_SPP_STATUS_CFG; break; +#ifdef SUPPORT_HEARTBEAT + case SPP_IDX_SPP_HEARTBEAT_VAL: cfg_idx = SPP_IDX_SPP_HEARTBEAT_CFG; break; +#endif + default: return 0; + } + const esp_gattc_db_elem_t *d = &db[cfg_idx]; + if (d->type != ESP_GATT_DB_DESCRIPTOR || + d->uuid.len != ESP_UUID_LEN_16 || + d->uuid.uuid.uuid16 != ESP_GATT_UUID_CHAR_CLIENT_CONFIG) { + return 0; + } + return d->attribute_handle; } static void esp_gap_cb(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) @@ -246,7 +288,9 @@ static void esp_gap_cb(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *par ESP_BLE_AD_TYPE_NAME_CMPL, &adv_name_len); ESP_LOGI(GATTC_TAG, "Scan result, device "ESP_BD_ADDR_STR", name len %u", ESP_BD_ADDR_HEX(scan_result->scan_rst.bda), adv_name_len); ESP_LOG_BUFFER_CHAR(GATTC_TAG, adv_name, adv_name_len); - if (adv_name != NULL && strncmp((char *)adv_name, device_name, adv_name_len) == 0) { + /* Full-name match (strncmp(adv_name, device_name, adv_name_len) would also accept prefixes). */ + if (adv_name != NULL && adv_name_len == (sizeof(device_name) - 1) && + memcmp(adv_name, device_name, adv_name_len) == 0) { if (connect == false) { connect = true; esp_ble_gap_stop_scanning(); @@ -373,6 +417,11 @@ static void gattc_profile_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_ if (p_data->reg_for_notify.status != ESP_GATT_OK) { break; } + uint16_t descr_handle = spp_get_cccd_handle(cmd); + if (descr_handle == 0) { + ESP_LOGW(GATTC_TAG, "CCCD handle not resolved for char index %u", (unsigned)cmd); + break; + } uint16_t notify_en = 0x01; #ifdef CONFIG_EXAMPLE_SPP_RELIABLE if (cmd == SPP_IDX_SPP_DATA_NTY_VAL) { @@ -382,7 +431,7 @@ static void gattc_profile_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_ esp_ble_gattc_write_char_descr( spp_gattc_if, spp_conn_id, - (db+cmd+1)->attribute_handle, + descr_handle, sizeof(notify_en), (uint8_t *)¬ify_en, ESP_GATT_WRITE_TYPE_RSP, @@ -438,26 +487,35 @@ static void gattc_profile_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_ break; case ESP_GATTC_CFG_MTU_EVT: ESP_LOGI(GATTC_TAG, "MTU exchange, status %d, MTU %d", param->cfg_mtu.status, param->cfg_mtu.mtu); - if(p_data->cfg_mtu.status != ESP_OK){ + if (param->cfg_mtu.status != ESP_GATT_OK) { break; } - spp_mtu_size = p_data->cfg_mtu.mtu; + spp_mtu_size = param->cfg_mtu.mtu; - db = (esp_gattc_db_elem_t *)malloc(count*sizeof(esp_gattc_db_elem_t)); - if(db == NULL){ + if (db != NULL) { + free(db); + db = NULL; + } + count = SPP_IDX_NB; + db = (esp_gattc_db_elem_t *)malloc(count * sizeof(esp_gattc_db_elem_t)); + if (db == NULL) { ESP_LOGE(GATTC_TAG, "Malloc db failed"); break; } - if(esp_ble_gattc_get_db(spp_gattc_if, spp_conn_id, spp_srv_start_handle, spp_srv_end_handle, db, &count) != ESP_GATT_OK){ + if (esp_ble_gattc_get_db(spp_gattc_if, spp_conn_id, spp_srv_start_handle, spp_srv_end_handle, db, &count) != ESP_GATT_OK) { ESP_LOGE(GATTC_TAG, "Get db failed"); + free(db); + db = NULL; break; } - if(count != SPP_IDX_NB){ + if (count != SPP_IDX_NB) { ESP_LOGE(GATTC_TAG, "Get db count != SPP_IDX_NB, count = %d, SPP_IDX_NB = %d", count, SPP_IDX_NB); + free(db); + db = NULL; break; } - for(int i = 0;i < SPP_IDX_NB;i++){ - switch((db+i)->type){ + for (int i = 0; i < SPP_IDX_NB; i++) { + switch ((db + i)->type) { case ESP_GATT_DB_PRIMARY_SERVICE: ESP_LOGI(GATTC_TAG, "PRIMARY_SERVICE, attribute_handle %d, start_handle %d, end_handle %d, properties 0x%x, uuid 0x%04x", (db+i)->attribute_handle, (db+i)->start_handle, (db+i)->end_handle, (db+i)->properties, (db+i)->uuid.uuid.uuid16); @@ -572,11 +630,11 @@ void ble_client_appRegister(void) ESP_LOGE(GATTC_TAG, "set local MTU failed: %s", esp_err_to_name_r(local_mtu_ret, err_msg, sizeof(err_msg))); } - cmd_reg_queue = xQueueCreate(10, sizeof(uint32_t)); + cmd_reg_queue = xQueueCreate(10, sizeof(uint16_t)); xTaskCreate(spp_client_reg_task, "spp_client_reg_task", 2048, NULL, 10, NULL); #ifdef SUPPORT_HEARTBEAT - cmd_heartbeat_queue = xQueueCreate(10, sizeof(uint32_t)); + cmd_heartbeat_queue = xQueueCreate(10, sizeof(uint16_t)); xTaskCreate(spp_heart_beat_task, "spp_heart_beat_task", 2048, NULL, 10, NULL); #endif esp_ble_gattc_app_register(PROFILE_APP_ID); diff --git a/examples/bluetooth/bluedroid/ble/ble_spp_client/sdkconfig.defaults b/examples/bluetooth/bluedroid/ble/ble_spp_client/sdkconfig.defaults index ad218785e70..046765b7c63 100644 --- a/examples/bluetooth/bluedroid/ble/ble_spp_client/sdkconfig.defaults +++ b/examples/bluetooth/bluedroid/ble/ble_spp_client/sdkconfig.defaults @@ -6,3 +6,10 @@ CONFIG_BT_ENABLED=y CONFIG_BT_BLE_42_FEATURES_SUPPORTED=y # CONFIG_BT_LE_50_FEATURE_SUPPORT is not used on ESP32, ESP32-C3 and ESP32-S3. # CONFIG_BT_LE_50_FEATURE_SUPPORT is not set +CONFIG_PARTITION_TABLE_SINGLE_APP_LARGE=y + +# Disable unused Bluedroid host features for GATT client only example +# CONFIG_BT_GATTS_ENABLE is not set +# CONFIG_BT_BLE_SMP_ENABLE is not set +# CONFIG_BT_BLE_42_ADV_EN is not set +# CONFIG_BT_BLE_42_DTM_TEST_EN is not set 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 422b2204869..26e2b01bd09 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 @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2021-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2021-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Unlicense OR CC0-1.0 */ @@ -59,7 +59,7 @@ static const uint8_t spp_adv_data[23] = { 0x0F, ESP_BLE_AD_TYPE_NAME_CMPL, 'E', 'S', 'P', '_', 'S', 'P', 'P', '_', 'S', 'E', 'R','V', 'E', 'R' }; -static uint16_t spp_mtu_size = SPP_GATT_MTU_SIZE; +static uint16_t spp_mtu_size = 23; static uint16_t spp_conn_id = 0xffff; static esp_gatt_if_t spp_gatts_if = 0xff; QueueHandle_t spp_uart_queue = NULL; @@ -108,19 +108,25 @@ typedef struct spp_receive_data_node{ struct spp_receive_data_node * next_node; }spp_receive_data_node_t; -static spp_receive_data_node_t * temp_spp_recv_data_node_p1 = NULL; -static spp_receive_data_node_t * temp_spp_recv_data_node_p2 = NULL; - typedef struct spp_receive_data_buff{ int32_t node_num; int32_t buff_size; - spp_receive_data_node_t * first_node; + spp_receive_data_node_t *first_node; + spp_receive_data_node_t *last_node; }spp_receive_data_buff_t; -static spp_receive_data_buff_t SppRecvDataBuff = { +/* Command queue carries (data, len); we cannot use strlen() on the payload because + * BLE writes are not NUL-terminated. */ +typedef struct { + uint16_t len; + uint8_t *data; +} spp_cmd_queue_msg_t; + +static spp_receive_data_buff_t spp_prep_wr_buff = { .node_num = 0, .buff_size = 0, - .first_node = NULL + .first_node = NULL, + .last_node = NULL, }; static void gatts_profile_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param); @@ -271,67 +277,74 @@ static uint8_t find_char_and_desr_index(uint16_t handle) static bool store_wr_buffer(esp_ble_gatts_cb_param_t *p_data) { - temp_spp_recv_data_node_p1 = (spp_receive_data_node_t *)malloc(sizeof(spp_receive_data_node_t)); - - if(temp_spp_recv_data_node_p1 == NULL){ - ESP_LOGI(GATTS_TABLE_TAG, "malloc error %s %d", __func__, __LINE__); + if (p_data == NULL) { 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; + /* Per Bluetooth Core Spec (Vol 3, Part F, 3.4.6.1) the Part Attribute Value + * in ATT_PREPARE_WRITE_REQ may be 0..(ATT_MTU-5) bytes, so a zero-length + * fragment is a valid request that carries no payload. Skip allocating an + * empty node here: malloc(0) is implementation-defined in C and on + * ESP-IDF's multi_heap returns NULL, which would otherwise be reported as + * a (false) allocation failure. */ + if (p_data->write.len == 0) { + return true; + } + + spp_receive_data_node_t *node = (spp_receive_data_node_t *)malloc(sizeof(spp_receive_data_node_t)); + if (node == NULL) { + ESP_LOGE(GATTS_TABLE_TAG, "malloc error %s %d", __func__, __LINE__); 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; + node->len = p_data->write.len; + node->next_node = NULL; + node->node_buff = (uint8_t *)malloc(p_data->write.len); + if (node->node_buff == NULL) { + ESP_LOGE(GATTS_TABLE_TAG, "malloc error %s %d", __func__, __LINE__); + free(node); + return false; } - temp_spp_recv_data_node_p2 = temp_spp_recv_data_node_p1; - SppRecvDataBuff.buff_size += p_data->write.len; + memcpy(node->node_buff, p_data->write.value, p_data->write.len); - if(SppRecvDataBuff.node_num == 0){ - SppRecvDataBuff.first_node = temp_spp_recv_data_node_p1; - SppRecvDataBuff.node_num++; - }else{ - SppRecvDataBuff.node_num++; + if (spp_prep_wr_buff.last_node != NULL) { + spp_prep_wr_buff.last_node->next_node = node; + } else { + spp_prep_wr_buff.first_node = node; } + spp_prep_wr_buff.last_node = node; + spp_prep_wr_buff.buff_size += p_data->write.len; + spp_prep_wr_buff.node_num++; return true; } -static void free_write_buffer(void) +static void free_prep_wr_buffer(void) { - temp_spp_recv_data_node_p1 = SppRecvDataBuff.first_node; + spp_receive_data_node_t *cur = spp_prep_wr_buff.first_node; - while(temp_spp_recv_data_node_p1 != NULL){ - temp_spp_recv_data_node_p2 = temp_spp_recv_data_node_p1->next_node; - if (temp_spp_recv_data_node_p1->node_buff) { - free(temp_spp_recv_data_node_p1->node_buff); + while (cur != NULL) { + spp_receive_data_node_t *next = cur->next_node; + if (cur->node_buff) { + free(cur->node_buff); } - free(temp_spp_recv_data_node_p1); - temp_spp_recv_data_node_p1 = temp_spp_recv_data_node_p2; + free(cur); + cur = next; } - SppRecvDataBuff.node_num = 0; - SppRecvDataBuff.buff_size = 0; - SppRecvDataBuff.first_node = NULL; + spp_prep_wr_buff.node_num = 0; + spp_prep_wr_buff.buff_size = 0; + spp_prep_wr_buff.first_node = NULL; + spp_prep_wr_buff.last_node = NULL; } -static void print_write_buffer(void) +static void print_prep_wr_buffer(void) { - temp_spp_recv_data_node_p1 = SppRecvDataBuff.first_node; + spp_receive_data_node_t *cur = spp_prep_wr_buff.first_node; - while (temp_spp_recv_data_node_p1 != NULL) { - uart_write_bytes(UART_NUM_0, (char *)(temp_spp_recv_data_node_p1->node_buff), temp_spp_recv_data_node_p1->len); - temp_spp_recv_data_node_p1 = temp_spp_recv_data_node_p1->next_node; + while (cur != NULL) { + uart_write_bytes(UART_NUM_0, (char *)(cur->node_buff), cur->len); + cur = cur->next_node; } } @@ -449,22 +462,26 @@ static void spp_uart_init(void) #ifdef SUPPORT_HEARTBEAT void spp_heartbeat_task(void * arg) { - uint16_t cmd_id; + uint32_t cmd_id; for(;;) { vTaskDelay(50 / portTICK_PERIOD_MS); if(xQueueReceive(cmd_heartbeat_queue, &cmd_id, portMAX_DELAY)) { - while(1){ - heartbeat_count_num++; - vTaskDelay(5000/ portTICK_PERIOD_MS); - if((heartbeat_count_num >3)&&(is_connected)){ - esp_ble_gap_disconnect(spp_remote_bda); - } - if(is_connected && enable_heart_ntf){ - esp_ble_gatts_send_indicate(spp_gatts_if, spp_conn_id, spp_handle_table[SPP_IDX_SPP_HEARTBEAT_VAL],sizeof(heartbeat_s), heartbeat_s, false); - }else if(!is_connected){ + heartbeat_count_num = 0; + while (1) { + if (!is_connected) { break; } + vTaskDelay(5000 / portTICK_PERIOD_MS); + heartbeat_count_num++; + if ((heartbeat_count_num > 3) && is_connected) { + esp_ble_gap_disconnect(spp_remote_bda); + break; + } + if (is_connected && enable_heart_ntf) { + esp_ble_gatts_send_indicate(spp_gatts_if, spp_conn_id, spp_handle_table[SPP_IDX_SPP_HEARTBEAT_VAL], + sizeof(heartbeat_s), heartbeat_s, false); + } } } } @@ -474,13 +491,15 @@ void spp_heartbeat_task(void * arg) void spp_cmd_task(void * arg) { - uint8_t * cmd_id; + spp_cmd_queue_msg_t msg; for (;;) { vTaskDelay(50 / portTICK_PERIOD_MS); - if(xQueueReceive(cmd_cmd_queue, &cmd_id, portMAX_DELAY)) { - ESP_LOG_BUFFER_CHAR(GATTS_TABLE_TAG, (char *)(cmd_id), strlen((char *)cmd_id)); - free(cmd_id); + if (xQueueReceive(cmd_cmd_queue, &msg, portMAX_DELAY)) { + if (msg.data != NULL) { + ESP_LOG_BUFFER_CHAR(GATTS_TABLE_TAG, (char *)msg.data, msg.len); + free(msg.data); + } } } vTaskDelete(NULL); @@ -499,7 +518,7 @@ static void spp_task_init(void) xTaskCreate(spp_heartbeat_task, "spp_heartbeat_task", 2048, NULL, 10, NULL); #endif - cmd_cmd_queue = xQueueCreate(10, sizeof(uint32_t)); + cmd_cmd_queue = xQueueCreate(10, sizeof(spp_cmd_queue_msg_t)); xTaskCreate(spp_cmd_task, "spp_cmd_task", 4096, NULL, 10, NULL); } @@ -518,7 +537,7 @@ static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param ESP_LOGI(GATTS_TABLE_TAG, "Advertising start successfully"); break; case ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT: - if (param->adv_start_cmpl.status != ESP_BT_STATUS_SUCCESS) { + if (param->adv_stop_cmpl.status != ESP_BT_STATUS_SUCCESS) { ESP_LOGE(GATTS_TABLE_TAG, "Advertising stop failed, status %d", param->adv_stop_cmpl.status); break; } @@ -556,15 +575,17 @@ static void gatts_profile_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_ res = find_char_and_desr_index(p_data->write.handle); if (p_data->write.is_prep == false) { if (res == SPP_IDX_SPP_COMMAND_VAL) { - uint8_t * spp_cmd_buff = NULL; - spp_cmd_buff = (uint8_t *)malloc((spp_mtu_size - 3) * sizeof(uint8_t)); - if(spp_cmd_buff == NULL){ + uint8_t *spp_cmd_buff = (uint8_t *)malloc(p_data->write.len); + if (spp_cmd_buff == NULL) { ESP_LOGE(GATTS_TABLE_TAG, "%s malloc failed", __func__); break; } - memset(spp_cmd_buff, 0x0, (spp_mtu_size - 3)); memcpy(spp_cmd_buff, p_data->write.value, p_data->write.len); - xQueueSend(cmd_cmd_queue, &spp_cmd_buff, 10/portTICK_PERIOD_MS); + spp_cmd_queue_msg_t msg = { .len = p_data->write.len, .data = spp_cmd_buff }; + if (xQueueSend(cmd_cmd_queue, &msg, 10 / portTICK_PERIOD_MS) != pdTRUE) { + ESP_LOGE(GATTS_TABLE_TAG, "%s cmd_cmd_queue send failed", __func__); + free(spp_cmd_buff); + } } else if (res == SPP_IDX_SPP_DATA_NTF_CFG) { if ((p_data->write.len == 2) && (p_data->write.value[0] == 0x01) && (p_data->write.value[1] == 0x00)) { ESP_LOGI(GATTS_TABLE_TAG, "SPP data notification enable"); @@ -588,9 +609,11 @@ static void gatts_profile_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_ if ((p_data->write.len == 2) && (p_data->write.value[0] == 0x01) && (p_data->write.value[1] == 0x00)) { ESP_LOGI(GATTS_TABLE_TAG, "SPP heartbeat notification enable"); enable_heart_ntf = true; + heartbeat_count_num = 0; } else if ((p_data->write.len == 2) && (p_data->write.value[0] == 0x00) && (p_data->write.value[1] == 0x00)) { ESP_LOGI(GATTS_TABLE_TAG, "SPP heartbeat notification disable"); enable_heart_ntf = false; + heartbeat_count_num = 0; } } else if (res == SPP_IDX_SPP_HEARTBEAT_VAL) { if ((p_data->write.len == sizeof(heartbeat_s)) && (memcmp(heartbeat_s, p_data->write.value, sizeof(heartbeat_s)) == 0)) { @@ -612,14 +635,15 @@ static void gatts_profile_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_ } break; } - case ESP_GATTS_EXEC_WRITE_EVT: { - ESP_LOGI(GATTS_TABLE_TAG, "Execute write"); - if (p_data->exec_write.exec_write_flag) { - print_write_buffer(); - free_write_buffer(); - } - break; - } + case ESP_GATTS_EXEC_WRITE_EVT: + /* End of prepared-write transaction: print on EXEC, then always release queued chunks + * (master only freed on EXEC, which leaked on CANCEL). */ + ESP_LOGI(GATTS_TABLE_TAG, "Execute write flag 0x%02x", p_data->exec_write.exec_write_flag); + if (p_data->exec_write.exec_write_flag == ESP_GATT_PREP_WRITE_EXEC) { + print_prep_wr_buffer(); + } + free_prep_wr_buffer(); + break; case ESP_GATTS_RESPONSE_EVT: break; case ESP_GATTS_MTU_EVT: @@ -644,18 +668,20 @@ static void gatts_profile_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_ case ESP_GATTS_CONNECT_EVT: ESP_LOGI(GATTS_TABLE_TAG, "Connected, conn_id %u, remote "ESP_BD_ADDR_STR"", param->connect.conn_id, ESP_BD_ADDR_HEX(param->connect.remote_bda)); + free_prep_wr_buffer(); spp_conn_id = p_data->connect.conn_id; spp_gatts_if = gatts_if; is_connected = true; memcpy(&spp_remote_bda,&p_data->connect.remote_bda,sizeof(esp_bd_addr_t)); #ifdef SUPPORT_HEARTBEAT - uint16_t cmd = 0; - xQueueSend(cmd_heartbeat_queue,&cmd,10/portTICK_PERIOD_MS); + uint32_t cmd = 0; + xQueueSend(cmd_heartbeat_queue, &cmd, 10 / portTICK_PERIOD_MS); #endif break; case ESP_GATTS_DISCONNECT_EVT: ESP_LOGI(GATTS_TABLE_TAG, "Disconnected, remote "ESP_BD_ADDR_STR", reason 0x%02x", ESP_BD_ADDR_HEX(param->disconnect.remote_bda), param->disconnect.reason); + free_prep_wr_buffer(); spp_mtu_size = 23; is_connected = false; enable_data_ntf = false; @@ -670,6 +696,7 @@ static void gatts_profile_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_ case ESP_GATTS_CANCEL_OPEN_EVT: break; case ESP_GATTS_CLOSE_EVT: + free_prep_wr_buffer(); break; case ESP_GATTS_LISTEN_EVT: break; diff --git a/examples/bluetooth/bluedroid/ble/ble_spp_server/sdkconfig.defaults b/examples/bluetooth/bluedroid/ble/ble_spp_server/sdkconfig.defaults index ad218785e70..34348140850 100644 --- a/examples/bluetooth/bluedroid/ble/ble_spp_server/sdkconfig.defaults +++ b/examples/bluetooth/bluedroid/ble/ble_spp_server/sdkconfig.defaults @@ -6,3 +6,10 @@ CONFIG_BT_ENABLED=y CONFIG_BT_BLE_42_FEATURES_SUPPORTED=y # CONFIG_BT_LE_50_FEATURE_SUPPORT is not used on ESP32, ESP32-C3 and ESP32-S3. # CONFIG_BT_LE_50_FEATURE_SUPPORT is not set +CONFIG_PARTITION_TABLE_SINGLE_APP_LARGE=y + +# Disable unused Bluedroid host features for GATT server only example +# CONFIG_BT_GATTC_ENABLE is not set +# CONFIG_BT_BLE_SMP_ENABLE is not set +# CONFIG_BT_BLE_42_SCAN_EN is not set +# CONFIG_BT_BLE_42_DTM_TEST_EN is not set diff --git a/examples/bluetooth/bluedroid/ble/ble_throughput/throughput_client/main/example_ble_client_throughput.c b/examples/bluetooth/bluedroid/ble/ble_throughput/throughput_client/main/example_ble_client_throughput.c index 05f4ba02e7a..522eacba5a6 100644 --- a/examples/bluetooth/bluedroid/ble/ble_throughput/throughput_client/main/example_ble_client_throughput.c +++ b/examples/bluetooth/bluedroid/ble/ble_throughput/throughput_client/main/example_ble_client_throughput.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2021-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2021-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Unlicense OR CC0-1.0 */ @@ -126,6 +126,9 @@ static uint8_t check_sum(uint8_t *addr, uint16_t count) if (addr == NULL || count == 0) { return 0; } + if (count > (ESP_GATT_MAX_MTU_SIZE - 3U)) { + return 0; + } for(int i = 0; i < count; i++) { sum = sum + addr[i]; @@ -288,6 +291,7 @@ static void gattc_profile_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_ /* free descr_elem_result */ free(descr_elem_result); + descr_elem_result = NULL; } } else{ @@ -299,10 +303,23 @@ static void gattc_profile_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_ } case ESP_GATTC_NOTIFY_EVT: { #if (CONFIG_EXAMPLE_GATTS_NOTIFY_THROUGHPUT) - if (p_data->notify.is_notify && - (p_data->notify.value[p_data->notify.value_len - 1] == - check_sum(p_data->notify.value, p_data->notify.value_len - 1))){ - notify_len += p_data->notify.value_len; + if (p_data->notify.is_notify) { + uint16_t vlen = p_data->notify.value_len; + uint8_t *val = p_data->notify.value; + const uint16_t max_notify_len = (uint16_t)(ESP_GATT_MAX_MTU_SIZE - 3U); + if (val == NULL) { + ESP_LOGW(GATTC_TAG, "notify ignored: null value"); + } else if (vlen == 0) { + ESP_LOGW(GATTC_TAG, "notify ignored: zero length"); + } else if (vlen < 2) { + ESP_LOGW(GATTC_TAG, "notify ignored: length too short for payload+checksum"); + } else if (vlen > max_notify_len) { + ESP_LOGW(GATTC_TAG, "notify ignored: length exceeds bound"); + } else if (val[vlen - 1] == check_sum(val, (uint16_t)(vlen - 1U))) { + notify_len += vlen; + } else { + ESP_LOGE(GATTC_TAG, "notify checksum mismatch"); + } } else { ESP_LOGE(GATTC_TAG, "Indication received, value:"); } @@ -347,6 +364,11 @@ static void gattc_profile_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_ current_time = 0; notify_len = 0; #endif /* #if (CONFIG_EXAMPLE_GATTS_NOTIFY_THROUGHPUT) */ +#if (CONFIG_EXAMPLE_GATTC_WRITE_THROUGHPUT) + /* Unblock throughput_client_task if it is waiting on gattc_semaphore while congested. */ + can_send_write = true; + xSemaphoreGive(gattc_semaphore); +#endif /* #if (CONFIG_EXAMPLE_GATTC_WRITE_THROUGHPUT) */ ESP_LOGI(GATTC_TAG, "Disconnected, remote "ESP_BD_ADDR_STR", reason 0x%02x", ESP_BD_ADDR_HEX(p_data->disconnect.remote_bda), p_data->disconnect.reason); break; @@ -405,7 +427,7 @@ static void esp_gap_cb(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *par esp_ble_conn_params_t phy_1m_conn_params = {0}; #if(CONFIG_EXAMPLE_GATTC_WRITE_THROUGHPUT && CONFIG_EXAMPLE_GATTS_NOTIFY_THROUGHPUT) - phy_1m_conn_params.interval_max = 34; + phy_1m_conn_params.interval_min = 34; phy_1m_conn_params.interval_max = 34; #else phy_1m_conn_params.interval_max = 32; @@ -540,9 +562,14 @@ static void throughput_cal_task(void *param) uint32_t bit_rate = 0; if (start_time) { current_time = esp_timer_get_time(); - bit_rate = notify_len * SECOND_TO_USECOND / (current_time - start_time); - ESP_LOGI(GATTC_TAG, "Notify Bit rate = %" PRIu32 " Byte/s, = %" PRIu32 " bit/s, time = %ds", - bit_rate, bit_rate<<3, (int)((current_time - start_time) / SECOND_TO_USECOND)); + uint64_t elapsed_us = current_time - start_time; + if (elapsed_us > 0) { + bit_rate = (uint32_t)(notify_len * SECOND_TO_USECOND / elapsed_us); + ESP_LOGI(GATTC_TAG, "Notify Bit rate = %" PRIu32 " Byte/s, = %" PRIu32 " bit/s, time = %ds", + bit_rate, bit_rate << 3, (int)(elapsed_us / SECOND_TO_USECOND)); + } else { + ESP_LOGI(GATTC_TAG, "Notify Bit rate = 0 Byte/s, = 0 bit/s (elapsed 0 us)"); + } } else { ESP_LOGI(GATTC_TAG, "Notify Bit rate = 0 Byte/s, = 0 bit/s"); } @@ -601,6 +628,17 @@ void app_main(void) return; } +#if (CONFIG_EXAMPLE_GATTC_WRITE_THROUGHPUT) + /* Create the semaphore before registering the GATTC callback so that any + * xSemaphoreGive() invoked from the callback is guaranteed to see a valid handle. + */ + gattc_semaphore = xSemaphoreCreateBinary(); + if (!gattc_semaphore) { + ESP_LOGE(GATTC_TAG, "%s, init fail, the gattc semaphore create fail.", __func__); + return; + } +#endif /* #if (CONFIG_EXAMPLE_GATTC_WRITE_THROUGHPUT) */ + //register the callback function to the gattc module ret = esp_ble_gattc_register_callback(esp_gattc_cb); if(ret){ @@ -626,12 +664,4 @@ void app_main(void) #if (CONFIG_EXAMPLE_GATTS_NOTIFY_THROUGHPUT) xTaskCreatePinnedToCore(&throughput_cal_task, "throughput_cal_task", 4096, NULL, 9, NULL, BLUETOOTH_TASK_PINNED_TO_CORE); #endif - -#if (CONFIG_EXAMPLE_GATTC_WRITE_THROUGHPUT) - gattc_semaphore = xSemaphoreCreateBinary(); - if (!gattc_semaphore) { - ESP_LOGE(GATTC_TAG, "%s, init fail, the gattc semaphore create fail.", __func__); - return; - } -#endif /* #if (CONFIG_EXAMPLE_GATTC_WRITE_THROUGHPUT) */ } diff --git a/examples/bluetooth/bluedroid/ble/ble_throughput/throughput_client/sdkconfig.defaults b/examples/bluetooth/bluedroid/ble/ble_throughput/throughput_client/sdkconfig.defaults index 155f79ab29c..240cb139eb1 100644 --- a/examples/bluetooth/bluedroid/ble/ble_throughput/throughput_client/sdkconfig.defaults +++ b/examples/bluetooth/bluedroid/ble/ble_throughput/throughput_client/sdkconfig.defaults @@ -8,3 +8,10 @@ CONFIG_BT_BLE_42_FEATURES_SUPPORTED=y # CONFIG_BT_LE_50_FEATURE_SUPPORT is not set CONFIG_EXAMPLE_GATTS_NOTIFY_THROUGHPUT=y CONFIG_EXAMPLE_GATTC_WRITE_THROUGHPUT=n +CONFIG_PARTITION_TABLE_SINGLE_APP_LARGE=y + +# Disable unused Bluedroid host features for GATT client only example +# CONFIG_BT_GATTS_ENABLE is not set +# CONFIG_BT_BLE_SMP_ENABLE is not set +# CONFIG_BT_BLE_42_ADV_EN is not set +# CONFIG_BT_BLE_42_DTM_TEST_EN is not set diff --git a/examples/bluetooth/bluedroid/ble/ble_throughput/throughput_server/main/example_ble_server_throughput.c b/examples/bluetooth/bluedroid/ble/ble_throughput/throughput_server/main/example_ble_server_throughput.c index 0f945ad3721..954354b0a26 100644 --- a/examples/bluetooth/bluedroid/ble/ble_throughput/throughput_server/main/example_ble_server_throughput.c +++ b/examples/bluetooth/bluedroid/ble/ble_throughput/throughput_server/main/example_ble_server_throughput.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2021-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2021-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Unlicense OR CC0-1.0 */ @@ -59,6 +59,7 @@ static bool start = false; static uint64_t write_len = 0; static uint64_t start_time = 0; static uint64_t current_time = 0; +static portMUX_TYPE s_write_throughput_stats_mux = portMUX_INITIALIZER_UNLOCKED; #endif /* #if (CONFIG_EXAMPLE_GATTC_WRITE_THROUGHPUT) */ static bool is_connect = false; @@ -204,6 +205,18 @@ static prepare_type_env_t a_prepare_write_env; void example_write_event_env(esp_gatt_if_t gatts_if, prepare_type_env_t *prepare_write_env, esp_ble_gatts_cb_param_t *param); void example_exec_write_event_env(prepare_type_env_t *prepare_write_env, esp_ble_gatts_cb_param_t *param); +static void prepare_write_env_clear(prepare_type_env_t *env) +{ + if (env == NULL) { + return; + } + if (env->prepare_buf != NULL) { + free(env->prepare_buf); + env->prepare_buf = NULL; + } + env->prepare_len = 0; +} + static uint8_t check_sum(uint8_t *addr, uint16_t count) { uint32_t sum = 0; @@ -211,6 +224,9 @@ static uint8_t check_sum(uint8_t *addr, uint16_t count) if (addr == NULL || count == 0) { return 0; } + if (count > (ESP_GATT_MAX_MTU_SIZE - 3U)) { + return 0; + } for(int i = 0; i < count; i++) { sum = sum + addr[i]; @@ -334,14 +350,14 @@ void example_write_event_env(esp_gatt_if_t gatts_if, prepare_type_env_t *prepare } void example_exec_write_event_env(prepare_type_env_t *prepare_write_env, esp_ble_gatts_cb_param_t *param){ + if (prepare_write_env == NULL) { + ESP_LOGE(GATTS_TAG, "exec_write: prepare_write_env is NULL"); + return; + } if (param->exec_write.exec_write_flag != ESP_GATT_PREP_WRITE_EXEC){ ESP_LOGI(GATTS_TAG,"Prepare write cancel"); } - if (prepare_write_env->prepare_buf) { - free(prepare_write_env->prepare_buf); - prepare_write_env->prepare_buf = NULL; - } - prepare_write_env->prepare_len = 0; + prepare_write_env_clear(prepare_write_env); } static void gatts_profile_a_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param) { @@ -442,17 +458,32 @@ static void gatts_profile_a_event_handler(esp_gatts_cb_event_t event, esp_gatt_i example_write_event_env(gatts_if, &a_prepare_write_env, param); #if (CONFIG_EXAMPLE_GATTC_WRITE_THROUGHPUT) if (param->write.handle == gl_profile_tab[PROFILE_A_APP_ID].char_handle) { - // The last value byte is the checksum data, should used to check the data is received corrected or not. - if (param->write.value[param->write.len - 1] == - check_sum(param->write.value, param->write.len - 1)) { - write_len += param->write.len; + uint16_t wlen = param->write.len; + uint8_t *wval = param->write.value; + /* len==0 makes (wlen-1) wrap; cap len before indexing or passing to check_sum. */ + const uint16_t max_write_len = (uint16_t)(ESP_GATT_MAX_MTU_SIZE - 3U); + if (wval == NULL) { + ESP_LOGW(GATTS_TAG, "write ignored: null value"); + } else if (wlen == 0) { + ESP_LOGW(GATTS_TAG, "write ignored: zero length"); + } else if (wlen < 2) { + ESP_LOGW(GATTS_TAG, "write ignored: length too short for payload+checksum"); + } else if (wlen > max_write_len) { + ESP_LOGW(GATTS_TAG, "write ignored: length exceeds bound"); + } else if (wval[wlen - 1] == check_sum(wval, (uint16_t)(wlen - 1U))) { + portENTER_CRITICAL(&s_write_throughput_stats_mux); + write_len += wlen; + portEXIT_CRITICAL(&s_write_throughput_stats_mux); + } else { + ESP_LOGE(GATTS_TAG, "write checksum mismatch"); } + portENTER_CRITICAL(&s_write_throughput_stats_mux); if (start == false) { start_time = esp_timer_get_time(); start = true; - break; } + portEXIT_CRITICAL(&s_write_throughput_stats_mux); } #endif /* #if (CONFIG_EXAMPLE_GATTC_WRITE_THROUGHPUT) */ @@ -462,11 +493,13 @@ static void gatts_profile_a_event_handler(esp_gatts_cb_event_t event, esp_gatt_i ESP_LOGI(GATTS_TAG,"Execute write"); #if (CONFIG_EXAMPLE_GATTC_WRITE_THROUGHPUT) if (param->exec_write.exec_write_flag == ESP_GATT_PREP_WRITE_CANCEL) { + portENTER_CRITICAL(&s_write_throughput_stats_mux); if (write_len > a_prepare_write_env.prepare_len) { write_len -= a_prepare_write_env.prepare_len; } else { write_len = 0; } + portEXIT_CRITICAL(&s_write_throughput_stats_mux); } #endif /* #if (CONFIG_EXAMPLE_GATTC_WRITE_THROUGHPUT) */ esp_ble_gatts_send_response(gatts_if, param->write.conn_id, param->write.trans_id, ESP_GATT_OK, NULL); @@ -538,10 +571,34 @@ static void gatts_profile_a_event_handler(esp_gatts_cb_event_t event, esp_gatt_i ESP_LOGI(GATTS_TAG, "Connected, conn_id %d, remote "ESP_BD_ADDR_STR"", param->connect.conn_id, ESP_BD_ADDR_HEX(param->connect.remote_bda)); gl_profile_tab[PROFILE_A_APP_ID].conn_id = param->connect.conn_id; + prepare_write_env_clear(&a_prepare_write_env); +#if (CONFIG_EXAMPLE_GATTC_WRITE_THROUGHPUT) + portENTER_CRITICAL(&s_write_throughput_stats_mux); + write_len = 0; + start_time = 0; + start = false; + current_time = 0; + portEXIT_CRITICAL(&s_write_throughput_stats_mux); +#endif +#if (CONFIG_EXAMPLE_GATTS_NOTIFY_THROUGHPUT) + can_send_notify = false; +#endif break; } case ESP_GATTS_DISCONNECT_EVT: is_connect = false; + prepare_write_env_clear(&a_prepare_write_env); +#if (CONFIG_EXAMPLE_GATTC_WRITE_THROUGHPUT) + portENTER_CRITICAL(&s_write_throughput_stats_mux); + write_len = 0; + start_time = 0; + start = false; + current_time = 0; + portEXIT_CRITICAL(&s_write_throughput_stats_mux); +#endif +#if (CONFIG_EXAMPLE_GATTS_NOTIFY_THROUGHPUT) + can_send_notify = false; +#endif ESP_LOGI(GATTS_TAG, "Disconnected, remote "ESP_BD_ADDR_STR", reason 0x%02x", ESP_BD_ADDR_HEX(param->disconnect.remote_bda), param->disconnect.reason); esp_ble_gap_start_advertising(&adv_params); @@ -637,11 +694,22 @@ void throughput_cal_task(void *param) { uint32_t bit_rate = 0; vTaskDelay(2000 / portTICK_PERIOD_MS); - if (is_connect && start_time) { - current_time = esp_timer_get_time(); - bit_rate = write_len * SECOND_TO_USECOND / (current_time - start_time); - ESP_LOGI(GATTS_TAG, "GATTC write Bit rate = %" PRIu32 " Byte/s, = %" PRIu32 " bit/s, time %d", - bit_rate, bit_rate<<3, (int)((current_time - start_time) / SECOND_TO_USECOND)); + if (is_connect) { + uint64_t snap_write_len; + uint64_t snap_start_time; + portENTER_CRITICAL(&s_write_throughput_stats_mux); + snap_start_time = start_time; + snap_write_len = write_len; + portEXIT_CRITICAL(&s_write_throughput_stats_mux); + if (snap_start_time != 0) { + current_time = esp_timer_get_time(); + uint64_t elapsed_us = current_time - snap_start_time; + if (elapsed_us > 0) { + bit_rate = (uint32_t)(snap_write_len * SECOND_TO_USECOND / elapsed_us); + ESP_LOGI(GATTS_TAG, "GATTC write Bit rate = %" PRIu32 " Byte/s, = %" PRIu32 " bit/s, time %d", + bit_rate, bit_rate << 3, (int)(elapsed_us / SECOND_TO_USECOND)); + } + } } } diff --git a/examples/bluetooth/bluedroid/ble/ble_throughput/throughput_server/sdkconfig.defaults b/examples/bluetooth/bluedroid/ble/ble_throughput/throughput_server/sdkconfig.defaults index a83167746f3..197178412b1 100644 --- a/examples/bluetooth/bluedroid/ble/ble_throughput/throughput_server/sdkconfig.defaults +++ b/examples/bluetooth/bluedroid/ble/ble_throughput/throughput_server/sdkconfig.defaults @@ -8,3 +8,10 @@ CONFIG_BT_ENABLED=y CONFIG_BT_BLE_42_FEATURES_SUPPORTED=y # CONFIG_BT_LE_50_FEATURE_SUPPORT is not used on ESP32, ESP32-C3 and ESP32-S3. # CONFIG_BT_LE_50_FEATURE_SUPPORT is not set +CONFIG_PARTITION_TABLE_SINGLE_APP_LARGE=y + +# Disable unused Bluedroid host features for GATT server only example +# CONFIG_BT_GATTC_ENABLE is not set +# CONFIG_BT_BLE_SMP_ENABLE is not set +# CONFIG_BT_BLE_42_SCAN_EN is not set +# CONFIG_BT_BLE_42_DTM_TEST_EN is not set diff --git a/examples/bluetooth/bluedroid/ble/gatt_client/main/gattc_demo.c b/examples/bluetooth/bluedroid/ble/gatt_client/main/gattc_demo.c index 66c93787fe0..134d0e32623 100644 --- a/examples/bluetooth/bluedroid/ble/gatt_client/main/gattc_demo.c +++ b/examples/bluetooth/bluedroid/ble/gatt_client/main/gattc_demo.c @@ -204,6 +204,7 @@ static void gattc_profile_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_ } /* free char_elem_result */ free(char_elem_result); + char_elem_result = NULL; }else{ ESP_LOGE(GATTC_TAG, "no char found"); } @@ -262,6 +263,7 @@ static void gattc_profile_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_ /* free descr_elem_result */ free(descr_elem_result); + descr_elem_result = NULL; } } else{ @@ -488,7 +490,7 @@ void app_main(void) } esp_bluedroid_config_t cfg = BT_BLUEDROID_INIT_CONFIG_DEFAULT(); - ret = esp_bluedroid_init_with_cfg(&cfg);; + ret = esp_bluedroid_init_with_cfg(&cfg); if (ret) { ESP_LOGE(GATTC_TAG, "%s init bluetooth failed: %s", __func__, esp_err_to_name(ret)); return; diff --git a/examples/bluetooth/bluedroid/ble/gatt_client/sdkconfig.defaults b/examples/bluetooth/bluedroid/ble/gatt_client/sdkconfig.defaults index ad218785e70..046765b7c63 100644 --- a/examples/bluetooth/bluedroid/ble/gatt_client/sdkconfig.defaults +++ b/examples/bluetooth/bluedroid/ble/gatt_client/sdkconfig.defaults @@ -6,3 +6,10 @@ CONFIG_BT_ENABLED=y CONFIG_BT_BLE_42_FEATURES_SUPPORTED=y # CONFIG_BT_LE_50_FEATURE_SUPPORT is not used on ESP32, ESP32-C3 and ESP32-S3. # CONFIG_BT_LE_50_FEATURE_SUPPORT is not set +CONFIG_PARTITION_TABLE_SINGLE_APP_LARGE=y + +# Disable unused Bluedroid host features for GATT client only example +# CONFIG_BT_GATTS_ENABLE is not set +# CONFIG_BT_BLE_SMP_ENABLE is not set +# CONFIG_BT_BLE_42_ADV_EN is not set +# CONFIG_BT_BLE_42_DTM_TEST_EN is not set diff --git a/examples/bluetooth/bluedroid/ble/gatt_security_client/main/example_ble_sec_gattc_demo.c b/examples/bluetooth/bluedroid/ble/gatt_security_client/main/example_ble_sec_gattc_demo.c index 0c6954d7fc3..493afb75242 100644 --- a/examples/bluetooth/bluedroid/ble/gatt_security_client/main/example_ble_sec_gattc_demo.c +++ b/examples/bluetooth/bluedroid/ble/gatt_security_client/main/example_ble_sec_gattc_demo.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2021-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Unlicense OR CC0-1.0 */ @@ -15,6 +15,7 @@ ****************************************************************************/ #include +#include #include #include #include @@ -177,6 +178,7 @@ static void gattc_profile_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_ case ESP_GATTC_OPEN_EVT: if (param->open.status != ESP_GATT_OK){ ESP_LOGE(GATTC_TAG, "Open failed, status %x", p_data->open.status); + connect = false; break; } ESP_LOGI(GATTC_TAG, "Open successfully, MTU %d", p_data->open.mtu); diff --git a/examples/bluetooth/bluedroid/ble/gatt_security_client/sdkconfig.defaults b/examples/bluetooth/bluedroid/ble/gatt_security_client/sdkconfig.defaults index ad218785e70..aa14cd37002 100644 --- a/examples/bluetooth/bluedroid/ble/gatt_security_client/sdkconfig.defaults +++ b/examples/bluetooth/bluedroid/ble/gatt_security_client/sdkconfig.defaults @@ -6,3 +6,9 @@ CONFIG_BT_ENABLED=y CONFIG_BT_BLE_42_FEATURES_SUPPORTED=y # CONFIG_BT_LE_50_FEATURE_SUPPORT is not used on ESP32, ESP32-C3 and ESP32-S3. # CONFIG_BT_LE_50_FEATURE_SUPPORT is not set +CONFIG_PARTITION_TABLE_SINGLE_APP_LARGE=y + +# Disable unused Bluedroid host features for GATT security client example +# CONFIG_BT_GATTS_ENABLE is not set +# CONFIG_BT_BLE_42_ADV_EN is not set +# CONFIG_BT_BLE_42_DTM_TEST_EN is not set diff --git a/examples/bluetooth/bluedroid/ble/gatt_security_server/main/example_ble_sec_gatts_demo.c b/examples/bluetooth/bluedroid/ble/gatt_security_server/main/example_ble_sec_gatts_demo.c index ff7e262b27d..929ea6d8ea5 100644 --- a/examples/bluetooth/bluedroid/ble/gatt_security_server/main/example_ble_sec_gatts_demo.c +++ b/examples/bluetooth/bluedroid/ble/gatt_security_server/main/example_ble_sec_gatts_demo.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2021-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2021-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Unlicense OR CC0-1.0 */ @@ -178,9 +178,9 @@ static const esp_gatts_attr_db_t heart_rate_gatt_db[HRS_IDX_NB] = sizeof(uint8_t), sizeof(heart_ctrl_point), (uint8_t *)heart_ctrl_point}}, }; -static char *esp_key_type_to_str(esp_ble_key_type_t key_type) +static const char *esp_key_type_to_str(esp_ble_key_type_t key_type) { - char *key_str = NULL; + const char *key_str = NULL; switch(key_type) { case ESP_LE_KEY_NONE: key_str = "ESP_LE_KEY_NONE"; @@ -257,12 +257,16 @@ static char *esp_auth_req_to_str(esp_ble_auth_req_t auth_req) static void show_bonded_devices(void) { int dev_num = esp_ble_get_bond_device_num(); + if (dev_num < 0) { + ESP_LOGE(GATTS_TABLE_TAG, "Get bond device num failed (stack may be disabled), ret %d", dev_num); + return; + } if (dev_num == 0) { ESP_LOGI(GATTS_TABLE_TAG, "Bonded devices number zero\n"); return; } - esp_ble_bond_dev_t *dev_list = (esp_ble_bond_dev_t *)malloc(sizeof(esp_ble_bond_dev_t) * dev_num); + esp_ble_bond_dev_t *dev_list = (esp_ble_bond_dev_t *)malloc(sizeof(esp_ble_bond_dev_t) * (size_t)dev_num); if (!dev_list) { ESP_LOGI(GATTS_TABLE_TAG, "malloc failed, return\n"); return; @@ -280,12 +284,16 @@ static void show_bonded_devices(void) static void __attribute__((unused)) remove_all_bonded_devices(void) { int dev_num = esp_ble_get_bond_device_num(); + if (dev_num < 0) { + ESP_LOGE(GATTS_TABLE_TAG, "Get bond device num failed (stack may be disabled), ret %d", dev_num); + return; + } if (dev_num == 0) { ESP_LOGI(GATTS_TABLE_TAG, "Bonded devices number zero\n"); return; } - esp_ble_bond_dev_t *dev_list = (esp_ble_bond_dev_t *)malloc(sizeof(esp_ble_bond_dev_t) * dev_num); + esp_ble_bond_dev_t *dev_list = (esp_ble_bond_dev_t *)malloc(sizeof(esp_ble_bond_dev_t) * (size_t)dev_num); if (!dev_list) { ESP_LOGI(GATTS_TABLE_TAG, "malloc failed, return\n"); return; @@ -461,7 +469,7 @@ static void gatts_profile_event_handler(esp_gatts_cb_event_t event, case ESP_GATTS_CONGEST_EVT: break; case ESP_GATTS_CREAT_ATTR_TAB_EVT: { - if (param->create.status == ESP_GATT_OK){ + if (param->add_attr_tab.status == ESP_GATT_OK){ if(param->add_attr_tab.num_handle == HRS_IDX_NB) { ESP_LOGI(GATTS_TABLE_TAG, "Attribute table create successfully, num_handle %x", param->add_attr_tab.num_handle); memcpy(heart_rate_handle_table, param->add_attr_tab.handles, @@ -472,7 +480,7 @@ static void gatts_profile_event_handler(esp_gatts_cb_event_t event, param->add_attr_tab.num_handle, HRS_IDX_NB); } }else{ - ESP_LOGE(GATTS_TABLE_TAG, "Attribute table create failed, error code = %x", param->create.status); + ESP_LOGE(GATTS_TABLE_TAG, "Attribute table create failed, error code = %x", param->add_attr_tab.status); } break; } diff --git a/examples/bluetooth/bluedroid/ble/gatt_security_server/sdkconfig.defaults b/examples/bluetooth/bluedroid/ble/gatt_security_server/sdkconfig.defaults index ad218785e70..06d749dd051 100644 --- a/examples/bluetooth/bluedroid/ble/gatt_security_server/sdkconfig.defaults +++ b/examples/bluetooth/bluedroid/ble/gatt_security_server/sdkconfig.defaults @@ -6,3 +6,9 @@ CONFIG_BT_ENABLED=y CONFIG_BT_BLE_42_FEATURES_SUPPORTED=y # CONFIG_BT_LE_50_FEATURE_SUPPORT is not used on ESP32, ESP32-C3 and ESP32-S3. # CONFIG_BT_LE_50_FEATURE_SUPPORT is not set +CONFIG_PARTITION_TABLE_SINGLE_APP_LARGE=y + +# Disable unused Bluedroid host features for GATT security server example +# CONFIG_BT_GATTC_ENABLE is not set +# CONFIG_BT_BLE_42_SCAN_EN is not set +# CONFIG_BT_BLE_42_DTM_TEST_EN is not set 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 0d6acead7b1..2c2ca0412df 100644 --- a/examples/bluetooth/bluedroid/ble/gatt_server/main/gatts_demo.c +++ b/examples/bluetooth/bluedroid/ble/gatt_server/main/gatts_demo.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2021-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2021-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Unlicense OR CC0-1.0 */ @@ -198,6 +198,18 @@ typedef struct { static prepare_type_env_t a_prepare_write_env; static prepare_type_env_t b_prepare_write_env; +static void prepare_write_env_clear(prepare_type_env_t *env) +{ + if (env == NULL) { + return; + } + if (env->prepare_buf != NULL) { + free(env->prepare_buf); + env->prepare_buf = NULL; + } + env->prepare_len = 0; +} + void example_write_event_env(esp_gatt_if_t gatts_if, prepare_type_env_t *prepare_write_env, esp_ble_gatts_cb_param_t *param); void example_exec_write_event_env(prepare_type_env_t *prepare_write_env, esp_ble_gatts_cb_param_t *param); @@ -206,27 +218,43 @@ static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param switch (event) { #ifdef CONFIG_EXAMPLE_SET_RAW_ADV_DATA case ESP_GAP_BLE_ADV_DATA_RAW_SET_COMPLETE_EVT: + if (param->adv_data_raw_cmpl.status != ESP_BT_STATUS_SUCCESS) { + ESP_LOGE(GATTS_TAG, "Raw adv data set failed, status %d", param->adv_data_raw_cmpl.status); + break; + } adv_config_done &= (~adv_config_flag); - if (adv_config_done==0){ + if (adv_config_done == 0) { esp_ble_gap_start_advertising(&adv_params); } break; case ESP_GAP_BLE_SCAN_RSP_DATA_RAW_SET_COMPLETE_EVT: + if (param->scan_rsp_data_raw_cmpl.status != ESP_BT_STATUS_SUCCESS) { + ESP_LOGE(GATTS_TAG, "Raw scan rsp data set failed, status %d", param->scan_rsp_data_raw_cmpl.status); + break; + } adv_config_done &= (~scan_rsp_config_flag); - if (adv_config_done==0){ + if (adv_config_done == 0) { esp_ble_gap_start_advertising(&adv_params); } break; #else case ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT: + if (param->adv_data_cmpl.status != ESP_BT_STATUS_SUCCESS) { + ESP_LOGE(GATTS_TAG, "Adv data set failed, status %d", param->adv_data_cmpl.status); + break; + } adv_config_done &= (~adv_config_flag); - if (adv_config_done == 0){ + if (adv_config_done == 0) { esp_ble_gap_start_advertising(&adv_params); } break; case ESP_GAP_BLE_SCAN_RSP_DATA_SET_COMPLETE_EVT: + if (param->scan_rsp_data_cmpl.status != ESP_BT_STATUS_SUCCESS) { + ESP_LOGE(GATTS_TAG, "Scan rsp data set failed, status %d", param->scan_rsp_data_cmpl.status); + break; + } adv_config_done &= (~scan_rsp_config_flag); - if (adv_config_done == 0){ + if (adv_config_done == 0) { esp_ble_gap_start_advertising(&adv_params); } break; @@ -240,7 +268,7 @@ static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param ESP_LOGI(GATTS_TAG, "Advertising start successfully"); break; case ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT: - if (param->adv_start_cmpl.status != ESP_BT_STATUS_SUCCESS) { + if (param->adv_stop_cmpl.status != ESP_BT_STATUS_SUCCESS) { ESP_LOGE(GATTS_TAG, "Advertising stop failed, status %d", param->adv_stop_cmpl.status); break; } @@ -274,7 +302,7 @@ void example_write_event_env(esp_gatt_if_t gatts_if, prepare_type_env_t *prepare status = ESP_GATT_INVALID_ATTR_LEN; } if (status == ESP_GATT_OK && prepare_write_env->prepare_buf == NULL) { - prepare_write_env->prepare_buf = (uint8_t *)malloc(PREPARE_BUF_MAX_SIZE*sizeof(uint8_t)); + prepare_write_env->prepare_buf = (uint8_t *)calloc(PREPARE_BUF_MAX_SIZE, sizeof(uint8_t)); prepare_write_env->prepare_len = 0; if (prepare_write_env->prepare_buf == NULL) { ESP_LOGE(GATTS_TAG, "Gatt_server prep no mem"); @@ -305,7 +333,14 @@ void example_write_event_env(esp_gatt_if_t gatts_if, prepare_type_env_t *prepare memcpy(prepare_write_env->prepare_buf + param->write.offset, param->write.value, param->write.len); - prepare_write_env->prepare_len += param->write.len; + /* Extent is max(end of this fragment), not sum(len): same offset overwrites, not appends. */ + int frag_end = (int)param->write.offset + (int)param->write.len; + if (frag_end > prepare_write_env->prepare_len) { + prepare_write_env->prepare_len = frag_end; + } + if (prepare_write_env->prepare_len > PREPARE_BUF_MAX_SIZE) { + prepare_write_env->prepare_len = PREPARE_BUF_MAX_SIZE; + } }else{ esp_ble_gatts_send_response(gatts_if, param->write.conn_id, param->write.trans_id, status, NULL); @@ -315,15 +350,19 @@ void example_write_event_env(esp_gatt_if_t gatts_if, prepare_type_env_t *prepare void example_exec_write_event_env(prepare_type_env_t *prepare_write_env, esp_ble_gatts_cb_param_t *param){ if (param->exec_write.exec_write_flag == ESP_GATT_PREP_WRITE_EXEC){ - ESP_LOG_BUFFER_HEX(GATTS_TAG, prepare_write_env->prepare_buf, prepare_write_env->prepare_len); + int log_len = prepare_write_env->prepare_len; + if (log_len < 0) { + log_len = 0; + } else if (log_len > PREPARE_BUF_MAX_SIZE) { + log_len = PREPARE_BUF_MAX_SIZE; + } + if (prepare_write_env->prepare_buf != NULL && log_len > 0) { + ESP_LOG_BUFFER_HEX(GATTS_TAG, prepare_write_env->prepare_buf, (size_t)log_len); + } }else{ ESP_LOGI(GATTS_TAG,"Prepare write cancel"); } - if (prepare_write_env->prepare_buf) { - free(prepare_write_env->prepare_buf); - prepare_write_env->prepare_buf = NULL; - } - prepare_write_env->prepare_len = 0; + prepare_write_env_clear(prepare_write_env); } static void gatts_profile_a_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param) { @@ -546,12 +585,13 @@ static void gatts_profile_a_event_handler(esp_gatts_cb_event_t event, esp_gatt_i case ESP_GATTS_DISCONNECT_EVT: ESP_LOGI(GATTS_TAG, "Disconnected, remote "ESP_BD_ADDR_STR", reason 0x%02x", ESP_BD_ADDR_HEX(param->disconnect.remote_bda), param->disconnect.reason); + prepare_write_env_clear(&a_prepare_write_env); esp_ble_gap_start_advertising(&adv_params); local_mtu = 23; // Reset MTU for a single connection break; case ESP_GATTS_CONF_EVT: ESP_LOGI(GATTS_TAG, "Confirm receive, status %d, attr_handle %d", param->conf.status, param->conf.handle); - if (param->conf.status != ESP_GATT_OK){ + if (param->conf.status == ESP_GATT_OK && param->conf.value != NULL && param->conf.len > 0) { ESP_LOG_BUFFER_HEX(GATTS_TAG, param->conf.value, param->conf.len); } break; @@ -692,11 +732,13 @@ static void gatts_profile_b_event_handler(esp_gatts_cb_event_t event, esp_gatt_i break; case ESP_GATTS_CONF_EVT: ESP_LOGI(GATTS_TAG, "Confirm receive, status %d, attr_handle %d", param->conf.status, param->conf.handle); - if (param->conf.status != ESP_GATT_OK){ + if (param->conf.status == ESP_GATT_OK && param->conf.value != NULL && param->conf.len > 0) { ESP_LOG_BUFFER_HEX(GATTS_TAG, param->conf.value, param->conf.len); } - break; + break; case ESP_GATTS_DISCONNECT_EVT: + prepare_write_env_clear(&b_prepare_write_env); + break; case ESP_GATTS_OPEN_EVT: case ESP_GATTS_CANCEL_OPEN_EVT: case ESP_GATTS_CLOSE_EVT: diff --git a/examples/bluetooth/bluedroid/ble/gatt_server/sdkconfig.defaults b/examples/bluetooth/bluedroid/ble/gatt_server/sdkconfig.defaults index ad218785e70..34348140850 100644 --- a/examples/bluetooth/bluedroid/ble/gatt_server/sdkconfig.defaults +++ b/examples/bluetooth/bluedroid/ble/gatt_server/sdkconfig.defaults @@ -6,3 +6,10 @@ CONFIG_BT_ENABLED=y CONFIG_BT_BLE_42_FEATURES_SUPPORTED=y # CONFIG_BT_LE_50_FEATURE_SUPPORT is not used on ESP32, ESP32-C3 and ESP32-S3. # CONFIG_BT_LE_50_FEATURE_SUPPORT is not set +CONFIG_PARTITION_TABLE_SINGLE_APP_LARGE=y + +# Disable unused Bluedroid host features for GATT server only example +# CONFIG_BT_GATTC_ENABLE is not set +# CONFIG_BT_BLE_SMP_ENABLE is not set +# CONFIG_BT_BLE_42_SCAN_EN is not set +# CONFIG_BT_BLE_42_DTM_TEST_EN is not set diff --git a/examples/bluetooth/bluedroid/ble/gatt_server_service_table/main/gatts_table_creat_demo.c b/examples/bluetooth/bluedroid/ble/gatt_server_service_table/main/gatts_table_creat_demo.c index 7dfc803ae37..2e486d41bf8 100644 --- a/examples/bluetooth/bluedroid/ble/gatt_server_service_table/main/gatts_table_creat_demo.c +++ b/examples/bluetooth/bluedroid/ble/gatt_server_service_table/main/gatts_table_creat_demo.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2021-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2021-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Unlicense OR CC0-1.0 */ @@ -14,6 +14,7 @@ * ****************************************************************************/ +#include #include "freertos/FreeRTOS.h" #include "freertos/task.h" @@ -57,8 +58,21 @@ typedef struct { int prepare_len; } prepare_type_env_t; +/* Single-connection demo: one prepare-write buffer for the lone GATT link. */ static prepare_type_env_t prepare_write_env; +static void prepare_write_env_clear(prepare_type_env_t *env) +{ + if (env == NULL) { + return; + } + if (env->prepare_buf != NULL) { + free(env->prepare_buf); + env->prepare_buf = NULL; + } + env->prepare_len = 0; +} + #define CONFIG_SET_RAW_ADV_DATA #ifdef CONFIG_SET_RAW_ADV_DATA static uint8_t raw_adv_data[] = { @@ -288,7 +302,7 @@ void example_prepare_write_event_env(esp_gatt_if_t gatts_if, prepare_type_env_t status = ESP_GATT_INVALID_ATTR_LEN; } if (status == ESP_GATT_OK && prepare_write_env->prepare_buf == NULL) { - prepare_write_env->prepare_buf = (uint8_t *)malloc(PREPARE_BUF_MAX_SIZE * sizeof(uint8_t)); + prepare_write_env->prepare_buf = (uint8_t *)calloc(PREPARE_BUF_MAX_SIZE, sizeof(uint8_t)); prepare_write_env->prepare_len = 0; if (prepare_write_env->prepare_buf == NULL) { ESP_LOGE(GATTS_TABLE_TAG, "%s, Gatt_server prep no mem", __func__); @@ -321,21 +335,32 @@ void example_prepare_write_event_env(esp_gatt_if_t gatts_if, prepare_type_env_t memcpy(prepare_write_env->prepare_buf + param->write.offset, param->write.value, param->write.len); - prepare_write_env->prepare_len += param->write.len; + /* Extent is max(end of fragment), not sum(len); cap to allocated size. */ + int frag_end = (int)param->write.offset + (int)param->write.len; + if (frag_end > prepare_write_env->prepare_len) { + prepare_write_env->prepare_len = frag_end; + } + if (prepare_write_env->prepare_len > PREPARE_BUF_MAX_SIZE) { + prepare_write_env->prepare_len = PREPARE_BUF_MAX_SIZE; + } } void example_exec_write_event_env(prepare_type_env_t *prepare_write_env, esp_ble_gatts_cb_param_t *param){ if (param->exec_write.exec_write_flag == ESP_GATT_PREP_WRITE_EXEC && prepare_write_env->prepare_buf){ - ESP_LOG_BUFFER_HEX(GATTS_TABLE_TAG, prepare_write_env->prepare_buf, prepare_write_env->prepare_len); + int log_len = prepare_write_env->prepare_len; + if (log_len < 0) { + log_len = 0; + } else if (log_len > PREPARE_BUF_MAX_SIZE) { + log_len = PREPARE_BUF_MAX_SIZE; + } + if (log_len > 0) { + ESP_LOG_BUFFER_HEX(GATTS_TABLE_TAG, prepare_write_env->prepare_buf, (size_t)log_len); + } }else{ ESP_LOGI(GATTS_TABLE_TAG,"ESP_GATT_PREP_WRITE_CANCEL"); } - if (prepare_write_env->prepare_buf) { - free(prepare_write_env->prepare_buf); - prepare_write_env->prepare_buf = NULL; - } - prepare_write_env->prepare_len = 0; + prepare_write_env_clear(prepare_write_env); } static void gatts_profile_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param) @@ -459,6 +484,7 @@ static void gatts_profile_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_ break; case ESP_GATTS_DISCONNECT_EVT: ESP_LOGI(GATTS_TABLE_TAG, "ESP_GATTS_DISCONNECT_EVT, reason = 0x%x", param->disconnect.reason); + prepare_write_env_clear(&prepare_write_env); esp_ble_gap_start_advertising(&adv_params); break; case ESP_GATTS_CREAT_ATTR_TAB_EVT:{ diff --git a/examples/bluetooth/bluedroid/ble/gatt_server_service_table/sdkconfig.defaults b/examples/bluetooth/bluedroid/ble/gatt_server_service_table/sdkconfig.defaults index ad218785e70..34348140850 100644 --- a/examples/bluetooth/bluedroid/ble/gatt_server_service_table/sdkconfig.defaults +++ b/examples/bluetooth/bluedroid/ble/gatt_server_service_table/sdkconfig.defaults @@ -6,3 +6,10 @@ CONFIG_BT_ENABLED=y CONFIG_BT_BLE_42_FEATURES_SUPPORTED=y # CONFIG_BT_LE_50_FEATURE_SUPPORT is not used on ESP32, ESP32-C3 and ESP32-S3. # CONFIG_BT_LE_50_FEATURE_SUPPORT is not set +CONFIG_PARTITION_TABLE_SINGLE_APP_LARGE=y + +# Disable unused Bluedroid host features for GATT server only example +# CONFIG_BT_GATTC_ENABLE is not set +# CONFIG_BT_BLE_SMP_ENABLE is not set +# CONFIG_BT_BLE_42_SCAN_EN is not set +# CONFIG_BT_BLE_42_DTM_TEST_EN is not set diff --git a/examples/bluetooth/bluedroid/ble/gattc_multi_connect/main/gattc_multi_connect.c b/examples/bluetooth/bluedroid/ble/gattc_multi_connect/main/gattc_multi_connect.c index f747e25e944..d1f997e3bf7 100644 --- a/examples/bluetooth/bluedroid/ble/gattc_multi_connect/main/gattc_multi_connect.c +++ b/examples/bluetooth/bluedroid/ble/gattc_multi_connect/main/gattc_multi_connect.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2021-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Unlicense OR CC0-1.0 */ @@ -125,10 +125,11 @@ static struct gattc_profile_inst gl_profile_tab[PROFILE_NUM] = { }; +/* Restarts scanning; does not clear Isconnecting — that flag is tied to enh_open/OPEN_EVT only + * so unrelated events (e.g. another profile disconnect) cannot drop the guard mid–connect attempt. */ static void start_scan(void) { stop_scan_done = false; - Isconnecting = false; uint32_t duration = 30; esp_ble_gap_start_scanning(duration); } @@ -154,7 +155,8 @@ static void gattc_profile_a_event_handler(esp_gattc_cb_event_t event, esp_gatt_i //open failed, ignore the first device, connect the second device ESP_LOGE(GATTC_TAG, "connect device failed, status %d", p_data->open.status); conn_device_a = false; - //start_scan(); + Isconnecting = false; + start_scan(); break; } memcpy(gl_profile_tab[PROFILE_A_APP_ID].remote_bda, p_data->open.remote_bda, 6); @@ -166,6 +168,7 @@ static void gattc_profile_a_event_handler(esp_gattc_cb_event_t event, esp_gatt_i if (mtu_ret){ ESP_LOGE(GATTC_TAG, "config MTU error, error code = %x", mtu_ret); } + Isconnecting = false; break; case ESP_GATTC_CFG_MTU_EVT: if (param->cfg_mtu.status != ESP_GATT_OK){ @@ -267,6 +270,9 @@ static void gattc_profile_a_event_handler(esp_gattc_cb_event_t event, esp_gatt_i &count); if (ret_status != ESP_GATT_OK){ ESP_LOGE(GATTC_TAG, "esp_ble_gattc_get_descr_by_char_handle error"); + free(descr_elem_result_a); + descr_elem_result_a = NULL; + break; } /* Every char has only one descriptor in our 'ESP_GATTS_DEMO' demo, so we used first 'descr_elem_result' */ @@ -286,6 +292,7 @@ static void gattc_profile_a_event_handler(esp_gattc_cb_event_t event, esp_gatt_i /* free descr_elem_result */ free(descr_elem_result_a); + descr_elem_result_a = NULL; } } else{ @@ -327,8 +334,8 @@ static void gattc_profile_a_event_handler(esp_gattc_cb_event_t event, esp_gatt_i case ESP_GATTC_SRVC_CHG_EVT: { esp_bd_addr_t bda; memcpy(bda, p_data->srvc_chg.remote_bda, sizeof(esp_bd_addr_t)); - ESP_LOGI(GATTC_TAG, "ESP_GATTC_SRVC_CHG_EVT, bd_addr:%08x%04x",(bda[0] << 24) + (bda[1] << 16) + (bda[2] << 8) + bda[3], - (bda[4] << 8) + bda[5]); + ESP_LOGI(GATTC_TAG, "ESP_GATTC_SRVC_CHG_EVT, bd_addr:%02x:%02x:%02x:%02x:%02x:%02x", + bda[0], bda[1], bda[2], bda[3], bda[4], bda[5]); break; } case ESP_GATTC_DISCONNECT_EVT: @@ -360,7 +367,8 @@ static void gattc_profile_b_event_handler(esp_gattc_cb_event_t event, esp_gatt_i //open failed, ignore the second device, connect the third device ESP_LOGE(GATTC_TAG, "connect device failed, status %d", p_data->open.status); conn_device_b = false; - //start_scan(); + Isconnecting = false; + start_scan(); break; } memcpy(gl_profile_tab[PROFILE_B_APP_ID].remote_bda, p_data->open.remote_bda, 6); @@ -372,6 +380,7 @@ static void gattc_profile_b_event_handler(esp_gattc_cb_event_t event, esp_gatt_i if (mtu_ret){ ESP_LOGE(GATTC_TAG, "config MTU error, error code = %x", mtu_ret); } + Isconnecting = false; break; case ESP_GATTC_CFG_MTU_EVT: if (param->cfg_mtu.status != ESP_GATT_OK){ @@ -538,8 +547,8 @@ static void gattc_profile_b_event_handler(esp_gattc_cb_event_t event, esp_gatt_i case ESP_GATTC_SRVC_CHG_EVT: { esp_bd_addr_t bda; memcpy(bda, p_data->srvc_chg.remote_bda, sizeof(esp_bd_addr_t)); - ESP_LOGI(GATTC_TAG, "ESP_GATTC_SRVC_CHG_EVT, bd_addr:%08x%04x",(bda[0] << 24) + (bda[1] << 16) + (bda[2] << 8) + bda[3], - (bda[4] << 8) + bda[5]); + ESP_LOGI(GATTC_TAG, "ESP_GATTC_SRVC_CHG_EVT, bd_addr:%02x:%02x:%02x:%02x:%02x:%02x", + bda[0], bda[1], bda[2], bda[3], bda[4], bda[5]); break; } case ESP_GATTC_DISCONNECT_EVT: @@ -568,7 +577,8 @@ static void gattc_profile_c_event_handler(esp_gattc_cb_event_t event, esp_gatt_i if (p_data->open.status != ESP_GATT_OK){ ESP_LOGE(GATTC_TAG, "connect device failed, status %d", p_data->open.status); conn_device_c = false; - //start_scan(); + Isconnecting = false; + start_scan(); break; } memcpy(gl_profile_tab[PROFILE_C_APP_ID].remote_bda, p_data->open.remote_bda, 6); @@ -580,6 +590,7 @@ static void gattc_profile_c_event_handler(esp_gattc_cb_event_t event, esp_gatt_i if (mtu_ret){ ESP_LOGE(GATTC_TAG, "config MTU error, error code = %x", mtu_ret); } + Isconnecting = false; break; case ESP_GATTC_CFG_MTU_EVT: if (param->cfg_mtu.status != ESP_GATT_OK){ @@ -746,8 +757,8 @@ static void gattc_profile_c_event_handler(esp_gattc_cb_event_t event, esp_gatt_i case ESP_GATTC_SRVC_CHG_EVT: { esp_bd_addr_t bda; memcpy(bda, p_data->srvc_chg.remote_bda, sizeof(esp_bd_addr_t)); - ESP_LOGI(GATTC_TAG, "ESP_GATTC_SRVC_CHG_EVT, bd_addr:%08x%04x",(bda[0] << 24) + (bda[1] << 16) + (bda[2] << 8) + bda[3], - (bda[4] << 8) + bda[5]); + ESP_LOGI(GATTC_TAG, "ESP_GATTC_SRVC_CHG_EVT, bd_addr:%02x:%02x:%02x:%02x:%02x:%02x", + bda[0], bda[1], bda[2], bda[3], bda[4], bda[5]); break; } case ESP_GATTC_DISCONNECT_EVT: diff --git a/examples/bluetooth/bluedroid/ble/gattc_multi_connect/sdkconfig.defaults b/examples/bluetooth/bluedroid/ble/gattc_multi_connect/sdkconfig.defaults index ad218785e70..046765b7c63 100644 --- a/examples/bluetooth/bluedroid/ble/gattc_multi_connect/sdkconfig.defaults +++ b/examples/bluetooth/bluedroid/ble/gattc_multi_connect/sdkconfig.defaults @@ -6,3 +6,10 @@ CONFIG_BT_ENABLED=y CONFIG_BT_BLE_42_FEATURES_SUPPORTED=y # CONFIG_BT_LE_50_FEATURE_SUPPORT is not used on ESP32, ESP32-C3 and ESP32-S3. # CONFIG_BT_LE_50_FEATURE_SUPPORT is not set +CONFIG_PARTITION_TABLE_SINGLE_APP_LARGE=y + +# Disable unused Bluedroid host features for GATT client only example +# CONFIG_BT_GATTS_ENABLE is not set +# CONFIG_BT_BLE_SMP_ENABLE is not set +# CONFIG_BT_BLE_42_ADV_EN is not set +# CONFIG_BT_BLE_42_DTM_TEST_EN is not set diff --git a/examples/bluetooth/bluedroid/ble_50/ble50_security_client/sdkconfig.defaults b/examples/bluetooth/bluedroid/ble_50/ble50_security_client/sdkconfig.defaults index d1004c7abde..b2530c1ec90 100644 --- a/examples/bluetooth/bluedroid/ble_50/ble50_security_client/sdkconfig.defaults +++ b/examples/bluetooth/bluedroid/ble_50/ble50_security_client/sdkconfig.defaults @@ -2,3 +2,7 @@ # Espressif IoT Development Framework (ESP-IDF) Project Minimal Configuration # CONFIG_BT_ENABLED=y + +# Disable unused Bluedroid host features (GATT security client, extended scan only) +# CONFIG_BT_GATTS_ENABLE is not set +# CONFIG_BT_BLE_50_DTM_TEST_EN is not set diff --git a/examples/bluetooth/bluedroid/ble_50/ble50_security_server/main/ble50_sec_gatts_demo.c b/examples/bluetooth/bluedroid/ble_50/ble50_security_server/main/ble50_sec_gatts_demo.c index fdadcbd928d..37eddbfc0e4 100644 --- a/examples/bluetooth/bluedroid/ble_50/ble50_security_server/main/ble50_sec_gatts_demo.c +++ b/examples/bluetooth/bluedroid/ble_50/ble50_security_server/main/ble50_sec_gatts_demo.c @@ -287,7 +287,7 @@ static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param esp_ble_gap_ext_adv_start(NUM_EXT_ADV_SET, &ext_adv[0]); break; case ESP_GAP_BLE_EXT_ADV_START_COMPLETE_EVT: - ESP_LOGI(GATTS_TABLE_TAG, "Extended advertising start, status %d", param->ext_adv_data_set.status); + ESP_LOGI(GATTS_TABLE_TAG, "Extended advertising start, status %d", param->ext_adv_start.status); break; case ESP_GAP_BLE_ADV_TERMINATED_EVT: ESP_LOGI(GATTS_TABLE_TAG, "Extended advertising terminated, status %d", param->adv_terminate.status); diff --git a/examples/bluetooth/bluedroid/ble_50/ble50_security_server/sdkconfig.defaults b/examples/bluetooth/bluedroid/ble_50/ble50_security_server/sdkconfig.defaults index d1004c7abde..e0161417835 100644 --- a/examples/bluetooth/bluedroid/ble_50/ble50_security_server/sdkconfig.defaults +++ b/examples/bluetooth/bluedroid/ble_50/ble50_security_server/sdkconfig.defaults @@ -2,3 +2,7 @@ # Espressif IoT Development Framework (ESP-IDF) Project Minimal Configuration # CONFIG_BT_ENABLED=y + +# Disable unused Bluedroid host features (GATT security server, extended adv only) +# CONFIG_BT_GATTC_ENABLE is not set +# CONFIG_BT_BLE_50_DTM_TEST_EN is not set diff --git a/examples/bluetooth/bluedroid/ble_50/ble50_throughput/throughput_client/main/example_ble_client_throughput.c b/examples/bluetooth/bluedroid/ble_50/ble50_throughput/throughput_client/main/example_ble_client_throughput.c index d316ba59a0a..aebddb73f21 100644 --- a/examples/bluetooth/bluedroid/ble_50/ble50_throughput/throughput_client/main/example_ble_client_throughput.c +++ b/examples/bluetooth/bluedroid/ble_50/ble50_throughput/throughput_client/main/example_ble_client_throughput.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2021-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2021-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Unlicense OR CC0-1.0 */ @@ -163,6 +163,9 @@ static uint8_t check_sum(uint8_t *addr, uint16_t count) if (addr == NULL || count == 0) { return 0; } + if (count > (ESP_GATT_MAX_MTU_SIZE - 3U)) { + return 0; + } for(int i = 0; i < count; i++) { sum = sum + addr[i]; @@ -175,6 +178,15 @@ static uint8_t check_sum(uint8_t *addr, uint16_t count) return (uint8_t)~sum; } +static void throughput_client_resume_ext_scan(void) +{ + connect = false; + esp_err_t err = esp_ble_gap_start_ext_scan(EXT_SCAN_DURATION, EXT_SCAN_PERIOD); + if (err != ESP_OK) { + ESP_LOGE(GATTC_TAG, "start_ext_scan failed: %s", esp_err_to_name(err)); + } +} + static void gattc_profile_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) { esp_ble_gattc_cb_param_t *p_data = (esp_ble_gattc_cb_param_t *)param; @@ -202,6 +214,7 @@ static void gattc_profile_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_ case ESP_GATTC_OPEN_EVT: if (param->open.status != ESP_GATT_OK){ ESP_LOGE(GATTC_TAG, "Open failed, status %d", p_data->open.status); + throughput_client_resume_ext_scan(); break; } ESP_LOGI(GATTC_TAG, "Open successfully, MTU %u", param->open.mtu); @@ -293,12 +306,13 @@ static void gattc_profile_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_ &count); if (ret_status != ESP_GATT_OK){ ESP_LOGE(GATTC_TAG, "esp_ble_gattc_get_attr_count error"); - } - if (count > 0){ + } else if (count == 0) { + ESP_LOGE(GATTC_TAG, "decsr not found"); + } else { descr_elem_result = malloc(sizeof(esp_gattc_descr_elem_t) * count); - if (!descr_elem_result){ + if (descr_elem_result == NULL){ ESP_LOGE(GATTC_TAG, "malloc error, gattc no mem"); - }else{ + } else { ret_status = esp_ble_gattc_get_descr_by_char_handle( gattc_if, gl_profile_tab[PROFILE_A_APP_ID].conn_id, p_data->reg_for_notify.handle, @@ -307,10 +321,10 @@ static void gattc_profile_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_ &count); if (ret_status != ESP_GATT_OK){ ESP_LOGE(GATTC_TAG, "esp_ble_gattc_get_descr_by_char_handle error"); - } - - /* Every char has only one descriptor in our 'throughput_server' demo, so we use first 'descr_elem_result' */ - if (count > 0 && descr_elem_result[0].uuid.len == ESP_UUID_LEN_16 && descr_elem_result[0].uuid.uuid.uuid16 == ESP_GATT_UUID_CHAR_CLIENT_CONFIG){ + } else if (count > 0 && + descr_elem_result[0].uuid.len == ESP_UUID_LEN_16 && + descr_elem_result[0].uuid.uuid.uuid16 == ESP_GATT_UUID_CHAR_CLIENT_CONFIG) { + /* Every char has only one descriptor in our 'throughput_server' demo, so we use first 'descr_elem_result' */ ret_status = esp_ble_gattc_write_char_descr( gattc_if, gl_profile_tab[PROFILE_A_APP_ID].conn_id, descr_elem_result[0].handle, @@ -318,29 +332,37 @@ static void gattc_profile_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_ (uint8_t *)¬ify_en, ESP_GATT_WRITE_TYPE_RSP, ESP_GATT_AUTH_REQ_NONE); + if (ret_status != ESP_GATT_OK){ + ESP_LOGE(GATTC_TAG, "esp_ble_gattc_write_char_descr error"); + } } - - if (ret_status != ESP_GATT_OK){ - ESP_LOGE(GATTC_TAG, "esp_ble_gattc_write_char_descr error"); - } - - /* free descr_elem_result */ free(descr_elem_result); + descr_elem_result = NULL; } } - else{ - ESP_LOGE(GATTC_TAG, "decsr not found"); - } - } break; } case ESP_GATTC_NOTIFY_EVT: { #if (CONFIG_GATTS_NOTIFY_THROUGHPUT) - if (p_data->notify.is_notify && - (p_data->notify.value[p_data->notify.value_len - 1] == - check_sum(p_data->notify.value, p_data->notify.value_len - 1))){ - notify_len += p_data->notify.value_len; + if (p_data->notify.is_notify) { + uint16_t vlen = p_data->notify.value_len; + uint8_t *val = p_data->notify.value; + /* value_len == 0 makes (vlen - 1) wrap; never index or pass to check_sum before validating. */ + const uint16_t max_notify_len = (uint16_t)(ESP_GATT_MAX_MTU_SIZE - 3U); + if (val == NULL) { + ESP_LOGW(GATTC_TAG, "notify ignored: null value"); + } else if (vlen == 0) { + ESP_LOGW(GATTC_TAG, "notify ignored: zero length"); + } else if (vlen < 2) { + ESP_LOGW(GATTC_TAG, "notify ignored: length too short for payload+checksum"); + } else if (vlen > max_notify_len) { + ESP_LOGW(GATTC_TAG, "notify ignored: length exceeds bound"); + } else if (val[vlen - 1] == check_sum(val, (uint16_t)(vlen - 1U))) { + notify_len += vlen; + } else { + ESP_LOGE(GATTC_TAG, "notify checksum mismatch"); + } } else { ESP_LOGE(GATTC_TAG, "Indication received, value:"); } @@ -385,8 +407,14 @@ static void gattc_profile_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_ current_time = 0; notify_len = 0; #endif /* #if (CONFIG_GATTS_NOTIFY_THROUGHPUT) */ +#if (CONFIG_GATTC_WRITE_THROUGHPUT) + /* Unblock throughput_client_task if it is waiting on gattc_semaphore while congested. */ + can_send_write = true; + xSemaphoreGive(gattc_semaphore); +#endif /* #if (CONFIG_GATTC_WRITE_THROUGHPUT) */ ESP_LOGI(GATTC_TAG, "Disconnected, remote "ESP_BD_ADDR_STR", reason 0x%02x", ESP_BD_ADDR_HEX(p_data->disconnect.remote_bda), p_data->disconnect.reason); + throughput_client_resume_ext_scan(); break; case ESP_GATTC_CONGEST_EVT: #if (CONFIG_GATTC_WRITE_THROUGHPUT) @@ -517,7 +545,6 @@ static void esp_gattc_cb(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp #if (CONFIG_GATTC_WRITE_THROUGHPUT) static void throughput_client_task(void *param) { - vTaskDelay(2000 / portTICK_PERIOD_MS); uint8_t sum = check_sum(write_data, sizeof(write_data) - 1); write_data[GATTC_WRITE_LEN - 1] = sum; @@ -619,6 +646,17 @@ void app_main(void) return; } +#if (CONFIG_GATTC_WRITE_THROUGHPUT) + /* Create the semaphore before registering the GATTC callback so that any + * xSemaphoreGive() invoked from the callback is guaranteed to see a valid handle. + */ + gattc_semaphore = xSemaphoreCreateBinary(); + if (gattc_semaphore == NULL) { + ESP_LOGE(GATTC_TAG, "%s: gattc semaphore create failed", __func__); + return; + } +#endif /* #if (CONFIG_GATTC_WRITE_THROUGHPUT) */ + //register the callback function to the gattc module ret = esp_ble_gattc_register_callback(esp_gattc_cb); if(ret){ @@ -636,20 +674,11 @@ void app_main(void) ESP_LOGE(GATTC_TAG, "set local MTU failed, error code = %x", local_mtu_ret); } #if (CONFIG_GATTC_WRITE_THROUGHPUT) - // The task is only created on the CPU core that Bluetooth is working on, - // preventing the sending task from using the un-updated Bluetooth state on another CPU. + /* Create the task only after the semaphore exists; never rely on priority or delays. */ xTaskCreatePinnedToCore(&throughput_client_task, "throughput_client_task", 4096, NULL, 10, NULL, BLUETOOTH_TASK_PINNED_TO_CORE); #endif #if (CONFIG_GATTS_NOTIFY_THROUGHPUT) xTaskCreatePinnedToCore(&throughput_cal_task, "throughput_cal_task", 4096, NULL, 9, NULL, BLUETOOTH_TASK_PINNED_TO_CORE); #endif - -#if (CONFIG_GATTC_WRITE_THROUGHPUT) - gattc_semaphore = xSemaphoreCreateBinary(); - if (!gattc_semaphore) { - ESP_LOGE(GATTC_TAG, "%s, init fail, the gattc semaphore create fail.", __func__); - return; - } -#endif /* #if (CONFIG_GATTC_WRITE_THROUGHPUT) */ } diff --git a/examples/bluetooth/bluedroid/ble_50/ble50_throughput/throughput_client/sdkconfig.defaults b/examples/bluetooth/bluedroid/ble_50/ble50_throughput/throughput_client/sdkconfig.defaults index 5976aa8c259..8b023e864c9 100644 --- a/examples/bluetooth/bluedroid/ble_50/ble50_throughput/throughput_client/sdkconfig.defaults +++ b/examples/bluetooth/bluedroid/ble_50/ble50_throughput/throughput_client/sdkconfig.defaults @@ -8,3 +8,8 @@ CONFIG_BT_BLE_42_FEATURES_SUPPORTED=n # CONFIG_BT_LE_50_FEATURE_SUPPORT=n CONFIG_GATTS_NOTIFY_THROUGHPUT=y CONFIG_GATTC_WRITE_THROUGHPUT=n + +# Disable unused Bluedroid host features (GATT client only) +# CONFIG_BT_GATTS_ENABLE is not set +# CONFIG_BT_BLE_SMP_ENABLE is not set +# CONFIG_BT_BLE_50_DTM_TEST_EN is not set diff --git a/examples/bluetooth/bluedroid/ble_50/ble50_throughput/throughput_server/main/example_ble_server_throughput.c b/examples/bluetooth/bluedroid/ble_50/ble50_throughput/throughput_server/main/example_ble_server_throughput.c index 9fe5da324ee..68475946340 100644 --- a/examples/bluetooth/bluedroid/ble_50/ble50_throughput/throughput_server/main/example_ble_server_throughput.c +++ b/examples/bluetooth/bluedroid/ble_50/ble50_throughput/throughput_server/main/example_ble_server_throughput.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2021-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2021-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Unlicense OR CC0-1.0 */ @@ -164,6 +164,18 @@ typedef struct { static prepare_type_env_t a_prepare_write_env; +static void prepare_write_env_clear(prepare_type_env_t *env) +{ + if (env == NULL) { + return; + } + if (env->prepare_buf != NULL) { + free(env->prepare_buf); + env->prepare_buf = NULL; + } + env->prepare_len = 0; +} + extern void esp_ble_switch_phy_coded(bool phy_500k); void example_write_event_env(esp_gatt_if_t gatts_if, prepare_type_env_t *prepare_write_env, esp_ble_gatts_cb_param_t *param); void example_exec_write_event_env(prepare_type_env_t *prepare_write_env, esp_ble_gatts_cb_param_t *param); @@ -175,6 +187,9 @@ static uint8_t check_sum(uint8_t *addr, uint16_t count) if (addr == NULL || count == 0) { return 0; } + if (count > (ESP_GATT_MAX_MTU_SIZE - 3U)) { + return 0; + } for(int i = 0; i < count; i++) { sum = sum + addr[i]; @@ -228,6 +243,8 @@ void example_write_event_env(esp_gatt_if_t gatts_if, prepare_type_env_t *prepare status = ESP_GATT_INVALID_OFFSET; } else if ((param->write.offset + param->write.len) > PREPARE_BUF_MAX_SIZE) { status = ESP_GATT_INVALID_ATTR_LEN; + } else if (param->write.len > ESP_GATT_MAX_ATTR_LEN) { + status = ESP_GATT_INVALID_ATTR_LEN; } if (status == ESP_GATT_OK && prepare_write_env->prepare_buf == NULL) { @@ -239,13 +256,19 @@ 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)); + 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; gatt_rsp->attr_value.offset = param->write.offset; gatt_rsp->attr_value.auth_req = ESP_GATT_AUTH_REQ_NONE; - memcpy(gatt_rsp->attr_value.value, param->write.value, param->write.len); + if (status == ESP_GATT_OK) { + if (param->write.value == NULL) { + status = ESP_GATT_INVALID_ATTR_LEN; + } else { + gatt_rsp->attr_value.len = param->write.len; + memcpy(gatt_rsp->attr_value.value, param->write.value, param->write.len); + } + } esp_err_t response_err = esp_ble_gatts_send_response(gatts_if, param->write.conn_id, param->write.trans_id, status, gatt_rsp); if (response_err != ESP_OK) { @@ -275,11 +298,7 @@ void example_exec_write_event_env(prepare_type_env_t *prepare_write_env, esp_ble if (param->exec_write.exec_write_flag != ESP_GATT_PREP_WRITE_EXEC){ ESP_LOGI(GATTS_TAG,"Prepare write cancel"); } - if (prepare_write_env->prepare_buf) { - free(prepare_write_env->prepare_buf); - prepare_write_env->prepare_buf = NULL; - } - prepare_write_env->prepare_len = 0; + prepare_write_env_clear(prepare_write_env); } static void gatts_profile_a_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param) { @@ -356,9 +375,22 @@ static void gatts_profile_a_event_handler(esp_gatts_cb_event_t event, esp_gatt_i #if (CONFIG_EXAMPLE_GATTC_WRITE_THROUGHPUT) if (param->write.handle == gl_profile_tab[PROFILE_A_APP_ID].char_handle) { // The last value byte is the checksum data, should used to check the data is received corrected or not. - if (param->write.value[param->write.len - 1] == - check_sum(param->write.value, param->write.len - 1)) { - write_len += param->write.len; + uint16_t wlen = param->write.len; + uint8_t *wval = param->write.value; + /* len==0 makes (wlen-1) wrap; cap len before indexing or passing to check_sum. */ + const uint16_t max_write_len = (uint16_t)(ESP_GATT_MAX_MTU_SIZE - 3U); + if (wval == NULL) { + ESP_LOGW(GATTS_TAG, "write ignored: null value"); + } else if (wlen == 0) { + ESP_LOGW(GATTS_TAG, "write ignored: zero length"); + } else if (wlen < 2) { + ESP_LOGW(GATTS_TAG, "write ignored: length too short for payload+checksum"); + } else if (wlen > max_write_len) { + ESP_LOGW(GATTS_TAG, "write ignored: length exceeds bound"); + } else if (wval[wlen - 1] == check_sum(wval, (uint16_t)(wlen - 1U))) { + write_len += wlen; + } else { + ESP_LOGE(GATTS_TAG, "write checksum mismatch"); } if (start == false) { @@ -451,10 +483,12 @@ static void gatts_profile_a_event_handler(esp_gatts_cb_event_t event, esp_gatt_i ESP_LOGI(GATTS_TAG, "Connected, conn_id %u, remote "ESP_BD_ADDR_STR"", param->connect.conn_id, ESP_BD_ADDR_HEX(param->connect.remote_bda)); gl_profile_tab[PROFILE_A_APP_ID].conn_id = param->connect.conn_id; + prepare_write_env_clear(&a_prepare_write_env); break; } case ESP_GATTS_DISCONNECT_EVT: is_connect = false; + prepare_write_env_clear(&a_prepare_write_env); ESP_LOGI(GATTS_TAG, "Disconnected, remote "ESP_BD_ADDR_STR", reason 0x%x", ESP_BD_ADDR_HEX(param->disconnect.remote_bda), param->disconnect.reason); esp_ble_gap_ext_adv_start(NUM_EXT_ADV_SET, &ext_adv[0]); diff --git a/examples/bluetooth/bluedroid/ble_50/ble50_throughput/throughput_server/sdkconfig.defaults b/examples/bluetooth/bluedroid/ble_50/ble50_throughput/throughput_server/sdkconfig.defaults index 83bac6e78ac..521d85b1514 100644 --- a/examples/bluetooth/bluedroid/ble_50/ble50_throughput/throughput_server/sdkconfig.defaults +++ b/examples/bluetooth/bluedroid/ble_50/ble50_throughput/throughput_server/sdkconfig.defaults @@ -9,3 +9,8 @@ CONFIG_BT_BLE_50_FEATURES_SUPPORTED=y CONFIG_BT_BLE_42_FEATURES_SUPPORTED=n # CONFIG_BT_LE_50_FEATURE_SUPPORT is not used on ESP32, ESP32-C3 and ESP32-S3. # CONFIG_BT_LE_50_FEATURE_SUPPORT=n + +# Disable unused Bluedroid host features (GATT server only) +# CONFIG_BT_GATTC_ENABLE is not set +# CONFIG_BT_BLE_SMP_ENABLE is not set +# CONFIG_BT_BLE_50_DTM_TEST_EN is not set diff --git a/examples/bluetooth/bluedroid/ble_50/ble_conn_subrating_central/main/main.c b/examples/bluetooth/bluedroid/ble_50/ble_conn_subrating_central/main/main.c index 4399dc4418d..285eb431684 100644 --- a/examples/bluetooth/bluedroid/ble_50/ble_conn_subrating_central/main/main.c +++ b/examples/bluetooth/bluedroid/ble_50/ble_conn_subrating_central/main/main.c @@ -96,6 +96,16 @@ static struct gattc_profile_inst gl_profile_tab[PROFILE_NUM] = { }, }; +/** Clear connect-in-progress flag and restart indefinite extended scan (after failed open or disconnect). */ +static void central_resume_ext_scan(void) +{ + connect = false; + esp_err_t err = esp_ble_gap_start_ext_scan(0, 0); + if (err != ESP_OK) { + ESP_LOGE(TAG, "start ext scan failed, error = 0x%x", err); + } +} + /** * @brief GATT client event handler */ @@ -112,6 +122,10 @@ static void gattc_profile_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_ case ESP_GATTC_REG_EVT: ESP_LOGI(TAG, "GATT client register, status %d, app_id %d, gattc_if %d", p_data->reg.status, p_data->reg.app_id, gattc_if); + if (p_data->reg.status != ESP_GATT_OK) { + ESP_LOGE(TAG, "GATT client register failed, status %d", p_data->reg.status); + break; + } gl_profile_tab[PROFILE_A_APP_ID].gattc_if = gattc_if; // Set default subrate parameters esp_ble_default_subrate_param_t default_subrate_params = { @@ -148,11 +162,19 @@ static void gattc_profile_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_ ESP_LOGI(TAG, "Subrate request sent successfully"); } break; + case ESP_GATTC_OPEN_EVT: + if (p_data->open.status != ESP_GATT_OK) { + ESP_LOGE(TAG, "Open failed, status %d", p_data->open.status); + central_resume_ext_scan(); + break; + } + ESP_LOGI(TAG, "GATT open OK, conn_id %d, MTU %u", p_data->open.conn_id, p_data->open.mtu); + break; case ESP_GATTC_DISCONNECT_EVT: - connect = false; g_conn_handle = 0xFFFF; ESP_LOGI(TAG, "Disconnected, remote "ESP_BD_ADDR_STR", reason 0x%02x", ESP_BD_ADDR_HEX(p_data->disconnect.remote_bda), p_data->disconnect.reason); + central_resume_ext_scan(); break; default: break; @@ -208,10 +230,9 @@ static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param creat_conn_params.phy_1m_conn_params = &phy_1m_conn_params; creat_conn_params.phy_2m_conn_params = &phy_2m_conn_params; creat_conn_params.phy_coded_conn_params = &phy_coded_conn_params; - if (esp_ble_gattc_enh_open(gl_profile_tab[PROFILE_A_APP_ID].gattc_if, &creat_conn_params) != ESP_OK) - { - connect = false; + if (esp_ble_gattc_enh_open(gl_profile_tab[PROFILE_A_APP_ID].gattc_if, &creat_conn_params) != ESP_OK) { ESP_LOGE(TAG, "Failed to open connection"); + central_resume_ext_scan(); } } } diff --git a/examples/bluetooth/bluedroid/ble_50/ble_conn_subrating_central/sdkconfig.defaults b/examples/bluetooth/bluedroid/ble_50/ble_conn_subrating_central/sdkconfig.defaults index 4fcdd7ebcc1..e32cd09388c 100644 --- a/examples/bluetooth/bluedroid/ble_50/ble_conn_subrating_central/sdkconfig.defaults +++ b/examples/bluetooth/bluedroid/ble_50/ble_conn_subrating_central/sdkconfig.defaults @@ -25,3 +25,4 @@ CONFIG_BT_BLE_50_EXTEND_ADV_EN=n CONFIG_BT_BLE_50_DTM_TEST_EN=n CONFIG_BT_BLE_50_EXTEND_SYNC_EN=n +CONFIG_PARTITION_TABLE_SINGLE_APP_LARGE=y diff --git a/examples/bluetooth/bluedroid/ble_50/ble_conn_subrating_peripheral/sdkconfig.defaults b/examples/bluetooth/bluedroid/ble_50/ble_conn_subrating_peripheral/sdkconfig.defaults index c806ef0519b..510c23942b4 100644 --- a/examples/bluetooth/bluedroid/ble_50/ble_conn_subrating_peripheral/sdkconfig.defaults +++ b/examples/bluetooth/bluedroid/ble_50/ble_conn_subrating_peripheral/sdkconfig.defaults @@ -25,3 +25,4 @@ CONFIG_BT_BLE_50_PERIODIC_ADV_EN=n CONFIG_BT_BLE_50_EXTEND_SCAN_EN=n CONFIG_BT_BLE_50_DTM_TEST_EN=n +CONFIG_PARTITION_TABLE_SINGLE_APP_LARGE=y diff --git a/examples/bluetooth/bluedroid/ble_50/ble_connection_central_with_cte/main/connection_central_with_cte.c b/examples/bluetooth/bluedroid/ble_50/ble_connection_central_with_cte/main/connection_central_with_cte.c index 9541004bda6..b16d9af6022 100644 --- a/examples/bluetooth/bluedroid/ble_50/ble_connection_central_with_cte/main/connection_central_with_cte.c +++ b/examples/bluetooth/bluedroid/ble_50/ble_connection_central_with_cte/main/connection_central_with_cte.c @@ -37,6 +37,7 @@ #define REMOTE_NOTIFY_UUID 0xFF01 #define EXT_SCAN_DURATION 0 #define EXT_SCAN_PERIOD 0 +#define BLE_CONN_HDL_INVALID ((uint16_t)0xFFFF) ///Declare static functions static void esp_gap_cb(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param); @@ -95,7 +96,7 @@ const esp_ble_conn_params_t phy_coded_conn_params = { static uint8_t antenna_ids[2] = {0x00, 0x01}; esp_ble_cte_recv_params_params_t cte_recv_params = { - .conn_handle = 0xff, + .conn_handle = BLE_CONN_HDL_INVALID, .sampling_en = ESP_BLE_CTE_SAMPLING_ENABLE, .slot_dur = ESP_BLE_CTE_SLOT_DURATION_2US, .switching_pattern_len = sizeof(antenna_ids), @@ -103,14 +104,14 @@ esp_ble_cte_recv_params_params_t cte_recv_params = { }; static esp_ble_cte_req_en_params_t cte_conn_req_en = { - .conn_handle = 0xff, + .conn_handle = BLE_CONN_HDL_INVALID, .enable = ESP_BLE_CTE_SAMPLING_ENABLE, .cte_req_interval = 0x05, .req_cte_len = ESP_BLE_CTE_MAX_REQUESTED_CTE_LENGTH, .req_cte_Type = ESP_BLE_CTE_TYPE_AOA, }; -uint16_t cur_conn_hdl = 0xff; +uint16_t cur_conn_hdl = BLE_CONN_HDL_INVALID; #define PROFILE_NUM 1 #define PROFILE_A_APP_ID 0 @@ -210,6 +211,31 @@ static char *esp_auth_req_to_str(esp_ble_auth_req_t auth_req) return auth_str; } +/** After a failed open or disconnect: clear connect flag and restart extended scan. */ +static void cte_resume_ext_scan(void) +{ + connect = false; + esp_err_t err = esp_ble_gap_start_ext_scan(EXT_SCAN_DURATION, EXT_SCAN_PERIOD); + if (err != ESP_OK) { + ESP_LOGE(LOG_TAG, "start ext scan failed, error = 0x%x", err); + } +} + +/** Start CTE receive setup only after link is authenticated (see esp_gap_cb AUTH_CMPL). */ +static void cte_enable_connection_receive_after_encrypted(void) +{ + if (cur_conn_hdl == BLE_CONN_HDL_INVALID) { + ESP_LOGW(LOG_TAG, "Skip CTE receive params: no connection handle"); + return; + } + ESP_LOGI(LOG_TAG, "Set CTE connection receive params after encryption, conn_handle %d", cur_conn_hdl); + cte_recv_params.conn_handle = cur_conn_hdl; + esp_err_t cte_ret = esp_ble_cte_set_connection_receive_params(&cte_recv_params); + if (cte_ret != ESP_OK) { + ESP_LOGE(LOG_TAG, "CTE set connection receive params failed, 0x%x", cte_ret); + } +} + static void gattc_profile_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) { esp_ble_gattc_cb_param_t *p_data = (esp_ble_gattc_cb_param_t *)param; @@ -227,6 +253,7 @@ static void gattc_profile_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_ case ESP_GATTC_OPEN_EVT: if (param->open.status != ESP_GATT_OK){ ESP_LOGE(LOG_TAG, "Open failed, status %x", p_data->open.status); + cte_resume_ext_scan(); break; } ESP_LOGI(LOG_TAG, "Open successfully, MTU %d", p_data->open.mtu); @@ -239,19 +266,14 @@ static void gattc_profile_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_ break; case ESP_GATTC_CFG_MTU_EVT: ESP_LOGI(LOG_TAG, "MTU exchange, status %d, MTU %d, conn_id %d", param->cfg_mtu.status, param->cfg_mtu.mtu, param->cfg_mtu.conn_id); - if (!param->cfg_mtu.status) { - ESP_LOGI(LOG_TAG, "Set CTE connection receive params, conn_handle %d", cur_conn_hdl); - cte_recv_params.conn_handle = cur_conn_hdl; - esp_ble_cte_set_connection_receive_params(&cte_recv_params); - } break; case ESP_GATTC_DIS_SRVC_CMPL_EVT: if (param->dis_srvc_cmpl.status != ESP_GATT_OK){ ESP_LOGE(LOG_TAG, "Service discover failed, status %d", param->dis_srvc_cmpl.status); break; } - ESP_LOGI(LOG_TAG, "Service discover complete, conn_id %d", param->dis_srvc_cmpl.conn_id); - esp_ble_gattc_search_service(gattc_if, param->cfg_mtu.conn_id, &remote_filter_service_uuid); + ESP_LOGI(LOG_TAG, "Service discover complete, conn_id %d", p_data->dis_srvc_cmpl.conn_id); + esp_ble_gattc_search_service(gattc_if, p_data->dis_srvc_cmpl.conn_id, &remote_filter_service_uuid); break; case ESP_GATTC_SEARCH_RES_EVT: { ESP_LOGI(LOG_TAG, "Service search result, conn_id %x, is primary service %d", p_data->search_res.conn_id, p_data->search_res.is_primary); @@ -314,9 +336,9 @@ static void gattc_profile_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_ case ESP_GATTC_DISCONNECT_EVT: ESP_LOGI(LOG_TAG, "Disconnected, remote "ESP_BD_ADDR_STR", reason 0x%02x", ESP_BD_ADDR_HEX(p_data->disconnect.remote_bda), p_data->disconnect.reason); - connect = false; get_service = false; - esp_ble_gap_start_ext_scan(EXT_SCAN_DURATION, EXT_SCAN_PERIOD); + cur_conn_hdl = BLE_CONN_HDL_INVALID; + cte_resume_ext_scan(); break; default: break; @@ -397,8 +419,7 @@ static void esp_gap_cb(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *par ESP_LOGI(LOG_TAG, "Pairing failed, reason 0x%x",param->ble_security.auth_cmpl.fail_reason); } else { ESP_LOGI(LOG_TAG, "Pairing successfully, auth mode %s",esp_auth_req_to_str(param->ble_security.auth_cmpl.auth_mode)); - // Enable CTE - + cte_enable_connection_receive_after_encrypted(); } break; } @@ -429,7 +450,10 @@ static void esp_gap_cb(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *par creat_conn_params.phy_1m_conn_params = &phy_1m_conn_params; creat_conn_params.phy_2m_conn_params = &phy_2m_conn_params; creat_conn_params.phy_coded_conn_params = &phy_coded_conn_params; - esp_ble_gattc_enh_open(gl_profile_tab[PROFILE_A_APP_ID].gattc_if, &creat_conn_params); + if (esp_ble_gattc_enh_open(gl_profile_tab[PROFILE_A_APP_ID].gattc_if, &creat_conn_params) != ESP_OK) { + ESP_LOGE(LOG_TAG, "Failed to open connection"); + cte_resume_ext_scan(); + } } break; @@ -453,14 +477,17 @@ static void cte_event_handler(esp_ble_cte_cb_event_t event, esp_ble_cte_cb_param case ESP_BLE_CTE_SET_CONN_TRANS_PARAMS_CMPL_EVT: ESP_LOGI(LOG_TAG, "CTE set connection transmit params, status %d", param->conn_trans_params_cmpl.status); break; - case ESP_BLE_CTE_SET_CONN_RECV_PARAMS_CMPL_EVT: - ESP_LOGI(LOG_TAG, "CTE set connection receive params, status %d", param->conn_recv_params_cmpl.status); + case ESP_BLE_CTE_SET_CONN_RECV_PARAMS_CMPL_EVT: { + uint16_t recv_cmpl_conn_hdl = param->conn_recv_params_cmpl.conn_handle; + ESP_LOGI(LOG_TAG, "CTE set connection receive params, status %d, conn_handle %d", + param->conn_recv_params_cmpl.status, recv_cmpl_conn_hdl); if (!param->conn_recv_params_cmpl.status) { - cte_conn_req_en.conn_handle = cur_conn_hdl; - ESP_LOGI(LOG_TAG, "Enable CTE request, conn_handle %d", cur_conn_hdl); + cte_conn_req_en.conn_handle = recv_cmpl_conn_hdl; + ESP_LOGI(LOG_TAG, "Enable CTE request, conn_handle %d", recv_cmpl_conn_hdl); esp_ble_cte_connection_cte_request_enable(&cte_conn_req_en); } break; + } case ESP_BLE_CTE_SET_CONN_REQ_ENABLE_CMPL_EVT: ESP_LOGI(LOG_TAG, "CTE set connection request enable, status %d", param->conn_req_en_cmpl.status); break; diff --git a/examples/bluetooth/bluedroid/ble_50/ble_connection_central_with_cte/sdkconfig.defaults b/examples/bluetooth/bluedroid/ble_50/ble_connection_central_with_cte/sdkconfig.defaults index 6272796eefd..ecb634a0783 100644 --- a/examples/bluetooth/bluedroid/ble_50/ble_connection_central_with_cte/sdkconfig.defaults +++ b/examples/bluetooth/bluedroid/ble_50/ble_connection_central_with_cte/sdkconfig.defaults @@ -20,3 +20,5 @@ CONFIG_BT_BLE_50_EXTEND_ADV_EN=n CONFIG_BT_BLE_50_EXTEND_SYNC_EN=n CONFIG_BT_BLE_50_DTM_TEST_EN=n + +CONFIG_PARTITION_TABLE_SINGLE_APP_LARGE=y diff --git a/examples/bluetooth/bluedroid/ble_50/ble_connection_peripheral_with_cte/main/connection_peripheral_with_cte.c b/examples/bluetooth/bluedroid/ble_50/ble_connection_peripheral_with_cte/main/connection_peripheral_with_cte.c index 3711a1d9077..cd3c395cba5 100644 --- a/examples/bluetooth/bluedroid/ble_50/ble_connection_peripheral_with_cte/main/connection_peripheral_with_cte.c +++ b/examples/bluetooth/bluedroid/ble_50/ble_connection_peripheral_with_cte/main/connection_peripheral_with_cte.c @@ -35,6 +35,9 @@ #define GATTS_DEMO_CHAR_VAL_LEN_MAX 0x40 +/* Connection_Handle is 12-bit (0x0000..0x0EFF); 0xFFFF is outside the valid range. */ +#define BLE_CONN_HDL_INVALID ((uint16_t)0xFFFF) + #ifndef MIN #define MIN(a, b) (((a) < (b)) ? (a) : (b)) #endif @@ -68,18 +71,18 @@ esp_ble_gap_ext_adv_params_t ext_adv_params_2M = { static uint8_t antenna_ids[2] = {0x00, 0x01}; static esp_ble_cte_conn_trans_params_t cte_conn_trans_params = { - .conn_handle = 0xff, + .conn_handle = BLE_CONN_HDL_INVALID, .cte_types = ESP_BLE_CTE_TYPES_ALL, .switching_pattern_len = sizeof(antenna_ids), .antenna_ids = &antenna_ids[0], }; static esp_ble_cte_rsp_en_params_t cte_conn_rsp_en = { - .conn_handle = 0xff, + .conn_handle = BLE_CONN_HDL_INVALID, .enable = ESP_BLE_CTE_RESPONSE_FOR_CONNECTION_ENABLE, }; -uint16_t cur_conn_hdl = 0xff; +uint16_t cur_conn_hdl = BLE_CONN_HDL_INVALID; struct gatts_profile_inst { esp_gatts_cb_t gatts_cb; @@ -315,10 +318,14 @@ static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param ESP_LOGI(LOG_TAG, "Pairing failed, reason 0x%x",param->ble_security.auth_cmpl.fail_reason); } else { ESP_LOGI(LOG_TAG, "Pairing successfully, auth_mode %s",esp_auth_req_to_str(param->ble_security.auth_cmpl.auth_mode)); - // Setting CTE connection transmit parameters - cte_conn_trans_params.conn_handle = cur_conn_hdl; - ESP_LOGI(LOG_TAG, "Set CTE connection transmit params, conn_handle %d", cur_conn_hdl); - esp_ble_cte_set_connection_transmit_params(&cte_conn_trans_params); + if (cur_conn_hdl != BLE_CONN_HDL_INVALID) { + // Setting CTE connection transmit parameters + cte_conn_trans_params.conn_handle = cur_conn_hdl; + ESP_LOGI(LOG_TAG, "Set CTE connection transmit params, conn_handle %d", cur_conn_hdl); + esp_ble_cte_set_connection_transmit_params(&cte_conn_trans_params); + } else { + ESP_LOGW(LOG_TAG, "Skip CTE transmit params: no active connection handle"); + } } break; } @@ -385,6 +392,7 @@ static void gatts_profile_event_handler(esp_gatts_cb_event_t event, case ESP_GATTS_DISCONNECT_EVT: ESP_LOGI(LOG_TAG, "Disconnected, remote "ESP_BD_ADDR_STR", reason 0x%x", ESP_BD_ADDR_HEX(param->disconnect.remote_bda), param->disconnect.reason); + cur_conn_hdl = BLE_CONN_HDL_INVALID; /* start advertising again when missing the connect */ esp_ble_gap_ext_adv_start(NUM_EXT_ADV_SET, &ext_adv[0]); break; @@ -399,18 +407,18 @@ static void gatts_profile_event_handler(esp_gatts_cb_event_t event, case ESP_GATTS_CONGEST_EVT: break; case ESP_GATTS_CREAT_ATTR_TAB_EVT: { - if (param->create.status == ESP_GATT_OK){ - if(param->add_attr_tab.num_handle == HRS_IDX_NB) { + if (param->add_attr_tab.status == ESP_GATT_OK) { + if (param->add_attr_tab.num_handle == HRS_IDX_NB) { ESP_LOGI(LOG_TAG, "Attribute table create successfully, num_handle %x", param->add_attr_tab.num_handle); memcpy(profile_handle_table, param->add_attr_tab.handles, - sizeof(profile_handle_table)); + sizeof(profile_handle_table)); esp_ble_gatts_start_service(profile_handle_table[IDX_SVC]); - }else{ + } else { ESP_LOGE(LOG_TAG, "Attribute table create abnormally, num_handle (%d) doesn't equal to HRS_IDX_NB(%d)", - param->add_attr_tab.num_handle, HRS_IDX_NB); + param->add_attr_tab.num_handle, HRS_IDX_NB); } - }else{ - ESP_LOGE(LOG_TAG, "Attribute table create failed, status %x", param->create.status); + } else { + ESP_LOGE(LOG_TAG, "Attribute table create failed, status %x", param->add_attr_tab.status); } break; } @@ -422,15 +430,17 @@ static void gatts_profile_event_handler(esp_gatts_cb_event_t event, static void cte_event_handler(esp_ble_cte_cb_event_t event, esp_ble_cte_cb_param_t *param) { switch (event) { - case ESP_BLE_CTE_SET_CONN_TRANS_PARAMS_CMPL_EVT: - ESP_LOGI(LOG_TAG, "CTE set connection transmit params, status %d", param->conn_trans_params_cmpl.status); + case ESP_BLE_CTE_SET_CONN_TRANS_PARAMS_CMPL_EVT: { + uint16_t trans_cmpl_conn_hdl = param->conn_trans_params_cmpl.conn_handle; + ESP_LOGI(LOG_TAG, "CTE set connection transmit params, status %d, conn_handle %d", + param->conn_trans_params_cmpl.status, trans_cmpl_conn_hdl); if (!param->conn_trans_params_cmpl.status) { - ESP_LOGI(LOG_TAG, "Setting CTE connection response enable"); - // Enable CTE response for connection - cte_conn_rsp_en.conn_handle = cur_conn_hdl; + ESP_LOGI(LOG_TAG, "Setting CTE connection response enable, conn_handle %d", trans_cmpl_conn_hdl); + cte_conn_rsp_en.conn_handle = trans_cmpl_conn_hdl; esp_ble_cte_connection_cte_response_enable(&cte_conn_rsp_en); } break; + } case ESP_BLE_CTE_SET_CONN_RECV_PARAMS_CMPL_EVT: ESP_LOGI(LOG_TAG, "CTE set connection receive params, status %d", param->conn_recv_params_cmpl.status); break; diff --git a/examples/bluetooth/bluedroid/ble_50/ble_connection_peripheral_with_cte/sdkconfig.defaults b/examples/bluetooth/bluedroid/ble_50/ble_connection_peripheral_with_cte/sdkconfig.defaults index 3d045fb9883..f6ad0662f23 100644 --- a/examples/bluetooth/bluedroid/ble_50/ble_connection_peripheral_with_cte/sdkconfig.defaults +++ b/examples/bluetooth/bluedroid/ble_50/ble_connection_peripheral_with_cte/sdkconfig.defaults @@ -23,3 +23,5 @@ CONFIG_BT_BLE_50_EXTEND_SCAN_EN=n CONFIG_BT_BLE_50_EXTEND_SYNC_EN=n CONFIG_BT_BLE_50_DTM_TEST_EN=n + +CONFIG_PARTITION_TABLE_SINGLE_APP_LARGE=y diff --git a/examples/bluetooth/bluedroid/ble_50/ble_pawr_advertiser/main/main.c b/examples/bluetooth/bluedroid/ble_50/ble_pawr_advertiser/main/main.c index 871d871011a..d2c5b55e192 100644 --- a/examples/bluetooth/bluedroid/ble_50/ble_pawr_advertiser/main/main.c +++ b/examples/bluetooth/bluedroid/ble_50/ble_pawr_advertiser/main/main.c @@ -223,7 +223,11 @@ static void start_periodic_adv(void) { // Create static random address esp_bd_addr_t rand_addr; - esp_ble_gap_addr_create_static(rand_addr); + esp_err_t addr_ret = esp_ble_gap_addr_create_static(rand_addr); + if (addr_ret != ESP_OK) { + ESP_LOGE(TAG, "esp_ble_gap_addr_create_static failed: %s", esp_err_to_name(addr_ret)); + return; + } ESP_LOG_BUFFER_HEX(TAG, rand_addr, ESP_BD_ADDR_LEN); diff --git a/examples/bluetooth/bluedroid/ble_50/ble_pawr_advertiser/sdkconfig.defaults b/examples/bluetooth/bluedroid/ble_50/ble_pawr_advertiser/sdkconfig.defaults index fa70c13f46a..416d51fc277 100644 --- a/examples/bluetooth/bluedroid/ble_50/ble_pawr_advertiser/sdkconfig.defaults +++ b/examples/bluetooth/bluedroid/ble_50/ble_pawr_advertiser/sdkconfig.defaults @@ -34,3 +34,5 @@ CONFIG_BT_GATTS_ENABLE=n CONFIG_BT_BLE_50_EXTEND_SCAN_EN=n CONFIG_BT_BLE_50_DTM_TEST_EN=n + +CONFIG_PARTITION_TABLE_SINGLE_APP_LARGE=y diff --git a/examples/bluetooth/bluedroid/ble_50/ble_pawr_advertiser_conn/sdkconfig.defaults b/examples/bluetooth/bluedroid/ble_50/ble_pawr_advertiser_conn/sdkconfig.defaults index b0b55071e50..b740991bb8e 100644 --- a/examples/bluetooth/bluedroid/ble_50/ble_pawr_advertiser_conn/sdkconfig.defaults +++ b/examples/bluetooth/bluedroid/ble_50/ble_pawr_advertiser_conn/sdkconfig.defaults @@ -32,3 +32,5 @@ CONFIG_BT_BLE_50_DTM_TEST_EN=n CONFIG_BT_BLE_SMP_ENABLE=n CONFIG_BT_GATTS_ENABLE=n + +CONFIG_PARTITION_TABLE_SINGLE_APP_LARGE=y diff --git a/examples/bluetooth/bluedroid/ble_50/ble_pawr_synchronizer/main/ble_pawr_synchronizer_demo.c b/examples/bluetooth/bluedroid/ble_50/ble_pawr_synchronizer/main/ble_pawr_synchronizer_demo.c index f3761761d28..8c0f33a629f 100644 --- a/examples/bluetooth/bluedroid/ble_50/ble_pawr_synchronizer/main/ble_pawr_synchronizer_demo.c +++ b/examples/bluetooth/bluedroid/ble_50/ble_pawr_synchronizer/main/ble_pawr_synchronizer_demo.c @@ -243,7 +243,9 @@ static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param param->ext_adv_report.params.adv_data_len, ESP_BLE_AD_TYPE_NAME_CMPL, &adv_name_len); - if ((adv_name != NULL) && (memcmp(adv_name, remote_device_name, adv_name_len) == 0) && !periodic_sync) { + if ((adv_name != NULL) && (adv_name_len > 0) && + (adv_name_len == strlen(remote_device_name)) && + (memcmp(adv_name, remote_device_name, adv_name_len) == 0) && !periodic_sync) { // Note: If there are multiple devices with the same device name, the device may sync to an unintended one. // It is recommended to change the default device name to ensure it is unique. periodic_sync = true; diff --git a/examples/bluetooth/bluedroid/ble_50/ble_pawr_synchronizer/sdkconfig.defaults b/examples/bluetooth/bluedroid/ble_50/ble_pawr_synchronizer/sdkconfig.defaults index 7b444caaf14..810004f14ba 100644 --- a/examples/bluetooth/bluedroid/ble_50/ble_pawr_synchronizer/sdkconfig.defaults +++ b/examples/bluetooth/bluedroid/ble_50/ble_pawr_synchronizer/sdkconfig.defaults @@ -12,3 +12,6 @@ CONFIG_BT_GATTC_ENABLE=n CONFIG_BT_BLE_SMP_ENABLE=n CONFIG_BT_BLE_50_EXTEND_ADV_EN=n + +CONFIG_PARTITION_TABLE_SINGLE_APP_LARGE=y +CONFIG_BT_BLE_50_DTM_TEST_EN=n diff --git a/examples/bluetooth/bluedroid/ble_50/ble_periodic_adv_with_cte/main/periodic_adv_with_cte_demo.c b/examples/bluetooth/bluedroid/ble_50/ble_periodic_adv_with_cte/main/periodic_adv_with_cte_demo.c index 0d3aece9815..7269ba8370b 100644 --- a/examples/bluetooth/bluedroid/ble_50/ble_periodic_adv_with_cte/main/periodic_adv_with_cte_demo.c +++ b/examples/bluetooth/bluedroid/ble_50/ble_periodic_adv_with_cte/main/periodic_adv_with_cte_demo.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Unlicense OR CC0-1.0 */ @@ -41,16 +41,21 @@ #define FUNC_SEND_WAIT_SEM(func, sem) do {\ esp_err_t __err_rc = (func);\ if (__err_rc != ESP_OK) { \ - ESP_LOGE(LOG_TAG, "%s, message send fail, error = %d", __func__, __err_rc); \ + ESP_LOGE(LOG_TAG, "%s failed: %s", #func, esp_err_to_name(__err_rc)); \ + return; \ + } \ + xSemaphoreTake((sem), portMAX_DELAY); \ + if (last_ble_async_status != ESP_BT_STATUS_SUCCESS) { \ + ESP_LOGE(LOG_TAG, "Async completion after %s failed, status 0x%x", #func, last_ble_async_status); \ + return; \ } \ - xSemaphoreTake(sem, portMAX_DELAY); \ } while(0); #define EXT_ADV_HANDLE 0 #define NUM_EXT_ADV 1 static SemaphoreHandle_t test_sem = NULL; - +static esp_bt_status_t last_ble_async_status = ESP_BT_STATUS_SUCCESS; uint8_t addr_2m[6] = {0xc0, 0xde, 0x52, 0x00, 0x00, 0x02}; @@ -113,44 +118,56 @@ static esp_ble_cte_trans_enable_params_t cte_trans_enable = { static uint8_t periodic_adv_hdl = 0xff; +static void ble_async_complete_signal(esp_bt_status_t status) +{ + last_ble_async_status = status; + if (test_sem != NULL) { + xSemaphoreGive(test_sem); + } +} + static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) { switch (event) { case ESP_GAP_BLE_EXT_ADV_SET_RAND_ADDR_COMPLETE_EVT: - xSemaphoreGive(test_sem); + ble_async_complete_signal(param->ext_adv_set_rand_addr.status); ESP_LOGI(LOG_TAG, "ESP_GAP_BLE_EXT_ADV_SET_RAND_ADDR_COMPLETE_EVT, status %d", param->ext_adv_set_rand_addr.status); break; case ESP_GAP_BLE_EXT_ADV_SET_PARAMS_COMPLETE_EVT: - xSemaphoreGive(test_sem); + ble_async_complete_signal(param->ext_adv_set_params.status); ESP_LOGI(LOG_TAG, "ESP_GAP_BLE_EXT_ADV_SET_PARAMS_COMPLETE_EVT, status %d", param->ext_adv_set_params.status); break; case ESP_GAP_BLE_EXT_ADV_DATA_SET_COMPLETE_EVT: - xSemaphoreGive(test_sem); + ble_async_complete_signal(param->ext_adv_data_set.status); ESP_LOGI(LOG_TAG, "ESP_GAP_BLE_EXT_ADV_DATA_SET_COMPLETE_EVT, status %d", param->ext_adv_data_set.status); break; case ESP_GAP_BLE_EXT_SCAN_RSP_DATA_SET_COMPLETE_EVT: - xSemaphoreGive(test_sem); + ble_async_complete_signal(param->scan_rsp_set.status); ESP_LOGI(LOG_TAG, "ESP_GAP_BLE_EXT_SCAN_RSP_DATA_SET_COMPLETE_EVT, status %d", param->scan_rsp_set.status); break; case ESP_GAP_BLE_EXT_ADV_START_COMPLETE_EVT: - xSemaphoreGive(test_sem); + ble_async_complete_signal(param->ext_adv_start.status); ESP_LOGI(LOG_TAG, "ESP_GAP_BLE_EXT_ADV_START_COMPLETE_EVT, status %d", param->ext_adv_start.status); break; case ESP_GAP_BLE_EXT_ADV_STOP_COMPLETE_EVT: - xSemaphoreGive(test_sem); + ble_async_complete_signal(param->ext_adv_stop.status); ESP_LOGI(LOG_TAG, "ESP_GAP_BLE_EXT_ADV_STOP_COMPLETE_EVT, status %d", param->ext_adv_stop.status); break; case ESP_GAP_BLE_PERIODIC_ADV_SET_PARAMS_COMPLETE_EVT: - periodic_adv_hdl = param->peroid_adv_set_params.instance; - xSemaphoreGive(test_sem); + if (param->peroid_adv_set_params.status == ESP_BT_STATUS_SUCCESS) { + periodic_adv_hdl = param->peroid_adv_set_params.instance; + } else { + ESP_LOGE(LOG_TAG, "periodic adv set params failed, not updating periodic_adv_hdl"); + } + ble_async_complete_signal(param->peroid_adv_set_params.status); ESP_LOGI(LOG_TAG, "ESP_GAP_BLE_PERIODIC_ADV_SET_PARAMS_COMPLETE_EVT, status %d", param->peroid_adv_set_params.status); break; case ESP_GAP_BLE_PERIODIC_ADV_DATA_SET_COMPLETE_EVT: - xSemaphoreGive(test_sem); + ble_async_complete_signal(param->period_adv_data_set.status); ESP_LOGI(LOG_TAG, "ESP_GAP_BLE_PERIODIC_ADV_DATA_SET_COMPLETE_EVT, status %d", param->period_adv_data_set.status); break; case ESP_GAP_BLE_PERIODIC_ADV_START_COMPLETE_EVT: - xSemaphoreGive(test_sem); + ble_async_complete_signal(param->period_adv_start.status); ESP_LOGI(LOG_TAG, "ESP_GAP_BLE_PERIODIC_ADV_START_COMPLETE_EVT, status %d", param->period_adv_start.status); break; default: @@ -162,11 +179,11 @@ static void cte_event_handler(esp_ble_cte_cb_event_t event, esp_ble_cte_cb_param { switch (event) { case ESP_BLE_CTE_SET_CONNLESS_TRANS_PARAMS_CMPL_EVT: - xSemaphoreGive(test_sem); + ble_async_complete_signal(param->set_trans_params_cmpl.status); ESP_LOGI(LOG_TAG, "ESP_BLE_CTE_SET_CONNLESS_TRANS_PARAMS_CMPL_EVT, status %d", param->set_trans_params_cmpl.status); break; case ESP_BLE_CTE_SET_CONNLESS_TRANS_ENABLE_CMPL_EVT: - xSemaphoreGive(test_sem); + ble_async_complete_signal(param->set_trans_enable_cmpl.status); ESP_LOGI(LOG_TAG, "ESP_BLE_CTE_SET_CONNLESS_TRANS_ENABLE_CMPL_EVT, status %d", param->set_trans_enable_cmpl.status); break; case ESP_BLE_CTE_SET_CONNLESS_IQ_SAMPLING_ENABLE_CMPL_EVT: diff --git a/examples/bluetooth/bluedroid/ble_50/ble_periodic_adv_with_cte/sdkconfig.defaults b/examples/bluetooth/bluedroid/ble_50/ble_periodic_adv_with_cte/sdkconfig.defaults index fa787c9290c..714f390bbfa 100644 --- a/examples/bluetooth/bluedroid/ble_50/ble_periodic_adv_with_cte/sdkconfig.defaults +++ b/examples/bluetooth/bluedroid/ble_50/ble_periodic_adv_with_cte/sdkconfig.defaults @@ -24,3 +24,5 @@ CONFIG_BT_BLE_50_PERIODIC_ADV_EN=y CONFIG_BT_BLE_50_EXTEND_SCAN_EN=n CONFIG_BT_BLE_50_DTM_TEST_EN=n + +CONFIG_PARTITION_TABLE_SINGLE_APP_LARGE=y diff --git a/examples/bluetooth/bluedroid/ble_50/ble_periodic_sync_with_cte/main/periodic_sync_with_cte_demo.c b/examples/bluetooth/bluedroid/ble_50/ble_periodic_sync_with_cte/main/periodic_sync_with_cte_demo.c index 8ee31646e40..281984152f4 100644 --- a/examples/bluetooth/bluedroid/ble_50/ble_periodic_sync_with_cte/main/periodic_sync_with_cte_demo.c +++ b/examples/bluetooth/bluedroid/ble_50/ble_periodic_sync_with_cte/main/periodic_sync_with_cte_demo.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Unlicense OR CC0-1.0 */ @@ -37,15 +37,16 @@ #include "esp_ble_cte_api.h" +#define LOG_TAG "PERIODIC_SYNC" + #define FUNC_SEND_WAIT_SEM(func, sem) do {\ esp_err_t __err_rc = (func);\ if (__err_rc != ESP_OK) { \ - ESP_LOGE(LOG_TAG, "%s, message send fail, error = %d", __func__, __err_rc); \ + ESP_LOGE(LOG_TAG, "%s failed: %s", #func, esp_err_to_name(__err_rc)); \ + return; \ } \ - xSemaphoreTake(sem, portMAX_DELAY); \ + xSemaphoreTake((sem), portMAX_DELAY); \ } while(0); - -#define LOG_TAG "PERIODIC_SYNC" #define EXT_SCAN_DURATION 0 #define EXT_SCAN_PERIOD 0 @@ -136,13 +137,16 @@ static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param param->ext_adv_report.params.adv_data_len, ESP_BLE_AD_TYPE_NAME_CMPL, &adv_name_len); - if ((adv_name != NULL) && (memcmp(adv_name, remote_device_name, adv_name_len) == 0) && !periodic_sync) { + const size_t remote_cap = sizeof(remote_device_name); + size_t remote_len = strnlen(remote_device_name, remote_cap); + if ((adv_name != NULL) && (adv_name_len > 0) && + (adv_name_len <= remote_cap) && + (adv_name_len == remote_len) && + (memcmp(adv_name, remote_device_name, adv_name_len) == 0) && !periodic_sync) { // Note: If there are multiple devices with the same device name, the device may sync to an unintended one. // It is recommended to change the default device name to ensure it is unique. periodic_sync = true; - char adv_temp_name[30] = {'0'}; - memcpy(adv_temp_name, adv_name, adv_name_len); - ESP_LOGI(LOG_TAG, "Create sync with the peer device %s", adv_temp_name); + ESP_LOGI(LOG_TAG, "Create sync with the peer device %.*s", (int)adv_name_len, (const char *)adv_name); periodic_adv_sync_params.sid = param->ext_adv_report.params.sid; periodic_adv_sync_params.addr_type = param->ext_adv_report.params.addr_type; memcpy(periodic_adv_sync_params.addr, param->ext_adv_report.params.addr, sizeof(esp_bd_addr_t)); diff --git a/examples/bluetooth/bluedroid/ble_50/ble_periodic_sync_with_cte/sdkconfig.defaults b/examples/bluetooth/bluedroid/ble_50/ble_periodic_sync_with_cte/sdkconfig.defaults index 8e462e05234..06d12a5a754 100644 --- a/examples/bluetooth/bluedroid/ble_50/ble_periodic_sync_with_cte/sdkconfig.defaults +++ b/examples/bluetooth/bluedroid/ble_50/ble_periodic_sync_with_cte/sdkconfig.defaults @@ -22,3 +22,5 @@ CONFIG_BT_BLE_50_EXTEND_ADV_EN=n CONFIG_BT_BLE_50_EXTEND_SCAN_EN=y CONFIG_BT_BLE_50_DTM_TEST_EN=n + +CONFIG_PARTITION_TABLE_SINGLE_APP_LARGE=y diff --git a/examples/bluetooth/bluedroid/ble_50/ble_power_control_central/main/main.c b/examples/bluetooth/bluedroid/ble_50/ble_power_control_central/main/main.c index f012bf4b620..84effffd80b 100644 --- a/examples/bluetooth/bluedroid/ble_50/ble_power_control_central/main/main.c +++ b/examples/bluetooth/bluedroid/ble_50/ble_power_control_central/main/main.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Unlicense OR CC0-1.0 */ @@ -180,12 +180,12 @@ static void gattc_profile_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_ } break; case ESP_GATTC_CONNECT_EVT: - ESP_LOGI(TAG, "Connected, conn_id %d, remote "ESP_BD_ADDR_STR"", - p_data->connect.conn_id, ESP_BD_ADDR_HEX(p_data->connect.remote_bda)); + ESP_LOGI(TAG, "Connected, conn_id %d, hci_conn_handle %d, remote "ESP_BD_ADDR_STR"", + p_data->connect.conn_id, p_data->connect.conn_handle, + ESP_BD_ADDR_HEX(p_data->connect.remote_bda)); gl_profile_tab[PROFILE_A_APP_ID].conn_id = p_data->connect.conn_id; memcpy(gl_profile_tab[PROFILE_A_APP_ID].remote_bda, p_data->connect.remote_bda, sizeof(esp_bd_addr_t)); - conn_handle = p_data->connect.conn_id; - // Initialize power control after connection + conn_handle = p_data->connect.conn_handle; init_power_control(conn_handle); break; case ESP_GATTC_DISCONNECT_EVT: diff --git a/examples/bluetooth/bluedroid/ble_50/ble_power_control_central/sdkconfig.defaults b/examples/bluetooth/bluedroid/ble_50/ble_power_control_central/sdkconfig.defaults index 6370d701cdb..8e0aee8bc40 100644 --- a/examples/bluetooth/bluedroid/ble_50/ble_power_control_central/sdkconfig.defaults +++ b/examples/bluetooth/bluedroid/ble_50/ble_power_control_central/sdkconfig.defaults @@ -29,3 +29,5 @@ CONFIG_BT_BLE_50_EXTEND_ADV_EN=n CONFIG_BT_BLE_50_EXTEND_SYNC_EN=n CONFIG_BT_BLE_50_DTM_TEST_EN=n + +CONFIG_PARTITION_TABLE_SINGLE_APP_LARGE=y diff --git a/examples/bluetooth/bluedroid/ble_50/ble_power_control_peripheral/sdkconfig.defaults b/examples/bluetooth/bluedroid/ble_50/ble_power_control_peripheral/sdkconfig.defaults index a55da6e3629..069b8682e7b 100644 --- a/examples/bluetooth/bluedroid/ble_50/ble_power_control_peripheral/sdkconfig.defaults +++ b/examples/bluetooth/bluedroid/ble_50/ble_power_control_peripheral/sdkconfig.defaults @@ -30,3 +30,5 @@ CONFIG_BT_BLE_50_EXTEND_SCAN_EN=y CONFIG_BT_BLE_50_EXTEND_SYNC_EN=n CONFIG_BT_BLE_50_DTM_TEST_EN=n + +CONFIG_PARTITION_TABLE_SINGLE_APP_LARGE=y diff --git a/examples/bluetooth/bluedroid/ble_50/multi-adv/sdkconfig.defaults b/examples/bluetooth/bluedroid/ble_50/multi-adv/sdkconfig.defaults index 6777f92fd54..7553f5a56e6 100644 --- a/examples/bluetooth/bluedroid/ble_50/multi-adv/sdkconfig.defaults +++ b/examples/bluetooth/bluedroid/ble_50/multi-adv/sdkconfig.defaults @@ -2,4 +2,20 @@ # Espressif IoT Development Framework (ESP-IDF) Project Minimal Configuration # CONFIG_BT_ENABLED=y +CONFIG_BT_BLUEDROID_ENABLED=y +CONFIG_BT_BLE_ENABLED=y +CONFIG_BT_BLE_50_FEATURES_SUPPORTED=y +CONFIG_BT_BLE_42_FEATURES_SUPPORTED=n + +# Multiple extended advertisers only CONFIG_BT_LE_MAX_EXT_ADV_INSTANCES=4 +CONFIG_PARTITION_TABLE_SINGLE_APP_LARGE=y +CONFIG_BT_BLE_50_EXTEND_ADV_EN=y +CONFIG_BT_BLE_50_EXTEND_SCAN_EN=n +CONFIG_BT_BLE_50_EXTEND_SYNC_EN=n +CONFIG_BT_BLE_50_PERIODIC_ADV_EN=n + +# CONFIG_BT_GATTS_ENABLE is not set +# CONFIG_BT_GATTC_ENABLE is not set +# CONFIG_BT_BLE_SMP_ENABLE is not set +# CONFIG_BT_BLE_50_DTM_TEST_EN is not set diff --git a/examples/bluetooth/bluedroid/ble_50/periodic_adv/main/periodic_adv_demo.c b/examples/bluetooth/bluedroid/ble_50/periodic_adv/main/periodic_adv_demo.c index 9c9cffb8ca1..010856f6985 100644 --- a/examples/bluetooth/bluedroid/ble_50/periodic_adv/main/periodic_adv_demo.c +++ b/examples/bluetooth/bluedroid/ble_50/periodic_adv/main/periodic_adv_demo.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2021-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2021-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Unlicense OR CC0-1.0 */ @@ -40,9 +40,10 @@ #define FUNC_SEND_WAIT_SEM(func, sem) do {\ esp_err_t __err_rc = (func);\ if (__err_rc != ESP_OK) { \ - ESP_LOGE(LOG_TAG, "%s, message send fail, error = %d", __func__, __err_rc); \ + ESP_LOGE(LOG_TAG, "%s failed: %s", #func, esp_err_to_name(__err_rc)); \ + return; \ } \ - xSemaphoreTake(sem, portMAX_DELAY); \ + xSemaphoreTake((sem), portMAX_DELAY); \ } while(0); #define EXT_ADV_HANDLE 0 @@ -95,43 +96,50 @@ static esp_ble_gap_ext_adv_t ext_adv[1] = { [0] = {EXT_ADV_HANDLE, 0, 0}, }; +static void periodic_adv_gap_sem_give(void) +{ + if (test_sem != NULL) { + xSemaphoreGive(test_sem); + } +} + static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) { switch (event) { case ESP_GAP_BLE_EXT_ADV_SET_RAND_ADDR_COMPLETE_EVT: - xSemaphoreGive(test_sem); + periodic_adv_gap_sem_give(); ESP_LOGI(LOG_TAG, "Extended advertising random address set, status %d, instance %d", param->ext_adv_set_rand_addr.status, param->ext_adv_set_rand_addr.instance); break; case ESP_GAP_BLE_EXT_ADV_SET_PARAMS_COMPLETE_EVT: - xSemaphoreGive(test_sem); + periodic_adv_gap_sem_give(); ESP_LOGI(LOG_TAG, "Extended advertising params set, status %d, instance %d", param->ext_adv_set_params.status, param->ext_adv_set_params.instance); break; case ESP_GAP_BLE_EXT_ADV_DATA_SET_COMPLETE_EVT: - xSemaphoreGive(test_sem); + periodic_adv_gap_sem_give(); ESP_LOGI(LOG_TAG, "Extended advertising data set, status %d, instance %d", param->ext_adv_data_set.status, param->ext_adv_data_set.instance); break; case ESP_GAP_BLE_EXT_SCAN_RSP_DATA_SET_COMPLETE_EVT: - xSemaphoreGive(test_sem); + periodic_adv_gap_sem_give(); ESP_LOGI(LOG_TAG, "Extended advertising scan response data set, status %d, instance %d", param->scan_rsp_set.status, param->scan_rsp_set.instance); break; case ESP_GAP_BLE_EXT_ADV_START_COMPLETE_EVT: - xSemaphoreGive(test_sem); - ESP_LOGI(LOG_TAG, "Extended advertising start, status %d, instance numble %d", param->ext_adv_start.status, param->ext_adv_start.instance_num); + periodic_adv_gap_sem_give(); + ESP_LOGI(LOG_TAG, "Extended advertising start, status %d, instance number %d", param->ext_adv_start.status, param->ext_adv_start.instance_num); break; case ESP_GAP_BLE_EXT_ADV_STOP_COMPLETE_EVT: - xSemaphoreGive(test_sem); - ESP_LOGI(LOG_TAG, "Extended advertising start, status %d, instance numble %d", param->ext_adv_stop.status, param->ext_adv_stop.instance_num); + periodic_adv_gap_sem_give(); + ESP_LOGI(LOG_TAG, "Extended advertising stop, status %d, instance number %d", param->ext_adv_stop.status, param->ext_adv_stop.instance_num); break; case ESP_GAP_BLE_PERIODIC_ADV_SET_PARAMS_COMPLETE_EVT: - xSemaphoreGive(test_sem); + periodic_adv_gap_sem_give(); ESP_LOGI(LOG_TAG, "Periodic advertising params set, status %d, instance %d", param->peroid_adv_set_params.status, param->peroid_adv_set_params.instance); break; case ESP_GAP_BLE_PERIODIC_ADV_DATA_SET_COMPLETE_EVT: - xSemaphoreGive(test_sem); + periodic_adv_gap_sem_give(); ESP_LOGI(LOG_TAG, "Periodic advertising data set, status %d, instance %d", param->period_adv_data_set.status, param->period_adv_data_set.instance); break; case ESP_GAP_BLE_PERIODIC_ADV_START_COMPLETE_EVT: - xSemaphoreGive(test_sem); + periodic_adv_gap_sem_give(); ESP_LOGI(LOG_TAG, "Periodic advertising start, status %d, instance %d", param->period_adv_start.status, param->period_adv_start.instance); break; default: @@ -185,9 +193,18 @@ void app_main(void) ESP_LOGE(LOG_TAG, "%s enable bluetooth failed: %s", __func__, esp_err_to_name(ret)); return; } + + test_sem = xSemaphoreCreateBinary(); + if (test_sem == NULL) { + ESP_LOGE(LOG_TAG, "Failed to create semaphore"); + return; + } + ret = esp_ble_gap_register_callback(gap_event_handler); if (ret){ ESP_LOGE(LOG_TAG, "gap register error, error code = %x", ret); + vSemaphoreDelete(test_sem); + test_sem = NULL; return; } @@ -195,8 +212,6 @@ void app_main(void) esp_bd_addr_t rand_addr; esp_ble_gap_addr_create_static(rand_addr); - test_sem = xSemaphoreCreateBinary(); - // 2M phy extend adv, Non-Connectable and Non-Scannable Undirected advertising ESP_LOG_BUFFER_HEX(LOG_TAG, rand_addr, ESP_BD_ADDR_LEN); FUNC_SEND_WAIT_SEM(esp_ble_gap_ext_adv_set_params(EXT_ADV_HANDLE, &ext_adv_params_2M), test_sem); diff --git a/examples/bluetooth/bluedroid/ble_50/periodic_adv/sdkconfig.defaults b/examples/bluetooth/bluedroid/ble_50/periodic_adv/sdkconfig.defaults index d1004c7abde..c2c8c906cd1 100644 --- a/examples/bluetooth/bluedroid/ble_50/periodic_adv/sdkconfig.defaults +++ b/examples/bluetooth/bluedroid/ble_50/periodic_adv/sdkconfig.defaults @@ -2,3 +2,19 @@ # Espressif IoT Development Framework (ESP-IDF) Project Minimal Configuration # CONFIG_BT_ENABLED=y +CONFIG_PARTITION_TABLE_SINGLE_APP_LARGE=y +CONFIG_BT_BLUEDROID_ENABLED=y +CONFIG_BT_BLE_ENABLED=y +CONFIG_BT_BLE_50_FEATURES_SUPPORTED=y +CONFIG_BT_BLE_42_FEATURES_SUPPORTED=n + +# Periodic extended advertiser only (no scan / sync / GATT) +CONFIG_BT_BLE_50_EXTEND_ADV_EN=y +CONFIG_BT_BLE_50_PERIODIC_ADV_EN=y +CONFIG_BT_BLE_50_EXTEND_SCAN_EN=n +CONFIG_BT_BLE_50_EXTEND_SYNC_EN=n + +# CONFIG_BT_GATTS_ENABLE is not set +# CONFIG_BT_GATTC_ENABLE is not set +# CONFIG_BT_BLE_SMP_ENABLE is not set +# CONFIG_BT_BLE_50_DTM_TEST_EN is not set diff --git a/examples/bluetooth/bluedroid/ble_50/periodic_sync/main/periodic_sync_demo.c b/examples/bluetooth/bluedroid/ble_50/periodic_sync/main/periodic_sync_demo.c index 92194f1e96b..f353ccf408c 100644 --- a/examples/bluetooth/bluedroid/ble_50/periodic_sync/main/periodic_sync_demo.c +++ b/examples/bluetooth/bluedroid/ble_50/periodic_sync/main/periodic_sync_demo.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2021-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Unlicense OR CC0-1.0 */ @@ -84,10 +84,13 @@ static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param break; case ESP_GAP_BLE_EXT_SCAN_STOP_COMPLETE_EVT: xSemaphoreGive(test_sem); - ESP_LOGI(LOG_TAG, "Extended scanning stop, status %d", param->period_adv_stop.status); + ESP_LOGI(LOG_TAG, "Extended scanning stop, status %d", param->ext_scan_stop.status); break; case ESP_GAP_BLE_PERIODIC_ADV_CREATE_SYNC_COMPLETE_EVT: ESP_LOGI(LOG_TAG, "Periodic advertising create sync, status %d", param->period_adv_create_sync.status); + if (param->period_adv_create_sync.status != ESP_BT_STATUS_SUCCESS) { + periodic_sync = false; + } break; case ESP_GAP_BLE_PERIODIC_ADV_SYNC_CANCEL_COMPLETE_EVT: ESP_LOGI(LOG_TAG, "Periodic advertising sync cancel, status %d", param->period_adv_sync_cancel.status); @@ -97,9 +100,13 @@ static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param break; case ESP_GAP_BLE_PERIODIC_ADV_SYNC_LOST_EVT: ESP_LOGI(LOG_TAG, "Periodic advertising sync lost, sync handle %d", param->periodic_adv_sync_lost.sync_handle); + periodic_sync = false; break; case ESP_GAP_BLE_PERIODIC_ADV_SYNC_ESTAB_EVT: ESP_LOGI(LOG_TAG, "Periodic advertising sync establish, status %d", param->periodic_adv_sync_estab.status); + if (param->periodic_adv_sync_estab.status != ESP_BT_STATUS_SUCCESS) { + periodic_sync = false; + } ESP_LOGI(LOG_TAG, "address "ESP_BD_ADDR_STR"", ESP_BD_ADDR_HEX(param->periodic_adv_sync_estab.adv_addr)); ESP_LOGI(LOG_TAG, "sync handle %d sid %d perioic adv interval %d adv phy %d", param->periodic_adv_sync_estab.sync_handle, param->periodic_adv_sync_estab.sid, @@ -113,13 +120,16 @@ static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param param->ext_adv_report.params.adv_data_len, ESP_BLE_AD_TYPE_NAME_CMPL, &adv_name_len); - if ((adv_name != NULL) && (memcmp(adv_name, remote_device_name, adv_name_len) == 0) && !periodic_sync) { + const size_t remote_cap = sizeof(remote_device_name); + size_t remote_len = strnlen(remote_device_name, remote_cap); + if ((adv_name != NULL) && (adv_name_len > 0) && + (adv_name_len <= remote_cap) && + (adv_name_len == remote_len) && + (memcmp(adv_name, remote_device_name, adv_name_len) == 0) && !periodic_sync) { // Note: If there are multiple devices with the same device name, the device may sync to an unintended one. // It is recommended to change the default device name to ensure it is unique. periodic_sync = true; - char adv_temp_name[30] = {'0'}; - memcpy(adv_temp_name, adv_name, adv_name_len); - ESP_LOGI(LOG_TAG, "Create sync with the peer device %s", adv_temp_name); + ESP_LOGI(LOG_TAG, "Create sync with the peer device %.*s", (int)adv_name_len, (const char *)adv_name); periodic_adv_sync_params.sid = param->ext_adv_report.params.sid; periodic_adv_sync_params.addr_type = param->ext_adv_report.params.addr_type; memcpy(periodic_adv_sync_params.addr, param->ext_adv_report.params.addr, sizeof(esp_bd_addr_t)); diff --git a/examples/bluetooth/bluedroid/ble_50/periodic_sync/sdkconfig.defaults b/examples/bluetooth/bluedroid/ble_50/periodic_sync/sdkconfig.defaults index d1004c7abde..c54620a9513 100644 --- a/examples/bluetooth/bluedroid/ble_50/periodic_sync/sdkconfig.defaults +++ b/examples/bluetooth/bluedroid/ble_50/periodic_sync/sdkconfig.defaults @@ -2,3 +2,19 @@ # Espressif IoT Development Framework (ESP-IDF) Project Minimal Configuration # CONFIG_BT_ENABLED=y +CONFIG_PARTITION_TABLE_SINGLE_APP_LARGE=y +CONFIG_BT_BLUEDROID_ENABLED=y +CONFIG_BT_BLE_ENABLED=y +CONFIG_BT_BLE_50_FEATURES_SUPPORTED=y +CONFIG_BT_BLE_42_FEATURES_SUPPORTED=n + +# Periodic advertising sync role: extended scan + sync only (no extended adv / no periodic adv in host) +CONFIG_BT_BLE_50_EXTEND_SCAN_EN=y +CONFIG_BT_BLE_50_EXTEND_SYNC_EN=y +# CONFIG_BT_BLE_50_EXTEND_ADV_EN is not set +# CONFIG_BT_BLE_50_PERIODIC_ADV_EN is not set + +# CONFIG_BT_GATTS_ENABLE is not set +# CONFIG_BT_GATTC_ENABLE is not set +# CONFIG_BT_BLE_SMP_ENABLE is not set +# CONFIG_BT_BLE_50_DTM_TEST_EN is not set diff --git a/examples/bluetooth/bluedroid/classic_bt/a2dp_source/main/bt_app_core.c b/examples/bluetooth/bluedroid/classic_bt/a2dp_source/main/bt_app_core.c index 6bdca650096..b3f83bd2434 100644 --- a/examples/bluetooth/bluedroid/classic_bt/a2dp_source/main/bt_app_core.c +++ b/examples/bluetooth/bluedroid/classic_bt/a2dp_source/main/bt_app_core.c @@ -127,7 +127,7 @@ bool bt_app_work_dispatch(bt_app_cb_t p_cback, uint16_t event, void *p_params, i void bt_app_task_start_up(void) { s_bt_app_task_queue = xQueueCreate(10, sizeof(bt_app_msg_t)); - xTaskCreate(bt_app_task_handler, "BtAppTask", 3072, NULL, 10, &s_bt_app_task_handle); + xTaskCreate(bt_app_task_handler, "BtAppTask", 4096, NULL, 10, &s_bt_app_task_handle); } void bt_app_task_shut_down(void) diff --git a/examples/bluetooth/bluedroid/classic_bt/a2dp_source/main/main.c b/examples/bluetooth/bluedroid/classic_bt/a2dp_source/main/main.c index 77cbc2a6b3c..87330fdb9b1 100644 --- a/examples/bluetooth/bluedroid/classic_bt/a2dp_source/main/main.c +++ b/examples/bluetooth/bluedroid/classic_bt/a2dp_source/main/main.c @@ -325,10 +325,6 @@ static void bt_av_hdl_stack_evt(uint16_t event, void *p_param) esp_avrc_ct_init(); esp_avrc_ct_register_callback(bt_app_rc_ct_cb); - esp_avrc_rn_evt_cap_mask_t evt_set = {0}; - esp_avrc_rn_evt_bit_mask_operation(ESP_AVRC_BIT_MASK_OP_SET, &evt_set, ESP_AVRC_RN_VOLUME_CHANGE); - ESP_ERROR_CHECK(esp_avrc_tg_set_rn_evt_cap(&evt_set)); - esp_a2d_source_init(); esp_a2d_register_callback(&bt_app_a2d_cb); esp_a2d_source_register_data_callback(bt_app_a2d_data_cb); diff --git a/examples/bluetooth/bluedroid/classic_bt/bt_l2cap_client/main/bt_app_core.c b/examples/bluetooth/bluedroid/classic_bt/bt_l2cap_client/main/bt_app_core.c index 4080389df0c..2ce4eff3df6 100644 --- a/examples/bluetooth/bluedroid/classic_bt/bt_l2cap_client/main/bt_app_core.c +++ b/examples/bluetooth/bluedroid/classic_bt/bt_l2cap_client/main/bt_app_core.c @@ -129,7 +129,7 @@ bool bt_app_work_dispatch(bt_app_cb_t p_cback, uint16_t event, void *p_params, i void bt_app_task_start_up(void) { s_bt_app_task_queue = xQueueCreate(10, sizeof(bt_app_msg_t)); - xTaskCreate(bt_app_task_handler, "BtAppTask", 3072, NULL, configMAX_PRIORITIES - 3, &s_bt_app_task_handle); + xTaskCreate(bt_app_task_handler, "BtAppTask", 4096, NULL, configMAX_PRIORITIES - 3, &s_bt_app_task_handle); } void bt_app_task_shut_down(void) diff --git a/examples/bluetooth/bluedroid/classic_bt/bt_l2cap_server/main/bt_app_core.c b/examples/bluetooth/bluedroid/classic_bt/bt_l2cap_server/main/bt_app_core.c index 4080389df0c..2ce4eff3df6 100644 --- a/examples/bluetooth/bluedroid/classic_bt/bt_l2cap_server/main/bt_app_core.c +++ b/examples/bluetooth/bluedroid/classic_bt/bt_l2cap_server/main/bt_app_core.c @@ -129,7 +129,7 @@ bool bt_app_work_dispatch(bt_app_cb_t p_cback, uint16_t event, void *p_params, i void bt_app_task_start_up(void) { s_bt_app_task_queue = xQueueCreate(10, sizeof(bt_app_msg_t)); - xTaskCreate(bt_app_task_handler, "BtAppTask", 3072, NULL, configMAX_PRIORITIES - 3, &s_bt_app_task_handle); + xTaskCreate(bt_app_task_handler, "BtAppTask", 4096, NULL, configMAX_PRIORITIES - 3, &s_bt_app_task_handle); } void bt_app_task_shut_down(void) diff --git a/examples/bluetooth/bluedroid/classic_bt/common/bt_app_core_utils/bt_app_core_utils.c b/examples/bluetooth/bluedroid/classic_bt/common/bt_app_core_utils/bt_app_core_utils.c index 3141ce04c95..ae9e01cac2a 100644 --- a/examples/bluetooth/bluedroid/classic_bt/common/bt_app_core_utils/bt_app_core_utils.c +++ b/examples/bluetooth/bluedroid/classic_bt/common/bt_app_core_utils/bt_app_core_utils.c @@ -134,7 +134,7 @@ bool bt_app_work_dispatch(bt_app_cb_t p_cback, uint16_t event, void *p_params, i void bt_app_task_start_up(void) { s_bt_app_task_queue = xQueueCreate(10, sizeof(bt_app_msg_t)); - xTaskCreate(bt_app_task_handler, "BtAppTask", 3072, NULL, 10, &s_bt_app_task_handle); + xTaskCreate(bt_app_task_handler, "BtAppTask", 4096, NULL, 10, &s_bt_app_task_handle); } void bt_app_task_shut_down(void) diff --git a/examples/bluetooth/bluedroid/classic_bt/hfp_ag/sdkconfig.ci.extcodec b/examples/bluetooth/bluedroid/classic_bt/hfp_ag/sdkconfig.ci.extcodec index 761b29c1457..419df6054de 100644 --- a/examples/bluetooth/bluedroid/classic_bt/hfp_ag/sdkconfig.ci.extcodec +++ b/examples/bluetooth/bluedroid/classic_bt/hfp_ag/sdkconfig.ci.extcodec @@ -11,3 +11,4 @@ CONFIG_BT_HFP_AG_ENABLE=y CONFIG_BT_HFP_AUDIO_DATA_PATH_HCI=y CONFIG_BT_HFP_USE_EXTERNAL_CODEC=y CONFIG_EXAMPLE_ENABLE_CONSOLE_REPL=n +CONFIG_EXAMPLE_LOCAL_DEVICE_NAME="${CI_PIPELINE_ID}_HFP" diff --git a/examples/bluetooth/bluedroid/classic_bt/hfp_hf/sdkconfig.ci.extcodec b/examples/bluetooth/bluedroid/classic_bt/hfp_hf/sdkconfig.ci.extcodec index 62e13eedb8c..7251c972b92 100644 --- a/examples/bluetooth/bluedroid/classic_bt/hfp_hf/sdkconfig.ci.extcodec +++ b/examples/bluetooth/bluedroid/classic_bt/hfp_hf/sdkconfig.ci.extcodec @@ -11,3 +11,4 @@ CONFIG_BT_HFP_CLIENT_ENABLE=y CONFIG_BT_HFP_AUDIO_DATA_PATH_HCI=y CONFIG_BT_HFP_USE_EXTERNAL_CODEC=y CONFIG_EXAMPLE_ENABLE_CONSOLE_REPL=n +CONFIG_EXAMPLE_PEER_DEVICE_NAME="${CI_PIPELINE_ID}_HFP" 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 e5988c921ad..75cddcbcc29 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-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2021-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Unlicense OR CC0-1.0 */ @@ -81,6 +81,15 @@ typedef struct { static prepare_type_env_t a_prepare_write_env; static prepare_type_env_t b_prepare_write_env; +static void prepare_write_env_free(prepare_type_env_t *env) +{ + if (env->prepare_buf != NULL) { + free(env->prepare_buf); + env->prepare_buf = NULL; + } + env->prepare_len = 0; +} + //Declare the static function static void gatts_profile_a_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param); static void gatts_profile_b_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param); @@ -176,10 +185,21 @@ static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param { switch (event) { case ESP_GAP_BLE_ADV_DATA_RAW_SET_COMPLETE_EVT: - //esp_ble_gap_start_advertising(&adv_params); + break; + case ESP_GAP_BLE_SET_LOCAL_PRIVACY_COMPLETE_EVT: + /* Configure adv data only after local privacy is set up (REG_EVT requests it). */ + if (param->local_privacy_cmpl.status != ESP_BT_STATUS_SUCCESS) { + ESP_LOGE(BT_BLE_COEX_TAG, "set local privacy failed, status %d", param->local_privacy_cmpl.status); + } else { + ble_init_adv_data(BLE_ADV_NAME); + } break; case ESP_GAP_BLE_SCAN_RSP_DATA_RAW_SET_COMPLETE_EVT: - esp_ble_gap_start_advertising(&adv_params); + if (param->scan_rsp_data_raw_cmpl.status != ESP_BT_STATUS_SUCCESS) { + ESP_LOGE(BT_BLE_COEX_TAG, "set raw scan rsp data failed, status %d", param->scan_rsp_data_raw_cmpl.status); + } else { + esp_ble_gap_start_advertising(&adv_params); + } break; case ESP_GAP_BLE_ADV_START_COMPLETE_EVT: //advertising start complete event to indicate advertising start successfully or failed @@ -265,11 +285,69 @@ void example_exec_write_event_env(prepare_type_env_t *prepare_write_env, esp_ble } else { ESP_LOGI(BT_BLE_COEX_TAG, "ESP_GATT_PREP_WRITE_CANCEL"); } - if (prepare_write_env->prepare_buf) { - free(prepare_write_env->prepare_buf); - prepare_write_env->prepare_buf = NULL; + prepare_write_env_free(prepare_write_env); +} + +/* Profile A and B handle READ/WRITE identically (apart from per-profile state); these helpers + * avoid the copy-pasted bodies that existed in the previous version. */ +static void gatts_coex_read_evt(esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param) +{ + ESP_LOGI(BT_BLE_COEX_TAG, "GATT_READ_EVT, conn_id %d, trans_id %"PRIu32", handle %d", + param->read.conn_id, param->read.trans_id, param->read.handle); + esp_gatt_rsp_t rsp; + memset(&rsp, 0, sizeof(esp_gatt_rsp_t)); + rsp.attr_value.handle = param->read.handle; + rsp.attr_value.len = 4; + rsp.attr_value.value[0] = 0xde; + rsp.attr_value.value[1] = 0xed; + rsp.attr_value.value[2] = 0xbe; + rsp.attr_value.value[3] = 0xef; + esp_ble_gatts_send_response(gatts_if, param->read.conn_id, param->read.trans_id, + ESP_GATT_OK, &rsp); +} + +static void gatts_coex_write_evt(esp_gatt_if_t gatts_if, int profile_idx, prepare_type_env_t *prep_env, + esp_gatt_char_prop_t char_prop, esp_ble_gatts_cb_param_t *param) +{ + ESP_LOGI(BT_BLE_COEX_TAG, "GATT_WRITE_EVT, conn_id %d, trans_id %"PRIu32", handle %d", + param->write.conn_id, param->write.trans_id, param->write.handle); + + if (!param->write.is_prep) { + ESP_LOGI(BT_BLE_COEX_TAG, "GATT_WRITE_EVT, value len %d, value :", param->write.len); + ESP_LOG_BUFFER_HEX(BT_BLE_COEX_TAG, param->write.value, param->write.len); + if (gl_profile_tab[profile_idx].descr_handle == param->write.handle && param->write.len == 2) { + uint16_t descr_value = param->write.value[1] << 8 | param->write.value[0]; + if (descr_value == 0x0001) { + if (char_prop & ESP_GATT_CHAR_PROP_BIT_NOTIFY) { + ESP_LOGI(BT_BLE_COEX_TAG, "notify enable"); + uint8_t notify_data[15]; + for (int i = 0; i < sizeof(notify_data); ++i) { + notify_data[i] = i % 0xff; + } + //the size of notify_data[] need less than MTU size + esp_ble_gatts_send_indicate(gatts_if, param->write.conn_id, gl_profile_tab[profile_idx].char_handle, + sizeof(notify_data), notify_data, false); + } + } else if (descr_value == 0x0002) { + if (char_prop & ESP_GATT_CHAR_PROP_BIT_INDICATE) { + ESP_LOGI(BT_BLE_COEX_TAG, "indicate enable"); + uint8_t indicate_data[15]; + for (int i = 0; i < sizeof(indicate_data); ++i) { + indicate_data[i] = i % 0xff; + } + //the size of indicate_data[] need less than MTU size + esp_ble_gatts_send_indicate(gatts_if, param->write.conn_id, gl_profile_tab[profile_idx].char_handle, + sizeof(indicate_data), indicate_data, true); + } + } else if (descr_value == 0x0000) { + ESP_LOGI(BT_BLE_COEX_TAG, "notify/indicate disable "); + } else { + ESP_LOGE(BT_BLE_COEX_TAG, "unknown descr value"); + ESP_LOG_BUFFER_HEX(BT_BLE_COEX_TAG, param->write.value, param->write.len); + } + } } - prepare_write_env->prepare_len = 0; + example_write_event_env(gatts_if, prep_env, param); } static void gatts_profile_a_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param) @@ -282,68 +360,20 @@ static void gatts_profile_a_event_handler(esp_gatts_cb_event_t event, esp_gatt_i gl_profile_tab[PROFILE_A_APP_ID].service_id.id.inst_id = 0x00; gl_profile_tab[PROFILE_A_APP_ID].service_id.id.uuid.len = ESP_UUID_LEN_16; gl_profile_tab[PROFILE_A_APP_ID].service_id.id.uuid.uuid.uuid16 = GATTS_SERVICE_UUID_A; - //init BLE adv data and scan response data - ble_init_adv_data(BLE_ADV_NAME); + /* Adv raw data is set from gap_event_handler after ESP_GAP_BLE_SET_LOCAL_PRIVACY_COMPLETE_EVT. */ esp_ble_gatts_create_service(gatts_if, &gl_profile_tab[PROFILE_A_APP_ID].service_id, GATTS_NUM_HANDLE_A); break; - case ESP_GATTS_READ_EVT: { - ESP_LOGI(BT_BLE_COEX_TAG, "GATT_READ_EVT, conn_id %d, trans_id %"PRIu32", handle %d", param->read.conn_id, param->read.trans_id, param->read.handle); - esp_gatt_rsp_t rsp; - memset(&rsp, 0, sizeof(esp_gatt_rsp_t)); - rsp.attr_value.handle = param->read.handle; - rsp.attr_value.len = 4; - rsp.attr_value.value[0] = 0xde; - rsp.attr_value.value[1] = 0xed; - rsp.attr_value.value[2] = 0xbe; - rsp.attr_value.value[3] = 0xef; - esp_ble_gatts_send_response(gatts_if, param->read.conn_id, param->read.trans_id, - ESP_GATT_OK, &rsp); + case ESP_GATTS_READ_EVT: + gatts_coex_read_evt(gatts_if, param); break; - } - case ESP_GATTS_WRITE_EVT: { - ESP_LOGI(BT_BLE_COEX_TAG, "GATT_WRITE_EVT, conn_id %d, trans_id %"PRIu32", handle %d", param->write.conn_id, param->write.trans_id, param->write.handle); - if (!param->write.is_prep) { - ESP_LOGI(BT_BLE_COEX_TAG, "GATT_WRITE_EVT, value len %d, value :", param->write.len); - ESP_LOG_BUFFER_HEX(BT_BLE_COEX_TAG, param->write.value, param->write.len); - if (gl_profile_tab[PROFILE_A_APP_ID].descr_handle == param->write.handle && param->write.len == 2) { - uint16_t descr_value = param->write.value[1] << 8 | param->write.value[0]; - if (descr_value == 0x0001) { - if (a_property & ESP_GATT_CHAR_PROP_BIT_NOTIFY) { - ESP_LOGI(BT_BLE_COEX_TAG, "notify enable"); - uint8_t notify_data[15]; - for (int i = 0; i < sizeof(notify_data); ++i) { - notify_data[i] = i % 0xff; - } - //the size of notify_data[] need less than MTU size - esp_ble_gatts_send_indicate(gatts_if, param->write.conn_id, gl_profile_tab[PROFILE_A_APP_ID].char_handle, - sizeof(notify_data), notify_data, false); - } - } else if (descr_value == 0x0002) { - if (a_property & ESP_GATT_CHAR_PROP_BIT_INDICATE) { - ESP_LOGI(BT_BLE_COEX_TAG, "indicate enable"); - uint8_t indicate_data[15]; - for (int i = 0; i < sizeof(indicate_data); ++i) { - indicate_data[i] = i % 0xff; - } - //the size of indicate_data[] need less than MTU size - esp_ble_gatts_send_indicate(gatts_if, param->write.conn_id, gl_profile_tab[PROFILE_A_APP_ID].char_handle, - sizeof(indicate_data), indicate_data, true); - } - } else if (descr_value == 0x0000) { - ESP_LOGI(BT_BLE_COEX_TAG, "notify/indicate disable "); - } else { - ESP_LOGE(BT_BLE_COEX_TAG, "unknown descr value"); - ESP_LOG_BUFFER_HEX(BT_BLE_COEX_TAG, param->write.value, param->write.len); - } - - } - } - example_write_event_env(gatts_if, &a_prepare_write_env, param); + case ESP_GATTS_WRITE_EVT: + gatts_coex_write_evt(gatts_if, PROFILE_A_APP_ID, &a_prepare_write_env, a_property, param); break; - } case ESP_GATTS_EXEC_WRITE_EVT: ESP_LOGI(BT_BLE_COEX_TAG, "ESP_GATTS_EXEC_WRITE_EVT"); - esp_ble_gatts_send_response(gatts_if, param->write.conn_id, param->write.trans_id, ESP_GATT_OK, NULL); + /* Use exec_write union member, not write — these share offsets in the union but it + * was a latent bug in master to refer to param->write here. */ + esp_ble_gatts_send_response(gatts_if, param->exec_write.conn_id, param->exec_write.trans_id, ESP_GATT_OK, NULL); example_exec_write_event_env(&a_prepare_write_env, param); break; case ESP_GATTS_MTU_EVT: @@ -395,13 +425,11 @@ static void gatts_profile_a_event_handler(esp_gatts_cb_event_t event, esp_gatt_i break; case ESP_GATTS_STOP_EVT: break; - case ESP_GATTS_CONNECT_EVT: { - esp_ble_conn_update_params_t conn_params = {0}; - memcpy(conn_params.bda, param->connect.remote_bda, sizeof(esp_bd_addr_t)); + case ESP_GATTS_CONNECT_EVT: break; - } case ESP_GATTS_DISCONNECT_EVT: ESP_LOGI(BT_BLE_COEX_TAG, "ESP_GATTS_DISCONNECT_EVT"); + prepare_write_env_free(&a_prepare_write_env); esp_ble_gap_start_advertising(&adv_params); break; case ESP_GATTS_CONF_EVT: @@ -432,63 +460,15 @@ static void gatts_profile_b_event_handler(esp_gatts_cb_event_t event, esp_gatt_i esp_ble_gatts_create_service(gatts_if, &gl_profile_tab[PROFILE_B_APP_ID].service_id, GATTS_NUM_HANDLE_B); break; - case ESP_GATTS_READ_EVT: { - ESP_LOGI(BT_BLE_COEX_TAG, "GATT_READ_EVT, conn_id %d, trans_id %"PRIu32", handle %d", param->read.conn_id, param->read.trans_id, param->read.handle); - esp_gatt_rsp_t rsp; - memset(&rsp, 0, sizeof(esp_gatt_rsp_t)); - rsp.attr_value.handle = param->read.handle; - rsp.attr_value.len = 4; - rsp.attr_value.value[0] = 0xde; - rsp.attr_value.value[1] = 0xed; - rsp.attr_value.value[2] = 0xbe; - rsp.attr_value.value[3] = 0xef; - esp_ble_gatts_send_response(gatts_if, param->read.conn_id, param->read.trans_id, - ESP_GATT_OK, &rsp); + case ESP_GATTS_READ_EVT: + gatts_coex_read_evt(gatts_if, param); break; - } - case ESP_GATTS_WRITE_EVT: { - ESP_LOGI(BT_BLE_COEX_TAG, "GATT_WRITE_EVT, conn_id %d, trans_id %"PRIu32", handle %d", param->write.conn_id, param->write.trans_id, param->write.handle); - if (!param->write.is_prep) { - ESP_LOGI(BT_BLE_COEX_TAG, "GATT_WRITE_EVT, value len %d, value :", param->write.len); - ESP_LOG_BUFFER_HEX(BT_BLE_COEX_TAG, param->write.value, param->write.len); - if (gl_profile_tab[PROFILE_B_APP_ID].descr_handle == param->write.handle && param->write.len == 2) { - uint16_t descr_value = param->write.value[1] << 8 | param->write.value[0]; - if (descr_value == 0x0001) { - if (b_property & ESP_GATT_CHAR_PROP_BIT_NOTIFY) { - ESP_LOGI(BT_BLE_COEX_TAG, "notify enable"); - uint8_t notify_data[15]; - for (int i = 0; i < sizeof(notify_data); ++i) { - notify_data[i] = i % 0xff; - } - //the size of notify_data[] need less than MTU size - esp_ble_gatts_send_indicate(gatts_if, param->write.conn_id, gl_profile_tab[PROFILE_B_APP_ID].char_handle, - sizeof(notify_data), notify_data, false); - } - } else if (descr_value == 0x0002) { - if (b_property & ESP_GATT_CHAR_PROP_BIT_INDICATE) { - ESP_LOGI(BT_BLE_COEX_TAG, "indicate enable"); - uint8_t indicate_data[15]; - for (int i = 0; i < sizeof(indicate_data); ++i) { - indicate_data[i] = i % 0xff; - } - //the size of indicate_data[] need less than MTU size - esp_ble_gatts_send_indicate(gatts_if, param->write.conn_id, gl_profile_tab[PROFILE_B_APP_ID].char_handle, - sizeof(indicate_data), indicate_data, true); - } - } else if (descr_value == 0x0000) { - ESP_LOGI(BT_BLE_COEX_TAG, "notify/indicate disable "); - } else { - ESP_LOGE(BT_BLE_COEX_TAG, "unknown value"); - } - - } - } - example_write_event_env(gatts_if, &b_prepare_write_env, param); + case ESP_GATTS_WRITE_EVT: + gatts_coex_write_evt(gatts_if, PROFILE_B_APP_ID, &b_prepare_write_env, b_property, param); break; - } case ESP_GATTS_EXEC_WRITE_EVT: ESP_LOGI(BT_BLE_COEX_TAG, "ESP_GATTS_EXEC_WRITE_EVT"); - esp_ble_gatts_send_response(gatts_if, param->write.conn_id, param->write.trans_id, ESP_GATT_OK, NULL); + esp_ble_gatts_send_response(gatts_if, param->exec_write.conn_id, param->exec_write.trans_id, ESP_GATT_OK, NULL); example_exec_write_event_env(&b_prepare_write_env, param); break; case ESP_GATTS_MTU_EVT: @@ -552,6 +532,9 @@ static void gatts_profile_b_event_handler(esp_gatts_cb_event_t event, esp_gatt_i } break; case ESP_GATTS_DISCONNECT_EVT: + ESP_LOGI(BT_BLE_COEX_TAG, "ESP_GATTS_DISCONNECT_EVT (profile B)"); + prepare_write_env_free(&b_prepare_write_env); + break; case ESP_GATTS_OPEN_EVT: case ESP_GATTS_CANCEL_OPEN_EVT: case ESP_GATTS_CLOSE_EVT: diff --git a/examples/bluetooth/bluedroid/coex/gattc_gatts_coex/main/gattc_gatts_coex.c b/examples/bluetooth/bluedroid/coex/gattc_gatts_coex/main/gattc_gatts_coex.c index f8257e4b6d9..f376930816d 100644 --- a/examples/bluetooth/bluedroid/coex/gattc_gatts_coex/main/gattc_gatts_coex.c +++ b/examples/bluetooth/bluedroid/coex/gattc_gatts_coex/main/gattc_gatts_coex.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2021-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2021-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Unlicense OR CC0-1.0 */ @@ -105,6 +105,15 @@ static esp_gatt_char_prop_t b_property = 0; static prepare_type_env_t a_prepare_write_env; static prepare_type_env_t b_prepare_write_env; static uint8_t adv_config_done = 0; + +static void prepare_write_env_free(prepare_type_env_t *env) +{ + if (env->prepare_buf != NULL) { + free(env->prepare_buf); + env->prepare_buf = NULL; + } + env->prepare_len = 0; +} static uint8_t char1_str[] = {0x11, 0x22, 0x33}; static bool connect = false; static bool get_server = false; @@ -293,11 +302,17 @@ static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param case ESP_GAP_BLE_SCAN_RESULT_EVT: { esp_ble_gap_cb_param_t *scan_result = (esp_ble_gap_cb_param_t *)param; switch (scan_result->scan_rst.search_evt) { - case ESP_GAP_SEARCH_INQ_RES_EVT: + case ESP_GAP_SEARCH_INQ_RES_EVT: { + const uint16_t ble_adv_storage_max = ESP_BLE_ADV_DATA_LEN_MAX + ESP_BLE_SCAN_RSP_DATA_LEN_MAX; + uint32_t combined_len = (uint32_t)scan_result->scan_rst.adv_data_len + + (uint32_t)scan_result->scan_rst.scan_rsp_len; + uint16_t resolve_len = (combined_len > ble_adv_storage_max) + ? ble_adv_storage_max + : (uint16_t)combined_len; adv_name = esp_ble_resolve_adv_data_by_type(scan_result->scan_rst.ble_adv, - scan_result->scan_rst.adv_data_len + scan_result->scan_rst.scan_rsp_len, - ESP_BLE_AD_TYPE_NAME_CMPL, - &adv_name_len); + resolve_len, + ESP_BLE_AD_TYPE_NAME_CMPL, + &adv_name_len); if (adv_name != NULL) { if (strlen(remote_device_name) == adv_name_len && strncmp((char *)adv_name, remote_device_name, adv_name_len) == 0) { if (connect == false) { @@ -324,6 +339,7 @@ static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param } } break; + } case ESP_GAP_SEARCH_INQ_CMPL_EVT: ESP_LOGI(COEX_TAG, "ESP_GAP_SEARCH_INQ_CMPL_EVT, scan stop"); break; @@ -563,6 +579,7 @@ static void gattc_profile_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_ // Update connect flag and get_server flag if peer device is a gatt server connect = false; get_server = false; + gattc_profile_tab[GATTC_PROFILE_C_APP_ID].conn_id = UINT16_MAX; } ESP_LOGI(COEX_TAG, "ESP_GATTC_DISCONNECT_EVT, reason = %d", p_data->disconnect.reason); break; @@ -577,9 +594,11 @@ static void example_write_event_env(esp_gatt_if_t gatts_if, prepare_type_env_t * esp_gatt_status_t status = ESP_GATT_OK; if (param->write.need_rsp) { if (param->write.is_prep) { - if (param->write.offset > PREPARE_BUF_MAX_SIZE) { + size_t w_off = param->write.offset; + size_t w_len = param->write.len; + if (w_off > PREPARE_BUF_MAX_SIZE) { status = ESP_GATT_INVALID_OFFSET; - } else if ((param->write.offset + param->write.len) > PREPARE_BUF_MAX_SIZE) { + } else if (w_len > ESP_GATT_MAX_ATTR_LEN || (w_off + w_len) > PREPARE_BUF_MAX_SIZE) { status = ESP_GATT_INVALID_ATTR_LEN; } if (status == ESP_GATT_OK && prepare_write_env->prepare_buf == NULL) { @@ -591,13 +610,19 @@ static void example_write_event_env(esp_gatt_if_t gatts_if, prepare_type_env_t * } } - esp_gatt_rsp_t *gatt_rsp = (esp_gatt_rsp_t *)malloc(sizeof(esp_gatt_rsp_t)); + 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; gatt_rsp->attr_value.offset = param->write.offset; gatt_rsp->attr_value.auth_req = ESP_GATT_AUTH_REQ_NONE; - memcpy(gatt_rsp->attr_value.value, param->write.value, param->write.len); + if (status == ESP_GATT_OK) { + if (param->write.value == NULL) { + status = ESP_GATT_INVALID_ATTR_LEN; + } else { + gatt_rsp->attr_value.len = param->write.len; + memcpy(gatt_rsp->attr_value.value, param->write.value, param->write.len); + } + } esp_err_t response_err = esp_ble_gatts_send_response(gatts_if, param->write.conn_id, param->write.trans_id, status, gatt_rsp); if (response_err != ESP_OK) { ESP_LOGE(COEX_TAG, "Send response error\n"); @@ -605,15 +630,29 @@ static void example_write_event_env(esp_gatt_if_t gatts_if, prepare_type_env_t * free(gatt_rsp); } else { ESP_LOGE(COEX_TAG, "%s, malloc failed", __func__); - status = ESP_GATT_NO_RESOURCES; + if (status == ESP_GATT_OK) { + status = ESP_GATT_NO_RESOURCES; + } + esp_err_t response_err = esp_ble_gatts_send_response(gatts_if, param->write.conn_id, + param->write.trans_id, status, NULL); + if (response_err != ESP_OK) { + ESP_LOGE(COEX_TAG, "Send response error\n"); + } } if (status != ESP_GATT_OK) { return; } - memcpy(prepare_write_env->prepare_buf + param->write.offset, + memcpy(prepare_write_env->prepare_buf + w_off, param->write.value, - param->write.len); - prepare_write_env->prepare_len += param->write.len; + w_len); + /* High-water end of written range (not sum of chunk lengths). */ + int chunk_end = (int)(w_off + w_len); + if (chunk_end > prepare_write_env->prepare_len) { + prepare_write_env->prepare_len = chunk_end; + } + if (prepare_write_env->prepare_len > PREPARE_BUF_MAX_SIZE) { + prepare_write_env->prepare_len = PREPARE_BUF_MAX_SIZE; + } } else { esp_ble_gatts_send_response(gatts_if, param->write.conn_id, param->write.trans_id, status, NULL); @@ -624,15 +663,17 @@ static void example_write_event_env(esp_gatt_if_t gatts_if, prepare_type_env_t * static void example_exec_write_event_env(prepare_type_env_t *prepare_write_env, esp_ble_gatts_cb_param_t *param) { if (param->exec_write.exec_write_flag == ESP_GATT_PREP_WRITE_EXEC) { - ESP_LOG_BUFFER_HEX(COEX_TAG, prepare_write_env->prepare_buf, prepare_write_env->prepare_len); + if (prepare_write_env->prepare_buf != NULL && prepare_write_env->prepare_len > 0) { + size_t log_len = (size_t)prepare_write_env->prepare_len; + if (log_len > PREPARE_BUF_MAX_SIZE) { + log_len = PREPARE_BUF_MAX_SIZE; + } + ESP_LOG_BUFFER_HEX(COEX_TAG, prepare_write_env->prepare_buf, log_len); + } } else { ESP_LOGI(COEX_TAG, "ESP_GATT_PREP_WRITE_CANCEL"); } - if (prepare_write_env->prepare_buf) { - free(prepare_write_env->prepare_buf); - prepare_write_env->prepare_buf = NULL; - } - prepare_write_env->prepare_len = 0; + prepare_write_env_free(prepare_write_env); } static void gatts_profile_a_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param) @@ -793,6 +834,7 @@ static void gatts_profile_a_event_handler(esp_gatts_cb_event_t event, esp_gatt_i } case ESP_GATTS_DISCONNECT_EVT: ESP_LOGI(COEX_TAG, "ESP_GATTS_DISCONNECT_EVT, disconnect reason 0x%x", param->disconnect.reason); + prepare_write_env_free(&a_prepare_write_env); if (memcmp(peer_gatts_addr, param->disconnect.remote_bda, sizeof(esp_bd_addr_t))) { // If the peer device is a GATT client, restart advertising esp_ble_gap_start_advertising(&adv_params); @@ -800,7 +842,7 @@ static void gatts_profile_a_event_handler(esp_gatts_cb_event_t event, esp_gatt_i break; case ESP_GATTS_CONF_EVT: ESP_LOGI(COEX_TAG, "ESP_GATTS_CONF_EVT, status %d attr_handle %d", param->conf.status, param->conf.handle); - if (param->conf.status != ESP_GATT_OK) { + if (param->conf.status == ESP_GATT_OK && param->conf.value != NULL && param->conf.len > 0) { ESP_LOG_BUFFER_HEX(COEX_TAG, param->conf.value, param->conf.len); } break; @@ -941,11 +983,14 @@ static void gatts_profile_b_event_handler(esp_gatts_cb_event_t event, esp_gatt_i break; case ESP_GATTS_CONF_EVT: ESP_LOGI(COEX_TAG, "ESP_GATTS_CONF_EVT status %d attr_handle %d", param->conf.status, param->conf.handle); - if (param->conf.status != ESP_GATT_OK) { + if (param->conf.status == ESP_GATT_OK && param->conf.value != NULL && param->conf.len > 0) { ESP_LOG_BUFFER_HEX(COEX_TAG, param->conf.value, param->conf.len); } break; case ESP_GATTS_DISCONNECT_EVT: + ESP_LOGI(COEX_TAG, "ESP_GATTS_DISCONNECT_EVT, disconnect reason 0x%x", param->disconnect.reason); + prepare_write_env_free(&b_prepare_write_env); + break; case ESP_GATTS_OPEN_EVT: default: break; diff --git a/examples/bluetooth/common/ble_uart/Kconfig b/examples/bluetooth/common/ble_uart/Kconfig index 433d629fcc9..276e7bdd90a 100644 --- a/examples/bluetooth/common/ble_uart/Kconfig +++ b/examples/bluetooth/common/ble_uart/Kconfig @@ -1,4 +1,4 @@ -menu "BLE UART library" +menu "ESP-BLE-UART library" config BLE_UART_DEVICE_NAME_PREFIX string "BLE device name prefix" diff --git a/examples/bluetooth/common/ble_uart/PORTING.md b/examples/bluetooth/common/ble_uart/PORTING.md index 1d3cbe8a9c2..bea60be19bc 100644 --- a/examples/bluetooth/common/ble_uart/PORTING.md +++ b/examples/bluetooth/common/ble_uart/PORTING.md @@ -1,4 +1,6 @@ -# BLE UART Porting & API Guide +# ESP-BLE-UART Porting & API Guide + +> **Naming convention:** Use **ESP-BLE-UART** for Espressif-owned product names (Bridge, Console, Daemon, Echo Server, the `ble_uart` component, and the `ble_uart_service` example). Use **BLE UART** for the generic GATT service convention, transport layer, and compatible third-party devices. This follows the same pattern as ESP-BLE-MESH. This document lives in **`examples/bluetooth/common/ble_uart/`** next to the `ble_uart` component sources (`ble_uart.h`, backend `.c` files). @@ -6,9 +8,10 @@ This document lives in **`examples/bluetooth/common/ble_uart/`** next to the **Reference application:** use the **`examples/bluetooth/ble_uart_service`** example as the working template. Its root `CMakeLists.txt` appends this directory to **`EXTRA_COMPONENT_DIRS`** so `main` can `REQUIRES ble_uart`; -`main/main.c` initializes NVS and a MAC-derived GAP name, calls -`ble_uart_install()` / `ble_uart_open()` with the default encrypted UART-over-BLE echo -path, and the tree ships `sdkconfig.defaults` plus the Bluedroid overlay +`main/main.c` initializes NVS, calls `ble_uart_install()` / +`ble_uart_open()` with the Kconfig-supplied GAP name and the default +encrypted UART-over-BLE echo path, and the tree ships +`sdkconfig.defaults` plus the Bluedroid overlay (`sdkconfig.bluedroid`). Clone or diff that project when adapting to a new target or host stack. @@ -59,8 +62,8 @@ is entirely up to you**. Canonical sources live under **`$IDF_PATH/examples/bluetooth/common/ble_uart/`** (component name `ble_uart`): `ble_uart.h`, `ble_uart_nimble.c`, -`ble_uart_bluedroid.c`, `CMakeLists.txt`, and `Kconfig` (prefix + RX scratch; -`menuconfig → Component configuration → BLE UART library`). When reusing +`ble_uart_bluedroid.c`, `CMakeLists.txt`, and `Kconfig` (device name + RX scratch; +`menuconfig → Component configuration → ESP-BLE-UART library`). When reusing outside this tree, copy the whole `common/ble_uart/` directory or at least merge `Kconfig` into your component so the same `CONFIG_BLE_UART_*` symbols exist. @@ -73,7 +76,7 @@ then use `REQUIRES ble_uart` from `main/CMakeLists.txt` (see `ble_uart` target exists when CMake expands `main`'s requirements. Kconfig options appear under -`menuconfig → Component configuration → BLE UART library`. +`menuconfig → Component configuration → ESP-BLE-UART library`. > A `main/idf_component.yml` path dependency alone is **not** sufficient if > `main/CMakeLists.txt` lists `REQUIRES ble_uart`: the early requirement scan @@ -212,7 +215,7 @@ void app_main(void) } ESP_ERROR_CHECK(err); - /* 2. Bring up BLE UART */ + /* 2. Bring up ESP-BLE-UART */ ESP_ERROR_CHECK(ble_uart_install(&(ble_uart_config_t){ .encrypted = true, .device_name = "MyDevice", @@ -262,17 +265,32 @@ back. ```c typedef struct { - bool encrypted; /* Master switch for SC + Bonding + MITM */ - const char *device_name; /* GAP device name; NULL uses the NimBLE default */ - ble_uart_rx_cb_t ble_uart_on_rx;/* RX byte callback */ + bool encrypted; /* Preset shortcut for SC + Bonding + MITM */ + ble_uart_security_t security; /* Per-feature overrides — see §5.6 */ + + const char *device_name; /* GAP service device name (UUID 0x2A00) */ + + /* Custom advertising bytes — see §5.9. NULL keeps the default + * payload. ble_uart prepends the 3-byte Flags AD itself; you don't. */ + const uint8_t *adv_data; + size_t adv_data_len; /* ≤ BLE_UART_ADV_DATA_MAX (28) */ + const uint8_t *scan_rsp_data; + size_t scan_rsp_data_len;/* ≤ BLE_UART_SCAN_RSP_DATA_MAX (31) */ + + ble_uart_rx_cb_t ble_uart_on_rx;/* RX byte callback */ + ble_uart_evt_cb_t on_event; /* Lifecycle / link-state events */ } ble_uart_config_t; ``` | Field | Type | Required | Default / meaning | | --- | --- | --- | --- | -| `encrypted` | `bool` | yes | `true` = SC + Bonding + MITM + DisplayOnly + encrypted GATT chars; `false` = fully plaintext (sniffable, lab use only) | -| `device_name` | `const char *` | recommended | Any string. Mind the 31-byte primary advertising packet limit: flags(3) + tx_pwr(3) + name(2 + length) + 128-bit UUID(18) → keep the name ≤ 8 bytes | +| `encrypted` | `bool` | yes | One-line preset for the override fields under `security`: `true` = SC + Bonding + MITM + DisplayOnly + encrypted+authenticated GATT chars; `false` = fully plaintext (sniffable, lab use only). Override individual bits via `security.*` — see §5.6. | +| `security` | `ble_uart_security_t` | optional | A zero-initialised member (`security.{sc,bonding,mitm,io_cap} = AUTO`) inherits everything from `encrypted`. Set any sub-field to `OFF`/`ON` (or pick a specific `io_cap`) to override just that bit. Out-of-range enum values, or impossible combos like `mitm=ON` with `io_cap=NO_INPUT_OUTPUT`, fail `ble_uart_install()` with `BLE_UART_EINVAL`. Full reference in §5.6. | +| `device_name` | `const char *` | recommended | Set as the GAP-service Device Name (UUID 0x2A00). With the **default** advertising payload it is also placed in the primary adv as the Complete Local Name; with custom `adv_data` (see §5.9) it is **not** auto-included — the application owns the adv bytes. Length must be ≤ **`BLE_UART_DEVICE_NAME_MAX` = 26** (sized so the default Flags + Name AD layout always fits in a 31-byte primary packet). Longer names fail `ble_uart_install()` synchronously with `BLE_UART_EINVAL`. | +| `adv_data` / `adv_data_len` | bytes + length | optional | Application-controlled raw advertisement data. NULL keeps the built-in default (Complete Local Name only). Max length **`BLE_UART_ADV_DATA_MAX` = 28** (the 31-byte primary packet minus our 3-byte Flags AD). Buffer is copied in `install`; the pointer doesn't need to outlive the call. See §5.9. | +| `scan_rsp_data` / `scan_rsp_data_len` | bytes + length | optional | Application-controlled raw scan-response data. NULL keeps the built-in default (128-bit BLE UART service UUID). Max length **`BLE_UART_SCAN_RSP_DATA_MAX` = 31** (no Flags element here). Same copy semantics as `adv_data`. | | `ble_uart_on_rx` | callback | optional | `NULL` discards every received byte | +| `on_event` | callback | optional | `NULL` drops every event (see §5.2.1). **Not required** for the default preset (`encrypted=true`, all `security.*` AUTO → Passkey Display): the port logs the 6-digit passkey to UART and completes pairing without a callback. **Required** when `io_cap` is `KEYBOARD_ONLY`, `DISPLAY_YES_NO`, or `KEYBOARD_DISPLAY` — otherwise `ble_uart_install()` returns `BLE_UART_EINVAL`. | ### 5.2 RX callback signature @@ -288,50 +306,322 @@ static void my_handler(const uint8_t *data, size_t len) **Caveats**: -- The callback runs in the **NimBLE host task** context — **do not - block**; offload heavy work to your own task. +- The callback runs on the BLE host task (NimBLE host task / + Bluedroid BTC task) — **do not block**; offload heavy work to your + own task. - A single callback may carry only **part** of an upper-layer frame (the central slices on ATT MTU). Framing logic (line / TLV / length-prefixed) is your responsibility. - The data carries **no `ctx` argument**. If your callback needs state, use a file-scope `static` or a global. -### 5.3 Lifecycle functions +### 5.2.1 Event callback + +```c +typedef void (*ble_uart_evt_cb_t)(const ble_uart_evt_t *evt); + +static void on_event(const ble_uart_evt_t *e) +{ + switch (e->id) { + case BLE_UART_EVT_CONNECTED: /* link up */ break; + case BLE_UART_EVT_DISCONNECTED: /* e->disconnected.reason */ break; + case BLE_UART_EVT_SUBSCRIBED: /* e->subscribed.subscribed */ break; + case BLE_UART_EVT_LINK_SECURE: + if (e->link_secure.encrypted && e->link_secure.authenticated) { + /* Safe to forward sensitive payloads now */ + } + break; + case BLE_UART_EVT_PASSKEY_DISPLAY: /* e->passkey.passkey */ break; + case BLE_UART_EVT_PASSKEY_REQUEST: /* user types peer's 6-digit; + ble_uart_passkey_reply(d) */ break; + case BLE_UART_EVT_NUMERIC_COMPARE: /* e->numeric_compare.passkey, + ble_uart_compare_reply(b) */ break; + case BLE_UART_EVT_PAIRING_FAILED: /* e->pairing_failed.reason */ break; + } +} +``` + +| `evt->id` | Payload (anonymous-union member) | Fires when | +| --- | --- | --- | +| `BLE_UART_EVT_CONNECTED` | — | Physical link up | +| `BLE_UART_EVT_DISCONNECTED` | `disconnected.reason` (int, stack-specific) | Physical link down — Bluedroid: `esp_gatt_conn_reason_t`; NimBLE: BLE host return code (`BLE_HS_HCI_ERR()` for HCI) | +| `BLE_UART_EVT_SUBSCRIBED` | `subscribed.subscribed` | CCCD on TX changed (edge-triggered) | +| `BLE_UART_EVT_LINK_SECURE` | `link_secure.{encrypted,authenticated,bonded,key_size}` | Pairing or bonded reconnect succeeds | +| `BLE_UART_EVT_PASSKEY_DISPLAY` | `passkey.passkey` (0..999999) | SM generated a passkey for the central to type (Passkey Display). **Optional** — the port always prints a banner to UART; with `on_event == NULL` the event is dropped and pairing still completes (NimBLE injects the passkey internally; Bluedroid needs no app reply). Register `on_event` only if you want a custom UI in addition to the log line. | +| `BLE_UART_EVT_PASSKEY_REQUEST` | — | SM asks the user to enter a passkey shown by the central — application **must** reply via `ble_uart_passkey_reply()` (see §5.6.1). Requires `on_event != NULL` at install time. | +| `BLE_UART_EVT_NUMERIC_COMPARE` | `numeric_compare.passkey` (0..999999) | SM asks the user to confirm the displayed value matches the central — application **must** reply via `ble_uart_compare_reply()` (see §5.6.1). Requires `on_event != NULL` at install time. | +| `BLE_UART_EVT_PAIRING_FAILED` | `pairing_failed.reason` | Pairing rejected or timed out (including no application reply for `PASSKEY_REQUEST` / `NUMERIC_COMPARE` before the SM's pairing timeout) | +| `BLE_UART_EVT_CLOSED` | `closed.status` (`BLE_UART_*` from the worker's `ble_uart_close()`) | `ble_uart_close_async()` worker finished — then `uninstall` on an app task (§5.3.2) | + +**Use `LINK_SECURE`, not `is_connected()`, to gate any logic that +requires the link to be encrypted / authenticated** — bare +`is_connected()` returns `true` while the link is still plaintext, and +inferring security from `encrypted` / `authenticated` separately on the +caller side is exactly the kind of leak the callback is designed to +plug. + +**Threading**: same context and rules as `ble_uart_on_rx` (NimBLE host +task / Bluedroid BTC task). Don't block, don't call `ble_uart_close` / +`ble_uart_uninstall` from inside the callback — use +`ble_uart_close_async()` (§5.3.2) if you need to teardown in response +to an event. + +**Exception — `BLE_UART_EVT_CLOSED`**: this single event fires from +the close-async worker task instead of the BLE host task; by the time +it runs the host task is already gone. Keep the handler short: set a +flag or notify an app task — do **not** call `ble_uart_uninstall()` +here (see §5.3.2). The worker clears `s_closing` only after your +handler returns. + +**Ordering contracts (both backends)**: + +- A single CCCD value change fires exactly one `SUBSCRIBED` event + (edge-triggered — repeating the same write is a no-op). +- If the central was subscribed at the moment the link drops, you get + `SUBSCRIBED(false)` **before** `DISCONNECTED`. NimBLE does this + natively (`BLE_GAP_SUBSCRIBE_REASON_TERM`); the Bluedroid backend + synthesizes the same sequence so consumers can write a single state + machine that works on either host. +- `LINK_SECURE` always arrives after `CONNECTED` — pairing can't run + without a link. +- `BLE_UART_EVT_CLOSED` always arrives **after** + `BLE_UART_EVT_DISCONNECTED` (when there was a peer) — the + close-async worker calls the same disconnect+wait sequence as the + synchronous `ble_uart_close()` before firing CLOSED. + +**Backend differences**: + +- `BLE_UART_EVT_LINK_SECURE.key_size`: NimBLE reports the negotiated + size (7..16); Bluedroid surfaces a fixed 16 — Bluedroid sets + `ESP_BLE_SM_MAX_KEY_SIZE=16` at install time and does not expose the + negotiated size on `AUTH_CMPL`. +- Bonded reconnects: NimBLE re-fires `LINK_SECURE` on every encryption + change; Bluedroid only fires `AUTH_CMPL_EVT` when the SM exchange + actually runs, so a pure LTK-restart may not refire the event. +- CCCD persistence on bonded reconnect: NimBLE re-fires + `SUBSCRIBED(true)` automatically (via `BLE_GAP_SUBSCRIBE_REASON_RESTORE`) + when the bonded peer reconnects; Bluedroid does not persist CCCD + across connections, so the central has to write CCCD again to + resubscribe. + +### 5.3 Lifecycle — bring-up and release + +#### API summary ```c int ble_uart_install(const ble_uart_config_t *cfg); int ble_uart_open(void); int ble_uart_close(void); +int ble_uart_close_async(void); /* fire-and-forget, see §5.3.2–5.3.4 */ int ble_uart_uninstall(void); ``` | Function | What it does (NimBLE) | What it does (Bluedroid) | When to call | Blocking? | | --- | --- | --- | --- | --- | | `install` | `nimble_port_init` + `ble_hs_cfg` + SM + SIG services + UART GATT | `controller_init/enable` + `bluedroid_init/enable` + SM + `esp_ble_gatts_create_attr_tab` (waits ≤500 ms for the attr-table event) | After `nvs_flash_init()`, before `open` | No, ~50 ms (NimBLE) / ~150 ms (Bluedroid) | -| `open` | Bond store + spawn host task + start advertising once synced | Configure adv data + scan rsp + start advertising | After `install` | No, host runs in the background | -| `close` | Stop adv → graceful disconnect (LL_TERMINATE_IND, waits ≤500 ms for the disconnect event) → `nimble_port_stop()` | Stop adv → graceful disconnect (`esp_ble_gap_disconnect`, waits ≤500 ms) | After `open`, before `uninstall` | Yes, up to ~500 ms while waiting for the peer disconnect | -| `uninstall` | Calls `close` if still open, then `nimble_port_deinit()` and resets module state | Calls `close` if still open, then `bluedroid_disable+deinit` + `controller_disable+deinit` | After `close` (or directly — `uninstall` cascades into `close` on its own) | Yes, follows the same wait window as `close` | +| `open` | Spawn host task + `ble_hs_start` (first time via `BLE_HS_AUTO_START`, later via `ble_hs_sched_start`) + advertising once synced; after a prior `close`, re-queues GAP/GATT/UART svc defs (§5.3.1a) | Configure adv data + scan rsp + start advertising (GATT table from `install` stays up) | After `install` | No, host runs in the background | +| `close` | Stop adv → graceful disconnect (≤500 ms) → `nimble_port_stop()` → `ble_gatts_reset()` | Stop adv → graceful disconnect (≤500 ms); host + GATT table stay up | After `open`, before `uninstall`; **not** from host-task callbacks (§5.3.2) | Yes, up to ~500 ms (NimBLE) | +| `close_async` | Worker runs the same body as `close`, then `BLE_UART_EVT_CLOSED` | Same | From `on_event` / `on_rx` (host task) when sync `close` would deadlock | No (returns once worker is spawned) | +| `uninstall` | `close` if still open (+ poll in-flight `close_async` ≤~5 s), then `nimble_port_deinit`, wipe module state | Same + controller deinit | After the radio is fully closed (§5.3.2); **not** from host-task callbacks | Yes | -Call order: +**Bring-up** (every product): -``` -nvs_flash_init - └── ble_uart_install - └── ble_uart_open ← BLE is live - └── ble_uart_close - └── ble_uart_uninstall ← clean state, can install again +```text +nvs_flash_init() + └── ble_uart_install(&cfg) /* once per uninstall cycle */ + └── ble_uart_open() /* advertising + pairing; BLE is live */ ``` -Each call returns `BLE_HS_EALREADY` if the corresponding state is -already true (e.g. `open` called twice, or `close` called when the -radio is already down). It is therefore safe to call `close` / -`uninstall` defensively at shutdown without checking the current state -yourself. +Run-forever firmware can stop here — no `close` / `uninstall` required. -**Do NOT call `close` / `uninstall` from inside `ble_uart_on_rx`** — -that callback runs on the NimBLE host task, and `close` blocks on -`nimble_port_stop()` which expects the host task to exit. Self-stop -deadlocks. Forward the request to a normal FreeRTOS task instead. +**Release** — pick **one** path below. `close` stops the radio but keeps +`install` state (you can `open()` again). `uninstall` tears the host + +controller down so `install()` can run from scratch. + +| Goal | Call sequence | Who calls `close` / `uninstall` | +| --- | --- | --- | +| Power BLE off from a **normal app task** (button, Wi-Fi, `app_main` shutdown) | `ble_uart_close()` → `ble_uart_uninstall()` | That app task only | +| Power BLE off **because of a BLE event** (RX command, failed pairing, policy) | `ble_uart_close_async()` in `on_event` / `on_rx` → wait for `BLE_UART_EVT_CLOSED` → `ble_uart_uninstall()` on an **app task** (§5.3.2) | `close_async` in callback; `uninstall` deferred | + +Each API returns `BLE_UART_EALREADY` when the module is already in the +target state, so defensive `close` / `uninstall` at shutdown without +manual state checks is fine **as long as** you follow the release path +for your scenario. + +#### 5.3.1 Path A — synchronous release (recommended default) + +Use when teardown is **not** triggered from inside `on_event` / +`on_rx` (NimBLE host task / Bluedroid BTC task). This is what the +`ble_uart_service` example does. + +```c +void shutdown_ble_from_app_task(void) +{ + int rc; + + rc = ble_uart_close(); + if (rc != BLE_UART_OK && rc != BLE_UART_EALREADY) { + ESP_LOGE(TAG, "ble_uart_close rc=%d", rc); + } + + rc = ble_uart_uninstall(); + if (rc != BLE_UART_OK && rc != BLE_UART_EALREADY) { + ESP_LOGE(TAG, "ble_uart_uninstall rc=%d", rc); + } + /* BLE UART fully released — safe to ble_uart_install() again */ +} +``` + +```text +ble_uart_open() /* running */ + │ + ▼ +ble_uart_close() /* same app task; not from on_event / on_rx */ + │ + ▼ +ble_uart_uninstall() +``` + +- `uninstall` may call `close` internally if you skipped `close` — still + call both explicitly so return codes are obvious in your logs. +- Do **not** call `close` or `uninstall` from `on_event` / `on_rx` — use + Path B instead. + +#### 5.3.1a Pausing and resuming (`close` then `open` again) + +`install` state is preserved across `close()` — you may call `open()` +again without `uninstall()`. This is what the `ble_uart_service` example +exercises in `app_main` (open → close → open) to prove the cycle. + +**NimBLE backend** + +| Topic | Behaviour | +| --- | --- | +| GATT services | Same set as after `install`: GAP (`0x1800`), GATT (`0x1801`), BLE UART (NUS). `close()` calls the public `ble_gatts_reset()`; the next `open()` re-runs `ble_svc_gap_init()`, `ble_svc_gatt_init()`, and re-adds the UART service. | +| ATT handles | **Not stable** — centrals must run a full service discovery after each reconnect; do not cache handles across a `close`/`open` cycle. | +| Subscriptions | Cleared — the central must re-enable TX notifications (CCCD). | +| Bonds | NVS bond store is unchanged (still configured at `install()`). | +| First vs later `open` | With default `BLE_HS_AUTO_START`, the first `open()` consumes the one-shot auto-start queued by `nimble_port_init()`; every later `open()` must call `ble_hs_sched_start()` (handled inside `ble_uart_open()`). | + +**Bluedroid backend** + +`close()` only stops advertising and disconnects; the host and attribute +table created at `install()` stay registered. A second `open()` restarts +advertising. GATT handles are typically unchanged. + +**Extra GATT services (§6.3)** + +Services you register with `ble_gatts_add_svcs()` / `ble_svc_*_init()` +at `install()` time are **not** automatically re-registered by +`ble_uart` on a later `open()` after `close()` (NimBLE only re-adds +GAP, GATT, and UART). Either call your init/add functions again inside +your own `open()` hook after `ble_uart_close()`, or use +`close()` → `uninstall()` → `install()` → `open()` for a full rebuild. + +#### 5.3.2 Path B — release after a BLE event (`close_async`) + +Use when the **reason** to shut down arrives on the host task (e.g. +`BLE_UART_EVT_PAIRING_FAILED`, an RX “power off” byte, or +`LINK_SECURE` policy). Synchronous `close()` deadlocks there; use +`close_async()` and **defer** `uninstall()` to a normal task. + +```c +static volatile bool s_ble_closed_ok; + +static void on_event(const ble_uart_evt_t *e) +{ + switch (e->id) { + case BLE_UART_EVT_PAIRING_FAILED: + ble_uart_close_async(); /* OK: host-task context */ + break; + + case BLE_UART_EVT_CLOSED: + /* Runs on the close-async worker — keep this short. Do NOT call + * ble_uart_uninstall() here (s_closing is still set; see §5.3.3). */ + if (e->closed.status == BLE_UART_OK) { + s_ble_closed_ok = true; /* or xTaskNotifyGive / queue */ + } + break; + default: + break; + } +} + +void ble_shutdown_task(void *arg) +{ + (void)arg; + for (;;) { + if (s_ble_closed_ok) { + s_ble_closed_ok = false; + ble_uart_uninstall(); /* normal app task */ + break; + } + vTaskDelay(pdMS_TO_TICKS(50)); + } + vTaskDelete(NULL); +} +``` + +```text +on_event / on_rx (host task): + ble_uart_close_async() + │ + ▼ + [worker: do_close ≈ sync close] + │ + ├── BLE_UART_EVT_DISCONNECTED (if peer was connected) + └── BLE_UART_EVT_CLOSED (worker task; set flag only) + │ + ▼ +app task (not host, not inside CLOSED handler): + ble_uart_uninstall() +``` + +- `close_async` returns `BLE_UART_OK` once the worker is **spawned**, not + when close finishes. +- Only `BLE_UART_EVT_CLOSED` with `.closed.status == BLE_UART_OK` means + the same quiesced state as `ble_uart_close()` — then it is safe to + `uninstall()` from your app task. +- On failure (`BLE_UART_EFAIL`, etc.) the port may still be open; retry + `ble_uart_close()` / `ble_uart_close_async()` from an app task. + +#### 5.3.3 `close_async` + `uninstall` — rules and pitfalls + +`ble_uart_uninstall()` **polls** an in-flight `close_async` worker for +up to **~5 s**. If the worker has not exited it logs +`uninstall: close_async worker still running, tearing down anyway` and +continues anyway — treat that as an application bug, not a supported +path. + +| Do | Don't | +| --- | --- | +| `close_async()` in `on_event` / `on_rx`; `uninstall()` later on **one** app task after `CLOSED` + `BLE_UART_OK` | `uninstall()` in the same task right after `close_async()` without waiting | +| Set a flag / queue in `BLE_UART_EVT_CLOSED`; return immediately | `ble_uart_uninstall()` **inside** `BLE_UART_EVT_CLOSED` (worker still holds `s_closing`) | +| Sync `close` + `uninstall` from a button / network task | `close` / `uninstall` from host-task callbacks | +| Keep `on_event` / `on_rx` short while a close is in flight | Multi-second blocking in callbacks during `close_async` | +| After a timeout log, fix ordering before `install()` again | Immediate `install()` + `open()` + `close_async()` after a wedged teardown | + +If you see `uninstall: close_async worker still running, tearing down +anyway`, fix call ordering (§5.3.2) before calling `install()` again. + +#### 5.3.4 `ble_uart_close_async()` — reference + +Some applications need to teardown the radio in response to a BLE +event — examples: a "shutdown" command on RX, a `LINK_SECURE` whose +flags don't meet the application's policy, or a `PAIRING_FAILED` from +a peer that's been blacklisted. Because the synchronous `close()` is +called *from* the host task it would normally run on, calling it +inline would deadlock. `close_async()` papers over that: it spawns a +small worker task (~3 KB stack, idle+2 priority) that runs the same +close body, then signals completion via the event callback. + +**Behaviour** (see §5.3.2 for the full release flow): + +- `close_async` returns `BLE_UART_OK` once the worker has been spawned. +- `BLE_UART_EVT_DISCONNECTED` (if connected) then `BLE_UART_EVT_CLOSED` + with `.closed.status` — same ≤500 ms disconnect window as sync `close`. +- Second call while draining → `BLE_UART_EALREADY`; before `open` → + `BLE_UART_EALREADY`; spawn failure → `BLE_UART_ENOMEM` (latch reset). ### 5.4 TX interface @@ -352,11 +642,11 @@ ble_uart_tx((const uint8_t *)line, (size_t)n); | Return | Meaning | | --- | --- | -| `0` | Success (notification handed to the stack) | -| `BLE_HS_ENOTCONN` | No central connected; **this is normal — typically ignore** | -| `BLE_HS_EINVAL` | `data == NULL` or `len == 0` | -| `BLE_HS_ENOMEM` | Stack mbuf pool exhausted | -| other | Internal stack error — see `ble_hs.h` | +| `BLE_UART_OK` | Success (notification handed to the stack) | +| `BLE_UART_ENOTCONN` | No central connected; **this is normal — typically ignore** | +| `BLE_UART_EINVAL` | `data == NULL` or `len == 0` | +| `BLE_UART_ENOMEM` | Stack mbuf pool exhausted | +| `BLE_UART_EFAIL` | Internal stack error — see logs | **Calling context**: any FreeRTOS task at any priority. **Not callable from an ISR** — push the data to a queue from the ISR and let a task @@ -380,16 +670,425 @@ bool ble_uart_is_subscribed(void); You usually **don't need** to query these up-front — `ble_uart_tx` returns `ENOTCONN` to tell you. -### 5.6 Service UUID constant +### 5.6 Security configuration + +`cfg.encrypted` is a one-line **preset** that turns on every part of +the stack's security toolbox at once — LE Secure Connections, bonding +(LTK persisted in NVS), MITM protection, DisplayOnly IO, and the +`_ENC | _AUTHEN` flags on the GATT characteristics. It maps to the +older two-state behaviour and is what the "secure by default" template +in §4.4 picks. + +For applications that need finer control — a displayless gateway that +still wants encrypted bonding, a one-shot encrypted session that +doesn't keep an LTK, an interop test build that disables only MITM — +each component of the preset can be flipped individually through the +`cfg.security` sub-struct: + +```c +typedef enum { + BLE_UART_SEC_AUTO = 0, /* follow cfg.encrypted */ + BLE_UART_SEC_OFF = 1, + BLE_UART_SEC_ON = 2, +} ble_uart_sec_t; + +typedef enum { + BLE_UART_IO_CAP_AUTO = 0, /* DisplayOnly when MITM is on; + NoInputNoOutput when off. + Passkey Display needs no on_event */ + BLE_UART_IO_CAP_NO_INPUT_OUTPUT = 1, /* Just Works only */ + BLE_UART_IO_CAP_DISPLAY_ONLY = 2, /* Passkey Display — UART banner + + optional PASSKEY_DISPLAY; + no on_event required */ + BLE_UART_IO_CAP_KEYBOARD_ONLY = 3, /* Passkey Entry — PASSKEY_REQUEST; + on_event required */ + BLE_UART_IO_CAP_DISPLAY_YES_NO = 4, /* Numeric Comparison; + on_event required */ + BLE_UART_IO_CAP_KEYBOARD_DISPLAY = 5, /* PASSKEY_REQUEST or NUMERIC_COMPARE; + on_event required */ +} ble_uart_io_cap_t; + +typedef struct { + ble_uart_sec_t sc; /* tri-state */ + ble_uart_sec_t bonding; /* tri-state */ + ble_uart_sec_t mitm; /* tri-state */ + ble_uart_io_cap_t io_cap; /* AUTO + the five IO caps above */ +} ble_uart_security_t; +``` + +Each of `cfg.security.{sc,bonding,mitm}` is a tri-state. `AUTO` +(the value of any zero-initialised member) inherits from +`cfg.encrypted`; `OFF` / `ON` override that specific bit only. The +resolution table: + +| `cfg.encrypted` | Override field | Resolved bit | +| --- | --- | --- | +| `true` | `AUTO` | ON | +| `true` | `OFF` | OFF | +| `true` | `ON` | ON | +| `false` | `AUTO` | OFF | +| `false` | `OFF` | OFF | +| `false` | `ON` | ON | + +`cfg.security.io_cap` follows the same `AUTO` / explicit pattern. +The application picks an IO cap matching its UI; the SM combines it +with the central's IO cap to elect the pairing model (see BT Core +Spec §2.3.5.1) and ble_uart fires the matching event: + +| Pairing model | Trigger event | Application response | +| --- | --- | --- | +| Just Works | (none — pairs silently) | — | +| Passkey Display (we show) | `BLE_UART_EVT_PASSKEY_DISPLAY` (optional; UART banner always) | (none — port handles SM reply; central types the digits) | +| Passkey Entry (user types)| `BLE_UART_EVT_PASSKEY_REQUEST` | `ble_uart_passkey_reply(d)` — **`on_event` required** | +| Numeric Comparison | `BLE_UART_EVT_NUMERIC_COMPARE` | `ble_uart_compare_reply(b)` — **`on_event` required** | + +Numeric Comparison additionally requires LE Secure Connections on +both sides (legacy SM doesn't support it); against a legacy peer a +`DISPLAY_YES_NO` / `KEYBOARD_DISPLAY` IO cap falls back to either +Passkey Entry (with our keypad) or Just Works. + +#### What is checked synchronously + +`ble_uart_install()` rejects the following with `BLE_UART_EINVAL` +**before** bringing the host stack up, so misconfigured applications +fail fast and predictably: + +- `cfg.security.{sc,bonding,mitm}` outside `{AUTO, OFF, ON}` +- `cfg.security.io_cap` outside the six values listed above +- Resolved `mitm == ON` together with resolved + `io_cap == NO_INPUT_OUTPUT` — Just Works cannot satisfy MITM and + the SM would otherwise reject pairing in flight +- `cfg.on_event == NULL` together with a **configured** (not resolved) + input-capable `io_cap` — only `KEYBOARD_ONLY`, `DISPLAY_YES_NO`, and + `KEYBOARD_DISPLAY`. Without an event sink the application would never + see `PASSKEY_REQUEST` / `NUMERIC_COMPARE` and pairing would silently + stall until the SM times out. **`AUTO` (even when it resolves to + DisplayOnly because `mitm=ON`), `DISPLAY_ONLY`, and `NO_INPUT_OUTPUT` + do not require `on_event`** — Passkey Display is satisfied inside the + port (UART log + internal SM reply); `PASSKEY_DISPLAY` via `on_event` + is additive only. + +#### How the resolved policy is applied + +| Component | Effect | +| --- | --- | +| Resolved `sc` / `bonding` / `mitm` (any ON) | SM is enabled; `ble_gap_security_initiate` (NimBLE) / `esp_ble_set_encryption` (Bluedroid) runs on connect | +| Resolved `mitm` | `ESP_BLE_SEC_ENCRYPT_MITM` vs `_NO_MITM` (Bluedroid); `_AUTHEN` flag added to GATT chars | +| Any of the three on | Encrypted GATT permission flags (`_ENC` on NimBLE, `_ENCRYPTED` on Bluedroid) | +| All three off | Plain `READ`/`WRITE` permissions; SM disabled | +| Resolved `io_cap` | `BLE_HS_IO_*` (NimBLE) / `ESP_IO_CAP_*` (Bluedroid) | + +#### Common combinations + +```c +/* (a) Default — secure-by-default UART. SC + Bonding + MITM, DisplayOnly. + * on_event may be NULL: passkey is printed to UART and pairing + * completes without PASSKEY_DISPLAY / reply callbacks. */ +ble_uart_install(&(ble_uart_config_t){ + .encrypted = true, + /* security.{sc,bonding,mitm,io_cap} all AUTO → all ON. */ + /* .on_event = NULL — valid for this preset */ +}); + +/* (b) Displayless gateway. SC + Bonding + Just Works (no passkey UI). */ +ble_uart_install(&(ble_uart_config_t){ + .encrypted = true, + .security = { + .mitm = BLE_UART_SEC_OFF, + .io_cap = BLE_UART_IO_CAP_NO_INPUT_OUTPUT, + }, +}); + +/* (c) Encrypted but ephemeral. Re-pair every reconnect, no NVS bond. */ +ble_uart_install(&(ble_uart_config_t){ + .encrypted = true, + .security = { .bonding = BLE_UART_SEC_OFF }, +}); + +/* (d) Plaintext lab build. */ +ble_uart_install(&(ble_uart_config_t){ + .encrypted = false, + /* security.* all AUTO → all OFF. */ +}); + +/* (e) Interop test — keep encryption + bonding, drop MITM only. */ +ble_uart_install(&(ble_uart_config_t){ + .encrypted = true, + .security = { .mitm = BLE_UART_SEC_OFF }, + /* security.io_cap AUTO → NoInputNoOutput once MITM is gone. */ +}); + +/* (f) Passkey Entry — peripheral has a keypad, central has a display. + * User reads the 6-digit code off the central and types it here. + * on_event MUST be set; the application wires PASSKEY_REQUEST to + * a UI prompt and feeds the digits to ble_uart_passkey_reply(). */ +ble_uart_install(&(ble_uart_config_t){ + .encrypted = true, + .security = { .io_cap = BLE_UART_IO_CAP_KEYBOARD_ONLY }, + .on_event = on_event, + ... +}); + +/* (g) Numeric Comparison — peripheral has display + yes/no button. + * Both sides see the same 6-digit value; user confirms match. + * Requires LE Secure Connections (so .sc must be ON, which it is + * by default with .encrypted=true). on_event MUST be set. */ +ble_uart_install(&(ble_uart_config_t){ + .encrypted = true, + .security = { .io_cap = BLE_UART_IO_CAP_DISPLAY_YES_NO }, + .on_event = on_event, + ... +}); + +/* (h) Touchscreen UI — full keypad+display. The SM elects either + * Passkey Entry or Numeric Comparison depending on the central; + * wire BOTH events. */ +ble_uart_install(&(ble_uart_config_t){ + .encrypted = true, + .security = { .io_cap = BLE_UART_IO_CAP_KEYBOARD_DISPLAY }, + .on_event = on_event, + ... +}); +``` + +#### 5.6.1 Pairing reply API + +**Passkey Display (default / `DISPLAY_ONLY` / `AUTO` + `mitm=ON`)** does +not use the reply APIs. The port generates the 6-digit value, logs it, +and drives the SM (NimBLE: `ble_sm_inject_io` on `BLE_SM_IOACT_DISP`; +Bluedroid: no `esp_ble_passkey_reply` needed on `PASSKEY_NOTIF`). You +only need `ble_uart_passkey_reply()` / `ble_uart_compare_reply()` for +the interactive models below. + +`Passkey Entry` and `Numeric Comparison` are interactive — the SM +suspends pairing until the application reports the user's input. +`ble_uart` exposes one reply call per flavour: + +```c +int ble_uart_passkey_reply(uint32_t passkey); /* 0..999999 */ +int ble_uart_compare_reply(bool match); +``` + +Both are safe from any task, return immediately, and accept exactly +one reply per request. Subsequent calls (or calls with no request in +flight) return `BLE_UART_ENOTCONN`. `passkey > 999999` returns +`BLE_UART_EINVAL`. If the user fails to reply before the SM's pairing +timeout (controller default ≈ 30 s), the link surfaces +`BLE_UART_EVT_PAIRING_FAILED` and any later reply is silently dropped. + +```c +static void on_event(const ble_uart_evt_t *e) +{ + switch (e->id) { + case BLE_UART_EVT_PASSKEY_REQUEST: + /* Prompt the user; once digits are entered: */ + ble_uart_passkey_reply(user_input); /* 0..999999 */ + break; + + case BLE_UART_EVT_NUMERIC_COMPARE: + ESP_LOGI(TAG, "compare %06" PRIu32, e->numeric_compare.passkey); + /* Once the user confirms: */ + ble_uart_compare_reply(true /* or false on mismatch */); + break; + + default: break; + } +} +``` + +A `false` reply to `compare_reply()` makes pairing fail with a +numeric-comparison mismatch — surfaced as +`BLE_UART_EVT_PAIRING_FAILED`. To cancel `PASSKEY_REQUEST` without a +mismatch event, just don't call `passkey_reply()`; the SM will time +out the pairing. + +#### Backend differences + +- **Passkey Display without `on_event`**: both backends complete pairing; + only `PASSKEY_DISPLAY` is suppressed when the callback is `NULL`. The + UART banner (`show_passkey`) is always emitted for log-scraping tests. +- **Numeric Comparison edge case**: if `io_cap` resolved to DisplayOnly + but the central still negotiates NC (rare), Bluedroid rejects the + request when `on_event == NULL`; NimBLE may stall until the SM times + out — use `DISPLAY_YES_NO` / `KEYBOARD_DISPLAY` with a registered + `on_event` if you need NC. +- **NimBLE** lets the application observe the negotiated `key_size` + on `BLE_UART_EVT_LINK_SECURE`; **Bluedroid** surfaces a fixed `16` + (the value forced via `ESP_BLE_SM_MAX_KEY_SIZE` at install time — + Bluedroid does not expose the negotiated size on `AUTH_CMPL`). +- With `mitm=OFF`, NimBLE pairs with `_AUTHEN` permissions still + off on the chars; Bluedroid uses `ESP_GATT_PERM_*_ENCRYPTED` + (the encryption-without-MITM tier) to match. +- `cfg.encrypted=false` plus any `cfg.security.*=ON` override is + allowed — it partially enables the SM, e.g. + `cfg.encrypted=false, cfg.security.sc=ON` is "SC pairing without + MITM and without persisted bond". Useful only for lab interop + tests; production firmware should keep `cfg.encrypted = true` and + only override surgically. + +### 5.7 Bond management + +```c +/* All three are usable as soon as ble_uart_install() returns; they + * do not require ble_uart_open() to have been called yet — clearing + * stale bonds before the first advertising window is the canonical + * use case. */ +int ble_uart_get_bond_count(size_t *out_count); +int ble_uart_get_bonded_peers(ble_uart_addr_t *out, size_t cap, size_t *out_count); +int ble_uart_remove_peer(const ble_uart_addr_t *peer); +int ble_uart_clear_bonds(void); + +/* Address type used by remove_peer and BLE_UART_EVT_CONNECTED. */ +typedef struct { + uint8_t bytes[6]; /* big-endian: bytes[0] is the MSB octet */ + uint8_t type; /* BLE_UART_ADDR_TYPE_PUBLIC or _RANDOM */ +} ble_uart_addr_t; +``` + +| Function | Effect | +| --- | --- | +| `ble_uart_get_bond_count` | Number of peers in the persistent store; 0 means "no bonds yet". Pass `cap == 0` to `get_bonded_peers` for the same count without an address buffer. | +| `ble_uart_get_bonded_peers` | List bonded peer addresses; writes up to `cap`, reports total in `*out_count` (caller may re-call with a larger buffer if total > cap). `cap == 0` returns the count only. | +| `ble_uart_remove_peer` | Drop one peer's LTK / IRK / persisted CCCD. **Idempotent** — returns `BLE_UART_OK` even when the peer is not in the store (NimBLE: `ble_store_util_delete_peer` treats `BLE_HS_ENOENT` as success; Bluedroid: `esp_ble_remove_bond_device` does not fail on a missing entry). Call `get_bonded_peers()` first if you need to tell "removed" from "was never bonded". | +| `ble_uart_clear_bonds` | Drop *all* of the above; equivalent to a factory reset of the bond store, but does not touch any other NVS namespace | + +`ble_uart_remove_peer` and `ble_uart_clear_bonds` do **not** actively +disconnect the current link (encrypted or not). Call `ble_uart_close()` +first if you need an immediate disconnect and re-pair. + +**Where do I get the address?** From `BLE_UART_EVT_CONNECTED.connected.peer` +(see §5.2.1). Save it from your event handler the first time you see +each new peer, then pass it to `ble_uart_remove_peer` later when you +want to forget it. + +**Backend notes**: + +- Bluedroid matches bonds by BD address alone — `peer->type` is + ignored by `remove_peer`. If the peer first connected as + `address_A` and bonding succeeded, CONNECT and `get_bonded_peers()` + keep reporting `address_A` on later reconnects even when the + peer's over-the-air address has changed (e.g. a new RPA). +- NimBLE matches by `(type, identity-address)` — for an RPA peer this + is the resolved identity, **not** the random address you saw on the + wire. `BLE_UART_EVT_CONNECTED` reports the resolved identity when + it's known (post-pairing reconnect of a bonded RPA peer); on first + pair it equals the OTA random address, so the bond is recorded + under that random address and `remove_peer` works either way. +- Neither backend reports "peer not found" from `remove_peer` — a + wrong `(type, bytes)` pair that does not match any stored bond + still returns `BLE_UART_OK`. This mirrors the underlying stacks' + delete-if-present semantics, not a lookup-then-delete API. +- `ble_uart_clear_bonds` on Bluedroid iterates the bond list and + removes each entry; on NimBLE it calls `ble_store_clear()`, which + also wipes the local LTK and any persisted CCCD. +- **NimBLE** `get_bond_count` / `get_bonded_peers(cap=0)` heap-allocate a + scratch buffer sized to `BLE_STORE_MAX_BONDS` (not the caller's stack), + so they are safe from small-stack tasks regardless of + `CONFIG_BT_NIMBLE_MAX_BONDS`. + +### 5.8 Service UUID constant ```c extern const ble_uart_uuid128_t ble_uart_service_uuid; ``` Always `6e400001-b5a3-f393-e0a9-e50e24dcca9e` (the de-facto BLE UART service UUID). It is -already inserted into the scan response, so the **application normally -does not touch it**. You only need it if you take over advertising -yourself (see 6.3). +already inserted into the scan response **by the default payload**, so +the application only needs to reference it when it takes over the adv +bytes itself (see §5.9) or otherwise replaces our advertising (see §6.3). + +### 5.9 Custom advertising payloads + +`ble_uart` builds a sensible default for both the primary advertisement +and the scan response: + +| Packet | Default content | Why | +| --- | --- | --- | +| Primary adv (31 B max) | Flags AD + Complete Local Name (`device_name`) | Phones show the name; everything else in the 31 bytes is left for the application to add via `adv_data` | +| Scan response (31 B max) | Complete 128-bit BLE UART service UUID (18 B element) | The 128-bit UUID alone is too big to share the primary packet with a typical name | + +Set `adv_data` / `scan_rsp_data` in the config to override **everything +the application sees** — only the 3-byte Flags AD element of the primary +packet stays library-controlled (the BT spec mandates a Flags element, +and a few of its bits — General Discoverable / BR-EDR Not Supported — +are state we already negotiated with the controller). + +```c +/* +-- 31-byte primary advertisement packet ---------------------+ + * | [02 01 06] ← Flags AD prepended by ble_uart (3 bytes) | + * | | + * +-------------------------------------------------------------+ + * + * +-- 31-byte scan-response packet -----------------------------+ + * | | + * +-------------------------------------------------------------+ + */ +``` + +`adv_data_len` is checked at install time; oversized buffers fail with +`BLE_UART_EINVAL`. Both buffers are copied into module-private storage, +so the caller's pointers do not need to outlive the call. + +**Format**: a sequence of standard BT Core "AD structure" triplets — +`[length(1)] [AD type(1)] [value(length-1)]`. AD-type values are +defined in the *Bluetooth Assigned Numbers* document +([Generic Access Profile, §1](https://www.bluetooth.com/specifications/assigned-numbers/)). +Common ones: + +| Type | Name | Value format | +| --- | --- | --- | +| `0x09` | Complete Local Name | UTF-8 bytes | +| `0x08` | Shortened Local Name | UTF-8 bytes | +| `0x0A` | TX Power Level | 1 signed byte (dBm) | +| `0x07` | Complete List of 128-bit Service UUIDs | 16 bytes per UUID | +| `0xFF` | Manufacturer Specific Data | 2-byte company ID + payload | + +**Example — replace the default with name + UUID + 4 bytes of vendor data** + +```c +static const uint8_t adv_payload[] = { + /* Complete Local Name "MyDev" (1 + 1 + 5 = 7 bytes) */ + 0x06, 0x09, 'M', 'y', 'D', 'e', 'v', + + /* Complete 128-bit Service UUID — bytes are little-endian on air, + * matching ble_uart_service_uuid.bytes[]. (1 + 1 + 16 = 18 bytes) */ + 0x11, 0x07, + 0x9e, 0xca, 0xdc, 0x24, 0x0e, 0xe5, 0xa9, 0xe0, + 0x93, 0xf3, 0xa3, 0xb5, 0x01, 0x00, 0x40, 0x6e, + /* total = 7 + 18 = 25 bytes (≤ BLE_UART_ADV_DATA_MAX = 28) */ +}; + +static const uint8_t scan_rsp_payload[] = { + /* Manufacturer Specific Data: Espressif Systems (0x02E5) + 4 bytes */ + 0x07, 0xFF, 0xE5, 0x02, 0xDE, 0xAD, 0xBE, 0xEF, +}; + +ble_uart_install(&(ble_uart_config_t){ + .encrypted = true, + .device_name = "MyDev", /* GAP service value, peer-readable */ + .adv_data = adv_payload, + .adv_data_len = sizeof(adv_payload), + .scan_rsp_data = scan_rsp_payload, + .scan_rsp_data_len = sizeof(scan_rsp_payload), + .ble_uart_on_rx = on_rx, + .on_event = on_event, +}); +``` + +**Notes**: + +- `device_name` and `adv_data` are independent. The first is the GAP + service value that any connected peer can read over GATT; the second + is what scanners see before connecting. If you want the device name + visible during a scan, include a Complete-Local-Name AD element + (`0x09`) in `adv_data` yourself — providing custom `adv_data` + disables the auto-include path. +- The 31-byte packet limit is BLE 4.x legacy advertising. Extended + advertising (BLE 5.0) is **not** wired through this API — both + backends fall back to legacy advertising for portability. +- Set only one half if you want the other to keep its default — e.g. + custom `adv_data` with `scan_rsp_data = NULL` keeps the default + service-UUID scan response. --- @@ -465,8 +1164,12 @@ Effect: `ble_uart` registers its own service; you can call `ble_gatts_add_svcs()` **multiple times** and NimBLE will build all of them into the GATT -table. **Caveat**: this must happen before `ble_uart_open()`, otherwise -the host task is already running and the GATT table is locked. +table. **Caveat**: this must happen before the **first** `ble_uart_open()` +for that `install()` cycle, otherwise the host task is already running +and the GATT table is locked. If you use `ble_uart_close()` and later +`ble_uart_open()` without `uninstall()`, you must call your extra +`ble_svc_*_init()` / `ble_gatts_add_svcs()` again before that second +`open()` — see §5.3.1a. ```c ble_uart_install(&cfg); @@ -486,15 +1189,20 @@ ble_uart_open(); > call `ble_gap_adv_start` yourself. In that case, just fork > `ble_uart_nimble.c` (or the matching `ble_uart_bluedroid.c`). -### 6.4 Configuring the device-name prefix via Kconfig +### 6.4 Configuring the device name via Kconfig If you use the shared `ble_uart` component, options are already in -`menuconfig → Component configuration → BLE UART library`. If you copied only +`menuconfig → Component configuration → ESP-BLE-UART library`. If you copied only the `.c` / `.h` files into `main/`, copy `Kconfig` from `common/ble_uart/` as well (or merge its symbols into your own `Kconfig.projbuild`), then: +The bundled example builds a per-unit name as `-XXXX` where +`XXXX` is the last two BT MAC bytes in hex: + ```c -char name[24]; +uint8_t mac[6] = {0}; +esp_read_mac(mac, ESP_MAC_BT); +char name[BLE_UART_DEVICE_NAME_MAX + 1]; snprintf(name, sizeof(name), "%s-%02X%02X", CONFIG_BLE_UART_DEVICE_NAME_PREFIX, mac[4], mac[5]); @@ -505,8 +1213,11 @@ ble_uart_install(&(ble_uart_config_t){ }); ``` -Edit the default through `menuconfig → Component configuration → BLE UART -library → BLE device name prefix`. +Edit the prefix through `menuconfig → Component configuration → +ESP-BLE-UART library → BLE device name prefix`. + +For a fixed name on every unit, skip the MAC suffix and pass any +string ≤ `BLE_UART_DEVICE_NAME_MAX` directly to `device_name`. ### 6.5 Pushing data proactively @@ -529,7 +1240,7 @@ static void sensor_task(void *arg) xTaskCreate(sensor_task, "sensor", 3072, NULL, 5, NULL); ``` -When nobody is subscribed, `ble_uart_tx` returns `BLE_HS_ENOTCONN` — +When nobody is subscribed, `ble_uart_tx` returns `BLE_UART_ENOTCONN` — **just ignore it**. --- @@ -541,10 +1252,11 @@ When nobody is subscribed, `ble_uart_tx` returns `BLE_HS_ENOTCONN` — | `ble_uart_install` | Any task; once per uninstall cycle | One-shot until `uninstall` | | `ble_uart_open` | Any task; after `install` | One-shot until `close` | | `ble_uart_close` | Any task **except the BLE host task** (NimBLE host task / Bluedroid BTC task) | Idempotent; second call returns `EALREADY` | -| `ble_uart_uninstall` | Any task **except the BLE host task** | Idempotent; cascades into `close` if needed | +| `ble_uart_close_async` | Any task — including the BLE host task (use this from inside `on_rx` / `on_event`) | Idempotent; second call while a worker is draining returns `EALREADY` | +| `ble_uart_uninstall` | Any task **except the BLE host task** | Idempotent; see §5.3 release paths; polls in-flight `close_async` ≤~5 s. Best-effort teardown: returns the **first** `BLE_UART_*` failure (`ble_uart_close` or translated `esp_err_t`) but always wipes module state so a retry is possible. | | `ble_uart_tx` | Any FreeRTOS task | Yes — multi-task concurrent | | `ble_uart_is_connected` / `is_subscribed` | Any context | Yes (bool read; best-effort snapshot) | -| `ble_uart_on_rx` callback | BLE host task (NimBLE host task / Bluedroid BTC task) | Your code must not block, **must not call `close` / `uninstall`** | +| `ble_uart_on_rx` / `on_event` callback | BLE host task (NimBLE host task / Bluedroid BTC task); **`BLE_UART_EVT_CLOSED` is the lone exception — fires on the close-async worker task** | Your code must not block, **must not call `close` / `uninstall`** — use `ble_uart_close_async()` instead | | **Calling any `ble_uart` API from an ISR** | not allowed | Neither host stack supports it | --- @@ -573,7 +1285,7 @@ Measured throughput (ESP32-S3, iPhone 14 Pro central, MTU 247): | --- | --- | | `nimble_port_init rc=...` | NVS not initialised, or BT controller not enabled | | Compile error: `host/ble_hs.h` not found | `REQUIRES bt` is missing from CMakeLists | -| Device not discoverable | Device name exceeds the advertising packet limit (drop the tx_pwr field or shorten the name) | +| `ble_uart_install()` returns `BLE_UART_EINVAL` | Buffer too long (`device_name` / `adv_data` / `scan_rsp` limits in §5.9), impossible security (`mitm=ON` + `io_cap=NO_INPUT_OUTPUT`), **`io_cap` in `{KEYBOARD_ONLY, DISPLAY_YES_NO, KEYBOARD_DISPLAY}` with `on_event=NULL`** (note: default `AUTO` + `encrypted=true` and explicit `DISPLAY_ONLY` **do** allow `on_event=NULL`), or out-of-range `sc`/`bonding`/`mitm`/`io_cap`. See §5.6. | | Pairing fails | Central uses "Just Works" but we require MITM (`encrypted=true`). Use a central that supports passkey entry | | `enc_change status=13 encrypted=1 bonded=1` | `13 = BLE_HS_ETIMEOUT`. Bonded-reconnect race; **the link is actually encrypted — safe to ignore** | | Notifications missing after a reconnect | Bonded centrals often skip the CCCD write; our TX path doesn't gate on subscription state, so notifications still go out — make sure the central side has its callback registered | @@ -611,19 +1323,42 @@ If you **start from an empty project**: #include "ble_uart.h" /* === Types === */ -typedef void (*ble_uart_rx_cb_t)(const uint8_t *data, size_t len); +typedef void (*ble_uart_rx_cb_t) (const uint8_t *data, size_t len); +typedef void (*ble_uart_evt_cb_t)(const ble_uart_evt_t *evt); typedef struct { - bool encrypted; - const char *device_name; - ble_uart_rx_cb_t ble_uart_on_rx; + ble_uart_sec_t sc; /* AUTO / OFF / ON — follow `encrypted` when AUTO */ + ble_uart_sec_t bonding; + ble_uart_sec_t mitm; + ble_uart_io_cap_t io_cap; /* AUTO / NO_INPUT_OUTPUT / DISPLAY_ONLY / + KEYBOARD_ONLY / DISPLAY_YES_NO / + KEYBOARD_DISPLAY */ +} ble_uart_security_t; + +typedef struct { + bool encrypted; /* preset: SC + Bonding + MITM + DisplayOnly */ + ble_uart_security_t security; /* per-feature overrides; see §5.6 */ + + const char *device_name; /* ≤ BLE_UART_DEVICE_NAME_MAX (26) */ + /* Custom adv payloads (NULL → defaults). + * Limits: adv_data_len ≤ BLE_UART_ADV_DATA_MAX (28), + * scan_rsp_data_len ≤ BLE_UART_SCAN_RSP_DATA_MAX (31). */ + const uint8_t *adv_data; + size_t adv_data_len; + const uint8_t *scan_rsp_data; + size_t scan_rsp_data_len; + ble_uart_rx_cb_t ble_uart_on_rx; + ble_uart_evt_cb_t on_event; /* optional for default Passkey Display; + required for KEYBOARD_ONLY / + DISPLAY_YES_NO / KEYBOARD_DISPLAY */ } ble_uart_config_t; /* === Lifecycle === */ int ble_uart_install(const ble_uart_config_t *cfg); /* host + GATT */ int ble_uart_open(void); /* start advertising (NimBLE: spawn host task) */ int ble_uart_close(void); /* stop adv / disconnect / quiesce host */ -int ble_uart_uninstall(void); /* tear down host + reset state */ +int ble_uart_close_async(void); /* same, fire-and-forget; signals BLE_UART_EVT_CLOSED on completion */ +int ble_uart_uninstall(void); /* best-effort teardown; first error, state always cleared */ /* === Send (callable from any task) === */ int ble_uart_tx(const uint8_t *data, size_t len); @@ -632,10 +1367,25 @@ int ble_uart_tx(const uint8_t *data, size_t len); /* Via the cfg.ble_uart_on_rx callback, signature: * void cb(const uint8_t *data, size_t len); */ +/* === Pairing replies (PASSKEY_REQUEST / NUMERIC_COMPARE only) === */ +int ble_uart_passkey_reply(uint32_t passkey); /* answer PASSKEY_REQUEST */ +int ble_uart_compare_reply(bool match); /* answer NUMERIC_COMPARE */ + /* === Status === */ bool ble_uart_is_connected(void); bool ble_uart_is_subscribed(void); +/* === Bond management (works after install) === */ +typedef struct { + uint8_t bytes[6]; /* big-endian: bytes[0] is MSB */ + uint8_t type; /* BLE_UART_ADDR_TYPE_PUBLIC or _RANDOM */ +} ble_uart_addr_t; + +int ble_uart_get_bond_count(size_t *out_count); +int ble_uart_get_bonded_peers(ble_uart_addr_t *out, size_t cap, size_t *out_count); +int ble_uart_remove_peer(const ble_uart_addr_t *peer); +int ble_uart_clear_bonds(void); + /* === Service UUID (for advertising; usually no need to touch) === */ extern const ble_uart_uuid128_t ble_uart_service_uuid; ``` diff --git a/examples/bluetooth/common/ble_uart/ble_uart.h b/examples/bluetooth/common/ble_uart/ble_uart.h index 6388252e1aa..ccf5cbd24cb 100644 --- a/examples/bluetooth/common/ble_uart/ble_uart.h +++ b/examples/bluetooth/common/ble_uart/ble_uart.h @@ -3,23 +3,31 @@ * * SPDX-License-Identifier: Unlicense OR CC0-1.0 * - * BLE UART — turnkey serial-over-BLE peripheral. + * ESP-BLE-UART — turnkey serial-over-BLE peripheral. * * Implements the de-facto BLE UART-over-GATT layout (RX write, TX notify; * fixed 128-bit UUIDs below) on top of either NimBLE or Bluedroid; the * backend is picked at compile time via CONFIG_BT_NIMBLE_ENABLED / * CONFIG_BT_BLUEDROID_ENABLED. * - * Lifecycle: + * Lifecycle — bring-up: * - * ble_uart_install(&cfg); // host + GATT service - * ble_uart_open(); // start advertising + auto-encrypt - * ... + * nvs_flash_init(); + * ble_uart_install(&cfg); // host + GATT service (once per uninstall) + * ble_uart_open(); // advertising + pairing + * + * Run-forever apps stop after open(). + * + * Lifecycle — release (pick one path; see PORTING.md §5.3): + * + * Path A — from a normal app task (not on_event / on_rx): * ble_uart_close(); // stop adv / disconnect / halt host * ble_uart_uninstall(); // free port + reset state * - * Run-forever apps only need install + open. close / uninstall is - * for apps that need to power BLE off at runtime. + * Path B — teardown triggered by a BLE event on the host task: + * ble_uart_close_async(); // in on_event / on_rx only + * // wait for BLE_UART_EVT_CLOSED (.closed.status == BLE_UART_OK) + * ble_uart_uninstall(); // on an app task — NOT inside CLOSED * * GATT layout (UUIDs are the widely used fixed 128-bit values): * @@ -57,6 +65,23 @@ typedef struct { uint8_t bytes[16]; } ble_uart_uuid128_t; +/* ----- BLE address ---------------------------------------------------- */ + +/** Address type, mirroring the BT Core spec values. */ +#define BLE_UART_ADDR_TYPE_PUBLIC 0 +#define BLE_UART_ADDR_TYPE_RANDOM 1 + +/** 6-octet BLE device address. + * + * `bytes` is in big-endian order — `bytes[0]` is the MSB octet, the + * way addresses are usually printed (`AA:BB:CC:DD:EE:FF`). Both + * backends marshal between this representation and their own native + * byte order internally, so callers never need to flip bytes. */ +typedef struct { + uint8_t bytes[6]; + uint8_t type; /* BLE_UART_ADDR_TYPE_PUBLIC or _RANDOM */ +} ble_uart_addr_t; + /* ----- Configuration -------------------------------------------------- */ /** RX byte callback. Invoked from the BLE host task whenever bytes @@ -71,27 +96,427 @@ typedef struct { * are rejected with ATT error 0x0d. */ typedef void (*ble_uart_rx_cb_t)(const uint8_t *data, size_t len); +/* ----- Event callback ------------------------------------------------- */ + +/** Lifecycle / link-state events delivered to ble_uart_config_t::on_event. + * + * All events fire from the BLE host task context (NimBLE host task / + * Bluedroid BTC task), with one documented exception: + * BLE_UART_EVT_CLOSED is fired by the close-async worker task, after + * the host stack has been torn down — there is no host task left to + * deliver it from. See ble_uart_close_async(). + * + * The same threading rules as ble_uart_on_rx apply: don't block, and + * don't call ble_uart_close() / ble_uart_uninstall() (use the async + * variant if you need to teardown from inside an event handler). */ +typedef enum { + /** Physical link established. Payload: .connected.peer. + * Type is always BLE_UART_ADDR_TYPE_PUBLIC or _RANDOM (each + * backend's wider addr-type enum is collapsed before delivery). + * + * Backend semantics differ: + * - NimBLE: peer identity address (`peer_id_addr`). On first + * connect this equals the over-the-air address; on a bonded + * RPA reconnect it is the resolved identity, not the random + * address currently on the wire. + * - Bluedroid: the BD address recorded at bond time. If the + * peer connected as address_A and bonding succeeded, later + * reconnects still report address_A in CONNECT even when the + * peer's over-the-air address has changed (e.g. a new RPA). + * Matches `get_bonded_peers()` / `remove_peer` (`bytes` only). */ + BLE_UART_EVT_CONNECTED, + + /** Physical link torn down. Payload: .disconnected.reason + * (stack-specific disconnect code — esp_gatt_conn_reason_t on + * Bluedroid, NimBLE BLE host return code on NimBLE; see + * BLE_HS_HCI_ERR() / BLE_HS_ERR_HCI_BASE for HCI encoding). */ + BLE_UART_EVT_DISCONNECTED, + + /** CCCD on the TX characteristic changed. Payload: + * .subscribed.subscribed (true = notifications enabled). */ + BLE_UART_EVT_SUBSCRIBED, + + /** Link reached the encrypted+authenticated state requested at + * install time. Payload: .link_secure.{encrypted, authenticated, + * bonded, key_size}. Use this — not is_connected() — to gate any + * application logic that requires the channel to be secure. */ + BLE_UART_EVT_LINK_SECURE, + + /** SM asks the application to display a 6-digit passkey. + * Payload: .passkey.passkey (0..999999). The default banner on + * UART still prints; this callback is additive so a UI / test + * harness can avoid scraping logs. */ + BLE_UART_EVT_PASSKEY_DISPLAY, + + /** SM asks the application to collect a 6-digit passkey from the + * user (the central displays it; the user types it into this + * device). No payload. + * + * The application MUST respond by calling ble_uart_passkey_reply() + * with the 6 digits the user entered. Until the reply arrives — + * or until the SM's pairing timeout fires (the controller's + * default ~30 s) — pairing is suspended; on timeout the link + * surfaces BLE_UART_EVT_PAIRING_FAILED. + * + * Only fires when cfg.security.io_cap is one of the input-capable + * values (KEYBOARD_ONLY / KEYBOARD_DISPLAY) and the central asks + * for Passkey Entry. */ + BLE_UART_EVT_PASSKEY_REQUEST, + + /** SM asks the application to display a 6-digit value and let the + * user confirm whether the same value appears on the central. + * Payload: .numeric_compare.passkey (0..999999). + * + * The application MUST respond by calling ble_uart_compare_reply() + * with the user's verdict (true = match). Same suspend-and-time- + * out semantics as BLE_UART_EVT_PASSKEY_REQUEST. + * + * Only fires when cfg.security.io_cap is one of the + * comparison-capable values (DISPLAY_YES_NO / KEYBOARD_DISPLAY) + * and the central asks for Numeric Comparison (which itself + * requires LE Secure Connections on both sides). */ + BLE_UART_EVT_NUMERIC_COMPARE, + + /** Pairing failed or was rejected. Payload: .pairing_failed.reason + * (NimBLE BLE_HS_E* / Bluedroid esp_ble_auth_fail_rsn_t). */ + BLE_UART_EVT_PAIRING_FAILED, + + /** Async-close completion — fired only by ble_uart_close_async() + * after the worker task has finished the equivalent of a + * synchronous ble_uart_close(). Payload: .closed.status — the + * return code from that close (BLE_UART_OK on success). + * + * When .closed.status is BLE_UART_OK the host stack is fully + * quiesced — same state as right after ble_uart_close() returns. + * Defer ble_uart_uninstall() to a normal app task (set a flag / + * queue here); do not call uninstall from this handler — see + * PORTING.md §5.3.2. On failure (e.g. BLE_UART_EFAIL) the port + * may still be open; retry ble_uart_close() / ble_uart_close_async() + * from an app task. + * + * Unlike every other event in this enum, this one runs on the + * close-async worker task, NOT on the BLE host task — by the + * time it fires the host task is already gone. Keep the handler + * short; the worker clears s_closing after it returns. */ + BLE_UART_EVT_CLOSED, +} ble_uart_evt_id_t; + +/** Tagged union delivered to ble_uart_config_t::on_event. */ +typedef struct { + ble_uart_evt_id_t id; + union { + struct { + ble_uart_addr_t peer; + } connected; + + struct { + int reason; /* stack-specific disconnect code */ + } disconnected; + + struct { + bool subscribed; + } subscribed; + + struct { + bool encrypted; /* 1 = link is AES-CCM encrypted */ + bool authenticated; /* 1 = pairing used MITM protection */ + bool bonded; /* 1 = LTK persisted in NVS */ + uint8_t key_size; /* 7..16 (octets) */ + } link_secure; + + struct { + uint32_t passkey; /* 0..999999 */ + } passkey; + + struct { + uint32_t passkey; /* 0..999999 — the value to display */ + } numeric_compare; + + struct { + int reason; /* stack-specific status code */ + } pairing_failed; + + struct { + int status; /* BLE_UART_* from async close worker */ + } closed; + }; +} ble_uart_evt_t; + +/** Event callback. May be NULL — events are silently dropped then. */ +typedef void (*ble_uart_evt_cb_t)(const ble_uart_evt_t *evt); + +/* ----- Security configuration ---------------------------------------- */ + +/** Tri-state knob for the per-feature security overrides in + * ble_uart_config_t (`sc`, `bonding`, `mitm`). + * + * AUTO (= 0, the value of a zero-initialised struct member) means + * "use whatever cfg.encrypted implies": + * + * encrypted = true → AUTO behaves as ON + * encrypted = false → AUTO behaves as OFF + * + * OFF / ON force the bit regardless of the preset, letting the + * caller mix the preset with one or two surgical overrides without + * spelling out every other field. */ +typedef enum { + BLE_UART_SEC_AUTO = 0, + BLE_UART_SEC_OFF = 1, + BLE_UART_SEC_ON = 2, +} ble_uart_sec_t; + +/** SM Input/Output capability — combines with the central's IO cap and + * the resolved `mitm` bit to pick the pairing model (Just Works / + * Passkey Display / Passkey Entry / Numeric Comparison — see BT Core + * Spec §2.3.5.1). The application doesn't decide the method directly; + * it picks the IO cap that matches its UI and ble_uart fires the right + * event when the SM negotiates a method. + * + * Passing an out-of-range integer makes ble_uart_install() return + * BLE_UART_EINVAL. Only the input-capable values (KEYBOARD_ONLY, + * DISPLAY_YES_NO, KEYBOARD_DISPLAY) require cfg.on_event to be + * non-NULL — pairing would otherwise stall on unanswered + * BLE_UART_EVT_PASSKEY_REQUEST / NUMERIC_COMPARE. AUTO (resolves to + * DisplayOnly when MITM is ON), DISPLAY_ONLY, and NO_INPUT_OUTPUT do + * not require on_event; Passkey Display is handled internally. */ +typedef enum { + /** Default: DisplayOnly when the resolved MITM bit is ON; + * NoInputNoOutput when it is OFF. */ + BLE_UART_IO_CAP_AUTO = 0, + + /** Device has no UI; pairing always uses Just Works. Cannot + * satisfy MITM — combining this with mitm=ON makes + * ble_uart_install() return BLE_UART_EINVAL. */ + BLE_UART_IO_CAP_NO_INPUT_OUTPUT = 1, + + /** Device shows a 6-digit passkey on a display; the central + * enters it. Generates a fresh passkey for every pairing, + * surfaced via BLE_UART_EVT_PASSKEY_DISPLAY (no reply call + * needed — the central does the typing). */ + BLE_UART_IO_CAP_DISPLAY_ONLY = 2, + + /** Device has keys (or some other way to feed digits to the + * library) but no display; the central displays a 6-digit + * passkey, the user reads it from there and types it in. + * + * ble_uart fires BLE_UART_EVT_PASSKEY_REQUEST and waits for + * ble_uart_passkey_reply(). Requires cfg.on_event != NULL. */ + BLE_UART_IO_CAP_KEYBOARD_ONLY = 3, + + /** Device has a display + a yes/no confirmation control. With a + * similarly-equipped LE Secure Connections central this elects + * Numeric Comparison: ble_uart fires BLE_UART_EVT_NUMERIC_COMPARE + * with the 6-digit value to display, and waits for + * ble_uart_compare_reply(). + * + * Falls back to Just Works against legacy or NoInput peers. + * Requires cfg.on_event != NULL. */ + BLE_UART_IO_CAP_DISPLAY_YES_NO = 4, + + /** Device has a display AND a keypad (covers both Numeric + * Comparison and Passkey Entry). Best fit for a touchscreen UI + * that wants to handle every MITM-capable peer. + * + * ble_uart fires either BLE_UART_EVT_PASSKEY_REQUEST or + * BLE_UART_EVT_NUMERIC_COMPARE depending on what the SM + * negotiates with the central; respond with the matching reply + * API. Requires cfg.on_event != NULL. */ + BLE_UART_IO_CAP_KEYBOARD_DISPLAY = 5, +} ble_uart_io_cap_t; + +/** Per-feature security overrides, embedded in ble_uart_config_t. + * + * Each tri-state field defaults to AUTO (= 0, the value of any + * zero-initialised member), inheriting its bit from + * ble_uart_config_t::encrypted: + * + * encrypted = true → AUTO behaves as ON + * encrypted = false → AUTO behaves as OFF + * + * Set any field to OFF / ON to override that single bit while the + * rest still follow the preset. Common patterns are listed in + * PORTING.md §5.6 (e.g. encrypted=true with mitm=OFF + + * io_cap=NO_INPUT_OUTPUT for a displayless gateway). + * + * Combinations the SM cannot satisfy — io_cap=NO_INPUT_OUTPUT + * together with the resolved mitm=ON, or an out-of-range enum value + * — make ble_uart_install() return BLE_UART_EINVAL up front, before + * the host stack is brought up. */ +typedef struct { + /** Override LE Secure Connections (the BT 4.2+ pairing method + * that uses ECDH for the LTK). */ + ble_uart_sec_t sc; + + /** Override bonding (persistence of the LTK / IRK / persisted + * CCCD in NVS). With bonding=OFF the link is still encrypted + * (if sc/mitm are on) but every reconnect re-pairs. */ + ble_uart_sec_t bonding; + + /** Override MITM protection (man-in-the-middle: link + * authentication via passkey display / entry / numeric + * comparison). With mitm=OFF the link pairs via Just Works, + * which is encrypted but unauthenticated; the GATT permission + * flags drop their _AUTHEN bit so a Just-Works peer can + * read/write the UART characteristics. */ + ble_uart_sec_t mitm; + + /** SM IO capability — controls which pairing model is chosen + * alongside `mitm`. AUTO picks DisplayOnly when the resolved + * MITM bit is ON, NoInputNoOutput when it is OFF. */ + ble_uart_io_cap_t io_cap; +} ble_uart_security_t; + +/* ----- Advertising payload limits ------------------------------------ */ + +/** Maximum bytes the application may put in `adv_data`. + * + * BLE 4.x legacy primary advertising packets are capped at 31 bytes + * total. Of those, the 3-byte Flags AD element (length+type+value) + * is always added by ble_uart, leaving 31 − 3 = 28 bytes for the + * application. */ +#define BLE_UART_ADV_DATA_MAX 28 + +/** Maximum bytes the application may put in `scan_rsp_data`. + * + * Scan response packets are also capped at 31 bytes, with no + * mandatory AD elements — the entire 31 bytes belong to the + * application. */ +#define BLE_UART_SCAN_RSP_DATA_MAX 31 + +/** Maximum length (bytes, excluding NUL terminator) of `device_name`. + * + * Sized so that the *default* advertising payload — Flags AD + + * Complete Local Name AD — always fits in the 31-byte primary packet: + * + * 31 − 3 (Flags AD) − 2 (Name AD header) = 26 + * + * Names that exceed this length make `ble_uart_install()` return + * `BLE_UART_EINVAL` synchronously, instead of silently failing later + * in the host stack when advertising starts. + * + * This applies regardless of whether `adv_data` is set — the GAP + * service Device Name characteristic (UUID 0x2A00) reports the same + * string. Apps that need a longer GAP-service name with a shorter + * advertised name should keep `device_name` ≤ this limit and use + * `adv_data` to broadcast a shortened/different name instead. */ +#define BLE_UART_DEVICE_NAME_MAX 26 + /** Configuration handed to ble_uart_install(). */ typedef struct { - /** True = LE Secure Connections + Bonding + MITM, DisplayOnly IO, - * encrypted RX/TX chars, bond persisted in NVS (NimBLE: requires - * CONFIG_BT_NIMBLE_NVS_PERSIST=y; Bluedroid: default). - * False = plaintext (lab debugging only — sniffable). */ + /** Security preset (a one-line shortcut for the four override + * fields under `security` below). + * + * True = LE Secure Connections + Bonding + MITM, DisplayOnly IO, + * encrypted+authenticated RX/TX chars, bond persisted in + * NVS (NimBLE: requires CONFIG_BT_NIMBLE_NVS_PERSIST=y; + * Bluedroid: default). + * False = plaintext (lab debugging only — sniffable). + * + * Every member of `security` defaults to AUTO, meaning "follow + * this preset". Override individual bits there; see + * ble_uart_security_t for the resolution rules. */ bool encrypted; - /** GAP device name. NULL keeps the host stack default. Mind the - * 31-byte primary advertising limit (≤ 8 bytes recommended). */ + /** Per-feature security overrides. A zero-initialised value + * (every field AUTO) inherits everything from `encrypted`, so + * callers that just want the secure-by-default preset can leave + * this field unset: + * + * ble_uart_install(&(ble_uart_config_t){ + * .encrypted = true, // sc/bonding/mitm/io_cap all AUTO + * ... + * }); + * + * Surgical override: + * + * ble_uart_install(&(ble_uart_config_t){ + * .encrypted = true, + * .security = { .mitm = BLE_UART_SEC_OFF }, // SC + Bonding, no MITM + * ... + * }); + * + * See ble_uart_security_t for the full per-field docs. */ + ble_uart_security_t security; + + /** GAP device name (peer-readable via the GAP service, UUID 0x2A00). + * NULL keeps the host-stack default. + * + * Length must be ≤ BLE_UART_DEVICE_NAME_MAX (26) — over-long + * strings make ble_uart_install() return BLE_UART_EINVAL. + * + * This is NOT automatically inserted into the advertising payload + * when `adv_data` (below) is non-NULL — if you want the name to + * appear in scans without connecting, include a Complete Local + * Name AD element (type 0x09) in your `adv_data` bytes yourself. */ const char *device_name; + /** Optional raw advertising data — everything that goes after the + * 3-byte Flags AD element in the primary advertising packet. The + * Flags element is built by ble_uart and is NOT part of these + * bytes (don't include it). + * + * Format: standard BT Core "AD structure" sequence — repeating + * `[length(1)][AD type(1)][value(length-1)]` triplets. See the + * Bluetooth Assigned Numbers (Generic Access Profile) document + * for the full type list. + * + * Length must be ≤ BLE_UART_ADV_DATA_MAX (28). The buffer is + * copied at install time; the pointer does not need to outlive + * the call. + * + * Set to NULL (with adv_data_len=0) to keep the built-in default, + * which advertises only the Complete Local Name (taken from + * device_name). */ + const uint8_t *adv_data; + size_t adv_data_len; + + /** Optional raw scan response data — entire 31-byte payload is at + * the application's disposal; ble_uart adds nothing. + * + * Same `[len][type][value]` format and copy semantics as + * adv_data. Length must be ≤ BLE_UART_SCAN_RSP_DATA_MAX (31). + * + * Set to NULL (with scan_rsp_data_len=0) to keep the built-in + * default, which advertises the 128-bit BLE UART service UUID. */ + const uint8_t *scan_rsp_data; + size_t scan_rsp_data_len; + /** Byte handler for RX writes. NULL discards incoming data. */ ble_uart_rx_cb_t ble_uart_on_rx; + + /** Lifecycle / link-state event sink. NULL drops every event. + * See ble_uart_evt_id_t for the supported events; runs on the + * BLE host task with the same caveats as ble_uart_on_rx. */ + ble_uart_evt_cb_t on_event; } ble_uart_config_t; /* ----- Lifecycle ------------------------------------------------------ */ /** Bring up host stack + Security Manager + SIG services + BLE UART GATT * service. Caller must have already called nvs_flash_init(). - * cfg->device_name is copied; doesn't need to outlive the call. + * + * cfg->device_name, cfg->adv_data and cfg->scan_rsp_data are all + * copied internally; the caller's buffers don't need to outlive the + * call. Returns BLE_UART_EINVAL if any of these checks fail: + * strlen(cfg->device_name) > BLE_UART_DEVICE_NAME_MAX (26) + * cfg->adv_data_len > BLE_UART_ADV_DATA_MAX (28) + * cfg->scan_rsp_data_len > BLE_UART_SCAN_RSP_DATA_MAX (31) + * cfg->security.{sc,bonding,mitm} outside BLE_UART_SEC_{AUTO,OFF,ON} + * cfg->security.io_cap outside BLE_UART_IO_CAP_{AUTO, + * NO_INPUT_OUTPUT,DISPLAY_ONLY, + * KEYBOARD_ONLY,DISPLAY_YES_NO, + * KEYBOARD_DISPLAY} + * resolved mitm=ON + io_cap=NO_INPUT_OUTPUT + * (Just Works can never satisfy MITM) + * io_cap requires user input (KEYBOARD_ONLY, DISPLAY_YES_NO, + * KEYBOARD_DISPLAY) but cfg->on_event + * is NULL — the application would have + * no way to receive PASSKEY_REQUEST / + * NUMERIC_COMPARE and answer it + * (io_cap=AUTO with resolved mitm=ON, or DISPLAY_ONLY, does not + * need on_event — equivalent to Passkey Display handled inside + * the port; PASSKEY_DISPLAY via on_event is optional) + * * Single-shot until ble_uart_uninstall(); a second call returns * BLE_UART_EALREADY. */ int ble_uart_install(const ble_uart_config_t *cfg); @@ -101,7 +526,7 @@ int ble_uart_install(const ble_uart_config_t *cfg); * Bluedroid: triggers adv-data + scan-response config; advertising * begins once the stack acknowledges both. * - * Returns immediately; the BLE UART then runs autonomously + * Returns immediately; the ESP-BLE-UART then runs autonomously * (connect, pairing, passkey display, RX delivery all via internal * callbacks). Single-shot. */ int ble_uart_open(void); @@ -111,16 +536,84 @@ int ble_uart_open(void); * quiesces the host. install state is preserved — call open() again * to resume. * - * Don't call from the BLE host task (i.e. from ble_uart_on_rx). */ + * NimBLE: also resets the local GATT server; the next open() re-adds + * GAP/GATT/UART. Service UUIDs are unchanged but ATT handles may + * differ — centrals must rediscover and re-subscribe (PORTING.md + * §5.3.1a). Bluedroid: host and GATT table stay up; open() only + * restarts advertising. + * + * Don't call from the BLE host task (i.e. from ble_uart_on_rx or + * ble_uart_evt_cb_t) — it would deadlock waiting for the disconnect + * event that the host task itself is supposed to deliver. Use + * ble_uart_close_async() in those contexts instead. */ int ble_uart_close(void); +/** Fire-and-forget variant of ble_uart_close(). Returns immediately + * after spawning a small worker task that runs the regular close + * sequence in the background; safe from ANY task — including the + * BLE host task (i.e. from inside ble_uart_on_rx or on_event), where + * the synchronous variant deadlocks. + * + * Completion is reported on the on_event callback as + * BLE_UART_EVT_CLOSED with .closed.status set to the worker's + * ble_uart_close() result. When status is BLE_UART_OK the host stack + * is fully torn down — then uninstall on an app task after + * BLE_UART_EVT_CLOSED (PORTING.md §5.3.2 Path B). + * (BLE_UART_EVT_DISCONNECTED is also delivered, ahead of CLOSED, if + * there was a peer.) + * + * Idempotent in the harmless sense: calling it before + * ble_uart_open() has succeeded, or while a previous async close + * is still draining, returns BLE_UART_EALREADY without spawning a + * second worker. Returns BLE_UART_ENOMEM if FreeRTOS can't + * allocate the worker task. */ +int ble_uart_close_async(void); + /** Counterpart to ble_uart_install(). Force-closes if still open, * then tears down the host stack + controller. After this returns, * install() can run from scratch. * - * Don't call from the BLE host task. */ + * Don't call from the BLE host task (NimBLE host / Bluedroid BTC). + * If a ble_uart_close_async() worker is still running, this call + * polls for up to ~5 s and then proceeds with teardown anyway if the + * worker has not exited — do not call uninstall from another task + * while a close_async is in flight unless you follow PORTING.md §5.3: + * Path A — ble_uart_close() then uninstall from an app task; or + * Path B — close_async, then uninstall on an app task after + * BLE_UART_EVT_CLOSED with .closed.status == BLE_UART_OK (never + * call uninstall from inside the CLOSED handler). */ int ble_uart_uninstall(void); +/* ----- Pairing replies ----------------------------------------------- */ + +/** Answer an in-flight BLE_UART_EVT_PASSKEY_REQUEST. + * + * `passkey` is the 6-digit value the user read off the central's + * display and entered on this device — must be in 0..999999. + * + * Safe from any task. Returns: + * BLE_UART_OK reply was injected into the SM + * BLE_UART_EINVAL passkey > 999999 + * BLE_UART_ENOTCONN no PASSKEY_REQUEST is currently pending + * (link dropped, pairing already timed out, + * or the SM asked for something else) + * BLE_UART_EFAIL backend rejected the inject + * + * Each PASSKEY_REQUEST event accepts exactly one reply; subsequent + * calls return BLE_UART_ENOTCONN until the next request. */ +int ble_uart_passkey_reply(uint32_t passkey); + +/** Answer an in-flight BLE_UART_EVT_NUMERIC_COMPARE. + * + * `match` is the user's verdict: true if the 6-digit values shown + * on this device and on the central are identical, false otherwise. + * A `false` reply makes pairing fail with a numeric-comparison + * mismatch, surfaced as BLE_UART_EVT_PAIRING_FAILED. + * + * Same threading semantics and return codes as + * ble_uart_passkey_reply(). */ +int ble_uart_compare_reply(bool match); + /* ----- TX ------------------------------------------------------------- */ /** Send raw bytes to the connected central as one or more TX @@ -143,6 +636,68 @@ bool ble_uart_is_connected(void); * the CCCD write); exposed for diagnostics only. */ bool ble_uart_is_subscribed(void); +/* ----- Bond management ----------------------------------------------- */ + +/** Number of bonded peers in the persistent store. + * + * Requires ble_uart_install() to have run; works whether or not + * ble_uart_open() has been called. *out_count is left untouched on + * failure. Safe from any task. */ +int ble_uart_get_bond_count(size_t *out_count); + +/** List the bonded peers' addresses. + * + * Up to `cap` entries are written to `out`; on success *out_count + * receives the **total** number of bonds (which may exceed `cap`). + * When *out_count > cap the caller may allocate a larger buffer + * and re-call to read the rest. + * + * `out` may be NULL if `cap` is 0 — useful as a preflight to size + * an exactly-fitting buffer (although ble_uart_get_bond_count() + * does the same with one less argument). + * + * Safe from any task. Requires ble_uart_install() to have run. */ +int ble_uart_get_bonded_peers(ble_uart_addr_t *out, + size_t cap, + size_t *out_count); + +/** Drop the bond (LTK / IRK / persisted CCCD) for one peer. + * + * Does not actively disconnect the current link (encrypted or not). + * Call ble_uart_close() first if you need an immediate disconnect + * and re-pair. + * + * `peer` is matched against the identity address in the bond store. + * Backend matching: + * - NimBLE: `(type, bytes)`. `BLE_UART_EVT_CONNECTED` and + * `get_bonded_peers()` both yield identity addresses suitable + * for this call (first connect: same as over-the-air; bonded RPA + * reconnect: resolved identity, not the random on the wire). + * - Bluedroid: `bytes` only — `type` is ignored. The bond store + * and CONNECT both use the address seen when bonding was + * established (address_A); later over-the-air changes are not + * reflected in either API. + * + * Idempotent: returns BLE_UART_OK whether or not the peer was bonded + * (both backends treat "already absent" as success — NimBLE's + * ble_store_util_delete_peer maps BLE_HS_ENOENT to 0). Use + * ble_uart_get_bonded_peers() first if you need to distinguish + * "removed" from "was never bonded". + * + * Returns BLE_UART_EINVAL if peer is NULL or ble_uart_install() has + * not run. Safe from any task. */ +int ble_uart_remove_peer(const ble_uart_addr_t *peer); + +/** Drop ALL bonded peers — equivalent to a factory reset of the bond + * store, but does not touch any other NVS namespace. + * + * Does not actively disconnect the current link (encrypted or not). + * Call ble_uart_close() first if you need an immediate disconnect + * and re-pair. + * + * Returns BLE_UART_OK if the store was cleared. Safe from any task. */ +int ble_uart_clear_bonds(void); + /* ----- Service UUID -------------------------------------------------- */ /** The BLE UART service UUID, exposed for custom advertising payloads. diff --git a/examples/bluetooth/common/ble_uart/ble_uart_bluedroid.c b/examples/bluetooth/common/ble_uart/ble_uart_bluedroid.c index 26b41ac585a..7bd3474ee08 100644 --- a/examples/bluetooth/common/ble_uart/ble_uart_bluedroid.c +++ b/examples/bluetooth/common/ble_uart/ble_uart_bluedroid.c @@ -3,7 +3,7 @@ * * SPDX-License-Identifier: Unlicense OR CC0-1.0 * - * BLE UART — Bluedroid backend. Implements the lifecycle declared in + * ESP-BLE-UART — Bluedroid backend. Implements the lifecycle declared in * ble_uart.h on top of the Bluedroid host using the service-table API * (esp_ble_gatts_create_attr_tab). Active when * CONFIG_BT_BLUEDROID_ENABLED=y; otherwise ble_uart_nimble.c is used. @@ -21,6 +21,7 @@ #include "ble_uart.h" #include +#include #include #include "freertos/FreeRTOS.h" @@ -109,9 +110,42 @@ static bool s_subscribed; static bool s_installed; static bool s_opened; static bool s_shutting_down; +/* Set by ble_uart_close_async() when its worker task is in flight, + * cleared by the worker just before it exits. uninstall() polls this + * to drain a pending async close before tearing the stack down. */ +static volatile bool s_closing; static volatile bool s_attr_tab_ready; static bool s_adv_active; +/* Resolved security policy. Computed once in install() from cfg.encrypted + * + the per-feature overrides (cfg.sc/bonding/mitm/io_cap), then read + * by the GAP event handler and the GATT-table builder. + * s_link_encrypted = true if any of {sc, bonding, mitm} resolved ON + * → kick pairing on connect, require encryption on chars + * s_mitm_required = resolved mitm bit + * → require ENC_MITM perm flags (Just-Works peer cannot read/write) */ +static bool s_link_encrypted; +static bool s_mitm_required; + +/* Pending Passkey-Entry / Numeric-Comparison request awaiting an + * application reply via ble_uart_passkey_reply / ble_uart_compare_reply. + * + * Bluedroid identifies the pairing peer by BD address (no conn-handle + * exposed at the SM layer), so we cache it in s_pending_io_bda. The + * `kind` field discriminates the two flavors so the wrong reply API + * is rejected up front. NONE = no request in flight. + * + * No FreeRTOS lock — both fields are written only from the BTC task, + * and the reply API is the only outside reader. The reader takes a + * local snapshot before issuing the SDK reply call. */ +typedef enum { + PENDING_IO_NONE = 0, + PENDING_IO_PASSKEY, /* expects ble_uart_passkey_reply */ + PENDING_IO_NUMCMP, /* expects ble_uart_compare_reply */ +} pending_io_kind_t; +static volatile pending_io_kind_t s_pending_io_kind; +static esp_bd_addr_t s_pending_io_bda; + /* Two-bit latch driving the "configure adv data + scan rsp before * start_advertising" sequence. start_advertising fires only when both * SET_COMPLETE_EVT events have cleared their bit. */ @@ -119,11 +153,26 @@ static bool s_adv_active; #define SCAN_RSP_CONFIG_FLAG (1 << 1) static uint8_t s_adv_config_done; +/* Optional user-supplied advertising payloads. When *_len is non-zero + * we feed *_data straight to the Bluedroid raw configuration API + * (esp_ble_gap_config_adv_data_raw / config_scan_rsp_data_raw); the + * adv buffer carries the 3-byte Flags AD element we built in install() + * followed by the user's bytes. Zero length means "use the default + * struct-based path in configure_advertising()". */ +static uint8_t s_adv_data_buf[3 + BLE_UART_ADV_DATA_MAX]; +static uint8_t s_adv_data_len; +static uint8_t s_scan_rsp_buf[BLE_UART_SCAN_RSP_DATA_MAX]; +static uint8_t s_scan_rsp_len; + /* Long-write accumulator (Bluedroid doesn't reassemble for us). */ static uint8_t s_rx_buf[RX_SCRATCH]; static uint16_t s_prep_len; static bool s_prep_bad; +/* Forward declaration so handle_write() / GATT-event handler / GAP-event + * handler can fire events before emit_evt's body lower in this file. */ +static void emit_evt(const ble_uart_evt_t *evt); + /* ===== Backend rc → public rc ========================================= */ static int xlate_rc(esp_err_t rc) @@ -139,17 +188,26 @@ static int xlate_rc(esp_err_t rc) /* ===== GATT attribute table =========================================== */ -/* Permissions are patched at install time depending on cfg.encrypted. */ +/* Permissions are patched at install time depending on the resolved + * security policy. Three permission tiers: + * !link_enc → plain READ / WRITE (any peer) + * link_enc && !mitm → ENCRYPTED (Just-Works peer OK; auth bit not required) + * link_enc && mitm → ENC_MITM (only authenticated peers) */ static esp_gatts_attr_db_t s_nus_db[NUS_IDX_NB]; -static void build_attr_table(bool encrypted) +static void build_attr_table(bool link_enc, bool mitm) { - const esp_gatt_perm_t r_perm = encrypted - ? (ESP_GATT_PERM_READ_ENC_MITM) - : (ESP_GATT_PERM_READ); - const esp_gatt_perm_t w_perm = encrypted - ? (ESP_GATT_PERM_WRITE_ENC_MITM) - : (ESP_GATT_PERM_WRITE); + esp_gatt_perm_t r_perm, w_perm; + if (!link_enc) { + r_perm = ESP_GATT_PERM_READ; + w_perm = ESP_GATT_PERM_WRITE; + } else if (mitm) { + r_perm = ESP_GATT_PERM_READ_ENC_MITM; + w_perm = ESP_GATT_PERM_WRITE_ENC_MITM; + } else { + r_perm = ESP_GATT_PERM_READ_ENCRYPTED; + w_perm = ESP_GATT_PERM_WRITE_ENCRYPTED; + } /* [SVC] primary service declaration */ s_nus_db[NUS_IDX_SVC] = (esp_gatts_attr_db_t){ @@ -238,7 +296,7 @@ static void build_attr_table(bool encrypted) static esp_ble_adv_data_t s_adv_data = { .set_scan_rsp = false, .include_name = true, - .include_txpower = true, + .include_txpower = false, .min_interval = 0, .max_interval = 0, .appearance = 0x00, @@ -251,8 +309,9 @@ static esp_ble_adv_data_t s_adv_data = { .flag = (ESP_BLE_ADV_FLAG_GEN_DISC | ESP_BLE_ADV_FLAG_BREDR_NOT_SPT), }; -/* Scan response = 128-bit UART service UUID. Splitting it off the primary payload - * leaves room for name + tx_pwr in the 31-byte primary. */ +/* Scan response = 128-bit UART service UUID. Splitting it off the primary + * payload leaves room for the Complete Local Name in the 31-byte primary + * (an 18-byte UUID element + a 9-byte name AD wouldn't fit alongside Flags). */ static esp_ble_adv_data_t s_scan_rsp_data = { .set_scan_rsp = true, .include_name = false, @@ -284,6 +343,11 @@ static int start_advertising(void) /* Push adv data + scan response. start_advertising is triggered from * the matching SET_COMPLETE_EVT once both halves are realised. * + * Each half independently picks struct-API (default payload) or raw-API + * (when the app supplied bytes via cfg). The two SET_COMPLETE event + * variants (regular vs. _RAW_) clear the same latch bit, so the GAP + * handler doesn't need to know which path we took. + * * On a sync failure of either config call, the matching SET_COMPLETE_EVT * will NEVER fire — so we must wipe the latch entirely (not just clear * one bit) to avoid (a) advertising silently lost, or (b) the other @@ -292,13 +356,23 @@ static int configure_advertising(void) { s_adv_config_done = ADV_CONFIG_FLAG | SCAN_RSP_CONFIG_FLAG; - esp_err_t rc = esp_ble_gap_config_adv_data(&s_adv_data); + esp_err_t rc; + if (s_adv_data_len > 0) { + rc = esp_ble_gap_config_adv_data_raw(s_adv_data_buf, s_adv_data_len); + } else { + rc = esp_ble_gap_config_adv_data(&s_adv_data); + } if (rc != ESP_OK) { ESP_LOGE(TAG, "config_adv_data rc=%s", esp_err_to_name(rc)); s_adv_config_done = 0; return xlate_rc(rc); } - rc = esp_ble_gap_config_adv_data(&s_scan_rsp_data); + + if (s_scan_rsp_len > 0) { + rc = esp_ble_gap_config_scan_rsp_data_raw(s_scan_rsp_buf, s_scan_rsp_len); + } else { + rc = esp_ble_gap_config_adv_data(&s_scan_rsp_data); + } if (rc != ESP_OK) { ESP_LOGE(TAG, "config_scan_rsp rc=%s", esp_err_to_name(rc)); /* adv_data is in flight; its SET_COMPLETE_EVT will hit the @@ -420,8 +494,17 @@ static void handle_write(esp_ble_gatts_cb_param_t *p) } else { uint16_t cccd = (uint16_t)p->write.value[0] | ((uint16_t)p->write.value[1] << 8); - s_subscribed = (cccd & 0x0001) != 0; - ESP_LOGI(TAG, "subscribe cccd=0x%04x sub=%d", cccd, s_subscribed); + bool sub = (cccd & 0x0001) != 0; + ESP_LOGI(TAG, "subscribe cccd=0x%04x sub=%d", cccd, sub); + /* Edge-trigger: a redundant CCCD write (same value twice) + * shouldn't double-fire SUBSCRIBED. */ + if (sub != s_subscribed) { + s_subscribed = sub; + emit_evt(&(ble_uart_evt_t){ + .id = BLE_UART_EVT_SUBSCRIBED, + .subscribed = { .subscribed = sub }, + }); + } } } @@ -508,7 +591,7 @@ static void gatts_profile_event_handler(esp_gatts_cb_event_t event, ESP_LOGI(TAG, "mtu=%u (conn=%u)", s_local_mtu, param->mtu.conn_id); break; - case ESP_GATTS_CONNECT_EVT: + case ESP_GATTS_CONNECT_EVT: { /* Bluedroid only fires this on a successful physical link; * the param struct has no status field. */ s_conn_id = param->connect.conn_id; @@ -521,24 +604,61 @@ static void gatts_profile_event_handler(esp_gatts_cb_event_t event, memcpy(s_remote_bda, param->connect.remote_bda, sizeof(s_remote_bda)); ESP_LOGI(TAG, "connect conn_id=%u remote " ESP_BD_ADDR_STR, s_conn_id, ESP_BD_ADDR_HEX(s_remote_bda)); - if (s_cfg.encrypted) { + /* esp_bd_addr_t is already MSB-first, matches our public + * bytes[] convention — no byte reversal needed. Narrow the + * 4-value Bluedroid addr type into our public 2-value enum + * (RPA_* collapse onto their underlying public/random type). */ + ble_uart_evt_t e = { .id = BLE_UART_EVT_CONNECTED }; + memcpy(e.connected.peer.bytes, param->connect.remote_bda, 6); + e.connected.peer.type = + (param->connect.ble_addr_type == BLE_ADDR_TYPE_PUBLIC + || param->connect.ble_addr_type == BLE_ADDR_TYPE_RPA_PUBLIC) + ? BLE_UART_ADDR_TYPE_PUBLIC + : BLE_UART_ADDR_TYPE_RANDOM; + emit_evt(&e); + if (s_link_encrypted) { /* Kick pairing immediately rather than lazily on the - * first encrypted attribute access. */ - esp_ble_set_encryption(param->connect.remote_bda, - ESP_BLE_SEC_ENCRYPT_MITM); + * first encrypted attribute access. The security level + * tracks the resolved MITM bit so a mitm=OFF peer is + * allowed to pair via Just Works. */ + esp_ble_sec_act_t sec_act = s_mitm_required + ? ESP_BLE_SEC_ENCRYPT_MITM + : ESP_BLE_SEC_ENCRYPT_NO_MITM; + esp_ble_set_encryption(param->connect.remote_bda, sec_act); } break; + } case ESP_GATTS_DISCONNECT_EVT: ESP_LOGI(TAG, "disconnect conn_id=%u reason=0x%x", param->disconnect.conn_id, param->disconnect.reason); + /* Drop any pending Passkey-Entry / NC reply; pairing was + * cancelled along with the link. */ + s_pending_io_kind = PENDING_IO_NONE; + /* Match NimBLE's BLE_GAP_SUBSCRIBE_REASON_TERM behaviour: if + * the central was subscribed when the link dropped, synthesize + * an "implicit unsubscribe" event before DISCONNECTED so a + * strict state-machine consumer can rely on a single rule + * ("SUBSCRIBED tracks notification flow") regardless of host + * stack. NimBLE does this in ble_gatts.c on TERM; Bluedroid + * doesn't, so we do it here. */ + if (s_subscribed) { + s_subscribed = false; + emit_evt(&(ble_uart_evt_t){ + .id = BLE_UART_EVT_SUBSCRIBED, + .subscribed = { .subscribed = false }, + }); + } s_conn_id = 0xFFFF; - s_subscribed = false; /* MTU is per-connection: reset to the spec default 23 so the * next peer (if it skips the MTU exchange) doesn't inherit * the previous link's negotiated value and overflow tx chunks. */ s_local_mtu = 23; prep_reset(); + emit_evt(&(ble_uart_evt_t){ + .id = BLE_UART_EVT_DISCONNECTED, + .disconnected = { .reason = (int)param->disconnect.reason }, + }); if (!s_shutting_down) { start_advertising(); } @@ -576,15 +696,23 @@ static void gap_event_handler(esp_gap_ble_cb_event_t event, * 1) drop stale events (latch already zeroed by a sync failure); * 2) drop async failures (status != SUCCESS) and wipe the latch * so the other half can't satisfy the "==0 → start_adv" check - * and launch advertising with a malformed payload. */ + * and launch advertising with a malformed payload. + * + * Each side handles two event variants — the regular SET_COMPLETE + * (struct API) and the _RAW_ variant (raw-bytes API). They both + * clear the same latch bit; only the parameter struct differs. */ case ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT: + case ESP_GAP_BLE_ADV_DATA_RAW_SET_COMPLETE_EVT: { if (!(s_adv_config_done & ADV_CONFIG_FLAG)) { ESP_LOGD(TAG, "stale ADV_DATA_SET_COMPLETE_EVT ignored"); break; } - if (param->adv_data_cmpl.status != ESP_BT_STATUS_SUCCESS) { - ESP_LOGE(TAG, "adv_data set failed status=0x%x", - param->adv_data_cmpl.status); + esp_bt_status_t st = + (event == ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT) + ? param->adv_data_cmpl.status + : param->adv_data_raw_cmpl.status; + if (st != ESP_BT_STATUS_SUCCESS) { + ESP_LOGE(TAG, "adv_data set failed status=0x%x", st); s_adv_config_done = 0; break; } @@ -593,15 +721,20 @@ static void gap_event_handler(esp_gap_ble_cb_event_t event, start_advertising(); } break; + } case ESP_GAP_BLE_SCAN_RSP_DATA_SET_COMPLETE_EVT: + case ESP_GAP_BLE_SCAN_RSP_DATA_RAW_SET_COMPLETE_EVT: { if (!(s_adv_config_done & SCAN_RSP_CONFIG_FLAG)) { ESP_LOGD(TAG, "stale SCAN_RSP_DATA_SET_COMPLETE_EVT ignored"); break; } - if (param->scan_rsp_data_cmpl.status != ESP_BT_STATUS_SUCCESS) { - ESP_LOGE(TAG, "scan_rsp set failed status=0x%x", - param->scan_rsp_data_cmpl.status); + esp_bt_status_t st = + (event == ESP_GAP_BLE_SCAN_RSP_DATA_SET_COMPLETE_EVT) + ? param->scan_rsp_data_cmpl.status + : param->scan_rsp_data_raw_cmpl.status; + if (st != ESP_BT_STATUS_SUCCESS) { + ESP_LOGE(TAG, "scan_rsp set failed status=0x%x", st); s_adv_config_done = 0; break; } @@ -610,6 +743,7 @@ static void gap_event_handler(esp_gap_ble_cb_event_t event, start_advertising(); } break; + } case ESP_GAP_BLE_ADV_START_COMPLETE_EVT: if (param->adv_start_cmpl.status != ESP_BT_STATUS_SUCCESS) { @@ -618,7 +752,20 @@ static void gap_event_handler(esp_gap_ble_cb_event_t event, s_adv_active = false; break; } - ESP_LOGI(TAG, "advertising started"); + /* With a caller-supplied adv_data the broadcast name is whatever + * bytes the caller put in — not necessarily the GAP-service + * Device Name. Log each path differently so a misconfigured + * payload is easy to spot. (The GAP name itself isn't echoed + * here on this backend: Bluedroid swallows the pointer in + * REG_EVT and there's no sync getter.) */ + if (s_adv_data_len > 0 || s_scan_rsp_len > 0) { + ESP_LOGI(TAG, "advertising with custom payload " + "(adv=%u B, scan_rsp=%u B)", + (unsigned)s_adv_data_len, + (unsigned)s_scan_rsp_len); + } else { + ESP_LOGI(TAG, "advertising started"); + } break; case ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT: @@ -626,16 +773,45 @@ static void gap_event_handler(esp_gap_ble_cb_event_t event, ESP_LOGI(TAG, "advertising stopped"); break; - case ESP_GAP_BLE_PASSKEY_NOTIF_EVT: - show_passkey(param->ble_security.key_notif.passkey); + case ESP_GAP_BLE_PASSKEY_NOTIF_EVT: { + uint32_t pk = param->ble_security.key_notif.passkey; + /* Banner stays for backward compat with log-scraping tests; + * on_event is additive. */ + show_passkey(pk); + emit_evt(&(ble_uart_evt_t){ + .id = BLE_UART_EVT_PASSKEY_DISPLAY, + .passkey = { .passkey = pk }, + }); break; + } case ESP_GAP_BLE_AUTH_CMPL_EVT: { esp_ble_auth_cmpl_t *a = ¶m->ble_security.auth_cmpl; + /* Pairing has resolved one way or the other; clear any pending + * Passkey-Entry / NC request so the next pairing starts fresh + * and a stale reply from a slow user gets rejected. */ + s_pending_io_kind = PENDING_IO_NONE; if (a->success) { ESP_LOGI(TAG, "pairing ok auth_mode=0x%x", a->auth_mode); + /* Bluedroid has no `key_size` field on auth_cmpl; we + * forced 16 in configure_security() (ESP_BLE_SM_MAX_KEY_SIZE). + * Authenticated/bonded come from the negotiated auth_mode + * bitfield (ESP_LE_AUTH_BOND=bit0, REQ_MITM=bit2, SC=bit3). */ + emit_evt(&(ble_uart_evt_t){ + .id = BLE_UART_EVT_LINK_SECURE, + .link_secure = { + .encrypted = true, + .authenticated = !!(a->auth_mode & ESP_LE_AUTH_REQ_MITM), + .bonded = !!(a->auth_mode & ESP_LE_AUTH_BOND), + .key_size = 16, + }, + }); } else { ESP_LOGW(TAG, "pairing failed reason=0x%x", a->fail_reason); + emit_evt(&(ble_uart_evt_t){ + .id = BLE_UART_EVT_PAIRING_FAILED, + .pairing_failed = { .reason = (int)a->fail_reason }, + }); } break; } @@ -649,14 +825,50 @@ static void gap_event_handler(esp_gap_ble_cb_event_t event, param->ble_security.ble_key.key_type); break; - case ESP_GAP_BLE_NC_REQ_EVT: - /* Numeric Comparison shouldn't fire with our DisplayOnly IO - * (BT Core §2.3.5.1). Reject — accepting would flag the LTK - * as MITM-authenticated without any user actually comparing - * the digits, silently downgrading the security we asked for. */ - esp_ble_confirm_reply(param->ble_security.ble_req.bd_addr, false); + case ESP_GAP_BLE_PASSKEY_REQ_EVT: + /* Central is asking us to enter a passkey it just displayed. + * Cache the peer + flavour so ble_uart_passkey_reply() knows + * where to inject; the application now owes us a reply. */ + ESP_LOGI(TAG, "passkey entry requested from " ESP_BD_ADDR_STR, + ESP_BD_ADDR_HEX(param->ble_security.ble_req.bd_addr)); + memcpy(s_pending_io_bda, param->ble_security.ble_req.bd_addr, + sizeof(s_pending_io_bda)); + s_pending_io_kind = PENDING_IO_PASSKEY; + emit_evt(&(ble_uart_evt_t){ .id = BLE_UART_EVT_PASSKEY_REQUEST }); break; + case ESP_GAP_BLE_NC_REQ_EVT: { + /* Numeric Comparison: both ends should display the same + * 6-digit value. We surface it via on_event and wait for the + * application to confirm via ble_uart_compare_reply(). + * + * Backstop: with no on_event registered we'd silently hang + * the SM until pairing times out. resolve_sec_policy already + * rejects that combination at install time (DISPLAY_YES_NO / + * KEYBOARD_DISPLAY both require on_event), but a peer can + * still trigger NC against an AUTO io_cap that resolved to + * DisplayOnly — extremely unlikely in practice (would need + * the central to *also* run with DisplayOnly), but if it + * does happen we reject the comparison rather than silently + * accept and pretend the user verified the digits. */ + uint32_t cmp = param->ble_security.key_notif.passkey; + if (s_cfg.on_event == NULL) { + ESP_LOGW(TAG, "NC_REQ but no on_event handler; rejecting"); + esp_ble_confirm_reply(param->ble_security.ble_req.bd_addr, false); + break; + } + ESP_LOGI(TAG, "numeric compare %06" PRIu32 " from " ESP_BD_ADDR_STR, + cmp, ESP_BD_ADDR_HEX(param->ble_security.ble_req.bd_addr)); + memcpy(s_pending_io_bda, param->ble_security.ble_req.bd_addr, + sizeof(s_pending_io_bda)); + s_pending_io_kind = PENDING_IO_NUMCMP; + emit_evt(&(ble_uart_evt_t){ + .id = BLE_UART_EVT_NUMERIC_COMPARE, + .numeric_compare = { .passkey = cmp }, + }); + break; + } + default: break; } @@ -705,14 +917,307 @@ int ble_uart_tx(const uint8_t *data, size_t len) bool ble_uart_is_connected(void) { return s_conn_id != 0xFFFF; } bool ble_uart_is_subscribed(void) { return s_subscribed; } +/* ===== Event dispatch ================================================= */ + +/* NULL-safe forwarder so each call site stays one-liner. Runs on the + * BTC task; emit_evt's caller owns the (typically stack-allocated) + * ble_uart_evt_t. */ +static void emit_evt(const ble_uart_evt_t *evt) +{ + if (s_cfg.on_event != NULL) { + s_cfg.on_event(evt); + } +} + +/* ===== Pairing replies ================================================ */ + +/* Snapshot the pending request, validate against the expected kind, + * issue the matching SDK reply, clear pending state. Mirror of the + * NimBLE backend's do_pairing_reply. */ +static int do_pairing_reply(pending_io_kind_t expected, + uint32_t passkey, + bool accept) +{ + pending_io_kind_t kind = s_pending_io_kind; + if (kind == PENDING_IO_NONE || kind != expected) { + return BLE_UART_ENOTCONN; + } + + /* Snapshot the address; clear pending state up front so a + * re-entrant on_event triggered by the SDK reply doesn't see + * stale state. (esp_ble_passkey_reply / confirm_reply are + * synchronous on Bluedroid.) */ + esp_bd_addr_t bda; + memcpy(bda, s_pending_io_bda, sizeof(bda)); + s_pending_io_kind = PENDING_IO_NONE; + + esp_err_t rc; + if (expected == PENDING_IO_PASSKEY) { + rc = esp_ble_passkey_reply(bda, true, passkey); + } else { /* PENDING_IO_NUMCMP */ + rc = esp_ble_confirm_reply(bda, accept); + } + if (rc != ESP_OK) { + ESP_LOGW(TAG, "%s rc=%s", + expected == PENDING_IO_PASSKEY ? "passkey_reply" : "confirm_reply", + esp_err_to_name(rc)); + return xlate_rc(rc); + } + return BLE_UART_OK; +} + +int ble_uart_passkey_reply(uint32_t passkey) +{ + if (passkey > 999999) { + return BLE_UART_EINVAL; + } + return do_pairing_reply(PENDING_IO_PASSKEY, passkey, false); +} + +int ble_uart_compare_reply(bool match) +{ + return do_pairing_reply(PENDING_IO_NUMCMP, 0, match); +} + +/* ===== Bond management ================================================ */ + +int ble_uart_get_bond_count(size_t *out_count) +{ + if (out_count == NULL || !s_installed) { + return BLE_UART_EINVAL; + } + int n = esp_ble_get_bond_device_num(); + if (n < 0) { + ESP_LOGW(TAG, "get_bond_device_num rc=%d", n); + return BLE_UART_EFAIL; + } + *out_count = (size_t)n; + return BLE_UART_OK; +} + +int ble_uart_get_bonded_peers(ble_uart_addr_t *out, size_t cap, size_t *out_count) +{ + if (out_count == NULL || !s_installed + || (out == NULL && cap > 0)) { + return BLE_UART_EINVAL; + } + int total = esp_ble_get_bond_device_num(); + if (total < 0) { + ESP_LOGW(TAG, "get_bond_device_num rc=%d", total); + return BLE_UART_EFAIL; + } + if (total == 0) { + *out_count = 0; + return BLE_UART_OK; + } + if (cap == 0) { + *out_count = (size_t)total; + return BLE_UART_OK; + } + + /* esp_ble_get_bond_device_list expects a buffer sized to `total` + * (it takes dev_num as in/out — the input must be ≥ actual). Heap + * because esp_ble_bond_dev_t is ~80 B per entry. */ + esp_ble_bond_dev_t *list = calloc((size_t)total, sizeof(*list)); + if (list == NULL) { + return BLE_UART_ENOMEM; + } + int got = total; + esp_err_t rc = esp_ble_get_bond_device_list(&got, list); + if (rc != ESP_OK) { + ESP_LOGW(TAG, "get_bond_device_list rc=%s", esp_err_to_name(rc)); + free(list); + return xlate_rc(rc); + } + + /* Marshal at most `cap` entries; report total count regardless so + * a caller with an under-sized buffer learns to retry. */ + size_t to_copy = ((size_t)got < cap) ? (size_t)got : cap; + for (size_t i = 0; i < to_copy; i++) { + memcpy(out[i].bytes, list[i].bd_addr, 6); + out[i].type = + (list[i].bd_addr_type == BLE_ADDR_TYPE_PUBLIC + || list[i].bd_addr_type == BLE_ADDR_TYPE_RPA_PUBLIC) + ? BLE_UART_ADDR_TYPE_PUBLIC + : BLE_UART_ADDR_TYPE_RANDOM; + } + free(list); + *out_count = (size_t)got; + return BLE_UART_OK; +} + +int ble_uart_remove_peer(const ble_uart_addr_t *peer) +{ + if (peer == NULL || !s_installed) { + return BLE_UART_EINVAL; + } + /* esp_bd_addr_t is uint8_t[6] in MSB-first order, identical to + * our public ble_uart_addr_t.bytes — pass through. The address + * type is not part of esp_ble_remove_bond_device's contract: + * Bluedroid identifies bonds by BD address alone. */ + esp_bd_addr_t bd; + memcpy(bd, peer->bytes, sizeof(bd)); + esp_err_t rc = esp_ble_remove_bond_device(bd); + if (rc != ESP_OK) { + ESP_LOGW(TAG, "remove_bond_device rc=%s", esp_err_to_name(rc)); + return xlate_rc(rc); + } + return BLE_UART_OK; +} + +int ble_uart_clear_bonds(void) +{ + if (!s_installed) { + return BLE_UART_EINVAL; + } + int n = esp_ble_get_bond_device_num(); + if (n < 0) { + ESP_LOGW(TAG, "get_bond_device_num rc=%d", n); + return BLE_UART_EFAIL; + } + if (n == 0) { + return BLE_UART_OK; + } + + /* Pull the full list once. Removing entries one-by-one inside + * the iterator would not be safe — esp_ble_remove_bond_device + * mutates the underlying SMP list. Heap-allocate to avoid a + * worst-case stack burst (each esp_ble_bond_dev_t is ~80 B). */ + esp_ble_bond_dev_t *list = calloc((size_t)n, sizeof(*list)); + if (list == NULL) { + return BLE_UART_ENOMEM; + } + esp_err_t rc = esp_ble_get_bond_device_list(&n, list); + if (rc != ESP_OK) { + ESP_LOGW(TAG, "get_bond_device_list rc=%s", esp_err_to_name(rc)); + free(list); + return xlate_rc(rc); + } + + /* Remove each. Record the first failure but keep going so a + * single corrupt entry doesn't strand the rest. */ + esp_err_t first_err = ESP_OK; + for (int i = 0; i < n; i++) { + esp_err_t e = esp_ble_remove_bond_device(list[i].bd_addr); + if (e != ESP_OK && first_err == ESP_OK) { + first_err = e; + ESP_LOGW(TAG, "remove_bond_device[%d] rc=%s", + i, esp_err_to_name(e)); + } + } + free(list); + return first_err == ESP_OK ? BLE_UART_OK : xlate_rc(first_err); +} + /* ===== Lifecycle ====================================================== */ -static int configure_security(bool encrypted) +/* Resolved view of cfg.encrypted + the per-feature overrides + * (cfg.sc / cfg.bonding / cfg.mitm / cfg.io_cap). Computed once in + * install() and consumed by configure_security() / build_attr_table(). */ +struct sec_policy { + bool sc; + bool bonding; + bool mitm; + bool link_enc; /* derived: sc || bonding || mitm */ + esp_ble_io_cap_t iocap; /* ESP_IO_CAP_OUT / NONE */ + esp_ble_auth_req_t auth_req;/* assembled bit-mask, see below */ +}; + +static int resolve_sec_policy(const ble_uart_config_t *cfg, + struct sec_policy *out) { - esp_ble_auth_req_t auth_req = encrypted ? ESP_LE_AUTH_REQ_SC_MITM_BOND - : ESP_LE_AUTH_NO_BOND; - esp_ble_io_cap_t iocap = encrypted ? ESP_IO_CAP_OUT - : ESP_IO_CAP_NONE; + const ble_uart_security_t *sec = &cfg->security; + + /* Range-check the public enums up front. Accepting (say) a + * dangling 99 here would propagate to esp_ble_gap_set_security_param + * as garbage and the SM would refuse pairing for non-obvious reasons. */ + if ((unsigned)sec->sc > BLE_UART_SEC_ON + || (unsigned)sec->bonding > BLE_UART_SEC_ON + || (unsigned)sec->mitm > BLE_UART_SEC_ON + || (unsigned)sec->io_cap > BLE_UART_IO_CAP_KEYBOARD_DISPLAY) { + return BLE_UART_EINVAL; + } + + /* Input-capable IO caps fire BLE_UART_EVT_PASSKEY_REQUEST or + * BLE_UART_EVT_NUMERIC_COMPARE and need an application reply via + * ble_uart_passkey_reply / ble_uart_compare_reply. With on_event + * NULL the caller would never see the request and pairing would + * silently stall until the SM times out — fail synchronously. + * + * This checks the *configured* io_cap, not the value resolved + * below. AUTO and DISPLAY_ONLY are excluded on purpose: AUTO with + * mitm=ON becomes DisplayOnly; the central enters the passkey we + * generate — no ble_uart_passkey_reply() / compare_reply() needed. + * BLE_UART_EVT_PASSKEY_DISPLAY is additive when on_event is set. */ + if (cfg->on_event == NULL + && (sec->io_cap == BLE_UART_IO_CAP_KEYBOARD_ONLY + || sec->io_cap == BLE_UART_IO_CAP_DISPLAY_YES_NO + || sec->io_cap == BLE_UART_IO_CAP_KEYBOARD_DISPLAY)) { + return BLE_UART_EINVAL; + } + + /* AUTO inherits the bit from cfg.encrypted; OFF / ON override. */ + bool preset = cfg->encrypted; + out->sc = (sec->sc == BLE_UART_SEC_AUTO) ? preset + : (sec->sc == BLE_UART_SEC_ON); + out->bonding = (sec->bonding == BLE_UART_SEC_AUTO) ? preset + : (sec->bonding == BLE_UART_SEC_ON); + out->mitm = (sec->mitm == BLE_UART_SEC_AUTO) ? preset + : (sec->mitm == BLE_UART_SEC_ON); + /* "Link will be encrypted" iff the SM runs at all — and the SM + * runs whenever any of these three bits is set. Pairing-without- + * bonding still encrypts the live link with a session LTK. */ + out->link_enc = out->sc || out->bonding || out->mitm; + + /* IO capability: AUTO picks the minimum that lets the resolved + * MITM bit succeed; the explicit values map straight to the + * Bluedroid ESP_IO_CAP_* constants used by the SM. */ + switch (sec->io_cap) { + case BLE_UART_IO_CAP_DISPLAY_ONLY: + out->iocap = ESP_IO_CAP_OUT; + break; + case BLE_UART_IO_CAP_NO_INPUT_OUTPUT: + out->iocap = ESP_IO_CAP_NONE; + break; + case BLE_UART_IO_CAP_KEYBOARD_ONLY: + out->iocap = ESP_IO_CAP_IN; + break; + case BLE_UART_IO_CAP_DISPLAY_YES_NO: + out->iocap = ESP_IO_CAP_IO; + break; + case BLE_UART_IO_CAP_KEYBOARD_DISPLAY: + out->iocap = ESP_IO_CAP_KBDISP; + break; + case BLE_UART_IO_CAP_AUTO: + default: + out->iocap = out->mitm ? ESP_IO_CAP_OUT : ESP_IO_CAP_NONE; + break; + } + + /* Just Works (NoInputNoOutput) cannot satisfy MITM — the SM + * would reject pairing in flight. Catch it synchronously here. */ + if (out->mitm && out->iocap == ESP_IO_CAP_NONE) { + return BLE_UART_EINVAL; + } + + /* Bluedroid's auth_req is a bit-mask: + * bit 0 = ESP_LE_AUTH_BOND + * bit 2 = ESP_LE_AUTH_REQ_MITM + * bit 3 = ESP_LE_AUTH_REQ_SC_ONLY + * The combined ESP_LE_AUTH_REQ_SC_MITM_BOND etc. constants are + * just convenience names for those bit unions — assembling from + * the individual flags here mirrors any combination cleanly. */ + out->auth_req = (esp_ble_auth_req_t)( + (out->bonding ? ESP_LE_AUTH_BOND : 0) + | (out->mitm ? ESP_LE_AUTH_REQ_MITM : 0) + | (out->sc ? ESP_LE_AUTH_REQ_SC_ONLY : 0)); + return BLE_UART_OK; +} + +static int configure_security(const struct sec_policy *pol) +{ + esp_ble_auth_req_t auth_req = pol->auth_req; + esp_ble_io_cap_t iocap = pol->iocap; uint8_t key_size = 16; uint8_t init_key = ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK; uint8_t rsp_key = ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK; @@ -754,6 +1259,83 @@ int ble_uart_install(const ble_uart_config_t *cfg) memset(&s_cfg, 0, sizeof(s_cfg)); } + /* Validate device_name length up front. Beyond + * BLE_UART_DEVICE_NAME_MAX the default-path advertising would + * silently fail at config_adv_data time; surfacing the error here + * is much friendlier. strnlen with cap+1 also stops a missing-NUL + * caller buffer from running into uninitialised memory. */ + if (s_cfg.device_name != NULL) { + size_t nlen = strnlen(s_cfg.device_name, BLE_UART_DEVICE_NAME_MAX + 1); + if (nlen > BLE_UART_DEVICE_NAME_MAX) { + ESP_LOGE(TAG, "device_name too long: > %u bytes", + (unsigned)BLE_UART_DEVICE_NAME_MAX); + memset(&s_cfg, 0, sizeof(s_cfg)); + return BLE_UART_EINVAL; + } + } + + /* Validate caller-supplied advertising payloads up front so an + * oversized buffer fails the install instead of corrupting the + * adv packet at start time (where errors only surface in logs). + * (NULL + len>0 is also rejected — almost always a caller bug.) */ + if (s_cfg.adv_data_len > BLE_UART_ADV_DATA_MAX + || (s_cfg.adv_data == NULL && s_cfg.adv_data_len > 0)) { + ESP_LOGE(TAG, "bad adv_data: ptr=%p len=%u (max=%u)", + s_cfg.adv_data, + (unsigned)s_cfg.adv_data_len, + (unsigned)BLE_UART_ADV_DATA_MAX); + memset(&s_cfg, 0, sizeof(s_cfg)); + return BLE_UART_EINVAL; + } + if (s_cfg.scan_rsp_data_len > BLE_UART_SCAN_RSP_DATA_MAX + || (s_cfg.scan_rsp_data == NULL && s_cfg.scan_rsp_data_len > 0)) { + ESP_LOGE(TAG, "bad scan_rsp_data: ptr=%p len=%u (max=%u)", + s_cfg.scan_rsp_data, + (unsigned)s_cfg.scan_rsp_data_len, + (unsigned)BLE_UART_SCAN_RSP_DATA_MAX); + memset(&s_cfg, 0, sizeof(s_cfg)); + return BLE_UART_EINVAL; + } + + /* Resolve cfg.encrypted + per-feature overrides into a flat + * policy. Validation (out-of-range enums + impossible MITM/IO + * combination) happens up front so install() rejects bad cfgs + * synchronously, before any host-stack resources are allocated. */ + struct sec_policy pol; + int srv = resolve_sec_policy(&s_cfg, &pol); + if (srv != BLE_UART_OK) { + ESP_LOGE(TAG, "bad security cfg: encrypted=%d sc=%d bonding=%d " + "mitm=%d io_cap=%d", + (int)s_cfg.encrypted, + (int)s_cfg.security.sc, (int)s_cfg.security.bonding, + (int)s_cfg.security.mitm, (int)s_cfg.security.io_cap); + memset(&s_cfg, 0, sizeof(s_cfg)); + return srv; + } + s_link_encrypted = pol.link_enc; + s_mitm_required = pol.mitm; + + /* Copy raw payloads now (caller's pointers may not outlive install). + * For adv_data we also prepend the 3-byte Flags AD ourselves — + * the controller-visible Flags element is library-controlled and + * not part of what the application owns. */ + s_adv_data_len = 0; + s_scan_rsp_len = 0; + if (s_cfg.adv_data != NULL && s_cfg.adv_data_len > 0) { + s_adv_data_buf[0] = 0x02; /* AD length */ + s_adv_data_buf[1] = 0x01; /* AD type: Flags */ + s_adv_data_buf[2] = ESP_BLE_ADV_FLAG_GEN_DISC | ESP_BLE_ADV_FLAG_BREDR_NOT_SPT; + memcpy(s_adv_data_buf + 3, s_cfg.adv_data, s_cfg.adv_data_len); + s_adv_data_len = (uint8_t)(3 + s_cfg.adv_data_len); + } + if (s_cfg.scan_rsp_data != NULL && s_cfg.scan_rsp_data_len > 0) { + memcpy(s_scan_rsp_buf, s_cfg.scan_rsp_data, s_cfg.scan_rsp_data_len); + s_scan_rsp_len = (uint8_t)s_cfg.scan_rsp_data_len; + } + /* Drop the pointers — install must not retain caller buffers. */ + s_cfg.adv_data = NULL; + s_cfg.scan_rsp_data = NULL; + /* Free BR/EDR controller RAM we won't use (no-op on BLE-only chips). */ esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT); @@ -813,7 +1395,7 @@ int ble_uart_install(const ble_uart_config_t *cfg) /* SM must be configured before app_register so any incoming * pairing request finds the right policy. */ - int srv = configure_security(s_cfg.encrypted); + srv = configure_security(&pol); if (srv != BLE_UART_OK) { ESP_LOGE(TAG, "security config failed rc=%d", srv); rc = ESP_FAIL; @@ -830,7 +1412,7 @@ int ble_uart_install(const ble_uart_config_t *cfg) esp_err_to_name(rc)); } - build_attr_table(s_cfg.encrypted); + build_attr_table(s_link_encrypted, s_mitm_required); rc = esp_ble_gatts_app_register(UART_APP_ID); if (rc != ESP_OK) { @@ -908,7 +1490,11 @@ int ble_uart_open(void) return BLE_UART_OK; } -int ble_uart_close(void) +/* Body of ble_uart_close(); also called directly by the close-async + * worker, which has already latched s_closing itself. The public + * wrapper below uses s_closing to reject a sync close that races + * with an in-flight async close. */ +static int do_close(void) { if (!s_opened) { return BLE_UART_EALREADY; @@ -951,32 +1537,115 @@ int ble_uart_close(void) return BLE_UART_OK; } +int ble_uart_close(void) +{ + /* If a ble_uart_close_async() worker is in flight, the close + * sequence is already running on the worker's task — let it + * finish rather than racing it from here. The worker drives + * s_opened to false on its own, so the next sync close after + * the worker drains will get the natural !s_opened EALREADY. */ + if (s_closing) { + return BLE_UART_EALREADY; + } + return do_close(); +} + +/* ===== Async close ==================================================== */ + +/* Background worker spawned by ble_uart_close_async(). Lives just + * long enough to run the synchronous close path (which blocks up to + * 500 ms waiting for DISCONNECT_EVT), then fires the completion event + * and self-deletes. Spawned as a separate task so on_event handlers + * running on the BTC task aren't pinned by the disconnect wait. */ +static void close_async_task(void *arg) +{ + (void)arg; + + /* Bypass the s_closing gate in ble_uart_close(): we ARE the + * in-flight async close that gate is meant to protect against. */ + int rc = do_close(); + if (rc != BLE_UART_OK && rc != BLE_UART_EALREADY) { + ESP_LOGW(TAG, "close_async: do_close rc=%d", rc); + } + + /* Deliver CLOSED on the worker task. Applications must defer + * ble_uart_uninstall() to another task (PORTING.md §5.3.2). + * Concurrent uninstall() may clear s_cfg while we read on_event. */ + emit_evt(&(ble_uart_evt_t){ + .id = BLE_UART_EVT_CLOSED, + .closed = { .status = rc }, + }); + + s_closing = false; + vTaskDelete(NULL); +} + +int ble_uart_close_async(void) +{ + /* Same-state checks as the synchronous variant: nothing to close + * if we never opened, and no point spawning a second worker if + * the first hasn't drained yet. */ + if (!s_opened || s_closing) { + return BLE_UART_EALREADY; + } + + /* Latch BEFORE spawning so a racing caller (different task) sees + * the in-flight state immediately and gets EALREADY. */ + s_closing = true; + + /* 3 KB is comfortably more than the close path uses (a couple of + * GAP API calls + a 50×10ms vTaskDelay loop); bump if you wedge + * a heavy on_event handler between adv_stop and CLOSED. */ + BaseType_t ok = xTaskCreate(close_async_task, "ble_close", + 3072, NULL, + tskIDLE_PRIORITY + 2, NULL); + if (ok != pdPASS) { + s_closing = false; + return BLE_UART_ENOMEM; + } + return BLE_UART_OK; +} + int ble_uart_uninstall(void) { if (!s_installed) { return BLE_UART_EALREADY; } + /* If a ble_uart_close_async() worker is still draining, poll s_closing + * for up to ~5 s before touching shared state. On timeout, teardown + * continues anyway — applications must follow PORTING.md §5.3.2 so + * uninstall runs only after the worker has finished. */ + for (int i = 0; i < 500 && s_closing; i++) { + vTaskDelay(pdMS_TO_TICKS(10)); + } + if (s_closing) { + ESP_LOGW(TAG, "uninstall: close_async worker still running, " + "tearing down anyway"); + } + /* Best-effort cleanup. We MUST NOT early-return on a per-step * failure: that would leave s_installed=true with the SDK in * some half-torn-down state, blocking both re-install and retry. * Mirror the install() goto-fail philosophy: record the first * error, keep tearing down, and always wipe our state. */ - esp_err_t first_err = ESP_OK; + int first_rc = BLE_UART_OK; if (s_opened) { int rc = ble_uart_close(); if (rc != BLE_UART_OK && rc != BLE_UART_EALREADY) { ESP_LOGE(TAG, "ble_uart_close rc=%d", rc); - if (first_err == ESP_OK) first_err = rc; + if (first_rc == BLE_UART_OK) { + first_rc = rc; + } } } if (s_gatts_if != ESP_GATT_IF_NONE) { esp_err_t rc = esp_ble_gatts_app_unregister(s_gatts_if); - if (rc != ESP_OK && first_err == ESP_OK) { + if (rc != ESP_OK && first_rc == BLE_UART_OK) { ESP_LOGE(TAG, "gatts_app_unregister rc=%s", esp_err_to_name(rc)); - first_err = rc; + first_rc = xlate_rc(rc); } s_gatts_if = ESP_GATT_IF_NONE; } @@ -984,22 +1653,30 @@ int ble_uart_uninstall(void) esp_err_t rc = esp_bluedroid_disable(); if (rc != ESP_OK) { ESP_LOGE(TAG, "bluedroid_disable rc=%s", esp_err_to_name(rc)); - if (first_err == ESP_OK) first_err = rc; + if (first_rc == BLE_UART_OK) { + first_rc = xlate_rc(rc); + } } rc = esp_bluedroid_deinit(); if (rc != ESP_OK) { ESP_LOGE(TAG, "bluedroid_deinit rc=%s", esp_err_to_name(rc)); - if (first_err == ESP_OK) first_err = rc; + if (first_rc == BLE_UART_OK) { + first_rc = xlate_rc(rc); + } } rc = esp_bt_controller_disable(); if (rc != ESP_OK) { ESP_LOGE(TAG, "controller_disable rc=%s", esp_err_to_name(rc)); - if (first_err == ESP_OK) first_err = rc; + if (first_rc == BLE_UART_OK) { + first_rc = xlate_rc(rc); + } } rc = esp_bt_controller_deinit(); if (rc != ESP_OK) { ESP_LOGE(TAG, "controller_deinit rc=%s", esp_err_to_name(rc)); - if (first_err == ESP_OK) first_err = rc; + if (first_rc == BLE_UART_OK) { + first_rc = xlate_rc(rc); + } } /* Wipe state unconditionally, even on partial failure. */ @@ -1012,11 +1689,18 @@ int ble_uart_uninstall(void) s_installed = false; s_opened = false; s_shutting_down = false; + s_closing = false; s_attr_tab_ready = false; s_adv_active = false; s_adv_config_done = 0; + s_adv_data_len = 0; + s_scan_rsp_len = 0; + s_link_encrypted = false; + s_mitm_required = false; + s_pending_io_kind = PENDING_IO_NONE; + memset(s_pending_io_bda, 0, sizeof(s_pending_io_bda)); prep_reset(); - return first_err == ESP_OK ? BLE_UART_OK : xlate_rc(first_err); + return first_rc; } #endif /* CONFIG_BT_BLUEDROID_ENABLED */ diff --git a/examples/bluetooth/common/ble_uart/ble_uart_nimble.c b/examples/bluetooth/common/ble_uart/ble_uart_nimble.c index c59698f09a2..9887240c517 100644 --- a/examples/bluetooth/common/ble_uart/ble_uart_nimble.c +++ b/examples/bluetooth/common/ble_uart/ble_uart_nimble.c @@ -3,7 +3,7 @@ * * SPDX-License-Identifier: Unlicense OR CC0-1.0 * - * BLE UART — NimBLE backend. Implements the lifecycle declared in + * ESP-BLE-UART — NimBLE backend. Implements the lifecycle declared in * ble_uart.h on top of the NimBLE host. Active when * CONFIG_BT_NIMBLE_ENABLED=y; otherwise ble_uart_bluedroid.c is used. */ @@ -16,6 +16,7 @@ #include #include +#include #include #include "freertos/FreeRTOS.h" @@ -59,6 +60,25 @@ static int xlate_rc(int nimble_rc) } } +/* Collapse NimBLE's 4-value peer addr type (0–3) into our public + * 2-value enum (identity types map onto public/random). */ +static uint8_t nimble_peer_type_to_uart(uint8_t nimble_type) +{ + return (nimble_type == BLE_ADDR_PUBLIC || + nimble_type == BLE_ADDR_PUBLIC_ID) + ? BLE_UART_ADDR_TYPE_PUBLIC + : BLE_UART_ADDR_TYPE_RANDOM; +} + +/* Marshal a NimBLE ble_addr_t into our public ble_uart_addr_t. */ +static void from_nimble_addr(const ble_addr_t *src, ble_uart_addr_t *dst) +{ + dst->type = nimble_peer_type_to_uart(src->type); + for (int i = 0; i < 6; i++) { + dst->bytes[i] = src->val[5 - i]; + } +} + /* Provided by NimBLE's `store/config` lib. */ extern void ble_store_config_init(void); @@ -80,7 +100,7 @@ static const ble_uuid128_t s_chr_tx_uuid = BLE_UUID128_INIT(NUS_TX_BYTES); /* ===== State =========================================================== */ -/* RX scratch capacity. Tunable via menuconfig (Component config → BLE UART +/* RX scratch capacity. Tunable via menuconfig (Component config → ESP-BLE-UART * library); fall * back to 1024 if CONFIG_BLE_UART_RX_SCRATCH_SIZE is absent. */ #ifndef CONFIG_BLE_UART_RX_SCRATCH_SIZE @@ -89,9 +109,10 @@ static const ble_uuid128_t s_chr_tx_uuid = BLE_UUID128_INIT(NUS_TX_BYTES); #define RX_SCRATCH CONFIG_BLE_UART_RX_SCRATCH_SIZE /* Cached device name. Avoids ble_svc_gap_device_name() which returns - * NULL when CONFIG_BT_NIMBLE_GAP_SERVICE=n (would NULL-deref). 32B - * covers the BLE 31-byte adv-payload limit + NUL. */ -#define DEV_NAME_MAX 32 + * NULL when CONFIG_BT_NIMBLE_GAP_SERVICE=n (would NULL-deref). The + * cap is BLE_UART_DEVICE_NAME_MAX (validated in install) + NUL; we + * round up for safety margin. */ +#define DEV_NAME_MAX (BLE_UART_DEVICE_NAME_MAX + 2) static ble_uart_config_t s_cfg; static char s_dev_name[DEV_NAME_MAX]; @@ -101,9 +122,60 @@ static volatile uint16_t s_conn_handle = BLE_HS_CONN_HANDLE_NONE; static bool s_subscribed; static bool s_installed; static bool s_opened; +/* After close(), ble_gatts_stop() drops svc-def pointers; open() must + * count/add again before the next ble_hs_start(). Cleared on install. */ +static bool s_gatts_needs_readd; +#if MYNEWT_VAL(BLE_HS_AUTO_START) +/* Set in install() when nimble_port_init() queues the one-shot AUTO_START + * event; cleared on first open() so we don't also ble_hs_sched_start() and + * trip assert(rc==0) in ble_hs_event_start_stage2 (BLE_HS_EALREADY). */ +static bool s_hs_auto_start_pending; +#endif static bool s_shutting_down; /* gates auto-readvertise during close */ +/* Set by ble_uart_close_async() when its worker task is in flight, + * cleared by the worker just before it exits. uninstall() polls this + * to drain a pending async close before tearing the port down. */ +static volatile bool s_closing; static uint8_t s_own_addr_type; +/* Resolved security policy. Computed once in install() from cfg.encrypted + * + the per-feature overrides (cfg.sc/bonding/mitm/io_cap), then read + * in the GAP event handler and the GATT-table builder. + * s_link_encrypted = true if any of {sc, bonding, mitm} resolved ON + * → kick pairing on connect, require encryption on chars + * s_mitm_required = resolved mitm bit + * → require AUTHEN flag on chars (Just-Works peer cannot read/write) */ +static bool s_link_encrypted; +static bool s_mitm_required; + +/* Pending Passkey-Entry / Numeric-Comparison request awaiting an + * application reply via ble_uart_passkey_reply / ble_uart_compare_reply. + * + * s_pending_io_conn — conn_handle the SM is asking about, or + * BLE_HS_CONN_HANDLE_NONE if no request is in + * flight. Set in PASSKEY_ACTION, cleared on + * reply, on disconnect, and on enc_change. + * s_pending_io_action — BLE_SM_IOACT_INPUT or BLE_SM_IOACT_NUMCMP; + * used to reject mismatched reply calls (e.g. + * passkey_reply during NUMCMP). + * + * No FreeRTOS lock — both fields are written only from the host task, + * and the reply API is the only outside reader. The reader takes a + * local snapshot before injecting. */ +static volatile uint16_t s_pending_io_conn = BLE_HS_CONN_HANDLE_NONE; +static volatile uint8_t s_pending_io_action; + +/* Optional user-supplied advertising payloads. When `s_adv_data_len` + * is non-zero we feed `s_adv_data` straight to ble_gap_adv_set_data; + * the buffer always carries the 3-byte Flags AD element we built in + * install() followed by the user's bytes. Same for the scan response + * (no Flags element there). Zero length means "use the default + * field-builder path in start_advertising()". */ +static uint8_t s_adv_data[3 + BLE_UART_ADV_DATA_MAX]; +static uint8_t s_adv_data_len; +static uint8_t s_scan_rsp_data[BLE_UART_SCAN_RSP_DATA_MAX]; +static uint8_t s_scan_rsp_data_len; + static int gap_event(struct ble_gap_event *event, void *arg); static int start_advertising(void); @@ -144,21 +216,35 @@ static int chr_access(uint16_t conn_handle, uint16_t attr_handle, * NOTIFY_INDICATE_* (not from READ/WRITE_*), so notify-only chars need * the NOTIFY_INDICATE mask, not just the RW mask — otherwise an * unpaired central could subscribe and receive notifications over the - * unencrypted link (see ble_gatts.c:ble_gatts_chr_clt_cfg_flags_from_chr_flags). */ -#define CHR_FLAG_RW_ENC (BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_READ_AUTHEN | \ - BLE_GATT_CHR_F_WRITE_ENC | BLE_GATT_CHR_F_WRITE_AUTHEN) -#define CHR_FLAG_NOTIFY_ENC (BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC | \ - BLE_GATT_CHR_F_NOTIFY_INDICATE_AUTHEN) + * unencrypted link (see ble_gatts.c:ble_gatts_chr_clt_cfg_flags_from_chr_flags). + * + * The _ENC and _AUTHEN halves are split so an encrypted-but-unauthenticated + * (Just Works) link still passes when mitm=OFF — _AUTHEN gates on the + * link's authenticated bit which Just Works doesn't set. */ +#define CHR_FLAG_RW_ENC (BLE_GATT_CHR_F_READ_ENC | \ + BLE_GATT_CHR_F_WRITE_ENC) +#define CHR_FLAG_RW_AUTHEN (BLE_GATT_CHR_F_READ_AUTHEN | \ + BLE_GATT_CHR_F_WRITE_AUTHEN) +#define CHR_FLAG_NOTIFY_ENC (BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC) +#define CHR_FLAG_NOTIFY_AUTHEN (BLE_GATT_CHR_F_NOTIFY_INDICATE_AUTHEN) static struct ble_gatt_chr_def s_chr_defs[3]; static struct ble_gatt_svc_def s_svc_defs[2]; -static void build_gatt_table(bool encrypted) +static void build_gatt_table(bool link_enc, bool mitm) { /* `ble_gatt_chr_flags` is uint32_t — match width here so the * 0x10000-and-above NOTIFY_INDICATE flags don't get truncated. */ - ble_gatt_chr_flags rw_enc = encrypted ? CHR_FLAG_RW_ENC : 0; - ble_gatt_chr_flags notify_enc = encrypted ? CHR_FLAG_NOTIFY_ENC : 0; + ble_gatt_chr_flags rw_enc = 0; + ble_gatt_chr_flags notify_enc = 0; + if (link_enc) { + rw_enc |= CHR_FLAG_RW_ENC; + notify_enc |= CHR_FLAG_NOTIFY_ENC; + if (mitm) { + rw_enc |= CHR_FLAG_RW_AUTHEN; + notify_enc |= CHR_FLAG_NOTIFY_AUTHEN; + } + } s_chr_defs[0] = (struct ble_gatt_chr_def){ .uuid = &s_chr_rx_uuid.u, @@ -181,16 +267,47 @@ static void build_gatt_table(bool encrypted) s_svc_defs[1] = (struct ble_gatt_svc_def){0}; } +static int register_uart_gatt_svc(void) +{ + build_gatt_table(s_link_encrypted, s_mitm_required); + + int rc = ble_gatts_count_cfg(s_svc_defs); + if (rc != 0) { + ESP_LOGE(TAG, "ble_gatts_count_cfg rc=%d", rc); + return xlate_rc(rc); + } + rc = ble_gatts_add_svcs(s_svc_defs); + if (rc != 0) { + ESP_LOGE(TAG, "ble_gatts_add_svcs rc=%d", rc); + return xlate_rc(rc); + } + return BLE_UART_OK; +} + +/* Re-queue std + UART svc defs after close(). Host stop + gatts_reset clear + * the ATT table and free the svc-def pointer array; only re-adding UART would + * leave GAP/GATT (and anything else added at install) off-air on the next + * ble_hs_start(). ble_svc_*_init() is safe to recall on ESP-IDF: SYSINIT_ASSERT + * is a no-op and gap name storage is not reallocated if already present. */ +static int reregister_gatt_svcs_after_close(void) +{ +#if NIMBLE_BLE_CONNECT + ble_svc_gap_init(); +#endif + ble_svc_gatt_init(); + return register_uart_gatt_svc(); +} + static void register_cb(struct ble_gatt_register_ctxt *ctxt, void *arg) { char buf[BLE_UUID_STR_LEN]; switch (ctxt->op) { case BLE_GATT_REGISTER_OP_SVC: - ESP_LOGI(TAG, "registered service %s handle=%d", + ESP_LOGD(TAG, "registered service %s handle=%d", ble_uuid_to_str(ctxt->svc.svc_def->uuid, buf), ctxt->svc.handle); break; case BLE_GATT_REGISTER_OP_CHR: - ESP_LOGI(TAG, "registered chr %s def=%d val=%d", + ESP_LOGD(TAG, "registered chr %s def=%d val=%d", ble_uuid_to_str(ctxt->chr.chr_def->uuid, buf), ctxt->chr.def_handle, ctxt->chr.val_handle); break; @@ -248,41 +365,74 @@ int ble_uart_tx(const uint8_t *data, size_t len) bool ble_uart_is_connected(void) { return s_conn_handle != BLE_HS_CONN_HANDLE_NONE; } bool ble_uart_is_subscribed(void) { return s_subscribed; } +/* ===== Event dispatch ================================================= */ + +/* Forward a tagged event to the application callback. NULL-safe so all + * call sites stay one-liners; runs on the NimBLE host task — caller + * must keep the local `ble_uart_evt_t` alive across the call (we do + * via stack/compound literal at each site). */ +static void emit_evt(const ble_uart_evt_t *evt) +{ + if (s_cfg.on_event != NULL) { + s_cfg.on_event(evt); + } +} + /* ===== Advertising ==================================================== */ static int start_advertising(void) { - /* 31-byte primary adv can't hold flags + tx_pwr + name + 128-bit - * UUID together, so split: primary = flags+tx_pwr+name, - * scan rsp = 128-bit service UUID. */ - const char *name = s_dev_name; - size_t name_len = strlen(name); + int rc; - struct ble_hs_adv_fields adv = { - .flags = BLE_HS_ADV_F_DISC_GEN | BLE_HS_ADV_F_BREDR_UNSUP, - .tx_pwr_lvl_is_present = 1, - .tx_pwr_lvl = BLE_HS_ADV_TX_PWR_LVL_AUTO, - /* If no name was set, advertise without one (NimBLE accepts - * NULL+0); the service UUID in scan rsp still identifies us. */ - .name = name_len > 0 ? (uint8_t *)name : NULL, - .name_len = name_len, - .name_is_complete = name_len > 0 ? 1 : 0, - }; - int rc = ble_gap_adv_set_fields(&adv); - if (rc != 0) { - ESP_LOGE(TAG, "adv_set_fields rc=%d (name too long?)", rc); - return rc; + /* Two paths: raw-bytes (when the app provided its own payload) + * and field-builder (default). Mixing is allowed — e.g. raw + * adv_data + default scan_rsp. + * + * Default primary payload: Flags AD + Complete Local Name. The + * 128-bit service UUID lives in the scan response (the 31-byte + * primary packet can't hold name + 128-bit UUID together). */ + if (s_adv_data_len > 0) { + rc = ble_gap_adv_set_data(s_adv_data, s_adv_data_len); + if (rc != 0) { + ESP_LOGE(TAG, "adv_set_data rc=%d", rc); + return rc; + } + } else { + const char *name = s_dev_name; + size_t name_len = strlen(name); + + struct ble_hs_adv_fields adv = { + .flags = BLE_HS_ADV_F_DISC_GEN | BLE_HS_ADV_F_BREDR_UNSUP, + /* If no name was set, advertise without one (NimBLE accepts + * NULL+0); the service UUID in scan rsp still identifies us. */ + .name = name_len > 0 ? (uint8_t *)name : NULL, + .name_len = name_len, + .name_is_complete = name_len > 0 ? 1 : 0, + }; + rc = ble_gap_adv_set_fields(&adv); + if (rc != 0) { + ESP_LOGE(TAG, "adv_set_fields rc=%d (name too long?)", rc); + return rc; + } } - struct ble_hs_adv_fields rsp = { - .uuids128 = &s_svc_uuid, - .num_uuids128 = 1, - .uuids128_is_complete = 1, - }; - rc = ble_gap_adv_rsp_set_fields(&rsp); - if (rc != 0) { - ESP_LOGE(TAG, "adv_rsp_set_fields rc=%d", rc); - return rc; + if (s_scan_rsp_data_len > 0) { + rc = ble_gap_adv_rsp_set_data(s_scan_rsp_data, s_scan_rsp_data_len); + if (rc != 0) { + ESP_LOGE(TAG, "adv_rsp_set_data rc=%d", rc); + return rc; + } + } else { + struct ble_hs_adv_fields rsp = { + .uuids128 = &s_svc_uuid, + .num_uuids128 = 1, + .uuids128_is_complete = 1, + }; + rc = ble_gap_adv_rsp_set_fields(&rsp); + if (rc != 0) { + ESP_LOGE(TAG, "adv_rsp_set_fields rc=%d", rc); + return rc; + } } struct ble_gap_adv_params params = { @@ -295,7 +445,21 @@ static int start_advertising(void) ESP_LOGE(TAG, "adv_start rc=%d", rc); return rc; } - ESP_LOGI(TAG, "advertising as '%s'", name_len > 0 ? name : ""); + /* "advertising as ''" only makes sense when ble_uart owns + * the primary payload — with a caller-supplied adv_data the name + * the scanner sees is whatever bytes the caller put in there, not + * s_dev_name (which is only exposed via the GAP-service Device + * Name characteristic, post-connect). Pick the wording per path. */ + if (s_adv_data_len > 0 || s_scan_rsp_data_len > 0) { + ESP_LOGI(TAG, "advertising with custom payload " + "(adv=%u B, scan_rsp=%u B; GAP-service name='%s')", + (unsigned)s_adv_data_len, + (unsigned)s_scan_rsp_data_len, + s_dev_name[0] ? s_dev_name : ""); + } else { + ESP_LOGI(TAG, "advertising as '%s'", + s_dev_name[0] ? s_dev_name : ""); + } return 0; } @@ -325,9 +489,22 @@ static int gap_event(struct ble_gap_event *event, void *arg) if (event->connect.status == 0) { s_conn_handle = event->connect.conn_handle; s_subscribed = false; + /* Look up the peer's address; on first pair this equals + * peer_ota_addr, on bonded reconnect this is the resolved + * identity address. ble_gap_conn_find should never fail + * for a just-arrived connect event, but guard anyway — + * a zero-address payload is preferable to a stale stack + * read. */ + ble_uart_evt_t e = { .id = BLE_UART_EVT_CONNECTED }; + struct ble_gap_conn_desc d; + if (ble_gap_conn_find(event->connect.conn_handle, &d) == 0) { + from_nimble_addr(&d.peer_id_addr, &e.connected.peer); + } + emit_evt(&e); /* Start pairing immediately (rather than lazily on the - * first encrypted attribute access). */ - if (s_cfg.encrypted) { + * first encrypted attribute access). Resolved policy: + * any of {sc, bonding, mitm} ON → pairing required. */ + if (s_link_encrypted) { ble_gap_security_initiate(event->connect.conn_handle); } } else if (!s_shutting_down) { @@ -339,6 +516,14 @@ static int gap_event(struct ble_gap_event *event, void *arg) ESP_LOGI(TAG, "disconnect reason=%d", event->disconnect.reason); s_conn_handle = BLE_HS_CONN_HANDLE_NONE; s_subscribed = false; + /* Drop any pending Passkey-Entry / NC reply; pairing was + * cancelled along with the link. A stale value here would + * make the next reply call inject into a closed conn. */ + s_pending_io_conn = BLE_HS_CONN_HANDLE_NONE; + emit_evt(&(ble_uart_evt_t){ + .id = BLE_UART_EVT_DISCONNECTED, + .disconnected = { .reason = event->disconnect.reason }, + }); if (!s_shutting_down) { start_advertising(); } @@ -350,18 +535,49 @@ static int gap_event(struct ble_gap_event *event, void *arg) case BLE_GAP_EVENT_ADV_COMPLETE: ESP_LOGI(TAG, "adv_complete reason=%d", event->adv_complete.reason); - if (!s_shutting_down) { + /* Don't auto-restart while a connection is up. Undirected adv + * auto-stops at the LL on connect (BT Core spec), and an + * explicit start while connected would either fail (single- + * conn build, the default) or accept a second peripheral + * link we don't want to handle here. ADV_COMPLETE can still + * arrive in connected state via NimBLE-internal cleanup + * (e.g. resolving-list updates after bonding); ignore it. */ + if (!s_shutting_down && s_conn_handle == BLE_HS_CONN_HANDLE_NONE) { start_advertising(); } return 0; case BLE_GAP_EVENT_ENC_CHANGE: + /* Pairing has resolved one way or the other; clear any pending + * Passkey-Entry / NC request so the next pairing starts fresh. */ + s_pending_io_conn = BLE_HS_CONN_HANDLE_NONE; if (ble_gap_conn_find(event->enc_change.conn_handle, &desc) == 0) { ESP_LOGI(TAG, "enc_change status=%d encrypted=%d authenticated=%d bonded=%d", event->enc_change.status, desc.sec_state.encrypted, desc.sec_state.authenticated, desc.sec_state.bonded); + /* Dispatch on the actual sec_state, not the rc — bonded + * reconnects can finish with status=BLE_HS_ETIMEOUT (13) + * while encrypted=1 thanks to a benign race with the + * peer's auto-encrypt; reporting that as PAIRING_FAILED + * would be wrong. */ + if (desc.sec_state.encrypted) { + emit_evt(&(ble_uart_evt_t){ + .id = BLE_UART_EVT_LINK_SECURE, + .link_secure = { + .encrypted = (bool)desc.sec_state.encrypted, + .authenticated = (bool)desc.sec_state.authenticated, + .bonded = (bool)desc.sec_state.bonded, + .key_size = (uint8_t)desc.sec_state.key_size, + }, + }); + } else { + emit_evt(&(ble_uart_evt_t){ + .id = BLE_UART_EVT_PAIRING_FAILED, + .pairing_failed = { .reason = event->enc_change.status }, + }); + } } return 0; @@ -373,7 +589,8 @@ static int gap_event(struct ble_gap_event *event, void *arg) return BLE_GAP_REPEAT_PAIRING_RETRY; case BLE_GAP_EVENT_PASSKEY_ACTION: - if (event->passkey.params.action == BLE_SM_IOACT_DISP) { + switch (event->passkey.params.action) { + case BLE_SM_IOACT_DISP: { /* Rejection sampling avoids the modulo bias of * `esp_random() % 1000000` (2^32 % 1e6 != 0). */ const uint32_t passkey_max = 1000000U; @@ -387,14 +604,57 @@ static int gap_event(struct ble_gap_event *event, void *arg) .action = BLE_SM_IOACT_DISP, .passkey = r % passkey_max, }; + /* Banner stays for backward compat with log-scraping + * tests; on_event is additive. */ show_passkey(pkey.passkey); + emit_evt(&(ble_uart_evt_t){ + .id = BLE_UART_EVT_PASSKEY_DISPLAY, + .passkey = { .passkey = pkey.passkey }, + }); int rc = ble_sm_inject_io(event->passkey.conn_handle, &pkey); if (rc != 0) { - ESP_LOGW(TAG, "ble_sm_inject_io rc=%d", rc); + ESP_LOGW(TAG, "ble_sm_inject_io(DISP) rc=%d", rc); } - } else { - ESP_LOGW(TAG, "passkey action %d not handled (DisplayOnly only)", + break; + } + + case BLE_SM_IOACT_INPUT: + /* Central displays a passkey, user reads it from there + * and types it into our device. We can't inject anything + * yet — wait for ble_uart_passkey_reply(). */ + ESP_LOGI(TAG, "passkey entry requested (conn=%d)", + event->passkey.conn_handle); + s_pending_io_conn = event->passkey.conn_handle; + s_pending_io_action = BLE_SM_IOACT_INPUT; + emit_evt(&(ble_uart_evt_t){ .id = BLE_UART_EVT_PASSKEY_REQUEST }); + break; + + case BLE_SM_IOACT_NUMCMP: + /* Both sides should display the same 6-digit value; user + * confirms match. The value is in `numcmp` (already a + * decimal 0..999999, computed by the SM). */ + ESP_LOGI(TAG, "numeric compare %06" PRIu32 " (conn=%d)", + event->passkey.params.numcmp, + event->passkey.conn_handle); + s_pending_io_conn = event->passkey.conn_handle; + s_pending_io_action = BLE_SM_IOACT_NUMCMP; + emit_evt(&(ble_uart_evt_t){ + .id = BLE_UART_EVT_NUMERIC_COMPARE, + .numeric_compare = { .passkey = event->passkey.params.numcmp }, + }); + break; + + case BLE_SM_IOACT_OOB: + /* OOB plumbing is intentionally not exposed. Letting the + * SM hang here would surface as a pairing timeout — log + * loudly and let it. */ + ESP_LOGW(TAG, "OOB pairing requested but not implemented"); + break; + + default: + ESP_LOGW(TAG, "unexpected passkey action %d", event->passkey.params.action); + break; } return 0; @@ -407,7 +667,16 @@ static int gap_event(struct ble_gap_event *event, void *arg) ESP_LOGI(TAG, "subscribe attr=%d cur_notify=%d", event->subscribe.attr_handle, event->subscribe.cur_notify); if (event->subscribe.attr_handle == s_tx_val_handle) { - s_subscribed = (event->subscribe.cur_notify != 0); + bool sub = (event->subscribe.cur_notify != 0); + /* Edge-trigger so a redundant CCCD write (same value + * twice) doesn't fire two SUBSCRIBED events. */ + if (sub != s_subscribed) { + s_subscribed = sub; + emit_evt(&(ble_uart_evt_t){ + .id = BLE_UART_EVT_SUBSCRIBED, + .subscribed = { .subscribed = sub }, + }); + } } return 0; @@ -416,6 +685,65 @@ static int gap_event(struct ble_gap_event *event, void *arg) } } +/* ===== Pairing replies ================================================ */ + +/* Shared body for both reply APIs — looks at the pending state, builds + * the matching ble_sm_io payload, and injects it. `expected_action` is + * BLE_SM_IOACT_INPUT for passkey_reply and BLE_SM_IOACT_NUMCMP for + * compare_reply; calling the wrong API for the in-flight request + * returns ENOTCONN (treated as "no such request waiting"). */ +static int do_pairing_reply(uint8_t expected_action, + uint32_t passkey, + bool numcmp_accept) +{ + /* Snapshot the volatile fields once. The host task may clear them + * at any moment (disconnect / enc_change), and we want a coherent + * decision below. */ + uint16_t conn = s_pending_io_conn; + uint8_t action = s_pending_io_action; + if (conn == BLE_HS_CONN_HANDLE_NONE || action != expected_action) { + return BLE_UART_ENOTCONN; + } + + struct ble_sm_io io = { .action = expected_action }; + if (expected_action == BLE_SM_IOACT_INPUT) { + io.passkey = passkey; + } else { /* BLE_SM_IOACT_NUMCMP */ + io.numcmp_accept = numcmp_accept ? 1 : 0; + } + + /* Clear pending BEFORE inject so a re-entrant on_event triggered + * by inject_io doesn't see stale state. If inject fails the + * request is gone anyway (the SM will time out from the central's + * side), so leaving it cleared is the right move. */ + s_pending_io_conn = BLE_HS_CONN_HANDLE_NONE; + + int rc = ble_sm_inject_io(conn, &io); + if (rc != 0) { + ESP_LOGW(TAG, "ble_sm_inject_io(%s) rc=%d", + expected_action == BLE_SM_IOACT_INPUT ? "INPUT" : "NUMCMP", + rc); + /* ENOTCONN from NimBLE means the conn vanished between the + * snapshot and the inject — surface that to the caller as + * such; everything else is a stack-internal failure. */ + return (rc == BLE_HS_ENOTCONN) ? BLE_UART_ENOTCONN : BLE_UART_EFAIL; + } + return BLE_UART_OK; +} + +int ble_uart_passkey_reply(uint32_t passkey) +{ + if (passkey > 999999) { + return BLE_UART_EINVAL; + } + return do_pairing_reply(BLE_SM_IOACT_INPUT, passkey, false); +} + +int ble_uart_compare_reply(bool match) +{ + return do_pairing_reply(BLE_SM_IOACT_NUMCMP, 0, match); +} + /* ===== Host plumbing =================================================== */ static void on_reset(int reason) @@ -444,13 +772,112 @@ static void on_sync(void) static void nimble_host_task(void *param) { + (void)param; ESP_LOGI(TAG, "BLE host task started"); nimble_port_run(); - nimble_port_freertos_deinit(); + /* Self-delete instead of nimble_port_freertos_deinit(): do_close()'s + * nimble_port_stop() returns once port_run() exits in this task, but + * this task is still running. A quick ble_uart_open() may already have + * updated the port layer's host_task_h; freertos_deinit() would + * vTaskDelete(host_task_h) and kill the new host task. */ + vTaskDelete(NULL); } /* ===== Public lifecycle ================================================ */ +/* Resolved view of cfg.encrypted + the per-feature overrides + * (cfg.sc / cfg.bonding / cfg.mitm / cfg.io_cap). Used in install() + * to drive both ble_hs_cfg.sm_* and the GATT-table builder. */ +struct sec_policy { + bool sc; /* LE Secure Connections */ + bool bonding; /* persist LTK in NVS */ + bool mitm; /* require authentication */ + bool link_enc; /* derived: sc || bonding || mitm */ + uint8_t sm_io_cap; /* NimBLE BLE_HS_IO_* */ +}; + +static int resolve_sec_policy(const ble_uart_config_t *cfg, + struct sec_policy *out) +{ + const ble_uart_security_t *sec = &cfg->security; + + /* Range-check the public enums up front. Accepting (say) a + * dangling 99 here would propagate to ble_hs_cfg as garbage and + * the SM would refuse pairing for non-obvious reasons. */ + if ((unsigned)sec->sc > BLE_UART_SEC_ON + || (unsigned)sec->bonding > BLE_UART_SEC_ON + || (unsigned)sec->mitm > BLE_UART_SEC_ON + || (unsigned)sec->io_cap > BLE_UART_IO_CAP_KEYBOARD_DISPLAY) { + return BLE_UART_EINVAL; + } + + /* Input-capable IO caps fire BLE_UART_EVT_PASSKEY_REQUEST or + * BLE_UART_EVT_NUMERIC_COMPARE and need an application reply via + * ble_uart_passkey_reply / ble_uart_compare_reply. With on_event + * NULL the caller would never see the request and pairing would + * silently stall until the SM times out — fail synchronously. + * + * This checks the *configured* io_cap, not the value resolved + * below. AUTO and DISPLAY_ONLY are excluded on purpose: AUTO with + * mitm=ON becomes DisplayOnly; Passkey Display (BLE_SM_IOACT_DISP) + * is satisfied inside gap_event via ble_sm_inject_io with no app + * reply. BLE_UART_EVT_PASSKEY_DISPLAY is additive when on_event is + * set; with on_event NULL emit_evt() drops it and pairing still + * completes. */ + if (cfg->on_event == NULL + && (sec->io_cap == BLE_UART_IO_CAP_KEYBOARD_ONLY + || sec->io_cap == BLE_UART_IO_CAP_DISPLAY_YES_NO + || sec->io_cap == BLE_UART_IO_CAP_KEYBOARD_DISPLAY)) { + return BLE_UART_EINVAL; + } + + /* AUTO inherits the bit from cfg.encrypted; OFF / ON override. */ + bool preset = cfg->encrypted; + out->sc = (sec->sc == BLE_UART_SEC_AUTO) ? preset + : (sec->sc == BLE_UART_SEC_ON); + out->bonding = (sec->bonding == BLE_UART_SEC_AUTO) ? preset + : (sec->bonding == BLE_UART_SEC_ON); + out->mitm = (sec->mitm == BLE_UART_SEC_AUTO) ? preset + : (sec->mitm == BLE_UART_SEC_ON); + /* "Link will be encrypted" iff the SM runs at all — and the SM + * runs whenever any of these three bits is set. Pairing-without- + * bonding still encrypts the live link with a session LTK. */ + out->link_enc = out->sc || out->bonding || out->mitm; + + /* IO capability: AUTO picks the minimum that lets the resolved + * MITM bit succeed; the explicit values map straight to the + * NimBLE BLE_HS_IO_* constants used by the SM. */ + switch (sec->io_cap) { + case BLE_UART_IO_CAP_DISPLAY_ONLY: + out->sm_io_cap = BLE_HS_IO_DISPLAY_ONLY; + break; + case BLE_UART_IO_CAP_NO_INPUT_OUTPUT: + out->sm_io_cap = BLE_HS_IO_NO_INPUT_OUTPUT; + break; + case BLE_UART_IO_CAP_KEYBOARD_ONLY: + out->sm_io_cap = BLE_HS_IO_KEYBOARD_ONLY; + break; + case BLE_UART_IO_CAP_DISPLAY_YES_NO: + out->sm_io_cap = BLE_HS_IO_DISPLAY_YESNO; + break; + case BLE_UART_IO_CAP_KEYBOARD_DISPLAY: + out->sm_io_cap = BLE_HS_IO_KEYBOARD_DISPLAY; + break; + case BLE_UART_IO_CAP_AUTO: + default: + out->sm_io_cap = out->mitm ? BLE_HS_IO_DISPLAY_ONLY + : BLE_HS_IO_NO_INPUT_OUTPUT; + break; + } + + /* Just Works (NoInputNoOutput) cannot satisfy MITM — the SM + * would reject pairing in flight. Catch it synchronously here. */ + if (out->mitm && out->sm_io_cap == BLE_HS_IO_NO_INPUT_OUTPUT) { + return BLE_UART_EINVAL; + } + return BLE_UART_OK; +} + int ble_uart_install(const ble_uart_config_t *cfg) { if (s_installed) { @@ -464,11 +891,91 @@ int ble_uart_install(const ble_uart_config_t *cfg) memset(&s_cfg, 0, sizeof(s_cfg)); } + /* Validate device_name length up front. Beyond + * BLE_UART_DEVICE_NAME_MAX the default-path advertising would + * silently fail at adv_set_fields time; surfacing the error here + * is much friendlier. strnlen with cap+1 also stops a missing-NUL + * caller buffer from running into uninitialised memory. */ + if (s_cfg.device_name != NULL) { + size_t nlen = strnlen(s_cfg.device_name, BLE_UART_DEVICE_NAME_MAX + 1); + if (nlen > BLE_UART_DEVICE_NAME_MAX) { + ESP_LOGE(TAG, "device_name too long: > %u bytes", + (unsigned)BLE_UART_DEVICE_NAME_MAX); + memset(&s_cfg, 0, sizeof(s_cfg)); + return BLE_UART_EINVAL; + } + } + + /* Validate caller-supplied advertising payloads up front so an + * oversized buffer fails the install instead of corrupting the + * adv packet at start time (where errors only surface in logs). + * (NULL + len>0 is also rejected — almost always a caller bug.) */ + if (s_cfg.adv_data_len > BLE_UART_ADV_DATA_MAX + || (s_cfg.adv_data == NULL && s_cfg.adv_data_len > 0)) { + ESP_LOGE(TAG, "bad adv_data: ptr=%p len=%u (max=%u)", + s_cfg.adv_data, + (unsigned)s_cfg.adv_data_len, + (unsigned)BLE_UART_ADV_DATA_MAX); + memset(&s_cfg, 0, sizeof(s_cfg)); + return BLE_UART_EINVAL; + } + if (s_cfg.scan_rsp_data_len > BLE_UART_SCAN_RSP_DATA_MAX + || (s_cfg.scan_rsp_data == NULL && s_cfg.scan_rsp_data_len > 0)) { + ESP_LOGE(TAG, "bad scan_rsp_data: ptr=%p len=%u (max=%u)", + s_cfg.scan_rsp_data, + (unsigned)s_cfg.scan_rsp_data_len, + (unsigned)BLE_UART_SCAN_RSP_DATA_MAX); + memset(&s_cfg, 0, sizeof(s_cfg)); + return BLE_UART_EINVAL; + } + + /* Resolve cfg.encrypted + per-feature overrides into a flat + * policy. Validation (out-of-range enums + impossible MITM/IO + * combination) happens up front so install() rejects bad cfgs + * synchronously, before any host-stack resources are allocated. */ + struct sec_policy pol; + int srv = resolve_sec_policy(&s_cfg, &pol); + if (srv != BLE_UART_OK) { + ESP_LOGE(TAG, "bad security cfg: encrypted=%d sc=%d bonding=%d " + "mitm=%d io_cap=%d", + (int)s_cfg.encrypted, + (int)s_cfg.security.sc, (int)s_cfg.security.bonding, + (int)s_cfg.security.mitm, (int)s_cfg.security.io_cap); + memset(&s_cfg, 0, sizeof(s_cfg)); + return srv; + } + s_link_encrypted = pol.link_enc; + s_mitm_required = pol.mitm; + + /* Copy raw payloads now (caller's pointers may not outlive install). + * For adv_data we also prepend the 3-byte Flags AD ourselves — + * the controller-visible Flags element is library-controlled and + * not part of what the application owns. */ + s_adv_data_len = 0; + s_scan_rsp_data_len = 0; + if (s_cfg.adv_data != NULL && s_cfg.adv_data_len > 0) { + s_adv_data[0] = 0x02; /* AD length */ + s_adv_data[1] = 0x01; /* AD type: Flags */ + s_adv_data[2] = BLE_HS_ADV_F_DISC_GEN | BLE_HS_ADV_F_BREDR_UNSUP; + memcpy(s_adv_data + 3, s_cfg.adv_data, s_cfg.adv_data_len); + s_adv_data_len = (uint8_t)(3 + s_cfg.adv_data_len); + } + if (s_cfg.scan_rsp_data != NULL && s_cfg.scan_rsp_data_len > 0) { + memcpy(s_scan_rsp_data, s_cfg.scan_rsp_data, s_cfg.scan_rsp_data_len); + s_scan_rsp_data_len = (uint8_t)s_cfg.scan_rsp_data_len; + } + /* Drop the pointers — install must not retain caller buffers. */ + s_cfg.adv_data = NULL; + s_cfg.scan_rsp_data = NULL; + esp_err_t err = nimble_port_init(); if (err != ESP_OK) { ESP_LOGE(TAG, "nimble_port_init rc=%d", err); return BLE_UART_EFAIL; } +#if MYNEWT_VAL(BLE_HS_AUTO_START) + s_hs_auto_start_pending = true; +#endif /* From here every failure must `goto fail` so nimble_port_deinit() * runs — leaving the port allocated breaks the next install(). */ @@ -477,23 +984,27 @@ int ble_uart_install(const ble_uart_config_t *cfg) ble_hs_cfg.store_status_cb = ble_store_util_status_rr; ble_hs_cfg.gatts_register_cb = register_cb; - /* Encrypted = LE Secure Connections + Bonding + MITM, DisplayOnly. - * Plaintext = SM disabled. */ - if (s_cfg.encrypted) { - ble_hs_cfg.sm_io_cap = BLE_HS_IO_DISPLAY_ONLY; - ble_hs_cfg.sm_sc = 1; - ble_hs_cfg.sm_bonding = 1; - ble_hs_cfg.sm_mitm = 1; + /* Apply the resolved security policy. NimBLE checks sm_bonding + * before consulting the key-distribution masks, so it's safe to + * leave them set unconditionally — they're a no-op when bonding=0. */ + if (pol.link_enc) { + ble_hs_cfg.sm_io_cap = pol.sm_io_cap; + ble_hs_cfg.sm_sc = pol.sc ? 1 : 0; + ble_hs_cfg.sm_bonding = pol.bonding ? 1 : 0; + ble_hs_cfg.sm_mitm = pol.mitm ? 1 : 0; ble_hs_cfg.sm_our_key_dist = BLE_SM_PAIR_KEY_DIST_ENC | BLE_SM_PAIR_KEY_DIST_ID; ble_hs_cfg.sm_their_key_dist = BLE_SM_PAIR_KEY_DIST_ENC | BLE_SM_PAIR_KEY_DIST_ID; } else { + /* Fully plaintext: SM disabled, no keys exchanged. */ ble_hs_cfg.sm_io_cap = BLE_HS_IO_NO_INPUT_OUTPUT; ble_hs_cfg.sm_sc = 0; ble_hs_cfg.sm_bonding = 0; ble_hs_cfg.sm_mitm = 0; } +#if NIMBLE_BLE_CONNECT ble_svc_gap_init(); +#endif ble_svc_gatt_init(); /* Cache the device name into our own buffer (caller's pointer may @@ -517,23 +1028,26 @@ int ble_uart_install(const ble_uart_config_t *cfg) s_dev_name[0] = '\0'; } - build_gatt_table(s_cfg.encrypted); + rc = register_uart_gatt_svc(); + if (rc != BLE_UART_OK) { + goto fail; + } + s_gatts_needs_readd = false; - rc = ble_gatts_count_cfg(s_svc_defs); - if (rc != 0) { - ESP_LOGE(TAG, "ble_gatts_count_cfg rc=%d", rc); - goto fail; - } - rc = ble_gatts_add_svcs(s_svc_defs); - if (rc != 0) { - ESP_LOGE(TAG, "ble_gatts_add_svcs rc=%d", rc); - goto fail; - } + /* Wire up the NVS bond store (requires CONFIG_BT_NIMBLE_NVS_PERSIST=y). + * Done here — not in open() — so bond-management APIs + * (ble_uart_get_bond_count / clear_bonds / remove_peer) work + * between install and open, letting callers wipe stale bonds + * before the first advertising window opens. */ + ble_store_config_init(); s_installed = true; return BLE_UART_OK; fail: +#if MYNEWT_VAL(BLE_HS_AUTO_START) + s_hs_auto_start_pending = false; +#endif nimble_port_deinit(); memset(&s_cfg, 0, sizeof(s_cfg)); return xlate_rc(rc); @@ -550,16 +1064,40 @@ int ble_uart_open(void) return BLE_UART_EALREADY; } - /* NVS bond store (requires CONFIG_BT_NIMBLE_NVS_PERSIST=y). */ - ble_store_config_init(); + if (s_gatts_needs_readd) { + int grc = reregister_gatt_svcs_after_close(); + if (grc != BLE_UART_OK) { + return grc; + } + s_gatts_needs_readd = false; + } - /* Spawn host task; on_sync starts advertising once controller is ready. */ + /* Spawn host task, then queue host start. on_sync() starts advertising + * once the controller sync completes. + * + * With BLE_HS_AUTO_START (default), install()'s nimble_port_init() + * already queued a one-shot start event — open() must not sched_start() + * again or ble_hs_start() returns BLE_HS_EALREADY and the host task + * asserts. After close()'s nimble_port_stop() the host is OFF and no + * AUTO_START event remains, so every later open() must sched_start(). */ nimble_port_freertos_init(nimble_host_task); +#if MYNEWT_VAL(BLE_HS_AUTO_START) + if (!s_hs_auto_start_pending) { + ble_hs_sched_start(); + } + s_hs_auto_start_pending = false; +#else + ble_hs_sched_start(); +#endif s_opened = true; return BLE_UART_OK; } -int ble_uart_close(void) +/* Body of ble_uart_close(); also called directly by the close-async + * worker, which has already latched s_closing itself. The public + * wrapper below uses s_closing to reject a sync close that races + * with an in-flight async close. */ +static int do_close(void) { if (!s_opened) { return BLE_UART_EALREADY; @@ -568,9 +1106,16 @@ int ble_uart_close(void) /* Latch first so GAP events stop re-arming advertising. */ s_shutting_down = true; - int rc = ble_gap_adv_stop(); - if (rc != 0 && rc != BLE_HS_EALREADY) { - ESP_LOGW(TAG, "adv_stop rc=%d", rc); + /* Only stop adv if it's still running. Undirected adv auto-stops + * at the LL on connect, so calling adv_stop while connected just + * burns one HCI cmd that NimBLE answers with BLE_HS_EALREADY. + * Mirrors the Bluedroid backend's `if (s_adv_active)` gate. */ + int rc = 0; + if (ble_gap_adv_active()) { + rc = ble_gap_adv_stop(); + if (rc != 0 && rc != BLE_HS_EALREADY) { + ESP_LOGW(TAG, "adv_stop rc=%d", rc); + } } /* Graceful disconnect: wait up to 500 ms for the disconnect event @@ -588,8 +1133,8 @@ int ble_uart_close(void) } } - /* nimble_host_task self-cleans (port_freertos_deinit + delete) when - * port_run returns, so no explicit join. */ + /* nimble_port_stop() waits for port_run() to exit in the host task; + * the host then vTaskDelete(NULL) — no join after stop returns. */ rc = nimble_port_stop(); if (rc != 0) { ESP_LOGE(TAG, "nimble_port_stop rc=%d", rc); @@ -597,6 +1142,16 @@ int ble_uart_close(void) return BLE_UART_EFAIL; } + /* Host stop frees the svc-def pointer array; stale ATT rows can remain + * until cleared. Use the public ble_gatts_reset() only (no NimBLE + * source edits) and re-queue svc defs on the next open(). */ + rc = ble_gatts_reset(); + if (rc != 0) { + ESP_LOGW(TAG, "ble_gatts_reset rc=%d", rc); + } + s_tx_val_handle = 0; + s_gatts_needs_readd = true; + s_conn_handle = BLE_HS_CONN_HANDLE_NONE; s_subscribed = false; s_opened = false; @@ -604,12 +1159,237 @@ int ble_uart_close(void) return BLE_UART_OK; } +int ble_uart_close(void) +{ + /* If a ble_uart_close_async() worker is in flight, the close + * sequence is already running on the worker's task — let it + * finish rather than racing it from here. The worker drives + * s_opened to false on its own, so the next sync close after + * the worker drains will get the natural !s_opened EALREADY. */ + if (s_closing) { + return BLE_UART_EALREADY; + } + return do_close(); +} + +/* ===== Async close ==================================================== */ + +/* Background worker spawned by ble_uart_close_async(). Lives just + * long enough to run the synchronous close path (which itself can + * block on the disconnect timeout and on nimble_port_stop()), then + * fires the completion event and self-deletes. + * + * Spawned as a separate task — not a deferred ble_npl callout — so + * that nimble_port_stop() can join the host task without us being + * the host task. */ +static void close_async_task(void *arg) +{ + (void)arg; + + /* Bypass the s_closing gate in ble_uart_close(): we ARE the + * in-flight async close that gate is meant to protect against. */ + int rc = do_close(); + if (rc != BLE_UART_OK && rc != BLE_UART_EALREADY) { + ESP_LOGW(TAG, "close_async: do_close rc=%d", rc); + } + + /* Deliver CLOSED on the worker task. Applications must defer + * ble_uart_uninstall() to another task (PORTING.md §5.3.2). + * Concurrent uninstall() may clear s_cfg while we read on_event. */ + emit_evt(&(ble_uart_evt_t){ + .id = BLE_UART_EVT_CLOSED, + .closed = { .status = rc }, + }); + + s_closing = false; + vTaskDelete(NULL); +} + +int ble_uart_close_async(void) +{ + /* Same-state checks as the synchronous variant: nothing to close + * if we never opened, and no point spawning a second worker if + * the first hasn't drained yet. */ + if (!s_opened || s_closing) { + return BLE_UART_EALREADY; + } + + /* Latch BEFORE spawning so a racing caller (different task) sees + * the in-flight state immediately and gets EALREADY. */ + s_closing = true; + + /* 3 KB is comfortably more than the close path uses (mostly small + * GAP/HCI helpers + a 50×10ms vTaskDelay loop); bump if you wedge + * a heavy on_event handler between adv_stop and CLOSED. */ + BaseType_t ok = xTaskCreate(close_async_task, "ble_close", + 3072, NULL, + tskIDLE_PRIORITY + 2, NULL); + if (ok != pdPASS) { + s_closing = false; + return BLE_UART_ENOMEM; + } + return BLE_UART_OK; +} + +/* ===== Bond management ================================================ */ + +/* Public API uses big-endian bytes (bytes[0] = MSB) but NimBLE stores + * addresses little-endian (val[0] = LSB). Caller must supply + * BLE_UART_ADDR_TYPE_PUBLIC/RANDOM (0/1); the bond store keys on + * those same identity types. */ +static void to_nimble_addr(const ble_uart_addr_t *src, ble_addr_t *dst) +{ + dst->type = src->type; + for (int i = 0; i < 6; i++) { + dst->val[i] = src->bytes[5 - i]; + } +} + +#if MYNEWT_VAL(BLE_STORE_MAX_BONDS) > 0 +/* ble_store_util_bonded_peers enumerates OUR_SEC (unique peer_addr). + * Heap-allocate the scratch buffer so callers on small-stack tasks are + * safe regardless of CONFIG_BT_NIMBLE_MAX_BONDS. */ +static int bonded_peers_unique_count(size_t *out_count) +{ + const int max_peers = MYNEWT_VAL(BLE_STORE_MAX_BONDS); + ble_addr_t *peer_addrs = calloc((size_t)max_peers, sizeof(*peer_addrs)); + if (peer_addrs == NULL) { + return BLE_UART_ENOMEM; + } + int num_peers = 0; + int rc = ble_store_util_bonded_peers(peer_addrs, &num_peers, max_peers); + free(peer_addrs); + if (rc != 0) { + ESP_LOGW(TAG, "ble_store_util_bonded_peers rc=%d", rc); + return xlate_rc(rc); + } + *out_count = (size_t)num_peers; + return BLE_UART_OK; +} +#endif + +int ble_uart_get_bond_count(size_t *out_count) +{ + if (out_count == NULL || !s_installed) { + return BLE_UART_EINVAL; + } +#if MYNEWT_VAL(BLE_STORE_MAX_BONDS) > 0 + return bonded_peers_unique_count(out_count); +#else + *out_count = 0; + return BLE_UART_OK; +#endif +} + +int ble_uart_get_bonded_peers(ble_uart_addr_t *out, size_t cap, size_t *out_count) +{ + if (out_count == NULL || !s_installed + || (out == NULL && cap > 0)) { + return BLE_UART_EINVAL; + } + +#if MYNEWT_VAL(BLE_STORE_MAX_BONDS) <= 0 + *out_count = 0; + return BLE_UART_OK; +#else + /* ble_store_util_bonded_peers enumerates OUR_SEC (unique peer_addr). + * Size the buffer to BLE_STORE_MAX_BONDS — the compile-time cap — + * not PEER_SEC/OUR_SEC raw entry counts (they can disagree). */ + const int max_peers = MYNEWT_VAL(BLE_STORE_MAX_BONDS); + + if (cap == 0) { + return bonded_peers_unique_count(out_count); + } + + int n_our = 0; + int rc = ble_store_util_count(BLE_STORE_OBJ_TYPE_OUR_SEC, &n_our); + if (rc != 0) { + ESP_LOGW(TAG, "ble_store_util_count rc=%d", rc); + return xlate_rc(rc); + } + if (n_our <= 0) { + *out_count = 0; + return BLE_UART_OK; + } + + /* Bonded peers in NimBLE's native LE byte order. Heap-allocate to + * keep the host task's stack untouched even when many peers exist. */ + ble_addr_t *tmp = calloc((size_t)max_peers, sizeof(*tmp)); + if (tmp == NULL) { + return BLE_UART_ENOMEM; + } + int got = 0; + rc = ble_store_util_bonded_peers(tmp, &got, max_peers); + if (rc != 0) { + ESP_LOGW(TAG, "ble_store_util_bonded_peers rc=%d", rc); + free(tmp); + return xlate_rc(rc); + } + + /* Copy at most cap entries into the caller's buffer, flipping + * NimBLE's LE byte order back to our public big-endian convention + * and narrowing addr types to BLE_UART_ADDR_TYPE_* . */ + size_t to_copy = ((size_t)got < cap) ? (size_t)got : cap; + for (size_t i = 0; i < to_copy; i++) { + from_nimble_addr(&tmp[i], &out[i]); + } + free(tmp); + *out_count = (size_t)got; + return BLE_UART_OK; +#endif +} + +int ble_uart_remove_peer(const ble_uart_addr_t *peer) +{ + if (peer == NULL || !s_installed) { + return BLE_UART_EINVAL; + } + ble_addr_t addr; + to_nimble_addr(peer, &addr); + int rc = ble_store_util_delete_peer(&addr); + if (rc != 0) { + ESP_LOGW(TAG, "ble_store_util_delete_peer rc=%d", rc); + return xlate_rc(rc); + } + return BLE_UART_OK; +} + +int ble_uart_clear_bonds(void) +{ + if (!s_installed) { + return BLE_UART_EINVAL; + } + /* Wipes peer LTK + our LTK + persisted CCCD (and a few NimBLE + * internal records). Doesn't touch our s_cfg or any other NVS + * namespace. */ + int rc = ble_store_clear(); + if (rc != 0) { + ESP_LOGW(TAG, "ble_store_clear rc=%d", rc); + return xlate_rc(rc); + } + return BLE_UART_OK; +} + +/* ===== Uninstall ====================================================== */ + int ble_uart_uninstall(void) { if (!s_installed) { return BLE_UART_EALREADY; } + /* If a ble_uart_close_async() worker is still draining, poll s_closing + * for up to ~5 s before touching shared state. On timeout, teardown + * continues anyway — applications must follow PORTING.md §5.3.2 so + * uninstall runs only after the worker has finished. */ + for (int i = 0; i < 500 && s_closing; i++) { + vTaskDelay(pdMS_TO_TICKS(10)); + } + if (s_closing) { + ESP_LOGW(TAG, "uninstall: close_async worker still running, " + "tearing down anyway"); + } + /* Best-effort cleanup. Do NOT early-return on a per-step failure: * leaving s_installed=true with partially torn-down NimBLE state * makes the module unrecoverable (can't re-install, can't retry @@ -639,14 +1419,25 @@ int ble_uart_uninstall(void) } memset(&s_cfg, 0, sizeof(s_cfg)); - s_dev_name[0] = '\0'; - s_tx_val_handle = 0; - s_conn_handle = BLE_HS_CONN_HANDLE_NONE; - s_subscribed = false; - s_own_addr_type = 0; - s_shutting_down = false; - s_installed = false; - s_opened = false; + s_dev_name[0] = '\0'; + s_tx_val_handle = 0; + s_conn_handle = BLE_HS_CONN_HANDLE_NONE; + s_subscribed = false; + s_own_addr_type = 0; + s_shutting_down = false; + s_closing = false; + s_installed = false; + s_opened = false; + s_gatts_needs_readd = false; +#if MYNEWT_VAL(BLE_HS_AUTO_START) + s_hs_auto_start_pending = false; +#endif + s_adv_data_len = 0; + s_scan_rsp_data_len = 0; + s_link_encrypted = false; + s_mitm_required = false; + s_pending_io_conn = BLE_HS_CONN_HANDLE_NONE; + s_pending_io_action = 0; return first_rc; } diff --git a/examples/bluetooth/esp_hid_device/main/esp_hid_device_main.c b/examples/bluetooth/esp_hid_device/main/esp_hid_device_main.c index d0081b843be..39e760f7e55 100644 --- a/examples/bluetooth/esp_hid_device/main/esp_hid_device_main.c +++ b/examples/bluetooth/esp_hid_device/main/esp_hid_device_main.c @@ -41,6 +41,7 @@ #include "esp_hid_gap.h" static const char *TAG = "HID_DEV_DEMO"; +#define HID_BATTERY_LEVEL 60 typedef struct { @@ -941,6 +942,7 @@ void app_main(void) ESP_LOGI(TAG, "setting ble device"); ESP_ERROR_CHECK( esp_hidd_dev_init(&ble_hid_config, ESP_HID_TRANSPORT_BLE, ble_hidd_event_callback, &s_ble_hid_param.hid_dev)); + ESP_ERROR_CHECK(esp_hidd_dev_battery_set(s_ble_hid_param.hid_dev, HID_BATTERY_LEVEL)); #endif #if CONFIG_BT_HID_DEVICE_ENABLED diff --git a/examples/bluetooth/nimble/ble_chan_sound_initiator/main/gatt_svr.c b/examples/bluetooth/nimble/ble_chan_sound_initiator/main/gatt_svr.c index 168d0035c79..2f06346da8c 100644 --- a/examples/bluetooth/nimble/ble_chan_sound_initiator/main/gatt_svr.c +++ b/examples/bluetooth/nimble/ble_chan_sound_initiator/main/gatt_svr.c @@ -31,7 +31,8 @@ gatt_svr_init(void) #if MYNEWT_VAL(BLE_GATTS) ble_svc_gatt_init(); #endif +#if MYNEWT_VAL(BLE_GATTS) && CONFIG_BT_NIMBLE_RAS_SERVICE ble_svc_ras_init(); - +#endif return 0; } diff --git a/examples/bluetooth/nimble/ble_chan_sound_reflector/main/gatt_svr.c b/examples/bluetooth/nimble/ble_chan_sound_reflector/main/gatt_svr.c index fdadd4c2aa7..f559b1d6f34 100644 --- a/examples/bluetooth/nimble/ble_chan_sound_reflector/main/gatt_svr.c +++ b/examples/bluetooth/nimble/ble_chan_sound_reflector/main/gatt_svr.c @@ -31,7 +31,8 @@ custom_gatt_svr_init(void) #if MYNEWT_VAL(BLE_GATTS) ble_svc_gatt_init(); #endif +#if MYNEWT_VAL(BLE_GATTS) && CONFIG_BT_NIMBLE_RAS_SERVICE ble_svc_ras_init(); - +#endif return 0; } diff --git a/examples/bluetooth/nimble/ble_cte/ble_periodic_adv_with_cte/main/main.c b/examples/bluetooth/nimble/ble_cte/ble_periodic_adv_with_cte/main/main.c index 21c701170fb..ac8941cec61 100644 --- a/examples/bluetooth/nimble/ble_cte/ble_periodic_adv_with_cte/main/main.c +++ b/examples/bluetooth/nimble/ble_cte/ble_periodic_adv_with_cte/main/main.c @@ -19,6 +19,11 @@ static const char *TAG = "CTE_ADV_EXAMPLE"; static uint8_t s_periodic_adv_raw_data[] = {0x0D, BLE_HS_ADV_TYPE_COMP_NAME, 'C','T','E',' ','P','e','r','i','o','d','i','c'}; +#if !(MYNEWT_VAL(BLE_EXT_ADV) && MYNEWT_VAL(BLE_PERIODIC_ADV) && MYNEWT_VAL(BLE_AOA_AOD)) +#error "This example requires NimBLE Extended Advertising, Periodic Advertising, and CTE (AoA/AoD). " \ + "Use a supported target from README.md (e.g. esp32h2, esp32c5, esp32c61) and run idf.py set-target before build." +#endif + /** * @brief Configure and start periodic advertising with CTE */ diff --git a/examples/bluetooth/nimble/ble_cte/ble_periodic_adv_with_cte/sdkconfig.defaults b/examples/bluetooth/nimble/ble_cte/ble_periodic_adv_with_cte/sdkconfig.defaults index 4e27f1576f2..2a58e694c98 100644 --- a/examples/bluetooth/nimble/ble_cte/ble_periodic_adv_with_cte/sdkconfig.defaults +++ b/examples/bluetooth/nimble/ble_cte/ble_periodic_adv_with_cte/sdkconfig.defaults @@ -1,7 +1,11 @@ -# This file was generated using idf.py save-defconfig. It can be edited manually. -# Espressif IoT Development Framework (ESP-IDF) 5.5.0 Project Minimal Configuration +# Minimal NimBLE config for periodic advertising with CTE (requires BLE 5.0). # +# Supported targets: ESP32-H2, ESP32-C5, ESP32-C61, etc. (see README.md). +# Run: idf.py set-target before building. + CONFIG_BT_ENABLED=y CONFIG_BT_NIMBLE_ENABLED=y -CONFIG_BT_NIMBLE_AOA_AOD=y +CONFIG_BT_NIMBLE_50_FEATURE_SUPPORT=y CONFIG_BT_NIMBLE_EXT_ADV=y +CONFIG_BT_NIMBLE_ENABLE_PERIODIC_ADV=y +CONFIG_BT_NIMBLE_AOA_AOD=y diff --git a/examples/bluetooth/nimble/ble_cte/ble_periodic_adv_with_cte/sdkconfig.defaults.esp32h2 b/examples/bluetooth/nimble/ble_cte/ble_periodic_adv_with_cte/sdkconfig.defaults.esp32h2 index 9543898142c..c0a363679de 100644 --- a/examples/bluetooth/nimble/ble_cte/ble_periodic_adv_with_cte/sdkconfig.defaults.esp32h2 +++ b/examples/bluetooth/nimble/ble_cte/ble_periodic_adv_with_cte/sdkconfig.defaults.esp32h2 @@ -1,5 +1,6 @@ -# This file was generated using idf.py save-defconfig. It can be edited manually. -# Espressif IoT Development Framework (ESP-IDF) 5.5.0 Project Minimal Configuration -# CONFIG_IDF_TARGET="esp32h2" CONFIG_BT_NIMBLE_SECURITY_ENABLE=n +CONFIG_BT_NIMBLE_50_FEATURE_SUPPORT=y +CONFIG_BT_NIMBLE_EXT_ADV=y +CONFIG_BT_NIMBLE_ENABLE_PERIODIC_ADV=y +CONFIG_BT_NIMBLE_AOA_AOD=y diff --git a/examples/bluetooth/nimble/ble_cts/cts_cent/main/main.c b/examples/bluetooth/nimble/ble_cts/cts_cent/main/main.c index bd765009ea2..5a358a0c363 100644 --- a/examples/bluetooth/nimble/ble_cts/cts_cent/main/main.c +++ b/examples/bluetooth/nimble/ble_cts/cts_cent/main/main.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2017-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2017-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -207,10 +207,6 @@ ext_ble_cts_cent_should_connect(const struct ble_gap_ext_disc_desc *disc) int offset = 0; int ad_struct_len = 0; uint8_t test_addr[6]; - uint32_t peer_addr[6]; - - memset(peer_addr, 0x0, sizeof peer_addr); - if (disc->legacy_event_type != BLE_HCI_ADV_RPT_EVTYPE_ADV_IND && disc->legacy_event_type != BLE_HCI_ADV_RPT_EVTYPE_DIR_IND) { return 0; @@ -219,15 +215,7 @@ ext_ble_cts_cent_should_connect(const struct ble_gap_ext_disc_desc *disc) ESP_LOGI(tag, "Peer address from menuconfig: %s", CONFIG_EXAMPLE_PEER_ADDR); /* Convert string to address */ - sscanf(CONFIG_EXAMPLE_PEER_ADDR, "%lx:%lx:%lx:%lx:%lx:%lx", - &peer_addr[5], &peer_addr[4], &peer_addr[3], - &peer_addr[2], &peer_addr[1], &peer_addr[0]); - - /* Conversion */ - for(int i=0; i<6; i++) { - test_addr[i] = (uint8_t )peer_addr[i]; - } - + peer_addr_parse(CONFIG_EXAMPLE_PEER_ADDR, test_addr); if (memcmp(test_addr, disc->addr.val, sizeof(disc->addr.val)) != 0) { return 0; } @@ -268,10 +256,6 @@ ble_cts_cent_should_connect(const struct ble_gap_disc_desc *disc) int rc; int i; uint8_t test_addr[6]; - uint32_t peer_addr[6]; - - memset(peer_addr, 0x0, sizeof peer_addr); - /* The device has to be advertising connectability. */ if (disc->event_type != BLE_HCI_ADV_RPT_EVTYPE_ADV_IND && disc->event_type != BLE_HCI_ADV_RPT_EVTYPE_DIR_IND) { @@ -287,15 +271,7 @@ ble_cts_cent_should_connect(const struct ble_gap_disc_desc *disc) if (strlen(CONFIG_EXAMPLE_PEER_ADDR) && (strncmp(CONFIG_EXAMPLE_PEER_ADDR, "ADDR_ANY", strlen("ADDR_ANY")) != 0)) { ESP_LOGI(tag, "Peer address from menuconfig: %s", CONFIG_EXAMPLE_PEER_ADDR); /* Convert string to address */ - sscanf(CONFIG_EXAMPLE_PEER_ADDR, "%lx:%lx:%lx:%lx:%lx:%lx", - &peer_addr[5], &peer_addr[4], &peer_addr[3], - &peer_addr[2], &peer_addr[1], &peer_addr[0]); - - /* Conversion */ - for (int i=0; i<6; i++) { - test_addr[i] = (uint8_t )peer_addr[i]; - } - + peer_addr_parse(CONFIG_EXAMPLE_PEER_ADDR, test_addr); if (memcmp(test_addr, disc->addr.val, sizeof(disc->addr.val)) != 0) { return 0; } diff --git a/examples/bluetooth/nimble/ble_enc_adv_data/enc_adv_data_cent/main/main.c b/examples/bluetooth/nimble/ble_enc_adv_data/enc_adv_data_cent/main/main.c index e315ae02922..3d17bd4e76d 100644 --- a/examples/bluetooth/nimble/ble_enc_adv_data/enc_adv_data_cent/main/main.c +++ b/examples/bluetooth/nimble/ble_enc_adv_data/enc_adv_data_cent/main/main.c @@ -322,10 +322,6 @@ enc_adv_data_cent_ext_should_connect(const struct ble_gap_ext_disc_desc *disc) uint32_t *addr_offset; #endif // CONFIG_EXAMPLE_USE_CI_ADDRESS uint8_t test_addr[6]; - uint32_t peer_addr[6]; - - memset(peer_addr, 0x0, sizeof peer_addr); - if (disc->legacy_event_type != BLE_HCI_ADV_RPT_EVTYPE_ADV_IND && disc->legacy_event_type != BLE_HCI_ADV_RPT_EVTYPE_DIR_IND) { return 0; @@ -334,9 +330,7 @@ enc_adv_data_cent_ext_should_connect(const struct ble_gap_ext_disc_desc *disc) #if !CONFIG_EXAMPLE_USE_CI_ADDRESS ESP_LOGI(tag, "Peer address from menuconfig: %s", CONFIG_EXAMPLE_PEER_ADDR); /* Convert string to address */ - sscanf(CONFIG_EXAMPLE_PEER_ADDR, "%lx:%lx:%lx:%lx:%lx:%lx", - &peer_addr[5], &peer_addr[4], &peer_addr[3], - &peer_addr[2], &peer_addr[1], &peer_addr[0]); + peer_addr_parse(CONFIG_EXAMPLE_PEER_ADDR, test_addr); #endif /* Conversion */ @@ -348,7 +342,7 @@ enc_adv_data_cent_ext_should_connect(const struct ble_gap_ext_disc_desc *disc) addr_offset = (uint32_t *)&test_addr[1]; *addr_offset = atoi(CONFIG_EXAMPLE_PEER_ADDR); test_addr[5] = 0xC3; - test_addr[0] = TEST_CI_ADDRESS_CHIP_OFFSET; + test_addr[0] = CONFIG_IDF_FIRMWARE_CHIP_ID; #endif if (memcmp(test_addr, disc->addr.val, sizeof(disc->addr.val)) != 0) { return 0; @@ -406,10 +400,6 @@ enc_adv_data_cent_should_connect(const struct ble_gap_disc_desc *disc) int rc; int i; uint8_t test_addr[6]; - uint32_t peer_addr[6]; - - memset(peer_addr, 0x0, sizeof peer_addr); - if (disc->event_type != BLE_HCI_ADV_RPT_EVTYPE_ADV_IND && disc->event_type != BLE_HCI_ADV_RPT_EVTYPE_DIR_IND) { return 0; @@ -423,15 +413,7 @@ enc_adv_data_cent_should_connect(const struct ble_gap_disc_desc *disc) if (strlen(CONFIG_EXAMPLE_PEER_ADDR) && (strncmp(CONFIG_EXAMPLE_PEER_ADDR, "ADDR_ANY", strlen ("ADDR_ANY")) != 0)) { MODLOG_DFLT(INFO, "Peer address from menuconfig: %s", CONFIG_EXAMPLE_PEER_ADDR); /* Convert string to address */ - sscanf(CONFIG_EXAMPLE_PEER_ADDR, "%lx:%lx:%lx:%lx:%lx:%lx", - &peer_addr[5], &peer_addr[4], &peer_addr[3], - &peer_addr[2], &peer_addr[1], &peer_addr[0]); - - /* Conversion */ - for (int i=0; i<6; i++) { - test_addr[i] = (uint8_t )peer_addr[i]; - } - + peer_addr_parse(CONFIG_EXAMPLE_PEER_ADDR, test_addr); if (memcmp(test_addr, disc->addr.val, sizeof(disc->addr.val)) != 0) { return 0; } diff --git a/examples/bluetooth/nimble/ble_htp/htp_cent/main/main.c b/examples/bluetooth/nimble/ble_htp/htp_cent/main/main.c index 81bf979739a..ccc03dbecac 100644 --- a/examples/bluetooth/nimble/ble_htp/htp_cent/main/main.c +++ b/examples/bluetooth/nimble/ble_htp/htp_cent/main/main.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2017-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2017-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -320,10 +320,7 @@ ext_ble_htp_cent_should_connect(const struct ble_gap_ext_disc_desc *disc) int offset = 0; int ad_struct_len = 0; uint8_t test_addr[6]; - uint32_t peer_addr[6]; - - memset(peer_addr, 0x0, sizeof peer_addr); - + uint8_t parsed_addr[6]; if (disc->legacy_event_type != BLE_HCI_ADV_RPT_EVTYPE_ADV_IND && disc->legacy_event_type != BLE_HCI_ADV_RPT_EVTYPE_DIR_IND) { return 0; @@ -331,13 +328,9 @@ ext_ble_htp_cent_should_connect(const struct ble_gap_ext_disc_desc *disc) if (strlen(CONFIG_EXAMPLE_PEER_ADDR) && (strncmp(CONFIG_EXAMPLE_PEER_ADDR, "ADDR_ANY", strlen ("ADDR_ANY")) != 0)) { ESP_LOGI(tag, "Peer address from menuconfig: %s", CONFIG_EXAMPLE_PEER_ADDR); /* Convert string to address */ - sscanf(CONFIG_EXAMPLE_PEER_ADDR, "%lx:%lx:%lx:%lx:%lx:%lx", - &peer_addr[5], &peer_addr[4], &peer_addr[3], - &peer_addr[2], &peer_addr[1], &peer_addr[0]); - - /* Conversion */ - for (int i=0; i<6; i++) { - test_addr[i] = (uint8_t )peer_addr[5 - i]; + peer_addr_parse(CONFIG_EXAMPLE_PEER_ADDR, parsed_addr); + for (int i = 0; i < 6; i++) { + test_addr[i] = parsed_addr[5 - i]; } if (memcmp(test_addr, disc->addr.val, sizeof(disc->addr.val)) != 0) { @@ -377,10 +370,6 @@ ble_htp_cent_should_connect(const struct ble_gap_disc_desc *disc) int rc; int i; uint8_t test_addr[6]; - uint32_t peer_addr[6]; - - memset(peer_addr, 0x0, sizeof peer_addr); - /* The device has to be advertising connectability. */ if (disc->event_type != BLE_HCI_ADV_RPT_EVTYPE_ADV_IND && disc->event_type != BLE_HCI_ADV_RPT_EVTYPE_DIR_IND) { @@ -396,15 +385,7 @@ ble_htp_cent_should_connect(const struct ble_gap_disc_desc *disc) if (strlen(CONFIG_EXAMPLE_PEER_ADDR) && (strncmp(CONFIG_EXAMPLE_PEER_ADDR, "ADDR_ANY", strlen("ADDR_ANY")) != 0)) { ESP_LOGI(tag, "Peer address from menuconfig: %s", CONFIG_EXAMPLE_PEER_ADDR); /* Convert string to address */ - sscanf(CONFIG_EXAMPLE_PEER_ADDR, "%lx:%lx:%lx:%lx:%lx:%lx", - &peer_addr[5], &peer_addr[4], &peer_addr[3], - &peer_addr[2], &peer_addr[1], &peer_addr[0]); - - /* Conversion */ - for (int i=0; i<6; i++) { - test_addr[i] = (uint8_t )peer_addr[i]; - } - + peer_addr_parse(CONFIG_EXAMPLE_PEER_ADDR, test_addr); if (memcmp(test_addr, disc->addr.val, sizeof(disc->addr.val)) != 0) { return 0; } diff --git a/examples/bluetooth/nimble/ble_htp/htp_prph/main/gatt_svr.c b/examples/bluetooth/nimble/ble_htp/htp_prph/main/gatt_svr.c index 82b6b1f1bcf..aae6bd14d51 100644 --- a/examples/bluetooth/nimble/ble_htp/htp_prph/main/gatt_svr.c +++ b/examples/bluetooth/nimble/ble_htp/htp_prph/main/gatt_svr.c @@ -123,7 +123,7 @@ gatt_svr_init(void) #if MYNEWT_VAL(BLE_GATTS) ble_svc_gatt_init(); #endif -#if CONFIG_BT_NIMBLE_ANS_SERVICE +#if CONFIG_BT_NIMBLE_HTP_SERVICE ble_svc_htp_init(); #endif diff --git a/examples/bluetooth/nimble/ble_l2cap_coc/coc_blecent/main/main.c b/examples/bluetooth/nimble/ble_l2cap_coc/coc_blecent/main/main.c index e6cff455f2f..c7f4fa68efe 100644 --- a/examples/bluetooth/nimble/ble_l2cap_coc/coc_blecent/main/main.c +++ b/examples/bluetooth/nimble/ble_l2cap_coc/coc_blecent/main/main.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2021-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2021-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Unlicense OR CC0-1.0 */ @@ -266,10 +266,6 @@ ext_blecent_should_connect(const struct ble_gap_ext_disc_desc *disc) int offset = 0; int ad_struct_len = 0; uint8_t test_addr[6]; - uint32_t peer_addr[6]; - - memset(peer_addr, 0x0, sizeof peer_addr); - if (disc->legacy_event_type != BLE_HCI_ADV_RPT_EVTYPE_ADV_IND && disc->legacy_event_type != BLE_HCI_ADV_RPT_EVTYPE_DIR_IND) { return 0; @@ -278,15 +274,7 @@ ext_blecent_should_connect(const struct ble_gap_ext_disc_desc *disc) (strncmp(CONFIG_EXAMPLE_PEER_ADDR, "ADDR_ANY", strlen("ADDR_ANY")) != 0)) { ESP_LOGI(tag, "Peer address from menuconfig: %s", CONFIG_EXAMPLE_PEER_ADDR); /* Convert string to address */ - sscanf(CONFIG_EXAMPLE_PEER_ADDR, "%lx:%lx:%lx:%lx:%lx:%lx", - &peer_addr[5], &peer_addr[4], &peer_addr[3], - &peer_addr[2], &peer_addr[1], &peer_addr[0]); - - /* Conversion */ - for (int i=0; i<6; i++) { - test_addr[i] = (uint8_t )peer_addr[i]; - } - + peer_addr_parse(CONFIG_EXAMPLE_PEER_ADDR, test_addr); if (memcmp(test_addr, disc->addr.val, sizeof(disc->addr.val)) != 0) { return 0; } @@ -329,10 +317,6 @@ blecent_should_connect(const struct ble_gap_disc_desc *disc) int rc; int i; uint8_t test_addr[6]; - uint32_t peer_addr[6]; - - memset(peer_addr, 0x0, sizeof peer_addr); - /* The device has to be advertising connectability. */ if (disc->event_type != BLE_HCI_ADV_RPT_EVTYPE_ADV_IND && disc->event_type != BLE_HCI_ADV_RPT_EVTYPE_DIR_IND) { @@ -349,15 +333,7 @@ blecent_should_connect(const struct ble_gap_disc_desc *disc) (strncmp(CONFIG_EXAMPLE_PEER_ADDR, "ADDR_ANY", strlen("ADDR_ANY")) != 0)) { MODLOG_DFLT(INFO, "Peer address from menuconfig:%s", CONFIG_EXAMPLE_PEER_ADDR); /* Convert string to address */ - sscanf(CONFIG_EXAMPLE_PEER_ADDR, "%lx:%lx:%lx:%lx:%lx:%lx", - &peer_addr[5], &peer_addr[4], &peer_addr[3], - &peer_addr[2], &peer_addr[1], &peer_addr[0]); - - /* Conversion */ - for (int i=0; i<6; i++) { - test_addr[i] = (uint8_t )peer_addr[i]; - } - + peer_addr_parse(CONFIG_EXAMPLE_PEER_ADDR, test_addr); if (memcmp(test_addr, disc->addr.val, sizeof(disc->addr.val)) != 0) { return 0; } diff --git a/examples/bluetooth/nimble/ble_l2cap_coc/coc_bleprph/main/main.c b/examples/bluetooth/nimble/ble_l2cap_coc/coc_bleprph/main/main.c index a8e5f3b8df0..3f3c46a1663 100644 --- a/examples/bluetooth/nimble/ble_l2cap_coc/coc_bleprph/main/main.c +++ b/examples/bluetooth/nimble/ble_l2cap_coc/coc_bleprph/main/main.c @@ -196,8 +196,8 @@ bleprph_l2cap_coc_accept(uint16_t conn_handle, uint16_t peer_mtu, { struct os_mbuf *sdu_rx; - console_printf("LE CoC accepting, chan: 0x%08lx, peer_mtu %d\n", - (uint32_t) chan, peer_mtu); + console_printf("LE CoC accepting, chan: 0x%08x, peer_mtu %d\n", + (unsigned) (uint32_t) chan, peer_mtu); sdu_rx = os_mbuf_get_pkthdr(&sdu_os_mbuf_pool, 0); if (!sdu_rx) { diff --git a/examples/bluetooth/nimble/ble_multi_conn/ble_multi_conn_cent/main/main.c b/examples/bluetooth/nimble/ble_multi_conn/ble_multi_conn_cent/main/main.c index a03a6bac9b3..d79daed6f84 100644 --- a/examples/bluetooth/nimble/ble_multi_conn/ble_multi_conn_cent/main/main.c +++ b/examples/bluetooth/nimble/ble_multi_conn/ble_multi_conn_cent/main/main.c @@ -14,6 +14,12 @@ #include "services/gap/ble_svc_gap.h" #include "ble_multi_conn_cent.h" +#if !MYNEWT_VAL(BLE_EXT_ADV) || !MYNEWT_VAL(OPTIMIZE_MULTI_CONN) +#error "This example requires NimBLE Extended Advertising and multi-connection optimization. " \ + "Enable BT_NIMBLE_50_FEATURE_SUPPORT, BT_NIMBLE_EXT_ADV, and BT_NIMBLE_OPTIMIZE_MULTI_CONN; " \ + "use a supported target from README.md (e.g. esp32h2, esp32c6) and run idf.py set-target." +#endif + #define BLE_PEER_NAME "esp-multi-conn" #define BLE_PEER_MAX_NUM (MYNEWT_VAL(BLE_MAX_CONNECTIONS) - 1) #define BLE_PREF_EVT_LEN_MS (5) diff --git a/examples/bluetooth/nimble/ble_multi_conn/ble_multi_conn_cent/sdkconfig.defaults b/examples/bluetooth/nimble/ble_multi_conn/ble_multi_conn_cent/sdkconfig.defaults index 762eb4d35ee..5ed952b38ab 100644 --- a/examples/bluetooth/nimble/ble_multi_conn/ble_multi_conn_cent/sdkconfig.defaults +++ b/examples/bluetooth/nimble/ble_multi_conn/ble_multi_conn_cent/sdkconfig.defaults @@ -1,10 +1,11 @@ -# This file was generated using idf.py save-defconfig. It can be edited manually. -# Espressif IoT Development Framework (ESP-IDF) Project Minimal Configuration -# +# Minimal NimBLE config for multi-connection central (requires BLE 5.0 ext adv). +# Supported targets: ESP32-C5, ESP32-C6, ESP32-C61, ESP32-H2, etc. (see README.md). + CONFIG_BT_ENABLED=y CONFIG_BT_NIMBLE_ENABLED=y -CONFIG_BT_NIMBLE_HCI_EVT_BUF_SIZE=70 +CONFIG_BT_NIMBLE_50_FEATURE_SUPPORT=y CONFIG_BT_NIMBLE_EXT_ADV=y +CONFIG_BT_NIMBLE_TRANSPORT_EVT_SIZE=70 CONFIG_BT_NIMBLE_MAX_CONNECTIONS=70 CONFIG_BT_NIMBLE_GATT_MAX_PROCS=70 CONFIG_BT_NIMBLE_MSYS_1_BLOCK_COUNT=100 diff --git a/examples/bluetooth/nimble/ble_multi_conn/ble_multi_conn_prph/sdkconfig.defaults b/examples/bluetooth/nimble/ble_multi_conn/ble_multi_conn_prph/sdkconfig.defaults index 6040051b36c..2d10b8c6709 100644 --- a/examples/bluetooth/nimble/ble_multi_conn/ble_multi_conn_prph/sdkconfig.defaults +++ b/examples/bluetooth/nimble/ble_multi_conn/ble_multi_conn_prph/sdkconfig.defaults @@ -1,10 +1,11 @@ -# This file was generated using idf.py save-defconfig. It can be edited manually. -# Espressif IoT Development Framework (ESP-IDF) Project Minimal Configuration -# +# Minimal NimBLE config for multi-connection peripheral (requires BLE 5.0 ext adv). +# Supported targets: ESP32-C5, ESP32-C6, ESP32-C61, ESP32-H2, etc. (see README.md). + CONFIG_BT_ENABLED=y CONFIG_BT_NIMBLE_ENABLED=y -CONFIG_BT_NIMBLE_HCI_EVT_BUF_SIZE=70 +CONFIG_BT_NIMBLE_50_FEATURE_SUPPORT=y CONFIG_BT_NIMBLE_EXT_ADV=y +CONFIG_BT_NIMBLE_TRANSPORT_EVT_SIZE=70 CONFIG_BT_NIMBLE_MAX_CONNECTIONS=69 CONFIG_BT_NIMBLE_MSYS_1_BLOCK_COUNT=100 CONFIG_BT_NIMBLE_LOG_LEVEL_WARNING=y diff --git a/examples/bluetooth/nimble/ble_phy/phy_cent/main/main.c b/examples/bluetooth/nimble/ble_phy/phy_cent/main/main.c index 7181eaedc58..ccb57c124b9 100644 --- a/examples/bluetooth/nimble/ble_phy/phy_cent/main/main.c +++ b/examples/bluetooth/nimble/ble_phy/phy_cent/main/main.c @@ -235,10 +235,6 @@ ext_blecent_should_connect(const struct ble_gap_ext_disc_desc *disc) int offset = 0; int ad_struct_len = 0; uint8_t test_addr[6]; - uint32_t peer_addr[6]; - - memset(peer_addr, 0x0, sizeof peer_addr); - if (disc->legacy_event_type != BLE_HCI_ADV_RPT_EVTYPE_ADV_IND && disc->legacy_event_type != BLE_HCI_ADV_RPT_EVTYPE_DIR_IND) { return 0; @@ -246,13 +242,10 @@ ext_blecent_should_connect(const struct ble_gap_ext_disc_desc *disc) if (strlen(CONFIG_EXAMPLE_PEER_ADDR) && (strncmp(CONFIG_EXAMPLE_PEER_ADDR, "ADDR_ANY", strlen("ADDR_ANY")) != 0)) { ESP_LOGI(tag, "Peer address from menuconfig: %s", CONFIG_EXAMPLE_PEER_ADDR); /* Convert string to address */ - sscanf(CONFIG_EXAMPLE_PEER_ADDR, "%lx:%lx:%lx:%lx:%lx:%lx", - &peer_addr[5], &peer_addr[4], &peer_addr[3], - &peer_addr[2], &peer_addr[1], &peer_addr[0]); - - /* Conversion */ - for (int i=0; i<6; i++) { - test_addr[5 - i] = (uint8_t )peer_addr[i]; + uint8_t parsed_addr[6]; + peer_addr_parse(CONFIG_EXAMPLE_PEER_ADDR, parsed_addr); + for (int i = 0; i < 6; i++) { + test_addr[5 - i] = parsed_addr[i]; } if (memcmp(test_addr, disc->addr.val, sizeof(disc->addr.val)) != 0) { @@ -472,10 +465,6 @@ blecent_on_sync(void) int ii, rc; uint8_t all_phy; uint8_t test_addr[6]; - uint32_t peer_addr[6]; - - memset(peer_addr, 0x0, sizeof peer_addr); - /* Make sure we have proper identity address set (public preferred) */ rc = ble_hs_util_ensure_addr(0); assert(rc == 0); @@ -487,15 +476,7 @@ blecent_on_sync(void) if (strlen(CONFIG_EXAMPLE_PEER_ADDR) && (strncmp(CONFIG_EXAMPLE_PEER_ADDR, "ADDR_ANY", strlen("ADDR_ANY")) != 0)) { /* User wants to connect on 2M or coded phy directly */ - sscanf(CONFIG_EXAMPLE_PEER_ADDR, "%lx:%lx:%lx:%lx:%lx:%lx", - &peer_addr[5], &peer_addr[4], &peer_addr[3], - &peer_addr[2], &peer_addr[1], &peer_addr[0]); - - /* Conversion */ - for (int i=0; i<6; i++) { - test_addr[i] = (uint8_t )peer_addr[i]; - } - + peer_addr_parse(CONFIG_EXAMPLE_PEER_ADDR, test_addr); for(ii = 0 ;ii < 6; ii++) conn_addr.val[ii] = test_addr[ii]; diff --git a/examples/bluetooth/nimble/ble_proximity_sensor/proximity_sensor_cent/main/main.c b/examples/bluetooth/nimble/ble_proximity_sensor/proximity_sensor_cent/main/main.c index 1223715a026..67a6e1ecc53 100644 --- a/examples/bluetooth/nimble/ble_proximity_sensor/proximity_sensor_cent/main/main.c +++ b/examples/bluetooth/nimble/ble_proximity_sensor/proximity_sensor_cent/main/main.c @@ -228,10 +228,6 @@ ext_ble_prox_cent_should_connect(const struct ble_gap_ext_disc_desc *disc) int offset = 0; int ad_struct_len = 0; uint8_t test_addr[6]; - uint32_t peer_addr[6]; - - memset(peer_addr, 0x0, sizeof peer_addr); - if (disc->legacy_event_type != BLE_HCI_ADV_RPT_EVTYPE_ADV_IND && disc->legacy_event_type != BLE_HCI_ADV_RPT_EVTYPE_DIR_IND) { return 0; @@ -239,13 +235,10 @@ ext_ble_prox_cent_should_connect(const struct ble_gap_ext_disc_desc *disc) if (strlen(CONFIG_EXAMPLE_PEER_ADDR) && (strncmp(CONFIG_EXAMPLE_PEER_ADDR, "ADDR_ANY", strlen ("ADDR_ANY")) != 0)) { ESP_LOGI(tag, "Peer address from menuconfig: %s", CONFIG_EXAMPLE_PEER_ADDR); /* Convert string to address */ - sscanf(CONFIG_EXAMPLE_PEER_ADDR, "%lx:%lx:%lx:%lx:%lx:%lx", - &peer_addr[5], &peer_addr[4], &peer_addr[3], - &peer_addr[2], &peer_addr[1], &peer_addr[0]); - - /* Conversion */ - for (int i=0; i<6; i++) { - test_addr[5 - i] = (uint8_t )peer_addr[i]; + uint8_t parsed_addr[6]; + peer_addr_parse(CONFIG_EXAMPLE_PEER_ADDR, parsed_addr); + for (int i = 0; i < 6; i++) { + test_addr[5 - i] = parsed_addr[i]; } if (memcmp(test_addr, disc->addr.val, sizeof(disc->addr.val)) != 0) { @@ -284,10 +277,6 @@ ble_prox_cent_should_connect(const struct ble_gap_disc_desc *disc) int rc; int i; uint8_t test_addr[6]; - uint32_t peer_addr[6]; - - memset(peer_addr, 0x0, sizeof peer_addr); - /* The device has to be advertising connectability. */ if (disc->event_type != BLE_HCI_ADV_RPT_EVTYPE_ADV_IND && disc->event_type != BLE_HCI_ADV_RPT_EVTYPE_DIR_IND) { @@ -303,15 +292,7 @@ ble_prox_cent_should_connect(const struct ble_gap_disc_desc *disc) if (strlen(CONFIG_EXAMPLE_PEER_ADDR) && (strncmp(CONFIG_EXAMPLE_PEER_ADDR, "ADDR_ANY", strlen("ADDR_ANY")) != 0)) { ESP_LOGI(tag, "Peer address from menuconfig: %s", CONFIG_EXAMPLE_PEER_ADDR); /* Convert string to address */ - sscanf(CONFIG_EXAMPLE_PEER_ADDR, "%lx:%lx:%lx:%lx:%lx:%lx", - &peer_addr[5], &peer_addr[4], &peer_addr[3], - &peer_addr[2], &peer_addr[1], &peer_addr[0]); - - /* Conversion */ - for (int i=0; i<6; i++) { - test_addr[i] = (uint8_t )peer_addr[i]; - } - + peer_addr_parse(CONFIG_EXAMPLE_PEER_ADDR, test_addr); if (memcmp(test_addr, disc->addr.val, sizeof(disc->addr.val)) != 0) { return 0; } diff --git a/examples/bluetooth/nimble/blecent/main/main.c b/examples/bluetooth/nimble/blecent/main/main.c index 1f11f9f2bde..3ac1990f51b 100644 --- a/examples/bluetooth/nimble/blecent/main/main.c +++ b/examples/bluetooth/nimble/blecent/main/main.c @@ -31,28 +31,6 @@ #include "host/ble_esp_gattc_cache.h" #endif -#if CONFIG_EXAMPLE_USE_CI_ADDRESS -#ifdef CONFIG_IDF_TARGET_ESP32 -#define TEST_CI_ADDRESS_CHIP_OFFSET (0) -#elif CONFIG_IDF_TARGET_ESP32C2 -#define TEST_CI_ADDRESS_CHIP_OFFSET (1) -#elif CONFIG_IDF_TARGET_ESP32C3 -#define TEST_CI_ADDRESS_CHIP_OFFSET (2) -#elif CONFIG_IDF_TARGET_ESP32C6 -#define TEST_CI_ADDRESS_CHIP_OFFSET (3) -#elif CONFIG_IDF_TARGET_ESP32C5 -#define TEST_CI_ADDRESS_CHIP_OFFSET (4) -#elif CONFIG_IDF_TARGET_ESP32H2 -#define TEST_CI_ADDRESS_CHIP_OFFSET (5) -#elif CONFIG_IDF_TARGET_ESP32P4 -#define TEST_CI_ADDRESS_CHIP_OFFSET (6) -#elif CONFIG_IDF_TARGET_ESP32S3 -#define TEST_CI_ADDRESS_CHIP_OFFSET (7) -#elif CONFIG_IDF_TARGET_ESP32C61 -#define TEST_CI_ADDRESS_CHIP_OFFSET (8) -#endif -#endif - #if MYNEWT_VAL(BLE_GATTC) /*** The UUID of the service containing the subscribable characteristic ***/ static const ble_uuid_t * remote_svc_uuid = @@ -503,10 +481,6 @@ ext_blecent_should_connect(const struct ble_gap_ext_disc_desc *disc) uint32_t *addr_offset; #endif // CONFIG_EXAMPLE_USE_CI_ADDRESS uint8_t test_addr[6]; - uint32_t peer_addr[6]; - - memset(peer_addr, 0x0, sizeof peer_addr); - if (disc->legacy_event_type != BLE_HCI_ADV_RPT_EVTYPE_ADV_IND && disc->legacy_event_type != BLE_HCI_ADV_RPT_EVTYPE_DIR_IND) { return 0; @@ -515,21 +489,14 @@ ext_blecent_should_connect(const struct ble_gap_ext_disc_desc *disc) #if !CONFIG_EXAMPLE_USE_CI_ADDRESS ESP_LOGI(tag, "Peer address from menuconfig: %s", CONFIG_EXAMPLE_PEER_ADDR); /* Convert string to address */ - sscanf(CONFIG_EXAMPLE_PEER_ADDR, "%lx:%lx:%lx:%lx:%lx:%lx", - &peer_addr[5], &peer_addr[4], &peer_addr[3], - &peer_addr[2], &peer_addr[1], &peer_addr[0]); + peer_addr_parse(CONFIG_EXAMPLE_PEER_ADDR, test_addr); #endif - /* Conversion */ - for(int i=0; i<6; i++) { - test_addr[i] = (uint8_t )peer_addr[i]; - } - #if CONFIG_EXAMPLE_USE_CI_ADDRESS addr_offset = (uint32_t *)&test_addr[1]; *addr_offset = atoi(CONFIG_EXAMPLE_PEER_ADDR); test_addr[5] = 0xC3; - test_addr[0] = TEST_CI_ADDRESS_CHIP_OFFSET; + test_addr[0] = CONFIG_IDF_FIRMWARE_CHIP_ID; #endif if (memcmp(test_addr, disc->addr.val, sizeof(disc->addr.val)) != 0) { return 0; @@ -571,10 +538,6 @@ blecent_should_connect(const struct ble_gap_disc_desc *disc) uint32_t *addr_offset; #endif // CONFIG_EXAMPLE_USE_CI_ADDRESS uint8_t test_addr[6]; - uint32_t peer_addr[6]; - - memset(peer_addr, 0x0, sizeof peer_addr); - /* The device has to be advertising connectability. */ if (disc->event_type != BLE_HCI_ADV_RPT_EVTYPE_ADV_IND && disc->event_type != BLE_HCI_ADV_RPT_EVTYPE_DIR_IND) { @@ -591,22 +554,14 @@ blecent_should_connect(const struct ble_gap_disc_desc *disc) ESP_LOGI(tag, "Peer address from menuconfig: %s", CONFIG_EXAMPLE_PEER_ADDR); #if !CONFIG_EXAMPLE_USE_CI_ADDRESS /* Convert string to address */ - sscanf(CONFIG_EXAMPLE_PEER_ADDR, "%lx:%lx:%lx:%lx:%lx:%lx", - &peer_addr[5], &peer_addr[4], &peer_addr[3], - &peer_addr[2], &peer_addr[1], &peer_addr[0]); - printf("peer--> %lx %lx %lx %lx %lx %lx \n", peer_addr[5], peer_addr[4], - peer_addr[3], peer_addr[2], peer_addr[1], peer_addr[0]); + peer_addr_parse(CONFIG_EXAMPLE_PEER_ADDR, test_addr); + printf("peer--> %s\n", addr_str(test_addr)); #endif - /* Conversion */ - for (int i=0; i<6; i++) { - test_addr[i] = (uint8_t )peer_addr[i]; - } - #if CONFIG_EXAMPLE_USE_CI_ADDRESS addr_offset = (uint32_t *)&test_addr[1]; *addr_offset = atoi(CONFIG_EXAMPLE_PEER_ADDR); test_addr[5] = 0xC3; - test_addr[0] = TEST_CI_ADDRESS_CHIP_OFFSET; + test_addr[0] = CONFIG_IDF_FIRMWARE_CHIP_ID; #endif if (memcmp(test_addr, disc->addr.val, sizeof(disc->addr.val)) != 0) { diff --git a/examples/bluetooth/nimble/common/nimble_central_utils/esp_central.h b/examples/bluetooth/nimble/common/nimble_central_utils/esp_central.h index 45fbb6ab0e7..2bca76cd05e 100644 --- a/examples/bluetooth/nimble/common/nimble_central_utils/esp_central.h +++ b/examples/bluetooth/nimble/common/nimble_central_utils/esp_central.h @@ -15,6 +15,7 @@ extern "C" { #define PEER_ADDR_VAL_SIZE 6 /** Misc. */ +int peer_addr_parse(const char *addr_str, uint8_t addr[PEER_ADDR_VAL_SIZE]); void print_bytes(const uint8_t *bytes, int len); void print_mbuf(const struct os_mbuf *om); void print_mbuf_data(const struct os_mbuf *om); diff --git a/examples/bluetooth/nimble/common/nimble_central_utils/misc.c b/examples/bluetooth/nimble/common/nimble_central_utils/misc.c index 1c89f7e3902..bf936e80b2c 100644 --- a/examples/bluetooth/nimble/common/nimble_central_utils/misc.c +++ b/examples/bluetooth/nimble/common/nimble_central_utils/misc.c @@ -4,7 +4,21 @@ * SPDX-License-Identifier: Unlicense OR CC0-1.0 */ +#include +#include #include "host/ble_hs.h" +#include "esp_central.h" + +int +peer_addr_parse(const char *addr_str, uint8_t addr[PEER_ADDR_VAL_SIZE]) +{ + if (addr_str == NULL) { + return 0; + } + return sscanf(addr_str, "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx", + &addr[5], &addr[4], &addr[3], + &addr[2], &addr[1], &addr[0]); +} /** * Utility function to log an array of bytes. diff --git a/examples/bluetooth/nimble/power_save/main/Kconfig.projbuild b/examples/bluetooth/nimble/power_save/main/Kconfig.projbuild index 1ce82a20a12..70dcff8042b 100644 --- a/examples/bluetooth/nimble/power_save/main/Kconfig.projbuild +++ b/examples/bluetooth/nimble/power_save/main/Kconfig.projbuild @@ -1,90 +1,5 @@ menu "Example Configuration" - choice EXAMPLE_MAX_CPU_FREQ - prompt "Maximum CPU frequency" - default EXAMPLE_MAX_CPU_FREQ_160 if !IDF_TARGET_ESP32H2 && !IDF_TARGET_ESP32C2 && !IDF_TARGET_ESP32C5 - default EXAMPLE_MAX_CPU_FREQ_96 if IDF_TARGET_ESP32H2 - default EXAMPLE_MAX_CPU_FREQ_120 if IDF_TARGET_ESP32C2 - default EXAMPLE_MAX_CPU_FREQ_240 if IDF_TARGET_ESP32C5 - depends on PM_ENABLE - help - Maximum CPU frequency to use for dynamic frequency scaling. - - config EXAMPLE_MAX_CPU_FREQ_80 - bool "80 MHz" - config EXAMPLE_MAX_CPU_FREQ_96 - bool "96 MHz" - depends on IDF_TARGET_ESP32H2 - config EXAMPLE_MAX_CPU_FREQ_160 - bool "160 MHz" - config EXAMPLE_MAX_CPU_FREQ_120 - bool "120 MHz" - depends on IDF_TARGET_ESP32C2 - config EXAMPLE_MAX_CPU_FREQ_240 - bool "240 MHz" - depends on IDF_TARGET_ESP32 || IDF_TARGET_ESP32S3 || IDF_TARGET_ESP32C5 - endchoice - - config EXAMPLE_MAX_CPU_FREQ_MHZ - int - default 80 if EXAMPLE_MAX_CPU_FREQ_80 - default 96 if EXAMPLE_MAX_CPU_FREQ_96 - default 120 if EXAMPLE_MAX_CPU_FREQ_120 - default 160 if EXAMPLE_MAX_CPU_FREQ_160 - default 240 if EXAMPLE_MAX_CPU_FREQ_240 - - choice EXAMPLE_MIN_CPU_FREQ - prompt "Minimum CPU frequency" - default EXAMPLE_MIN_CPU_FREQ_40M if !IDF_TARGET_ESP32H2 && !IDF_TARGET_ESP32C2 && !IDF_TARGET_ESP32C5 - default EXAMPLE_MIN_CPU_FREQ_48M if IDF_TARGET_ESP32C5 - default EXAMPLE_MIN_CPU_FREQ_32M if IDF_TARGET_ESP32H2 - default EXAMPLE_MIN_CPU_FREQ_26M if IDF_TARGET_ESP32C2 - depends on PM_ENABLE - help - Minimum CPU frequency to use for dynamic frequency scaling. - Should be set to XTAL frequency or XTAL frequency divided by integer. - - config EXAMPLE_MIN_CPU_FREQ_80M - bool "80 MHz" - depends on !(IDF_TARGET_ESP32 && EXAMPLE_MAX_CPU_FREQ_240) - help - ESP32 does not support switching between 240M and 80M.The root cause - is that when switching between 240M and 80M, we need to disable - BBPLL and then re-enable it with a different frequency.Since the - Bluetooth baseband works from PLL frequency, it will temporarily - lose its 80 MHz clock, while the BBPLL is disabled. - config EXAMPLE_MIN_CPU_FREQ_48M - bool "48 MHz (use with 48MHz XTAL)" - depends on XTAL_FREQ_48 || XTAL_FREQ_AUTO - config EXAMPLE_MIN_CPU_FREQ_40M - bool "40 MHz (use with 40MHz XTAL)" - depends on XTAL_FREQ_40 || XTAL_FREQ_AUTO - config EXAMPLE_MIN_CPU_FREQ_32M - bool "32 MHz (use with 32MHz XTAL)" - depends on IDF_TARGET_ESP32H2 - depends on XTAL_FREQ_32 || XTAL_FREQ_AUTO - config EXAMPLE_MIN_CPU_FREQ_26M - bool "26 MHz (use with 26MHz XTAL)" - depends on IDF_TARGET_ESP32C2 - depends on XTAL_FREQ_26 || XTAL_FREQ_AUTO - config EXAMPLE_MIN_CPU_FREQ_20M - bool "20 MHz (use with 40MHz XTAL)" - depends on XTAL_FREQ_40 || XTAL_FREQ_AUTO - config EXAMPLE_MIN_CPU_FREQ_10M - bool "10 MHz (use with 40MHz XTAL)" - depends on XTAL_FREQ_40 || XTAL_FREQ_AUTO - endchoice - - config EXAMPLE_MIN_CPU_FREQ_MHZ - int - default 80 if EXAMPLE_MIN_CPU_FREQ_80M - default 48 if EXAMPLE_MIN_CPU_FREQ_48M - default 40 if EXAMPLE_MIN_CPU_FREQ_40M - default 32 if EXAMPLE_MIN_CPU_FREQ_32M - default 26 if EXAMPLE_MIN_CPU_FREQ_26M - default 20 if EXAMPLE_MIN_CPU_FREQ_20M - default 10 if EXAMPLE_MIN_CPU_FREQ_10M - choice EXAMPLE_USE_IO_TYPE prompt "I/O Capability" default BLE_SM_IO_CAP_NO_IO diff --git a/examples/bluetooth/nimble/power_save/main/main.c b/examples/bluetooth/nimble/power_save/main/main.c index 4b3ffe1a679..1f85cb4bf83 100644 --- a/examples/bluetooth/nimble/power_save/main/main.c +++ b/examples/bluetooth/nimble/power_save/main/main.c @@ -16,28 +16,7 @@ #include "console/console.h" #include "services/gap/ble_svc_gap.h" #include "bleprph.h" - -#if CONFIG_EXAMPLE_USE_CI_ADDRESS -#ifdef CONFIG_IDF_TARGET_ESP32 -#define TEST_CI_ADDRESS_CHIP_OFFSET (0) -#elif CONFIG_IDF_TARGET_ESP32C2 -#define TEST_CI_ADDRESS_CHIP_OFFSET (1) -#elif CONFIG_IDF_TARGET_ESP32C3 -#define TEST_CI_ADDRESS_CHIP_OFFSET (2) -#elif CONFIG_IDF_TARGET_ESP32C6 -#define TEST_CI_ADDRESS_CHIP_OFFSET (3) -#elif CONFIG_IDF_TARGET_ESP32C5 -#define TEST_CI_ADDRESS_CHIP_OFFSET (4) -#elif CONFIG_IDF_TARGET_ESP32H2 -#define TEST_CI_ADDRESS_CHIP_OFFSET (5) -#elif CONFIG_IDF_TARGET_ESP32P4 -#define TEST_CI_ADDRESS_CHIP_OFFSET (6) -#elif CONFIG_IDF_TARGET_ESP32S3 -#define TEST_CI_ADDRESS_CHIP_OFFSET (7) -#elif CONFIG_IDF_TARGET_ESP32C61 -#define TEST_CI_ADDRESS_CHIP_OFFSET (8) -#endif -#endif +#include "soc/rtc.h" #if CONFIG_EXAMPLE_EXTENDED_ADV static uint8_t ext_adv_pattern_1[] = { @@ -531,7 +510,7 @@ bleprph_on_sync(void) uint32_t *offset = (uint32_t *)&addr[1]; *offset = atoi(CONFIG_EXAMPLE_CI_ADDRESS_OFFSET); addr[5] = 0xC3; - addr[0] = TEST_CI_ADDRESS_CHIP_OFFSET; + addr[0] = CONFIG_IDF_FIRMWARE_CHIP_ID; rc = ble_hs_id_set_rnd(addr); assert(rc == 0); } @@ -595,8 +574,8 @@ app_main(void) // maximum and minimum frequencies are set in sdkconfig, // automatic light sleep is enabled if tickless idle support is enabled. esp_pm_config_t pm_config = { - .max_freq_mhz = CONFIG_EXAMPLE_MAX_CPU_FREQ_MHZ, - .min_freq_mhz = CONFIG_EXAMPLE_MIN_CPU_FREQ_MHZ, + .max_freq_mhz = CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ, + .min_freq_mhz = rtc_clk_xtal_freq_get(), #if CONFIG_FREERTOS_USE_TICKLESS_IDLE .light_sleep_enable = true #endif diff --git a/examples/bluetooth/nimble/power_save/sdkconfig.40m.esp32c61 b/examples/bluetooth/nimble/power_save/sdkconfig.40m.esp32c61 index 2d712debeb7..34fd71dd83b 100644 --- a/examples/bluetooth/nimble/power_save/sdkconfig.40m.esp32c61 +++ b/examples/bluetooth/nimble/power_save/sdkconfig.40m.esp32c61 @@ -18,6 +18,5 @@ CONFIG_ESP_MODEM_CLOCK_ENABLE_CHECKING=y # # Sleep Config # -CONFIG_ESP_SLEEP_POWER_DOWN_FLASH=y CONFIG_ESP_SLEEP_CACHE_SAFE_ASSERTION=y # end of Sleep Config diff --git a/examples/bluetooth/nimble/power_save/sdkconfig.48m.esp32c5 b/examples/bluetooth/nimble/power_save/sdkconfig.48m.esp32c5 index 42fde259685..f3f2dbe10ca 100644 --- a/examples/bluetooth/nimble/power_save/sdkconfig.48m.esp32c5 +++ b/examples/bluetooth/nimble/power_save/sdkconfig.48m.esp32c5 @@ -18,6 +18,5 @@ CONFIG_ESP_MODEM_CLOCK_ENABLE_CHECKING=y # # Sleep Config # -CONFIG_ESP_SLEEP_POWER_DOWN_FLASH=y CONFIG_ESP_SLEEP_CACHE_SAFE_ASSERTION=y # end of Sleep Config diff --git a/examples/bluetooth/nimble/power_save/sdkconfig.defaults.esp32c5 b/examples/bluetooth/nimble/power_save/sdkconfig.defaults.esp32c5 index 59c24a69a86..943c1c203aa 100644 --- a/examples/bluetooth/nimble/power_save/sdkconfig.defaults.esp32c5 +++ b/examples/bluetooth/nimble/power_save/sdkconfig.defaults.esp32c5 @@ -19,7 +19,6 @@ CONFIG_ESP_MODEM_CLOCK_ENABLE_CHECKING=y # # Sleep Config # -CONFIG_ESP_SLEEP_POWER_DOWN_FLASH=y CONFIG_ESP_SLEEP_CACHE_SAFE_ASSERTION=y # end of Sleep Config diff --git a/examples/bluetooth/nimble/power_save/sdkconfig.defaults.esp32c61 b/examples/bluetooth/nimble/power_save/sdkconfig.defaults.esp32c61 index 6e60a0026ca..cd0f68a6d24 100644 --- a/examples/bluetooth/nimble/power_save/sdkconfig.defaults.esp32c61 +++ b/examples/bluetooth/nimble/power_save/sdkconfig.defaults.esp32c61 @@ -19,7 +19,6 @@ CONFIG_ESP_MODEM_CLOCK_ENABLE_CHECKING=y # # Sleep Config # -CONFIG_ESP_SLEEP_POWER_DOWN_FLASH=y CONFIG_ESP_SLEEP_CACHE_SAFE_ASSERTION=y # end of Sleep Config diff --git a/examples/bluetooth/nimble/throughput_app/README.md b/examples/bluetooth/nimble/throughput_app/README.md index f19b438111b..4e209860bde 100644 --- a/examples/bluetooth/nimble/throughput_app/README.md +++ b/examples/bluetooth/nimble/throughput_app/README.md @@ -1,22 +1,38 @@ # Throughput Demo Examples -There are two example folders inside this `throughput_app`: `bleprph_throughput` (peripheral) and `blecent_throughput` (central). These examples demonstrate BLE GATT throughput measurement using NimBLE on ESP32. Two ESP32 boards are needed to run this demo. The `blecent_throughput` example has CLI support to select GATT operation from READ/WRITE/NOTIFY and configure connection parameters at runtime. More details can be found in respective READMEs. +This folder contains BLE throughput measurement examples for NimBLE on ESP32, organized into two sub-folders by protocol: -## Using the Examples +``` +throughput_app/ +├── gatt/ +│ ├── blecent_throughput/ — GATT central (initiator) +│ └── bleprph_throughput/ — GATT peripheral (responder) +└── l2cap_coc/ + ├── l2cap_coc_cent/ — L2CAP CoC central (sender) + └── l2cap_coc_prph/ — L2CAP CoC peripheral (receiver) +``` + +--- + +## gatt/ + +There are two example folders inside `gatt/`: `bleprph_throughput` (peripheral) and `blecent_throughput` (central). These examples demonstrate BLE GATT throughput measurement using NimBLE on ESP32. Two ESP32 boards are needed to run this demo. The `blecent_throughput` example has CLI support to select GATT operation from READ/WRITE/NOTIFY and configure connection parameters at runtime. More details can be found in respective READMEs. + +### Using the Examples Build and flash two ESP32 boards with `bleprph_throughput` and `blecent_throughput` examples. The central automatically scans and connects to the peripheral based on device name (`nimble_prph`). After connection, the user may optionally configure connection parameters (`MTU`, `connection interval`, `latency`, `supervision timeout`, `connection event length`). Then the user specifies the throughput test type (`read`, `write` or `notify`) and test duration in seconds. Below are sample throughput numbers for a 60-second test run (MTU = 512, conn itvl = 7.5ms, DLE = 251 bytes, 1M PHY): |GATT Method | Measurement Time | Application Throughput| -|--- | --- | ---| -|NOTIFY | 60 seconds | ~340 Kbps| -|READ | 60 seconds | ~200 Kbps| -|WRITE | 60 seconds | ~500 Kbps| +|----------- | ---------------- | ----------------------| +|NOTIFY | 60 seconds | ~340 Kbps | +|READ | 60 seconds | ~200 Kbps | +|WRITE | 60 seconds | ~500 Kbps | The notify throughput output is displayed on the `bleprph_throughput` console, while read/write throughput results are shown on the `blecent_throughput` console. -## Throughput Optimization +### Throughput Optimization The following parameters have the most significant impact on throughput: @@ -35,3 +51,29 @@ The following parameters have the most significant impact on throughput: 7. **MSYS Buffer Count**: Both peripheral and central are configured with 50 MSYS blocks (`CONFIG_BT_NIMBLE_MSYS_1_BLOCK_COUNT=50`) to provide sufficient buffer space for high-throughput operations. 8. **PHY**: On BLE 5.0 supported chipsets, 2M PHY can be selected to double the air data rate. Use Extended Advertising mode and specify PHY in the throughput CLI command. + +--- + +## l2cap_coc/ + +There are two example folders inside `l2cap_coc/`: `l2cap_coc_prph` (peripheral/receiver) and `l2cap_coc_cent` (central/sender). These examples demonstrate BLE L2CAP Connection-Oriented Channel (CoC) throughput measurement using NimBLE on ESP32. Two ESP32 boards are needed to run this demo. More details can be found in respective READMEs. + +### How It Works + +L2CAP CoC provides a direct channel between two devices without the ATT/GATT overhead, making it more efficient for bulk data transfer. + +- The peripheral (`l2cap_coc_prph`) advertises with UUID 0x1812 and registers an L2CAP CoC server on PSM 0x1002. On connection it pre-grants receive credits to the central so the central can pipeline multiple SDUs immediately. +- The central (`l2cap_coc_cent`) scans for UUID 0x1812, connects, enables Data Length Extension (DLE), then opens an L2CAP CoC channel and continuously sends SDUs to the peripheral. +- Data flows **central → peripheral**. The central controls PHY selection, cycling through all enabled PHYs (1M, 2M, Coded S2, Coded S8) in sequence and printing a TX throughput summary after each test interval. +- The peripheral tracks RX throughput per PHY, printing a per-PHY summary box each time the central switches PHY, and a live per-second RX rate while data is flowing. + +### Using the Examples + +Build and flash two ESP32 boards with `l2cap_coc_prph` and `l2cap_coc_cent` examples. The central automatically scans and connects — no user input required. The test runs continuously, cycling through enabled PHYs. + +Below are sample throughput numbers (MTU = 2048, DLE = 251 bytes, conn itvl = 7.5ms, ESP32-C6): + +| PHY | Measurement Time | Application Throughput | +|-----|-----------------|------------------------| +| 1M | 8 seconds | ~741 kbps | +| 2M | 8 seconds | ~1310 kbps | diff --git a/examples/bluetooth/nimble/throughput_app/blecent_throughput/sdkconfig.defaults b/examples/bluetooth/nimble/throughput_app/blecent_throughput/sdkconfig.defaults deleted file mode 100644 index 215ee57cb50..00000000000 --- a/examples/bluetooth/nimble/throughput_app/blecent_throughput/sdkconfig.defaults +++ /dev/null @@ -1,18 +0,0 @@ -# Override some defaults so BT stack is enabled -# in this example and some misc buffer sizes are increased - -# -# BT config -# -CONFIG_BT_ENABLED=y -CONFIG_BTDM_CTRL_MODE_BLE_ONLY=y -CONFIG_BTDM_CTRL_MODE_BR_EDR_ONLY=n -CONFIG_BTDM_CTRL_MODE_BTDM=n -CONFIG_BT_BLUEDROID_ENABLED=n -CONFIG_BT_NIMBLE_ENABLED=y -CONFIG_BT_NIMBLE_ATT_PREFERRED_MTU=512 -CONFIG_BT_NIMBLE_TRANSPORT_ACL_FROM_LL_COUNT=20 -CONFIG_BT_NIMBLE_TRANSPORT_EVT_SIZE=255 -CONFIG_BT_NIMBLE_MSYS_1_BLOCK_COUNT=50 -CONFIG_BT_NIMBLE_LOG_LEVEL=4 -CONFIG_BT_NIMBLE_LOG_LEVEL_NONE=y diff --git a/examples/bluetooth/nimble/throughput_app/bleprph_throughput/sdkconfig.defaults b/examples/bluetooth/nimble/throughput_app/bleprph_throughput/sdkconfig.defaults deleted file mode 100644 index d6e31cf3ad2..00000000000 --- a/examples/bluetooth/nimble/throughput_app/bleprph_throughput/sdkconfig.defaults +++ /dev/null @@ -1,18 +0,0 @@ -# Override some defaults so BT stack is enabled -# in this example and some example specific misc sizes are increased. - -# -# BT config -# -CONFIG_BT_ENABLED=y -CONFIG_BTDM_CTRL_MODE_BLE_ONLY=y -CONFIG_BTDM_CTRL_MODE_BR_EDR_ONLY=n -CONFIG_BTDM_CTRL_MODE_BTDM=n -CONFIG_BT_BLUEDROID_ENABLED=n -CONFIG_BT_NIMBLE_ENABLED=y -CONFIG_BT_NIMBLE_ATT_PREFERRED_MTU=512 -CONFIG_BT_NIMBLE_TRANSPORT_ACL_FROM_LL_COUNT=20 -CONFIG_BT_NIMBLE_TRANSPORT_EVT_SIZE=255 -CONFIG_BT_NIMBLE_MSYS_1_BLOCK_COUNT=50 -CONFIG_BT_NIMBLE_LOG_LEVEL=4 -CONFIG_BT_NIMBLE_LOG_LEVEL_NONE=y diff --git a/examples/bluetooth/nimble/throughput_app/blecent_throughput/CMakeLists.txt b/examples/bluetooth/nimble/throughput_app/gatt/blecent_throughput/CMakeLists.txt similarity index 100% rename from examples/bluetooth/nimble/throughput_app/blecent_throughput/CMakeLists.txt rename to examples/bluetooth/nimble/throughput_app/gatt/blecent_throughput/CMakeLists.txt diff --git a/examples/bluetooth/nimble/throughput_app/blecent_throughput/README.md b/examples/bluetooth/nimble/throughput_app/gatt/blecent_throughput/README.md similarity index 100% rename from examples/bluetooth/nimble/throughput_app/blecent_throughput/README.md rename to examples/bluetooth/nimble/throughput_app/gatt/blecent_throughput/README.md diff --git a/examples/bluetooth/nimble/throughput_app/blecent_throughput/components/cmd_system/CMakeLists.txt b/examples/bluetooth/nimble/throughput_app/gatt/blecent_throughput/components/cmd_system/CMakeLists.txt similarity index 100% rename from examples/bluetooth/nimble/throughput_app/blecent_throughput/components/cmd_system/CMakeLists.txt rename to examples/bluetooth/nimble/throughput_app/gatt/blecent_throughput/components/cmd_system/CMakeLists.txt diff --git a/examples/bluetooth/nimble/throughput_app/blecent_throughput/components/cmd_system/cmd_system.c b/examples/bluetooth/nimble/throughput_app/gatt/blecent_throughput/components/cmd_system/cmd_system.c similarity index 99% rename from examples/bluetooth/nimble/throughput_app/blecent_throughput/components/cmd_system/cmd_system.c rename to examples/bluetooth/nimble/throughput_app/gatt/blecent_throughput/components/cmd_system/cmd_system.c index 60a4735a443..ef6ccfda967 100644 --- a/examples/bluetooth/nimble/throughput_app/blecent_throughput/components/cmd_system/cmd_system.c +++ b/examples/bluetooth/nimble/throughput_app/gatt/blecent_throughput/components/cmd_system/cmd_system.c @@ -4,6 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ +#include #include #include #include @@ -329,7 +330,7 @@ static int light_sleep(int argc, char **argv) uint32_t causes = esp_sleep_get_wakeup_causes(); if (causes & BIT(ESP_SLEEP_WAKEUP_UNDEFINED)) { ESP_LOGI(TAG, "Woke up from: unknown"); - printf("%lx\n", causes); + printf("%" PRIx32 "\n", causes); return 0; } if (causes & BIT(ESP_SLEEP_WAKEUP_GPIO)) { diff --git a/examples/bluetooth/nimble/throughput_app/blecent_throughput/components/cmd_system/cmd_system.h b/examples/bluetooth/nimble/throughput_app/gatt/blecent_throughput/components/cmd_system/cmd_system.h similarity index 100% rename from examples/bluetooth/nimble/throughput_app/blecent_throughput/components/cmd_system/cmd_system.h rename to examples/bluetooth/nimble/throughput_app/gatt/blecent_throughput/components/cmd_system/cmd_system.h diff --git a/examples/bluetooth/nimble/throughput_app/blecent_throughput/components/cmd_system/component.mk b/examples/bluetooth/nimble/throughput_app/gatt/blecent_throughput/components/cmd_system/component.mk similarity index 100% rename from examples/bluetooth/nimble/throughput_app/blecent_throughput/components/cmd_system/component.mk rename to examples/bluetooth/nimble/throughput_app/gatt/blecent_throughput/components/cmd_system/component.mk diff --git a/examples/bluetooth/nimble/throughput_app/blecent_throughput/main/CMakeLists.txt b/examples/bluetooth/nimble/throughput_app/gatt/blecent_throughput/main/CMakeLists.txt similarity index 100% rename from examples/bluetooth/nimble/throughput_app/blecent_throughput/main/CMakeLists.txt rename to examples/bluetooth/nimble/throughput_app/gatt/blecent_throughput/main/CMakeLists.txt diff --git a/examples/bluetooth/nimble/throughput_app/blecent_throughput/main/Kconfig.projbuild b/examples/bluetooth/nimble/throughput_app/gatt/blecent_throughput/main/Kconfig.projbuild similarity index 100% rename from examples/bluetooth/nimble/throughput_app/blecent_throughput/main/Kconfig.projbuild rename to examples/bluetooth/nimble/throughput_app/gatt/blecent_throughput/main/Kconfig.projbuild diff --git a/examples/bluetooth/nimble/throughput_app/blecent_throughput/main/gattc.h b/examples/bluetooth/nimble/throughput_app/gatt/blecent_throughput/main/gattc.h similarity index 96% rename from examples/bluetooth/nimble/throughput_app/blecent_throughput/main/gattc.h rename to examples/bluetooth/nimble/throughput_app/gatt/blecent_throughput/main/gattc.h index 6762d3d42c8..b90f4a998cb 100644 --- a/examples/bluetooth/nimble/throughput_app/blecent_throughput/main/gattc.h +++ b/examples/bluetooth/nimble/throughput_app/gatt/blecent_throughput/main/gattc.h @@ -27,7 +27,10 @@ union ble_store_key; #define BLECENT_CHR_UNR_ALERT_STAT_UUID 0x2A45 #define BLECENT_CHR_ALERT_NOT_CTRL_PT 0x2A44 +#define PEER_ADDR_VAL_SIZE 6 + /** Misc. */ +int peer_addr_parse(const char *addr_str, uint8_t addr[PEER_ADDR_VAL_SIZE]); void print_bytes(const uint8_t *bytes, int len); void print_mbuf(const struct os_mbuf *om); char *addr_str(const void *addr); diff --git a/examples/bluetooth/nimble/throughput_app/blecent_throughput/main/main.c b/examples/bluetooth/nimble/throughput_app/gatt/blecent_throughput/main/main.c similarity index 77% rename from examples/bluetooth/nimble/throughput_app/blecent_throughput/main/main.c rename to examples/bluetooth/nimble/throughput_app/gatt/blecent_throughput/main/main.c index 320c7e5f7d4..43a4ff8cb70 100644 --- a/examples/bluetooth/nimble/throughput_app/blecent_throughput/main/main.c +++ b/examples/bluetooth/nimble/throughput_app/gatt/blecent_throughput/main/main.c @@ -49,13 +49,13 @@ #define WRITE_THROUGHPUT 2 #define NOTIFY_THROUGHPUT 3 -#define READ_THROUGHPUT_PAYLOAD 510 /* MTU(512) - ATT read rsp header(1) - 1 (avoid Read Blob) */ -#define WRITE_THROUGHPUT_PAYLOAD 509 /* MTU(512) - ATT write cmd header(3) */ +#define READ_THROUGHPUT_PAYLOAD 497 /* 502 bytes ACL -> 2x 251 LL packets exactly (497 + 1 Read Rsp + 4 L2CAP) */ +#define WRITE_THROUGHPUT_PAYLOAD 495 /* 502 bytes ACL -> 2x 251 LL packets exactly (495 + 3 Write Cmd + 4 L2CAP) */ #define LL_PACKET_TIME 2120 #define LL_PACKET_LENGTH 251 static const char *tag = "blecent_throughput"; static int blecent_gap_event(struct ble_gap_event *event, void *arg); -static SemaphoreHandle_t xSemaphore; + static int mbuf_len_total; static int failure_count; static TaskHandle_t throughput_task_handle = NULL; @@ -66,28 +66,102 @@ static int mtu_def = 512; static ble_addr_t conn_addr; static uint16_t handle; #define PHY_1M 0 -#if CONFIG_EXAMPLE_EXTENDED_ADV -static int current_phy_updated; #define PHY_2M 1 #define PHY_CODED_S2 2 #define PHY_CODED_S8 3 + +#if CONFIG_EXAMPLE_EXTENDED_ADV +static int current_phy_updated; #endif +/* State for callback-chained read throughput test */ +static volatile bool read_test_active = false; +static uint16_t read_val_handle_g; +static uint16_t read_conn_handle_g; +static int read_count_g; + +/* ============================================================================== + * Connection Parameter Tuning + * ============================================================================== + * itvl_min / itvl_max: Connection interval (time between connection events). + * Units are in 1.25ms. (e.g., 24 * 1.25ms = 30ms). + * latency: Slave latency (number of events the peripheral can skip). + * supervision_timeout: Time before link is considered lost. Units: 10ms. + * min_ce_len / max_ce_len: Connection Event length. max_ce_len = 0xFFFF tells + * the controller to use the ENTIRE connection interval + * for transmitting data, instead of cutting it short. + * ============================================================================== + * + * Write/Notify-optimized for 1M/2M: 30-50ms interval, full CE length. */ static struct ble_gap_upd_params conn_params = { - /** Minimum value for connection interval in 1.25ms units */ .itvl_min = CONFIG_EXAMPLE_CONN_ITVL_MIN, - /** Maximum value for connection interval in 1.25ms units */ .itvl_max = CONFIG_EXAMPLE_CONN_ITVL_MAX, - /** Connection latency */ .latency = CONFIG_EXAMPLE_CONN_LATENCY, - /** Supervision timeout in 10ms units */ .supervision_timeout = CONFIG_EXAMPLE_CONN_TIMEOUT, - /** Minimum length of connection event in 0.625ms units */ - .min_ce_len = CONFIG_EXAMPLE_CONN_CE_LEN_MIN, - /** Maximum length of connection event in 0.625ms units */ - .max_ce_len = CONFIG_EXAMPLE_CONN_CE_LEN_MAX, + .min_ce_len = 0, + .max_ce_len = 0xFFFF, }; +/* Write/Notify-optimized for Coded S2 */ +static struct ble_gap_upd_params conn_params_coded_s2 = { + .itvl_min = 24, /* 30 ms */ + .itvl_max = 32, /* 40 ms */ + .latency = 0, + .supervision_timeout = CONFIG_EXAMPLE_CONN_TIMEOUT, + .min_ce_len = 0, + .max_ce_len = 0xFFFF, +}; + +/* Write/Notify-optimized for Coded S8 */ +static struct ble_gap_upd_params conn_params_coded_s8 = { + .itvl_min = 60, /* 75 ms */ + .itvl_max = 80, /* 100 ms */ + .latency = 0, + .supervision_timeout = CONFIG_EXAMPLE_CONN_TIMEOUT, + .min_ce_len = 0, + .max_ce_len = 0xFFFF, +}; + +/* Read-optimized for 1M/2M: short interval for fast round-trips */ +static struct ble_gap_upd_params conn_params_read = { + .itvl_min = 6, /* 7.5 ms */ + .itvl_max = 8, /* 10 ms */ + .latency = 0, + .supervision_timeout = CONFIG_EXAMPLE_CONN_TIMEOUT, + .min_ce_len = 0, + .max_ce_len = 0xFFFF, +}; + +/* Read-optimized for Coded S2 */ +static struct ble_gap_upd_params conn_params_coded_s2_read = { + .itvl_min = 12, /* 15 ms */ + .itvl_max = 16, /* 20 ms */ + .latency = 0, + .supervision_timeout = CONFIG_EXAMPLE_CONN_TIMEOUT, + .min_ce_len = 0, + .max_ce_len = 0xFFFF, +}; + +/* Read-optimized for Coded S8 */ +static struct ble_gap_upd_params conn_params_coded_s8_read = { + .itvl_min = 36, /* 45 ms */ + .itvl_max = 40, /* 50 ms */ + .latency = 0, + .supervision_timeout = CONFIG_EXAMPLE_CONN_TIMEOUT, + .min_ce_len = 0, + .max_ce_len = 0xFFFF, +}; + +static void switch_conn_params(uint16_t conn_handle, const struct ble_gap_upd_params *params) { + int rc = ble_gap_update_params(conn_handle, params); + if (rc != 0) { + ESP_LOGE(tag, "Failed to update connection parameters: %d", rc); + } else { + ESP_LOGI(tag, "Requested connection parameter update"); + vTaskDelay(500 / portTICK_PERIOD_MS); + } +} + void ble_store_config_init(void); #if CONFIG_EXAMPLE_EXTENDED_ADV @@ -137,22 +211,20 @@ static int blecent_write(uint16_t conn_handle, uint16_t val_handle, start_time = esp_timer_get_time(); while (write_time < test_time * 1000) { - /* Wait till the previous write is complete. For first time Semaphore - * is already available */ - label: - rc = ble_gattc_write_no_rsp_flat(conn_handle, val_handle, &value, sizeof value); + rc = ble_gattc_write_no_rsp_flat(conn_handle, val_handle, &value, sizeof value); - if(rc == BLE_HS_ENOMEM) { - vTaskDelay(2); /* Wait for buffers to free up and try again */ - goto label; - } - else if (rc != 0) { - ESP_LOGE(tag, "Error: Failed to write characteristic; rc=%d",rc); - goto err; + if (rc == BLE_HS_ENOMEM) { + vTaskDelay(2); /* Wait for buffers to free up and try again */ + end_time = esp_timer_get_time(); + write_time = (end_time - start_time) / 1000; + continue; + } else if (rc != 0) { + ESP_LOGE(tag, "Error: Failed to write characteristic; rc=%d", rc); + goto err; } end_time = esp_timer_get_time(); - write_time = (end_time - start_time) / 1000 ; + write_time = (end_time - start_time) / 1000; write_count += 1; } @@ -171,6 +243,11 @@ err: return ble_gap_terminate(peer->conn_handle, BLE_ERR_REM_USER_CONN_TERM); } +/** + * Read callback that chains the next read immediately from within the + * NimBLE host context. This eliminates the FreeRTOS task-switch delay + * that previously caused each read to waste an extra connection event. + */ static int blecent_repeat_read(uint16_t conn_handle, const struct ble_gatt_error *error, @@ -178,55 +255,96 @@ blecent_repeat_read(uint16_t conn_handle, void *arg) { if (error->status == 0) { - xSemaphoreGive(xSemaphore); ESP_LOGD(tag, " attr_handle=%d value=", attr->handle); mbuf_len_total += OS_MBUF_PKTLEN(attr->om); + read_count_g++; } else { - ESP_LOGE(tag, " Read failed, callback error code = %d", error->status ); - xSemaphoreGive(xSemaphore); + ESP_LOGE(tag, " Read failed, callback error code = %d", error->status); + /* On error, stop chaining and wake up main task */ + read_test_active = false; + if (throughput_task_handle) { + xTaskNotifyGive(throughput_task_handle); + } + return 0; } - return error->status; + + /* Chain next read immediately (zero task-switch overhead) */ + if (read_test_active) { + int rc = ble_gattc_read(read_conn_handle_g, read_val_handle_g, + blecent_repeat_read, arg); + if (rc != 0) { + ESP_LOGE(tag, "Failed to chain read; rc=%d", rc); + read_test_active = false; + if (throughput_task_handle) { + xTaskNotifyGive(throughput_task_handle); + } + } + } else { + /* Test time expired — wake up main task to print results */ + if (throughput_task_handle) { + xTaskNotifyGive(throughput_task_handle); + } + } + + return 0; } static int blecent_read(uint16_t conn_handle, uint16_t val_handle, - ble_gatt_attr_fn *cb, struct peer *peer, int test_time) + struct peer *peer, int test_time) { - int rc, read_count = 0; - int64_t start_time, end_time, read_time = 0; - /* Keep track of number of bytes read from char */ + int rc; + int64_t start_time; + + /* Reset counters */ mbuf_len_total = 0; + read_count_g = 0; + read_conn_handle_g = conn_handle; + read_val_handle_g = val_handle; + read_test_active = true; + + /* Drain any stale notifications from a prior test that timed out */ + ulTaskNotifyTake(pdTRUE, 0); + start_time = esp_timer_get_time(); ESP_LOGD(tag, " Throughput read started :val_handle=%d test_time=%d", val_handle, test_time); - while (read_time < (test_time * 1000)) { - /* Wait till the previous read is complete. For first time use Semaphore - * is already available */ - xSemaphoreTake(xSemaphore, portMAX_DELAY); - - rc = ble_gattc_read(peer->conn_handle, val_handle, - blecent_repeat_read, (void *) &peer); - if (rc != 0) { - ESP_LOGE(tag, "Error: Failed to read characteristic; rc=%d", - rc); - goto err; - } - - end_time = esp_timer_get_time(); - read_time = (end_time - start_time) / 1000 ; - read_count += 1; + /* Kick off the first read — the callback will chain all subsequent reads */ + rc = ble_gattc_read(conn_handle, val_handle, + blecent_repeat_read, (void *) peer); + if (rc != 0) { + ESP_LOGE(tag, "Error: Failed to start read chain; rc=%d", rc); + read_test_active = false; + goto err; } - /* Application data throughput */ + /* Sleep for the test duration while the callback chain runs autonomously. + * If the chain encounters an error (e.g. disconnect), it will notify us early. */ + uint32_t notified = ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(test_time * 1000)); + + if (notified == 0) { + /* Normal timeout, signal the chain to stop after the current in-flight read completes */ + read_test_active = false; + /* Wait for the last callback to notify us (up to 5s safety timeout) */ + ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(5000)); + } else { + /* Woken early by an error in the callback chain */ + read_test_active = false; + } + + int64_t end_time = esp_timer_get_time(); + int actual_secs = (int)((end_time - start_time) / 1000000); + if (actual_secs == 0) actual_secs = test_time; + + /* Application data throughput */ printf("\n****************************************************************\n"); ESP_LOGI(tag, "Application Read throughput = %d bps, Read op counter = %d", - (mbuf_len_total * 8) / (test_time), read_count); + (mbuf_len_total * 8) / actual_secs, read_count_g); printf("\n****************************************************************\n"); return 0; err: - xSemaphoreGive(xSemaphore); /* Terminate the connection. */ vTaskDelay(100 / portTICK_PERIOD_MS); return ble_gap_terminate(peer->conn_handle, BLE_ERR_REM_USER_CONN_TERM); @@ -334,7 +452,7 @@ static void throughput_task(void *arg) printf(" | Phy mode: Enter value in 0 for 1M, 1 for 2M ,2 for Coded S2, |\n"); printf(" | 3 for Coded S8. |\n"); printf(" | |\n"); - printf(" | e.g. throughput read 600 3 |\n"); + printf(" | e.g. throughput read 60 3 |\n"); printf(" | |\n"); printf(" | ** Enter 'throughput read 60 0' for reading char for 60 seconds on 1M Phy |\n"); printf(" | OR 'throughput write 60 1' for writing to char for 60 seconds on 2M Phy |\n"); @@ -365,12 +483,46 @@ static void throughput_task(void *arg) vTaskDelay(100 / portTICK_PERIOD_MS); } } + + /* Send a 1-byte control message to the peripheral's read/write char + * to tell it which PHY coding we are testing. This ensures the peripheral + * perfectly mirrors our PHY preference (1M, 2M, S2, or S8). */ + if (test_data[2] >= 0 && test_data[2] <= 3) { + const struct peer_chr *cmd_chr = peer_chr_find_uuid(peer, + THRPT_UUID_DECLARE(THRPT_SVC), + THRPT_UUID_DECLARE(THRPT_CHR_READ_WRITE)); + if (cmd_chr != NULL) { + uint8_t phy_cmd = test_data[2]; + rc = ble_gattc_write_no_rsp_flat(conn_handle, cmd_chr->chr.val_handle, &phy_cmd, 1); + if (rc != 0) { + ESP_LOGW(tag, "Failed to send PHY cmd to peripheral; rc=%d", rc); + } else { + vTaskDelay(200 / portTICK_PERIOD_MS); /* Give peripheral time to apply PHY */ + } + } + } + } +#else + /* Extended advertising is disabled, only 1M PHY is available */ + if (test_data[2] != PHY_1M) { + ESP_LOGW(tag, "Extended advertising disabled; forcing PHY to 1M (ignoring user selection %d)", test_data[2]); + test_data[2] = PHY_1M; } #endif switch (test_data[0]) { case READ_THROUGHPUT: + /* PHY-aware read connection params: short interval for fast + * round-trips on 1M/2M, longer for coded PHY */ + if (test_data[2] == PHY_CODED_S2) { + switch_conn_params(conn_handle, &conn_params_coded_s2_read); + } else if (test_data[2] == PHY_CODED_S8) { + switch_conn_params(conn_handle, &conn_params_coded_s8_read); + } else { + switch_conn_params(conn_handle, &conn_params_read); + } + /* Read the characteristic supporting long read support * `THRPT_LONG_CHR_READ_WRITE` (0x000b) */ chr = peer_chr_find_uuid(peer, @@ -379,12 +531,12 @@ static void throughput_task(void *arg) if (chr == NULL) { ESP_LOGE(tag, "Peer does not support " "LONG_READ (0x000b) characteristic "); - break; + goto read_cleanup; } if (test_data[1] > 0) { rc = blecent_read(conn_handle, chr->chr.val_handle, - blecent_repeat_read, (void *) peer, test_data[1]); + (void *) peer, test_data[1]); if (rc != 0) { ESP_LOGE(tag, "Error while reading from GATTS; rc = %d", rc); /* Delete task on critical error (connection lost or fatal error) */ @@ -398,9 +550,28 @@ static void throughput_task(void *arg) } else { ESP_LOGE(tag, "Please enter non-zero value for test time in seconds!!"); } + +read_cleanup: + /* Restore write-optimized params after the read test */ + ESP_LOGI(tag, "Restoring WRITE-optimized conn params"); + if (test_data[2] == PHY_CODED_S2) { + switch_conn_params(conn_handle, &conn_params_coded_s2); + } else if (test_data[2] == PHY_CODED_S8) { + switch_conn_params(conn_handle, &conn_params_coded_s8); + } else { + switch_conn_params(conn_handle, &conn_params); + } break; case WRITE_THROUGHPUT: + if (test_data[2] == PHY_CODED_S2) { + switch_conn_params(conn_handle, &conn_params_coded_s2); + } else if (test_data[2] == PHY_CODED_S8) { + switch_conn_params(conn_handle, &conn_params_coded_s8); + } else { + switch_conn_params(conn_handle, &conn_params); + } + chr = peer_chr_find_uuid(peer, THRPT_UUID_DECLARE(THRPT_SVC), THRPT_UUID_DECLARE(THRPT_CHR_READ_WRITE)); @@ -428,6 +599,14 @@ static void throughput_task(void *arg) break; case NOTIFY_THROUGHPUT: + if (test_data[2] == PHY_CODED_S2) { + switch_conn_params(conn_handle, &conn_params_coded_s2); + } else if (test_data[2] == PHY_CODED_S8) { + switch_conn_params(conn_handle, &conn_params_coded_s8); + } else { + switch_conn_params(conn_handle, &conn_params); + } + chr = peer_chr_find_uuid(peer, THRPT_UUID_DECLARE(THRPT_SVC), THRPT_UUID_DECLARE(THRPT_CHR_NOTIFY)); @@ -463,6 +642,20 @@ static void throughput_task(void *arg) test_data[1]); } vTaskDelay(test_data[1]*1000 / portTICK_PERIOD_MS); + + /* Unsubscribe so the next notify test triggers a fresh + * BLE_GAP_EVENT_SUBSCRIBE on the peripheral (cur_notify 0→1) */ + { + uint8_t unsub_value[2] = {0, 0}; + rc = ble_gattc_write_flat(conn_handle, dsc->dsc.handle, + unsub_value, sizeof unsub_value, + NULL, NULL); + if (rc != 0) { + ESP_LOGW(tag, "Unsubscribe failed; rc=%d (non-fatal)", rc); + } else { + ESP_LOGI(tag, "Unsubscribed from notifications"); + } + } break; default: @@ -570,7 +763,7 @@ ext_blecent_should_connect(const struct ble_gap_ext_disc_desc *disc) int offset = 0; int ad_struct_len = 0; uint8_t test_addr[6]; - uint32_t peer_addr[6]; + uint8_t parsed_addr[6]; uint8_t phy_uuid_found = 0; if (disc->legacy_event_type != BLE_HCI_ADV_RPT_EVTYPE_ADV_IND && @@ -580,13 +773,9 @@ ext_blecent_should_connect(const struct ble_gap_ext_disc_desc *disc) if (strlen(CONFIG_EXAMPLE_PEER_ADDR) && (strncmp(CONFIG_EXAMPLE_PEER_ADDR, "ADDR_ANY", strlen("ADDR_ANY")) != 0)) { // ESP_LOGI(tag, "Peer address from menuconfig: %s", CONFIG_EXAMPLE_PEER_ADDR); /* Convert string to address */ - sscanf(CONFIG_EXAMPLE_PEER_ADDR, "%lx:%lx:%lx:%lx:%lx:%lx", - &peer_addr[5], &peer_addr[4], &peer_addr[3], - &peer_addr[2], &peer_addr[1], &peer_addr[0]); - - /* Conversion */ - for (int i=0; i<6; i++) { - test_addr[5 - i] = (uint8_t )peer_addr[i]; + peer_addr_parse(CONFIG_EXAMPLE_PEER_ADDR, parsed_addr); + for (int i = 0; i < 6; i++) { + test_addr[5 - i] = parsed_addr[i]; } if (memcmp(test_addr, disc->addr.val, sizeof(disc->addr.val)) != 0) { return 0; @@ -646,10 +835,6 @@ blecent_should_connect(const struct ble_gap_disc_desc *disc) int rc; int i; uint8_t test_addr[6]; - uint32_t peer_addr[6]; - - memset(peer_addr, 0x0, sizeof peer_addr); - rc = ble_hs_adv_parse_fields(&fields, disc->data, disc->length_data); if (rc != 0) { return 0; @@ -658,21 +843,13 @@ blecent_should_connect(const struct ble_gap_disc_desc *disc) if (strlen(CONFIG_EXAMPLE_PEER_ADDR) && (strncmp(CONFIG_EXAMPLE_PEER_ADDR, "ADDR_ANY", strlen("ADDR_ANY")) != 0)) { ESP_LOGI(tag, "Peer address from menuconfig: %s", CONFIG_EXAMPLE_PEER_ADDR); /* Convert string to address */ - sscanf(CONFIG_EXAMPLE_PEER_ADDR, "%lx:%lx:%lx:%lx:%lx:%lx", - &peer_addr[5], &peer_addr[4], &peer_addr[3], - &peer_addr[2], &peer_addr[1], &peer_addr[0]); - - /* Conversion */ - for (int i=0; i<6; i++) { - test_addr[i] = (uint8_t )peer_addr[i]; - } - + peer_addr_parse(CONFIG_EXAMPLE_PEER_ADDR, test_addr); if (memcmp(test_addr, disc->addr.val, sizeof(disc->addr.val)) != 0) { return 0; } } - ESP_LOGI(tag, "connect; fields.num_uuids128 =%d", fields.num_uuids128); + ESP_LOGD(tag, "connect; fields.num_uuids128 =%d", fields.num_uuids128); for (i = 0; i < fields.num_uuids128; i++) { if ((memcmp(&fields.uuids128[i], THRPT_UUID_DECLARE(THRPT_SVC), sizeof(ble_uuid128_t))) == 0 ) { @@ -775,7 +952,7 @@ blecent_gap_event(struct ble_gap_event *event, void *arg) switch (event->type) { case BLE_GAP_EVENT_DISC: - ESP_LOGI(tag, "Event DISC "); + ESP_LOGD(tag, "Event DISC "); rc = ble_hs_adv_parse_fields(&fields, event->disc.data, event->disc.length_data); if (rc != 0) { @@ -896,6 +1073,11 @@ blecent_gap_event(struct ble_gap_event *event, void *arg) /* Attribute data is contained in event->notify_rx.attr_data. */ return 0; + case BLE_GAP_EVENT_CONN_UPDATE: + ESP_LOGI(tag, "connection updated; status=%d", + event->conn_update.status); + return 0; + case BLE_GAP_EVENT_MTU: ESP_LOGI(tag, "mtu update event; conn_handle = %d cid = %d mtu = %d", event->mtu.conn_handle, @@ -906,7 +1088,6 @@ blecent_gap_event(struct ble_gap_event *event, void *arg) #if CONFIG_EXAMPLE_EXTENDED_ADV case BLE_GAP_EVENT_EXT_DISC: /* An advertisement report was received during GAP discovery. */ - blecent_connect_if_interesting(&event->ext_disc); return 0; @@ -993,12 +1174,9 @@ blecent_on_sync(void) void blecent_host_task(void *param) { ESP_LOGI(tag, "BLE Host Task Started"); - xSemaphore = xSemaphoreCreateBinary(); - xSemaphoreGive(xSemaphore); /* This function will return only when nimble_port_stop() is executed */ nimble_port_run(); - vSemaphoreDelete(xSemaphore); nimble_port_freertos_deinit(); } diff --git a/examples/bluetooth/nimble/throughput_app/blecent_throughput/main/misc.c b/examples/bluetooth/nimble/throughput_app/gatt/blecent_throughput/main/misc.c similarity index 94% rename from examples/bluetooth/nimble/throughput_app/blecent_throughput/main/misc.c rename to examples/bluetooth/nimble/throughput_app/gatt/blecent_throughput/main/misc.c index 4492ba84df7..1b8ba43a4b6 100644 --- a/examples/bluetooth/nimble/throughput_app/blecent_throughput/main/misc.c +++ b/examples/bluetooth/nimble/throughput_app/gatt/blecent_throughput/main/misc.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -11,6 +11,17 @@ #include "host/ble_uuid.h" #include "gattc.h" +int +peer_addr_parse(const char *addr_str, uint8_t addr[PEER_ADDR_VAL_SIZE]) +{ + if (addr_str == NULL) { + return 0; + } + return sscanf(addr_str, "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx", + &addr[5], &addr[4], &addr[3], + &addr[2], &addr[1], &addr[0]); +} + /** * Utility function to log an array of bytes. */ diff --git a/examples/bluetooth/nimble/throughput_app/blecent_throughput/main/peer.c b/examples/bluetooth/nimble/throughput_app/gatt/blecent_throughput/main/peer.c similarity index 100% rename from examples/bluetooth/nimble/throughput_app/blecent_throughput/main/peer.c rename to examples/bluetooth/nimble/throughput_app/gatt/blecent_throughput/main/peer.c diff --git a/examples/bluetooth/nimble/throughput_app/blecent_throughput/main/scli.c b/examples/bluetooth/nimble/throughput_app/gatt/blecent_throughput/main/scli.c similarity index 100% rename from examples/bluetooth/nimble/throughput_app/blecent_throughput/main/scli.c rename to examples/bluetooth/nimble/throughput_app/gatt/blecent_throughput/main/scli.c diff --git a/examples/bluetooth/nimble/throughput_app/gatt/blecent_throughput/sdkconfig.defaults b/examples/bluetooth/nimble/throughput_app/gatt/blecent_throughput/sdkconfig.defaults new file mode 100644 index 00000000000..e65b4a19cb7 --- /dev/null +++ b/examples/bluetooth/nimble/throughput_app/gatt/blecent_throughput/sdkconfig.defaults @@ -0,0 +1,36 @@ +# Override some defaults so BT stack is enabled +# in this example and some misc buffer sizes are increased + +# +# BT config (universal across all ESP32 variants) +# +CONFIG_BT_ENABLED=y +CONFIG_BT_BLUEDROID_ENABLED=n +CONFIG_BT_NIMBLE_ENABLED=y + +# === BT 5.0 / 2M PHY support === +# Ignored automatically on classic ESP32 which lacks BT 5.0 +CONFIG_BT_NIMBLE_EXT_ADV=n +CONFIG_BT_NIMBLE_50_FEATURE_SUPPORT=y +CONFIG_EXAMPLE_EXTENDED_ADV=n + +# === ATT / GATT === +CONFIG_BT_NIMBLE_ATT_PREFERRED_MTU=512 + +# === Memory Pool Tuning === +CONFIG_BT_NIMBLE_MAX_CONNECTIONS=1 +CONFIG_BT_NIMBLE_MSYS_1_BLOCK_COUNT=50 +CONFIG_BT_NIMBLE_MSYS_2_BLOCK_SIZE=592 +CONFIG_BT_NIMBLE_MSYS_2_BLOCK_COUNT=40 + +# Increase controller-to-host buffers +CONFIG_BT_NIMBLE_TRANSPORT_ACL_FROM_LL_COUNT=40 +CONFIG_BT_NIMBLE_TRANSPORT_EVT_SIZE=255 + +# === Connection Parameters (write/notify optimal; code switches dynamically for reads) === +CONFIG_EXAMPLE_CONN_ITVL_MIN=24 +CONFIG_EXAMPLE_CONN_ITVL_MAX=40 + +# Disable NimBLE INFO logging for throughput tests +CONFIG_BT_NIMBLE_LOG_LEVEL_WARNING=y +CONFIG_BT_NIMBLE_LOG_LEVEL=2 diff --git a/examples/bluetooth/nimble/throughput_app/gatt/blecent_throughput/sdkconfig.defaults.esp32c6 b/examples/bluetooth/nimble/throughput_app/gatt/blecent_throughput/sdkconfig.defaults.esp32c6 new file mode 100644 index 00000000000..a12d9353d92 --- /dev/null +++ b/examples/bluetooth/nimble/throughput_app/gatt/blecent_throughput/sdkconfig.defaults.esp32c6 @@ -0,0 +1,5 @@ +# === High-Performance Tuning === +# This config is only applied when building for the ESP32-C6 target +# It is necessary for the C6 to reach 750+ kbps and 1.3+ Mbps throughput. +CONFIG_COMPILER_OPTIMIZATION_PERF=y +CONFIG_FREERTOS_HZ=1000 diff --git a/examples/bluetooth/nimble/throughput_app/bleprph_throughput/CMakeLists.txt b/examples/bluetooth/nimble/throughput_app/gatt/bleprph_throughput/CMakeLists.txt similarity index 100% rename from examples/bluetooth/nimble/throughput_app/bleprph_throughput/CMakeLists.txt rename to examples/bluetooth/nimble/throughput_app/gatt/bleprph_throughput/CMakeLists.txt diff --git a/examples/bluetooth/nimble/throughput_app/bleprph_throughput/README.md b/examples/bluetooth/nimble/throughput_app/gatt/bleprph_throughput/README.md similarity index 100% rename from examples/bluetooth/nimble/throughput_app/bleprph_throughput/README.md rename to examples/bluetooth/nimble/throughput_app/gatt/bleprph_throughput/README.md diff --git a/examples/bluetooth/nimble/throughput_app/bleprph_throughput/main/CMakeLists.txt b/examples/bluetooth/nimble/throughput_app/gatt/bleprph_throughput/main/CMakeLists.txt similarity index 100% rename from examples/bluetooth/nimble/throughput_app/bleprph_throughput/main/CMakeLists.txt rename to examples/bluetooth/nimble/throughput_app/gatt/bleprph_throughput/main/CMakeLists.txt diff --git a/examples/bluetooth/nimble/throughput_app/bleprph_throughput/main/Kconfig.projbuild b/examples/bluetooth/nimble/throughput_app/gatt/bleprph_throughput/main/Kconfig.projbuild similarity index 100% rename from examples/bluetooth/nimble/throughput_app/bleprph_throughput/main/Kconfig.projbuild rename to examples/bluetooth/nimble/throughput_app/gatt/bleprph_throughput/main/Kconfig.projbuild diff --git a/examples/bluetooth/nimble/throughput_app/bleprph_throughput/main/gatt_svr.c b/examples/bluetooth/nimble/throughput_app/gatt/bleprph_throughput/main/gatt_svr.c similarity index 77% rename from examples/bluetooth/nimble/throughput_app/bleprph_throughput/main/gatt_svr.c rename to examples/bluetooth/nimble/throughput_app/gatt/bleprph_throughput/main/gatt_svr.c index 0f952f2f67c..b5535b81e05 100644 --- a/examples/bluetooth/nimble/throughput_app/bleprph_throughput/main/gatt_svr.c +++ b/examples/bluetooth/nimble/throughput_app/gatt/bleprph_throughput/main/gatt_svr.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -33,8 +33,8 @@ #define THRPT_CHR_NOTIFY 0x000a #define THRPT_LONG_CHR_READ_WRITE 0x000b -#define READ_THROUGHPUT_PAYLOAD 510 /* MTU(512) - ATT read rsp header(1) - 1 (avoid Read Blob) */ -#define WRITE_THROUGHPUT_PAYLOAD 509 /* MTU(512) - ATT write cmd header(3) */ +#define READ_THROUGHPUT_PAYLOAD 497 /* 502 bytes ACL -> 2x 251 LL packets exactly (497 + 1 Read Rsp + 4 L2CAP) */ +#define WRITE_THROUGHPUT_PAYLOAD 495 /* 502 bytes ACL -> 2x 251 LL packets exactly (495 + 3 Write Cmd + 4 L2CAP) */ static const char *tag = "bleprph_throughput"; @@ -142,6 +142,32 @@ gatt_svr_read_write_long_test(uint16_t conn_handle, uint16_t attr_handle, case THRPT_CHR_READ_WRITE: if (ctxt->op == BLE_GATT_ACCESS_OP_WRITE_CHR) { + uint16_t om_len = OS_MBUF_PKTLEN(ctxt->om); + /* If the central sends exactly 1 byte, it is a PHY control command */ + if (om_len == 1) { + uint8_t requested_phy; + os_mbuf_copydata(ctxt->om, 0, 1, &requested_phy); + rc = 0; + if (requested_phy == 0) { /* 0 = PHY_1M */ + rc = ble_gap_set_prefered_le_phy(conn_handle, BLE_HCI_LE_PHY_1M_PREF_MASK, BLE_HCI_LE_PHY_1M_PREF_MASK, 0); + ESP_LOGI(tag, "Central requested 1M PHY via GATT command"); + } else if (requested_phy == 1) { /* 1 = PHY_2M */ + rc = ble_gap_set_prefered_le_phy(conn_handle, BLE_HCI_LE_PHY_2M_PREF_MASK, BLE_HCI_LE_PHY_2M_PREF_MASK, 0); + ESP_LOGI(tag, "Central requested 2M PHY via GATT command"); + } else if (requested_phy == 2) { /* 2 = PHY_CODED_S2 */ + rc = ble_gap_set_prefered_le_phy(conn_handle, BLE_HCI_LE_PHY_CODED_PREF_MASK, BLE_HCI_LE_PHY_CODED_PREF_MASK, 0x01); + ESP_LOGI(tag, "Central requested S2 PHY via GATT command"); + } else if (requested_phy == 3) { /* 3 = PHY_CODED_S8 */ + rc = ble_gap_set_prefered_le_phy(conn_handle, BLE_HCI_LE_PHY_CODED_PREF_MASK, BLE_HCI_LE_PHY_CODED_PREF_MASK, 0x02); + ESP_LOGI(tag, "Central requested S8 PHY via GATT command"); + } + + if (rc != 0) { + ESP_LOGE(tag, "Failed to set preferred LE PHY; rc=%d", rc); + } + return 0; + } + rc = gatt_svr_chr_write(conn_handle, attr_handle, ctxt->om, 0, sizeof gatt_svr_thrpt_static_short_val, diff --git a/examples/bluetooth/nimble/throughput_app/bleprph_throughput/main/gatts_sens.h b/examples/bluetooth/nimble/throughput_app/gatt/bleprph_throughput/main/gatts_sens.h similarity index 100% rename from examples/bluetooth/nimble/throughput_app/bleprph_throughput/main/gatts_sens.h rename to examples/bluetooth/nimble/throughput_app/gatt/bleprph_throughput/main/gatts_sens.h diff --git a/examples/bluetooth/nimble/throughput_app/bleprph_throughput/main/main.c b/examples/bluetooth/nimble/throughput_app/gatt/bleprph_throughput/main/main.c similarity index 81% rename from examples/bluetooth/nimble/throughput_app/bleprph_throughput/main/main.c rename to examples/bluetooth/nimble/throughput_app/gatt/bleprph_throughput/main/main.c index 27d95cc72d4..a17495cc464 100644 --- a/examples/bluetooth/nimble/throughput_app/bleprph_throughput/main/main.c +++ b/examples/bluetooth/nimble/throughput_app/gatt/bleprph_throughput/main/main.c @@ -30,16 +30,16 @@ static uint8_t s_current_phy; static const char *device_name = "nimble_prph"; -#define NOTIFY_THROUGHPUT_PAYLOAD 509 /* MTU(512) - ATT notify header(3) */ -#define MIN_REQUIRED_MBUF 2 /* Assuming payload of 500Bytes and each mbuf can take 292Bytes. */ -#define NOTIFY_PIPELINE_DEPTH 15 /* Number of notifications to keep in flight for throughput */ +#define NOTIFY_THROUGHPUT_PAYLOAD 495 /* Exactly aligns with 2 LL packets */ +#define MIN_REQUIRED_MBUF 10 /* Reserve mbufs for incoming ATT packets */ +#define NOTIFY_PIPELINE_DEPTH 8 /* Number of notifications to keep in flight */ #define PREFERRED_MTU_VALUE 512 #define LL_PACKET_TIME 2120 #define LL_PACKET_LENGTH 251 #define MTU_DEF 512 static const char *tag = "bleprph_throughput"; -static SemaphoreHandle_t notify_sem; +static TaskHandle_t notify_task_handle = NULL; static bool notify_state; static int notify_test_time = 60; static uint16_t conn_handle; @@ -260,12 +260,13 @@ notify_task(void *arg) break; } - while (notify_time < (notify_test_time * 1000)) { - /* We are anyway using counting semaphore for sending - * notifications. So hopefully not much waiting period will be - * introduced before sending a new notification. Revisit this - * counter if need to do away with semaphore waiting. XXX */ - xSemaphoreTake(notify_sem, portMAX_DELAY); + while (notify_state && notify_time < (notify_test_time * 1000)) { + ulTaskNotifyTake(pdFALSE, portMAX_DELAY); + + /* Stop immediately if central unsubscribed */ + if (!notify_state) { + break; + } if (dummy == 200) { dummy = 0; @@ -284,17 +285,22 @@ notify_task(void *arg) rc = ble_gatts_notify_custom(conn_handle, notify_handle, om); if (rc != 0) { - ESP_LOGE(tag, "Error while sending notification; rc = %d", rc); + if (rc == BLE_HS_ENOMEM) { + ESP_LOGD(tag, "Notification queue full (expected backpressure)"); + } else { + ESP_LOGE(tag, "Error while sending notification; rc = %d", rc); + } notify_count -= 1; - xSemaphoreGive(notify_sem); /* Yield to let mbufs free up */ vTaskDelay(1); } } else { - xSemaphoreGive(notify_sem); - notify_count -= 1; - /* Yield briefly to let mbufs free up */ - vTaskDelay(1); + if (notify_task_handle) { + xTaskNotifyGive(notify_task_handle); + } + notify_count -= 1; + /* Yield briefly to let mbufs free up */ + vTaskDelay(1); } end_time = esp_timer_get_time(); @@ -302,13 +308,20 @@ notify_task(void *arg) notify_count += 1; } - printf("\n*********************************\n"); - ESP_LOGI(tag, "Notify throughput = %d bps, count = %d", - (notify_count * NOTIFY_THROUGHPUT_PAYLOAD * 8) / notify_test_time, notify_count); - printf("\n*********************************\n"); - ESP_LOGI(tag, " Notification test complete for stipulated time of %d sec", notify_test_time); + /* Use actual elapsed time for accurate throughput calculation */ + { + int actual_secs = (int)(notify_time / 1000); + if (actual_secs > 0 && notify_count > 0) { + printf("\n*********************************\n"); + ESP_LOGI(tag, "Notify throughput = %d bps, count = %d", + (int)(((uint64_t)notify_count * NOTIFY_THROUGHPUT_PAYLOAD * 8) / actual_secs), notify_count); + printf("\n*********************************\n"); + ESP_LOGI(tag, " Notification test complete (%d sec elapsed)", actual_secs); + } + } notify_test_time = 0; notify_count = 0; + notify_time = 0; break; } @@ -355,6 +368,12 @@ gatts_gap_event(struct ble_gap_event *event, void *arg) case BLE_GAP_EVENT_DISCONNECT: ESP_LOGI(tag, "disconnect; reason = %d", event->disconnect.reason); + /* Stop notification task loop cleanly */ + notify_state = false; + if (notify_task_handle) { + xTaskNotifyGive(notify_task_handle); + } + /* Connection terminated; resume advertising */ #if CONFIG_EXAMPLE_EXTENDED_ADV ble_gap_ext_adv_stop(0); @@ -375,11 +394,6 @@ gatts_gap_event(struct ble_gap_event *event, void *arg) case BLE_GAP_EVENT_ADV_COMPLETE: ESP_LOGI(tag, "adv complete "); -#if CONFIG_EXAMPLE_EXTENDED_ADV - ext_bleprph_advertise(); -#else - gatts_advertise(); -#endif break; case BLE_GAP_EVENT_SUBSCRIBE: @@ -388,17 +402,22 @@ gatts_gap_event(struct ble_gap_event *event, void *arg) event->subscribe.cur_notify, event->subscribe.attr_handle); if (event->subscribe.attr_handle == notify_handle) { notify_state = event->subscribe.cur_notify; - if (arg != NULL) { - ESP_LOGI(tag, "notify test time = %d", *(int *)arg); - notify_test_time = *((int *)arg); - } if (notify_state) { + /* Always reset test time on new subscription. + * The central controls the actual duration by unsubscribing. + * Use a large default so the peripheral never stops on its own. */ + notify_test_time = 3600; + ESP_LOGI(tag, "Notifications enabled, test time = %d sec", notify_test_time); /* Prime the notification pipeline to allow multiple in-flight * notifications. This enables the controller to fill connection * events with back-to-back PDUs for maximum throughput. */ for (int i = 0; i < NOTIFY_PIPELINE_DEPTH; i++) { - xSemaphoreGive(notify_sem); + if (notify_task_handle) { + xTaskNotifyGive(notify_task_handle); + } } + } else { + ESP_LOGI(tag, "Notifications disabled"); } } else if (event->subscribe.attr_handle != notify_handle) { notify_state = event->subscribe.cur_notify; @@ -407,16 +426,20 @@ gatts_gap_event(struct ble_gap_event *event, void *arg) case BLE_GAP_EVENT_NOTIFY_TX: ESP_LOGD(tag, "BLE_GAP_EVENT_NOTIFY_TX success !!"); - if ((event->notify_tx.status == 0) || - (event->notify_tx.status == BLE_HS_EDONE)) { - /* Send new notification i.e. give Semaphore. By definition, - * sending new notifications should not be based on successful - * notifications sent, but let us adopt this method to avoid too - * many `BLE_HS_ENOMEM` errors because of continuous transfer of - * notifications.XXX */ - xSemaphoreGive(notify_sem); - } else { - ESP_LOGE(tag, "BLE_GAP_EVENT_NOTIFY_TX notify tx status = %d", event->notify_tx.status); + + /* Always return the pipeline token to prevent the task from blocking permanently, + * even if the notification failed (e.g., due to disconnect or buffer full). */ + if (notify_task_handle) { + xTaskNotifyGive(notify_task_handle); + } + + if ((event->notify_tx.status != 0) && + (event->notify_tx.status != BLE_HS_EDONE)) { + if (event->notify_tx.status == BLE_HS_ENOMEM) { + ESP_LOGD(tag, "BLE_GAP_EVENT_NOTIFY_TX flow control (ENOMEM)"); + } else { + ESP_LOGE(tag, "BLE_GAP_EVENT_NOTIFY_TX notify tx status = %d", event->notify_tx.status); + } } break; @@ -476,13 +499,8 @@ gatts_on_reset(int reason) void gatts_host_task(void *param) { ESP_LOGI(tag, "BLE Host Task Started"); - /* Create a counting semaphore for Notification. Can be used to track - * successful notification txmission. Optimistically take some big number - * for counting Semaphore */ - notify_sem = xSemaphoreCreateCounting(100, 0); /* This function will return only when nimble_port_stop() is executed */ nimble_port_run(); - vSemaphoreDelete(notify_sem); nimble_port_freertos_deinit(); } @@ -511,7 +529,7 @@ void app_main(void) ble_hs_cfg.store_status_cb = ble_store_util_status_rr; /* Initialize Notify Task */ - BaseType_t task_rc = xTaskCreate(notify_task, "notify_task", 4096, NULL, 10, NULL); + BaseType_t task_rc = xTaskCreate(notify_task, "notify_task", 4096, NULL, 10, ¬ify_task_handle); if (task_rc != pdPASS) { ESP_LOGE(tag, "Failed to create notify_task (rc=%d)", task_rc); return ; diff --git a/examples/bluetooth/nimble/throughput_app/gatt/bleprph_throughput/sdkconfig.defaults b/examples/bluetooth/nimble/throughput_app/gatt/bleprph_throughput/sdkconfig.defaults new file mode 100644 index 00000000000..2b58c85d2ff --- /dev/null +++ b/examples/bluetooth/nimble/throughput_app/gatt/bleprph_throughput/sdkconfig.defaults @@ -0,0 +1,33 @@ +# Override some defaults so BT stack is enabled +# in this example and some example specific misc sizes are increased. + +# +# BT config (universal across all ESP32 variants) +# +CONFIG_BT_ENABLED=y +CONFIG_BT_BLUEDROID_ENABLED=n +CONFIG_BT_NIMBLE_ENABLED=y + +# === BT 5.0 / 2M PHY support === +# Ignored automatically on classic ESP32 which lacks BT 5.0 +CONFIG_BT_NIMBLE_EXT_ADV=n +CONFIG_BT_NIMBLE_50_FEATURE_SUPPORT=y +CONFIG_EXAMPLE_EXTENDED_ADV=n + +# === ATT / GATT === +CONFIG_BT_NIMBLE_ATT_PREFERRED_MTU=512 + +# === Memory Pool Tuning === +CONFIG_BT_NIMBLE_MAX_CONNECTIONS=1 +CONFIG_BT_NIMBLE_MSYS_1_BLOCK_COUNT=80 +CONFIG_BT_NIMBLE_MSYS_2_BLOCK_SIZE=592 +CONFIG_BT_NIMBLE_MSYS_2_BLOCK_COUNT=80 + +# Increase controller-to-host buffers +CONFIG_BT_NIMBLE_TRANSPORT_ACL_FROM_LL_COUNT=40 +CONFIG_BT_NIMBLE_TRANSPORT_ACL_SIZE=255 +CONFIG_BT_NIMBLE_TRANSPORT_EVT_SIZE=255 + +# Disable NimBLE INFO logging for throughput tests +CONFIG_BT_NIMBLE_LOG_LEVEL_WARNING=y +CONFIG_BT_NIMBLE_LOG_LEVEL=2 diff --git a/examples/bluetooth/nimble/throughput_app/gatt/bleprph_throughput/sdkconfig.defaults.esp32c6 b/examples/bluetooth/nimble/throughput_app/gatt/bleprph_throughput/sdkconfig.defaults.esp32c6 new file mode 100644 index 00000000000..a12d9353d92 --- /dev/null +++ b/examples/bluetooth/nimble/throughput_app/gatt/bleprph_throughput/sdkconfig.defaults.esp32c6 @@ -0,0 +1,5 @@ +# === High-Performance Tuning === +# This config is only applied when building for the ESP32-C6 target +# It is necessary for the C6 to reach 750+ kbps and 1.3+ Mbps throughput. +CONFIG_COMPILER_OPTIMIZATION_PERF=y +CONFIG_FREERTOS_HZ=1000 diff --git a/examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_cent/CMakeLists.txt b/examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_cent/CMakeLists.txt new file mode 100644 index 00000000000..c66bff776f7 --- /dev/null +++ b/examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_cent/CMakeLists.txt @@ -0,0 +1,5 @@ +cmake_minimum_required(VERSION 3.22) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +idf_build_set_property(MINIMAL_BUILD ON) +project(l2cap_coc_cent) diff --git a/examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_cent/README.md b/examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_cent/README.md new file mode 100644 index 00000000000..59b1e64d1fa --- /dev/null +++ b/examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_cent/README.md @@ -0,0 +1,94 @@ +| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C5 | ESP32-C6 | ESP32-C61 | ESP32-H2 | ESP32-S3 | +| ----------------- | ----- | -------- | -------- | -------- | -------- | --------- | -------- | -------- | + +# L2CAP COC Throughput Central Example + +`l2cap_coc_cent` demonstrates the central (initiator) side of an L2CAP Connection-Oriented Channel (COC) throughput test using NimBLE on ESP32. It passively scans for a peripheral advertising UUID 0x1812, establishes a GAP connection, enables Data Length Extension (DLE), then opens an L2CAP COC channel over PSM 0x1002 and continuously sends SDUs to measure TX throughput. + +The central automatically cycles through all enabled PHYs (1M, 2M, Coded S2, Coded S8) in sequence, printing a throughput summary box after each test interval. It must be used together with the `l2cap_coc_prph` example which acts as the receiving side. + +It uses ESP32's Bluetooth controller and NimBLE stack based BLE host. + +## How to Use Example + +Before project configuration and build, be sure to set the correct chip target using: + +```bash +idf.py set-target +``` + +### Hardware Required + +* Two development boards, one flashed with `l2cap_coc_cent` and the other with `l2cap_coc_prph`. +* A USB cable for power supply and programming. + +See [Development Boards](https://www.espressif.com/en/products/devkits) for more information. + +### Configure the Project + +Open the project configuration menu: + +```bash +idf.py menuconfig +``` + +In the `L2CAP COC Throughput Configuration` menu: + +| Option | Default | Description | +|--------|---------|-------------| +| `EXAMPLE_L2CAP_COC_MTU` | 2048 | L2CAP CoC SDU MTU size in bytes (central receive buffer). Data flows cent → prph in this test, so throughput is governed by the peripheral's MTU. This value only limits how much the peripheral can send back and does not affect TX throughput. | +| `EXAMPLE_EXTENDED_ADV` | y (BLE 5.0 chips) | Enable extended scanning to find peripherals using extended advertising. Required for Coded PHY testing on ESP32-C6/H2. | +| `EXAMPLE_TEST_PHY_1M` | n | Enable throughput test on 1M PHY. | +| `EXAMPLE_TEST_PHY_2M` | y | Enable throughput test on 2M PHY (BLE 5.0 chips only). | +| `EXAMPLE_TEST_PHY_CODED_S2` | n | Enable throughput test on Coded PHY S2 (500 kbps, BLE 5.0 chips only). | +| `EXAMPLE_TEST_PHY_CODED_S8` | n | Enable throughput test on Coded PHY S8 (125 kbps, BLE 5.0 chips only). | +| `EXAMPLE_TEST_DURATION_1M` | 8 | Test duration in seconds for 1M PHY. | +| `EXAMPLE_TEST_DURATION_2M` | 8 | Test duration in seconds for 2M PHY. | +| `EXAMPLE_TEST_DURATION_CODED_S2` | 8 | Test duration in seconds for Coded S2 PHY. | +| `EXAMPLE_TEST_DURATION_CODED_S8` | 8 | Test duration in seconds for Coded S8 PHY. | + +### Build and Flash + +Run `idf.py -p PORT flash monitor` to build, flash and monitor the project. + +(To exit the serial monitor, type ``Ctrl-]``.) + +See the [Getting Started Guide](https://idf.espressif.com/) for full steps to configure and use ESP-IDF to build projects. + +## Example Output + +On successful connection and throughput test, the central prints a per-PHY summary box after each test interval, then loops back to the first enabled PHY continuously: + +``` +I (xxx) l2cap_coc_cent: BLE Host Task started +I (xxx) l2cap_coc_cent: Device Address: xx:xx:xx:xx:xx:xx +I (xxx) l2cap_coc_cent: Connecting to xx:xx:xx:xx:xx:xx (addr_type=0) +I (xxx) l2cap_coc_cent: Connected; handle=0 peer=xx:xx:xx:xx:xx:xx +I (xxx) l2cap_coc_cent: L2CAP COC connected, chan=0xxxxxxxxx +I (xxx) l2cap_coc_cent: L2CAP COC Throughput — TX side (central sends to peripheral) +I (xxx) l2cap_coc_cent: Number of enabled PHYs: x +I (xxx) l2cap_coc_cent: PHY updated: tx=2 rx=2 status=0 +I (xxx) l2cap_coc_cent: [2M PHY] Sending for 8 s +I (xxx) l2cap_coc_cent: +-------------------------------------------------+ +I (xxx) l2cap_coc_cent: | PHY : 2M | +I (xxx) l2cap_coc_cent: | TX : xxxx kbps | +I (xxx) l2cap_coc_cent: | Bytes : xxxxxxx | +I (xxx) l2cap_coc_cent: | Time : 8 s | +I (xxx) l2cap_coc_cent: +-------------------------------------------------+ +I (xxx) l2cap_coc_cent: Cycle complete. Looping back to first PHY... +I (xxx) l2cap_coc_cent: PHY updated: tx=2 rx=2 status=0 +I (xxx) l2cap_coc_cent: [2M PHY] Sending for 8 s +I (xxx) l2cap_coc_cent: +-------------------------------------------------+ +I (xxx) l2cap_coc_cent: | PHY : 2M | +I (xxx) l2cap_coc_cent: | TX : xxxx kbps | +I (xxx) l2cap_coc_cent: | Bytes : xxxxxxx | +I (xxx) l2cap_coc_cent: | Time : 8 s | +I (xxx) l2cap_coc_cent: +-------------------------------------------------+ +I (xxx) l2cap_coc_cent: Cycle complete. Looping back to first PHY... +``` + +> **Note:** The above output was captured on ESP32-H2 with only 2M PHY enabled. With additional PHYs enabled (1M, Coded S2, Coded S8), the central cycles through each in sequence before looping back. + +## Troubleshooting + +For any technical queries, please open an [issue](https://github.com/espressif/esp-idf/issues) on GitHub. We will get back to you soon. diff --git a/examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_cent/main/CMakeLists.txt b/examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_cent/main/CMakeLists.txt new file mode 100644 index 00000000000..718f89cab8e --- /dev/null +++ b/examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_cent/main/CMakeLists.txt @@ -0,0 +1,3 @@ +idf_component_register(SRCS "main.c" + PRIV_REQUIRES bt nvs_flash esp_timer + INCLUDE_DIRS ".") diff --git a/examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_cent/main/Kconfig.projbuild b/examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_cent/main/Kconfig.projbuild new file mode 100644 index 00000000000..5632e2c99d0 --- /dev/null +++ b/examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_cent/main/Kconfig.projbuild @@ -0,0 +1,67 @@ +menu "L2CAP COC Throughput Configuration" + + config EXAMPLE_L2CAP_COC_MTU + int "L2CAP CoC MTU size in bytes" + default 2048 + range 512 65511 + help + L2CAP CoC SDU MTU size used by the central device. + Note: memory pool allocates 6 buffers of this size; total pool + memory = MTU x 6. On chips without PSRAM ensure sufficient heap + is available before increasing this value. + Use idf.py size-components to verify. + + config EXAMPLE_EXTENDED_ADV + bool + depends on SOC_BLE_50_SUPPORTED && BT_NIMBLE_50_FEATURE_SUPPORT + default y if SOC_ESP_NIMBLE_CONTROLLER + select BT_NIMBLE_EXT_ADV + prompt "Enable Extended Scanning" + help + Use extended scanning on chips that support BLE 5.0 + + config EXAMPLE_TEST_PHY_1M + bool "Test on 1M PHY" + default y if !SOC_BLE_50_SUPPORTED + default n + + config EXAMPLE_TEST_PHY_2M + bool "Test on 2M PHY" + default y + depends on SOC_BLE_50_SUPPORTED + + config EXAMPLE_TEST_PHY_CODED_S2 + bool "Test on Coded PHY S2" + default n + depends on SOC_BLE_50_SUPPORTED + + config EXAMPLE_TEST_PHY_CODED_S8 + bool "Test on Coded PHY S8" + default n + depends on SOC_BLE_50_SUPPORTED + + config EXAMPLE_TEST_DURATION_1M + int "Test duration for 1M PHY (sec)" + default 8 + range 1 3600 + depends on EXAMPLE_TEST_PHY_1M + + config EXAMPLE_TEST_DURATION_2M + int "Test duration for 2M PHY (sec)" + default 8 + range 1 3600 + depends on EXAMPLE_TEST_PHY_2M + + config EXAMPLE_TEST_DURATION_CODED_S2 + int "Test duration for Coded S2 PHY (sec)" + default 8 + range 1 3600 + depends on EXAMPLE_TEST_PHY_CODED_S2 + + config EXAMPLE_TEST_DURATION_CODED_S8 + int "Test duration for Coded S8 PHY (sec)" + default 8 + range 1 3600 + depends on EXAMPLE_TEST_PHY_CODED_S8 + +endmenu diff --git a/examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_cent/main/idf_component.yml b/examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_cent/main/idf_component.yml new file mode 100644 index 00000000000..db8886afea4 --- /dev/null +++ b/examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_cent/main/idf_component.yml @@ -0,0 +1,3 @@ +dependencies: + nimble_central_utils: + path: ${IDF_PATH}/examples/bluetooth/nimble/common/nimble_central_utils diff --git a/examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_cent/main/main.c b/examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_cent/main/main.c new file mode 100644 index 00000000000..09f2c989884 --- /dev/null +++ b/examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_cent/main/main.c @@ -0,0 +1,738 @@ +/* + * SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include "esp_log.h" +#include "nvs_flash.h" +#include "esp_timer.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/event_groups.h" +#include "nimble/nimble_port.h" +#include "nimble/nimble_port_freertos.h" +#include "host/ble_hs.h" +#include "host/util/util.h" +#include "services/gap/ble_svc_gap.h" +#include "host/ble_esp_gap.h" + +static const char *TAG = "l2cap_coc_cent"; + +#define L2CAP_COC_PSM 0x1002 +#define L2CAP_COC_MTU CONFIG_EXAMPLE_L2CAP_COC_MTU +#define COC_BUF_COUNT (6 * MYNEWT_VAL(BLE_L2CAP_COC_MAX_NUM)) +/* Block size must include mbuf headers so each SDU fits in one pool entry. */ +#define SDU_BLOCK_SIZE (L2CAP_COC_MTU + sizeof(struct os_mbuf_pkthdr) + sizeof(struct os_mbuf)) +#define LL_PACKET_LENGTH 251 +#define LL_PACKET_TIME 2120 +#define L2CAP_COC_UUID 0x1812 + +/* EventGroup bits */ +#define PHY_UPDATED_BIT (1 << 0) +#define COC_CONNECTED_BIT (1 << 1) +#define CONN_UPDATED_BIT (1 << 2) +#define TX_UNSTALLED_BIT (1 << 3) + +/* Timeout / interval constants */ +#define CONN_PARAM_UPDATE_TIMEOUT_MS 5000 +#define PREDRAIN_TIMEOUT_MS 20000 +#define POSTDRAIN_TIMEOUT_MS 5000 +#define TX_YIELD_INTERVAL 50 + +static EventGroupHandle_t coc_event_group; +static uint16_t conn_handle = BLE_HS_CONN_HANDLE_NONE; +static struct ble_l2cap_chan *coc_chan = NULL; +static bool ci_is_slow = false; +static bool l2cap_connecting = false; /* guards double L2CAP connect */ +static volatile bool chan_stalled = false; +static uint32_t *cent_seg_tx_done = NULL; /* points to segment SDU counter for async TX_UNSTALLED */ +static uint32_t *cent_seg_tx_drop = NULL; /* counts SDUs dropped (TX_UNSTALLED status != 0) */ +static uint16_t cent_tx_sdu_len = L2CAP_COC_MTU; /* min(local, peer) after COC connect */ + +static const struct ble_gap_upd_params conn_params = { + .itvl_min = 6, + .itvl_max = 6, + .latency = 0, + .supervision_timeout = 2000, + .min_ce_len = 12, + .max_ce_len = 24, +}; + +void ble_store_config_init(void); + +static os_membuf_t sdu_coc_mem[OS_MEMPOOL_SIZE(COC_BUF_COUNT, SDU_BLOCK_SIZE)]; +static struct os_mempool sdu_coc_mempool; +static struct os_mbuf_pool sdu_os_mbuf_pool; + +typedef struct { + uint8_t tx_phys; + uint8_t rx_phys; + uint8_t phy_opts; /* 0=none, 1=S2, 2=S8 */ + int duration_s; + const char *name; + bool is_coded_s8; +} phy_entry_t; + +static const phy_entry_t phy_list[] = { +#if CONFIG_EXAMPLE_TEST_PHY_1M + { BLE_HCI_LE_PHY_1M_PREF_MASK, BLE_HCI_LE_PHY_1M_PREF_MASK, 0, + CONFIG_EXAMPLE_TEST_DURATION_1M, "1M", false }, +#endif +#if CONFIG_EXAMPLE_TEST_PHY_2M + { BLE_HCI_LE_PHY_2M_PREF_MASK, BLE_HCI_LE_PHY_2M_PREF_MASK, 0, + CONFIG_EXAMPLE_TEST_DURATION_2M, "2M", false }, +#endif +#if CONFIG_EXAMPLE_TEST_PHY_CODED_S2 + { BLE_HCI_LE_PHY_CODED_PREF_MASK, BLE_HCI_LE_PHY_CODED_PREF_MASK, 0x01, + CONFIG_EXAMPLE_TEST_DURATION_CODED_S2, "Coded S2", false }, +#endif +#if CONFIG_EXAMPLE_TEST_PHY_CODED_S8 + { BLE_HCI_LE_PHY_CODED_PREF_MASK, BLE_HCI_LE_PHY_CODED_PREF_MASK, 0x02, + CONFIG_EXAMPLE_TEST_DURATION_CODED_S8, "Coded S8", true }, +#endif +}; +#define PHY_LIST_LEN ((int)(sizeof(phy_list) / sizeof(phy_list[0]))) + +static int cent_gap_event(struct ble_gap_event *event, void *arg); +static int cent_l2cap_coc_event_cb(struct ble_l2cap_event *event, void *arg); + +static void cent_l2cap_coc_mem_init(void) +{ + int rc; + rc = os_mempool_init(&sdu_coc_mempool, COC_BUF_COUNT, SDU_BLOCK_SIZE, + sdu_coc_mem, "cent_coc_pool"); + assert(rc == 0); + rc = os_mbuf_pool_init(&sdu_os_mbuf_pool, &sdu_coc_mempool, + SDU_BLOCK_SIZE, COC_BUF_COUNT); + assert(rc == 0); +} + +static void cent_l2cap_coc_connect(uint16_t conn_handle) +{ + struct ble_gap_conn_desc desc; + if (ble_gap_conn_find(conn_handle, &desc) != 0) { + ESP_LOGE(TAG, "L2CAP COC connect: connection %d not found", conn_handle); + l2cap_connecting = false; + return; + } + struct os_mbuf *sdu_rx = os_mbuf_get_pkthdr(&sdu_os_mbuf_pool, 0); + if (!sdu_rx) { + ESP_LOGE(TAG, "Failed to alloc sdu_rx for L2CAP connect"); + l2cap_connecting = false; + return; + } + int rc = ble_l2cap_connect(conn_handle, L2CAP_COC_PSM, L2CAP_COC_MTU, + sdu_rx, cent_l2cap_coc_event_cb, NULL); + if (rc != 0) { + ESP_LOGE(TAG, "L2CAP COC connect failed; rc=%d", rc); + l2cap_connecting = false; + /* EINVAL: NimBLE returns before chan alloc, sdu_rx not consumed — free it. + * ENOTCONN: NimBLE frees sdu_rx on all ENOTCONN paths (early !conn check + * and late TX failure via ble_l2cap_coc_cleanup_chan). Do not free here. */ + if (rc == BLE_HS_EINVAL) { + os_mbuf_free_chain(sdu_rx); + } + } +} + +static void cent_scan(void) +{ + struct ble_gap_disc_params disc_params = { + .filter_duplicates = 1, + .passive = 1, + }; + uint8_t own_addr_type; + int rc = ble_hs_id_infer_auto(0, &own_addr_type); + if (rc != 0) { + ESP_LOGE(TAG, "Error inferring addr type; rc=%d", rc); + return; + } + rc = ble_gap_disc(own_addr_type, BLE_HS_FOREVER, &disc_params, + cent_gap_event, NULL); + if (rc != 0) { + ESP_LOGE(TAG, "Error starting scan; rc=%d", rc); + } +} + +static int cent_should_connect(const struct ble_gap_disc_desc *disc) +{ + struct ble_hs_adv_fields fields; + if (disc->event_type != BLE_HCI_ADV_RPT_EVTYPE_ADV_IND && + disc->event_type != BLE_HCI_ADV_RPT_EVTYPE_DIR_IND) { + return 0; + } + int rc = ble_hs_adv_parse_fields(&fields, disc->data, disc->length_data); + if (rc != 0) { + return 0; + } + for (int i = 0; i < fields.num_uuids16; i++) { + if (ble_uuid_u16(&fields.uuids16[i].u) == L2CAP_COC_UUID) { + return 1; + } + } + return 0; +} + +static void cent_connect_if_interesting(const struct ble_gap_disc_desc *disc) +{ + if (!cent_should_connect(disc)) { + return; + } + int rc = ble_gap_disc_cancel(); + if (rc != 0) { + return; + } + uint8_t own_addr_type; + rc = ble_hs_id_infer_auto(0, &own_addr_type); + if (rc != 0) { + ESP_LOGE(TAG, "Error inferring addr type; rc=%d", rc); + cent_scan(); + return; + } + ESP_LOGI(TAG, "Connecting to %02x:%02x:%02x:%02x:%02x:%02x (addr_type=%d)", + disc->addr.val[5], disc->addr.val[4], disc->addr.val[3], + disc->addr.val[2], disc->addr.val[1], disc->addr.val[0], + disc->addr.type); + rc = ble_gap_connect(own_addr_type, &disc->addr, 30000, NULL, + cent_gap_event, NULL); + if (rc != 0) { + ESP_LOGE(TAG, "Connect failed; rc=%d", rc); + cent_scan(); + } +} + +#if CONFIG_EXAMPLE_EXTENDED_ADV +static void cent_connect_if_interesting_ext(const struct ble_gap_ext_disc_desc *disc) +{ + if (!(disc->props & BLE_HCI_ADV_CONN_MASK)) { + return; + } + struct ble_hs_adv_fields fields; + if (ble_hs_adv_parse_fields(&fields, disc->data, disc->length_data) != 0) { + return; + } + int found = 0; + for (int i = 0; i < fields.num_uuids16; i++) { + if (ble_uuid_u16(&fields.uuids16[i].u) == L2CAP_COC_UUID) { + found = 1; + break; + } + } + if (!found) { + return; + } + int rc = ble_gap_disc_cancel(); + if (rc != 0) { + return; + } + uint8_t own_addr_type; + rc = ble_hs_id_infer_auto(0, &own_addr_type); + if (rc != 0) { + ESP_LOGE(TAG, "Error inferring addr type; rc=%d", rc); + cent_scan(); + return; + } + ESP_LOGI(TAG, "Connecting to %02x:%02x:%02x:%02x:%02x:%02x (addr_type=%d)", + disc->addr.val[5], disc->addr.val[4], disc->addr.val[3], + disc->addr.val[2], disc->addr.val[1], disc->addr.val[0], + disc->addr.type); + rc = ble_gap_connect(own_addr_type, &disc->addr, 30000, NULL, + cent_gap_event, NULL); + if (rc != 0) { + ESP_LOGE(TAG, "Connect failed; rc=%d", rc); + cent_scan(); + } +} +#endif /* CONFIG_EXAMPLE_EXTENDED_ADV */ + +static int cent_l2cap_coc_event_cb(struct ble_l2cap_event *event, void *arg) +{ + switch (event->type) { + case BLE_L2CAP_EVENT_COC_CONNECTED: { + struct ble_l2cap_chan_info info; + uint16_t peer_mtu; + + if (event->connect.status != 0) { + ESP_LOGE(TAG, "L2CAP COC connect status: %d,terminating GAP to restart", event->connect.status); + l2cap_connecting = false; + ble_gap_terminate(conn_handle, BLE_ERR_REM_USER_CONN_TERM); + return 0; + } + ESP_LOGI(TAG, "L2CAP COC connected, chan=%p", event->connect.chan); + coc_chan = event->connect.chan; + + peer_mtu = 0; + cent_tx_sdu_len = L2CAP_COC_MTU; + if (ble_l2cap_get_chan_info(coc_chan, &info) == 0) { + peer_mtu = info.peer_coc_mtu; + if (peer_mtu > 0 && peer_mtu < cent_tx_sdu_len) { + cent_tx_sdu_len = peer_mtu; + } + } + ESP_LOGI(TAG, "TX SDU size: %u bytes (peer CoC MTU %u)", + cent_tx_sdu_len, peer_mtu); + l2cap_connecting = false; + xEventGroupSetBits(coc_event_group, COC_CONNECTED_BIT); + return 0; + } + + case BLE_L2CAP_EVENT_COC_DISCONNECTED: + ESP_LOGI(TAG, "L2CAP COC disconnected"); + coc_chan = NULL; + chan_stalled = false; + cent_seg_tx_done = NULL; + cent_seg_tx_drop = NULL; + l2cap_connecting = false; + xEventGroupClearBits(coc_event_group, COC_CONNECTED_BIT); + xEventGroupSetBits(coc_event_group, TX_UNSTALLED_BIT | CONN_UPDATED_BIT | PHY_UPDATED_BIT); + return 0; + + case BLE_L2CAP_EVENT_COC_TX_UNSTALLED: + /* status==0: SDU delivered; status!=0: NimBLE dropped it (ENOMEM) — don't count. */ + chan_stalled = false; + if (event->tx_unstalled.status == 0) { + if (cent_seg_tx_done) { + (*cent_seg_tx_done)++; + } + } else { + if (cent_seg_tx_drop) { + (*cent_seg_tx_drop)++; + } + ESP_LOGD(TAG, "TX_UNSTALLED status=%d: SDU dropped", event->tx_unstalled.status); + } + xEventGroupSetBits(coc_event_group, TX_UNSTALLED_BIT); + return 0; + + case BLE_L2CAP_EVENT_COC_DATA_RECEIVED: { + struct os_mbuf *sdu_rx; + int rc; + + if (event->receive.sdu_rx) { + os_mbuf_free_chain(event->receive.sdu_rx); + } + sdu_rx = os_mbuf_get_pkthdr(&sdu_os_mbuf_pool, 0); + if (sdu_rx) { + rc = ble_l2cap_recv_ready(event->receive.chan, sdu_rx); + if (rc != 0) { + os_mbuf_free_chain(sdu_rx); + } + } else { + ESP_LOGE(TAG, "DATA_RECEIVED: no mbuf for recv_ready; RX may stall"); + } + return 0; + } + + default: + return 0; + } +} + +static void wait_unstall(uint32_t timeout_ms) +{ + xEventGroupWaitBits(coc_event_group, TX_UNSTALLED_BIT, pdTRUE, pdTRUE, pdMS_TO_TICKS(timeout_ms)); +} + +static void cent_send_task(void *arg) +{ + static uint8_t value[L2CAP_COC_MTU]; + int rc; + + for (int i = 0; i < L2CAP_COC_MTU; i++) { + value[i] = i & 0xFF; + } + + xEventGroupWaitBits(coc_event_group, COC_CONNECTED_BIT, pdFALSE, pdTRUE, portMAX_DELAY); + + ESP_LOGI(TAG, "L2CAP COC Throughput — TX side (central sends to peripheral)"); + ESP_LOGI(TAG, "Number of enabled PHYs: %d", (int)PHY_LIST_LEN); + + if (PHY_LIST_LEN == 0) { + ESP_LOGE(TAG, "No test PHY enabled; enable at least one EXAMPLE_TEST_PHY_* in menuconfig"); + vTaskDelete(NULL); + return; + } + + while (1) { + bool lost_connection = false; + + for (int i = 0; i < PHY_LIST_LEN && !lost_connection; i++) { + const phy_entry_t *phy = &phy_list[i]; + + xEventGroupClearBits(coc_event_group, PHY_UPDATED_BIT); +#if CONFIG_SOC_BLE_50_SUPPORTED + rc = ble_gap_set_prefered_le_phy(conn_handle, + phy->tx_phys, + phy->rx_phys, + phy->phy_opts); + if (rc != 0) { + ESP_LOGE(TAG, "PHY switch to %s failed; rc=%d — continuing anyway", + phy->name, rc); + xEventGroupSetBits(coc_event_group, PHY_UPDATED_BIT); + } +#else + xEventGroupSetBits(coc_event_group, PHY_UPDATED_BIT); +#endif + + EventBits_t bits = xEventGroupWaitBits(coc_event_group, PHY_UPDATED_BIT, + pdTRUE, pdTRUE, + pdMS_TO_TICKS(5000)); + if (!(bits & PHY_UPDATED_BIT)) { + ESP_LOGW(TAG, "PHY update timeout for %s; continuing anyway", phy->name); + } + + if (phy->is_coded_s8) { + /* CI=40ms, CE=32.5-40ms: fits 2 Coded S8 K-frames per CI and prevents credit starvation */ + struct ble_gap_upd_params s8_params = { + .itvl_min = 32, + .itvl_max = 32, + .latency = 0, + .supervision_timeout = 2000, + .min_ce_len = 52, + .max_ce_len = 64, + }; + xEventGroupClearBits(coc_event_group, CONN_UPDATED_BIT); + rc = ble_gap_update_params(conn_handle, &s8_params); + if (rc == 0) { + ESP_LOGI(TAG, "Coded S8: updating CI"); + ci_is_slow = true; + xEventGroupWaitBits(coc_event_group, CONN_UPDATED_BIT, pdTRUE, pdTRUE, + pdMS_TO_TICKS(CONN_PARAM_UPDATE_TIMEOUT_MS)); + } else { + ESP_LOGW(TAG, "S8 CI update failed (rc=%d)", rc); + } + } else if (phy->tx_phys == BLE_HCI_LE_PHY_CODED_PREF_MASK) { + /* CI=20ms, CE=10-20ms: fits 2 Coded S2 K-frames per CI */ + struct ble_gap_upd_params s2_params = { + .itvl_min = 16, + .itvl_max = 16, + .latency = 0, + .supervision_timeout = 2000, + .min_ce_len = 16, + .max_ce_len = 32, + }; + xEventGroupClearBits(coc_event_group, CONN_UPDATED_BIT); + rc = ble_gap_update_params(conn_handle, &s2_params); + if (rc == 0) { + ESP_LOGI(TAG, "Coded S2: updating CI"); + ci_is_slow = true; + xEventGroupWaitBits(coc_event_group, CONN_UPDATED_BIT, pdTRUE, pdTRUE, + pdMS_TO_TICKS(CONN_PARAM_UPDATE_TIMEOUT_MS)); + } else { + ESP_LOGW(TAG, "S2 CI update failed (rc=%d)", rc); + } + } else if (ci_is_slow) { + ci_is_slow = false; + xEventGroupClearBits(coc_event_group, CONN_UPDATED_BIT); + rc = ble_gap_update_params(conn_handle, &conn_params); + if (rc == 0) { + ESP_LOGI(TAG, "%s: restoring CI to 6 ms", phy->name); + xEventGroupWaitBits(coc_event_group, CONN_UPDATED_BIT, pdTRUE, pdTRUE, + pdMS_TO_TICKS(CONN_PARAM_UPDATE_TIMEOUT_MS)); + } else { + ESP_LOGW(TAG, "CI restore failed (rc=%d)", rc); + } + } + + if (chan_stalled) { + ESP_LOGI(TAG, "Pre-drain: waiting for unstall"); + wait_unstall(PREDRAIN_TIMEOUT_MS); + if (chan_stalled) { + ESP_LOGW(TAG, "Pre-drain timed out; forcing clear"); + chan_stalled = false; + } + } + + int64_t start_us = esp_timer_get_time(); + int64_t end_us = start_us + (int64_t)phy->duration_s * 1000000LL; + /* Both the send task and TX_UNSTALLED callback update these counters; a + * lost update is possible on dual-core but harmless for throughput stats. */ + + uint32_t segment_sdus = 0; + uint32_t segment_drops = 0; + + cent_seg_tx_done = &segment_sdus; + cent_seg_tx_drop = &segment_drops; + + xEventGroupClearBits(coc_event_group, TX_UNSTALLED_BIT); /* clear stale signal from previous segment */ + + ESP_LOGI(TAG, "[%s PHY] Sending for %d s", phy->name, phy->duration_s); + + while (!lost_connection && esp_timer_get_time() < end_us) { + /* Snapshot coc_chan — NimBLE host task can NULL it between check and send. */ + struct ble_l2cap_chan *chan = coc_chan; + if (!chan) { + ESP_LOGW(TAG, "COC channel lost during test"); + cent_seg_tx_done = NULL; + cent_seg_tx_drop = NULL; + lost_connection = true; + break; + } + + struct os_mbuf *sdu_tx = os_mbuf_get_pkthdr(&sdu_os_mbuf_pool, 0); + if (!sdu_tx) { + vTaskDelay(1); + continue; + } + + rc = os_mbuf_append(sdu_tx, value, cent_tx_sdu_len); + if (rc != 0) { + os_mbuf_free_chain(sdu_tx); + continue; + } + + rc = ble_l2cap_send(chan, sdu_tx); + + if (rc == 0) { + segment_sdus++; + } else if (rc == BLE_HS_ESTALLED) { + chan_stalled = true; + xEventGroupWaitBits(coc_event_group, TX_UNSTALLED_BIT, pdTRUE, pdTRUE, pdMS_TO_TICKS(100)); + continue; + } else if (rc == BLE_HS_EBUSY) { + os_mbuf_free_chain(sdu_tx); + if (chan_stalled) { + xEventGroupWaitBits(coc_event_group, TX_UNSTALLED_BIT, pdTRUE, pdTRUE, pdMS_TO_TICKS(100)); + } else { + taskYIELD(); + } + continue; + } else if (rc == BLE_HS_ENOMEM) { + vTaskDelay(1); + continue; + } else { + ESP_LOGE(TAG, "Send failed; rc=%d", rc); + if (rc == BLE_HS_EBADDATA) { + os_mbuf_free_chain(sdu_tx); + } + break; + } + + if (segment_sdus % TX_YIELD_INTERVAL == 0 && segment_sdus > 0) { + vTaskDelay(1); + } + } + + if (!coc_chan && !lost_connection) { + cent_seg_tx_done = NULL; + cent_seg_tx_drop = NULL; + lost_connection = true; + } + + if (lost_connection) { + break; + } + + if (chan_stalled) { + ESP_LOGI(TAG, "Post-drain: waiting for unstall"); + wait_unstall(POSTDRAIN_TIMEOUT_MS); + if (chan_stalled) { + ESP_LOGW(TAG, "Post-drain timed out; pre-drain will retry"); + } + } + + int64_t elapsed_us = esp_timer_get_time() - start_us; + if (elapsed_us < 1) { elapsed_us = 1; } + + uint64_t bytes_sent = (uint64_t)segment_sdus * cent_tx_sdu_len; + uint32_t elapsed_ms = (uint32_t)(elapsed_us / 1000); + if (elapsed_ms == 0) { elapsed_ms = 1; } + uint32_t tp_kbps = (uint32_t)((bytes_sent * 8ULL) / elapsed_ms); + uint32_t dropped = segment_drops; + + cent_seg_tx_done = NULL; + cent_seg_tx_drop = NULL; + + ESP_LOGI(TAG, "+-------------------------------------------------+"); + ESP_LOGI(TAG, "| PHY : %-39s|", phy->name); + ESP_LOGI(TAG, "| TX : %-6" PRIu32 " kbps |", tp_kbps); + ESP_LOGI(TAG, "| Bytes : %-10" PRIu64 " |", bytes_sent); + ESP_LOGI(TAG, "| Time : %-5" PRIu32 " s |", elapsed_ms / 1000); + ESP_LOGI(TAG, "+-------------------------------------------------+"); + if (dropped > 0) { + ESP_LOGW(TAG, "%" PRIu32 " SDUs dropped (ENOMEM); raise BT_NIMBLE_MSYS_1_BLOCK_COUNT", + dropped); + } + } + + if (lost_connection) { + ESP_LOGI(TAG, "Waiting for L2CAP COC reconnection..."); + /* disconnect handler already cleared COC_CONNECTED_BIT; clearing it + * again here could cancel a bit set by a reconnect that raced ahead. */ + xEventGroupWaitBits(coc_event_group, COC_CONNECTED_BIT, pdFALSE, pdTRUE, portMAX_DELAY); + } else { + ESP_LOGI(TAG, "Cycle complete. Looping back to first PHY..."); + } + } +} + +static int cent_gap_event(struct ble_gap_event *event, void *arg) +{ + int rc; + + switch (event->type) { + case BLE_GAP_EVENT_DISC: + cent_connect_if_interesting(&event->disc); + return 0; + +#if CONFIG_EXAMPLE_EXTENDED_ADV + case BLE_GAP_EVENT_EXT_DISC: + cent_connect_if_interesting_ext(&event->ext_disc); + return 0; +#endif + + case BLE_GAP_EVENT_CONNECT: + if (event->connect.status == 0) { + struct ble_gap_conn_desc desc; + if (ble_gap_conn_find(event->connect.conn_handle, &desc) == 0) { + ESP_LOGI(TAG, "Connected; handle=%d peer=%02x:%02x:%02x:%02x:%02x:%02x", + event->connect.conn_handle, + desc.peer_id_addr.val[5], desc.peer_id_addr.val[4], + desc.peer_id_addr.val[3], desc.peer_id_addr.val[2], + desc.peer_id_addr.val[1], desc.peer_id_addr.val[0]); + } + + conn_handle = event->connect.conn_handle; + l2cap_connecting = false; + + rc = ble_hs_hci_util_set_data_len(conn_handle, + LL_PACKET_LENGTH, LL_PACKET_TIME); + if (rc != 0) { + /* DATA_LEN_CHG won't fire — connect L2CAP directly as fallback */ + ESP_LOGE(TAG, "Set packet length failed; rc=%d, connecting L2CAP directly", rc); + l2cap_connecting = true; + cent_l2cap_coc_connect(conn_handle); + } else { + /* DLE accepted; connect L2CAP now — DATA_LEN_CHG may not fire if + * the peer's data length is already at the requested value */ + l2cap_connecting = true; + cent_l2cap_coc_connect(conn_handle); + } + } else { + ESP_LOGE(TAG, "Connection failed; status=%d", event->connect.status); + cent_scan(); + } + return 0; + + case BLE_GAP_EVENT_DISCONNECT: + ESP_LOGI(TAG, "Disconnected; reason=%d", event->disconnect.reason); + conn_handle = BLE_HS_CONN_HANDLE_NONE; + coc_chan = NULL; + chan_stalled = false; + ci_is_slow = false; + l2cap_connecting = false; + xEventGroupClearBits(coc_event_group, COC_CONNECTED_BIT); + xEventGroupSetBits(coc_event_group, TX_UNSTALLED_BIT | CONN_UPDATED_BIT | PHY_UPDATED_BIT); + cent_scan(); + return 0; + + case BLE_GAP_EVENT_PHY_UPDATE_COMPLETE: + ESP_LOGI(TAG, "PHY updated: tx=%d rx=%d status=%d", + event->phy_updated.tx_phy, + event->phy_updated.rx_phy, + event->phy_updated.status); + if (event->phy_updated.status != 0) { + ESP_LOGW(TAG, "PHY update failed; status=%d", event->phy_updated.status); + } + xEventGroupSetBits(coc_event_group, PHY_UPDATED_BIT); + return 0; + + case BLE_GAP_EVENT_CONN_UPDATE: + ESP_LOGI(TAG, "Conn params updated; status=%d", event->conn_update.status); + if (event->conn_update.status != 0) { + ESP_LOGW(TAG, "Connection parameter update failed (status=%d)", + event->conn_update.status); + } + xEventGroupSetBits(coc_event_group, CONN_UPDATED_BIT); + return 0; + + case BLE_GAP_EVENT_DATA_LEN_CHG: + /* fires for TX and RX; guard ensures connect called only once */ + if (!l2cap_connecting && coc_chan == NULL) { + l2cap_connecting = true; + cent_l2cap_coc_connect(conn_handle); + } + return 0; + + case BLE_GAP_EVENT_DISC_COMPLETE: + ESP_LOGI(TAG, "Discovery complete; reason=%d", event->disc_complete.reason); + return 0; + + default: + return 0; + } +} + +static void cent_on_reset(int reason) +{ + ESP_LOGE(TAG, "Host reset; reason=%d", reason); +} + +static void cent_on_sync(void) +{ + int rc = ble_hs_util_ensure_addr(0); + assert(rc == 0); + + uint8_t own_addr_type; + uint8_t addr[6] = {0}; + + rc = ble_hs_id_infer_auto(0, &own_addr_type); + if (rc != 0) { + ESP_LOGE(TAG, "Error inferring addr type; rc=%d", rc); + return; + } + ble_hs_id_copy_addr(own_addr_type, addr, NULL); + ESP_LOGI(TAG, "Device Address: %02x:%02x:%02x:%02x:%02x:%02x", + addr[5], addr[4], addr[3], addr[2], addr[1], addr[0]); + + cent_scan(); +} + +static void cent_host_task(void *param) +{ + ESP_LOGI(TAG, "BLE Host Task started"); + nimble_port_run(); + nimble_port_freertos_deinit(); +} + +void app_main(void) +{ + esp_err_t ret = nvs_flash_init(); + if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) { + ESP_ERROR_CHECK(nvs_flash_erase()); + ret = nvs_flash_init(); + } + ESP_ERROR_CHECK(ret); + + coc_event_group = xEventGroupCreate(); + assert(coc_event_group); + + ret = nimble_port_init(); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "nimble_port_init failed; rc=%d", ret); + return; + } + + cent_l2cap_coc_mem_init(); + + ble_hs_cfg.reset_cb = cent_on_reset; + ble_hs_cfg.sync_cb = cent_on_sync; + ble_hs_cfg.store_status_cb = ble_store_util_status_rr; + +#if CONFIG_BT_NIMBLE_GAP_SERVICE + int rc = ble_svc_gap_device_name_set("l2cap-coc-cent"); + assert(rc == 0); +#endif + + ble_store_config_init(); + + if (xTaskCreate(cent_send_task, "cent_send_task", 4096, NULL, 5, NULL) != pdPASS) { + ESP_LOGE(TAG, "Failed to create cent_send_task"); + return; + } + + nimble_port_freertos_init(cent_host_task); +} diff --git a/examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_cent/sdkconfig.defaults b/examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_cent/sdkconfig.defaults new file mode 100644 index 00000000000..484df7d544b --- /dev/null +++ b/examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_cent/sdkconfig.defaults @@ -0,0 +1,16 @@ +CONFIG_BT_ENABLED=y +CONFIG_BT_NIMBLE_ENABLED=y +CONFIG_BT_NIMBLE_ATT_PREFERRED_MTU=512 +CONFIG_BT_NIMBLE_TRANSPORT_EVT_SIZE=255 +CONFIG_BT_NIMBLE_LOG_LEVEL=4 +CONFIG_BT_NIMBLE_LOG_LEVEL_NONE=y +CONFIG_BT_NIMBLE_MSYS_1_BLOCK_COUNT=400 +CONFIG_BT_NIMBLE_MSYS_1_BLOCK_SIZE=255 +CONFIG_BT_NIMBLE_MSYS_2_BLOCK_COUNT=50 +CONFIG_BT_NIMBLE_MSYS_2_BLOCK_SIZE=260 +CONFIG_BT_NIMBLE_L2CAP_COC_MAX_NUM=1 +CONFIG_BT_NIMBLE_L2CAP_COC_SDU_BUFF_COUNT=12 +CONFIG_EXAMPLE_L2CAP_COC_MTU=2048 +CONFIG_BT_NIMBLE_TRANSPORT_ACL_FROM_LL_COUNT=67 +CONFIG_FREERTOS_HZ=1000 +CONFIG_ESP_TASK_WDT_TIMEOUT_S=30 diff --git a/examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_cent/sdkconfig.defaults.esp32 b/examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_cent/sdkconfig.defaults.esp32 new file mode 100644 index 00000000000..d11249b3f31 --- /dev/null +++ b/examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_cent/sdkconfig.defaults.esp32 @@ -0,0 +1,4 @@ +CONFIG_EXAMPLE_L2CAP_COC_MTU=2048 +CONFIG_BT_NIMBLE_MSYS_1_BLOCK_COUNT=96 +CONFIG_BT_NIMBLE_MSYS_2_BLOCK_COUNT=48 +CONFIG_BT_NIMBLE_TRANSPORT_ACL_FROM_LL_COUNT=10 diff --git a/examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_cent/sdkconfig.defaults.esp32c2 b/examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_cent/sdkconfig.defaults.esp32c2 new file mode 100644 index 00000000000..7afcaef3d97 --- /dev/null +++ b/examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_cent/sdkconfig.defaults.esp32c2 @@ -0,0 +1,6 @@ +# ACL_FROM_LL_COUNT kept at 67 (not reduced like MSYS pools) to avoid 0 kbps +# throughput after Coded S2 <-> S8 PHY switches on this RAM-limited target. +CONFIG_BT_NIMBLE_MSYS_1_BLOCK_COUNT=12 +CONFIG_BT_NIMBLE_MSYS_1_BLOCK_SIZE=292 +CONFIG_BT_NIMBLE_MSYS_2_BLOCK_COUNT=0 +CONFIG_BT_NIMBLE_TRANSPORT_ACL_FROM_LL_COUNT=67 diff --git a/examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_cent/sdkconfig.defaults.esp32c3 b/examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_cent/sdkconfig.defaults.esp32c3 new file mode 100644 index 00000000000..8fd238fb854 --- /dev/null +++ b/examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_cent/sdkconfig.defaults.esp32c3 @@ -0,0 +1,4 @@ +CONFIG_BT_NIMBLE_MSYS_1_BLOCK_COUNT=160 +CONFIG_BT_NIMBLE_MSYS_2_BLOCK_COUNT=48 +CONFIG_BT_NIMBLE_TRANSPORT_ACL_FROM_LL_COUNT=24 +CONFIG_BT_CTRL_BLE_STATIC_ACL_TX_BUF_NB=8 diff --git a/examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_cent/sdkconfig.defaults.esp32c6 b/examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_cent/sdkconfig.defaults.esp32c6 new file mode 100644 index 00000000000..589bf21d601 --- /dev/null +++ b/examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_cent/sdkconfig.defaults.esp32c6 @@ -0,0 +1,3 @@ +CONFIG_BT_NIMBLE_MSYS_1_BLOCK_COUNT=120 +CONFIG_BT_NIMBLE_MSYS_2_BLOCK_COUNT=48 +CONFIG_BT_NIMBLE_TRANSPORT_ACL_FROM_LL_COUNT=67 diff --git a/examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_cent/sdkconfig.defaults.esp32h2 b/examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_cent/sdkconfig.defaults.esp32h2 new file mode 100644 index 00000000000..589bf21d601 --- /dev/null +++ b/examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_cent/sdkconfig.defaults.esp32h2 @@ -0,0 +1,3 @@ +CONFIG_BT_NIMBLE_MSYS_1_BLOCK_COUNT=120 +CONFIG_BT_NIMBLE_MSYS_2_BLOCK_COUNT=48 +CONFIG_BT_NIMBLE_TRANSPORT_ACL_FROM_LL_COUNT=67 diff --git a/examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_prph/CMakeLists.txt b/examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_prph/CMakeLists.txt new file mode 100644 index 00000000000..c4181070e58 --- /dev/null +++ b/examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_prph/CMakeLists.txt @@ -0,0 +1,5 @@ +cmake_minimum_required(VERSION 3.22) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +idf_build_set_property(MINIMAL_BUILD ON) +project(l2cap_coc_prph) diff --git a/examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_prph/README.md b/examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_prph/README.md new file mode 100644 index 00000000000..461d4c07058 --- /dev/null +++ b/examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_prph/README.md @@ -0,0 +1,77 @@ +| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C5 | ESP32-C6 | ESP32-C61 | ESP32-H2 | ESP32-S3 | +| ----------------- | ----- | -------- | -------- | -------- | -------- | --------- | -------- | -------- | + +# L2CAP COC Throughput Peripheral Example + +`l2cap_coc_prph` demonstrates the peripheral side of an L2CAP Connection-Oriented Channel (COC) throughput test using NimBLE on ESP32. It advertises with UUID 0x1812, accepts an incoming GAP connection from `l2cap_coc_cent`, registers an L2CAP COC server on PSM 0x1002, and measures RX throughput as the central sends SDUs. + +The peripheral tracks throughput per PHY — each time the central switches PHY, the peripheral prints a per-PHY throughput summary box and resets its counters. A background stats task also prints a live per-second RX rate while data is flowing. It must be used together with the `l2cap_coc_cent` example which acts as the sending side. + +It uses ESP32's Bluetooth controller and NimBLE stack based BLE host. + +## How to Use Example + +Before project configuration and build, be sure to set the correct chip target using: + +```bash +idf.py set-target +``` + +### Hardware Required + +* Two development boards, one flashed with `l2cap_coc_prph` and the other with `l2cap_coc_cent`. +* A USB cable for power supply and programming. + +See [Development Boards](https://www.espressif.com/en/products/devkits) for more information. + +### Configure the Project + +Open the project configuration menu: + +```bash +idf.py menuconfig +``` + +In the `L2CAP COC Throughput Configuration` menu: + +| Option | Default | Description | +|---------|---------|-------------| +| `EXAMPLE_L2CAP_COC_MTU` | `16384` | Peripheral L2CAP CoC SDU MTU size in bytes. | +| `EXAMPLE_EXTENDED_ADV` | `y` (BLE 5.0 chips) | Enable extended advertising for BLE 5.0 capable devices. Required for Coded PHY testing on ESP32-C6 and ESP32-H2. | + +> **Note:** Throughput in the central → peripheral direction is primarily determined by the peripheral MTU. With the default configuration (`MTU=16384`, `MPS=247`), NimBLE grants approximately 67 initial credits to the central sender, allowing multiple packets to remain in flight and maximizing link throughput. + +### Build and Flash + +Run `idf.py -p PORT flash monitor` to build, flash and monitor the project. + +(To exit the serial monitor, type ``Ctrl-]``.) + +See the [Getting Started Guide](https://idf.espressif.com/) for full steps to configure and use ESP-IDF to build projects. + +## Example Output + +On successful connection and data reception, the peripheral prints a live per-second RX rate while data flows: + +``` +I (xxx) l2cap_coc_prph: BLE Host Task started +I (xxx) l2cap_coc_prph: Device Address: xx:xx:xx:xx:xx:xx +I (xxx) l2cap_coc_prph: Extended advertising started +I (xxx) l2cap_coc_prph: Connected; handle=0 +I (xxx) l2cap_coc_prph: L2CAP COC connected, chan=0xxxxxxxxx +I (xxx) l2cap_coc_prph: PHY updated: tx=2 rx=2 status=0 +I (xxx) l2cap_coc_prph: | RX : xxxx kbps | +I (xxx) l2cap_coc_prph: | RX : xxxx kbps | +I (xxx) l2cap_coc_prph: | RX : xxxx kbps | +I (xxx) l2cap_coc_prph: | RX : xxxx kbps | +I (xxx) l2cap_coc_prph: | RX : xxxx kbps | +I (xxx) l2cap_coc_prph: | RX : xxxx kbps | +I (xxx) l2cap_coc_prph: | RX : xxxx kbps | +I (xxx) l2cap_coc_prph: | RX : xxxx kbps | +``` + +> **Note:** The peripheral prints one RX line per second. The central controls PHY selection and test duration; the peripheral tracks and displays throughput continuously as long as data is flowing. + +## Troubleshooting + +For any technical queries, please open an [issue](https://github.com/espressif/esp-idf/issues) on GitHub. We will get back to you soon. diff --git a/examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_prph/main/CMakeLists.txt b/examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_prph/main/CMakeLists.txt new file mode 100644 index 00000000000..718f89cab8e --- /dev/null +++ b/examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_prph/main/CMakeLists.txt @@ -0,0 +1,3 @@ +idf_component_register(SRCS "main.c" + PRIV_REQUIRES bt nvs_flash esp_timer + INCLUDE_DIRS ".") diff --git a/examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_prph/main/Kconfig.projbuild b/examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_prph/main/Kconfig.projbuild new file mode 100644 index 00000000000..ee49e5894fd --- /dev/null +++ b/examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_prph/main/Kconfig.projbuild @@ -0,0 +1,23 @@ +menu "L2CAP COC Throughput Configuration" + + config EXAMPLE_L2CAP_COC_MTU + int "L2CAP CoC MTU size in bytes" + default 16384 + range 512 65511 + help + L2CAP CoC SDU MTU size in bytes. + Note: memory pool allocates 6 buffers of this size; total pool + memory = MTU x 6. On chips without PSRAM ensure sufficient heap + is available before increasing this value. + Use idf.py size-components to verify. + + config EXAMPLE_EXTENDED_ADV + bool + depends on SOC_BLE_50_SUPPORTED && BT_NIMBLE_50_FEATURE_SUPPORT + default y if SOC_ESP_NIMBLE_CONTROLLER + select BT_NIMBLE_EXT_ADV + prompt "Enable Extended Advertising" + help + Enable BLE 5.0 extended advertising. + +endmenu diff --git a/examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_prph/main/main.c b/examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_prph/main/main.c new file mode 100644 index 00000000000..99767c15b84 --- /dev/null +++ b/examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_prph/main/main.c @@ -0,0 +1,445 @@ +/* + * SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include "esp_log.h" +#include "nvs_flash.h" +#include "esp_timer.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "nimble/nimble_port.h" +#include "nimble/nimble_port_freertos.h" +#include "host/ble_hs.h" +#include "host/util/util.h" +#include "services/gap/ble_svc_gap.h" + +static const char *TAG = "l2cap_coc_prph"; + +#define L2CAP_COC_PSM 0x1002 +#define L2CAP_COC_MTU CONFIG_EXAMPLE_L2CAP_COC_MTU +#define COC_BUF_COUNT (6 * MYNEWT_VAL(BLE_L2CAP_COC_MAX_NUM)) +/* Block size must include mbuf headers so each SDU fits in one pool entry. */ +#define SDU_BLOCK_SIZE (L2CAP_COC_MTU + sizeof(struct os_mbuf_pkthdr) + sizeof(struct os_mbuf)) +#define LL_PACKET_LENGTH 251 +#define LL_PACKET_TIME 2120 +#define L2CAP_COC_UUID 0x1812 + +static uint16_t conn_handle = BLE_HS_CONN_HANDLE_NONE; +static struct ble_l2cap_chan *coc_chan = NULL; +static uint8_t own_addr_type; + +static int64_t phy_start_time = 0; +static volatile uint32_t rx_bytes = 0; +static uint32_t rx_packets = 0; +static volatile bool coc_active = false; +static const char *phy_name = "1M"; +static uint8_t current_phy = BLE_HCI_LE_PHY_1M; + +void ble_store_config_init(void); + +static os_membuf_t sdu_coc_mem[OS_MEMPOOL_SIZE(COC_BUF_COUNT, SDU_BLOCK_SIZE)]; +static struct os_mempool sdu_coc_mempool; +static struct os_mbuf_pool sdu_os_mbuf_pool; + +static int prph_gap_event(struct ble_gap_event *event, void *arg); + +static const char *prph_phy_str(uint8_t phy) +{ + switch (phy) { + case BLE_HCI_LE_PHY_2M: return "2M"; + case BLE_HCI_LE_PHY_CODED: return "Coded"; + default: return "1M"; + } +} + +static void prph_report_phy(int64_t end_time, int64_t start_time, + uint32_t bytes, uint32_t packets, + const char *phy_name) +{ + if (packets == 0 || start_time == 0) { + return; + } + int64_t elapsed_ms = (end_time - start_time) / 1000; + if (elapsed_ms == 0) { elapsed_ms = 1; } + uint32_t kbps = (uint32_t)((uint64_t)bytes * 8ULL + / (uint64_t)elapsed_ms); + ESP_LOGI(TAG, "+-------------------------------------------------+"); + ESP_LOGI(TAG, "| PHY : %-39s|", phy_name); + ESP_LOGI(TAG, "| RX : %-6" PRIu32 " kbps |", kbps); + ESP_LOGI(TAG, "| Bytes : %-10" PRIu32 " |", bytes); + ESP_LOGI(TAG, "| Time : %-5lld s |", elapsed_ms / 1000); + ESP_LOGI(TAG, "+-------------------------------------------------+"); +} + +#if CONFIG_EXAMPLE_EXTENDED_ADV +static uint8_t ext_adv_pattern[] = { + 0x02, BLE_HS_ADV_TYPE_FLAGS, 0x06, + 0x03, BLE_HS_ADV_TYPE_COMP_UUIDS16, 0x12, 0x18, + 0x11, BLE_HS_ADV_TYPE_COMP_NAME, + 'l','2','c','a','p','-','c','o','c','-','p','r','p','h','-','e', +}; + +static void prph_advertise(void) +{ + struct ble_gap_ext_adv_params params; + struct os_mbuf *data; + uint8_t instance = 0; + int rc; + + memset(¶ms, 0, sizeof(params)); + params.connectable = 1; + params.own_addr_type = own_addr_type; + params.primary_phy = BLE_HCI_LE_PHY_1M; + params.secondary_phy = BLE_HCI_LE_PHY_1M; + params.tx_power = 127; + params.sid = 1; + params.itvl_min = BLE_GAP_ADV_FAST_INTERVAL1_MIN; + params.itvl_max = BLE_GAP_ADV_FAST_INTERVAL1_MIN; + + rc = ble_gap_ext_adv_configure(instance, ¶ms, NULL, prph_gap_event, NULL); + if (rc != 0) { + ESP_LOGE(TAG, "ext_adv_configure failed; rc=%d", rc); + return; + } + + data = os_msys_get_pkthdr(sizeof(ext_adv_pattern), 0); + if (!data) { + ESP_LOGE(TAG, "ext_adv: failed to alloc adv data mbuf"); + return; + } + rc = os_mbuf_append(data, ext_adv_pattern, sizeof(ext_adv_pattern)); + if (rc != 0) { + ESP_LOGE(TAG, "ext_adv: mbuf_append failed; rc=%d", rc); + os_mbuf_free_chain(data); + return; + } + + rc = ble_gap_ext_adv_set_data(instance, data); + if (rc != 0) { + ESP_LOGE(TAG, "ext_adv_set_data failed; rc=%d", rc); + return; + } + + rc = ble_gap_ext_adv_start(instance, 0, 0); + if (rc != 0) { + ESP_LOGE(TAG, "ext_adv_start failed; rc=%d", rc); + return; + } + ESP_LOGI(TAG, "Extended advertising started"); +} +#else +static void prph_advertise(void) +{ + struct ble_gap_adv_params adv_params; + struct ble_hs_adv_fields fields; + int rc; + + memset(&fields, 0, sizeof(fields)); + fields.flags = BLE_HS_ADV_F_DISC_GEN | BLE_HS_ADV_F_BREDR_UNSUP; + fields.tx_pwr_lvl_is_present = 1; + fields.tx_pwr_lvl = BLE_HS_ADV_TX_PWR_LVL_AUTO; +#if CONFIG_BT_NIMBLE_GAP_SERVICE + const char *name = ble_svc_gap_device_name(); + fields.name = (uint8_t *)name; + fields.name_len = strlen(name); + fields.name_is_complete = 1; +#endif + fields.uuids16 = (ble_uuid16_t[]){ BLE_UUID16_INIT(L2CAP_COC_UUID) }; + fields.num_uuids16 = 1; + fields.uuids16_is_complete = 1; + + rc = ble_gap_adv_set_fields(&fields); + if (rc != 0) { + ESP_LOGE(TAG, "Error setting adv data; rc=%d", rc); + return; + } + + memset(&adv_params, 0, sizeof(adv_params)); + adv_params.conn_mode = BLE_GAP_CONN_MODE_UND; + adv_params.disc_mode = BLE_GAP_DISC_MODE_GEN; + rc = ble_gap_adv_start(own_addr_type, NULL, BLE_HS_FOREVER, + &adv_params, prph_gap_event, NULL); + if (rc != 0) { + ESP_LOGE(TAG, "Error starting adv; rc=%d", rc); + } +} +#endif /* CONFIG_EXAMPLE_EXTENDED_ADV */ + +static void prph_l2cap_coc_mem_init(void) +{ + int rc; + rc = os_mempool_init(&sdu_coc_mempool, COC_BUF_COUNT, SDU_BLOCK_SIZE, + sdu_coc_mem, "prph_coc_pool"); + assert(rc == 0); + rc = os_mbuf_pool_init(&sdu_os_mbuf_pool, &sdu_coc_mempool, SDU_BLOCK_SIZE, + COC_BUF_COUNT); + assert(rc == 0); +} + +static int prph_l2cap_coc_accept(struct ble_l2cap_chan *chan) +{ + struct os_mbuf *sdu_rx = os_mbuf_get_pkthdr(&sdu_os_mbuf_pool, 0); + if (!sdu_rx) { + return BLE_HS_ENOMEM; + } + int rc = ble_l2cap_recv_ready(chan, sdu_rx); + if (rc != 0) { + os_mbuf_free_chain(sdu_rx); + } + return rc; +} + +static int prph_l2cap_coc_event_cb(struct ble_l2cap_event *event, void *arg) +{ + switch (event->type) { + case BLE_L2CAP_EVENT_COC_CONNECTED: + if (event->connect.status != 0) { + ESP_LOGE(TAG, "L2CAP COC connect error: %d", event->connect.status); + return 0; + } + ESP_LOGI(TAG, "L2CAP COC connected, chan=%p", event->connect.chan); + coc_chan = event->connect.chan; + phy_start_time = 0; /* anchored on first data SDU, not connect */ + rx_bytes = 0; + rx_packets = 0; + coc_active = true; + phy_name = prph_phy_str(current_phy); + return 0; + + case BLE_L2CAP_EVENT_COC_DISCONNECTED: + coc_active = false; + coc_chan = NULL; + current_phy = BLE_HCI_LE_PHY_1M; + { + int64_t end = esp_timer_get_time(); + int64_t st = phy_start_time; + uint32_t by = rx_bytes; + uint32_t pk = rx_packets; + prph_report_phy(end, st, by, pk, phy_name); + rx_bytes = 0; rx_packets = 0; phy_start_time = 0; + } + ESP_LOGI(TAG, "L2CAP COC disconnected"); + return 0; + + case BLE_L2CAP_EVENT_COC_ACCEPT: { + /* Pre-grant 2 receive buffers so the central can pipeline 2 SDUs. */ + int rc = prph_l2cap_coc_accept(event->accept.chan); + if (rc != 0) { + return rc; + } + /* Second buffer is best-effort; one buffer is enough for the channel to operate. */ + if (prph_l2cap_coc_accept(event->accept.chan) != 0) { + ESP_LOGW(TAG, "L2CAP COC accept: second RX buffer unavailable, running with one"); + } + return 0; + } + + case BLE_L2CAP_EVENT_COC_DATA_RECEIVED: + if (event->receive.sdu_rx) { + if (rx_packets == 0) { + phy_start_time = esp_timer_get_time(); + } + rx_bytes += OS_MBUF_PKTLEN(event->receive.sdu_rx); + rx_packets += 1; + os_mbuf_free_chain(event->receive.sdu_rx); + } + if (prph_l2cap_coc_accept(event->receive.chan) != 0) { + ESP_LOGE(TAG, "DATA_RECEIVED: no mbuf for recv_ready; RX may stall"); + } + return 0; + + default: + return 0; + } +} + +static void prph_stats_task(void *arg) +{ + uint32_t prev_bytes = 0; + int64_t prev_time = 0; + + while (1) { + vTaskDelay(pdMS_TO_TICKS(1000)); + + if (!coc_active) { + prev_bytes = 0; + prev_time = 0; + continue; + } + + int64_t now = esp_timer_get_time(); + uint32_t bytes = rx_bytes; + + if (prev_time > 0) { + if (bytes < prev_bytes) { + prev_bytes = bytes; + prev_time = now; + continue; + } + int64_t dt_us = now - prev_time; + uint32_t dt_bytes = bytes - prev_bytes; + uint32_t kbps = (uint32_t)((uint64_t)dt_bytes * 8ULL * 1000000ULL + / (uint64_t)dt_us / 1000ULL); + ESP_LOGI(TAG, "| RX : %-6" PRIu32 " kbps |", kbps); + } + + prev_bytes = bytes; + prev_time = now; + } +} + +static int prph_gap_event(struct ble_gap_event *event, void *arg) +{ + switch (event->type) { + case BLE_GAP_EVENT_CONNECT: + if (event->connect.status != 0) { + ESP_LOGE(TAG, "Connection failed; status=%d", event->connect.status); + prph_advertise(); + return 0; + } + ESP_LOGI(TAG, "Connected; handle=%d", event->connect.conn_handle); + conn_handle = event->connect.conn_handle; + return 0; + + case BLE_GAP_EVENT_DISCONNECT: + ESP_LOGI(TAG, "Disconnected; reason=%d", event->disconnect.reason); + conn_handle = BLE_HS_CONN_HANDLE_NONE; + coc_chan = NULL; + coc_active = false; + current_phy = BLE_HCI_LE_PHY_1M; + phy_name = "1M"; +#if CONFIG_EXAMPLE_EXTENDED_ADV + ble_gap_ext_adv_stop(0); +#endif + prph_advertise(); + return 0; + + case BLE_GAP_EVENT_PHY_UPDATE_COMPLETE: + ESP_LOGI(TAG, "PHY updated: tx=%d rx=%d status=%d", + event->phy_updated.tx_phy, + event->phy_updated.rx_phy, + event->phy_updated.status); + if (event->phy_updated.status == 0) { + if (coc_active) { + int64_t end = esp_timer_get_time(); + int64_t st = phy_start_time; + uint32_t by = rx_bytes; + uint32_t pk = rx_packets; + prph_report_phy(end, st, by, pk, phy_name); + rx_bytes = 0; rx_packets = 0; phy_start_time = 0; + } + current_phy = event->phy_updated.rx_phy; + phy_name = prph_phy_str(event->phy_updated.rx_phy); + } + return 0; + + case BLE_GAP_EVENT_CONN_UPDATE: + ESP_LOGI(TAG, "Conn params updated; status=%d", event->conn_update.status); + if (event->conn_update.status == 0 && coc_active && + current_phy == BLE_HCI_LE_PHY_CODED) { + struct ble_gap_conn_desc desc; + if (ble_gap_conn_find(conn_handle, &desc) == 0) { + if (desc.conn_itvl != 16 && desc.conn_itvl != 32) { + return 0; + } + if (strcmp(phy_name, "Coded") != 0) { + int64_t end = esp_timer_get_time(); + uint32_t by = rx_bytes; + uint32_t pk = rx_packets; + prph_report_phy(end, phy_start_time, by, pk, phy_name); + rx_bytes = 0; rx_packets = 0; phy_start_time = 0; + } + phy_name = (desc.conn_itvl >= 32) ? "Coded S8" : "Coded S2"; + ESP_LOGI(TAG, "Coding scheme updated to %s (CI=%u × 1.25ms)", + phy_name, desc.conn_itvl); + } + } + return 0; + + case BLE_GAP_EVENT_ADV_COMPLETE: +#if !CONFIG_EXAMPLE_EXTENDED_ADV + prph_advertise(); +#endif + return 0; + + default: + return 0; + } +} + +static void prph_on_reset(int reason) +{ + ESP_LOGE(TAG, "Host reset; reason=%d", reason); +} + +static void prph_on_sync(void) +{ + int rc; + + rc = ble_hs_util_ensure_addr(0); + assert(rc == 0); + + rc = ble_hs_id_infer_auto(0, &own_addr_type); + assert(rc == 0); + + rc = ble_l2cap_create_server(L2CAP_COC_PSM, L2CAP_COC_MTU, + prph_l2cap_coc_event_cb, NULL); + if (rc != 0 && rc != BLE_HS_EALREADY) { + ESP_LOGE(TAG, "Failed to create L2CAP COC server; rc=%d", rc); + return; + } + + uint8_t addr[6] = {0}; + ble_hs_id_copy_addr(own_addr_type, addr, NULL); + ESP_LOGI(TAG, "Device Address: %02x:%02x:%02x:%02x:%02x:%02x", + addr[5], addr[4], addr[3], addr[2], addr[1], addr[0]); + + prph_advertise(); +} + +static void prph_host_task(void *param) +{ + ESP_LOGI(TAG, "BLE Host Task started"); + nimble_port_run(); + nimble_port_freertos_deinit(); +} + +void app_main(void) +{ + esp_err_t ret = nvs_flash_init(); + if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) { + ESP_ERROR_CHECK(nvs_flash_erase()); + ret = nvs_flash_init(); + } + ESP_ERROR_CHECK(ret); + + ret = nimble_port_init(); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "nimble_port_init failed; rc=%d", ret); + return; + } + + prph_l2cap_coc_mem_init(); + + ble_hs_cfg.reset_cb = prph_on_reset; + ble_hs_cfg.sync_cb = prph_on_sync; + ble_hs_cfg.store_status_cb = ble_store_util_status_rr; + +#if CONFIG_BT_NIMBLE_GAP_SERVICE + int rc = ble_svc_gap_device_name_set("l2cap-coc-prph"); + assert(rc == 0); +#endif + + ble_store_config_init(); + + if (xTaskCreate(prph_stats_task, "prph_stats", 4096, NULL, 5, NULL) != pdPASS) { + ESP_LOGE(TAG, "Failed to create stats task"); + } + + nimble_port_freertos_init(prph_host_task); +} diff --git a/examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_prph/sdkconfig.defaults b/examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_prph/sdkconfig.defaults new file mode 100644 index 00000000000..441f5c6c3a2 --- /dev/null +++ b/examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_prph/sdkconfig.defaults @@ -0,0 +1,15 @@ +CONFIG_BT_ENABLED=y +CONFIG_BT_NIMBLE_ENABLED=y +CONFIG_BT_NIMBLE_ATT_PREFERRED_MTU=512 +CONFIG_BT_NIMBLE_TRANSPORT_EVT_SIZE=255 +CONFIG_BT_NIMBLE_LOG_LEVEL=4 +CONFIG_BT_NIMBLE_LOG_LEVEL_NONE=y +CONFIG_BT_NIMBLE_MSYS_1_BLOCK_COUNT=400 +CONFIG_BT_NIMBLE_MSYS_1_BLOCK_SIZE=255 +CONFIG_BT_NIMBLE_MSYS_2_BLOCK_COUNT=50 +CONFIG_BT_NIMBLE_MSYS_2_BLOCK_SIZE=260 +CONFIG_BT_NIMBLE_L2CAP_COC_MAX_NUM=1 +CONFIG_BT_NIMBLE_L2CAP_COC_SDU_BUFF_COUNT=12 +CONFIG_BT_NIMBLE_TRANSPORT_ACL_FROM_LL_COUNT=24 +CONFIG_FREERTOS_HZ=1000 +CONFIG_ESP_TASK_WDT_TIMEOUT_S=30 diff --git a/examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_prph/sdkconfig.defaults.esp32 b/examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_prph/sdkconfig.defaults.esp32 new file mode 100644 index 00000000000..796c5e2681c --- /dev/null +++ b/examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_prph/sdkconfig.defaults.esp32 @@ -0,0 +1,4 @@ +CONFIG_EXAMPLE_L2CAP_COC_MTU=8192 +CONFIG_BT_NIMBLE_MSYS_1_BLOCK_COUNT=20 +CONFIG_BT_NIMBLE_MSYS_2_BLOCK_COUNT=20 +CONFIG_BT_NIMBLE_TRANSPORT_ACL_FROM_LL_COUNT=10 diff --git a/examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_prph/sdkconfig.defaults.esp32c2 b/examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_prph/sdkconfig.defaults.esp32c2 new file mode 100644 index 00000000000..3d6eecb81ac --- /dev/null +++ b/examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_prph/sdkconfig.defaults.esp32c2 @@ -0,0 +1,7 @@ +# MTU is intentionally left at the Kconfig default (16384) for throughput. +# Reducing to 2048 cuts credits from ~67 to ~9, dropping throughput ~8x. +# If the build fails due to RAM pressure (~96 KB static SDU pool), lower MTU here. +CONFIG_BT_NIMBLE_MSYS_1_BLOCK_COUNT=12 +CONFIG_BT_NIMBLE_MSYS_1_BLOCK_SIZE=292 +CONFIG_BT_NIMBLE_MSYS_2_BLOCK_COUNT=0 +CONFIG_BT_NIMBLE_TRANSPORT_ACL_FROM_LL_COUNT=24 diff --git a/examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_prph/sdkconfig.defaults.esp32c3 b/examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_prph/sdkconfig.defaults.esp32c3 new file mode 100644 index 00000000000..77211f1473e --- /dev/null +++ b/examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_prph/sdkconfig.defaults.esp32c3 @@ -0,0 +1,6 @@ +# MTU is intentionally left at the Kconfig default (16384) for throughput. +# Reducing MTU lowers L2CAP credit flow, which directly cuts throughput. +# If the build fails due to RAM pressure (~98 KB static SDU pool), lower MTU here. +CONFIG_BT_NIMBLE_MSYS_1_BLOCK_COUNT=20 +CONFIG_BT_NIMBLE_TRANSPORT_ACL_FROM_LL_COUNT=24 +CONFIG_BT_CTRL_BLE_STATIC_ACL_TX_BUF_NB=8 diff --git a/examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_prph/sdkconfig.defaults.esp32c6 b/examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_prph/sdkconfig.defaults.esp32c6 new file mode 100644 index 00000000000..a9588a5cd15 --- /dev/null +++ b/examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_prph/sdkconfig.defaults.esp32c6 @@ -0,0 +1,2 @@ +CONFIG_BT_NIMBLE_MSYS_1_BLOCK_COUNT=30 +CONFIG_BT_NIMBLE_TRANSPORT_ACL_FROM_LL_COUNT=67 diff --git a/examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_prph/sdkconfig.defaults.esp32h2 b/examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_prph/sdkconfig.defaults.esp32h2 new file mode 100644 index 00000000000..a9588a5cd15 --- /dev/null +++ b/examples/bluetooth/nimble/throughput_app/l2cap_coc/l2cap_coc_prph/sdkconfig.defaults.esp32h2 @@ -0,0 +1,2 @@ +CONFIG_BT_NIMBLE_MSYS_1_BLOCK_COUNT=30 +CONFIG_BT_NIMBLE_TRANSPORT_ACL_FROM_LL_COUNT=67 diff --git a/examples/custom_bootloader/bootloader_extra_dir/CMakeLists.txt b/examples/custom_bootloader/bootloader_extra_dir/CMakeLists.txt index 3584d91482f..90014542bea 100644 --- a/examples/custom_bootloader/bootloader_extra_dir/CMakeLists.txt +++ b/examples/custom_bootloader/bootloader_extra_dir/CMakeLists.txt @@ -6,6 +6,31 @@ cmake_minimum_required(VERSION 3.22) include($ENV{IDF_PATH}/tools/cmake/project.cmake) -idf_build_set_property(BOOTLOADER_EXTRA_COMPONENT_DIRS "${CMAKE_CURRENT_LIST_DIR}/extra_bootloader_components/" APPEND) +# The bootloader always uses the "bootloader_components" folder name +# as the project-local search path for bootloader components added to the bootloader build. +# +# Use BOOTLOADER_EXTRA_COMPONENT_DIRS when bootloader components already lives somewhere else, +# for example in a shared components directory or in a component reused by several projects. +# The path may point to a directory containing multiple components. +idf_build_set_property(BOOTLOADER_EXTRA_COMPONENT_DIRS "${CMAKE_CURRENT_LIST_DIR}/extra_bootloader_components" APPEND) + +# The path may also point directly to a single component when adding a wrapper directory +# just for the bootloader build would be unnecessary. +idf_build_set_property(BOOTLOADER_EXTRA_COMPONENT_DIRS "${CMAKE_CURRENT_LIST_DIR}/extra_component4" APPEND) +idf_build_set_property(BOOTLOADER_EXTRA_COMPONENT_DIRS "${CMAKE_CURRENT_LIST_DIR}/extra_component5" APPEND) + +# A shared extra directory may contain components that are not needed in this bootloader build. +# Ignore them by component name, including direct single-component paths. +set(BOOTLOADER_IGNORE_EXTRA_COMPONENT extra_component3 extra_component5) project(main) + +# Check which extra bootloader components are included and which ones are ignored. +add_custom_target(check_bootloader_extra_components ALL + COMMAND ${CMAKE_COMMAND} + -DPROJECT_DESCRIPTION=${CMAKE_BINARY_DIR}/bootloader/project_description.json + -DEXPECTED_COMPONENTS=extra_component1,extra_component2,extra_component4 + -DIGNORED_COMPONENTS=extra_component3,extra_component5 + -P ${CMAKE_CURRENT_LIST_DIR}/check_bootloader_component.cmake + DEPENDS bootloader + VERBATIM) diff --git a/examples/custom_bootloader/bootloader_extra_dir/README.md b/examples/custom_bootloader/bootloader_extra_dir/README.md index 14f4471f714..4ce443467b2 100644 --- a/examples/custom_bootloader/bootloader_extra_dir/README.md +++ b/examples/custom_bootloader/bootloader_extra_dir/README.md @@ -5,9 +5,11 @@ (See the README.md file in the upper level for more information about bootloader examples.) -The purpose of this example is to show how to add a custom directory that contains a component to the bootloader build. +The purpose of this example is to show how to add bootloader components that are not placed in the conventional `bootloader_components` directory. -Registering extra components for the bootloader can be done thanks to the IDF property `BOOTLOADER_EXTRA_COMPONENT_DIRS`. It can either refer to a directory that contains several components, either refer to a single component. +The bootloader always uses the `bootloader_components` folder name as the project-local search path for bootloader components. Use this folder for bootloader-specific code that belongs to the project, such as hooks or overrides. + +Use the `BOOTLOADER_EXTRA_COMPONENT_DIRS` property when bootloader components already live somewhere else, for example in a shared components directory or in a component reused by several projects. Each path can point either to a directory containing multiple components or directly to a single component. If the extra location contains components that are not needed in a particular bootloader build, list them by component name in `BOOTLOADER_IGNORE_EXTRA_COMPONENT`. ## Usage of this example: @@ -33,9 +35,11 @@ User application is loaded and running. ## Organization of this example -This project contains a `main` directory that represents an application. It also has a `bootloader_components` directory that contains a component that will be compiled and linked with the bootloader. This `bootloader_components` can contain several components, each of them would be in a different directory. +This project contains a `main` directory that represents an application. It also has a `bootloader_components` directory with `my_boot_hooks`, a bootloader-specific hook component that belongs to this project. -The directory `extra_bootloader_components/extra_component/` contains a component that is meant to be included in the bootloader build. To do so, the CMake property `BOOTLOADER_EXTRA_COMPONENT_DIRS` is set from the `CMakeLists.txt` file. +The `extra_bootloader_components/` directory demonstrates an extra path that contains several components. `extra_component1` is required by `my_boot_hooks`, while `extra_component2` is included directly from `BOOTLOADER_EXTRA_COMPONENT_DIRS` without being required by another component. `extra_component3` is present in the same directory but is excluded from this bootloader build with `BOOTLOADER_IGNORE_EXTRA_COMPONENT`. + +The `extra_component4/` and `extra_component5/` directories demonstrate extra paths that point directly to individual components. `extra_component4` is included directly, while `extra_component5` is excluded with `BOOTLOADER_IGNORE_EXTRA_COMPONENT`. Below is a short explanation of files in the project folder. @@ -49,8 +53,20 @@ Below is a short explanation of files in the project folder. │   ├── CMakeLists.txt │   └── hooks.c Implementation of the hooks to execute on boot ├── extra_bootloader_components -│   └── extra_component +│   ├── extra_component1 +│   │   ├── CMakeLists.txt +│   │   └── extra_component1.c Implementation of the extra component +│   ├── extra_component2 +│   │   ├── CMakeLists.txt +│   │   └── extra_component2.c Implementation of the 2nd extra component +│   └── extra_component3 │   ├── CMakeLists.txt -│   └── extra_component.c Implementation of the extra component +│   └── extra_component3.c Extra component excluded from this bootloader build +├── extra_component4 +│   ├── CMakeLists.txt +│   └── extra_component4.c Extra component included directly +├── extra_component5 +│   ├── CMakeLists.txt +│   └── extra_component5.c Extra component excluded directly └── README.md This is the file you are currently reading ``` diff --git a/examples/custom_bootloader/bootloader_extra_dir/bootloader_components/my_boot_hooks/CMakeLists.txt b/examples/custom_bootloader/bootloader_extra_dir/bootloader_components/my_boot_hooks/CMakeLists.txt index 6b620571a47..ce482610cd9 100644 --- a/examples/custom_bootloader/bootloader_extra_dir/bootloader_components/my_boot_hooks/CMakeLists.txt +++ b/examples/custom_bootloader/bootloader_extra_dir/bootloader_components/my_boot_hooks/CMakeLists.txt @@ -1,5 +1,5 @@ idf_component_register(SRCS "hooks.c" - REQUIRES extra_component) + REQUIRES extra_component1) # We need to force GCC to integrate this static library into the # bootloader link. Indeed, by default, as the hooks in the bootloader are weak, diff --git a/examples/custom_bootloader/bootloader_extra_dir/check_bootloader_component.cmake b/examples/custom_bootloader/bootloader_extra_dir/check_bootloader_component.cmake new file mode 100644 index 00000000000..426850c6885 --- /dev/null +++ b/examples/custom_bootloader/bootloader_extra_dir/check_bootloader_component.cmake @@ -0,0 +1,32 @@ +# SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: Unlicense OR CC0-1.0 + +file(READ "${PROJECT_DESCRIPTION}" project_description) +string(JSON component_count LENGTH "${project_description}" build_components) + +set(build_components) +if(component_count GREATER 0) + math(EXPR last_component_index "${component_count} - 1") + foreach(component_index RANGE 0 ${last_component_index}) + string(JSON component_name GET "${project_description}" build_components ${component_index}) + if(NOT component_name STREQUAL "") + list(APPEND build_components "${component_name}") + endif() + endforeach() +endif() + +string(REPLACE "," ";" expected_components "${EXPECTED_COMPONENTS}") +foreach(expected_component ${expected_components}) + list(FIND build_components "${expected_component}" component_index) + if(component_index EQUAL -1) + message(FATAL_ERROR "${expected_component} was not included in the bootloader build") + endif() +endforeach() + +string(REPLACE "," ";" ignored_components "${IGNORED_COMPONENTS}") +foreach(ignored_component ${ignored_components}) + list(FIND build_components "${ignored_component}" component_index) + if(NOT component_index EQUAL -1) + message(FATAL_ERROR "${ignored_component} was included in the bootloader build") + endif() +endforeach() diff --git a/examples/custom_bootloader/bootloader_extra_dir/extra_bootloader_components/extra_component/CMakeLists.txt b/examples/custom_bootloader/bootloader_extra_dir/extra_bootloader_components/extra_component/CMakeLists.txt deleted file mode 100644 index ac58fa9c07e..00000000000 --- a/examples/custom_bootloader/bootloader_extra_dir/extra_bootloader_components/extra_component/CMakeLists.txt +++ /dev/null @@ -1 +0,0 @@ -idf_component_register(SRCS "extra_component.c") diff --git a/examples/custom_bootloader/bootloader_extra_dir/extra_bootloader_components/extra_component1/CMakeLists.txt b/examples/custom_bootloader/bootloader_extra_dir/extra_bootloader_components/extra_component1/CMakeLists.txt new file mode 100644 index 00000000000..706d10816b5 --- /dev/null +++ b/examples/custom_bootloader/bootloader_extra_dir/extra_bootloader_components/extra_component1/CMakeLists.txt @@ -0,0 +1 @@ +idf_component_register(SRCS "extra_component1.c") diff --git a/examples/custom_bootloader/bootloader_extra_dir/extra_bootloader_components/extra_component/extra_component.c b/examples/custom_bootloader/bootloader_extra_dir/extra_bootloader_components/extra_component1/extra_component1.c similarity index 100% rename from examples/custom_bootloader/bootloader_extra_dir/extra_bootloader_components/extra_component/extra_component.c rename to examples/custom_bootloader/bootloader_extra_dir/extra_bootloader_components/extra_component1/extra_component1.c diff --git a/examples/custom_bootloader/bootloader_extra_dir/extra_bootloader_components/extra_component2/CMakeLists.txt b/examples/custom_bootloader/bootloader_extra_dir/extra_bootloader_components/extra_component2/CMakeLists.txt new file mode 100644 index 00000000000..573ebd90436 --- /dev/null +++ b/examples/custom_bootloader/bootloader_extra_dir/extra_bootloader_components/extra_component2/CMakeLists.txt @@ -0,0 +1 @@ +idf_component_register(SRCS "extra_component2.c") diff --git a/examples/custom_bootloader/bootloader_extra_dir/extra_bootloader_components/extra_component2/extra_component2.c b/examples/custom_bootloader/bootloader_extra_dir/extra_bootloader_components/extra_component2/extra_component2.c new file mode 100644 index 00000000000..3633dcc4e29 --- /dev/null +++ b/examples/custom_bootloader/bootloader_extra_dir/extra_bootloader_components/extra_component2/extra_component2.c @@ -0,0 +1,9 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ + +void bootloader_extra_component2_marker(void) +{ +} diff --git a/examples/custom_bootloader/bootloader_extra_dir/extra_bootloader_components/extra_component3/CMakeLists.txt b/examples/custom_bootloader/bootloader_extra_dir/extra_bootloader_components/extra_component3/CMakeLists.txt new file mode 100644 index 00000000000..a95c4d92bab --- /dev/null +++ b/examples/custom_bootloader/bootloader_extra_dir/extra_bootloader_components/extra_component3/CMakeLists.txt @@ -0,0 +1 @@ +idf_component_register(SRCS "extra_component3.c") diff --git a/examples/custom_bootloader/bootloader_extra_dir/extra_bootloader_components/extra_component3/extra_component3.c b/examples/custom_bootloader/bootloader_extra_dir/extra_bootloader_components/extra_component3/extra_component3.c new file mode 100644 index 00000000000..411b1b34017 --- /dev/null +++ b/examples/custom_bootloader/bootloader_extra_dir/extra_bootloader_components/extra_component3/extra_component3.c @@ -0,0 +1,9 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ + +void bootloader_extra_component3_marker(void) +{ +} diff --git a/examples/custom_bootloader/bootloader_extra_dir/extra_component4/CMakeLists.txt b/examples/custom_bootloader/bootloader_extra_dir/extra_component4/CMakeLists.txt new file mode 100644 index 00000000000..0b9343f95fa --- /dev/null +++ b/examples/custom_bootloader/bootloader_extra_dir/extra_component4/CMakeLists.txt @@ -0,0 +1 @@ +idf_component_register(SRCS "extra_component4.c") diff --git a/examples/custom_bootloader/bootloader_extra_dir/extra_component4/extra_component4.c b/examples/custom_bootloader/bootloader_extra_dir/extra_component4/extra_component4.c new file mode 100644 index 00000000000..815f61018d4 --- /dev/null +++ b/examples/custom_bootloader/bootloader_extra_dir/extra_component4/extra_component4.c @@ -0,0 +1,9 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ + +void bootloader_extra_component4_marker(void) +{ +} diff --git a/examples/custom_bootloader/bootloader_extra_dir/extra_component5/CMakeLists.txt b/examples/custom_bootloader/bootloader_extra_dir/extra_component5/CMakeLists.txt new file mode 100644 index 00000000000..0c866b67b8f --- /dev/null +++ b/examples/custom_bootloader/bootloader_extra_dir/extra_component5/CMakeLists.txt @@ -0,0 +1 @@ +idf_component_register(SRCS "extra_component5.c") diff --git a/examples/custom_bootloader/bootloader_extra_dir/extra_component5/extra_component5.c b/examples/custom_bootloader/bootloader_extra_dir/extra_component5/extra_component5.c new file mode 100644 index 00000000000..87f67a037d9 --- /dev/null +++ b/examples/custom_bootloader/bootloader_extra_dir/extra_component5/extra_component5.c @@ -0,0 +1,9 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ + +void bootloader_extra_component5_marker(void) +{ +} diff --git a/examples/lowpower/power_management/pytest_power_management.py b/examples/lowpower/power_management/pytest_power_management.py index 485b724204c..728189df99a 100644 --- a/examples/lowpower/power_management/pytest_power_management.py +++ b/examples/lowpower/power_management/pytest_power_management.py @@ -61,7 +61,7 @@ def test_esp_pm_mode_stats_pd_top(dut: Dut) -> None: @pytest.mark.generic -@pytest.mark.esp32c5_eco3 +@pytest.mark.esp32c5_rev1 @pytest.mark.parametrize('config', ['pd_top'], indirect=True) @idf_parametrize('target', ['esp32c5'], indirect=['target']) def test_esp_pm_mode_stats_pd_top_esp32c5_eco3(dut: Dut) -> None: diff --git a/examples/openthread/ot_br/sdkconfig.defaults b/examples/openthread/ot_br/sdkconfig.defaults index f0f8e7f5894..c0adf25023e 100644 --- a/examples/openthread/ot_br/sdkconfig.defaults +++ b/examples/openthread/ot_br/sdkconfig.defaults @@ -12,6 +12,7 @@ CONFIG_PARTITION_TABLE_MD5=y # CONFIG_MBEDTLS_SSL_PROTO_DTLS=y CONFIG_MBEDTLS_KEY_EXCHANGE_ECJPAKE=y +CONFIG_MBEDTLS_ECJPAKE_C=y # end of TLS Key Exchange Methods # end of mbedTLS diff --git a/examples/openthread/ot_ci_function.py b/examples/openthread/ot_ci_function.py index 74b84998a36..5233cc14bc4 100644 --- a/examples/openthread/ot_ci_function.py +++ b/examples/openthread/ot_ci_function.py @@ -413,17 +413,19 @@ def wait_for_host_ra_route( raise AssertionError('Host did not receive valid RA in time (OMR route and onlink GUA both required)') -def host_global_address_has_onlink_prefix(interface_name: str, onlinkprefix: str) -> bool: +def _list_host_onlink_global_address_entries(interface_name: str, onlinkprefix: str) -> list[tuple[str, bool]]: + """Return (address, is_usable) for each global address in the onlink /64.""" onlinkprefix = onlinkprefix.strip() if not onlinkprefix: - return False + return [] base = onlinkprefix.rstrip(':') try: network = ipaddress.IPv6Network(f'{base}::/64', strict=False) except ValueError: logging.warning(f'Invalid onlinkprefix for /64 check: {onlinkprefix}') - return False + return [] + entries: dict[str, bool] = {} out = subprocess.getoutput(f'ip -6 addr show dev {interface_name}') for line in out.splitlines(): if 'inet6' not in line or 'scope global' not in line: @@ -433,11 +435,30 @@ def host_global_address_has_onlink_prefix(interface_name: str, onlinkprefix: str continue addr_s = m.group(1).split('%')[0] try: - if ipaddress.IPv6Address(addr_s) in network: - return True + if ipaddress.IPv6Address(addr_s) not in network: + continue except ValueError: continue - return False + is_usable = 'tentative' not in line and 'dadfailed' not in line + if addr_s in entries: + entries[addr_s] = entries[addr_s] and is_usable + else: + entries[addr_s] = is_usable + return list(entries.items()) + + +def host_global_address_has_onlink_prefix(interface_name: str, onlinkprefix: str) -> bool: + entries = _list_host_onlink_global_address_entries(interface_name, onlinkprefix) + if not entries: + return False + return all(usable for _, usable in entries) + + +def list_host_usable_onlink_global_addresses(interface_name: str, onlinkprefix: str) -> list[str]: + entries = _list_host_onlink_global_address_entries(interface_name, onlinkprefix) + if not entries or not all(usable for _, usable in entries): + return [] + return [addr for addr, _ in entries] def wait_for_host_onlink_global_address( diff --git a/examples/openthread/ot_cli/sdkconfig.defaults b/examples/openthread/ot_cli/sdkconfig.defaults index 13f8537d33e..eabd3d39628 100644 --- a/examples/openthread/ot_cli/sdkconfig.defaults +++ b/examples/openthread/ot_cli/sdkconfig.defaults @@ -13,6 +13,7 @@ CONFIG_PARTITION_TABLE_MD5=y # CONFIG_MBEDTLS_SSL_PROTO_DTLS=y CONFIG_MBEDTLS_KEY_EXCHANGE_ECJPAKE=y +CONFIG_MBEDTLS_ECJPAKE_C=y # end of mbedTLS # diff --git a/examples/openthread/ot_sleepy_device/deep_sleep/sdkconfig.defaults b/examples/openthread/ot_sleepy_device/deep_sleep/sdkconfig.defaults index 8a57ae889c4..416e9349fb2 100644 --- a/examples/openthread/ot_sleepy_device/deep_sleep/sdkconfig.defaults +++ b/examples/openthread/ot_sleepy_device/deep_sleep/sdkconfig.defaults @@ -11,6 +11,7 @@ CONFIG_PARTITION_TABLE_FILENAME="partitions.csv" # CONFIG_MBEDTLS_SSL_PROTO_DTLS=y CONFIG_MBEDTLS_KEY_EXCHANGE_ECJPAKE=y +CONFIG_MBEDTLS_ECJPAKE_C=y # end of mbedTLS # diff --git a/examples/openthread/ot_sleepy_device/light_sleep/sdkconfig.defaults b/examples/openthread/ot_sleepy_device/light_sleep/sdkconfig.defaults index 2b8111f6abf..03acbcb41c0 100644 --- a/examples/openthread/ot_sleepy_device/light_sleep/sdkconfig.defaults +++ b/examples/openthread/ot_sleepy_device/light_sleep/sdkconfig.defaults @@ -11,6 +11,7 @@ CONFIG_PARTITION_TABLE_FILENAME="partitions.csv" # CONFIG_MBEDTLS_SSL_PROTO_DTLS=y CONFIG_MBEDTLS_KEY_EXCHANGE_ECJPAKE=y +CONFIG_MBEDTLS_ECJPAKE_C=y # end of mbedTLS # diff --git a/examples/openthread/ot_trel/sdkconfig.defaults b/examples/openthread/ot_trel/sdkconfig.defaults index 1232a900b11..367f2d10ced 100644 --- a/examples/openthread/ot_trel/sdkconfig.defaults +++ b/examples/openthread/ot_trel/sdkconfig.defaults @@ -13,6 +13,7 @@ CONFIG_PARTITION_TABLE_MD5=y # CONFIG_MBEDTLS_SSL_PROTO_DTLS=y CONFIG_MBEDTLS_KEY_EXCHANGE_ECJPAKE=y +CONFIG_MBEDTLS_ECJPAKE_C=y # end of mbedTLS # diff --git a/examples/openthread/pytest_otbr.py b/examples/openthread/pytest_otbr.py index b2cf83bd8d0..9f4ae0722e9 100644 --- a/examples/openthread/pytest_otbr.py +++ b/examples/openthread/pytest_otbr.py @@ -11,6 +11,7 @@ import subprocess import sys import threading import time +from collections.abc import Generator sys.path.append(os.path.dirname(os.path.abspath(__file__))) import ot_ci_function as ocf @@ -105,6 +106,25 @@ ESPPORT4 = os.getenv('ESPPORT4') PORT_MAPPING = {'ESPPORT1': 'esp32h2', 'ESPPORT2': 'esp32s3', 'ESPPORT3': 'esp32c6', 'ESPPORT4': 'esp32c5'} +@pytest.fixture(scope='module', autouse=True) +def erase_flash_after_all_cases() -> Generator[None, None, None]: + yield + + serial_ports = list(dict.fromkeys(filter(None, map(os.getenv, PORT_MAPPING)))) + failed_ports = [] + for serial_port in serial_ports: + command = ['python', '-m', 'esptool', '--port', serial_port, 'erase_flash'] + logging.info('Erasing flash on %s: %s', serial_port, ' '.join(command)) + result = subprocess.run(command, capture_output=True, text=True) + logging.info('Erase flash stdout on %s:\n%s', serial_port, result.stdout) + if result.stderr: + logging.info('Erase flash stderr on %s:\n%s', serial_port, result.stderr) + if result.returncode != 0: + failed_ports.append(serial_port) + + assert not failed_ports, f'Failed to erase flash on ports: {failed_ports}' + + # Case 1: Thread network formation and attaching @pytest.mark.openthread_br @pytest.mark.flaky(reruns=1, reruns_delay=5) @@ -246,11 +266,7 @@ def test_Bidirectional_IPv6_connectivity(Init_interface: bool, dut: tuple[IdfDut cli_global_unicast_addr = ocf.get_global_unicast_addr(cli, br) logging.info(f'cli_global_unicast_addr {cli_global_unicast_addr}') interface_name = ocf.get_host_interface_name() - command = 'ifconfig ' + interface_name + ' | grep inet6 | grep global' - out_bytes = subprocess.check_output(command, shell=True, timeout=5) - out_str = out_bytes.decode('utf-8') - pattern = rf'\W+({onlinkprefix}(?:\w+:){{3}}\w+)\W+' - host_global_unicast_addr = re.findall(pattern, out_str) + host_global_unicast_addr = ocf.list_host_usable_onlink_global_addresses(interface_name, onlinkprefix) logging.info(f'host_global_unicast_addr: {host_global_unicast_addr}') if not host_global_unicast_addr: raise Exception(f'onlinkprefix: {onlinkprefix}, host_global_unicast_addr: {host_global_unicast_addr}') diff --git a/examples/peripherals/.build-test-rules.yml b/examples/peripherals/.build-test-rules.yml index a00541e3ab6..873a87dd31e 100644 --- a/examples/peripherals/.build-test-rules.yml +++ b/examples/peripherals/.build-test-rules.yml @@ -84,6 +84,12 @@ examples/peripherals/dac/dac_cosine_wave: - esp_driver_spi - esp_driver_dac +examples/peripherals/dma/async_color_convert: + disable: + - if: SOC_DMA2D_SUPPORTED != 1 + depends_components: + - esp_driver_dma + examples/peripherals/gpio: depends_components: - *common_components @@ -626,6 +632,12 @@ examples/peripherals/twai/twai_utils: examples/peripherals/uart/uart_dma_ota: disable: - if: SOC_UHCI_SUPPORTED != 1 + depends_components: + - esp_driver_uart + - esp_driver_dma + - app_update + - esp_ringbuf + - soc examples/peripherals/uart/uart_echo_rs485: enable: @@ -654,6 +666,10 @@ examples/peripherals/usb/device/cherryusb_serial_device: temporary: true reason: CherryUSB does not support esp32h4 +examples/peripherals/usb/device/tusb_cdc_acm_wakeup: + disable: + - if: SOC_USB_OTG_SUPPORTED != 1 or SOC_PM_SUPPORT_USB_WAKEUP != 1 + examples/peripherals/usb/device/tusb_ncm: disable: - if: SOC_USB_OTG_SUPPORTED != 1 or SOC_WIFI_SUPPORTED != 1 diff --git a/examples/peripherals/adc/continuous_read/pytest_adc_continuous.py b/examples/peripherals/adc/continuous_read/pytest_adc_continuous.py index c7793c69a52..8645c0f4f04 100644 --- a/examples/peripherals/adc/continuous_read/pytest_adc_continuous.py +++ b/examples/peripherals/adc/continuous_read/pytest_adc_continuous.py @@ -5,7 +5,7 @@ from pytest_embedded.dut import Dut from pytest_embedded_idf.utils import idf_parametrize -@pytest.mark.adc +@pytest.mark.generic @idf_parametrize( 'target', ['esp32', 'esp32s2', 'esp32s3', 'esp32c3', 'esp32c6', 'esp32h2', 'esp32c5', 'esp32p4', 'esp32c61'], diff --git a/examples/peripherals/adc/oneshot_read/pytest_adc_oneshot.py b/examples/peripherals/adc/oneshot_read/pytest_adc_oneshot.py index 17e15ba7277..24154a90e56 100644 --- a/examples/peripherals/adc/oneshot_read/pytest_adc_oneshot.py +++ b/examples/peripherals/adc/oneshot_read/pytest_adc_oneshot.py @@ -5,7 +5,7 @@ from pytest_embedded.dut import Dut from pytest_embedded_idf.utils import idf_parametrize -@pytest.mark.adc +@pytest.mark.generic @idf_parametrize( 'target', ['esp32', 'esp32s2', 'esp32s3', 'esp32c3', 'esp32c6', 'esp32h2', 'esp32c5', 'esp32p4', 'esp32c61'], @@ -15,7 +15,7 @@ def test_adc_oneshot(dut: Dut) -> None: dut.expect(r'EXAMPLE: ADC1 Channel\[(\d+)\] Raw Data: (\d+)', timeout=5) -@pytest.mark.adc +@pytest.mark.generic @pytest.mark.xtal_26mhz @pytest.mark.parametrize( 'config, baud', diff --git a/examples/peripherals/dac/dac_cosine_wave/pytest_dac_cosine_wave.py b/examples/peripherals/dac/dac_cosine_wave/pytest_dac_cosine_wave.py index 2e4ab8ce3f1..b1102bb62f0 100644 --- a/examples/peripherals/dac/dac_cosine_wave/pytest_dac_cosine_wave.py +++ b/examples/peripherals/dac/dac_cosine_wave/pytest_dac_cosine_wave.py @@ -5,7 +5,7 @@ from pytest_embedded import Dut from pytest_embedded_idf.utils import idf_parametrize -@pytest.mark.adc +@pytest.mark.generic @idf_parametrize('target', ['esp32'], indirect=['target']) def test_dac_cosine_wave_example_with_12bit_adc(dut: Dut) -> None: res = [] @@ -19,7 +19,7 @@ def test_dac_cosine_wave_example_with_12bit_adc(dut: Dut) -> None: assert max(chan0_val) - min(chan0_val) > 1000 -@pytest.mark.adc +@pytest.mark.generic @idf_parametrize('target', ['esp32s2'], indirect=['target']) def test_dac_cosine_wave_example_with_13bit_adc(dut: Dut) -> None: res = [] diff --git a/examples/peripherals/dma/async_color_convert/CMakeLists.txt b/examples/peripherals/dma/async_color_convert/CMakeLists.txt new file mode 100644 index 00000000000..3192fb91ee2 --- /dev/null +++ b/examples/peripherals/dma/async_color_convert/CMakeLists.txt @@ -0,0 +1,5 @@ +cmake_minimum_required(VERSION 3.22) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +idf_build_set_property(MINIMAL_BUILD ON) +project(async_color_convert_example) diff --git a/examples/peripherals/dma/async_color_convert/README.md b/examples/peripherals/dma/async_color_convert/README.md new file mode 100644 index 00000000000..defc28ed31d --- /dev/null +++ b/examples/peripherals/dma/async_color_convert/README.md @@ -0,0 +1,69 @@ +| Supported Targets | ESP32-P4 | +| ----------------- | -------- | + +# Async Color Convert Example + +(See the README.md file in the upper level 'examples' directory for more information about examples.) + +## Overview + +This example demonstrates how to use the Async Color Convert driver (`esp_async_color_convert.h`) with the DMA2D backend. + +The example performs: + +- Loading an embedded UYVY422 raw image from flash +- Letting DMA2D read the source image directly from mapped flash +- Performing a blocking UYVY422 -> RGB888 conversion with the Async Color Convert driver +- Base64-encoding the converted BGR24 image and printing it with machine-parseable markers +- Letting pytest decode the payload, save a PPM artifact, and compare it against a golden reference image + +## Hardware Required + +Any board with a supported ESP target that mentioned in the above table can be used. + +## Build and Flash + +Run `idf.py -p PORT flash monitor` to build and flash the project. + +(To exit the serial monitor, type ``Ctrl-]``.) + +See the [Getting Started Guide](https://docs.espressif.com/projects/esp-idf/en/latest/get-started/index.html) for full steps to configure and use ESP-IDF to build projects. + +## Example Output + +```text +Loading embedded UYVY image from flash... +Embedded image size: 12288 bytes +Converting UYVY422 -> RGB888... +Converted image size: 18432 bytes +IMAGE_META width=96 height=64 format=BGR24 encoding=base64 +IMAGE_BASE64_BEGIN +IMAGE_BASE64 ... +IMAGE_BASE64 ... +IMAGE_BASE64_END +Async color convert visual demo done. +``` + +## Visual Result In Pytest + +The accompanying pytest script captures the `IMAGE_META` and `IMAGE_BASE64` output, reconstructs the converted image, and saves it as: + +- `dut.logdir/async_color_convert_result.ppm` + +It also compares the generated result with `golden_result.ppm` by hashing the decoded RGB pixel content. This turns the example into a regression test as well as a visual demo: the image must both render correctly for a human and match the stored golden output for CI. + +## Replacing The Embedded UYVY Asset + +The example embeds `main/assets/sample_96x64_uyvy.yuv`. + +You can regenerate a compatible asset from any PNG with `ffmpeg`. One simple workflow is: + +```bash +ffmpeg -y -i input.png -vf scale=96:64 -pix_fmt uyvy422 -f rawvideo sample_96x64_uyvy.yuv +``` + +After replacing the `.yuv` file, rebuild and flash the example. The firmware will emit the converted image as base64, and pytest will save the resulting PPM artifact automatically. If you intend the new image to become the expected output, update `golden_result.ppm` as well so the regression check stays in sync. + +## Troubleshooting + +(For any technical queries, please open an [issue](https://github.com/espressif/esp-idf/issues) on GitHub. We will get back to you as soon as possible.) diff --git a/examples/peripherals/dma/async_color_convert/golden_result.ppm b/examples/peripherals/dma/async_color_convert/golden_result.ppm new file mode 100644 index 00000000000..0f987ed1191 Binary files /dev/null and b/examples/peripherals/dma/async_color_convert/golden_result.ppm differ diff --git a/examples/peripherals/dma/async_color_convert/main/CMakeLists.txt b/examples/peripherals/dma/async_color_convert/main/CMakeLists.txt new file mode 100644 index 00000000000..85414639ef9 --- /dev/null +++ b/examples/peripherals/dma/async_color_convert/main/CMakeLists.txt @@ -0,0 +1,4 @@ +idf_component_register(SRCS "async_color_convert_example_main.c" + PRIV_REQUIRES esp_driver_dma mbedtls + INCLUDE_DIRS "." + EMBED_FILES "assets/sample_96x64_uyvy.yuv") diff --git a/examples/peripherals/dma/async_color_convert/main/assets/sample_96x64_uyvy.yuv b/examples/peripherals/dma/async_color_convert/main/assets/sample_96x64_uyvy.yuv new file mode 100644 index 00000000000..d551abaead1 --- /dev/null +++ b/examples/peripherals/dma/async_color_convert/main/assets/sample_96x64_uyvy.yuv @@ -0,0 +1 @@ +nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww{!{!{!{!{!{!{!{!{!{!{!{!{!{!{!{!{!{!{!{!{!{!{!{!{!{!{!{!{!{!{!{!{!{!{!{!{!{!{!{!{!{!{!{!{!{!{!{!$$R$R$R$$''S'mQZQQZQQZQQZQQZQQZQQZQQZQQZQQZQQZQQZQQS'[Q6"6"6"6"6"6"6"6"6"6"6"6"S''))U)mQZQQZQQZQQZQQZQQZQQZQQZQQZQQZQQZQQZQQU)[Q6"6"6"6"6"6"6"6"6"6"6"6"U)),,V,mQZQQZQQZQQZQQZQQZQQZQQZQQZQQZQQZQQZQQV,[Q6"6"6"6"6"6"6"6"6"6"6"6"V,,..W.mQZQQZQQZQQZQQZQQZQQZQQZQQZQQZQQZQQZQQW.[Q6"6"6"6"6"6"6"6"6"6"6"6"W..11Y1mQZQQZQQZQQZQQZQQZQQZQQZQQZQQZQQZQQZQQY1[Q6"6"6"6"6"6"6"6"6"6"6"6"Y1144Z4mQZQQZQQZQQZQQZQQZQQZQQZQQZQQZQQZQQZQQZ4[Q6"6"6"6"6"6"6"6"6"6"6"6"Z4466[6mQZQQZQQZQQZQQZQQZQQZQQZQQZQQZQQZQQZQQ[6[Q6"6"6"6"6"6"6"6"6"6"6"6"[6699\9mQZQQZQQZQQZQQZQQZQQZQQZQQZQQZQQZQQZQQ\9[Q6"6"6"6"6"6"6"6"6"6"6"6"\99<<^>_>mQZQQZQQZQQZQQZQQZQQZQQZQQZQQZQQZQQZQQ_>[Q6"6"6"6"6"6"6"6"6"6"6"6"_>>AA`AmQZQQZQQZQQZQQZQQZQQZQQZQQZQQZQQZQQZQQ`A[Q6"6"6"6"6"6"6"6"6"6"6"6"`AAwMwM|gwMmQZQQZQQZQQZQQZQQZQQZQQZQQZQQZQQZQQZQQ|gwM[Q6"6"6"6"6"6"6"6"6"6"6"6"|gwMwM|P|P~h|PmQZQQZQQZQQZQQZQQZQQZQQZQQZQQZQQZQQZQQ~h|P[Q6"6"6"6"6"6"6"6"6"6"6"6"~h|P|PRRiRmQZQQZQQZQQZQQZQQZQQZQQZQQZQQZQQZQQZQQiR[Q6"6"6"6"6"6"6"6"6"6"6"6"iRRUUjUmQZQQZQQZQQZQQZQQZQQZQQZQQZQQZQQZQQZQQjU[Q6"6"6"6"6"6"6"6"6"6"6"6"jUUXXlXmQZQQZQQZQQZQQZQQZQQZQQZQQZQQZQQZQQZQQlX[Q6"6"6"6"6"6"6"6"6"6"6"6"lXX[[m[mQZQQZQQZQQZQQZQQZQQZQQZQQZQQZQQZQQZQQm[[Q6"6"6"6"6"6"6"6"6"6"6"6"m[[^^o^mQZQQZQQZQQZQQZQQZQQZQQZQQZQQZQQZQQZQQo^[Q6"6"6"6"6"6"6"6"6"6"6"6"o^^``p`mQZQQZQQZQQZQQZQQZQQZQQZQQZQQZQQZQQZQQp`[Q6"6"6"6"6"6"6"6"6"6"6"6"p``ccqcmQZQQZQQZQQZQQZQQZQQZQQZQQZQQZQQZQQZQQqc[Q6"6"6"6"6"6"6"6"6"6"6"6"qccffsfmQZQQZQQZQQZQQZQQZQQZQQZQQZQQZQQZQQZQQsf[Q6"6"6"6"6"6"6"6"6"6"6"6"sffhhthththhkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmppppppppppppppppppppppppppppppppppppppppppppppppssssssssssssysssssssssssssssssssssssssssssssuuuuuuuuuu{eBeeBeeBeeBeea븊uuuuuuuuuuuuuuuuuuuuuuuuuuuuuu}}}}}}}}}}}}}}}}}}}aeeBeeBeeBeeBeeBeeBeeBe}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}|||||||||||||||||aeeBeeBeeBeeBeeBeeBeeBeeBeeBe||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||aeeBeeBeeBeeBeeBeeBeeBeeBeeBeeBeeBe||||||||||||||||||||||||||||||||||||||||||O|||||||||||||||||||||||||eBeeBeeBeeBeeBeeBeeBeeBeeBeeBeeBeeBeea|||||||||||||||||||||||||||||||||||||||O|||||||||||||||||||||||aeeBeeBeeBeeBeeBeeBeeBeeBeeBeeBeeBeeBeeBe|||||||||||||||||||||||||||||||||||||O||||||||||||||||||||||eBeeBeeBeeBeeBeeBeeBeeBeeBeeBeeBeeBeeBeeBeea||||||||||||||||||||||||||||||||||||||||||||||||||||||||aeeBeeBeeBeeBeeBeeBeeBeeBeeBeeBeeBeeBeeBeeBeeBe|||||||||||||||||||||||||||||||||O|||||||||||||||||||||aeeBeeBeeBeeBeeBeeBeeBeeBeeBeeBeeBeeBeeBeeBeeBe|||||||||||||||||||||||||||||||||||||||||{{{{{{{{{{{eBeeBeeBeeBeeBeeBeeBeeBeeBeeBeeBeeBeeBeeBeeBeeBeea{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{OO{{{{{{{{{{{{{{{{{{{eBeeBeeBeeBeeBeeBeeBeeBeeBeeBeeBeeBeeBeeBeeBeeBeea{{{{{{{{{{{{{{{{{{{{{{{{{{{{{O{{{{{{{{{{{{{{{{{{aeeBeeBeeBeeBeeBeeBeeBeeBeeBeeBeeBeeBeeBeeBeeBeeBeeBe{{{{{{{{{{{{{{{{{{{{{{{{{{OO{{{{{{{{{{{{{{{{{{aeeBeeBeeBeeBeeBeeBeeBeeBeeBeeBeeBeeBeeBeeBeeBeeBeeBe{{{{{{{{{{{{{{{{{{{{{{{{{OO{{{{{{{{zzzzzzzzzzaeeBeeBeeBeeBeeBeeBeeBeeBeeBeeBeeBeeBeeBeeBeeBeeBeeBezzzzzzzzzzzzzzzzzzzzzzzzOzzzzzzzzzzzzzzzzzzaeeBeeBeeBeeBeeBeeBeeBeeBeeBeeBeeBeeBeeBeeBeeBeeBeeBezzzzzzzzzzzzzzzzzzzzzzzOzzzzzzzzzzzzzzzzzaeeBeeBeeBeeBeeBeeBeeBeeBeeBeeBeeBeeBeeBeeBeeBeeBeeBezzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzaeeBeeBeeBeeBeeBeeBeeBeeBeeBeeBeeBeeBeeBeeBeeBeeBeeBezzzzzzzzzzzzzzzzzzzzzOzzzzzzzmmmmmmmmmmaeeBeeBeeBeeBeeBeeBeeBeeBeeBeeBeeBeeBeeBeeBeeBeeBeeBemmmmmmmmmmmmmmmmmmmmmmmmmmllllllllllleBeeBeeBeeBeeBeeBeeBeeBeeBeeBeeBeeBeeBeeBeeBeeBeeallllllllllllllllllllllllllllllllllllleBeeBeeBeeBeeBeeBeeBeeBeeBeeBeeBeeBeeBeeBeeBeeBeeallllllllllllllllllOlllllllllllllllllllaeeBeeBeeBeeBeeBeeBeeBeeBeeBeeBeeBeeBeeBeeBeeBellllllllllllllllllOllllllllllllllllllaeeBeeBeeBeeBeeBeeBeeBeeBeeBeeBeeBeeBeeBeeBeeBelllllllllllllllllOOllllllllllllllllllleBeeBeeBeeBeeBeeBeeBeeBeeBeeBeeBeeBeeBeeBeealllllllllllllllllOllllllllllllllllllllaeeBeeBeeBeeBeeBeeBeeBeeBeeBeeBeeBeeBeeBelllllllllllllllllOOllllllkkkkkkkkkkkkkkkeBeeBeeBeeBeeBeeBeeBeeBeeBeeBeeBeeBeeakkkkkkkkkkkkkkkkOkkkkkkkkkkkkkkkkkkkkkkaeeBeeBeeBeeBeeBeeBeeBeeBeeBeeBeeBekkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkaeeBeeBeeBeeBeeBeeBeeBeeBeeBekkkkkkkkkkkkkkkkOkkkkkkkkkkkkkkkkkkkkkkkkaeeBeeBeeBeeBeeBeeBeeBekkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkeBeeBeeBeeBeeakkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjj \ No newline at end of file diff --git a/examples/peripherals/dma/async_color_convert/main/async_color_convert_example_main.c b/examples/peripherals/dma/async_color_convert/main/async_color_convert_example_main.c new file mode 100644 index 00000000000..93a50b6f661 --- /dev/null +++ b/examples/peripherals/dma/async_color_convert/main/async_color_convert_example_main.c @@ -0,0 +1,119 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include "mbedtls/base64.h" +#include "esp_async_color_convert.h" +#include "esp_check.h" +#include "esp_heap_caps.h" + +#define EXAMPLE_WIDTH 96 +#define EXAMPLE_HEIGHT 64 +#define EXAMPLE_BASE64_CHUNK_LEN 96 + +/* These linker symbols are generated automatically for the file added by + * EMBED_FILES in CMakeLists.txt. They let the example treat the embedded + * raw .yuv asset as a byte array stored in flash. */ +extern const uint8_t sample_96x64_uyvy_yuv_start[] asm("_binary_sample_96x64_uyvy_yuv_start"); +extern const uint8_t sample_96x64_uyvy_yuv_end[] asm("_binary_sample_96x64_uyvy_yuv_end"); + +static void print_base64_payload(const unsigned char *encoded, size_t encoded_len) +{ + /* The payload is split into short lines so the UART log stays easy to + * parse from pytest and less likely to be damaged by very long lines. */ + printf("IMAGE_BASE64_BEGIN\n"); + for (size_t offset = 0; offset < encoded_len; offset += EXAMPLE_BASE64_CHUNK_LEN) { + size_t chunk_len = encoded_len - offset; + if (chunk_len > EXAMPLE_BASE64_CHUNK_LEN) { + chunk_len = EXAMPLE_BASE64_CHUNK_LEN; + } + printf("IMAGE_BASE64 %.*s\n", (int)chunk_len, (const char *)&encoded[offset]); + } + printf("IMAGE_BASE64_END\n"); +} + +void app_main(void) +{ + /* UYVY422 stores 2 bytes per pixel on average, while BGR/RGB888 uses + * 3 bytes per pixel. The example keeps the image size small so the + * buffers and UART payload stay beginner-friendly. */ + const size_t pixel_num = EXAMPLE_WIDTH * EXAMPLE_HEIGHT; + const size_t yuv422_size = pixel_num * 2; + const size_t rgb888_size = pixel_num * 3; + const size_t embedded_size = sample_96x64_uyvy_yuv_end - sample_96x64_uyvy_yuv_start; + + printf("Loading embedded UYVY image from flash...\n"); + printf("Embedded image size: %zu bytes\n", embedded_size); + assert(embedded_size == yuv422_size); + + /* The destination buffer still needs DMA-capable internal RAM because + * DMA2D writes the converted pixels into this memory region. */ + uint8_t *dst_bgr = heap_caps_aligned_calloc(64, 1, rgb888_size, + MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA | MALLOC_CAP_8BIT); + assert(dst_bgr); + + async_color_convert_config_t config = { + .backlog = 1, // because we use the blocking API, so only need 1 in-flight request at most + .dma_burst_size = 16, + }; + async_color_convert_handle_t conv_hdl = NULL; + + /* Install the async color convert driver with the DMA2D backend. + * The returned handle is used by later conversion requests. */ + ESP_ERROR_CHECK(esp_async_color_convert_install_dma2d(&config, &conv_hdl)); + + /* This request describes one full-frame conversion: + * - source buffer: UYVY422 image + * - destination buffer: BGR24 image + * - stride/height: layout of each image in memory + * - copy_width/copy_height: region to convert */ + async_color_convert_request_t req_yuv_to_bgr = { + /* DMA2D can read the source image directly from mapped flash, so the + * example does not need an extra CPU copy into internal RAM first. */ + .src_buffer = sample_96x64_uyvy_yuv_start, + .src_stride = EXAMPLE_WIDTH, + .src_height = EXAMPLE_HEIGHT, + .src_x = 0, + .src_y = 0, + .dst_buffer = dst_bgr, + .dst_stride = EXAMPLE_WIDTH, + .dst_height = EXAMPLE_HEIGHT, + .dst_x = 0, + .dst_y = 0, + .copy_width = EXAMPLE_WIDTH, + .copy_height = EXAMPLE_HEIGHT, + .src_color_format = ESP_COLOR_FOURCC_UYVY, + .dst_color_format = ESP_COLOR_FOURCC_BGR24, + .color_conv_std = COLOR_CONV_STD_RGB_YUV_BT601, + }; + + printf("Converting UYVY422 -> RGB888...\n"); + /* This example uses the blocking API for simplicity: the call returns only + * after the hardware conversion is finished and dst_bgr contains the result. */ + ESP_ERROR_CHECK(esp_color_convert_blocking(conv_hdl, &req_yuv_to_bgr, -1)); + printf("Converted image size: %zu bytes\n", rgb888_size); + + /* Base64 turns the binary BGR image into printable ASCII so it can be + * safely transported through the serial console and reconstructed by pytest. */ + size_t encoded_len = 0; + int ret = mbedtls_base64_encode(NULL, 0, &encoded_len, dst_bgr, rgb888_size); + ESP_ERROR_CHECK((ret == MBEDTLS_ERR_BASE64_BUFFER_TOO_SMALL) ? ESP_OK : ESP_FAIL); + unsigned char *encoded = calloc(encoded_len + 1, 1); + assert(encoded); + ESP_ERROR_CHECK(mbedtls_base64_encode(encoded, encoded_len + 1, &encoded_len, dst_bgr, rgb888_size) == 0 ? ESP_OK : ESP_FAIL); + + /* IMAGE_META plus the chunked IMAGE_BASE64 lines form a tiny text protocol + * that the pytest script understands and converts back into a PPM file. */ + printf("IMAGE_META width=%u height=%u format=BGR24 encoding=base64\n", EXAMPLE_WIDTH, EXAMPLE_HEIGHT); + print_base64_payload(encoded, encoded_len); + printf("Async color convert visual demo done.\n"); + + ESP_ERROR_CHECK(esp_async_color_convert_uninstall(conv_hdl)); + free(encoded); + free(dst_bgr); +} diff --git a/examples/peripherals/dma/async_color_convert/pytest_async_color_convert.py b/examples/peripherals/dma/async_color_convert/pytest_async_color_convert.py new file mode 100644 index 00000000000..eb0d0fb0eb6 --- /dev/null +++ b/examples/peripherals/dma/async_color_convert/pytest_async_color_convert.py @@ -0,0 +1,168 @@ +# SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: CC0-1.0 + +import base64 +import hashlib +import logging +import re +from dataclasses import dataclass +from pathlib import Path + +import pytest +from pytest_embedded import Dut +from pytest_embedded_idf.utils import idf_parametrize +from pytest_embedded_idf.utils import soc_filtered_targets + +IMAGE_META_PATTERN = r'IMAGE_META width=(\d+) height=(\d+) format=(\w+) encoding=(\w+)' +IMAGE_META_RE = re.compile(rf'^{IMAGE_META_PATTERN}$') +IMAGE_CHUNK_RE = re.compile(r'^IMAGE_BASE64 ([A-Za-z0-9+/=]+)$') +IMAGE_OUTPUT_NAME = 'async_color_convert_result.ppm' +GOLDEN_IMAGE_NAME = 'golden_result.ppm' +EXPECTED_PIXEL_FORMAT = 'BGR24' +EXPECTED_ENCODING = 'base64' +PPM_MAGIC = b'P6' +PPM_MAX_VALUE = b'255' + + +@dataclass(frozen=True) +class ImageMetadata: + width: int + height: int + pixel_format: str + encoding: str + + +@dataclass(frozen=True) +class RgbImage: + width: int + height: int + pixels_rgb888: bytes + + def __post_init__(self) -> None: + expected_size = self.width * self.height * 3 + if len(self.pixels_rgb888) != expected_size: + raise ValueError(f'Expected {expected_size} RGB bytes, got {len(self.pixels_rgb888)}') + + +def parse_image_metadata(meta_line: str) -> ImageMetadata: + match = IMAGE_META_RE.match(meta_line) + if not match: + raise ValueError(f'Invalid image metadata line: {meta_line}') + + return ImageMetadata( + width=int(match.group(1)), + height=int(match.group(2)), + pixel_format=match.group(3), + encoding=match.group(4), + ) + + +def collect_base64_payload(dut: Dut) -> list[str]: + payload_lines: list[str] = [] + while True: + match = dut.expect(r'(IMAGE_BASE64_END|IMAGE_BASE64 [A-Za-z0-9+/=]+\r?\n)') + line = match.group(1).decode('utf-8').strip() + if line == 'IMAGE_BASE64_END': + return payload_lines + + chunk_match = IMAGE_CHUNK_RE.match(line) + assert chunk_match is not None + payload_lines.append(chunk_match.group(1)) + + +def _bgr24_to_rgb888(raw_bytes: bytes) -> bytes: + rgb_bytes = bytearray(len(raw_bytes)) + for offset in range(0, len(raw_bytes), 3): + blue, green, red = raw_bytes[offset : offset + 3] + rgb_bytes[offset : offset + 3] = (red, green, blue) + return bytes(rgb_bytes) + + +def _encode_ppm(image: RgbImage) -> bytes: + header = b'%s\n%d %d\n%s\n' % (PPM_MAGIC, image.width, image.height, PPM_MAX_VALUE) + return header + image.pixels_rgb888 + + +def _load_ppm(path: Path) -> RgbImage: + ppm_bytes = path.read_bytes() + header_match = re.match(rb'^P6\s+(\d+)\s+(\d+)\s+(\d+)\s', ppm_bytes) + if not header_match: + raise ValueError('Invalid PPM header') + + width = int(header_match.group(1)) + height = int(header_match.group(2)) + max_value = header_match.group(3) + if width <= 0 or height <= 0: + raise ValueError('Unsupported PPM dimensions') + if max_value != PPM_MAX_VALUE: + raise ValueError(f'Unsupported PPM max value: {max_value.decode("ascii", errors="replace")}') + + pixel_data = ppm_bytes[header_match.end() :] + expected_size = width * height * 3 + if len(pixel_data) != expected_size: + raise ValueError(f'Expected {expected_size} PPM pixel bytes, got {len(pixel_data)}') + + return RgbImage(width=width, height=height, pixels_rgb888=pixel_data) + + +def decode_bgr24_base64_image(metadata: ImageMetadata, payload_lines: list[str]) -> RgbImage: + if metadata.pixel_format != EXPECTED_PIXEL_FORMAT: + raise ValueError(f'Unsupported pixel format: {metadata.pixel_format}') + if metadata.encoding != EXPECTED_ENCODING: + raise ValueError(f'Unsupported payload encoding: {metadata.encoding}') + + raw_bytes = base64.b64decode(''.join(payload_lines), validate=True) + expected_size = metadata.width * metadata.height * 3 + if len(raw_bytes) != expected_size: + raise ValueError(f'Expected {expected_size} decoded bytes, got {len(raw_bytes)}') + + return RgbImage(width=metadata.width, height=metadata.height, pixels_rgb888=_bgr24_to_rgb888(raw_bytes)) + + +def save_ppm_artifact(image: RgbImage, output_path: Path) -> None: + output_path.parent.mkdir(parents=True, exist_ok=True) + try: + output_path.write_bytes(_encode_ppm(image)) + except OSError: + logging.exception('Failed to save async color convert artifact to %s', output_path) + return + + logging.info('Saved async color convert artifact to %s', output_path) + + +def rgb_pixel_digest(image: RgbImage) -> str: + digest = hashlib.sha256() + digest.update(image.width.to_bytes(4, 'big')) + digest.update(image.height.to_bytes(4, 'big')) + digest.update(image.pixels_rgb888) + return digest.hexdigest() + + +def assert_image_matches_golden(result_image: RgbImage, golden_path: Path) -> None: + assert golden_path.is_file(), f'Golden image not found: {golden_path}' + golden_image = _load_ppm(golden_path) + + assert rgb_pixel_digest(result_image) == rgb_pixel_digest(golden_image), ( + f'Generated image does not match golden file: {golden_path.name}' + ) + + +@pytest.mark.generic +@idf_parametrize('target', soc_filtered_targets('SOC_DMA2D_SUPPORTED == 1'), indirect=['target']) +def test_async_color_convert_example(dut: Dut) -> None: + dut.expect_exact('Loading embedded UYVY image from flash...') + dut.expect(r'Embedded image size: \d+ bytes') + dut.expect_exact('Converting UYVY422 -> RGB888...') + dut.expect(r'Converted image size: \d+ bytes') + + metadata = parse_image_metadata(dut.expect(IMAGE_META_PATTERN).group(0).decode('utf-8')) + + dut.expect_exact('IMAGE_BASE64_BEGIN') + payload_lines = collect_base64_payload(dut) + + result_image = decode_bgr24_base64_image(metadata, payload_lines) + output_path = Path(dut.logdir) / IMAGE_OUTPUT_NAME + save_ppm_artifact(result_image, output_path) + assert_image_matches_golden(result_image, Path(__file__).with_name(GOLDEN_IMAGE_NAME)) + + dut.expect_exact('Async color convert visual demo done.') diff --git a/examples/peripherals/i2c/i2c_basic/README.md b/examples/peripherals/i2c/i2c_basic/README.md index 4ae7c6ff080..8aa2d3cf750 100644 --- a/examples/peripherals/i2c/i2c_basic/README.md +++ b/examples/peripherals/i2c/i2c_basic/README.md @@ -15,7 +15,7 @@ If you have a new I2C application to go (for example, read the temperature data ### Hardware Required -To run this example, you should have an Espressif development board based on a chip listed in supported targets as well as a MPU9250. MPU9250 is a inertial measurement unit, which contains a accelerometer, gyroscope as well as a magnetometer, for more information about it, you can read the [datasheet of the MPU9250 sensor](https://invensense.tdk.com/wp-content/uploads/2015/02/PS-MPU-9250A-01-v1.1.pdf). +To run this example, you should have an Espressif development board based on a chip listed in supported targets as well as a MPU9250. MPU9250 is a inertial measurement unit, which contains a accelerometer, gyroscope as well as a magnetometer, for more information about it, you can read the [datasheet of the MPU9250 sensor](https://download.mikroe.com/documents/datasheets/PS-MPU-9250A-01-v1.1.pdf). #### Pin Assignment diff --git a/examples/peripherals/jpeg/jpeg_encode/CMakeLists.txt b/examples/peripherals/jpeg/jpeg_encode/CMakeLists.txt index b452de017de..d6edee83d66 100644 --- a/examples/peripherals/jpeg/jpeg_encode/CMakeLists.txt +++ b/examples/peripherals/jpeg/jpeg_encode/CMakeLists.txt @@ -5,4 +5,4 @@ cmake_minimum_required(VERSION 3.22) include($ENV{IDF_PATH}/tools/cmake/project.cmake) # "Trim" the build. Include the minimal set of components, main, and anything it depends on. idf_build_set_property(MINIMAL_BUILD ON) -project(jpeg_encode) +project(jpeg_encode_example) diff --git a/examples/peripherals/jpeg/jpeg_encode/README.md b/examples/peripherals/jpeg/jpeg_encode/README.md index 82aa2704b4a..a8f9271a2aa 100644 --- a/examples/peripherals/jpeg/jpeg_encode/README.md +++ b/examples/peripherals/jpeg/jpeg_encode/README.md @@ -5,25 +5,23 @@ ## Overview -This example demonstrates how to use the JPEG hardware [encoder](https://docs.espressif.com/projects/esp-idf/en/latest/esp32p4/api-reference/peripherals/jpeg.html) to encode a 1080p picture: +This example demonstrates how to use the JPEG hardware [encoder](https://docs.espressif.com/projects/esp-idf/en/latest/esp32p4/api-reference/peripherals/jpeg.html) to encode a 720p raw image. -This example makes use of the hardware-based JPEG encoder. If you have multiple pictures that need to be decoded, such as *.rgb -> *.jpg, you can use this example to accelerate encoding. +The example performs: -## How to use example +- Embedding `main/assets/esp720p.rgb` into the final firmware image +- Letting the JPEG encoder read one 1280x720 `bgr24` frame directly from flash +- Encoding the frame into JPEG with the hardware encoder +- Base64-encoding the resulting JPEG bitstream and printing it with machine-parseable markers +- Letting pytest rebuild `jpeg_encode_result.jpeg` and compare it against `golden_output.jpeg` -### Hardware Required +## Hardware Required -* An Espressif development board based on a chip listed in supported targets -* A USB cable for power supply and serial communication -* Computer with ESP-IDF installed and configured -* The raw picture is the only source that you need to prepare (We have an [esp1080p.rgb](https://github.com/espressif/esp-idf/tree/master/examples/peripherals/jpeg/jpeg_encode/resources/esp1080.rgb) in resources folder, you can also get it from [jpeg_decode](https://github.com/espressif/esp-idf/tree/master/examples/peripherals/jpeg/jpeg_decode) example). -* ffmpeg can also be used to produce rgb picture. For example `ffmpeg -i input.jpg -pix_fmt rgb24 output.rgb` +Any board based on a supported target can be used, provided it has enough flash to hold the embedded 720p raw asset and the application image. The example defaults are configured for a 4 MB flash layout and PSRAM-enabled builds. ### Build and Flash -Before you start build and flash this example, please put the image `esp1080.rgb` in your sdcard. - -Enter `idf.py -p PORT flash monitor` to build, flash and monitor the project. +Run `idf.py -p PORT flash monitor` to build, flash and monitor the project. (To exit the serial monitor, type ``Ctrl-]``.) @@ -31,27 +29,37 @@ See the [Getting Started Guide](https://docs.espressif.com/projects/esp-idf/en/l ## Example Output -```bash -I (1114) jpeg.example: Initializing SD card -I (1114) gpio: GPIO[43]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0 -I (1124) gpio: GPIO[44]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0 -I (1134) gpio: GPIO[39]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0 -I (1144) gpio: GPIO[40]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0 -I (1154) gpio: GPIO[41]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0 -I (1164) gpio: GPIO[42]| InputEn: 0| OutputEn: 1| OpenDrain: 0| Pullup: 0| Pulldown: 0| Intr:0 -I (1414) gpio: GPIO[42]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0 -Name: SD64G -Type: SDHC/SDXC -Speed: 40.00 MHz (limit: 40.00 MHz) -Size: 60906MB -CSD: ver=2, sector_size=512, capacity=124735488 read_bl_len=9 -SSR: bus_width=4 -I (1434) jpeg.example: infile_1080p:/sdcard/esp1080.rgb -I (5174) jpeg.example: outfile:/sdcard/outjpg.jpg -I (5284) jpeg.example: Card unmounted -I (5284) main_task: Returned from app_main() +```text +Loading embedded BGR24 image from flash... +Embedded raw image size: 2764800 bytes +Encoding BGR24(raw) -> JPEG... +Encoded JPEG size: 30795 bytes +JPEG_META width=1280 height=720 format=JPEG encoding=base64 size=30795 +JPEG_BASE64_BEGIN +JPEG_BASE64 ... +JPEG_BASE64 ... +JPEG_BASE64_END +JPEG encode demo done. ``` +## Pytest Visual Check + +The accompanying `pytest_jpeg_encode.py` script captures the `JPEG_META` and `JPEG_BASE64` output, reconstructs the encoded JPEG, and saves it as: + +- `dut.logdir/jpeg_encode_result.jpeg` + +It also compares the generated JPEG with `golden_output.jpeg`. This turns the example into both a functional regression test and a host-side artifact generator that makes the encoded result easy to inspect. + +## Replacing The Embedded RGB Asset + +If you want to regenerate a compatible raw frame from another input image, one simple workflow is: + +```bash +ffmpeg -y -i input.jpg -vf scale=1280:720 -pix_fmt bgr24 -f rawvideo main/assets/esp720p.rgb +``` + +After replacing the raw asset, rebuild and flash the example. The firmware will emit the encoded JPEG as base64, and pytest will save the reconstructed JPEG artifact automatically. If the new image is intended to become the expected output, update `golden_output.jpeg` as well. + ## Troubleshooting (For any technical queries, please open an [issue](https://github.com/espressif/esp-idf/issues) on GitHub. We will get back to you as soon as possible.) diff --git a/examples/peripherals/jpeg/jpeg_encode/golden_output.jpeg b/examples/peripherals/jpeg/jpeg_encode/golden_output.jpeg new file mode 100644 index 00000000000..c051b55a5e1 Binary files /dev/null and b/examples/peripherals/jpeg/jpeg_encode/golden_output.jpeg differ diff --git a/examples/peripherals/jpeg/jpeg_encode/main/CMakeLists.txt b/examples/peripherals/jpeg/jpeg_encode/main/CMakeLists.txt index 491dcb7a84f..468dc1f84ae 100644 --- a/examples/peripherals/jpeg/jpeg_encode/main/CMakeLists.txt +++ b/examples/peripherals/jpeg/jpeg_encode/main/CMakeLists.txt @@ -1,3 +1,5 @@ -idf_component_register(SRCS "jpeg_encode_main.c" - PRIV_REQUIRES fatfs esp_driver_jpeg - INCLUDE_DIRS ".") +idf_component_register(SRCS "jpeg_encode_example_main.c" + PRIV_REQUIRES esp_driver_jpeg mbedtls + INCLUDE_DIRS ".") + +target_add_binary_data(${COMPONENT_LIB} "${CMAKE_CURRENT_LIST_DIR}/assets/esp720p.rgb" BINARY RENAME_TO "esp720p_rgb") diff --git a/examples/peripherals/jpeg/jpeg_encode/main/Kconfig.projbuild b/examples/peripherals/jpeg/jpeg_encode/main/Kconfig.projbuild deleted file mode 100644 index 7677cd73bd4..00000000000 --- a/examples/peripherals/jpeg/jpeg_encode/main/Kconfig.projbuild +++ /dev/null @@ -1,18 +0,0 @@ -menu "JPEG Encode Example menu" - - config EXAMPLE_FORMAT_IF_MOUNT_FAILED - bool "Format the card if mount failed" - default n - help - If this config item is set, format_if_mount_failed will be set to true and the card will be formatted if - the mount has failed. - - config EXAMPLE_SDMMC_IO_POWER_INTERNAL_LDO - depends on SOC_SDMMC_IO_POWER_EXTERNAL - bool "SDMMC IO power supply comes from internal LDO (READ HELP!)" - default y - help - Please read the schematic first and check if the SDMMC VDD is connected to any internal LDO output. - If the SDMMC is powered by an external supplier, unselect me - -endmenu diff --git a/examples/peripherals/jpeg/jpeg_encode/main/assets/esp720p.rgb b/examples/peripherals/jpeg/jpeg_encode/main/assets/esp720p.rgb new file mode 100644 index 00000000000..1b1dd554ee4 --- /dev/null +++ b/examples/peripherals/jpeg/jpeg_encode/main/assets/esp720p.rgb @@ -0,0 +1,393 @@ +孳監錓犑叕܏܈児⇑ߎ朤褬oqQODB87/.0+.)2-2-&-%,)//5?B>AKIVTc^idrpӄ大?B66440*/)0'/&0+1,%,%,)-)-/0-.31422/.+/0237;6:EIVZǂ֢hm9>-5'/(&)'/-/-*2*2".".(0(0/1/1/1/1)/)/'1'1)0)0//..8697JH][vzQK<6+/,0),$',2,2'8%6$1%2,3,323232222////+0+0+0+01/1/623/4231:6FBaZyr姧>4>4(,)-'.'.,4+3"4 2'4(5/4/4434351513/3/-/-/,.,./,/,2-2-53648192?+;'F<]S݁Чa\4<19/5/5/001).+0)2)2.2.221214/4/2/2/....-----,-,0,0,/0.//+1-3#4$9-5)85>;gmq}OR@C8.8.2&6**.)-+.+.,-+,,--.-/-/.0.0././,,,,****)+(*),+.)*,-3/1-7250:785OQqsﳽhVO=>+=*)**+*,)+&*'+&-&-'1$.)/,2.1.1,/,/*-*-).).(2'1)6)6+.+./+/+5.2+0,0,OUou紸㵱|oTG,*0.,.+-%+&,$-#, -"/+2+2.2.2,1,1+/+/*.*.(2(2'5'5).).++++.,.,//..5230KLwxrvFGBCJDUOdZj`ᅌ˛ᵽ{zUT5689-2-2*3*3%0&1-3*0.0.0-1-1-1-1,0,0+0+0)0)0*.*.+.+.,0,0-2-281:36677XY|}칺HI:;,0,0*,+-./-.4:=CHMSXvyՓ_a6969.2-1*/*/*-,/..../////////0/00.0.-----0-0-2-2-3-3-3-34,3+2334-3)/YZ͒YI@04/:5$,$, 0 0$8$8--((+),*9472LC^UzpٔWZ33--1/42-+.,-,-,0-0-1.1.2/2/2.2.0/0//2/2.4.4.1.1//..3+6.)*%&'-)/87<;}v~y˷hg<;/*4/....(-',%.&/)3)3((((--...--,21/.1/31KK[[~ΧTO=0@3-&-&&,&,&0'1*,*,1,1,4/4/10101.1.0-0-....,0,00/.-+,,-*+&'02+-/3GK֕UZ3)WM굾@K#',0*.*.,0,0-0-0.0.0,.,.-/-/,/,/+/+/*/*/).(-23./7441HFqo⢢{TK..44+3(0",%/'.'./-/-2-3.10102+2+0*0*.-.-+1+1,0,0-----,,++/+/&1&1>?`aOJ<7:/7,c_zyAB:;-0/2+.,/,0,0,0,0,0,0,0,0-/-/-0-0-0,/+/,0,1,1.,,*3.3.51<8MIYUppϐqJX),.11,-(*0*0,1,1.*/+.0.0/./././..0.0,2,2,1,1,.,.,-+,-0/2,5(1=9@<[Ufd.%=41.-*?1YK㴺_Y?94/72+--/,/,/,0,0,0,0,0,0,0+/+-+-+.+.+.+.,0,0-1,0*+&'"!)(1..+211003993/62+0+0).(-)**+)1)1*3*3-3-3/0/0-3-3,0,0,.,.)+(*-1.2,(-)6,-#B?da۹y1/64%,-4'4"/@29+iu9:78,/-0+,./0..,,0,0,0,0,0,0,0,0,0,0,.,.,/,/,/,/,0,0-0-0)1(0$/$/(2&0'2&1(6*8+2(/2)0'74OLԇ׭EA<8.//0'+(,++++&.'/(4'3,1,10-0--3-3,1,1,/,/*,+-.1,/'))+*+./9854`_yoC9..//'2'2'.%,71a[rjA9,&3-&1%0*2*2/,/,,0,0,0,0,0,0,0,0,0,0,.,.,/,/,/,/,0,0,0,0-3-3,3,3+1+1)0)0)1(0*0*0.+.+5/3->9GBstgk22334040-),()/*0(1'0,.,.1+1+-3-3,1,1,0,0,.,.-/-/&.&.(4(43333;0B7՘<693-/&(+1.42.)%jlȶno3+;3*!-$&3&3*5*5.-.-,0,0,0,0,0,0,0,0,0,0,.,.,/,/,/,/,0,0,0,0/0/00/0//,/,-,-,,+,++0*/(2)3-+-+4+3*?9E?qq̠QU7+:.5061-1+/'.(/,,,,1+1+-2-2,1,1,0,0,/,/-.-.'2'2'9'9-0.1=-6&KLPZ0156),&)12347/QI乿VX570-63*#0)*2)1+5+5,/,/,0,0,0,0,0,0,0,0,0,0,.,.,/,/,/,/,0,0,0,0././/-/-.,.,-+-+-+-+,1,1)7)7)-)-.-/.1,3.:54/nxmbH=94;6/1.0(-).,-,-1-1--1-1,1,1,1,1,0,0,/,/+2+2*6*6+.+.1'2(>>;;ɀTW7:,4(0'+'+97/-YW&*7;*.&*1/0.////-1-1*1*1-/-/-0-0-0-0-0-0-0-0,.,.,.,.,/,/,0,0,0,0,1,1+1+1+/+/+/+/,0,0,2,2+3+3&1&1"-!,.3)./,30=7ICrsϟMJ85789:&+*/,/,/1111-0-0-1-1,1,1,0,0,0,00000././*+*+,,,,4387BEwz.*1-*3&/)+)+84EAҤ{(,+/%1)515152-2--.-.*1*1,0,0,0,0,0,0,0,0,0,0,.,.,/,/,/,/,0,0-0-0*3*3)4)4)3)3*4*4+6+6,2+1)*)*#0#0"4#5(3(3,.*,:'8%<3KB}ıgd4635-2+0+1+1/203-0-0,/,/-1-1,1,1,0,01.1./)/)+**)(/+2..00:583υx9@(/)0/.)(9,@vx:B)1,7(31..+@5>3؃~~-*52.2.2.1.1.1.1/1/1.1.1*1*1*1*1*0*0*0*0*/*/,0,0,0,0,0,0,0,0,0,0-/-/-/-/-/-/-/-/-/-/-/-/-0-0-0-0-0-0-/-/-0-00/0/+,*+,.)+7:ORӓ=@7:+0+0/7'/)0*1+.+.-0-0-2-2,/,/,/,/,0,0-0-0-0-0,/,/-/+-6734ʂss*2+3+3'/5410>=VU{.(600/0/101010102020////*/*/*0*0*/*/*0*0*0*0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0-0-0+1+1'0'0'0)249,179_aٯ_d.)72-,-,),),*4*4,4,4,.,.,2,2,2,2,/,/,0,0,0,0,/,/+1-336.1D9y~{EB(0-5)1'/64.,^dy-$5,1)1)1*1*1+1+2+2+/,/,*.*.+/+/+/+/+0+0+0+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)2)2$0%1%0$/)/-38721H@rjohB;/'6.*'*')7)7*5*5,,,,,4,4,3,3,/,/,0,0,0,0-/-/*2)1,004>)L7HC72)/(.(,-151;7֛x*%2-,*,*-,-,---------.-.,-,-,.,.,/,/,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0+0+0(2(2'2(3)-*..),'9.5*]_˜B6>2-)-)*4*4)4)4*-*--2-2-2-2,/,/,0,0,0,0-/-/)3'1%+'-=(:%[Uhq0*4.+.-0()23;7miw(*/1(3'2'2&1(3)4)6(5(/*1.--,..0001./.0.0.0.0+/+/,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0-/-/2-2-././+0+0)+*,.(-'5*4)78CDzȷb\2.2.-.,-)/*0*0*0-.-.-/-/,0,0,0,0,0,0-0-0(1(1"+$-4)3(C<]V?G*&1-,,((10*)\[Ʋx*-03&2&2(4(4(4&2$0%1+2+2//00./-.--........--+/+/+/,0-1,0+/,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0-/-/6*6*4-4-10100/0/1-1-/*,'12/0:=QTѡST:;0*71-/+-*3*3,+,+-,-,,0,0,0,0,0,0-0-0*.*.%-%-././<772usAI19(*-/0-,)::44ϟ-+31),'**++,**++,++***//04,0+-.001-.......--+/+/+/,0-1,0+/,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0-/-/6*6*5,5,4/4/21212121+.),*.-1//..JG|yin:4=7-,.-)2)2++++,,,,,0,0,0,0,0,0-0-0,-,-)/)/+5*4520-B<|v%)/3)/(.3052;=fh+%710-1.3.4/6/4-3)5+8260/304/1/1//000.0.0./-,/,/+/,0-1,0+/,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,00.0.1-1-0.0.0/0/0101*0+1)1'/),,/9674hmƢhhCC1122)-)-*.*.,/,/,0,0,0,0,0,0,0,0-.-..0.0)4)4+*/.<6A;ה\S&'+,&/$-622.S\ĩcdkldo_jW^PWHLAE9:783636(-).*-*-**,,/,/,/+/++/+/+/,0-1,0,/-0-/-/-/-/-/-/-0-0-0-0,0,0,/,/,/,/,0,0,0,0,0,0,0,0(2(2*/*/,,,,,-,-+0+0%1%1&3&3(/(/202098JI˦r~2648''((*3*3-4-4,/,/,/,/,0,0,0,00/0/3131(2(2((&&612-X]nfH@)*"#%/$.:572ғoy]gOVGNAE:>//--/-1/.+/,.,.,-.-.+0).&1(3,5+4+1*0(,*.*.*..0.0.0.0,0,0-/-/-.-.-/-/.1.1(3(3)/)/+-+-+-+-*/*/)3)3%0%0)1'/----5342OMxv|~EG/)0*&1%0(3(3,0,0/1/1////,/,/-0-02121*/*/)+(*,,))=@VYA@32'+$(+3*2B=543)9/6*6*0*,&&-+2&7&7(3(3*.*.+1+1+2+2+2+2+2+2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0-/+-97-+VU6677*-*-(/(/-/-//-/-+/+/*0*0,/,/,0,0+/+/,1-2,1+0<=ghUX;>),/2+2(/57EGְg_ME4-6/3-600-1.)2)2&4&4'1'1+/+/*2*2*4*4*2*2)0)0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0-0-0),-0+*.-95?;͗}EB+,01)/)/+,+,,-,-,0,0,0,0,/,/,0,0+/+/,1-2,0,0;:43ɤ4657(,(,&.-5:;uvmtCB875)6*-*/,(2'1'2'2*++,+2*1)4)4(1(1'.'.,/,/,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0-0-0'-*0#)$*74/,JHsq;=68+1*0,,***-*-,0,0-0-0,0,0,0,0+/+/,0-1,/+.100/`d}*,57%+'-08*2SSRJA91*2+13-/(**,-,-,)-(,'.)0(.(.',',,/,/,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0-0-0(0(0"1!0.1.1:.;/}vFK2604//**(-).,0,0.0.0,0,0,0,0,0,0,0,0,/,/.-/.<@{QY$'.1$)#(2;'0†ghEF1.:73-0*.,.,..00,/*-),),'+'+,/,/,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0-/-/*0*0%6%6*1*13#/C5hZ;>:=+*21)0(/,0,0....,1,1,1,1,0,0,0,0,/,/,.+-7;FJyz89),+.(,&*4=9BƻihGF3,3,447720/--+.,+-+-).).,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,/,/-1-1+6+6)2)2+#,$9.3(munp@B8800'1(2+0+0/-/-,2,2,1,1,0,0,0,0,/,/-/*,,1',yVO=612./+-+-2:aijmJM362585850,1-,/,/+3+3-0-0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0////3131)0)0).&+3254=<]\⵼uv..22(3(3+/+//+/+,2,2,1,1,0,0,/,/-0,/,/*-$'*-MXA3C51212+,*+6@ُ|yc_[WUORLRKTMRKRKTOYT_]igzӈᖟܩck8@.-542.2..1.1,7,7-0-0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,02/2/8,8,*/*/%8#6.7/88,8,efTR42'4'4*/*//+/+,2,2,1,1,0,0,/,/-0,/,0.2(*)+=Gr|vy3096"').(0$,om|vSI?57,<14231.0.0/001/0/0.0-/-/+-*/*/1589-<0,+0/$3&5$2#1+.,/1+1+1,1,----,/,/,0,0,0,0,0,0,0,0././1-1-,/,/)2*3/5+11-3/@7SJ׽[g,/-0,++*+0,1(5(5,1,1-,-,,+,++/+/'/(0.-,+88MMݾPZ-2/4#%+'4,9ܮgd@7<31&1&0'0'+-,.(/(/)/)/)/)/)/)/(.'-&)&),--.320/68=?ORZ]zΘf\F<2-50&-'.!/-'++/4.3-2-2-----,/,/,0,0,0,0,0,0,0,0,0,0+0+0-0-0-/-/+.,/,/+.:07-efDA?<0)0)+4)2'5'5+2+2-,-,,+,+*/*/%.%./.0/5/3-}>G01/0$('+(5CPgh=>>?3322,/.1/2-0*/+0*/*/*0*0*0*0*0*0+0*/).(-..//638587437575?8A:QNmj➡黾KH740(1)+,()'+)-(/*1,3,3,1,1-0-0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0+/7542>9faJG.)50+.,/)3)3*3*3,0,0,/,/*1*1&.&.0101611,Wd\c290.0.++((1>ivrx89231414&-*1&0%/#/$0(/)0+/+/*0*0*0*0*/*/*0,2,2,2./010..,3.4/40407(4%3(4)C@OLnr•|{I=>22(2(,1-2"3"3&6&6.4.4.1.1,/,/,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0+/-2,1801)ql736221.-*0+1+2+2+3+3+2+2,1,1'.'.0404402.@E?E06.+-*-,)(?LAI5=11--*.)-)5'3"0"0#1%3+2(/+0+0+0+0+0+0*/*/+0-2/3-1,/-0..---+-+-*,)/)/)2-1,737375;9SOws\X=2=2-2.3 2 2"3"3-4-4-/.0,/,/,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0+/&0%//*3.@9rkjh=;2,2,-.-.+0+0*3*3,3,3,/,/).).16162//,<8[W,2,2,)+(1/+)Zew{AE,4(0%%''+/(,'0(1'1(2(0(0).*/+0+0+0+0+0+0*/*/,0,0.0-/*.+/*-+.),(+(+(+'/*2.3,1..113/3/5+1'=8VQ⚚upTO/247!-".!+!+,0,013.0,/,/,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,/+. ,++/*.511-}yff0(=5.).),,**)1)1+2+2,,,,*-*-17171.0-;37/۲gq'-(.*)*)742/ׅ8271(-',*,*,-/-//2/2/0/0/,/,+.+.*0*0*0*0*0*0*0*0,0,0....-0-0+1+1)1)1'2'2&4&4'2(3+0+0/./.1-1-*+*+BGLQԊʺWX>?&'-.%'$&22//,/.1,/,/,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,/,/#,#,'5'52402JBɂzJ@=31*1*-)-))/)/*1*1.*.*,.,.1515/..-81-&㇌CM).+0,-'(95B>ׯutCB+#2*())*,0,0020232323/3/2+2+,.,.*0*0*0*0*0*0*0*0,/,/.-.-,1,1,3,3*4*4*3*3'1'1(0(0*0*0,2,2.4.4%0'2-6$-?9RLҏܽyy934.3.+&-,21*-.1,/,/,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,/,/)+)+'9'9+2/68/=4ڜE=5/4.)$.)%+'-*0*0.).)....1212,0*.4/*%]a4>+0(-+2&-97XV|5847.*.**).-,2,2020240403/3//-/-+/+/*0*0+0+0+0+0*0*0,/,/.....1.1.3.3-3-3-2-2*.*.*-*-+/+/,3,3,7,7'3&2&-&-2.0,C7RFوѺQL61>943/.,/-0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0-/-/1,1,)7)7'1$.813,RW~78561--)&-&-*2*2/,/,/./.2-2-+1,2((00IJgr4?-2(-(4'3;;uuqr?@'/,4*.'+2,60)1)1.1.12/2//./.(1(1)0)0+0+0*0*0*0*0*0*0+0+0-0-0/1/1/1/10.0.1,1,////.-.-....-/-/-3,2.01302-/(++.31,*86MKюVX:<./23.1+.-/-/,/,/,0,0-0-0-0-0,0,0,0,0,0,0,0,0././9-9--2-2$-$--,/.74TQPY2;.-+*(0&.)5)5/0/00/0/2)2)(109%**/:9vuLT2:,0,0)3+5?>5162%/&0*3+4834/'0(1+.+..-.-,/,/%2%2(1(1+/+/+0+0+0+0+/+/+0+0,1,1.1.100001+1+2)2)02020000/,/,.*.*.,-+4+5,100/&2'3*2'/4310DBXVڠox0224.0,.+/,0-0-0-/-/,/,/,0,0,0,0,0,0,0,0,0,0.0.0;/;//-/-$+(/)-,0824.׀vDO)(21%/&0)5)5020200001(1()3(2+2*142XVA>63/,1.15*.OQĥUU*)-,+-)+/2/2,/,/,/,/,/,/-/-/,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0-0-0-0-0-/-/-/-/-0-0-0-0-/-/-/-/-/-/-/-/-0,/,0-1,0+/-0-0,0)-5?Xb~tG=0*4.'2)4,206.(.(++++*2*2-/-/,0,0,0,0,0,0,0,0././-/-/,/,/,/,/-/+-=8vq:0B8$/(3(.(.0101-0-0-,-,,.(*-2+0-8-8ڦ3/40/*6134./_d˽ab23,.+-(+*-0202*.*.,/,/-0-0,0,0-0-0-0-0-0-0-0-0-0-0-0-0-0-0-0-0,0,0,0,0,0,0,0,0,0,0,/,/,0,0,0,0,0,0,0,0,0,0,0,0-0-0-0-0,/,/*/).-6#,FK;8300505)-.2,+,++.+.+2+2,/,/,0,0,0,0,0,0,0,0,0,0,0,0-0-0-0-0+.+.40;7䖤ha5.*/+0(.(.././-0-0,.,.----.2+/(5!.،t*)32.)505432x78;<(0'/(/)00303+.+.,/,/,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,/,/,1+006/588==jhƨRV1/7531-+(/)0+2+2.0.0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,/+.53.,[_OQ--..(2(2+/+/-1-1,/,/+--/,1+0-6'0uwdm++11/+1-8484יe`-126"-(3%/(20404,.,.,/,/,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,/,/,1,1-3.42020;8HE܍NG<51,-(%2&3+4+41.1.,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,/,//102<7a\0,62*2*2(/(/,2,2,0,0+-.0,0+/)0%,ZYXa-/+-2/-*:3:3պ~A<,2%+"2 0(4(40404,.,.,/,/,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,/,/-1+/+/-1()'(,.(*CGhl۶mg+,23&1%0-0-02-2-,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,/,/,2-39090\Y:7-1-1&/&/,2,2,1,1,/-0,/,/'-*0FEGM+.*-/-.,;4D=QL61(.'- 1.)5(41313+/+/,/,/,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0-0-0,.,.%,$+&-&-68/1XVġJS09)--1.'0)1+1+,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0-/-/+5(21*/(LMGI0-30%/(2+2,3,1,1,/,/+.,/*-,/CAtr<@(.(.,,..B;_X8350',',0 1*2*21111*/*/,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0-/-/,-,-"-"-%/!+01454+:1ٌ[i.,200%2'4/2-,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0-/-/(3&1,)1.9:TU5.70&0)3,2,2,1,1,/,/,/,/+.*-<;SRxx::'0&/*,+-G@zsci/+3/).).#2#2,1,11/1/)0)0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,/,/+-+-'1'1'1)3*,)+1*.'GDkhUR;82(7-/-/-,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0-/-/)4,7'*+.56./MH:5)1'/,0,0,0,0,/,/,/,/+.+.74=:gf98&1$/).).GBAC/-20+/,0%2%2/-/-2-2-'2'2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0-0-0+/+/*/*/.1.1-./0/1+-(,%):662gjce1.85//--+/,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0-0-0(..4*0)/741.HGEG*./3-/+-,/,/,0,0,0,0+/+/5353۾WU75&3$1)/(.ID}970/.-0202(2(22+2+3+3+%3%3+0+0,0,0,0,0,0,0,0,0.0.0-0-0,0,0,0,0-0-0-/-/-/-/-0-0,0,0,0,0,/,/-0-0-0-0-0-0-0-0-/-/-/-/,0,0,0,0,0,0-0-0*0*0*/*/4-3,9/8.'++/#5!33,:3E>[TշGN07(+*-,0,0-/-/-0-0-0-0,0,0,0,0,0,0,0,0,0,0,0,0')*,-4-41*.'@7_Vep%$.-++++-.-.-0-0,0,0+/+/302/ߤDA85&4#1).',SQd_50.1+.0101+1+10,0,0,0,(2(2,0,0-0-0.0.0,/-0,0,0%-%-&,&,..,,+,+,(+(++1+1)0*1+.+.+,*+./-.-/-/,0,0+.+.+.+.*/*/*2*2,1,1,/,/,0,0,0,0-0-0+0+0+0+03-3-4+3*/3.20%6/2,/813,YVPY/'5-,,..)/(.)0)0,/,/././-0-0,0,0,0,0,0,0,0,0*.+/+3-5.'0)?53)Ն<53,.-,+*/).,0,00/0/././20/-ڐ1/97)2#,,-'(deQI3+*1(/,/,/-/-/,0,0,0,0-/-/-/-/////00//,0,0-2+0+.(.!'0)2+*'-*-0,/+4+4'5'5*+*++',(0-.+.1/2+1+1)-)-(,(,'0'0%6%6*2*2-/-/,0,0,0,0,0,0,0,0,0,0,0,0,0,0+/,0-0,/&5&5(+(+/1;=ᘢN9D//*,'%0'2#2#2*.*.1/1/-0-0,0,0,0,0,0,0-/-/*2*2/3/32-2-1,*%UWrgC81/-+$0%1+1+15-6.1.1.45/0Ո1/86'1",./'(moE=91&-&--/-/,0,0,0,0,0,0,/,/...../././-.,/,/.2,0&1%01357DAKHAC7911553377-3,2+.,/-.,-+0*/*4*4.2.2,2,2,1,1,/,/*,*,,.,.,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)/)/).).3401OOG>/.32).+0&0$.*/*/////-0-0,0,0,0,0,0,0-0-0*0*0/1/10,/+/*.)>?YZQJ-,21$/(3*/(-3.3.....24.0~}1/86$.!+0.)'vx>49/&.'/-0-0,0,0,0,0,0,0-0-0.0.0-0-0-0,/,0,0/2.11414a`ō鯯Ꝣ㒗؈|xe]ME3827/4/4/326-3/5-3)/.+/,/0/0/3/3.0.0-*-*----,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,/,/*)*)*0*00033:2LD١578:2/*'*.'+*0*0////-/-/,0,0,0,0,0,0,0,0+-+-0.0./+/+-*/,5689ʜ-,87)3&0'-'-2.2.,.,.02-/pn1166",!+1/*(~8-9.(/+2,/-0,0,0,0,0,0,0-0-0.0.0,0,0,0+/+0+003.1=>depwU\?D6;*+.//,/,/&0'0,0,/1/1-2-2-.-.-/-/,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,/,/+)+)*0*0.-+*:/3(JJej6;7.6-)&+(*1*1////-/-/,0,0,0,0,0,0,0,0,,,,1-1-/,/,()()4624XRFF66'/&.',(-.-/.*.*..1,/ba1256+ ,51-)0&:0'/)1,/-0,0,0,0,0,0,0-0-0.0.0+/+/*0*0+0+012,-Y]c^LG:.4(.'0)/+.*,,--*1*1(4(4,2,2,/,/,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,/,/,-,-*.*.,+,+2-7244BB՞^e5(=0+%/)+1+1,/-0-/-/,0,0,0,0,0,0,0,0+-+-1.1.0.0.&-%,26(,A;~x|}AB)/-3$*'-,.-/(/(/,0+/YY/214!-".93.(ۉ.%7.&/&/-0-0,0,0,0,0,0,0-0-0.0.0*/*/(0)1-2(-....{ph=E3;-/13-+,*(-(-"2#3)1*2-/-/,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0-0-0.3.3+*+*)*+,/5/505*/NIB7;02,2,-2+0'+-1,/,/,0,0,0,0,0,0,0,0+/+/10100101$1$1(0'/96C@ڵ``)-7;%*).,.-/(.(.*-.1PQ)-04$0#/6//(ؑ/'7/&0%/,/-0,0,0,0,0,0,0-0-0-0-0)0)0&0$.*/+0/-75捙o?J4?/.43*)('%-'/*0(.-0-0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0-1-1.6.6,),))++--:+8*1)0<2LB߸xJC502-+.-0,2(.+/,0,0,0,0,0,0,0-/-/+1+112121313%3%3&1&13142v~1357)+*,-1-1*.*.(*+-EG*.-1"-%080?7㦧.(5/#/$0-0-0,0,0,0,0,0,0-0-0-1-1'0'0$.(2)/(.0-74ፗQS:<0-52,,))(++.-0-0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0-1-1-6-6,+,+*-+.,7+6'0'07-0&WU|}--::-.'(*1*1,0,0,0,0,0,0,0,0-/-/*2*222223131'4'4(1(10./-ELTS87,,,,/1/1+/+/)*+,=>,2,2,"/7.@7⭭,'2-."1-/-/,0,0,0,0,0,0-0-0-1-1&0&0$/$/*0.4>:0,pt]^891,83++..,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0-0-0,0,0-/-/-0-0+0+0'1'12.0,?:`[KN58-,.-&1'2,0,0,/,/,0,0,0,0-/-/)3)31/1/4-4-*1*1,0,0.,,*17]c|z;9/-1/1223-/-/)),,:ִPN/-203333////''**9?56%0(3+/,0,0,0,0,0,0,0,0,0,0,0-0-0-0-0+.,/*.*.21/.SRvunq/157////+//344///113'2(3/,/,0+0+*.*.#4#4,0,0,/,/,0,0,0,0-/,.,5*3,+-,32>=⣰YNB71.,)-/-/././/,/,,+,+(2(2+1+1,0,0,0,0,0,0-/-/)2(1*..2IG@76---+0+0-*/,AJHZX`^mi~z砙HM41413131-+1/02,.(0(0-.-./-/-,/,/'1'1,0,0,0,0,0,0,0,0,/+.,4)1&(,.311/ZaE@.*3/,.,.-/-/.-.-----+0+0,0,0,0,0,0,0,0,0-/-/)2'0',-2>:hda^85(4'3+0*/,0,0:>uz+0.3',).2288৮NK30.0+-+0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,,--.,,*--,,,*205-4,E8G:jYЄsꠔa]?;2-721-0,)*)*(.(.-/-/////-/-/+.+.,0,0,0,0,0,0,0,0-/-/)0'.(.*01256AAppst4.?9,-,-,/,/-/-/...././.-/-/,0,0,0,0,0,0-/-/(2(2(/$+A:JCسDG'0.7(-',+5)37@z-1.2',).1098⧭d_83,(.*+0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0-0-0+.*-(.(.*/+0.2-1,+*)2*1)A1:*>0RDzrӟ}y;:321/.,*'-*',(-,0,0.1.1././----,/,/,0,0,0,0,0,0-/-/(.(.,6+5-0-08473̎WO@8----,.,.,1,1/0/02-2-.-.-,0,0,0,0,0,0-/-/'1*4&."*<36-}`h'++/'*),(1(18B|/203(+),2165ᠦ~>;/'0(,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2+2*1)2(1(0'/&)'*30528260=7@:XXrrգSY,,99.+)&(,)-+0+0,1-2..../+/+,/,/,0,0,0,0,0,0-0-0*/*/%1$0*.)-5/60TYKC.-.-,.,.+1*0.2.24+4+/-/-,0,0,0,0,0,0-/-/)2)2$+'.4-0)W^1111*)-,%*(-<@|/124*++,32/.ܐGH1)2*,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,00/0/0000.2.2+3+3)2)2&2&2$0#/)-)-..--3412GKbf豪RW7<.*2./1.0-1-1+1,2....0,0,-/-/,0,0,0,0,0,0,0,0,/,/(3(3'/'/-(.)6:`dľmg/023././+1(.-3-34+4+/,/,,0,0,0,0,0,0-0-0*0*0&.&..--,=Cx~@=30+&0++*.-EB0044,.,.12()هhn-&5.,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,02,2,4*4*2-2-////-1-1'3'333"/"/&+%*-/-/,-)*JCibؤks94<7121203/2+0+0-/-/.-.--/-/,0,0,0,0,0,0,0,0-.-.)1)1'/'/,*+)7:36Ȉ>>77-,-,)-(,+3+32-2-/,/,,0,0,0,0,0,0,0,0,/,/*/*/*.*.48FJkh74604.1.-*SM2143--..34)*܂5173+0+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,01+1+3)3)1+1+/,/,....)0)0"3"3$/#.'.'.&)'*,-,-8620A@edٱnlEC449934-.)-*.*.*.,0,0,0,0,0,0,0,0,0,0,0,0/./.,0,0(1'0'+)-88//QReg686150(+*-)3)3////.-.-,0,0,0,0,0,0,0,0.-.-.0.0(2(204*.զ?@2-4/=;42]Z/.32,*-+44--|MJ52+/+/,0,0,0,0,1,1,1,1,0,0,0,0,0,0,0,0,0,0....0.0..-.--.-.,.,.+/+/*0*0+1+1*0)/*.+/+.),(+(+7452OGwoĿ{{852/2244,/(+(/(/)2)2,0,0,0,0,0,0,0,0,0,01.1././.(2(2*4'1+*21B;rkEH5.5.%$*)'4'4,1,1././,0,0,0,0,0,0,0,00+0+2222%6$5.0#%ry]c843/47GJƊ2/74**((01*+s|ww;;+/+/.1-0-0-0-0-0,0,0,0,0,0,0,0,0,0,0,/,/,2,2,3,3,1,1+0+0*0*0-/-/1-1-0101.2/305.3,1.3&-&.*/+C7@4d[ig643074()*+'-&,(2)3,0,0,0,0,0,0,0,0,0,02.2.0-0-(3(3'4+80/.->2;/ؤnr2+4-+)*('2'2,1,1-/-/,0,0,0,0,0,0,0,00+0+2222&8&8*+!"NREChf蒚ﱹ/(;4#-",)3(2jlcb(,)-2.-)2,2,0,0,-.-.,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0-/-/-0-0-0-0-0,/,/-0-----.,-1201698;w|ܹy5,7.----+(*'*--0,0-1,/,/,0,0,0,0,0,0-0-0-/-/,0,0+/,0,/+.0.*(]eB=72.4)/.-.-1-1--1-1,0,0,0,0,0,0,0,0,0,0.0.0,2,2(.*045rs4,=5%0$/*4)3dd(-/4*$1+1/.,.2.2-,-,-/-/,0,0,0,0,0,0,0,0,0,0,/,/,0,0-0-0-0-0,0,0,0,0,0,0,/,/,/,/-0-0....,-,--/.025478:[]⽸jg:731.,+**)02-/,/-0,0,0,0,0,0,0-0-0,0,0,0,0-0-0-/-/,/,/..,,=A}c_84/3*./,/,0-0-,1,1,/,/,0,0,0,0-0-0+1+1-/-/.0.0)1*232RQ@:;5'1%/(1'0XXX`8@1)4,&,)/*7*7.*.*-/-/,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0-0-0.1.1,.,.--..,*4284/+]VÕhq0-74/5%+,0-1,.-/,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0--2287FEGE-,/.,.,.,/,/,0,0,0,0,0,0,0,0,/,/,0,0-/-//0./)/*06554գTN:4&.&.'0%.JKPW0)3,%0&1(9(9/*/*-/-/,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.2.2-/-/,++*,(+'1,3.5/<6֎UU661818-/*,+.,/,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,-+,720+ېqq,&5/*.*.*1*1,/,/,0,0,0,0,0,0,/,/+1+1-/-///..)/*043,+}fa>9'.'.)1(0CC4275&3#0&4&4/+/+-/-/,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.1.1-/-/++((+*.---++/.*)=AptRU),5801,-+.,/,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0+.),4./)ZY8.:0(0(0(2(2,.,.,0,0,0,0,0,0-/-/+1+1,/,//./.).(-.-,+XZzu>9+.,/+2)0@=}zjn<@+2*1(-',1/.,,/-0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0././....*,-/)-(,'/'/'.)06813{z<8730/-,,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0-0-0*/)..*-)A>{xYO8.)4$/&1'2-.-.,0,0,0,0,0,0-/-/+1+1-/-/0.0.)1'//-.,IJA<+-,.+1)/<9`]{./341,0+.//0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0-.-..-.--0,/*1*1'4'4$/"--,*)EAmi{u?9510,+/,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0-0-0*1*1,-./85JG~C;(4%1(1(1--..,0,0,0,0,0,0-/-/+1+1-/-/1-1-(2%/**))<=^_JF+,-.+1*0:5E@da@=.&91.103,0,0,/,/,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,/,/+,+,././0101.4.4)5(4)3'1,,005522ub_6150-0-0,/,/,0,0,0,0,0,0,0,0,0,0,0,0,0,0-/-/+2*1-1-1;650Й^[!-(4,0*./.0/,0,0,0,0,0,0-/-/+1+1-.-.1-1-)3+5).(-99==XT+)/-+0+0;683zx<7?:15,0,0,0,/,/,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,/,/)-)-././21211111+1+1,0+/+--/+1(.=HxHB:4.0-/,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,/,/,2)/14(+<37.ce,$1.+0-1/20,/,/,0,0,0,0-/-/+1+1-.-.1-1-&3'4(/$+5645ֶc`(%.++0,1;391֦quCG%,07,/+.-0-0-0,/-0-0,/,/,/,/-0-0,0,0,0,0,/,/,/,/,0,0,0,0,0,0,0,0,/,/(/(//0/050504-4-.*.*4-5..2,0#2%41919͏wr;6+,,-,0,0,.-/-0-0,0,0,/,/,0,0,0,0,0,0,/,/-2-2/2-03&7*DB*7"/0(4,2/41,/,/,0,0,0,0-/-/+1+1----2-2-'6'6#,%.3513זtq**//,1*/420.؃SP30()-.)+')*-+.+0).+0-2,2+1+/+/+/,0.0.0/0/0.0.0-/-/-/-/-/-/,0,0,0,0*/*/.0.020202-2-.*.*2,2,-5-5'4%201()OOee,+10./+,-4+2+.+.-/-/-1-1,/,/,0,0,0,0,0,0,0.2).160%6+=8\WIU)5/)0*0011,/,/,0,0,0,0-/-/+1+1-.-.0./-+5)3#+)1/1*,qn4905-3'-)1#+fex65.-&',-$(!%%+(..5-4*4*4)/(.),*-....11110101-/-/........,0,0*1*1-0-0,0,0,/,/,0,0,0,0-1-1,6,6.2.25,6-55EEϰ<:755/,&+7(4(-(--.-.-3-3,/,/,0,0,0,0-0-0*-*-)1(02/2/6194ᮼ}7B,/'*-0+.,0,0,0,0,0,0,0,0,/,/,0,0,0,0-/-/-0-0,-,-HI?B14+./2*0)/WVei9=9=/304(,&*&&&&'**-(+'*&(')*+*+*.*.*1*1+2+2-1-1-1-1,0,0+/+/,0,0,0,0,0,0,0,0,0,0....-3-3,1,11*2+<:/-jpil:=5.4-)2*3)/)/-.-.-2-2,/,/,0,0,0,0-0-0*.*.*1*12.2.2-0+܁BN,-/0-.-.,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,.+-66NM32)+/1(,,0EBim@D:>+-,.*****+*+*,*,*++,'+'+%,%,&/&/)2)2*3*3+2+2,/,/,-,-,/,/,0,0,0,0,0,0,0,0.,.,-0-0,0+/-'-'8563:=y|QY/':2-2,1'0(1-.-.-0-0,0,0,0,0,0,0-0-0*/*/*3*32-2-5/3-^g[f+*21+,-.,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,.,.88rrkg:6+,-.)+-/<7wr||OS;?+1.4/203/./.,--.+,,-(,(,&-&-)0)0(3(3)3)3+0+0-,-,,/,/,0,0,0,0,0,0,0,0.+.+././,0+/+)20('('66BB٫5,90----)4)4-/-/-/-/,0,0,0,0,0,0-/-/*0*0+4+41,1,2,0*CJ/,2/-,.-,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,.,.66NNC=-,-,-.,-93YSu]`SVMLHG==::20421032//..+,+,(1(1(2(2+0+0....-0-0,0,0,0,0,0,0,0,0.,.,././+2+2,.*,(+'*44''fk̴QH=4/+.*)4)4.0.0.-.-,0,0,0,0,0,0-/-/*1*1*4*40,0,-(1,9=tx9540/.-,,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,.,.6699ԼQK/-0.+,,-40@<٫wtc^QL<6<690;2/).(,.,.&0&0+2+2.1,/,/-0,0,0,0,0,0,0,0,0..../0/0-4-4*0)/'-'-,.(*?AqsGA+'0,*4*4.0.0....,0,0,0,0,0,0-/-/*1*1*3*3.-.-+*-,::KKRO:7-,.-,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,/+.1223ךpi-.01*+-.4253ۈqgE:B74,3+1010(/(/*0)/.3.3-0-0,0,0,0,0,0,0,/,/,1,1-3-3/3/3,2,2(0(0(.$*9;68ӯ__.,20*2*2/0/0.-.-,0,0,0,0,0,0-/-/*1*1*1*1././(*)+5364ԢxvB@,+.-,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0+/*.04,0|.215,/),22,,_cȼ{JH:83445(-+0(-(--205-0-0,0,0,0,0,0,0,/,/*2*2,2,201010202+1+1(/(/44//u{.-54+/+/0101....,0,0,0,0,0,0-0-0*0*0*/*/-1-1'/(061,'{zGG.-/.,/,/,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0+0*/,3&-e`:A07.2(,/1+-JMĘu};tqps&.(0#/*64/+&mwPO-+1/1.1.$1$1'/'/1+1+././+1+1-0-0,0,0,0,0,/,/,1,1,,,,620,JS5,;2'-)/*0(.-0-0,0,0,0,0,0,0,0,0*1*1+.+.,-/0.2,0/4DIIK46(.(.(-(-3.3.0000)/)/-0-0,0,0,0,0,0,0,0,0-.12=8b]*6.:(6%301/0HFJE@;0,0,"0"0$/$/0-0-././+0+0,0,0,0,0,0,0,/,/,1+002,./+0,:?}E?82'-(.)/)/-0-0,0,0,0,0,0,0,0,0*1*1+.+.,,11.0,.2312۴WY68)/)/'-'-3.3.0000(/(/-0-0,0,0,0,0,0,0,0,0*/*/;3VNAM/;+5$.,1.37-kaHA1+3-#0#0#.$/////////,0,0,0,0,0,0,0,0,/,/,1*/*-,//,2/?@VW]Y:6+1)/(0)1-0-0,0,0,0,0,/,/,1,1*1*1+.+.-,0/,,..21-,ޘkk99+/+/(.(.2/2/1010*/*/-0-0,0,0,0,0,0,0,0,0'.*1:0D:qy5=+.,/,3+2=1G;7171'1%/%0#./0/0.0.0,/,/,0,0,0,0,0,0,/,/,0/3*.+/-,)(97;9Ҥ|zB@+2+2'/)1-/-/,0,0,0,0,0,0,0,0*1*1+/+/.-/..,0.1..+@=,0,0(.(.1/1/1/1/+/+/-0-0,0,0,0,0,0,0,0,0(/+2;18.ֱIL,)0-'.)03+3+脋暕䖑誥b]?:,4+3(2#-/1/1-1-1,/,/,0,0,0,0,0,0,0,0-0,/+/+/),),741.׀LK(-+0'0(1-0-0,0,0,0,0,0,0,0,0*1*1+/+/.....,.,0,.*jhKE.0,.'/&.0.0.1/1/+/+/,0,0,0,0,0,0,0,0,0,0+0).:1H?ټst5/82(/(/-.)*T\rzSOIEG8D5H9K9(.&,,6%/=_^԰:954--....//,1,1+/+/-0-0,0,0,0,0,0,0-/,.*0*0%1%1-.+,JCuw*-*-'1(2-/-/,0,0,0,0,0,0,0,0*0*0+0+0.0.0/./.0--*JHǾ`W-.12)2'0--..0/0/,/,/,0,0,0,0,0,0,0,0,/+.9630^\[ZA@$)"'"3$574DAݸGB500+,',,++)..3.2(,-+20<3:1QGie<81+/)0-0-*0*0*/*/-0-0,0,0,0,0,0,0-.,-)/)/"4"4+-+-E;})++-'0(1-/-/,0,0,0,0,0,0,/,/*/*/+0+0.1.1/0/00./-IFre--11)2'0....1/1/,/,/,0,0,/,/,/,/,0,0,/+.:2<4ٖMK)-'+!6"750/*~UT1&7,*$)#$*%+$5"3&6&6,2-34(2&>-G6ҜPK0(0(.,.,+0+0+/+/-0-0,0,0,0,0,0,0-.-.+0+0$5$5*.)-A7h^()./*3'0-0-0,0,0,0,0,0,0,/,/*/*/+0+0.2.2.0.0/..-A?~|{2121'0%.././1/1/-/-/-0-0,0,0,0+/+/,0-/-/<4ZRsj-../#3$4*.*.LJ?H+444--.*-)+.,/,2,2,2,2,/,//,*'50.)GM~|.'81$/$/,2+1/-0.,0,0,0,0,0,0,0,0././0000+-+-%3$265HGBG49,.*,,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0+.,/<=ef6,8.%-'/,/,/0000....////-3,2-4,3(+,//+-)QP3322'2&1+4%.;9eceh#-,603),-+.,-0-0-1-1-1-1,/,/-..///11-3\b<64. +!,,1-20.0.,0,0,0,0,0,0,0,0-/-/0000-,,+$2%345;<۪W^3:-/*,,0,0,0,0,0,0,0,0-0-0-0-0-0-0,0,0,0,0,/,/98^]5+9/)2)2,/,/0000....././*4*4)0,300,,511-ڇ^`8:&,%+,6(298<;ՙGG*4*4'++//.-,,/,/,1,1,1,1,/,/--11)+/15;6<ΓVR3/$-$--1-1/./.,0,0,0,0,0,0,0,0-/-/0101,-,-%1%15410֍kq39/0+,,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,/,/75TR4+8/+4(1,/,/0000.,.,.0.0&4%3&-")0,51A=PLDI*+,-(2)346(*X]\Y=:-7'1&.)1+,-.,/,/,0,0,0,0,/,/-/+-.2+/05.3im{y@>*1)0,0+/-../,0,0,0,0,0,0,0,0-/-/0101----%0&130.+z|x~6<-/,.,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,/,/65KJ7/7/)2'0,.,.0000/,/,-/-/ 4 4#+ (81/(ROv~-*85)..3)/*09@ovC?73$.%/'0'0,--.,0,0,0,0,0,0,/,/,0+/*2'/-0-0VWQR(-*/+.,/././,0,0,0,0,0,0,0,0,/,//1/1.,.,'0*30+.)nn=A()-.,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,/,/85HE?9;5)3)3,.,.0000/+/+,/,/55!)%->5;2דDC43-+.,*3*3399?ܵ;582"-"-'0'0....,0,0,/,/,/,/,0,0,1,1&/'0.1),EF^a$%./.0,.03-0,0,0,0,0,0,0,0,0,/,//1/1.--,'0(1/*-(aaEI()./,/,/,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,/,/64CA㳶EA73*4'1,.,.0000-*0-*-*-!6!6+1&,=4jaDF5.5.'/&.-2$)fh0+61!-!-*0*0....,0,0,/,/,/,/,/,/,2,2'/'/(+),FGuy+(30,.)+.3-2,/,/,0,0,0,0,0,0,/,/.0.0/-/-(1'0.*-)VWMP-,/.,/,/,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,/,/2053ۣKI42*4)3,.,.0000.--,+--/)7'537/3e^ôim1,61&+.3'(01==,'72 .!/,0,00.0.,0,0,.,.,.,.,/,/-2-2'/&.',).EG6161*+)*,2,2,/,/,0,0,0,0,0,0+.+.-1-10.0.'1'1-*,)MPQS--..,/,/,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,.,.2132ܜVU54%0'2,.,.0000-..///))/22556JK̴DC7610-,,*,*<;@?͠/,52"1"1/0/00.0.,0,0,.,.,.,.,0,0.2.2*/,1*0&,>C:471)*./.5+2,/,/,0,0,0,0-0-0+.+.-1-11/1/(2(2++**DKQS++00,/,/,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,.,.028:䙡]^67%0'2,.,.0000,/+..,.,>6<4OKsy9?/$-"83*%5599[b531/"3"320202.2.,1,1,.,.,.,.,0,0/2/2,/-0(0%-BH<29/()-.+3,4,/,/,0,0,0,0-0-0+.+.-0-01/1/)2+4*+*+AIUU++00,/,/,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,/+..147Ꮩ[]46&3&3,,,,0/0/*0*0,+10?,B/ً^g5*3(,(1-1.3097[Y?E39&5%42.2.0+1,,/,/-.-.----,.,.-1,0)-)-,.,.QY>792(*(*(.+1,0,0,0,0,0,0-0-0+.+.-0-00/0/)3+5,++*@FZ[--00+/+/,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,.,.23,-م``99/3*.----1/1/0//.908/I5kGA;5//$$3-3->/6'ᏓL[5D/7.61)2*/*/*----/-/-/-/-.+.+)-*.&.)192-&aj@>86*1#*+-*,-0-0,0,0,0,0-0-0+.+.,/,///..(3)4/+/+BA]^*+01*/*/*0*0*0*0*0*0+0+0,0,0,0,0,0,0,0,0,.+-31)'~jh<:3-0*,.-/11118.4*C2D3{oɽ|C?-*2/+&2-3.2-KK|AE1065.*-)//--0/0/00000/0/0.0.*-.1#'(,;751тCA86(/%,+-)+-0-0,0,0,0,0-0-0+.+.,/,/////(3'2.+2/CC\]**00*/*/*0*0*0*0*0*0+0+0,0,0,0,0,0,0,0,0,.,.42)'줧ۊ愇yyۂ݅Ӌus.(@:*')&,206;7EAϴaZ5';-,,**-2-21111111111111111,.+--/,.96a^BA76(.(.-/*,-0-0,0,0,0,0-0-0+.+.,0,0//..)4)4,*.,CD\\**00*/*/+0+0+0+0*0*0+0+0,0,0,0,0,0,0,0,0,.,.42*(؃ZR:2./+,#1!/73,(aa³O;>*,/,/,2+1////.0.0.0.0/0/0,.(*01+,RQ??55(/(/,.*,-0-0,0,0,0,0-0-0+.+.,0,0/..-)4*5-,*)BD[[)*/0*/*/+0+0+0+0*0*0+0+0,0,0,0,0,0,0,0,0,.,.43-,يTM/315) /(*.0:7]ZvN=1627+1+1+,,-),),)-)-,/,/,-013456ϒ9933'1(2,/*--0-0,0,0,0,0-0-0+.+.,0,0/./.'1(21/+)CFYY*+/0*/*/*0*0*0*0*0*0+0+0,0,0,0,0,0,0,0,0,.,.3300ې3;5=)!+%-$,502-ro9<1403/2)*,-&*)-)-&*'+)-11..HJxz5555&/)2+-,.,0,0,0,0,0,0-0-0+.+.,0,0/./.'1'10/,+FJVV+,/0*/*/*0*0*0*0*0*0+0+0,0,0,0,0,0,0,0,0,.+-2367ߚhneWB@75,)-*!+&00+,'A@rqDD3+3++-)++0+0+0+0+0+0+0+0+0+0,0,00.0.....&/#,2/-*qyy{;=/*0+.,.,,.,.,0,0)1)1+0+0-/-/+1+1*1*1-.-..*.*16JO~{;8>5:1Q\PP4&5'$+$+*.+/6846nrUV:;2+2+,.,.+1+1*/*/+0+0+0+0*0*0-1-12,2,....&.$,8271㈎hk7:.)0+/+/+-.-.,0,0*1*1,0,0....*1*1(3(3,/,//)0*3:MTx34./*(.,.>>NѯGB=8)0(/,4+3,*,*=6XQ඿4;5<+-+-,0,0+1+1*/*/*0*0*0*0*0*0,0,02-2-/./.'.)073=9࣪SY39,/-0-1-1,1,1-0-0-0-0.-.-.-.-*1*1'3'3*1*1-../69TWqzEN&+/40/,+4062icMM<>68*2&.(*-/3-.(ejqy"-,7#.$/-1-1-1-1*/*/*0*0*0*0*0*0,0,01-1-....'/+310GF9B/8.5)0,4,4,1,1.0.0/./.0-0-/-/-*0*0'2'2)1)1,/,/:8hfmt3636-3/510-,;+9)OCID<74646*!,*3/862A=̎IN#0!.///0/0////*0*0+0+0+0+0*0*0,0,00.0././.)/+167`a,2/5*1'.,2,2-0-0/./.1.1.2.2././.*/*/(/(/).).,-*+?9}IQ1900--.1-00/10:.8,ݔbX+(41!1,"6!56,5+A?pnde67$/ +/.1/0..-/.*0*0+0+0+0+0*0*0,/,/////////,1).@D}**00(())+,,--*+(/,/,1.2/3221/0/0*.)-),(+**++,&-'JBOR)2(1*).--),(;931GG||WU97&2'3&4"0.'3,9180hfȨ=:B?$*%+#/!--)-),+.-*0*0*0*0*0*0*0*0+/+/.0.0//../3+/IRnn+%1+-(,'.)/*0)/(/+.*/.1024351403,.,.+**)1+/)5*8-SNwwA>96&/'0,+*)/%0&<;LKΓlo1010+&*%*-.130.+5/E?Ж|WT-126(.(.403/,--.*0*0*0*0*0*0*0*0*.*.-1-1/////1,.VaWV1-510*1+4.0*-&3,.*0,/1/1/3.2.0/1..--/,415-2*B64(][``;;/+73+0-220-+6+4)ZZjaA82&6*.6,4,/+.2.2.GFvuZ[DE9999.-.-,/,/*0*0+0+0+0+0+0+0*.*.-2-20//.35)+z`_:>6:678951626060.,/-.2-1+0)...3343103041A<;6IEd`֞UX0189-.,-)(,+944/@;WRիʿuB;:3+3.6)1*2/.*)732.VTFA4/1436%,*1*/*/+0+0+0+0+0+0*-*-,2,20/0//1/1ޠvLW5@02352121-/-/(.(.&,'-,,++-,,+7621GIsuߩkb9.>3*/%*$.(20)2+5-2*dhWZ+,./+1%+*/.3+(-*:3?8||kg28/5-3,2*.*.+/+/*0*0+0+0*/*/)2)20/0///NNhqHN=C/2-0%*&+)+.0.+%"//==X[wzQAC37'3#%+)/'4'49/6,?6pgYSA;0+,'$0(4&3$1+#';3IAߎVV:1;20)1*)-(,)4(3(3&1&9(;4$:0.0.?=}zwgdJA?6>.=-H/-31,-'(.*1-824.=8JEєwv3434(-).'.(/-0+.:70-=6aZݤhcE@8372././,-./2,1+,.')*4(22*3+TSހֆܥriJA82>8,4*2#.%04231:36/_bäR]0.312/*'-:)62615;*7&D8cW馧x|UWFH>:733$5&6):-4.1+D5=.vusqLJ4/83-/-/*#.&3&3.3.3ACsuig@>1(90,61;39.4/%,"4/2-BBhh䧮qkf`ZQTKF9;.?2M@ów|KP1.52+,+,&-%,($.)4)47AR\ҞIC=71+0*&((*&1$/$3%40/-,F>jb藍栞ۍ̘tsPO10.--1.2#,#,"*"*$&(*477:s}ñqv8/6-1-1-&8(:!63%%))6'/ E;_UݙdfIK1+5/+'+'*1(/'.(/+.036510^]Ĝ[]2,5/,2.4"/#0+,)*.&2*>58/?9IC~|ڤOP=>0/76200.--,,$*'-3/2.<750UX넁TQ5)9-0..,&()+04-1/0/083:52J>d`с}ipKG<83)5+0+6114*-*5+6*0)/))))33//CEegޣ}PT,/14)--1*/(-(2+5,3(/1+.(5*5*3/2.85B?ga|v蛓譥{ieKH>;43654)3(0$0$(%+((0%-(2&0+/-100++HHiiߥ~_e<:532+3,',).+,*+,-,--/.01605//1112)*25ADPS\_nr|ٕߩᄉot[\WXEF45')02//,,,*/-/-.,,--.+0,1)2&/+%,&1144TZtzҬffFD;9(3)4*2+3,/,/*,(**-+.),*-,.)+,-,-1/1/3.61C?HDGLJOPVW]ckjrjplrxz儆zwqsmspli_]ZXVRQMKAH>>29-4+6-/*4/00--*/,1,2+1-0/2.0,.+/*.*/).9887UWǂGU:H.:-9,206+.*-))((!)"* +#.)2'0,1.3/3,0)-)-)0*1(1(1*2(0).(-+/04=1:.6,7-7/914,3+4*7-6+6+4)3(1*2+2301)0(/)3&0%,(/,2,2/3,0,2*05;@Ftwğr}Xc@F8>,/,/*+,-&.&. . .#1#1.!0#3#3#. +')*,*-),*,*,./-.++++/,0-0/0/,.-/--++*),+**((()()*-*-'.%,(5+8)2*3-/+-/2-0388=PYjsٗ`cKN6:26*.-1+1,2*2,4&3&3$5&7+5*451401/314/2-5/3-3,5.)3)3'2'2%1'3)5)5'2'2%1$0&1'2&/$-)0(/(1(104,00,2.EDXWvzҖtyWUGE=8;6:6953/3//+/+203175755151507261504.5/(3)4)5(4&2'3*3*3*4*4'1'1*/*/.1-012346666?@PQol~{坝_YICD9@5<,=-2(4*+/,0-../....,-01/1+-0011210/0.1/3/2.1/1/0000./-.5689:>NRoxڌž|ldaYMSHNBF=A9=59(.)/)1$,5.2+3)8.B6D8H;OBWL]Rc`khxyً쥩숎艑爐擐鎋䎈䖐줚㷵Ҳޢӟ䶷װխ欪ήa_b203  + + + + + + +  a_b򟝠HFI-+.  +  + + + + + + + + + + +  !! +   +    + + + + + + + + + +  /-0^\_  +      + + + + +  + + +  536ywzfch749  + + +  + + + + + + + +  TRUZW]/,2  +  + + + + + + + + + + + +  + 𢠣NLO(&)         + + + + + +  + '%(    TRTPNQ)'* + + + + + + + + + + + + + + + + + +vtv\Z]ecf   !0.1%#&MKNqnthfiKIK`^akik  +  + ZX[[X]       + +  + + + +-*/  + +     OLQtqv  [X] +    MKMRPS + nln   +       + \Z]  + +                 +   <9?    IFL0.0 +  +       NKQ    + +  +       +    + + + + + +  MKM{y{  + + +           nln`]c  [Y\A>D +    +   +       LIOKHM  + +  HEK  MKM424 + jhj,(0 +   +   _]`    &$&    ##'!%!%!%!%!%!%!% $ $ $   +   OKS + + + !"!"!"!"!" ! ! ! ! ! #      +oktFDF  TPY          +  MKM  qoq" + _]`  !   +LHPPNQ + +"$ gelձ326    +0/31/2 MMQǾ¿þ  +zy}ſ +   NLNzw| + + +    +A@Atst   +uvw    \Y_202   }}    #$%%#&&'(YZ[|z}jkl + NLO{w  + + llp jhjllm + +    [X^PNQ +  !" yx%&(   $%' OPQ~| + + NLO{x~ +  nlnoop +   + ZW]OMP + !" wv}+,.$%' \]^~|  + NLO{x~   +mkmpop + +      [X^MKM +  !! ww|$%& $%&Z[\~| + NLO{x~   mkmono  + +    ZW]PNP  !! ww|&'( "#$\]^~| + NLO{x~ mkmmmn +    ZW]OMP   !" vu|%&( +"#% Z[\~|  + NLO{x~  +  mkmmmn  +    ZW]OMP   !" wv}%&( +"#% \]^~|  + NLO{x~    +mkmnmn +    ZW]OMO   !! ww|%&' +"#$]^_~| + NLO{x~   mkmnmn +  +  ZW]OMO   !" vv{%&' +"#%_`a~| + NLO{x~  +pnpmlm  yz|   + ZW]OMP  !" wv}&') +"#% abdTUW~{ +uvw + NLO{x~ VVZtru  !{  +\Y_MJP +" "   + xw&&+ "#$GFN +   +lhq~   |}½ NLO{x~   + +   +  +  +!!;:B YV[MJP  +    +  yw +!"#    + ! +   + +  #449 + +  #>;@ NLO{x~    C@F       BBG   ZW]*'-  jhq^^c/01    + + +   +,,1     + 203 NLO{x~    +/,1 JGM +       >>B  ?@,*,#!#(&(+)+$%'$%'$%'$%'#$&&')"#%$%'GHJ         +       +  +     + +  + +   + +|} ABD}zDAF'%('%((&)(&)(&)&$'%#&&$'&$'&$'$#'$#'!    224zw|89;)*,%&('(*%&($%'%&(%&(%&(%&('')((*(&) + <8@ NLO{x~  + +  ssxᬭBCE679   + +  *',  +    + abd緷DDI **,ީB?D + -)1 NLO{x~ +  oot=>@  +B?E|y~    .,6_]g_]g^\f][e][eXV`_]gNLVpqs _`b##'  ))+   +.*2 NLO{x~ + onr.-2??A  +A>D  rrw徼mkr   }~aacRRW + +,,.*',  .*2  NLO{x~    mkm(%+ ?=@  + + ^[a +   wyw + +   a_aYX`  +*.647+(- + NLO{x~ + nln(%+ ?=@   + \Y_ +   wyxYY[  QRT a_aXW_  +*.536,)/ + NLO{x~ + +nln)&, ?=@   + \Y_ +   wyx +  + !"$a_aXW_  +*.647,)/ + NLO{x~ + +nln)&, ?=@   + \Y_ +   wyx'(*  a_aXW_  +*.647,)/ + NLO{x~ + mkm)&, ?=@   + [X^ +   wyxYZ\ z{} b`bYX`  +*.536,)/ + NLO{x~ + +kik*'-> <:<('/  +.-1.+1 LJM{x~ feeHFI   + [X^ + +  vxwTUWMMO-+.usuijk   +.-1igj :7= LJM{x~ " 639KHNJGMIFLIFLIFLIFLIFLIFLIFLIFLIFLIGJHFIJHJIGICACFDGIGJHFIJHKIGJIGJIGJIGJIGJIGJHGLHGLHGLHGLHGLFEJLKPGFK'&+   +`]b  YU] yxy{z|0.1;8>  +  +.-2FEJGFKHGLIHMIHMIHMIHMJGMIFLIFLIFLIFLIFLIFLHEKB?E{x~DBEHFIIGJIGJIGJIGJIFLIFLIFLIFLIFLIFLIFLIFLIFLIFLJIMJIM)).    <;?B@BJHKJHKIFKIFKIFLIFLIFLIFLIFLIFLIFLIFLIFLIFLGDJJGMGDJ# &  ROULJL{y|EAI  TRTjhj       XT\ + ywy gck      LHP + + ppr} + +  KIK{y{ [Y[kik   + +'/  XUZ + ywy647|z}   MJP  ~    +(-KIK{y{[X^  ZXZmkm  +   +XU[ + ywyMKN XVYspv     +MJP  +ppu~ +KIK{y{XUZZXZnln  +  XUZ + zxzrps 314b^f +LINjjn~  zw|LJL{y{툆758  +SQScacKHNQOR{y{qoq  +73; GEHCDFzxzLJM A?Axvxڟ~|}{~{y||z}|z}|z}|z}|z}|z}|z}|z}|z}|z}|z}ywy{y{vtv}{}sqs|z}{y|}zx{}|z}|z}|z}|z}|z}z{}z{}z{}z{}z{}z{}z{}z{}z{}yz|xvy|z}xvyywzywzxvy{y{}}{}}{}{y{~|~ׄywz|z}|z}|z}|z|zxzݤz{}yz|z{}z{}z{}z{}z{}z{}z{}z{}{{}{{}|z}|z}|z}|z}xvy|z}xvyzx{|z}|z}|z}|z}|z}|z}|z}|z}|z}|z}|z}|z}|z}|z}|z}{{}yy{|}|}wuw|z}|z}|y~|y~|z}|z}|z}|z}|z}|z}|z}|z}|z}|z}|z}}{~}zx{}{~Ӂywy|z|xvx{y{zxzÍ \ No newline at end of file diff --git a/examples/peripherals/jpeg/jpeg_encode/main/jpeg_encode_example_main.c b/examples/peripherals/jpeg/jpeg_encode/main/jpeg_encode_example_main.c new file mode 100644 index 00000000000..3dbadaae07d --- /dev/null +++ b/examples/peripherals/jpeg/jpeg_encode/main/jpeg_encode_example_main.c @@ -0,0 +1,106 @@ +/* + * SPDX-FileCopyrightText: 2024-2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ +#include +#include +#include +#include +#include +#include "mbedtls/base64.h" +#include "esp_check.h" +#include "driver/jpeg_encode.h" + +#define EXAMPLE_WIDTH 1280 +#define EXAMPLE_HEIGHT 720 +#define EXAMPLE_RGB_FRAME_SIZE (EXAMPLE_WIDTH * EXAMPLE_HEIGHT * 3) +#define EXAMPLE_JPEG_BUFFER_SIZE (EXAMPLE_RGB_FRAME_SIZE / 10) /* Estimate output size with an approximately 10:1 JPEG compression ratio for this demo image. */ +#define EXAMPLE_BASE64_CHUNK_LEN 96 +#define EXAMPLE_JPEG_QUALITY 80 + +extern const uint8_t esp720p_rgb_start[] asm("_binary_esp720p_rgb_start"); +extern const uint8_t esp720p_rgb_end[] asm("_binary_esp720p_rgb_end"); + +static void print_base64_payload(const unsigned char *encoded, size_t encoded_len) +{ + /* Split the printable payload into short lines so it is easy to read in + * the serial monitor and robust for pytest to parse back into a JPEG. */ + printf("JPEG_BASE64_BEGIN\n"); + for (size_t offset = 0; offset < encoded_len; offset += EXAMPLE_BASE64_CHUNK_LEN) { + size_t chunk_len = encoded_len - offset; + if (chunk_len > EXAMPLE_BASE64_CHUNK_LEN) { + chunk_len = EXAMPLE_BASE64_CHUNK_LEN; + } + printf("JPEG_BASE64 %.*s\n", (int)chunk_len, (const char *)&encoded[offset]); + } + printf("JPEG_BASE64_END\n"); +} + +void app_main(void) +{ + /* EMBED_FILES turns the raw asset into linker symbols, so the example can + * read the picture directly from flash without mounting a filesystem. */ + const size_t embedded_size = esp720p_rgb_end - esp720p_rgb_start; + uint32_t jpeg_size = 0; + jpeg_encoder_handle_t jpeg_handle = NULL; + + printf("Loading embedded BGR24 image from flash...\n"); + printf("Embedded raw image size: %zu bytes\n", embedded_size); + assert(embedded_size == EXAMPLE_RGB_FRAME_SIZE); + + /* Despite the enum name, the current driver maps + * JPEG_ENCODE_IN_FORMAT_RGB888 to a BGR24-style byte layout. + * Keep the embedded raw asset in bgr24 order or red/blue will swap. */ + jpeg_encode_cfg_t enc_config = { + .src_type = JPEG_ENCODE_IN_FORMAT_RGB888, + .sub_sample = JPEG_DOWN_SAMPLING_YUV422, + .image_quality = EXAMPLE_JPEG_QUALITY, + .width = EXAMPLE_WIDTH, + .height = EXAMPLE_HEIGHT, + }; + + size_t result_buffer_size = 0; + /* The output JPEG is compressed, so the example does not need to reserve + * a full raw-frame worth of space for the bitstream. This 10:1 estimate + * is intentionally conservative for the bundled demo image and quality + * setting; real applications should size this buffer for their own worst + * case and handle "buffer too small" errors if needed. */ + jpeg_encode_memory_alloc_cfg_t mem_cfg = { + .buffer_direction = JPEG_ENC_ALLOC_OUTPUT_BUFFER, + }; + uint8_t *jpeg_buf = (uint8_t *)jpeg_alloc_encoder_mem(EXAMPLE_JPEG_BUFFER_SIZE, &mem_cfg, &result_buffer_size); + assert(jpeg_buf != NULL); + + /* Create the encoder instance once, then feed it one full-frame request. */ + jpeg_encode_engine_cfg_t encode_eng_cfg = { + .timeout_ms = 200, + }; + ESP_ERROR_CHECK(jpeg_new_encoder_engine(&encode_eng_cfg, &jpeg_handle)); + + printf("JPEG encoder will read the embedded raw buffer directly from flash.\n"); + printf("Encoding BGR24(raw) -> JPEG...\n"); + ESP_ERROR_CHECK(jpeg_encoder_process(jpeg_handle, &enc_config, esp720p_rgb_start, EXAMPLE_RGB_FRAME_SIZE, + jpeg_buf, result_buffer_size, &jpeg_size)); + printf("Encoded JPEG size: %" PRIu32 " bytes\n", jpeg_size); + + size_t encoded_len = 0; + /* First call asks mbedTLS how big the base64 buffer must be, then the + * second call performs the actual binary-to-text conversion. */ + int ret = mbedtls_base64_encode(NULL, 0, &encoded_len, jpeg_buf, jpeg_size); + ESP_ERROR_CHECK((ret == MBEDTLS_ERR_BASE64_BUFFER_TOO_SMALL) ? ESP_OK : ESP_FAIL); + unsigned char *encoded = calloc(encoded_len + 1, 1); + assert(encoded != NULL); + ESP_ERROR_CHECK(mbedtls_base64_encode(encoded, encoded_len + 1, &encoded_len, jpeg_buf, jpeg_size) == 0 ? ESP_OK : ESP_FAIL); + + /* JPEG_META plus the chunked JPEG_BASE64 lines form a tiny text protocol + * that pytest understands and can reconstruct into a host-side .jpeg. */ + printf("JPEG_META width=%u height=%u format=JPEG encoding=base64 size=%" PRIu32 "\n", + EXAMPLE_WIDTH, EXAMPLE_HEIGHT, jpeg_size); + print_base64_payload(encoded, encoded_len); + printf("JPEG encode demo done.\n"); + + ESP_ERROR_CHECK(jpeg_del_encoder_engine(jpeg_handle)); + free(encoded); + free(jpeg_buf); +} diff --git a/examples/peripherals/jpeg/jpeg_encode/main/jpeg_encode_main.c b/examples/peripherals/jpeg/jpeg_encode/main/jpeg_encode_main.c deleted file mode 100644 index cc38e62b8bf..00000000000 --- a/examples/peripherals/jpeg/jpeg_encode/main/jpeg_encode_main.c +++ /dev/null @@ -1,153 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD - * - * SPDX-License-Identifier: Unlicense OR CC0-1.0 - */ -#include -#include -#include "esp_heap_caps.h" -#include "esp_vfs_fat.h" -#include "sdmmc_cmd.h" -#include "driver/sdmmc_host.h" -#include "driver/jpeg_encode.h" -#include "sd_pwr_ctrl_by_on_chip_ldo.h" - -static const char *TAG = "jpeg.example"; -static sdmmc_card_t *s_card; -#define MOUNT_POINT "/sdcard" - -const static char s_infile_1080p[] = "/sdcard/esp1080.rgb"; -const static char s_outfile_1080p[] = "/sdcard/outjpg.jpg"; - -static esp_err_t sdcard_init(void) -{ - esp_err_t ret = ESP_OK; - esp_vfs_fat_sdmmc_mount_config_t mount_config = { -#ifdef CONFIG_EXAMPLE_FORMAT_IF_MOUNT_FAILED - .format_if_mount_failed = true, -#else - .format_if_mount_failed = false, -#endif // EXAMPLE_FORMAT_IF_MOUNT_FAILED - .max_files = 5, - .allocation_unit_size = 16 * 1024 - }; - const char mount_point[] = MOUNT_POINT; - ESP_LOGI(TAG, "Initializing SD card"); - - sdmmc_host_t host = SDMMC_HOST_DEFAULT(); - host.max_freq_khz = SDMMC_FREQ_HIGHSPEED; - -#if CONFIG_EXAMPLE_SDMMC_IO_POWER_INTERNAL_LDO - sd_pwr_ctrl_ldo_config_t ldo_config = { - .ldo_chan_id = 4, // `LDO_VO4` is used as the SDMMC IO power - }; - sd_pwr_ctrl_handle_t pwr_ctrl_handle = NULL; - - ret = sd_pwr_ctrl_new_on_chip_ldo(&ldo_config, &pwr_ctrl_handle); - if (ret != ESP_OK) { - ESP_LOGE(TAG, "Failed to new an on-chip ldo power control driver"); - return ret; - } - host.pwr_ctrl_handle = pwr_ctrl_handle; -#endif - - // This initializes the slot without card detect (CD) and write protect (WP) signals. - // Modify slot_config.gpio_cd and slot_config.gpio_wp if your board has these signals. - sdmmc_slot_config_t slot_config = SDMMC_SLOT_CONFIG_DEFAULT(); - slot_config.width = 4; - slot_config.flags |= SDMMC_SLOT_FLAG_INTERNAL_PULLUP; - - ret = esp_vfs_fat_sdmmc_mount(mount_point, &host, &slot_config, &mount_config, &s_card); - - if (ret != ESP_OK) { - if (ret == ESP_FAIL) { - ESP_LOGE(TAG, "Failed to mount filesystem. " - "If you want the card to be formatted, set the EXAMPLE_FORMAT_IF_MOUNT_FAILED menuconfig option."); - } else { - ESP_LOGE(TAG, "Failed to initialize the card (%s). " - "Make sure SD card lines have pull-up resistors in place.", esp_err_to_name(ret)); - } - return ret; - } - // Card has been initialized, print its properties - sdmmc_card_print_info(stdout, s_card); - return ret; -} - -static void sdcard_deinit(void) -{ - const char mount_point[] = MOUNT_POINT; - esp_vfs_fat_sdcard_unmount(mount_point, s_card); -#if SOC_SDMMC_IO_POWER_EXTERNAL - esp_err_t ret = sd_pwr_ctrl_del_on_chip_ldo(s_card->host.pwr_ctrl_handle); - if (ret != ESP_OK) { - ESP_LOGE(TAG, "Failed to delete on-chip ldo power control driver"); - return; - } -#endif -} - -void app_main(void) -{ - ESP_ERROR_CHECK(sdcard_init()); - uint32_t raw_size_1080p; - uint32_t jpg_size_1080p; - jpeg_encoder_handle_t jpeg_handle; - - FILE *file_raw_1080p = fopen(s_infile_1080p, "rb"); - ESP_LOGI(TAG, "s_infile_1080p:%s", s_infile_1080p); - if (file_raw_1080p == NULL) { - ESP_LOGE(TAG, "fopen file_raw_1080p error"); - return; - } - - jpeg_encode_engine_cfg_t encode_eng_cfg = { - .timeout_ms = 70, - }; - - jpeg_encode_memory_alloc_cfg_t rx_mem_cfg = { - .buffer_direction = JPEG_DEC_ALLOC_OUTPUT_BUFFER, - }; - - jpeg_encode_memory_alloc_cfg_t tx_mem_cfg = { - .buffer_direction = JPEG_DEC_ALLOC_INPUT_BUFFER, - }; - - ESP_ERROR_CHECK(jpeg_new_encoder_engine(&encode_eng_cfg, &jpeg_handle)); - // Read 1080p raw picture - fseek(file_raw_1080p, 0, SEEK_END); - raw_size_1080p = ftell(file_raw_1080p); - fseek(file_raw_1080p, 0, SEEK_SET); - size_t tx_buffer_size = 0; - uint8_t *raw_buf_1080p = (uint8_t*)jpeg_alloc_encoder_mem(raw_size_1080p, &tx_mem_cfg, &tx_buffer_size); - assert(raw_buf_1080p != NULL); - fread(raw_buf_1080p, 1, raw_size_1080p, file_raw_1080p); - fclose(file_raw_1080p); - - size_t rx_buffer_size = 0; - uint8_t *jpg_buf_1080p = (uint8_t*)jpeg_alloc_encoder_mem(raw_size_1080p / 10, &rx_mem_cfg, &rx_buffer_size); // Assume that compression ratio of 10 to 1 - assert(jpg_buf_1080p != NULL); - - jpeg_encode_cfg_t enc_config = { - .src_type = JPEG_ENCODE_IN_FORMAT_RGB888, - .sub_sample = JPEG_DOWN_SAMPLING_YUV422, - .image_quality = 80, - .width = 1920, - .height = 1080, - }; - - ESP_ERROR_CHECK(jpeg_encoder_process(jpeg_handle, &enc_config, raw_buf_1080p, raw_size_1080p, jpg_buf_1080p, rx_buffer_size, &jpg_size_1080p)); - - FILE *file_jpg_1080p = fopen(s_outfile_1080p, "wb"); - ESP_LOGI(TAG, "outfile:%s", s_outfile_1080p); - if (file_jpg_1080p == NULL) { - ESP_LOGE(TAG, "fopen file_jpg_1080p error"); - return; - } - - fwrite(jpg_buf_1080p, 1, jpg_size_1080p, file_jpg_1080p); - fclose(file_jpg_1080p); - - sdcard_deinit(); - ESP_LOGI(TAG, "Card unmounted"); -} diff --git a/examples/peripherals/jpeg/jpeg_encode/partitions.csv b/examples/peripherals/jpeg/jpeg_encode/partitions.csv new file mode 100644 index 00000000000..1bd604e2b12 --- /dev/null +++ b/examples/peripherals/jpeg/jpeg_encode/partitions.csv @@ -0,0 +1,4 @@ +# Name, Type, SubType, Offset, Size, Flags +nvs, data, nvs, , 0x6000, +phy_init, data, phy, , 0x1000, +factory, app, factory, , 0x380000, diff --git a/examples/peripherals/jpeg/jpeg_encode/pytest_jpeg_encode.py b/examples/peripherals/jpeg/jpeg_encode/pytest_jpeg_encode.py new file mode 100644 index 00000000000..5278b90ca2e --- /dev/null +++ b/examples/peripherals/jpeg/jpeg_encode/pytest_jpeg_encode.py @@ -0,0 +1,125 @@ +# SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: CC0-1.0 + +import base64 +import hashlib +import logging +import re +from dataclasses import dataclass +from pathlib import Path + +import pytest +from pytest_embedded import Dut +from pytest_embedded_idf.utils import idf_parametrize +from pytest_embedded_idf.utils import soc_filtered_targets + +JPEG_META_PATTERN = r'JPEG_META width=(\d+) height=(\d+) format=(\w+) encoding=(\w+) size=(\d+)' +JPEG_META_RE = re.compile(rf'^{JPEG_META_PATTERN}$') +JPEG_CHUNK_RE = re.compile(r'^JPEG_BASE64 ([A-Za-z0-9+/=]+)$') +JPEG_OUTPUT_NAME = 'jpeg_encode_result.jpeg' +GOLDEN_IMAGE_NAME = 'golden_output.jpeg' +GOLDEN_IMAGE_PATH = Path(__file__).with_name(GOLDEN_IMAGE_NAME) +EXPECTED_FORMAT = 'JPEG' +EXPECTED_ENCODING = 'base64' +EXPECTED_WIDTH = 1280 +EXPECTED_HEIGHT = 720 + + +@dataclass(frozen=True, slots=True) +class JpegMetadata: + width: int + height: int + image_format: str + encoding: str + size: int + + +def parse_jpeg_metadata(meta_line: str) -> JpegMetadata: + match = JPEG_META_RE.match(meta_line) + if not match: + raise ValueError(f'Invalid JPEG metadata line: {meta_line}') + + return JpegMetadata( + width=int(match.group(1)), + height=int(match.group(2)), + image_format=match.group(3), + encoding=match.group(4), + size=int(match.group(5)), + ) + + +def collect_base64_payload(dut: Dut) -> list[str]: + payload_chunks: list[str] = [] + while True: + match = dut.expect(r'(JPEG_BASE64_END|JPEG_BASE64 [A-Za-z0-9+/=]+\r?\n)') + line = match.group(1).decode('utf-8').strip() + if line == 'JPEG_BASE64_END': + return payload_chunks + + chunk_match = JPEG_CHUNK_RE.match(line) + assert chunk_match is not None + payload_chunks.append(chunk_match.group(1)) + + +def decode_jpeg_base64_payload(metadata: JpegMetadata, payload_lines: list[str]) -> bytes: + if metadata.image_format != EXPECTED_FORMAT: + raise ValueError(f'Unsupported image format: {metadata.image_format}') + if metadata.encoding != EXPECTED_ENCODING: + raise ValueError(f'Unsupported payload encoding: {metadata.encoding}') + + jpeg_bytes = base64.b64decode(''.join(payload_lines), validate=True) + if len(jpeg_bytes) != metadata.size: + raise ValueError(f'Expected {metadata.size} JPEG bytes, got {len(jpeg_bytes)}') + + return jpeg_bytes + + +def save_jpeg_artifact(jpeg_bytes: bytes, output_path: Path) -> None: + output_path.parent.mkdir(parents=True, exist_ok=True) + try: + output_path.write_bytes(jpeg_bytes) + except OSError: + logging.exception('Failed to save JPEG artifact to %s', output_path) + return + + logging.info('Saved JPEG artifact to %s', output_path) + + +def _sha256_digest(data: bytes) -> str: + return hashlib.sha256(data).hexdigest() + + +def assert_jpeg_matches_golden(result_bytes: bytes, golden_path: Path) -> None: + assert golden_path.is_file(), f'Golden JPEG not found: {golden_path}' + golden_bytes = golden_path.read_bytes() + result_digest = _sha256_digest(result_bytes) + golden_digest = _sha256_digest(golden_bytes) + + assert result_digest == golden_digest, ( + f'Generated JPEG does not match golden file: {golden_path.name} ' + f'(result sha256={result_digest}, golden sha256={golden_digest})' + ) + + +@pytest.mark.generic +@idf_parametrize('target', soc_filtered_targets('SOC_JPEG_ENCODE_SUPPORTED == 1'), indirect=['target']) +def test_jpeg_encode_example(dut: Dut) -> None: + dut.expect_exact('Loading embedded BGR24 image from flash...') + dut.expect(r'Embedded raw image size: \d+ bytes') + dut.expect_exact('JPEG encoder will read the embedded raw buffer directly from flash.') + dut.expect_exact('Encoding BGR24(raw) -> JPEG...') + dut.expect(r'Encoded JPEG size: \d+ bytes') + + metadata = parse_jpeg_metadata(dut.expect(JPEG_META_PATTERN).group(0).decode('utf-8')) + assert metadata.width == EXPECTED_WIDTH + assert metadata.height == EXPECTED_HEIGHT + + dut.expect_exact('JPEG_BASE64_BEGIN') + payload_lines = collect_base64_payload(dut) + + jpeg_bytes = decode_jpeg_base64_payload(metadata, payload_lines) + output_path = Path(dut.logdir) / JPEG_OUTPUT_NAME + save_jpeg_artifact(jpeg_bytes, output_path) + assert_jpeg_matches_golden(jpeg_bytes, GOLDEN_IMAGE_PATH) + + dut.expect_exact('JPEG encode demo done.') diff --git a/examples/peripherals/jpeg/jpeg_encode/resources/esp1080.rgb b/examples/peripherals/jpeg/jpeg_encode/resources/esp1080.rgb deleted file mode 100644 index 2e5ba5e6a5e..00000000000 --- a/examples/peripherals/jpeg/jpeg_encode/resources/esp1080.rgb +++ /dev/null @@ -1,97 +0,0 @@ -Ɇv~ajmw7A6@7?5=8?6=:@:@5C3@2A3BF@JCKDZZoogh67<;A@'$30'!'!((,!-"/&0' ( (")")&+%**+(*.,1/7362702,2+2+55789<475;>EXajrϵ8B/9!-6B).%"%"($'#%($' *)%%&&((%*%*'*'*%)%)!(!('',,!+!+%*%*++++FFLL^]pp8B1;".)5 - &"'#&)&)",",%%&&((%*%*'*'*%)%)!(!('',,!+!+%*%*+++++,**0043?@HITWZ]䐣ʵŹh\8520)-*/%*)/ "(#) 1 132++,,%,%,),),+++++)+)'('(%&%&!)!)!(!(%&%&)&)&/+-)++('+*+*..//PDbU}vNC631.$)!""$*$*0-.-++,,%,%,),),+++++)+)'('(%&%&!)!)!(!(%&%&)&)&.*.*0/115577;;;;J>A4=6E>feϊC5<7:4+2"(%0 *#+#+&,&,,*'&--#-#-'-'-++++-*-*-(-(+'+')&)&#'#'#%#%%#%#%#%## # &#($()()&)&)146#/.2#IBZT{m[VC>%,$+$/*5 )"*$*%+, -----#-#-'-'-++++-*-*-(-(+'+')&)&#'#'#%#%%#%#%#%#*'*',),)+,*+)+(*115"6#8)1"4.6046KMxäXhO_,8-97:694,4,-"/$)+ ((!'!'#'#'#&#&%&%&%&%&%&%&%'%'%(%(%&%&#%#%#### "" #!$!%!%!&!&-, 1-3/1,-'.+*&.+86W[悆my_k=@;=7/4,, -"')((!'!'#'#'#&#&%&%&%&%&%&%&%'%'%(%(%&%&#%#%######"" # #""$$$#""&%+**&#*%3-.+73?=64,0.2CIV\ٮ|yQ?:(2@*=(=("! #"&#'#   #%**(%%+%+%*%*#(#(#'#'!&!&&&&&&&22..!(!(#$#$%"%")#)#+&+&+(+(NThnƴu_V@9#/"$&)&*$' !'&'%&%&%%+%+%*%*#(#(#'#'!&!&&&&&&&22..!(!(#$#$%"%")#)#+&+&+(+(06/5AH_fӜútoVQ)),++,./',', '&$%%$$&+-%+%+%*%*#*#*#)#)#(#(!(!(!(!(!(!(--**''!%!%!$!$#%#%#(#(%*%*.(,'....GIor{Y^GHLLTP^Znexox}הּڷ^]IH./%'$)+0%- '$&)'((- 1%+%+%*%*#*#*#)#)#(#(!(!(!(!(!(!(--**''!%!%!$!$#%#%#(#(%*%*1,94>>4324;>dg̀黻vyLO-2+0%*%*%'%'''''))))7AFP\fku~_cCG=A.5,3/506*2)1$,!) ( (#'#'%&%&%'%'%(%(%(%(%(%(%(%('('(#%#%#%#%#'#'#(#(#)#)#+#+#,#,#,#,/&*!&#*',0+/&-&il٘xdB40#-&:3(+!(')(-,//)*&'#" ,)1.0)<5XPnfjm*@-;-=0/)/)!$"%  %'/177#"0//-/,'$5.70?6;3@7G>XMbXФƵadQT88/.-,1/-**'#"#"####%$%$'%'%'&'&)&)&)&)&+&+&'''''('('*'*%,%,%,%,%*%*%(%(%'%'3*/&&"&" %!&$"'+>=11>:[WWYqfQV##(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#("%"%"%"%"&"&"&"&#(#(#(#(#(#(#(#(9:89MOuxxsQL67583669=@fhÉsTG;)7&!#&$- )&$)0%%""""($($.'.')*)*)')')#)#)!)!)!)!%#%##'#'!*!*#(#(#&#&#$#$#$#$"$#%$*"( ,"-'5+9SPTR*#0)7,9-}x罸xz8=# %7<#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#($'$'$'$'$($($($(#(#(#(#(#(#(#(#("$"*''%";9WUWUrojpNT,A,A*2+3)%($ ' ' - -$($(& & %(%(%'%''&'&'%'%%&%&%'%'#)#)#+#+#)#)#(#(#%#%#$#$"$$&&,&,$."+".".8.RHᅀFD6581=64(2&NIupߺKL?A7<9>(-&+#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#($'$'$'$'$($($($(#(#(#(#(#(#(#(#()&&$,*42;7406363GDFCKKNNehVk>R )##.*%%!(!(!.!.%)%)'!'!%(%(%'%''&'&'%'%%&%&%'%'#)#)#+#+#)#)#(#(#%#%#$#$"$$&%+&,%/#, -+UKB8?;XTǛYU* 2/*'! %7?'tsṹolKH',$.++(#####(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#("%"%"%"%"&"&"&"&#(#(#(#(#(#(#(#("$ !" ,-)*$)"$)8=V_nvdj?F-%4,3(7,))))#,#,!)!)""!%!%!&!&!)!)!*!*#,#,#,#,%*%*%*%*#+#+#)#)#'#'#%#%#%"$"&"&#*#*#,#,.!.!2%4&NFyqVSLH5,7-.+*'&+)-<$<$NNrrt{GEC@91<4/,&#((11#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#("%"%"%"%"&"&"&"&#(#(#(#(#(#(#(#("#%&..33%&388=8=05)1%.[aqwۗ鳺UM:2&%****$.$."*"* # #!%!%!&!&!)!)!*!*#,#,#,#,%*%*%*%*#+#+#)#)#'#'#%#%$& ##$")$+%.%.'5(D6=/:2H@څشjj+)%&!#$(0/7::?PPޤuv/042,*&(%1%12-9;;=BDLQ-5<64EInqȳZ\68%&,-./68(!$ '")+)))!'!''$'$)")"#,#,#+#+#)#)#(#(#'#'#&#&#'#'#'#'%%**%/%//-/-5,+"<5ke>91- /-##-",(*),,$&JLhn.)*&*&%**33!-!-%#%##(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#&#&#&#&#'#'#'#'#(#(#(#(#(#(#(#('('('''''$'$%#%#%"%"#########$#$ 0 0 + +"$"$&!&!(1(A:D0BHY`ȠOW;D!(18&)&'0162*&HAkdd`))#&(*$ " -*#(#(#,#,#+#+!(!(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#&#&#&#&#'#'#'#'#(#(#(#(#(#(#(#(%'%'%&%&#%#%#$#$#$#$#$#$#%#%#&#&"2"2 / /**&&/2%)"#'(:685.)OVip뙡lc<6<65252,--/"( '&&!&!&%&%&)')'#(#(#)#)#)#)#)#)#(#(#(#(#(#(#'#'%+%+#,#,!*!*!$!$% &"/,24/2\h␜}4=9A%-,4$"%",-+,*&/+tmd`;7*5+6')!&!'%&$#(#(#,#,#+#+!(!(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#&#&#&#&#'#'#'#'#(#(#(#(#(#(#(#(%'%'%&%&#%#%#$#$#$#$#$#$#%#%#&#&"2"2 / /**&& -.++'#".)946=AH[bx㥮a\JD+(*'.08:'.!&&!&!&%&%&)')'#(#(#)#)#)#)#)#)#(#(#(#(#(#(#'#'%+%+#,#,!*!*!$!$(#% (%,(:==@FRHTλncK@! 54"-&2 $0!!$0.*(CEilٻFD53*),,'3*5+.$')&)&'#'##'#',,#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#&#&#&#&#'#'#'#'#(#(#(#(#(#(#(#(....,,,,,,!.!.!/!/!0!0#"#"$$((,,0.+* ( ($&"%8#36'8)UO{uXX@A377:)/"(!(!(!(!(%*%*)-)-#(#(#(#(#(#(#)#)#)#)#(#(#(#(#(#(+&+&'"'"##!"!"!"+'/'$#*)*!pxǜLB9/22 +%1&"!$)'%#]_̠B@IG" '-%0"-)+02)&)&'#'##'#',,#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#&#&#&#&#'#'#'#'#(#(#(#(#(#(#(#(....,,,,,,!.!.!/!/!0!0"!"!##''++)-01#+ (!$ #.39*3$93D>ij~]^03&*#)-2!(!(!(!(%*%*)-)-#(#(#(#(#(#(#)#)#)#)#(#(#(#(#(#(+&+&'"'"##!"!" ( ($,%--,//3*2)6=lt÷QW*,%'')"#$-$1#@2H=UJѪD8=1#)#)#)#)#(#(#(#(#(#(#'#'#'#'#&#&!*!*!*!*!*!*!*!*!)!)!(!(!(!(!'!'#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(-&-&+$*"("&!(&(&SSxywF=&!93(."'$5!2$%'5!0!0!'!'##%"%"%*%*%.%.#'#'!!#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(1-&!PW؝lr4:!/&4#-!*')$&'/&>0=.UJтxD8=1#)#)#)#)#(#(#(#(#(#(#'#'#'#'#&#&!*!*!*!*!*!*!*!*!)!)!(!(!(!(!'!'#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(-&-&+$*"("&!(&(&44;;dgЖOI)$(.)/.* 1(9$2%!0!0!'!'##%"%"%*%*%.%.#'#'!!#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(*&,'>D[b㧷;A"*$5#,''(&'*'0-<8>;}~B7;0'+'+'+'+'+'+'+'+'+'+'+'+'*'*'*'*!)!)!)!)!)!)!)!)!(!(!(!(!(!(!(!(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(%)%)#(#(&& ' '$+&-5@GnsWb055:-,'&"%),%-%%%!(!(!*!*#+#+#(#(#(#(#(#(#*#*#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#*)0*-#&YVӤip,1%+)"2 (%)*)*,)-*40RO賳B7;0'+'+'+'+'+'+'+'+'+'+'+'+'*'*'*'*!)!)!)!)!)!)!)!)!(!(!(!(!(!(!(!(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(%)%)#(#(''!(!(/5066>+215IN\`;?'&(+"%%$,%%!(!(!*!*#+#+#(#(#(#(#(#(#*#*#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#()0(.02*-<9_\ؽnnDD&-(/. 0 ' '10--1277[e>67/)")")")")")")")")")")#)#)#)#)#)#!&!&!&!&!'!'!'!'!'!'!(!(!(!(!(!(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(++**))(()&")$**,-/=:JGƽE?:46.5-(#*$((33!1!1!'!'#####)#)%2%2#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(41-+1011|i¯DD44$,#+ 0." '+*! //EE螧>67/)")")")")")")")")")")#)#)#)#)#)#!&!&!&!&!'!'!'!'!'!'!(!(!(!(!(!(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(++****))*%")+179340.,)H@sk㳶YS)"'(#.)((33!1!1!'!'#####)#)%2%2#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(/2*)+*6"7#[Ho\`_'")$#)"*%!%(.+ GIy{>67/#########$#$#$#$#$#$#%#%#%#%#&#&#%#%#%#%#&#&#&#&#'#'#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(!(")*+++%/"-$($($!( 0(%0$RS߄b^B=8)@2%%#'#'!.!.--((!%!%#(#(%,%,#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(+..4 B/;(甒JI(#3/+1 '+)&)1452(%acު>67/#########$#$#$#$#$#$#%#%#%#%#&#&#%#%#%#%#&#&#&#&#'#'#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(!(")*+++'!"%"" %&E9:/:;IJzͭuqA37(%%#'#'!.!.--((!%!%#(#(%,%,#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(., !$%1->,D2`^00))*%$!,/$(663/;8ت3+>7.....+*),.010-+*()&'%&$%%&')+,-/%&%&%'%'%'%'%'%'"&"&"&#(#($)$)$)#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(-"-"+$+$''''%(%(%(%(%&%&'#'#)!)!%#((,,6=KRዓhg5712*),*&$"!""$**!*!*#&#&% % #(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(''##%"%"1*1*/*[V嫪q++**+&'! $),$(""&'@6A:"("( $#""!!"! "(-(-).).+/).)-)-%(%(%'%'%&%&%&%&"&"&"&#(#($)$)$)#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(/!/!-"-"-%-%+'+'+(+()))))()()()($ $'+).$&',,a_핓薞bk7597811*" (&!*!*,,!&!&###(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(""))!.!.',',82#91tm:E3>%"'$%%&('$$""11,,99♞F>?7#)"(!& $# $ $!% $!%#&$'#%"$ "!).&+"'$"!%%('*%(%(%'%'%&%&%&%&"&"&"&#(#($)$)$)#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(/!/!-"-"-%-%+'+'+(+()))))()()()( % #%)*/&+./11/-ECz{ׯ]\?=1*1*&#&#!*!*,,!&!&###(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(""))!.!.',',3.0*7/E=Պvq!$&*'' (!),*(&54,,;CltND8.+*+*,+.,0-2/5061/)/)/(/(/&0'1(2)-3*1(.$*#(#(#'$('('('''''&'&'%'%"&"&"&#(#($)$)$)#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(%&%&'&'&'%'%'%'%'&'&'('(%*%*%*%* +)))&#!$%'54/.CCggjr8:@B57)+#&#&%%!&!&!(!(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#()#)#%+%+!/!/))"-*4,*"NQюJE#&),)'''+(+(33)(]eۡXN=3535332210-,)*%)$823-,%(!(.%5+90$!('-+1.3-2+/),'('('''''&'&'%'%"&"&"&#(#($)$)$)#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(%&%&'&'&'%'%'%'%'&'&'('(%*%*%*%*&( , ,'$!$$&**'&1033MTx߼mo79!#68#&#&%%!&!&!(!(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#()#)#%+%+!/!/))$ %"4,4,>AY\ܧnb?4%!%!$%+).(1,.0@B朮|~yrj{cr_mGTCO=G6@1:/8/708$.#-#,#,#*#*"'"'')')'''''%'%'$'$"&"&"&#(#($)$)$)#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(,,**''!$!$!#!#!%!%(())(+./-.)(%&)+52.+21HGm~4=@I%#%#((!6!6#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(/(/()+)+!*!*## $!*'/157oxNCNC'#!#+).(2-')WYcmXaFO5>)0$+&+(-')')'''''%'%'$'$"&"&"&#(#($)$)$)#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(,,**''!$!$!#!#!%!%(())1/*((,'$%&!#,*/-8698LN\^x4=%#%#((!6!6#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(/(/()+)+!*!*## # (*57W_mums)&&(&-$ )/'$op쉔}jsW`=C27.1266666.&/'-&,%)")"'$)&"("(,)-.6;#5$7!0 /%%#&$$!'!''('(+'+'#,#,#)#)#&#&%"%"%"%"%%%%'('('*'*#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(AB~TNIB.'1* '!$)$$((%,%,+,+,+(+('%'%!&!&((#&#&#'#'#'#'#(#("'#(#)$*$+#*#+"*{W\*(*( ".!1&/)24+.%ps`cNN@A###$$( &#'$ #-/4010%8$7. /'&$$!'!''('(+'+'#,#,#)#)#&#&%"%"%"%"%%%%'('('*'*#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(DFJLފ篻}TN%2+*2&#/$$((%,%,+,+,+(+('%'%!&!&((#&#&#'#'#'#'#(#("'#(#)$*$+#*#+"*twGL8=.0!##!-!-,4*286IH婩{k}TdDS8D5@06/5/3-1%&"# ! !#$.)*%##$$#*&,"0 .00//!)!)%"%"!+!+!*!*!)!)#(#(#(#(#(#(#*#*%*%*#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(66CjhPGF<;.7)$* -&1)-)+(%-&...//++''!(!(!)!)!*!*!,!,!,!,!+!+!*!*!*!*#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(%)!$,*/-1-95WZDG&,%''''!&!&#%#%%%%%#&#&!(!(!)!)#'#'#(#(#(#(#(#("'#(#)$*$*#)#)"(99,,CG=A/3%$"" *",'),-#%>@sucaDA@K7C-/+-)".&)'%"%"!'!'++..!#!#!&!&!)!),,,,**((&&#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(*(-+)+)'%QN헙RK>8%/%/#+#+!%!%!#!#!%!%!'!'#(#(#(#(#(#(#(#(#(#(#(#("'#(#($*$*#(#("'++$$.1\`,0;?%$ &0!,34%&FHÛr}KN8:2*703'/$%"%"!'!'++..!#!#!&!&!)!),,,,**((&&#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#($' $%#:8,)<9?@wxQJ%/%/#+#+!%!%!#!#!%!%!'!'#(#(#(#(#(#(#(#(#(#(#(#("'#(#($*$*#(#("'##((=@9=ϓe~&++0** "$/%3$27:!`_ED4,3+/(2+.-20$'$*$!$!"##$&)&)") '((&&$$""#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(((++!+!++&+&5%>.viEzQ]6B!"'"$ "'4"//7nvkt@D482-945(2&*+&'%%((++//-.++#$#$!$!$$$$$#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(,,11..!"!"'&,2!efݢCI>D0/+*%'+-((++#(#('$'$#)#)#)#)#)#)#(#(#(#(#(#(#(#(#'#'&+#$&*++2=E.:*7!&#(  #"'4$29A^b94,').!"##$%%((((((%&"##$#$!$!$$$$$#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(,,11..!"!"%,5#*=?gimsA:>,+##!&$'$'$#&#&!)!)!+!+#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(+)+)'+'+!*!*&& !!"-+,*--55ilv{><@>,0!$,,,,#'#'' ' #*#*#*#*#)#)#(#(#(#(#(#(#'#'#'#'$+)1#*#HXƀĺqg*(0.-0+.!&"'$($(!*1;ސkk=D/6#''*32;:20)&'$'$#&#&!)!)!+!+#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(+)+)'+'+!*!*&&%'$"0-78-->AVYߢfd42,0,,,,#'#'' ' #*#*#*#*#)#)#(#(#(#(#(#(#'#'#'#'!)+2%,$+;KRaUH2%2%*-+.%,#*#$$&*2?Gzt_\\ZYUURSMSMSKTMRJRJSMUOZVa]gdjgvSb+5)*-7:1.&")&)&'('(#-#-#1#1#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(3$3$-"-"#%#%,,6/.%5..().0!cb̛`^#&03----!&!&''#+#+#*#*#)#)#(#(#(#(#'#'#'#'#&#&#,*3"&!%3A6DŮ~I<5)0#&**-%, ( !6>LTdfMNFDKHJDGA;89773627193<4=5;3;39393847374744:8>FNR[`jgq⍤ΣyeV2%3&-'3-$,%+2*)%&#%%(-%,$-"-")")"%#%#!%!%#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(7&3#B>TO޵258;0(*"# %#$.$.--..!+!+#&#&#!#!# # !%!%!)!)%%!*!*)%)%-%-%8Beoku&,$**,+-!'(:=OḼhj@B13&&,,#$$%"$$%%'%'$($(!(!(!(!(!(!(!(!(!(!(!(!(!(!(!(!(&&%&"&%(****3200/0+,../04455E@RMge}{uuZZ52/,*&.)# $"  %!'$*#)#+#+#*#*#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(,)3020%"li嫨egST3.0*(&(&#'#',,,,!,!,!*!*#&#&#%#%!(!(!*!*$$!*!*))))-&-&.349IS&,$*+-,.#$ /AQb{~EG6879AC8721)**+')(*(+'*%)$(!(!(!(!(!(!(!(!(!(!(!(!(!(!(!(!( ' '&&$'%(****4332231233224444;6725364HHdd׋ࣨ[X96'",''"$ !%### & &&%#+#+#*#*#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(/-1/52-*IFdaݳpq-(*$+*)(##',,,,!,!,!*!*#&#&#%#%!(!(!*!*$$!*!*))))-&-&6;+0˕0:!'%-*-*"!%%(DVvsz35+-$((-(.$*%$+**)('((!(!(!(!(!(!(!(!(!(!(!(!(!(!(!(!($,#+#*#*%(&)')')"!"!&"($)%)%*')&-,*%*/$>:FBWYqr\XD?=1D8."."($)&!, ++(//..#,#,'+'+#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#( +!,$%)*.#-"f^鯨ff?>+)20-)+'#(#(#(#(!*!*!,!,!,!,!+!+#*#*#)#)###,#,).).+'+'/-/-T\Ʒdn0: &%.+.+"!%%%TfԙPWEL)++-,0.2&+$%",('&&'(,-!(!(!(!(!(!(!(!(!(!(!(!(!(!(!(!(%-$,$+#*%(&)')')(&(&+',(-)-),),)./2%0"2'/$3/3/BD:G.7!*%(#$!'%&.$+sjw5B&(312%&  '!)"/(5***+*, +!-!(!(!(!(!(!(!(!(!(!(!(!(!(!(!(!(&,&,%*$("'#(#&$'#$#$ " "  #%"$#% #$%&&,+..,*,*/,,(.'>8ia_bI>F;5342$-&/**''((#*#*)-)-#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#($ 2&).*2-傀KI86.%/&'$'$%$%$#%#%!)!),,!,!,#(#(#%#%""%-%-)1)1)()($80-)ea+43="(#%!!&$%,'/ހ_i(66D/:'!#%(!)"$'((*+))'&!(!(!(!(!(!(!(!(!(!(!(!(!(!(!(!(%*%*$("' & &"%"%#$#$!#!#!"!"!"!"!"$'*)+++((((&%52,*.*40932,0(0(IFqmװWLOD861/( )&'''((#*#*)-)-#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#( +$,#!)'% 51.,omDB%6-'$'$%$%$#%#%!)!),,!,!,#(#(#%#%""%-%-)1)1)()(*"2+3/3/ҹbo'*2%" )'.*'#14Y]UP>9),')!!"!"!%!%#(#(%*%*%*%*%)%)%&%&#$#$!(!(!(!(!(!(!(!(!(!(!(!(!(!(!(!(%'%'%'%'#(#(#(#(!)!)))))))----++!)!)%'%''&'&)%)%+$+$/416JOkp_bAD24;>%*!!&!"%%)25#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(""))..'-'-1+E?oo1'D:6/)"!(!'""!#))++#%#%% % !$!$%*%*)/)/')') '#4,,$쏒Q^!)+4%$!%"-('#8;koig510+&((*'(!"!"!%!%#(#(%*%*%*%*%)%)%&%&#$#$!(!(!(!(!(!(!(!(!(!(!(!(!(!(!(!(%'%'%'%'#(#(#(#(!)!)))))))----++!)!)%'%''&'&)%)%+$+$#(9>38?DUZy|=@+- #("!!*-,/*-##(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(""))..'-'-5/4-VPϋ_U6,(!-&+%)"% '"$'#%))++#%#%% % !$!$%*%*)/)/')')"/+0(' fiZe3?"(%,&")"&0.*(OPGE75%$!$!" %#(#(%*%*)+)++++++)+)+'+')$)$'"'"!(!(!(!(!(!(!(!(!(!(!(!(!(!(!(!(%%%%%&%&#(#(#+#+#,#,!-!-!-!-!,!,))((((((!*!*#+#+#-#-#.#.'.&3)"#(LEc\\[5/0+/'4,)%! -,+/!#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(!!,,55../+%!+"D:WM$"313.)%& "#&&**#%#%''#'#''''')*)*#+#+ +.("%:;>I+7&-"( '!($(/-)'bcΪcn=;86,#/''$  $#(#(%*%*)+)++++++)+)+'+')$)$'"'"!(!(!(!(!(!(!(!(!(!(!(!(!(!(!(!(%%%%%&%&#(#(#+#+#,#,!-!-!-!-!,!,))((((((!*!*#+#+#-#-#.#..() .596:=64-[Lrd⤙^Y=7&1*/+'# ##'*'*#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(!!,,55..*&-)6,5+hmê3164/**&"%&&**#%#%''#'#''''')*)*#+#+ !.(.(45,6(2(-""+#,!.!33--׃MU)1!'"(!##%$"! )&/+++#*#*')')+(+(+'+'''''#'#'!&!&!(!(!(!(!(!(!(!(!(!(!(!(!(!(!(!(#'#'%'%'%)%)%*%*%*%*%*%*%(%(''''#&#&#%#%#%#%#&#&#(#(#+#+#.#.#0#0&'%"#"(&+#+#>9XSA@=<66@@67-.!$$&#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(/"/"'*'*//**"&5/,&77YY鮶^jDO,/25/,*' "$((,,#*#*'$'$%*%*)")")#)##+#+$1,-)*74WU'11:%+#)"+!.%44..ؠ[a.7+3%*%*!# " ! *'/+++#*#*')')+(+(+'+'''''#'#'!&!&!(!(!(!(!(!(!(!(!(!(!(!(!(!(!(!(#'#'%'%'%)%)%*%*%*%*%*%*%(%(''''#&#&#%#%#%#%#&#&#(#(#+#+#.#.#0#0"-&0)0$+#"(&/-3+-%2,0+FHhjѡ|{VU**,+,-,-%'(+#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(/"/"'*'*//**" /*/*2177wմYd+.-0-)'$$&((,,#*#*'$'$%*%*)")")#)##+#+.;)*"#74:8Ԫ}(/9 $#(#.&0.,7957ÿYS60"&&+ /. 0-)+)+3(0%**((#&#&%&%&%&%&!(!(++,,!(!(!(!(!(!(!(!(!(!(!(!(!(!(!(!(#)#)#)#)%*%*')')'('()$)$)!)!++'-'-'*'*'''''$'$'#'#%"%"%#%#%$%$1$/")&,)&/$-&!".!-(,%))(,,IFb^\f8B!'(.&)+.#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(7'7'/'/'#%#%##"+""&)-3.-(LCkbDN.8#&#& #**00#.#.)()(%-%-))))!,!,%9+"&"&*(0.؇fw$.8).$/&0,/79792+3-)-$((,,,/-04).#**((#&#&%&%&%&%&!(!(++,,!(!(!(!(!(!(!(!(!(!(!(!(!(!(!(!(#)#)#)#)%*%*')')'('()$)$)!)!++'-'-'*'*'''''$'$'#'#%"%"%#%#%$%$0#2&,))&&'-$3 +$!%(,762295;8NUku4:,1$'"%#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(7'7'/'/'#%#%##)"!%(,51/+<2E:*".&"!**//#,#,)%)%#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(&)/2512.qoqq00)&)&!+!+,,###+#+#)#)#'#'#&#&'+$("'"' ) )*)陕.72;!# &%'&*'(&;30'ֻ^V&&..&'.+(!-!-'-'-%*%*!%!%#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#"("'"' $#8=UZOM64 &(.**))%&%&-"-"#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#( -&30(*#G5LCOI3-%$%$&&((++!-!-)+)+%)%)''#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(%&%&!%!%$$##'&+0-2)(+)g_t.=$3"(*0)%"%+!-###(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#("5.%"0&+!fnҪgmEF#((-+' '#*,&("^Wԗu|QX)'1/%#%#&&****%)%))')'%(%())#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#$#$!&!&((*** ,"(""(%1'2(A>FBlsŨddHH(*!.%-$(#(##(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#( 3"5"+)+)-2-2`n]W=8#!0."0#(#(#(#(#(#(#(#(#'#'#'#'#'#'#'#'714/BA %$).*+!/,%' b`PN76 $,0)&)&!)!)++(()$)$+$+$#(#(,,#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(''!'!'%(%('('(*+,/&)#$# 50,'55WWDE45.-11&($&#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(((2!*!%)+//+.*0.nk_c'+59 ).#'#'#'#'#(#(#(#(#(#(#(#(#(#(#(#(3-+$srA? %%*.*+ .+$' fc?>0/&*+/)&)&!)!)++(()$)$+$+$#(#(,,#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(''!'!'%(%('('(()'("%"%!'$*#,'4/>=<%$))$&&(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#( &/%/('+(,,(.*/,OL㟥|,1)-").#'#'#'#'#(#(#(#(#(#(#(#(#(#(#(#(4..(䪽`^:7 $#(1-. 0+$&rs5.+$#+'-)-)%+%+,,#'#'+!+!+!+!#'#'..#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(**#'#'+$+$1"1"7,." !,/57#-';33+:2SJ圖l~1=%0 "$#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#($&$0!-'/$+#&7)5'fiͨR_(5!$#$#$#%#%#'#'#(#(#(#(#(#(#(#(#(#(.(/)VS74 $#(1-. 0+$&||3,+$&."-)-)%+%+,,#'#'+!+!+!+!#'#'..#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(**#'#'+$+$1"1"+ ,!),.1.$ +5/(& .'4,:28/e_MY.:!#%#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#% ,+&.#*#*B3)?B}}4@%#$#$#%#%#'#'#(#(#(#(#(#(#(#(#(#(*%0*n%;5 ,)5(!"#!"*006㛛uq%("0"0#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#'#'%'%'%(%('('(%)$("'#(#*#*(&!$!! )(+$#$!$!!!"#*&../+, & &! !  %#%#&'$&$*&+!*!*(($$##%%))//22#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(22((!"!")+)+uhgA(H/- *%$('*$'-**''%&%&)')'#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(,,%-%-)+)+)#)#/'$/.<;騬uu3"5%2/*'&!*,'("/)')'/&/&+'+'#(#(11-.fe%:4 ,(4'!#$!"*017䪪¾kh',!!/)#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#'#'%'%'%(%('('(%)$("'#(#*#*(& '*&  *#/'" %%*'.$,#*'33,,%$ %"&#'%&$'(&''-'-!*!*(($$##%%))//22#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(22((!"!")+)+@Jr|eLB).!,! (!,.,**''%&%&)')'#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(,,%-%-)+)+)#)#( &+)97y}ѺK;2!-*'$$&)*+,)')'/&/&+'+'#(#(11./dd%81 ,&3&!%$#",16:弿`['0&+"#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#'#'%'%'%'%'%(%(%($'"'#(%+%+!)&,+*0)/6341;:DB>A9=-3,21266;6;6)2(1(/(/&,'-'+),"% $ &%** -!/%*%*%*%*#+#+#*#*#(#(#&#&!$!$!"!"#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(&&&&!'!''+'+.-<;xy䷸[S)(.-,,() $%) )'''((%'%')&)&#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(((%)%))')''"'" '"&%00@Astsk4,,)-+")'(-*&''''+'+'''''!'!'/0,.^_' 70!-$0$!%$"!,19>XR(1')$#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#'#'%'%'%'%'%(%(%($'"'#(%+%+!)&"0$%+6<[Xdbihji]aPTAji˒귽zyj2929183:3;5>5=6>18.6*1*1+/).$( $' ' '$'$'('(',',%,%,%(%(%#%#% % #(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(!!!%!%!*!*%(%(0)(!3(J?grEP).5:3-'"'###))#)#)'&'&#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(!"!"'$'$)&)&%"%"#" +,!#/269mk(&20-2*0&&$$#%#%)&)&%&%&''-.,-QSɜ*%5/,, #(&" 23CDh`NG)0#&%2#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(%'%'#(#(#(#(#(#(#("'"(#)',',%*"'58fiﱰyX`6420#)&+%$$)#%#%)&)&%&%&''-.,-MNŋ,(40.,#+($!64IHRJE>*/#%$2#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(%(%(#(#(!(!(!(!(!) (")#*),),)+&)qy\bPW<;540(2*.#.#' ' ' ' %"%"%%%%#(#(!*!*!-!-!.!.#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#"#"!&!&!&!&!$!$&!)$/*& ##77w}_c<@4(<0, $#!#!!*!*#*#*%&%&#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(!"!"'%'%+'+'%&%&& &!+/2&*2/LHijJJ&,(. )%!'!%!%%'%'!'!'&&)+(*GH{}.*51.+#+($!64KIE>A9+3'#"0#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(%(%(#(#(!(!(!(!(!) (")#*),),)+&)tsXWC:80-!)' ' ' ' %"%"%%%%#(#(!*!*!-!-!.!.#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#"#"!&!&!&!&!$!$)$"/*<6;:,+BIfmRV-!5), ##!#!!*!*#*#*%&%&#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(!"!"'%'%+'+'%&%&&&#,5%)#GD%!ϸ`a(.(.$-(%!%!%%'%'!'!'&&)+)+EFtvsr/./.+''$1+2,63KH?6?6*&**#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(%(%(#(#((((("-#.'#&)$'//DDkbSK*:,;+5*4%)$'""$#!"#$ (&'%,.#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(%/%/#'#'! ! !!!!#+#+&0%/'/&.&*&*|sA93+1&-".+*'&,'-' $+0#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(!'!''('(+*+*%*%*****!*!*+)+)QPǰ9>)-27-3")!%!%%(%(!'!'$$#$/0GJWZji**/./**#+%+%2.KHB9B9+!(++#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(%(%(#(#(((((&)")#**,$'55TUǾetL\/:&1+.25+*%$"# "-/30#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(%/%/#'#'! ! !!!!#+#+&0%/'/&.&*&*]Uf^:2.#9.# # #(&,#, ( %"&#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(!'!''('(+*+*%*%*****!*!*+)+)KJOT*.%*(. '!%!%%(%(!'!'$$#$--BFPTaa%%---+ -$*$*$;5]WA9A9*#' --#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(%)%)!(!(((((%&!'%**+$&A>mjxMf.<+8+/492/-+  "&'#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(%3%3#(#(##!"!"#/#/"4!3!- ,"":*E4痔ih<9;8&%((#&"%")#*!*%#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(,,'+'+-*-*'+'+--,,)))&)&27CGDG&(-/"##$#&#&%*%*#(#(##"'*9dg(*.0&$*&.&1)SK؆~@8?7&"!++#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(%)%)!)!)(((("/)$%+12()93YSntHNAG01241/30(##(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#*#*#(#(#&#&#'#'$*$*!*!*))''5*.#'$PL9C+-13('$"!#. /#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(--'('(-%-%+&+&!*!*++#(#(+$+$(#KS˙88,+('(''('(),),%)%)## "&)1558fh*+/1*$(#*"+#OFӄ|?7=6&" +*#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(%)%)!)!)((((&("),2<=/0.(71~?@&'" /-,'%!#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#*#*#(#(#&#&#'#'#)#)!*!*))((0%.#>:95^coy)+)+$")(%* $+-#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(--'('(-%-%+&+&!*!*++#(#(+$+$#$5=ltFF((&%-,'('(),),%)%)## "&)2658]_#'/3.%(%+$*#NDτz?:>9&$!--#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(%*%*!)!)((((%-&,&,9832.(.(ܕjuGJ>B7575#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(###'#'%.%.%+%+####!!&&..! &%>;.+31trFK16'$(. %-1#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(,,%&%&/ / -!-!#&#&))%&%&-"-"-2INⰪji75(%/,))))+-+-')')#"#"$(0528UW"/30&)&/(.'QG҇}@;?:(&" ..#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(%*%*!)!)(((((#1%+!+*0/5/61ffSW531/#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(###'#'%.%.%+%+####!!&&..&%-,,)-*HEHEχkpAF!&+$.$7#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(,,%&%&/ / -!-!#&#&))%&%&-"-"$49=BքGF*'*'))))+-+-')')#"#"#'0528?G"+0,%!(%,(,(DLxllBB25"(3#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(@?^]gc"'0536,/!!"%&)(0# '+.+.-*/,--!*!*%&%&'#'#%#%#!&!&**--#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#($3-! "!%NPpr.$B7/$0% &,%'%'%(%(%(%(%%%%%"%"!#!#((..#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(*.((-.FAҐzx,;--. .,('"%%(?48-@H").,%!(%,(,(DLxzzHH14#&,2#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(--<<[\{}rw?D'+/3%(!'+!)#++1+1)+$')&/,--!*!*%&%&'#'#%#%#!&!&**--#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#2-"#&(*$%;=LNTJ@6.#7,%+%'%'%(%(%(%(%%%%%"%"!#!#((..#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(,,((00>9idA33%')- .+*#&$'>38-BJ$(-,%#( $,(,(DLxQR/0*+**#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(% % )&0-BATTlmvwU_5@029:.-&%"!)(+,&(##$%('****((#&#&'$'$%%%%#&#&((**#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#($1*"$*+)$"*,,.nxמOJ)"91 &(#%#%#'#'%'%'%&%&%$%$#$#$#(#(!*!*#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(,,!#.050<7ݧc^0+&)"&'),!.(#'"!/ .7621HP"'',,%#( $,(,(DLxXY'(+,!-+#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(601,,)'%**-,9:<=??@@FFJIWTkhЈ뛖jt9:23+)&%.--,%&!$%---,&%**((#&#&'$'$%%%%#&#&((**#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(%2)"'$$"-/02@Imwws$,$%')+#%#%#'#'%'%'%&%&%$%$#$#$#(#(!*!*#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(+, "+,:51+х83'*(,//!.,'"'""0,5510QW),),-&#( $*'*'BKvfg$!,)%&%&#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#$#$$%#$"$!#"$!# %&.).)/&2*:0C8O?)*ef¦lgE@724.#$#$#%#%#'#'#(#(%'%'%'%''&'&'%'%#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(21 #6-:0JLnp#%,++'$%&)*0/29.5Y`/1(*!-&#( $*'*'@Jt~4/2.#* #(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#("(!'''(() *$.", (&#$"&#'2(2(/%'(1'J@[R|uMQ;>%(.1))## +%##%%!(!(#*#*%*%*%(%(%%%%%#%##(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(%(!0!0!- ,"& $/*75_\||A6>3%&%&#%#%#&#&!)!)#+#+')'))%)%+!+!#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(3+ &+"2)42IGܾ4;3: # # #$'$'(*48.3Y`/1&(!-&#( $*'*'@Jt~=850!)#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#( &!' *!+!,!, *) * * ("*"($-#2(:0;1=29/=4=4@:KDabwxϘԼcg'**-//--  ##%%!(!(#*#*%*%*%(%(%%%%%#%##(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#( *()%' ,$(#'.)4/@=C@ؖFE"$!#"&$'"'+5:.3_d12,-%-"*#%/+($9DZd<;;:1#*#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#('('('('(%*%*#+#+!,!,,,++++))''!&!&%'%'+,,-01.0=BZ_jx6C$(9<6/'  #$(#'"("(") (#*#*%'%''$'$'#'##(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(!&!&++,,%%!%!830+MWÞA6"&15&',/ %,$!+!+%+%++%+%--#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(++%%#"#"////R]4962-(!!+,"$!"93@:`e./)*%-#+!#%.*&!4?Q\VTA?)*#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#('('('('(%*%*#+#+!,!,,,++++))''!&!&%'%'%&'(.0,-/46;FNRYߪt:>%)!(!)+)+'+%)$*$*#*")#*#*%'%''$'$'#'##(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(!&!&++,,%%" ,('#9Cjtf[%)-2&'(*#&"%(.$!+!+%+%++%+%--#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(++%%#"#"////FRU[$$(* "((%&=7F@hn.-,,'-&, #$&,)",8EQV\'3'#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(- - +!+!+"+")$)$'&'&%(%(#)#)#)#)..++((%%!"%#&"% =7ZT狈MS4/:500.-(*)+),),%,%,!(!(#'#'#&#&%&%&#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(%%%%!*!*++&&"# $#('-34:x9>)/13-/(&#'**#,#,'('(+ + #(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#%#%!)!)((%(%(/7QY+'3/5.6/4310&$&$G=cYu{,,0/%*&, #$&/+$!+7@Lkq(5)#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(- - +!+!+"+")$)$'&'&%(%(#)#)#)#)..++((%%$$"%#&'+),),(+>871>;SPƇҼVQ62,+))(*+.*.'*"("(!(!(#'#'#&#&%&%&#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(%%%%!*!*++&& "#*)109?'-N]ƙW\!'.002+*"!**#,#,'('(+ + #(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#%#%!)!)((%(%(.6-56340(!!''(&$"G=qg+)42#''+""&%0,)%-9=I:27/#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#()")")")"'#'#'#'#%$%$%%%%#&#&#&#&++**))(("*'  #!$ #./+,,-#$44UT떖^`88543388/1(*" $%%%%((!*!*#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#()%)%%'%'!)!)**")!'%*44&&10ZY6?//=<,&1*%&((--#*#*'$'$#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(+ + %.%.11&&*-ܒLM78,'0,33@@>D6::NNZZȮEE54'(,,+.+.%))-%%%%((!*!*#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#()%)%%'%'!)!)**)!,%!,,//44;;W_**66.((""#((--#*#*'$'$#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(+ + %.%.11&&/2dnҰqsEF94<800**.49?hdѝ%!51!%&* ! -)+',:8F}{=;#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#,#,#,#,#+#+#)#)!(!(!(!(!(!(!(!('$'$'&'&')')'+'+!)$,'.)1)0)0(-(-!+*$,&;-;-UH̓wXW,+'&,+32)+)+##%%**--#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(-%-%)%)%!(!(,,!40$. +$"-+8*.wqγ;;33,$#&&--!+!+%&%&#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(//'1'177&&('@EċYb3423..+,_p}ݴ&"95"&&**&)%(63AJH#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#,#,#,#,#+#+#)#)!(!(!(!(!(!(!(!('$'$'&'&')')'+'+&.'/'.%,!( '"'%*% #"'.)*$T[EB20&(#&#'#'%$%$%%%%#)#)#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(!*!*%&%&''''!,!, *(-,%#HLQK83&&&&(',,&*&*QW4:-&6/ --44#+#+''#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(%+%+%)%)#%#%####+*# ,'*%)%,';4keu{;A65330:+5( *$%#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(-) :7%"զ}~KL(#3/%"%"!(!(!*!*!&!&#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(!+!+%%%%'&'&#+#+!)'++""46prd_;5%%$$'',+%)%)npy@F%3+##--44#+#+''#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(%+%+%)%)#%#%#####"'$# '#-(0,-(.'=6qsӭy11'&#--6'1'1%&#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#($ &";8(%lncc .)%"%"!(!(!*!*!&!&#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(!+!+%%%%'&'&#+#+!)'))$%35XZytC?(%(%'( , ,&)&)\[2164"()/**,,!(!()!)!#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(%*%*%(%(#&#&!#!# &(()$'"$"# ")*+1PV`d;? $,1*/+0&'""#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#("#&2+*"IC׎3'5)'"'",,..!$!$#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(,,%%%%'%'%#*#* (' " "00=<ፔ~GB(%)&()".!-)+(*UT?>32)/&-**,,!(!()!)!#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(%*%*%(%(#&#&!#!#*,"# !""$!"#&("#()-3%+U]ae)-)-(-',#$#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#( #1) GANHE:;/'"'",,..!$!$#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(,,%%%%'%'%#*#*'& "43//t{EA-(-( ( (",!+&*&*LJƔS\'16@"'%+"#""'&**%%#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(%&%&%&%&%&%&#&#&!'!'((****(+.2+/63HE뗖JG2//'7/!#),#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(' *("("5.=6猘s;0/.0$+0%$&%#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(,,%$%$)#)##+#+ ,(#%*,/-/-fi蛞JF,'-(' (",!+'+&*EC}zMW%/%$*(()*))%$&'&'#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(%&%&%&%&%&%&#&#&!'!'((****$!"'"'/,74lkΒFC)!7/&(#&#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#()!,& *%6/+$lyQF--0(,0%$%$#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(,,%$%$)#)##+#+("!")')'QTruYW.).)!&"'"+!*'*&):3YRFO953/( .&)&#!#+-4#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(!$!$#%#%%&%&'('(%*%*#,#,--..*$!$($$!67CD䍚oo1):3,+'%#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(."2!!))70FJ}x'.-3,+)),%*$#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(,,%#%#)")"%+%+,*"#$&$&9=BFli,(.)!&"'"+"+'*'*92?8IE3/*#/,)&#+)0#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(!$!$#%#%%&%&'('(%*%*#,#,--.. ,(!$(+1.*'1201S`NF:3(&/-#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#()&5!!%%>7&=A}18(/&-)(-&*$#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(,,%#%#)")"%+%+.!0!)&)+*,5:050)2+#$$&"*"*&+%*7./&cb75<::=58!)&#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(%%!&!&'('())))+)+)')')#(#(!'!'&($'"%&)")& * *$1ZfTW*!=4#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#((,8$& 6.1*90PHH[*<)!" 5,0'#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(,,%"%")")"%,%,)0)$ $$(-1(,޽/(0)"##$"*"*&+&+:00'ҺSQ75&)(,$,'/#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(%%!&!&'('())))+)+)')')#(#(!'!'&(')#&#&$$#,%/)55AeoŠqt.%3+#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(+)5&',$4,:12)~j|3F '7.0'#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(,,%"%")")"%,%,).'$ $#'.2.2䛗/)0*""#$")#*',&+7+4(ᒘgo3<+3$*,3#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(''!(!('('(+(+(-&-&+$+$'!'!%%2(3)*.(+,.,-",(35Y[SF4'#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#((2!+'+&*#-!9'(\^ҠE]'  ?15(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(,,%"%"+!+!%-%-30%)$)#(*1*1{t/)0*""#$")#*',',4(9-ziqBK#)##(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(''!(!('('(+(+(-&-&+$+$'!'!%%.$0%'*'*.0,*",$/-/46Ǝr>1#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(,6!'+15"&=+0GIz|Qj(  ?13&#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(,,%"%"+!+!%-%-#8,&&+!&#*#*g`HK="8D#(# &)#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(QFg\fiDG'0'('1 $(,)!.;?44)*"%$('!,'1%/%0$/ - -!* ( ' '"&#'%&%&'('()*)*)+)+#(#(%&%&%&%&%&%&%&%&#(#(!*!*!*!*#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(%)%)#/#/#0#0%*%*/'-%900().DIʒ>C*/,$5-!#1 -..!!#!#!%/%/#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(###,#,)*)*-!-!--44敨-81=$)!& $%(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(<1PESX'*&)"&+'/'/!$,=>Àt{HO6=/78@.5*0'-&, $# &%#"  ##$$''((!*!*!*!*#*#*#*#*#)#)#(#(#'#'!&!&#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(%$%$#*#*#,#,#(#(("("0)0),-%&WdԠgs=I+&72-&& )!*--$$####%,%,#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(&&#,#,))))+ + #"/-fvTa-;(,&)'&%$#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(*#7/\a #(+!#'-5$,"&/=>[\bi;B08*0/6%+"' & &$#""####$$''((!*!*!*!*#*#*#*#*#)#)#(#(#'#'!&!&#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(%$%$#*#*#,#,#(#("#' 1*CD12;=::872424././----)*)*%'%'#&#&!#!#!%!%((**++!)!)#&#&%$%$#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(%!%!%&%&#+#+!*!* $#'() !#,3)/82qkPS-!5)'"&!!*!*..#)#)'#'##(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(--#,#,'&'&' ' $)$8>,1./23$*"#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(*)%$NSI=5*-$#"51)# 50)%qr~kxOQMOJJGF@B>?9999----)*)*%'%'#&#&!#!#!%!%((**++!)!)#&#&%$%$#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(%!%!%&%&#+#+!*!*+/!"&(!"')/)#GB猘eh1%6*(#'"!*!*..#)#)'#'##(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(--#,#,'&'&' ' $1,9?#)0234",$#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(,+*)AFSFJ=/&) 41#+#'#,)LP͎退`_POFBD?;6>9>7B;@7?6-$/&+'*&$&#$ (!)&( *!,%,$*$("'#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(%&%&%)%)%,%,#,#,!*!*''&&&&,'.)NW؏<5GA-(!'!'!/!/#+#+'"'"#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(..#*#*%&%&#$#$""+,'':;UYPRAC%+##(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#( ' '<;zypcRE,"-#0/#)!'#,)DH|oiLG4-/(.&0'.%'!%'*+"*#()) *$*#)%)$(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(%&%&%)%)%,%,#,#,!*!*''&&&&#3.7@Waf_<5&!+%!'!'!/!/#+#+'"'"#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(..#*#*%&%&#$#$%&$%23HLnpEG*"( #(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#($*%+<;fei^+&3/*,*$,&((**48\`ļy[OA6;3:23/3/--..&+%* ('('")#*%,&-#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(!+!+#+#+%+%+'+'+%+%+#*#*((((24'*'*ןEJ/-0/#%#%!+!+%+%+)$)$#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(++#'#'%'%'!*!*"")$"+):7vsRV1*,%#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(-#24.C=xm+&2-*.*$)"**++.3EJ禮KD@8-))&%%()$"!) ('& (")&-'.#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(!+!+#+#+%+%+'+'+%+%+#*#*(((("$-003(+vخin(&10#%#%!+!+%+%+)$)$#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(++#'#'%'%'!*!*$"))&,*85PNX\0)/(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(+#21,,&9;01*/%%""$'$'!(,4逄V_BK9@=E+4"*#*"(&$' (&-'.#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(,,!)!)'%'%)&)&+(+('*'*#*#*!)!)#+/,2.GCso#'48%#%#!'!'%+%+)')'#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(((#$#$#)#)11%(%!&"4+3*nt*)21#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(.40'#巴RS)*..**$$(,&)(/,4hlܵzCK-5!)(0"(!''& (!*%,&-#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(,,!)!)'%'%)&)&+(+('*'*#*#*!)!))1 --)510+GC4837%#%#!'!'%+%+)')'#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(((#$#$#)#)11))'#($<40(Щ%$32#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(243*,#qz"+-$12(**/$*+2+2NPӒtGW'2*4 (!)% &"(!'#+"*#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(++!&!&' ' + + -$-$+(+(')')%)%)%2#*#,%6)1$ԧfr.41.+&$+(+(!+!+,,''%"%"+ + %&%&,,#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#+""!.,/**$LRqw9350-!/#"#'"#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(!*!*!(!(!&!&#&#&&) %,$+-7 *+8䎛QU:=%*%*''$$$$)&)&-(-(%(%(&&#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(+.$'.*62@@nr1(0'!.2% !2$:,vh2"-&#&#)(3+(+(!+!+,,''%"%"+ + %&%&,,#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(%-$&$(# 4:GM<761, 1%#$(%#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(!*!*!(!(!&!&#&#&%( #%,"*+6!,)6{knEI%*%*''$$$$)&)&-(-(%(%(&&#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#((*&(3.62==>:2.'#(/&)$'+#)!bmonJH,,'' "$'+&+&))++((%#%#+"+"'&'&++#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(%,'$"*&&".2.2џIE=8-#3) &%*)#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(!*!*!)!)!&!&#&#&"%%''- &)0#+&0[fW\'+'+!'!'%%%%)&)&-(-(%(%(&&#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#)#)502.52LH4/*&-1),&),%( OZ~<<,,#&"%+&+&))++((%#%#+"+"'&'&++#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#*$+%#%#,(0,48,/o~\XB>) 0' &%))#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(!*!*!)!)!&!&#&#&!$),'- &'.&-$/8d^x|NR>8E?+$+$''))((!&!&)&)&''''!)!)#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#+)1)*"##&"+,$%HP~TU,%1*"&$)*#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(!*!*!)!)!&!&#%#%!",-&)!%&)&)',&+qx+-+-#)#)&&%%)&)&-(-('('(&&#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#( -%,)($;2h_BF-4!2,'(+,-.3-:4պqu821++$+$''))((!&!&)&)&''''!)!)#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(&$,%'"##&"1223;Cgh-&.'#( %((#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(!*!*!)!)!&!&#%#%"#,-"&%(&)$(*/!堘+-+-#)#)&&%%)&)&-(-('('(&&#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(!.$-*%">6YPku-&6'+"&(+%.%.-(}le@81(1(#(#(((((!(!('('('('(!(!(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#((( $$'%&%&20658:Y[븿723/'+&*+)#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(!*!*!)!)!'!'#%#%$$++ ()''""01x1/1/'+'+''%%''''+(+('('(''#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(,$0,#:.C7鰲.&6'+#'!.$1'0"+:+3$c\װtm1(1(#(#(((((!(!('('('('(!(!(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(*3"+"%(%&$"'&<>@BҜ:650).(,,)#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(!*!*!)!)!'!'#%#%%%))++'' 44pi1/1/'+'+''%%''''+(+('('(''#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(*&62$ 6*5)ۦDI6;!%%, (&$0%3*=6unG@1*.169!($3!!(!('*'*'('(!'!'#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#($($( ' ' $ $$%$%75-+tuUS97)+!#1)#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(!*!*!)!)!'!'#&#&%%%%%%%%'%'%'%'%_VѱVP/)*,-0! *&&%&%&+(+(''''!&!&#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(..#'"@8>6הei5:$1*%$#.)%&D=MF᚞䌐|wtzxҍ䭯}uLD(+/2*,-$!(!('*'*'('(!'!'#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(%*%*!(!(!%!%%&%&64-+dfϷdb86'("$.+#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(!*!*!)!)!'!'#&#&%%%%%%%%'%'%'%'%QHà]W2,+-.1#!+&%%&%&+(+(''''!&!&#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(+),&'"5,XOPQ0(C; "!&1"#!33%%զz\[LKG48.:9CB\_orֵYX54$+'-##*#&#&')')%)%)!&!&#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(&(&("+"+** ( (-**';6toHH(%*((.#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(!)!)!(!(!(!(#'#'%&%&%&%&'%'%'%'%3,sluk>5,-11(#/&%#'#')')'''''!&!&#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#((,*.610+d`ڼA@/-42/,)3/$+#*BA҄uxBD)+'%*'+)+)'$$!   '#3.6261.)&#"c^ۏUT(&-+%#,*%#%#'('(#)#)((#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#($$$$**,,((&&((4+\T]_*$-(',#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(!'!'!(!(!(!(#(#(%(%(%(%('('(''''4,d[zF:*)0/'%/('#'#')')'''''#&#&#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(1+7140GCݣ_^/-31-+&2 29!EELLܻ]a8<-/79%#)&+)(%(%,)0-0,)%% #*%1,7395JEJExx~|0.,*)(('%#%#'('(#)#)((#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#($$$$**,,((&&))5,PG鹷rt(#,&),#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(!'!'!(!(!(!(#(#(%(%(%(%('('(''''<4[ROC+*--'%/'(#'#')')'''''#&#&#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(.)2-95heSZ0+0+ 6',7!>2-!牐gc0'&'5( !#!&&-//,),'+)-(&$!@$/C6znPK82/(& '!'!'&'&#(#(((#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(%"%",,00++%%&&6+C8虔%("--#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(!&!&!'!'!)!)#)#)%*%*%*%*'('('('(81G@]P0/0/'$/&'#'#')')'''''#&#&#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(7,2'YVۣu|*%/*#-56 *%00$/#ahǞda@=0'90,#!,-0-22!/"0&*&*&$$!;<9->2fa>8-'!'!'!'&'&#(#(((#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(&$&$ - -11,,%%%%6+;0~!%//#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(!&!&!'!'!)!)#)#)%*%*%*%*'('('('(6.=5g[5420(#.%&#'#')')'''''#&#&#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(;0.#tqC77,/8%(3&#).%)5;V[W_(.*2#+$-%#%#%#'#'#(#(#*#*#*#*#(#(#'#'#%#%*&*)"!6=kscb) 5,"#"#+* .-,#,##(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#('&'&'+'+%$%$&&0.,1!&b^߼NY-8#$%'#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(98-,؈0!:,!!(.!&!&'&'&'+'+#$#$'&'&%)%)#,#,#,#,)1& "&((''&"&"ABhjfZ7,'(.1*)&*!%.4?E^f;C($3-5$,#)!#%#%#'#'#(#(#*#*#*#*#(#(#'#'#%#%%!+'0/('%-CJ뢮~~7.7.))---$-$#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#('&'&'+'+%$%$&&0/,1"'[Wآ_j0;"#%'#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(66,+׃0!:," %"+/!&!&'&'&'+'+#$#$'&'&%)%)#,#,#,#,$,%-'*"&!!""'#'#YZJK -2!+% .$1",)(+(+|½*1%,($2&0 )#&"#%#%#'#'#(#(#)#)#)#)#(#(#'#'#%#%++&&(,+.'!)qzID1+  && *!+,&,&#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(%&%&'+'+%$%$%%0/*/!&OIysw2> #&)#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(1/'&{0"9+%#('./!&!&'%'%'+'+#$#$'$'$#)#)--++%")))%%,).,31.,܋no(,-2 **%3*",/2"%c]vy%,,3"0 -'(($*&#%#%#'#'#(#(#)#)#)#)#(#(#'#'#%#%//!! #,/.6%,OYvd_*$!!((!+!++%,&#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(%&%&'+'+%$%$%%//).#(HBWQ2>")+#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(.-'&sz0"7)$")(..!&!&'%'%'+'+#$#$'$'$#)#)--++ '! ((420.64;9齿NP'*('*#0*#1-0!9;UW箶JG*-14"2'#)('&%#&#&#'#'#(#(#(#(#(#(#(#(#'#'#&#&'+"'%$++2(0:B>F86),),))!)!))&(%#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(%&%&',',%$%$$$,.'('(?8@94?&&++#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(,)'$in̹1%5*"")).-!&!&'%'%'+'+#%#%)")"!*!*//))!#-(:35.^\Ȉsv$&'&$# -,+#1), $029;ˁig:7),/2/*)-&% #&#&#'#'#(#(#(#(#(#(#(#(#'#'#&#& $).(.%&%,:B7?ijNL*-*-**"*!)(%(%#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(%&%&',',%$%$$$,2"#)*<4=57B))''#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(.+)&bhů5*8,""'&--!&!&'%'%'+'+#%#%)")"!*!*//)),'+&-&6/ӆU\+)53)*#$"*$,&(!)!)LVܐKF4/ #$*)+,$$!!#'#'#(#(#(#(#(#(#(#(#(#(#(#(#'#')%1",$!!&3715ٌvy.2.2&&!&!&'('(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#'#'',','$'$!$!$+$2''7,@5AJ//""#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(.(*%[aɣ<4>5%&'(.0!&!&'%'%'+'+#%#%) ) **00**%7-.%H@ng53() !&/%-$"+.6(06@blB=<7 "#$)(('$$((#'#'#(#(#(#(#(#(#(#(#(#(#(#(#'#'#.*"%(-)/15)-x{).).$% %!&(*(*#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#'#'',','$'$!$!$,'6&&5*C8诸GP22#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(/*-'Y`ȟA8A8&'()/ 1!&!&'%'%'+'+#%#%) ) **00** &2(+!aZУ\c18"2+$!*8+#$-#+3;蟣;46.  ''**'$'$#(#(#(#(#(#(#'#'#'#'#(#(#(#(#(#(!."/((%#*-(+Z]/3'*$#$##%&) '+1#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(!&!&',','%'%!%!%,* ://$᢬gp,)'%#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(*%$UZ̏TN.($&),**!&!&'$'$'+'+#%#%/)& $ (0.,1+. "0(IAQX(!6/+(&$"0,!&'0 )]b­1)7/  ''**'$'$#(#(#(#(#(#(#'#'#'#'#(#(#(#(#(#(!.!.&$##.1.1Z]=A-0&%%$!#$'%(/#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(!&!&',','%'%!%!%++ ://$ᚤnw.,,)#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(*%IOWQ1+$&),.0!&!&'$'$'+'+#%#%& # (#,21./.0"%B:tl82712+*#%!,!%*."&'+fjus$71!!))+++#+##(#(#(#(#'#'#&#&#&#&#'#'#(#(#(#(#-",#" #.215W[»X\:=+%*$ $!)$0#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(!%!%%+%+)&)&#&#&,- ""5-+#䇕|1/1/#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#()%@Ioy_\52"''+. 2!&!&'$'$'+'+#&#&$$&&&)"&!)"+).&+46?A|{׾OJ2-+$("&&2'+&*5926<@˙us$:4!!))+++#+##(#(#(#(#'#'#&#&#&#&#'#'#(#(#(#(#-#-$#"#-1.2RWnr@C*$+%$! -%1#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(!%!%%+%+)&)&#&#&+- ##4,,$v,*1/#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(*&!EOktkg:6!%"'*.!&!&'$'$'+'+#&#&''(($' '&..3',*,^`LR++11)(/.#",*87''PRҕ)%51%%,,#,#,/ / #)#)#(#(#'#'#%#%#%#%#'#'#(#(#)#)',', &!'%$&/%.PVGI(-$%!#3$4#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#($$%+%+)&)&%'%',0 !$.**&dw($2-#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(##""KVepqp>< %!&*.!&!&'$'$'+'+#&#&"'"'%#%#5-;2G>@7[\qx./,,)(&$//%#3333?B_b2-/+%%,,#,#,/ / #)#)#(#(#'#'#%#%#%#%#'#'#(#(#)#)',(-!'")&$%.#+V\GI%.%%!%6#3#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#($$%+%+)&)&%'%',1 !$,),)Yk$2-#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(##MX\gqp>',@HDA/,((..%,%,33#*#*#(#(#'#'#%#%#%#%#'#'#(#(#*#*)+(*##"%-5/8ltLN&4'""4,#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#($$%*%*)&)&%(%(/#4 $*'1.La&":6#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(%% ?JDOrq<A;&$"% +*%$&3#7(>/數L_2F);(:#,"+'#)%, *#$#$#$#$%%%%'%'%'$'$%$%$#####"#",- %"'+$)"679;ۚJE*+*+&1) #(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(##%*%*)&)&#(#( 10#% !3*)W`+&72!(!(!(!(!(!(!(!(!(!(!(!(!(!(!(!(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#($#&%44BAFF60A;0,*&*,*,,/,/0.0.2+2+5#F5M<;)H8yi_Y5/&$*("%'&004$0 :,2$qtZm.A&8'9$-#,'#($-!, #$#$#$#$%%%%'%'%'$'$%$%$#####"#" 4-!#(0)*#01/0жKF,-*+%0)!#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(##%*%*)&)&#(#(/.#%"#7-+!U^+&72!(!(!(!(!(!(!(!(!(!(!(!(!(!(!(!(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(%$&%32A@CD,&2+# ()()*,*,.+.+0)0)5#7&=,H6|l躪NI-.-.*'&#+&.,!/-*(9:nnJP0032,(,(&$$!%%((%(%('('('('('('('('('('(''''%&%& -"%(51.*?=GFNH./)*$0* ##(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(##%*%*)&)&#(#(.,"##%:1,#T^+%71!(!(!(!(!(!(!(!(!(!(!(!(!(!(!(!(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(%%&&33BB죩v{filobidlS_S_ScScYcYc[a[a\fXa^eu|ÿ'( ,)74 &$!+)75789:ov2121+'/+'%" ""&&%(%('('('('('('('('('('(''''%&%&*#"$'-)-)YX~OJ01)*#.* ##(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(##%*%*)&)&#(#(/,!"!"90,#T^+%71!(!(!(!(!(!(!(!(!(!(!(!(!(!(!(!(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(&&&&44CC^a9<0'D;(#(,*&330!B>͎K9D205"&%$#)+'*'*')')')')'('('('(')')')')'*'* #03),01?@ǒJH/2&)". ,!"%#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#($$%*%*)&)&#(#(!2. " 4,+#Wc+$70!(!(!(!(!(!(!(!(!(!(!(!(!(!(!(!(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(&'&'56HI[^'/&)%&!'("/&1/1/2.SNݮmZSA..%$&%+,'*'*')')')')'('('('(')')')')'*'*",0#&23UVݴHE/2&)!- ,!"%#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#($$%*%*)&)&#(#(!2/!#1))!\h+$70!(!(!(!(!(!(!(!(!(!(!(!(!(!(!(!(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(&'&'9:MOPI6/+-/1$$$# #'0/*)]]瞝|H7;*,,..11%&%&#&#&!%!%$$$$!&!&#(#(%(%(,/.2afB?,0$'!.!.$!'#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#($$%*%*)&)&#'#'//$'!0))"^l)#5/!(!(!(!(!(!(!(!(!(!(!(!(!(!(!(!(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(&(&(=,+11cc츸\JB1/0222/%&%&#&#&!%!%$$$$!&!&#(#(%(%(**##+.;?ފ@<,0$'!."/$ %#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#($$%*%*)&)&#'#'*/&)!#1+)"_m)#5/!(!(!(!(!(!(!(!(!(!(!(!(!(!(!(!(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(%&')>AWZ2<7A*4'!% $")/'.&`[֯nf5611(4-:11((" %!& &!'%"$%$*%+'*&)=;64OS|C@!03,'$("#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#($$%*%*)%)%#&#&)/"-1)$-)_o% 51!(!(!(!(!(!(!(!(!(!(!(!(!(!(!(!(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(!%(,CGknKU)3 '&!',+1%++#,%NJvr`a99!-&3##&)+-!& & &###%(+*.DBwuA>!$/2+)%)!#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#($$%*%*)%)%#&#&,.),'#0+jz'#51!(!(!(!(!(!(!(!(!(!(!(!(!(!(!(!(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#("&(,EInrU^0/65&)!)#2&3,$#3131hnݱ^_CE0.1/0246-2(-'.-4 ('/3;5>8?3:6857ooКYV85"%),'*(-!#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(%%%*%*)%)%#&#&--#''%2/z("4.!(!(!(!(!(!(!(!(!(!(!(!(!(!(!(!(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(%)'+HLswKJ,+$&#%,,*+" $#65-,?Ekqa_HF+-&(&++0&-$,/7)1%- ).5HOqt>;52&('*' .(-!#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(%%%*%*)%)%#&#&.-#'+(41뉛v})#2+!(!(!(!(!(!(!(!(!(!(!(!(!(!(!(!(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(&*%)KPz~~A=52@>39)/'!,( '0%.(+/2ssdcRQFJEI>F=D@I@IHPQXnr,&1,&(#$&!-)*!#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(%%%+%+)$)$#%#%-. %)0.43蘬gl,(1,!(!(!(!(!(!(!(!(!(!(!(!(!(!(!(!(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(%* &OTӃ}x0.75)/&, +(3+ ("+,0*.GGrr(#4.&("#* ,$&"##(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(%%%+%+)$)$#%#%,/!%&+0.21妺[a3/40!(!(!(!(!(!(!(!(!(!(!(!(!(!(!(!(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#($)#TZَoqllLM"!(&$,$,11'(')&(. 7)u)$4/%$"!!,* "$&#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(%%%+%+)$)$#%#%)/"&$(-,1/LT4/2-!(!(!(!(!(!(!(!(!(!(!(!(!(!(!(!(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(%*"[bߘK:G7yɽss(&+*&-! &3,-(*$%,. RHh^in.)61%$#"#/)%'#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(%%%+%+)$)$#%#%'."&!%+*.-BJ1+.)!(!(!(!(!(!(!(!(!(!(!(!(!(!(!(!(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(&,#`f㟥}RJG7ZIG;i\VY:=-$0(9&$%''&&%%1402r}ޭBG3-3-))####!+!+!(!(!(!(!(!(!(!(!(!(!(!(!(!(!(!(')'))")"'$'$,,))#("EA>:=B+*,+' ' %"%"%#%##&#&#(#(!)!)****%&%&#(#(!*!*++**#'#'%#%#) ) +1#fxƻ+)&$*#1**4,6Zy䒱`c.&.&0 -+)# ""%%2525EPitձsx9=3-3-))####!+!+!(!(!(!(!(!(!(!(!(!(!(!(!(!(!(!(')'))")"'$'$,,' -'("HEMI=B*)((' ' %"%"%#%##&#&#(#(!)!)****%&%&#(#(!*!*++**#'#'%#%#) ) -3#izp|HT/,$""*#,6(16UMkֺVUA@0(90#"'"-'0&/$' "&/(`\䘔DL/7',',!$!$#&#&#,#,!(!(!(!(!(!(!(!(!(!(!(!(!(!(!(!(')'))")"'$'$!*!*$&0-&#PNhgs}7@$)#'%'%'%(%(#(#(#(#(#(#(#(#(#(#(#(#('$'$#&#&!)!)++++!*!*#(#(%&%&24 #n}{7C*(+#'!!%%**%%&(&(ጇᴯ^]+">6'&)"-%."+!# "4-0)=9VR٢098A',',!$!$#&#&#,#,!(!(!(!(!(!(!(!(!(!(!(!(!(!(!(!(')'))")"'$'$!*!*$ *,)VTՄ`i2;$)(,%'%'%(%(#(#(#(#(#(#(#(#(#(#(#(#('$'$#&#&!)!)++++!*!*#(#(%&%&/2#%uq~R_2>(3 #(+++&&%%((1346_ZfaMPADCE:;,2(.$" "*%$!$!'$]b֛Yd#'5))((!*!*)*)*!(!(!(!(!(!(!(!(!(!(!(!(!(!(!(!(%(%('$'$'%'%!*!*'$.*+bcΤCQ'5&&/!-!-#,#,#+#+#)#)%(%(%&%&'&'&'%'%'$'$%&%&!(!(******!)!)#(#(3-2+臏ip25*-&+(-''0+/$() /&9&9&F9TGX[57-.'-.4%0 *"'#!/,2/>BHMźGQ*%3))((!*!*)*)*!(!(!(!(!(!(!(!(!(!(!(!(!(!(!(!(%(%('$'$'%'%!*!*'#-&( "rs3A)7('0!-!-#,#,#+#+#)#)%(%(%&%&'&'&'%'%'$'$%&%&!(!(******!)!)#(#(.(=7V]@G03*- %).*2&."'!&-#/&-$YLXOF>1,;624,.%"-+#5(:)(<*`NNS16!.&%%))#(#(-%-%!(!(!(!(!(!(!(!(!(!(!(!(!(!(!(!(%'%'''''''''#(#(")&.,127ٌ&.+2 !)!)#(#(#'#'%&%&'%'%'&'&)&)&)')')')'%'%'!'!'&&%%%%#$#$%$%$3)RHky@N%"+)+%( %%)*$&()**302/:2>6㡘ph94/)"%$&$&(,/.1165=+3 Yk'-%*)6 %%))#(#(-%-%!(!(!(!(!(!(!(!(!(!(!(!(!(!(!(!(%'%'''''''''#(#( ($+-2')'"0.2/)! '%0-3333IHml㼺@@.-54$!#)&'*!(!(!(!(!(!(!(!(!(!(!(!(!(!(!(!(#&#&%)%)'('(%&%&'/&.2>epOR'$%"! $!%"&#&#'#'#+&-'&"&"&%&%))))*,+-+0+0(-',$($(!$!$#")&&#( +$7,<15-}vSSC;JB&( (!)"$!# (*2/85cmКNT..66%)"#!#&'3'3&$% ($[Z֕򄁻][FE1425(1$-'")-,0//)*$!(!(!(!(!(!(!(!(!(!(!(!(!(!(!(!(!&!&%+%+')')%$%$&,+18IwFF5342(&(&'#+&,&("#$!&"%&&(&+&+%+%+(,'+%*%*$&$&&$&$+)/,.'$/&918-&=<@@&%$2,&+(-)/*1,-'),%)#3)2(FEfdޭFG((!$"""%#&%1%1.%*!3.4/</=.1'3**/(-20'(''.-++IJghZ^>@DE><86%'!"!&(-!(!(!(!(!(!(!(!(!(!(!(!(!(!(!(!($$%-%-'*'*'#'#28$fzUZCN7B,<0?6:25,/+.**,,'&&%&&''$(%)#*")('"$%&(*+,..++,++*))-.=<;::9<<]_~⨭rp[Y45'(%%* )$('! "*'5/2-9595QTqtٹL=@1* +!',(- 6!7 $%&& )(11;<45XXĄ[]HI+*)',.46,1"'!(!(!(!(!(!(!(!(!(!(!(!(!(!(!(!($$%-%-'*'*'#'#+1#(܃tVf?N;?59,/,//.11,**(''''$($(")!(('%&%&&(&(''&&''&%@@11<;SRxx풒ts86=;4511" "(-,'&)&/+1,)$2.95ք뻾zzGM9?%+)/ #"&)(5+2")&"#%B<}y?:.**.37 0!!(!(!(!(!(!(!(!(!(!(!(!(!(!(!(!($$#.#.'+'+'"'"%)BFBR?O8E5A0909+1+1)-(,!'!'$$$$%$"!!"$%/1342615KSx¹U@5!14)#*$+).-&%$")((*OOoppuLQ*0%+ "/"(/,, $0+;5-'84NKmi48#'!1/?!(!(!(!(!(!(!(!(!(!(!(!(!(!(!(!($$#.#.'+'+'"'"'+fjmwPY:A3:*.)-!' &##$%+*&% &&01BDJL|h`KC=)D06!2!"&)0..-/.6+5*;.UHQK1,2+HA "-%/53('"*+;8UQ郈W\=8;7:.5)*$("##&&")#*+,!. - - -#0$1A 1'0%&$$611-CE^_VVE?<65+:0C4J;>-?.C;F>X_zԮwlUJ6420$'%(,2 '*&/+4*.$2.95?<>;oormG@6/!/4+(*/"731-FP[eȿpkIE0%0%,%.(((&&!"/ -,'((,"/ -07"6!5"#"#50-(ce̲dZMC<-5&?.YHy}lhWG<@6/-0.!%!% &()*,(($1'4*,'(#HEρ~ouDJ.9/:,0*-#"#"!&#(*7$1*7.:22(',1#bSpFEAA77661379679:(&,*/'2)5&3$/,#(/.," '/#?67.җ林确ml:14+,)41-3,2/-/-#-$.242450/*?F]dѝ=H3=$'"% $#!(5 - ,".,,,,6(<.@1D5\Tʆ~ppKM57 !(&'%&',-..#*#* /#1%<2<2ֽswLK87;28.0-2/%+ '%)/*%"%'&(-()${ﴻxEI475)B63.&!*+$6*<+/%)$&&'0)5/SS||엠튓kp\aFD863+7.>2B7A9=4:+B3P@D4ULxoel<<..'#,("' $##!) .4B-;/=7E_i{X[."'("3.$0'4,>,>+/&*()%-$820)32@?jrȇ~urjeZ^SMDF=7(0!5%+_U˳{JQ5<,+320,.*!&!& '$+'#',-+#2"1@Mjwbd=?+(62,$*#* ,)-. 0&+"'+%)"h_ߒyyiimiea=;20!"%+*0)"! $ 03%'.7GOņɼ[W95/'/'&#&#$)%*+%3/('!&%*3-5/2)90WT}y겶_f88))5353*+)* %!&)!+-)$%"!&',-(+=@t|v:775/'5---&&%0,756.*"'((2$4'MHtoyRY9:77/+/+'"!$+'.(,,!!"",,;<577812`cfc.& $#,,'2"..-&+!& %"/ 2#?1;.:583CAHGܯS^FQ4;/6//774040)$+'%%##"")".""))22,,&'*+VW{{ꃋZb;62,0"5',+)(%%$$#&%)),),"'&*....0//-3.4/B1TDupc\MF3+2*.+.+*,()%!&"*%,#) #"$%(*C.;&9-9-SUwyͶsmKF6(3& %$''""!'+).%)%%##*)0/:5=96&7'=8FBaiƃ诽떜y][NL?8814,5.1.0-)+&( ' ' )%#$*-0,/!#;&6"D9eYv^R5+9/7283/3-0$-#,%0#. * * (%)('&%#*13:GOS[瀃glW\<>-/%%++0.42! # #$"&&.+3/7*2,1.47:6933,,H?lcҤdZMB/*(#*.58-6&/#."-!+$/&/(0*.'+&* /..-.4)0/719.8(5*.#1145:6PMxrϖǻzv]XMG.62:23562)2).0".* !!##% )&"74+*$ &%#$"+*+*=IBIDLS]du}ņֵ둞؃juS^=D548'*(+(+-0*,()!" "!##&#&#%#%#)*')$*#)!+ **)!*'$!')/1QWek謵UW8787>=GF$4$4$2$2$.#,"( '#& $!!""&"&"&& '"($! !!#()()+))'.*62F@QK?GAIENHROYV``kfqxxyz~~sqmkdb`]\VXROIIDM>I:?08)1%4(5,9/ %&%)'$("' '"(%0%0$-$-$+%-)-)-"&#'$)$)%,%,%-%->>34-//1OT{봹KJ(',,+-#,&0&-&-#& $!!" $ $" #%(&)*,)+**--52524/.*,%*$/6/6/8,6,6,6/:3>9D8B8@7?8@=DGKNRXNSIMDG>BCNKVى~_pET7F*4%0!( '!"!!#! +)%%%&''#$%&&&(("%&%&&(!*%,%,#)"("'#(0&0&/(0)0*0*-),(,&+%(#%!%& ( )" "&"))++#!$'!0"1 1-#+#+$+#*$,"*#+")'$,0:>HWavs{emFMAH39/5(.*0*/+0&&()* +. /(*.241,')(+*,,+*)(('(')(&"(%+&,'*$*$*$+%*++++*)(******, -*, .#1 .("((+ ,$0#/!.+*''$&%%$,,..9>;@V^emЃўszY_GM4:.4(-', (#+$/$/". +,..+**-2 6!7*)*))(('%%&&*),,*&,(-(,'*$*$,&.) , , ,++++,. / .-*((*&&&&'()*(()'( ,%2*7(%(%,+11CCUUjotyv|bhPXHO8340/+/+0,516172/*0+.(.(+$+$+&-(/////./..,.,.*.*,'-(,)+(*&*&+'-)*++,*)''#.#.#/#/#/"."/"/!+!+$+%-&,#($'"%,/,/02-/+.),*.+0KLOOZYed֢qlkf`\VSLHC?722,/*.)*%)$'!'!% % ....-,-,,),),(*'3.3.1-1-/,-),(+'+ .!/#1#0#0"/!.**++ , , - -%%& '#(&,-/.0#%)+68?AKMSV\`bf⍑mqOL?SHZOjaofxo܃z珆옏ƽƽ˦uuxLKN336--00/2336336336336336336336336336447447447447447447447447214103?>A--0447214447ihkHHK558103214447447447447447447447447447447447447447447447447447.//778223566;<<::;zz{dcgJJM::=76910333655855855855855855855855844744744744744744744744744744744744744744744744798;87:OOR^^a,,/214558558558558558558558558447447447447447447447447336336336336336336336336+*.214)(+>=@zz}۷_`cJKN358+-0)+.+-02479;>24724724724724724724724744744744744744744744744787:447@?B87:87:ØMLOEDG::=4473362140/2+*.447447447447447447447447447447447447447447447447..198;336769yx{윛zz}QQT::=769::=87:214558558558558558558558558447447447447447447447447769336..1#"%214"!$cbe<<ssv - #CAIWU]ZW_WU]US[US[US[US[US[US[US[US[VVYVVYVVYVVYVVYVVYVVYVVYVVYTSVIILTSVWWZBBE;9AEBJPNVWU]XV^VT\VT\VT\VT\VT\VT\VT\VT\VT\VT\VT\VT\VT\VT\VT\VT\VT\VT\VT\WXXQRRVVW]]^"NPSz|('*{{~BCBzz{!!//7QSS?>Aqtt7::!%ywDFH=?A('*889zz{!!//7#&&447y||/12!% )+.kmpign -%%&<=@~('*iijzz{!!..5add&&)NQQ!%=?Ahem AAB=?A('*rrszz{!!..5"$''cff!%KMOljr - ;<<>@B('*rrszz{!!,-4퍐! #+..z}}!%UWZmks 334<=@('*ooozz{!!,-4%%( wzz!%NPSmks -CDD=?A('*mnnzz{!!,-4~('*+..x{{!%VX[olt <==@BE('*tuuzz{!!+,3򌏏||)(+033z}}!%]_bolt -<==9;>('*rrszz{!!+,3󈋋#"%),,ruu!%UWZolt -<==9;>('*rrszz{!!+,3󈋋#"%*--wzz!%VX[olt -<==9;>('*rrszz{!!+,3󈋋#"%+..vxy!%VX[olt -<==9;>('*rrszz{!!+,3󈋋#"%,//svv!%VX[olt -<==9;>('*rrszz{!!+,3󈋋#"%.01!%XY\olt -<==9;>('*rrszz{!!+,3󈋋#"%/12!%XY\olt -<==9;>wy{('*rrszz{!!+,3󈋋#"%033Z]]!%Y[]olt -<==9;>RTV('*rrszz{!!+,3󈋋#"%144!%Y[]not -EFK86>olt(&,kjo|}} #('1&&)11:FCM"!%VW^not - GHMfglZ[`RSX[\a_]e_]e_]e_]e_]e_]e_]e_]e^\d^\d^\d^\d^\d^\d^\d^\dccdZZZ_``aaaFDK -$"*NLTolt\\f\\f\\f\\f\\f\\f\\f\\f\\f\\f\\f\\f\\f\\f\\f\\f(&,igmyyz #%%/vw|&&)=?D -!%A@Enot -<;Aghm(&,{z} -uvv #%OOY127MPP&&)@?D -GHMfej - +*.!%A@Enot -{z lnq(&,CBH}a^f$ #!!!!!!!!"!!  !!" ' -&&)RSXFDJ "!%98=not -AADwy{(&, -            &'' #!$$&&)LJP[\aCBH!%;;>not - 136(&,VVW #144&&)#!',+0 !%::=lns VVYnpr(&,wwx ##"%!NOTBAF !%EDGlns ! #Z\^(&,99: #JLM#"%@?D  -238jin!%<@B569)+.$&)')+(*,%'*#%(#%(#%(#%(#%(#%(#%(#%(#%(,,/)(+)(+)(+ HHI!%hhplns 89>宰UWZ !qsv(&,ffi #lnq#"%569\]b pqvttw--0;<<!% -NNVlns KLQvxz "$vxz(&,HHK # #"%*,/ -  jkp,+0<==!%\\dlns =>C "$(&,0.4qou #AAKyyddn(68:#"%68:56; -hinXW]@@A!%YY`lns @AF=?A (&,BAF #++5㾽ǁ/.8358#"%136not -ijosrwAAB!%MMUolt -<==++,$#'~})(+ %+/-^^_[]`&&)223 - jkp -889!%XX[olt -<==++,$#'~})(+ %+/-?AD #kmp&&)223 - jkp -889!%XX[olt -<==++,$#'~})(+ %+/- -&&)223 - jkp -889!%XX[olt -<==,,-$#'~})(+ %+/-xz}&&)223 - jkp889!%XX[olt -<==,,-$#'~})(+ %+/-oqs&&)223 - jkp889!%XX[olt -<==,,-$#'~})(+ %+/-9;>&&)223 - jkp889!%XX[olt -<==-..$#'~})(+ %+/-TVY #&&)223 - jkp889!%XX[olt -<==-..$#'~})(+ %+/- -&&)223 - jkp889!%XX[kiq 778))*")(+ %+/-<=@{{~&&)778 lns566!%XX[ljrEEF889 )(+ %+/-!{{~)(+""# jkp334!%XX[pnu EEF++,")(+ %+/-024zz}('*??@ -klq 778!%XX[qow -778((("!$)(+ %+/-dfizz}! #pqv pqv|| <==!%XX[jhp -99:223 )(+ %+/- `af -rsxxwz ;<<!%XX[olt )(+ %+/-TVY#"% qrwBBE;<<!%XX[eefrrs('*)(+ %+/- 447WXX~ -rsx%%(MMN!%XX[(%-778ssthhh-..::=)(+ %+/-CCF[[\ћill255vw|{||FFGbbc!%XX[" ',*2*(0*(0*(0*(0*(0*(0*(0*(0*(0*(0*(0*(0*(0*(0*(0*(0(((&''**+%%&))***+por%%(+*.*),+*.&&)0/2('**),*),*),*),*),*),*),*),((0((0((0((0((0((0((0((0%&-))1**2""* 216&$,~,,-QQT FDJ - -""*((0%&-$%,))1))1))1))1))1))1))1))1*(0*(0*(0*(0*(0*(0*(0*(0)'.(%-,*2*(0)(+)(+)(+)(+)(+)(+)(+)(+*(0*(0*(0*(0*(0*(0*(0*(0+)1+)1+)1+)1+)1+)1+)1+)1//7$%, -**+**++*.+*.+*.*),)'-)'-*(0*(0*(0*(0*(0*(0*(0*(0*(0*(0*(0*(0*(0*(0*(0*(0)'.*(0&$, - ZZZIGOzz{103ZX^(%-~,,-{{~!cbe{zolt 77?  - - ZZZ::= -)'-,,-558*),pnt dejBAF ZZZ#447IGO)'-,,-IILljr mlq ZZZ <=@  -xv|('*,,-!455  ihkHJM ZZZJJM - 336hem&&),,-214caissvDFH214ffi ZZZSRU$#'rrs$#' a_essv,,-SRU𯭳_^c(&,dcg!UWZ JJM!!!##$ uvvƿ­ŹŻǿĹʷ¿ݳ޽ſ뱰Žÿÿû载ɹ diff --git a/examples/peripherals/jpeg/jpeg_encode/sdkconfig.defaults b/examples/peripherals/jpeg/jpeg_encode/sdkconfig.defaults new file mode 100644 index 00000000000..87d024ce27f --- /dev/null +++ b/examples/peripherals/jpeg/jpeg_encode/sdkconfig.defaults @@ -0,0 +1,6 @@ +CONFIG_SPIRAM=y +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv" +CONFIG_PARTITION_TABLE_FILENAME="partitions.csv" +CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y +CONFIG_ESPTOOLPY_FLASHSIZE="4MB" diff --git a/examples/peripherals/lcd/i80_controller/main/i80_controller_example_main.c b/examples/peripherals/lcd/i80_controller/main/i80_controller_example_main.c index 6418a947387..693d8d6e7be 100644 --- a/examples/peripherals/lcd/i80_controller/main/i80_controller_example_main.c +++ b/examples/peripherals/lcd/i80_controller/main/i80_controller_example_main.c @@ -360,7 +360,7 @@ void app_main(void) ESP_LOGI(TAG, "Create LVGL task"); xTaskCreate(example_lvgl_port_task, "LVGL", EXAMPLE_LVGL_TASK_STACK_SIZE, NULL, EXAMPLE_LVGL_TASK_PRIORITY, NULL); - + vTaskDelay(pdMS_TO_TICKS(10)); ESP_LOGI(TAG, "Display LVGL animation"); // Lock the mutex due to the LVGL APIs are not thread-safe _lock_acquire(&lvgl_api_lock); diff --git a/examples/peripherals/lcd/i80_controller/sdkconfig.ci.image_in_fs b/examples/peripherals/lcd/i80_controller/sdkconfig.ci.image_in_fs index 7c5b547dc4f..ffa8e138f32 100644 --- a/examples/peripherals/lcd/i80_controller/sdkconfig.ci.image_in_fs +++ b/examples/peripherals/lcd/i80_controller/sdkconfig.ci.image_in_fs @@ -8,4 +8,3 @@ CONFIG_LV_USE_FS_POSIX=y # use 'S' (83 in ASCII) as drive letter for POSIX FS CONFIG_LV_FS_POSIX_LETTER=83 -CONFIG_LV_FS_POSIX_CACHE_SIZE=65535 diff --git a/examples/peripherals/lcd/mipi_dsi/main/idf_component.yml b/examples/peripherals/lcd/mipi_dsi/main/idf_component.yml index 047ec83cd94..5e11de7c083 100644 --- a/examples/peripherals/lcd/mipi_dsi/main/idf_component.yml +++ b/examples/peripherals/lcd/mipi_dsi/main/idf_component.yml @@ -1,4 +1,4 @@ dependencies: - lvgl/lvgl: "9.4.0" + lvgl/lvgl: "9.5.0" esp_lcd_ili9881c: "^1.0.0" esp_lcd_ek79007: "^1.0.0" diff --git a/examples/peripherals/lcd/mipi_dsi/main/mipi_dsi_lcd_example_main.c b/examples/peripherals/lcd/mipi_dsi/main/mipi_dsi_lcd_example_main.c index 5f236ca1c57..38918f0351d 100644 --- a/examples/peripherals/lcd/mipi_dsi/main/mipi_dsi_lcd_example_main.c +++ b/examples/peripherals/lcd/mipi_dsi/main/mipi_dsi_lcd_example_main.c @@ -334,7 +334,7 @@ void app_main(void) esp_lcd_dpi_panel_event_callbacks_t cbs = { .on_color_trans_done = example_notify_lvgl_flush_ready, #if CONFIG_EXAMPLE_MONITOR_REFRESH_BY_GPIO - .on_refresh_done = example_monitor_refresh_rate, + .on_vsync = example_monitor_refresh_rate, #endif }; ESP_ERROR_CHECK(esp_lcd_dpi_panel_register_event_callbacks(mipi_dpi_panel, &cbs, display)); diff --git a/examples/peripherals/lcd/rgb_panel/main/CMakeLists.txt b/examples/peripherals/lcd/rgb_panel/main/CMakeLists.txt index 0b88bc41fb8..2c95073c568 100644 --- a/examples/peripherals/lcd/rgb_panel/main/CMakeLists.txt +++ b/examples/peripherals/lcd/rgb_panel/main/CMakeLists.txt @@ -1,3 +1,3 @@ idf_component_register(SRCS "rgb_lcd_example_main.c" "lvgl_demo_ui.c" - PRIV_REQUIRES esp_lcd + PRIV_REQUIRES esp_lcd esp_timer INCLUDE_DIRS ".") diff --git a/examples/peripherals/lcd/rgb_panel/main/idf_component.yml b/examples/peripherals/lcd/rgb_panel/main/idf_component.yml index 4ca86dce7ec..0a082a3aeca 100644 --- a/examples/peripherals/lcd/rgb_panel/main/idf_component.yml +++ b/examples/peripherals/lcd/rgb_panel/main/idf_component.yml @@ -1,2 +1,2 @@ dependencies: - lvgl/lvgl: "9.2.0" + lvgl/lvgl: "9.5.0" diff --git a/examples/peripherals/lcd/rgb_panel/main/rgb_lcd_example_main.c b/examples/peripherals/lcd/rgb_panel/main/rgb_lcd_example_main.c index aedf4c3365f..4d31f9f5edb 100644 --- a/examples/peripherals/lcd/rgb_panel/main/rgb_lcd_example_main.c +++ b/examples/peripherals/lcd/rgb_panel/main/rgb_lcd_example_main.c @@ -101,15 +101,43 @@ static const char *TAG = "example"; // LVGL library is not thread-safe, this example will call LVGL APIs from different tasks, so use a mutex to protect it static _lock_t lvgl_api_lock; +static TaskHandle_t lvgl_task_handle; extern void example_lvgl_demo_ui(lv_display_t *disp); +#if CONFIG_EXAMPLE_USE_DOUBLE_FB +static bool example_on_frame_buf_complete(esp_lcd_panel_handle_t panel, const esp_lcd_rgb_panel_event_data_t *event_data, void *user_ctx) +{ + (void)panel; + (void)event_data; + (void)user_ctx; + BaseType_t need_yield = pdFALSE; + + if (lvgl_task_handle) { + vTaskNotifyGiveFromISR(lvgl_task_handle, &need_yield); + } + return need_yield == pdTRUE; +} + +static void example_lvgl_flush_wait_cb(lv_display_t *disp) +{ + // The flush callback only submits the rendered buffer to the LCD driver. + // With direct-mode double buffering, LVGL must wait until the RGB panel has + // switched away from the previous frame buffer before rendering into it again. + if (lv_display_flush_is_last(disp)) { + // Wait until the previous frame buffer is no longer referenced by DMA. + ulTaskNotifyTake(pdTRUE, portMAX_DELAY); + } + lv_display_flush_ready(disp); +} +#else static bool example_notify_lvgl_flush_ready(esp_lcd_panel_handle_t panel, const esp_lcd_rgb_panel_event_data_t *event_data, void *user_ctx) { lv_display_t *disp = (lv_display_t *)user_ctx; lv_display_flush_ready(disp); return false; } +#endif static void example_lvgl_flush_cb(lv_display_t *disp, const lv_area_t *area, uint8_t *px_map) { @@ -118,6 +146,20 @@ static void example_lvgl_flush_cb(lv_display_t *disp, const lv_area_t *area, uin int offsetx2 = area->x2; int offsety1 = area->y1; int offsety2 = area->y2; +#if CONFIG_EXAMPLE_USE_DOUBLE_FB + if (!lv_display_flush_is_last(disp)) { + lv_display_flush_ready(disp); + return; + } + // In direct mode, LVGL may flush multiple dirty areas. Switch the RGB panel to the new + // frame buffer only after the last dirty area has been rendered. + offsetx1 = 0; + offsety1 = 0; + offsetx2 = EXAMPLE_LCD_H_RES - 1; + offsety2 = EXAMPLE_LCD_V_RES - 1; + // Clear any stale completion from the previous frame before waiting for this frame. + ulTaskNotifyTake(pdTRUE, 0); +#endif // pass the draw buffer to the driver esp_lcd_panel_draw_bitmap(panel_handle, offsetx1, offsety1, offsetx2 + 1, offsety2 + 1, px_map); } @@ -264,10 +306,19 @@ void app_main(void) // set the callback which can copy the rendered image to an area of the display lv_display_set_flush_cb(display, example_lvgl_flush_cb); +#if CONFIG_EXAMPLE_USE_DOUBLE_FB + // The wait callback keeps LVGL from reusing a frame buffer until the panel driver + // reports that the buffer has finished refreshing. + lv_display_set_flush_wait_cb(display, example_lvgl_flush_wait_cb); +#endif ESP_LOGI(TAG, "Register event callbacks"); esp_lcd_rgb_panel_event_callbacks_t cbs = { +#if CONFIG_EXAMPLE_USE_DOUBLE_FB + .on_frame_buf_complete = example_on_frame_buf_complete, +#else .on_color_trans_done = example_notify_lvgl_flush_ready, +#endif }; ESP_ERROR_CHECK(esp_lcd_rgb_panel_register_event_callbacks(panel_handle, &cbs, display)); @@ -282,7 +333,7 @@ void app_main(void) ESP_ERROR_CHECK(esp_timer_start_periodic(lvgl_tick_timer, EXAMPLE_LVGL_TICK_PERIOD_MS * 1000)); ESP_LOGI(TAG, "Create LVGL task"); - xTaskCreate(example_lvgl_port_task, "LVGL", EXAMPLE_LVGL_TASK_STACK_SIZE, NULL, EXAMPLE_LVGL_TASK_PRIORITY, NULL); + xTaskCreate(example_lvgl_port_task, "LVGL", EXAMPLE_LVGL_TASK_STACK_SIZE, NULL, EXAMPLE_LVGL_TASK_PRIORITY, &lvgl_task_handle); ESP_LOGI(TAG, "Display LVGL UI"); // Lock the mutex due to the LVGL APIs are not thread-safe diff --git a/examples/peripherals/twai/twai_network/README.md b/examples/peripherals/twai/twai_network/README.md index 13cb1531696..ecaf8564587 100644 --- a/examples/peripherals/twai/twai_network/README.md +++ b/examples/peripherals/twai/twai_network/README.md @@ -17,6 +17,7 @@ This example demonstrates TWAI (Two-Wire Automotive Interface) network communica - Event-driven message handling with callbacks - Message filtering using acceptance filters in listen-only mode - Single/Burst data transmission and reception +- Local transmit queue prioritization for urgent frames - Real-time bus error and node status reporting ## Hardware Setup @@ -55,6 +56,7 @@ Navigate to: `Example Configuration` → Configure the following: |----|------|-----------|------|-------------| | 0x7FF | Heartbeat | 1 Hz | 8 bytes | Timestamp data | | 0x100 | Data | Every 10s | 1000 bytes | Test data (125 frames) | +| 0x080 | Emergency | During data burst | 0 bytes | High-priority frame inserted into the transmit queue | ## Building and Running @@ -72,9 +74,10 @@ idf.py set-target esp32 build flash monitor ``` ===================TWAI Sender Example Starting...=================== I (xxx) twai_sender: TWAI Sender started successfully -I (xxx) twai_sender: Sending messages on IDs: 0x100 (data), 0x7FF (heartbeat) +I (xxx) twai_sender: Sending messages with IDs: 0x100 (data), 0x7FF (heartbeat) I (xxx) twai_sender: Sending heartbeat message: 1234567890 I (xxx) twai_sender: Sending packet of 1000 bytes in 125 frames +I (xxx) twai_sender: Inserting Emergency message: 0x080 ``` ### Listen-Only Monitor @@ -102,6 +105,21 @@ Each program uses a buffer pool to handle incoming messages efficiently: - **Normal Mode** (Sender): Participates in bus communication, sends ACK frames - **Listen-Only Mode** (Monitor): Receives filtered messages without transmitting anything +### Transmit Queue Priority + +The sender inserts an emergency frame while a burst of data frames is pending in the transmit queue: + +```c +twai_frame_t emergency_frame = { + .header.id = TWAI_EMERGENCY_ID, + .tx_queue_priority = 10, +}; +``` + +The `tx_queue_priority` field controls the driver's local transmit queue. Frames with a higher priority value are dequeued before lower-priority frames, while frames with the same priority keep their enqueue order. This allows urgent frames, such as the emergency frame in this example, to be transmitted before queued burst data frames. + +The local queue priority is separate from TWAI bus arbitration. Once a frame is sent to the bus, arbitration is still determined by the frame ID, where lower IDs have higher bus priority. + ### Message Filtering The listen-only monitor uses hardware acceptance filters to receive only specific message IDs: @@ -133,6 +151,7 @@ Update the message ID definitions: ```c #define TWAI_DATA_ID 0x100 #define TWAI_HEARTBEAT_ID 0x7FF +#define TWAI_EMERGENCY_ID 0x080 ``` ## Use Cases diff --git a/examples/peripherals/twai/twai_network/pytest_twai_network.py b/examples/peripherals/twai/twai_network/pytest_twai_network.py index af3cb1f7c84..eee23823af0 100644 --- a/examples/peripherals/twai/twai_network/pytest_twai_network.py +++ b/examples/peripherals/twai/twai_network/pytest_twai_network.py @@ -1,30 +1,43 @@ # SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD # SPDX-License-Identifier: CC0-1.0 +import os import os.path import subprocess +import time import pytest from can import Bus from can import Message +from pytest_embedded import Dut from pytest_embedded_idf import IdfDut +can_env = os.getenv('CAN_PORT', 'can0') +print(f'CAN_PORT={can_env}') + # Socket CAN fixture @pytest.fixture(name='socket_can') def fixture_create_socket_can() -> Bus: - start_command = 'sudo ip link set can0 up type can bitrate 1000000' - stop_command = 'sudo ip link set can0 down' - try: - subprocess.run(start_command, shell=True, capture_output=True, text=True) - except Exception as e: - print(f'Open bus Error: {e}') - bus = Bus(interface='socketcan', channel='can0', bitrate=1000000) + start_command = f'sudo ip link set {can_env} up type can bitrate 1000000' + stop_command = f'sudo ip link set {can_env} down' + subprocess.run(start_command, shell=True, capture_output=True, text=True) + bus = Bus(interface='socketcan', channel=f'{can_env}', bitrate=1000000) yield bus # test invoked here bus.shutdown() subprocess.run(stop_command, shell=True, capture_output=True, text=True) -@pytest.mark.twai_std +def esp_enter_flash_mode(dut: Dut) -> None: + ser = dut.serial.proc + ser.setRTS(True) # EN Low + time.sleep(0.5) + ser.setDTR(True) # GPIO0 Low + ser.setRTS(False) # EN High + dut.expect('waiting for download', timeout=2) + ser.setDTR(False) # Back RTS/DTR to 1/1 to avoid affect to esptool + + +@pytest.mark.twai_adapter @pytest.mark.parametrize('count', [2], indirect=True) @pytest.mark.timeout(120) @pytest.mark.parametrize( @@ -55,45 +68,50 @@ def test_twai_network_multi(dut: tuple[IdfDut, IdfDut], socket_can: Bus) -> None - dut[1]: twai_sender """ - # Print chip information for debugging - print(f'===> Pytest testing with chips: {dut[0].app.target} (listener), {dut[1].app.target} (sender)') + try: + # Print chip information for debugging + print(f'===> Pytest testing with chips: {dut[0].app.target} (listener), {dut[1].app.target} (sender)') - # Initialize listener node first - dut[0].expect('===================TWAI Listen Only Example Starting...===================') - dut[0].expect('TWAI start listening...') + # Initialize listener node first + dut[0].expect('===================TWAI Listen Only Example Starting...===================') + dut[0].expect('TWAI start listening...') - # Initialize sender node and start communication - dut[1].expect('===================TWAI Sender Example Starting...===================') - dut[1].expect('TWAI Sender started successfully') + # Initialize sender node and start communication + dut[1].expect('===================TWAI Sender Example Starting...===================') + dut[1].expect('TWAI Sender started successfully') - # Verify communication is working - # Wait for sender to send messages - dut[1].expect('Sending heartbeat message:', timeout=10) + # Verify communication is working + # Wait for sender to send messages + dut[1].expect('Sending heartbeat message:', timeout=10) - # Check that listener is receiving data - dut[0].expect('RX:', timeout=15) # Listener should see filtered messages + # Check that listener is receiving data + dut[0].expect('RX:', timeout=15) # Listener should see filtered messages - # Check if socket receive any messages - socket_rcv_cnt = 0 - for i in range(100): - msg = socket_can.recv(timeout=1) - if msg is not None: - socket_rcv_cnt += 1 - print(f'Socket receive {socket_rcv_cnt} messages') - assert socket_rcv_cnt > 50, 'Socket NO messages' + # Check if socket receive any messages + socket_rcv_cnt = 0 + for i in range(100): + msg = socket_can.recv(timeout=1) + if msg is not None: + socket_rcv_cnt += 1 + print(f'Socket receive {socket_rcv_cnt} messages') + assert socket_rcv_cnt > 50, 'Socket NO messages' - # Wait a bit more to ensure stable communication - dut[1].expect('Sending packet of', timeout=10) - dut[0].expect('RX:', timeout=10) + # Wait a bit more to ensure stable communication + dut[1].expect('Sending packet of', timeout=10) + dut[0].expect('RX:', timeout=10) - # Check if esp32 receive messages from usb can - message = Message( - arbitration_id=0x10A, - is_extended_id=False, - data=b'Hi ESP32', - ) - print('USB CAN Send:', message) - socket_can.send(message, timeout=0.2) - dut[0].expect_exact('10a [8] 48 69 20 45 53 50 33 32', timeout=10) # ASCII: Hi ESP32 + # Check if esp32 receive messages from usb can + message = Message( + arbitration_id=0x10A, + is_extended_id=False, + data=b'Hi ESP32', + ) + print('USB CAN Send:', message) + socket_can.send(message, timeout=0.2) + dut[0].expect_exact('10a [8] 48 69 20 45 53 50 33 32', timeout=10) # ASCII: Hi ESP32 - print('===> TWAI network communication test completed successfully') + print('===> TWAI network communication test completed successfully') + + finally: + esp_enter_flash_mode(dut[0]) + esp_enter_flash_mode(dut[1]) diff --git a/examples/peripherals/twai/twai_network/twai_sender/main/twai_sender.c b/examples/peripherals/twai/twai_network/twai_sender/main/twai_sender.c index a8255752bc6..f397d1d7ad0 100644 --- a/examples/peripherals/twai/twai_network/twai_sender/main/twai_sender.c +++ b/examples/peripherals/twai/twai_network/twai_sender/main/twai_sender.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -22,6 +22,7 @@ // Message IDs #define TWAI_DATA_ID 0x100 #define TWAI_HEARTBEAT_ID 0x7FF +#define TWAI_EMERGENCY_ID 0x080 #define TWAI_DATA_LEN 1000 static const char *TAG = "twai_sender"; @@ -108,6 +109,15 @@ void app_main(void) ESP_ERROR_CHECK(twai_node_transmit(sender_node, &data[i].frame, 500)); } + // Insert an emergency frame with high priority + // This frame will be transmitted before the queue remaining data frames + twai_frame_t emergency_frame = { + .header.id = TWAI_EMERGENCY_ID, + .tx_queue_priority = 10, + }; + ESP_LOGI(TAG, "Inserting Emergency message: 0x%03X", TWAI_EMERGENCY_ID); + ESP_ERROR_CHECK(twai_node_transmit(sender_node, &emergency_frame, 500)); + // Frames mounted, wait for all frames to be transmitted ESP_ERROR_CHECK(twai_node_transmit_wait_all_done(sender_node, -1)); free(data); diff --git a/examples/peripherals/twai/twai_utils/pytest_twai_utils.py b/examples/peripherals/twai/twai_utils/pytest_twai_utils.py index 8b5288062a9..e2557936035 100644 --- a/examples/peripherals/twai/twai_utils/pytest_twai_utils.py +++ b/examples/peripherals/twai/twai_utils/pytest_twai_utils.py @@ -1,6 +1,6 @@ # SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD # SPDX-License-Identifier: Apache-2.0 - +import os import subprocess import time from collections.abc import Generator @@ -18,6 +18,9 @@ from pytest_embedded_idf.utils import soc_filtered_targets # Constants / Helpers # --------------------------------------------------------------------------- +can_env = os.getenv('CAN_PORT', 'can0') +print(f'CAN_PORT={can_env}') + PROMPTS = ['twai>'] # Hardware configuration @@ -253,7 +256,7 @@ class TwaiTestHelper: class CanBusManager: """CAN bus manager for external hardware tests""" - def __init__(self, interface: str = 'can0'): + def __init__(self, interface: str = can_env): self.interface = interface self.bus: can.Bus | None = None @@ -280,8 +283,6 @@ class CanBusManager: self.bus = can.Bus(interface='socketcan', channel=self.interface) yield self.bus - except Exception as e: - pytest.skip(f'CAN interface not available: {str(e)}') finally: if self.bus: self.bus.shutdown() @@ -593,8 +594,9 @@ def test_twai_utils_range_filters(twai: TwaiTestHelper) -> None: # --------------------------------------------------------------------------- -@pytest.mark.twai_std -@pytest.mark.temp_skip_ci(targets=['esp32h4'], reason='no runner') +@pytest.mark.twai_adapter +@pytest.mark.temp_skip_ci(targets=['esp32h4'], reason='no runner') # TODO: IDFCI-11110 +@pytest.mark.temp_skip_ci(targets=['esp32s31'], reason='no runner') @idf_parametrize('target', soc_filtered_targets('SOC_TWAI_SUPPORTED == 1'), indirect=['target']) def test_twai_utils_external_communication(twai: TwaiTestHelper, usb_can: CanBusManager) -> None: test_frames = [ @@ -611,34 +613,34 @@ def test_twai_utils_external_communication(twai: TwaiTestHelper, usb_can: CanBus bitrate=DEFAULT_BITRATE, start_dump=False, ): - # --- ESP -> PC Test --- - for frame_str, frame_id, expected_data, is_extended in test_frames: - assert twai.send(frame_str), f'ESP->PC send failed: {frame_str}' - deadline = time.time() + 2.0 - got = None - while time.time() < deadline: - try: - msg = can_bus.recv(timeout=0.1) - if msg and msg.arbitration_id == frame_id: - got = msg - break - except Exception: - continue - assert got is not None, f'ESP->PC receive timeout for ID=0x{frame_id:X}' - assert bool(got.is_extended_id) == is_extended, ( - f'ESP->PC extended flag mismatch for 0x{frame_id:X}: ' - f'expected {is_extended}, got {got.is_extended_id}' - ) - assert bytes(got.data) == expected_data, ( - f'ESP->PC data mismatch for 0x{frame_id:X}: ' - f'expected {expected_data.hex()}, got {bytes(got.data).hex()}' - ) - - # --- PC -> ESP --- - assert twai.dump_start(), 'Failed to start twai_dump' - assert twai.info(), 'Failed to get twai_info' - try: + # --- ESP -> PC Test --- + for frame_str, frame_id, expected_data, is_extended in test_frames: + assert twai.send(frame_str), f'ESP->PC send failed: {frame_str}' + deadline = time.time() + 2.0 + got = None + while time.time() < deadline: + try: + msg = can_bus.recv(timeout=0.1) + if msg and msg.arbitration_id == frame_id: + got = msg + break + except Exception: + continue + assert got is not None, f'ESP->PC receive timeout for ID=0x{frame_id:X}' + assert bool(got.is_extended_id) == is_extended, ( + f'ESP->PC extended flag mismatch for 0x{frame_id:X}: ' + f'expected {is_extended}, got {got.is_extended_id}' + ) + assert bytes(got.data) == expected_data, ( + f'ESP->PC data mismatch for 0x{frame_id:X}: ' + f'expected {expected_data.hex()}, got {bytes(got.data).hex()}' + ) + + # --- PC -> ESP --- + assert twai.dump_start(), 'Failed to start twai_dump' + assert twai.info(), 'Failed to get twai_info' + for frame_str, frame_id, expected_data, is_extended in test_frames: msg = can.Message(arbitration_id=frame_id, data=expected_data, is_extended_id=is_extended) print(f'\nPC->ESP sending frame: {msg}, Return: {can_bus.send(msg)}') diff --git a/examples/peripherals/twai/twai_utils/sdkconfig.defaults.esp32h4 b/examples/peripherals/twai/twai_utils/sdkconfig.defaults.esp32h4 new file mode 100644 index 00000000000..daff3b9a4d6 --- /dev/null +++ b/examples/peripherals/twai/twai_utils/sdkconfig.defaults.esp32h4 @@ -0,0 +1 @@ +CONFIG_EXAMPLE_ENABLE_TWAI_FD=y diff --git a/examples/peripherals/uart/uart_dma_ota/main/CMakeLists.txt b/examples/peripherals/uart/uart_dma_ota/main/CMakeLists.txt index bbc4476020d..e789ac28455 100644 --- a/examples/peripherals/uart/uart_dma_ota/main/CMakeLists.txt +++ b/examples/peripherals/uart/uart_dma_ota/main/CMakeLists.txt @@ -1,3 +1,3 @@ idf_component_register(SRCS "uart_dma_ota_example_main.c" - REQUIRES esp_driver_uart app_update + REQUIRES esp_driver_uart app_update esp_ringbuf INCLUDE_DIRS ".") diff --git a/examples/peripherals/uart/uart_dma_ota/main/uart_dma_ota_example_main.c b/examples/peripherals/uart/uart_dma_ota/main/uart_dma_ota_example_main.c index cf99e5c50cd..7c2f70594be 100644 --- a/examples/peripherals/uart/uart_dma_ota/main/uart_dma_ota_example_main.c +++ b/examples/peripherals/uart/uart_dma_ota/main/uart_dma_ota_example_main.c @@ -1,20 +1,22 @@ /* - * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include +#include #include #include "freertos/FreeRTOS.h" #include "freertos/task.h" -#include "freertos/queue.h" +#include "freertos/ringbuf.h" #include "driver/uart.h" #include "driver/uhci.h" #include "esp_log.h" #include "esp_ota_ops.h" #include "esp_err.h" #include "esp_check.h" +#include "esp_heap_caps.h" static const char *TAG = "uhci-example"; @@ -22,48 +24,36 @@ static const char *TAG = "uhci-example"; #define EXAMPLE_UART_BAUD_RATE CONFIG_UART_BAUD_RATE #define EXAMPLE_UART_RX_IO CONFIG_UART_RX_IO #define UART_DMA_OTA_BUFFER_SIZE (10 * 1024) - -typedef enum { - UHCI_EVT_PARTIAL_DATA, - UHCI_EVT_EOF, -} uhci_event_t; +#define UART_DMA_OTA_RINGBUF_SIZE (10 * 1024) typedef struct { - QueueHandle_t uhci_queue; - size_t receive_size; - uint8_t *ota_data1; - uint8_t *ota_data2; - bool use_ota_data1; -} ota_example_context_t; + RingbufHandle_t ringbuf; + volatile bool rx_eof; + volatile bool rx_overflow; +} ota_rx_context_t; static bool s_uhci_rx_event_cbs(uhci_controller_handle_t uhci_ctrl, const uhci_rx_event_data_t *edata, void *user_ctx) { - ota_example_context_t *ctx = (ota_example_context_t *)user_ctx; - BaseType_t xTaskWoken = 0; - uhci_event_t evt = 0; + ota_rx_context_t *ctx = (ota_rx_context_t *)user_ctx; + BaseType_t xTaskWoken = pdFALSE; + if (xRingbufferSendFromISR(ctx->ringbuf, edata->data, edata->recv_size, &xTaskWoken) != pdTRUE) { + ctx->rx_overflow = true; + } if (edata->flags.totally_received) { - evt = UHCI_EVT_EOF; - } else { - evt = UHCI_EVT_PARTIAL_DATA; + ctx->rx_eof = true; } - - // Choose the buffer to store received data - ctx->receive_size = edata->recv_size; - if (ctx->use_ota_data1) { - ctx->ota_data1 = edata->data; - } else { - ctx->ota_data2 = edata->data; - } - - // Toggle the buffer for the next receive - ctx->use_ota_data1 = !ctx->use_ota_data1; - - xQueueSendFromISR(ctx->uhci_queue, &evt, &xTaskWoken); - return xTaskWoken; + return xTaskWoken == pdTRUE; } -static void perform_ota_update(uhci_controller_handle_t uhci_ctrl, ota_example_context_t *ctx) +static bool rx_ringbuf_is_empty(RingbufHandle_t ringbuf) +{ + UBaseType_t items_waiting = 0; + vRingbufferGetInfo(ringbuf, NULL, NULL, NULL, NULL, &items_waiting); + return items_waiting == 0; +} + +static void perform_ota_update(uhci_controller_handle_t uhci_ctrl, ota_rx_context_t *ctx) { const esp_partition_t *ota_partition = esp_ota_get_next_update_partition(NULL); if (!ota_partition) { @@ -75,25 +65,32 @@ static void perform_ota_update(uhci_controller_handle_t uhci_ctrl, ota_example_c ESP_ERROR_CHECK(esp_ota_begin(ota_partition, OTA_SIZE_UNKNOWN, &ota_handle)); ESP_LOGI(TAG, "OTA process started"); - uhci_event_t evt; - uint32_t received_size = 0; uint8_t *pdata = heap_caps_calloc(1, UART_DMA_OTA_BUFFER_SIZE, MALLOC_CAP_DEFAULT); assert(pdata); ESP_ERROR_CHECK(uhci_receive(uhci_ctrl, pdata, UART_DMA_OTA_BUFFER_SIZE)); - while (1) { - if (xQueueReceive(ctx->uhci_queue, &evt, portMAX_DELAY) == pdTRUE) { - uint8_t *data_to_write = ctx->use_ota_data1 ? ctx->ota_data2 : ctx->ota_data1; - ESP_ERROR_CHECK(esp_ota_write(ota_handle, data_to_write, ctx->receive_size)); - received_size += ctx->receive_size; - if (evt == UHCI_EVT_EOF) { - break; - } + size_t total_received_size = 0; + while (1) { + size_t item_size = 0; + uint8_t *data = xRingbufferReceive(ctx->ringbuf, &item_size, pdMS_TO_TICKS(1000)); + if (data) { + ESP_ERROR_CHECK(esp_ota_write(ota_handle, data, item_size)); + vRingbufferReturnItem(ctx->ringbuf, data); + total_received_size += item_size; + } + + if (ctx->rx_overflow) { + ESP_LOGE(TAG, "RX ring buffer overflow, please reduce the baud rate or increase the ring buffer size"); + abort(); + } + + if (ctx->rx_eof && rx_ringbuf_is_empty(ctx->ringbuf)) { + break; } } free(pdata); - ESP_LOGI(TAG, "Total received size: %ld", received_size); + ESP_LOGI(TAG, "Total received size: %zu", total_received_size); ESP_ERROR_CHECK(esp_ota_end(ota_handle)); ESP_ERROR_CHECK(esp_ota_set_boot_partition(ota_partition)); } @@ -126,25 +123,24 @@ void app_main(void) ESP_LOGI(TAG, "UHCI initialized, baud rate is %d, rx pin is %d", uart_config.baud_rate, EXAMPLE_UART_RX_IO); - ota_example_context_t *ctx = calloc(1, sizeof(ota_example_context_t)); - assert(ctx); - - ctx->uhci_queue = xQueueCreate(2, sizeof(uhci_event_t)); - assert(ctx->uhci_queue); - - ctx->use_ota_data1 = true; // Start with ota_data1 + ota_rx_context_t ctx = { + .ringbuf = xRingbufferCreate(UART_DMA_OTA_RINGBUF_SIZE, RINGBUF_TYPE_BYTEBUF), + .rx_eof = false, + .rx_overflow = false, + }; + assert(ctx.ringbuf); uhci_event_callbacks_t uhci_cbs = { .on_rx_trans_event = s_uhci_rx_event_cbs, }; - ESP_ERROR_CHECK(uhci_register_event_callbacks(uhci_ctrl, &uhci_cbs, ctx)); + ESP_ERROR_CHECK(uhci_register_event_callbacks(uhci_ctrl, &uhci_cbs, &ctx)); - perform_ota_update(uhci_ctrl, ctx); + perform_ota_update(uhci_ctrl, &ctx); ESP_ERROR_CHECK(uhci_del_controller(uhci_ctrl)); - free(ctx); + vRingbufferDelete(ctx.ringbuf); ESP_LOGI(TAG, "OTA update successful. Rebooting..."); esp_restart(); } diff --git a/examples/peripherals/uart/uart_dma_ota/pytest_uart_dma_ota.py b/examples/peripherals/uart/uart_dma_ota/pytest_uart_dma_ota.py index bfd4e0ec782..7b73ef811c7 100644 --- a/examples/peripherals/uart/uart_dma_ota/pytest_uart_dma_ota.py +++ b/examples/peripherals/uart/uart_dma_ota/pytest_uart_dma_ota.py @@ -48,9 +48,11 @@ def test_uart_dma_ota(dut: Dut) -> None: # We OTA the same binary to another partition and switch to there. binary_path = os.path.join(dut.app.binary_path, 'uart_dma_ota.bin') assert os.path.exists(binary_path), f'OTA binary not found at {binary_path}' + binary_size = os.path.getsize(binary_path) buad_rate = dut.app.sdkconfig.get('UART_BAUD_RATE') send_file_via_uart(FLASH_PORT, buad_rate, binary_path, PACKET_SIZE) + dut.expect_exact(f'uhci-example: Total received size: {binary_size}') dut.expect('OTA update successful. Rebooting', timeout=10) dut.expect('ESP-ROM:', timeout=10) diff --git a/examples/peripherals/usb/device/tusb_cdc_acm_wakeup/CMakeLists.txt b/examples/peripherals/usb/device/tusb_cdc_acm_wakeup/CMakeLists.txt new file mode 100644 index 00000000000..a310f9ffd0c --- /dev/null +++ b/examples/peripherals/usb/device/tusb_cdc_acm_wakeup/CMakeLists.txt @@ -0,0 +1,8 @@ +# The following five lines of boilerplate have to be in your project's +# CMakeLists in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.22) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +# "Trim" the build. Include the minimal set of components, main, and anything it depends on. +idf_build_set_property(MINIMAL_BUILD ON) +project(tusb_cdc_acm_wakeup) diff --git a/examples/peripherals/usb/device/tusb_cdc_acm_wakeup/README.md b/examples/peripherals/usb/device/tusb_cdc_acm_wakeup/README.md new file mode 100644 index 00000000000..fb779ed750d --- /dev/null +++ b/examples/peripherals/usb/device/tusb_cdc_acm_wakeup/README.md @@ -0,0 +1,86 @@ +| Supported Targets | ESP32-P4 | +| ----------------- | -------- | + +# TinyUSB CDC ACM Wakeup Example + +(See the README.md file in the upper level 'examples' directory for more information about examples.) + +This example shows how to set up an ESP chip as a USB CDC ACM device that enters light sleep when the USB bus is suspended and wakes up from USB activity. +The wakeup source is available only on USB-OTG capable targets with High-Speed USB support. It is not supported by USB Serial/JTAG-only peripherals. + +As a USB stack, a TinyUSB component is used. + +## How to use example + +The example implements a USB CDC ACM echo device. When the host suspends the USB bus, the `tud_suspend_cb` callback configures the USB peripheral suspend state and enters light sleep. USB bus activity from the host wakes the chip and the CDC ACM echo device continues running. + +### Hardware Required + +Any ESP board that supports High-Speed USB-OTG wakeup from light sleep. + +#### Pin Assignment + +_Note:_ In case your board doesn't have micro-USB connector connected to USB-OTG peripheral, you may have to DIY a cable and connect **D+** and **D-** to the pins listed below. + +See common pin assignments for USB Device examples from [upper level](../../README.md#common-pin-assignments). + +### Build and Flash + +Build the project and flash it to the board, then run monitor tool to view serial output: + +```bash +idf.py -p PORT flash monitor +``` + +(Replace PORT with the name of the serial port to use.) + +(To exit the serial monitor, type ``Ctrl-]``.) + +See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects. + +## Example Output + +After the flashing you should see this output: + +``` +I (285) tusb_cdc_acm_wakeup: USB initialization +I (455) TinyUSB: TinyUSB Driver installed +I (465) tusb_cdc_acm_wakeup: USB initialization DONE +``` + +Connect to the serial port (e.g. on Linux, it should be `/dev/ttyACM0`) by any terminal application (e.g. `picocom /dev/ttyACM0`). +Now you can send data strings to the device, the device will echo back the same data string. + +To trigger USB suspend from the host, disable or suspend the CDC ACM device on the host side. On Windows, open Device Manager, find the COM port for the device, and disable it. On Linux, you can unbind the CDC ACM driver for the device or put the USB device into autosuspend. On macOS, disconnecting the terminal application and letting the host suspend the interface can also trigger suspend depending on the host power policy. + +When the USB host suspends and resumes the bus, the monitor output should include: + +``` +I (122142) tusb_cdc_acm_wakeup: 0x4ff3afa4 48 65 6c 6c 6f |Hello| +I (122272) tusb_cdc_acm_wakeup: Data from channel 0: +I (122272) tusb_cdc_acm_wakeup: 0x4ff3afa4 48 65 6c 6c 6f |Hello| +I (122402) tusb_cdc_acm_wakeup: Data from channel 0: +I (122402) tusb_cdc_acm_wakeup: 0x4ff3afa4 48 65 6c 6c 6f |Hello| +I (122622) tusb_cdc_acm_wakeup: Data from channel 0: +I (122622) tusb_cdc_acm_wakeup: 0x4ff3afa4 48 65 6c 6c 6f |Hello| +I (127432) tusb_cdc_acm_wakeup: Line state changed on channel 0: DTR:0, RTS:0 +I (132402) tusb_cdc_acm_wakeup: USB suspended, entering light sleep +I (132422) tusb_cdc_acm_wakeup: Woke up from: USB +I (132422) tusb_cdc_acm_wakeup: USB resumed +I (145762) tusb_cdc_acm_wakeup: Line state changed on channel 0: DTR:0, RTS:0 +I (145812) tusb_cdc_acm_wakeup: Line state changed on channel 0: DTR:0, RTS:0 +I (145812) tusb_cdc_acm_wakeup: Line state changed on channel 0: DTR:0, RTS:0 +I (145822) tusb_cdc_acm_wakeup: Line state changed on channel 0: DTR:0, RTS:0 +I (145822) tusb_cdc_acm_wakeup: Line state changed on channel 0: DTR:0, RTS:0 +I (145832) tusb_cdc_acm_wakeup: Line state changed on channel 0: DTR:0, RTS:0 +I (148762) tusb_cdc_acm_wakeup: Data from channel 0: +I (148762) tusb_cdc_acm_wakeup: 0x4ff3afa4 48 65 6c 6c 6f |Hello| +I (148902) tusb_cdc_acm_wakeup: Data from channel 0: +I (148902) tusb_cdc_acm_wakeup: 0x4ff3afa4 48 65 6c 6c 6f |Hello| +I (149032) tusb_cdc_acm_wakeup: Data from channel 0: +I (149032) tusb_cdc_acm_wakeup: 0x4ff3afa4 48 65 6c 6c 6f |Hello| +I (149252) tusb_cdc_acm_wakeup: Data from channel 0: +I (149252) tusb_cdc_acm_wakeup: 0x4ff3afa4 48 65 6c 6c 6f |Hello| +I (151382) tusb_cdc_acm_wakeup: Line state changed on channel 0: DTR:0, RTS:0 +I (155312) tusb_cdc_acm_wakeup: USB suspended, entering light sleep +``` diff --git a/examples/peripherals/usb/device/tusb_cdc_acm_wakeup/main/CMakeLists.txt b/examples/peripherals/usb/device/tusb_cdc_acm_wakeup/main/CMakeLists.txt new file mode 100644 index 00000000000..f2c0b4447b0 --- /dev/null +++ b/examples/peripherals/usb/device/tusb_cdc_acm_wakeup/main/CMakeLists.txt @@ -0,0 +1,3 @@ +idf_component_register(SRCS "tusb_cdc_acm_wakeup_main.c" + INCLUDE_DIRS . + PRIV_REQUIRES esp_hal_usb) diff --git a/examples/peripherals/usb/device/tusb_cdc_acm_wakeup/main/idf_component.yml b/examples/peripherals/usb/device/tusb_cdc_acm_wakeup/main/idf_component.yml new file mode 100644 index 00000000000..e1a93a262ee --- /dev/null +++ b/examples/peripherals/usb/device/tusb_cdc_acm_wakeup/main/idf_component.yml @@ -0,0 +1,4 @@ +## IDF Component Manager Manifest File +dependencies: + espressif/esp_tinyusb: + version: "^2.0.1~1" diff --git a/examples/peripherals/usb/device/tusb_cdc_acm_wakeup/main/tusb_cdc_acm_wakeup_main.c b/examples/peripherals/usb/device/tusb_cdc_acm_wakeup/main/tusb_cdc_acm_wakeup_main.c new file mode 100644 index 00000000000..519404a30b8 --- /dev/null +++ b/examples/peripherals/usb/device/tusb_cdc_acm_wakeup/main/tusb_cdc_acm_wakeup_main.c @@ -0,0 +1,164 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ + +#include +#include +#include +#include "esp_log.h" +#include "esp_rom_serial_output.h" +#include "esp_sleep.h" +#include "esp_private/usb_phy.h" +#include "freertos/FreeRTOS.h" +#include "freertos/queue.h" +#include "freertos/task.h" +#include "sdkconfig.h" +#include "soc/soc_caps.h" +#include "tinyusb.h" +#include "tinyusb_cdc_acm.h" +#include "tinyusb_default_config.h" + +#if !SOC_PM_SUPPORT_USB_WAKEUP || !SOC_PM_SUPPORT_CNNT_PD +#error This example requires a target with SOC_PM_SUPPORT_USB_WAKEUP. +#endif + +static const char *TAG = "tusb_cdc_acm_wakeup"; +static uint8_t rx_buf[CONFIG_TINYUSB_CDC_RX_BUFSIZE + 1]; + +/** + * @brief Application Queue + */ +static QueueHandle_t app_queue; +typedef struct { + uint8_t buf[CONFIG_TINYUSB_CDC_RX_BUFSIZE + 1]; // Data buffer + size_t buf_len; // Number of bytes received + uint8_t itf; // Index of CDC device interface +} app_message_t; + +/** + * @brief CDC device RX callback + * + * CDC device signals, that new data were received + * + * @param[in] itf CDC device index + * @param[in] event CDC event type + */ +void tinyusb_cdc_rx_callback(int itf, cdcacm_event_t *event) +{ + (void) event; + + size_t rx_size = 0; + + esp_err_t ret = tinyusb_cdcacm_read(itf, rx_buf, CONFIG_TINYUSB_CDC_RX_BUFSIZE, &rx_size); + if (ret == ESP_OK) { + app_message_t tx_msg = { + .buf_len = rx_size, + .itf = itf, + }; + + memcpy(tx_msg.buf, rx_buf, rx_size); + xQueueSend(app_queue, &tx_msg, 0); + } else { + ESP_LOGE(TAG, "Read Error"); + } +} + +/** + * @brief CDC device line change callback + * + * CDC device signals, that the DTR, RTS states changed + * + * @param[in] itf CDC device index + * @param[in] event CDC event type + */ +void tinyusb_cdc_line_state_changed_callback(int itf, cdcacm_event_t *event) +{ + int dtr = event->line_state_changed_data.dtr; + int rts = event->line_state_changed_data.rts; + ESP_LOGI(TAG, "Line state changed on channel %d: DTR:%d, RTS:%d", itf, dtr, rts); +} + +void tud_suspend_cb(bool remote_wakeup_en) +{ + (void) remote_wakeup_en; + + ESP_LOGI(TAG, "USB suspended, entering light sleep"); + usb_phy_set_otg_suspend_state(true); + + esp_rom_output_tx_wait_idle(CONFIG_ESP_CONSOLE_UART_NUM); + + esp_err_t err = esp_light_sleep_start(); + if (err != ESP_OK) { + ESP_LOGW(TAG, "Light sleep rejected: %s", esp_err_to_name(err)); + } else { + uint32_t causes = esp_sleep_get_wakeup_causes(); + if (causes & BIT(ESP_SLEEP_WAKEUP_UNDEFINED)) { + ESP_LOGW(TAG, "Woke up from an unknown source: 0x%lx", causes); + } else if (causes & BIT(ESP_SLEEP_WAKEUP_USB)) { + ESP_LOGI(TAG, "Woke up from: USB"); + } + } + usb_phy_set_otg_suspend_state(false); + usb_phy_clear_otg_wakeup_status(); +} + +void tud_resume_cb(void) +{ + ESP_LOGI(TAG, "USB resumed"); +} + +void tud_reset_cb(void) +{ + ESP_LOGI(TAG, "USB reset"); +} + +void app_main(void) +{ + // Create FreeRTOS primitives + app_queue = xQueueCreate(5, sizeof(app_message_t)); + assert(app_queue); + app_message_t msg; + + ESP_ERROR_CHECK(esp_sleep_cpu_retention_init()); + ESP_ERROR_CHECK(esp_sleep_enable_usb_wakeup()); + ESP_ERROR_CHECK(esp_sleep_pd_config(ESP_PD_DOMAIN_CNNT, ESP_PD_OPTION_ON)); + + ESP_LOGI(TAG, "USB initialization"); + const tinyusb_config_t tusb_cfg = TINYUSB_DEFAULT_CONFIG(); + ESP_ERROR_CHECK(tinyusb_driver_install(&tusb_cfg)); + + tinyusb_config_cdcacm_t acm_cfg = { + .cdc_port = TINYUSB_CDC_ACM_0, + .callback_rx = &tinyusb_cdc_rx_callback, // the first way to register a callback + .callback_rx_wanted_char = NULL, + .callback_line_state_changed = NULL, + .callback_line_coding_changed = NULL + }; + + ESP_ERROR_CHECK(tinyusb_cdcacm_init(&acm_cfg)); + /* the second way to register a callback */ + ESP_ERROR_CHECK(tinyusb_cdcacm_register_callback( + TINYUSB_CDC_ACM_0, + CDC_EVENT_LINE_STATE_CHANGED, + &tinyusb_cdc_line_state_changed_callback)); + + ESP_LOGI(TAG, "USB initialization DONE"); + while (1) { + if (xQueueReceive(app_queue, &msg, portMAX_DELAY)) { + if (msg.buf_len) { + /* Print received data */ + ESP_LOGI(TAG, "Data from channel %d:", msg.itf); + ESP_LOG_BUFFER_HEXDUMP(TAG, msg.buf, msg.buf_len, ESP_LOG_INFO); + + /* Write back */ + tinyusb_cdcacm_write_queue(msg.itf, msg.buf, msg.buf_len); + esp_err_t err = tinyusb_cdcacm_write_flush(msg.itf, 0); + if (err != ESP_OK) { + ESP_LOGE(TAG, "CDC ACM write flush error: %s", esp_err_to_name(err)); + } + } + } + } +} diff --git a/examples/peripherals/usb/device/tusb_cdc_acm_wakeup/sdkconfig.defaults b/examples/peripherals/usb/device/tusb_cdc_acm_wakeup/sdkconfig.defaults new file mode 100644 index 00000000000..00c763834c7 --- /dev/null +++ b/examples/peripherals/usb/device/tusb_cdc_acm_wakeup/sdkconfig.defaults @@ -0,0 +1,3 @@ +CONFIG_PM_ENABLE=y +CONFIG_PM_POWER_DOWN_PERIPHERAL_IN_LIGHT_SLEEP=y +CONFIG_TINYUSB_CDC_ENABLED=y diff --git a/examples/protocols/.build-test-rules.yml b/examples/protocols/.build-test-rules.yml index c0a1535f25a..6a347a1402a 100644 --- a/examples/protocols/.build-test-rules.yml +++ b/examples/protocols/.build-test-rules.yml @@ -26,6 +26,15 @@ examples/protocols/esp_http_client: depends_components+: - esp_http_client +examples/protocols/esp_http_client_mutual_auth: + <<: *default_rules + disable_test: + - if: IDF_TARGET != "esp32c3" + depends_components+: + - esp_http_client + - esp-tls + - esp_secure_cert_mgr + examples/protocols/esp_local_ctrl: <<: *default_rules disable+: diff --git a/examples/protocols/esp_http_client_mutual_auth/CMakeLists.txt b/examples/protocols/esp_http_client_mutual_auth/CMakeLists.txt new file mode 100644 index 00000000000..6b76a8c32f3 --- /dev/null +++ b/examples/protocols/esp_http_client_mutual_auth/CMakeLists.txt @@ -0,0 +1,15 @@ +cmake_minimum_required(VERSION 3.22) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +idf_build_set_property(MINIMAL_BUILD ON) + +project(esp_http_client_mutual_auth) + +if(CONFIG_EXAMPLE_MUTUAL_AUTH_USE_HW_RSA_DS) + # Flash the pre-built esp_secure_cert partition (device cert + DS context) so it + # is included in the QEMU flash image created by esptool merge_bin. + esptool_py_flash_to_partition( + flash esp_secure_cert + "${CMAKE_CURRENT_SOURCE_DIR}/main/certs/esp_secure_cert_data/esp_secure_cert.bin" + ) +endif() diff --git a/examples/protocols/esp_http_client_mutual_auth/README.md b/examples/protocols/esp_http_client_mutual_auth/README.md new file mode 100644 index 00000000000..d005c96b202 --- /dev/null +++ b/examples/protocols/esp_http_client_mutual_auth/README.md @@ -0,0 +1,104 @@ +| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C5 | ESP32-C6 | ESP32-C61 | ESP32-H2 | ESP32-P4 | ESP32-S2 | ESP32-S3 | +| ----------------- | ----- | -------- | -------- | -------- | -------- | --------- | -------- | -------- | -------- | -------- | + +# ESP HTTP Client Mutual TLS Authentication Example + +This example demonstrates mutual TLS (mTLS) authentication using `esp_http_client`. The server verifies the client's identity via a client certificate, and the client verifies the server using a CA certificate. + +Two client key modes are supported, selectable via Kconfig: + +| Mode | Kconfig | Client key source | +|------|---------|-------------------| +| **Software keys** (default) | `EXAMPLE_MUTUAL_AUTH_USE_HW_RSA_DS=n` | PEM files embedded in firmware | +| **DS peripheral** | `EXAMPLE_MUTUAL_AUTH_USE_HW_RSA_DS=y` | Hardware Digital Signature peripheral via `esp_secure_cert` partition | + +## How it works + +1. Device connects to Wi-Fi or Ethernet (via `protocol_examples_common`) +2. Prompts for HTTPS server URL on serial console +3. Performs HTTPS GET with mutual TLS — sends client certificate to server +4. Logs the HTTP status code + +## Quick start (software key mode) + +```bash +idf.py set-target esp32c3 +idf.py build flash monitor +``` + +When prompted, enter the URL of an HTTPS server that requires client certificates. The pre-generated certificates in `main/certs/` are used automatically. + +## Certificates + +Pre-generated certificates with 10-year lifetime are in `main/certs/`. A single CA signs both client and server certificates. See `main/certs/README.md` for OpenSSL commands to regenerate. + +| File | Purpose | Embedded in firmware? | +|------|---------|----------------------| +| `ca_cert.pem` | CA certificate (verifies server) | Yes (both modes) | +| `client_cert.pem` | Client certificate | Yes (software mode only) | +| `client_key.pem` | Client private key | Yes (software mode only) | +| `server_cert.pem` | Server certificate (pytest) | No | +| `server_key.pem` | Server private key (pytest) | No | + +## DS peripheral mode + +The DS (Digital Signature) peripheral holds the client private key in hardware. The client certificate and DS context are stored in the `esp_secure_cert` partition. + +### Prerequisites + +- ESP32-C3 or other chip with `SOC_DIG_SIGN_SUPPORTED` +- `esp-secure-cert-tool` (`pip install esp-secure-cert-tool`) + +### Provisioning + +1. **Generate DS partition and HMAC key** (from the example directory): + +```bash +configure_esp_secure_cert.py \ + --device-cert main/certs/client_cert.pem \ + --target_chip esp32c3 \ + --configure_ds --priv_key_algo RSA 2048 \ + --skip_flash --keep_ds_data_on_host \ + --private-key main/certs/client_key.pem \ + --efuse_key_id 1 +``` + +Copy the output files to `main/certs/esp_secure_cert_data/`: +- `esp_secure_cert.bin` +- `hmac_key.bin` + +2. **Burn HMAC key to eFuse:** + +```bash +# QEMU +idf.py qemu efuse-burn-key --do-not-confirm BLOCK_KEY1 \ + main/certs/esp_secure_cert_data/hmac_key.bin HMAC_DOWN_DIGITAL_SIGNATURE + +# Hardware (irreversible!) +idf.py efuse-burn-key --do-not-confirm BLOCK_KEY1 \ + main/certs/esp_secure_cert_data/hmac_key.bin HMAC_DOWN_DIGITAL_SIGNATURE +``` + +3. **Build and flash:** + +```bash +idf.py -DSDKCONFIG_DEFAULTS="sdkconfig.defaults;sdkconfig.ci.qemu_ds" build flash monitor +``` + +## QEMU testing + +Three QEMU tests are included: + +- `test_mutual_auth_software_keys` — software key mTLS, expects HTTP 200 +- `test_mutual_auth_ds` — DS peripheral mTLS, expects HTTP 200 +- `test_mutual_auth_ds_fails_wrong_credentials` — DS with wrong server CA, expects failure + +The tests start a local HTTPS server with mutual TLS, inject the URL via serial, and verify device output. + +## Troubleshooting + +**TLS handshake failed: BAD_SIGNATURE** +The client certificate is not signed by the CA the server expects. Regenerate certs using the commands in `main/certs/README.md`. + +**ESP_ERR_HW_CRYPTO_DS_HMAC_FAIL** +The HMAC key in eFuse does not match the key used to create the DS partition. For QEMU, delete the eFuse image and regenerate it from the current `hmac_key.bin`. diff --git a/examples/protocols/esp_http_client_mutual_auth/main/CMakeLists.txt b/examples/protocols/esp_http_client_mutual_auth/main/CMakeLists.txt new file mode 100644 index 00000000000..f320bfe099c --- /dev/null +++ b/examples/protocols/esp_http_client_mutual_auth/main/CMakeLists.txt @@ -0,0 +1,14 @@ +set(requires esp-tls nvs_flash esp_event esp_netif esp_http_client) + +set(embed_txt certs/ca_cert.pem) + +if(CONFIG_EXAMPLE_MUTUAL_AUTH_USE_HW_RSA_DS) + list(APPEND requires esp_secure_cert_mgr) +else() + list(APPEND embed_txt certs/client_cert.pem certs/client_key.pem) +endif() + +idf_component_register(SRCS "mutual_auth_example.c" + INCLUDE_DIRS "." + PRIV_REQUIRES ${requires} + EMBED_TXTFILES ${embed_txt}) diff --git a/examples/protocols/esp_http_client_mutual_auth/main/Kconfig.projbuild b/examples/protocols/esp_http_client_mutual_auth/main/Kconfig.projbuild new file mode 100644 index 00000000000..f821f6fe594 --- /dev/null +++ b/examples/protocols/esp_http_client_mutual_auth/main/Kconfig.projbuild @@ -0,0 +1,12 @@ +menu "Example Configuration" + config EXAMPLE_MUTUAL_AUTH_USE_HW_RSA_DS + bool "Use DS peripheral for client authentication" if SOC_DIG_SIGN_SUPPORTED + default n + help + When enabled, the client private key is held by the DS (Digital Signature) + peripheral. The client certificate and DS context are loaded from the + esp_secure_cert partition at runtime. Requires provisioning; see the README. + + When disabled (default), the client certificate and private key are embedded + in the firmware as PEM files from main/certs/. +endmenu diff --git a/examples/protocols/esp_http_client_mutual_auth/main/certs/README.md b/examples/protocols/esp_http_client_mutual_auth/main/certs/README.md new file mode 100644 index 00000000000..a1be2db4a0e --- /dev/null +++ b/examples/protocols/esp_http_client_mutual_auth/main/certs/README.md @@ -0,0 +1,41 @@ +# Certificate Generation for Mutual TLS Example + +All certificates are pre-generated with a 10-year lifetime. To regenerate, run +the following OpenSSL commands from this directory (`main/certs/`). + +## Generate CA (signs both client and server certs) + + openssl req -newkey rsa:2048 -nodes -keyout ca_key.pem -x509 -days 3650 \ + -out ca_cert.pem -subj "/CN=Mutual Auth Test CA" + +## Generate client certificate (embedded in firmware) + + openssl genrsa -out client_key.pem 2048 + openssl req -out client.csr -key client_key.pem -new -subj "/CN=esp_mutual_auth_client" + openssl x509 -req -days 3650 -in client.csr -CA ca_cert.pem -CAkey ca_key.pem \ + -sha256 -CAcreateserial -out client_cert.pem + rm client.csr ca_cert.srl + +## Generate server certificate (used by pytest mTLS server) + + openssl genrsa -out server_key.pem 2048 + openssl req -out server.csr -key server_key.pem -new -subj "/CN=esp_mutual_auth_server" + openssl x509 -req -days 3650 -in server.csr -CA ca_cert.pem -CAkey ca_key.pem \ + -sha256 -CAcreateserial -out server_cert.pem + rm server.csr ca_cert.srl + +## Files + +| File | Purpose | Embedded in firmware? | +|------|---------|----------------------| +| `ca_cert.pem` | CA certificate (verifies server identity) | Yes | +| `ca_key.pem` | CA private key (signs certs; keep private) | No | +| `client_cert.pem` | Client certificate (sent to server) | Yes (software mode) | +| `client_key.pem` | Client private key | Yes (software mode) | +| `server_cert.pem` | Server certificate (pytest mTLS server) | No | +| `server_key.pem` | Server private key (pytest mTLS server) | No | + +## DS peripheral mode + +For DS peripheral mode, see `esp_secure_cert_data/` and the DS provisioning +section in the top-level README. diff --git a/examples/protocols/esp_http_client_mutual_auth/main/certs/ca_cert.pem b/examples/protocols/esp_http_client_mutual_auth/main/certs/ca_cert.pem new file mode 100644 index 00000000000..ffd2068b266 --- /dev/null +++ b/examples/protocols/esp_http_client_mutual_auth/main/certs/ca_cert.pem @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDBTCCAe2gAwIBAgIUHrbEhg+kRb2wd66oi57asPHvrBYwDQYJKoZIhvcNAQEL +BQAwEjEQMA4GA1UEAwwHVGVzdCBDQTAeFw0yNjAyMDQwNzMwMDRaFw0zNjAyMDIw +NzMwMDRaMBIxEDAOBgNVBAMMB1Rlc3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IB +DwAwggEKAoIBAQCpug883pCJKGj0cZ/zdJLnjBa9M6lPWscgDFh1wPO1wyOzu25Q +kUykgUE8GE3W7RC4zw4LCUkU06EeVnp6UkXsKktC94OvAeYczsq6Z95WwyF8ktXP +AzW/Lpz202MFpA4edcoczLJEf1GPmoI1RM//V19Op+6nLBNLLTj35yO3CUMjxeh6 +ptlxi3PbiFliOVff6DfPcNzp7rh1k+SWoNP5adTN8aHjpoO53wFHhBfiP4zqnLJG +uHFHApGcwyf1SAbERJ4F2tV+xMRVnp2i/Bx9pIY7qVRKdqOswQK6YGSlznfY8RCM +he9B0mZpSa9NFsPdm+Lpt2wxDvuSC/WtMLHBAgMBAAGjUzBRMB0GA1UdDgQWBBRG +ECXpPzDsNPv55ZHXAsKgCIYF6zAfBgNVHSMEGDAWgBRGECXpPzDsNPv55ZHXAsKg +CIYF6zAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAFiLooiqC/ +LQBoN7J6FyQhsKvly+yX4Vh8ituhQQnt4kRD28ci+GmceqgNlX86e/MMTzTXOCMJ +EIhR4xHd1xg5lnPLPS1o+LXsbyOlU8QTCoYgVkjDVHi4fGnbMIoWaZBt8o3MYgAy +Fc+7A6ZoK9PIbbcRUYPSPGe1loop490tgyNrvyc29lLdg4ljO+Xg3Wpu95bTxe/F +bjWFWZgF6aqAfrT7QZXk9OIfOkDpcb5EsxBySQIZNC06FEtNkwanVWFSMdDm3Axh +Xjuv1HdGyHnS/jcFTZ4EfB76oU0+NuoYtDYF7Fns3Upvg8QZhhs+6Ob5mRKJefRD +HlIA+c6CBmsY +-----END CERTIFICATE----- diff --git a/examples/protocols/esp_http_client_mutual_auth/main/certs/ca_key.pem b/examples/protocols/esp_http_client_mutual_auth/main/certs/ca_key.pem new file mode 100644 index 00000000000..ed7aa03dad5 --- /dev/null +++ b/examples/protocols/esp_http_client_mutual_auth/main/certs/ca_key.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQCpug883pCJKGj0 +cZ/zdJLnjBa9M6lPWscgDFh1wPO1wyOzu25QkUykgUE8GE3W7RC4zw4LCUkU06Ee +Vnp6UkXsKktC94OvAeYczsq6Z95WwyF8ktXPAzW/Lpz202MFpA4edcoczLJEf1GP +moI1RM//V19Op+6nLBNLLTj35yO3CUMjxeh6ptlxi3PbiFliOVff6DfPcNzp7rh1 +k+SWoNP5adTN8aHjpoO53wFHhBfiP4zqnLJGuHFHApGcwyf1SAbERJ4F2tV+xMRV +np2i/Bx9pIY7qVRKdqOswQK6YGSlznfY8RCMhe9B0mZpSa9NFsPdm+Lpt2wxDvuS +C/WtMLHBAgMBAAECggEAAt0DUwvvrQqwG5ibodIwhMS/oPVBSNgPli4zI1hsHB/E +x0xVD/mljPxrvvFrhcHV14JRurSvRZFM7Wu40P24lYOApcpyb6ZE7S09bQ/hx72u +v9Dj18RWeKlXB1B5Yg/al5+1107KPp7Vv8oT4oVsy2JcVqG9ZFdZY3optP/yoay4 +oRJprOV4jc8aaLB0+zze/7RJZ4DtpeOWhyNws/cGwwFvaDlncnylAoaDs20MBfsg +VV+glo69rqvn3SCfSXIG5/vyfHS52xPN04kPrpXpu7kvTeJHqCfq4w5LSS0GgBdM +5CbBSpW25AnX9Jqczxhu7Y2rt8kIx+4gnW05z0YB2QKBgQDn9yeQzsXK98R7R6hz +ogKyOYQuNVNjjR7guSD0285QTU+XB8jC2nqiTVPy8Xy1w6udh9SjvqS47yQyySzC +ZCFvT3rwwdWP5pUj89FG7XoX33hQrV4+jbBCcjajeyMxAmMM83ISL9NahsVLWx7K +gdPdxhBiCB8dueF+kVcBf1Bb2QKBgQC7UAjwl45oOIDoXBqN3TDGVodwQM6gn1rY +heYjEUeQmjL6j4e8PiUFl5rHCNavIUr0gkBhnN5hcq2mvfECBEVVm8dlrvHHa4Ti +ujhC6DGgrOUIPok+rXJoELvmrYa72H04uHwkOkESkuHO+gHuRJ7nIxH/BktGKsol +Unp+CNBcKQKBgQCtn+NvkjWeTII2vFYr5xIZkM+NPsDh/Nkl36v5WyU8GgH+zAbL +QnkUTskNSQ/NhV5JFUhmH+Zvvh/cG5RzFDuqc1VUK+HMSg1L0c3NRydiAxStXnby +X1+U/KRFDYAzyNOW+Alj74RFeCbo1pVfgnmwv/W3StjviRhtgiAbsM3XUQKBgQCg +E94P/vWtC9zetxfadVXhqsFEpZ3wlz9EG+p5vaKzaZR3nYIa1eE9zjpwLpWKRaGR +JF9xDGbgUOkmvtzhJFU8vEzEEaZ/DtwaB5tdUqJW9msliIwyDHjhhquOkG28y174 +wnEVZNOH1A82m2JbBjnmvon6sJ5T8O2gx8P3QgEPQQKBgQCcpSf/rJxMfEeZf4Kl +EeqSjPwFRKsC/8gBs63VJrmYh2XigL5tI2MnTdgts+4W1xC8avPiRoeaZ8YN6258 +n2tVyhJrm3F1cw/tFmLE0kuNkS0jOA/hzFeOkLJTZXR9npBzwNduQQ5i0HqQM9Qi +aIIVlhLZ0Jlqu6SLnJ74n34VnA== +-----END PRIVATE KEY----- diff --git a/examples/protocols/esp_http_client_mutual_auth/main/certs/client_cert.pem b/examples/protocols/esp_http_client_mutual_auth/main/certs/client_cert.pem new file mode 100644 index 00000000000..871e8952e75 --- /dev/null +++ b/examples/protocols/esp_http_client_mutual_auth/main/certs/client_cert.pem @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDBDCCAeygAwIBAgIUa91CxumwjyUKK9AHnJ9upekrqlgwDQYJKoZIhvcNAQEL +BQAwEjEQMA4GA1UEAwwHVGVzdCBDQTAeFw0yNjA1MjkwMzI5MTlaFw0zNjA1MjYw +MzI5MTlaMCIxIDAeBgNVBAMMF2VzcF9odHRwX2NsaWVudF9kc190ZXN0MIIBIjAN +BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsztCh5DaIh+KDzk6L5CEps8AJ6cw +xJffL/wjZc3yTOCjwDOX7UdDilizLobkzCBMm8lccafts2+jEAVaMrR1pQVO0c8J +5sDk1IHlNJVlq1UxnIl7JvbDjtAWp+cHYMRQSBXmHrkqJl45LPTdjC0FvdsB2oyj +HFmDHp3A3B/BwzdwyBzucG+Xa3OILZI0wsri0oDMyTw6Xg6SoXmLTQhbqCD38Ijt +d9AalLMElM+2j+IH5zVlnG+JpCrvzYy7pY7gIUmjLFBewSpEiPjxWjrFJ0teXyTj +smhd/IK5rVQw2v1wjKT1Wc5lgRDifZTVNRxDIepr+m4Ck3clRfyesSslhQIDAQAB +o0IwQDAdBgNVHQ4EFgQU7jBC4N9rFhRFkzBq0KcXGRsNatcwHwYDVR0jBBgwFoAU +RhAl6T8w7DT7+eWR1wLCoAiGBeswDQYJKoZIhvcNAQELBQADggEBAHWHKGJ0Z4+b +rw25oZ9ZK2pZkQcvAG1HBQxHoNBRmYruitvALnQhq7UuHlAoMFxBmkJ61FRjgA1x +zGAZsfTCy/Agv2lfVf6LiRd+Rl134rzHLlCoTg0tZC5uFcS/6W5mbfXIviYaPRaf +RMgpM1ZPYMF5VsX0B6CJH6I+UHoZVLTb1nz5gu01WiKKULTygW70xyk5B0osiK9C +UDCOQ7jcYrjYNZulwY2qv+Kg3PH6aFR9xT+RUp1fDb0HJk0/M0YDOdh4QUzm29UL +KCCM+E25tan4Nvgg1jQIw3dppWc6dCSE0IgCCVY+O3aZqnfRugHl7NCxJ5pHCyPr +zV9BSaFx8Uk= +-----END CERTIFICATE----- diff --git a/examples/protocols/esp_http_client_mutual_auth/main/certs/client_key.pem b/examples/protocols/esp_http_client_mutual_auth/main/certs/client_key.pem new file mode 100644 index 00000000000..1e72440c6b8 --- /dev/null +++ b/examples/protocols/esp_http_client_mutual_auth/main/certs/client_key.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCzO0KHkNoiH4oP +OTovkISmzwAnpzDEl98v/CNlzfJM4KPAM5ftR0OKWLMuhuTMIEybyVxxp+2zb6MQ +BVoytHWlBU7RzwnmwOTUgeU0lWWrVTGciXsm9sOO0Ban5wdgxFBIFeYeuSomXjks +9N2MLQW92wHajKMcWYMencDcH8HDN3DIHO5wb5drc4gtkjTCyuLSgMzJPDpeDpKh +eYtNCFuoIPfwiO130BqUswSUz7aP4gfnNWWcb4mkKu/NjLuljuAhSaMsUF7BKkSI ++PFaOsUnS15fJOOyaF38grmtVDDa/XCMpPVZzmWBEOJ9lNU1HEMh6mv6bgKTdyVF +/J6xKyWFAgMBAAECggEABhTu3dUbTx4dMVq37+Y/rL4IcNAFC8llgDuA41L43lqf +DoYRqTJfsdbvBEH9Nd9WCiaWivVvpO+XAROqwF626+nBXMWMPfbHV7NYA4/wOmny +fC/UFySMypCivCwkPXMxOt5Mso0w8iAEtlL8Hjtm3vmKrLpUoevNqGEiVWCEfYBv +h7Yc7uKcB90WyZ3LtgmghV46kDFvaSMMdPTqVU6jgCTpq6yBCntpeYz1VqcnDhZt +nU6i/vinJz8BMDXIDbVonie0lZ9tCWnYwHaWCkIPy9Cb5eqlldJfiN1CYz9MFGGs +oymeTF+lrtPphRTakpziDkxBM/AwnIkx5HQTsxF4jwKBgQD882SmrGvbshJLYMVl +4UznqDoP59M7G5kwQ/j579VKkhbrKrV0JvOEYQuJx18d7drDyY7eLRF6KYRLYOhG +FWznMpNhRYQMcWcAqNLHb5W4FSsGGRt8Q1qmmKQmO192FZVveG0czz2FAPY4uSBX +4va+mJszMrnfEApqW8TdOrEwYwKBgQC1ZF5qTQR9wZPj+65mGwfYei1EP1OEE5xc +DWo9fsdDVxv4ykHTpURF+EftMkOF+b/GTgJHyEceZfgm/9dx15EeQuHqpNDjffUD +y/b8GVT7NB2nR2m3ztXYBHsRK7uYGbgVtB+7w1gFpcZRx92SvITy+EjQRTs9z4n/ +Ru2y8P+S9wKBgF0bTfIXxz+/xQIf5akBjCg9ANo378Vy/CkK7As7n1vqeCsptk7B +w6L3gaK+UyGWGo80krTvC97Wh06jpfueCU70i9EjIF7gIxTYD3W/efGfQQ3mkfpk +ZGqsBsfX1OSHP1Efl7IiCjf5yafJZMFU1pQDYiUvR8F2iw7pJoZ0AyKXAoGBAKKv +RCaesLqBFTzSC5Y0BBNZcKPXD/ZTCFdfCLviqqBwzfuSmvtRLCx9AzVvcTQFzMP0 +TwNGUtKmrat8piPKLLMxVSF3dImz/D3NftSXe6pZEYdn+x8JeK1nR2EdEgDWgE2m +4RcrmhRmm7nZQZZLUgoAOH3iucE0FBZJ7QIiN3X9AoGBAJmGvBoa6ZQpbRXZbVfv +qvz+gzwN2fO3t7iWGaHVCGygnSaplCGl/MfVtb/ak3AG5NcjwDqjld8X7RE9dSk2 +l12Y3AtsyaDozLg5KXdwt+F1Pdhju9WB3pd5O25Ocn5JU1jUqL+huW+d7WhUDPO7 +PHTgtfI15eDu5LinkNPD73hE +-----END PRIVATE KEY----- diff --git a/examples/protocols/esp_http_client_mutual_auth/main/certs/esp_secure_cert_data/esp_secure_cert.bin b/examples/protocols/esp_http_client_mutual_auth/main/certs/esp_secure_cert_data/esp_secure_cert.bin new file mode 100644 index 00000000000..f2655a7b753 Binary files /dev/null and b/examples/protocols/esp_http_client_mutual_auth/main/certs/esp_secure_cert_data/esp_secure_cert.bin differ diff --git a/examples/protocols/esp_http_client_mutual_auth/main/certs/esp_secure_cert_data/hmac_key.bin b/examples/protocols/esp_http_client_mutual_auth/main/certs/esp_secure_cert_data/hmac_key.bin new file mode 100644 index 00000000000..50910577bcd --- /dev/null +++ b/examples/protocols/esp_http_client_mutual_auth/main/certs/esp_secure_cert_data/hmac_key.bin @@ -0,0 +1 @@ +BĔ<|Spt3Nksc d \ No newline at end of file diff --git a/examples/protocols/esp_http_client_mutual_auth/main/certs/esp_secure_cert_data/qemu_efuse.bin b/examples/protocols/esp_http_client_mutual_auth/main/certs/esp_secure_cert_data/qemu_efuse.bin new file mode 100644 index 00000000000..742c5e07f65 Binary files /dev/null and b/examples/protocols/esp_http_client_mutual_auth/main/certs/esp_secure_cert_data/qemu_efuse.bin differ diff --git a/examples/protocols/esp_http_client_mutual_auth/main/certs/server_cert.pem b/examples/protocols/esp_http_client_mutual_auth/main/certs/server_cert.pem new file mode 100644 index 00000000000..bb56c04e252 --- /dev/null +++ b/examples/protocols/esp_http_client_mutual_auth/main/certs/server_cert.pem @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDAzCCAeugAwIBAgIUcDdlf0FDC5KPEcTnJG58JcXKGtAwDQYJKoZIhvcNAQEL +BQAwEjEQMA4GA1UEAwwHVGVzdCBDQTAeFw0yNjA0MDgwMzAzMzdaFw0zNjA0MDUw +MzAzMzdaMCExHzAdBgNVBAMMFmVzcF9tdXR1YWxfYXV0aF9zZXJ2ZXIwggEiMA0G +CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC4AfXPbz06C1kwJHSkMWgzVHgz85Pb +bF7aRzImfHR9nfz2hNJ2Yg8Apdxt/xWM1n2uDDlVWjg0vK50X3DK4oz1Lk32//IE +iYajZuiFsO6ZBNHgy1rehJUlk92Z4fklhpEgpVT67Ph6o8yhPDjNgPM03eJsl6yC +lawxETSXU+dOhjzfuiT0IA74UOhTVIfVe8E0AlXGqjrN5QkKP5Xb74w1ovIZPOzy +vWNjnzuwg2MKWHTFOolPpKft4GUqPvMD41qNfymUL46JxtONcKwx//Q5hO0bSAnB +GJ1Brqca366ObF6Xh+dAXALnxVkbpauoJ7ZqdyiJg6YANovawe/oZZXpAgMBAAGj +QjBAMB0GA1UdDgQWBBTOXVwKpLmhvv/2Nqe9E4Hx8VYsrzAfBgNVHSMEGDAWgBRG +ECXpPzDsNPv55ZHXAsKgCIYF6zANBgkqhkiG9w0BAQsFAAOCAQEAdgeCBgBgu8N7 +r9ycEytvs1AKxf2P3c6VFOlXF8rRNq/+8a8CoyGm4cF8u7AyvQQJZya6Luv+xg+/ +tguXIbw5goNenNkEuUOHRlo39vZ/pHe/HGCAxHatWG8qgoahXCZh3OSEmHfDwEDc +RbYTJXGb98VynFzuxQ4RSDXb+WxhslwGCV2NXrF6gLS1FgZDKpJChmhkvw/ubdxY +QS3fSToWlvs7PSyZi2Ei5XuGfthQPT+PZpyHZufXX9evLDdGOB/5SOz+p0SSBXcZ +he21O0jovx0RDSnnq6kdG+qHfUrwOt5TGJ8tIJQZuKi9VUIMEONC7RMV4u1T00j9 +8VtZEG2gfg== +-----END CERTIFICATE----- diff --git a/examples/protocols/esp_http_client_mutual_auth/main/certs/server_key.pem b/examples/protocols/esp_http_client_mutual_auth/main/certs/server_key.pem new file mode 100644 index 00000000000..a0bc9da5ec7 --- /dev/null +++ b/examples/protocols/esp_http_client_mutual_auth/main/certs/server_key.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQC4AfXPbz06C1kw +JHSkMWgzVHgz85PbbF7aRzImfHR9nfz2hNJ2Yg8Apdxt/xWM1n2uDDlVWjg0vK50 +X3DK4oz1Lk32//IEiYajZuiFsO6ZBNHgy1rehJUlk92Z4fklhpEgpVT67Ph6o8yh +PDjNgPM03eJsl6yClawxETSXU+dOhjzfuiT0IA74UOhTVIfVe8E0AlXGqjrN5QkK +P5Xb74w1ovIZPOzyvWNjnzuwg2MKWHTFOolPpKft4GUqPvMD41qNfymUL46JxtON +cKwx//Q5hO0bSAnBGJ1Brqca366ObF6Xh+dAXALnxVkbpauoJ7ZqdyiJg6YANova +we/oZZXpAgMBAAECggEAHNtEgaUP9K6w7EmF9/Xo7HEpGfnvTHBAqSSUgHqbXiki +RvAU0rdADmRJyapA5pcAm1npZ9w/3O1gwnlGhrAOq6ijq2z/Yadw85ErkpkTt0WN +Ox6eVS/KSrFXWvmYM+1YNxwWfp9zvEU8eGX/ASNhgKnyB1ZQmc3/ygDJFEOhOQFe +rAMntZvoEiQkkQIL45iKLceFfnTQ4iJNLRDvkOxt98OYgjOkVfLZvYXgTTH0r1mn +x2M3ITVVcU4OGs5AFQgbV/9fB5raPCAnroQkRIG69XD+uOJmRX04WvW8Hk9BQXk0 +aAkPSa5yPdZbpjsV5aYaQYoQodblN2XKuVxoqmj6bQKBgQD+Bo5lDmhxNGC7x64B +6pTLAmSM2oT/mHRGRUyR+YB/Mf3mw5MhoBwZS5CuB1UaOaFBUx0b8SpfXfXMAhcF +483FcyYKK3B/ZV9u3EfvxCBiiLzwYqLQYaBp7efeQkAJzb+sIj0+ZE70Lh2IjspL +zhFh7WaMaxeU1IAfqvfRaATjewKBgQC5cBY19erRuztFkP2SeNYHlIj0Y1uF6QRX +8ds2QLQsnj7J4WU/5VgTKWTywx7gn82/hWcJAs2k1PEUspRfMLwpChtOxvYHEosM +4a9fCBKlA+KPc4NpSGaC2M9VoJIJrgo7JjzJMFS6qSJ3zbmWUQh1XCLliNWNey2s +LkiNrA0M6wKBgQCG5wxv9nrYw6wrjRuHwQBL33VuqA3Bf0EgoGTNkOcApZflGS/l +x5WkiVDIWvSC/N/6RR1MXYLXKpsCQInhgt0gYsps1CzmOvu3cBxz5IAeU+ei8X7t +kysRllpw2lYP3shPrc9AdxzG6Eae4tXj9AefLegr4iOf0kpIhw8cklUmSQKBgQC1 +5qRy9DMO3unqeKq0poHU17hsepZJymSvXBjbpCbZabVP1SC7x95YlY9nr003rKpo +B5UlurE80oFV+0MeCTFZ1IcrBHJMR71MuomL3+BiLGhurTIn8ZRVIBZp+WOnyShS +E1UnSZijrcuY154IPJ7eeK3mmQ5ahY0szA3xoub+VwKBgQDUqNuMGgDmZo5O2Jsz +0J43BbRfqWXyQ9Q/nQQftHoxPEWkEQSoa3y58zWlYJKN4JoUU/UNR7dqdP5rnbhZ +f/pPXWdmhWQQlImgAfqJNEAZscA2NgsW/BOcot7zl/ZMgMOyDNXHoDSHenKPksKb +9POv9y/PnUWSUfG/WFwMEH65iQ== +-----END PRIVATE KEY----- diff --git a/examples/protocols/esp_http_client_mutual_auth/main/idf_component.yml b/examples/protocols/esp_http_client_mutual_auth/main/idf_component.yml new file mode 100644 index 00000000000..dc9f88e897a --- /dev/null +++ b/examples/protocols/esp_http_client_mutual_auth/main/idf_component.yml @@ -0,0 +1,7 @@ +dependencies: + protocol_examples_common: + path: ${IDF_PATH}/examples/common_components/protocol_examples_common + esp_secure_cert_mgr: + version: ">=2.0.0" + rules: + - if: "idf_version >=5.0" diff --git a/examples/protocols/esp_http_client_mutual_auth/main/mutual_auth_example.c b/examples/protocols/esp_http_client_mutual_auth/main/mutual_auth_example.c new file mode 100644 index 00000000000..22dd79ea1b4 --- /dev/null +++ b/examples/protocols/esp_http_client_mutual_auth/main/mutual_auth_example.c @@ -0,0 +1,212 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ +/* ESP HTTP Client Mutual Authentication Example + + This example code is in the Public Domain (or CC0 licensed, at your option.) + + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. +*/ + +#include +#include +#include + +#include "esp_log.h" +#include "esp_err.h" +#include "nvs_flash.h" +#include "esp_event.h" +#include "esp_netif.h" +#include "protocol_examples_common.h" +#include "esp_http_client.h" + +#if CONFIG_EXAMPLE_MUTUAL_AUTH_USE_HW_RSA_DS +#include "esp_secure_cert_read.h" +#if CONFIG_MBEDTLS_VER_4_X_SUPPORT +#include "psa_crypto_driver_esp_rsa_ds_contexts.h" +#else +#include "esp_rsa_sign_alt.h" +#endif +#endif + +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" + +static const char *TAG = "MUTUAL_AUTH"; + +#define URL_BUF_SIZE 256 + +/* CA cert for server verification (embedded in both modes) */ +extern const char ca_cert_pem_start[] asm("_binary_ca_cert_pem_start"); +extern const char ca_cert_pem_end[] asm("_binary_ca_cert_pem_end"); + +#if !CONFIG_EXAMPLE_MUTUAL_AUTH_USE_HW_RSA_DS +/* Software mode: client cert and key embedded as PEM */ +extern const char client_cert_pem_start[] asm("_binary_client_cert_pem_start"); +extern const char client_cert_pem_end[] asm("_binary_client_cert_pem_end"); +extern const char client_key_pem_start[] asm("_binary_client_key_pem_start"); +extern const char client_key_pem_end[] asm("_binary_client_key_pem_end"); +#endif + +/** + * Read the server URL from stdin. Used by pytest to inject the mTLS server address. + * Returns ESP_OK on success, ESP_FAIL on error or empty input. + */ +static esp_err_t read_url_from_stdin(char *url_buf, size_t buf_size) +{ + example_configure_stdin_stdout(); + ESP_LOGI(TAG, "Enter mutual auth server URL:"); + + if (fgets(url_buf, (int)buf_size, stdin) == NULL) { + ESP_LOGE(TAG, "Failed to read URL from stdin"); + return ESP_FAIL; + } + /* Strip trailing newline */ + size_t len = strlen(url_buf); + if (len > 0 && url_buf[len - 1] == '\n') { + url_buf[--len] = '\0'; + } + if (len == 0) { + ESP_LOGE(TAG, "Empty URL"); + return ESP_FAIL; + } + return ESP_OK; +} + +#if CONFIG_EXAMPLE_MUTUAL_AUTH_USE_HW_RSA_DS +/** + * DS peripheral mode: load client cert and DS context from esp_secure_cert partition. + */ +static void mutual_auth_with_ds(const char *url) +{ + char *dev_cert_buf = NULL; + uint32_t dev_cert_len = 0; + esp_ds_data_ctx_t *ds_ctx = NULL; + + esp_err_t ret = esp_secure_cert_get_device_cert(&dev_cert_buf, &dev_cert_len); + if (ret != ESP_OK || dev_cert_buf == NULL || dev_cert_len == 0) { + ESP_LOGE(TAG, "Failed to get device cert: %s", esp_err_to_name(ret)); + return; + } + + ds_ctx = esp_secure_cert_get_ds_ctx(); + if (ds_ctx == NULL) { + ESP_LOGE(TAG, "Failed to get DS context"); + esp_secure_cert_free_device_cert(dev_cert_buf); + return; + } + + size_t ca_cert_len = ca_cert_pem_end - ca_cert_pem_start; + esp_http_client_config_t config = { + .url = url, + .cert_pem = ca_cert_pem_start, + .cert_len = ca_cert_len, + .client_cert_pem = dev_cert_buf, + .client_cert_len = dev_cert_len, + .ds_data = ds_ctx, + .skip_cert_common_name_check = true, + .timeout_ms = 10000, + }; + + esp_http_client_handle_t client = esp_http_client_init(&config); + if (client == NULL) { + ESP_LOGE(TAG, "Failed to init HTTP client"); + goto cleanup; + } + + esp_err_t err = esp_http_client_perform(client); + int status = esp_http_client_get_status_code(client); + if (err == ESP_OK) { + ESP_LOGI(TAG, "HTTPS Mutual Auth Status = %d, content_length = %" PRId64, + status, esp_http_client_get_content_length(client)); + } else { + ESP_LOGE(TAG, "Request failed: %s", esp_err_to_name(err)); + } + + esp_http_client_cleanup(client); + +cleanup: + esp_secure_cert_free_ds_ctx(ds_ctx); + esp_secure_cert_free_device_cert(dev_cert_buf); +} + +#else /* Software key mode */ + +/** + * Software key mode: use embedded PEM client cert and key. + */ +static void mutual_auth_with_software_keys(const char *url) +{ + size_t ca_cert_len = ca_cert_pem_end - ca_cert_pem_start; + size_t client_cert_len = client_cert_pem_end - client_cert_pem_start; + size_t client_key_len = client_key_pem_end - client_key_pem_start; + + esp_http_client_config_t config = { + .url = url, + .cert_pem = ca_cert_pem_start, + .cert_len = ca_cert_len, + .client_cert_pem = client_cert_pem_start, + .client_cert_len = client_cert_len, + .client_key_pem = client_key_pem_start, + .client_key_len = client_key_len, + .skip_cert_common_name_check = true, + .timeout_ms = 10000, + }; + + esp_http_client_handle_t client = esp_http_client_init(&config); + if (client == NULL) { + ESP_LOGE(TAG, "Failed to init HTTP client"); + return; + } + + esp_err_t err = esp_http_client_perform(client); + int status = esp_http_client_get_status_code(client); + if (err == ESP_OK) { + ESP_LOGI(TAG, "HTTPS Mutual Auth Status = %d, content_length = %" PRId64, + status, esp_http_client_get_content_length(client)); + } else { + ESP_LOGE(TAG, "Request failed: %s", esp_err_to_name(err)); + } + + esp_http_client_cleanup(client); +} +#endif /* CONFIG_EXAMPLE_MUTUAL_AUTH_USE_HW_RSA_DS */ + +static void mutual_auth_task(void *pvParameters) +{ + char url_buf[URL_BUF_SIZE]; + if (read_url_from_stdin(url_buf, sizeof(url_buf)) != ESP_OK) { + goto done; + } + +#if CONFIG_EXAMPLE_MUTUAL_AUTH_USE_HW_RSA_DS + mutual_auth_with_ds(url_buf); +#else + mutual_auth_with_software_keys(url_buf); +#endif + +done: + ESP_LOGI(TAG, "Finish mutual auth example"); + vTaskDelete(NULL); +} + +void app_main(void) +{ + esp_err_t ret = nvs_flash_init(); + if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) { + ESP_ERROR_CHECK(nvs_flash_erase()); + ret = nvs_flash_init(); + } + ESP_ERROR_CHECK(ret); + + ESP_ERROR_CHECK(esp_netif_init()); + ESP_ERROR_CHECK(esp_event_loop_create_default()); + ESP_ERROR_CHECK(example_connect()); + + ESP_LOGI(TAG, "Connected, starting mutual auth example"); + xTaskCreate(mutual_auth_task, "mutual_auth", 8192, NULL, 5, NULL); +} diff --git a/examples/protocols/esp_http_client_mutual_auth/partitions_esp_secure_cert.csv b/examples/protocols/esp_http_client_mutual_auth/partitions_esp_secure_cert.csv new file mode 100644 index 00000000000..5afedd6fcc5 --- /dev/null +++ b/examples/protocols/esp_http_client_mutual_auth/partitions_esp_secure_cert.csv @@ -0,0 +1,8 @@ +# Partition table for DS test using esp_secure_cert partition. +# esp_secure_cert_mgr looks for type 0x3F (custom) and name "esp_secure_cert". +# Flash main/certs/esp_secure_cert_data/esp_secure_cert.bin to the esp_secure_cert partition. +# Name, Type, SubType, Offset, Size, Flags +nvs, data, nvs, , 0x6000, +phy_init, data, phy, , 0x1000, +esp_secure_cert, 0x3F, 0x00, , 0x4000, +factory, app, factory, , 0x100000, diff --git a/examples/protocols/esp_http_client_mutual_auth/pytest_esp_http_client_mutual_auth.py b/examples/protocols/esp_http_client_mutual_auth/pytest_esp_http_client_mutual_auth.py new file mode 100644 index 00000000000..8b49a6cd58b --- /dev/null +++ b/examples/protocols/esp_http_client_mutual_auth/pytest_esp_http_client_mutual_auth.py @@ -0,0 +1,230 @@ +# SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: Unlicense OR CC0-1.0 +import http.server +import logging +import multiprocessing +import os +import socket +import ssl +from typing import Any + +import pytest +from common_test_methods import get_host_ip4_by_dest_ip +from pytest_embedded_idf.utils import idf_parametrize +from pytest_embedded_qemu.app import QemuApp +from pytest_embedded_qemu.dut import QemuDut + +# --------------------------------------------------------------------------- +# mTLS HTTPS server (runs in a subprocess during tests) +# --------------------------------------------------------------------------- + + +def _https_mtls_request_handler() -> type[http.server.BaseHTTPRequestHandler]: + """Request handler for mutual TLS: responds 200 OK to GET, logs at INFO.""" + + class RequestHandler(http.server.BaseHTTPRequestHandler): + protocol_version = 'HTTP/1.1' + + def do_GET(self) -> None: + logging.info('[HTTPS server] GET from %s', self.address_string()) + self.send_response(200) + self.send_header('Content-Length', '0') + self.end_headers() + + def log_message(self, _format: str, *args: object) -> None: + logging.info( + '[HTTPS server] %s - - [%s] %s', self.address_string(), self.log_date_time_string(), _format % args + ) + + return RequestHandler + + +class _MTLSHTTPServer(http.server.HTTPServer): + """HTTPServer that wraps each connection with TLS and logs handshake success/failure.""" + + def __init__( + self, + server_address: tuple[str, int], + RequestHandlerClass: type[http.server.BaseHTTPRequestHandler], + ssl_context: ssl.SSLContext, + ) -> None: + super().__init__(server_address, RequestHandlerClass) + self.ssl_context = ssl_context + + def get_request(self) -> tuple[socket.socket, Any]: + conn, addr = self.socket.accept() + try: + wrapped = self.ssl_context.wrap_socket(conn, server_side=True) + logging.info('[HTTPS server] TLS handshake OK from %s:%s', addr[0], addr[1]) + return wrapped, addr + except ssl.SSLError as e: + logging.warning('[HTTPS server] TLS handshake failed from %s:%s: %s', addr[0], addr[1], e) + conn.close() + raise + + def server_bind(self) -> None: + self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + super().server_bind() + + +def start_https_server_mutual_tls( + server_cert: str, + server_key: str, + client_ca: str, + host: str, + port: int, +) -> None: + """ + Start an HTTPS server that requires and verifies client certificates (mutual TLS). + Uses server_cert/server_key for the server TLS identity and client_ca to verify the client cert. + """ + request_handler = _https_mtls_request_handler() + ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) + ssl_context.load_cert_chain(certfile=server_cert, keyfile=server_key) + ssl_context.verify_mode = ssl.CERT_REQUIRED + ssl_context.load_verify_locations(cafile=client_ca) + httpd = _MTLSHTTPServer((host, port), request_handler, ssl_context) + logging.info('[HTTPS server] Listening on %s:%s (mTLS)', host, port) + httpd.serve_forever() + + +# --------------------------------------------------------------------------- +# Paths and constants +# --------------------------------------------------------------------------- + +_EXAMPLE_DIR = os.path.dirname(__file__) +_CERTS_DIR = os.path.join(_EXAMPLE_DIR, 'main', 'certs') +_DS_DATA_DIR = os.path.join(_CERTS_DIR, 'esp_secure_cert_data') + +_QEMU_EFUSE_FILE = os.path.join(_DS_DATA_DIR, 'qemu_efuse.bin') +_HMAC_KEY_FILE = os.path.join(_DS_DATA_DIR, 'hmac_key.bin') +_ESP_SECURE_CERT_PARTITION = os.path.join(_DS_DATA_DIR, 'esp_secure_cert.bin') + +# qemu_extra_args must be a single string (plugin passes it to shlex.split). +_QEMU_EXTRA_DS = ( + f'-drive file={_QEMU_EFUSE_FILE},if=none,format=raw,id=efuse ' + '-global driver=nvram.esp32c3.efuse,property=drive,value=efuse ' + '-global driver=timer.esp32c3.timg,property=wdt_disable,value=true' +) + + +# --------------------------------------------------------------------------- +# DS eFuse and artifact helpers +# --------------------------------------------------------------------------- + + +def _ensure_ds_efuse_image() -> None: + """ + Verify DS artifacts (eFuse image, HMAC key, secure cert partition) are present + before QEMU starts. All three are committed under main/certs/esp_secure_cert_data/; + see the DS provisioning section in the example README to regenerate them. + """ + for path in (_ESP_SECURE_CERT_PARTITION, _HMAC_KEY_FILE, _QEMU_EFUSE_FILE): + if not os.path.isfile(path): + pytest.skip( + f'DS test requires {os.path.basename(path)}. ' + 'See the DS peripheral mode section in the example README for provisioning steps.' + ) + + +@pytest.fixture(autouse=True) +def _ensure_ds_efuse_before_qemu(request: pytest.FixtureRequest) -> None: + """Ensure eFuse image exists before QEMU starts for DS tests.""" + if 'qemu_ds' not in request.node.name: + return + _ensure_ds_efuse_image() + + +# --------------------------------------------------------------------------- +# Helper: start mTLS server and send URL to device +# --------------------------------------------------------------------------- + + +def _run_mtls_test( + dut: QemuDut, + server_port: int, + client_ca: str, + expect_success: bool, +) -> None: + """ + Start mTLS server, wait for device to request URL, send it, and verify outcome. + """ + server_cert = os.path.join(_CERTS_DIR, 'server_cert.pem') + server_key = os.path.join(_CERTS_DIR, 'server_key.pem') + + server_proc = multiprocessing.Process( + target=start_https_server_mutual_tls, + args=(server_cert, server_key, client_ca, '0.0.0.0', server_port), + ) + server_proc.daemon = True + server_proc.start() + logging.info('HTTPS server with mutual TLS started on port %s', server_port) + try: + ip_address = dut.expect(r'IPv4 address: (\d+\.\d+\.\d+\.\d+)', timeout=60)[1].decode() + host_ip = get_host_ip4_by_dest_ip(ip_address) + dut.expect('Enter mutual auth server URL:', timeout=30) + dut.write(f'https://{host_ip}:{server_port}\n') + + if expect_success: + dut.expect('HTTPS Mutual Auth Status = 200', timeout=30) + logging.info('mTLS test passed: status 200') + else: + dut.expect('mbedtls_ssl_handshake returned -0x7780', timeout=30) + dut.expect('Request failed:', timeout=30) + logging.info('Negative test passed: connection failed as expected') + + dut.expect('Finish mutual auth example', timeout=10) + finally: + server_proc.terminate() + server_proc.join(timeout=5) + if server_proc.is_alive(): + server_proc.kill() + + +# --------------------------------------------------------------------------- +# Test: software key mutual TLS +# --------------------------------------------------------------------------- + + +@pytest.mark.host_test +@pytest.mark.qemu +@pytest.mark.parametrize('config', ['default'], indirect=True) +@idf_parametrize('target', ['esp32c3'], indirect=['target']) +def test_mutual_auth_software_keys(app: QemuApp, dut: QemuDut) -> None: + """QEMU test: mutual TLS with embedded software client cert + key.""" + if os.environ.get('IDF_TOOLCHAIN') == 'clang': + pytest.skip('QEMU mTLS test not supported with clang toolchain (Docker SLIRP networking issue)') + client_ca = os.path.join(_CERTS_DIR, 'ca_cert.pem') + _run_mtls_test(dut, server_port=8070, client_ca=client_ca, expect_success=True) + + +# --------------------------------------------------------------------------- +# Tests: DS peripheral mutual TLS (migrated from esp_http_client) +# --------------------------------------------------------------------------- + + +@pytest.mark.host_test +@pytest.mark.qemu +@pytest.mark.parametrize('config', ['qemu_ds'], indirect=True) +@idf_parametrize('target', ['esp32c3'], indirect=['target']) +@pytest.mark.parametrize('qemu_extra_args', [_QEMU_EXTRA_DS], indirect=True) +def test_mutual_auth_ds(app: QemuApp, dut: QemuDut) -> None: + """QEMU + DS peripheral test: mTLS using DS peripheral for client key.""" + if os.environ.get('IDF_TOOLCHAIN') == 'clang': + pytest.skip('DS QEMU test not supported with clang toolchain (Docker SLIRP networking issue)') + client_ca = os.path.join(_CERTS_DIR, 'ca_cert.pem') + _run_mtls_test(dut, server_port=8070, client_ca=client_ca, expect_success=True) + + +@pytest.mark.host_test +@pytest.mark.qemu +@pytest.mark.parametrize('config', ['qemu_ds'], indirect=True) +@idf_parametrize('target', ['esp32c3'], indirect=['target']) +@pytest.mark.parametrize('qemu_extra_args', [_QEMU_EXTRA_DS], indirect=True) +def test_mutual_auth_ds_fails_wrong_credentials(app: QemuApp, dut: QemuDut) -> None: + """Negative test: server uses wrong CA, rejects device client cert.""" + if os.environ.get('IDF_TOOLCHAIN') == 'clang': + pytest.skip('DS QEMU test not supported with clang toolchain (Docker SLIRP networking issue)') + # Wrong CA: server uses server_cert as client CA (won't verify our client cert) + wrong_ca = os.path.join(_CERTS_DIR, 'server_cert.pem') + _run_mtls_test(dut, server_port=8071, client_ca=wrong_ca, expect_success=False) diff --git a/examples/protocols/esp_http_client_mutual_auth/sdkconfig.ci.default b/examples/protocols/esp_http_client_mutual_auth/sdkconfig.ci.default new file mode 100644 index 00000000000..e4509cb5601 --- /dev/null +++ b/examples/protocols/esp_http_client_mutual_auth/sdkconfig.ci.default @@ -0,0 +1,5 @@ +# Software key mutual TLS test over QEMU ethernet +CONFIG_EXAMPLE_CONNECT_WIFI=n +CONFIG_ETHERNET_SPI_SUPPORT=n +CONFIG_ETHERNET_OPENETH_SUPPORT=y +CONFIG_MBEDTLS_TLS_CLIENT_ONLY=y diff --git a/examples/protocols/esp_http_client_mutual_auth/sdkconfig.ci.qemu_ds b/examples/protocols/esp_http_client_mutual_auth/sdkconfig.ci.qemu_ds new file mode 100644 index 00000000000..5f13c929ab9 --- /dev/null +++ b/examples/protocols/esp_http_client_mutual_auth/sdkconfig.ci.qemu_ds @@ -0,0 +1,8 @@ +# DS peripheral mutual TLS test over QEMU ethernet +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions_esp_secure_cert.csv" +CONFIG_EXAMPLE_MUTUAL_AUTH_USE_HW_RSA_DS=y +CONFIG_EXAMPLE_CONNECT_WIFI=n +CONFIG_ETHERNET_SPI_SUPPORT=n +CONFIG_ETHERNET_OPENETH_SUPPORT=y +CONFIG_MBEDTLS_TLS_CLIENT_ONLY=y diff --git a/examples/protocols/esp_http_client_mutual_auth/sdkconfig.defaults b/examples/protocols/esp_http_client_mutual_auth/sdkconfig.defaults new file mode 100644 index 00000000000..5430aef3bca --- /dev/null +++ b/examples/protocols/esp_http_client_mutual_auth/sdkconfig.defaults @@ -0,0 +1 @@ +CONFIG_MBEDTLS_TLS_CLIENT_ONLY=y diff --git a/examples/protocols/http_request/pytest_http_request.py b/examples/protocols/http_request/pytest_http_request.py index b2a871e9830..7d6c7aa0c1a 100644 --- a/examples/protocols/http_request/pytest_http_request.py +++ b/examples/protocols/http_request/pytest_http_request.py @@ -22,7 +22,7 @@ def test_examples_protocol_http_request(dut: Dut) -> None: # check and log bin size binary_file = os.path.join(dut.app.binary_path, 'http_request.bin') bin_size = os.path.getsize(binary_file) - logging.info('http_request_bin_size : {}KB'.format(bin_size // 1024)) + logging.info(f'http_request_bin_size : {bin_size // 1024}KB') # start test dut.expect(r'DNS lookup succeeded.', timeout=30) # check if connected or not @@ -30,7 +30,7 @@ def test_examples_protocol_http_request(dut: Dut) -> None: dut.expect(' ... socket send success') dut.expect(' ... set socket receiving timeout success') # check server response - dut.expect(r'HTTP/1.0 200 OK') + dut.expect(r'HTTP/1.1 200 OK') # read from the socket completed dut.expect('... done reading from socket. Last read return=0 errno=128') dut.expect(r'(\d)...') diff --git a/examples/protocols/http_server/restful_server/front/web-demo/package.json b/examples/protocols/http_server/restful_server/front/web-demo/package.json index f9a24b80a39..691eef9c997 100644 --- a/examples/protocols/http_server/restful_server/front/web-demo/package.json +++ b/examples/protocols/http_server/restful_server/front/web-demo/package.json @@ -27,7 +27,7 @@ "unplugin-fonts": "~1.4.0", "unplugin-vue-components": "~30.0.0", "unplugin-vue-router": "~0.16.0", - "vite": "~7.1.11", + "vite": "~7.3.2", "vite-plugin-vue-layouts-next": "~1.0.0", "vite-plugin-vuetify": "~2.1.2", "vue-router": "~4.6.3" diff --git a/examples/protocols/https_request/pytest_https_request.py b/examples/protocols/https_request/pytest_https_request.py index c4dfc541323..c4a3c75f6f5 100644 --- a/examples/protocols/https_request/pytest_https_request.py +++ b/examples/protocols/https_request/pytest_https_request.py @@ -389,6 +389,7 @@ def test_examples_protocol_https_request(dut: Dut) -> None: @pytest.mark.wifi_ap +@pytest.mark.esp32c2_rev2 @pytest.mark.xtal_26mhz @pytest.mark.parametrize( 'config, baud', @@ -399,6 +400,7 @@ def test_examples_protocol_https_request(dut: Dut) -> None: ) @idf_parametrize('target', ['esp32c2'], indirect=['target']) def test_examples_protocol_https_request_rom_impl(dut: Dut) -> None: + write_time_to_nvs(dut) # Connect to AP if dut.app.sdkconfig.get('EXAMPLE_WIFI_SSID_PWD_FROM_STDIN') is True: dut.expect('Please input ssid password:') diff --git a/examples/protocols/https_request/sdkconfig.ci.esp32c2_rom_mbedtls b/examples/protocols/https_request/sdkconfig.ci.esp32c2_rom_mbedtls index 04000befa49..73f8dcc658d 100644 --- a/examples/protocols/https_request/sdkconfig.ci.esp32c2_rom_mbedtls +++ b/examples/protocols/https_request/sdkconfig.ci.esp32c2_rom_mbedtls @@ -1,6 +1,6 @@ CONFIG_IDF_TARGET="esp32c2" CONFIG_XTAL_FREQ_26=y +CONFIG_ESP32C2_REV_MIN_200=y CONFIG_EXAMPLE_CONNECT_WIFI=y CONFIG_EXAMPLE_WIFI_SSID_PWD_FROM_STDIN=y -# TODO: IDF-15012 -CONFIG_MBEDTLS_USE_CRYPTO_ROM_IMPL=n +CONFIG_MBEDTLS_USE_CRYPTO_ROM_IMPL=y diff --git a/examples/security/.build-test-rules.yml b/examples/security/.build-test-rules.yml index 3657f732b6e..fd3da7181ec 100644 --- a/examples/security/.build-test-rules.yml +++ b/examples/security/.build-test-rules.yml @@ -60,7 +60,6 @@ examples/security/tee/tee_attestation: disable: - if: IDF_TARGET not in ["esp32c6", "esp32c61"] depends_components: - - *common_components - esp_tee depends_filepatterns: - examples/security/tee/tee_attestation/**/* @@ -69,7 +68,6 @@ examples/security/tee/tee_basic: disable: - if: IDF_TARGET not in ["esp32c6", "esp32c61"] depends_components: - - *common_components - esp_tee depends_filepatterns: - examples/security/tee/tee_basic/**/* @@ -78,7 +76,6 @@ examples/security/tee/tee_secure_ota: disable: - if: IDF_TARGET not in ["esp32c6", "esp32c61"] depends_components: - - *common_components - esp_tee - protocol_examples_common depends_filepatterns: @@ -88,7 +85,6 @@ examples/security/tee/tee_secure_storage: disable: - if: IDF_TARGET not in ["esp32c6", "esp32c61"] depends_components: - - *common_components - esp_tee depends_filepatterns: - examples/security/tee/tee_secure_storage/**/* diff --git a/examples/security/tee/tee_attestation/README.md b/examples/security/tee/tee_attestation/README.md index 72b87ce690a..5b2dd6bfd48 100644 --- a/examples/security/tee/tee_attestation/README.md +++ b/examples/security/tee/tee_attestation/README.md @@ -28,7 +28,12 @@ "eat": { "auth_challenge": "dcb9b53143ad6b081dad1a05c7ebda4e314d388762215799cf24ed52e9387678", "client_id": 262974944, + "chip_id": 13, "device_ver": 1, + "ueid": { + "mac": "d885ac67c978", + "optional_id": "94fa4d7e305682714d48e7bbd710c961" + }, "device_id": "e8cddb2a7f9a5a7c61735d6dda26e4bd153c6d772a9be6f26bd321dfe25e0ac8", "instance_id": "1adba85e0df997fd961f25a9e312430cef162b5c69466cd5b172f1e65ac7360c", "psa_cert_ref": "0716053550477-10100", @@ -127,9 +132,9 @@ See the Getting Started Guide for full steps to configure and use ESP-IDF to bui ```log I (438) example_tee_attest: TEE Attestation Service -I (1008) example_tee_attest: Attestation token - Length: 1538 +I (1008) example_tee_attest: Attestation token - Length: 1705 I (1018) example_tee_attest: Attestation token - Data: -'{"header":{"magic":"44fef7cc","encr_alg":"","sign_alg":"ecdsa_secp256r1_sha256","key_id":"tee_att_key0"},"eat":{"nonce":-1582119980,"client_id":262974944,"device_ver":1,"device_id":"4ecc458ef4290329552b4dcdccb99d55e5ea7624f24c87b27b71515e1666f39c","instance_id":"66571b78918f4bb7ae2723f235a9e4fe1c7070ae6261ce5df7049b44b1f8a318","psa_cert_ref":"0716053550477-10100","device_status":165,"sw_claims":{"tee":{"type":1,"ver":"1.0.0","idf_ver":"v5.5-dev-2978-gd75a0105dac-dirt","secure_ver":0,"part_chip_rev":{"min":0,"max":99},"part_digest":{"type":0,"calc_digest":"5213904fd8ca7538776bdf372c08c13138f20b2fac3503bc878f19c6e36a710d","digest_validated":true,"sign_verified":false,"secure_padding":false}},"app":{"type":2,"ver":"v0.1.0","idf_ver":"v5.5-dev-2978-gd75a0105dac-dirt","secure_ver":0,"part_chip_rev":{"min":0,"max":99},"part_digest":{"type":0,"calc_digest":"65c905fc0fc135fdfa8def210d1c186627cb3a17ecb2e7f020b56411b2d2fc76","digest_validated":true,"sign_verified":false,"secure_padding":false}},"bootloader":{"type":0,"ver":"01000000","idf_ver":"v5.5-dev-2978-gd75a0105dac-dirt","secure_ver":0,"part_chip_rev":{"min":0,"max":99},"part_digest":{"type":0,"calc_digest":"9efd37d29266f3239f7c6a095df880f1e85e41505f154cfd3bbfad4b8a2b18dd","digest_validated":true,"sign_verified":false}}}},"public_key":{"compressed":"02ce0188c61b0118c86ca20af7e01185dd687c6698b2265a288fee845d083e9066"},"sign":{"r":"362e2053bab26c779559793b2eae89e96c1a058e5fffc49d544d07b934ce3b32","s":"fc5f0e4d329fc6e031cbf425ef62d4756b728392b2a77282baa1f15b554d2716"}}' +'{"header":{"magic":"44fef7cc","encr_alg":"","sign_alg":"ecdsa_secp256r1_sha256","key_id":"tee_att_key0"},"eat":{"nonce":-1582119980,"client_id":262974944,"chip_id":13,"device_ver":1,"ueid":{"mac":"d885ac67c978","optional_id":"94fa4d7e305682714d48e7bbd710c961"},"device_id":"4ecc458ef4290329552b4dcdccb99d55e5ea7624f24c87b27b71515e1666f39c","instance_id":"66571b78918f4bb7ae2723f235a9e4fe1c7070ae6261ce5df7049b44b1f8a318","psa_cert_ref":"0716053550477-10100","device_status":165,"sw_claims":{"tee":{"type":1,"ver":"1.0.0","idf_ver":"v5.5-dev-2978-gd75a0105dac-dirt","secure_ver":0,"part_chip_rev":{"min":0,"max":99},"part_digest":{"type":0,"calc_digest":"5213904fd8ca7538776bdf372c08c13138f20b2fac3503bc878f19c6e36a710d","digest_validated":true,"sign_verified":false,"secure_padding":false}},"app":{"type":2,"ver":"v0.1.0","idf_ver":"v5.5-dev-2978-gd75a0105dac-dirt","secure_ver":0,"part_chip_rev":{"min":0,"max":99},"part_digest":{"type":0,"calc_digest":"65c905fc0fc135fdfa8def210d1c186627cb3a17ecb2e7f020b56411b2d2fc76","digest_validated":true,"sign_verified":false,"secure_padding":false}},"bootloader":{"type":0,"ver":"01000000","idf_ver":"v5.5-dev-2978-gd75a0105dac-dirt","secure_ver":0,"part_chip_rev":{"min":0,"max":99},"part_digest":{"type":0,"calc_digest":"9efd37d29266f3239f7c6a095df880f1e85e41505f154cfd3bbfad4b8a2b18dd","digest_validated":true,"sign_verified":false}}}},"public_key":{"compressed":"02ce0188c61b0118c86ca20af7e01185dd687c6698b2265a288fee845d083e9066"},"sign":{"r":"362e2053bab26c779559793b2eae89e96c1a058e5fffc49d544d07b934ce3b32","s":"fc5f0e4d329fc6e031cbf425ef62d4756b728392b2a77282baa1f15b554d2716"}}' I (1148) main_task: Returned from app_main() ``` diff --git a/examples/storage/nvs/.build-test-rules.yml b/examples/storage/nvs/.build-test-rules.yml index 24f56e4dae4..4ff9b1bb989 100644 --- a/examples/storage/nvs/.build-test-rules.yml +++ b/examples/storage/nvs/.build-test-rules.yml @@ -9,9 +9,6 @@ examples/storage/nvs/nvs_bootloader: - if: CONFIG_NAME == "nvs_enc_flash_enc" and (SOC_AES_SUPPORTED != 1 and ESP_ROM_HAS_MBEDTLS_CRYPTO_LIB != 1) - if: CONFIG_NAME == "nvs_enc_hmac" and (SOC_HMAC_SUPPORTED != 1 or (SOC_HMAC_SUPPORTED == 1 and (SOC_AES_SUPPORTED != 1 and ESP_ROM_HAS_MBEDTLS_CRYPTO_LIB != 1))) reason: As of now in such cases, we do not have any way to perform AES operations in the bootloader build - # TODO: IDF-15012 - - if: IDF_TARGET in ["esp32c2"] - reason: PSA is not yet available for ESP32-C2 examples/storage/nvs/nvs_console: depends_components: diff --git a/examples/storage/nvs/nvs_bootloader/README.md b/examples/storage/nvs/nvs_bootloader/README.md index 50d209cd546..8d1f7dda6b1 100644 --- a/examples/storage/nvs/nvs_bootloader/README.md +++ b/examples/storage/nvs/nvs_bootloader/README.md @@ -1,5 +1,5 @@ -| Supported Targets | ESP32 | ESP32-C3 | ESP32-C5 | ESP32-C6 | ESP32-C61 | ESP32-H2 | ESP32-H21 | ESP32-H4 | ESP32-P4 | ESP32-S2 | ESP32-S3 | -| ----------------- | ----- | -------- | -------- | -------- | --------- | -------- | --------- | -------- | -------- | -------- | -------- | +| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C5 | ESP32-C6 | ESP32-C61 | ESP32-H2 | ESP32-H21 | ESP32-H4 | ESP32-P4 | ESP32-S2 | ESP32-S3 | +| ----------------- | ----- | -------- | -------- | -------- | -------- | --------- | -------- | --------- | -------- | -------- | -------- | -------- | # NVS Bootloader diff --git a/examples/storage/nvs/nvs_bootloader/pytest_nvs_bootloader.py b/examples/storage/nvs/nvs_bootloader/pytest_nvs_bootloader.py index fadfa1e1a9f..ca904169b51 100644 --- a/examples/storage/nvs/nvs_bootloader/pytest_nvs_bootloader.py +++ b/examples/storage/nvs/nvs_bootloader/pytest_nvs_bootloader.py @@ -6,6 +6,7 @@ from pytest_embedded_idf.utils import idf_parametrize @pytest.mark.generic +@pytest.mark.flaky(reruns=2, reruns_delay=5) @idf_parametrize('target', ['supported_targets'], indirect=['target']) def test_nvs_bootloader_example(dut: Dut) -> None: # Full bootloader hook logs must be visible before app output. On some targets (e.g. ESP32-H2) @@ -26,6 +27,7 @@ def test_nvs_bootloader_example(dut: Dut) -> None: @pytest.mark.nvs_encr_hmac +@pytest.mark.flaky(reruns=2, reruns_delay=5) @pytest.mark.parametrize('config', ['nvs_enc_hmac'], indirect=True) @idf_parametrize('target', ['esp32c3'], indirect=['target']) def test_nvs_bootloader_example_nvs_encr_hmac(dut: Dut) -> None: @@ -33,6 +35,7 @@ def test_nvs_bootloader_example_nvs_encr_hmac(dut: Dut) -> None: @pytest.mark.flash_encryption +@pytest.mark.flaky(reruns=2, reruns_delay=5) @pytest.mark.parametrize('config', ['nvs_enc_flash_enc'], indirect=True) @idf_parametrize('target', ['esp32', 'esp32c3'], indirect=['target']) def test_nvs_bootloader_example_flash_enc(dut: Dut) -> None: diff --git a/examples/storage/nvs/nvs_bootloader/sdkconfig.defaults.esp32c2 b/examples/storage/nvs/nvs_bootloader/sdkconfig.defaults.esp32c2 index 5e3a3c88f46..6b1f01f17c8 100644 --- a/examples/storage/nvs/nvs_bootloader/sdkconfig.defaults.esp32c2 +++ b/examples/storage/nvs/nvs_bootloader/sdkconfig.defaults.esp32c2 @@ -1,2 +1,3 @@ CONFIG_IDF_TARGET="esp32c2" +CONFIG_ESP32C2_REV_MIN_200=y CONFIG_MBEDTLS_USE_CRYPTO_ROM_IMPL_BOOTLOADER=y diff --git a/examples/storage/perf_benchmark/main/perf_benchmark_example_tests.c b/examples/storage/perf_benchmark/main/perf_benchmark_example_tests.c index c28bce81f20..c36c3b0842e 100644 --- a/examples/storage/perf_benchmark/main/perf_benchmark_example_tests.c +++ b/examples/storage/perf_benchmark/main/perf_benchmark_example_tests.c @@ -41,10 +41,13 @@ static void print_results(const char *name, double time, size_t size, int repeat_count) { +/* Suppress benchmark printf in CI (CONFIG_IDF_CI_BUILD, IDF_CI_BUILD=1) to avoid UART flooding. */ +#if !CONFIG_IDF_CI_BUILD double average = time / repeat_count; double speed = (size / average) / (1024 * 1024) * (1000 * 1000); printf("[%-55s] (%dx) %8.3f ms %8.2f k" UNIT_STRING " %10.3f M" UNIT_STRING "/s\n", name, repeat_count, (float) average / 1000, (float)size * UNIT_MULTIPLIER / 1024, speed); +#endif } void spiflash_speed_test_raw_run(size_t repeat_count) diff --git a/examples/storage/semihost_vfs/README.md b/examples/storage/semihost_vfs/README.md index 88e322224c7..73f8bd70dd6 100644 --- a/examples/storage/semihost_vfs/README.md +++ b/examples/storage/semihost_vfs/README.md @@ -73,8 +73,6 @@ openocd -c "set ESP_SEMIHOST_BASEDIR %IDF_PATH%/examples/storage/semihost_vfs/da The above command will set `ESP_SEMIHOST_BASEDIR` variable to `examples/storage/semihost_vfs/data` subdirectory of ESP-IDF. With that, it is not necessary to run OpenOCD from that specific directory. -> Note: This feature is not available for RISC-V based SoCs (ESP32-C3, ESP32-H2). To set the semihosting base directory, change into the required directory before running `openocd` command. - ## Example output There are two outputs produced by example: diff --git a/examples/system/.build-test-rules.yml b/examples/system/.build-test-rules.yml index 46de6e7ebc2..a1993e0256c 100644 --- a/examples/system/.build-test-rules.yml +++ b/examples/system/.build-test-rules.yml @@ -68,6 +68,21 @@ examples/system/esp_timer: - *common_components - esp_timer +examples/system/esp_trace: + disable: + - if: SOC_USB_SERIAL_JTAG_SUPPORTED != 1 + reason: example transport is USB Serial JTAG + disable_test: + - if: IDF_TARGET == "esp32h21" + temporary: true + reason: lack of runners + - if: IDF_TARGET == "esp32h4" + temporary: true + reason: lack of runners + depends_components: + - esp_trace + - freertos + examples/system/eventfd: disable: - if: SOC_GPTIMER_SUPPORTED != 1 and (IDF_TARGET != "esp32" and (NIGHTLY_RUN != "1" or IDF_TARGET == "linux")) diff --git a/examples/system/deep_sleep/README.md b/examples/system/deep_sleep/README.md index aafbbda2fe9..da637e2bba6 100644 --- a/examples/system/deep_sleep/README.md +++ b/examples/system/deep_sleep/README.md @@ -11,7 +11,7 @@ The following wake up sources are demonstrated in this example (refer to the [Wa - **Timer:** An RTC timer that can be programmed to trigger a wake up after a preset time. This example will trigger a wake up every 20 seconds. - **EXT0:** External wake up 0 can trigger wakeup when one predefined RTC GPIO is at a predefined logic level. This example uses GPIO25 in ESP32 or GPIO3 in ESP32-S2/S3 to trigger a wake up when the pin is HIGH. (This wake up source is only available on ESP32, ESP32-S2, and ESP32-S3.) -- **EXT1:** External wake up 1 which is tied to multiple RTC GPIOs. This example uses GPIO2 and GPIO4 to trigger a wake up with any one of the two pins are HIGH. (This wake up source is available on ESP32, ESP32-S2, ESP32-S3, ESP32-C6 and ESP32-H2.) +- **EXT1:** External wake up 1 which is tied to multiple RTC GPIOs. This example uses GPIO2 and GPIO4 to trigger a wake up with any one of the two pins are HIGH. (Available on targets that define `SOC_PM_SUPPORT_EXT1_WAKEUP`, pin set depends on the chip—see the programming guide and datasheet.) - **GPIO:** Pads powered by VDD3P3_RTC can be used to trigger a wake up from deep sleep. You may choose the pin and trigger level in menuconfig. (This wake up source is unavailable on ESP32, ESP32-S2, ESP32-S3 and ESP32-H2.) > [!NOTE] diff --git a/examples/system/efuse/sdkconfig.ci.virt_sb_v2_and_fe.esp32c5 b/examples/system/efuse/sdkconfig.ci.virt_sb_v2_and_fe.esp32c5 index 9616643ab51..22226e8fdb6 100644 --- a/examples/system/efuse/sdkconfig.ci.virt_sb_v2_and_fe.esp32c5 +++ b/examples/system/efuse/sdkconfig.ci.virt_sb_v2_and_fe.esp32c5 @@ -2,12 +2,15 @@ CONFIG_IDF_TARGET="esp32c5" -CONFIG_PARTITION_TABLE_OFFSET=0xE000 +CONFIG_PARTITION_TABLE_OFFSET=0xF000 CONFIG_PARTITION_TABLE_CUSTOM=y CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="test/partitions_efuse_emul.csv" CONFIG_SECURE_BOOT=y CONFIG_SECURE_BOOT_V2_ENABLED=y +# ECDSA Secure Boot V2 is gated behind the insecure option on the affected SoCs +CONFIG_SECURE_BOOT_INSECURE=y +CONFIG_SECURE_BOOT_V2_FORCE_ENABLE_ECDSA=y CONFIG_SECURE_SIGNED_APPS_ECDSA_V2_SCHEME=y CONFIG_SECURE_BOOT_SIGNING_KEY="test/secure_boot_signing_key_ecdsa_nistp256.pem" CONFIG_SECURE_ENABLE_SECURE_ROM_DL_MODE=y diff --git a/examples/system/efuse/sdkconfig.ci.virt_sb_v2_and_fe.esp32c61 b/examples/system/efuse/sdkconfig.ci.virt_sb_v2_and_fe.esp32c61 index f90b7d9c333..468c9477ada 100644 --- a/examples/system/efuse/sdkconfig.ci.virt_sb_v2_and_fe.esp32c61 +++ b/examples/system/efuse/sdkconfig.ci.virt_sb_v2_and_fe.esp32c61 @@ -8,6 +8,9 @@ CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="test/partitions_efuse_emul.csv" CONFIG_SECURE_BOOT=y CONFIG_SECURE_BOOT_V2_ENABLED=y +# ECDSA based Secure Boot V2 is not recommended on ESP32-C61 and must be force-enabled. +CONFIG_SECURE_BOOT_INSECURE=y +CONFIG_SECURE_BOOT_V2_FORCE_ENABLE_ECDSA=y CONFIG_SECURE_SIGNED_APPS_ECDSA_V2_SCHEME=y CONFIG_SECURE_BOOT_SIGNING_KEY="test/secure_boot_signing_key_ecdsa_nistp256.pem" CONFIG_SECURE_ENABLE_SECURE_ROM_DL_MODE=y diff --git a/examples/system/efuse/sdkconfig.ci.virt_sb_v2_ecdsa_p384_and_fe.esp32c5 b/examples/system/efuse/sdkconfig.ci.virt_sb_v2_ecdsa_p384_and_fe.esp32c5 index 0751e9770c5..71d9d0c0fc7 100644 --- a/examples/system/efuse/sdkconfig.ci.virt_sb_v2_ecdsa_p384_and_fe.esp32c5 +++ b/examples/system/efuse/sdkconfig.ci.virt_sb_v2_ecdsa_p384_and_fe.esp32c5 @@ -2,12 +2,15 @@ CONFIG_IDF_TARGET="esp32c5" -CONFIG_PARTITION_TABLE_OFFSET=0xE000 +CONFIG_PARTITION_TABLE_OFFSET=0xF000 CONFIG_PARTITION_TABLE_CUSTOM=y CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="test/partitions_efuse_emul.csv" CONFIG_SECURE_BOOT=y CONFIG_SECURE_BOOT_V2_ENABLED=y +# ECDSA Secure Boot V2 is gated behind the insecure option on the affected SoCs +CONFIG_SECURE_BOOT_INSECURE=y +CONFIG_SECURE_BOOT_V2_FORCE_ENABLE_ECDSA=y CONFIG_SECURE_SIGNED_APPS_ECDSA_V2_SCHEME=y CONFIG_SECURE_BOOT_ECDSA_KEY_LEN_384_BITS=y CONFIG_SECURE_BOOT_SIGNING_KEY="test/secure_boot_signing_key_ecdsa_nistp384.pem" diff --git a/examples/system/efuse/sdkconfig.ci.virt_secure_boot_v2.esp32c5 b/examples/system/efuse/sdkconfig.ci.virt_secure_boot_v2.esp32c5 index 910a3c5754a..9a5317f9e20 100644 --- a/examples/system/efuse/sdkconfig.ci.virt_secure_boot_v2.esp32c5 +++ b/examples/system/efuse/sdkconfig.ci.virt_secure_boot_v2.esp32c5 @@ -2,12 +2,15 @@ CONFIG_IDF_TARGET="esp32c5" -CONFIG_PARTITION_TABLE_OFFSET=0xD000 +CONFIG_PARTITION_TABLE_OFFSET=0xE000 CONFIG_PARTITION_TABLE_CUSTOM=y CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="test/partitions_efuse_emul.csv" CONFIG_SECURE_BOOT=y CONFIG_SECURE_BOOT_V2_ENABLED=y +# ECDSA Secure Boot V2 is gated behind the insecure option on the affected SoCs +CONFIG_SECURE_BOOT_INSECURE=y +CONFIG_SECURE_BOOT_V2_FORCE_ENABLE_ECDSA=y CONFIG_SECURE_SIGNED_APPS_ECDSA_V2_SCHEME=y CONFIG_SECURE_BOOT_SIGNING_KEY="test/secure_boot_signing_key_ecdsa_nistp256.pem" CONFIG_SECURE_INSECURE_ALLOW_DL_MODE=y diff --git a/examples/system/efuse/sdkconfig.ci.virt_secure_boot_v2.esp32c61 b/examples/system/efuse/sdkconfig.ci.virt_secure_boot_v2.esp32c61 index 6c57b5baec0..f0ada0868be 100644 --- a/examples/system/efuse/sdkconfig.ci.virt_secure_boot_v2.esp32c61 +++ b/examples/system/efuse/sdkconfig.ci.virt_secure_boot_v2.esp32c61 @@ -8,6 +8,9 @@ CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="test/partitions_efuse_emul.csv" CONFIG_SECURE_BOOT=y CONFIG_SECURE_BOOT_V2_ENABLED=y +# ECDSA based Secure Boot V2 is not recommended on ESP32-C61 and must be force-enabled. +CONFIG_SECURE_BOOT_INSECURE=y +CONFIG_SECURE_BOOT_V2_FORCE_ENABLE_ECDSA=y CONFIG_SECURE_SIGNED_APPS_ECDSA_V2_SCHEME=y CONFIG_SECURE_BOOT_SIGNING_KEY="test/secure_boot_signing_key_ecdsa_nistp256.pem" CONFIG_SECURE_INSECURE_ALLOW_DL_MODE=y diff --git a/examples/system/efuse/sdkconfig.ci.virt_secure_boot_v2.esp32p4 b/examples/system/efuse/sdkconfig.ci.virt_secure_boot_v2.esp32p4 index 5305d602c62..1b83c3d5946 100644 --- a/examples/system/efuse/sdkconfig.ci.virt_secure_boot_v2.esp32p4 +++ b/examples/system/efuse/sdkconfig.ci.virt_secure_boot_v2.esp32p4 @@ -2,7 +2,7 @@ CONFIG_IDF_TARGET="esp32p4" -CONFIG_PARTITION_TABLE_OFFSET=0xD000 +CONFIG_PARTITION_TABLE_OFFSET=0XE000 CONFIG_PARTITION_TABLE_CUSTOM=y CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="test/partitions_efuse_emul.csv" diff --git a/examples/system/efuse/sdkconfig.ci.virt_secure_boot_v2_ecdsa_p384.esp32c5 b/examples/system/efuse/sdkconfig.ci.virt_secure_boot_v2_ecdsa_p384.esp32c5 index 9264a22b99e..7096272a85d 100644 --- a/examples/system/efuse/sdkconfig.ci.virt_secure_boot_v2_ecdsa_p384.esp32c5 +++ b/examples/system/efuse/sdkconfig.ci.virt_secure_boot_v2_ecdsa_p384.esp32c5 @@ -8,6 +8,9 @@ CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="test/partitions_efuse_emul.csv" CONFIG_SECURE_BOOT=y CONFIG_SECURE_BOOT_V2_ENABLED=y +# ECDSA Secure Boot V2 is gated behind the insecure option on the affected SoCs +CONFIG_SECURE_BOOT_INSECURE=y +CONFIG_SECURE_BOOT_V2_FORCE_ENABLE_ECDSA=y CONFIG_SECURE_SIGNED_APPS_ECDSA_V2_SCHEME=y CONFIG_SECURE_BOOT_ECDSA_KEY_LEN_384_BITS=y CONFIG_SECURE_BOOT_SIGNING_KEY="test/secure_boot_signing_key_ecdsa_nistp384.pem" diff --git a/examples/system/esp_trace/CMakeLists.txt b/examples/system/esp_trace/CMakeLists.txt new file mode 100644 index 00000000000..dd56d133980 --- /dev/null +++ b/examples/system/esp_trace/CMakeLists.txt @@ -0,0 +1,8 @@ +# The following lines of boilerplate have to be in your project's +# CMakeLists in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.16) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +# "Trim" the build. Include the minimal set of components, main, and anything it depends on. +idf_build_set_property(MINIMAL_BUILD ON) +project(esp_trace_example) diff --git a/examples/system/esp_trace/README.md b/examples/system/esp_trace/README.md new file mode 100644 index 00000000000..aca36972975 --- /dev/null +++ b/examples/system/esp_trace/README.md @@ -0,0 +1,207 @@ +| Supported Targets | ESP32-C3 | ESP32-C5 | ESP32-C6 | ESP32-C61 | ESP32-H2 | ESP32-H21 | ESP32-H4 | ESP32-P4 | ESP32-S3 | +| ----------------- | -------- | -------- | -------- | --------- | -------- | --------- | -------- | -------- | -------- | + +# ESP Trace External Library Integration Example + +This example shows the **minimal** set of files and configuration needed to plug a third-party trace library into the [`esp_trace`](../../../components/esp_trace) component using the public `CONFIG_ESP_TRACE_LIB_EXTERNAL` extension point. It is meant as a copy-paste starting point for vendors and users who want to integrate their own trace recorder (e.g. Percepio TraceRecorder, a custom CTF emitter, a printf-style logger, …) without patching ESP-IDF itself. + +The example covers: + +* How to expose a custom **encoder** to `esp_trace` via `ESP_TRACE_REGISTER_ENCODER()`. +* How to provide an **`esp_trace_freertos_impl.h`** header that injects your trace hooks into FreeRTOS without breaking the `FreeRTOSConfig.h` include chain. +* How to wire everything up in **CMake** so the registration is not stripped by the linker, and so your header is visible to the FreeRTOS kernel. +* How to override the trace session parameters from the application via **`esp_trace_get_user_params()`**. + +## How to Use + +### Hardware Required + +By default this example targets devices with built-in USB Serial JTAG (ESP32-C3/C5/C6/C61/H2/P4/S3, …). For other transports, see [Changing the Transport](#changing-the-transport). + +You only need a development board and a USB cable. + +### Configure the Project + +``` +idf.py set-target +idf.py menuconfig +``` + +The defaults in [`sdkconfig.defaults`](sdkconfig.defaults) already enable everything the example needs: + +```ini +CONFIG_ESP_TRACE_ENABLE=y +CONFIG_ESP_TRACE_LIB_EXTERNAL=y # use an external encoder +CONFIG_ESP_TRACE_TRANSPORT_USB_SERIAL_JTAG=y # transport: built-in USB-Serial-JTAG +CONFIG_ESP_TRACE_TS_SOURCE_ESP_TIMER=y # timestamp source +CONFIG_ESP_CONSOLE_SECONDARY_NONE=y # free up USB-Serial-JTAG for trace +``` + +`CONFIG_ESP_CONSOLE_SECONDARY_NONE=y` is required so the USB-Serial-JTAG peripheral is not claimed by the secondary console — otherwise the transport option is not selectable. + +### Build, Flash, and Monitor + +``` +idf.py -p PORT flash monitor +``` + +You should see the app start up and create a task. Whatever trace bytes your encoder produces will be emitted over the configured transport — in this example, the encoder writes a short string to the transport on every task switch. + +(To exit the serial monitor, type `Ctrl-]`.) + +## Project Layout + +``` +esp_trace/ +├── CMakeLists.txt +├── sdkconfig.defaults +├── main/ +│ ├── CMakeLists.txt +│ └── app_main.c # overrides esp_trace_get_user_params() +└── components/ + └── ext_trace_lib/ # the external trace library component + ├── CMakeLists.txt # WHOLE_ARCHIVE + freertos include trick + ├── include/ + │ ├── esp_trace_freertos_impl.h # entry point pulled in by FreeRTOSConfig.h + │ └── trace_FreeRTOS.h # trace*() macros + forward declarations + └── src/ + ├── adapter_encoder_ext_trace_lib.c # vtable + ESP_TRACE_REGISTER_ENCODER() + └── trace_FreeRTOS.c # hook implementations (may include FreeRTOS.h) +``` + +## How the Integration Works + +### 1. Selecting the external library + +`CONFIG_ESP_TRACE_LIB_EXTERNAL=y` tells `esp_trace` that the encoder lives in a separate component. Internally, `CONFIG_ESP_TRACE_LIB_NAME` resolves to `"ext"`. You can either: + +* register your encoder under that default name — `ESP_TRACE_REGISTER_ENCODER("ext", &vt);` — and the system picks it up automatically, **or** +* register under any name you like (this example uses `"ext_trace_lib"`) and override the session parameters at runtime via `esp_trace_get_user_params()`. See [`main/app_main.c`](main/app_main.c): + + ```c + esp_trace_open_params_t esp_trace_get_user_params(void) + { + esp_trace_open_params_t trace_params = { + .core_cfg = NULL, + .encoder_name = "ext_trace_lib", + .encoder_cfg = NULL, + .transport_name = "usb_serial_jtag", + .transport_cfg = NULL, + }; + return trace_params; + } + ``` + +### 2. Providing the FreeRTOS trace hooks + +`esp_trace`'s public header [`esp_trace_freertos.h`](../../../components/esp_trace/include/esp_trace_freertos.h) is included from `FreeRTOSConfig.h`. When `CONFIG_ESP_TRACE_LIB_EXTERNAL=y` is set, it pulls in **your** `esp_trace_freertos_impl.h`: + +```c +#if CONFIG_ESP_TRACE_LIB_EXTERNAL +#include "esp_trace_freertos_impl.h" +#endif +``` + +The example splits the contract into two files: + +* [`esp_trace_freertos_impl.h`](components/ext_trace_lib/include/esp_trace_freertos_impl.h) — a one-line shim that pulls in `trace_FreeRTOS.h`. +* [`trace_FreeRTOS.h`](components/ext_trace_lib/include/trace_FreeRTOS.h) — defines only the `trace*()` macros this example actually hooks into, plus forward declarations of the helper functions called from them. **No FreeRTOS includes.** Anything left undefined here falls back to FreeRTOS's own empty default (every trace macro is guarded by `#ifndef traceXXX / #define traceXXX() / #endif` in `freertos/FreeRTOS.h`), so you only need to declare what you actually intercept. Trace macros are allowed to reference FreeRTOS identifiers like `pxTCB` or `xTicksToWait` by name — they are resolved later, when the macro is expanded inside the FreeRTOS kernel `.c` files where those names are already in scope. + +The actual hook implementation lives in [`trace_FreeRTOS.c`](components/ext_trace_lib/src/trace_FreeRTOS.c) and is free to `#include "freertos/FreeRTOS.h"`. By the time a `.c` file is compiled, `FreeRTOSConfig.h` has been fully parsed. + +### 3. Registering the encoder + +[`adapter_encoder_ext_trace_lib.c`](components/ext_trace_lib/src/adapter_encoder_ext_trace_lib.c) implements the encoder vtable (`init`, `write`, `panic_handler`) and registers it at link time: + +```c +ESP_TRACE_REGISTER_ENCODER("ext_trace_lib", &s_ext_trace_lib_vt); +``` + +The registration places a descriptor into a dedicated linker section that `esp_trace_core` scans during startup. Because nothing in the application references that descriptor directly, the linker would normally garbage-collect it — `WHOLE_ARCHIVE TRUE` in the component's `CMakeLists.txt` prevents that. + +### 4. CMake setup + +[`components/ext_trace_lib/CMakeLists.txt`](components/ext_trace_lib/CMakeLists.txt) shows the two pieces of CMake plumbing every external trace library needs: + +```cmake +if(CONFIG_ESP_TRACE_LIB_EXTERNAL) + idf_component_register(SRC_DIRS ${src_dirs} + INCLUDE_DIRS ${include_dirs} + PRIV_REQUIRES esp_trace + WHOLE_ARCHIVE TRUE) # keep ESP_TRACE_REGISTER_* symbols + + # Expose esp_trace_freertos_impl.h to the freertos component + idf_component_get_property(freertos_lib freertos COMPONENT_LIB) + target_include_directories(${freertos_lib} INTERFACE ${include_dirs}) +else() + idf_component_register(PRIV_REQUIRES esp_trace) +endif() +``` + +The second `target_include_directories(...)` call is what makes `esp_trace_freertos_impl.h` resolvable from inside the FreeRTOS kernel's translation units. + +## Changing the Transport + +The example defaults to USB Serial JTAG. To use a different transport, edit `sdkconfig.defaults` (or run `idf.py menuconfig` → *Component config → ESP Trace Configuration → Trace transport*): + +| Transport | Config | Notes | +| --- | --- | --- | +| USB Serial JTAG | `CONFIG_ESP_TRACE_TRANSPORT_USB_SERIAL_JTAG=y` | Default. Requires `ESP_CONSOLE_SECONDARY_NONE=y`. | +| apptrace over JTAG | `CONFIG_ESP_TRACE_TRANSPORT_APPTRACE=y` + `CONFIG_APPTRACE_DEST_JTAG=y` | Needs OpenOCD on the host to drain the buffer. | +| apptrace over UART | `CONFIG_ESP_TRACE_TRANSPORT_APPTRACE=y` + `CONFIG_APPTRACE_DEST_UART=y` | Pick a UART different from the console. | +| External transport | `CONFIG_ESP_TRACE_TRANSPORT_EXTERNAL=y` | Another component must register a transport with `ESP_TRACE_REGISTER_TRANSPORT(...)`. | +| None | `CONFIG_ESP_TRACE_TRANSPORT_NONE=y` | Useful if your library streams data over its own channel and just needs the FreeRTOS hooks. | + +Don't forget to update the `transport_name` field in `esp_trace_get_user_params()` to match (e.g. `"apptrace"`, `"usb_serial_jtag"`, or your custom transport's registered name). + +## What the Demo Emits + +`encode()` in [`trace_FreeRTOS.c`](components/ext_trace_lib/src/trace_FreeRTOS.c) writes one line per trace event in the form: + +``` +[+ 9933 us] ISR_IN irq=63 +[+ 29 us] ISR_IN irq=57 +[+ 21 us] ISR_YIELD +[+ 16 us] ISR_OUT +[+ 1234 us] Q_CREATE q=0x3fc8a210 +[+ 167 us] TASK_IN producer +[+ 54 us] Q_SEND q=0x3fc8a210 +[+ 32 us] TASK_IN consumer +``` + +The leading number is the time elapsed since the previous traced event (microseconds when `CONFIG_ESP_TRACE_TS_SOURCE_ESP_TIMER` is selected). Eight FreeRTOS hooks are wired up — see the *active hooks* block at the top of [`trace_FreeRTOS.h`](components/ext_trace_lib/include/trace_FreeRTOS.h). The rest stay as no-ops (FreeRTOS still expects every `trace*()` macro to be defined). + +`ISR_OUT` vs `ISR_YIELD` reflects how FreeRTOS leaves the interrupt: `ISR_OUT` when the handler returns to the interrupted task without scheduling, `ISR_YIELD` when it calls `portYIELD_FROM_ISR()` (triggering `traceISR_EXIT_TO_SCHEDULER`). On a busy SMP target the yield path dominates; on a mostly-idle single-core target the plain `ISR_OUT` path does. + +Because the transport is USB-Serial-JTAG and the console is on UART (`CONFIG_ESP_CONSOLE_SECONDARY_NONE=y`), `idf.py monitor` shows ESP-IDF logs while the trace stream is on a separate USB endpoint — open it in any serial terminal (`screen /dev/cu.usbmodem...`, picocom, etc.) to read the output above. + +## Runtime Control — `esp_trace_start` / `_stop` / `_flush` + +[`esp_trace.h`](../../../components/esp_trace/include/esp_trace.h) exposes three generic lifecycle calls that dispatch to the active encoder's vtable. The application uses only the public API — it never reaches into the external library: + +```c +esp_trace_start(); // resume emission (also resets the delta baseline) +// ... do stuff ... +esp_trace_stop(); // pause emission +esp_trace_flush(); // drain transport buffers +``` + +In this example the library boots with `s_enabled = false`, so nothing is emitted until `app_main()` calls `esp_trace_start()`. The trailing pair `esp_trace_flush(); esp_trace_stop();` makes sure the last events reach the host before the trace channel goes silent. Adapter wiring lives in [`adapter_encoder_ext_trace_lib.c`](components/ext_trace_lib/src/adapter_encoder_ext_trace_lib.c) (`start` / `stop` / `flush` callbacks); flush forwards to the transport's `flush_nolock`. + +## Cross-Core Serialization + +`encode()` wraps its body in the encoder's `take_lock` / `give_lock` vtable entries (an `esp_trace_lock_t` allocated in the adapter's `init()`). See [`trace_FreeRTOS.c`](components/ext_trace_lib/src/trace_FreeRTOS.c) and [`adapter_encoder_ext_trace_lib.c`](components/ext_trace_lib/src/adapter_encoder_ext_trace_lib.c). + +## Other `esp_trace` Helpers + +Beyond what this example uses, [`esp_trace.h`](../../../components/esp_trace/include/esp_trace.h) and [`esp_trace_util.h`](../../../components/esp_trace/include/esp_trace_util.h) also expose: + +* `esp_trace_is_host_connected()` — gate expensive work when no host is listening. +* `esp_trace_get_link_type()` — returns `ESP_TRACE_LINK_DEBUG_PROBE`, `_UART`, or `_USB_SERIAL_JTAG`. +* `esp_trace_rb_*()` — power-of-2, FreeRTOS-free ring buffer for trace hot paths. +* `esp_trace_tmo_init/check()` — cooperative timeouts for flush loops. + +## See Also + +* [`components/esp_trace/README.md`](../../../components/esp_trace/README.md) — full architecture overview and adapter API reference. +* [`examples/system/sysview_tracing`](../sysview_tracing) — a production-grade integration of SEGGER SystemView built on the same extension points. diff --git a/examples/system/esp_trace/components/ext_trace_lib/CMakeLists.txt b/examples/system/esp_trace/components/ext_trace_lib/CMakeLists.txt new file mode 100644 index 00000000000..ce4555c4eb5 --- /dev/null +++ b/examples/system/esp_trace/components/ext_trace_lib/CMakeLists.txt @@ -0,0 +1,23 @@ +set(src_dirs + "src" +) + +set(include_dirs + "include" +) + +set(priv_requires + "esp_trace" +) + +if(CONFIG_ESP_TRACE_LIB_EXTERNAL) + idf_component_register(SRC_DIRS ${src_dirs} + INCLUDE_DIRS ${include_dirs} + PRIV_REQUIRES ${priv_requires} + WHOLE_ARCHIVE TRUE) + + idf_component_get_property(freertos_lib freertos COMPONENT_LIB) + target_include_directories(${freertos_lib} INTERFACE ${include_dirs}) +else() + idf_component_register(PRIV_REQUIRES ${priv_requires}) +endif() diff --git a/examples/system/esp_trace/components/ext_trace_lib/include/esp_trace_freertos_impl.h b/examples/system/esp_trace/components/ext_trace_lib/include/esp_trace_freertos_impl.h new file mode 100644 index 00000000000..fc365434365 --- /dev/null +++ b/examples/system/esp_trace/components/ext_trace_lib/include/esp_trace_freertos_impl.h @@ -0,0 +1,9 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include "trace_FreeRTOS.h" diff --git a/examples/system/esp_trace/components/ext_trace_lib/include/trace_FreeRTOS.h b/examples/system/esp_trace/components/ext_trace_lib/include/trace_FreeRTOS.h new file mode 100644 index 00000000000..c49ca59bf49 --- /dev/null +++ b/examples/system/esp_trace/components/ext_trace_lib/include/trace_FreeRTOS.h @@ -0,0 +1,68 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ +/* + * External Trace Library - FreeRTOS trace hooks + * + * This header is pulled in (via esp_trace_freertos_impl.h) from FreeRTOSConfig.h + * Do NOT include any FreeRTOS header here — keep this file restricted to: + * - forward declarations of the C functions called by the macros below, + * - the trace*() macro definitions themselves. + * + * Macros are allowed to reference FreeRTOS identifiers (pxTCB, xTicksToWait, ...) + * by name — they are resolved later, when the macro is expanded inside the + * FreeRTOS kernel .c files where those names are already in scope. + * + * Only the trace*() macros this example actually hooks are defined here. + * Anything left undefined falls back to FreeRTOS's own empty default (see + * the #ifndef guards in freertos/FreeRTOS.h). + */ + +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* Forward declaration so we can pass an encoder pointer from the adapter to + * init_trace_lib() without pulling in esp_trace_port_encoder.h. */ +typedef struct esp_trace_encoder esp_trace_encoder_t; + +void init_trace_lib(esp_trace_encoder_t *enc); +void trace_lib_start(void); +void trace_lib_stop(void); + +/* Hook implementations — defined in trace_FreeRTOS.c. + * Kept void*-typed to avoid depending on FreeRTOS types in this header. */ +void trace_lib_task_switched_in(void); +void trace_lib_task_create(void *pxNewTCB); +void trace_lib_isr_enter(uint32_t irq); +void trace_lib_isr_exit(void); +void trace_lib_isr_exit_to_scheduler(void); +void trace_lib_queue_send(void *pxQueue); +void trace_lib_queue_receive(void *pxQueue); +void trace_lib_queue_create(void *pxNewQueue); + +#ifdef __cplusplus +} +#endif + +/* ------------------------------------------------------------------ * + * Active hooks (forwarded to trace_FreeRTOS.c) + * ------------------------------------------------------------------ */ +#define traceTASK_SWITCHED_IN() trace_lib_task_switched_in() +#define traceTASK_CREATE(pxNewTCB) trace_lib_task_create(pxNewTCB) +#define traceISR_ENTER(n) trace_lib_isr_enter(n) +#define traceISR_EXIT() trace_lib_isr_exit() +#define traceISR_EXIT_TO_SCHEDULER() trace_lib_isr_exit_to_scheduler() +#define traceQUEUE_SEND(pxQueue) trace_lib_queue_send(pxQueue) +#define traceQUEUE_RECEIVE(pxQueue) trace_lib_queue_receive(pxQueue) +#define traceQUEUE_CREATE(pxNewQueue) trace_lib_queue_create(pxNewQueue) + +/* All other trace*() macros fall back to FreeRTOS's default empty defines + * (#ifndef ... #define ... empty in freertos/FreeRTOS.h); no need to list + * them here. Add a mapping above when you want to hook one. */ diff --git a/examples/system/esp_trace/components/ext_trace_lib/src/adapter_encoder_ext_trace_lib.c b/examples/system/esp_trace/components/ext_trace_lib/src/adapter_encoder_ext_trace_lib.c new file mode 100644 index 00000000000..976704b44e4 --- /dev/null +++ b/examples/system/esp_trace/components/ext_trace_lib/src/adapter_encoder_ext_trace_lib.c @@ -0,0 +1,141 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include + +#include "esp_err.h" +#include "esp_heap_caps.h" +#include "esp_trace_types.h" +#include "esp_trace_registry.h" +#include "esp_trace_port_encoder.h" +#include "esp_trace_port_transport.h" +#include "esp_trace_util.h" +#include "trace_FreeRTOS.h" + +typedef struct { + esp_trace_lock_t lock; +} ext_trace_lib_ctx_t; +/** + * @brief Initializes ext_trace_lib encoder. + * This function is called for each core. + * Adapter implementations do NOT need their own multi-core protection. Core does it for them. + * + * @param enc Pointer to the encoder structure. Must not be NULL. + * @param enc_cfg Pointer to the encoder configuration. Can be NULL for defaults. + * + * @return ESP_OK on success, otherwise \see esp_err_t + */ +static esp_err_t init(esp_trace_encoder_t *enc, const void *enc_cfg) +{ + (void)enc_cfg; + + // Ensure the encoder is initialized only once unless something todo for both cores + static bool initialized = false; + + if (!enc) { + return ESP_ERR_INVALID_ARG; + } + + if (initialized) { + return ESP_OK; + } + + ext_trace_lib_ctx_t *ctx = heap_caps_calloc(1, sizeof(*ctx), + MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT); + if (!ctx) { + return ESP_ERR_NO_MEM; + } + esp_trace_lock_init(&ctx->lock); + enc->ctx = ctx; + + init_trace_lib(enc); + + initialized = true; + + return ESP_OK; +} + +static esp_err_t write(esp_trace_encoder_t *enc, const void *data, size_t size, uint32_t tmo) +{ + if (!enc || !data || size == 0) { + return ESP_ERR_INVALID_ARG; + } + + if (!enc->tp || !enc->tp->vt->write) { + return ESP_ERR_NOT_SUPPORTED; + } + + return enc->tp->vt->write(enc->tp, data, size, tmo); +} + +static esp_err_t start(esp_trace_encoder_t *enc) +{ + (void)enc; + trace_lib_start(); + return ESP_OK; +} + +static esp_err_t stop(esp_trace_encoder_t *enc) +{ + (void)enc; + trace_lib_stop(); + return ESP_OK; +} + +static esp_err_t flush(esp_trace_encoder_t *enc) +{ + if (!enc || !enc->tp || !enc->tp->vt->flush_nolock) { + return ESP_ERR_NOT_SUPPORTED; + } + return enc->tp->vt->flush_nolock(enc->tp); +} + +/** + * @brief Panic handler + * + * Called during system panic to finalize encoder state. + * + * @param enc Pointer to the encoder structure. Must not be NULL. + * @param info Panic information + */ +static void panic_handler(esp_trace_encoder_t *enc, const void *info) +{ + (void)info; + flush(enc); +} + +static unsigned int take_lock(esp_trace_encoder_t *enc, uint32_t tmo_us) +{ + if (!enc || !enc->ctx) { + return 0; + } + ext_trace_lib_ctx_t *ctx = enc->ctx; + esp_trace_lock_take(&ctx->lock, tmo_us); + return ctx->lock.int_state; +} + +static void give_lock(esp_trace_encoder_t *enc, unsigned int int_state) +{ + if (!enc || !enc->ctx) { + return; + } + ext_trace_lib_ctx_t *ctx = enc->ctx; + ctx->lock.int_state = int_state; + esp_trace_lock_give(&ctx->lock); +} + +static const esp_trace_encoder_vtable_t s_ext_trace_lib_vt = { + .init = init, + .write = write, + .panic_handler = panic_handler, + .start = start, + .stop = stop, + .flush = flush, + .take_lock = take_lock, + .give_lock = give_lock, +}; + +ESP_TRACE_REGISTER_ENCODER("ext_trace_lib", &s_ext_trace_lib_vt); diff --git a/examples/system/esp_trace/components/ext_trace_lib/src/trace_FreeRTOS.c b/examples/system/esp_trace/components/ext_trace_lib/src/trace_FreeRTOS.c new file mode 100644 index 00000000000..5e88bf6df37 --- /dev/null +++ b/examples/system/esp_trace/components/ext_trace_lib/src/trace_FreeRTOS.c @@ -0,0 +1,147 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * Implementations of the trace*() hooks declared in trace_FreeRTOS.h. + * + * Each hook encodes a single human-readable line that can be observed directly + * in any serial monitor — no decoder is needed: + * + * [+ 123 us] TASK_IN Task 1 + * [+ 1000 us] ISR_IN irq=5 + * + * The leading number is the time elapsed since the previous traced event. + * + */ + +#include +#include +#include + +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" + +#include "trace_FreeRTOS.h" +#include "esp_trace.h" +#include "esp_trace_port_encoder.h" +#include "esp_trace_util.h" + +static esp_trace_handle_t s_esp_trace_handle = NULL; +static esp_trace_encoder_t *s_enc = NULL; +static uint32_t s_ts_freq_hz = 1000000; /* default assume 1 MHz */ +static uint32_t s_last_ts = 0; +static volatile bool s_enabled = false; + +void init_trace_lib(esp_trace_encoder_t *enc) +{ + s_esp_trace_handle = esp_trace_get_active_handle(); + s_enc = enc; + + uint32_t freq = esp_trace_timestamp_init(); + if (freq != 0) { + s_ts_freq_hz = freq; + } + s_last_ts = esp_trace_timestamp_get(); +} + +void trace_lib_start(void) +{ + if (!s_esp_trace_handle) { + return; + } + + s_last_ts = esp_trace_timestamp_get(); + s_enabled = true; +} + +void trace_lib_stop(void) +{ + s_enabled = false; +} + +/* Encode one trace line and write it through. */ +static void encode(const char *type, const char *detail) +{ + if (!s_enabled || !s_esp_trace_handle || !s_enc) { + return; + } + + unsigned int int_state = s_enc->vt->take_lock(s_enc, ESP_TRACE_TMO_INFINITE); + + uint32_t now = esp_trace_timestamp_get(); + uint32_t delta = now - s_last_ts; /* uint32 modular subtraction handles wrap */ + s_last_ts = now; + + uint32_t delta_us = (s_ts_freq_hz == 1000000) + ? delta + : (uint32_t)((uint64_t)delta * 1000000ULL / s_ts_freq_hz); + + char line[96]; + int n = snprintf(line, sizeof(line), "[+%7lu us] %-12s %s\n", + (unsigned long)delta_us, type, detail ? detail : ""); + if (n > 0) { + if (n >= (int)sizeof(line)) { + n = (int)sizeof(line) - 1; + } + esp_trace_write(s_esp_trace_handle, line, (size_t)n, 0); + } + + s_enc->vt->give_lock(s_enc, int_state); +} + +void trace_lib_task_switched_in(void) +{ + TaskHandle_t h = xTaskGetCurrentTaskHandle(); + encode("TASK_IN", h ? pcTaskGetName(h) : "?"); +} + +void trace_lib_task_create(void *pxNewTCB) +{ + if (!pxNewTCB) { + encode("TASK_CREATE", "(null)"); + return; + } + encode("TASK_CREATE", pcTaskGetName((TaskHandle_t)pxNewTCB)); +} + +void trace_lib_isr_enter(uint32_t irq) +{ + char d[24]; + snprintf(d, sizeof(d), "irq=%lu", (unsigned long)irq); + encode("ISR_IN", d); +} + +void trace_lib_isr_exit(void) +{ + encode("ISR_OUT", ""); +} + +void trace_lib_isr_exit_to_scheduler(void) +{ + encode("ISR_YIELD", ""); +} + +void trace_lib_queue_send(void *pxQueue) +{ + char d[24]; + snprintf(d, sizeof(d), "q=%p", pxQueue); + encode("Q_SEND", d); +} + +void trace_lib_queue_receive(void *pxQueue) +{ + char d[24]; + snprintf(d, sizeof(d), "q=%p", pxQueue); + encode("Q_RECEIVE", d); +} + +void trace_lib_queue_create(void *pxNewQueue) +{ + char d[24]; + snprintf(d, sizeof(d), "q=%p", pxNewQueue); + encode("Q_CREATE", d); +} diff --git a/examples/system/esp_trace/main/CMakeLists.txt b/examples/system/esp_trace/main/CMakeLists.txt new file mode 100644 index 00000000000..f9cd30438ad --- /dev/null +++ b/examples/system/esp_trace/main/CMakeLists.txt @@ -0,0 +1,3 @@ +idf_component_register(SRCS "app_main.c" + REQUIRES ext_trace_lib + INCLUDE_DIRS "") diff --git a/examples/system/esp_trace/main/app_main.c b/examples/system/esp_trace/main/app_main.c new file mode 100644 index 00000000000..28d7c07aa27 --- /dev/null +++ b/examples/system/esp_trace/main/app_main.c @@ -0,0 +1,76 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: CC0-1.0 + */ +#include "sdkconfig.h" + +#include +#include + +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/queue.h" +#include "esp_log.h" +#include "esp_trace.h" + +static const char *TAG = "main"; + +esp_trace_open_params_t esp_trace_get_user_params(void) +{ + esp_trace_open_params_t trace_params = { + .core_cfg = NULL, + .encoder_name = "ext_trace_lib", + .encoder_cfg = NULL, + .transport_name = "usb_serial_jtag", + .transport_cfg = NULL, + }; + return trace_params; +} + +static QueueHandle_t s_q; + +/* Producer: sends a counter value to s_q every 50 ms. Generates Q_SEND + * trace events and unblocks the consumer (driving TASK_IN switches). */ +static void producer(void *arg) +{ + uint32_t v = 0; + while (1) { + xQueueSend(s_q, &v, portMAX_DELAY); + v++; + vTaskDelay(50 / portTICK_PERIOD_MS); + } +} + +/* Consumer: blocks on s_q indefinitely. Each receive wakes this task and + * fires a TASK_IN trace event when the scheduler switches us in. */ +static void consumer(void *arg) +{ + uint32_t v; + while (1) { + xQueueReceive(s_q, &v, portMAX_DELAY); + } +} + +void app_main(void) +{ + ESP_LOGI(TAG, "Start of trace session"); + + // Wait some time for host to be ready + vTaskDelay(2000 / portTICK_PERIOD_MS); + + esp_trace_start(); + + s_q = xQueueCreate(4, sizeof(uint32_t)); + + xTaskCreatePinnedToCore(producer, "producer", 2048, NULL, 5, NULL, 0); + xTaskCreatePinnedToCore(consumer, "consumer", 2048, NULL, 5, NULL, portNUM_PROCESSORS - 1); + + // 1 second delay is enough to generate the expected number of trace events. + vTaskDelay(1000 / portTICK_PERIOD_MS); + + esp_trace_stop(); + esp_trace_flush(); + + ESP_LOGI(TAG, "End of trace session"); +} diff --git a/examples/system/esp_trace/pytest_esp_trace.py b/examples/system/esp_trace/pytest_esp_trace.py new file mode 100644 index 00000000000..d1f031f5201 --- /dev/null +++ b/examples/system/esp_trace/pytest_esp_trace.py @@ -0,0 +1,115 @@ +# SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: Unlicense OR CC0-1.0 +import os.path +import re +import time + +import pytest +import serial +from pytest_embedded_idf import IdfDut +from pytest_embedded_idf.utils import idf_parametrize +from pytest_embedded_idf.utils import soc_filtered_targets + +# Matches lines emitted by encode() in trace_FreeRTOS.c, e.g. +# [+ 12345 us] TASK_IN producer +# [+ 12 us] Q_SEND q=0x3fc8a210 +TRACE_LINE_RE = re.compile(r'^\[\+\s*\d+ us\] ([A-Z_]+)\s*(.*)$') + +# Minimum number of occurrences each event type must reach within the capture window. +EXPECTED_MIN_COUNTS = { + 'TASK_CREATE': 2, # producer + consumer + 'Q_CREATE': 1, # xQueueCreate in app_main + 'TASK_IN': 5, # producer/consumer + idle task + 'Q_SEND': 5, # producer sends every 50 ms + 'Q_RECEIVE': 5, # consumer receives every send + 'ISR_IN': 5, # FreeRTOS systick +} + +ISR_EXIT_MIN = 5 + +# Fraction of malformed lines we tolerate before failing. +MAX_MALFORMED_RATIO = 0.05 + + +def _validate_trace_data(trace_log_path: str) -> None: + """Validate the human-readable trace log produced by ext_trace_lib.""" + with open(trace_log_path, encoding='utf-8', errors='replace') as f: + lines = [line.rstrip() for line in f if line.strip()] + + assert lines, f'No trace data captured in {trace_log_path}' + + counts: dict[str, int] = {} + create_names: set[str] = set() + malformed = 0 + + for line in lines: + m = TRACE_LINE_RE.match(line) + if not m: + malformed += 1 + continue + evt, detail = m.group(1), m.group(2).strip() + counts[evt] = counts.get(evt, 0) + 1 + if evt == 'TASK_CREATE': + create_names.add(detail) + + ratio = malformed / len(lines) + assert ratio <= MAX_MALFORMED_RATIO, ( + f'Too many malformed lines: {malformed}/{len(lines)} ' + f'({ratio:.1%} > {MAX_MALFORMED_RATIO:.0%}). Likely USJ TX ring overflow; ' + f'bump CONFIG_ESP_TRACE_USJ_TX_BUFFER_SIZE or slow down the producer.' + ) + + for evt, minimum in EXPECTED_MIN_COUNTS.items(): + seen = counts.get(evt, 0) + assert seen >= minimum, f'Expected at least {minimum} {evt} events, got {seen}' + + isr_exits = counts.get('ISR_OUT', 0) + counts.get('ISR_YIELD', 0) + assert isr_exits >= ISR_EXIT_MIN, ( + f'Expected at least {ISR_EXIT_MIN} ISR_OUT+ISR_YIELD events, ' + f'got {isr_exits} (ISR_OUT={counts.get("ISR_OUT", 0)}, ' + f'ISR_YIELD={counts.get("ISR_YIELD", 0)})' + ) + + assert 'producer' in create_names, f'producer task not seen in TASK_CREATE: {create_names}' + assert 'consumer' in create_names, f'consumer task not seen in TASK_CREATE: {create_names}' + + +def _capture_trace(ser: serial.Serial, trace_log_path: str, capture_s: float = 5.0) -> None: + """Capture trace output from the USB-Serial-JTAG endpoint.""" + ser.reset_input_buffer() + with open(trace_log_path, 'w+b') as f: + end_time = time.time() + capture_s + while time.time() < end_time: + try: + if ser.in_waiting: + f.write(ser.read(ser.in_waiting)) + except serial.SerialTimeoutException: + assert False, 'Timeout reached while reading from serial port, exiting...' + + # Drain anything still in flight after the capture window. + time.sleep(0.2) + end_time = time.time() + 1.0 + last_data_time = time.time() + while time.time() < end_time and (time.time() - last_data_time) <= 0.3: + try: + if ser.in_waiting: + f.write(ser.read(ser.in_waiting)) + last_data_time = time.time() + except serial.SerialTimeoutException: + assert False, 'Timeout reached while reading from serial port, exiting...' + + +@pytest.mark.usb_serial_jtag +@idf_parametrize('target', soc_filtered_targets('SOC_USB_SERIAL_JTAG_SUPPORTED == 1'), indirect=['target']) +@pytest.mark.parametrize('config', [pytest.param('default')], indirect=True) +@pytest.mark.temp_skip_ci(targets=['esp32h4'], reason='lack of runner # TODO: IDFCI-10703') +def test_esp_trace_ext_lib_usj(dut: IdfDut) -> None: + dut.expect('Start of trace session', timeout=5) + + time.sleep(1) # wait for USJ port to be ready + usj_port = '/dev/serial_ports/ttyACM-esp32' + ser = serial.Serial(usj_port, baudrate=1000000, timeout=10) + trace_log_path = os.path.join(dut.logdir, 'ext_trace.log') + + _capture_trace(ser, trace_log_path) + _validate_trace_data(trace_log_path) diff --git a/examples/system/esp_trace/sdkconfig.defaults b/examples/system/esp_trace/sdkconfig.defaults new file mode 100644 index 00000000000..f90dbb5d285 --- /dev/null +++ b/examples/system/esp_trace/sdkconfig.defaults @@ -0,0 +1,6 @@ +CONFIG_ESP_TRACE_ENABLE=y +CONFIG_ESP_TRACE_LIB_EXTERNAL=y +CONFIG_ESP_TRACE_TS_SOURCE_ESP_TIMER=y +CONFIG_ESP_CONSOLE_SECONDARY_NONE=y +CONFIG_ESP_TRACE_TRANSPORT_USB_SERIAL_JTAG=y +CONFIG_ESP_TRACE_USJ_TX_BUFFER_SIZE=32768 diff --git a/examples/system/ota/partitions_ota/sdkconfig.ci.virt_sb_v2_and_fe.esp32c61 b/examples/system/ota/partitions_ota/sdkconfig.ci.virt_sb_v2_and_fe.esp32c61 new file mode 100644 index 00000000000..dd383a0e946 --- /dev/null +++ b/examples/system/ota/partitions_ota/sdkconfig.ci.virt_sb_v2_and_fe.esp32c61 @@ -0,0 +1,6 @@ +# ESP32-C61 has no RSA based Secure Boot V2; the ECDSA scheme is gated behind the +# insecure option (see SECURE_BOOT_V2_ECDSA_INSECURE), so force-enable it and use an ECDSA key. +CONFIG_SECURE_BOOT_INSECURE=y +CONFIG_SECURE_BOOT_V2_FORCE_ENABLE_ECDSA=y +CONFIG_SECURE_SIGNED_APPS_ECDSA_V2_SCHEME=y +CONFIG_SECURE_BOOT_SIGNING_KEY="test/secure_boot_signing_key_ecdsa.pem" diff --git a/examples/system/ota/partitions_ota/sdkconfig.ci.virt_sb_v2_and_fe_2.esp32c61 b/examples/system/ota/partitions_ota/sdkconfig.ci.virt_sb_v2_and_fe_2.esp32c61 new file mode 100644 index 00000000000..dd383a0e946 --- /dev/null +++ b/examples/system/ota/partitions_ota/sdkconfig.ci.virt_sb_v2_and_fe_2.esp32c61 @@ -0,0 +1,6 @@ +# ESP32-C61 has no RSA based Secure Boot V2; the ECDSA scheme is gated behind the +# insecure option (see SECURE_BOOT_V2_ECDSA_INSECURE), so force-enable it and use an ECDSA key. +CONFIG_SECURE_BOOT_INSECURE=y +CONFIG_SECURE_BOOT_V2_FORCE_ENABLE_ECDSA=y +CONFIG_SECURE_SIGNED_APPS_ECDSA_V2_SCHEME=y +CONFIG_SECURE_BOOT_SIGNING_KEY="test/secure_boot_signing_key_ecdsa.pem" diff --git a/examples/system/sysview_tracing/main/sysview_tracing.c b/examples/system/sysview_tracing/main/sysview_tracing.c index 79505963558..b988471ba27 100644 --- a/examples/system/sysview_tracing/main/sysview_tracing.c +++ b/examples/system/sysview_tracing/main/sysview_tracing.c @@ -15,11 +15,9 @@ #include #include #include "esp_log.h" -#include "esp_app_trace.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "driver/gptimer.h" -#include "soc/uart_pins.h" #include "esp_trace.h" static const char *TAG = "example"; @@ -127,6 +125,9 @@ static void example_task(void *p) } } +#if CONFIG_ESP_TRACE_TRANSPORT_APPTRACE +#include "soc/uart_pins.h" +#include "esp_app_trace.h" esp_trace_open_params_t esp_trace_get_user_params(void) { static esp_apptrace_config_t app_trace_config = APPTRACE_CONFIG_DEFAULT(); @@ -145,6 +146,19 @@ esp_trace_open_params_t esp_trace_get_user_params(void) }; return trace_params; } +#elif CONFIG_ESP_TRACE_TRANSPORT_USB_SERIAL_JTAG +esp_trace_open_params_t esp_trace_get_user_params(void) +{ + esp_trace_open_params_t trace_params = { + .core_cfg = NULL, + .encoder_name = "sysview", + .encoder_cfg = NULL, + .transport_name = "usb_serial_jtag", + .transport_cfg = NULL, + }; + return trace_params; +} +#endif void app_main(void) { diff --git a/examples/system/sysview_tracing/pytest_sysview_tracing.py b/examples/system/sysview_tracing/pytest_sysview_tracing.py index 56ae1ec20fd..3f93ef8e3d0 100644 --- a/examples/system/sysview_tracing/pytest_sysview_tracing.py +++ b/examples/system/sysview_tracing/pytest_sysview_tracing.py @@ -10,6 +10,7 @@ import pytest import serial from pytest_embedded_idf import IdfDut from pytest_embedded_idf.utils import idf_parametrize +from pytest_embedded_idf.utils import soc_filtered_targets if typing.TYPE_CHECKING: from conftest import OpenOCD @@ -158,3 +159,15 @@ def _test_sysview_tracing_uart(dut: IdfDut) -> None: @idf_parametrize('target', ['supported_targets'], indirect=['target']) def test_sysview_tracing_uart(dut: IdfDut) -> None: _test_sysview_tracing_uart(dut) + + +@pytest.mark.usb_serial_jtag +@idf_parametrize('target', soc_filtered_targets('SOC_USB_SERIAL_JTAG_SUPPORTED == 1'), indirect=['target']) +@pytest.mark.parametrize('config', [pytest.param('sysview_usj')], indirect=True) +def test_sysview_tracing_usj_serial(dut: IdfDut) -> None: + time.sleep(1) # wait for USJ port to be ready + usj_port = '/dev/serial_ports/ttyACM-esp32' + ser = serial.Serial(usj_port, baudrate=1000000, timeout=10) + trace_log = [os.path.join(dut.logdir, 'sys_log_usj.svdat')] # pylint: disable=protected-access + _capture_sysview_trace(ser, trace_log[0]) + _validate_trace_data(trace_log, dut.target, is_uart=True) diff --git a/examples/system/sysview_tracing/sdkconfig.ci.sysview_jtag b/examples/system/sysview_tracing/sdkconfig.ci.sysview_jtag index 772bddde156..621f02d84f9 100644 --- a/examples/system/sysview_tracing/sdkconfig.ci.sysview_jtag +++ b/examples/system/sysview_tracing/sdkconfig.ci.sysview_jtag @@ -1 +1,2 @@ +CONFIG_ESP_TRACE_TRANSPORT_APPTRACE=y CONFIG_APPTRACE_DEST_JTAG=y diff --git a/examples/system/sysview_tracing/sdkconfig.ci.sysview_uart b/examples/system/sysview_tracing/sdkconfig.ci.sysview_uart index 15555d5e93a..2d827ec7bba 100644 --- a/examples/system/sysview_tracing/sdkconfig.ci.sysview_uart +++ b/examples/system/sysview_tracing/sdkconfig.ci.sysview_uart @@ -1,4 +1,5 @@ CONFIG_ESP_CONSOLE_NONE=y +CONFIG_ESP_TRACE_TRANSPORT_APPTRACE=y CONFIG_APPTRACE_DEST_UART=y CONFIG_APPTRACE_DEST_UART_NUM=0 CONFIG_APPTRACE_UART_BAUDRATE=1000000 diff --git a/examples/system/sysview_tracing/sdkconfig.ci.sysview_usj b/examples/system/sysview_tracing/sdkconfig.ci.sysview_usj new file mode 100644 index 00000000000..cafe4275e66 --- /dev/null +++ b/examples/system/sysview_tracing/sdkconfig.ci.sysview_usj @@ -0,0 +1,3 @@ +CONFIG_ESP_CONSOLE_SECONDARY_NONE=y +CONFIG_ESP_TRACE_TRANSPORT_USB_SERIAL_JTAG=y +CONFIG_USE_CUSTOM_EVENT_ID=y diff --git a/examples/system/sysview_tracing/sdkconfig.defaults b/examples/system/sysview_tracing/sdkconfig.defaults index 9a260fdef2e..b58df2ad9cd 100644 --- a/examples/system/sysview_tracing/sdkconfig.defaults +++ b/examples/system/sysview_tracing/sdkconfig.defaults @@ -3,7 +3,6 @@ CONFIG_FREERTOS_HZ=1000 # Enable FreeRTOS SystemView Tracing by default CONFIG_ESP_TRACE_ENABLE=y CONFIG_ESP_TRACE_LIB_EXTERNAL=y -CONFIG_ESP_TRACE_TRANSPORT_APPTRACE=y CONFIG_ESP_TRACE_TS_SOURCE_ESP_TIMER=y CONFIG_SEGGER_SYSVIEW_EVT_OVERFLOW_ENABLE=y CONFIG_SEGGER_SYSVIEW_EVT_ISR_ENTER_ENABLE=y diff --git a/examples/system/ulp/lp_core/lp_uart/lp_uart_char_seq_wakeup/main/lp_core/main.c b/examples/system/ulp/lp_core/lp_uart/lp_uart_char_seq_wakeup/main/lp_core/main.c index 0bc04ec231b..237842c54c1 100644 --- a/examples/system/ulp/lp_core/lp_uart/lp_uart_char_seq_wakeup/main/lp_core/main.c +++ b/examples/system/ulp/lp_core/lp_uart/lp_uart_char_seq_wakeup/main/lp_core/main.c @@ -1,20 +1,17 @@ /* - * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2024-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include "ulp_lp_core_utils.h" #include "ulp_lp_core_print.h" -#include "ulp_lp_core_lp_uart_shared.h" +#include "ulp_lp_core_uart.h" int main (void) { lp_core_printf("Hello world\r\n"); + lp_core_uart_tx_flush(LP_UART_NUM_0); - // If you want to make it possible to wake up from UART after sleep, - // you have to reset the wakeup register and the UART buffer manually. - // ulp_lp_core_lp_uart_reset_wakeup_en(); - // lp_core_uart_clear_buf(); return 0; } diff --git a/examples/wifi/itwt/main/Kconfig.projbuild b/examples/wifi/itwt/main/Kconfig.projbuild index 9dc0b28ee4f..9304447a1a9 100644 --- a/examples/wifi/itwt/main/Kconfig.projbuild +++ b/examples/wifi/itwt/main/Kconfig.projbuild @@ -111,15 +111,16 @@ menu "Example Configuration" config EXAMPLE_MAX_CPU_FREQ_80 bool "80 MHz" + depends on IDF_TARGET_ESP32C6 || IDF_TARGET_ESP32C61 || IDF_TARGET_ESP32C5 config EXAMPLE_MAX_CPU_FREQ_120 bool "120 MHz" - depends on IDF_TARGET_ESP32C2 + depends on IDF_TARGET_ESP32C6 config EXAMPLE_MAX_CPU_FREQ_160 bool "160 MHz" - depends on !IDF_TARGET_ESP32C2 + depends on IDF_TARGET_ESP32C6 || IDF_TARGET_ESP32C61 || IDF_TARGET_ESP32C5 config EXAMPLE_MAX_CPU_FREQ_240 bool "240 MHz" - depends on IDF_TARGET_ESP32 || IDF_TARGET_ESP32S2 || IDF_TARGET_ESP32S3 || IDF_TARGET_ESP32C5 + depends on IDF_TARGET_ESP32C5 endchoice config EXAMPLE_MAX_CPU_FREQ_MHZ diff --git a/examples/wifi/itwt/sdkconfig.defaults b/examples/wifi/itwt/sdkconfig.defaults index dfa88a13673..c709278812e 100644 --- a/examples/wifi/itwt/sdkconfig.defaults +++ b/examples/wifi/itwt/sdkconfig.defaults @@ -1,5 +1,3 @@ -# Use lower CPU frequency -CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_80=y # Enable support for power management CONFIG_PM_ENABLE=y # Enable tickless idle mode @@ -17,3 +15,16 @@ CONFIG_LWIP_ESP_GRATUITOUS_ARP=n CONFIG_FREERTOS_HZ=1000 # CONFIG_LWIP_ESP_GRATUITOUS_ARP is not set # CONFIG_ESP_GRATUITOUS_ARP is not set + +CONFIG_ESP_WIFI_ENABLE_WIFI_RX_STATS=n +CONFIG_ESP_WIFI_ENABLE_WIFI_TX_STATS=n + +CONFIG_LWIP_IPV6=n + +CONFIG_LWIP_DHCP_COARSE_TIMER_SECS=10 +CONFIG_PM_POWER_DOWN_CPU_IN_LIGHT_SLEEP=y +CONFIG_PM_POWER_DOWN_PERIPHERAL_IN_LIGHT_SLEEP=y +CONFIG_ESP_SLEEP_POWER_DOWN_FLASH=y +CONFIG_ESP_MODEM_CLOCK_ENABLE_CHECKING=y +CONFIG_ESP_PHY_MAC_BB_PD=y +#CONFIG_ESP_WIFI_ENHANCED_LIGHT_SLEEP=y diff --git a/examples/wifi/itwt/sdkconfig.defaults.esp32c6 b/examples/wifi/itwt/sdkconfig.defaults.esp32c6 deleted file mode 100644 index 27b081e674d..00000000000 --- a/examples/wifi/itwt/sdkconfig.defaults.esp32c6 +++ /dev/null @@ -1,30 +0,0 @@ -# -# ESP32C6-Specific -# -CONFIG_ESP_WIFI_STATIC_RX_BUFFER_NUM=20 -CONFIG_ESP_WIFI_DYNAMIC_RX_BUFFER_NUM=38 -CONFIG_ESP_WIFI_DYNAMIC_TX_BUFFER_NUM=35 -CONFIG_ESP_WIFI_AMPDU_TX_ENABLED=y -CONFIG_ESP_WIFI_TX_BA_WIN=20 -CONFIG_ESP_WIFI_AMPDU_RX_ENABLED=y -CONFIG_ESP_WIFI_RX_BA_WIN=20 -CONFIG_ESP_WIFI_NVS_ENABLED=n - -CONFIG_LWIP_TCP_SND_BUF_DEFAULT=30000 -CONFIG_LWIP_TCP_WND_DEFAULT=34000 -CONFIG_LWIP_TCP_RECVMBOX_SIZE=64 -CONFIG_LWIP_UDP_RECVMBOX_SIZE=64 -CONFIG_LWIP_TCPIP_RECVMBOX_SIZE=64 - -CONFIG_ESP_WIFI_ENABLE_WIFI_RX_STATS=n -CONFIG_ESP_WIFI_ENABLE_WIFI_TX_STATS=n - -CONFIG_LWIP_IPV6=n - -CONFIG_LWIP_DHCP_COARSE_TIMER_SECS=10 -CONFIG_PM_POWER_DOWN_CPU_IN_LIGHT_SLEEP=y -CONFIG_PM_POWER_DOWN_PERIPHERAL_IN_LIGHT_SLEEP=y -CONFIG_ESP_SLEEP_POWER_DOWN_FLASH=y -CONFIG_ESP_MODEM_CLOCK_ENABLE_CHECKING=y -CONFIG_ESP_PHY_MAC_BB_PD=y -#CONFIG_ESP_WIFI_ENHANCED_LIGHT_SLEEP=y diff --git a/pytest.ini b/pytest.ini index 3a715b08347..b857f43b6ef 100644 --- a/pytest.ini +++ b/pytest.ini @@ -94,7 +94,6 @@ env_markers = flash_120m: runner with 120M supported Flash jtag: runner where the chip is accessible through JTAG as well usb_serial_jtag: runner where the chip is accessible through builtin JTAG as well - adc: ADC related tests should run on adc runners xtal32k: Runner with external 32k crystal connected no32kXtal: Runner with no external 32k crystal connected psramv0: Runner with PSRAM version 0 @@ -123,7 +122,7 @@ env_markers = sdio_multidev_32_c61: Test sdio multi board, esp32+esp32c61 sdio_multidev_p4_c5: Test sdio multi board, esp32p4+esp32c5 usj_device: Test usb_serial_jtag and usb_serial_jtag is used as serial only (not console) - twai_std: twai runner with all twai supported targets connect to usb-can adapter + twai_adapter: runner with multiple twai_std lp_i2s: lp_i2s runner tested with hp_i2s ram_app: ram_app runners recovery_bootloader: Runner with recovery bootloader offset set in diff --git a/tools/ble/ble_uart_bridge/README.md b/tools/ble/ble_uart_bridge/README.md index d4a514b1693..a539e757b06 100644 --- a/tools/ble/ble_uart_bridge/README.md +++ b/tools/ble/ble_uart_bridge/README.md @@ -1,9 +1,11 @@ -# BLE UART Bridge +# ESP-BLE-UART Bridge -BLE UART Bridge is a host-side utility for talking to ESP-IDF applications that expose a BLE UART-style GATT service. It provides a reusable Python transport layer, an interactive console for manual testing, and a daemon mode for simple local IPC request/response workflows. +ESP-BLE-UART Bridge is a host-side utility for talking to ESP-IDF applications that expose a BLE UART-style GATT service. It provides a reusable Python transport layer, an interactive console for manual testing, and a daemon mode for simple local IPC request/response workflows. + +> **Naming convention:** Use **ESP-BLE-UART** for Espressif-owned product names (Bridge, Console, Daemon, Echo Server, the `ble_uart` component, and the `ble_uart_service` example). Use **BLE UART** for the generic GATT service convention, transport layer, and compatible third-party devices. This follows the same pattern as ESP-BLE-MESH. ## Table of contents @@ -26,7 +28,7 @@ BLE UART Bridge is a host-side utility for talking to ESP-IDF applications that ## Quick Start -You can reuse the ESP-IDF Python environment, or use your own Python virtual environment. If you reuse the ESP-IDF environment, export it first and then install the additional BLE UART Bridge dependencies: +You can reuse the ESP-IDF Python environment, or use your own Python virtual environment. If you reuse the ESP-IDF environment, export it first and then install the additional ESP-BLE-UART Bridge dependencies: ```bash cd $IDF_PATH @@ -56,15 +58,15 @@ Check whether the device can be connected: python main.py connection-check DEVICE_ID ``` -Open an interactive BLE UART Console: +Open an interactive ESP-BLE-UART Console: ```bash python main.py console DEVICE_ID ``` -For Console options such as line endings, hex mode, and write-with-response, see [Quick-Start-BLE-UART-Console.md](docs/Quick-Start-BLE-UART-Console.md). If you need firmware to test against, use the [BLE UART Service example](../../../examples/bluetooth/ble_uart_service) as an Echo Server: it advertises the default BLE UART-over-GATT UUIDs and echoes RX writes back through TX notifications. +For Console options such as line endings, hex mode, and write-with-response, see [Quick-Start-BLE-UART-Console.md](docs/Quick-Start-BLE-UART-Console.md). If you need firmware to test against, use the [ESP-BLE-UART example](../../../examples/bluetooth/ble_uart_service) as an Echo Server: it advertises the default BLE UART-over-GATT UUIDs and echoes RX writes back through TX notifications. -Run the BLE UART Daemon: +Run the ESP-BLE-UART Daemon: ```bash python main.py daemon DEVICE_ID @@ -111,7 +113,7 @@ python main.py daemon-notify DATA ### Typical Console workflow -Use Console when you want to manually test a BLE UART device from a terminal UI. For a known-compatible target, build and flash the [BLE UART Service example](../../../examples/bluetooth/ble_uart_service), which acts as an Echo Server for Console smoke tests: +Use Console when you want to manually test a BLE UART device from a terminal UI. For a known-compatible target, build and flash the [ESP-BLE-UART example](../../../examples/bluetooth/ble_uart_service), which acts as an Echo Server for Console smoke tests: ```bash python main.py list-devices @@ -194,7 +196,7 @@ Main responsibilities: - Connect and disconnect with a BLE UART GATT profile. - Subscribe to device-to-host notifications. - Send host-to-device data as `str`, `bytes`, or `bytearray`. -- Support a default BLE-UART UUID profile and user-defined BLE UART profiles. +- Support a default BLE UART UUID profile and user-defined BLE UART profiles. Important APIs: @@ -241,10 +243,10 @@ By default, the daemon binds to `127.0.0.1`. Keep it on a loopback address unles ## Demos -The `demos/` directory contains example integrations that build on BLE UART +The `demos/` directory contains example integrations that build on ESP-BLE-UART Bridge components. -- [BLE UART Bridge Demo - OpenCode Integration](demos/opencode/README.md) shows +- [ESP-BLE-UART Bridge Demo - OpenCode Integration](demos/opencode/README.md) shows how an OpenCode plugin can forward session status and permission requests to a BLE device through the daemon. It also includes a firmware-side protocol reference for devices, such as the planned `esp-vocat` / MiaoBan (喵伴) @@ -294,6 +296,6 @@ The tool depends on: - [Quick-Start-BLE-UART-Console.md](docs/Quick-Start-BLE-UART-Console.md) - [Quick-Start-BLE-UART-Daemon.md](docs/Quick-Start-BLE-UART-Daemon.md) -- [BLE UART Bridge Demo - OpenCode Integration](demos/opencode/README.md) +- [ESP-BLE-UART Bridge Demo - OpenCode Integration](demos/opencode/README.md) - [Profile-Compatibility.md](docs/Profile-Compatibility.md) - [PORTING.md](docs/PORTING.md) diff --git a/tools/ble/ble_uart_bridge/demos/opencode/README.md b/tools/ble/ble_uart_bridge/demos/opencode/README.md index 92347e448a4..240657681d6 100644 --- a/tools/ble/ble_uart_bridge/demos/opencode/README.md +++ b/tools/ble/ble_uart_bridge/demos/opencode/README.md @@ -1,4 +1,4 @@ -# BLE UART Bridge Demo - OpenCode Integration +# ESP-BLE-UART Bridge Demo - OpenCode Integration This demo sketches how to bridge OpenCode events to a BLE device through `tools/ble/ble_uart_bridge`. @@ -12,7 +12,7 @@ device decisions, and daemon-side protocol handling for their own products. - [Goal](#goal) - [Quick Start](#quick-start) -- [How it relates to BLE UART Bridge](#how-it-relates-to-ble-uart-bridge) +- [How it relates to ESP-BLE-UART Bridge](#how-it-relates-to-esp-ble-uart-bridge) - [Daemon JSONL protocol summary](#daemon-jsonl-protocol-summary) - [Files](#files) - [Demo and customization notes](#demo-and-customization-notes) @@ -40,7 +40,7 @@ Use an OpenCode plugin to: flowchart LR OC[OpenCode] -->|session.status| Plugin[OpenCode BLE plugin] OC -->|permission.asked| Plugin - Plugin -->|POST /notify| Daemon[ble_uart_bridge daemon] + Plugin -->|POST /notify| Daemon[ESP-BLE-UART Daemon] Plugin -->|POST /request| Daemon Daemon -->|BLE UART JSONL| Device[BLE device UI] Device -->|once / reject| Daemon @@ -52,10 +52,13 @@ flowchart LR 1. Prepare a BLE device firmware example. - The intended firmware companion is an `esp-vocat` example for the MiaoBan - (喵伴) device, planned for the `esp-iot-solution` repository. Until that - example is available, use any device that implements the default BLE UART-over-GATT UUIDs and - the JSONL request/response envelope described in + The intended firmware companion is the `esp-vocat` example for the MiaoBan + (喵伴) device, available in the + [esp-iot-solution](https://github.com/espressif/esp-iot-solution) repository + at `examples/bluetooth/ble_uart_service`. See the example README for + supported boards, dependency versions, and build instructions. Alternatively, + use any device that implements the default BLE UART-over-GATT UUIDs and the + JSONL request/response envelope described in [Firmware protocol reference](#firmware-protocol-reference). 2. Install the bridge dependencies: @@ -64,7 +67,7 @@ flowchart LR python -m pip install -r tools/ble/ble_uart_bridge/requirements.txt ``` -3. Start the BLE UART daemon: +3. Start the ESP-BLE-UART Daemon: ```bash python tools/ble/ble_uart_bridge/main.py list-devices @@ -152,14 +155,15 @@ flowchart LR should receive a `permission.request` JSONL message and return `once` or `reject`. -After the `esp-vocat` example is published in `esp-iot-solution`, this section -should be updated with the exact example path, build/flash commands, and any -MiaoBan-specific button or display behavior. +The `esp-vocat` example is available in the +[esp-iot-solution](https://github.com/espressif/esp-iot-solution) repository at +`examples/bluetooth/ble_uart_service`. See the example README for build/flash +commands, dependency versions, and MiaoBan-specific button and display behavior. -## How it relates to BLE UART Bridge +## How it relates to ESP-BLE-UART Bridge The OpenCode plugin does not talk to BLE directly. It sends local HTTP requests -to the BLE UART Bridge daemon, and the daemon keeps the BLE connection open for +to the ESP-BLE-UART Daemon, and the Daemon keeps the BLE connection open for the plugin: - `POST /notify` sends fire-and-forget events, such as session status updates. @@ -170,8 +174,8 @@ the plugin: For the daemon itself, see: -- [BLE UART Bridge README](../../README.md) -- [BLE UART Daemon Quick Start](../../docs/Quick-Start-BLE-UART-Daemon.md) +- [ESP-BLE-UART Bridge README](../../README.md) +- [ESP-BLE-UART Daemon Quick Start](../../docs/Quick-Start-BLE-UART-Daemon.md) ### Daemon JSONL protocol summary @@ -201,7 +205,7 @@ are documented below in [Firmware protocol reference](#firmware-protocol-referen ## Files - `src/opencode-ble-uart-bridge.ts` — OpenCode plugin entry point using `/notify` for status and `/request` for permission decisions. -- `src/*.ts` helper modules — typed, commented demo code for payloads, BLE daemon transport, OpenCode replies, and permission queue handling. +- `src/*.ts` helper modules — typed, commented demo code for payloads, ESP-BLE-UART Daemon transport, OpenCode replies, and permission queue handling. - `opencode.json.example` — example OpenCode config to load the plugin and ask for permissions. ## Demo and customization notes @@ -224,7 +228,7 @@ permission requests can be approved once with `once` or denied with `reject`. ## Environment variables -- `OPENCODE_BLE_DAEMON_URL`: BLE daemon base URL. Defaults to +- `OPENCODE_BLE_DAEMON_URL`: ESP-BLE-UART Daemon base URL. Defaults to `http://127.0.0.1:8888`. - `OPENCODE_BLE_DECISION_TIMEOUT_SECONDS`: permission decision timeout in seconds. Defaults to `60`; set it to a positive number. @@ -232,9 +236,9 @@ permission requests can be approved once with `once` or denied with `reject`. ## Current assumptions -- The BLE daemon endpoint is configured by `OPENCODE_BLE_DAEMON_URL`, defaulting +- The ESP-BLE-UART Daemon endpoint is configured by `OPENCODE_BLE_DAEMON_URL`, defaulting to `http://127.0.0.1:8888`. -- The BLE daemon supports both `POST /notify` and `POST /request`. +- The ESP-BLE-UART Daemon supports both `POST /notify` and `POST /request`. - The BLE device implements the default BLE UART-over-GATT UUID layout. - The BLE device understands JSON messages described in [Firmware protocol reference](#firmware-protocol-reference). @@ -247,7 +251,7 @@ permission requests can be approved once with `once` or denied with `reject`. - The plugin checks daemon `/status` to maintain a connected, degraded, or disabled BLE forwarding state. State changes are reported with OpenCode TUI notifications when `client.tui.showToast` is available. -- If BLE forwarding is disabled or the BLE daemon cannot return a permission +- If BLE forwarding is disabled or the ESP-BLE-UART Daemon cannot return a permission decision, the plugin replies `reject`. ## Message routing @@ -259,7 +263,7 @@ permission requests can be approved once with `once` or denied with `reject`. ## Firmware protocol reference -The BLE UART Bridge daemon wraps plugin messages into JSONL over BLE UART. For +The ESP-BLE-UART Daemon wraps plugin messages into JSONL over BLE UART. For request/response RPC, `POST /request` sends a non-empty bridge request ID: ```json @@ -410,4 +414,4 @@ and truncated before crossing BLE. ## Open items -- Add an integration test with a mocked BLE daemon. +- Add an integration test with a mocked ESP-BLE-UART Daemon. diff --git a/tools/ble/ble_uart_bridge/demos/opencode/src/ble-daemon-client.ts b/tools/ble/ble_uart_bridge/demos/opencode/src/ble-daemon-client.ts index 1a70326c2c6..be73b26e43a 100644 --- a/tools/ble/ble_uart_bridge/demos/opencode/src/ble-daemon-client.ts +++ b/tools/ble/ble_uart_bridge/demos/opencode/src/ble-daemon-client.ts @@ -7,7 +7,7 @@ import { isPermissionDecision } from "./opencode-permission-reply" import type { BridgeResponse, DaemonResponse, DaemonStatus } from "./types" /** - * Check whether the local BLE daemon is reachable. + * Check whether the local ESP-BLE-UART Daemon is reachable. * * This probe is intentionally silent: OpenCode loads plugins during startup, so * a missing optional daemon must not print fetch errors into the OpenCode UI. @@ -25,13 +25,13 @@ export async function isDaemonAvailable(): Promise { export async function getDaemonStatus(): Promise { const response = await fetch(`${BLE_DAEMON_URL}/status`) if (!response.ok) { - throw new Error(`BLE daemon status failed: HTTP ${response.status}`) + throw new Error(`ESP-BLE-UART Daemon status check failed: HTTP ${response.status}`) } return (await response.json()) as DaemonStatus } /** - * Normalize the BLE daemon's response envelope into a permission response. + * Normalize the ESP-BLE-UART Daemon's response envelope into a permission response. * * The daemon supports both nested `data`/`response` envelopes and a direct * top-level decision. Keeping that tolerance here prevents transport quirks @@ -61,11 +61,11 @@ function parseBridgeResponse(body: DaemonResponse): BridgeResponse { return body as BridgeResponse } - throw new Error(`BLE daemon returned an invalid response payload: ${JSON.stringify(body)}`) + throw new Error(`ESP-BLE-UART Daemon returned an invalid response payload: ${JSON.stringify(body)}`) } /** - * Send a one-way notification to the BLE daemon. + * Send a one-way notification to the ESP-BLE-UART Daemon. * * Use this for events such as session status updates or permission cancellation, * where the BLE device should update its UI but OpenCode is not waiting for a @@ -79,12 +79,12 @@ export async function notifyBLE(op: string, data: unknown): Promise { }) if (!response.ok) { - throw new Error(`BLE daemon notify failed: HTTP ${response.status}`) + throw new Error(`ESP-BLE-UART Daemon notify failed: HTTP ${response.status}`) } } /** - * Send a request to the BLE daemon and wait for a structured response. + * Send a request to the ESP-BLE-UART Daemon and wait for a structured response. * * Permission prompts use this path because OpenCode cannot continue until the * BLE device returns a decision or the request times out. @@ -101,7 +101,7 @@ export async function sendRequestToBLE(op: string, data: unknown, timeoutSeconds }) if (!response.ok) { - throw new Error(`BLE daemon request failed: HTTP ${response.status}`) + throw new Error(`ESP-BLE-UART Daemon request failed: HTTP ${response.status}`) } return parseBridgeResponse((await response.json()) as DaemonResponse) diff --git a/tools/ble/ble_uart_bridge/demos/opencode/src/config.ts b/tools/ble/ble_uart_bridge/demos/opencode/src/config.ts index 92b67734c12..3e60b0b5be7 100644 --- a/tools/ble/ble_uart_bridge/demos/opencode/src/config.ts +++ b/tools/ble/ble_uart_bridge/demos/opencode/src/config.ts @@ -31,7 +31,7 @@ export const METADATA_DISPLAY_KEYS = ["command", "path", "url"] as const /** Enables verbose local console logging when OPENCODE_BLE_DEBUG=1. */ export const DEBUG = process.env.OPENCODE_BLE_DEBUG === "1" -/** HTTP base URL of the local BLE daemon that bridges OpenCode to the BLE device. */ +/** HTTP base URL of the local ESP-BLE-UART Daemon that bridges OpenCode to the BLE device. */ export const BLE_DAEMON_URL = process.env.OPENCODE_BLE_DAEMON_URL ?? "http://127.0.0.1:8888" const DEFAULT_DECISION_TIMEOUT_SECONDS = 60 diff --git a/tools/ble/ble_uart_bridge/demos/opencode/src/opencode-ble-uart-bridge.ts b/tools/ble/ble_uart_bridge/demos/opencode/src/opencode-ble-uart-bridge.ts index 9c7e14694cd..980f405cf61 100644 --- a/tools/ble/ble_uart_bridge/demos/opencode/src/opencode-ble-uart-bridge.ts +++ b/tools/ble/ble_uart_bridge/demos/opencode/src/opencode-ble-uart-bridge.ts @@ -39,12 +39,12 @@ function stateMessage(state: BLEPluginState, status?: DaemonStatus): string { status?.reconnect_failures !== undefined && status.max_reconnect_failures !== undefined ? ` (${status.reconnect_failures}/${status.max_reconnect_failures} reconnect failures)` : "" - return `BLE UART daemon is reachable, but the device is disconnected${attempts}. The next BLE send will try to reconnect.` + return `ESP-BLE-UART Daemon is reachable, but the device is disconnected${attempts}. The next BLE send will try to reconnect.` } if (status?.daemon_state === "exiting") { - return "BLE UART daemon is exiting after repeated reconnect failures. BLE forwarding is disabled." + return "ESP-BLE-UART Daemon is exiting after repeated reconnect failures. BLE forwarding is disabled." } - return "BLE UART daemon is unreachable. BLE forwarding is disabled until the daemon is available." + return "ESP-BLE-UART Daemon is unreachable. BLE forwarding is disabled until the daemon is available." } async function notifyStateChange( @@ -54,7 +54,7 @@ async function notifyStateChange( ): Promise { const variant = state === "connected" ? "success" : state === "degraded" ? "warning" : "error" const message = stateMessage(state, status) - await showToastBestEffort(client, variant, "OpenCode BLE UART Bridge", message) + await showToastBestEffort(client, variant, "OpenCode ESP-BLE-UART Bridge", message) await appLogBestEffort(client, variant === "error" ? "error" : variant === "warning" ? "warn" : "info", message, { state, status, @@ -95,7 +95,7 @@ export const BLEDeviceBridgePlugin: Plugin = async ({ client, serverUrl, directo bleState = "disabled" if (shouldNotify) { await notifyStateChange(openCodeClient, "disabled") - await appLogBestEffort(openCodeClient, "warn", "BLE UART daemon status check failed", { error: String(error) }) + await appLogBestEffort(openCodeClient, "warn", "ESP-BLE-UART Daemon status check failed", { error: String(error) }) } } return bleState @@ -124,7 +124,7 @@ export const BLEDeviceBridgePlugin: Plugin = async ({ client, serverUrl, directo // Forwarding session status is best-effort background work. Do not // await this async IIFE from the OpenCode event callback; otherwise a - // slow or unavailable BLE daemon could block OpenCode's own event loop. + // slow or unavailable ESP-BLE-UART Daemon could block OpenCode's own event loop. void (async () => { try { const previousState = bleState @@ -139,7 +139,7 @@ export const BLEDeviceBridgePlugin: Plugin = async ({ client, serverUrl, directo await showToastBestEffort( openCodeClient, "success", - "OpenCode BLE UART Bridge", + "OpenCode ESP-BLE-UART Bridge", "BLE UART device is connected for this OpenCode session.", ) } @@ -149,8 +149,8 @@ export const BLEDeviceBridgePlugin: Plugin = async ({ client, serverUrl, directo if ( // If the session became idle while a BLE permission prompt is - // active, mark that prompt as externally resolved and ask the BLE - // daemon to dismiss it. This prevents an old prompt from being + // active, mark that prompt as externally resolved and ask the + // ESP-BLE-UART Daemon to dismiss it. This prevents an old prompt from being // answered after OpenCode no longer needs the decision. Only idle // triggers this cancellation: busy/retry are normal activity // transitions and should not dismiss an actively displayed prompt. diff --git a/tools/ble/ble_uart_bridge/demos/opencode/src/opencode-permission-reply.ts b/tools/ble/ble_uart_bridge/demos/opencode/src/opencode-permission-reply.ts index 8d4fe00c37c..b966c4bcdd7 100644 --- a/tools/ble/ble_uart_bridge/demos/opencode/src/opencode-permission-reply.ts +++ b/tools/ble/ble_uart_bridge/demos/opencode/src/opencode-permission-reply.ts @@ -174,7 +174,7 @@ export async function replyToOpenCodePermission( throw new Error("OpenCode client does not expose a permission reply API") } -/** Runtime guard for permission decisions parsed from BLE daemon JSON. */ +/** Runtime guard for permission decisions parsed from ESP-BLE-UART Daemon JSON. */ export function isPermissionDecision(value: unknown): value is PermissionDecision { return value === "once" || value === "reject" } diff --git a/tools/ble/ble_uart_bridge/demos/opencode/src/permission-payload.ts b/tools/ble/ble_uart_bridge/demos/opencode/src/permission-payload.ts index 9507de4cc22..a5903a8fea0 100644 --- a/tools/ble/ble_uart_bridge/demos/opencode/src/permission-payload.ts +++ b/tools/ble/ble_uart_bridge/demos/opencode/src/permission-payload.ts @@ -9,7 +9,7 @@ import { } from "./config" import type { PermissionEventProperties } from "./types" -/** Create a unique event ID for messages sent to the BLE daemon. */ +/** Create a unique event ID for messages sent to the ESP-BLE-UART Daemon. */ function eventID() { return crypto.randomUUID() } @@ -116,7 +116,7 @@ export function permissionRequestID(permission: PermissionEventProperties): stri } /** - * Build the protocol message sent to the BLE daemon whenever OpenCode's session + * Build the protocol message sent to the ESP-BLE-UART Daemon whenever OpenCode's session * state changes. This is a one-way notification, so the BLE device can update * its UI but is not expected to send a reply. */ @@ -135,7 +135,7 @@ export function buildSessionStatusPayload( } /** - * Tell the BLE daemon to dismiss any permission prompt for this session. + * Tell the ESP-BLE-UART Daemon to dismiss any permission prompt for this session. * * This is used when OpenCode has already moved on, for example after the * session becomes idle before the BLE device returns a decision. @@ -156,7 +156,7 @@ export function buildPermissionCancelPayload(sessionID: string) { /** * Build the BLE permission prompt payload from an OpenCode permission event. * - * This is the main protocol boundary between OpenCode and the BLE daemon. The + * This is the main protocol boundary between OpenCode and the ESP-BLE-UART Daemon. The * outer fields describe routing and reply behavior; the nested `payload` fields * are intentionally small and display-oriented for the device UI. */ diff --git a/tools/ble/ble_uart_bridge/demos/opencode/src/permission-queue.ts b/tools/ble/ble_uart_bridge/demos/opencode/src/permission-queue.ts index e3ef49b254a..e1e6f66c7b7 100644 --- a/tools/ble/ble_uart_bridge/demos/opencode/src/permission-queue.ts +++ b/tools/ble/ble_uart_bridge/demos/opencode/src/permission-queue.ts @@ -174,7 +174,7 @@ async function handlePermissionQueueItem(item: PermissionQueueItem): Promise -# Porting BLE UART Bridge to Custom Scripts +# Porting ESP-BLE-UART Bridge to Custom Scripts -This guide explains how to reuse BLE UART Bridge in your own Python scripts. +This guide explains how to reuse ESP-BLE-UART Bridge in your own Python scripts. Use the Core API when the Console and Daemon are not the right abstraction for your application. For example, use Core directly when you want to implement custom framing, a test harness, a device provisioning flow, or a domain-specific automation script. @@ -18,7 +18,7 @@ Use the Core API when the Console and Daemon are not the right abstraction for y ## Install dependencies -You can reuse the ESP-IDF Python environment, or use your own Python virtual environment. If you reuse the ESP-IDF environment, export it first and then install the extra dependencies required by BLE UART Bridge: +You can reuse the ESP-IDF Python environment, or use your own Python virtual environment. If you reuse the ESP-IDF environment, export it first and then install the extra dependencies required by ESP-BLE-UART Bridge: ```bash cd $IDF_PATH diff --git a/tools/ble/ble_uart_bridge/docs/Profile-Compatibility.md b/tools/ble/ble_uart_bridge/docs/Profile-Compatibility.md index fc88264786b..ca2203849a3 100644 --- a/tools/ble/ble_uart_bridge/docs/Profile-Compatibility.md +++ b/tools/ble/ble_uart_bridge/docs/Profile-Compatibility.md @@ -1,16 +1,16 @@ -# BLE UART Profile Compatibility +# ESP-BLE-UART Profile Compatibility -BLE UART Bridge works with BLE GATT profiles that provide a UART-like data path: +ESP-BLE-UART Bridge works with BLE GATT profiles that provide a UART-like data path: - one characteristic that the host writes to - one characteristic that the device uses to notify data back to the host The default profile matches the widely used BLE UART-over-GATT UUID set (service `6E400001-…`, RX/TX characteristics), but that layout is not the only possible BLE UART-style profile. -## Default BLE-UART-compatible profile +## Default BLE UART-compatible profile The built-in default profile uses these UUIDs: @@ -33,7 +33,7 @@ ESP-IDF includes BLE SPP examples that implement Espressif BLE UART-like vendor- BLE SPP over BLE is not a Bluetooth SIG standard profile. It is a vendor-specific GATT design that emulates a serial link, similar in purpose to the default BLE UART layout above. -ESP-IDF BLE SPP examples may define more characteristics than BLE UART Bridge needs, such as data, command, and status characteristics. To use BLE UART Bridge with such a profile, map only the UART-like data path into `BLEUARTProfile`. +ESP-IDF BLE SPP examples may define more characteristics than ESP-BLE-UART Bridge needs, such as data, command, and status characteristics. To use ESP-BLE-UART Bridge with such a profile, map only the UART-like data path into `BLEUARTProfile`. ## Mapping an ESP-IDF BLE SPP profile @@ -63,22 +63,22 @@ bridge = BLEUARTBridge("AA:BB:CC:DD:EE:FF", profile=profile) Replace the UUIDs with the actual UUIDs used by the device firmware. -## What BLE UART Bridge does not map +## What ESP-BLE-UART Bridge does not map -BLE UART Bridge is intentionally focused on the data path. It does not automatically map extra control-plane characteristics that a profile may expose, such as: +ESP-BLE-UART Bridge is intentionally focused on the data path. It does not automatically map extra control-plane characteristics that a profile may expose, such as: - command characteristics - status characteristics - custom configuration characteristics - profile-specific flow-control semantics -If an application needs those characteristics, implement that logic in a custom script on top of `bleak`, or extend BLE UART Bridge for that specific profile. +If an application needs those characteristics, implement that logic in a custom script on top of `bleak`, or extend ESP-BLE-UART Bridge for that specific profile. ## Classic Bluetooth SPP is different Classic Bluetooth SPP examples, such as `examples/bluetooth/bluedroid/classic_bt/bt_spp_*`, are not BLE GATT profiles. -They use Classic Bluetooth SPP rather than BLE GATT characteristics, so they are not compatible with BLE UART Bridge. +They use Classic Bluetooth SPP rather than BLE GATT characteristics, so they are not compatible with ESP-BLE-UART Bridge. ## Related docs diff --git a/tools/ble/ble_uart_bridge/docs/Quick-Start-BLE-UART-Console.md b/tools/ble/ble_uart_bridge/docs/Quick-Start-BLE-UART-Console.md index 5570ff2195d..7d435a7d464 100644 --- a/tools/ble/ble_uart_bridge/docs/Quick-Start-BLE-UART-Console.md +++ b/tools/ble/ble_uart_bridge/docs/Quick-Start-BLE-UART-Console.md @@ -1,16 +1,16 @@ -# Quick Start: BLE UART Console +# Quick Start: ESP-BLE-UART Console -This guide shows how to use the BLE UART Console for quick manual testing. +This guide shows how to use the ESP-BLE-UART Console for quick manual testing. The Console is useful when you want to type data into a BLE UART device and inspect the bytes or text sent back by the device. ## Prerequisites 1. A host machine with Bluetooth access. -2. Python environment prepared. You can reuse the ESP-IDF Python environment, or use your own Python virtual environment. If you reuse the ESP-IDF environment, export it first and then install the BLE UART Bridge dependencies: +2. Python environment prepared. You can reuse the ESP-IDF Python environment, or use your own Python virtual environment. If you reuse the ESP-IDF environment, export it first and then install the ESP-BLE-UART Bridge dependencies: ```bash cd $IDF_PATH @@ -21,7 +21,7 @@ The Console is useful when you want to type data into a BLE UART device and insp On Windows, run `export.bat` or `export.ps1` from the ESP-IDF root directory before installing `requirements.txt`. If you use your own Python virtual environment instead, activate it before installing `requirements.txt`. -3. A BLE device advertising the BLE UART service. By default the tool scans for the de-facto BLE UART-over-GATT UUIDs (`6E400001-…` / `…02` / `…03`). For a known-compatible test target, build and flash the [BLE UART Service example](../../../../examples/bluetooth/ble_uart_service), which acts as an Echo Server by echoing RX writes back through TX notifications. +3. A BLE device advertising the BLE UART service. By default the tool scans for the de-facto BLE UART-over-GATT UUIDs (`6E400001-…` / `…02` / `…03`). For a known-compatible test target, build and flash the [ESP-BLE-UART example](../../../../examples/bluetooth/ble_uart_service), which acts as an Echo Server by echoing RX writes back through TX notifications. ## Find a device @@ -33,7 +33,7 @@ python main.py list-devices Example output may include a device address and name: ```text -Found: AA:BB:CC:DD:EE:FF, with name esp-ble-uart, rssi=-42 +Found: AA:BB:CC:DD:EE:FF, with name BleUart-XXXX, rssi=-42 ``` Use the printed device identifier as `DEVICE_ID`. On macOS, this identifier is a CoreBluetooth UUID and is different from the device MAC address. @@ -131,9 +131,9 @@ This affects BLE GATT write behavior only. It does not create an application-lev ## Common examples -### ESP-IDF BLE UART Echo Server +### ESP-BLE-UART Echo Server -Use the [BLE UART Service example](../../../../examples/bluetooth/ble_uart_service) when you want a ready-made ESP-IDF Echo Server for testing BLE UART Bridge Console. After building, flashing, and pairing with the example, open Console and type any text; the example should echo the same data back as `[RX]` output. +Use the [ESP-BLE-UART example](../../../../examples/bluetooth/ble_uart_service) when you want a ready-made ESP-IDF Echo Server for testing ESP-BLE-UART Bridge Console. After building, flashing, and pairing with the example, open Console and type any text; the example should echo the same data back as `[RX]` output. ```bash # List nearby BLE devices and use the printed device ID as DEVICE_ID diff --git a/tools/ble/ble_uart_bridge/docs/Quick-Start-BLE-UART-Daemon.md b/tools/ble/ble_uart_bridge/docs/Quick-Start-BLE-UART-Daemon.md index 32f67f7c6f9..bf130feef71 100644 --- a/tools/ble/ble_uart_bridge/docs/Quick-Start-BLE-UART-Daemon.md +++ b/tools/ble/ble_uart_bridge/docs/Quick-Start-BLE-UART-Daemon.md @@ -1,16 +1,16 @@ -# Quick Start: BLE UART Daemon +# Quick Start: ESP-BLE-UART Daemon -This guide shows how to use BLE UART Daemon mode and the lightweight JSONL RPC protocol used between the host and the BLE device. +This guide shows how to use ESP-BLE-UART Daemon mode and the lightweight JSONL RPC protocol used between the host and the BLE device. Daemon mode is useful when another local process needs to communicate with a BLE UART device without owning the BLE connection itself. ## Prerequisites 1. A host machine with Bluetooth access. -2. Python environment prepared. You can reuse the ESP-IDF Python environment, or use your own Python virtual environment. If you reuse the ESP-IDF environment, export it first and then install the BLE UART Bridge dependencies: +2. Python environment prepared. You can reuse the ESP-IDF Python environment, or use your own Python virtual environment. If you reuse the ESP-IDF environment, export it first and then install the ESP-BLE-UART Bridge dependencies: ```bash cd $IDF_PATH diff --git a/tools/ble/ble_uart_bridge/src/console/api.py b/tools/ble/ble_uart_bridge/src/console/api.py index e8e01ebb058..3e193472ae7 100644 --- a/tools/ble/ble_uart_bridge/src/console/api.py +++ b/tools/ble/ble_uart_bridge/src/console/api.py @@ -16,7 +16,7 @@ def run_console( encoding: ConsoleEncoding | str = ConsoleEncoding.text, with_response: bool = False, ) -> None: - # Initialize BLE UART Console + # Initialize ESP-BLE-UART Console console = BLEUARTConsole( device_id, terminator=terminator, diff --git a/tools/ble/ble_uart_bridge/src/console/console.py b/tools/ble/ble_uart_bridge/src/console/console.py index ffa8079689c..23d563e38c9 100644 --- a/tools/ble/ble_uart_bridge/src/console/console.py +++ b/tools/ble/ble_uart_bridge/src/console/console.py @@ -130,14 +130,14 @@ class BLEUARTConsole(App): try: # Should try connection to catch KeyInterrupt during connection establishment if not await self._bridge.connect(): - logger.error(f'Failed to open BLE UART Console for {self._device_id}') + logger.error(f'Failed to open ESP-BLE-UART Console for {self._device_id}') return # Run UI event loop await self.run_async() finally: # Disconnect from device - logger.info(f'Closing BLE UART Console for {self._device_id}...') + logger.info(f'Closing ESP-BLE-UART Console for {self._device_id}...') await self._bridge.disconnect() # Textual lifecycle hook: build the widget tree before the app is mounted. @@ -166,7 +166,7 @@ class BLEUARTConsole(App): # Textual lifecycle hook: widgets are ready, so BLE can be connected and UI updated. async def on_mount(self) -> None: self._ui_ready = True - self.title = f'BLE UART — {self._device_id}' + self.title = f'ESP-BLE-UART — {self._device_id}' self.query_one('#input', Input).focus() self._write_info(f'Connected to {self._device_id}') self._drain_rx_pending() diff --git a/tools/ble/ble_uart_bridge/src/core/api.py b/tools/ble/ble_uart_bridge/src/core/api.py index c0b174bface..5966606ca98 100644 --- a/tools/ble/ble_uart_bridge/src/core/api.py +++ b/tools/ble/ble_uart_bridge/src/core/api.py @@ -11,7 +11,7 @@ def run_list_devices() -> None: def run_connection_check(device_id: str) -> None: - # Initialize BLE UART Bridge + # Initialize ESP-BLE-UART Bridge bridge = BLEUARTBridge(device_id) # Connection check diff --git a/tools/ble/ble_uart_bridge/src/core/errors.py b/tools/ble/ble_uart_bridge/src/core/errors.py index 936be35af42..6c8dca31a87 100644 --- a/tools/ble/ble_uart_bridge/src/core/errors.py +++ b/tools/ble/ble_uart_bridge/src/core/errors.py @@ -5,15 +5,15 @@ from __future__ import annotations class BUBError(Exception): - """Base exception for the BLE UART Bridge.""" + """Base exception for the ESP-BLE-UART Bridge.""" class DeviceNotFoundError(BUBError): - """Raised when the requested BLE UART device cannot be found.""" + """Raised when the requested device cannot be found.""" class ConnectionTimeout(BUBError): - """Raised when the client cannot connect to a BLE UART device.""" + """Raised when the client cannot connect to the device.""" class NotConnectedError(BUBError): diff --git a/tools/ble/ble_uart_bridge/src/daemon/api.py b/tools/ble/ble_uart_bridge/src/daemon/api.py index 54af7d882da..44eebc8f0ff 100644 --- a/tools/ble/ble_uart_bridge/src/daemon/api.py +++ b/tools/ble/ble_uart_bridge/src/daemon/api.py @@ -34,9 +34,9 @@ def _request_json( detail = e.read().decode(errors='replace') raise RuntimeError(f'Daemon request failed with HTTP {e.code}: {detail}') from e except TimeoutError as e: - raise RuntimeError(f'Timed out waiting for BLE UART Daemon: {url}') from e + raise RuntimeError(f'Timed out waiting for ESP-BLE-UART Daemon: {url}') from e except URLError as e: - raise RuntimeError(f'Failed to connect to BLE UART Daemon: {e.reason}') from e + raise RuntimeError(f'Failed to connect to ESP-BLE-UART Daemon: {e.reason}') from e if not body: return {} diff --git a/tools/ble/ble_uart_bridge/src/daemon/server.py b/tools/ble/ble_uart_bridge/src/daemon/server.py index 3f4235ead86..33510956537 100644 --- a/tools/ble/ble_uart_bridge/src/daemon/server.py +++ b/tools/ble/ble_uart_bridge/src/daemon/server.py @@ -29,7 +29,7 @@ async def lifespan(app: FastAPI) -> AsyncIterator[None]: app.state.bridge = BLEUARTBridge(app.state.device_id) app.state.request_lock = asyncio.Lock() - # Set BLE UART Bridge RX callback + # Set ESP-BLE-UART Bridge RX callback loop = asyncio.get_running_loop() app.state.rx_buffer = bytearray() app.state.pending_requests = {} @@ -53,8 +53,8 @@ async def lifespan(app: FastAPI) -> AsyncIterator[None]: # Try to connect to the device if not await app.state.bridge.connect(): - logger.error('Failed to start BLE UART Daemon!') - raise RuntimeError('Failed to start BLE UART Daemon!') + logger.error('Failed to start ESP-BLE-UART Daemon!') + raise RuntimeError('Failed to start ESP-BLE-UART Daemon!') yield @@ -62,7 +62,7 @@ async def lifespan(app: FastAPI) -> AsyncIterator[None]: await app.state.bridge.disconnect() -app = FastAPI(title='BLE UART Daemon', lifespan=lifespan) +app = FastAPI(title='ESP-BLE-UART Daemon', lifespan=lifespan) def _request_data_size(data: object) -> int: diff --git a/tools/bt/ble_log_console/console.py b/tools/bt/ble_log_console/console.py index 071761c0320..d2a449d0d03 100644 --- a/tools/bt/ble_log_console/console.py +++ b/tools/bt/ble_log_console/console.py @@ -12,7 +12,7 @@ Usage: from datetime import datetime from pathlib import Path -import click +import rich_click as click from src.app import BLELogApp from src.backend.models import format_bytes from src.backend.uart_transport import validate_uart_port diff --git a/tools/bt/bt_hci_to_btsnoop/bt_hci_to_btsnoop.py b/tools/bt/bt_hci_to_btsnoop/bt_hci_to_btsnoop.py index 19c217c7c4c..a9b858c4b61 100644 --- a/tools/bt/bt_hci_to_btsnoop/bt_hci_to_btsnoop.py +++ b/tools/bt/bt_hci_to_btsnoop/bt_hci_to_btsnoop.py @@ -1,11 +1,14 @@ -# SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD +# SPDX-FileCopyrightText: 2024-2026 Espressif Systems (Shanghai) CO LTD # SPDX-License-Identifier: Apache-2.0 -import argparse import os import re import struct import time +import rich_click as click +from esp_pylib.logger import log +from rich.markup import escape + def create_new_bt_snoop_file(filename: str) -> None: with open(filename, 'wb') as f: @@ -19,9 +22,9 @@ def create_new_bt_snoop_file(filename: str) -> None: def append_hci_to_bt_snoop_file(filename: str, direction: int, data: str, timestamp_us: int) -> None: if os.path.exists(filename): - print(f'Appending to existing file: {filename}') + log.print(f'Appending to existing file: {escape(filename)}') else: - print(f'Creating new file: {filename}') + log.print(f'Creating new file: {escape(filename)}') create_new_bt_snoop_file(filename) data_bytes = bytearray.fromhex(data) with open(filename, 'ab') as f: @@ -39,8 +42,7 @@ def log_data_clean(data: str) -> str: def parse_log(input_path: str, output_tag: str, has_timestamp: bool = True) -> None: if not os.path.exists(input_path): - print(f"Error: The file '{input_path}' does not exist.") - return + log.die(f"The file '{escape(input_path)}' does not exist.") output_dir = './parsed_logs' os.makedirs(output_dir, exist_ok=True) output_file = os.path.join(output_dir, f'parsed_log_{output_tag}.btsnoop.log') @@ -112,28 +114,38 @@ def parse_log(input_path: str, output_tag: str, has_timestamp: bool = True) -> N append_hci_to_bt_snoop_file(output_file, direction, hci_data, timestamp_us) parsed_num += 1 except Exception as e: - print(f'Exception: {e}') + log.warn(f'Exception: {escape(str(e))}') if parsed_num > 0: - print( - f'Parsing completed, parsed_num {parsed_num}, all_line_num {all_line_num}.\nOutput saved to: {output_file}' + log.print( + f'Parsing completed, parsed_num {parsed_num}, all_line_num {all_line_num}.\n' + f'Output saved to: {escape(output_file)}' ) else: - print('No data could be parsed.') + log.warn('No data could be parsed.') + + +@click.command( + context_settings={'help_option_names': ['-h', '--help']}, + help='Parse Bluetooth HCI logs and convert them to BTSnoop format.', +) +@click.option('-p', '--path', required=True, help='Path to the input log file') +@click.option('-o', '--output', required=True, help='Name tag for the output file') +@click.option( + '--has-ts', + is_flag=True, + default=False, + help='Set this if the input file has timestamp information as part of packets (default: False)', +) +def cli(path: str, output: str, has_ts: bool) -> None: + parse_log(path, output, has_timestamp=has_ts) def main() -> None: - parser = argparse.ArgumentParser(description='Log Parsing Tool') - parser.add_argument('-p', '--path', required=True, help='Path to the input log file') - parser.add_argument('-o', '--output', required=True, help='Name tag for the output file') - parser.add_argument( - '--has-ts', - action='store_true', - default=False, - help='Set this if the input file has timestamp information as part of packets (default: False)', - ) - args = parser.parse_args() - parse_log(args.path, args.output, has_timestamp=args.has_ts) + cli() if __name__ == '__main__': + from esp_pylib.excepthook import install_exception_reporting + + install_exception_reporting() main() diff --git a/tools/ci/astyle-rules.yml b/tools/ci/astyle-rules.yml index f2f51aa9463..4ba573982dc 100644 --- a/tools/ci/astyle-rules.yml +++ b/tools/ci/astyle-rules.yml @@ -155,6 +155,8 @@ components_not_formatted_permanent: - /components/esp_system/openocd_stub_bins/*.inc - /components/esp_system/openocd_stub_bins/esp32c6/*.inc - /components/esp_system/openocd_stub_bins/esp32h2/*.inc + # TEE ASM helper macros — .inc file, not C include files + - /components/esp_tee/subproject/main/arch/riscv/*.inc docs: # Docs directory contains some .inc files, which are not C include files diff --git a/tools/ci/configure_ci_environment.sh b/tools/ci/configure_ci_environment.sh index 3388fa4c75e..54f2e03159f 100644 --- a/tools/ci/configure_ci_environment.sh +++ b/tools/ci/configure_ci_environment.sh @@ -36,9 +36,13 @@ fi # https://ccache.dev/manual/latest.html#_configuring_ccache # Set ccache base directory to the project checkout path, to cancel out differences between runners export CCACHE_BASEDIR="${IDF_PATH}" +export CCACHE_COMPILERCHECK="${CCACHE_COMPILERCHECK:-content}" # host mapping volume to share ccache fbetween runner concurrent jobs -export CCACHE_SLOPPINESS="time_macros" +export CCACHE_SLOPPINESS="time_macros,file_macro,include_file_mtime,include_file_ctime" + +# Keep per-job statistics in the checkout directory while sharing the cache itself. +export CCACHE_STATSLOG="${CCACHE_STATSLOG:-${IDF_PATH}/.ccache-stats.log}" # CCACHE_RECACHE Used when invalidating the current cache. # could be enabled by MR label "ccache:recache" diff --git a/tools/ci/mypy_ignore_list.txt b/tools/ci/mypy_ignore_list.txt index 00e7789276b..703e2544542 100644 --- a/tools/ci/mypy_ignore_list.txt +++ b/tools/ci/mypy_ignore_list.txt @@ -26,7 +26,6 @@ examples/protocols/esp_local_ctrl/scripts/transport/transport_http.py examples/storage/parttool/parttool_example.py examples/system/ota/otatool/get_running_partition.py examples/system/ota/otatool/otatool_example.py -tools/ble/ble_uart_bridge/src/console/console.py tools/ble/ble_uart_bridge/src/daemon/jsonl.py tools/ble/lib_ble_client.py tools/ble/lib_gap.py diff --git a/tools/cmake/version.cmake b/tools/cmake/version.cmake index f37890ba32e..4952aab171d 100644 --- a/tools/cmake/version.cmake +++ b/tools/cmake/version.cmake @@ -1,5 +1,5 @@ set(IDF_VERSION_MAJOR 6) set(IDF_VERSION_MINOR 0) -set(IDF_VERSION_PATCH 1) +set(IDF_VERSION_PATCH 2) set(ENV{IDF_VERSION} "${IDF_VERSION_MAJOR}.${IDF_VERSION_MINOR}.${IDF_VERSION_PATCH}") diff --git a/tools/cmakev2/manager.cmake b/tools/cmakev2/manager.cmake index ed90fb4f57d..66bc7293d6a 100644 --- a/tools/cmakev2/manager.cmake +++ b/tools/cmakev2/manager.cmake @@ -293,23 +293,46 @@ function(__inject_requirements_for_component_from_manager component_name) idf_build_get_property(component_manager_interface_version IDF_COMPONENT_MANAGER_INTERFACE_VERSION) idf_build_get_property(idf_path IDF_PATH) idf_build_get_property(component_prefix PREFIX) - idf_component_get_property(component_source "${component_name}" COMPONENT_SOURCE) idf_component_get_property(component_dir "${component_name}" COMPONENT_DIR) # The component manager will inject requirements for this component. To do this, it needs to files: # - # 1. An input file which states the component's source type. This is a minimal build system v1-style file - # which contains the component's source type. To make the component manager happy, we create a file with - # shim __component_set_property(), which calls idf_component_set_property(). The component manager will - # modify this file by adding the component's requirements. TODO: Improve this. + # 1. An input file which seeds the component manager with one entry per + # discovered component, each carrying its __COMPONENT_SOURCE. This is a + # minimal build system v1-style file consumed by the manager via the + # shim __component_set_property(), which calls idf_component_set_property(). + # Seeding the whole project ensures handle_project_requirements()'s + # known_components matches the project-wide list v1 provides, so its + # _choose_component() can rewrite a manifest-declared namespaced dep + # (e.g. "lvgl__lvgl") to its locally-shadowing short name ("lvgl") when + # a local component shadows a managed dependency. Without seeding, the + # manager would only see the one component being processed and the + # rewrite would never fire. The manager then modifies this file by + # appending the component's resolved requirements. TODO: Improve this. # 2. A file which lists the components with manifests. This file is created by the component manager, # and is deleted after the component manager is done. This works for build system v1 where we provide # a global list of components with manifests. However, for build system v2, we need to provide this file # for each component. Hence, we create this file and place it in the build directory. + # Iterate COMPONENT_INTERFACES (not COMPONENTS_DISCOVERED) and read each + # component's properties via __idf_component_get_property_unchecked so the + # COMPONENT_SOURCE comes from the component's own interface target rather + # than the alias-aware lookup. After __init_component_interface_cache + # short-name aliasing fires (e.g. a higher-priority "example__cmp" rebinds + # the cache entry "cmp" to its own interface), the name-based getter + # returns the alias target's source -- in that case both "cmp" and + # "example__cmp" would be seeded as "project_managed_components" and the + # manager's _override_requirements_by_component_sources would reject the + # duplicate. set(out_file "${build_dir}/component_requires.${component_name}.temp.cmake") - set(cmgr_target "___${component_prefix}_${component_name}") - # We only provide component source to the component manager - file(WRITE "${out_file}" "__component_set_property(${cmgr_target} __COMPONENT_SOURCE \"${component_source}\")\n") + idf_build_get_property(component_interfaces COMPONENT_INTERFACES) + set(requires_content "") + foreach(seed_interface IN LISTS component_interfaces) + __idf_component_get_property_unchecked(seed_name "${seed_interface}" COMPONENT_NAME) + __idf_component_get_property_unchecked(seed_source "${seed_interface}" COMPONENT_SOURCE) + string(APPEND requires_content + "__component_set_property(___${component_prefix}_${seed_name} __COMPONENT_SOURCE \"${seed_source}\")\n") + endforeach() + file(WRITE "${out_file}" "${requires_content}") # Create components_with_manifests_list.temp file with only this component if it has a manifest set(components_with_manifests_file "${build_dir}/components_with_manifests_list.temp") diff --git a/tools/idf.py b/tools/idf.py index 075ebfb8b58..e7e884f4786 100755 --- a/tools/idf.py +++ b/tools/idf.py @@ -156,9 +156,48 @@ def _safe_relpath(path: str, start: str | None = None) -> str: def init_cli(verbose_output: list | None = None) -> Any: - # Click is imported here to run it after check_environment() - import click + # rich-click is imported here to run it after check_environment() + import rich_click as click from click.shell_completion import CompletionItem + from rich_click import Context + from rich_click import RichHelpConfiguration + from rich_click.rich_click import MAX_WIDTH + + # ``RichHelpFormatter`` was promoted to the top-level namespace in + # rich-click 1.9; the submodule path is stable across 1.8.x/1.9.x. For + # positional/option base classes we deliberately use plain ``click.Argument`` + # / ``click.Option`` rather than the rich-click 1.9 ``RichArgument`` / + # ``RichOption`` subclasses: those subclasses are empty wrappers on 1.9.x + # (the only observable difference is the ``isinstance(obj, RichArgument)`` + # gate that auto-populates an Arguments panel when ``obj.help is not None`` + # -- idf.py never declares ``help=`` on a positional argument), and the + # symbols don't exist on 1.8.x at all. Using the click base classes keeps + # one code path for both rich-click lines. + from rich_click.rich_help_formatter import RichHelpFormatter + + # click 8.2 made ``Parameter.make_metavar(ctx)`` mandatory. rich-click + # versions before 1.8.6 still call ``param.make_metavar()`` with no ctx + # (see ``rich_click/rich_help_rendering.py``), which crashes with + # ``TypeError`` on click >= 8.2. The crash hits *every* parameter rich-click + # iterates -- including click's built-in ``--help`` option, which is a + # plain ``click.Option`` instance not under our control. So patch the + # ``Parameter.make_metavar`` method itself to accept ctx as optional, + # fishing the running context from ``click.get_current_context`` when + # rich-click forgets to pass it. The patch is a no-op on click < 8.2 + # (signature already takes only ``self``) and on click >= 8.2 it simply + # bridges the old rich-click call site. + if click.Parameter.make_metavar.__code__.co_argcount >= 2: + _orig_make_metavar = click.Parameter.make_metavar + + def _make_metavar_with_optional_ctx(self: 'click.Parameter', ctx: 'Context | None' = None) -> str: + if ctx is None: + try: + ctx = click.get_current_context() + except RuntimeError: + ctx = click.Context(click.Command(self.name or '_')) + return _orig_make_metavar(self, ctx) # type: ignore[no-any-return] + + click.Parameter.make_metavar = _make_metavar_with_optional_ctx # type: ignore[method-assign] class Deprecation: """Construct deprecation notice for help messages""" @@ -210,7 +249,7 @@ def init_cli(verbose_output: list | None = None) -> Any: text = text or '' return ('Deprecated! ' + text) if self.deprecated else text - def check_deprecation(ctx: click.core.Context) -> None: + def check_deprecation(ctx: Context) -> None: """Prints deprecation warnings for arguments in given context""" for option in ctx.command.params: default = () if option.multiple else option.default @@ -240,15 +279,13 @@ def init_cli(verbose_output: list | None = None) -> Any: self.action_args = action_args self.aliases = aliases - def __call__( - self, context: click.core.Context, global_args: PropertyDict, action_args: dict | None = None - ) -> None: + def __call__(self, context: Context, global_args: PropertyDict, action_args: dict | None = None) -> None: if action_args is None: action_args = self.action_args self.callback(self.name, context, global_args, **action_args) - class Action(click.Command): + class Action(click.RichCommand): callback: Callable def __init__( @@ -309,7 +346,7 @@ def init_cli(verbose_output: list | None = None) -> Any: self.callback: Callable = wrapped_callback - def invoke(self, ctx: click.core.Context) -> click.core.Context: + def invoke(self, ctx: Context) -> Context: if self.deprecated: deprecation = Deprecation(self.deprecated) message = deprecation.full_message(f'Command "{self.name}"') @@ -325,6 +362,26 @@ def init_cli(verbose_output: list | None = None) -> Any: check_deprecation(ctx) return super().invoke(ctx) + def format_options(self, ctx: Context, formatter: RichHelpFormatter) -> None: + """ + default_panels_first=True causes the + renderer to drop `post_default_panels` for options on non-Group + commands, which is exactly where the subcommand "Options" panel + lives -- `idf.py --help` would otherwise show only + Usage + description. Temporarily flip the flag to False while + rendering options. + """ + # default_panels_first=True is introduced in rich-click 1.9.6 + if not hasattr(formatter.config, 'default_panels_first'): + super().format_options(ctx, formatter) + return + prev_default_first = formatter.config.default_panels_first + try: + formatter.config.default_panels_first = False + super().format_options(ctx, formatter) + finally: + formatter.config.default_panels_first = prev_default_first + class Argument(click.Argument): """ Positional argument @@ -406,14 +463,14 @@ def init_cli(verbose_output: list | None = None) -> Any: if self.scope.is_global: self.help += ' This option can be used at most once either globally, or for one subcommand.' - def get_help_record(self, ctx: click.core.Context) -> Any: + def get_help_record(self, ctx: Context) -> Any: # Backport "hidden" parameter to click 5.0 if self.hidden: return None return super().get_help_record(ctx) - class CLI(click.Group): + class CLI(click.RichGroup): """Action list contains all actions with options available for CLI""" def __init__( @@ -421,13 +478,26 @@ def init_cli(verbose_output: list | None = None) -> Any: all_actions: dict | None = None, verbose_output: list | None = None, cli_help: str | None = None, + command_groups: dict[str, list[dict[str, Any]]] | None = None, ) -> None: + rich_help_config_kwargs: dict[str, Any] = { + 'max_width': MAX_WIDTH, + 'command_groups': command_groups if command_groups is not None else {}, + } + # ``default_panels_first`` was added in rich-click 1.9.6; on older + # versions passing it raises TypeError. + if hasattr(RichHelpConfiguration, 'default_panels_first'): + rich_help_config_kwargs['default_panels_first'] = True super().__init__( + PROG, chain=True, invoke_without_command=True, result_callback=self.execute_tasks, no_args_is_help=True, - context_settings={'max_content_width': 140}, + context_settings={ + 'help_option_names': ['-h', '--help'], + 'rich_help_config': RichHelpConfiguration(**rich_help_config_kwargs), + }, help=cli_help, ) self._actions = {} @@ -467,6 +537,7 @@ def init_cli(verbose_output: list | None = None) -> Any: options = [] self._actions[name] = Action(name=name, **action) + self.commands[name] = self._actions[name] for alias in [name] + action.get('aliases', []): self.commands_with_aliases[alias] = name @@ -492,10 +563,10 @@ def init_cli(verbose_output: list | None = None) -> Any: self._actions[name].params.append(option) - def list_commands(self, ctx: click.core.Context) -> list: + def list_commands(self, ctx: Context) -> list: return sorted(filter(lambda name: not self._actions[name].hidden, self._actions)) - def get_command(self, ctx: click.core.Context, name: str) -> Action | None: + def get_command(self, ctx: Context, name: str) -> Action | None: if name in self.commands_with_aliases: return self._actions.get(self.commands_with_aliases.get(name)) @@ -506,7 +577,7 @@ def init_cli(verbose_output: list | None = None) -> Any: return Action(name=name, callback=callback.unwrapped_callback) return None - def shell_complete(self, ctx: click.core.Context, incomplete: str) -> list[CompletionItem]: + def shell_complete(self, ctx: Context, incomplete: str) -> list[CompletionItem]: # Enable @-argument completion in bash only if @ is not present in # COMP_WORDBREAKS. When @ is included, the @-argument is not considered # part of the completion word, causing @-argument completion to function @@ -799,15 +870,14 @@ def init_cli(verbose_output: list | None = None) -> Any: build_dir: str = args.build_dir return os.path.abspath(build_dir) - def _extract_relevant_path(path: str) -> str: - """ - Returns part of the path starting from 'components' or 'managed_components'. - If neither is found, returns the full path. - """ - for keyword in ('components', 'managed_components'): - # arg path is loaded from project_description.json, where paths are always defined with '/' - if keyword in path.split('/'): - return keyword + path.split(keyword, 1)[1] + def _path_relative_to_project(path: str, project_dir: str) -> str: + """If ``path`` is under ``project_dir``, return its path relative to the project; else ``path`` unchanged.""" + path_abs = os.path.abspath(os.path.normpath(path)) + project_abs = os.path.abspath(os.path.normpath(project_dir)) + parent_prefix = project_abs.rstrip(os.sep) + os.sep + if path_abs == project_abs or path_abs.startswith(parent_prefix): + return _safe_relpath(path_abs, project_abs) + return path # Mutable dict used as a cache keyed by lock path @@ -879,6 +949,20 @@ def init_cli(verbose_output: list | None = None) -> Any: return lock_key in _get_trusted_names_from_lock(lock_path) return False + def _build_rich_help_command_groups( + external_panels: list[tuple[str, list[str]]], + ) -> dict[str, list[dict[str, Any]]]: + """Build ``command_groups`` for rich-click's ``RichHelpConfiguration``. + ``external_panels`` is a list of ``(title, command_names)`` from ``idf_ext.py`` extension + modules and from Python entry-point extensions. Those panels appear on + the root ``idf.py --help`` after the default Commands section. + """ + panels: list[dict[str, Any]] = [] + for title, cmds in external_panels: + if cmds: + panels.append({'name': title, 'commands': cmds}) + return {PROG: panels} if panels else {} + # That's a tiny parser that parse project-dir even before constructing # fully featured click parser to be sure that extensions are loaded from the right place @click.command( @@ -978,31 +1062,44 @@ def init_cli(verbose_output: list | None = None) -> Any: else: print_warning( f'WARNING: Not loading component extension from untrusted source ' - f'"{_extract_relevant_path(comp_dir)}". ' + f'"{_path_relative_to_project(comp_dir, project_dir)}". ' 'Only extensions from trusted sources are loaded. Run ' '"idf.py docs -sp api-guides/tools/idf-py.html#extending-idf-py" ' 'for the list of trusted sources. Set IDF_EXTENSION_ALLOW_UNTRUSTED=1 to load all.' ) # Load extensions from directories that participate in the build (components and project) + external_help_panels: list[tuple[str, list[str]]] = [] for ext_dir in component_idf_ext_dirs + [project_dir]: extension_func = load_cli_extension_from_dir(ext_dir) if extension_func: try: - all_actions = merge_action_lists(all_actions, custom_actions=extension_func(all_actions, project_dir)) + custom_actions = extension_func(all_actions, project_dir) + all_actions = merge_action_lists(all_actions, custom_actions=custom_actions) except Exception as e: print_warning(f'WARNING: Cannot load directory extension from "{ext_dir}": {e}') else: + panel_cmds = sorted(n for n in custom_actions.get('actions') or {} if n != 'fallback') + if panel_cmds: + panel_title = ( + 'Project' if ext_dir == project_dir else _path_relative_to_project(ext_dir, project_dir) + ) + external_help_panels.append((panel_title, panel_cmds)) if ext_dir != project_dir: - print(f'INFO: Loaded component extension from "{_extract_relevant_path(ext_dir)}"') + print(f'INFO: Loaded component extension from "{_path_relative_to_project(ext_dir, project_dir)}"') # Load extensions from Python entry points entry_point_extensions = load_cli_extensions_from_entry_points() - for name, extension_func in entry_point_extensions: + for ep_name, extension_func in entry_point_extensions: try: - all_actions = merge_action_lists(all_actions, custom_actions=extension_func(all_actions, project_dir)) + custom_actions = extension_func(all_actions, project_dir) + all_actions = merge_action_lists(all_actions, custom_actions=custom_actions) except Exception as e: - print_warning(f'WARNING: Cannot load entry point extension "{name}": {e}') + print_warning(f'WARNING: Cannot load entry point extension "{ep_name}": {e}') + else: + panel_cmds = sorted(n for n in (custom_actions.get('actions') or {}) if n != 'fallback') + if panel_cmds: + external_help_panels.append((ep_name, panel_cmds)) cli_help = ( 'ESP-IDF CLI build management tool. ' @@ -1010,7 +1107,13 @@ def init_cli(verbose_output: list | None = None) -> Any: f'Selected target: {get_target(project_dir)}' ) - return CLI(cli_help=cli_help, verbose_output=verbose_output, all_actions=all_actions) + help_command_groups = _build_rich_help_command_groups(external_help_panels) + return CLI( + cli_help=cli_help, + verbose_output=verbose_output, + all_actions=all_actions, + command_groups=help_command_groups, + ) def main(argv: list[Any] | None = None) -> None: diff --git a/tools/idf_py_actions/core_ext.py b/tools/idf_py_actions/core_ext.py index 82c12aa9cde..b763393c6fd 100644 --- a/tools/idf_py_actions/core_ext.py +++ b/tools/idf_py_actions/core_ext.py @@ -15,8 +15,8 @@ from urllib.request import Request from urllib.request import urlopen from webbrowser import open_new_tab -import click -from click.core import Context +import rich_click as click +from rich_click import Context from idf_py_actions.constants import GENERATORS from idf_py_actions.constants import PREVIEW_TARGETS diff --git a/tools/idf_py_actions/create_ext.py b/tools/idf_py_actions/create_ext.py index ef53d2d11f7..6286b0d7db8 100644 --- a/tools/idf_py_actions/create_ext.py +++ b/tools/idf_py_actions/create_ext.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD +# SPDX-FileCopyrightText: 2022-2026 Espressif Systems (Shanghai) CO LTD # SPDX-License-Identifier: Apache-2.0 import os import re @@ -6,9 +6,8 @@ import stat import sys from shutil import copyfile from shutil import copytree -from typing import Dict -import click +from rich_click import Context from idf_py_actions.tools import PropertyDict @@ -100,8 +99,8 @@ def create_component(target_path: str, name: str) -> None: replace_in_file(os.path.join(target_path, 'CMakeLists.txt'), 'main', name) -def action_extensions(base_actions: Dict, project_path: str) -> Dict: - def create_new(action: str, ctx: click.core.Context, global_args: PropertyDict, **action_args: str) -> Dict: +def action_extensions(base_actions: dict, project_path: str) -> dict: + def create_new(action: str, ctx: Context, global_args: PropertyDict, **action_args: str) -> dict: target_path = action_args.get('path') or os.path.join(project_path, action_args['name']) is_empty_and_create(target_path, action) diff --git a/tools/idf_py_actions/debug_ext.py b/tools/idf_py_actions/debug_ext.py index ca4aeacf7b6..5efb28dc9fe 100644 --- a/tools/idf_py_actions/debug_ext.py +++ b/tools/idf_py_actions/debug_ext.py @@ -11,9 +11,9 @@ import time from threading import Thread from typing import Any -from click import INT -from click.core import Context from esp_coredump import CoreDump +from rich_click import INT +from rich_click import Context from idf_py_actions.errors import FatalError from idf_py_actions.serial_ext import BAUD_RATE diff --git a/tools/idf_py_actions/dfu_ext.py b/tools/idf_py_actions/dfu_ext.py index 4c72f6db829..b86bb309964 100644 --- a/tools/idf_py_actions/dfu_ext.py +++ b/tools/idf_py_actions/dfu_ext.py @@ -1,17 +1,16 @@ -# SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD +# SPDX-FileCopyrightText: 2022-2026 Espressif Systems (Shanghai) CO LTD # SPDX-License-Identifier: Apache-2.0 -from typing import Dict -from click.core import Context +from rich_click import Context + from idf_py_actions.errors import FatalError +from idf_py_actions.tools import PropertyDict from idf_py_actions.tools import ensure_build_directory from idf_py_actions.tools import is_target_supported -from idf_py_actions.tools import PropertyDict from idf_py_actions.tools import run_target -def action_extensions(base_actions: Dict, project_path: str) -> Dict: - +def action_extensions(base_actions: dict, project_path: str) -> dict: SUPPORTED_TARGETS = ['esp32s2', 'esp32s3', 'esp32p4'] def dfu_target(target_name: str, ctx: Context, args: PropertyDict, part_size: str) -> None: @@ -29,8 +28,10 @@ def action_extensions(base_actions: Dict, project_path: str) -> Dict: run_target(target_name, args, {'ESP_DFU_PATH': path}) except FatalError: # Cannot capture the error from dfu-util here so the best advise is: - print('Please have a look at the "Device Firmware Upgrade through USB" chapter in API Guides of the ' - 'ESP-IDF documentation for solving common dfu-util issues.') + print( + 'Please have a look at the "Device Firmware Upgrade through USB" chapter in API Guides of the ' + 'ESP-IDF documentation for solving common dfu-util issues.' + ) raise dfu_actions = { @@ -43,8 +44,8 @@ def action_extensions(base_actions: Dict, project_path: str) -> Dict: { 'names': ['--part-size'], 'help': 'Large files are split up into smaller partitions in order to avoid timeout during ' - 'erasing flash. This option allows to overwrite the default partition size of ' - 'mkdfu.py.' + 'erasing flash. This option allows to overwrite the default partition size of ' + 'mkdfu.py.', } ], }, @@ -62,8 +63,8 @@ def action_extensions(base_actions: Dict, project_path: str) -> Dict: 'names': ['--path'], 'default': '', 'help': 'Specify path to DFU device. The default empty path works if there is just one ' - 'ESP device with the same product identifier. See the device list for paths ' - 'of available devices.' + 'ESP device with the same product identifier. See the device list for paths ' + 'of available devices.', } ], }, diff --git a/tools/idf_py_actions/diag_ext.py b/tools/idf_py_actions/diag_ext.py index 8950d04fef7..2a8961491ca 100644 --- a/tools/idf_py_actions/diag_ext.py +++ b/tools/idf_py_actions/diag_ext.py @@ -1,13 +1,10 @@ -# SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD +# SPDX-FileCopyrightText: 2024-2026 Espressif Systems (Shanghai) CO LTD # SPDX-License-Identifier: Apache-2.0 import sys import uuid from typing import Any -from typing import Dict -from typing import Optional -from typing import Tuple -import click +from rich_click import Context from idf_py_actions.tools import PropertyDict from idf_py_actions.tools import RunTool @@ -16,20 +13,20 @@ from idf_py_actions.tools import yellow_print def diag( action: str, - ctx: click.core.Context, + ctx: Context, args: PropertyDict, debug: bool, log_prefix: bool, force: bool, no_color: bool, - zip_directory: Optional[str], + zip_directory: str | None, list_recipes: bool, check_recipes: bool, - cmdl_recipes: Tuple, - cmdl_tags: Tuple, - purge_file: Optional[str], + cmdl_recipes: tuple, + cmdl_tags: tuple, + purge_file: str | None, append: bool, - output: Optional[str], + output: str | None, ) -> None: diag_args: list = [sys.executable, '-m', 'esp_idf_diag'] @@ -106,12 +103,10 @@ def diag( diag_args += ['--port', args.port] else: yellow_print( - ( - 'The target serial port is not specified, so ' - 'autodetection will be used. To set it manually, use ' - 'the "--port" option. Example: "idf.py --port ' - '/dev/ttyUSB0 diag".' - ) + 'The target serial port is not specified, so ' + 'autodetection will be used. To set it manually, use ' + 'the "--port" option. Example: "idf.py --port ' + '/dev/ttyUSB0 diag".' ) try: @@ -121,18 +116,16 @@ def diag( if command == 'create': yellow_print( - ( - f'Please make sure to thoroughly check it for any sensitive ' - f'information before sharing and remove files you do not want ' - f'to share. Kindly include any additional files you find ' - f'relevant that were not automatically added. Please archive ' - f'the contents of the final report directory using the command:\n' - f'"idf.py diag --zip {output}".' - ) + f'Please make sure to thoroughly check it for any sensitive ' + f'information before sharing and remove files you do not want ' + f'to share. Kindly include any additional files you find ' + f'relevant that were not automatically added. Please archive ' + f'the contents of the final report directory using the command:\n' + f'"idf.py diag --zip {output}".' ) -def action_extensions(base_actions: Dict, project_path: str) -> Any: +def action_extensions(base_actions: dict, project_path: str) -> Any: return { 'actions': { 'diag': { diff --git a/tools/idf_py_actions/errors.py b/tools/idf_py_actions/errors.py index 8648952c29a..a4f5f69158a 100644 --- a/tools/idf_py_actions/errors.py +++ b/tools/idf_py_actions/errors.py @@ -1,6 +1,6 @@ # SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD # SPDX-License-Identifier: Apache-2.0 -from click.core import Context +from rich_click import Context class FatalError(RuntimeError): @@ -8,7 +8,7 @@ class FatalError(RuntimeError): Wrapper class for runtime errors that aren't caused by bugs in idf.py or the build process. """ - def __init__(self, message: str, ctx: Context=None): + def __init__(self, message: str, ctx: Context = None): super(RuntimeError, self).__init__(message) # if context is defined, check for the cleanup tasks if ctx is not None and 'cleanup' in ctx.meta: diff --git a/tools/idf_py_actions/mcp_ext.py b/tools/idf_py_actions/mcp_ext.py index 51174e45550..b28d82383c0 100644 --- a/tools/idf_py_actions/mcp_ext.py +++ b/tools/idf_py_actions/mcp_ext.py @@ -8,7 +8,7 @@ import sys from pathlib import Path from typing import Any -from click.core import Context +from rich_click import Context from idf_py_actions.errors import FatalError from idf_py_actions.tools import PropertyDict @@ -32,7 +32,7 @@ except ImportError: MCP_AVAILABLE = False -def is_valid_project_dir(directory: str) -> bool: +def _is_valid_project_dir(directory: str) -> bool: """ Determine if the given directory is a valid ESP-IDF project directory. - Must be a directory. @@ -47,10 +47,16 @@ def is_valid_project_dir(directory: str) -> bool: if not cmakelists_path.is_file(): return False + # Normalised patterns (whitespace removed) for whitespace-insensitive matching. + # CMake treats whitespace inside include(...) as insignificant, so + # `include( $ENV{...} )` must be accepted alongside `include($ENV{...})`. + normalised_patterns = [''.join(p.split()) for p in CMAKE_PROJECT_LINE] + try: with open(str(cmakelists_path), encoding='utf-8') as f: for line in f: - if any(proj_line in line for proj_line in CMAKE_PROJECT_LINE): + line_normalised = ''.join(line.split()) + if any(line_normalised.startswith(pattern) for pattern in normalised_patterns): return True except Exception: return False @@ -58,6 +64,45 @@ def is_valid_project_dir(directory: str) -> bool: return False +def resolve_default_project_dir(launch_dir: str) -> str | None: + """ + Returns the first valid ESP-IDF project directory from the server's launch + context, or None if none is found. + + Priority: IDF_MCP_WORKSPACE_FOLDER env var > launch_dir + + Use this from contexts without an explicit ``project_dir`` argument + """ + for candidate in [os.environ.get('IDF_MCP_WORKSPACE_FOLDER', ''), launch_dir]: + if candidate and _is_valid_project_dir(candidate): + return candidate + return None + + +def resolve_tool_project_dir(explicit_dir: str | None, launch_dir: str) -> tuple[str | None, str | None]: + """ + Resolves the effective project directory for an MCP tool call. + + Returns ``(effective_dir, None)`` on success, or ``(None, error_message)`` + on failure. When ``explicit_dir`` is provided it is validated immediately — + the fallback chain is never tried for an explicit but invalid path. + + Use this from MCP tools that accept a ``project_dir`` argument + """ + if explicit_dir is not None: + if not _is_valid_project_dir(explicit_dir): + return None, f'"{explicit_dir}" is not a valid ESP-IDF project directory.' + return explicit_dir, None + effective = resolve_default_project_dir(launch_dir) + if effective is not None: + return effective, None + return None, ( + 'No valid ESP-IDF project directory found. ' + 'Pass project_dir explicitly, set IDF_MCP_WORKSPACE_FOLDER, ' + 'or restart with: idf.py -C mcp-server' + ) + + def action_extensions(base_actions: dict, project_path: str) -> dict: """ESP-IDF MCP Server Extension""" @@ -72,50 +117,59 @@ def action_extensions(base_actions: dict, project_path: str) -> dict: 'or use "idf.py docs" and search for EIM configuration instructions.' ) - # Verify that mcp-server was executed from a valid ESP-IDF project directory. - # This is necessary to obtain the correct context such as args, project path, etc. - if not is_valid_project_dir(project_path): - current_project = None - for candidate in [os.getcwd(), os.environ.get('IDF_MCP_WORKSPACE_FOLDER', '')]: - if is_valid_project_dir(candidate): - current_project = candidate - break - if not current_project: - raise FatalError('Open the MCP server in a valid ESP-IDF project directory.') + # Resolve the default project directory. Then derive the startup log line, and the + # bound_hint that is appended to every tool's description so the LLM driving + # the MCP client knows when (not) to pass project_dir. + startup_default_dir = resolve_default_project_dir(project_path) + if startup_default_dir is not None: + print(f'INFO: Starting ESP-IDF MCP Server. Default project: {startup_default_dir}', file=sys.stderr) + bound_hint = ( + f"This MCP server was launched with '{startup_default_dir}' as the default ESP-IDF " + 'project. Leave project_dir as None to operate on this project. Only set project_dir ' + '(absolute path to a directory containing a CMakeLists.txt with project()) when the ' + 'user explicitly asks to operate on a different ESP-IDF project.' + ) + else: + print( + 'INFO: Starting ESP-IDF MCP Server. No project directory configured at startup. ' + 'Pass project_dir in each tool call, or set IDF_MCP_WORKSPACE_FOLDER, ' + 'or restart with: idf.py -C mcp-server', + file=sys.stderr, + ) + bound_hint = ( + 'This MCP server was launched without a project context. You MUST pass project_dir ' + '(absolute path to a directory containing a CMakeLists.txt with project()) on every ' + 'call, otherwise the call will fail.' + ) + + # Initialize MCP server — project validity is checked per-tool call + mcp = FastMCP('ESP-IDF') + + # === TOOLS (Actions) === + @mcp.tool(description=f'Build the ESP-IDF project (runs `idf.py build`). {bound_hint}') + def build_project(project_dir: str | None = None) -> str: + """Build the ESP-IDF project. + + Args: + project_dir: Optional absolute path to a valid ESP-IDF project directory. + Leave as None to use the project this MCP server was launched with + (or the IDF_MCP_WORKSPACE_FOLDER environment variable). Set this + only to override that default with another project. + """ + effective_dir, error = resolve_tool_project_dir(project_dir, project_path) + if error: + return error + assert effective_dir is not None # mypy narrowing try: cmd = [ sys.executable, os.path.join(os.environ['IDF_PATH'], 'tools', 'idf.py'), '-C', - current_project, - 'mcp-server', - ] - print( - f'Starting ESP-IDF MCP Server with command: {" ".join(cmd)} in project path: {current_project}', - file=sys.stderr, - ) - subprocess.run(cmd, cwd=current_project, check=True) - return - except Exception as e: - print(f'ERROR: Failed to start ESP-IDF MCP Server: {str(e)}', file=sys.stderr) - raise FatalError(f'Failed to start ESP-IDF MCP Server: {str(e)}') from e - - # Initialize MCP server - mcp = FastMCP('ESP-IDF') - - # === TOOLS (Actions) === - @mcp.tool() - def build_project() -> str: - """Build ESP-IDF project""" - try: - cmd = [ - sys.executable, - os.path.join(os.environ['IDF_PATH'], 'tools', 'idf.py'), + effective_dir, 'build', ] - # Information logs are shown in some mcp clients using stderr - print(f'INFO: Building project with command: {" ".join(cmd)} in path: {project_path}', file=sys.stderr) - result = subprocess.run(cmd, capture_output=True, text=True, cwd=project_path) + print(f'INFO: Building project with command: {" ".join(cmd)} in path: {effective_dir}', file=sys.stderr) + result = subprocess.run(cmd, capture_output=True, text=True) if result.returncode == 0: print('INFO: Build successful', file=sys.stderr) return 'Successfully built project' @@ -126,18 +180,37 @@ def action_extensions(base_actions: dict, project_path: str) -> dict: print(f'ERROR: Build failed: {str(e)}', file=sys.stderr) return f'Build failed: {str(e)}' - @mcp.tool() - def set_target(target: str) -> str: - """Set the ESP-IDF target (esp32, esp32s3, esp32c6, etc.)""" + @mcp.tool( + description=( + 'Set the ESP-IDF target chip (esp32, esp32s3, esp32c6, etc.) for the project ' + f'(runs `idf.py set-target`). {bound_hint}' + ) + ) + def set_target(target: str, project_dir: str | None = None) -> str: + """Set the ESP-IDF target for the project. + + Args: + target: Target chip identifier (e.g. esp32, esp32s3, esp32c6). + project_dir: Optional absolute path to a valid ESP-IDF project directory. + Leave as None to use the project this MCP server was launched with + (or the IDF_MCP_WORKSPACE_FOLDER environment variable). Set this + only to override that default with another project. + """ + effective_dir, error = resolve_tool_project_dir(project_dir, project_path) + if error: + return error + assert effective_dir is not None # mypy narrowing try: cmd = [ sys.executable, os.path.join(os.environ['IDF_PATH'], 'tools', 'idf.py'), + '-C', + effective_dir, 'set-target', target, ] - print(f'INFO: Setting target with command: {" ".join(cmd)} in path: {project_path}', file=sys.stderr) - result = subprocess.run(cmd, capture_output=True, text=True, cwd=project_path) + print(f'INFO: Setting target with command: {" ".join(cmd)} in path: {effective_dir}', file=sys.stderr) + result = subprocess.run(cmd, capture_output=True, text=True) if result.returncode == 0: print(f'INFO: Target set to: {target}', file=sys.stderr) return f'Target set to: {target}' @@ -148,9 +221,24 @@ def action_extensions(base_actions: dict, project_path: str) -> dict: print(f'ERROR: Failed to set target: {str(e)}', file=sys.stderr) return f'Error setting target: {str(e)}' - @mcp.tool() - def flash_project(port: str | None = None) -> str: - """Flash the built project to connected device""" + @mcp.tool( + description=(f'Flash the built ESP-IDF project to a connected device (runs `idf.py flash`). {bound_hint}') + ) + def flash_project(port: str | None = None, project_dir: str | None = None) -> str: + """Flash the built ESP-IDF project to a connected device. + + Args: + port: Optional serial port to flash through (e.g. /dev/ttyUSB0, COM3). + Leave as None to let idf.py auto-detect. + project_dir: Optional absolute path to a valid ESP-IDF project directory. + Leave as None to use the project this MCP server was launched with + (or the IDF_MCP_WORKSPACE_FOLDER environment variable). Set this + only to override that default with another project. + """ + effective_dir, error = resolve_tool_project_dir(project_dir, project_path) + if error: + return error + assert effective_dir is not None # mypy narrowing try: flash_args = [] if port: @@ -160,9 +248,11 @@ def action_extensions(base_actions: dict, project_path: str) -> dict: cmd = [ sys.executable, os.path.join(os.environ['IDF_PATH'], 'tools', 'idf.py'), + '-C', + effective_dir, ] + flash_args - print(f'INFO: Flashing project with command: {" ".join(cmd)} in path: {project_path}', file=sys.stderr) - result = subprocess.run(cmd, capture_output=True, text=True, cwd=project_path) + print(f'INFO: Flashing project with command: {" ".join(cmd)} in path: {effective_dir}', file=sys.stderr) + result = subprocess.run(cmd, capture_output=True, text=True) if result.returncode == 0: print('INFO: Flash successful', file=sys.stderr) @@ -174,17 +264,72 @@ def action_extensions(base_actions: dict, project_path: str) -> dict: print(f'ERROR: Flash failed: {str(e)}', file=sys.stderr) return f'Error flashing: {str(e)}' - @mcp.tool() - def clean_project() -> str: - """Clean build artifacts""" + @mcp.tool( + description=( + 'Create a new ESP-IDF project from the sample template (runs `idf.py create-project`). ' + 'A directory named is created inside . Use this only when the user asks ' + 'to bootstrap a new project; do not use it on an existing project.' + ) + ) + def create_project(name: str, path: str | None = None) -> str: + """Create a new ESP-IDF project from the sample template. + + Args: + name: Name of the new project; also becomes the subdirectory name. + path: Optional absolute path to the parent directory in which the + / subdirectory will be created. Leave as None to create + it in the directory the MCP server was launched from. + """ + parent_dir = path or project_path or os.getcwd() + if not os.path.isdir(parent_dir): + return f'Parent directory does not exist: {parent_dir}' try: cmd = [ sys.executable, os.path.join(os.environ['IDF_PATH'], 'tools', 'idf.py'), + '-C', + parent_dir, + 'create-project', + name, + ] + print(f'INFO: Creating project "{name}" in {parent_dir}', file=sys.stderr) + result = subprocess.run(cmd, capture_output=True, text=True) + if result.returncode == 0: + project_path_new = os.path.join(parent_dir, name) + print(f'INFO: Project "{name}" created at {project_path_new}', file=sys.stderr) + return f'Project "{name}" created at {project_path_new}' + else: + output = result.stderr or result.stdout + print(f'ERROR: Failed to create project: {output}', file=sys.stderr) + return f'Failed to create project "{name}": {output}' + except Exception as e: + print(f'ERROR: Failed to create project: {str(e)}', file=sys.stderr) + return f'Failed to create project "{name}": {str(e)}' + + @mcp.tool(description=f'Remove build artifacts from the ESP-IDF project (runs `idf.py clean`). {bound_hint}') + def clean_project(project_dir: str | None = None) -> str: + """Remove build artifacts from the ESP-IDF project. + + Args: + project_dir: Optional absolute path to a valid ESP-IDF project directory. + Leave as None to use the project this MCP server was launched with + (or the IDF_MCP_WORKSPACE_FOLDER environment variable). Set this + only to override that default with another project. + """ + effective_dir, error = resolve_tool_project_dir(project_dir, project_path) + if error: + return error + assert effective_dir is not None # mypy narrowing + try: + cmd = [ + sys.executable, + os.path.join(os.environ['IDF_PATH'], 'tools', 'idf.py'), + '-C', + effective_dir, 'clean', ] - print(f'INFO: Cleaning project with command: {" ".join(cmd)} in path: {project_path}', file=sys.stderr) - result = subprocess.run(cmd, capture_output=True, text=True, cwd=project_path) + print(f'INFO: Cleaning project with command: {" ".join(cmd)} in path: {effective_dir}', file=sys.stderr) + result = subprocess.run(cmd, capture_output=True, text=True) if result.returncode == 0: print('INFO: Project cleaned successfully', file=sys.stderr) return 'Project cleaned successfully' @@ -199,15 +344,28 @@ def action_extensions(base_actions: dict, project_path: str) -> dict: @mcp.resource('project://config') def get_project_config() -> str: """Get current project configuration""" - build_dir = args.get('build_dir', '') + effective_dir = resolve_default_project_dir(project_path) config: dict[str, Any] = {} + if effective_dir is None: + config['error'] = ( + 'No valid ESP-IDF project directory found. ' + 'Set IDF_MCP_WORKSPACE_FOLDER or restart with: idf.py -C mcp-server' + ) + return json.dumps(config, indent=2) + config['project_path'] = effective_dir + + # Use the build_dir from idf.py args when the project matches; otherwise derive it. + build_dir = ( + args.get('build_dir', '') if project_path == effective_dir else os.path.join(effective_dir, 'build') + ) + if not os.path.exists(build_dir): config['build_dir_exists'] = False return json.dumps(config, indent=2) config['build_dir'] = build_dir - proj_desc_fn = f'{build_dir}/project_description.json' + proj_desc_fn = os.path.join(build_dir, 'project_description.json') config['project_description'] = 'Project description does not exist' try: @@ -221,15 +379,26 @@ def action_extensions(base_actions: dict, project_path: str) -> dict: @mcp.resource('project://status') def get_project_status() -> str: """Get current project build status""" + status: dict[str, Any] = {} try: - status = { - 'project_path': project_path, - 'target': get_target(project_path), - 'idf_version': idf_version(), - } + effective_dir = resolve_default_project_dir(project_path) - # Check if built - build_dir = args.build_dir + if effective_dir is None: + status['error'] = ( + 'No valid ESP-IDF project directory found. ' + 'Set IDF_MCP_WORKSPACE_FOLDER or restart with: idf.py -C mcp-server' + ) + status['idf_version'] = idf_version() + return json.dumps(status, indent=2) + + status['project_path'] = effective_dir + status['target'] = get_target(effective_dir) + status['idf_version'] = idf_version() + + # Use the build_dir from idf.py args when the project matches; otherwise derive it. + build_dir = ( + args.get('build_dir', '') if project_path == effective_dir else os.path.join(effective_dir, 'build') + ) if os.path.exists(build_dir): status['build_dir'] = build_dir artifacts = ['bootloader', 'partition_table', 'app-flash', 'flash_args'] @@ -242,7 +411,8 @@ def action_extensions(base_actions: dict, project_path: str) -> dict: return json.dumps(status, indent=2) except Exception as e: - return f'Error getting status: {str(e)}' + status['error'] = f'Error getting status: {str(e)}' + return json.dumps(status, indent=2) @mcp.resource('project://devices') def get_connected_devices() -> str: diff --git a/tools/idf_py_actions/qemu_ext.py b/tools/idf_py_actions/qemu_ext.py index 680d361cd59..6aa8a7a5fc8 100644 --- a/tools/idf_py_actions/qemu_ext.py +++ b/tools/idf_py_actions/qemu_ext.py @@ -14,7 +14,7 @@ import time from dataclasses import dataclass from typing import Any -from click.core import Context +from rich_click import Context try: from idf_py_actions.tools import PropertyDict diff --git a/tools/idf_py_actions/serial_ext.py b/tools/idf_py_actions/serial_ext.py index ccba6a7f64d..90c44880bd7 100644 --- a/tools/idf_py_actions/serial_ext.py +++ b/tools/idf_py_actions/serial_ext.py @@ -8,7 +8,9 @@ import sys from pathlib import Path from typing import Any -import click +import rich_click as click +from click.core import ParameterSource +from rich_click import Context from idf_py_actions.errors import FatalError from idf_py_actions.global_options import global_options @@ -50,7 +52,7 @@ def yellow_print(message: str, newline: str | None = '\n') -> None: def action_extensions(base_actions: dict, project_path: str) -> dict: - def _get_project_desc(ctx: click.core.Context, args: PropertyDict) -> Any: + def _get_project_desc(ctx: Context, args: PropertyDict) -> Any: desc_path = os.path.join(args.build_dir, 'project_description.json') if not os.path.exists(desc_path): ensure_build_directory(args, ctx.info_name) @@ -83,7 +85,7 @@ def action_extensions(base_actions: dict, project_path: str) -> dict: result += ['--no-stub'] return result - def _get_commandline_options(ctx: click.core.Context) -> list: + def _get_commandline_options(ctx: Context) -> list: """Return all the command line options up to first action""" # This approach ignores argument parsing done Click result = [] @@ -98,7 +100,7 @@ def action_extensions(base_actions: dict, project_path: str) -> dict: def monitor( action: str, - ctx: click.core.Context, + ctx: Context, args: PropertyDict, print_filter: str, monitor_baud: str, @@ -136,7 +138,7 @@ def action_extensions(base_actions: dict, project_path: str) -> dict: # Use the global baud rate if it has been changed by the command line. # Use project_desc['monitor_baud'] as the last option. - global_baud_defined = ctx._parameter_source['baud'] == click.core.ParameterSource.COMMANDLINE + global_baud_defined = ctx._parameter_source['baud'] == ParameterSource.COMMANDLINE baud = args.baud if global_baud_defined else project_desc['monitor_baud'] monitor_args += ['-b', baud] @@ -205,7 +207,7 @@ def action_extensions(base_actions: dict, project_path: str) -> dict: def flash( action: str, - ctx: click.core.Context, + ctx: Context, args: PropertyDict, force: bool, extra_args: str, @@ -240,13 +242,13 @@ def action_extensions(base_actions: dict, project_path: str) -> dict: } run_target(action, args, env, force_progression=True, interactive=True) - def erase_flash(action: str, ctx: click.core.Context, args: PropertyDict) -> None: + def erase_flash(action: str, ctx: Context, args: PropertyDict) -> None: ensure_build_directory(args, ctx.info_name) esptool_args = _get_esptool_args(args) esptool_args += ['erase-flash'] RunTool('esptool', esptool_args, args.build_dir, hints=not args.no_hints, interactive=True)() - def global_callback(ctx: click.core.Context, global_args: dict, tasks: PropertyDict) -> None: + def global_callback(ctx: Context, global_args: dict, tasks: PropertyDict) -> None: encryption = any([task.name in ('encrypted-flash', 'encrypted-app-flash') for task in tasks]) if encryption: for task in tasks: @@ -254,7 +256,7 @@ def action_extensions(base_actions: dict, project_path: str) -> dict: task.action_args['encrypted'] = True break - def ota_targets(target_name: str, ctx: click.core.Context, args: PropertyDict) -> None: + def ota_targets(target_name: str, ctx: Context, args: PropertyDict) -> None: """ Execute the target build system to build target 'target_name'. Additionally set global variables for baud and port. @@ -267,7 +269,7 @@ def action_extensions(base_actions: dict, project_path: str) -> dict: def merge_bin( action: str, - ctx: click.core.Context, + ctx: Context, args: PropertyDict, output: str, format: str, # noqa: A002 @@ -315,7 +317,7 @@ def action_extensions(base_actions: dict, project_path: str) -> dict: def secure_decrypt_flash_data( action: str, - ctx: click.core.Context, + ctx: Context, args: PropertyDict, aes_xts: bool, keyfile: str, @@ -341,7 +343,7 @@ def action_extensions(base_actions: dict, project_path: str) -> dict: RunTool('espsecure', decrypt_flash_data_args, args.build_dir)() def secure_digest_secure_bootloader( - action: str, ctx: click.core.Context, args: PropertyDict, keyfile: str, output: str, iv: str, **extra_args: str + action: str, ctx: Context, args: PropertyDict, keyfile: str, output: str, iv: str, **extra_args: str ) -> None: ensure_build_directory(args, ctx.info_name) digest_secure_bootloader_args = [PYTHON, '-m', 'espsecure', 'digest-secure-bootloader'] @@ -357,7 +359,7 @@ def action_extensions(base_actions: dict, project_path: str) -> dict: def secure_encrypt_flash_data( action: str, - ctx: click.core.Context, + ctx: Context, args: PropertyDict, aes_xts: bool, keyfile: str, @@ -383,7 +385,7 @@ def action_extensions(base_actions: dict, project_path: str) -> dict: RunTool('espsecure', encrypt_flash_data_args, args.build_dir)() def secure_generate_flash_encryption_key( - action: str, ctx: click.core.Context, args: PropertyDict, keylen: str, **extra_args: str + action: str, ctx: Context, args: PropertyDict, keylen: str, **extra_args: str ) -> None: ensure_build_directory(args, ctx.info_name) generate_flash_encryption_key_args = [PYTHON, '-m', 'espsecure', 'generate-flash-encryption-key'] @@ -394,7 +396,7 @@ def action_extensions(base_actions: dict, project_path: str) -> dict: RunTool('espsecure', generate_flash_encryption_key_args, args.project_dir)() def secure_generate_signing_key( - action: str, ctx: click.core.Context, args: PropertyDict, version: str, scheme: str, **extra_args: str + action: str, ctx: Context, args: PropertyDict, version: str, scheme: str, **extra_args: str ) -> None: ensure_build_directory(args, ctx.info_name) generate_signing_key_args = [PYTHON, '-m', 'espsecure', 'generate-signing-key'] @@ -415,7 +417,7 @@ def action_extensions(base_actions: dict, project_path: str) -> dict: RunTool('espsecure', generate_signing_key_args, args.project_dir)() def secure_generate_key_digest( - action: str, ctx: click.core.Context, args: PropertyDict, keyfile: str, output: str, **extra_args: str + action: str, ctx: Context, args: PropertyDict, keyfile: str, output: str, **extra_args: str ) -> None: ensure_build_directory(args, ctx.info_name) generate_key_digest_args = [PYTHON, '-m', 'espsecure', 'digest-sbv2-public-key'] @@ -427,7 +429,7 @@ def action_extensions(base_actions: dict, project_path: str) -> dict: def secure_sign_data( action: str, - ctx: click.core.Context, + ctx: Context, args: PropertyDict, version: str, keyfile: str, @@ -456,7 +458,7 @@ def action_extensions(base_actions: dict, project_path: str) -> dict: RunTool('espsecure', sign_data_args, args.build_dir)() def secure_verify_signature( - action: str, ctx: click.core.Context, args: PropertyDict, version: str, keyfile: str, **extra_args: str + action: str, ctx: Context, args: PropertyDict, version: str, keyfile: str, **extra_args: str ) -> None: ensure_build_directory(args, ctx.info_name) verify_signature_args = [PYTHON, '-m', 'espsecure', 'verify-signature'] @@ -470,7 +472,7 @@ def action_extensions(base_actions: dict, project_path: str) -> dict: def secure_generate_nvs_partition_key( action: str, - ctx: click.core.Context, + ctx: Context, args: PropertyDict, encryption_scheme: str, keyfile: str, @@ -488,7 +490,7 @@ def action_extensions(base_actions: dict, project_path: str) -> dict: RunTool('espsecure', generate_nvs_partition_key_args, args.project_dir)() def secure_encrypt_nvs_partition( - action: str, ctx: click.core.Context, args: PropertyDict, keyfile: str, **extra_args: str + action: str, ctx: Context, args: PropertyDict, keyfile: str, **extra_args: str ) -> None: ensure_build_directory(args, ctx.info_name) encrypt_nvs_partition_args = [PYTHON, '-m', 'esp_idf_nvs_partition_gen', 'encrypt'] @@ -501,7 +503,7 @@ def action_extensions(base_actions: dict, project_path: str) -> dict: encrypt_nvs_partition_args += [extra_args['partition_size']] RunTool('espsecure', encrypt_nvs_partition_args, args.project_dir)() - def _parse_efuse_args(ctx: click.core.Context, args: PropertyDict, extra_args: dict) -> list: + def _parse_efuse_args(ctx: Context, args: PropertyDict, extra_args: dict) -> list: efuse_args = [] if args.port: efuse_args += ['-p', args.port] @@ -518,7 +520,7 @@ def action_extensions(base_actions: dict, project_path: str) -> dict: efuse_args += ['--do-not-confirm'] return efuse_args - def efuse_burn(action: str, ctx: click.core.Context, args: PropertyDict, **extra_args: dict) -> None: + def efuse_burn(action: str, ctx: Context, args: PropertyDict, **extra_args: dict) -> None: ensure_build_directory(args, ctx.info_name) burn_efuse_args = [PYTHON, '-m', 'espefuse'] burn_efuse_args += _parse_efuse_args(ctx, args, extra_args) @@ -527,7 +529,7 @@ def action_extensions(base_actions: dict, project_path: str) -> dict: burn_efuse_args += list(extra_args['efuse_positional_args']) RunTool('espefuse', burn_efuse_args, args.build_dir)() - def efuse_burn_key(action: str, ctx: click.core.Context, args: PropertyDict, **extra_args: str) -> None: + def efuse_burn_key(action: str, ctx: Context, args: PropertyDict, **extra_args: str) -> None: ensure_build_directory(args, ctx.info_name) burn_key_args = [PYTHON, '-m', 'espefuse'] burn_key_args += _parse_efuse_args(ctx, args, extra_args) @@ -542,9 +544,7 @@ def action_extensions(base_actions: dict, project_path: str) -> dict: burn_key_args += extra_args['efuse_positional_args'] RunTool('espefuse', burn_key_args, args.project_dir, build_dir=args.build_dir)() - def efuse_dump( - action: str, ctx: click.core.Context, args: PropertyDict, file_name: str, **extra_args: dict - ) -> None: + def efuse_dump(action: str, ctx: Context, args: PropertyDict, file_name: str, **extra_args: dict) -> None: ensure_build_directory(args, ctx.info_name) dump_args = [PYTHON, '-m', 'espefuse'] dump_args += _parse_efuse_args(ctx, args, extra_args) @@ -553,7 +553,7 @@ def action_extensions(base_actions: dict, project_path: str) -> dict: dump_args += ['--file-name', file_name] RunTool('espefuse', dump_args, args.build_dir)() - def efuse_read_protect(action: str, ctx: click.core.Context, args: PropertyDict, **extra_args: dict) -> None: + def efuse_read_protect(action: str, ctx: Context, args: PropertyDict, **extra_args: dict) -> None: ensure_build_directory(args, ctx.info_name) read_protect_args = [PYTHON, '-m', 'espefuse'] read_protect_args += _parse_efuse_args(ctx, args, extra_args) @@ -564,7 +564,7 @@ def action_extensions(base_actions: dict, project_path: str) -> dict: def efuse_summary( action: str, - ctx: click.core.Context, + ctx: Context, args: PropertyDict, format: str, # noqa: A002 **extra_args: dict, @@ -579,7 +579,7 @@ def action_extensions(base_actions: dict, project_path: str) -> dict: summary_args += [str(extra_args['efuse_name'])] RunTool('espefuse', summary_args, args.build_dir)() - def efuse_write_protect(action: str, ctx: click.core.Context, args: PropertyDict, **extra_args: dict) -> None: + def efuse_write_protect(action: str, ctx: Context, args: PropertyDict, **extra_args: dict) -> None: ensure_build_directory(args, ctx.info_name) write_protect_args = [PYTHON, '-m', 'espefuse'] write_protect_args += _parse_efuse_args(ctx, args, extra_args) diff --git a/tools/idf_py_actions/tools.py b/tools/idf_py_actions/tools.py index 2ab73dc937c..218a1769113 100644 --- a/tools/idf_py_actions/tools.py +++ b/tools/idf_py_actions/tools.py @@ -16,7 +16,7 @@ from typing import Any from typing import TextIO from typing import cast -import click +import rich_click as click import yaml from esp_idf_monitor import get_ansi_converter @@ -395,6 +395,19 @@ class RunTool: env_copy = dict(os.environ) env_copy.update(self.env or {}) + # Color control: + # 1. CLICOLOR_FORCE: + # By default, GNU Make and Ninja strip away color escape sequences when they see that their stdout + # is redirected. If idf.py's stdout is not redirected, the final output is a TTY, so we can tell + # Make/Ninja to disable stripping of color escape sequences. (Requires Ninja v1.9.0 or later.) + # 2. FORCE_COLOR: + # The same idea as above, but FORCE_COLOR is used by Python packages like rich. + # 3. NO_COLOR: + # Universal kill switch; if set, we won't force colors. + if sys.stdout.isatty() and not env_copy.get('NO_COLOR'): + env_copy.setdefault('CLICOLOR_FORCE', '1') + env_copy.setdefault('FORCE_COLOR', '1') + process: Process | subprocess.CompletedProcess[bytes] if self.hints: process, stderr_output_file, stdout_output_file = asyncio.run(self.run_command(self.args, env_copy)) @@ -592,18 +605,24 @@ def run_target( if env is None: env = {} - generator_cmd = GENERATORS[args.generator]['command'] + generator_cmd = list(GENERATORS[args.generator]['command']) + + if args.generator == 'Ninja': + parallel_level = os.environ.get('IDF_PY_BUILD_JOBS') + if parallel_level: + try: + jobs = int(parallel_level) + except ValueError as e: + raise FatalError('Environment variable IDF_PY_BUILD_JOBS must be a positive integer') from e + + if jobs <= 0: + raise FatalError('Environment variable IDF_PY_BUILD_JOBS must be a positive integer') + + generator_cmd += ['-j', str(jobs)] if args.verbose: generator_cmd += [GENERATORS[args.generator]['verbose_flag']] - # By default, GNU Make and Ninja strip away color escape sequences when they see that their stdout is redirected. - # If idf.py's stdout is not redirected, the final output is a TTY, so we can tell Make/Ninja to disable stripping - # of color escape sequences. (Requires Ninja v1.9.0 or later.) - if sys.stdout.isatty(): - if 'CLICOLOR_FORCE' not in env: - env['CLICOLOR_FORCE'] = '1' - RunTool( generator_cmd[0], generator_cmd + [target_name], diff --git a/tools/idf_py_actions/uf2_ext.py b/tools/idf_py_actions/uf2_ext.py index a6b2f467003..416735718f6 100644 --- a/tools/idf_py_actions/uf2_ext.py +++ b/tools/idf_py_actions/uf2_ext.py @@ -1,12 +1,14 @@ -# SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD +# SPDX-FileCopyrightText: 2022-2026 Espressif Systems (Shanghai) CO LTD # SPDX-License-Identifier: Apache-2.0 -from typing import Dict, List -from click.core import Context -from idf_py_actions.tools import PropertyDict, ensure_build_directory, run_target +from rich_click import Context + +from idf_py_actions.tools import PropertyDict +from idf_py_actions.tools import ensure_build_directory +from idf_py_actions.tools import run_target -def action_extensions(base_actions: Dict, project_path: List) -> Dict: +def action_extensions(base_actions: dict, project_path: list) -> dict: def uf2_target(target_name: str, ctx: Context, args: PropertyDict, md5_disable: bool) -> None: ensure_build_directory(args, ctx.info_name) extra = list() diff --git a/tools/idf_tools.py b/tools/idf_tools.py index 019fac7bff1..62b5518b332 100755 --- a/tools/idf_tools.py +++ b/tools/idf_tools.py @@ -3126,6 +3126,7 @@ def action_uninstall(args: Any) -> None: else: tool_name, tool_version = tool_spec.split('@', 1) tool_obj = tools_info_for_platform[tool_name] + archive_version = None if tool_version is None: tool_version = tool_obj.get_preferred_installed_version() # mypy-checks @@ -3133,9 +3134,8 @@ def action_uninstall(args: Any) -> None: archive_version = tool_obj.versions[tool_version].get_download_for_platform(CURRENT_PLATFORM) if archive_version is not None: archive_version_url = archive_version.url - - archive = os.path.basename(archive_version_url) - used_archives.append(archive) + archive = os.path.basename(archive_version_url) + used_archives.append(archive) downloaded_archives = os.listdir(dist_path) for archive in downloaded_archives: diff --git a/tools/requirements/requirements.core.txt b/tools/requirements/requirements.core.txt index f89d81c8e10..f2d4518fcc1 100644 --- a/tools/requirements/requirements.core.txt +++ b/tools/requirements/requirements.core.txt @@ -7,6 +7,7 @@ setuptools packaging click +rich-click pyserial cryptography pyparsing diff --git a/tools/requirements/requirements.docs.txt b/tools/requirements/requirements.docs.txt index f2673966802..fa286aca4a1 100644 --- a/tools/requirements/requirements.docs.txt +++ b/tools/requirements/requirements.docs.txt @@ -7,3 +7,4 @@ esp-docs linuxdoc +sphinxcontrib-mermaid diff --git a/tools/test_apps/build_system/bootloader/sdkconfig.ci.secure_boot.ecdsa.esp32h2 b/tools/test_apps/build_system/bootloader/sdkconfig.ci.secure_boot.ecdsa.esp32h2 index d1e1ff2a7ab..116cbc0df37 100644 --- a/tools/test_apps/build_system/bootloader/sdkconfig.ci.secure_boot.ecdsa.esp32h2 +++ b/tools/test_apps/build_system/bootloader/sdkconfig.ci.secure_boot.ecdsa.esp32h2 @@ -1,6 +1,9 @@ CONFIG_IDF_TARGET="esp32h2" CONFIG_IDF_TARGET_ESP32H2=y +# ECDSA Secure Boot V2 is gated behind the insecure option on the affected SoCs +CONFIG_SECURE_BOOT_INSECURE=y +CONFIG_SECURE_BOOT_V2_FORCE_ENABLE_ECDSA=y CONFIG_SECURE_BOOT_V2_ECDSA_ENABLED=y CONFIG_SECURE_SIGNED_APPS_ECDSA_V2_SCHEME=y CONFIG_SECURE_BOOT_ECDSA_KEY_LEN_256_BITS=y diff --git a/tools/test_apps/build_system/bootloader/sdkconfig.defaults.esp32c61 b/tools/test_apps/build_system/bootloader/sdkconfig.defaults.esp32c61 new file mode 100644 index 00000000000..d3845b46592 --- /dev/null +++ b/tools/test_apps/build_system/bootloader/sdkconfig.defaults.esp32c61 @@ -0,0 +1,6 @@ +# ESP32-C61 has no RSA based Secure Boot V2; only the ECDSA scheme exists, and it +# is not functional for certain input vectors (see SECURE_BOOT_V2_ECDSA_INSECURE), +# so it is gated behind the insecure option. Force-enable it for this build-only test. +CONFIG_SECURE_BOOT_INSECURE=y +CONFIG_SECURE_BOOT_V2_FORCE_ENABLE_ECDSA=y +CONFIG_SECURE_SIGNED_APPS_ECDSA_V2_SCHEME=y diff --git a/tools/test_apps/security/.build-test-rules.yml b/tools/test_apps/security/.build-test-rules.yml index cb02072ea3c..2aadd7e564c 100644 --- a/tools/test_apps/security/.build-test-rules.yml +++ b/tools/test_apps/security/.build-test-rules.yml @@ -7,6 +7,8 @@ tools/test_apps/security/secure_boot: disable: - if: CONFIG_NAME != "qemu" or IDF_TARGET == "linux" reason: Skipping redundant CI builds for all the targets. + - if: IDF_TARGET == "esp32h4" + reason: Secure Boot V2 is disabled on ESP32-H4 (ECDSA-only, vulnerable scheme), so the secure boot test app does not apply. tools/test_apps/security/signed_app_no_secure_boot: enable: diff --git a/tools/test_apps/security/secure_boot/README.md b/tools/test_apps/security/secure_boot/README.md index 8e978da32b9..351cec6eedf 100644 --- a/tools/test_apps/security/secure_boot/README.md +++ b/tools/test_apps/security/secure_boot/README.md @@ -1,5 +1,5 @@ -| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C5 | ESP32-C6 | ESP32-C61 | ESP32-H2 | ESP32-H21 | ESP32-H4 | ESP32-P4 | ESP32-S2 | ESP32-S3 | -| ----------------- | ----- | -------- | -------- | -------- | -------- | --------- | -------- | --------- | -------- | -------- | -------- | -------- | +| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C5 | ESP32-C6 | ESP32-C61 | ESP32-H2 | ESP32-H21 | ESP32-P4 | ESP32-S2 | ESP32-S3 | +| ----------------- | ----- | -------- | -------- | -------- | -------- | --------- | -------- | --------- | -------- | -------- | -------- | # Secure Boot diff --git a/tools/test_apps/security/secure_boot/pytest_secure_boot.py b/tools/test_apps/security/secure_boot/pytest_secure_boot.py index 8dd090c6032..f8f841aaf78 100644 --- a/tools/test_apps/security/secure_boot/pytest_secure_boot.py +++ b/tools/test_apps/security/secure_boot/pytest_secure_boot.py @@ -38,14 +38,15 @@ SECURE_BOOT_RSA_TARGETS = [ 'esp32c3', 'esp32c5', 'esp32c6', - 'esp32c61', 'esp32h2', 'esp32h21', 'esp32s2', 'esp32s3', 'esp32p4', ] -SECURE_BOOT_ECDSA_TARGETS = ['esp32c2', 'esp32c5', 'esp32c6', 'esp32c61', 'esp32h2', 'esp32h21', 'esp32p4'] +# ESP32-H21 is a preview target with ECDSA based Secure Boot V2 marked unsupported, +# so it is excluded here. +SECURE_BOOT_ECDSA_TARGETS = ['esp32c2', 'esp32c5', 'esp32c6', 'esp32c61', 'esp32h2', 'esp32p4'] SECURE_BOOT_ECDSA_P384_TARGETS = ['esp32c5'] CONFIGS_SECURE_BOOT_ECDSA = list( diff --git a/tools/test_apps/security/secure_boot/sdkconfig.ci.ecdsa_p256 b/tools/test_apps/security/secure_boot/sdkconfig.ci.ecdsa_p256 index b70b07cdf18..8f285355d77 100644 --- a/tools/test_apps/security/secure_boot/sdkconfig.ci.ecdsa_p256 +++ b/tools/test_apps/security/secure_boot/sdkconfig.ci.ecdsa_p256 @@ -3,6 +3,10 @@ CONFIG_PARTITION_TABLE_OFFSET=0xD000 CONFIG_SECURE_BOOT=y CONFIG_SECURE_BOOT_V2_ENABLED=y +# ECDSA based Secure Boot V2 is not recommended on the affected SoCs (see +# SECURE_BOOT_V2_ECDSA_INSECURE) and must be force-enabled to be selected. +CONFIG_SECURE_BOOT_INSECURE=y +CONFIG_SECURE_BOOT_V2_FORCE_ENABLE_ECDSA=y CONFIG_SECURE_SIGNED_APPS_ECDSA_V2_SCHEME=y CONFIG_SECURE_BOOT_ECDSA_KEY_LEN_256_BITS=y CONFIG_SECURE_BOOT_SIGNING_KEY="test_ecdsa_p256_key.pem" diff --git a/tools/test_apps/security/secure_boot/sdkconfig.ci.ecdsa_p384 b/tools/test_apps/security/secure_boot/sdkconfig.ci.ecdsa_p384 index d46c56ea0a4..6addb07b9b4 100644 --- a/tools/test_apps/security/secure_boot/sdkconfig.ci.ecdsa_p384 +++ b/tools/test_apps/security/secure_boot/sdkconfig.ci.ecdsa_p384 @@ -3,6 +3,10 @@ CONFIG_PARTITION_TABLE_OFFSET=0xD000 CONFIG_SECURE_BOOT=y CONFIG_SECURE_BOOT_V2_ENABLED=y +# ECDSA based Secure Boot V2 is not recommended on the affected SoCs (see +# SECURE_BOOT_V2_ECDSA_INSECURE) and must be force-enabled to be selected. +CONFIG_SECURE_BOOT_INSECURE=y +CONFIG_SECURE_BOOT_V2_FORCE_ENABLE_ECDSA=y CONFIG_SECURE_SIGNED_APPS_ECDSA_V2_SCHEME=y CONFIG_SECURE_BOOT_ECDSA_KEY_LEN_384_BITS=y CONFIG_SECURE_BOOT_SIGNING_KEY="test_ecdsa_p384_key.pem" diff --git a/tools/test_build_system/buildv2/test_component.py b/tools/test_build_system/buildv2/test_component.py index 89a6cbc3d1a..2c7bd1e3840 100644 --- a/tools/test_build_system/buildv2/test_component.py +++ b/tools/test_build_system/buildv2/test_component.py @@ -1,5 +1,6 @@ # SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD # SPDX-License-Identifier: Apache-2.0 +import json import logging from pathlib import Path @@ -297,3 +298,53 @@ def test_idf_component_set_get_property_apis(idf_py: IdfPyFunc) -> None: assert 'LIB=' in result and len(result.split('LIB=')[1].split('\n')[0]) > 0, ( 'idf_component_get_property should retrieve COMPONENT_LIB' ) + + +@pytest.mark.usefixtures('test_app_copy') +def test_local_component_shadows_managed_dep_in_manifest(idf_py: IdfPyFunc) -> None: + """A component's manifest declares `/` while the project has a + local `components/` that shadows it. The dep must resolve to the + local short-named component rather than failing with + `Failed to resolve component '__'`. + + Regression test for the cmakev2 per-component injection path that lost + the project-wide context _choose_component needs to rewrite a manifest- + declared namespaced dep to its locally-shadowing short name. The fix + seeds the per-component requirements file with one entry per discovered + component so known_components matches what cmakev1's project-wide + injection produces. + """ + logging.info('Testing local component shadows manifest-declared managed dep') + + # Local shadow named `lvgl`. + (Path('components/lvgl')).mkdir(parents=True) + (Path('components/lvgl/CMakeLists.txt')).write_text('idf_component_register(SRCS "lvgl_stub.c" INCLUDE_DIRS ".")\n') + (Path('components/lvgl/lvgl_stub.c')).write_text('void lvgl_local_stub(void) {}\n') + + # A second local component whose manifest declares the namespaced dep. + # override_path keeps the component manager offline by pointing the dep + # at the local lvgl directory. + (Path('components/consumer')).mkdir(parents=True) + (Path('components/consumer/CMakeLists.txt')).write_text( + 'idf_component_register(SRCS "consumer.c" INCLUDE_DIRS ".")\n' + ) + (Path('components/consumer/consumer.c')).write_text('void consumer_stub(void) {}\n') + (Path('components/consumer/idf_component.yml')).write_text( + 'dependencies:\n idf: ">=5.0"\n lvgl/lvgl:\n version: "*"\n override_path: "../lvgl"\n' + ) + + # Force consumer into the build via main (cmakev2 only builds required components). + replace_in_file( + 'main/CMakeLists.txt', + '# placeholder_inside_idf_component_register', + 'PRIV_REQUIRES consumer', + ) + + # Without the fix this fails with "Failed to resolve component 'lvgl__lvgl'". + idf_py('reconfigure') + + with open('build/project_description.json') as f: + data = json.load(f) + paths = data.get('build_component_paths', []) + assert any(p.endswith('/components/lvgl') for p in paths), f'local lvgl not in build_component_paths: {paths}' + assert not any('lvgl__lvgl' in p for p in paths), f'managed lvgl__lvgl should not be in build: {paths}' diff --git a/tools/test_build_system/test_idf_extension.py b/tools/test_build_system/test_idf_extension.py index b378d4d0eb9..58c7310e22c 100644 --- a/tools/test_build_system/test_idf_extension.py +++ b/tools/test_build_system/test_idf_extension.py @@ -18,6 +18,7 @@ from test_build_system_helpers import EnvDict from test_build_system_helpers import IdfPyFunc from test_build_system_helpers import find_python from test_build_system_helpers import replace_in_file +from test_build_system_helpers import run_idf_py from conftest import should_clean_test_dir @@ -185,10 +186,11 @@ def test_extension_from_component(idf_py: IdfPyFunc, test_app_copy: Path) -> Non idf_py('reconfigure') ret = idf_py('--help') assert 'test-component-action' in ret.stdout - assert 'INFO: Loaded component extension from "components/test_component"' in ret.stdout + expected_info = f'INFO: Loaded component extension from "{os.path.join("components", "test_component")}"' + assert expected_info in ret.stdout ret = idf_py('test-component-action') assert 'Test extension action executed - component extension' in ret.stdout - assert 'INFO: Loaded component extension from "components/test_component"' in ret.stdout + assert expected_info in ret.stdout def test_extension_from_component_invalid_syntax(idf_py: IdfPyFunc, test_app_copy: Path) -> None: @@ -239,6 +241,47 @@ def test_extension_from_component_invalid_syntax(idf_py: IdfPyFunc, test_app_cop assert 'Attribute "version" is required in custom extension.' in ret.stderr +@pytest.mark.usefixtures('test_app_copy') +def test_idf_py_help_rich_click_component_extension_panel( + idf_py: IdfPyFunc, + default_idf_env: dict[str, str], +) -> None: + """Default Commands panel first; component extension gets its own panel after.""" + idf_py('create-component', '-C', 'components', 'help_group_comp') + comp_dir = Path('components') / 'help_group_comp' + (comp_dir / 'idf_ext.py').write_text( + textwrap.dedent( + TEST_EXT_TEMPLATE.format( + suffix='help panel comp', + global_options='', + actions="""'help-group-comp-cmd': { + 'callback': test_extension_action, + 'help': 'Component extension command for help panel test' + }""", + ) + ) + ) + replace_in_file( + Path('main') / 'CMakeLists.txt', + '# placeholder_inside_idf_component_register', + '\n'.join(['INCLUDE_DIRS "." ', 'REQUIRES "help_group_comp" ']), + ) + idf_py('reconfigure') + + env = {**default_idf_env, 'COLUMNS': '120', 'NO_COLOR': '1'} + ret = run_idf_py('--help', env=env, workdir=os.getcwd(), check=True) + + idx_commands = ret.stdout.find('Commands') + assert idx_commands != -1, 'Root idf.py --help should list the default Commands group.' + idx_comp_panel = ret.stdout.find('help_group_comp', idx_commands) + assert idx_comp_panel != -1, 'Expected help group for the help_group_comp component extension.' + assert idx_comp_panel > idx_commands, 'Component extension group should appear after the default Commands group.' + + # ret.stdout[i:j]: substring from index i (inclusive) to j (exclusive); ':' separates the two bounds. + default_block = ret.stdout[idx_commands:idx_comp_panel] + assert 'build' in default_block, 'Built-in `build` should still appear under the default Commands panel.' + + # ----------- Test cases for entry point extension ----------- @@ -370,6 +413,44 @@ def test_extension_entrypoint_conflicting_names( assert 'This global option conflicts with existing one' not in ret.stdout +@pytest.mark.usefixtures('test_app_copy') +def test_idf_py_help_rich_click_entrypoint_extension_panel( + idf_py: IdfPyFunc, + default_idf_env: dict[str, str], + extension_package_manager: ExtensionPackageManager, +) -> None: + """Default Commands panel first; entry-point extension gets its own panel after.""" + extension_package_manager.create_package('helpgroup') + + env = {**default_idf_env, 'COLUMNS': '120', 'NO_COLOR': '1'} + ret = run_idf_py('--help', env=env, workdir=os.getcwd(), check=True) + + idx_commands = ret.stdout.find('Commands') + assert idx_commands != -1, 'Root idf.py --help should list the default Commands group.' + idx_ep_panel = ret.stdout.find('test_extension_helpgroup', idx_commands) + assert idx_ep_panel != -1, 'Expected help group for the helpgroup entry-point extension.' + assert idx_ep_panel > idx_commands, 'Entry-point extension group should appear after the default Commands group.' + + # ret.stdout[i:j]: substring from index i (inclusive) to j (exclusive); ':' separates the two bounds. + default_block = ret.stdout[idx_commands:idx_ep_panel] + assert 'build' in default_block, 'Built-in `build` should still appear under the default Commands panel.' + + +# ----------- General extension tests ----------- + + +@pytest.mark.usefixtures('test_app_copy') +def test_idf_py_subcommand_help_shows_options( + idf_py: IdfPyFunc, + default_idf_env: dict[str, str], +) -> None: + """Subcommand --help must list global/action options (rich-click + default_panels_first).""" + idf_py('reconfigure') + env = {**default_idf_env, 'NO_COLOR': '1', 'COLUMNS': '120'} + ret = run_idf_py('flash', '--help', env=env, workdir=os.getcwd(), check=True) + assert '--project-dir' in ret.stdout or '-C ' in ret.stdout + + # ----------- Regression test: idf.py recursion via idf_version clause ----------- diff --git a/tools/test_idf_py/test_idf_py.py b/tools/test_idf_py/test_idf_py.py index 83998bfed70..f48d890821e 100755 --- a/tools/test_idf_py/test_idf_py.py +++ b/tools/test_idf_py/test_idf_py.py @@ -33,6 +33,28 @@ py_actions_path = os.path.join(current_dir, '..', 'idf_py_actions') link_path = os.path.join(py_actions_path, 'test_ext') +# As idf.py uses rich-click, unite modification variables to ensure constant results on various CI terminals +_idf_py_test_env_saved: dict[str, str | None] = {} + + +def setUpModule() -> None: + for key in ('COLUMNS', 'LINES', 'NO_COLOR', 'FORCE_COLOR', 'PY_COLORS', 'TERM'): + _idf_py_test_env_saved[key] = os.environ.get(key) + os.environ['COLUMNS'] = '200' + os.environ['LINES'] = '40' + os.environ['NO_COLOR'] = '1' + for unset in ('FORCE_COLOR', 'PY_COLORS'): + os.environ.pop(unset, None) + + +def tearDownModule() -> None: + for key, previous in _idf_py_test_env_saved.items(): + if previous is None: + os.environ.pop(key, None) + else: + os.environ[key] = previous + + class TestWithoutExtensions(TestCase): @classmethod def setUpClass(cls): diff --git a/tools/test_idf_py/test_mcp_ext.py b/tools/test_idf_py/test_mcp_ext.py new file mode 100644 index 00000000000..0e5e0ac6fb9 --- /dev/null +++ b/tools/test_idf_py/test_mcp_ext.py @@ -0,0 +1,591 @@ +# SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: Apache-2.0 + +"""Unit tests for idf_py_actions/mcp_ext.py. + +No full IDF environment is required — all external imports and subprocess +calls are mocked. +""" + +import importlib +import importlib.util +import json +import os +import sys +import types +from collections.abc import Callable +from pathlib import Path +from typing import Any +from unittest import mock + +import pytest + +# --------------------------------------------------------------------------- +# Helpers for creating fake project directories +# --------------------------------------------------------------------------- + +IDF_CMAKE_LINE = r'include($ENV{IDF_PATH}/tools/cmake/project.cmake)' + + +def _make_valid_project(path: Path) -> Path: + """Write a minimal valid ESP-IDF CMakeLists.txt into *path*.""" + path.mkdir(parents=True, exist_ok=True) + (path / 'CMakeLists.txt').write_text( + f'cmake_minimum_required(VERSION 3.16)\n{IDF_CMAKE_LINE}\nproject(hello_world)\n', + encoding='utf-8', + ) + return path + + +def _make_invalid_project(path: Path) -> Path: + """Write a CMakeLists.txt that does NOT include the IDF line.""" + path.mkdir(parents=True, exist_ok=True) + (path / 'CMakeLists.txt').write_text( + 'cmake_minimum_required(VERSION 3.16)\nproject(plain_cmake)\n', + encoding='utf-8', + ) + return path + + +# --------------------------------------------------------------------------- +# Fixture: stub out all non-stdlib imports so the module can be loaded +# without an IDF installation. +# --------------------------------------------------------------------------- + + +class _MockFastMCP: + """Captures tool/resource registrations so tests can invoke them.""" + + def __init__(self, name: str) -> None: + self.name = name + self.tools: dict[str, Callable[..., Any]] = {} + self.resources: dict[str, Callable[..., Any]] = {} + + def tool(self, **kwargs: Any) -> Callable[[Callable[..., Any]], Callable[..., Any]]: + def decorator(fn: Callable[..., Any]) -> Callable[..., Any]: + self.tools[fn.__name__] = fn + return fn + + return decorator + + def resource(self, uri: str) -> Callable[[Callable[..., Any]], Callable[..., Any]]: + def decorator(fn: Callable[..., Any]) -> Callable[..., Any]: + self.resources[uri] = fn + return fn + + return decorator + + def run(self) -> None: + pass # don't block in tests + + +@pytest.fixture() +def mcp_ext(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> tuple[types.ModuleType, _MockFastMCP]: + """ + Import (or reimport) mcp_ext with all external dependencies mocked. + Returns the module object plus a _MockFastMCP instance that was used so + that tests can inspect registered tools/resources. + """ + # Build a fresh _MockFastMCP for this test + mock_mcp_instance = _MockFastMCP('ESP-IDF') + + # Stub rich_click + rich_click = types.ModuleType('rich_click') + rich_click.Context = object # type: ignore[attr-defined] + + # Stub idf_py_actions hierarchy + idf_py_actions_pkg = types.ModuleType('idf_py_actions') + errors_mod = types.ModuleType('idf_py_actions.errors') + + class FatalError(Exception): + pass + + errors_mod.FatalError = FatalError # type: ignore[attr-defined] + + tools_mod = types.ModuleType('idf_py_actions.tools') + tools_mod.PropertyDict = dict # type: ignore[attr-defined] + tools_mod.get_target = mock.Mock(return_value='esp32') # type: ignore[attr-defined] + tools_mod.idf_version = mock.Mock(return_value='5.4.0') # type: ignore[attr-defined] + + idf_py_actions_pkg.errors = errors_mod # type: ignore[attr-defined] + idf_py_actions_pkg.tools = tools_mod # type: ignore[attr-defined] + + # Stub mcp.server.fastmcp — FastMCP constructor returns our mock + mcp_pkg = types.ModuleType('mcp') + mcp_server_pkg = types.ModuleType('mcp.server') + fastmcp_mod = types.ModuleType('mcp.server.fastmcp') + fastmcp_mod.FastMCP = lambda name: mock_mcp_instance # type: ignore[attr-defined] + + stubs = { + 'rich_click': rich_click, + 'idf_py_actions': idf_py_actions_pkg, + 'idf_py_actions.errors': errors_mod, + 'idf_py_actions.tools': tools_mod, + 'mcp': mcp_pkg, + 'mcp.server': mcp_server_pkg, + 'mcp.server.fastmcp': fastmcp_mod, + } + for name, stub_mod in stubs.items(): + monkeypatch.setitem(sys.modules, name, stub_mod) + + # Load mcp_ext directly from its file so that the stub 'idf_py_actions' + # package (which is not a real package) doesn't prevent import. + mcp_ext_path = Path(__file__).parent.parent / 'idf_py_actions' / 'mcp_ext.py' + spec = importlib.util.spec_from_file_location('idf_py_actions.mcp_ext', mcp_ext_path) + mod = importlib.util.module_from_spec(spec) # type: ignore[arg-type] + # Register under both names so cross-references inside the module work + monkeypatch.setitem(sys.modules, 'idf_py_actions.mcp_ext', mod) + spec.loader.exec_module(mod) # type: ignore[union-attr] + + return mod, mock_mcp_instance + + +# --------------------------------------------------------------------------- +# Helper: call start_mcp_server and return registered tools/resources +# --------------------------------------------------------------------------- + + +def _start_server( + mcp_ext_module: tuple[types.ModuleType, _MockFastMCP], + mock_mcp_instance: _MockFastMCP, + project_path: str, +) -> tuple[dict[str, Callable[..., Any]], dict[str, Callable[..., Any]]]: + """ + Call action_extensions / start_mcp_server with *project_path* so that + tools and resources are registered on *mock_mcp_instance*. + """ + mod, _ = mcp_ext_module + ext = mod.action_extensions({}, project_path) + callback = ext['actions']['mcp-server']['callback'] + # ctx and args are only used inside resources; use simple stubs + fake_args = {'build_dir': os.path.join(project_path, 'build')} + callback('mcp-server', ctx=None, args=fake_args) + return mock_mcp_instance.tools, mock_mcp_instance.resources + + +# --------------------------------------------------------------------------- +# Tests: _is_valid_project_dir +# --------------------------------------------------------------------------- + + +class TestIsValidProjectDir: + def test_valid_project(self, tmp_path: Path, mcp_ext: tuple[types.ModuleType, _MockFastMCP]) -> None: + mod, _ = mcp_ext + proj = _make_valid_project(tmp_path / 'my_proj') + assert mod._is_valid_project_dir(str(proj)) is True + + def test_missing_directory(self, tmp_path: Path, mcp_ext: tuple[types.ModuleType, _MockFastMCP]) -> None: + mod, _ = mcp_ext + assert mod._is_valid_project_dir(str(tmp_path / 'nonexistent')) is False + + def test_directory_without_cmakelists(self, tmp_path: Path, mcp_ext: tuple[types.ModuleType, _MockFastMCP]) -> None: + mod, _ = mcp_ext + d = tmp_path / 'no_cmake' + d.mkdir() + assert mod._is_valid_project_dir(str(d)) is False + + def test_cmakelists_without_idf_line(self, tmp_path: Path, mcp_ext: tuple[types.ModuleType, _MockFastMCP]) -> None: + mod, _ = mcp_ext + proj = _make_invalid_project(tmp_path / 'plain') + assert mod._is_valid_project_dir(str(proj)) is False + + def test_cmakelists_with_spaces_in_include( + self, tmp_path: Path, mcp_ext: tuple[types.ModuleType, _MockFastMCP] + ) -> None: + mod, _ = mcp_ext + path = tmp_path / 'spaced' + path.mkdir() + (path / 'CMakeLists.txt').write_text( + 'cmake_minimum_required(VERSION 3.16)\n' + 'include( $ENV{IDF_PATH}/tools/cmake/project.cmake )\n' + 'project(hello_world)\n', + encoding='utf-8', + ) + assert mod._is_valid_project_dir(str(path)) is True + + def test_commented_out_include_is_rejected( + self, tmp_path: Path, mcp_ext: tuple[types.ModuleType, _MockFastMCP] + ) -> None: + mod, _ = mcp_ext + path = tmp_path / 'commented' + path.mkdir() + (path / 'CMakeLists.txt').write_text( + 'cmake_minimum_required(VERSION 3.16)\n' + '# include($ENV{IDF_PATH}/tools/cmake/project.cmake)\n' + 'project(hello_world)\n', + encoding='utf-8', + ) + assert mod._is_valid_project_dir(str(path)) is False + + def test_empty_string(self, mcp_ext: tuple[types.ModuleType, _MockFastMCP]) -> None: + mod, _ = mcp_ext + assert mod._is_valid_project_dir('') is False + + +# --------------------------------------------------------------------------- +# Tests: resolve_default_project_dir +# --------------------------------------------------------------------------- + + +class TestResolveDefaultProjectDir: + def test_env_var_takes_priority_over_default( + self, tmp_path: Path, mcp_ext: tuple[types.ModuleType, _MockFastMCP], monkeypatch: pytest.MonkeyPatch + ) -> None: + mod, _ = mcp_ext + env_proj = _make_valid_project(tmp_path / 'env_proj') + default = _make_valid_project(tmp_path / 'default') + monkeypatch.setenv('IDF_MCP_WORKSPACE_FOLDER', str(env_proj)) + result = mod.resolve_default_project_dir(str(default)) + assert result == str(env_proj) + + def test_default_used_when_env_not_set( + self, tmp_path: Path, mcp_ext: tuple[types.ModuleType, _MockFastMCP], monkeypatch: pytest.MonkeyPatch + ) -> None: + mod, _ = mcp_ext + default = _make_valid_project(tmp_path / 'default') + monkeypatch.setenv('IDF_MCP_WORKSPACE_FOLDER', '') + result = mod.resolve_default_project_dir(str(default)) + assert result == str(default) + + def test_returns_none_when_nothing_valid( + self, tmp_path: Path, mcp_ext: tuple[types.ModuleType, _MockFastMCP], monkeypatch: pytest.MonkeyPatch + ) -> None: + mod, _ = mcp_ext + invalid = _make_invalid_project(tmp_path / 'bad') + monkeypatch.setenv('IDF_MCP_WORKSPACE_FOLDER', '') + result = mod.resolve_default_project_dir(str(invalid)) + assert result is None + + +# --------------------------------------------------------------------------- +# Tests: tools +# --------------------------------------------------------------------------- + + +class TestBuildProject: + def test_returns_error_when_no_valid_dir( + self, tmp_path: Path, mcp_ext: tuple[types.ModuleType, _MockFastMCP], monkeypatch: pytest.MonkeyPatch + ) -> None: + mod, mock_mcp = mcp_ext + invalid = _make_invalid_project(tmp_path / 'bad') + monkeypatch.setenv('IDF_MCP_WORKSPACE_FOLDER', '') + tools, _ = _start_server(mcp_ext, mock_mcp, str(invalid)) + result = tools['build_project'](project_dir=None) + assert 'No valid ESP-IDF project directory found' in result + + def test_explicit_invalid_dir_returns_error_not_fallback( + self, tmp_path: Path, mcp_ext: tuple[types.ModuleType, _MockFastMCP], monkeypatch: pytest.MonkeyPatch + ) -> None: + mod, mock_mcp = mcp_ext + bad = _make_invalid_project(tmp_path / 'bad') + good_env = _make_valid_project(tmp_path / 'good_env') + # Even though IDF_MCP_WORKSPACE_FOLDER is a valid project, passing an + # explicit but invalid project_dir must return an error, not silently + # fall through to the env var project. + monkeypatch.setenv('IDF_MCP_WORKSPACE_FOLDER', str(good_env)) + tools, _ = _start_server(mcp_ext, mock_mcp, str(bad)) + result = tools['build_project'](project_dir=str(bad)) + assert str(bad) in result + assert 'not a valid' in result + + def test_explicit_dir_builds_in_correct_location( + self, tmp_path: Path, mcp_ext: tuple[types.ModuleType, _MockFastMCP], monkeypatch: pytest.MonkeyPatch + ) -> None: + mod, mock_mcp = mcp_ext + proj = _make_valid_project(tmp_path / 'proj') + invalid = _make_invalid_project(tmp_path / 'bad') + monkeypatch.setenv('IDF_MCP_WORKSPACE_FOLDER', '') + monkeypatch.setenv('IDF_PATH', str(tmp_path)) + tools, _ = _start_server(mcp_ext, mock_mcp, str(invalid)) + + with mock.patch('subprocess.run') as mock_run: + mock_run.return_value = mock.Mock(returncode=0, stderr='') + result = tools['build_project'](project_dir=str(proj)) + + assert result == 'Successfully built project' + call_args = mock_run.call_args + cmd = call_args[0][0] + assert '-C' in cmd + assert str(proj) in cmd + assert cmd[cmd.index('-C') + 1] == str(proj) + # cwd is intentionally not passed — -C is authoritative for idf.py + assert call_args[1].get('cwd') is None + + def test_build_failure_returns_error( + self, tmp_path: Path, mcp_ext: tuple[types.ModuleType, _MockFastMCP], monkeypatch: pytest.MonkeyPatch + ) -> None: + mod, mock_mcp = mcp_ext + proj = _make_valid_project(tmp_path / 'proj') + monkeypatch.setenv('IDF_MCP_WORKSPACE_FOLDER', '') + monkeypatch.setenv('IDF_PATH', str(tmp_path)) + tools, _ = _start_server(mcp_ext, mock_mcp, str(proj)) + + with mock.patch('subprocess.run') as mock_run: + mock_run.return_value = mock.Mock(returncode=1, stderr='cmake error') + result = tools['build_project']() + + assert 'Build failed' in result + assert 'cmake error' in result + + +class TestSetTarget: + def test_returns_error_when_no_valid_dir( + self, tmp_path: Path, mcp_ext: tuple[types.ModuleType, _MockFastMCP], monkeypatch: pytest.MonkeyPatch + ) -> None: + mod, mock_mcp = mcp_ext + invalid = _make_invalid_project(tmp_path / 'bad') + monkeypatch.setenv('IDF_MCP_WORKSPACE_FOLDER', '') + tools, _ = _start_server(mcp_ext, mock_mcp, str(invalid)) + result = tools['set_target']('esp32c6') + assert 'No valid ESP-IDF project directory found' in result + + def test_explicit_dir_sets_target( + self, tmp_path: Path, mcp_ext: tuple[types.ModuleType, _MockFastMCP], monkeypatch: pytest.MonkeyPatch + ) -> None: + mod, mock_mcp = mcp_ext + proj = _make_valid_project(tmp_path / 'proj') + monkeypatch.setenv('IDF_MCP_WORKSPACE_FOLDER', '') + monkeypatch.setenv('IDF_PATH', str(tmp_path)) + tools, _ = _start_server(mcp_ext, mock_mcp, str(proj)) + + with mock.patch('subprocess.run') as mock_run: + mock_run.return_value = mock.Mock(returncode=0, stderr='') + result = tools['set_target']('esp32s3', project_dir=str(proj)) + + assert result == 'Target set to: esp32s3' + cmd = mock_run.call_args[0][0] + assert 'set-target' in cmd + assert 'esp32s3' in cmd + assert '-C' in cmd + assert cmd[cmd.index('-C') + 1] == str(proj) + + +class TestFlashProject: + def test_returns_error_when_no_valid_dir( + self, tmp_path: Path, mcp_ext: tuple[types.ModuleType, _MockFastMCP], monkeypatch: pytest.MonkeyPatch + ) -> None: + mod, mock_mcp = mcp_ext + invalid = _make_invalid_project(tmp_path / 'bad') + monkeypatch.setenv('IDF_MCP_WORKSPACE_FOLDER', '') + tools, _ = _start_server(mcp_ext, mock_mcp, str(invalid)) + result = tools['flash_project']() + assert 'No valid ESP-IDF project directory found' in result + + def test_port_and_dir_forwarded( + self, tmp_path: Path, mcp_ext: tuple[types.ModuleType, _MockFastMCP], monkeypatch: pytest.MonkeyPatch + ) -> None: + mod, mock_mcp = mcp_ext + proj = _make_valid_project(tmp_path / 'proj') + monkeypatch.setenv('IDF_MCP_WORKSPACE_FOLDER', '') + monkeypatch.setenv('IDF_PATH', str(tmp_path)) + tools, _ = _start_server(mcp_ext, mock_mcp, str(proj)) + + with mock.patch('subprocess.run') as mock_run: + mock_run.return_value = mock.Mock(returncode=0, stderr='') + result = tools['flash_project'](port='/dev/ttyUSB0', project_dir=str(proj)) + + assert 'Successfully flashed' in result + assert '/dev/ttyUSB0' in result + cmd = mock_run.call_args[0][0] + assert '-p' in cmd + assert '/dev/ttyUSB0' in cmd + assert '-C' in cmd + assert cmd[cmd.index('-C') + 1] == str(proj) + + +class TestCreateProject: + def test_creates_project_with_explicit_path( + self, tmp_path: Path, mcp_ext: tuple[types.ModuleType, _MockFastMCP], monkeypatch: pytest.MonkeyPatch + ) -> None: + mod, mock_mcp = mcp_ext + monkeypatch.setenv('IDF_PATH', str(tmp_path)) + # Start from a non-project directory — that is the whole point of this tool + tools, _ = _start_server(mcp_ext, mock_mcp, str(tmp_path)) + + with mock.patch('subprocess.run') as mock_run: + mock_run.return_value = mock.Mock(returncode=0, stdout='', stderr='') + result = tools['create_project']('my_app', path=str(tmp_path)) + + assert 'my_app' in result + assert str(tmp_path) in result + cmd = mock_run.call_args[0][0] + assert 'create-project' in cmd + assert 'my_app' in cmd + assert '-C' in cmd + assert cmd[cmd.index('-C') + 1] == str(tmp_path) + + def test_uses_project_path_when_no_path_given( + self, tmp_path: Path, mcp_ext: tuple[types.ModuleType, _MockFastMCP], monkeypatch: pytest.MonkeyPatch + ) -> None: + mod, mock_mcp = mcp_ext + monkeypatch.setenv('IDF_PATH', str(tmp_path)) + tools, _ = _start_server(mcp_ext, mock_mcp, str(tmp_path)) + + with mock.patch('subprocess.run') as mock_run: + mock_run.return_value = mock.Mock(returncode=0, stdout='', stderr='') + tools['create_project']('my_app') + + cmd = mock_run.call_args[0][0] + assert cmd[cmd.index('-C') + 1] == str(tmp_path) + + def test_returns_error_when_parent_dir_missing( + self, tmp_path: Path, mcp_ext: tuple[types.ModuleType, _MockFastMCP], monkeypatch: pytest.MonkeyPatch + ) -> None: + mod, mock_mcp = mcp_ext + tools, _ = _start_server(mcp_ext, mock_mcp, str(tmp_path)) + + result = tools['create_project']('my_app', path=str(tmp_path / 'nonexistent')) + assert 'does not exist' in result + + def test_returns_error_on_idf_failure( + self, tmp_path: Path, mcp_ext: tuple[types.ModuleType, _MockFastMCP], monkeypatch: pytest.MonkeyPatch + ) -> None: + mod, mock_mcp = mcp_ext + monkeypatch.setenv('IDF_PATH', str(tmp_path)) + tools, _ = _start_server(mcp_ext, mock_mcp, str(tmp_path)) + + with mock.patch('subprocess.run') as mock_run: + mock_run.return_value = mock.Mock(returncode=3, stdout='', stderr='directory not empty') + result = tools['create_project']('my_app', path=str(tmp_path)) + + assert 'Failed to create project' in result + assert 'directory not empty' in result + + +class TestCleanProject: + def test_returns_error_when_no_valid_dir( + self, tmp_path: Path, mcp_ext: tuple[types.ModuleType, _MockFastMCP], monkeypatch: pytest.MonkeyPatch + ) -> None: + mod, mock_mcp = mcp_ext + invalid = _make_invalid_project(tmp_path / 'bad') + monkeypatch.setenv('IDF_MCP_WORKSPACE_FOLDER', '') + tools, _ = _start_server(mcp_ext, mock_mcp, str(invalid)) + result = tools['clean_project']() + assert 'No valid ESP-IDF project directory found' in result + + def test_explicit_dir_cleans( + self, tmp_path: Path, mcp_ext: tuple[types.ModuleType, _MockFastMCP], monkeypatch: pytest.MonkeyPatch + ) -> None: + mod, mock_mcp = mcp_ext + proj = _make_valid_project(tmp_path / 'proj') + monkeypatch.setenv('IDF_MCP_WORKSPACE_FOLDER', '') + monkeypatch.setenv('IDF_PATH', str(tmp_path)) + tools, _ = _start_server(mcp_ext, mock_mcp, str(proj)) + + with mock.patch('subprocess.run') as mock_run: + mock_run.return_value = mock.Mock(returncode=0, stderr='') + result = tools['clean_project'](project_dir=str(proj)) + + assert result == 'Project cleaned successfully' + cmd = mock_run.call_args[0][0] + assert 'clean' in cmd + assert '-C' in cmd + assert cmd[cmd.index('-C') + 1] == str(proj) + + +# --------------------------------------------------------------------------- +# Test: server starts without error when project_path is not valid +# --------------------------------------------------------------------------- + + +class TestServerStartsOutsideProject: + def test_no_fatal_error_when_project_path_invalid( + self, tmp_path: Path, mcp_ext: tuple[types.ModuleType, _MockFastMCP], monkeypatch: pytest.MonkeyPatch + ) -> None: + """start_mcp_server must not raise when project_path is not a valid project.""" + mod, mock_mcp = mcp_ext + invalid = _make_invalid_project(tmp_path / 'not_a_project') + monkeypatch.setenv('IDF_MCP_WORKSPACE_FOLDER', '') + + ext = mod.action_extensions({}, str(invalid)) + callback = ext['actions']['mcp-server']['callback'] + fake_args = {'build_dir': str(invalid / 'build')} + + # Should not raise + callback('mcp-server', ctx=None, args=fake_args) + + # FastMCP was still initialised + assert mock_mcp.name == 'ESP-IDF' + + def test_tools_registered_even_when_project_path_invalid( + self, tmp_path: Path, mcp_ext: tuple[types.ModuleType, _MockFastMCP], monkeypatch: pytest.MonkeyPatch + ) -> None: + mod, mock_mcp = mcp_ext + invalid = _make_invalid_project(tmp_path / 'not_a_project') + monkeypatch.setenv('IDF_MCP_WORKSPACE_FOLDER', '') + + tools, _ = _start_server(mcp_ext, mock_mcp, str(invalid)) + assert 'build_project' in tools + assert 'set_target' in tools + assert 'flash_project' in tools + assert 'clean_project' in tools + + +# --------------------------------------------------------------------------- +# Tests: resources use resolve_default_project_dir +# --------------------------------------------------------------------------- + + +class TestGetProjectStatus: + def test_returns_error_json_when_no_valid_dir( + self, tmp_path: Path, mcp_ext: tuple[types.ModuleType, _MockFastMCP], monkeypatch: pytest.MonkeyPatch + ) -> None: + mod, mock_mcp = mcp_ext + invalid = _make_invalid_project(tmp_path / 'bad') + monkeypatch.setenv('IDF_MCP_WORKSPACE_FOLDER', '') + _, resources = _start_server(mcp_ext, mock_mcp, str(invalid)) + + result = json.loads(resources['project://status']()) + assert 'error' in result + assert 'idf_version' in result + + def test_returns_status_when_valid_project( + self, tmp_path: Path, mcp_ext: tuple[types.ModuleType, _MockFastMCP], monkeypatch: pytest.MonkeyPatch + ) -> None: + mod, mock_mcp = mcp_ext + proj = _make_valid_project(tmp_path / 'proj') + monkeypatch.setenv('IDF_MCP_WORKSPACE_FOLDER', '') + _, resources = _start_server(mcp_ext, mock_mcp, str(proj)) + + result = json.loads(resources['project://status']()) + assert result['project_path'] == str(proj) + assert result['target'] == 'esp32' + assert 'error' not in result + + def test_uses_env_var_when_project_path_invalid( + self, tmp_path: Path, mcp_ext: tuple[types.ModuleType, _MockFastMCP], monkeypatch: pytest.MonkeyPatch + ) -> None: + mod, mock_mcp = mcp_ext + invalid = _make_invalid_project(tmp_path / 'bad') + env_proj = _make_valid_project(tmp_path / 'env_proj') + monkeypatch.setenv('IDF_MCP_WORKSPACE_FOLDER', str(env_proj)) + _, resources = _start_server(mcp_ext, mock_mcp, str(invalid)) + + result = json.loads(resources['project://status']()) + assert result['project_path'] == str(env_proj) + assert 'error' not in result + + +class TestGetProjectConfig: + def test_returns_error_json_when_no_valid_dir( + self, tmp_path: Path, mcp_ext: tuple[types.ModuleType, _MockFastMCP], monkeypatch: pytest.MonkeyPatch + ) -> None: + mod, mock_mcp = mcp_ext + invalid = _make_invalid_project(tmp_path / 'bad') + monkeypatch.setenv('IDF_MCP_WORKSPACE_FOLDER', '') + _, resources = _start_server(mcp_ext, mock_mcp, str(invalid)) + + result = json.loads(resources['project://config']()) + assert 'error' in result + + def test_returns_no_build_dir_when_build_missing( + self, tmp_path: Path, mcp_ext: tuple[types.ModuleType, _MockFastMCP], monkeypatch: pytest.MonkeyPatch + ) -> None: + mod, mock_mcp = mcp_ext + proj = _make_valid_project(tmp_path / 'proj') + monkeypatch.setenv('IDF_MCP_WORKSPACE_FOLDER', '') + _, resources = _start_server(mcp_ext, mock_mcp, str(proj)) + + # build/ does not exist → build_dir_exists: False + result = json.loads(resources['project://config']()) + assert result.get('build_dir_exists') is False diff --git a/tools/tools.json b/tools/tools.json index 703bfea13e3..94129721e65 100644 --- a/tools/tools.json +++ b/tools/tools.json @@ -995,31 +995,31 @@ "versions": [ { "linux-amd64": { - "sha256": "588bfaccd0f929650655d10a580f020c6ba9c131712d8fa519280081b8d126eb", - "size": 15648448, - "url": "https://github.com/espressif/qemu/releases/download/esp-develop-9.2.2-20250817/qemu-xtensa-softmmu-esp_develop_9.2.2_20250817-x86_64-linux-gnu.tar.xz" + "sha256": "0eecb2a34a5586c0e59110f77b9343b7b336e82fdb0e1a30e1dc1bab8a547e35", + "size": 15619396, + "url": "https://github.com/espressif/qemu/releases/download/esp-develop-9.2.2-20260417/qemu-xtensa-softmmu-esp_develop_9.2.2_20260417-x86_64-linux-gnu.tar.xz" }, "linux-arm64": { - "sha256": "317f6e0fd1dba0886d8110709823d909593ef29438822a14f81ebe19d72ce7cd", - "size": 15123084, - "url": "https://github.com/espressif/qemu/releases/download/esp-develop-9.2.2-20250817/qemu-xtensa-softmmu-esp_develop_9.2.2_20250817-aarch64-linux-gnu.tar.xz" + "sha256": "00de5985094c14e47d1b38464a006b8ed64fd0fa7a289c56da24f3a329f65339", + "size": 15111492, + "url": "https://github.com/espressif/qemu/releases/download/esp-develop-9.2.2-20260417/qemu-xtensa-softmmu-esp_develop_9.2.2_20260417-aarch64-linux-gnu.tar.xz" }, "macos": { - "sha256": "00b9dbc2124cf7633cb86f264fbc524226ad4001bce68bbdba43c9bdc4eb026e", - "size": 4092932, - "url": "https://github.com/espressif/qemu/releases/download/esp-develop-9.2.2-20250817/qemu-xtensa-softmmu-esp_develop_9.2.2_20250817-x86_64-apple-darwin.tar.xz" + "sha256": "ae8170fe46bcdfa54a7c0d7afcdb7a066711991be680f72ff7bbc6c4ae3ad88f", + "size": 3870980, + "url": "https://github.com/espressif/qemu/releases/download/esp-develop-9.2.2-20260417/qemu-xtensa-softmmu-esp_develop_9.2.2_20260417-x86_64-apple-darwin.tar.xz" }, "macos-arm64": { - "sha256": "aa92e337461d482f5d9f31cd8efc0bd67b3de8fcfcfb567289cb43a59c184651", - "size": 3882404, - "url": "https://github.com/espressif/qemu/releases/download/esp-develop-9.2.2-20250817/qemu-xtensa-softmmu-esp_develop_9.2.2_20250817-aarch64-apple-darwin.tar.xz" + "sha256": "bb8c15810565d3df1665dc34962430885e11bc95575b228fb44698146be1e9d6", + "size": 3867936, + "url": "https://github.com/espressif/qemu/releases/download/esp-develop-9.2.2-20260417/qemu-xtensa-softmmu-esp_develop_9.2.2_20260417-aarch64-apple-darwin.tar.xz" }, - "name": "esp_develop_9.2.2_20250817", + "name": "esp_develop_9.2.2_20260417", "status": "recommended", "win64": { - "sha256": "ef550b912726997f3c1ff4a4fb13c1569e2b692efdc5c9f9c3c926a8f7c540fa", - "size": 34664956, - "url": "https://github.com/espressif/qemu/releases/download/esp-develop-9.2.2-20250817/qemu-xtensa-softmmu-esp_develop_9.2.2_20250817-x86_64-w64-mingw32.tar.xz" + "sha256": "3c483d77f5350a568df1faf4d8dbc82c95d6bc2b826d0d4be910485e0a68ca2a", + "size": 35996720, + "url": "https://github.com/espressif/qemu/releases/download/esp-develop-9.2.2-20260417/qemu-xtensa-softmmu-esp_develop_9.2.2_20260417-x86_64-w64-mingw32.tar.xz" } } ] @@ -1048,31 +1048,31 @@ "versions": [ { "linux-amd64": { - "sha256": "373b37a68bae3ef441ead24a7bfc950fcbfc274cbdd2b628fc6915f179eb1d8e", - "size": 16850080, - "url": "https://github.com/espressif/qemu/releases/download/esp-develop-9.2.2-20250817/qemu-riscv32-softmmu-esp_develop_9.2.2_20250817-x86_64-linux-gnu.tar.xz" + "sha256": "547f03e04701a92cbb699f7f7d015adc1f5b5ef93cbb94c0dd9b7107e2d84e77", + "size": 16842920, + "url": "https://github.com/espressif/qemu/releases/download/esp-develop-9.2.2-20260417/qemu-riscv32-softmmu-esp_develop_9.2.2_20260417-x86_64-linux-gnu.tar.xz" }, "linux-arm64": { - "sha256": "f907a54313058f8a9681d2f48257d518950ff98bcd5a319194b4bee7c10cf223", - "size": 16332832, - "url": "https://github.com/espressif/qemu/releases/download/esp-develop-9.2.2-20250817/qemu-riscv32-softmmu-esp_develop_9.2.2_20250817-aarch64-linux-gnu.tar.xz" + "sha256": "a9f7b98636008edcf7a11c96f10b3a3ec83c2a890fc54c3e3ceb3ec9edace427", + "size": 16325180, + "url": "https://github.com/espressif/qemu/releases/download/esp-develop-9.2.2-20260417/qemu-riscv32-softmmu-esp_develop_9.2.2_20260417-aarch64-linux-gnu.tar.xz" }, "macos": { - "sha256": "820028ee7cd2dd8fe8cd8ca5519ab6e792d15fea9367c4525cf63c0f707c0b1f", - "size": 4086872, - "url": "https://github.com/espressif/qemu/releases/download/esp-develop-9.2.2-20250817/qemu-riscv32-softmmu-esp_develop_9.2.2_20250817-x86_64-apple-darwin.tar.xz" + "sha256": "a47c38c6e2eb9f5028eda9585dce999ce02b8983a2cdf71c48cfb10a14ae25fe", + "size": 3763576, + "url": "https://github.com/espressif/qemu/releases/download/esp-develop-9.2.2-20260417/qemu-riscv32-softmmu-esp_develop_9.2.2_20260417-x86_64-apple-darwin.tar.xz" }, "macos-arm64": { - "sha256": "234690b6fa7c1d5dfe3dbb2bdd0c2810755e7c98999a9f21c389a6046b7eb76d", - "size": 3761104, - "url": "https://github.com/espressif/qemu/releases/download/esp-develop-9.2.2-20250817/qemu-riscv32-softmmu-esp_develop_9.2.2_20250817-aarch64-apple-darwin.tar.xz" + "sha256": "67bff66ff7158f272ce167fc211c0f8f4c1a79b6f6174350678a6d5035644b30", + "size": 3763728, + "url": "https://github.com/espressif/qemu/releases/download/esp-develop-9.2.2-20260417/qemu-riscv32-softmmu-esp_develop_9.2.2_20260417-aarch64-apple-darwin.tar.xz" }, - "name": "esp_develop_9.2.2_20250817", + "name": "esp_develop_9.2.2_20260417", "status": "recommended", "win64": { - "sha256": "9474015f24d27acb7516955ec932e5307226bd9d6652cdc870793ed36010ab73", - "size": 37189656, - "url": "https://github.com/espressif/qemu/releases/download/esp-develop-9.2.2-20250817/qemu-riscv32-softmmu-esp_develop_9.2.2_20250817-x86_64-w64-mingw32.tar.xz" + "sha256": "697aa4800a1f52be0b1693b30e22a684f7ea93c46c489e619384cae7b0e9b87b", + "size": 38496020, + "url": "https://github.com/espressif/qemu/releases/download/esp-develop-9.2.2-20260417/qemu-riscv32-softmmu-esp_develop_9.2.2_20260417-x86_64-w64-mingw32.tar.xz" } } ]