mirror of https://github.com/wolfSSL/wolfBoot.git
New feature: allow swapping from external memory (e.g. SPI flash)
parent
07db864ab1
commit
503b008cf5
13
Makefile
13
Makefile
|
|
@ -69,16 +69,17 @@ ifeq ($(FASTMATH),1)
|
|||
CFLAGS+=-DUSE_FAST_MATH
|
||||
endif
|
||||
|
||||
ifeq ($(EXT_FLASH),1)
|
||||
CFLAGS+=-DEXT_FLASH -DPART_UPDATE_EXT
|
||||
endif
|
||||
|
||||
CFLAGS+=-mthumb -Wall -Wextra -Wno-main -Wstack-usage=1024 -ffreestanding -Wno-unused \
|
||||
-Ilib/bootutil/include -Iinclude/ -Ilib/wolfssl -nostartfiles \
|
||||
-DWOLFSSL_USER_SETTINGS \
|
||||
-mthumb -mlittle-endian -mthumb-interwork \
|
||||
-DPLATFORM_$(TARGET)
|
||||
|
||||
ifeq ($(EXT_FLASH),1)
|
||||
CFLAGS+=-DEXT_FLASH=1 -DPART_UPDATE_EXT=1 -DPART_SWAP_EXT=1
|
||||
endif
|
||||
|
||||
|
||||
ifeq ($(SIGN),ED25519)
|
||||
OBJS+= ./lib/wolfssl/wolfcrypt/src/sha512.o \
|
||||
./lib/wolfssl/wolfcrypt/src/ed25519.o \
|
||||
|
|
@ -131,7 +132,7 @@ wolfboot-align.bin: wolfboot.elf
|
|||
$(SIZE) wolfboot.elf
|
||||
|
||||
test-app/image.bin:
|
||||
make -C test-app TARGET=$(TARGET)
|
||||
make -C test-app TARGET=$(TARGET) EXT_FLASH=$(EXT_FLASH)
|
||||
|
||||
tools/ed25519/ed25519_sign:
|
||||
make -C tools/ed25519
|
||||
|
|
@ -165,7 +166,7 @@ src/ecc256_pub_key.c: ecc256.der
|
|||
keys: $(PRIVATE_KEY)
|
||||
|
||||
clean:
|
||||
rm -f *.bin *.elf $(OBJS) wolfboot.map *.bin *.hex
|
||||
rm -f *.bin *.elf $(OBJS) wolfboot.map *.bin *.hex hal/*.o
|
||||
make -C test-app clean
|
||||
|
||||
distclean: clean
|
||||
|
|
|
|||
|
|
@ -112,9 +112,9 @@
|
|||
#define FLASH_SECTOR_5 0x0020000
|
||||
#define FLASH_SECTOR_6 0x0040000
|
||||
#define FLASH_SECTOR_7 0x0060000
|
||||
#define FLASH_TOP 0x007FFFF
|
||||
#define FLASH_TOP 0x0080000
|
||||
|
||||
const uint32_t flash_sector[8] = {
|
||||
const uint32_t flash_sector[9] = {
|
||||
FLASH_SECTOR_0,
|
||||
FLASH_SECTOR_1,
|
||||
FLASH_SECTOR_2,
|
||||
|
|
@ -122,7 +122,8 @@ const uint32_t flash_sector[8] = {
|
|||
FLASH_SECTOR_4,
|
||||
FLASH_SECTOR_5,
|
||||
FLASH_SECTOR_6,
|
||||
FLASH_SECTOR_7
|
||||
FLASH_SECTOR_7,
|
||||
FLASH_TOP
|
||||
};
|
||||
|
||||
static void flash_set_waitstates(int waitstates)
|
||||
|
|
@ -194,17 +195,20 @@ void hal_flash_lock(void)
|
|||
int hal_flash_erase(uint32_t address, int len)
|
||||
{
|
||||
int start = -1, end = -1;
|
||||
uint32_t end_address = address + len;
|
||||
uint32_t end_address;
|
||||
int i;
|
||||
if (len == 0)
|
||||
return -1;
|
||||
end_address = address + len - 1;
|
||||
|
||||
if (address < flash_sector[0] || end_address > FLASH_TOP)
|
||||
return -1;
|
||||
for (i = 0; i < 7; i++)
|
||||
for (i = 0; i < 8; i++)
|
||||
{
|
||||
if ((address >= flash_sector[i]) && (address < flash_sector[i + 1])) {
|
||||
start = i;
|
||||
}
|
||||
if ((end_address >= flash_sector[i]) && (address < flash_sector[i + 1])) {
|
||||
if ((end_address >= flash_sector[i]) && (end_address < flash_sector[i + 1])) {
|
||||
end = i;
|
||||
}
|
||||
if (start > 0 && end > 0)
|
||||
|
|
@ -325,3 +329,78 @@ void hal_prepare_boot(void)
|
|||
clock_pll_off();
|
||||
}
|
||||
|
||||
|
||||
#ifdef EXT_FLASH
|
||||
#define SIM_ADDRESS (0x40000)
|
||||
|
||||
int ext_flash_read(uint32_t address, uint8_t *data, int len)
|
||||
{
|
||||
int i;
|
||||
uint32_t val;
|
||||
address += SIM_ADDRESS;
|
||||
for (i = 0; i < len; i++)
|
||||
data[i] = *((uint8_t *)(address + i));
|
||||
return len;
|
||||
}
|
||||
|
||||
int ext_flash_write(uint32_t address, const uint8_t *data, int len)
|
||||
{
|
||||
int i;
|
||||
uint32_t val;
|
||||
address += SIM_ADDRESS;
|
||||
flash_wait_complete();
|
||||
clear_errors();
|
||||
/* Set 8-bit write */
|
||||
FLASH_CR &= (~(0x03 << 8));
|
||||
for (i = 0; i < len; i++) {
|
||||
FLASH_CR |= FLASH_CR_PG;
|
||||
*((uint8_t *)(address + i)) = data[i];
|
||||
flash_wait_complete();
|
||||
FLASH_CR &= ~FLASH_CR_PG;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void ext_flash_unlock(void)
|
||||
{
|
||||
FLASH_CR |= FLASH_CR_LOCK;
|
||||
FLASH_KEYR = FLASH_KEY1;
|
||||
FLASH_KEYR = FLASH_KEY2;
|
||||
}
|
||||
|
||||
void ext_flash_lock(void)
|
||||
{
|
||||
FLASH_CR |= FLASH_CR_LOCK;
|
||||
}
|
||||
|
||||
|
||||
int ext_flash_erase(uint32_t address, int len)
|
||||
{
|
||||
int start = -1, end = -1;
|
||||
uint32_t end_address;
|
||||
int i;
|
||||
address += SIM_ADDRESS;
|
||||
end_address = address + len - 1;
|
||||
|
||||
if (address < flash_sector[0] || end_address > FLASH_TOP)
|
||||
return -1;
|
||||
for (i = 0; i < 8; i++)
|
||||
{
|
||||
if ((address >= flash_sector[i]) && (address < flash_sector[i + 1])) {
|
||||
start = i;
|
||||
}
|
||||
if ((end_address >= flash_sector[i]) && (end_address < flash_sector[i + 1])) {
|
||||
end = i;
|
||||
}
|
||||
if (start > 0 && end > 0)
|
||||
break;
|
||||
}
|
||||
if (start < 0 || end < 0)
|
||||
return -1;
|
||||
for (i = start; i <= end; i++)
|
||||
flash_erase_sector(i);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -3,11 +3,21 @@
|
|||
|
||||
|
||||
#include <inttypes.h>
|
||||
#include "target.h"
|
||||
void hal_init(void);
|
||||
int hal_flash_write(uint32_t address, const uint8_t *data, int len);
|
||||
int hal_flash_erase(uint32_t address, int len);
|
||||
void hal_flash_unlock(void);
|
||||
void hal_flash_lock(void);
|
||||
void hal_prepare_boot(void);
|
||||
|
||||
#ifdef EXT_FLASH
|
||||
/* external flash interface */
|
||||
int ext_flash_write(uint32_t address, const uint8_t *data, int len);
|
||||
int ext_flash_read(uint32_t address, uint8_t *data, int len);
|
||||
int ext_flash_erase(uint32_t address, int len);
|
||||
void ext_flash_lock(void);
|
||||
void ext_flash_unlock(void);
|
||||
#endif
|
||||
|
||||
#endif /* H_HAL_FLASH_ */
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
#include <stdint.h>
|
||||
#include <target.h>
|
||||
#include <wolfboot/wolfboot.h>
|
||||
#include "image.h"
|
||||
|
||||
|
||||
#define SECT_FLAG_NEW 0x0F
|
||||
|
|
@ -11,32 +12,6 @@
|
|||
#define SECT_FLAG_UPDATED 0x00
|
||||
|
||||
|
||||
#ifdef EXT_FLASH
|
||||
# ifndef PART_UPDATE_DRIVER
|
||||
# define PART_UPDATE_DRIVER (&internal_flash_driver)
|
||||
# endif
|
||||
# define flash_lock(x) (x->flash_driver.lock())
|
||||
# define flash_unlock(x) (x->flash_driver.unlock())
|
||||
# define flash_erase(x, addr, len) (x->flash_driver.erase(addr, len)
|
||||
# define flash_write(x, addr, data, len) (x->flash_driver.write(addr, data, len)
|
||||
# define flash_read(x, addr, data, len) (x->flash_driver.read(addr, data, len)
|
||||
#else
|
||||
# define flash_lock(x) hal_flash_lock()
|
||||
# define flash_unlock(x) hal_flash_unlock()
|
||||
# define flash_erase(x, addr, len) (x->flash_driver.erase(addr, len)
|
||||
# define flash_write(x, addr, data, len) (x->flash_driver.write(addr, data, len)
|
||||
# define flash_read(x, addr, data, len) (memcpy(data, addr, len) - data + len)
|
||||
#endif
|
||||
|
||||
struct wolfBoot_flash_driver {
|
||||
int (*write)(uint32_t address, const uint8_t *data, int len);
|
||||
int (*read)(uint32_t address, uint8_t *data, int len);
|
||||
int (*erase)(uint32_t address, int len);
|
||||
void (*lock)(void);
|
||||
void (*unlock)(void);
|
||||
};
|
||||
|
||||
extern const struct wolfBoot_flash_driver internal_flash_driver;
|
||||
|
||||
struct wolfBoot_image {
|
||||
uint8_t *hdr;
|
||||
|
|
@ -47,7 +22,6 @@ struct wolfBoot_image {
|
|||
uint8_t *fw_base;
|
||||
uint32_t fw_size;
|
||||
uint8_t part;
|
||||
struct wolfBoot_flash_driver *flash_driver;
|
||||
};
|
||||
|
||||
|
||||
|
|
@ -59,4 +33,64 @@ int wolfBoot_set_sector_flag(uint8_t part, uint8_t sector, uint8_t newflag);
|
|||
int wolfBoot_get_partition_state(uint8_t part, uint8_t *st);
|
||||
int wolfBoot_get_sector_flag(uint8_t part, uint8_t sector, uint8_t *flag);
|
||||
|
||||
#ifdef EXT_FLASH
|
||||
# ifdef PART_UPDATE_EXT
|
||||
# define UPDATE_EXT 1
|
||||
# else
|
||||
# define UPDATE_EXT 0
|
||||
# endif
|
||||
# ifdef PART_SWAP_EXT
|
||||
# define SWAP_EXT 1
|
||||
# else
|
||||
# define SWAP_EXT 0
|
||||
# endif
|
||||
# define PART_IS_EXT(x) (((x)->part == PART_UPDATE)?UPDATE_EXT:(((x)->part == PART_SWAP)?SWAP_EXT:0))
|
||||
#include "hal.h"
|
||||
|
||||
static inline int wb_flash_erase(struct wolfBoot_image *img, uint32_t off, uint32_t size)
|
||||
{
|
||||
if (PART_IS_EXT(img))
|
||||
return ext_flash_erase((uint32_t)(img->hdr) + off, size);
|
||||
else
|
||||
return hal_flash_erase((uint32_t)(img->hdr) + off, size);
|
||||
}
|
||||
|
||||
static inline int wb_flash_write(struct wolfBoot_image *img, uint32_t off, const void *data, uint32_t size)
|
||||
{
|
||||
if (PART_IS_EXT(img))
|
||||
return ext_flash_write((uint32_t)(img->hdr) + off, data, size);
|
||||
else
|
||||
return hal_flash_write((uint32_t)(img->hdr) + off, data, size);
|
||||
}
|
||||
|
||||
static inline int wb_flash_write_verify_word(struct wolfBoot_image *img, uint32_t off, uint32_t word)
|
||||
{
|
||||
int ret;
|
||||
volatile uint32_t copy;
|
||||
if (PART_IS_EXT(img))
|
||||
{
|
||||
ext_flash_read((uint32_t)(img->hdr) + off, (void *)©, sizeof(uint32_t));
|
||||
while (copy != word) {
|
||||
ret = ext_flash_write((uint32_t)(img->hdr) + off, (void *)&word, sizeof(uint32_t));
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
ext_flash_read((uint32_t)(img->hdr) + off, (void *)©, sizeof(uint32_t));
|
||||
}
|
||||
} else {
|
||||
volatile uint32_t *pcopy = (volatile uint32_t*)(img->hdr + off);
|
||||
while(*pcopy != word) {
|
||||
hal_flash_write((uint32_t)pcopy, (void *)&word, sizeof(uint32_t));
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
#else
|
||||
# define PART_IS_EXT(x) (0)
|
||||
# define wb_flash_erase(im, of, siz) hal_flash_erase(((uint32_t)(((im)->hdr)) + of), siz)
|
||||
# define wb_flash_write(im, of, dat, siz) hal_flash_write(((uint32_t)((im)->hdr)) + of, dat, siz)
|
||||
# define wb_flash_write_verify_word(im, of, x) do { hal_flash_write(((uint32_t)((im)->hdr)) + of, (void *)&x, sizeof(uint32_t)); } while (*(uint32_t *)(((im)->hdr) + of) != x)
|
||||
#endif /* EXT_FLASH */
|
||||
|
||||
#endif /* IMAGE_H */
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@
|
|||
#define WOLFBOOT_PARTITION_SIZE 0x20000
|
||||
|
||||
#define WOLFBOOT_PARTITION_BOOT_ADDRESS 0x20000
|
||||
#define WOLFBOOT_PARTITION_UPDATE_ADDRESS 0x40000
|
||||
#define WOLFBOOT_PARTITION_SWAP_ADDRESS 0x60000
|
||||
#define WOLFBOOT_PARTITION_UPDATE_ADDRESS 0x0
|
||||
#define WOLFBOOT_PARTITION_SWAP_ADDRESS 0x20000
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -29,5 +29,8 @@
|
|||
void wolfBoot_erase_partition(uint8_t part);
|
||||
void wolfBoot_update_trigger(void);
|
||||
void wolfBoot_success(void);
|
||||
uint32_t wolfBoot_get_image_version(uint8_t part);
|
||||
#define wolfBoot_current_firmware_version() wolfBoot_get_image_version(PART_BOOT)
|
||||
#define wolfBoot_update_firmware_version() wolfBoot_get_image_version(PART_UPDATE)
|
||||
|
||||
#endif /* IMAGE_H */
|
||||
|
|
|
|||
93
src/image.c
93
src/image.c
|
|
@ -26,6 +26,10 @@
|
|||
|
||||
#ifdef WOLFBOOT_SIGN_ED25519
|
||||
#include <wolfssl/wolfcrypt/ed25519.h>
|
||||
|
||||
|
||||
extern uint8_t wolfBoot_find_header(uint8_t *haystack, uint8_t type, uint8_t **ptr);
|
||||
|
||||
static int wolfBoot_verify_signature(uint8_t *hash, uint8_t *sig)
|
||||
{
|
||||
int ret, res;
|
||||
|
|
@ -82,6 +86,7 @@ static int wolfBoot_verify_signature(uint8_t *hash, uint8_t *sig)
|
|||
}
|
||||
#endif
|
||||
|
||||
#if 0
|
||||
static uint8_t find_header(uint8_t *haystack, uint8_t type, uint8_t **ptr)
|
||||
{
|
||||
uint8_t *p = haystack;
|
||||
|
|
@ -101,6 +106,7 @@ static uint8_t find_header(uint8_t *haystack, uint8_t type, uint8_t **ptr)
|
|||
*ptr = NULL;
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
|
|
@ -108,16 +114,13 @@ static uint8_t get_header_ext(struct wolfBoot_image *img, uint8_t type, uint8_t
|
|||
|
||||
static uint8_t get_header(struct wolfBoot_image *img, uint8_t type, uint8_t **ptr)
|
||||
{
|
||||
#if defined(PART_UPDATE_EXT) && defined(EXT_FLASH)
|
||||
#if defined(PART_UPDATE_EXT)
|
||||
if(img->part == PART_UPDATE)
|
||||
return get_header_ext(img, type, ptr);
|
||||
#endif
|
||||
return find_header(img->hdr + IMAGE_HEADER_OFFSET, type, ptr);
|
||||
return wolfBoot_find_header(img->hdr + IMAGE_HEADER_OFFSET, type, ptr);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
static uint8_t ext_hash_block[SHA256_BLOCK_SIZE];
|
||||
|
||||
static uint8_t *get_sha_block(struct wolfBoot_image *img, uint32_t offset)
|
||||
|
|
@ -126,23 +129,55 @@ static uint8_t *get_sha_block(struct wolfBoot_image *img, uint32_t offset)
|
|||
return NULL;
|
||||
#ifdef PART_UPDATE_EXT
|
||||
if (img->part == PART_UPDATE) {
|
||||
ext_flash_read(img->fw_base + offset, ext_hash_block, SHA256_BLOCK_SIZE);
|
||||
ext_flash_read((uint32_t)(img->fw_base) + offset, ext_hash_block, SHA256_BLOCK_SIZE);
|
||||
return ext_hash_block;
|
||||
}
|
||||
#endif
|
||||
return (uint8_t *)(img->fw_base + offset);
|
||||
}
|
||||
|
||||
static uint8_t digest[SHA256_DIGEST_SIZE];
|
||||
static uint8_t verification[IMAGE_SIGNATURE_SIZE];
|
||||
#ifdef EXT_FLASH
|
||||
|
||||
static uint8_t hdr_cpy[IMAGE_HEADER_SIZE];
|
||||
static int hdr_cpy_done = 0;
|
||||
|
||||
static uint8_t *fetch_hdr_cpy(struct wolfBoot_image *img)
|
||||
{
|
||||
if (!hdr_cpy_done) {
|
||||
ext_flash_read((uint32_t)img->hdr, hdr_cpy, IMAGE_HEADER_SIZE);
|
||||
hdr_cpy_done = 1;
|
||||
}
|
||||
return hdr_cpy;
|
||||
}
|
||||
|
||||
static uint8_t get_header_ext(struct wolfBoot_image *img, uint8_t type, uint8_t **ptr)
|
||||
{
|
||||
return wolfBoot_find_header(fetch_hdr_cpy(img) + IMAGE_HEADER_OFFSET, type, ptr);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
static uint8_t *get_img_hdr(struct wolfBoot_image *img)
|
||||
{
|
||||
#ifdef PART_UPDATE_EXT
|
||||
if (img->part == PART_UPDATE) {
|
||||
return fetch_hdr_cpy(img);
|
||||
}
|
||||
#endif
|
||||
return (uint8_t *)(img->hdr);
|
||||
}
|
||||
|
||||
static int image_hash(struct wolfBoot_image *img, uint8_t *hash)
|
||||
{
|
||||
uint8_t *stored_sha, *end_sha;
|
||||
uint8_t stored_sha_len;
|
||||
uint8_t *p = img->hdr;
|
||||
uint8_t *fw_end = img->fw_base + img->fw_size;
|
||||
uint8_t *p = get_img_hdr(img);
|
||||
int blksz;
|
||||
uint32_t position = 0;
|
||||
wc_Sha256 sha256_ctx;
|
||||
if (!img || !img->hdr)
|
||||
if (!img)
|
||||
return -1;
|
||||
stored_sha_len = get_header(img, HDR_SHA256, &stored_sha);
|
||||
if (stored_sha_len != SHA256_DIGEST_SIZE)
|
||||
|
|
@ -188,39 +223,8 @@ static void key_hash(uint8_t *hash)
|
|||
wc_Sha256Final(&sha256_ctx, hash);
|
||||
}
|
||||
|
||||
static uint8_t digest[SHA256_DIGEST_SIZE];
|
||||
static uint8_t verification[IMAGE_SIGNATURE_SIZE];
|
||||
#ifdef EXT_FLASH
|
||||
|
||||
static uint8_t hdr_cpy[IMAGE_HEADER_SIZE];
|
||||
static int hdr_cpy_done = 0;
|
||||
|
||||
static uint8_t *fetch_hdr_cpy(struct wolfBoot_image *img)
|
||||
{
|
||||
if (!hdr_cpy_done) {
|
||||
ext_flash_read(img->hdr, hdr_cpy, IMAGE_HEADER_SIZE);
|
||||
hdr_cpy_done = 1;
|
||||
}
|
||||
return hdr_cpy;
|
||||
}
|
||||
|
||||
static uint8_t get_header_ext(struct wolfBoot_image *img, uint8_t type, uint8_t **ptr)
|
||||
{
|
||||
return find_header(fetch_hdr_cpy(img) + IMAGE_HEADER_OFFSET, type, ptr);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
static uint8_t *get_img_hdr(struct wolfBoot_image *img)
|
||||
{
|
||||
#ifdef PART_UPDATE_EXT
|
||||
if (img->part == PART_UPDATE) {
|
||||
return fetch_hdr_cpy(img);
|
||||
}
|
||||
#endif
|
||||
return (uint8_t *)(img->hdr);
|
||||
}
|
||||
|
||||
int wolfBoot_open_image(struct wolfBoot_image *img, uint8_t part)
|
||||
{
|
||||
|
|
@ -230,12 +234,19 @@ int wolfBoot_open_image(struct wolfBoot_image *img, uint8_t part)
|
|||
if (!img)
|
||||
return -1;
|
||||
memset(img, 0, sizeof(struct wolfBoot_image));
|
||||
if (part == PART_SWAP) {
|
||||
img->part = PART_SWAP;
|
||||
img->hdr = (void *)WOLFBOOT_PARTITION_SWAP_ADDRESS;
|
||||
img->fw_base = img->hdr;
|
||||
img->fw_size = WOLFBOOT_SECTOR_SIZE;
|
||||
return 0;
|
||||
}
|
||||
if (part == PART_BOOT) {
|
||||
img->hdr = (void *)WOLFBOOT_PARTITION_BOOT_ADDRESS;
|
||||
image = (uint8_t *)img->hdr;
|
||||
} else if (part == PART_UPDATE) {
|
||||
img->hdr = (void *)WOLFBOOT_PARTITION_UPDATE_ADDRESS;
|
||||
#if defined(PART_UPDATE_EXT)
|
||||
#ifdef PART_UPDATE_EXT
|
||||
image = fetch_hdr_cpy(img);
|
||||
#else
|
||||
image = (uint8_t *)img->hdr;
|
||||
|
|
|
|||
|
|
@ -23,83 +23,165 @@
|
|||
#include <inttypes.h>
|
||||
#include <wolfboot/wolfboot.h>
|
||||
|
||||
static uint8_t *get_trailer(uint8_t part)
|
||||
#ifndef NULL
|
||||
# define NULL (void *)0
|
||||
#endif
|
||||
|
||||
uint32_t ext_cache;
|
||||
|
||||
#ifndef TRAILER_SKIP
|
||||
# define TRAILER_SKIP 0
|
||||
#endif
|
||||
#define PART_BOOT_ENDFLAGS ((WOLFBOOT_PARTITION_BOOT_ADDRESS + WOLFBOOT_PARTITION_SIZE) - TRAILER_SKIP)
|
||||
#define PART_UPDATE_ENDFLAGS ((WOLFBOOT_PARTITION_UPDATE_ADDRESS + WOLFBOOT_PARTITION_SIZE) - TRAILER_SKIP)
|
||||
|
||||
#if defined PART_UPDATE_EXT
|
||||
static uint8_t *get_trailer_at(uint8_t part, uint32_t at)
|
||||
{
|
||||
if (part == PART_BOOT)
|
||||
return (void *)(WOLFBOOT_PARTITION_BOOT_ADDRESS + WOLFBOOT_PARTITION_SIZE);
|
||||
else if (part == PART_UPDATE)
|
||||
return (void *)(WOLFBOOT_PARTITION_UPDATE_ADDRESS + WOLFBOOT_PARTITION_SIZE);
|
||||
else
|
||||
return (void *)0;
|
||||
return (void *)(PART_BOOT_ENDFLAGS - (sizeof(uint32_t) + at));
|
||||
else if (part == PART_UPDATE) {
|
||||
ext_flash_read(PART_UPDATE_ENDFLAGS - (sizeof(uint32_t) + at), (void *)&ext_cache, sizeof(uint32_t));
|
||||
return (uint8_t *)&ext_cache;
|
||||
} else
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void set_trailer_at(uint8_t part, uint32_t at, uint8_t val)
|
||||
{
|
||||
if (part == PART_BOOT) {
|
||||
hal_flash_write(PART_BOOT_ENDFLAGS - (sizeof(uint32_t) + at), (void *)&val, 1);
|
||||
}
|
||||
else if (part == PART_UPDATE) {
|
||||
ext_flash_write(PART_UPDATE_ENDFLAGS - (sizeof(uint32_t) + at), (void *)&val, 1);
|
||||
}
|
||||
}
|
||||
|
||||
static void set_partition_magic(uint8_t part)
|
||||
{
|
||||
uint32_t wolfboot_magic_trail = WOLFBOOT_MAGIC_TRAIL;
|
||||
if (part == PART_BOOT) {
|
||||
hal_flash_write(PART_BOOT_ENDFLAGS - sizeof(uint32_t), (void *)&wolfboot_magic_trail, sizeof(uint32_t));
|
||||
}
|
||||
else if (part == PART_UPDATE) {
|
||||
ext_flash_write(PART_UPDATE_ENDFLAGS - sizeof(uint32_t), (void *)&wolfboot_magic_trail, sizeof(uint32_t));
|
||||
}
|
||||
}
|
||||
|
||||
#else
|
||||
static uint8_t *get_trailer_at(uint8_t part, uint32_t at)
|
||||
{
|
||||
if (part == PART_BOOT)
|
||||
return (void *)(PART_BOOT_ENDFLAGS - (sizeof(uint32_t) + at));
|
||||
else if (part == PART_UPDATE) {
|
||||
return (void *)(PART_UPDATE_ENDFLAGS - (sizeof(uint32_t) + at));
|
||||
} else
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void set_trailer_at(uint8_t part, uint32_t at, uint8_t val)
|
||||
{
|
||||
if (part == PART_BOOT) {
|
||||
hal_flash_write(PART_BOOT_ENDFLAGS - (sizeof(uint32_t) + at), (void *)&val, 1);
|
||||
}
|
||||
else if (part == PART_UPDATE) {
|
||||
hal_flash_write(PART_UPDATE_ENDFLAGS - (sizeof(uint32_t) + at), (void *)&val, 1);
|
||||
}
|
||||
}
|
||||
|
||||
static void set_partition_magic(uint8_t part)
|
||||
{
|
||||
uint32_t wolfboot_magic_trail = WOLFBOOT_MAGIC_TRAIL;
|
||||
if (part == PART_BOOT) {
|
||||
hal_flash_write(PART_BOOT_ENDFLAGS - sizeof(uint32_t), (void *)&wolfboot_magic_trail, sizeof(uint32_t));
|
||||
}
|
||||
else if (part == PART_UPDATE) {
|
||||
hal_flash_write(PART_UPDATE_ENDFLAGS - sizeof(uint32_t), (void *)&wolfboot_magic_trail, sizeof(uint32_t));
|
||||
}
|
||||
}
|
||||
#endif /* PART_UPDATE_EXT */
|
||||
|
||||
|
||||
|
||||
static uint32_t *get_partition_magic(uint8_t part)
|
||||
{
|
||||
return (uint32_t *)get_trailer_at(part, 0);
|
||||
}
|
||||
|
||||
static uint8_t *get_partition_state(uint8_t part)
|
||||
{
|
||||
return (uint8_t *)get_trailer_at(part, 1);
|
||||
}
|
||||
|
||||
static uint8_t *get_sector_flags(uint8_t part, uint32_t pos)
|
||||
{
|
||||
return (uint8_t *)get_trailer_at(part, 2 + pos);
|
||||
}
|
||||
|
||||
static void set_partition_state(uint8_t part, uint8_t val)
|
||||
{
|
||||
set_trailer_at(part, 1, val);
|
||||
}
|
||||
|
||||
static void set_sector_flags(uint8_t part, uint32_t pos, uint8_t val)
|
||||
{
|
||||
set_trailer_at(part, 2 + pos, val);
|
||||
}
|
||||
|
||||
int wolfBoot_set_partition_state(uint8_t part, uint8_t newst)
|
||||
{
|
||||
uint8_t *trailer_end = get_trailer(part);
|
||||
uint32_t *magic;
|
||||
uint8_t *state;
|
||||
uint32_t wolfboot_magic_trail = WOLFBOOT_MAGIC_TRAIL;
|
||||
if (!trailer_end)
|
||||
return -1;
|
||||
magic = (uint32_t *)(trailer_end - sizeof(uint32_t));
|
||||
magic = get_partition_magic(part);
|
||||
if (*magic != WOLFBOOT_MAGIC_TRAIL)
|
||||
hal_flash_write((uint32_t)magic, (void *)&wolfboot_magic_trail, sizeof(uint32_t));
|
||||
state = (trailer_end - sizeof(uint32_t)) - 1;
|
||||
set_partition_magic(part);
|
||||
state = get_partition_state(part);
|
||||
if (*state != newst)
|
||||
hal_flash_write((uint32_t)state, (void *)&newst, 1);
|
||||
set_partition_state(part, newst);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int wolfBoot_set_sector_flag(uint8_t part, uint8_t sector, uint8_t newflag)
|
||||
{
|
||||
uint8_t *trailer_end = get_trailer(part);
|
||||
uint32_t *magic;
|
||||
uint8_t *flags;
|
||||
uint8_t fl_value;
|
||||
uint32_t wolfboot_magic_trail = WOLFBOOT_MAGIC_TRAIL;
|
||||
uint8_t pos = sector >> 1;
|
||||
if (!trailer_end)
|
||||
return -1;
|
||||
magic = (uint32_t *)(trailer_end - sizeof(uint32_t));
|
||||
magic = get_partition_magic(part);
|
||||
if (*magic != WOLFBOOT_MAGIC_TRAIL)
|
||||
hal_flash_write((uint32_t)magic, (void *)&wolfboot_magic_trail, sizeof(uint32_t));
|
||||
flags = (trailer_end - sizeof(uint32_t)) - (2 + pos);
|
||||
set_partition_magic(part);
|
||||
flags = get_sector_flags(part, pos);
|
||||
if (sector == (pos << 1))
|
||||
fl_value = (*flags & 0xF0) | (newflag & 0x0F);
|
||||
else
|
||||
fl_value = ((newflag & 0x0F) << 4) | (*flags & 0x0F);
|
||||
if (fl_value != *flags)
|
||||
hal_flash_write((uint32_t)flags, &fl_value, 1);
|
||||
set_sector_flags(part, pos, fl_value);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int wolfBoot_get_partition_state(uint8_t part, uint8_t *st)
|
||||
{
|
||||
uint8_t *trailer_end = get_trailer(part);
|
||||
uint32_t *magic;
|
||||
uint8_t *state;
|
||||
if (!trailer_end)
|
||||
return -1;
|
||||
magic = (uint32_t *)(trailer_end - sizeof(uint32_t));
|
||||
magic = get_partition_magic(part);
|
||||
if (*magic != WOLFBOOT_MAGIC_TRAIL)
|
||||
return -1;
|
||||
state = (trailer_end - sizeof(uint32_t)) - 1;
|
||||
state = get_partition_state(part);
|
||||
*st = *state;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int wolfBoot_get_sector_flag(uint8_t part, uint8_t sector, uint8_t *flag)
|
||||
{
|
||||
uint8_t *trailer_end = get_trailer(part);
|
||||
uint32_t *magic;
|
||||
uint8_t *flags;
|
||||
uint8_t pos = sector >> 1;
|
||||
if (!trailer_end)
|
||||
return -1;
|
||||
magic = (uint32_t *)(trailer_end - sizeof(uint32_t));
|
||||
magic = get_partition_magic(part);
|
||||
if (*magic != WOLFBOOT_MAGIC_TRAIL)
|
||||
return -1;
|
||||
flags = (trailer_end - sizeof(uint32_t)) - (2 + pos);
|
||||
flags = get_sector_flags(part, pos);
|
||||
if (sector == (pos << 1))
|
||||
*flag = *flags & 0x0F;
|
||||
else
|
||||
|
|
@ -111,8 +193,15 @@ void wolfBoot_erase_partition(uint8_t part)
|
|||
{
|
||||
if (part == PART_BOOT)
|
||||
hal_flash_erase(WOLFBOOT_PARTITION_BOOT_ADDRESS, WOLFBOOT_PARTITION_SIZE);
|
||||
if (part == PART_UPDATE)
|
||||
if (part == PART_UPDATE) {
|
||||
#ifdef PART_UPDATE_EXT
|
||||
ext_flash_unlock();
|
||||
ext_flash_erase(WOLFBOOT_PARTITION_UPDATE_ADDRESS, WOLFBOOT_PARTITION_SIZE);
|
||||
ext_flash_lock();
|
||||
#else
|
||||
hal_flash_erase(WOLFBOOT_PARTITION_UPDATE_ADDRESS, WOLFBOOT_PARTITION_SIZE);
|
||||
#endif
|
||||
}
|
||||
if (part == PART_SWAP)
|
||||
hal_flash_erase(WOLFBOOT_PARTITION_SWAP_ADDRESS, WOLFBOOT_SECTOR_SIZE);
|
||||
}
|
||||
|
|
@ -120,9 +209,15 @@ void wolfBoot_erase_partition(uint8_t part)
|
|||
void wolfBoot_update_trigger(void)
|
||||
{
|
||||
uint8_t st = IMG_STATE_UPDATING;
|
||||
#ifdef PART_UPDATE_EXT
|
||||
ext_flash_unlock();
|
||||
wolfBoot_set_partition_state(PART_UPDATE, st);
|
||||
ext_flash_lock();
|
||||
#else
|
||||
hal_flash_unlock();
|
||||
wolfBoot_set_partition_state(PART_UPDATE, st);
|
||||
hal_flash_lock();
|
||||
#endif
|
||||
}
|
||||
|
||||
void wolfBoot_success(void)
|
||||
|
|
@ -132,3 +227,59 @@ void wolfBoot_success(void)
|
|||
wolfBoot_set_partition_state(PART_BOOT, st);
|
||||
hal_flash_lock();
|
||||
}
|
||||
|
||||
|
||||
uint8_t wolfBoot_find_header(uint8_t *haystack, uint8_t type, uint8_t **ptr)
|
||||
{
|
||||
uint8_t *p = haystack;
|
||||
while (*p != 0) {
|
||||
if (*p == HDR_PADDING) {
|
||||
p++;
|
||||
continue;
|
||||
}
|
||||
if (*p == type) {
|
||||
p++;
|
||||
*ptr = (p + 1);
|
||||
return *p;
|
||||
}
|
||||
p++;
|
||||
p += (*p + 1);
|
||||
}
|
||||
*ptr = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef EXT_FLASH
|
||||
static uint8_t hdr_cpy[IMAGE_HEADER_SIZE];
|
||||
static uint32_t hdr_cpy_done = 0;
|
||||
#endif
|
||||
|
||||
uint32_t wolfBoot_get_image_version(uint8_t part)
|
||||
{
|
||||
uint32_t *version_field = NULL;
|
||||
uint32_t version = 0;
|
||||
uint8_t *image = NULL;
|
||||
uint32_t *magic = NULL;
|
||||
if(part == PART_UPDATE) {
|
||||
#ifdef PART_UPDATE_EXT
|
||||
ext_flash_read((uint32_t)WOLFBOOT_PARTITION_UPDATE_ADDRESS, hdr_cpy, IMAGE_HEADER_SIZE);
|
||||
hdr_cpy_done = 1;
|
||||
image = hdr_cpy;
|
||||
#else
|
||||
image = (uint8_t *)WOLFBOOT_PARTITION_UPDATE_ADDRESS;
|
||||
#endif
|
||||
}
|
||||
if (part == PART_BOOT)
|
||||
image = (uint8_t *)WOLFBOOT_PARTITION_BOOT_ADDRESS;
|
||||
|
||||
if (image) {
|
||||
magic = (uint32_t *)image;
|
||||
if (*magic != WOLFBOOT_MAGIC)
|
||||
return 0;
|
||||
wolfBoot_find_header(image + IMAGE_HEADER_OFFSET, HDR_VERSION, (void *)&version_field);
|
||||
if (version_field)
|
||||
return *version_field;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
|
|||
79
src/loader.c
79
src/loader.c
|
|
@ -24,19 +24,35 @@
|
|||
|
||||
extern void do_boot(const uint32_t *app_offset);
|
||||
|
||||
static int wolfBoot_copy(uint32_t src, uint32_t dst, uint32_t size)
|
||||
static int wolfBoot_copy_sector(struct wolfBoot_image *src, struct wolfBoot_image *dst, uint32_t sector)
|
||||
{
|
||||
uint32_t *orig, *copy;
|
||||
volatile uint32_t *orig, *copy;
|
||||
uint32_t pos = 0;
|
||||
uint32_t src_sector_offset = (sector * WOLFBOOT_SECTOR_SIZE);
|
||||
uint32_t dst_sector_offset = (sector * WOLFBOOT_SECTOR_SIZE);
|
||||
if (src == dst)
|
||||
return 0;
|
||||
if ((src & 0x03) || (dst & 0x03))
|
||||
return -1;
|
||||
while (pos < size) {
|
||||
orig = (uint32_t *)(src + pos);
|
||||
copy = (uint32_t *)(dst + pos);
|
||||
while (*orig != *copy)
|
||||
hal_flash_write(dst + pos, (void *)orig, sizeof(uint32_t));
|
||||
|
||||
if (src->part == PART_SWAP)
|
||||
src_sector_offset = 0;
|
||||
if (dst->part == PART_SWAP)
|
||||
dst_sector_offset = 0;
|
||||
#ifdef EXT_FLASH
|
||||
if (PART_IS_EXT(src)) {
|
||||
uint32_t word;
|
||||
wb_flash_erase(dst, dst_sector_offset, WOLFBOOT_SECTOR_SIZE);
|
||||
while (pos < WOLFBOOT_SECTOR_SIZE) {
|
||||
ext_flash_read((uint32_t)(src->hdr) + src_sector_offset + pos, (void *)&word, sizeof(uint32_t));
|
||||
wb_flash_write_verify_word(dst, dst_sector_offset + pos, word);
|
||||
pos += sizeof(uint32_t);
|
||||
}
|
||||
return pos;
|
||||
}
|
||||
#endif
|
||||
wb_flash_erase(dst, dst_sector_offset, WOLFBOOT_SECTOR_SIZE);
|
||||
while (pos < WOLFBOOT_SECTOR_SIZE) {
|
||||
orig = (volatile uint32_t *)(src->hdr + src_sector_offset + pos);
|
||||
wb_flash_write_verify_word(dst, dst_sector_offset + pos, *orig);
|
||||
pos += sizeof(uint32_t);
|
||||
}
|
||||
return pos;
|
||||
|
|
@ -45,16 +61,20 @@ static int wolfBoot_copy(uint32_t src, uint32_t dst, uint32_t size)
|
|||
static int wolfBoot_update(void)
|
||||
{
|
||||
uint32_t total_size = 0;
|
||||
uint32_t sector_size = WOLFBOOT_SECTOR_SIZE;
|
||||
const uint32_t sector_size = WOLFBOOT_SECTOR_SIZE;
|
||||
uint32_t sector = 0;
|
||||
uint8_t flag, st;
|
||||
struct wolfBoot_image boot, update;
|
||||
struct wolfBoot_image boot, update, swap;
|
||||
|
||||
/* No Safety check on open: we might be in the middle of a broken update */
|
||||
wolfBoot_open_image(&update, PART_UPDATE);
|
||||
wolfBoot_open_image(&boot, PART_BOOT);
|
||||
wolfBoot_open_image(&swap, PART_SWAP);
|
||||
|
||||
/* Use biggest size for the swap */
|
||||
if ((wolfBoot_open_image(&update, PART_UPDATE) == 0) && (update.fw_size + IMAGE_HEADER_SIZE) > total_size)
|
||||
total_size = boot.fw_size + IMAGE_HEADER_SIZE;
|
||||
if ((update.fw_size + IMAGE_HEADER_SIZE) > total_size)
|
||||
total_size = update.fw_size + IMAGE_HEADER_SIZE;
|
||||
if ((wolfBoot_open_image(&boot, PART_BOOT) == 0) && (boot.fw_size + IMAGE_HEADER_SIZE) > total_size)
|
||||
total_size = boot.fw_size + IMAGE_HEADER_SIZE;
|
||||
|
||||
if (total_size < IMAGE_HEADER_SIZE)
|
||||
return -1;
|
||||
|
|
@ -73,6 +93,9 @@ static int wolfBoot_update(void)
|
|||
}
|
||||
|
||||
hal_flash_unlock();
|
||||
#ifdef EXT_FLASH
|
||||
ext_flash_unlock();
|
||||
#endif
|
||||
|
||||
/* Interruptible swap
|
||||
* The status is saved in the sector flags of the update partition.
|
||||
|
|
@ -81,10 +104,7 @@ static int wolfBoot_update(void)
|
|||
while ((sector * sector_size) < total_size) {
|
||||
if ((wolfBoot_get_sector_flag(PART_UPDATE, sector, &flag) != 0) || (flag == SECT_FLAG_NEW)) {
|
||||
flag = SECT_FLAG_SWAPPING;
|
||||
hal_flash_erase(WOLFBOOT_PARTITION_SWAP_ADDRESS, WOLFBOOT_SECTOR_SIZE);
|
||||
wolfBoot_copy(WOLFBOOT_PARTITION_UPDATE_ADDRESS + sector * sector_size,
|
||||
WOLFBOOT_PARTITION_SWAP_ADDRESS,
|
||||
WOLFBOOT_SECTOR_SIZE);
|
||||
wolfBoot_copy_sector(&update, &swap, sector);
|
||||
wolfBoot_set_sector_flag(PART_UPDATE, sector, flag);
|
||||
}
|
||||
if (flag == SECT_FLAG_SWAPPING) {
|
||||
|
|
@ -92,11 +112,7 @@ static int wolfBoot_update(void)
|
|||
if (size > sector_size)
|
||||
size = sector_size;
|
||||
flag = SECT_FLAG_BACKUP;
|
||||
hal_flash_erase(WOLFBOOT_PARTITION_UPDATE_ADDRESS + sector * sector_size,
|
||||
WOLFBOOT_SECTOR_SIZE);
|
||||
wolfBoot_copy(WOLFBOOT_PARTITION_BOOT_ADDRESS + sector * sector_size,
|
||||
WOLFBOOT_PARTITION_UPDATE_ADDRESS + sector * sector_size,
|
||||
WOLFBOOT_SECTOR_SIZE);
|
||||
wolfBoot_copy_sector(&boot, &update, sector);
|
||||
wolfBoot_set_sector_flag(PART_UPDATE, sector, flag);
|
||||
}
|
||||
if (flag == SECT_FLAG_BACKUP) {
|
||||
|
|
@ -104,25 +120,22 @@ static int wolfBoot_update(void)
|
|||
if (size > sector_size)
|
||||
size = sector_size;
|
||||
flag = SECT_FLAG_UPDATED;
|
||||
hal_flash_erase(WOLFBOOT_PARTITION_BOOT_ADDRESS + sector * sector_size,
|
||||
sector_size);
|
||||
wolfBoot_copy(WOLFBOOT_PARTITION_SWAP_ADDRESS,
|
||||
WOLFBOOT_PARTITION_BOOT_ADDRESS + sector * sector_size,
|
||||
size);
|
||||
wolfBoot_copy_sector(&swap, &boot, sector);
|
||||
wolfBoot_set_sector_flag(PART_UPDATE, sector, flag);
|
||||
}
|
||||
sector++;
|
||||
}
|
||||
while((sector * sector_size) < WOLFBOOT_PARTITION_SIZE) {
|
||||
hal_flash_erase(WOLFBOOT_PARTITION_BOOT_ADDRESS + sector * sector_size,
|
||||
sector_size);
|
||||
hal_flash_erase(WOLFBOOT_PARTITION_UPDATE_ADDRESS + sector * sector_size,
|
||||
sector_size);
|
||||
wb_flash_erase(&boot, sector * sector_size, sector_size);
|
||||
wb_flash_erase(&update, sector * sector_size, sector_size);
|
||||
sector++;
|
||||
}
|
||||
hal_flash_erase(WOLFBOOT_PARTITION_SWAP_ADDRESS, WOLFBOOT_SECTOR_SIZE);
|
||||
wb_flash_erase(&swap, 0, WOLFBOOT_SECTOR_SIZE);
|
||||
st = IMG_STATE_TESTING;
|
||||
wolfBoot_set_partition_state(PART_BOOT, st);
|
||||
#ifdef EXT_FLASH
|
||||
ext_flash_lock();
|
||||
#endif
|
||||
hal_flash_lock();
|
||||
return 0;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,17 @@
|
|||
CROSS_COMPILE:=arm-none-eabi-
|
||||
CC:=$(CROSS_COMPILE)gcc
|
||||
LD:=$(CROSS_COMPILE)gcc
|
||||
OBJS:=startup.o main.o timer.o led.o system.o
|
||||
OBJS:=startup.o main.o timer.o led.o system.o ../src/libwolfboot.o ../hal/$(TARGET).o
|
||||
TARGET?=none
|
||||
LSCRIPT:=app.ld
|
||||
OBJCOPY:=$(CROSS_COMPILE)objcopy
|
||||
CFLAGS:=-mcpu=cortex-m3 -mthumb -g -ggdb -Wall -Wno-main -Wstack-usage=200 -ffreestanding -Wno-unused -nostdlib -DPLATFORM_$(TARGET)
|
||||
CFLAGS:=-mcpu=cortex-m3 -mthumb -g -ggdb -Wall -Wno-main -Wstack-usage=200 -ffreestanding -Wno-unused -nostdlib -DPLATFORM_$(TARGET) -I../include
|
||||
LDFLAGS:=-T $(LSCRIPT) -Wl,-gc-sections -Wl,-Map=image.map -nostdlib
|
||||
|
||||
ifeq ($(EXT_FLASH),1)
|
||||
CFLAGS+=-DEXT_FLASH=1 -DPART_UPDATE_EXT=1
|
||||
endif
|
||||
|
||||
|
||||
image.bin: image.elf
|
||||
$(OBJCOPY) -O binary $^ $@
|
||||
|
|
|
|||
|
|
@ -26,10 +26,12 @@
|
|||
#include "system.h"
|
||||
#include "timer.h"
|
||||
#include "led.h"
|
||||
#include "wolfboot/wolfboot.h"
|
||||
|
||||
#ifdef PLATFORM_stm32f4
|
||||
|
||||
void main(void) {
|
||||
uint32_t version;
|
||||
boot_led_on();
|
||||
flash_set_waitstates();
|
||||
clock_config();
|
||||
|
|
@ -43,6 +45,15 @@ void main(void) {
|
|||
* effect.
|
||||
*/
|
||||
timer_init(CPU_FREQ, 1, 50);
|
||||
version = wolfBoot_current_firmware_version();
|
||||
if (version & 0x01) {
|
||||
/* Odd version: unstable, trigger update */
|
||||
if (wolfBoot_update_firmware_version() != 0)
|
||||
wolfBoot_update_trigger();
|
||||
} else {
|
||||
/* Even version, stabilise */
|
||||
wolfBoot_success();
|
||||
}
|
||||
asm volatile ("cpsie i");
|
||||
while(1)
|
||||
WFI();
|
||||
|
|
|
|||
Loading…
Reference in New Issue