From e23181e40bdc907110117930e06269f8fe0b3afa Mon Sep 17 00:00:00 2001 From: Werner Almesberger Date: Fri, 13 Aug 2010 08:47:40 -0300 Subject: [PATCH] fw/boot/ - The boot loader, fresh from IDBG. Needs major cleanup. --- fw/boot/Makefile | 26 ++++ fw/boot/README | 1 + fw/boot/boot.c | 243 ++++++++++++++++++++++++++++++++++ fw/boot/config.h | 21 +++ fw/boot/dfu.c | 331 ++++++++++++++++++++++++++++++++++++++++++++++ fw/boot/dfu.h | 86 ++++++++++++ fw/boot/version.h | 26 ++++ 7 files changed, 734 insertions(+) create mode 100644 fw/boot/Makefile create mode 100644 fw/boot/README create mode 100644 fw/boot/boot.c create mode 100644 fw/boot/config.h create mode 100644 fw/boot/dfu.c create mode 100644 fw/boot/dfu.h create mode 100644 fw/boot/version.h diff --git a/fw/boot/Makefile b/fw/boot/Makefile new file mode 100644 index 0000000..59c5894 --- /dev/null +++ b/fw/boot/Makefile @@ -0,0 +1,26 @@ +# +# boot/Makefile - Makefile for DFU-capable boot loader +# +# Written 2008, 2010 by Werner Almesberger +# Copyright 2008, 2010 Werner Almesberger +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# + +MAIN=boot +OBJS=$(MAIN) uart usb dfu version + +include ../common/Makefile.system +include ../common/Makefile.common + +CFLAGS += -I../include # -DLOW_SPEED +LDFLAGS += --code-size $(PAYLOAD_START) + +uart.rel: ../common/uart.c + $(CC) $(CFLAGS) $(DEFINE_UART_SPEED) -o $@ -c $< + +usb.rel: ../common/usb.c + $(CC) $(CFLAGS) -o $@ -c $< diff --git a/fw/boot/README b/fw/boot/README new file mode 100644 index 0000000..2b9ea4e --- /dev/null +++ b/fw/boot/README @@ -0,0 +1 @@ +Prerequisites: sdcc diff --git a/fw/boot/boot.c b/fw/boot/boot.c new file mode 100644 index 0000000..f161b59 --- /dev/null +++ b/fw/boot/boot.c @@ -0,0 +1,243 @@ +/* + * boot/boot.c - Boot loader setup and main loop + * + * Written 2008, 2010 by Werner Almesberger + * Copyright 2008, 2010 Werner Almesberger + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +/* + * This code follows the register read/write sequences from the examples in + * SiLabs/MCU/Examples/C8051F326_7/USB_Interrupt/Firmware/F326_USB_Main.c and + * SiLabs/MCU/Examples/C8051F326_7/USB_Interrupt/Firmware/F326_USB_ISR.c + */ + + +#include + +#include "version.h" +#include "regs.h" +#include "io.h" +#include "uart.h" +#include "usb.h" +#include "dfu.h" + + +void run_payload(void) +{ + /* No interrupts while jumping between worlds */ + EA = 0; + + /* Restart USB */ + USB0XCN = 0; + + /* Re-enable pull-ups */ + GPIOCN &= ~WEAKPUD; + +#ifdef GTA + /* Don't waste power in pull-down */ + I2C_SDA_PULL = 1; +#endif + + debug("launching payload\n"); + + __asm + ljmp PAYLOAD_START + __endasm; +} + + +/* ----- Interrupts -------------------------------------------------------- */ + + +/* + * The boot loader doesn't use interrupts, so we forward all interrupts to the + * payload. + * + * What we'd really like to do here is to say something like + * + * void whatever_isr(void) __interrupt(n) __at(PAYLOAD+n*8+1); + * + * However, sdcc doesn't support such things yet. So we declare the ISR such + * that the vector entry gets created, and then we tell the assembler where to + * find it. + * + * Since __asm/__endasm isn't allowed outside a function body, we generate a + * dummy function for each assignment. The function is "naked", so that no + * actual code is generated for it. + */ + +#define ISR(n) \ + void isr_nr_##n(void) __interrupt(n); \ + void isr_dummy_##n(void) __naked \ + { \ + __asm \ + _isr_nr_##n = PAYLOAD_START+n*8+3 \ + __endasm; \ + } + + +ISR(0) +ISR(1) +ISR(2) +ISR(3) +ISR(4) +ISR(8) +ISR(15) + + +/* ----- The actual boot loader -------------------------------------------- */ + + +static void delay(void) +{ + int x; + + for (x = 0; x < 500; x) + x++; +} + + +static void boot_loader(void) +{ + /* + * If we have VBUS, proceed as follows: + * - bring up USB + * - try to contact the PMU (in a loop) + * - possible transitions: + * - DFU gets selected -> enter DFU mode + * - PMU responds -> jump to payload + * + * In DFU mode, the following transitions are possible: + * - VBUS drops -> reset + * - USB bus reset -> reset + * + * @@@ this may be too complex - probably don't really need to talk to + * the PMU. + */ + + /* + * Note: if we do anything that delays CPU bringup after nRESET goes + * high, we must still stay in the 100ms budget for raising KEEPACT. + */ + + OSCICN |= IFCN0 | IFCN1; + +#ifdef LOW_SPEED + + CLKSEL = 0x10; /* USBCLK = int/2, SYS_INT_OSC = int */ + +#else /* LOW_SPEED */ + + /* + * Clock multiplier enable sequence, section 10.4 + * + * - reset the multiplier + * - select the multiplier input source + * - enable the multiplier + * - delay for 5us + * - initialize the multiplier + * - poll for multiplier to be ready + */ + + CLKMUL = 0; + CLKMUL |= MULEN; + delay(); + CLKMUL |= MULINIT | MULEN; + while (!(CLKMUL & MULRDY)); + CLKSEL = 0; /* USBCLK = 4*int, SYSCLK = int */ + CLKSEL = 0x02; /* F326_USB_Main.c does this (sets 24MHz). Why ? */ + + uart_init(24); + +#endif /* !LOW_SPEED */ + + printk("%s #%u\n", build_date, build_number); + + /* + * Very weakly pull SDA down and disable all pull-ups. + * + * We use SDA as a system power presence detector. When I2C is idle, + * SDA is kept high. This is accomplished with pull-ups in the system. + * We can therefore detect if IO_3V3 is any good by checking whether + * SDA is high. + * + * This should work even without our own pull-down. However, when the + * IDBG board is operating standalone (or, generally, if SDA isn't + * connected), SDA would float. We therefore have to pull it down a + * little. + */ + + /* + * Ben variant: + * + * We use exactly the same logic as on GTA01/02, but with different + * signals. P0_1 (I2C_SDA_PULL) and P0_2 (I2C_SDA) both connect to + * +V3.3, with a 1 kOhm resistor on P0_1. If the Ben is not powered, we + * can therefore pull +V3.3 to GND, and detect this condition. As on + * the GTA01/02, once the system is powered up, IDBG exits this loop. + */ + + GPIOCN |= WEAKPUD; +#ifdef GTA + I2C_SDA_PULL = 0; +#endif + delay(); + + dfu_init(); + usb_init(); + +#ifdef GTA + + while (!I2C_SDA || dfu.state != dfuIDLE) + usb_poll(); + +#else /* GTA */ + +#define MS_TO_LOOPS(ms) ((uint32_t) (ms)*190) + + { + uint32_t loop = 0; + + while (loop != MS_TO_LOOPS(2000)) { + usb_poll(); + if (dfu.state == dfuIDLE) + loop++; + else + loop = 0; + } + } +#endif /* !GTA */ +} + + +void main(void) +{ + /* + * VDD monitor enable sequence, section 7.2 + * + * - enable voltage monitor + * - wait for monitor to stabilize + * - enable VDD monitor reset + */ + + VDM0CN = VDMEN; + while (!(VDM0CN & VDDSTAT)); + RSTSRC = PORSF; + + /* + * @@@ if we don't have VBUS, proceed as follows: + * - stay at 3MHz (current < 2mA, so we're fine with GPIO power) + * - jump directly to the payload + */ + + OSCICN |= IFCN0; + uart_init(3); + if (REG0CN & VBSTAT) + boot_loader(); + run_payload(); +} diff --git a/fw/boot/config.h b/fw/boot/config.h new file mode 100644 index 0000000..2b6502f --- /dev/null +++ b/fw/boot/config.h @@ -0,0 +1,21 @@ +/* + * boot/config.h - Boot loader configuration + * + * Written 2008 by Werner Almesberger + * Copyright 2008 Werner Almesberger + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + + +#ifndef CONFIG_H +#define CONFIG_H + +//#define CONFIG_DEBUG +//#define CONFIG_ERROR +//#define CONFIG_PRINTK + +#endif /* !CONFIG_H */ diff --git a/fw/boot/dfu.c b/fw/boot/dfu.c new file mode 100644 index 0000000..de9ef56 --- /dev/null +++ b/fw/boot/dfu.c @@ -0,0 +1,331 @@ +/* + * boot/dfu.c - DFU protocol engine + * + * Written 2008-2010 by Werner Almesberger + * Copyright 2008-2010 Werner Almesberger + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +/* + * http://www.usb.org/developers/devclass_docs/DFU_1.1.pdf + */ + +/* + * A few, erm, shortcuts: + * + * - we don't bother with the app* states since DFU is all this firmware does + * - after DFU_DNLOAD, we just block until things are written, so we never + * enter dfuDNLOAD_SYNC or dfuDNBUSY + * - no dfuMANIFEST_SYNC, dfuMANIFEST, or dfuMANIFEST_WAIT_RESET + * - to keep our buffers small, we only accept EP0-sized blocks + */ + + +#include + +#include "regs.h" +#include "uart.h" +#include "usb.h" +#include "dfu.h" +#include "idbg/usb-ids.h" + + +#ifndef NULL +#define NULL 0 +#endif + + +#define PAYLOAD_END (PAYLOAD_START+PAYLOAD_SIZE) + + +const uint8_t device_descriptor[] = { + 18, /* bLength */ + USB_DT_DEVICE, /* bDescriptorType */ + LE(0x100), /* bcdUSB */ + USB_CLASS_PER_INTERFACE,/* bDeviceClass */ + 0x00, /* bDeviceSubClass (per interface) */ + 0x00, /* bDeviceProtocol (per interface) */ + EP0_SIZE, /* bMaxPacketSize */ + LE(USB_VENDOR), /* idVendor */ + LE(USB_PRODUCT_IDBG_DFU),/* idProduct */ + LE(0x0001), /* bcdDevice */ + 0, /* iManufacturer */ + 0, /* iProduct */ + 0, /* iSerialNumber */ + 1 /* bNumConfigurations */ +}; + + +const uint8_t config_descriptor[] = { + 9, /* bLength */ + USB_DT_CONFIG, /* bDescriptorType */ + LE(9+9), /* wTotalLength */ + 1, /* bNumInterfaces */ + 1, /* bConfigurationValue (> 0 !) */ + 0, /* iConfiguration */ +// USB_ATTR_SELF_POWERED | USB_ATTR_BUS_POWERED, + USB_ATTR_BUS_POWERED, /* bmAttributes */ + 15, /* bMaxPower */ + + /* Interface #0 */ + + 9, /* bLength */ + USB_DT_INTERFACE, /* bDescriptorType */ + 0, /* bInterfaceNumber */ + 0, /* bAlternateSetting */ + 0, /* bNumEndpoints */ + 0xfe, /* bInterfaceClass (application specific) */ + 0x01, /* bInterfaceSubClass (device fw upgrade) */ + 0x02, /* bInterfaceProtocol (DFU mode protocol) */ + 0, /* iInterface */ +}; + + +static const uint8_t functional_descriptor[] = { + 9, /* bLength */ + DFU_DT_FUNCTIONAL, /* bDescriptorType */ + 0xf, /* bmAttributes (claim omnipotence :-) */ + LE(0xffff), /* wDetachTimeOut (we're very patient) */ + LE(EP0_SIZE), /* wTransferSize */ + LE(0x101), /* bcdDFUVersion */ +}; + + +struct dfu dfu = { + OK, + LE(1000), 0, + dfuIDLE, + 0, +}; + + +static uint16_t next_block = 0; +static uint16_t payload; +static __bit did_download; + + +static __xdata uint8_t buf[EP0_SIZE]; + + +static void flash_erase_page(uint16_t addr) +{ + FLKEY = 0xa5; + FLKEY = 0xf1; + PSCTL |= PSEE; + PSCTL |= PSWE; + *(__xdata uint8_t *) addr = 0; + PSCTL &= ~PSWE; + PSCTL &= ~PSEE; +} + + +static void flash_write_byte(uint16_t addr, uint8_t value) +{ + FLKEY = 0xa5; + FLKEY = 0xf1; + PSCTL |= PSWE; + PSCTL &= ~PSEE; + *(__xdata uint8_t *) addr = value; + PSCTL &= ~PSWE; +} + + +static void block_write(void *user) +{ + uint16_t *size = user; + uint8_t *p; + + for (p = buf; p != buf+*size; p++) { + if (!(payload & 511)) + flash_erase_page(payload); + flash_write_byte(payload, *p); + payload++; + } +} + + +static __bit block_receive(uint16_t length) +{ + static uint16_t size; + + if (payload < PAYLOAD_START || payload+length > PAYLOAD_END) { + dfu.state = dfuERROR; + dfu.status = errADDRESS; + return 0; + } + if (length > EP0_SIZE) { + dfu.state = dfuERROR; + dfu.status = errUNKNOWN; + return 0; + } + size = length; + usb_recv(&ep0, buf, size, block_write, &size); + return 1; +} + + +static __bit block_transmit(uint16_t length) +{ + uint16_t left; + + if (payload < PAYLOAD_START || payload > PAYLOAD_END) { + dfu.state = dfuERROR; + dfu.status = errADDRESS; + return 1; + } + if (length > EP0_SIZE) { + dfu.state = dfuERROR; + dfu.status = errUNKNOWN; + return 1; + } + left = PAYLOAD_END-payload; + if (left < length) { + length = left; + dfu.state = dfuIDLE; + } + usb_send(&ep0, (__code uint8_t *) payload, length, NULL, NULL); + payload += length; + return 1; +} + + +static __bit my_setup(struct setup_request *setup) __reentrant +{ + __bit ok; + + switch (setup->bmRequestType | setup->bRequest << 8) { + case DFU_TO_DEV(DFU_DETACH): + debug("DFU_DETACH\n"); + /* + * The DFU spec says thay this is sent in protocol 1 only. + * However, dfu-util also sends it to get out of DFU mode, + * so we just don't make a fuss and ignore it. + */ + return 1; + case DFU_TO_DEV(DFU_DNLOAD): + debug("DFU_DNLOAD\n"); + if (dfu.state == dfuIDLE) { + next_block = setup->wValue; + payload = PAYLOAD_START; + } + else if (dfu.state != dfuDNLOAD_IDLE) { + error("bad state\n"); + return 0; + } + if (dfu.state != dfuIDLE && setup->wValue == next_block-1) { + debug("retransmisson\n"); + return 1; + } + if (setup->wValue != next_block) { + debug("bad block (%d vs. %d)\n", + setup->wValue, next_block); + dfu.state = dfuERROR; + dfu.status = errUNKNOWN; + return 1; + } + if (!setup->wLength) { + debug("DONE\n"); + dfu.state = dfuIDLE; + did_download = 1; + return 1; + } + ok = block_receive(setup->wLength); + next_block++; + dfu.state = dfuDNLOAD_IDLE; + return ok; + case DFU_FROM_DEV(DFU_UPLOAD): + debug("DFU_UPLOAD\n"); + if (dfu.state == dfuIDLE) { + next_block = setup->wValue; + payload = PAYLOAD_START; + } + else if (dfu.state != dfuUPLOAD_IDLE) + return 0; + if (dfu.state != dfuIDLE && setup->wValue == next_block-1) { + debug("retransmisson\n"); + /* @@@ try harder */ + dfu.state = dfuERROR; + dfu.status = errUNKNOWN; + return 1; + } + if (setup->wValue != next_block) { + debug("bad block (%d vs. %d)\n", + setup->wValue, next_block); + dfu.state = dfuERROR; + dfu.status = errUNKNOWN; + return 1; + } + ok = block_transmit(setup->wLength); + next_block++; + dfu.state = dfuUPLOAD_IDLE; + return ok; + case DFU_FROM_DEV(DFU_GETSTATUS): + debug("DFU_GETSTATUS\n"); + usb_send(&ep0, (uint8_t *) &dfu, sizeof(dfu), NULL, NULL); + return 1; + case DFU_TO_DEV(DFU_CLRSTATUS): + debug("DFU_CLRSTATUS\n"); + dfu.state = dfuIDLE; + dfu.status = OK; + return 1; + case DFU_FROM_DEV(DFU_GETSTATE): + debug("DFU_GETSTATE\n"); + usb_send(&ep0, &dfu.state, 1, NULL, NULL); + return 1; + case DFU_TO_DEV(DFU_ABORT): + debug("DFU_ABORT\n"); + dfu.state = dfuIDLE; + dfu.status = OK; + return 1; + default: +#ifdef CONFIG_PRINTK + printk("DFU rt %x, rq%x ?\n", + setup->bmRequestType, setup->bRequest); +#else + /* + * @@@ SDCC 2.7.0 ends up OR'in setup->bmRequestType with + * setup->bRequest unshifted if we don't use at least one of + * them here. + */ + { + static volatile uint8_t foo; + foo = setup->bRequest; + } +#endif + return 0; + } +} + + +static __bit my_descr(uint8_t type, uint8_t index, const uint8_t **reply, + uint8_t *size) __reentrant +{ + index; /* suppress warning */ + if (type != DFU_DT_FUNCTIONAL) + return 0; + *reply = functional_descriptor; + *size = sizeof(functional_descriptor); + return 1; +} + + +static void my_reset(void) __reentrant +{ + /* @@@ not nice -- think about where this should go */ + extern void run_payload(void); + + if (did_download) + run_payload(); +} + + +void dfu_init(void) +{ + user_setup = my_setup; + user_get_descriptor = my_descr; + user_reset = my_reset; +} diff --git a/fw/boot/dfu.h b/fw/boot/dfu.h new file mode 100644 index 0000000..7adb538 --- /dev/null +++ b/fw/boot/dfu.h @@ -0,0 +1,86 @@ +/* + * boot/dfu.h - DFU protocol constants and data structures + * + * Written 2008 by Werner Almesberger + * Copyright 2008 Werner Almesberger + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + + +#ifndef DFU_H +#define DFU_H + +#include + + +enum dfu_request { + DFU_DETACH, + DFU_DNLOAD, + DFU_UPLOAD, + DFU_GETSTATUS, + DFU_CLRSTATUS, + DFU_GETSTATE, + DFU_ABORT, +}; + + +enum dfu_status { + OK, + errTARGET, + errFILE, + errWRITE, + errERASE, + errCHECK_ERASED, + errPROG, + errVERIFY, + errADDRESS, + errNOTDONE, + errFIRMWARE, + errVENDOR, + errUSBR, + errPOR, + errUNKNOWN, + errSTALLEDPKT, +}; + + +enum dfu_state { + appIDLE, + appDETACH, + dfuIDLE, + dfuDNLOAD_SYNC, + dfuDNBUSY, + dfuDNLOAD_IDLE, + dfuMANIFEST_SYNC, + dfuMANIFEST, + dfuMANIFEST_WAIT_RESET, + dfuUPLOAD_IDLE, + dfuERROR +}; + + +#define DFU_DT_FUNCTIONAL 0x21 /* DFU FUNCTIONAL descriptor type */ + + +#define DFU_TO_DEV(req) (0x21 | (req) << 8) +#define DFU_FROM_DEV(req) (0xa1 | (req) << 8) + + +struct dfu { + uint8_t status; /* bStatus */ + uint8_t toL, toM, toH; /* bwPollTimeout */ + uint8_t state; /* bState */ + uint8_t iString; +}; + + +extern struct dfu dfu; + + +void dfu_init(void); + +#endif /* !DFU_H */ diff --git a/fw/boot/version.h b/fw/boot/version.h new file mode 100644 index 0000000..6080177 --- /dev/null +++ b/fw/boot/version.h @@ -0,0 +1,26 @@ +/* + * boot/version.h - Automatically generated version string + * + * Written 2008 by Werner Almesberger + * Copyright 2008 Werner Almesberger + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + + +#ifndef VERSION_H +#define VERSION_H + +#include + +/* + * Oddly, sdcc seems to insist on the "extern" to mean "declaration". + */ + +extern const char *build_date; +extern const uint16_t build_number; + +#endif /* !VERSION_H */