Commit e0256893 authored by Dominik Ermel's avatar Dominik Ermel Committed by Jamie
Browse files

bootutil: Add support for devices without erase and reduced erases



The commit adds two MCUboot configuration options:
  - MCUBOOT_SUPPORT_DEV_WITHOUT_ERASE
  - MCUBOOT_SUPPORT_DEV_WITH_ERASE
  - MCUBOOT_MINIMAL_SCRAMBLE

The first one should be enabled to support devices that do not require erase.
When such devices are used in system then MCUboot will avoid erasing such
device, which is not needed by hardware, and will just write data to it.
This allows to both improve device lifetime and reduce time of operations
like swap.

The second option is just bringing a configuration option for already existing
support for deviceses with erase.

The third option allows to reduce amount of removed data. When enabled,
MCUboot will remove enough of data, depending on the purpose of the removal,
to just fulfill the purpose; for example if removal of data is done to
make image unrecognizable for MCUboot, with this option, it will only
remove header.

Signed-off-by: default avatarDominik Ermel <dominik.ermel@nordicsemi.no>
parent 92422ca8
Loading
Loading
Loading
Loading
+2 −2
Original line number Diff line number Diff line
@@ -856,7 +856,7 @@ static off_t erase_range(const struct flash_area *fap, off_t start, off_t end)
    BOOT_LOG_DBG("Erasing range 0x%jx:0x%jx", (intmax_t)start,
		 (intmax_t)(start + size - 1));

    rc = flash_area_erase(fap, start, size);
    rc = boot_erase_region(fap, start, size);
    if (rc != 0) {
        BOOT_LOG_ERR("Error %d while erasing range", rc);
        return -EINVAL;
@@ -1000,7 +1000,7 @@ bs_upload(char *buf, int len)
        /* Non-progressive erase erases entire image slot when first chunk of
         * an image is received.
         */
        rc = flash_area_erase(fap, 0, area_size);
        rc = boot_erase_region(fap, 0, area_size);
        if (rc) {
            goto out_invalid_data;
        }
+1 −1
Original line number Diff line number Diff line
@@ -187,7 +187,7 @@ decrypt_region_inplace(struct boot_loader_state *state,
                    (off + bytes_copied + idx) - hdr->ih_hdr_size, blk_sz,
                    blk_off, &buf[idx]);
        }
        rc = flash_area_erase(fap, off + bytes_copied, chunk_sz);
        rc = boot_erase_region(fap, off + bytes_copied, chunk_sz);
        if (rc != 0) {
            return BOOT_EFLASH;
        }
+68 −0
Original line number Diff line number Diff line
@@ -135,6 +135,74 @@ boot_trailer_sz(uint32_t min_write_sz)
    return boot_status_sz(min_write_sz) + boot_trailer_info_sz();
}

int boot_trailer_scramble_offset(const struct flash_area *fa, size_t alignment,
                                 size_t *off)
{
    int ret = 0;

    /* Not allowed to enforce alignment smaller than device allows */
    if (alignment < flash_area_align(fa)) {
        alignment = flash_area_align(fa);
    }

    if (device_requires_erase(fa)) {
        /* For device requiring erase align to erase unit */
        struct flash_sector sector;

        ret = flash_area_get_sector(fa, flash_area_get_size(fa) - boot_trailer_sz(alignment),
                                    &sector);
        if (ret < 0) {
            return ret;
        }

        *off = flash_sector_get_off(&sector);
    } else {
        /* For device not requiring erase align to write block */
        *off = flash_area_get_size(fa) - ALIGN_DOWN(boot_trailer_sz(alignment), alignment);
    }

    return ret;
}

int boot_header_scramble_off_sz(const struct flash_area *fa, int slot, size_t *off,
                                size_t *size)
{
    int ret = 0;
    const size_t write_block = flash_area_align(fa);
    size_t loff = 0;
    struct flash_sector sector;

    (void)slot;
#if defined(MCUBOOT_SWAP_USING_OFFSET)
    /* In case of swap offset, header of secondary slot image is positioned
     * in second sector of slot.
     */
    if (slot == BOOT_SECONDARY_SLOT) {
        ret = flash_area_get_sector(fa, 0, &sector);
        if (ret < 0) {
            return ret;
        }
        loff = flash_sector_get_off(&sector);
    }
#endif

    if (device_requires_erase(fa)) {
        /* For device requiring erase align to erase unit */
        ret = flash_area_get_sector(fa, loff, &sector);
        if (ret < 0) {
            return ret;
        }

        *size = flash_sector_get_size(&sector);
    } else {
        /* For device not requiring erase align to write block */
        *size = ALIGN_UP(sizeof(((struct image_header *)0)->ih_magic), write_block);
    }
    *off = loff;

    return ret;
}

#if MCUBOOT_SWAP_USING_SCRATCH
/*
 * Similar to `boot_trailer_sz` but this function returns the space used to
+36 −0
Original line number Diff line number Diff line
@@ -301,6 +301,16 @@ int boot_find_status(int image_index, const struct flash_area **fap);
int boot_magic_compatible_check(uint8_t tbl_val, uint8_t val);
uint32_t boot_status_sz(uint32_t min_write_sz);
uint32_t boot_trailer_sz(uint32_t min_write_sz);
/* Get offset of trailer aligned to either device erase unit or alignment
 * depending on whether device has erase or not.
 */
int boot_trailer_scramble_offset(const struct flash_area *fa, size_t alignment,
                                 size_t *off);
/* Get size of header aligned to device erase unit or write block,
 * depending on whether device has erase or not.
 */
int boot_header_scramble_off_sz(const struct flash_area *fa, int slot, size_t *off,
                                size_t *size);
int boot_status_entries(int image_index, const struct flash_area *fap);
uint32_t boot_status_off(const struct flash_area *fap);
int boot_read_swap_state(const struct flash_area *fap,
@@ -334,7 +344,19 @@ int boot_copy_region(struct boot_loader_state *state,
                     const struct flash_area *fap_dst,
                     uint32_t off_src, uint32_t off_dst, uint32_t sz);
#endif
/* Prepare for write device that requires erase prior to write. This will
 * do nothing on devices without erase requirement.
 */
int boot_erase_region(const struct flash_area *fap, uint32_t off, uint32_t sz);
/* Similar to boot_erase_region but will always remove data */
int boot_scramble_region(const struct flash_area *fap, uint32_t off, uint32_t sz);
/* Similar to boot_scramble_region but works backwards */
int boot_scramble_region_backwards(const struct flash_area *fap, uint32_t off, uint32_t sz);
/* Makes slot unbootable, either by scrambling header magic, header sector
 * or entire slot, depending on settings.
 * Note: slot is passed here becuase at this point there is no function
 * matching flash_area object to slot */
int boot_scramble_slot(const struct flash_area *fap, int slot);
bool boot_status_is_reset(const struct boot_status *bs);

#ifdef MCUBOOT_ENC_IMAGES
@@ -516,6 +538,20 @@ uint32_t bootutil_max_image_size(const struct flash_area *fap);
int boot_read_image_size(struct boot_loader_state *state, int slot,
                         uint32_t *size);

/* Helper macro to avoid compile errors with systems that do not
 * provide function to check device type.
 * Note: it used to be inline, but somehow compiler would not
 * optimize out branches that were impossible when this evaluated to
 * just "true".
 */
#if defined(MCUBOOT_SUPPORT_DEV_WITHOUT_ERASE) && defined(MCUBOOT_SUPPORT_DEV_WITH_ERASE)
#define device_requires_erase(fa) (flash_area_erase_required(fa))
#elif defined(MCUBOOT_SUPPORT_DEV_WITHOUT_ERASE)
#define device_requires_erase(fa) (false)
#else
#define device_requires_erase(fa) (true)
#endif

#ifdef __cplusplus
}
#endif
+201 −12
Original line number Diff line number Diff line
@@ -1051,7 +1051,7 @@ boot_validate_slot(struct boot_loader_state *state, int slot,
         * is erased.
         */
        if (slot != BOOT_PRIMARY_SLOT) {
            swap_erase_trailer_sectors(state, fap);
            swap_scramble_trailer_sectors(state, fap);

#if defined(MCUBOOT_SWAP_USING_MOVE)
            if (bs->swap_type == BOOT_SWAP_TYPE_REVERT ||
@@ -1063,7 +1063,7 @@ boot_validate_slot(struct boot_loader_state *state, int slot,
                                                                         &fap_pri);

                if (rc == 0) {
                    rc = swap_erase_trailer_sectors(state, fap_pri);
                    rc = swap_scramble_trailer_sectors(state, fap_pri);
                    flash_area_close(fap_pri);

                    if (rc == 0) {
@@ -1109,7 +1109,7 @@ boot_validate_slot(struct boot_loader_state *state, int slot,
                &boot_img_hdr(state, BOOT_PRIMARY_SLOT)->ih_ver);
        if (rc < 0 && boot_check_header_erased(state, BOOT_PRIMARY_SLOT)) {
            BOOT_LOG_ERR("insufficient version in secondary slot");
            flash_area_erase(fap, 0, flash_area_get_size(fap));
            boot_scramble_slot(fap, slot);
            /* Image in the secondary slot does not satisfy version requirement.
             * Erase the image and continue booting from the primary slot.
             */
@@ -1132,7 +1132,7 @@ check_validity:
#endif
    if (FIH_NOT_EQ(fih_rc, FIH_SUCCESS)) {
        if ((slot != BOOT_PRIMARY_SLOT) || ARE_SLOTS_EQUIVALENT()) {
            flash_area_erase(fap, 0, flash_area_get_size(fap));
            boot_scramble_slot(fap, slot);
            /* Image is invalid, erase it to prevent further unnecessary
             * attempts to validate and boot it.
             */
@@ -1173,7 +1173,7 @@ check_validity:
             *
             * Erase the image and continue booting from the primary slot.
             */
            flash_area_erase(fap, 0, fap->fa_size);
            boot_scramble_slot(fap, slot);
            fih_rc = FIH_NO_BOOTABLE_IMAGE;
            goto out;
        }
@@ -1267,9 +1267,10 @@ boot_validated_swap_type(struct boot_loader_state *state,
#endif

/**
 * Erases a region of flash.
 * Erases a region of device that requires erase prior to write; does
 * nothing on devices without erase.
 *
 * @param flash_area           The flash_area containing the region to erase.
 * @param fap                   The flash_area containing the region to erase.
 * @param off                   The offset within the flash area to start the
 *                              erase.
 * @param sz                    The number of bytes to erase.
@@ -1279,8 +1280,198 @@ boot_validated_swap_type(struct boot_loader_state *state,
int
boot_erase_region(const struct flash_area *fap, uint32_t off, uint32_t sz)
{
    if (device_requires_erase(fap)) {
        return flash_area_erase(fap, off, sz);
    }
    return 0;
}

/**
 * Removes data from specified region either by writing erase value in place of
 * data or by doing erase, if device has such hardware requirement.
 * Note that function will fail if off or size are not aligned to device
 * write block size or erase block size.
 *
 * @param fa                    The flash_area containing the region to erase.
 * @param off                   The offset within the flash area to start the
 *                              erase.
 * @param size                  The number of bytes to erase.
 *
 * @return                      0 on success; nonzero on failure.
 */
int
boot_scramble_region(const struct flash_area *fa, uint32_t off, uint32_t size)
{
    int ret = 0;

    if (size == 0) {
        return 0;
    }

    if (device_requires_erase(fa)) {
        return flash_area_erase(fa, off, size);
    } else {
        uint8_t buf[BOOT_MAX_ALIGN];
        size_t size_done = 0;
        const size_t write_block = flash_area_align(fa);

        memset(buf, flash_area_erased_val(fa), sizeof(buf));

        while (size_done < size) {
            ret = flash_area_write(fa, size_done + off, buf, write_block);
            if (ret != 0) {
                break;
            }
            size_done += write_block;
        }
    }
    return ret;
}

/**
 * Removes data from specified region backwards either by writing erase_value
 * in place of data or by doing erase, if device has such hardware requirement.
 * Note that function will fail if off or size are not aligned to device
 * write block size or erase block size.
 *
 * @param fa                    The flash_area containing the region to erase.
 * @param off                   The offset within the flash area to start the
 *                              erase.
 * @param size                  The number of bytes to erase.
 *
 * @return                      0 on success; nonzero on failure.
 */
int boot_scramble_region_backwards(const struct flash_area *fa, uint32_t off, uint32_t size)
{
    int ret = 0;
    uint32_t first_offset = 0;

    if (size == 0) {
        return 0;
    }

    if (off >= flash_area_get_size(fa) || (flash_area_get_size(fa) - off) < size) {
        return -1;
    }

    if (device_requires_erase(fa)) {
        struct flash_sector sector;

        /* Get the lowest erased page offset first */
        ret = flash_area_get_sector(fa, off, &sector);
        if (ret < 0) {
            return ret;
        }
        first_offset = flash_sector_get_off(&sector);

        /* Set boundary condition, the highest probable offset to erase, within
         * last sector to erase
         */
        off += size - 1;

        while (true) {
            /* Size to read in this iteration */
            size_t csize;

            /* Get current sector and, also, correct offset */
            ret = flash_area_get_sector(fa, off, &sector);
            if (ret < 0) {
                return ret;
            }

            /* Corrected offset and size of current sector to erase */
            off = flash_sector_get_off(&sector);
            csize = flash_sector_get_size(&sector);

            ret = flash_area_erase(fa, off, csize);
            if (ret < 0) {
                return ret;
            }

            if (first_offset >= off) {
                /* Reached the first offsset in range and already erased it */
                break;
            }

            /* Move down to previous sector, the flash_area_get_sector will
             * correct the value to real page offset
             */
            off -= 1;
        }
    } else {
        uint8_t buf[BOOT_MAX_ALIGN];
        const size_t write_block = flash_area_align(fa);
        uint32_t first_offset = ALIGN_DOWN(off, write_block);

        memset(buf, flash_area_erased_val(fa), sizeof(buf));

        /* Starting at the last write block in range */
        off += size - write_block;

        while (true) {
            /* Write over the area to scramble data that is there */
            ret = flash_area_write(fa, off, buf, write_block);
            if (ret != 0) {
                break;
            }

            if (first_offset >= off) {
                /* Reached the first offset in range and already scrambled it */
                break;
            }

            off -= write_block;
        }
    }
    return ret;
}

/**
 * Remove enough data from slot to mark is as unused
 * Assumption: header and trailer are not overlapping on write block or
 * erase block, if device has erase requirement.
 * Note that this function is intended for removing data not preparing device
 * for write.
 *
 * @param fa        Pointer to flash area object for slot
 * @param slot      Slot the @p fa represents
 *
 * @return          0 on success; nonzero on failure.
 */
int
boot_scramble_slot(const struct flash_area *fa, int slot)
{
    size_t size;
    int ret = 0;

    (void)slot;

    /* Without minimal entire area needs to be scrambled */
#if !defined(MCUBOOT_MINIMAL_SCRAMBLE)
    size = flash_area_get_size(fa);
    ret = boot_scramble_region(fa, 0, size);
#else
    size_t off = 0;

    ret = boot_header_scramble_off_sz(fa, slot, &off, &size);
    if (ret < 0) {
        return ret;
    }

    ret = boot_scramble_region(fa, off, size);
    if (ret < 0) {
        return ret;
    }

    ret = boot_trailer_scramble_offset(fa, 0, &off);
    if (ret < 0) {
        return ret;
    }

    ret = boot_scramble_region_backwards(fa, off, flash_area_get_size(fa) - off);
#endif
    return ret;
}

#if !defined(MCUBOOT_DIRECT_XIP) && !defined(MCUBOOT_RAM_LOAD)

@@ -1776,7 +1967,6 @@ boot_swap_image(struct boot_loader_state *state, struct boot_status *bs)
}
#endif


/**
 * Performs a clean (not aborted) image update.
 *
@@ -2239,8 +2429,7 @@ check_downgrade_prevention(struct boot_loader_state *state)
    if (rc < 0) {
        /* Image in slot 0 prevents downgrade, delete image in slot 1 */
        BOOT_LOG_INF("Image %d in slot 1 erased due to downgrade prevention", BOOT_CURR_IMG(state));
        flash_area_erase(BOOT_IMG_AREA(state, 1), 0,
                         flash_area_get_size(BOOT_IMG_AREA(state, 1)));
        boot_scramble_slot(BOOT_IMG_AREA(state, 1), BOOT_SECONDARY_SLOT);
    } else {
        rc = 0;
    }
Loading