sdrangel/plugins/samplesource/osmosdr/osmosdrupgrade.cpp

519 lines
12 KiB
C++

#include <QFileDialog>
#include <QMessageBox>
#include <QCursor>
#include <QThread>
#include <libusb.h>
#include "osmosdrupgrade.h"
#include "ui_osmosdrupgrade.h"
#define OSMOSDR_USB_VID 0x16c0
#define OSMOSDR_USB_PID 0x0763
// DFU commands
#define DFU_DETACH 0x00
#define DFU_DNLOAD 0x01
#define DFU_UPLOAD 0x02
#define DFU_GETSTATUS 0x03
#define DFU_CLRSTATUS 0x04
#define DFU_GETSTATE 0x05
#define DFU_ABORT 0x06
// DFU states
#define ST_appIDLE 0
#define ST_appDETACH 1
#define ST_dfuIDLE 2
#define ST_dfuDNLOAD_SYNC 3
#define ST_dfuDNBUSY 4
#define ST_dfuDNLOAD_IDLE 5
#define ST_dfuMANIFEST_SYNC 6
#define ST_dfuMANIFEST 7
#define ST_dfuMANIFEST_WAIT_RST 8
#define ST_dfuUPLOAD_IDLE 9
#define ST_dfuERROR 10
#define DFU_PACKETSIZE 512
#pragma pack(push, 1)
struct dfu_getstatus {
quint8 bStatus;
quint8 bwPollTimeout[3];
quint8 bState;
quint8 iString;
};
#pragma pack(pop)
OsmoSDRUpgrade::OsmoSDRUpgrade(QWidget* parent) :
QDialog(parent),
ui(new Ui::OsmoSDRUpgrade),
m_usb(NULL)
{
ui->setupUi(this);
connect(&m_searchDeviceTimer, SIGNAL(timeout()), this, SLOT(searchDeviceTick()));
}
OsmoSDRUpgrade::~OsmoSDRUpgrade()
{
delete ui;
}
void OsmoSDRUpgrade::on_browse_clicked()
{
QString filename = QFileDialog::getOpenFileName(this, tr("OsmoSDR Firmware File"), QString(), tr("OsmoSDR Firmware (*.ofw);;All Files (*)"));
if(filename.isEmpty())
return;
QFile file(filename);
if(file.size() > 10 * 1024 * 1024) {
QMessageBox::critical(this, tr("File Open Error"), tr("File is too big to be a firmware update for OsmoSDR."));
return;
}
QByteArray dfuApp;
QByteArray radioApp;
QByteArray fpgaBin;
mz_zip_archive zip;
memset(&zip, 0x00, sizeof(zip));
if(!mz_zip_reader_init_file(&zip, qPrintable(filename), 0)) {
mz_zip_reader_end(&zip);
QMessageBox::critical(this, tr("File Open Error"), tr("File is not a valid OsmoSDR update."));
return;
}
int dfuAppIndex = mz_zip_reader_locate_file(&zip, "dfuapp.bin", NULL, 0);
int radioAppIndex = mz_zip_reader_locate_file(&zip, "radioapp.bin", NULL, 0);
int fpgaIndex = mz_zip_reader_locate_file(&zip, "fpga.bin", NULL, 0);
if((dfuAppIndex < 0) && (radioAppIndex < 0) && (fpgaIndex < 0)) {
mz_zip_reader_end(&zip);
QMessageBox::critical(this, tr("File Open Error"), tr("File is not a valid OsmoSDR update."));
return;
}
if(!mz_zip_reader_extract_to_callback(&zip, dfuAppIndex, zipHelper, &dfuApp, 0)) {
mz_zip_reader_end(&zip);
QMessageBox::critical(this, tr("File Open Error"), tr("File is not a valid OsmoSDR update."));
return;
}
if(!mz_zip_reader_extract_to_callback(&zip, radioAppIndex, zipHelper, &radioApp, 0)) {
mz_zip_reader_end(&zip);
QMessageBox::critical(this, tr("File Open Error"), tr("File is not a valid OsmoSDR update."));
return;
}
if(!mz_zip_reader_extract_to_callback(&zip, fpgaIndex, zipHelper, &fpgaBin, 0)) {
mz_zip_reader_end(&zip);
QMessageBox::critical(this, tr("File Open Error"), tr("File is not a valid OsmoSDR update."));
return;
}
mz_zip_reader_end(&zip);
m_dfuApp = dfuApp;
m_radioApp = radioApp;
m_fpgaBin = fpgaBin;
if(dfuAppIndex >= 0) {
ui->dfu->setEnabled(true);
ui->dfuSize->setText(tr("%1").arg(m_dfuApp.size()));
} else {
ui->dfu->setEnabled(false);
ui->dfuSize->setText(tr("-"));
}
if(radioAppIndex >= 0) {
ui->radio->setEnabled(true);
ui->radioSize->setText(tr("%1").arg(m_radioApp.size()));
} else {
ui->radio->setEnabled(false);
ui->dfuSize->setText(tr("-"));
}
if(fpgaIndex >= 0) {
ui->fpga->setEnabled(true);
ui->fpgaSize->setText(tr("%1").arg(m_fpgaBin.size()));
} else {
ui->fpga->setEnabled(false);
ui->fpgaSize->setText(tr("-"));
}
if((dfuAppIndex >= 0) && (radioAppIndex < 0) && (fpgaIndex < 0)) {
ui->dfu->setChecked(true);
ui->radio->setChecked(false);
ui->fpga->setChecked(false);
} else {
ui->dfu->setChecked(false);
ui->radio->setChecked(radioAppIndex >= 0);
ui->fpga->setChecked(fpgaIndex >= 0);
}
ui->fwBox->setEnabled(true);
ui->progressBox->setEnabled(true);
QFileInfo fi(file.fileName());
ui->filename->setText(fi.fileName());
}
void OsmoSDRUpgrade::on_dfu_toggled(bool checked)
{
updateFlashButton();
}
void OsmoSDRUpgrade::on_radio_toggled(bool checked)
{
updateFlashButton();
}
void OsmoSDRUpgrade::on_fpga_toggled(bool checked)
{
updateFlashButton();
}
void OsmoSDRUpgrade::updateFlashButton()
{
if(ui->dfu->isChecked() || ui->radio->isChecked() || ui->fpga->isChecked())
ui->progressBox->setEnabled(true);
else ui->progressBox->setEnabled(false);
}
void OsmoSDRUpgrade::on_start_clicked()
{
ui->close->setEnabled(false);
ui->start->setEnabled(false);
QApplication::setOverrideCursor(Qt::WaitCursor);
ui->log->clear();
log(tr("Starting flash operation..."));
if(libusb_init(&m_usb) < 0) {
fail(tr("Could not initialize libusb."));
return;
}
switchToDFU();
m_searchTries = 0;
m_searchDeviceTimer.start(250);
}
void OsmoSDRUpgrade::searchDeviceTick()
{
m_searchTries++;
log(tr("Searching device (try %1)").arg(m_searchTries));
libusb_device_handle* device = libusb_open_device_with_vid_pid(m_usb, OSMOSDR_USB_VID, OSMOSDR_USB_PID);
if(device == NULL) {
if(m_searchTries >= 10) {
m_searchDeviceTimer.stop();
finish();
}
return;
}
m_searchDeviceTimer.stop();
libusb_device_descriptor deviceDescriptor;
if(libusb_get_descriptor(device, LIBUSB_DT_DEVICE, 0, (unsigned char*)&deviceDescriptor, sizeof(deviceDescriptor)) < 0) {
libusb_close(device);
fail(tr("Could not read device descriptor."));
return;
}
char sn[64];
memset(sn, 0x00, sizeof(sn));
libusb_get_string_descriptor_ascii(device, deviceDescriptor.iSerialNumber, (unsigned char*)sn, sizeof(sn));
sn[sizeof(sn) - 1] = '\0';
log(tr("OsmoSDR found (SN %1)").arg(sn));
quint8 buffer[255];
if(libusb_get_descriptor(device, LIBUSB_DT_CONFIG, 0, buffer, sizeof(buffer)) < 16) {
libusb_close(device);
fail(tr("Could not get a configuration descriptor"));
return;
}
if(buffer[15] != 1) {
libusb_close(device);
fail(tr("Device not in DFU mode as it should be"));
return;
}
quint8 state;
if(dfuGetState(device, &state) < 0) {
libusb_reset_device(device);
if(dfuGetState(device, &state) < 0) {
libusb_close(device);
fail(tr("Cannot get device DFU state"));
return;
}
}
if((state != ST_dfuIDLE) && (state != ST_dfuDNLOAD_IDLE)) {
dfuGetStatus(device);
libusb_close(device);
fail(tr("Device is not in dfuIDLE or dfuDNLOAD_IDLE state"));
return;
}
if(libusb_claim_interface(device, 0) < 0) {
libusb_close(device);
fail(tr("Could not claim interface"));
return;
}
QByteArray* image;
int altSetting;
while(true) {
if(ui->dfu->isChecked()) {
log(tr("Starting download of DFU application"));
image = &m_dfuApp;
altSetting = 1;
} else if(ui->radio->isChecked()) {
log(tr("Starting download of radio application"));
image = &m_radioApp;
altSetting = 0;
} else if(ui->fpga->isChecked()) {
log(tr("Starting download of FPGA image"));
image = &m_fpgaBin;
altSetting = 2;
} else {
log(tr("Switching back to radio application mode"));
dfuDetach(device);
break;
}
if(libusb_set_interface_alt_setting(device, 0, altSetting) < 0) {
libusb_close(device);
fail(tr("Could not set alternate interface setting to %1").arg(altSetting));
return;
}
ui->progress->setMaximum((image->size() / DFU_PACKETSIZE) + ((image->size() % DFU_PACKETSIZE) ? 1 : 0));
int blocknum = 0;
while((state == ST_dfuIDLE) || (state == ST_dfuDNLOAD_IDLE)) {
int offset = blocknum * DFU_PACKETSIZE;
int blocklen = (int)image->size() - offset;
if(blocklen < 0)
blocklen = 0;
if(blocklen > DFU_PACKETSIZE)
blocklen = DFU_PACKETSIZE;
if(dfuDownloadBlock(device, blocknum, (const quint8*)(image->data() + offset), blocklen) < 0) {
if(dfuGetState(device, &state) < 0) {
if(ui->dfu->isChecked())
break;
libusb_close(device);
fail(tr("Cannot get device DFU state"));
return;
}
dfuGetStatus(device);
libusb_close(device);
fail(tr("Error downloading block %1").arg(blocknum));
return;
}
dfuGetStatus(device);
if(blocklen == 0)
break;
blocknum++;
ui->progress->setValue(blocknum);
QApplication::processEvents();
}
log(tr("Download complete (state %1)").arg(state));
do {
if(dfuGetState(device, &state) < 0) {
if(ui->dfu->isChecked()) {
break;
} else {
libusb_close(device);
fail(tr("Cannot get device DFU state"));
return;
}
}
dfuGetStatus(device);
} while(state == ST_dfuMANIFEST);
if(ui->dfu->isChecked()) {
ui->dfu->setChecked(false);
m_searchTries = 0;
m_searchDeviceTimer.start(250);
libusb_close(device);
return;
} else if(ui->radio->isChecked()) {
ui->radio->setChecked(false);
} else if(ui->fpga->isChecked()) {
ui->fpga->setChecked(false);
}
QApplication::processEvents();
}
log(tr("Upgrade finished successfully"));
libusb_close(device);
finish();
}
void OsmoSDRUpgrade::switchToDFU()
{
libusb_device_handle* device = libusb_open_device_with_vid_pid(m_usb, OSMOSDR_USB_VID, OSMOSDR_USB_PID);
if(device == NULL) {
log(tr("No OsmoSDR VID:PID %1:%2 found").arg(OSMOSDR_USB_VID, 4, 16, QChar('0')).arg(OSMOSDR_USB_PID, 4, 16, QChar('0')));
return;
}
quint8 buffer[255];
if(libusb_get_descriptor(device, LIBUSB_DT_CONFIG, 0, buffer, sizeof(buffer)) < 16) {
log(tr("Could not get a valid configuration descriptor"));
libusb_close(device);
return;
}
if(buffer[15] != 1) {
if(libusb_claim_interface(device, 0) < 0) {
log(tr("Could not claim interface"));
libusb_close(device);
return;
}
log(tr("Switching device to DFU mode"));
libusb_control_transfer(device, LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_ENDPOINT_OUT, 0x07, 0x0003, 0, NULL, 0, 50);
libusb_release_interface(device, 0);
} else {
log(tr("Device is already in DFU mode"));
}
libusb_close(device);
}
int OsmoSDRUpgrade::dfuGetState(libusb_device_handle* device, quint8* state)
{
int res;
res = libusb_control_transfer(
device,
0xa1,
DFU_GETSTATE,
0,
0,
(unsigned char*)state,
1,
1000);
return res;
}
int OsmoSDRUpgrade::dfuGetStatus(libusb_device_handle* device)
{
int res;
struct dfu_getstatus getstatus;
/*
static const char* statedesc[] = {
"appIDLE",
"appDETACH",
"dfuIDLE",
"dfuDNLOAD-SYNC",
"dfuDNBUSY",
"dfuDNLOAD-IDLE",
"dfuMANIFEST-SYNC",
"dfuMANIFEST",
"dfuMANIFEST-WAIT-RESET",
"dfuUPLOAD-IDLE",
"dfuERROR"
};
*/
res = libusb_control_transfer(
device,
0xa1,
DFU_GETSTATUS,
0,
0,
(unsigned char*)&getstatus,
sizeof(getstatus),
1000);
/*
if(res >= 0)
log(tr("OsmoSDR bStatus: %1, bState: %2 (%3)").arg(getstatus.bStatus).arg(statedesc[getstatus.bState]).arg(getstatus.bState));
*/
return res;
}
int OsmoSDRUpgrade::dfuClrStatus(libusb_device_handle* device)
{
return libusb_control_transfer(
device,
0x21,
DFU_CLRSTATUS,
0,
0,
NULL,
0,
1000);
}
int OsmoSDRUpgrade::dfuDownloadBlock(libusb_device_handle* device, quint16 block, const quint8* data, quint16 len)
{
return libusb_control_transfer(
device,
0x21,
DFU_DNLOAD,
block,
0,
(unsigned char*)data,
len,
10000);
}
int OsmoSDRUpgrade::dfuDetach(libusb_device_handle* device)
{
return libusb_control_transfer(
device,
0x21,
DFU_DETACH,
0,
0,
NULL,
0,
1000);
}
void OsmoSDRUpgrade::fail(const QString& msg)
{
log(tr("Fatal error: %1").arg(msg));
QMessageBox::critical(this, tr("OsmoSDR Upgrade Failed"), msg);
finish();
}
void OsmoSDRUpgrade::finish()
{
if(m_usb != NULL) {
libusb_exit(m_usb);
m_usb = NULL;
}
QApplication::restoreOverrideCursor();
ui->start->setEnabled(true);
ui->close->setEnabled(true);
}
void OsmoSDRUpgrade::log(const QString& msg)
{
ui->log->appendPlainText(msg);
ui->log->moveCursor(QTextCursor::End);
}
void OsmoSDRUpgrade::reject()
{
if(ui->close->isEnabled())
return QDialog::reject();
}
size_t OsmoSDRUpgrade::zipHelper(void* pOpaque, mz_uint64 file_ofs, const void* pBuf, size_t n)
{
QByteArray* bytes = (QByteArray*)pOpaque;
bytes->append((const char*)pBuf, n);
return n;
}