boot: zephyr: fix flash page layout hacks

Zephyr now has proper page layout APIs in its flash driver. Use these
to implement flash_area_get_sectors() and flash_area_to_sectors() when
they are available. Leave the existing implementation behind as a
"legacy" version, for flash drivers which lack page layout support.

This lets us obtain all the target support we need for nRF and STM32
targets entirely from the operating system.

There are two cases where OS-level support is still not enough:

- NXP K6x targets still need an SoC family header, because their flash
  driver does not yet support CONFIG_FLASH_PAGE_LAYOUT.

- The arduino_101 target needs partition and flash alignment
  definitions in its DTS, and a flash driver with page layout support
  as well, so its board-specific header must remain for now.

Fixing these is left to future work. Once that is done,
boot/zephyr/targets/ can be removed completely.

Signed-off-by: Marti Bolivar <marti.bolivar@linaro.org>
diff --git a/boot/zephyr/Makefile b/boot/zephyr/Makefile
index e63adbc..3f9b0a0 100644
--- a/boot/zephyr/Makefile
+++ b/boot/zephyr/Makefile
@@ -7,6 +7,11 @@
 
 obj-y += main.o
 obj-y += flash_map.o hal_flash.o os.o
+# Legacy fallbacks used when the flash driver doesn't provide page
+# layout support.
+ifneq ($(CONFIG_FLASH_PAGE_LAYOUT),y)
+obj-y += flash_map_legacy.o
+endif
 obj-y += keys.o
 
 obj-y += ../bootutil/src/
diff --git a/boot/zephyr/flash_map.c b/boot/zephyr/flash_map.c
index e894699..899c5ad 100644
--- a/boot/zephyr/flash_map.c
+++ b/boot/zephyr/flash_map.c
@@ -197,12 +197,11 @@
     return slot + FLASH_AREA_IMAGE_0;
 }
 
-#ifndef FLASH_AREA_IMAGE_SECTOR_SIZE
-#warning "Missing FLASH_AREA_IMAGE_SECTOR_SIZE; assuming scratch size instead"
-#define FLASH_AREA_IMAGE_SECTOR_SIZE FLASH_AREA_IMAGE_SCRATCH_SIZE
-#endif
-
-static int validate_idx(int idx, uint32_t *off, uint32_t *len)
+/*
+ * This is used by the legacy file as well; don't mark it static until
+ * that file is removed.
+ */
+int flash_area_get_bounds(int idx, uint32_t *off, uint32_t *len)
 {
     /*
      * This simple layout has uniform slots, so just fill in the
@@ -230,92 +229,132 @@
         return -1;
     }
 
-    BOOT_LOG_DBG("area %d: offset=0x%x, length=0x%x, sector size=0x%x",
-                 idx, *off, *len, FLASH_AREA_IMAGE_SECTOR_SIZE);
-    return 0;
-}
-
-int flash_area_to_sectors(int idx, int *cnt, struct flash_area *ret)
-{
-    uint32_t off;
-    uint32_t len;
-    uint32_t max_cnt = *cnt;
-    uint32_t rem_len;
-
-    if (validate_idx(idx, &off, &len)) {
-        return -1;
-    }
-
-    if (*cnt < 1) {
-        return -1;
-    }
-
-    rem_len = len;
-    *cnt = 0;
-    while (rem_len > 0 && *cnt < max_cnt) {
-        if (rem_len < FLASH_AREA_IMAGE_SECTOR_SIZE) {
-            BOOT_LOG_ERR("area %d size 0x%x not divisible by sector size 0x%x",
-                     idx, len, FLASH_AREA_IMAGE_SECTOR_SIZE);
-            return -1;
-        }
-
-        ret[*cnt].fa_id = idx;
-        ret[*cnt].fa_device_id = 0;
-        ret[*cnt].pad16 = 0;
-        ret[*cnt].fa_off = off + (FLASH_AREA_IMAGE_SECTOR_SIZE * (*cnt));
-        ret[*cnt].fa_size = FLASH_AREA_IMAGE_SECTOR_SIZE;
-        *cnt = *cnt + 1;
-        rem_len -= FLASH_AREA_IMAGE_SECTOR_SIZE;
-    }
-
-    if (*cnt >= max_cnt) {
-        BOOT_LOG_ERR("flash area %d sector count overflow", idx);
-        return -1;
-    }
-
+    BOOT_LOG_DBG("area %d: offset=0x%x, length=0x%x", idx, *off, *len);
     return 0;
 }
 
 /*
- * Lookup the sector map for a given flash area.  This should fill in
- * `ret` with all of the sectors in the area.  `*cnt` will be set to
- * the storage at `ret` and should be set to the final number of
- * sectors in this area.
+ * The legacy fallbacks are used instead if the flash driver doesn't
+ * provide page layout support.
  */
+#if defined(CONFIG_FLASH_PAGE_LAYOUT)
+struct layout_data {
+    uint32_t area_idx;
+    uint32_t area_off;
+    uint32_t area_len;
+    void *ret;        /* struct flash_area* or struct flash_sector* */
+    uint32_t ret_idx;
+    uint32_t ret_len;
+    int status;
+};
+
+/*
+ * Generic page layout discovery routine. This is kept separate to
+ * support both the deprecated flash_area_to_sectors() and the current
+ * flash_area_get_sectors(). A lot of this can be inlined once
+ * flash_area_to_sectors() is removed.
+ */
+static int flash_area_layout(int idx, int *cnt, void *ret,
+                             flash_page_cb cb, struct layout_data *cb_data)
+{
+    cb_data->area_idx = idx;
+    if (flash_area_get_bounds(idx, &cb_data->area_off, &cb_data->area_len)) {
+        return -1;
+    }
+    cb_data->ret = ret;
+    cb_data->ret_idx = 0;
+    cb_data->ret_len = *cnt;
+    cb_data->status = 0;
+
+    flash_page_foreach(boot_flash_device, cb, cb_data);
+
+    if (cb_data->status == 0) {
+        *cnt = cb_data->ret_idx;
+    }
+
+    return cb_data->status;
+}
+
+/*
+ * Check if a flash_page_foreach() callback should exit early, due to
+ * one of the following conditions:
+ *
+ * - The flash page described by "info" is before the area of interest
+ *   described in "data"
+ * - The flash page is after the end of the area
+ * - There are too many flash pages on the device to fit in the array
+ *   held in data->ret. In this case, data->status is set to -ENOMEM.
+ *
+ * The value to return to flash_page_foreach() is stored in
+ * "bail_value" if the callback should exit early.
+ */
+static bool should_bail(const struct flash_pages_info *info,
+                        struct layout_data *data,
+                        bool *bail_value)
+{
+    if (info->start_offset < data->area_off) {
+        *bail_value = true;
+        return true;
+    } else if (info->start_offset >= data->area_off + data->area_len) {
+        *bail_value = false;
+        return true;
+    } else if (data->ret_idx >= data->ret_len) {
+        data->status = -ENOMEM;
+        *bail_value = false;
+        return true;
+    }
+
+    return false;
+}
+
+static bool to_sectors_cb(const struct flash_pages_info *info, void *datav)
+{
+    struct layout_data *data = datav;
+    struct flash_area *ret = data->ret;
+    bool bail;
+
+    if (should_bail(info, data, &bail)) {
+        return bail;
+    }
+
+    ret[data->ret_idx].fa_id = data->area_idx;
+    ret[data->ret_idx].fa_device_id = 0;
+    ret[data->ret_idx].pad16 = 0;
+    ret[data->ret_idx].fa_off = info->start_offset;
+    ret[data->ret_idx].fa_size = info->size;
+    data->ret_idx++;
+
+    return true;
+}
+
+int flash_area_to_sectors(int idx, int *cnt, struct flash_area *ret)
+{
+    struct layout_data data;
+
+    return flash_area_layout(idx, cnt, ret, to_sectors_cb, &data);
+}
+
+static bool get_sectors_cb(const struct flash_pages_info *info, void *datav)
+{
+    struct layout_data *data = datav;
+    struct flash_sector *ret = data->ret;
+    bool bail;
+
+    if (should_bail(info, data, &bail)) {
+        return bail;
+    }
+
+    ret[data->ret_idx].fs_off = info->start_offset - data->area_off;
+    ret[data->ret_idx].fs_size = info->size;
+    data->ret_idx++;
+
+    return true;
+}
+
 int flash_area_get_sectors(int idx, uint32_t *cnt, struct flash_sector *ret)
 {
-    uint32_t off;
-    uint32_t len;
-    uint32_t max_cnt = *cnt;
-    uint32_t rem_len;
+    struct layout_data data;
 
-    if (validate_idx(idx, &off, &len)) {
-        return -1;
-    }
-
-    if (*cnt < 1) {
-        return -1;
-    }
-
-    rem_len = len;
-    *cnt = 0;
-    while (rem_len > 0 && *cnt < max_cnt) {
-        if (rem_len < FLASH_AREA_IMAGE_SECTOR_SIZE) {
-            BOOT_LOG_ERR("area %d size 0x%x not divisible by sector size 0x%x",
-                         idx, len, FLASH_AREA_IMAGE_SECTOR_SIZE);
-            return -1;
-        }
-
-        ret[*cnt].fs_off = FLASH_AREA_IMAGE_SECTOR_SIZE * (*cnt);
-        ret[*cnt].fs_size = FLASH_AREA_IMAGE_SECTOR_SIZE;
-        *cnt = *cnt + 1;
-        rem_len -= FLASH_AREA_IMAGE_SECTOR_SIZE;
-    }
-
-    if (*cnt >= max_cnt) {
-        BOOT_LOG_ERR("flash area %d sector count overflow", idx);
-        return -1;
-    }
-
-    return 0;
+    return flash_area_layout(idx, cnt, ret, get_sectors_cb, &data);
 }
+#endif /* defined(CONFIG_FLASH_PAGE_LAYOUT) */
diff --git a/boot/zephyr/flash_map_legacy.c b/boot/zephyr/flash_map_legacy.c
new file mode 100644
index 0000000..2bfa182
--- /dev/null
+++ b/boot/zephyr/flash_map_legacy.c
@@ -0,0 +1,138 @@
+/*
+ * 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.
+ */
+
+/**
+ * @file
+ * @brief Legacy flash fallbacks
+ *
+ * This file contains hacks for flash drivers without page layout
+ * support. They're hacks because they guess a page layout that may be
+ * incorrect, but is likely to "work". Needless to say, such guesswork
+ * is undesirable in trusted bootloader code.
+ *
+ * The behavior is:
+ *
+ * - If FLASH_AREA_IMAGE_SECTOR_SIZE is defined (this was used by
+ *   older Zephyr ports), the image sectors have uniform sector sizes.
+ *   We also assume that's the size of the scratch sectors.
+ *
+ * - Otherwise, we assume that the size of the entire scratch area is
+ *   a least common multiple of all sector sizes, and use that as
+ *   FLASH_AREA_IMAGE_SECTOR_SIZE.
+ */
+
+#define BOOT_LOG_LEVEL BOOT_LOG_LEVEL_INFO
+#include "bootutil/bootutil_log.h"
+
+#include <flash_map/flash_map.h>
+#include <inttypes.h>
+#include <target.h>
+
+#warning "The flash driver lacks page layout support; falling back on hacks."
+
+#if !defined(FLASH_AREA_IMAGE_SECTOR_SIZE)
+#define FLASH_AREA_IMAGE_SECTOR_SIZE FLASH_AREA_IMAGE_SCRATCH_SIZE
+#endif
+
+extern int flash_area_get_bounds(int idx, uint32_t *off, uint32_t *len);
+
+int flash_area_to_sectors(int idx, int *cnt, struct flash_area *ret)
+{
+    uint32_t off;
+    uint32_t len;
+    uint32_t max_cnt = *cnt;
+    uint32_t rem_len;
+
+    if (flash_area_get_bounds(idx, &off, &len)) {
+        return -1;
+    }
+
+    if (*cnt < 1) {
+        return -1;
+    }
+
+    rem_len = len;
+    *cnt = 0;
+    while (rem_len > 0 && *cnt < max_cnt) {
+        if (rem_len < FLASH_AREA_IMAGE_SECTOR_SIZE) {
+            BOOT_LOG_ERR("area %d size 0x%x not divisible by sector size 0x%x",
+                     idx, len, FLASH_AREA_IMAGE_SECTOR_SIZE);
+            return -1;
+        }
+
+        ret[*cnt].fa_id = idx;
+        ret[*cnt].fa_device_id = 0;
+        ret[*cnt].pad16 = 0;
+        ret[*cnt].fa_off = off + (FLASH_AREA_IMAGE_SECTOR_SIZE * (*cnt));
+        ret[*cnt].fa_size = FLASH_AREA_IMAGE_SECTOR_SIZE;
+        *cnt = *cnt + 1;
+        rem_len -= FLASH_AREA_IMAGE_SECTOR_SIZE;
+    }
+
+    if (*cnt >= max_cnt) {
+        BOOT_LOG_ERR("flash area %d sector count overflow", idx);
+        return -1;
+    }
+
+    return 0;
+}
+
+/*
+ * Lookup the sector map for a given flash area.  This should fill in
+ * `ret` with all of the sectors in the area.  `*cnt` will be set to
+ * the storage at `ret` and should be set to the final number of
+ * sectors in this area.
+ */
+int flash_area_get_sectors(int idx, uint32_t *cnt, struct flash_sector *ret)
+{
+    uint32_t off;
+    uint32_t len;
+    uint32_t max_cnt = *cnt;
+    uint32_t rem_len;
+
+    if (flash_area_get_bounds(idx, &off, &len)) {
+        return -1;
+    }
+
+    if (*cnt < 1) {
+        return -1;
+    }
+
+    rem_len = len;
+    *cnt = 0;
+    while (rem_len > 0 && *cnt < max_cnt) {
+        if (rem_len < FLASH_AREA_IMAGE_SECTOR_SIZE) {
+            BOOT_LOG_ERR("area %d size 0x%x not divisible by sector size 0x%x",
+                         idx, len, FLASH_AREA_IMAGE_SECTOR_SIZE);
+            return -1;
+        }
+
+        ret[*cnt].fs_off = FLASH_AREA_IMAGE_SECTOR_SIZE * (*cnt);
+        ret[*cnt].fs_size = FLASH_AREA_IMAGE_SECTOR_SIZE;
+        *cnt = *cnt + 1;
+        rem_len -= FLASH_AREA_IMAGE_SECTOR_SIZE;
+    }
+
+    if (*cnt >= max_cnt) {
+        BOOT_LOG_ERR("flash area %d sector count overflow", idx);
+        return -1;
+    }
+
+    return 0;
+}
diff --git a/boot/zephyr/include/target.h b/boot/zephyr/include/target.h
index 8a3b14d..9ccc032 100644
--- a/boot/zephyr/include/target.h
+++ b/boot/zephyr/include/target.h
@@ -16,19 +16,16 @@
 /*
  * Otherwise, the Zephyr SoC header and the DTS provide most
  * everything we need.
- *
- * TODO: remove soc_family_foo.h once image sector sizes come from the
- * flash driver.
  */
 #include <soc.h>
 
 #define FLASH_ALIGN FLASH_WRITE_BLOCK_SIZE
 
-#if defined(CONFIG_SOC_FAMILY_NRF5)
-#include "soc_family_nrf5.h"
-#elif defined(CONFIG_SOC_FAMILY_STM32)
-#include "soc_family_stm32.h"
-#elif defined(CONFIG_SOC_FAMILY_KINETIS)
+/*
+ * TODO: remove soc_family_kinetis.h once its flash driver supports
+ * FLASH_PAGE_LAYOUT.
+ */
+#if defined(CONFIG_SOC_FAMILY_KINETIS)
 #include "soc_family_kinetis.h"
 #endif
 #endif /* !defined(MCUBOOT_TARGET_CONFIG) */
diff --git a/boot/zephyr/targets/soc_family_nrf5.h b/boot/zephyr/targets/soc_family_nrf5.h
deleted file mode 100644
index 7fbdb71..0000000
--- a/boot/zephyr/targets/soc_family_nrf5.h
+++ /dev/null
@@ -1,6 +0,0 @@
-/*
- *  Copyright (C) 2017, Linaro Ltd
- *  SPDX-License-Identifier: Apache-2.0
- */
-
-#define FLASH_AREA_IMAGE_SECTOR_SIZE (NRF_FICR->CODEPAGESIZE)
diff --git a/boot/zephyr/targets/soc_family_stm32.h b/boot/zephyr/targets/soc_family_stm32.h
deleted file mode 100644
index 4e7b9e3..0000000
--- a/boot/zephyr/targets/soc_family_stm32.h
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- *  Copyright (C) 2017, Linaro Ltd
- *  SPDX-License-Identifier: Apache-2.0
- */
-
-#if defined(CONFIG_SOC_SERIES_STM32F4X)
-/*
- * The Zephyr flash driver will let us remove the need for
- * FLASH_AREA_IMAGE_SECTOR_SIZE at some point. It doesn't make sense
- * on these targets, anyway, as they have variable sized sectors.
- *
- * For now, let's rely on the fact that on all STM32F4 chips with at
- * most 16 flash sectors, all of the sectors after the first 128 KB
- * are equal sized.
- */
-#if (!defined(FLASH_AREA_IMAGE_SECTOR_SIZE) && FLASH_SECTOR_TOTAL <= 16 && \
-     FLASH_AREA_IMAGE_0_OFFSET >= KB(128))
-#define FLASH_AREA_IMAGE_SECTOR_SIZE    0x20000
-#endif
-#elif defined(CONFIG_SOC_SERIES_STM32L4X) /* !CONFIG_SOC_SERIES_STM32F4X */
-#define FLASH_AREA_IMAGE_SECTOR_SIZE    FLASH_PAGE_SIZE /* from the HAL */
-#endif /* CONFIG_SOC_SERIES_STM32F4X */