| /* |
| * 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. |
| */ |
| /* Firmware update flow */ |
| |
| #define LOG_TAG "fwtool" |
| |
| #include "errno.h" |
| #include <stdint.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <unistd.h> |
| |
| #include "edify/expr.h" |
| #include "flash_device.h" |
| #include "update_log.h" |
| #include "vboot_interface.h" |
| |
| int check_compatible_keys(struct flash_device *img, struct flash_device *spi) |
| { |
| size_t img_size = 0, spi_size = 0; |
| uint8_t *img_rootkey = gbb_get_rootkey(img, &img_size); |
| uint8_t *spi_rootkey = gbb_get_rootkey(spi, &spi_size); |
| |
| if (!img_rootkey || !spi_rootkey || img_size != spi_size) { |
| ALOGD("Invalid root key SPI %zd IMG %zd\n", spi_size, img_size); |
| return 0; |
| } |
| |
| if (memcmp(img_rootkey, spi_rootkey, img_size)) { |
| ALOGD("Incompatible root keys\n"); |
| return 0; |
| } |
| |
| /* TODO: check RW signature and TPM compatibility */ |
| |
| return 1; |
| } |
| |
| static int update_partition(struct flash_device *src, struct flash_device *dst, |
| const char *name) |
| { |
| int res; |
| void *content; |
| size_t size; |
| off_t offset; |
| const char *display_name = name ? name : "<flash>"; |
| |
| content = fmap_read_section(src, name, &size, &offset); |
| if (!content) { |
| ALOGW("Cannot read firmware image partition %s\n", |
| display_name); |
| return -EIO; |
| } |
| ALOGD("Erasing partition '%s' ...\n", display_name); |
| res = flash_erase(dst, offset, size); |
| if (res) { |
| ALOGW("Cannot erase flash\n"); |
| goto out_free; |
| } |
| ALOGD("Writing partition '%s' ...\n", display_name); |
| res = flash_write(dst, offset, content, size); |
| if (res) |
| ALOGW("Cannot write flash\n"); |
| |
| out_free: |
| free(content); |
| return 0; |
| } |
| |
| static int update_recovery_fw(struct flash_device *spi, struct flash_device *ec, |
| struct flash_device *img, Value *ec_file) |
| { |
| int res, ra, rb, rs; |
| int wp = 1; /* TODO: read SPI read-write */ |
| |
| if (wp) { /* Update only RW */ |
| ALOGD("RW Recovery\n"); |
| |
| if (!check_compatible_keys(img, spi)) |
| return -EINVAL; |
| |
| ra = update_partition(img, spi, "RW_SECTION_A"); |
| rb = update_partition(img, spi, "RW_SECTION_B"); |
| rs = update_partition(img, spi, "RW_SHARED"); |
| res = ra || rb || rs ? -EIO : 0; |
| } else { /* Update both RO & RW on SPI + EC */ |
| ALOGD("RO+RW Recovery\n"); |
| // TODO Preserve VPD + GBB |
| // TODO write full SPI flash with "img" |
| // TODO Update EC with ec_file |
| (void)ec; |
| (void)ec_file; |
| res = -ENOENT; |
| } |
| |
| /* Go back to a sane state for the firmware update */ |
| //VBNV: fwupdate_tries = 0; |
| |
| return res; |
| } |
| |
| static int update_rw_fw(struct flash_device *spi, struct flash_device *img, |
| char cur_part) |
| { |
| int res; |
| /* Update part A if we are running on B, write B in all other cases */ |
| const char *rw_name = cur_part == 'B' ? "RW_SECTION_A" : "RW_SECTION_B"; |
| int try_next = cur_part == 'B' ? 0 : 1; |
| |
| ALOGD("RW Update of firmware '%s'\n", rw_name); |
| |
| if (!check_compatible_keys(img, spi)) |
| return -EINVAL; |
| |
| res = update_partition(img, spi, rw_name); |
| if (!res) { |
| /* We have updated the SPI flash */ |
| vbnv_set_flag(spi, "fw_try_next", try_next); |
| vbnv_set_flag(spi, "try_count", 6); |
| } |
| |
| return res; |
| } |
| |
| /* |
| * Provide RO and RW updates until RO FW is changing in dogfood. |
| * TODO (Change this to only RW update and call update_rw_fw instead.) |
| */ |
| static int update_ap_fw(struct flash_device *spi, struct flash_device *img) |
| { |
| int res = -EINVAL; |
| |
| /* |
| * Save serial number. VPD changed in fmap. Dogfooders need serial |
| * number for future OTAs. |
| */ |
| size_t rovpd_sz, new_rovpd_sz; |
| off_t rovpd_off, new_rovpd_off; |
| void *rovpd = fmap_read_section(spi, "RO_VPD", &rovpd_sz, &rovpd_off); |
| void *newvpd = fmap_read_section(img, "RO_VPD", &new_rovpd_sz, |
| &new_rovpd_off); |
| |
| res = update_partition(img, spi, NULL); |
| |
| res = flash_erase(spi, new_rovpd_off, new_rovpd_sz); |
| if (res) |
| return res; |
| |
| res = flash_write(spi, new_rovpd_off, rovpd, new_rovpd_sz); |
| if (res) |
| return res; |
| |
| return res; |
| } |
| |
| int update_fw(Value *fw_file, Value *ec_file, int force) |
| { |
| int res = -EINVAL; |
| struct flash_device *img, *spi, *ec; |
| size_t size; |
| char *fwid; |
| char cur_part = vboot_get_mainfw_act(); |
| char *version = fdt_read_string("firmware-version"); |
| if (!version) { |
| ALOGW("Cannot read firmware version from FDT\n"); |
| return -EIO; |
| } |
| ALOGD("Running firmware: %s / partition %c\n", version, cur_part); |
| |
| img = flash_open("file", fw_file); |
| if (!img) |
| goto out_free; |
| fwid = fmap_read_section(img, "RW_FWID_A", &size, NULL); |
| |
| if (!fwid) { |
| ALOGD("Cannot find firmware image version\n"); |
| goto out_close_img; |
| } |
| |
| /* TODO: force update if keyblock does not match */ |
| if (!strncmp(version, fwid, size) && !force) { |
| ALOGI("Firmware already up-to-date: %s\n", version); |
| free(fwid); |
| res = 0; |
| goto out_close_img; |
| } |
| free(fwid); |
| |
| ec = flash_open("ec", NULL); |
| if (!ec) |
| goto out_close_img; |
| spi = flash_open("spi", NULL); |
| if (!spi) |
| goto out_close_ec; |
| |
| if (0) |
| res = update_ap_fw(spi, img); |
| |
| if (cur_part == 'R') /* Recovery mode */ |
| res = update_recovery_fw(spi, ec, img, ec_file); |
| else /* Normal mode */ |
| res = update_rw_fw(spi, img, cur_part); |
| |
| if (!res) /* successful update : record it */ |
| res = 1; |
| |
| flash_close(spi); |
| out_close_ec: |
| flash_close(ec); |
| out_close_img: |
| flash_close(img); |
| |
| out_free: |
| free(version); |
| return res; |
| } |
| |