From b2ac77b37b66ce2e6e621743d5cd1510457cc19f Mon Sep 17 00:00:00 2001 From: Randall Spangler Date: Mon, 19 Mar 2012 10:59:38 -0700 Subject: [PATCH] Support warm reboot from one EC image to another. This is necessary at init-time for verified boot to jump from RO to one of the RW images. It's also used by factory EC update to update one image and then jump to the updated image to finish the update. In this case, the x86 does NOT reboot. Signed-off-by: Randall Spangler BUG=chrome-os-partner:8449 TEST=manual 1) power on x86 and log in 2) sysjump a --> system is in a; x86 has not rebooted 3) sysjump ro --> system is back in RO; x86 has not rebooted 4) reboot -> system is in RO; x86 HAS rebooted Change-Id: I9dbadcf9775e146a0718abfd4ee0758b65350a87 --- chip/lm4/gpio.c | 26 +++++-- chip/lm4/peci.c | 3 + chip/lm4/system.c | 3 +- chip/lm4/uart.c | 11 +++ chip/lm4/watchdog.c | 12 +++- chip/stm32l/uart.c | 12 +++- common/main.c | 8 +-- common/system_common.c | 152 ++++++++++++++++++++++++++++++++++------- common/vboot.c | 29 -------- common/x86_power.c | 28 +++++++- core/cortex-m/cpu.h | 1 + core/cortex-m/init.S | 8 ++- core/cortex-m/task.c | 22 ++++-- include/task.h | 2 +- include/uart.h | 2 + 15 files changed, 239 insertions(+), 80 deletions(-) diff --git a/chip/lm4/gpio.c b/chip/lm4/gpio.c index 81b7312f03..a7c4f96163 100644 --- a/chip/lm4/gpio.c +++ b/chip/lm4/gpio.c @@ -16,7 +16,7 @@ /* 0-terminated list of GPIO bases */ -const uint32_t gpio_bases[] = { +static const uint32_t gpio_bases[] = { LM4_GPIO_A, LM4_GPIO_B, LM4_GPIO_C, LM4_GPIO_D, LM4_GPIO_E, LM4_GPIO_F, LM4_GPIO_G, LM4_GPIO_H, LM4_GPIO_J, LM4_GPIO_K, LM4_GPIO_L, LM4_GPIO_M, @@ -45,12 +45,18 @@ int gpio_pre_init(void) { volatile uint32_t scratch __attribute__((unused)); const struct gpio_info *g = gpio_list; + int is_warm = 0; int i; - /* Enable clocks to all the GPIO blocks (since we use all of them as - * GPIOs) */ - LM4_SYSTEM_RCGCGPIO |= 0x7fff; - scratch = LM4_SYSTEM_RCGCGPIO; /* Delay a few clocks */ + if (LM4_SYSTEM_RCGCGPIO == 0x7fff) { + /* This is a warm reboot */ + is_warm = 1; + } else { + /* Enable clocks to all the GPIO blocks (since we use all of + * them as GPIOs) */ + LM4_SYSTEM_RCGCGPIO |= 0x7fff; + scratch = LM4_SYSTEM_RCGCGPIO; /* Delay a few clocks */ + } /* Disable GPIO commit control for PD7 and PF0, since we don't use the * NMI pin function. */ @@ -64,6 +70,10 @@ int gpio_pre_init(void) /* Clear SSI0 alternate function on PA2:5 */ LM4_GPIO_AFSEL(LM4_GPIO_A) &= ~0x3c; + /* Mask all GPIO interrupts */ + for (i = 0; gpio_bases[i]; i++) + LM4_GPIO_IM(gpio_bases[i]) = 0; + /* Set all GPIOs to defaults */ for (i = 0; i < GPIO_COUNT; i++, g++) { @@ -76,7 +86,11 @@ int gpio_pre_init(void) LM4_GPIO_DIR(g->port) |= g->mask; /* Must set level after direction; writes to GPIO_DATA * before direction is output appear to be ignored. */ - gpio_set_level(i, g->flags & GPIO_HIGH); + /* Only set level on a cold reboot; on a warm reboot we + * want to leave things where they were or we'll shut + * off the x86. */ + if (!is_warm) + gpio_set_level(i, g->flags & GPIO_HIGH); } else { /* Input */ if (g->flags & GPIO_PULL) { diff --git a/chip/lm4/peci.c b/chip/lm4/peci.c index f3479821e2..75946a4e61 100644 --- a/chip/lm4/peci.c +++ b/chip/lm4/peci.c @@ -97,6 +97,9 @@ int peci_init(void) /* Configure GPIOs */ configure_gpios(); + /* Disable polling while reconfiguring */ + LM4_PECI_CTL = 0; + /* Calculate baud setting from desired rate, compensating for internal * and external delays. */ baud = CPU_CLOCK / (4 * PECI_BAUD_RATE) - 2; diff --git a/chip/lm4/system.c b/chip/lm4/system.c index 200cd6c73c..4915bb242f 100644 --- a/chip/lm4/system.c +++ b/chip/lm4/system.c @@ -61,7 +61,8 @@ static void check_reset_cause(void) } else if (raw_reset_cause) { reset_cause = SYSTEM_RESET_OTHER; } else { - reset_cause = SYSTEM_RESET_UNKNOWN; + /* Reset cause is still 0, so this is a warm reset. */ + reset_cause = SYSTEM_RESET_SOFT_WARM; } system_set_reset_cause(reset_cause); } diff --git a/chip/lm4/uart.c b/chip/lm4/uart.c index fa9ddb005f..652f1edf37 100644 --- a/chip/lm4/uart.c +++ b/chip/lm4/uart.c @@ -19,6 +19,15 @@ /* Baud rate for UARTs */ #define BAUD_RATE 115200 + +static int init_done; + + +int uart_init_done(void) +{ + return init_done; +} + void uart_tx_start(void) { /* Re-enable the transmit interrupt, then forcibly trigger the @@ -186,6 +195,8 @@ int uart_init(void) */ task_enable_irq(LM4_IRQ_UART0); + init_done = 1; + return EC_SUCCESS; } diff --git a/chip/lm4/watchdog.c b/chip/lm4/watchdog.c index 5a3080dcdb..74fe3e5fbd 100644 --- a/chip/lm4/watchdog.c +++ b/chip/lm4/watchdog.c @@ -114,10 +114,13 @@ int watchdog_init(int period_ms) /* Enable watchdog 0 clock */ LM4_SYSTEM_RCGCWD |= 0x1; - /* wait 3 clock cycles before using the module */ + /* Wait 3 clock cycles before using the module */ scratch = LM4_SYSTEM_RCGCWD; - /* set the time-out period */ + /* Unlock watchdog registers */ + LM4_WATCHDOG_LOCK(0) = LM4_WATCHDOG_MAGIC_WORD; + + /* Set the time-out period */ watchdog_period = period_ms * (CPU_CLOCK / 1000); LM4_WATCHDOG_LOAD(0) = watchdog_period; @@ -129,7 +132,10 @@ int watchdog_init(int period_ms) */ LM4_WATCHDOG_CTL(0) = 0x3; - /* lock watchdog registers against unintended accesses */ + /* Reset watchdog interrupt bits */ + LM4_WATCHDOG_ICR(0) = LM4_WATCHDOG_RIS(0); + + /* Lock watchdog registers against unintended accesses */ LM4_WATCHDOG_LOCK(0) = 0xdeaddead; /* Enable watchdog interrupt */ diff --git a/chip/stm32l/uart.c b/chip/stm32l/uart.c index 9d5dd139f7..9da697debe 100644 --- a/chip/stm32l/uart.c +++ b/chip/stm32l/uart.c @@ -20,8 +20,14 @@ /* Console USART index */ #define UARTN CONFIG_CONSOLE_UART -/* record last TX control action */ -static int should_stop; +static int init_done; /* Initialization done? */ +static int should_stop; /* Last TX control action */ + + +int uart_init_done(void) +{ + return init_done; +} void uart_tx_start(void) { @@ -138,5 +144,7 @@ int uart_init(void) /* Enable interrupts */ task_enable_irq(STM32L_IRQ_USART(UARTN)); + init_done = 1; + return EC_SUCCESS; } diff --git a/common/main.c b/common/main.c index 7af2959317..303ca059db 100644 --- a/common/main.c +++ b/common/main.c @@ -49,6 +49,10 @@ int main(void) jtag_pre_init(); gpio_pre_init(); + /* Initialize interrupts, but don't enable any of them. Note that + * task scheduling is not enabled until task_start() below. */ + task_pre_init(); + #ifdef CONFIG_FLASH flash_pre_init(); #endif @@ -64,10 +68,6 @@ int main(void) /* Set the CPU clocks / PLLs. System is now running at full speed. */ clock_init(); - /* Initialize interrupts, but don't enable any of them. Note that - * task scheduling is not enabled until task_start() below. */ - task_init(); - /* Main initialization stage. Modules may enable interrupts here. */ /* Initialize UART. uart_printf(), etc. may now be used. */ diff --git a/common/system_common.c b/common/system_common.c index e52cac05b6..d75ac188ea 100644 --- a/common/system_common.c +++ b/common/system_common.c @@ -9,6 +9,7 @@ #include "host_command.h" #include "lpc_commands.h" #include "system.h" +#include "task.h" #include "uart.h" #include "util.h" #include "version.h" @@ -64,39 +65,60 @@ const char *system_get_image_copy_string(void) } +/* Jump to what we hope is the init address of an image. This function does + * not return. */ +static void jump_to_image(uint32_t init_addr) +{ + void (*resetvec)(void) = (void(*)(void))init_addr; + + /* Flush UART output unless the UART hasn't been initialized yet */ + if (uart_init_done()) + uart_flush_output(); + + /* Disable interrupts before jump */ + interrupt_disable(); + + /* Jump to the reset vector */ + resetvec(); +} + + int system_run_image_copy(enum system_image_copy_t copy) { + uint32_t base; uint32_t init_addr; - void (*resetvec)(void); - - /* Fail if we're not in RO firmware */ - if (system_get_image_copy() != SYSTEM_IMAGE_RO) - return EC_ERROR_UNKNOWN; - - /* Load the appropriate reset vector */ - if (copy == SYSTEM_IMAGE_RW_A) - init_addr = *(uint32_t *)(CONFIG_FW_A_OFF + 4); -#ifndef CONFIG_NO_RW_B - else if (copy == SYSTEM_IMAGE_RW_B) - init_addr = *(uint32_t *)(CONFIG_FW_B_OFF + 4); -#endif - else - return EC_ERROR_UNKNOWN; /* TODO: sanity checks (crosbug.com/p/7468) * - * Fail if called outside of pre-init. - * - * Fail if reboot reason is not soft reboot. Power-on - * reset cause must run RO firmware; if it wants to move to RW - * firmware, it must go through a soft reboot first - * - * Sanity check reset vector; must be inside the appropriate - * image. */ + * For this to be allowed either WP must be disabled, or ALL of the + * following must be true: + * - We must currently be running the RO image. + * - We must still be in init (that is, before task_start(). + * - The target image must be A or B. */ - /* Jump to the reset vector */ - resetvec = (void(*)(void))init_addr; - resetvec(); + /* Load the appropriate reset vector */ + switch (copy) { + case SYSTEM_IMAGE_RO: + base = CONFIG_FW_RO_OFF; + break; + case SYSTEM_IMAGE_RW_A: + base = CONFIG_FW_A_OFF; + break; +#ifndef CONFIG_NO_RW_B + case SYSTEM_IMAGE_RW_B: + base = CONFIG_FW_B_OFF; + break; +#endif + default: + return EC_ERROR_INVAL; + } + + /* Make sure the reset vector is inside the destination image */ + init_addr = *(uint32_t *)(base + 4); + if (init_addr < base || init_addr >= base + CONFIG_FW_IMAGE_SIZE) + return EC_ERROR_UNKNOWN; + + jump_to_image(init_addr); /* Should never get here */ return EC_ERROR_UNIMPLEMENTED; @@ -215,6 +237,46 @@ static int command_version(int argc, char **argv) } DECLARE_CONSOLE_COMMAND(version, command_version); + +static int command_sysjump(int argc, char **argv) +{ + uint32_t addr; + char *e; + + /* TODO: (crosbug.com/p/7468) For this command to be allowed, WP must + * be disabled. */ + + if (argc < 2) { + uart_puts("Usage: sysjump \n"); + return EC_ERROR_INVAL; + } + + /* Handle named images */ + if (!strcasecmp(argv[1], "RO")) { + uart_puts("Jumping directly to RO image...\n"); + return system_run_image_copy(SYSTEM_IMAGE_RO); + } else if (!strcasecmp(argv[1], "A")) { + uart_puts("Jumping directly to image A...\n"); + return system_run_image_copy(SYSTEM_IMAGE_RW_A); + } else if (!strcasecmp(argv[1], "B")) { + uart_puts("Jumping directly to image B...\n"); + return system_run_image_copy(SYSTEM_IMAGE_RW_B); + } + + /* Check for arbitrary address */ + addr = strtoi(argv[1], &e, 0); + if (e && *e) { + uart_puts("Invalid image address\n"); + return EC_ERROR_INVAL; + } + uart_printf("Jumping directly to 0x%08x...\n", addr); + uart_flush_output(); + jump_to_image(addr); + return EC_SUCCESS; +} +DECLARE_CONSOLE_COMMAND(sysjump, command_sysjump); + + /*****************************************************************************/ /* Host commands */ @@ -261,3 +323,41 @@ static enum lpc_status host_command_build_info(uint8_t *data) return EC_LPC_RESULT_SUCCESS; } DECLARE_HOST_COMMAND(EC_LPC_COMMAND_GET_BUILD_INFO, host_command_build_info); + + +#ifdef CONFIG_REBOOT_EC +enum lpc_status host_command_reboot(uint8_t *data) +{ + struct lpc_params_reboot_ec *p = + (struct lpc_params_reboot_ec *)data; + + /* TODO: (crosbug.com/p/7468) For this command to be allowed, WP must + * be disabled. */ + + switch (p->target) { + case EC_LPC_IMAGE_RO: + uart_puts("[Rebooting to image RO!\n]"); + system_run_image_copy(SYSTEM_IMAGE_RO); + break; + case EC_LPC_IMAGE_RW_A: + uart_puts("[Rebooting to image A!]\n"); + system_run_image_copy(SYSTEM_IMAGE_RW_A); + break; + case EC_LPC_IMAGE_RW_B: + uart_puts("[Rebooting to image B!]\n"); + system_run_image_copy(SYSTEM_IMAGE_RW_B); + break; + default: + return EC_LPC_RESULT_ERROR; + } + + /* We normally never get down here, because we'll have jumped to + * another image. To confirm this command worked, the host will need + * to check what image is current using GET_VERSION. + * + * If we DO get down here, something went wrong in the reboot, so + * return error. */ + return EC_LPC_RESULT_ERROR; +} +DECLARE_HOST_COMMAND(EC_LPC_COMMAND_REBOOT_EC, host_command_reboot); +#endif /* CONFIG_REBOOT_EC */ diff --git a/common/vboot.c b/common/vboot.c index 307dfac2d0..f6e8ff1644 100644 --- a/common/vboot.c +++ b/common/vboot.c @@ -90,35 +90,6 @@ static int command_reboot(int argc, char **argv) } DECLARE_CONSOLE_COMMAND(reboot, command_reboot); -#ifdef CONFIG_REBOOT_EC -enum lpc_status vboot_command_reboot(uint8_t *data) { - struct lpc_params_reboot_ec *p = - (struct lpc_params_reboot_ec *)data; - - switch (p->target) { - case EC_LPC_IMAGE_RW_A: - uart_puts("Rebooting to image A!\n\n\n"); - system_set_scratchpad(SCRATCHPAD_REQUEST_A); - break; - case EC_LPC_IMAGE_RW_B: - uart_puts("Rebooting to image B!\n\n\n"); - system_set_scratchpad(SCRATCHPAD_REQUEST_B); - break; - case EC_LPC_IMAGE_RO: /* do nothing */ - uart_puts("Rebooting to image RO!\n\n\n"); - break; - default: - return EC_LPC_RESULT_ERROR; - } - - uart_flush_output(); - /* TODO - param to specify warm/cold */ - system_reset(1); - return EC_LPC_RESULT_SUCCESS; -} -DECLARE_HOST_COMMAND(EC_LPC_COMMAND_REBOOT_EC, vboot_command_reboot); -#endif /* CONFIG_REBOOT_EC */ - /*****************************************************************************/ /* Initialization */ diff --git a/common/x86_power.c b/common/x86_power.c index e58bbdf7d2..22641452b2 100644 --- a/common/x86_power.c +++ b/common/x86_power.c @@ -12,6 +12,7 @@ #include "gpio.h" #include "lpc.h" #include "pwm.h" +#include "system.h" #include "task.h" #include "timer.h" #include "uart.h" @@ -77,7 +78,9 @@ static const char * const state_names[] = { IN_PCH_SLP_S4n_DEASSERTED | \ IN_PCH_SLP_S5n_DEASSERTED | \ IN_PCH_SLP_An_DEASSERTED) - +/* All inputs in the right state for S0 */ +#define IN_ALL_S0 (IN_PGOOD_ALWAYS_ON | IN_PGOOD_ALL_NONCORE | \ + IN_PGOOD_ALL_CORE | IN_ALL_PM_SLP_DEASSERTED) static enum x86_state state; /* Current state */ static uint32_t in_signals; /* Current input signal states (IN_PGOOD_*) */ @@ -245,12 +248,35 @@ void x86_power_interrupt(enum gpio_signal signal) int x86_power_init(void) { + /* Default to G3 state unless proven otherwise */ state = X86_G3; /* Update input state */ update_in_signals(); in_want = 0; + /* If this is a warm reboot, see if the x86 is already powered on; if + * so, leave it there instead of cycling through G3. */ + if (system_get_reset_cause() == SYSTEM_RESET_SOFT_WARM) { + if ((in_signals & IN_ALL_S0) == IN_ALL_S0) { + uart_puts("[x86 already in S0]\n"); + state = X86_S0; + } else { + /* Force all signals to their G3 states */ + uart_puts("[x86 forcing G3]\n"); + gpio_set_level(GPIO_PCH_PWROK, 0); + gpio_set_level(GPIO_ENABLE_VCORE, 0); + gpio_set_level(GPIO_PCH_RCINn, 0); + gpio_set_level(GPIO_ENABLE_VS, 0); + gpio_set_level(GPIO_ENABLE_TOUCHPAD, 0); + gpio_set_level(GPIO_TOUCHSCREEN_RESETn, 0); + gpio_set_level(GPIO_ENABLE_1_5V_DDR, 0); + gpio_set_level(GPIO_SHUNT_1_5V_DDR, 1); + gpio_set_level(GPIO_PCH_RSMRSTn, 0); + gpio_set_level(GPIO_PCH_DPWROK, 0); + } + } + /* Enable interrupts for our GPIOs */ gpio_enable_interrupt(GPIO_PCH_BKLTEN); gpio_enable_interrupt(GPIO_PCH_SLP_An); diff --git a/core/cortex-m/cpu.h b/core/cortex-m/cpu.h index 1ec95442fe..bd94e179fe 100644 --- a/core/cortex-m/cpu.h +++ b/core/cortex-m/cpu.h @@ -16,6 +16,7 @@ /* Nested Vectored Interrupt Controller */ #define CPU_NVIC_EN(x) CPUREG(0xe000e100 + 4 * (x)) #define CPU_NVIC_DIS(x) CPUREG(0xe000e180 + 4 * (x)) +#define CPU_NVIC_UNPEND(x) CPUREG(0xe000e280 + 4 * (x)) #define CPU_NVIC_PRI(x) CPUREG(0xe000e400 + 4 * (x)) #define CPU_NVIC_APINT CPUREG(0xe000ed0c) #define CPU_NVIC_SWTRIG CPUREG(0xe000ef00) diff --git a/core/cortex-m/init.S b/core/cortex-m/init.S index f8fa196dee..a2c0d0b71e 100644 --- a/core/cortex-m/init.S +++ b/core/cortex-m/init.S @@ -302,7 +302,13 @@ vector_irq 254 @ IRQ 254 handler .global reset .thumb_func reset: - /* set the vector table on our current code */ + /* Ensure we're in privileged mode with main stack. + * Necessary if we've jumped directly here from another image + * after task_start(). */ + mov r0, #0 + msr control, r0 @ use : priv. mode / main stack / no floating point + isb @ ensure the write is done + /* Set the vector table on our current code */ ldr r1, =vectors ldr r2, =0xE000ED08 /* VTABLE register in SCB*/ str r1, [r2] diff --git a/core/cortex-m/task.c b/core/cortex-m/task.c index 41b91de348..246147b7c7 100644 --- a/core/cortex-m/task.c +++ b/core/cortex-m/task.c @@ -299,18 +299,28 @@ void task_trigger_irq(int irq) } -/** - * Enable all used IRQ in the NVIC and set their priorities - * as defined by the DECLARE_IRQ statements - */ +/* Initialize IRQs in the NVIC and set their priorities as defined by the + * DECLARE_IRQ statements. */ static void __nvic_init_irqs(void) { - /* get the IRQ priorities section from the linker */ + /* Get the IRQ priorities section from the linker */ extern struct irq_priority __irqprio[]; extern struct irq_priority __irqprio_end[]; int irq_count = __irqprio_end - __irqprio; int i; + /* Mask and clear all pending interrupts */ + for (i = 0; i < 5; i++) { + CPU_NVIC_DIS(i) = 0xffffffff; + CPU_NVIC_UNPEND(i) = 0xffffffff; + } + + /* Re-enable global interrupts in case they're disabled. On a reboot, + * they're already enabled; if we've jumped here from another image, + * they're not. */ + interrupt_enable(); + + /* Set priorities */ for (i = 0; i < irq_count; i++) { uint8_t irq = __irqprio[i].irq; uint8_t prio = __irqprio[i].priority; @@ -405,7 +415,7 @@ DECLARE_CONSOLE_COMMAND(taskready, command_task_ready); #endif -int task_init(void) +int task_pre_init(void) { /* sanity checks about static task invariants */ BUILD_ASSERT(TASK_ID_COUNT <= sizeof(unsigned) * 8); diff --git a/include/task.h b/include/task.h index 83c1d73555..b5e57e736b 100644 --- a/include/task.h +++ b/include/task.h @@ -74,7 +74,7 @@ uint32_t task_wait_msg(int timeout_us); void task_resched_if_needed(void *excep_return); /* Initializes tasks and interrupt controller. */ -int task_init(void); +int task_pre_init(void); /* Starts task scheduling. */ int task_start(void); diff --git a/include/uart.h b/include/uart.h index 1d43206731..dc64d9ad6f 100644 --- a/include/uart.h +++ b/include/uart.h @@ -14,6 +14,8 @@ /* Initializes the UART module. */ int uart_init(void); +/* Return non-zero if UART init has completed. */ +int uart_init_done(void); /* Enables console mode if !=0. In console mode: * - Input is echoed