/*! * * Copyright (c) 2023 Diality Inc. - All Rights Reserved. * * \copyright * THIS CODE MAY NOT BE COPIED OR REPRODUCED IN ANY FORM, IN PART OR IN * WHOLE, WITHOUT THE EXPLICIT PERMISSION OF THE COPYRIGHT OWNER. * * \file UiProtocol.cpp * \author (last) Phil Braica * \date (last) 23-Jan-2023 * \author (original) Phil Braica * \date (original) 23-Jan-2023 * */ #include "UiProtocol.h" #include "UiSwUpdate.h" #include "IDataProvider.h" #include "Logger.h" #include #include namespace SwUpdate { uint16 UiProtocol::_gRandSeed = 0; ///< Seed value. uint8 UiProtocol::_gIdNum = 0; ///< Id number. /*! * \brief Constructor. * * \param target Target to go after for update. */ UiProtocol::UiProtocol(SwUpdateTargetEnum target) : _desired(Idle), _state(None), _lastState(None), _target(target), _pProvider(nullptr), _transfered(0), _maxTransfer(0), _retries(0), _lastRx{(uint8)0}, _link(KBPS_WIRE), _pThread(nullptr), _completedOk(false) { ; // NOP. } /*! * \brief Destructor (virtual). */ UiProtocol::~UiProtocol() { // Set shutdown, then fire start, // finally join thread. _desired = Shutdown; // It finished and ready to join, join and exit. if (_pThread != nullptr) { _pThread->join(); _pThread = nullptr; } } /*! * \brief Start updating. * * \return True on started ok. */ bool UiProtocol::start() { std::lock_guard lockApi(_apiMutex); if (_pThread != nullptr) { _pThread->join(); delete _pThread; _pThread = nullptr; } _pThread = new std::thread(&UiProtocol::_run, this); // Copy the key info in. _desired = Complete; _lastState = None; return true; } /*! * \brief Abort update. */ bool UiProtocol::abort() { std::lock_guard lockapi(_apiMutex); _desired = Idle; _link.abort(); return true; } /*! * \brief Current estimated progress. * * When not "updating" it is the last known step * which can describe a failure condition for debug. * * \return Status object. */ UiUpdateStatus UiProtocol::progress() { // Our state to descriptive text. static const std::string state_text[] = { "Not started", "Starting", "Streaming", "Streamed", "Verified", "Version checked", "Completed", "Start failed", "Stream failed", "Verification failed", "Version check failed", "Reboot failed", "Cyber attack", "File failure"}; // Grab mutex so status is always coherent. std::lock_guard lockapi(_apiMutex); UiUpdateStatus rv; // One of 3 things is the current desired end state. rv.goal = _desired == Idle ? "Idle" : _desired == Complete ? "Complete" : "Shutdown"; // Show as text which target. rv.targetName = _target == HD ? "HD" : _target == HDFPGA ? "HDFPGA" : _target == DG ? "DG" : _target == DGFPGA ? "DGFPGA" : "Files"; bool stillInProgress = (_state != UpdateState::None) && (_state < UpdateState::Completed); rv.inProgress = stillInProgress; rv.totalSteps = (uint16)UpdateState::Completed; // Compute the descriptive text. uint32 stepIndex = (uint32)_state; rv.stepName = stepIndex <= FileFailure ? state_text[stepIndex] : "Unknown SW Err"; rv.stepIndex = _state < UpdateState::Completed ? (UpdateState)stepIndex : UpdateState::Completed; const float one_hundred_percent = 100.0f; rv.percentTotal = _maxTransfer == 0 ? 0 : (_transfered * one_hundred_percent / _maxTransfer); return rv; } /*! * \brief Receive and check if matched. * * \param msgId Can message ID. * \param pData Data pointer. * * \return True if it matched. */ bool UiProtocol::receive(uint16 msgId, uint8 * pData) { // Return value. bool matched = false; // IF we're aren't trying to make progress in this object // towards completion, we don't care. if (_desired == Complete) { // If it's a message that matches what we'd want, // then copy it. matched = _link.checkReceived(msgId, pData); if (matched) { memcpy(_lastRx, pData, CAN_PAYLOAD_SIZE); } } return matched; } /////////////////////////////////////////////////////////// // Protected. /////////////////////////////////////////////////////////// /** * \brief Thread runs update process. */ void UiProtocol::_run() { // Wait till we start. if (_state != None) { _lastState = _state; _state = _desired == Complete ? Starting : None; } _completedOk = false; // In progress. // Do most of the work, virtual call so // it can be overriden for file like things and test. _doUpdate(); // Throw away the pointer to the old data provider. _pProvider = nullptr; // Cyber check. if (_link.numberRetries() > _maxCyberRetries) { _state = UpdateState::CyberThreat; } if (_state == Completed) { _completedOk = true; } // If anything is lingering, stop it. _link.abort(); { std::unique_lock lock2(_apiMutex); _desired = _desired == Complete ? Idle : _desired; } // Finally tell UiSwUpdate that coordinates multiple things // that this set of actions are complete, and if they completed ok. UiSwUpdate::instance().taskCompleted(_completedOk == true, this); } /*! * \brief Do the update for FW / FPGA */ void UiProtocol::_doUpdate() { // Null check / race condition. if (_pProvider == nullptr) { return; } // Reset the transfered counter. _transfered = 0; _link.resetRetries(); // Send start. _state = UpdateState::Starting; bool ok = _sendCommand(SwUpdateCmdEnum::Start); _state = ok ? UpdateState::Started : UpdateState::StartTimedOut; if ((_desired != Complete) || (!ok)) { return; } // Stream the data. ok = _streamData(); _state = ok ? UpdateState::Streamed : UpdateState::StreamTimedOut; if ((_desired != Complete) || (!ok)) { return; } // Send signature. ok = _sendSignature(); _state = ok ? UpdateState::Streamed : UpdateState::StreamTimedOut; if ((_desired != Complete) || (!ok)) { return; } // Get verification data. ok = _sendCommand(SwUpdateCmdEnum::Verify); SwUpdateVerifyResponse *pVerify = (SwUpdateVerifyResponse*)_lastRx; ok &= pVerify->data == _pProvider->verifyData; _state = ok ? UpdateState::Verified : UpdateState::FailedVerify; if ((_desired != Complete) || (!ok)) { return; } // Get the version data. ok = _sendCommand(SwUpdateCmdEnum::Version); pVerify = (SwUpdateVerifyResponse*)_lastRx; ok &= pVerify->data == _pProvider->versionData; _state = ok ? UpdateState::Versioned : UpdateState::FailedVersion; if ((_desired != Complete) || (!ok)) { return; } // Say if we agree. ok = _sendCommand(SwUpdateCmdEnum::Verified); if ((_desired != Complete) || (!ok)) { return; } // Reboot but only if it's a FW target, not if its a FPGA target. if ((_target == HD) || (_target == DG)) { ok = _sendCommand(SwUpdateCmdEnum::RunApp); } _state = ok ? UpdateState::Completed : UpdateState::RebootTimeout; // If it failed, tell the boot loader to abort, // at least try to. if (_state != UpdateState::Completed) { // Best effort abort. _sendCommand(SwUpdateCmdEnum::Abort); } } /*! * \brief Send command. * * \param cmd Command enum to use. * * \return True on success, else false. */ bool UiProtocol::_sendCommand(SwUpdateCmdEnum cmd) { SwUpdateCommand msg; msg.id = _gIdNum++; msg.cmd = SwUpdate_FormCommand(_target, cmd); msg.rand = _gRandSeed++; SwUpdate_createSecurity((uint8 *)&msg, sizeof(SwUpdateCommand)); return _link.sendOk(SwUpdateCanMsgIds::CommandId, (uint8 *)&msg, _retryEffort); } /*! * \brief Stream the data. * * \return True on completed ok. */ bool UiProtocol::_streamData() { // Stream the data. SwUpdateDataBuffer msg; msg.id = _gIdNum++; msg.kind = _target; _transfered = 0; msg.index = 0; // While data to stream... const std::size_t image_size = _pProvider->totalSize; while (_transfered < image_size) { // Compute the size in bytes of the transfer, upto MAX_TRANSFER_SIZE. const uint32 sz = (uint32)(_transfered + MAX_TRANSFER_SIZE) < (uint32)image_size ? (uint32)MAX_TRANSFER_SIZE : (uint32)(image_size - _transfered); // Copy the data in. std::size_t rd_sz = _pProvider->read(msg.index * BYTES_PER_INDEX, msg.buffer, sz); if (rd_sz < MAX_TRANSFER_SIZE) { memset(&msg.buffer[rd_sz], 0, MAX_TRANSFER_SIZE - rd_sz); } // Obscure it. SwUpdate_encodeData(&msg); // The read typically takes <1ms time, but it might not, and it might block or even fail. bool ok = rd_sz > 0; if (_desired != Complete) { return false; } if (!ok) { // Retry read. continue; } // Create the security token and send it. SwUpdate_createSecurity((uint8 *) &msg, sizeof(SwUpdateDataBuffer)); ok = _link.sendOk( (_target == HD) || (_target == DG) ? SwUpdateCanMsgIds::DataBufferId_FW : SwUpdateCanMsgIds::DataBufferId_FPGA, (uint8*)&msg, _retryEffort); if ((_desired != Complete) || (!ok)) { return false; } // Cyber check. if (_link.numberRetries() > _maxCyberRetries) { return false; } // Good, so increment. msg.index += (MAX_TRANSFER_SIZE / BYTES_PER_INDEX); _transfered += MAX_TRANSFER_SIZE; } return true; } /*! * \brief Stream the data. * * \return True on completed ok. */ bool UiProtocol::_sendSignature() { uint32 crc1 = _pProvider->verifyData; uint32 crc2 = _pProvider->versionData; SwUpdateDataBuffer msg; msg.id = _gIdNum++; msg.kind = _target; _transfered = 0; msg.index = 0xFFFF; SwUpdate_makeSignature(crc1, crc2, msg.buffer); // Obscure it. SwUpdate_encodeData(&msg); // Create the security token and send it. SwUpdate_createSecurity((uint8 *) &msg, sizeof(SwUpdateDataBuffer)); bool ok = _link.sendOk( (_target == HD) || (_target == DG) ? SwUpdateCanMsgIds::DataBufferId_FW : SwUpdateCanMsgIds::DataBufferId_FPGA, (uint8*)&msg, _retryEffort); return (ok && (_link.numberRetries() <= _maxCyberRetries)); } } // namespace::SwUpdate