| /*************************************************************************** |
| * __________ __ ___. |
| * Open \______ \ ____ ____ | | _\_ |__ _______ ___ |
| * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / |
| * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < |
| * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ |
| * \/ \/ \/ \/ \/ |
| * $Id$ |
| * |
| * Copyright (C) 2015 by Cástor Muñoz |
| * |
| * based on: |
| * ipoddfu_c by user890104 |
| * xpwn/pwnmetheus2 |
| * |
| * This program is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU General Public License |
| * as published by the Free Software Foundation; either version 2 |
| * of the License, or (at your option) any later version. |
| * |
| * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY |
| * KIND, either express or implied. |
| * |
| ****************************************************************************/ |
| |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <unistd.h> |
| #include <stdbool.h> |
| #include <string.h> |
| #ifdef WIN32 |
| #include <windows.h> |
| #include <setupapi.h> |
| #endif |
| #ifdef USE_LIBUSBAPI |
| #include <libusb-1.0/libusb.h> |
| #endif |
| #ifdef __APPLE__ |
| #include <CoreFoundation/CoreFoundation.h> |
| #include <IOKit/IOCFPlugIn.h> |
| #include <IOKit/usb/IOUSBLib.h> |
| #endif |
| |
| #include "mks5lboot.h" |
| |
| |
| #ifdef WIN32 |
| #define sleep_ms(ms) Sleep(ms) |
| #else |
| #include <time.h> |
| static void sleep_ms(unsigned int ms) |
| { |
| struct timespec req; |
| req.tv_sec = ms / 1000; |
| req.tv_nsec = (ms % 1000) * 1000000; |
| nanosleep(&req, NULL); |
| } |
| #endif |
| |
| static void put_uint32le(unsigned char* p, uint32_t x) |
| { |
| p[0] = x & 0xff; |
| p[1] = (x >> 8) & 0xff; |
| p[2] = (x >> 16) & 0xff; |
| p[3] = (x >> 24) & 0xff; |
| } |
| |
| /* |
| * CRC32 functions |
| * Based on public domain implementation by Finn Yannick Jacobs. |
| * |
| * Written and copyright 1999 by Finn Yannick Jacobs |
| * No rights were reserved to this, so feel free to |
| * manipulate or do with it, what you want or desire :) |
| */ |
| |
| /* crc32table[] built by crc32_init() */ |
| static uint32_t crc32table[256]; |
| |
| /* Calculate crc32 */ |
| static uint32_t crc32(void *data, unsigned int len, uint32_t previousCrc32) |
| { |
| uint32_t crc = ~previousCrc32; |
| unsigned char *d = (unsigned char*) data; |
| while (len--) |
| crc = (crc >> 8) ^ crc32table[(crc & 0xFF) ^ *d++]; |
| return ~crc; |
| } |
| |
| /* Calculate crc32table */ |
| static void crc32_init() |
| { |
| uint32_t poly = 0xEDB88320L; |
| uint32_t crc; |
| int i, j; |
| for (i = 0; i < 256; ++i) |
| { |
| crc = i; |
| for (j = 0; j < 8; ++j) |
| crc = (crc >> 1) ^ ((crc & 1) ? poly : 0); |
| crc32table[i] = crc; |
| } |
| } |
| |
| /* USB */ |
| #define APPLE_VID 0x05AC |
| |
| struct pid_info { |
| int pid; |
| int mode; /* 0->DFU, 1->WTF */ |
| char *desc; |
| }; |
| |
| struct pid_info known_pids[] = |
| { |
| /* DFU */ |
| { 0x1220, 0, "Nano 2G" }, |
| { 0x1223, 0, "Nano 3G / Classic" }, |
| { 0x1224, 0, "Shuffle 3G" }, |
| { 0x1225, 0, "Nano 4G" }, |
| { 0x1231, 0, "Nano 5G" }, |
| { 0x1232, 0, "Nano 6G" }, |
| { 0x1233, 0, "Shuffle 4G" }, |
| { 0x1234, 0, "Nano 7G" }, |
| /* WTF */ |
| { 0x1240, 1, "Nano 2G" }, |
| { 0x1241, 1, "Classic 1G" }, |
| { 0x1242, 1, "Nano 3G" }, |
| { 0x1243, 1, "Nano 4G" }, |
| { 0x1245, 1, "Classic 2G" }, |
| { 0x1246, 1, "Nano 5G" }, |
| { 0x1247, 1, "Classic 3G" }, |
| { 0x1248, 1, "Nano 6G" }, |
| { 0x1249, 1, "Nano 7G" }, |
| { 0x124a, 1, "Nano 7G" }, |
| { 0x1250, 1, "Classic 4G" }, |
| }; |
| #define N_KNOWN_PIDS (sizeof(known_pids)/sizeof(struct pid_info)) |
| |
| struct usbControlSetup { |
| uint8_t bmRequestType; |
| uint8_t bRequest; |
| uint16_t wValue; |
| uint16_t wIndex; |
| uint16_t wLength; |
| } __attribute__ ((packed)); |
| #define USB_CS_SZ (sizeof(struct usbControlSetup)) |
| |
| struct usbStatusData { |
| uint8_t bStatus; |
| uint8_t bwPollTimeout0; |
| uint8_t bwPollTimeout1; |
| uint8_t bwPollTimeout2; |
| uint8_t bState; |
| uint8_t iString; |
| } __attribute__ ((packed)); |
| |
| |
| /* |
| * DFU API |
| */ |
| #define DFU_PKT_SZ 2048 /* must be pow2 <= wTransferSize (2048) */ |
| |
| /* DFU 1.1 specs */ |
| typedef 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 |
| } DFUState; |
| |
| typedef enum { |
| errNONE = 0, |
| errTARGET = 1, |
| errFILE = 2, |
| errWRITE = 3, |
| errERASE = 4, |
| errCHECK_ERASED = 5, |
| errPROG = 6, |
| errVERIFY = 7, |
| errADDRESS = 8, |
| errNOTDONE = 9, |
| errFIRMWARE = 10, |
| errVENDOR = 11, |
| errUSBR = 12, |
| errPOR = 13, |
| errUNKNOWN = 14, |
| errSTALLEDPKT = 15 |
| } DFUStatus; |
| |
| typedef enum { |
| DFU_DETACH = 0, |
| DFU_DNLOAD = 1, |
| DFU_UPLOAD = 2, |
| DFU_GETSTATUS = 3, |
| DFU_CLRSTATUS = 4, |
| DFU_GETSTATE = 5, |
| DFU_ABORT = 6 |
| } DFURequest; |
| |
| typedef enum { |
| DFUAPIFail = 0, |
| DFUAPISuccess, |
| } dfuAPIResult; |
| |
| struct dfuDev { |
| struct dfuAPI *api; |
| int found_pid; |
| int detached; |
| char descr[256]; |
| dfuAPIResult res; |
| char err[256]; |
| /* API private */ |
| #ifdef WIN32 |
| HANDLE fh; |
| HANDLE ph; |
| DWORD ec; /* winapi error code */ |
| #endif |
| #ifdef USE_LIBUSBAPI |
| libusb_context* ctx; |
| libusb_device_handle* devh; |
| int rc; /* libusb return code */ |
| #endif |
| #ifdef __APPLE__ |
| IOUSBDeviceInterface** dev; |
| kern_return_t kr; |
| #endif |
| }; |
| |
| struct dfuAPI { |
| char *name; |
| dfuAPIResult (*open_fn)(struct dfuDev*, int*); |
| dfuAPIResult (*dfureq_fn)(struct dfuDev*, struct usbControlSetup*, void*); |
| dfuAPIResult (*reset_fn)(struct dfuDev*); |
| void (*close_fn)(struct dfuDev*); |
| }; |
| |
| |
| /* |
| * DFU API low-level (specific) functions |
| */ |
| static bool dfu_check_id(int vid, int pid, int *pid_list) |
| { |
| int *p; |
| if (vid != APPLE_VID) |
| return 0; |
| for (p = pid_list; *p; p++) |
| if (*p == pid) |
| return 1; |
| return 0; |
| } |
| |
| /* adds extra DFU request error info */ |
| static void dfu_add_reqerrstr(struct dfuDev *dfuh, struct usbControlSetup *cs) |
| { |
| snprintf(dfuh->err + strlen(dfuh->err), |
| sizeof(dfuh->err) - strlen(dfuh->err), " (cs=%02x/%d/%d/%d/%d)", |
| cs->bmRequestType, cs->bRequest, cs->wValue, cs->wIndex, cs->wLength); |
| } |
| |
| #ifdef WIN32 |
| static bool dfu_winapi_chkrc(struct dfuDev *dfuh, char *str, bool success) |
| { |
| dfuh->res = (success) ? DFUAPISuccess : DFUAPIFail; |
| if (!success) { |
| dfuh->ec = GetLastError(); |
| snprintf(dfuh->err, sizeof(dfuh->err), "%s error %ld", str, dfuh->ec); |
| } |
| return success; |
| } |
| |
| static dfuAPIResult dfu_winapi_request(struct dfuDev *dfuh, |
| struct usbControlSetup* cs, void* data) |
| { |
| unsigned char buf[USB_CS_SZ + DFU_PKT_SZ]; |
| DWORD rdwr; |
| bool rc; |
| |
| memcpy(buf, cs, USB_CS_SZ); |
| |
| if (cs->bmRequestType & 0x80) |
| { |
| rc = ReadFile(dfuh->ph, buf, USB_CS_SZ + cs->wLength, &rdwr, NULL); |
| memcpy(data, buf+USB_CS_SZ, cs->wLength); |
| dfu_winapi_chkrc(dfuh, "DFU request failed: ReadFile()", rc); |
| } |
| else |
| { |
| memcpy(buf+USB_CS_SZ, data, cs->wLength); |
| rc = WriteFile(dfuh->ph, buf, USB_CS_SZ + cs->wLength, &rdwr, NULL); |
| dfu_winapi_chkrc(dfuh, "DFU request failed: WriteFile()", rc); |
| } |
| if (!rc) |
| dfu_add_reqerrstr(dfuh, cs); |
| |
| return dfuh->res; |
| } |
| |
| static dfuAPIResult dfu_winapi_reset(struct dfuDev *dfuh) |
| { |
| DWORD bytesReturned; |
| bool rc = DeviceIoControl(dfuh->fh, |
| 0x22000c, NULL, 0, NULL, 0, &bytesReturned, NULL); |
| dfu_winapi_chkrc(dfuh, |
| "Could not reset USB device: DeviceIoControl()", rc); |
| return dfuh->res; |
| } |
| |
| static void dfu_winapi_close(struct dfuDev *dfuh) |
| { |
| if (dfuh->fh != INVALID_HANDLE_VALUE) { |
| CloseHandle(dfuh->fh); |
| dfuh->fh = INVALID_HANDLE_VALUE; |
| } |
| if (dfuh->ph != INVALID_HANDLE_VALUE) { |
| CloseHandle(dfuh->ph); |
| dfuh->ph = INVALID_HANDLE_VALUE; |
| } |
| } |
| |
| static const GUID GUID_AAPLDFU = |
| { 0xB8085869L, 0xFEB9, 0x404B, {0x8C, 0xB1, 0x1E, 0x5C, 0x14, 0xFA, 0x8C, 0x54}}; |
| |
| static dfuAPIResult dfu_winapi_open(struct dfuDev *dfuh, int *pid_list) |
| { |
| const GUID *guid = &GUID_AAPLDFU; |
| HDEVINFO devinfo = NULL; |
| SP_DEVICE_INTERFACE_DETAIL_DATA_A* details = NULL; |
| SP_DEVICE_INTERFACE_DATA iface; |
| char *path = NULL; |
| DWORD i, size; |
| bool rc; |
| |
| dfuh->fh = |
| dfuh->ph = INVALID_HANDLE_VALUE; |
| dfuh->found_pid = 0; |
| dfuh->res = DFUAPISuccess; |
| dfuh->ec = 0; |
| |
| /* Get DFU path */ |
| devinfo = SetupDiGetClassDevsA(guid, NULL, NULL, |
| DIGCF_PRESENT | DIGCF_DEVICEINTERFACE); |
| if (!dfu_winapi_chkrc(dfuh, "SetupDiGetClassDevsA()", |
| (devinfo != INVALID_HANDLE_VALUE))) |
| goto error; |
| |
| iface.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA); |
| |
| for (i = 0; SetupDiEnumDeviceInterfaces(devinfo, NULL, guid, i, &iface); i++) |
| { |
| int vid, pid; |
| |
| SetupDiGetDeviceInterfaceDetailA(devinfo, &iface, NULL, 0, &size, NULL); |
| |
| if (details) free(details); |
| details = (SP_DEVICE_INTERFACE_DETAIL_DATA_A*) malloc(size); |
| details->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA_A); |
| rc = SetupDiGetDeviceInterfaceDetailA(devinfo, &iface, details, size, NULL, NULL); |
| if (!dfu_winapi_chkrc(dfuh, "SetupDiGetDeviceInterfaceDetailA()", rc)) |
| goto error; |
| |
| CharUpperA(details->DevicePath); |
| if (sscanf(details->DevicePath, "%*4cUSB#VID_%04x&PID_%04x%*s", &vid, &pid) != 2) |
| continue; |
| if (!dfu_check_id(vid, pid, pid_list)) |
| continue; |
| |
| if (path) free(path); |
| path = malloc(size - sizeof(DWORD) + 16); |
| memcpy(path, details->DevicePath, size - sizeof(DWORD)); |
| |
| dfuh->fh = CreateFileA(path, GENERIC_READ|GENERIC_WRITE, |
| FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL); |
| if (!dfu_winapi_chkrc(dfuh, "CreateFileA(fh)", (dfuh->fh != INVALID_HANDLE_VALUE))) |
| goto error; |
| |
| strcat(path, "\\PIPE0"); |
| dfuh->ph = CreateFileA(path, GENERIC_READ|GENERIC_WRITE, |
| FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL); |
| if (!dfu_winapi_chkrc(dfuh, "CreateFileA(ph)", (dfuh->ph != INVALID_HANDLE_VALUE))) |
| goto error; |
| |
| /* ok */ |
| snprintf(dfuh->descr, sizeof(dfuh->descr), "%s", details->DevicePath); |
| dfuh->found_pid = pid; |
| goto bye; |
| } |
| |
| if (!dfu_winapi_chkrc(dfuh, "SetupDiEnumDeviceInterfaces()", |
| (GetLastError() == ERROR_NO_MORE_ITEMS))) |
| goto error; |
| |
| /* no devices found */ |
| |
| bye: |
| if (path) free(path); |
| if (details) free(details); |
| if (devinfo) SetupDiDestroyDeviceInfoList(devinfo); |
| return dfuh->res; |
| |
| error: |
| dfu_winapi_close(dfuh); |
| goto bye; |
| } |
| #endif /* WIN32 */ |
| |
| #ifdef USE_LIBUSBAPI |
| static bool dfu_libusb_chkrc(struct dfuDev *dfuh, char *str) |
| { |
| dfuh->res = (dfuh->rc < LIBUSB_SUCCESS) ? DFUAPIFail : DFUAPISuccess; |
| if (dfuh->res == DFUAPIFail) |
| snprintf(dfuh->err, sizeof(dfuh->err), |
| "%s: %s", str, libusb_error_name(dfuh->rc)); |
| return (dfuh->res == DFUAPISuccess); |
| } |
| |
| static dfuAPIResult dfu_libusb_request(struct dfuDev *dfuh, |
| struct usbControlSetup *cs, void *data) |
| { |
| dfuh->rc = libusb_control_transfer(dfuh->devh, cs->bmRequestType, |
| cs->bRequest, cs->wValue, cs->wIndex, data, cs->wLength, 500); |
| if (!dfu_libusb_chkrc(dfuh, "DFU request failed")) |
| dfu_add_reqerrstr(dfuh, cs); |
| return dfuh->res; |
| } |
| |
| static dfuAPIResult dfu_libusb_reset(struct dfuDev *dfuh) |
| { |
| dfuh->rc = libusb_reset_device(dfuh->devh); |
| dfu_libusb_chkrc(dfuh, "Could not reset USB device"); |
| return dfuh->res; |
| } |
| |
| static void dfu_libusb_close(struct dfuDev *dfuh) |
| { |
| if (dfuh->devh) { |
| libusb_release_interface(dfuh->devh, 0); |
| if (dfuh->detached) |
| libusb_attach_kernel_driver(dfuh->devh, 0); |
| libusb_close(dfuh->devh); |
| dfuh->devh = NULL; |
| } |
| if (dfuh->ctx) { |
| libusb_exit(dfuh->ctx); |
| dfuh->ctx = NULL; |
| } |
| } |
| |
| static dfuAPIResult dfu_libusb_open(struct dfuDev *dfuh, int *pid_list) |
| { |
| struct libusb_device_descriptor desc; |
| libusb_device **devs = NULL, *dev; |
| int n_devs, i; |
| |
| dfuh->devh = NULL; |
| dfuh->found_pid = 0; |
| dfuh->detached = 0; |
| dfuh->res = DFUAPISuccess; |
| |
| dfuh->rc = libusb_init(&(dfuh->ctx)); |
| if (!dfu_libusb_chkrc(dfuh, "Could not init USB library")) { |
| dfuh->ctx = NULL; /* invalidate ctx (if any) */ |
| goto error; |
| } |
| |
| n_devs = |
| dfuh->rc = libusb_get_device_list(dfuh->ctx, &devs); |
| if (!dfu_libusb_chkrc(dfuh, "Could not get USB device list")) |
| goto error; |
| |
| for (i = 0; i < n_devs; ++i) |
| { |
| dev = devs[i]; |
| |
| /* Note: since libusb-1.0.16 (LIBUSB_API_VERSION >= 0x01000102) |
| this function always succeeds. */ |
| if (libusb_get_device_descriptor(dev, &desc) != LIBUSB_SUCCESS) |
| continue; /* Unable to get device descriptor */ |
| |
| if (!dfu_check_id(desc.idVendor, desc.idProduct, pid_list)) |
| continue; |
| |
| dfuh->rc = libusb_open(dev, &(dfuh->devh)); |
| if (!dfu_libusb_chkrc(dfuh, "Could not open USB device")) |
| goto error; |
| |
| dfuh->rc = libusb_set_configuration(dfuh->devh, 1); |
| if (!dfu_libusb_chkrc(dfuh, "Could not set USB configuration")) |
| goto error; |
| |
| dfuh->rc = libusb_kernel_driver_active(dfuh->devh, 0); |
| if (dfuh->rc != LIBUSB_ERROR_NOT_SUPPORTED) { |
| if (!dfu_libusb_chkrc(dfuh, "Could not get USB driver status")) |
| goto error; |
| if (dfuh->rc == 1) { |
| dfuh->rc = libusb_detach_kernel_driver(dfuh->devh, 0); |
| if (!dfu_libusb_chkrc(dfuh, "Could not detach USB driver")) |
| goto error; |
| dfuh->detached = 1; |
| } |
| } |
| |
| dfuh->rc = libusb_claim_interface(dfuh->devh, 0); |
| if (!dfu_libusb_chkrc(dfuh, "Could not claim USB interface")) |
| goto error; |
| |
| /* ok */ |
| snprintf(dfuh->descr, sizeof(dfuh->descr), |
| "[%04x:%04x] at bus %d, device %d, USB ver. %04x", |
| desc.idVendor, desc.idProduct, libusb_get_bus_number(dev), |
| libusb_get_device_address(dev), desc.bcdUSB); |
| dfuh->found_pid = desc.idProduct; |
| break; |
| } |
| |
| bye: |
| if (devs) |
| libusb_free_device_list(devs, 1); |
| if (!dfuh->found_pid) |
| dfu_libusb_close(dfuh); |
| return dfuh->res; |
| |
| error: |
| goto bye; |
| } |
| #endif /* USE_LIBUSBAPI */ |
| |
| #ifdef __APPLE__ |
| static bool dfu_iokit_chkrc(struct dfuDev *dfuh, char *str) |
| { |
| dfuh->res = (dfuh->kr == kIOReturnSuccess) ? DFUAPISuccess : DFUAPIFail; |
| if (dfuh->res == DFUAPIFail) |
| snprintf(dfuh->err, sizeof(dfuh->err), |
| "%s: error %08x", str, dfuh->kr); |
| return (dfuh->res == DFUAPISuccess); |
| } |
| |
| static dfuAPIResult dfu_iokit_request(struct dfuDev *dfuh, |
| struct usbControlSetup *cs, void *data) |
| { |
| IOUSBDevRequest req; |
| req.bmRequestType = cs->bmRequestType; |
| req.bRequest = cs->bRequest; |
| req.wValue = cs->wValue; |
| req.wIndex = cs->wIndex; |
| req.wLength = cs->wLength; |
| req.pData = data; |
| |
| dfuh->kr = (*(dfuh->dev))->DeviceRequest(dfuh->dev, &req); |
| if (!dfu_iokit_chkrc(dfuh, "DFU request failed")) |
| dfu_add_reqerrstr(dfuh, cs); |
| |
| return dfuh->res; |
| } |
| |
| static dfuAPIResult dfu_iokit_reset(struct dfuDev *dfuh) |
| { |
| dfuh->kr = (*(dfuh->dev))->ResetDevice(dfuh->dev); |
| #if 0 |
| /* On 10.11+ ResetDevice() returns no error but does not perform |
| * any reset, just a kernel log message. |
| * USBDeviceReEnumerate() could be used as a workaround. |
| */ |
| dfuh->kr = (*(dfuh->dev))->USBDeviceReEnumerate(dfuh->dev, 0); |
| #endif |
| dfu_iokit_chkrc(dfuh, "Could not reset USB device"); |
| return dfuh->res; |
| } |
| |
| static void dfu_iokit_close(struct dfuDev *dfuh) |
| { |
| if (dfuh->dev) { |
| (*(dfuh->dev))->USBDeviceClose(dfuh->dev); |
| (*(dfuh->dev))->Release(dfuh->dev); |
| dfuh->dev = NULL; |
| } |
| } |
| |
| static dfuAPIResult dfu_iokit_open(struct dfuDev *dfuh, int *pid_list) |
| { |
| kern_return_t kr; |
| CFMutableDictionaryRef usb_matching_dict = 0; |
| io_object_t usbDevice; |
| io_iterator_t usb_iterator = IO_OBJECT_NULL; |
| IOCFPlugInInterface **plugInInterface = NULL; |
| IOUSBDeviceInterface **dev = NULL; |
| HRESULT result; |
| SInt32 score; |
| UInt16 vendor; |
| UInt16 product; |
| UInt16 release; |
| |
| dfuh->dev = NULL; |
| dfuh->found_pid = 0; |
| dfuh->res = DFUAPISuccess; |
| |
| usb_matching_dict = IOServiceMatching(kIOUSBDeviceClassName); |
| dfuh->kr = IOServiceGetMatchingServices( |
| kIOMasterPortDefault, usb_matching_dict, &usb_iterator); |
| if (!dfu_iokit_chkrc(dfuh, "Could not get matching services")) |
| goto error; |
| |
| while ((usbDevice = IOIteratorNext(usb_iterator))) |
| { |
| /* Create an intermediate plug-in */ |
| kr = IOCreatePlugInInterfaceForService(usbDevice, |
| kIOUSBDeviceUserClientTypeID, |
| kIOCFPlugInInterfaceID, |
| &plugInInterface, |
| &score); |
| IOObjectRelease(usbDevice); |
| |
| if ((kIOReturnSuccess != kr) || !plugInInterface) |
| continue; /* Unable to create a plugin */ |
| |
| /* Now create the device interface */ |
| result = (*plugInInterface)->QueryInterface(plugInInterface, |
| CFUUIDGetUUIDBytes(kIOUSBDeviceInterfaceID), |
| (LPVOID*)&dev); |
| (*plugInInterface)->Release(plugInInterface); |
| |
| if (result || !dev) |
| continue; /* Couldn't create a device interface */ |
| |
| kr = (*dev)->GetDeviceVendor(dev, &vendor); |
| kr = (*dev)->GetDeviceProduct(dev, &product); |
| kr = (*dev)->GetDeviceReleaseNumber(dev, &release); |
| |
| if (!dfu_check_id(vendor, product, pid_list)) { |
| (*dev)->Release(dev); |
| continue; |
| } |
| |
| /* Device found, open it */ |
| dfuh->kr = (*dev)->USBDeviceOpen(dev); |
| if (!dfu_iokit_chkrc(dfuh, "Could not open USB device")) { |
| (*dev)->Release(dev); |
| goto error; |
| } |
| |
| /* ok */ |
| dfuh->found_pid = product; |
| dfuh->dev = dev; |
| snprintf(dfuh->descr, sizeof(dfuh->descr), |
| "[%04x:%04x] release: %d", vendor, product, release); |
| break; |
| } |
| |
| bye: |
| if (usb_iterator != IO_OBJECT_NULL) |
| IOObjectRelease(usb_iterator); |
| return dfuh->res; |
| |
| error: |
| goto bye; |
| } |
| #endif /* __APPLE__ */ |
| |
| /* list of suported APIs */ |
| static struct dfuAPI api_list[] = |
| { |
| #ifdef WIN32 |
| { "winapi", |
| dfu_winapi_open, |
| dfu_winapi_request, |
| dfu_winapi_reset, |
| dfu_winapi_close }, |
| #endif |
| #ifdef USE_LIBUSBAPI |
| { "libusb", |
| dfu_libusb_open, |
| dfu_libusb_request, |
| dfu_libusb_reset, |
| dfu_libusb_close }, |
| #endif |
| #ifdef __APPLE__ |
| { "IOKit", |
| dfu_iokit_open, |
| dfu_iokit_request, |
| dfu_iokit_reset, |
| dfu_iokit_close }, |
| #endif |
| }; |
| #define N_DFU_APIS (sizeof(api_list)/sizeof(struct dfuAPI)) |
| |
| |
| /* |
| * DFU API common functions |
| */ |
| static int DEBUG_DFUREQ = 0; |
| |
| static dfuAPIResult dfuapi_request(struct dfuDev *dfuh, |
| struct usbControlSetup *cs, void *data) |
| { |
| if (!DEBUG_DFUREQ) |
| return dfuh->api->dfureq_fn(dfuh, cs, data); |
| |
| /* DEBUG */ |
| |
| /* previous state */ |
| unsigned char ste = 0; |
| struct usbControlSetup css = { 0xA1, DFU_GETSTATE, 0, 0, sizeof(ste) }; |
| if (dfuh->api->dfureq_fn(dfuh, &css, &ste) != DFUAPISuccess) { |
| snprintf(dfuh->err + strlen(dfuh->err), sizeof(dfuh->err) - |
| strlen(dfuh->err), " [DEBUG_DFUREQ ERROR: state=%d]", ste); |
| goto error; |
| } |
| |
| dfuh->api->dfureq_fn(dfuh, cs, data); |
| fprintf(stderr, "[DEBUG]: REQ: ste=%d, cs=%2x/%d/%d/%d/%d -> %s", |
| ste, cs->bmRequestType, cs->bRequest, cs->wValue, |
| cs->wIndex, cs->wLength, |
| (dfuh->res == DFUAPISuccess) ? "ok" : "ERROR"); |
| if (cs->bRequest == DFU_GETSTATE) |
| fprintf(stderr, " (state=%d)", *((unsigned char*)(data))); |
| if (cs->bRequest == DFU_GETSTATUS) { |
| struct usbStatusData *sd = (struct usbStatusData*)data; |
| fprintf(stderr, " (status=%d, polltmo=%d, state=%d)", sd->bStatus, |
| (sd->bwPollTimeout2 << 16) | (sd->bwPollTimeout1 << 8) | |
| (sd->bwPollTimeout0), sd->bState); |
| } |
| fputc('\n', stderr); |
| fflush(stderr); |
| |
| bye: |
| return dfuh->res; |
| error: |
| goto bye; |
| } |
| |
| static dfuAPIResult dfuapi_req_getstatus(struct dfuDev *dfuh, |
| DFUStatus *status, int *poll_tmo /*ms*/, |
| DFUState *state) |
| { |
| struct usbStatusData sd = { 0, 0, 0, 0, 0, 0 }; |
| struct usbControlSetup cs = { 0xA1, DFU_GETSTATUS, 0, 0, sizeof(sd) }; |
| dfuapi_request(dfuh, &cs, &sd); |
| if (status) *status = sd.bStatus; |
| if (state) *state = sd.bState; |
| if (poll_tmo) *poll_tmo = (sd.bwPollTimeout2 << 16) | |
| (sd.bwPollTimeout1 << 8) | (sd.bwPollTimeout0); |
| return dfuh->res; |
| } |
| |
| static dfuAPIResult dfuapi_req_getstate(struct dfuDev *dfuh, DFUState *state) |
| { |
| unsigned char sts = 0; |
| struct usbControlSetup cs = { 0xA1, DFU_GETSTATE, 0, 0, sizeof(sts) }; |
| dfuapi_request(dfuh, &cs, &sts); |
| if (state) *state = sts; |
| return dfuh->res; |
| } |
| |
| static dfuAPIResult dfuapi_req_dnload(struct dfuDev* dfuh, uint16_t blknum, |
| uint16_t len, unsigned char *data) |
| { |
| struct usbControlSetup cs = { 0x21, DFU_DNLOAD, blknum, 0, len }; |
| return dfuapi_request(dfuh, &cs, data); |
| } |
| |
| /* not used */ |
| #if 0 |
| static dfuAPIResult dfuapi_req_upload(struct dfuDev* dfuh, |
| uint16_t blknum, uint16_t len, unsigned char *data) |
| { |
| struct usbControlSetup cs = { 0xA1, DFU_UPLOAD, blknum, 0, len }; |
| return dfuapi_request(dfuh, &cs, data); |
| } |
| |
| static dfuAPIResult dfuapi_req_clrstatus(struct dfuDev* dfuh) |
| { |
| struct usbControlSetup cs = { 0x21, DFU_CLRSTATUS, 0, 0, 0 }; |
| return dfuapi_request(dfuh, &cs, NULL); |
| } |
| |
| static dfuAPIResult dfuapi_req_abort(struct dfuDev* dfuh) |
| { |
| struct usbControlSetup cs = { 0x21, DFU_ABORT, 0, 0, 0 }; |
| return dfuapi_request(dfuh, &cs, NULL); |
| } |
| |
| /* not implemented on DFU8702 */ |
| static dfuAPIResult dfuapi_req_detach(struct dfuDev* dfuh, int tmo) |
| { |
| struct usbControlSetup cs = { 0x21, DFU_DETACH, tmo, 0, 0 }; |
| return dfuapi_request(dfuh, &cs, NULL); |
| } |
| #endif |
| |
| static dfuAPIResult dfuapi_reset(struct dfuDev *dfuh) |
| { |
| return dfuh->api->reset_fn(dfuh); |
| } |
| |
| static dfuAPIResult dfuapi_send_packet(struct dfuDev* dfuh, uint16_t blknum, |
| uint16_t len, unsigned char *data, DFUStatus *status, |
| int *poll_tmo, DFUState *state, DFUState *pre_state) |
| { |
| if (dfuapi_req_dnload(dfuh, blknum, len, data) != DFUAPISuccess) |
| goto error; |
| |
| /* device is in dfuDLSYNC state, waiting for a GETSTATUS request |
| * to enter the next state, if she respond with dfuDLBUSY then |
| * we must wait to resend the GETSTATUS request */ |
| |
| if (dfuapi_req_getstatus(dfuh, status, poll_tmo, state) != DFUAPISuccess) |
| goto error; |
| |
| if (*state == dfuDNBUSY) { |
| if (*poll_tmo) |
| sleep_ms(*poll_tmo); |
| if (pre_state) |
| if (dfuapi_req_getstate(dfuh, pre_state) != DFUAPISuccess) |
| goto error; |
| if (dfuapi_req_getstatus(dfuh, status, poll_tmo, state) != DFUAPISuccess) |
| goto error; |
| } |
| |
| bye: |
| return dfuh->res; |
| error: |
| goto bye; |
| } |
| |
| static void dfuapi_set_err(struct dfuDev *dfuh, char *str) |
| { |
| dfuh->res = DFUAPIFail; |
| strncpy(dfuh->err, str, sizeof(dfuh->err)); |
| } |
| |
| static dfuAPIResult dfuapi_open(struct dfuDev *dfuh, int pid) |
| { |
| int pid_l[N_KNOWN_PIDS+1] = { 0 }; |
| struct dfuAPI *api; |
| unsigned i, p; |
| |
| /* fill pid list */ |
| if (pid) |
| pid_l[0] = pid; |
| else |
| for (p = 0; p < N_KNOWN_PIDS; p++) |
| pid_l[p] = known_pids[p].pid; |
| |
| for (i = 0; i < N_DFU_APIS; i++) |
| { |
| api = &api_list[i]; |
| if (api->open_fn(dfuh, pid_l) != DFUAPISuccess) |
| goto error; |
| if (dfuh->found_pid) { |
| /* ok */ |
| dfuh->api = api; |
| printf("[INFO] %s: found %s\n", api->name, dfuh->descr); |
| for (p = 0; p < N_KNOWN_PIDS; p++) { |
| if (known_pids[p].pid == dfuh->found_pid) { |
| printf("[INFO] iPod %s, mode: %s\n", known_pids[p].desc, |
| known_pids[p].mode ? "WTF" : "DFU"); |
| break; |
| } |
| } |
| fflush(stdout); |
| goto bye; |
| } |
| printf("[INFO] %s: no DFU devices found\n", api->name); |
| fflush(stdout); |
| } |
| |
| /* error */ |
| dfuapi_set_err(dfuh, "DFU device not found"); |
| |
| bye: |
| return dfuh->res; |
| error: |
| goto bye; |
| } |
| |
| static void dfuapi_destroy(struct dfuDev *dfuh) |
| { |
| if (dfuh) { |
| if (dfuh->api) |
| dfuh->api->close_fn(dfuh); |
| free(dfuh); |
| } |
| } |
| |
| static struct dfuDev *dfuapi_create(void) |
| { |
| return calloc(sizeof(struct dfuDev), 1); |
| } |
| |
| |
| /* |
| * app level functions |
| */ |
| static int ipoddfu_download_file(struct dfuDev* dfuh, |
| unsigned char *data, unsigned long size) |
| { |
| unsigned int blknum, len, remaining; |
| int poll_tmo; |
| DFUStatus status; |
| DFUState state; |
| |
| if (dfuapi_req_getstate(dfuh, &state) != DFUAPISuccess) |
| goto error; |
| |
| if (state != dfuIDLE) { |
| dfuapi_set_err(dfuh, "Could not start DFU download: not idle"); |
| goto error; |
| } |
| |
| blknum = 0; |
| remaining = size; |
| while (remaining) |
| { |
| len = (remaining < DFU_PKT_SZ) ? remaining : DFU_PKT_SZ; |
| |
| if (dfuapi_send_packet(dfuh, blknum, len, data + blknum*DFU_PKT_SZ, |
| &status, &poll_tmo, &state, NULL) != DFUAPISuccess) |
| goto error; |
| |
| if (state != dfuDNLOAD_IDLE) { |
| dfuapi_set_err(dfuh, "DFU download aborted: unexpected state"); |
| goto error; |
| } |
| |
| remaining -= len; |
| blknum++; |
| } |
| |
| /* send ZLP */ |
| DFUState pre_state = appIDLE; /* dummy state */ |
| if (dfuapi_send_packet(dfuh, blknum, 0, NULL, |
| &status, &poll_tmo, &state, &pre_state) != DFUAPISuccess) { |
| if (pre_state == dfuMANIFEST_SYNC) |
| goto ok; /* pwnaged .dfu file */ |
| goto error; |
| } |
| |
| if (state != dfuMANIFEST) { |
| if (status == errFIRMWARE) |
| dfuapi_set_err(dfuh, "DFU download failed: corrupt firmware"); |
| else |
| dfuapi_set_err(dfuh, "DFU download failed: unexpected state"); |
| goto error; |
| } |
| |
| /* wait for manifest stage */ |
| if (poll_tmo) |
| sleep_ms(poll_tmo); |
| |
| if (dfuapi_req_getstatus(dfuh, &status, NULL, &state) != DFUAPISuccess) |
| goto ok; /* 1223 .dfu file */ |
| |
| /* XXX: next code never tested */ |
| |
| if (state != dfuMANIFEST_WAIT_RESET) { |
| if (status == errVERIFY) |
| dfuapi_set_err(dfuh, "DFU manifest failed: wrong FW verification"); |
| else |
| dfuapi_set_err(dfuh, "DFU manifest failed: unexpected state"); |
| goto error; |
| } |
| |
| if (dfuapi_reset(dfuh) != DFUAPISuccess) |
| goto error; |
| |
| ok: |
| return 1; |
| error: |
| return 0; |
| } |
| |
| /* exported functions */ |
| int ipoddfu_send(int pid, unsigned char *data, int size, |
| char* errstr, int errstrsize) |
| { |
| struct dfuDev *dfuh; |
| unsigned char *buf; |
| uint32_t checksum; |
| int ret = 1; /* ok */ |
| |
| dfuh = dfuapi_create(); |
| |
| buf = malloc(size+4); |
| if (!buf) { |
| dfuapi_set_err(dfuh, "Could not allocate memory for DFU buffer"); |
| goto error; |
| } |
| |
| if (memcmp(data, IM3_IDENT, 4)) { |
| dfuapi_set_err(dfuh, "Bad DFU image data"); |
| goto error; |
| } |
| |
| crc32_init(); |
| checksum = crc32(data, size, 0); |
| memcpy(buf, data, size); |
| put_uint32le(buf+size, ~checksum); |
| |
| if (dfuapi_open(dfuh, pid) != DFUAPISuccess) |
| goto error; |
| |
| if (!ipoddfu_download_file(dfuh, buf, size+4)) |
| goto error; |
| |
| bye: |
| if (buf) free(buf); |
| dfuapi_destroy(dfuh); |
| return ret; |
| |
| error: |
| ret = 0; |
| if (errstr) |
| snprintf(errstr, errstrsize, "[ERR] %s", dfuh->err); |
| goto bye; |
| } |
| |
| /* search for the DFU device and gets its DFUState */ |
| int ipoddfu_scan(int pid, int *state, int reset, |
| char* errstr, int errstrsize) |
| { |
| struct dfuDev *dfuh; |
| int ret = 1; /* ok */ |
| |
| dfuh = dfuapi_create(); |
| |
| if (dfuapi_open(dfuh, pid) != DFUAPISuccess) |
| goto error; |
| |
| if (reset) |
| if (dfuapi_reset(dfuh) != DFUAPISuccess) |
| goto error; |
| |
| if (state) { |
| DFUState sts; |
| if (dfuapi_req_getstate(dfuh, &sts) != DFUAPISuccess) |
| goto error; |
| *state = (int)sts; |
| } |
| |
| bye: |
| dfuapi_destroy(dfuh); |
| return ret; |
| |
| error: |
| ret = 0; |
| if (errstr) |
| snprintf(errstr, errstrsize, "[ERR] %s", dfuh->err); |
| goto bye; |
| } |
| |
| void ipoddfu_debug(int debug) |
| { |
| DEBUG_DFUREQ = debug; |
| } |