From 25e679ed524368c0e5a6561ce53331a6a1eee962 Mon Sep 17 00:00:00 2001 From: Marius Tache <102153746+marius-alex-tache@users.noreply.github.com> Date: Tue, 3 Oct 2023 18:32:48 +0300 Subject: [PATCH] [NXP] Initial support for K32W1 (#28777) * [NXP] Introducing Matter support for K32W1 Additional information: * SDK: 2.12.5 K32W148-EVK, downloadable from https://mcuxpresso.nxp.com * MCU overview: https://www.nxp.com/products/wireless/multiprotocol-mcus/secure-and-ultra-low-power-mcu-for-matter-over-thread-and-bluetooth-le-5-3:K32W148 Signed-off-by: Doru Gucea Signed-off-by: Marius Tache * [NXP] Add CI/CV support for K32W1 Signed-off-by: Marius Tache * [NXP] Bump ot-nxp submodule Signed-off-by: Marius Tache * [K32W] Add OPENTHREAD_PLATFORM_CORE_CONFIG_FILE define Signed-off-by: Marius Tache * [K32W1] Fix misspell Signed-off-by: Marius Tache * Restyled by whitespace * Restyled by clang-format * Restyled by gn * Restyled by prettier-markdown * Restyled by autopep8 * Restyled by isort * [K32W1] Fix lint errors Signed-off-by: Marius Tache * [K32W1] Removed deprecated class Signed-off-by: Marius Tache * Restyled by clang-format * [test] Update testdata/all_targets_linux_x64 with K32W1 targets Signed-off-by: Marius Tache * [K32W] Fix target names in workflow Signed-off-by: Marius Tache * [K32W1] Remove deprecated commented sections Signed-off-by: Marius Tache * [K32W1] Add missing # to reference targets in README Signed-off-by: Marius Tache * [K32W1] Fix spelling errors Signed-off-by: Marius Tache * [K32W1] Update CSR crypto flag usage Signed-off-by: Marius Tache * [K32W1] Remove ResetWatermarks empty implementation Signed-off-by: Marius Tache * Fix typo in device.gni after fixing conflict Signed-off-by: marius-alex-tache * [K32W1] Remove anchors and backtick some words Signed-off-by: marius-alex-tache * [K32W] Fix confusing naming in K32W builder Signed-off-by: marius-alex-tache * [K32W] Remove commented option Signed-off-by: marius-alex-tache * [K32W] Fix binary extension format Signed-off-by: marius-alex-tache * Restyled by autopep8 * [K32W] Fix binary name when copying Signed-off-by: marius-alex-tache * Restyled by clang-format * [K32W1] Fix ErrorStr header inclusion Signed-off-by: marius-alex-tache --------- Signed-off-by: Doru Gucea Signed-off-by: Marius Tache Signed-off-by: marius-alex-tache Co-authored-by: Doru Gucea Co-authored-by: Restyled.io --- .github/workflows/examples-k32w.yaml | 34 +- .gitmodules | 6 +- build_overrides/k32w1_sdk.gni | 18 + examples/build_overrides/k32w1_sdk.gni | 19 + .../contact-sensor-app/nxp/k32w/k32w1/.gn | 29 + .../nxp/k32w/k32w1/BUILD.gn | 136 +++ .../nxp/k32w/k32w1/README.md | 414 +++++++ .../nxp/k32w/k32w1/args.gni | 25 + .../nxp/k32w/k32w1/build_overrides | 1 + .../k32w/k32w1/include/CHIPProjectConfig.h | 178 +++ .../nxp/k32w/k32w1/include/FreeRTOSConfig.h | 207 ++++ .../nxp/k32w/k32w1/main/AppTask.cpp | 812 +++++++++++++ .../k32w/k32w1/main/ContactSensorManager.cpp | 84 ++ .../nxp/k32w/k32w1/main/ZclCallbacks.cpp | 98 ++ .../nxp/k32w/k32w1/main/include/AppEvent.h | 63 ++ .../nxp/k32w/k32w1/main/include/AppTask.h | 129 +++ .../k32w1/main/include/ContactSensorManager.h | 66 ++ .../nxp/k32w/k32w1/main/include/app_config.h | 51 + .../nxp/k32w/k32w1/main/main.cpp | 143 +++ .../k32w/k32w1/third_party/connectedhomeip | 1 + examples/lighting-app/nxp/k32w/k32w1/.gn | 28 + examples/lighting-app/nxp/k32w/k32w1/BUILD.gn | 141 +++ .../lighting-app/nxp/k32w/k32w1/README.md | 425 +++++++ examples/lighting-app/nxp/k32w/k32w1/args.gni | 23 + .../nxp/k32w/k32w1/build_overrides | 1 + .../k32w/k32w1/include/CHIPProjectConfig.h | 176 +++ .../nxp/k32w/k32w1/include/FreeRTOSConfig.h | 181 +++ .../nxp/k32w/k32w1/main/AppTask.cpp | 870 ++++++++++++++ .../nxp/k32w/k32w1/main/LightingManager.cpp | 80 ++ .../nxp/k32w/k32w1/main/ZclCallbacks.cpp | 70 ++ .../nxp/k32w/k32w1/main/include/AppEvent.h | 57 + .../nxp/k32w/k32w1/main/include/AppTask.h | 119 ++ .../k32w/k32w1/main/include/LightingManager.h | 68 ++ .../nxp/k32w/k32w1/main/include/app_config.h | 49 + .../lighting-app/nxp/k32w/k32w1/main/main.cpp | 140 +++ .../k32w/k32w1/third_party/connectedhomeip | 1 + examples/platform/nxp/k32w/k32w1/BUILD.gn | 33 + examples/platform/nxp/k32w/k32w1/app/BUILD.gn | 27 + examples/platform/nxp/k32w/k32w1/app/args.gni | 33 + .../nxp/k32w/k32w1/app/ldscripts/k32w1_app.ld | 52 + .../app/project_include/OpenThreadConfig.h | 81 ++ .../nxp/k32w/k32w1/app/support/BUILD.gn | 53 + .../k32w/k32w1/app/support/FreeRtosHooks.c | 152 +++ .../k32w/k32w1/app/support/FreeRtosHooks.h | 42 + .../nxp/k32w/k32w1/app/support/Memconfig.cpp | 184 +++ examples/platform/nxp/k32w/k32w1/args.gni | 38 + .../nxp/k32w/k32w1/doc/images/debug_k32w1.jpg | Bin 0 -> 60945 bytes .../nxp/k32w/k32w1/doc/images/import_demo.jpg | Bin 0 -> 43033 bytes .../k32w/k32w1/doc/images/installed_sdks.jpg | Bin 0 -> 47309 bytes .../nxp/k32w/k32w1/doc/images/k32w1-evk.jpg | Bin 0 -> 187204 bytes .../k32w1/doc/images/mcux-sdk-download.jpg | Bin 0 -> 54407 bytes .../nxp/k32w/k32w1/doc/images/new_project.jpg | Bin 0 -> 50137 bytes .../k32w/k32w1/doc/images/ota_topology.JPG | Bin 0 -> 19680 bytes .../nxp/k32w/k32w1/util/LEDWidget.cpp | 93 ++ .../nxp/k32w/k32w1/util/include/LEDWidget.h | 42 + scripts/build/build/targets.py | 11 +- scripts/build/builders/k32w.py | 64 +- .../build/testdata/all_targets_linux_x64.txt | 2 +- scripts/checkout_submodules.py | 2 +- src/lwip/BUILD.gn | 15 +- src/platform/BUILD.gn | 5 + src/platform/device.gni | 27 +- .../nxp/k32w/common/BLEManagerCommon.cpp | 12 + .../nxp/k32w/common/K32W_OTA_README.md | 13 +- .../nxp/k32w/common/OTAImageProcessorImpl.cpp | 49 +- .../nxp/k32w/common/OTATlvProcessor.h | 3 +- .../nxp/k32w/k32w1/BLEManagerImpl.cpp | 106 ++ src/platform/nxp/k32w/k32w1/BLEManagerImpl.h | 99 ++ src/platform/nxp/k32w/k32w1/BUILD.gn | 112 ++ .../nxp/k32w/k32w1/BlePlatformConfig.h | 36 + .../nxp/k32w/k32w1/CHIPCryptoPalK32W1.cpp | 1004 +++++++++++++++++ .../nxp/k32w/k32w1/CHIPDevicePlatformConfig.h | 117 ++ .../nxp/k32w/k32w1/CHIPDevicePlatformEvent.h | 62 + .../nxp/k32w/k32w1/CHIPPlatformConfig.h | 78 ++ .../k32w/k32w1/ConfigurationManagerImpl.cpp | 291 +++++ .../nxp/k32w/k32w1/ConfigurationManagerImpl.h | 95 ++ .../k32w/k32w1/ConnectivityManagerImpl.cpp | 83 ++ .../nxp/k32w/k32w1/ConnectivityManagerImpl.h | 105 ++ .../k32w1/DefaultTestEventTriggerDelegate.cpp | 42 + .../k32w1/DefaultTestEventTriggerDelegate.h | 39 + .../k32w/k32w1/DiagnosticDataProviderImpl.cpp | 232 ++++ .../k32w/k32w1/DiagnosticDataProviderImpl.h | 66 ++ .../nxp/k32w/k32w1/InetPlatformConfig.h | 43 + src/platform/nxp/k32w/k32w1/K32W1Config.cpp | 435 +++++++ src/platform/nxp/k32w/k32w1/K32W1Config.h | 144 +++ .../K32W1PersistentStorageOpKeystore.cpp | 333 ++++++ .../k32w1/K32W1PersistentStorageOpKeystore.h | 181 +++ .../k32w/k32w1/KeyValueStoreManagerImpl.cpp | 225 ++++ .../nxp/k32w/k32w1/KeyValueStoreManagerImpl.h | 83 ++ src/platform/nxp/k32w/k32w1/Logging.cpp | 198 ++++ src/platform/nxp/k32w/k32w1/LowPowerHooks.cpp | 45 + .../nxp/k32w/k32w1/OTAFirmwareProcessor.cpp | 126 +++ .../nxp/k32w/k32w1/OTAFirmwareProcessor.h | 57 + src/platform/nxp/k32w/k32w1/OTAHooks.cpp | 119 ++ .../nxp/k32w/k32w1/PlatformManagerImpl.cpp | 140 +++ .../nxp/k32w/k32w1/PlatformManagerImpl.h | 96 ++ .../k32w/k32w1/SoftwareUpdateManagerImpl.cpp | 46 + .../k32w/k32w1/SoftwareUpdateManagerImpl.h | 89 ++ .../nxp/k32w/k32w1/SystemPlatformConfig.h | 42 + .../nxp/k32w/k32w1/SystemTimeSupport.cpp | 122 ++ .../nxp/k32w/k32w1/ThreadStackManagerImpl.cpp | 127 +++ .../nxp/k32w/k32w1/ThreadStackManagerImpl.h | 121 ++ src/platform/nxp/k32w/k32w1/args.gni | 50 + .../nxp/k32w/k32w1/ble_function_mux.c | 94 ++ .../nxp/k32w/k32w1/ble_function_mux.h | 34 + src/platform/nxp/k32w/k32w1/gatt_db.h | 30 + src/platform/nxp/k32w/k32w1/gatt_uuid128.h | 26 + .../k32w/k32w1/k32w1-chip-mbedtls-config.h | 131 +++ src/platform/nxp/k32w/k32w1/ram_storage.c | 225 ++++ src/platform/nxp/k32w/k32w1/ram_storage.h | 103 ++ src/system/BUILD.gn | 5 + third_party/nxp/k32w1_sdk/BUILD.gn | 81 ++ .../nxp/k32w1_sdk/Jlink_Script/K32W1.jlink | 6 + .../nxp/k32w1_sdk/k32w1_executable.gni | 34 + third_party/nxp/k32w1_sdk/k32w1_sdk.gni | 495 ++++++++ third_party/openthread/ot-nxp | 2 +- .../platforms/nxp/k32w/k32w0/BUILD.gn | 1 + .../platforms/nxp/k32w/k32w1/BUILD.gn | 76 ++ 118 files changed, 12615 insertions(+), 93 deletions(-) create mode 100644 build_overrides/k32w1_sdk.gni create mode 100644 examples/build_overrides/k32w1_sdk.gni create mode 100644 examples/contact-sensor-app/nxp/k32w/k32w1/.gn create mode 100644 examples/contact-sensor-app/nxp/k32w/k32w1/BUILD.gn create mode 100644 examples/contact-sensor-app/nxp/k32w/k32w1/README.md create mode 100644 examples/contact-sensor-app/nxp/k32w/k32w1/args.gni create mode 120000 examples/contact-sensor-app/nxp/k32w/k32w1/build_overrides create mode 100644 examples/contact-sensor-app/nxp/k32w/k32w1/include/CHIPProjectConfig.h create mode 100644 examples/contact-sensor-app/nxp/k32w/k32w1/include/FreeRTOSConfig.h create mode 100644 examples/contact-sensor-app/nxp/k32w/k32w1/main/AppTask.cpp create mode 100644 examples/contact-sensor-app/nxp/k32w/k32w1/main/ContactSensorManager.cpp create mode 100644 examples/contact-sensor-app/nxp/k32w/k32w1/main/ZclCallbacks.cpp create mode 100644 examples/contact-sensor-app/nxp/k32w/k32w1/main/include/AppEvent.h create mode 100644 examples/contact-sensor-app/nxp/k32w/k32w1/main/include/AppTask.h create mode 100644 examples/contact-sensor-app/nxp/k32w/k32w1/main/include/ContactSensorManager.h create mode 100644 examples/contact-sensor-app/nxp/k32w/k32w1/main/include/app_config.h create mode 100644 examples/contact-sensor-app/nxp/k32w/k32w1/main/main.cpp create mode 120000 examples/contact-sensor-app/nxp/k32w/k32w1/third_party/connectedhomeip create mode 100644 examples/lighting-app/nxp/k32w/k32w1/.gn create mode 100644 examples/lighting-app/nxp/k32w/k32w1/BUILD.gn create mode 100644 examples/lighting-app/nxp/k32w/k32w1/README.md create mode 100644 examples/lighting-app/nxp/k32w/k32w1/args.gni create mode 120000 examples/lighting-app/nxp/k32w/k32w1/build_overrides create mode 100644 examples/lighting-app/nxp/k32w/k32w1/include/CHIPProjectConfig.h create mode 100644 examples/lighting-app/nxp/k32w/k32w1/include/FreeRTOSConfig.h create mode 100644 examples/lighting-app/nxp/k32w/k32w1/main/AppTask.cpp create mode 100644 examples/lighting-app/nxp/k32w/k32w1/main/LightingManager.cpp create mode 100644 examples/lighting-app/nxp/k32w/k32w1/main/ZclCallbacks.cpp create mode 100644 examples/lighting-app/nxp/k32w/k32w1/main/include/AppEvent.h create mode 100644 examples/lighting-app/nxp/k32w/k32w1/main/include/AppTask.h create mode 100644 examples/lighting-app/nxp/k32w/k32w1/main/include/LightingManager.h create mode 100644 examples/lighting-app/nxp/k32w/k32w1/main/include/app_config.h create mode 100644 examples/lighting-app/nxp/k32w/k32w1/main/main.cpp create mode 120000 examples/lighting-app/nxp/k32w/k32w1/third_party/connectedhomeip create mode 100644 examples/platform/nxp/k32w/k32w1/BUILD.gn create mode 100644 examples/platform/nxp/k32w/k32w1/app/BUILD.gn create mode 100644 examples/platform/nxp/k32w/k32w1/app/args.gni create mode 100644 examples/platform/nxp/k32w/k32w1/app/ldscripts/k32w1_app.ld create mode 100644 examples/platform/nxp/k32w/k32w1/app/project_include/OpenThreadConfig.h create mode 100644 examples/platform/nxp/k32w/k32w1/app/support/BUILD.gn create mode 100644 examples/platform/nxp/k32w/k32w1/app/support/FreeRtosHooks.c create mode 100644 examples/platform/nxp/k32w/k32w1/app/support/FreeRtosHooks.h create mode 100644 examples/platform/nxp/k32w/k32w1/app/support/Memconfig.cpp create mode 100644 examples/platform/nxp/k32w/k32w1/args.gni create mode 100644 examples/platform/nxp/k32w/k32w1/doc/images/debug_k32w1.jpg create mode 100644 examples/platform/nxp/k32w/k32w1/doc/images/import_demo.jpg create mode 100644 examples/platform/nxp/k32w/k32w1/doc/images/installed_sdks.jpg create mode 100644 examples/platform/nxp/k32w/k32w1/doc/images/k32w1-evk.jpg create mode 100644 examples/platform/nxp/k32w/k32w1/doc/images/mcux-sdk-download.jpg create mode 100644 examples/platform/nxp/k32w/k32w1/doc/images/new_project.jpg create mode 100644 examples/platform/nxp/k32w/k32w1/doc/images/ota_topology.JPG create mode 100644 examples/platform/nxp/k32w/k32w1/util/LEDWidget.cpp create mode 100644 examples/platform/nxp/k32w/k32w1/util/include/LEDWidget.h create mode 100644 src/platform/nxp/k32w/k32w1/BLEManagerImpl.cpp create mode 100644 src/platform/nxp/k32w/k32w1/BLEManagerImpl.h create mode 100644 src/platform/nxp/k32w/k32w1/BUILD.gn create mode 100644 src/platform/nxp/k32w/k32w1/BlePlatformConfig.h create mode 100644 src/platform/nxp/k32w/k32w1/CHIPCryptoPalK32W1.cpp create mode 100644 src/platform/nxp/k32w/k32w1/CHIPDevicePlatformConfig.h create mode 100644 src/platform/nxp/k32w/k32w1/CHIPDevicePlatformEvent.h create mode 100644 src/platform/nxp/k32w/k32w1/CHIPPlatformConfig.h create mode 100644 src/platform/nxp/k32w/k32w1/ConfigurationManagerImpl.cpp create mode 100644 src/platform/nxp/k32w/k32w1/ConfigurationManagerImpl.h create mode 100644 src/platform/nxp/k32w/k32w1/ConnectivityManagerImpl.cpp create mode 100644 src/platform/nxp/k32w/k32w1/ConnectivityManagerImpl.h create mode 100644 src/platform/nxp/k32w/k32w1/DefaultTestEventTriggerDelegate.cpp create mode 100644 src/platform/nxp/k32w/k32w1/DefaultTestEventTriggerDelegate.h create mode 100644 src/platform/nxp/k32w/k32w1/DiagnosticDataProviderImpl.cpp create mode 100644 src/platform/nxp/k32w/k32w1/DiagnosticDataProviderImpl.h create mode 100644 src/platform/nxp/k32w/k32w1/InetPlatformConfig.h create mode 100644 src/platform/nxp/k32w/k32w1/K32W1Config.cpp create mode 100644 src/platform/nxp/k32w/k32w1/K32W1Config.h create mode 100644 src/platform/nxp/k32w/k32w1/K32W1PersistentStorageOpKeystore.cpp create mode 100644 src/platform/nxp/k32w/k32w1/K32W1PersistentStorageOpKeystore.h create mode 100644 src/platform/nxp/k32w/k32w1/KeyValueStoreManagerImpl.cpp create mode 100644 src/platform/nxp/k32w/k32w1/KeyValueStoreManagerImpl.h create mode 100644 src/platform/nxp/k32w/k32w1/Logging.cpp create mode 100644 src/platform/nxp/k32w/k32w1/LowPowerHooks.cpp create mode 100644 src/platform/nxp/k32w/k32w1/OTAFirmwareProcessor.cpp create mode 100644 src/platform/nxp/k32w/k32w1/OTAFirmwareProcessor.h create mode 100644 src/platform/nxp/k32w/k32w1/OTAHooks.cpp create mode 100644 src/platform/nxp/k32w/k32w1/PlatformManagerImpl.cpp create mode 100644 src/platform/nxp/k32w/k32w1/PlatformManagerImpl.h create mode 100644 src/platform/nxp/k32w/k32w1/SoftwareUpdateManagerImpl.cpp create mode 100644 src/platform/nxp/k32w/k32w1/SoftwareUpdateManagerImpl.h create mode 100644 src/platform/nxp/k32w/k32w1/SystemPlatformConfig.h create mode 100644 src/platform/nxp/k32w/k32w1/SystemTimeSupport.cpp create mode 100644 src/platform/nxp/k32w/k32w1/ThreadStackManagerImpl.cpp create mode 100644 src/platform/nxp/k32w/k32w1/ThreadStackManagerImpl.h create mode 100644 src/platform/nxp/k32w/k32w1/args.gni create mode 100644 src/platform/nxp/k32w/k32w1/ble_function_mux.c create mode 100644 src/platform/nxp/k32w/k32w1/ble_function_mux.h create mode 100644 src/platform/nxp/k32w/k32w1/gatt_db.h create mode 100644 src/platform/nxp/k32w/k32w1/gatt_uuid128.h create mode 100644 src/platform/nxp/k32w/k32w1/k32w1-chip-mbedtls-config.h create mode 100644 src/platform/nxp/k32w/k32w1/ram_storage.c create mode 100644 src/platform/nxp/k32w/k32w1/ram_storage.h create mode 100644 third_party/nxp/k32w1_sdk/BUILD.gn create mode 100644 third_party/nxp/k32w1_sdk/Jlink_Script/K32W1.jlink create mode 100644 third_party/nxp/k32w1_sdk/k32w1_executable.gni create mode 100644 third_party/nxp/k32w1_sdk/k32w1_sdk.gni create mode 100644 third_party/openthread/platforms/nxp/k32w/k32w1/BUILD.gn diff --git a/.github/workflows/examples-k32w.yaml b/.github/workflows/examples-k32w.yaml index 0a79f70a125b72..1f9c2f5871c8b6 100644 --- a/.github/workflows/examples-k32w.yaml +++ b/.github/workflows/examples-k32w.yaml @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -name: Build example - K32W with SE051 +name: Build example - K32W on: push: @@ -37,7 +37,7 @@ jobs: if: github.actor != 'restyled-io[bot]' container: - image: ghcr.io/project-chip/chip-build-k32w:5 + image: ghcr.io/project-chip/chip-build-k32w:6 volumes: - "/tmp/bloat_reports:/tmp/bloat_reports" steps: @@ -46,7 +46,7 @@ jobs: - name: Checkout submodules & Bootstrap uses: ./.github/actions/checkout-submodules-and-bootstrap with: - platform: k32w0 + platform: k32w - name: Set up environment for size reports uses: ./.github/actions/setup-size-reports @@ -58,12 +58,14 @@ jobs: run: | scripts/run_in_build_env.sh "\ ./scripts/build/build_examples.py \ - --target k32w-light-crypto-platform-tokenizer \ - --target k32w-lock-crypto-platform-tokenizer \ - --target k32w-lock-crypto-platform-low-power-nologs \ - --target k32w-contact-crypto-platform-tokenizer \ - --target k32w-contact-crypto-platform-low-power-nologs \ - --target k32w-shell-crypto-platform \ + --target k32w-k32w0-light-crypto-platform-tokenizer \ + --target k32w-k32w0-lock-crypto-platform-tokenizer \ + --target k32w-k32w0-lock-crypto-platform-low-power-nologs \ + --target k32w-k32w0-contact-crypto-platform-tokenizer \ + --target k32w-k32w0-contact-crypto-platform-low-power-nologs \ + --target k32w-k32w0-shell-crypto-platform \ + --target k32w-k32w1-light-crypto-platform-openthread-ftd \ + --target k32w-k32w1-contact-crypto-platform-low-power-nologs \ build \ --copy-artifacts-to out/artifacts \ " @@ -71,19 +73,27 @@ jobs: run: | .environment/pigweed-venv/bin/python3 scripts/tools/memory/gh_sizes.py \ k32w k32w0+release light \ - out/artifacts/k32w-light-crypto-platform-tokenizer/chip-k32w0x-light-example \ + out/artifacts/k32w-k32w0-light-crypto-platform-tokenizer/chip-k32w0x-light-example.elf \ + /tmp/bloat_reports/ + .environment/pigweed-venv/bin/python3 scripts/tools/memory/gh_sizes.py \ + k32w k32w1+release light \ + out/artifacts/k32w-k32w1-light-crypto-platform-openthread-ftd/chip-k32w1-light-example.elf \ /tmp/bloat_reports/ - name: Get lock size stats run: | .environment/pigweed-venv/bin/python3 scripts/tools/memory/gh_sizes.py \ k32w k32w0+release lock \ - out/artifacts/k32w-lock-crypto-platform-tokenizer/chip-k32w0x-lock-example \ + out/artifacts/k32w-k32w0-lock-crypto-platform-tokenizer/chip-k32w0x-lock-example.elf \ /tmp/bloat_reports/ - name: Get contact size stats run: | .environment/pigweed-venv/bin/python3 scripts/tools/memory/gh_sizes.py \ k32w k32w0+release contact \ - out/artifacts/k32w-contact-crypto-platform-tokenizer/chip-k32w0x-contact-example \ + out/artifacts/k32w-k32w0-contact-crypto-platform-tokenizer/chip-k32w0x-contact-example.elf \ + /tmp/bloat_reports/ + .environment/pigweed-venv/bin/python3 scripts/tools/memory/gh_sizes.py \ + k32w k32w1+release contact \ + out/artifacts/k32w-k32w1-contact-crypto-platform-low-power-nologs/chip-k32w1-contact-example.elf \ /tmp/bloat_reports/ - name: Uploading Size Reports uses: ./.github/actions/upload-size-reports diff --git a/.gitmodules b/.gitmodules index b5d7c5dc253ca2..6ebfd6dbad7141 100644 --- a/.gitmodules +++ b/.gitmodules @@ -54,16 +54,16 @@ path = third_party/freertos/repo url = https://github.com/FreeRTOS/FreeRTOS-Kernel.git branch = V10.3.1-kernel-only - platforms = ameba,cc13xx_26xx,bouffalolab,efr32,esp32,k32w0,infineon,qpg,cc32xx,silabs_docker + platforms = ameba,cc13xx_26xx,bouffalolab,efr32,esp32,k32w,infineon,qpg,cc32xx,silabs_docker [submodule "simw-top-mini"] path = third_party/simw-top-mini/repo url = https://github.com/NXP/plug-and-trust.git branch = int/CHIPSE_Release - platforms = k32w0 + platforms = k32w [submodule "third_party/openthread/ot-nxp"] path = third_party/openthread/ot-nxp url = https://github.com/openthread/ot-nxp.git - platforms = k32w0 + platforms = k32w [submodule "third_party/openthread/ot-qorvo"] path = third_party/openthread/ot-qorvo url = https://github.com/openthread/ot-qorvo.git diff --git a/build_overrides/k32w1_sdk.gni b/build_overrides/k32w1_sdk.gni new file mode 100644 index 00000000000000..2f4f2320937360 --- /dev/null +++ b/build_overrides/k32w1_sdk.gni @@ -0,0 +1,18 @@ +# Copyright (c) 2020 Project CHIP Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +declare_args() { + # Root directory for K32W SDK build files. + k32w1_sdk_build_root = "//third_party/nxp/k32w1_sdk" +} diff --git a/examples/build_overrides/k32w1_sdk.gni b/examples/build_overrides/k32w1_sdk.gni new file mode 100644 index 00000000000000..ab4655d7717f9a --- /dev/null +++ b/examples/build_overrides/k32w1_sdk.gni @@ -0,0 +1,19 @@ +# Copyright (c) 2020 Project CHIP Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +declare_args() { + # Root directory for k32w SDK. + k32w1_sdk_build_root = + "//third_party/connectedhomeip/third_party/nxp/k32w1_sdk" +} diff --git a/examples/contact-sensor-app/nxp/k32w/k32w1/.gn b/examples/contact-sensor-app/nxp/k32w/k32w1/.gn new file mode 100644 index 00000000000000..dec954b4b9ff69 --- /dev/null +++ b/examples/contact-sensor-app/nxp/k32w/k32w1/.gn @@ -0,0 +1,29 @@ +# Copyright (c) 2020-2023 Project CHIP Authors +# Copyright (c) 2023 NXP +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import("//build_overrides/build.gni") + +# The location of the build configuration file. +buildconfig = "${build_root}/config/BUILDCONFIG.gn" + +# CHIP uses angle bracket includes. +check_system_includes = true + +default_args = { + target_cpu = "arm" + target_os = "freertos" + + import("//args.gni") +} diff --git a/examples/contact-sensor-app/nxp/k32w/k32w1/BUILD.gn b/examples/contact-sensor-app/nxp/k32w/k32w1/BUILD.gn new file mode 100644 index 00000000000000..1404ad8dd36a1c --- /dev/null +++ b/examples/contact-sensor-app/nxp/k32w/k32w1/BUILD.gn @@ -0,0 +1,136 @@ +# Copyright (c) 2021-2023 Project CHIP Authors +# Copyright (c) 2023 NXP +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import("//build_overrides/chip.gni") +import("//build_overrides/k32w1_sdk.gni") +import("//build_overrides/openthread.gni") + +import("${k32w1_sdk_build_root}/k32w1_executable.gni") +import("${k32w1_sdk_build_root}/k32w1_sdk.gni") + +import("${chip_root}/src/crypto/crypto.gni") +import("${chip_root}/src/lib/core/core.gni") +import("${chip_root}/src/platform/device.gni") + +declare_args() { + chip_software_version = 0 +} + +assert(current_os == "freertos") + +k32w1_platform_dir = "${chip_root}/examples/platform/nxp/k32w/k32w1" +k32w1_sdk_root = getenv("NXP_K32W1_SDK_ROOT") + +k32w1_sdk("sdk") { + sources = [ + "${k32w1_platform_dir}/app/project_include/OpenThreadConfig.h", + "include/CHIPProjectConfig.h", + "include/FreeRTOSConfig.h", + "main/include/app_config.h", + ] + + public_deps = + [ "${chip_root}/third_party/openthread/platforms:libopenthread-platform" ] + + include_dirs = [ + "main/include", + "main", + "include", + "${k32w1_platform_dir}/app/project_include", + "${k32w1_platform_dir}/app/support", + "${k32w1_platform_dir}/util/include", + ] + + defines = [] + if (is_debug) { + defines += [ "BUILD_RELEASE=0" ] + } else { + defines += [ "BUILD_RELEASE=1" ] + } + + if (chip_software_version != 0) { + defines += [ + "CHIP_DEVICE_CONFIG_DEVICE_SOFTWARE_VERSION=${chip_software_version}", + ] + } +} + +k32w1_executable("contact_sensor_app") { + output_name = "chip-k32w1-contact-example" + + sources = [ + "${k32w1_platform_dir}/util/LEDWidget.cpp", + "${k32w1_platform_dir}/util/include/LEDWidget.h", + "main/AppTask.cpp", + "main/ContactSensorManager.cpp", + "main/ZclCallbacks.cpp", + "main/include/AppEvent.h", + "main/include/AppTask.h", + "main/include/ContactSensorManager.h", + "main/main.cpp", + ] + + deps = [ + ":sdk", + "${chip_root}/examples/common/QRCode", + "${chip_root}/examples/contact-sensor-app/contact-sensor-common", + "${chip_root}/examples/providers:device_info_provider", + "${chip_root}/src/lib", + "${chip_root}/src/platform:syscalls_stub", + "${chip_root}/third_party/mbedtls:mbedtls", + "${k32w1_platform_dir}/app/support:freertos_mbedtls_utils", + ] + + if (chip_openthread_ftd) { + deps += [ + "${chip_root}/third_party/openthread/repo:libopenthread-cli-ftd", + "${chip_root}/third_party/openthread/repo:libopenthread-ftd", + ] + } else { + deps += [ + "${chip_root}/third_party/openthread/repo:libopenthread-cli-mtd", + "${chip_root}/third_party/openthread/repo:libopenthread-mtd", + ] + } + + cflags = [ "-Wconversion" ] + + output_dir = root_out_dir + + ldscript = "${k32w1_sdk_root}/middleware/wireless/framework/Common/devices/kw45_k32w1/gcc/connectivity.ld" + + inputs = [ ldscript ] + + ldflags = [ + "-Wl,--defsym=__heap_size__=0", + "-Wl,--defsym=__stack_size__=0x480", + "-Wl,--defsym=gNvmSectors=8", + "-Wl,--defsym=lp_ram_lower_limit=0x04000000", + "-Wl,--defsym=lp_ram_upper_limit=0x2001C000", + "-Wl,-print-memory-usage", + "-Wl,--no-warn-rwx-segments", + "-T" + rebase_path(ldscript, root_build_dir), + ] + + output_dir = root_out_dir +} + +group("k32w1") { + deps = [ ":contact_sensor_app" ] +} + +group("default") { + deps = [ ":k32w1" ] +} diff --git a/examples/contact-sensor-app/nxp/k32w/k32w1/README.md b/examples/contact-sensor-app/nxp/k32w/k32w1/README.md new file mode 100644 index 00000000000000..da6e3eb00f2f66 --- /dev/null +++ b/examples/contact-sensor-app/nxp/k32w/k32w1/README.md @@ -0,0 +1,414 @@ +# Matter K32W1 Contact Sensor Example Application + +Matter K32W1 Contact Sensor Example uses buttons to test changing the lock and +device states and LEDs to show the state of these changes. You can use this +example as a reference for creating your own application. + +The example is based on +[Matter](https://github.com/project-chip/connectedhomeip) and the NXP K32W1 SDK, +and a simulated contact sensor over a low-power, 802.15.4 Thread network. + +The example behaves as a Matter accessory, that is a device that can be paired +into an existing Matter network and can be controlled by this network. + +
+ +- [Matter K32W1 Contact Sensor Example Application](#matter-k32w1-contact-sensor-example-application) +- [Introduction](#introduction) + - [Bluetooth LE Advertising](#bluetooth-le-advertising) + - [Bluetooth LE Rendezvous](#bluetooth-le-rendezvous) +- [Device UI](#device-ui) +- [Building](#building) +- [Flashing](#flashing) + - [Flashing the NBU image](#flashing-the-nbu-image) + - [Flashing the host image](#flashing-the-host-image) +- [Debugging](#debugging) +- [OTA](#ota) + - [Convert srec into sb3 file](#convert-srec-into-sb3-file) + - [Convert sb3 into ota file](#convert-sb3-into-ota-file) + - [Running OTA](#running-ota) + - [Known issues](#known-issues) +- [Low power](#low-power) + + + +## Introduction + +![K32W1 EVK](../../../../platform/nxp/k32w/k32w1/doc/images/k32w1-evk.jpg) + +The K32W1 contact sensor example application provides a working demonstration of +a connected contact sensor device, built using the Matter codebase and the NXP +K32W1 SDK. The example supports remote access (e.g.: using CHIP Tool from a +mobile phone) and control of a simulated contact sensor over a low-power, +802.15.4 Thread network. It is capable of being paired into an existing Matter +network along with other Matter-enabled devices. + +The Matter device that runs the contact sensor application is controlled by the +Matter controller device over the Thread protocol. By default, the Matter device +has Thread disabled, and it should be paired over Bluetooth LE with the Matter +controller and obtain configuration from it. The actions required before +establishing full communication are described below. + +### Bluetooth LE Advertising + +In this example, to commission the device onto a Matter network, it must be +discoverable over Bluetooth LE. For security reasons, you must start Bluetooth +LE advertising manually after powering up the device by pressing Button SW2. + +### Bluetooth LE Rendezvous + +In this example, the commissioning procedure (called rendezvous) is done over +Bluetooth LE between a Matter device and the Matter controller, where the +controller has the commissioner role. + +To start the rendezvous, the controller must get the commissioning information +from the Matter device. The data payload is encoded within a QR code, or printed +to the UART console. + +### Thread Provisioning + +## Device UI + +The example application provides a simple UI that depicts the state of the +device and offers basic user control. This UI is implemented via the +general-purpose LEDs and buttons built in the K32W1 EVK board. + +**LED 2** shows the overall state of the device and its connectivity. Four +states are depicted: + +- _Short Flash On (50ms on/950ms off)_ — The device is in an + unprovisioned (unpaired) state and is waiting for a commissioning + application to connect. + +* _Rapid Even Flashing (100ms on/100ms off)_ — The device is in an + unprovisioned state and a commissioning application is connected via BLE. + +- _Short Flash Off (950ms on/50ms off)_ — The device is full + provisioned, but does not yet have full network (Thread) or service + connectivity. + +* _Solid On_ — The device is fully provisioned and has full network and + service connectivity. + +NOTE: LED2 will be disabled when CHIP_DEVICE_CONFIG_ENABLE_OTA_REQUESTOR is +enabled. On K32W1 EVK board, `PTB0` is wired to LED2 also is wired to CS (Chip +Select) External Flash Memory. OTA image is stored in external memory because of +it's size. If LED2 is enabled then it will affect External Memory CS and OTA +will not work. + +**RGB LED** shows the state of the simulated contact sensor. when the LED is +lit, the sensor is contacted, when not lit, the sensor is non-contacted. + +**Button SW2** can be used to start BLE advertising. A SHORT press of the button +will enable Bluetooth LE advertising for a predefined period of time. A LONG +Press Button SW2 initiates a factory reset. After an initial period of 3 +seconds, LED 2 and RGB LED will flash in unison to signal the pending reset. +After 6 seconds will cause the device to reset its persistent configuration and +initiate a reboot. The reset action can be cancelled by press SW2 button at any +point before the 6 second limit. + +**Button SW3** can be used to change the state of the simulated contact sensor. +The button behaves as a toggle, swapping the state every time it is pressed. + +## Building + +In order to build the Matter example, we recommend using a Linux distribution +(the demo-application was compiled on Ubuntu 20.04). + +- Download [K32W1 SDK for Matter](https://mcuxpresso.nxp.com/). Creating an + nxp.com account is required before being able to download the SDK. Once the + account is created, login and follow the steps for downloading K32W1 SDK. + The SDK Builder UI selection should be similar with the one from the image + below. + ![MCUXpresso SDK Download](../../../../platform/nxp/k32w/k32w1/doc/images/mcux-sdk-download.jpg) + +``` +user@ubuntu:~/Desktop/git/connectedhomeip$ export NXP_K32W1_SDK_ROOT=/home/user/Desktop/SDK_K32W1/ +user@ubuntu:~/Desktop/git/connectedhomeip$ source ./scripts/activate.sh +user@ubuntu:~/Desktop/git/connectedhomeip$ cd examples/contact-sensor-app/nxp/k32w/k32w1 +user@ubuntu:~/Desktop/git/connectedhomeip/examples/contact-sensor-app/nxp/k32w/k32w1$ gn gen out/debug --args="chip_with_ot_cli=0 is_debug=false chip_openthread_ftd=false chip_crypto=\"platform\"" +user@ubuntu:~/Desktop/git/connectedhomeip/examples/contact-sensor-app/nxp/k32w/k32w1$ ninja -C out/debug +``` + +In case that Openthread CLI is needed, chip_with_ot_cli build argument must be +set to 1. + +After a successful build, the `elf` and `srec` files are found in `out/debug/` - +`see the files prefixed with chip-k32w1-contact-example`. + +## Flashing + +Two images must be written to the board: one for the host (CM33) and one for the +`NBU` (CM3). + +The image needed on the host side is the one generated in `out/debug/` while the +one needed on the `NBU` side can be found in the downloaded NXP-SDK package at +path - +`middleware\wireless\ieee-802.15.4\bin\k32w1\k32w1_nbu_ble_15_4_dyn_matter_$version.sb3`. + +### Flashing the `NBU` image + +`NBU` image should be written only when a new NXP-SDK is released. + +[K32W148 board quick start guide](https://www.nxp.com/document/guide/getting-started-with-the-k32w148-development-platform:GS-K32W148EVK) +can be used for updating the `NBU/radio` core: + +- Section 2.4 – Get Software – install `SPSDK` (Secure Provisioning Command + Line Tool) +- Section 3.3 – Updating `NBU` for Wireless examples - use the corresponding + `.sb3` file found in the SDK package at path + `middleware\wireless\ieee-802.15.4\bin\k32w1\` + +### Flashing the host image + +Host image is the one found under `out/debug/`. It should be written after each +build process. + +If debugging is needed then jump directly to the [Debugging](#debugging) +section. Otherwise, if only flashing is needed then +[JLink 7.84b](https://www.segger.com/downloads/jlink/) can be used: + +- Plug K32W1 to the USB port (no need to keep the SW4 button pressed while + doing this) + +- Create a new file, `commands_script`, with the following content (change + application name accordingly): + +```bash +reset +halt +loadfile chip-k32w1-contact-example.srec +reset +go +quit +``` + +- copy the application and `commands_script` in the same folder that JLink + executable is placed. Execute: + +```bash +$ jlink -device K32W1480 -if SWD -speed 4000 -autoconnect 1 -CommanderScript commands_script +``` + +## Debugging + +One option for debugging would be to use MCUXpresso IDE. + +- Drag-and-drop the zip file containing the NXP SDK in the "Installed SDKs" + tab: + +![Installed SDKs](../../../../platform/nxp/k32w/k32w1/doc/images/installed_sdks.jpg) + +- Import any demo application from the installed SDK: + +``` +Import SDK example(s).. -> choose a demo app (demo_apps -> hello_world) -> Finish +``` + +![Import demo](../../../../platform/nxp/k32w/k32w1/doc/images/import_demo.jpg) + +- Flash the previously imported demo application on the board: + +``` +Right click on the application (from Project Explorer) -> Debug as -> JLink/CMSIS-DAP +``` + +After this step, a debug configuration specific for the K32W1 board was created. +This debug configuration will be used later on for debugging the application +resulted after ot-nxp compilation. + +- Import Matter repo in MCUXpresso IDE as Makefile Project. Use _none_ as + _Toolchain for Indexer Settings_: + +``` +File -> Import -> C/C++ -> Existing Code as Makefile Project +``` + +![New Project](../../../../platform/nxp/k32w/k32w1/doc/images/new_project.jpg) + +- Replace the path of the existing demo application with the path of the K32W1 + application: + +``` +Run -> Debug Configurations... -> C/C++ Application +``` + +![Debug K32W1](../../../../platform/nxp/k32w/k32w1/doc/images/debug_k32w1.jpg) + +## OTA + +### Convert `srec` into `sb3` file + +The OTA image files must be encrypted using Over The Air Programming Tool +([OTAP](https://www.nxp.com/design/microcontrollers-developer-resources/connectivity-tool-suite:CONNECTIVITY-TOOL-SUITE?#downloads)). +Bootloader will load the new OTA image only if it detects that the file was +encrypted with the `OTAP` correct keys. + +`.srec` file is input for Over The air Programming (`OTAP`) application +(unencrypted) and it's converted to `.sb3` format (encrypted). + +In `OTAP` application + +- select OTA protocol => `OTAP` Matter +- Browse File +- follow default options (KW45/K32W148, Preserve NVM) +- image information: will update "Application Core (MCU)" - this will generate + the image only for the CM33 core +- keep other settings at default values + +### Convert sb3 into ota file + +In order to build an OTA image, use NXP wrapper over the standard tool +`src/app/ota_image_tool.py`: + +- `scripts/tools/nxp/factory_data_generator/ota_image_tool.py` The tool can be + used to generate an OTA image with the following format: + `| OTA image header | TLV1 | TLV2 | ... | TLVn |` where each TLV is in the + form `|tag|length|value|` + +Note that "standard" TLV format is used. Matter TLV format is only used for +factory data TLV value. + +Please see more in the +[OTA image tool guide](../../../../../scripts/tools/nxp/ota/README.md). + +Here is an example that generates an OTA image with application update TLV from +a sb3 file: + +``` +./scripts/tools/nxp/ota/ota_image_tool.py create -v 0xDEAD -p 0xBEEF -vn 43033 -vs "1.0" -da sha256 --app-input-file ~/binaries/chip-k32w1-43033.sb3 ~/binaries/chip-k32w1-43033.ota + +``` + +A note regarding OTA image header version (`-vn` option). An application binary +has its own software version (given by +`CHIP_DEVICE_CONFIG_DEVICE_SOFTWARE_VERSION`, which can be overwritten). For +having a correct OTA process, the OTA header version should be the same as the +binary embedded software version. A user can set a custom software version in +the gn build args by setting `chip_software_version` to the wanted version. + +### Running OTA + +The OTA topology used for OTA testing is illustrated in the figure below. +Topology is similar with the one used for Matter Test Events. + +![OTA_TOPOLOGY](../../../../platform/nxp/k32w/k32w1/doc/images/ota_topology.JPG) + +The concept for OTA is the next one: + +- there is an OTA Provider Application that holds the OTA image. In our case, + this is a Linux application running on an Ubuntu based-system; +- the OTA Requestor functionality is embedded inside the Contact Sensor + Application. It will be used for requesting OTA blocks from the OTA + Provider; +- the controller (a linux application called chip-tool) will be used for + commissioning both the device and the OTA Provider App. The device will be + commissioned using the standard Matter flow (BLE + IEEE 802.15.4) while the + OTA Provider Application will be commissioned using the _onnetwork_ option + of chip-tool; +- during commissioning, each device is assigned a node id by the chip-tool + (can be specified manually by the user). Using the node id of the device and + of the contact sensor application, chip-tool triggers the OTA transfer by + invoking the _announce-ota-provider_ command - basically, the OTA Requestor + is informed of the node id of the OTA Provider Application. + +_Computer #1_ can be any system running an Ubuntu distribution. We recommand +using CSA official instructions from +[here](https://groups.csa-iot.org/wg/matter-csg/document/28566), where RPi 4 are +proposed. Also, CSA official instructions document point to the OS/Docker images +that should be used on the RPis. For compatibility reasons, we recommand +compiling chip-tool and OTA Provider applications with the same commit id that +was used for compiling the Contact Sensor Application. Also, please note that +there is a single controller (chip-tool) running on Computer #1 which is used +for commissioning both the device and the OTA Provider Application. If needed, +[these instructions](https://itsfoss.com/connect-wifi-terminal-ubuntu/) could be +used for connecting the RPis to WiFi. + +Build the Linux OTA provider application: + +``` +user@computer1:~/connectedhomeip$ : ./scripts/examples/gn_build_example.sh examples/ota-provider-app/linux out/ota-provider-app chip_config_network_layer_ble=false +``` + +Build Linux chip-tool: + +``` +user@computer1:~/connectedhomeip$ : ./scripts/examples/gn_build_example.sh examples/chip-tool out/chip-tool-app +``` + +Start the OTA Provider Application: + +``` +user@computer1:~/connectedhomeip$ : rm -rf /tmp/chip_* +user@computer1:~/connectedhomeip$ : ./out/ota-provider-app/chip-ota-provider-app -f chip-k32w1-43033.ota +``` + +Provision the OTA provider application and assign node id _1_. Also, grant ACL +entries to allow OTA requestors: + +``` +user@computer1:~/connectedhomeip$ : rm -rf /tmp/chip_* +user@computer1:~/connectedhomeip$ : ./out/chip-tool-app/chip-tool pairing onnetwork 1 20202021 +user@computer1:~/connectedhomeip$ : ./out/chip-tool-app/chip-tool accesscontrol write acl '[{"fabricIndex": 1, "privilege": 5, "authMode": 2, "subjects": [112233], "targets": null}, {"fabricIndex": 1, "privilege": 3, "authMode": 2, "subjects": null, "targets": null}]' 1 0 +``` + +Provision the device and assign node id _2_: + +``` +user@computer1:~/connectedhomeip$ : ./out/chip-tool-app/chip-tool pairing ble-thread 2 hex: 20202021 3840 +``` + +Start the OTA process: + +``` +user@computer1:~/connectedhomeip$ : ./out/chip-tool-app/chip-tool otasoftwareupdaterequestor announce-ota-provider 1 0 0 0 2 0 +``` + +## Low power + +The example also offers the possibility to run in low power mode. This means +that the board will go in deep sleep most of the time and the power consumption +will be very low. + +In order to build with low power support, the `chip_with_low_power=1` must be +provided to the build system. In this case, please note that the GN build +arguments `chip_openthread_ftd` and `chip_with_ot_cli` must be set to `false/0` +and `chip_logging` must be set to `false` to disable logging. + +In order to maintain a low power consumption, the LEDs showing the state of the +contact sensor and the internal state are disabled. Console logs can be used +instead. Also, please note that once the board is flashed with MCUXpresso the +debugger disconnects because the board enters low power. + +### Known issues + +- SRP cache on the openthread border router needs to flushed each time a new + commissioning process is attempted. For this, factory reset the device, then + execute _ot-ctl server disable_ followed by _ot-ctl server enable_. After + this step, the commissioning process of the device can start; +- Due to some MDNS issues, the commissioning of the OTA Provider Application + may fail. Please make sure that the SRP cache is disabled (_ot-ctl srp + server disable_) on the openthread border router while commissioning the OTA + Provider Application; +- No other Docker image should be running (e.g.: Docker image needed by Test + Harness) except the OTBR one. A docker image can be killed using the + command: + +``` +user@computer1:~/connectedhomeip$ : sudo docker kill $container_id +``` + +- In order to avoid MDNS issues, only one interface should be active at one + time. E.g.: if WiFi is used then disable the Ethernet interface and also + disable multicast on that interface: + +``` +user@computer1:~/connectedhomeip$ sudo ip link set dev eth0 down +user@computer1:~/connectedhomeip$ sudo ifconfig eth0 -multicast +``` + +- If OTBR Docker image is used, then the "-B" parameter should point to the + interface used for the backbone. + +- If Wi-Fi is used on a RPI4, then a 5Ghz network should be selected. + Otherwise, issues related to BLE-WiFi combo may appear. diff --git a/examples/contact-sensor-app/nxp/k32w/k32w1/args.gni b/examples/contact-sensor-app/nxp/k32w/k32w1/args.gni new file mode 100644 index 00000000000000..c0497aa27421d2 --- /dev/null +++ b/examples/contact-sensor-app/nxp/k32w/k32w1/args.gni @@ -0,0 +1,25 @@ +# Copyright (c) 2020-2023 Project CHIP Authors +# Copyright (c) 2023 NXP +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import("//build_overrides/chip.gni") +import("${chip_root}/config/standalone/args.gni") +import("${chip_root}/examples/platform/nxp/k32w/k32w1/args.gni") + +# SDK target. This is overridden to add our SDK app_config.h & defines. +k32w1_sdk_target = get_label_info(":sdk", "label_no_toolchain") + +chip_enable_ota_requestor = true +chip_stack_lock_tracking = "fatal" +chip_enable_ble = true diff --git a/examples/contact-sensor-app/nxp/k32w/k32w1/build_overrides b/examples/contact-sensor-app/nxp/k32w/k32w1/build_overrides new file mode 120000 index 00000000000000..ad07557834803a --- /dev/null +++ b/examples/contact-sensor-app/nxp/k32w/k32w1/build_overrides @@ -0,0 +1 @@ +../../../../build_overrides/ \ No newline at end of file diff --git a/examples/contact-sensor-app/nxp/k32w/k32w1/include/CHIPProjectConfig.h b/examples/contact-sensor-app/nxp/k32w/k32w1/include/CHIPProjectConfig.h new file mode 100644 index 00000000000000..af3a7067c99e1a --- /dev/null +++ b/examples/contact-sensor-app/nxp/k32w/k32w1/include/CHIPProjectConfig.h @@ -0,0 +1,178 @@ +/* + * Copyright (c) 2020-2023 Project CHIP Authors + * Copyright (c) 2020 Google LLC. + * Copyright (c) 2023 NXP + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file + * Example project configuration file for CHIP. + * + * This is a place to put application or project-specific overrides + * to the default configuration values for general CHIP features. + * + */ + +#pragma once + +/** + * CHIP_DEVICE_CONFIG_DEVICE_VENDOR_ID + * + * 0xFFF1: Test vendor. + */ +#define CHIP_DEVICE_CONFIG_DEVICE_VENDOR_ID 0xFFF1 + +/** + * CHIP_DEVICE_CONFIG_DEVICE_PRODUCT_ID + * + */ +#define CHIP_DEVICE_CONFIG_DEVICE_PRODUCT_ID 0x8006 + +// Use a default setup PIN code if one hasn't been provisioned in flash. +#define CHIP_DEVICE_CONFIG_USE_TEST_SETUP_PIN_CODE 20202021 +#define CHIP_DEVICE_CONFIG_USE_TEST_SETUP_DISCRIMINATOR 0xF00 + +// Use a default pairing code if one hasn't been provisioned in flash. +#define CHIP_DEVICE_CONFIG_USE_TEST_PAIRING_CODE "CHIPUS" + +/** + * CHIP_DEVICE_CONFIG_TEST_SERIAL_NUMBER + * + * Enables the use of a hard-coded default serial number if none + * is found in CHIP NV storage. + */ +#define CHIP_DEVICE_CONFIG_TEST_SERIAL_NUMBER "TEST_SN" + +/** + * CHIP_DEVICE_CONFIG_DEVICE_VENDOR_ID + * + * 0xFFF1: Test vendor. + */ +#define CHIP_DEVICE_CONFIG_DEVICE_VENDOR_ID 0xFFF1 + +/** + * CHIP_DEVICE_CONFIG_DEVICE_PRODUCT_ID + * + */ +#define CHIP_DEVICE_CONFIG_DEVICE_PRODUCT_ID 0x8006 + +/** + * CHIP_DEVICE_CONFIG_DEVICE_HARDWARE_VERSION + * + * The hardware version number assigned to device or product by the device vendor. This + * number is scoped to the device product id, and typically corresponds to a revision of the + * physical device, a change to its packaging, and/or a change to its marketing presentation. + * This value is generally *not* incremented for device software versions. + */ +#define CHIP_DEVICE_CONFIG_DEVICE_HARDWARE_VERSION 100 + +#ifndef CHIP_DEVICE_CONFIG_DEFAULT_DEVICE_HARDWARE_VERSION_STRING +#define CHIP_DEVICE_CONFIG_DEFAULT_DEVICE_HARDWARE_VERSION_STRING "v0.1.0" +#endif + +/** + * CHIP_DEVICE_CONFIG_DEVICE_SOFTWARE_VERSION_STRING + * + * A string identifying the software version running on the device. + * CHIP currently expects the software version to be in the format + * {MAJOR_VERSION}.0d{MINOR_VERSION} + */ +#ifndef CHIP_DEVICE_CONFIG_DEVICE_SOFTWARE_VERSION_STRING +#define CHIP_DEVICE_CONFIG_DEVICE_SOFTWARE_VERSION_STRING "03-2022-te8" +#endif + +#ifndef CHIP_DEVICE_CONFIG_DEVICE_SOFTWARE_VERSION +#define CHIP_DEVICE_CONFIG_DEVICE_SOFTWARE_VERSION 42020 +#endif + +#ifndef CHIP_DEVICE_CONFIG_DEVICE_VENDOR_NAME +#define CHIP_DEVICE_CONFIG_DEVICE_VENDOR_NAME "NXP Semiconductors" +#endif + +#ifndef CHIP_DEVICE_CONFIG_DEVICE_PRODUCT_NAME +#define CHIP_DEVICE_CONFIG_DEVICE_PRODUCT_NAME "NXP Demo App" +#endif + +/** + * CHIP_DEVICE_CONFIG_BLE_FAST_ADVERTISING_TIMEOUT + * + * The amount of time in miliseconds after which BLE should change his advertisements + * from fast interval to slow interval. + * + * 30000 (30 secondes). + */ +#define CHIP_DEVICE_CONFIG_BLE_FAST_ADVERTISING_TIMEOUT (30 * 1000) + +/** + * CHIP_DEVICE_CONFIG_BLE_ADVERTISING_TIMEOUT + * + * The amount of time in miliseconds after which BLE advertisement should be disabled, counting + * from the moment of slow advertisement commencement. + * + * Defaults to 9000000 (15 minutes). + */ +#define CHIP_DEVICE_CONFIG_BLE_ADVERTISING_TIMEOUT (15 * 60 * 1000) + +/** + * CONFIG_CHIP_NFC_COMMISSIONING, CHIP_DEVICE_CONFIG_ENABLE_NFC + * + * NFC commissioning is not supported on K32W1 + */ +#define CONFIG_CHIP_NFC_COMMISSIONING 0 +#define CHIP_DEVICE_CONFIG_ENABLE_NFC 0 + +/** + * @def CHIP_CONFIG_MAX_FABRICS + * + * @brief + * Maximum number of fabrics the device can participate in. Each fabric can + * provision the device with its unique operational credentials and manage + * its own access control lists. + */ +#define CHIP_CONFIG_MAX_FABRICS 5 // 5 is the minimum number of supported fabrics + +#define CHIP_DEVICE_CONFIG_ENABLE_SED 1 +#define CHIP_DEVICE_CONFIG_SED_IDLE_INTERVAL 1000_ms32 +#define CHIP_DEVICE_CONFIG_SED_ACTIVE_INTERVAL 100_ms32 + +/** + * @def CHIP_IM_MAX_NUM_COMMAND_HANDLER + * + * @brief Defines the maximum number of CommandHandler, limits the number of active commands transactions on server. + */ +#define CHIP_IM_MAX_NUM_COMMAND_HANDLER 2 + +/** + * @def CHIP_IM_MAX_NUM_WRITE_HANDLER + * + * @brief Defines the maximum number of WriteHandler, limits the number of active write transactions on server. + */ +#define CHIP_IM_MAX_NUM_WRITE_HANDLER 2 + +/** + * CHIP_CONFIG_EVENT_LOGGING_DEFAULT_IMPORTANCE + * + * For a development build, set the default importance of events to be logged as Debug. + * Since debug is the lowest importance level, this means all standard, critical, info and + * debug importance level vi events get logged. + */ +#if BUILD_RELEASE +#define CHIP_CONFIG_EVENT_LOGGING_DEFAULT_IMPORTANCE chip::Profiles::DataManagement::Production +#else +#define CHIP_CONFIG_EVENT_LOGGING_DEFAULT_IMPORTANCE chip::Profiles::DataManagement::Debug +#endif // BUILD_RELEASE + +#define CHIP_DEVICE_CONFIG_ENABLE_EXTENDED_DISCOVERY 1 diff --git a/examples/contact-sensor-app/nxp/k32w/k32w1/include/FreeRTOSConfig.h b/examples/contact-sensor-app/nxp/k32w/k32w1/include/FreeRTOSConfig.h new file mode 100644 index 00000000000000..a4e204700672a3 --- /dev/null +++ b/examples/contact-sensor-app/nxp/k32w/k32w1/include/FreeRTOSConfig.h @@ -0,0 +1,207 @@ +/* + * FreeRTOS Kernel V10.2.0 + * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright (c) 2023 NXP + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * http://www.FreeRTOS.org + * http://aws.amazon.com/freertos + * + * 1 tab == 4 spaces! + */ + +#pragma once + +/* Ensure stdint is only used by the compiler, and not the assembler. */ +#if defined(__ICCARM__) || defined(__ARMCC_VERSION) || defined(__GNUC__) +#include +extern uint32_t SystemCoreClock; +#endif + +/*----------------------------------------------------------- + * Application specific definitions. + * + * These definitions should be adjusted for your particular hardware and + * application requirements. + * + * THESE PARAMETERS ARE DESCRIBED WITHIN THE 'CONFIGURATION' SECTION OF THE + * FreeRTOS API DOCUMENTATION AVAILABLE ON THE FreeRTOS.org WEB SITE. + * + * See http://www.freertos.org/a00110.html. + *----------------------------------------------------------*/ + +#define configUSE_PREEMPTION 1 + +#if defined(chip_with_low_power) && (chip_with_low_power == 1) +#define configUSE_TICKLESS_IDLE 1 +#else +#define configUSE_TICKLESS_IDLE 0 +#endif + +#define configCPU_CLOCK_HZ (SystemCoreClock) +#define configTICK_RATE_HZ ((TickType_t) 100) +#define configMAX_PRIORITIES (8) + +#if defined(configUSE_TICKLESS_IDLE) && (configUSE_TICKLESS_IDLE == 1) +#define configMINIMAL_STACK_SIZE ((unsigned short) 610) +#else +#define configMINIMAL_STACK_SIZE ((unsigned short) 450) +#endif + +#define configMAX_TASK_NAME_LEN 20 +#define configUSE_16_BIT_TICKS 0 +#define configIDLE_SHOULD_YIELD 1 +#define configUSE_TASK_NOTIFICATIONS 1 +#define configUSE_MUTEXES 1 +#define configUSE_RECURSIVE_MUTEXES 1 +#define configUSE_COUNTING_SEMAPHORES 1 +#define configUSE_ALTERNATIVE_API 0 /* Deprecated! */ +#define configQUEUE_REGISTRY_SIZE 8 +#define configUSE_QUEUE_SETS 0 +/* make sure that Thread task can interrupt lengthy Matter + * processing in case priority inversion occurs + */ +#define configUSE_TIME_SLICING 1 +#define configUSE_NEWLIB_REENTRANT 0 +#define configENABLE_BACKWARD_COMPATIBILITY 1 +#define configNUM_THREAD_LOCAL_STORAGE_POINTERS 5 + +/* Tasks.c additions (e.g. Thread Aware Debug capability) */ +#define configINCLUDE_FREERTOS_TASK_C_ADDITIONS_H 1 + +/* Used memory allocation (heap_x.c) */ +#define configFRTOS_MEMORY_SCHEME 4 + +/* Memory allocation related definitions. */ +#define configSUPPORT_STATIC_ALLOCATION 0 +#define configSUPPORT_DYNAMIC_ALLOCATION 1 +#define configTOTAL_HEAP_SIZE ((size_t) (gTotalHeapSize_c)) +#define configAPPLICATION_ALLOCATED_HEAP 1 + +/* Hook function related definitions. */ +#ifndef configUSE_IDLE_HOOK +#define configUSE_IDLE_HOOK 1 +#endif +#define configUSE_TICK_HOOK 0 +#define configCHECK_FOR_STACK_OVERFLOW 0 +#ifndef configUSE_MALLOC_FAILED_HOOK +#define configUSE_MALLOC_FAILED_HOOK 0 +#endif +#define configUSE_DAEMON_TASK_STARTUP_HOOK 0 + +/* Run time and task stats gathering related definitions. */ +#define configGENERATE_RUN_TIME_STATS 0 +#define configUSE_TRACE_FACILITY 1 +#define configUSE_STATS_FORMATTING_FUNCTIONS 0 + +/* Task aware debugging. */ +#define configRECORD_STACK_HIGH_ADDRESS 1 + +/* Co-routine related definitions. */ +#define configUSE_CO_ROUTINES 0 +#define configMAX_CO_ROUTINE_PRIORITIES 2 + +/* Software timer related definitions. */ +#define configUSE_TIMERS 1 +#define configTIMER_TASK_PRIORITY (configMAX_PRIORITIES - 1) +#define configTIMER_QUEUE_LENGTH 10 +#define configTIMER_TASK_STACK_DEPTH (360) + +/* Define to trap errors during development. */ +#if defined gLoggingActive_d && (gLoggingActive_d != 0) +#include "dbg_logging.h" +#define configASSERT(x) \ + if ((x) == 0) \ + { \ + taskDISABLE_INTERRUPTS(); \ + DbgLogDump(1); \ + } +#else +#define configASSERT(x) \ + if ((x) == 0) \ + { \ + taskDISABLE_INTERRUPTS(); \ + for (;;) \ + ; \ + } +#endif + +/* Optional functions - most linkers will remove unused functions anyway. */ +#define INCLUDE_vTaskPrioritySet 1 +#define INCLUDE_uxTaskPriorityGet 1 +#define INCLUDE_vTaskDelete 1 +#define INCLUDE_vTaskSuspend 1 +#define INCLUDE_xResumeFromISR 1 +#define INCLUDE_vTaskDelayUntil 1 +#define INCLUDE_vTaskDelay 1 +#define INCLUDE_xTaskGetSchedulerState 1 +#define INCLUDE_xTaskGetCurrentTaskHandle 1 +#define INCLUDE_uxTaskGetStackHighWaterMark 1 +#define INCLUDE_xTaskGetIdleTaskHandle 0 +#define INCLUDE_eTaskGetState 0 +#define INCLUDE_xEventGroupSetBitFromISR 1 +#define INCLUDE_xTimerPendFunctionCall 1 +#define INCLUDE_xTaskAbortDelay 0 +#define INCLUDE_xTaskGetHandle 0 +#define INCLUDE_xTaskResumeFromISR 1 +#define INCLUDE_xQueueGetMutexHolder 1 + +/* Interrupt nesting behaviour configuration. Cortex-M specific. */ +#ifdef __NVIC_PRIO_BITS +/* __BVIC_PRIO_BITS will be specified when CMSIS is being used. */ +#define configPRIO_BITS __NVIC_PRIO_BITS +#else +#define configPRIO_BITS 3 +#endif + +/* The lowest interrupt priority that can be used in a call to a "set priority" +function. */ +#define configLIBRARY_LOWEST_INTERRUPT_PRIORITY 0x7 + +/* The highest interrupt priority that can be used by any interrupt service +routine that makes calls to interrupt safe FreeRTOS API functions. DO NOT CALL +INTERRUPT SAFE FREERTOS API FUNCTIONS FROM ANY INTERRUPT THAT HAS A HIGHER +PRIORITY THAN THIS! (higher priorities are lower numeric values. */ +#define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 1 + +/* Interrupt priorities used by the kernel port layer itself. These are generic +to all Cortex-M ports, and do not rely on any particular library functions. */ +#define configKERNEL_INTERRUPT_PRIORITY (configLIBRARY_LOWEST_INTERRUPT_PRIORITY << (8 - configPRIO_BITS)) +/* !!!! configMAX_SYSCALL_INTERRUPT_PRIORITY must not be set to zero !!!! +See http://www.FreeRTOS.org/RTOS-Cortex-M3-M4.html. */ +#define configMAX_SYSCALL_INTERRUPT_PRIORITY (configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY << (8 - configPRIO_BITS)) + +#ifndef configENABLE_FPU +#define configENABLE_FPU 0 +#endif +#ifndef configENABLE_MPU +#define configENABLE_MPU 0 +#endif +#ifndef configENABLE_TRUSTZONE +#define configENABLE_TRUSTZONE 0 +#endif +#ifndef configRUN_FREERTOS_SECURE_ONLY +#define configRUN_FREERTOS_SECURE_ONLY 1 +#endif + +/* Definitions that map the FreeRTOS port interrupt handlers to their CMSIS +standard names. */ +#define vPortSVCHandler SVC_Handler +#define xPortPendSVHandler PendSV_Handler +#define xPortSysTickHandler SysTick_Handler diff --git a/examples/contact-sensor-app/nxp/k32w/k32w1/main/AppTask.cpp b/examples/contact-sensor-app/nxp/k32w/k32w1/main/AppTask.cpp new file mode 100644 index 00000000000000..bce7374d7ea7ef --- /dev/null +++ b/examples/contact-sensor-app/nxp/k32w/k32w1/main/AppTask.cpp @@ -0,0 +1,812 @@ +/* + * + * Copyright (c) 2022-2023 Project CHIP Authors + * Copyright (c) 2022 Google LLC. + * Copyright (c) 2023 NXP + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "AppTask.h" +#include "AppEvent.h" +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +/* OTA related includes */ +#if CHIP_DEVICE_CONFIG_ENABLE_OTA_REQUESTOR +#include "OtaSupport.h" +#include +#include +#include +#include +#include +#endif + +#include "K32W1PersistentStorageOpKeystore.h" + +#include "LEDWidget.h" +#include "app.h" +#include "app_config.h" +#include "fsl_component_button.h" +#include "fwk_platform.h" + +#define FACTORY_RESET_TRIGGER_TIMEOUT 6000 +#define FACTORY_RESET_CANCEL_WINDOW_TIMEOUT 3000 +#define APP_TASK_PRIORITY 2 +#define APP_EVENT_QUEUE_SIZE 10 + +TimerHandle_t sFunctionTimer; // FreeRTOS app sw timer. + +static QueueHandle_t sAppEventQueue; + +#if !defined(chip_with_low_power) || (chip_with_low_power == 0) +/* + * The status LED and the external flash CS pin are wired together. + * The OTA image writing may fail if used together. + */ +#ifndef CHIP_DEVICE_CONFIG_ENABLE_OTA_REQUESTOR +static LEDWidget sStatusLED; +#endif +static LEDWidget sContactSensorLED; +#endif + +static bool sIsThreadProvisioned = false; +static bool sHaveBLEConnections = false; + +static uint32_t eventMask = 0; + +#if CHIP_DEVICE_CONFIG_THREAD_ENABLE_CLI +extern "C" void otPlatUartProcess(void); +#endif + +extern "C" void PWR_DisallowDeviceToSleep(void); +extern "C" void PWR_AllowDeviceToSleep(void); + +using namespace ::chip::Credentials; +using namespace ::chip::DeviceLayer; +using namespace chip; +using namespace chip::app; + +AppTask AppTask::sAppTask; + +static Identify gIdentify = { chip::EndpointId{ 1 }, AppTask::OnIdentifyStart, AppTask::OnIdentifyStop, + Clusters::Identify::IdentifyTypeEnum::kVisibleIndicator }; + +/* OTA related variables */ +#if CHIP_DEVICE_CONFIG_ENABLE_OTA_REQUESTOR +static DefaultOTARequestor gRequestorCore __attribute__((section(".data"))); +static DefaultOTARequestorStorage gRequestorStorage __attribute__((section(".data"))); +static DeviceLayer::DefaultOTARequestorDriver gRequestorUser __attribute__((section(".data"))); +static BDXDownloader gDownloader __attribute__((section(".data"))); + +constexpr uint16_t requestedOtaBlockSize = 1024; +#endif + +CHIP_ERROR AppTask::StartAppTask() +{ + CHIP_ERROR err = CHIP_NO_ERROR; + + sAppEventQueue = xQueueCreate(APP_EVENT_QUEUE_SIZE, sizeof(AppEvent)); + if (sAppEventQueue == NULL) + { + err = APP_ERROR_EVENT_QUEUE_FAILED; + K32W_LOG("Failed to allocate app event queue"); + assert(err == CHIP_NO_ERROR); + } + + return err; +} + +CHIP_ERROR AppTask::Init() +{ + CHIP_ERROR err = CHIP_NO_ERROR; + + if (ContactSensorMgr().Init() != 0) + { + K32W_LOG("ContactSensorMgr().Init() failed"); + assert(status == 0); + } + + PlatformMgr().AddEventHandler(MatterEventHandler, 0); + + // Init ZCL Data Model and start server + PlatformMgr().ScheduleWork(InitServer, 0); + + // Initialize device attestation config + SetDeviceAttestationCredentialsProvider(Examples::GetExampleDACProvider()); + + // QR code will be used with CHIP Tool + AppTask::PrintOnboardingInfo(); + +#if !defined(chip_with_low_power) || (chip_with_low_power == 0) + /* start with all LEDS turnedd off */ +#ifndef CHIP_DEVICE_CONFIG_ENABLE_OTA_REQUESTOR + sStatusLED.Init(SYSTEM_STATE_LED, false); +#endif + + sContactSensorLED.Init(CONTACT_SENSOR_STATE_LED, false); + sContactSensorLED.Set(ContactSensorMgr().IsContactClosed()); +#endif + + UpdateDeviceState(); + + /* intialize the Keyboard and button press callback */ + BUTTON_InstallCallback((button_handle_t) g_buttonHandle[0], KBD_Callback, (void *) BLE_BUTTON); + BUTTON_InstallCallback((button_handle_t) g_buttonHandle[1], KBD_Callback, (void *) CONTACT_SENSOR_BUTTON); + + // Create FreeRTOS sw timer for Function Selection. + sFunctionTimer = xTimerCreate("FnTmr", // Just a text name, not used by the RTOS kernel + 1, // == default timer period (mS) + false, // no timer reload (==one-shot) + (void *) this, // init timer id = app task obj context + TimerEventHandler // timer callback handler + ); + if (sFunctionTimer == NULL) + { + err = APP_ERROR_CREATE_TIMER_FAILED; + K32W_LOG("app_timer_create() failed"); + assert(err == CHIP_NO_ERROR); + } + + ContactSensorMgr().SetCallback(OnStateChanged); + + // Print the current software version + char currentSoftwareVer[ConfigurationManager::kMaxSoftwareVersionStringLength + 1] = { 0 }; + err = ConfigurationMgr().GetSoftwareVersionString(currentSoftwareVer, sizeof(currentSoftwareVer)); + if (err != CHIP_NO_ERROR) + { + K32W_LOG("Get version error"); + assert(err == CHIP_NO_ERROR); + } + + uint32_t currentVersion; + err = ConfigurationMgr().GetSoftwareVersion(currentVersion); + + K32W_LOG("Current Software Version: %s, %d", currentSoftwareVer, currentVersion); + + return err; +} + +void LockOpenThreadTask(void) +{ + PWR_DisallowDeviceToSleep(); + chip::DeviceLayer::ThreadStackMgr().LockThreadStack(); +} + +void UnlockOpenThreadTask(void) +{ + chip::DeviceLayer::ThreadStackMgr().UnlockThreadStack(); + PWR_AllowDeviceToSleep(); +} + +void AppTask::InitServer(intptr_t arg) +{ + static chip::CommonCaseDeviceServerInitParams initParams; + (void) initParams.InitializeStaticResourcesBeforeServerInit(); + +#if CHIP_CRYPTO_PLATFORM + static chip::K32W1PersistentStorageOpKeystore sK32W1PersistentStorageOpKeystore; + VerifyOrDie((sK32W1PersistentStorageOpKeystore.Init(initParams.persistentStorageDelegate)) == CHIP_NO_ERROR); + initParams.operationalKeystore = &sK32W1PersistentStorageOpKeystore; +#endif + + // Init ZCL Data Model and start server + chip::Inet::EndPointStateOpenThread::OpenThreadEndpointInitParam nativeParams; + nativeParams.lockCb = LockOpenThreadTask; + nativeParams.unlockCb = UnlockOpenThreadTask; + nativeParams.openThreadInstancePtr = chip::DeviceLayer::ThreadStackMgrImpl().OTInstance(); + initParams.endpointNativeParams = static_cast(&nativeParams); + VerifyOrDie((chip::Server::GetInstance().Init(initParams)) == CHIP_NO_ERROR); +} + +void AppTask::PrintOnboardingInfo() +{ + chip::PayloadContents payload; + CHIP_ERROR err = GetPayloadContents(payload, chip::RendezvousInformationFlags(chip::RendezvousInformationFlag::kBLE)); + if (err != CHIP_NO_ERROR) + { + ChipLogError(AppServer, "GetPayloadContents() failed: %" CHIP_ERROR_FORMAT, err.Format()); + } + payload.commissioningFlow = chip::CommissioningFlow::kUserActionRequired; + PrintOnboardingCodes(payload); +} + +#if CHIP_DEVICE_CONFIG_ENABLE_OTA_REQUESTOR +void AppTask::InitOTA(intptr_t arg) +{ + // Initialize and interconnect the Requestor and Image Processor objects -- START + SetRequestorInstance(&gRequestorCore); + + gRequestorStorage.Init(chip::Server::GetInstance().GetPersistentStorage()); + gRequestorCore.Init(chip::Server::GetInstance(), gRequestorStorage, gRequestorUser, gDownloader); + gRequestorUser.SetMaxDownloadBlockSize(requestedOtaBlockSize); + auto & imageProcessor = OTAImageProcessorImpl::GetDefaultInstance(); + gRequestorUser.Init(&gRequestorCore, &imageProcessor); + CHIP_ERROR err = imageProcessor.Init(&gDownloader); + if (err != CHIP_NO_ERROR) + { + K32W_LOG("Image processor init failed"); + assert(err == CHIP_NO_ERROR); + } + + // Connect the gDownloader and Image Processor objects + gDownloader.SetImageProcessorDelegate(&imageProcessor); + // Initialize and interconnect the Requestor and Image Processor objects -- END +} +#endif + +void AppTask::AppTaskMain(void * pvParameter) +{ + AppEvent event; + + CHIP_ERROR err = sAppTask.Init(); + if (err != CHIP_NO_ERROR) + { + K32W_LOG("AppTask.Init() failed"); + assert(err == CHIP_NO_ERROR); + } + + while (true) + { + TickType_t xTicksToWait = pdMS_TO_TICKS(10); + +#if defined(chip_with_low_power) && (chip_with_low_power == 1) + xTicksToWait = portMAX_DELAY; +#endif + + BaseType_t eventReceived = xQueueReceive(sAppEventQueue, &event, xTicksToWait); + while (eventReceived == pdTRUE) + { + sAppTask.DispatchEvent(&event); + eventReceived = xQueueReceive(sAppEventQueue, &event, 0); + } + + // Collect connectivity and configuration state from the CHIP stack. Because the + // CHIP event loop is being run in a separate task, the stack must be locked + // while these values are queried. However we use a non-blocking lock request + // (TryLockChipStack()) to avoid blocking other UI activities when the CHIP + // task is busy (e.g. with a long crypto operation). + if (PlatformMgr().TryLockChipStack()) + { +#if CHIP_DEVICE_CONFIG_THREAD_ENABLE_CLI + otPlatUartProcess(); +#endif + + sHaveBLEConnections = (ConnectivityMgr().NumBLEConnections() != 0); + PlatformMgr().UnlockChipStack(); + } + + // Update the status LED if factory reset or identify process have not been initiated. + // + // If system has "full connectivity", keep the LED On constantly. + // + // If thread and service provisioned, but not attached to the thread network yet OR no + // connectivity to the service OR subscriptions are not fully established + // THEN blink the LED Off for a short period of time. + // + // If the system has ble connection(s) uptill the stage above, THEN blink the LEDs at an even + // rate of 100ms. + // + // Otherwise, blink the LED ON for a very short time. + +#if !defined(chip_with_low_power) || (chip_with_low_power == 0) +#ifndef CHIP_DEVICE_CONFIG_ENABLE_OTA_REQUESTOR + if (sAppTask.mFunction != Function::kFactoryReset && sAppTask.mFunction != Function::kIdentify) + { + if (sIsThreadProvisioned) + { + sStatusLED.Blink(950, 50); + } + else if (sHaveBLEConnections) + { + sStatusLED.Blink(100, 100); + } + else + { + sStatusLED.Blink(50, 950); + } + } + + sStatusLED.Animate(); +#endif + + sContactSensorLED.Animate(); +#endif + } +} + +void AppTask::ButtonEventHandler(uint8_t pin_no, uint8_t button_action) +{ + if ((pin_no != RESET_BUTTON) && (pin_no != CONTACT_SENSOR_BUTTON) && (pin_no != OTA_BUTTON) && (pin_no != BLE_BUTTON)) + { + return; + } + + AppEvent button_event; + button_event.Type = AppEvent::kButton; + button_event.ButtonEvent.PinNo = pin_no; + button_event.ButtonEvent.Action = button_action; + + if (pin_no == RESET_BUTTON) + { + button_event.Handler = ResetActionEventHandler; + } + else if (pin_no == CONTACT_SENSOR_BUTTON) + { + button_event.Handler = ContactActionEventHandler; + } + else if (pin_no == OTA_BUTTON) + { + // Starting OTA by button functionality is not used. + // button_event.Handler = OTAHandler; + } + else if (pin_no == BLE_BUTTON) + { + button_event.Handler = BleHandler; + + if (button_action == RESET_BUTTON_PUSH) + { + button_event.Handler = ResetActionEventHandler; + } + } + + sAppTask.PostEvent(&button_event); +} + +button_status_t AppTask::KBD_Callback(void * buttonHandle, button_callback_message_t * message, void * callbackParam) +{ + uint32_t pinNb = (uint32_t) callbackParam; + switch (message->event) + { + case kBUTTON_EventOneClick: + case kBUTTON_EventShortPress: + switch (pinNb) + { + case BLE_BUTTON: + K32W_LOG("pb1 short press"); + if (sAppTask.mResetTimerActive) + { + ButtonEventHandler(BLE_BUTTON, RESET_BUTTON_PUSH); + } + else + { + ButtonEventHandler(BLE_BUTTON, BLE_BUTTON_PUSH); + } + break; + + case CONTACT_SENSOR_BUTTON: + K32W_LOG("pb2 short press"); + ButtonEventHandler(CONTACT_SENSOR_BUTTON, CONTACT_SENSOR_BUTTON_PUSH); + break; + } + break; + + case kBUTTON_EventLongPress: + switch (pinNb) + { + case BLE_BUTTON: + K32W_LOG("pb1 long press"); + ButtonEventHandler(BLE_BUTTON, RESET_BUTTON_PUSH); + break; + + case CONTACT_SENSOR_BUTTON: + K32W_LOG("pb2 long press"); + ButtonEventHandler(OTA_BUTTON, OTA_BUTTON_PUSH); + break; + } + break; + + default: + /* No action required */ + break; + } + return kStatus_BUTTON_Success; +} + +void AppTask::TimerEventHandler(TimerHandle_t xTimer) +{ + AppEvent event; + event.Type = AppEvent::kTimer; + event.TimerEvent.Context = (void *) xTimer; + event.Handler = FunctionTimerEventHandler; + sAppTask.PostEvent(&event); +} + +void AppTask::FunctionTimerEventHandler(void * aGenericEvent) +{ + AppEvent * aEvent = (AppEvent *) aGenericEvent; + + if (aEvent->Type != AppEvent::kTimer) + return; + + K32W_LOG("Device will factory reset..."); + + // Actually trigger Factory Reset + chip::Server::GetInstance().ScheduleFactoryReset(); +} + +void AppTask::ResetActionEventHandler(void * aGenericEvent) +{ + AppEvent * aEvent = (AppEvent *) aGenericEvent; + + if (aEvent->ButtonEvent.PinNo != RESET_BUTTON && aEvent->ButtonEvent.PinNo != BLE_BUTTON) + return; + + if (sAppTask.mResetTimerActive) + { + sAppTask.CancelTimer(); + sAppTask.mFunction = Function::kNoneSelected; + +#if !defined(chip_with_low_power) || (chip_with_low_power == 0) + /* restore initial state for the LED indicating contact state */ + if (!ContactSensorMgr().IsContactClosed()) + { + sContactSensorLED.Set(false); + } + else + { + sContactSensorLED.Set(true); + } +#endif + + K32W_LOG("Factory Reset was cancelled!"); + } + else + { + uint32_t resetTimeout = FACTORY_RESET_TRIGGER_TIMEOUT; + + if (sAppTask.mFunction != Function::kNoneSelected) + { + K32W_LOG("Another function is scheduled. Could not initiate Factory Reset!"); + return; + } + + K32W_LOG("Factory Reset Triggered. Push the RESET button within %lu ms to cancel!", resetTimeout); + sAppTask.mFunction = Function::kFactoryReset; + + /* LEDs will start blinking to signal that a Factory Reset was scheduled */ +#if !defined(chip_with_low_power) || (chip_with_low_power == 0) +#ifndef CHIP_DEVICE_CONFIG_ENABLE_OTA_REQUESTOR + sStatusLED.Set(false); + sStatusLED.Blink(500); +#endif + sContactSensorLED.Set(false); + sContactSensorLED.Blink(500); +#endif + + sAppTask.StartTimer(FACTORY_RESET_TRIGGER_TIMEOUT); + } +} + +void AppTask::ContactActionEventHandler(void * aGenericEvent) +{ + AppEvent * aEvent = (AppEvent *) aGenericEvent; + ContactSensorManager::Action action = ContactSensorManager::Action::kInvalid; + CHIP_ERROR err = CHIP_NO_ERROR; + bool state_changed = false; + + if (sAppTask.mFunction != Function::kNoneSelected) + { + K32W_LOG("Another function is scheduled. Could not change contact state."); + return; + } + + if (aEvent->Type == AppEvent::kContact) + { + action = static_cast(aEvent->ContactEvent.Action); + } + else if (aEvent->Type == AppEvent::kButton) + { + if (ContactSensorMgr().IsContactClosed()) + { + action = ContactSensorManager::Action::kSignalLost; + } + else + { + action = ContactSensorManager::Action::kSignalDetected; + } + + sAppTask.SetSyncClusterToButtonAction(true); + } + else + { + err = APP_ERROR_UNHANDLED_EVENT; + action = ContactSensorManager::Action::kInvalid; + } + + if (err == CHIP_NO_ERROR) + { + ContactSensorMgr().InitiateAction(action); + } +} + +void AppTask::OTAHandler(void * aGenericEvent) +{ + AppEvent * aEvent = (AppEvent *) aGenericEvent; + if (aEvent->ButtonEvent.PinNo != OTA_BUTTON) + return; + +#if CHIP_DEVICE_CONFIG_ENABLE_OTA_REQUESTOR + if (sAppTask.mFunction != Function::kNoneSelected) + { + K32W_LOG("Another function is scheduled. Could not initiate OTA!"); + return; + } + + PlatformMgr().ScheduleWork(StartOTAQuery, 0); +#endif +} + +#if CHIP_DEVICE_CONFIG_ENABLE_OTA_REQUESTOR +void AppTask::StartOTAQuery(intptr_t arg) +{ + GetRequestorInstance()->TriggerImmediateQuery(); +} +#endif + +void AppTask::BleHandler(void * aGenericEvent) +{ + AppEvent * aEvent = (AppEvent *) aGenericEvent; + + if (aEvent->ButtonEvent.PinNo != BLE_BUTTON) + return; + + if (sAppTask.mFunction != Function::kNoneSelected) + { + K32W_LOG("Another function is scheduled. Could not toggle BLE state!"); + return; + } + PlatformMgr().ScheduleWork(AppTask::BleStartAdvertising, 0); +} + +void AppTask::BleStartAdvertising(intptr_t arg) +{ + if (ConnectivityMgr().IsBLEAdvertisingEnabled()) + { + ConnectivityMgr().SetBLEAdvertisingEnabled(false); + K32W_LOG("Stopped BLE Advertising!"); + } + else + { + ConnectivityMgr().SetBLEAdvertisingEnabled(true); + + if (chip::Server::GetInstance().GetCommissioningWindowManager().OpenBasicCommissioningWindow() == CHIP_NO_ERROR) + { + K32W_LOG("Started BLE Advertising!"); + } + else + { + K32W_LOG("OpenBasicCommissioningWindow() failed"); + } + } +} + +void AppTask::MatterEventHandler(const ChipDeviceEvent * event, intptr_t) +{ + if (event->Type == DeviceEventType::kServiceProvisioningChange && event->ServiceProvisioningChange.IsServiceProvisioned) + { + if (event->ServiceProvisioningChange.IsServiceProvisioned) + { + sIsThreadProvisioned = TRUE; + } + else + { + sIsThreadProvisioned = FALSE; + } + } + +#if CHIP_DEVICE_CONFIG_ENABLE_OTA_REQUESTOR + if (event->Type == DeviceEventType::kDnssdInitialized) + { + K32W_LOG("Dnssd platform initialized."); + PlatformMgr().ScheduleWork(InitOTA, 0); + } +#endif +} + +void AppTask::CancelTimer() +{ + if (xTimerStop(sFunctionTimer, 0) == pdFAIL) + { + K32W_LOG("app timer stop() failed"); + } + + mResetTimerActive = false; +} + +void AppTask::StartTimer(uint32_t aTimeoutInMs) +{ + if (xTimerIsTimerActive(sFunctionTimer)) + { + K32W_LOG("app timer already started!"); + CancelTimer(); + } + + // timer is not active, change its period to required value (== restart). + // FreeRTOS- Block for a maximum of 100 ticks if the change period command + // cannot immediately be sent to the timer command queue. + if (xTimerChangePeriod(sFunctionTimer, aTimeoutInMs / portTICK_PERIOD_MS, 100) != pdPASS) + { + K32W_LOG("app timer start() failed"); + } + + mResetTimerActive = true; +} + +void AppTask::OnStateChanged(ContactSensorManager::State aState) +{ + // If the contact state was changed, update LED state and cluster state (only if button was pressed). + // - turn on the contact LED if contact sensor is in closed state. + // - turn off the lock LED if contact sensor is in opened state. + if (ContactSensorManager::State::kContactClosed == aState) + { + K32W_LOG("Contact state changed to closed.") +#if !defined(chip_with_low_power) || (chip_with_low_power == 0) + sContactSensorLED.Set(true); +#endif + } + else if (ContactSensorManager::State::kContactOpened == aState) + { + K32W_LOG("Contact state changed to opened.") +#if !defined(chip_with_low_power) || (chip_with_low_power == 0) + sContactSensorLED.Set(false); +#endif + } + + if (sAppTask.IsSyncClusterToButtonAction()) + { + sAppTask.UpdateClusterState(); + } + + sAppTask.mFunction = Function::kNoneSelected; +} + +void AppTask::OnIdentifyStart(Identify * identify) +{ + if (Clusters::Identify::EffectIdentifierEnum::kBlink == identify->mCurrentEffectIdentifier) + { + if (Function::kNoneSelected != sAppTask.mFunction) + { + K32W_LOG("Another function is scheduled. Could not initiate Identify process!"); + return; + } + K32W_LOG("Identify process has started. Status LED should blink every 0.5 seconds."); + sAppTask.mFunction = Function::kIdentify; +#if !defined(chip_with_low_power) || (chip_with_low_power == 0) +#ifndef CHIP_DEVICE_CONFIG_ENABLE_OTA_REQUESTOR + sStatusLED.Set(false); + sStatusLED.Blink(500); +#endif +#endif + } +} + +void AppTask::OnIdentifyStop(Identify * identify) +{ + if (Clusters::Identify::EffectIdentifierEnum::kBlink == identify->mCurrentEffectIdentifier) + { + K32W_LOG("Identify process has stopped."); + sAppTask.mFunction = Function::kNoneSelected; + } +} + +void AppTask::PostContactActionRequest(ContactSensorManager::Action aAction) +{ + AppEvent event; + event.Type = AppEvent::kContact; + event.ContactEvent.Action = static_cast(aAction); + event.Handler = ContactActionEventHandler; + PostEvent(&event); +} + +void AppTask::PostEvent(const AppEvent * aEvent) +{ + portBASE_TYPE taskToWake = pdFALSE; + if (sAppEventQueue != NULL) + { + if (__get_IPSR()) + { + if (!xQueueSendToFrontFromISR(sAppEventQueue, aEvent, &taskToWake)) + { + K32W_LOG("Failed to post event to app task event queue"); + } + if (taskToWake) + { + portYIELD_FROM_ISR(taskToWake); + } + } + else if (!xQueueSend(sAppEventQueue, aEvent, 0)) + { + K32W_LOG("Failed to post event to app task event queue"); + } + } +} + +void AppTask::DispatchEvent(AppEvent * aEvent) +{ +#if defined(chip_with_low_power) && (chip_with_low_power == 1) + /* specific processing for events sent from App_PostCallbackMessage (see main.cpp) */ + if (aEvent->Type == AppEvent::kEventType_Lp) + { + aEvent->Handler(aEvent->param); + } + else +#endif + + if (aEvent->Handler) + { + aEvent->Handler(aEvent); + } + else + { + K32W_LOG("Event received with no handler. Dropping event."); + } +} + +void AppTask::UpdateClusterState(void) +{ + PlatformMgr().ScheduleWork(UpdateClusterStateInternal, 0); +} +extern void logBooleanStateEvent(bool state); +void AppTask::UpdateClusterStateInternal(intptr_t arg) +{ + uint8_t newValue = ContactSensorMgr().IsContactClosed(); + + // write the new on/off value + EmberAfStatus status = app::Clusters::BooleanState::Attributes::StateValue::Set(1, newValue); + + if (status != EMBER_ZCL_STATUS_SUCCESS) + { + ChipLogError(NotSpecified, "ERR: updating boolean status value %x", status); + } + logBooleanStateEvent(newValue); +} + +void AppTask::UpdateDeviceState(void) +{ + PlatformMgr().ScheduleWork(UpdateDeviceStateInternal, 0); +} + +void AppTask::UpdateDeviceStateInternal(intptr_t arg) +{ + bool stateValueAttrValue = 0; + + /* get onoff attribute value */ + (void) app::Clusters::BooleanState::Attributes::StateValue::Get(1, &stateValueAttrValue); + +#if !defined(chip_with_low_power) || (chip_with_low_power == 0) + /* set the device state */ + sContactSensorLED.Set(stateValueAttrValue); +#endif +} + +extern "C" void OTAIdleActivities(void) +{ +#if CHIP_DEVICE_CONFIG_ENABLE_OTA_REQUESTOR + OTA_TransactionResume(); +#endif +} diff --git a/examples/contact-sensor-app/nxp/k32w/k32w1/main/ContactSensorManager.cpp b/examples/contact-sensor-app/nxp/k32w/k32w1/main/ContactSensorManager.cpp new file mode 100644 index 00000000000000..9e0a665cbb5aa9 --- /dev/null +++ b/examples/contact-sensor-app/nxp/k32w/k32w1/main/ContactSensorManager.cpp @@ -0,0 +1,84 @@ +/* + * + * Copyright (c) 2022-2023 Project CHIP Authors + * Copyright (c) 2022 Google LLC. + * Copyright (c) 2023 NXP + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "ContactSensorManager.h" + +#include "AppTask.h" +#include "FreeRTOS.h" + +#include "app_config.h" + +ContactSensorManager ContactSensorManager::sContactSensor; + +int ContactSensorManager::Init() +{ + int err = 0; + + mState = State::kContactOpened; + mCallbackStateChanged = nullptr; + + return err; +} + +void ContactSensorManager::SetCallback(CallbackStateChanged aCallbackStateChanged) +{ + mCallbackStateChanged = aCallbackStateChanged; +} + +bool ContactSensorManager::IsContactClosed() +{ + return mState == State::kContactClosed; +} + +void ContactSensorManager::InitiateAction(Action aAction) +{ + AppEvent event; + event.Type = AppEvent::kContact; + event.ContactEvent.Action = static_cast(aAction); + event.Handler = HandleAction; + GetAppTask().PostEvent(&event); +} + +void ContactSensorManager::HandleAction(void * aGenericEvent) +{ + AppEvent * event = static_cast(aGenericEvent); + Action action = static_cast(event->ContactEvent.Action); + // Change current state based on action: + // - if state is closed and action is signal lost, change state to opened + // - if state is opened and action is signal detected, change state to closed + // - else, the state/action combination does not change the state. + if (State::kContactClosed == sContactSensor.mState && Action::kSignalLost == action) + { + sContactSensor.mState = State::kContactOpened; + } + else if (State::kContactOpened == sContactSensor.mState && Action::kSignalDetected == action) + { + sContactSensor.mState = State::kContactClosed; + } + + if (sContactSensor.mCallbackStateChanged != nullptr) + { + sContactSensor.mCallbackStateChanged(sContactSensor.mState); + } + else + { + K32W_LOG("Callback for state change was not set. Please set an appropriate callback."); + } +} diff --git a/examples/contact-sensor-app/nxp/k32w/k32w1/main/ZclCallbacks.cpp b/examples/contact-sensor-app/nxp/k32w/k32w1/main/ZclCallbacks.cpp new file mode 100644 index 00000000000000..e1e35b4cb4ec7e --- /dev/null +++ b/examples/contact-sensor-app/nxp/k32w/k32w1/main/ZclCallbacks.cpp @@ -0,0 +1,98 @@ +/* + * + * Copyright (c) 2022-2023 Project CHIP Authors + * Copyright (c) 2023 NXP + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include "AppTask.h" +#include "ContactSensorManager.h" + +#include +#include +#include +#include +#include +#include +#include + +using namespace ::chip; +using namespace ::chip::app; +using namespace ::chip::app::Clusters; +using namespace ::chip::app::Clusters::BooleanState; + +void MatterPostAttributeChangeCallback(const chip::app::ConcreteAttributePath & path, uint8_t type, uint16_t size, uint8_t * value) +{ + ChipLogProgress(Zcl, "MatterPostAttributeChangeCallback, value:%d\n", *value); + if (path.mClusterId != BooleanState::Id) + { + ChipLogProgress(Zcl, "Unknown cluster ID: " ChipLogFormatMEI, ChipLogValueMEI(path.mClusterId)); + return; + } + + if (path.mAttributeId != BooleanState::Attributes::StateValue::Id) + { + ChipLogProgress(Zcl, "Unknown attribute ID: " ChipLogFormatMEI, ChipLogValueMEI(path.mAttributeId)); + return; + } + + AppTask & task = GetAppTask(); + // If the callback is called after the cluster attribute was changed due to pressing a button, + // set the sync value to false. Both LED and attribute were updated at this point. + // On the other hand, if the cluster attribute was changed due to a cluster command, + // forward the request to AppTask in order to update the LED state. + if (task.IsSyncClusterToButtonAction()) + { + task.SetSyncClusterToButtonAction(false); + } + else + { + task.PostContactActionRequest(*value ? ContactSensorManager::Action::kSignalDetected + : ContactSensorManager::Action::kSignalLost); + } +} + +/** @brief OnOff Cluster Init + * + * This function is called when a specific cluster is initialized. It gives the + * application an opportunity to take care of cluster initialization procedures. + * It is called exactly once for each endpoint where cluster is present. + * + * @param endpoint Ver.: always + * + * TODO Issue #3841 + * emberAfOnOffClusterInitCallback happens before the stack initialize the cluster + * attributes to the default value. + * The logic here expects something similar to the deprecated Plugins callback + * emberAfPluginOnOffClusterServerPostInitCallback. + * + */ +void emberAfBooleanStateClusterInitCallback(EndpointId endpoint) +{ + ChipLogProgress(Zcl, "emberAfBooleanStateClusterInitCallback\n"); + GetAppTask().UpdateClusterState(); +} + +void logBooleanStateEvent(bool state) +{ + EventNumber eventNumber; + Events::StateChange::Type event{ state }; + if (CHIP_NO_ERROR != LogEvent(event, 1, eventNumber)) + { + ChipLogProgress(Zcl, "booleanstate: failed to reacord state-change event"); + } +} diff --git a/examples/contact-sensor-app/nxp/k32w/k32w1/main/include/AppEvent.h b/examples/contact-sensor-app/nxp/k32w/k32w1/main/include/AppEvent.h new file mode 100644 index 00000000000000..f3e0d729abe2e7 --- /dev/null +++ b/examples/contact-sensor-app/nxp/k32w/k32w1/main/include/AppEvent.h @@ -0,0 +1,63 @@ +/* + * + * Copyright (c) 2022 Nest Labs, Inc. + * Copyright (c) 2023 NXP + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +struct AppEvent; +typedef void (*EventHandler)(void *); + +struct AppEvent +{ + enum AppEventTypes + { + kButton = 0, + kTimer, + kContact, + kInstall, +#if defined(chip_with_low_power) && (chip_with_low_power == 1) + kEventType_Lp, +#endif + kOTAResume, + }; + + AppEventTypes Type; + + union + { + struct + { + uint8_t PinNo; + uint8_t Action; + } ButtonEvent; + struct + { + void * Context; + } TimerEvent; + struct + { + uint8_t Action; + } ContactEvent; + }; + + EventHandler Handler; + +#if defined(chip_with_low_power) && (chip_with_low_power == 1) + void * param; +#endif +}; diff --git a/examples/contact-sensor-app/nxp/k32w/k32w1/main/include/AppTask.h b/examples/contact-sensor-app/nxp/k32w/k32w1/main/include/AppTask.h new file mode 100644 index 00000000000000..47b644769cfb63 --- /dev/null +++ b/examples/contact-sensor-app/nxp/k32w/k32w1/main/include/AppTask.h @@ -0,0 +1,129 @@ +/* + * + * Copyright (c) 2022 Google LLC. + * Copyright (c) 2023 NXP + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include + +#include "AppEvent.h" +#include "ContactSensorManager.h" + +#include "CHIPProjectConfig.h" + +#include +#include + +#include "FreeRTOS.h" +#include "fsl_component_button.h" +#include "timers.h" + +// Application-defined error codes in the CHIP_ERROR space. +#define APP_ERROR_EVENT_QUEUE_FAILED CHIP_APPLICATION_ERROR(0x01) +#define APP_ERROR_CREATE_TASK_FAILED CHIP_APPLICATION_ERROR(0x02) +#define APP_ERROR_UNHANDLED_EVENT CHIP_APPLICATION_ERROR(0x03) +#define APP_ERROR_CREATE_TIMER_FAILED CHIP_APPLICATION_ERROR(0x04) +#define APP_ERROR_START_TIMER_FAILED CHIP_APPLICATION_ERROR(0x05) +#define APP_ERROR_STOP_TIMER_FAILED CHIP_APPLICATION_ERROR(0x06) + +class AppTask +{ +public: + CHIP_ERROR StartAppTask(); + static void AppTaskMain(void * pvParameter); + + void PostContactActionRequest(ContactSensorManager::Action aAction); + void PostEvent(const AppEvent * event); + + void UpdateClusterState(void); + void UpdateDeviceState(void); + + bool IsSyncClusterToButtonAction(); + void SetSyncClusterToButtonAction(bool value); + // Identify cluster callbacks. + static void OnIdentifyStart(Identify * identify); + static void OnIdentifyStop(Identify * identify); + +private: + friend AppTask & GetAppTask(void); + + CHIP_ERROR Init(); + + static void OnStateChanged(ContactSensorManager::State aState); + + void CancelTimer(void); + + void DispatchEvent(AppEvent * event); + + static void FunctionTimerEventHandler(void * aGenericEvent); + static button_status_t KBD_Callback(void * buttonHandle, button_callback_message_t * message, void * callbackParam); + static void HandleKeyboard(void); + static void OTAHandler(void * aGenericEvent); + static void BleHandler(void * aGenericEvent); + static void BleStartAdvertising(intptr_t arg); + static void ContactActionEventHandler(void * aGenericEvent); + static void ResetActionEventHandler(void * aGenericEvent); + static void InstallEventHandler(void * aGenericEvent); + + static void ButtonEventHandler(uint8_t pin_no, uint8_t button_action); + static void TimerEventHandler(TimerHandle_t xTimer); + + static void MatterEventHandler(const chip::DeviceLayer::ChipDeviceEvent * event, intptr_t arg); + void StartTimer(uint32_t aTimeoutInMs); + +#if CHIP_DEVICE_CONFIG_ENABLE_OTA_REQUESTOR + static void InitOTA(intptr_t arg); + static void StartOTAQuery(intptr_t arg); +#endif + + static void UpdateClusterStateInternal(intptr_t arg); + static void UpdateDeviceStateInternal(intptr_t arg); + static void InitServer(intptr_t arg); + static void PrintOnboardingInfo(); + + enum class Function : uint8_t + { + kNoneSelected = 0, + kFactoryReset, + kContact, + kIdentify, + kInvalid + }; + + Function mFunction = Function::kNoneSelected; + bool mResetTimerActive = false; + bool mSyncClusterToButtonAction = false; + + static AppTask sAppTask; +}; + +inline AppTask & GetAppTask(void) +{ + return AppTask::sAppTask; +} + +inline bool AppTask::IsSyncClusterToButtonAction() +{ + return mSyncClusterToButtonAction; +} + +inline void AppTask::SetSyncClusterToButtonAction(bool value) +{ + mSyncClusterToButtonAction = value; +} diff --git a/examples/contact-sensor-app/nxp/k32w/k32w1/main/include/ContactSensorManager.h b/examples/contact-sensor-app/nxp/k32w/k32w1/main/include/ContactSensorManager.h new file mode 100644 index 00000000000000..69a71ee14ee344 --- /dev/null +++ b/examples/contact-sensor-app/nxp/k32w/k32w1/main/include/ContactSensorManager.h @@ -0,0 +1,66 @@ +/* + * + * Copyright (c) 2022 Google LLC. + * Copyright (c) 2023 NXP + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include + +#include "AppEvent.h" + +#include "FreeRTOS.h" +#include "timers.h" // provides FreeRTOS timer support + +class ContactSensorManager +{ +public: + enum class Action : uint8_t + { + kSignalDetected = 0, + kSignalLost, + kInvalid + }; + + enum class State : uint8_t + { + kContactClosed = 0, + kContactOpened, + kInvalid + }; + + int Init(); + bool IsContactClosed(); + void InitiateAction(Action aAction); + + typedef void (*CallbackStateChanged)(State aState); + void SetCallback(CallbackStateChanged aCallbackStateChanged); + + static void HandleAction(void * aGenericEvent); + +private: + friend ContactSensorManager & ContactSensorMgr(void); + State mState; + CallbackStateChanged mCallbackStateChanged; + static ContactSensorManager sContactSensor; +}; + +inline ContactSensorManager & ContactSensorMgr(void) +{ + return ContactSensorManager::sContactSensor; +} diff --git a/examples/contact-sensor-app/nxp/k32w/k32w1/main/include/app_config.h b/examples/contact-sensor-app/nxp/k32w/k32w1/main/include/app_config.h new file mode 100644 index 00000000000000..b62ce79567e8db --- /dev/null +++ b/examples/contact-sensor-app/nxp/k32w/k32w1/main/include/app_config.h @@ -0,0 +1,51 @@ +/* + * + * Copyright (c) 2022 Google LLC. + * Copyright (c) 2023 NXP + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +// ---- Contact Example App Config ---- + +#define RESET_BUTTON 1 +#define CONTACT_SENSOR_BUTTON 2 +#define OTA_BUTTON 3 +#define BLE_BUTTON 4 + +#define RESET_BUTTON_PUSH 1 +#define CONTACT_SENSOR_BUTTON_PUSH 2 +#define OTA_BUTTON_PUSH 3 +#define BLE_BUTTON_PUSH 4 + +#define APP_BUTTON_PUSH 1 + +#define CONTACT_SENSOR_STATE_LED 1 +#define SYSTEM_STATE_LED 0 + +// ---- Contact Example SWU Config ---- +#define SWU_INTERVAl_WINDOW_MIN_MS (23 * 60 * 60 * 1000) // 23 hours +#define SWU_INTERVAl_WINDOW_MAX_MS (24 * 60 * 60 * 1000) // 24 hours + +// ---- Thread Polling Config ---- +// #define THREAD_ACTIVE_POLLING_INTERVAL_MS 100 +// #define THREAD_INACTIVE_POLLING_INTERVAL_MS 1000 + +#if K32W_LOG_ENABLED +#define K32W_LOG(...) otPlatLog(OT_LOG_LEVEL_NONE, OT_LOG_REGION_API, ##__VA_ARGS__); +#else +#define K32W_LOG(...) +#endif diff --git a/examples/contact-sensor-app/nxp/k32w/k32w1/main/main.cpp b/examples/contact-sensor-app/nxp/k32w/k32w1/main/main.cpp new file mode 100644 index 00000000000000..700bbc5ad9356c --- /dev/null +++ b/examples/contact-sensor-app/nxp/k32w/k32w1/main/main.cpp @@ -0,0 +1,143 @@ +/* + * + * Copyright (c) 2022 Google LLC. + * Copyright (c) 2023 NXP + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// ================================================================================ +// Main Code +// ================================================================================ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "FreeRtosHooks.h" +#include "app_config.h" +#include "openthread/platform/logging.h" + +using namespace ::chip; +using namespace ::chip::Inet; +using namespace ::chip::DeviceLayer; +using namespace ::chip::Logging; + +#include + +typedef void (*InitFunc)(void); +extern InitFunc __init_array_start; +extern InitFunc __init_array_end; + +extern "C" void main_task(void const * argument) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + + /* Call C++ constructors */ + InitFunc * pFunc = &__init_array_start; + for (; pFunc < &__init_array_end; ++pFunc) + { + (*pFunc)(); + } + + mbedtls_platform_set_calloc_free(CHIPPlatformMemoryCalloc, CHIPPlatformMemoryFree); + + err = PlatformMgrImpl().InitBoardFwk(); + if (err != CHIP_NO_ERROR) + { + return; + } + + /* Used for HW initializations */ + otSysInit(0, NULL); + + if (err != CHIP_NO_ERROR) + { + return; + } + + K32W_LOG("Welcome to NXP Contact Sensor Demo App"); + + /* Mbedtls Threading support is needed because both + * Thread and Matter tasks are using it */ + freertos_mbedtls_mutex_init(); + + // Init Chip memory management before the stack + chip::Platform::MemoryInit(); + + err = PlatformMgr().InitChipStack(); + if (err != CHIP_NO_ERROR) + { + K32W_LOG("Error during PlatformMgr().InitMatterStack()"); + goto exit; + } + + err = ThreadStackMgr().InitThreadStack(); + if (err != CHIP_NO_ERROR) + { + K32W_LOG("Error during ThreadStackMgr().InitThreadStack()"); + goto exit; + } + + err = ConnectivityMgr().SetThreadDeviceType(ConnectivityManager::kThreadDeviceType_SleepyEndDevice); + if (err != CHIP_NO_ERROR) + { + goto exit; + } + + // Start OpenThread task + err = ThreadStackMgrImpl().StartThreadTask(); + if (err != CHIP_NO_ERROR) + { + K32W_LOG("Error during ThreadStackMgrImpl().StartThreadTask()"); + goto exit; + } + + err = GetAppTask().StartAppTask(); + if (err != CHIP_NO_ERROR) + { + K32W_LOG("Error during GetAppTask().StartAppTask()"); + goto exit; + } + + err = PlatformMgr().StartEventLoopTask(); + if (err != CHIP_NO_ERROR) + { + K32W_LOG("Error during PlatformMgr().StartEventLoopTask();"); + goto exit; + } + + GetAppTask().AppTaskMain(NULL); + +exit: + return; +} + +/** + * Glue function called directly by the OpenThread stack + * when system event processing work is pending. + */ +extern "C" void otSysEventSignalPending(void) +{ + BaseType_t yieldRequired = ThreadStackMgrImpl().SignalThreadActivityPendingFromISR(); + portYIELD_FROM_ISR(yieldRequired); +} diff --git a/examples/contact-sensor-app/nxp/k32w/k32w1/third_party/connectedhomeip b/examples/contact-sensor-app/nxp/k32w/k32w1/third_party/connectedhomeip new file mode 120000 index 00000000000000..305f2077ffe860 --- /dev/null +++ b/examples/contact-sensor-app/nxp/k32w/k32w1/third_party/connectedhomeip @@ -0,0 +1 @@ +../../../../../.. \ No newline at end of file diff --git a/examples/lighting-app/nxp/k32w/k32w1/.gn b/examples/lighting-app/nxp/k32w/k32w1/.gn new file mode 100644 index 00000000000000..3d48789e30ab3d --- /dev/null +++ b/examples/lighting-app/nxp/k32w/k32w1/.gn @@ -0,0 +1,28 @@ +# Copyright (c) 2020 Project CHIP Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import("//build_overrides/build.gni") + +# The location of the build configuration file. +buildconfig = "${build_root}/config/BUILDCONFIG.gn" + +# CHIP uses angle bracket includes. +check_system_includes = true + +default_args = { + target_cpu = "arm" + target_os = "freertos" + + import("//args.gni") +} diff --git a/examples/lighting-app/nxp/k32w/k32w1/BUILD.gn b/examples/lighting-app/nxp/k32w/k32w1/BUILD.gn new file mode 100644 index 00000000000000..fd095a2cb197c5 --- /dev/null +++ b/examples/lighting-app/nxp/k32w/k32w1/BUILD.gn @@ -0,0 +1,141 @@ +# Copyright (c) 2021 Project CHIP Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import("//build_overrides/chip.gni") +import("//build_overrides/k32w1_sdk.gni") +import("//build_overrides/openthread.gni") + +import("${k32w1_sdk_build_root}/k32w1_executable.gni") +import("${k32w1_sdk_build_root}/k32w1_sdk.gni") + +import("${chip_root}/src/crypto/crypto.gni") +import("${chip_root}/src/lib/core/core.gni") +import("${chip_root}/src/platform/device.gni") + +declare_args() { + chip_software_version = 0 +} + +assert(current_os == "freertos") + +k32w1_platform_dir = "${chip_root}/examples/platform/nxp/k32w/k32w1" +k32w1_sdk_root = getenv("NXP_K32W1_SDK_ROOT") + +k32w1_sdk("sdk") { + sources = [ + "${k32w1_platform_dir}/app/project_include/OpenThreadConfig.h", + "include/CHIPProjectConfig.h", + "include/FreeRTOSConfig.h", + "main/include/app_config.h", + ] + + public_deps = + [ "${chip_root}/third_party/openthread/platforms:libopenthread-platform" ] + + include_dirs = [ + "main/include", + "main", + "include", + "${k32w1_platform_dir}/app/project_include", + "${k32w1_platform_dir}/app/support", + "${k32w1_platform_dir}/app/ldscripts", + "${k32w1_platform_dir}/util/include", + ] + + defines = [] + if (is_debug) { + defines += [ "BUILD_RELEASE=0" ] + } else { + defines += [ "BUILD_RELEASE=1" ] + } + + if (chip_software_version != 0) { + defines += [ + "CHIP_DEVICE_CONFIG_DEVICE_SOFTWARE_VERSION=${chip_software_version}", + ] + } +} + +k32w1_executable("light_app") { + output_name = "chip-k32w1-light-example" + + sources = [ + "${k32w1_platform_dir}/util/LEDWidget.cpp", + "${k32w1_platform_dir}/util/include/LEDWidget.h", + "main/AppTask.cpp", + "main/LightingManager.cpp", + "main/ZclCallbacks.cpp", + "main/include/AppEvent.h", + "main/include/AppTask.h", + "main/include/LightingManager.h", + "main/main.cpp", + ] + + deps = [ + ":sdk", + "${chip_root}/examples/common/QRCode", + "${chip_root}/examples/lighting-app/nxp/zap/", + "${chip_root}/examples/providers:device_info_provider", + "${chip_root}/src/lib", + "${chip_root}/src/platform:syscalls_stub", + "${chip_root}/third_party/mbedtls:mbedtls", + "${k32w1_platform_dir}/app/support:freertos_mbedtls_utils", + ] + + if (chip_openthread_ftd) { + deps += [ + "${chip_root}/third_party/openthread/repo:libopenthread-cli-ftd", + "${chip_root}/third_party/openthread/repo:libopenthread-ftd", + ] + } else { + deps += [ + "${chip_root}/third_party/openthread/repo:libopenthread-cli-mtd", + "${chip_root}/third_party/openthread/repo:libopenthread-mtd", + ] + } + + cflags = [ "-Wconversion" ] + + if (use_smu2_as_system_memory) { + ldscript = "${k32w1_platform_dir}/app/ldscripts/k32w1_app.ld" + base_ldscript_dir = "${k32w1_sdk_root}/middleware/wireless/framework/Common/devices/kw45_k32w1/gcc" + } else { + ldscript = "${k32w1_sdk_root}/middleware/wireless/framework/Common/devices/kw45_k32w1/gcc/connectivity.ld" + } + + inputs = [ ldscript ] + + ldflags = [ + "-Wl,--defsym=__heap_size__=0", + "-Wl,--defsym=__stack_size__=0x480", + "-Wl,--defsym=gNvmSectors=8", + "-Wl,-print-memory-usage", + "-Wl,--no-warn-rwx-segments", + "-T" + rebase_path(ldscript, root_build_dir), + ] + + if (use_smu2_as_system_memory) { + ldflags += [ "-L" + rebase_path(base_ldscript_dir, root_build_dir) ] + } + + output_dir = root_out_dir +} + +group("k32w1") { + deps = [ ":light_app" ] +} + +group("default") { + deps = [ ":k32w1" ] +} diff --git a/examples/lighting-app/nxp/k32w/k32w1/README.md b/examples/lighting-app/nxp/k32w/k32w1/README.md new file mode 100644 index 00000000000000..92dca287111449 --- /dev/null +++ b/examples/lighting-app/nxp/k32w/k32w1/README.md @@ -0,0 +1,425 @@ +# Matter K32W1 Lighting Example Application + +Matter K32W1 Lighting Example demonstrates how to remotely control a light bulb. +The light bulb is simulated using one of the LEDs from the expansion board. It +uses buttons to test turn on/turn off of the light bulb. You can use this +example as a reference for creating your own application. + +The example is based on +[Matter](https://github.com/project-chip/connectedhomeip) and the NXP K32W1 SDK, +and supports remote access and control of a light bulb over a low-power, +802.15.4 Thread network. + +The example behaves as a Matter accessory, that is a device that can be paired +into an existing Matter network and can be controlled by this network. + +
+ +- [Matter K32W1 Lighting Example Application](#matter-k32w1-lighting-example-application) +- [Introduction](#introduction) + - [Bluetooth LE Advertising](#bluetooth-le-advertising) + - [Bluetooth LE Rendezvous](#bluetooth-le-rendezvous) +- [Device UI](#device-ui) +- [Building](#building) + - [SMU2](#smu2-memory) +- [Flashing](#flashing) + - [Flashing the NBU image](#flashing-the-nbu-image) + - [Flashing the host image](#flashing-the-host-image) +- [Debugging](#debugging) +- [OTA](#ota) + + - [Convert srec into sb3 file](#convert-srec-into-sb3-file) + - [Convert sb3 into ota file](#convert-sb3-into-ota-file) + - [Running OTA](#running-ota) + - [Known issues](#known-issues) + + + +## Introduction + +![K32W1 EVK](../../../../platform/nxp/k32w/k32w1/doc/images/k32w1-evk.jpg) + +The K32W1 lighting example application provides a working demonstration of a +light bulb device, built using the Matter codebase and the NXP K32W1 SDK. The +example supports remote access (e.g.: using CHIP Tool from a mobile phone) and +control of a light bulb over a low-power, 802.15.4 Thread network. It is capable +of being paired into an existing Matter network along with other Matter-enabled +devices. + +The Matter device that runs the lighting application is controlled by the Matter +controller device over the Thread protocol. By default, the Matter device has +Thread disabled, and it should be paired over Bluetooth LE with the Matter +controller and obtain configuration from it. The actions required before +establishing full communication are described below. + +### Bluetooth LE Advertising + +In this example, to commission the device onto a Matter network, it must be +discoverable over Bluetooth LE. For security reasons, you must start Bluetooth +LE advertising manually after powering up the device by pressing Button SW2. + +### Bluetooth LE Rendezvous + +In this example, the commissioning procedure (called rendezvous) is done over +Bluetooth LE between a Matter device and the Matter controller, where the +controller has the commissioner role. + +To start the rendezvous, the controller must get the commissioning information +from the Matter device. The data payload is encoded within a QR code, or printed +to the UART console. + +### Thread Provisioning + +## Device UI + +The example application provides a simple UI that depicts the state of the +device and offers basic user control. This UI is implemented via the +general-purpose LEDs and buttons built in the K32W1 EVK board. + +**LED 2** shows the overall state of the device and its connectivity. Four +states are depicted: + +- _Short Flash On (50ms on/950ms off)_ — The device is in an + unprovisioned (unpaired) state and is waiting for a commissioning + application to connect. + +* _Rapid Even Flashing (100ms on/100ms off)_ — The device is in an + unprovisioned state and a commissioning application is connected via BLE. + +- _Short Flash Off (950ms on/50ms off)_ — The device is full + provisioned, but does not yet have full network (Thread) or service + connectivity. + +* _Solid On_ — The device is fully provisioned and has full network and + service connectivity. + +NOTE: LED2 will be disabled when CHIP_DEVICE_CONFIG_ENABLE_OTA_REQUESTOR is +enabled. On K32W1 EVK board, `PTB0` is wired to LED2 also is wired to CS (Chip +Select) External Flash Memory. OTA image is stored in external memory because of +it's size. If LED2 is enabled then it will affect External Memory CS and OTA +will not work. + +**RGB LED** shows the state of the simulated light bulb. When the LED is lit the +light bulb is on; when not lit, the light bulb is off. + +**Button SW2** can be used to start BLE advertising. A SHORT press of the button +will enable Bluetooth LE advertising for a predefined period of time. A LONG +Press Button SW2 initiates a factory reset. After an initial period of 3 +seconds, LED 2 and RGB LED will flash in unison to signal the pending reset. +After 6 seconds will cause the device to reset its persistent configuration and +initiate a reboot. The reset action can be cancelled by press SW2 button at any +point before the 6 second limit. + +**Button SW3** can be used to change the state of the simulated light bulb. This +can be used to mimic a user manually operating a switch. The button behaves as a +toggle, swapping the state every time it is pressed. + +## Building + +In order to build the Matter example, we recommend using a Linux distribution +(the demo-application was compiled on Ubuntu 20.04). + +- Download [K32W1 SDK for Matter](https://mcuxpresso.nxp.com/). Creating an + nxp.com account is required before being able to download the SDK. Once the + account is created, login and follow the steps for downloading K32W148-EVK + MCUXpresso SDK. The SDK Builder UI selection should be similar with the one + from the image below. + + ![MCUXpresso SDK Download](../../../../platform/nxp/k32w/k32w1/doc/images/mcux-sdk-download.jpg) + + Please refer to Matter release notes for getting the latest released SDK. + +``` +user@ubuntu:~/Desktop/git/connectedhomeip$ export NXP_K32W1_SDK_ROOT=/home/user/Desktop/SDK_K32W1/ +user@ubuntu:~/Desktop/git/connectedhomeip$ source ./scripts/activate.sh +user@ubuntu:~/Desktop/git/connectedhomeip$ cd examples/lighting-app/nxp/k32w/k32w1 +user@ubuntu:~/Desktop/git/connectedhomeip/examples/lighting-app/nxp/k32w/k32w1$ gn gen out/debug --args="chip_with_ot_cli=0 is_debug=false chip_openthread_ftd=true chip_crypto=\"platform\"" +user@ubuntu:~/Desktop/git/connectedhomeip/examples/lighting-app/nxp/k32w/k32w1$ ninja -C out/debug +``` + +In case that Openthread CLI is needed, `chip_with_ot_cli` build argument must be +set to 1. + +After a successful build, the `elf` and `srec` files are found in `out/debug/` - +see the files prefixed with `chip-k32w1-light-example`. + +### `SMU2` Memory + +Some Matter instances and global variables can be placed in the `NBU` `SMU2` +memory. When compiling with OpenThread FTD support (`chip_openthread_ftd=true`) +and with `use_smu2_as_system_memory=true`, the following components are placed +in `SMU2` memory: + +- `gImageProcessor` from `OTAImageProcessorImpl.cpp`. +- `gApplicationProcessor` from `OTAHooks.cpp`. +- `Server::sServer` from `Server.cpp`. +- `ThreadStackManagerImpl::sInstance` from `ThreadStackManagerImpl.cpp`. + +These instances and global variables are placed in `SMU2` memory through name +matching in the application linker script. They should not be changed or, if +changed, the names must be updated in `k32w1_app.ld`. See +[k32w1_app.ld](../../../../platform/nxp/k32w/k32w1/app/ldscripts/k32w1_app.ld) +for names and `SMU2` memory range size. + +To use the `SMU2` Memory an optimized `NBU` binary is also needed. See +[Flashing the NBU image](#flashing-the-nbu-image). + +## Flashing + +Two images must be written to the board: one for the host (CM33) and one for the +`NBU` (CM3). + +The image needed on the host side is the one generated in `out/debug/` while the +one needed on the `NBU` side can be found in the downloaded NXP-SDK package at +path - +`middleware\wireless\ieee-802.15.4\bin\k32w1\k32w1_nbu_ble_15_4_dyn_matter_$version.sb3`. + +### Flashing the `NBU` image + +`NBU` image should be written only when a new NXP-SDK is released. + +[K32W148 board quick start guide](https://www.nxp.com/document/guide/getting-started-with-the-k32w148-development-platform:GS-K32W148EVK) +can be used for updating the `NBU/radio` core: + +- Section 2.4 – Get Software – install `SPSDK` (Secure Provisioning Command + Line Tool) +- Section 3.3 – Updating `NBU` for Wireless examples - use the corresponding + .sb3 file found in the SDK package at path + `middleware\wireless\ieee-802.15.4\bin\k32w1\` + +### Flashing the host image + +Host image is the one found under `out/debug/`. It should be written after each +build process. + +If debugging is needed then jump directly to the [Debugging](#debugging) +section. Otherwise, if only flashing is needed then +[JLink 7.84b](https://www.segger.com/downloads/jlink/) can be used: + +- Plug K32W1 to the USB port (no need to keep the SW4 button pressed while + doing this) + +- Create a new file, `commands_script`, with the following content (change + application name accordingly): + +```bash +reset +halt +loadfile chip-k32w1-light-example.srec +reset +go +quit +``` + +- copy the application and `commands_script` in the same folder that JLink + executable is placed. Execute: + +```bash +$ jlink -device K32W1480 -if SWD -speed 4000 -autoconnect 1 -CommanderScript commands_script +``` + +## Debugging + +One option for debugging would be to use MCUXpresso IDE. + +- Drag-and-drop the zip file containing the NXP SDK in the "Installed SDKs" + tab: + +![Installed SDKs](../../../../platform/nxp/k32w/k32w1/doc/images/installed_sdks.jpg) + +- Import any demo application from the installed SDK: + +``` +Import SDK example(s).. -> choose a demo app (demo_apps -> hello_world) -> Finish +``` + +![Import demo](../../../../platform/nxp/k32w/k32w1/doc/images/import_demo.jpg) + +- Flash the previously imported demo application on the board: + +``` +Right click on the application (from Project Explorer) -> Debug as -> JLink/CMSIS-DAP +``` + +After this step, a debug configuration specific for the K32W1 board was created. +This debug configuration will be used later on for debugging the application +resulted after ot-nxp compilation. + +- Import Matter repo in MCUXpresso IDE as Makefile Project. Use _none_ as + _Toolchain for Indexer Settings_: + +``` +File -> Import -> C/C++ -> Existing Code as Makefile Project +``` + +![New Project](../../../../platform/nxp/k32w/k32w1/doc/images/new_project.jpg) + +- Replace the path of the existing demo application with the path of the K32W1 + application: + +``` +Run -> Debug Configurations... -> C/C++ Application +``` + +![Debug K32W1](../../../../platform/nxp/k32w/k32w1/doc/images/debug_k32w1.jpg) + +## OTA + +### Convert `srec` into `sb3` file + +The OTA image files must be encrypted using Over The Air Programming Tool +([OTAP](https://www.nxp.com/design/microcontrollers-developer-resources/connectivity-tool-suite:CONNECTIVITY-TOOL-SUITE?#downloads)). +Bootloader will load the new OTA image only if it detects that the file was +encrypted with the `OTAP` correct keys. + +`.srec` file is input for Over The air Programming (`OTAP`) application +(unencrypted) and it's converted to `.sb3` format (encrypted). + +In `OTAP` application + +- select OTA protocol => `OTAP` Matter +- Browse File +- follow default options (KW45/K32W148, Preserve NVM) +- image information: will update "Application Core (MCU)" - this will generate + the image only for the CM33 core +- keep other settings at default values + +### Convert sb3 into ota file + +In order to build an OTA image, use NXP wrapper over the standard tool +`src/app/ota_image_tool.py`: + +- `scripts/tools/nxp/factory_data_generator/ota_image_tool.py` The tool can be + used to generate an OTA image with the following format: + `| OTA image header | TLV1 | TLV2 | ... | TLVn |` where each TLV is in the + form `|tag|length|value|` + +Note that "standard" TLV format is used. Matter TLV format is only used for +factory data TLV value. + +Please see more in the +[OTA image tool guide](../../../../../scripts/tools/nxp/ota/README.md). + +Here is an example that generates an OTA image with application update TLV from +a sb3 file: + +``` +./scripts/tools/nxp/ota/ota_image_tool.py create -v 0xDEAD -p 0xBEEF -vn 43033 -vs "1.0" -da sha256 --app-input-file ~/binaries/chip-k32w1-43033.sb3 ~/binaries/chip-k32w1-43033.ota + +``` + +A note regarding OTA image header version (`-vn` option). An application binary +has its own software version (given by +`CHIP_DEVICE_CONFIG_DEVICE_SOFTWARE_VERSION`, which can be overwritten). For +having a correct OTA process, the OTA header version should be the same as the +binary embedded software version. A user can set a custom software version in +the gn build args by setting `chip_software_version` to the wanted version. + +### Running OTA + +The OTA topology used for OTA testing is illustrated in the figure below. +Topology is similar with the one used for Matter Test Events. + +![OTA_TOPOLOGY](../../../../platform/nxp/k32w/k32w1/doc/images/ota_topology.JPG) + +The concept for OTA is the next one: + +- there is an OTA Provider Application that holds the OTA image. In our case, + this is a Linux application running on an Ubuntu based-system; +- the OTA Requestor functionality is embedded inside the Lighting Application. + It will be used for requesting OTA blocks from the OTA Provider; +- the controller (a linux application called chip-tool) will be used for + commissioning both the device and the OTA Provider App. The device will be + commissioned using the standard Matter flow (BLE + IEEE 802.15.4) while the + OTA Provider Application will be commissioned using the _onnetwork_ option + of chip-tool; +- during commissioning, each device is assigned a node id by the chip-tool + (can be specified manually by the user). Using the node id of the device and + of the lighting application, chip-tool triggers the OTA transfer by invoking + the _announce-ota-provider_ command - basically, the OTA Requestor is + informed of the node id of the OTA Provider Application. + +_Computer #1_ can be any system running an Ubuntu distribution. We recommand +using CSA official instructions from +[here](https://groups.csa-iot.org/wg/matter-csg/document/28566), where RPi 4 are +proposed. Also, CSA official instructions document point to the OS/Docker images +that should be used on the RPis. For compatibility reasons, we recommand +compiling chip-tool and OTA Provider applications with the same commit id that +was used for compiling the Lighting Application. Also, please note that there is +a single controller (chip-tool) running on Computer #1 which is used for +commissioning both the device and the OTA Provider Application. If needed, +[these instructions](https://itsfoss.com/connect-wifi-terminal-ubuntu/) could be +used for connecting the RPis to WiFi. + +Build the Linux OTA provider application: + +``` +user@computer1:~/connectedhomeip$ : ./scripts/examples/gn_build_example.sh examples/ota-provider-app/linux out/ota-provider-app chip_config_network_layer_ble=false +``` + +Build Linux chip-tool: + +``` +user@computer1:~/connectedhomeip$ : ./scripts/examples/gn_build_example.sh examples/chip-tool out/chip-tool-app +``` + +Start the OTA Provider Application: + +``` +user@computer1:~/connectedhomeip$ : rm -rf /tmp/chip_* +user@computer1:~/connectedhomeip$ : ./out/ota-provider-app/chip-ota-provider-app -f chip-k32w1-43033.ota +``` + +Provision the OTA provider application and assign node id _1_. Also, grant ACL +entries to allow OTA requestors: + +``` +user@computer1:~/connectedhomeip$ : rm -rf /tmp/chip_* +user@computer1:~/connectedhomeip$ : ./out/chip-tool-app/chip-tool pairing onnetwork 1 20202021 +user@computer1:~/connectedhomeip$ : ./out/chip-tool-app/chip-tool accesscontrol write acl '[{"fabricIndex": 1, "privilege": 5, "authMode": 2, "subjects": [112233], "targets": null}, {"fabricIndex": 1, "privilege": 3, "authMode": 2, "subjects": null, "targets": null}]' 1 0 +``` + +Provision the device and assign node id _2_: + +``` +user@computer1:~/connectedhomeip$ : ./out/chip-tool-app/chip-tool pairing ble-thread 2 hex: 20202021 3840 +``` + +Start the OTA process: + +``` +user@computer1:~/connectedhomeip$ : ./out/chip-tool-app/chip-tool otasoftwareupdaterequestor announce-ota-provider 1 0 0 0 2 0 +``` + +### Known issues + +- SRP cache on the openthread border router needs to flushed each time a new + commissioning process is attempted. For this, factory reset the device, then + execute _ot-ctl server disable_ followed by _ot-ctl server enable_. After + this step, the commissioning process of the device can start; +- Due to some MDNS issues, the commissioning of the OTA Provider Application + may fail. Please make sure that the SRP cache is disabled (_ot-ctl srp + server disable_) on the openthread border router while commissioning the OTA + Provider Application; +- No other Docker image should be running (e.g.: Docker image needed by Test + Harness) except the OTBR one. A docker image can be killed using the + command: + +``` +user@computer1:~/connectedhomeip$ : sudo docker kill $container_id +``` + +- In order to avoid MDNS issues, only one interface should be active at one + time. E.g.: if WiFi is used then disable the Ethernet interface and also + disable multicast on that interface: + +``` +user@computer1:~/connectedhomeip$ sudo ip link set dev eth0 down +user@computer1:~/connectedhomeip$ sudo ifconfig eth0 -multicast +``` + +- If OTBR Docker image is used, then the "-B" parameter should point to the + interface used for the backbone. + +- If Wi-Fi is used on a RPI4, then a 5Ghz network should be selected. + Otherwise, issues related to BLE-WiFi combo may appear. diff --git a/examples/lighting-app/nxp/k32w/k32w1/args.gni b/examples/lighting-app/nxp/k32w/k32w1/args.gni new file mode 100644 index 00000000000000..4efb6421f5ca02 --- /dev/null +++ b/examples/lighting-app/nxp/k32w/k32w1/args.gni @@ -0,0 +1,23 @@ +# Copyright (c) 2020 Project CHIP Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import("//build_overrides/chip.gni") +import("${chip_root}/examples/platform/nxp/k32w/k32w1/args.gni") + +# SDK target. This is overridden to add our SDK app_config.h & defines. +k32w1_sdk_target = get_label_info(":sdk", "label_no_toolchain") + +chip_enable_ota_requestor = true +chip_stack_lock_tracking = "fatal" +chip_enable_ble = true diff --git a/examples/lighting-app/nxp/k32w/k32w1/build_overrides b/examples/lighting-app/nxp/k32w/k32w1/build_overrides new file mode 120000 index 00000000000000..ad07557834803a --- /dev/null +++ b/examples/lighting-app/nxp/k32w/k32w1/build_overrides @@ -0,0 +1 @@ +../../../../build_overrides/ \ No newline at end of file diff --git a/examples/lighting-app/nxp/k32w/k32w1/include/CHIPProjectConfig.h b/examples/lighting-app/nxp/k32w/k32w1/include/CHIPProjectConfig.h new file mode 100644 index 00000000000000..07d8b6a92f5e69 --- /dev/null +++ b/examples/lighting-app/nxp/k32w/k32w1/include/CHIPProjectConfig.h @@ -0,0 +1,176 @@ +/* + * Copyright (c) 2020 Project CHIP Authors + * Copyright (c) 2020 Google LLC. + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file + * Example project configuration file for CHIP. + * + * This is a place to put application or project-specific overrides + * to the default configuration values for general CHIP features. + * + */ + +#pragma once + +// Security and Authentication disabled for development build. +// For convenience, enable CHIP Security Test Mode and disable the requirement for +// authentication in various protocols. +// WARNING: These options make it possible to circumvent basic CHIP security functionality, +// including message encryption. Because of this they MUST NEVER BE ENABLED IN PRODUCTION BUILDS. +#define CHIP_CONFIG_SECURITY_TEST_MODE 0 + +/** + * CHIP_DEVICE_CONFIG_DEVICE_VENDOR_ID + * + * 0xFFF1: Test vendor. + */ +#define CHIP_DEVICE_CONFIG_DEVICE_VENDOR_ID 0xFFF1 + +/** + * CHIP_DEVICE_CONFIG_DEVICE_PRODUCT_ID + * + * 0x8005: example lighting-app + */ +#define CHIP_DEVICE_CONFIG_DEVICE_PRODUCT_ID 0x8005 + +// Use a default setup PIN code if one hasn't been provisioned in flash. +#define CHIP_DEVICE_CONFIG_USE_TEST_SETUP_PIN_CODE 20202021 +#define CHIP_DEVICE_CONFIG_USE_TEST_SETUP_DISCRIMINATOR 0xF00 + +// Use a default pairing code if one hasn't been provisioned in flash. +#define CHIP_DEVICE_CONFIG_USE_TEST_PAIRING_CODE "CHIPUS" + +/** + * CHIP_DEVICE_CONFIG_TEST_SERIAL_NUMBER + * + * Enables the use of a hard-coded default serial number if none + * is found in CHIP NV storage. + */ +#define CHIP_DEVICE_CONFIG_TEST_SERIAL_NUMBER "TEST_SN" + +/** + * CHIP_DEVICE_CONFIG_DEVICE_HARDWARE_VERSION + * + * The hardware version number assigned to device or product by the device vendor. This + * number is scoped to the device product id, and typically corresponds to a revision of the + * physical device, a change to its packaging, and/or a change to its marketing presentation. + * This value is generally *not* incremented for device software versions. + */ +#define CHIP_DEVICE_CONFIG_DEVICE_HARDWARE_VERSION 100 + +#ifndef CHIP_DEVICE_CONFIG_DEFAULT_DEVICE_HARDWARE_VERSION_STRING +#define CHIP_DEVICE_CONFIG_DEFAULT_DEVICE_HARDWARE_VERSION_STRING "v0.1.0" +#endif + +/** + * CHIP_DEVICE_CONFIG_DEVICE_SOFTWARE_VERSION_STRING + * + * A string identifying the software version running on the device. + * CHIP currently expects the software version to be in the format + * {MAJOR_VERSION}.0d{MINOR_VERSION} + */ +#ifndef CHIP_DEVICE_CONFIG_DEVICE_SOFTWARE_VERSION_STRING +#define CHIP_DEVICE_CONFIG_DEVICE_SOFTWARE_VERSION_STRING "03-2022-te8" +#endif + +#ifndef CHIP_DEVICE_CONFIG_DEVICE_SOFTWARE_VERSION +#define CHIP_DEVICE_CONFIG_DEVICE_SOFTWARE_VERSION 42020 +#endif + +#ifndef CHIP_DEVICE_CONFIG_DEVICE_VENDOR_NAME +#define CHIP_DEVICE_CONFIG_DEVICE_VENDOR_NAME "NXP Semiconductors" +#endif + +#ifndef CHIP_DEVICE_CONFIG_DEVICE_PRODUCT_NAME +#define CHIP_DEVICE_CONFIG_DEVICE_PRODUCT_NAME "NXP Demo App" +#endif + +/** + * CHIP_DEVICE_CONFIG_ENABLE_CHIP_TIME_SERVICE_TIME_SYNC + * + * Enables synchronizing the device's real time clock with a remote CHIP Time service + * using the CHIP Time Sync protocol. + */ +// #define CHIP_DEVICE_CONFIG_ENABLE_CHIP_TIME_SERVICE_TIME_SYNC 1 + +/** + * CHIP_DEVICE_CONFIG_BLE_FAST_ADVERTISING_TIMEOUT + * + * The amount of time in miliseconds after which BLE should change his advertisements + * from fast interval to slow interval. + * + * 30000 (30 secondes). + */ +#define CHIP_DEVICE_CONFIG_BLE_FAST_ADVERTISING_TIMEOUT (30 * 1000) + +/** + * CHIP_DEVICE_CONFIG_BLE_ADVERTISING_TIMEOUT + * + * The amount of time in miliseconds after which BLE advertisement should be disabled, counting + * from the moment of slow advertisement commencement. + * + * Defaults to 9000000 (15 minutes). + */ +#define CHIP_DEVICE_CONFIG_BLE_ADVERTISING_TIMEOUT (15 * 60 * 1000) + +/** + * CONFIG_CHIP_NFC_COMMISSIONING, CHIP_DEVICE_CONFIG_ENABLE_NFC + * + * NFC commissioning is not supported on K32W1 + */ +#define CONFIG_CHIP_NFC_COMMISSIONING 0 +#define CHIP_DEVICE_CONFIG_ENABLE_NFC 0 + +/** + * @def CHIP_CONFIG_MAX_FABRICS + * + * @brief + * Maximum number of fabrics the device can participate in. Each fabric can + * provision the device with its unique operational credentials and manage + * its own access control lists. + */ +#define CHIP_CONFIG_MAX_FABRICS 5 // 5 is the minimum number of supported fabrics + +/** + * @def CHIP_IM_MAX_NUM_COMMAND_HANDLER + * + * @brief Defines the maximum number of CommandHandler, limits the number of active commands transactions on server. + */ +#define CHIP_IM_MAX_NUM_COMMAND_HANDLER 2 + +/** + * @def CHIP_IM_MAX_NUM_WRITE_HANDLER + * + * @brief Defines the maximum number of WriteHandler, limits the number of active write transactions on server. + */ +#define CHIP_IM_MAX_NUM_WRITE_HANDLER 2 + +/** + * CHIP_CONFIG_EVENT_LOGGING_DEFAULT_IMPORTANCE + * + * For a development build, set the default importance of events to be logged as Debug. + * Since debug is the lowest importance level, this means all standard, critical, info and + * debug importance level vi events get logged. + */ +#if BUILD_RELEASE +#define CHIP_CONFIG_EVENT_LOGGING_DEFAULT_IMPORTANCE chip::Profiles::DataManagement::Production +#else +#define CHIP_CONFIG_EVENT_LOGGING_DEFAULT_IMPORTANCE chip::Profiles::DataManagement::Debug +#endif // BUILD_RELEASE + +#define CHIP_DEVICE_CONFIG_ENABLE_EXTENDED_DISCOVERY 1 diff --git a/examples/lighting-app/nxp/k32w/k32w1/include/FreeRTOSConfig.h b/examples/lighting-app/nxp/k32w/k32w1/include/FreeRTOSConfig.h new file mode 100644 index 00000000000000..95279e6337a7f7 --- /dev/null +++ b/examples/lighting-app/nxp/k32w/k32w1/include/FreeRTOSConfig.h @@ -0,0 +1,181 @@ +/* + * FreeRTOS Kernel V10.2.0 + * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * http://www.FreeRTOS.org + * http://aws.amazon.com/freertos + * + * 1 tab == 4 spaces! + */ + +#pragma once + +/*----------------------------------------------------------- + * Application specific definitions. + * + * These definitions should be adjusted for your particular hardware and + * application requirements. + * + * THESE PARAMETERS ARE DESCRIBED WITHIN THE 'CONFIGURATION' SECTION OF THE + * FreeRTOS API DOCUMENTATION AVAILABLE ON THE FreeRTOS.org WEB SITE. + * + * See http://www.freertos.org/a00110.html. + *----------------------------------------------------------*/ + +#define configUSE_PREEMPTION 1 +#define configUSE_TICKLESS_IDLE 0 +/* Ensure stdint is only used by the compiler, and not the assembler. */ +#if defined(__ICCARM__) || defined(__ARMCC_VERSION) || defined(__GNUC__) +#include +extern uint32_t SystemCoreClock; +#endif +#define configCPU_CLOCK_HZ (SystemCoreClock) +#define configTICK_RATE_HZ ((TickType_t) 100) +#define configMAX_PRIORITIES (8) +// idle task stack size needs to be increased for OTA EEPROM processing +#define configMINIMAL_STACK_SIZE ((unsigned short) 450) +#define configMAX_TASK_NAME_LEN 20 +#define configUSE_16_BIT_TICKS 0 +#define configIDLE_SHOULD_YIELD 1 +#define configUSE_TASK_NOTIFICATIONS 1 +#define configUSE_MUTEXES 1 +#define configUSE_RECURSIVE_MUTEXES 1 +#define configUSE_COUNTING_SEMAPHORES 1 +#define configUSE_ALTERNATIVE_API 0 /* Deprecated! */ +#define configQUEUE_REGISTRY_SIZE 8 +#define configUSE_QUEUE_SETS 0 +#define configUSE_TIME_SLICING 0 +#define configUSE_NEWLIB_REENTRANT 0 +#define configENABLE_BACKWARD_COMPATIBILITY 1 +#define configNUM_THREAD_LOCAL_STORAGE_POINTERS 5 + +/* Tasks.c additions (e.g. Thread Aware Debug capability) */ +#define configINCLUDE_FREERTOS_TASK_C_ADDITIONS_H 1 + +/* Used memory allocation (heap_x.c) */ +#define configFRTOS_MEMORY_SCHEME 4 + +/* Memory allocation related definitions. */ +#define configSUPPORT_STATIC_ALLOCATION 0 +#define configSUPPORT_DYNAMIC_ALLOCATION 1 +#define configTOTAL_HEAP_SIZE ((size_t) (gTotalHeapSize_c)) +#define configAPPLICATION_ALLOCATED_HEAP 1 + +/* Hook function related definitions. */ +#ifndef configUSE_IDLE_HOOK +#define configUSE_IDLE_HOOK 1 +#endif +#define configUSE_TICK_HOOK 0 +#define configCHECK_FOR_STACK_OVERFLOW 0 +#ifndef configUSE_MALLOC_FAILED_HOOK +#define configUSE_MALLOC_FAILED_HOOK 0 +#endif +#define configUSE_DAEMON_TASK_STARTUP_HOOK 0 + +/* Run time and task stats gathering related definitions. */ +#define configGENERATE_RUN_TIME_STATS 0 +#define configUSE_TRACE_FACILITY 1 +#define configUSE_STATS_FORMATTING_FUNCTIONS 0 + +/* Task aware debugging. */ +#define configRECORD_STACK_HIGH_ADDRESS 1 + +/* Co-routine related definitions. */ +#define configUSE_CO_ROUTINES 0 +#define configMAX_CO_ROUTINE_PRIORITIES 2 + +/* Software timer related definitions. */ +#define configUSE_TIMERS 1 +#define configTIMER_TASK_PRIORITY (configMAX_PRIORITIES - 1) +#define configTIMER_QUEUE_LENGTH 10 +#define configTIMER_TASK_STACK_DEPTH (360) + +/* Define to trap errors during development. */ +#define configASSERT(x) \ + if ((x) == 0) \ + { \ + taskDISABLE_INTERRUPTS(); \ + for (;;) \ + ; \ + } + +/* Optional functions - most linkers will remove unused functions anyway. */ +#define INCLUDE_vTaskPrioritySet 1 +#define INCLUDE_uxTaskPriorityGet 1 +#define INCLUDE_vTaskDelete 1 +#define INCLUDE_vTaskSuspend 1 +#define INCLUDE_xResumeFromISR 1 +#define INCLUDE_vTaskDelayUntil 1 +#define INCLUDE_vTaskDelay 1 +#define INCLUDE_xTaskGetSchedulerState 1 +#define INCLUDE_xTaskGetCurrentTaskHandle 1 +#define INCLUDE_uxTaskGetStackHighWaterMark 1 +#define INCLUDE_xTaskGetIdleTaskHandle 0 +#define INCLUDE_eTaskGetState 0 +#define INCLUDE_xEventGroupSetBitFromISR 1 +#define INCLUDE_xTimerPendFunctionCall 1 +#define INCLUDE_xTaskAbortDelay 0 +#define INCLUDE_xTaskGetHandle 0 +#define INCLUDE_xTaskResumeFromISR 1 +#define INCLUDE_xQueueGetMutexHolder 1 + +/* Interrupt nesting behaviour configuration. Cortex-M specific. */ +#ifdef __NVIC_PRIO_BITS +/* __BVIC_PRIO_BITS will be specified when CMSIS is being used. */ +#define configPRIO_BITS __NVIC_PRIO_BITS +#else +#define configPRIO_BITS 3 +#endif + +/* The lowest interrupt priority that can be used in a call to a "set priority" +function. */ +#define configLIBRARY_LOWEST_INTERRUPT_PRIORITY 0x7 + +/* The highest interrupt priority that can be used by any interrupt service +routine that makes calls to interrupt safe FreeRTOS API functions. DO NOT CALL +INTERRUPT SAFE FREERTOS API FUNCTIONS FROM ANY INTERRUPT THAT HAS A HIGHER +PRIORITY THAN THIS! (higher priorities are lower numeric values. */ +#define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 1 + +/* Interrupt priorities used by the kernel port layer itself. These are generic +to all Cortex-M ports, and do not rely on any particular library functions. */ +#define configKERNEL_INTERRUPT_PRIORITY (configLIBRARY_LOWEST_INTERRUPT_PRIORITY << (8 - configPRIO_BITS)) +/* !!!! configMAX_SYSCALL_INTERRUPT_PRIORITY must not be set to zero !!!! +See http://www.FreeRTOS.org/RTOS-Cortex-M3-M4.html. */ +#define configMAX_SYSCALL_INTERRUPT_PRIORITY (configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY << (8 - configPRIO_BITS)) + +#ifndef configENABLE_FPU +#define configENABLE_FPU 0 +#endif +#ifndef configENABLE_MPU +#define configENABLE_MPU 0 +#endif +#ifndef configENABLE_TRUSTZONE +#define configENABLE_TRUSTZONE 0 +#endif +#ifndef configRUN_FREERTOS_SECURE_ONLY +#define configRUN_FREERTOS_SECURE_ONLY 1 +#endif + +/* Definitions that map the FreeRTOS port interrupt handlers to their CMSIS +standard names. */ +#define vPortSVCHandler SVC_Handler +#define xPortPendSVHandler PendSV_Handler +#define xPortSysTickHandler SysTick_Handler diff --git a/examples/lighting-app/nxp/k32w/k32w1/main/AppTask.cpp b/examples/lighting-app/nxp/k32w/k32w1/main/AppTask.cpp new file mode 100644 index 00000000000000..66eee725c581c2 --- /dev/null +++ b/examples/lighting-app/nxp/k32w/k32w1/main/AppTask.cpp @@ -0,0 +1,870 @@ +/* + * + * Copyright (c) 2021 Project CHIP Authors + * Copyright (c) 2021 Google LLC. + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "AppTask.h" +#include "AppEvent.h" +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +/* OTA related includes */ +#if CHIP_DEVICE_CONFIG_ENABLE_OTA_REQUESTOR +#include "OtaSupport.h" +#include +#include +#include +#include +#include +#endif + +#include "K32W1PersistentStorageOpKeystore.h" + +#include "LEDWidget.h" +#include "app.h" +#include "app_config.h" +#include "fsl_component_button.h" +#include "fwk_platform.h" + +#define FACTORY_RESET_TRIGGER_TIMEOUT 6000 +#define FACTORY_RESET_CANCEL_WINDOW_TIMEOUT 3000 +#define APP_TASK_PRIORITY 2 +#define APP_EVENT_QUEUE_SIZE 10 + +TimerHandle_t sFunctionTimer; // FreeRTOS app sw timer. + +static QueueHandle_t sAppEventQueue; + +/* + * The status LED and the external flash CS pin are wired together. + * The OTA image writing may fail if used together. + */ +#ifndef CHIP_DEVICE_CONFIG_ENABLE_OTA_REQUESTOR +static LEDWidget sStatusLED; +#endif +static LEDWidget sLightLED; + +static bool sIsThreadProvisioned = false; +#ifndef CHIP_DEVICE_CONFIG_ENABLE_OTA_REQUESTOR +static bool sHaveFullConnectivity = false; +#endif +static bool sHaveBLEConnections = false; + +#if CHIP_DEVICE_CONFIG_THREAD_ENABLE_CLI +extern "C" void otPlatUartProcess(void); +#endif + +using namespace ::chip::Credentials; +using namespace ::chip::DeviceLayer; +using namespace chip; +using namespace chip::app; + +AppTask AppTask::sAppTask; + +// This key is for testing/certification only and should not be used in production devices. +// For production devices this key must be provided from factory data. +uint8_t sTestEventTriggerEnableKey[TestEventTriggerDelegate::kEnableKeyLength] = { 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, + 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff }; + +static Identify gIdentify = { chip::EndpointId{ 1 }, AppTask::OnIdentifyStart, AppTask::OnIdentifyStop, + Clusters::Identify::IdentifyTypeEnum::kVisibleIndicator, AppTask::OnTriggerEffect, + // Use invalid value for identifiers to enable TriggerEffect command + // to stop Identify command for each effect + Clusters::Identify::EffectIdentifierEnum::kUnknownEnumValue, + Clusters::Identify::EffectVariantEnum::kDefault }; + +/* OTA related variables */ +#if CHIP_DEVICE_CONFIG_ENABLE_OTA_REQUESTOR +static DefaultOTARequestor gRequestorCore; +static DefaultOTARequestorStorage gRequestorStorage; +static DeviceLayer::DefaultOTARequestorDriver gRequestorUser; +static BDXDownloader gDownloader; + +constexpr uint16_t requestedOtaBlockSize = 1024; +#endif + +CHIP_ERROR AppTask::StartAppTask() +{ + CHIP_ERROR err = CHIP_NO_ERROR; + + sAppEventQueue = xQueueCreate(APP_EVENT_QUEUE_SIZE, sizeof(AppEvent)); + if (sAppEventQueue == NULL) + { + err = APP_ERROR_EVENT_QUEUE_FAILED; + K32W_LOG("Failed to allocate app event queue"); + assert(err == CHIP_NO_ERROR); + } + + return err; +} + +CHIP_ERROR AppTask::Init() +{ + CHIP_ERROR err = CHIP_NO_ERROR; + + PlatformMgr().AddEventHandler(MatterEventHandler, 0); + + // Init ZCL Data Model and start server + PlatformMgr().ScheduleWork(InitServer, 0); + + // Initialize device attestation config + SetDeviceAttestationCredentialsProvider(Examples::GetExampleDACProvider()); + + // QR code will be used with CHIP Tool + AppTask::PrintOnboardingInfo(); + + if (LightingMgr().Init() != 0) + { + K32W_LOG("LightingMgr().Init() failed"); + assert(0); + } + + LightingMgr().SetCallbacks(ActionInitiated, ActionCompleted); + + /* start with all LEDS turnedd off */ +#ifndef CHIP_DEVICE_CONFIG_ENABLE_OTA_REQUESTOR + sStatusLED.Init(SYSTEM_STATE_LED, false); +#endif + sLightLED.Init(LIGHT_STATE_LED, false); + UpdateDeviceState(); + + /* intialize the Keyboard and button press callback */ + BUTTON_InstallCallback((button_handle_t) g_buttonHandle[0], KBD_Callback, (void *) BLE_BUTTON); + BUTTON_InstallCallback((button_handle_t) g_buttonHandle[1], KBD_Callback, (void *) LIGHT_BUTTON); + + // Create FreeRTOS sw timer for Function Selection. + sFunctionTimer = xTimerCreate("FnTmr", // Just a text name, not used by the RTOS kernel + 1, // == default timer period (mS) + false, // no timer reload (==one-shot) + (void *) this, // init timer id = app task obj context + TimerEventHandler // timer callback handler + ); + + if (sFunctionTimer == NULL) + { + err = APP_ERROR_CREATE_TIMER_FAILED; + K32W_LOG("app_timer_create() failed"); + assert(err == CHIP_NO_ERROR); + } + + // Print the current software version + char currentSoftwareVer[ConfigurationManager::kMaxSoftwareVersionStringLength + 1] = { 0 }; + err = ConfigurationMgr().GetSoftwareVersionString(currentSoftwareVer, sizeof(currentSoftwareVer)); + if (err != CHIP_NO_ERROR) + { + K32W_LOG("Get version error"); + assert(err == CHIP_NO_ERROR); + } + + uint32_t currentVersion; + err = ConfigurationMgr().GetSoftwareVersion(currentVersion); + + K32W_LOG("Current Software Version: %s, %d", currentSoftwareVer, currentVersion); + + return err; +} + +void LockOpenThreadTask(void) +{ + chip::DeviceLayer::ThreadStackMgr().LockThreadStack(); +} + +void UnlockOpenThreadTask(void) +{ + chip::DeviceLayer::ThreadStackMgr().UnlockThreadStack(); +} + +void AppTask::InitServer(intptr_t arg) +{ + static chip::CommonCaseDeviceServerInitParams initParams; + (void) initParams.InitializeStaticResourcesBeforeServerInit(); + +#if CHIP_CRYPTO_PLATFORM + static chip::K32W1PersistentStorageOpKeystore sK32W1PersistentStorageOpKeystore; + VerifyOrDie((sK32W1PersistentStorageOpKeystore.Init(initParams.persistentStorageDelegate)) == CHIP_NO_ERROR); + initParams.operationalKeystore = &sK32W1PersistentStorageOpKeystore; +#endif + + // Init ZCL Data Model and start server + static DefaultTestEventTriggerDelegate testEventTriggerDelegate{ ByteSpan(sTestEventTriggerEnableKey) }; + initParams.testEventTriggerDelegate = &testEventTriggerDelegate; + chip::Inet::EndPointStateOpenThread::OpenThreadEndpointInitParam nativeParams; + nativeParams.lockCb = LockOpenThreadTask; + nativeParams.unlockCb = UnlockOpenThreadTask; + nativeParams.openThreadInstancePtr = chip::DeviceLayer::ThreadStackMgrImpl().OTInstance(); + initParams.endpointNativeParams = static_cast(&nativeParams); + VerifyOrDie((chip::Server::GetInstance().Init(initParams)) == CHIP_NO_ERROR); +} + +void AppTask::PrintOnboardingInfo() +{ + chip::PayloadContents payload; + CHIP_ERROR err = GetPayloadContents(payload, chip::RendezvousInformationFlags(chip::RendezvousInformationFlag::kBLE)); + if (err != CHIP_NO_ERROR) + { + ChipLogError(AppServer, "GetPayloadContents() failed: %" CHIP_ERROR_FORMAT, err.Format()); + } + payload.commissioningFlow = chip::CommissioningFlow::kUserActionRequired; + PrintOnboardingCodes(payload); +} + +#if CHIP_DEVICE_CONFIG_ENABLE_OTA_REQUESTOR +void AppTask::InitOTA(intptr_t arg) +{ + // Initialize and interconnect the Requestor and Image Processor objects -- START + SetRequestorInstance(&gRequestorCore); + + gRequestorStorage.Init(chip::Server::GetInstance().GetPersistentStorage()); + gRequestorCore.Init(chip::Server::GetInstance(), gRequestorStorage, gRequestorUser, gDownloader); + gRequestorUser.SetMaxDownloadBlockSize(requestedOtaBlockSize); + auto & imageProcessor = OTAImageProcessorImpl::GetDefaultInstance(); + gRequestorUser.Init(&gRequestorCore, &imageProcessor); + CHIP_ERROR err = imageProcessor.Init(&gDownloader); + if (err != CHIP_NO_ERROR) + { + K32W_LOG("Image processor init failed"); + assert(err == CHIP_NO_ERROR); + } + + // Connect the gDownloader and Image Processor objects + gDownloader.SetImageProcessorDelegate(&imageProcessor); + // Initialize and interconnect the Requestor and Image Processor objects -- END +} +#endif + +void AppTask::AppTaskMain(void * pvParameter) +{ + AppEvent event; + + CHIP_ERROR err = sAppTask.Init(); + if (err != CHIP_NO_ERROR) + { + K32W_LOG("AppTask.Init() failed"); + assert(err == CHIP_NO_ERROR); + } + + while (true) + { + BaseType_t eventReceived = xQueueReceive(sAppEventQueue, &event, pdMS_TO_TICKS(10)); + while (eventReceived == pdTRUE) + { + sAppTask.DispatchEvent(&event); + eventReceived = xQueueReceive(sAppEventQueue, &event, 0); + } + + // Collect connectivity and configuration state from the CHIP stack. Because the + // CHIP event loop is being run in a separate task, the stack must be locked + // while these values are queried. However we use a non-blocking lock request + // (TryLockChipStack()) to avoid blocking other UI activities when the CHIP + // task is busy (e.g. with a long crypto operation). + if (PlatformMgr().TryLockChipStack()) + { +#if CHIP_DEVICE_CONFIG_THREAD_ENABLE_CLI + otPlatUartProcess(); +#endif + + sIsThreadProvisioned = ConnectivityMgr().IsThreadProvisioned(); + sHaveBLEConnections = (ConnectivityMgr().NumBLEConnections() != 0); + PlatformMgr().UnlockChipStack(); + } + + // Update the status LED if factory reset or identify process have not been initiated. + // + // If system has "full connectivity", keep the LED On constantly. + // + // If thread and service provisioned, but not attached to the thread network yet OR no + // connectivity to the service OR subscriptions are not fully established + // THEN blink the LED Off for a short period of time. + // + // If the system has ble connection(s) uptill the stage above, THEN blink the LEDs at an even + // rate of 100ms. + // + // Otherwise, blink the LED ON for a very short time. + if (sAppTask.mFunction != kFunction_FactoryReset) + { +#ifndef CHIP_DEVICE_CONFIG_ENABLE_OTA_REQUESTOR + if (sHaveFullConnectivity) + { + sStatusLED.Set(true); + } + else if (sIsThreadProvisioned) + { + sStatusLED.Blink(950, 50); + } + else if (sHaveBLEConnections) + { + sStatusLED.Blink(100, 100); + } + else + { + sStatusLED.Blink(50, 950); + } +#endif + } + +#ifndef CHIP_DEVICE_CONFIG_ENABLE_OTA_REQUESTOR + sStatusLED.Animate(); +#endif + sLightLED.Animate(); + } +} + +void AppTask::ButtonEventHandler(uint8_t pin_no, uint8_t button_action) +{ + if ((pin_no != RESET_BUTTON) && (pin_no != LIGHT_BUTTON) && (pin_no != OTA_BUTTON) && (pin_no != BLE_BUTTON)) + { + return; + } + + AppEvent button_event; + button_event.Type = AppEvent::kEventType_Button; + button_event.ButtonEvent.PinNo = pin_no; + button_event.ButtonEvent.Action = button_action; + + if (pin_no == LIGHT_BUTTON) + { + button_event.Handler = LightActionEventHandler; + } + else if (pin_no == OTA_BUTTON) + { + // button_event.Handler = OTAHandler; + } + else if (pin_no == BLE_BUTTON) + { + button_event.Handler = BleHandler; + + if (button_action == RESET_BUTTON_PUSH) + { + button_event.Handler = ResetActionEventHandler; + } + } + sAppTask.PostEvent(&button_event); +} + +button_status_t AppTask::KBD_Callback(void * buttonHandle, button_callback_message_t * message, void * callbackParam) +{ + uint32_t pinNb = (uint32_t) callbackParam; + switch (message->event) + { + case kBUTTON_EventOneClick: + case kBUTTON_EventShortPress: + switch (pinNb) + { + case BLE_BUTTON: + K32W_LOG("pb1 short press"); + if (sAppTask.mResetTimerActive) + { + ButtonEventHandler(BLE_BUTTON, RESET_BUTTON_PUSH); + } + else + { + ButtonEventHandler(BLE_BUTTON, BLE_BUTTON_PUSH); + } + break; + + case LIGHT_BUTTON: + K32W_LOG("pb2 short press"); + ButtonEventHandler(LIGHT_BUTTON, LIGHT_BUTTON_PUSH); + break; + } + break; + + case kBUTTON_EventLongPress: + switch (pinNb) + { + case BLE_BUTTON: + K32W_LOG("pb1 long press"); + ButtonEventHandler(BLE_BUTTON, RESET_BUTTON_PUSH); + break; + + case LIGHT_BUTTON: + K32W_LOG("pb2 long press"); + ButtonEventHandler(OTA_BUTTON, OTA_BUTTON_PUSH); + break; + } + break; + + default: + /* No action required */ + break; + } + return kStatus_BUTTON_Success; +} + +void AppTask::TimerEventHandler(TimerHandle_t xTimer) +{ + AppEvent event; + event.Type = AppEvent::kEventType_Timer; + event.TimerEvent.Context = (void *) xTimer; + event.Handler = FunctionTimerEventHandler; + sAppTask.PostEvent(&event); +} + +void AppTask::FunctionTimerEventHandler(AppEvent * aEvent) +{ + if (aEvent->Type != AppEvent::kEventType_Timer) + return; + + K32W_LOG("Device will factory reset..."); + + // Actually trigger Factory Reset + chip::Server::GetInstance().ScheduleFactoryReset(); +} + +void AppTask::ResetActionEventHandler(AppEvent * aEvent) +{ + if (aEvent->ButtonEvent.PinNo != RESET_BUTTON && aEvent->ButtonEvent.PinNo != BLE_BUTTON) + return; + + if (sAppTask.mResetTimerActive) + { + sAppTask.CancelTimer(); + sAppTask.mFunction = kFunction_NoneSelected; + + RestoreLightingState(); + + K32W_LOG("Factory Reset was cancelled!"); + } + else + { + uint32_t resetTimeout = FACTORY_RESET_TRIGGER_TIMEOUT; + + if (sAppTask.mFunction != kFunction_NoneSelected) + { + K32W_LOG("Another function is scheduled. Could not initiate Factory Reset!"); + return; + } + + K32W_LOG("Factory Reset Triggered. Push the RESET button within %lu ms to cancel!", resetTimeout); + sAppTask.mFunction = kFunction_FactoryReset; + + /* LEDs will start blinking to signal that a Factory Reset was scheduled */ +#ifndef CHIP_DEVICE_CONFIG_ENABLE_OTA_REQUESTOR + sStatusLED.Set(false); +#endif + sLightLED.Set(false); + +#ifndef CHIP_DEVICE_CONFIG_ENABLE_OTA_REQUESTOR + sStatusLED.Blink(500); +#endif + sLightLED.Blink(500); + + sAppTask.StartTimer(FACTORY_RESET_TRIGGER_TIMEOUT); + } +} + +void AppTask::LightActionEventHandler(AppEvent * aEvent) +{ + LightingManager::Action_t action; + CHIP_ERROR err = CHIP_NO_ERROR; + int32_t actor = 0; + bool initiated = false; + + if (sAppTask.mFunction != kFunction_NoneSelected) + { + K32W_LOG("Another function is scheduled. Could not initiate ON/OFF Light command!"); + return; + } + + if (aEvent->Type == AppEvent::kEventType_TurnOn) + { + action = static_cast(aEvent->LightEvent.Action); + actor = aEvent->LightEvent.Actor; + } + else if (aEvent->Type == AppEvent::kEventType_Button) + { + actor = AppEvent::kEventType_Button; + + if (LightingMgr().IsTurnedOff()) + { + action = LightingManager::TURNON_ACTION; + } + else + { + action = LightingManager::TURNOFF_ACTION; + } + } + else + { + err = APP_ERROR_UNHANDLED_EVENT; + action = LightingManager::INVALID_ACTION; + } + + if (err == CHIP_NO_ERROR) + { + initiated = LightingMgr().InitiateAction(actor, action); + + if (!initiated) + { + K32W_LOG("Action is already in progress or active."); + } + } +} + +void AppTask::OTAHandler(AppEvent * aEvent) +{ + if (aEvent->ButtonEvent.PinNo != OTA_BUTTON) + return; + +#if CHIP_DEVICE_CONFIG_ENABLE_OTA_REQUESTOR + if (sAppTask.mFunction != kFunction_NoneSelected) + { + K32W_LOG("Another function is scheduled. Could not initiate OTA!"); + return; + } + + PlatformMgr().ScheduleWork(StartOTAQuery, 0); +#endif +} + +#if CHIP_DEVICE_CONFIG_ENABLE_OTA_REQUESTOR +void AppTask::StartOTAQuery(intptr_t arg) +{ + GetRequestorInstance()->TriggerImmediateQuery(); +} +#endif + +void AppTask::BleHandler(AppEvent * aEvent) +{ + if (aEvent->ButtonEvent.PinNo != BLE_BUTTON) + return; + + if (sAppTask.mFunction != kFunction_NoneSelected) + { + K32W_LOG("Another function is scheduled. Could not toggle BLE state!"); + return; + } + PlatformMgr().ScheduleWork(AppTask::BleStartAdvertising, 0); +} + +void AppTask::BleStartAdvertising(intptr_t arg) +{ + if (ConnectivityMgr().IsBLEAdvertisingEnabled()) + { + ConnectivityMgr().SetBLEAdvertisingEnabled(false); + K32W_LOG("Stopped BLE Advertising!"); + } + else + { + ConnectivityMgr().SetBLEAdvertisingEnabled(true); + if (chip::Server::GetInstance().GetCommissioningWindowManager().OpenBasicCommissioningWindow() == CHIP_NO_ERROR) + { + K32W_LOG("Started BLE Advertising!"); + } + else + { + K32W_LOG("OpenBasicCommissioningWindow() failed"); + } + } +} + +void AppTask::MatterEventHandler(const ChipDeviceEvent * event, intptr_t) +{ +#if CHIP_DEVICE_CONFIG_ENABLE_OTA_REQUESTOR + if (event->Type == DeviceEventType::kDnssdInitialized) + { + K32W_LOG("Dnssd platform initialized."); + PlatformMgr().ScheduleWork(InitOTA, 0); + } +#else + if (event->Type == DeviceEventType::kDnssdInitialized) + { + sHaveFullConnectivity = TRUE; + } +#endif +} + +void AppTask::CancelTimer() +{ + if (xTimerStop(sFunctionTimer, 0) == pdFAIL) + { + K32W_LOG("app timer stop() failed"); + } + + mResetTimerActive = false; +} + +void AppTask::StartTimer(uint32_t aTimeoutInMs) +{ + if (xTimerIsTimerActive(sFunctionTimer)) + { + K32W_LOG("app timer already started!"); + CancelTimer(); + } + + // timer is not active, change its period to required value (== restart). + // FreeRTOS- Block for a maximum of 100 ticks if the change period command + // cannot immediately be sent to the timer command queue. + if (xTimerChangePeriod(sFunctionTimer, aTimeoutInMs / portTICK_PERIOD_MS, 100) != pdPASS) + { + K32W_LOG("app timer start() failed"); + } + + mResetTimerActive = true; +} + +void AppTask::ActionInitiated(LightingManager::Action_t aAction, int32_t aActor) +{ + // start flashing the LEDs rapidly to indicate action initiation. + if (aAction == LightingManager::TURNON_ACTION) + { + K32W_LOG("Turn on Action has been initiated") + } + else if (aAction == LightingManager::TURNOFF_ACTION) + { + K32W_LOG("Turn off Action has been initiated") + } + + if (aActor == AppEvent::kEventType_Button) + { + sAppTask.mSyncClusterToButtonAction = true; + } + + sAppTask.mFunction = kFunctionTurnOnTurnOff; +} + +void AppTask::ActionCompleted(LightingManager::Action_t aAction) +{ + // Turn on the light LED if in a TURNON state OR + // Turn off the light LED if in a TURNOFF state. + if (aAction == LightingManager::TURNON_ACTION) + { + K32W_LOG("Turn on action has been completed") + sLightLED.Set(true); + } + else if (aAction == LightingManager::TURNOFF_ACTION) + { + K32W_LOG("Turn off action has been completed") + sLightLED.Set(false); + } + + if (sAppTask.mSyncClusterToButtonAction) + { + sAppTask.UpdateClusterState(); + sAppTask.mSyncClusterToButtonAction = false; + } + + sAppTask.mFunction = kFunction_NoneSelected; +} + +void AppTask::RestoreLightingState(void) +{ + /* restore initial state for the LED indicating Lighting state */ + if (LightingMgr().IsTurnedOff()) + { + sLightLED.Set(false); + } + else + { + sLightLED.Set(true); + } +} + +void AppTask::OnIdentifyStart(Identify * identify) +{ + if ((kFunction_NoneSelected != sAppTask.mFunction) && (kFunction_TriggerEffect != sAppTask.mFunction)) + { + K32W_LOG("Another function is scheduled. Could not initiate Identify process!"); + return; + } + + if (kFunction_TriggerEffect == sAppTask.mFunction) + { + chip::DeviceLayer::SystemLayer().CancelTimer(OnTriggerEffectComplete, identify); + OnTriggerEffectComplete(&chip::DeviceLayer::SystemLayer(), identify); + } + + ChipLogProgress(Zcl, "Identify process has started. Status LED should blink with a period of 0.5 seconds."); + sAppTask.mFunction = kFunction_Identify; + sLightLED.Set(false); + sLightLED.Blink(250); +} + +void AppTask::OnIdentifyStop(Identify * identify) +{ + if (kFunction_Identify == sAppTask.mFunction) + { + ChipLogProgress(Zcl, "Identify process has stopped."); + sAppTask.mFunction = kFunction_NoneSelected; + + RestoreLightingState(); + } +} + +void AppTask::OnTriggerEffectComplete(chip::System::Layer * systemLayer, void * appState) +{ + // Let Identify command take over if called during TriggerEffect already running + if (kFunction_TriggerEffect == sAppTask.mFunction) + { + ChipLogProgress(Zcl, "TriggerEffect has stopped."); + sAppTask.mFunction = kFunction_NoneSelected; + + // TriggerEffect finished - reset identifiers + // Use invalid value for identifiers to enable TriggerEffect command + // to stop Identify command for each effect + gIdentify.mCurrentEffectIdentifier = Clusters::Identify::EffectIdentifierEnum::kUnknownEnumValue; + gIdentify.mTargetEffectIdentifier = Clusters::Identify::EffectIdentifierEnum::kUnknownEnumValue; + gIdentify.mEffectVariant = Clusters::Identify::EffectVariantEnum::kDefault; + + RestoreLightingState(); + } +} + +void AppTask::OnTriggerEffect(Identify * identify) +{ + // Allow overlapping TriggerEffect calls + if ((kFunction_NoneSelected != sAppTask.mFunction) && (kFunction_TriggerEffect != sAppTask.mFunction)) + { + K32W_LOG("Another function is scheduled. Could not initiate Identify process!"); + return; + } + + sAppTask.mFunction = kFunction_TriggerEffect; + uint16_t timerDelay = 0; + + ChipLogProgress(Zcl, "TriggerEffect has started."); + + switch (identify->mCurrentEffectIdentifier) + { + case Clusters::Identify::EffectIdentifierEnum::kBlink: + timerDelay = 2; + break; + + case Clusters::Identify::EffectIdentifierEnum::kBreathe: + timerDelay = 15; + break; + + case Clusters::Identify::EffectIdentifierEnum::kOkay: + timerDelay = 4; + break; + + case Clusters::Identify::EffectIdentifierEnum::kChannelChange: + ChipLogProgress(Zcl, "Channel Change effect not supported, using effect %d", + to_underlying(Clusters::Identify::EffectIdentifierEnum::kBlink)); + timerDelay = 2; + break; + + case Clusters::Identify::EffectIdentifierEnum::kFinishEffect: + chip::DeviceLayer::SystemLayer().CancelTimer(OnTriggerEffectComplete, identify); + timerDelay = 1; + break; + + case Clusters::Identify::EffectIdentifierEnum::kStopEffect: + chip::DeviceLayer::SystemLayer().CancelTimer(OnTriggerEffectComplete, identify); + OnTriggerEffectComplete(&chip::DeviceLayer::SystemLayer(), identify); + break; + + default: + ChipLogProgress(Zcl, "Invalid effect identifier."); + } + + if (timerDelay) + { + sLightLED.Set(false); + sLightLED.Blink(500); + + chip::DeviceLayer::SystemLayer().StartTimer(chip::System::Clock::Seconds16(timerDelay), OnTriggerEffectComplete, identify); + } +} + +void AppTask::PostTurnOnActionRequest(int32_t aActor, LightingManager::Action_t aAction) +{ + AppEvent event; + event.Type = AppEvent::kEventType_TurnOn; + event.LightEvent.Actor = aActor; + event.LightEvent.Action = aAction; + event.Handler = LightActionEventHandler; + PostEvent(&event); +} + +void AppTask::PostEvent(const AppEvent * aEvent) +{ + if (sAppEventQueue != NULL) + { + if (!xQueueSend(sAppEventQueue, aEvent, 1)) + { + K32W_LOG("Failed to post event to app task event queue"); + } + } +} + +void AppTask::DispatchEvent(AppEvent * aEvent) +{ + if (aEvent->Handler) + { + aEvent->Handler(aEvent); + } + else + { + K32W_LOG("Event received with no handler. Dropping event."); + } +} + +void AppTask::UpdateClusterState(void) +{ + PlatformMgr().ScheduleWork(UpdateClusterStateInternal, 0); +} + +void AppTask::UpdateClusterStateInternal(intptr_t arg) +{ + uint8_t newValue = !LightingMgr().IsTurnedOff(); + + // write the new on/off value + EmberAfStatus status = app::Clusters::OnOff::Attributes::OnOff::Set(1, newValue); + if (status != EMBER_ZCL_STATUS_SUCCESS) + { + ChipLogError(NotSpecified, "ERR: updating on/off %x", status); + } +} + +void AppTask::UpdateDeviceState(void) +{ + PlatformMgr().ScheduleWork(UpdateDeviceStateInternal, 0); +} + +void AppTask::UpdateDeviceStateInternal(intptr_t arg) +{ + bool onoffAttrValue = 0; + + /* get onoff attribute value */ + (void) app::Clusters::OnOff::Attributes::OnOff::Get(1, &onoffAttrValue); + + /* set the device state */ + sLightLED.Set(onoffAttrValue); + LightingMgr().SetState(onoffAttrValue); +} + +extern "C" void OTAIdleActivities(void) +{ +#if CHIP_DEVICE_CONFIG_ENABLE_OTA_REQUESTOR + OTA_TransactionResume(); +#endif +} diff --git a/examples/lighting-app/nxp/k32w/k32w1/main/LightingManager.cpp b/examples/lighting-app/nxp/k32w/k32w1/main/LightingManager.cpp new file mode 100644 index 00000000000000..7a5cefc9b668fc --- /dev/null +++ b/examples/lighting-app/nxp/k32w/k32w1/main/LightingManager.cpp @@ -0,0 +1,80 @@ +/* + * + * Copyright (c) 2021 Project CHIP Authors + * Copyright (c) 2019 Google LLC. + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "LightingManager.h" + +#include "AppTask.h" +#include "FreeRTOS.h" + +#include "app_config.h" + +LightingManager LightingManager::sLight; + +int LightingManager::Init() +{ + mState = kState_On; + + return 0; +} + +void LightingManager::SetCallbacks(Callback_fn_initiated aActionInitiated_CB, Callback_fn_completed aActionCompleted_CB) +{ + mActionInitiated_CB = aActionInitiated_CB; + mActionCompleted_CB = aActionCompleted_CB; +} + +void LightingManager::SetState(bool state) +{ + mState = state ? kState_On : kState_Off; +} + +bool LightingManager::IsTurnedOff() +{ + return (mState == kState_Off) ? true : false; +} + +bool LightingManager::InitiateAction(int32_t aActor, Action_t aAction) +{ + bool action_initiated = false; + + if (mState == kState_On && aAction == TURNOFF_ACTION) + { + action_initiated = true; + mState = kState_Off; + } + else if (mState == kState_Off && aAction == TURNON_ACTION) + { + action_initiated = true; + mState = kState_On; + } + + if (action_initiated) + { + if (mActionInitiated_CB) + { + mActionInitiated_CB(aAction, aActor); + } + if (mActionCompleted_CB) + { + mActionCompleted_CB(aAction); + } + } + + return action_initiated; +} diff --git a/examples/lighting-app/nxp/k32w/k32w1/main/ZclCallbacks.cpp b/examples/lighting-app/nxp/k32w/k32w1/main/ZclCallbacks.cpp new file mode 100644 index 00000000000000..5a4eee6e2c09e4 --- /dev/null +++ b/examples/lighting-app/nxp/k32w/k32w1/main/ZclCallbacks.cpp @@ -0,0 +1,70 @@ +/* + * + * Copyright (c) 2021 Project CHIP Authors + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include "AppTask.h" +#include "LightingManager.h" + +#include +#include +#include +#include +#include + +using namespace ::chip; +using namespace ::chip::app::Clusters; + +void MatterPostAttributeChangeCallback(const chip::app::ConcreteAttributePath & path, uint8_t type, uint16_t size, uint8_t * value) +{ + if (path.mClusterId == OnOff::Id) + { + if (path.mAttributeId != OnOff::Attributes::OnOff::Id) + { + ChipLogProgress(Zcl, "Unknown attribute ID: " ChipLogFormatMEI, ChipLogValueMEI(path.mAttributeId)); + return; + } + + LightingMgr().InitiateAction(0, *value ? LightingManager::TURNON_ACTION : LightingManager::TURNOFF_ACTION); + } + else if (path.mClusterId == LevelControl::Id) + { + ChipLogProgress(Zcl, "Level Control attribute ID: " ChipLogFormatMEI " Type: %u Value: %u, length %u", + ChipLogValueMEI(path.mAttributeId), type, *value, size); + + // WIP Apply attribute change to Light + } + else if (path.mClusterId == ColorControl::Id) + { + ChipLogProgress(Zcl, "Color Control attribute ID: " ChipLogFormatMEI " Type: %u Value: %u, length %u", + ChipLogValueMEI(path.mAttributeId), type, *value, size); + + // WIP Apply attribute change to Light + } + else if (path.mClusterId == OnOffSwitchConfiguration::Id) + { + ChipLogProgress(Zcl, "OnOff Switch Configuration attribute ID: " ChipLogFormatMEI " Type: %u Value: %u, length %u", + ChipLogValueMEI(path.mAttributeId), type, *value, size); + + // WIP Apply attribute change to Light + } + else + { + ChipLogProgress(Zcl, "Unknown attribute ID: " ChipLogFormatMEI, ChipLogValueMEI(path.mAttributeId)); + } +} diff --git a/examples/lighting-app/nxp/k32w/k32w1/main/include/AppEvent.h b/examples/lighting-app/nxp/k32w/k32w1/main/include/AppEvent.h new file mode 100644 index 00000000000000..902c70b3cb656f --- /dev/null +++ b/examples/lighting-app/nxp/k32w/k32w1/main/include/AppEvent.h @@ -0,0 +1,57 @@ +/* + * + * Copyright (c) 2021 Nest Labs, Inc. + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +struct AppEvent; +typedef void (*EventHandler)(AppEvent *); + +struct AppEvent +{ + enum AppEventTypes + { + kEventType_None = 0, + kEventType_Button, + kEventType_Timer, + kEventType_TurnOn, + kEventType_Install, + kEventType_OTAResume, + }; + + AppEventTypes Type; + + union + { + struct + { + uint8_t PinNo; + uint8_t Action; + } ButtonEvent; + struct + { + void * Context; + } TimerEvent; + struct + { + uint8_t Action; + int32_t Actor; + } LightEvent; + }; + + EventHandler Handler; +}; diff --git a/examples/lighting-app/nxp/k32w/k32w1/main/include/AppTask.h b/examples/lighting-app/nxp/k32w/k32w1/main/include/AppTask.h new file mode 100644 index 00000000000000..db81edf168c41e --- /dev/null +++ b/examples/lighting-app/nxp/k32w/k32w1/main/include/AppTask.h @@ -0,0 +1,119 @@ +/* + * + * Copyright (c) 2021 Google LLC. + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include + +#include "AppEvent.h" +#include "LightingManager.h" + +#include +#include + +#include "FreeRTOS.h" +#include "fsl_component_button.h" +#include "timers.h" + +// Application-defined error codes in the CHIP_ERROR space. +#define APP_ERROR_EVENT_QUEUE_FAILED CHIP_APPLICATION_ERROR(0x01) +#define APP_ERROR_CREATE_TASK_FAILED CHIP_APPLICATION_ERROR(0x02) +#define APP_ERROR_UNHANDLED_EVENT CHIP_APPLICATION_ERROR(0x03) +#define APP_ERROR_CREATE_TIMER_FAILED CHIP_APPLICATION_ERROR(0x04) +#define APP_ERROR_START_TIMER_FAILED CHIP_APPLICATION_ERROR(0x05) +#define APP_ERROR_STOP_TIMER_FAILED CHIP_APPLICATION_ERROR(0x06) + +class AppTask +{ +public: + CHIP_ERROR StartAppTask(); + static void AppTaskMain(void * pvParameter); + + void PostTurnOnActionRequest(int32_t aActor, LightingManager::Action_t aAction); + void PostEvent(const AppEvent * event); + + void UpdateClusterState(void); + void UpdateDeviceState(void); + + // Identify cluster callbacks. + static void OnIdentifyStart(Identify * identify); + static void OnIdentifyStop(Identify * identify); + static void OnTriggerEffect(Identify * identify); + static void OnTriggerEffectComplete(chip::System::Layer * systemLayer, void * appState); + +private: + friend AppTask & GetAppTask(void); + + CHIP_ERROR Init(); + + static void ActionInitiated(LightingManager::Action_t aAction, int32_t aActor); + static void ActionCompleted(LightingManager::Action_t aAction); + + void CancelTimer(void); + + void DispatchEvent(AppEvent * event); + + static void FunctionTimerEventHandler(AppEvent * aEvent); + static button_status_t KBD_Callback(void * buttonHandle, button_callback_message_t * message, void * callbackParam); + static void OTAHandler(AppEvent * aEvent); + static void BleHandler(AppEvent * aEvent); + static void BleStartAdvertising(intptr_t arg); + static void LightActionEventHandler(AppEvent * aEvent); + static void ResetActionEventHandler(AppEvent * aEvent); + static void InstallEventHandler(AppEvent * aEvent); + + static void ButtonEventHandler(uint8_t pin_no, uint8_t button_action); + static void TimerEventHandler(TimerHandle_t xTimer); + + static void MatterEventHandler(const chip::DeviceLayer::ChipDeviceEvent * event, intptr_t arg); + void StartTimer(uint32_t aTimeoutInMs); + + static void RestoreLightingState(void); + +#if CHIP_DEVICE_CONFIG_ENABLE_OTA_REQUESTOR + static void InitOTA(intptr_t arg); + static void StartOTAQuery(intptr_t arg); +#endif + + static void UpdateClusterStateInternal(intptr_t arg); + static void UpdateDeviceStateInternal(intptr_t arg); + static void InitServer(intptr_t arg); + static void PrintOnboardingInfo(); + + enum Function_t + { + kFunction_NoneSelected = 0, + kFunction_FactoryReset, + kFunctionTurnOnTurnOff, + kFunction_Identify, + kFunction_TriggerEffect, + kFunction_Invalid + } Function; + + Function_t mFunction = kFunction_NoneSelected; + bool mResetTimerActive = false; + bool mSyncClusterToButtonAction = false; + + static AppTask sAppTask; +}; + +inline AppTask & GetAppTask(void) +{ + return AppTask::sAppTask; +} diff --git a/examples/lighting-app/nxp/k32w/k32w1/main/include/LightingManager.h b/examples/lighting-app/nxp/k32w/k32w1/main/include/LightingManager.h new file mode 100644 index 00000000000000..327bf3bdf02763 --- /dev/null +++ b/examples/lighting-app/nxp/k32w/k32w1/main/include/LightingManager.h @@ -0,0 +1,68 @@ +/* + * + * Copyright (c) 2021 Google LLC. + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include + +#include "AppEvent.h" + +#include "FreeRTOS.h" +#include "timers.h" // provides FreeRTOS timer support + +class LightingManager +{ +public: + enum Action_t + { + TURNON_ACTION = 0, + TURNOFF_ACTION, + + INVALID_ACTION + } Action; + + enum State_t + { + kState_On = 0, + kState_Off, + } State; + + int Init(); + bool IsTurnedOff(); + bool InitiateAction(int32_t aActor, Action_t aAction); + + typedef void (*Callback_fn_initiated)(Action_t, int32_t aActor); + typedef void (*Callback_fn_completed)(Action_t); + void SetCallbacks(Callback_fn_initiated aActionInitiated_CB, Callback_fn_completed aActionCompleted_CB); + void SetState(bool state); + +private: + friend LightingManager & LightingMgr(void); + State_t mState; + + Callback_fn_initiated mActionInitiated_CB; + Callback_fn_completed mActionCompleted_CB; + + static LightingManager sLight; +}; + +inline LightingManager & LightingMgr(void) +{ + return LightingManager::sLight; +} diff --git a/examples/lighting-app/nxp/k32w/k32w1/main/include/app_config.h b/examples/lighting-app/nxp/k32w/k32w1/main/include/app_config.h new file mode 100644 index 00000000000000..ff938f494b089d --- /dev/null +++ b/examples/lighting-app/nxp/k32w/k32w1/main/include/app_config.h @@ -0,0 +1,49 @@ +/* + * + * Copyright (c) 2021 Google LLC. + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +// ---- Light Example App Config ---- + +#define RESET_BUTTON 1 +#define LIGHT_BUTTON 2 +#define OTA_BUTTON 3 +#define BLE_BUTTON 4 + +#define RESET_BUTTON_PUSH 1 +#define LIGHT_BUTTON_PUSH 2 +#define OTA_BUTTON_PUSH 3 +#define BLE_BUTTON_PUSH 4 + +#define APP_BUTTON_PUSH 1 + +#define LIGHT_STATE_LED 1 +#define SYSTEM_STATE_LED 0 + +// Time it takes for the light to switch on/off +#define ACTUATOR_MOVEMENT_PERIOS_MS 50 + +// ---- Light Example SWU Config ---- +#define SWU_INTERVAl_WINDOW_MIN_MS (23 * 60 * 60 * 1000) // 23 hours +#define SWU_INTERVAl_WINDOW_MAX_MS (24 * 60 * 60 * 1000) // 24 hours + +#if K32W_LOG_ENABLED +#define K32W_LOG(...) otPlatLog(OT_LOG_LEVEL_NONE, OT_LOG_REGION_API, ##__VA_ARGS__); +#else +#define K32W_LOG(...) +#endif diff --git a/examples/lighting-app/nxp/k32w/k32w1/main/main.cpp b/examples/lighting-app/nxp/k32w/k32w1/main/main.cpp new file mode 100644 index 00000000000000..7ebd3f3ed3b1b7 --- /dev/null +++ b/examples/lighting-app/nxp/k32w/k32w1/main/main.cpp @@ -0,0 +1,140 @@ +/* + * + * Copyright (c) 2021 Google LLC. + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// ================================================================================ +// Main Code +// ================================================================================ + +#include "openthread/platform/logging.h" +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "FreeRtosHooks.h" +#include "app_config.h" + +using namespace ::chip; +using namespace ::chip::Inet; +using namespace ::chip::DeviceLayer; +using namespace ::chip::Logging; + +#include + +typedef void (*InitFunc)(void); +extern InitFunc __init_array_start; +extern InitFunc __init_array_end; + +extern "C" void main_task(void const * argument) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + + /* Call C++ constructors */ + InitFunc * pFunc = &__init_array_start; + for (; pFunc < &__init_array_end; ++pFunc) + { + (*pFunc)(); + } + + mbedtls_platform_set_calloc_free(CHIPPlatformMemoryCalloc, CHIPPlatformMemoryFree); + + err = PlatformMgrImpl().InitBoardFwk(); + if (err != CHIP_NO_ERROR) + { + return; + } + + /* Used for HW initializations */ + otSysInit(0, NULL); + + K32W_LOG("Welcome to NXP Lighting Demo App"); + + /* Mbedtls Threading support is needed because both + * Thread and Matter tasks are using it */ + freertos_mbedtls_mutex_init(); + + // Init Chip memory management before the stack + chip::Platform::MemoryInit(); + + err = PlatformMgr().InitChipStack(); + if (err != CHIP_NO_ERROR) + { + K32W_LOG("Error during PlatformMgr().InitMatterStack()"); + goto exit; + } + + err = ThreadStackMgr().InitThreadStack(); + if (err != CHIP_NO_ERROR) + { + K32W_LOG("Error during ThreadStackMgr().InitThreadStack()"); + goto exit; + } + + err = ConnectivityMgr().SetThreadDeviceType(ConnectivityManager::kThreadDeviceType_Router); + + if (err != CHIP_NO_ERROR) + { + goto exit; + } + + // Start OpenThread task + err = ThreadStackMgrImpl().StartThreadTask(); + if (err != CHIP_NO_ERROR) + { + K32W_LOG("Error during ThreadStackMgrImpl().StartThreadTask()"); + goto exit; + } + + err = GetAppTask().StartAppTask(); + if (err != CHIP_NO_ERROR) + { + K32W_LOG("Error during GetAppTask().StartAppTask()"); + goto exit; + } + + err = PlatformMgr().StartEventLoopTask(); + if (err != CHIP_NO_ERROR) + { + K32W_LOG("Error during PlatformMgr().StartEventLoopTask();"); + goto exit; + } + + GetAppTask().AppTaskMain(NULL); + +exit: + return; +} + +/** + * Glue function called directly by the OpenThread stack + * when system event processing work is pending. + */ +extern "C" void otSysEventSignalPending(void) +{ + { + BaseType_t yieldRequired = ThreadStackMgrImpl().SignalThreadActivityPendingFromISR(); + portYIELD_FROM_ISR(yieldRequired); + } +} diff --git a/examples/lighting-app/nxp/k32w/k32w1/third_party/connectedhomeip b/examples/lighting-app/nxp/k32w/k32w1/third_party/connectedhomeip new file mode 120000 index 00000000000000..305f2077ffe860 --- /dev/null +++ b/examples/lighting-app/nxp/k32w/k32w1/third_party/connectedhomeip @@ -0,0 +1 @@ +../../../../../.. \ No newline at end of file diff --git a/examples/platform/nxp/k32w/k32w1/BUILD.gn b/examples/platform/nxp/k32w/k32w1/BUILD.gn new file mode 100644 index 00000000000000..0a980406bce075 --- /dev/null +++ b/examples/platform/nxp/k32w/k32w1/BUILD.gn @@ -0,0 +1,33 @@ +# Copyright (c) 2020 Project CHIP Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import("//build_overrides/chip.gni") +import("//build_overrides/k32w1_sdk.gni") + +import("${k32w1_sdk_build_root}/k32w1_sdk.gni") + +config("chip_examples_project_config") { + include_dirs = [ + "app/project_include", + "${chip_root}", + ] +} + +source_set("openthread_core_config_k32w1_chip_examples") { + sources = [ "app/project_include/OpenThreadConfig.h" ] + + public_deps = [ "${chip_root}/third_party/openthread/platforms/nxp/k32w/k32w1:openthread_core_config_k32w1" ] + + public_configs = [ ":chip_examples_project_config" ] +} diff --git a/examples/platform/nxp/k32w/k32w1/app/BUILD.gn b/examples/platform/nxp/k32w/k32w1/app/BUILD.gn new file mode 100644 index 00000000000000..6671d140688df9 --- /dev/null +++ b/examples/platform/nxp/k32w/k32w1/app/BUILD.gn @@ -0,0 +1,27 @@ +# Copyright (c) 2020 Project CHIP Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import("//build_overrides/chip.gni") + +config("chip_examples_project_config") { + include_dirs = [ "app/project_include" ] +} + +source_set("openthread_core_config_k32w1_chip_examples") { + sources = [ "app/project_include/OpenThreadConfig.h" ] + + public_deps = [ "${chip_root}/third_party/openthread/platforms/nxp/k32w/k32w1:openthread_core_config_k32w1" ] + + public_configs = [ ":chip_examples_project_config" ] +} diff --git a/examples/platform/nxp/k32w/k32w1/app/args.gni b/examples/platform/nxp/k32w/k32w1/app/args.gni new file mode 100644 index 00000000000000..c09510c14df229 --- /dev/null +++ b/examples/platform/nxp/k32w/k32w1/app/args.gni @@ -0,0 +1,33 @@ +# Copyright (c) 2020 Project CHIP Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import("//build_overrides/chip.gni") + +import("${chip_root}/src/platform/nxp/k32w/k32w1/args.gni") + +arm_float_abi = "hard" +arm_cpu = "cortex-m33" +arm_fpu = "fpv5-sp-d16" +arm_arch = "armv8-m.main+dsp+fp" + +openthread_project_core_config_file = "OpenThreadConfig.h" + +chip_ble_project_config_include = "" +chip_device_project_config_include = "" +chip_project_config_include = "" +chip_inet_project_config_include = "" +chip_system_project_config_include = "" + +chip_system_config_provide_statistics = false +chip_with_nlfaultinjection = true diff --git a/examples/platform/nxp/k32w/k32w1/app/ldscripts/k32w1_app.ld b/examples/platform/nxp/k32w/k32w1/app/ldscripts/k32w1_app.ld new file mode 100644 index 00000000000000..de9c882af9fed4 --- /dev/null +++ b/examples/platform/nxp/k32w/k32w1/app/ldscripts/k32w1_app.ld @@ -0,0 +1,52 @@ +/* +** Copyright (c) 2023 Project CHIP Authors +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ + +m_smu2_data_start = 0x489C0000; +m_smu2_data_end = 0x489C537B; +m_smu2_data_size = m_smu2_data_end - m_smu2_data_start + 1; + +/* Specify the extra application specific memory areas */ +MEMORY +{ + m_smu2_data (RW) : ORIGIN = m_smu2_data_start, LENGTH = m_smu2_data_size +} + + +/* Define the extra application specific output sections */ +SECTIONS +{ + .smu2 (NOLOAD) : + { + /* This is used by the startup in order to initialize the free .smu2 section */ + . = ALIGN(4); + __START_SMU2 = .; + __smu2_start__ = .; + *(.smu2) + /* These input section descriptions should not be changed as they match */ + /* specific Matter instances/global variables. */ + *(.bss.*chip*Server*sServer*) + *(*gImageProcessor) + *(*gApplicationProcessor) + *(.bss.*ThreadStackManagerImpl*sInstance*) + . = ALIGN(4); + __smu2_end__ = .; + __END_SMU2 = .; + ASSERT(__smu2_end__ > 18K, "SMU2 section unexpected end address, check variable names"); + } > m_smu2_data +} + + +INCLUDE connectivity.ld diff --git a/examples/platform/nxp/k32w/k32w1/app/project_include/OpenThreadConfig.h b/examples/platform/nxp/k32w/k32w1/app/project_include/OpenThreadConfig.h new file mode 100644 index 00000000000000..932812aa711659 --- /dev/null +++ b/examples/platform/nxp/k32w/k32w1/app/project_include/OpenThreadConfig.h @@ -0,0 +1,81 @@ +/* + * + * Copyright (c) 2020 Google LLC. + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file + * Overrides to default OpenThread configuration. + * + */ + +#pragma once + +// Disable the Nxp-supplied OpenThread logging facilities +// and use the facilities provided by the Device Layer +// (see src/platform/K32W/Logging.cpp). +#define OPENTHREAD_CONFIG_LOG_OUTPUT OPENTHREAD_CONFIG_LOG_OUTPUT_APP + +// When operating in a less than ideal RF environment, having a more forgiving configuration +// of OpenThread makes thread a great deal more reliable. +#define OPENTHREAD_CONFIG_TMF_ADDRESS_QUERY_MAX_RETRY_DELAY 120 // default is 28800 +#define OPENTHREAD_CONFIG_MAC_DEFAULT_MAX_FRAME_RETRIES_DIRECT 15 // default is 3 +#define OPENTHREAD_CONFIG_MAC_DEFAULT_MAX_FRAME_RETRIES_INDIRECT 1 // default is 0 +#define OPENTHREAD_CONFIG_MAC_MAX_TX_ATTEMPTS_INDIRECT_POLLS 16 // default is 4 + +// Enable periodic parent search to speed up finding a better parent. +#define OPENTHREAD_CONFIG_PARENT_SEARCH_ENABLE 1 // default is 0 +#define OPENTHREAD_CONFIG_PARENT_SEARCH_RSS_THRESHOLD -45 // default is -65 +#define OPENTHREAD_CONFIG_MLE_INFORM_PREVIOUS_PARENT_ON_REATTACH 1 // default is 0 + +// Use smaller maximum interval to speed up reattaching. +#define OPENTHREAD_CONFIG_MLE_ATTACH_BACKOFF_MAXIMUM_INTERVAL (60 * 10 * 1000) // default 1200000 ms + +// disable unused features +#define OPENTHREAD_CONFIG_COAP_API_ENABLE 0 +#define OPENTHREAD_CONFIG_JOINER_ENABLE 0 +#define OPENTHREAD_CONFIG_COMMISSIONER_ENABLE 0 +#define OPENTHREAD_CONFIG_UDP_FORWARD_ENABLE 0 +#define OPENTHREAD_CONFIG_BORDER_ROUTER_ENABLE 0 +#define OPENTHREAD_CONFIG_DHCP6_CLIENT_ENABLE 0 +#define OPENTHREAD_CONFIG_DHCP6_SERVER_ENABLE 0 +#define OPENTHREAD_CONFIG_TCP_ENABLE 0 + +#define OPENTHREAD_CONFIG_MLE_LINK_METRICS_INITIATOR_ENABLE 0 +#define OPENTHREAD_CONFIG_MLE_LINK_METRICS_SUBJECT_ENABLE 0 + +#define OPENTHREAD_CONFIG_DUA_ENABLE 1 +#define OPENTHREAD_CONFIG_MLR_ENABLE 1 + +#define OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE 1 +#define OPENTHREAD_CONFIG_MAC_SOFTWARE_TX_SECURITY_ENABLE 0 +#define OPENTHREAD_CONFIG_MAC_SOFTWARE_TX_TIMING_ENABLE 0 + +#define OPENTHREAD_CONFIG_NUM_MESSAGE_BUFFERS 30 + +#define OPENTHREAD_CONFIG_DNSSD_SERVER_ENABLE 0 +#define OPENTHREAD_CONFIG_SRP_SERVER_ENABLE 0 +#define OPENTHREAD_CONFIG_TMF_NETDATA_SERVICE_ENABLE 0 + +// #define OPENTHREAD_CONFIG_LOG_LEVEL OT_LOG_LEVEL_DEBG + +// Use the NXP-supplied default platform configuration for remainder +// of OpenThread config options. +// +// NB: This file gets included during the build of OpenThread. Hence +// it cannot use "openthread" in the path to the included file. +// +#include "openthread-core-k32w1-config.h" diff --git a/examples/platform/nxp/k32w/k32w1/app/support/BUILD.gn b/examples/platform/nxp/k32w/k32w1/app/support/BUILD.gn new file mode 100644 index 00000000000000..dafa6de4d3d31d --- /dev/null +++ b/examples/platform/nxp/k32w/k32w1/app/support/BUILD.gn @@ -0,0 +1,53 @@ +# Copyright (c) 2020 Project CHIP Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import("//build_overrides/chip.gni") +import("//build_overrides/k32w1_sdk.gni") + +config("support_config") { + include_dirs = [ "../../../../.." ] + + # Link options that provides replace dynamic memory operations in standard + # library with the FreeRTOS malloc in platform code. + ldflags = [ + # memory allocation -- these must be re-entrant and do locking + "-Wl,--wrap=malloc", + "-Wl,--wrap=free", + "-Wl,--wrap=realloc", + "-Wl,--wrap=calloc", + "-Wl,--wrap=MemoryAlloc", + + # Wrap these in case internal newlib call them (e.g. strdup will) + # directly call _malloc_r) + "-Wl,--wrap=_malloc_r", + "-Wl,--wrap=_realloc_r", + "-Wl,--wrap=_free_r", + "-Wl,--wrap=_calloc_r", + "-Wl,--gc-sections,--defsym=gUseNVMLink_d=1", + ] +} + +source_set("freertos_mbedtls_utils") { + sources = [ + "FreeRtosHooks.c", + "FreeRtosHooks.h", + "Memconfig.cpp", + ] + + deps = [ "${chip_root}/src/lib/support" ] + + cflags = [ "-Wconversion" ] + + public_configs = [ ":support_config" ] +} diff --git a/examples/platform/nxp/k32w/k32w1/app/support/FreeRtosHooks.c b/examples/platform/nxp/k32w/k32w1/app/support/FreeRtosHooks.c new file mode 100644 index 00000000000000..83549b1dc1f04a --- /dev/null +++ b/examples/platform/nxp/k32w/k32w1/app/support/FreeRtosHooks.c @@ -0,0 +1,152 @@ +/* + * + * Copyright (c) 2020 Google LLC. + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#include "FreeRtosHooks.h" + +#include "FreeRTOS.h" +#include "semphr.h" + +#include + +#include +#include + +#include "NVM_Interface.h" +#include "PWR_Interface.h" +#include "board.h" +#include "fsl_os_abstraction.h" + +/* Bluetooth Low Energy */ +#include "ble_config.h" +#include "l2ca_cb_interface.h" + +#if defined gLoggingActive_d && (gLoggingActive_d > 0) +#include "dbg_logging.h" +#ifndef DBG_APP +#define DBG_APP 0 +#endif +#define APP_DBG_LOG(fmt, ...) \ + if (DBG_APP) \ + do \ + { \ + DbgLogAdd(__FUNCTION__, fmt, VA_NUM_ARGS(__VA_ARGS__), ##__VA_ARGS__); \ + } while (0); +#else +#define APP_DBG_LOG(...) +#endif + +#if (configUSE_TICKLESS_IDLE != 0) +extern uint64_t PWR_TryEnterLowPower(uint64_t timeoutUs); +#endif + +static inline void mutex_init(mbedtls_threading_mutex_t * p_mutex) +{ + assert(p_mutex != NULL); + *p_mutex = xSemaphoreCreateMutex(); + assert(*p_mutex != NULL); +} + +static inline void mutex_free(mbedtls_threading_mutex_t * p_mutex) +{ + assert(p_mutex != NULL); + assert(*p_mutex != NULL); + vSemaphoreDelete(*p_mutex); +} + +static inline int mutex_lock(mbedtls_threading_mutex_t * p_mutex) +{ + assert(p_mutex != NULL); + assert(*p_mutex != NULL); + return xSemaphoreTake(*p_mutex, portMAX_DELAY) != pdTRUE; +} + +static inline int mutex_unlock(mbedtls_threading_mutex_t * p_mutex) +{ + assert(p_mutex != NULL); + assert(*p_mutex != NULL); + return xSemaphoreGive(*p_mutex) != pdTRUE; +} + +void freertos_mbedtls_mutex_init(void) +{ + mbedtls_threading_set_alt(mutex_init, mutex_free, mutex_lock, mutex_unlock); +} + +void freertos_mbedtls_mutex_free(void) +{ + mbedtls_threading_free_alt(); +} + +#if (configUSE_TICKLESS_IDLE != 0) + +/*! ********************************************************************************* + *\private + *\fn void vPortSuppressTicksAndSleep( TickType_t xExpectedIdleTime ) + *\brief This function will try to put the MCU into a deep sleep mode for at + * most the maximum OS idle time specified. Else the MCU will enter a + * sleep mode until the first IRQ. + * + *\param [in] xExpectedIdleTime The idle time in OS ticks. + * + *\retval none. + * + *\remarks This feature is available only for FreeRTOS. + ********************************************************************************** */ +void vPortSuppressTicksAndSleep(TickType_t xExpectedIdleTime) +{ + bool abortIdle = false; + uint64_t actualIdleTimeUs, expectedIdleTimeUs; + + /* The OSA_InterruptDisable() API will prevent us to wakeup so we use + * OSA_DisableIRQGlobal() */ + OSA_DisableIRQGlobal(); + + /* Disable and prepare systicks for low power */ + abortIdle = PWR_SysticksPreProcess((uint32_t) xExpectedIdleTime, &expectedIdleTimeUs); + + if (abortIdle == false) + { + /* Enter low power with a maximal timeout */ + actualIdleTimeUs = PWR_TryEnterLowPower(expectedIdleTimeUs); + + /* Re enable systicks and compensate systick timebase */ + PWR_SysticksPostProcess(expectedIdleTimeUs, actualIdleTimeUs); + } + + /* Exit from critical section */ + OSA_EnableIRQGlobal(); +} +#endif + +extern void OTAIdleActivities(void); + +void vApplicationIdleHook(void) +{ + // Data queued by PDM will be written to external flash + // when PDM_vIdleTask is called. Interrupts are disabled + // to ensure there is no context switch during the actual + // writing, thus avoiding race conditions. + OSA_InterruptDisable(); +#if CHIP_PLAT_NVM_SUPPORT + NvIdle(); +#endif + OSA_InterruptEnable(); + + OTAIdleActivities(); +} diff --git a/examples/platform/nxp/k32w/k32w1/app/support/FreeRtosHooks.h b/examples/platform/nxp/k32w/k32w1/app/support/FreeRtosHooks.h new file mode 100644 index 00000000000000..a27f72d498f81d --- /dev/null +++ b/examples/platform/nxp/k32w/k32w1/app/support/FreeRtosHooks.h @@ -0,0 +1,42 @@ +/* + * + * Copyright (c) 2020 Nest Labs, Inc. + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +typedef void * mbedtls_threading_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 *)); + +extern void mbedtls_threading_free_alt(void); + +#ifdef __cplusplus +extern "C" { +#endif + +/**@brief Function for initializing alternative MbedTLS mutexes to enable the usage of the FreeRTOS implementation. */ +void freertos_mbedtls_mutex_init(void); + +/**@brief Function for releasing MbedTLS alternative mutexes. */ +void freertos_mbedtls_mutex_free(void); + +#ifdef __cplusplus +} +#endif diff --git a/examples/platform/nxp/k32w/k32w1/app/support/Memconfig.cpp b/examples/platform/nxp/k32w/k32w1/app/support/Memconfig.cpp new file mode 100644 index 00000000000000..e5acf5ea3ceecb --- /dev/null +++ b/examples/platform/nxp/k32w/k32w1/app/support/Memconfig.cpp @@ -0,0 +1,184 @@ +/* + * + * Copyright (c) 2020-2021 Project CHIP Authors + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file + * This file contains platform specific implementations for stdlib malloc/calloc/realloc/free + * functions, so that CHIPPlatformMemory* works as intended with the platform's heap. + */ + +#include "FreeRTOS.h" +#include "task.h" +#include +#include +#include + +#ifndef NDEBUG +#include +#include +#endif + +#include + +#if CHIP_CONFIG_MEMORY_DEBUG_DMALLOC +#include +#include +#endif // CHIP_CONFIG_MEMORY_DEBUG_DMALLOC + +/* Assumes 8bit bytes! */ +#define heapBITS_PER_BYTE ((size_t) 8) + +/* Define the linked list structure. This is used to link free blocks in order +of their memory address. */ +typedef struct A_BLOCK_LINK +{ + struct A_BLOCK_LINK * pxNextFreeBlock; /*<< The next free block in the list. */ + size_t xBlockSize; /*<< The size of the free block. */ +} BlockLink_t; + +/* The size of the structure placed at the beginning of each allocated memory +block must by correctly byte aligned. */ +static const size_t xHeapStructSize = + (sizeof(BlockLink_t) + ((size_t) (portBYTE_ALIGNMENT - 1))) & ~((size_t) portBYTE_ALIGNMENT_MASK); + +/* Gets set to the top bit of an size_t type. When this bit in the xBlockSize +member of an BlockLink_t structure is set then the block belongs to the +application. When the bit is free the block is still part of the free heap +space. */ +static size_t xBlockAllocatedBit = ((size_t) 1) << ((sizeof(size_t) * heapBITS_PER_BYTE) - 1); + +extern "C" { + +/* xPortMallocUsableSize relies on heap4 implementation. +It returns the size of an allocated block and it is +called by __wrap_realloc. +Thus it is validated in __wrap_realloc function that the allocated size +of the old_ptr is smaller than the allocated size of new_ptr */ +size_t xPortMallocUsableSize(void * pv) +{ + uint8_t * puc = (uint8_t *) pv; + BlockLink_t * pxLink; + void * voidp; + size_t sz = 0; + + if (pv != NULL) + { + vTaskSuspendAll(); + { + /* The memory being checked will have an BlockLink_t structure immediately + before it. */ + puc -= xHeapStructSize; + + /* This casting is to keep the compiler from issuing warnings. */ + voidp = (void *) puc; + pxLink = (BlockLink_t *) voidp; + + /* Check if the block is actually allocated. */ + configASSERT((pxLink->xBlockSize & xBlockAllocatedBit) != 0); + configASSERT(pxLink->pxNextFreeBlock == NULL); + + sz = (pxLink->xBlockSize & ~xBlockAllocatedBit) - xHeapStructSize; + } + (void) xTaskResumeAll(); + } + + return sz; +} + +void * __wrap_malloc(size_t size) +{ + return pvPortMalloc(size); +} + +void __wrap_free(void * ptr) +{ + vPortFree(ptr); +} + +void * __wrap_calloc(size_t num, size_t size) +{ + size_t total_size = num * size; + + // Handle overflow from (num * size) + if ((size != 0) && ((total_size / size) != num)) + { + return nullptr; + } + + void * ptr = pvPortMalloc(total_size); + if (ptr) + { + memset(ptr, 0, total_size); + } + else + { + ChipLogError(DeviceLayer, "__wrap_calloc: Could not allocate memory!"); + } + + return ptr; +} + +void * __wrap_realloc(void * ptr, size_t new_size) +{ + + void * new_ptr = NULL; + + if (new_size) + { + size_t old_ptr_size = xPortMallocUsableSize(ptr); + if (new_size <= old_ptr_size) + { + /* Return old pointer if the newly allocated size is smaller + or equal to the allocated size for old_ptr */ + return ptr; + } + + /* if old_ptr is NULL, then old_ptr_size is zero and new_ptr will be returned */ + new_ptr = pvPortMalloc(new_size); + + if (!new_ptr) + { + ChipLogError(DeviceLayer, "__wrap_realloc: Could not allocate memory!"); + return NULL; + } + + memset(reinterpret_cast(new_ptr) + old_ptr_size, 0, (new_size - old_ptr_size)); + memcpy(new_ptr, ptr, old_ptr_size); + } + + vPortFree(ptr); + + return new_ptr; +} + +void * __wrap__malloc_r(void * REENT, size_t size) +{ + return __wrap_malloc(size); +} + +void __wrap__free_r(void * REENT, void * ptr) +{ + __wrap_free(ptr); +} + +void * __wrap__realloc_r(void * REENT, void * ptr, size_t new_size) +{ + return __wrap_realloc(ptr, new_size); +} + +} // extern "C" diff --git a/examples/platform/nxp/k32w/k32w1/args.gni b/examples/platform/nxp/k32w/k32w1/args.gni new file mode 100644 index 00000000000000..f7d9dccd82e1fd --- /dev/null +++ b/examples/platform/nxp/k32w/k32w1/args.gni @@ -0,0 +1,38 @@ +# Copyright (c) 2020 Project CHIP Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import("//build_overrides/chip.gni") + +import("${chip_root}/src/platform/nxp/k32w/k32w1/args.gni") + +arm_float_abi = "hard" +arm_cpu = "cortex-m33" +arm_fpu = "fpv5-sp-d16" +arm_arch = "armv8-m.main+dsp+fp" + +chip_openthread_ftd = false +openthread_core_config_deps = [] +openthread_core_config_deps = [ "${chip_root}/examples/platform/nxp/k32w/k32w1:openthread_core_config_k32w1_chip_examples" ] + +chip_ble_project_config_include = "" +chip_device_project_config_include = "" +chip_project_config_include = "" +chip_inet_project_config_include = "" +chip_system_project_config_include = "" + +chip_system_config_provide_statistics = false +chip_with_nlfaultinjection = true + +chip_system_config_use_open_thread_inet_endpoints = true +chip_with_lwip = false diff --git a/examples/platform/nxp/k32w/k32w1/doc/images/debug_k32w1.jpg b/examples/platform/nxp/k32w/k32w1/doc/images/debug_k32w1.jpg new file mode 100644 index 0000000000000000000000000000000000000000..407c781220c4a60aebc4301ad36e15d68f0ec888 GIT binary patch literal 60945 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 DSQ9o_ literal 0 HcmV?d00001 diff --git a/examples/platform/nxp/k32w/k32w1/doc/images/import_demo.jpg b/examples/platform/nxp/k32w/k32w1/doc/images/import_demo.jpg new file mode 100644 index 0000000000000000000000000000000000000000..6016c448a48e49545bd35ef1049b5434ffb3bdf5 GIT binary patch literal 43033 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 literal 0 HcmV?d00001 diff --git a/examples/platform/nxp/k32w/k32w1/doc/images/k32w1-evk.jpg b/examples/platform/nxp/k32w/k32w1/doc/images/k32w1-evk.jpg new file mode 100644 index 0000000000000000000000000000000000000000..550f2743367865350029268125abb23dee1a27a0 GIT binary patch literal 187204 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

S}<_3+IeRy2<Y`X5-h$&<=EevdKrX0sGJ?&3>9 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

  • G2i(1#xlhBz|=B$N)z{pLn?W3C!Vx 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 literal 0 HcmV?d00001 diff --git a/examples/platform/nxp/k32w/k32w1/doc/images/mcux-sdk-download.jpg b/examples/platform/nxp/k32w/k32w1/doc/images/mcux-sdk-download.jpg new file mode 100644 index 0000000000000000000000000000000000000000..9dd4190b6d99e033c43cd16ee7f0a03b8625e306 GIT binary patch literal 54407 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 literal 0 HcmV?d00001 diff --git a/examples/platform/nxp/k32w/k32w1/doc/images/new_project.jpg b/examples/platform/nxp/k32w/k32w1/doc/images/new_project.jpg new file mode 100644 index 0000000000000000000000000000000000000000..ce34586229bd676c702d8bc44af396101c3c97c9 GIT binary patch literal 50137 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 I?5nT;2c|ys`2YX_ literal 0 HcmV?d00001 diff --git a/examples/platform/nxp/k32w/k32w1/util/LEDWidget.cpp b/examples/platform/nxp/k32w/k32w1/util/LEDWidget.cpp new file mode 100644 index 00000000000000..b7a01ae6c7545f --- /dev/null +++ b/examples/platform/nxp/k32w/k32w1/util/LEDWidget.cpp @@ -0,0 +1,93 @@ +/* + * + * Copyright (c) 2020 Project CHIP Authors + * Copyright (c) 2019 Google LLC. + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "LEDWidget.h" + +#include + +#include "app.h" + +#if (defined(gAppLedCnt_c) && (gAppLedCnt_c > 0)) + +void LEDWidget::Init(uint8_t led, bool inverted) +{ + mLastChangeTimeMS = 0; + mBlinkOnTimeMS = 0; + mBlinkOffTimeMS = 0; + mGPIONum = led; + mState = false; + mOnLogic = !inverted; + + Set(false); +} + +void LEDWidget::Invert(void) +{ + Set(!mState); +} + +void LEDWidget::Set(bool state) +{ + mLastChangeTimeMS = mBlinkOnTimeMS = mBlinkOffTimeMS = 0; + DoSet(state); +} + +void LEDWidget::Blink(uint32_t changeRateMS) +{ + Blink(changeRateMS, changeRateMS); +} + +void LEDWidget::Blink(uint32_t onTimeMS, uint32_t offTimeMS) +{ + mBlinkOnTimeMS = onTimeMS; + mBlinkOffTimeMS = offTimeMS; + Animate(); +} + +void LEDWidget::Animate() +{ + if (mBlinkOnTimeMS != 0 && mBlinkOffTimeMS != 0) + { + uint64_t nowMS = chip::System::SystemClock().GetMonotonicMilliseconds64().count(); + uint64_t stateDurMS = mState ? mBlinkOnTimeMS : mBlinkOffTimeMS; + uint64_t nextChangeTimeMS = mLastChangeTimeMS + stateDurMS; + + if (nextChangeTimeMS < nowMS) + { + DoSet(!mState); + mLastChangeTimeMS = nowMS; + } + } +} + +void LEDWidget::DoSet(bool state) +{ + mState = state; + + if (state) + { + (void) LED_TurnOnOff((led_handle_t) g_ledHandle[mGPIONum], mOnLogic); + } + else + { + (void) LED_TurnOnOff((led_handle_t) g_ledHandle[mGPIONum], !mOnLogic); + } +} + +#endif /* (defined(gAppLedCnt_c) && (gAppLedCnt_c > 0)) */ diff --git a/examples/platform/nxp/k32w/k32w1/util/include/LEDWidget.h b/examples/platform/nxp/k32w/k32w1/util/include/LEDWidget.h new file mode 100644 index 00000000000000..4d55f246d49e05 --- /dev/null +++ b/examples/platform/nxp/k32w/k32w1/util/include/LEDWidget.h @@ -0,0 +1,42 @@ +/* + * + * Copyright (c) 2020 Google LLC. + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// #include "LED.h" +#include "EmbeddedTypes.h" +#pragma once + +class LEDWidget +{ +public: + void Init(uint8_t gpioNum, bool inverted); + void Set(bool state); + void Invert(void); + void Blink(uint32_t changeRateMS); + void Blink(uint32_t onTimeMS, uint32_t offTimeMS); + void Animate(); + +private: + uint64_t mLastChangeTimeMS; + uint32_t mBlinkOnTimeMS; + uint32_t mBlinkOffTimeMS; + uint8_t mGPIONum; + bool mState; + bool mOnLogic; + + void DoSet(bool state); +}; diff --git a/scripts/build/build/targets.py b/scripts/build/build/targets.py index 6d039d53b5676c..03089edd1d5571 100755 --- a/scripts/build/build/targets.py +++ b/scripts/build/build/targets.py @@ -24,7 +24,7 @@ from builders.host import HostApp, HostBoard, HostBuilder, HostCryptoLibrary, HostFuzzingType from builders.imx import IMXApp, IMXBuilder from builders.infineon import InfineonApp, InfineonBoard, InfineonBuilder -from builders.k32w import K32WApp, K32WBuilder +from builders.k32w import K32WApp, K32WBoard, K32WBuilder from builders.mbed import MbedApp, MbedBoard, MbedBuilder, MbedProfile from builders.mw320 import MW320App, MW320Builder from builders.nrf import NrfApp, NrfBoard, NrfConnectBuilder @@ -454,12 +454,18 @@ def BuildASRTarget(): def BuildK32WTarget(): target = BuildTarget('k32w', K32WBuilder) + # boards + target.AppendFixedTargets([ + TargetPart('k32w0', board=K32WBoard.K32W0), + TargetPart('k32w1', board=K32WBoard.K32W1) + ]) + # apps target.AppendFixedTargets([ TargetPart('light', app=K32WApp.LIGHT, release=True), TargetPart('shell', app=K32WApp.SHELL, release=True), TargetPart('lock', app=K32WApp.LOCK, release=True), - TargetPart('contact', app=K32WApp.CONTACT, release=True), + TargetPart('contact', app=K32WApp.CONTACT, release=True) ]) target.AppendModifier(name="se05x", se05x=True) @@ -470,6 +476,7 @@ def BuildK32WTarget(): target.AppendModifier(name="crypto-platform", crypto_platform=True) target.AppendModifier( name="tokenizer", tokenizer=True).ExceptIfRe("-nologs") + target.AppendModifier(name="openthread-ftd", openthread_ftd=True) return target diff --git a/scripts/build/builders/k32w.py b/scripts/build/builders/k32w.py index be17730c481148..1cf7862d3d8739 100644 --- a/scripts/build/builders/k32w.py +++ b/scripts/build/builders/k32w.py @@ -18,6 +18,27 @@ from .gn import GnBuilder +class K32WBoard(Enum): + K32W0 = auto() + K32W1 = auto() + + def Name(self): + if self == K32WBoard.K32W0: + return 'k32w0x' + elif self == K32WBoard.K32W1: + return 'k32w1' + else: + raise Exception('Unknown board type: %r' % self) + + def FolderName(self): + if self == K32WBoard.K32W0: + return 'k32w/k32w0' + elif self == K32WBoard.K32W1: + return 'k32w/k32w1' + else: + raise Exception('Unknown board type: %r' % self) + + class K32WApp(Enum): LIGHT = auto() LOCK = auto() @@ -36,20 +57,20 @@ def ExampleName(self): else: raise Exception('Unknown app type: %r' % self) - def AppNamePrefix(self): + def NameSuffix(self): if self == K32WApp.LIGHT: - return 'chip-k32w0x-light-example' + return 'light-example' elif self == K32WApp.LOCK: - return 'chip-k32w0x-lock-example' + return 'lock-example' elif self == K32WApp.SHELL: - return 'chip-k32w0x-shell-example' + return 'shell-example' elif self == K32WApp.CONTACT: - return 'chip-k32w0x-contact-example' + return 'contact-example' else: raise Exception('Unknown app type: %r' % self) - def BuildRoot(self, root): - return os.path.join(root, 'examples', self.ExampleName(), 'nxp', 'k32w', 'k32w0') + def BuildRoot(self, root, board): + return os.path.join(root, 'examples', self.ExampleName(), 'nxp', board.FolderName()) class K32WBuilder(GnBuilder): @@ -58,6 +79,7 @@ def __init__(self, root, runner, app: K32WApp = K32WApp.LIGHT, + board: K32WBoard = K32WBoard.K32W0, release: bool = False, low_power: bool = False, tokenizer: bool = False, @@ -66,12 +88,14 @@ def __init__(self, disable_logs: bool = False, se05x: bool = False, tinycrypt: bool = False, - crypto_platform: bool = False): + crypto_platform: bool = False, + openthread_ftd: bool = False): super(K32WBuilder, self).__init__( - root=app.BuildRoot(root), + root=app.BuildRoot(root, board), runner=runner) self.code_root = root self.app = app + self.board = board self.low_power = low_power self.tokenizer = tokenizer self.release = release @@ -81,11 +105,10 @@ def __init__(self, self.se05x = se05x self.tinycrypt = tinycrypt self.crypto_platform = crypto_platform + self.openthread_ftd = openthread_ftd def GnBuildArgs(self): - args = [ - 'k32w0_sdk_root="%s"' % os.environ['NXP_K32W0_SDK_ROOT'], - ] + args = [] if self.low_power: args.append('chip_with_low_power=1') @@ -116,18 +139,17 @@ def GnBuildArgs(self): if self.crypto_platform: args.append('chip_crypto=\"platform\"') + if self.openthread_ftd: + args.append('chip_openthread_ftd=true') + return args def generate(self): - self._Execute([os.path.join( - self.code_root, 'third_party/nxp/k32w0_sdk/sdk_fixes/patch_k32w_sdk.sh')]) - super(K32WBuilder, self).generate() def build_outputs(self): - items = {} - for extension in ["", ".map", ".hex"]: - name = '%s%s' % (self.app.AppNamePrefix(), extension) - items[name] = os.path.join(self.output_dir, name) - - return items + name = 'chip-%s-%s' % (self.board.Name(), self.app.NameSuffix()) + return { + '%s.elf' % name: os.path.join(self.output_dir, name), + '%s.map' % name: os.path.join(self.output_dir, '%s.map' % name) + } diff --git a/scripts/build/testdata/all_targets_linux_x64.txt b/scripts/build/testdata/all_targets_linux_x64.txt index e7500ab0ca3b76..c78c87b83fda35 100644 --- a/scripts/build/testdata/all_targets_linux_x64.txt +++ b/scripts/build/testdata/all_targets_linux_x64.txt @@ -14,7 +14,7 @@ linux-{x64,arm64}-{rpc-console,all-clusters,all-clusters-minimal,chip-tool,therm linux-x64-efr32-test-runner[-clang] imx-{chip-tool,lighting-app,thermostat,all-clusters-app,all-clusters-minimal-app,ota-provider-app}[-release] infineon-psoc6-{lock,light,all-clusters,all-clusters-minimal}[-ota][-updateimage] -k32w-{light,shell,lock,contact}[-se05x][-no-ble][-no-ota][-low-power][-nologs][-crypto-platform][-tokenizer] +k32w-{k32w0,k32w1}-{light,shell,lock,contact}[-se05x][-no-ble][-no-ota][-low-power][-nologs][-crypto-platform][-tokenizer][-openthread-ftd] mbed-cy8cproto_062_4343w-{lock,light,all-clusters,all-clusters-minimal,pigweed,ota-requestor,shell}[-release][-develop][-debug] mw320-all-clusters-app nrf-{nrf5340dk,nrf52840dk,nrf52840dongle}-{all-clusters,all-clusters-minimal,lock,light,light-switch,shell,pump,pump-controller,window-covering}[-rpc] diff --git a/scripts/checkout_submodules.py b/scripts/checkout_submodules.py index 22750fd40a8bff..e1c8eb888fcc99 100755 --- a/scripts/checkout_submodules.py +++ b/scripts/checkout_submodules.py @@ -36,7 +36,7 @@ 'efr32', 'esp32', 'infineon', - 'k32w0', + 'k32w', 'linux', 'mbed', 'nrfconnect', diff --git a/src/lwip/BUILD.gn b/src/lwip/BUILD.gn index 3547ac1157568d..241a059abb7428 100644 --- a/src/lwip/BUILD.gn +++ b/src/lwip/BUILD.gn @@ -31,11 +31,12 @@ if (lwip_platform == "") { assert(lwip_platform == "external" || lwip_platform == "standalone" || lwip_platform == "cc13xx_26xx" || lwip_platform == "cc32xx" || lwip_platform == "silabs" || lwip_platform == "k32w0" || - lwip_platform == "qpg" || lwip_platform == "mbed" || - lwip_platform == "psoc6" || lwip_platform == "cyw30739" || - lwip_platform == "bl602" || lwip_platform == "mw320" || - lwip_platform == "bl702" || lwip_platform == "bl702l" || - lwip_platform == "mt793x" || lwip_platform == "asr", + lwip_platform == "k32w1" || lwip_platform == "qpg" || + lwip_platform == "mbed" || lwip_platform == "psoc6" || + lwip_platform == "cyw30739" || lwip_platform == "bl602" || + lwip_platform == "mw320" || lwip_platform == "bl702" || + lwip_platform == "bl702l" || lwip_platform == "mt793x" || + lwip_platform == "asr", "Unsupported lwIP platform: ${lwip_platform}") if (lwip_platform != "external") { @@ -56,6 +57,8 @@ if (lwip_platform == "cc13xx_26xx") { import("${qpg_sdk_build_root}/qpg_sdk.gni") } else if (lwip_platform == "k32w0") { import("//build_overrides/k32w0_sdk.gni") +} else if (lwip_platform == "k32w1") { + import("//build_overrides/k32w1_sdk.gni") } else if (lwip_platform == "psoc6") { import("//build_overrides/psoc6.gni") } else if (lwip_platform == "cyw30739") { @@ -207,6 +210,8 @@ if (current_os == "zephyr" || current_os == "mbed") { public_deps += [ "${chip_root}/src/lib/support" ] } else if (lwip_platform == "k32w0") { public_deps += [ "${k32w0_sdk_build_root}:k32w0_sdk" ] + } else if (lwip_platform == "k32w1") { + public_deps += [ "${k32w1_sdk_build_root}:k32w1_sdk" ] } else if (lwip_platform == "cyw30739") { public_deps += [ "${cyw30739_sdk_build_root}:cyw30739_sdk" ] } else if (lwip_platform == "bl702") { diff --git a/src/platform/BUILD.gn b/src/platform/BUILD.gn index 292ab1cbd7ae77..c588b94fa1621b 100644 --- a/src/platform/BUILD.gn +++ b/src/platform/BUILD.gn @@ -205,6 +205,9 @@ if (chip_device_platform != "none" && chip_device_platform != "external") { } else if (chip_device_platform == "k32w0") { device_layer_target_define = "K32W" defines += [ "CHIP_DEVICE_LAYER_TARGET=nxp/k32w/k32w0" ] + } else if (chip_device_platform == "k32w1") { + device_layer_target_define = "K32W" + defines += [ "CHIP_DEVICE_LAYER_TARGET=nxp/k32w/k32w1" ] } else if (chip_device_platform == "telink") { device_layer_target_define = "TELINK" defines += [ "CHIP_DEVICE_LAYER_TARGET=telink" ] @@ -494,6 +497,8 @@ if (chip_device_platform != "none") { _platform_target = "ESP32" } else if (chip_device_platform == "k32w0") { _platform_target = "nxp/k32w/k32w0" + } else if (chip_device_platform == "k32w1") { + _platform_target = "nxp/k32w/k32w1" } else if (chip_device_platform == "linux") { _platform_target = "Linux" } else if (chip_device_platform == "nrfconnect") { diff --git a/src/platform/device.gni b/src/platform/device.gni index 8bfaee12b86a01..b139f44fba879b 100644 --- a/src/platform/device.gni +++ b/src/platform/device.gni @@ -16,7 +16,7 @@ import("//build_overrides/chip.gni") import("${chip_root}/src/ble/ble.gni") declare_args() { - # Device platform layer: cc13x2_26x2, cc13x4_26x4, cc32xx, darwin, efr32, esp32, external, freertos, linux, nrfconnect, k32w0, qpg, tizen, cyw30739, bl602, mw320, zephyr, beken, openiotsdk, none. + # Device platform layer: cc13x2_26x2, cc13x4_26x4, cc32xx, darwin, efr32, esp32, external, freertos, linux, nrfconnect, k32w0, k32w1, qpg, tizen, cyw30739, bl602, mw320, zephyr, beken, openiotsdk, none. chip_device_platform = "auto" chip_platform_target = "" @@ -51,8 +51,9 @@ declare_args() { chip_device_platform == "linux" || chip_device_platform == "qpg" || chip_device_platform == "cc13x2_26x2" || chip_device_platform == "cc13x4_26x4" || - chip_device_platform == "k32w0" || chip_device_platform == "tizen" || - chip_device_platform == "webos" || chip_device_platform == "stm32" + chip_device_platform == "k32w0" || chip_device_platform == "k32w1" || + chip_device_platform == "tizen" || chip_device_platform == "stm32" || + chip_device_platform == "webos" } declare_args() { @@ -140,6 +141,8 @@ if (chip_device_platform == "cc13x2_26x2") { _chip_device_layer = "qpg" } else if (chip_device_platform == "k32w0") { _chip_device_layer = "nxp/k32w/k32w0" +} else if (chip_device_platform == "k32w1") { + _chip_device_layer = "nxp/k32w/k32w1" } else if (chip_device_platform == "telink") { _chip_device_layer = "telink" } else if (chip_device_platform == "mbed") { @@ -234,15 +237,15 @@ assert( chip_device_platform == "external" || chip_device_platform == "linux" || chip_device_platform == "tizen" || chip_device_platform == "nrfconnect" || - chip_device_platform == "k32w0" || chip_device_platform == "qpg" || - chip_device_platform == "telink" || chip_device_platform == "mbed" || - chip_device_platform == "psoc6" || chip_device_platform == "android" || - chip_device_platform == "ameba" || chip_device_platform == "cyw30739" || - chip_device_platform == "webos" || chip_device_platform == "mw320" || - chip_device_platform == "zephyr" || chip_device_platform == "beken" || - chip_device_platform == "bl602" || chip_device_platform == "bl702" || - chip_device_platform == "bl702l" || chip_device_platform == "mt793x" || - chip_device_platform == "SiWx917" || + chip_device_platform == "k32w0" || chip_device_platform == "k32w1" || + chip_device_platform == "qpg" || chip_device_platform == "telink" || + chip_device_platform == "mbed" || chip_device_platform == "psoc6" || + chip_device_platform == "android" || chip_device_platform == "ameba" || + chip_device_platform == "cyw30739" || chip_device_platform == "webos" || + chip_device_platform == "mw320" || chip_device_platform == "zephyr" || + chip_device_platform == "beken" || chip_device_platform == "bl602" || + chip_device_platform == "bl702" || chip_device_platform == "bl702l" || + chip_device_platform == "mt793x" || chip_device_platform == "SiWx917" || chip_device_platform == "openiotsdk" || chip_device_platform == "asr" || chip_device_platform == "stm32", "Please select a valid value for chip_device_platform") diff --git a/src/platform/nxp/k32w/common/BLEManagerCommon.cpp b/src/platform/nxp/k32w/common/BLEManagerCommon.cpp index 2e3c5a35974c96..65b2f68cb20e90 100644 --- a/src/platform/nxp/k32w/common/BLEManagerCommon.cpp +++ b/src/platform/nxp/k32w/common/BLEManagerCommon.cpp @@ -184,6 +184,18 @@ CHIP_ERROR BLEManagerCommon::_Init() eventBits = xEventGroupWaitBits(sEventGroup, CHIP_BLE_KW_EVNT_INIT_COMPLETE, pdTRUE, pdTRUE, CHIP_BLE_KW_EVNT_TIMEOUT); VerifyOrExit(eventBits & CHIP_BLE_KW_EVNT_INIT_COMPLETE, err = CHIP_ERROR_INCORRECT_STATE); +#if BLE_HIGH_TX_POWER + /* Set Adv Power */ + Gap_SetTxPowerLevel(gAdvertisingPowerLeveldBm_c, gTxPowerAdvChannel_c); + eventBits = xEventGroupWaitBits(sEventGroup, CHIP_BLE_KW_EVNT_POWER_LEVEL_SET, pdTRUE, pdTRUE, CHIP_BLE_KW_EVNT_TIMEOUT); + VerifyOrExit(eventBits & CHIP_BLE_KW_EVNT_POWER_LEVEL_SET, err = CHIP_ERROR_INCORRECT_STATE); + + /* Set Connect Power */ + Gap_SetTxPowerLevel(gConnectPowerLeveldBm_c, gTxPowerConnChannel_c); + eventBits = xEventGroupWaitBits(sEventGroup, CHIP_BLE_KW_EVNT_POWER_LEVEL_SET, pdTRUE, pdTRUE, CHIP_BLE_KW_EVNT_TIMEOUT); + VerifyOrExit(eventBits & CHIP_BLE_KW_EVNT_POWER_LEVEL_SET, err = CHIP_ERROR_INCORRECT_STATE); +#endif + #if defined(CPU_JN518X) && defined(chip_with_low_power) && (chip_with_low_power == 1) PWR_ChangeDeepSleepMode(cPWR_PowerDown_RamRet); #endif diff --git a/src/platform/nxp/k32w/common/K32W_OTA_README.md b/src/platform/nxp/k32w/common/K32W_OTA_README.md index 4f2ba7b6e52960..63079e0ce3c81e 100644 --- a/src/platform/nxp/k32w/common/K32W_OTA_README.md +++ b/src/platform/nxp/k32w/common/K32W_OTA_README.md @@ -128,6 +128,13 @@ at next boot. The `FactoryDataProvider` offers a default restore mechanism and support for registering additional restore mechanisms or overwriting the default one. +Prior to factory data update, the old factory data is backed up in external +flash. If anything interrupts the update (e.g. power loss), there is a slight +chance the internal flash factory data section is erased and has to be restored +at next boot. The `FactoryDataProvider` offers a default restore mechanism and +support for registering additional restore mechanisms or overwriting the default +one. + Restore mechanisms are just functions that have this signature: `CHIP_ERROR (*)(void)`. Any such function can be registered through `FactoryDataProvider::RegisterRestoreMechanism`. @@ -138,12 +145,6 @@ The default restore mechanism is implemented as a weak function: overwritten at application level. When doing the actual restore, the mechanisms are called in the order they were registered. -The default restore mechanism is implemented as a weak function: -`FactoryDataDefaultRestoreMechanism`. It is registered in -`K32W0FactoryDataProvider::Init`, before factory data validation, and it can be -overwritten at application level. When doing the actual restore, the mechanisms -are called in the order they were registered. - Please note that the restore mechanisms registration order matters. Once a restore mechanism is successful (`CHIP_NO_ERROR` is returned), the restore process has finished and subsequent restore mechanisms will not be called. diff --git a/src/platform/nxp/k32w/common/OTAImageProcessorImpl.cpp b/src/platform/nxp/k32w/common/OTAImageProcessorImpl.cpp index f8d8b4b5a0f8e6..16493144c632be 100644 --- a/src/platform/nxp/k32w/common/OTAImageProcessorImpl.cpp +++ b/src/platform/nxp/k32w/common/OTAImageProcessorImpl.cpp @@ -28,6 +28,13 @@ using namespace chip::DeviceLayer; using namespace ::chip::DeviceLayer::Internal; +#if USE_SMU2_AS_SYSTEM_MEMORY +// The attribute specifier should not be changed. +static chip::OTAImageProcessorImpl gImageProcessor __attribute__((section(".smu2"))); +#else +static chip::OTAImageProcessorImpl gImageProcessor; +#endif + namespace chip { CHIP_ERROR OTAImageProcessorImpl::Init(OTADownloader * downloader) @@ -96,16 +103,10 @@ CHIP_ERROR OTAImageProcessorImpl::ProcessBlock(ByteSpan & block) void OTAImageProcessorImpl::HandlePrepareDownload(intptr_t context) { auto * imageProcessor = reinterpret_cast(context); - if (imageProcessor == nullptr) - { - ChipLogError(SoftwareUpdate, "ImageProcessor context is null"); - return; - } - else if (imageProcessor->mDownloader == nullptr) - { - ChipLogError(SoftwareUpdate, "mDownloader is null"); - return; - } + + VerifyOrReturn(imageProcessor != nullptr, ChipLogError(SoftwareUpdate, "ImageProcessor context is null")); + + VerifyOrReturn(imageProcessor->mDownloader != nullptr, ChipLogError(SoftwareUpdate, "mDownloader is null")); GetRequestorInstance()->GetProviderLocation(imageProcessor->mBackupProviderLocation); @@ -168,11 +169,11 @@ CHIP_ERROR OTAImageProcessorImpl::SelectProcessor(ByteSpan & block) auto pair = mProcessorMap.find(header.tag); if (pair == mProcessorMap.end()) { - ChipLogError(SoftwareUpdate, "There is no registered processor for tag: %" PRIu32, header.tag); + ChipLogError(SoftwareUpdate, "There is no registered processor for tag: %lu", header.tag); return CHIP_OTA_PROCESSOR_NOT_REGISTERED; } - ChipLogDetail(SoftwareUpdate, "Selected processor with tag: %ld", pair->first); + ChipLogDetail(SoftwareUpdate, "Selected processor with tag: %lu", pair->first); mCurrentProcessor = pair->second; mCurrentProcessor->SetLength(header.length); mCurrentProcessor->SetWasSelected(true); @@ -185,7 +186,7 @@ CHIP_ERROR OTAImageProcessorImpl::RegisterProcessor(uint32_t tag, OTATlvProcesso auto pair = mProcessorMap.find(tag); if (pair != mProcessorMap.end()) { - ChipLogError(SoftwareUpdate, "A processor for tag %" PRIu32 " is already registered.", tag); + ChipLogError(SoftwareUpdate, "A processor for tag %lu is already registered.", tag); return CHIP_OTA_PROCESSOR_ALREADY_REGISTERED; } @@ -210,17 +211,10 @@ void OTAImageProcessorImpl::HandleAbort(intptr_t context) void OTAImageProcessorImpl::HandleProcessBlock(intptr_t context) { auto * imageProcessor = reinterpret_cast(context); - if (imageProcessor == nullptr) - { - ChipLogError(SoftwareUpdate, "ImageProcessor context is null"); - return; - } - if (imageProcessor->mDownloader == nullptr) - { - ChipLogError(SoftwareUpdate, "mDownloader is null"); - return; - } + VerifyOrReturn(imageProcessor != nullptr, ChipLogError(SoftwareUpdate, "ImageProcessor context is null")); + + VerifyOrReturn(imageProcessor->mDownloader != nullptr, ChipLogError(SoftwareUpdate, "mDownloader is null")); CHIP_ERROR status; auto block = ByteSpan(imageProcessor->mBlock.data(), imageProcessor->mBlock.size()); @@ -243,7 +237,7 @@ void OTAImageProcessorImpl::HandleStatus(CHIP_ERROR status) if (status == CHIP_NO_ERROR || status == CHIP_ERROR_BUFFER_TOO_SMALL) { mParams.downloadedBytes += mBlock.size(); - FetchNextData(reinterpret_cast(this)); + FetchNextData(0); } else if (status == CHIP_OTA_FETCH_ALREADY_SCHEDULED) { @@ -294,8 +288,8 @@ CHIP_ERROR OTAImageProcessorImpl::ConfirmCurrentImage() ReturnErrorOnFailure(DeviceLayer::ConfigurationMgr().GetSoftwareVersion(currentVersion)); if (currentVersion != targetVersion) { - ChipLogError(SoftwareUpdate, "Current sw version %" PRIu32 " is different than the expected sw version = %" PRIu32, - currentVersion, targetVersion); + ChipLogError(SoftwareUpdate, "Current sw version %lu is different than the expected sw version = %lu", currentVersion, + targetVersion); return CHIP_ERROR_INCORRECT_STATE; } @@ -414,8 +408,7 @@ void OTAImageProcessorImpl::FetchNextData(uint32_t context) OTAImageProcessorImpl & OTAImageProcessorImpl::GetDefaultInstance() { - static OTAImageProcessorImpl imageProcessor; - return imageProcessor; + return gImageProcessor; } } // namespace chip diff --git a/src/platform/nxp/k32w/common/OTATlvProcessor.h b/src/platform/nxp/k32w/common/OTATlvProcessor.h index 534da067148da8..1968f983eda14b 100644 --- a/src/platform/nxp/k32w/common/OTATlvProcessor.h +++ b/src/platform/nxp/k32w/common/OTATlvProcessor.h @@ -39,7 +39,8 @@ namespace chip { #define CHIP_OTA_PROCESSOR_IMG_COMMIT CHIP_ERROR_TLV_PROCESSOR(0x0A) #define CHIP_OTA_PROCESSOR_CB_NOT_REGISTERED CHIP_ERROR_TLV_PROCESSOR(0x0B) #define CHIP_OTA_PROCESSOR_EEPROM_OFFSET CHIP_ERROR_TLV_PROCESSOR(0x0C) -#define CHIP_OTA_PROCESSOR_START_IMAGE CHIP_ERROR_TLV_PROCESSOR(0x0D) +#define CHIP_OTA_PROCESSOR_EXTERNAL_STORAGE CHIP_ERROR_TLV_PROCESSOR(0x0D) +#define CHIP_OTA_PROCESSOR_START_IMAGE CHIP_ERROR_TLV_PROCESSOR(0x0E) // Descriptor constants inline constexpr size_t kVersionStringSize = 64; diff --git a/src/platform/nxp/k32w/k32w1/BLEManagerImpl.cpp b/src/platform/nxp/k32w/k32w1/BLEManagerImpl.cpp new file mode 100644 index 00000000000000..daac54fb5a16e7 --- /dev/null +++ b/src/platform/nxp/k32w/k32w1/BLEManagerImpl.cpp @@ -0,0 +1,106 @@ +/* + * + * Copyright (c) 2021-2023 Project CHIP Authors + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* this file behaves like a config.h, comes first */ +#include + +#if CHIP_DEVICE_CONFIG_ENABLE_CHIPOBLE + +/*! App to Host message queue for the Host Task */ +messaging_t gApp2Host_TaskQueue; +/*! HCI to Host message queue for the Host Task */ +messaging_t gHci2Host_TaskQueue; +/*! Event for the Host Task Queue */ +OSA_EVENT_HANDLE_DEFINE(gHost_TaskEvent); + +#include + +namespace chip { +namespace DeviceLayer { +namespace Internal { + +BLEManagerImpl BLEManagerImpl::sInstance; + +BLEManagerCommon * BLEManagerImpl::GetImplInstance() +{ + return &BLEManagerImpl::sInstance; +} + +CHIP_ERROR BLEManagerImpl::InitHostController(ble_generic_cb_fp cb_fp) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + + PLATFORM_InitBle(); + + (void) RNG_Init(); + RNG_SetPseudoRandomNoSeed(NULL); + + /* Has to be called after RNG_Init(), once seed is generated. */ + (void) Controller_SetRandomSeed(); + + /* Create BLE Host Task */ + VerifyOrExit(BLEManagerImpl::blekw_host_init() == CHIP_NO_ERROR, err = CHIP_ERROR_INCORRECT_STATE); + + VerifyOrExit(Hcit_Init(Ble_HciRecv) == gHciSuccess_c, err = CHIP_ERROR_INCORRECT_STATE); + + /* Set BD Address in Controller. Must be done after HCI init and before Host init. */ + Ble_SetBDAddr(); + + /* BLE Host Stack Init */ + VerifyOrExit(Ble_HostInitialize(cb_fp, Hcit_SendPacket) == gBleSuccess_c, err = CHIP_ERROR_INCORRECT_STATE); + + /* configure tx power to use in NBU specific to BLE */ + Controller_SetTxPowerLevelDbm(mAdvertisingDefaultTxPower_c, gAdvTxChannel_c); + Controller_SetTxPowerLevelDbm(mConnectionDefaultTxPower_c, gConnTxChannel_c); + Controller_ConfigureInvalidPduHandling(gLlInvalidPduHandlingType_c); + +exit: + return err; +} + +void BLEManagerImpl::Host_Task(osaTaskParam_t argument) +{ + Host_TaskHandler((void *) NULL); +} + +CHIP_ERROR BLEManagerImpl::blekw_host_init(void) +{ + /* Initialization of task related */ + if (KOSA_StatusSuccess != OSA_EventCreate((osa_event_handle_t) gHost_TaskEvent, TRUE)) + { + return CHIP_ERROR_NO_MEMORY; + } + + /* Initialization of task message queue */ + MSG_InitQueue(&gApp2Host_TaskQueue); + MSG_InitQueue(&gHci2Host_TaskQueue); + + /* Task creation */ + if (pdPASS != xTaskCreate(Host_Task, "hostTask", HOST_TASK_STACK_SIZE, (void *) 0, HOST_TASK_PRIORITY, NULL)) + { + return CHIP_ERROR_NO_MEMORY; + } + + return CHIP_NO_ERROR; +} + +} // namespace Internal +} // namespace DeviceLayer +} // namespace chip + +#endif /* CHIP_DEVICE_CONFIG_ENABLE_CHIPOBLE */ diff --git a/src/platform/nxp/k32w/k32w1/BLEManagerImpl.h b/src/platform/nxp/k32w/k32w1/BLEManagerImpl.h new file mode 100644 index 00000000000000..7aa4e9f1604b42 --- /dev/null +++ b/src/platform/nxp/k32w/k32w1/BLEManagerImpl.h @@ -0,0 +1,99 @@ +/* + * + * Copyright (c) 2021-2023 Project CHIP Authors + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#if CHIP_DEVICE_CONFIG_ENABLE_CHIPOBLE + +#include "RNG_Interface.h" +#include "fwk_messaging.h" +#include "fwk_os_abs.h" +#include "fwk_platform_ble.h" +#include "hci_transport.h" + +#include "ble_init.h" +#include "controller_api.h" +#include "controller_interface.h" + +#include + +/* host task configuration */ +#define HOST_TASK_PRIORITY (4U) +#define HOST_TASK_STACK_SIZE (gHost_TaskStackSize_c / sizeof(StackType_t)) + +namespace chip { +namespace DeviceLayer { +namespace Internal { + +using namespace chip::Ble; + +class BLEManagerImpl final : public BLEManagerCommon +{ +public: + // Allow the BLEManager interface class to delegate method calls to + // the implementation methods provided by this class. + friend BLEManager; + + CHIP_ERROR InitHostController(ble_generic_cb_fp cb_fp) override; + BLEManagerCommon * GetImplInstance() override; + +private: + static BLEManagerImpl sInstance; + + static CHIP_ERROR blekw_host_init(void); + static void Host_Task(osaTaskParam_t argument); + + BleLayer * _GetBleLayer(void); + + // ===== Members for internal use by the following friends. + friend BLEManager & BLEMgr(void); + friend BLEManagerImpl & BLEMgrImpl(void); +}; + +/** + * Returns a reference to the public interface of the BLEManager singleton object. + * + * Internal components should use this to access features of the BLEManager object + * that are common to all platforms. + */ +inline BLEManager & BLEMgr(void) +{ + return BLEManagerImpl::sInstance; +} + +/** + * Returns the platform-specific implementation of the BLEManager singleton object. + * + * Internal components can use this to gain access to features of the BLEManager + * that are specific to the K32W platforms. + */ +inline BLEManagerImpl & BLEMgrImpl(void) +{ + return BLEManagerImpl::sInstance; +} + +inline BleLayer * BLEManagerImpl::_GetBleLayer() +{ + return this; +} + +} // namespace Internal +} // namespace DeviceLayer +} // namespace chip + +#endif /* CHIP_DEVICE_CONFIG_ENABLE_CHIPOBLE */ diff --git a/src/platform/nxp/k32w/k32w1/BUILD.gn b/src/platform/nxp/k32w/k32w1/BUILD.gn new file mode 100644 index 00000000000000..effb6cb2fc9d1e --- /dev/null +++ b/src/platform/nxp/k32w/k32w1/BUILD.gn @@ -0,0 +1,112 @@ +# Copyright (c) 2021 Project CHIP Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import("//build_overrides/chip.gni") +import("//build_overrides/k32w1_sdk.gni") +import("${chip_root}/src/crypto/crypto.gni") +import("${chip_root}/src/platform/device.gni") +import("${k32w1_sdk_build_root}/k32w1_sdk.gni") + +assert(chip_device_platform == "k32w1") + +if (chip_enable_openthread) { + import("//build_overrides/openthread.gni") +} + +if (chip_crypto == "platform") { + import("//build_overrides/mbedtls.gni") +} + +static_library("k32w1") { + sources = [ + "../../../SingletonConfigurationManager.cpp", + "../common/BLEManagerCommon.cpp", + "../common/BLEManagerCommon.h", + "BLEManagerImpl.cpp", + "BLEManagerImpl.h", + "CHIPDevicePlatformConfig.h", + "CHIPDevicePlatformEvent.h", + "ConfigurationManagerImpl.cpp", + "ConfigurationManagerImpl.h", + "ConnectivityManagerImpl.cpp", + "ConnectivityManagerImpl.h", + "DefaultTestEventTriggerDelegate.cpp", + "DefaultTestEventTriggerDelegate.h", + "DiagnosticDataProviderImpl.cpp", + "DiagnosticDataProviderImpl.h", + "K32W1Config.cpp", + "K32W1Config.h", + "KeyValueStoreManagerImpl.cpp", + "KeyValueStoreManagerImpl.h", + "Logging.cpp", + "PlatformManagerImpl.cpp", + "PlatformManagerImpl.h", + "SystemTimeSupport.cpp", + "ble_function_mux.c", + "ram_storage.c", + "ram_storage.h", + ] + + public_deps = [ "${chip_root}/src/platform:platform_base" ] + + if (chip_with_low_power != 0) { + sources += [ "LowPowerHooks.cpp" ] + } + + if (chip_enable_ota_requestor) { + sources += [ + "../common/OTAImageProcessorImpl.cpp", + "../common/OTAImageProcessorImpl.h", + "../common/OTATlvProcessor.cpp", + "../common/OTATlvProcessor.h", + "OTAFirmwareProcessor.cpp", + "OTAFirmwareProcessor.h", + "OTAHooks.cpp", + ] + } + + if (chip_crypto == "platform") { + sources += [ + "CHIPCryptoPalK32W1.cpp", + "K32W1PersistentStorageOpKeystore.cpp", + "K32W1PersistentStorageOpKeystore.h", + ] + + public_deps += [ "${mbedtls_root}:mbedtls" ] + } + + deps = [] + + if (chip_enable_openthread) { + sources += [ + "../../../OpenThread/OpenThreadUtils.cpp", + "ThreadStackManagerImpl.cpp", + "ThreadStackManagerImpl.h", + ] + + if (chip_mdns == "platform") { + sources += [ + "../../../OpenThread/DnssdImpl.cpp", + "../../../OpenThread/OpenThreadDnssdImpl.cpp", + "../../../OpenThread/OpenThreadDnssdImpl.h", + ] + deps += [ "${chip_root}/src/lib/dnssd:platform_header" ] + } + } + + public_deps += [ "${chip_root}/src/crypto" ] + + public_configs = + [ "${chip_root}/src/lib/address_resolve:default_address_resolve_config" ] +} diff --git a/src/platform/nxp/k32w/k32w1/BlePlatformConfig.h b/src/platform/nxp/k32w/k32w1/BlePlatformConfig.h new file mode 100644 index 00000000000000..a003927c74626e --- /dev/null +++ b/src/platform/nxp/k32w/k32w1/BlePlatformConfig.h @@ -0,0 +1,36 @@ +/* + * + * Copyright (c) 2020 Project CHIP Authors + * Copyright (c) 2020 Google LLC. + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file + * Platform-specific configuration overrides for the CHIP BLE + * Layer on K32W platforms using the NXP SDK. + * + */ + +#pragma once + +// ==================== Platform Adaptations ==================== + +#define BLE_CONNECTION_OBJECT uint8_t +#define BLE_CONNECTION_UNINITIALIZED ((uint8_t) 0xFF) + +// ========== Platform-specific Configuration Overrides ========= + +/* none so far */ diff --git a/src/platform/nxp/k32w/k32w1/CHIPCryptoPalK32W1.cpp b/src/platform/nxp/k32w/k32w1/CHIPCryptoPalK32W1.cpp new file mode 100644 index 00000000000000..6aad4ae85ea0fc --- /dev/null +++ b/src/platform/nxp/k32w/k32w1/CHIPCryptoPalK32W1.cpp @@ -0,0 +1,1004 @@ +/* + * + * Copyright (c) 2020-2022 Project CHIP Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file + * mbedTLS based implementation of CHIP crypto primitives + */ + +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if defined(MBEDTLS_X509_CRT_PARSE_C) +#include +#endif // defined(MBEDTLS_X509_CRT_PARSE_C) +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "SecLib_ecp256.h" +#include "sss_crypto.h" + +namespace chip { +namespace Crypto { + +#define MAX_ERROR_STR_LEN 128 +#define NUM_BYTES_IN_SHA256_HASH 32 + +// In mbedTLS 3.0.0 direct access to structure fields was replaced with using MBEDTLS_PRIVATE macro. +#if (MBEDTLS_VERSION_NUMBER >= 0x03000000) +#define CHIP_CRYPTO_PAL_PRIVATE(x) MBEDTLS_PRIVATE(x) +#else +#define CHIP_CRYPTO_PAL_PRIVATE(x) x +#endif + +#if (MBEDTLS_VERSION_NUMBER >= 0x03000000 && MBEDTLS_VERSION_NUMBER < 0x03010000) +#define CHIP_CRYPTO_PAL_PRIVATE_X509(x) MBEDTLS_PRIVATE(x) +#else +#define CHIP_CRYPTO_PAL_PRIVATE_X509(x) x +#endif + +typedef struct +{ + bool mInitialized; + bool mDRBGSeeded; + mbedtls_ctr_drbg_context mDRBGCtxt; + mbedtls_entropy_context mEntropy; +} EntropyContext; + +static EntropyContext gsEntropyContext; + +static void _log_mbedTLS_error(int error_code) +{ + if (error_code != 0) + { +#if defined(MBEDTLS_ERROR_C) + char error_str[MAX_ERROR_STR_LEN]; + mbedtls_strerror(error_code, error_str, sizeof(error_str)); + ChipLogError(Crypto, "mbedTLS error: %s", error_str); +#else + // Error codes defined in 16-bit negative hex numbers. Ease lookup by printing likewise + ChipLogError(Crypto, "mbedTLS error: -0x%04X", -static_cast(error_code)); +#endif + } +} + +static bool _isValidTagLength(size_t tag_length) +{ + if (tag_length == 8 || tag_length == 12 || tag_length == 16) + { + return true; + } + return false; +} + +static bool _isValidKeyLength(size_t length) +{ + // 16 bytes key for AES-CCM-128, 32 for AES-CCM-256 + if (length == 16 || length == 32) + { + return true; + } + return false; +} + +CHIP_ERROR AES_CCM_encrypt(const uint8_t * plaintext, size_t plaintext_length, const uint8_t * aad, size_t aad_length, + const Aes128KeyHandle & key, const uint8_t * nonce, size_t nonce_length, uint8_t * ciphertext, + uint8_t * tag, size_t tag_length) +{ + CHIP_ERROR error = CHIP_NO_ERROR; + int result = 1; + + mbedtls_ccm_context context; + mbedtls_ccm_init(&context); + + VerifyOrExit(plaintext != nullptr || plaintext_length == 0, error = CHIP_ERROR_INVALID_ARGUMENT); + VerifyOrExit(ciphertext != nullptr || plaintext_length == 0, error = CHIP_ERROR_INVALID_ARGUMENT); + VerifyOrExit(nonce != nullptr, error = CHIP_ERROR_INVALID_ARGUMENT); + VerifyOrExit(nonce_length > 0, error = CHIP_ERROR_INVALID_ARGUMENT); + VerifyOrExit(tag != nullptr, error = CHIP_ERROR_INVALID_ARGUMENT); + VerifyOrExit(_isValidTagLength(tag_length), error = CHIP_ERROR_INVALID_ARGUMENT); + if (aad_length > 0) + { + VerifyOrExit(aad != nullptr, error = CHIP_ERROR_INVALID_ARGUMENT); + } + + // Size of key is expressed in bits, hence the multiplication by 8. + result = mbedtls_ccm_setkey(&context, MBEDTLS_CIPHER_ID_AES, key.As(), sizeof(Aes128KeyByteArray) * 8); + VerifyOrExit(result == 0, error = CHIP_ERROR_INTERNAL); + + // Encrypt + result = mbedtls_ccm_encrypt_and_tag(&context, plaintext_length, Uint8::to_const_uchar(nonce), nonce_length, + Uint8::to_const_uchar(aad), aad_length, Uint8::to_const_uchar(plaintext), + Uint8::to_uchar(ciphertext), Uint8::to_uchar(tag), tag_length); + _log_mbedTLS_error(result); + VerifyOrExit(result == 0, error = CHIP_ERROR_INTERNAL); + +exit: + mbedtls_ccm_free(&context); + return error; +} + +CHIP_ERROR AES_CCM_decrypt(const uint8_t * ciphertext, size_t ciphertext_len, const uint8_t * aad, size_t aad_len, + const uint8_t * tag, size_t tag_length, const Aes128KeyHandle & key, const uint8_t * nonce, + size_t nonce_length, uint8_t * plaintext) +{ + CHIP_ERROR error = CHIP_NO_ERROR; + int result = 1; + + mbedtls_ccm_context context; + mbedtls_ccm_init(&context); + + VerifyOrExit(plaintext != nullptr || ciphertext_len == 0, error = CHIP_ERROR_INVALID_ARGUMENT); + VerifyOrExit(ciphertext != nullptr || ciphertext_len == 0, error = CHIP_ERROR_INVALID_ARGUMENT); + VerifyOrExit(tag != nullptr, error = CHIP_ERROR_INVALID_ARGUMENT); + VerifyOrExit(_isValidTagLength(tag_length), error = CHIP_ERROR_INVALID_ARGUMENT); + VerifyOrExit(nonce != nullptr, error = CHIP_ERROR_INVALID_ARGUMENT); + VerifyOrExit(nonce_length > 0, error = CHIP_ERROR_INVALID_ARGUMENT); + if (aad_len > 0) + { + VerifyOrExit(aad != nullptr, error = CHIP_ERROR_INVALID_ARGUMENT); + } + + // Size of key is expressed in bits, hence the multiplication by 8. + result = mbedtls_ccm_setkey(&context, MBEDTLS_CIPHER_ID_AES, key.As(), sizeof(Aes128KeyByteArray) * 8); + VerifyOrExit(result == 0, error = CHIP_ERROR_INTERNAL); + + // Decrypt + result = mbedtls_ccm_auth_decrypt(&context, ciphertext_len, Uint8::to_const_uchar(nonce), nonce_length, + Uint8::to_const_uchar(aad), aad_len, Uint8::to_const_uchar(ciphertext), + Uint8::to_uchar(plaintext), Uint8::to_const_uchar(tag), tag_length); + _log_mbedTLS_error(result); + VerifyOrExit(result == 0, error = CHIP_ERROR_INTERNAL); + +exit: + mbedtls_ccm_free(&context); + return error; +} + +CHIP_ERROR Hash_SHA256(const uint8_t * data, const size_t data_length, uint8_t * out_buffer) +{ + // zero data length hash is supported. + VerifyOrReturnError(data != nullptr, CHIP_ERROR_INVALID_ARGUMENT); + VerifyOrReturnError(out_buffer != nullptr, CHIP_ERROR_INVALID_ARGUMENT); + +#if (MBEDTLS_VERSION_NUMBER >= 0x03000000) + const int result = mbedtls_sha256(Uint8::to_const_uchar(data), data_length, Uint8::to_uchar(out_buffer), 0); +#else + const int result = mbedtls_sha256_ret(Uint8::to_const_uchar(data), data_length, Uint8::to_uchar(out_buffer), 0); +#endif + + VerifyOrReturnError(result == 0, CHIP_ERROR_INTERNAL); + + return CHIP_NO_ERROR; +} + +CHIP_ERROR Hash_SHA1(const uint8_t * data, const size_t data_length, uint8_t * out_buffer) +{ + // zero data length hash is supported. + VerifyOrReturnError(out_buffer != nullptr, CHIP_ERROR_INVALID_ARGUMENT); + +#if (MBEDTLS_VERSION_NUMBER >= 0x03000000) + const int result = mbedtls_sha1(Uint8::to_const_uchar(data), data_length, Uint8::to_uchar(out_buffer)); +#else + const int result = mbedtls_sha1_ret(Uint8::to_const_uchar(data), data_length, Uint8::to_uchar(out_buffer)); +#endif + + VerifyOrReturnError(result == 0, CHIP_ERROR_INTERNAL); + + return CHIP_NO_ERROR; +} + +static_assert(kMAX_Hash_SHA256_Context_Size >= sizeof(mbedtls_sha256_context), + "kMAX_Hash_SHA256_Context_Size is too small for the size of underlying mbedtls_sha256_context"); + +static inline mbedtls_sha256_context * to_inner_hash_sha256_context(HashSHA256OpaqueContext * context) +{ + return SafePointerCast(context); +} + +Hash_SHA256_stream::Hash_SHA256_stream(void) +{ + mbedtls_sha256_context * context = to_inner_hash_sha256_context(&mContext); + mbedtls_sha256_init(context); +} + +Hash_SHA256_stream::~Hash_SHA256_stream(void) +{ + mbedtls_sha256_context * context = to_inner_hash_sha256_context(&mContext); + mbedtls_sha256_free(context); + Clear(); +} + +CHIP_ERROR Hash_SHA256_stream::Begin(void) +{ + mbedtls_sha256_context * const context = to_inner_hash_sha256_context(&mContext); + +#if (MBEDTLS_VERSION_NUMBER >= 0x03000000) + const int result = mbedtls_sha256_starts(context, 0); +#else + const int result = mbedtls_sha256_starts_ret(context, 0); +#endif + + VerifyOrReturnError(result == 0, CHIP_ERROR_INTERNAL); + + return CHIP_NO_ERROR; +} + +CHIP_ERROR Hash_SHA256_stream::AddData(const ByteSpan data) +{ + mbedtls_sha256_context * const context = to_inner_hash_sha256_context(&mContext); + +#if (MBEDTLS_VERSION_NUMBER >= 0x03000000) + const int result = mbedtls_sha256_update(context, Uint8::to_const_uchar(data.data()), data.size()); +#else + const int result = mbedtls_sha256_update_ret(context, Uint8::to_const_uchar(data.data()), data.size()); +#endif + + VerifyOrReturnError(result == 0, CHIP_ERROR_INTERNAL); + + return CHIP_NO_ERROR; +} + +CHIP_ERROR Hash_SHA256_stream::GetDigest(MutableByteSpan & out_buffer) +{ + mbedtls_sha256_context * context = to_inner_hash_sha256_context(&mContext); + + // Back-up context as we are about to finalize the hash to extract digest. + mbedtls_sha256_context previous_ctx; + mbedtls_sha256_init(&previous_ctx); + mbedtls_sha256_clone(&previous_ctx, context); + + // Pad + compute digest, then finalize context. It is restored next line to continue. + CHIP_ERROR result = Finish(out_buffer); + + // Restore context prior to finalization. + mbedtls_sha256_clone(context, &previous_ctx); + mbedtls_sha256_free(&previous_ctx); + + return result; +} + +CHIP_ERROR Hash_SHA256_stream::Finish(MutableByteSpan & out_buffer) +{ + VerifyOrReturnError(out_buffer.size() >= kSHA256_Hash_Length, CHIP_ERROR_BUFFER_TOO_SMALL); + mbedtls_sha256_context * const context = to_inner_hash_sha256_context(&mContext); + +#if (MBEDTLS_VERSION_NUMBER >= 0x03000000) + const int result = mbedtls_sha256_finish(context, Uint8::to_uchar(out_buffer.data())); +#else + const int result = mbedtls_sha256_finish_ret(context, Uint8::to_uchar(out_buffer.data())); +#endif + + VerifyOrReturnError(result == 0, CHIP_ERROR_INTERNAL); + out_buffer = out_buffer.SubSpan(0, kSHA256_Hash_Length); + + return CHIP_NO_ERROR; +} + +void Hash_SHA256_stream::Clear(void) +{ + mbedtls_platform_zeroize(this, sizeof(*this)); +} + +CHIP_ERROR HKDF_sha::HKDF_SHA256(const uint8_t * secret, const size_t secret_length, const uint8_t * salt, const size_t salt_length, + const uint8_t * info, const size_t info_length, uint8_t * out_buffer, size_t out_length) +{ + VerifyOrReturnError(secret != nullptr, CHIP_ERROR_INVALID_ARGUMENT); + VerifyOrReturnError(secret_length > 0, CHIP_ERROR_INVALID_ARGUMENT); + + // Salt is optional + if (salt_length > 0) + { + VerifyOrReturnError(salt != nullptr, CHIP_ERROR_INVALID_ARGUMENT); + } + + VerifyOrReturnError(info_length > 0, CHIP_ERROR_INVALID_ARGUMENT); + VerifyOrReturnError(info != nullptr, CHIP_ERROR_INVALID_ARGUMENT); + VerifyOrReturnError(out_length > 0, CHIP_ERROR_INVALID_ARGUMENT); + VerifyOrReturnError(out_buffer != nullptr, CHIP_ERROR_INVALID_ARGUMENT); + + const mbedtls_md_info_t * const md = mbedtls_md_info_from_type(MBEDTLS_MD_SHA256); + VerifyOrReturnError(md != nullptr, CHIP_ERROR_INTERNAL); + + const int result = mbedtls_hkdf(md, Uint8::to_const_uchar(salt), salt_length, Uint8::to_const_uchar(secret), secret_length, + Uint8::to_const_uchar(info), info_length, Uint8::to_uchar(out_buffer), out_length); + _log_mbedTLS_error(result); + VerifyOrReturnError(result == 0, CHIP_ERROR_INTERNAL); + + return CHIP_NO_ERROR; +} + +CHIP_ERROR HMAC_sha::HMAC_SHA256(const uint8_t * key, size_t key_length, const uint8_t * message, size_t message_length, + uint8_t * out_buffer, size_t out_length) +{ + VerifyOrReturnError(key != nullptr, CHIP_ERROR_INVALID_ARGUMENT); + VerifyOrReturnError(key_length > 0, CHIP_ERROR_INVALID_ARGUMENT); + VerifyOrReturnError(message != nullptr, CHIP_ERROR_INVALID_ARGUMENT); + VerifyOrReturnError(message_length > 0, CHIP_ERROR_INVALID_ARGUMENT); + VerifyOrReturnError(out_length >= kSHA256_Hash_Length, CHIP_ERROR_INVALID_ARGUMENT); + VerifyOrReturnError(out_buffer != nullptr, CHIP_ERROR_INVALID_ARGUMENT); + + const mbedtls_md_info_t * const md = mbedtls_md_info_from_type(MBEDTLS_MD_SHA256); + VerifyOrReturnError(md != nullptr, CHIP_ERROR_INTERNAL); + + const int result = + mbedtls_md_hmac(md, Uint8::to_const_uchar(key), key_length, Uint8::to_const_uchar(message), message_length, out_buffer); + + _log_mbedTLS_error(result); + VerifyOrReturnError(result == 0, CHIP_ERROR_INTERNAL); + + return CHIP_NO_ERROR; +} + +CHIP_ERROR PBKDF2_sha256::pbkdf2_sha256(const uint8_t * password, size_t plen, const uint8_t * salt, size_t slen, + unsigned int iteration_count, uint32_t key_length, uint8_t * output) +{ + CHIP_ERROR error = CHIP_NO_ERROR; + int result = 0; + const mbedtls_md_info_t * md_info; + mbedtls_md_context_t md_ctxt; + constexpr int use_hmac = 1; + + bool free_md_ctxt = false; + + VerifyOrExit(password != nullptr, error = CHIP_ERROR_INVALID_ARGUMENT); + VerifyOrExit(plen > 0, error = CHIP_ERROR_INVALID_ARGUMENT); + VerifyOrExit(salt != nullptr, error = CHIP_ERROR_INVALID_ARGUMENT); + VerifyOrExit(slen >= kSpake2p_Min_PBKDF_Salt_Length, error = CHIP_ERROR_INVALID_ARGUMENT); + VerifyOrExit(slen <= kSpake2p_Max_PBKDF_Salt_Length, error = CHIP_ERROR_INVALID_ARGUMENT); + VerifyOrExit(key_length > 0, error = CHIP_ERROR_INVALID_ARGUMENT); + VerifyOrExit(output != nullptr, error = CHIP_ERROR_INVALID_ARGUMENT); + + md_info = mbedtls_md_info_from_type(MBEDTLS_MD_SHA256); + VerifyOrExit(md_info != nullptr, error = CHIP_ERROR_INTERNAL); + + mbedtls_md_init(&md_ctxt); + free_md_ctxt = true; + + result = mbedtls_md_setup(&md_ctxt, md_info, use_hmac); + VerifyOrExit(result == 0, error = CHIP_ERROR_INTERNAL); + + result = mbedtls_pkcs5_pbkdf2_hmac(&md_ctxt, Uint8::to_const_uchar(password), plen, Uint8::to_const_uchar(salt), slen, + iteration_count, key_length, Uint8::to_uchar(output)); + + VerifyOrExit(result == 0, error = CHIP_ERROR_INTERNAL); + +exit: + _log_mbedTLS_error(result); + + if (free_md_ctxt) + { + mbedtls_md_free(&md_ctxt); + } + + return error; +} + +static EntropyContext * get_entropy_context() +{ + if (!gsEntropyContext.mInitialized) + { + mbedtls_entropy_init(&gsEntropyContext.mEntropy); + mbedtls_ctr_drbg_init(&gsEntropyContext.mDRBGCtxt); + + gsEntropyContext.mInitialized = true; + } + + return &gsEntropyContext; +} + +static mbedtls_ctr_drbg_context * get_drbg_context() +{ + EntropyContext * const context = get_entropy_context(); + + mbedtls_ctr_drbg_context * const drbgCtxt = &context->mDRBGCtxt; + + if (!context->mDRBGSeeded) + { + const int status = mbedtls_ctr_drbg_seed(drbgCtxt, mbedtls_entropy_func, &context->mEntropy, nullptr, 0); + if (status != 0) + { + _log_mbedTLS_error(status); + return nullptr; + } + + context->mDRBGSeeded = true; + } + + return drbgCtxt; +} + +CHIP_ERROR add_entropy_source(entropy_source fn_source, void * p_source, size_t threshold) +{ + VerifyOrReturnError(fn_source != nullptr, CHIP_ERROR_INVALID_ARGUMENT); + + EntropyContext * const entropy_ctxt = get_entropy_context(); + VerifyOrReturnError(entropy_ctxt != nullptr, CHIP_ERROR_INTERNAL); + + const int result = + mbedtls_entropy_add_source(&entropy_ctxt->mEntropy, fn_source, p_source, threshold, MBEDTLS_ENTROPY_SOURCE_STRONG); + VerifyOrReturnError(result == 0, CHIP_ERROR_INTERNAL); + return CHIP_NO_ERROR; +} + +CHIP_ERROR DRBG_get_bytes(uint8_t * out_buffer, const size_t out_length) +{ + VerifyOrReturnError(out_buffer != nullptr, CHIP_ERROR_INVALID_ARGUMENT); + VerifyOrReturnError(out_length > 0, CHIP_ERROR_INVALID_ARGUMENT); + + mbedtls_ctr_drbg_context * const drbg_ctxt = get_drbg_context(); + VerifyOrReturnError(drbg_ctxt != nullptr, CHIP_ERROR_INTERNAL); + + const int result = mbedtls_ctr_drbg_random(drbg_ctxt, Uint8::to_uchar(out_buffer), out_length); + VerifyOrReturnError(result == 0, CHIP_ERROR_INTERNAL); + + return CHIP_NO_ERROR; +} + +static int CryptoRNG(void * ctxt, uint8_t * out_buffer, size_t out_length) +{ + return (chip::Crypto::DRBG_get_bytes(out_buffer, out_length) == CHIP_NO_ERROR) ? 0 : 1; +} + +mbedtls_ecp_group_id MapECPGroupId(SupportedECPKeyTypes keyType) +{ + switch (keyType) + { + case SupportedECPKeyTypes::ECP256R1: + return MBEDTLS_ECP_DP_SECP256R1; + default: + return MBEDTLS_ECP_DP_NONE; + } +} + +static inline sss_sscp_object_t * to_keypair(P256KeypairContext * context) +{ + return SafePointerCast(context); +} + +static inline const sss_sscp_object_t * to_const_keypair(const P256KeypairContext * context) +{ + return SafePointerCast(context); +} + +CHIP_ERROR P256Keypair::ECDSA_sign_msg(const uint8_t * msg, const size_t msg_length, P256ECDSASignature & out_signature) const +{ + CHIP_ERROR error = CHIP_NO_ERROR; + sss_sscp_asymmetric_t asyc; + size_t signatureSize = kP256_ECDSA_Signature_Length_Raw; + + VerifyOrReturnError(mInitialized, CHIP_ERROR_WELL_UNINITIALIZED); + VerifyOrReturnError((msg != nullptr) && (msg_length > 0), CHIP_ERROR_INVALID_ARGUMENT); + + uint8_t digest[kSHA256_Hash_Length]; + memset(&digest[0], 0, sizeof(digest)); + ReturnErrorOnFailure(Hash_SHA256(msg, msg_length, &digest[0])); + + sss_sscp_object_t * keypair = to_keypair(&mKeypair); + + VerifyOrExit((sss_sscp_asymmetric_context_init(&asyc, &g_sssSession, keypair, kAlgorithm_SSS_ECDSA_SHA256, kMode_SSS_Sign) == + kStatus_SSS_Success), + CHIP_ERROR_INTERNAL); + VerifyOrExit((sss_sscp_asymmetric_sign_digest(&asyc, digest, kP256_FE_Length, out_signature.Bytes(), &signatureSize) == + kStatus_SSS_Success), + CHIP_ERROR_INTERNAL); + VerifyOrExit(out_signature.SetLength(kP256_ECDSA_Signature_Length_Raw) == CHIP_NO_ERROR, error = CHIP_ERROR_INTERNAL); + +exit: + (void) sss_sscp_asymmetric_context_free(&asyc); + return error; +} + +CHIP_ERROR P256PublicKey::ECDSA_validate_msg_signature(const uint8_t * msg, const size_t msg_length, + const P256ECDSASignature & signature) const +{ +#if defined(MBEDTLS_ECDSA_C) + VerifyOrReturnError((msg != nullptr) && (msg_length > 0), CHIP_ERROR_INVALID_ARGUMENT); + + uint8_t digest[kSHA256_Hash_Length]; + memset(&digest[0], 0, sizeof(digest)); + ReturnErrorOnFailure(Hash_SHA256(msg, msg_length, &digest[0])); + + return ECDSA_validate_hash_signature(&digest[0], sizeof(digest), signature); +#else + return CHIP_ERROR_NOT_IMPLEMENTED; +#endif +} + +CHIP_ERROR P256PublicKey::ECDSA_validate_hash_signature(const uint8_t * hash, const size_t hash_length, + const P256ECDSASignature & signature) const +{ + + VerifyOrReturnError(hash != nullptr, CHIP_ERROR_INVALID_ARGUMENT); + VerifyOrReturnError(hash_length == kSHA256_Hash_Length, CHIP_ERROR_INVALID_ARGUMENT); + VerifyOrReturnError(signature.Length() == kP256_ECDSA_Signature_Length_Raw, CHIP_ERROR_INVALID_ARGUMENT); + + CHIP_ERROR error = CHIP_NO_ERROR; + + sss_sscp_object_t ecdsaPublic; + sss_sscp_asymmetric_t asyc; + bool bFreeAsyncCtx = false; + + size_t keySize = SSS_ECP_KEY_SZ(kP256_PrivateKey_Length); + + VerifyOrReturnError(sss_sscp_key_object_init(&ecdsaPublic, &g_keyStore) == kStatus_SSS_Success, CHIP_ERROR_INTERNAL); + + VerifyOrReturnError(sss_sscp_key_object_allocate_handle(&ecdsaPublic, 0u, kSSS_KeyPart_Public, kSSS_CipherType_EC_NIST_P, + keySize, SSS_KEYPROP_OPERATION_ASYM) == kStatus_SSS_Success, + CHIP_ERROR_INTERNAL); + + // The first byte of the public key is the uncompressed marker + VerifyOrExit(SSS_KEY_STORE_SET_KEY(&ecdsaPublic, Uint8::to_const_uchar(*this) + 1, Length() - 1, keySize * 8, + (uint32_t) kSSS_KeyPart_Public) == kStatus_SSS_Success, + CHIP_ERROR_INTERNAL); + + VerifyOrExit(sss_sscp_asymmetric_context_init(&asyc, &g_sssSession, &ecdsaPublic, kAlgorithm_SSS_ECDSA_SHA256, + kMode_SSS_Verify) == kStatus_SSS_Success, + CHIP_ERROR_INTERNAL); + + bFreeAsyncCtx = true; + VerifyOrExit(sss_sscp_asymmetric_verify_digest(&asyc, (uint8_t *) hash, hash_length, (uint8_t *) signature.ConstBytes(), + signature.Length()) == kStatus_SSS_Success, + CHIP_ERROR_INTERNAL); +exit: + + if (bFreeAsyncCtx) + { + /* Need to be very careful, if we try to free something that is not initialized with success we will get an hw fault */ + (void) sss_sscp_asymmetric_context_free(&asyc); + } + (void) SSS_KEY_OBJ_FREE(&ecdsaPublic); + + return error; +} + +CHIP_ERROR P256Keypair::ECDH_derive_secret(const P256PublicKey & remote_public_key, P256ECDHDerivedSecret & out_secret) const +{ + + CHIP_ERROR error = CHIP_NO_ERROR; + size_t secret_length = (out_secret.Length() == 0) ? out_secret.Capacity() : out_secret.Length(); + + sss_sscp_object_t * keypair = to_keypair(&mKeypair); + size_t coordinateLen = kP256_PrivateKey_Length; + size_t coordinateBitsLen = coordinateLen * 8; + size_t keySize = SSS_ECP_KEY_SZ(kP256_PrivateKey_Length); + + sss_sscp_derive_key_t dCtx; + sss_sscp_object_t pEcdhPubKey; + sss_sscp_object_t sharedSecret; + + bool bFreeSharedSecret = false; + bool bFreeDeriveContex = false; + + VerifyOrExit(mInitialized, error = CHIP_ERROR_WELL_UNINITIALIZED); + + /* Remote public key */ + VerifyOrReturnError(sss_sscp_key_object_init(&pEcdhPubKey, &g_keyStore) == kStatus_SSS_Success, CHIP_ERROR_INTERNAL); + VerifyOrReturnError(sss_sscp_key_object_allocate_handle(&pEcdhPubKey, 0u, kSSS_KeyPart_Public, kSSS_CipherType_EC_NIST_P, + keySize, SSS_KEYPROP_OPERATION_KDF) == kStatus_SSS_Success, + CHIP_ERROR_INTERNAL); + + // The first byte of the public key is the uncompressed marker + VerifyOrExit(SSS_KEY_STORE_SET_KEY(&pEcdhPubKey, Uint8::to_const_uchar(remote_public_key) + 1, keySize, coordinateBitsLen, + kSSS_KeyPart_Public) == kStatus_SSS_Success, + error = CHIP_ERROR_INTERNAL); + + /* Shared secret */ + VerifyOrExit(sss_sscp_key_object_init(&sharedSecret, &g_keyStore) == kStatus_SSS_Success, error = CHIP_ERROR_INTERNAL); + VerifyOrExit(sss_sscp_key_object_allocate_handle(&sharedSecret, 0u, kSSS_KeyPart_Default, kSSS_CipherType_AES, coordinateLen, + SSS_KEYPROP_OPERATION_NONE) == kStatus_SSS_Success, + error = CHIP_ERROR_INTERNAL); + bFreeSharedSecret = true; + + /* Secret Key generated inside SSS */ + VerifyOrExit(sss_sscp_derive_key_context_init(&dCtx, &g_sssSession, keypair, kAlgorithm_SSS_ECDH, + kMode_SSS_ComputeSharedSecret) == kStatus_SSS_Success, + error = CHIP_ERROR_INTERNAL); + bFreeDeriveContex = true; + + VerifyOrExit(sss_sscp_asymmetric_dh_derive_key(&dCtx, &pEcdhPubKey, &sharedSecret) == kStatus_SSS_Success, + error = CHIP_ERROR_INTERNAL); + + VerifyOrExit(SSS_KEY_STORE_GET_PUBKEY(&sharedSecret, out_secret.Bytes(), &coordinateLen, &coordinateBitsLen) == + kStatus_SSS_Success, + error = CHIP_ERROR_INTERNAL); + SuccessOrExit(error = out_secret.SetLength(secret_length)); + +exit: + (void) SSS_KEY_OBJ_FREE(&pEcdhPubKey); + + /* Need to be very careful, if we try to free something that is not initialized with success we will get a hw fault */ + if (bFreeSharedSecret) + (void) SSS_KEY_OBJ_FREE(&sharedSecret); + if (bFreeDeriveContex) + (void) sss_sscp_derive_key_context_free(&dCtx); + + return error; +} + +void ClearSecretData(uint8_t * buf, size_t len) +{ + mbedtls_platform_zeroize(buf, len); +} + +// THE BELOW IS FROM `third_party/openthread/repo/third_party/mbedtls/repo/library/constant_time.c` since +// mbedtls_ct_memcmp is not available on Linux somehow :( +int mbedtls_ct_memcmp_copy(const void * a, const void * b, size_t n) +{ + size_t i; + volatile const unsigned char * A = (volatile const unsigned char *) a; + volatile const unsigned char * B = (volatile const unsigned char *) b; + volatile unsigned char diff = 0; + + for (i = 0; i < n; i++) + { + /* Read volatile data in order before computing diff. + * This avoids IAR compiler warning: + * 'the order of volatile accesses is undefined ..' */ + unsigned char x = A[i], y = B[i]; + diff |= x ^ y; + } + + return ((int) diff); +} + +bool IsBufferContentEqualConstantTime(const void * a, const void * b, size_t n) +{ + return mbedtls_ct_memcmp_copy(a, b, n) == 0; +} + +CHIP_ERROR P256Keypair::Initialize(ECPKeyTarget key_target) +{ + + CHIP_ERROR error = CHIP_NO_ERROR; + + size_t keyBitsLen = kP256_PrivateKey_Length * 8; + size_t keySize = SSS_ECP_KEY_SZ(kP256_PrivateKey_Length); + + Clear(); + + sss_sscp_object_t * keypair = to_keypair(&mKeypair); + + VerifyOrReturnError(sss_sscp_key_object_init(keypair, &g_keyStore) == kStatus_SSS_Success, CHIP_ERROR_INTERNAL); + + VerifyOrReturnError(sss_sscp_key_object_allocate_handle( + keypair, 0x0u, kSSS_KeyPart_Pair, kSSS_CipherType_EC_NIST_P, 3 * kP256_PrivateKey_Length, + SSS_KEYPROP_OPERATION_KDF | SSS_KEYPROP_OPERATION_ASYM) == kStatus_SSS_Success, + error = CHIP_ERROR_INTERNAL); + + VerifyOrExit(SSS_ECP_GENERATE_KEY(keypair, keyBitsLen) == kStatus_SSS_Success, error = CHIP_ERROR_INTERNAL); + + // The first byte of the public key is the uncompressed marker + Uint8::to_uchar(mPublicKey)[0] = 0x04; + + // Extract public key, write from the second byte + VerifyOrExit(SSS_KEY_STORE_GET_PUBKEY(keypair, Uint8::to_uchar(mPublicKey) + 1, &keySize, &keyBitsLen) == kStatus_SSS_Success, + CHIP_ERROR_INTERNAL); + + mInitialized = true; + +exit: + if (mInitialized != true) + (void) SSS_KEY_OBJ_FREE(keypair); + + return error; +} + +CHIP_ERROR P256Keypair::Serialize(P256SerializedKeypair & output) const +{ + return CHIP_ERROR_NOT_IMPLEMENTED; +} + +CHIP_ERROR P256Keypair::Deserialize(P256SerializedKeypair & input) +{ + Encoding::BufferWriter bbuf(mPublicKey, mPublicKey.Length()); + CHIP_ERROR error = CHIP_NO_ERROR; + + Clear(); + + const uint8_t * privkey = input.ConstBytes() + mPublicKey.Length(); + sss_sscp_object_t * keypair = to_keypair(&mKeypair); + + VerifyOrExit(input.Length() == mPublicKey.Length() + kP256_PrivateKey_Length, error = CHIP_ERROR_INVALID_ARGUMENT); + bbuf.Put(input.ConstBytes(), mPublicKey.Length()); + VerifyOrExit(bbuf.Fit(), error = CHIP_ERROR_NO_MEMORY); + + /* must load plain text private key inside SSS */ + VerifyOrExit((sss_sscp_key_object_init(keypair, &g_keyStore) == kStatus_SSS_Success), error = CHIP_ERROR_INTERNAL); + + /* Allocate key handle */ + VerifyOrExit(sss_sscp_key_object_allocate_handle(keypair, 0x0u, kSSS_KeyPart_Private, kSSS_CipherType_EC_NIST_P, + kP256_PrivateKey_Length, SSS_KEYPROP_OPERATION_ASYM) == kStatus_SSS_Success, + error = CHIP_ERROR_INTERNAL); + + if (SSS_KEY_STORE_SET_KEY(keypair, privkey, kP256_PrivateKey_Length, kP256_PrivateKey_Length * 8, kSSS_KeyPart_Private) != + kStatus_SSS_Success) + { + (void) SSS_KEY_OBJ_FREE(keypair); + error = CHIP_ERROR_INTERNAL; + } + else + { + mInitialized = true; + } + +exit: + return error; +} +void P256Keypair::Clear() +{ + if (mInitialized) + { + sss_sscp_object_t * keypair = to_keypair(&mKeypair); + (void) SSS_KEY_OBJ_FREE(keypair); + mInitialized = false; + } +} + +P256Keypair::~P256Keypair() +{ + Clear(); +} + +CHIP_ERROR P256Keypair::NewCertificateSigningRequest(uint8_t * out_csr, size_t & csr_length) const +{ + VerifyOrReturnError(mInitialized, CHIP_ERROR_WELL_UNINITIALIZED); + + MutableByteSpan csr(out_csr, csr_length); + CHIP_ERROR err = GenerateCertificateSigningRequest(this, csr); + csr_length = (CHIP_NO_ERROR == err) ? csr.size() : 0; + return err; +} + +typedef struct Spake2p_Context +{ + ecp256Point_t M; + ecp256Point_t N; + ecp256Point_t X; + ecp256Point_t Y; + ecp256Point_t L; + ecp256Point_t Z; + ecp256Point_t V; + + big_int256_t w0; + big_int256_t w1; + big_int256_t xy; + big_int256_t tempbn; + +} Spake2p_Context; + +static inline Spake2p_Context * to_inner_spake2p_context(Spake2pOpaqueContext * context) +{ + return SafePointerCast(context); +} + +CHIP_ERROR Spake2p_P256_SHA256_HKDF_HMAC::InitInternal(void) +{ + CHIP_ERROR error = CHIP_NO_ERROR; + int result = 0; + + Spake2p_Context * context = to_inner_spake2p_context(&mSpake2pContext); + memset(context, 0, sizeof(Spake2p_Context)); + + M = &context->M; + N = &context->N; + X = &context->X; + Y = &context->Y; + L = &context->L; + V = &context->V; + Z = &context->Z; + + w0 = &context->w0; + w1 = &context->w1; + xy = &context->xy; + tempbn = &context->tempbn; + + return error; + +exit: + _log_mbedTLS_error(result); + Clear(); + return error; +} + +void Spake2p_P256_SHA256_HKDF_HMAC::Clear() +{ + VerifyOrReturn(state != CHIP_SPAKE2P_STATE::PREINIT); + + state = CHIP_SPAKE2P_STATE::PREINIT; +} + +CHIP_ERROR Spake2p_P256_SHA256_HKDF_HMAC::Mac(const uint8_t * key, size_t key_len, const uint8_t * in, size_t in_len, + MutableByteSpan & out_span) +{ + HMAC_sha hmac; + VerifyOrReturnError(out_span.size() >= kSHA256_Hash_Length, CHIP_ERROR_BUFFER_TOO_SMALL); + ReturnErrorOnFailure(hmac.HMAC_SHA256(key, key_len, in, in_len, out_span.data(), kSHA256_Hash_Length)); + out_span = out_span.SubSpan(0, kSHA256_Hash_Length); + return CHIP_NO_ERROR; +} + +CHIP_ERROR Spake2p_P256_SHA256_HKDF_HMAC::MacVerify(const uint8_t * key, size_t key_len, const uint8_t * mac, size_t mac_len, + const uint8_t * in, size_t in_len) +{ + CHIP_ERROR error = CHIP_NO_ERROR; + int result = 0; + + uint8_t computed_mac[kSHA256_Hash_Length]; + MutableByteSpan computed_mac_span{ computed_mac }; + VerifyOrExit(mac_len == kSHA256_Hash_Length, error = CHIP_ERROR_INVALID_ARGUMENT); + + SuccessOrExit(error = Mac(key, key_len, in, in_len, computed_mac_span)); + VerifyOrExit(computed_mac_span.size() == mac_len, error = CHIP_ERROR_INTERNAL); + + VerifyOrExit(IsBufferContentEqualConstantTime(mac, computed_mac, kSHA256_Hash_Length), error = CHIP_ERROR_INTERNAL); + +exit: + _log_mbedTLS_error(result); + return error; +} + +CHIP_ERROR Spake2p_P256_SHA256_HKDF_HMAC::FELoad(const uint8_t * in, size_t in_len, void * fe) +{ + secEcp256Status_t result; + uint32_t FE[SEC_ECP256_COORDINATE_WLEN]; + + result = ECP256_ModularReductionN(FE, in, in_len); + VerifyOrReturnError(result == gSecEcp256Success_c, CHIP_ERROR_INTERNAL); + + result = ECP256_FieldLoad((uint32_t *) fe, (const uint8_t *) FE, in_len); + VerifyOrReturnError(result == gSecEcp256Success_c, CHIP_ERROR_INTERNAL); + + return CHIP_NO_ERROR; +} + +CHIP_ERROR Spake2p_P256_SHA256_HKDF_HMAC::FEWrite(const void * fe, uint8_t * out, size_t out_len) +{ + secEcp256Status_t result; + + result = ECP256_FieldWrite(out, (uint8_t *) fe); + VerifyOrReturnError(result == gSecEcp256Success_c, CHIP_ERROR_INTERNAL); + + return CHIP_NO_ERROR; +} + +CHIP_ERROR Spake2p_P256_SHA256_HKDF_HMAC::FEGenerate(void * fe) +{ + secEcp256Status_t result; + big_int256_t PrivateKey; + + result = ECP256_GeneratePrivateKey(&PrivateKey); + VerifyOrReturnError(result == gSecEcp256Success_c, CHIP_ERROR_INTERNAL); + + result = ECP256_FieldWrite((uint8_t *) fe, (uint8_t *) &PrivateKey); + VerifyOrReturnError(result == gSecEcp256Success_c, CHIP_ERROR_INTERNAL); + + return CHIP_NO_ERROR; +} + +CHIP_ERROR Spake2p_P256_SHA256_HKDF_HMAC::FEMul(void * fer, const void * fe1, const void * fe2) +{ + secEcp256Status_t result; + + result = ECP256_ScalarMultiplicationModN((uint32_t *) fer, (const uint32_t *) fe1, (const uint32_t *) fe2); + VerifyOrReturnError(result == gSecEcp256Success_c, CHIP_ERROR_INTERNAL); + + return CHIP_NO_ERROR; +} + +CHIP_ERROR Spake2p_P256_SHA256_HKDF_HMAC::PointLoad(const uint8_t * in, size_t in_len, void * R) +{ + ECP256_PointLoad((ecp256Point_t *) R, in, false); + + return CHIP_NO_ERROR; +} + +CHIP_ERROR Spake2p_P256_SHA256_HKDF_HMAC::PointWrite(const void * R, uint8_t * out, size_t out_len) +{ + ECP256_PointWrite(out, (ecp256Point_t *) R, false); + + return CHIP_NO_ERROR; +} + +CHIP_ERROR Spake2p_P256_SHA256_HKDF_HMAC::PointMul(void * R, const void * P1, const void * fe1) +{ + secEcp256Status_t result; + + result = ECP256_PointMult((ecp256Point_t *) R, (const uint8_t *) P1, (const uint8_t *) fe1); + VerifyOrReturnError(result == gSecEcp256Success_c, CHIP_ERROR_INTERNAL); + + return CHIP_NO_ERROR; +} + +CHIP_ERROR Spake2p_P256_SHA256_HKDF_HMAC::PointAddMul(void * R, const void * P1, const void * fe1, const void * P2, + const void * fe2) +{ + secEcp256Status_t result; + + result = ECP256_DoublePointMulAdd(R, P1, fe1, P2, fe2); + VerifyOrReturnError(result == gSecEcp256Success_c, CHIP_ERROR_INTERNAL); + + return CHIP_NO_ERROR; +} + +CHIP_ERROR Spake2p_P256_SHA256_HKDF_HMAC::PointInvert(void * R) +{ + secEcp256Status_t result; + + result = ECP256_PointInvert((uint32_t *) R, (const uint32_t *) R); + VerifyOrReturnError(result == gSecEcp256Success_c, CHIP_ERROR_INTERNAL); + + return CHIP_NO_ERROR; +} + +CHIP_ERROR Spake2p_P256_SHA256_HKDF_HMAC::PointCofactorMul(void * R) +{ + return CHIP_NO_ERROR; +} + +CHIP_ERROR Spake2p_P256_SHA256_HKDF_HMAC::ComputeL(uint8_t * Lout, size_t * L_len, const uint8_t * w1in, size_t w1in_len) +{ + secEcp256Status_t result; + ecp256Point_t gen_point; + uint32_t W1[SEC_ECP256_COORDINATE_WLEN]; + + result = ECP256_ModularReductionN(W1, w1in, w1in_len); + VerifyOrReturnError(result == gSecEcp256Success_c, CHIP_ERROR_INTERNAL); + + result = ECP256_GeneratePublicKey((uint8_t *) &gen_point, (uint8_t *) &W1, NULL); + VerifyOrReturnError(result == gSecEcp256Success_c, CHIP_ERROR_INTERNAL); + + ECP256_PointWrite(Lout, (ecp256Point_t *) &gen_point, false); + + return CHIP_NO_ERROR; +} + +CHIP_ERROR Spake2p_P256_SHA256_HKDF_HMAC::PointIsValid(void * R) +{ + VerifyOrReturnError(ECP256_PointValid((ecp256Point_t *) R), CHIP_ERROR_INTERNAL); + + return CHIP_NO_ERROR; +} + +} // namespace Crypto +} // namespace chip diff --git a/src/platform/nxp/k32w/k32w1/CHIPDevicePlatformConfig.h b/src/platform/nxp/k32w/k32w1/CHIPDevicePlatformConfig.h new file mode 100644 index 00000000000000..60a1498cff9444 --- /dev/null +++ b/src/platform/nxp/k32w/k32w1/CHIPDevicePlatformConfig.h @@ -0,0 +1,117 @@ +/* + * + * Copyright (c) 2020 Project CHIP Authors + * Copyright (c) 2020 Google LLC. + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file + * Platform-specific configuration overrides for the chip Device Layer + * on K32W platforms using the NXP SDK. + */ + +#pragma once + +// ==================== Platform Adaptations ==================== + +#define K32W_NO_ERRORS 0 +#define K32W_ENTRY_NOT_FOUND 1 + +#define CHIP_DEVICE_CONFIG_ENABLE_WIFI_STATION 0 +#define CHIP_DEVICE_CONFIG_ENABLE_WIFI_AP 0 + +#ifndef CHIP_DEVICE_CONFIG_ENABLE_CHIPOBLE +#define CHIP_DEVICE_CONFIG_ENABLE_CHIPOBLE 1 +#endif + +#ifndef CHIP_DEVICE_CONFIG_ENABLE_CHIPOBLE +#define CHIP_DEVICE_CHIP0BLE_DEBUG 0 +#endif + +#define CHIP_DEVICE_CONFIG_ENABLE_CHIP_TIME_SERVICE_TIME_SYNC 0 +// #define CHIP_DEVICE_CONFIG_PERSISTED_STORAGE_GLOBAL_EIDC_KEY 2 + +// ========== Platform-specific Configuration ========= + +// These are configuration options that are unique to the K32W platform. +// These can be overridden by the application as needed. + +/** + * @def CHIP_DEVICE_LAYER_BLE_OBSERVER_PRIORITY + * + * The priority of the SoftDevice observer event handler registered by the + * chip BleLayer. + */ +#ifndef CHIP_DEVICE_LAYER_BLE_OBSERVER_PRIORITY +#define CHIP_DEVICE_LAYER_BLE_OBSERVER_PRIORITY 3 +#endif // CHIP_DEVICE_LAYER_BLE_OBSERVER_PRIORITY + +/** + * @def CHIP_DEVICE_LAYER_BLE_CONN_CFG_TAG + * + * The SoftDevice BLE connection configuration tag used by the chip + * BleLayer. + */ +#ifndef CHIP_DEVICE_LAYER_BLE_CONN_CFG_TAG +#define CHIP_DEVICE_LAYER_BLE_CONN_CFG_TAG 1 +#endif // CHIP_DEVICE_LAYER_BLE_CONN_CFG_TAG + +/** + * @def CHIP_DEVICE_LAYER_OTA_REBOOT_DELAY + * + * The delay before rebooting after an OTA process was finished. + */ +#ifndef CHIP_DEVICE_LAYER_OTA_REBOOT_DELAY +#define CHIP_DEVICE_LAYER_OTA_REBOOT_DELAY 3000 +#endif // CHIP_DEVICE_LAYER_OTA_REBOOT_DELAY + +// ========== Platform-specific Configuration Overrides ========= +#ifndef CHIP_DEVICE_CONFIG_CHIP_TASK_STACK_SIZE +#define CHIP_DEVICE_CONFIG_CHIP_TASK_STACK_SIZE (6 * 1024) +#endif // CHIP_DEVICE_CONFIG_CHIP_TASK_STACK_SIZE + +#ifndef CHIP_DEVICE_CONFIG_THREAD_TASK_STACK_SIZE +#define CHIP_DEVICE_CONFIG_THREAD_TASK_STACK_SIZE 3072 +#endif // CHIP_DEVICE_CONFIG_THREAD_TASK_STACK_SIZE + +// Max size of event queue +#define CHIP_DEVICE_CONFIG_MAX_EVENT_QUEUE_SIZE 25 + +#ifndef CHIP_DEVICE_CONFIG_BLE_APP_TASK_NAME +#define CHIP_DEVICE_CONFIG_BLE_APP_TASK_NAME "BLE App Task" +#endif // CHIP_DEVICE_CONFIG_BLE_APP_TASK_NAME + +#define CHIP_DEVICE_CONFIG_ENABLE_WIFI_TELEMETRY 0 +#define CHIP_DEVICE_CONFIG_ENABLE_THREAD_TELEMETRY 0 +#define CHIP_DEVICE_CONFIG_ENABLE_THREAD_TELEMETRY_FULL 0 + +#define CHIP_DEVICE_CONFIG_CHIPOBLE_DISABLE_ADVERTISING_WHEN_PROVISIONED 1 + +#define CHIP_DEVICE_CONFIG_CHIPOBLE_ENABLE_ADVERTISING_AUTOSTART 0 + +#define CHIP_DEVICE_CONFIG_ENABLE_PAIRING_AUTOSTART 0 + +#define CHIP_DEVICE_CONFIG_THREAD_TASK_PRIORITY 3 + +#define CHIP_DEVICE_CONFIG_CHIP_TASK_PRIORITY 2 + +#if CHIP_ENABLE_OPENTHREAD +#define CHIP_DEVICE_CONFIG_ENABLE_THREAD 1 +#define CHIP_DEVICE_CONFIG_ENABLE_THREAD_SRP_CLIENT 1 +#define CHIP_DEVICE_CONFIG_ENABLE_THREAD_DNS_CLIENT 1 +#endif + +#define CHIP_DEVICE_CONFIG_ENABLE_TEST_SETUP_PARAMS 1 diff --git a/src/platform/nxp/k32w/k32w1/CHIPDevicePlatformEvent.h b/src/platform/nxp/k32w/k32w1/CHIPDevicePlatformEvent.h new file mode 100644 index 00000000000000..e4acfb369c4bef --- /dev/null +++ b/src/platform/nxp/k32w/k32w1/CHIPDevicePlatformEvent.h @@ -0,0 +1,62 @@ +/* + * + * Copyright (c) 2020 Project CHIP Authors + * Copyright (c) 2020 Nest Labs, Inc. + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file + * Defines platform-specific event types and data for the chip + * Device Layer on K32W061 platforms using the NXP SDK. + */ + +#pragma once + +#include + +namespace chip { +namespace DeviceLayer { + +namespace DeviceEventType { + +/** + * Enumerates K32W1 platform-specific event types that are visible to the application. + */ +enum PublicPlatformSpecificEventTypes +{ + /* None currently defined */ +}; + +/** + * Enumerates K32W061 platform-specific event types that are internal to the chip Device Layer. + */ +enum InternalPlatformSpecificEventTypes +{ + /* None currently defined */ +}; + +} // namespace DeviceEventType + +/** + * Represents platform-specific event information for NXP K32W061 platforms. + */ + +struct ChipDevicePlatformEvent final +{ +}; + +} // namespace DeviceLayer +} // namespace chip diff --git a/src/platform/nxp/k32w/k32w1/CHIPPlatformConfig.h b/src/platform/nxp/k32w/k32w1/CHIPPlatformConfig.h new file mode 100644 index 00000000000000..b8ae07689e14bb --- /dev/null +++ b/src/platform/nxp/k32w/k32w1/CHIPPlatformConfig.h @@ -0,0 +1,78 @@ +/* + * + * Copyright (c) 2020 Project CHIP Authors + * Copyright (c) 2020 Google LLC. + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file + * Platform-specific configuration overrides for CHIP on + * NXP K32W platforms. + */ + +#pragma once + +#include + +// ==================== General Platform Adaptations ==================== + +#define CHIP_CONFIG_ABORT() abort() + +#define CHIP_CONFIG_PERSISTED_STORAGE_KEY_TYPE uint16_t +#define CHIP_CONFIG_PERSISTED_STORAGE_ENC_MSG_CNTR_ID 1 +#define CHIP_CONFIG_PERSISTED_STORAGE_MAX_KEY_LENGTH 2 + +#define CHIP_CONFIG_LIFETIIME_PERSISTED_COUNTER_KEY 0x01 + +#define CHIP_DEVICE_CONFIG_EVENT_LOGGING_DEBUG_BUFFER_SIZE (512) +#define CHIP_DEVICE_CONFIG_EVENT_LOGGING_INFO_BUFFER_SIZE (512) +#define CHIP_DEVICE_CONFIG_EVENT_LOGGING_CRIT_BUFFER_SIZE (512) + +// ==================== Security Adaptations ==================== + +// FIXME: K32W currently set to CHIP (Does this use Entropy.cpp ?) + +// ==================== General Configuration Overrides ==================== + +#ifndef CHIP_CONFIG_MAX_UNSOLICITED_MESSAGE_HANDLERS +#define CHIP_CONFIG_MAX_UNSOLICITED_MESSAGE_HANDLERS 8 +#endif // CHIP_CONFIG_MAX_UNSOLICITED_MESSAGE_HANDLERS + +#ifndef CHIP_CONFIG_MAX_EXCHANGE_CONTEXTS +#define CHIP_CONFIG_MAX_EXCHANGE_CONTEXTS 8 +#endif // CHIP_CONFIG_MAX_EXCHANGE_CONTEXTS + +#ifndef CHIP_LOG_FILTERING +#define CHIP_LOG_FILTERING 0 +#endif // CHIP_LOG_FILTERING + +#ifndef CHIP_CONFIG_BDX_MAX_NUM_TRANSFERS +#define CHIP_CONFIG_BDX_MAX_NUM_TRANSFERS 1 +#endif // CHIP_CONFIG_BDX_MAX_NUM_TRANSFERS + +// ==================== WDM Configuration Overrides ==================== + +#ifndef WDM_MAX_NUM_SUBSCRIPTION_CLIENTS +#define WDM_MAX_NUM_SUBSCRIPTION_CLIENTS 2 +#endif // WDM_MAX_NUM_SUBSCRIPTION_CLIENTS + +#ifndef WDM_MAX_NUM_SUBSCRIPTION_HANDLERS +#define WDM_MAX_NUM_SUBSCRIPTION_HANDLERS 2 +#endif // WDM_MAX_NUM_SUBSCRIPTION_HANDLERS + +#ifndef WDM_PUBLISHER_MAX_NOTIFIES_IN_FLIGHT +#define WDM_PUBLISHER_MAX_NOTIFIES_IN_FLIGHT 2 +#endif // WDM_PUBLISHER_MAX_NOTIFIES_IN_FLIGHT diff --git a/src/platform/nxp/k32w/k32w1/ConfigurationManagerImpl.cpp b/src/platform/nxp/k32w/k32w1/ConfigurationManagerImpl.cpp new file mode 100644 index 00000000000000..43aa7fa591b8f0 --- /dev/null +++ b/src/platform/nxp/k32w/k32w1/ConfigurationManagerImpl.cpp @@ -0,0 +1,291 @@ +/* + * + * Copyright (c) 2020 Project CHIP Authors + * Copyright (c) 2020 Nest Labs, Inc. + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file + * Provides the implementation of the Device Layer ConfigurationManager object + * for K32W platforms using the NXP SDK. + */ + +/* this file behaves like a config.h, comes first */ +#include + +#include +#include +#include +#include + +// #include +#include "fsl_cmc.h" +#include "fsl_device_registers.h" + +namespace chip { +namespace DeviceLayer { + +using namespace ::chip::DeviceLayer::Internal; + +// TODO: Define a Singleton instance of CHIP Group Key Store here + +ConfigurationManagerImpl & ConfigurationManagerImpl::GetDefaultInstance() +{ + static ConfigurationManagerImpl sInstance; + return sInstance; +} + +CHIP_ERROR ConfigurationManagerImpl::Init() +{ + CHIP_ERROR err; + uint32_t rebootCount = 0; + + if (K32WConfig::ConfigValueExists(K32WConfig::kCounterKey_RebootCount)) + { + err = GetRebootCount(rebootCount); + SuccessOrExit(err); + + err = StoreRebootCount(rebootCount + 1); + SuccessOrExit(err); + } + else + { + // The first boot after factory reset of the Node. + err = StoreRebootCount(0); + SuccessOrExit(err); + } + + if (!K32WConfig::ConfigValueExists(K32WConfig::kCounterKey_TotalOperationalHours)) + { + err = StoreTotalOperationalHours(0); + SuccessOrExit(err); + } + + if (!K32WConfig::ConfigValueExists(K32WConfig::kCounterKey_BootReason)) + { + err = StoreBootReason(to_underlying(BootReasonType::kUnspecified)); + SuccessOrExit(err); + } + + // Initialize the generic implementation base class. + err = Internal::GenericConfigurationManagerImpl::Init(); + SuccessOrExit(err); + + // TODO: Initialize the global GroupKeyStore object here + + err = CHIP_NO_ERROR; + +exit: + return err; +} + +CHIP_ERROR ConfigurationManagerImpl::StoreSoftwareUpdateCompleted() +{ + /* Empty implementation*/ + return CHIP_NO_ERROR; +} + +CHIP_ERROR ConfigurationManagerImpl::GetRebootCount(uint32_t & rebootCount) +{ + return ReadConfigValue(K32WConfig::kCounterKey_RebootCount, rebootCount); +} + +CHIP_ERROR ConfigurationManagerImpl::StoreRebootCount(uint32_t rebootCount) +{ + return WriteConfigValue(K32WConfig::kCounterKey_RebootCount, rebootCount); +} + +CHIP_ERROR ConfigurationManagerImpl::GetTotalOperationalHours(uint32_t & totalOperationalHours) +{ + return ReadConfigValue(K32WConfig::kCounterKey_TotalOperationalHours, totalOperationalHours); +} + +CHIP_ERROR ConfigurationManagerImpl::StoreTotalOperationalHours(uint32_t totalOperationalHours) +{ + return WriteConfigValue(K32WConfig::kCounterKey_TotalOperationalHours, totalOperationalHours); +} + +CHIP_ERROR ConfigurationManagerImpl::GetBootReason(uint32_t & bootReason) +{ + bootReason = to_underlying(BootReasonType::kUnspecified); + + uint32_t reason = CMC_GetSystemResetStatus(CMC0); + + if ((reason & CMC_SRS_POR_MASK) || (reason & CMC_SRS_PIN_MASK)) + { + bootReason = to_underlying(BootReasonType::kPowerOnReboot); + } + else if (reason & CMC_SRS_SW_MASK) + { + bootReason = to_underlying(BootReasonType::kSoftwareReset); + } + else if ((reason & CMC_SRS_WDOG0_MASK) || (reason & CMC_SRS_WDOG1_MASK)) + { + bootReason = to_underlying(BootReasonType::kSoftwareWatchdogReset); + } + else + { + bootReason = to_underlying(BootReasonType::kUnspecified); + } + + return CHIP_NO_ERROR; +} + +CHIP_ERROR ConfigurationManagerImpl::StoreBootReason(uint32_t bootReason) +{ + return WriteConfigValue(K32WConfig::kCounterKey_BootReason, bootReason); +} + +bool ConfigurationManagerImpl::CanFactoryReset() +{ + // TODO: query the application to determine if factory reset is allowed. + return true; +} + +void ConfigurationManagerImpl::InitiateFactoryReset() +{ + PlatformMgr().ScheduleWork(DoFactoryReset); +} + +CHIP_ERROR ConfigurationManagerImpl::ReadPersistedStorageValue(::chip::Platform::PersistedStorage::Key persistedStorageKey, + uint32_t & value) +{ + CHIP_ERROR err; + + err = K32WConfig::ReadConfigValueCounter(persistedStorageKey, value); + if (err == CHIP_DEVICE_ERROR_CONFIG_NOT_FOUND) + { + err = CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND; + } + SuccessOrExit(err); + +exit: + return err; +} + +CHIP_ERROR ConfigurationManagerImpl::WritePersistedStorageValue(::chip::Platform::PersistedStorage::Key persistedStorageKey, + uint32_t value) +{ + // This method reads Chip Persisted Counter type nvm3 objects. + // (where persistedStorageKey represents an index to the counter). + CHIP_ERROR err; + + err = K32WConfig::WriteConfigValueCounter(persistedStorageKey, value); + if (err == CHIP_DEVICE_ERROR_CONFIG_NOT_FOUND) + { + err = CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND; + } + SuccessOrExit(err); + +exit: + return err; +} + +CHIP_ERROR ConfigurationManagerImpl::ReadConfigValue(Key key, bool & val) +{ + return K32WConfig::ReadConfigValue(key, val); +} + +CHIP_ERROR ConfigurationManagerImpl::ReadConfigValue(Key key, uint32_t & val) +{ + return K32WConfig::ReadConfigValue(key, val); +} + +CHIP_ERROR ConfigurationManagerImpl::ReadConfigValue(Key key, uint64_t & val) +{ + return K32WConfig::ReadConfigValue(key, val); +} + +CHIP_ERROR ConfigurationManagerImpl::ReadConfigValueStr(Key key, char * buf, size_t bufSize, size_t & outLen) +{ + return K32WConfig::ReadConfigValueStr(key, buf, bufSize, outLen); +} + +CHIP_ERROR ConfigurationManagerImpl::ReadConfigValueBin(Key key, uint8_t * buf, size_t bufSize, size_t & outLen) +{ + return K32WConfig::ReadConfigValueBin(key, buf, bufSize, outLen); +} + +CHIP_ERROR ConfigurationManagerImpl::WriteConfigValue(Key key, bool val) +{ + return K32WConfig::WriteConfigValue(key, val); +} + +CHIP_ERROR ConfigurationManagerImpl::WriteConfigValue(Key key, uint32_t val) +{ + return K32WConfig::WriteConfigValue(key, val); +} + +CHIP_ERROR ConfigurationManagerImpl::WriteConfigValue(Key key, uint64_t val) +{ + return K32WConfig::WriteConfigValue(key, val); +} + +CHIP_ERROR ConfigurationManagerImpl::WriteConfigValueStr(Key key, const char * str) +{ + return K32WConfig::WriteConfigValueStr(key, str); +} + +CHIP_ERROR ConfigurationManagerImpl::WriteConfigValueStr(Key key, const char * str, size_t strLen) +{ + return K32WConfig::WriteConfigValueStr(key, str, strLen); +} + +CHIP_ERROR ConfigurationManagerImpl::WriteConfigValueBin(Key key, const uint8_t * data, size_t dataLen) +{ + return K32WConfig::WriteConfigValueBin(key, data, dataLen); +} + +void ConfigurationManagerImpl::RunConfigUnitTest(void) +{ + K32WConfig::RunConfigUnitTest(); +} + +void ConfigurationManagerImpl::DoFactoryReset(intptr_t arg) +{ + CHIP_ERROR err; + + ChipLogProgress(DeviceLayer, "Performing factory reset"); + + err = K32WConfig::FactoryResetConfig(); + if (err != CHIP_NO_ERROR) + { + ChipLogError(DeviceLayer, "FactoryResetConfig() failed: %s", ErrorStr(err)); + } + +#if CHIP_DEVICE_CONFIG_ENABLE_THREAD + + ThreadStackMgr().ErasePersistentInfo(); + +#endif // CHIP_DEVICE_CONFIG_ENABLE_THREAD + + // Restart the system. + ChipLogProgress(DeviceLayer, "System restarting"); + + NVIC_SystemReset(); + + while (1) + { + } +} + +ConfigurationManager & ConfigurationMgrImpl() +{ + return ConfigurationManagerImpl::GetDefaultInstance(); +} + +} // namespace DeviceLayer +} // namespace chip diff --git a/src/platform/nxp/k32w/k32w1/ConfigurationManagerImpl.h b/src/platform/nxp/k32w/k32w1/ConfigurationManagerImpl.h new file mode 100644 index 00000000000000..26201d85f58a52 --- /dev/null +++ b/src/platform/nxp/k32w/k32w1/ConfigurationManagerImpl.h @@ -0,0 +1,95 @@ +/* + * + * Copyright (c) 2020 Project CHIP Authors + * Copyright (c) 2020 Nest Labs, Inc. + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file + * Provides an implementation of the ConfigurationManager object + * for K32W platforms using the NXP SDK. + */ + +#pragma once + +#include "K32W1Config.h" +#include + +namespace chip { +namespace DeviceLayer { + +/** + * Concrete implementation of the ConfigurationManager singleton object for the K32W platform. + */ +class ConfigurationManagerImpl final : public Internal::GenericConfigurationManagerImpl +{ +public: + // This returns an instance of this class. + static ConfigurationManagerImpl & GetDefaultInstance(); + CHIP_ERROR StoreSoftwareUpdateCompleted(); + +private: + // ===== Members that implement the ConfigurationManager public interface. + + CHIP_ERROR Init(void) override; + CHIP_ERROR GetPrimaryWiFiMACAddress(uint8_t * buf) override; + bool CanFactoryReset(void) override; + void InitiateFactoryReset(void) override; + CHIP_ERROR ReadPersistedStorageValue(::chip::Platform::PersistedStorage::Key key, uint32_t & value) override; + CHIP_ERROR WritePersistedStorageValue(::chip::Platform::PersistedStorage::Key key, uint32_t value) override; + CHIP_ERROR GetRebootCount(uint32_t & rebootCount) override; + CHIP_ERROR StoreRebootCount(uint32_t rebootCount) override; + CHIP_ERROR GetTotalOperationalHours(uint32_t & totalOperationalHours) override; + CHIP_ERROR StoreTotalOperationalHours(uint32_t totalOperationalHours) override; + CHIP_ERROR GetBootReason(uint32_t & bootReasons) override; + CHIP_ERROR StoreBootReason(uint32_t bootReasons) override; + + // NOTE: Other public interface methods are implemented by GenericConfigurationManagerImpl<>. + + // ===== Members that implement the GenericConfigurationManagerImpl protected interface. + CHIP_ERROR ReadConfigValue(Key key, bool & val) override; + CHIP_ERROR ReadConfigValue(Key key, uint32_t & val) override; + CHIP_ERROR ReadConfigValue(Key key, uint64_t & val) override; + CHIP_ERROR ReadConfigValueStr(Key key, char * buf, size_t bufSize, size_t & outLen) override; + CHIP_ERROR ReadConfigValueBin(Key key, uint8_t * buf, size_t bufSize, size_t & outLen) override; + CHIP_ERROR WriteConfigValue(Key key, bool val) override; + CHIP_ERROR WriteConfigValue(Key key, uint32_t val) override; + CHIP_ERROR WriteConfigValue(Key key, uint64_t val) override; + CHIP_ERROR WriteConfigValueStr(Key key, const char * str) override; + CHIP_ERROR WriteConfigValueStr(Key key, const char * str, size_t strLen) override; + CHIP_ERROR WriteConfigValueBin(Key key, const uint8_t * data, size_t dataLen) override; + void RunConfigUnitTest(void) override; + + // ===== Private members reserved for use by this class only. + + static void DoFactoryReset(intptr_t arg); +}; + +inline CHIP_ERROR ConfigurationManagerImpl::GetPrimaryWiFiMACAddress(uint8_t * buf) +{ + return CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE; +} + +/** + * Returns the platform-specific implementation of the ConfigurationManager object. + * + * Applications can use this to gain access to features of the ConfigurationManager + * that are specific to the selected platform. + */ +ConfigurationManager & ConfigurationMgrImpl(); + +} // namespace DeviceLayer +} // namespace chip diff --git a/src/platform/nxp/k32w/k32w1/ConnectivityManagerImpl.cpp b/src/platform/nxp/k32w/k32w1/ConnectivityManagerImpl.cpp new file mode 100644 index 00000000000000..3182a874fbf41c --- /dev/null +++ b/src/platform/nxp/k32w/k32w1/ConnectivityManagerImpl.cpp @@ -0,0 +1,83 @@ +/* + * + * Copyright (c) 2020 Project CHIP Authors + * Copyright (c) 2020 Nest Labs, Inc. + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/* this file behaves like a config.h, comes first */ +#include + +#include +#include +#include +#include + +#if CHIP_SYSTEM_CONFIG_USE_LWIP +#include +#include +#include +#include +#endif + +#include + +#if INET_CONFIG_ENABLE_TCP_ENDPOINT +#include +#endif + +#if CHIP_DEVICE_CONFIG_ENABLE_CHIPOBLE +#include +#endif + +#if CHIP_DEVICE_CONFIG_ENABLE_THREAD +#include +#endif + +using namespace ::chip; +using namespace ::chip::Inet; +using namespace ::chip::System; +using namespace ::chip::TLV; +using namespace ::chip::DeviceLayer::Internal; + +namespace chip { +namespace DeviceLayer { + +ConnectivityManagerImpl ConnectivityManagerImpl::sInstance; + +CHIP_ERROR ConnectivityManagerImpl::_Init() +{ + CHIP_ERROR err = CHIP_NO_ERROR; + + // Initialize the generic base classes that require it. +#if CHIP_DEVICE_CONFIG_ENABLE_THREAD + GenericConnectivityManagerImpl_Thread::_Init(); +#endif + + SuccessOrExit(err); + +exit: + return err; +} + +void ConnectivityManagerImpl::_OnPlatformEvent(const ChipDeviceEvent * event) +{ + // Forward the event to the generic base classes as needed. +#if CHIP_DEVICE_CONFIG_ENABLE_THREAD + GenericConnectivityManagerImpl_Thread::_OnPlatformEvent(event); +#endif +} + +} // namespace DeviceLayer +} // namespace chip diff --git a/src/platform/nxp/k32w/k32w1/ConnectivityManagerImpl.h b/src/platform/nxp/k32w/k32w1/ConnectivityManagerImpl.h new file mode 100644 index 00000000000000..4b8708d3dc604b --- /dev/null +++ b/src/platform/nxp/k32w/k32w1/ConnectivityManagerImpl.h @@ -0,0 +1,105 @@ +/* + * + * Copyright (c) 2020 Project CHIP Authors + * Copyright (c) 2020 Nest Labs, Inc. + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include +#if INET_CONFIG_ENABLE_TCP_ENDPOINT +#include +#endif +#if CHIP_DEVICE_CONFIG_ENABLE_CHIPOBLE +#include +#else +#include +#endif +#if CHIP_DEVICE_CONFIG_ENABLE_THREAD +#include +#else +#include +#endif +#include + +namespace chip { +namespace DeviceLayer { + +/** + * Concrete implementation of the ConnectivityManager singleton object for NXP K32W platforms. + */ +class ConnectivityManagerImpl final : public ConnectivityManager, +#if CHIP_DEVICE_CONFIG_ENABLE_CHIPOBLE + public Internal::GenericConnectivityManagerImpl_BLE, +#else + public Internal::GenericConnectivityManagerImpl_NoBLE, +#endif +#if CHIP_DEVICE_CONFIG_ENABLE_THREAD + public Internal::GenericConnectivityManagerImpl_Thread, +#else + public Internal::GenericConnectivityManagerImpl_NoThread, +#endif + public Internal::GenericConnectivityManagerImpl_NoWiFi, + public Internal::GenericConnectivityManagerImpl_UDP, +#if INET_CONFIG_ENABLE_TCP_ENDPOINT + public Internal::GenericConnectivityManagerImpl_TCP, +#endif + public Internal::GenericConnectivityManagerImpl +{ + // Allow the ConnectivityManager interface class to delegate method calls to + // the implementation methods provided by this class. + friend class ConnectivityManager; + +private: + // ===== Members that implement the ConnectivityManager abstract interface. + + CHIP_ERROR _Init(void); + void _OnPlatformEvent(const ChipDeviceEvent * event); + + // ===== Members for internal use by the following friends. + + friend ConnectivityManager & ConnectivityMgr(void); + friend ConnectivityManagerImpl & ConnectivityMgrImpl(void); + + static ConnectivityManagerImpl sInstance; +}; + +/** + * Returns the public interface of the ConnectivityManager singleton object. + * + * Chip applications should use this to access features of the ConnectivityManager object + * that are common to all platforms. + */ +inline ConnectivityManager & ConnectivityMgr(void) +{ + return ConnectivityManagerImpl::sInstance; +} + +/** + * Returns the platform-specific implementation of the ConnectivityManager singleton object. + * + * Chip applications can use this to gain access to features of the ConnectivityManager + * that are specific to the ESP32 platform. + */ +inline ConnectivityManagerImpl & ConnectivityMgrImpl(void) +{ + return ConnectivityManagerImpl::sInstance; +} + +} // namespace DeviceLayer +} // namespace chip diff --git a/src/platform/nxp/k32w/k32w1/DefaultTestEventTriggerDelegate.cpp b/src/platform/nxp/k32w/k32w1/DefaultTestEventTriggerDelegate.cpp new file mode 100644 index 00000000000000..1a01acfdb69385 --- /dev/null +++ b/src/platform/nxp/k32w/k32w1/DefaultTestEventTriggerDelegate.cpp @@ -0,0 +1,42 @@ +/* + * + * Copyright (c) 2022 Project CHIP Authors + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "DefaultTestEventTriggerDelegate.h" + +#include +#include + +namespace chip { + +bool DefaultTestEventTriggerDelegate::DoesEnableKeyMatch(const ByteSpan & enableKey) const +{ + return !mEnableKey.empty() && mEnableKey.data_equal(enableKey); +} + +CHIP_ERROR DefaultTestEventTriggerDelegate::HandleEventTrigger(uint64_t eventTrigger) +{ + if (eventTrigger == kQueryTrigger) + { + ChipLogProgress(DeviceLayer, "DefaultTestEventTriggerDelegate: event triggered"); + return CHIP_NO_ERROR; + } + + return CHIP_ERROR_INVALID_ARGUMENT; +} + +} // namespace chip diff --git a/src/platform/nxp/k32w/k32w1/DefaultTestEventTriggerDelegate.h b/src/platform/nxp/k32w/k32w1/DefaultTestEventTriggerDelegate.h new file mode 100644 index 00000000000000..cf71fb37d96470 --- /dev/null +++ b/src/platform/nxp/k32w/k32w1/DefaultTestEventTriggerDelegate.h @@ -0,0 +1,39 @@ +/* + * + * Copyright (c) 2022 Project CHIP Authors + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include + +namespace chip { + +class DefaultTestEventTriggerDelegate : public TestEventTriggerDelegate +{ +public: + static constexpr uint64_t kQueryTrigger = 1234; + + explicit DefaultTestEventTriggerDelegate(const ByteSpan & enableKey) : mEnableKey(enableKey) {} + + bool DoesEnableKeyMatch(const ByteSpan & enableKey) const override; + CHIP_ERROR HandleEventTrigger(uint64_t eventTrigger) override; + +private: + ByteSpan mEnableKey; +}; + +} // namespace chip diff --git a/src/platform/nxp/k32w/k32w1/DiagnosticDataProviderImpl.cpp b/src/platform/nxp/k32w/k32w1/DiagnosticDataProviderImpl.cpp new file mode 100644 index 00000000000000..5bc354351e4fb7 --- /dev/null +++ b/src/platform/nxp/k32w/k32w1/DiagnosticDataProviderImpl.cpp @@ -0,0 +1,232 @@ +/* + * + * Copyright (c) 2022 Project CHIP Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file + * Provides an implementation of the DiagnosticDataProvider object + * for k32w1 platform. + */ + +#include + +#include +#include +#include + +#if CHIP_SYSTEM_CONFIG_USE_LWIP +#include +#endif + +extern "C" void xPortResetHeapMinimumEverFreeHeapSize(void); + +#include + +#include + +using namespace ::chip::app::Clusters::GeneralDiagnostics; + +namespace chip { +namespace DeviceLayer { + +DiagnosticDataProviderImpl & DiagnosticDataProviderImpl::GetDefaultInstance() +{ + static DiagnosticDataProviderImpl sInstance; + return sInstance; +} + +CHIP_ERROR DiagnosticDataProviderImpl::GetCurrentHeapFree(uint64_t & currentHeapFree) +{ + size_t freeHeapSize; + + freeHeapSize = xPortGetFreeHeapSize(); + currentHeapFree = static_cast(freeHeapSize); + return CHIP_NO_ERROR; +} + +CHIP_ERROR DiagnosticDataProviderImpl::GetCurrentHeapUsed(uint64_t & currentHeapUsed) +{ + size_t freeHeapSize; + size_t usedHeapSize; + + freeHeapSize = xPortGetFreeHeapSize(); + usedHeapSize = MinimalHeapSize_c - freeHeapSize; + + currentHeapUsed = static_cast(usedHeapSize); + return CHIP_NO_ERROR; +} + +CHIP_ERROR DiagnosticDataProviderImpl::GetCurrentHeapHighWatermark(uint64_t & currentHeapHighWatermark) +{ + size_t highWatermarkHeapSize; + + highWatermarkHeapSize = MinimalHeapSize_c - xPortGetMinimumEverFreeHeapSize(); + currentHeapHighWatermark = static_cast(highWatermarkHeapSize); + return CHIP_NO_ERROR; +} + +CHIP_ERROR DiagnosticDataProviderImpl::GetThreadMetrics(ThreadMetrics ** threadMetricsOut) +{ + /* Obtain all available task information */ + TaskStatus_t * taskStatusArray; + ThreadMetrics * head = nullptr; + unsigned long arraySize, x, dummy; + + arraySize = uxTaskGetNumberOfTasks(); + + taskStatusArray = (TaskStatus_t *) pvPortMalloc(arraySize * sizeof(TaskStatus_t)); + + if (taskStatusArray != NULL) + { + /* Generate raw status information about each task. */ + arraySize = uxTaskGetSystemState(taskStatusArray, arraySize, &dummy); + /* For each populated position in the taskStatusArray array, + format the raw data as human readable ASCII data. */ + + for (x = 0; x < arraySize; x++) + { + ThreadMetrics * thread = (ThreadMetrics *) pvPortMalloc(sizeof(ThreadMetrics)); + + strncpy(thread->NameBuf, taskStatusArray[x].pcTaskName, kMaxThreadNameLength - 1); + thread->NameBuf[kMaxThreadNameLength] = '\0'; + thread->name.Emplace(CharSpan::fromCharString(thread->NameBuf)); + thread->id = taskStatusArray[x].xTaskNumber; + + thread->stackFreeMinimum.Emplace(taskStatusArray[x].usStackHighWaterMark); + + /* Unsupported metrics */ + thread->stackFreeCurrent.ClearValue(); + thread->stackSize.ClearValue(); + + thread->Next = head; + head = thread; + } + + *threadMetricsOut = head; + /* The array is no longer needed, free the memory it consumes. */ + vPortFree(taskStatusArray); + } + + return CHIP_NO_ERROR; +} + +void DiagnosticDataProviderImpl::ReleaseThreadMetrics(ThreadMetrics * threadMetrics) +{ + while (threadMetrics) + { + ThreadMetrics * del = threadMetrics; + threadMetrics = threadMetrics->Next; + vPortFree(del); + } +} + +CHIP_ERROR DiagnosticDataProviderImpl::GetRebootCount(uint16_t & rebootCount) +{ + uint32_t count = 0; + + CHIP_ERROR err = ConfigurationMgr().GetRebootCount(count); + + if (err == CHIP_NO_ERROR) + { + VerifyOrReturnError(count <= UINT16_MAX, CHIP_ERROR_INVALID_INTEGER_VALUE); + rebootCount = static_cast(count); + } + + return err; +} + +CHIP_ERROR DiagnosticDataProviderImpl::GetUpTime(uint64_t & upTime) +{ + System::Clock::Timestamp currentTime = System::SystemClock().GetMonotonicTimestamp(); + System::Clock::Timestamp startTime = PlatformMgrImpl().GetStartTime(); + + if (currentTime >= startTime) + { + upTime = std::chrono::duration_cast(currentTime - startTime).count(); + return CHIP_NO_ERROR; + } + + return CHIP_ERROR_INVALID_TIME; +} + +CHIP_ERROR DiagnosticDataProviderImpl::GetTotalOperationalHours(uint32_t & totalOperationalHours) +{ + uint64_t upTime = 0; + + if (GetUpTime(upTime) == CHIP_NO_ERROR) + { + uint32_t totalHours = 0; + if (ConfigurationMgr().GetTotalOperationalHours(totalHours) == CHIP_NO_ERROR) + { + VerifyOrReturnError(upTime / 3600 <= UINT32_MAX, CHIP_ERROR_INVALID_INTEGER_VALUE); + totalOperationalHours = totalHours + static_cast(upTime / 3600); + return CHIP_NO_ERROR; + } + } + + return CHIP_ERROR_INVALID_TIME; +} + +CHIP_ERROR DiagnosticDataProviderImpl::GetBootReason(BootReasonType & bootReason) +{ + uint32_t reason = 0; + + CHIP_ERROR err = ConfigurationMgr().GetBootReason(reason); + + if (err == CHIP_NO_ERROR) + { + VerifyOrReturnError(reason <= UINT8_MAX, CHIP_ERROR_INVALID_INTEGER_VALUE); + bootReason = static_cast(reason); + } + + return err; +} + +DiagnosticDataProvider & GetDiagnosticDataProviderImpl() +{ + return DiagnosticDataProviderImpl::GetDefaultInstance(); +} + +CHIP_ERROR DiagnosticDataProviderImpl::GetNetworkInterfaces(NetworkInterface ** netifpp) +{ + NetworkInterface * ifp = new NetworkInterface(); + + const char * threadNetworkName = otThreadGetNetworkName(ThreadStackMgrImpl().OTInstance()); + ifp->name = Span(threadNetworkName, strlen(threadNetworkName)); + ifp->isOperational = true; + ifp->offPremiseServicesReachableIPv4.SetNull(); + ifp->offPremiseServicesReachableIPv6.SetNull(); + ifp->type = InterfaceTypeEnum::EMBER_ZCL_INTERFACE_TYPE_ENUM_THREAD; + uint8_t macBuffer[ConfigurationManager::kPrimaryMACAddressLength]; + ConfigurationMgr().GetPrimary802154MACAddress(macBuffer); + ifp->hardwareAddress = ByteSpan(macBuffer, ConfigurationManager::kPrimaryMACAddressLength); + ifp->Next = nullptr; + + *netifpp = ifp; + return CHIP_NO_ERROR; +} + +void DiagnosticDataProviderImpl::ReleaseNetworkInterfaces(NetworkInterface * netifp) +{ + while (netifp) + { + NetworkInterface * del = netifp; + netifp = netifp->Next; + delete del; + } +} +} // namespace DeviceLayer +} // namespace chip diff --git a/src/platform/nxp/k32w/k32w1/DiagnosticDataProviderImpl.h b/src/platform/nxp/k32w/k32w1/DiagnosticDataProviderImpl.h new file mode 100644 index 00000000000000..78cca26b3683ee --- /dev/null +++ b/src/platform/nxp/k32w/k32w1/DiagnosticDataProviderImpl.h @@ -0,0 +1,66 @@ +/* + * + * Copyright (c) 2022 Project CHIP Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file + * Provides an implementation of the DiagnosticDataProvider object. + */ + +#pragma once + +#include + +#include + +namespace chip { +namespace DeviceLayer { + +/** + * Concrete implementation of the PlatformManager singleton object for Linux platforms. + */ +class DiagnosticDataProviderImpl : public DiagnosticDataProvider +{ +public: + static DiagnosticDataProviderImpl & GetDefaultInstance(); + + // ===== Methods that implement the PlatformManager abstract interface. + + bool SupportsWatermarks() override { return true; } + CHIP_ERROR GetCurrentHeapFree(uint64_t & currentHeapFree) override; + CHIP_ERROR GetCurrentHeapUsed(uint64_t & currentHeapUsed) override; + CHIP_ERROR GetCurrentHeapHighWatermark(uint64_t & currentHeapHighWatermark) override; + CHIP_ERROR GetThreadMetrics(ThreadMetrics ** threadMetricsOut) override; + void ReleaseThreadMetrics(ThreadMetrics * threadMetrics) override; + + CHIP_ERROR GetRebootCount(uint16_t & rebootCount) override; + CHIP_ERROR GetUpTime(uint64_t & upTime) override; + CHIP_ERROR GetTotalOperationalHours(uint32_t & totalOperationalHours) override; + CHIP_ERROR GetBootReason(BootReasonType & bootReason) override; + CHIP_ERROR GetNetworkInterfaces(NetworkInterface ** netifpp) override; + void ReleaseNetworkInterfaces(NetworkInterface * netifp) override; +}; + +/** + * Returns the platform-specific implementation of the DiagnosticDataProvider singleton object. + * + * Applications can use this to gain access to features of the DiagnosticDataProvider + * that are specific to the selected platform. + */ +DiagnosticDataProvider & GetDiagnosticDataProviderImpl(); + +} // namespace DeviceLayer +} // namespace chip diff --git a/src/platform/nxp/k32w/k32w1/InetPlatformConfig.h b/src/platform/nxp/k32w/k32w1/InetPlatformConfig.h new file mode 100644 index 00000000000000..e59c31f75eac37 --- /dev/null +++ b/src/platform/nxp/k32w/k32w1/InetPlatformConfig.h @@ -0,0 +1,43 @@ +/* + * + * Copyright (c) 2020 Project CHIP Authors + * Copyright (c) 2020 Google LLC. + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file + * Platform-specific configuration overrides for the CHIP Inet + * Layer on K32W platforms using the NXP SDK. + * + */ + +#pragma once + +// ==================== Platform Adaptations ==================== + +#ifndef INET_CONFIG_ENABLE_IPV4 +#error Inet IPv4 configuration should be configured at build generation time +#endif + +// ========== Platform-specific Configuration Overrides ========= + +#ifndef INET_CONFIG_NUM_TCP_ENDPOINTS +#define INET_CONFIG_NUM_TCP_ENDPOINTS 4 +#endif // INET_CONFIG_NUM_TCP_ENDPOINTS + +#ifndef INET_CONFIG_NUM_UDP_ENDPOINTS +#define INET_CONFIG_NUM_UDP_ENDPOINTS 4 +#endif // INET_CONFIG_NUM_UDP_ENDPOINTS diff --git a/src/platform/nxp/k32w/k32w1/K32W1Config.cpp b/src/platform/nxp/k32w/k32w1/K32W1Config.cpp new file mode 100644 index 00000000000000..ee41809dcca7b9 --- /dev/null +++ b/src/platform/nxp/k32w/k32w1/K32W1Config.cpp @@ -0,0 +1,435 @@ +/* + * + * Copyright (c) 2020 Project CHIP Authors + * Copyright (c) 2020 Google LLC. + * Copyright (c) 2018 Nest Labs, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file + * Utilities for accessing persisted device configuration on + * platforms based on the NXP K32W SDK. + */ +/* this file behaves like a config.h, comes first */ +#include + +#include + +#include +#include +#include + +#if CHIP_DEVICE_CONFIG_ENABLE_THREAD +#include +#endif + +#if (defined(K32W_LOG_ENABLED) && (K32W_LOG_ENABLED > 0)) +// #include "fsl_component_log.h" +// #include "fsl_component_log_backend_debugconsole.h" +#endif +#include "FreeRTOS.h" +#include "FunctionLib.h" +#include "NVM_Interface.h" + +namespace chip { +namespace DeviceLayer { +namespace Internal { + +#ifndef CHIP_PLAT_NVM_SUPPORT +#define CHIP_PLAT_NVM_SUPPORT 0 +#endif + +#ifndef CHIP_PLAT_NVM_STATIC_ALLOC +#define CHIP_PLAT_NVM_STATIC_ALLOC 1 +#endif + +#define CHIP_CONFIG_RAM_BUFFER_SIZE 10240 + +#ifndef NVM_ID_CHIP_CONFIG_DATA +#define NVM_ID_CHIP_CONFIG_DATA 0xf104 +#endif + +#if 0 +typedef struct +{ + uint16_t chipConfigRamBufferLen; + uint8_t chipConfigRamBuffer[3072]; +} ChipConfigRamStruct_t; +static ChipConfigRamStruct_t *chipConfigRamStruct; +#endif + +#if CHIP_PLAT_NVM_STATIC_ALLOC +static uint8_t chipConfigRamBuffer[CHIP_CONFIG_RAM_BUFFER_SIZE] = { 0 }; +#endif + +static ramBufferDescriptor * ramDescr; + +#define RAM_DESC_HEADER_SIZE (sizeof(ramDescr->ramBufferLen + ramDescr->ramBufferMaxLen)) + +#if CHIP_PLAT_NVM_SUPPORT +NVM_RegisterDataSet((void *) chipConfigRamBuffer, 1, sizeof(chipConfigRamBuffer), NVM_ID_CHIP_CONFIG_DATA, gNVM_MirroredInRam_c); +#endif + +static rsError AddToRamStorage(ramBufferDescriptor * pBuffer, uint16_t aKey, const uint8_t * aValue, uint16_t aValueLength) +{ + rsError err; + +#if !CHIP_PLAT_NVM_STATIC_ALLOC + uint32_t allocSize = pBuffer->ramBufferMaxLen; + + if (allocSize <= pBuffer->ramBufferLen + aValueLength) + { + while (allocSize < pBuffer->ramBufferLen + aValueLength) + { + /* Need to realocate the memory buffer, increase size by 512B until nvm data fits */ + allocSize += 512; + } + + pBuffer->ramBufferLen = allocSize; + allocSize += RAM_DESC_HEADER_SIZE; + + pBuffer = (ramBufferDescriptor *) realloc((void *) pBuffer, allocSize); + VerifyOrExit((NULL != pBuffer), err = RS_ERROR_NO_BUFS); + } +#endif + + err = ramStorageSet(pBuffer, aKey, aValue, aValueLength); + +#if !CHIP_PLAT_NVM_STATIC_ALLOC +exit: +#endif + + return err; +} + +CHIP_ERROR K32WConfig::Init() +{ + CHIP_ERROR err = CHIP_NO_ERROR; + bool bLoadDataFromNvm = true; + uint32_t allocSize = CHIP_CONFIG_RAM_BUFFER_SIZE; + + /* Initialise the Persistent Data Manager */ +#if CHIP_PLAT_NVM_SUPPORT + /* Init the NVM module */ + NvModuleInit(); +#endif + +#if CHIP_PLAT_NVM_STATIC_ALLOC + ramDescr = (ramBufferDescriptor *) chipConfigRamBuffer; +#else + ramDescr = (ramBufferDescriptor *) malloc(allocSize); + VerifyOrExit((NULL != ramDescr), err = CHIP_ERROR_NO_MEMORY); +#endif + + ramDescr->ramBufferLen = 0; + + if (bLoadDataFromNvm) + { + /* Try to load the dataset in RAM */ +#if CHIP_PLAT_NVM_SUPPORT + NvRestoreDataSet((void *) ramDescr, 0); +#endif + } + + ramDescr->ramBufferMaxLen = allocSize - RAM_DESC_HEADER_SIZE; + +#if !CHIP_PLAT_NVM_STATIC_ALLOC +exit: +#endif + + return err; +} + +CHIP_ERROR K32WConfig::ReadConfigValue(Key key, bool & val) +{ + CHIP_ERROR err; + bool tempVal; + rsError status; + uint16_t sizeToRead = sizeof(tempVal); + + VerifyOrExit(ValidConfigKey(key), err = CHIP_DEVICE_ERROR_CONFIG_NOT_FOUND); // Verify key id. + status = ramStorageGet(ramDescr, key, 0, (uint8_t *) &tempVal, &sizeToRead); + SuccessOrExit(err = MapRamStorageStatus(status)); + val = tempVal; + +exit: + return err; +} + +CHIP_ERROR K32WConfig::ReadConfigValue(Key key, uint32_t & val) +{ + CHIP_ERROR err; + uint32_t tempVal; + rsError status; + uint16_t sizeToRead = sizeof(tempVal); + + VerifyOrExit(ValidConfigKey(key), err = CHIP_DEVICE_ERROR_CONFIG_NOT_FOUND); // Verify key id. + status = ramStorageGet(ramDescr, key, 0, (uint8_t *) &tempVal, &sizeToRead); + SuccessOrExit(err = MapRamStorageStatus(status)); + val = tempVal; + +exit: + return err; +} + +CHIP_ERROR K32WConfig::ReadConfigValue(Key key, uint64_t & val) +{ + CHIP_ERROR err; + uint32_t tempVal; + rsError status; + uint16_t sizeToRead = sizeof(tempVal); + + VerifyOrExit(ValidConfigKey(key), err = CHIP_DEVICE_ERROR_CONFIG_NOT_FOUND); // Verify key id. + status = ramStorageGet(ramDescr, key, 0, (uint8_t *) &tempVal, &sizeToRead); + SuccessOrExit(err = MapRamStorageStatus(status)); + val = tempVal; + +exit: + return err; +} + +CHIP_ERROR K32WConfig::ReadConfigValueStr(Key key, char * buf, size_t bufSize, size_t & outLen) +{ + CHIP_ERROR err; + rsError status; + uint16_t sizeToRead = bufSize; + + VerifyOrExit(ValidConfigKey(key), err = CHIP_DEVICE_ERROR_CONFIG_NOT_FOUND); // Verify key id. + + // We can call ramStorageGet with null pointer to only retrieve the size + status = ramStorageGet(ramDescr, key, 0, (uint8_t *) buf, &sizeToRead); + SuccessOrExit(err = MapRamStorageStatus(status)); + outLen = sizeToRead; + +exit: + return err; +} + +CHIP_ERROR K32WConfig::ReadConfigValueBin(Key key, uint8_t * buf, size_t bufSize, size_t & outLen) +{ + return ReadConfigValueStr(key, (char *) buf, bufSize, outLen); +} + +CHIP_ERROR K32WConfig::ReadConfigValueCounter(uint8_t counterIdx, uint32_t & val) +{ + Key key = kMinConfigKey_ChipCounter + counterIdx; + return ReadConfigValue(key, val); +} + +CHIP_ERROR K32WConfig::WriteConfigValue(Key key, bool val) +{ + CHIP_ERROR err; + rsError status; + + VerifyOrExit(ValidConfigKey(key), err = CHIP_DEVICE_ERROR_CONFIG_NOT_FOUND); // Verify key id. + status = AddToRamStorage(ramDescr, key, (uint8_t *) &val, sizeof(bool)); + SuccessOrExit(err = MapRamStorageStatus(status)); + +#if CHIP_PLAT_NVM_SUPPORT + NvSaveOnIdle(ramDescr, false); +#endif + // ChipLogProgress(DeviceLayer, "WriteConfigValue done"); + +exit: + return err; +} + +CHIP_ERROR K32WConfig::WriteConfigValueSync(Key key, bool val) +{ + CHIP_ERROR err; + rsError status; + + VerifyOrExit(ValidConfigKey(key), err = CHIP_DEVICE_ERROR_CONFIG_NOT_FOUND); // Verify key id. + status = AddToRamStorage(ramDescr, key, (uint8_t *) &val, sizeof(bool)); + SuccessOrExit(err = MapRamStorageStatus(status)); + +#if CHIP_PLAT_NVM_SUPPORT + NvSyncSave(ramDescr, false); +#endif + // ChipLogProgress(DeviceLayer, "WriteConfigValue done"); + +exit: + return err; +} + +CHIP_ERROR K32WConfig::WriteConfigValue(Key key, uint32_t val) +{ + CHIP_ERROR err; + rsError status; + + VerifyOrExit(ValidConfigKey(key), err = CHIP_DEVICE_ERROR_CONFIG_NOT_FOUND); // Verify key id. + status = AddToRamStorage(ramDescr, key, (uint8_t *) &val, sizeof(uint32_t)); + SuccessOrExit(err = MapRamStorageStatus(status)); + +#if CHIP_PLAT_NVM_SUPPORT + NvSaveOnIdle(ramDescr, false); +#endif + // ChipLogProgress(DeviceLayer, "WriteConfigValue done"); + +exit: + return err; +} + +CHIP_ERROR K32WConfig::WriteConfigValue(Key key, uint64_t val) +{ + CHIP_ERROR err; + rsError status; + + VerifyOrExit(ValidConfigKey(key), err = CHIP_DEVICE_ERROR_CONFIG_NOT_FOUND); // Verify key id. + status = AddToRamStorage(ramDescr, key, (uint8_t *) &val, sizeof(uint64_t)); + SuccessOrExit(err = MapRamStorageStatus(status)); + +#if CHIP_PLAT_NVM_SUPPORT + NvSaveOnIdle(ramDescr, false); +#endif + // ChipLogProgress(DeviceLayer, "WriteConfigValue done"); + +exit: + return err; +} + +CHIP_ERROR K32WConfig::WriteConfigValueStr(Key key, const char * str) +{ + return WriteConfigValueStr(key, str, (str != NULL) ? strlen(str) : 0); +} + +CHIP_ERROR K32WConfig::WriteConfigValueStr(Key key, const char * str, size_t strLen) +{ + CHIP_ERROR err; + rsError status; + + VerifyOrExit(ValidConfigKey(key), err = CHIP_DEVICE_ERROR_CONFIG_NOT_FOUND); // Verify key id. + + if (str != NULL) + { + status = AddToRamStorage(ramDescr, key, (uint8_t *) str, strLen); + SuccessOrExit(err = MapRamStorageStatus(status)); + +#if CHIP_PLAT_NVM_SUPPORT + NvSaveOnIdle(ramDescr, false); +#endif + } + else + { + err = ClearConfigValue(key); + SuccessOrExit(err); + } + +exit: + return err; +} + +CHIP_ERROR K32WConfig::WriteConfigValueBin(Key key, const uint8_t * data, size_t dataLen) +{ + return WriteConfigValueStr(key, (char *) data, dataLen); +} + +CHIP_ERROR K32WConfig::WriteConfigValueCounter(uint8_t counterIdx, uint32_t val) +{ + Key key = kMinConfigKey_ChipCounter + counterIdx; + return WriteConfigValue(key, val); +} + +CHIP_ERROR K32WConfig::ClearConfigValue(Key key) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + rsError status; + + VerifyOrExit(ValidConfigKey(key), err = CHIP_DEVICE_ERROR_CONFIG_NOT_FOUND); // Verify key id. + status = ramStorageDelete(ramDescr, key, 0); + SuccessOrExit(err = MapRamStorageStatus(status)); + +#if CHIP_PLAT_NVM_SUPPORT + NvSaveOnIdle(ramDescr, false); +#endif + +exit: + return err; +} + +bool K32WConfig::ConfigValueExists(Key key) +{ + rsError status; + uint16_t sizeToRead; + bool found = false; + + if (ValidConfigKey(key)) + { + status = ramStorageGet(ramDescr, key, 0, NULL, &sizeToRead); + found = (status == RS_ERROR_NONE && sizeToRead != 0); + } + return found; +} + +CHIP_ERROR K32WConfig::FactoryResetConfig(void) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + + FactoryResetConfigInternal(kMinConfigKey_ChipConfig, kMaxConfigKey_ChipConfig); + FactoryResetConfigInternal(kMinConfigKey_KVSKey, kMaxConfigKey_KVSKey); + FactoryResetConfigInternal(kMinConfigKey_KVSValue, kMaxConfigKey_KVSValue); + +#if CHIP_PLAT_NVM_SUPPORT + /* Save in flash now */ + NvSyncSave(ramDescr, false); +#endif + + return err; +} + +void K32WConfig::FactoryResetConfigInternal(Key firstKey, Key lastKey) +{ + for (Key key = firstKey; key <= lastKey; key++) + { + ramStorageDelete(ramDescr, key, 0); + } +} + +CHIP_ERROR K32WConfig::MapRamStorageStatus(rsError rsStatus) +{ + CHIP_ERROR err; + + switch (rsStatus) + { + case RS_ERROR_NONE: + err = CHIP_NO_ERROR; + break; + case RS_ERROR_NOT_FOUND: + err = CHIP_DEVICE_ERROR_CONFIG_NOT_FOUND; + break; + default: + err = CHIP_ERROR_BUFFER_TOO_SMALL; + break; + } + + return err; +} + +bool K32WConfig::ValidConfigKey(Key key) +{ + // Returns true if the key is in the valid CHIP Config PDM key range. + if ((key >= kMinConfigKey_ChipFactory) && (key <= kMaxConfigKey_KVSValue)) + { + return true; + } + + return false; +} + +void K32WConfig::RunConfigUnitTest() {} + +} // namespace Internal +} // namespace DeviceLayer +} // namespace chip diff --git a/src/platform/nxp/k32w/k32w1/K32W1Config.h b/src/platform/nxp/k32w/k32w1/K32W1Config.h new file mode 100644 index 00000000000000..a14b4d5cd10fdd --- /dev/null +++ b/src/platform/nxp/k32w/k32w1/K32W1Config.h @@ -0,0 +1,144 @@ +/* + * + * Copyright (c) 2020-2022 Project CHIP Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file + * Utilities for accessing persisted device configuration on + * platforms based on the K32W1 SDK. + */ + +#pragma once + +#include + +#include + +#include "FreeRTOS.h" +#include "NVM_Interface.h" + +#include "ram_storage.h" + +namespace chip { +namespace DeviceLayer { +namespace Internal { + +constexpr inline uint16_t K32WConfigKey(uint8_t chipId, uint8_t nvmId) +{ + return static_cast(chipId) << 8 | nvmId; +} + +/** + * This implementation uses the NVM component as the underlying storage layer. + * + * NOTE: This class is designed to be mixed-in to the concrete subclass of the + * GenericConfigurationManagerImpl<> template. When used this way, the class + * naturally provides implementations for the delegated members referenced by + * the template class (e.g. the ReadConfigValue() method). + */ +class K32WConfig +{ +public: + // Category ids used by the CHIP Device Layer + static constexpr uint8_t kFileId_ChipFactory = 0x01; /**< Category containing persistent config values set at + * manufacturing time. Retained during factory reset. */ + static constexpr uint8_t kFileId_ChipConfig = 0x02; /**< Catyegory containing dynamic config values set at runtime. + * Cleared during factory reset. */ + static constexpr uint8_t kFileId_ChipCounter = 0x03; /**< Category containing dynamic counter values set at runtime. + * Retained during factory reset. */ + static constexpr uint8_t kFileId_KVSKey = 0x04; /**< Category containing KVS set at runtime. + * Cleared during factory reset. */ + static constexpr uint8_t kFileId_KVSValue = 0x05; /**< Category containing KVS values set at runtime. + * Cleared during factory reset. */ + + using Key = uint16_t; + + // Key definitions for well-known configuration values. + // Factory config keys + static constexpr Key kConfigKey_SerialNum = K32WConfigKey(kFileId_ChipFactory, 0x00); + static constexpr Key kConfigKey_MfrDeviceId = K32WConfigKey(kFileId_ChipFactory, 0x01); + static constexpr Key kConfigKey_MfrDeviceCert = K32WConfigKey(kFileId_ChipFactory, 0x02); + static constexpr Key kConfigKey_MfrDevicePrivateKey = K32WConfigKey(kFileId_ChipFactory, 0x03); + static constexpr Key kConfigKey_ManufacturingDate = K32WConfigKey(kFileId_ChipFactory, 0x04); + static constexpr Key kConfigKey_SetupPinCode = K32WConfigKey(kFileId_ChipFactory, 0x05); + static constexpr Key kConfigKey_MfrDeviceICACerts = K32WConfigKey(kFileId_ChipFactory, 0x06); + static constexpr Key kConfigKey_HardwareVersion = K32WConfigKey(kFileId_ChipFactory, 0x07); + static constexpr Key kConfigKey_SetupDiscriminator = K32WConfigKey(kFileId_ChipFactory, 0x08); + static constexpr Key kConfigKey_Spake2pIterationCount = K32WConfigKey(kFileId_ChipFactory, 0x09); + static constexpr Key kConfigKey_Spake2pSalt = K32WConfigKey(kFileId_ChipFactory, 0x0A); + static constexpr Key kConfigKey_Spake2pVerifier = K32WConfigKey(kFileId_ChipFactory, 0x0B); + + // CHIP Config Keys + static constexpr Key kConfigKey_ServiceConfig = K32WConfigKey(kFileId_ChipConfig, 0x01); + static constexpr Key kConfigKey_PairedAccountId = K32WConfigKey(kFileId_ChipConfig, 0x02); + static constexpr Key kConfigKey_ServiceId = K32WConfigKey(kFileId_ChipConfig, 0x03); + static constexpr Key kConfigKey_LastUsedEpochKeyId = K32WConfigKey(kFileId_ChipConfig, 0x05); + static constexpr Key kConfigKey_FailSafeArmed = K32WConfigKey(kFileId_ChipConfig, 0x06); + static constexpr Key kConfigKey_RegulatoryLocation = K32WConfigKey(kFileId_ChipConfig, 0x07); + static constexpr Key kConfigKey_CountryCode = K32WConfigKey(kFileId_ChipConfig, 0x08); + static constexpr Key kConfigKey_UniqueId = K32WConfigKey(kFileId_ChipConfig, 0x0A); + static constexpr Key kConfigKey_SoftwareVersion = K32WConfigKey(kFileId_ChipConfig, 0x0B); + + // CHIP Counter Keys + static constexpr Key kCounterKey_RebootCount = K32WConfigKey(kFileId_ChipCounter, 0x00); + static constexpr Key kCounterKey_UpTime = K32WConfigKey(kFileId_ChipCounter, 0x01); + static constexpr Key kCounterKey_TotalOperationalHours = K32WConfigKey(kFileId_ChipCounter, 0x02); + static constexpr Key kCounterKey_BootReason = K32WConfigKey(kFileId_ChipCounter, 0x03); + + // Set key id limits for each group. + static constexpr Key kMinConfigKey_ChipFactory = K32WConfigKey(kFileId_ChipFactory, 0x00); + static constexpr Key kMaxConfigKey_ChipFactory = K32WConfigKey(kFileId_ChipFactory, 0xFF); + static constexpr Key kMinConfigKey_ChipConfig = K32WConfigKey(kFileId_ChipConfig, 0x00); + static constexpr Key kMaxConfigKey_ChipConfig = K32WConfigKey(kFileId_ChipConfig, 0xFF); + static constexpr Key kMinConfigKey_ChipCounter = K32WConfigKey(kFileId_ChipCounter, 0x00); + static constexpr Key kMaxConfigKey_ChipCounter = K32WConfigKey(kFileId_ChipCounter, 0xFF); // Allows 32 Counters to be created. + static constexpr Key kMinConfigKey_KVSKey = K32WConfigKey(kFileId_KVSKey, 0x00); + static constexpr Key kMaxConfigKey_KVSKey = K32WConfigKey(kFileId_KVSKey, 0xFF); + static constexpr Key kMinConfigKey_KVSValue = K32WConfigKey(kFileId_KVSValue, 0x00); + static constexpr Key kMaxConfigKey_KVSValue = K32WConfigKey(kFileId_KVSValue, 0xFF); + + static CHIP_ERROR Init(void); + + // Configuration methods used by the GenericConfigurationManagerImpl<> template. + static CHIP_ERROR ReadConfigValue(Key key, bool & val); + static CHIP_ERROR ReadConfigValue(Key key, uint32_t & val); + static CHIP_ERROR ReadConfigValue(Key key, uint64_t & val); + static CHIP_ERROR ReadConfigValueStr(Key key, char * buf, size_t bufSize, size_t & outLen); + static CHIP_ERROR ReadConfigValueBin(Key key, uint8_t * buf, size_t bufSize, size_t & outLen); + static CHIP_ERROR ReadConfigValueCounter(uint8_t counterIdx, uint32_t & val); + static CHIP_ERROR WriteConfigValue(Key key, bool val); + static CHIP_ERROR WriteConfigValueSync(Key key, bool val); + static CHIP_ERROR WriteConfigValue(Key key, uint32_t val); + static CHIP_ERROR WriteConfigValue(Key key, uint64_t val); + static CHIP_ERROR WriteConfigValueStr(Key key, const char * str); + static CHIP_ERROR WriteConfigValueStr(Key key, const char * str, size_t strLen); + static CHIP_ERROR WriteConfigValueBin(Key key, const uint8_t * data, size_t dataLen); + static CHIP_ERROR WriteConfigValueCounter(uint8_t counterIdx, uint32_t val); + static CHIP_ERROR ClearConfigValue(Key key); + static bool ConfigValueExists(Key key); + static CHIP_ERROR FactoryResetConfig(void); + static bool ValidConfigKey(Key key); + + static void RunConfigUnitTest(void); + +private: + static CHIP_ERROR MapRamStorageStatus(rsError rsStatus); + static void FactoryResetConfigInternal(Key firstKey, Key lastKey); +}; + +} // namespace Internal +} // namespace DeviceLayer +} // namespace chip diff --git a/src/platform/nxp/k32w/k32w1/K32W1PersistentStorageOpKeystore.cpp b/src/platform/nxp/k32w/k32w1/K32W1PersistentStorageOpKeystore.cpp new file mode 100644 index 00000000000000..18c1ff41a79963 --- /dev/null +++ b/src/platform/nxp/k32w/k32w1/K32W1PersistentStorageOpKeystore.cpp @@ -0,0 +1,333 @@ +/* + * Copyright (c) 2022 Project CHIP Authors + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "K32W1PersistentStorageOpKeystore.h" + +#include "sss_crypto.h" + +namespace chip { + +using namespace chip::Crypto; + +CHIP_ERROR P256KeypairSSS::Initialize(Crypto::ECPKeyTarget key_target) +{ + CHIP_ERROR error = CHIP_NO_ERROR; + size_t keyBitsLen = kP256_PrivateKey_Length * 8; + size_t keySize = SSS_ECP_KEY_SZ(kP256_PrivateKey_Length); + + Clear(); + + VerifyOrReturnError(sss_sscp_key_object_init(&mKeyObj, &g_keyStore) == kStatus_SSS_Success, CHIP_ERROR_INTERNAL); + + VerifyOrReturnError(sss_sscp_key_object_allocate_handle( + &mKeyObj, 0x0u, kSSS_KeyPart_Pair, kSSS_CipherType_EC_NIST_P, 3 * kP256_PrivateKey_Length, + SSS_KEYPROP_OPERATION_KDF | SSS_KEYPROP_OPERATION_ASYM) == kStatus_SSS_Success, + error = CHIP_ERROR_INTERNAL); + + VerifyOrExit(SSS_ECP_GENERATE_KEY(&mKeyObj, keyBitsLen) == kStatus_SSS_Success, error = CHIP_ERROR_INTERNAL); + + // The first byte of the public key is the uncompressed marker + Uint8::to_uchar(mPublicKey)[0] = 0x04; + + // Extract public key, write from the second byte + VerifyOrExit(SSS_KEY_STORE_GET_PUBKEY(&mKeyObj, Uint8::to_uchar(mPublicKey) + 1, &keySize, &keyBitsLen) == kStatus_SSS_Success, + CHIP_ERROR_INTERNAL); + + mInitialized = true; + +exit: + if (mInitialized != true) + (void) SSS_KEY_OBJ_FREE(&mKeyObj); + + return error; +} + +CHIP_ERROR P256KeypairSSS::ExportBlob(P256SerializedKeypairSSS & output) const +{ + VerifyOrReturnError(mInitialized, CHIP_ERROR_WELL_UNINITIALIZED); + + size_t keyBlobLen = output.Capacity(); + VerifyOrReturnError(sss_sscp_key_store_export_key(&g_keyStore, &mKeyObj, output.Bytes(), &keyBlobLen, + kSSS_blobType_ELKE_blob) == kStatus_SSS_Success, + CHIP_ERROR_INTERNAL); + output.SetLength(keyBlobLen); + + return CHIP_NO_ERROR; +} + +CHIP_ERROR P256KeypairSSS::ImportBlob(P256SerializedKeypairSSS & input) +{ + CHIP_ERROR error = CHIP_NO_ERROR; + + if (false == mInitialized) + { + VerifyOrExit((sss_sscp_key_object_init(&mKeyObj, &g_keyStore) == kStatus_SSS_Success), error = CHIP_ERROR_INTERNAL); + + /* Allocate key handle */ + VerifyOrExit( + (sss_sscp_key_object_allocate_handle(&mKeyObj, 0x0u, kSSS_KeyPart_Pair, kSSS_CipherType_EC_NIST_P, + 3 * kP256_PrivateKey_Length, SSS_KEYPROP_OPERATION_ASYM) == kStatus_SSS_Success), + error = CHIP_ERROR_INTERNAL); + } + + VerifyOrExit((sss_sscp_key_store_import_key(&g_keyStore, &mKeyObj, input.Bytes(), input.Length(), kP256_PrivateKey_Length * 8, + kSSS_blobType_ELKE_blob) == kStatus_SSS_Success), + error = CHIP_ERROR_INTERNAL); + + mInitialized = true; + +exit: + return error; +} + +CHIP_ERROR P256KeypairSSS::ECDSA_sign_msg(const uint8_t * msg, const size_t msg_length, P256ECDSASignature & out_signature) const +{ + CHIP_ERROR error = CHIP_NO_ERROR; + sss_sscp_asymmetric_t asyc; + size_t signatureSize = kP256_ECDSA_Signature_Length_Raw; + + VerifyOrReturnError(mInitialized, CHIP_ERROR_WELL_UNINITIALIZED); + VerifyOrReturnError((msg != nullptr) && (msg_length > 0), CHIP_ERROR_INVALID_ARGUMENT); + + uint8_t digest[kSHA256_Hash_Length]; + memset(&digest[0], 0, sizeof(digest)); + ReturnErrorOnFailure(Hash_SHA256(msg, msg_length, &digest[0])); + + VerifyOrExit((sss_sscp_asymmetric_context_init(&asyc, &g_sssSession, &mKeyObj, kAlgorithm_SSS_ECDSA_SHA256, kMode_SSS_Sign) == + kStatus_SSS_Success), + error = CHIP_ERROR_INTERNAL); + VerifyOrExit((sss_sscp_asymmetric_sign_digest(&asyc, digest, kP256_FE_Length, out_signature.Bytes(), &signatureSize) == + kStatus_SSS_Success), + error = CHIP_ERROR_INTERNAL); + VerifyOrExit(out_signature.SetLength(kP256_ECDSA_Signature_Length_Raw) == CHIP_NO_ERROR, error = CHIP_ERROR_INTERNAL); + +exit: + (void) sss_sscp_asymmetric_context_free(&asyc); + return error; +} + +CHIP_ERROR P256KeypairSSS::NewCertificateSigningRequest(uint8_t * out_csr, size_t & csr_length) const +{ + VerifyOrReturnError(mInitialized, CHIP_ERROR_WELL_UNINITIALIZED); + + MutableByteSpan csr(out_csr, csr_length); + CHIP_ERROR err = GenerateCertificateSigningRequest(this, csr); + csr_length = (CHIP_NO_ERROR == err) ? csr.size() : 0; + return err; +} + +void P256KeypairSSS::Clear() +{ + if (mInitialized) + { + (void) SSS_KEY_OBJ_FREE(&mKeyObj); + mInitialized = false; + } +} + +P256KeypairSSS::~P256KeypairSSS() +{ + Clear(); +} + +bool K32W1PersistentStorageOpKeystore::HasOpKeypairForFabric(FabricIndex fabricIndex) const +{ + VerifyOrReturnError(mStorage != nullptr, false); + VerifyOrReturnError(IsValidFabricIndex(fabricIndex), false); + + // If there was a pending keypair, then there's really a usable key + if (mIsPendingKeypairActive && (fabricIndex == mPendingFabricIndex) && (mPendingKeypair != nullptr)) + { + return true; + } + + P256SerializedKeypairSSS buf; + + uint16_t keySize = static_cast(buf.Capacity()); + CHIP_ERROR err = + mStorage->SyncGetKeyValue(DefaultStorageKeyAllocator::FabricOpKey(fabricIndex).KeyName(), buf.Bytes(), keySize); + + return (err == CHIP_NO_ERROR && (keySize == SSS_KEY_PAIR_BLOB_SIZE)); +} + +CHIP_ERROR K32W1PersistentStorageOpKeystore::NewOpKeypairForFabric(FabricIndex fabricIndex, + MutableByteSpan & outCertificateSigningRequest) +{ + VerifyOrReturnError(mStorage != nullptr, CHIP_ERROR_INCORRECT_STATE); + VerifyOrReturnError(IsValidFabricIndex(fabricIndex), CHIP_ERROR_INVALID_FABRIC_INDEX); + // If a key is pending, we cannot generate for a different fabric index until we commit or revert. + if ((mPendingFabricIndex != kUndefinedFabricIndex) && (fabricIndex != mPendingFabricIndex)) + { + return CHIP_ERROR_INVALID_FABRIC_INDEX; + } + VerifyOrReturnError(outCertificateSigningRequest.size() >= Crypto::kMIN_CSR_Buffer_Size, CHIP_ERROR_BUFFER_TOO_SMALL); + + // Replace previous pending keypair, if any was previously allocated + ResetPendingKey(); + + mPendingKeypair = Platform::New(); + VerifyOrReturnError(mPendingKeypair != nullptr, CHIP_ERROR_NO_MEMORY); + + mPendingKeypair->Initialize(Crypto::ECPKeyTarget::ECDSA); + size_t csrLength = outCertificateSigningRequest.size(); + CHIP_ERROR err = mPendingKeypair->NewCertificateSigningRequest(outCertificateSigningRequest.data(), csrLength); + if (err != CHIP_NO_ERROR) + { + ResetPendingKey(); + return err; + } + + outCertificateSigningRequest.reduce_size(csrLength); + mPendingFabricIndex = fabricIndex; + + return CHIP_NO_ERROR; +} + +CHIP_ERROR K32W1PersistentStorageOpKeystore::ActivateOpKeypairForFabric(FabricIndex fabricIndex, + const Crypto::P256PublicKey & nocPublicKey) +{ + VerifyOrReturnError(mStorage != nullptr, CHIP_ERROR_INCORRECT_STATE); + VerifyOrReturnError(mPendingKeypair != nullptr, CHIP_ERROR_INVALID_FABRIC_INDEX); + VerifyOrReturnError(IsValidFabricIndex(fabricIndex) && (fabricIndex == mPendingFabricIndex), CHIP_ERROR_INVALID_FABRIC_INDEX); + + // Validate public key being activated matches last generated pending keypair + VerifyOrReturnError(mPendingKeypair->Pubkey().Matches(nocPublicKey), CHIP_ERROR_INVALID_PUBLIC_KEY); + + mIsPendingKeypairActive = true; + + return CHIP_NO_ERROR; +} +CHIP_ERROR K32W1PersistentStorageOpKeystore::CommitOpKeypairForFabric(FabricIndex fabricIndex) +{ + VerifyOrReturnError(mStorage != nullptr, CHIP_ERROR_INCORRECT_STATE); + VerifyOrReturnError(mPendingKeypair != nullptr, CHIP_ERROR_INVALID_FABRIC_INDEX); + VerifyOrReturnError(IsValidFabricIndex(fabricIndex) && (fabricIndex == mPendingFabricIndex), CHIP_ERROR_INVALID_FABRIC_INDEX); + VerifyOrReturnError(mIsPendingKeypairActive == true, CHIP_ERROR_INCORRECT_STATE); + + P256SerializedKeypairSSS tmpKeyBlob; + uint16_t keyBlobLen = tmpKeyBlob.Capacity(); + + mPendingKeypair->ExportBlob(tmpKeyBlob); + ReturnErrorOnFailure( + mStorage->SyncSetKeyValue(DefaultStorageKeyAllocator::FabricOpKey(fabricIndex).KeyName(), tmpKeyBlob.Bytes(), keyBlobLen)); + + // If we got here, we succeeded and can reset the pending key: next `SignWithOpKeypair` will use the stored key. + ResetPendingKey(); + return CHIP_NO_ERROR; +} + +CHIP_ERROR K32W1PersistentStorageOpKeystore::RemoveOpKeypairForFabric(FabricIndex fabricIndex) +{ + VerifyOrReturnError(mStorage != nullptr, CHIP_ERROR_INCORRECT_STATE); + VerifyOrReturnError(IsValidFabricIndex(fabricIndex), CHIP_ERROR_INVALID_FABRIC_INDEX); + + // Remove pending state if matching + if ((mPendingKeypair != nullptr) && (fabricIndex == mPendingFabricIndex)) + { + RevertPendingKeypair(); + } + + CHIP_ERROR err = mStorage->SyncDeleteKeyValue(DefaultStorageKeyAllocator::FabricOpKey(fabricIndex).KeyName()); + if (err == CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND) + { + err = CHIP_ERROR_INVALID_FABRIC_INDEX; + } + + return err; +} + +void K32W1PersistentStorageOpKeystore::RevertPendingKeypair() +{ + VerifyOrReturn(mStorage != nullptr); + + // Just reset the pending key, we never stored anything + ResetPendingKey(); +} + +CHIP_ERROR K32W1PersistentStorageOpKeystore::SignWithOpKeypair(FabricIndex fabricIndex, const ByteSpan & message, + Crypto::P256ECDSASignature & outSignature) const +{ + CHIP_ERROR error = CHIP_NO_ERROR; + + VerifyOrReturnError(mStorage != nullptr, CHIP_ERROR_INCORRECT_STATE); + VerifyOrReturnError(IsValidFabricIndex(fabricIndex), CHIP_ERROR_INVALID_FABRIC_INDEX); + + if (mIsPendingKeypairActive && (fabricIndex == mPendingFabricIndex)) + { + VerifyOrReturnError(mPendingKeypair != nullptr, CHIP_ERROR_INTERNAL); + // We have an override key: sign with it! + return mPendingKeypair->ECDSA_sign_msg(message.data(), message.size(), outSignature); + } + + P256SerializedKeypairSSS keyBlob; + uint16_t keyBlobLen = keyBlob.Capacity(); + keyBlob.SetLength(keyBlobLen); + + if (fabricIndex != mCachedFabricIndex) + { + error = + mStorage->SyncGetKeyValue(DefaultStorageKeyAllocator::FabricOpKey(fabricIndex).KeyName(), keyBlob.Bytes(), keyBlobLen); + keyBlob.SetLength(keyBlobLen); + + if (error == CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND) + { + error = CHIP_ERROR_INVALID_FABRIC_INDEX; + } + ReturnErrorOnFailure(error); + + if (nullptr == mCachedKeypair) + { + mCachedKeypair = Platform::New(); + VerifyOrReturnError(mCachedKeypair != nullptr, CHIP_ERROR_NO_MEMORY); + } + + VerifyOrReturnError(mCachedKeypair->ImportBlob(keyBlob) == CHIP_NO_ERROR, CHIP_ERROR_INTERNAL); + } + + return mCachedKeypair->ECDSA_sign_msg(message.data(), message.size(), outSignature); +} + +Crypto::P256Keypair * K32W1PersistentStorageOpKeystore::AllocateEphemeralKeypairForCASE() +{ + // DO NOT CUT AND PASTE without considering the ReleaseEphemeralKeypair(). + // If allocating a derived class, then `ReleaseEphemeralKeypair` MUST + // de-allocate the derived class after up-casting the base class pointer. + return Platform::New(); +} + +void K32W1PersistentStorageOpKeystore::ReleaseEphemeralKeypair(Crypto::P256Keypair * keypair) +{ + // DO NOT CUT AND PASTE without considering the AllocateEphemeralKeypairForCASE(). + // This must delete the same concrete class as allocated in `AllocateEphemeralKeypairForCASE` + Platform::Delete(keypair); +} + +} // namespace chip diff --git a/src/platform/nxp/k32w/k32w1/K32W1PersistentStorageOpKeystore.h b/src/platform/nxp/k32w/k32w1/K32W1PersistentStorageOpKeystore.h new file mode 100644 index 00000000000000..6402c235527ff0 --- /dev/null +++ b/src/platform/nxp/k32w/k32w1/K32W1PersistentStorageOpKeystore.h @@ -0,0 +1,181 @@ +/* + * Copyright (c) 2022 Project CHIP Authors + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file + * Platform-specific implementation of the persistent operational keystore for K32W1 + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +#include "sss_crypto.h" + +namespace chip { + +#define SSS_KEY_PAIR_BLOB_SIZE 120 + +typedef Crypto::SensitiveDataBuffer P256SerializedKeypairSSS; + +class P256KeypairSSS : public Crypto::P256Keypair +{ +public: + P256KeypairSSS() {} + ~P256KeypairSSS() override; + + /** + * @brief Initialize the keypair. + * @return Returns a CHIP_ERROR on error, CHIP_NO_ERROR otherwise + **/ + CHIP_ERROR Initialize(Crypto::ECPKeyTarget key_target) override; + + CHIP_ERROR ExportBlob(P256SerializedKeypairSSS & output) const; + + CHIP_ERROR ImportBlob(P256SerializedKeypairSSS & input); + + /** + * @brief Generate a new Certificate Signing Request (CSR). + * @param csr Newly generated CSR in DER format + * @param csr_length The caller provides the length of input buffer (csr). The function returns the actual length of generated + *CSR. + * @return Returns a CHIP_ERROR on error, CHIP_NO_ERROR otherwise + **/ + CHIP_ERROR NewCertificateSigningRequest(uint8_t * csr, size_t & csr_length) const override; + + /** + * @brief A function to sign a msg using ECDSA + * @param msg Message that needs to be signed + * @param msg_length Length of message + * @param out_signature Buffer that will hold the output signature. The signature consists of: 2 EC elements (r and s), + * in raw point form (see SEC1). + * @return Returns a CHIP_ERROR on error, CHIP_NO_ERROR otherwise + **/ + CHIP_ERROR ECDSA_sign_msg(const uint8_t * msg, size_t msg_length, Crypto::P256ECDSASignature & out_signature) const override; + + const Crypto::P256PublicKey & Pubkey() const override { return mPublicKey; } + + /** Release resources associated with this key pair */ + void Clear(); + +private: + Crypto::P256PublicKey mPublicKey; + mutable sss_sscp_object_t mKeyObj; + bool mInitialized = false; +}; + +/** + * @brief OperationalKeystore implementation making use of PersistentStorageDelegate + * to load/store keypairs. This is the legacy behavior of `FabricTable` prior + * to refactors to use `OperationalKeystore` and exists as a baseline example + * of how to use the interface. + * + */ +class K32W1PersistentStorageOpKeystore : public Crypto::OperationalKeystore +{ +public: + K32W1PersistentStorageOpKeystore() = default; + virtual ~K32W1PersistentStorageOpKeystore() { Finish(); } + + // Non-copyable + K32W1PersistentStorageOpKeystore(K32W1PersistentStorageOpKeystore const &) = delete; + void operator=(K32W1PersistentStorageOpKeystore const &) = delete; + + /** + * @brief Initialize the Operational Keystore to map to a given storage delegate. + * + * @param storage Pointer to persistent storage delegate to use. Must outlive this instance. + * @retval CHIP_NO_ERROR on success + * @retval CHIP_ERROR_INCORRECT_STATE if already initialized + */ + CHIP_ERROR Init(PersistentStorageDelegate * storage) + { + VerifyOrReturnError(mStorage == nullptr, CHIP_ERROR_INCORRECT_STATE); + mPendingFabricIndex = kUndefinedFabricIndex; + mIsExternallyOwnedKeypair = false; + mStorage = storage; + mPendingKeypair = nullptr; + mIsPendingKeypairActive = false; + return CHIP_NO_ERROR; + } + + /** + * @brief Finalize the keystore, so that subsequent operations fail + */ + void Finish() + { + VerifyOrReturn(mStorage != nullptr); + + ResetPendingKey(); + mStorage = nullptr; + } + + bool HasPendingOpKeypair() const override { return (mPendingKeypair != nullptr); } + + bool HasOpKeypairForFabric(FabricIndex fabricIndex) const override; + CHIP_ERROR NewOpKeypairForFabric(FabricIndex fabricIndex, MutableByteSpan & outCertificateSigningRequest) override; + CHIP_ERROR ActivateOpKeypairForFabric(FabricIndex fabricIndex, const Crypto::P256PublicKey & nocPublicKey) override; + CHIP_ERROR CommitOpKeypairForFabric(FabricIndex fabricIndex) override; + CHIP_ERROR RemoveOpKeypairForFabric(FabricIndex fabricIndex) override; + void RevertPendingKeypair() override; + CHIP_ERROR SignWithOpKeypair(FabricIndex fabricIndex, const ByteSpan & message, + Crypto::P256ECDSASignature & outSignature) const override; + Crypto::P256Keypair * AllocateEphemeralKeypairForCASE() override; + void ReleaseEphemeralKeypair(Crypto::P256Keypair * keypair) override; + +protected: + void ResetPendingKey() + { + if (!mIsExternallyOwnedKeypair && (mPendingKeypair != nullptr)) + { + Platform::Delete(mPendingKeypair); + } + if (mCachedKeypair != nullptr) + { + Platform::Delete(mCachedKeypair); + } + mPendingKeypair = nullptr; + mCachedKeypair = nullptr; + mIsExternallyOwnedKeypair = false; + mIsPendingKeypairActive = false; + mPendingFabricIndex = kUndefinedFabricIndex; + mCachedFabricIndex = kUndefinedFabricIndex; + } + + PersistentStorageDelegate * mStorage = nullptr; + + // This pending fabric index is `kUndefinedFabricIndex` if there isn't a pending keypair override for a given fabric. + FabricIndex mPendingFabricIndex = kUndefinedFabricIndex; + P256KeypairSSS * mPendingKeypair = nullptr; + bool mIsPendingKeypairActive = false; + + // Optimize loading the keyblob from storage all the time + mutable P256KeypairSSS * mCachedKeypair = nullptr; + mutable FabricIndex mCachedFabricIndex = kUndefinedFabricIndex; + + // If overridding NewOpKeypairForFabric method in a subclass, set this to true in + // `NewOpKeypairForFabric` if the mPendingKeypair should not be deleted when no longer in use. + bool mIsExternallyOwnedKeypair = false; +}; + +} // namespace chip diff --git a/src/platform/nxp/k32w/k32w1/KeyValueStoreManagerImpl.cpp b/src/platform/nxp/k32w/k32w1/KeyValueStoreManagerImpl.cpp new file mode 100644 index 00000000000000..00f6a9314398bc --- /dev/null +++ b/src/platform/nxp/k32w/k32w1/KeyValueStoreManagerImpl.cpp @@ -0,0 +1,225 @@ +/* + * + * Copyright (c) 2021 Project CHIP Authors + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file + * Platform-specific key value storage implementation for K32W + */ +/* this file behaves like a config.h, comes first */ +#include + +#include +#include +#include +#include +#include + +#include + +namespace chip { +namespace DeviceLayer { +namespace PersistedStorage { + +/* TODO: adjust these values */ +constexpr size_t kMaxNumberOfKeys = 150; +constexpr size_t kMaxKeyValueBytes = 255; + +KeyValueStoreManagerImpl KeyValueStoreManagerImpl::sInstance; + +uint16_t GetStringKeyId(const char * key, uint16_t * freeId) +{ + CHIP_ERROR err = CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND; + uint8_t keyId = 0; + uint8_t nvmIdKvsKey = chip::DeviceLayer::Internal::K32WConfig::kFileId_KVSKey; + bool bFreeIdxFound = false; + char keyString[kMaxKeyValueBytes] = { 0 }; + size_t keyStringSize = 0; + uint16_t nvmInternalId; + + for (keyId = 0; keyId < kMaxNumberOfKeys; keyId++) + { + nvmInternalId = chip::DeviceLayer::Internal::K32WConfigKey(nvmIdKvsKey, keyId); + err = + chip::DeviceLayer::Internal::K32WConfig::ReadConfigValueStr(nvmInternalId, keyString, kMaxKeyValueBytes, keyStringSize); + + if (err == CHIP_NO_ERROR) + { + if (strcmp(key, keyString) == 0) + { + // found the entry we are looking for + break; + } + } + else if ((NULL != freeId) && (false == bFreeIdxFound)) + { + bFreeIdxFound = true; + *freeId = keyId; + } + } + return keyId; +} + +CHIP_ERROR KeyValueStoreManagerImpl::_Get(const char * key, void * value, size_t value_size, size_t * read_bytes_size, + size_t offset_bytes) +{ + CHIP_ERROR err = CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND; + uint8_t nvmIdKvsValue = chip::DeviceLayer::Internal::K32WConfig::kFileId_KVSValue; + size_t read_bytes = 0; + uint8_t keyId = 0; + uint16_t nvmInternalId = 0; + + VerifyOrExit((key != NULL) && (value != NULL), err = CHIP_ERROR_INVALID_ARGUMENT); + + keyId = GetStringKeyId(key, NULL); + + if (keyId < kMaxNumberOfKeys) + { + // This is the ID of the actual data + nvmInternalId = chip::DeviceLayer::Internal::K32WConfigKey(nvmIdKvsValue, keyId); + ChipLogProgress(DeviceLayer, "KVS, get the value of Matter key [%s] with NVM id: %i", key, nvmInternalId); + err = chip::DeviceLayer::Internal::K32WConfig::ReadConfigValueBin(nvmInternalId, (uint8_t *) value, value_size, read_bytes); + + // According to Get api read_bytes_size can be null + if (read_bytes_size) + { + *read_bytes_size = read_bytes; + } + } + else + { + ChipLogProgress(DeviceLayer, "KVS, error in getting the value of Matter key [%s]. Key not found in persistent storage.", + key); + } + +exit: + ConvertError(err); + return err; +} + +CHIP_ERROR KeyValueStoreManagerImpl::_Put(const char * key, const void * value, size_t value_size) +{ + CHIP_ERROR err = CHIP_ERROR_INVALID_ARGUMENT; + bool_t putKey = false; + uint8_t nvmIdKvsKey = chip::DeviceLayer::Internal::K32WConfig::kFileId_KVSKey; + uint8_t nvmIdKvsValue = chip::DeviceLayer::Internal::K32WConfig::kFileId_KVSValue; + uint16_t nvmInternalId = 0; + uint16_t freeKeyId; + uint8_t keyId; + + VerifyOrExit((key != NULL) && (value != NULL), err = CHIP_ERROR_INVALID_ARGUMENT); + + keyId = GetStringKeyId(key, &freeKeyId); + + // Key does not exist. Write both key and value in persistent storage. + if (kMaxNumberOfKeys == keyId) + { + putKey = true; + keyId = freeKeyId; + } + + nvmInternalId = chip::DeviceLayer::Internal::K32WConfigKey(nvmIdKvsValue, keyId); + ChipLogProgress(DeviceLayer, "KVS, save in flash the value of the Matter key [%s] with NVM id: %i", key, nvmInternalId); + + err = chip::DeviceLayer::Internal::K32WConfig::WriteConfigValueBin(nvmInternalId, (uint8_t *) value, value_size); + + /* save the 'key' in flash such that it can be retrieved later on */ + if (err == CHIP_NO_ERROR) + { + if (true == putKey) + { + nvmInternalId = chip::DeviceLayer::Internal::K32WConfigKey(nvmIdKvsKey, keyId); + ChipLogProgress(DeviceLayer, "KVS, save in flash the Matter key [%s] with NVM id: %i", key, nvmInternalId); + + err = chip::DeviceLayer::Internal::K32WConfig::WriteConfigValueStr(nvmInternalId, key, strlen(key) + 1); + + if (err != CHIP_NO_ERROR) + { + ChipLogProgress(DeviceLayer, "KVS, Error while saving in flash the Matter key [%s] with NVM id: %i", key, + nvmInternalId); + } + } + } + else + { + ChipLogProgress(DeviceLayer, "KVS, Error while saving in flash the value of the Matter key [%s] with NVM id: %i", key, + nvmInternalId); + } + +exit: + ConvertError(err); + return err; +} + +CHIP_ERROR KeyValueStoreManagerImpl::_Delete(const char * key) +{ + CHIP_ERROR err = CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND; + uint8_t nvmIdKvsKey = chip::DeviceLayer::Internal::K32WConfig::kFileId_KVSKey; + uint8_t nvmIdKvsValue = chip::DeviceLayer::Internal::K32WConfig::kFileId_KVSValue; + uint8_t keyId = 0; + uint16_t nvmInternalId = 0; + + VerifyOrExit((key != NULL), err = CHIP_ERROR_INVALID_ARGUMENT); + + keyId = GetStringKeyId(key, NULL); + + if (keyId < kMaxNumberOfKeys) + { + // entry exists so we can remove it + nvmInternalId = chip::DeviceLayer::Internal::K32WConfigKey(nvmIdKvsKey, keyId); + + ChipLogProgress(DeviceLayer, "KVS, delete from flash the Matter key [%s] with NVM id: %i", key, nvmInternalId); + err = chip::DeviceLayer::Internal::K32WConfig::ClearConfigValue(nvmInternalId); + + /* also delete the 'key string' from flash */ + if (err == CHIP_NO_ERROR) + { + nvmInternalId = chip::DeviceLayer::Internal::K32WConfigKey(nvmIdKvsValue, keyId); + ChipLogProgress(DeviceLayer, "KVS, delete from flash the value of the Matter key [%s] with NVM id: %i", key, + nvmInternalId); + + err = chip::DeviceLayer::Internal::K32WConfig::ClearConfigValue(nvmInternalId); + + if (err != CHIP_NO_ERROR) + { + ChipLogProgress(DeviceLayer, + "KVS, Error while deleting from flash the value of the Matter key [%s] with NVM id: %i", key, + nvmInternalId); + } + } + else + { + ChipLogProgress(DeviceLayer, "KVS, Error while deleting from flash the Matter key [%s] with NVM id: %i", key, + nvmInternalId); + } + } +exit: + ConvertError(err); + return err; +} + +void KeyValueStoreManagerImpl::ConvertError(CHIP_ERROR & err) +{ + if (err == CHIP_DEVICE_ERROR_CONFIG_NOT_FOUND) + { + err = CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND; + } +} + +} // namespace PersistedStorage +} // namespace DeviceLayer +} // namespace chip diff --git a/src/platform/nxp/k32w/k32w1/KeyValueStoreManagerImpl.h b/src/platform/nxp/k32w/k32w1/KeyValueStoreManagerImpl.h new file mode 100644 index 00000000000000..df942779fe4bc2 --- /dev/null +++ b/src/platform/nxp/k32w/k32w1/KeyValueStoreManagerImpl.h @@ -0,0 +1,83 @@ + +/* + * + * Copyright (c) 2021 Project CHIP Authors + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file + * Platform-specific key value storage implementation for K32W + */ + +#pragma once + +namespace chip { +namespace DeviceLayer { +namespace PersistedStorage { + +class KeyValueStoreManagerImpl final : public KeyValueStoreManager +{ + // Allow the KeyValueStoreManager interface class to delegate method calls to + // the implementation methods provided by this class. + friend class KeyValueStoreManager; + +public: + // NOTE: Currently this platform does not support partial and offset reads + // these will return CHIP_ERROR_NOT_IMPLEMENTED. + CHIP_ERROR _Get(const char * key, void * value, size_t value_size, size_t * read_bytes_size, size_t offset); + + CHIP_ERROR _Delete(const char * key); + + CHIP_ERROR _Put(const char * key, const void * value, size_t value_size); + +private: + // ===== Members for internal use by the following friends. + friend KeyValueStoreManager & KeyValueStoreMgr(); + friend KeyValueStoreManagerImpl & KeyValueStoreMgrImpl(); + + // Reading config values uses the K32WConfig API, which returns CHIP_DEVICE_ERROR_CONFIG_NOT_FOUND + // error if a key was not found. Convert this error to the correct error KeyValueStoreManagerImpl + // should return: CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND + void ConvertError(CHIP_ERROR & err); + + static KeyValueStoreManagerImpl sInstance; +}; + +/** + * Returns the public interface of the KeyValueStoreManager singleton object. + * + * Chip applications should use this to access features of the KeyValueStoreManager object + * that are common to all platforms. + */ +inline KeyValueStoreManager & KeyValueStoreMgr(void) +{ + return KeyValueStoreManagerImpl::sInstance; +} + +/** + * Returns the platform-specific implementation of the KeyValueStoreManager singleton object. + * + * Chip applications can use this to gain access to features of the KeyValueStoreManager + * that are specific to the K32W platform. + */ +inline KeyValueStoreManagerImpl & KeyValueStoreMgrImpl(void) +{ + return KeyValueStoreManagerImpl::sInstance; +} + +} // namespace PersistedStorage +} // namespace DeviceLayer +} // namespace chip diff --git a/src/platform/nxp/k32w/k32w1/Logging.cpp b/src/platform/nxp/k32w/k32w1/Logging.cpp new file mode 100644 index 00000000000000..62b5d114379f3a --- /dev/null +++ b/src/platform/nxp/k32w/k32w1/Logging.cpp @@ -0,0 +1,198 @@ +/* See Project CHIP LICENSE file for licensing information. */ + +#include + +#include +#include +#include +#include +#include +#include + +#include "fsl_debug_console.h" +#include + +#define K32W_LOG_MODULE_NAME chip +#define EOL_CHARS "\r\n" /* End of Line Characters */ +#define EOL_CHARS_LEN 2 /* Length of EOL */ + +/* maximum value for uint32_t is 4294967295 - 10 bytes + 2 bytes for the brackets */ +static constexpr uint8_t timestamp_max_len_bytes = 12; + +/* one byte for the category + 2 bytes for the brackets */ +static constexpr uint8_t category_max_len_bytes = 3; + +#if CHIP_DEVICE_CONFIG_ENABLE_THREAD +#include +#include +#endif // CHIP_DEVICE_CONFIG_ENABLE_THREAD + +static bool isLogInitialized; +extern "C" uint32_t otPlatAlarmMilliGetNow(void); +extern "C" void otPlatUartSendBlocking(const uint8_t * aBuf, uint32_t len); + +namespace chip { +namespace Logging { +namespace Platform { + +void GetMessageString(char * buf, uint8_t bufLen, const char * module, uint8_t category) +{ + int writtenLen = 0; + const char * categoryString; + + /* bufLen must accommodate the length of the timestamp + length of the category + + * length of the module + 2 bytes for the module's brackets + + * 1 byte for the terminating character. + */ + assert(bufLen >= (timestamp_max_len_bytes + category_max_len_bytes + (strlen(module) + 2) + 1)); + + writtenLen = snprintf(buf, bufLen, "[%ld]", otPlatAlarmMilliGetNow()); + bufLen -= writtenLen; + buf += writtenLen; + + if (category != kLogCategory_None) + { + switch (category) + { + case kLogCategory_Error: + categoryString = "E"; + break; + case kLogCategory_Progress: + categoryString = "P"; + break; + case kLogCategory_Detail: + categoryString = "D"; + break; + default: + categoryString = "U"; + } + + writtenLen = snprintf(buf, bufLen, "[%s]", categoryString); + bufLen -= writtenLen; + buf += writtenLen; + } + + writtenLen = snprintf(buf, bufLen, "[%s]", module); +} + +} // namespace Platform +} // namespace Logging +} // namespace chip + +void FillPrefix(char * buf, uint8_t bufLen, const char * module, uint8_t category) +{ + chip::Logging::Platform::GetMessageString(buf, bufLen, module, category); +} + +namespace chip { +namespace DeviceLayer { + +/** + * Called whenever a log message is emitted by CHIP or LwIP. + * + * This function is intended be overridden by the application to, e.g., + * schedule output of queued log entries. + */ +void __attribute__((weak)) OnLogOutput(void) {} + +} // namespace DeviceLayer +} // namespace chip + +void ENFORCE_FORMAT(1, 0) GenericLog(const char * format, va_list arg, const char * module, uint8_t category) +{ + +#if K32W_LOG_ENABLED + + char formattedMsg[CHIP_CONFIG_LOG_MESSAGE_MAX_SIZE - 1] = { 0 }; + size_t prefixLen, writtenLen; + + if (!isLogInitialized) + { + isLogInitialized = true; + otPlatUartEnable(); + } + + /* Prefix is composed of [Time Reference][Debug String][Module Name String] */ + FillPrefix(formattedMsg, CHIP_CONFIG_LOG_MESSAGE_MAX_SIZE - 1, module, category); + prefixLen = strlen(formattedMsg); + + // Append the log message. + writtenLen = vsnprintf(formattedMsg + prefixLen, CHIP_CONFIG_LOG_MESSAGE_MAX_SIZE - prefixLen - EOL_CHARS_LEN, format, arg); + VerifyOrDie(writtenLen > 0); + memcpy(formattedMsg + prefixLen + writtenLen, EOL_CHARS, EOL_CHARS_LEN); + + otPlatUartSendBlocking((const uint8_t *) formattedMsg, strlen(formattedMsg)); + + // Let the application know that a log message has been emitted. + chip::DeviceLayer::OnLogOutput(); + +#endif // K32W_LOG_ENABLED +} + +namespace chip { +namespace Logging { +namespace Platform { + +/** + * CHIP log output function. + */ +void ENFORCE_FORMAT(3, 0) LogV(const char * module, uint8_t category, const char * msg, va_list v) +{ + (void) module; + (void) category; + +#if K32W_LOG_ENABLED + GenericLog(msg, v, module, category); + // Let the application know that a log message has been emitted. + DeviceLayer::OnLogOutput(); + +#endif // K32W_LOG_ENABLED +} + +} // namespace Platform +} // namespace Logging +} // namespace chip + +#undef K32W_LOG_MODULE_NAME +#define K32W_LOG_MODULE_NAME lwip + +#if CHIP_SYSTEM_CONFIG_USE_LWIP +/** + * LwIP log output function. + */ +extern "C" void ENFORCE_FORMAT(1, 2) LwIPLog(const char * msg, ...) +{ + +#if K32W_LOG_ENABLED + va_list v; + const char * module = "LWIP"; + + va_start(v, msg); + GenericLog(msg, v, module, chip::Logging::kLogCategory_None); + va_end(v); +#endif +} +#endif // CHIP_SYSTEM_CONFIG_USE_LWIP + +#if CHIP_DEVICE_CONFIG_ENABLE_THREAD + +#undef K32W_LOG_MODULE_NAME +#define K32W_LOG_MODULE_NAME thread + +extern "C" void ENFORCE_FORMAT(3, 4) otPlatLog(otLogLevel aLogLevel, otLogRegion aLogRegion, const char * aFormat, ...) +{ + +#if K32W_LOG_ENABLED + va_list v; + const char * module = "OT"; + + (void) aLogLevel; + (void) aLogRegion; + + va_start(v, aFormat); + GenericLog(aFormat, v, module, chip::Logging::kLogCategory_None); + va_end(v); +#endif +} + +#endif // CHIP_DEVICE_CONFIG_ENABLE_THREAD diff --git a/src/platform/nxp/k32w/k32w1/LowPowerHooks.cpp b/src/platform/nxp/k32w/k32w1/LowPowerHooks.cpp new file mode 100644 index 00000000000000..752344e5e42cae --- /dev/null +++ b/src/platform/nxp/k32w/k32w1/LowPowerHooks.cpp @@ -0,0 +1,45 @@ +/* + * + * Copyright (c) 2022 Project CHIP Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file + * Provides a glue layer between Matter and NXP-SDK Low Power + */ + +#if defined(chip_with_low_power) && (chip_with_low_power == 1) + +#include +#include + +extern "C" void PWR_DisallowDeviceToSleep(void); +extern "C" void PWR_AllowDeviceToSleep(void); + +using namespace ::chip; +using namespace ::chip::Inet; +using namespace ::chip::DeviceLayer; + +extern "C" void App_AllowDeviceToSleep() +{ + PWR_AllowDeviceToSleep(); +} + +extern "C" void App_DisallowDeviceToSleep() +{ + PWR_DisallowDeviceToSleep(); +} + +#endif diff --git a/src/platform/nxp/k32w/k32w1/OTAFirmwareProcessor.cpp b/src/platform/nxp/k32w/k32w1/OTAFirmwareProcessor.cpp new file mode 100644 index 00000000000000..528261b12b5e59 --- /dev/null +++ b/src/platform/nxp/k32w/k32w1/OTAFirmwareProcessor.cpp @@ -0,0 +1,126 @@ +/* + * + * Copyright (c) 2023 Project CHIP Authors + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include + +#include "OtaSupport.h" + +namespace chip { + +CHIP_ERROR OTAFirmwareProcessor::Init() +{ + ReturnErrorCodeIf(mCallbackProcessDescriptor == nullptr, CHIP_OTA_PROCESSOR_CB_NOT_REGISTERED); + mAccumulator.Init(sizeof(Descriptor)); + + ReturnErrorCodeIf(gOtaSuccess_c != OTA_SelectExternalStoragePartition(), CHIP_OTA_PROCESSOR_EXTERNAL_STORAGE); + + otaResult_t ota_status; + ota_status = OTA_ServiceInit(&mPostedOperationsStorage[0], NB_PENDING_TRANSACTIONS * TRANSACTION_SZ); + + ReturnErrorCodeIf(ota_status != gOtaSuccess_c, CHIP_OTA_PROCESSOR_CLIENT_INIT); + ReturnErrorCodeIf(gOtaSuccess_c != OTA_StartImage(mLength - sizeof(Descriptor)), CHIP_OTA_PROCESSOR_START_IMAGE); + + return CHIP_NO_ERROR; +} + +CHIP_ERROR OTAFirmwareProcessor::Clear() +{ + OTATlvProcessor::ClearInternal(); + mAccumulator.Clear(); + mDescriptorProcessed = false; + + return CHIP_NO_ERROR; +} + +CHIP_ERROR OTAFirmwareProcessor::ProcessInternal(ByteSpan & block) +{ + otaResult_t status; + static uint32_t ulEraseLen = 0; + static uint32_t ulCrtAddr = 0; + + if (!mDescriptorProcessed) + { + ReturnErrorOnFailure(ProcessDescriptor(block)); + } + + ulCrtAddr += block.size(); + + if (ulCrtAddr >= ulEraseLen) + { + ulEraseLen += 0x1000; // flash sector size + + status = OTA_MakeHeadRoomForNextBlock(block.size(), OTAImageProcessorImpl::FetchNextData, 0); + if (gOtaSuccess_c != status) + { + ChipLogError(SoftwareUpdate, "Failed to make room for next block. Status: %d", status); + return CHIP_OTA_PROCESSOR_MAKE_ROOM; + } + } + else + { + OTAImageProcessorImpl::FetchNextData(0); + } + + status = OTA_PushImageChunk((uint8_t *) block.data(), (uint16_t) block.size(), NULL, NULL); + if (gOtaSuccess_c != status) + { + ChipLogError(SoftwareUpdate, "Failed to write image block. Status: %d", status); + return CHIP_OTA_PROCESSOR_PUSH_CHUNK; + } + + return CHIP_OTA_FETCH_ALREADY_SCHEDULED; +} + +CHIP_ERROR OTAFirmwareProcessor::ProcessDescriptor(ByteSpan & block) +{ + ReturnErrorOnFailure(mAccumulator.Accumulate(block)); + ReturnErrorOnFailure(mCallbackProcessDescriptor(static_cast(mAccumulator.data()))); + + mDescriptorProcessed = true; + mAccumulator.Clear(); + + return CHIP_NO_ERROR; +} + +CHIP_ERROR OTAFirmwareProcessor::ApplyAction() +{ + return CHIP_NO_ERROR; +} + +CHIP_ERROR OTAFirmwareProcessor::AbortAction() +{ + OTA_CancelImage(); + Clear(); + + return CHIP_NO_ERROR; +} + +CHIP_ERROR OTAFirmwareProcessor::ExitAction() +{ + if (OTA_CommitImage(NULL) != gOtaSuccess_c) + { + ChipLogError(SoftwareUpdate, "Failed to commit firmware image."); + return CHIP_OTA_PROCESSOR_IMG_COMMIT; + } + + return CHIP_NO_ERROR; +} + +} // namespace chip diff --git a/src/platform/nxp/k32w/k32w1/OTAFirmwareProcessor.h b/src/platform/nxp/k32w/k32w1/OTAFirmwareProcessor.h new file mode 100644 index 00000000000000..c06a2342ba08d6 --- /dev/null +++ b/src/platform/nxp/k32w/k32w1/OTAFirmwareProcessor.h @@ -0,0 +1,57 @@ +/* + * + * Copyright (c) 2023 Project CHIP Authors + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "OtaPrivate.h" +#include +#include + +/* Posted Operations Size Info */ +#define NB_PENDING_TRANSACTIONS 12 +#define TRANSACTION_SZ (sizeof(FLASH_TransactionOpNode_t)) + +namespace chip { + +class OTAFirmwareProcessor : public OTATlvProcessor +{ +public: + struct Descriptor + { + uint32_t version; + char versionString[kVersionStringSize]; + char buildDate[kBuildDateSize]; + }; + + CHIP_ERROR Init() override; + CHIP_ERROR Clear() override; + CHIP_ERROR ApplyAction() override; + CHIP_ERROR AbortAction() override; + CHIP_ERROR ExitAction() override; + +private: + CHIP_ERROR ProcessInternal(ByteSpan & block) override; + CHIP_ERROR ProcessDescriptor(ByteSpan & block); + + OTADataAccumulator mAccumulator; + bool mDescriptorProcessed = false; + + alignas(4) uint8_t mPostedOperationsStorage[NB_PENDING_TRANSACTIONS * TRANSACTION_SZ]; +}; + +} // namespace chip diff --git a/src/platform/nxp/k32w/k32w1/OTAHooks.cpp b/src/platform/nxp/k32w/k32w1/OTAHooks.cpp new file mode 100644 index 00000000000000..b672ed8322177d --- /dev/null +++ b/src/platform/nxp/k32w/k32w1/OTAHooks.cpp @@ -0,0 +1,119 @@ +/* + * + * Copyright (c) 2023 Project CHIP Authors + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include + +#include + +#include + +#include "OtaSupport.h" + +#ifndef CONFIG_CHIP_K32W1_MAX_ENTRIES_TEST +#define CONFIG_CHIP_K32W1_MAX_ENTRIES_TEST 0 +#endif + +#ifndef CONFIG_CHIP_K32W1_OTA_ABORT_HOOK +#define CONFIG_CHIP_K32W1_OTA_ABORT_HOOK 0 +#endif + +#define APPLICATION_PROCESSOR_TAG 1 + +extern "C" void HAL_ResetMCU(void); + +#define ResetMCU HAL_ResetMCU + +#if USE_SMU2_AS_SYSTEM_MEMORY +// The attribute specifier should not be changed. +static chip::OTAFirmwareProcessor gApplicationProcessor __attribute__((section(".smu2"))); +#else +static chip::OTAFirmwareProcessor gApplicationProcessor; +#endif + +CHIP_ERROR ProcessDescriptor(void * descriptor) +{ + auto desc = static_cast(descriptor); + ChipLogDetail(SoftwareUpdate, "Descriptor: %ld, %s, %s", desc->version, desc->versionString, desc->buildDate); + + return CHIP_NO_ERROR; +} + +extern "C" WEAK CHIP_ERROR OtaHookInit() +{ +#if CONFIG_CHIP_K32W1_MAX_ENTRIES_TEST + static chip::OTAFirmwareProcessor processors[8]; +#endif + + gApplicationProcessor.RegisterDescriptorCallback(ProcessDescriptor); + + auto & imageProcessor = chip::OTAImageProcessorImpl::GetDefaultInstance(); + ReturnErrorOnFailure(imageProcessor.RegisterProcessor(APPLICATION_PROCESSOR_TAG, &gApplicationProcessor)); + +#if CONFIG_CHIP_K32W1_MAX_ENTRIES_TEST + for (auto i = 0; i < 8; i++) + { + processors[i].RegisterDescriptorCallback(ProcessDescriptor); + ReturnErrorOnFailure(imageProcessor.RegisterProcessor(i + 4, &processors[i])); + } +#endif // CONFIG_CHIP_K32W1_MAX_ENTRIES_TEST + + return CHIP_NO_ERROR; +} + +extern "C" WEAK void OtaHookReset() +{ + // Process all idle saves + NvShutdown(); + // Set the bootloader flags + OTA_SetNewImageFlag(); + ResetMCU(); +} + +extern "C" WEAK void OtaHookAbort() +{ + /* + Disclaimer: This is not default behavior and it was not checked against + Matter specification compliance. You should use this at your own discretion. + + Use CONFIG_CHIP_K32W1_OTA_ABORT_HOOK to enable/disable this feature (disabled by default). + This hook is called inside OTAImageProcessorImpl::HandleAbort to schedule a retry (when enabled). + */ +#if CONFIG_CHIP_K32W1_OTA_ABORT_HOOK + auto & imageProcessor = chip::OTAImageProcessorImpl::GetDefaultInstance(); + auto & providerLocation = imageProcessor.GetBackupProvider(); + + if (providerLocation.HasValue()) + { + auto * requestor = chip::GetRequestorInstance(); + requestor->SetCurrentProviderLocation(providerLocation.Value()); + if (requestor->GetCurrentUpdateState() == chip::OTARequestorInterface::OTAUpdateStateEnum::kIdle) + { + chip::DeviceLayer::SystemLayer().ScheduleLambda([requestor] { requestor->TriggerImmediateQueryInternal(); }); + } + else + { + ChipLogError(SoftwareUpdate, "OTA requestor not in kIdle"); + } + } + else + { + ChipLogError(SoftwareUpdate, "Backup provider info not available"); + } +#endif +} diff --git a/src/platform/nxp/k32w/k32w1/PlatformManagerImpl.cpp b/src/platform/nxp/k32w/k32w1/PlatformManagerImpl.cpp new file mode 100644 index 00000000000000..53e68779bcecd2 --- /dev/null +++ b/src/platform/nxp/k32w/k32w1/PlatformManagerImpl.cpp @@ -0,0 +1,140 @@ +/* + * + * Copyright (c) 2020 Project CHIP Authors + * Copyright (c) 2020 Nest Labs, Inc. + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file + * Provides an implementation of the PlatformManager object + * for K32W platforms using the NXP K32W SDK. + */ +/* this file behaves like a config.h, comes first */ +#include + +#include +#include +#include +#include +#include + +#if CHIP_SYSTEM_CONFIG_USE_LWIP +#include +#endif + +#include "fsl_component_mem_manager.h" +#include "fwk_platform.h" +#include + +extern uint8_t __data_end__[], m_data0_end[]; +memAreaCfg_t data0Heap = { .start_address = (void *) __data_end__, .end_address = (void *) m_data0_end }; + +#if defined(gAppHighSystemClockFrequency_d) && (gAppHighSystemClockFrequency_d > 0) && defined(USE_SMU2_AS_SYSTEM_MEMORY) +extern "C" void APP_SysInitHook(void) +{ + // NBU has to be initialized before calling this function + PLATFORM_SetNbuConstraintFrequency(PLATFORM_NBU_MIN_FREQ_64MHZ); + // Disable low-power on NBU + PLATFORM_DisableControllerLowPower(); +} +#endif + +namespace chip { +namespace DeviceLayer { + +PlatformManagerImpl PlatformManagerImpl::sInstance; + +CHIP_ERROR PlatformManagerImpl::InitBoardFwk(void) +{ + mem_status_t memSt = kStatus_MemSuccess; + + SecLib_Init(); + + memSt = MEM_RegisterExtendedArea(&data0Heap, NULL, 0U); + VerifyOrReturnError(memSt == kStatus_MemSuccess, CHIP_ERROR_INTERNAL); + + return CHIP_NO_ERROR; +} + +static int app_entropy_source(void * data, unsigned char * output, size_t len, size_t * olen) +{ + otError otErr = otPlatEntropyGet(output, (uint16_t) len); + + if (otErr != OT_ERROR_NONE) + { + return -1; + } + + *olen = len; + return 0; +} + +CHIP_ERROR PlatformManagerImpl::_InitChipStack(void) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + + // Initialize the configuration system. + err = Internal::K32WConfig::Init(); + SuccessOrExit(err); + + SetConfigurationMgr(&ConfigurationManagerImpl::GetDefaultInstance()); + + mStartTime = System::SystemClock().GetMonotonicTimestamp(); + +#if CHIP_SYSTEM_CONFIG_USE_LWIP + // Initialize LwIP. + tcpip_init(NULL, NULL); +#endif + + err = chip::Crypto::add_entropy_source(app_entropy_source, NULL, 16); + SuccessOrExit(err); + + // Call _InitChipStack() on the generic implementation base class + // to finish the initialization process. + err = Internal::GenericPlatformManagerImpl_FreeRTOS::_InitChipStack(); + SuccessOrExit(err); + +exit: + return err; +} + +void PlatformManagerImpl::_Shutdown() +{ + uint64_t upTime = 0; + + if (GetDiagnosticDataProvider().GetUpTime(upTime) == CHIP_NO_ERROR) + { + uint32_t totalOperationalHours = 0; + + if (ConfigurationMgr().GetTotalOperationalHours(totalOperationalHours) == CHIP_NO_ERROR) + { + ConfigurationMgr().StoreTotalOperationalHours(totalOperationalHours + static_cast(upTime / 3600)); + } + else + { + ChipLogError(DeviceLayer, "Failed to get total operational hours of the Node"); + } + } + else + { + ChipLogError(DeviceLayer, "Failed to get current uptime since the Node’s last reboot"); + } + + Internal::GenericPlatformManagerImpl_FreeRTOS::_Shutdown(); +} + +} // namespace DeviceLayer +} // namespace chip diff --git a/src/platform/nxp/k32w/k32w1/PlatformManagerImpl.h b/src/platform/nxp/k32w/k32w1/PlatformManagerImpl.h new file mode 100644 index 00000000000000..70ece1cdf11f3c --- /dev/null +++ b/src/platform/nxp/k32w/k32w1/PlatformManagerImpl.h @@ -0,0 +1,96 @@ +/* + * + * Copyright (c) 2020 Project CHIP Authors + * Copyright (c) 2020 Nest Labs, Inc. + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file + * Provides an implementation of the PlatformManager object + * for K32W platforms using the NXP SDK. + */ + +#pragma once + +#include + +namespace chip { +namespace DeviceLayer { + +/** + * Concrete implementation of the PlatformManager singleton object for the K32W platform. + */ +class PlatformManagerImpl final : public PlatformManager, public Internal::GenericPlatformManagerImpl_FreeRTOS +{ + // Allow the PlatformManager interface class to delegate method calls to + // the implementation methods provided by this class. + friend PlatformManager; + + // Allow the generic implementation base class to call helper methods on + // this class. +#ifndef DOXYGEN_SHOULD_SKIP_THIS + friend Internal::GenericPlatformManagerImpl_FreeRTOS; +#endif + +public: + // ===== Platform-specific members that may be accessed directly by the application. + + System::Clock::Timestamp GetStartTime() { return mStartTime; } + CHIP_ERROR InitBoardFwk(void); + +private: + // ===== Methods that implement the PlatformManager abstract interface. + + CHIP_ERROR _InitChipStack(void); + void _Shutdown(); + + // ===== Members for internal use by the following friends. + + friend PlatformManager & PlatformMgr(void); + friend PlatformManagerImpl & PlatformMgrImpl(void); + friend class Internal::BLEManagerImpl; + + System::Clock::Timestamp mStartTime = System::Clock::kZero; + + static PlatformManagerImpl sInstance; + + using Internal::GenericPlatformManagerImpl_FreeRTOS::PostEventFromISR; +}; + +/** + * Returns the public interface of the PlatformManager singleton object. + * + * chip applications should use this to access features of the PlatformManager object + * that are common to all platforms. + */ +inline PlatformManager & PlatformMgr(void) +{ + return PlatformManagerImpl::sInstance; +} + +/** + * Returns the platform-specific implementation of the PlatformManager singleton object. + * + * chip applications can use this to gain access to features of the PlatformManager + * that are specific to the K32W platform. + */ +inline PlatformManagerImpl & PlatformMgrImpl(void) +{ + return PlatformManagerImpl::sInstance; +} + +} // namespace DeviceLayer +} // namespace chip diff --git a/src/platform/nxp/k32w/k32w1/SoftwareUpdateManagerImpl.cpp b/src/platform/nxp/k32w/k32w1/SoftwareUpdateManagerImpl.cpp new file mode 100644 index 00000000000000..c464bb327c24a6 --- /dev/null +++ b/src/platform/nxp/k32w/k32w1/SoftwareUpdateManagerImpl.cpp @@ -0,0 +1,46 @@ +/* + * + * Copyright (c) 2020 Project CHIP Authors + * Copyright (c) 2020 Google LLC. + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/* this file behaves like a config.h, comes first */ +#include + +#if CHIP_DEVICE_CONFIG_ENABLE_SOFTWARE_UPDATE_MANAGER + +#include +#include + +#include +#include + +namespace chip { +namespace DeviceLayer { + +SoftwareUpdateManagerImpl SoftwareUpdateManagerImpl::sInstance; + +CHIP_ERROR SoftwareUpdateManagerImpl::_Init(void) +{ + Internal::GenericSoftwareUpdateManagerImpl_BDX::DoInit(); + Internal::GenericSoftwareUpdateManagerImpl::DoInit(); + + return CHIP_NO_ERROR; +} + +} // namespace DeviceLayer +} // namespace chip + +#endif // CHIP_DEVICE_CONFIG_ENABLE_SOFTWARE_UPDATE_MANAGER diff --git a/src/platform/nxp/k32w/k32w1/SoftwareUpdateManagerImpl.h b/src/platform/nxp/k32w/k32w1/SoftwareUpdateManagerImpl.h new file mode 100644 index 00000000000000..f04025435928d5 --- /dev/null +++ b/src/platform/nxp/k32w/k32w1/SoftwareUpdateManagerImpl.h @@ -0,0 +1,89 @@ +/* + * + * Copyright (c) 2020 Project CHIP Authors + * Copyright (c) 2020 Google LLC. + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#if CHIP_DEVICE_CONFIG_ENABLE_SOFTWARE_UPDATE_MANAGER + +#include +#include + +namespace chip { +namespace DeviceLayer { + +/** + * Concrete implementation of the SoftwareUpdateManager singleton object for the + * NXP K32W platforms. + */ +class SoftwareUpdateManagerImpl final : public SoftwareUpdateManager, + public Internal::GenericSoftwareUpdateManagerImpl, + public Internal::GenericSoftwareUpdateManagerImpl_BDX +{ + // Allow the SoftwareUpdateManager interface class to delegate method calls to + // the implementation methods provided by this class. + friend class SoftwareUpdateManager; + + // Allow the GenericSoftwareUpdateManagerImpl base class to access helper methods + // and types defined on this class. + friend class Internal::GenericSoftwareUpdateManagerImpl; + + // Allow the GenericSoftwareUpdateManagerImpl_BDX base class to access helper methods + // and types defined on this class. + friend class Internal::GenericSoftwareUpdateManagerImpl_BDX; + +public: + // ===== Members for internal use by the following friends. + + friend ::chip::DeviceLayer::SoftwareUpdateManager & SoftwareUpdateMgr(void); + friend SoftwareUpdateManagerImpl & SoftwareUpdateMgrImpl(void); + + static SoftwareUpdateManagerImpl sInstance; + +private: + // ===== Members that implement the SoftwareUpdateManager abstract interface. + + CHIP_ERROR _Init(void); +}; + +/** + * Returns a reference to the public interface of the SoftwareUpdateManager singleton object. + * + * Internal components should use this to access features of the SoftwareUpdateManager object + * that are common to all platforms. + */ +inline SoftwareUpdateManager & SoftwareUpdateMgr(void) +{ + return SoftwareUpdateManagerImpl::sInstance; +} + +/** + * Returns the platform-specific implementation of the SoftwareUpdateManager singleton object. + * + * Internal components can use this to gain access to features of the SoftwareUpdateManager + * that are specific to the K32W platform. + */ +inline SoftwareUpdateManagerImpl & SoftwareUpdateMgrImpl(void) +{ + return SoftwareUpdateManagerImpl::sInstance; +} + +} // namespace DeviceLayer +} // namespace chip + +#endif // CHIP_DEVICE_CONFIG_ENABLE_SOFTWARE_UPDATE_MANAGER diff --git a/src/platform/nxp/k32w/k32w1/SystemPlatformConfig.h b/src/platform/nxp/k32w/k32w1/SystemPlatformConfig.h new file mode 100644 index 00000000000000..bda39157039697 --- /dev/null +++ b/src/platform/nxp/k32w/k32w1/SystemPlatformConfig.h @@ -0,0 +1,42 @@ +/* + * + * Copyright (c) 2020 Project CHIP Authors + * Copyright (c) 2020 Google LLC. + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file + * Platform-specific configuration overrides for the CHIP System + * Layer on NXP K32W Platforms. + * + */ + +#pragma once + +#include + +namespace chip { +namespace DeviceLayer { +struct ChipDeviceEvent; +} // namespace DeviceLayer +} // namespace chip + +// ==================== Platform Adaptations ==================== +#define CHIP_SYSTEM_CONFIG_PLATFORM_PROVIDES_TIME 1 +#define CHIP_SYSTEM_CONFIG_EVENT_OBJECT_TYPE const struct ::chip::DeviceLayer::ChipDeviceEvent * +#define CHIP_SYSTEM_CONFIG_PACKETBUFFER_POOL_SIZE 0 + +// ========== Platform-specific Configuration Overrides ========= diff --git a/src/platform/nxp/k32w/k32w1/SystemTimeSupport.cpp b/src/platform/nxp/k32w/k32w1/SystemTimeSupport.cpp new file mode 100644 index 00000000000000..a61c5d648bc039 --- /dev/null +++ b/src/platform/nxp/k32w/k32w1/SystemTimeSupport.cpp @@ -0,0 +1,122 @@ +/* + * + * Copyright (c) 2020-2023 Project CHIP Authors + * Copyright (c) 2018 Nest Labs, Inc. + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file + * Provides implementations of the CHIP System Layer platform + * time/clock functions that are suitable for use on the K32W1 platform. + */ +/* this file behaves like a config.h, comes first */ +#include + +extern "C" uint32_t otPlatAlarmMicroGetNow(void); + +namespace chip { +namespace System { +namespace Clock { + +namespace Internal { +ClockImpl gClockImpl; +} // namespace Internal + +namespace { + +uint64_t sBootTimeUS = 0; + +} // unnamed namespace + +Microseconds64 ClockImpl::GetMonotonicMicroseconds64(void) +{ + return Clock::Microseconds64(otPlatAlarmMicroGetNow()); +} + +Milliseconds64 ClockImpl::GetMonotonicMilliseconds64(void) +{ + return std::chrono::duration_cast(GetMonotonicMicroseconds64()); +} + +uint64_t GetClock_Monotonic(void) +{ + return otPlatAlarmMicroGetNow(); +} + +uint64_t GetClock_MonotonicMS(void) +{ + return (otPlatAlarmMicroGetNow() / 1000); +} + +uint64_t GetClock_MonotonicHiRes(void) +{ + return GetClock_Monotonic(); +} + +CHIP_ERROR ClockImpl::GetClock_RealTime(Clock::Microseconds64 & aCurTime) +{ + // TODO(19081): This platform does not properly error out if wall clock has + // not been set. For now, short circuit this. + return CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE; +#if 0 + if (sBootTimeUS == 0) + { + return CHIP_ERROR_REAL_TIME_NOT_SYNCED; + } + aCurTime = Clock::Microseconds64(sBootTimeUS + GetClock_Monotonic()); + return CHIP_NO_ERROR; +#endif +} + +CHIP_ERROR ClockImpl::GetClock_RealTimeMS(Clock::Milliseconds64 & aCurTime) +{ + if (sBootTimeUS == 0) + { + return CHIP_ERROR_REAL_TIME_NOT_SYNCED; + } + aCurTime = Clock::Milliseconds64((sBootTimeUS + GetClock_Monotonic()) / 1000); + return CHIP_NO_ERROR; +} + +CHIP_ERROR ClockImpl::SetClock_RealTime(Clock::Microseconds64 aNewCurTime) +{ + uint64_t timeSinceBootUS = GetClock_Monotonic(); + if (aNewCurTime.count() > timeSinceBootUS) + { + sBootTimeUS = aNewCurTime.count() - timeSinceBootUS; + } + else + { + sBootTimeUS = 0; + } + return CHIP_NO_ERROR; +} + +CHIP_ERROR InitClock_RealTime() +{ + Clock::Microseconds64 curTime = + Clock::Microseconds64((static_cast(CHIP_SYSTEM_CONFIG_VALID_REAL_TIME_THRESHOLD) * UINT64_C(1000000))); + // Use CHIP_SYSTEM_CONFIG_VALID_REAL_TIME_THRESHOLD as the initial value of RealTime. + // Then the RealTime obtained from GetClock_RealTime will be always valid. + // + // TODO(19081): This is broken because it causes the platform to report + // that it does have wall clock time when it actually doesn't. + return System::SystemClock().SetClock_RealTime(curTime); +} + +} // namespace Clock +} // namespace System +} // namespace chip diff --git a/src/platform/nxp/k32w/k32w1/ThreadStackManagerImpl.cpp b/src/platform/nxp/k32w/k32w1/ThreadStackManagerImpl.cpp new file mode 100644 index 00000000000000..7bf96771fb78d7 --- /dev/null +++ b/src/platform/nxp/k32w/k32w1/ThreadStackManagerImpl.cpp @@ -0,0 +1,127 @@ +/* + * + * Copyright (c) 2020 Project CHIP Authors + * Copyright (c) 2020 Nest Labs, Inc. + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file + * Provides an implementation of the ThreadStackManager object for + * K32W platforms using the NXP SDK and the OpenThread + * stack. + * + */ + +/* this file behaves like a config.h, comes first */ +#include + +#include +#include + +#include +#include + +#include + +namespace chip { +namespace DeviceLayer { + +using namespace ::chip::DeviceLayer::Internal; + +ThreadStackManagerImpl ThreadStackManagerImpl::sInstance; + +CHIP_ERROR ThreadStackManagerImpl::_InitThreadStack(void) +{ + return InitThreadStack(NULL); +} + +CHIP_ERROR ThreadStackManagerImpl::InitThreadStack(otInstance * otInst) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + + // Initialize the generic implementation base classes. + err = GenericThreadStackManagerImpl_FreeRTOS::DoInit(); + SuccessOrExit(err); + err = GenericThreadStackManagerImpl_OpenThread::DoInit(otInst); + SuccessOrExit(err); + +exit: + return err; +} + +void ThreadStackManagerImpl::ProcessThreadActivity() +{ + /* reuse thread task for ble processing. + * by doing this, we avoid allocating a new stack for short-lived + * BLE processing (e.g.: only during Matter commissioning) + */ + auto * bleManager = &chip::DeviceLayer::Internal::BLEMgrImpl(); + bleManager->DoBleProcessing(); + + otTaskletsProcess(OTInstance()); + otSysProcessDrivers(OTInstance()); +} + +bool ThreadStackManagerImpl::IsInitialized() +{ + return sInstance.mThreadStackLock != NULL; +} + +} // namespace DeviceLayer +} // namespace chip + +using namespace ::chip::DeviceLayer; + +/** + * Glue function called directly by the OpenThread stack when tasklet processing work + * is pending. + */ +extern "C" void otTaskletsSignalPending(otInstance * p_instance) +{ + ThreadStackMgrImpl().SignalThreadActivityPending(); +} + +extern "C" void * pvPortCallocRtos(size_t num, size_t size) +{ + size_t totalAllocSize = (size_t) (num * size); + + if (size && totalAllocSize / size != num) + return nullptr; + + void * p = pvPortMalloc(totalAllocSize); + + if (p) + { + memset(p, 0, totalAllocSize); + } + + return p; +} + +extern "C" void * otPlatCAlloc(size_t aNum, size_t aSize) +{ + return CHIPPlatformMemoryCalloc(aNum, aSize); +} + +extern "C" void otPlatFree(void * aPtr) +{ + return CHIPPlatformMemoryFree(aPtr); +} + +extern "C" void * otPlatRealloc(void * p, size_t aSize) +{ + return CHIPPlatformMemoryRealloc(p, aSize); +} diff --git a/src/platform/nxp/k32w/k32w1/ThreadStackManagerImpl.h b/src/platform/nxp/k32w/k32w1/ThreadStackManagerImpl.h new file mode 100644 index 00000000000000..5f7adcefec6cb1 --- /dev/null +++ b/src/platform/nxp/k32w/k32w1/ThreadStackManagerImpl.h @@ -0,0 +1,121 @@ +/* + * + * Copyright (c) 2020 Project CHIP Authors + * Copyright (c) 2020 Nest Labs, Inc. + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file + * Provides an implementation of the ThreadStackManager object + * for K32W platforms using the NXP SDK and the OpenThread stack. + */ + +#pragma once + +#include +#include +#include +#include + +extern "C" void otSysEventSignalPending(void); + +namespace chip { +namespace DeviceLayer { + +class ThreadStackManager; +class ThreadStackManagerImpl; +namespace Internal { +extern int GetEntropy_K32W(uint8_t * buf, size_t bufSize); +} + +/** + * Concrete implementation of the ThreadStackManager singleton object for K32W platforms + * using the NXP SDK and the OpenThread stack. + */ +class ThreadStackManagerImpl final : public ThreadStackManager, + public Internal::GenericThreadStackManagerImpl_OpenThread, + public Internal::GenericThreadStackManagerImpl_FreeRTOS +{ + // Allow the ThreadStackManager interface class to delegate method calls to + // the implementation methods provided by this class. + friend class ThreadStackManager; + + // Allow the generic implementation base classes to call helper methods on + // this class. +#ifndef DOXYGEN_SHOULD_SKIP_THIS + friend Internal::GenericThreadStackManagerImpl_OpenThread; + friend Internal::GenericThreadStackManagerImpl_OpenThread; + friend Internal::GenericThreadStackManagerImpl_FreeRTOS; +#endif + + // Allow glue functions called by OpenThread to call helper methods on this + // class. + friend void ::otTaskletsSignalPending(otInstance * otInst); + friend void ::otSysEventSignalPending(void); + +public: + // ===== Platform-specific members that may be accessed directly by the application. + + using ThreadStackManager::InitThreadStack; + CHIP_ERROR InitThreadStack(otInstance * otInst); + + using ThreadStackManager::ProcessThreadActivity; + void ProcessThreadActivity(); + +private: + // ===== Methods that implement the ThreadStackManager abstract interface. + + CHIP_ERROR _InitThreadStack(void); + + // ===== Members for internal use by the following friends. + + friend ThreadStackManager & ::chip::DeviceLayer::ThreadStackMgr(void); + friend ThreadStackManagerImpl & ::chip::DeviceLayer::ThreadStackMgrImpl(void); + friend int Internal::GetEntropy_K32W(uint8_t * buf, size_t bufSize); + + static ThreadStackManagerImpl sInstance; + + static bool IsInitialized(); + + // ===== Private members for use by this class only. + + ThreadStackManagerImpl() = default; +}; + +/** + * Returns the public interface of the ThreadStackManager singleton object. + * + * chip applications should use this to access features of the ThreadStackManager object + * that are common to all platforms. + */ +inline ThreadStackManager & ThreadStackMgr(void) +{ + return ThreadStackManagerImpl::sInstance; +} + +/** + * Returns the platform-specific implementation of the ThreadStackManager singleton object. + * + * chip applications can use this to gain access to features of the ThreadStackManager + * that are specific to K32W platforms. + */ +inline ThreadStackManagerImpl & ThreadStackMgrImpl(void) +{ + return ThreadStackManagerImpl::sInstance; +} + +} // namespace DeviceLayer +} // namespace chip diff --git a/src/platform/nxp/k32w/k32w1/args.gni b/src/platform/nxp/k32w/k32w1/args.gni new file mode 100644 index 00000000000000..4170e95ea6f632 --- /dev/null +++ b/src/platform/nxp/k32w/k32w1/args.gni @@ -0,0 +1,50 @@ +# Copyright (c) 2020 Project CHIP Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import("//build_overrides/chip.gni") +import("//build_overrides/k32w1_sdk.gni") +import("//build_overrides/openthread.gni") + +declare_args() { + chip_with_ot_cli = 0 + chip_with_low_power = 0 + sdk_release = 1 +} + +chip_device_platform = "k32w1" + +lwip_platform = "k32w1" + +chip_inet_config_enable_ipv4 = false + +chip_inet_config_enable_tcp_endpoint = false + +chip_build_tests = false + +chip_detail_logging = true +chip_progress_logging = true + +chip_mdns = "platform" + +chip_system_config_use_open_thread_inet_endpoints = true +chip_with_lwip = false +mbedtls_target = "${chip_root}/third_party/nxp/k32w1_sdk:mbedtls" +openthread_external_mbedtls = mbedtls_target + +openthread_project_core_config_file = "OpenThreadConfig.h" +openthread_core_config_platform_check_file = + "openthread-core-k32w1-config-check.h" +openthread_core_config_deps = [ "${chip_root}/examples/platform/nxp/k32w/k32w1:openthread_core_config_k32w1_chip_examples" ] + +openthread_external_platform = "${chip_root}/third_party/openthread/platforms/nxp/k32w/k32w1:libopenthread-k32w1" diff --git a/src/platform/nxp/k32w/k32w1/ble_function_mux.c b/src/platform/nxp/k32w/k32w1/ble_function_mux.c new file mode 100644 index 00000000000000..ed3a643b8bdb9b --- /dev/null +++ b/src/platform/nxp/k32w/k32w1/ble_function_mux.c @@ -0,0 +1,94 @@ +/* + * + * Copyright (c) 2021 Project CHIP Authors + * Copyright (c) 2020 Nest Labs, Inc. + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file + * Provides an implementation for BLE Host NVM functions + */ +#include "assert.h" +#include "ble_constants.h" +#include "fsl_os_abstraction.h" +#include "gap_interface.h" +#include "gatt_database.h" + +#include "ble_constants.h" +#include "gatt_db_dynamic.h" + +#include "ble_function_mux.h" + +/* Security Manager */ +#define smpEdiv 0x1F99 +#define mcEncryptionKeySize_c 16 + +/* LTK */ +static uint8_t smpLtk[gcSmpMaxLtkSize_c] = { 0xD6, 0x93, 0xE8, 0xA4, 0x23, 0x55, 0x48, 0x99, + 0x1D, 0x77, 0x61, 0xE6, 0x63, 0x2B, 0x10, 0x8E }; + +/* RAND*/ +static uint8_t smpRand[gcSmpMaxRandSize_c] = { 0x26, 0x1E, 0xF6, 0x09, 0x97, 0x2E, 0xAD, 0x7E }; + +/* IRK */ +static uint8_t smpIrk[gcSmpIrkSize_c] = { 0x0A, 0x2D, 0xF4, 0x65, 0xE3, 0xBD, 0x7B, 0x49, + 0x1E, 0xB4, 0xC0, 0x95, 0x95, 0x13, 0x46, 0x73 }; + +/* CSRK */ +static uint8_t smpCsrk[gcSmpCsrkSize_c] = { 0x90, 0xD5, 0x06, 0x95, 0x92, 0xED, 0x91, 0xD7, + 0xA8, 0x9E, 0x2C, 0xDC, 0x4A, 0x93, 0x5B, 0xF9 }; + +gapSmpKeys_t gSmpKeys = { + .cLtkSize = mcEncryptionKeySize_c, + .aLtk = (void *) smpLtk, + .aIrk = (void *) smpIrk, + .aCsrk = (void *) smpCsrk, + .aRand = (void *) smpRand, + .cRandSize = gcSmpMaxRandSize_c, + .ediv = smpEdiv, +}; + +/******************************************************************************* + * Functions needed by the BLE stack + ******************************************************************************/ +void App_NvmRead(uint8_t mEntryIdx, void * pBondHeader, void * pBondDataDynamic, void * pBondDataStatic, void * pBondDataDeviceInfo, + void * pBondDataDescriptor, uint8_t mDescriptorIndex) +{ + NOT_USED(mEntryIdx); + NOT_USED(pBondHeader); + NOT_USED(pBondDataDynamic); + NOT_USED(pBondDataStatic); + NOT_USED(pBondDataDeviceInfo); + NOT_USED(pBondDataDescriptor); + NOT_USED(mDescriptorIndex); +} + +void App_NvmWrite(uint8_t mEntryIdx, void * pBondHeader, void * pBondDataDynamic, void * pBondDataStatic, + void * pBondDataDeviceInfo, void * pBondDataDescriptor, uint8_t mDescriptorIndex) +{ + NOT_USED(mEntryIdx); + NOT_USED(pBondHeader); + NOT_USED(pBondDataDynamic); + NOT_USED(pBondDataStatic); + NOT_USED(pBondDataDeviceInfo); + NOT_USED(pBondDataDescriptor); + NOT_USED(mDescriptorIndex); +} + +void App_NvmErase(uint8_t mEntryIdx) +{ + NOT_USED(mEntryIdx); +} diff --git a/src/platform/nxp/k32w/k32w1/ble_function_mux.h b/src/platform/nxp/k32w/k32w1/ble_function_mux.h new file mode 100644 index 00000000000000..8b9417d8b54e13 --- /dev/null +++ b/src/platform/nxp/k32w/k32w1/ble_function_mux.h @@ -0,0 +1,34 @@ +/* + * + * Copyright (c) 2020 Project CHIP Authors + * Copyright (c) 2020 Nest Labs, Inc. + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file + * Provides an implementation for BLE Host NVM functions + */ + +#ifndef BLE_FUNCTION_MUX_H +#define BLE_FUNCTION_MUX_H + +typedef enum +{ + kBleFuncMux_AppMode_None, + kBleFuncMux_AppMode_Ota +} ble_func_mux_app_mode_t; + +#endif diff --git a/src/platform/nxp/k32w/k32w1/gatt_db.h b/src/platform/nxp/k32w/k32w1/gatt_db.h new file mode 100644 index 00000000000000..604fcfb61a7ba1 --- /dev/null +++ b/src/platform/nxp/k32w/k32w1/gatt_db.h @@ -0,0 +1,30 @@ +PRIMARY_SERVICE(service_gatt, gBleSig_GenericAttributeProfile_d) +CHARACTERISTIC(char_service_changed, gBleSig_GattServiceChanged_d, (gGattCharPropRead_c | gGattCharPropNotify_c)) +VALUE(value_service_changed, gBleSig_GattServiceChanged_d, (gPermissionNone_c), 4, 0x00, 0x00, 0x00, 0x00) +CCCD(cccd_service_changed) + +PRIMARY_SERVICE(service_gap, gBleSig_GenericAccessProfile_d) +CHARACTERISTIC(char_device_name, gBleSig_GapDeviceName_d, (gGattCharPropRead_c)) +VALUE(value_device_name, gBleSig_GapDeviceName_d, (gPermissionFlagReadable_c), 16, "NXP_ELOCK_DEMO") +CHARACTERISTIC(char_appearance, gBleSig_GapAppearance_d, (gGattCharPropRead_c)) +VALUE(value_appearance, gBleSig_GapAppearance_d, (gPermissionFlagReadable_c), 2, 0x00, 0x00) + +PRIMARY_SERVICE(service_chipoble, gChipoBleService_d) +CHARACTERISTIC_UUID128(chipoble_rx, uuid_chipoble_rx, (gGattCharPropWrite_c)) +VALUE_UUID128_VARLEN(value_chipoble_rx, uuid_chipoble_rx, (gPermissionFlagWritable_c), gAttMaxMtu_c - 3, gAttMaxMtu_c - 3, 0x00) +CHARACTERISTIC_UUID128(chipoble_tx, uuid_chipoble_tx, (gGattCharPropIndicate_c | gGattCharPropRead_c)) +VALUE_UUID128_VARLEN(value_chipoble_tx, uuid_chipoble_tx, (gPermissionFlagReadable_c), gAttMaxMtu_c - 3, gAttMaxMtu_c - 3, 0x00) +CCCD(cccd_chipoble_tx) +CHARACTERISTIC_UUID128(chipoble_c3, uuid_chipoble_c3, (gGattCharPropRead_c)) +VALUE_UUID128_VARLEN(value_chipoble_c3, uuid_chipoble_c3, (gPermissionFlagReadable_c), gAttMaxReadDataSize_d(gAttMaxValueLength_c), + gAttMaxReadDataSize_d(gAttMaxValueLength_c), 0x00) + +PRIMARY_SERVICE(service_device_info, gBleSig_DeviceInformationService_d) +CHARACTERISTIC(char_model_no, gBleSig_ModelNumberString_d, (gGattCharPropRead_c)) +VALUE(value_model_no, gBleSig_ModelNumberString_d, (gPermissionFlagReadable_c), 15, "Chip ELock Demo") +CHARACTERISTIC(char_serial_no, gBleSig_SerialNumberString_d, (gGattCharPropRead_c)) +VALUE(value_serial_no, gBleSig_SerialNumberString_d, (gPermissionFlagReadable_c), 7, "BLESN01") +CHARACTERISTIC(char_fw_rev, gBleSig_FirmwareRevisionString_d, (gGattCharPropRead_c)) +VALUE(value_fw_rev, gBleSig_FirmwareRevisionString_d, (gPermissionFlagReadable_c), 5, "1.1.1") +CHARACTERISTIC(char_sw_rev, gBleSig_SoftwareRevisionString_d, (gGattCharPropRead_c)) +VALUE(value_sw_rev, gBleSig_SoftwareRevisionString_d, (gPermissionFlagReadable_c), 5, "1.1.4") diff --git a/src/platform/nxp/k32w/k32w1/gatt_uuid128.h b/src/platform/nxp/k32w/k32w1/gatt_uuid128.h new file mode 100644 index 00000000000000..938968b1943ce2 --- /dev/null +++ b/src/platform/nxp/k32w/k32w1/gatt_uuid128.h @@ -0,0 +1,26 @@ +/* +* Declare all custom 128-bit UUIDs here using the format: +* +* UUID128(name, bytes) +* +* where: +* -name : an unique tag for the newly defined UUID; + will be used to reference this UUID when defining + services and characteristics in <> +* -bytes: 16 bytes representing the 128-bit value +* +* One definition per line. No semicolon required after each definition. +* +* example: +* UUID128(uuid_service_robot_characteristics, 0x12, 0x34, 0x56, 0x78, 0x90, 0xAB, 0xCD, 0xEF, 0x12, 0x34, 0x56, 0x78, 0x90, 0xAB, +0xCD, 0xEF) +* UUID128(uuid_char_robot_direction, 0x12, 0x34, 0x50, 0x00, 0x90, 0xAB, 0xCD, 0xEF, 0x12, 0x34, 0x56, 0x78, 0x90, 0xAB, 0xCD, +0xEF) +*/ +/* Services */ + +#define gChipoBleService_d 0xFFF6 + +UUID128(uuid_chipoble_tx, 0x12, 0x9D, 0x9F, 0x42, 0x9C, 0x4F, 0x9F, 0x95, 0x59, 0x45, 0x3D, 0x26, 0xF5, 0x2E, 0xEE, 0x18) +UUID128(uuid_chipoble_rx, 0x11, 0x9D, 0x9F, 0x42, 0x9C, 0x4F, 0x9F, 0x95, 0x59, 0x45, 0x3D, 0x26, 0xF5, 0x2E, 0xEE, 0x18) +UUID128(uuid_chipoble_c3, 0x04, 0x8f, 0x21, 0x83, 0x8a, 0x74, 0x7d, 0xb8, 0xf2, 0x45, 0x72, 0x87, 0x38, 0x02, 0x63, 0x64) diff --git a/src/platform/nxp/k32w/k32w1/k32w1-chip-mbedtls-config.h b/src/platform/nxp/k32w/k32w1/k32w1-chip-mbedtls-config.h new file mode 100644 index 00000000000000..f3d9949f730df0 --- /dev/null +++ b/src/platform/nxp/k32w/k32w1/k32w1-chip-mbedtls-config.h @@ -0,0 +1,131 @@ +/* + * + * Copyright (c) 2021 Project CHIP Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// Spans multiple lines to avoid being processed by unifdef +#ifndef MBEDTLS_CONFIG_H +#define MBEDTLS_CONFIG_H + +#include "openthread-core-config.h" + +#include +#include + +#include +#include +#include + +#undef MBEDTLS_NO_DEFAULT_ENTROPY_SOURCES +#define MBEDTLS_PLATFORM_SNPRINTF_MACRO snprintf + +#define MBEDTLS_AES_C +#define MBEDTLS_AES_ROM_TABLES +#define MBEDTLS_ASN1_PARSE_C +#define MBEDTLS_ASN1_WRITE_C +#define MBEDTLS_BIGNUM_C +#define MBEDTLS_CCM_C +#define MBEDTLS_CIPHER_C +#define MBEDTLS_CMAC_C +#define MBEDTLS_CTR_DRBG_C +#define MBEDTLS_ECJPAKE_C +#define MBEDTLS_ECP_C +#define MBEDTLS_ECP_DP_SECP256R1_ENABLED +#define MBEDTLS_ECP_NIST_OPTIM +#define MBEDTLS_ENTROPY_C +#define MBEDTLS_HAVE_ASM +#define MBEDTLS_HMAC_DRBG_C +#define MBEDTLS_KEY_EXCHANGE_ECJPAKE_ENABLED +#define MBEDTLS_MD_C +#define MBEDTLS_NO_PLATFORM_ENTROPY +#define MBEDTLS_PK_C +#define MBEDTLS_PK_PARSE_C +#define MBEDTLS_PLATFORM_C +#define MBEDTLS_PLATFORM_MEMORY +#define MBEDTLS_PLATFORM_NO_STD_FUNCTIONS +#define MBEDTLS_SHA224_C +#define MBEDTLS_SHA256_C +#define MBEDTLS_SHA256_SMALLER +#define MBEDTLS_SSL_CLI_C +#define MBEDTLS_SSL_DTLS_ANTI_REPLAY +#define MBEDTLS_SSL_DTLS_HELLO_VERIFY +#define MBEDTLS_SSL_EXPORT_KEYS +#define MBEDTLS_SSL_MAX_FRAGMENT_LENGTH +#define MBEDTLS_SSL_PROTO_TLS1_2 +#define MBEDTLS_SSL_PROTO_DTLS +#define MBEDTLS_SSL_TLS_C + +#if OPENTHREAD_CONFIG_BORDER_AGENT_ENABLE || OPENTHREAD_CONFIG_COMMISSIONER_ENABLE || OPENTHREAD_CONFIG_COAP_SECURE_API_ENABLE +#define MBEDTLS_SSL_COOKIE_C +#define MBEDTLS_SSL_SRV_C +#endif + +#if OPENTHREAD_CONFIG_COAP_SECURE_API_ENABLE +#define MBEDTLS_KEY_EXCHANGE_PSK_ENABLED +#define MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED +#endif + +#ifdef MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED +#define MBEDTLS_BASE64_C +#define MBEDTLS_ECDH_C +#define MBEDTLS_ECDSA_C +#define MBEDTLS_OID_C +#define MBEDTLS_PEM_PARSE_C +#define MBEDTLS_X509_USE_C +#define MBEDTLS_X509_CRT_PARSE_C +#endif + +#if OPENTHREAD_CONFIG_ECDSA_ENABLE +#define MBEDTLS_BASE64_C +#define MBEDTLS_ECDH_C +#define MBEDTLS_ECDSA_C +#define MBEDTLS_ECDSA_DETERMINISTIC +#define MBEDTLS_OID_C +#define MBEDTLS_PEM_PARSE_C +#define MBEDTLS_PK_WRITE_C +#endif + +#define MBEDTLS_MPI_WINDOW_SIZE 1 /**< Maximum windows size used. */ +#define MBEDTLS_MPI_MAX_SIZE 32 /**< Maximum number of bytes for usable MPIs. */ +#define MBEDTLS_ECP_MAX_BITS 256 /**< Maximum bit size of groups */ +#define MBEDTLS_ECP_WINDOW_SIZE 2 /**< Maximum window size used */ +#define MBEDTLS_ECP_FIXED_POINT_OPTIM 0 /**< Enable fixed-point speed-up */ +#define MBEDTLS_ENTROPY_MAX_SOURCES 2 /**< Maximum number of sources supported */ + +#if OPENTHREAD_CONFIG_HEAP_EXTERNAL_ENABLE +#define MBEDTLS_PLATFORM_STD_CALLOC otPlatCAlloc /**< Default allocator to use, can be undefined */ +#define MBEDTLS_PLATFORM_STD_FREE otPlatFree /**< Default free to use, can be undefined */ +#else +#define MBEDTLS_MEMORY_BUFFER_ALLOC_C +#endif + +#if OPENTHREAD_CONFIG_COAP_SECURE_API_ENABLE +#define MBEDTLS_SSL_MAX_CONTENT_LEN 900 /**< Maxium fragment length in bytes */ +#else +#define MBEDTLS_SSL_MAX_CONTENT_LEN 768 /**< Maxium fragment length in bytes */ +#endif + +#define MBEDTLS_SSL_IN_CONTENT_LEN MBEDTLS_SSL_MAX_CONTENT_LEN +#define MBEDTLS_SSL_OUT_CONTENT_LEN MBEDTLS_SSL_MAX_CONTENT_LEN +#define MBEDTLS_SSL_CIPHERSUITES MBEDTLS_TLS_ECJPAKE_WITH_AES_128_CCM_8 + +// Spans multiple lines to avoid being processed by unifdef +#if defined(MBEDTLS_USER_CONFIG_FILE) +#include MBEDTLS_USER_CONFIG_FILE +#endif + +#include "mbedtls/check_config.h" + +#endif /* MBEDTLS_CONFIG_H */ diff --git a/src/platform/nxp/k32w/k32w1/ram_storage.c b/src/platform/nxp/k32w/k32w1/ram_storage.c new file mode 100644 index 00000000000000..189d8be9f8db1e --- /dev/null +++ b/src/platform/nxp/k32w/k32w1/ram_storage.c @@ -0,0 +1,225 @@ +/* + * Copyright (c) 2022, The OpenThread Authors. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file + * RAM buffer structure used for keeping NVM records. + */ +#include +#include +#include + +#include "FunctionLib.h" +#include "ram_storage.h" + +#ifndef RAM_STORAGE_LOG +#define RAM_STORAGE_LOG 0 +#endif + +#if RAM_STORAGE_LOG +#include "fsl_debug_console.h" +#define RAM_STORAGE_PRINTF(...) \ + PRINTF("[%s] ", __FUNCTION__); \ + PRINTF(__VA_ARGS__); \ + PRINTF("\n\r"); +#else +#define RAM_STORAGE_PRINTF(...) +#endif + +rsError ramStorageAdd(ramBufferDescriptor * pBuffer, uint16_t aKey, const uint8_t * aValue, uint16_t aValueLength) +{ + rsError error = RS_ERROR_NONE; + struct settingsBlock currentBlock = { 0 }; + const uint16_t newBlockLength = sizeof(struct settingsBlock) + aValueLength; + + assert(pBuffer); + if (pBuffer->ramBufferLen + newBlockLength <= pBuffer->ramBufferMaxLen) + { + currentBlock.key = aKey; + currentBlock.length = aValueLength; + + memcpy(&pBuffer->pRamBuffer[pBuffer->ramBufferLen], ¤tBlock, sizeof(struct settingsBlock)); + memcpy(&pBuffer->pRamBuffer[pBuffer->ramBufferLen + sizeof(struct settingsBlock)], aValue, aValueLength); + pBuffer->ramBufferLen += newBlockLength; + + error = RS_ERROR_NONE; + } + else + { + error = RS_ERROR_NO_BUFS; + } + + RAM_STORAGE_PRINTF("key = %d lengthWriten = %d err = %d", aKey, aValueLength, error); + + return error; +} + +rsError ramStorageGet(const ramBufferDescriptor * pBuffer, uint16_t aKey, int aIndex, uint8_t * aValue, uint16_t * aValueLength) +{ + uint16_t i = 0; + uint16_t valueLength = 0; + uint16_t readLength = 0; + int currentIndex = 0; + struct settingsBlock currentBlock = { 0 }; + rsError error = RS_ERROR_NOT_FOUND; + + assert(pBuffer); + while (i < pBuffer->ramBufferLen) + { + memcpy(¤tBlock, &pBuffer->pRamBuffer[i], sizeof(struct settingsBlock)); + + if (aKey == currentBlock.key) + { + if (currentIndex == aIndex) + { + readLength = currentBlock.length; + + // Perform read only if an input buffer was passed in + if (aValue != NULL && aValueLength != NULL) + { + // Adjust read length if input buffer size is smaller + if (readLength > *aValueLength) + { + readLength = *aValueLength; + } + + memcpy(aValue, &pBuffer->pRamBuffer[i + sizeof(struct settingsBlock)], readLength); + } + + valueLength = currentBlock.length; + error = RS_ERROR_NONE; + break; + } + + currentIndex++; + } + + i += sizeof(struct settingsBlock) + currentBlock.length; + } + + if (aValueLength != NULL) + { + *aValueLength = valueLength; + } + + RAM_STORAGE_PRINTF("key = %d err = %d", aKey, error); + + return error; +} + +rsError ramStorageSet(ramBufferDescriptor * pBuffer, uint16_t aKey, const uint8_t * aValue, uint16_t aValueLength) +{ + uint16_t i = 0; + uint16_t currentBlockLength = 0; + struct settingsBlock currentBlock = { 0 }; + bool_t alreadyExists = FALSE; + rsError error = RS_ERROR_NONE; + + while (i < pBuffer->ramBufferLen) + { + memcpy(¤tBlock, &pBuffer->pRamBuffer[i], sizeof(struct settingsBlock)); + currentBlockLength = sizeof(struct settingsBlock) + currentBlock.length; + + if (aKey == currentBlock.key) + { + /* unlikely: the updated value has a different length */ + if (currentBlock.length != aValueLength) + { + ramStorageDelete(pBuffer, aKey, -1); + break; + } + + memcpy(&pBuffer->pRamBuffer[i + sizeof(struct settingsBlock)], aValue, aValueLength); + alreadyExists = TRUE; + break; + } + else + { + i += currentBlockLength; + } + } + + if (!alreadyExists) + { + error = ramStorageAdd(pBuffer, aKey, aValue, aValueLength); + } + + return error; +} + +rsError ramStorageDelete(ramBufferDescriptor * pBuffer, uint16_t aKey, int aIndex) +{ + uint16_t i = 0; + int currentIndex = 0; + uint16_t nextBlockStart = 0; + uint16_t currentBlockLength = 0; + struct settingsBlock currentBlock = { 0 }; + rsError error = RS_ERROR_NOT_FOUND; + bool_t found = FALSE; + + assert(pBuffer); + while (i < pBuffer->ramBufferLen) + { + memcpy(¤tBlock, &pBuffer->pRamBuffer[i], sizeof(struct settingsBlock)); + currentBlockLength = sizeof(struct settingsBlock) + currentBlock.length; + + if (aKey == currentBlock.key) + { + if ((currentIndex == aIndex) || (aIndex == -1)) + { + currentIndex++; + + nextBlockStart = i + currentBlockLength; + + if (nextBlockStart < pBuffer->ramBufferLen) + { + memmove(&pBuffer->pRamBuffer[i], &pBuffer->pRamBuffer[nextBlockStart], pBuffer->ramBufferLen - nextBlockStart); + } + + assert(pBuffer->ramBufferLen >= currentBlockLength); + pBuffer->ramBufferLen -= currentBlockLength; + found = TRUE; + + error = RS_ERROR_NONE; + } + + if (found && (aIndex != -1)) + { + break; + } + } + + if (!found) + { + i += currentBlockLength; + } + found = FALSE; + } + RAM_STORAGE_PRINTF("key = %d err = %d", aKey, error); + return error; +} diff --git a/src/platform/nxp/k32w/k32w1/ram_storage.h b/src/platform/nxp/k32w/k32w1/ram_storage.h new file mode 100644 index 00000000000000..77cdca10b4f7f1 --- /dev/null +++ b/src/platform/nxp/k32w/k32w1/ram_storage.h @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2022, The OpenThread Authors. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef RAM_STORAGE_K32W_H_ +#define RAM_STORAGE_K32W_H_ + +#include + +typedef enum +{ + RS_ERROR_NONE, + RS_ERROR_NOT_FOUND, + RS_ERROR_NO_BUFS +} rsError; + +/* the structure used for keeping the records has the same structure both in RAM and in NVM: + * ramBufferLen | ramBufferMaxLen | settingsBlock+Data | .... | settingsBlock+Data + * + * ramBufferLen shows how much of the RAM buffer is currently occupied with settingsBlock structures + * ramBufferMaxLen shows the total malloc'ed size. Dynamic re-allocation is possible + */ +typedef struct +{ + uint16_t ramBufferLen; + uint16_t ramBufferMaxLen; + uint8_t pRamBuffer[1]; +} ramBufferDescriptor; + +struct settingsBlock +{ + uint16_t key; + uint16_t length; +} __attribute__((packed)); + +#if defined(PDM_USE_DYNAMIC_MEMORY) && PDM_USE_DYNAMIC_MEMORY && defined(OPENTHREAD_CONFIG_HEAP_EXTERNAL_ENABLE) && \ + OPENTHREAD_CONFIG_HEAP_EXTERNAL_ENABLE +#define ENABLE_STORAGE_DYNAMIC_MEMORY 1 +#else +#define ENABLE_STORAGE_DYNAMIC_MEMORY 0 +#endif + +/* increment size for dynamic memory re-allocation in case the + * initial RAM buffer size gets insufficient + */ +#define kRamBufferReallocSize 512 +#define kRamBufferMaxAllocSize 10240 + +#define kRamDescHeaderSize offsetof(ramBufferDescriptor, pRamBuffer) + +#ifdef __cplusplus +extern "C" { +#endif + +/* search RAM Buffer for aKey and return its value in aValue. aValueLength will contain the length of aValueLength */ +rsError ramStorageGet(const ramBufferDescriptor * pBuffer, uint16_t aKey, int aIndex, uint8_t * aValue, uint16_t * aValueLength); + +/* search RAM buffer for aKey and set its value to aValue (having aValueLength length) + * - aValue and aValueLength can be NULL - the function checks only for the existence of aKey + * - if only aValue is NULL and aKey exists in the RAM buffer - the function will return its value in aValueLength + */ +rsError ramStorageSet(ramBufferDescriptor * pBuffer, uint16_t aKey, const uint8_t * aValue, uint16_t aValueLength); + +/* adds a settingsBlock (aKey:aValue) to the end of the RAM Buffer: + * - doesn't check if aKey already exists in the RAM Buffer + * - aValueLength can be 0 + */ +rsError ramStorageAdd(ramBufferDescriptor * pBuffer, uint16_t aKey, const uint8_t * aValue, uint16_t aValueLength); + +/* search RAM Buffer for aKey (with aIndex) and delete it: + * - if aIndex is -1 then all the occurrences of aKey are deleted + */ +rsError ramStorageDelete(ramBufferDescriptor * pBuffer, uint16_t aKey, int aIndex); + +#ifdef __cplusplus +} +#endif + +#endif /* RAM_STORAGE_K32W_H_ */ diff --git a/src/system/BUILD.gn b/src/system/BUILD.gn index 8882854b675f42..6c036a73d920b6 100644 --- a/src/system/BUILD.gn +++ b/src/system/BUILD.gn @@ -56,6 +56,8 @@ if (chip_device_platform == "cc13x2_26x2") { import("${qpg_sdk_build_root}/qpg_sdk.gni") } else if (chip_device_platform == "k32w0") { import("//build_overrides/k32w0_sdk.gni") +} else if (chip_device_platform == "k32w1") { + import("//build_overrides/k32w1_sdk.gni") } else if (chip_device_platform == "psoc6") { import("//build_overrides/psoc6.gni") } else if (chip_device_platform == "cyw30739") { @@ -176,6 +178,9 @@ source_set("system_config_header") { if (chip_device_platform == "k32w0") { public_deps += [ "${k32w0_sdk_build_root}:k32w0_sdk" ] } + if (chip_device_platform == "k32w1") { + public_deps += [ "${k32w1_sdk_build_root}:k32w1_sdk" ] + } if (chip_device_platform == "cyw30739") { public_deps += [ "${cyw30739_sdk_build_root}:cyw30739_sdk" ] } diff --git a/third_party/nxp/k32w1_sdk/BUILD.gn b/third_party/nxp/k32w1_sdk/BUILD.gn new file mode 100644 index 00000000000000..8173cdd0df7863 --- /dev/null +++ b/third_party/nxp/k32w1_sdk/BUILD.gn @@ -0,0 +1,81 @@ +# Copyright (c) 2020 Project CHIP Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import("//build_overrides/k32w1_sdk.gni") +import("//build_overrides/mbedtls.gni") + +import("${k32w1_sdk_build_root}/k32w1_sdk.gni") +import("${mbedtls_root}/mbedtls.gni") + +declare_args() { + # Build target to use for k32w1 SDK. Use this to set global SDK defines. + k32w1_sdk_target = "" +} + +assert(k32w1_sdk_target != "", "k32w1_sdk_target must be specified") + +group("k32w1_sdk") { + public_deps = [ k32w1_sdk_target ] +} + +config("mbedtls_k32w1_config") { + defines = [ + "MBEDTLS_CONFIG_FILE=", + "MBEDTLS_USER_CONFIG_FILE=", + + # These options should really be in the config.h... + "MBEDTLS_FREESCALE_FREERTOS_CALLOC_ALT=1", + "MBEDTLS_THREADING_C=1", + "MBEDTLS_THREADING_ALT=1", + "MBEDTLS_X509_CSR_WRITE_C", + "MBEDTLS_X509_CREATE_C", + "MBEDTLS_PEM_WRITE_C", + "MBEDTLS_HKDF_C", + "MBEDTLS_ERROR_C", + "MBEDTLS_PKCS5_C", + + "MBEDTLS_ECP_FIXED_POINT_OPTIM=0", # To reduce peak memory usage + "MBEDTLS_MPI_WINDOW_SIZE=1", + "MBEDTLS_ECP_WINDOW_SIZE=2", + "MBEDTLS_MPI_MAX_SIZE=32", # Maximum number of bytes for usable MPIs. + "MBEDTLS_ECP_MAX_BITS=256", # Maximum bit size of groups */ + "MBEDTLS_ENTROPY_MAX_SOURCES=1", # Maximum number of sources supported + + "MBEDTLS_ENTROPY_HARDWARE_ALT", + ] + + if (chip_mdns == "none") { + defines += [ + "MBEDTLS_PK_WRITE_C", + "MBEDTLS_OID_C", + "MBEDTLS_BASE64_C", + ] + } + + include_dirs = [ chip_root ] +} + +mbedtls_target("mbedtls") { + sources = [ + "${k32w1_sdk_root}/middleware/mbedtls/port/sssapi/ccm_alt.c", + "${k32w1_sdk_root}/middleware/mbedtls/port/sssapi/entropy_poll_alt.c", + ] + + public_configs = [ ":mbedtls_k32w1_config" ] + + public_deps = [ + ":k32w1_sdk", + "${chip_root}/third_party/openthread/platforms/nxp/k32w/k32w1:openthread_mbedtls_config_k32w1", + ] +} diff --git a/third_party/nxp/k32w1_sdk/Jlink_Script/K32W1.jlink b/third_party/nxp/k32w1_sdk/Jlink_Script/K32W1.jlink new file mode 100644 index 00000000000000..9d9e4264c1b7d9 --- /dev/null +++ b/third_party/nxp/k32w1_sdk/Jlink_Script/K32W1.jlink @@ -0,0 +1,6 @@ +r +h +loadfile chip-k32w1-light-example.srec +r +g +q \ No newline at end of file diff --git a/third_party/nxp/k32w1_sdk/k32w1_executable.gni b/third_party/nxp/k32w1_sdk/k32w1_executable.gni new file mode 100644 index 00000000000000..bc78dfc13bf95c --- /dev/null +++ b/third_party/nxp/k32w1_sdk/k32w1_executable.gni @@ -0,0 +1,34 @@ +# Copyright (c) 2020 Project CHIP Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import("//build_overrides/build.gni") +import("//build_overrides/chip.gni") + +import("${build_root}/toolchain/flashable_executable.gni") + +template("k32w1_executable") { + output_base_name = get_path_info(invoker.output_name, "name") + objcopy_image_name = output_base_name + ".srec" + objcopy_image_format = "srec" + objcopy = "arm-none-eabi-objcopy" + + # Copy flashing dependencies to the output directory so that the output + # is collectively self-contained; this allows flashing to work reliably + # even if the build and flashing steps take place on different machines + # or in different containers. + + flashable_executable(target_name) { + forward_variables_from(invoker, "*") + } +} diff --git a/third_party/nxp/k32w1_sdk/k32w1_sdk.gni b/third_party/nxp/k32w1_sdk/k32w1_sdk.gni new file mode 100644 index 00000000000000..14f355b9da068c --- /dev/null +++ b/third_party/nxp/k32w1_sdk/k32w1_sdk.gni @@ -0,0 +1,495 @@ +# Copyright (c) 2020 Project CHIP Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import("//build_overrides/build.gni") +import("//build_overrides/chip.gni") +import("//build_overrides/k32w1_sdk.gni") +import("//build_overrides/mbedtls.gni") +import("//build_overrides/openthread.gni") + +import("${build_root}/config/compiler/compiler.gni") +import("${chip_root}/src/crypto/crypto.gni") +import("${chip_root}/src/lib/core/core.gni") +import("${chip_root}/src/platform/device.gni") +import("${chip_root}/src/platform/nxp/k32w/k32w1/args.gni") + +declare_args() { + # Location of the k32w1 SDK. + k32w1_sdk_root = getenv("NXP_K32W1_SDK_ROOT") + use_smu2_as_system_memory = false +} + +openthread_nxp_root = "${chip_root}/third_party/openthread/ot-nxp" + +assert(k32w1_sdk_root != "", "k32w1_sdk_root must be specified") + +assert(!(use_smu2_as_system_memory && !chip_openthread_ftd), + "SMU2 can be used as system memory only with OT-FTD suppport") + +# Defines an k32w1 SDK build target. +# +# Parameters: +# k32w1_sdk_root - The location of the k32w1 SDK. +# sources - Extra source files to build. +template("k32w1_sdk") { + if (defined(invoker.k32w1_sdk_root)) { + k32w1_sdk_root = invoker.k32w1_sdk_root + } + + assert(chip_with_low_power == 0 || + (chip_with_low_power == 1 && chip_with_ot_cli == 0), + "Please disable low power if openthread CLI is needed!") + + sdk_target_name = target_name + + config("${sdk_target_name}_config") { + include_dirs = [] + if (defined(invoker.include_dirs)) { + include_dirs += invoker.include_dirs + } + + # We want to treat SDK headers as system headers, so that warnings in those + # headers are not fatal. Therefore don't add them directly to include_dirs; + # we will add them to cflags below instead. + _sdk_include_dirs = [ + "${k32w1_sdk_root}/devices/K32W1480", + "${k32w1_sdk_root}/CMSIS/Core/Include", + "${k32w1_sdk_root}/platform/drivers/snt", + "${k32w1_sdk_root}/platform/drivers/spc", + "${k32w1_sdk_root}/platform/drivers/ccm32k", + "${k32w1_sdk_root}/platform/drivers/wuu", + "${k32w1_sdk_root}/platform/drivers/cmc", + "${k32w1_sdk_root}/platform/drivers/lpspi", + "${k32w1_sdk_root}/components/osa", + "${k32w1_sdk_root}/components/lists", + "${k32w1_sdk_root}/components/messaging", + "${k32w1_sdk_root}/components/mem_manager", + "${k32w1_sdk_root}/components/panic", + "${k32w1_sdk_root}/components/serial_manager", + "${k32w1_sdk_root}/components/uart", + "${k32w1_sdk_root}/components/gpio", + "${k32w1_sdk_root}/components/led", + "${k32w1_sdk_root}/components/button", + "${k32w1_sdk_root}/components/timer_manager", + "${k32w1_sdk_root}/components/time_stamp", + "${k32w1_sdk_root}/components/timer", + "${k32w1_sdk_root}/components/rpmsg", + "${k32w1_sdk_root}/components/internal_flash", + "${k32w1_sdk_root}/components/reset", + "${k32w1_sdk_root}/components/flash/nor/lpspi", + "${k32w1_sdk_root}/components/flash/nor", + "${k32w1_sdk_root}/components/power_manager/boards", + "${k32w1_sdk_root}/components/power_manager/boards/K32W148-EVK", + "${k32w1_sdk_root}/components/power_manager/core", + + "${k32w1_sdk_root}/middleware/wireless/framework/DBG", + "${k32w1_sdk_root}/middleware/wireless/framework/Common", + "${k32w1_sdk_root}/middleware/wireless/framework/FunctionLib", + "${k32w1_sdk_root}/middleware/wireless/framework/HWParameter", + "${k32w1_sdk_root}/middleware/wireless/framework/SecLib", + "${k32w1_sdk_root}/middleware/wireless/framework/RNG", + "${k32w1_sdk_root}/middleware/wireless/framework/Sensors", + "${k32w1_sdk_root}/middleware/wireless/framework/LowPower", + "${k32w1_sdk_root}/middleware/wireless/framework/NVM/Source", + "${k32w1_sdk_root}/middleware/wireless/framework/NVM/Interface", + "${k32w1_sdk_root}/middleware/wireless/framework/ModuleInfo", + "${k32w1_sdk_root}/middleware/wireless/framework/OtaSupport/Interface", + "${k32w1_sdk_root}/middleware/wireless/framework/OtaSupport/Source", + "${k32w1_sdk_root}/middleware/wireless/framework/boards/kw45_k32w1", + "${k32w1_sdk_root}/middleware/wireless/framework/boards/kw45_k32w1/K32W1480", + "${k32w1_sdk_root}/middleware/wireless/framework/platform/kw45_k32w1", + "${k32w1_sdk_root}/middleware/wireless/framework/platform/include", + "${k32w1_sdk_root}/middleware/wireless/framework/platform/kw45_k32w1/configs", + "${openthread_nxp_root}/third_party/k32w1_sdk", + + "${k32w1_sdk_root}/middleware/multicore/rpmsg_lite/lib/include", + "${k32w1_sdk_root}/middleware/multicore/rpmsg_lite/lib/include/platform/k32w1", + "${k32w1_sdk_root}/middleware/multicore/mcmgr/src", + + "${k32w1_sdk_root}/middleware/wireless/ble_controller/interface", + "${k32w1_sdk_root}/middleware/wireless/bluetooth/hci_transport/interface", + "${k32w1_sdk_root}/middleware/wireless/bluetooth/port", + "${k32w1_sdk_root}/middleware/wireless/bluetooth/application/common", + "${k32w1_sdk_root}/middleware/wireless/bluetooth/application/common/gatt_db", + "${k32w1_sdk_root}/middleware/wireless/bluetooth/application/common/gatt_db/macros", + "${k32w1_sdk_root}/middleware/wireless/bluetooth/host/config", + "${k32w1_sdk_root}/middleware/wireless/bluetooth/host/interface", + + "${k32w1_sdk_root}/middleware/mbedtls/port/sssapi", + "${k32w1_sdk_root}/middleware/mbedtls/port/ksdk", + "${k32w1_sdk_root}/middleware/mbedtls/include/mbedtls", + "${k32w1_sdk_root}/middleware/mbedtls/include", + "${k32w1_sdk_root}/middleware/secure-subsystem/inc", + "${k32w1_sdk_root}/middleware/secure-subsystem/inc/snt", + "${k32w1_sdk_root}/middleware/secure-subsystem/port/kw45_k4w1", + + "${k32w1_sdk_root}/middleware/wireless/XCVR/drv", + "${k32w1_sdk_root}/middleware/wireless/XCVR/drv/nb2p4ghz", + "${k32w1_sdk_root}/middleware/wireless/XCVR/drv/nb2p4ghz/configs/gen45", + "${k32w1_sdk_root}/middleware/wireless/ieee-802.15.4/ieee_802_15_4/phy/interface", + "${k32w1_sdk_root}/middleware/wireless/ieee-802.15.4/utils", + + "${k32w1_sdk_root}/rtos/freertos/freertos_kernel/include", + "${k32w1_sdk_root}/rtos/freertos/libraries/3rdparty/mbedtls_config", + "${k32w1_sdk_root}/rtos/freertos/freertos_kernel/portable/GCC/ARM_CM33_NTZ/non_secure", + + "${chip_root}/src/platform/nxp/k32w/k32w1", + ] + + if (sdk_release == 1) { + _sdk_include_dirs += [ + "${k32w1_sdk_root}/middleware/wireless/bluetooth/application/common/matter", + "${k32w1_sdk_root}/devices/K32W1480/drivers", + "${k32w1_sdk_root}/devices/K32W1480/utilities", + "${k32w1_sdk_root}/devices/K32W1480/utilities/debug_console", + "${k32w1_sdk_root}/devices/K32W1480/utilities/str", + "${k32w1_sdk_root}/devices/K32W1480/utilities/format", + ] + } else { + _sdk_include_dirs += [ + "${k32w1_sdk_root}/devices/KW45B41Z83/drivers", + "${k32w1_sdk_root}/devices/KW45B41Z83/drivers/romapi", + "${k32w1_sdk_root}/platform/drivers/common", + "${k32w1_sdk_root}/platform/drivers/flash_k4", + "${k32w1_sdk_root}/platform/drivers/gpio", + "${k32w1_sdk_root}/platform/drivers/lpuart", + "${k32w1_sdk_root}/platform/drivers/ltc", + "${k32w1_sdk_root}/platform/drivers/port", + "${k32w1_sdk_root}/platform/drivers/lptmr", + "${k32w1_sdk_root}/platform/drivers/ccm32k", + "${k32w1_sdk_root}/platform/drivers/imu", + "${k32w1_sdk_root}/platform/drivers/crc", + "${k32w1_sdk_root}/platform/utilities/misc_utilities", + "${k32w1_sdk_root}/platform/utilities/debug_console", + "${k32w1_sdk_root}/platform/utilities/str", + ] + } + + libs = [ + "${k32w1_sdk_root}/middleware/wireless/bluetooth/host/lib/lib_ble_host_matter_cm33_gcc.a", + "${k32w1_sdk_root}/middleware/wireless/framework/SecLib/lib_crypto_m33.a", + ] + + defines = [ + "gMainThreadPriority_c=5", + "CPU_K32W1480VFTA", + "__STARTUP_CLEAR_BSS", + "SERIAL_MANAGER_NON_BLOCKING_MODE=1", + "SERIAL_USE_CONFIGURE_STRUCTURE=1", + "SDK_COMPONENT_INTEGRATION=1", + "SERIAL_PORT_TYPE_UART=1", + "gSerialManagerMaxInterfaces_c=1", + "SDK_OS_FREE_RTOS", + "gAppHighSystemClockFrequency_d=1", + + "USE_NBU=1", + "KW45_A0_SUPPORT=0", + "HAL_RPMSG_SELECT_ROLE=0", + "TM_ENABLE_TIME_STAMP=1", + "FSL_OSA_TASK_ENABLE=1", + "FSL_OSA_MAIN_FUNC_ENABLE=1", + "gAspCapability_d=1", + "gNvStorageIncluded_d=1", + "gUnmirroredFeatureSet_d=1", + "gNvFragmentation_Enabled_d=1", + "gAppButtonCnt_c=2", + "gAppLowpowerEnabled_d=1", + "BUTTON_SHORT_PRESS_THRESHOLD=1500", + "BUTTON_LONG_PRESS_THRESHOLD=2500", + "SSS_CONFIG_FILE=\"fsl_sss_config_snt.h\"", + "SSCP_CONFIG_FILE=\"fsl_sscp_config_snt.h\"", + + "SDK_DEBUGCONSOLE=1", + "NO_SYSCORECLK_UPD=0", + "USE_RTOS=1", + "USE_SDK_OSA=0", + "FSL_RTOS_FREE_RTOS=1", + "MinimalHeapSize_c=0x8C00", + "gMemManagerLightExtendHeapAreaUsage=0", + "DEBUG_SERIAL_INTERFACE_INSTANCE=0", + "APP_SERIAL_INTERFACE_INSTANCE=1", + + "configFRTOS_MEMORY_SCHEME=4", + "osCustomStartup=1", + + "ENABLE_RAM_VECTOR_TABLE=1", + + "CHIP_ENABLE_OPENTHREAD=1", + "gUseHciTransportDownward_d=1", + "gAppMaxConnections_c=1", + "gL2caMaxLeCbChannels_c=2", + "gGapSimultaneousEAChainedReports_c=0", + "gTmrStackTimers_c= 3 + gAppMaxConnections_c * 2 + gL2caMaxLeCbChannels_c + gGapSimultaneousEAChainedReports_c", + "gAppUseBonding_d=0", + "gAppUsePairing_d=0", + "gAppUsePrivacy_d=0", + "gGattUseUpdateDatabaseCopyProc_c=0", + "gBleBondIdentityHeaderSize_c=56", + "gPasskeyValue_c=999999", + "gMainThreadStackSize_c=3096", + "gHost_TaskStackSize_c=2400", + "gBleSetMacAddrFromVendorCommand_d=1", + "gLoggingActive_d=0", + "gLogRingPlacementOffset_c=0xF000", + "CHIP_PLAT_NVM_SUPPORT=1", + "mAdvertisingDefaultTxPower_c=0", # default advertising TX power + "mConnectionDefaultTxPower_c=0", # default connection TX power + "BLE_HIGH_TX_POWER=0", # when enabled overwrite default tx power with + # following values gAdvertisingPowerLeveldBm_c and + # gConnectPowerLeveldBm_c + "gAdvertisingPowerLeveldBm_c=0", + "gConnectPowerLeveldBm_c=0", + ] + + if (chip_with_low_power == 1 && chip_logging == true) { + print( + "WARNING: enabling logs in low power might break the LP timings. Use at your own risk!") + print("WARNING: set chip_logging=false to disable logging.") + } + + if (chip_mdns == "platform") { + defines += [ + "OPENTHREAD_CONFIG_SRP_CLIENT_ENABLE=1", + "OPENTHREAD_CONFIG_ECDSA_ENABLE=1", + "OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE=1", + "OPENTHREAD_CONFIG_DNS_CLIENT_ENABLE=1", + ] + } + + if (chip_with_ot_cli == 1) { + defines += [ "CHIP_DEVICE_CONFIG_THREAD_ENABLE_CLI=1" ] + } + + if (use_smu2_as_system_memory) { + defines += [ + "__STARTUP_CLEAR_SMU2", + "USE_SMU2_AS_SYSTEM_MEMORY", + ] + } + + if (chip_with_low_power == 1) { + defines += [ + "chip_with_low_power=1", + "cPWR_UsePowerDownMode=1", + "gAppLowpowerEnabled_d=1", + ] + + if (chip_logging == false) { + defines += [ + "K32W_LOG_ENABLED=0", + "gUartDebugConsole_d=0", + ] + } else { + defines += [ + "K32W_LOG_ENABLED=1", + "OSA_USED=1", + "gUartDebugConsole_d=1", + ] + } + } else { + defines += [ + "gAppLedCnt_c=2", + "K32W_LOG_ENABLED=1", + "gUartDebugConsole_d=1", + ] + } + + if (defined(invoker.defines)) { + defines += invoker.defines + } + + cflags = [ + "-Wno-unused-function", + "-Wno-conversion", + "-Wno-sign-compare", + "-Wno-clobbered", + "-Wno-implicit-fallthrough", + "-fno-optimize-strlen", + "-mthumb", + "-MMD", + "-MP", + ] + + cflags += [ + # TODO After upgrading the compiler we started to see new error from address + # warning. To allow PR that rolls up compiler we have suppress this warning + # as an error temporarily. + # see https://github.com/project-chip/connectedhomeip/issues/26221 + "-Wno-error=address", + ] + + # Now add our "system-header" include dirs + foreach(include_dir, _sdk_include_dirs) { + cflags += [ "-isystem" + rebase_path(include_dir, root_build_dir) ] + } + } + + # TODO - Break up this monolith and make it configurable. + source_set(sdk_target_name) { + forward_variables_from(invoker, "*") + + if (!defined(sources)) { + sources = [] + } + + if (sdk_release == 1) { + sources += [ + "${k32w1_sdk_root}/devices/K32W1480/drivers/fsl_ccm32k.c", + "${k32w1_sdk_root}/devices/K32W1480/drivers/fsl_clock.c", + "${k32w1_sdk_root}/devices/K32W1480/drivers/fsl_cmc.c", + "${k32w1_sdk_root}/devices/K32W1480/drivers/fsl_crc.c", + "${k32w1_sdk_root}/devices/K32W1480/drivers/fsl_gpio.c", + "${k32w1_sdk_root}/devices/K32W1480/drivers/fsl_imu.c", + "${k32w1_sdk_root}/devices/K32W1480/drivers/fsl_k4_controller.c", + "${k32w1_sdk_root}/devices/K32W1480/drivers/fsl_k4_flash.c", + "${k32w1_sdk_root}/devices/K32W1480/drivers/fsl_lpspi.c", + "${k32w1_sdk_root}/devices/K32W1480/drivers/fsl_lptmr.c", + "${k32w1_sdk_root}/devices/K32W1480/drivers/fsl_lpuart.c", + "${k32w1_sdk_root}/devices/K32W1480/drivers/fsl_ltc.c", + "${k32w1_sdk_root}/devices/K32W1480/drivers/fsl_snt.c", + "${k32w1_sdk_root}/devices/K32W1480/drivers/fsl_spc.c", + "${k32w1_sdk_root}/devices/K32W1480/drivers/fsl_wuu.c", + "${k32w1_sdk_root}/devices/K32W1480/utilities/debug_console/fsl_debug_console.c", + "${k32w1_sdk_root}/devices/K32W1480/utilities/fsl_assert.c", + "${k32w1_sdk_root}/devices/K32W1480/utilities/str/fsl_str.c", + "${k32w1_sdk_root}/middleware/wireless/bluetooth/application/common/matter/ble_init.c", + ] + } else { + sources += [ + "${k32w1_sdk_root}/devices/KW45B41Z83/drivers/fsl_clock.c", + "${k32w1_sdk_root}/middleware/wireless/bluetooth/application/common/ble_init.c", + "${k32w1_sdk_root}/platform/drivers/ccm32k/fsl_ccm32k.c", + "${k32w1_sdk_root}/platform/drivers/cmc/fsl_cmc.c", + "${k32w1_sdk_root}/platform/drivers/crc/fsl_crc.c", + "${k32w1_sdk_root}/platform/drivers/flash_k4/fsl_k4_controller.c", + "${k32w1_sdk_root}/platform/drivers/flash_k4/fsl_k4_flash.c", + "${k32w1_sdk_root}/platform/drivers/gpio/fsl_gpio.c", + "${k32w1_sdk_root}/platform/drivers/imu/fsl_imu.c", + "${k32w1_sdk_root}/platform/drivers/lpspi/fsl_lpspi.c", + "${k32w1_sdk_root}/platform/drivers/lptmr/fsl_lptmr.c", + "${k32w1_sdk_root}/platform/drivers/lpuart/fsl_lpuart.c", + "${k32w1_sdk_root}/platform/drivers/ltc/fsl_ltc.c", + "${k32w1_sdk_root}/platform/drivers/snt/fsl_snt.c", + "${k32w1_sdk_root}/platform/drivers/spc/fsl_spc.c", + "${k32w1_sdk_root}/platform/drivers/wuu/fsl_wuu.c", + "${k32w1_sdk_root}/platform/utilities/assert/fsl_assert.c", + "${k32w1_sdk_root}/platform/utilities/debug_console/fsl_debug_console.c", + "${k32w1_sdk_root}/platform/utilities/str/fsl_str.c", + ] + } + + sources += [ + "${k32w1_sdk_root}/components/button/fsl_component_button.c", + "${k32w1_sdk_root}/components/flash//nor/lpspi/fsl_lpspi_mem_adapter.c", + "${k32w1_sdk_root}/components/flash//nor/lpspi/fsl_lpspi_nor_flash.c", + "${k32w1_sdk_root}/components/gpio/fsl_adapter_gpio.c", + "${k32w1_sdk_root}/components/internal_flash/fsl_adapter_k4_flash.c", + "${k32w1_sdk_root}/components/led/fsl_component_led.c", + "${k32w1_sdk_root}/components/lists/fsl_component_generic_list.c", + "${k32w1_sdk_root}/components/mem_manager/fsl_component_mem_manager_light.c", + "${k32w1_sdk_root}/components/messaging/fsl_component_messaging.c", + "${k32w1_sdk_root}/components/osa/fsl_os_abstraction_free_rtos.c", + "${k32w1_sdk_root}/components/panic/fsl_component_panic.c", + "${k32w1_sdk_root}/components/power_manager/boards/K32W148-EVK/fsl_pm_board.c", + "${k32w1_sdk_root}/components/power_manager/core/fsl_pm_core.c", + "${k32w1_sdk_root}/components/reset/fsl_adapter_reset.c", + "${k32w1_sdk_root}/components/rpmsg/fsl_adapter_rpmsg.c", + "${k32w1_sdk_root}/components/serial_manager/fsl_component_serial_manager.c", + "${k32w1_sdk_root}/components/serial_manager/fsl_component_serial_port_uart.c", + "${k32w1_sdk_root}/components/time_stamp/fsl_adapter_lptmr_time_stamp.c", + "${k32w1_sdk_root}/components/timer/fsl_adapter_lptmr.c", + "${k32w1_sdk_root}/components/timer_manager/fsl_component_timer_manager.c", + "${k32w1_sdk_root}/components/uart/fsl_adapter_lpuart.c", + "${k32w1_sdk_root}/devices/K32W1480/gcc/startup_K32W1480.S", + "${k32w1_sdk_root}/devices/K32W1480/system_K32W1480.c", + "${k32w1_sdk_root}/middleware/multicore/mcmgr/src/mcmgr.c", + "${k32w1_sdk_root}/middleware/multicore/mcmgr/src/mcmgr_imu_internal.c", + "${k32w1_sdk_root}/middleware/multicore/mcmgr/src/mcmgr_internal_core_api_k32w1.c", + "${k32w1_sdk_root}/middleware/multicore/rpmsg_lite/lib/common/llist.c", + "${k32w1_sdk_root}/middleware/multicore/rpmsg_lite/lib/rpmsg_lite/porting/environment/rpmsg_env_freertos.c", + "${k32w1_sdk_root}/middleware/multicore/rpmsg_lite/lib/rpmsg_lite/porting/platform/k32w1/rpmsg_platform.c", + "${k32w1_sdk_root}/middleware/multicore/rpmsg_lite/lib/rpmsg_lite/rpmsg_lite.c", + "${k32w1_sdk_root}/middleware/multicore/rpmsg_lite/lib/virtio/virtqueue.c", + "${k32w1_sdk_root}/middleware/secure-subsystem/port/kw45_k4w1/sss_aes.c", + "${k32w1_sdk_root}/middleware/secure-subsystem/port/kw45_k4w1/sss_aes_cmac.c", + "${k32w1_sdk_root}/middleware/secure-subsystem/port/kw45_k4w1/sss_ecdh.c", + "${k32w1_sdk_root}/middleware/secure-subsystem/port/kw45_k4w1/sss_init.c", + "${k32w1_sdk_root}/middleware/secure-subsystem/port/kw45_k4w1/sss_sha256.c", + "${k32w1_sdk_root}/middleware/secure-subsystem/src/sscp/fsl_sscp_mu.c", + "${k32w1_sdk_root}/middleware/secure-subsystem/src/sscp/fsl_sss_mgmt.c", + "${k32w1_sdk_root}/middleware/secure-subsystem/src/sscp/fsl_sss_sscp.c", + "${k32w1_sdk_root}/middleware/wireless/ble_controller/src/controller_api.c", + "${k32w1_sdk_root}/middleware/wireless/bluetooth/application/common/ble_conn_manager.c", + "${k32w1_sdk_root}/middleware/wireless/bluetooth/application/common/gatt_db/gatt_database.c", + "${k32w1_sdk_root}/middleware/wireless/bluetooth/hci_transport/source/hcit_generic_adapter_interface.c", + "${k32w1_sdk_root}/middleware/wireless/bluetooth/host/config/ble_globals.c", + "${k32w1_sdk_root}/middleware/wireless/bluetooth/port/fwk_generic_list.c", + "${k32w1_sdk_root}/middleware/wireless/bluetooth/port/fwk_timer_manager.c", + "${k32w1_sdk_root}/middleware/wireless/framework/Common/rtos/freertos/heap_mem_manager.c", + "${k32w1_sdk_root}/middleware/wireless/framework/FunctionLib/FunctionLib.c", + "${k32w1_sdk_root}/middleware/wireless/framework/HWParameter/HWParameter.c", + "${k32w1_sdk_root}/middleware/wireless/framework/LowPower/PWR.c", + "${k32w1_sdk_root}/middleware/wireless/framework/LowPower/PWR_systicks.c", + "${k32w1_sdk_root}/middleware/wireless/framework/NVM/Source/NV_Flash.c", + "${k32w1_sdk_root}/middleware/wireless/framework/OtaSupport/Source/OtaExternalFlash.c", + "${k32w1_sdk_root}/middleware/wireless/framework/OtaSupport/Source/OtaSupport.c", + "${k32w1_sdk_root}/middleware/wireless/framework/RNG/RNG.c", + "${k32w1_sdk_root}/middleware/wireless/framework/SecLib/SecLib_sss.c", + "${k32w1_sdk_root}/middleware/wireless/framework/Sensors/sensors.c", + "${k32w1_sdk_root}/middleware/wireless/framework/boards/kw45_k32w1/K32W1480/clock_config.c", + "${k32w1_sdk_root}/middleware/wireless/framework/boards/kw45_k32w1/K32W1480/pin_mux.c", + "${k32w1_sdk_root}/middleware/wireless/framework/boards/kw45_k32w1/app_services_init.c", + "${k32w1_sdk_root}/middleware/wireless/framework/boards/kw45_k32w1/board.c", + "${k32w1_sdk_root}/middleware/wireless/framework/boards/kw45_k32w1/board_comp.c", + "${k32w1_sdk_root}/middleware/wireless/framework/boards/kw45_k32w1/board_dcdc.c", + "${k32w1_sdk_root}/middleware/wireless/framework/boards/kw45_k32w1/board_extflash.c", + "${k32w1_sdk_root}/middleware/wireless/framework/boards/kw45_k32w1/board_lp.c", + "${k32w1_sdk_root}/middleware/wireless/framework/boards/kw45_k32w1/hardware_init.c", + "${k32w1_sdk_root}/middleware/wireless/framework/platform/kw45_k32w1/fwk_platform.c", + "${k32w1_sdk_root}/middleware/wireless/framework/platform/kw45_k32w1/fwk_platform_ble.c", + "${k32w1_sdk_root}/middleware/wireless/framework/platform/kw45_k32w1/fwk_platform_extflash.c", + "${k32w1_sdk_root}/middleware/wireless/framework/platform/kw45_k32w1/fwk_platform_ics.c", + "${k32w1_sdk_root}/middleware/wireless/framework/platform/kw45_k32w1/fwk_platform_lowpower.c", + "${k32w1_sdk_root}/middleware/wireless/framework/platform/kw45_k32w1/fwk_platform_ot.c", + "${k32w1_sdk_root}/middleware/wireless/framework/platform/kw45_k32w1/fwk_platform_ota.c", + "${k32w1_sdk_root}/middleware/wireless/ieee-802.15.4/ieee_802_15_4/phy/source/PhyTime.c", + "${k32w1_sdk_root}/middleware/wireless/ieee-802.15.4/ieee_802_15_4/phy/source/SerialDevice/ASP.c", + "${k32w1_sdk_root}/middleware/wireless/ieee-802.15.4/ieee_802_15_4/phy/source/SerialDevice/Phy.c", + "${k32w1_sdk_root}/rtos/freertos/freertos_kernel/croutine.c", + "${k32w1_sdk_root}/rtos/freertos/freertos_kernel/event_groups.c", + "${k32w1_sdk_root}/rtos/freertos/freertos_kernel/list.c", + "${k32w1_sdk_root}/rtos/freertos/freertos_kernel/portable/GCC/ARM_CM33_NTZ/non_secure/port.c", + "${k32w1_sdk_root}/rtos/freertos/freertos_kernel/portable/GCC/ARM_CM33_NTZ/non_secure/portasm.c", + "${k32w1_sdk_root}/rtos/freertos/freertos_kernel/queue.c", + "${k32w1_sdk_root}/rtos/freertos/freertos_kernel/stream_buffer.c", + "${k32w1_sdk_root}/rtos/freertos/freertos_kernel/tasks.c", + "${k32w1_sdk_root}/rtos/freertos/freertos_kernel/timers.c", + ] + + if (chip_with_low_power == 1) { + sources += [] + } + + if (!defined(public_deps)) { + public_deps = [] + } + + public_deps += [ "${openthread_root}/src/core:libopenthread_core_headers" ] + + if (!defined(public_configs)) { + public_configs = [] + } + + public_configs += [ ":${sdk_target_name}_config" ] + } +} diff --git a/third_party/openthread/ot-nxp b/third_party/openthread/ot-nxp index d533504a90f231..72b9cbf7e9edb4 160000 --- a/third_party/openthread/ot-nxp +++ b/third_party/openthread/ot-nxp @@ -1 +1 @@ -Subproject commit d533504a90f2312ba3cfe94f716f3ee9b59d4209 +Subproject commit 72b9cbf7e9edb4d687855be75474ee59628831a1 diff --git a/third_party/openthread/platforms/nxp/k32w/k32w0/BUILD.gn b/third_party/openthread/platforms/nxp/k32w/k32w0/BUILD.gn index 0bf402053b8e80..a67158170d0a18 100644 --- a/third_party/openthread/platforms/nxp/k32w/k32w0/BUILD.gn +++ b/third_party/openthread/platforms/nxp/k32w/k32w0/BUILD.gn @@ -38,6 +38,7 @@ config("openthread_k32w0_config") { "OPENTHREAD_CONFIG_HEAP_EXTERNAL_ENABLE=1", "OPENTHREAD_CONFIG_IP6_SLAAC_ENABLE=1", "K32W0_RADIO_NUM_OF_RX_BUFS=16", + "OPENTHREAD_PLATFORM_CORE_CONFIG_FILE=\"app/project_include/OpenThreadConfig.h\"", ] } diff --git a/third_party/openthread/platforms/nxp/k32w/k32w1/BUILD.gn b/third_party/openthread/platforms/nxp/k32w/k32w1/BUILD.gn new file mode 100644 index 00000000000000..341c8960ede578 --- /dev/null +++ b/third_party/openthread/platforms/nxp/k32w/k32w1/BUILD.gn @@ -0,0 +1,76 @@ +# Copyright (c) 2020 Project CHIP Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import("//build_overrides/chip.gni") +import("//build_overrides/k32w1_sdk.gni") +import("//build_overrides/openthread.gni") +import("${chip_root}/third_party/nxp/k32w1_sdk/k32w1_sdk.gni") + +openthread_nxp_root = "${chip_root}/third_party/openthread/ot-nxp" + +config("openthread_k32w1_config") { + include_dirs = [ "${openthread_nxp_root}/src/k32w1/k32w1" ] + include_dirs += [ "${chip_root}/examples/platform/nxp/k32w/k32w1" ] + + defines = [ + "OPENTHREAD_CONFIG_HEAP_EXTERNAL_ENABLE=1", + "OPENTHREAD_CONFIG_IP6_SLAAC_ENABLE=1", + "MBEDTLS_ENTROPY_HARDWARE_ALT=1", + "OPENTHREAD_PLATFORM_CORE_CONFIG_FILE=\"app/project_include/OpenThreadConfig.h\"", + ] +} + +source_set("openthread_core_config_k32w1") { + sources = [ + "${openthread_nxp_root}/src/k32w1/k32w1/openthread-core-k32w1-config-check.h", + "${openthread_nxp_root}/src/k32w1/k32w1/openthread-core-k32w1-config.h", + ] + + public_configs = [ ":openthread_k32w1_config" ] +} + +source_set("openthread_mbedtls_config_k32w1") { + sources = [ "${openthread_nxp_root}/src/k32w1/k32w1/k32w1-mbedtls-config.h" ] +} + +source_set("libopenthread-k32w1") { + sources = [ + "${openthread_nxp_root}/src/common/flash_nvm.c", + "${openthread_nxp_root}/src/k32w1/k32w1/alarm.c", + "${openthread_nxp_root}/src/k32w1/k32w1/diag.c", + "${openthread_nxp_root}/src/k32w1/k32w1/entropy.c", + "${openthread_nxp_root}/src/k32w1/k32w1/logging.c", + "${openthread_nxp_root}/src/k32w1/k32w1/misc.c", + "${openthread_nxp_root}/src/k32w1/k32w1/power.c", + "${openthread_nxp_root}/src/k32w1/k32w1/radio.c", + "${openthread_nxp_root}/src/k32w1/k32w1/system.c", + "${openthread_nxp_root}/src/k32w1/k32w1/uart.c", + ] + + if (chip_crypto == "platform") { + sources += [ "${openthread_nxp_root}/src/k32w1/k32w1/ecdsa_sss.cpp" ] + } + + if (chip_with_ot_cli == 1) { + sources += [ "${openthread_root}/examples/apps/cli/cli_uart.cpp" ] + } + + public_deps = [ + ":openthread_core_config_k32w1", + "${k32w1_sdk_build_root}:k32w1_sdk", + "${openthread_root}/src/core:libopenthread_core_headers", + "../../..:libopenthread-platform", + "../../..:libopenthread-platform-utils", + ] +}