| # |
| # 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()). |