blob: 0aa0bdf0fc4cafc476727fe1275f9316eb338202 [file] [log] [blame]
/*
*
* 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 <app/clusters/ota-requestor/OTADownloader.h>
#include <app/clusters/ota-requestor/OTARequestorInterface.h>
#include <lib/support/DefaultStorageKeyAllocator.h>
#include "OTAImageProcessorImpl.h"
#include <ti/common/cc26xx/flash_interface/flash_interface.h>
#include <ti/common/cc26xx/oad/ext_flash_layout.h>
#include <ti_drivers_config.h>
// clang-format off
/* driverlib header for resetting the SoC */
#include <ti/devices/DeviceFamily.h>
#include DeviceFamily_constructPath(driverlib/sys_ctrl.h)
// clang-format on
using namespace chip::DeviceLayer;
using namespace chip::DeviceLayer::PersistedStorage;
namespace chip {
CHIP_ERROR OTAImageProcessorImpl::PrepareDownload()
{
PlatformMgr().ScheduleWork(HandlePrepareDownload, reinterpret_cast<intptr_t>(this));
return CHIP_NO_ERROR;
}
CHIP_ERROR OTAImageProcessorImpl::Finalize()
{
PlatformMgr().ScheduleWork(HandleFinalize, reinterpret_cast<intptr_t>(this));
return CHIP_NO_ERROR;
}
CHIP_ERROR OTAImageProcessorImpl::Apply()
{
PlatformMgr().ScheduleWork(HandleApply, reinterpret_cast<intptr_t>(this));
return CHIP_NO_ERROR;
}
CHIP_ERROR OTAImageProcessorImpl::Abort()
{
PlatformMgr().ScheduleWork(HandleAbort, reinterpret_cast<intptr_t>(this));
return CHIP_NO_ERROR;
}
CHIP_ERROR OTAImageProcessorImpl::ProcessBlock(ByteSpan & block)
{
if (nullptr == mNvsHandle)
{
return CHIP_ERROR_INTERNAL;
}
if ((nullptr == block.data()) || 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());
}
PlatformMgr().ScheduleWork(HandleProcessBlock, reinterpret_cast<intptr_t>(this));
return CHIP_NO_ERROR;
}
bool OTAImageProcessorImpl::IsFirstImageRun()
{
OTARequestorInterface * requestor;
uint32_t runningSwVer;
if (CHIP_NO_ERROR != ConfigurationMgr().GetSoftwareVersion(runningSwVer))
{
return false;
}
requestor = GetRequestorInstance();
return (requestor->GetTargetVersion() == runningSwVer) &&
(requestor->GetCurrentUpdateState() == chip::app::Clusters::OtaSoftwareUpdateRequestor::OTAUpdateStateEnum::kApplying);
}
/* DESIGN NOTE: The Boot Image Manager will search external flash for an
* `ExtImageInfo_t` structure every 4K for 1M. This structure points to where
* the executable image is in external flash with a uint32_t. It is possible to
* have multiple images ready to be programmed into the internal flash of the
* device. This design is only concerned with managing 1 image in external
* flash starting at `IMG_START` and being defined by a meta header at address
* 0. Future designs may be able to take advantage of other images for rollback
* functionality, however this will require a larger external flash chip.
*/
#define IMG_START (4 * EFL_SIZE_META)
static bool readExtFlashImgHeader(NVS_Handle handle, imgFixedHdr_t * header, size_t otaHeaderLen)
{
int_fast16_t status;
status = NVS_read(handle, IMG_START + otaHeaderLen, header, sizeof(imgFixedHdr_t));
return (status == NVS_STATUS_SUCCESS);
}
/* makes room for the new block if needed */
static bool writeExtFlashImgPages(NVS_Handle handle, size_t bytesWritten, MutableByteSpan block)
{
int_fast16_t status;
NVS_Attrs regionAttrs;
unsigned int erasedSectors;
unsigned int neededSectors;
size_t sectorSize;
NVS_getAttrs(handle, &regionAttrs);
sectorSize = regionAttrs.sectorSize;
erasedSectors = (bytesWritten + (sectorSize - 1)) / sectorSize;
neededSectors = ((bytesWritten + block.size()) + (sectorSize - 1)) / sectorSize;
if (neededSectors != erasedSectors)
{
status = NVS_erase(handle, IMG_START + (erasedSectors * sectorSize), (neededSectors - erasedSectors) * sectorSize);
if (status != NVS_STATUS_SUCCESS)
{
ChipLogError(SoftwareUpdate, "NVS_erase failed status: %d", status);
return false;
}
}
status = NVS_write(handle, IMG_START + bytesWritten, block.data(), block.size(), NVS_WRITE_POST_VERIFY);
if (status != NVS_STATUS_SUCCESS)
{
ChipLogError(SoftwareUpdate, "NVS_write failed status: %d", status);
return false;
}
return true;
}
static bool readExtFlashMetaHeader(NVS_Handle handle, ExtImageInfo_t * header)
{
int_fast16_t status;
status = NVS_read(handle, EFL_ADDR_META, header, sizeof(ExtImageInfo_t));
return (status == NVS_STATUS_SUCCESS);
}
static bool eraseExtFlashMetaHeader(NVS_Handle handle)
{
int_fast16_t status;
NVS_Attrs regionAttrs;
unsigned int sectors;
NVS_getAttrs(handle, &regionAttrs);
/* calculate the number of sectors to erase */
sectors = (sizeof(ExtImageInfo_t) + (regionAttrs.sectorSize - 1)) / regionAttrs.sectorSize;
status = NVS_erase(handle, EFL_ADDR_META, sectors * regionAttrs.sectorSize);
return (status == NVS_STATUS_SUCCESS);
}
static bool writeExtFlashMetaHeader(NVS_Handle handle, ExtImageInfo_t * header)
{
int_fast16_t status;
if (!eraseExtFlashMetaHeader(handle))
{
return false;
}
status = NVS_write(handle, EFL_ADDR_META, header, sizeof(ExtImageInfo_t), NVS_WRITE_POST_VERIFY);
return (status == NVS_STATUS_SUCCESS);
}
/**
* Generated on by pycrc v0.9.2, https://pycrc.org using the configuration:
* - Width = 32
* - Poly = 0x04c11db7
* - XorIn = 0xffffffff
* - ReflectIn = True
* - XorOut = 0xffffffff
* - ReflectOut = True
* - Algorithm = bit-by-bit-fast
*
* Modified to take uint32_t as the CRC type
*/
uint32_t crc_reflect(uint32_t data, size_t data_len)
{
unsigned int i;
uint32_t ret;
ret = data & 0x01;
for (i = 1; i < data_len; i++)
{
data >>= 1;
ret = (ret << 1) | (data & 0x01);
}
return ret;
}
uint32_t crc_update(uint32_t crc, const void * data, size_t data_len)
{
const unsigned char * d = (const unsigned char *) data;
unsigned int i;
bool bit;
unsigned char c;
while (data_len--)
{
c = *d++;
for (i = 0x01; i & 0xff; i <<= 1)
{
bit = crc & 0x80000000;
if (c & i)
{
bit = !bit;
}
crc <<= 1;
if (bit)
{
crc ^= 0x04c11db7;
}
}
crc &= 0xffffffff;
}
return crc & 0xffffffff;
}
static bool validateExtFlashImage(NVS_Handle handle, size_t otaHeaderLen)
{
uint32_t crc;
imgFixedHdr_t header;
size_t addr, endAddr;
if (!readExtFlashImgHeader(handle, &header, otaHeaderLen))
{
return false;
}
/* CRC is calculated after the CRC element of the image header */
addr = IMG_START + otaHeaderLen + IMG_DATA_OFFSET;
endAddr = IMG_START + otaHeaderLen + header.len;
crc = 0xFFFFFFFF;
while (addr < endAddr)
{
uint8_t buffer[32];
size_t bytesLeft = endAddr - addr;
size_t toRead = (sizeof(buffer) < bytesLeft) ? sizeof(buffer) : bytesLeft;
if (NVS_STATUS_SUCCESS != NVS_read(handle, addr, buffer, toRead))
{
return false;
}
crc = crc_update(crc, buffer, toRead);
addr += toRead;
}
crc = crc_reflect(crc, 32) ^ 0xffffffff;
return (crc == header.crc32);
}
CHIP_ERROR OTAImageProcessorImpl::ConfirmCurrentImage()
{
NVS_Handle handle;
NVS_Params nvsParams;
NVS_Params_init(&nvsParams);
handle = NVS_open(CONFIG_NVSEXTERNAL, &nvsParams);
if (NULL != handle)
{
eraseExtFlashMetaHeader(handle);
NVS_close(handle);
return CHIP_NO_ERROR;
}
return CHIP_NO_ERROR;
}
void OTAImageProcessorImpl::HandlePrepareDownload(intptr_t context)
{
auto * imageProcessor = reinterpret_cast<OTAImageProcessorImpl *>(context);
if (imageProcessor == nullptr)
{
ChipLogError(SoftwareUpdate, "ImageProcessor context is null");
return;
}
else if (imageProcessor->mDownloader == nullptr)
{
ChipLogError(SoftwareUpdate, "mDownloader is null");
return;
}
if (NULL == imageProcessor->mNvsHandle)
{
NVS_Params nvsParams;
NVS_Params_init(&nvsParams);
imageProcessor->mNvsHandle = NVS_open(CONFIG_NVSEXTERNAL, &nvsParams);
if (NULL == imageProcessor->mNvsHandle)
{
imageProcessor->mDownloader->OnPreparedForDownload(CHIP_ERROR_OPEN_FAILED);
return;
}
}
if (!eraseExtFlashMetaHeader(imageProcessor->mNvsHandle))
{
NVS_close(imageProcessor->mNvsHandle);
imageProcessor->mDownloader->OnPreparedForDownload(CHIP_ERROR_WRITE_FAILED);
return;
}
imageProcessor->mDownloader->OnPreparedForDownload(CHIP_NO_ERROR);
}
void OTAImageProcessorImpl::HandleFinalize(intptr_t context)
{
ExtImageInfo_t header;
auto * imageProcessor = reinterpret_cast<OTAImageProcessorImpl *>(context);
const uint8_t extImgId[] = OAD_EFL_MAGIC;
if (imageProcessor == nullptr)
{
return;
}
if (!readExtFlashImgHeader(imageProcessor->mNvsHandle, &(header.fixedHdr),
imageProcessor->mFixedOtaHeader.headerSize + sizeof(imageProcessor->mFixedOtaHeader)))
{
return;
}
// offset in the size of the fixed and variable OTA image headers
memcpy(&(header.fixedHdr.imgID), extImgId, sizeof(header.fixedHdr.imgID));
header.extFlAddr = IMG_START + imageProcessor->mFixedOtaHeader.headerSize + sizeof(imageProcessor->mFixedOtaHeader);
header.counter = 0x0;
if (validateExtFlashImage(imageProcessor->mNvsHandle,
imageProcessor->mFixedOtaHeader.headerSize + sizeof(imageProcessor->mFixedOtaHeader)))
{
// only write the meta header if the crc check passes
writeExtFlashMetaHeader(imageProcessor->mNvsHandle, &header);
}
else
{
// ensure the external image is not mistaken for a valid image
eraseExtFlashMetaHeader(imageProcessor->mNvsHandle);
}
imageProcessor->ReleaseBlock();
ChipLogProgress(SoftwareUpdate, "OTA image downloaded");
}
void OTAImageProcessorImpl::HandleApply(intptr_t context)
{
ExtImageInfo_t header;
auto * imageProcessor = reinterpret_cast<OTAImageProcessorImpl *>(context);
if (imageProcessor == nullptr)
{
return;
}
if (!readExtFlashMetaHeader(imageProcessor->mNvsHandle, &header))
{
return;
}
header.fixedHdr.imgCpStat = NEED_COPY;
writeExtFlashMetaHeader(imageProcessor->mNvsHandle, &header);
// reset SoC to kick BIM
SysCtrlSystemReset();
}
void OTAImageProcessorImpl::HandleAbort(intptr_t context)
{
auto * imageProcessor = reinterpret_cast<OTAImageProcessorImpl *>(context);
if (imageProcessor == nullptr)
{
return;
}
if (!eraseExtFlashMetaHeader(imageProcessor->mNvsHandle))
{
imageProcessor->mDownloader->OnPreparedForDownload(CHIP_ERROR_WRITE_FAILED);
}
NVS_close(imageProcessor->mNvsHandle);
imageProcessor->ReleaseBlock();
}
void OTAImageProcessorImpl::HandleProcessBlock(intptr_t context)
{
auto * imageProcessor = reinterpret_cast<OTAImageProcessorImpl *>(context);
if (imageProcessor == nullptr)
{
ChipLogError(SoftwareUpdate, "ImageProcessor context is null");
return;
}
else if (imageProcessor->mDownloader == nullptr)
{
ChipLogError(SoftwareUpdate, "mDownloader is null");
return;
}
/* Save the fixed size header */
if (imageProcessor->mParams.downloadedBytes < sizeof(imageProcessor->mFixedOtaHeader))
{
uint8_t * header = reinterpret_cast<uint8_t *>(&(imageProcessor->mFixedOtaHeader));
if (imageProcessor->mBlock.size() + imageProcessor->mParams.downloadedBytes < sizeof(imageProcessor->mFixedOtaHeader))
{
// the first block is smaller than the header, somehow
memcpy(header + imageProcessor->mParams.downloadedBytes, imageProcessor->mBlock.data(), imageProcessor->mBlock.size());
}
else
{
// we have received the whole header, fill it up
memcpy(header + imageProcessor->mParams.downloadedBytes, imageProcessor->mBlock.data(),
sizeof(imageProcessor->mFixedOtaHeader) - imageProcessor->mParams.downloadedBytes);
// update the total size for download tracking
imageProcessor->mParams.totalFileBytes = imageProcessor->mFixedOtaHeader.totalSize;
}
}
/* chip::OTAImageHeaderParser can be used for processing the variable size header */
ChipLogDetail(SoftwareUpdate, "Write block %d, %d", (size_t) imageProcessor->mParams.downloadedBytes,
imageProcessor->mBlock.size());
if (!writeExtFlashImgPages(imageProcessor->mNvsHandle, imageProcessor->mParams.downloadedBytes, imageProcessor->mBlock))
{
imageProcessor->mDownloader->EndDownload(CHIP_ERROR_WRITE_FAILED);
return;
}
imageProcessor->mParams.downloadedBytes += imageProcessor->mBlock.size();
imageProcessor->mDownloader->FetchNextData();
}
CHIP_ERROR OTAImageProcessorImpl::SetBlock(ByteSpan & block)
{
if (!IsSpanUsable(block))
{
ReleaseBlock();
return CHIP_NO_ERROR;
}
if (mBlock.size() < block.size())
{
if (!mBlock.empty())
{
ReleaseBlock();
}
uint8_t * mBlock_ptr = static_cast<uint8_t *>(chip::Platform::MemoryAlloc(block.size()));
if (mBlock_ptr == nullptr)
{
return CHIP_ERROR_NO_MEMORY;
}
mBlock = MutableByteSpan(mBlock_ptr, block.size());
}
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;
}
CHIP_ERROR OTAImageProcessorImpl::ReleaseBlock()
{
if (mBlock.data() != nullptr)
{
chip::Platform::MemoryFree(mBlock.data());
}
mBlock = MutableByteSpan();
return CHIP_NO_ERROR;
}
} // namespace chip