| /** |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you 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. |
| */ |
| |
| #include <assert.h> |
| #include <stddef.h> |
| #include <inttypes.h> |
| #include <string.h> |
| #include <hal/flash_map.h> |
| #include <hal/hal_flash.h> |
| #include <os/os_malloc.h> |
| #include "bootutil/loader.h" |
| #include "bootutil/image.h" |
| #include "bootutil/bootutil_misc.h" |
| #include "bootutil_priv.h" |
| |
| /** Number of image slots in flash; currently limited to two. */ |
| #define BOOT_NUM_SLOTS 2 |
| |
| /** The request object provided by the client. */ |
| static const struct boot_req *boot_req; |
| |
| /** Image headers read from flash. */ |
| struct image_header boot_img_hdrs[2]; |
| |
| static struct boot_status boot_state; |
| |
| #define BOOT_PERSIST(idx, st) (((idx) << 8) | (0xff & (st))) |
| #define BOOT_PERSIST_IDX(st) (((st) >> 8) & 0xffffff) |
| #define BOOT_PERSIST_ST(st) ((st) & 0xff) |
| |
| /** |
| * Calculates the flash offset of the specified image slot. |
| * |
| * @param slot_num The number of the slot to calculate. |
| * |
| * @return The flash offset of the image slot. |
| */ |
| static void |
| boot_slot_addr(int slot_num, uint8_t *flash_id, uint32_t *address) |
| { |
| const struct flash_area *area_desc; |
| uint8_t area_idx; |
| |
| assert(slot_num >= 0 && slot_num < BOOT_NUM_SLOTS); |
| |
| area_idx = boot_req->br_slot_areas[slot_num]; |
| area_desc = boot_req->br_area_descs + area_idx; |
| *flash_id = area_desc->fa_flash_id; |
| *address = area_desc->fa_off; |
| } |
| |
| /** |
| * Searches flash for an image with the specified version number. |
| * |
| * @param ver The version number to search for. |
| * |
| * @return The image slot containing the specified image |
| * on success; -1 on failure. |
| */ |
| static int |
| boot_find_image_slot(const struct image_version *ver) |
| { |
| int i; |
| |
| for (i = 0; i < 2; i++) { |
| if (memcmp(&boot_img_hdrs[i].ih_ver, ver, sizeof *ver) == 0) { |
| return i; |
| } |
| } |
| |
| return -1; |
| } |
| |
| /** |
| * Selects a slot number to boot from, based on the contents of the boot |
| * vector. |
| * |
| * @return The slot number to boot from on success; |
| * -1 if an appropriate slot could not be |
| * determined. |
| */ |
| static int |
| boot_select_image_slot(void) |
| { |
| struct image_version ver; |
| int slot; |
| int rc; |
| |
| rc = boot_vect_read_test(&ver); |
| if (rc == 0) { |
| slot = boot_find_image_slot(&ver); |
| if (slot == -1) { |
| boot_vect_write_test(NULL); |
| } else { |
| return slot; |
| } |
| } |
| |
| rc = boot_vect_read_main(&ver); |
| if (rc == 0) { |
| slot = boot_find_image_slot(&ver); |
| if (slot == -1) { |
| boot_vect_write_main(NULL); |
| } else { |
| return slot; |
| } |
| } |
| |
| return -1; |
| } |
| |
| /* |
| * Validate image hash/signature in a slot. |
| */ |
| static int |
| boot_image_check(struct image_header *hdr, struct boot_image_location *loc) |
| { |
| static void *tmpbuf; |
| |
| if (!tmpbuf) { |
| tmpbuf = malloc(BOOT_TMPBUF_SZ); |
| if (!tmpbuf) { |
| return BOOT_ENOMEM; |
| } |
| } |
| if (bootutil_img_validate(hdr, loc->bil_flash_id, loc->bil_address, |
| tmpbuf, BOOT_TMPBUF_SZ)) { |
| return BOOT_EBADIMAGE; |
| } |
| return 0; |
| } |
| |
| /* |
| * How many sectors starting from sector[idx] can fit inside scratch. |
| * |
| */ |
| static uint32_t |
| boot_copy_sz(int idx, int max_idx, int *cnt) |
| { |
| int i; |
| uint32_t sz; |
| static uint32_t scratch_sz = 0; |
| |
| if (!scratch_sz) { |
| for (i = boot_req->br_scratch_area_idx; |
| i < boot_req->br_num_image_areas; |
| i++) { |
| scratch_sz += boot_req->br_area_descs[i].fa_size; |
| } |
| } |
| sz = 0; |
| *cnt = 0; |
| for (i = idx; i < max_idx; i++) { |
| if (sz + boot_req->br_area_descs[i].fa_size > scratch_sz) { |
| break; |
| } |
| sz += boot_req->br_area_descs[i].fa_size; |
| *cnt = *cnt + 1; |
| } |
| return sz; |
| } |
| |
| |
| static int |
| boot_erase_area(int area_idx, uint32_t sz) |
| { |
| const struct flash_area *area_desc; |
| int rc; |
| |
| area_desc = boot_req->br_area_descs + area_idx; |
| rc = hal_flash_erase(area_desc->fa_flash_id, area_desc->fa_off, sz); |
| if (rc != 0) { |
| return BOOT_EFLASH; |
| } |
| return 0; |
| } |
| |
| /** |
| * Copies the contents of one area to another. The destination area must |
| * be erased prior to this function being called. |
| * |
| * @param from_area_idx The index of the source area. |
| * @param to_area_idx The index of the destination area. |
| * @param sz The number of bytes to move. |
| * |
| * @return 0 on success; nonzero on failure. |
| */ |
| static int |
| boot_copy_area(int from_area_idx, int to_area_idx, uint32_t sz) |
| { |
| const struct flash_area *from_area_desc; |
| const struct flash_area *to_area_desc; |
| uint32_t from_addr; |
| uint32_t to_addr; |
| uint32_t off; |
| int chunk_sz; |
| int rc; |
| |
| static uint8_t buf[1024]; |
| |
| from_area_desc = boot_req->br_area_descs + from_area_idx; |
| to_area_desc = boot_req->br_area_descs + to_area_idx; |
| |
| assert(to_area_desc->fa_size >= from_area_desc->fa_size); |
| |
| off = 0; |
| while (off < sz) { |
| if (sz - off > sizeof buf) { |
| chunk_sz = sizeof buf; |
| } else { |
| chunk_sz = sz - off; |
| } |
| |
| from_addr = from_area_desc->fa_off + off; |
| rc = hal_flash_read(from_area_desc->fa_flash_id, from_addr, buf, |
| chunk_sz); |
| if (rc != 0) { |
| return rc; |
| } |
| |
| to_addr = to_area_desc->fa_off + off; |
| rc = hal_flash_write(to_area_desc->fa_flash_id, to_addr, buf, |
| chunk_sz); |
| if (rc != 0) { |
| return rc; |
| } |
| |
| off += chunk_sz; |
| } |
| |
| return 0; |
| } |
| |
| /** |
| * Swaps the contents of two flash areas. |
| * |
| * @param area_idx_1 The index of one area to swap. This area |
| * must be part of the first image slot. |
| * @param area_idx_2 The index of the other area to swap. This |
| * area must be part of the second image |
| * slot. |
| * @return 0 on success; nonzero on failure. |
| */ |
| static int |
| boot_swap_areas(int idx, uint32_t sz) |
| { |
| int area_idx_1; |
| int area_idx_2; |
| int rc; |
| int state; |
| |
| area_idx_1 = boot_req->br_slot_areas[0] + idx; |
| area_idx_2 = boot_req->br_slot_areas[1] + idx; |
| assert(area_idx_1 != area_idx_2); |
| assert(area_idx_1 != boot_req->br_scratch_area_idx); |
| assert(area_idx_2 != boot_req->br_scratch_area_idx); |
| |
| state = BOOT_PERSIST_ST(boot_state.state); |
| if (state == 0) { |
| rc = boot_erase_area(boot_req->br_scratch_area_idx, sz); |
| if (rc != 0) { |
| return rc; |
| } |
| |
| rc = boot_copy_area(area_idx_2, boot_req->br_scratch_area_idx, sz); |
| if (rc != 0) { |
| return rc; |
| } |
| |
| boot_state.state = BOOT_PERSIST(idx, 1); |
| (void)boot_write_status(&boot_state); |
| state = 1; |
| } |
| if (state == 1) { |
| rc = boot_erase_area(area_idx_2, sz); |
| if (rc != 0) { |
| return rc; |
| } |
| |
| rc = boot_copy_area(area_idx_1, area_idx_2, sz); |
| if (rc != 0) { |
| return rc; |
| } |
| |
| boot_state.state = BOOT_PERSIST(idx, 2); |
| (void)boot_write_status(&boot_state); |
| state = 2; |
| } |
| if (state == 2) { |
| rc = boot_erase_area(area_idx_1, sz); |
| if (rc != 0) { |
| return rc; |
| } |
| |
| rc = boot_copy_area(boot_req->br_scratch_area_idx, area_idx_1, sz); |
| if (rc != 0) { |
| return rc; |
| } |
| |
| boot_state.state = BOOT_PERSIST(idx + 1, 0); |
| (void)boot_write_status(&boot_state); |
| state = 3; |
| } |
| return 0; |
| } |
| |
| /** |
| * Swaps the two images in flash. If a prior copy operation was interrupted |
| * by a system reset, this function completes that operation. |
| * |
| * @param img1_length The length, in bytes, of the slot 1 image. |
| * @param img2_length The length, in bytes, of the slot 2 image. |
| * |
| * @return 0 on success; nonzero on failure. |
| */ |
| static int |
| boot_copy_image(void) |
| { |
| uint32_t off; |
| uint32_t sz; |
| int i; |
| int cnt; |
| int rc; |
| int state_idx; |
| |
| state_idx = BOOT_PERSIST_IDX(boot_state.state); |
| for (off = 0, i = 0; off < boot_state.length; off += sz, i += cnt) { |
| sz = boot_copy_sz(i, boot_req->br_slot_areas[1], &cnt); |
| if (i >= state_idx) { |
| rc = boot_swap_areas(i, sz); |
| assert(rc == 0); |
| } |
| } |
| |
| return 0; |
| } |
| |
| /** |
| * Builds a default boot status corresponding to all images being fully present |
| * in their slots. This function is used when a boot status is not present in |
| * flash (i.e., in the usual case when the previous boot operation ran to |
| * completion). |
| */ |
| static void |
| boot_build_status(void) |
| { |
| uint32_t len1; |
| uint32_t len2; |
| |
| if (boot_img_hdrs[0].ih_magic == IMAGE_MAGIC) { |
| len1 = boot_img_hdrs[0].ih_hdr_size + boot_img_hdrs[0].ih_img_size + |
| boot_img_hdrs[0].ih_tlv_size; |
| } else { |
| len1 = 0; |
| } |
| |
| if (boot_img_hdrs[1].ih_magic == IMAGE_MAGIC) { |
| len2 = boot_img_hdrs[1].ih_hdr_size + boot_img_hdrs[1].ih_img_size + |
| boot_img_hdrs[0].ih_tlv_size; |
| } else { |
| len2 = 0; |
| } |
| boot_state.length = len1; |
| if (len1 < len2) { |
| boot_state.length = len2; |
| } |
| boot_state.state = 0; |
| } |
| |
| /** |
| * Prepares the booting process. Based on the information provided in the |
| * request object, this function moves images around in flash as appropriate, |
| * and tells you what address to boot from. |
| * |
| * @param req Contains information about the flash layout. |
| * @param rsp On success, indicates how booting should occur. |
| * |
| * @return 0 on success; nonzero on failure. |
| */ |
| int |
| boot_go(const struct boot_req *req, struct boot_rsp *rsp) |
| { |
| struct boot_image_location image_addrs[BOOT_NUM_SLOTS]; |
| int slot; |
| int rc; |
| int i; |
| |
| /* Set the global boot request object. The remainder of the boot process |
| * will reference the global. |
| */ |
| boot_req = req; |
| |
| /* Read the boot status to determine if an image copy operation was |
| * interrupted (i.e., the system was reset before the boot loader could |
| * finish its task last time). |
| */ |
| if (boot_read_status(&boot_state)) { |
| /* We are resuming an interrupted image copy. */ |
| /* XXX if copy has not actually started yet, validate image */ |
| rc = boot_copy_image(); |
| if (rc != 0) { |
| /* We failed to put the images back together; there is really no |
| * solution here. |
| */ |
| return rc; |
| } |
| } |
| |
| /* Cache the flash address of each image slot. */ |
| for (i = 0; i < BOOT_NUM_SLOTS; i++) { |
| boot_slot_addr(i, &image_addrs[i].bil_flash_id, |
| &image_addrs[i].bil_address); |
| } |
| |
| /* Attempt to read an image header from each slot. */ |
| boot_read_image_headers(boot_img_hdrs, image_addrs, BOOT_NUM_SLOTS); |
| |
| /* Build a boot status structure indicating the flash location of each |
| * image part. This structure will need to be used if an image copy |
| * operation is required. |
| */ |
| boot_build_status(); |
| |
| /* Determine which image the user wants to run, and where it is located. */ |
| slot = boot_select_image_slot(); |
| if (slot == -1) { |
| /* Either there is no image vector, or none of the requested images are |
| * present. Just try booting from the first image slot. |
| */ |
| if (boot_img_hdrs[0].ih_magic != IMAGE_MAGIC_NONE) { |
| slot = 0; |
| } else if (boot_img_hdrs[1].ih_magic != IMAGE_MAGIC_NONE) { |
| slot = 1; |
| } else { |
| /* No images present. */ |
| return BOOT_EBADIMAGE; |
| } |
| } |
| |
| /* |
| * If the selected image fails integrity check, try the other one. |
| */ |
| if (boot_image_check(&boot_img_hdrs[slot], &image_addrs[slot])) { |
| slot ^= 1; |
| if (boot_image_check(&boot_img_hdrs[slot], &image_addrs[slot])) { |
| return BOOT_EBADIMAGE; |
| } |
| } |
| switch (slot) { |
| case 0: |
| rsp->br_hdr = &boot_img_hdrs[0]; |
| break; |
| |
| case 1: |
| /* The user wants to run the image in the secondary slot. The contents |
| * of this slot need to moved to the primary slot. |
| */ |
| rc = boot_copy_image(); |
| if (rc != 0) { |
| /* We failed to put the images back together; there is really no |
| * solution here. |
| */ |
| return rc; |
| } |
| |
| rsp->br_hdr = &boot_img_hdrs[1]; |
| break; |
| |
| default: |
| assert(0); |
| break; |
| } |
| |
| /* Always boot from the primary slot. */ |
| rsp->br_flash_id = image_addrs[0].bil_flash_id; |
| rsp->br_image_addr = image_addrs[0].bil_address; |
| |
| /* After successful boot, there should not be a status file. */ |
| boot_clear_status(); |
| |
| /* If an image is being tested, it should only be booted into once. */ |
| boot_vect_write_test(NULL); |
| |
| return 0; |
| } |