#include "icsneo/device/extensions/bootloader/communication/radperiphcommunication.h" #include "icsneo/device/extensions/bootloader/parser/fwrepo.h" #include "icsneo/device/extensions/bootloader/platform/logging.h" #include #include #include using namespace icsneo::Bootloader; RADPeriphCommunication::RADPeriphCommunication(icsneo::Device& dev, const ChipInfo& chipToFlash) : Communication(dev), targetChip(chipToFlash) {} RADPeriphCommunication::~RADPeriphCommunication() { if(device.com->readsArePaused()) { device.com->resumeReads(); } } bool RADPeriphCommunication::sendRequestNoResponse(Request req, std::vector&& args) { return sendRequestNoResponse(req, targetChip.getID(), std::move(args)); } bool RADPeriphCommunication::sendRequestNoResponse(Request req, ChipID id, std::vector&& args) { args.insert(args.begin(), { (uint8_t)id, (uint8_t)req, }); return device.com->sendCommand(icsneo::Command::PeripheralBootloader, args); } RADPeriphCommunication::Response RADPeriphCommunication::sendRequest(Request req, std::vector&& args, std::chrono::milliseconds timeout) { return sendRequest(req, targetChip.getID(), std::move(args), timeout); } RADPeriphCommunication::Response RADPeriphCommunication::sendRequest(Request req, ChipID id, std::vector&& args, std::chrono::milliseconds timeout) { // Total reply sizes, including the header bytes size_t expectedResponseLength = 0; bool checkBlAccepted = false; switch(req) { case Request::Mode: expectedResponseLength = 6; break; case Request::Version: expectedResponseLength = 24; checkBlAccepted = true; break; case Request::Authenticate: expectedResponseLength = 13; checkBlAccepted = true; break; case Request::Echo: expectedResponseLength = 3; break; case Request::Erase: case Request::FirmwareValidate: case Request::FlashSector: expectedResponseLength = 3; checkBlAccepted = true; break; default: expectedResponseLength = 3; break; } auto& rb = device.com->driver->getReadBuffer(); rb.clear(); if(!sendRequestNoResponse(req, id, std::move(args))) { Log::Error(__func__, "Failed to send request " + std::to_string(static_cast(req))); return {}; } auto result = device.com->driver->waitForRx(expectedResponseLength, timeout); if(!result) { Log::Error(__func__, "No response to request " + std::to_string(static_cast(req))); return {}; } Response resp; resp.data.resize(expectedResponseLength, 0); rb.read(resp.data.data(), 0, expectedResponseLength); resp.success = resp.data[ReplySuccessOffset] == ReplySuccess; if(!resp.success) { Log::Error(__func__, "The peripheral reported failure for request " + std::to_string(static_cast(req))); return resp; } if(checkBlAccepted && resp.data[ReplyAcceptedOffset] != ReplyAccepted) { Log::Error(__func__, "The bootloader rejected request " + std::to_string(static_cast(req))); return {}; } return resp; } RADPeriphCommunication::Mode RADPeriphCommunication::getMode() { auto resp = sendRequest(Request::Mode, {}, std::chrono::milliseconds(2000)); if(!resp) { return Mode::NoAnswer; } static constexpr size_t ModeOffset = 4; return (Mode)resp.data[ModeOffset]; } bool RADPeriphCommunication::sendPeriphTest() { // Echo is sent to chip id 0, the reply carries the bitwise inverse of the test byte const uint8_t testByte = 0x00; auto resp = sendRequest(Request::Echo, (ChipID)0x00, {testByte}, std::chrono::milliseconds(2000)); if(!resp) { return false; } if(resp.data[2] != static_cast(~testByte)) { Log::Error(__func__, "The peripheral echo reply did not match"); return false; } return true; } bool RADPeriphCommunication::readSerialNumber() { auto resp = sendRequest(Request::Version, {0x00}, std::chrono::milliseconds(2000)); if(!resp) { return false; } // The reply payload is the start of a BootloaderVersionResponse static constexpr size_t SerialOffset = ReplyHeaderSize + offsetof(BootloaderVersionResponse, serialNum); serialNumber = readLE32(resp.data.data() + SerialOffset); return true; } bool RADPeriphCommunication::authenticate() { icsbl_seed_v1 seed = {}; if(!icsbl_generate_seed_v1(serialNumber, &seed)) { Log::Error(__func__, "libicsbl could not generate an authentication challenge"); return false; } std::vector args(BootloaderArgsOffset + sizeof(seed.a), 0); writeLE32(args.data() + BootloaderArgsOffset, seed.a[0]); writeLE32(args.data() + BootloaderArgsOffset + 4, seed.a[1]); auto resp = sendRequest(Request::Authenticate, std::move(args), std::chrono::milliseconds(3000)); if(!resp) { return false; } // seed.b follows the header and one more byte static constexpr size_t ReplyOffset = ReplyHeaderSize + 1; return readLE32(resp.data.data() + ReplyOffset) == seed.b[0] && readLE32(resp.data.data() + ReplyOffset + 4) == seed.b[1]; } bool RADPeriphCommunication::validateFirmware() { return sendRequest(Request::FirmwareValidate, {0x00u}, std::chrono::milliseconds(2000)); } bool RADPeriphCommunication::flashSector(const IEF::Sector& sector) { if(sector.second.size() < FlashSectorFirstChunkSize) { Log::Error(__func__, "Invalid sector length"); return false; } // The first request is the 32-bit little-endian address followed by the first chunk, encrypted std::vector args(BootloaderArgsOffset + sizeof(uint32_t) + FlashSectorFirstChunkSize, 0); writeLE32(args.data() + BootloaderArgsOffset, sector.first); icsbl_encrypt_v1(serialNumber, sector.second.begin(), FlashSectorFirstChunkSize, args.data() + BootloaderArgsOffset + sizeof(uint32_t)); if(!sendRequestNoResponse(Request::FlashSector, std::move(args))) { return false; } std::this_thread::sleep_for(std::chrono::milliseconds(400)); // The second request is the rest of the sector, encrypted const size_t bytesLeft = sector.second.size() - FlashSectorFirstChunkSize; args = std::vector(bytesLeft, 0); icsbl_encrypt_v1(serialNumber, sector.second.begin() + FlashSectorFirstChunkSize, bytesLeft, args.data()); return sendRequest(Request::FlashSector, std::move(args), std::chrono::milliseconds(3000)); } bool RADPeriphCommunication::flash(const FWRepo& fw, const icsneo::Bootloader::StatusHandler& handler) { // Validate the firmware before touching the device so a bad file can not leave it erased std::vector bin; if(!fw.getFW(targetChip.getIEFName(), bin)) { return false; } IEF ief; if(!ief.load(std::move(bin))) { return false; } // The supplied IEF is not for this product if(ief.getVersion().prodId != static_cast(device.getProductID())) { Log::Error(__func__, "IEF product ID mismatch for " + targetChip.getName()); return false; } // The supplied IEF is for this product, but not for this chip if(ief.getVersion().chipId != static_cast(targetChip.getID())) { Log::Error(__func__, "IEF chip ID mismatch for " + targetChip.getName()); return false; } const auto& sectors = ief.getSectors(); uint32_t totalSize = 0; for(const auto& sector : sectors) { if(sector.second.size() < FlashSectorFirstChunkSize) { Log::Error(__func__, "Invalid sector length in IEF for " + targetChip.getName()); return false; } totalSize += static_cast(sector.second.size()); } if(!readSerialNumber()) { return false; } if(!authenticate()) { return false; } if(!sendRequest(Request::Erase)) { Log::Error(__func__, "Failed to erase firmware"); return false; } handler( StatusType::Progress, "Flashing " + targetChip.getName() + "...", std::nullopt ); uint32_t sizeFlashed = 0; for(const auto& sector : sectors) { if(!flashSector(sector)) { return false; } sizeFlashed += static_cast(sector.second.size()); handler( StatusType::Progress, "Flashing " + targetChip.getName() + "...", (double)sizeFlashed / (double)totalSize ); } return validateFirmware(); } bool RADPeriphCommunication::waitForConnect(std::chrono::milliseconds timeout) { const auto deadline = std::chrono::steady_clock::now() + timeout; while(std::chrono::steady_clock::now() < deadline) { if(sendPeriphTest()) { return true; } } return false; } bool RADPeriphCommunication::open() { device.com->pauseReads(); int attempts = 3; do { mode = getMode(); if(mode == Mode::NoAnswer) { std::this_thread::sleep_for(std::chrono::milliseconds(500)); mode = getMode(); if(mode == Mode::NoAnswer) { return false; } } if(mode == Mode::MainApplication) { if(!sendRequestNoResponse(Request::SwitchToBootloader)) { return false; } if(!device.reconnect(std::chrono::seconds(30))) { return false; } if(!waitForConnect(std::chrono::seconds(30))) { return false; } } else if(mode == Mode::Bootloader) { return true; } } while(attempts--); return false; } bool RADPeriphCommunication::close() { if(mode != Mode::Bootloader) { device.com->resumeReads(); return true; } if(!sendRequestNoResponse(Request::SwitchToApp)) { return false; } std::this_thread::sleep_for(std::chrono::seconds(5)); // measured at 2s, 5s for buffer, yes this is needed if(!device.reconnect(std::chrono::seconds(30))) { return false; } if(!waitForConnect()) { return false; } mode = getMode(); if(mode == Mode::MainApplication) { device.com->resumeReads(); return true; } return false; } bool RADPeriphCommunication::enterApplication() { return true; } bool RADPeriphCommunication::finalize() { return true; } bool RADPeriphCommunication::hasFinalizationStep() const { return false; }