Arduino_STM32/usb_bootloader/STM32F1/dfu.c

446 lines
15 KiB
C

/* *****************************************************************************
* The MIT License
*
* Copyright (c) 2010 LeafLabs LLC.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
* ****************************************************************************/
/**
* @file dfu.c
*
* @brief The principle dfu state machine as well as the data
* transfer callbacks accessed by the usb library
*
*
*/
#include "dfu.h"
#include "usb.h"
/* DFU globals */
static volatile u32 userAppAddr = USER_CODE_RAM; /* default RAM user code location */
static volatile u32 userAppEnd = RAM_END;
static volatile DFUStatus dfuAppStatus; /* includes state */
volatile dfuUploadTypes_t userUploadType = DFU_UPLOAD_NONE;
volatile bool dfuBusy = FALSE;
static volatile u8 recvBuffer[wTransferSize] __attribute__((aligned(4)));
static volatile u32 userFirmwareLen = 0;
static volatile u16 thisBlockLen = 0;
static volatile u16 uploadBlockLen = 0;
volatile PLOT code_copy_lock;
/* todo: force dfu globals to be singleton to avoid re-inits? */
void dfuInit(void) {
dfuAppStatus.bStatus = OK;
dfuAppStatus.bwPollTimeout0 = 0x00;
dfuAppStatus.bwPollTimeout1 = 0x00;
dfuAppStatus.bwPollTimeout2 = 0x00;
dfuAppStatus.bState = dfuIDLE;
dfuAppStatus.iString = 0x00; /* all strings must be 0x00 until we make them! */
userFirmwareLen = 0;
thisBlockLen = 0;;
userAppAddr = USER_CODE_RAM; /* default RAM user code location */
userAppEnd = RAM_END;
userUploadType=DFU_UPLOAD_NONE;
code_copy_lock = WAIT;
dfuBusy = FALSE;
}
bool dfuUpdateByRequest(void) {
/* were using the global pInformation struct from usb_lib here,
see comment in maple_dfu.h around DFUEvent struct */
dfuBusy = TRUE;
u8 startState = dfuAppStatus.bState;
dfuAppStatus.bStatus = OK;
/* often leaner to nest if's then embed a switch/case */
if (startState == dfuIDLE) {
/* device running inside DFU mode */
dfuBusy = TRUE; // signals the main loop to defer to the dfu write-loop
if (pInformation->USBbRequest == DFU_DNLOAD) {
if (pInformation->USBwLengths.w > 0) {
userFirmwareLen = 0;
dfuAppStatus.bState = dfuDNLOAD_SYNC;
switch(pInformation->Current_AlternateSetting)
{
case 0:
userAppAddr = USER_CODE_RAM;
userUploadType = DFU_UPLOAD_RAM;
break;
case 1:
userAppAddr = USER_CODE_FLASH0X8005000;
userUploadType = DFU_UPLOAD_FLASH_0X8005000;
/* make sure the flash is setup properly, unlock it */
setupFLASH();
flashUnlock();
// Clear lower memory so that we can check on cold boot, whether the last upload was to 0x8002000 or 0x8005000
flashErasePage((u32)USER_CODE_FLASH0X8002000);
break;
case 2:
userUploadType = DFU_UPLOAD_FLASH_0X8002000;
userAppAddr = USER_CODE_FLASH0X8002000;
/* make sure the flash is setup properly, unlock it */
setupFLASH();
flashUnlock();
break;
default:
userAppAddr = USER_CODE_RAM;
userUploadType = DFU_UPLOAD_RAM;
break;
}
} else {
dfuAppStatus.bState = dfuERROR;
dfuAppStatus.bStatus = errNOTDONE;
}
} else if (pInformation->USBbRequest == DFU_UPLOAD) {
dfuAppStatus.bState = dfuUPLOAD_IDLE;
/* record length of first block for calculating target
address from wValue in consecutive blocks */
uploadBlockLen = pInformation->USBwLengths.w;
thisBlockLen = uploadBlockLen; /* for this first block as well */
/* calculate where the data should be copied from */
userFirmwareLen = uploadBlockLen * pInformation->USBwValue;
switch(pInformation->Current_AlternateSetting)
{
case 0:
userAppAddr = USER_CODE_RAM;
userAppEnd = RAM_END;
case 1:
userAppAddr = USER_CODE_FLASH0X8005000;
userAppEnd = FLASH_END;
break;
case 2:
userAppAddr = USER_CODE_FLASH0X8002000;
userAppEnd = FLASH_END;
break;
default:
userAppAddr = USER_CODE_RAM;
userAppEnd = RAM_END;
break;
}
} else if (pInformation->USBbRequest == DFU_ABORT) {
dfuAppStatus.bState = dfuIDLE;
dfuAppStatus.bStatus = OK; /* are we really ok? we were just aborted */
} else if (pInformation->USBbRequest == DFU_GETSTATUS) {
dfuAppStatus.bState = dfuIDLE;
} else if (pInformation->USBbRequest == DFU_GETSTATE) {
dfuAppStatus.bState = dfuIDLE;
} else {
dfuAppStatus.bState = dfuERROR;
dfuAppStatus.bStatus = errSTALLEDPKT;
}
} else if (startState == dfuDNLOAD_SYNC) {
/* device received block, waiting for DFU_GETSTATUS request */
if (pInformation->USBbRequest == DFU_GETSTATUS) {
/* todo, add routine to wait for last block write to finish */
if (userUploadType == DFU_UPLOAD_RAM) {
if (code_copy_lock == WAIT) {
code_copy_lock = BEGINNING;
dfuAppStatus.bwPollTimeout0 = 0x20; /* 32 ms */
dfuAppStatus.bwPollTimeout1 = 0x00;
dfuAppStatus.bState = dfuDNBUSY;
} else if (code_copy_lock == BEGINNING) {
dfuAppStatus.bState = dfuDNLOAD_SYNC;
} else if (code_copy_lock == MIDDLE) {
dfuAppStatus.bState = dfuDNLOAD_SYNC;
} else if (code_copy_lock == END) {
dfuAppStatus.bwPollTimeout0 = 0x00;
code_copy_lock = WAIT;
dfuAppStatus.bState = dfuDNLOAD_IDLE;
}
} else {
dfuAppStatus.bState = dfuDNLOAD_IDLE;
dfuCopyBufferToExec();
}
} else if (pInformation->USBbRequest == DFU_GETSTATE) {
dfuAppStatus.bState = dfuDNLOAD_SYNC;
} else {
dfuAppStatus.bState = dfuERROR;
dfuAppStatus.bStatus = errSTALLEDPKT;
}
} else if (startState == dfuDNBUSY) {
/* if were actually done writing, goto sync, else stay busy */
if (code_copy_lock == END) {
dfuAppStatus.bwPollTimeout0 = 0x00;
code_copy_lock = WAIT;
dfuAppStatus.bState = dfuDNLOAD_IDLE;
} else {
dfuAppStatus.bState = dfuDNBUSY;
}
} else if (startState == dfuDNLOAD_IDLE) {
/* device is expecting dfu_dnload requests */
if (pInformation->USBbRequest == DFU_DNLOAD) {
if (pInformation->USBwLengths.w > 0) {
dfuAppStatus.bState = dfuDNLOAD_SYNC;
} else {
/* todo, support "disagreement" if device expects more data than this */
dfuAppStatus.bState = dfuMANIFEST_SYNC;
/* relock the flash */
flashLock();
}
} else if (pInformation->USBbRequest == DFU_ABORT) {
dfuAppStatus.bState = dfuIDLE;
} else if (pInformation->USBbRequest == DFU_GETSTATUS) {
dfuAppStatus.bState = dfuIDLE;
} else if (pInformation->USBbRequest == DFU_GETSTATE) {
dfuAppStatus.bState = dfuIDLE;
} else {
dfuAppStatus.bState = dfuERROR;
dfuAppStatus.bStatus = errSTALLEDPKT;
}
} else if (startState == dfuMANIFEST_SYNC) {
/* device has received last block, waiting DFU_GETSTATUS request */
if (pInformation->USBbRequest == DFU_GETSTATUS) {
dfuAppStatus.bState = dfuMANIFEST_WAIT_RESET;
dfuAppStatus.bStatus = OK;
} else if (pInformation->USBbRequest == DFU_GETSTATE) {
dfuAppStatus.bState = dfuMANIFEST_SYNC;
} else {
dfuAppStatus.bState = dfuERROR;
dfuAppStatus.bStatus = errSTALLEDPKT;
}
} else if (startState == dfuMANIFEST) {
/* device is in manifestation phase */
/* should never receive request while in manifest! */
dfuAppStatus.bState = dfuMANIFEST_WAIT_RESET;
dfuAppStatus.bStatus = OK;
} else if (startState == dfuMANIFEST_WAIT_RESET) {
/* device has programmed new firmware but needs external
usb reset or power on reset to run the new code */
/* consider timing out and self-resetting */
dfuAppStatus.bState = dfuMANIFEST_WAIT_RESET;
} else if (startState == dfuUPLOAD_IDLE) {
/* device expecting further dfu_upload requests */
if (pInformation->USBbRequest == DFU_UPLOAD) {
if (pInformation->USBwLengths.w > 0) {
/* check that this is not the last possible block */
userFirmwareLen = uploadBlockLen * pInformation->USBwValue;
if (userAppAddr + userFirmwareLen + uploadBlockLen <= userAppEnd) {
thisBlockLen = uploadBlockLen;
dfuAppStatus.bState = dfuUPLOAD_IDLE;
} else {
/* if above comparison was just equal, thisBlockLen becomes zero
next time when USBWValue has been increased by one */
thisBlockLen = userAppEnd - userAppAddr - userFirmwareLen;
/* check for overflow due to USBwValue out of range */
if (thisBlockLen >= pInformation->USBwLengths.w) {
thisBlockLen = 0;
}
dfuAppStatus.bState = dfuIDLE;
}
} else {
dfuAppStatus.bState = dfuERROR;
dfuAppStatus.bStatus = errNOTDONE;
}
} else if (pInformation->USBbRequest == DFU_ABORT) {
dfuAppStatus.bState = dfuIDLE;
} else if (pInformation->USBbRequest == DFU_GETSTATUS) {
dfuAppStatus.bState = dfuUPLOAD_IDLE;
} else if (pInformation->USBbRequest == DFU_GETSTATE) {
dfuAppStatus.bState = dfuUPLOAD_IDLE;
} else {
dfuAppStatus.bState = dfuERROR;
dfuAppStatus.bStatus = errSTALLEDPKT;
}
} else if (startState == dfuERROR) {
/* status is in error, awaiting DFU_CLRSTATUS request */
if (pInformation->USBbRequest == DFU_GETSTATUS) {
/* todo, add routine to wait for last block write to finish */
dfuAppStatus.bState = dfuERROR;
} else if (pInformation->USBbRequest == DFU_GETSTATE) {
dfuAppStatus.bState = dfuERROR;
} else if (pInformation->USBbRequest == DFU_CLRSTATUS) {
/* todo handle any cleanup we need here */
dfuAppStatus.bState = dfuIDLE;
dfuAppStatus.bStatus = OK;
} else {
dfuAppStatus.bState = dfuERROR;
dfuAppStatus.bStatus = errSTALLEDPKT;
}
} else {
/* some kind of error... */
dfuAppStatus.bState = dfuERROR;
dfuAppStatus.bStatus = errSTALLEDPKT;
}
if (dfuAppStatus.bStatus == OK) {
return TRUE;
} else {
return FALSE;
}
}
void dfuUpdateByReset(void) {
u8 startState = dfuAppStatus.bState;
userFirmwareLen = 0;
if (startState == appDETACH) {
dfuAppStatus.bState = dfuIDLE;
dfuAppStatus.bStatus = OK;
nvicDisableInterrupts();
usbEnbISR();
} else if (startState == appIDLE || startState == dfuIDLE) {
/* do nothing...might be normal usb bus activity */
} else {
/* we reset from the dfu, reset everything and startover,
which is the correct operation if this is an erroneous
event or properly following a MANIFEST */
dfuAppStatus.bState = dfuIDLE;
dfuAppStatus.bStatus = OK;
systemHardReset();
}
}
void dfuUpdateByTimeout(void) {
}
u8 *dfuCopyState(u16 length) {
if (length == 0) {
pInformation->Ctrl_Info.Usb_wLength = 1;
return NULL;
} else {
return (&(dfuAppStatus.bState));
}
}
u8 *dfuCopyStatus(u16 length) {
if (length == 0) {
pInformation->Ctrl_Info.Usb_wLength = 6;
return NULL;
} else {
return (u8*)(&dfuAppStatus);
}
}
u8 *dfuCopyDNLOAD(u16 length) {
if (length == 0) {
pInformation->Ctrl_Info.Usb_wLength = pInformation->USBwLengths.w - pInformation->Ctrl_Info.Usb_wOffset;
thisBlockLen = pInformation->USBwLengths.w;
return NULL;
} else {
return ((u8 *)recvBuffer + pInformation->Ctrl_Info.Usb_wOffset);
}
}
u8 *dfuCopyUPLOAD(u16 length) {
if (length == 0) {
pInformation->Ctrl_Info.Usb_wLength = thisBlockLen - pInformation->Ctrl_Info.Usb_wOffset;
return NULL;
} else {
return((u8*) userAppAddr + userFirmwareLen + pInformation->Ctrl_Info.Usb_wOffset);
}
}
void dfuCopyBufferToExec() {
int i;
u32 *userSpace;
if (userUploadType == DFU_UPLOAD_RAM)
{
userSpace = (u32 *)(USER_CODE_RAM + userFirmwareLen);
/* we dont need to handle when thisBlock len is not divisible by 4,
since the linker will align everything to 4B anyway */
for (i = 0; i < thisBlockLen; i = i + 4) {
*userSpace++ = *(u32 *)(recvBuffer + i);
}
}
else
{
if (userUploadType == DFU_UPLOAD_FLASH_0X8005000)
{
userSpace = (u32 *)(USER_CODE_FLASH0X8005000 + userFirmwareLen);
}
else
{
userSpace = (u32 *)(USER_CODE_FLASH0X8002000 + userFirmwareLen);
}
flashErasePage((u32)(userSpace));
for (i = 0; i < thisBlockLen; i = i + 4) {
flashWriteWord((u32)(userSpace++), *(u32 *)(recvBuffer +i));
}
}
userFirmwareLen += thisBlockLen;
thisBlockLen = 0;
}
u8 dfuGetState(void) {
return dfuAppStatus.bState;
}
void dfuSetState(u8 newState) {
dfuAppStatus.bState = newState;
}
bool dfuUploadStarted() {
return dfuBusy;
}
void dfuFinishUpload() {
while (1) {
if (userUploadType==DFU_UPLOAD_RAM) {
if (code_copy_lock == BEGINNING) {
code_copy_lock = MIDDLE;
strobePin(LED_BANK, LED, 2, 0x1000);
dfuCopyBufferToExec();
strobePin(LED_BANK, LED, 2, 0x500);
code_copy_lock = END;
}
}
/* otherwise do nothing, dfu state machine resets itself */
}
}