From c524637e2453de16796605b746b7e19531f8dbea Mon Sep 17 00:00:00 2001 From: Marius Tache <102153746+marius-alex-tache@users.noreply.github.com> Date: Thu, 1 Jun 2023 18:35:39 +0300 Subject: [PATCH] [K32W0] SDK 2.6.11 fixes & features (#26827) * [K32W0] Sync with SDK NCCL change Signed-off-by: Marius Tache * [K32W0] Add a flag chip_with_pdm_encryption to disable PDM encryption * [K32W0] Fix name for OPENTHREAD_CONFIG_MAC_DEFAULT_MAX_FRAME_RETRIES_DIRECT * [K32W0] Fix reboot reason bug * [K32W0] Change date type for rebootCause, uint8_t is enough * [K32W0] Add stop advertising retry If stop advertising fails (timeout on event wait), then rearm the timer as fast as possible to retry. Once stop advertising is successful, slow advertising can start. Signed-off-by: Marius Tache * [K32W0] Add ExitAction to OTATlvProcessor API An OTA TLV processor should have the option of executing an action when before a new processor is selected. This is called an exit action. It should be useful in the context of transferring multiple OTA images in external flash. Signed-off-by: Marius Tache * [K32W0] Update OTA SDK usage * Application and bootloader processors are now the same, referred to as firmware processor. * Application can now register callbacks for descriptor processing. * When using default processors, the OTA flags gOTAUseCustomOtaEntry and gOTAAllowCustomStartAddress are defined and set to 1. Signed-off-by: Marius Tache * [K32W0] Add JSON support for custom TLVs *A user can now specify a custom TLV format (tag, descriptor and path) by following the design of the ota_payload.schema. Option `--json` should be used to specify the JSON file path. * Add SSBL payload generation. Signed-off-by: Marius Tache * [K32W0] Call ExitAction from TLV processor when all block has been consumed - Move code from ApplyAction to ExitAction method * [K32W0] Update usage of new OTA SDK APIs * [K32W0] Set custom OTA entry flags Custom OTA entry structure will be saved at the top of external flash by default. Fix heap start address. It should start after the stack size is subtracted from the top. Signed-off-by: Marius Tache * [K32W0] Access OTA entry at Init and check OTA status * [K32W0] Update OTA tool to support SSBL Add a few examples to generate: * application update image * factory data update image * SSBL update image * app + SSBL + factory data update image * maximum number of custom OTA entries image Add README for K32W OTA. Signed-off-by: Marius Tache * [K32W0] Add example for max entries testing Signed-off-by: Marius Tache * [K32W0] Split default processors options * chip_enable_ota_firmware_processor enables the firmware (App/SSBL) processor. Enabled by default. * chip_enable_ota_factory_data_processor enables the factory data processor. Disabled by default. Signed-off-by: Marius Tache * [K32W0] Update README SSBL/PSECT sections Signed-off-by: Marius Tache * [K32W0] Bring README files up to date Signed-off-by: Marius Tache * [K32W0] Fix chip_crypto_flavour name Signed-off-by: Marius Tache * [K32W0] Fix readmes for k32w0 apps * [K32W0] Update SecLib path in SDK build file Signed-off-by: Marius Tache * [K32W0] Fix typo in README files regarding OTA image tool Signed-off-by: Marius Tache * [K32W0] Extend factory data restore mechanism 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. Application can register additional restore mechanisms using the API: `K32W0FactoryDataProvider::RegisterRestoreMechanism`. Signed-off-by: Marius Tache * [K32W0] Improve rotating device id Add README section related to rotating device id. If unique id is not found in factory data, use the default one. Signed-off-by: Marius Tache * [K32W0] Fix error status for rotating device id getter Signed-off-by: Marius Tache * [K32W0] Add retry mechanism when OTA is aborted Upon an aborted transfer, the OTA will retry a query on the backed up provider if CONFIG_CHIP_K32W0_OTA_ABORT_HOOK is set. The retry mechanism is disabled by default. Signed-off-by: Marius Tache * [K32W0] Add ota_custom_entry_address option ota_custom_entry_address specifies at which address in external flash the OTA custom entry is saved during OTA. By default, it is set to the end of the PDM area: 0x000C1000. Add internal/external flash description in linker. Signed-off-by: Marius Tache * [K32W0] Revert all applied actions upon error AbortAction for all selected OTA TLV processors will be called if any processor fails to apply its action. This is a safety mechanism to avoid having incompatible apps/data caused by partial updates (e.g. incompatibility between SSBL and app). Other changes: * Clean factory data RAM buffer * Increase reset resilience during OTA During OTA, if factory data is updated, it is firstly backed up in a PDM entry with id kNvmId_FactoryDataBackup. This entry should be deleted only if the OTA entry state is otaApplied, which means the device reset happened as expected. If OTA entry state is not otaApplied, it means the reset was caused by a different factor (e.g. power loss etc.) and the factory data should be restored from the PDM (for the default registered mechanism). * AbortAction should delete PDM id for factory data backup * Add reset state for OTATlvProcessor base class Derived classes should call OTATlvProcessor::ClearInternal() whenever they want to reset their state. * Update abort action for firmware processor OTA_ResetCurrentEepromAddress should be called before setting the offset to 0 through OTA_SetStartEepromOffset. Otherwise, a check in OTA_SetStartEepromOffset will fail and the state is not reset correctly. Signed-off-by: Marius Tache * [K32W0] Add SPIFI_DUAL_MODE_SUPPORT info in README files For K32W041AM, the multi-image SSBL must also be compiled with SPIFI_DUAL_MODE_SUPPORT=1. Signed-off-by: Marius Tache * [K32W0] Enclose CheckOtaEntry in OTA flag Signed-off-by: Marius Tache * [K32W0] Enclose factory data backup usage in corresponding flag Signed-off-by: Marius Tache * [K32W0] Update SDK in README files Fix minor issue with image partitions information. Signed-off-by: Marius Tache * [K32W0] Fix DiagnosticDataProvider memory leak GetNetworkInterfaces returns some dynamically allocated data, which is supposed to be freed upon calling ReleaseNetworkInterfaces. Our platform implementation was using the default implementation of ReleaseNetworkInterfaces (which does nothing). This is an obvious memory leak, since subsequent calls to GetNetworkInterfaces dynamically allocated data that was never freed. Signed-off-by: Marius Tache * [K32W0] Automate output binary signing Add BUILD.gn changes to call a python script postbuild that signs the output binary. Add signing python script. Update readme files. Signed-off-by: Andrei Menzopol * [K32W0] Fix chip_crypto_flavor typo Signed-off-by: Marius Tache * [K32W0] Bump ot-nxp to latest Signed-off-by: Marius Tache * [K32W0] Fix OTA generation tool Signed-off-by: Marius Tache * [K32W0] Bump ot-nxp to latest Signed-off-by: Marius Tache * [K32W0] Sync scripts and generated data with development Updated manufacturing flow. Signed-off-by: Mihai Ignat Signed-off-by: Marius Tache * [K32W0] Bump ot-nxp to latest Signed-off-by: Marius Tache * Restyled by whitespace * Restyled by clang-format * Restyled by gn * Restyled by prettier-markdown * Restyled by shellharden * Restyled by shfmt * Restyled by autopep8 * Restyled by isort * [K32W0] Fix spell check Signed-off-by: Marius Tache * [K32W0] Remove some binary examples (not used) Signed-off-by: Marius Tache * [K32W0] Fix some lint errors Signed-off-by: Marius Tache * [K32W0] Update workflow example yaml to latest Signed-off-by: Marius Tache * Restyled by prettier-markdown * [K32W0] Fix script issues Signed-off-by: Marius Tache * Restyled by autopep8 * Restyled by isort * [K32W0] Add PDM ID base value for apps Applications should use kNvmId_ApplicationBase alongside an offset to generate their own PDM IDs. Signed-off-by: Marius Tache * Restyled by clang-format --------- Signed-off-by: Marius Tache Signed-off-by: Andrei Menzopol Signed-off-by: Mihai Ignat Co-authored-by: tanyue518 Co-authored-by: Martin Cuvelier Co-authored-by: Mihai Ignat Co-authored-by: Andrei Menzopol Co-authored-by: Restyled.io --- .github/workflows/examples-k32w.yaml | 2 +- docs/guides/nxp_manufacturing_flow.md | 274 +++++++++++------- .../nxp/k32w/k32w0/BUILD.gn | 12 +- .../nxp/k32w/k32w0/README.md | 163 ++++++++--- .../nxp/k32w/k32w0/main/AppTask.cpp | 50 +++- examples/lighting-app/nxp/k32w/k32w0/BUILD.gn | 12 +- .../lighting-app/nxp/k32w/k32w0/README.md | 163 ++++++++--- .../nxp/k32w/k32w0/main/AppTask.cpp | 50 +++- examples/lock-app/nxp/k32w/k32w0/BUILD.gn | 12 +- examples/lock-app/nxp/k32w/k32w0/README.md | 24 +- .../k32w0/app/ldscripts/chip-k32w0x-linker.ld | 71 ++++- .../app/project_include/OpenThreadConfig.h | 4 +- .../k32w0/doc/images/ssbl_multi_image.JPG | Bin 0 -> 114896 bytes .../demo_factory_data_dut1.bin} | Bin .../demo_factory_data_dut2.bin} | Bin .../nxp/k32w/k32w0/scripts/detokenizer.py | 21 +- .../nxp/k32w/k32w0/scripts/sign-outdir.py | 14 + .../dac}/dut1/Chip-DAC-NXP-1037-A220-Cert.der | Bin .../dac}/dut1/Chip-DAC-NXP-1037-A220-Cert.pem | 0 .../dac}/dut1/Chip-DAC-NXP-1037-A220-Key.der | Bin .../dac}/dut1/Chip-DAC-NXP-1037-A220-Key.pem | 0 .../dac}/dut2/Chip-DAC-NXP-1037-A220-Cert.der | Bin .../dac}/dut2/Chip-DAC-NXP-1037-A220-Cert.pem | 0 .../dac}/dut2/Chip-DAC-NXP-1037-A220-Key.der | Bin .../dac}/dut2/Chip-DAC-NXP-1037-A220-Key.pem | 0 .../paa/Chip-PAA-NXP-Cert.der | Bin .../paa/Chip-PAA-NXP-Cert.pem | 0 .../paa/Chip-PAA-NXP-Key.pem | 0 .../pai/Chip-PAI-NXP-1037-A220-Cert.der | Bin .../pai/Chip-PAI-NXP-1037-A220-Cert.pem | 0 .../pai/Chip-PAI-NXP-1037-A220-Key.pem | 0 .../tools/nxp}/generate_cert.sh | 64 +++- scripts/tools/nxp/ota/README.md | 170 +++++------ .../ssbl_ext_flash_ota_entry_example.bin | Bin 0 -> 7056 bytes .../binaries/ssbl_ram_ota_entry_example.bin | Bin 0 -> 6768 bytes .../nxp/ota/examples/create_ota_images.sh | 119 ++++++++ .../ota/examples/ota_max_entries_example.json | 172 +++++++++++ scripts/tools/nxp/ota/ota_image_tool.py | 175 ++++++++--- scripts/tools/nxp/ota/ota_payload.schema | 67 +++++ .../CHIPDevicePlatformRamStorageConfig.h | 10 + .../nxp/k32w/common/K32W_OTA_README.md | 143 +++++++++ .../nxp/k32w/common/OTAImageProcessorImpl.cpp | 44 ++- .../nxp/k32w/common/OTAImageProcessorImpl.h | 29 +- .../nxp/k32w/common/OTATlvProcessor.cpp | 21 +- .../nxp/k32w/common/OTATlvProcessor.h | 25 +- .../nxp/k32w/k32w0/BLEManagerImpl.cpp | 19 +- src/platform/nxp/k32w/k32w0/BUILD.gn | 11 +- .../nxp/k32w/k32w0/CHIPDevicePlatformConfig.h | 10 +- .../k32w/k32w0/ConfigurationManagerImpl.cpp | 17 +- .../nxp/k32w/k32w0/ConfigurationManagerImpl.h | 2 +- .../k32w/k32w0/DiagnosticDataProviderImpl.cpp | 15 +- .../k32w/k32w0/DiagnosticDataProviderImpl.h | 1 + .../k32w/k32w0/K32W0FactoryDataProvider.cpp | 119 +++++--- .../nxp/k32w/k32w0/K32W0FactoryDataProvider.h | 10 +- .../nxp/k32w/k32w0/OTABootloaderProcessor.cpp | 55 ---- .../nxp/k32w/k32w0/OTABootloaderProcessor.h | 46 --- .../k32w/k32w0/OTAFactoryDataProcessor.cpp | 16 +- ...Processor.cpp => OTAFirmwareProcessor.cpp} | 63 ++-- ...tionProcessor.h => OTAFirmwareProcessor.h} | 17 +- src/platform/nxp/k32w/k32w0/OTAHooks.cpp | 87 +++++- .../crypto/CHIPCryptoPALNXPUltrafastP256.cpp | 12 +- third_party/nxp/k32w0_sdk/k32w0_sdk.gni | 56 +++- .../nxp/k32w0_sdk/sdk_fixes/patch_k32w_sdk.sh | 2 +- third_party/openthread/ot-nxp | 2 +- 64 files changed, 1833 insertions(+), 638 deletions(-) create mode 100644 examples/platform/nxp/k32w/k32w0/doc/images/ssbl_multi_image.JPG rename examples/platform/nxp/k32w/k32w0/scripts/{demo_generated_certs/dut1/out_dut1.bin => demo_generated_factory_data/demo_factory_data_dut1.bin} (100%) rename examples/platform/nxp/k32w/k32w0/scripts/{demo_generated_certs/dut2/out_dut2.bin => demo_generated_factory_data/demo_factory_data_dut2.bin} (100%) create mode 100644 examples/platform/nxp/k32w/k32w0/scripts/sign-outdir.py rename {examples/platform/nxp/k32w/k32w0/scripts/demo_generated_certs => scripts/tools/nxp/demo_generated_certs/dac}/dut1/Chip-DAC-NXP-1037-A220-Cert.der (100%) mode change 100755 => 100644 rename {examples/platform/nxp/k32w/k32w0/scripts/demo_generated_certs => scripts/tools/nxp/demo_generated_certs/dac}/dut1/Chip-DAC-NXP-1037-A220-Cert.pem (100%) mode change 100755 => 100644 rename {examples/platform/nxp/k32w/k32w0/scripts/demo_generated_certs => scripts/tools/nxp/demo_generated_certs/dac}/dut1/Chip-DAC-NXP-1037-A220-Key.der (100%) mode change 100755 => 100644 rename {examples/platform/nxp/k32w/k32w0/scripts/demo_generated_certs => scripts/tools/nxp/demo_generated_certs/dac}/dut1/Chip-DAC-NXP-1037-A220-Key.pem (100%) mode change 100755 => 100644 rename {examples/platform/nxp/k32w/k32w0/scripts/demo_generated_certs => scripts/tools/nxp/demo_generated_certs/dac}/dut2/Chip-DAC-NXP-1037-A220-Cert.der (100%) mode change 100755 => 100644 rename {examples/platform/nxp/k32w/k32w0/scripts/demo_generated_certs => scripts/tools/nxp/demo_generated_certs/dac}/dut2/Chip-DAC-NXP-1037-A220-Cert.pem (100%) mode change 100755 => 100644 rename {examples/platform/nxp/k32w/k32w0/scripts/demo_generated_certs => scripts/tools/nxp/demo_generated_certs/dac}/dut2/Chip-DAC-NXP-1037-A220-Key.der (100%) mode change 100755 => 100644 rename {examples/platform/nxp/k32w/k32w0/scripts/demo_generated_certs => scripts/tools/nxp/demo_generated_certs/dac}/dut2/Chip-DAC-NXP-1037-A220-Key.pem (100%) mode change 100755 => 100644 rename {examples/platform/nxp/k32w/k32w0/scripts => scripts/tools/nxp}/demo_generated_certs/paa/Chip-PAA-NXP-Cert.der (100%) mode change 100755 => 100644 rename {examples/platform/nxp/k32w/k32w0/scripts => scripts/tools/nxp}/demo_generated_certs/paa/Chip-PAA-NXP-Cert.pem (100%) mode change 100755 => 100644 rename {examples/platform/nxp/k32w/k32w0/scripts => scripts/tools/nxp}/demo_generated_certs/paa/Chip-PAA-NXP-Key.pem (100%) mode change 100755 => 100644 rename {examples/platform/nxp/k32w/k32w0/scripts => scripts/tools/nxp}/demo_generated_certs/pai/Chip-PAI-NXP-1037-A220-Cert.der (100%) mode change 100755 => 100644 rename {examples/platform/nxp/k32w/k32w0/scripts => scripts/tools/nxp}/demo_generated_certs/pai/Chip-PAI-NXP-1037-A220-Cert.pem (100%) mode change 100755 => 100644 rename {examples/platform/nxp/k32w/k32w0/scripts => scripts/tools/nxp}/demo_generated_certs/pai/Chip-PAI-NXP-1037-A220-Key.pem (100%) mode change 100755 => 100644 rename {examples/platform/nxp/k32w/k32w0/scripts => scripts/tools/nxp}/generate_cert.sh (64%) create mode 100755 scripts/tools/nxp/ota/examples/binaries/ssbl_ext_flash_ota_entry_example.bin create mode 100755 scripts/tools/nxp/ota/examples/binaries/ssbl_ram_ota_entry_example.bin create mode 100644 scripts/tools/nxp/ota/examples/create_ota_images.sh create mode 100644 scripts/tools/nxp/ota/examples/ota_max_entries_example.json create mode 100644 scripts/tools/nxp/ota/ota_payload.schema create mode 100644 src/platform/nxp/k32w/common/K32W_OTA_README.md delete mode 100644 src/platform/nxp/k32w/k32w0/OTABootloaderProcessor.cpp delete mode 100644 src/platform/nxp/k32w/k32w0/OTABootloaderProcessor.h rename src/platform/nxp/k32w/k32w0/{OTAApplicationProcessor.cpp => OTAFirmwareProcessor.cpp} (64%) rename src/platform/nxp/k32w/k32w0/{OTAApplicationProcessor.h => OTAFirmwareProcessor.h} (82%) diff --git a/.github/workflows/examples-k32w.yaml b/.github/workflows/examples-k32w.yaml index f71f3f26f1ae61..d5d31eddbdaba0 100644 --- a/.github/workflows/examples-k32w.yaml +++ b/.github/workflows/examples-k32w.yaml @@ -40,7 +40,7 @@ jobs: if: github.actor != 'restyled-io[bot]' container: - image: connectedhomeip/chip-build-k32w:0.7.3 + image: connectedhomeip/chip-build-k32w:0.7.14 volumes: - "/tmp/bloat_reports:/tmp/bloat_reports" steps: diff --git a/docs/guides/nxp_manufacturing_flow.md b/docs/guides/nxp_manufacturing_flow.md index 019cfa67a38680..141f90c8b8b57c 100644 --- a/docs/guides/nxp_manufacturing_flow.md +++ b/docs/guides/nxp_manufacturing_flow.md @@ -6,141 +6,193 @@ orphan: true By default, the example application is configured to use generic test certificates and provisioning data embedded with the application code. It is -possible for a final stage application to generate its own manufacturing data: - -- Generate new certificates - - _PAI_VID_ and _PAI_PID_ variables must be changed accordingly inside - generate_cert.sh script - - _generate_cert.sh_ script needs as input parameter the path to chip-cert - tool (compile it from ./src/tools/chip-cert). The output of the script is: - the DAC, PAI and PAA certificates. The DAC and PAI certificates will be - written in a special section of the internal flash, while the PAA will be - used on the chip-tool side as trust anchor. Please note that for _real - production manufacturing_ the "production PAA" is trusted via the DCL rather - than thorough PAA certificate generated along with DAC and PAI. The PAI cert - may also have a different lifecycle. - - ``` - user@ubuntu:~/Desktop/git/connectedhomeip$ ./examples/platform/nxp/k32w/k32w0/scripts/generate_cert.sh ./src/tools/chip-cert/out/chip-cert - ``` - -- Generate new provisioning data and convert all the data to a binary - (unencrypted data): - - ``` - user@ubuntu:~/Desktop/git/connectedhomeip$ python3 ./scripts/tools/nxp/factory_data_generator/generate.py -i 10000 -s UXKLzwHdN3DZZLBaL2iVGhQi/OoQwIwJRQV4rpEalbA= -p 14014 -d 1000 --vid 0x1037 --pid 0xa220 --vendor_name "NXP Semiconductors" --product_name "Lighting app" --serial_num "SN:12345678" --date "2022-10-21" --hw_version 1 --hw_version_str "1.0" --cert_declaration /home/ubuntu/manufacturing/Chip-Test-CD-1037-a220.der --dac_cert /home/ubuntu/manufacturing/Chip-DAC-NXP-Cert.der --dac_key /home/ubuntu/manufacturing/Chip-DAC-NXP-Key.der --pai_cert /home/ubuntu/manufacturing/Chip-PAI-NXP-Cert.der --spake2p_path ./src/tools/spake2p/out/spake2p --out out.bin - ``` - -- Same example as above, but with an already generated verifier passed as - input: - - ``` - user@ubuntu:~/Desktop/git/connectedhomeip$ python3 ./scripts/tools/nxp/factory_data_generator/generate.py -i 10000 -s UXKLzwHdN3DZZLBaL2iVGhQi/OoQwIwJRQV4rpEalbA= -p 14014 -d 1000 --vid 0x1037 --pid 0xa220 --vendor_name "NXP Semiconductors" --product_name "Lighting app" --serial_num "SN:12345678" --date "2022-10-21" --hw_version 1 --hw_version_str "1.0" --cert_declaration /home/ubuntu/manufacturing/Chip-Test-CD-1037-a220.der --dac_cert /home/ubuntu/manufacturing/Chip-DAC-NXP-Cert.der --dac_key /home/ubuntu/manufacturing/Chip-DAC-NXP-Key.der --pai_cert /home/ubuntu/manufacturing/Chip-PAI-NXP-Cert.der --spake2p_path ./src/tools/spake2p/out/spake2p --spake2p_verifier ivD5n3L2t5+zeFt6SjW7BhHRF30gFXWZVvvXgDxgCNcE+BGuTA5AUaVm3qDZBcMMKn1a6CakI4SxyPUnJr0CpJ4pwpr0DvpTlkQKqaRvkOQfAQ1XDyf55DuavM5KVGdDrg== --out out.bin - ``` +possible for a final stage application to generate its own manufacturing data +using the procedure described below. -- Generate new provisioning data and convert all the data to a binary - (encrypted data with the AES key): +## 1. Prerequisites - ``` - user@ubuntu:~/Desktop/git/connectedhomeip$ python3 ./scripts/tools/nxp/factory_data_generator/generate.py -i 10000 -s UXKLzwHdN3DZZLBaL2iVGhQi/OoQwIwJRQV4rpEalbA= -p 14014 -d 1000 --vid 0x1037 --pid 0xa220 --vendor_name "NXP Semiconductors" --product_name "Lighting app" --serial_num "SN:12345678" --date "2022-10-21" --hw_version 1 --hw_version_str "1.0" --cert_declaration /home/ubuntu/manufacturing/Chip-Test-CD-1037-a220.der --dac_cert /home/ubuntu/manufacturing/Chip-DAC-NXP-Cert.der --dac_key /home/ubuntu/manufacturing/Chip-DAC-NXP-Key.der --pai_cert /home/ubuntu/manufacturing/Chip-PAI-NXP-Cert.der --spake2p_path ./src/tools/spake2p/out/spake2p --out outEncrypted.bin --aes128_key 2B7E151628AED2A6ABF7158809CF4F3C - ``` +Build `chip-cert` tool: - Here is the interpretation of the parameters: +``` +cd src/tools/chip-cert +gn gen out +ninja -C out +``` - ``` - -i -> SPAKE2+ iteration - -s -> SPAKE2+ salt (passed as base64 encoded string) - -p -> SPAKE2+ passcode - -d -> discriminator - --vid -> Vendor ID - --pid -> Product ID - --vendor_name -> Vendor Name - --product_name -> Product Name - --serial_num -> Serial Number - --date -> Manufacturing Date (YYYY-MM-DD format) - --hw_version -> Hardware Version as number - --hw_version_str -> Hardware Version as string - --cert_declaration -> path to the Certification Declaration (der format) location - --dac_cert -> path to the DAC (der format) location - --dac_key -> path to the DAC key (der format) location - --pai_cert -> path to the PAI (der format) location - --spake2p_path -> path to the spake2p tool (compile it from ./src/tools/spake2p) - --out -> name of the binary that will be used for storing all the generated data - --aes128_key -> 128 bits AES key used to encrypt the whole dataset - --spake2p_verifier -> SPAKE2+ verifier (passed as base64 encoded string). If this option is set, - all SPAKE2+ inputs will be encoded in the final binary. The spake2p tool - will not be used to generate a new verifier on the fly. - ``` +Build `spake2p` tool: -- Write out.bin to the \$platform: +``` +cd src/tool/spake2p +gn gen out +ninja -C out +``` + +### Environment variables + +A user can customize the certificate generation by setting some environment +variables that are used within the utility scripts. Please note that the values +below are just an example and should be modified accordingly: - For the K32W0x1 platform, the binary needs to be written in the internal - flash at location 0x9D600 using DK6Programmer: +``` +export FACTORY_DATA_DEST=path/factory/data/dest +export DEVICE_TYPE=100 +export DATE=$(date +"%F") +export TIME=$(date +"%T") +export LIFETIME="7305" +export VID="1037" +export PID="A220" +``` + +`FACTORY_DATA_DEST` is the path where all factory related data is generated. + +`DEVICE_TYPE` should be updated according to the application device type (0x0100 +for the provided K32W0 lighting app). + +Additionally, `PAA_CERT` and `PAA_KEY` paths can be specified to use an already +existent **PAA**: + +``` +export PAA_CERT=path/certs/Chip-PAA-NXP-Cert.pem +export PAA_KEY=path/certs/Chip-PAA-NXP-Key.pem +``` + +## 2. Generate + +### a. Certificates + +``` +./scripts/tools/nxp/generate_cert.sh ./src/tools/chip-cert/out/chip-cert +``` + +The output of the script is the **DAC**, **PAI** and **PAA** certificates. If +`FACTORY_DATA_DEST` is set, the certificates will be moved there. The **DAC** +and **PAI** certificates will be written in a special section of the internal +flash, while the **PAA** will be used by `chip-tool` as trust anchor. Please +note that for _real production manufacturing_ the "production PAA" is trusted +via the **DCL** rather than through the generated **PAA** certificate. The +**PAI** certificate may also have a different lifecycle. - ``` - DK6Programmer.exe -Y -V2 -s -P 1000000 -Y -p FLASH@0x9D600="out.bin" - ``` +### b. Certification declaration (CD) -- Generate a new CD (certification declaration): +``` +./src/tools/chip-cert/out/chip-cert gen-cd --key ./credentials/test/certification-declaration/Chip-Test-CD-Signing-Key.pem --cert ./credentials/test/certification-declaration/Chip-Test-CD-Signing-Cert.pem --out $FACTORY_DATA_DEST/Chip-Test-CD-$VID-$PID.der --format-version 1 --vendor-id "0x$VID" --product-id "0x$PID" --device-type-id "0x$DEVICE_TYPE" --certificate-id "ZIG20142ZB330003-24" --security-level 0 --security-info 0 --version-number 9876 --certification-type 1 +``` - Inside _gen-test-cds.sh_, the parameters _vids_, _pid0_, _device_type_id_ - must be changed accordingly. Use _Chip-Test-CD-Signing-\*_ key and - certificate already available in - _./credentials/test/certification-declaration/_ which acts as CSA - Certificate. This CSA certificate is also hard-coded as Trust Anchor in the - current chip-tool version. To use this certificate and avoid generating a - new one, lines 69-70 must be commented in the _gen-test-cds.sh_ script (the - ones that are generating a new CD signing authority). +The command above is extracted from `./credentials/test/gen-test-cds.sh` script. +The CD generation uses predefined key and certificate found in +`./credentials/test/certification-declaration`. This **CSA** certificate is also +hard-coded as Trust Anchor in the current `chip-tool` version. - ``` - user@ubuntu:~/Desktop/git/connectedhomeip$ ./credentials/test/gen-test-cds.sh ./src/tools/chip-cert/out/chip-cert - ``` +By default, the CD is added to the factory data section. In order to have it +integrated in the application binary, set +`CHIP_USE_DEVICE_CONFIG_CERTIFICATION_DECLARATION` to 1 in the application's +CHIPProjectConfig.h file. -- Set the correct VID/PID and CD in the - examples/$APP_NAME/nxp/$platform/ChipProjectConfig.h file VID and PID values - should correspond to the ones used for DAC. CD bytes should be the ones - obtained at the step above: +### c. Provisioning data - ``` - user@ubuntu:~/manufacturing hexdump -ve '1/1 "0x%.2x, "' Chip-Test-CD-1037-A220.der - ``` +Generate new provisioning data and convert all the data to a binary (unencrypted +data): -- Use _chip_with_factory_data=1_ gn compilation argument +``` +python3 ./scripts/tools/nxp/factory_data_generator/generate.py -i 10000 -s UXKLzwHdN3DZZLBaL2iVGhQi/OoQwIwJRQV4rpEalbA= -p 14014 -d 1000 --vid "0x$VID" --pid "0x$PID" --vendor_name "NXP Semiconductors" --product_name "Lighting app" --serial_num "12345678" --date "$DATE" --hw_version 1 --hw_version_str "1.0" --cert_declaration $FACTORY_DATA_DEST/Chip-Test-CD-$VID-$PID.der --dac_cert $FACTORY_DATA_DEST/Chip-DAC-NXP-$VID-$PID-Cert.der --dac_key $FACTORY_DATA_DEST/Chip-DAC-NXP-$VID-$PID-Key.der --pai_cert $FACTORY_DATA_DEST/Chip-PAI-NXP-$VID-$PID-Cert.der --spake2p_path ./src/tools/spake2p/out/spake2p --unique_id "00112233445566778899aabbccddeeff" --out $FACTORY_DATA_DEST/factory_data.bin +``` - This is needed in order to load the data from the special flash section. - Build and flash the application. +Same example as above, but with an already generated verifier passed as input: -- Run chip-tool with a new PAA: +``` +python3 ./scripts/tools/nxp/factory_data_generator/generate.py -i 10000 -s UXKLzwHdN3DZZLBaL2iVGhQi/OoQwIwJRQV4rpEalbA= -p 14014 -d 1000 --vid "0x$VID" --pid "0x$PID" --vendor_name "NXP Semiconductors" --product_name "Lighting app" --serial_num "12345678" --date "$DATE" --hw_version 1 --hw_version_str "1.0" --cert_declaration $FACTORY_DATA_DEST/Chip-Test-CD-$VID-$PID.der --dac_cert $FACTORY_DATA_DEST/Chip-DAC-NXP-$VID-$PID-Cert.der --dac_key $FACTORY_DATA_DEST/Chip-DAC-NXP-$VID-$PID-Key.der --pai_cert $FACTORY_DATA_DEST/Chip-PAI-NXP-$VID-$PID-Cert.der --spake2p_path ./src/tools/spake2p/out/spake2p --spake2p_verifier ivD5n3L2t5+zeFt6SjW7BhHRF30gFXWZVvvXgDxgCNcE+BGuTA5AUaVm3qDZBcMMKn1a6CakI4SxyPUnJr0CpJ4pwpr0DvpTlkQKqaRvkOQfAQ1XDyf55DuavM5KVGdDrg== --unique_id "00112233445566778899aabbccddeeff" --out $FACTORY_DATA_DEST/factory_data.bin +``` - ``` - ./chip-tool pairing ble-thread 2 hex: $hex_value 14014 1000 --paa-trust-store-path /home/ubuntu/certs/paa - ``` +Generate new provisioning data and convert all the data to a binary (encrypted +data with the AES key). Add the following option to one of the above examples: - Here is the interpretation of the parameters: +``` +--aes128_key 2B7E151628AED2A6ABF7158809CF4F3C +``` - ``` - --paa-trust-store-path -> path to the generated PAA (der format) - ``` +Here is the interpretation of the **required** parameters: - _paa-trust-store-path_ must contain only the PAA certificate. Avoid placing - other certificates in the same location as this may confuse chip-tool. +``` +-i -> SPAKE2+ iteration +-s -> SPAKE2+ salt (passed as base64 encoded string) +-p -> SPAKE2+ passcode +-d -> discriminator +--vid -> Vendor ID +--pid -> Product ID +--vendor_name -> Vendor Name +--product_name -> Product Name +--hw_version -> Hardware Version as number +--hw_version_str -> Hardware Version as string +--cert_declaration -> path to the Certification Declaration (der format) location +--dac_cert -> path to the DAC (der format) location +--dac_key -> path to the DAC key (der format) location +--pai_cert -> path to the PAI (der format) location +--spake2p_path -> path to the spake2p tool (compile it from ./src/tools/spake2p) +--out -> name of the binary that will be used for storing all the generated data - PAA certificate can be copied to the chip-tool machine using SCP for - example. - This is needed for testing self-generated DACs, but likely not required for - "true production" with production PAI issued DACs. +``` -- Useful information/Known issues +Here is the interpretation of the **optional** parameters: - Implementation of manufacturing data provisioning has been validated using test certificates generated by OpenSSL 1.1.1l. +``` +--dac_key_password -> Password to decode DAC key +--spake2p_verifier -> SPAKE2+ verifier (passed as base64 encoded string). If this option is set, + all SPAKE2+ inputs will be encoded in the final binary. The spake2p tool + will not be used to generate a new verifier on the fly. +--aes128_key -> 128 bits AES key used to encrypt the whole dataset +--date -> Manufacturing Date (YYYY-MM-DD format) +--part_number -> Part number as string +--product_url -> Product URL as string +--product_label -> Product label as string +--serial_num -> Serial Number +--unique_id -> Unique id used for rotating device id generation +``` - Also, demo DAC, PAI and PAA certificates needed in case _chip_with_factory_data=1_ is used can be found in examples/platform/nxp/k32w/k32w0/scripts/demo_generated_certs. +## 3. Write provisioning data - dut1/dut2 folders contains different DACs/Private Keys and can be used for testing topologies with 2 DUTS. +For the **K32W0x1** variants, the binary needs to be written in the internal +flash at location **0x9D600** using `DK6Programmer.exe`: - out_dut1.bin/out2_dut2.bin contains the corresponding DACs/PAIs generated using generate_nxp_chip_factory_bin.py script. The discriminator is 14014 and the passcode is 1000. +``` +DK6Programmer.exe -Y -V2 -s -P 1000000 -Y -p FLASH@0x9D600="factory_data.bin" +``` - These demo certificates are working with the CDs installed in CHIPProjectConfig.h. +For the **RT1060**, **RT1170** and **RW61X** platform, the binary needs to be +written using `MCUXpresso Flash Tool GUI` at the address value corresponding to +`__FACTORY_DATA_START` (the map file of the application should be checked to get +the exact value). + +## 4. Build app and usage + +Use `chip_with_factory_data=1` when compiling to enable factory data usage. + +Run chip-tool with a new PAA: + +``` +./chip-tool pairing ble-thread 2 hex: $hex_value 14014 1000 --paa-trust-store-path /home/ubuntu/certs/paa +``` + +Here is the interpretation of the parameters: + +``` +--paa-trust-store-path -> path to the generated PAA (der format) +``` + +`paa-trust-store-path` must contain only the PAA certificate. Avoid placing +other certificates in the same location as this may confuse `chip-tool`. + +**PAA** certificate can be copied to the chip-tool machine using **SCP** for +example. + +This is needed for testing self-generated **DACs**, but likely not required for +"true production" with production **PAI** issued **DACs**. + +## 5. Useful information/Known issues + +Implementation of manufacturing data provisioning has been validated using test +certificates generated by `OpenSSL 1.1.1l`. + +Also, demo **DAC**, **PAI** and **PAA** certificates needed in case +`chip_with_factory_data=1` is used can be found in +`./scripts/tools/nxp/demo_generated_certs`. diff --git a/examples/contact-sensor-app/nxp/k32w/k32w0/BUILD.gn b/examples/contact-sensor-app/nxp/k32w/k32w0/BUILD.gn index 0087777552cdce..fc64f7de9c8e66 100644 --- a/examples/contact-sensor-app/nxp/k32w/k32w0/BUILD.gn +++ b/examples/contact-sensor-app/nxp/k32w/k32w0/BUILD.gn @@ -167,12 +167,22 @@ if (chip_pw_tokenizer_logging) { } group("k32w0") { - deps = [ ":contact_sensor_app" ] + deps = [ + ":binsign", + ":contact_sensor_app", + ] if (chip_pw_tokenizer_logging) { deps += [ ":contact_sensor_app.database" ] } } +action("binsign") { + deps = [ ":contact_sensor_app" ] + script = "${k32w0_platform_dir}/scripts/sign-outdir.py" + output_name = "bignsign.log" + outputs = [ "${root_build_dir}/${output_name}" ] +} + group("default") { deps = [ ":k32w0" ] } diff --git a/examples/contact-sensor-app/nxp/k32w/k32w0/README.md b/examples/contact-sensor-app/nxp/k32w/k32w0/README.md index 54854b41613622..bfbf287e113d6c 100644 --- a/examples/contact-sensor-app/nxp/k32w/k32w0/README.md +++ b/examples/contact-sensor-app/nxp/k32w/k32w0/README.md @@ -177,20 +177,19 @@ In order to build the Project CHIP example, we recommend using a Linux distribution (the demo-application was compiled on Ubuntu 20.04). - Download - [K32W061DK6 SDK 2.6.10](https://cache.nxp.com/lgfiles/bsps/SDK_2_6_10_K32W061DK6.zip). + [K32W061DK6 SDK 2.6.11](https://cache.nxp.com/lgfiles/bsps/SDK_2_6_11_K32W061DK6.zip). - Start building the application either with Secure Element or without - without Secure Element ``` - user@ubuntu:~/Desktop/git/connectedhomeip$ export NXP_K32W0_SDK_ROOT=/home/user/Desktop/SDK_2_6_10_K32W061DK6/ + user@ubuntu:~/Desktop/git/connectedhomeip$ export NXP_K32W0_SDK_ROOT=/home/user/Desktop/SDK_2_6_11_K32W061DK6/ user@ubuntu:~/Desktop/git/connectedhomeip$ ./third_party/nxp/k32w0_sdk/sdk_fixes/patch_k32w_sdk.sh user@ubuntu:~/Desktop/git/connectedhomeip$ source ./scripts/activate.sh user@ubuntu:~/Desktop/git/connectedhomeip$ cd examples/contact-sensor-app/nxp/k32w/k32w0 user@ubuntu:~/Desktop/git/connectedhomeip/examples/contact-sensor-app/nxp/k32w/k32w0$ gn gen out/debug --args="k32w0_sdk_root=\"${NXP_K32W0_SDK_ROOT}\" chip_with_OM15082=1 chip_with_ot_cli=0 is_debug=false chip_crypto=\"platform\" chip_with_se05x=0 chip_pw_tokenizer_logging=true" user@ubuntu:~/Desktop/git/connectedhomeip/examples/contact-sensor-app/nxp/k32w/k32w0$ ninja -C out/debug - user@ubuntu:~/Desktop/git/connectedhomeip/examples/contact-sensor-app/nxp/k32w/k32w0$ $NXP_K32W0_SDK_ROOT/tools/imagetool/sign_images.sh out/debug/ ``` - with Secure element Exactly the same steps as above but set @@ -204,7 +203,7 @@ Secure Element. These can be changed if building without Secure Element Exactly the same steps as above but set argument build_for_k32w041am=1 in the gn command and use - [K32W041AMDK6 SDK 2.6.10](https://cache.nxp.com/lgfiles/bsps/SDK_2_6_10_K32W041AMDK6.zip). + [K32W041AMDK6 SDK 2.6.11](https://cache.nxp.com/lgfiles/bsps/SDK_2_6_11_K32W041AMDK6.zip). Also, in case the OM15082 Expansion Board is not attached to the DK6 board, the build argument (chip_with_OM15082) inside the gn build instruction should be set @@ -218,8 +217,8 @@ running oscillator as a clock source. In this case one must set the use_fro_32k argument to 1. In case signing errors are encountered when running the "sign_images.sh" script -install the recommanded packages (python version > 3, pip3, pycrypto, -pycryptodome): +(run automatically) install the recommanded packages (python version > 3, pip3, +pycrypto, pycryptodome): ``` user@ubuntu:~$ python3 --version @@ -238,6 +237,21 @@ The resulting output file can be found in out/debug/chip-k32w0x-contact-example. - When using Secure element and cross-compiling on Linux, log messages from the Plug&Trust middleware stack may not echo to the console. +## Rotating device id + +This is an optional feature and can be used in multiple ways (please see section +5.4.2.4.5 from Matter specification). One use case is Amazon Frustration Free +Setup, which leverages the C3 Characteristic (Additional commissioning-related +data) to offer an easier way to set up the device. The rotating device id will +be encoded in this additional data and is programmed to rotate at pre-defined +moments. The algorithm uses a unique per-device identifier that must be +programmed during factory provisioning. + +Please use the following build args: + +- `chip_enable_rotating_device_id=1` - to enable rotating device id. +- `chip_enable_additional_data_advertising=1` - to enable C3 characteristic. + ## Manufacturing data See @@ -375,18 +389,34 @@ application. ### Writing the SSBL -The SSBL can ge generated from one of the SDK demo examples. The SDK demo -example needs to be compiled inside MCUXpresso with the define _PDM_EXT_FLASH_. -The SSBL demo application can be imported from the _Quickstart panel_: _Import -SDK example(s)_ -> select _wireless->framework->ssbl_ application. +The SDK already provides an SSBL binary compiled with external flash support: +`boards/k32w061dk6/wireless_examples/framework/ssbl/binary/ssbl_ext_flash_pdm_support.bin`, +but it does not offer multi-image OTA support. + +Alternatively, the SSBL can ge generated from one of the SDK demo examples. The +SSBL demo application can be imported from the `Quickstart panel`: +`Import SDK example(s) -> select wireless -> framework -> ssbl` application. ![SSBL Application Select](../../../../platform/nxp/k32w/k32w0/doc/images/ssbl_select.JPG) -The SSBL project must be compiled using the PDM_EXT_FLASH define. +To support multi-image OTA feature, the SSBL project must be compiled using the +following defines: -![PDM_EXT_FLASH](../../../../platform/nxp/k32w/k32w0/doc/images/pdm_ext_flash.JPG) +- `PDM_EXT_FLASH=1` - support PDM in external flash. +- `gOTAUseCustomOtaEntry=1` - support custom OTA entry for multi-image. +- `gOTACustomOtaEntryMemory=OTACustomStorage_ExtFlash` - K32W0 uses + `OTACustomStorage_ExtFlash` (1) by default. +- `SPIFI_DUAL_MODE_SUPPORT=1` - only for configurations that use dual `SPIFI` + flash (e.g. K32W041AM variant). -Once compiled, the required ssbl file is called k32w061dk6_ssbl.bin +Optionally, add the following defines: + +- `SPIFI_OPTIM_SIZE=1` - to optimize SSBL size. +- `EXTERNAL_FLASH_DATA_OTA=1` - to support external read only data. + +![SSBL_MULTI_IMAGE](../../../../platform/nxp/k32w/k32w0/doc/images/ssbl_multi_image.JPG) + +Once compiled, the required SSBL file is called `k32w061dk6_ssbl.bin`. ![SSBL_BIN](../../../../platform/nxp/k32w/k32w0/doc/images/ssbl_bin.JPG) @@ -396,7 +426,7 @@ Before writing the SSBL, it it recommanded to fully erase the internal flash: DK6Programmer.exe -V 5 -P 1000000 -s -e Flash ``` -k32w061dk6_ssbl.bin must be written at address 0 in the internal flash: +`k32w061dk6_ssbl.bin` must be written at address 0 in the internal flash: ``` DK6Programmer.exe -V2 -s -P 1000000 -Y -p FLASH@0x00="k32w061dk6_ssbl.bin" @@ -404,7 +434,46 @@ DK6Programmer.exe -V2 -s -P 1000000 -Y -p FLASH@0x00="k32w061dk6_ssbl ### Writing the PSECT -First, image directory 0 must be written: +This is the list of all supported partitions: + +``` +0000000010000000 : SSBL partition + + 00000000 -----------> Start Address + 1000 ---------------> 0x0010 Number of 512-bytes pages + 00 -----------------> 0x00 Bootable flag + 00 -----------------> 0x00 Image type (0x00 = SSBL) + +004000000f020101: Application partition + + 00400000 -----------> 0x00004000 Start Address + 0f02 ---------------> 0x020f Number of 512-bytes pages + 01 -----------------> 0x01 Bootable flag + 01 -----------------> 0x01 Image type (0x01 = Application) + +00000010800000fe: Ext Flash text partition + + 00000010 -----------> 0x10000000 Start Address (external flash) + 8000 ---------------> 0x0080 Number of 512-bytes pages + 00 -----------------> 0x00 Bootable flag + fe -----------------> 0xFE Image type (0xFE = Ext Flash text) + +00000110300200fc : OTA Image partition + + 00000110 -----------> 0x10010000 Start Address + 3002----------------> 0x0230 Number of 512-bytes pages + 00 -----------------> 0x00 Bootable flag + fc -----------------> 0xFC Image type (0xFC = OTA partition) + +00000510100000fd: NVM partition + + 00000510 -----------> 0x10050000 Start Address + 1000 ---------------> 0x0010 Number of 512-bytes pages + 00 -----------------> 0x00 Bootable flag + fd -----------------> 0xFD Image type (0xFD = NVM partition) +``` + +First, image directory 0 (SSBL partition) must be written: ``` DK6Programmer.exe -V5 -s -P 1000000 -w image_dir_0=0000000010000000 @@ -419,7 +488,7 @@ Here is the interpretation of the fields: 00 -> SSBL Image Type ``` -Second, image directory 1 must be written: +Second, image directory 1 (application partition) must be written: ``` DK6Programmer.exe -V5 -s -P 1000000 -w image_dir_1=00400000C9040101 @@ -429,11 +498,14 @@ Here is the interpretation of the fields: ``` 00400000 -> start address 0x00004000 -CD04 -> 0x4C9 pages of 512-bytes (= 612,5kB) +C904 -> 0x4C9 pages of 512-bytes (= 612.5kB) 01 -> bootable flag 01 -> image type for the application ``` +Please note the user can write additional partitions by writing +`image_dir_2/3/4` with the wanted configuration. + ### Writing the application DK6Programmer can be used for flashing the application: @@ -500,37 +572,56 @@ Build OTA image: 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|` +- `scripts/tools/nxp/ota/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. A user can enable the default processors by specifying -`chip_enable_ota_default_processors=1` in the build command. Please see more in -the [OTA image tool guide](../../../../../scripts/tools/nxp/ota/README.md). +factory data TLV value. -Here is an example that generate an OTA image with factory data and app TLV: -`user@computer1:~/connectedhomeip$ : ./scripts/tools/nxp/ota/ota_image_tool.py create -v 0xDEAD -p 0xBEEF -vn 1 -vs "1.0" -da sha256 -fd --cert_declaration ~/manufacturing/Chip-Test-CD-1037-a220.der -app chip-k32w0x-contact-example.bin chip-k32w0x-contact-example.bin chip-k32w0x-contact-example.ota` +A user can select which default processors to enable: -Start the OTA Provider Application: +- `chip_enable_ota_firmware_processor=1` to enable default firmware (app/SSBL) + update processor (enabled by default). +- `chip_enable_ota_factory_data_processor=1` to enable default factory data + update processor (disabled by default). + +The address for storing the custom OTA entry can also be specified: + +- `ota_custom_entry_address="0x000C1000"` is the default value, where + `0x000C1000` is the end address of the PDM area. PDM area ends at + `0x00100000` (top of external flash) and has a size of `63 * 4096` bytes. + The user should be aware of the external flash configuration and use an + address that does not overlap with anything else. + +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: ``` -user@computer1:~/connectedhomeip$ : rm -rf /tmp/chip_* -user@computer1:~/connectedhomeip$ : ./out/ota-provider-app/chip-ota-provider-app -f chip-k32w0x-contact-example.ota +./scripts/tools/nxp/ota/ota_image_tool.py create -v 0xDEAD -p 0xBEEF -vn 42021 -vs "1.0" -da sha256 --app-input-file chip-k32w0x-contact-example.bin chip-k32w0x-contact-example.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. +has its own software version, given by +`CHIP_DEVICE_CONFIG_DEVICE_SOFTWARE_VERSION` (`42020` by default), 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. -Build Linux chip-tool: +Start the OTA Provider Application: ``` -user@computer1:~/connectedhomeip$ : ./scripts/examples/gn_build_example.sh examples/chip-tool out/chip-tool-app +user@computer1:~/connectedhomeip$ : rm -rf /tmp/chip_* +user@computer1:~/connectedhomeip$ : ./out/ota-provider-app/chip-ota-provider-app -f chip-k32w0x-contact-example.ota ``` Provision the OTA provider application and assign node id _1_. Also, grant ACL diff --git a/examples/contact-sensor-app/nxp/k32w/k32w0/main/AppTask.cpp b/examples/contact-sensor-app/nxp/k32w/k32w0/main/AppTask.cpp index 3630e581ba9954..ecd120d9020f17 100644 --- a/examples/contact-sensor-app/nxp/k32w/k32w0/main/AppTask.cpp +++ b/examples/contact-sensor-app/nxp/k32w/k32w0/main/AppTask.cpp @@ -99,6 +99,15 @@ static BDXDownloader gDownloader; constexpr uint16_t requestedOtaBlockSize = 1024; #endif +#if CONFIG_CHIP_K32W0_REAL_FACTORY_DATA +CHIP_ERROR CustomFactoryDataRestoreMechanism(void) +{ + K32W_LOG("This is a custom factory data restore mechanism."); + + return CHIP_NO_ERROR; +} +#endif + CHIP_ERROR AppTask::StartAppTask() { CHIP_ERROR err = CHIP_NO_ERROR; @@ -114,6 +123,40 @@ CHIP_ERROR AppTask::StartAppTask() return err; } +#if CHIP_DEVICE_CONFIG_ENABLE_OTA_REQUESTOR +static void CheckOtaEntry() +{ + K32W_LOG("Current OTA_ENTRY_TOP_ADDR: 0x%x", OTA_ENTRY_TOP_ADDR); + + CustomOtaEntries_t ota_entries; + if (gOtaSuccess_c == OTA_GetCustomEntries(&ota_entries) && ota_entries.ota_state != otaNoImage) + { + if (ota_entries.ota_state == otaApplied) + { + K32W_LOG("OTA successfully applied"); +#if CONFIG_CHIP_K32W0_OTA_FACTORY_DATA_PROCESSOR + // If this point is reached, it means OTA_CommitCustomEntries was successfully called. + // Delete the factory data backup to stop doing a restore when the factory data provider + // is initialized. This ensures that both the factory data and app were updated, otherwise + // revert to the backed up factory data. + PDM_vDeleteDataRecord(kNvmId_FactoryDataBackup); +#endif + } + else + { + K32W_LOG("OTA failed with status %d", ota_entries.ota_state); + } + + // Clear the entry + OTA_ResetCustomEntries(); + } + else + { + K32W_LOG("Unable to access OTA entries structure"); + } +} +#endif + CHIP_ERROR AppTask::Init() { CHIP_ERROR err = CHIP_NO_ERROR; @@ -129,10 +172,15 @@ CHIP_ERROR AppTask::Init() // Init ZCL Data Model and start server PlatformMgr().ScheduleWork(InitServer, 0); -// Initialize device attestation config +#if CHIP_DEVICE_CONFIG_ENABLE_OTA_REQUESTOR + CheckOtaEntry(); +#endif + + // Initialize device attestation config #if CONFIG_CHIP_K32W0_REAL_FACTORY_DATA // Initialize factory data provider ReturnErrorOnFailure(AppTask::FactoryDataProvider::GetDefaultInstance().Init()); + AppTask::FactoryDataProvider::GetDefaultInstance().RegisterRestoreMechanism(CustomFactoryDataRestoreMechanism); SetDeviceInstanceInfoProvider(&AppTask::FactoryDataProvider::GetDefaultInstance()); SetDeviceAttestationCredentialsProvider(&AppTask::FactoryDataProvider::GetDefaultInstance()); SetCommissionableDataProvider(&AppTask::FactoryDataProvider::GetDefaultInstance()); diff --git a/examples/lighting-app/nxp/k32w/k32w0/BUILD.gn b/examples/lighting-app/nxp/k32w/k32w0/BUILD.gn index 3ccf6f3df1b282..54721dad1a397d 100644 --- a/examples/lighting-app/nxp/k32w/k32w0/BUILD.gn +++ b/examples/lighting-app/nxp/k32w/k32w0/BUILD.gn @@ -160,12 +160,22 @@ if (chip_pw_tokenizer_logging) { } group("k32w0") { - deps = [ ":light_app" ] + deps = [ + ":binsign", + ":light_app", + ] if (chip_pw_tokenizer_logging) { deps += [ ":light_app.database" ] } } +action("binsign") { + deps = [ ":light_app" ] + script = "${k32w0_platform_dir}/scripts/sign-outdir.py" + output_name = "bignsign.log" + outputs = [ "${root_build_dir}/${output_name}" ] +} + group("default") { deps = [ ":k32w0" ] } diff --git a/examples/lighting-app/nxp/k32w/k32w0/README.md b/examples/lighting-app/nxp/k32w/k32w0/README.md index 1bebb18a50e650..5019e8a4f612f6 100644 --- a/examples/lighting-app/nxp/k32w/k32w0/README.md +++ b/examples/lighting-app/nxp/k32w/k32w0/README.md @@ -192,18 +192,17 @@ In order to build the Project CHIP example, we recommend using a Linux distribution (the demo-application was compiled on Ubuntu 20.04). - Download - [K32W061DK6 SDK 2.6.10](https://cache.nxp.com/lgfiles/bsps/SDK_2_6_10_K32W061DK6.zip). + [K32W061DK6 SDK 2.6.11](https://cache.nxp.com/lgfiles/bsps/SDK_2_6_11_K32W061DK6.zip). - Start building the application either with Secure Element or without - without Secure Element ``` -user@ubuntu:~/Desktop/git/connectedhomeip$ export NXP_K32W0_SDK_ROOT=/home/user/Desktop/SDK_2_6_10_K32W061DK6/ +user@ubuntu:~/Desktop/git/connectedhomeip$ export NXP_K32W0_SDK_ROOT=/home/user/Desktop/SDK_2_6_11_K32W061DK6/ user@ubuntu:~/Desktop/git/connectedhomeip$ source ./scripts/activate.sh user@ubuntu:~/Desktop/git/connectedhomeip$ cd examples/lighting-app/nxp/k32w/k32w0 user@ubuntu:~/Desktop/git/connectedhomeip/examples/lighting-app/nxp/k32w/k32w0$ gn gen out/debug --args="k32w0_sdk_root=\"${NXP_K32W0_SDK_ROOT}\" chip_with_OM15082=1 chip_with_ot_cli=0 is_debug=false chip_crypto=\"platform\" chip_with_se05x=0 chip_pw_tokenizer_logging=true" user@ubuntu:~/Desktop/git/connectedhomeip/examples/lighting-app/nxp/k32w/k32w0$ ninja -C out/debug -user@ubuntu:~/Desktop/git/connectedhomeip/examples/lighting-app/nxp/k32w/k32w0$ $NXP_K32W0_SDK_ROOT/tools/imagetool/sign_images.sh out/debug/ ``` - with Secure element @@ -217,7 +216,7 @@ Secure Element. These can be changed if building without Secure Element Exactly the same steps as above but set argument build_for_k32w041am=1 in the gn command and use - [K32W041AMDK6 SDK 2.6.10](https://cache.nxp.com/lgfiles/bsps/SDK_2_6_10_K32W041AMDK6.zip). + [K32W041AMDK6 SDK 2.6.11](https://cache.nxp.com/lgfiles/bsps/SDK_2_6_11_K32W041AMDK6.zip). Also, in case the OM15082 Expansion Board is not attached to the DK6 board, the build argument (chip_with_OM15082) inside the gn build instruction should be set @@ -231,8 +230,8 @@ running oscillator as a clock source. In this case one must set the use_fro_32k argument to 1. In case signing errors are encountered when running the "sign_images.sh" script -install the recommanded packages (python version > 3, pip3, pycrypto, -pycryptodome): +(run automatically) install the recommanded packages (python version > 3, pip3, +pycrypto, pycryptodome): ``` user@ubuntu:~$ python3 --version @@ -251,6 +250,21 @@ The resulting output file can be found in out/debug/chip-k32w0x-light-example. - When using Secure element and cross-compiling on Linux, log messages from the Plug&Trust middleware stack may not echo to the console. +## Rotating device id + +This is an optional feature and can be used in multiple ways (please see section +5.4.2.4.5 from Matter specification). One use case is Amazon Frustration Free +Setup, which leverages the C3 Characteristic (Additional commissioning-related +data) to offer an easier way to set up the device. The rotating device id will +be encoded in this additional data and is programmed to rotate at pre-defined +moments. The algorithm uses a unique per-device identifier that must be +programmed during factory provisioning. + +Please use the following build args: + +- `chip_enable_rotating_device_id=1` - to enable rotating device id. +- `chip_enable_additional_data_advertising=1` - to enable C3 characteristic. + ## Manufacturing data See @@ -388,18 +402,34 @@ application. ### Writing the SSBL -The SSBL can ge generated from one of the SDK demo examples. The SDK demo -example needs to be compiled inside MCUXpresso with the define _PDM_EXT_FLASH_. -The SSBL demo application can be imported from the _Quickstart panel_: _Import -SDK example(s)_ -> select _wireless->framework->ssbl_ application. +The SDK already provides an SSBL binary compiled with external flash support: +`boards/k32w061dk6/wireless_examples/framework/ssbl/binary/ssbl_ext_flash_pdm_support.bin`, +but it does not offer multi-image OTA support. + +Alternatively, the SSBL can ge generated from one of the SDK demo examples. The +SSBL demo application can be imported from the `Quickstart panel`: +`Import SDK example(s) -> select wireless -> framework -> ssbl` application. ![SSBL Application Select](../../../../platform/nxp/k32w/k32w0/doc/images/ssbl_select.JPG) -The SSBL project must be compiled using the PDM_EXT_FLASH define. +To support multi-image OTA feature, the SSBL project must be compiled using the +following defines: -![PDM_EXT_FLASH](../../../../platform/nxp/k32w/k32w0/doc/images/pdm_ext_flash.JPG) +- `PDM_EXT_FLASH=1` - support PDM in external flash. +- `gOTAUseCustomOtaEntry=1` - support custom OTA entry for multi-image. +- `gOTACustomOtaEntryMemory=OTACustomStorage_ExtFlash` - K32W0 uses + `OTACustomStorage_ExtFlash` (1) by default. +- `SPIFI_DUAL_MODE_SUPPORT=1` - only for configurations that use dual `SPIFI` + flash (e.g. K32W041AM variant). -Once compiled, the required ssbl file is called k32w061dk6_ssbl.bin +Optionally, add the following defines: + +- `SPIFI_OPTIM_SIZE=1` - to optimize SSBL size. +- `EXTERNAL_FLASH_DATA_OTA=1` - to support external read only data. + +![SSBL_MULTI_IMAGE](../../../../platform/nxp/k32w/k32w0/doc/images/ssbl_multi_image.JPG) + +Once compiled, the required SSBL file is called `k32w061dk6_ssbl.bin`. ![SSBL_BIN](../../../../platform/nxp/k32w/k32w0/doc/images/ssbl_bin.JPG) @@ -409,7 +439,7 @@ Before writing the SSBL, it it recommanded to fully erase the internal flash: DK6Programmer.exe -V 5 -P 1000000 -s -e Flash ``` -k32w061dk6_ssbl.bin must be written at address 0 in the internal flash: +`k32w061dk6_ssbl.bin` must be written at address 0 in the internal flash: ``` DK6Programmer.exe -V2 -s -P 1000000 -Y -p FLASH@0x00="k32w061dk6_ssbl.bin" @@ -417,7 +447,46 @@ DK6Programmer.exe -V2 -s -P 1000000 -Y -p FLASH@0x00="k32w061dk6_ssbl ### Writing the PSECT -First, image directory 0 must be written: +This is the list of all supported partitions: + +``` +0000000010000000 : SSBL partition + + 00000000 -----------> Start Address + 1000 ---------------> 0x0010 Number of 512-bytes pages + 00 -----------------> 0x00 Bootable flag + 00 -----------------> 0x00 Image type (0x00 = SSBL) + +004000000f020101: Application partition + + 00400000 -----------> 0x00004000 Start Address + 0f02 ---------------> 0x020f Number of 512-bytes pages + 01 -----------------> 0x01 Bootable flag + 01 -----------------> 0x01 Image type (0x01 = Application) + +00000010800000fe: Ext Flash text partition + + 00000010 -----------> 0x10000000 Start Address (external flash) + 8000 ---------------> 0x0080 Number of 512-bytes pages + 00 -----------------> 0x00 Bootable flag + fe -----------------> 0xFE Image type (0xFE = Ext Flash text) + +00000110300200fc : OTA Image partition + + 00000110 -----------> 0x10010000 Start Address + 3002----------------> 0x0230 Number of 512-bytes pages + 00 -----------------> 0x00 Bootable flag + fc -----------------> 0xFC Image type (0xFC = OTA partition) + +00000510100000fd: NVM partition + + 00000510 -----------> 0x10050000 Start Address + 1000 ---------------> 0x0010 Number of 512-bytes pages + 00 -----------------> 0x00 Bootable flag + fd -----------------> 0xFD Image type (0xFD = NVM partition) +``` + +First, image directory 0 (SSBL partition) must be written: ``` DK6Programmer.exe -V5 -s -P 1000000 -w image_dir_0=0000000010000000 @@ -432,7 +501,7 @@ Here is the interpretation of the fields: 00 -> SSBL Image Type ``` -Second, image directory 1 must be written: +Second, image directory 1 (application partition) must be written: ``` DK6Programmer.exe -V5 -s -P 1000000 -w image_dir_1=00400000C9040101 @@ -442,11 +511,14 @@ Here is the interpretation of the fields: ``` 00400000 -> start address 0x00004000 -CD04 -> 0x4C9 pages of 512-bytes (= 612,5kB) +C904 -> 0x4C9 pages of 512-bytes (= 612.5kB) 01 -> bootable flag 01 -> image type for the application ``` +Please note the user can write additional partitions by writing +`image_dir_2/3/4` with the wanted configuration. + ### Writing the application DK6Programmer can be used for flashing the application: @@ -513,18 +585,50 @@ Build OTA image: 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|` +- `scripts/tools/nxp/ota/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. A user can enable the default processors by specifying -`chip_enable_ota_default_processors=1` in the build command. Please see more in -the [OTA image tool guide](../../../../../scripts/tools/nxp/ota/README.md). +factory data TLV value. + +A user can select which default processors to enable: + +- `chip_enable_ota_firmware_processor=1` to enable default firmware (app/SSBL) + update processor (enabled by default). +- `chip_enable_ota_factory_data_processor=1` to enable default factory data + update processor (disabled by default). + +The address for storing the custom OTA entry can also be specified: -Here is an example that generate an OTA image with factory data and app TLV: -`user@computer1:~/connectedhomeip$ : ./scripts/tools/nxp/ota/ota_image_tool.py create -v 0xDEAD -p 0xBEEF -vn 1 -vs "1.0" -da sha256 -fd --cert_declaration ~/manufacturing/Chip-Test-CD-1037-a220.der -app chip-k32w0x-contact-example.bin chip-k32w0x-contact-example.bin chip-k32w0x-contact-example.ota` +- `ota_custom_entry_address="0x000C1000"` is the default value, where + `0x000C1000` is the end address of the PDM area. PDM area ends at + `0x00100000` (top of external flash) and has a size of `63 * 4096` bytes. + The user should be aware of the external flash configuration and use an + address that does not overlap with anything else. + +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: + +``` +./scripts/tools/nxp/ota/ota_image_tool.py create -v 0xDEAD -p 0xBEEF -vn 42021 -vs "1.0" -da sha256 --app-input-file chip-k32w0x-light-example.bin chip-k32w0x-light-example.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` (`42020` by default), 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. Start the OTA Provider Application: @@ -533,13 +637,6 @@ user@computer1:~/connectedhomeip$ : rm -rf /tmp/chip_* user@computer1:~/connectedhomeip$ : ./out/ota-provider-app/chip-ota-provider-app -f chip-k32w0x-light-example.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. - Provision the OTA provider application and assign node id _1_. Also, grant ACL entries to allow OTA requestors: diff --git a/examples/lighting-app/nxp/k32w/k32w0/main/AppTask.cpp b/examples/lighting-app/nxp/k32w/k32w0/main/AppTask.cpp index 2298c42c4c395c..d2bdbe55c0d2cc 100644 --- a/examples/lighting-app/nxp/k32w/k32w0/main/AppTask.cpp +++ b/examples/lighting-app/nxp/k32w/k32w0/main/AppTask.cpp @@ -110,6 +110,15 @@ static BDXDownloader gDownloader; constexpr uint16_t requestedOtaBlockSize = 1024; #endif +#if CONFIG_CHIP_K32W0_REAL_FACTORY_DATA +CHIP_ERROR CustomFactoryDataRestoreMechanism(void) +{ + K32W_LOG("This is a custom factory data restore mechanism."); + + return CHIP_NO_ERROR; +} +#endif + CHIP_ERROR AppTask::StartAppTask() { CHIP_ERROR err = CHIP_NO_ERROR; @@ -125,6 +134,40 @@ CHIP_ERROR AppTask::StartAppTask() return err; } +#if CHIP_DEVICE_CONFIG_ENABLE_OTA_REQUESTOR +static void CheckOtaEntry() +{ + K32W_LOG("Current OTA_ENTRY_TOP_ADDR: 0x%x", OTA_ENTRY_TOP_ADDR); + + CustomOtaEntries_t ota_entries; + if (gOtaSuccess_c == OTA_GetCustomEntries(&ota_entries) && ota_entries.ota_state != otaNoImage) + { + if (ota_entries.ota_state == otaApplied) + { + K32W_LOG("OTA successfully applied"); +#if CONFIG_CHIP_K32W0_OTA_FACTORY_DATA_PROCESSOR + // If this point is reached, it means OTA_CommitCustomEntries was successfully called. + // Delete the factory data backup to stop doing a restore when the factory data provider + // is initialized. This ensures that both the factory data and app were updated, otherwise + // revert to the backed up factory data. + PDM_vDeleteDataRecord(kNvmId_FactoryDataBackup); +#endif + } + else + { + K32W_LOG("OTA failed with status %d", ota_entries.ota_state); + } + + // Clear the entry + OTA_ResetCustomEntries(); + } + else + { + K32W_LOG("Unable to access OTA entries structure"); + } +} +#endif + CHIP_ERROR AppTask::Init() { CHIP_ERROR err = CHIP_NO_ERROR; @@ -134,10 +177,15 @@ CHIP_ERROR AppTask::Init() // Init ZCL Data Model and start server PlatformMgr().ScheduleWork(InitServer, 0); -// Initialize device attestation config +#if CHIP_DEVICE_CONFIG_ENABLE_OTA_REQUESTOR + CheckOtaEntry(); +#endif + + // Initialize device attestation config #if CONFIG_CHIP_K32W0_REAL_FACTORY_DATA // Initialize factory data provider ReturnErrorOnFailure(AppTask::FactoryDataProvider::GetDefaultInstance().Init()); + AppTask::FactoryDataProvider::GetDefaultInstance().RegisterRestoreMechanism(CustomFactoryDataRestoreMechanism); SetDeviceInstanceInfoProvider(&AppTask::FactoryDataProvider::GetDefaultInstance()); SetDeviceAttestationCredentialsProvider(&AppTask::FactoryDataProvider::GetDefaultInstance()); SetCommissionableDataProvider(&AppTask::FactoryDataProvider::GetDefaultInstance()); diff --git a/examples/lock-app/nxp/k32w/k32w0/BUILD.gn b/examples/lock-app/nxp/k32w/k32w0/BUILD.gn index 70753788008300..7a18fc3eb14396 100644 --- a/examples/lock-app/nxp/k32w/k32w0/BUILD.gn +++ b/examples/lock-app/nxp/k32w/k32w0/BUILD.gn @@ -150,12 +150,22 @@ if (chip_pw_tokenizer_logging) { } } group("k32w0") { - deps = [ ":lock_app" ] + deps = [ + ":binsign", + ":lock_app", + ] if (chip_pw_tokenizer_logging) { deps += [ ":lock_app.database" ] } } +action("binsign") { + deps = [ ":lock_app" ] + script = "${k32w0_platform_dir}/scripts/sign-outdir.py" + output_name = "bignsign.log" + outputs = [ "${root_build_dir}/${output_name}" ] +} + group("default") { deps = [ ":k32w0" ] } diff --git a/examples/lock-app/nxp/k32w/k32w0/README.md b/examples/lock-app/nxp/k32w/k32w0/README.md index 3a2f027e447ab8..26ccf8fe663a11 100644 --- a/examples/lock-app/nxp/k32w/k32w0/README.md +++ b/examples/lock-app/nxp/k32w/k32w0/README.md @@ -173,20 +173,19 @@ In order to build the Project CHIP example, we recommend using a Linux distribution (the demo-application was compiled on Ubuntu 20.04). - Download - [K32W061DK6 SDK 2.6.10](https://cache.nxp.com/lgfiles/bsps/SDK_2_6_10_K32W061DK6.zip). + [K32W061DK6 SDK 2.6.11](https://cache.nxp.com/lgfiles/bsps/SDK_2_6_11_K32W061DK6.zip). - Start building the application either with Secure Element or without - without Secure Element ``` -user@ubuntu:~/Desktop/git/connectedhomeip$ export NXP_K32W0_SDK_ROOT=/home/user/Desktop/SDK_2_6_10_K32W061DK6/ +user@ubuntu:~/Desktop/git/connectedhomeip$ export NXP_K32W0_SDK_ROOT=/home/user/Desktop/SDK_2_6_11_K32W061DK6/ user@ubuntu:~/Desktop/git/connectedhomeip$ ./third_party/nxp/k32w0_sdk/sdk_fixes/patch_k32w_sdk.sh user@ubuntu:~/Desktop/git/connectedhomeip$ source ./scripts/activate.sh user@ubuntu:~/Desktop/git/connectedhomeip$ cd examples/lock-app/nxp/k32w/k32w0 user@ubuntu:~/Desktop/git/connectedhomeip/examples/lock-app/nxp/k32w/k32w0$ gn gen out/debug --args="k32w0_sdk_root=\"${NXP_K32W0_SDK_ROOT}\" chip_with_OM15082=1 chip_with_ot_cli=0 is_debug=false chip_crypto=\"platform\" chip_with_se05x=0 chip_pw_tokenizer_logging=true" user@ubuntu:~/Desktop/git/connectedhomeip/examples/lock-app/nxp/k32w/k32w0$ ninja -C out/debug -user@ubuntu:~/Desktop/git/connectedhomeip/examples/lock-app/nxp/k32w/k32w0$ $NXP_K32W0_SDK_ROOT/tools/imagetool/sign_images.sh out/debug/ ``` - with Secure element @@ -204,8 +203,8 @@ running oscillator as a clock source. In this case one must set the use_fro_32k argument to 1. In case signing errors are encountered when running the "sign_images.sh" script -install the recommanded packages (python version > 3, pip3, pycrypto, -pycryptodome): +(run automatically) install the recommanded packages (python version > 3, pip3, +pycrypto, pycryptodome): ``` user@ubuntu:~$ python3 --version @@ -224,6 +223,21 @@ The resulting output file can be found in out/debug/chip-k32w0x-lock-example. - When using Secure element and cross-compiling on Linux, log messages from the Plug&Trust middleware stack may not echo to the console. +## Rotating device id + +This is an optional feature and can be used in multiple ways (please see section +5.4.2.4.5 from Matter specification). One use case is Amazon Frustration Free +Setup, which leverages the C3 Characteristic (Additional commissioning-related +data) to offer an easier way to set up the device. The rotating device id will +be encoded in this additional data and is programmed to rotate at pre-defined +moments. The algorithm uses a unique per-device identifier that must be +programmed during factory provisioning. + +Please use the following build args: + +- `chip_enable_rotating_device_id=1` - to enable rotating device id. +- `chip_enable_additional_data_advertising=1` - to enable C3 characteristic. + ## Manufacturing data See diff --git a/examples/platform/nxp/k32w/k32w0/app/ldscripts/chip-k32w0x-linker.ld b/examples/platform/nxp/k32w/k32w0/app/ldscripts/chip-k32w0x-linker.ld index 82580235b3bdda..c7a2e4009865e5 100644 --- a/examples/platform/nxp/k32w/k32w0/app/ldscripts/chip-k32w0x-linker.ld +++ b/examples/platform/nxp/k32w/k32w0/app/ldscripts/chip-k32w0x-linker.ld @@ -31,6 +31,61 @@ * GCC linker script for K32W061/K32W041. */ +/******************* Map of K32W0 internal flash *********************************** + + 0x000A_0000 + - - - +---------------+ - - - - - - - - + | | + 8.5K | Flash config | + | RESERVED | 0x0009_DE00 + - - - +---------------+ - - - - - - - - + | | + 2K | Factory data | + | | 0x0009_D600 + - - - +---------------+ - - - - - - - - + | | + 1K | App metadata | + | | 0x0009_D200 + - - - +---------------+ - - - - - - - - + | | + 612.5K | Application | + | | 0x0000_4000 + - - - +---------------+ - - - - - - - - + | | + 8K | SSBL update | + | | 0x0000_2000 + - - - +---------------+ - - - - - - - - + | | + 8K | SSBL | + | | 0x0000_0000 + - - - +---------------+ - - - - - - - - + 0x0000_0000 + +* - If OTA is disabled, SSBL and SSBL updated region are not present. + - The only address range that changes is the application, which will span from + 0x0000_0000 to 0x0009_D200, having 628.5K max size. + *****************************************************************************/ + +/******************* Map of DK6 external flash *********************************** + + 0x0010_0000 + - - - +---------------+ - - - - - - - - + | | + 252K | PDM area | + | | 0x000C_1000 + - - - +---------------+ - - - - - - - - + | | + 4K | OTA entry | + | | 0x000C_0000 + - - - +---------------+ - - - - - - - - + | | + 768K | OTA area | + | | + - - - +---------------+ - - - - - - - - + 0x0000_0000 + + *****************************************************************************/ + OUTPUT_FORMAT ("elf32-littlearm", "elf32-bigarm", "elf32-littlearm") /* @@ -81,10 +136,11 @@ MEMORY } /* Define a symbol for the top of each memory region */ -__top_RAM1 = MEM_RAM1_BASE + MEM_RAM1_SIZE; /* 64K bytes */ +__top_RAM1 = MEM_RAM1_BASE + MEM_RAM1_SIZE; /* 64K bytes */ -/* To be improved. At this moment the second RAM bank is dedicated entirely to heap + stack. */ -HEAP_SIZE = DEFINED(HEAP_SIZE) ? HEAP_SIZE : 0x10000; +/* The second RAM bank is dedicated entirely to heap + stack. */ +HEAP_SIZE = MEM_RAM1_SIZE - STACK_SIZE; +ASSERT(((HEAP_SIZE + STACK_SIZE) == MEM_RAM1_SIZE), "Heap size + stack size should total RAM1 size."); /* set external flash properties - external flash is present on the DK6 board */ m_ext_flash_size = 0x00100000; @@ -376,9 +432,10 @@ SECTIONS __StackLimit = _vStackTop - STACK_SIZE; - __FACTORY_DATA_START = FACTORY_DATA_START_ADDRESS; - __FACTORY_DATA_SIZE = m_factory_data_size; + __FACTORY_DATA_START = FACTORY_DATA_START_ADDRESS; + __FACTORY_DATA_SIZE = m_factory_data_size; - ASSERT(((m_app_start + m_app_size + m_app_meta_data + m_factory_data_size + m_flash_config_size) <= m_int_flash_size), - "Internal flash capacity exceeded") + ASSERT(((m_app_start + m_app_size + m_app_meta_data + m_factory_data_size + m_flash_config_size) <= m_int_flash_size), + "Internal flash capacity exceeded") + } diff --git a/examples/platform/nxp/k32w/k32w0/app/project_include/OpenThreadConfig.h b/examples/platform/nxp/k32w/k32w0/app/project_include/OpenThreadConfig.h index 93f6d442baefe9..52cf1d2a738f90 100644 --- a/examples/platform/nxp/k32w/k32w0/app/project_include/OpenThreadConfig.h +++ b/examples/platform/nxp/k32w/k32w0/app/project_include/OpenThreadConfig.h @@ -37,8 +37,8 @@ #undef OPENTHREAD_CONFIG_TMF_ADDRESS_QUERY_MAX_RETRY_DELAY #define OPENTHREAD_CONFIG_TMF_ADDRESS_QUERY_MAX_RETRY_DELAY 120 // default is 28800 -#undef OPENTHREAD_CONFIG_MAC__DEFAULT_MAX_FRAME_RETRIES_DIRECT -#define OPENTHREAD_CONFIG_MAC__DEFAULT_MAX_FRAME_RETRIES_DIRECT 15 // default is 3 +#undef OPENTHREAD_CONFIG_MAC_DEFAULT_MAX_FRAME_RETRIES_DIRECT +#define OPENTHREAD_CONFIG_MAC_DEFAULT_MAX_FRAME_RETRIES_DIRECT 15 // default is 3 #undef OPENTHREAD_CONFIG_MAC_DEFAULT_MAX_FRAME_RETRIES_INDIRECT #define OPENTHREAD_CONFIG_MAC_DEFAULT_MAX_FRAME_RETRIES_INDIRECT 1 // default is 0 diff --git a/examples/platform/nxp/k32w/k32w0/doc/images/ssbl_multi_image.JPG b/examples/platform/nxp/k32w/k32w0/doc/images/ssbl_multi_image.JPG new file mode 100644 index 0000000000000000000000000000000000000000..31449b25cf269efbc1ba90ba7496ce0c94811fed GIT binary patch literal 114896 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

G^k2O*s2mrL0`Y)7PQbm=4?yBI0yR7;M{ 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

gOi$J=+rj#`=uYE6{J>CtMBGOUQuraQf`;L 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 literal 0 HcmV?d00001 diff --git a/examples/platform/nxp/k32w/k32w0/scripts/demo_generated_certs/dut1/out_dut1.bin b/examples/platform/nxp/k32w/k32w0/scripts/demo_generated_factory_data/demo_factory_data_dut1.bin similarity index 100% rename from examples/platform/nxp/k32w/k32w0/scripts/demo_generated_certs/dut1/out_dut1.bin rename to examples/platform/nxp/k32w/k32w0/scripts/demo_generated_factory_data/demo_factory_data_dut1.bin diff --git a/examples/platform/nxp/k32w/k32w0/scripts/demo_generated_certs/dut2/out_dut2.bin b/examples/platform/nxp/k32w/k32w0/scripts/demo_generated_factory_data/demo_factory_data_dut2.bin similarity index 100% rename from examples/platform/nxp/k32w/k32w0/scripts/demo_generated_certs/dut2/out_dut2.bin rename to examples/platform/nxp/k32w/k32w0/scripts/demo_generated_factory_data/demo_factory_data_dut2.bin diff --git a/examples/platform/nxp/k32w/k32w0/scripts/detokenizer.py b/examples/platform/nxp/k32w/k32w0/scripts/detokenizer.py index a4035d0655baa1..6f76903e97f8a5 100644 --- a/examples/platform/nxp/k32w/k32w0/scripts/detokenizer.py +++ b/examples/platform/nxp/k32w/k32w0/scripts/detokenizer.py @@ -77,17 +77,16 @@ def decode_serial(serialport, outfile, database): try: while (True): - if (input.in_waiting > 0): - # read line from serial port and ascii decode - line = input.readline().decode('ascii').strip() - # find token start and detokenize - idx = line.rfind(']') - dstr = decode_string(line[idx + 1:], detokenizer) - if dstr: - line = line[:idx+1] + dstr - print(line, file=sys.stdout) - if output: - print(line, file=output) + # read line from serial port and ascii decode + line = input.readline().decode('ascii').strip() + # find token start and detokenize + idx = line.rfind(']') + dstr = decode_string(line[idx + 1:], detokenizer) + if dstr: + line = line[:idx+1] + dstr + print(line, file=sys.stdout) + if output: + print(line, file=output) except Exception: print("Serial error or program closed", file=sys.stderr) diff --git a/examples/platform/nxp/k32w/k32w0/scripts/sign-outdir.py b/examples/platform/nxp/k32w/k32w0/scripts/sign-outdir.py new file mode 100644 index 00000000000000..0c4a0b34a9dc17 --- /dev/null +++ b/examples/platform/nxp/k32w/k32w0/scripts/sign-outdir.py @@ -0,0 +1,14 @@ +import os +import subprocess + +sign_images_path = os.environ["NXP_K32W0_SDK_ROOT"] + "/tools/imagetool/sign_images.sh" + +# Give execute permission if needed +if os.access(sign_images_path, os.X_OK) is False: + os.chmod(sign_images_path, 0o766) + +# Convert script to unix format if needed +subprocess.call("(file " + sign_images_path + " | grep CRLF > /dev/null) && (dos2unix " + sign_images_path + ")", shell=True) + +# Call sign_images.sh script with the output directory +subprocess.call(sign_images_path + " " + os.getcwd(), shell=True) diff --git a/examples/platform/nxp/k32w/k32w0/scripts/demo_generated_certs/dut1/Chip-DAC-NXP-1037-A220-Cert.der b/scripts/tools/nxp/demo_generated_certs/dac/dut1/Chip-DAC-NXP-1037-A220-Cert.der old mode 100755 new mode 100644 similarity index 100% rename from examples/platform/nxp/k32w/k32w0/scripts/demo_generated_certs/dut1/Chip-DAC-NXP-1037-A220-Cert.der rename to scripts/tools/nxp/demo_generated_certs/dac/dut1/Chip-DAC-NXP-1037-A220-Cert.der diff --git a/examples/platform/nxp/k32w/k32w0/scripts/demo_generated_certs/dut1/Chip-DAC-NXP-1037-A220-Cert.pem b/scripts/tools/nxp/demo_generated_certs/dac/dut1/Chip-DAC-NXP-1037-A220-Cert.pem old mode 100755 new mode 100644 similarity index 100% rename from examples/platform/nxp/k32w/k32w0/scripts/demo_generated_certs/dut1/Chip-DAC-NXP-1037-A220-Cert.pem rename to scripts/tools/nxp/demo_generated_certs/dac/dut1/Chip-DAC-NXP-1037-A220-Cert.pem diff --git a/examples/platform/nxp/k32w/k32w0/scripts/demo_generated_certs/dut1/Chip-DAC-NXP-1037-A220-Key.der b/scripts/tools/nxp/demo_generated_certs/dac/dut1/Chip-DAC-NXP-1037-A220-Key.der old mode 100755 new mode 100644 similarity index 100% rename from examples/platform/nxp/k32w/k32w0/scripts/demo_generated_certs/dut1/Chip-DAC-NXP-1037-A220-Key.der rename to scripts/tools/nxp/demo_generated_certs/dac/dut1/Chip-DAC-NXP-1037-A220-Key.der diff --git a/examples/platform/nxp/k32w/k32w0/scripts/demo_generated_certs/dut1/Chip-DAC-NXP-1037-A220-Key.pem b/scripts/tools/nxp/demo_generated_certs/dac/dut1/Chip-DAC-NXP-1037-A220-Key.pem old mode 100755 new mode 100644 similarity index 100% rename from examples/platform/nxp/k32w/k32w0/scripts/demo_generated_certs/dut1/Chip-DAC-NXP-1037-A220-Key.pem rename to scripts/tools/nxp/demo_generated_certs/dac/dut1/Chip-DAC-NXP-1037-A220-Key.pem diff --git a/examples/platform/nxp/k32w/k32w0/scripts/demo_generated_certs/dut2/Chip-DAC-NXP-1037-A220-Cert.der b/scripts/tools/nxp/demo_generated_certs/dac/dut2/Chip-DAC-NXP-1037-A220-Cert.der old mode 100755 new mode 100644 similarity index 100% rename from examples/platform/nxp/k32w/k32w0/scripts/demo_generated_certs/dut2/Chip-DAC-NXP-1037-A220-Cert.der rename to scripts/tools/nxp/demo_generated_certs/dac/dut2/Chip-DAC-NXP-1037-A220-Cert.der diff --git a/examples/platform/nxp/k32w/k32w0/scripts/demo_generated_certs/dut2/Chip-DAC-NXP-1037-A220-Cert.pem b/scripts/tools/nxp/demo_generated_certs/dac/dut2/Chip-DAC-NXP-1037-A220-Cert.pem old mode 100755 new mode 100644 similarity index 100% rename from examples/platform/nxp/k32w/k32w0/scripts/demo_generated_certs/dut2/Chip-DAC-NXP-1037-A220-Cert.pem rename to scripts/tools/nxp/demo_generated_certs/dac/dut2/Chip-DAC-NXP-1037-A220-Cert.pem diff --git a/examples/platform/nxp/k32w/k32w0/scripts/demo_generated_certs/dut2/Chip-DAC-NXP-1037-A220-Key.der b/scripts/tools/nxp/demo_generated_certs/dac/dut2/Chip-DAC-NXP-1037-A220-Key.der old mode 100755 new mode 100644 similarity index 100% rename from examples/platform/nxp/k32w/k32w0/scripts/demo_generated_certs/dut2/Chip-DAC-NXP-1037-A220-Key.der rename to scripts/tools/nxp/demo_generated_certs/dac/dut2/Chip-DAC-NXP-1037-A220-Key.der diff --git a/examples/platform/nxp/k32w/k32w0/scripts/demo_generated_certs/dut2/Chip-DAC-NXP-1037-A220-Key.pem b/scripts/tools/nxp/demo_generated_certs/dac/dut2/Chip-DAC-NXP-1037-A220-Key.pem old mode 100755 new mode 100644 similarity index 100% rename from examples/platform/nxp/k32w/k32w0/scripts/demo_generated_certs/dut2/Chip-DAC-NXP-1037-A220-Key.pem rename to scripts/tools/nxp/demo_generated_certs/dac/dut2/Chip-DAC-NXP-1037-A220-Key.pem diff --git a/examples/platform/nxp/k32w/k32w0/scripts/demo_generated_certs/paa/Chip-PAA-NXP-Cert.der b/scripts/tools/nxp/demo_generated_certs/paa/Chip-PAA-NXP-Cert.der old mode 100755 new mode 100644 similarity index 100% rename from examples/platform/nxp/k32w/k32w0/scripts/demo_generated_certs/paa/Chip-PAA-NXP-Cert.der rename to scripts/tools/nxp/demo_generated_certs/paa/Chip-PAA-NXP-Cert.der diff --git a/examples/platform/nxp/k32w/k32w0/scripts/demo_generated_certs/paa/Chip-PAA-NXP-Cert.pem b/scripts/tools/nxp/demo_generated_certs/paa/Chip-PAA-NXP-Cert.pem old mode 100755 new mode 100644 similarity index 100% rename from examples/platform/nxp/k32w/k32w0/scripts/demo_generated_certs/paa/Chip-PAA-NXP-Cert.pem rename to scripts/tools/nxp/demo_generated_certs/paa/Chip-PAA-NXP-Cert.pem diff --git a/examples/platform/nxp/k32w/k32w0/scripts/demo_generated_certs/paa/Chip-PAA-NXP-Key.pem b/scripts/tools/nxp/demo_generated_certs/paa/Chip-PAA-NXP-Key.pem old mode 100755 new mode 100644 similarity index 100% rename from examples/platform/nxp/k32w/k32w0/scripts/demo_generated_certs/paa/Chip-PAA-NXP-Key.pem rename to scripts/tools/nxp/demo_generated_certs/paa/Chip-PAA-NXP-Key.pem diff --git a/examples/platform/nxp/k32w/k32w0/scripts/demo_generated_certs/pai/Chip-PAI-NXP-1037-A220-Cert.der b/scripts/tools/nxp/demo_generated_certs/pai/Chip-PAI-NXP-1037-A220-Cert.der old mode 100755 new mode 100644 similarity index 100% rename from examples/platform/nxp/k32w/k32w0/scripts/demo_generated_certs/pai/Chip-PAI-NXP-1037-A220-Cert.der rename to scripts/tools/nxp/demo_generated_certs/pai/Chip-PAI-NXP-1037-A220-Cert.der diff --git a/examples/platform/nxp/k32w/k32w0/scripts/demo_generated_certs/pai/Chip-PAI-NXP-1037-A220-Cert.pem b/scripts/tools/nxp/demo_generated_certs/pai/Chip-PAI-NXP-1037-A220-Cert.pem old mode 100755 new mode 100644 similarity index 100% rename from examples/platform/nxp/k32w/k32w0/scripts/demo_generated_certs/pai/Chip-PAI-NXP-1037-A220-Cert.pem rename to scripts/tools/nxp/demo_generated_certs/pai/Chip-PAI-NXP-1037-A220-Cert.pem diff --git a/examples/platform/nxp/k32w/k32w0/scripts/demo_generated_certs/pai/Chip-PAI-NXP-1037-A220-Key.pem b/scripts/tools/nxp/demo_generated_certs/pai/Chip-PAI-NXP-1037-A220-Key.pem old mode 100755 new mode 100644 similarity index 100% rename from examples/platform/nxp/k32w/k32w0/scripts/demo_generated_certs/pai/Chip-PAI-NXP-1037-A220-Key.pem rename to scripts/tools/nxp/demo_generated_certs/pai/Chip-PAI-NXP-1037-A220-Key.pem diff --git a/examples/platform/nxp/k32w/k32w0/scripts/generate_cert.sh b/scripts/tools/nxp/generate_cert.sh similarity index 64% rename from examples/platform/nxp/k32w/k32w0/scripts/generate_cert.sh rename to scripts/tools/nxp/generate_cert.sh index 172662ed192496..e7f93f2b79be93 100755 --- a/examples/platform/nxp/k32w/k32w0/scripts/generate_cert.sh +++ b/scripts/tools/nxp/generate_cert.sh @@ -33,23 +33,49 @@ function exit_err() { exit 1 } -DATE="2022-06-21 12:35:00" -LIFETIME="7305" +if [ -z "$DATE" ]; then + DATE="2023-01-19" +fi + +if [ -z "$TIME" ]; then + TIME="10:17:00" +fi + +if [ -z "$LIFETIME" ]; then + LIFETIME="7305" +fi + +if [ -z "$VID" ]; then + VID="1037" +fi + +if [ -z "$PID" ]; then + PID="A220" +fi -PAA_DATE="$DATE" +PAA_DATE="$DATE $TIME" PAA_LIFETIME="$LIFETIME" -PAA_CERT="Chip-PAA-NXP-Cert.pem" -PAA_KEY="Chip-PAA-NXP-Key.pem" -PAI_DATE="$DATE" +# Generate a new PAA only if PAA cert and key paths were not both specified. +if [[ -n "$PAA_CERT" && -n "$PAA_KEY" ]]; then + echo "A PAA was provided. Will not generate a new one." + GENERATE_PAA=false +else + GENERATE_PAA=true + PAA_CERT="Chip-PAA-NXP-Cert.pem" + PAA_CERT_DER="Chip-PAA-NXP-Cert.der" + PAA_KEY="Chip-PAA-NXP-Key.pem" +fi + +PAI_DATE="$PAA_DATE" PAI_LIFETIME="$LIFETIME" -PAI_VID="1037" -PAI_PID="A220" +PAI_VID="$VID" +PAI_PID="$PID" PAI_CERT="Chip-PAI-NXP-"$PAI_VID"-"$PAI_PID"-Cert.pem" PAI_CERT_DER="Chip-PAI-NXP-"$PAI_VID"-"$PAI_PID"-Cert.der" PAI_KEY="Chip-PAI-NXP-"$PAI_VID"-"$PAI_PID"-Key.pem" -DAC_DATE="$DATE" +DAC_DATE="$PAA_DATE" DAC_LIFETIME="$LIFETIME" DAC_VID="$PAI_VID" DAC_PID="$PAI_PID" @@ -59,13 +85,19 @@ DAC_KEY="Chip-DAC-NXP-"$DAC_VID"-"$DAC_PID"-Key.pem" DAC_KEY_DER="Chip-DAC-NXP-"$DAC_VID"-"$DAC_PID"-Key.der" # Remove certificates if present -rm -rf "$PAA_CERT" "$PAA_KEY" "$PAI_CERT" "$PAI_KEY" "$DAC_CERT" "$DAC_KEY" "$PAI_CERT_DER" "$DAC_CERT_DER" "$DAC_KEY_DER" >/dev/null 2>&1 +if [ "$GENERATE_PAA" = true ]; then + rm -rf "$PAA_CERT" "$PAA_KEY" "$PAA_CERT_DER" >/dev/null 2>&1 +fi + +rm -rf "$PAI_CERT" "$PAI_KEY" "$DAC_CERT" "$DAC_KEY" "$PAI_CERT_DER" "$DAC_CERT_DER" "$DAC_KEY_DER" >/dev/null 2>&1 # Generate certificates echo "Generate certificates" # PAA (root authoritity) -"$CHIP_CERT_TOOL" gen-att-cert --type a --subject-cn "Matter Development PAA NXP" --valid-from "$PAA_DATE" --lifetime "$PAA_LIFETIME" --out-key "$PAA_KEY" --out "$PAA_CERT" && echo "Generated PAA" || exit_err "Failed to generate PAA" +if [ "$GENERATE_PAA" = true ]; then + "$CHIP_CERT_TOOL" gen-att-cert --type a --subject-cn "Matter Development PAA NXP" --valid-from "$PAA_DATE" --lifetime "$PAA_LIFETIME" --out-key "$PAA_KEY" --out "$PAA_CERT" && echo "Generated PAA" || exit_err "Failed to generate PAA" +fi # PAI (vendor) "$CHIP_CERT_TOOL" gen-att-cert --type i --subject-cn "Matter Development PAI NXP" --subject-vid "$PAI_VID" --valid-from "$PAI_DATE" --lifetime "$PAI_LIFETIME" --ca-key "$PAA_KEY" --ca-cert "$PAA_CERT" --out-key "$PAI_KEY" --out "$PAI_CERT" && echo "Generated PAI" || exit_err "Failed to generate PAI" @@ -76,6 +108,11 @@ echo "Generate certificates" # Convert certificates and keys to der format (binary x509) echo "Convert certificates and keys to DER format" +# PAA +if [ "$GENERATE_PAA" = true ]; then + "$CHIP_CERT_TOOL" convert-cert -d "$PAA_CERT" "$PAA_CERT_DER" && echo "Converted PAA" || exit_err "Failed to convert PAA" +fi + # PAI "$CHIP_CERT_TOOL" convert-cert -d "$PAI_CERT" "$PAI_CERT_DER" && echo "Converted PAI" || exit_err "Failed to convert PAI" @@ -84,3 +121,8 @@ echo "Convert certificates and keys to DER format" # DAC Key "$CHIP_CERT_TOOL" convert-key -d "$DAC_KEY" "$DAC_KEY_DER" && echo "Converted DAC Key" || exit_err "Failed to convert DAC Key" + +if [ -n "$FACTORY_DATA_DEST" ]; then + echo "Moving certificates to $FACTORY_DATA_DEST" + mv Chip-* "$FACTORY_DATA_DEST" +fi diff --git a/scripts/tools/nxp/ota/README.md b/scripts/tools/nxp/ota/README.md index e1848a007f3441..45766b1db8d44f 100644 --- a/scripts/tools/nxp/ota/README.md +++ b/scripts/tools/nxp/ota/README.md @@ -7,121 +7,83 @@ orphan: true ## Overview This tool can generate an OTA image in the `|OTA standard header|TLV1|...|TLVn|` -format. The payload contains data in standard TLV format (not Matter TLV format. -During OTA transfer, these TLV can span across multiple BDX blocks, thus the -`OTAImageProcessorImpl` instance should take this into account. +format. The payload contains data in standard TLV format (not Matter TLV +format). During OTA transfer, these TLV can span across multiple BDX blocks, +thus the `OTAImageProcessorImpl` instance should take this into account. -Each TLV will be processed by its associated processor, pre-registered in -`OTAImageProcessorImpl` and identified by the TLV tag. If a processor cannot be -found for current decoded tag, the OTA transfer will be canceled. +## Supported platforms -An application is able to define its own processors, thus enabling extending the -default OTA functionality. The application can also opt to disable the default -processors (application, bootloader and factory data) by setting -`chip_enable_ota_default_processors=0`. +- K32W0 - + [K32W OTA README](../../../../src/platform/nxp/k32w/common/K32W_OTA_README.md) ## Usage -TODO: add more options - -Example: - -``` -python3 ./scripts/tools/nxp/ota/ota_image_tool.py create -v 0xDEAD -p 0xBEEF -vn 50000 -vs "1.0" -da sha256 -fd --cert_declaration $FACTORY_DATA_DEST/Chip-Test-CD-$VID-$PID.der --dac_cert $FACTORY_DATA_DEST/Chip-DAC-NXP-$VID-$PID-Cert.der --dac_key $FACTORY_DATA_DEST/Chip-DAC-NXP-$VID-$PID-Key.der --pai_cert $FACTORY_DATA_DEST/Chip-PAI-NXP-$VID-$PID-Cert.der -app ~/binaries/ota_update/chip-k32w0x-light-example-50000.bin --app-version 50000 --app-version-str "50000_test" --app-build-date "$DATE" ~/binaries/ota_update/chip-k32w0x-light-example-50000.bin $FACTORY_DATA_DEST/chip-k32w0x-light-example-50000.ota -``` - -Example (only factory data update): +This is a wrapper over standard `ota_image_tool.py`, so the options for `create` +are also available here: ``` -python3 ./scripts/tools/nxp/ota/ota_image_tool.py create -v 0xDEAD -p 0xBEEF -vn 50000 -vs "1.0" -da sha256 -fd --cert_declaration $FACTORY_DATA_DEST/Chip-Test-CD-$VID-$PID.der --dac_cert $FACTORY_DATA_DEST/Chip-DAC-NXP-$VID-$PID-Cert.der --dac_key $FACTORY_DATA_DEST/Chip-DAC-NXP-$VID-$PID-Key.der --pai_cert $FACTORY_DATA_DEST/Chip-PAI-NXP-$VID-$PID-Cert.der $FACTORY_DATA_DEST/chip-k32w0x-light-example-50000.ota +python3 ./scripts/tools/nxp/ota/ota_image_tool.py create -v 0xDEAD -p 0xBEEF -vn 50000 -vs "1.0" -da sha256 ``` -## Default processors - -The default processors for K32W0 are already implemented in: +followed by \*_custom options_- and a positional argument (should be last) that +specifies the output file. Please see the `create_ota_images.sh` for some +reference commands. -- `OTAApplicationProcessor` for application update. -- TODO: `OTABootloaderProcessor` for SSBL update. -- `OTAFactoryDataProcessor` for factory data update. - -## Implementing custom processors - -A custom processor should implement the interface defined by the -`OTATlvProcessor` abstract interface (simplified version; see `OTATlvHeader.h` -for full version): +The list of **custom options**: ``` -class OTATlvProcessor -{ -public: - virtual CHIP_ERROR Init() = 0; - virtual CHIP_ERROR Clear() = 0; - virtual CHIP_ERROR ApplyAction() = 0; - virtual CHIP_ERROR AbortAction() = 0; - - CHIP_ERROR Process(ByteSpan & block); -protected: - virtual CHIP_ERROR ProcessInternal(ByteSpan & block) = 0; -}; - +# Application options +--app-input-file --> Path to the application binary. +--app-version --> Application version. It's part of the descriptor and + can be different than the OTA image header version: -vn. +--app-version-str --> Application version string. Same as above. +--app-build-date --> Application build date. Same as above. + +# SSBL options +--bl-input-file --> Path to the SSBL binary. +--bl-version --> SSBL version. +--bl-version-str --> SSBL version string. +--bl-build-date --> SSBL build date. + +# Factory data options +--factory-data --> If set, enables the generation of factory data. +--cert_declaration --> Certification Declaration. +--dac_cert --> DAC certificate. +--dac_key --> DAC private key. +--pai_cert --> PAI certificate. + +# Custom TLV options +--json --> Path to a JSON file following ota_payload.schema ``` -Note that `ProcessInternal` should return: - -- `CHIP_NO_ERROR` if block was processed successfully. -- `CHIP_ERROR_BUFFER_TOO_SMALL` if current block doesn't contain all necessary - data. This can happen when a TLV value field has a header, but it is split - across two blocks. -- `CHIP_OTA_FETCH_ALREADY_SCHEDULED` if block was processed successfully and - the fetching is already scheduled by the processor. This happens in the - default application processor, because the next data fetching is scheduled - through a callback (called when enough external flash was erased). - -`Process` is the public API that is used inside `OTAImageProcessorImpl` for data -processing. This is a wrapper over `ProcessInternal`, which can return -`CHIP_OTA_CHANGE_PROCESSOR` to notify a new processor should be selected for the -remaining data. - -Furthermore, a processor can use an instance of `OTADataAccumulator` to to -accumulate data until a given threshold. This is useful when a custom payload -contains metadata that need parsing: accumulate data until the threshold is -reached or return `CHIP_ERROR_BUFFER_TOO_SMALL` to signal -`OTAImageProcessorImpl` more data is needed. - -``` -/** - * This class can be used to accumulate data until a given threshold. - * Should be used by OTATlvProcessor derived classes if they need - * metadata accumulation (e.g. for custom header decoding). - */ -class OTADataAccumulator -{ -public: - void Init(uint32_t threshold); - void Clear(); - CHIP_ERROR Accumulate(ByteSpan & block); - - inline uint8_t* data() { return mBuffer.Get(); } - -private: - uint32_t mThreshold; - uint32_t mBufferOffset; - Platform::ScopedMemoryBuffer mBuffer; -}; -``` - -## Factory data update - -`DAC`, `PAI` and `CD` can be updated at a later time by creating a factory data -update OTA image. If the `PAA` changes, make sure to generate the new -certificates using the new `PAA` (which is only used by the controller, e.g. -`chip-tool`). Please see the -[manufacturing flow guide](../../../../docs/guides/nxp_manufacturing_flow.md) -for generating new certificates. - -Example of OTA image generation with factory data and application update (using -env variables set in the prerequisites of manufacturing flow): - -``` -python3 ./scripts/tools/nxp/ota/ota_image_tool.py create -v 0xDEAD -p 0xBEEF -vn 50000 -vs "1.0" -da sha256 -fd --cert_declaration $FACTORY_DATA_DEST/Chip-Test-CD-$VID-$PID.der --dac_cert $FACTORY_DATA_DEST/Chip-DAC-NXP-$VID-$PID-Cert.der --dac_key $FACTORY_DATA_DEST/Chip-DAC-NXP-$VID-$PID-Key.der --pai_cert $FACTORY_DATA_DEST/Chip-PAI-NXP-$VID-$PID-Cert.der -app $FACTORY_DATA_DEST/chip-k32w0x-light-example-50000.bin --app-version 50000 --app-version-str "50000_test" --app-build-date "$DATE" $FACTORY_DATA_DEST/chip-k32w0x-light-example-50000.bin $FACTORY_DATA_DEST/chip-k32w0x-light-example-50000.ota -``` +Please note that the options above are separated into four categories: +application, bootloader, factory data and custom TLV (`--json` option). If no +descriptor options are specified for app/SSBL, the script will use the default +values (`50000`, `"50000-default"`, `"2023-01-01"`). The descriptor feature is +optional, TLV processors having the option to register a callback for descriptor +processing. + +## Custom payload + +When defining a custom processor, a user is able to also specify the custom +format of the TLV by creating a JSON file based on the `ota_payload.schema`. The +tool offers support for describing multiple TLV in the same JSON file. Please +see the `examples/ota_max_entries_example.json` for a multi-app + SSBL example. +Option `--json` must be used to specify the path to the JSON file. + +## Examples + +A set of examples can be found in `./examples`. Please run `create_ota_image.sh` +to generate the examples: + +- Application image with default descriptor +- Application image with specified descriptor +- Factory data image +- SSBL image +- Application + SSBL + factory data image +- Maximum number of entries image, using `ota_max_entries_example.json`. The + examples uses 8 SSBL binaries because they have a small size and fit in + external flash. + +The binaries from `./examples/binaries` should only be used only as an example. +The user should provide their own binaries when generating the OTA image. diff --git a/scripts/tools/nxp/ota/examples/binaries/ssbl_ext_flash_ota_entry_example.bin b/scripts/tools/nxp/ota/examples/binaries/ssbl_ext_flash_ota_entry_example.bin new file mode 100755 index 0000000000000000000000000000000000000000..5800a9860f70aae4d8f235a4371b31c6b77a498f GIT binary patch literal 7056 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 aOX=pJ+=kw7SL#0dc~SP73%^%zf&UFmw4W~k literal 0 HcmV?d00001 diff --git a/scripts/tools/nxp/ota/examples/binaries/ssbl_ram_ota_entry_example.bin b/scripts/tools/nxp/ota/examples/binaries/ssbl_ram_ota_entry_example.bin new file mode 100755 index 0000000000000000000000000000000000000000..9a5bc3ac60b97083c107f26061620d3c10c32b96 GIT binary patch literal 6768 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 G5B~?l&D%!+ literal 0 HcmV?d00001 diff --git a/scripts/tools/nxp/ota/examples/create_ota_images.sh b/scripts/tools/nxp/ota/examples/create_ota_images.sh new file mode 100644 index 00000000000000..36b100376de058 --- /dev/null +++ b/scripts/tools/nxp/ota/examples/create_ota_images.sh @@ -0,0 +1,119 @@ +#!/usr/bin/bash + +ROOT=../../../../../ +CHIP_CERT=$ROOT/src/tools/chip-cert +SPAKE2P=$ROOT/src/tools/spake2p + +# Prerequisites +if [ ! -d "$CHIP_CERT/out" ]; then + printf "chip-cert is not available. Compile it.\n" + cd "$CHIP_CERT" + gn gen out + ninja -C out + cd - +else + printf "chip-cert is available.\n" +fi + +if [ ! -d "$SPAKE2P/out" ]; then + printf "spake2p is not available. Compile it.\n" + cd "$SPAKE2P" + gn gen out + ninja -C out + cd - +else + printf "spake2p is available.\n" +fi + +VERSION=50000 +export FACTORY_DATA_DEST=./out/factory_data +export DEVICE_TYPE=100 +export DATE="2023-01-01" +export TIME="$(date +"%T")" +export LIFETIME="7305" +export VID="1037" +export PID="A220" +export PAA_CERT=../../demo_generated_certs/paa/Chip-PAA-NXP-Cert.pem +export PAA_KEY=../../demo_generated_certs/paa/Chip-PAA-NXP-Key.pem + +if [ ! -d "./out" ]; then + mkdir "./out" +fi + +if [ ! -d "$FACTORY_DATA_DEST" ]; then + mkdir "$FACTORY_DATA_DEST" +fi + +printf "Generate new certificates based on the same PAA\n" +../../generate_cert.sh "$CHIP_CERT"/out/chip-cert + +printf "Generate new Certification Declaration\n" +"$CHIP_CERT"/out/chip-cert gen-cd \ + --key "$ROOT"/credentials/test/certification-declaration/Chip-Test-CD-Signing-Key.pem \ + --cert "$ROOT"/credentials/test/certification-declaration/Chip-Test-CD-Signing-Cert.pem \ + --out "$FACTORY_DATA_DEST/Chip-Test-CD-$VID-$PID".der \ + --format-version 1 \ + --vendor-id "0x$VID" \ + --product-id "0x$PID" \ + --device-type-id "0x$DEVICE_TYPE" \ + --certificate-id "ZIG20142ZB330003-24" \ + --security-level 0 \ + --security-info 0 \ + --version-number 9876 \ + --certification-type 1 + +printf "\nExample: command without input option specified\n" +python3 ../ota_image_tool.py create -v 0xDEAD -p 0xBEEF -vn "$VERSION" -vs "1.0" -da sha256 \ + ./out/app-standard-example-50000.ota || printf "Command failed because no option was specified.\n" + +printf "\nExample: generate app OTA image with default descriptor\n" +python3 ../ota_image_tool.py create -v 0xDEAD -p 0xBEEF -vn "$VERSION" -vs "1.0" -da sha256 \ + --app-input-file ./binaries/app_example.bin \ + ./out/app-standard-example-50000.ota + +printf "\nExample: generate app OTA image with specified descriptor\n" +python3 ../ota_image_tool.py create -v 0xDEAD -p 0xBEEF -vn "$VERSION" -vs "1.0" -da sha256 \ + --app-input-file ./binaries/app_example.bin \ + --app-version 50000 \ + --app-version-str "50000-version" \ + --app-build-date "$(date +\"%F\")" \ + ./out/app-with-descriptor-example-50000.ota + +printf "\nExample: generate factory data OTA image\n" +python3 ../ota_image_tool.py create -v 0xDEAD -p 0xBEEF -vn "$VERSION" -vs "1.0" -da sha256 \ + -fd \ + --cert_declaration "$FACTORY_DATA_DEST/Chip-Test-CD-$VID-$PID".der \ + --dac_cert "$FACTORY_DATA_DEST/Chip-DAC-NXP-$VID-$PID"-Cert.der \ + --dac_key "$FACTORY_DATA_DEST/Chip-DAC-NXP-$VID-$PID"-Key.der \ + --pai_cert "$FACTORY_DATA_DEST/Chip-PAI-NXP-$VID-$PID"-Cert.der \ + ./out/factory-data-example-50000.ota + +printf "\nExample: generate SSBL OTA image with specified descriptor\n" +python3 ../ota_image_tool.py create -v 0xDEAD -p 0xBEEF -vn 1 -vs "1.0" -da sha256 \ + --bl-input-file ./binaries/ssbl_ram_ota_entry_example.bin \ + --bl-version 1 \ + --bl-version-str "SSBL-version-1" \ + --bl-build-date "$(date +\"%F\")" \ + ./out/ssbl-with-descriptor-example.ota + +printf "\nExample: generate app + SSBL + factory data update OTA image\n" +python3 ../ota_image_tool.py create -v 0xDEAD -p 0xBEEF -vn 1 -vs "1.0" -da sha256 \ + --app-input-file ./binaries/app_example.bin \ + --app-version 50000 \ + --app-version-str "50000-version" \ + --app-build-date "$(date +\"%F\")" \ + --bl-input-file ./binaries/ssbl_ram_ota_entry_example.bin \ + --bl-version 1 \ + --bl-version-str "SSBL-version-1" \ + --bl-build-date "$(date +\"%F\")" \ + -fd \ + --cert_declaration "$FACTORY_DATA_DEST/Chip-Test-CD-$VID-$PID".der \ + --dac_cert "$FACTORY_DATA_DEST/Chip-DAC-NXP-$VID-$PID"-Cert.der \ + --dac_key "$FACTORY_DATA_DEST/Chip-DAC-NXP-$VID-$PID"-Key.der \ + --pai_cert "$FACTORY_DATA_DEST/Chip-PAI-NXP-$VID-$PID"-Cert.der \ + ./out/app-ssbl-factory-data-example.ota + +printf "\nExample: generate OTA image for maximum number of entries (8) using JSON\n" +python3 ../ota_image_tool.py create -v 0xDEAD -p 0xBEEF -vn 50000 -vs "1.0" -da sha256 \ + --json ./ota_max_entries_example.json \ + ./out/max-entries-example.ota diff --git a/scripts/tools/nxp/ota/examples/ota_max_entries_example.json b/scripts/tools/nxp/ota/examples/ota_max_entries_example.json new file mode 100644 index 00000000000000..d5ba8ff8e80964 --- /dev/null +++ b/scripts/tools/nxp/ota/examples/ota_max_entries_example.json @@ -0,0 +1,172 @@ +{ + "inputs": [ + { + "tag": 4, + "descriptor": [ + { + "name": "ssbl_version", + "length": 4, + "value": 1003 + }, + { + "name": "ssbl_version_str", + "length": 64, + "value": "SsblTestVersion" + }, + { + "name": "ssbl_build_date", + "length": 64, + "value": "2023-02-13" + } + ], + "path": "./binaries/ssbl_ext_flash_ota_entry_example.bin" + }, + { + "tag": 5, + "descriptor": [ + { + "name": "ssbl_version", + "length": 4, + "value": 1004 + }, + { + "name": "ssbl_version_str", + "length": 64, + "value": "SsblTestVersion" + }, + { + "name": "ssbl_build_date", + "length": 64, + "value": "2023-02-13" + } + ], + "path": "./binaries/ssbl_ext_flash_ota_entry_example.bin" + }, + { + "tag": 6, + "descriptor": [ + { + "name": "ssbl_version", + "length": 4, + "value": 1005 + }, + { + "name": "ssbl_version_str", + "length": 64, + "value": "SsblTestVersion" + }, + { + "name": "ssbl_build_date", + "length": 64, + "value": "2023-02-13" + } + ], + "path": "./binaries/ssbl_ext_flash_ota_entry_example.bin" + }, + { + "tag": 7, + "descriptor": [ + { + "name": "ssbl_version", + "length": 4, + "value": 1006 + }, + { + "name": "ssbl_version_str", + "length": 64, + "value": "SsblTestVersion" + }, + { + "name": "ssbl_build_date", + "length": 64, + "value": "2023-02-13" + } + ], + "path": "./binaries/ssbl_ext_flash_ota_entry_example.bin" + }, + { + "tag": 8, + "descriptor": [ + { + "name": "ssbl_version", + "length": 4, + "value": 1007 + }, + { + "name": "ssbl_version_str", + "length": 64, + "value": "SsblTestVersion" + }, + { + "name": "ssbl_build_date", + "length": 64, + "value": "2023-02-13" + } + ], + "path": "./binaries/ssbl_ext_flash_ota_entry_example.bin" + }, + { + "tag": 9, + "descriptor": [ + { + "name": "ssbl_version", + "length": 4, + "value": 1008 + }, + { + "name": "ssbl_version_str", + "length": 64, + "value": "SsblTestVersion" + }, + { + "name": "ssbl_build_date", + "length": 64, + "value": "2023-02-13" + } + ], + "path": "./binaries/ssbl_ext_flash_ota_entry_example.bin" + }, + { + "tag": 10, + "descriptor": [ + { + "name": "ssbl_version", + "length": 4, + "value": 1009 + }, + { + "name": "ssbl_version_str", + "length": 64, + "value": "SsblTestVersion" + }, + { + "name": "ssbl_build_date", + "length": 64, + "value": "2023-02-13" + } + ], + "path": "./binaries/ssbl_ext_flash_ota_entry_example.bin" + }, + { + "tag": 11, + "descriptor": [ + { + "name": "ssbl_version", + "length": 4, + "value": 1010 + }, + { + "name": "ssbl_version_str", + "length": 64, + "value": "SsblTestVersion" + }, + { + "name": "ssbl_build_date", + "length": 64, + "value": "2023-02-13" + } + ], + "path": "./binaries/ssbl_ext_flash_ota_entry_example.bin" + } + ] +} diff --git a/scripts/tools/nxp/ota/ota_image_tool.py b/scripts/tools/nxp/ota/ota_image_tool.py index 13b1895fac6acf..bf796f1010119f 100755 --- a/scripts/tools/nxp/ota/ota_image_tool.py +++ b/scripts/tools/nxp/ota/ota_image_tool.py @@ -30,15 +30,13 @@ ''' import argparse +import glob +import json import logging import os import sys -import ota_image_tool -from chip.tlv import TLVWriter -from custom import CertDeclaration, DacCert, DacPKey, PaiCert -from default import InputArgument -from generate import set_logger +import jsonschema sys.path.insert(0, os.path.join( os.path.dirname(__file__), '../factory_data_generator')) @@ -47,9 +45,15 @@ sys.path.insert(0, os.path.join( os.path.dirname(__file__), '../../../../src/app/')) +import ota_image_tool # noqa: E402 isort:skip +from chip.tlv import TLVWriter # noqa: E402 isort:skip +from custom import CertDeclaration, DacCert, DacPKey, PaiCert # noqa: E402 isort:skip +from default import InputArgument # noqa: E402 isort:skip +from generate import set_logger # noqa: E402 isort:skip -OTA_FACTORY_TLV_TEMP = os.path.join(os.path.dirname(__file__), "ota_factory_tlv_temp.bin") -OTA_APP_TLV_TEMP = os.path.join(os.path.dirname(__file__), "ota_app_tlv_temp.bin") +OTA_APP_TLV_TEMP = os.path.join(os.path.dirname(__file__), "ota_temp_app_tlv.bin") +OTA_BOOTLOADER_TLV_TEMP = os.path.join(os.path.dirname(__file__), "ota_temp_ssbl_tlv.bin") +OTA_FACTORY_TLV_TEMP = os.path.join(os.path.dirname(__file__), "ota_temp_factory_tlv.bin") class TAG: @@ -58,6 +62,13 @@ class TAG: FACTORY_DATA = 3 +def write_to_temp(path: str, payload: bytearray): + with open(path, "wb") as _handle: + _handle.write(payload) + + logging.info(f"Data payload size for {path.split('/')[-1]}: {len(payload)}") + + def generate_header(tag: int, length: int): header = bytearray(tag.to_bytes(4, "little")) header += bytearray(length.to_bytes(4, "little")) @@ -84,29 +95,100 @@ def generate_factory_data(args: object): payload = generate_header(TAG.FACTORY_DATA, len(writer.encoding)) payload += writer.encoding - with open(OTA_FACTORY_TLV_TEMP, "wb") as _handle: - _handle.write(payload) + write_to_temp(OTA_FACTORY_TLV_TEMP, payload) + + return [OTA_FACTORY_TLV_TEMP] + + +def generate_descriptor(version: int, versionStr: str, buildDate: str): + """ + Generate descriptor as bytearray for app/SSBL payload. + """ + v = version if version is not None else 50000 + vs = versionStr if versionStr is not None else "50000-default" + bd = buildDate if buildDate is not None else "2023-01-01" + + logging.info(f"\t-version: {v}") + logging.info(f"\t-version str: {vs}") + logging.info(f"\t-build date: {bd}") + + v = v.to_bytes(4, "little") + vs = bytearray(vs, "ascii") + bytearray(64 - len(vs)) + bd = bytearray(bd, "ascii") + bytearray(64 - len(bd)) - logging.info(f"Factory data payload size: {len(payload)}") + return v + vs + bd def generate_app(args: object): - version = args.app_version.to_bytes(4, "little") - versionStr = bytearray(args.app_version_str, "ascii") + bytearray(64 - len(args.app_version_str)) - buildDate = bytearray(args.app_build_date, "ascii") + bytearray(64 - len(args.app_build_date)) - descriptor = version + versionStr + buildDate + """ + Generate app payload with descriptor. If a certain option is not specified, use the default values. + """ + logging.info("App descriptor information:") + + descriptor = generate_descriptor(args.app_version, args.app_version_str, args.app_build_date) file_size = os.path.getsize(args.app_input_file) payload = generate_header(TAG.APPLICATION, len(descriptor) + file_size) + descriptor - with open(OTA_APP_TLV_TEMP, "wb") as _handle: - _handle.write(payload) + write_to_temp(OTA_APP_TLV_TEMP, payload) - logging.info(f"Application payload size: {len(payload)}") + return [OTA_APP_TLV_TEMP, args.app_input_file] def generate_bootloader(args: object): - # TODO - pass + """ + Generate SSBL payload with descriptor. If a certain option is not specified, use the default values. + """ + logging.info("SSBL descriptor information:") + + descriptor = generate_descriptor(args.bl_version, args.bl_version_str, args.bl_build_date) + file_size = os.path.getsize(args.bl_input_file) + payload = generate_header(TAG.BOOTLOADER, len(descriptor) + file_size) + descriptor + + write_to_temp(OTA_BOOTLOADER_TLV_TEMP, payload) + + return [OTA_BOOTLOADER_TLV_TEMP, args.bl_input_file] + + +def validate_json(data: str): + with open(os.path.join(os.path.dirname(__file__), 'ota_payload.schema'), 'r') as fd: + payload_schema = json.load(fd) + + try: + jsonschema.validate(instance=data, schema=payload_schema) + logging.info("JSON data is valid") + except jsonschema.exceptions.ValidationError as err: + logging.error(f"JSON data is invalid: {err}") + sys.exit(1) + + +def generate_custom_tlvs(data): + """ + Generate custom OTA payload from a JSON object following a predefined schema. + The payload is written in a temporary file that will be appended to args.input_files. + """ + input_files = [] + + payload = bytearray() + descriptor = bytearray() + iteration = 0 + for entry in data["inputs"]: + if "descriptor" in entry: + for field in entry["descriptor"]: + if isinstance(field["value"], str): + descriptor += bytearray(field["value"], "ascii") + bytearray(field["length"] - len(field["value"])) + elif isinstance(field["value"], int): + descriptor += bytearray(field["value"].to_bytes(field["length"], "little")) + file_size = os.path.getsize(entry["path"]) + payload = generate_header(entry["tag"], len(descriptor) + file_size) + descriptor + + temp_output = os.path.join(os.path.dirname(__file__), "ota_temp_custom_tlv_" + str(iteration) + ".bin") + write_to_temp(temp_output, payload) + + input_files += [temp_output, entry["path"]] + iteration += 1 + descriptor = bytearray() + + return input_files def show_payload(args: object): @@ -119,23 +201,36 @@ def show_payload(args: object): def create_image(args: object): ota_image_tool.validate_header_attributes(args) + input_files = list() + if args.json: + with open(args.json, 'r') as fd: + data = json.load(fd) + validate_json(data) + input_files += generate_custom_tlvs(data) + if args.factory_data: - generate_factory_data(args) - input_files += [OTA_FACTORY_TLV_TEMP] + input_files += generate_factory_data(args) + + if args.bl_input_file: + input_files += generate_bootloader(args) + + if args.app_input_file: + input_files += generate_app(args) + + if len(input_files) == 0: + print("Please specify an input option.") + sys.exit(1) + + logging.info("Input files used:") + [logging.info(f"\t- {_file}") for _file in input_files] - if args.app_input_file is not None: - generate_app(args) - input_files += [OTA_APP_TLV_TEMP, args.app_input_file] - print(input_files) args.input_files = input_files ota_image_tool.generate_image(args) - if args.factory_data: - os.remove(OTA_FACTORY_TLV_TEMP) - if args.app_input_file is not None: - os.remove(OTA_APP_TLV_TEMP) + for filename in glob.glob(os.path.dirname(__file__) + "/ota_temp_*"): + os.remove(filename) def main(): @@ -169,14 +264,11 @@ def any_base_int(s): return int(s, 0) help='Minimum software version that can be updated to this image') create_parser.add_argument('-ma', '--max-version', type=any_base_int, help='Maximum software version that can be updated to this image') - create_parser.add_argument( - '-rn', '--release-notes', help='Release note URL') - create_parser.add_argument('input_files', nargs='*', - help='Path to input image payload file') - create_parser.add_argument('output_file', help='Path to output image file') + create_parser.add_argument('-rn', '--release-notes', + help='Release note URL') - create_parser.add_argument('-app', '--app-input-file', - help='Path to input application image payload file') + create_parser.add_argument('-app', "--app-input-file", + help='Path to application input file') create_parser.add_argument('--app-version', type=any_base_int, help='Application Software version (numeric)') create_parser.add_argument('--app-version-str', type=str, @@ -184,7 +276,7 @@ def any_base_int(s): return int(s, 0) create_parser.add_argument('--app-build-date', type=str, help='Application build date (string)') - create_parser.add_argument('-bl', '--bootloader-input-file', + create_parser.add_argument('-bl', '--bl-input-file', help='Path to input bootloader image payload file') create_parser.add_argument('--bl-version', type=any_base_int, help='Bootloader Software version (numeric)') @@ -192,8 +284,6 @@ def any_base_int(s): return int(s, 0) help='Bootloader Software version (string)') create_parser.add_argument('--bl-build-date', type=str, help='Bootloader build date (string)') - create_parser.add_argument('--bl-load-addr', type=any_base_int, - help='Bootloader load address (numeric)') # Factory data specific arguments. Will be used to generate the TLV payload. create_parser.add_argument('-fd', '--factory-data', action='store_true', @@ -209,6 +299,13 @@ def any_base_int(s): return int(s, 0) create_parser.add_argument("--pai_cert", type=PaiCert, help="[path] Path to PAI certificate in DER format") + # Path to input JSON file which describes custom TLVs. + create_parser.add_argument('--json', help="[path] Path to the JSON describing custom TLVs") + + create_parser.add_argument('-i', '--input_files', default=list(), + help='Path to input image payload file') + create_parser.add_argument('output_file', help='Path to output image file') + show_parser = subcommands.add_parser('show', help='Show OTA image info') show_parser.add_argument('image_file', help='Path to OTA image file') diff --git a/scripts/tools/nxp/ota/ota_payload.schema b/scripts/tools/nxp/ota/ota_payload.schema new file mode 100644 index 00000000000000..bceacf75f029b9 --- /dev/null +++ b/scripts/tools/nxp/ota/ota_payload.schema @@ -0,0 +1,67 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "Custom_OTA_TLV_schema", + "description": "A representation of custom OTA payload with variable number of TLVs", + "type": "object", + "required": [ + "inputs" + ], + "properties": { + "inputs": { + "type": "array", + "items": { + "type": "object", + "required": [ + "tag", + "path" + ], + "properties": { + "tag": { + "type": "integer", + "description": "TLV's tag value used to select a parser" + }, + "descriptor": { + "type": "array", + "description": "Metadata of the TLV value field (C struct)", + "items": { + "$ref": "#/$defs/field" + } + }, + "path": { + "type": "string", + "description": "System path to the binary" + } + } + } + } + }, + "$defs": { + "field": { + "type": "object", + "required": [ + "name", + "length", + "value" + ], + "properties": { + "name": { + "type": "string" + }, + "length": { + "type": "integer", + "description": "Number of bytes occupied in memory" + }, + "value": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "integer" + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/src/platform/nxp/k32w/common/CHIPDevicePlatformRamStorageConfig.h b/src/platform/nxp/k32w/common/CHIPDevicePlatformRamStorageConfig.h index 0d466e3913e160..b55e8fccc38fc8 100644 --- a/src/platform/nxp/k32w/common/CHIPDevicePlatformRamStorageConfig.h +++ b/src/platform/nxp/k32w/common/CHIPDevicePlatformRamStorageConfig.h @@ -122,6 +122,16 @@ #define kNvmId_OTConfigData (uint16_t) 0x4F00 #endif +/** + * @def kNvmId_ApplicationBase + * + * Base PDM ID to be used by applications to define their own + * PDM IDs by using an offset. + */ +#ifndef kNvmId_ApplicationBase +#define kNvmId_ApplicationBase (uint16_t) 0xA000 +#endif + #if CONFIG_CHIP_K32W0_REAL_FACTORY_DATA /** * @def kNvmId_FactoryDataBackup diff --git a/src/platform/nxp/k32w/common/K32W_OTA_README.md b/src/platform/nxp/k32w/common/K32W_OTA_README.md new file mode 100644 index 00000000000000..97c73ec9e5a7a2 --- /dev/null +++ b/src/platform/nxp/k32w/common/K32W_OTA_README.md @@ -0,0 +1,143 @@ +# K32W OTA + +The OTA processing is now delegated to instances of `OTATlvProcessor` derived +classes. These instances are registered with the `OTAImageProcessorImpl` +instance, which manages the selection of processors that should process the next +blocks, until a full TLV block was transferred. + +The application is able to define its own processors, thus extending the default +OTA functionality. The application can also opt to disable the default +processors (application, SSBL and factory data). + +Please note that if an OTA image containing multiple TLV is transferred, then +the action for each TLV is applied sequentially, If one of the actions fails, +the remaining actions will not be applied and OTA abort is called. TBD: should +all actions be applied only if there is no error? Or should each action be +applied separately? + +## Default processors + +The default processors for K32W0 are already implemented in: + +- `OTAFirmwareProcessor` for application/SSBL update. Enabled by default. +- `OTAFactoryDataProcessor` for factory data update. Disabled by default, user + has to specify `chip_ota_enable_factory_data_processor=1` in the build args. + +Some SDK OTA module flags are defined to support additional features: + +- `gOTAAllowCustomStartAddress=1` - enable `EEPROM` offset value. Used + internally by SDK OTA module. +- `gOTAUseCustomOtaEntry=1` - support custom OTA entry for multi-image. +- `gOTACustomOtaEntryMemory=1` - K32W0 uses `OTACustomStorage_ExtFlash` (1) by + default. + +## Implementing custom processors + +A custom processor should implement the abstract interface defined in +`OTATlvProcessor.h`. Below is a compact version: + +``` +class OTATlvProcessor +{ +public: + virtual CHIP_ERROR Init() = 0; + virtual CHIP_ERROR Clear() = 0; + virtual CHIP_ERROR ApplyAction() = 0; + virtual CHIP_ERROR AbortAction() = 0; + virtual CHIP_ERROR ExitAction(); + + CHIP_ERROR Process(ByteSpan & block); + void RegisterDescriptorCallback(ProcessDescriptor callback); + +protected: + virtual CHIP_ERROR ProcessInternal(ByteSpan & block) = 0; +}; + +``` + +Some details regarding the interface: + +- `Init` will be called whenever the processor is selected. +- `Clear` will be called when abort occurs or after the apply action takes + place. +- `ApplyAction` will be called in `OTAImageProcessorImpl::HandleApply`, before + the board is reset. +- `AbortAction` will be called in `OTAImageProcessorImpl::HandleAbort`. + Processors should reset state here. +- `ExitAction` is optional and should be implemented by the processors that + want to execute an action after all data has been transferred, but before + `HandleApply` is called. It's called before the new processor selection + takes place. This is useful in the context of multiple TLV transferred in a + single OTA process. +- `Process` is the public API used inside `OTAImageProcessorImpl` for data + processing. This is a wrapper over `ProcessInternal`, which can return + `CHIP_OTA_CHANGE_PROCESSOR` to notify a new processor should be selected for + the remaining data. +- `RegisterDescriptorCallback` can be used to register a callback for + processing the descriptor. It's optional. +- `ProcessInternal` should return: _ `CHIP_NO_ERROR` if block was processed + successfully. _ `CHIP_ERROR_BUFFER_TOO_SMALL` if current block doesn't + contain all necessary data. This can happen when a TLV value field has a + header, but it is split across two blocks. \* + `CHIP_OTA_FETCH_ALREADY_SCHEDULED` if block was processed successfully and + the fetching is already scheduled by the processor. This happens in the + default application processor, because the next data fetching is scheduled + through a callback (called when enough external flash was erased). + +Furthermore, a processor can use an instance of `OTADataAccumulator` to +accumulate data until a given threshold. This is useful when a custom payload +contains metadata that need parsing: accumulate data until the threshold is +reached or return `CHIP_ERROR_BUFFER_TOO_SMALL` to signal +`OTAImageProcessorImpl` more data is needed. + +``` +/** + * This class can be used to accumulate data until a given threshold. + * Should be used by OTATlvProcessor derived classes if they need + * metadata accumulation (e.g. for custom header decoding). + */ +class OTADataAccumulator +{ +public: + void Init(uint32_t threshold); + void Clear(); + CHIP_ERROR Accumulate(ByteSpan & block); + + inline uint8_t* data() { return mBuffer.Get(); } + +private: + uint32_t mThreshold; + uint32_t mBufferOffset; + Platform::ScopedMemoryBuffer mBuffer; +}; +``` + +## SSBL max entries example + +`CONFIG_CHIP_K32W0_MAX_ENTRIES_TEST` can be set to 1 to enable max entries test. +There will be 8 additional processors registered in default `OtaHooks` +implementation. The OTA image should be generated with the +`create_ota_images.sh` script from `./scripts/tools/nxp/ota/examples`. + +## Factory data restore mechanism + +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 `K32W0FactoryDataProvider` 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 +`K32W0FactoryDataProvider::RegisterRestoreMechanism`. + +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 4efb456e4a8f23..b87ef560aa5841 100644 --- a/src/platform/nxp/k32w/common/OTAImageProcessorImpl.cpp +++ b/src/platform/nxp/k32w/common/OTAImageProcessorImpl.cpp @@ -105,6 +105,8 @@ void OTAImageProcessorImpl::HandlePrepareDownload(intptr_t context) return; } + GetRequestorInstance()->GetProviderLocation(imageProcessor->mBackupProviderLocation); + imageProcessor->mHeaderParser.Init(); imageProcessor->mAccumulator.Init(sizeof(OTATlvHeader)); imageProcessor->mDownloader->OnPreparedForDownload(CHIP_NO_ERROR); @@ -133,7 +135,7 @@ CHIP_ERROR OTAImageProcessorImpl::ProcessPayload(ByteSpan & block) ReturnErrorOnFailure(mAccumulator.Accumulate(block)); ByteSpan tlvHeader{ mAccumulator.data(), sizeof(OTATlvHeader) }; ReturnErrorOnFailure(SelectProcessor(tlvHeader)); - mCurrentProcessor->Init(); + ReturnErrorOnFailure(mCurrentProcessor->Init()); } status = mCurrentProcessor->Process(block); @@ -141,6 +143,7 @@ CHIP_ERROR OTAImageProcessorImpl::ProcessPayload(ByteSpan & block) { mAccumulator.Clear(); mAccumulator.Init(sizeof(OTATlvHeader)); + mCurrentProcessor = nullptr; } else @@ -167,6 +170,7 @@ CHIP_ERROR OTAImageProcessorImpl::SelectProcessor(ByteSpan & block) return CHIP_OTA_PROCESSOR_NOT_REGISTERED; } + ChipLogDetail(SoftwareUpdate, "Selected processor with tag: %ld", pair->first); mCurrentProcessor = pair->second; mCurrentProcessor->SetLength(header.length); mCurrentProcessor->SetWasSelected(true); @@ -194,16 +198,11 @@ void OTAImageProcessorImpl::HandleAbort(intptr_t context) auto * imageProcessor = reinterpret_cast(context); if (imageProcessor != nullptr) { - for (auto const & pair : imageProcessor->mProcessorMap) - { - if (pair.second->WasSelected()) - { - pair.second->AbortAction(); - pair.second->Clear(); - } - } + imageProcessor->AbortAllProcessors(); } imageProcessor->Clear(); + + OtaHookAbort(); } void OTAImageProcessorImpl::HandleProcessBlock(intptr_t context) @@ -255,6 +254,21 @@ void OTAImageProcessorImpl::HandleStatus(CHIP_ERROR status) } } +void OTAImageProcessorImpl::AbortAllProcessors() +{ + ChipLogError(SoftwareUpdate, "All selected processors will call abort action"); + + for (auto const & pair : mProcessorMap) + { + if (pair.second->WasSelected()) + { + pair.second->AbortAction(); + pair.second->Clear(); + pair.second->SetWasSelected(false); + } + } +} + bool OTAImageProcessorImpl::IsFirstImageRun() { OTARequestorInterface * requestor = chip::GetRequestorInstance(); @@ -344,15 +358,23 @@ void OTAImageProcessorImpl::HandleApply(intptr_t context) if (error != CHIP_NO_ERROR) { ChipLogError(SoftwareUpdate, "Apply action for tag %d processor failed.", (uint8_t) pair.first); + // Revert all previously applied actions if current apply action fails. + // Reset image processor and requestor states. + imageProcessor->AbortAllProcessors(); imageProcessor->Clear(); GetRequestorInstance()->Reset(); + return; } - pair.second->Clear(); - pair.second->SetWasSelected(false); } } + for (auto const & pair : imageProcessor->mProcessorMap) + { + pair.second->Clear(); + pair.second->SetWasSelected(false); + } + imageProcessor->mAccumulator.Clear(); // Set the necessary information to inform the SSBL that a new image is available diff --git a/src/platform/nxp/k32w/common/OTAImageProcessorImpl.h b/src/platform/nxp/k32w/common/OTAImageProcessorImpl.h index 4c545c2a218f13..4eea088891109f 100644 --- a/src/platform/nxp/k32w/common/OTAImageProcessorImpl.h +++ b/src/platform/nxp/k32w/common/OTAImageProcessorImpl.h @@ -22,22 +22,40 @@ #include #include #include +#include #include #include /* * OTA hooks that can be overwritten by application. - * Default behavior is implemented as WEAK symbols in - * platform OtaHooks.cpp. + * Default behavior is implemented as WEAK symbols in platform OtaHooks.cpp. + */ + +/* + * This hook is called at the end of OTAImageProcessorImpl::Init. + * It should generally register the OTATlvProcessor instances. */ extern "C" CHIP_ERROR OtaHookInit(); + +/* + * This hook is called at the end of OTAImageProcessorImpl::HandleApply. + * The default implementation saves the internal OTA entry structure and resets the device. + */ extern "C" void OtaHookReset(); +/* + * This hook is called at the end of OTAImageProcessorImpl::HandleAbort. + * For example, it can be used to schedule a retry. + */ +extern "C" void OtaHookAbort(); + namespace chip { class OTAImageProcessorImpl : public OTAImageProcessorInterface { public: + using ProviderLocation = chip::OTARequestorInterface::ProviderLocationType; + CHIP_ERROR Init(OTADownloader * downloader); void Clear(); @@ -54,6 +72,7 @@ class OTAImageProcessorImpl : public OTAImageProcessorInterface CHIP_ERROR ProcessPayload(ByteSpan & block); CHIP_ERROR SelectProcessor(ByteSpan & block); CHIP_ERROR RegisterProcessor(uint32_t tag, OTATlvProcessor * processor); + Optional & GetBackupProvider() { return mBackupProviderLocation; } static void FetchNextData(uint32_t context); static OTAImageProcessorImpl & GetDefaultInstance(); @@ -78,12 +97,18 @@ class OTAImageProcessorImpl : public OTAImageProcessorInterface */ CHIP_ERROR ReleaseBlock(); + /** + * Call AbortAction for all processors that were used + */ + void AbortAllProcessors(); + MutableByteSpan mBlock; OTADownloader * mDownloader; OTAImageHeaderParser mHeaderParser; OTATlvProcessor * mCurrentProcessor = nullptr; OTADataAccumulator mAccumulator; std::map mProcessorMap; + Optional mBackupProviderLocation; }; } // namespace chip diff --git a/src/platform/nxp/k32w/common/OTATlvProcessor.cpp b/src/platform/nxp/k32w/common/OTATlvProcessor.cpp index 819f539263f572..40b068b446ead5 100644 --- a/src/platform/nxp/k32w/common/OTATlvProcessor.cpp +++ b/src/platform/nxp/k32w/common/OTATlvProcessor.cpp @@ -36,18 +36,29 @@ CHIP_ERROR OTATlvProcessor::Process(ByteSpan & block) { mProcessedLength += bytes; block = block.SubSpan(bytes); - if (mProcessedLength == mLength && block.size() > 0) + if (mProcessedLength == mLength) { - // If current block was processed fully and the block still contains data, it - // means that the block contains another TLV's data and the current processor - // should be changed by OTAImageProcessorImpl. - return CHIP_OTA_CHANGE_PROCESSOR; + status = ExitAction(); + if (!IsError(status) && (block.size() > 0)) + { + // If current block was processed fully and the block still contains data, it + // means that the block contains another TLV's data and the current processor + // should be changed by OTAImageProcessorImpl. + return CHIP_OTA_CHANGE_PROCESSOR; + } } } return status; } +void OTATlvProcessor::ClearInternal() +{ + mLength = 0; + mProcessedLength = 0; + mWasSelected = false; +} + bool OTATlvProcessor::IsError(CHIP_ERROR & status) { return status != CHIP_NO_ERROR && status != CHIP_ERROR_BUFFER_TOO_SMALL && status != CHIP_OTA_FETCH_ALREADY_SCHEDULED; diff --git a/src/platform/nxp/k32w/common/OTATlvProcessor.h b/src/platform/nxp/k32w/common/OTATlvProcessor.h index 0f3f00ffaf9688..400e23e8f9a4e1 100644 --- a/src/platform/nxp/k32w/common/OTATlvProcessor.h +++ b/src/platform/nxp/k32w/common/OTATlvProcessor.h @@ -36,7 +36,10 @@ namespace chip { #define CHIP_OTA_PROCESSOR_PUSH_CHUNK CHIP_ERROR_TLV_PROCESSOR(0x07) #define CHIP_OTA_PROCESSOR_IMG_AUTH CHIP_ERROR_TLV_PROCESSOR(0x08) #define CHIP_OTA_FETCH_ALREADY_SCHEDULED CHIP_ERROR_TLV_PROCESSOR(0x09) -#define CHIP_OTA_PROCESSOR_IMG_COMMIT CHIP_ERROR_TLV_PROCESSOR(0x0a) +#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) // Descriptor constants constexpr size_t kVersionStringSize = 64; @@ -44,6 +47,13 @@ constexpr size_t kBuildDateSize = 64; constexpr uint16_t requestedOtaMaxBlockSize = 1024; +/** + * Used alongside RegisterDescriptorCallback to register + * a custom descriptor processing function with a certain + * TLV processor. + */ +typedef CHIP_ERROR (*ProcessDescriptor)(void * descriptor); + struct OTATlvHeader { uint32_t tag; @@ -59,7 +69,7 @@ struct OTATlvHeader * data from two different TLVs, the processor should ensure the remaining * data is returned in the block passed as input. * The default processors: application, SSBL and factory data are registered - * in OTAImageProcessorImpl::Init. + * in OTAImageProcessorImpl::Init through OtaHookInit. * Applications should use OTAImageProcessorImpl::RegisterProcessor * to register additional processors. */ @@ -72,8 +82,10 @@ class OTATlvProcessor virtual CHIP_ERROR Clear() = 0; virtual CHIP_ERROR ApplyAction() = 0; virtual CHIP_ERROR AbortAction() = 0; + virtual CHIP_ERROR ExitAction() { return CHIP_NO_ERROR; } CHIP_ERROR Process(ByteSpan & block); + void RegisterDescriptorCallback(ProcessDescriptor callback) { mCallbackProcessDescriptor = callback; } void SetLength(uint32_t length) { mLength = length; } void SetWasSelected(bool selected) { mWasSelected = selected; } bool WasSelected() { return mWasSelected; } @@ -104,11 +116,14 @@ class OTATlvProcessor */ virtual CHIP_ERROR ProcessInternal(ByteSpan & block) = 0; + void ClearInternal(); + bool IsError(CHIP_ERROR & status); - uint32_t mLength = 0; - uint32_t mProcessedLength = 0; - bool mWasSelected = false; + uint32_t mLength = 0; + uint32_t mProcessedLength = 0; + bool mWasSelected = false; + ProcessDescriptor mCallbackProcessDescriptor = nullptr; }; /** diff --git a/src/platform/nxp/k32w/k32w0/BLEManagerImpl.cpp b/src/platform/nxp/k32w/k32w0/BLEManagerImpl.cpp index 29a3e9ee88ef3a..7ae29b191578fe 100644 --- a/src/platform/nxp/k32w/k32w0/BLEManagerImpl.cpp +++ b/src/platform/nxp/k32w/k32w0/BLEManagerImpl.cpp @@ -1614,17 +1614,20 @@ void BLEManagerImpl::blekw_new_data_received_notification(uint32_t mask) void BLEManagerImpl::BleAdvTimeoutHandler(TimerHandle_t xTimer) { - if (sInstance.mFlags.Has(Flags::kFastAdvertisingEnabled)) + // If stop advertising fails (timeout on event wait), then + // rearm the timer as fast as possible to retry. + // Once stop advertising is successful, slow advertising can start. + auto err = sInstance.StopAdvertising(); + if (err != CHIP_NO_ERROR) { - ChipLogDetail(DeviceLayer, "bleAdv Timeout : Start slow advertisement"); - - sInstance.mFlags.Clear(Flags::kFastAdvertisingEnabled); - // stop advertiser, change interval and restart it; - sInstance.StopAdvertising(); - sInstance.StartAdvertising(); + ChipLogDetail(DeviceLayer, "Stop advertising failed. Retrying..."); + StartBleAdvTimeoutTimer(portTICK_PERIOD_MS); + return; } - return; + sInstance.mFlags.Clear(Flags::kFastAdvertisingEnabled); + ChipLogDetail(DeviceLayer, "Start slow advertisement"); + sInstance.StartAdvertising(); } void BLEManagerImpl::CancelBleAdvTimeoutTimer(void) diff --git a/src/platform/nxp/k32w/k32w0/BUILD.gn b/src/platform/nxp/k32w/k32w0/BUILD.gn index f8f33dd6798c46..9e2a205d8e0ba6 100644 --- a/src/platform/nxp/k32w/k32w0/BUILD.gn +++ b/src/platform/nxp/k32w/k32w0/BUILD.gn @@ -88,16 +88,15 @@ static_library("k32w0") { "../common/OTATlvProcessor.h", ] - if (chip_enable_ota_default_processors == 1) { + if (chip_enable_ota_firmware_processor == 1) { sources += [ - "OTAApplicationProcessor.cpp", - "OTAApplicationProcessor.h", - "OTABootloaderProcessor.cpp", - "OTABootloaderProcessor.h", + "OTAFirmwareProcessor.cpp", + "OTAFirmwareProcessor.h", "OTAHooks.cpp", ] - if (chip_with_factory_data == 1) { + if (chip_with_factory_data == 1 && + chip_enable_ota_factory_data_processor == 1) { sources += [ "OTAFactoryDataProcessor.cpp", "OTAFactoryDataProcessor.h", diff --git a/src/platform/nxp/k32w/k32w0/CHIPDevicePlatformConfig.h b/src/platform/nxp/k32w/k32w0/CHIPDevicePlatformConfig.h index 5bd6c3199d3c1d..310a175fcf4cbf 100644 --- a/src/platform/nxp/k32w/k32w0/CHIPDevicePlatformConfig.h +++ b/src/platform/nxp/k32w/k32w0/CHIPDevicePlatformConfig.h @@ -104,14 +104,14 @@ #endif // CHIP_DEVICE_LAYER_OTA_REBOOT_DELAY /** - * @def CONFIG_CHIP_K32W0_OTA_DEFAULT_PROCESSORS + * @def CONFIG_CHIP_K32W0_OTA_FACTORY_DATA_PROCESSOR * - * Enables default OTA TLV processors. + * Enables default OTA TLV factory data processor. * Disabled by default. */ -#ifndef CONFIG_CHIP_K32W0_OTA_DEFAULT_PROCESSORS -#define CONFIG_CHIP_K32W0_OTA_DEFAULT_PROCESSORS 0 -#endif // CONFIG_CHIP_K32W0_OTA_DEFAULT_PROCESSORS +#ifndef CONFIG_CHIP_K32W0_OTA_FACTORY_DATA_PROCESSOR +#define CONFIG_CHIP_K32W0_OTA_FACTORY_DATA_PROCESSOR 0 +#endif // CONFIG_CHIP_K32W0_OTA_FACTORY_DATA_PROCESSOR /** * @def CHIP_DEVICE_LAYER_ENABLE_PDM_LOGS diff --git a/src/platform/nxp/k32w/k32w0/ConfigurationManagerImpl.cpp b/src/platform/nxp/k32w/k32w0/ConfigurationManagerImpl.cpp index b58ecc6be850f7..b27bdec546e4d0 100644 --- a/src/platform/nxp/k32w/k32w0/ConfigurationManagerImpl.cpp +++ b/src/platform/nxp/k32w/k32w0/ConfigurationManagerImpl.cpp @@ -80,6 +80,8 @@ CHIP_ERROR ConfigurationManagerImpl::Init() SuccessOrExit(err); } + rebootCause = POWER_GetResetCause(); + // Initialize the generic implementation base class. err = Internal::GenericConfigurationManagerImpl::Init(); SuccessOrExit(err); @@ -114,26 +116,25 @@ CHIP_ERROR ConfigurationManagerImpl::StoreTotalOperationalHours(uint32_t totalOp CHIP_ERROR ConfigurationManagerImpl::GetBootReason(uint32_t & bootReason) { - bootReason = to_underlying(BootReasonType::kUnspecified); - uint8_t reason = POWER_GetResetCause(); - - if (reason == RESET_UNDEFINED) + bootReason = to_underlying(BootReasonType::kUnspecified); + // rebootCause is obtained at bootup. + if (rebootCause == RESET_UNDEFINED) { bootReason = to_underlying(BootReasonType::kUnspecified); } - else if ((reason == RESET_POR) || (reason == RESET_EXT_PIN)) + else if ((rebootCause == RESET_POR) || (rebootCause == RESET_EXT_PIN)) { bootReason = to_underlying(BootReasonType::kPowerOnReboot); } - else if (reason == RESET_BOR) + else if (rebootCause == RESET_BOR) { bootReason = to_underlying(BootReasonType::kBrownOutReset); } - else if (reason == RESET_SW_REQ) + else if (rebootCause == RESET_SW_REQ) { bootReason = to_underlying(BootReasonType::kSoftwareReset); } - else if (reason == RESET_WDT) + else if (rebootCause == RESET_WDT) { bootReason = to_underlying(BootReasonType::kSoftwareWatchdogReset); /* Reboot can be due to hardware or software watchdog */ diff --git a/src/platform/nxp/k32w/k32w0/ConfigurationManagerImpl.h b/src/platform/nxp/k32w/k32w0/ConfigurationManagerImpl.h index 61cd098ae606d3..4807162ee00ba9 100644 --- a/src/platform/nxp/k32w/k32w0/ConfigurationManagerImpl.h +++ b/src/platform/nxp/k32w/k32w0/ConfigurationManagerImpl.h @@ -76,7 +76,7 @@ class ConfigurationManagerImpl final : public Internal::GenericConfigurationMana void RunConfigUnitTest(void) override; // ===== Private members reserved for use by this class only. - + uint8_t rebootCause; static void DoFactoryReset(intptr_t arg); }; diff --git a/src/platform/nxp/k32w/k32w0/DiagnosticDataProviderImpl.cpp b/src/platform/nxp/k32w/k32w0/DiagnosticDataProviderImpl.cpp index 34ef40e47b1351..96b8e663fe15aa 100644 --- a/src/platform/nxp/k32w/k32w0/DiagnosticDataProviderImpl.cpp +++ b/src/platform/nxp/k32w/k32w0/DiagnosticDataProviderImpl.cpp @@ -222,9 +222,22 @@ CHIP_ERROR DiagnosticDataProviderImpl::GetNetworkInterfaces(NetworkInterface ** uint8_t macBuffer[ConfigurationManager::kPrimaryMACAddressLength]; ConfigurationMgr().GetPrimary802154MACAddress(macBuffer); ifp->hardwareAddress = ByteSpan(macBuffer, ConfigurationManager::kPrimaryMACAddressLength); - *netifpp = ifp; + 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/k32w0/DiagnosticDataProviderImpl.h b/src/platform/nxp/k32w/k32w0/DiagnosticDataProviderImpl.h index 05be335999482b..2adbb1b2180a71 100644 --- a/src/platform/nxp/k32w/k32w0/DiagnosticDataProviderImpl.h +++ b/src/platform/nxp/k32w/k32w0/DiagnosticDataProviderImpl.h @@ -52,6 +52,7 @@ class DiagnosticDataProviderImpl : public DiagnosticDataProvider CHIP_ERROR GetTotalOperationalHours(uint32_t & totalOperationalHours) override; CHIP_ERROR GetBootReason(BootReasonType & bootReason) override; CHIP_ERROR GetNetworkInterfaces(NetworkInterface ** netifpp) override; + void ReleaseNetworkInterfaces(NetworkInterface * netifp) override; }; /** diff --git a/src/platform/nxp/k32w/k32w0/K32W0FactoryDataProvider.cpp b/src/platform/nxp/k32w/k32w0/K32W0FactoryDataProvider.cpp index 9bde35a7dcebc0..53533218cccf72 100644 --- a/src/platform/nxp/k32w/k32w0/K32W0FactoryDataProvider.cpp +++ b/src/platform/nxp/k32w/k32w0/K32W0FactoryDataProvider.cpp @@ -63,6 +63,37 @@ K32W0FactoryDataProvider & K32W0FactoryDataProvider::GetDefaultInstance() return sInstance; } +extern "C" WEAK CHIP_ERROR FactoryDataDefaultRestoreMechanism() +{ + CHIP_ERROR error = CHIP_NO_ERROR; + uint16_t backupLength = 0; + +#if CONFIG_CHIP_K32W0_OTA_FACTORY_DATA_PROCESSOR + // Check if PDM id related to factory data backup exists. + // If it does, it means an external event (such as a power loss) + // interrupted the factory data update process and the section + // from internal flash is most likely erased and should be restored. + if (PDM_bDoesDataExist(kNvmId_FactoryDataBackup, &backupLength)) + { + chip::Platform::ScopedMemoryBuffer buffer; + buffer.Calloc(K32W0FactoryDataProvider::kFactoryDataSize); + ReturnErrorCodeIf(buffer.Get() == nullptr, CHIP_ERROR_NO_MEMORY); + + auto status = PDM_eReadDataFromRecord(kNvmId_FactoryDataBackup, (void *) buffer.Get(), + K32W0FactoryDataProvider::kFactoryDataSize, &backupLength); + ReturnErrorCodeIf(PDM_E_STATUS_OK != status, CHIP_FACTORY_DATA_PDM_RESTORE); + + error = K32W0FactoryDataProvider::GetDefaultInstance().UpdateData(buffer.Get()); + if (error == CHIP_NO_ERROR) + { + ChipLogProgress(DeviceLayer, "Factory data was restored successfully"); + } + } +#endif + + return error; +} + K32W0FactoryDataProvider::K32W0FactoryDataProvider() { maxLengths[FactoryDataId::kVerifierId] = kSpake2pSerializedVerifier_MaxBase64Len; @@ -86,6 +117,8 @@ K32W0FactoryDataProvider::K32W0FactoryDataProvider() maxLengths[FactoryDataId::kPartNumber] = ConfigurationManager::kMaxPartNumberLength; maxLengths[FactoryDataId::kProductURL] = ConfigurationManager::kMaxProductURLLength; maxLengths[FactoryDataId::kProductLabel] = ConfigurationManager::kMaxProductLabelLength; + + RegisterRestoreMechanism(FactoryDataDefaultRestoreMechanism); } CHIP_ERROR K32W0FactoryDataProvider::Init() @@ -106,22 +139,43 @@ CHIP_ERROR K32W0FactoryDataProvider::Init() kFactoryDataSize); } - error = Validate(); + VerifyOrReturnError(mRestoreMechanisms.size() > 0, CHIP_FACTORY_DATA_RESTORE_MECHANISM); + + for (auto & restore : mRestoreMechanisms) + { + error = restore(); + if (error != CHIP_NO_ERROR) + { + continue; + } + + error = Validate(); + if (error != CHIP_NO_ERROR) + { + continue; + } + + break; + } + if (error != CHIP_NO_ERROR) { - ChipLogError(DeviceLayer, "Factory data validation failed with error: %s", ErrorStr(error)); - return error; + ChipLogError(DeviceLayer, "Factory data init failed with: %s", ErrorStr(error)); + } + else + { +#if CONFIG_CHIP_K32W0_OTA_FACTORY_DATA_PROCESSOR + PDM_vDeleteDataRecord(kNvmId_FactoryDataBackup); +#endif } - return CHIP_NO_ERROR; + return error; } CHIP_ERROR K32W0FactoryDataProvider::Validate() { uint8_t sha256Output[SHA256_HASH_SIZE] = { 0 }; - ReturnErrorOnFailure(Restore()); - auto status = OtaUtils_ReadFromInternalFlash((uint16_t) sizeof(Header), kFactoryDataStart, (uint8_t *) &mHeader, NULL, pFunctionEepromRead); ReturnErrorCodeIf(gOtaUtilsSuccess_c != status, CHIP_FACTORY_DATA_HEADER_READ); @@ -133,34 +187,9 @@ CHIP_ERROR K32W0FactoryDataProvider::Validate() return CHIP_NO_ERROR; } -CHIP_ERROR K32W0FactoryDataProvider::Restore() +void K32W0FactoryDataProvider::RegisterRestoreMechanism(RestoreMechanism restore) { - CHIP_ERROR error = CHIP_NO_ERROR; - uint16_t backupLength = 0; - - // Check if PDM id related to factory data backup exists. - // If it does, it means an external event (such as a power loss) - // interrupted the factory data update process and the section - // from internal flash is most likely erased and should be restored. - if (PDM_bDoesDataExist(kNvmId_FactoryDataBackup, &backupLength)) - { - chip::Platform::ScopedMemoryBuffer buffer; - buffer.Calloc(kFactoryDataSize); - ReturnErrorCodeIf(buffer.Get() == nullptr, CHIP_ERROR_NO_MEMORY); - - auto status = PDM_eReadDataFromRecord(kNvmId_FactoryDataBackup, (void *) buffer.Get(), kFactoryDataSize, &backupLength); - ReturnErrorCodeIf(PDM_E_STATUS_OK != status, CHIP_FACTORY_DATA_PDM_RESTORE); - - error = UpdateData(buffer.Get()); - if (CHIP_NO_ERROR == error) - { - PDM_vDeleteDataRecord(kNvmId_FactoryDataBackup); - } - } - - // TODO: add hook to enable restore customization - - return error; + mRestoreMechanisms.insert(mRestoreMechanisms.end(), restore); } CHIP_ERROR K32W0FactoryDataProvider::UpdateData(uint8_t * pBuf) @@ -469,19 +498,25 @@ CHIP_ERROR K32W0FactoryDataProvider::GetHardwareVersionString(char * buf, size_t CHIP_ERROR K32W0FactoryDataProvider::GetRotatingDeviceIdUniqueId(MutableByteSpan & uniqueIdSpan) { - ChipError err = CHIP_ERROR_WRONG_KEY_TYPE; -#if CHIP_ENABLE_ROTATING_DEVICE_ID && defined(CHIP_DEVICE_CONFIG_ROTATING_DEVICE_ID_UNIQUE_ID) - char uniqueId[ConfigurationManager::kMaxUniqueIDLength] = { 0 }; - uint16_t uniqueIdLen = 0; - ReturnErrorOnFailure(SearchForId(FactoryDataId::kUniqueId, (uint8_t *) (&uniqueId[0]), sizeof(uniqueId), uniqueIdLen)); + CHIP_ERROR err = CHIP_ERROR_NOT_IMPLEMENTED; +#if CHIP_ENABLE_ROTATING_DEVICE_ID static_assert(ConfigurationManager::kRotatingDeviceIDUniqueIDLength >= ConfigurationManager::kMinRotatingDeviceIDUniqueIDLength, "Length of unique ID for rotating device ID is smaller than minimum."); + uint16_t uniqueIdLen = 0; + err = SearchForId(FactoryDataId::kUniqueId, (uint8_t *) uniqueIdSpan.data(), uniqueIdSpan.size(), uniqueIdLen); +#if defined(CHIP_DEVICE_CONFIG_ROTATING_DEVICE_ID_UNIQUE_ID) + if (err != CHIP_NO_ERROR) + { + constexpr uint8_t uniqueId[] = CHIP_DEVICE_CONFIG_ROTATING_DEVICE_ID_UNIQUE_ID; - ReturnErrorCodeIf(uniqueIdLen > uniqueIdSpan.size(), CHIP_ERROR_BUFFER_TOO_SMALL); - ReturnErrorCodeIf(uniqueIdLen != ConfigurationManager::kRotatingDeviceIDUniqueIDLength, CHIP_ERROR_BUFFER_TOO_SMALL); - memcpy(uniqueIdSpan.data(), uniqueId, uniqueIdLen); + ReturnErrorCodeIf(sizeof(uniqueId) > uniqueIdSpan.size(), CHIP_ERROR_BUFFER_TOO_SMALL); + memcpy(uniqueIdSpan.data(), uniqueId, sizeof(uniqueId)); + uniqueIdLen = sizeof(uniqueId); + err = CHIP_NO_ERROR; + } +#endif // CHIP_DEVICE_CONFIG_ROTATING_DEVICE_ID_UNIQUE_ID + ReturnErrorOnFailure(err); uniqueIdSpan.reduce_size(uniqueIdLen); - return CHIP_NO_ERROR; #endif return err; diff --git a/src/platform/nxp/k32w/k32w0/K32W0FactoryDataProvider.h b/src/platform/nxp/k32w/k32w0/K32W0FactoryDataProvider.h index 742a750b2fcc45..2cbfeb664dc7fa 100644 --- a/src/platform/nxp/k32w/k32w0/K32W0FactoryDataProvider.h +++ b/src/platform/nxp/k32w/k32w0/K32W0FactoryDataProvider.h @@ -28,6 +28,8 @@ #include "OtaUtils.h" #include "SecLib.h" +#include + /* Grab symbol for the base address from the linker file. */ extern uint32_t __FACTORY_DATA_START[]; extern uint32_t __FACTORY_DATA_SIZE[]; @@ -48,6 +50,7 @@ namespace DeviceLayer { #define CHIP_FACTORY_DATA_INTERNAL_FLASH_READ CHIP_FACTORY_DATA_ERROR(0x08) #define CHIP_FACTORY_DATA_PDM_SAVE_RECORD CHIP_FACTORY_DATA_ERROR(0x09) #define CHIP_FACTORY_DATA_PDM_READ_RECORD CHIP_FACTORY_DATA_ERROR(0x0A) +#define CHIP_FACTORY_DATA_RESTORE_MECHANISM CHIP_FACTORY_DATA_ERROR(0x0B) /** * @brief This class provides Commissionable data, Device Attestation Credentials, @@ -110,15 +113,17 @@ class K32W0FactoryDataProvider : public DeviceInstanceInfoProvider, static constexpr size_t kHashId = 0xCE47BA5E; typedef otaUtilsResult_t (*OtaUtils_EEPROM_ReadData)(uint16_t nbBytes, uint32_t address, uint8_t * pInbuf); - static uint8_t ReadDataMemcpy(uint16_t num, uint32_t src, uint8_t * dst); + using RestoreMechanism = CHIP_ERROR (*)(void); + + static uint8_t ReadDataMemcpy(uint16_t num, uint32_t src, uint8_t * dst); static K32W0FactoryDataProvider & GetDefaultInstance(); K32W0FactoryDataProvider(); CHIP_ERROR Init(); CHIP_ERROR Validate(); - CHIP_ERROR Restore(); + void RegisterRestoreMechanism(RestoreMechanism mechanism); CHIP_ERROR UpdateData(uint8_t * pBuf); CHIP_ERROR SearchForId(uint8_t searchedType, uint8_t * pBuf, size_t bufLength, uint16_t & length, uint32_t * offset = nullptr); @@ -159,6 +164,7 @@ class K32W0FactoryDataProvider : public DeviceInstanceInfoProvider, protected: uint16_t maxLengths[kNumberOfIds]; Header mHeader; + std::vector mRestoreMechanisms; }; } // namespace DeviceLayer diff --git a/src/platform/nxp/k32w/k32w0/OTABootloaderProcessor.cpp b/src/platform/nxp/k32w/k32w0/OTABootloaderProcessor.cpp deleted file mode 100644 index eafb57bba9bd40..00000000000000 --- a/src/platform/nxp/k32w/k32w0/OTABootloaderProcessor.cpp +++ /dev/null @@ -1,55 +0,0 @@ -/* - * - * 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 - -#include "OtaSupport.h" -#include "OtaUtils.h" - -namespace chip { - -CHIP_ERROR OTABootloaderProcessor::Init() -{ - return CHIP_NO_ERROR; -} - -CHIP_ERROR OTABootloaderProcessor::Clear() -{ - return CHIP_NO_ERROR; -} - -CHIP_ERROR OTABootloaderProcessor::ProcessInternal(ByteSpan & block) -{ - return CHIP_NO_ERROR; -} - -CHIP_ERROR OTABootloaderProcessor::ApplyAction() -{ - return CHIP_NO_ERROR; -} - -CHIP_ERROR OTABootloaderProcessor::AbortAction() -{ - return CHIP_NO_ERROR; -} - -} // namespace chip diff --git a/src/platform/nxp/k32w/k32w0/OTABootloaderProcessor.h b/src/platform/nxp/k32w/k32w0/OTABootloaderProcessor.h deleted file mode 100644 index e4e428114c8110..00000000000000 --- a/src/platform/nxp/k32w/k32w0/OTABootloaderProcessor.h +++ /dev/null @@ -1,46 +0,0 @@ -/* - * - * 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 -#include - -namespace chip { - -struct BootLoaderDescriptor -{ - uint32_t version; - char versionString[kVersionStringSize]; - char buildDate[kBuildDateSize]; - uint32_t loadAddress; -}; - -class OTABootloaderProcessor : public OTATlvProcessor -{ -public: - CHIP_ERROR Init() override; - CHIP_ERROR Clear() override; - CHIP_ERROR ApplyAction() override; - CHIP_ERROR AbortAction() override; - -private: - CHIP_ERROR ProcessInternal(ByteSpan & block) override; -}; - -} // namespace chip diff --git a/src/platform/nxp/k32w/k32w0/OTAFactoryDataProcessor.cpp b/src/platform/nxp/k32w/k32w0/OTAFactoryDataProcessor.cpp index 367d11d4ce2bf9..8a3a9b7b661779 100644 --- a/src/platform/nxp/k32w/k32w0/OTAFactoryDataProcessor.cpp +++ b/src/platform/nxp/k32w/k32w0/OTAFactoryDataProcessor.cpp @@ -38,6 +38,7 @@ CHIP_ERROR OTAFactoryDataProcessor::Init() CHIP_ERROR OTAFactoryDataProcessor::Clear() { + OTATlvProcessor::ClearInternal(); mAccumulator.Clear(); mPayload.Clear(); ClearBuffer(); @@ -86,24 +87,22 @@ CHIP_ERROR OTAFactoryDataProcessor::ApplyAction() if (error != CHIP_NO_ERROR) { ChipLogError(DeviceLayer, "Failed to update factory data. Error: %s", ErrorStr(error)); - error = Restore(); - if (error == CHIP_NO_ERROR) - { - error = FactoryProvider::GetDefaultInstance().UpdateData(mFactoryData); - } } else { ChipLogProgress(DeviceLayer, "Factory data update finished."); } - ClearBuffer(); - return error; } CHIP_ERROR OTAFactoryDataProcessor::AbortAction() { + ReturnErrorOnFailure(Restore()); + ReturnErrorOnFailure(FactoryProvider::GetDefaultInstance().UpdateData(mFactoryData)); + + PDM_vDeleteDataRecord(kNvmId_FactoryDataBackup); + return CHIP_NO_ERROR; } @@ -207,11 +206,10 @@ void OTAFactoryDataProcessor::ClearBuffer() { if (mFactoryData) { + memset(mFactoryData, 0, FactoryProvider::kFactoryDataSize); chip::Platform::MemoryFree(mFactoryData); mFactoryData = nullptr; } - - PDM_vDeleteDataRecord(kNvmId_FactoryDataBackup); } CHIP_ERROR OTAFactoryDataProcessor::UpdateValue(uint8_t tag, ByteSpan & newValue) diff --git a/src/platform/nxp/k32w/k32w0/OTAApplicationProcessor.cpp b/src/platform/nxp/k32w/k32w0/OTAFirmwareProcessor.cpp similarity index 64% rename from src/platform/nxp/k32w/k32w0/OTAApplicationProcessor.cpp rename to src/platform/nxp/k32w/k32w0/OTAFirmwareProcessor.cpp index 9c78b705f0d852..cf26fc8c2149ea 100644 --- a/src/platform/nxp/k32w/k32w0/OTAApplicationProcessor.cpp +++ b/src/platform/nxp/k32w/k32w0/OTAFirmwareProcessor.cpp @@ -18,36 +18,41 @@ #include #include -#include -#include +#include #include "OtaSupport.h" #include "OtaUtils.h" namespace chip { -CHIP_ERROR OTAApplicationProcessor::Init() +CHIP_ERROR OTAFirmwareProcessor::Init() { - mAccumulator.Init(sizeof(AppDescriptor)); - + ReturnErrorCodeIf(mCallbackProcessDescriptor == nullptr, CHIP_OTA_PROCESSOR_CB_NOT_REGISTERED); + mAccumulator.Init(sizeof(Descriptor)); ReturnErrorCodeIf(gOtaSuccess_c != OTA_ClientInit(), CHIP_OTA_PROCESSOR_CLIENT_INIT); - ReturnErrorCodeIf(gOtaSuccess_c != OTA_StartImage(mLength - sizeof(AppDescriptor)), CHIP_ERROR_INTERNAL); + + auto offset = OTA_GetCurrentEepromAddressOffset(); + if (offset != 0) + { + offset += 1; + } + + ReturnErrorCodeIf(OTA_UTILS_IMAGE_INVALID_ADDR == OTA_SetStartEepromOffset(offset), CHIP_OTA_PROCESSOR_EEPROM_OFFSET); + ReturnErrorCodeIf(gOtaSuccess_c != OTA_StartImage(mLength - sizeof(Descriptor)), CHIP_OTA_PROCESSOR_START_IMAGE); return CHIP_NO_ERROR; } -CHIP_ERROR OTAApplicationProcessor::Clear() +CHIP_ERROR OTAFirmwareProcessor::Clear() { + OTATlvProcessor::ClearInternal(); mAccumulator.Clear(); - mLength = 0; - mProcessedLength = 0; - mWasSelected = false; mDescriptorProcessed = false; return CHIP_NO_ERROR; } -CHIP_ERROR OTAApplicationProcessor::ProcessInternal(ByteSpan & block) +CHIP_ERROR OTAFirmwareProcessor::ProcessInternal(ByteSpan & block) { if (!mDescriptorProcessed) { @@ -71,11 +76,10 @@ CHIP_ERROR OTAApplicationProcessor::ProcessInternal(ByteSpan & block) return CHIP_OTA_FETCH_ALREADY_SCHEDULED; } -CHIP_ERROR OTAApplicationProcessor::ProcessDescriptor(ByteSpan & block) +CHIP_ERROR OTAFirmwareProcessor::ProcessDescriptor(ByteSpan & block) { ReturnErrorOnFailure(mAccumulator.Accumulate(block)); - - // TODO: use accumulator data in some way. What should be done with AppDescriptor data? + ReturnErrorOnFailure(mCallbackProcessDescriptor(static_cast(mAccumulator.data()))); mDescriptorProcessed = true; mAccumulator.Clear(); @@ -83,27 +87,38 @@ CHIP_ERROR OTAApplicationProcessor::ProcessDescriptor(ByteSpan & block) return CHIP_NO_ERROR; } -CHIP_ERROR OTAApplicationProcessor::ApplyAction() +CHIP_ERROR OTAFirmwareProcessor::ApplyAction() +{ + + return CHIP_NO_ERROR; +} + +CHIP_ERROR OTAFirmwareProcessor::AbortAction() +{ + OTA_CancelImage(); + OTA_ResetCustomEntries(); + OTA_ResetCurrentEepromAddress(); + OTA_SetStartEepromOffset(0); + Clear(); + + return CHIP_NO_ERROR; +} + +CHIP_ERROR OTAFirmwareProcessor::ExitAction() { if (OTA_CommitImage(NULL) != gOtaSuccess_c) { - ChipLogError(SoftwareUpdate, "Failed to commit application image."); + ChipLogError(SoftwareUpdate, "Failed to commit firmware image."); return CHIP_OTA_PROCESSOR_IMG_COMMIT; } if (OTA_ImageAuthenticate() != gOtaImageAuthPass_c) { - ChipLogError(SoftwareUpdate, "Failed to authenticate application image."); + ChipLogError(SoftwareUpdate, "Failed to authenticate firmware image."); return CHIP_OTA_PROCESSOR_IMG_AUTH; } - return CHIP_NO_ERROR; -} - -CHIP_ERROR OTAApplicationProcessor::AbortAction() -{ - OTA_CancelImage(); - Clear(); + OTA_AddNewImageFlag(); return CHIP_NO_ERROR; } diff --git a/src/platform/nxp/k32w/k32w0/OTAApplicationProcessor.h b/src/platform/nxp/k32w/k32w0/OTAFirmwareProcessor.h similarity index 82% rename from src/platform/nxp/k32w/k32w0/OTAApplicationProcessor.h rename to src/platform/nxp/k32w/k32w0/OTAFirmwareProcessor.h index c500eccff8c247..bc12394598ac89 100644 --- a/src/platform/nxp/k32w/k32w0/OTAApplicationProcessor.h +++ b/src/platform/nxp/k32w/k32w0/OTAFirmwareProcessor.h @@ -23,20 +23,21 @@ namespace chip { -struct AppDescriptor -{ - uint32_t version; - char versionString[kVersionStringSize]; - char buildDate[kBuildDateSize]; -}; - -class OTAApplicationProcessor : public OTATlvProcessor +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; diff --git a/src/platform/nxp/k32w/k32w0/OTAHooks.cpp b/src/platform/nxp/k32w/k32w0/OTAHooks.cpp index 28b8f764aac59e..a00ae428a0f40d 100644 --- a/src/platform/nxp/k32w/k32w0/OTAHooks.cpp +++ b/src/platform/nxp/k32w/k32w0/OTAHooks.cpp @@ -19,37 +19,100 @@ #include #include +#include + #include -#include -#include -#if CONFIG_CHIP_K32W0_REAL_FACTORY_DATA +#include +#if CONFIG_CHIP_K32W0_OTA_FACTORY_DATA_PROCESSOR #include -#endif // CONFIG_CHIP_K32W0_REAL_FACTORY_DATA +#endif // CONFIG_CHIP_K32W0_OTA_FACTORY_DATA_PROCESSOR #include "OtaSupport.h" +#ifndef CONFIG_CHIP_K32W0_MAX_ENTRIES_TEST +#define CONFIG_CHIP_K32W0_MAX_ENTRIES_TEST 0 +#endif + +#ifndef CONFIG_CHIP_K32W0_OTA_ABORT_HOOK +#define CONFIG_CHIP_K32W0_OTA_ABORT_HOOK 0 +#endif + extern "C" void ResetMCU(void); -static chip::OTAApplicationProcessor sApplicationProcessor; -static chip::OTABootloaderProcessor sBootloaderProcessor; -#if CONFIG_CHIP_K32W0_REAL_FACTORY_DATA -static chip::OTAFactoryDataProcessor sFactoryDataProcessor; -#endif // CONFIG_CHIP_K32W0_REAL_FACTORY_DATA +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() { + static chip::OTAFirmwareProcessor sApplicationProcessor; + static chip::OTAFirmwareProcessor sBootloaderProcessor; +#if CONFIG_CHIP_K32W0_OTA_FACTORY_DATA_PROCESSOR + static chip::OTAFactoryDataProcessor sFactoryDataProcessor; +#endif // CONFIG_CHIP_K32W0_OTA_FACTORY_DATA_PROCESSOR +#if CONFIG_CHIP_K32W0_MAX_ENTRIES_TEST + static chip::OTAFirmwareProcessor processors[8]; +#endif + + sApplicationProcessor.RegisterDescriptorCallback(ProcessDescriptor); + sBootloaderProcessor.RegisterDescriptorCallback(ProcessDescriptor); + auto & imageProcessor = chip::OTAImageProcessorImpl::GetDefaultInstance(); ReturnErrorOnFailure(imageProcessor.RegisterProcessor(1, &sApplicationProcessor)); ReturnErrorOnFailure(imageProcessor.RegisterProcessor(2, &sBootloaderProcessor)); -#if CONFIG_CHIP_K32W0_REAL_FACTORY_DATA +#if CONFIG_CHIP_K32W0_OTA_FACTORY_DATA_PROCESSOR ReturnErrorOnFailure(imageProcessor.RegisterProcessor(3, &sFactoryDataProcessor)); -#endif // CONFIG_CHIP_K32W0_REAL_FACTORY_DATA +#endif // CONFIG_CHIP_K32W0_OTA_FACTORY_DATA_PROCESSOR +#if CONFIG_CHIP_K32W0_MAX_ENTRIES_TEST + for (auto i = 0; i < 8; i++) + { + processors[i].RegisterDescriptorCallback(ProcessDescriptor); + ReturnErrorOnFailure(imageProcessor.RegisterProcessor(i + 4, &processors[i])); + } +#endif // CONFIG_CHIP_K32W0_MAX_ENTRIES_TEST return CHIP_NO_ERROR; } extern "C" WEAK void OtaHookReset() { - OTA_SetNewImageFlag(); + OTA_CommitCustomEntries(); 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_K32W0_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_K32W0_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/k32w0/crypto/CHIPCryptoPALNXPUltrafastP256.cpp b/src/platform/nxp/k32w/k32w0/crypto/CHIPCryptoPALNXPUltrafastP256.cpp index 289296c25d6d4d..771e2638a8a176 100644 --- a/src/platform/nxp/k32w/k32w0/crypto/CHIPCryptoPALNXPUltrafastP256.cpp +++ b/src/platform/nxp/k32w/k32w0/crypto/CHIPCryptoPALNXPUltrafastP256.cpp @@ -644,7 +644,7 @@ CHIP_ERROR P256Keypair::Initialize(ECPKeyTarget key_target) secEcp256Status_t st; ecp256KeyPair_t * keypair; keypair = to_keypair(&mKeypair); - if ((st = ECP256_GenerateKeyPair(&keypair->public_key, &keypair->private_key)) != gSecEcp256Success_c) + if ((st = ECP256_GenerateKeyPair(&keypair->public_key, &keypair->private_key, NULL)) != gSecEcp256Success_c) { result = st; } @@ -918,7 +918,7 @@ CHIP_ERROR Spake2p_P256_SHA256_HKDF_HMAC::FEGenerate(void * fe) ecp256Point_t PublicKey; ecp256Coordinate_t PrivateKey; - result = ECP256_GenerateKeyPair(&PublicKey, &PrivateKey); + result = ECP256_GenerateKeyPair(&PublicKey, &PrivateKey, NULL); Spake2p_Context * context = to_inner_spake2p_context(&mSpake2pContext); @@ -1016,19 +1016,19 @@ CHIP_ERROR Spake2p_P256_SHA256_HKDF_HMAC::ComputeL(uint8_t * Lout, size_t * L_le secEcp256Status_t result; uint8_t * p; - uint32_t W1[ECP256_COORDINATE_WLEN]; + uint32_t W1[SEC_ECP256_COORDINATE_WLEN]; do { result = ECP256_ModularReductionN(W1, w1in, w1in_len); if (result != gSecEcp256Success_c) break; ecp256Point_t gen_point; - result = ECP256_GeneratePublicKey((uint8_t *) &gen_point, (uint8_t *) &W1); + result = ECP256_GeneratePublicKey((uint8_t *) &gen_point, (uint8_t *) &W1, NULL); if (result != gSecEcp256Success_c) break; p = Lout; *p++ = 0x04; /* uncompressed format */ - memcpy(p, (uint8_t *) &gen_point, ECP256_COORDINATE_LEN * 2); + memcpy(p, (uint8_t *) &gen_point, SEC_ECP256_COORDINATE_LEN * 2); } while (0); exit: @@ -1461,7 +1461,7 @@ CHIP_ERROR ExtractPubkeyFromX509Cert(const ByteSpan & certificate, Crypto::P256P keypair = (ecp256KeyPair_t *) (mbed_cert.CHIP_CRYPTO_PAL_PRIVATE_X509(pk)).pk_ctx; Uint8::to_uchar(pubkey)[0] = 0x04; // uncompressed type - memcpy(Uint8::to_uchar(pubkey) + 1, keypair->public_key.raw, 2 * ECP256_COORDINATE_LEN); + memcpy(Uint8::to_uchar(pubkey) + 1, keypair->public_key.raw, 2 * SEC_ECP256_COORDINATE_LEN); VerifyOrExit(result == 0, error = CHIP_ERROR_INTERNAL); diff --git a/third_party/nxp/k32w0_sdk/k32w0_sdk.gni b/third_party/nxp/k32w0_sdk/k32w0_sdk.gni index b9900540a79169..41e9761044106f 100644 --- a/third_party/nxp/k32w0_sdk/k32w0_sdk.gni +++ b/third_party/nxp/k32w0_sdk/k32w0_sdk.gni @@ -43,7 +43,10 @@ declare_args() { use_custom_factory_provider = 0 chip_crypto_flavor = "NXP-Ultrafast-P256" chip_reduce_ssbl_size = false - chip_enable_ota_default_processors = 1 + chip_enable_ota_firmware_processor = 1 + chip_enable_ota_factory_data_processor = 0 + chip_with_pdm_encryption = 1 + ota_custom_entry_address = "0x000C1000" } assert(k32w0_sdk_root != "", "k32w0_sdk_root must be specified") @@ -81,6 +84,16 @@ template("k32w0_sdk") { use_custom_factory_provider == 0 || chip_with_factory_data == 1, "Please set chip_with_factory_data=1 if using custom factory provider.") + assert( + chip_enable_ota_factory_data_processor == 0 || + chip_enable_ota_firmware_processor == 1, + "Please set chip_enable_ota_firmware_processor=1 if using default factory data processor.") + + assert( + chip_enable_ota_factory_data_processor == 0 || + chip_with_factory_data == 1, + "Please set chip_with_factory_data=1 if using default factory data processor.") + if (build_for_k32w041am == 1 || build_for_k32w041a == 1 || build_for_k32w041 == 1) { build_for_k32w061 = 0 @@ -125,7 +138,10 @@ template("k32w0_sdk") { print("increased TX power:", chip_with_high_power) print("FRO32k: ", use_fro_32k) print("low power: ", chip_with_low_power) - print("OTA default processors: ", chip_enable_ota_default_processors) + print("OTA default firmware processor: ", chip_enable_ota_firmware_processor) + print("OTA default factory data processor: ", + chip_enable_ota_factory_data_processor) + print("PDM Encryption: ", chip_with_pdm_encryption) if (chip_with_low_power == 1 && chip_logging == true) { print( @@ -237,15 +253,9 @@ template("k32w0_sdk") { "${k32w0_sdk_root}/middleware/wireless/ieee-802.15.4/lib/libMiniMac_Sched.a", "${k32w0_sdk_root}/middleware/wireless/framework/PDM/Library/libPDM_extFlash.a", "${k32w0_sdk_root}/middleware/wireless/framework/XCVR/lib/libRadio.a", + "${k32w0_sdk_root}/middleware/wireless/framework/SecLib/lib_crypto_m4.a", ] - if (chip_crypto == "platform" && - chip_crypto_flavor == "NXP-Ultrafast-P256") { - libs += [ "${k32w0_sdk_root}/middleware/wireless/framework/SecLib/lib_crypto_m4_dspext.a" ] - } else { - libs += [ "${k32w0_sdk_root}/middleware/wireless/framework/SecLib/lib_crypto_m4.a" ] - } - defines = [ "gPWR_CpuClk_48MHz=1", "gMainThreadPriority_c=5", @@ -262,7 +272,7 @@ template("k32w0_sdk") { "USE_SDK_OSA=0", "gSerialManagerMaxInterfaces_c=2", "FSL_RTOS_FREE_RTOS=1", - "gTotalHeapSize_c=0x10000", + "gTotalHeapSize_c=0xF000", "DEBUG_SERIAL_INTERFACE_INSTANCE=0", "APP_SERIAL_INTERFACE_INSTANCE=1", "gOTA_externalFlash_d=1", @@ -272,7 +282,6 @@ template("k32w0_sdk") { "gExternalFlashIsCiphered_d=1", "PDM_USE_DYNAMIC_MEMORY=1", "PDM_SAVE_IDLE=1", - "PDM_ENCRYPTION=1", "gBootData_None_c=1", "PROGRAM_PAGE_SZ=256", "configFRTOS_MEMORY_SCHEME=4", @@ -305,6 +314,21 @@ template("k32w0_sdk") { "gResetSystemReset_d=1", ] + # If OTA default processors are enabled, then OTA custom entry structure + # will be saved in external flash: gOTACustomOtaEntryMemory=OTACustomStorage_ExtFlash (1) + if (chip_enable_ota_firmware_processor == 1) { + defines += [ + "gOTAAllowCustomStartAddress=1", + "gOTAUseCustomOtaEntry=1", + "gOTACustomOtaEntryMemory=1", + "OTA_ENTRY_TOP_ADDR=${ota_custom_entry_address}", + ] + + if (chip_enable_ota_factory_data_processor == 1) { + defines += [ "CONFIG_CHIP_K32W0_OTA_FACTORY_DATA_PROCESSOR=1" ] + } + } + if (chip_crypto == "platform" && chip_crypto_flavor == "NXP-Ultrafast-P256") { defines += [ "EC_P256_DSPEXT=1" ] @@ -316,6 +340,12 @@ template("k32w0_sdk") { defines += [ "gClkUseFro32K=0" ] } + if (chip_with_pdm_encryption == 1) { + defines += [ "PDM_ENCRYPTION=1" ] + } else { + defines += [ "PDM_ENCRYPTION=0" ] + } + if (chip_mdns == "platform") { defines += [ "OPENTHREAD_CONFIG_SRP_CLIENT_ENABLE=1", @@ -429,10 +459,6 @@ template("k32w0_sdk") { defines += [ "CONFIG_CHIP_K32W0_REAL_FACTORY_DATA=1" ] } - if (chip_enable_ota_default_processors == 1) { - defines += [ "CONFIG_CHIP_K32W0_OTA_DEFAULT_PROCESSORS=1" ] - } - if (defined(invoker.defines)) { defines += invoker.defines } diff --git a/third_party/nxp/k32w0_sdk/sdk_fixes/patch_k32w_sdk.sh b/third_party/nxp/k32w0_sdk/sdk_fixes/patch_k32w_sdk.sh index 9f7a1c517177a8..c9e048d64a4f39 100755 --- a/third_party/nxp/k32w0_sdk/sdk_fixes/patch_k32w_sdk.sh +++ b/third_party/nxp/k32w0_sdk/sdk_fixes/patch_k32w_sdk.sh @@ -15,5 +15,5 @@ convert_to_dos() { SOURCE=${BASH_SOURCE[0]} SOURCE_DIR=$(cd "$(dirname "$SOURCE")" >/dev/null 2>&1 && pwd) -echo "K32W0 SDK 2.6.9 no patch needed!" +echo "K32W0 SDK 2.6.11 no patch needed!" exit 0 diff --git a/third_party/openthread/ot-nxp b/third_party/openthread/ot-nxp index 4c2fa0d3d29c35..389c4776124c6d 160000 --- a/third_party/openthread/ot-nxp +++ b/third_party/openthread/ot-nxp @@ -1 +1 @@ -Subproject commit 4c2fa0d3d29c358fbdc57fa627ed2ba1e76dcdfc +Subproject commit 389c4776124c6dd887869cae060efa4eeccaf965