Bootloader.cc 22 KB
Newer Older
1 2
/****************************************************************************
 *
3
 * (c) 2009-2020 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org>
4 5 6 7 8 9
 *
 * QGroundControl is licensed according to the terms in the file
 * COPYING.md in the root of the source code directory.
 *
 ****************************************************************************/

10 11
#include "Bootloader.h"
#include "QGCLoggingCategory.h"
12 13 14 15

#include <QFile>
#include <QSerialPortInfo>
#include <QDebug>
16
#include <QElapsedTimer>
17

18 19
#include "QGC.h"

20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161
/// 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)
162
{
163 164 165 166 167 168 169
    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);
170

171 172 173 174 175
        if (!_sync()) {
            return false;
        }
    }
    return true;
176 177
}

178
bool Bootloader::erase(void)
179
{
180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223
    // 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);
224
    if (bytesWritten == -1) {
225
        _errorString = tr("Write failed: %1").arg(_port.errorString());
226 227 228 229 230 231 232 233 234 235 236 237
        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;
}

238
bool Bootloader::_write(const uint8_t byte)
239 240
{
    uint8_t buf[1] = { byte };
241
    return _write(buf, 1);
242 243
}

244
bool Bootloader::_read(uint8_t* data, qint64 cBytesExpected, int readTimeout)
245
{
246 247 248 249 250 251
    QElapsedTimer timeout;

    timeout.start();
    while (_port.bytesAvailable() < cBytesExpected) {
        if (timeout.elapsed() > readTimeout) {
            _errorString = tr("Timeout waiting for bytes to be available");
252 253
            return false;
        }
254
        _port.waitForReadyRead(100);
255
    }
256 257 258 259 260 261 262 263 264

    qint64 bytesRead;
    bytesRead = _port.read((char *)data, cBytesExpected);

    if (bytesRead != cBytesExpected) {
        _errorString = tr("Read failed: error: %1").arg(_port.errorString());
        return false;
    }

265 266 267
    return true;
}

268 269
/// Read a PROTO_SYNC command response from the bootloader
///     @param responseTimeout Msecs to wait for response bytes to become available on port
270
bool Bootloader::_getCommandResponse(int responseTimeout)
271 272 273
{
    uint8_t response[2];
    
274
    if (!_read(response, 2, responseTimeout)) {
275
        _errorString.prepend(tr("Get Command Response: "));
276 277 278 279 280 281 282
        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;
283 284 285
    } 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;
286 287 288 289 290 291 292 293 294 295 296 297 298 299
    } 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;
}

300 301 302
/// 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
303
bool Bootloader::_protoGetDevice(uint8_t param, uint32_t& value)
304 305 306
{
    uint8_t buf[3] = { PROTO_GET_DEVICE, param, PROTO_EOC };
    
307
    if (!_write(buf, sizeof(buf))) {
Don Gagne's avatar
Don Gagne committed
308
        goto Error;
309
    }
310
    if (!_read((uint8_t*)&value, sizeof(value))) {
Don Gagne's avatar
Don Gagne committed
311
        goto Error;
312
    }
313
    if (!_getCommandResponse()) {
Don Gagne's avatar
Don Gagne committed
314 315 316 317 318 319
        goto Error;
    }
    
    return true;
    
Error:
320
    _errorString.prepend(tr("Get Device: "));
Don Gagne's avatar
Don Gagne committed
321
    return false;
322 323
}

324 325 326
/// Send a command to the bootloader
///     @param cmd Command to send using PROTO_* enums
/// @return true: Command sent and valid sync response returned
327
bool Bootloader::_sendCommand(const uint8_t cmd, int responseTimeout)
328 329 330
{
    uint8_t buf[2] = { cmd, PROTO_EOC };
    
331
    if (!_write(buf, 2)) {
Don Gagne's avatar
Don Gagne committed
332
        goto Error;
333
    }
334 335 336
    _port.flush();

    if (!_getCommandResponse(responseTimeout)) {
Don Gagne's avatar
Don Gagne committed
337 338 339 340 341 342
        goto Error;
    }
    
    return true;

Error:
343
    _errorString.prepend(tr("Send Command: "));
Don Gagne's avatar
Don Gagne committed
344
    return false;
345 346
}

347
bool Bootloader::_binProgram(const FirmwareImage* image)
348 349
{
    QFile firmwareFile(image->binFilename());
350
    if (!firmwareFile.open(QIODevice::ReadOnly)) {
351
        _errorString = tr("Unable to open firmware file %1: %2").arg(image->binFilename(), firmwareFile.errorString());
352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371
        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) {
Don Gagne's avatar
Don Gagne committed
372
            _errorString = tr("Firmware file read failed: %1").arg(firmwareFile.errorString());
373 374 375 376 377
            return false;
        }
        
        Q_ASSERT(bytesToSend <= 0x8F);
        
378
        bool failed = true;
379 380 381 382 383 384
        if (_write(PROTO_PROG_MULTI) &&
                _write((uint8_t)bytesToSend) &&
                _write(imageBuf, bytesToSend) &&
                _write(PROTO_EOC)) {
            if (_getCommandResponse()) {
                failed = false;
385 386 387
            }
        }
        if (failed) {
388
            _errorString = tr("Flash failed: %1 at address 0x%2").arg(_errorString).arg(bytesSent, 8, 16, QLatin1Char('0'));
389 390
            return false;
        }
391

392
        bytesSent += bytesToSend;
393

394
        // Calculate the CRC now so we can test it after the board is flashed.
395
        _imageCRC = QGC::crc32((uint8_t *)imageBuf, bytesToSend, _imageCRC);
396

397
        emit updateProgress(bytesSent, imageSize);
398 399
    }
    firmwareFile.close();
400

401 402 403
    // We calculate the CRC using the entire flash size, filling the remainder with 0xFF.
    while (bytesSent < _boardFlashSize) {
        const uint8_t fill = 0xFF;
404
        _imageCRC = QGC::crc32(&fill, 1, _imageCRC);
405 406
        bytesSent++;
    }
407

408 409 410
    return true;
}

411
bool Bootloader::_ihxProgram(const FirmwareImage* image)
412 413 414 415 416 417 418 419 420 421
{
    uint32_t imageSize = image->imageSize();
    uint32_t bytesSent = 0;

    for (uint16_t index=0; index<image->ihxBlockCount(); index++) {
        bool        failed;
        uint16_t    flashAddress;
        QByteArray  bytes;
        
        if (!image->ihxGetBlock(index, flashAddress, bytes)) {
422
            _errorString = tr("Unable to retrieve block from ihx: index %1").arg(index);
423 424 425
            return false;
        }
        
Don Gagne's avatar
Don Gagne committed
426
        qCDebug(FirmwareUpgradeVerboseLog) << QString("Bootloader::_ihxProgram - address:0x%1 size:%2 block:%3").arg(flashAddress, 8, 16, QLatin1Char('0')).arg(bytes.count()).arg(index);
427 428 429 430
        
        // Set flash address
        
        failed = true;
431 432 433 434 435 436
        if (_write(PROTO_LOAD_ADDRESS) &&
                _write(flashAddress & 0xFF) &&
                _write((flashAddress >> 8) & 0xFF) &&
                _write(PROTO_EOC)) {
            _port.flush();
            if (_getCommandResponse()) {
437 438 439 440 441
                failed = false;
            }
        }
        
        if (failed) {
442
            _errorString = tr("Unable to set flash start address: 0x%2").arg(flashAddress, 8, 16, QLatin1Char('0'));
443 444 445 446 447 448 449 450 451 452
            return false;
        }
        
        // Flash
        
        int bytesIndex = 0;
        uint16_t bytesLeftToWrite = bytes.count();
        
        while (bytesLeftToWrite > 0) {
            uint8_t bytesToWrite;
453

454 455 456 457 458
            if (bytesLeftToWrite > PROG_MULTI_MAX) {
                bytesToWrite = PROG_MULTI_MAX;
            } else {
                bytesToWrite = bytesLeftToWrite;
            }
459

460
            failed = true;
461 462 463 464 465 466
            if (_write(PROTO_PROG_MULTI) &&
                    _write(bytesToWrite) &&
                    _write(&((uint8_t *)bytes.data())[bytesIndex], bytesToWrite) &&
                    _write(PROTO_EOC)) {
                _port.flush();
                if (_getCommandResponse()) {
467 468 469 470
                    failed = false;
                }
            }
            if (failed) {
471
                _errorString = tr("Flash failed: %1 at address 0x%2").arg(_errorString).arg(flashAddress, 8, 16, QLatin1Char('0'));
472 473 474 475 476 477 478 479 480 481 482 483 484 485
                return false;
            }
            
            bytesIndex += bytesToWrite;
            bytesLeftToWrite -= bytesToWrite;
            bytesSent += bytesToWrite;
            
            emit updateProgress(bytesSent, imageSize);
        }
    }
    
    return true;
}

486
bool Bootloader::verify(const FirmwareImage* image)
487 488 489
{
    bool ret;
    
490
    if (!image->imageIsBinFormat() || _bootloaderVersion <= 2) {
491
        ret = _verifyBytes(image);
492
    } else {
493
        ret = _verifyCRC();
494 495
    }
    
496
    reboot();
497 498 499 500
    
    return ret;
}

Don Gagne's avatar
Don Gagne committed
501
/// @brief Verify the flash on bootloader reading it back and comparing it against the original image
502
bool Bootloader::_verifyBytes(const FirmwareImage* image)
503
{
504
    if (image->imageIsBinFormat()) {
505
        return _binVerifyBytes(image);
506
    } else {
507
        return _ihxVerifyBytes(image);
508 509 510
    }
}

511
bool Bootloader::_binVerifyBytes(const FirmwareImage* image)
512 513 514 515
{
    Q_ASSERT(image->imageIsBinFormat());
    
    QFile firmwareFile(image->binFilename());
516
    if (!firmwareFile.open(QIODevice::ReadOnly)) {
517
        _errorString = tr("Unable to open firmware file %1: %2").arg(image->binFilename(), firmwareFile.errorString());
518 519 520 521
        return false;
    }
    uint32_t imageSize = (uint32_t)firmwareFile.size();
    
522
    if (!_sendCommand(PROTO_CHIP_VERIFY)) {
523 524 525 526
        return false;
    }
    
    uint8_t fileBuf[READ_MULTI_MAX];
527
    uint8_t readBuf[READ_MULTI_MAX];
528 529 530 531 532 533
    uint32_t bytesVerified = 0;
    
    Q_ASSERT(PROG_MULTI_MAX <= 0x8F);
    
    while (bytesVerified < imageSize) {
        int bytesToRead = imageSize - bytesVerified;
534 535
        if (bytesToRead > (int)sizeof(readBuf)) {
            bytesToRead = (int)sizeof(readBuf);
536 537 538 539 540 541
        }
        
        Q_ASSERT((bytesToRead % 4) == 0);
        
        int bytesRead = firmwareFile.read((char *)fileBuf, bytesToRead);
        if (bytesRead == -1 || bytesRead != bytesToRead) {
Don Gagne's avatar
Don Gagne committed
542
            _errorString = tr("Firmware file read failed: %1").arg(firmwareFile.errorString());
543 544 545 546 547
            return false;
        }
        
        Q_ASSERT(bytesToRead <= 0x8F);
        
548
        bool failed = true;
549 550 551 552 553 554
        if (_write(PROTO_READ_MULTI) &&
                _write((uint8_t)bytesToRead) &&
                _write(PROTO_EOC)) {
            _port.flush();
            if (_read(readBuf, bytesToRead)) {
                if (_getCommandResponse()) {
555
                    failed = false;
556 557 558 559
                }
            }
        }
        if (failed) {
560
            _errorString = tr("Read failed: %1 at address: 0x%2").arg(_errorString).arg(bytesVerified, 8, 16, QLatin1Char('0'));
561 562 563 564
            return false;
        }

        for (int i=0; i<bytesToRead; i++) {
565 566
            if (fileBuf[i] != readBuf[i]) {
                _errorString = tr("Compare failed: expected(0x%1) actual(0x%2) at address: 0x%3").arg(fileBuf[i], 2, 16, QLatin1Char('0')).arg(readBuf[i], 2, 16, QLatin1Char('0')).arg(bytesVerified + i, 8, 16, QLatin1Char('0'));
567 568 569 570 571
                return false;
            }
        }
        
        bytesVerified += bytesToRead;
572 573
        
        emit updateProgress(bytesVerified, imageSize);
574
    }
575
    
576 577 578 579 580
    firmwareFile.close();
    
    return true;
}

581
bool Bootloader::_ihxVerifyBytes(const FirmwareImage* image)
582 583 584 585 586 587 588 589 590 591 592 593
{
    Q_ASSERT(!image->imageIsBinFormat());
    
    uint32_t imageSize = image->imageSize();
    uint32_t bytesVerified = 0;
    
    for (uint16_t index=0; index<image->ihxBlockCount(); index++) {
        bool        failed;
        uint16_t    readAddress;
        QByteArray  imageBytes;
        
        if (!image->ihxGetBlock(index, readAddress, imageBytes)) {
594
            _errorString = tr("Unable to retrieve block from ihx: index %1").arg(index);
595 596 597
            return false;
        }
        
Don Gagne's avatar
Don Gagne committed
598
        qCDebug(FirmwareUpgradeLog) << QString("Bootloader::_ihxVerifyBytes - address:0x%1 size:%2 block:%3").arg(readAddress, 8, 16, QLatin1Char('0')).arg(imageBytes.count()).arg(index);
599 600 601 602
        
        // Set read address
        
        failed = true;
603 604 605 606 607 608
        if (_write(PROTO_LOAD_ADDRESS) &&
                _write(readAddress & 0xFF) &&
                _write((readAddress >> 8) & 0xFF) &&
                _write(PROTO_EOC)) {
            _port.flush();
            if (_getCommandResponse()) {
609 610 611 612 613
                failed = false;
            }
        }
        
        if (failed) {
614
            _errorString = tr("Unable to set read start address: 0x%2").arg(readAddress, 8, 16, QLatin1Char('0'));
615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631
            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;
            }
Don Gagne's avatar
Don Gagne committed
632

633
            failed = true;
634 635 636 637 638 639
            if (_write(PROTO_READ_MULTI) &&
                    _write(bytesToRead) &&
                    _write(PROTO_EOC)) {
                _port.flush();
                if (_read(readBuf, bytesToRead)) {
                    if (_getCommandResponse()) {
640 641 642 643 644 645 646 647 648 649 650 651 652
                        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<bytesToRead; i++) {
                if ((uint8_t)imageBytes[bytesIndex + i] != readBuf[i]) {
653
                    _errorString = tr("Compare failed: expected(0x%1) actual(0x%2) at address: 0x%3").arg(imageBytes[bytesIndex + i], 2, 16, QLatin1Char('0')).arg(readBuf[i], 2, 16, QLatin1Char('0')).arg(readAddress + i, 8, 16, QLatin1Char('0'));
654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669
                    return false;
                }
            }
            
            bytesVerified += bytesToRead;
            bytesIndex += bytesToRead;
            bytesLeftToRead -= bytesToRead;
            
            emit updateProgress(bytesVerified, imageSize);
        }
    }
    
    return true;
}

/// @Brief Verify the flash by comparing CRCs.
670
bool Bootloader::_verifyCRC(void)
671 672
{
    uint8_t buf[2] = { PROTO_GET_CRC, PROTO_EOC };
673

674 675
    quint32 flashCRC;
    
676
    bool failed = true;
677 678 679 680
    if (_write(buf, 2)) {
        _port.flush();
        if (_read((uint8_t*)&flashCRC, sizeof(flashCRC), _verifyTimeout)) {
            if (_getCommandResponse()) {
681 682 683 684 685
                failed = false;
            }
        }
    }
    if (failed) {
686 687 688 689 690 691 692 693 694 695 696
        return false;
    }

    if (_imageCRC != flashCRC) {
        _errorString = tr("CRC mismatch: board(0x%1) file(0x%2)").arg(flashCRC, 4, 16, QLatin1Char('0')).arg(_imageCRC, 4, 16, QLatin1Char('0'));
        return false;
    }
    
    return true;
}

697
bool Bootloader::_syncWorker(void)
698 699
{
    // Send sync command
700 701
    if (_sendCommand(PROTO_GET_SYNC)) {
        _inBootloaderMode = true;
Don Gagne's avatar
Don Gagne committed
702 703 704 705 706
        return true;
    } else {
        _errorString.prepend("Sync: ");
        return false;
    }
707 708
}

709
bool Bootloader::_sync(void)
710
{
711 712 713 714 715 716 717 718 719
    // Sometimes getting sync is flaky, try 3 times
    _port.readAll();
    bool success = false;
    for (int i=0; i<3; i++) {
        success = _syncWorker();

        if (success) {
            return true;
        }
DonLakeFlyer's avatar
DonLakeFlyer committed
720
    }
721
    return success;
722 723
}

724
bool Bootloader::_get3DRRadioBoardId(uint32_t& boardID)
725 726
{
    uint8_t buf[2] = { PROTO_GET_DEVICE, PROTO_EOC };
727 728

    if (!_write(buf, sizeof(buf))) {
729 730
        goto Error;
    }
731 732 733
    _port.flush();

    if (!_read((uint8_t*)buf, 2)) {
734 735
        goto Error;
    }
736
    if (!_getCommandResponse()) {
737 738
        goto Error;
    }
739

740
    boardID = buf[0];
741

742 743
    _bootloaderVersion = 0;
    _boardFlashSize = 0;
744

745
    return true;
746

747
Error:
748
    _errorString.prepend(tr("Get Board Id: "));
749 750
    return false;
}