blob: a39759bc4d12131cb6f9ef1b6777755fddeae1e7 [file] [log] [blame]
#
# 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.
#
****** BOOTUTIL
*** SUMMARY
The bootutil library performs most of the functions of a boot loader. In
particular, the piece that is missing is the final step of actually jumping to
the main image. This last step should instead be implemented in an
architecture-specific project. Boot loader functionality is separated in this
manner for the following two reasons:
1. By keeping architecture-dependent code separate, the bootutil library can be
reused among several boot loaders.
2. By excluding the last boot step from the library, the rest of the code can
be tested in a sim environment.
There is a boot loader project specific to the olimex_stm32-e407 devboard
called "boot." This project provides an example of how the bootutil library
should be used.
*** LIMITATIONS
The boot loader currently only supports images with the following
characteristics:
* Built to run from flash.
* Build to run from a fixed location (i.e., position-independent).
These limitations will likely be addressed soon.
*** IMAGE FORMAT
The following definitions describe the image header format.
#define IMAGE_MAGIC 0x96f3b83c
struct image_version {
uint8_t iv_major;
uint8_t iv_minor;
uint16_t iv_revision;
uint32_t iv_build_num;
};
/** Image header. All fields are in little endian byte order. */
struct image_header {
uint32_t ih_magic;
uint32_t ih_crc32; /* Covers remainder of header and all of image body. */
uint32_t ih_hdr_size;
uint32_t ih_img_size; /* Does not include header. */
uint32_t ih_flags;
struct image_version ih_ver;
};
At this time, no flags have been defined.
The ih_hdr_size field indicates the length of the header, and therefore the
offset of the image itself. This field provides for backwards compatibility in
case of changes to the format of the image header.
When security is added, security data will likely go in a footer at the end of
the image.
*** FLASH AREAS
Bootutil uses the same concept of "flash areas" as the nffs file system.
Briefly, an area is a region of disk with the following properties:
(1) An area can be fully erased without affecting any other areas.
(2) A write to one area does not restrict writes to other areas.
The areas used for image data must not be used for anything else. In
particular, these areas must be kept separate from the nffs file system.
*** IMAGE SLOTS
A portion of the flash memory is partitioned into two image slots: a primary
slot and a secondary slot. The boot loader will only run an image from the
primary slot, so images must be built such that they can run from that fixed
location in flash. If the boot loader needs to run the image resident in the
secondary slot, it must swap the two images in flash prior to booting.
In addition to the two image slots, the boot loader requires a scratch area to
allow for reliable image swapping.
All areas used by image data (including the scratch area) must be the same
size.
*** BOOT VECTOR
Bootutil determines which image it should boot into by reading the contents of
the boot vector. The boot vector comprises the following files in the flash
file system:
#define BOOT_PATH_MAIN "/boot/main"
#define BOOT_PATH_TEST "/boot/test"
Each file, if present, must contain a 64-bit image version. This version is
simply a "binary dump" of an image_version struct. The test file is used to
indicate that an image is being "tested out," and should only be booted once.
The main file indicates the "last known good" image which should be booted
repeatedly.
The boot loader uses the following procedure to determine which image to boot:
1) If the test file is present and contains a valid image version:
* Delete the test file.
* Boot into the specified image.
2) Else if the main file is present and contains a valid image version:
* Boot into the specified image.
3) Else:
* Just boot into whichever image is in the primary slot. If there is no
image in the primary slot, boot into the image in the secondary slot.
If a vector file contains a version which doesn't correspond to an image
actually present in flash, the boot loader deletes the file and procedes as
though the file was not present.
*** BOOT STATUS
The boot status file allows the boot loader to recover in case it was reset
while in the middle of an image swap operation. Image swapping is discussed
later in this document; the structure of the boot status file is presented
here. To ensure recovery is always possible, bootutil updates the status file
at each step throughout the image swap process. The boot status is contained
in the following file:
#define BOOT_PATH_STATUS "/boot/status"
The contents of the boot status file are defined below.
struct boot_status {
uint32_t bs_img1_length;
uint32_t bs_img2_length;
/* Followed by sequence of boot status entries; file size indicates number
* of entries.
*/
};
struct boot_status_entry {
uint8_t bse_image_num;
uint8_t bse_part_num;
};
#define BOOT_IMAGE_NUM_NONE 0xff
There is a separate boot status entry for each flash area used by the boot
loader (i.e., each area in the two slots, plus one for the scratch area). The
entries are placed in the file in the same order as their corresponding areas
in flash. Each entry indicates which image part is resident in the
corresponding flash area. If a flash area does not contain any image data, its
corresponding entry will have a bse_image_num value of BOOT_IMAGE_NUM_NONE.
Consider the following example:
Five flash areas are dedicated to image data, as follows:
Area 0: slot 0, 0/1
Area 1: slot 0, 1/1
Area 2: slot 1, 0/1
Area 3: slot 1, 1/1
Area 4: scratch
The following array of boot status entries is read from the status file:
[0] = {
.bse_image_num = 0,
.bse_part_num = 0,
},
[1] = {
.bse_image_num = 0,
.bse_part_num = 1,
},
[2] = {
.bse_image_num = 1,
.bse_part_num = 0,
},
[3] = {
.bse_image_num = 1,
.bse_part_num = 1,
},
[4] = {
.bse_image_num = 0xff,
.bse_part_num = 0xff,
},
This status file indicates the following image placement:
Area 0: image 0, part 0
Area 1: image 0, part 1
Area 2: image 1, part 0
Area 3: image 1, part 1
Scratch area: empty
Images don't have an instrinsic image number. When a swap operation is
started, the image initially in the primary slot is labelled image 0, and the
image in the secondary slot is labelled image 1. All swap operations end with
image 1 in the primary slot, and image 0 in the secondary slot.
The boot status header containing the image sizes is necessary so that bootutil
can determine how many flash areas each image occupies. Without this
information, bootutil would need to swap the full contents of the image slots,
including useless data after the end of each image.
The status file is always deleted upon successful boot.
*** IMAGE SWAPPING
If the boot vector indicates that the image in the secondary slot should be
run, the boot loader needs to copy it to the primary slot. The image currently
in the primary slot also needs to be retained in flash so that it can be used
later. Furthermore, both images need to be recoverable if the boot loader
resets in the middle of the process. The two images are swapped according to
the following procedure:
1. Determine how many flash areas are required to hold the desired image.
2. For each required area in the primary slot ("destination area"):
a. Identify the flash area in the secondary slot which contains the
required image data ("source area").
b. Erase scratch area.
c. Copy destination area to scratch area.
d. Write updated boot status to the file system.
e. Erase destination area.
f. Copy source area to destination area.
g. Write updated boot status to the file system.
h. Erase source area.
i. Copy scratch area to source area.
j. Write updated boot status to the file system.
3. Determine how many flash areas are required to hold image 1.
4. For each required area in the secondary slot ("destination area"):
a. If the destination area already contains the required image data,
advance to the next image part.
b. Else, identify the flash area in the primary slot which contains the
required image data ("source area").
c. Repeat steps b through j from step 2.
This procedure ensures that the contents of the boot status file are always
accurate, so the boot loader can recover after an unexpected reset.
Step 4 is necessary in case the two images do not occupy the same number of
flash areas.
*** RESET RECOVERY
If the boot loader resets in the middle of a swap operation, the two images may
be discontiguous in flash. Bootutil recovers from this condition by using the
boot status file to determine how the image parts are placed in flash.
If the boot status file indicates that the images are not contiguous, bootutil
completes the swap operation that was in progress when the system was reset.
In other words, it applies the procedure defined in the previous section,
moving image 1 into slot 0 and image 0 into slot 1. If the boot status file
indicates that an image part is present in the scratch area, this part is
copied into the correct location by starting at step e or step h in the
area-swap procedure, depending on whether the part belongs to image 0 or image
1.
After the swap operation has been completed, the boot loader proceeds as though
it had just been started.
*** API
The API consists of a single function:
/**
* 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)
The request and response structures are defined as follows:
/** A request object instructing the boot loader how to proceed. */
struct boot_req {
/**
* Array of area descriptors indicating the layout of internal flash; must
* be terminated with a 0-length element.
*/
struct nffs_area_desc *br_area_descs;
/**
* Array of indices of elements in the br_area_descs array; indicates which
* areas hold image data.
*/
uint8_t *br_image_areas;
/**
* Array of indices of elements in the br_area_descs array; indicates which
* areas represent the beginning of an image slot. This should be a subset
* of the br_image_areas array.
*/
uint8_t *br_slot_areas;
/**
* The number of image areas (i.e., the size of the br_image_areas array).
*/
uint8_t br_num_image_areas;
/** The index of the area to use as the image scratch area. */
uint8_t br_scratch_area_idx;
};
/**
* A response object provided by the boot loader code; indicates where to jump
* to execute the main image.
*/
struct boot_rsp {
/** A pointer to the header of the image to be executed. */
const struct image_header *br_hdr;
/**
* The flash offset of the image to execute. Indicates the position of
* the image header.
*/
uint32_t br_image_addr;
};
*** EXAMPLE USAGE
In this example, each image slot consists of three flash areas.
/** Internal flash layout. */
static struct nffs_area_desc boot_area_descs[] = {
[0] = { 0x08000000, 16 * 1024 },
[1] = { 0x08004000, 16 * 1024 },
[2] = { 0x08008000, 16 * 1024 },
[3] = { 0x0800c000, 16 * 1024 },
[4] = { 0x08010000, 64 * 1024 },
[5] = { 0x08020000, 128 * 1024 }, /* Image data; 0,0. */
[6] = { 0x08040000, 128 * 1024 }, /* Image data; 0,1. */
[7] = { 0x08060000, 128 * 1024 }, /* Image data; 0,2. */
[8] = { 0x08080000, 128 * 1024 }, /* Image data; 1,0. */
[9] = { 0x080a0000, 128 * 1024 }, /* Image data; 1,1. */
[10] = { 0x080c0000, 128 * 1024 }, /* Image data; 1,2. */
[11] = { 0x080e0000, 128 * 1024 }, /* Image scratch area. */
{ 0, 0 },
};
/** Contains indices of the areas which can contain image data. */
static uint8_t boot_img_areas[] = {
5, 6, 7, 8, 9, 10, 11,
};
/** Areas representing the beginning of image slots. */
static uint8_t boot_slot_areas[] = {
5, 8,
};
#define BOOT_NUM_IMG_AREAS \
((int)(sizeof boot_img_areas / sizeof boot_img_areas[0]))
/** The scratch area to use during an image swap operation. */
#define BOOT_AREA_IDX_SCRATCH 11
int
main(void)
{
struct boot_rsp rsp;
int rc;
const struct boot_req req = {
.br_area_descs = boot_area_descs,
.br_image_areas = boot_img_areas,
.br_slot_areas = boot_slot_areas,
.br_num_image_areas = BOOT_NUM_IMG_AREAS,
.br_scratch_area_idx = BOOT_AREA_IDX_SCRATCH,
};
rc = boot_go(&req, &rsp);
assert(rc == 0);
/* Perform jump to address indicated by the response object. */
return 0;
}
*** DEPENDENCIES
* nffs (for the boot vector and boot status files).
* os (for os_malloc() and os_free()).