/**************************************************************************** * * (c) 2009-2020 QGROUNDCONTROL PROJECT * * QGroundControl is licensed according to the terms in the file * COPYING.md in the root of the source code directory. * ****************************************************************************/ #include "Bootloader.h" #include "QGCLoggingCategory.h" #include #include #include #include #include "QGC.h" /// This class manages interactions with the bootloader Bootloader::Bootloader(bool sikRadio, QObject *parent) : QObject (parent) , _sikRadio (sikRadio) { } bool Bootloader::open(const QString portName) { qCDebug(FirmwareUpgradeLog) << "open:" << portName; _port.setPortName (portName); _port.setBaudRate (QSerialPort::Baud115200); _port.setDataBits (QSerialPort::Data8); _port.setParity (QSerialPort::NoParity); _port.setStopBits (QSerialPort::OneStop); _port.setFlowControl(QSerialPort::NoFlowControl); if (!_port.open(QIODevice::ReadWrite)) { _errorString = tr("Open failed on port %1: %2").arg(portName, _port.errorString()); return false; } if (_sikRadio) { // Radios are slow to start up QGC::SLEEP::msleep(1000); } return true; } QString Bootloader::_getNextLine(int timeoutMsecs) { QString line; QElapsedTimer timeout; bool foundCR = false; timeout.start(); while (timeout.elapsed() < timeoutMsecs) { char oneChar; _port.waitForReadyRead(100); if (_port.read(&oneChar, 1) > 0) { if (oneChar == '\r') { foundCR = true; continue; } else if (oneChar == '\n' && foundCR) { return line; } line += oneChar; } } return QString(); } bool Bootloader::getBoardInfo(uint32_t& bootloaderVersion, uint32_t& boardID, uint32_t& flashSize) { if (_sikRadio) { // Try sync to see if already in bootloader mode _sync(); if (_inBootloaderMode) { qCDebug(FirmwareUpgradeLog) << "Radio in bootloader mode already"; if (!_get3DRRadioBoardId(_boardID)) { goto Error; } } else { qCDebug(FirmwareUpgradeLog) << "Radio in normal mode"; _port.readAll(); _port.setBaudRate(QSerialPort::Baud57600); // Put radio into command mode _write("+++"); if (!_port.waitForReadyRead(2000)) { _errorString = tr("Unable to put radio into command mode +++"); goto Error; } QByteArray bytes = _port.readAll(); if (!bytes.contains("OK")) { _errorString = tr("Radio did not respond to command mode"); goto Error; } // Use ATI2 command to get board id _write("ATI2\r\n"); QString echo = _getNextLine(2000); if (echo.isEmpty() || echo != "ATI2") { _errorString = tr("Radio did not respond to ATI2 command"); goto Error; } QString boardIdStr = _getNextLine(2000); bool ok = false; _boardID = boardIdStr.toInt(&ok); _boardID = 130; if (boardIdStr.isEmpty() || !ok) { _errorString = tr("Radio did not return board id"); goto Error; } } bootloaderVersion = 0; boardID = _boardID; flashSize = 0; return true; } else { if (!_sync()) { goto Error; } if (!_protoGetDevice(INFO_BL_REV, _bootloaderVersion)) { goto Error; } if (_bootloaderVersion < BL_REV_MIN || _bootloaderVersion > BL_REV_MAX) { _errorString = tr("Found unsupported bootloader version: %1").arg(_bootloaderVersion); goto Error; } if (!_protoGetDevice(INFO_BOARD_ID, _boardID)) { goto Error; } if (!_protoGetDevice(INFO_FLASH_SIZE, _boardFlashSize)) { qWarning() << _errorString; goto Error; } // Older V2 boards have large flash space but silicon error which prevents it from being used. Bootloader v5 and above // will correctly account/report for this. Older bootloaders will not. Newer V2 board which support larger flash space are // reported as V3 board id. if (_boardID == boardIDPX4FMUV2 && _bootloaderVersion >= _bootloaderVersionV2CorrectFlash && _boardFlashSize > _flashSizeSmall) { _boardID = boardIDPX4FMUV3; } bootloaderVersion = _bootloaderVersion; boardID = _boardID; flashSize = _boardFlashSize; return true; } Error: qCDebug(FirmwareUpgradeLog) << "getBoardInfo failed:" << _errorString; _errorString.prepend(tr("Get Board Info: ")); return false; } bool Bootloader::initFlashSequence(void) { if (_sikRadio && !_inBootloaderMode) { _write("AT&UPDATE\r\n"); if (!_port.waitForReadyRead(1500)) { _errorString = tr("Unable to reboot radio (ready read)"); return false; } _port.setBaudRate(QSerialPort::Baud115200); } if (!_sync()) { return false; } return true; } bool Bootloader::erase(void) { // Erase is slow, need larger timeout if (!_sendCommand(PROTO_CHIP_ERASE, _eraseTimeout)) { _errorString = tr("Erase failed: %1").arg(_errorString); return false; } return true; } bool Bootloader::program(const FirmwareImage* image) { if (image->imageIsBinFormat()) { return _binProgram(image); } else { return _ihxProgram(image); } } bool Bootloader::reboot(void) { bool success; if (_sikRadio && !_inBootloaderMode) { qCDebug(FirmwareUpgradeLog) << "reboot ATZ"; _port.readAll(); success = _write("ATZ\r\n"); } else { qCDebug(FirmwareUpgradeLog) << "reboot"; success = _write(PROTO_BOOT) && _write(PROTO_EOC); } _port.flush(); if (success) { QGC::SLEEP::msleep(1000); } return success; } bool Bootloader::_write(const char* data) { return _write((uint8_t*)data, qstrlen(data)); } bool Bootloader::_write(const uint8_t* data, qint64 maxSize) { qint64 bytesWritten = _port.write((const char*)data, maxSize); if (bytesWritten == -1) { _errorString = tr("Write failed: %1").arg(_port.errorString()); qWarning() << _errorString; return false; } if (bytesWritten != maxSize) { _errorString = tr("Incorrect number of bytes returned for write: actual(%1) expected(%2)").arg(bytesWritten).arg(maxSize); qWarning() << _errorString; return false; } return true; } bool Bootloader::_write(const uint8_t byte) { uint8_t buf[1] = { byte }; return _write(buf, 1); } bool Bootloader::_read(uint8_t* data, qint64 cBytesExpected, int readTimeout) { QElapsedTimer timeout; timeout.start(); while (_port.bytesAvailable() < cBytesExpected) { if (timeout.elapsed() > readTimeout) { _errorString = tr("Timeout waiting for bytes to be available"); return false; } _port.waitForReadyRead(100); } qint64 bytesRead; bytesRead = _port.read((char *)data, cBytesExpected); if (bytesRead != cBytesExpected) { _errorString = tr("Read failed: error: %1").arg(_port.errorString()); return false; } return true; } /// Read a PROTO_SYNC command response from the bootloader /// @param responseTimeout Msecs to wait for response bytes to become available on port bool Bootloader::_getCommandResponse(int responseTimeout) { uint8_t response[2]; if (!_read(response, 2, responseTimeout)) { _errorString.prepend(tr("Get Command Response: ")); return false; } // Make sure we get a good sync response if (response[0] != PROTO_INSYNC) { _errorString = tr("Invalid sync response: 0x%1 0x%2").arg(response[0], 2, 16, QLatin1Char('0')).arg(response[1], 2, 16, QLatin1Char('0')); return false; } else if (response[0] == PROTO_INSYNC && response[1] == PROTO_BAD_SILICON_REV) { _errorString = tr("This board is using a microcontroller with faulty silicon and an incorrect configuration and should be put out of service."); return false; } else if (response[1] != PROTO_OK) { QString responseCode = tr("Unknown response code"); if (response[1] == PROTO_FAILED) { responseCode = "PROTO_FAILED"; } else if (response[1] == PROTO_INVALID) { responseCode = "PROTO_INVALID"; } _errorString = tr("Command failed: 0x%1 (%2)").arg(response[1], 2, 16, QLatin1Char('0')).arg(responseCode); return false; } return true; } /// Send a PROTO_GET_DEVICE command to retrieve a value from the PX4 bootloader /// @param param Value to retrieve using INFO_BOARD_* enums /// @param value Returned value bool Bootloader::_protoGetDevice(uint8_t param, uint32_t& value) { uint8_t buf[3] = { PROTO_GET_DEVICE, param, PROTO_EOC }; if (!_write(buf, sizeof(buf))) { goto Error; } if (!_read((uint8_t*)&value, sizeof(value))) { goto Error; } if (!_getCommandResponse()) { goto Error; } return true; Error: _errorString.prepend(tr("Get Device: ")); return false; } /// Send a command to the bootloader /// @param cmd Command to send using PROTO_* enums /// @return true: Command sent and valid sync response returned bool Bootloader::_sendCommand(const uint8_t cmd, int responseTimeout) { uint8_t buf[2] = { cmd, PROTO_EOC }; if (!_write(buf, 2)) { goto Error; } if (!_getCommandResponse(responseTimeout)) { goto Error; } return true; Error: _errorString.prepend(tr("Send Command: ")); return false; } bool Bootloader::_binProgram(const FirmwareImage* image) { QFile firmwareFile(image->binFilename()); if (!firmwareFile.open(QIODevice::ReadOnly)) { _errorString = tr("Unable to open firmware file %1: %2").arg(image->binFilename(), firmwareFile.errorString()); return false; } uint32_t imageSize = (uint32_t)firmwareFile.size(); uint8_t imageBuf[PROG_MULTI_MAX]; uint32_t bytesSent = 0; _imageCRC = 0; Q_ASSERT(PROG_MULTI_MAX <= 0x8F); while (bytesSent < imageSize) { int bytesToSend = imageSize - bytesSent; if (bytesToSend > (int)sizeof(imageBuf)) { bytesToSend = (int)sizeof(imageBuf); } Q_ASSERT((bytesToSend % 4) == 0); int bytesRead = firmwareFile.read((char *)imageBuf, bytesToSend); if (bytesRead == -1 || bytesRead != bytesToSend) { _errorString = tr("Firmware file read failed: %1").arg(firmwareFile.errorString()); return false; } Q_ASSERT(bytesToSend <= 0x8F); bool failed = true; if (_write(PROTO_PROG_MULTI) && _write((uint8_t)bytesToSend) && _write(imageBuf, bytesToSend) && _write(PROTO_EOC)) { if (_getCommandResponse()) { failed = false; } } if (failed) { _errorString = tr("Flash failed: %1 at address 0x%2").arg(_errorString).arg(bytesSent, 8, 16, QLatin1Char('0')); return false; } bytesSent += bytesToSend; // Calculate the CRC now so we can test it after the board is flashed. _imageCRC = QGC::crc32((uint8_t *)imageBuf, bytesToSend, _imageCRC); emit updateProgress(bytesSent, imageSize); } firmwareFile.close(); // We calculate the CRC using the entire flash size, filling the remainder with 0xFF. while (bytesSent < _boardFlashSize) { const uint8_t fill = 0xFF; _imageCRC = QGC::crc32(&fill, 1, _imageCRC); bytesSent++; } return true; } bool Bootloader::_ihxProgram(const FirmwareImage* image) { uint32_t imageSize = image->imageSize(); uint32_t bytesSent = 0; for (uint16_t index=0; indexihxBlockCount(); index++) { bool failed; uint16_t flashAddress; QByteArray bytes; if (!image->ihxGetBlock(index, flashAddress, bytes)) { _errorString = tr("Unable to retrieve block from ihx: index %1").arg(index); return false; } qCDebug(FirmwareUpgradeVerboseLog) << QString("Bootloader::_ihxProgram - address:0x%1 size:%2 block:%3").arg(flashAddress, 8, 16, QLatin1Char('0')).arg(bytes.count()).arg(index); // Set flash address failed = true; if (_write(PROTO_LOAD_ADDRESS) && _write(flashAddress & 0xFF) && _write((flashAddress >> 8) & 0xFF) && _write(PROTO_EOC)) { _port.flush(); if (_getCommandResponse()) { failed = false; } } if (failed) { _errorString = tr("Unable to set flash start address: 0x%2").arg(flashAddress, 8, 16, QLatin1Char('0')); return false; } // Flash int bytesIndex = 0; uint16_t bytesLeftToWrite = bytes.count(); while (bytesLeftToWrite > 0) { uint8_t bytesToWrite; if (bytesLeftToWrite > PROG_MULTI_MAX) { bytesToWrite = PROG_MULTI_MAX; } else { bytesToWrite = bytesLeftToWrite; } failed = true; if (_write(PROTO_PROG_MULTI) && _write(bytesToWrite) && _write(&((uint8_t *)bytes.data())[bytesIndex], bytesToWrite) && _write(PROTO_EOC)) { _port.flush(); if (_getCommandResponse()) { failed = false; } } if (failed) { _errorString = tr("Flash failed: %1 at address 0x%2").arg(_errorString).arg(flashAddress, 8, 16, QLatin1Char('0')); return false; } bytesIndex += bytesToWrite; bytesLeftToWrite -= bytesToWrite; bytesSent += bytesToWrite; emit updateProgress(bytesSent, imageSize); } } return true; } bool Bootloader::verify(const FirmwareImage* image) { bool ret; if (!image->imageIsBinFormat() || _bootloaderVersion <= 2) { ret = _verifyBytes(image); } else { ret = _verifyCRC(); } reboot(); return ret; } /// @brief Verify the flash on bootloader reading it back and comparing it against the original image bool Bootloader::_verifyBytes(const FirmwareImage* image) { if (image->imageIsBinFormat()) { return _binVerifyBytes(image); } else { return _ihxVerifyBytes(image); } } bool Bootloader::_binVerifyBytes(const FirmwareImage* image) { Q_ASSERT(image->imageIsBinFormat()); QFile firmwareFile(image->binFilename()); if (!firmwareFile.open(QIODevice::ReadOnly)) { _errorString = tr("Unable to open firmware file %1: %2").arg(image->binFilename(), firmwareFile.errorString()); return false; } uint32_t imageSize = (uint32_t)firmwareFile.size(); if (!_sendCommand(PROTO_CHIP_VERIFY)) { return false; } uint8_t fileBuf[READ_MULTI_MAX]; uint8_t readBuf[READ_MULTI_MAX]; uint32_t bytesVerified = 0; Q_ASSERT(PROG_MULTI_MAX <= 0x8F); while (bytesVerified < imageSize) { int bytesToRead = imageSize - bytesVerified; if (bytesToRead > (int)sizeof(readBuf)) { bytesToRead = (int)sizeof(readBuf); } Q_ASSERT((bytesToRead % 4) == 0); int bytesRead = firmwareFile.read((char *)fileBuf, bytesToRead); if (bytesRead == -1 || bytesRead != bytesToRead) { _errorString = tr("Firmware file read failed: %1").arg(firmwareFile.errorString()); return false; } Q_ASSERT(bytesToRead <= 0x8F); bool failed = true; if (_write(PROTO_READ_MULTI) && _write((uint8_t)bytesToRead) && _write(PROTO_EOC)) { _port.flush(); if (_read(readBuf, bytesToRead)) { if (_getCommandResponse()) { failed = false; } } } if (failed) { _errorString = tr("Read failed: %1 at address: 0x%2").arg(_errorString).arg(bytesVerified, 8, 16, QLatin1Char('0')); return false; } for (int i=0; iimageIsBinFormat()); uint32_t imageSize = image->imageSize(); uint32_t bytesVerified = 0; for (uint16_t index=0; indexihxBlockCount(); index++) { bool failed; uint16_t readAddress; QByteArray imageBytes; if (!image->ihxGetBlock(index, readAddress, imageBytes)) { _errorString = tr("Unable to retrieve block from ihx: index %1").arg(index); return false; } qCDebug(FirmwareUpgradeLog) << QString("Bootloader::_ihxVerifyBytes - address:0x%1 size:%2 block:%3").arg(readAddress, 8, 16, QLatin1Char('0')).arg(imageBytes.count()).arg(index); // Set read address failed = true; if (_write(PROTO_LOAD_ADDRESS) && _write(readAddress & 0xFF) && _write((readAddress >> 8) & 0xFF) && _write(PROTO_EOC)) { _port.flush(); if (_getCommandResponse()) { failed = false; } } if (failed) { _errorString = tr("Unable to set read start address: 0x%2").arg(readAddress, 8, 16, QLatin1Char('0')); return false; } // Read back int bytesIndex = 0; uint16_t bytesLeftToRead = imageBytes.count(); while (bytesLeftToRead > 0) { uint8_t bytesToRead; uint8_t readBuf[READ_MULTI_MAX]; if (bytesLeftToRead > READ_MULTI_MAX) { bytesToRead = READ_MULTI_MAX; } else { bytesToRead = bytesLeftToRead; } failed = true; if (_write(PROTO_READ_MULTI) && _write(bytesToRead) && _write(PROTO_EOC)) { _port.flush(); if (_read(readBuf, bytesToRead)) { if (_getCommandResponse()) { failed = false; } } } if (failed) { _errorString = tr("Read failed: %1 at address: 0x%2").arg(_errorString).arg(readAddress, 8, 16, QLatin1Char('0')); return false; } // Compare for (int i=0; i