soc: espressif: Simple boot support

Add simplistic booting method which allows to load without 2nd stage
bootloader.
Update common architecture loader to support all build scenarios.

- simple boot: using single binary application without bootloader
- mcuboot: zephyr port of MCUboot
- application: loaded by the MCUboot

Signed-off-by: Marek Matej <marek.matej@espressif.com>
diff --git a/soc/espressif/common/Kconfig.flash b/soc/espressif/common/Kconfig.flash
index 3dd465c..2ed5d3f 100644
--- a/soc/espressif/common/Kconfig.flash
+++ b/soc/espressif/common/Kconfig.flash
@@ -127,4 +127,12 @@
 	  This option is invisible, and will be selected automatically
 	  when ``ESPTOOLPY_FLASHFREQ_120M`` is selected.
 
+config ESP_SIMPLE_BOOT
+	bool "Simple Boot method"
+	default y if !BOOTLOADER_MCUBOOT
+	help
+	  The Simple Boot is a method of booting that doesn't depend on a
+	  2nd stage bootloader. Please note that some of the bootloader features
+	  are not available using simple boot, such secure boot and OTA.
+
 endif # SOC_FAMILY_ESPRESSIF_ESP32
diff --git a/soc/espressif/common/loader.c b/soc/espressif/common/loader.c
index 6d1b7ec..e563f67 100644
--- a/soc/espressif/common/loader.c
+++ b/soc/espressif/common/loader.c
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2023 Espressif Systems (Shanghai) Co., Ltd.
+ * Copyright (c) 2024 Espressif Systems (Shanghai) Co., Ltd.
  *
  * SPDX-License-Identifier: Apache-2.0
  */
@@ -12,29 +12,64 @@
 #include <hal/cache_hal.h>
 #include <rom/cache.h>
 #include <esp_rom_sys.h>
+#include <esp_err.h>
 
 #define MMU_FLASH_MASK    (~(CONFIG_MMU_PAGE_SIZE - 1))
 
-#ifdef CONFIG_BOOTLOADER_MCUBOOT
+#include <esp_app_format.h>
 #include <zephyr/storage/flash_map.h>
 #include "esp_rom_uart.h"
+#include "esp_flash.h"
+#include "esp_log.h"
+#include "bootloader_init.h"
+
+#define TAG "boot"
+
+#define CHECKSUM_ALIGN 16
+#define IS_PADD(addr) (addr == 0)
+#define IS_DRAM(addr) (addr >= SOC_DRAM_LOW && addr < SOC_DRAM_HIGH)
+#define IS_IRAM(addr) (addr >= SOC_IRAM_LOW && addr < SOC_IRAM_HIGH)
+#define IS_IROM(addr) (addr >= SOC_IROM_LOW && addr < SOC_IROM_HIGH)
+#define IS_DROM(addr) (addr >= SOC_DROM_LOW && addr < SOC_DROM_HIGH)
+#define IS_SRAM(addr) (IS_IRAM(addr) || IS_DRAM(addr))
+#define IS_MMAP(addr) (IS_IROM(addr) || IS_DROM(addr))
+#define IS_NONE(addr) (!IS_IROM(addr) && !IS_DROM(addr) \
+			&& !IS_IRAM(addr) && !IS_DRAM(addr) && !IS_PADD(addr))
 
 #define BOOT_LOG_INF(_fmt, ...) \
 	ets_printf("[" CONFIG_SOC_SERIES "] [INF] " _fmt "\n\r", ##__VA_ARGS__)
+#define BOOT_LOG_ERR(_fmt, ...) \
+	ets_printf("[" CONFIG_SOC_SERIES "] [ERR] " _fmt "\n\r", ##__VA_ARGS__)
 
 #define HDR_ATTR __attribute__((section(".entry_addr"))) __attribute__((used))
 
 void __start(void);
-
 static HDR_ATTR void (*_entry_point)(void) = &__start;
 
+extern esp_image_header_t bootloader_image_hdr;
 extern uint32_t _image_irom_start, _image_irom_size, _image_irom_vaddr;
 extern uint32_t _image_drom_start, _image_drom_size, _image_drom_vaddr;
+
+static uint32_t _app_irom_start = (FIXED_PARTITION_OFFSET(slot0_partition) +
+						(uint32_t)&_image_irom_start);
+static uint32_t _app_irom_size = (uint32_t)&_image_irom_size;
+static uint32_t _app_irom_vaddr = ((uint32_t)&_image_irom_vaddr);
+
+static uint32_t _app_drom_start = (FIXED_PARTITION_OFFSET(slot0_partition) +
+						(uint32_t)&_image_drom_start);
+static uint32_t _app_drom_size = (uint32_t)&_image_drom_size;
+static uint32_t _app_drom_vaddr = ((uint32_t)&_image_drom_vaddr);
+
+#ifndef CONFIG_BOOTLOADER_MCUBOOT
+static esp_err_t spi_flash_read(uint32_t address, void *buffer, size_t length)
+{
+	return esp_flash_read(NULL, buffer, address, length);
+}
 #endif /* CONFIG_BOOTLOADER_MCUBOOT */
 
 void map_rom_segments(uint32_t app_drom_start, uint32_t app_drom_vaddr,
-				uint32_t app_drom_size, uint32_t app_irom_start,
-				uint32_t app_irom_vaddr, uint32_t app_irom_size)
+			uint32_t app_drom_size, uint32_t app_irom_start,
+			uint32_t app_irom_vaddr, uint32_t app_irom_size)
 {
 	uint32_t app_irom_start_aligned = app_irom_start & MMU_FLASH_MASK;
 	uint32_t app_irom_vaddr_aligned = app_irom_vaddr & MMU_FLASH_MASK;
@@ -43,12 +78,74 @@
 	uint32_t app_drom_vaddr_aligned = app_drom_vaddr & MMU_FLASH_MASK;
 	uint32_t actual_mapped_len = 0;
 
+#ifndef CONFIG_BOOTLOADER_MCUBOOT
+	esp_image_segment_header_t WORD_ALIGNED_ATTR segment_hdr;
+	size_t offset = FIXED_PARTITION_OFFSET(boot_partition);
+	bool checksum = false;
+	unsigned int segments = 0;
+	unsigned int ram_segments = 0;
+
+	/* Using already fetched bootloader image header from bootloader_init */
+	offset += sizeof(esp_image_header_t);
+
+	while (segments++ < 16) {
+
+		if (spi_flash_read(offset, &segment_hdr,
+				sizeof(esp_image_segment_header_t)) != ESP_OK) {
+			BOOT_LOG_ERR("Failed to read segment header at %x", offset);
+			abort();
+		}
+
+		/* TODO: Find better end-of-segment detection */
+		if (IS_NONE(segment_hdr.load_addr)) {
+			/* Total segment count = (segments - 1) */
+			break;
+		}
+
+		BOOT_LOG_INF("%s: lma 0x%08x vma 0x%08x len 0x%-6x (%u)",
+			IS_NONE(segment_hdr.load_addr) ? "???" :
+			 IS_MMAP(segment_hdr.load_addr) ?
+			  IS_IROM(segment_hdr.load_addr) ? "IMAP" : "DMAP" :
+			   IS_PADD(segment_hdr.load_addr) ? "padd" :
+			    IS_DRAM(segment_hdr.load_addr) ? "DRAM" : "IRAM",
+			offset + sizeof(esp_image_segment_header_t),
+			segment_hdr.load_addr, segment_hdr.data_len, segment_hdr.data_len);
+
+		/* Fix drom and irom produced be the linker, as it could
+		 * be invalidated by the elf2image and flash load offset
+		 */
+		if (segment_hdr.load_addr == _app_drom_vaddr) {
+			app_drom_start = offset + sizeof(esp_image_segment_header_t);
+			app_drom_start_aligned = app_drom_start & MMU_FLASH_MASK;
+		}
+		if (segment_hdr.load_addr == _app_irom_vaddr) {
+			app_irom_start = offset + sizeof(esp_image_segment_header_t);
+			app_irom_start_aligned = app_irom_start & MMU_FLASH_MASK;
+		}
+		if (IS_SRAM(segment_hdr.load_addr)) {
+			ram_segments++;
+		}
+		offset += sizeof(esp_image_segment_header_t) + segment_hdr.data_len;
+
+		if (ram_segments == bootloader_image_hdr.segment_count && !checksum) {
+			offset += (CHECKSUM_ALIGN - 1) - (offset % CHECKSUM_ALIGN) + 1;
+			checksum = true;
+		}
+	}
+	if (segments == 0 || segments == 16) {
+		BOOT_LOG_ERR("Error parsing segments");
+		abort();
+	}
+
+	BOOT_LOG_INF("Image with %d segments", segments - 1);
+#endif /* !CONFIG_BOOTLOADER_MCUBOOT */
+
 #if CONFIG_SOC_SERIES_ESP32
 	Cache_Read_Disable(0);
 	Cache_Flush(0);
 #else
 	cache_hal_disable(CACHE_TYPE_ALL);
-#endif
+#endif /* CONFIG_SOC_SERIES_ESP32 */
 
 	/* Clear the MMU entries that are already set up,
 	 * so the new app only has the mappings it creates.
@@ -58,31 +155,33 @@
 #if CONFIG_SOC_SERIES_ESP32
 	int rc = 0;
 	uint32_t drom_page_count =
-				(app_drom_size + CONFIG_MMU_PAGE_SIZE - 1) / CONFIG_MMU_PAGE_SIZE;
+			(app_drom_size + CONFIG_MMU_PAGE_SIZE - 1) / CONFIG_MMU_PAGE_SIZE;
 
 	rc |= cache_flash_mmu_set(0, 0, app_drom_vaddr_aligned,
-						app_drom_start_aligned, 64, drom_page_count);
+					app_drom_start_aligned, 64, drom_page_count);
 	rc |= cache_flash_mmu_set(1, 0, app_drom_vaddr_aligned,
-						app_drom_start_aligned, 64, drom_page_count);
+					app_drom_start_aligned, 64, drom_page_count);
 
 	uint32_t irom_page_count =
-				(app_irom_size + CONFIG_MMU_PAGE_SIZE - 1) / CONFIG_MMU_PAGE_SIZE;
+			(app_irom_size + CONFIG_MMU_PAGE_SIZE - 1) / CONFIG_MMU_PAGE_SIZE;
 
 	rc |= cache_flash_mmu_set(0, 0, app_irom_vaddr_aligned,
-						app_irom_start_aligned, 64, irom_page_count);
+					app_irom_start_aligned, 64, irom_page_count);
 	rc |= cache_flash_mmu_set(1, 0, app_irom_vaddr_aligned,
-						app_irom_start_aligned, 64, irom_page_count);
+					app_irom_start_aligned, 64, irom_page_count);
 	if (rc != 0) {
-		esp_rom_printf("Failed to setup XIP, aborting\n");
+		BOOT_LOG_ERR("Failed to setup XIP, aborting");
 		abort();
 	}
 #else
-	mmu_hal_map_region(0, MMU_TARGET_FLASH0, app_drom_vaddr_aligned, app_drom_start_aligned,
+	mmu_hal_map_region(0, MMU_TARGET_FLASH0,
+				app_drom_vaddr_aligned, app_drom_start_aligned,
 				app_drom_size, &actual_mapped_len);
 
-	mmu_hal_map_region(0, MMU_TARGET_FLASH0, app_irom_vaddr_aligned, app_irom_start_aligned,
+	mmu_hal_map_region(0, MMU_TARGET_FLASH0,
+				app_irom_vaddr_aligned, app_irom_start_aligned,
 				app_irom_size, &actual_mapped_len);
-#endif
+#endif /* CONFIG_SOC_SERIES_ESP32 */
 
 	/* ----------------------Enable corresponding buses---------------- */
 	cache_bus_mask_t bus_mask = cache_ll_l1_get_bus(0, app_drom_vaddr_aligned, app_drom_size);
@@ -103,31 +202,42 @@
 	Cache_Read_Enable(0);
 #else
 	cache_hal_enable(CACHE_TYPE_ALL);
-#endif
+#endif /* CONFIG_SOC_SERIES_ESP32 */
+
+	/* Show map segments continue using same log format as during MCUboot phase */
+	BOOT_LOG_INF("DROM segment: paddr=%08xh, vaddr=%08xh, size=%05Xh (%6d) map",
+			app_drom_start_aligned, app_drom_vaddr_aligned,
+			app_drom_size, app_drom_size);
+	BOOT_LOG_INF("IROM segment: paddr=%08xh, vaddr=%08xh, size=%05Xh (%6d) map\r\n",
+			app_irom_start_aligned, app_irom_vaddr_aligned,
+			app_irom_size, app_irom_size);
+	esp_rom_uart_tx_wait_idle(0);
 }
 
 void __start(void)
 {
-#ifdef CONFIG_BOOTLOADER_MCUBOOT
-	size_t _partition_offset = FIXED_PARTITION_OFFSET(slot0_partition);
-	uint32_t _app_irom_start = (_partition_offset + (uint32_t)&_image_irom_start);
-	uint32_t _app_irom_size = (uint32_t)&_image_irom_size;
-	uint32_t _app_irom_vaddr = ((uint32_t)&_image_irom_vaddr);
+#ifdef CONFIG_RISCV_GP
+	/* Configure the global pointer register
+	 * (This should be the first thing startup does, as any other piece of code could be
+	 * relaxed by the linker to access something relative to __global_pointer$)
+	 */
+	__asm__ __volatile__(".option push\n"
+				".option norelax\n"
+				"la gp, __global_pointer$\n"
+				".option pop");
+#endif /* CONFIG_RISCV_GP */
 
-	uint32_t _app_drom_start = (_partition_offset + (uint32_t)&_image_drom_start);
-	uint32_t _app_drom_size = (uint32_t)&_image_drom_size;
-	uint32_t _app_drom_vaddr = ((uint32_t)&_image_drom_vaddr);
-	uint32_t actual_mapped_len = 0;
+#ifndef CONFIG_BOOTLOADER_MCUBOOT
+	/* Init fundamental components */
+	if (bootloader_init()) {
+		BOOT_LOG_ERR("HW init failed, aborting");
+		abort();
+	}
+#endif
 
+#ifndef CONFIG_MCUBOOT
 	map_rom_segments(_app_drom_start, _app_drom_vaddr, _app_drom_size,
-				_app_irom_start, _app_irom_vaddr, _app_irom_size);
-
-	/* Show map segments continue using same log format as during MCUboot phase */
-	BOOT_LOG_INF("DROM segment: paddr=%08Xh, vaddr=%08Xh, size=%05Xh (%6d) map",
-		_app_drom_start, _app_drom_vaddr, _app_drom_size, _app_drom_size);
-	BOOT_LOG_INF("IROM segment: paddr=%08Xh, vaddr=%08Xh, size=%05Xh (%6d) map\r\n",
-		_app_irom_start, _app_irom_vaddr, _app_irom_size, _app_irom_size);
-	esp_rom_uart_tx_wait_idle(0);
+			 _app_irom_start, _app_irom_vaddr, _app_irom_size);
 #endif
 	__esp_platform_start();
 }