blob: c918a160c86c88faa3015bcb7696abeb095add57 [file]
/**
* 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/hal_flash.h"
#include "os/os_malloc.h"
#include "nffs/nffs.h"
#include "fs/fs.h"
#include "bootutil/loader.h"
#include "bootutil/image.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];
/**
* The boot status header read from the file system, or generated if not
* present on disk. The boot status indicates the state of the image slots in
* case the system was restarted while images were being moved in flash.
*/
struct boot_status boot_status;
/** The entries associated with the boot status header. */
struct boot_status_entry *boot_status_entries;
/**
* 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 nffs_area_desc *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->nad_flash_id;
*address = area_desc->nad_offset;
}
/**
* 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_delete_test();
} else {
return slot;
}
}
rc = boot_vect_read_main(&ver);
if (rc == 0) {
slot = boot_find_image_slot(&ver);
if (slot == -1) {
boot_vect_delete_main();
} else {
return slot;
}
}
return -1;
}
/**
* Searches the current boot status for the specified image-num,part-num pair.
*
* @param image_num The image number to search for.
* @param part_num The part number of the specified image to
* search for.
*
* @return The area index containing the specified image
* part number;
* -1 if the part number is not present in flash.
*/
static int
boot_find_image_part(int image_num, int part_num)
{
int i;
for (i = 0; i < boot_req->br_num_image_areas; i++) {
if (boot_status_entries[i].bse_image_num == image_num &&
boot_status_entries[i].bse_part_num == part_num) {
return boot_req->br_image_areas[i];
}
}
return -1;
}
static int
boot_slot_to_area_idx(int slot_num)
{
int i;
uint8_t flash_id;
uint32_t address;
assert(slot_num >= 0 && slot_num < BOOT_NUM_SLOTS);
for (i = 0; boot_req->br_area_descs[i].nad_length != 0; i++) {
boot_slot_addr(slot_num, &flash_id, &address);
if (boot_req->br_area_descs[i].nad_offset == address &&
boot_req->br_area_descs[i].nad_flash_id == flash_id) {
return i;
}
}
return -1;
}
/**
* Locates the specified area index within the array of image areas.
*
* @param area_idx The area index to search for.
*
* @return The index of the element in the image area
* array. that contains the sought after area
* index; -1 if the area index is not present.
*/
static int
boot_find_image_area_idx(int area_idx)
{
int i;
for (i = 0; i < boot_req->br_num_image_areas; i++) {
if (boot_req->br_image_areas[i] == area_idx) {
return i;
}
}
return -1;
}
static int
boot_erase_area(int area_idx)
{
const struct nffs_area_desc *area_desc;
int rc;
area_desc = boot_req->br_area_descs + area_idx;
rc = hal_flash_erase(area_desc->nad_flash_id, area_desc->nad_offset,
area_desc->nad_length);
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.
*
* @return 0 on success; nonzero on failure.
*/
static int
boot_copy_area(int from_area_idx, int to_area_idx)
{
const struct nffs_area_desc *from_area_desc;
const struct nffs_area_desc *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->nad_length >= from_area_desc->nad_length);
off = 0;
while (off < from_area_desc->nad_length) {
if (from_area_desc->nad_length - off > sizeof buf) {
chunk_sz = sizeof buf;
} else {
chunk_sz = from_area_desc->nad_length - off;
}
from_addr = from_area_desc->nad_offset + off;
rc = hal_flash_read(from_area_desc->nad_flash_id, from_addr, buf,
chunk_sz);
if (rc != 0) {
return rc;
}
to_addr = to_area_desc->nad_offset + off;
rc = hal_flash_write(to_area_desc->nad_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 part_num_1 The image part number stored in the first
* area.
* @param area_idx_2 The index of the other area to swap. This
* area must be part of the second image
* slot.
* @param part_num_2 The image part number stored in the second
* area.
*
* @return 0 on success; nonzero on failure.
*/
static int
boot_move_area(int from_area_idx, int to_area_idx,
int img_num, uint8_t part_num)
{
int src_image_idx;
int dst_image_idx;
int rc;
src_image_idx = boot_find_image_area_idx(from_area_idx);
assert(src_image_idx != -1);
dst_image_idx = boot_find_image_area_idx(to_area_idx);
assert(dst_image_idx != -1);
rc = boot_erase_area(to_area_idx);
if (rc != 0) {
return rc;
}
rc = boot_copy_area(from_area_idx, to_area_idx);
if (rc != 0) {
return rc;
}
boot_status_entries[src_image_idx].bse_image_num = BOOT_IMAGE_NUM_NONE;
boot_status_entries[src_image_idx].bse_part_num = BOOT_IMAGE_NUM_NONE;
boot_status_entries[dst_image_idx].bse_image_num = img_num;
boot_status_entries[dst_image_idx].bse_part_num = part_num;
rc = boot_write_status(&boot_status, boot_status_entries,
boot_req->br_num_image_areas);
if (rc != 0) {
return rc;
}
rc = boot_erase_area(from_area_idx);
if (rc != 0) {
return rc;
}
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 part_num_1 The image part number stored in the first
* area.
* @param area_idx_2 The index of the other area to swap. This
* area must be part of the second image
* slot.
* @param part_num_2 The image part number stored in the second
* area.
*
* @return 0 on success; nonzero on failure.
*/
static int
boot_swap_areas(int area_idx_1, int img_num_1, uint8_t part_num_1,
int area_idx_2, int img_num_2, uint8_t part_num_2)
{
int scratch_image_idx;
int image_idx_1;
int image_idx_2;
int rc;
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);
image_idx_1 = boot_find_image_area_idx(area_idx_1);
assert(image_idx_1 != -1);
image_idx_2 = boot_find_image_area_idx(area_idx_2);
assert(image_idx_2 != -1);
scratch_image_idx =
boot_find_image_area_idx(boot_req->br_scratch_area_idx);
assert(scratch_image_idx != -1);
rc = boot_erase_area(boot_req->br_scratch_area_idx);
if (rc != 0) {
return rc;
}
rc = boot_copy_area(area_idx_2, boot_req->br_scratch_area_idx);
if (rc != 0) {
return rc;
}
boot_status_entries[scratch_image_idx] = boot_status_entries[image_idx_2];
boot_status_entries[image_idx_2].bse_image_num = BOOT_IMAGE_NUM_NONE;
boot_status_entries[image_idx_2].bse_part_num = BOOT_IMAGE_NUM_NONE;
rc = boot_write_status(&boot_status, boot_status_entries,
boot_req->br_num_image_areas);
if (rc != 0) {
return rc;
}
rc = boot_erase_area(area_idx_2);
if (rc != 0) {
return rc;
}
rc = boot_copy_area(area_idx_1, area_idx_2);
if (rc != 0) {
return rc;
}
boot_status_entries[image_idx_2] = boot_status_entries[image_idx_1];
boot_status_entries[image_idx_1].bse_image_num = BOOT_IMAGE_NUM_NONE;
boot_status_entries[image_idx_1].bse_part_num = BOOT_IMAGE_NUM_NONE;
rc = boot_write_status(&boot_status, boot_status_entries,
boot_req->br_num_image_areas);
if (rc != 0) {
return rc;
}
rc = boot_erase_area(area_idx_1);
if (rc != 0) {
return rc;
}
rc = boot_copy_area(boot_req->br_scratch_area_idx, area_idx_1);
if (rc != 0) {
return rc;
}
boot_status_entries[image_idx_1] = boot_status_entries[scratch_image_idx];
boot_status_entries[scratch_image_idx].bse_image_num = BOOT_IMAGE_NUM_NONE;
boot_status_entries[scratch_image_idx].bse_part_num = BOOT_IMAGE_NUM_NONE;
rc = boot_write_status(&boot_status, boot_status_entries,
boot_req->br_num_image_areas);
if (rc != 0) {
return rc;
}
return 0;
}
static int
boot_fill_slot(int img_num, uint32_t img_length, int start_area_idx)
{
const struct nffs_area_desc *area_desc;
uint32_t off;
int dst_image_area_idx;
int src_area_idx;
int dst_area_idx;
int src_img_num;
int dst_img_num;
int part_num;
int rc;
part_num = 0;
off = 0;
while (off < img_length) {
/* Determine which area contains the current part of the image that
* we want to boot.
*/
src_area_idx = boot_find_image_part(img_num, part_num);
if (src_area_idx == -1) {
return BOOT_EBADIMAGE;
}
/* Determine which area we want to copy the source to. */
dst_area_idx = start_area_idx + part_num;
if (src_area_idx != dst_area_idx) {
/* Determine what is currently in the destination area. */
dst_image_area_idx = boot_find_image_area_idx(dst_area_idx);
if (boot_status_entries[dst_image_area_idx].bse_image_num ==
BOOT_IMAGE_NUM_NONE) {
/* The destination doesn't contain anything useful; we don't
* need to back up its contents.
*/
rc = boot_move_area(src_area_idx, dst_area_idx,
img_num, part_num);
} else {
/* Swap the two areas. */
src_img_num = img_num ^ 1;
dst_img_num = img_num;
rc = boot_swap_areas(src_area_idx, src_img_num, part_num,
dst_area_idx, dst_img_num, part_num);
}
if (rc != 0) {
return rc;
}
}
area_desc = boot_req->br_area_descs + dst_area_idx;
off += area_desc->nad_length;
part_num++;
}
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(uint32_t img1_length, uint32_t img2_length)
{
int rc;
rc = boot_fill_slot(1, img2_length, boot_slot_to_area_idx(0));
if (rc != 0) {
return rc;
}
rc = boot_fill_slot(0, img1_length, boot_slot_to_area_idx(1));
if (rc != 0) {
return rc;
}
return 0;
}
/**
* Builds a single default boot status for the specified image slot.
*/
static void
boot_build_status_one(int image_num, uint8_t flash_id, uint32_t addr,
uint32_t length)
{
uint32_t offset;
int area_idx = 0;
int part_num;
int i;
for (i = 0; i < boot_req->br_num_image_areas; i++) {
area_idx = boot_req->br_image_areas[i];
if (boot_req->br_area_descs[area_idx].nad_offset == addr &&
boot_req->br_area_descs[area_idx].nad_flash_id == flash_id) {
break;
}
}
assert(i < boot_req->br_num_image_areas);
offset = 0;
part_num = 0;
while (offset < length) {
assert(boot_status_entries[i].bse_image_num == 0xff);
boot_status_entries[i].bse_image_num = image_num;
boot_status_entries[i].bse_part_num = part_num;
offset += boot_req->br_area_descs[area_idx].nad_length;
part_num++;
i++;
area_idx++;
}
assert(i <= boot_req->br_num_image_areas);
}
/**
* 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)
{
uint8_t flash_id;
uint32_t address;
memset(boot_status_entries, 0xff,
boot_req->br_num_image_areas * sizeof *boot_status_entries);
if (boot_img_hdrs[0].ih_magic == IMAGE_MAGIC) {
boot_status.bs_img1_length = boot_img_hdrs[0].ih_img_size;
boot_slot_addr(0, &flash_id, &address);
boot_build_status_one(0, flash_id, address,
boot_img_hdrs[0].ih_img_size);
} else {
boot_status.bs_img1_length = 0;
}
if (boot_img_hdrs[1].ih_magic == IMAGE_MAGIC) {
boot_status.bs_img2_length = boot_img_hdrs[1].ih_img_size;
boot_slot_addr(1, &flash_id, &address);
boot_build_status_one(1, flash_id, address,
boot_img_hdrs[1].ih_img_size);
} else {
boot_status.bs_img2_length = 0;
}
}
/**
* Initializes the flash driver and file system for use by the boot loader.
*
* @return 0 on success; nonzero on failure.
*/
static int
boot_init_flash(void)
{
int rc;
rc = hal_flash_init();
if (rc != 0) {
return BOOT_EFLASH;
}
rc = nffs_init();
if (rc != 0) {
return BOOT_EFILE;
}
/* Look for an nffs file system in internal flash. If no file system gets
* detected, all subsequent file operations will fail, but the boot loader
* should proceed anyway.
*/
nffs_detect(boot_req->br_area_descs);
/* Create the boot directory if it doesn't already exist. */
fs_mkdir("/boot");
return 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;
/* Initialize the flash hardware and the file system. */
rc = boot_init_flash();
if (rc != 0) {
return rc;
}
/* 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).
*/
boot_status_entries =
malloc(req->br_num_image_areas * sizeof *boot_status_entries);
if (boot_status_entries == NULL) {
return BOOT_ENOMEM;
}
rc = boot_read_status(&boot_status, boot_status_entries,
boot_req->br_num_image_areas);
if (rc == 0) {
/* We are resuming an interrupted image copy. */
rc = boot_copy_image(boot_status.bs_img1_length,
boot_status.bs_img2_length);
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;
}
}
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(boot_status.bs_img1_length,
boot_status.bs_img2_length);
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. */
fs_unlink(BOOT_PATH_STATUS);
/* If an image is being tested, it should only be booted into once. */
boot_vect_delete_test();
return 0;
}