Implement new swap scheme for devices with large erase size using scratch with status area
diff --git a/boot/bootutil/src/swap_status_part.c b/boot/bootutil/src/swap_status_part.c
new file mode 100644
index 0000000..a2899a9
--- /dev/null
+++ b/boot/bootutil/src/swap_status_part.c
@@ -0,0 +1,404 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Copyright (c) 2017-2019 Linaro LTD
+ * Copyright (c) 2016-2019 JUUL Labs
+ * Copyright (c) 2019-2020 Arm Limited
+ * Copyright (c) 2020 Cypress Semiconductors
+ *
+ * Original license:
+ *
+ * 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 "crc32c.h"
+#include <string.h>
+#include "swap_status.h"
+
+#ifdef MCUBOOT_SWAP_USING_STATUS
+
+#define IMAGE_0_STATUS_OFFS        0
+#define IMAGE_0_STATUS_SIZE        (BOOT_SWAP_STATUS_SIZE)
+
+#define IMAGE_1_STATUS_OFFS        (IMAGE_0_STATUS_OFFS + IMAGE_0_STATUS_SIZE)
+#define IMAGE_1_STATUS_SIZE        (BOOT_SWAP_STATUS_SIZE)
+
+#define SCRATCH_STATUS_OFFS        (IMAGE_1_STATUS_OFFS + BOOT_SWAP_STATUS_SIZE)
+#ifdef MCUBOOT_SWAP_USING_SCRATCH
+#define SCRATCH_STATUS_SIZE        (BOOT_SWAP_STATUS_SIZE)
+#else
+#define SCRATCH_STATUS_SIZE        0
+#endif
+
+#if (MCUBOOT_IMAGE_NUMBER == 2)
+#define IMAGE_2_STATUS_OFFS        (SCRATCH_STATUS_OFFS + SCRATCH_STATUS_SIZE)
+#define IMAGE_2_STATUS_SIZE        (BOOT_SWAP_STATUS_SIZE)
+
+#define IMAGE_3_STATUS_OFFS        (IMAGE_2_STATUS_OFFS + IMAGE_2_STATUS_SIZE)
+#define IMAGE_3_STATUS_SIZE        (BOOT_SWAP_STATUS_SIZE)
+#endif
+
+const uint32_t stat_part_magic[] = {
+    BOOT_SWAP_STATUS_MAGIC
+};
+
+uint32_t calc_rec_idx(uint32_t value)
+{
+    uint32_t rec_idx;
+
+    rec_idx = value/BOOT_SWAP_STATUS_PAYLD_SZ;
+
+    return rec_idx;
+}
+
+uint32_t calc_record_offs(uint32_t offs)
+{
+    uint32_t rec_offs;
+
+    rec_offs = BOOT_SWAP_STATUS_ROW_SZ*calc_rec_idx(offs);
+
+    return rec_offs;
+}
+
+uint32_t calc_record_crc(const uint8_t *data, uint32_t length)
+{
+    uint32_t crc;
+
+    crc = crc32c_checksum(data, length);
+
+    return crc;
+}
+
+int32_t swap_status_init_offset(uint32_t area_id)
+{
+    int32_t offset = -1;
+    /* calculate an offset caused by area type: primary_x/secondary_x */
+    switch (area_id) {
+    case FLASH_AREA_IMAGE_0:
+        offset = (int)IMAGE_0_STATUS_OFFS;
+        break;
+    case FLASH_AREA_IMAGE_1:
+        offset = (int)IMAGE_1_STATUS_OFFS;
+        break;
+#ifdef MCUBOOT_SWAP_USING_SCRATCH
+    case FLASH_AREA_IMAGE_SCRATCH:
+        offset = (int)SCRATCH_STATUS_OFFS;
+        break;
+#endif
+#if (MCUBOOT_IMAGE_NUMBER == 2)
+    case FLASH_AREA_IMAGE_2:
+        offset = (int)IMAGE_2_STATUS_OFFS;
+        break;
+    case FLASH_AREA_IMAGE_3:
+        offset = (int)IMAGE_3_STATUS_OFFS;
+        break;
+#endif
+    default:
+        offset = -1;
+        break;
+    }
+    return offset;
+}
+
+int swap_status_read_record(uint32_t rec_offset, uint8_t *data, uint32_t *copy_counter)
+{ /* returns BOOT_SWAP_STATUS_PAYLD_SZ of data */
+    int rc = -1;
+
+    uint32_t fin_offset;
+    uint32_t data_offset = 0;
+    uint32_t counter, crc, magic;
+    uint32_t crc_fail = 0;
+    uint32_t magic_fail = 0;
+    uint32_t max_cnt = 0;
+
+    int32_t max_idx = 0;
+
+    uint8_t buff[BOOT_SWAP_STATUS_ROW_SZ];
+
+    const struct flash_area *fap_stat = NULL;
+
+    rc = flash_area_open(FLASH_AREA_IMAGE_SWAP_STATUS, &fap_stat);
+    if (rc != 0) {
+        return BOOT_EFLASH;
+    }
+
+    /* loop over copies/duplicates */
+    for(uint32_t i = 0; i<BOOT_SWAP_STATUS_MULT; i++) {
+        /* calculate final duplicate offset */
+        fin_offset = rec_offset + i*BOOT_SWAP_STATUS_D_SIZE;
+
+        rc = flash_area_read(fap_stat, fin_offset, buff, sizeof(buff));
+        if (rc != 0) {
+            return BOOT_EFLASH;
+        }
+        /* read magic value to know if area was pre-erased */
+        magic = *((uint32_t *)&buff[BOOT_SWAP_STATUS_ROW_SZ -\
+                                  BOOT_SWAP_STATUS_MGCREC_SZ -\
+                                  BOOT_SWAP_STATUS_CNT_SZ-\
+                                  BOOT_SWAP_STATUS_CRC_SZ]);
+        if (magic == BOOT_SWAP_STATUS_MAGIC) {   /* read CRC */
+            crc = *((uint32_t *)&buff[BOOT_SWAP_STATUS_ROW_SZ -\
+                                      BOOT_SWAP_STATUS_CRC_SZ]);
+            /* check record data integrity first */
+            if (crc == calc_record_crc(buff, BOOT_SWAP_STATUS_ROW_SZ-BOOT_SWAP_STATUS_CRC_SZ)) {
+                /* look for counter */
+                counter = *((uint32_t *)&buff[BOOT_SWAP_STATUS_ROW_SZ -\
+                                              BOOT_SWAP_STATUS_CNT_SZ - \
+                                              BOOT_SWAP_STATUS_CRC_SZ]);
+                /* find out counter max */
+                if (counter >= max_cnt) {
+                    max_cnt = counter;
+                    max_idx = (int32_t)i;
+                    data_offset = fin_offset;
+                }
+            }
+            /* if crc != calculated() */
+            else {
+                crc_fail++;
+            }
+        }
+        else {
+            magic_fail++;
+        }
+    }
+    /* no magic found - status area is pre-erased, start from scratch */
+    if (magic_fail == BOOT_SWAP_STATUS_MULT) {
+        /* emulate last index was received, so next will start from beginning */
+        max_idx = (int32_t)(BOOT_SWAP_STATUS_MULT-1U);
+        *copy_counter = 0;
+        /* return all erased values */
+        (void)memset(data, (int32_t)flash_area_erased_val(fap_stat), BOOT_SWAP_STATUS_PAYLD_SZ);
+    }
+    else {
+        /* no valid CRC found - status pre-read failure */
+        if (crc_fail == BOOT_SWAP_STATUS_MULT) {
+            max_idx = -1;
+        }
+        else {
+            *copy_counter = max_cnt;
+            /* read payload data */
+            rc = flash_area_read(fap_stat, data_offset, data, BOOT_SWAP_STATUS_PAYLD_SZ);
+	        if (rc != 0) {
+	            return BOOT_EFLASH;
+	        }
+        }
+    }
+    flash_area_close(fap_stat);
+
+    /* return back duplicate index */
+    return max_idx;
+}
+
+static int swap_status_write_record(uint32_t rec_offset, uint32_t copy_num, uint32_t copy_counter, const uint8_t *data)
+{ /* it receives explicitly BOOT_SWAP_STATUS_PAYLD_SZ of data */
+    int rc = -1;
+
+    uint32_t fin_offset;
+    /* increment counter field */
+    uint32_t next_counter = copy_counter + 1U;
+    uint32_t next_crc;
+
+    uint8_t buff[BOOT_SWAP_STATUS_ROW_SZ];
+
+    const struct flash_area *fap_stat = NULL;
+
+    rc = flash_area_open(FLASH_AREA_IMAGE_SWAP_STATUS, &fap_stat);
+    if (rc != 0) {
+        return BOOT_EFLASH;
+    }
+
+    /* copy data into buffer */
+    (void)memcpy(buff, data, BOOT_SWAP_STATUS_PAYLD_SZ);
+    /* append next counter to whole record row */
+    (void)memcpy(&buff[BOOT_SWAP_STATUS_ROW_SZ-BOOT_SWAP_STATUS_CNT_SZ-BOOT_SWAP_STATUS_CRC_SZ], \
+            &next_counter, \
+            BOOT_SWAP_STATUS_CNT_SZ);
+    /* append record magic */
+    (void)memcpy(&buff[BOOT_SWAP_STATUS_ROW_SZ-\
+                    BOOT_SWAP_STATUS_MGCREC_SZ-\
+                    BOOT_SWAP_STATUS_CNT_SZ-\
+                    BOOT_SWAP_STATUS_CRC_SZ], \
+                    stat_part_magic, \
+                    BOOT_SWAP_STATUS_MGCREC_SZ);
+
+    /* calculate CRC field*/
+    next_crc = calc_record_crc(buff, BOOT_SWAP_STATUS_ROW_SZ-BOOT_SWAP_STATUS_CRC_SZ);
+
+    /* append new CRC to whole record row */
+    (void)memcpy(&buff[BOOT_SWAP_STATUS_ROW_SZ-BOOT_SWAP_STATUS_CRC_SZ], \
+            &next_crc, \
+            BOOT_SWAP_STATUS_CRC_SZ);
+
+    /* we already know what copy number was last and correct */
+    /* increment duplicate index */
+    /* calculate final duplicate offset */
+    if (copy_num == (BOOT_SWAP_STATUS_MULT - 1U)) {
+        copy_num = 0;
+    }
+    else {
+        copy_num++;
+    }
+    fin_offset = rec_offset + copy_num*BOOT_SWAP_STATUS_D_SIZE;
+
+    /* write prepared record into flash */
+    rc = flash_area_write(fap_stat, fin_offset, buff, sizeof(buff));
+
+    flash_area_close(fap_stat);
+
+    return rc;
+}
+
+/**
+ * Updates len bytes of status partition with values from *data-pointer.
+ *
+ * @param targ_area_id  Target area id for which status is being written.
+ *                      Not a status-partition area id.
+ * @param offset        Status byte offset inside status table. Should not include CRC and CNT.
+ * @param data          Pointer to data status table to needs to be updated with.
+ * @param len           Number of bytes to be written
+ *
+ * @return              0 on success; nonzero on failure.
+ */
+int swap_status_update(uint32_t targ_area_id, uint32_t offs, const void *data, uint32_t len)
+{
+    int rc = -1;
+
+    int32_t init_offs;
+    int32_t length = (int32_t)len;
+    int32_t copy_num;
+
+    uint32_t rec_offs;
+    uint32_t copy_sz;
+    uint32_t copy_counter;
+    uint32_t data_idx = 0;
+    uint32_t buff_idx = offs%BOOT_SWAP_STATUS_PAYLD_SZ;
+
+    uint8_t buff[BOOT_SWAP_STATUS_PAYLD_SZ];
+
+    /* check if end of data is still inside writable area */
+    assert ((int)((offs + len) <= BOOT_SWAP_STATUS_D_SIZE_RAW));
+
+    /* pre-calculate sub-area offset */
+    init_offs = swap_status_init_offset(targ_area_id);
+    assert ((int)(init_offs >= 0));
+
+    /* will start from it
+     * this will be write-aligned */
+    rec_offs = (uint32_t)init_offs + calc_record_offs(offs);
+
+    /* go over all records to be updated */
+    while (length > 0) {
+        /* preserve record */
+        copy_num = swap_status_read_record(rec_offs, buff, &copy_counter);
+        /* it returns copy number */
+        if (copy_num < 0)
+        {   /* something went wrong while read, exit */
+            rc = -1;
+            break;
+        }
+        /* update record data */
+        if (length > (int)BOOT_SWAP_STATUS_PAYLD_SZ) {
+            copy_sz = BOOT_SWAP_STATUS_PAYLD_SZ - buff_idx;
+        }
+        else {
+            copy_sz = (uint32_t)length;
+        }
+        (void)memcpy((void *)&buff[buff_idx], &((uint8_t *)data)[data_idx], copy_sz);
+        buff_idx = 0;
+
+        /* write record back */
+        rc = swap_status_write_record(rec_offs, (uint32_t)copy_num, copy_counter, buff);
+        assert ((int)(rc == 0));
+
+        /* proceed to next record */
+        length -= (int32_t)BOOT_SWAP_STATUS_PAYLD_SZ;
+        rec_offs += BOOT_SWAP_STATUS_ROW_SZ;
+        data_idx += BOOT_SWAP_STATUS_PAYLD_SZ;
+    }
+    return rc;
+}
+
+/**
+ * Reads len bytes of status partition with values from *data-pointer.
+ *
+ * @param targ_area_id  Target area id for which status is being read.
+ *                      Not a status-partition area id.
+ * @param offset        Status byte offset inside status table. Should not include CRC and CNT.
+ * @param data          Pointer to data where status table values will be written.
+ * @param len           Number of bytes to be read from status table.
+ *
+ * @return              0 on success; nonzero on failure.
+ */
+int swap_status_retrieve(uint32_t target_area_id, uint32_t offs, void *data, uint32_t len)
+{
+    int rc = 0;
+
+    int32_t init_offs;
+    int32_t length = (int32_t)len;
+    int32_t copy_num;
+
+    uint32_t rec_offs;
+    uint32_t copy_sz;
+    uint32_t copy_counter;
+    uint32_t data_idx = 0;
+    uint32_t buff_idx = offs % BOOT_SWAP_STATUS_PAYLD_SZ;
+
+    uint8_t buff[BOOT_SWAP_STATUS_PAYLD_SZ];
+
+    /* check if end of data is still inside writable area */
+    // TODO: update for multi image
+    assert ((int)((offs + len) <= BOOT_SWAP_STATUS_D_SIZE_RAW));
+
+    /* pre-calculate sub-area offset */
+    init_offs = swap_status_init_offset(target_area_id);
+    assert ((int)(init_offs >= 0));
+
+    /* will start from it
+     * this will be write-aligned */
+    rec_offs = (uint32_t)init_offs + calc_record_offs(offs);
+
+    /* go over all records to be updated */
+    while (length > 0) {
+        /* preserve record */
+        copy_num = swap_status_read_record(rec_offs, buff, &copy_counter);
+        /* it returns copy number */
+        if (copy_num < 0) {
+            /* something went wrong while read, exit */
+            rc = -1;
+            break;
+        }
+        /* update record data */
+        if (length > (int)BOOT_SWAP_STATUS_PAYLD_SZ) {
+            copy_sz = BOOT_SWAP_STATUS_PAYLD_SZ - buff_idx;
+        }
+        else {
+            copy_sz = (uint32_t)length;
+        }
+        (void)memcpy(&((uint8_t *)data)[data_idx], &buff[buff_idx], copy_sz);
+        buff_idx = 0;
+
+        /* proceed to next record */
+        length -= (int32_t)BOOT_SWAP_STATUS_PAYLD_SZ;
+        rec_offs += BOOT_SWAP_STATUS_ROW_SZ;
+        data_idx += BOOT_SWAP_STATUS_PAYLD_SZ;
+    }
+    return rc;
+}
+
+#endif /* MCUBOOT_SWAP_USING_STATUS */