commit ac48fd350d6d91fffb53922f52875afcb37f2780 Author: Boris Honman Date: Wed Aug 14 21:26:27 2019 -0400 initial commit with unmodified qt5-based bootloader flash utility source from the MLA diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0bd6c0c --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/build-* \ No newline at end of file diff --git a/qt5_src/.gitignore b/qt5_src/.gitignore new file mode 100644 index 0000000..b990768 --- /dev/null +++ b/qt5_src/.gitignore @@ -0,0 +1,13 @@ +Makefile +Makefile.* +.qmake.stash + +*.pro.user + +*_plugin_import.cpp +object_script.* + +debug +release + +ui_*.h \ No newline at end of file diff --git a/qt5_src/Bootloader/Bootloader.pro b/qt5_src/Bootloader/Bootloader.pro new file mode 100644 index 0000000..4cc8c72 --- /dev/null +++ b/qt5_src/Bootloader/Bootloader.pro @@ -0,0 +1,71 @@ +TARGET = "HIDBootloader" +TEMPLATE = app +QT += sql +QT += widgets +QMAKE_CXXFLAGS_RELEASE = -Os +INCLUDEPATH += ../ +SOURCES += \ + Settings.cpp \ + MainWindow.cpp \ + main.cpp \ + DeviceData.cpp \ + Device.cpp \ + Comm.cpp \ + ImportExportHex.cpp +HEADERS += \ + Settings.h \ + MainWindow.h \ + DeviceData.h \ + Device.h \ + Comm.h \ + ImportExportHex.h + + +FORMS += MainWindow.ui \ + Settings.ui +RESOURCES += resources.qrc +OTHER_FILES += windows.rc + +#------------------------------------------------- +# Add the correct HIDAPI library according to what +# OS is being used +#------------------------------------------------- +win32: LIBS += -L../HIDAPI/windows +macx: LIBS += -L../HIDAPI/mac +unix: !macx: LIBS += -L../HIDAPI/linux +LIBS += -lHIDAPI + +#------------------------------------------------- +# Make sure to add the required libraries or +# frameoworks for the hidapi to work depending on +# what OS is being used +#------------------------------------------------- +macx: LIBS += -framework CoreFoundation -framework IOkit +win32: LIBS += -lSetupAPI +unix: !macx: LIBS += -lusb-1.0 + +#------------------------------------------------- +# Make sure output directory for object file and +# executable is in the correct subdirectory +#------------------------------------------------- +macx { + DESTDIR = mac + OBJECTS_DIR = mac + MOC_DIR = mac + UI_DIR = mac + RCC_DIR = mac +} +unix: !macx { + DESTDIR = linux + OBJECTS_DIR = linux + MOC_DIR = linux + UI_DIR = linux + RCC_DIR = linux +} +win32 { + DESTDIR = windows + OBJECTS_DIR = windows + MOC_DIR = windows + UI_DIR = windows + RCC_DIR = windows +} diff --git a/qt5_src/Bootloader/Comm.cpp b/qt5_src/Bootloader/Comm.cpp new file mode 100644 index 0000000..74c83eb --- /dev/null +++ b/qt5_src/Bootloader/Comm.cpp @@ -0,0 +1,1021 @@ +/************************************************************************ +* Copyright (c) 2010-2011, Microchip Technology Inc. +* +* Microchip licenses this software to you solely for use with Microchip +* products. The software is owned by Microchip and its licensors, and +* is protected under applicable copyright laws. All rights reserved. +* +* SOFTWARE IS PROVIDED "AS IS." MICROCHIP EXPRESSLY DISCLAIMS ANY +* WARRANTY OF ANY KIND, WHETHER EXPRESS OR IMPLIED, INCLUDING BUT +* NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS +* FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT. IN NO EVENT SHALL +* MICROCHIP BE LIABLE FOR ANY INCIDENTAL, SPECIAL, INDIRECT OR +* CONSEQUENTIAL DAMAGES, LOST PROFITS OR LOST DATA, HARM TO YOUR +* EQUIPMENT, COST OF PROCUREMENT OF SUBSTITUTE GOODS, TECHNOLOGY +* OR SERVICES, ANY CLAIMS BY THIRD PARTIES (INCLUDING BUT NOT LIMITED +* TO ANY DEFENSE THEREOF), ANY CLAIMS FOR INDEMNITY OR CONTRIBUTION, +* OR OTHER SIMILAR COSTS. +* +* To the fullest extent allowed by law, Microchip and its licensors +* liability shall not exceed the amount of fees, if any, that you +* have paid directly to Microchip to use this software. +* +* MICROCHIP PROVIDES THIS SOFTWARE CONDITIONALLY UPON YOUR ACCEPTANCE +* OF THESE TERMS. +* +************************************************************************/ + +#include "Comm.h" + +#include +#include +#include +#include + +const int Comm::SyncWaitTime = 40000; + +/** + * + */ +Comm::Comm() +{ + connected = false; + boot_device = NULL; +} + +/** + * + */ +Comm::~Comm() +{ + boot_device = NULL; +} + +/** + * + */ +void Comm::PollUSB(uint16_t deviceVIDtoPoll, uint16_t devicePIDtoPoll) +{ + hid_device_info *dev; + + dev = hid_enumerate(deviceVIDtoPoll, devicePIDtoPoll); + + connected = (dev != NULL); + hid_free_enumeration(dev); +} + +/** + * + */ +bool Comm::isConnected(void) +{ + return connected; +} + +/** + * + */ +Comm::ErrorCode Comm::open(uint16_t deviceVIDtoOpen, uint16_t devicePIDtoOpen) +{ + boot_device = hid_open(deviceVIDtoOpen, devicePIDtoOpen, NULL); + if(boot_device) + { + connected = true; + hid_set_nonblocking(boot_device, true); + qWarning("Device successfully connected to."); + return Success; + } + + qWarning("Unable to open device."); + return NotConnected; +} + +/** + * + */ +void Comm::close(void) +{ + hid_close(boot_device); + boot_device = NULL; + connected = false; +} + +/** + * + */ +void Comm::Reset(void) +{ + unsigned char sendPacket[65]; + QTime elapsed; + ErrorCode status; + + if(connected) { + memset((void*)&sendPacket, 0x00, sizeof(sendPacket)); + sendPacket[1] = RESET_DEVICE; + + qDebug("Sending Reset Command..."); + elapsed.start(); + + status = SendPacket(sendPacket, sizeof(sendPacket)); + + if(status == Comm::Success) + qDebug("Successfully sent reset command (%fs)", (double)elapsed.elapsed() / 1000); + else + qWarning("Sending reset command failed."); + } +} + +/** + * + */ +Comm::ErrorCode Comm::Program(uint32_t address, unsigned char bytesPerPacket, + unsigned char bytesPerAddress, unsigned char bytesPerWord, unsigned char deviceFamily, + uint32_t endAddress, unsigned char *pData) +{ + WritePacket writePacket; + ErrorCode result = Success; + uint32_t i; + bool allPayloadBytesFF; + bool firstAllFFPacketFound = false; + uint32_t bytesToSend; + unsigned char lastCommandSent = PROGRAM_DEVICE; + uint32_t percentCompletion; + uint32_t addressesToProgram; + uint32_t startOfDataPayloadIndex; + + //Error check input parameters before using them + if((pData == NULL) || (bytesPerAddress == 0) || (address > endAddress) || (bytesPerWord == 0)) + { + qWarning("Bad parameters specified when calling Program() function."); + return Fail; + } + + //Error check to make sure the requested maximum data payload size is an exact multiple of the bytesPerAddress. + //If not, shrink the number of bytes we actually send, so that it is always an exact multiple of the + //programmable media bytesPerAddress. This ensures that we don't "half" program any memory address (ex: if each + //flash address is a 16-bit word address, we don't want to only program one byte of the address, we want to program + //both bytes. + while((bytesPerPacket % bytesPerWord) != 0) + { + bytesPerPacket--; + } + + //Setup variable, used for progress bar updating computations. + addressesToProgram = endAddress - address; + if(addressesToProgram == 0) //Check to avoid potential divide by zero later. + addressesToProgram++; + + + //Make sure the device is still connected before we start trying to communicate with it. + if(connected) + { + //Loop through the entire data set/region, but break it into individual packets before sending it + //to the device. + while(address < endAddress) + { + //Update the progress bar so the user knows things are happening. + percentCompletion = 100*((float)1 - (float)((float)(endAddress - address)/(float)addressesToProgram)); + if(percentCompletion > 100) + { + percentCompletion = 100; + } + //Reformat the percent completion so it "fits" in the 33% to 66% region (since erase + //"completes" 0%-32% of the total erase/program/verify cycle, and verify completes 67%-100%). + percentCompletion /= 3; + percentCompletion += 33; + emit SetProgressBar(percentCompletion); + + //Prepare the packet to send to the device. + memset((void*)&writePacket, 0x00, sizeof(writePacket)); //initialize all bytes clear, so unused pad bytes are = 0x00. + writePacket.command = PROGRAM_DEVICE; + writePacket.address = address; + + //Check if we are near the end of the programmable region, and need to send a "short packet" (with less than the maximum + //allowed program data payload bytes). In this case we need to notify the device by using the PROGRAM_COMPLETE command instead + //of the normal PROGRAM_DEVICE command. This lets the bootloader firmware in the device know it should flush any internal + //buffers it may be using, by programming all of the bufferred data to NVM memory. + if(((endAddress - address) * bytesPerAddress) < bytesPerPacket) + { + writePacket.bytesPerPacket = (endAddress - address) * bytesPerAddress; + //Copy the packet data to the actual outgoing buffer and then send it over USB to the device. + memcpy((unsigned char*)&writePacket.data[0] + 58 - writePacket.bytesPerPacket, pData, writePacket.bytesPerPacket); + //Check to make sure we are completely programming all bytes of the destination address. If not, + //increase the data size and set the extra byte(s) to 0xFF (the default/blank value). + while((writePacket.bytesPerPacket % bytesPerWord) != 0) + { + if(writePacket.bytesPerPacket >= bytesPerPacket) + { + break; //should never hit this break, due to while((bytesPerPacket % bytesPerWord) != 0) check at start of function + } + + //Shift all the data payload bytes in the packet to the left one (lower address), + //so we can insert a new 0xFF byte. + for(i = 0; i < (unsigned char)(bytesPerPacket - 1); i++) + { + writePacket.data[i] = writePacket.data[i+1]; + } + writePacket.data[writePacket.bytesPerPacket] = 0xFF; + writePacket.bytesPerPacket++; + + } + bytesToSend = writePacket.bytesPerPacket; + qDebug("Preparing short packet of final program data with payload: 0x%x", (uint32_t)writePacket.bytesPerPacket); + } + else + { + //Else we are planning on sending a full length packet with the full size payload. + writePacket.bytesPerPacket = bytesPerPacket; + bytesToSend = bytesPerPacket; + //Copy the packet data to the actual outgoing buffer and then prepare to send it. + memcpy((unsigned char*)&writePacket.data[0] + 58 - writePacket.bytesPerPacket, pData, writePacket.bytesPerPacket); + } + + + //Check if all bytes of the data payload section of the packet are == 0xFF. If so, we can save programming + //time by skipping the packet by not sending it to the device. The default/erased value is already = 0xFF, so + //the contents of the flash memory will be correct (although if we want to be certain we should make sure + //the 0xFF regions are still getting checked during the verify step, in case the erase procedure failed to set + //all bytes = 0xFF). + allPayloadBytesFF = true; //assume true until we do the actual check below + //Loop for all of the bytes in the data payload portion of the writePacket. The data payload is little endian but is stored + //"right justified" in the packet. Therefore, writePacket.data[0] isn't necessarily the LSB data byte in the packet. + startOfDataPayloadIndex = 58 - writePacket.bytesPerPacket; + for(i = startOfDataPayloadIndex; i < (startOfDataPayloadIndex + writePacket.bytesPerPacket); i++) + { + if(writePacket.data[i] != 0xFF) + { + //Special check for PIC24, where every 4th byte from the .hex file is == 0x00, + //which is the "phantom byte" (the upper byte of each odd address 16-bit word + //is unimplemented, and is probably 0x00 in the .hex file). + if((((i - startOfDataPayloadIndex) % bytesPerWord) == 3) && (deviceFamily == Device::PIC24)) + { + //We can ignore this "phantom byte", since it is unimplemented and effectively a "don't care" byte. + } + else + { + //We found a non 0xFF (or blank value) byte. We need to send and program the + //packet of useful data into the device. + allPayloadBytesFF = false; + break; + } + } + } + + //Check if we need to send a normal packet of data to the device, if the packet was all 0xFF and + //we need to send a PROGRAM_COMPLETE packet, or if it was all 0xFF and we can simply skip it without + //doing anything. + if(allPayloadBytesFF == false) + { + qDebug("Sending program data packet with address: 0x%x", (uint32_t)writePacket.address); + + //We need to send a normal PROGRAM_DEVICE packet worth of data to program. + result = SendPacket((unsigned char*)&writePacket, sizeof(writePacket)); + //Verify the data was successfully received by the USB device. + if(result != Success) + { + qWarning("Error during program sending packet with address: 0x%x", (uint32_t)writePacket.address); + return result; + } + firstAllFFPacketFound = true; //reset flag so it will be true the next time a pure 0xFF packet is found + lastCommandSent = PROGRAM_DEVICE; + + } + else if((allPayloadBytesFF == true) && (firstAllFFPacketFound == true)) + { + //In this case we need to send a PROGRAM_COMPLETE command to let the firmware know it should flush + //its buffer by programming all of it to flash, since we are about to skip to a new address range. + writePacket.command = PROGRAM_COMPLETE; + writePacket.bytesPerPacket = 0; + firstAllFFPacketFound = false; + qDebug("Sending program complete data packet to skip a packet with address: 0x%x", (uint32_t)writePacket.address); + result = SendPacket((unsigned char*)&writePacket, sizeof(writePacket)); + //Verify the data was successfully received by the USB device. + if(result != Success) + { + qWarning("Error during program sending packet with address: 0x%x", (uint32_t)writePacket.address); + return result; + } + lastCommandSent = PROGRAM_COMPLETE; + } + else + { + //If we get to here, this means that (allPayloadBytesFF == true) && (firstAllFFPacketFound == false). + //In this case, the last packet that we processed was all 0xFF, and all bytes of this packet are + //also all 0xFF. In this case, we don't need to send any packet data to the device. All we need + //to do is advance our pointers and keep checking for a new non-0xFF section. + qDebug("Skipping data packet with all 0xFF with address: 0x%x", (uint32_t)writePacket.address); + } + + //Increment pointers now that we successfully programmed (or deliberately skipped) a packet worth of data + address += bytesPerPacket / bytesPerAddress; + pData += bytesToSend; + + //Check if we just now exactly finished programming the memory region (in which case address will be exactly == endAddress) + //region. (ex: we sent a PROGRAM_DEVICE instead of PROGRAM_COMPLETE for the last packet sent). + //In this case, we still need to send the PROGRAM_COMPLETE command to let the firmware know that it is done, + //and will not be receiving any subsequent program packets for this memory region. + if((uint32_t)address >= (uint32_t)endAddress) + { + //Check if we still need to send a PROGRAM_COMPLETE command (we don't need to send one if + //the last command we sent was a PRORAM_COMPLETE already). + if(lastCommandSent == PROGRAM_COMPLETE) + { + break; + } + + memset((void*)&writePacket, 0x00, sizeof(writePacket)); + writePacket.command = PROGRAM_COMPLETE; + writePacket.bytesPerPacket = 0; + qDebug("Sending final program complete command for this region."); + + result = SendPacket((unsigned char*)&writePacket, sizeof(writePacket)); + break; + } + }//while(address < endAddress) + + return result; + + }//if(connected) + else + { + return NotConnected; + } +} + + + + +#if defined(Q_OS_WIN) +//Must be running on Windows. +#include +#include "../HIDAPI/hidapi.h" + +//Note: Below structure definition is from HIDAPI Windows hid.cpp file (and not in the header). +//In order to speed/bandwdith optimize the GetData() method, it is desireable to use asynchronous +//queueing of packet requests with the host, particularly on the HID interrupt OUT endpoint. +//Queueing up multiple OUT requests simultaneously ensures that the host won't waste potential bandwidth, +//due to the maximum 1 packet / 1 ms polling interval for full speed interrupt endpoints, whereby a single +//missed write opportunity will waste half of the theoretical bandwidth (ex: 1 packet / 2 ms). +//In order to do this queueing however, it is necessary to have direct access to the device_handle, as +//the currently implemented hid_write() HIDAPI doesn't implement multi-packet queuing and tracking. +struct hid_device_ +{ + HANDLE device_handle; + BOOL blocking; + size_t input_report_length; + void *last_error_str; + DWORD last_error_num; + BOOL ioPending; +}; + +Comm::ErrorCode Comm::GetData(uint32_t address, unsigned char bytesPerPacket, + unsigned char bytesPerAddress, unsigned char bytesPerWord, + uint32_t endAddress, unsigned char *pData) +{ + ReadPacket readPacket; + WritePacket writePacket1; + WritePacket writePacket2; + ErrorCode result; + uint32_t percentCompletion; + uint32_t addressesToFetch = endAddress - address; + uint32_t readAddress = address; + OVERLAPPED write1; + OVERLAPPED write2; + DWORD bytesWritten1 = 0; + DWORD bytesWritten2 = 0; + DWORD actualBytesWritten1 = 0; + DWORD actualBytesWritten2 = 0; + bool useWrite1StructNext = true; + bool checkWrite1ForCompletionNext = true; + uint32_t writePacketsPending = 0; + + //Check to avoid possible division by zero when computing the percentage completion status. + if(addressesToFetch == 0) + addressesToFetch++; + + //Initialize a couple of overlapped write structures for doing async write operations. + write1.Internal = 0; + write1.InternalHigh = 0; + write1.Offset = 0; + write1.OffsetHigh = 0; + write1.Pointer = NULL; + write1.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); + + write2.Internal = 0; + write2.InternalHigh = 0; + write2.Offset = 0; + write2.OffsetHigh = 0; + write2.Pointer = NULL; + write2.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); + + + if(connected) + { + //First error check the input parameters before using them + if((pData == NULL) || (endAddress < address) || (bytesPerPacket == 0)) + { + qWarning("Error, bad parameters provided to call of GetData()"); + return Fail; + } + + //Set up an initial write to request the first packet of data we want from the device. + + // Set up the buffer packet with the appropriate address and with the get data command + memset((void*)&writePacket1, 0x00, sizeof(writePacket1)); + writePacket1.command = GET_DATA; + writePacket1.address = address; + + //Debug output info. + qWarning("Fetching packet with address: 0x%X", (uint32_t)writePacket1.address); + + // Calculate to see if the entire buffer can be filled with data, or just partially + if(((endAddress - address) * bytesPerAddress) < bytesPerPacket) + { + // If the amount of bytes left over between current address and end address is less than + // the max amount of bytes per packet, then make sure the bytesPerPacket info is updated + writePacket1.bytesPerPacket = (endAddress - address) * bytesPerAddress; + } + else + { + // Otherwise keep it at its maximum + writePacket1.bytesPerPacket = bytesPerPacket; + } + WriteFile(boot_device->device_handle, &writePacket1, sizeof(writePacket1), &bytesWritten1, &write1); + useWrite1StructNext = false; + // Increment address by however many bytes were requested divided by how many bytes per address + address += writePacket1.bytesPerPacket / bytesPerAddress; + checkWrite1ForCompletionNext = true; + writePacketsPending++; + + + + // Continue reading from device until the entire programmable region has been read + while(readAddress < endAddress) + { + //Update the progress bar so the user knows things are happening. + percentCompletion = 100*((float)1 - (float)((float)(endAddress - address)/(float)addressesToFetch)); + if(percentCompletion > 100) + { + percentCompletion = 100; + } + //Reformat the percent completion so it "fits" in the 33% to 66% region (since erase + //"completes" 0%-32% of the total erase/program/verify cycle, and verify completes 67%-100%). + percentCompletion /= 3; + percentCompletion += 67; + emit SetProgressBar(percentCompletion); + + //Queue up the next HID OUT write request, to begin requesting the next packet worth of data. + //Note: At this point, there is already one outstanding write request already pending in the background. + //This queuing of two HID OUT write requests simultaneosly is done so as to maximize bandwidth/avoid + //any wasted time in between packets. + if(useWrite1StructNext == true) + { + //Check if there is at least one additional packet worth of data that needs requesting. + if(address < endAddress) + { + //Prepare an OUT packet to request the data. + write1.Internal = 0; + write1.InternalHigh = 0; + write1.Offset = 0; + write1.OffsetHigh = 0; + write1.Pointer = NULL; + ResetEvent(write1.hEvent); + + // Set up the buffer packet with the appropriate address and with the get data command + memset((void*)&writePacket1, 0x00, sizeof(writePacket1)); + writePacket1.command = GET_DATA; + writePacket1.address = address; + + //Debug output info. + qWarning("Fetching packet with address: 0x%X", (uint32_t)writePacket1.address); + + // Calculate to see if the entire buffer can be filled with data, or just partially + if(((endAddress - address) * bytesPerAddress) < bytesPerPacket) + { + // If the amount of bytes left over between current address and end address is less than + // the max amount of bytes per packet, then make sure the bytesPerPacket info is updated + writePacket1.bytesPerPacket = (endAddress - address) * bytesPerAddress; + } + else + { + // Otherwise keep it at its maximum + writePacket1.bytesPerPacket = bytesPerPacket; + } + WriteFile(boot_device->device_handle, &writePacket1, sizeof(writePacket1), &bytesWritten1, &write1); + useWrite1StructNext = false; + // Increment address by however many bytes were requested divided by how many bytes per address + address += writePacket1.bytesPerPacket / bytesPerAddress; + writePacketsPending++; + } + } + else + { + //Check if there is at least one additional packet worth of data that needs requesting. + if(address < endAddress) + { + //Prepare an OUT packet to request the data. + write2.Internal = 0; + write2.InternalHigh = 0; + write2.Offset = 0; + write2.OffsetHigh = 0; + write2.Pointer = NULL; + ResetEvent(write2.hEvent); + + // Set up the buffer packet with the appropriate address and with the get data command + memset((void*)&writePacket2, 0x00, sizeof(writePacket2)); + writePacket2.command = GET_DATA; + writePacket2.address = address; + + //Debug output info. + qWarning("Fetching packet with address: 0x%X", (uint32_t)writePacket2.address); + + // Calculate to see if the entire buffer can be filled with data, or just partially + if(((endAddress - address) * bytesPerAddress) < bytesPerPacket) + { + // If the amount of bytes left over between current address and end address is less than + // the max amount of bytes per packet, then make sure the bytesPerPacket info is updated + writePacket2.bytesPerPacket = (endAddress - address) * bytesPerAddress; + } + else + { + // Otherwise keep it at its maximum + writePacket2.bytesPerPacket = bytesPerPacket; + } + WriteFile(boot_device->device_handle, &writePacket2, sizeof(writePacket2), &bytesWritten2, &write2); + useWrite1StructNext = true; + // Increment address by however many bytes were requested divided by how many bytes per address + address += writePacket2.bytesPerPacket / bytesPerAddress; + writePacketsPending++; + } + } + + + + //Now read back a HID IN packet from the device, which should contains the data of interest (which was previously + //requested via a HID OUT packet). + memset((void*)&readPacket, 0x00, sizeof(readPacket)); + result = ReceivePacket((unsigned char*)&readPacket, sizeof(readPacket)); + + // If it wasn't successful, then return with error + if(result != Success) + { + qWarning("Error reading packet with address: 0x%x", (uint32_t)readPacket.address); + return result; + } + + //Now block until the older of the two HID OUT write requests completes. This is necessary, to flow control the code, + //to make sure it never has more than two total (max) HID interrupt OUT write requests pending at any given time. + if(checkWrite1ForCompletionNext == true) + { + if(writePacketsPending != 0) + { + GetOverlappedResult(boot_device->device_handle, &write1, &actualBytesWritten1, TRUE); //blocks until finished + checkWrite1ForCompletionNext = false; + writePacketsPending--; //Just finished sending an OUT packet. This effectively dequeues the request since it is finished. + } + } + else + { + if(writePacketsPending != 0) + { + GetOverlappedResult(boot_device->device_handle, &write2, &actualBytesWritten2, TRUE); //blocks until finished + checkWrite1ForCompletionNext = true; + writePacketsPending--; //Just finished sending an OUT packet. This effectively dequeues the request since it is finished. + } + } + + + // Copy contents from packet to data pointer + memcpy(pData, readPacket.data + 58 - readPacket.bytesPerPacket, readPacket.bytesPerPacket); + + // Increment data pointer + pData += readPacket.bytesPerPacket; + + // Increment address by however many bytes were received divided by how many bytes per address + readAddress += readPacket.bytesPerPacket / bytesPerAddress; + } + + // if successfully received entire region, return success + return Success; + + }//if(connected) + + // If not connected, return not connected + return NotConnected; +} + +#else +//Must be running on Mac OS X or Linux + +Comm::ErrorCode Comm::GetData(uint32_t address, unsigned char bytesPerPacket, + unsigned char bytesPerAddress, unsigned char bytesPerWord, + uint32_t endAddress, unsigned char *pData) +{ + ReadPacket readPacket; + WritePacket writePacket; + ErrorCode result; + uint32_t percentCompletion; + uint32_t addressesToFetch = endAddress - address; + + //Check to avoid possible division by zero when computing the percentage completion status. + if(addressesToFetch == 0) + addressesToFetch++; + + + if(connected) + { + //First error check the input parameters before using them + if((pData == NULL) || (endAddress < address) || (bytesPerPacket == 0)) + { + qWarning("Error, bad parameters provided to call of GetData()"); + return Fail; + } + + // Continue reading from device until the entire programmable region has been read + while(address < endAddress) + { + //Update the progress bar so the user knows things are happening. + percentCompletion = 100*((float)1 - (float)((float)(endAddress - address)/(float)addressesToFetch)); + if(percentCompletion > 100) + { + percentCompletion = 100; + } + //Reformat the percent completion so it "fits" in the 33% to 66% region (since erase + //"completes" 0%-32% of the total erase/program/verify cycle, and verify completes 67%-100%). + percentCompletion /= 3; + percentCompletion += 67; + emit SetProgressBar(percentCompletion); + + + // Set up the buffer packet with the appropriate address and with the get data command + memset((void*)&writePacket, 0x00, sizeof(writePacket)); + writePacket.command = GET_DATA; + writePacket.address = address; + + //Debug output info. + qWarning("Fetching packet with address: 0x%x", (uint32_t)writePacket.address); + + // Calculate to see if the entire buffer can be filled with data, or just partially + if(((endAddress - address) * bytesPerAddress) < bytesPerPacket) + // If the amount of bytes left over between current address and end address is less than + // the max amount of bytes per packet, then make sure the bytesPerPacket info is updated + writePacket.bytesPerPacket = (endAddress - address) * bytesPerAddress; + else + // Otherwise keep it at its maximum + writePacket.bytesPerPacket = bytesPerPacket; + + // Send the packet + result = SendPacket((unsigned char*)&writePacket, sizeof(writePacket)); + + // If it wasn't successful, then return with error + if(result != Success) + { + qWarning("Error during verify sending packet with address: 0x%x", (uint32_t)writePacket.address); + return result; + } + + // Otherwise, read back the packet from the device + memset((void*)&readPacket, 0x00, sizeof(readPacket)); + result = ReceivePacket((unsigned char*)&readPacket, sizeof(readPacket)); + + // If it wasn't successful, then return with error + if(result != Success) + { + qWarning("Error reading packet with address: 0x%x", (uint32_t)readPacket.address); + return result; + } + + // Copy contents from packet to data pointer + memcpy(pData, readPacket.data + 58 - readPacket.bytesPerPacket, readPacket.bytesPerPacket); + + // Increment data pointer + pData += readPacket.bytesPerPacket; + + // Increment address by however many bytes were received divided by how many bytes per address + address += readPacket.bytesPerPacket / bytesPerAddress; + } + + // if successfully received entire region, return success + return Success; + + }//if(connected) + + // If not connected, return not connected + return NotConnected; +} +#endif + + +/** + * + */ +Comm::ErrorCode Comm::Erase(void) { + WritePacket sendPacket; + QTime elapsed; + ErrorCode status; + + if(connected) { + memset((void*)&sendPacket, 0x00, sizeof(sendPacket)); + sendPacket.command = ERASE_DEVICE; + + qDebug("Sending Erase Command..."); + + elapsed.start(); + + status = SendPacket((unsigned char*)&sendPacket, sizeof(sendPacket)); + + if(status == Comm::Success) + qDebug("Successfully sent erase command (%fs)", (double)elapsed.elapsed() / 1000); + else + qDebug("Erasing Device Failed"); + + return status; + } + + qDebug("Device not connected"); + return NotConnected; +} + +//Sends command to USB device to lock or unlock the config bit region. If the config bits overlapped an +//erase page with standard program memory, this will also affect the status of the erase page. (for example +//locking the config bits on a PIC18FxxJ or PIC24FJ device, which stores config bits at the end of the last page +//of flash memory, will also prevent reprogramming of the last page of flash memory). +//The bool lock value input parameter is either true (lock/protect the config bits), or false (unlock/allow +//reprogramming of config bits and any overlapping flash erase page, if relevant). +Comm::ErrorCode Comm::LockUnlockConfig(bool lock) +{ + WritePacket sendPacket; + QTime elapsed; + ErrorCode status; + + if(connected) { + memset((void*)&sendPacket, 0x00, sizeof(sendPacket)); + sendPacket.command = UNLOCK_CONFIG; + if(lock == false) + { + sendPacket.LockedValue = 0x00; //0x00 is used in the bootloader firmware to mean unlock the config bits ("UNLOCKCONFIG") + } + else + { + //All other values should mean we are locking the config bits + sendPacket.LockedValue = 0x01; //lock the config bits + } + + if(lock) + qDebug("Locking Config Bits..."); + else + qDebug("Unlocking Config Bits..."); + + elapsed.start(); + + status = SendPacket((unsigned char*)&sendPacket, sizeof(sendPacket)); + + if(status == Comm::Success) + qDebug("Successfully sent un/lock command (%fs)", (double)elapsed.elapsed() / 1000); + else + qDebug("Unsuccessfully sent un/lock command"); + + return status; + } + + qDebug("Device not connected"); + return NotConnected; +} + +/** + * + */ +Comm::ErrorCode Comm::ReadBootloaderInfo(BootInfo* bootInfo) +{ + QTime elapsed; + WritePacket sendPacket; + ErrorCode status; + + qDebug("Getting Query packet..."); + + if(connected) + { + memset((void*)&sendPacket, 0x00, sizeof(sendPacket)); + sendPacket.command = QUERY_DEVICE; + + elapsed.start(); + + status = SendPacket((unsigned char*)&sendPacket, sizeof(sendPacket)); + + switch(status) + { + case Fail: + close(); + case Timeout: + return status; + default: + break; + } + + qDebug("Successfully sent querying command (%fs)", (double)elapsed.elapsed() / 1000); + memset((void*)bootInfo, 0x00, sizeof(BootInfo)); + + elapsed.start(); + + status = ReceivePacket((unsigned char*)bootInfo, sizeof(BootInfo)); + + if(bootInfo->command != 0x02) { + qWarning("Received incorrect command."); + return IncorrectCommand; + } + + switch(status) + { + case Fail: + close(); + case Timeout: + return status; + default: + break; + } + + qDebug("Successfully received query packet (%fs)", (double)elapsed.elapsed() / 1000); + return Success; + } + + return NotConnected; +} + + +Comm::ErrorCode Comm::ReadExtendedQueryInfo(ExtendedQueryInfo* extendedBootInfo) +{ + QTime elapsed; + WritePacket sendPacket; + ErrorCode status; + + qDebug("Getting Extended Query Info packet..."); + + if(connected) + { + memset((void*)&sendPacket, 0x00, sizeof(sendPacket)); + sendPacket.command = QUERY_EXTENDED_INFO; + + elapsed.start(); + + status = SendPacket((unsigned char*)&sendPacket, sizeof(sendPacket)); + + switch(status) + { + case Fail: + close(); + case Timeout: + return status; + default: + break; + } + + qDebug("Successfully sent QUERY_EXTENDED_INFO command (%fs)", (double)elapsed.elapsed() / 1000); + memset((void*)extendedBootInfo, 0x00, sizeof(ExtendedQueryInfo)); + + elapsed.start(); + + status = ReceivePacket((unsigned char*)extendedBootInfo, sizeof(ExtendedQueryInfo)); + + + if(extendedBootInfo->command != QUERY_EXTENDED_INFO) + { + qWarning("Received incorrect command."); + return IncorrectCommand; + } + + switch(status) + { + case Fail: + close(); + case Timeout: + return status; + default: + break; + } + + qDebug("Successfully received QUERY_EXTENDED_INFO response packet (%fs)", (double)elapsed.elapsed() / 1000); + return Success; + } + + return NotConnected; +} + + +Comm::ErrorCode Comm::SignFlash(void) +{ + QTime elapsed; + WritePacket sendPacket; + ErrorCode status; + BootInfo QueryInfoBuffer; + + qDebug("Sending SIGN_FLASH command..."); + + if(connected) + { + memset((void*)&sendPacket, 0x00, sizeof(sendPacket)); + sendPacket.command = SIGN_FLASH; + + elapsed.start(); + + status = SendPacket((unsigned char*)&sendPacket, sizeof(sendPacket)); + + switch(status) + { + case Fail: + close(); + case Timeout: + return status; + default: + break; + } + + //Now issue a query command, so as to "poll" for the completion of + //the prior request (which doesn't by itself generate a respone packet). + status = ReadBootloaderInfo(&QueryInfoBuffer); + switch(status) + { + case Fail: + close(); + case Timeout: + return status; + default: + break; + } + + qDebug("Successfully sent SIGN_FLASH command (%fs)", (double)elapsed.elapsed() / 1000); + + return Success; + }//if(connected) + + return NotConnected; +} + + +Comm::ErrorCode Comm::SendPacket(unsigned char *pData, int size) +{ + QTime timeoutTimer; + int res = 0, timeout = 5; + + timeoutTimer.start(); + + while(res < 1) + { + res = hid_write(boot_device, pData, size); + + if(timeoutTimer.elapsed() > SyncWaitTime) + { + timeoutTimer.start(); + timeout--; + } + + // If timed out several times, or return error then close device and return failure + if(timeout == 0) + { + qWarning("Timed out waiting for query command acknowledgement."); + return Timeout; + } + + if(res == -1) + { + qWarning("Write failed."); + close(); + return Fail; + } + } + return Success; +} + + +Comm::ErrorCode Comm::ReceivePacket(unsigned char *data, int size) +{ + QTime timeoutTimer; + int res = 0, timeout = 3; + + timeoutTimer.start(); + + while(res < 1) + { + res = hid_read(boot_device, data, size); + + if(timeoutTimer.elapsed() > SyncWaitTime) + { + timeoutTimer.start(); + timeout--; + } + + // If timed out twice, or return error then close device and return failure + if(timeout == 0) + { + qWarning("Timeout."); + return Timeout; + } + + if(res == -1) + { + qWarning("Read failed."); + close(); + return Fail; + } + } + return Success; +} diff --git a/qt5_src/Bootloader/Comm.h b/qt5_src/Bootloader/Comm.h new file mode 100644 index 0000000..ba50532 --- /dev/null +++ b/qt5_src/Bootloader/Comm.h @@ -0,0 +1,197 @@ +/************************************************************************ +* Copyright (c) 2009-2010, Microchip Technology Inc. +* +* Microchip licenses this software to you solely for use with Microchip +* products. The software is owned by Microchip and its licensors, and +* is protected under applicable copyright laws. All rights reserved. +* +* SOFTWARE IS PROVIDED "AS IS." MICROCHIP EXPRESSLY DISCLAIMS ANY +* WARRANTY OF ANY KIND, WHETHER EXPRESS OR IMPLIED, INCLUDING BUT +* NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS +* FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT. IN NO EVENT SHALL +* MICROCHIP BE LIABLE FOR ANY INCIDENTAL, SPECIAL, INDIRECT OR +* CONSEQUENTIAL DAMAGES, LOST PROFITS OR LOST DATA, HARM TO YOUR +* EQUIPMENT, COST OF PROCUREMENT OF SUBSTITUTE GOODS, TECHNOLOGY +* OR SERVICES, ANY CLAIMS BY THIRD PARTIES (INCLUDING BUT NOT LIMITED +* TO ANY DEFENSE THEREOF), ANY CLAIMS FOR INDEMNITY OR CONTRIBUTION, +* OR OTHER SIMILAR COSTS. +* +* To the fullest extent allowed by law, Microchip and its licensors +* liability shall not exceed the amount of fees, if any, that you +* have paid directly to Microchip to use this software. +* +* MICROCHIP PROVIDES THIS SOFTWARE CONDITIONALLY UPON YOUR ACCEPTANCE +* OF THESE TERMS. +* +* Author Date Comment +************************************************************************* +* T. Lawrence 2011/01/24 Initial code ported from AN1310. +************************************************************************/ + +#ifndef COMM_H +#define COMM_H + +#include + +#include +#include + +#include "../HIDAPI/hidapi.h" +#include "Device.h" + + + + +#define USB_PACKET_SIZE 64 +#define USB_PACKET_SIZE_WITH_REPORT_ID (USB_PACKET_SIZE + 1) + + +// Packet commands +#define QUERY_DEVICE 0x02 +#define UNLOCK_CONFIG 0x03 +#define ERASE_DEVICE 0x04 +#define PROGRAM_DEVICE 0x05 +#define PROGRAM_COMPLETE 0x06 +#define GET_DATA 0x07 +#define RESET_DEVICE 0x08 +#define SIGN_FLASH 0x09 //The host PC application should send this command after the verify operation has completed successfully. If checksums are used instead of a true verify (due to ALLOW_GET_DATA_COMMAND being commented), then the host PC application should send SIGN_FLASH command after is has verified the checksums are as exected. The firmware will then program the SIGNATURE_WORD into flash at the SIGNATURE_ADDRESS. +#define QUERY_EXTENDED_INFO 0x0C //Used by host PC app to get additional info about the device, beyond the basic NVM layout provided by the query device command + +// Maximum number of memory regions that can be bootloaded +#define MAX_DATA_REGIONS 0x06 + + +#define MAX_ERASE_BLOCK_SIZE 8196 //Increase this in the future if any microcontrollers with bigger than 8196 byte erase block is implemented + + +/*! + * Provides low level HID bootloader communication. + */ +class Comm : public QObject +{ + Q_OBJECT + +signals: + void SetProgressBar(int newValue); + + +protected: + hid_device *boot_device; + bool connected; + +public: + + explicit Comm(); + ~Comm(); + + static const int SyncWaitTime; + + enum ErrorCode + { + Success = 0, NotConnected, Fail, IncorrectCommand, Timeout, Other = 0xFF + }; + + QString ErrorString(ErrorCode errorCode) const; + + #pragma pack(1) + struct MemoryRegion + { + unsigned char type; + uint32_t address; + uint32_t size; + }; + + struct BootInfo + { + unsigned char command; + unsigned char bytesPerPacket; + unsigned char deviceFamily; + MemoryRegion memoryRegions[MAX_DATA_REGIONS]; + unsigned char versionFlag; + unsigned char pad[7]; + }; + + //Structure for the response to the QUERY_EXTENDED_INFO command + union ExtendedQueryInfo + { + unsigned char command; + struct + { + unsigned char command; + uint16_t bootloaderVersion; + uint16_t applicationVersion; + uint32_t signatureAddress; + uint16_t signatureValue; + uint32_t erasePageSize; + unsigned char config1LMask; + unsigned char config1HMask; + unsigned char config2LMask; + unsigned char config2HMask; + unsigned char config3LMask; + unsigned char config3HMask; + unsigned char config4LMask; + unsigned char config4HMask; + unsigned char config5LMask; + unsigned char config5HMask; + unsigned char config6LMask; + unsigned char config6HMask; + unsigned char config7LMask; + unsigned char config7HMask; + unsigned char pad[USB_PACKET_SIZE_WITH_REPORT_ID - 29]; + }PIC18; + struct + { + unsigned char command; + uint16_t bootloaderVersion; + uint16_t applicationVersion; + uint32_t signatureAddress; + uint16_t signatureValue; + uint32_t erasePageSize; + #warning Replace with real stuff when implemented. + //uint16_t configxxMask... + //unsigned char pad[USB_PACKET_SIZE_WITH_REPORT_ID - XX]; + }PIC24; + }; + + + + struct WritePacket + { + unsigned char report; + unsigned char command; + union { + uint32_t address; + unsigned char LockedValue; + }; + unsigned char bytesPerPacket; + unsigned char data[58]; + }; + struct ReadPacket + { + unsigned char command; + uint32_t address; + unsigned char bytesPerPacket; + unsigned char data[59]; + }; + #pragma pack() + + void PollUSB(uint16_t deviceVIDtoPoll, uint16_t devicePIDtoPoll); + ErrorCode open(uint16_t deviceVIDtoOpen, uint16_t devicePIDtoOpen); + void close(void); + bool isConnected(void); + void Reset(void); + + ErrorCode GetData(uint32_t address, unsigned char bytesPerPacket, unsigned char bytesPerAddress, + unsigned char bytesPerWord, uint32_t endAddress, unsigned char *data); + ErrorCode Program(uint32_t address, unsigned char bytesPerPacket, unsigned char bytesPerAddress, + unsigned char bytesPerWord, unsigned char deviceFamily, uint32_t endAddress, unsigned char *data); + ErrorCode Erase(void); + ErrorCode LockUnlockConfig(bool lock); + ErrorCode ReadBootloaderInfo(BootInfo* bootInfo); + ErrorCode ReadExtendedQueryInfo(ExtendedQueryInfo* extendedBootInfo); + ErrorCode SignFlash(void); + ErrorCode SendPacket(unsigned char *data, int size); + ErrorCode ReceivePacket(unsigned char *data, int size); +}; + +#endif // COMM_H diff --git a/qt5_src/Bootloader/Device.cpp b/qt5_src/Bootloader/Device.cpp new file mode 100644 index 0000000..7439bf2 --- /dev/null +++ b/qt5_src/Bootloader/Device.cpp @@ -0,0 +1,204 @@ +/************************************************************************ +* Copyright (c) 2009-2011, Microchip Technology Inc. +* +* Microchip licenses this software to you solely for use with Microchip +* products. The software is owned by Microchip and its licensors, and +* is protected under applicable copyright laws. All rights reserved. +* +* SOFTWARE IS PROVIDED "AS IS." MICROCHIP EXPRESSLY DISCLAIMS ANY +* WARRANTY OF ANY KIND, WHETHER EXPRESS OR IMPLIED, INCLUDING BUT +* NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS +* FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT. IN NO EVENT SHALL +* MICROCHIP BE LIABLE FOR ANY INCIDENTAL, SPECIAL, INDIRECT OR +* CONSEQUENTIAL DAMAGES, LOST PROFITS OR LOST DATA, HARM TO YOUR +* EQUIPMENT, COST OF PROCUREMENT OF SUBSTITUTE GOODS, TECHNOLOGY +* OR SERVICES, ANY CLAIMS BY THIRD PARTIES (INCLUDING BUT NOT LIMITED +* TO ANY DEFENSE THEREOF), ANY CLAIMS FOR INDEMNITY OR CONTRIBUTION, +* OR OTHER SIMILAR COSTS. +* +* To the fullest extent allowed by law, Microchip and its licensors +* liability shall not exceed the amount of fees, if any, that you +* have paid directly to Microchip to use this software. +* +* MICROCHIP PROVIDES THIS SOFTWARE CONDITIONALLY UPON YOUR ACCEPTANCE +* OF THESE TERMS. +* +* Author Date Comment +************************************************************************* +* E. Schlunder 2010/02/01 Loading config bit fields/settings added. +* E. Schlunder 2009/04/14 Initial code ported from VB app. +* F. Schlunder 2011/06/13 Minor changes for USB HID Bootloader use. +************************************************************************/ + +#include "Device.h" + +Device::Device(DeviceData *data) +{ + deviceData = data; + + setUnknown(); +} + +void Device::setUnknown(void) +{ + family = Unknown; + + bytesPerAddressFLASH = 1; + bytesPerAddressEEPROM = 1; + bytesPerAddressConfig = 1; + + bytesPerWordEEPROM = 1; + bytesPerWordConfig = 2; + bytesPerWordFLASH = 2; +} + +bool Device::hasEeprom(void) +{ + DeviceData::MemoryRange range; + foreach(range, deviceData->ranges) + { + if(range.type == EEPROM_MEMORY) + return true; + } + + return false; +} + +bool Device::hasConfig(void) +{ + DeviceData::MemoryRange range; + foreach(range, deviceData->ranges) + { + if(range.type == CONFIG_MEMORY) + return true; + } + + return false; +} + +//This method is useful for hex file parsing. This function checks if the address indicated in the hex +//file line is contained in one of the programmable regions (ex: flash, eeprom, config words, etc.), as +//specified by the USB device, (from the query response that we received earlier). If the address +//from the hex file is contained in the programmable device address range, this function returns +//bool includedInProgrammableRange = true, with the type set to the programmable region type +//(which can be determined based on the address and the query response). This function also returns +//the new effective device address (not from the .hex file, which is a raw linear byte address, but the +//effective address the device should load into its self programming address registers, in order to +//program the contents [PIC24 uses a 16-bit word addressed flash array, so the hex file addresses +//don't match the microcontroller addresses]). +//This function also returns bool addressWasEndofRange = true, if the input hexAddress corresponded +//to the very last byte of the last address of the programmable memory region that the hexAddress +//corresponded to. Otherwise this value returns false. This provides an easy check later to +//know when an end of a region has been completed during hex file parsing. +unsigned int Device::GetDeviceAddressFromHexAddress(unsigned int hexAddress, DeviceData* pData, unsigned char& type, bool& includedInProgrammableRange, bool& addressWasEndofRange, unsigned int& bytesPerAddressAndType, unsigned int& endDeviceAddressofRegion, unsigned char*& pPCRAMBuffer) +{ + DeviceData::MemoryRange range; + unsigned int flashAddress = hexAddress / bytesPerAddressFLASH; + unsigned int eepromAddress = hexAddress / bytesPerAddressEEPROM; + unsigned int configAddress = hexAddress / bytesPerAddressConfig; + unsigned char* pRAMDataBuffer; + unsigned int byteOffset; + + + //Loop for each of the previously identified programmable regions, based on the results of the + //previous Query device response packet. + foreach(range, pData->ranges) + { + //Find what address range the hex address seems to contained within (if any, could be none, in + //the case the .hex file contains info that is not part of the bootloader re-programmable region of flash). + if((range.type == PROGRAM_MEMORY) && (flashAddress >= range.start) && (flashAddress < range.end)) + { + includedInProgrammableRange = true; + if(range.start != 0) + { + byteOffset = ((flashAddress - range.start) * bytesPerAddressFLASH) + (hexAddress % bytesPerAddressFLASH); + pRAMDataBuffer = range.pDataBuffer + byteOffset; + pPCRAMBuffer = pRAMDataBuffer; + } + else + { + pPCRAMBuffer = 0; + } + + type = PROGRAM_MEMORY; + bytesPerAddressAndType = bytesPerAddressFLASH; + endDeviceAddressofRegion = range.end; + //Check if this was the very last byte of the very last address of the region. + //We can determine this, using the below check. + if((flashAddress == (range.end - 1)) && ((hexAddress % bytesPerAddressFLASH) == (bytesPerAddressFLASH - 1))) + { + addressWasEndofRange = true; + } + else + { + addressWasEndofRange = false; + } + return flashAddress; + } + + if((range.type == EEPROM_MEMORY) && (eepromAddress >= range.start) && (eepromAddress < range.end)) + { + includedInProgrammableRange = true; + if(range.start != 0) + { + byteOffset = ((eepromAddress - range.start) * bytesPerAddressEEPROM) + (hexAddress % bytesPerAddressEEPROM); + pRAMDataBuffer = range.pDataBuffer + byteOffset; + pPCRAMBuffer = pRAMDataBuffer; + } + else + { + pPCRAMBuffer = 0; + } + type = EEPROM_MEMORY; + bytesPerAddressAndType = bytesPerAddressEEPROM; + endDeviceAddressofRegion = range.end; + //Check if this was the very last byte of the very last address of the region. + //We can determine this, using the below check. + if((eepromAddress == (range.end - 1)) && ((eepromAddress % bytesPerAddressEEPROM) == (bytesPerAddressEEPROM - 1))) + { + addressWasEndofRange = true; + } + else + { + addressWasEndofRange = false; + } + return eepromAddress; + } + + if((range.type == CONFIG_MEMORY) && (configAddress >= range.start) && (configAddress < range.end)) + { + includedInProgrammableRange = true; + if(range.start != 0) + { + byteOffset = ((configAddress - range.start) * bytesPerAddressConfig) + (hexAddress % bytesPerAddressConfig); + pRAMDataBuffer = range.pDataBuffer + byteOffset; + pPCRAMBuffer = pRAMDataBuffer; + } + else + { + pPCRAMBuffer = 0; + } + type = CONFIG_MEMORY; + bytesPerAddressAndType = bytesPerAddressConfig; + endDeviceAddressofRegion = range.end; + //Check if this was the very last byte of the very last address of the region. + //We can determine this, using the below check. + if((configAddress == (range.end - 1)) && ((configAddress % bytesPerAddressConfig) == (bytesPerAddressConfig - 1))) + { + addressWasEndofRange = true; + } + else + { + addressWasEndofRange = false; + } + return configAddress; + } + } + + //If we get to here, that means the hex file address that was passed in was not included in any of the + //device's reported programmable memory regions. + includedInProgrammableRange = false; + addressWasEndofRange = false; + pPCRAMBuffer = 0; + return 0; +} diff --git a/qt5_src/Bootloader/Device.h b/qt5_src/Bootloader/Device.h new file mode 100644 index 0000000..636fe47 --- /dev/null +++ b/qt5_src/Bootloader/Device.h @@ -0,0 +1,80 @@ +/************************************************************************ +* Copyright (c) 2009-2010, Microchip Technology Inc. +* +* Microchip licenses this software to you solely for use with Microchip +* products. The software is owned by Microchip and its licensors, and +* is protected under applicable copyright laws. All rights reserved. +* +* SOFTWARE IS PROVIDED "AS IS." MICROCHIP EXPRESSLY DISCLAIMS ANY +* WARRANTY OF ANY KIND, WHETHER EXPRESS OR IMPLIED, INCLUDING BUT +* NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS +* FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT. IN NO EVENT SHALL +* MICROCHIP BE LIABLE FOR ANY INCIDENTAL, SPECIAL, INDIRECT OR +* CONSEQUENTIAL DAMAGES, LOST PROFITS OR LOST DATA, HARM TO YOUR +* EQUIPMENT, COST OF PROCUREMENT OF SUBSTITUTE GOODS, TECHNOLOGY +* OR SERVICES, ANY CLAIMS BY THIRD PARTIES (INCLUDING BUT NOT LIMITED +* TO ANY DEFENSE THEREOF), ANY CLAIMS FOR INDEMNITY OR CONTRIBUTION, +* OR OTHER SIMILAR COSTS. +* +* To the fullest extent allowed by law, Microchip and its licensors +* liability shall not exceed the amount of fees, if any, that you +* have paid directly to Microchip to use this software. +* +* MICROCHIP PROVIDES THIS SOFTWARE CONDITIONALLY UPON YOUR ACCEPTANCE +* OF THESE TERMS. +*/ + +#ifndef DEVICE_H +#define DEVICE_H + +#include +#include +#include +#include + +#include "DeviceData.h" + +/*! + * Provides microcontroller device specific parameters, address calculations, and + * assembly code tools. + */ +class Device +{ +public: + enum Families + { + Unknown = 0, + PIC18 = 0x01, + PIC24 = 0x02, + PIC32 = 0x03, + PIC16 = 0x04 + }; + + Device(DeviceData* data); + + void setUnknown(void); + + Families family; + + unsigned int bytesPerPacket; + unsigned int bytesPerWordFLASH; + unsigned int bytesPerWordEEPROM; + unsigned int bytesPerWordConfig; + + unsigned int bytesPerAddressFLASH; + unsigned int bytesPerAddressEEPROM; + unsigned int bytesPerAddressConfig; + + bool hasEeprom(void); + bool hasConfig(void); + + bool hasConfigAsFlash(void); + bool hasConfigAsFuses(void); + + unsigned int GetDeviceAddressFromHexAddress(unsigned int hexAddress, DeviceData* pData, unsigned char& type, bool& includedInProgrammableRange, bool& addressWasEndofRange, unsigned int& bytesPerAddressAndType, unsigned int& endDeviceAddressofRegion, unsigned char*& pPCRAMBuffer); + +protected: + DeviceData *deviceData; +}; + +#endif // DEVICE_H diff --git a/qt5_src/Bootloader/DeviceData.cpp b/qt5_src/Bootloader/DeviceData.cpp new file mode 100644 index 0000000..43ccaaf --- /dev/null +++ b/qt5_src/Bootloader/DeviceData.cpp @@ -0,0 +1,40 @@ +/************************************************************************ +* Copyright (c) 2009-2011, Microchip Technology Inc. +* +* Microchip licenses this software to you solely for use with Microchip +* products. The software is owned by Microchip and its licensors, and +* is protected under applicable copyright laws. All rights reserved. +* +* SOFTWARE IS PROVIDED "AS IS." MICROCHIP EXPRESSLY DISCLAIMS ANY +* WARRANTY OF ANY KIND, WHETHER EXPRESS OR IMPLIED, INCLUDING BUT +* NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS +* FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT. IN NO EVENT SHALL +* MICROCHIP BE LIABLE FOR ANY INCIDENTAL, SPECIAL, INDIRECT OR +* CONSEQUENTIAL DAMAGES, LOST PROFITS OR LOST DATA, HARM TO YOUR +* EQUIPMENT, COST OF PROCUREMENT OF SUBSTITUTE GOODS, TECHNOLOGY +* OR SERVICES, ANY CLAIMS BY THIRD PARTIES (INCLUDING BUT NOT LIMITED +* TO ANY DEFENSE THEREOF), ANY CLAIMS FOR INDEMNITY OR CONTRIBUTION, +* OR OTHER SIMILAR COSTS. +* +* To the fullest extent allowed by law, Microchip and its licensors +* liability shall not exceed the amount of fees, if any, that you +* have paid directly to Microchip to use this software. +* +* MICROCHIP PROVIDES THIS SOFTWARE CONDITIONALLY UPON YOUR ACCEPTANCE +* OF THESE TERMS. +* +* Author Date Comment +************************************************************************* +* E. Schlunder 2009/05/07 Added code for CRC calculation. +* E. Schlunder 2009/04/29 Code ported from PicKit2 pk2cmd source code. +*************************************************************************/ + +#include "DeviceData.h" + +DeviceData::DeviceData() +{ +} + +DeviceData::~DeviceData() +{ +} diff --git a/qt5_src/Bootloader/DeviceData.h b/qt5_src/Bootloader/DeviceData.h new file mode 100644 index 0000000..149766b --- /dev/null +++ b/qt5_src/Bootloader/DeviceData.h @@ -0,0 +1,62 @@ +/************************************************************************ +* Copyright (c) 2009-2011, Microchip Technology Inc. +* +* Microchip licenses this software to you solely for use with Microchip +* products. The software is owned by Microchip and its licensors, and +* is protected under applicable copyright laws. All rights reserved. +* +* SOFTWARE IS PROVIDED "AS IS." MICROCHIP EXPRESSLY DISCLAIMS ANY +* WARRANTY OF ANY KIND, WHETHER EXPRESS OR IMPLIED, INCLUDING BUT +* NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS +* FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT. IN NO EVENT SHALL +* MICROCHIP BE LIABLE FOR ANY INCIDENTAL, SPECIAL, INDIRECT OR +* CONSEQUENTIAL DAMAGES, LOST PROFITS OR LOST DATA, HARM TO YOUR +* EQUIPMENT, COST OF PROCUREMENT OF SUBSTITUTE GOODS, TECHNOLOGY +* OR SERVICES, ANY CLAIMS BY THIRD PARTIES (INCLUDING BUT NOT LIMITED +* TO ANY DEFENSE THEREOF), ANY CLAIMS FOR INDEMNITY OR CONTRIBUTION, +* OR OTHER SIMILAR COSTS. +* +* To the fullest extent allowed by law, Microchip and its licensors +* liability shall not exceed the amount of fees, if any, that you +* have paid directly to Microchip to use this software. +* +* MICROCHIP PROVIDES THIS SOFTWARE CONDITIONALLY UPON YOUR ACCEPTANCE +* OF THESE TERMS. +*/ +#ifndef DEVICEDATA_H +#define DEVICEDATA_H + +// Types of memory regions +#define PROGRAM_MEMORY 0x01 +#define EEPROM_MEMORY 0x02 +#define CONFIG_MEMORY 0x03 +#define USERID_MEMORY 0x04 +#define END_OF_TYPES_LIST 0xFF +#define BOOTLOADER_V1_01_OR_NEWER_FLAG 0xA5 //Tacked on in region Type6 byte, to indicate when using newer version of bootloader with extended query info available + + +#include + + +/*! + * Provides in-memory, PC representation of microcontroller device memory contents. + */ +class DeviceData +{ + public: + DeviceData(); + ~DeviceData(); + + struct MemoryRange + { + unsigned char type; + unsigned int start; + unsigned int end; + unsigned int dataBufferLength; + unsigned char* pDataBuffer; + }; + + QList ranges; +}; + +#endif // DEVICEDATA_H diff --git a/qt5_src/Bootloader/ImportExportHex.cpp b/qt5_src/Bootloader/ImportExportHex.cpp new file mode 100644 index 0000000..12a5105 --- /dev/null +++ b/qt5_src/Bootloader/ImportExportHex.cpp @@ -0,0 +1,287 @@ +/************************************************************************ +* Copyright (c) 2005-2010, Microchip Technology Inc. +* +* Microchip licenses this software to you solely for use with Microchip +* products. The software is owned by Microchip and its licensors, and +* is protected under applicable copyright laws. All rights reserved. +* +* SOFTWARE IS PROVIDED "AS IS." MICROCHIP EXPRESSLY DISCLAIMS ANY +* WARRANTY OF ANY KIND, WHETHER EXPRESS OR IMPLIED, INCLUDING BUT +* NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS +* FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT. IN NO EVENT SHALL +* MICROCHIP BE LIABLE FOR ANY INCIDENTAL, SPECIAL, INDIRECT OR +* CONSEQUENTIAL DAMAGES, LOST PROFITS OR LOST DATA, HARM TO YOUR +* EQUIPMENT, COST OF PROCUREMENT OF SUBSTITUTE GOODS, TECHNOLOGY +* OR SERVICES, ANY CLAIMS BY THIRD PARTIES (INCLUDING BUT NOT LIMITED +* TO ANY DEFENSE THEREOF), ANY CLAIMS FOR INDEMNITY OR CONTRIBUTION, +* OR OTHER SIMILAR COSTS. +* +* To the fullest extent allowed by law, Microchip and its licensors +* liability shall not exceed the amount of fees, if any, that you +* have paid directly to Microchip to use this software. +* +* MICROCHIP PROVIDES THIS SOFTWARE CONDITIONALLY UPON YOUR ACCEPTANCE +* OF THESE TERMS. +* +* Author Date Ver Comment +************************************************************************* +* E. Schlunder 2009/04/29 0.01 Code ported from PicKit2 pk2cmd source code. +* F. Schlunder 2011/06/13 2.90 Some changes for USB HID Bootloader use. +* F. Schlunder 2011/07/06 2.90a Updated ImportHexFile() function so it can +* support importing of hex files with +* "non-monotonic" line address ordering. +*************************************************************************/ + +#include +#include "ImportExportHex.h" +#include "Device.h" + + +HexImporter::HexImporter(void) +{ +} + +HexImporter::~HexImporter(void) +{ +} + + +/* + PIC16Fxx parts use only one address for each FLASH program word. Address 0 has 14 bits of data, Address 1 has + 14 bits of data, etc. However, the PIC16Fxx HEX file addresses each byte of data with a unique address number. + As a result, you basically have to take the HEX file address and divide by 2 to figure out the actual + PIC16Fxx FLASH memory address that the byte belongs to. + + Example: PIC16F886 has 8K program words, word addressed as 0 to 0x2000. + A full HEX file for this part would have 16Kbytes of FLASH data. The HEX file bytes would + be addressed from 0 to 0x4000. + + This presents a predicament for EEPROM data. Instead of starting from HEX file address 0x2100 as + the EDC device database might indicate, the HEX file has to start EEPROM data at 0x2000 + 0x2100 = 0x4100, + to avoid overlapping with the HEX file's FLASH addresses. +*/ + +//This function reads in Intel 32-bit .hex file formatted firmware image files and stores the +//parsed data into buffers in the PC system RAM, so that it is in a format more suitable for directly +//programming into the target microcontroller. +HexImporter::ErrorCode HexImporter::ImportHexFile(QString fileName, DeviceData* pData, Device* device) +{ + QFile hexfile(fileName); + + hasEndOfFileRecord = false; + fileExceedsFlash = false; + + //Open the user specified .hex file. + if (!hexfile.open(QIODevice::ReadOnly | QIODevice::Text)) + { + return CouldNotOpenFile; + } + + bool ok; + bool includedInProgrammableRange; + bool addressWasEndofRange; + unsigned int segmentAddress = 0; + unsigned int byteCount; + unsigned int lineAddress; + unsigned int deviceAddress; + unsigned int i; + unsigned int endDeviceAddressofRegion; + unsigned int bytesPerAddressAndType; + + unsigned char* pPCRAMBuffer = 0; + bool importedAtLeastOneByte = false; + + unsigned char type; + + HEX32_RECORD recordType; + + QString hexByte; + unsigned int wordByte; + hasConfigBits = false; + + + //Parse the entire hex file, line by line. + while (!hexfile.atEnd()) + { + //Fetch a line of ASCII text from the .hex file. + QByteArray line = hexfile.readLine(); + + //Do some error checking on the .hex file contents, to make sure the file is + //formatted like a legitimate Intel 32-bit formatted .hex file. + if ((line[0] != ':') || (line.size() < 11)) + { + //Something is wrong if hex line entry, is not minimum length or does not have leading colon (ex: ":BBAAAATTCC") + //If an error is detected in the hex file formatting, the safest approach is to + //abort the operation and force the user to supply a properly formatted hex file. + if(hexfile.isOpen()) + hexfile.close(); + return ErrorInHexFile; + } + + //Extract the info prefix fields from the hex file line data. + //Example Intel 32-bit hex file line format is as follows (Note: spaces added to separate fields, actual line does not have spaces in it): + //: 10 0100 00 214601360121470136007EFE09D21901 40 + //Leading ":" is always present on each line from the .hex file. + //Next two chars (10) are the byte count of the data payload on this hex file line. (ex: 10 = 0x10 = 16 bytes) + //Next four chars (0100) are the 16 bit address (needs to be combined with the extended linear address to generate a 32-bit address). + //Next two chars (00) are the "record type". "00" means it is a "data" record, which means it contains programmable payload data bytes. + //Next 2n characters are the data payload bytes (where n is the number of bytes specified in the first two numbers (10 in this example)) + //Last two characters on the line are the two complement of the byte checksum computed on the other bytes in the line. + //For more details on Intel 32-bit hex file formatting see: http://en.wikipedia.org/wiki/Intel_HEX + byteCount = line.mid(1, 2).toInt(&ok, 16); //Convert the two ASCII chars corresponding to the byte count into a binary encoded byte + lineAddress = segmentAddress + line.mid(3, 4).toInt(&ok, 16); //Convert the four ASCII chars that correspond to the line address into a 16-bit binary encoded word + recordType = (HEX32_RECORD)line.mid(7, 2).toInt(&ok, 16); //Convert the two ASCII chars corresponding to the record type into a binary encoded byte + + //Error check: Verify checksum byte at the end of the .hex file line is valid. Note, + //this is not the same checksum as MPLAB(R) IDE uses/computes for the entire hex file. + //This is only the mini-checksum at the end of each line in the .hex file. + unsigned int hexLineChecksum = 0; + for(i = 0; i < (byteCount+4); i++) //+4 correction is for byte count, 16-bit address, and record type bytes + { + hexByte = line.mid(1 + (2 * i), 2); //Fetch two adjacent ASCII bytes from the .hex file + wordByte = hexByte.toInt(&ok, 16); //Re-format the above two ASCII bytes into a single binary encoded byte (0x00-0xFF) + //Add the newly fetched byte to the running checksum + hexLineChecksum += (unsigned char)wordByte; + } + //Now get the two's complement of the hexLineChecksum. + hexLineChecksum = 0 - hexLineChecksum; + hexLineChecksum &= 0xFF; //Truncate to a single byte. We now have our computed checksum. This should match the .hex file. + //Fetch checksum byte from the .hex file + hexByte = line.mid(1 + (2 * i), 2); //Fetch the two ASCII bytes that correspond to the checksum byte + wordByte = hexByte.toInt(&ok, 16); //Re-format the above two ASCII bytes into a single binary encoded byte (0x00-0xFF) + wordByte &= 0xFF; + //Now check if the checksum we computed matches the one at the end of the line in the hex file. + if(hexLineChecksum != wordByte) + { + //Checksum in the hex file doesn't match the line contents. This implies a corrupted hex file. + //If an error is detected in the hex file formatting, the safest approach is to + //abort the operation and force the user to supply a properly formatted hex file. + if(hexfile.isOpen()) + hexfile.close(); + return ErrorInHexFile; + } + + + + //Check the record type of the hex line, to determine how to continue parsing the data. + if (recordType == END_OF_FILE) // end of file record + { + hasEndOfFileRecord = true; + break; + } + else if ((recordType == EXTENDED_SEGMENT_ADDR) || (recordType == EXTENDED_LINEAR_ADDR)) // Segment address + { + //Error check: Make sure the line contains the correct number of bytes for the specified record type + if((unsigned int)line.size() >= (11 + (2 * byteCount))) + { + //Fetch the payload, which is the upper 4 or 16-bits of the 20-bit or 32-bit hex file address + segmentAddress = line.mid(9, 4).toInt(&ok, 16); + + //Load the upper bits of the address + if (recordType == EXTENDED_SEGMENT_ADDR) + { + segmentAddress <<= 4; + } + else + { + segmentAddress <<= 16; + } + + //Update the line address, now that we know the upper bits are something new. + lineAddress = segmentAddress + line.mid(3, 4).toInt(&ok, 16); + } + else + { + //Length appears to be wrong in hex line entry. + //If an error is detected in the hex file formatting, the safest approach is to + //abort the operation and force the user to supply a properly formatted hex file. + if(hexfile.isOpen()) + hexfile.close(); + return ErrorInHexFile; + } + + } // end if ((recordType == EXTENDED_SEGMENT_ADDR) || (recordType == EXTENDED_LINEAR_ADDR)) // Segment address + else if (recordType == DATA) // Data Record + { + //Error check to make sure line is long enough to be consistent with the specified record type + if ((unsigned int)line.size() < (11 + (2 * byteCount))) + { + //If an error is detected in the hex file formatting, the safest approach is to + //abort the operation and force the user to supply a proper hex file. + if(hexfile.isOpen()) + hexfile.close(); + return ErrorInHexFile; + } + + + //For each data payload byte we find in the hex file line, check if it is contained within + //a progammable region inside the microcontroller. If so save it. If not, discard it. + for(i = 0; i < byteCount; i++) + { + //Use the hex file linear byte address, to compute other imformation about the + //byte/location. The GetDeviceAddressFromHexAddress() function gives us a pointer to + //the PC RAM buffer byte that will get programmed into the microcontroller, which corresponds + //to the specified .hex file extended address. + //The function also returns a boolean letting us know if the address is part of a programmable memory region on the device. + deviceAddress = device->GetDeviceAddressFromHexAddress(lineAddress + i, pData, type, includedInProgrammableRange, addressWasEndofRange, bytesPerAddressAndType, endDeviceAddressofRegion, pPCRAMBuffer); + //Check if the just parsed hex byte was included in one of the microcontroller reported programmable memory regions. + //If so, save the byte into the proper location in the PC RAM buffer, so it can be programmed later. + if((includedInProgrammableRange == true) && (pPCRAMBuffer != 0)) //Make sure pPCRAMBuffer pointer is valid before using it. + { + //Print debug output text to debug window + if(i == 0) + { + qDebug(QString("Importing .hex file line with device address: 0x" + QString::number(deviceAddress, 16)).toLatin1()); + } + + //Fetch ASCII encoded payload byte from .hex file and save the byte to our temporary RAM buffer. + hexByte = line.mid(9 + (2 * i), 2); //Fetch two ASCII data payload bytes from the .hex file + wordByte = hexByte.toInt(&ok, 16); //Re-format the above two ASCII bytes into a single binary encoded byte (0x00-0xFF) + + *pPCRAMBuffer = (unsigned char)wordByte; //Save the .hex file data byte into the PC RAM buffer that holds the data to be programmed + importedAtLeastOneByte = true; //Set flag so we know we imported something successfully. + + //Check if we just parsed a config bit byte. If so, set flag so the user is no longer locked out + //of programming the config bits section. + if(type == CONFIG_MEMORY) + { + hasConfigBits = true; + } + } + else if((includedInProgrammableRange == true) && (pPCRAMBuffer == 0)) + { + //Previous memory allocation must have failed, or otherwise pPCRAMBuffer would not be = 0. + //Since the memory allocation failed, we should bug out and let the user know. + if(hexfile.isOpen()) + hexfile.close(); + return InsufficientMemory; + } + }//for(i = 0; i < byteCount; i++) + } // end else if (recordType == DATA) + }//while (!hexfile.atEnd()) + + //If we get to here, that means we reached the end of the hex file, or we found a END_OF_FILE record in the .hex file. + if(hexfile.isOpen()) + { + hexfile.close(); + } + + //Check if we imported any data from the .hex file. + if(importedAtLeastOneByte == true) + { + qDebug(QString("Hex File imported successfully.").toLatin1()); + return Success; + } + else + { + //If we get to here, we didn't import anything. The hex file must have been empty or otherwise didn't + //contain any data that overlaps a device programmable region. We should let the user know they should + //supply a better hex file designed for their device. + return NoneInRange; + } +} + + + + + diff --git a/qt5_src/Bootloader/ImportExportHex.h b/qt5_src/Bootloader/ImportExportHex.h new file mode 100644 index 0000000..a6716c1 --- /dev/null +++ b/qt5_src/Bootloader/ImportExportHex.h @@ -0,0 +1,78 @@ +/************************************************************************ +* Copyright (c) 2005-2009, Microchip Technology Inc. +* +* Microchip licenses this software to you solely for use with Microchip +* products. The software is owned by Microchip and its licensors, and +* is protected under applicable copyright laws. All rights reserved. +* +* SOFTWARE IS PROVIDED "AS IS." MICROCHIP EXPRESSLY DISCLAIMS ANY +* WARRANTY OF ANY KIND, WHETHER EXPRESS OR IMPLIED, INCLUDING BUT +* NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS +* FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT. IN NO EVENT SHALL +* MICROCHIP BE LIABLE FOR ANY INCIDENTAL, SPECIAL, INDIRECT OR +* CONSEQUENTIAL DAMAGES, LOST PROFITS OR LOST DATA, HARM TO YOUR +* EQUIPMENT, COST OF PROCUREMENT OF SUBSTITUTE GOODS, TECHNOLOGY +* OR SERVICES, ANY CLAIMS BY THIRD PARTIES (INCLUDING BUT NOT LIMITED +* TO ANY DEFENSE THEREOF), ANY CLAIMS FOR INDEMNITY OR CONTRIBUTION, +* OR OTHER SIMILAR COSTS. +* +* To the fullest extent allowed by law, Microchip and its licensors +* liability shall not exceed the amount of fees, if any, that you +* have paid directly to Microchip to use this software. +* +* MICROCHIP PROVIDES THIS SOFTWARE CONDITIONALLY UPON YOUR ACCEPTANCE +* OF THESE TERMS. +*/ +#ifndef IMPORTEXPORTHEX_H +#define IMPORTEXPORTHEX_H + +#include +#include "DeviceData.h" +#include "Device.h" + +/*! + * Reads HEX files into an in-memory DeviceData object. + */ +class HexImporter +{ +public: + enum ErrorCode { Success = 0, CouldNotOpenFile, NoneInRange, ErrorInHexFile, InsufficientMemory }; + //Definitions for the record type bytes in Intel 32-bit .HEX file formatted firmware images + enum HEX32_RECORD + { + DATA = 0x00, + END_OF_FILE = 0x01, + EXTENDED_SEGMENT_ADDR = 0x02, + EXTENDED_LINEAR_ADDR = 0x04, + }; + + //Definitions for the program memory "word write" size for different microcontroller families + enum PROG_WORD_WRITE_SIZE + { + PIC18_PROG_WORD_WRITE_SIZE = 0x02, //Word writes perform two byte writes on these devices. + PIC24_PROG_WORD_WRITE_SIZE = 0x04, //3 useful bytes per "word write", but the empty phantom byte makes 4 bytes total + PIC32_PROG_WORD_WRITE_SIZE = 0x04, + }; + + + + HexImporter(void); + ~HexImporter(void); + + ErrorCode ImportHexFile(QString fileName, DeviceData* data, Device* device); + + bool hasEndOfFileRecord; // hex file does have an end of file record + bool hasConfigBits; // hex file has config bit settings + bool fileExceedsFlash; // hex file records exceed device memory constraints + + QList rawimport; + + +protected: + //int ParseHex(char* characters, int length); + //unsigned char computeChecksum(char* fileLine); + + +}; + +#endif // IMPORTEXPORTHEX_H diff --git a/qt5_src/Bootloader/MainWindow.cpp b/qt5_src/Bootloader/MainWindow.cpp new file mode 100644 index 0000000..d44d4c6 --- /dev/null +++ b/qt5_src/Bootloader/MainWindow.cpp @@ -0,0 +1,1538 @@ +/************************************************************************ +* Copyright (c) 2009-2011, Microchip Technology Inc. +* +* Microchip licenses this software to you solely for use with Microchip +* products. The software is owned by Microchip and its licensors, and +* is protected under applicable copyright laws. All rights reserved. +* +* SOFTWARE IS PROVIDED "AS IS." MICROCHIP EXPRESSLY DISCLAIMS ANY +* WARRANTY OF ANY KIND, WHETHER EXPRESS OR IMPLIED, INCLUDING BUT +* NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS +* FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT. IN NO EVENT SHALL +* MICROCHIP BE LIABLE FOR ANY INCIDENTAL, SPECIAL, INDIRECT OR +* CONSEQUENTIAL DAMAGES, LOST PROFITS OR LOST DATA, HARM TO YOUR +* EQUIPMENT, COST OF PROCUREMENT OF SUBSTITUTE GOODS, TECHNOLOGY +* OR SERVICES, ANY CLAIMS BY THIRD PARTIES (INCLUDING BUT NOT LIMITED +* TO ANY DEFENSE THEREOF), ANY CLAIMS FOR INDEMNITY OR CONTRIBUTION, +* OR OTHER SIMILAR COSTS. +* +* To the fullest extent allowed by law, Microchip and its licensors +* liability shall not exceed the amount of fees, if any, that you +* have paid directly to Microchip to use this software. +* +* MICROCHIP PROVIDES THIS SOFTWARE CONDITIONALLY UPON YOUR ACCEPTANCE +* OF THESE TERMS. +* +* Author Date Ver Comment +************************************************************************* +* E. Schlunder 2009/04/14 0.01 Initial code ported from VB app. +* T. Lawrence 2011/01/14 2.9 Initial implementation of USB version of this +* bootloader application. +* F. Schlunder 2011/07/06 2.9a Small update to support importing of hex files +* with "non-monotonic" line address ordering. +* F. Schlunder 2015/07/28 2.13 Added ability to override default VID/PID by specifing +* new values when launching the .exe. +* Example Usage: +* HIDBootloader.exe 0x04D8 0x1234 +* Where "0x04D8" is the target VID (of the bootloader firmware) +* Where "0x1234" is the target PID (of the bootloader firmware) +* Also added ability to specify optional third parameter (a .hex file) +* to be programmed immediately (ex: for non-GUI/fully automated programming +* operation, where the bootloader immediately programs the device and then exits). +* Example Usage: +* HIDBootloader.exe 0x04D8 0x1234 TheNewApplicationImage.hex +************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "MainWindow.h" +#include "ui_MainWindow.h" + +#include "Settings.h" + +#include "../version.h" + + + +// Device Vendor and Product IDs. These values should match the values used in the USB device firmware (in the device descriptor). +// These are the default values that this PC application will serach for when trying to find/connect to the device. If this application +// is called from the command line with extra parameters (specifying new VID and PID values), then those values will be used instead +// of these default values. +#define DEFAULT_VID 0x04D8 +#define DEFAULT_PID 0x003C + + + + + +//Surely the micro doesn't have a programmable memory region greater than 268 Megabytes... +//Value used for error checking device reponse values. +#define MAXIMUM_PROGRAMMABLE_MEMORY_SEGMENT_SIZE 0x0FFFFFFF + +bool deviceFirmwareIsAtLeast101 = false; +Comm::ExtendedQueryInfo extendedBootInfo; + + +volatile bool consoleInvokedEraseProgramVerifyComplete = false; +volatile bool consoleEraseProgramVerifiedOkay = false; +uint16_t DeviceVID; +uint16_t DevicePID; + + + + +MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindowClass) +{ + int i; + hexOpen = false; + fileWatcher = NULL; + timer = new QTimer(); + + ui->setupUi(this); + setWindowTitle(APPLICATION + QString(" v") + VERSION); + + QSettings settings; + settings.beginGroup("MainWindow"); + fileName = settings.value("fileName").toString(); + + for(i = 0; i < MAX_RECENT_FILES; i++) + { + recentFiles[i] = new QAction(this); + connect(recentFiles[i], SIGNAL(triggered()), this, SLOT(openRecentFile())); + recentFiles[i]->setVisible(false); + ui->menuFile->insertAction(ui->actionExit, recentFiles[i]); + } + ui->menuFile->insertSeparator(ui->actionExit); + + settings.endGroup(); + + settings.beginGroup("WriteOptions"); + writeFlash = settings.value("writeFlash", true).toBool(); + //writeConfig = settings.value("writeConfig", false).toBool(); + writeConfig = false; //Force user to manually re-enable it every time they re-launch the application. Safer that way. + writeEeprom = settings.value("writeEeprom", false).toBool(); + eraseDuringWrite = true; + settings.endGroup(); + + comm = new Comm(); + deviceData = new DeviceData(); + hexData = new DeviceData(); + + device = new Device(deviceData); + + qRegisterMetaType("Comm::ErrorCode"); + + connect(timer, SIGNAL(timeout()), this, SLOT(Connection())); + connect(this, SIGNAL(IoWithDeviceCompleted(QString,Comm::ErrorCode,double)), this, SLOT(IoWithDeviceComplete(QString,Comm::ErrorCode,double))); + connect(this, SIGNAL(IoWithDeviceStarted(QString)), this, SLOT(IoWithDeviceStart(QString))); + connect(this, SIGNAL(AppendString(QString)), this, SLOT(AppendStringToTextbox(QString))); + connect(this, SIGNAL(SetProgressBar(int)), this, SLOT(UpdateProgressBar(int))); + connect(comm, SIGNAL(SetProgressBar(int)), this, SLOT(UpdateProgressBar(int))); + + + this->statusBar()->addPermanentWidget(&deviceLabel); + deviceLabel.setText("Disconnected"); + + + //Assume initally that the USB device that we are trying to find is using the default VID/PID. + DeviceVID = DEFAULT_VID; + DevicePID = DEFAULT_PID; + + //Check if the caller specified alternate VID and PID values by specifying them at the command line when they called this application + //ex: HIDBootloader.exe 0x04D8 0x1234 + //Where the first number string is the VID and the second number provided is the PID, of the USB device bootloader + //firmware (the "application" or non-bootloader firmware VID and PID don't matter and aren't relevant to this program, + //only the bootloader VID/PID). + //If the caller provided VID/PID values, use those values instead of the DEFAULT_VID and DEFAULT_PID when searching/connecting + //to the USB device. + + //Fetch an array of strings of the input parmaters that the caller used when invoking the application. + QStringList args = QCoreApplication::arguments(); + if(args.size() >= 2) + { + //Check if the user passed in a VID string when invoking the program. + QString firstParameter = args.value(1); + if((firstParameter.size() == 4) || (firstParameter.size() == 6)) //could be 4 characters long if no leading "0x" was provided, or 6 chars if the full thing was provided, ex: "0x04D8" + { + bool status; + //Convert the hex ASCII string into a proper ulong type variable. + ulong HexVID = firstParameter.toULong(&status, 16); + + if(status == true) + { + //Make sure the ulong converted from the text string was a 16-bit hex value. + if(HexVID <= 0x0000FFFF) + { + //Looks okay. Use the value as the real value to search for when looking for the USB device. + DeviceVID = HexVID; + } + } + } + + //Check if the user passed in a PID string when invoking the program. + if(args.size() >= 3) + { + QString secondParameter = args.value(2); + if((secondParameter.size() == 4) || (secondParameter.size() == 6)) + { + bool status; + ulong HexPID = secondParameter.toULong(&status, 16); + + if(status == true) + { + //Make sure the ulong converted from the text string was a 16-bit hex value. + if(HexPID <= 0x0000FFFF) + { + //Looks okay. Use the value as the real value to search for when looking for the USB device. + DevicePID = HexPID; + } + } + } + } + } + + + + + //Make initial check to see if the USB device is attached + comm->PollUSB(DeviceVID, DevicePID); + if(comm->isConnected()) + { + qWarning("Attempting to open device..."); + comm->open(DeviceVID, DevicePID); + ui->plainTextEdit->setPlainText("Device Attached."); + ui->plainTextEdit->appendPlainText("Connecting..."); + GetQuery(); + } + else + { + ui->plainTextEdit->appendPlainText("Device not detected. Verify device is attached and in firmware update mode."); + deviceLabel.setText("Disconnected"); + hexOpen = false; + setBootloadEnabled(false); + emit SetProgressBar(0); + } + + //Update the file list in the File-->[import files list] area, so the user can quickly re-load a previously used .hex file. + UpdateRecentFileList(); + + + timer->start(1000); //Check for future USB connection status changes every 1000 milliseconds. + + + //Check if the user ran the .exe with extensions pointing staight to the hex file of interest to be programmed. + //If the device is connected, and the extra info was provided, jump straight to erase/program/verify operation + //and then exit the program. This is useful if you want to use the bootloader in a more production or automated + //environment where you don't necessarily want to have to click a button on the GUI. + if(args.size() >= 4) + { + //Copy the .hex file name/path string from the string array into a separate string. + QString argument = args.value(3); + if(argument.endsWith(".hex", Qt::CaseInsensitive)) + { + //Make sure the device was already connected in bootloader mode and ready. + if(comm->isConnected()) + { + //Load up the .hex file from the hard drive + LoadFile(argument); + + printf("Found device and hex file. Programming device..."); + + //Start the thread that does the erase/program/verify sequence. + future = QtConcurrent::run(this, &MainWindow::WriteDevice); + + //Wait for the thread to finish up. + while((future.isFinished() == false) && (consoleInvokedEraseProgramVerifyComplete == false)) + { + QThread::msleep(5); + } + + //Check the status and exit the application. + printf("\r\nFinished erase/program/verify sequence."); + if(consoleEraseProgramVerifiedOkay == true) + { + //Successfully erase/program/verified via console invocation. + printf(" Operation successful."); + + //Now command the USB device to reset itself. This would normally switch it out + //of USB HID bootloader mode, into application run mode (although the exact + //behavior will depend upon how the bootloader firmware is implemented and what is uses + //as an entry method into the bootloader mode). + comm->Reset(); + + //Exit the application now and return '0', which indicates success to the caller. + exit(0); + } + else + { + //Failure during verify. Return something other than '0' to let the caller + //know something unexpected happened. + printf(" Operation FAILED!"); + exit(-366); + } + } + else + { + //The device isn't in bootloader mode or otherwise couldn't be connected with. + //In this scenario, we should promptly exit with failure status, so the automated caller + //knows something is wrong promptly (rather than launching the full GUI and waiting indefinitely + //for a human to do something). + printf("\r\nDevice was not found. Exiting application..."); + exit(-367); + } + } + } +} + + + + + + +MainWindow::~MainWindow() +{ + QSettings settings; + + settings.beginGroup("MainWindow"); + settings.setValue("size", size()); + settings.setValue("pos", pos()); + settings.setValue("fileName", fileName); + settings.endGroup(); + + settings.beginGroup("WriteOptions"); + settings.setValue("writeFlash", writeFlash); + settings.setValue("writeConfig", writeConfig); + settings.setValue("writeEeprom", writeEeprom); + settings.endGroup(); + + comm->close(); + setBootloadEnabled(false); + + delete timer; + delete ui; + delete comm; + delete deviceData; + delete hexData; + delete device; +} + +void MainWindow::Connection(void) +{ + bool currStatus = comm->isConnected(); + Comm::ErrorCode result; + + comm->PollUSB(DeviceVID, DevicePID); + + if(currStatus != comm->isConnected()) + { + UpdateRecentFileList(); + + if(comm->isConnected()) + { + qWarning("Attempting to open device..."); + comm->open(DeviceVID, DevicePID); + ui->plainTextEdit->setPlainText("Device Attached."); + ui->plainTextEdit->appendPlainText("Connecting..."); + if(writeConfig) + { + //ui->plainTextEdit->appendPlainText("Disabling Erase button to prevent accidental erasing of the configuration words without reprogramming them\n"); + writeConfig = true; + result = comm->LockUnlockConfig(false); + if(result == Comm::Success) + { + ui->plainTextEdit->appendPlainText("Unlocked Configuration bits successfully\n"); + } + } + else + { + writeConfig = false; + } + GetQuery(); + } + else + { + qWarning("Closing device."); + comm->close(); + deviceLabel.setText("Disconnected"); + ui->plainTextEdit->setPlainText("Device Detached."); + hexOpen = false; + setBootloadEnabled(false); + emit SetProgressBar(0); + } + } +} + +void MainWindow::setBootloadEnabled(bool enable) +{ + ui->action_Settings->setEnabled(enable); + ui->actionErase_Device->setEnabled(enable && !writeConfig); + ui->actionWrite_Device->setEnabled(enable && hexOpen); + ui->actionExit->setEnabled(enable); + ui->action_Verify_Device->setEnabled(enable && hexOpen); + ui->actionOpen->setEnabled(enable); + ui->actionBlank_Check->setEnabled(enable && !writeConfig); + ui->actionReset_Device->setEnabled(enable); +} + +void MainWindow::setBootloadBusy(bool busy) +{ + if(busy) + { + QApplication::setOverrideCursor(Qt::BusyCursor); + timer->stop(); + } + else + { + QApplication::restoreOverrideCursor(); + timer->start(1000); + } + + ui->action_Settings->setEnabled(!busy); + ui->actionErase_Device->setEnabled(!busy && !writeConfig); + ui->actionWrite_Device->setEnabled(!busy && hexOpen); + ui->actionExit->setEnabled(!busy); + ui->action_Verify_Device->setEnabled(!busy && hexOpen); + ui->actionOpen->setEnabled(!busy); + ui->action_Settings->setEnabled(!busy); + ui->actionBlank_Check->setEnabled(!busy && !writeConfig); + ui->actionReset_Device->setEnabled(!busy); +} + +void MainWindow::on_actionExit_triggered() +{ + QApplication::exit(); +} + +void MainWindow::IoWithDeviceStart(QString msg) +{ + ui->plainTextEdit->appendPlainText(msg); + setBootloadBusy(true); +} + + +//Useful for adding lines of text to the main window from other threads. +void MainWindow::AppendStringToTextbox(QString msg) +{ + ui->plainTextEdit->appendPlainText(msg); +} + +void MainWindow::UpdateProgressBar(int newValue) +{ + ui->progressBar->setValue(newValue); +} + + + +void MainWindow::IoWithDeviceComplete(QString msg, Comm::ErrorCode result, double time) +{ + QTextStream ss(&msg); + + switch(result) + { + case Comm::Success: + ss << " Complete (" << time << "s)\n"; + setBootloadBusy(false); + break; + case Comm::NotConnected: + ss << " Failed. Device not connected.\n"; + setBootloadBusy(false); + break; + case Comm::Fail: + ss << " Failed.\n"; + setBootloadBusy(false); + break; + case Comm::IncorrectCommand: + ss << " Failed. Unable to communicate with device.\n"; + setBootloadBusy(false); + break; + case Comm::Timeout: + ss << " Timed out waiting for device (" << time << "s)\n"; + setBootloadBusy(false); + break; + default: + break; + } + + ui->plainTextEdit->appendPlainText(msg); +} + +void MainWindow::on_action_Verify_Device_triggered() +{ + future = QtConcurrent::run(this, &MainWindow::VerifyDevice); +} + + +//Routine that verifies the contents of the non-voltaile memory regions in the device, after an erase/programming cycle. +//This function requests the memory contents of the device, then compares it against the parsed .hex file data to make sure +//The locations that got programmed properly match. +void MainWindow::VerifyDevice() +{ + Comm::ErrorCode result; + DeviceData::MemoryRange deviceRange, hexRange; + QTime elapsed; + unsigned int i, j; + unsigned int arrayIndex; + bool failureDetected = false; + unsigned char flashData[MAX_ERASE_BLOCK_SIZE]; + unsigned char hexEraseBlockData[MAX_ERASE_BLOCK_SIZE]; + uint32_t startOfEraseBlock; + uint32_t errorAddress = 0; + uint16_t expectedResult = 0; + uint16_t actualResult = 0; + + //Initialize an erase block sized buffer with 0xFF. + //Used later for post SIGN_FLASH verify operation. + memset(&hexEraseBlockData[0], 0xFF, MAX_ERASE_BLOCK_SIZE); + + emit IoWithDeviceStarted("Verifying Device..."); + elapsed.start(); + foreach(deviceRange, deviceData->ranges) + { + if(writeFlash && (deviceRange.type == PROGRAM_MEMORY)) + { + //elapsed.start(); + //emit IoWithDeviceStarted("Verifying Device's Program Memory..."); + + result = comm->GetData(deviceRange.start, + device->bytesPerPacket, + device->bytesPerAddressFLASH, + device->bytesPerWordFLASH, + deviceRange.end, + deviceRange.pDataBuffer); + + if(result != Comm::Success) + { + failureDetected = true; + qWarning("Error reading device."); + //emit IoWithDeviceCompleted("Verifying Device's Program Memory", result, ((double)elapsed.elapsed()) / 1000); + } + + //Search through all of the programmable memory regions from the parsed .hex file data. + //For each of the programmable memory regions found, if the region also overlaps a region + //that was included in the device programmed area (which just got read back with GetData()), + //then verify both the parsed hex contents and read back data match. + foreach(hexRange, hexData->ranges) + { + if(deviceRange.start == hexRange.start) + { + //For this entire programmable memory address range, check to see if the data read from the device exactly + //matches what was in the hex file. + for(i = deviceRange.start; i < deviceRange.end; i++) + { + //For each byte of each device address (1 on PIC18, 2 on PIC24, since flash memory is 16-bit WORD array) + for(j = 0; j < device->bytesPerAddressFLASH; j++) + { + //Check if the device response data matches the data we parsed from the original input .hex file. + if(deviceRange.pDataBuffer[((i - deviceRange.start) * device->bytesPerAddressFLASH)+j] != hexRange.pDataBuffer[((i - deviceRange.start) * device->bytesPerAddressFLASH)+j]) + { + //A mismatch was detected. + + //Check if this is a PIC24 device and we are looking at the "phantom byte" + //(upper byte [j = 1] of odd address [i%2 == 1] 16-bit flash words). If the hex data doesn't match + //the device (which should be = 0x00 for these locations), this isn't a real verify + //failure, since value is a don't care anyway. This could occur if the hex file imported + //doesn't contain all locations, and we "filled" the region with pure 0xFFFFFFFF, instead of 0x00FFFFFF + //when parsing the hex file. + if((device->family == Device::PIC24) && ((i % 2) == 1) && (j == 1)) + { + //Not a real verify failure, phantom byte is unimplemented and is a don't care. + } + else + { + //If the data wasn't a match, and this wasn't a PIC24 phantom byte, then if we get + //here this means we found a true verify failure. + failureDetected = true; + if(device->family == Device::PIC24) + { + qWarning("Device: 0x%x Hex: 0x%x", *(uint16_t*)&deviceRange.pDataBuffer[((i - deviceRange.start) * device->bytesPerAddressFLASH)+j], *(uint16_t*)&hexRange.pDataBuffer[((i - deviceRange.start) * device->bytesPerAddressFLASH)+j]); + } + else + { + qWarning("Device: 0x%x Hex: 0x%x", deviceRange.pDataBuffer[((i - deviceRange.start) * device->bytesPerAddressFLASH)+j], hexRange.pDataBuffer[((i - deviceRange.start) * device->bytesPerAddressFLASH)+j]); + } + qWarning("Failed verify at address 0x%x", i); + emit IoWithDeviceCompleted("Verify", Comm::Fail, ((double)elapsed.elapsed()) / 1000); + return; + } + }//if(deviceRange.pDataBuffer[((i - deviceRange.start) * device->bytesPerAddressFLASH)+j] != hexRange.pDataBuffer[((i - deviceRange.start) * device->bytesPerAddressFLASH)+j]) + }//for(j = 0; j < device->bytesPerAddressFLASH; j++) + }//for(i = deviceRange.start; i < deviceRange.end; i++) + }//if(deviceRange.start == hexRange.start) + }//foreach(hexRange, hexData->ranges) + //emit IoWithDeviceCompleted("Verify", Comm::Success, ((double)elapsed.elapsed()) / 1000); + }//if(writeFlash && (deviceRange.type == PROGRAM_MEMORY)) + else if(writeEeprom && (deviceRange.type == EEPROM_MEMORY)) + { + //elapsed.start(); + //emit IoWithDeviceStarted("Verifying Device's EEPROM Memory..."); + + result = comm->GetData(deviceRange.start, + device->bytesPerPacket, + device->bytesPerAddressEEPROM, + device->bytesPerWordEEPROM, + deviceRange.end, + deviceRange.pDataBuffer); + + if(result != Comm::Success) + { + failureDetected = true; + qWarning("Error reading device."); + //emit IoWithDeviceCompleted("Verifying Device's EEPROM Memory", result, ((double)elapsed.elapsed()) / 1000); + } + + + //Search through all of the programmable memory regions from the parsed .hex file data. + //For each of the programmable memory regions found, if the region also overlaps a region + //that was included in the device programmed area (which just got read back with GetData()), + //then verify both the parsed hex contents and read back data match. + foreach(hexRange, hexData->ranges) + { + if(deviceRange.start == hexRange.start) + { + //For this entire programmable memory address range, check to see if the data read from the device exactly + //matches what was in the hex file. + for(i = deviceRange.start; i < deviceRange.end; i++) + { + //For each byte of each device address (only 1 for EEPROM byte arrays, presumably 2 for EEPROM WORD arrays) + for(j = 0; j < device->bytesPerAddressEEPROM; j++) + { + //Check if the device response data matches the data we parsed from the original input .hex file. + if(deviceRange.pDataBuffer[((i - deviceRange.start) * device->bytesPerAddressEEPROM)+j] != hexRange.pDataBuffer[((i - deviceRange.start) * device->bytesPerAddressEEPROM)+j]) + { + //A mismatch was detected. + failureDetected = true; + qWarning("Device: 0x%x Hex: 0x%x", deviceRange.pDataBuffer[((i - deviceRange.start) * device->bytesPerAddressFLASH)+j], hexRange.pDataBuffer[((i - deviceRange.start) * device->bytesPerAddressFLASH)+j]); + qWarning("Failed verify at address 0x%x", i); + emit IoWithDeviceCompleted("Verify EEPROM Memory", Comm::Fail, ((double)elapsed.elapsed()) / 1000); + return; + } + } + } + } + }//foreach(hexRange, hexData->ranges) + //emit IoWithDeviceCompleted("Verifying", Comm::Success, ((double)elapsed.elapsed()) / 1000); + }//else if(writeEeprom && (deviceRange.type == EEPROM_MEMORY)) + else if(writeConfig && (deviceRange.type == CONFIG_MEMORY)) + { + //elapsed.start(); + //emit IoWithDeviceStarted("Verifying Device's Config Words..."); + + result = comm->GetData(deviceRange.start, + device->bytesPerPacket, + device->bytesPerAddressConfig, + device->bytesPerWordConfig, + deviceRange.end, + deviceRange.pDataBuffer); + + if(result != Comm::Success) + { + failureDetected = true; + qWarning("Error reading device."); + //emit IoWithDeviceCompleted("Verifying Device's Config Words", result, ((double)elapsed.elapsed()) / 1000); + } + + //Search through all of the programmable memory regions from the parsed .hex file data. + //For each of the programmable memory regions found, if the region also overlaps a region + //that was included in the device programmed area (which just got read back with GetData()), + //then verify both the parsed hex contents and read back data match. + foreach(hexRange, hexData->ranges) + { + if(deviceRange.start == hexRange.start) + { + //For this entire programmable memory address range, check to see if the data read from the device exactly + //matches what was in the hex file. + for(i = deviceRange.start; i < deviceRange.end; i++) + { + //For each byte of each device address (1 on PIC18, 2 on PIC24, since flash memory is 16-bit WORD array) + for(j = 0; j < device->bytesPerAddressConfig; j++) + { + //Compute an index into the device and hex data arrays, based on the current i and j values. + arrayIndex = ((i - deviceRange.start) * device->bytesPerAddressConfig)+j; + + //Check if the device response data matches the data we parsed from the original input .hex file. + if(deviceRange.pDataBuffer[arrayIndex] != hexRange.pDataBuffer[arrayIndex]) + { + //A mismatch was detected. Perform additional checks to make sure it was a real/unexpected verify failure. + + //Check if this is a PIC24 device and we are looking at the "phantom byte" + //(upper byte [j = 1] of odd address [i%2 == 1] 16-bit flash words). If the hex data doesn't match + //the device (which should be = 0x00 for these locations), this isn't a real verify + //failure, since value is a don't care anyway. This could occur if the hex file imported + //doesn't contain all locations, and we "filled" the region with pure 0xFFFFFFFF, instead of 0x00FFFFFF + //when parsing the hex file. + if((device->family == Device::PIC24) && ((i % 2) == 1) && (j == 1)) + { + //Not a real verify failure, phantom byte is unimplemented and is a don't care. + }//Make further special checks for PIC18 non-J devices + else if((device->family == Device::PIC18) && (deviceRange.start == 0x300000) && ((i == 0x300004) || (i == 0x300007))) + { + //The "CONFIG3L" and "CONFIG4H" locations (0x300004 and 0x300007) on PIC18 non-J USB devices + //are unimplemented and should be masked out from the verify operation. + } + else + { + //If the data wasn't a match, and this wasn't a PIC24 phantom byte, then if we get + //here this means we found a true verify failure. + failureDetected = true; + if(device->family == Device::PIC24) + { + qWarning("Device: 0x%x Hex: 0x%x", *(uint16_t*)&deviceRange.pDataBuffer[((i - deviceRange.start) * device->bytesPerAddressConfig)+j], *(uint16_t*)&hexRange.pDataBuffer[((i - deviceRange.start) * device->bytesPerAddressConfig)+j]); + } + else + { + qWarning("Device: 0x%x Hex: 0x%x", deviceRange.pDataBuffer[((i - deviceRange.start) * device->bytesPerAddressConfig)+j], hexRange.pDataBuffer[((i - deviceRange.start) * device->bytesPerAddressConfig)+j]); + } + qWarning("Failed verify at address 0x%x", i); + emit IoWithDeviceCompleted("Verify Config Bit Memory", Comm::Fail, ((double)elapsed.elapsed()) / 1000); + return; + } + } + } + }//for(i = deviceRange.start; i < deviceRange.end; i++) + }//if(deviceRange.start == hexRange.start) + }//foreach(hexRange, hexData->ranges) + //emit IoWithDeviceCompleted("Verifying", Comm::Success, ((double)elapsed.elapsed()) / 1000); + }//else if(writeConfig && (deviceRange.type == CONFIG_MEMORY)) + else + { + continue; + } + }//foreach(deviceRange, deviceData->ranges) + + if(failureDetected == false) + { + //Successfully verified all regions without error. + //If this is a v1.01 or later device, we now need to issue the SIGN_FLASH + //command, and then re-verify the first erase page worth of flash memory + //(but with the exclusion of the signature WORD address from the verify, + //since the bootloader firmware will have changed it to the new/magic + //value (probably 0x600D, or "good" in leet speak). + if(deviceFirmwareIsAtLeast101 == true) + { + comm->SignFlash(); + + qDebug("Expected Signature Address: 0x%x", extendedBootInfo.PIC18.signatureAddress); + qDebug("Expected Signature Value: 0x%x", extendedBootInfo.PIC18.signatureValue); + + + //Now re-verify the first erase page of flash memory. + if(device->family == Device::PIC18) + { + startOfEraseBlock = extendedBootInfo.PIC18.signatureAddress - (extendedBootInfo.PIC18.signatureAddress % extendedBootInfo.PIC18.erasePageSize); + result = comm->GetData(startOfEraseBlock, + device->bytesPerPacket, + device->bytesPerAddressFLASH, + device->bytesPerWordFLASH, + (startOfEraseBlock + extendedBootInfo.PIC18.erasePageSize), + &flashData[0]); + if(result != Comm::Success) + { + failureDetected = true; + qWarning("Error reading, post signing, flash data block."); + } + + //Search through all of the programmable memory regions from the parsed .hex file data. + //For each of the programmable memory regions found, if the region also overlaps a region + //that is part of the erase block, copy out bytes into the hexEraseBlockData[] buffer, + //for re-verification. + foreach(hexRange, hexData->ranges) + { + //Check if any portion of the range is within the erase block of interest in the device. + if((hexRange.start <= startOfEraseBlock) && (hexRange.end > startOfEraseBlock)) + { + unsigned int rangeSize = hexRange.end - hexRange.start; + unsigned int address = hexRange.start; + unsigned int k = 0; + + //Check every byte in the hex file range, to see if it is inside the erase block of interest + for(i = 0; i < rangeSize; i++) + { + //Check if the current byte we are looking at is inside the erase block of interst + if(((address+i) >= startOfEraseBlock) && ((address+i) < (startOfEraseBlock + extendedBootInfo.PIC18.erasePageSize))) + { + //The byte is in the erase block of interst. Copy it out into a new buffer. + hexEraseBlockData[k] = *(hexRange.pDataBuffer + i); + //Check if this is a signature byte. If so, replace the value in the buffer + //with the post-signing expected signature value, since this is now the expected + //value from the device, rather than the value from the hex file... + if((address+i) == extendedBootInfo.PIC18.signatureAddress) + { + hexEraseBlockData[k] = (unsigned char)extendedBootInfo.PIC18.signatureValue; //Write LSB of signature into buffer + } + if((address+i) == (extendedBootInfo.PIC18.signatureAddress + 1)) + { + hexEraseBlockData[k] = (unsigned char)(extendedBootInfo.PIC18.signatureValue >> 8); //Write MSB into buffer + } + k++; + } + if((k >= extendedBootInfo.PIC18.erasePageSize) || (k >= sizeof(hexEraseBlockData))) + break; + }//for(i = 0; i < rangeSize; i++) + } + }//foreach(hexRange, hexData->ranges) + + //We now have both the hex data and the post signing flash erase block data + //in two RAM buffers. Compare them to each other to perform post-signing + //verify. + for(i = 0; i < extendedBootInfo.PIC18.erasePageSize; i++) + { + if(flashData[i] != hexEraseBlockData[i]) + { + failureDetected = true; + qWarning("Post signing verify failure."); + EraseDevice(); //Send an erase command, to forcibly + //remove the signature (which might be valid), since + //there was a verify error and we can't trust the application + //firmware image integrity. This ensures the device jumps + //back into bootloader mode always. + + errorAddress = startOfEraseBlock + i; + expectedResult = hexEraseBlockData[i] + ((uint32_t)hexEraseBlockData[i+1] << 8); + //expectedResult = hexEraseBlockData[i]; + actualResult = flashData[i] + ((uint32_t)flashData[i+1] << 8); + //actualResult = flashData[i]; + + break; + } + }//for(i = 0; i < extendedBootInfo.PIC18.erasePageSize; i++) + }//if(device->family == Device::PIC18) + + }//if(deviceFirmwareIsAtLeast101 == true) + + }//if(failureDetected == false) + + if(failureDetected == true) + { + qDebug("Verify failed at address: 0x%x", errorAddress); + qDebug("Expected result: 0x%x", expectedResult); + qDebug("Actual result: 0x%x", actualResult); + emit AppendString("Operation aborted due to error encountered during verify operation."); + emit AppendString("Please try the erase/program/verify sequence again."); + emit AppendString("If repeated failures are encountered, this may indicate the flash"); + emit AppendString("memory has worn out, that the device has been damaged, or that"); + emit AppendString("there is some other unidentified problem."); + + emit IoWithDeviceCompleted("Verify", Comm::Fail, ((double)elapsed.elapsed()) / 1000); + + //Set flags only used when the program was invoked from the console with extra parameters for auto-program and close behavior. + consoleEraseProgramVerifiedOkay = false; + consoleInvokedEraseProgramVerifyComplete = true; + } + else + { + emit IoWithDeviceCompleted("Verify", Comm::Success, ((double)elapsed.elapsed()) / 1000); + emit AppendString("Erase/Program/Verify sequence completed successfully."); + emit AppendString("You may now unplug or reset the device."); + + //Set flags only used when the program was invoked from the console with extra parameters for auto-program and close behavior. + consoleEraseProgramVerifiedOkay = true; + consoleInvokedEraseProgramVerifyComplete = true; + } + + emit SetProgressBar(100); //Set progress bar to 100% +}//void MainWindow::VerifyDevice() + + +//Gets called when the user clicks to program button in the GUI. +void MainWindow::on_actionWrite_Device_triggered() +{ + future = QtConcurrent::run(this, &MainWindow::WriteDevice); + ui->plainTextEdit->clear(); + ui->plainTextEdit->appendPlainText("Starting Erase/Program/Verify Sequence."); + ui->plainTextEdit->appendPlainText("Do not unplug device or disconnect power until the operation is fully complete."); + ui->plainTextEdit->appendPlainText(" "); +} + + +//This thread programs previously parsed .hex file data into the device's programmable memory regions. +void MainWindow::WriteDevice(void) +{ + QTime elapsed; + Comm::ErrorCode result; + DeviceData::MemoryRange hexRange; + + //Update the progress bar so the user knows things are happening. + emit SetProgressBar(3); + //First erase the entire device. + EraseDevice(); + + //Now begin re-programming each section based on the info we obtained when + //we parsed the user's .hex file. + + emit IoWithDeviceStarted("Writing Device..."); + elapsed.start(); + foreach(hexRange, hexData->ranges) + { + if(writeFlash && (hexRange.type == PROGRAM_MEMORY)) + { + //emit IoWithDeviceStarted("Writing Device Program Memory..."); + //elapsed.start(); + + result = comm->Program(hexRange.start, + device->bytesPerPacket, + device->bytesPerAddressFLASH, + device->bytesPerWordFLASH, + device->family, + hexRange.end, + hexRange.pDataBuffer); + } + else if(writeEeprom && (hexRange.type == EEPROM_MEMORY)) + { + //emit IoWithDeviceStarted("Writing Device EEPROM Memory..."); + //elapsed.start(); + + result = comm->Program(hexRange.start, + device->bytesPerPacket, + device->bytesPerAddressEEPROM, + device->bytesPerWordEEPROM, + device->family, + hexRange.end, + hexRange.pDataBuffer); + } + else if(writeConfig && (hexRange.type == CONFIG_MEMORY)) + { + //emit IoWithDeviceStarted("Writing Device Config Words..."); + //elapsed.start(); + + result = comm->Program(hexRange.start, + device->bytesPerPacket, + device->bytesPerAddressConfig, + device->bytesPerWordConfig, + device->family, + hexRange.end, + hexRange.pDataBuffer); + } + else + { + continue; + } + + //emit IoWithDeviceCompleted("Writing", result, ((double)elapsed.elapsed()) / 1000); + + if(result != Comm::Success) + { + qWarning("Programming failed"); + return; + } + } + + emit IoWithDeviceCompleted("Write", result, ((double)elapsed.elapsed()) / 1000); + + VerifyDevice(); +} + +void MainWindow::on_actionBlank_Check_triggered() +{ + future = QtConcurrent::run(this, &MainWindow::BlankCheckDevice); +} + +void MainWindow::BlankCheckDevice(void) +{ + QTime elapsed; + Comm::ErrorCode result; + DeviceData::MemoryRange deviceRange; + + elapsed.start(); + + foreach(deviceRange, deviceData->ranges) + { + if(writeFlash && (deviceRange.type == PROGRAM_MEMORY)) + { + emit IoWithDeviceStarted("Blank Checking Device's Program Memory..."); + + result = comm->GetData(deviceRange.start, + device->bytesPerPacket, + device->bytesPerAddressFLASH, + device->bytesPerWordFLASH, + deviceRange.end, + deviceRange.pDataBuffer); + + + if(result != Comm::Success) + { + qWarning("Blank Check failed"); + emit IoWithDeviceCompleted("Blank Checking Program Memory", result, ((double)elapsed.elapsed()) / 1000); + return; + } + + for(unsigned int i = 0; i < ((deviceRange.end - deviceRange.start) * device->bytesPerAddressFLASH); i++) + { + if((deviceRange.pDataBuffer[i] != 0xFF) && !((device->family == Device::PIC24) && ((i % 4) == 3))) + { + qWarning("Failed blank check at address 0x%x", deviceRange.start + i); + qWarning("The value was 0x%x", deviceRange.pDataBuffer[i]); + emit IoWithDeviceCompleted("Blank Check", Comm::Fail, ((double)elapsed.elapsed()) / 1000); + return; + } + } + emit IoWithDeviceCompleted("Blank Checking Program Memory", Comm::Success, ((double)elapsed.elapsed()) / 1000); + } + else if(writeEeprom && deviceRange.type == EEPROM_MEMORY) + { + emit IoWithDeviceStarted("Blank Checking Device's EEPROM Memory..."); + + result = comm->GetData(deviceRange.start, + device->bytesPerPacket, + device->bytesPerAddressEEPROM, + device->bytesPerWordEEPROM, + deviceRange.end, + deviceRange.pDataBuffer); + + + if(result != Comm::Success) + { + qWarning("Blank Check failed"); + emit IoWithDeviceCompleted("Blank Checking EEPROM Memory", result, ((double)elapsed.elapsed()) / 1000); + return; + } + + for(unsigned int i = 0; i < ((deviceRange.end - deviceRange.start) * device->bytesPerWordEEPROM); i++) + { + if(deviceRange.pDataBuffer[i] != 0xFF) + { + qWarning("Failed blank check at address 0x%x + 0x%x", deviceRange.start, i); + qWarning("The value was 0x%x", deviceRange.pDataBuffer[i]); + emit IoWithDeviceCompleted("Blank Check", Comm::Fail, ((double)elapsed.elapsed()) / 1000); + return; + } + } + emit IoWithDeviceCompleted("Blank Checking EEPROM Memory", Comm::Success, ((double)elapsed.elapsed()) / 1000); + } + else + { + continue; + } + } +} + +void MainWindow::on_actionErase_Device_triggered() +{ + future = QtConcurrent::run(this, &MainWindow::EraseDevice); +} + +void MainWindow::EraseDevice(void) +{ + QTime elapsed; + Comm::ErrorCode result; + Comm::BootInfo bootInfo; + + + //if(writeFlash || writeEeprom) + { + emit IoWithDeviceStarted("Erasing Device... (no status update until complete, may take several seconds)"); + elapsed.start(); + + result = comm->Erase(); + if(result != Comm::Success) + { + emit IoWithDeviceCompleted("Erase", result, ((double)elapsed.elapsed()) / 1000); + return; + } + + result = comm->ReadBootloaderInfo(&bootInfo); + + emit IoWithDeviceCompleted("Erase", result, ((double)elapsed.elapsed()) / 1000); + } +} + +//Executes when the user clicks the open hex file button on the main form. +void MainWindow::on_actionOpen_triggered() +{ + QString msg, newFileName; + QTextStream stream(&msg); + + //Create an open file dialog box, so the user can select a .hex file. + newFileName = + QFileDialog::getOpenFileName(this, "Open Hex File", fileName, "Hex Files (*.hex *.ehx)"); + + if(newFileName.isEmpty()) + { + return; + } + + LoadFile(newFileName); +} + +void MainWindow::LoadFile(QString newFileName) +{ + QString msg; + QTextStream stream(&msg); + QFileInfo nfi(newFileName); + + QApplication::setOverrideCursor(Qt::BusyCursor); + + HexImporter import; + HexImporter::ErrorCode result; + Comm::ErrorCode commResultCode; + int i; + + hexData->ranges.clear(); + + //Print some debug info to the debug window. + qDebug(QString("Total Programmable Regions Reported by Device: " + QString::number(deviceData->ranges.count(), 10)).toLatin1()); + + //First duplicate the deviceData programmable region list and + //allocate some RAM buffers to hold the hex data that we are about to import. + for(i = 0; i < deviceData->ranges.count(); i++) + { + //Allocate some RAM for the hex file data we are about to import. + //Initialize all bytes of the buffer to 0xFF, the default unprogrammed memory value, + //which is also the "assumed" value, if a value is missing inside the .hex file, but + //is still included in a programmable memory region. + + DeviceData::MemoryRange HexBuffer = deviceData->ranges[i]; + + //Allocate a new RAM buffer to hold the .hex file contents associated with the programmable region in the microcontroller. + HexBuffer.pDataBuffer = new unsigned char[deviceData->ranges[i].dataBufferLength]; + //Initialize the assumed .hex contents buffer to 0xFF, or the values read from the microcontroller initally. + //Any contents found in the .hex will subsequently overwrite these values with the new values from the .hex file. + memset(HexBuffer.pDataBuffer, 0xFF, HexBuffer.dataBufferLength); + + if(deviceData->ranges[i].type == CONFIG_MEMORY) + { + //Initialize our local RAM buffer (intended for storing .hex file read in data) with + //values that we obtained by reading from the device. + //When the .hex file is opened/parsed, the values that overlap with what we read from + //the device, but also existin the .hex file, will get overwritten + //with the values from the hex file. However, values that are not in the .hex file, will + //remain the same as what we read from the device initially, prior to an erase/program/verify + //operation. This is done, since it is possible to have a .hex file that is incomplete, and + //only includes some config bit values, but not others (which may still be in the silicon, or could be an unimplemented location). + //In these cases, in these special locations, during the erase/program/verify, we want to program into the device + //the same values that already exist in the silicon, at these extra locations. + if(deviceData->ranges[i].pDataBuffer != 0) + { + unsigned int copySize; + copySize = deviceData->ranges[i].dataBufferLength; + if(HexBuffer.dataBufferLength < copySize) + { + copySize = HexBuffer.dataBufferLength; + } + memcpy(HexBuffer.pDataBuffer, deviceData->ranges[i].pDataBuffer, copySize); + } + //comm->GetData(bootInfo.memoryRegions[i].address, bootInfo.bytesPerPacket, device->bytesPerAddressConfig, device->bytesPerWordConfig, bootInfo.memoryRegions[i].address + (range.dataBufferLength / device->bytesPerAddressConfig), &range.pDataBuffer[0]); + } + + + hexData->ranges.append(HexBuffer); + + //Print info regarding the programmable memory region to the debug window. + qDebug(QString("Device Programmable Region: [" + QString::number(HexBuffer.start, 16).toUpper() + " - " + + QString::number(HexBuffer.end, 16).toUpper() +")").toLatin1()); + } + + //Import the hex file data into the hexData->ranges[].pDataBuffer buffers. + result = import.ImportHexFile(newFileName, hexData, device); + //Based on the result of the hex file import operation, decide how to proceed. + switch(result) + { + case HexImporter::Success: + break; + + case HexImporter::CouldNotOpenFile: + QApplication::restoreOverrideCursor(); + stream << "Error: Could not open file " << nfi.fileName() << "\n"; + ui->plainTextEdit->appendPlainText(msg); + return; + + case HexImporter::NoneInRange: + QApplication::restoreOverrideCursor(); + stream << "No address within range in file: " << nfi.fileName() << ". Verify the correct firmware image was specified and is designed for your device.\n"; + ui->plainTextEdit->appendPlainText(msg); + return; + + case HexImporter::ErrorInHexFile: + QApplication::restoreOverrideCursor(); + stream << "Error in hex file. Please make sure the firmware image supplied was designed for the device to be programmed. \n"; + ui->plainTextEdit->appendPlainText(msg); + return; + case HexImporter::InsufficientMemory: + QApplication::restoreOverrideCursor(); + stream << "Memory allocation failed. Please close other applications to free up system RAM and try again. \n"; + ui->plainTextEdit->appendPlainText(msg); + return; + + default: + QApplication::restoreOverrideCursor(); + stream << "Failed to import: " << result << "\n"; + ui->plainTextEdit->appendPlainText(msg); + return; + } + + //Check if the user has imported a .hex file that doesn't contain config bits in it, + //even though the user is planning on re-programming the config bits section. + if(writeConfig && (import.hasConfigBits == false) && device->hasConfig()) + { + //The user had config bit reprogramming selected, but the hex file opened didn't have config bit + //data in it. We should automatically prevent config bit programming, to avoid leaving the device + //in a broken state following the programming cycle. + commResultCode = comm->LockUnlockConfig(true); //Lock the config bits. + if(commResultCode != Comm::Success) + { + ui->plainTextEdit->appendPlainText("Unexpected internal error encountered. Recommend restarting the application to avoid ""bricking"" the device.\n"); + } + + QMessageBox::warning(this, "Warning!", "This HEX file does not contain config bit information.\n\nAutomatically disabling config bit reprogramming to avoid leaving the device in a state that could prevent further bootloading.", QMessageBox::AcceptRole, QMessageBox::AcceptRole); + writeConfig = false; + } + + fileName = newFileName; + watchFileName = newFileName; + + QSettings settings; + settings.beginGroup("MainWindow"); + + QStringList files = settings.value("recentFileList").toStringList(); + files.removeAll(fileName); + files.prepend(fileName); + while(files.size() > MAX_RECENT_FILES) + { + files.removeLast(); + } + settings.setValue("recentFileList", files); + UpdateRecentFileList(); + + stream.setIntegerBase(10); + + msg.clear(); + QFileInfo fi(fileName); + QString name = fi.fileName(); + stream << "Opened: " << name << "\n"; + ui->plainTextEdit->appendPlainText(msg); + hexOpen = true; + setBootloadEnabled(true); + QApplication::restoreOverrideCursor(); + + return; +} + +void MainWindow::openRecentFile(void) +{ + QAction *action = qobject_cast(sender()); + if (action) + { + LoadFile(action->data().toString()); + } +} + +void MainWindow::UpdateRecentFileList(void) +{ + QSettings settings; + settings.beginGroup("MainWindow"); + QStringList files; + + files = settings.value("recentFileList").toStringList(); + + int recentFileCount = qMin(files.size(), MAX_RECENT_FILES); + QString text; + int i; + + for(i = 0; i < recentFileCount; i++) + { + text = tr("&%1 %2").arg(i + 1).arg(QFileInfo(files[i]).fileName()); + + recentFiles[i]->setText(text); + recentFiles[i]->setData(files[i]); + recentFiles[i]->setVisible(comm->isConnected()); + } + + for(; i < MAX_RECENT_FILES; i++) + { + recentFiles[i]->setVisible(false); + } +} + +void MainWindow::on_action_About_triggered() +{ + QString msg; + QTextStream stream(&msg); + + stream << "USB HID Bootloader v" << VERSION << "\n"; + stream << "Copyright " << (char)Qt::Key_copyright << " 2011-2013, Microchip Technology Inc.\n\n"; + + stream << "Microchip licenses this software to you solely for use with\n"; + stream << "Microchip products. The software is owned by Microchip and\n"; + stream << "its licensors, and is protected under applicable copyright\n"; + stream << "laws. All rights reserved.\n\n"; + + stream << "SOFTWARE IS PROVIDED \"AS IS.\" MICROCHIP EXPRESSLY\n"; + stream << "DISCLAIMS ANY WARRANTY OF ANY KIND, WHETHER EXPRESS\n"; + stream << "OR IMPLIED, INCLUDING BUT NOT LIMITED TO, THE IMPLIED\n"; + stream << "WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\n"; + stream << "PARTICULAR PURPOSE, OR NON-INFRINGEMENT. IN NO EVENT\n"; + stream << "SHALL MICROCHIP BE LIABLE FOR ANY INCIDENTAL, SPECIAL,\n"; + stream << "INDIRECT OR CONSEQUENTIAL DAMAGES, LOST PROFITS OR\n"; + stream << "LOST DATA, HARM TO YOUR EQUIPMENT, COST OF\n"; + stream << "PROCUREMENT OF SUBSTITUTE GOODS, TECHNOLOGY OR\n"; + stream << "SERVICES, ANY CLAIMS BY THIRD PARTIES (INCLUDING BUT\n"; + stream << "NOT LIMITED TO ANY DEFENSE THEREOF), ANY CLAIMS FOR\n"; + stream << "INDEMNITY OR CONTRIBUTION, OR OTHER SIMILAR COSTS.\n\n"; + + stream << "To the fullest extent allowed by law, Microchip and its\n"; + stream << "licensors liability shall not exceed the amount of fees, if any,\n"; + stream << "that you have paid directly to Microchip to use this software.\n\n"; + + stream << "MICROCHIP PROVIDES THIS SOFTWARE CONDITIONALLY UPON\n"; + stream << "YOUR ACCEPTANCE OF THESE TERMS."; + + QMessageBox::about(this, "About", msg); +} + +void MainWindow::GetQuery() +{ + QTime totalTime; + Comm::BootInfo bootInfo; + DeviceData::MemoryRange range; + QString connectMsg; + QTextStream ss(&connectMsg); + + + qDebug("Executing GetQuery() command."); + + totalTime.start(); + + if(!comm->isConnected()) + { + qWarning("Query not sent, device not connected"); + return; + } + + //Send the Query command to the device over USB, and check the result status. + switch(comm->ReadBootloaderInfo(&bootInfo)) + { + case Comm::Fail: + case Comm::IncorrectCommand: + ui->plainTextEdit->appendPlainText("Unable to communicate with device\n"); + return; + case Comm::Timeout: + ss << "Operation timed out"; + break; + case Comm::Success: + wasBootloaderMode = true; + ss << "Device Ready"; + deviceLabel.setText("Connected"); + break; + default: + return; + } + + ss << " (" << (double)totalTime.elapsed() / 1000 << "s)\n"; + ui->plainTextEdit->appendPlainText(connectMsg); + deviceData->ranges.clear(); + + //Now start parsing the bootInfo packet to learn more about the device. The bootInfo packet contains + //contains the query response data from the USB device. We will save these values into globabl variables + //so other parts of the application can use the info when deciding how to do things. + device->family = (Device::Families) bootInfo.deviceFamily; + device->bytesPerPacket = bootInfo.bytesPerPacket; + + //Set some processor family specific global variables that will be used elsewhere (ex: during program/verify operations). + switch(device->family) + { + case Device::PIC18: + device->bytesPerWordFLASH = 2; + device->bytesPerAddressFLASH = 1; + break; + case Device::PIC24: + device->bytesPerWordFLASH = 4; + device->bytesPerAddressFLASH = 2; + device->bytesPerWordConfig = 4; + device->bytesPerAddressConfig = 2; + break; + case Device::PIC32: + device->bytesPerWordFLASH = 4; + device->bytesPerAddressFLASH = 1; + break; + case Device::PIC16: + device->bytesPerWordFLASH = 2; + device->bytesPerAddressFLASH = 2; + default: + device->bytesPerWordFLASH = 2; + device->bytesPerAddressFLASH = 1; + break; + } + + //Initialize the deviceData buffers and length variables, with the regions that the firmware claims are + //reprogrammable. We will need this information later, to decide what part(s) of the .hex file we + //should look at/try to program into the device. Data sections in the .hex file that are not included + //in these regions should be ignored. + for(int i = 0; i < MAX_DATA_REGIONS; i++) + { + if(bootInfo.memoryRegions[i].type == END_OF_TYPES_LIST) + { + //Before we quit, check the special versionFlag byte, + //to see if the bootloader firmware is at least version 1.01. + //If it is, then it will support the extended query command. + //If the device is based on v1.00 bootloader firmware, it will have + //loaded the versionFlag location with 0x00, which was a pad byte. + if(bootInfo.versionFlag == BOOTLOADER_V1_01_OR_NEWER_FLAG) + { + deviceFirmwareIsAtLeast101 = true; + qDebug("Device bootloader firmware is v1.01 or newer and supports Extended Query."); + //Now fetch the extended query information packet from the USB firmware. + comm->ReadExtendedQueryInfo(&extendedBootInfo); + qDebug("Device bootloader firmware version is: " + extendedBootInfo.PIC18.bootloaderVersion); + } + else + { + deviceFirmwareIsAtLeast101 = false; + } + break; + } + + //Error check: Check the firmware's reported size to make sure it is sensible. This ensures + //we don't try to allocate ourselves a massive amount of RAM (capable of crashing this PC app) + //if the firmware claimed an improper value. + if(bootInfo.memoryRegions[i].size > MAXIMUM_PROGRAMMABLE_MEMORY_SEGMENT_SIZE) + { + bootInfo.memoryRegions[i].size = MAXIMUM_PROGRAMMABLE_MEMORY_SEGMENT_SIZE; + } + + //Parse the bootInfo response packet and allocate ourselves some RAM to hold the eventual data to program. + if(bootInfo.memoryRegions[i].type == PROGRAM_MEMORY) + { + range.type = PROGRAM_MEMORY; + range.dataBufferLength = bootInfo.memoryRegions[i].size * device->bytesPerAddressFLASH; + range.pDataBuffer = new unsigned char[range.dataBufferLength]; + memset(&range.pDataBuffer[0], 0xFF, range.dataBufferLength); + } + else if(bootInfo.memoryRegions[i].type == EEPROM_MEMORY) + { + range.type = EEPROM_MEMORY; + range.dataBufferLength = bootInfo.memoryRegions[i].size * device->bytesPerAddressEEPROM; + range.pDataBuffer = new unsigned char[range.dataBufferLength]; + memset(&range.pDataBuffer[0], 0xFF, range.dataBufferLength); + } + else if(bootInfo.memoryRegions[i].type == CONFIG_MEMORY) + { + range.type = CONFIG_MEMORY; + range.dataBufferLength = bootInfo.memoryRegions[i].size * device->bytesPerAddressConfig; + range.pDataBuffer = new unsigned char[range.dataBufferLength]; + memset(&range.pDataBuffer[0], 0xFF, range.dataBufferLength); + + //Read in the current config bit values from the device, and initialize our local RAM + //buffer to contain the same values. When the .hex file is opened/parsed, the values + //that overlap with what we read from the device and what is in the .hex file will get overwritten + //with the values from the hex file. However, values that are not in the .hex file, will + //remain the same as what we read from the device initially, prior to an erase/program/verify + //operation. This is done, since it is possible to have a .hex file that is incomplete, and + //only include some config bit values, but not others. In these cases, we want to program into + //the device the same values for these extra locations (not in the .hex, but present in the silicon), + //as were present in the device, prior to doing the erase/program/verify operation. + comm->GetData(bootInfo.memoryRegions[i].address, bootInfo.bytesPerPacket, device->bytesPerAddressConfig, device->bytesPerWordConfig, bootInfo.memoryRegions[i].address + (range.dataBufferLength / device->bytesPerAddressConfig), &range.pDataBuffer[0]); + } + + //Notes regarding range.start and range.end: The range.start is defined as the starting address inside + //the USB device that will get programmed. For example, if the bootloader occupies 0x000-0xFFF flash + //memory addresses (ex: on a PIC18), then the starting bootloader programmable address would typically + //be = 0x1000 (ex: range.start = 0x1000). + //The range.end is defined as the last address that actually gets programmed, plus one, in this programmable + //region. For example, for a 64kB PIC18 microcontroller, the last implemented flash memory address + //is 0xFFFF. If the last 1024 bytes are reserved by the bootloader (since that last page contains the config + //bits for instance), then the bootloader firmware may only allow the last address to be programmed to + //be = 0xFBFF. In this scenario, the range.end value would be = 0xFBFF + 1 = 0xFC00. + //When this application uses the range.end value, it should be aware that the actual address limit of + //range.end does not actually get programmed into the device, but the address just below it does. + //In this example, the programmed region would end up being 0x1000-0xFBFF (even though range.end = 0xFC00). + //The proper code to program this would basically be something like this: + //for(i = range.start; i < range.end; i++) + //{ + // //Insert code here that progams one device address. Note: for PIC18 this will be one byte for flash memory. + // //For PIC24 this is actually 2 bytes, since the flash memory is addressed as a 16-bit word array. + //} + //In the above example, the for() loop exits just before the actual range.end value itself is programmed. + + range.start = bootInfo.memoryRegions[i].address; + range.end = bootInfo.memoryRegions[i].address + bootInfo.memoryRegions[i].size; + //Add the new structure+buffer to the list + deviceData->ranges.append(range); + } + + + //Make sure user has allowed at least one region to be programmed + if(!(writeFlash || writeEeprom || writeConfig)) + { + setBootloadEnabled(false); + ui->action_Settings->setEnabled(true); + } + else + setBootloadEnabled(true); +} + + + + +void MainWindow::on_action_Settings_triggered() +{ + Comm::ErrorCode result; + Settings* dlg = new Settings(this); + + dlg->enableEepromBox(device->hasEeprom()); + + dlg->setWriteFlash(writeFlash); + dlg->setWriteConfig(writeConfig); + dlg->setWriteEeprom(writeEeprom); + + if(dlg->exec() == QDialog::Accepted) + { + writeFlash = dlg->writeFlash; + writeEeprom = dlg->writeEeprom; + + if(!writeConfig && dlg->writeConfig) + { + ui->plainTextEdit->appendPlainText("Disabling Erase button to prevent accidental erasing of the configuration words without reprogramming them\n"); + writeConfig = true; + hexOpen = false; + result = comm->LockUnlockConfig(false); + if(result == Comm::Success) + { + ui->plainTextEdit->appendPlainText("Unlocked Configuration bits successfully\n"); + GetQuery(); + } + } + else if(writeConfig && !dlg->writeConfig) + { + writeConfig = false; + hexOpen = false; + result = comm->LockUnlockConfig(true); + if(result == Comm::Success) + { + ui->plainTextEdit->appendPlainText("Locked Configuration bits successfully\n"); + GetQuery(); + } + } + + if(!(writeFlash || writeEeprom || writeConfig)) + { + setBootloadEnabled(false); + ui->action_Settings->setEnabled(true); + } + else + { + setBootloadEnabled(true); + } + } + + delete dlg; +} + +void MainWindow::on_actionReset_Device_triggered() +{ + if(!comm->isConnected()) + { + failed = -1; + qWarning("Reset not sent, device not connected"); + return; + } + + ui->plainTextEdit->appendPlainText("Resetting..."); + comm->Reset(); +} diff --git a/qt5_src/Bootloader/MainWindow.h b/qt5_src/Bootloader/MainWindow.h new file mode 100644 index 0000000..fbca241 --- /dev/null +++ b/qt5_src/Bootloader/MainWindow.h @@ -0,0 +1,130 @@ +/************************************************************************ +* Copyright (c) 2009-2010, Microchip Technology Inc. +* +* Microchip licenses this software to you solely for use with Microchip +* products. The software is owned by Microchip and its licensors, and +* is protected under applicable copyright laws. All rights reserved. +* +* SOFTWARE IS PROVIDED "AS IS." MICROCHIP EXPRESSLY DISCLAIMS ANY +* WARRANTY OF ANY KIND, WHETHER EXPRESS OR IMPLIED, INCLUDING BUT +* NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS +* FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT. IN NO EVENT SHALL +* MICROCHIP BE LIABLE FOR ANY INCIDENTAL, SPECIAL, INDIRECT OR +* CONSEQUENTIAL DAMAGES, LOST PROFITS OR LOST DATA, HARM TO YOUR +* EQUIPMENT, COST OF PROCUREMENT OF SUBSTITUTE GOODS, TECHNOLOGY +* OR SERVICES, ANY CLAIMS BY THIRD PARTIES (INCLUDING BUT NOT LIMITED +* TO ANY DEFENSE THEREOF), ANY CLAIMS FOR INDEMNITY OR CONTRIBUTION, +* OR OTHER SIMILAR COSTS. +* +* To the fullest extent allowed by law, Microchip and its licensors +* liability shall not exceed the amount of fees, if any, that you +* have paid directly to Microchip to use this software. +* +* MICROCHIP PROVIDES THIS SOFTWARE CONDITIONALLY UPON YOUR ACCEPTANCE +* OF THESE TERMS. +*/ + +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include +#include +#include +#include +#include +#include +#include + +#include "Comm.h" +#include "DeviceData.h" +#include "Device.h" +#include "ImportExportHex.h" + +namespace Ui +{ + class MainWindowClass; +} + +#define MAX_RECENT_FILES 3 + +/*! + * The main Serial Bootloader GUI window. + */ +class MainWindow : public QMainWindow +{ + Q_OBJECT + +public: + MainWindow(QWidget *parent = 0); + ~MainWindow(); + + void GetQuery(void); + void LoadFile(QString fileName); + + void EraseDevice(void); + void BlankCheckDevice(void); + void WriteDevice(void); + void VerifyDevice(void); + + void setBootloadBusy(bool busy); + +signals: + void IoWithDeviceCompleted(QString msg, Comm::ErrorCode, double time); + void IoWithDeviceStarted(QString msg); + void AppendString(QString msg); + void SetProgressBar(int newValue); + +public slots: + void Connection(void); + void openRecentFile(void); + void IoWithDeviceComplete(QString msg, Comm::ErrorCode, double time); + void IoWithDeviceStart(QString msg); + void AppendStringToTextbox(QString msg); + void UpdateProgressBar(int newValue); + +protected: + Comm* comm; + DeviceData* deviceData; + DeviceData* hexData; + Device* device; + + QFuture future; + + QString fileName, watchFileName; + QFileSystemWatcher* fileWatcher; + QTimer *timer; + + bool writeFlash; + bool writeEeprom; + bool writeConfig; + bool eraseDuringWrite; + bool hexOpen; + + void setBootloadEnabled(bool enable); + + void UpdateRecentFileList(void); + + Comm::ErrorCode RemapInterruptVectors(Device* device, DeviceData* deviceData); + +private: + Ui::MainWindowClass *ui; + QLabel deviceLabel; + + int failed; + QAction *recentFiles[MAX_RECENT_FILES]; + + bool wasBootloaderMode; + +private slots: + void on_actionBlank_Check_triggered(); + void on_actionReset_Device_triggered(); + void on_action_Settings_triggered(); + void on_action_Verify_Device_triggered(); + void on_action_About_triggered(); + void on_actionWrite_Device_triggered(); + void on_actionOpen_triggered(); + void on_actionErase_Device_triggered(); + void on_actionExit_triggered(); +}; + +#endif // MAINWINDOW_H diff --git a/qt5_src/Bootloader/MainWindow.ui b/qt5_src/Bootloader/MainWindow.ui new file mode 100644 index 0000000..6ef8836 --- /dev/null +++ b/qt5_src/Bootloader/MainWindow.ui @@ -0,0 +1,243 @@ + + + MainWindowClass + + + + 0 + 0 + 439 + 403 + + + + + 0 + 0 + + + + USB HID Bootloader + + + + :/MainWindow/img/Microchip_logo.Ico:/MainWindow/img/Microchip_logo.Ico + + + + + + + 0 + + + false + + + + + + + Qt::Horizontal + + + + + + + + 350 + 0 + + + + true + + + + + + + + + 0 + 0 + 439 + 19 + + + + + &File + + + + + + + + &Program + + + + + + + + + + + + + &Help + + + + + + + + + + + Display Toolbar + + + false + + + false + + + TopToolBarArea + + + false + + + + + + + + + + + + false + + + + :/MainWindow/img/open.png:/MainWindow/img/open.png + + + &Import Firmware Image + + + Import Firmware Image + + + Ctrl+O + + + + + E&xit + + + Ctrl+Q + + + + + false + + + + :/MainWindow/img/writetqfp.png:/MainWindow/img/writetqfp.png + + + Erase/Program/Verify Device + + + + + false + + + + :/MainWindow/img/erasetqfp.png:/MainWindow/img/erasetqfp.png + + + Erase Device + + + false + + + + + + :/MainWindow/img/help.png:/MainWindow/img/help.png + + + &About + + + + + false + + + + :/MainWindow/img/verify.png:/MainWindow/img/verify.png + + + &Verify Device + + + false + + + + + false + + + &Settings... + + + + + false + + + + :/MainWindow/img/Reset.png:/MainWindow/img/Reset.png + + + Reset Device + + + + + false + + + + :/MainWindow/img/BlankCheck.png:/MainWindow/img/BlankCheck.png + + + Blank Check + + + false + + + + + + + + + diff --git a/qt5_src/Bootloader/Settings.cpp b/qt5_src/Bootloader/Settings.cpp new file mode 100644 index 0000000..71e14b2 --- /dev/null +++ b/qt5_src/Bootloader/Settings.cpp @@ -0,0 +1,120 @@ +/************************************************************************ +* Copyright (c) 2009, Microchip Technology Inc. +* +* Microchip licenses this software to you solely for use with Microchip +* products. The software is owned by Microchip and its licensors, and +* is protected under applicable copyright laws. All rights reserved. +* +* SOFTWARE IS PROVIDED "AS IS." MICROCHIP EXPRESSLY DISCLAIMS ANY +* WARRANTY OF ANY KIND, WHETHER EXPRESS OR IMPLIED, INCLUDING BUT +* NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS +* FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT. IN NO EVENT SHALL +* MICROCHIP BE LIABLE FOR ANY INCIDENTAL, SPECIAL, INDIRECT OR +* CONSEQUENTIAL DAMAGES, LOST PROFITS OR LOST DATA, HARM TO YOUR +* EQUIPMENT, COST OF PROCUREMENT OF SUBSTITUTE GOODS, TECHNOLOGY +* OR SERVICES, ANY CLAIMS BY THIRD PARTIES (INCLUDING BUT NOT LIMITED +* TO ANY DEFENSE THEREOF), ANY CLAIMS FOR INDEMNITY OR CONTRIBUTION, +* OR OTHER SIMILAR COSTS. +* +* To the fullest extent allowed by law, Microchip and its licensors +* liability shall not exceed the amount of fees, if any, that you +* have paid directly to Microchip to use this software. +* +* MICROCHIP PROVIDES THIS SOFTWARE CONDITIONALLY UPON YOUR ACCEPTANCE +* OF THESE TERMS. +* +* Author Date Comment +************************************************************************* +* E. Schlunder 2009/04/29 Initial code. +************************************************************************/ + +#include "Settings.h" +#include "ui_Settings.h" + +#include + +Settings::Settings(QWidget *parent) : QDialog(parent), m_ui(new Ui::Settings) +{ + m_ui->setupUi(this); + + alreadyWarnedConfigBitWrite = false; +} + +Settings::~Settings() +{ + delete m_ui; +} + +void Settings::enableEepromBox(bool Eeprom) +{ + m_ui->EepromCheckBox->setEnabled(Eeprom); + hasEeprom = Eeprom; +} + +void Settings::setWriteFlash(bool value) +{ + writeFlash = value; + m_ui->FlashProgramMemorycheckBox->setChecked(value); +} + +void Settings::setWriteEeprom(bool value) +{ + writeEeprom = value && hasEeprom; + m_ui->EepromCheckBox->setChecked(value && hasEeprom); +} + +void Settings::setWriteConfig(bool value) +{ + writeConfig = value && hasConfig; + bool warnedFlag = alreadyWarnedConfigBitWrite; + alreadyWarnedConfigBitWrite = true; + m_ui->ConfigBitsCheckBox->setChecked(value && hasConfig); + alreadyWarnedConfigBitWrite = warnedFlag; +} + +void Settings::changeEvent(QEvent *e) +{ + switch (e->type()) + { + case QEvent::LanguageChange: + m_ui->retranslateUi(this); + break; + default: + break; + } +} + +void Settings::on_buttonBox_accepted() +{ + writeFlash = m_ui->FlashProgramMemorycheckBox->isChecked(); + writeConfig = m_ui->ConfigBitsCheckBox->isChecked(); + writeEeprom = m_ui->EepromCheckBox->isChecked(); +} + +void Settings::on_ConfigBitsCheckBox_toggled(bool checked) +{ + if((alreadyWarnedConfigBitWrite == false) && checked) + { + QMessageBox msgBox(this); + msgBox.setWindowTitle("Warning!"); + + msgBox.setText("Writing Config Bits is not a safe bootloader operation.\n" \ + "\n" \ + "The device and bootloader may stop functioning if the new config bit settings are\nnot compatible with USB operation.\n" \ + "When the bootloader becomes inoperable, restoring the device will not be possible\n" \ + "without dedicated external chip programming tools.\n" \ + "\n" \ + "Are you sure you wish to enable the \"Write Config Bits\" option?"); + + msgBox.setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel); + msgBox.setDefaultButton(QMessageBox::Cancel); + int result = msgBox.exec(); + if(result != QMessageBox::Ok) + { + m_ui->ConfigBitsCheckBox->setChecked(false); + return; + } + + alreadyWarnedConfigBitWrite = true; + } +} diff --git a/qt5_src/Bootloader/Settings.h b/qt5_src/Bootloader/Settings.h new file mode 100644 index 0000000..a0a23d6 --- /dev/null +++ b/qt5_src/Bootloader/Settings.h @@ -0,0 +1,77 @@ +/************************************************************************ +* Copyright (c) 2009, Microchip Technology Inc. +* +* Microchip licenses this software to you solely for use with Microchip +* products. The software is owned by Microchip and its licensors, and +* is protected under applicable copyright laws. All rights reserved. +* +* SOFTWARE IS PROVIDED "AS IS." MICROCHIP EXPRESSLY DISCLAIMS ANY +* WARRANTY OF ANY KIND, WHETHER EXPRESS OR IMPLIED, INCLUDING BUT +* NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS +* FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT. IN NO EVENT SHALL +* MICROCHIP BE LIABLE FOR ANY INCIDENTAL, SPECIAL, INDIRECT OR +* CONSEQUENTIAL DAMAGES, LOST PROFITS OR LOST DATA, HARM TO YOUR +* EQUIPMENT, COST OF PROCUREMENT OF SUBSTITUTE GOODS, TECHNOLOGY +* OR SERVICES, ANY CLAIMS BY THIRD PARTIES (INCLUDING BUT NOT LIMITED +* TO ANY DEFENSE THEREOF), ANY CLAIMS FOR INDEMNITY OR CONTRIBUTION, +* OR OTHER SIMILAR COSTS. +* +* To the fullest extent allowed by law, Microchip and its licensors +* liability shall not exceed the amount of fees, if any, that you +* have paid directly to Microchip to use this software. +* +* MICROCHIP PROVIDES THIS SOFTWARE CONDITIONALLY UPON YOUR ACCEPTANCE +* OF THESE TERMS. +* +* Author Date Comment +************************************************************************* +* E. Schlunder 2009/04/29 Initial code. +************************************************************************/ + +#ifndef SETTINGS_H +#define SETTINGS_H + +#include +//#include +#include + +namespace Ui { + class Settings; +} + +/*! + * The Settings GUI dialog box for configuring flash, EEPROM, and config bit write regions. + */ +class Settings : public QDialog { + Q_OBJECT + Q_DISABLE_COPY(Settings) +public: + explicit Settings(QWidget *parent = 0); + virtual ~Settings(); + + void enableEepromBox(bool Eeprom); + void setWriteFlash(bool value); + void setWriteEeprom(bool value); + void setWriteConfig(bool value); + + bool writeFlash; + bool writeEeprom; + bool writeConfig; + + bool hasEeprom; + bool hasConfig; + +protected: + virtual void changeEvent(QEvent *e); + void populateBaudRates(QComboBox* comboBox); + +private: + Ui::Settings *m_ui; + bool alreadyWarnedConfigBitWrite; + +private slots: + void on_ConfigBitsCheckBox_toggled(bool checked); + void on_buttonBox_accepted(); +}; + +#endif // SETTINGS_H diff --git a/qt5_src/Bootloader/Settings.ui b/qt5_src/Bootloader/Settings.ui new file mode 100644 index 0000000..30a5508 --- /dev/null +++ b/qt5_src/Bootloader/Settings.ui @@ -0,0 +1,124 @@ + + + Settings + + + + 0 + 0 + 402 + 190 + + + + Settings + + + + :/MainWindow/img/microchip.png:/MainWindow/img/microchip.png + + + + + 30 + 150 + 341 + 32 + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + 20 + 10 + 361 + 131 + + + + Write Options + + + + + 17 + 30 + 201 + 19 + + + + FLASH Program Memory + + + + + + 17 + 60 + 201 + 19 + + + + Config Bits + + + + + + 16 + 90 + 201 + 19 + + + + EEPROM + + + + + + + + buttonBox + accepted() + Settings + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + Settings + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/qt5_src/Bootloader/img/BlankCheck.png b/qt5_src/Bootloader/img/BlankCheck.png new file mode 100644 index 0000000..3d68fa5 Binary files /dev/null and b/qt5_src/Bootloader/img/BlankCheck.png differ diff --git a/qt5_src/Bootloader/img/Microchip_logo.Ico b/qt5_src/Bootloader/img/Microchip_logo.Ico new file mode 100644 index 0000000..9726163 Binary files /dev/null and b/qt5_src/Bootloader/img/Microchip_logo.Ico differ diff --git a/qt5_src/Bootloader/img/Reset.png b/qt5_src/Bootloader/img/Reset.png new file mode 100644 index 0000000..b299b69 Binary files /dev/null and b/qt5_src/Bootloader/img/Reset.png differ diff --git a/qt5_src/Bootloader/img/abort.png b/qt5_src/Bootloader/img/abort.png new file mode 100644 index 0000000..9f88a56 Binary files /dev/null and b/qt5_src/Bootloader/img/abort.png differ diff --git a/qt5_src/Bootloader/img/clear.png b/qt5_src/Bootloader/img/clear.png new file mode 100644 index 0000000..e0cf00e Binary files /dev/null and b/qt5_src/Bootloader/img/clear.png differ diff --git a/qt5_src/Bootloader/img/erasetqfp.png b/qt5_src/Bootloader/img/erasetqfp.png new file mode 100644 index 0000000..ada57a3 Binary files /dev/null and b/qt5_src/Bootloader/img/erasetqfp.png differ diff --git a/qt5_src/Bootloader/img/help.png b/qt5_src/Bootloader/img/help.png new file mode 100644 index 0000000..fdfd654 Binary files /dev/null and b/qt5_src/Bootloader/img/help.png differ diff --git a/qt5_src/Bootloader/img/open.png b/qt5_src/Bootloader/img/open.png new file mode 100644 index 0000000..a08b8d1 Binary files /dev/null and b/qt5_src/Bootloader/img/open.png differ diff --git a/qt5_src/Bootloader/img/readtqfp.png b/qt5_src/Bootloader/img/readtqfp.png new file mode 100644 index 0000000..ed3d82b Binary files /dev/null and b/qt5_src/Bootloader/img/readtqfp.png differ diff --git a/qt5_src/Bootloader/img/stop.png b/qt5_src/Bootloader/img/stop.png new file mode 100644 index 0000000..5b90b85 Binary files /dev/null and b/qt5_src/Bootloader/img/stop.png differ diff --git a/qt5_src/Bootloader/img/verify.png b/qt5_src/Bootloader/img/verify.png new file mode 100644 index 0000000..0904fd8 Binary files /dev/null and b/qt5_src/Bootloader/img/verify.png differ diff --git a/qt5_src/Bootloader/img/writetqfp.png b/qt5_src/Bootloader/img/writetqfp.png new file mode 100644 index 0000000..7df9b2c Binary files /dev/null and b/qt5_src/Bootloader/img/writetqfp.png differ diff --git a/qt5_src/Bootloader/main.cpp b/qt5_src/Bootloader/main.cpp new file mode 100644 index 0000000..d018980 --- /dev/null +++ b/qt5_src/Bootloader/main.cpp @@ -0,0 +1,44 @@ +/************************************************************************ +* Copyright (c) 2009-2010, Microchip Technology Inc. +* +* Microchip licenses this software to you solely for use with Microchip +* products. The software is owned by Microchip and its licensors, and +* is protected under applicable copyright laws. All rights reserved. +* +* SOFTWARE IS PROVIDED "AS IS." MICROCHIP EXPRESSLY DISCLAIMS ANY +* WARRANTY OF ANY KIND, WHETHER EXPRESS OR IMPLIED, INCLUDING BUT +* NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS +* FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT. IN NO EVENT SHALL +* MICROCHIP BE LIABLE FOR ANY INCIDENTAL, SPECIAL, INDIRECT OR +* CONSEQUENTIAL DAMAGES, LOST PROFITS OR LOST DATA, HARM TO YOUR +* EQUIPMENT, COST OF PROCUREMENT OF SUBSTITUTE GOODS, TECHNOLOGY +* OR SERVICES, ANY CLAIMS BY THIRD PARTIES (INCLUDING BUT NOT LIMITED +* TO ANY DEFENSE THEREOF), ANY CLAIMS FOR INDEMNITY OR CONTRIBUTION, +* OR OTHER SIMILAR COSTS. +* +* To the fullest extent allowed by law, Microchip and its licensors +* liability shall not exceed the amount of fees, if any, that you +* have paid directly to Microchip to use this software. +* +* MICROCHIP PROVIDES THIS SOFTWARE CONDITIONALLY UPON YOUR ACCEPTANCE +* OF THESE TERMS. +* +* Author Date Comment +************************************************************************* +* E. Schlunder 2009/04/14 Initial code ported from VB app. +************************************************************************/ + +#include +#include "MainWindow.h" + +int main(int argc, char *argv[]) +{ + QApplication a(argc, argv); + QCoreApplication::setOrganizationName("Microchip"); + QCoreApplication::setOrganizationDomain("microchip.com"); + QCoreApplication::setApplicationName("USB HID Bootloader"); + + MainWindow w; + w.show(); + return a.exec(); +} diff --git a/qt5_src/Bootloader/resources.qrc b/qt5_src/Bootloader/resources.qrc new file mode 100644 index 0000000..399435c --- /dev/null +++ b/qt5_src/Bootloader/resources.qrc @@ -0,0 +1,14 @@ + + + img/abort.png + img/open.png + img/writetqfp.png + img/erasetqfp.png + img/verify.png + img/stop.png + img/BlankCheck.png + img/Reset.png + img/help.png + img/Microchip_logo.Ico + + diff --git a/qt5_src/Bootloader/windows.rc b/qt5_src/Bootloader/windows.rc new file mode 100644 index 0000000..00c1726 --- /dev/null +++ b/qt5_src/Bootloader/windows.rc @@ -0,0 +1 @@ +IDI_ICON1 ICON DISCARDABLE "img/icon.ico" diff --git a/qt5_src/HIDAPI/HIDAPI.pro b/qt5_src/HIDAPI/HIDAPI.pro new file mode 100644 index 0000000..da1959a --- /dev/null +++ b/qt5_src/HIDAPI/HIDAPI.pro @@ -0,0 +1,41 @@ +# ------------------------------------------------- +# Project created by QtCreator 2010-10-29T15:54:07 +# ------------------------------------------------- +QT -= gui +TARGET = HIDAPI +TEMPLATE = lib +CONFIG += staticlib +HEADERS += hidapi.h + +# ------------------------------------------------- +# Add appropriate source file depending on OS +# ------------------------------------------------- +macx: SOURCES += mac/hid.c +unix: !macx: SOURCES += linux/hid-libusb.c +win32: SOURCES += windows/hid.cpp + +# ------------------------------------------------- +# Make sure output directory for object file and +# library is in the correct subdirectory +# ------------------------------------------------- +macx { + DESTDIR = mac + OBJECTS_DIR = mac + MOC_DIR = mac + UI_DIR = mac + RCC_DIR = mac +} +unix: !macx { + DESTDIR = linux + OBJECTS_DIR = linux + MOC_DIR = linux + UI_DIR = linux + RCC_DIR = linux +} +win32 { + DESTDIR = windows + OBJECTS_DIR = windows + MOC_DIR = windows + UI_DIR = windows + RCC_DIR = windows +} diff --git a/qt5_src/HIDAPI/hidapi.h b/qt5_src/HIDAPI/hidapi.h new file mode 100644 index 0000000..7ab9439 --- /dev/null +++ b/qt5_src/HIDAPI/hidapi.h @@ -0,0 +1,339 @@ +/******************************************************* + HIDAPI - Multi-Platform library for + communication with HID devices. + Alan Ott + Signal 11 Software + 8/22/2009 + Copyright 2009, All Rights Reserved. + At the discretion of the user of this library, + this software may be licensed under the terms of the + GNU General Public License v3, a BSD-Style license, or the + original HIDAPI license as outlined in the LICENSE.txt, + LICENSE-gpl3.txt, LICENSE-bsd.txt, and LICENSE-orig.txt + files located at the root of the source distribution. + These files may also be found in the public source + code repository located at: + http://github.com/signal11/hidapi . +********************************************************/ + +/** @file + * @defgroup API hidapi API + */ + +#ifndef HIDAPI_H__ +#define HIDAPI_H__ + +#include + +#ifdef _WIN32 + #define HID_API_EXPORT __declspec(dllexport) + #define HID_API_CALL +#else + #define HID_API_EXPORT /**< API export macro */ + #define HID_API_CALL /**< API call macro */ +#endif + +#define HID_API_EXPORT_CALL HID_API_EXPORT HID_API_CALL /**< API export and call macro*/ + +#ifdef __cplusplus +extern "C" { +#endif + struct hid_device_; + typedef struct hid_device_ hid_device; /**< opaque hidapi structure */ + + /** hidapi info structure */ + struct hid_device_info { + /** Platform-specific device path */ + char *path; + /** Device Vendor ID */ + unsigned short vendor_id; + /** Device Product ID */ + unsigned short product_id; + /** Serial Number */ + wchar_t *serial_number; + /** Device Release Number in binary-coded decimal, + also known as Device Version Number */ + unsigned short release_number; + /** Manufacturer String */ + wchar_t *manufacturer_string; + /** Product string */ + wchar_t *product_string; + /** Usage Page for this Device/Interface + (Windows/Mac only). */ + unsigned short usage_page; + /** Usage for this Device/Interface + (Windows/Mac only).*/ + unsigned short usage; + /** The USB interface which this logical device + represents. Valid on both Linux implementations + in all cases, and valid on the Windows implementation + only if the device contains more than one interface. */ + int interface_number; + + /** Pointer to the next device */ + struct hid_device_info *next; + }; + + + /** @brief Initialize the HIDAPI library. + This function initializes the HIDAPI library. Calling it is not + strictly necessary, as it will be called automatically by + hid_enumerate() and any of the hid_open_*() functions if it is + needed. This function should be called at the beginning of + execution however, if there is a chance of HIDAPI handles + being opened by different threads simultaneously. + + @ingroup API + @returns + This function returns 0 on success and -1 on error. + */ + int HID_API_EXPORT HID_API_CALL hid_init(void); + + /** @brief Finalize the HIDAPI library. + This function frees all of the static data associated with + HIDAPI. It should be called at the end of execution to avoid + memory leaks. + @ingroup API + @returns + This function returns 0 on success and -1 on error. + */ + int HID_API_EXPORT HID_API_CALL hid_exit(void); + + /** @brief Enumerate the HID Devices. + This function returns a linked list of all the HID devices + attached to the system which match vendor_id and product_id. + If @p vendor_id is set to 0 then any vendor matches. + If @p product_id is set to 0 then any product matches. + If @p vendor_id and @p product_id are both set to 0, then + all HID devices will be returned. + @ingroup API + @param vendor_id The Vendor ID (VID) of the types of device + to open. + @param product_id The Product ID (PID) of the types of + device to open. + @returns + This function returns a pointer to a linked list of type + struct #hid_device, containing information about the HID devices + attached to the system, or NULL in the case of failure. Free + this linked list by calling hid_free_enumeration(). + */ + struct hid_device_info HID_API_EXPORT * HID_API_CALL hid_enumerate(unsigned short vendor_id, unsigned short product_id); + + /** @brief Free an enumeration Linked List + This function frees a linked list created by hid_enumerate(). + @ingroup API + @param devs Pointer to a list of struct_device returned from + hid_enumerate(). + */ + void HID_API_EXPORT HID_API_CALL hid_free_enumeration(struct hid_device_info *devs); + + /** @brief Open a HID device using a Vendor ID (VID), Product ID + (PID) and optionally a serial number. + If @p serial_number is NULL, the first device with the + specified VID and PID is opened. + @ingroup API + @param vendor_id The Vendor ID (VID) of the device to open. + @param product_id The Product ID (PID) of the device to open. + @param serial_number The Serial Number of the device to open + (Optionally NULL). + @returns + This function returns a pointer to a #hid_device object on + success or NULL on failure. + */ + HID_API_EXPORT hid_device * HID_API_CALL hid_open(unsigned short vendor_id, unsigned short product_id, const wchar_t *serial_number); + + /** @brief Open a HID device by its path name. + The path name be determined by calling hid_enumerate(), or a + platform-specific path name can be used (eg: /dev/hidraw0 on + Linux). + @ingroup API + @param path The path name of the device to open + @returns + This function returns a pointer to a #hid_device object on + success or NULL on failure. + */ + HID_API_EXPORT hid_device * HID_API_CALL hid_open_path(const char *path); + + /** @brief Write an Output report to a HID device. + The first byte of @p data[] must contain the Report ID. For + devices which only support a single report, this must be set + to 0x0. The remaining bytes contain the report data. Since + the Report ID is mandatory, calls to hid_write() will always + contain one more byte than the report contains. For example, + if a hid report is 16 bytes long, 17 bytes must be passed to + hid_write(), the Report ID (or 0x0, for devices with a + single report), followed by the report data (16 bytes). In + this example, the length passed in would be 17. + hid_write() will send the data on the first OUT endpoint, if + one exists. If it does not, it will send the data through + the Control Endpoint (Endpoint 0). + @ingroup API + @param device A device handle returned from hid_open(). + @param data The data to send, including the report number as + the first byte. + @param length The length in bytes of the data to send. + @returns + This function returns the actual number of bytes written and + -1 on error. + */ + int HID_API_EXPORT HID_API_CALL hid_write(hid_device *device, const unsigned char *data, size_t length); + + /** @brief Read an Input report from a HID device with timeout. + Input reports are returned + to the host through the INTERRUPT IN endpoint. The first byte will + contain the Report number if the device uses numbered reports. + @ingroup API + @param device A device handle returned from hid_open(). + @param data A buffer to put the read data into. + @param length The number of bytes to read. For devices with + multiple reports, make sure to read an extra byte for + the report number. + @param milliseconds timeout in milliseconds or -1 for blocking wait. + @returns + This function returns the actual number of bytes read and + -1 on error. If no packet was available to be read within + the timeout period, this function returns 0. + */ + int HID_API_EXPORT HID_API_CALL hid_read_timeout(hid_device *dev, unsigned char *data, size_t length, int milliseconds); + + /** @brief Read an Input report from a HID device. + Input reports are returned + to the host through the INTERRUPT IN endpoint. The first byte will + contain the Report number if the device uses numbered reports. + @ingroup API + @param device A device handle returned from hid_open(). + @param data A buffer to put the read data into. + @param length The number of bytes to read. For devices with + multiple reports, make sure to read an extra byte for + the report number. + @returns + This function returns the actual number of bytes read and + -1 on error. If no packet was available to be read and + the handle is in non-blocking mode, this function returns 0. + */ + int HID_API_EXPORT HID_API_CALL hid_read(hid_device *device, unsigned char *data, size_t length); + + /** @brief Set the device handle to be non-blocking. + In non-blocking mode calls to hid_read() will return + immediately with a value of 0 if there is no data to be + read. In blocking mode, hid_read() will wait (block) until + there is data to read before returning. + Nonblocking can be turned on and off at any time. + @ingroup API + @param device A device handle returned from hid_open(). + @param nonblock enable or not the nonblocking reads + - 1 to enable nonblocking + - 0 to disable nonblocking. + @returns + This function returns 0 on success and -1 on error. + */ + int HID_API_EXPORT HID_API_CALL hid_set_nonblocking(hid_device *device, int nonblock); + + /** @brief Send a Feature report to the device. + Feature reports are sent over the Control endpoint as a + Set_Report transfer. The first byte of @p data[] must + contain the Report ID. For devices which only support a + single report, this must be set to 0x0. The remaining bytes + contain the report data. Since the Report ID is mandatory, + calls to hid_send_feature_report() will always contain one + more byte than the report contains. For example, if a hid + report is 16 bytes long, 17 bytes must be passed to + hid_send_feature_report(): the Report ID (or 0x0, for + devices which do not use numbered reports), followed by the + report data (16 bytes). In this example, the length passed + in would be 17. + @ingroup API + @param device A device handle returned from hid_open(). + @param data The data to send, including the report number as + the first byte. + @param length The length in bytes of the data to send, including + the report number. + @returns + This function returns the actual number of bytes written and + -1 on error. + */ + int HID_API_EXPORT HID_API_CALL hid_send_feature_report(hid_device *device, const unsigned char *data, size_t length); + + /** @brief Get a feature report from a HID device. + Set the first byte of @p data[] to the Report ID of the + report to be read. Make sure to allow space for this + extra byte in @p data[]. Upon return, the first byte will + still contain the Report ID, and the report data will + start in data[1]. + @ingroup API + @param device A device handle returned from hid_open(). + @param data A buffer to put the read data into, including + the Report ID. Set the first byte of @p data[] to the + Report ID of the report to be read, or set it to zero + if your device does not use numbered reports. + @param length The number of bytes to read, including an + extra byte for the report ID. The buffer can be longer + than the actual report. + @returns + This function returns the number of bytes read plus + one for the report ID (which is still in the first + byte), or -1 on error. + */ + int HID_API_EXPORT HID_API_CALL hid_get_feature_report(hid_device *device, unsigned char *data, size_t length); + + /** @brief Close a HID device. + @ingroup API + @param device A device handle returned from hid_open(). + */ + void HID_API_EXPORT HID_API_CALL hid_close(hid_device *device); + + /** @brief Get The Manufacturer String from a HID device. + @ingroup API + @param device A device handle returned from hid_open(). + @param string A wide string buffer to put the data into. + @param maxlen The length of the buffer in multiples of wchar_t. + @returns + This function returns 0 on success and -1 on error. + */ + int HID_API_EXPORT_CALL hid_get_manufacturer_string(hid_device *device, wchar_t *string, size_t maxlen); + + /** @brief Get The Product String from a HID device. + @ingroup API + @param device A device handle returned from hid_open(). + @param string A wide string buffer to put the data into. + @param maxlen The length of the buffer in multiples of wchar_t. + @returns + This function returns 0 on success and -1 on error. + */ + int HID_API_EXPORT_CALL hid_get_product_string(hid_device *device, wchar_t *string, size_t maxlen); + + /** @brief Get The Serial Number String from a HID device. + @ingroup API + @param device A device handle returned from hid_open(). + @param string A wide string buffer to put the data into. + @param maxlen The length of the buffer in multiples of wchar_t. + @returns + This function returns 0 on success and -1 on error. + */ + int HID_API_EXPORT_CALL hid_get_serial_number_string(hid_device *device, wchar_t *string, size_t maxlen); + + /** @brief Get a string from a HID device, based on its string index. + @ingroup API + @param device A device handle returned from hid_open(). + @param string_index The index of the string to get. + @param string A wide string buffer to put the data into. + @param maxlen The length of the buffer in multiples of wchar_t. + @returns + This function returns 0 on success and -1 on error. + */ + int HID_API_EXPORT_CALL hid_get_indexed_string(hid_device *device, int string_index, wchar_t *string, size_t maxlen); + + /** @brief Get a string describing the last error which occurred. + @ingroup API + @param device A device handle returned from hid_open(). + @returns + This function returns a string containing the last error + which occurred or NULL if none has occurred. + */ + HID_API_EXPORT const wchar_t* HID_API_CALL hid_error(hid_device *device); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/qt5_src/HIDAPI/linux/hid-libusb.c b/qt5_src/HIDAPI/linux/hid-libusb.c new file mode 100644 index 0000000..8949c25 --- /dev/null +++ b/qt5_src/HIDAPI/linux/hid-libusb.c @@ -0,0 +1,1122 @@ +/******************************************************* + HIDAPI - Multi-Platform library for + communication with HID devices. + + Alan Ott + Signal 11 Software + + 8/22/2009 + Linux Version - 6/2/2010 + Libusb Version - 8/13/2010 + + Copyright 2009, All Rights Reserved. + + At the discretion of the user of this library, + this software may be licensed under the terms of the + GNU Public License v3, a BSD-Style license, or the + original HIDAPI license as outlined in the LICENSE.txt, + LICENSE-gpl3.txt, LICENSE-bsd.txt, and LICENSE-orig.txt + files located at the root of the source distribution. + These files may also be found in the public source + code repository located at: + http://github.com/signal11/hidapi . +********************************************************/ + +/* C */ +#include +#include +#include +#include +#include +#include + +/* Unix */ +#include +#include +#include +#include +#include +#include +#include + +/* GNU / LibUSB */ +#include +#include "iconv.h" + +#include "hidapi.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef DEBUG_PRINTF +#define LOG(...) fprintf(stderr, __VA_ARGS__) +#else +#define LOG(...) do {} while (0) +#endif + +/* Linked List of input reports received from the device. */ +struct input_report { + uint8_t *data; + size_t len; + struct input_report *next; +}; + + +struct hid_device_ { + /* Handle to the actual device. */ + libusb_device_handle *device_handle; + + /* Endpoint information */ + int input_endpoint; + int output_endpoint; + int input_ep_max_packet_size; + + /* The interface number of the HID */ + int interface; + + /* Indexes of Strings */ + int manufacturer_index; + int product_index; + int serial_index; + + /* Whether blocking reads are used */ + int blocking; /* boolean */ + + /* Read thread objects */ + pthread_t thread; + pthread_mutex_t mutex; /* Protects input_reports */ + pthread_cond_t condition; + pthread_barrier_t barrier; /* Ensures correct startup sequence */ + int shutdown_thread; + struct libusb_transfer *transfer; + + /* List of received input reports. */ + struct input_report *input_reports; +}; + +static int initialized = 0; + +uint16_t get_usb_code_for_current_locale(void); +static int return_data(hid_device *dev, unsigned char *data, size_t length); + +static hid_device *new_hid_device(void) +{ + hid_device *dev = calloc(1, sizeof(hid_device)); + dev->device_handle = NULL; + dev->input_endpoint = 0; + dev->output_endpoint = 0; + dev->input_ep_max_packet_size = 0; + dev->interface = 0; + dev->manufacturer_index = 0; + dev->product_index = 0; + dev->serial_index = 0; + dev->blocking = 1; + dev->shutdown_thread = 0; + dev->transfer = NULL; + dev->input_reports = NULL; + + pthread_mutex_init(&dev->mutex, NULL); + pthread_cond_init(&dev->condition, NULL); + pthread_barrier_init(&dev->barrier, NULL, 2); + + return dev; +} + +static void free_hid_device(hid_device *dev) +{ + /* Clean up the thread objects */ + pthread_barrier_destroy(&dev->barrier); + pthread_cond_destroy(&dev->condition); + pthread_mutex_destroy(&dev->mutex); + + /* Free the device itself */ + free(dev); +} + +static void register_error(hid_device *device, const char *op) +{ + +} + +/* Get the first language the device says it reports. This comes from + USB string #0. */ +static uint16_t get_first_language(libusb_device_handle *dev) +{ + uint16_t buf[32]; + int len; + + /* Get the string from libusb. */ + len = libusb_get_string_descriptor(dev, + 0x0, /* String ID */ + 0x0, /* Language */ + (unsigned char*)buf, + sizeof(buf)); + if (len < 4) + return 0x0; + + return buf[1]; // First two bytes are len and descriptor type. +} + +static int is_language_supported(libusb_device_handle *dev, uint16_t lang) +{ + uint16_t buf[32]; + int len; + int i; + + /* Get the string from libusb. */ + len = libusb_get_string_descriptor(dev, + 0x0, /* String ID */ + 0x0, /* Language */ + (unsigned char*)buf, + sizeof(buf)); + if (len < 4) + return 0x0; + + + len /= 2; /* language IDs are two-bytes each. */ + /* Start at index 1 because there are two bytes of protocol data. */ + for (i = 1; i < len; i++) { + if (buf[i] == lang) + return 1; + } + + return 0; +} + + +/* This function returns a newly allocated wide string containing the USB + device string numbered by the index. The returned string must be freed + by using free(). */ +static wchar_t *get_usb_string(libusb_device_handle *dev, uint8_t idx) +{ + char buf[512]; + int len; + wchar_t *str = NULL; + wchar_t wbuf[256]; + + /* iconv variables */ + iconv_t ic; + size_t inbytes; + size_t outbytes; + size_t res; + char *inptr; + char *outptr; + + /* Determine which language to use. */ + uint16_t lang; + lang = get_usb_code_for_current_locale(); + if (!is_language_supported(dev, lang)) + lang = get_first_language(dev); + + /* Get the string from libusb. */ + len = libusb_get_string_descriptor(dev, + idx, + lang, + (unsigned char*)buf, + sizeof(buf)); + if (len < 0) + return NULL; + + buf[sizeof(buf)-1] = '\0'; + + if (len+1 < sizeof(buf)) + buf[len+1] = '\0'; + + /* Initialize iconv. */ + ic = iconv_open("UTF-32", "UTF-16"); + if (ic == (iconv_t)-1) + return NULL; + + /* Convert to UTF-32 (wchar_t on glibc systems). + Skip the first character (2-bytes). */ + inptr = buf+2; + inbytes = len-2; + outptr = (char*) wbuf; + outbytes = sizeof(wbuf); + res = iconv(ic, &inptr, &inbytes, &outptr, &outbytes); + if (res == (size_t)-1) + goto err; + + /* Write the terminating NULL. */ + wbuf[sizeof(wbuf)/sizeof(wbuf[0])-1] = 0x00000000; + if (outbytes >= sizeof(wbuf[0])) + *((wchar_t*)outptr) = 0x00000000; + + /* Allocate and copy the string. */ + str = wcsdup(wbuf+1); + +err: + iconv_close(ic); + + return str; +} + +static char *make_path(libusb_device *dev, int interface_number) +{ + char str[64]; + snprintf(str, sizeof(str), "%04x:%04x:%02x", + libusb_get_bus_number(dev), + libusb_get_device_address(dev), + interface_number); + str[sizeof(str)-1] = '\0'; + + return strdup(str); +} + +struct hid_device_info HID_API_EXPORT *hid_enumerate(unsigned short vendor_id, unsigned short product_id) +{ + libusb_device **devs; + libusb_device *dev; + libusb_device_handle *handle; + ssize_t num_devs; + int i = 0; + + struct hid_device_info *root = NULL; // return object + struct hid_device_info *cur_dev = NULL; + + setlocale(LC_ALL,""); + + if (!initialized) { + libusb_init(NULL); + initialized = 1; + } + + num_devs = libusb_get_device_list(NULL, &devs); + if (num_devs < 0) + return NULL; + while ((dev = devs[i++]) != NULL) { + struct libusb_device_descriptor desc; + struct libusb_config_descriptor *conf_desc = NULL; + int skip = 1; + int j, k; + int interface_num = 0; + + int res = libusb_get_device_descriptor(dev, &desc); + unsigned short dev_vid = desc.idVendor; + unsigned short dev_pid = desc.idProduct; + + /* HID's are defined at the interface level. */ + if (desc.bDeviceClass != LIBUSB_CLASS_PER_INTERFACE) + continue; + + res = libusb_get_active_config_descriptor(dev, &conf_desc); + if (res < 0) + libusb_get_config_descriptor(dev, 0, &conf_desc); + if (conf_desc) { + for (j = 0; j < conf_desc->bNumInterfaces; j++) { + const struct libusb_interface *intf = &conf_desc->interface[j]; + for (k = 0; k < intf->num_altsetting; k++) { + const struct libusb_interface_descriptor *intf_desc; + intf_desc = &intf->altsetting[k]; + if (intf_desc->bInterfaceClass == LIBUSB_CLASS_HID) { + interface_num = intf_desc->bInterfaceNumber; + skip = 0; + } + } + } + libusb_free_config_descriptor(conf_desc); + } + + if (skip) + continue; + + /* Check the VID/PID against the arguments */ + if ((vendor_id == 0x0 && product_id == 0x0) || + (vendor_id == dev_vid && product_id == dev_pid)) { + struct hid_device_info *tmp; + + /* VID/PID match. Create the record. */ + tmp = calloc(1, sizeof(struct hid_device_info)); + if (cur_dev) { + cur_dev->next = tmp; + } + else { + root = tmp; + } + cur_dev = tmp; + + /* Fill out the record */ + cur_dev->next = NULL; + cur_dev->path = make_path(dev, interface_num); + + res = libusb_open(dev, &handle); + + + if (res >= 0) { + + /* Serial Number */ + if (desc.iSerialNumber > 0) + cur_dev->serial_number = + get_usb_string(handle, desc.iSerialNumber); + + /* Manufacturer and Product strings */ + if (desc.iManufacturer > 0) + cur_dev->manufacturer_string = + get_usb_string(handle, desc.iManufacturer); + if (desc.iProduct > 0) + cur_dev->product_string = + get_usb_string(handle, desc.iProduct); + + libusb_close(handle); + } + /* VID/PID */ + cur_dev->vendor_id = dev_vid; + cur_dev->product_id = dev_pid; + } + } + + libusb_free_device_list(devs, 1); + + return root; +} + +void HID_API_EXPORT hid_free_enumeration(struct hid_device_info *devs) +{ + struct hid_device_info *d = devs; + while (d) { + struct hid_device_info *next = d->next; + free(d->path); + free(d->serial_number); + free(d->manufacturer_string); + free(d->product_string); + free(d); + d = next; + } +} + +hid_device * hid_open(unsigned short vendor_id, unsigned short product_id, const wchar_t *serial_number) +{ + struct hid_device_info *devs, *cur_dev; + const char *path_to_open = NULL; + hid_device *handle = NULL; + + devs = hid_enumerate(vendor_id, product_id); + cur_dev = devs; + while (cur_dev) { + if (cur_dev->vendor_id == vendor_id && + cur_dev->product_id == product_id) { + if (serial_number) { + if (wcscmp(serial_number, cur_dev->serial_number) == 0) { + path_to_open = cur_dev->path; + break; + } + } + else { + path_to_open = cur_dev->path; + break; + } + } + cur_dev = cur_dev->next; + } + + if (path_to_open) { + /* Open the device */ + handle = hid_open_path(path_to_open); + } + + hid_free_enumeration(devs); + + return handle; +} + +static void read_callback(struct libusb_transfer *transfer) +{ + hid_device *dev = transfer->user_data; + + if (transfer->status == LIBUSB_TRANSFER_COMPLETED) { + + struct input_report *rpt = malloc(sizeof(*rpt)); + rpt->data = malloc(transfer->actual_length); + memcpy(rpt->data, transfer->buffer, transfer->actual_length); + rpt->len = transfer->actual_length; + rpt->next = NULL; + + pthread_mutex_lock(&dev->mutex); + + /* Attach the new report object to the end of the list. */ + if (dev->input_reports == NULL) { + /* The list is empty. Put it at the root. */ + dev->input_reports = rpt; + pthread_cond_signal(&dev->condition); + } + else { + /* Find the end of the list and attach. */ + struct input_report *cur = dev->input_reports; + int num_queued = 0; + while (cur->next != NULL) { + cur = cur->next; + num_queued++; + } + cur->next = rpt; + + /* Pop one off if we've reached 30 in the queue. This + way we don't grow forever if the user never reads + anything from the device. */ + if (num_queued > 30) { + return_data(dev, NULL, 0); + } + } + pthread_mutex_unlock(&dev->mutex); + } + else if (transfer->status == LIBUSB_TRANSFER_CANCELLED) { + dev->shutdown_thread = 1; + return; + } + else if (transfer->status == LIBUSB_TRANSFER_NO_DEVICE) { + dev->shutdown_thread = 1; + return; + } + else if (transfer->status == LIBUSB_TRANSFER_TIMED_OUT) { + //LOG("Timeout (normal)\n"); + } + else { + LOG("Unknown transfer code: %d\n", transfer->status); + } + + /* Re-submit the transfer object. */ + libusb_submit_transfer(transfer); +} + + +static void *read_thread(void *param) +{ + hid_device *dev = param; + unsigned char *buf; + const size_t length = dev->input_ep_max_packet_size; + + /* Set up the transfer object. */ + buf = malloc(length); + dev->transfer = libusb_alloc_transfer(0); + libusb_fill_interrupt_transfer(dev->transfer, + dev->device_handle, + dev->input_endpoint, + buf, + length, + read_callback, + dev, + 5000/*timeout*/); + + /* Make the first submission. Further submissions are made + from inside read_callback() */ + libusb_submit_transfer(dev->transfer); + + // Notify the main thread that the read thread is up and running. + pthread_barrier_wait(&dev->barrier); + + /* Handle all the events. */ + while (!dev->shutdown_thread) { + int res; + struct timeval tv; + + tv.tv_sec = 0; + tv.tv_usec = 100; //TODO: Fix this value. + res = libusb_handle_events_timeout(NULL, &tv); + if (res < 0) { + /* There was an error. Break out of this loop. */ + break; + } + } + + /* Cancel any transfer that may be pending. This call will fail + if no transfers are pending, but that's OK. */ + if (libusb_cancel_transfer(dev->transfer) == 0) { + /* The transfer was cancelled, so wait for its completion. */ + libusb_handle_events(NULL); + } + + /* The dev->transfer->buffer and dev->transfer objects are cleaned up + in hid_close(). They are not cleaned up here because this thread + could end either due to a disconnect or due to a user + call to hid_close(). In both cases the objects can be safely + cleaned up after the call to pthread_join() (in hid_close()), but + since hid_close() calls libusb_cancel_transfer(), on these objects, + they can not be cleaned up here. */ + + return NULL; +} + + +hid_device * HID_API_EXPORT hid_open_path(const char *path) +{ + hid_device *dev = NULL; + + dev = new_hid_device(); + + libusb_device **devs; + libusb_device *usb_dev; + ssize_t num_devs; + int res; + int d = 0; + int good_open = 0; + + setlocale(LC_ALL,""); + + if (!initialized) { + libusb_init(NULL); + initialized = 1; + } + + num_devs = libusb_get_device_list(NULL, &devs); + while ((usb_dev = devs[d++]) != NULL) { + struct libusb_device_descriptor desc; + struct libusb_config_descriptor *conf_desc = NULL; + int i,j,k; + libusb_get_device_descriptor(usb_dev, &desc); + + if (libusb_get_active_config_descriptor(usb_dev, &conf_desc) < 0) + continue; + for (j = 0; j < conf_desc->bNumInterfaces; j++) { + const struct libusb_interface *intf = &conf_desc->interface[j]; + for (k = 0; k < intf->num_altsetting; k++) { + const struct libusb_interface_descriptor *intf_desc; + intf_desc = &intf->altsetting[k]; + if (intf_desc->bInterfaceClass == LIBUSB_CLASS_HID) { + char *dev_path = make_path(usb_dev, intf_desc->bInterfaceNumber); + if (!strcmp(dev_path, path)) { + /* Matched Paths. Open this device */ + + // OPEN HERE // + res = libusb_open(usb_dev, &dev->device_handle); + if (res < 0) { + LOG("can't open device\n"); + break; + } + good_open = 1; + res = libusb_detach_kernel_driver(dev->device_handle, intf_desc->bInterfaceNumber); + if (res < 0) { + //LOG("Unable to detach. Maybe this is OK\n"); + } + + res = libusb_claim_interface(dev->device_handle, intf_desc->bInterfaceNumber); + if (res < 0) { + LOG("can't claim interface %d: %d\n", intf_desc->bInterfaceNumber, res); + libusb_close(dev->device_handle); + good_open = 0; + break; + } + + /* Store off the string descriptor indexes */ + dev->manufacturer_index = desc.iManufacturer; + dev->product_index = desc.iProduct; + dev->serial_index = desc.iSerialNumber; + + /* Store off the interface number */ + dev->interface = intf_desc->bInterfaceNumber; + + /* Find the INPUT and OUTPUT endpoints. An + OUTPUT endpoint is not required. */ + for (i = 0; i < intf_desc->bNumEndpoints; i++) { + const struct libusb_endpoint_descriptor *ep + = &intf_desc->endpoint[i]; + + /* Determine the type and direction of this + endpoint. */ + int is_interrupt = + (ep->bmAttributes & LIBUSB_TRANSFER_TYPE_MASK) + == LIBUSB_TRANSFER_TYPE_INTERRUPT; + int is_output = + (ep->bEndpointAddress & LIBUSB_ENDPOINT_DIR_MASK) + == LIBUSB_ENDPOINT_OUT; + int is_input = + (ep->bEndpointAddress & LIBUSB_ENDPOINT_DIR_MASK) + == LIBUSB_ENDPOINT_IN; + + /* Decide whether to use it for intput or output. */ + if (dev->input_endpoint == 0 && + is_interrupt && is_input) { + /* Use this endpoint for INPUT */ + dev->input_endpoint = ep->bEndpointAddress; + dev->input_ep_max_packet_size = ep->wMaxPacketSize; + } + if (dev->output_endpoint == 0 && + is_interrupt && is_output) { + /* Use this endpoint for OUTPUT */ + dev->output_endpoint = ep->bEndpointAddress; + } + } + + pthread_create(&dev->thread, NULL, read_thread, dev); + + // Wait here for the read thread to be initialized. + pthread_barrier_wait(&dev->barrier); + + } + free(dev_path); + } + } + } + libusb_free_config_descriptor(conf_desc); + + } + + libusb_free_device_list(devs, 1); + + // If we have a good handle, return it. + if (good_open) { + return dev; + } + else { + // Unable to open any devices. + free_hid_device(dev); + return NULL; + } +} + + +int HID_API_EXPORT hid_write(hid_device *dev, const unsigned char *data, size_t length) +{ + int res; + int report_number = data[0]; + int skipped_report_id = 0; + + if (report_number == 0x0) { + data++; + length--; + skipped_report_id = 1; + } + + + if (dev->output_endpoint <= 0) { + /* No interrput out endpoint. Use the Control Endpoint */ + res = libusb_control_transfer(dev->device_handle, + LIBUSB_REQUEST_TYPE_CLASS|LIBUSB_RECIPIENT_INTERFACE|LIBUSB_ENDPOINT_OUT, + 0x09/*HID Set_Report*/, + (2/*HID output*/ << 8) | report_number, + dev->interface, + (unsigned char *)data, length, + 1000/*timeout millis*/); + + if (res < 0) + return -1; + + if (skipped_report_id) + length++; + + return length; + } + else { + /* Use the interrupt out endpoint */ + int actual_length; + res = libusb_interrupt_transfer(dev->device_handle, + dev->output_endpoint, + (unsigned char*)data, + length, + &actual_length, 1000); + + if (res < 0) + return -1; + + if (skipped_report_id) + actual_length++; + + return actual_length; + } +} + +/* Helper function, to simplify hid_read(). + This should be called with dev->mutex locked. */ +static int return_data(hid_device *dev, unsigned char *data, size_t length) +{ + /* Copy the data out of the linked list item (rpt) into the + return buffer (data), and delete the liked list item. */ + struct input_report *rpt = dev->input_reports; + size_t len = (length < rpt->len)? length: rpt->len; + if (len > 0) + memcpy(data, rpt->data, len); + dev->input_reports = rpt->next; + free(rpt->data); + free(rpt); + return len; +} + + +int HID_API_EXPORT hid_read(hid_device *dev, unsigned char *data, size_t length) +{ + int bytes_read = -1; + +#if 0 + int transferred; + int res = libusb_interrupt_transfer(dev->device_handle, dev->input_endpoint, data, length, &transferred, 5000); + LOG("transferred: %d\n", transferred); + return transferred; +#endif + + pthread_mutex_lock(&dev->mutex); + + /* There's an input report queued up. Return it. */ + if (dev->input_reports) { + /* Return the first one */ + bytes_read = return_data(dev, data, length); + goto ret; + } + + if (dev->shutdown_thread) { + /* This means the device has been disconnected. + An error code of -1 should be returned. */ + bytes_read = -1; + goto ret; + } + + if (dev->blocking) { + pthread_cond_wait(&dev->condition, &dev->mutex); + bytes_read = return_data(dev, data, length); + } + else { + bytes_read = 0; + } + +ret: + pthread_mutex_unlock(&dev->mutex); + + return bytes_read; +} + +int HID_API_EXPORT hid_set_nonblocking(hid_device *dev, int nonblock) +{ + dev->blocking = !nonblock; + + return 0; +} + + +int HID_API_EXPORT hid_send_feature_report(hid_device *dev, const unsigned char *data, size_t length) +{ + int res = -1; + int skipped_report_id = 0; + int report_number = data[0]; + + if (report_number == 0x0) { + data++; + length--; + skipped_report_id = 1; + } + + res = libusb_control_transfer(dev->device_handle, + LIBUSB_REQUEST_TYPE_CLASS|LIBUSB_RECIPIENT_INTERFACE|LIBUSB_ENDPOINT_OUT, + 0x09/*HID set_report*/, + (3/*HID feature*/ << 8) | report_number, + dev->interface, + (unsigned char *)data, length, + 1000/*timeout millis*/); + + if (res < 0) + return -1; + + /* Account for the report ID */ + if (skipped_report_id) + length++; + + return length; +} + +int HID_API_EXPORT hid_get_feature_report(hid_device *dev, unsigned char *data, size_t length) +{ + int res = -1; + int skipped_report_id = 0; + int report_number = data[0]; + + if (report_number == 0x0) { + /* Offset the return buffer by 1, so that the report ID + will remain in byte 0. */ + data++; + length--; + skipped_report_id = 1; + } + res = libusb_control_transfer(dev->device_handle, + LIBUSB_REQUEST_TYPE_CLASS|LIBUSB_RECIPIENT_INTERFACE|LIBUSB_ENDPOINT_IN, + 0x01/*HID get_report*/, + (3/*HID feature*/ << 8) | report_number, + dev->interface, + (unsigned char *)data, length, + 1000/*timeout millis*/); + + if (res < 0) + return -1; + + if (skipped_report_id) + res++; + + return res; +} + + +void HID_API_EXPORT hid_close(hid_device *dev) +{ + if (!dev) + return; + + /* Cause read_thread() to stop. */ + dev->shutdown_thread = 1; + libusb_cancel_transfer(dev->transfer); + + /* Wait for read_thread() to end. */ + pthread_join(dev->thread, NULL); + + /* Clean up the Transfer objects allocated in read_thread(). */ + free(dev->transfer->buffer); + libusb_free_transfer(dev->transfer); + + /* release the interface */ + libusb_release_interface(dev->device_handle, dev->interface); + + /* Close the handle */ + libusb_close(dev->device_handle); + + /* Clear out the queue of received reports. */ + pthread_mutex_lock(&dev->mutex); + while (dev->input_reports) { + return_data(dev, NULL, 0); + } + pthread_mutex_unlock(&dev->mutex); + + free_hid_device(dev); +} + + +int HID_API_EXPORT_CALL hid_get_manufacturer_string(hid_device *dev, wchar_t *string, size_t maxlen) +{ + return hid_get_indexed_string(dev, dev->manufacturer_index, string, maxlen); +} + +int HID_API_EXPORT_CALL hid_get_product_string(hid_device *dev, wchar_t *string, size_t maxlen) +{ + return hid_get_indexed_string(dev, dev->product_index, string, maxlen); +} + +int HID_API_EXPORT_CALL hid_get_serial_number_string(hid_device *dev, wchar_t *string, size_t maxlen) +{ + return hid_get_indexed_string(dev, dev->serial_index, string, maxlen); +} + +int HID_API_EXPORT_CALL hid_get_indexed_string(hid_device *dev, int string_index, wchar_t *string, size_t maxlen) +{ + wchar_t *str; + + str = get_usb_string(dev->device_handle, string_index); + if (str) { + wcsncpy(string, str, maxlen); + string[maxlen-1] = L'\0'; + free(str); + return 0; + } + else + return -1; +} + + +HID_API_EXPORT const wchar_t * HID_API_CALL hid_error(hid_device *dev) +{ + return NULL; +} + + +struct lang_map_entry { + const char *name; + const char *string_code; + uint16_t usb_code; +}; + +#define LANG(name,code,usb_code) { name, code, usb_code } +static struct lang_map_entry lang_map[] = { + LANG("Afrikaans", "af", 0x0436), + LANG("Albanian", "sq", 0x041C), + LANG("Arabic - United Arab Emirates", "ar_ae", 0x3801), + LANG("Arabic - Bahrain", "ar_bh", 0x3C01), + LANG("Arabic - Algeria", "ar_dz", 0x1401), + LANG("Arabic - Egypt", "ar_eg", 0x0C01), + LANG("Arabic - Iraq", "ar_iq", 0x0801), + LANG("Arabic - Jordan", "ar_jo", 0x2C01), + LANG("Arabic - Kuwait", "ar_kw", 0x3401), + LANG("Arabic - Lebanon", "ar_lb", 0x3001), + LANG("Arabic - Libya", "ar_ly", 0x1001), + LANG("Arabic - Morocco", "ar_ma", 0x1801), + LANG("Arabic - Oman", "ar_om", 0x2001), + LANG("Arabic - Qatar", "ar_qa", 0x4001), + LANG("Arabic - Saudi Arabia", "ar_sa", 0x0401), + LANG("Arabic - Syria", "ar_sy", 0x2801), + LANG("Arabic - Tunisia", "ar_tn", 0x1C01), + LANG("Arabic - Yemen", "ar_ye", 0x2401), + LANG("Armenian", "hy", 0x042B), + LANG("Azeri - Latin", "az_az", 0x042C), + LANG("Azeri - Cyrillic", "az_az", 0x082C), + LANG("Basque", "eu", 0x042D), + LANG("Belarusian", "be", 0x0423), + LANG("Bulgarian", "bg", 0x0402), + LANG("Catalan", "ca", 0x0403), + LANG("Chinese - China", "zh_cn", 0x0804), + LANG("Chinese - Hong Kong SAR", "zh_hk", 0x0C04), + LANG("Chinese - Macau SAR", "zh_mo", 0x1404), + LANG("Chinese - Singapore", "zh_sg", 0x1004), + LANG("Chinese - Taiwan", "zh_tw", 0x0404), + LANG("Croatian", "hr", 0x041A), + LANG("Czech", "cs", 0x0405), + LANG("Danish", "da", 0x0406), + LANG("Dutch - Netherlands", "nl_nl", 0x0413), + LANG("Dutch - Belgium", "nl_be", 0x0813), + LANG("English - Australia", "en_au", 0x0C09), + LANG("English - Belize", "en_bz", 0x2809), + LANG("English - Canada", "en_ca", 0x1009), + LANG("English - Caribbean", "en_cb", 0x2409), + LANG("English - Ireland", "en_ie", 0x1809), + LANG("English - Jamaica", "en_jm", 0x2009), + LANG("English - New Zealand", "en_nz", 0x1409), + LANG("English - Phillippines", "en_ph", 0x3409), + LANG("English - Southern Africa", "en_za", 0x1C09), + LANG("English - Trinidad", "en_tt", 0x2C09), + LANG("English - Great Britain", "en_gb", 0x0809), + LANG("English - United States", "en_us", 0x0409), + LANG("Estonian", "et", 0x0425), + LANG("Farsi", "fa", 0x0429), + LANG("Finnish", "fi", 0x040B), + LANG("Faroese", "fo", 0x0438), + LANG("French - France", "fr_fr", 0x040C), + LANG("French - Belgium", "fr_be", 0x080C), + LANG("French - Canada", "fr_ca", 0x0C0C), + LANG("French - Luxembourg", "fr_lu", 0x140C), + LANG("French - Switzerland", "fr_ch", 0x100C), + LANG("Gaelic - Ireland", "gd_ie", 0x083C), + LANG("Gaelic - Scotland", "gd", 0x043C), + LANG("German - Germany", "de_de", 0x0407), + LANG("German - Austria", "de_at", 0x0C07), + LANG("German - Liechtenstein", "de_li", 0x1407), + LANG("German - Luxembourg", "de_lu", 0x1007), + LANG("German - Switzerland", "de_ch", 0x0807), + LANG("Greek", "el", 0x0408), + LANG("Hebrew", "he", 0x040D), + LANG("Hindi", "hi", 0x0439), + LANG("Hungarian", "hu", 0x040E), + LANG("Icelandic", "is", 0x040F), + LANG("Indonesian", "id", 0x0421), + LANG("Italian - Italy", "it_it", 0x0410), + LANG("Italian - Switzerland", "it_ch", 0x0810), + LANG("Japanese", "ja", 0x0411), + LANG("Korean", "ko", 0x0412), + LANG("Latvian", "lv", 0x0426), + LANG("Lithuanian", "lt", 0x0427), + LANG("F.Y.R.O. Macedonia", "mk", 0x042F), + LANG("Malay - Malaysia", "ms_my", 0x043E), + LANG("Malay – Brunei", "ms_bn", 0x083E), + LANG("Maltese", "mt", 0x043A), + LANG("Marathi", "mr", 0x044E), + LANG("Norwegian - Bokml", "no_no", 0x0414), + LANG("Norwegian - Nynorsk", "no_no", 0x0814), + LANG("Polish", "pl", 0x0415), + LANG("Portuguese - Portugal", "pt_pt", 0x0816), + LANG("Portuguese - Brazil", "pt_br", 0x0416), + LANG("Raeto-Romance", "rm", 0x0417), + LANG("Romanian - Romania", "ro", 0x0418), + LANG("Romanian - Republic of Moldova", "ro_mo", 0x0818), + LANG("Russian", "ru", 0x0419), + LANG("Russian - Republic of Moldova", "ru_mo", 0x0819), + LANG("Sanskrit", "sa", 0x044F), + LANG("Serbian - Cyrillic", "sr_sp", 0x0C1A), + LANG("Serbian - Latin", "sr_sp", 0x081A), + LANG("Setsuana", "tn", 0x0432), + LANG("Slovenian", "sl", 0x0424), + LANG("Slovak", "sk", 0x041B), + LANG("Sorbian", "sb", 0x042E), + LANG("Spanish - Spain (Traditional)", "es_es", 0x040A), + LANG("Spanish - Argentina", "es_ar", 0x2C0A), + LANG("Spanish - Bolivia", "es_bo", 0x400A), + LANG("Spanish - Chile", "es_cl", 0x340A), + LANG("Spanish - Colombia", "es_co", 0x240A), + LANG("Spanish - Costa Rica", "es_cr", 0x140A), + LANG("Spanish - Dominican Republic", "es_do", 0x1C0A), + LANG("Spanish - Ecuador", "es_ec", 0x300A), + LANG("Spanish - Guatemala", "es_gt", 0x100A), + LANG("Spanish - Honduras", "es_hn", 0x480A), + LANG("Spanish - Mexico", "es_mx", 0x080A), + LANG("Spanish - Nicaragua", "es_ni", 0x4C0A), + LANG("Spanish - Panama", "es_pa", 0x180A), + LANG("Spanish - Peru", "es_pe", 0x280A), + LANG("Spanish - Puerto Rico", "es_pr", 0x500A), + LANG("Spanish - Paraguay", "es_py", 0x3C0A), + LANG("Spanish - El Salvador", "es_sv", 0x440A), + LANG("Spanish - Uruguay", "es_uy", 0x380A), + LANG("Spanish - Venezuela", "es_ve", 0x200A), + LANG("Southern Sotho", "st", 0x0430), + LANG("Swahili", "sw", 0x0441), + LANG("Swedish - Sweden", "sv_se", 0x041D), + LANG("Swedish - Finland", "sv_fi", 0x081D), + LANG("Tamil", "ta", 0x0449), + LANG("Tatar", "tt", 0X0444), + LANG("Thai", "th", 0x041E), + LANG("Turkish", "tr", 0x041F), + LANG("Tsonga", "ts", 0x0431), + LANG("Ukrainian", "uk", 0x0422), + LANG("Urdu", "ur", 0x0420), + LANG("Uzbek - Cyrillic", "uz_uz", 0x0843), + LANG("Uzbek – Latin", "uz_uz", 0x0443), + LANG("Vietnamese", "vi", 0x042A), + LANG("Xhosa", "xh", 0x0434), + LANG("Yiddish", "yi", 0x043D), + LANG("Zulu", "zu", 0x0435), + LANG(NULL, NULL, 0x0), +}; + +uint16_t get_usb_code_for_current_locale(void) +{ + char *locale; + char search_string[64]; + char *ptr; + + /* Get the current locale. */ + locale = setlocale(0, NULL); + if (!locale) + return 0x0; + + /* Make a copy of the current locale string. */ + strncpy(search_string, locale, sizeof(search_string)); + search_string[sizeof(search_string)-1] = '\0'; + + /* Chop off the encoding part, and make it lower case. */ + ptr = search_string; + while (*ptr) { + *ptr = tolower(*ptr); + if (*ptr == '.') { + *ptr = '\0'; + break; + } + ptr++; + } + + /* Find the entry which matches the string code of our locale. */ + struct lang_map_entry *lang = lang_map; + while (lang->string_code) { + if (!strcmp(lang->string_code, search_string)) { + return lang->usb_code; + } + lang++; + } + + /* There was no match. Find with just the language only. */ + /* Chop off the variant. Chop it off at the '_'. */ + ptr = search_string; + while (*ptr) { + *ptr = tolower(*ptr); + if (*ptr == '_') { + *ptr = '\0'; + break; + } + ptr++; + } + +#if 0 // TODO: Do we need this? + /* Find the entry which matches the string code of our language. */ + lang = lang_map; + while (lang->string_code) { + if (!strcmp(lang->string_code, search_string)) { + return lang->usb_code; + } + lang++; + } +#endif + + /* Found nothing. */ + return 0x0; +} + +#ifdef __cplusplus +} +#endif diff --git a/qt5_src/HIDAPI/linux/hid.c b/qt5_src/HIDAPI/linux/hid.c new file mode 100644 index 0000000..286ee15 --- /dev/null +++ b/qt5_src/HIDAPI/linux/hid.c @@ -0,0 +1,532 @@ +/******************************************************* + HIDAPI - Multi-Platform library for + communication with HID devices. + + Alan Ott + Signal 11 Software + + 8/22/2009 + Linux Version - 6/2/2009 + + Copyright 2009, All Rights Reserved. + + At the discretion of the user of this library, + this software may be licensed under the terms of the + GNU Public License v3, a BSD-Style license, or the + original HIDAPI license as outlined in the LICENSE.txt, + LICENSE-gpl3.txt, LICENSE-bsd.txt, and LICENSE-orig.txt + files located at the root of the source distribution. + These files may also be found in the public source + code repository located at: + http://github.com/signal11/hidapi . +********************************************************/ + +/* C */ +#include +#include +#include +#include +#include + +/* Unix */ +#include +#include +#include +#include +#include +#include + +/* Linux */ +#include +#include +#include + +#include "hidapi.h" + +struct hid_device_ { + int device_handle; + int blocking; + int uses_numbered_reports; +}; + + +static __u32 kernel_version = 0; + +hid_device *new_hid_device() +{ + hid_device *dev = calloc(1, sizeof(hid_device)); + dev->device_handle = -1; + dev->blocking = 1; + dev->uses_numbered_reports = 0; + + return dev; +} + +static void register_error(hid_device *device, const char *op) +{ + +} + +/* Get an attribute value from a udev_device and return it as a whar_t + string. The returned string must be freed with free() when done.*/ +static wchar_t *copy_udev_string(struct udev_device *dev, const char *udev_name) +{ + const char *str; + wchar_t *ret = NULL; + str = udev_device_get_sysattr_value(dev, udev_name); + if (str) { + /* Convert the string from UTF-8 to wchar_t */ + size_t wlen = mbstowcs(NULL, str, 0); + ret = calloc(wlen+1, sizeof(wchar_t)); + mbstowcs(ret, str, wlen+1); + ret[wlen] = 0x0000; + } + + return ret; +} + +/* uses_numbered_reports() returns 1 if report_descriptor describes a device + which contains numbered reports. */ +static int uses_numbered_reports(__u8 *report_descriptor, __u32 size) { + int i = 0; + int size_code; + int data_len, key_size; + + while (i < size) { + int key = report_descriptor[i]; + + /* Check for the Report ID key */ + if (key == 0x85/*Report ID*/) { + /* This device has a Report ID, which means it uses + numbered reports. */ + return 1; + } + + //printf("key: %02hhx\n", key); + + if ((key & 0xf0) == 0xf0) { + /* This is a Long Item. The next byte contains the + length of the data section (value) for this key. + See the HID specification, version 1.11, section + 6.2.2.3, titled "Long Items." */ + if (i+1 < size) + data_len = report_descriptor[i+1]; + else + data_len = 0; /* malformed report */ + key_size = 3; + } + else { + /* This is a Short Item. The bottom two bits of the + key contain the size code for the data section + (value) for this key. Refer to the HID + specification, version 1.11, section 6.2.2.2, + titled "Short Items." */ + size_code = key & 0x3; + switch (size_code) { + case 0: + case 1: + case 2: + data_len = size_code; + break; + case 3: + data_len = 4; + break; + default: + /* Can't ever happen since size_code is & 0x3 */ + data_len = 0; + break; + }; + key_size = 1; + } + + /* Skip over this key and it's associated data */ + i += data_len + key_size; + } + + /* Didn't find a Report ID key. Device doesn't use numbered reports. */ + return 0; +} + +static int get_device_string(hid_device *dev, const char *key, wchar_t *string, size_t maxlen) +{ + struct udev *udev; + struct udev_device *udev_dev, *parent; + struct stat s; + int ret = -1; + + setlocale(LC_ALL,""); + + /* Create the udev object */ + udev = udev_new(); + if (!udev) { + printf("Can't create udev\n"); + return -1; + } + + /* Get the dev_t (major/minor numbers) from the file handle. */ + fstat(dev->device_handle, &s); + /* Open a udev device from the dev_t. 'c' means character device. */ + udev_dev = udev_device_new_from_devnum(udev, 'c', s.st_rdev); + if (udev_dev) { + const char *str; + /* Find the parent USB Device */ + parent = udev_device_get_parent_with_subsystem_devtype( + udev_dev, + "usb", + "usb_device"); + if (parent) { + str = udev_device_get_sysattr_value(parent, key); + if (str) { + /* Convert the string from UTF-8 to wchar_t */ + ret = mbstowcs(string, str, maxlen); + goto end; + } + } + } + +end: + udev_device_unref(parent); + // udev_dev doesn't need unref'd. Not sure why, but + // it'll throw "double free" errors. + udev_unref(udev); + + return ret; +} + + +struct hid_device_info HID_API_EXPORT *hid_enumerate(unsigned short vendor_id, unsigned short product_id) +{ + struct udev *udev; + struct udev_enumerate *enumerate; + struct udev_list_entry *devices, *dev_list_entry; + struct udev_device *dev; + + struct hid_device_info *root = NULL; // return object + struct hid_device_info *cur_dev = NULL; + + setlocale(LC_ALL,""); + + /* Create the udev object */ + udev = udev_new(); + if (!udev) { + printf("Can't create udev\n"); + return NULL; + } + + /* Create a list of the devices in the 'hidraw' subsystem. */ + enumerate = udev_enumerate_new(udev); + udev_enumerate_add_match_subsystem(enumerate, "hidraw"); + udev_enumerate_scan_devices(enumerate); + devices = udev_enumerate_get_list_entry(enumerate); + /* For each item, see if it matches the vid/pid, and if so + create a udev_device record for it */ + udev_list_entry_foreach(dev_list_entry, devices) { + const char *sysfs_path; + const char *dev_path; + const char *str; + unsigned short dev_vid; + unsigned short dev_pid; + + /* Get the filename of the /sys entry for the device + and create a udev_device object (dev) representing it */ + sysfs_path = udev_list_entry_get_name(dev_list_entry); + dev = udev_device_new_from_syspath(udev, sysfs_path); + dev_path = udev_device_get_devnode(dev); + + /* The device pointed to by dev contains information about + the hidraw device. In order to get information about the + USB device, get the parent device with the + subsystem/devtype pair of "usb"/"usb_device". This will + be several levels up the tree, but the function will find + it.*/ + dev = udev_device_get_parent_with_subsystem_devtype( + dev, + "usb", + "usb_device"); + if (!dev) { + /* Unable to find parent usb device. */ + goto next; + } + + /* Get the VID/PID of the device */ + str = udev_device_get_sysattr_value(dev,"idVendor"); + dev_vid = (str)? strtol(str, NULL, 16): 0x0; + str = udev_device_get_sysattr_value(dev, "idProduct"); + dev_pid = (str)? strtol(str, NULL, 16): 0x0; + + /* Check the VID/PID against the arguments */ + if ((vendor_id == 0x0 && product_id == 0x0) || + (vendor_id == dev_vid && product_id == dev_pid)) { + struct hid_device_info *tmp; + size_t len; + + /* VID/PID match. Create the record. */ + tmp = malloc(sizeof(struct hid_device_info)); + if (cur_dev) { + cur_dev->next = tmp; + } + else { + root = tmp; + } + cur_dev = tmp; + + /* Fill out the record */ + cur_dev->next = NULL; + str = dev_path; + if (str) { + len = strlen(str); + cur_dev->path = calloc(len+1, sizeof(char)); + strncpy(cur_dev->path, str, len+1); + cur_dev->path[len] = '\0'; + } + else + cur_dev->path = NULL; + + /* Serial Number */ + cur_dev->serial_number + = copy_udev_string(dev, "serial"); + + /* Manufacturer and Product strings */ + cur_dev->manufacturer_string + = copy_udev_string(dev, "manufacturer"); + cur_dev->product_string + = copy_udev_string(dev, "product"); + + /* VID/PID */ + cur_dev->vendor_id = dev_vid; + cur_dev->product_id = dev_pid; + } + else + goto next; + + next: + udev_device_unref(dev); + } + /* Free the enumerator and udev objects. */ + udev_enumerate_unref(enumerate); + udev_unref(udev); + + return root; +} + +void HID_API_EXPORT hid_free_enumeration(struct hid_device_info *devs) +{ + struct hid_device_info *d = devs; + while (d) { + struct hid_device_info *next = d->next; + free(d->path); + free(d->serial_number); + free(d->manufacturer_string); + free(d->product_string); + free(d); + d = next; + } +} + +hid_device * hid_open(unsigned short vendor_id, unsigned short product_id, const wchar_t *serial_number) +{ + struct hid_device_info *devs, *cur_dev; + const char *path_to_open = NULL; + hid_device *handle = NULL; + + devs = hid_enumerate(vendor_id, product_id); + cur_dev = devs; + while (cur_dev) { + if (cur_dev->vendor_id == vendor_id && + cur_dev->product_id == product_id) { + if (serial_number) { + if (wcscmp(serial_number, cur_dev->serial_number) == 0) { + path_to_open = cur_dev->path; + break; + } + } + else { + path_to_open = cur_dev->path; + break; + } + } + cur_dev = cur_dev->next; + } + + if (path_to_open) { + /* Open the device */ + handle = hid_open_path(path_to_open); + } + + hid_free_enumeration(devs); + + return handle; +} + +hid_device * HID_API_EXPORT hid_open_path(const char *path) +{ + hid_device *dev = NULL; + + dev = new_hid_device(); + + if (kernel_version == 0) { + struct utsname name; + int major, minor, release; + int ret; + uname(&name); + ret = sscanf(name.release, "%d.%d.%d", &major, &minor, &release); + if (ret == 3) { + kernel_version = major << 16 | minor << 8 | release; + //printf("Kernel Version: %d\n", kernel_version); + } + else { + printf("Couldn't sscanf() version string %s\n", name.release); + } + } + + // OPEN HERE // + dev->device_handle = open(path, O_RDWR); + + // If we have a good handle, return it. + if (dev->device_handle > 0) { + + /* Get the report descriptor */ + int res, desc_size = 0; + struct hidraw_report_descriptor rpt_desc; + + memset(&rpt_desc, 0x0, sizeof(rpt_desc)); + + /* Get Report Descriptor Size */ + res = ioctl(dev->device_handle, HIDIOCGRDESCSIZE, &desc_size); + if (res < 0) + perror("HIDIOCGRDESCSIZE"); + + + /* Get Report Descriptor */ + rpt_desc.size = desc_size; + res = ioctl(dev->device_handle, HIDIOCGRDESC, &rpt_desc); + if (res < 0) { + perror("HIDIOCGRDESC"); + } else { + /* Determine if this device uses numbered reports. */ + dev->uses_numbered_reports = + uses_numbered_reports(rpt_desc.value, + rpt_desc.size); + } + + return dev; + } + else { + // Unable to open any devices. + free(dev); + return NULL; + } +} + + +int HID_API_EXPORT hid_write(hid_device *dev, const unsigned char *data, size_t length) +{ + int bytes_written; + + bytes_written = write(dev->device_handle, data, length); + + return bytes_written; +} + + +int HID_API_EXPORT hid_read(hid_device *dev, unsigned char *data, size_t length) +{ + int bytes_read; + + bytes_read = read(dev->device_handle, data, length); + if (bytes_read < 0 && errno == EAGAIN) + bytes_read = 0; + + if (bytes_read >= 0 && + kernel_version < KERNEL_VERSION(2,6,34) && + dev->uses_numbered_reports) { + /* Work around a kernel bug. Chop off the first byte. */ + memmove(data, data+1, bytes_read); + bytes_read--; + } + + return bytes_read; +} + +int HID_API_EXPORT hid_set_nonblocking(hid_device *dev, int nonblock) +{ + int flags, res; + + flags = fcntl(dev->device_handle, F_GETFL, 0); + if (flags >= 0) { + if (nonblock) + res = fcntl(dev->device_handle, F_SETFL, flags | O_NONBLOCK); + else + res = fcntl(dev->device_handle, F_SETFL, flags & ~O_NONBLOCK); + } + else + return -1; + + if (res < 0) { + return -1; + } + else { + dev->blocking = !nonblock; + return 0; /* Success */ + } +} + + +int HID_API_EXPORT hid_send_feature_report(hid_device *dev, const unsigned char *data, size_t length) +{ + int res; + + res = ioctl(dev->device_handle, HIDIOCSFEATURE(length), data); + if (res < 0) + perror("ioctl (SFEATURE)"); + + return res; +} + +int HID_API_EXPORT hid_get_feature_report(hid_device *dev, unsigned char *data, size_t length) +{ + int res; + + res = ioctl(dev->device_handle, HIDIOCGFEATURE(length), data); + if (res < 0) + perror("ioctl (GFEATURE)"); + + + return res; +} + + +void HID_API_EXPORT hid_close(hid_device *dev) +{ + if (!dev) + return; + close(dev->device_handle); + free(dev); +} + + +int HID_API_EXPORT_CALL hid_get_manufacturer_string(hid_device *dev, wchar_t *string, size_t maxlen) +{ + return get_device_string(dev, "manufacturer", string, maxlen); +} + +int HID_API_EXPORT_CALL hid_get_product_string(hid_device *dev, wchar_t *string, size_t maxlen) +{ + return get_device_string(dev, "product", string, maxlen); +} + +int HID_API_EXPORT_CALL hid_get_serial_number_string(hid_device *dev, wchar_t *string, size_t maxlen) +{ + return get_device_string(dev, "serial", string, maxlen); +} + +int HID_API_EXPORT_CALL hid_get_indexed_string(hid_device *dev, int string_index, wchar_t *string, size_t maxlen) +{ + return -1; +} + + +HID_API_EXPORT const wchar_t * HID_API_CALL hid_error(hid_device *dev) +{ + return NULL; +} diff --git a/qt5_src/HIDAPI/mac/hid.c b/qt5_src/HIDAPI/mac/hid.c new file mode 100644 index 0000000..2fc0322 --- /dev/null +++ b/qt5_src/HIDAPI/mac/hid.c @@ -0,0 +1,1094 @@ +/******************************************************* + HIDAPI - Multi-Platform library for + communication with HID devices. + Alan Ott + Signal 11 Software + 2010-07-03 + Copyright 2010, All Rights Reserved. + At the discretion of the user of this library, + this software may be licensed under the terms of the + GNU General Public License v3, a BSD-Style license, or the + original HIDAPI license as outlined in the LICENSE.txt, + LICENSE-gpl3.txt, LICENSE-bsd.txt, and LICENSE-orig.txt + files located at the root of the source distribution. + These files may also be found in the public source + code repository located at: + http://github.com/signal11/hidapi . +********************************************************/ + +/* See Apple Technical Note TN2187 for details on IOHidManager. */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "hidapi.h" + +/* Barrier implementation because Mac OSX doesn't have pthread_barrier. + It also doesn't have clock_gettime(). So much for POSIX and SUSv2. + This implementation came from Brent Priddy and was posted on + StackOverflow. It is used with his permission. */ +typedef int pthread_barrierattr_t; +typedef struct pthread_barrier { + pthread_mutex_t mutex; + pthread_cond_t cond; + int count; + int trip_count; +} pthread_barrier_t; + +static int pthread_barrier_init(pthread_barrier_t *barrier, const pthread_barrierattr_t *attr, unsigned int count) +{ + if(count == 0) { + errno = EINVAL; + return -1; + } + + if(pthread_mutex_init(&barrier->mutex, 0) < 0) { + return -1; + } + if(pthread_cond_init(&barrier->cond, 0) < 0) { + pthread_mutex_destroy(&barrier->mutex); + return -1; + } + barrier->trip_count = count; + barrier->count = 0; + + return 0; +} + +static int pthread_barrier_destroy(pthread_barrier_t *barrier) +{ + pthread_cond_destroy(&barrier->cond); + pthread_mutex_destroy(&barrier->mutex); + return 0; +} + +static int pthread_barrier_wait(pthread_barrier_t *barrier) +{ + pthread_mutex_lock(&barrier->mutex); + ++(barrier->count); + if(barrier->count >= barrier->trip_count) + { + barrier->count = 0; + pthread_cond_broadcast(&barrier->cond); + pthread_mutex_unlock(&barrier->mutex); + return 1; + } + else + { + pthread_cond_wait(&barrier->cond, &(barrier->mutex)); + pthread_mutex_unlock(&barrier->mutex); + return 0; + } +} + +static int return_data(hid_device *dev, unsigned char *data, size_t length); + +/* Linked List of input reports received from the device. */ +struct input_report { + uint8_t *data; + size_t len; + struct input_report *next; +}; + +struct hid_device_ { + IOHIDDeviceRef device_handle; + int blocking; + int uses_numbered_reports; + int disconnected; + CFStringRef run_loop_mode; + CFRunLoopRef run_loop; + CFRunLoopSourceRef source; + uint8_t *input_report_buf; + CFIndex max_input_report_len; + struct input_report *input_reports; + + pthread_t thread; + pthread_mutex_t mutex; /* Protects input_reports */ + pthread_cond_t condition; + pthread_barrier_t barrier; /* Ensures correct startup sequence */ + pthread_barrier_t shutdown_barrier; /* Ensures correct shutdown sequence */ + int shutdown_thread; +}; + +static hid_device *new_hid_device(void) +{ + hid_device *dev = calloc(1, sizeof(hid_device)); + dev->device_handle = NULL; + dev->blocking = 1; + dev->uses_numbered_reports = 0; + dev->disconnected = 0; + dev->run_loop_mode = NULL; + dev->run_loop = NULL; + dev->source = NULL; + dev->input_report_buf = NULL; + dev->input_reports = NULL; + dev->shutdown_thread = 0; + + /* Thread objects */ + pthread_mutex_init(&dev->mutex, NULL); + pthread_cond_init(&dev->condition, NULL); + pthread_barrier_init(&dev->barrier, NULL, 2); + pthread_barrier_init(&dev->shutdown_barrier, NULL, 2); + + return dev; +} + +static void free_hid_device(hid_device *dev) +{ + if (!dev) + return; + + /* Delete any input reports still left over. */ + struct input_report *rpt = dev->input_reports; + while (rpt) { + struct input_report *next = rpt->next; + free(rpt->data); + free(rpt); + rpt = next; + } + + /* Free the string and the report buffer. The check for NULL + is necessary here as CFRelease() doesn't handle NULL like + free() and others do. */ + if (dev->run_loop_mode) + CFRelease(dev->run_loop_mode); + if (dev->source) + CFRelease(dev->source); + free(dev->input_report_buf); + + /* Clean up the thread objects */ + pthread_barrier_destroy(&dev->shutdown_barrier); + pthread_barrier_destroy(&dev->barrier); + pthread_cond_destroy(&dev->condition); + pthread_mutex_destroy(&dev->mutex); + + /* Free the structure itself. */ + free(dev); +} + +static IOHIDManagerRef hid_mgr = 0x0; + + +#if 0 +static void register_error(hid_device *device, const char *op) +{ +} +#endif + + +static int32_t get_int_property(IOHIDDeviceRef device, CFStringRef key) +{ + CFTypeRef ref; + int32_t value; + + ref = IOHIDDeviceGetProperty(device, key); + if (ref) { + if (CFGetTypeID(ref) == CFNumberGetTypeID()) { + CFNumberGetValue((CFNumberRef) ref, kCFNumberSInt32Type, &value); + return value; + } + } + return 0; +} + +static unsigned short get_vendor_id(IOHIDDeviceRef device) +{ + return get_int_property(device, CFSTR(kIOHIDVendorIDKey)); +} + +static unsigned short get_product_id(IOHIDDeviceRef device) +{ + return get_int_property(device, CFSTR(kIOHIDProductIDKey)); +} + +static int32_t get_location_id(IOHIDDeviceRef device) +{ + return get_int_property(device, CFSTR(kIOHIDLocationIDKey)); +} + +static int32_t get_max_report_length(IOHIDDeviceRef device) +{ + return get_int_property(device, CFSTR(kIOHIDMaxInputReportSizeKey)); +} + +static int get_string_property(IOHIDDeviceRef device, CFStringRef prop, wchar_t *buf, size_t len) +{ + CFStringRef str; + + if (!len) + return 0; + + str = IOHIDDeviceGetProperty(device, prop); + + buf[0] = 0; + + if (str) { + CFIndex str_len = CFStringGetLength(str); + CFRange range; + CFIndex used_buf_len; + CFIndex chars_copied; + + len --; + + range.location = 0; + range.length = ((size_t)str_len > len)? len: (size_t)str_len; + chars_copied = CFStringGetBytes(str, + range, + kCFStringEncodingUTF32LE, + (char)'?', + FALSE, + (UInt8*)buf, + len * sizeof(wchar_t), + &used_buf_len); + + if (chars_copied == len) + buf[len] = 0; /* len is decremented above */ + else + buf[chars_copied] = 0; + + return 0; + } + else + return -1; + +} + +static int get_string_property_utf8(IOHIDDeviceRef device, CFStringRef prop, char *buf, size_t len) +{ + CFStringRef str; + if (!len) + return 0; + + str = IOHIDDeviceGetProperty(device, prop); + + buf[0] = 0; + + if (str) { + len--; + + CFIndex str_len = CFStringGetLength(str); + CFRange range; + range.location = 0; + range.length = str_len; + CFIndex used_buf_len; + CFIndex chars_copied; + chars_copied = CFStringGetBytes(str, + range, + kCFStringEncodingUTF8, + (char)'?', + FALSE, + (UInt8*)buf, + len, + &used_buf_len); + + if (used_buf_len == len) + buf[len] = 0; /* len is decremented above */ + else + buf[used_buf_len] = 0; + + return used_buf_len; + } + else + return 0; +} + + +static int get_serial_number(IOHIDDeviceRef device, wchar_t *buf, size_t len) +{ + return get_string_property(device, CFSTR(kIOHIDSerialNumberKey), buf, len); +} + +static int get_manufacturer_string(IOHIDDeviceRef device, wchar_t *buf, size_t len) +{ + return get_string_property(device, CFSTR(kIOHIDManufacturerKey), buf, len); +} + +static int get_product_string(IOHIDDeviceRef device, wchar_t *buf, size_t len) +{ + return get_string_property(device, CFSTR(kIOHIDProductKey), buf, len); +} + + +/* Implementation of wcsdup() for Mac. */ +static wchar_t *dup_wcs(const wchar_t *s) +{ + size_t len = wcslen(s); + wchar_t *ret = malloc((len+1)*sizeof(wchar_t)); + wcscpy(ret, s); + + return ret; +} + + +static int make_path(IOHIDDeviceRef device, char *buf, size_t len) +{ + int res; + unsigned short vid, pid; + char transport[32]; + int32_t location; + + buf[0] = '\0'; + + res = get_string_property_utf8( + device, CFSTR(kIOHIDTransportKey), + transport, sizeof(transport)); + + if (!res) + return -1; + + location = get_location_id(device); + vid = get_vendor_id(device); + pid = get_product_id(device); + + res = snprintf(buf, len, "%s_%04hx_%04hx_%x", + transport, vid, pid, location); + + + buf[len-1] = '\0'; + return res+1; +} + +/* Initialize the IOHIDManager. Return 0 for success and -1 for failure. */ +static int init_hid_manager(void) +{ + /* Initialize all the HID Manager Objects */ + hid_mgr = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone); + if (hid_mgr) { + IOHIDManagerSetDeviceMatching(hid_mgr, NULL); + IOHIDManagerScheduleWithRunLoop(hid_mgr, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode); + return 0; + } + + return -1; +} + +/* Initialize the IOHIDManager if necessary. This is the public function, and + it is safe to call this function repeatedly. Return 0 for success and -1 + for failure. */ +int HID_API_EXPORT hid_init(void) +{ + if (!hid_mgr) { + return init_hid_manager(); + } + + /* Already initialized. */ + return 0; +} + +int HID_API_EXPORT hid_exit(void) +{ + if (hid_mgr) { + /* Close the HID manager. */ + IOHIDManagerClose(hid_mgr, kIOHIDOptionsTypeNone); + CFRelease(hid_mgr); + hid_mgr = NULL; + } + + return 0; +} + +static void process_pending_events(void) { + SInt32 res; + do { + res = CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.001, FALSE); + } while(res != kCFRunLoopRunFinished && res != kCFRunLoopRunTimedOut); +} + +struct hid_device_info HID_API_EXPORT *hid_enumerate(unsigned short vendor_id, unsigned short product_id) +{ + struct hid_device_info *root = NULL; /* return object */ + struct hid_device_info *cur_dev = NULL; + CFIndex num_devices; + int i; + + /* Set up the HID Manager if it hasn't been done */ + if (hid_init() < 0) + return NULL; + + /* give the IOHIDManager a chance to update itself */ + process_pending_events(); + + /* Get a list of the Devices */ + IOHIDManagerSetDeviceMatching(hid_mgr, NULL); + CFSetRef device_set = IOHIDManagerCopyDevices(hid_mgr); + + /* Convert the list into a C array so we can iterate easily. */ + num_devices = CFSetGetCount(device_set); + IOHIDDeviceRef *device_array = calloc(num_devices, sizeof(IOHIDDeviceRef)); + CFSetGetValues(device_set, (const void **) device_array); + + /* Iterate over each device, making an entry for it. */ + for (i = 0; i < num_devices; i++) { + unsigned short dev_vid; + unsigned short dev_pid; + #define BUF_LEN 256 + wchar_t buf[BUF_LEN]; + char cbuf[BUF_LEN]; + + IOHIDDeviceRef dev = device_array[i]; + + if (!dev) { + continue; + } + dev_vid = get_vendor_id(dev); + dev_pid = get_product_id(dev); + + /* Check the VID/PID against the arguments */ + if ((vendor_id == 0x0 || vendor_id == dev_vid) && + (product_id == 0x0 || product_id == dev_pid)) { + struct hid_device_info *tmp; + size_t len; + + /* VID/PID match. Create the record. */ + tmp = malloc(sizeof(struct hid_device_info)); + if (cur_dev) { + cur_dev->next = tmp; + } + else { + root = tmp; + } + cur_dev = tmp; + + /* Get the Usage Page and Usage for this device. */ + cur_dev->usage_page = get_int_property(dev, CFSTR(kIOHIDPrimaryUsagePageKey)); + cur_dev->usage = get_int_property(dev, CFSTR(kIOHIDPrimaryUsageKey)); + + /* Fill out the record */ + cur_dev->next = NULL; + len = make_path(dev, cbuf, sizeof(cbuf)); + cur_dev->path = strdup(cbuf); + + /* Serial Number */ + get_serial_number(dev, buf, BUF_LEN); + cur_dev->serial_number = dup_wcs(buf); + + /* Manufacturer and Product strings */ + get_manufacturer_string(dev, buf, BUF_LEN); + cur_dev->manufacturer_string = dup_wcs(buf); + get_product_string(dev, buf, BUF_LEN); + cur_dev->product_string = dup_wcs(buf); + + /* VID/PID */ + cur_dev->vendor_id = dev_vid; + cur_dev->product_id = dev_pid; + + /* Release Number */ + cur_dev->release_number = get_int_property(dev, CFSTR(kIOHIDVersionNumberKey)); + + /* Interface Number (Unsupported on Mac)*/ + cur_dev->interface_number = -1; + } + } + + free(device_array); + CFRelease(device_set); + + return root; +} + +void HID_API_EXPORT hid_free_enumeration(struct hid_device_info *devs) +{ + /* This function is identical to the Linux version. Platform independent. */ + struct hid_device_info *d = devs; + while (d) { + struct hid_device_info *next = d->next; + free(d->path); + free(d->serial_number); + free(d->manufacturer_string); + free(d->product_string); + free(d); + d = next; + } +} + +hid_device * HID_API_EXPORT hid_open(unsigned short vendor_id, unsigned short product_id, const wchar_t *serial_number) +{ + /* This function is identical to the Linux version. Platform independent. */ + struct hid_device_info *devs, *cur_dev; + const char *path_to_open = NULL; + hid_device * handle = NULL; + + devs = hid_enumerate(vendor_id, product_id); + cur_dev = devs; + while (cur_dev) { + if (cur_dev->vendor_id == vendor_id && + cur_dev->product_id == product_id) { + if (serial_number) { + if (wcscmp(serial_number, cur_dev->serial_number) == 0) { + path_to_open = cur_dev->path; + break; + } + } + else { + path_to_open = cur_dev->path; + break; + } + } + cur_dev = cur_dev->next; + } + + if (path_to_open) { + /* Open the device */ + handle = hid_open_path(path_to_open); + } + + hid_free_enumeration(devs); + + return handle; +} + +static void hid_device_removal_callback(void *context, IOReturn result, + void *sender) +{ + /* Stop the Run Loop for this device. */ + hid_device *d = context; + + d->disconnected = 1; + CFRunLoopStop(d->run_loop); +} + +/* The Run Loop calls this function for each input report received. + This function puts the data into a linked list to be picked up by + hid_read(). */ +static void hid_report_callback(void *context, IOReturn result, void *sender, + IOHIDReportType report_type, uint32_t report_id, + uint8_t *report, CFIndex report_length) +{ + struct input_report *rpt; + hid_device *dev = context; + + /* Make a new Input Report object */ + rpt = calloc(1, sizeof(struct input_report)); + rpt->data = calloc(1, report_length); + memcpy(rpt->data, report, report_length); + rpt->len = report_length; + rpt->next = NULL; + + /* Lock this section */ + pthread_mutex_lock(&dev->mutex); + + /* Attach the new report object to the end of the list. */ + if (dev->input_reports == NULL) { + /* The list is empty. Put it at the root. */ + dev->input_reports = rpt; + } + else { + /* Find the end of the list and attach. */ + struct input_report *cur = dev->input_reports; + int num_queued = 0; + while (cur->next != NULL) { + cur = cur->next; + num_queued++; + } + cur->next = rpt; + + /* Pop one off if we've reached 30 in the queue. This + way we don't grow forever if the user never reads + anything from the device. */ + if (num_queued > 30) { + return_data(dev, NULL, 0); + } + } + + /* Signal a waiting thread that there is data. */ + pthread_cond_signal(&dev->condition); + + /* Unlock */ + pthread_mutex_unlock(&dev->mutex); + +} + +/* This gets called when the read_thred's run loop gets signaled by + hid_close(), and serves to stop the read_thread's run loop. */ +static void perform_signal_callback(void *context) +{ + hid_device *dev = context; + CFRunLoopStop(dev->run_loop); /*TODO: CFRunLoopGetCurrent()*/ +} + +static void *read_thread(void *param) +{ + hid_device *dev = param; + SInt32 code; + + /* Move the device's run loop to this thread. */ + IOHIDDeviceScheduleWithRunLoop(dev->device_handle, CFRunLoopGetCurrent(), dev->run_loop_mode); + + /* Create the RunLoopSource which is used to signal the + event loop to stop when hid_close() is called. */ + CFRunLoopSourceContext ctx; + memset(&ctx, 0, sizeof(ctx)); + ctx.version = 0; + ctx.info = dev; + ctx.perform = &perform_signal_callback; + dev->source = CFRunLoopSourceCreate(kCFAllocatorDefault, 0/*order*/, &ctx); + CFRunLoopAddSource(CFRunLoopGetCurrent(), dev->source, dev->run_loop_mode); + + /* Store off the Run Loop so it can be stopped from hid_close() + and on device disconnection. */ + dev->run_loop = CFRunLoopGetCurrent(); + + /* Notify the main thread that the read thread is up and running. */ + pthread_barrier_wait(&dev->barrier); + + /* Run the Event Loop. CFRunLoopRunInMode() will dispatch HID input + reports into the hid_report_callback(). */ + while (!dev->shutdown_thread && !dev->disconnected) { + code = CFRunLoopRunInMode(dev->run_loop_mode, 1000/*sec*/, FALSE); + /* Return if the device has been disconnected */ + if (code == kCFRunLoopRunFinished) { + dev->disconnected = 1; + break; + } + + + /* Break if The Run Loop returns Finished or Stopped. */ + if (code != kCFRunLoopRunTimedOut && + code != kCFRunLoopRunHandledSource) { + /* There was some kind of error. Setting + shutdown seems to make sense, but + there may be something else more appropriate */ + dev->shutdown_thread = 1; + break; + } + } + + /* Now that the read thread is stopping, Wake any threads which are + waiting on data (in hid_read_timeout()). Do this under a mutex to + make sure that a thread which is about to go to sleep waiting on + the condition acutally will go to sleep before the condition is + signaled. */ + pthread_mutex_lock(&dev->mutex); + pthread_cond_broadcast(&dev->condition); + pthread_mutex_unlock(&dev->mutex); + + /* Wait here until hid_close() is called and makes it past + the call to CFRunLoopWakeUp(). This thread still needs to + be valid when that function is called on the other thread. */ + pthread_barrier_wait(&dev->shutdown_barrier); + + return NULL; +} + +hid_device * HID_API_EXPORT hid_open_path(const char *path) +{ + int i; + hid_device *dev = NULL; + CFIndex num_devices; + + dev = new_hid_device(); + + /* Set up the HID Manager if it hasn't been done */ + if (hid_init() < 0) + return NULL; + + /* give the IOHIDManager a chance to update itself */ + process_pending_events(); + + CFSetRef device_set = IOHIDManagerCopyDevices(hid_mgr); + + num_devices = CFSetGetCount(device_set); + IOHIDDeviceRef *device_array = calloc(num_devices, sizeof(IOHIDDeviceRef)); + CFSetGetValues(device_set, (const void **) device_array); + for (i = 0; i < num_devices; i++) { + char cbuf[BUF_LEN]; + size_t len; + IOHIDDeviceRef os_dev = device_array[i]; + + len = make_path(os_dev, cbuf, sizeof(cbuf)); + if (!strcmp(cbuf, path)) { + /* Matched Paths. Open this Device. */ + IOReturn ret = IOHIDDeviceOpen(os_dev, kIOHIDOptionsTypeSeizeDevice); + if (ret == kIOReturnSuccess) { + char str[32]; + + free(device_array); + CFRetain(os_dev); + CFRelease(device_set); + dev->device_handle = os_dev; + + /* Create the buffers for receiving data */ + dev->max_input_report_len = (CFIndex) get_max_report_length(os_dev); + dev->input_report_buf = calloc(dev->max_input_report_len, sizeof(uint8_t)); + + /* Create the Run Loop Mode for this device. + printing the reference seems to work. */ + sprintf(str, "HIDAPI_%p", os_dev); + dev->run_loop_mode = + CFStringCreateWithCString(NULL, str, kCFStringEncodingASCII); + + /* Attach the device to a Run Loop */ + IOHIDDeviceRegisterInputReportCallback( + os_dev, dev->input_report_buf, dev->max_input_report_len, + &hid_report_callback, dev); + IOHIDDeviceRegisterRemovalCallback(dev->device_handle, hid_device_removal_callback, dev); + + /* Start the read thread */ + pthread_create(&dev->thread, NULL, read_thread, dev); + + /* Wait here for the read thread to be initialized. */ + pthread_barrier_wait(&dev->barrier); + + return dev; + } + else { + goto return_error; + } + } + } + +return_error: + free(device_array); + CFRelease(device_set); + free_hid_device(dev); + return NULL; +} + +static int set_report(hid_device *dev, IOHIDReportType type, const unsigned char *data, size_t length) +{ + const unsigned char *data_to_send; + size_t length_to_send; + IOReturn res; + + /* Return if the device has been disconnected. */ + if (dev->disconnected) + return -1; + + if (data[0] == 0x0) { + /* Not using numbered Reports. + Don't send the report number. */ + data_to_send = data+1; + length_to_send = length-1; + } + else { + /* Using numbered Reports. + Send the Report Number */ + data_to_send = data; + length_to_send = length; + } + + if (!dev->disconnected) { + res = IOHIDDeviceSetReport(dev->device_handle, + type, + data[0], /* Report ID*/ + data_to_send, length_to_send); + + if (res == kIOReturnSuccess) { + return length; + } + else + return -1; + } + + return -1; +} + +int HID_API_EXPORT hid_write(hid_device *dev, const unsigned char *data, size_t length) +{ + return set_report(dev, kIOHIDReportTypeOutput, data, length); +} + +/* Helper function, so that this isn't duplicated in hid_read(). */ +static int return_data(hid_device *dev, unsigned char *data, size_t length) +{ + /* Copy the data out of the linked list item (rpt) into the + return buffer (data), and delete the liked list item. */ + struct input_report *rpt = dev->input_reports; + size_t len = (length < rpt->len)? length: rpt->len; + memcpy(data, rpt->data, len); + dev->input_reports = rpt->next; + free(rpt->data); + free(rpt); + return len; +} + +static int cond_wait(const hid_device *dev, pthread_cond_t *cond, pthread_mutex_t *mutex) +{ + while (!dev->input_reports) { + int res = pthread_cond_wait(cond, mutex); + if (res != 0) + return res; + + /* A res of 0 means we may have been signaled or it may + be a spurious wakeup. Check to see that there's acutally + data in the queue before returning, and if not, go back + to sleep. See the pthread_cond_timedwait() man page for + details. */ + + if (dev->shutdown_thread || dev->disconnected) + return -1; + } + + return 0; +} + +static int cond_timedwait(const hid_device *dev, pthread_cond_t *cond, pthread_mutex_t *mutex, const struct timespec *abstime) +{ + while (!dev->input_reports) { + int res = pthread_cond_timedwait(cond, mutex, abstime); + if (res != 0) + return res; + + /* A res of 0 means we may have been signaled or it may + be a spurious wakeup. Check to see that there's acutally + data in the queue before returning, and if not, go back + to sleep. See the pthread_cond_timedwait() man page for + details. */ + + if (dev->shutdown_thread || dev->disconnected) + return -1; + } + + return 0; + +} + +int HID_API_EXPORT hid_read_timeout(hid_device *dev, unsigned char *data, size_t length, int milliseconds) +{ + int bytes_read = -1; + + /* Lock the access to the report list. */ + pthread_mutex_lock(&dev->mutex); + + /* There's an input report queued up. Return it. */ + if (dev->input_reports) { + /* Return the first one */ + bytes_read = return_data(dev, data, length); + goto ret; + } + + /* Return if the device has been disconnected. */ + if (dev->disconnected) { + bytes_read = -1; + goto ret; + } + + if (dev->shutdown_thread) { + /* This means the device has been closed (or there + has been an error. An error code of -1 should + be returned. */ + bytes_read = -1; + goto ret; + } + + /* There is no data. Go to sleep and wait for data. */ + + if (milliseconds == -1) { + /* Blocking */ + int res; + res = cond_wait(dev, &dev->condition, &dev->mutex); + if (res == 0) + bytes_read = return_data(dev, data, length); + else { + /* There was an error, or a device disconnection. */ + bytes_read = -1; + } + } + else if (milliseconds > 0) { + /* Non-blocking, but called with timeout. */ + int res; + struct timespec ts; + struct timeval tv; + gettimeofday(&tv, NULL); + TIMEVAL_TO_TIMESPEC(&tv, &ts); + ts.tv_sec += milliseconds / 1000; + ts.tv_nsec += (milliseconds % 1000) * 1000000; + if (ts.tv_nsec >= 1000000000L) { + ts.tv_sec++; + ts.tv_nsec -= 1000000000L; + } + + res = cond_timedwait(dev, &dev->condition, &dev->mutex, &ts); + if (res == 0) + bytes_read = return_data(dev, data, length); + else if (res == ETIMEDOUT) + bytes_read = 0; + else + bytes_read = -1; + } + else { + /* Purely non-blocking */ + bytes_read = 0; + } + +ret: + /* Unlock */ + pthread_mutex_unlock(&dev->mutex); + return bytes_read; +} + +int HID_API_EXPORT hid_read(hid_device *dev, unsigned char *data, size_t length) +{ + return hid_read_timeout(dev, data, length, (dev->blocking)? -1: 0); +} + +int HID_API_EXPORT hid_set_nonblocking(hid_device *dev, int nonblock) +{ + /* All Nonblocking operation is handled by the library. */ + dev->blocking = !nonblock; + + return 0; +} + +int HID_API_EXPORT hid_send_feature_report(hid_device *dev, const unsigned char *data, size_t length) +{ + return set_report(dev, kIOHIDReportTypeFeature, data, length); +} + +int HID_API_EXPORT hid_get_feature_report(hid_device *dev, unsigned char *data, size_t length) +{ + CFIndex len = length; + IOReturn res; + + /* Return if the device has been unplugged. */ + if (dev->disconnected) + return -1; + + res = IOHIDDeviceGetReport(dev->device_handle, + kIOHIDReportTypeFeature, + data[0], /* Report ID */ + data, &len); + if (res == kIOReturnSuccess) + return len; + else + return -1; +} + + +void HID_API_EXPORT hid_close(hid_device *dev) +{ + if (!dev) + return; + + /* Disconnect the report callback before close. */ + if (!dev->disconnected) { + IOHIDDeviceRegisterInputReportCallback( + dev->device_handle, dev->input_report_buf, dev->max_input_report_len, + NULL, dev); + IOHIDDeviceRegisterRemovalCallback(dev->device_handle, NULL, dev); + IOHIDDeviceUnscheduleFromRunLoop(dev->device_handle, dev->run_loop, dev->run_loop_mode); + IOHIDDeviceScheduleWithRunLoop(dev->device_handle, CFRunLoopGetMain(), kCFRunLoopDefaultMode); + } + + /* Cause read_thread() to stop. */ + dev->shutdown_thread = 1; + + /* Wake up the run thread's event loop so that the thread can exit. */ + CFRunLoopSourceSignal(dev->source); + CFRunLoopWakeUp(dev->run_loop); + + /* Notify the read thread that it can shut down now. */ + pthread_barrier_wait(&dev->shutdown_barrier); + + /* Wait for read_thread() to end. */ + pthread_join(dev->thread, NULL); + + /* Close the OS handle to the device, but only if it's not + been unplugged. If it's been unplugged, then calling + IOHIDDeviceClose() will crash. */ + if (!dev->disconnected) { + IOHIDDeviceClose(dev->device_handle, kIOHIDOptionsTypeSeizeDevice); + } + + /* Clear out the queue of received reports. */ + pthread_mutex_lock(&dev->mutex); + while (dev->input_reports) { + return_data(dev, NULL, 0); + } + pthread_mutex_unlock(&dev->mutex); + CFRelease(dev->device_handle); + + free_hid_device(dev); +} + +int HID_API_EXPORT_CALL hid_get_manufacturer_string(hid_device *dev, wchar_t *string, size_t maxlen) +{ + return get_manufacturer_string(dev->device_handle, string, maxlen); +} + +int HID_API_EXPORT_CALL hid_get_product_string(hid_device *dev, wchar_t *string, size_t maxlen) +{ + return get_product_string(dev->device_handle, string, maxlen); +} + +int HID_API_EXPORT_CALL hid_get_serial_number_string(hid_device *dev, wchar_t *string, size_t maxlen) +{ + return get_serial_number(dev->device_handle, string, maxlen); +} + +int HID_API_EXPORT_CALL hid_get_indexed_string(hid_device *dev, int string_index, wchar_t *string, size_t maxlen) +{ + /* TODO: */ + + return 0; +} + + +HID_API_EXPORT const wchar_t * HID_API_CALL hid_error(hid_device *dev) +{ + /* TODO: */ + + return NULL; +} + + + + + + + +#if 0 +static int32_t get_usage(IOHIDDeviceRef device) +{ + int32_t res; + res = get_int_property(device, CFSTR(kIOHIDDeviceUsageKey)); + if (!res) + res = get_int_property(device, CFSTR(kIOHIDPrimaryUsageKey)); + return res; +} +static int32_t get_usage_page(IOHIDDeviceRef device) +{ + int32_t res; + res = get_int_property(device, CFSTR(kIOHIDDeviceUsagePageKey)); + if (!res) + res = get_int_property(device, CFSTR(kIOHIDPrimaryUsagePageKey)); + return res; +} +static int get_transport(IOHIDDeviceRef device, wchar_t *buf, size_t len) +{ + return get_string_property(device, CFSTR(kIOHIDTransportKey), buf, len); +} +int main(void) +{ + IOHIDManagerRef mgr; + int i; + mgr = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone); + IOHIDManagerSetDeviceMatching(mgr, NULL); + IOHIDManagerOpen(mgr, kIOHIDOptionsTypeNone); + CFSetRef device_set = IOHIDManagerCopyDevices(mgr); + CFIndex num_devices = CFSetGetCount(device_set); + IOHIDDeviceRef *device_array = calloc(num_devices, sizeof(IOHIDDeviceRef)); + CFSetGetValues(device_set, (const void **) device_array); + for (i = 0; i < num_devices; i++) { + IOHIDDeviceRef dev = device_array[i]; + printf("Device: %p\n", dev); + printf(" %04hx %04hx\n", get_vendor_id(dev), get_product_id(dev)); + wchar_t serial[256], buf[256]; + char cbuf[256]; + get_serial_number(dev, serial, 256); + printf(" Serial: %ls\n", serial); + printf(" Loc: %ld\n", get_location_id(dev)); + get_transport(dev, buf, 256); + printf(" Trans: %ls\n", buf); + make_path(dev, cbuf, 256); + printf(" Path: %s\n", cbuf); + } + return 0; +} +#endif \ No newline at end of file diff --git a/qt5_src/HIDAPI/windows/hid.cpp b/qt5_src/HIDAPI/windows/hid.cpp new file mode 100644 index 0000000..66e4cb5 --- /dev/null +++ b/qt5_src/HIDAPI/windows/hid.cpp @@ -0,0 +1,792 @@ +/******************************************************* + HIDAPI - Multi-Platform library for + communication with HID devices. + + Alan Ott + Signal 11 Software + + 8/22/2009 + + Copyright 2009, All Rights Reserved. + + At the discretion of the user of this library, + this software may be licensed under the terms of the + GNU Public License v3, a BSD-Style license, or the + original HIDAPI license as outlined in the LICENSE.txt, + LICENSE-gpl3.txt, LICENSE-bsd.txt, and LICENSE-orig.txt + files located at the root of the source distribution. + These files may also be found in the public source + code repository located at: + http://github.com/signal11/hidapi . +********************************************************/ + +#include + +#ifdef __MINGW32__ +#include +#include +#endif + +#ifdef __CYGWIN__ +#include +#define _wcsdup wcsdup +#endif + +//#define HIDAPI_USE_DDK + +extern "C" { + #include + #include "WinIoCTL.h" + #ifdef HIDAPI_USE_DDK + #include + #endif + + // Copied from inc/ddk/hidclass.h, part of the Windows DDK. + #define HID_OUT_CTL_CODE(id) \ + CTL_CODE(FILE_DEVICE_KEYBOARD, (id), METHOD_OUT_DIRECT, FILE_ANY_ACCESS) + #define IOCTL_HID_GET_FEATURE HID_OUT_CTL_CODE(100) + +} +#include +#include + + +#include "../hidapi.h" + +#ifdef _MSC_VER + // Thanks Microsoft, but I know how to use strncpy(). + #pragma warning(disable:4996) +#endif + +extern "C" { + +#ifndef HIDAPI_USE_DDK + // Since we're not building with the DDK, and the HID header + // files aren't part of the SDK, we have to define all this + // stuff here. In lookup_functions(), the function pointers + // defined below are set. + typedef struct _HIDD_ATTRIBUTES{ + ULONG Size; + USHORT VendorID; + USHORT ProductID; + USHORT VersionNumber; + } HIDD_ATTRIBUTES, *PHIDD_ATTRIBUTES; + + typedef USHORT USAGE; + typedef struct _HIDP_CAPS { + USAGE Usage; + USAGE UsagePage; + USHORT InputReportByteLength; + USHORT OutputReportByteLength; + USHORT FeatureReportByteLength; + USHORT Reserved[17]; + USHORT fields_not_used_by_hidapi[10]; + } HIDP_CAPS, *PHIDP_CAPS; + typedef char* HIDP_PREPARSED_DATA; + #define HIDP_STATUS_SUCCESS 0x0 + + typedef BOOLEAN (__stdcall *HidD_GetAttributes_)(HANDLE device, PHIDD_ATTRIBUTES attrib); + typedef BOOLEAN (__stdcall *HidD_GetSerialNumberString_)(HANDLE device, PVOID buffer, ULONG buffer_len); + typedef BOOLEAN (__stdcall *HidD_GetManufacturerString_)(HANDLE handle, PVOID buffer, ULONG buffer_len); + typedef BOOLEAN (__stdcall *HidD_GetProductString_)(HANDLE handle, PVOID buffer, ULONG buffer_len); + typedef BOOLEAN (__stdcall *HidD_SetFeature_)(HANDLE handle, PVOID data, ULONG length); + typedef BOOLEAN (__stdcall *HidD_GetFeature_)(HANDLE handle, PVOID data, ULONG length); + typedef BOOLEAN (__stdcall *HidD_GetIndexedString_)(HANDLE handle, ULONG string_index, PVOID buffer, ULONG buffer_len); + typedef BOOLEAN (__stdcall *HidD_GetPreparsedData_)(HANDLE handle, HIDP_PREPARSED_DATA **preparsed_data); + typedef BOOLEAN (__stdcall *HidD_FreePreparsedData_)(HIDP_PREPARSED_DATA *preparsed_data); + typedef BOOLEAN (__stdcall *HidP_GetCaps_)(HIDP_PREPARSED_DATA *preparsed_data, HIDP_CAPS *caps); + + static HidD_GetAttributes_ HidD_GetAttributes; + static HidD_GetSerialNumberString_ HidD_GetSerialNumberString; + static HidD_GetManufacturerString_ HidD_GetManufacturerString; + static HidD_GetProductString_ HidD_GetProductString; + static HidD_SetFeature_ HidD_SetFeature; + static HidD_GetFeature_ HidD_GetFeature; + static HidD_GetIndexedString_ HidD_GetIndexedString; + static HidD_GetPreparsedData_ HidD_GetPreparsedData; + static HidD_FreePreparsedData_ HidD_FreePreparsedData; + static HidP_GetCaps_ HidP_GetCaps; + + static BOOLEAN initialized = FALSE; +#endif // HIDAPI_USE_DDK + +struct hid_device_ { + HANDLE device_handle; + BOOL blocking; + size_t input_report_length; + void *last_error_str; + DWORD last_error_num; + BOOL ioPending; +}; + +static hid_device *new_hid_device() +{ + hid_device *dev = (hid_device*) calloc(1, sizeof(hid_device)); + dev->device_handle = INVALID_HANDLE_VALUE; + dev->blocking = true; + dev->input_report_length = 0; + dev->last_error_str = NULL; + dev->last_error_num = 0; + dev->ioPending = false; + + return dev; +} + + +static void register_error(hid_device *device, const char *op) +{ + WCHAR *ptr, *msg; + + FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, + GetLastError(), + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPWSTR)&msg, 0/*sz*/, + NULL); + + // Get rid of the CR and LF that FormatMessage() sticks at the + // end of the message. Thanks Microsoft! + ptr = msg; + while (*ptr) { + if (*ptr == '\r') { + *ptr = 0x0000; + break; + } + ptr++; + } + + // Store the message off in the Device entry so that + // the hid_error() function can pick it up. + LocalFree(device->last_error_str); + device->last_error_str = msg; +} + +#ifndef HIDAPI_USE_DDK +static void lookup_functions() +{ + HMODULE lib = LoadLibraryA("hid.dll"); + if (lib) { +#define RESOLVE(x) x = (x##_)GetProcAddress(lib, #x); + RESOLVE(HidD_GetAttributes); + RESOLVE(HidD_GetSerialNumberString); + RESOLVE(HidD_GetManufacturerString); + RESOLVE(HidD_GetProductString); + RESOLVE(HidD_SetFeature); + RESOLVE(HidD_GetFeature); + RESOLVE(HidD_GetIndexedString); + RESOLVE(HidD_GetPreparsedData); + RESOLVE(HidD_FreePreparsedData); + RESOLVE(HidP_GetCaps); + //FreeLibrary(lib); +#undef RESOLVE + } + initialized = true; +} +#endif + +struct hid_device_info HID_API_EXPORT * HID_API_CALL hid_enumerate(unsigned short vendor_id, unsigned short product_id) +{ + BOOL res; + struct hid_device_info *root = NULL; // return object + struct hid_device_info *cur_dev = NULL; + +#ifndef HIDAPI_USE_DDK + if (!initialized) + lookup_functions(); +#endif + + // Windows objects for interacting with the driver. + GUID InterfaceClassGuid = {0x4d1e55b2, 0xf16f, 0x11cf, {0x88, 0xcb, 0x00, 0x11, 0x11, 0x00, 0x00, 0x30} }; + SP_DEVINFO_DATA devinfo_data; + SP_DEVICE_INTERFACE_DATA device_interface_data; + SP_DEVICE_INTERFACE_DETAIL_DATA_A *device_interface_detail_data = NULL; + HDEVINFO device_info_set = INVALID_HANDLE_VALUE; + + // Initialize the Windows objects. + devinfo_data.cbSize = sizeof(SP_DEVINFO_DATA); + device_interface_data.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA); + + + // Get information for all the devices belonging to the HID class. + device_info_set = SetupDiGetClassDevsA(&InterfaceClassGuid, NULL, NULL, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE); + + // Iterate over each device in the HID class, looking for the right one. + int device_index = 0; + for (;;) { + HANDLE write_handle = INVALID_HANDLE_VALUE; + DWORD required_size = 0; + + res = SetupDiEnumDeviceInterfaces(device_info_set, + NULL, + &InterfaceClassGuid, + device_index, + &device_interface_data); + + if (!res) { + // A return of FALSE from this function means that + // there are no more devices. + break; + } + + // Call with 0-sized detail size, and let the function + // tell us how long the detail struct needs to be. The + // size is put in &required_size. + res = SetupDiGetDeviceInterfaceDetailA(device_info_set, + &device_interface_data, + NULL, + 0, + &required_size, + NULL); + + // Allocate a long enough structure for device_interface_detail_data. + device_interface_detail_data = (SP_DEVICE_INTERFACE_DETAIL_DATA_A*) malloc(required_size); + device_interface_detail_data->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA_A); + + // Get the detailed data for this device. The detail data gives us + // the device path for this device, which is then passed into + // CreateFile() to get a handle to the device. + res = SetupDiGetDeviceInterfaceDetailA(device_info_set, + &device_interface_data, + device_interface_detail_data, + required_size, + NULL, + NULL); + + if (!res) { + //register_error(dev, "Unable to call SetupDiGetDeviceInterfaceDetail"); + // Continue to the next device. + goto cont; + } + + //wprintf(L"HandleName: %s\n", device_interface_detail_data->DevicePath); + + // Open a handle to the device + write_handle = CreateFileA(device_interface_detail_data->DevicePath, + GENERIC_WRITE |GENERIC_READ, + FILE_SHARE_READ|FILE_SHARE_WRITE, /*share mode*/ + NULL, + OPEN_EXISTING, + FILE_FLAG_OVERLAPPED,//FILE_ATTRIBUTE_NORMAL, + 0); + + // Check validity of write_handle. + if (write_handle == INVALID_HANDLE_VALUE) { + // Unable to open the device. + //register_error(dev, "CreateFile"); + goto cont_close; + } + + + // Get the Vendor ID and Product ID for this device. + HIDD_ATTRIBUTES attrib; + attrib.Size = sizeof(HIDD_ATTRIBUTES); + HidD_GetAttributes(write_handle, &attrib); + //wprintf(L"Product/Vendor: %x %x\n", attrib.ProductID, attrib.VendorID); + + // Check the VID/PID to see if we should add this + // device to the enumeration list. + if ((vendor_id == 0x0 && product_id == 0x0) || + (attrib.VendorID == vendor_id && attrib.ProductID == product_id)) { + + #define WSTR_LEN 512 + const char *str; + struct hid_device_info *tmp; + wchar_t wstr[WSTR_LEN]; // TODO: Determine Size + size_t len; + + /* VID/PID match. Create the record. */ + tmp = (hid_device_info*) calloc(1, sizeof(struct hid_device_info)); + if (cur_dev) { + cur_dev->next = tmp; + } + else { + root = tmp; + } + cur_dev = tmp; + + /* Fill out the record */ + cur_dev->next = NULL; + str = device_interface_detail_data->DevicePath; + if (str) { + len = strlen(str); + cur_dev->path = (char*) calloc(len+1, sizeof(char)); + strncpy(cur_dev->path, str, len+1); + cur_dev->path[len] = '\0'; + } + else + cur_dev->path = NULL; + + /* Serial Number */ + res = HidD_GetSerialNumberString(write_handle, wstr, sizeof(wstr)); + wstr[WSTR_LEN-1] = 0x0000; + if (res) { + cur_dev->serial_number = _wcsdup(wstr); + } + + /* Manufacturer String */ + res = HidD_GetManufacturerString(write_handle, wstr, sizeof(wstr)); + wstr[WSTR_LEN-1] = 0x0000; + if (res) { + cur_dev->manufacturer_string = _wcsdup(wstr); + } + + /* Product String */ + res = HidD_GetProductString(write_handle, wstr, sizeof(wstr)); + wstr[WSTR_LEN-1] = 0x0000; + if (res) { + cur_dev->product_string = _wcsdup(wstr); + } + + /* VID/PID */ + cur_dev->vendor_id = attrib.VendorID; + cur_dev->product_id = attrib.ProductID; + } + +cont_close: + CloseHandle(write_handle); +cont: + // We no longer need the detail data. It can be freed + free(device_interface_detail_data); + + device_index++; + + } + + // Close the device information handle. + SetupDiDestroyDeviceInfoList(device_info_set); + + return root; + +} + +void HID_API_EXPORT HID_API_CALL hid_free_enumeration(struct hid_device_info *devs) +{ + // TODO: Merge this with the Linux version. This function is platform-independent. + struct hid_device_info *d = devs; + while (d) { + struct hid_device_info *next = d->next; + free(d->path); + free(d->serial_number); + free(d->manufacturer_string); + free(d->product_string); + free(d); + d = next; + } +} + + +HID_API_EXPORT hid_device * HID_API_CALL hid_open(unsigned short vendor_id, unsigned short product_id, const wchar_t *serial_number) +{ + // TODO: Merge this functions with the Linux version. This function should be platform independent. + struct hid_device_info *devs, *cur_dev; + const char *path_to_open = NULL; + hid_device *handle = NULL; + + devs = hid_enumerate(vendor_id, product_id); + cur_dev = devs; + while (cur_dev) { + if (cur_dev->vendor_id == vendor_id && + cur_dev->product_id == product_id) { + if (serial_number) { + if (wcscmp(serial_number, cur_dev->serial_number) == 0) { + path_to_open = cur_dev->path; + break; + } + } + else { + path_to_open = cur_dev->path; + break; + } + } + cur_dev = cur_dev->next; + } + + if (path_to_open) { + /* Open the device */ + handle = hid_open_path(path_to_open); + } + + hid_free_enumeration(devs); + + return handle; +} + +HID_API_EXPORT hid_device * HID_API_CALL hid_open_path(const char *path) +{ + hid_device *dev; + HIDP_CAPS caps; + HIDP_PREPARSED_DATA *pp_data = NULL; + BOOLEAN res; + NTSTATUS nt_res; + +#ifndef HIDAPI_USE_DDK + if (!initialized) + lookup_functions(); +#endif + + dev = new_hid_device(); + + // Open a handle to the device + dev->device_handle = CreateFileA(path, + GENERIC_WRITE |GENERIC_READ, + FILE_SHARE_READ|FILE_SHARE_WRITE, /*share mode*/ + NULL, + OPEN_EXISTING, + FILE_FLAG_OVERLAPPED,//FILE_ATTRIBUTE_NORMAL, + 0); + + // Check validity of write_handle. + if (dev->device_handle == INVALID_HANDLE_VALUE) { + // Unable to open the device. + register_error(dev, "CreateFile"); + goto err; + } + + // Get the Input Report length for the device. + res = HidD_GetPreparsedData(dev->device_handle, &pp_data); + if (!res) { + register_error(dev, "HidD_GetPreparsedData"); + goto err; + } + nt_res = HidP_GetCaps(pp_data, &caps); + if (nt_res != HIDP_STATUS_SUCCESS) { + register_error(dev, "HidP_GetCaps"); + goto err_pp_data; + } + dev->input_report_length = caps.InputReportByteLength; + HidD_FreePreparsedData(pp_data); + + return dev; + +err_pp_data: + HidD_FreePreparsedData(pp_data); +err: + CloseHandle(dev->device_handle); + free(dev); + return NULL; +} + +int HID_API_EXPORT HID_API_CALL hid_write(hid_device *dev, const unsigned char *data, size_t length) +{ + DWORD bytes_written; + BOOL res; + + static OVERLAPPED ol; + +Check_For_Result: + if(!dev->blocking && dev->ioPending) + { + res = GetOverlappedResult(dev->device_handle, &ol, &bytes_written, FALSE/*don't wait*/); + if(!res) + { + return 0; + } + else + { + dev->ioPending = false; + return bytes_written; + } + } + + memset(&ol, 0, sizeof(ol)); + + res = WriteFile(dev->device_handle, data, length, &bytes_written, &ol); + + if (!res) { + if (GetLastError() != ERROR_IO_PENDING) { + // WriteFile() failed. Return error. + register_error(dev, "WriteFile"); + return -1; + } + } + + if(!dev->blocking) + { + dev->ioPending = true; + goto Check_For_Result; + } + else + { + // Wait here until the write is done. This makes + // hid_write() synchronous. + res = GetOverlappedResult(dev->device_handle, &ol, &bytes_written, TRUE/*wait*/); + if (!res) { + // The Write operation failed. + register_error(dev, "WriteFile"); + return -1; + } + } + + return bytes_written; +} + + +int HID_API_EXPORT HID_API_CALL hid_read(hid_device *dev, unsigned char *data, size_t length) +{ + DWORD bytes_read; + BOOL res; + + static OVERLAPPED ol; + +Check_For_Result: + if(!dev->blocking && dev->ioPending) + { + res = GetOverlappedResult(dev->device_handle, &ol, &bytes_read, FALSE/*don't wait*/); + if(!res) + { + return 0; + } + else + { + dev->ioPending = false; + if (bytes_read > 0 && data[0] == 0x0) { + /* If report numbers aren't being used, but Windows sticks a report + number (0x0) on the beginning of the report anyway. To make this + work like the other platforms, and to make it work more like the + HID spec, we'll skip over this byte. */ + bytes_read--; + memmove(data, data+1, bytes_read); + } + return bytes_read; + } + } + + memset(&ol, 0, sizeof(ol)); + + // Limit the data to be returned. This ensures we get + // only one report returned per call to hid_read(). + length = (length < dev->input_report_length)? length: dev->input_report_length; + + res = ReadFile(dev->device_handle, data, length, &bytes_read, &ol); + + if (!res) { + if (GetLastError() != ERROR_IO_PENDING) { + // ReadFile() failed. Return error. + register_error(dev, "ReadFile"); + return -1; + } + } + + if(!dev->blocking) + { + dev->ioPending = true; + goto Check_For_Result; + } + else + { + // Wait here until the write is done. This makes + // hid_write() synchronous. + res = GetOverlappedResult(dev->device_handle, &ol, &bytes_read, TRUE/*wait*/); + if (!res) { + // The Read operation failed. + register_error(dev, "ReadFile"); + return -1; + } + } + + return bytes_read; +} + +void HID_API_EXPORT HID_API_CALL hid_cancelIo(hid_device *dev) +{ + CancelIo(dev->device_handle); +} + +int HID_API_EXPORT HID_API_CALL hid_set_nonblocking(hid_device *dev, int nonblock) +{ + dev->blocking = !nonblock; + return 0; /* Success */ +} + +int HID_API_EXPORT HID_API_CALL hid_send_feature_report(hid_device *dev, const unsigned char *data, size_t length) +{ + BOOL res = HidD_SetFeature(dev->device_handle, (PVOID)data, length); + if (!res) { + register_error(dev, "HidD_SetFeature"); + return -1; + } + + return length; +} + + +int HID_API_EXPORT HID_API_CALL hid_get_feature_report(hid_device *dev, unsigned char *data, size_t length) +{ + BOOL res; +#if 0 + res = HidD_GetFeature(dev->device_handle, data, length); + if (!res) { + register_error(dev, "HidD_GetFeature"); + return -1; + } + return 0; /* HidD_GetFeature() doesn't give us an actual length, unfortunately */ +#else + DWORD bytes_returned; + + OVERLAPPED ol; + memset(&ol, 0, sizeof(ol)); + + res = DeviceIoControl(dev->device_handle, + IOCTL_HID_GET_FEATURE, + data, length, + data, length, + &bytes_returned, &ol); + + if (!res) { + if (GetLastError() != ERROR_IO_PENDING) { + // DeviceIoControl() failed. Return error. + register_error(dev, "Send Feature Report DeviceIoControl"); + return -1; + } + } + + // Wait here until the write is done. This makes + // hid_get_feature_report() synchronous. + res = GetOverlappedResult(dev->device_handle, &ol, &bytes_returned, TRUE/*wait*/); + if (!res) { + // The operation failed. + register_error(dev, "Send Feature Report GetOverLappedResult"); + return -1; + } + return bytes_returned; +#endif +} + +void HID_API_EXPORT HID_API_CALL hid_close(hid_device *dev) +{ + if (!dev) + return; + CloseHandle(dev->device_handle); + LocalFree(dev->last_error_str); + free(dev); +} + +int HID_API_EXPORT_CALL HID_API_CALL hid_get_manufacturer_string(hid_device *dev, wchar_t *string, size_t maxlen) +{ + BOOL res; + + res = HidD_GetManufacturerString(dev->device_handle, string, 2 * maxlen); + if (!res) { + register_error(dev, "HidD_GetManufacturerString"); + return -1; + } + + return 0; +} + +int HID_API_EXPORT_CALL HID_API_CALL hid_get_product_string(hid_device *dev, wchar_t *string, size_t maxlen) +{ + BOOL res; + + res = HidD_GetProductString(dev->device_handle, string, 2 * maxlen); + if (!res) { + register_error(dev, "HidD_GetProductString"); + return -1; + } + + return 0; +} + +int HID_API_EXPORT_CALL HID_API_CALL hid_get_serial_number_string(hid_device *dev, wchar_t *string, size_t maxlen) +{ + BOOL res; + + res = HidD_GetSerialNumberString(dev->device_handle, string, 2 * maxlen); + if (!res) { + register_error(dev, "HidD_GetSerialNumberString"); + return -1; + } + + return 0; +} + +int HID_API_EXPORT_CALL HID_API_CALL hid_get_indexed_string(hid_device *dev, int string_index, wchar_t *string, size_t maxlen) +{ + BOOL res; + + res = HidD_GetIndexedString(dev->device_handle, string_index, string, 2 * maxlen); + if (!res) { + register_error(dev, "HidD_GetIndexedString"); + return -1; + } + + return 0; +} + + +HID_API_EXPORT const wchar_t * HID_API_CALL hid_error(hid_device *dev) +{ + return (wchar_t*)dev->last_error_str; +} + + +//#define PICPGM +//#define S11 +#define P32 +#ifdef S11 + unsigned short VendorID = 0xa0a0; + unsigned short ProductID = 0x0001; +#endif + +#ifdef P32 + unsigned short VendorID = 0x04d8; + unsigned short ProductID = 0x3f; +#endif + + +#ifdef PICPGM + unsigned short VendorID = 0x04d8; + unsigned short ProductID = 0x0033; +#endif + + +#if 0 +int __cdecl main(int argc, char* argv[]) +{ + int res; + unsigned char buf[65]; + + UNREFERENCED_PARAMETER(argc); + UNREFERENCED_PARAMETER(argv); + + // Set up the command buffer. + memset(buf,0x00,sizeof(buf)); + buf[0] = 0; + buf[1] = 0x81; + + + // Open the device. + int handle = open(VendorID, ProductID, L"12345"); + if (handle < 0) + printf("unable to open device\n"); + + + // Toggle LED (cmd 0x80) + buf[1] = 0x80; + res = write(handle, buf, 65); + if (res < 0) + printf("Unable to write()\n"); + + // Request state (cmd 0x81) + buf[1] = 0x81; + write(handle, buf, 65); + if (res < 0) + printf("Unable to write() (2)\n"); + + // Read requested state + read(handle, buf, 65); + if (res < 0) + printf("Unable to read()\n"); + + // Print out the returned buffer. + for (int i = 0; i < 4; i++) + printf("buf[%d]: %d\n", i, buf[i]); + + return 0; +} +#endif + +} // extern "C" + diff --git a/qt5_src/HIDBootloader.pro b/qt5_src/HIDBootloader.pro new file mode 100644 index 0000000..381be36 --- /dev/null +++ b/qt5_src/HIDBootloader.pro @@ -0,0 +1,6 @@ +TEMPLATE = subdirs +CONFIG += ordered + +SUBDIRS = \ + HIDAPI \ + Bootloader diff --git a/qt5_src/README.txt b/qt5_src/README.txt new file mode 100644 index 0000000..bc3f3a6 --- /dev/null +++ b/qt5_src/README.txt @@ -0,0 +1,6 @@ +The application in this folder was build with QT, an open source cross-platform enviornment. +The license information for QT and its components can be found at http://qt-project.org/ + + +This project was built and tested with the Qt 5.0.2 for Windows 32-bit (using the MinGW 4.7 compiler). +The project has not been tested with other versions of Qt or with the MSVC compiler. \ No newline at end of file diff --git a/qt5_src/version.h b/qt5_src/version.h new file mode 100644 index 0000000..bf3f00f --- /dev/null +++ b/qt5_src/version.h @@ -0,0 +1,3 @@ + +#define APPLICATION "USB Bootloader" +#define VERSION "2.15"