/* * DFU routines for Windows. * * Copyright (C) 2018 Serge Vakulenko, KK6ABQ * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include #include #include #include "util.h" // // DFU request types. // #define PU_VENDOR_REQUEST CTL_CODE(FILE_DEVICE_UNKNOWN, 0x0805, METHOD_BUFFERED, FILE_ANY_ACCESS) #define URB_FUNCTION_CLASS_INTERFACE 0x001B #define VENDOR_DIRECTION_IN 1 #define VENDOR_DIRECTION_OUT 0 enum { REQUEST_DETACH = 0, REQUEST_DNLOAD = 1, REQUEST_UPLOAD = 2, REQUEST_GETSTATUS = 3, REQUEST_CLRSTATUS = 4, REQUEST_GETSTATE = 5, REQUEST_ABORT = 6, }; enum { appIDLE = 0, appDETACH = 1, dfuIDLE = 2, dfuDNLOAD_SYNC = 3, dfuDNBUSY = 4, dfuDNLOAD_IDLE = 5, dfuMANIFEST_SYNC = 6, dfuMANIFEST = 7, dfuMANIFEST_WAIT_RESET = 8, dfuUPLOAD_IDLE = 9, dfuERROR = 10, }; typedef struct { unsigned status : 8; unsigned poll_timeout : 24; unsigned state : 8; unsigned string_index : 8; } status_t; typedef struct { USHORT Function; ULONG Direction; UCHAR Request; USHORT Value; USHORT Index; ULONG Length; } CNTRPIPE_RQ, *PCNTRPIPE_RQ; static HANDLE dev; static status_t status; static int dev_request(int request, int value) { CNTRPIPE_RQ rq; DWORD nbytes = 0; rq.Function = URB_FUNCTION_CLASS_INTERFACE; rq.Direction = VENDOR_DIRECTION_OUT; rq.Request = request; rq.Value = value; rq.Index = 0; rq.Length = 0; if (!DeviceIoControl(dev, PU_VENDOR_REQUEST, &rq, sizeof(rq), NULL, 0, &nbytes, NULL)) { return -1; } return 0; } static int dev_write(int request, int value, int length, PBYTE data) { int rqlen = sizeof(CNTRPIPE_RQ) + length; PCNTRPIPE_RQ rq = alloca(rqlen); DWORD nbytes = 0; rq->Function = URB_FUNCTION_CLASS_INTERFACE; rq->Direction = VENDOR_DIRECTION_OUT; rq->Request = request; rq->Value = value; rq->Index = 0; rq->Length = length; memcpy(rq + 1, data, length); if (!DeviceIoControl(dev, PU_VENDOR_REQUEST, rq, rqlen, NULL, 0, &nbytes, NULL)) { return -1; } return 0; } static int dev_read(int request, int value, int length, PBYTE buffer) { CNTRPIPE_RQ rq; DWORD nbytes = 0; rq.Function = URB_FUNCTION_CLASS_INTERFACE; rq.Direction = VENDOR_DIRECTION_IN; rq.Request = request; rq.Value = value; rq.Index = 0; rq.Length = length; if (!DeviceIoControl(dev, PU_VENDOR_REQUEST, &rq, sizeof(rq), buffer, length, &nbytes, NULL)) { return -1; } if (nbytes != length) { return -1; } return 0; } static int detach(int timeout) { if (trace_flag) { printf("--- Send DETACH\n"); } int error = dev_request(REQUEST_DETACH, timeout); return error; } static int get_status() { if (trace_flag) { printf("--- Send GETSTATUS [6]\n"); } int error = dev_read(REQUEST_GETSTATUS, 0, 6, (unsigned char*)&status); if (trace_flag && error >= 0) { printf("--- Recv "); print_hex((unsigned char*)&status, 6); printf("\n"); } return error; } static int clear_status() { if (trace_flag) { printf("--- Send CLRSTATUS\n"); } int error = dev_request(REQUEST_CLRSTATUS, 0); return error; } static int get_state(int *pstate) { unsigned char state; if (trace_flag) { printf("--- Send GETSTATE [1]\n"); } int error = dev_read(REQUEST_GETSTATE, 0, 1, &state); *pstate = state; if (trace_flag && error >= 0) { printf("--- Recv "); print_hex(&state, 1); printf("\n"); } return error; } static int dfu_abort() { if (trace_flag) { printf("--- Send ABORT\n"); } int error = dev_request(REQUEST_ABORT, 0); return error; } static void wait_dfu_idle() { int state, error; for (;;) { error = get_state(&state); if (error < 0) { fprintf(stderr, "%s: cannot get state\n", __func__); exit(-1); } switch (state) { case dfuIDLE: return; case appIDLE: error = detach(5000); break; case dfuERROR: error = clear_status(); break; case appDETACH: case dfuDNBUSY: case dfuMANIFEST_WAIT_RESET: usleep(100000); continue; default: error = dfu_abort(); break; } if (error < 0) { fprintf(stderr, "%s: unexpected error in state=%d\n", __func__, state); exit(-1); } } } static void md380_command(uint8_t a, uint8_t b) { unsigned char cmd[2] = { a, b }; if (trace_flag) { printf("--- Send DNLOAD [2] "); print_hex(cmd, 2); printf("\n"); } int error = dev_write(REQUEST_DNLOAD, 0, 2, cmd); if (error < 0) { fprintf(stderr, "%s: cannot send command\n", __func__); exit(-1); } get_status(); usleep(100000); wait_dfu_idle(); } static void set_address(uint32_t address) { unsigned char cmd[5] = { 0x21, (uint8_t)address, (uint8_t)(address >> 8), (uint8_t)(address >> 16), (uint8_t)(address >> 24), }; if (trace_flag) { printf("--- Send DNLOAD [5] "); print_hex(cmd, 5); printf("\n"); } int error = dev_write(REQUEST_DNLOAD, 0, 5, cmd); if (error < 0) { fprintf(stderr, "%s: cannot send command\n", __func__); exit(-1); } get_status(); wait_dfu_idle(); } static void erase_block(uint32_t address, int progress_flag) { unsigned char cmd[5] = { 0x41, (uint8_t)address, (uint8_t)(address >> 8), (uint8_t)(address >> 16), (uint8_t)(address >> 24), }; if (trace_flag) { printf("--- Send DNLOAD [5] "); print_hex(cmd, 5); printf("\n"); } int error = dev_write(REQUEST_DNLOAD, 0, 5, cmd); if (error < 0) { fprintf(stderr, "%s: cannot send command\n", __func__); exit(-1); } get_status(); wait_dfu_idle(); if (progress_flag) { fprintf(stderr, "#"); fflush(stderr); } } static const char *identify() { static uint8_t data[64]; md380_command(0xa2, 0x01); if (trace_flag) { printf("--- Send UPLOAD [64]\n"); } int error = dev_read(REQUEST_UPLOAD, 0, 64, data); if (error < 0) { fprintf(stderr, "%s: cannot read data\n", __func__); exit(-1); } if (trace_flag) { printf("--- Recv "); print_hex(data, 64); printf("\n"); } get_status(); return (const char*) data; } // // Find path for a device with a given GUID. // Return the first path found (dynamically allocated). // Return 0 when no device with such GUID is present. // static char *find_path(GUID *guid) { char *path = 0; HDEVINFO devinfo = SetupDiGetClassDevs(guid, NULL, NULL, DIGCF_PRESENT | DIGCF_INTERFACEDEVICE); if (devinfo == INVALID_HANDLE_VALUE) { printf("Cannot get devinfo!\n"); return 0; } // Loop through available devices with a given GUID. int index; SP_INTERFACE_DEVICE_DATA iface; iface.cbSize = sizeof(iface); for (index=0; SetupDiEnumDeviceInterfaces(devinfo, NULL, guid, index, &iface); ++index) { // Obtain a required size of device detail structure. DWORD needed; SetupDiGetDeviceInterfaceDetail(devinfo, &iface, NULL, 0, &needed, NULL); // Allocate the device detail structure. PSP_INTERFACE_DEVICE_DETAIL_DATA detail = (PSP_INTERFACE_DEVICE_DETAIL_DATA)alloca(needed); detail->cbSize = sizeof(SP_INTERFACE_DEVICE_DETAIL_DATA); SP_DEVINFO_DATA did = { sizeof(SP_DEVINFO_DATA) }; // Get device information. if (SetupDiGetDeviceInterfaceDetail(devinfo, &iface, detail, needed, NULL, &did)) { // Use the first device found. //printf("Device %d: path %s\n", index, detail->DevicePath); path = strdup(detail->DevicePath); break; } printf("Device %d: cannot get path!\n", index); } SetupDiDestroyDeviceInfoList(devinfo); return path; } const char *dfu_init(unsigned vid, unsigned pid) { static GUID guid_0483_df11 = { 0x3fe809ab, 0xfb91, 0x4cb5, { 0xa6, 0x43, 0x69, 0x67, 0x0d, 0x52, 0x36, 0x6e } }; char *path = 0; // Find path for device. if (vid == 0x0483 && pid == 0xdf11) { path = find_path(&guid_0483_df11); } else { fprintf(stderr, "No guid for vid=%04x, pid=%04x!\n", vid, pid); exit(-1); } if (!path) { if (trace_flag) { fprintf(stderr, "Cannot find DFU device %04x:%04x\n", vid, pid); } return 0; } // Open the device. dev = CreateFile(path, GENERIC_WRITE | GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, NULL); if (! dev) { printf("%s: Cannot open\n", path); exit(-1); } // Deallocate path. free(path); path = 0; // Enter Programming Mode. wait_dfu_idle(); md380_command(0x91, 0x01); // Get device identifier in a static buffer. const char *ident = identify(); // Zero address. set_address(0x00000000); return ident; } void dfu_close() { if (dev) { CloseHandle(dev); dev = 0; } } void dfu_erase(unsigned start, unsigned finish) { // Enter Programming Mode. get_status(); wait_dfu_idle(); md380_command(0x91, 0x01); usleep(100000); if (start == 0) { // Erase 256kbytes of configuration memory. erase_block(0x00000000, 1); erase_block(0x00010000, 1); erase_block(0x00020000, 1); erase_block(0x00030000, 1); if (finish > 256*1024) { // Erase 768kbytes of extended configuration memory. erase_block(0x00110000, 1); erase_block(0x00120000, 1); erase_block(0x00130000, 1); erase_block(0x00140000, 1); erase_block(0x00150000, 1); erase_block(0x00160000, 1); erase_block(0x00170000, 1); erase_block(0x00180000, 1); erase_block(0x00190000, 1); erase_block(0x001a0000, 1); erase_block(0x001b0000, 1); erase_block(0x001c0000, 1); erase_block(0x001d0000, 1); } } else { // Erase callsign database. int addr; for (addr=start; addr= 256 && bno < 2048) bno += 832; if (trace_flag) { printf("--- Send UPLOAD [%d]\n", nbytes); } int error = dev_read(REQUEST_UPLOAD, bno+2, nbytes, data); if (error < 0) { fprintf(stderr, "%s: cannot read block %d, nbytes = %d\n", __func__, bno, nbytes); exit(-1); } if (trace_flag > 1) { printf("--- Recv "); print_hex(data, nbytes); printf("\n"); } get_status(); } void dfu_write_block(int bno, uint8_t *data, int nbytes) { if (bno >= 256 && bno < 2048) bno += 832; if (trace_flag) { printf("--- Send DNLOAD [%d] ", nbytes); if (trace_flag > 1) print_hex(data, nbytes); printf("\n"); } int error = dev_write(REQUEST_DNLOAD, bno+2, nbytes, data); if (error < 0) { fprintf(stderr, "%s: cannot write block %d, nbytes = %d\n", __func__, bno, nbytes); exit(-1); } get_status(); wait_dfu_idle(); } void dfu_reboot() { unsigned char cmd[2] = { 0x91, 0x05 }; if (!dev) return; if (trace_flag) { printf("--- Send DNLOAD [2] "); print_hex(cmd, 2); printf("\n"); } wait_dfu_idle(); int error = dev_write(REQUEST_DNLOAD, 0, 2, cmd); if (error < 0) { fprintf(stderr, "%s: cannot send command\n", __func__); exit(-1); } get_status(); }