/* Copyright (c) 2012 The Chromium OS Authors. All rights reserved. * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ /* System module for Chrome EC : common functions */ #include "board.h" #include "clock.h" #include "config.h" #include "console.h" #include "ec_commands.h" #include "flash.h" #include "gpio.h" #include "hooks.h" #include "host_command.h" #include "lpc.h" #include "system.h" #include "task.h" #include "uart.h" #include "util.h" #include "version.h" /* Console output macros */ #define CPUTS(outstr) cputs(CC_SYSTEM, outstr) #define CPRINTF(format, args...) cprintf(CC_SYSTEM, format, ## args) struct jump_tag { uint16_t tag; uint8_t data_size; uint8_t data_version; }; /* Data passed between the current image and the next one when jumping between * images. */ #define JUMP_DATA_MAGIC 0x706d754a /* "Jump" */ #define JUMP_DATA_VERSION 3 #define JUMP_DATA_SIZE_V2 16 /* Size of version 2 jump data struct */ struct jump_data { /* Add new fields to the _start_ of the struct, since we copy it to the * _end_ of RAM between images. This way, the magic number will always * be the last word in RAM regardless of how many fields are added. */ /* Fields from version 3 */ uint8_t reserved0; /* (used in proto1 to signal recovery mode) */ int struct_size; /* Size of struct jump_data */ /* Fields from version 2 */ int jump_tag_total; /* Total size of all jump tags */ /* Fields from version 1 */ int reset_cause; /* Reset cause for the previous boot */ int version; /* Version (JUMP_DATA_VERSION) */ int magic; /* Magic number (JUMP_DATA_MAGIC). If this * doesn't match at pre-init time, assume no valid * data from the previous image. */ }; /* Jump data goes at the end of RAM */ static struct jump_data * const jdata = (struct jump_data *)(CONFIG_RAM_BASE + CONFIG_RAM_SIZE - sizeof(struct jump_data)); static const char * const image_names[] = {"unknown", "RO", "A", "B"}; static enum system_reset_cause_t reset_cause = SYSTEM_RESET_UNKNOWN; static int jumped_to_image; static int disable_jump; /* Disable ALL jumps if system is locked */ static int force_locked; /* Force system locked even if WP isn't enabled */ static enum ec_reboot_cmd reboot_at_shutdown; int system_is_locked(void) { if (force_locked) return 1; #ifdef CONFIG_SYSTEM_UNLOCKED /* System is explicitly unlocked */ return 0; #elif defined(BOARD_link) && defined(CONFIG_FLASH) /* On link, unlocked if write protect pin deasserted or flash protect * lock not applied. */ if ((FLASH_PROTECT_PIN_ASSERTED | FLASH_PROTECT_LOCK_APPLIED) & ~flash_get_protect_lock()) return 0; /* If WP pin is asserted and lock is applied, we're locked */ return 1; #else /* Other configs are locked by default */ return 1; #endif } int system_usable_ram_end(void) { /* Leave space at the end of RAM for jump data and tags. * * Note that jump_tag_total is 0 on a reboot, so we have the maximum * amount of RAM available on a reboot; we only lose space for stored * tags after a sysjump. When verified boot runs after a reboot, it'll * have as much RAM as we can give it; after verified boot jumps to * another image there'll be less RAM, but we'll care less too. */ return (uint32_t)jdata - jdata->jump_tag_total; } enum system_reset_cause_t system_get_reset_cause(void) { return reset_cause; } int system_jumped_to_this_image(void) { return jumped_to_image; } int system_add_jump_tag(uint16_t tag, int version, int size, const void *data) { struct jump_tag *t; /* Only allowed during a sysjump */ if (jdata->magic != JUMP_DATA_MAGIC) return EC_ERROR_UNKNOWN; /* Make room for the new tag */ if (size > 255 || (size & 3)) return EC_ERROR_INVAL; jdata->jump_tag_total += size + sizeof(struct jump_tag); t = (struct jump_tag *)system_usable_ram_end(); t->tag = tag; t->data_size = size; t->data_version = version; if (size) memcpy(t + 1, data, size); return EC_SUCCESS; } const uint8_t *system_get_jump_tag(uint16_t tag, int *version, int *size) { const struct jump_tag *t; int used = 0; /* Search through tag data for a match */ while (used < jdata->jump_tag_total) { /* Check the next tag */ t = (const struct jump_tag *)(system_usable_ram_end() + used); used += sizeof(struct jump_tag) + t->data_size; if (t->tag != tag) continue; /* Found a match */ if (size) *size = t->data_size; if (version) *version = t->data_version; return (const uint8_t *)(t + 1); } /* If we're still here, no match */ return NULL; } void system_set_reset_cause(enum system_reset_cause_t cause) { reset_cause = cause; } void system_disable_jump(void) { disable_jump = 1; } const char *system_get_reset_cause_string(void) { static const char * const cause_descs[] = { "unknown", "other", "brownout", "power-on", "reset pin", "soft", "watchdog", "rtc alarm", "wake pin", "low battery"}; return reset_cause < ARRAY_SIZE(cause_descs) ? cause_descs[reset_cause] : "?"; } enum system_image_copy_t system_get_image_copy(void) { uint32_t my_addr = (uint32_t)system_get_image_copy - CONFIG_FLASH_BASE; if (my_addr >= CONFIG_SECTION_RO_OFF && my_addr < (CONFIG_SECTION_RO_OFF + CONFIG_SECTION_RO_SIZE)) return SYSTEM_IMAGE_RO; if (my_addr >= CONFIG_SECTION_A_OFF && my_addr < (CONFIG_SECTION_A_OFF + CONFIG_SECTION_A_SIZE)) return SYSTEM_IMAGE_RW_A; if (my_addr >= CONFIG_SECTION_B_OFF && my_addr < (CONFIG_SECTION_B_OFF + CONFIG_SECTION_B_SIZE)) return SYSTEM_IMAGE_RW_B; return SYSTEM_IMAGE_UNKNOWN; } /* Returns true if the given range is overlapped with the active image. * * We only care the runtime code since the EC is running over it. * We don't care about the vector table, FMAP, and init code. */ int system_unsafe_to_overwrite(uint32_t offset, uint32_t size) { uint32_t r_offset; uint32_t r_size; switch (system_get_image_copy()) { case SYSTEM_IMAGE_RO: r_offset = CONFIG_FW_RO_OFF; r_size = CONFIG_FW_RO_SIZE; break; case SYSTEM_IMAGE_RW_A: r_offset = CONFIG_FW_A_OFF; r_size = CONFIG_FW_A_SIZE; break; case SYSTEM_IMAGE_RW_B: r_offset = CONFIG_FW_B_OFF; r_size = CONFIG_FW_B_SIZE; break; default: return 0; } if ((offset >= r_offset && offset < (r_offset + r_size)) || (r_offset >= offset && r_offset < (offset + size))) return 1; else return 0; } const char *system_get_image_copy_string(void) { int copy = system_get_image_copy(); return copy < ARRAY_SIZE(image_names) ? image_names[copy] : "?"; } /* 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; #ifdef BOARD_link /* * Jumping to any image asserts the signal to the Silego chip that that * EC is not in read-only firmware. (This is not technically true if * jumping from RO -> RO, but that's not a meaningful use case...) */ gpio_set_level(GPIO_ENTERING_RW, 1); #endif /* Flush UART output unless the UART hasn't been initialized yet */ if (uart_init_done()) uart_flush_output(); /* Disable interrupts before jump */ interrupt_disable(); /* Fill in preserved data between jumps */ jdata->reserved0 = 0; jdata->magic = JUMP_DATA_MAGIC; jdata->version = JUMP_DATA_VERSION; jdata->reset_cause = reset_cause; jdata->jump_tag_total = 0; /* Reset tags */ jdata->struct_size = sizeof(struct jump_data); /* Call other hooks; these may add tags */ hook_notify(HOOK_SYSJUMP, 0); /* Jump to the reset vector */ resetvec(); } /* Return the base pointer for the image copy, or 0xffffffff if error. */ static uint32_t get_base(enum system_image_copy_t copy) { switch (copy) { case SYSTEM_IMAGE_RO: return CONFIG_FLASH_BASE + CONFIG_FW_RO_OFF; case SYSTEM_IMAGE_RW_A: return CONFIG_FLASH_BASE + CONFIG_FW_A_OFF; #ifdef CONFIG_RW_B case SYSTEM_IMAGE_RW_B: return CONFIG_FLASH_BASE + CONFIG_FW_B_OFF; #endif default: return 0xffffffff; } } /* Return the size of the image copy, or 0 if error. */ static uint32_t get_size(enum system_image_copy_t copy) { switch (copy) { case SYSTEM_IMAGE_RO: return CONFIG_FW_RO_SIZE; case SYSTEM_IMAGE_RW_A: return CONFIG_FW_A_SIZE; #ifdef CONFIG_RW_B case SYSTEM_IMAGE_RW_B: return CONFIG_FW_B_SIZE; #endif default: return 0; } } int system_run_image_copy(enum system_image_copy_t copy) { uint32_t base; uint32_t init_addr; /* If system is already running the requested image, done */ if (system_get_image_copy() == copy) return EC_SUCCESS; if (system_is_locked()) { /* System is locked, so disallow jumping between images unless * this is the initial jump from RO to RW code. */ /* Must currently be running the RO image */ if (system_get_image_copy() != SYSTEM_IMAGE_RO) return EC_ERROR_ACCESS_DENIED; /* Target image must be RW image */ if (copy != SYSTEM_IMAGE_RW_A && copy != SYSTEM_IMAGE_RW_B) return EC_ERROR_ACCESS_DENIED; /* Can't have already jumped between images */ if (jumped_to_image) return EC_ERROR_ACCESS_DENIED; /* Jumping must still be enabled */ if (disable_jump) return EC_ERROR_ACCESS_DENIED; } /* Load the appropriate reset vector */ base = get_base(copy); if (base == 0xffffffff) 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 + get_size(copy)) return EC_ERROR_UNKNOWN; CPRINTF("[%T Jumping to image %s]\n", image_names[copy]); jump_to_image(init_addr); /* Should never get here */ return EC_ERROR_UNKNOWN; } const char *system_get_version(enum system_image_copy_t copy) { uint32_t addr; const struct version_struct *v; /* Handle version of current image */ if (copy == system_get_image_copy() || copy == SYSTEM_IMAGE_UNKNOWN) return version_data.version; addr = get_base(copy); if (addr == 0xffffffff) return ""; /* The version string is always located after the reset vectors, so * it's the same as in the current image. */ addr += ((uint32_t)&version_data - get_base(system_get_image_copy())); /* Make sure the version struct cookies match before returning the * version string. */ v = (const struct version_struct *)addr; if (v->cookie1 == version_data.cookie1 && v->cookie2 == version_data.cookie2) return v->version; return ""; } int system_get_board_version(void) { int v = 0; #ifdef BOARD_link if (gpio_get_level(GPIO_BOARD_VERSION1)) v |= 0x01; if (gpio_get_level(GPIO_BOARD_VERSION2)) v |= 0x02; if (gpio_get_level(GPIO_BOARD_VERSION3)) v |= 0x04; #endif return v; } const char *system_get_build_info(void) { return build_info; } int system_common_pre_init(void) { /* Check jump data if this is a jump between images. Jumps all show up * as an unknown reset reason, because we jumped directly from one * image to another without actually triggering a chip reset. */ if (jdata->magic == JUMP_DATA_MAGIC && jdata->version >= 1 && reset_cause == SYSTEM_RESET_UNKNOWN) { int delta; /* Change in jump data struct size between the * previous image and this one. */ /* Yes, we jumped to this image */ jumped_to_image = 1; /* Overwrite the reset cause with the real one */ reset_cause = jdata->reset_cause; /* If the jump data structure isn't the same size as the * current one, shift the jump tags to immediately before the * current jump data structure, to make room for initalizing * the new fields below. */ if (jdata->version == 1) delta = 0; /* No tags in v1, so no need for move */ else if (jdata->version == 2) delta = sizeof(struct jump_data) - JUMP_DATA_SIZE_V2; else delta = sizeof(struct jump_data) - jdata->struct_size; if (delta && jdata->jump_tag_total) { uint8_t *d = (uint8_t *)system_usable_ram_end(); memmove(d, d + delta, jdata->jump_tag_total); } /* Initialize fields added after version 1 */ if (jdata->version < 2) jdata->jump_tag_total = 0; /* Initialize fields added after version 2 */ if (jdata->version < 3) jdata->reserved0 = 0; /* Struct size is now the current struct size */ jdata->struct_size = sizeof(struct jump_data); /* Clear the jump struct's magic number. This prevents * accidentally detecting a jump when there wasn't one, and * disallows use of system_add_jump_tag(). */ jdata->magic = 0; } else { /* Clear the whole jump_data struct */ memset(jdata, 0, sizeof(struct jump_data)); } return EC_SUCCESS; } /* Handle a pending reboot command */ static int handle_pending_reboot(enum ec_reboot_cmd cmd) { switch (cmd) { case EC_REBOOT_CANCEL: return EC_SUCCESS; case EC_REBOOT_JUMP_RO: return system_run_image_copy(SYSTEM_IMAGE_RO); case EC_REBOOT_JUMP_RW_A: return system_run_image_copy(SYSTEM_IMAGE_RW_A); case EC_REBOOT_JUMP_RW_B: return system_run_image_copy(SYSTEM_IMAGE_RW_B); case EC_REBOOT_COLD: system_reset(1); /* That shouldn't return... */ return EC_ERROR_UNKNOWN; case EC_REBOOT_DISABLE_JUMP: system_disable_jump(); return EC_SUCCESS; default: return EC_ERROR_INVAL; } } /*****************************************************************************/ /* Hooks */ static int system_common_shutdown(void) { return handle_pending_reboot(reboot_at_shutdown); } DECLARE_HOOK(HOOK_CHIPSET_SHUTDOWN, system_common_shutdown, HOOK_PRIO_DEFAULT); /*****************************************************************************/ /* Console commands */ static int command_sysinfo(int argc, char **argv) { ccprintf("Last reset: %d (%s)\n", system_get_reset_cause(), system_get_reset_cause_string()); ccprintf("Copy: %s\n", system_get_image_copy_string()); ccprintf("Jumped: %s\n", system_jumped_to_this_image() ? "yes" : "no"); ccputs("Flags: "); if (system_is_locked()) { ccputs(" locked"); if (force_locked) ccputs(" (forced)"); if (disable_jump) ccputs(" jump-disabled"); } else ccputs(" unlocked"); ccputs("\n"); return EC_SUCCESS; } DECLARE_CONSOLE_COMMAND(sysinfo, command_sysinfo, NULL, "Print system info", NULL); #define CONFIG_CONSOLE_CMD_SCRATCHPAD #ifdef CONFIG_CONSOLE_CMD_SCRATCHPAD static int command_scratchpad(int argc, char **argv) { int rv = EC_SUCCESS; if (argc == 2) { char *e; int s = strtoi(argv[1], &e, 0); if (*e) return EC_ERROR_PARAM1; rv = system_set_scratchpad(s); } ccprintf("Scratchpad: 0x%08x\n", system_get_scratchpad()); return rv; } DECLARE_CONSOLE_COMMAND(scratchpad, command_scratchpad, "[val]", "Get or set scratchpad value", NULL); #endif /* CONFIG_CONSOLE_CMD_SCRATCHPAD */ static int command_hibernate(int argc, char **argv) { int seconds; int microseconds = 0; if (argc < 2) return EC_ERROR_PARAM_COUNT; seconds = strtoi(argv[1], NULL, 0); if (argc >= 3) microseconds = strtoi(argv[2], NULL, 0); ccprintf("Hibernating for %d.%06d s\n", seconds, microseconds); cflush(); system_hibernate(seconds, microseconds); return EC_SUCCESS; } DECLARE_CONSOLE_COMMAND(hibernate, command_hibernate, "sec [usec]", "Hibernate the EC", NULL); static int command_version(int argc, char **argv) { ccprintf("Chip: %s %s %s\n", system_get_chip_vendor(), system_get_chip_name(), system_get_chip_revision()); ccprintf("Board: %d\n", system_get_board_version()); ccprintf("RO: %s\n", system_get_version(SYSTEM_IMAGE_RO)); ccprintf("RW-A: %s\n", system_get_version(SYSTEM_IMAGE_RW_A)); ccprintf("RW-B: %s\n", system_get_version(SYSTEM_IMAGE_RW_B)); ccprintf("Build: %s\n", system_get_build_info()); return EC_SUCCESS; } DECLARE_CONSOLE_COMMAND(version, command_version, NULL, "Print versions", NULL); static int command_sysjump(int argc, char **argv) { uint32_t addr; char *e; if (argc < 2) return EC_ERROR_PARAM_COUNT; /* Handle named images */ if (!strcasecmp(argv[1], "RO")) return system_run_image_copy(SYSTEM_IMAGE_RO); else if (!strcasecmp(argv[1], "A")) return system_run_image_copy(SYSTEM_IMAGE_RW_A); else if (!strcasecmp(argv[1], "B")) return system_run_image_copy(SYSTEM_IMAGE_RW_B); else if (!strcasecmp(argv[1], "disable")) { system_disable_jump(); return EC_SUCCESS; } /* Arbitrary jumps are only allowed on an unlocked system */ if (system_is_locked()) return EC_ERROR_ACCESS_DENIED; /* Check for arbitrary address */ addr = strtoi(argv[1], &e, 0); if (*e) return EC_ERROR_PARAM1; ccprintf("Jumping to 0x%08x\n", addr); cflush(); jump_to_image(addr); return EC_SUCCESS; } DECLARE_CONSOLE_COMMAND(sysjump, command_sysjump, "[RO | A | B | addr | disable]", "Jump to a system image or address", NULL); static int command_reboot(int argc, char **argv) { int is_hard = 0; if (argc == 2) { if (!strcasecmp(argv[1], "hard") || !strcasecmp(argv[1], "cold")) { ccputs("Hard-"); is_hard = 1; } else return EC_ERROR_PARAM1; } ccputs("Rebooting!\n\n\n"); cflush(); system_reset(is_hard); return EC_SUCCESS; } DECLARE_CONSOLE_COMMAND(reboot, command_reboot, "[hard]", "Reboot the EC", NULL); static int command_system_lock(int argc, char **argv) { force_locked = 1; return EC_SUCCESS; } DECLARE_CONSOLE_COMMAND(syslock, command_system_lock, NULL, "Lock the system, even if WP is disabled", NULL); /*****************************************************************************/ /* Host commands */ static int host_command_get_version(struct host_cmd_handler_args *args) { struct ec_response_get_version *r = (struct ec_response_get_version *)args->response; strzcpy(r->version_string_ro, system_get_version(SYSTEM_IMAGE_RO), sizeof(r->version_string_ro)); strzcpy(r->version_string_rw_a, system_get_version(SYSTEM_IMAGE_RW_A), sizeof(r->version_string_rw_a)); strzcpy(r->version_string_rw_b, system_get_version(SYSTEM_IMAGE_RW_B), sizeof(r->version_string_rw_b)); switch (system_get_image_copy()) { case SYSTEM_IMAGE_RO: r->current_image = EC_IMAGE_RO; break; case SYSTEM_IMAGE_RW_A: r->current_image = EC_IMAGE_RW_A; break; case SYSTEM_IMAGE_RW_B: r->current_image = EC_IMAGE_RW_B; break; default: r->current_image = EC_IMAGE_UNKNOWN; break; } args->response_size = sizeof(*r); return EC_RES_SUCCESS; } DECLARE_HOST_COMMAND(EC_CMD_GET_VERSION, host_command_get_version, EC_VER_MASK(0)); static int host_command_build_info(struct host_cmd_handler_args *args) { const char *info = system_get_build_info(); args->response = (uint8_t *)info; args->response_size = strlen(info) + 1; return EC_RES_SUCCESS; } DECLARE_HOST_COMMAND(EC_CMD_GET_BUILD_INFO, host_command_build_info, EC_VER_MASK(0)); static int host_command_get_chip_info(struct host_cmd_handler_args *args) { struct ec_response_get_chip_info *r = (struct ec_response_get_chip_info *)args->response; strzcpy(r->vendor, system_get_chip_vendor(), sizeof(r->vendor)); strzcpy(r->name, system_get_chip_name(), sizeof(r->name)); strzcpy(r->revision, system_get_chip_revision(), sizeof(r->revision)); args->response_size = sizeof(*r); return EC_RES_SUCCESS; } DECLARE_HOST_COMMAND(EC_CMD_GET_CHIP_INFO, host_command_get_chip_info, EC_VER_MASK(0)); int host_command_get_board_version(struct host_cmd_handler_args *args) { struct ec_response_board_version *r = (struct ec_response_board_version *)args->response; r->board_version = (uint16_t) system_get_board_version(); args->response_size = sizeof(*r); return EC_RES_SUCCESS; } DECLARE_HOST_COMMAND(EC_CMD_GET_BOARD_VERSION, host_command_get_board_version, EC_VER_MASK(0)); int host_command_reboot(struct host_cmd_handler_args *args) { struct ec_params_reboot_ec p; /* * Ensure reboot parameters don't get clobbered when the response * is sent in case data argument points to the host tx/rx buffer. */ memcpy(&p, args->params, sizeof(p)); if (p.cmd == EC_REBOOT_CANCEL) { /* Cancel pending reboot */ reboot_at_shutdown = EC_REBOOT_CANCEL; return EC_RES_SUCCESS; } else if (p.flags & EC_REBOOT_FLAG_ON_AP_SHUTDOWN) { /* Store request for processing at chipset shutdown */ reboot_at_shutdown = p.cmd; return EC_RES_SUCCESS; } /* TODO: (crosbug.com/p/9040) handle EC_REBOOT_FLAG_POWER_ON */ #ifdef CONFIG_TASK_HOSTCMD /* Clean busy bits on host */ host_send_response(EC_RES_SUCCESS); #endif CPUTS("[Executing host reboot command]\n"); switch (handle_pending_reboot(p.cmd)) { case EC_SUCCESS: return EC_RES_SUCCESS; case EC_ERROR_INVAL: return EC_RES_INVALID_PARAM; case EC_ERROR_ACCESS_DENIED: return EC_RES_ACCESS_DENIED; default: return EC_RES_ERROR; } } DECLARE_HOST_COMMAND(EC_CMD_REBOOT_EC, host_command_reboot, EC_VER_MASK(0));