| /* |
| * Copyright (C) 2015 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| /* Vboot/crossystem interface */ |
| |
| #define LOG_TAG "fwtool" |
| |
| #include <endian.h> |
| #include <errno.h> |
| #include <stdint.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <unistd.h> |
| |
| #include "ec_commands.h" |
| #include "flash_device.h" |
| #include "fmap.h" |
| #include "update_log.h" |
| #include "vboot_struct.h" |
| #include "gbb_header.h" |
| |
| /* ---- VBoot information passed by the firmware through the device-tree ---- */ |
| |
| /* Base name for firmware FDT files */ |
| #define FDT_BASE_PATH "/proc/device-tree/firmware/chromeos" |
| |
| char *fdt_read_string(const char *prop) |
| { |
| char filename[PATH_MAX]; |
| FILE *file; |
| size_t size; |
| char *data; |
| |
| snprintf(filename, sizeof(filename), FDT_BASE_PATH "/%s", prop); |
| file = fopen(filename, "r"); |
| if (!file) { |
| ALOGD("Unable to open FDT property %s\n", prop); |
| return NULL; |
| } |
| fseek(file, 0, SEEK_END); |
| size = ftell(file); |
| data = malloc(size + 1); |
| if (!data) |
| return NULL; |
| data[size] = '\0'; |
| |
| rewind(file); |
| if (fread(data, 1, size, file) != size) { |
| ALOGD("Unable to read FDT property %s\n", prop); |
| return NULL; |
| } |
| fclose(file); |
| |
| return data; |
| } |
| |
| uint32_t fdt_read_u32(const char *prop) |
| { |
| char filename[PATH_MAX]; |
| FILE *file; |
| int data = 0; |
| |
| snprintf(filename, sizeof(filename), FDT_BASE_PATH "/%s", prop); |
| file = fopen(filename, "r"); |
| if (!file) { |
| ALOGD("Unable to open FDT property %s\n", prop); |
| return -1U; |
| } |
| if (fread(&data, 1, sizeof(data), file) != sizeof(data)) { |
| ALOGD("Unable to read FDT property %s\n", prop); |
| return -1U; |
| } |
| fclose(file); |
| |
| return ntohl(data); /* FDT is network byte order */ |
| } |
| |
| char vboot_get_mainfw_act(void) |
| { |
| VbSharedDataHeader *shd = (void *)fdt_read_string("vboot-shared-data"); |
| char v; |
| |
| if (!shd || shd->magic != VB_SHARED_DATA_MAGIC) { |
| ALOGD("Cannot retrieve VBoot shared data\n"); |
| if (shd) |
| free(shd); |
| return 'E'; /* Error */ |
| } |
| |
| switch(shd->firmware_index) { |
| case 0: |
| v = 'A'; /* RW_A in use */ |
| break; |
| case 1: |
| v = 'B'; /* RW_B in use */ |
| break; |
| case 0xFF: |
| v = 'R'; /* Recovery/RO in use */ |
| break; |
| default: |
| ALOGD("Invalid firmware index : %02x\n", shd->firmware_index); |
| v = 'E'; /* Error */ |
| } |
| |
| free(shd); |
| return v; |
| } |
| |
| /* ---- Flash Maps handling ---- */ |
| |
| off_t fmap_scan_offset(struct flash_device *dev, off_t end) |
| { |
| struct fmap h; |
| uint32_t off = end - (end % 64); /* start on a 64-byte boundary */ |
| int res; |
| |
| /* |
| * Try to find the FMAP signature at 64-byte boundaries |
| * starting from the end. |
| */ |
| do { |
| off -= 64; |
| res = flash_read(dev, off, &h, sizeof(h.signature)); |
| if (res) |
| break; |
| if (!memcmp(&h.signature, FMAP_SIGNATURE, sizeof(h.signature))) |
| break; |
| } while (off); |
| |
| return off; |
| } |
| |
| struct fmap *fmap_load(struct flash_device *dev, off_t offset) |
| { |
| struct fmap hdr; |
| struct fmap *fmap; |
| size_t size; |
| int res; |
| |
| ALOGD("Searching FMAP @0x%08lx\n", offset); |
| res = flash_read(dev, offset, &hdr, sizeof(hdr)); |
| if (res) { |
| ALOGD("Cannot read FMAP header\n"); |
| return NULL; |
| } |
| |
| if (memcmp(&hdr.signature, FMAP_SIGNATURE, sizeof(hdr.signature))) { |
| ALOGD("Cannot find FMAP\n"); |
| return NULL; |
| } |
| |
| size = sizeof(struct fmap) + hdr.nareas * sizeof(struct fmap_area); |
| fmap = malloc(size); |
| |
| res = flash_read(dev, offset, fmap, size); |
| if (res) { |
| ALOGD("Cannot read FMAP\n"); |
| free(fmap); |
| return NULL; |
| } |
| |
| return fmap; |
| } |
| |
| int fmap_get_section_offset(struct flash_device *dev, const char *name, |
| off_t *offset) |
| { |
| int i; |
| struct fmap *fmap = flash_get_fmap(dev); |
| if (!fmap) |
| return -1; |
| |
| if (name) { |
| for (i = 0; i < fmap->nareas; i++) |
| if (!strcmp(name, (const char*)fmap->areas[i].name)) |
| break; |
| |
| if (i == fmap->nareas) { |
| ALOGD("Cannot find section '%s'\n", name); |
| return -1; |
| } |
| |
| *offset = fmap->areas[i].offset; |
| } else { |
| *offset = 0; |
| } |
| |
| return 0; |
| } |
| |
| void *fmap_read_section(struct flash_device *dev, |
| const char *name, size_t *size, off_t *offset) |
| { |
| int i, r; |
| struct fmap *fmap = flash_get_fmap(dev); |
| void *data; |
| off_t start_offset; |
| |
| if (!fmap) |
| return NULL; |
| |
| if (name) { |
| for (i = 0; i < fmap->nareas; i++) |
| if (!strcmp(name, (const char*)fmap->areas[i].name)) |
| break; |
| if (i == fmap->nareas) { |
| ALOGD("Cannot find section '%s'\n", name); |
| return NULL; |
| } |
| *size = fmap->areas[i].size; |
| start_offset = fmap->areas[i].offset; |
| } else { |
| *size = flash_get_size(dev); |
| start_offset = 0; |
| } |
| |
| data = malloc(*size); |
| if (!data) |
| return NULL; |
| |
| r = flash_read(dev, start_offset, data, *size); |
| if (r) { |
| ALOGD("Cannot read section '%s'\n", name); |
| free(data); |
| return NULL; |
| } |
| if (offset) |
| *offset = start_offset; |
| |
| return data; |
| } |
| |
| /* ---- Google Binary Block (GBB) ---- */ |
| |
| uint8_t *gbb_get_rootkey(struct flash_device *dev, size_t *size) |
| { |
| size_t gbb_size; |
| uint8_t *gbb = flash_get_gbb(dev, &gbb_size); |
| GoogleBinaryBlockHeader *hdr = (void *)gbb; |
| |
| if (!gbb || memcmp(hdr->signature, GBB_SIGNATURE, GBB_SIGNATURE_SIZE) || |
| gbb_size < sizeof(*hdr)) |
| return NULL; |
| |
| if (hdr->rootkey_offset + hdr->rootkey_size > gbb_size) |
| return NULL; |
| |
| if (size) |
| *size = hdr->rootkey_size; |
| |
| return gbb + hdr->rootkey_offset; |
| } |
| |
| /* ---- VBoot NVRAM (stored in SPI flash) ---- */ |
| |
| /* bits definition in NVRAM */ |
| |
| enum { |
| VB_HEADER_OFFSET = 0, |
| VB_BOOT_OFFSET = 1, |
| VB_RECOVERY_OFFSET = 2, |
| VB_LOCALIZATION_OFFSET = 3, |
| VB_DEV_OFFSET = 4, |
| VB_TPM_OFFSET = 5, |
| VB_RECVOERY_SUBCODE_OFFSET = 6, |
| VB_BOOT2_OFFSET = 7, |
| VB_MISC_OFFSET = 8, |
| VB_KERNEL_OFFSET = 11, |
| VB_CRC_OFFSET = 15, |
| VB_NVDATA_SIZE = 16 |
| }; |
| |
| #define VB_DEFAULT_MASK 0x01 |
| |
| /* HEADER_OFFSET */ |
| #define VB_HEADER_WIPEOUT_SHIFT 3 |
| #define VB_HEADER_KERNEL_SETTINGS_RESET_SHIFT 4 |
| #define VB_HEADER_FW_SETTINGS_RESET_SHIFT 5 |
| #define VB_HEADER_SIGNATURE_SHIFT 6 |
| |
| /* BOOT_OFFSET */ |
| #define VB_BOOT_TRY_COUNT_MASK 0xf |
| #define VB_BOOT_TRY_COUNT_SHIFT 0 |
| #define VB_BOOT_BACKUP_NVRAM_SHIFT 4 |
| #define VB_BOOT_OPROM_NEEDED_SHIFT 5 |
| #define VB_BOOT_DISABLE_DEV_SHIFT 6 |
| #define VB_BOOT_DEBUG_RESET_SHIFT 7 |
| |
| /* RECOVERY_OFFSET */ |
| #define VB_RECOVERY_REASON_SHIFT 0 |
| #define VB_RECOVERY_REASON_MASK 0xff |
| |
| /* BOOT2_OFFSET */ |
| #define VB_BOOT2_RESULT_MASK 0x3 |
| #define VB_BOOT2_RESULT_SHIFT 0 |
| #define VB_BOOT2_TRIED_SHIFT 2 |
| #define VB_BOOT2_TRY_NEXT_SHIFT 3 |
| #define VB_BOOT2_PREV_RESULT_MASK 0x3 |
| #define VB_BOOT2_PREV_RESULT_SHIFT 4 |
| #define VB_BOOT2_PREV_TRIED_SHIFT 6 |
| |
| /* DEV_OFFSET */ |
| #define VB_DEV_FLAG_USB_SHIFT 0 |
| #define VB_DEV_FLAG_SIGNED_ONLY_SHIFT 1 |
| #define VB_DEV_FLAG_LEGACY_SHIFT 2 |
| #define VB_DEV_FLAG_FASTBOOT_FULL_CAP_SHIFT 3 |
| |
| /* TPM_OFFSET */ |
| #define VB_TPM_CLEAR_OWNER_REQUEST_SHIFT 0 |
| #define VB_TPM_CLEAR_OWNER_DONE_SHIFT 1 |
| |
| /* MISC_OFFSET */ |
| #define VB_MISC_UNLOCK_FASTBOOT_SHIFT 0 |
| #define VB_MISC_BOOT_ON_AC_DETECT_SHIFT 1 |
| |
| typedef enum { |
| VBNV_DEFAULT_FLAG = 0x00, |
| VBNV_WRITABLE = 0x01, |
| } vbnv_param_flags_t; |
| |
| typedef struct vbnv_param { |
| char *name; |
| vbnv_param_flags_t flags; |
| int offset; |
| int shift; |
| int mask; |
| } vbnv_param_t; |
| |
| static const vbnv_param_t param_table[] = { |
| {"try_count", VBNV_WRITABLE, VB_BOOT_OFFSET, VB_BOOT_TRY_COUNT_SHIFT, |
| VB_BOOT_TRY_COUNT_MASK}, |
| {"backup_nvram", VBNV_WRITABLE, VB_BOOT_OFFSET, |
| VB_BOOT_BACKUP_NVRAM_SHIFT, VB_DEFAULT_MASK}, |
| {"oprom_needed", VBNV_WRITABLE, VB_BOOT_OFFSET, |
| VB_BOOT_OPROM_NEEDED_SHIFT, VB_DEFAULT_MASK}, |
| {"disable_dev", VBNV_WRITABLE, VB_BOOT_OFFSET, |
| VB_BOOT_DISABLE_DEV_SHIFT, VB_DEFAULT_MASK}, |
| {"debug_reset", VBNV_WRITABLE, VB_BOOT_OFFSET, |
| VB_BOOT_DEBUG_RESET_SHIFT, VB_DEFAULT_MASK}, |
| {"boot_result", VBNV_WRITABLE, VB_BOOT2_OFFSET, VB_BOOT2_RESULT_SHIFT, |
| VB_BOOT2_RESULT_MASK}, |
| {"fw_tried", VBNV_DEFAULT_FLAG, VB_BOOT2_OFFSET, VB_BOOT2_TRIED_SHIFT, |
| VB_DEFAULT_MASK}, |
| {"fw_try_next", VBNV_WRITABLE, VB_BOOT2_OFFSET, VB_BOOT2_TRY_NEXT_SHIFT, |
| VB_DEFAULT_MASK}, |
| {"fw_prev_result", VBNV_DEFAULT_FLAG, VB_BOOT2_OFFSET, |
| VB_BOOT2_PREV_RESULT_SHIFT, VB_BOOT2_PREV_RESULT_MASK}, |
| {"prev_tried", VBNV_DEFAULT_FLAG, VB_BOOT2_OFFSET, |
| VB_BOOT2_PREV_TRIED_SHIFT, VB_DEFAULT_MASK}, |
| {"dev_boot_usb", VBNV_WRITABLE, VB_DEV_OFFSET, VB_DEV_FLAG_USB_SHIFT, |
| VB_DEFAULT_MASK}, |
| {"dev_boot_signed_only", VBNV_WRITABLE, VB_DEV_OFFSET, |
| VB_DEV_FLAG_SIGNED_ONLY_SHIFT, VB_DEFAULT_MASK}, |
| {"dev_boot_legacy", VBNV_WRITABLE, VB_DEV_OFFSET, |
| VB_DEV_FLAG_LEGACY_SHIFT, VB_DEFAULT_MASK}, |
| {"dev_boot_fastboot_full_cap", VBNV_WRITABLE, VB_DEV_OFFSET, |
| VB_DEV_FLAG_FASTBOOT_FULL_CAP_SHIFT, VB_DEFAULT_MASK}, |
| {"tpm_clear_owner_request", VBNV_WRITABLE, VB_TPM_OFFSET, |
| VB_TPM_CLEAR_OWNER_REQUEST_SHIFT, VB_DEFAULT_MASK}, |
| {"tpm_clear_owner_done", VBNV_WRITABLE, VB_TPM_OFFSET, |
| VB_TPM_CLEAR_OWNER_DONE_SHIFT, VB_DEFAULT_MASK}, |
| {"unlock_fastboot", VBNV_WRITABLE, VB_MISC_OFFSET, |
| VB_MISC_UNLOCK_FASTBOOT_SHIFT, VB_DEFAULT_MASK}, |
| {"boot_on_ac_detect", VBNV_WRITABLE, VB_MISC_OFFSET, |
| VB_MISC_BOOT_ON_AC_DETECT_SHIFT, VB_DEFAULT_MASK}, |
| {"recovery_reason", VBNV_WRITABLE, VB_RECOVERY_OFFSET, |
| VB_RECOVERY_REASON_SHIFT, VB_RECOVERY_REASON_MASK}, |
| }; |
| |
| static uint8_t crc8(const uint8_t *data, int len) |
| { |
| uint32_t crc = 0; |
| int i, j; |
| |
| for (j = len; j; j--, data++) { |
| crc ^= (*data << 8); |
| for(i = 8; i; i--) { |
| if (crc & 0x8000) |
| crc ^= (0x1070 << 3); |
| crc <<= 1; |
| } |
| } |
| |
| return (uint8_t)(crc >> 8); |
| } |
| |
| static inline int can_overwrite(uint8_t current, uint8_t new) |
| { |
| return (current & new) == new; |
| } |
| |
| int vbnv_readwrite(struct flash_device *spi, const vbnv_param_t *param, |
| uint8_t *value, int write) |
| { |
| int i; |
| int res; |
| size_t size; |
| off_t offset; |
| uint8_t *block, *nvram, *end, *curr; |
| uint8_t dummy[VB_NVDATA_SIZE]; |
| |
| int off = param->offset; |
| uint8_t mask = param->mask << param->shift; |
| |
| if (off >= VB_NVDATA_SIZE) { |
| ALOGW("ERROR: Incorrect offset %d for NvStorage\n", off); |
| return -EIO; |
| } |
| |
| /* Read NVRAM. */ |
| nvram = fmap_read_section(spi, "RW_NVRAM", &size, &offset); |
| /* |
| * Ensure NVRAM is found, size is at least 1 block and total size is |
| * multiple of VB_NVDATA_SIZE. |
| */ |
| if ((nvram == NULL) || (size < VB_NVDATA_SIZE) || |
| (size % VB_NVDATA_SIZE)) { |
| ALOGW("ERROR: NVRAM not found\n"); |
| return -EIO; |
| } |
| |
| /* Create an empty dummy block to compare. */ |
| memset(dummy, 0xFF, sizeof(dummy)); |
| |
| /* |
| * Loop until the last used block in NVRAM. |
| * 1. All blocks will not be empty since we just booted up fine. |
| * 2. If all blocks are used, select the last block. |
| */ |
| block = nvram; |
| end = block + size; |
| for (curr = block; curr < end; curr += VB_NVDATA_SIZE) { |
| if (memcmp(curr, dummy, VB_NVDATA_SIZE) == 0) |
| break; |
| block = curr; |
| } |
| |
| if (write) { |
| uint8_t flag_value = (*value & param->mask) << param->shift; |
| |
| /* Copy last used block to make modifications. */ |
| memcpy(dummy, block, VB_NVDATA_SIZE); |
| |
| dummy[off] = (dummy[off] & ~mask) | (flag_value & mask); |
| dummy[VB_CRC_OFFSET] = crc8(dummy, VB_CRC_OFFSET); |
| |
| /* Check if new block can be overwritten */ |
| for (i = 0; i < VB_NVDATA_SIZE; i++) { |
| if (!can_overwrite(block[i], dummy[i])) { |
| if (curr != end) |
| offset += (curr - nvram); |
| else if (flash_erase(spi, offset, size)) { |
| ALOGW("ERROR: Cannot erase flash\n"); |
| return -EIO; |
| } |
| break; |
| } |
| } |
| |
| /* Block can be overwritten. */ |
| if (i == VB_NVDATA_SIZE) |
| offset += (block - nvram); |
| |
| ALOGI("Writing new entry into NVRAM @ 0x%lx\n", offset); |
| |
| /* Write new entry into NVRAM. */ |
| if (flash_write(spi, offset, dummy, VB_NVDATA_SIZE)) { |
| ALOGW("ERROR: Cannot update NVRAM\n"); |
| return -EIO; |
| } |
| |
| ALOGD("NVRAM updated.\n"); |
| } else { |
| *value = (block[off] & mask) >> param->shift; |
| } |
| |
| return 0; |
| } |
| |
| #define ARRAY_SIZE(arr) (sizeof(arr)/sizeof(arr[0])) |
| int vbnv_set_flag(struct flash_device *spi, const char *param, uint8_t value) |
| { |
| size_t i; |
| for (i = 0; i < ARRAY_SIZE(param_table); i++) { |
| if (!strcmp(param, param_table[i].name)) { |
| if (param_table[i].flags & VBNV_WRITABLE) |
| return vbnv_readwrite(spi, ¶m_table[i], |
| &value, 1); |
| |
| fprintf(stderr, "ERROR: Cannot write this flag.\n"); |
| return -EIO; |
| } |
| } |
| fprintf(stderr, "Error: Unknown param\n"); |
| return -EIO; |
| } |
| |
| int vbnv_get_flag(struct flash_device *spi, const char *param, uint8_t *value) |
| { |
| size_t i; |
| for (i = 0; i < ARRAY_SIZE(param_table); i++) { |
| if (!strcmp(param, param_table[i].name)) |
| return vbnv_readwrite(spi, ¶m_table[i], value, 0); |
| } |
| fprintf(stderr, "Error: Unknown param\n"); |
| return -EIO; |
| } |
| |
| void vbnv_usage(int write) |
| { |
| size_t i; |
| for (i = 0; i < ARRAY_SIZE(param_table); i++) |
| if ((write == 0) || (write && |
| (param_table[i].flags & VBNV_WRITABLE))) |
| printf(" %s\n", param_table[i].name); |
| } |
| |
| /* ---- Vital Product Data handling ---- */ |