Skip to content

Commit

Permalink
[OTA] Add support for applying image for Linux/Darwin platform (#16482)
Browse files Browse the repository at this point in the history
- Make sure driver restores the core states if there is a problem detected at initialization
  • Loading branch information
carol-apple authored and pull[bot] committed Dec 7, 2023
1 parent e0dbf66 commit 7560508
Show file tree
Hide file tree
Showing 22 changed files with 351 additions and 100 deletions.
8 changes: 8 additions & 0 deletions config/standalone/CHIPProjectConfig.h
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,14 @@
#define CHIP_DEVICE_CONFIG_DYNAMIC_ENDPOINT_COUNT 4
#endif

#ifndef CHIP_DEVICE_CONFIG_DEVICE_SOFTWARE_VERSION
#define CHIP_DEVICE_CONFIG_DEVICE_SOFTWARE_VERSION 1
#endif

#ifndef CHIP_DEVICE_CONFIG_DEVICE_SOFTWARE_VERSION_STRING
#define CHIP_DEVICE_CONFIG_DEVICE_SOFTWARE_VERSION_STRING "1.0"
#endif

//
// Default of 8 ECs is not sufficient for some of the unit tests
// that try to validate multiple simultaneous interactions.
Expand Down
2 changes: 1 addition & 1 deletion examples/lighting-app/nxp/k32w/k32w0/main/AppTask.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ CHIP_ERROR AppTask::Init()
gRequestorCore.Init(Server::GetInstance(), gRequestorStorage, gRequestorUser, gDownloader);
gRequestorUser.Init(&gRequestorCore, &gImageProcessor);

gImageProcessor.SetOTAImageFile(CharSpan("test.txt"));
gImageProcessor.SetOTAImageFile("test.txt");
gImageProcessor.SetOTADownloader(&gDownloader);

// Connect the gDownloader and Image Processor objects
Expand Down
2 changes: 1 addition & 1 deletion examples/ota-requestor-app/efr32/src/AppTask.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -542,7 +542,7 @@ void AppTask::InitOTARequestor()

gRequestorUser.Init(&gRequestorCore, &gImageProcessor);

gImageProcessor.SetOTAImageFile(CharSpan("test.txt"));
gImageProcessor.SetOTAImageFile("test.txt");
gImageProcessor.SetOTADownloader(&gDownloader);

// Connect the Downloader and Image Processor objects
Expand Down
18 changes: 17 additions & 1 deletion examples/ota-requestor-app/linux/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ static void InitOTARequestor(void)
gRequestorCore.Init(chip::Server::GetInstance(), gRequestorStorage, gRequestorUser, gDownloader);
gRequestorUser.Init(&gRequestorCore, &gImageProcessor);

gImageProcessor.SetOTAImageFile(CharSpan::fromCharString(gOtaDownloadPath));
gImageProcessor.SetOTAImageFile(gOtaDownloadPath);
gImageProcessor.SetOTADownloader(&gDownloader);

// Set the image processor instance used for handling image being downloaded
Expand Down Expand Up @@ -213,5 +213,21 @@ int main(int argc, char * argv[])
{
VerifyOrDie(ChipLinuxAppInit(argc, argv, &cmdLineOptions) == 0);
ChipLinuxAppMainLoop();

// If the event loop had been stopped due to an update being applied, boot into the new image
if (gRequestorCore.GetCurrentUpdateState() == OTARequestor::OTAUpdateStateEnum::kApplying)
{
if (kMaxFilePathSize <= strlen(kImageExecPath))
{
ChipLogError(SoftwareUpdate, "Buffer too small for the new image file path: %s", kImageExecPath);
return -1;
}

argv[0] = kImageExecPath;
execv(argv[0], argv);

// If successfully executing the new iamge, execv should not return
ChipLogError(SoftwareUpdate, "The OTA image is invalid");
}
return 0;
}
2 changes: 1 addition & 1 deletion examples/platform/efr32/OTAConfig.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ void OTAConfig::Init()
gRequestorUser.SetPeriodicQueryTimeout(OTA_PERIODIC_TIMEOUT);
gRequestorUser.Init(&gRequestorCore, &gImageProcessor);

gImageProcessor.SetOTAImageFile(chip::CharSpan("test.txt"));
gImageProcessor.SetOTAImageFile("test.txt");
gImageProcessor.SetOTADownloader(&gDownloader);

// Connect the Downloader and Image Processor objects
Expand Down
34 changes: 34 additions & 0 deletions src/app/clusters/ota-requestor/DefaultOTARequestorStorage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,40 @@ CHIP_ERROR DefaultOTARequestorStorage::LoadUpdateToken(MutableByteSpan & updateT
return Load(DefaultStorageKeyAllocator::OTAUpdateToken(), updateToken);
}

CHIP_ERROR DefaultOTARequestorStorage::StoreCurrentUpdateState(OTAUpdateStateEnum currentUpdateState)
{
return mPersistentStorage->SyncSetKeyValue(DefaultStorageKeyAllocator::OTACurrentUpdateState(), &currentUpdateState,
sizeof(currentUpdateState));
}

CHIP_ERROR DefaultOTARequestorStorage::LoadCurrentUpdateState(OTAUpdateStateEnum & currentUpdateState)
{
uint16_t size = static_cast<uint16_t>(sizeof(currentUpdateState));
return mPersistentStorage->SyncGetKeyValue(DefaultStorageKeyAllocator::OTACurrentUpdateState(), &currentUpdateState, size);
}

CHIP_ERROR DefaultOTARequestorStorage::ClearCurrentUpdateState()
{
return mPersistentStorage->SyncDeleteKeyValue(DefaultStorageKeyAllocator::OTACurrentUpdateState());
}

CHIP_ERROR DefaultOTARequestorStorage::StoreTargetVersion(uint32_t targetVersion)
{
return mPersistentStorage->SyncSetKeyValue(DefaultStorageKeyAllocator::OTATargetVersion(), &targetVersion,
sizeof(targetVersion));
}

CHIP_ERROR DefaultOTARequestorStorage::LoadTargetVersion(uint32_t & targetVersion)
{
uint16_t size = static_cast<uint16_t>(sizeof(targetVersion));
return mPersistentStorage->SyncGetKeyValue(DefaultStorageKeyAllocator::OTATargetVersion(), &targetVersion, size);
}

CHIP_ERROR DefaultOTARequestorStorage::ClearTargetVersion()
{
return mPersistentStorage->SyncDeleteKeyValue(DefaultStorageKeyAllocator::OTATargetVersion());
}

CHIP_ERROR DefaultOTARequestorStorage::Load(const char * key, MutableByteSpan & buffer)
{
uint16_t size = static_cast<uint16_t>(buffer.size());
Expand Down
8 changes: 8 additions & 0 deletions src/app/clusters/ota-requestor/DefaultOTARequestorStorage.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,14 @@ class DefaultOTARequestorStorage : public OTARequestorStorage
CHIP_ERROR ClearUpdateToken() override;
CHIP_ERROR LoadUpdateToken(MutableByteSpan & updateToken) override;

CHIP_ERROR StoreCurrentUpdateState(OTAUpdateStateEnum currentUpdateState) override;
CHIP_ERROR LoadCurrentUpdateState(OTAUpdateStateEnum & currentUpdateState) override;
CHIP_ERROR ClearCurrentUpdateState() override;

CHIP_ERROR StoreTargetVersion(uint32_t targetVersion) override;
CHIP_ERROR LoadTargetVersion(uint32_t & targetVersion) override;
CHIP_ERROR ClearTargetVersion() override;

private:
CHIP_ERROR Load(const char * key, MutableByteSpan & buffer);
PersistentStorageDelegate * mPersistentStorage = nullptr;
Expand Down
11 changes: 11 additions & 0 deletions src/app/clusters/ota-requestor/GenericOTARequestorDriver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -70,12 +70,23 @@ void GenericOTARequestorDriver::Init(OTARequestorInterface * requestor, OTAImage
if (error != CHIP_NO_ERROR)
{
ChipLogError(SoftwareUpdate, "Failed to confirm image: %" CHIP_ERROR_FORMAT, error.Format());
mRequestor->Reset();
return;
}

mRequestor->NotifyUpdateApplied();
});
}
else if ((mRequestor->GetCurrentUpdateState() != OTAUpdateStateEnum::kIdle))
{
// Not running a new image for the first time but also not in the idle state may indicate there is a problem
mRequestor->Reset();
}
else
{
// Start the first periodic query timer
StartDefaultProviderTimer();
}
}

bool GenericOTARequestorDriver::CanConsent()
Expand Down
128 changes: 114 additions & 14 deletions src/app/clusters/ota-requestor/OTARequestor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -107,12 +107,35 @@ void OTARequestor::InitState(intptr_t context)
OTARequestor * requestorCore = reinterpret_cast<OTARequestor *>(context);
VerifyOrDie(requestorCore != nullptr);

// This results in the initial periodic timer kicking off
requestorCore->RecordNewUpdateState(OTAUpdateStateEnum::kIdle, OTAChangeReasonEnum::kSuccess);

// This initialization may occur due to the following:
// 1) Regular boot up - the states should already be correct
// 2) Reboot from applying an image - once the image has been confirmed, the provider will be notified of the new version and
// all relevant states will reset for a new OTA update. If the image cannot be confirmed, the driver will be responsible for
// resetting the states appropriately, including the current update state.
OtaRequestorServerSetUpdateState(requestorCore->mCurrentUpdateState);
OtaRequestorServerSetUpdateStateProgress(app::DataModel::NullNullable);
}

CHIP_ERROR OTARequestor::Init(Server & server, OTARequestorStorage & storage, OTARequestorDriver & driver,
BDXDownloader & downloader)
{
mServer = &server;
mCASESessionManager = server.GetCASESessionManager();
mStorage = &storage;
mOtaRequestorDriver = &driver;
mBdxDownloader = &downloader;

ReturnErrorOnFailure(DeviceLayer::ConfigurationMgr().GetSoftwareVersion(mCurrentVersion));

// Load data from KVS
LoadCurrentUpdateInfo();

// Schedule the initializations that needs to be performed in the CHIP context
DeviceLayer::PlatformMgr().ScheduleWork(InitState, reinterpret_cast<intptr_t>(this));

return chip::DeviceLayer::PlatformMgrImpl().AddEventHandler(OnCommissioningCompleteRequestor, reinterpret_cast<intptr_t>(this));
}

void OTARequestor::OnQueryImageResponse(void * context, const QueryImageResponse::DecodableType & response)
{
LogQueryImageResponse(response);
Expand Down Expand Up @@ -159,7 +182,6 @@ void OTARequestor::OnQueryImageResponse(void * context, const QueryImageResponse
memcpy(fileDesignator.data(), update.fileDesignator.data(), update.fileDesignator.size());
fileDesignator.reduce_size(update.fileDesignator.size());
requestorCore->mFileDesignator = fileDesignator;
requestorCore->StoreCurrentUpdateInfo();

requestorCore->mOtaRequestorDriver->UpdateAvailable(update,
System::Clock::Seconds32(response.delayedActionTime.ValueOr(0)));
Expand Down Expand Up @@ -253,6 +275,22 @@ void OTARequestor::OnNotifyUpdateAppliedFailure(void * context, CHIP_ERROR error
requestorCore->RecordErrorUpdateState(UpdateFailureState::kNotifying, error);
}

void OTARequestor::Reset()
{
mProviderLocation.ClearValue();
mUpdateToken.reduce_size(0);
RecordNewUpdateState(OTAUpdateStateEnum::kIdle, OTAChangeReasonEnum::kSuccess);
mTargetVersion = 0;

// Persist in case of a reboot or crash
StoreCurrentUpdateInfo();
}

void OTARequestor::Shutdown(void)
{
mServer->DispatchShutDownAndStopEventLoop();
}

EmberAfStatus OTARequestor::HandleAnnounceOTAProvider(app::CommandHandler * commandObj,
const app::ConcreteCommandPath & commandPath,
const AnnounceOtaProvider::DecodableType & commandData)
Expand Down Expand Up @@ -365,14 +403,14 @@ void OTARequestor::CancelImageUpdate()
RecordNewUpdateState(OTAUpdateStateEnum::kIdle, OTAChangeReasonEnum::kUnknown);
}

CHIP_ERROR OTARequestor::GetUpdateProgress(EndpointId endpointId, app::DataModel::Nullable<uint8_t> & progress)
CHIP_ERROR OTARequestor::GetUpdateStateProgressAttribute(EndpointId endpointId, app::DataModel::Nullable<uint8_t> & progress)
{
VerifyOrReturnError(OtaRequestorServerGetUpdateStateProgress(endpointId, progress) == EMBER_ZCL_STATUS_SUCCESS,
CHIP_ERROR_BAD_REQUEST);
return CHIP_NO_ERROR;
}

CHIP_ERROR OTARequestor::GetState(EndpointId endpointId, OTAUpdateStateEnum & state)
CHIP_ERROR OTARequestor::GetUpdateStateAttribute(EndpointId endpointId, OTAUpdateStateEnum & state)
{
VerifyOrReturnError(OtaRequestorServerGetUpdateState(endpointId, state) == EMBER_ZCL_STATUS_SUCCESS, CHIP_ERROR_BAD_REQUEST);
return CHIP_NO_ERROR;
Expand Down Expand Up @@ -504,6 +542,10 @@ void OTARequestor::DownloadUpdateDelayedOnUserConsent()
void OTARequestor::ApplyUpdate()
{
RecordNewUpdateState(OTAUpdateStateEnum::kApplying, OTAChangeReasonEnum::kSuccess);

// If image is successfully applied, the device will reboot so persist all relevant data
StoreCurrentUpdateInfo();

ConnectToProvider(kApplyUpdate);
}

Expand All @@ -520,9 +562,6 @@ void OTARequestor::NotifyUpdateApplied()

OtaRequestorServerOnVersionApplied(mCurrentVersion, productId);

// There is no response for a notify so consider this OTA complete
RecordNewUpdateState(OTAUpdateStateEnum::kIdle, OTAChangeReasonEnum::kSuccess);

ConnectToProvider(kNotifyUpdateApplied);
}

Expand Down Expand Up @@ -788,27 +827,88 @@ CHIP_ERROR OTARequestor::SendNotifyUpdateAppliedRequest(OperationalDeviceProxy &
Controller::OtaSoftwareUpdateProviderCluster cluster;
cluster.Associate(&deviceProxy, mProviderLocation.Value().endpoint);

mProviderLocation.ClearValue(); // Clearing the last used provider location to start afresh on reboot
// There is no response for a notify so consider this OTA complete. Clear the provider location and reset any states to indicate
// so.
Reset();

return cluster.InvokeCommand(args, this, OnNotifyUpdateAppliedResponse, OnNotifyUpdateAppliedFailure);
}

void OTARequestor::StoreCurrentUpdateInfo()
{
// TODO: change OTA requestor storage interface to store both values at once
CHIP_ERROR error = mStorage->StoreCurrentProviderLocation(mProviderLocation.Value());
CHIP_ERROR error = CHIP_NO_ERROR;
if (mProviderLocation.HasValue())
{
error = mStorage->StoreCurrentProviderLocation(mProviderLocation.Value());
}
else
{
error = mStorage->ClearCurrentProviderLocation();
}

if (error == CHIP_NO_ERROR)
if ((error == CHIP_NO_ERROR) || (error == CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND))
{
if (mUpdateToken.size() > 0)
{
error = mStorage->StoreUpdateToken(mUpdateToken);
}
else
{
error = mStorage->ClearUpdateToken();
}
}

if ((error == CHIP_NO_ERROR) || (error == CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND))
{
mStorage->StoreUpdateToken(mUpdateToken);
error = mStorage->StoreCurrentUpdateState(mCurrentUpdateState);
}

if (error != CHIP_NO_ERROR)
if ((error == CHIP_NO_ERROR) || (error == CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND))
{
if (mTargetVersion > 0)
{
error = mStorage->StoreTargetVersion(mTargetVersion);
}
else
{
error = mStorage->ClearTargetVersion();
}
}

if ((error != CHIP_NO_ERROR) && (error != CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND))
{
ChipLogError(SoftwareUpdate, "Failed to store current update: %" CHIP_ERROR_FORMAT, error.Format());
}
}

void OTARequestor::LoadCurrentUpdateInfo()
{
mStorage->LoadDefaultProviders(mDefaultOtaProviderList);

ProviderLocationType providerLocation;
if (mStorage->LoadCurrentProviderLocation(providerLocation) == CHIP_NO_ERROR)
{
mProviderLocation.SetValue(providerLocation);
}

MutableByteSpan updateToken(mUpdateTokenBuffer);
if (mStorage->LoadUpdateToken(updateToken) == CHIP_NO_ERROR)
{
mUpdateToken = updateToken;
}

if (mStorage->LoadCurrentUpdateState(mCurrentUpdateState) != CHIP_NO_ERROR)
{
mCurrentUpdateState = OTAUpdateStateEnum::kIdle;
}

if (mStorage->LoadTargetVersion(mTargetVersion) != CHIP_NO_ERROR)
{
mTargetVersion = 0;
}
}

// Invoked when the device becomes commissioned
void OTARequestor::OnCommissioningCompleteRequestor(const DeviceLayer::ChipDeviceEvent * event, intptr_t arg)
{
Expand Down
Loading

0 comments on commit 7560508

Please sign in to comment.