365 lines
9.8 KiB
C
365 lines
9.8 KiB
C
/*
|
|
* Copyright (C) 2007-2012 Apple Inc. All rights reserved.
|
|
*
|
|
* This document is the property of Apple Inc.
|
|
* It is considered confidential and proprietary.
|
|
*
|
|
* This document may not be reproduced or transmitted in any form,
|
|
* in whole or in part, without the express written permission of
|
|
* Apple Inc.
|
|
*/
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// USB DFU
|
|
//
|
|
// This file implements usb dfu protocol (just download). This is used
|
|
// in SecureROM.
|
|
////////////////////////////////////////////////////////////////////
|
|
|
|
#include <debug.h>
|
|
#include <sys/task.h>
|
|
#include <sys/security.h>
|
|
#include <drivers/usb/usb_chap9.h>
|
|
#include <drivers/usb/usb_public.h>
|
|
#include <drivers/usb/usb_core.h>
|
|
#include <platform.h>
|
|
|
|
#define DEBUG 0
|
|
#if DEBUG
|
|
#define print(fmt, args...) printf("%s --- " fmt, __FUNCTION__, ##args)
|
|
#else
|
|
#define print(fmt, args...) (void)0
|
|
#endif
|
|
|
|
|
|
struct usb_dfu_run_time_descriptor {
|
|
uint8_t length;
|
|
uint8_t type;
|
|
uint8_t bmAttributes;
|
|
uint16_t wDetachTimeOut;
|
|
uint16_t wTransferSize;
|
|
} __attribute__((packed));
|
|
|
|
struct usb_dfu_status_request
|
|
{
|
|
UInt8 bStatus;
|
|
UInt8 bwPollTimeout[3];
|
|
UInt8 bState;
|
|
UInt8 iString;
|
|
} __attribute__((packed));
|
|
|
|
enum {
|
|
DFU_DETACH = 0,
|
|
DFU_DNLOAD,
|
|
DFU_UPLOAD,
|
|
DFU_GETSTATUS,
|
|
DFU_CLR_STATUS,
|
|
DFU_GETSTATE,
|
|
DFU_ABORT
|
|
};
|
|
|
|
enum {
|
|
appIDLE,
|
|
appDETACH,
|
|
dfuIDLE,
|
|
dfuDNLOAD_SYNC,
|
|
dfuDNBUSY,
|
|
dfuDNLOAD_IDLE,
|
|
dfuMANIFEST_SYNC,
|
|
dfuMANIFEST,
|
|
dfuMANIFEST_WAIT_RESET,
|
|
dfuUPLOAD_IDLE,
|
|
dfuERROR
|
|
};
|
|
|
|
enum {
|
|
errOK = 0,
|
|
errADDRESS = 0x08,
|
|
errNOTDONE = 0x09,
|
|
errSTALLEDPKT = 0x0f
|
|
};
|
|
|
|
#define DD_DFU (0x21)
|
|
#define USB_DFU_RUN_TIME_DT_SIZE (7)
|
|
#define DFU_MAX_TRANSFER_SIZE (2 * 1024)
|
|
#define DETACH_TIMEOUT_MS (10)
|
|
#define DFU_FILE_SUFFIX_LENGTH (16)
|
|
|
|
//the polling delay we generally report in GETSTATUS requests
|
|
#define STANDARD_DELAY_MS 50
|
|
|
|
//the delay we report after completing the file download. I don't think it needs to be different
|
|
//from the standard delay but this is what m68 h1 rom does.
|
|
#define DL_DONE_DELAY_MS 3000
|
|
|
|
#define DFU_BMATTRIBUTES (1 << 0) // download only
|
|
|
|
struct usb_dfu_io_buffer {
|
|
u_int8_t bytes[DFU_MAX_TRANSFER_SIZE];
|
|
};
|
|
|
|
static const struct usb_interface_descriptor usb_dfu_interface_descriptor = {
|
|
USB_DT_INTERFACE_SIZE,
|
|
USB_DT_INTERFACE,
|
|
APPLE_USB_DFU_INTF,
|
|
0,
|
|
0,
|
|
APPLE_USB_DFU_CLASS,
|
|
APPLE_USB_DFU_SUBCLASS,
|
|
APPLE_USB_DFU_PROTOCOL,
|
|
0
|
|
};
|
|
|
|
static const struct usb_dfu_run_time_descriptor usb_dfu_run_time_desc = {
|
|
USB_DFU_RUN_TIME_DT_SIZE,
|
|
DD_DFU,
|
|
DFU_BMATTRIBUTES,
|
|
DETACH_TIMEOUT_MS,
|
|
DFU_MAX_TRANSFER_SIZE
|
|
};
|
|
|
|
static bool usb_dfu_inited;
|
|
static struct usb_interface_instance usb_dfu_interface_instance;
|
|
static struct usb_dfu_status_request status_req;
|
|
static struct task_event dfu_event;
|
|
static volatile uint8_t dfu_state;
|
|
static volatile bool dfu_done;
|
|
static struct usb_dfu_io_buffer *io_buffer;
|
|
static int completion_status;
|
|
static u_int8_t *image_buffer;
|
|
static u_int32_t image_buffer_size;
|
|
static u_int32_t total_received;
|
|
static u_int32_t expecting;
|
|
|
|
static void set_status(u_int8_t bStatus, int bwPollTimeout, u_int8_t bState, u_int8_t iString);
|
|
static int handle_interface_request(struct usb_device_request *request, uint8_t **out_buffer);
|
|
static int handle_dfu_request(struct usb_device_request *setup, uint8_t **buffer);
|
|
static void handle_bus_reset(void);
|
|
static void data_received(u_int32_t received);
|
|
|
|
|
|
int getDFUImage(void* buffer, int maxlen)
|
|
{
|
|
dprintf(DEBUG_INFO, "Trying to get a file with DFU.\n" );
|
|
|
|
// save the info provided by our caller
|
|
image_buffer = buffer;
|
|
image_buffer_size = maxlen;
|
|
|
|
print( "Initializing USB...\n" );
|
|
#if WITH_PLATFORM_INIT_USB
|
|
if(platform_init_usb() != 0)
|
|
goto exit;
|
|
#else
|
|
if(usb_init() != 0)
|
|
goto exit;
|
|
#endif
|
|
|
|
// wait here till dfu download is finished
|
|
while(!dfu_done) event_wait(&dfu_event);
|
|
dprintf(DEBUG_INFO, "DFU complete, status=%d\n", completion_status);
|
|
|
|
exit:
|
|
print( "Exiting USB...\n" );
|
|
usb_quiesce();
|
|
|
|
return completion_status;
|
|
}
|
|
|
|
int usb_dfu_init()
|
|
{
|
|
if(usb_dfu_inited) return 0;
|
|
|
|
io_buffer = memalign(sizeof(*io_buffer), CPU_CACHELINE_SIZE);
|
|
bzero((void *)io_buffer, sizeof(*io_buffer));
|
|
|
|
set_status(errOK, STANDARD_DELAY_MS, dfuIDLE, 0);
|
|
|
|
completion_status = -1;
|
|
total_received = 0;
|
|
dfu_done = false;
|
|
|
|
event_init(&dfu_event, EVENT_FLAG_AUTO_UNSIGNAL, false);
|
|
|
|
usb_dfu_interface_instance.total_interfaces = APPLE_USB_DFU_TOTAL_INTFS;
|
|
usb_dfu_interface_instance.interface_descs = (struct usb_interface_descriptor *)&usb_dfu_interface_descriptor;
|
|
usb_dfu_interface_instance.total_other_descs = 1;
|
|
usb_dfu_interface_instance.other_descs = (void *)&usb_dfu_run_time_desc;
|
|
usb_dfu_interface_instance.handle_request = handle_interface_request;
|
|
usb_dfu_interface_instance.non_setup_data_phase_finished_callback = data_received;
|
|
usb_dfu_interface_instance.handle_bus_reset = handle_bus_reset;
|
|
|
|
usb_core_register_interface(&usb_dfu_interface_instance);
|
|
|
|
usb_dfu_inited = true;
|
|
|
|
return 0;
|
|
}
|
|
|
|
void usb_dfu_exit()
|
|
{
|
|
if(!usb_dfu_inited) return;
|
|
|
|
bzero((void*)&usb_dfu_interface_instance, sizeof(struct usb_interface_instance));
|
|
|
|
if(io_buffer) {
|
|
free(io_buffer);
|
|
io_buffer = NULL;
|
|
}
|
|
|
|
usb_dfu_inited = false;
|
|
}
|
|
|
|
|
|
static void set_status(u_int8_t bStatus, int bwPollTimeout, u_int8_t bState, u_int8_t iString)
|
|
{
|
|
status_req.bStatus = bStatus;
|
|
status_req.bwPollTimeout[0]= bwPollTimeout & 0xff;
|
|
status_req.bwPollTimeout[1]= (bwPollTimeout >> 8) & 0xff;
|
|
status_req.bwPollTimeout[2]= (bwPollTimeout >> 16) & 0xff;
|
|
dfu_state = status_req.bState = bState;
|
|
status_req.iString = iString;
|
|
}
|
|
|
|
static int handle_interface_request(struct usb_device_request *request, uint8_t **out_buffer)
|
|
{
|
|
u_int8_t bRequest = request->bRequest;
|
|
u_int16_t wLength = request->wLength;
|
|
int ret;
|
|
|
|
print("type: %d\n", (request->bmRequestType & USB_REQ_DIRECTION_MASK));
|
|
print("request=%02x wValue=%04x index=%d length=%d\n", bRequest,
|
|
request->wValue, request->wIndex, wLength);
|
|
|
|
ret = -1;
|
|
|
|
// DFU_GETSTATE and DFU_GETSTATUS request are DEVICE2HOST
|
|
// request. Response for these requests are copied into
|
|
// io_buffer.
|
|
// DFU_DNLOAD request is HOST2DEVICE type request. We pass our
|
|
// io_buffer to the stack to copy the data from the host.
|
|
//
|
|
// io_buffer is gauranteed to be larger than any data needs to
|
|
// be copied into it.
|
|
if((request->bmRequestType & USB_REQ_DIRECTION_MASK) == USB_REQ_HOST2DEVICE)
|
|
{
|
|
if(out_buffer == NULL)
|
|
return -1;
|
|
|
|
switch(bRequest)
|
|
{
|
|
case DFU_DNLOAD:
|
|
{
|
|
if(wLength == 0) {
|
|
print("File transfer complete. Waiting for status sync.\n");
|
|
set_status(errOK, STANDARD_DELAY_MS, dfuMANIFEST_SYNC, 0);
|
|
}
|
|
else {
|
|
if(wLength > sizeof(*io_buffer)) {
|
|
// cleanest way to recover is to stall here
|
|
set_status(errSTALLEDPKT, STANDARD_DELAY_MS, dfuIDLE, 0);
|
|
return -1;
|
|
}
|
|
|
|
*out_buffer = (uint8_t *)io_buffer;
|
|
}
|
|
expecting = wLength;
|
|
ret = wLength;
|
|
break;
|
|
}
|
|
case DFU_CLR_STATUS :
|
|
case DFU_ABORT:
|
|
{
|
|
set_status(errOK, STANDARD_DELAY_MS, dfuIDLE, 0);
|
|
|
|
total_received = 0;
|
|
|
|
if(!dfu_done) {
|
|
completion_status = -1;
|
|
dfu_done = true;
|
|
event_signal(&dfu_event);
|
|
}
|
|
ret = 0;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
else if((request->bmRequestType & USB_REQ_DIRECTION_MASK) == USB_REQ_DEVICE2HOST)
|
|
{
|
|
switch(bRequest) {
|
|
|
|
case DFU_GETSTATE:
|
|
print("Returning state = %d\n", dfu_state);
|
|
*(u_int8_t *)io_buffer = dfu_state;
|
|
ret = 1;
|
|
break;
|
|
|
|
case DFU_GETSTATUS:
|
|
memcpy((u_int8_t *)io_buffer, (void *)&status_req, sizeof(struct usb_dfu_status_request));
|
|
|
|
if(dfu_state == dfuMANIFEST_SYNC) {
|
|
set_status(errOK, DL_DONE_DELAY_MS, dfuMANIFEST, 0);
|
|
}
|
|
else if(dfu_state == dfuMANIFEST) {
|
|
// XXX TODO: validate DFU File Suffix? For now erase them
|
|
RELEASE_ASSERT(total_received >= DFU_FILE_SUFFIX_LENGTH);
|
|
RELEASE_ASSERT(image_buffer != NULL);
|
|
completion_status = total_received - DFU_FILE_SUFFIX_LENGTH;
|
|
bzero((void *)&image_buffer[completion_status], DFU_FILE_SUFFIX_LENGTH);
|
|
total_received = 0;
|
|
|
|
// transitioning to this state will break us out of the file wait loop
|
|
set_status(errOK, STANDARD_DELAY_MS, dfuMANIFEST_WAIT_RESET, 0);
|
|
}
|
|
|
|
ret = sizeof(struct usb_dfu_status_request);
|
|
break;
|
|
}
|
|
|
|
if(ret < 0)
|
|
return -1;
|
|
|
|
usb_core_do_transfer(EP0_IN, (u_int8_t *)io_buffer, ret, NULL);
|
|
|
|
return 0;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
static void handle_bus_reset(void)
|
|
{
|
|
if(dfu_state == dfuMANIFEST_WAIT_RESET) {
|
|
dfu_done = true;
|
|
event_signal(&dfu_event);
|
|
}
|
|
}
|
|
|
|
static void data_received(u_int32_t received)
|
|
{
|
|
print("DFU received %d of %d\n", received, expecting);
|
|
|
|
if(received != expecting) {
|
|
set_status(errNOTDONE, STANDARD_DELAY_MS, dfuERROR, 0);
|
|
}
|
|
else if((total_received + received) > image_buffer_size) {
|
|
set_status(errADDRESS, STANDARD_DELAY_MS, dfuERROR, 0);
|
|
}
|
|
else {
|
|
if(!security_allow_memory(&image_buffer[total_received], received)) {
|
|
dprintf(DEBUG_INFO, "Permission Denied\n");
|
|
completion_status = -1;
|
|
dfu_done = true;
|
|
event_signal(&dfu_event);
|
|
return;
|
|
}
|
|
|
|
memcpy(&image_buffer[total_received], (void *)io_buffer, received);
|
|
total_received += received;
|
|
expecting = 0;
|
|
set_status(errOK, STANDARD_DELAY_MS, dfuDNLOAD_IDLE, 0);
|
|
}
|
|
}
|