zinger: add support to flash RW firmware

Allow flashing the RW firmware by sending Vendor-Defined Messages over
the USB-PD link.

This is not the secure update whose design is still under discussion,
it's a simple update with integrity check.

Signed-off-by: Vincent Palatin <vpalatin@chromium.org>

BRANCH=none
BUG=chrome-os-partner:28330
TEST=using the following CLs,
./util/flash_pd.py ./build/zinger/ec.RW.flat
and see Zinger booting on RW, repeat the operations with different
builds of the RW firmware.

Change-Id: Icd90eb92f7321ccd66341a50b9dabd73c59c68c1
Reviewed-on: https://chromium-review.googlesource.com/197948
Reviewed-by: Alec Berg <alecaberg@chromium.org>
Tested-by: Vincent Palatin <vpalatin@chromium.org>
Commit-Queue: Vincent Palatin <vpalatin@chromium.org>
This commit is contained in:
Vincent Palatin
2014-05-13 13:02:26 -07:00
committed by chrome-internal-fetch
parent 00cf957832
commit 5fd3341077
7 changed files with 337 additions and 10 deletions

View File

@@ -8,11 +8,16 @@
#include "debug.h"
#include "irq_handler.h"
#include "registers.h"
#include "sha1.h"
#include "usb_pd.h"
#include "util.h"
extern void pd_rx_handler(void);
/* RW firmware reset vector */
static uint32_t * const rw_rst =
(uint32_t *)(CONFIG_FLASH_BASE+CONFIG_FW_RW_OFF+4);
/* External interrupt EXTINT7 for external comparator on PA7 */
void IRQ_HANDLER(STM32_IRQ_EXTI4_15)(void)
{
@@ -22,16 +27,64 @@ void IRQ_HANDLER(STM32_IRQ_EXTI4_15)(void)
pd_rx_handler();
}
static void jump_to_rw(void)
{
void (*jump_rw_rst)(void) = (void *)*rw_rst;
debug_printf("Jump to RW\n");
/* Disable interrupts */
asm volatile("cpsid i");
/* Call RW firmware reset vector */
jump_rw_rst();
}
int is_ro_mode(void)
{
return (uint32_t)&jump_to_rw < *rw_rst;
}
static int check_rw_valid(void)
{
uint32_t *hash;
uint32_t *fw_hash = (uint32_t *)
(CONFIG_FLASH_BASE + CONFIG_FLASH_SIZE - 32);
/* Check if we have a RW firmware flashed */
if (*rw_rst == 0xffffffff)
return 0;
hash = (uint32_t *)flash_hash_rw();
/* TODO(crosbug.com/p/28336) use secret key to check RW */
if (memcmp(hash, fw_hash, SHA1_DIGEST_SIZE) != 0) {
/* Firmware doesn't match the recorded hash */
debug_printf("SHA-1 %08x %08x %08x %08x %08x\n",
hash[0], hash[1], hash[2], hash[3], hash[4]);
debug_printf("FW SHA-1 %08x %08x %08x %08x %08x\n",
fw_hash[0], fw_hash[1], fw_hash[2],
fw_hash[3], fw_hash[4]);
return 0;
}
return 1;
}
extern void pd_task(void);
int main(void)
{
hardware_init();
debug_printf("Power supply started ...\n");
debug_printf("Power supply started ... %s\n",
is_ro_mode() ? "RO" : "RW");
/* Verify RW firmware and use it if valid */
if (is_ro_mode() && check_rw_valid())
jump_to_rw();
/* background loop for PD events */
pd_task();
while (1)
;
debug_printf("background loop exited !\n");
/* we should never reach that point */
cpu_reset();
return 0;
}

View File

@@ -18,7 +18,9 @@
#define CONFIG_USB_POWER_DELIVERY
#undef CONFIG_USB_PD_DUAL_ROLE
#undef CONFIG_USB_PD_INTERNAL_COMP
#define CONFIG_USB_PD_CUSTOM_VDM
#define CONFIG_HW_CRC
#define CONFIG_SHA1
#undef CONFIG_WATCHDOG_HELP
#undef CONFIG_LID_SWITCH
#undef CONFIG_TASK_PROFILING
@@ -63,6 +65,15 @@ void hardware_init(void);
/* last interrupt event */
extern volatile uint32_t last_event;
/* RW section flashing */
int flash_erase_rw(void);
int flash_write_rw(int offset, int size, const char *data);
uint8_t *flash_hash_rw(void);
int is_ro_mode(void);
/* Reboot the CPU */
void cpu_reset(void);
#endif /* !__ASSEMBLER__ */
#endif /* __BOARD_H */

View File

@@ -9,6 +9,7 @@
#include "common.h"
#include "cpu.h"
#include "registers.h"
#include "sha1.h"
#include "task.h"
#include "timer.h"
#include "util.h"
@@ -99,11 +100,14 @@ static void pins_init(void)
static void adc_init(void)
{
/* ADC calibration (done with ADEN = 0) */
STM32_ADC_CR = 1 << 31; /* set ADCAL = 1, ADC off */
/* wait for the end of calibration */
while (STM32_ADC_CR & (1 << 31))
;
/* Only do the calibration if the ADC is off */
if (!(STM32_ADC_CR & 1)) {
/* ADC calibration */
STM32_ADC_CR = 1 << 31; /* set ADCAL = 1, ADC off */
/* wait for the end of calibration */
while (STM32_ADC_CR & (1 << 31))
;
}
/* ADC enabled */
STM32_ADC_CR = 1 << 0;
/* Single conversion, right aligned, 12-bit */
@@ -178,3 +182,139 @@ int adc_read_channel(enum adc_channel ch)
return value;
}
/* ---- flash handling ---- */
/*
* Approximate number of CPU cycles per iteration of the loop when polling
* the flash status
*/
#define CYCLE_PER_FLASH_LOOP 10
/* Flash page programming timeout. This is 2x the datasheet max. */
#define FLASH_TIMEOUT_US 16000
#define FLASH_TIMEOUT_LOOP \
(FLASH_TIMEOUT_US * (CPU_CLOCK / SECOND) / CYCLE_PER_FLASH_LOOP)
/* Flash unlocking keys */
#define KEY1 0x45670123
#define KEY2 0xCDEF89AB
/* Lock bits for FLASH_CR register */
#define PG (1<<0)
#define PER (1<<1)
#define STRT (1<<6)
#define CR_LOCK (1<<7)
int flash_write_rw(int offset, int size, const char *data)
{
uint16_t *address = (uint16_t *)
(CONFIG_FLASH_BASE + CONFIG_FW_RW_OFF + offset);
int res = EC_SUCCESS;
int i;
if ((uint32_t)address > CONFIG_FLASH_BASE + CONFIG_FLASH_SIZE)
return EC_ERROR_INVAL;
/* unlock CR if needed */
if (STM32_FLASH_CR & CR_LOCK) {
STM32_FLASH_KEYR = KEY1;
STM32_FLASH_KEYR = KEY2;
}
/* Clear previous error status */
STM32_FLASH_SR = 0x34;
/* set the ProGram bit */
STM32_FLASH_CR |= PG;
for (; size > 0; size -= sizeof(uint16_t)) {
/* wait to be ready */
for (i = 0; (STM32_FLASH_SR & 1) && (i < FLASH_TIMEOUT_LOOP);
i++)
;
/* write the half word */
*address++ = data[0] + (data[1] << 8);
data += 2;
/* Wait for writes to complete */
for (i = 0; (STM32_FLASH_SR & 1) && (i < FLASH_TIMEOUT_LOOP);
i++)
;
if (i == FLASH_TIMEOUT_LOOP) {
res = EC_ERROR_TIMEOUT;
goto exit_wr;
}
/* Check for error conditions - erase failed, voltage error,
* protection error */
if (STM32_FLASH_SR & 0x14) {
res = EC_ERROR_UNKNOWN;
goto exit_wr;
}
}
exit_wr:
STM32_FLASH_CR &= ~PG;
STM32_FLASH_CR = CR_LOCK;
return res;
}
int flash_erase_rw(void)
{
int res = EC_SUCCESS;
int offset = CONFIG_FW_RW_OFF;
int size = CONFIG_FW_RW_SIZE;
/* unlock CR if needed */
if (STM32_FLASH_CR & CR_LOCK) {
STM32_FLASH_KEYR = KEY1;
STM32_FLASH_KEYR = KEY2;
}
/* Clear previous error status */
STM32_FLASH_SR = 0x34;
/* set PER bit */
STM32_FLASH_CR |= PER;
for (; size > 0; size -= CONFIG_FLASH_ERASE_SIZE,
offset += CONFIG_FLASH_ERASE_SIZE) {
int i;
/* select page to erase */
STM32_FLASH_AR = CONFIG_FLASH_BASE + offset;
/* set STRT bit : start erase */
STM32_FLASH_CR |= STRT;
/* Wait for erase to complete */
for (i = 0; (STM32_FLASH_SR & 1) && (i < FLASH_TIMEOUT_LOOP);
i++)
;
if (i == FLASH_TIMEOUT_LOOP) {
res = EC_ERROR_TIMEOUT;
goto exit_er;
}
/*
* Check for error conditions - erase failed, voltage error,
* protection error
*/
if (STM32_FLASH_SR & 0x14) {
res = EC_ERROR_UNKNOWN;
goto exit_er;
}
}
exit_er:
STM32_FLASH_CR &= ~PER;
STM32_FLASH_CR = CR_LOCK;
return res;
}
static struct sha1_ctx ctx;
uint8_t *flash_hash_rw(void)
{
sha1_init(&ctx);
sha1_update(&ctx, (void *)CONFIG_FLASH_BASE + CONFIG_FW_RW_OFF,
CONFIG_FW_RW_SIZE - 32);
return sha1_final(&ctx);
}

View File

@@ -91,6 +91,17 @@ uint32_t task_wait_event(int timeout_us)
return evt;
}
void cpu_reset(void)
{
/* Disable interrupts */
asm volatile("cpsid i");
/* reboot the CPU */
CPU_NVIC_APINT = 0x05fa0004;
/* Spin and wait for reboot; should never return */
while (1)
;
}
/* --- stubs --- */
void __hw_timer_enable_clock(int n, int enable)
{ /* Done in hardware init */ }

View File

@@ -10,9 +10,11 @@
#include "debug.h"
#include "hooks.h"
#include "registers.h"
#include "sha1.h"
#include "timer.h"
#include "util.h"
#include "usb_pd.h"
#include "version.h"
/* ------------------------- Power supply control ------------------------ */
@@ -205,3 +207,59 @@ int pd_board_checks(void)
return EC_SUCCESS;
}
/* ----------------- Vendor Defined Messages ------------------ */
int pd_custom_vdm(void *ctxt, int cnt, uint32_t *payload, uint32_t **rpayload)
{
static int flash_offset;
void *hash;
int cmd = PD_VDO_CMD(payload[0]);
int rsize = 1;
debug_printf("%T] VDM/%d [%d] %08x\n", cnt, cmd, payload[0]);
*rpayload = payload;
switch (cmd) {
case VDO_CMD_VERSION:
memcpy(payload + 1, &version_data.version, 24);
rsize = 7;
break;
case VDO_CMD_REBOOT:
/* ensure the power supply is in a safe state */
pd_power_supply_reset();
cpu_reset();
break;
case VDO_CMD_RW_HASH:
hash = flash_hash_rw();
memcpy(payload + 1, hash, SHA1_DIGEST_SIZE);
rsize = 6;
break;
case VDO_CMD_FLASH_ERASE:
/* do not kill the code under our feet */
if (!is_ro_mode())
break;
flash_offset = 0;
flash_erase_rw();
break;
case VDO_CMD_FLASH_WRITE:
/* do not kill the code under our feet */
if (!is_ro_mode())
break;
flash_write_rw(flash_offset, 4*(cnt - 1),
(const char *)(payload+1));
flash_offset += 4*(cnt - 1);
break;
case VDO_CMD_FLASH_HASH:
/* this is not touching the code area */
flash_write_rw(CONFIG_FW_RW_SIZE - 32, 4*cnt,
(const char *)(payload+1));
break;
default:
/* Unknown : do not answer */
return 0;
}
debug_printf("%T] DONE\n");
/* respond (positively) to the request */
payload[0] |= VDO_SRC_RESPONDER;
return rsize;
}

View File

@@ -395,8 +395,23 @@ static int send_bist(void *ctxt)
static void handle_vdm_request(void *ctxt, int cnt, uint32_t *payload)
{
CPRINTF("[%T PD Unhandled VDM VID %04x CMD %04x]\n", payload[0] >> 16,
payload[0] & 0xFFFF);
uint16_t vid = PD_VDO_VID(payload[0]);
#ifdef CONFIG_USB_PD_CUSTOM_VDM
int rlen;
uint32_t *rdata;
if (vid == USB_VID_GOOGLE) {
rlen = pd_custom_vdm(ctxt, cnt, payload, &rdata);
if (rlen > 0) {
uint16_t header = PD_HEADER(PD_DATA_VENDOR_DEF,
pd_role, pd_message_id, rlen);
send_validate_message(ctxt, header, rlen, rdata);
}
return;
}
#endif
CPRINTF("[%T PD Unhandled VDM VID %04x CMD %04x]\n",
vid, payload[0] & 0xFFFF);
}
static void handle_data_request(void *ctxt, uint16_t head, uint32_t *payload)

View File

@@ -104,6 +104,34 @@ enum pd_errors {
/* VDO : Vendor Defined Message Object */
#define VDO(vid, custom) (((vid) << 16) | ((custom) & 0xFFFF))
#define VDO_ACK (0 << 6)
#define VDO_NAK (1 << 6)
#define VDO_PENDING (2 << 6)
#define VDO_SRC_INITIATOR (0 << 5)
#define VDO_SRC_RESPONDER (1 << 5)
#define VDO_CMD_DISCOVER_VID (1 << 0)
#define VDO_CMD_DISCOVER_ALT (2 << 0)
#define VDO_CMD_AUTHENTICATE (3 << 0)
#define VDO_CMD_ENTER_ALT (4 << 0)
#define VDO_CMD_EXIT_ALT (5 << 0)
#define VDO_CMD_VENDOR(x) (((10 + (x)) & 0x1f))
/* ChromeOS specific commands */
#define VDO_CMD_VERSION VDO_CMD_VENDOR(0)
#define VDO_CMD_RW_HASH VDO_CMD_VENDOR(2)
#define VDO_CMD_REBOOT VDO_CMD_VENDOR(5)
#define VDO_CMD_FLASH_ERASE VDO_CMD_VENDOR(6)
#define VDO_CMD_FLASH_WRITE VDO_CMD_VENDOR(7)
#define VDO_CMD_FLASH_HASH VDO_CMD_VENDOR(8)
#define PD_VDO_VID(vdo) ((vdo) >> 16)
#define PD_VDO_CMD(vdo) ((vdo) & 0x1f)
/* USB Vendor ID assigned to Google Inc. */
#define USB_VID_GOOGLE 0x18d1
/* --- Policy layer functions --- */
/**
@@ -157,6 +185,17 @@ void pd_request_source_voltage(int mv);
*/
int pd_board_checks(void);
/*
* Handle Vendor Defined Message with our vendor ID.
*
* @param ctxt opaque context for PD communication.
* @param cnt number of data objects in the payload.
* @param payload payload data.
* @param rpayload pointer to the data to send back.
* @return if >0, number of VDOs to send back.
*/
int pd_custom_vdm(void *ctxt, int cnt, uint32_t *payload, uint32_t **rpayload);
/* Power Data Objects for the source and the sink */
extern const uint32_t pd_src_pdo[];
extern const int pd_src_pdo_cnt;