500
sw/src/dfu.c
Normal file
500
sw/src/dfu.c
Normal file
@ -0,0 +1,500 @@
|
||||
/*
|
||||
* Fadecandy DFU Bootloader
|
||||
*
|
||||
* Copyright (c) 2013 Micah Elizabeth Scott
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <toboot-api.h>
|
||||
#include <toboot-internal.h>
|
||||
#include <dfu.h>
|
||||
|
||||
// Internal flash-programming state machine
|
||||
static unsigned fl_current_addr = 0;
|
||||
static enum {
|
||||
flsIDLE = 0,
|
||||
flsERASING,
|
||||
flsPROGRAMMING
|
||||
} fl_state;
|
||||
|
||||
static struct toboot_state {
|
||||
// Version number of the program being loaded:
|
||||
// 0 (legacy)
|
||||
// 1 (toboot v1)
|
||||
// 2 (toboot v2)
|
||||
uint8_t version;
|
||||
|
||||
// When clearing, first ensure these sectors are cleared prior to updating
|
||||
uint32_t clear_lo;
|
||||
uint32_t clear_hi;
|
||||
|
||||
// The current block we're clearing
|
||||
uint32_t clear_current;
|
||||
|
||||
// This is the address we'll start programming/erasing from after clearing
|
||||
uint32_t next_addr;
|
||||
|
||||
enum {
|
||||
/// Toboot has just started
|
||||
tbsIDLE,
|
||||
|
||||
/// Secure erase memory is being cleared
|
||||
tbsCLEARING,
|
||||
|
||||
/// New image is being loaded
|
||||
tbsLOADING,
|
||||
} state;
|
||||
} tb_state;
|
||||
|
||||
static dfu_state_t dfu_state = dfuIDLE;
|
||||
static dfu_status_t dfu_status = OK;
|
||||
static unsigned dfu_poll_timeout = 1;
|
||||
|
||||
static uint32_t dfu_buffer[DFU_TRANSFER_SIZE/4];
|
||||
static uint32_t dfu_buffer_offset;
|
||||
static uint32_t fl_num_words;
|
||||
|
||||
// Memory offset we're uploading to.
|
||||
static uint32_t dfu_target_address;
|
||||
|
||||
static void set_state(dfu_state_t new_state, dfu_status_t new_status) {
|
||||
dfu_state = new_state;
|
||||
dfu_status = new_status;
|
||||
}
|
||||
|
||||
bool fl_is_idle(void) {
|
||||
return fl_state == flsIDLE;
|
||||
}
|
||||
|
||||
/*
|
||||
void *memcpy(void *dst, const void *src, size_t cnt) {
|
||||
uint8_t *dst8 = dst;
|
||||
const uint8_t *src8 = src;
|
||||
while (cnt > 0) {
|
||||
cnt--;
|
||||
*(dst8++) = *(src8++);
|
||||
}
|
||||
return dst;
|
||||
}
|
||||
*/
|
||||
|
||||
static bool ftfl_busy()
|
||||
{
|
||||
// Is the flash memory controller busy?
|
||||
// return (MSC->STATUS & MSC_STATUS_BUSY);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void ftfl_busy_wait()
|
||||
{
|
||||
// Wait for the flash memory controller to finish any pending operation.
|
||||
while (ftfl_busy())
|
||||
;//watchdog_refresh();
|
||||
}
|
||||
|
||||
static void ftfl_begin_erase_sector(uint32_t address)
|
||||
{
|
||||
// Erase the page at the specified address.
|
||||
//MSC->WRITECTRL |= MSC_WRITECTRL_WREN;
|
||||
|
||||
ftfl_busy_wait();
|
||||
//MSC->ADDRB = address;
|
||||
//MSC->WRITECMD = MSC_WRITECMD_LADDRIM;
|
||||
//MSC->WRITECMD = MSC_WRITECMD_ERASEPAGE;
|
||||
}
|
||||
|
||||
static void ftfl_begin_program_section(uint32_t address)
|
||||
{
|
||||
// Write the buffer word to the currently selected address.
|
||||
// Note that after this is done, the address is incremented by 4.
|
||||
dfu_buffer_offset = 0;
|
||||
dfu_target_address = address;
|
||||
fl_num_words--;
|
||||
ftfl_busy_wait();
|
||||
//MSC->ADDRB = address;
|
||||
ftfl_busy_wait();
|
||||
//MSC->WRITECTRL |= MSC_WRITECTRL_WREN;
|
||||
//MSC->WDATA = dfu_buffer[dfu_buffer_offset++];
|
||||
//MSC->WRITECMD = MSC_WRITECMD_WRITEONCE;
|
||||
}
|
||||
|
||||
static uint32_t address_for_block(unsigned blockNum)
|
||||
{
|
||||
static uint32_t starting_offset;
|
||||
if (blockNum == 0) {
|
||||
// Determine Toboot version.
|
||||
if ((dfu_buffer[0x94 / 4] & TOBOOT_V2_MAGIC_MASK) == TOBOOT_V2_MAGIC) {
|
||||
tb_state.version = 2;
|
||||
starting_offset = ((struct toboot_configuration *)&dfu_buffer[0x94 / 4])->start;
|
||||
}
|
||||
// V1 used a different offset.
|
||||
else if ((dfu_buffer[0x98 / 4] & TOBOOT_V1_MAGIC_MASK) == TOBOOT_V1_MAGIC) {
|
||||
// Applications that know about Toboot will indicate their block
|
||||
// offset by placing a magic byte at offset 0x98.
|
||||
// Ordinarily this would be the address offset for IRQ 22,
|
||||
// but since there are only 20 IRQs on the EFM32HG, there are three
|
||||
// 32-bit values that are unused starting at offset 0x94.
|
||||
// We already use offset 0x94 for "disable boot", so use offset 0x98
|
||||
// in the incoming stream to indicate flags for Toboot.
|
||||
tb_state.version = 1;
|
||||
starting_offset = (dfu_buffer[0x98 / 4] & TOBOOT_V1_APP_PAGE_MASK) >> TOBOOT_V1_APP_PAGE_SHIFT;
|
||||
}
|
||||
// Legacy programs default to offset 0x4000.
|
||||
else {
|
||||
tb_state.version = 0;
|
||||
starting_offset = 16;
|
||||
}
|
||||
|
||||
// Set the state to "CLEARING", since we're just starting the programming process.
|
||||
tb_state.state = tbsCLEARING;
|
||||
starting_offset *= 0x400;
|
||||
}
|
||||
return starting_offset + (blockNum << 10);
|
||||
}
|
||||
|
||||
// If requested, erase sectors before loading new code.
|
||||
static void pre_clear_next_block(void) {
|
||||
|
||||
// If there is another sector to clear, do that.
|
||||
while (++tb_state.clear_current < 64) {
|
||||
if (tb_state.clear_current < 32) {
|
||||
if ((tb_state.clear_lo & (1 << tb_state.clear_current))) {
|
||||
ftfl_begin_erase_sector(tb_state.clear_current * 1024);
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if (tb_state.clear_current < 64) {
|
||||
if ((tb_state.clear_hi & (1 << (tb_state.clear_current & 31)))) {
|
||||
ftfl_begin_erase_sector(tb_state.clear_current * 1024);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// No more sectors to clear, continue with programming
|
||||
tb_state.state = tbsLOADING;
|
||||
ftfl_begin_erase_sector(tb_state.next_addr);
|
||||
}
|
||||
|
||||
void dfu_init(void)
|
||||
{
|
||||
tb_state.state = tbsIDLE;
|
||||
/*
|
||||
// Ensure the clocks for the memory are enabled
|
||||
CMU->OSCENCMD = CMU_OSCENCMD_AUXHFRCOEN;
|
||||
while (!(CMU->STATUS & CMU_STATUS_AUXHFRCORDY))
|
||||
;
|
||||
|
||||
// Unlock the MSC
|
||||
MSC->LOCK = MSC_UNLOCK_CODE;
|
||||
|
||||
// Enable writing to flash
|
||||
MSC->WRITECTRL |= MSC_WRITECTRL_WREN;
|
||||
MSC->IEN |= MSC_IEN_WRITE | MSC_IEN_ERASE;
|
||||
NVIC_EnableIRQ(MSC_IRQn);
|
||||
*/
|
||||
}
|
||||
|
||||
uint8_t dfu_getstate(void)
|
||||
{
|
||||
return dfu_state;
|
||||
}
|
||||
|
||||
bool dfu_download(unsigned blockNum, unsigned blockLength,
|
||||
unsigned packetOffset, unsigned packetLength, const uint8_t *data)
|
||||
{
|
||||
uint32_t i;
|
||||
|
||||
if (packetOffset + packetLength > DFU_TRANSFER_SIZE ||
|
||||
packetOffset + packetLength > blockLength) {
|
||||
|
||||
// Overflow!
|
||||
set_state(dfuERROR, errADDRESS);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Store more data...
|
||||
memcpy(((uint8_t *)dfu_buffer) + packetOffset, data, packetLength);
|
||||
|
||||
if (packetOffset + packetLength != blockLength) {
|
||||
// Still waiting for more data.
|
||||
return true;
|
||||
}
|
||||
|
||||
if (dfu_state != dfuIDLE && dfu_state != dfuDNLOAD_IDLE) {
|
||||
// Wrong state! Oops.
|
||||
set_state(dfuERROR, errSTALLEDPKT);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ftfl_busy() || fl_state != flsIDLE) {
|
||||
// Flash controller shouldn't be busy now!
|
||||
set_state(dfuERROR, errUNKNOWN);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!blockLength) {
|
||||
// End of download
|
||||
set_state(dfuMANIFEST_SYNC, OK);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Start programming a block by erasing the corresponding flash sector
|
||||
fl_state = flsERASING;
|
||||
fl_current_addr = address_for_block(blockNum);
|
||||
fl_num_words = blockLength / 4;
|
||||
|
||||
// If it's the first block, figure out what we need to do in terms of erasing
|
||||
// data and programming the new file.
|
||||
if (blockNum == 0) {
|
||||
const struct toboot_configuration *old_config = tb_get_config();
|
||||
|
||||
// Don't allow overwriting Toboot itself.
|
||||
if (fl_current_addr < tb_first_free_address()) {
|
||||
set_state(dfuERROR, errADDRESS);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Calculate generation number and hash
|
||||
if (tb_state.version == 2) {
|
||||
struct toboot_configuration *new_config = (struct toboot_configuration *)&dfu_buffer[0x94 / 4];
|
||||
|
||||
// Update generation number
|
||||
new_config->reserved_gen = old_config->reserved_gen + 1;
|
||||
|
||||
// Ensure we know this header is not fake
|
||||
new_config->config &= ~TOBOOT_CONFIG_FAKE;
|
||||
|
||||
// Generate a valid signature
|
||||
tb_sign_config(new_config);
|
||||
}
|
||||
|
||||
// If the old configuration requires that certain blocks be erased, do that.
|
||||
tb_state.clear_hi = old_config->erase_mask_hi;
|
||||
tb_state.clear_lo = old_config->erase_mask_lo;
|
||||
tb_state.clear_current = 0;
|
||||
|
||||
// Ensure we don't erase Toboot itself
|
||||
for (i = 0; i < tb_first_free_sector(); i++) {
|
||||
if (i < 32)
|
||||
tb_state.clear_lo &= ~(1 << i);
|
||||
else
|
||||
tb_state.clear_hi &= ~(1 << i);
|
||||
}
|
||||
|
||||
// If the newly-loaded program does not conform to Toboot V2.0, then look
|
||||
// for any existing programs on the flash and delete those sectors.
|
||||
// Because of boot priority, we need to ensure that no V2.0 applications
|
||||
// exist on flash.
|
||||
if (tb_state.version < 2) {
|
||||
for (i = tb_first_free_sector(); i < 64; i++) {
|
||||
if (tb_valid_signature_at_page(i) < 0)
|
||||
continue;
|
||||
if (i < 32)
|
||||
tb_state.clear_lo |= (1 << i);
|
||||
else
|
||||
tb_state.clear_hi |= (1 << (i - 32));
|
||||
}
|
||||
}
|
||||
|
||||
// If we still have sectors to clear, do that. Otherwise,
|
||||
// go straight into loading the program.
|
||||
if (tb_state.clear_lo || tb_state.clear_hi) {
|
||||
tb_state.state = tbsCLEARING;
|
||||
tb_state.next_addr = fl_current_addr;
|
||||
pre_clear_next_block();
|
||||
}
|
||||
else {
|
||||
tb_state.state = tbsLOADING;
|
||||
ftfl_begin_erase_sector(fl_current_addr);
|
||||
}
|
||||
}
|
||||
else
|
||||
ftfl_begin_erase_sector(fl_current_addr);
|
||||
|
||||
set_state(dfuDNLOAD_SYNC, OK);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool fl_handle_status(uint8_t fstat)
|
||||
{
|
||||
/*
|
||||
* Handle common errors from an FSTAT register value.
|
||||
* The indicated "specificError" is used for reporting a command-specific
|
||||
* error from MGSTAT0.
|
||||
*
|
||||
* Returns true if handled, false if not.
|
||||
*/
|
||||
#if 0
|
||||
if (fstat & MSC_STATUS_BUSY) {
|
||||
// Still working...
|
||||
return true;
|
||||
}
|
||||
|
||||
if (fstat & (MSC_STATUS_ERASEABORTED | MSC_STATUS_WORDTIMEOUT)) {
|
||||
// Bus collision. We did something wrong internally.
|
||||
set_state(dfuERROR, errUNKNOWN);
|
||||
fl_state = flsIDLE;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (fstat & (MSC_STATUS_INVADDR | MSC_STATUS_LOCKED)) {
|
||||
// Address or protection error
|
||||
set_state(dfuERROR, errADDRESS);
|
||||
fl_state = flsIDLE;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (fl_state == flsPROGRAMMING) {
|
||||
// Still programming...
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
static void fl_state_poll(void)
|
||||
{
|
||||
// Try to advance the state of our own flash programming state machine.
|
||||
|
||||
uint32_t fstat = 0;//MSC->STATUS;
|
||||
|
||||
switch (fl_state) {
|
||||
|
||||
case flsIDLE:
|
||||
break;
|
||||
|
||||
case flsERASING:
|
||||
if (!fl_handle_status(fstat)) {
|
||||
// ?If we're still pre-clearing, continue with that.
|
||||
if (tb_state.state == tbsCLEARING) {
|
||||
pre_clear_next_block();
|
||||
}
|
||||
// Done! Move on to programming the sector.
|
||||
else {
|
||||
fl_state = flsPROGRAMMING;
|
||||
ftfl_begin_program_section(fl_current_addr);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case flsPROGRAMMING:
|
||||
if (!fl_handle_status(fstat)) {
|
||||
// Done!
|
||||
fl_state = flsIDLE;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool dfu_getstatus(uint8_t status[8])
|
||||
{
|
||||
switch (dfu_state) {
|
||||
|
||||
case dfuDNLOAD_SYNC:
|
||||
case dfuDNBUSY:
|
||||
// Programming operation in progress. Advance our private flash state machine.
|
||||
fl_state_poll();
|
||||
|
||||
if (dfu_state == dfuERROR) {
|
||||
// An error occurred inside fl_state_poll();
|
||||
} else if (fl_state == flsIDLE) {
|
||||
dfu_state = dfuDNLOAD_IDLE;
|
||||
} else {
|
||||
dfu_state = dfuDNBUSY;
|
||||
}
|
||||
break;
|
||||
|
||||
case dfuMANIFEST_SYNC:
|
||||
// Ready to reboot. The main thread will take care of this. Also let the DFU tool
|
||||
// know to leave us alone until this happens.
|
||||
dfu_state = dfuMANIFEST;
|
||||
dfu_poll_timeout = 10;
|
||||
break;
|
||||
|
||||
case dfuMANIFEST:
|
||||
// Perform the reboot
|
||||
dfu_state = dfuMANIFEST_WAIT_RESET;
|
||||
dfu_poll_timeout = 1000;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
status[0] = dfu_status;
|
||||
status[1] = dfu_poll_timeout;
|
||||
status[2] = dfu_poll_timeout >> 8;
|
||||
status[3] = dfu_poll_timeout >> 16;
|
||||
status[4] = dfu_state;
|
||||
status[5] = 0; // iString
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool dfu_clrstatus(void)
|
||||
{
|
||||
switch (dfu_state) {
|
||||
|
||||
case dfuERROR:
|
||||
// Clear an error
|
||||
set_state(dfuIDLE, OK);
|
||||
return true;
|
||||
|
||||
default:
|
||||
// Unexpected request
|
||||
set_state(dfuERROR, errSTALLEDPKT);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool dfu_abort(void)
|
||||
{
|
||||
set_state(dfuIDLE, OK);
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
void MSC_Handler(void) {
|
||||
uint32_t msc_irq_reason = MSC->IF;
|
||||
|
||||
if (msc_irq_reason & MSC_IF_WRITE) {
|
||||
// Write the buffer word to the currently selected address.
|
||||
// Note that after this is done, the address is incremented by 4.
|
||||
if (fl_num_words > 0) {
|
||||
fl_num_words--;
|
||||
dfu_target_address += 4;
|
||||
MSC->ADDRB = dfu_target_address;
|
||||
ftfl_busy_wait();
|
||||
MSC->WDATA = dfu_buffer[dfu_buffer_offset++];
|
||||
MSC->WRITECMD = MSC_WRITECMD_WRITEONCE;
|
||||
}
|
||||
else {
|
||||
// Move to the IDLE state only if we're out of data to write.
|
||||
fl_state = flsIDLE;
|
||||
}
|
||||
}
|
||||
|
||||
// Clear iterrupts so we don't fire again.
|
||||
MSC->IFC = MSC_IFC_ERASE | MSC_IFC_WRITE;
|
||||
}
|
||||
*/
|
138
sw/src/toboot.c
Normal file
138
sw/src/toboot.c
Normal file
@ -0,0 +1,138 @@
|
||||
#include "toboot-api.h"
|
||||
#include "toboot-internal.h"
|
||||
|
||||
#define XXH_NO_LONG_LONG
|
||||
#define XXH_FORCE_ALIGN_CHECK 0
|
||||
#define XXH_FORCE_NATIVE_FORMAT 0
|
||||
#define XXH_PRIVATE_API
|
||||
#include "xxhash.h"
|
||||
|
||||
static const struct toboot_configuration *current_config = NULL;
|
||||
|
||||
uint32_t tb_first_free_address(void) {
|
||||
return 131072;
|
||||
/*
|
||||
extern uint32_t _eflash;
|
||||
extern uint32_t _sdtext;
|
||||
extern uint32_t _edtext;
|
||||
#define PADDR(x) ((uint32_t)&x)
|
||||
#define PAGE_SIZE 1024
|
||||
#define PAGE_ROUND_UP(x) ( (((uint32_t)(x)) + PAGE_SIZE-1) & (~(PAGE_SIZE-1)) )
|
||||
return PAGE_ROUND_UP(PADDR(_eflash) + (PADDR(_edtext) - PADDR(_sdtext)));
|
||||
#undef PADDR
|
||||
#undef PAGE_SIZE
|
||||
#undef PAGE_ROUND_UP
|
||||
*/
|
||||
}
|
||||
|
||||
uint32_t tb_config_hash(const struct toboot_configuration *cfg) {
|
||||
return XXH32(cfg, sizeof(*cfg) - 4, TOBOOT_HASH_SEED);
|
||||
}
|
||||
|
||||
void tb_sign_config(struct toboot_configuration *cfg) {
|
||||
cfg->reserved_hash = tb_config_hash(cfg);
|
||||
}
|
||||
|
||||
int tb_valid_signature_at_page(uint32_t page) {
|
||||
const struct toboot_configuration *cfg = (const struct toboot_configuration *)((page * 1024) + 0x94);
|
||||
if (cfg->magic != TOBOOT_V2_MAGIC)
|
||||
return -1;
|
||||
|
||||
uint32_t calc_hash = tb_config_hash(cfg);
|
||||
if (calc_hash != cfg->reserved_hash)
|
||||
return -2;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint32_t tb_first_free_sector(void) {
|
||||
return tb_first_free_address() / 1024;
|
||||
}
|
||||
|
||||
const struct toboot_configuration *tb_get_config(void) {
|
||||
uint32_t __app_start__ = 131072;
|
||||
|
||||
// When examining every application in flash, find the newest program
|
||||
// with the highest generation counter.
|
||||
uint32_t newest_generation = 0;
|
||||
|
||||
// Fake toboot config, for v1 and v0 programs.
|
||||
static struct toboot_configuration fake_config;
|
||||
|
||||
if (current_config)
|
||||
return current_config;
|
||||
|
||||
// Look for a V2 header
|
||||
uint32_t page;
|
||||
for (page = 1; page < 65536/1024; page++) {
|
||||
if (!tb_valid_signature_at_page(page)) {
|
||||
const struct toboot_configuration *test_cfg = (const struct toboot_configuration *)((page * 1024) + 0x94);
|
||||
if (test_cfg->reserved_gen > newest_generation) {
|
||||
newest_generation = test_cfg->reserved_gen;
|
||||
current_config = test_cfg;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (current_config)
|
||||
return current_config;
|
||||
|
||||
// No V2 header found, so create one.
|
||||
|
||||
// Fake V2 magic
|
||||
fake_config.magic = TOBOOT_V2_MAGIC;
|
||||
|
||||
if (((*((uint32_t *)(((uint32_t)&__app_start__) + 0x98))) & TOBOOT_V1_MAGIC_MASK) == TOBOOT_V1_MAGIC)
|
||||
// Applications that know about Toboot will indicate their block
|
||||
// offset by placing a magic byte at offset 0x98.
|
||||
// Ordinarily this would be the address offset for IRQ 22,
|
||||
// but since there are only 20 IRQs on the EFM32HG, there are three
|
||||
// 32-bit values that are unused starting at offset 0x94.
|
||||
// We already use offset 0x94 for "disable boot", so use offset 0x98
|
||||
// in the incoming stream to indicate flags for Toboot.
|
||||
fake_config.start = ((*((uint32_t *)(((uint32_t)&__app_start__) + 0x98))) & TOBOOT_V1_APP_PAGE_MASK) >> TOBOOT_V1_APP_PAGE_SHIFT;
|
||||
else
|
||||
// Default to offset 0x4000
|
||||
fake_config.start = 16;
|
||||
|
||||
// Leave interrupts enabled (and indicate the header is fake)
|
||||
fake_config.config = TOBOOT_CONFIG_FLAG_ENABLE_IRQ | TOBOOT_CONFIG_FAKE;
|
||||
|
||||
// Lock out bootloader entry, if the magic value is present
|
||||
if (((*((uint32_t *)(((uint32_t)&__app_start__) + 0x94))) & TOBOOT_V1_CFG_MAGIC_MASK) == TOBOOT_V1_CFG_MAGIC)
|
||||
fake_config.lock_entry = TOBOOT_LOCKOUT_MAGIC;
|
||||
else
|
||||
fake_config.lock_entry = 0;
|
||||
|
||||
// Don't erase anything in particular
|
||||
fake_config.erase_mask_lo = 0;
|
||||
fake_config.erase_mask_hi = 0;
|
||||
|
||||
// Calculate a valid hash
|
||||
tb_sign_config(&fake_config);
|
||||
|
||||
return &fake_config;
|
||||
}
|
||||
|
||||
uint32_t tb_generation(const struct toboot_configuration *cfg) {
|
||||
if (!cfg)
|
||||
return 0;
|
||||
return cfg->reserved_gen;
|
||||
}
|
||||
|
||||
__attribute__ ((used, section(".toboot_configuration"))) struct toboot_configuration toboot_configuration = {
|
||||
.magic = TOBOOT_V2_MAGIC,
|
||||
|
||||
// The current "generation" flag sits at the same location as the
|
||||
// old Toboot "Config" flag. By setting "reserved_gen" to this value,
|
||||
// we can make the V1 bootloader treat V2 images as valid.
|
||||
.reserved_gen = TOBOOT_V1_APP_MAGIC,
|
||||
|
||||
.start = 0,
|
||||
.config = 0,
|
||||
|
||||
.lock_entry = 0,
|
||||
.erase_mask_lo = 0,
|
||||
.erase_mask_hi = 0,
|
||||
.reserved_hash = 0,
|
||||
};
|
229
sw/src/usb-desc.c
Normal file
229
sw/src/usb-desc.c
Normal file
@ -0,0 +1,229 @@
|
||||
/* Teensyduino Core Library
|
||||
* http://www.pjrc.com/teensy/
|
||||
* Copyright (c) 2013 PJRC.COM, 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:
|
||||
*
|
||||
* 1. The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* 2. If the Software is incorporated into a build system that allows
|
||||
* selection among a list of target devices, then similar target
|
||||
* devices manufactured by PJRC.COM must be included in the list of
|
||||
* target devices and selectable in the same manner.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include <usb-desc.h>
|
||||
|
||||
// USB Descriptors are binary data which the USB host reads to
|
||||
// automatically detect a USB device's capabilities. The format
|
||||
// and meaning of every field is documented in numerous USB
|
||||
// standards. When working with USB descriptors, despite the
|
||||
// complexity of the standards and poor writing quality in many
|
||||
// of those documents, remember descriptors are nothing more
|
||||
// than constant binary data that tells the USB host what the
|
||||
// device can do. Computers will load drivers based on this data.
|
||||
// Those drivers then communicate on the endpoints specified by
|
||||
// the descriptors.
|
||||
|
||||
// To configure a new combination of interfaces or make minor
|
||||
// changes to existing configuration (eg, change the name or ID
|
||||
// numbers), usually you would edit "usb_desc.h". This file
|
||||
// is meant to be configured by the header, so generally it is
|
||||
// only edited to add completely new USB interfaces or features.
|
||||
|
||||
// **************************************************************
|
||||
// USB Device
|
||||
// **************************************************************
|
||||
|
||||
#define LSB(n) ((n) & 255)
|
||||
#define MSB(n) (((n) >> 8) & 255)
|
||||
|
||||
#define USB_DT_BOS_SIZE 5
|
||||
#define USB_DT_BOS 0xf
|
||||
#define USB_DT_DEVICE_CAPABILITY 0x10
|
||||
#define USB_DC_PLATFORM 5
|
||||
|
||||
struct usb_bos_descriptor {
|
||||
uint8_t bLength;
|
||||
uint8_t bDescriptorType;
|
||||
uint16_t wTotalLength;
|
||||
uint8_t bNumDeviceCaps;
|
||||
} __attribute__((packed));
|
||||
|
||||
// USB Device Descriptor. The USB host reads this first, to learn
|
||||
// what type of device is connected.
|
||||
static const uint8_t device_descriptor[] = {
|
||||
18, // bLength
|
||||
1, // bDescriptorType
|
||||
0x10, 0x02, // bcdUSB
|
||||
0x00, // bDeviceClass
|
||||
0x00, // bDeviceSubClass
|
||||
0x00, // bDeviceProtocol
|
||||
EP0_SIZE, // bMaxPacketSize0
|
||||
LSB(VENDOR_ID), MSB(VENDOR_ID), // idVendor
|
||||
LSB(PRODUCT_ID), MSB(PRODUCT_ID), // idProduct
|
||||
LSB(DEVICE_VER), MSB(DEVICE_VER), // bcdDevice
|
||||
1, // iManufacturer
|
||||
2, // iProduct
|
||||
0, // iSerialNumber
|
||||
1 // bNumConfigurations
|
||||
};
|
||||
|
||||
// These descriptors must NOT be "const", because the USB DMA
|
||||
// has trouble accessing flash memory with enough bandwidth
|
||||
// while the processor is executing from flash.
|
||||
|
||||
|
||||
// **************************************************************
|
||||
// USB Configuration
|
||||
// **************************************************************
|
||||
|
||||
// USB Configuration Descriptor. This huge descriptor tells all
|
||||
// of the devices capbilities.
|
||||
static const uint8_t config_descriptor[CONFIG_DESC_SIZE] = {
|
||||
// configuration descriptor, USB spec 9.6.3, page 264-266, Table 9-10
|
||||
9, // bLength;
|
||||
2, // bDescriptorType;
|
||||
LSB(CONFIG_DESC_SIZE), // wTotalLength
|
||||
MSB(CONFIG_DESC_SIZE),
|
||||
NUM_INTERFACE, // bNumInterfaces
|
||||
1, // bConfigurationValue
|
||||
2, // iConfiguration
|
||||
0x80, // bmAttributes
|
||||
50, // bMaxPower
|
||||
|
||||
// interface descriptor, DFU Mode (DFU spec Table 4.4)
|
||||
9, // bLength
|
||||
4, // bDescriptorType
|
||||
DFU_INTERFACE, // bInterfaceNumber
|
||||
0, // bAlternateSetting
|
||||
0, // bNumEndpoints
|
||||
0xFE, // bInterfaceClass
|
||||
0x01, // bInterfaceSubClass
|
||||
0x02, // bInterfaceProtocol
|
||||
2, // iInterface
|
||||
|
||||
// DFU Functional Descriptor (DFU spec TAble 4.2)
|
||||
9, // bLength
|
||||
0x21, // bDescriptorType
|
||||
0x0D, // bmAttributes
|
||||
LSB(DFU_DETACH_TIMEOUT), // wDetachTimeOut
|
||||
MSB(DFU_DETACH_TIMEOUT),
|
||||
LSB(DFU_TRANSFER_SIZE), // wTransferSize
|
||||
MSB(DFU_TRANSFER_SIZE),
|
||||
0x01,0x01, // bcdDFUVersion
|
||||
};
|
||||
|
||||
|
||||
// **************************************************************
|
||||
// String Descriptors
|
||||
// **************************************************************
|
||||
|
||||
// The descriptors above can provide human readable strings,
|
||||
// referenced by index numbers. These descriptors are the
|
||||
// actual string data
|
||||
|
||||
static const struct usb_string_descriptor_struct string0 = {
|
||||
4,
|
||||
3,
|
||||
{0x0409}
|
||||
};
|
||||
|
||||
// Microsoft OS String Descriptor. See: https://github.com/pbatard/libwdi/wiki/WCID-Devices
|
||||
static const struct usb_string_descriptor_struct usb_string_microsoft = {
|
||||
18, 3,
|
||||
{'M','S','F','T','1','0','0', MSFT_VENDOR_CODE}
|
||||
};
|
||||
|
||||
// Microsoft WCID
|
||||
const uint8_t usb_microsoft_wcid[MSFT_WCID_LEN] = {
|
||||
MSFT_WCID_LEN, 0, 0, 0, // Length
|
||||
0x00, 0x01, // Version
|
||||
0x04, 0x00, // Compatibility ID descriptor index
|
||||
0x01, // Number of sections
|
||||
0, 0, 0, 0, 0, 0, 0, // Reserved (7 bytes)
|
||||
|
||||
0, // Interface number
|
||||
0x01, // Reserved
|
||||
'W','I','N','U','S','B',0,0, // Compatible ID
|
||||
0,0,0,0,0,0,0,0, // Sub-compatible ID (unused)
|
||||
0,0,0,0,0,0, // Reserved
|
||||
};
|
||||
|
||||
const struct webusb_url_descriptor landing_url_descriptor = {
|
||||
.bLength = LANDING_PAGE_DESCRIPTOR_SIZE,
|
||||
.bDescriptorType = WEBUSB_DT_URL,
|
||||
.bScheme = WEBUSB_URL_SCHEME_HTTPS,
|
||||
.URL = LANDING_PAGE_URL
|
||||
};
|
||||
|
||||
struct full_bos {
|
||||
struct usb_bos_descriptor bos;
|
||||
struct webusb_platform_descriptor webusb;
|
||||
};
|
||||
|
||||
static const struct full_bos full_bos = {
|
||||
.bos = {
|
||||
.bLength = USB_DT_BOS_SIZE,
|
||||
.bDescriptorType = USB_DT_BOS,
|
||||
.wTotalLength = USB_DT_BOS_SIZE + WEBUSB_PLATFORM_DESCRIPTOR_SIZE,
|
||||
.bNumDeviceCaps = 1,
|
||||
},
|
||||
.webusb = {
|
||||
.bLength = WEBUSB_PLATFORM_DESCRIPTOR_SIZE,
|
||||
.bDescriptorType = USB_DT_DEVICE_CAPABILITY,
|
||||
.bDevCapabilityType = USB_DC_PLATFORM,
|
||||
.bReserved = 0,
|
||||
.platformCapabilityUUID = WEBUSB_UUID,
|
||||
.bcdVersion = 0x0100,
|
||||
.bVendorCode = WEBUSB_VENDOR_CODE,
|
||||
.iLandingPage = 1,
|
||||
},
|
||||
};
|
||||
|
||||
__attribute__((aligned(4)))
|
||||
static const struct usb_string_descriptor_struct usb_string_manufacturer_name = {
|
||||
2 + MANUFACTURER_NAME_LEN,
|
||||
3,
|
||||
MANUFACTURER_NAME
|
||||
};
|
||||
|
||||
__attribute__((aligned(4)))
|
||||
struct usb_string_descriptor_struct usb_string_product_name = {
|
||||
2 + PRODUCT_NAME_LEN,
|
||||
3,
|
||||
PRODUCT_NAME
|
||||
};
|
||||
|
||||
// **************************************************************
|
||||
// Descriptors List
|
||||
// **************************************************************
|
||||
|
||||
// This table provides access to all the descriptor data above.
|
||||
|
||||
const usb_descriptor_list_t usb_descriptor_list[] = {
|
||||
{0x0100, sizeof(device_descriptor), device_descriptor},
|
||||
{0x0200, sizeof(config_descriptor), config_descriptor},
|
||||
{0x0300, 0, (const uint8_t *)&string0},
|
||||
{0x0301, 0, (const uint8_t *)&usb_string_manufacturer_name},
|
||||
{0x0302, 0, (const uint8_t *)&usb_string_product_name},
|
||||
{0x03EE, 0, (const uint8_t *)&usb_string_microsoft},
|
||||
{0x0F00, sizeof(full_bos), (const uint8_t *)&full_bos},
|
||||
{0, 0, NULL}
|
||||
};
|
245
sw/src/usb-dev.c
Normal file
245
sw/src/usb-dev.c
Normal file
@ -0,0 +1,245 @@
|
||||
#include <stdint.h>
|
||||
#include <unistd.h>
|
||||
#include <usb.h>
|
||||
#include <dfu.h>
|
||||
|
||||
#include <printf.h>
|
||||
|
||||
#include <usb-desc.h>
|
||||
|
||||
static uint8_t reply_buffer[8];
|
||||
static uint8_t usb_configuration = 0;
|
||||
#define USB_MAX_PACKET_SIZE 64 /* For FS device */
|
||||
static uint8_t rx_buffer[USB_MAX_PACKET_SIZE];
|
||||
|
||||
void usb_setup(struct usb_device *dev, const struct usb_setup_request *setup)
|
||||
{
|
||||
const uint8_t *data = NULL;
|
||||
uint32_t datalen = 0;
|
||||
const usb_descriptor_list_t *list;
|
||||
|
||||
// printf("%s:%d SETUP packet (%04x) value: %02x index: %02x\n", __FILE__, __LINE__, setup->wRequestAndType, setup->wIndex, setup->wValue);
|
||||
|
||||
switch (setup->wRequestAndType)
|
||||
{
|
||||
case 0x0500: // SET_ADDRESS
|
||||
// TODO: Handle set_daddr
|
||||
// efm32hg_set_daddr(setup->wValue);
|
||||
break;
|
||||
case 0x0900: // SET_CONFIGURATION
|
||||
usb_configuration = setup->wValue;
|
||||
break;
|
||||
case 0x0880: // GET_CONFIGURATION
|
||||
reply_buffer[0] = usb_configuration;
|
||||
datalen = 1;
|
||||
data = reply_buffer;
|
||||
break;
|
||||
case 0x0080: // GET_STATUS (device)
|
||||
reply_buffer[0] = 0;
|
||||
reply_buffer[1] = 0;
|
||||
datalen = 2;
|
||||
data = reply_buffer;
|
||||
break;
|
||||
case 0x0082: // GET_STATUS (endpoint)
|
||||
if (setup->wIndex > 0)
|
||||
{
|
||||
printf("get_status (setup->wIndex: %d)\n", setup->wIndex);
|
||||
usb_err(dev, 0);
|
||||
return;
|
||||
}
|
||||
reply_buffer[0] = 0;
|
||||
reply_buffer[1] = 0;
|
||||
|
||||
// XXX handle endpoint stall here
|
||||
// if (USB->DIEP0CTL & USB_DIEP_CTL_STALL)
|
||||
// reply_buffer[0] = 1;
|
||||
data = reply_buffer;
|
||||
datalen = 2;
|
||||
break;
|
||||
case 0x0102: // CLEAR_FEATURE (endpoint)
|
||||
if (setup->wIndex > 0 || setup->wValue != 0)
|
||||
{
|
||||
// TODO: do we need to handle IN vs OUT here?
|
||||
printf("%s:%d clear feature (%d / %d)\n", __FILE__, __LINE__, setup->wIndex, setup->wValue);
|
||||
usb_err(dev, 0);
|
||||
return;
|
||||
}
|
||||
// XXX: Should we clear the stall bit?
|
||||
// USB->DIEP0CTL &= ~USB_DIEP_CTL_STALL;
|
||||
// TODO: do we need to clear the data toggle here?
|
||||
break;
|
||||
case 0x0302: // SET_FEATURE (endpoint)
|
||||
if (setup->wIndex > 0 || setup->wValue != 0)
|
||||
{
|
||||
// TODO: do we need to handle IN vs OUT here?
|
||||
printf("%s:%d clear feature (%d / %d)\n", __FILE__, __LINE__, setup->wIndex, setup->wValue);
|
||||
usb_err(dev, 0);
|
||||
return;
|
||||
}
|
||||
// XXX: Should we set the stall bit?
|
||||
// USB->DIEP0CTL |= USB_DIEP_CTL_STALL;
|
||||
// TODO: do we need to clear the data toggle here?
|
||||
break;
|
||||
case 0x0680: // GET_DESCRIPTOR
|
||||
case 0x0681:
|
||||
for (list = usb_descriptor_list; 1; list++)
|
||||
{
|
||||
if (list->addr == NULL)
|
||||
break;
|
||||
if (setup->wValue == list->wValue)
|
||||
{
|
||||
data = list->addr;
|
||||
if ((setup->wValue >> 8) == 3)
|
||||
{
|
||||
// for string descriptors, use the descriptor's
|
||||
// length field, allowing runtime configured
|
||||
// length.
|
||||
datalen = *(list->addr);
|
||||
}
|
||||
else
|
||||
{
|
||||
datalen = list->length;
|
||||
}
|
||||
goto send;
|
||||
}
|
||||
}
|
||||
printf("%s:%d couldn't find descriptor (%d / %d)\n", __FILE__, __LINE__, setup->wIndex, setup->wValue);
|
||||
usb_err(dev, 0);
|
||||
return;
|
||||
|
||||
case (MSFT_VENDOR_CODE << 8) | 0xC0: // Get Microsoft descriptor
|
||||
case (MSFT_VENDOR_CODE << 8) | 0xC1:
|
||||
if (setup->wIndex == 0x0004)
|
||||
{
|
||||
// Return WCID descriptor
|
||||
data = usb_microsoft_wcid;
|
||||
datalen = MSFT_WCID_LEN;
|
||||
break;
|
||||
}
|
||||
printf("%s:%d couldn't find microsoft descriptor (%d / %d)\n", __FILE__, __LINE__, setup->wIndex, setup->wValue);
|
||||
usb_err(dev, 0);
|
||||
return;
|
||||
|
||||
case (WEBUSB_VENDOR_CODE << 8) | 0xC0: // Get WebUSB descriptor
|
||||
if (setup->wIndex == 0x0002)
|
||||
{
|
||||
if (setup->wValue == 0x0001)
|
||||
{
|
||||
// Return landing page URL descriptor
|
||||
data = (uint8_t*)&landing_url_descriptor;
|
||||
datalen = LANDING_PAGE_DESCRIPTOR_SIZE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
printf("%s:%d couldn't find webusb descriptor (%d / %d)\n", __FILE__, __LINE__, setup->wIndex, setup->wValue);
|
||||
usb_err(dev, 0);
|
||||
return;
|
||||
|
||||
case 0x0121: // DFU_DNLOAD
|
||||
if (setup->wIndex > 0)
|
||||
{
|
||||
printf("%s:%d dfu download descriptor index invalid (%d / %d)\n", __FILE__, __LINE__, setup->wIndex, setup->wValue);
|
||||
usb_err(dev, 0);
|
||||
return;
|
||||
}
|
||||
// Data comes in the OUT phase. But if it's a zero-length request, handle it now.
|
||||
if (setup->wLength == 0)
|
||||
{
|
||||
if (!dfu_download(setup->wValue, 0, 0, 0, NULL))
|
||||
{
|
||||
usb_err(dev, 0);
|
||||
return;
|
||||
}
|
||||
usb_ack(dev, 0);
|
||||
return;
|
||||
}
|
||||
unsigned int len = setup->wLength;
|
||||
if (len > sizeof(rx_buffer))
|
||||
len = sizeof(rx_buffer);
|
||||
usb_recv(dev, rx_buffer, len);
|
||||
return;
|
||||
|
||||
case 0x03a1: // DFU_GETSTATUS
|
||||
if (setup->wIndex > 0)
|
||||
{
|
||||
printf("%s:%d err (%d / %d)\n", __FILE__, __LINE__, setup->wIndex, setup->wValue);
|
||||
usb_err(dev, 0);
|
||||
return;
|
||||
}
|
||||
if (dfu_getstatus(reply_buffer))
|
||||
{
|
||||
data = reply_buffer;
|
||||
datalen = 6;
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("%s:%d err (%d / %d)\n", __FILE__, __LINE__, setup->wIndex, setup->wValue);
|
||||
usb_err(dev, 0);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x0421: // DFU_CLRSTATUS
|
||||
if (setup->wIndex > 0)
|
||||
{
|
||||
printf("%s:%d err (%d / %d)\n", __FILE__, __LINE__, setup->wIndex, setup->wValue);
|
||||
usb_err(dev, 0);
|
||||
return;
|
||||
}
|
||||
if (dfu_clrstatus())
|
||||
{
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("%s:%d err (%d / %d)\n", __FILE__, __LINE__, setup->wIndex, setup->wValue);
|
||||
usb_err(dev, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
case 0x05a1: // DFU_GETSTATE
|
||||
if (setup->wIndex > 0)
|
||||
{
|
||||
printf("%s:%d err (%d / %d)\n", __FILE__, __LINE__, setup->wIndex, setup->wValue);
|
||||
usb_err(dev, 0);
|
||||
return;
|
||||
}
|
||||
reply_buffer[0] = dfu_getstate();
|
||||
data = reply_buffer;
|
||||
datalen = 1;
|
||||
break;
|
||||
|
||||
case 0x0621: // DFU_ABORT
|
||||
if (setup->wIndex > 0)
|
||||
{
|
||||
printf("%s:%d err (%d / %d)\n", __FILE__, __LINE__, setup->wIndex, setup->wValue);
|
||||
usb_err(dev, 0);
|
||||
return;
|
||||
}
|
||||
if (dfu_abort())
|
||||
{
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("%s:%d err (%d / %d)\n", __FILE__, __LINE__, setup->wIndex, setup->wValue);
|
||||
usb_err(dev, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
default:
|
||||
printf("%s:%d unrecognized request type (%04x) value: %02x index: %02x\n", __FILE__, __LINE__, setup->wRequestAndType, setup->wIndex, setup->wValue);
|
||||
usb_err(dev, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
send:
|
||||
if (data && datalen) {
|
||||
printf("%s:%d sending %d bytes from %08x\n", __FILE__, __LINE__, datalen, data);
|
||||
usb_send(dev, 0, data, datalen);
|
||||
}
|
||||
else
|
||||
usb_ack(dev, 0);
|
||||
return;
|
||||
}
|
Reference in New Issue
Block a user