diff --git a/examples/lighting-app/nxp/k32w/k32w0/BUILD.gn b/examples/lighting-app/nxp/k32w/k32w0/BUILD.gn index b6592b564d8745..09bb0dde9f1760 100644 --- a/examples/lighting-app/nxp/k32w/k32w0/BUILD.gn +++ b/examples/lighting-app/nxp/k32w/k32w0/BUILD.gn @@ -76,7 +76,7 @@ k32w0_executable("light_app") { deps = [ ":sdk", "${chip_root}/examples/common/QRCode", - "${chip_root}/examples/lighting-app/lighting-common", + "${chip_root}/examples/ota-requestor-app/ota-requestor-common", "${chip_root}/src/lib", "${chip_root}/third_party/mbedtls:mbedtls", "${k32w0_platform_dir}/app/support:freertos_mbedtls_utils", diff --git a/examples/lighting-app/nxp/k32w/k32w0/args.gni b/examples/lighting-app/nxp/k32w/k32w0/args.gni index 0f921756420fce..114ae5310569ca 100644 --- a/examples/lighting-app/nxp/k32w/k32w0/args.gni +++ b/examples/lighting-app/nxp/k32w/k32w0/args.gni @@ -17,3 +17,7 @@ import("${chip_root}/examples/platform/nxp/k32w/k32w0/args.gni") # SDK target. This is overridden to add our SDK app_config.h & defines. k32w0_sdk_target = get_label_info(":sdk", "label_no_toolchain") + +declare_args() { + chip_enable_ota_requestor = true +} diff --git a/examples/lighting-app/nxp/k32w/k32w0/main/AppTask.cpp b/examples/lighting-app/nxp/k32w/k32w0/main/AppTask.cpp index e7ec334bc686ce..45ab2fb1002130 100644 --- a/examples/lighting-app/nxp/k32w/k32w0/main/AppTask.cpp +++ b/examples/lighting-app/nxp/k32w/k32w0/main/AppTask.cpp @@ -33,6 +33,13 @@ #include #include +/* OTA related includes */ +#include "OTAImageProcessorImpl.h" +#include "OtaSupport.h" +#include "platform/GenericOTARequestorDriver.h" +#include "src/app/clusters/ota-requestor/BDXDownloader.h" +#include "src/app/clusters/ota-requestor/OTARequestor.h" + #include "Keyboard.h" #include "LED.h" #include "LEDWidget.h" @@ -71,9 +78,20 @@ extern "C" void K32WUartProcess(void); using namespace ::chip::Credentials; using namespace ::chip::DeviceLayer; +using namespace chip; +; AppTask AppTask::sAppTask; +/* OTA related variables */ +static OTARequestor gRequestorCore; +DeviceLayer::GenericOTARequestorDriver gRequestorUser; +static BDXDownloader gDownloader; +static OTAImageProcessorImpl gImageProcessor; + +static NodeId providerNodeId = 2; +static FabricIndex providerFabricIndex = 1; + CHIP_ERROR AppTask::StartAppTask() { CHIP_ERROR err = CHIP_NO_ERROR; @@ -103,6 +121,32 @@ CHIP_ERROR AppTask::Init() SetDeviceAttestationCredentialsProvider(Examples::GetExampleDACProvider()); #endif + // Initialize and interconnect the Requestor and Image Processor objects -- START + SetRequestorInstance(&gRequestorCore); + + // Set server instance used for session establishment + chip::Server * server = &(chip::Server::GetInstance()); + gRequestorCore.SetServerInstance(server); + + // Connect the Requestor and Requestor Driver objects + gRequestorCore.SetOtaRequestorDriver(&gRequestorUser); + gRequestorUser.Init(&gRequestorCore, &gImageProcessor); + + // WARNING: this is probably not realistic to know such details of the image or to even have an OTADownloader instantiated at + // the beginning of program execution. We're using hardcoded values here for now since this is a reference application. + // TODO: instatiate and initialize these values when QueryImageResponse tells us an image is available + // TODO: add API for OTARequestor to pass QueryImageResponse info to the application to use for OTADownloader init + OTAImageProcessorParams ipParams; + ipParams.imageFile = CharSpan("test.txt"); + gImageProcessor.SetOTAImageProcessorParams(ipParams); + gImageProcessor.SetOTADownloader(&gDownloader); + + // Connect the gDownloader and Image Processor objects + gDownloader.SetImageProcessorDelegate(&gImageProcessor); + + gRequestorCore.SetBDXDownloader(&gDownloader); + // Initialize and interconnect the Requestor and Image Processor objects -- END + // QR code will be used with CHIP Tool PrintOnboardingCodes(chip::RendezvousInformationFlags(chip::RendezvousInformationFlag::kBLE)); @@ -230,12 +274,22 @@ void AppTask::AppTaskMain(void * pvParameter) sLightLED.Animate(); HandleKeyboard(); + + if (gDownloader.GetState() == OTADownloader::State::kInProgress) + { + OTA_TransactionResume(); + + if (!EEPROM_isBusy()) + { + gDownloader.FetchNextData(); + } + } } } void AppTask::ButtonEventHandler(uint8_t pin_no, uint8_t button_action) { - if ((pin_no != RESET_BUTTON) && (pin_no != LIGHT_BUTTON) && (pin_no != JOIN_BUTTON) && (pin_no != BLE_BUTTON)) + if ((pin_no != RESET_BUTTON) && (pin_no != LIGHT_BUTTON) && (pin_no != OTA_BUTTON) && (pin_no != BLE_BUTTON)) { return; } @@ -253,9 +307,9 @@ void AppTask::ButtonEventHandler(uint8_t pin_no, uint8_t button_action) { button_event.Handler = LightActionEventHandler; } - else if (pin_no == JOIN_BUTTON) + else if (pin_no == OTA_BUTTON) { - button_event.Handler = JoinHandler; + button_event.Handler = OTAHandler; } else if (pin_no == BLE_BUTTON) { @@ -307,7 +361,7 @@ void AppTask::HandleKeyboard(void) ButtonEventHandler(LIGHT_BUTTON, LIGHT_BUTTON_PUSH); break; case gKBD_EventPB3_c: - ButtonEventHandler(JOIN_BUTTON, JOIN_BUTTON_PUSH); + ButtonEventHandler(OTA_BUTTON, OTA_BUTTON_PUSH); break; case gKBD_EventPB4_c: ButtonEventHandler(BLE_BUTTON, BLE_BUTTON_PUSH); @@ -435,43 +489,21 @@ void AppTask::LightActionEventHandler(AppEvent * aEvent) } } -void AppTask::ThreadStart() +void AppTask::OTAHandler(AppEvent * aEvent) { - chip::Thread::OperationalDataset dataset{}; - - constexpr uint8_t xpanid[] = { 0xde, 0xad, 0x00, 0xbe, 0xef, 0x00, 0xca, 0xfe }; - constexpr uint8_t masterkey[] = { - 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF - }; - constexpr uint16_t panid = 0xabcd; - constexpr uint16_t channel = 15; - - dataset.SetNetworkName("OpenThread"); - dataset.SetExtendedPanId(xpanid); - dataset.SetMasterKey(masterkey); - dataset.SetPanId(panid); - dataset.SetChannel(channel); - - ThreadStackMgr().SetThreadEnabled(false); - ThreadStackMgr().SetThreadProvision(dataset.AsByteSpan()); - ThreadStackMgr().SetThreadEnabled(true); -} - -void AppTask::JoinHandler(AppEvent * aEvent) -{ - if (aEvent->ButtonEvent.PinNo != JOIN_BUTTON) + if (aEvent->ButtonEvent.PinNo != OTA_BUTTON) return; if (sAppTask.mFunction != kFunction_NoneSelected) { - K32W_LOG("Another function is scheduled. Could not initiate Thread Join!"); + K32W_LOG("Another function is scheduled. Could not initiate OTA!"); return; } - /* hard-code Thread Commissioning Parameters for the moment. - * In a future PR, these parameters will be sent via BLE. - */ - ThreadStart(); + // In this mode Provider node ID and fabric idx must be supplied explicitly from program args + gRequestorCore.TestModeSetProviderParameters(providerNodeId, providerFabricIndex, chip::kRootEndpointId); + + static_cast(GetRequestorInstance())->TriggerImmediateQuery(); } void AppTask::BleHandler(AppEvent * aEvent) diff --git a/examples/lighting-app/nxp/k32w/k32w0/main/include/AppTask.h b/examples/lighting-app/nxp/k32w/k32w0/main/include/AppTask.h index 7099eb2ea297c5..73ad95aebd50bc 100644 --- a/examples/lighting-app/nxp/k32w/k32w0/main/include/AppTask.h +++ b/examples/lighting-app/nxp/k32w/k32w0/main/include/AppTask.h @@ -63,7 +63,7 @@ class AppTask static void FunctionTimerEventHandler(AppEvent * aEvent); static void KBD_Callback(uint8_t events); static void HandleKeyboard(void); - static void JoinHandler(AppEvent * aEvent); + static void OTAHandler(AppEvent * aEvent); static void BleHandler(AppEvent * aEvent); static void LightActionEventHandler(AppEvent * aEvent); static void ResetActionEventHandler(AppEvent * aEvent); @@ -73,8 +73,6 @@ class AppTask static void TimerEventHandler(TimerHandle_t xTimer); static void ThreadProvisioningHandler(const chip::DeviceLayer::ChipDeviceEvent * event, intptr_t arg); - - static void ThreadStart(); void StartTimer(uint32_t aTimeoutInMs); enum Function_t diff --git a/examples/lighting-app/nxp/k32w/k32w0/main/include/app_config.h b/examples/lighting-app/nxp/k32w/k32w0/main/include/app_config.h index 8dc8697f645393..93c5530f937243 100644 --- a/examples/lighting-app/nxp/k32w/k32w0/main/include/app_config.h +++ b/examples/lighting-app/nxp/k32w/k32w0/main/include/app_config.h @@ -24,12 +24,12 @@ #define RESET_BUTTON 1 #define LIGHT_BUTTON 2 -#define JOIN_BUTTON 3 +#define OTA_BUTTON 3 #define BLE_BUTTON 4 #define RESET_BUTTON_PUSH 1 #define LIGHT_BUTTON_PUSH 2 -#define JOIN_BUTTON_PUSH 3 +#define OTA_BUTTON_PUSH 3 #define BLE_BUTTON_PUSH 4 #define APP_BUTTON_PUSH 1 diff --git a/src/platform/nxp/k32w/k32w0/BUILD.gn b/src/platform/nxp/k32w/k32w0/BUILD.gn index 3c44882b296b42..eb3e52b0becaf0 100644 --- a/src/platform/nxp/k32w/k32w0/BUILD.gn +++ b/src/platform/nxp/k32w/k32w0/BUILD.gn @@ -50,6 +50,13 @@ static_library("k32w0") { "ble_function_mux.c", ] + if (chip_enable_ota_requestor) { + sources += [ + "OTAImageProcessorImpl.cpp", + "OTAImageProcessorImpl.h", + ] + } + deps = [] public_deps = [ "${chip_root}/src/platform:platform_base" ] diff --git a/src/platform/nxp/k32w/k32w0/OTAImageProcessorImpl.cpp b/src/platform/nxp/k32w/k32w0/OTAImageProcessorImpl.cpp new file mode 100644 index 00000000000000..f377ceba34906e --- /dev/null +++ b/src/platform/nxp/k32w/k32w0/OTAImageProcessorImpl.cpp @@ -0,0 +1,201 @@ +/* + * + * Copyright (c) 2021 Project CHIP Authors + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include "OTAImageProcessorImpl.h" +#include "OtaSupport.h" +#include "OtaUtils.h" + +extern "C" void ResetMCU(void); + +namespace chip { + +CHIP_ERROR OTAImageProcessorImpl::PrepareDownload() +{ + if (mParams.imageFile.empty()) + { + ChipLogError(SoftwareUpdate, "Invalid output image file supplied"); + return CHIP_ERROR_INTERNAL; + } + + DeviceLayer::PlatformMgr().ScheduleWork(HandlePrepareDownload, reinterpret_cast(this)); + return CHIP_NO_ERROR; +} + +CHIP_ERROR OTAImageProcessorImpl::Finalize() +{ + DeviceLayer::PlatformMgr().ScheduleWork(HandleFinalize, reinterpret_cast(this)); + return CHIP_NO_ERROR; +} + +CHIP_ERROR OTAImageProcessorImpl::Apply() +{ + return CHIP_NO_ERROR; +} + +CHIP_ERROR OTAImageProcessorImpl::Abort() +{ + if (mParams.imageFile.empty()) + { + ChipLogError(SoftwareUpdate, "Invalid output image file supplied"); + return CHIP_ERROR_INTERNAL; + } + + DeviceLayer::PlatformMgr().ScheduleWork(HandleAbort, reinterpret_cast(this)); + return CHIP_NO_ERROR; +} + +CHIP_ERROR OTAImageProcessorImpl::ProcessBlock(ByteSpan & block) +{ + if ((block.data() == nullptr) || block.empty()) + { + return CHIP_ERROR_INVALID_ARGUMENT; + } + + // Store block data for HandleProcessBlock to access + CHIP_ERROR err = SetBlock(block); + if (err != CHIP_NO_ERROR) + { + ChipLogError(SoftwareUpdate, "Cannot set block data: %" CHIP_ERROR_FORMAT, err.Format()); + } + + DeviceLayer::PlatformMgr().ScheduleWork(HandleProcessBlock, reinterpret_cast(this)); + return CHIP_NO_ERROR; +} + +void OTAImageProcessorImpl::HandlePrepareDownload(intptr_t context) +{ + auto * imageProcessor = reinterpret_cast(context); + if (imageProcessor == nullptr) + { + ChipLogError(SoftwareUpdate, "ImageProcessor context is null"); + return; + } + else if (imageProcessor->mDownloader == nullptr) + { + ChipLogError(SoftwareUpdate, "mDownloader is null"); + return; + } + + if (gOtaSuccess_c == OTA_ClientInit()) + { + if (gOtaSuccess_c == OTA_StartImage(imageProcessor->mParams.imageFile.size())) + { + imageProcessor->mDownloader->OnPreparedForDownload(CHIP_NO_ERROR); + } + } +} + +void OTAImageProcessorImpl::HandleAbort(intptr_t context) +{ + auto * imageProcessor = reinterpret_cast(context); + if (imageProcessor == nullptr) + { + return; + } + + remove(imageProcessor->mParams.imageFile.data()); + imageProcessor->ReleaseBlock(); +} + +void OTAImageProcessorImpl::HandleProcessBlock(intptr_t context) +{ + auto * imageProcessor = reinterpret_cast(context); + if (imageProcessor == nullptr) + { + ChipLogError(SoftwareUpdate, "ImageProcessor context is null"); + return; + } + else if (imageProcessor->mDownloader == nullptr) + { + ChipLogError(SoftwareUpdate, "mDownloader is null"); + return; + } + + /* Will start an erase of 4K if necessary */ + if (gOtaSuccess_c == OTA_MakeHeadRoomForNextBlock(imageProcessor->mBlock.size(), NULL, 0)) + { + if (gOtaSuccess_c == + OTA_PushImageChunk(imageProcessor->mBlock.data(), (uint16_t) imageProcessor->mBlock.size(), NULL, NULL)) + { + imageProcessor->mParams.downloadedBytes += imageProcessor->mBlock.size(); + return; + } + } +} + +CHIP_ERROR OTAImageProcessorImpl::SetBlock(ByteSpan & block) +{ + if ((block.data() == nullptr) || block.empty()) + { + return CHIP_NO_ERROR; + } + + // Allocate memory for block data if it has not been done yet + if (mBlock.empty()) + { + mBlock = MutableByteSpan(static_cast(chip::Platform::MemoryAlloc(block.size())), block.size()); + if (mBlock.data() == nullptr) + { + return CHIP_ERROR_NO_MEMORY; + } + } + + // Store the actual block data + CHIP_ERROR err = CopySpanToMutableSpan(block, mBlock); + if (err != CHIP_NO_ERROR) + { + ChipLogError(SoftwareUpdate, "Cannot copy block data: %" CHIP_ERROR_FORMAT, err.Format()); + return err; + } + + return CHIP_NO_ERROR; +} + +void OTAImageProcessorImpl::HandleFinalize(intptr_t context) +{ + auto * imageProcessor = reinterpret_cast(context); + if (imageProcessor == nullptr) + { + return; + } + + OTA_CommitImage(NULL); + if (OTA_ImageAuthenticate() == gOtaImageAuthPass_c) + { + /* Set the necessary information to inform the SSBL that a new image is available */ + OTA_SetNewImageFlag(); + ResetMCU(); + } + + imageProcessor->ReleaseBlock(); +} + +CHIP_ERROR OTAImageProcessorImpl::ReleaseBlock() +{ + if (mBlock.data() != nullptr) + { + chip::Platform::MemoryFree(mBlock.data()); + } + + mBlock = MutableByteSpan(); + return CHIP_NO_ERROR; +} + +} // namespace chip diff --git a/src/platform/nxp/k32w/k32w0/OTAImageProcessorImpl.h b/src/platform/nxp/k32w/k32w0/OTAImageProcessorImpl.h new file mode 100644 index 00000000000000..05d6beb941e8ce --- /dev/null +++ b/src/platform/nxp/k32w/k32w0/OTAImageProcessorImpl.h @@ -0,0 +1,60 @@ +/* + * + * Copyright (c) 2021 Project CHIP Authors + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include + +namespace chip { + +class OTAImageProcessorImpl : public OTAImageProcessorInterface +{ +public: + //////////// OTAImageProcessorInterface Implementation /////////////// + CHIP_ERROR PrepareDownload() override; + CHIP_ERROR Finalize() override; + CHIP_ERROR Apply() override; + CHIP_ERROR Abort() override; + CHIP_ERROR ProcessBlock(ByteSpan & block) override; + + void SetOTADownloader(OTADownloader * downloader) { mDownloader = downloader; } + +private: + //////////// Actual handlers for the OTAImageProcessorInterface /////////////// + static void HandlePrepareDownload(intptr_t context); + static void HandleFinalize(intptr_t context); + static void HandleAbort(intptr_t context); + static void HandleProcessBlock(intptr_t context); + + /** + * Called to allocate memory for mBlock if necessary and set it to block + */ + CHIP_ERROR SetBlock(ByteSpan & block); + + /** + * Called to release allocated memory for mBlock + */ + CHIP_ERROR ReleaseBlock(); + + OTADownloader * mDownloader; + MutableByteSpan mBlock; +}; + +} // namespace chip diff --git a/src/platform/nxp/k32w/k32w0/args.gni b/src/platform/nxp/k32w/k32w0/args.gni index 55b943e941d844..5bfab05ecaff7e 100644 --- a/src/platform/nxp/k32w/k32w0/args.gni +++ b/src/platform/nxp/k32w/k32w0/args.gni @@ -29,8 +29,8 @@ chip_build_tests = false chip_mdns = "platform" # Trying to fit into the available flash by disabling optional logging for now -chip_detail_logging = false -chip_progress_logging = false +chip_detail_logging = true +chip_progress_logging = true mbedtls_target = "${chip_root}/third_party/nxp/k32w0_sdk:mbedtls" openthread_external_mbedtls = mbedtls_target diff --git a/third_party/nxp/k32w0_sdk/k32w0_sdk.gni b/third_party/nxp/k32w0_sdk/k32w0_sdk.gni index e6e587e522616c..c08806190eda51 100644 --- a/third_party/nxp/k32w0_sdk/k32w0_sdk.gni +++ b/third_party/nxp/k32w0_sdk/k32w0_sdk.gni @@ -94,6 +94,7 @@ template("k32w0_sdk") { "${k32w0_sdk_root}/middleware/wireless/framework/Messaging/Interface", "${k32w0_sdk_root}/middleware/wireless/framework/ModuleInfo", "${k32w0_sdk_root}/middleware/wireless/framework/OSAbstraction/Interface", + "${k32w0_sdk_root}/middleware/wireless/framework/OtaSupport/Interface/", "${k32w0_sdk_root}/middleware/wireless/framework/Panic/Interface", "${k32w0_sdk_root}/middleware/wireless/framework/PDM/Include", "${k32w0_sdk_root}/middleware/wireless/framework/RNG/Interface", @@ -143,6 +144,9 @@ template("k32w0_sdk") { "PDM_EXT_FLASH=1", "gEepromType_d=gEepromDevice_MX25R8035F_c", "gOTA_externalFlash_d=1", + "gEepromPostedOperations_d=1", + "gOtaEepromPostedOperations_d=1", + "PROGRAM_PAGE_SZ=256", "gKeyBoardSupported_d", "gPdmNbSegments=63", "configFRTOS_MEMORY_SCHEME=4", @@ -154,7 +158,8 @@ template("k32w0_sdk") { "gTimestamp_Enabled_d=0", "K32W_LOG_ENABLED=1", "CHIP_ENABLE_OPENTHREAD=1", - "PoolsDetails_c=_block_size_ 32 _number_of_blocks_ 6 _pool_id_(0) _eol_ _block_size_ 256 _number_of_blocks_ 3 _pool_id_(0) _eol_ _block_size_ 512 _number_of_blocks_ 2 _pool_id_(0) _eol_ _block_size_ 768 _number_of_blocks_ 1 _pool_id_(0) _eol_", + "gOtaMemPoolId_c=1", + "PoolsDetails_c=_block_size_ 32 _number_of_blocks_ 6 _pool_id_(0) _eol_ _block_size_ 256 _number_of_blocks_ 3 _pool_id_(0) _eol_ _block_size_ 512 _number_of_blocks_ 2 _pool_id_(0) _eol_ _block_size_ 768 _number_of_blocks_ 1 _pool_id_(0) _eol_ _block_size_ 268 _number_of_blocks_ 4 _pool_id_(gOtaMemPoolId_c) _eol_", "SUPPORT_FOR_15_4=1", "gAppMaxConnections_c=1", "gAppUseBonding_d=0", @@ -165,7 +170,7 @@ template("k32w0_sdk") { "SUPPORT_FOR_BLE=1", "gEnableBleInactivityTimeNotify=1", "DUAL_MODE_APP=1", - "gMainThreadStackSize_c=3096", + "gMainThreadStackSize_c=5096", "HEAP_SIZE=0xF000", "gLoggingActive_d=0", "gLogRingPlacementOffset_c=0xF000", @@ -306,6 +311,8 @@ template("k32w0_sdk") { "${k32w0_sdk_root}/middleware/wireless/framework/MemManager/Source/MemManager.c", "${k32w0_sdk_root}/middleware/wireless/framework/Messaging/Source/Messaging.c", "${k32w0_sdk_root}/middleware/wireless/framework/OSAbstraction/Source/fsl_os_abstraction_free_rtos.c", + "${k32w0_sdk_root}/middleware/wireless/framework/OtaSupport/Source/OtaSupport.c", + "${k32w0_sdk_root}/middleware/wireless/framework/OtaSupport/Source/OtaUtils.c", "${k32w0_sdk_root}/middleware/wireless/framework/PDM/pdm_port.c", "${k32w0_sdk_root}/middleware/wireless/framework/Panic/Source/Panic.c", "${k32w0_sdk_root}/middleware/wireless/framework/RNG/Source/RNG.c", diff --git a/third_party/nxp/k32w0_sdk/sdk_fixes/OtaUtils.c b/third_party/nxp/k32w0_sdk/sdk_fixes/OtaUtils.c new file mode 100644 index 00000000000000..c38167dcad8a6d --- /dev/null +++ b/third_party/nxp/k32w0_sdk/sdk_fixes/OtaUtils.c @@ -0,0 +1,594 @@ +/*! ********************************************************************************* + * Copyright 2020 NXP + * All rights reserved. + * + * \file + * + * This is the header file for the OTA Programming Support. + * + ** SPDX-License-Identifier: BSD-3-Clause + ********************************************************************************** */ + +/*! ********************************************************************************* +************************************************************************************* +* Include +************************************************************************************* +********************************************************************************** */ + +#include "OtaUtils.h" + +/* Driver includes */ +#include "fsl_flash.h" +#include "fsl_sha.h" + +#include "flash_header.h" +#include "rom_aes.h" +#include "rom_api.h" +#include "rom_psector.h" +#include "rom_secure.h" + +/************************************************************************************ +************************************************************************************* +* Private Macros +************************************************************************************* +************************************************************************************/ + +#define THUMB_ENTRY(x) (void *) ((x) | 1) +#define CRC_FINALIZE(x) ((x) ^ ~0UL) + +#ifdef PDM_EXT_FLASH +#define BOOT_BLOCK_OFFSET_MAX_VALUE 0x9de00 +#else +#define BOOT_BLOCK_OFFSET_MAX_VALUE 0x96000 +#endif + +#define SIGNATURE_WRD_LEN (SIGNATURE_LEN / 4) + +#define ROM_API_efuse_LoadUniqueKey THUMB_ENTRY(0x030016f4) +#define ROM_API_aesLoadKeyFromOTP THUMB_ENTRY(0x0300146c) +#define ROM_API_crc_update THUMB_ENTRY(0x0300229c) +#define ROM_API_boot_CheckVectorSum THUMB_ENTRY(0x03000648) +#define ROM_API_flash_GetDmaccStatus THUMB_ENTRY(0x03001f64) + +#define BUFFER_SHA_LENGTH 16 +#define OTA_UTILS_DEBUG(...) + +/************************************************************************************ +************************************************************************************* +* Private definitions +************************************************************************************* +************************************************************************************/ + +typedef struct +{ + IMAGE_CERT_T certificate; + uint8_t signature[SIGNATURE_LEN]; +} ImageCertificate_t; + +typedef struct +{ + uint8_t signature[SIGNATURE_LEN]; +} ImageSignature_t; + +typedef struct +{ + uint16_t blob_id; + uint32_t blob_version; +} ImageCompatibilityListElem_t; + +typedef union +{ + IMG_HEADER_T imgHeader; + BOOT_BLOCK_T imgBootBlock; + ImageCertificate_t imgCertificate; /* will contains only the img signature if no certificate is given */ +} ImageParserUnion; + +typedef int (*efuse_LoadUniqueKey_t)(void); +typedef uint32_t (*aesLoadKeyFromOTP_t)(AES_KEY_SIZE_T keySize); +typedef uint32_t (*crc_update_t)(uint32_t crc, const void * data, size_t data_len); +typedef uint32_t (*boot_CheckVectorSum_t)(const IMG_HEADER_T * image); +typedef uint32_t (*flash_GetDmaccStatus_t)(uint8_t * address); + +/************************************************************************************ +************************************************************************************* +* Private memory declarations +************************************************************************************* +************************************************************************************/ + +static const efuse_LoadUniqueKey_t efuse_LoadUniqueKey = (efuse_LoadUniqueKey_t) ROM_API_efuse_LoadUniqueKey; +static const aesLoadKeyFromOTP_t aesLoadKeyFromOTP = (aesLoadKeyFromOTP_t) ROM_API_aesLoadKeyFromOTP; +static const crc_update_t crc_update = (crc_update_t) ROM_API_crc_update; +static const boot_CheckVectorSum_t boot_CheckVectorSum = (boot_CheckVectorSum_t) ROM_API_boot_CheckVectorSum; +static const flash_GetDmaccStatus_t flash_GetDmaccStatus = (flash_GetDmaccStatus_t) ROM_API_flash_GetDmaccStatus; + +/****************************************************************************** +******************************************************************************* +* Private functions +******************************************************************************* +******************************************************************************/ + +static bool_t OtaUtils_IsInternalFlashAddr(uint32_t image_addr) +{ + uint32_t internalFlashAddrStart = 0; + uint32_t internalFlashSize = 0; + ROM_GetFlash(&internalFlashAddrStart, &internalFlashSize); + return ((image_addr >= internalFlashAddrStart) && image_addr < (internalFlashAddrStart + internalFlashSize)); +} + +/* In case of wrong ImgType, IMG_TYPE_NB is returned */ +static uint8_t OtaUtils_CheckImageTypeFromImgHeader(const IMG_HEADER_T * pImageHeader) +{ + uint8_t imgType = IMG_DIRECTORY_MAX_SIZE; + if (pImageHeader && pImageHeader->imageSignature >= IMAGE_SIGNATURE && + pImageHeader->imageSignature < IMAGE_SIGNATURE + IMG_DIRECTORY_MAX_SIZE) + { + imgType = (pImageHeader->imageSignature - IMAGE_SIGNATURE); + } + return imgType; +} + +static otaUtilsResult_t OtaUtils_ReadFromEncryptedExtFlash(uint16_t nbBytesToRead, uint32_t address, uint8_t * pOutbuf, + OtaUtils_EEPROM_ReadData pFunctionEepromRead, eEncryptionKeyType eType, + void * pParam) +{ + otaUtilsResult_t result = gOtaUtilsError_c; + otaUtilsResult_t readResult = gOtaUtilsSuccess_c; + uint8_t alignedBufferStart[16]; + uint8_t alignedBufferEnd[16]; + uint16_t nbByteToRead = nbBytesToRead; + uint8_t * pBuf = pOutbuf; + + uint32_t lastAddrToRead = address + nbBytesToRead - 1; + + /* Encrypted reads require to have an addr aligned on 16 bytes */ + uint16_t nbByteToAlignStart = address % 16; + uint16_t nbByteToAlignEnd = (16 * (lastAddrToRead / 16) + 15) - lastAddrToRead; + uint16_t nbByteToMoveInAlignedBufferStart = 0; + uint16_t nbByteToMoveInAlignedBufferEnd = 0; + uint16_t nbByteToReadBeforeEndAlignBuffer = 0; + + address -= nbByteToAlignStart; + + do + { +#ifdef DEBUG + if ((nbByteToRead + nbByteToAlignStart + nbByteToAlignEnd) % 16 != 0) + break; +#endif + /* Get the number of block that we will need to read */ + int nb_blocks = (nbByteToRead + nbByteToAlignStart + nbByteToAlignEnd) / 16; + + if (nbByteToAlignStart) + { + if ((readResult = pFunctionEepromRead(sizeof(alignedBufferStart), address, &alignedBufferStart[0])) != + gOtaUtilsSuccess_c) + { + result = readResult; + break; + } + else + { + address += sizeof(alignedBufferStart); + } + } + + /* Check if we need to read more bytes */ + if (address < lastAddrToRead) + { + if (nbByteToAlignStart) + { + nbByteToMoveInAlignedBufferStart = sizeof(alignedBufferStart) - nbByteToAlignStart; + pBuf += nbByteToMoveInAlignedBufferStart; + } + + if (nbByteToAlignEnd) + { + nbByteToMoveInAlignedBufferEnd = sizeof(alignedBufferEnd) - nbByteToAlignEnd; + } + nbByteToReadBeforeEndAlignBuffer = nbByteToRead - nbByteToMoveInAlignedBufferStart - nbByteToMoveInAlignedBufferEnd; + if (nbByteToReadBeforeEndAlignBuffer % 16 != 0) + break; + if ((readResult = pFunctionEepromRead(nbByteToReadBeforeEndAlignBuffer, address, pBuf)) != gOtaUtilsSuccess_c) + { + result = readResult; + break; + } + address += nbByteToReadBeforeEndAlignBuffer; + if (nbByteToAlignEnd && + (readResult = pFunctionEepromRead(sizeof(alignedBufferEnd), address, alignedBufferEnd)) != gOtaUtilsSuccess_c) + { + result = readResult; + break; + } + } + else + { + /* The asked buffer is too small and can fit in alignedBufferStart */ + nbByteToAlignEnd = 0; + } + + if (eType == eEfuseKey) + { + // aesInit(); + efuse_LoadUniqueKey(); + aesLoadKeyFromOTP(AES_KEY_128BITS); + } + else if (eType == eSoftwareKey && pParam != NULL) + { + sOtaUtilsSoftwareKey * pSoftKey = (sOtaUtilsSoftwareKey *) pParam; + aesLoadKeyFromSW(AES_KEY_128BITS, (uint32_t *) pSoftKey->pSoftKeyAes); + break; + } + + aesMode(AES_MODE_ECB_DECRYPT, AES_INT_BSWAP | AES_OUTT_BSWAP); + if (nbByteToAlignStart) + { + aesProcess((void *) alignedBufferStart, (void *) alignedBufferStart, 1); + nb_blocks -= 1; + } + if (nbByteToAlignEnd) + { + aesProcess((void *) pBuf, (void *) pBuf, nb_blocks - 1); + aesProcess((void *) alignedBufferEnd, (void *) alignedBufferEnd, 1); + } + else + { + aesProcess((void *) pBuf, (void *) pBuf, nb_blocks); + } + + /* Fill missing pBuf bytes */ + pBuf -= nbByteToMoveInAlignedBufferStart; + + if (nbByteToAlignStart) + { + uint16_t i; + uint16_t t = 0; + for (i = nbByteToAlignStart; i < sizeof(alignedBufferStart); i++) + { + pBuf[t++] = alignedBufferStart[i]; + } + } + + if (nbByteToAlignEnd) + { + uint16_t i; + for (i = 0; i < nbByteToMoveInAlignedBufferEnd; i++) + { + *(pBuf + nbByteToMoveInAlignedBufferStart + nbByteToReadBeforeEndAlignBuffer + i) = alignedBufferEnd[i]; + } + } + result = gOtaUtilsSuccess_c; + } while (0); + + return result; +} + +static bool_t OtaUtils_VerifySignature(uint32_t address, uint32_t nbBytesToRead, const uint32_t * pPublicKey, + const uint8_t * pSignatureToVerify, OtaUtils_ReadBytes pFunctionRead, + void * pReadFunctionParam, OtaUtils_EEPROM_ReadData pFunctionEepromRead) +{ + bool_t result = FALSE; + uint32_t nbPageToRead = nbBytesToRead / BUFFER_SHA_LENGTH; + uint32_t lastPageNbByteNumber = nbBytesToRead - (nbPageToRead * BUFFER_SHA_LENGTH); + uint32_t i = 0; + do + { + uint8_t pageContent[BUFFER_SHA_LENGTH]; + uint8_t digest[32]; + sha_ctx_t hash_ctx; + size_t sha_sz = 32; + /* Initialise SHA clock do not call SHA_ClkInit(SHA0) because the HAL pulls in too much code */ + SYSCON->AHBCLKCTRLSET[1] = SYSCON_AHBCLKCTRL1_HASH_MASK; + if (SHA_Init(SHA0, &hash_ctx, kSHA_Sha256) != kStatus_Success) + break; + for (i = 0; i < nbPageToRead; i++) + { + if (pFunctionRead(sizeof(pageContent), address + (i * BUFFER_SHA_LENGTH), &pageContent[0], pReadFunctionParam, + pFunctionEepromRead) != gOtaUtilsSuccess_c) + break; + if (SHA_Update(SHA0, &hash_ctx, (const uint8_t *) pageContent, BUFFER_SHA_LENGTH) != kStatus_Success) + break; + } + /* Read bytes located on the last page */ + if (pFunctionRead(lastPageNbByteNumber, address + (i * BUFFER_SHA_LENGTH), &pageContent[0], pReadFunctionParam, + pFunctionEepromRead) != gOtaUtilsSuccess_c) + break; + if (SHA_Update(SHA0, &hash_ctx, (const uint8_t *) pageContent, lastPageNbByteNumber) != kStatus_Success) + break; + if (SHA_Finish(SHA0, &hash_ctx, &digest[0], &sha_sz) != kStatus_Success) + break; + if (!secure_VerifySignature(digest, pSignatureToVerify, pPublicKey)) + break; + result = TRUE; + } while (0); + + SYSCON->AHBCLKCTRLCLR[1] = SYSCON_AHBCLKCTRL1_HASH_MASK; /* equivalent to SHA_ClkDeinit(SHA0) */ + return result; +} + +static bool_t OtaUtils_FindBlankPage(uint32_t startAddr, uint16_t size) +{ + bool_t result = FALSE; + uint32_t addrIterator = startAddr; + + while (addrIterator < startAddr + size) + { + if (flash_GetDmaccStatus((uint8_t *) addrIterator) == 0) + { + result = TRUE; + break; + } + addrIterator += FLASH_PAGE_SIZE; + } + + /* Check the endAddr */ + if (!result && flash_GetDmaccStatus((uint8_t *) startAddr + size) == 0) + result = TRUE; + + return result; +} + +/****************************************************************************** +******************************************************************************* +* Public functions +******************************************************************************* +******************************************************************************/ + +otaUtilsResult_t OtaUtils_ReadFromInternalFlash(uint16_t nbBytesToRead, uint32_t address, uint8_t * pOutbuf, void * pParam, + OtaUtils_EEPROM_ReadData pFunctionEepromRead) +{ + otaUtilsResult_t result = gOtaUtilsError_c; + + do + { + if (!OtaUtils_IsInternalFlashAddr(address)) + break; + /* If one blank page is found return error */ + if (OtaUtils_FindBlankPage(address, nbBytesToRead)) + break; + if (pFunctionEepromRead == NULL || pOutbuf == NULL) + break; + pFunctionEepromRead(nbBytesToRead, address, pOutbuf); + result = gOtaUtilsSuccess_c; + } while (0); + + return result; +} + +otaUtilsResult_t OtaUtils_ReadFromUnencryptedExtFlash(uint16_t nbBytesToRead, uint32_t address, uint8_t * pOutbuf, void * pParam, + OtaUtils_EEPROM_ReadData pFunctionEepromRead) +{ + otaUtilsResult_t result = gOtaUtilsError_c; + if (pFunctionEepromRead != NULL) + { + result = pFunctionEepromRead(nbBytesToRead, address, pOutbuf); + } + return result; +} + +otaUtilsResult_t OtaUtils_ReadFromEncryptedExtFlashEfuseKey(uint16_t nbBytesToRead, uint32_t address, uint8_t * pOutbuf, + void * pParam, OtaUtils_EEPROM_ReadData pFunctionEepromRead) +{ + return OtaUtils_ReadFromEncryptedExtFlash(nbBytesToRead, address, pOutbuf, pFunctionEepromRead, eEfuseKey, NULL); +} + +otaUtilsResult_t OtaUtils_ReadFromEncryptedExtFlashSoftwareKey(uint16_t nbBytesToRead, uint32_t address, uint8_t * pOutbuf, + void * pParam, OtaUtils_EEPROM_ReadData pFunctionEepromRead) +{ + return OtaUtils_ReadFromEncryptedExtFlash(nbBytesToRead, address, pOutbuf, pFunctionEepromRead, eSoftwareKey, pParam); +} + +uint32_t OtaUtils_ValidateImage(OtaUtils_ReadBytes pFunctionRead, void * pReadFunctionParam, + OtaUtils_EEPROM_ReadData pFunctionEepromRead, uint32_t imgAddr, uint32_t minValidAddr, + const IMAGE_CERT_T * pRootCert, bool_t inOtaCheck, bool_t isRemappable) +{ + uint32_t result_addr = OTA_UTILS_IMAGE_INVALID_ADDR; + ImageParserUnion uImgParser; + uint32_t headerBootBlockMarker = 0; + uint32_t runAddr = 0; + uint8_t imgType = IMG_DIRECTORY_MAX_SIZE; + uint32_t bootBlockOffsetFound = 0; + uint32_t imgSizeFound = 0; + + do + { + /* Try to extract the imageHeader */ + if (pFunctionRead(sizeof(IMG_HEADER_T), imgAddr, (uint8_t *) &uImgParser.imgHeader, pReadFunctionParam, + pFunctionEepromRead) != gOtaUtilsSuccess_c) + break; + + imgType = OtaUtils_CheckImageTypeFromImgHeader(&uImgParser.imgHeader); + if (imgType == IMG_DIRECTORY_MAX_SIZE) + { + break; + } + + if (isRemappable) + { + /* Check that entry point is within tested archive */ + runAddr = (uImgParser.imgHeader.vectors[1] & ~0xfffUL); + } + else + { + runAddr = imgAddr; + } + + if (!inOtaCheck) + { + if (runAddr != imgAddr) + break; + } + + if (uImgParser.imgHeader.bootBlockOffset % sizeof(uint32_t)) + break; + + if (uImgParser.imgHeader.bootBlockOffset + sizeof(BOOT_BLOCK_T) >= BOOT_BLOCK_OFFSET_MAX_VALUE) + break; + + /* compute CRC of the header */ + uint32_t crc = ~0UL; + crc = crc_update(crc, &uImgParser.imgHeader, sizeof(IMG_HEADER_T) - sizeof(uImgParser.imgHeader.header_crc)); + crc = CRC_FINALIZE(crc); + + if (uImgParser.imgHeader.header_crc != crc) + break; + + if (boot_CheckVectorSum(&uImgParser.imgHeader) != 0) + break; + + /* Save data before parsing the bootblock */ + bootBlockOffsetFound = uImgParser.imgHeader.bootBlockOffset; + + /* Try to extract the bootblock */ + if (pFunctionRead(sizeof(BOOT_BLOCK_T), bootBlockOffsetFound + imgAddr, (uint8_t *) &uImgParser.imgBootBlock, + pReadFunctionParam, pFunctionEepromRead) != gOtaUtilsSuccess_c) + break; + + headerBootBlockMarker = uImgParser.imgBootBlock.header_marker; + + if (!((headerBootBlockMarker >= BOOT_BLOCK_HDR_MARKER) && (headerBootBlockMarker <= BOOT_BLOCK_HDR_MARKER + 2))) + break; + if (!inOtaCheck) + { + if (uImgParser.imgBootBlock.target_addr != runAddr) + break; + } + else + { + runAddr = uImgParser.imgBootBlock.target_addr; + } + if (runAddr < minValidAddr) + break; + if (uImgParser.imgBootBlock.stated_size < (bootBlockOffsetFound + sizeof(BOOT_BLOCK_T))) + break; + if (uImgParser.imgBootBlock.img_len > uImgParser.imgBootBlock.stated_size) + break; + + if (uImgParser.imgBootBlock.compatibility_offset != 0) + { + uint32_t compatibility_list_sz = 0; + OTA_UTILS_DEBUG("Compatibility list found\n"); + if (uImgParser.imgBootBlock.compatibility_offset < (bootBlockOffsetFound - sizeof(uint32_t))) + { + /* Try to read the compatibility list size */ + if (pFunctionRead(sizeof(uint32_t), imgAddr + uImgParser.imgBootBlock.compatibility_offset, + (uint8_t *) &compatibility_list_sz, pReadFunctionParam, + pFunctionEepromRead) != gOtaUtilsSuccess_c) + break; + if (uImgParser.imgBootBlock.compatibility_offset != + bootBlockOffsetFound - (sizeof(uint32_t) + compatibility_list_sz * sizeof(ImageCompatibilityListElem_t))) + break; + } + else + break; + } + + /* Save bootblock data */ + imgSizeFound = uImgParser.imgBootBlock.img_len; + + /* Security check */ + if (pRootCert != NULL) + { + uint32_t imgSignatureOffset = bootBlockOffsetFound + sizeof(BOOT_BLOCK_T); + const uint32_t * pKey = (const uint32_t *) &pRootCert->public_key[0]; + OTA_UTILS_DEBUG("==> Img authentication is enabled\n"); + if (uImgParser.imgBootBlock.certificate_offset != 0) + { + OTA_UTILS_DEBUG("Certificate found\n"); + /* Check that the certificate is inside the img */ + if ((uImgParser.imgBootBlock.certificate_offset + sizeof(ImageCertificate_t)) != imgSizeFound) + break; + /* If there is a certificate is must comply with the expectations */ + /* There must be a trailing ImageAuthTrailer_t appended to boot block */ + if (pFunctionRead(sizeof(ImageCertificate_t), imgAddr + uImgParser.imgBootBlock.certificate_offset, + (uint8_t *) &uImgParser.imgCertificate, pReadFunctionParam, + pFunctionEepromRead) != gOtaUtilsSuccess_c) + break; + if (uImgParser.imgCertificate.certificate.certificate_marker != CERTIFICATE_MARKER) + break; + /* Accesses to certificate header, certificate signature and image signature fields + * indirectly allow their correct presence via the Bus Fault TRY-CATCH. + */ + if (uImgParser.imgCertificate.certificate.public_key[0] == + uImgParser.imgCertificate.certificate.public_key[SIGNATURE_WRD_LEN - 1]) + break; + const uint32_t * cert_sign = (uint32_t *) &uImgParser.imgCertificate.signature[0]; + if (cert_sign[0] == cert_sign[SIGNATURE_WRD_LEN - 1]) + { + break; + } + /* Check the signature of the certificate */ + if (!secure_VerifyCertificate(&uImgParser.imgCertificate.certificate, pKey, + &uImgParser.imgCertificate.signature[0])) + break; + pKey = (const uint32_t *) &uImgParser.imgCertificate.certificate.public_key[0]; + imgSignatureOffset += sizeof(ImageCertificate_t); + } + else if (imgSignatureOffset != imgSizeFound) + { + break; + } + OTA_UTILS_DEBUG("Img signature found\n"); + /* Read the img signature */ + if (pFunctionRead(sizeof(ImageSignature_t), imgAddr + imgSignatureOffset, + (uint8_t *) &uImgParser.imgCertificate.signature, pReadFunctionParam, + pFunctionEepromRead) != gOtaUtilsSuccess_c) + break; + + const uint8_t * img_sign = (uint8_t *) &uImgParser.imgCertificate.signature[0]; + if (img_sign[0] == img_sign[SIGNATURE_WRD_LEN - 1]) + break; + + if (!OtaUtils_VerifySignature(imgAddr, imgSignatureOffset, pKey, img_sign, pFunctionRead, pReadFunctionParam, + pFunctionEepromRead)) + break; + } + result_addr = runAddr; + + } while (0); + OTA_UTILS_DEBUG("OTA_Utils => OtaUtils_ValidateImage result addr = 0x%x\n", result_addr); + return result_addr; +} + +otaUtilsResult_t OtaUtils_ReconstructRootCert(IMAGE_CERT_T * pCert, const psector_page_data_t * pPage0, + const psector_page_data_t * pFlashPage) +{ + otaUtilsResult_t result = gOtaUtilsError_c; + uint32_t keyValid; + do + { + if (pCert == NULL || pPage0 == NULL) + break; + + keyValid = pPage0->page0_v3.img_pk_valid; + if (keyValid < 2) + { + result = gOtaUtilsInvalidKey_c; + break; + } + /* Decrypt the public key using the efuse key */ + efuse_LoadUniqueKey(); + aesLoadKeyFromOTP(AES_KEY_128BITS); + aesMode(AES_MODE_ECB_DECRYPT, AES_INT_BSWAP | AES_OUTT_BSWAP); + aesProcess((uint32_t *) &pPage0->page0_v3.image_pubkey[0], &pCert->public_key[0], SIGNATURE_LEN / 16); + + if (pFlashPage != NULL) + { + pCert->customer_id = pFlashPage->pFlash.customer_id; + pCert->min_device_id = pFlashPage->pFlash.min_device_id; + pCert->max_device_id = pFlashPage->pFlash.max_device_id; + } + else + { + pCert->customer_id = psector_Read_CustomerId(); + pCert->min_device_id = psector_Read_MinDeviceId(); + pCert->max_device_id = psector_Read_MaxDeviceId(); + } + + pCert->certificate_marker = CERTIFICATE_MARKER; + pCert->certificate_id = 0UL; + pCert->usage_flags = 0UL; + result = gOtaUtilsSuccess_c; + } while (0); + return result; +} 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 1787b1ac5953c0..378feb75f3ceba 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,7 @@ cp ./third_party/nxp/k32w0_sdk/sdk_fixes/SecLib.h "$NXP_K32W061_SDK_ROOT"/middle cp -r ./third_party/nxp/k32w0_sdk/sdk_fixes/app_dual_mode_low_power.h "$NXP_K32W061_SDK_ROOT"/boards/k32w061dk6/wireless_examples/hybrid/ble_ot/lped_ble_wuart/ble_802_15_4_common/ cp -r ./third_party/nxp/k32w0_sdk/sdk_fixes/app_dual_mode_switch.h "$NXP_K32W061_SDK_ROOT"/boards/k32w061dk6/wireless_examples/hybrid/ble_ot/lped_ble_wuart/ble_802_15_4_common/ +cp -r ./third_party/nxp/k32w0_sdk/sdk_fixes/OtaUtils.c "$NXP_K32W061_SDK_ROOT"/middleware/wireless/framework/OtaSupport/Source/ + echo "K32W SDK MR3 QP1 was patched!" exit 0