Re-enabled self-updater with SAMD51 support.

This commit is contained in:
Scott Shawcroft 2017-10-24 17:25:17 -07:00
parent ee100b0adc
commit 9de0794dc1
9 changed files with 340 additions and 42 deletions

3
.gitmodules vendored Normal file
View file

@ -0,0 +1,3 @@
[submodule "lib/uf2"]
path = lib/uf2
url = https://github.com/Microsoft/uf2.git

View file

@ -29,11 +29,13 @@ $(WFLAGS)
ifeq ($(CHIP_FAMILY), samd21)
LINKER_SCRIPT=./lib/samd21/samd21a/gcc/gcc/samd21j18a_flash.ld
BOOTLOADER_SIZE=8192
SELF_LINKER_SCRIPT=scripts/samd21j18a_self.ld
endif
ifeq ($(CHIP_FAMILY), samd51)
LINKER_SCRIPT=./lib/samd51/gcc/gcc/samd51j18a_flash.ld
BOOTLOADER_SIZE=16384
SELF_LINKER_SCRIPT=scripts/samd51j19a_self.ld
endif
LDFLAGS= $(COMMON_FLAGS) \
@ -80,7 +82,7 @@ NAME=bootloader
EXECUTABLE=$(BUILD_PATH)/$(NAME).bin
SELF_EXECUTABLE=$(BUILD_PATH)/update-$(NAME).uf2
all: dirs $(EXECUTABLE) #$(SELF_EXECUTABLE)
all: dirs $(EXECUTABLE) $(SELF_EXECUTABLE)
r: run
b: burn
@ -117,10 +119,10 @@ $(EXECUTABLE): $(OBJECTS)
$(SELF_EXECUTABLE): $(SELF_OBJECTS)
$(CC) -L$(BUILD_PATH) $(LDFLAGS) \
-T./scripts/samd21j18a_self.ld \
-T$(SELF_LINKER_SCRIPT) \
-Wl,-Map,$(BUILD_PATH)/update-$(NAME).map -o $(BUILD_PATH)/update-$(NAME).elf $(SELF_OBJECTS)
arm-none-eabi-objcopy -O binary $(BUILD_PATH)/update-$(NAME).elf $(BUILD_PATH)/update-$(NAME).bin
node scripts/bin2uf2.js $(BUILD_PATH)/update-$(NAME).bin $@
python lib/uf2/utils/uf2conv.py -b $(BOOTLOADER_SIZE) -c -o $@ $(BUILD_PATH)/update-$(NAME).bin
$(BUILD_PATH)/%.o: src/%.c $(wildcard inc/*.h boards/*/*.h)
echo "$<"
@ -130,7 +132,7 @@ $(BUILD_PATH)/%.o: $(BUILD_PATH)/%.c
$(CC) $(CFLAGS) $(BLD_EXTA_FLAGS) $(INCLUDES) $< -o $@
$(BUILD_PATH)/selfdata.c: $(EXECUTABLE) scripts/gendata.js src/sketch.cpp
node scripts/gendata.js $(BUILD_PATH) $(NAME).bin
python scripts/gendata.py $(BOOTLOADER_SIZE) $(EXECUTABLE)
clean:
rm -rf build

View file

@ -84,6 +84,8 @@ sketch. You can copy&paste it into Arduino IDE and upload it to the device.
## Fuses
### SAMD21
The SAMD21 supports a `BOOTPROT` fuse, which write-protects the flash area of
the bootloader. Changes to this fuse only take effect after device reset.
@ -98,6 +100,17 @@ The bootloader update programs (both the `.uf2` file and the Arduino sketch)
clear the `BOOTPROT` (i.e., set it to `0x7`) before trying to flash anything.
After flashing is done, they set `BOOTPROT` to 8 kilobyte bootloader size (i.e, `0x2`).
### SAMD51
The SAMD51s bootloader protection can be temporarily disabled through an NVM
command rather than a full erase and write of the AUX page. The boot protection
will be checked and set by the self updaters.
So, if you've used self-updaters but want to load it directly, then you'll need
to temporarily turn off the protection. In gdb the command is:
`set ((Nvmctrl *)0x41004000UL)->CTRLB.reg = (0xA5 << 8) | 0x1a`
## Build
### Requirements

1
lib/uf2 Submodule

@ -0,0 +1 @@
Subproject commit d7ab98ee50bb42bb1c2ff600324e3a155a3cc4ad

51
scripts/gendata.py Normal file
View file

@ -0,0 +1,51 @@
import sys
import os
def update_crc(new_byte, current_crc):
crc = current_crc ^ new_byte << 8
for cmpt in range(8):
if crc & 0x8000:
crc = crc << 1 ^ 0x1021
else:
crc = crc << 1
crc &= 0xffff
return crc
# Load the bootloader file
bootloader_size = int(sys.argv[1])
bin_name = sys.argv[2]
bootloader = bytearray()
with open(bin_name, "rb") as bootloader_bin:
bootloader.extend(bootloader_bin.read())
# Fill the remaining space with 0xff.
bootloader.extend([0xff] * (bootloader_size - len(bootloader)))
# Output the bootloader binary data into C code to use in the self updater.
with open(os.path.dirname(bin_name) + "/selfdata.c", "w") as output:
output.write("#include <stdint.h>\n")
output.write("const uint8_t bootloader[{}] ".format(bootloader_size) +
"__attribute__ ((aligned (4))) = {")
crcs = []
crc = 0
for row in range(bootloader_size / 16):
# Save the crc every 1k.
if row % (1024 / 16) == 0 and row > 0:
crcs.append(crc)
crc = 0
start_index = row * 16
row_bytes = bootloader[start_index:start_index+16]
formatted_bytes = ["0x{:02x}".format(x) for x in row_bytes]
output.write(", ".join(formatted_bytes) + ",\n")
# Update the crc
for b in row_bytes:
crc = update_crc(b, crc)
crcs.append(crc) # Add the last crc
output.write("\n};\n")
crcs = ["0x{:04x}".format(x) for x in crcs]
output.write("const uint16_t bootloader_crcs[] = {" +
" ,".join(crcs) + "};\n")

163
scripts/samd51j19a_self.ld Normal file
View file

@ -0,0 +1,163 @@
/**
* \file
*
* \brief Linker script for running in internal FLASH on the SAMD51J19A
*
* Copyright (c) 2017 Microchip Technology Inc.
*
* \asf_license_start
*
* \page License
*
* SPDX-License-Identifier: Apache-2.0
*
* Licensed 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 Licence 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.
*
* \asf_license_stop
*
*/
OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
OUTPUT_ARCH(arm)
SEARCH_DIR(.)
/* Memory Spaces Definitions */
MEMORY
{
rom (rx) : ORIGIN = 0x00004000, LENGTH = 0x00080000 - 0x4000
ram (rwx) : ORIGIN = 0x20000000, LENGTH = 0x00030000
bkupram (rwx) : ORIGIN = 0x47000000, LENGTH = 0x00002000
qspi (rwx) : ORIGIN = 0x04000000, LENGTH = 0x01000000
}
/* The stack size used by the application. NOTE: you need to adjust according to your application. */
STACK_SIZE = DEFINED(STACK_SIZE) ? STACK_SIZE : DEFINED(__stack_size__) ? __stack_size__ : 0xC000;
/* Section Definitions */
SECTIONS
{
.text :
{
. = ALIGN(4);
_sfixed = .;
KEEP(*(.vectors .vectors.*))
*(.text .text.* .gnu.linkonce.t.*)
*(.glue_7t) *(.glue_7)
*(.rodata .rodata* .gnu.linkonce.r.*)
*(.ARM.extab* .gnu.linkonce.armextab.*)
/* Support C constructors, and C destructors in both user code
and the C library. This also provides support for C++ code. */
. = ALIGN(4);
KEEP(*(.init))
. = ALIGN(4);
__preinit_array_start = .;
KEEP (*(.preinit_array))
__preinit_array_end = .;
. = ALIGN(4);
__init_array_start = .;
KEEP (*(SORT(.init_array.*)))
KEEP (*(.init_array))
__init_array_end = .;
. = ALIGN(4);
KEEP (*crtbegin.o(.ctors))
KEEP (*(EXCLUDE_FILE (*crtend.o) .ctors))
KEEP (*(SORT(.ctors.*)))
KEEP (*crtend.o(.ctors))
. = ALIGN(4);
KEEP(*(.fini))
. = ALIGN(4);
__fini_array_start = .;
KEEP (*(.fini_array))
KEEP (*(SORT(.fini_array.*)))
__fini_array_end = .;
KEEP (*crtbegin.o(.dtors))
KEEP (*(EXCLUDE_FILE (*crtend.o) .dtors))
KEEP (*(SORT(.dtors.*)))
KEEP (*crtend.o(.dtors))
. = ALIGN(4);
_efixed = .; /* End of text section */
} > rom
/* .ARM.exidx is sorted, so has to go in its own output section. */
PROVIDE_HIDDEN (__exidx_start = .);
.ARM.exidx :
{
*(.ARM.exidx* .gnu.linkonce.armexidx.*)
} > rom
PROVIDE_HIDDEN (__exidx_end = .);
. = ALIGN(4);
_etext = .;
.relocate : AT (_etext)
{
. = ALIGN(4);
_srelocate = .;
*(.ramfunc .ramfunc.*);
*(.data .data.*);
. = ALIGN(4);
_erelocate = .;
} > ram
.bkupram (NOLOAD):
{
. = ALIGN(8);
_sbkupram = .;
*(.bkupram .bkupram.*);
. = ALIGN(8);
_ebkupram = .;
} > bkupram
.qspi (NOLOAD):
{
. = ALIGN(8);
_sqspi = .;
*(.qspi .qspi.*);
. = ALIGN(8);
_eqspi = .;
} > qspi
/* .bss section which is used for uninitialized data */
.bss (NOLOAD) :
{
. = ALIGN(4);
_sbss = . ;
_szero = .;
*(.bss .bss.*)
*(COMMON)
. = ALIGN(4);
_ebss = . ;
_ezero = .;
} > ram
/* stack section */
.stack (NOLOAD):
{
. = ALIGN(8);
_sstack = .;
. = . + STACK_SIZE;
. = ALIGN(8);
_estack = .;
} > ram
. = ALIGN(4);
_end = . ;
}

View file

@ -42,7 +42,6 @@ void flash_write_words(uint32_t *dst, uint32_t *src, uint32_t n_words) {
uint32_t len = 4 < n_words ? 4 : n_words;
wait_ready();
// Fill a quad word so it triggers the auto-write.
for (uint32_t i = 0; i < 4; i++) {
if (i < len) {
dst[i] = src[i];
@ -78,6 +77,10 @@ bool row_same[FLASH_SIZE / NVMCTRL_BLOCK_SIZE][NVMCTRL_BLOCK_SIZE / FLASH_ROW_SI
#define QUICK_FLASH 1
void flash_write_row(uint32_t *dst, uint32_t *src) {
// The cache in Rev A isn't reliable when reading and writing to the NVM.
NVMCTRL->CTRLA.bit.CACHEDIS0 = true;
NVMCTRL->CTRLA.bit.CACHEDIS1 = true;
uint32_t block = ((uint32_t) dst) / NVMCTRL_BLOCK_SIZE;
uint8_t row = (((uint32_t) dst) % NVMCTRL_BLOCK_SIZE) / FLASH_ROW_SIZE;
#if QUICK_FLASH
@ -99,6 +102,7 @@ void flash_write_row(uint32_t *dst, uint32_t *src) {
if (!block_erased[block]) {
uint8_t rows_per_block = NVMCTRL_BLOCK_SIZE / FLASH_ROW_SIZE;
uint8_t* block_address = block * NVMCTRL_BLOCK_SIZE;
bool some_rows_same = false;
for (uint8_t i = 0; i < rows_per_block; i++) {
@ -108,7 +112,9 @@ void flash_write_row(uint32_t *dst, uint32_t *src) {
if (some_rows_same) {
for (uint8_t i = 0; i < rows_per_block; i++) {
if(row_same[block][i]) {
memcpy(row_cache[i], dst + i * FLASH_ROW_SIZE, FLASH_ROW_SIZE);
// dst is a uint32_t pointer so we add the number of words,
// not bytes.
memcpy(row_cache[i], block_address + i * (FLASH_ROW_SIZE / 4), FLASH_ROW_SIZE);
}
}
}
@ -117,11 +123,17 @@ void flash_write_row(uint32_t *dst, uint32_t *src) {
if (some_rows_same) {
for (uint8_t i = 0; i < rows_per_block; i++) {
if(row_same[block][i]) {
flash_write_words(dst + i * FLASH_ROW_SIZE, row_cache[i], FLASH_ROW_SIZE / 4);
// dst is a uint32_t pointer so we add the number of words,
// not bytes.
flash_write_words(block_address + i * (FLASH_ROW_SIZE / 4), row_cache[i], FLASH_ROW_SIZE / 4);
}
}
}
}
flash_write_words(dst, src, FLASH_ROW_SIZE / 4);
// Don't return until we're done writing in case something after us causes
// a reset.
wait_ready();
}

View file

@ -193,7 +193,7 @@ int main(void) {
// not enumerated yet
RGBLED_set_color(COLOR_START);
led_tick_step = 0;
led_tick_step = 10;
/* Wait for a complete enum on usb or a '#' char on serial line */
while (1) {
@ -204,8 +204,7 @@ int main(void) {
resetHorizon = 0;
#endif
RGBLED_set_color(COLOR_USB);
if (!led_tick_step)
led_tick_step = 1;
led_tick_step = 1;
}
main_b_cdc_enable = true;

View file

@ -1,29 +1,51 @@
#include "uf2.h"
#include "sam.h"
#ifdef SAMD21
#define BOOTLOADER_K 8
#endif
#ifdef SAMD51
#define BOOTLOADER_K 16
#endif
extern const uint8_t bootloader[];
extern const uint16_t bootloader_crcs[];
uint8_t pageBuf[FLASH_ROW_SIZE];
#define exec_cmd(cmd) \
do { \
NVMCTRL->STATUS.reg |= NVMCTRL_STATUS_MASK; \
NVMCTRL->ADDR.reg = (uint32_t)NVMCTRL_USER / 2; \
NVMCTRL->CTRLA.reg = NVMCTRL_CTRLA_CMDEX_KEY | cmd; \
while (NVMCTRL->INTFLAG.bit.READY == 0) \
; \
#ifdef SAMD21
#define NVM_FUSE_ADDR NVMCTRL_AUX0_ADDRESS
#define exec_cmd(cmd) \
do { \
NVMCTRL->STATUS.reg |= NVMCTRL_STATUS_MASK; \
NVMCTRL->ADDR.reg = (uint32_t)NVMCTRL_USER / 2; \
NVMCTRL->CTRLA.reg = NVMCTRL_CTRLA_CMDEX_KEY | cmd; \
while (NVMCTRL->INTFLAG.bit.READY == 0) {} \
} while (0)
#endif
#ifdef SAMD51
#define NVM_FUSE_ADDR NVMCTRL_FUSES_BOOTPROT_ADDR
#define exec_cmd(cmd) \
do { \
NVMCTRL->ADDR.reg = (uint32_t)NVMCTRL_USER; \
NVMCTRL->CTRLB.reg = NVMCTRL_CTRLB_CMDEX_KEY | cmd; \
while (NVMCTRL->STATUS.bit.READY == 0) {} \
} while (0)
#endif
void setBootProt(int v) {
uint32_t fuses[2];
while (!(NVMCTRL->INTFLAG.reg & NVMCTRL_INTFLAG_READY))
;
#ifdef SAMD21
while (!(NVMCTRL->INTFLAG.reg & NVMCTRL_INTFLAG_READY)) {}
#endif
#ifdef SAMD51
while (NVMCTRL->STATUS.bit.READY == 0) {}
#endif
fuses[0] = *((uint32_t *)NVMCTRL_AUX0_ADDRESS);
fuses[1] = *(((uint32_t *)NVMCTRL_AUX0_ADDRESS) + 1);
fuses[0] = *((uint32_t *)NVM_FUSE_ADDR);
fuses[1] = *(((uint32_t *)NVM_FUSE_ADDR) + 1);
uint32_t bootprot = (fuses[0] & NVMCTRL_FUSES_BOOTPROT_Msk) >> NVMCTRL_FUSES_BOOTPROT_Pos;
@ -37,15 +59,28 @@ void setBootProt(int v) {
fuses[0] = (fuses[0] & ~NVMCTRL_FUSES_BOOTPROT_Msk) | (v << NVMCTRL_FUSES_BOOTPROT_Pos);
#ifdef SAMD21
NVMCTRL->CTRLB.reg = NVMCTRL->CTRLB.reg | NVMCTRL_CTRLB_CACHEDIS | NVMCTRL_CTRLB_MANW;
exec_cmd(NVMCTRL_CTRLA_CMD_EAR);
exec_cmd(NVMCTRL_CTRLA_CMD_PBC);
#endif
#ifdef SAMD51
NVMCTRL->CTRLA.bit.WMODE = NVMCTRL_CTRLA_WMODE_MAN;
*((uint32_t *)NVMCTRL_AUX0_ADDRESS) = fuses[0];
*(((uint32_t *)NVMCTRL_AUX0_ADDRESS) + 1) = fuses[1];
exec_cmd(NVMCTRL_CTRLB_CMD_EP);
exec_cmd(NVMCTRL_CTRLB_CMD_PBC);
#endif
*((uint32_t *)NVM_FUSE_ADDR) = fuses[0];
*(((uint32_t *)NVM_FUSE_ADDR) + 1) = fuses[1];
#ifdef SAMD21
exec_cmd(NVMCTRL_CTRLA_CMD_WAP);
#endif
#ifdef SAMD51
exec_cmd(NVMCTRL_CTRLB_CMD_WQW);
#endif
resetIntoApp();
}
@ -55,7 +90,7 @@ int main(void) {
logmsg("Start");
assert(8 << NVMCTRL->PARAM.bit.PSZ == FLASH_PAGE_SIZE);
assert((8 << NVMCTRL->PARAM.bit.PSZ) == FLASH_PAGE_SIZE);
// assert(FLASH_PAGE_SIZE * NVMCTRL->PARAM.bit.NVMP == FLASH_SIZE);
/* We have determined we should stay in the monitor. */
@ -66,7 +101,19 @@ int main(void) {
logmsg("Before main loop");
#ifdef SAMD21
setBootProt(7); // 0k
#endif
#ifdef SAMD51
// We only need to set the BOOTPROT once on the SAMD51. For updates, we can
// temporarily turn the protection off instead.
if (NVMCTRL->STATUS.bit.BOOTPROT != 13) {
setBootProt(13); // 16k
}
exec_cmd(NVMCTRL_CTRLB_CMD_SBPDIS);
NVMCTRL->CTRLA.bit.CACHEDIS0 = true;
NVMCTRL->CTRLA.bit.CACHEDIS1 = true;
#endif
const uint8_t *ptr = bootloader;
int i;
@ -89,22 +136,29 @@ int main(void) {
logmsg("Update successful!");
// re-base int vector back to bootloader, so that the flash erase below doesn't write over the
// vectors
SCB->VTOR = 0;
// Write zeros to the stack location and reset handler location so the
// bootloader doesn't run us a second time. We don't need to erase to write
// zeros. The remainder of the write unit will be set to 1s which should
// preserve the existing values but its not critical.
uint32_t zeros[2] = {0, 0};
flash_write_words((void *)(BOOTLOADER_K * 1024), zeros, 2);
for (i = 0; i < 20; ++i) {
LED_MSC_TGL();
delay(1000);
}
// re-base int vector back to bootloader, so that the flash erase below doesn't write over the
// vectors
SCB->VTOR = 0;
// erase first row of this updater app, so the bootloader doesn't start us again
flash_erase_row((void *)(BOOTLOADER_K * 1024));
LED_MSC_OFF();
#ifdef SAMD21
setBootProt(2); // 8k
#endif
// For the SAMD51, the boot protection will automatically be re-enabled on
// reset.
resetIntoBootloader();