Add bootloader extension

This commit is contained in:
Thomas Stoddard
2026-10-21 19:22:50 -04:00
committed by Kyle Schwarz
parent 4db27aa187
commit 6ac041cadc
61 changed files with 13816 additions and 68 deletions
@@ -0,0 +1,407 @@
#include "icsneo/device/extensions/bootloader/communication/application.h"
#include "icsneo/device/extensions/bootloader/parser/fwrepo.h"
#include "icsneo/device/extensions/bootloader/platform/logging.h"
#include "icsneo/device/extensions/bootloader/api.h"
#include "icsneo/communication/message/extendedresponsemessage.h"
#include "icsneo/communication/message/main51message.h"
#include <algorithm>
#include <condition_variable>
#include <cstring>
#include <limits>
#include <mutex>
#include <optional>
#include <thread>
#include <icsbl/icsbl.h>
namespace icsneo {
namespace Bootloader {
bool Application::enterApplication() {
// Always reboot
Log::Info(__func__, "sending reboot command");
bool ok = device.com->sendCommand(icsneo::ExtendedCommand::Reboot);
if(!ok) {
Log::Error(__func__, "reboot command failed");
}
return ok;
}
bool Application::finalize() {
Log::Debug(__func__, "sending finalize command");
bool ok = sendCommand(Update::Finalize, [](double) {});
if(!ok) {
Log::Error(__func__, "finalize command failed");
}
return ok;
}
bool Application::flash(const FWRepo& fw, const icsneo::Device::OpenStatusHandler& handler) {
Log::Info(__func__, "starting flash for " + targetChip.getName());
const auto& srcVer = fw.getVersion();
std::vector<uint8_t> bin;
if(!fw.getFW(targetChip.getIEFName(), bin)) {
Log::Error(__func__, "failed to get firmware for " + targetChip.getIEFName());
return false;
}
IEF ief;
if(!ief.load(std::move(bin))) {
Log::Error(__func__, "failed to load IEF for " + targetChip.getName());
return false;
}
auto currentString = "Preparing to update " + targetChip.getName() + "...";
handler(icsneo::Device::OpenStatusType::Progress, currentString, std::nullopt);
// The supplied IEF is not for this product
if(ief.getVersion().prodId != static_cast<uint8_t>(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<uint8_t>(targetChip.getID())) {
Log::Error(__func__, "IEF chip ID mismatch for " + targetChip.getName());
return false;
}
if(doAuthChecks) {
if(!authenticate()) {
Log::Error(__func__, "authentication failed for " + targetChip.getName());
return false;
}
// Tell the application software what version of Vehicle Spy 3 this firmware is from.
// We check to make sure it approves, but also, the device will not accept new firmware
// if we have not sent this.
// Sometimes new hardware revisions can not accept firmware which is too old.
if(!sendCommand(Update::CheckVersion, [](double) {}, 0, {srcVer.major, srcVer.minor, srcVer.release, srcVer.build})) {
Log::Error(__func__, "version check failed for " + targetChip.getName());
return false;
}
}
bool ok = sendSoftware(ief.getSectors(), ief.getBlockSize(), handler);
if(!ok) {
Log::Error(__func__, "sendSoftware failed for " + targetChip.getName());
}
return ok;
}
bool Application::authenticate() {
icsbl_seed_v1 seed = {};
if(!icsbl_generate_seed_v1(device.getSerialNumber(), &seed)) {
Log::Error(__func__, "libicsbl could not generate an authentication challenge");
return false;
}
// Send the challenge (seed.a), the device replies with seed.b and one trailing byte
std::vector<uint8_t> cmdArgs(sizeof(seed.a));
writeLE32(cmdArgs.data(), seed.a[0]);
writeLE32(cmdArgs.data() + 4, seed.a[1]);
auto filter = std::make_shared<icsneo::MessageFilter>(icsneo::Message::Type::Main51);
auto msg = device.com->waitForMessageSync([&] {
return device.com->sendCommand(icsneo::Command::ValidateDevice, cmdArgs);
}, filter, std::chrono::milliseconds(200));
if(!msg) {
Log::Error(__func__, "No response from the device");
return false;
}
auto main51Msg = std::static_pointer_cast<icsneo::Main51Message>(msg);
if(main51Msg->data.size() != sizeof(seed.b) + 1) {
Log::Error(__func__, "Unexpected authentication reply size from the device");
return false;
}
const bool authenticated = readLE32(main51Msg->data.data()) == seed.b[0] && readLE32(main51Msg->data.data() + 4) == seed.b[1];
if(!authenticated) {
Log::Error(__func__, "The authentication reply does not match, the device did not authenticate");
}
return authenticated;
}
bool Application::validateFirmware(const DeviceAppVersion& chipSrcVer) {
return sendCommand(Update::CheckComponentVersion, [](double) {}, 0, {chipSrcVer.major, chipSrcVer.minor});
}
bool Application::sendSoftware(const std::vector<uint8_t>& bin, const icsneo::Device::OpenStatusHandler& handler) {
Log::Debug(__func__, "starting for " + targetChip.getName() + " (" + std::to_string(bin.size()) + " bytes)");
auto currentString = "Preparing to update " + targetChip.getName() + "...";
handler(icsneo::Device::OpenStatusType::Progress, currentString, std::nullopt);
if(bin.empty() || bin.size() > std::numeric_limits<uint32_t>::max()) {
Log::Error(__func__, "invalid firmware size for " + targetChip.getName());
return false;
}
const uint32_t totalSize = static_cast<uint32_t>(bin.size());
Log::Debug(__func__, "erasing " + targetChip.getName());
if(!sendCommand(Update::Erase, 0, totalSize, [&](double progress) {
handler(icsneo::Device::OpenStatusType::Progress, currentString, progress);
}, std::chrono::milliseconds(45000))) {
Log::Error(__func__, "erase failed for " + targetChip.getName());
return false;
}
currentString = "Updating " + targetChip.getName() + "...";
handler(icsneo::Device::OpenStatusType::Progress, currentString, std::nullopt);
const uint32_t stepSize = std::min<uint32_t>(totalSize, 2048);
uint32_t bytesTransferred = 0;
while(bytesTransferred < totalSize) {
const uint32_t bytesToSend = std::min(stepSize, totalSize - bytesTransferred);
if(!sendCommand(Update::Write, [](double) {}, bytesTransferred, { bin.begin() + bytesTransferred, bin.begin() + bytesTransferred + bytesToSend})) {
Log::Error(__func__, "write failed at offset " + std::to_string(bytesTransferred) + " for " + targetChip.getName());
return false;
}
bytesTransferred += bytesToSend;
handler(icsneo::Device::OpenStatusType::Progress, currentString, double(bytesTransferred) / totalSize);
}
Log::Debug(__func__, "write complete, validating " + targetChip.getName());
if(!validateComponent()) {
Log::Error(__func__, "validation failed for " + targetChip.getName());
return false;
}
Log::Debug(__func__, "completed successfully for " + targetChip.getName());
return true;
}
bool Application::sendSoftware(const std::vector<IEF::Sector>& sectorPairs, uint32_t blockSize, const icsneo::Device::OpenStatusHandler& handler) {
if(sectorPairs.empty()) {
Log::Error(__func__, "no sectors to flash for " + targetChip.getName());
return false;
}
Log::Debug(__func__, "starting for " + targetChip.getName() + " (" + std::to_string(sectorPairs.size()) + " sectors)");
auto currentString = "Preparing to update " + targetChip.getName() + "...";
handler(icsneo::Device::OpenStatusType::Progress, currentString, std::nullopt);
uint64_t totalSize = 0;
for(const auto& sector : sectorPairs) {
totalSize += sector.second.size();
}
if(totalSize > std::numeric_limits<uint32_t>::max()) {
Log::Error(__func__, "total size too large for " + targetChip.getName());
return false;
}
if(totalSize == 0) {
Log::Error(__func__, "total size is 0 for " + targetChip.getName());
return false;
}
Log::Debug(__func__, "erasing " + targetChip.getName() + " (" + std::to_string(totalSize) + " bytes)");
if(!sendCommand(Update::Erase, 0, static_cast<uint32_t>(totalSize), [&](double progress) {
handler(icsneo::Device::OpenStatusType::Progress, currentString, progress);
}, std::chrono::milliseconds(45000))) {
Log::Error(__func__, "erase failed for " + targetChip.getName());
return false;
}
if(isSFPChip(targetChip)) {
// SFPs are not ready to write until 4 seconds after finishing erasing (for some reason)
Log::Trace(__func__, "SFP chip detected, waiting 4000ms after erase");
std::this_thread::sleep_for(std::chrono::milliseconds(4000));
}
currentString = "Updating " + targetChip.getName() + "...";
handler(icsneo::Device::OpenStatusType::Progress, currentString, std::nullopt);
uint32_t bytesTransferred = 0;
for(const auto& pair : sectorPairs) {
const auto& rawData = pair.second;
const uint32_t offset = pair.first * blockSize;
std::vector<uint8_t> processedData(rawData.begin(), rawData.end());
if(encrypted) {
if(rawData.size() % 8 != 0) {
Log::Error(__func__, "raw data size not aligned");
return false;
}
icsbl_encrypt_v1(device.getSerialNumber(), rawData.data(), rawData.size(), processedData.data());
}
if(!sendCommand(Update::Write, [](double) {}, offset, processedData)) {
Log::Error(__func__, "write failed at offset " + std::to_string(offset) + " for " + targetChip.getName());
return false;
}
bytesTransferred += static_cast<uint32_t>(rawData.size());
handler(icsneo::Device::OpenStatusType::Progress, currentString, double(bytesTransferred) / double(totalSize));
}
Log::Debug(__func__, "write complete, validating " + targetChip.getName());
bool ok = validateComponent();
if(!ok) {
Log::Error(__func__, "validation failed for " + targetChip.getName());
}
else {
Log::Debug(__func__, "completed successfully for " + targetChip.getName());
}
return ok;
}
bool Application::validateComponent() {
Log::Debug(__func__, "validating " + targetChip.getName());
bool ok = sendCommand(Update::ValidateComponent, [](double) {}, 0, {}, std::chrono::milliseconds(45000));
if(!ok) {
Log::Error(__func__, "validation failed for " + targetChip.getName());
}
return ok;
}
/**
* Send one SoftwareUpdate sub-command and wait for it to complete.
*
* The device answers the request with OK when it is already done, or OperationPending when it will
* finish later. For a pending command we poll Update::GetProgress (except for Write and
* ValidateComponent, which do not report progress) until the device sends a final OK or the timeout
* passes. Progress replies are OperationPending with a SoftwareUpdateCommandHeader whose
* commandSizeOrProgress holds the progress so far, or a GenericReturn with no progress.
*/
bool Application::sendCommand(Update command, uint32_t offset, uint32_t length, const StatusHandler& handler, std::chrono::milliseconds timeout, std::vector<uint8_t> args) {
bool finishedResponse = false;
std::condition_variable finishedResponseCV;
std::mutex finishedResponseMutex;
std::optional<uint32_t> progress;
std::condition_variable progressCV;
std::mutex progressMutex;
bool firstResponse = true;
// Register the callback before sending the command, so that if it goes from pending to finished quickly we will still catch it
const auto callback = std::make_shared<icsneo::MessageCallback>(std::make_shared<icsneo::MessageFilter>(icsneo::Message::Type::ExtendedResponse), [&](std::shared_ptr<icsneo::Message> msg) {
if(firstResponse) {
firstResponse = false;
return true; // let waitForMessageSync handle the first message
}
auto extMsg = std::static_pointer_cast<ExtendedResponseMessage>(msg);
if(extMsg->command != ExtendedCommand::SoftwareUpdate) {
Log::Error(__func__, "Got an extended response for a command other than SoftwareUpdate");
return false;
}
if(extMsg->response == ExtendedResponse::OK) {
{
std::scoped_lock lk(finishedResponseMutex);
finishedResponse = true;
}
finishedResponseCV.notify_one();
} else if(extMsg->response == ExtendedResponse::OperationPending) {
if(extMsg->data.size() < sizeof(ExtendedResponseMessage::ResponseHeader)) {
Log::Error(__func__, "The pending response is too short to contain a header");
return false;
}
ExtendedResponseMessage::ResponseHeader respHeader;
std::memcpy(&respHeader, extMsg->data.data(), sizeof(respHeader));
if(respHeader.command == ExtendedCommand::SoftwareUpdate) {
if(respHeader.length != sizeof(SoftwareUpdateCommandHeader) ||
extMsg->data.size() < sizeof(ExtendedResponseMessage::ResponseHeader) + sizeof(SoftwareUpdateCommandHeader)) {
Log::Error(__func__, "The progress response does not contain a SoftwareUpdate header");
return false;
}
SoftwareUpdateCommandHeader resp;
std::memcpy(&resp, extMsg->data.data() + sizeof(ExtendedResponseMessage::ResponseHeader), sizeof(resp));
{
std::scoped_lock lk(progressMutex);
progress.emplace(resp.commandSizeOrProgress);
}
progressCV.notify_one();
} else if(respHeader.command == ExtendedCommand::GenericReturn) {
// No progress information, the final OK comes later
{
std::scoped_lock lk(progressMutex);
progress.emplace(0);
}
progressCV.notify_one();
} else {
Log::Error(__func__, "The pending response is for an unexpected command");
return false;
}
} else {
Log::Error(__func__, "The device reported an error for the SoftwareUpdate command");
return false;
}
return true;
});
const auto finishedCallbackHandle = device.com->addMessageCallback(callback);
Lifetime statusCallbackCleanup([&] {
device.com->removeMessageCallback(finishedCallbackHandle);
});
SoftwareUpdateCommandHeader header = {};
header.componentIdentifier = static_cast<uint32_t>(targetChip.getID());
header.commandType = command;
header.offset = offset;
header.commandSizeOrProgress = length;
header.expansionSlot = targetChip.expansionSlot;
const auto headerBytes = reinterpret_cast<const uint8_t*>(&header);
args.insert(args.begin(), headerBytes, headerBytes + sizeof(header));
{
auto msg = device.com->waitForMessageSync([&] {
return device.com->sendCommand(ExtendedCommand::SoftwareUpdate, args);
}, std::make_shared<icsneo::MessageFilter>(icsneo::Message::Type::ExtendedResponse), timeout);
if(!msg) {
Log::Error(__func__, "No response to the SoftwareUpdate command");
return false;
}
auto extMsg = std::static_pointer_cast<ExtendedResponseMessage>(msg);
if(extMsg->command != ExtendedCommand::SoftwareUpdate) {
Log::Error(__func__, "Got an extended response for a command other than SoftwareUpdate");
return false;
}
if(extMsg->response == ExtendedResponse::OK) {
return true;
}
if(extMsg->response != ExtendedResponse::OperationPending) {
Log::Error(__func__, "The device rejected the SoftwareUpdate command");
return false;
}
}
SoftwareUpdateCommandHeader progressHeader = {};
progressHeader.componentIdentifier = static_cast<uint32_t>(targetChip.getID());
progressHeader.commandType = Update::GetProgress;
progressHeader.expansionSlot = targetChip.expansionSlot;
const auto progressHeaderBytes = reinterpret_cast<const uint8_t*>(&progressHeader);
const std::vector<uint8_t> progressArgs(progressHeaderBytes, progressHeaderBytes + sizeof(progressHeader));
const auto deadline = std::chrono::steady_clock::now() + timeout;
// Write and ValidateComponent do not report progress, just wait for the final OK
if(command != Update::Write && command != Update::ValidateComponent) {
while(std::chrono::steady_clock::now() < deadline) {
{
std::unique_lock lk(progressMutex);
progress.reset();
if(!device.com->sendCommand(ExtendedCommand::SoftwareUpdate, progressArgs)) {
Log::Error(__func__, "Failed to send progress request to the device");
return false;
}
if(!progressCV.wait_for(lk, std::chrono::milliseconds(5000), [&]{ return progress.has_value(); })) {
Log::Error(__func__, "No progress response from device");
return false;
}
handler(*progress / double(length));
}
{
std::scoped_lock lk(finishedResponseMutex);
if(finishedResponse) {
break;
}
}
}
}
std::unique_lock lk(finishedResponseMutex);
return finishedResponseCV.wait_until(lk, deadline, [&]{ return finishedResponse; });
}
} // namespace Bootloader
} // namespace icsneo
@@ -0,0 +1,14 @@
#include "icsneo/device/extensions/bootloader/communication/communication.h"
using namespace icsneo::Bootloader;
uint32_t Communication::readLE32(const uint8_t* buf) {
return (uint32_t)buf[0] | ((uint32_t)buf[1] << 8) | ((uint32_t)buf[2] << 16) | ((uint32_t)buf[3] << 24);
}
void Communication::writeLE32(uint8_t* buf, uint32_t num) {
buf[0] = num & 0xFFu;
buf[1] = (num >> 8) & 0xFFu;
buf[2] = (num >> 16) & 0xFFu;
buf[3] = (num >> 24) & 0xFFu;
}
@@ -0,0 +1,547 @@
#include "icsneo/device/extensions/bootloader/communication/radcommunication.h"
#include "icsneo/device/extensions/bootloader/communication/bootloaderversion.h"
#include "icsneo/device/extensions/bootloader/parser/fwzip.h"
#include "icsneo/device/extensions/bootloader/parser/fwrepo.h"
#include "icsneo/device/extensions/bootloader/platform/logging.h"
#include <algorithm>
#include <cstring>
#include <thread>
#include <icsbl/icsbl.h>
using namespace icsneo::Bootloader;
RADCommunication::RADCommunication(icsneo::Device& dev, const ChipInfo& info)
: Communication(dev), targetChip(info) {}
bool RADCommunication::supportsFlashBuffer() const {
switch(targetChip.getID()) {
case icsneo::ChipID::RADGigastar_USBZ_ZYNQ:
case icsneo::ChipID::RADGigastar_USBZ_Z7007S_ZYNQ:
case icsneo::ChipID::RADGigastar_USBZ_Z7010_ZYNQ: {
return true;
}
default:
return false;
}
}
RADCommunication::Request RADCommunication::makeRequest(Request::Type type, size_t payloadSize, size_t expectedResponseLen) {
const size_t headerSize = supportsZynqBinaryFlasher ? ZynqFlasherHeaderSize : 0;
const size_t packetSize = headerSize + 1 + payloadSize; // Header + command + payload
// This shouldn't cause any re-allocations since we already reserved the MaxMessageLength in initialize()
txBuffer.resize(packetSize, 0);
if(supportsZynqBinaryFlasher) {
std::copy(std::begin(ZynqFlasherSync), std::end(ZynqFlasherSync), txBuffer.begin());
// The length covers the command byte and the payload
const uint16_t payloadPlusCmd = static_cast<uint16_t>(payloadSize) + 1;
txBuffer[4] = payloadPlusCmd & 0xFFu;
txBuffer[5] = (payloadPlusCmd >> 8) & 0xFFu;
}
txBuffer[headerSize] = static_cast<uint8_t>(type);
Request newRequest;
newRequest.type = type;
newRequest.expectedResponseLen = expectedResponseLen;
if(payloadSize > 0) {
newRequest.payload = &txBuffer[headerSize + 1]; // First byte right after the command
}
return newRequest;
}
bool RADCommunication::sendRequestNoResponse() {
/**
* Send our current packet, the payload was written to it if the user
* of this function wrote to request.payload. The remaining details were
* written when makeRequest() was called
*/
return device.com->sendPacket(txBuffer);
}
RADCommunication::Response& RADCommunication::sendRequest(const Request& request, std::chrono::milliseconds timeout) {
static constexpr size_t ResponseHeaderSize = 2;
const size_t rxSize = ResponseHeaderSize + request.expectedResponseLen;
/**
* Clear previous response
*/
lastResponse = Response();
if(!sendRequestNoResponse()) {
Log::Error(__func__, "Failed to send request " + std::to_string(static_cast<uint8_t>(request.type)));
return lastResponse;
}
auto& rb = device.com->driver->getReadBuffer();
rb.clear();
auto result = device.com->driver->waitForRx(rxSize, timeout);
if(!result) {
Log::Error(__func__, "No response to request " + std::to_string(static_cast<uint8_t>(request.type)));
return lastResponse;
}
rb.read(rxBuffer.data(), 0, rxSize);
rb.clear();
lastResponse.done = true;
/**
* Second byte is success/failure byte
*/
lastResponse.type = static_cast<Response::Type>(rxBuffer[1]);
/**
* The rest is the payload, if we requested any
*/
if(request.expectedResponseLen > 0) {
lastResponse.payload = &rxBuffer[2];
lastResponse.size = request.expectedResponseLen;
}
return lastResponse;
}
bool RADCommunication::enterApplication() {
Log::Info(__func__, "sending EnterApplication request");
makeRequest(Request::Type::EnterApplication);
bool ok = sendRequestNoResponse();
if(!ok) {
Log::Error(__func__, "EnterApplication request failed");
}
return ok;
}
bool RADCommunication::close() {
device.com->resumeReads();
return true;
}
bool RADCommunication::open() {
device.com->pauseReads();
if(!initialize()) {
close();
return false;
}
if(!sanityCheck()) {
// Note: RAD-Gigastar on low power USB flash fails here for some reason, try another sanity check
if(!sanityCheck()) {
close();
return false;
}
}
if(!getBootloaderVersion()) {
// Try again, just like the sanity checks
if(!getBootloaderVersion()) {
close();
return false;
}
}
if(!sanityCheck()) {
// Note: RAD-Gigastar on low power USB flash fails here for some reason, try another sanity check
if(!sanityCheck()) {
close();
return false;
}
}
if(!authenticate()) {
close();
return false;
}
return true;
}
bool RADCommunication::initialize() {
auto& rb = device.com->driver->getReadBuffer();
rxBuffer = std::vector<uint8_t>(MaxMessageLength, 0);
txBuffer.reserve(MaxMessageLength);
// An EnterBootloader command on Main51, as a framed short packet (0xAA, then length << 4 | netid)
// with a trailing 0xAF, followed by the same command without the 0xAA framing
const uint8_t lengthAndNetID = (1 << 4) | static_cast<uint8_t>(icsneo::Network::NetID::Main51);
std::vector<uint8_t> pkt = {
0xAA,
lengthAndNetID,
static_cast<uint8_t>(icsneo::Command::EnterBootloader),
0xAF
};
rb.clear();
if(!device.com->sendPacket(pkt)) {
Log::Error(__func__, "Failed to send the framed enter bootloader command");
return false;
}
pkt = {
lengthAndNetID,
static_cast<uint8_t>(icsneo::Command::EnterBootloader)
};
if(!device.com->sendPacket(pkt)) {
Log::Error(__func__, "Failed to send the unframed enter bootloader command");
return false;
}
// The Zynq binary flasher answers with the command, Success, then ZynqFlasherSync
static constexpr size_t ZynqFlasherReplySize = 2 + sizeof(ZynqFlasherSync);
if(!device.com->driver->waitForRx(ZynqFlasherReplySize, std::chrono::milliseconds(1000))) {
Log::Error(__func__, "No response to the enter bootloader command");
return false;
}
const size_t rxLen = rb.size();
if(rxLen == ZynqFlasherReplySize) {
rb.read(rxBuffer.data(), 0, rxLen);
}
rb.clear();
supportsZynqBinaryFlasher =
rxLen == ZynqFlasherReplySize &&
rxBuffer[0] == static_cast<uint8_t>(icsneo::Command::EnterBootloader) &&
rxBuffer[1] == static_cast<uint8_t>(Response::Type::Success) &&
std::equal(std::begin(ZynqFlasherSync), std::end(ZynqFlasherSync), rxBuffer.begin() + 2);
return true;
}
bool RADCommunication::finalize() {
return true;
}
bool RADCommunication::hasFinalizationStep() const {
return false;
}
bool RADCommunication::sanityCheck() {
if(!supportsZynqBinaryFlasher) {
return true;
}
// Two numbered test patterns the bootloader must accept, the 8 byte replies are not checked
constexpr uint8_t PatternA[8] = {0xAA, 0x55, 0xAA, 0x55, 0xAA, 0xAA, 0x55, 0x55};
constexpr uint8_t PatternB[8] = {0x12, 0x21, 0x34, 0x43, 0x56, 0x65, 0x78, 0x87};
auto request = makeRequest(Request::Type::SanityCheck, 1 + sizeof(PatternA), sizeof(PatternA));
request.payload[0] = 0x01u;
std::copy(std::begin(PatternA), std::end(PatternA), request.payload + 1);
if(!sendRequest(request, std::chrono::milliseconds(1000))) {
return false;
}
request.payload[0] = 0x02u;
std::copy(std::begin(PatternB), std::end(PatternB), request.payload + 1);
return sendRequest(request, std::chrono::milliseconds(1000));
}
bool RADCommunication::getBootloaderVersion() {
auto request = makeRequest(Request::Type::BootloaderVersion, 0, sizeof(BootloaderVersionResponse));
auto response = sendRequest(request, std::chrono::milliseconds(1000));
if(!response) {
return false;
}
if(response.size < sizeof(BootloaderVersionResponse)) {
return false;
}
BootloaderVersionResponse version;
std::memcpy(&version, response.payload, sizeof(version));
targetChip.info.id = static_cast<ChipID>(version.zynqId);
return true;
}
bool RADCommunication::validateChipVersion(const icsneo::DeviceAppVersion& chipSrcVer) {
if(!supportsZynqBinaryFlasher) {
return true;
}
auto request = makeRequest(Request::Type::ValidateVersion, 2, 2);
request.payload[0] = chipSrcVer.major;
request.payload[1] = chipSrcVer.minor;
return sendRequest(request, std::chrono::milliseconds(1000));
}
bool RADCommunication::authenticate() {
icsbl_seed_v1 seed = {};
if(!icsbl_generate_seed_v1(device.getSerialNumber(), &seed)) {
Log::Error(__func__, "libicsbl could not generate an authentication challenge");
return false;
}
auto request = makeRequest(Request::Type::Authenticate, sizeof(seed.a), sizeof(seed.b));
writeLE32(&request.payload[0], seed.a[0]);
writeLE32(&request.payload[4], seed.a[1]);
auto response = sendRequest(request, std::chrono::milliseconds(2000));
if(!response) {
return false;
}
return seed.b[0] == readLE32(&response.payload[0]) && seed.b[1] == readLE32(&response.payload[4]);
}
bool RADCommunication::sendSoftware(const std::vector<uint8_t>& chipSoftware) {
Log::Debug(__func__, "starting for " + targetChip.getName() + " (" + std::to_string(chipSoftware.size()) + " bytes)");
static constexpr size_t MaxEthernetPayload = 1487;
if(supportsZynqBinaryFlasher && chipSoftware.size() < sizeof(uint32_t)) {
Log::Error(__func__, "firmware too short to contain a CRC for " + targetChip.getName());
return false;
}
Log::Trace(__func__, "sending BeginTransfer for " + targetChip.getName());
auto request = makeRequest(Request::Type::BeginTransfer, 0, 0);
auto response = sendRequest(request, std::chrono::milliseconds(10000));
if(!response) {
Log::Error(__func__, "BeginTransfer failed for " + targetChip.getName());
return false;
}
const size_t headerSize = 1 + (supportsZynqBinaryFlasher ? ZynqFlasherHeaderSize : 0); // Command byte plus the optional Zynq flasher header
const size_t maxPayloadSize = MaxEthernetPayload - headerSize;
size_t remainingBytes = chipSoftware.size();
size_t currentlyReading = 0;
logger(
icsneo::Device::OpenStatusType::Progress,
"Flashing " + targetChip.getName() + "...",
std::nullopt
);
while(remainingBytes > 0) {
const double progress = static_cast<double>(currentlyReading) / static_cast<double>(chipSoftware.size());
logger(
icsneo::Device::OpenStatusType::Progress,
"Flashing " + targetChip.getName() + "...",
progress
);
size_t numToSend = std::min<size_t>(maxPayloadSize, remainingBytes);
request = makeRequest(Request::Type::FWPayload, numToSend, supportsZynqBinaryFlasher ? 2 : 0);
std::copy(
chipSoftware.begin() + currentlyReading,
chipSoftware.begin() + currentlyReading + numToSend,
request.payload
);
if(supportsFlashBuffer()) {
if(!sendRequest(request, std::chrono::milliseconds(2000))) {
return false;
}
} else if(!sendRequestNoResponse()) {
return false;
}
currentlyReading += numToSend;
remainingBytes -= numToSend;
if(device.com->driver->isEthernet()) {
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
}
Log::Trace(__func__, "sending EndTransfer for " + targetChip.getName());
request = makeRequest(Request::Type::EndTransfer, 0, 0);
response = sendRequest(request, std::chrono::milliseconds(10000));
if(!response) {
Log::Error(__func__, "EndTransfer failed for " + targetChip.getName());
return false;
}
request = makeRequest(Request::Type::DownloadedBinInfo, 0, 4);
response = sendRequest(request, std::chrono::milliseconds(6000));
if(!response) {
Log::Error(__func__, "DownloadedBinInfo failed for " + targetChip.getName());
return false;
}
const uint32_t numDownloaded = readLE32(response.payload);
if(numDownloaded != static_cast<uint32_t>(chipSoftware.size())) {
Log::Error(__func__, "downloaded size mismatch for " + targetChip.getName() + ": expected " + std::to_string(chipSoftware.size()) + ", got " + std::to_string(numDownloaded));
return false;
}
if(supportsZynqBinaryFlasher) {
request = makeRequest(Request::Type::VerifyCRC, sizeof(uint32_t), 2);
/**
* Copy CRC from end of the binary
*/
std::copy(chipSoftware.end() - sizeof(uint32_t), chipSoftware.end(), request.payload);
response = sendRequest(request, std::chrono::milliseconds(2000));
if(!response) {
return false;
}
// The first response byte is 1 when the CRC matched
if(response.payload[0] != 0x01) {
Log::Error(__func__, "CRC mismatch for " + targetChip.getName());
return false;
}
}
logger(
icsneo::Device::OpenStatusType::Progress,
"Flashing " + targetChip.getName() + "...",
1.0
);
Log::Debug(__func__, "completed successfully for " + targetChip.getName());
return true;
}
bool RADCommunication::getProgrammingStatus(size_t sizeToValidate) {
Log::Debug(__func__, "starting for " + targetChip.getName());
auto request = makeRequest(Request::Type::ProgramFlash, 0, supportsZynqBinaryFlasher ? 2 : 0);
if(!sendRequest(request, std::chrono::milliseconds(2000))) {
Log::Error(__func__, "ProgramFlash failed for " + targetChip.getName());
return false;
}
request = makeRequest(Request::Type::ProgrammingStatus, 0, 4);
uint32_t status = 0;
const auto beginTime = std::chrono::steady_clock::now();
logger(
icsneo::Device::OpenStatusType::Progress,
"Programming " + targetChip.getName() + "...",
std::nullopt
);
while(status < static_cast<uint32_t>(sizeToValidate)) {
const double progress = static_cast<double>(status) / static_cast<double>(sizeToValidate);
logger(
icsneo::Device::OpenStatusType::Progress,
"Programming " + targetChip.getName() + "...",
progress
);
const auto& response = sendRequest(request, std::chrono::milliseconds(60000));
if(!response) {
return false;
}
status = readLE32(response.payload);
if(std::chrono::steady_clock::now() - beginTime >= std::chrono::milliseconds(180000)) {
return false;
}
}
logger(
icsneo::Device::OpenStatusType::Progress,
"Programming " + targetChip.getName() + "...",
1.0
);
Log::Debug(__func__, "completed successfully for " + targetChip.getName());
return true;
}
bool RADCommunication::loadZips(const FWRepo& fw, std::vector<uint8_t>& chipSoftware) {
if(const auto& segmentNames = targetChip.getMultiIEFName(); !segmentNames.empty()) {
std::vector<std::unique_ptr<FWZip>> zips;
zips.reserve(segmentNames.size());
size_t totalSize = 0;
for(const auto& name : segmentNames) {
std::vector<uint8_t> segment;
if(!fw.getFW(name, segment)) {
return false;
}
zips.push_back(std::make_unique<FWZip>(std::move(segment)));
auto& zip = zips.back();
if(!zip->size()) {
return false;
}
totalSize += *zip->size();
}
chipSoftware.resize(totalSize, 0);
size_t offset = 0;
for(auto& zip : zips) {
if(!zip->readAll(chipSoftware.data() + offset)) {
return false;
}
offset += *(zip->size());
}
return true;
} else if(!targetChip.getIEFName().empty()) {
std::vector<uint8_t> bin;
if(!fw.getFW(targetChip.getIEFName(), bin)) {
return false;
}
FWZip fwZip(std::move(bin));
if(!fwZip.readAll(chipSoftware)) {
return false;
}
return true;
}
// Should never happen
return false;
}
bool RADCommunication::flash(const FWRepo& fw, const icsneo::Device::OpenStatusHandler& handler) {
Log::Info(__func__, "starting flash for " + targetChip.getName());
auto chipSrcVer = fw.getChipVersion(targetChip.getIEFName());
if(!chipSrcVer) {
Log::Error(__func__, "no chip version found for " + targetChip.getIEFName());
return false;
}
std::vector<uint8_t> chipSoftware;
if(!loadZips(fw, chipSoftware)) {
Log::Error(__func__, "loadZips failed for " + targetChip.getName());
return false;
}
logger = handler;
if(!validateChipVersion(*chipSrcVer)) {
Log::Error(__func__, "validateChipVersion failed for " + targetChip.getName());
return false;
}
if(!sendSoftware(chipSoftware)) {
Log::Error(__func__, "sendSoftware failed for " + targetChip.getName());
return false;
}
if(!supportsFlashBuffer()) {
if(!getProgrammingStatus(chipSoftware.size())) {
Log::Error(__func__, "getProgrammingStatus failed for " + targetChip.getName());
return false;
}
}
Log::Info(__func__, "completed successfully for " + targetChip.getName());
return true;
}
@@ -0,0 +1,144 @@
#include "icsneo/device/extensions/bootloader/communication/radmultichipcommunication.h"
#include "icsneo/device/extensions/bootloader/parser/fwrepo.h"
#include "icsneo/device/extensions/bootloader/parser/ief.h"
#include "icsneo/device/extensions/bootloader/api.h"
#include "icsneo/device/extensions/bootloader/platform/logging.h"
#include <algorithm>
#include <thread>
using namespace icsneo::Bootloader;
RADMultiChipCommunication::RADMultiChipCommunication(icsneo::Device& dev, const ChipInfo& chipToFlash, bool auth, uint8_t slot)
: RADCommunication(dev, chipToFlash), doAuthChecks(auth), slot(slot) {}
bool RADMultiChipCommunication::authenticate() {
if(!doAuthChecks) {
Log::Trace(__func__, "auth checks disabled, skipping");
return true;
}
Log::Debug(__func__, "starting authentication");
if(!standardComm->authenticate()) {
Log::Warning(__func__, "standardComm auth failed, retrying with sanity check");
device.com->pauseReads();
if(!sanityCheck()) {
if(!sanityCheck()) {
device.com->resumeReads();
Log::Error(__func__, "sanity check failed after retries");
return false;
}
}
if(!RADCommunication::authenticate()) {
device.com->resumeReads();
Log::Error(__func__, "RADCommunication auth failed");
return false;
}
device.com->resumeReads();
}
Log::Debug(__func__, "authentication successful");
return true;
}
bool RADMultiChipCommunication::flash(const FWRepo& fw, const icsneo::Device::OpenStatusHandler& handler) {
Log::Info(__func__, "starting flash for " + targetChip.getName());
auto chipSrcVer = fw.getChipVersion(targetChip.getIEFName());
if(!chipSrcVer) {
Log::Error(__func__, "no chip version found for " + targetChip.getIEFName());
return false;
}
if(!authenticate()) {
Log::Error(__func__, "authentication failed for " + targetChip.getName());
return false;
}
if(!standardComm->validateFirmware(*chipSrcVer)) {
Log::Error(__func__, "firmware validation failed for " + targetChip.getName());
handler(icsneo::Device::OpenStatusType::Failed, "Failed to validate firmware", std::nullopt);
return false;
}
if(targetChip.getFirmwareType() == FirmwareType::Zip) {
std::vector<uint8_t> chipSoftware;
if(!loadZips(fw, chipSoftware)) {
Log::Error(__func__, "loadZips failed for " + targetChip.getName());
return false;
}
if(!standardComm->sendSoftware(chipSoftware, handler)) {
return false;
}
} else {
std::vector<uint8_t> 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<uint8_t>(device.getProductID()) && !isSFPIEF(ief)) {
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<uint8_t>(targetChip.getID())) {
Log::Error(__func__, "IEF chip ID mismatch for " + targetChip.getName());
return false;
}
if(!standardComm->sendSoftware(ief.getSectors(), ief.getBlockSize(), handler)) {
return false;
}
}
Log::Debug(__func__, "calling finalize");
if(!standardComm->finalize()) {
Log::Error(__func__, "finalize failed");
return false;
}
Log::Debug(__func__, "finalize successful, proceeding to reboot");
handler(icsneo::Device::OpenStatusType::Progress, "Rebooting device...", std::nullopt);
return true;
}
bool RADMultiChipCommunication::enterApplication() {
Log::Info(__func__, "starting");
if(!standardComm->enterApplication()) {
Log::Error(__func__, "reboot failed");
return false;
}
Log::Debug(__func__, "reboot sent, waiting 5000ms");
std::this_thread::sleep_for(std::chrono::milliseconds(5000));
return true;
}
bool RADMultiChipCommunication::open() {
const auto& components = device.getComponentVersions();
auto component = std::find_if(components.begin(), components.end(), [this](const auto& candidate) {
if(slot != UINT8_MAX && slot != candidate.expansionSlot) {
return false;
}
return targetChip.getID() == (ChipID)candidate.identifier;
});
if(component == components.end()) {
Log::Error(__func__, "no component found for " + targetChip.getName());
return false;
}
standardComm = std::make_unique<Application>(device, targetChip.info);
standardComm->setExpansionSlot(component->expansionSlot);
// Pause the read thread so it doesn't consume any responses
device.com->pauseReads();
const bool res = initialize();
device.com->resumeReads();
return res;
}
bool RADMultiChipCommunication::close() {
return true;
}
@@ -0,0 +1,334 @@
#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 <cstddef>
#include <thread>
#include <icsbl/icsbl.h>
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<uint8_t>&& args) {
return sendRequestNoResponse(req, targetChip.getID(), std::move(args));
}
bool RADPeriphCommunication::sendRequestNoResponse(Request req, ChipID id, std::vector<uint8_t>&& 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<uint8_t>&& args, std::chrono::milliseconds timeout) {
return sendRequest(req, targetChip.getID(), std::move(args), timeout);
}
RADPeriphCommunication::Response RADPeriphCommunication::sendRequest(Request req, ChipID id, std::vector<uint8_t>&& 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<uint8_t>(req)));
return {};
}
auto result = device.com->driver->waitForRx(expectedResponseLength, timeout);
if(!result) {
Log::Error(__func__, "No response to request " + std::to_string(static_cast<uint8_t>(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<uint8_t>(req)));
return resp;
}
if(checkBlAccepted && resp.data[ReplyAcceptedOffset] != ReplyAccepted) {
Log::Error(__func__, "The bootloader rejected request " + std::to_string(static_cast<uint8_t>(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<uint8_t>(~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<uint8_t> 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<uint8_t> 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<uint8_t>(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::Device::OpenStatusHandler& handler) {
// Validate the firmware before touching the device so a bad file can not leave it erased
std::vector<uint8_t> 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<uint8_t>(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<uint8_t>(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<uint32_t>(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(
icsneo::Device::OpenStatusType::Progress,
"Flashing " + targetChip.getName() + "...",
std::nullopt
);
uint32_t sizeFlashed = 0;
for(const auto& sector : sectors) {
if(!flashSector(sector)) {
return false;
}
sizeFlashed += static_cast<uint32_t>(sector.second.size());
handler(
icsneo::Device::OpenStatusType::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;
}
@@ -0,0 +1,244 @@
#include "icsneo/device/extensions/bootloader/communication/red.h"
#include "icsneo/device/extensions/bootloader/parser/ief.h"
#include "icsneo/device/extensions/bootloader/parser/fwrepo.h"
#include "icsneo/device/extensions/bootloader/platform/logging.h"
#include <cstring>
#include <optional>
#include <icsbl/icsbl.h>
namespace icsneo {
namespace Bootloader {
REDCommunication::REDCommunication(icsneo::Device& dev, const ChipInfo& chip, bool auth, bool encryptWithDeviceKey)
: REDCommunicationBase(dev, chip), doAuthChecks(auth), encryptFirmwareWithDeviceKey(encryptWithDeviceKey) {}
bool REDCommunication::open() {
device.com->pauseReads();
if(doAuthChecks) {
if(!authenticate()) {
return false;
}
}
return getVersion();
}
bool REDCommunication::authenticate(bool firstTime) {
icsbl_seed_v1 seed = {};
if(!icsbl_generate_seed_v1(device.getSerialNumber(), &seed)) {
Log::Error(__func__, "libicsbl could not generate an authentication challenge");
return false;
}
// The challenge is sent as: uint8_t 0x09, uint16_t 0 (a length on top of the protocol length, always zero here),
// one padding byte, then seed.a
std::vector<uint8_t> challenge(3 + sizeof(seed.a));
challenge[0] = 0x09;
std::memcpy(&challenge[3], &seed.a, sizeof(seed.a));
// Ask the bootloader to decode the seed
Log::Debug(__func__, "Authenticating");
if(!sendCommand(uint8_t(Command::Authenticate), challenge, std::chrono::seconds(1), false)) {
if(firstTime) {
// Attempt to flush out any buffers
Log::Debug(__func__, "Authentication failed, flushing buffers");
std::vector<uint8_t> flush(4096, PaddingByte);
device.com->sendPacket(flush);
// Allow any failure messages to come back
const bool any = waitForRx(std::chrono::milliseconds(100));
Log::Trace(__func__, any ? "A message came back" : "No communication came back");
lastData.clear();
// Now try again
return authenticate(false); // Either way, we don't need to continue authenticating
} else {
Log::Error(__func__, "The bootloader rejected the authentication challenge twice");
enterApplication();
return false;
}
}
// The reply is one byte followed by seed.b
if(lastData.back().data.size() < 1 + sizeof(seed.b)) {
Log::Error(__func__, "The authentication reply is too short");
enterApplication();
return false;
}
if(std::memcmp(&lastData.back().data[1], &seed.b, sizeof(seed.b)) != 0) {
Log::Error(__func__, "The authentication reply does not match, the device did not authenticate");
enterApplication();
return false;
}
lastData.clear();
return true;
}
bool REDCommunication::getVersion() {
if(!sendCommand(uint8_t(Command::GetVersion), std::chrono::seconds(1), false)) {
Log::Error(__func__, "The bootloader did not answer the version request");
enterApplication();
return false;
}
if(lastData.front().data.size() < VersionResponse::EncodedSize) {
Log::Error(__func__, "The bootloader version reply is too short");
enterApplication();
return false;
}
currentVersion = VersionResponse(lastData.front());
lastData.clear();
if(doAuthChecks) {
// Check that the serial number the bootloader reports is correct
uint8_t correctSerial[sizeof(VersionResponse::serial_num)] = {};
uint32_t devSerial = device.getSerialNumber();
std::memcpy(correctSerial, &devSerial, sizeof(devSerial));
if(std::memcmp(correctSerial, currentVersion.serial_num, sizeof(correctSerial))) {
Log::Error(__func__, "The serial number reported by the bootloader does not match the device");
enterApplication();
return false;
}
}
blockSize = currentVersion.block_size;
device.setBootloaderVersion({currentVersion.bl_major, currentVersion.bl_minor});
return true;
}
bool REDCommunication::flash(const FWRepo& fw, const icsneo::Device::OpenStatusHandler& handler) {
const auto& srcVer = fw.getVersion();
std::vector<uint8_t> 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<uint8_t>(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<uint8_t>(targetChip.getID())) {
Log::Error(__func__, "IEF chip ID mismatch for " + targetChip.getName());
return false;
}
Log::Debug(__func__, "IEF version " + std::to_string(ief.getVersion().appMajor) + "." + std::to_string(ief.getVersion().appMinor)
+ ", " + std::to_string(ief.getSectors().size()) + " sectors");
if(doAuthChecks && device.supportsSwVersionValidate()) {
// Tell the bootloader what version of Vehicle Spy 3 this firmware is from.
// We check to make sure it approves, but also, some devices will not accept
// new firmware if we have not sent this.
// Sometimes new hardware revisions can not accept firmware which is too old.
if(!sendCommand(uint8_t(Command::ValidateSWVersion), {srcVer.major, srcVer.minor, srcVer.release, srcVer.build})) {
Log::Error(__func__, "SW version validation failed for " + targetChip.getName());
return false;
}
}
handler(icsneo::Device::OpenStatusType::Progress,
"Preparing to update " + targetChip.getName() + "...", std::nullopt);
Log::Debug(__func__, "Erasing " + targetChip.getName());
if(!sendCommand(uint8_t(Command::Erase), std::chrono::seconds(45))) {
Log::Error(__func__, "Erase failed for " + targetChip.getName());
return false;
}
const auto& sectorPairs = ief.getSectors();
size_t sectorsFlashed = 0;
for(const auto& [id, sector] : sectorPairs) {
Log::Trace(__func__, "Flashing sector " + std::to_string(id));
handler(icsneo::Device::OpenStatusType::Progress,
"Updating " + targetChip.getName() + "...",
double(sectorsFlashed++) / sectorPairs.size());
if(sector.size() > blockSize) {
Log::Error(__func__, "Sector " + std::to_string(id) + " too large");
return false;
}
// Sectors smaller than the block size are padded out with zeros
std::vector<uint8_t> padded(sector.begin(), sector.end());
padded.resize(blockSize, 0);
std::vector<uint8_t> encrypted(padded.size());
if(encryptFirmwareWithDeviceKey)
icsbl_encrypt_v1(device.getSerialNumber(), padded.data(), padded.size(), encrypted.data());
else
encrypted = std::move(padded);
encrypted.insert(encrypted.begin(), {
0x00, 0x00, 0x00, // Padding bytes
// Address
uint8_t((id >> 0x00) & 0xFF),
uint8_t((id >> 0x08) & 0xFF),
uint8_t((id >> 0x10) & 0xFF),
uint8_t((id >> 0x18) & 0xFF),
});
// Unencrypted firmware is used for FIRE2 Zynq (and VNET) which also uses the top bit of the ID
// to denote whether the sector is flashed to chip A or chip B. That is indicated
// with a 1 (for chip B) or a 0 as the first byte of the flash packet payload.
if(!encryptFirmwareWithDeviceKey)
encrypted.insert(encrypted.begin(), uint8_t(id & (1u << 31) ? 1 : 0));
if(!sendCommand(uint8_t(Command::Flash), encrypted)) {
Log::Error(__func__, "Flash failed at sector " + std::to_string(id) + " for " + targetChip.getName());
return false;
}
}
// Tell the application that we flashed 100%
handler(icsneo::Device::OpenStatusType::Progress,
"Updating " + targetChip.getName() + "...",
1.0);
handler(icsneo::Device::OpenStatusType::Progress, "Finalizing update...", std::nullopt);
if(!sendCommand(uint8_t(Command::ValidateFirmware))) {
Log::Error(__func__, "Firmware validation failed for " + targetChip.getName());
return false;
}
return true;
}
REDCommunication::VersionResponse::VersionResponse(const REDPacketizer::RxFrame& r) {
auto i = r.data.begin();
bl_major = *i++;
bl_minor = *i++;
app_major = *i++;
app_minor = *i++;
hardware_rev_major = *i++;
hardware_rev_minor = *i++;
flags = *i++;
block_size = *i++;
block_size |= uint16_t(*i++) << 8;
man_day = *i++;
man_month = *i++;
man_year = *i++;
man_year |= uint16_t(*i++) << 8;
for(size_t x = 0; x != sizeof(serial_num); ++x)
serial_num[x] = *i++;
prod_id = *i++;
last_error_code = *i++;
pad_byte = *i++;
}
} // namespace Bootloader
} // namespace icsneo
@@ -0,0 +1,86 @@
#include "icsneo/device/extensions/bootloader/communication/redbase.h"
#include <limits>
namespace icsneo {
namespace Bootloader {
REDCommunicationBase::REDCommunicationBase(icsneo::Device& dev, const ChipInfo& chip)
: Communication(dev), targetChip(chip) {}
bool REDCommunicationBase::open() {
device.com->pauseReads();
return true;
}
bool REDCommunicationBase::close() {
device.com->resumeReads();
return true;
}
bool REDCommunicationBase::handleData() {
auto& rb = device.com->driver->getReadBuffer();
if(rp.input(rb)) {
lastData = rp.output();
return true;
}
return false;
}
bool REDCommunicationBase::waitForRx(std::chrono::milliseconds timeout) {
return device.com->driver->waitForRx([this]() { return handleData(); }, timeout);
}
bool REDCommunicationBase::sendCommand(uint8_t cmd, std::vector<uint8_t> args, std::chrono::seconds timeout, bool clearLastResponse) {
lastData.clear();
device.com->driver->getReadBuffer().clear();
if(!sendCommandRaw(cmd, std::move(args)))
return false;
if(!waitForRx(std::chrono::duration_cast<std::chrono::milliseconds>(timeout)))
return false;
const bool ret = lastData.front().resp == REDPacketizer::Response::Accepted;
if(clearLastResponse) {
lastData.clear();
}
return ret;
}
bool REDCommunicationBase::sendCommandRaw(uint8_t cmd, std::vector<uint8_t> args) {
// For some reason, some devices need it padded to even total bytes, and some to odd total bytes
const bool padEven = device.getType() == DeviceType::FIRE2;
args.insert(args.begin(), uint8_t(cmd)); // Prepend the command byte to the arguments
if(args.size() > std::numeric_limits<uint16_t>::max())
return false;
uint16_t size = uint16_t(args.size());
// The header counts, and the header is 5 bytes, so we're checking for even inner size here
const bool padding = size % 2 != (padEven ? 0 : 1);
if(padding) // We will add padding, so make space for it
size++;
// Prepend the magic, 16-bit little-endian chip ID, and 16-bit little-endian size
const uint16_t chipID = uint16_t(targetChip.getID());
args.insert(args.begin(), {
REDPacketizer::Magic,
uint8_t(chipID & 0xFF), uint8_t(chipID >> 8),
uint8_t(size & 0xFF), uint8_t(size >> 8)
});
if(padding)
args.push_back(PaddingByte); // Ignored by the firmware
// Write it to the device
return device.com->sendPacket(args);
}
} // namespace Bootloader
} // namespace icsneo
@@ -0,0 +1,111 @@
#include "icsneo/device/extensions/bootloader/communication/redcore.h"
#include "icsneo/device/extensions/bootloader/parser/fwrepo.h"
#include "icsneo/device/extensions/bootloader/platform/logging.h"
#include <algorithm>
#include <limits>
namespace icsneo {
namespace Bootloader {
bool REDCoreCommunication::flash(const FWRepo& fw, const icsneo::Device::OpenStatusHandler& handler) {
constexpr const size_t MaxSectorSize = 0x600;
std::vector<uint8_t> bin;
if(!fw.getFW(targetChip.getIEFName(), bin)) {
return false;
}
if(bin.size() > std::numeric_limits<uint32_t>::max()) {
Log::Error(__func__, "Firmware too large for " + targetChip.getName());
return false;
}
const std::string message = "Updating " + targetChip.getName() + "...";
// First send the firmware
const uint32_t fileSize = uint32_t(bin.size());
size_t sectorsFlashed = 0;
const size_t sectorCount = (fileSize / MaxSectorSize) + (fileSize % MaxSectorSize ? 1 : 0);
std::vector<uint8_t> sector;
for(size_t i = 0; i < fileSize; i += MaxSectorSize) {
Log::Trace(__func__, "Sending sector " + std::to_string(sectorsFlashed));
handler(icsneo::Device::OpenStatusType::Progress, message, double(sectorsFlashed++) / sectorCount);
sector.assign(bin.begin() + i, bin.begin() + i + std::min<size_t>(MaxSectorSize, fileSize - i));
if(!sendCommand(uint8_t(Command::SendFirmware), sector)) {
Log::Error(__func__, "Failed to send sector " + std::to_string(sectorsFlashed - 1) + " for " + targetChip.getName());
return false;
}
}
// Then flash it
Log::Debug(__func__, "Starting core flash for " + targetChip.getName());
handler(icsneo::Device::OpenStatusType::Progress, message, std::nullopt);
// There is a long timeout on the StartFirmwareFlash because the bootloader may block here,
// potentially for the entire duration of the flash
if(!sendCommand(uint8_t(Command::StartFirmwareFlash), {
uint8_t(fileSize & 0xFF), uint8_t((fileSize >> 8) & 0xFF),
uint8_t((fileSize >> 16) & 0xFF), uint8_t((fileSize >> 24) & 0xFF),
}, std::chrono::seconds(60))) {
Log::Error(__func__, "Failed to start core flash for " + targetChip.getName());
return false;
}
// Watch the responses to know when it's done
const auto endTime = std::chrono::steady_clock::now() + std::chrono::seconds(60);
uint32_t flashed = 0;
while(flashed < fileSize && std::chrono::steady_clock::now() < endTime) {
if(!sendCommandRaw(uint8_t(Command::GetFlashProgress))) {
Log::Error(__func__, "Failed to request flash progress for " + targetChip.getName());
return false;
}
if(!waitForRx(std::chrono::milliseconds(3000))) {
Log::Trace(__func__, "No flash progress packet within 3 seconds");
continue;
}
const uint32_t oldFlashed = flashed;
for(const auto& packet : lastData) {
if(packet.resp != REDPacketizer::Response::Accepted)
return false;
if(packet.data.size() != 4) {
Log::Trace(__func__, "Got a flash progress packet of length " + std::to_string(packet.data.size()));
continue;
}
const uint32_t newFlashed = readLE32(packet.data.data());
if(newFlashed >= flashed)
flashed = newFlashed;
else
Log::Trace(__func__, "Got a flash progress packet which went backwards " + std::to_string(newFlashed) + " " + std::to_string(flashed));
// After sending us the packet that confirms the flash, the flash position is cleared and will send us zeros.
// We don't need any of these other packets, in that case.
if(flashed >= fileSize)
break;
}
lastData.clear();
Log::Trace(__func__, "Flashed " + std::to_string(flashed) + " of " + std::to_string(fileSize));
if(flashed != oldFlashed)
handler(icsneo::Device::OpenStatusType::Progress, message, std::min(double(flashed) / fileSize, 1.0));
}
return flashed == fileSize;
}
bool REDCoreCommunication::sendCommandRaw(uint8_t cmd, std::vector<uint8_t> args) {
if(args.size() > std::numeric_limits<uint16_t>::max())
return false;
const uint16_t size = static_cast<uint16_t>(args.size());
args.insert(args.begin(), { CoreFlasherMagic, uint8_t(size & 0xFF), uint8_t((size >> 8) & 0xFF), cmd });
return REDCommunicationBase::sendCommandRaw(CoreFlasherCommand, std::move(args));
}
} // namespace Bootloader
} // namespace icsneo
@@ -0,0 +1,85 @@
#include "icsneo/device/extensions/bootloader/communication/redpacketizer.h"
#include "icsneo/device/extensions/bootloader/platform/logging.h"
#include <algorithm>
namespace icsneo {
namespace Bootloader {
bool REDPacketizer::input(icsneo::RingBuffer& bytes) {
while(bytes.size() > 0) {
switch(state) {
case HEADER:
if(bytes[0] == Magic) {
working.emplace_back();
state = CMD;
} else {
Log::Trace(__func__, "Got bootloader unknown byte: " + std::to_string(bytes[0]));
}
bytes.pop_front();
break;
case CMD:
working.back().resp = Response(bytes[0]);
if(working.back().resp != Response::Accepted && working.back().resp != Response::Rejected) {
Log::Trace(__func__, "Got bootloader unknown response: " + std::to_string(bytes[0]));
working.pop_back();
state = HEADER;
} else {
state = LENGTH_LOWER;
}
bytes.pop_front();
break;
case LENGTH_LOWER:
working.back().len = bytes[0];
state = LENGTH_UPPER;
bytes.pop_front();
break;
case LENGTH_UPPER:
working.back().len |= uint16_t(bytes[0]) << 8;
if(working.back().len == 0) {
working.back().done = true;
state = HEADER;
} else {
state = DATA;
}
bytes.pop_front();
break;
case DATA: {
// The RingBuffer is not contiguous, so copy out with read() rather than taking a pointer
auto& frame = working.back();
const size_t dataToGo = frame.len - frame.data.size();
const size_t toCopy = std::min(bytes.size(), dataToGo);
const size_t oldSize = frame.data.size();
frame.data.resize(oldSize + toCopy);
bytes.read(frame.data.data() + oldSize, 0, toCopy);
bytes.pop(toCopy);
if(toCopy == dataToGo) {
frame.done = true;
state = HEADER;
}
break;
}
}
}
return std::any_of(working.begin(), working.end(), [](const RxFrame& rx) { return rx.done; });
}
std::vector<REDPacketizer::RxFrame> REDPacketizer::output() {
// Hand back the completed frames, anything still in progress stays in working
std::vector<RxFrame> done;
std::vector<RxFrame> pending;
for(auto& rx : working) {
if(rx.done)
done.push_back(std::move(rx));
else
pending.push_back(std::move(rx));
}
working = std::move(pending);
return done;
}
} // namespace Bootloader
} // namespace icsneo