Shubham Kulkarni | 052561d | 2021-07-20 11:42:44 +0530 | [diff] [blame] | 1 | /* |
Almir Okato | 14763b1 | 2021-11-25 00:45:26 -0300 | [diff] [blame] | 2 | * SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD |
Shubham Kulkarni | 052561d | 2021-07-20 11:42:44 +0530 | [diff] [blame] | 3 | * |
| 4 | * SPDX-License-Identifier: Apache-2.0 |
| 5 | */ |
| 6 | |
Gustavo Henrique Nihei | d985d22 | 2021-11-12 14:21:12 -0300 | [diff] [blame] | 7 | #include <stdbool.h> |
Shubham Kulkarni | 052561d | 2021-07-20 11:42:44 +0530 | [diff] [blame] | 8 | #include <stdlib.h> |
Gustavo Henrique Nihei | d985d22 | 2021-11-12 14:21:12 -0300 | [diff] [blame] | 9 | #include <string.h> |
Shubham Kulkarni | 052561d | 2021-07-20 11:42:44 +0530 | [diff] [blame] | 10 | |
Gustavo Henrique Nihei | d985d22 | 2021-11-12 14:21:12 -0300 | [diff] [blame] | 11 | #include <bootutil/bootutil.h> |
| 12 | #include <bootutil/bootutil_log.h> |
Shubham Kulkarni | 052561d | 2021-07-20 11:42:44 +0530 | [diff] [blame] | 13 | |
Almir Okato | 14763b1 | 2021-11-25 00:45:26 -0300 | [diff] [blame] | 14 | #include "sdkconfig.h" |
Shubham Kulkarni | cd86965 | 2021-07-20 11:44:08 +0530 | [diff] [blame] | 15 | #include "esp_err.h" |
Shubham Kulkarni | cd86965 | 2021-07-20 11:44:08 +0530 | [diff] [blame] | 16 | #include "bootloader_flash_priv.h" |
Almir Okato | 14763b1 | 2021-11-25 00:45:26 -0300 | [diff] [blame] | 17 | #include "esp_flash_encrypt.h" |
Shubham Kulkarni | cd86965 | 2021-07-20 11:44:08 +0530 | [diff] [blame] | 18 | |
Gustavo Henrique Nihei | d985d22 | 2021-11-12 14:21:12 -0300 | [diff] [blame] | 19 | #include "flash_map_backend/flash_map_backend.h" |
| 20 | #include "sysflash/sysflash.h" |
| 21 | |
Gustavo Henrique Nihei | 74a2742 | 2021-10-29 09:25:55 -0300 | [diff] [blame] | 22 | #ifndef ARRAY_SIZE |
| 23 | # define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0])) |
| 24 | #endif |
| 25 | |
| 26 | #ifndef MIN |
| 27 | # define MIN(a, b) (((a) < (b)) ? (a) : (b)) |
| 28 | #endif |
| 29 | |
| 30 | #ifndef ALIGN_UP |
| 31 | # define ALIGN_UP(num, align) (((num) + ((align) - 1)) & ~((align) - 1)) |
| 32 | #endif |
| 33 | |
| 34 | #ifndef ALIGN_DOWN |
| 35 | # define ALIGN_DOWN(num, align) ((num) & ~((align) - 1)) |
| 36 | #endif |
| 37 | |
| 38 | #ifndef ALIGN_OFFSET |
| 39 | # define ALIGN_OFFSET(num, align) ((num) & ((align) - 1)) |
| 40 | #endif |
| 41 | |
| 42 | #ifndef IS_ALIGNED |
| 43 | # define IS_ALIGNED(num, align) (ALIGN_OFFSET((num), (align)) == 0) |
| 44 | #endif |
| 45 | |
| 46 | #define FLASH_BUFFER_SIZE 256 /* SPI Flash block size */ |
| 47 | |
| 48 | _Static_assert(IS_ALIGNED(FLASH_BUFFER_SIZE, 4), "Buffer size for SPI Flash operations must be 4-byte aligned."); |
Shubham Kulkarni | 052561d | 2021-07-20 11:42:44 +0530 | [diff] [blame] | 49 | |
Almir Okato | 14763b1 | 2021-11-25 00:45:26 -0300 | [diff] [blame] | 50 | #define BOOTLOADER_START_ADDRESS CONFIG_BOOTLOADER_OFFSET_IN_FLASH |
Shubham Kulkarni | 052561d | 2021-07-20 11:42:44 +0530 | [diff] [blame] | 51 | #define BOOTLOADER_SIZE CONFIG_ESP_BOOTLOADER_SIZE |
Almir Okato | a1d641d | 2022-02-21 19:31:46 -0300 | [diff] [blame^] | 52 | #define IMAGE0_PRIMARY_START_ADDRESS CONFIG_ESP_IMAGE0_PRIMARY_START_ADDRESS |
| 53 | #define IMAGE0_SECONDARY_START_ADDRESS CONFIG_ESP_IMAGE0_SECONDARY_START_ADDRESS |
Shubham Kulkarni | 052561d | 2021-07-20 11:42:44 +0530 | [diff] [blame] | 54 | #define SCRATCH_OFFSET CONFIG_ESP_SCRATCH_OFFSET |
Almir Okato | a1d641d | 2022-02-21 19:31:46 -0300 | [diff] [blame^] | 55 | #if (MCUBOOT_IMAGE_NUMBER == 2) |
| 56 | #define IMAGE1_PRIMARY_START_ADDRESS CONFIG_ESP_IMAGE1_PRIMARY_START_ADDRESS |
| 57 | #define IMAGE1_SECONDARY_START_ADDRESS CONFIG_ESP_IMAGE1_SECONDARY_START_ADDRESS |
| 58 | #endif |
| 59 | |
| 60 | #define APPLICATION_SIZE CONFIG_ESP_APPLICATION_SIZE |
Shubham Kulkarni | 052561d | 2021-07-20 11:42:44 +0530 | [diff] [blame] | 61 | #define SCRATCH_SIZE CONFIG_ESP_SCRATCH_SIZE |
| 62 | |
Shubham Kulkarni | 052561d | 2021-07-20 11:42:44 +0530 | [diff] [blame] | 63 | extern int ets_printf(const char *fmt, ...); |
| 64 | |
| 65 | static const struct flash_area bootloader = { |
| 66 | .fa_id = FLASH_AREA_BOOTLOADER, |
| 67 | .fa_device_id = FLASH_DEVICE_INTERNAL_FLASH, |
| 68 | .fa_off = BOOTLOADER_START_ADDRESS, |
| 69 | .fa_size = BOOTLOADER_SIZE, |
| 70 | }; |
| 71 | |
| 72 | static const struct flash_area primary_img0 = { |
| 73 | .fa_id = FLASH_AREA_IMAGE_PRIMARY(0), |
| 74 | .fa_device_id = FLASH_DEVICE_INTERNAL_FLASH, |
Almir Okato | a1d641d | 2022-02-21 19:31:46 -0300 | [diff] [blame^] | 75 | .fa_off = IMAGE0_PRIMARY_START_ADDRESS, |
Shubham Kulkarni | 052561d | 2021-07-20 11:42:44 +0530 | [diff] [blame] | 76 | .fa_size = APPLICATION_SIZE, |
| 77 | }; |
| 78 | |
| 79 | static const struct flash_area secondary_img0 = { |
| 80 | .fa_id = FLASH_AREA_IMAGE_SECONDARY(0), |
| 81 | .fa_device_id = FLASH_DEVICE_INTERNAL_FLASH, |
Almir Okato | a1d641d | 2022-02-21 19:31:46 -0300 | [diff] [blame^] | 82 | .fa_off = IMAGE0_SECONDARY_START_ADDRESS, |
Shubham Kulkarni | 052561d | 2021-07-20 11:42:44 +0530 | [diff] [blame] | 83 | .fa_size = APPLICATION_SIZE, |
| 84 | }; |
| 85 | |
Almir Okato | a1d641d | 2022-02-21 19:31:46 -0300 | [diff] [blame^] | 86 | #if (MCUBOOT_IMAGE_NUMBER == 2) |
| 87 | static const struct flash_area primary_img1 = { |
| 88 | .fa_id = FLASH_AREA_IMAGE_PRIMARY(1), |
| 89 | .fa_device_id = FLASH_DEVICE_INTERNAL_FLASH, |
| 90 | .fa_off = IMAGE1_PRIMARY_START_ADDRESS, |
| 91 | .fa_size = APPLICATION_SIZE, |
| 92 | }; |
| 93 | |
| 94 | static const struct flash_area secondary_img1 = { |
| 95 | .fa_id = FLASH_AREA_IMAGE_SECONDARY(1), |
| 96 | .fa_device_id = FLASH_DEVICE_INTERNAL_FLASH, |
| 97 | .fa_off = IMAGE1_SECONDARY_START_ADDRESS, |
| 98 | .fa_size = APPLICATION_SIZE, |
| 99 | }; |
| 100 | #endif |
| 101 | |
Shubham Kulkarni | 052561d | 2021-07-20 11:42:44 +0530 | [diff] [blame] | 102 | static const struct flash_area scratch_img0 = { |
| 103 | .fa_id = FLASH_AREA_IMAGE_SCRATCH, |
| 104 | .fa_device_id = FLASH_DEVICE_INTERNAL_FLASH, |
| 105 | .fa_off = SCRATCH_OFFSET, |
| 106 | .fa_size = SCRATCH_SIZE, |
| 107 | }; |
| 108 | |
| 109 | static const struct flash_area *s_flash_areas[] = { |
| 110 | &bootloader, |
| 111 | &primary_img0, |
| 112 | &secondary_img0, |
Almir Okato | a1d641d | 2022-02-21 19:31:46 -0300 | [diff] [blame^] | 113 | #if (MCUBOOT_IMAGE_NUMBER == 2) |
| 114 | &primary_img1, |
| 115 | &secondary_img1, |
| 116 | #endif |
Shubham Kulkarni | 052561d | 2021-07-20 11:42:44 +0530 | [diff] [blame] | 117 | &scratch_img0, |
| 118 | }; |
| 119 | |
| 120 | static const struct flash_area *prv_lookup_flash_area(uint8_t id) { |
| 121 | for (size_t i = 0; i < ARRAY_SIZE(s_flash_areas); i++) { |
| 122 | const struct flash_area *area = s_flash_areas[i]; |
| 123 | if (id == area->fa_id) { |
| 124 | return area; |
| 125 | } |
| 126 | } |
| 127 | return NULL; |
| 128 | } |
| 129 | |
| 130 | int flash_area_open(uint8_t id, const struct flash_area **area_outp) |
| 131 | { |
Gustavo Henrique Nihei | d985d22 | 2021-11-12 14:21:12 -0300 | [diff] [blame] | 132 | BOOT_LOG_DBG("%s: ID=%d", __func__, (int)id); |
Shubham Kulkarni | 052561d | 2021-07-20 11:42:44 +0530 | [diff] [blame] | 133 | const struct flash_area *area = prv_lookup_flash_area(id); |
| 134 | *area_outp = area; |
| 135 | return area != NULL ? 0 : -1; |
| 136 | } |
| 137 | |
| 138 | void flash_area_close(const struct flash_area *area) |
| 139 | { |
| 140 | |
| 141 | } |
| 142 | |
Gustavo Henrique Nihei | 74a2742 | 2021-10-29 09:25:55 -0300 | [diff] [blame] | 143 | static bool aligned_flash_read(uintptr_t addr, void *dest, size_t size) |
| 144 | { |
| 145 | if (IS_ALIGNED(addr, 4) && IS_ALIGNED((uintptr_t)dest, 4) && IS_ALIGNED(size, 4)) { |
| 146 | /* A single read operation is enough when when all parameters are aligned */ |
| 147 | |
| 148 | return bootloader_flash_read(addr, dest, size, true) == ESP_OK; |
| 149 | } |
| 150 | |
| 151 | const uint32_t aligned_addr = ALIGN_DOWN(addr, 4); |
| 152 | const uint32_t addr_offset = ALIGN_OFFSET(addr, 4); |
| 153 | uint32_t bytes_remaining = size; |
| 154 | uint8_t read_data[FLASH_BUFFER_SIZE] = {0}; |
| 155 | |
| 156 | /* Align the read address to 4-byte boundary and ensure read size is a multiple of 4 bytes */ |
| 157 | |
| 158 | uint32_t bytes = MIN(bytes_remaining + addr_offset, sizeof(read_data)); |
| 159 | if (bootloader_flash_read(aligned_addr, read_data, ALIGN_UP(bytes, 4), true) != ESP_OK) { |
| 160 | return false; |
| 161 | } |
| 162 | |
| 163 | /* Skip non-useful data which may have been read for adjusting the alignment */ |
| 164 | |
| 165 | uint32_t bytes_read = bytes - addr_offset; |
| 166 | memcpy(dest, &read_data[addr_offset], bytes_read); |
| 167 | |
| 168 | bytes_remaining -= bytes_read; |
| 169 | |
| 170 | /* Read remaining data from Flash in case requested size is greater than buffer size */ |
| 171 | |
| 172 | uint32_t offset = bytes; |
| 173 | |
| 174 | while (bytes_remaining != 0) { |
| 175 | bytes = MIN(bytes_remaining, sizeof(read_data)); |
| 176 | if (bootloader_flash_read(aligned_addr + offset, read_data, ALIGN_UP(bytes, 4), true) != ESP_OK) { |
| 177 | return false; |
| 178 | } |
| 179 | |
| 180 | memcpy(&((uint8_t *)dest)[bytes_read], read_data, bytes); |
| 181 | |
| 182 | offset += bytes; |
| 183 | bytes_read += bytes; |
| 184 | bytes_remaining -= bytes; |
| 185 | } |
| 186 | |
| 187 | return true; |
| 188 | } |
| 189 | |
Shubham Kulkarni | 052561d | 2021-07-20 11:42:44 +0530 | [diff] [blame] | 190 | int flash_area_read(const struct flash_area *fa, uint32_t off, void *dst, |
| 191 | uint32_t len) |
| 192 | { |
| 193 | if (fa->fa_device_id != FLASH_DEVICE_INTERNAL_FLASH) { |
| 194 | return -1; |
| 195 | } |
| 196 | |
| 197 | const uint32_t end_offset = off + len; |
| 198 | if (end_offset > fa->fa_size) { |
Gustavo Henrique Nihei | d985d22 | 2021-11-12 14:21:12 -0300 | [diff] [blame] | 199 | BOOT_LOG_ERR("%s: Out of Bounds (0x%x vs 0x%x)", __func__, end_offset, fa->fa_size); |
Shubham Kulkarni | 052561d | 2021-07-20 11:42:44 +0530 | [diff] [blame] | 200 | return -1; |
| 201 | } |
Gustavo Henrique Nihei | 74a2742 | 2021-10-29 09:25:55 -0300 | [diff] [blame] | 202 | |
| 203 | bool success = aligned_flash_read(fa->fa_off + off, dst, len); |
| 204 | if (!success) { |
Gustavo Henrique Nihei | d985d22 | 2021-11-12 14:21:12 -0300 | [diff] [blame] | 205 | BOOT_LOG_ERR("%s: Flash read failed", __func__); |
Gustavo Henrique Nihei | 74a2742 | 2021-10-29 09:25:55 -0300 | [diff] [blame] | 206 | |
Shubham Kulkarni | cd86965 | 2021-07-20 11:44:08 +0530 | [diff] [blame] | 207 | return -1; |
| 208 | } |
Gustavo Henrique Nihei | 74a2742 | 2021-10-29 09:25:55 -0300 | [diff] [blame] | 209 | |
Shubham Kulkarni | 052561d | 2021-07-20 11:42:44 +0530 | [diff] [blame] | 210 | return 0; |
| 211 | } |
| 212 | |
| 213 | int flash_area_write(const struct flash_area *fa, uint32_t off, const void *src, |
| 214 | uint32_t len) |
| 215 | { |
| 216 | if (fa->fa_device_id != FLASH_DEVICE_INTERNAL_FLASH) { |
| 217 | return -1; |
| 218 | } |
| 219 | |
| 220 | const uint32_t end_offset = off + len; |
| 221 | if (end_offset > fa->fa_size) { |
Gustavo Henrique Nihei | d985d22 | 2021-11-12 14:21:12 -0300 | [diff] [blame] | 222 | BOOT_LOG_ERR("%s: Out of Bounds (0x%x vs 0x%x)", __func__, end_offset, fa->fa_size); |
Shubham Kulkarni | 052561d | 2021-07-20 11:42:44 +0530 | [diff] [blame] | 223 | return -1; |
| 224 | } |
| 225 | |
Almir Okato | 14763b1 | 2021-11-25 00:45:26 -0300 | [diff] [blame] | 226 | bool flash_encryption_enabled = esp_flash_encryption_enabled(); |
| 227 | |
Shubham Kulkarni | cd86965 | 2021-07-20 11:44:08 +0530 | [diff] [blame] | 228 | const uint32_t start_addr = fa->fa_off + off; |
Gustavo Henrique Nihei | d985d22 | 2021-11-12 14:21:12 -0300 | [diff] [blame] | 229 | BOOT_LOG_DBG("%s: Addr: 0x%08x Length: %d", __func__, (int)start_addr, (int)len); |
Shubham Kulkarni | 052561d | 2021-07-20 11:42:44 +0530 | [diff] [blame] | 230 | |
Almir Okato | 14763b1 | 2021-11-25 00:45:26 -0300 | [diff] [blame] | 231 | if (bootloader_flash_write(start_addr, (void *)src, len, flash_encryption_enabled) != ESP_OK) { |
Gustavo Henrique Nihei | d985d22 | 2021-11-12 14:21:12 -0300 | [diff] [blame] | 232 | BOOT_LOG_ERR("%s: Flash write failed", __func__); |
Shubham Kulkarni | cd86965 | 2021-07-20 11:44:08 +0530 | [diff] [blame] | 233 | return -1; |
| 234 | } |
Shubham Kulkarni | 052561d | 2021-07-20 11:42:44 +0530 | [diff] [blame] | 235 | |
| 236 | return 0; |
| 237 | } |
| 238 | |
| 239 | int flash_area_erase(const struct flash_area *fa, uint32_t off, uint32_t len) |
| 240 | { |
| 241 | if (fa->fa_device_id != FLASH_DEVICE_INTERNAL_FLASH) { |
| 242 | return -1; |
| 243 | } |
| 244 | |
| 245 | if ((len % FLASH_SECTOR_SIZE) != 0 || (off % FLASH_SECTOR_SIZE) != 0) { |
Gustavo Henrique Nihei | d985d22 | 2021-11-12 14:21:12 -0300 | [diff] [blame] | 246 | BOOT_LOG_ERR("%s: Not aligned on sector Offset: 0x%x Length: 0x%x", |
| 247 | __func__, (int)off, (int)len); |
Shubham Kulkarni | 052561d | 2021-07-20 11:42:44 +0530 | [diff] [blame] | 248 | return -1; |
| 249 | } |
| 250 | |
| 251 | const uint32_t start_addr = fa->fa_off + off; |
Gustavo Henrique Nihei | d985d22 | 2021-11-12 14:21:12 -0300 | [diff] [blame] | 252 | BOOT_LOG_DBG("%s: Addr: 0x%08x Length: %d", __func__, (int)start_addr, (int)len); |
Shubham Kulkarni | 052561d | 2021-07-20 11:42:44 +0530 | [diff] [blame] | 253 | |
Shubham Kulkarni | cd86965 | 2021-07-20 11:44:08 +0530 | [diff] [blame] | 254 | if (bootloader_flash_erase_range(start_addr, len) != ESP_OK) { |
Gustavo Henrique Nihei | d985d22 | 2021-11-12 14:21:12 -0300 | [diff] [blame] | 255 | BOOT_LOG_ERR("%s: Flash erase failed", __func__); |
Shubham Kulkarni | cd86965 | 2021-07-20 11:44:08 +0530 | [diff] [blame] | 256 | return -1; |
| 257 | } |
Shubham Kulkarni | 052561d | 2021-07-20 11:42:44 +0530 | [diff] [blame] | 258 | #if VALIDATE_PROGRAM_OP |
| 259 | for (size_t i = 0; i < len; i++) { |
| 260 | uint8_t *val = (void *)(start_addr + i); |
| 261 | if (*val != 0xff) { |
Gustavo Henrique Nihei | d985d22 | 2021-11-12 14:21:12 -0300 | [diff] [blame] | 262 | BOOT_LOG_ERR("%s: Erase at 0x%x Failed", __func__, (int)val); |
Shubham Kulkarni | 052561d | 2021-07-20 11:42:44 +0530 | [diff] [blame] | 263 | assert(0); |
| 264 | } |
| 265 | } |
| 266 | #endif |
| 267 | |
| 268 | return 0; |
| 269 | } |
| 270 | |
Gustavo Henrique Nihei | 4aa286d | 2021-11-24 14:54:56 -0300 | [diff] [blame] | 271 | uint32_t flash_area_align(const struct flash_area *area) |
Shubham Kulkarni | 052561d | 2021-07-20 11:42:44 +0530 | [diff] [blame] | 272 | { |
Almir Okato | 14763b1 | 2021-11-25 00:45:26 -0300 | [diff] [blame] | 273 | static size_t align = 0; |
| 274 | |
| 275 | if (align == 0) { |
| 276 | bool flash_encryption_enabled = esp_flash_encryption_enabled(); |
| 277 | |
| 278 | if (flash_encryption_enabled) { |
| 279 | align = 32; |
| 280 | } else { |
| 281 | align = 4; |
| 282 | } |
| 283 | } |
| 284 | return align; |
Shubham Kulkarni | 052561d | 2021-07-20 11:42:44 +0530 | [diff] [blame] | 285 | } |
| 286 | |
| 287 | uint8_t flash_area_erased_val(const struct flash_area *area) |
| 288 | { |
| 289 | return 0xff; |
| 290 | } |
| 291 | |
| 292 | int flash_area_get_sectors(int fa_id, uint32_t *count, |
| 293 | struct flash_sector *sectors) |
| 294 | { |
| 295 | const struct flash_area *fa = prv_lookup_flash_area(fa_id); |
| 296 | if (fa->fa_device_id != FLASH_DEVICE_INTERNAL_FLASH) { |
| 297 | return -1; |
| 298 | } |
| 299 | |
| 300 | const size_t sector_size = FLASH_SECTOR_SIZE; |
| 301 | uint32_t total_count = 0; |
| 302 | for (size_t off = 0; off < fa->fa_size; off += sector_size) { |
| 303 | // Note: Offset here is relative to flash area, not device |
| 304 | sectors[total_count].fs_off = off; |
| 305 | sectors[total_count].fs_size = sector_size; |
| 306 | total_count++; |
| 307 | } |
| 308 | |
| 309 | *count = total_count; |
| 310 | return 0; |
| 311 | } |
| 312 | |
| 313 | int flash_area_id_from_multi_image_slot(int image_index, int slot) |
| 314 | { |
Gustavo Henrique Nihei | d985d22 | 2021-11-12 14:21:12 -0300 | [diff] [blame] | 315 | BOOT_LOG_DBG("%s", __func__); |
Shubham Kulkarni | 052561d | 2021-07-20 11:42:44 +0530 | [diff] [blame] | 316 | switch (slot) { |
| 317 | case 0: |
| 318 | return FLASH_AREA_IMAGE_PRIMARY(image_index); |
| 319 | case 1: |
| 320 | return FLASH_AREA_IMAGE_SECONDARY(image_index); |
| 321 | } |
| 322 | |
Gustavo Henrique Nihei | d985d22 | 2021-11-12 14:21:12 -0300 | [diff] [blame] | 323 | BOOT_LOG_ERR("Unexpected Request: image_index=%d, slot=%d", image_index, slot); |
Shubham Kulkarni | 052561d | 2021-07-20 11:42:44 +0530 | [diff] [blame] | 324 | return -1; /* flash_area_open will fail on that */ |
| 325 | } |
| 326 | |
| 327 | int flash_area_id_from_image_slot(int slot) |
| 328 | { |
Almir Okato | d532029 | 2021-06-18 02:00:40 -0300 | [diff] [blame] | 329 | return flash_area_id_from_multi_image_slot(0, slot); |
Shubham Kulkarni | 052561d | 2021-07-20 11:42:44 +0530 | [diff] [blame] | 330 | } |
| 331 | |
| 332 | int flash_area_to_sectors(int idx, int *cnt, struct flash_area *fa) |
| 333 | { |
Almir Okato | d532029 | 2021-06-18 02:00:40 -0300 | [diff] [blame] | 334 | return -1; |
Shubham Kulkarni | 052561d | 2021-07-20 11:42:44 +0530 | [diff] [blame] | 335 | } |
| 336 | |
| 337 | void mcuboot_assert_handler(const char *file, int line, const char *func) |
| 338 | { |
| 339 | ets_printf("assertion failed: file \"%s\", line %d, func: %s\n", file, line, func); |
| 340 | abort(); |
| 341 | } |