419 lines
14 KiB
C
419 lines
14 KiB
C
#include "fwupdate.h"
|
||
|
||
|
||
#define unlock_flash() FLASH->KEYR = FLASH_KEY1; FLASH->KEYR = FLASH_KEY2
|
||
|
||
#define lock_flash() FLASH->CR = FLASH_CR_LOCK
|
||
// //The FLASH_CR register can be locked again by user software by writing the
|
||
// //LOCK bit in the FLASH_CR register to 1.
|
||
|
||
|
||
|
||
//flash must be erased prior to calling
|
||
FWUPDATE void wr_hword(uint16_t *addr, uint16_t data)
|
||
{
|
||
//Trying to get by with out a static variable
|
||
//writes to the current address of FLASH_AR,
|
||
//plus an 8bit address offset.
|
||
//the host can set the address by erasing a page
|
||
//that's really only useful when starting from a blank page though.
|
||
//it can also set the address by writing 0xFFFF to a byte that's already erased
|
||
|
||
unlock_flash();
|
||
|
||
//The main Flash memory programming sequence in standard mode is as follows:
|
||
// 1.Check that no main Flash memory operation is ongoing by checking the BSY bit in the FLASH_SR register.
|
||
while ( FLASH->SR & FLASH_SR_BSY ) { /* forever */ }
|
||
|
||
// 2. Set the PG bit in the FLASH_CR register.
|
||
FLASH->CR = FLASH_CR_PG; //shouldn't need to mask, all other bits clear by default
|
||
|
||
// 3. Perform the data write (half-word) at the desired address.
|
||
*addr = data;
|
||
|
||
// 4. Wait until the BSY bit is reset in the FLASH_SR register.
|
||
while ( FLASH->SR & FLASH_SR_BSY ) { /* forever */ }
|
||
|
||
// 5. Check the EOP flag in the FLASH_SR register (it is set when the programming operation has succeeded), and then clear it by software.
|
||
FLASH->SR = FLASH_SR_EOP;
|
||
|
||
// Note:The registers are not accessible in write mode when the BSY bit of the FLASH_SR register is set.
|
||
|
||
lock_flash();
|
||
}
|
||
|
||
FWUPDATE void erase_page(uint16_t page_num_1KB)
|
||
{
|
||
//usb driver & this code resisdes in first 2KByte of last (0x0800_0800)
|
||
//The smaller STMF070C6 has 32KByte of flash, and larger STMF070RB has 128KByte
|
||
//C6 flash is split into 1KB pages
|
||
//RB flash is split into 2KB pages
|
||
//So the C6 must leave first two pages alone, and RB only the first page
|
||
//But they're both leaving the first 2KByte untouched
|
||
//And erasing the 30KByte that follows
|
||
//For now we're ignoring the extra 96KByte of additional flash that the RB contains
|
||
|
||
//The program and erase operations can be performed over the whole product voltage range.
|
||
//They are managed through the following seven Flash registers:
|
||
//
|
||
// Key register (FLASH_KEYR)
|
||
// Option byte key register (FLASH_OPTKEYR)
|
||
// Flash control register (FLASH_CR)
|
||
// Flash status register (FLASH_SR)
|
||
// Flash address register (FLASH_AR)
|
||
// Option byte register (FLASH_OBR)
|
||
// Write protection register (FLASH_WRPR)
|
||
//
|
||
//An ongoing Flash memory operation will not block the CPU as long as the CPU does not access the Flash memory.
|
||
//On the contrary, during a program/erase operation to the Flash memory, any attempt to read the Flash memory
|
||
//will stall the bus. The read operation will proceed correctly once the program/erase operation has completed.
|
||
//This means that code or data fetches cannot be made while a program/erase operation is ongoing.
|
||
//
|
||
//For program and erase operations on the Flash memory (write/erase), the internal RC oscillator (HSI) must be ON.
|
||
//should be running on it right now...
|
||
//
|
||
//Unlocking the Flash memory
|
||
//After reset, the Flash memory is protected against unwanted write or erase operations.
|
||
//The FLASH_CR register is not accessible in write mode, except for the OBL_LAUNCH bit,
|
||
//used to reload the option bits. An unlocking sequence should be written to the FLASH_KEYR
|
||
//register to open the access to the FLASH_CR register. This sequence consists of two write operations:
|
||
// Write KEY1 = 0x45670123
|
||
//FLASH->KEYR = FLASH_KEY1;
|
||
// Write KEY2 = 0xCDEF89AB
|
||
//FLASH->KEYR = FLASH_KEY2;
|
||
unlock_flash();
|
||
//
|
||
//Any wrong sequence locks up the FLASH_CR register until the next reset.
|
||
//In the case of a wrong key sequence, a bus error is detected and a Hard Fault interrupt is generated.
|
||
//This is done after the first write cycle if KEY1 does not match, or during the second write cycle if
|
||
//KEY1 has been correctly written but KEY2 does not match.
|
||
//
|
||
//The FLASH_CR register can be locked again by user software by writing the
|
||
//LOCK bit in the FLASH_CR register to 1.
|
||
//FLASH->CR = FLASH_CR_LOCK;
|
||
|
||
//Page Erase
|
||
// To erase a page, the procedure below should be followed:
|
||
// 1.Check that no Flash memory operation is ongoing by checking the BSY bit in the FLASH_CR register.
|
||
// Think they mean the FLASH_SR register...?
|
||
// the BSY bit is supposed to clear itself when flash operation is complete, or errored out
|
||
// So it should never remain set forever..
|
||
while ( FLASH->SR & FLASH_SR_BSY ) { /* forever */ }
|
||
|
||
// 2. Set the PER bit in the FLASH_CR register.
|
||
FLASH->CR = FLASH_CR_PER; //shouldn't need to mask, all other bits clear by default
|
||
|
||
// 3. Program the FLASH_AR register to select a page to erase.
|
||
FLASH->AR = 0x08000000 + (page_num_1KB<<10); //page 2 (3rd KByte)
|
||
|
||
// 4. Set the STRT bit in the FLASH_CR register (see note below).
|
||
FLASH->CR = (FLASH_CR_PER | FLASH_CR_STRT);
|
||
|
||
// 5. Wait for the BSY bit to be reset.
|
||
__asm__ __volatile__ ("nop");
|
||
while ( FLASH->SR & FLASH_SR_BSY) { /* forever */ }
|
||
|
||
// 6. Check the EOP flag in the FLASH_SR register (it is set when the erase operation has succeeded).
|
||
// 7. Clear the EOP flag.
|
||
FLASH->SR = FLASH_SR_EOP;
|
||
// Note:The software should start checking if the BSY bit equals <20><><EFBFBD><EFBFBD>0<EFBFBD><30><EFBFBD><EFBFBD> at least one CPU cycle after setting the STRT bit.
|
||
|
||
//The FLASH_CR register can be locked again by user software by writing the
|
||
//LOCK bit in the FLASH_CR register to 1.
|
||
lock_flash();
|
||
}
|
||
|
||
|
||
|
||
FWUPDATE uint8_t fwupdate_call( uint8_t opcode, uint8_t miscdata, uint16_t operand, uint8_t *rdata )
|
||
{
|
||
#define RD_LEN 0
|
||
#define RD0 1
|
||
#define RD1 2
|
||
#define RD2 3
|
||
#define RD3 4
|
||
|
||
#define BYTE_LEN 1
|
||
#define HWORD_LEN 2
|
||
#define WORD_LEN 4
|
||
|
||
//pointer to flash address space
|
||
//inialize to the last accessed flash address
|
||
uint16_t *flash_addr = (uint16_t *)FLASH->AR;
|
||
|
||
switch (opcode) {
|
||
case ERASE_1KB_PAGE:
|
||
#ifdef STM32F070x6
|
||
if( (operand>1) && (operand<32)) { //only has 32KByte of flash
|
||
#else
|
||
if( (operand>1) && (operand<128)) { //RB has 128KByte of flash
|
||
#endif
|
||
erase_page(operand);
|
||
}else{
|
||
//don't want to erase ourselves!
|
||
//or hardfault
|
||
return ERR_FWUPDATE_BAD_ADDR; }
|
||
break;
|
||
|
||
//Don't really want to leave flash in an unlocked state..
|
||
// case UNLOCK_FLASH:
|
||
// unlock_flash(); break;
|
||
//
|
||
// case LOCK_FLASH:
|
||
// lock_flash(); break;
|
||
|
||
case WR_HWORD:
|
||
//the address is based on previous
|
||
//miscdata is the provided offset from last
|
||
flash_addr += miscdata;
|
||
wr_hword(flash_addr, operand);
|
||
break;
|
||
|
||
case SET_FLASH_ADDR:
|
||
//sets FLASH->AR to desired address by writing 0xFFFF to that address
|
||
//so it MUST already be erased!
|
||
#ifdef STM32F070x6 //only has 32KByte of flash
|
||
if (miscdata) { return ERR_FWUPDATE_BAD_ADDR; }
|
||
if (operand>0x7FFF) { return ERR_FWUPDATE_BAD_ADDR; }
|
||
#else
|
||
if (miscdata>1) { return ERR_FWUPDATE_BAD_ADDR; } //only 128KByte of flash
|
||
#endif
|
||
flash_addr = (uint16_t *) (0x08000000 + (miscdata<<16) + operand);
|
||
wr_hword(flash_addr, 0xFFFF);
|
||
|
||
break;
|
||
|
||
case GET_FLASH_ADDR:
|
||
rdata[RD_LEN] = WORD_LEN;
|
||
rdata[RD0] = FLASH->AR;
|
||
rdata[RD1] = FLASH->AR>>8;
|
||
rdata[RD2] = FLASH->AR>>16;
|
||
rdata[RD3] = FLASH->AR>>24;
|
||
break;
|
||
|
||
case GET_FLASH_DATA:
|
||
rdata[RD_LEN] = HWORD_LEN;
|
||
rdata[RD0] = *flash_addr;
|
||
rdata[RD1] = (*flash_addr)>>8;
|
||
break;
|
||
|
||
case READ_FLASH:
|
||
#ifdef STM32F070x6 //only has 32KByte of flash
|
||
if (miscdata) { return ERR_FWUPDATE_BAD_ADDR; }
|
||
if (operand>0x7FFF) { return ERR_FWUPDATE_BAD_ADDR; }
|
||
#else
|
||
if (miscdata>1) { return ERR_FWUPDATE_BAD_ADDR; } //only 128KByte of flash
|
||
#endif
|
||
flash_addr = (uint16_t *) (0x08000000 + (miscdata<<16) + operand);
|
||
rdata[RD_LEN] = HWORD_LEN;
|
||
rdata[RD0] = *flash_addr;
|
||
rdata[RD1] = (*flash_addr)>>8;
|
||
break;
|
||
|
||
case RESET_DEVICE:
|
||
SCB->AIRCR = 0x05FA0004;
|
||
//device will not actually return from this..
|
||
//although we could get it to by having it issue reset once back
|
||
//in the fwupdate forever loop:
|
||
//usbfuncwrite = RESETME;
|
||
//shouldn't need this variable till after reset..
|
||
//couldn't get that method to work though, so just don't bother returning for now..
|
||
break;
|
||
|
||
|
||
default: //opcode doesn't exist
|
||
return ERR_UNKN_FWUPDATE_OPCODE;
|
||
}
|
||
|
||
return SUCCESS;
|
||
|
||
}
|
||
|
||
|
||
FWUPDATE_NOIN uint16_t usb_fwupdate_setup(uint8_t data[8])
|
||
{
|
||
//cast incoming data to a setup_packet
|
||
setup_packet *spacket = (void *)data;
|
||
|
||
//create a return array for data
|
||
static uint16_t rv16[RETURN_BUFF_SIZE/2];
|
||
uint8_t *rv = (uint8_t*)rv16;
|
||
|
||
|
||
//create a usbMsgPtr variable from the stack which we can use convienently
|
||
//but then at end of the function we'll need to copy the value over to usb_buff usbMsgPtr_H/L
|
||
usbMsgPtr_t usbMsgPtr;
|
||
|
||
rv[RETURN_ERR_IDX] = GEN_FAIL; //default to error till opcode updates.
|
||
rv[RETURN_LEN_IDX] = 0; //reset to zero, number of bytes in return data (excluding ERR & LEN)
|
||
|
||
usbMsgPtr = (usbMsgPtr_t)rv;
|
||
|
||
uint8_t rlen = (uint8_t) spacket->wLength;
|
||
|
||
switch(spacket->bRequest) {
|
||
case DICT_FWUPDATE:
|
||
rv[RETURN_ERR_IDX] = fwupdate_call( spacket->opcode, spacket->miscdata, spacket->operand, &rv[RETURN_LEN_IDX] );
|
||
break;
|
||
default:
|
||
//request (aka dictionary) is unknown
|
||
rv[RETURN_ERR_IDX] = ERR_UNKN_DICTIONARY;
|
||
}
|
||
|
||
usbMsgPtr_L = (uint32_t)usbMsgPtr;
|
||
usbMsgPtr_H = ((uint32_t)usbMsgPtr)>>16;
|
||
|
||
return rlen;
|
||
}
|
||
|
||
|
||
FWUPDATE_NOIN uint8_t usb_fwupdate_write(uint8_t *data, uint8_t len)
|
||
{
|
||
|
||
}
|
||
|
||
|
||
|
||
//This function has a fixed location so the application code knows where to find it
|
||
//and it shouldn't change
|
||
FWUPMAIN uint8_t fwupdate_forever()
|
||
{
|
||
|
||
//need to turn off any interrupt sources except USB
|
||
|
||
//Cannot turn off the WDT not possible! We must keep petting him!
|
||
|
||
//update usb function pointers to fwupdate functions
|
||
//this file is compiled at same time as the the setup/write functions
|
||
//so it's okay to refernce them at compile time
|
||
usbfuncsetup = (uint32_t) &usb_fwupdate_setup; //should only assign lower 16bits
|
||
usbfuncwrite = (uint32_t) &usb_fwupdate_write; //should only assign lower 16bits
|
||
|
||
//need to return back to the bootloader PREP_FWUPDATE call that got us here
|
||
//but when that's done we want to hijack execution so the USB ISR returns here
|
||
//instead of the application main
|
||
|
||
//modify the return PC/LR that's on the stack for the USB interrupt that's
|
||
//currently being handled
|
||
//
|
||
//APSR bits 31-28 NZCV processor flags (could be any value)
|
||
//EPSR bit 24 Thumb (should be set)
|
||
//IPSR bits 5-0 Exception number (should be zero if not in nested interrupt)
|
||
//bits 27-25 & 23-6 should all be cleared for the stacked xPSR
|
||
//bit 24 should be set (always in thumb mode)
|
||
//bits 5-0 are probably clear if the device was in main (thread mode)
|
||
//probably don't want to jump into the fw updater if it wasn't anyway..!
|
||
//
|
||
//so we don't necessarily know how far the stack pointer has decremented away
|
||
//from the current processor stack frame created for the current exception
|
||
//
|
||
//but if we search back far enough, we'll find stack frame that looks like:
|
||
// R0, R1, R2, R3, R12, LR, PC, xPSR
|
||
// The PC should be 0x0800_????, and the LR probably is to..?
|
||
// PC could be something else if it happened to be executing from RAM
|
||
// or we're on a processor with more than 64KByte of flash
|
||
// The xPSR should be 0b????0001_00000000_00000000_00000000
|
||
//
|
||
// we're going to play it safe and require PC == 0x0800???? and the xPSR == 0x?1000000
|
||
// we're also going to stop the LR just so it can't cause any troubles
|
||
//
|
||
// once the PC & LR are hijacked to get back here, we need to return to the
|
||
// PREP_FWUPDATE call, and let it know all was well.
|
||
asm(
|
||
//use r0 as our pointer to the stack
|
||
"mov r0, r13\n"
|
||
|
||
//xPSR has to be atleast 8 words back/up
|
||
"add r0, #28\n"
|
||
|
||
"ldr r2, psr_mask\n"
|
||
"ldr r3, psr_expect\n"
|
||
|
||
"skip_2words:\n"
|
||
"add r0, #4\n"
|
||
|
||
"next_word:\n"
|
||
"add r0, #4\n"
|
||
"ldr r1, [r0]\n"
|
||
"and r1, r1, r2\n"
|
||
"sub r1, r1, r3\n"
|
||
"bne next_word\n"
|
||
|
||
//now r0 should be pointing to xPSR
|
||
//
|
||
//decrement to PC and verify 0x0800????
|
||
"ldr r2, pc_mask\n"
|
||
"ldr r3, pc_expect\n"
|
||
"sub r0, #4\n"
|
||
|
||
"ldr r1, [r0]\n"
|
||
"and r1, r1, r2\n"
|
||
"sub r1, r1, r3\n"
|
||
"bne skip_2words\n" //the PC didn't match the expected, xPSR must have been false positive
|
||
//if we go past the end of SRAM we'll get a hardfault and quit
|
||
|
||
//PC matched expectation, we've found it!
|
||
|
||
//stomp the PC and then the LR with
|
||
//loop forever PC
|
||
"mov r3, pc\n" //pc currently points to next instruction
|
||
|
||
"add r3, #10\n" //forever loop is 6 instructions ahead of here
|
||
//minus one as if the PC was executing the "b done" instruction
|
||
//and it should enter at fwupdateloop
|
||
|
||
//stomp the PC in stack frame
|
||
"str r3, [r0]\n"
|
||
"sub r0, #4\n"
|
||
//stomp the LR in the stack frame
|
||
"add r3, #1\n" //LR need to be Thumb
|
||
"str r3, [r0]\n"
|
||
|
||
"b done\n"
|
||
|
||
"fwupdateloop:\n");
|
||
|
||
//the forever main loop is here!
|
||
|
||
//when USB interrupts occur they should return back to here
|
||
|
||
//if fwupdate is done, intitate system reset
|
||
//maybe it's safer to have the user do this by unpluggig the device..?
|
||
|
||
//pet watchdog
|
||
IWDG->KR = 0x0000AAAA;
|
||
|
||
//Couldn't get this to work for some reason...
|
||
// if (usbfuncwrite == RESETME ) {
|
||
// SCB->AIRCR = 0x05FA0004;
|
||
// }
|
||
|
||
|
||
|
||
asm( "b fwupdateloop\n"
|
||
|
||
".p2align 2\n"
|
||
"pc_mask:\n"
|
||
".word 0xFFFF0000\n" //bits of the PC we want to match
|
||
"pc_expect:\n"
|
||
".word 0x08000000\n" //bits of the PC we want to match
|
||
"psr_mask:\n"
|
||
".word 0x0FFFFFFF\n" //bits of the xPSR we want to match
|
||
"psr_expect:\n"
|
||
".word 0x01000000\n" //bits of the xPSR we want to match
|
||
|
||
// "beef:\n"
|
||
// ".word 0xBEAD5678\n"
|
||
|
||
"done:\n"
|
||
// "bkpt\n"
|
||
|
||
|
||
);
|
||
|
||
//return the PREP_FWUPDATE call that got us here
|
||
return SUCCESS;
|
||
|
||
}
|
||
|