Skip to content

Commit

Permalink
Move bootloader reset signal detection to Wirish.
Browse files Browse the repository at this point in the history
Use usb_cdcacm hooks to move the DTR edge and "1EAF" magic packet
detection to usb_serial.cpp. We'll later be able to extend this system
to support Leonardo-style reset signalling.

Signed-off-by: Marti Bolivar <mbolivar@leaflabs.com>
  • Loading branch information
Marti Bolivar committed Jul 31, 2012
1 parent ef1cc46 commit 365f456
Show file tree
Hide file tree
Showing 3 changed files with 140 additions and 127 deletions.
1 change: 0 additions & 1 deletion libmaple/include/libmaple/libmaple.h
Expand Up @@ -48,7 +48,6 @@ extern "C" {
*/
#define USER_ADDR_ROM 0x08005000
#define USER_ADDR_RAM 0x20000C00
#define STACK_TOP 0x20000800

#ifdef __cplusplus
}
Expand Down
150 changes: 24 additions & 126 deletions libmaple/usb/stm32f1/usb_cdcacm.c
Expand Up @@ -82,8 +82,6 @@ static uint8* usbGetStringDescriptor(uint16 length);
static void usbSetConfiguration(void);
static void usbSetDeviceAddress(void);

static void wait_reset(void);

/*
* Descriptors
*/
Expand Down Expand Up @@ -263,13 +261,6 @@ ONE_DESCRIPTOR String_Descriptor[3] = {
* Etc.
*/

typedef enum {
DTR_UNSET,
DTR_HIGH,
DTR_NEGEDGE,
DTR_LOW
} RESET_STATE;

typedef struct {
uint32 bitrate;
uint8 format;
Expand All @@ -284,7 +275,6 @@ USB_Line_Coding line_coding = {
.paritytype = 0x00,
.datatype = 0x08
};
RESET_STATE reset_state = DTR_UNSET;

static volatile uint8 vcomBufferRx[USB_CDCACM_RX_BUFLEN];
static volatile uint32 rx_offset = 0;
Expand Down Expand Up @@ -492,64 +482,13 @@ static void vcomDataTxCb(void) {
countTx = 0;
}

#define EXC_RETURN 0xFFFFFFF9
#define DEFAULT_CPSR 0x61000000
static void vcomDataRxCb(void) {
/* FIXME this is mad buggy */

/* This following is safe since sizeof(vcomBufferRx) exceeds the
* largest possible USB_CDCACM_RX_EPSIZE, and we set to NAK after
* each data packet. Only when all bytes have been read is the RX
* endpoint set back to VALID. */
newBytes = usb_get_ep_rx_count(USB_CDCACM_RX_ENDP);
usb_set_ep_rx_stat(USB_CDCACM_RX_ENDP, USB_EP_STAT_RX_NAK);

/* magic number, {0x31, 0x45, 0x41, 0x46} is "1EAF" */
uint8 chkBuf[4];
uint8 cmpBuf[4] = {0x31, 0x45, 0x41, 0x46};
if (reset_state == DTR_NEGEDGE) {
reset_state = DTR_LOW;

if (newBytes >= 4) {
unsigned int target = (unsigned int)wait_reset | 0x1;

usb_copy_from_pma(chkBuf, 4, USB_CDCACM_RX_ADDR);

int i;
USB_Bool cmpMatch = TRUE;
for (i = 0; i < 4; i++) {
if (chkBuf[i] != cmpBuf[i]) {
cmpMatch = FALSE;
}
}

if (cmpMatch) {
asm volatile("mov r0, %[stack_top] \n\t" // Reset stack
"mov sp, r0 \n\t"
"mov r0, #1 \n\t"
"mov r1, %[target_addr] \n\t"
"mov r2, %[cpsr] \n\t"
"push {r2} \n\t" // Fake xPSR
"push {r1} \n\t" // PC target addr
"push {r0} \n\t" // Fake LR
"push {r0} \n\t" // Fake R12
"push {r0} \n\t" // Fake R3
"push {r0} \n\t" // Fake R2
"push {r0} \n\t" // Fake R1
"push {r0} \n\t" // Fake R0
"mov lr, %[exc_return] \n\t"
"bx lr"
:
: [stack_top] "r" (STACK_TOP),
[target_addr] "r" (target),
[exc_return] "r" (EXC_RETURN),
[cpsr] "r" (DEFAULT_CPSR)
: "r0", "r1", "r2");
/* should never get here */
}
}
}

usb_copy_from_pma((uint8*)vcomBufferRx, newBytes, USB_CDCACM_RX_ADDR);

if (rx_hook) {
Expand Down Expand Up @@ -634,28 +573,27 @@ static void usbReset(void) {
}

static RESULT usbDataSetup(uint8 request) {
uint8 *(*CopyRoutine)(uint16);
CopyRoutine = NULL;
uint8* (*CopyRoutine)(uint16) = 0;

if (Type_Recipient == (CLASS_REQUEST | INTERFACE_RECIPIENT)) {
/* Call the user hook first. */
if (iface_setup_hook) {
uint8 req_copy = request;
iface_setup_hook(USB_CDCACM_HOOK_IFACE_SETUP, &req_copy);
}

switch (request) {
case (USB_CDCACM_GET_LINE_CODING):
case USB_CDCACM_GET_LINE_CODING:
CopyRoutine = vcomGetSetLineCoding;
last_request = USB_CDCACM_GET_LINE_CODING;
break;
case (USB_CDCACM_SET_LINE_CODING):
case USB_CDCACM_SET_LINE_CODING:
CopyRoutine = vcomGetSetLineCoding;
last_request = USB_CDCACM_SET_LINE_CODING;
break;
default:
break;
}

/* Call the user hook. */
if (iface_setup_hook) {
uint8 req_copy = request;
iface_setup_hook(USB_CDCACM_HOOK_IFACE_SETUP, &req_copy);
}
}

if (CopyRoutine == NULL) {
Expand All @@ -669,66 +607,32 @@ static RESULT usbDataSetup(uint8 request) {
}

static RESULT usbNoDataSetup(uint8 request) {
RESULT ret = USB_UNSUPPORT;
uint8 new_signal;

/* we support set com feature but dont handle it */
if (Type_Recipient == (CLASS_REQUEST | INTERFACE_RECIPIENT)) {
/* Call the user hook first. */
if (iface_setup_hook) {
uint8 req_copy = request;
iface_setup_hook(USB_CDCACM_HOOK_IFACE_SETUP, &req_copy);
}

switch (request) {
case (USB_CDCACM_SET_COMM_FEATURE):
return USB_SUCCESS;
case (USB_CDCACM_SET_CONTROL_LINE_STATE):
/* to reset the board, pull both dtr and rts low
then pulse dtr by itself */
case USB_CDCACM_SET_COMM_FEATURE:
/* We support set comm. feature, but don't handle it. */
ret = USB_SUCCESS;
break;
case USB_CDCACM_SET_CONTROL_LINE_STATE:
/* Track changes to DTR and RTS. */
new_signal = (pInformation->USBwValues.bw.bb0 &
(USB_CDCACM_CONTROL_LINE_DTR |
USB_CDCACM_CONTROL_LINE_RTS));
line_dtr_rts = new_signal & 0x03;
ret = USB_SUCCESS;
break;
}

switch (reset_state) {
/* no default, covered enum */
case DTR_UNSET:
if ((new_signal & USB_CDCACM_CONTROL_LINE_DTR) == 0 ) {
reset_state = DTR_LOW;
} else {
reset_state = DTR_HIGH;
}
break;

case DTR_HIGH:
if ((new_signal & USB_CDCACM_CONTROL_LINE_DTR) == 0 ) {
reset_state = DTR_NEGEDGE;
} else {
reset_state = DTR_HIGH;
}
break;

case DTR_NEGEDGE:
if ((new_signal & USB_CDCACM_CONTROL_LINE_DTR) == 0 ) {
reset_state = DTR_LOW;
} else {
reset_state = DTR_HIGH;
}
break;

case DTR_LOW:
if ((new_signal & USB_CDCACM_CONTROL_LINE_DTR) == 0 ) {
reset_state = DTR_LOW;
} else {
reset_state = DTR_HIGH;
}
break;
}

return USB_SUCCESS;
/* Call the user hook. */
if (iface_setup_hook) {
uint8 req_copy = request;
iface_setup_hook(USB_CDCACM_HOOK_IFACE_SETUP, &req_copy);
}
}
return USB_UNSUPPORT;
return ret;
}

static RESULT usbGetInterfaceSetting(uint8 interface, uint8 alt_setting) {
Expand Down Expand Up @@ -767,9 +671,3 @@ static void usbSetConfiguration(void) {
static void usbSetDeviceAddress(void) {
USBLIB->state = USB_ADDRESSED;
}

#define RESET_DELAY 100000
static void wait_reset(void) {
delay_us(RESET_DELAY);
nvic_sys_reset();
}
116 changes: 116 additions & 0 deletions wirish/usb_serial.cpp
Expand Up @@ -32,11 +32,23 @@

#include <string.h>

#include <libmaple/nvic.h>
#include <libmaple/usb_cdcacm.h>
#include <libmaple/usb.h>

#include <wirish/wirish.h>

/*
* Hooks used for bootloader reset signalling
*/

static void rxHook(unsigned, void*);
static void ifaceSetupHook(unsigned, void*);

/*
* USBSerial interface
*/

#define USB_TIMEOUT 50

USBSerial::USBSerial(void) {
Expand All @@ -48,12 +60,15 @@ USBSerial::USBSerial(void) {
void USBSerial::begin(void) {
#if BOARD_HAVE_SERIALUSB
usb_cdcacm_enable(BOARD_USB_DISC_DEV, BOARD_USB_DISC_BIT);
usb_cdcacm_set_hooks(USB_CDCACM_HOOK_RX, rxHook);
usb_cdcacm_set_hooks(USB_CDCACM_HOOK_IFACE_SETUP, ifaceSetupHook);
#endif
}

void USBSerial::end(void) {
#if BOARD_HAVE_SERIALUSB
usb_cdcacm_disable(BOARD_USB_DISC_DEV, BOARD_USB_DISC_BIT);
usb_cdcacm_remove_hooks(USB_CDCACM_HOOK_RX | USB_CDCACM_HOOK_IFACE_SETUP);
#endif
}

Expand Down Expand Up @@ -126,3 +141,104 @@ uint8 USBSerial::getRTS(void) {
#if BOARD_HAVE_SERIALUSB
USBSerial SerialUSB;
#endif

/*
* Bootloader hook implementations
*/

enum reset_state_t {
DTR_UNSET,
DTR_HIGH,
DTR_NEGEDGE,
DTR_LOW
};

static reset_state_t reset_state = DTR_UNSET;

static void ifaceSetupHook(unsigned hook, void *requestvp) {
uint8 request = *(uint8*)requestvp;

// Ignore requests we're not interested in.
if (request != USB_CDCACM_SET_CONTROL_LINE_STATE) {
return;
}

// We need to see a negative edge on DTR before we start looking
// for the in-band magic reset byte sequence.
uint8 dtr = usb_cdcacm_get_dtr();
switch (reset_state) {
case DTR_UNSET:
reset_state = dtr ? DTR_HIGH : DTR_LOW;
break;
case DTR_HIGH:
reset_state = dtr ? DTR_HIGH : DTR_NEGEDGE;
break;
case DTR_NEGEDGE:
reset_state = dtr ? DTR_HIGH : DTR_LOW;
break;
case DTR_LOW:
reset_state = dtr ? DTR_HIGH : DTR_LOW;
break;
}
}

#define RESET_DELAY 100000
static void wait_reset(void) {
delay_us(RESET_DELAY);
nvic_sys_reset();
}

#define STACK_TOP 0x20000800
#define EXC_RETURN 0xFFFFFFF9
#define DEFAULT_CPSR 0x61000000
static void rxHook(unsigned hook, void *ignored) {
/* FIXME this is mad buggy; we need a new reset sequence. E.g. NAK
* after each RX means you can't reset if any bytes are waiting. */
if (reset_state == DTR_NEGEDGE) {
reset_state = DTR_LOW;

if (usb_cdcacm_data_available() >= 4) {
// The magic reset sequence is "1EAF".
uint8 cmpBuf[4] = {'1', 'E', 'A', 'F'};
uint8 chkBuf[4];

// Peek at the waiting bytes, looking for reset sequence.
usb_cdcacm_peek(chkBuf, 4);
bool cmpMatch = true;
for (int i = 0; i < 4; i++) {
if (chkBuf[i] != cmpBuf[i]) {
cmpMatch = false;
break;
}
}

// Got the magic sequence? Reset, presumably into the bootloader.
if (cmpMatch) {
// Return address is wait_reset, but we must set the thumb bit.
unsigned int target = (unsigned int)wait_reset | 0x1;
asm volatile("mov r0, %[stack_top] \n\t" // Reset stack
"mov sp, r0 \n\t"
"mov r0, #1 \n\t"
"mov r1, %[target_addr] \n\t"
"mov r2, %[cpsr] \n\t"
"push {r2} \n\t" // Fake xPSR
"push {r1} \n\t" // PC target addr
"push {r0} \n\t" // Fake LR
"push {r0} \n\t" // Fake R12
"push {r0} \n\t" // Fake R3
"push {r0} \n\t" // Fake R2
"push {r0} \n\t" // Fake R1
"push {r0} \n\t" // Fake R0
"mov lr, %[exc_return] \n\t"
"bx lr"
:
: [stack_top] "r" (STACK_TOP),
[target_addr] "r" (target),
[exc_return] "r" (EXC_RETURN),
[cpsr] "r" (DEFAULT_CPSR)
: "r0", "r1", "r2");
/* should never get here */
}
}
}
}

0 comments on commit 365f456

Please sign in to comment.