/*************************************************************************** * __________ __ ___. * 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 #include #include #include #include #ifdef WIN32 #include #include #endif #ifdef USE_LIBUSBAPI #include #endif #ifdef __APPLE__ #include #include #include #endif #include "mks5lboot.h" #ifdef WIN32 #define sleep_ms(ms) Sleep(ms) #else #include 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; }