mirror of
				https://github.com/f4exb/sdrangel.git
				synced 2025-10-30 20:40:20 -04:00 
			
		
		
		
	
		
			
				
	
	
		
			519 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			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;
 | |
| }
 |