/** * 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 #include #include #include #include #include #include #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; }