rockbox/rbutil/mks5lboot/ipoddfu.c
Cástor Muñoz fbbba9292b mks5lboot: updates
- fix Makefile to allow cross compilation
- Windows: use Sleep() instead of nanosleep()
- Windows: libusb now is optional
- OS X: use IOKit instead of libusb
- small rework on the DFU API

Change-Id: Ia4b07012c098ad608594e15f6effe9c9d2164b9b
2017-06-19 02:00:30 +02:00

1061 lines
28 KiB
C

/***************************************************************************
* __________ __ ___.
* 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;
}