-
-
Notifications
You must be signed in to change notification settings - Fork 39
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
firmware: Check bootloader write protection fuses during initialization
Previously, we assumed that the SAMD UF2 bootloader would *always* set the bootloader write protection fuses, however, it turns out that it only sets it in two cases: 1. The fuses have completely bogus values (`0xFFFFFFFF`) 2. The bootloader self-updates via UF2 Since our boards have factory fuses set and we install the bootloader via flashing a `.bin` file, neither of these cases ever happened. This left the bootloader write protection disabled which could lead to the bootloader getting corrupted by spurious writes. We actually observed this behavior by stress testing power cycles. This change checks fuses on startup and sets the `BOOTPROT` fuse if necessary.
- Loading branch information
Showing
4 changed files
with
143 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
/* | ||
Copyright (c) 2021 Alethea Katherine Flowers. | ||
Published under the standard MIT License. | ||
Full text available at: https://opensource.org/licenses/MIT | ||
*/ | ||
|
||
#include "gem_fuses.h" | ||
#include "printf.h" | ||
|
||
/* 0x02 = 8k, see datasheet section 22.6.5 */ | ||
#define BOOTLOADER_BOOTPROT_SIZE 0x02 | ||
|
||
/* Private forward declarations */ | ||
static void print_fuses(); | ||
|
||
/* Public functions */ | ||
|
||
void gem_fuses_check() { | ||
/* Check bootprot and ensure the bootloader is write-protected. */ | ||
if (NVM_USER->USER_ROW.bit.BOOTPROT == BOOTLOADER_BOOTPROT_SIZE) { | ||
/* All good, bootprot is set correctly. */ | ||
printf("Fuses OK\n"); | ||
return; | ||
} | ||
|
||
/* bootprot needs to be set. */ | ||
printf("Setting BOOTPROT fuses. Current fuses:\n"); | ||
print_fuses(); | ||
|
||
NVM_USER_Type fuses = *NVM_USER; | ||
fuses.USER_ROW.bit.BOOTPROT = BOOTLOADER_BOOTPROT_SIZE; | ||
gem_fuses_write(fuses); | ||
|
||
printf("BOOTPROT set, updated fuses:\n"); | ||
print_fuses(); | ||
|
||
/* Reboot. */ | ||
NVIC_SystemReset(); | ||
} | ||
|
||
void gem_fuses_write(NVM_USER_Type fuses) { | ||
__disable_irq(); | ||
|
||
/* Setup NVM for writing and disable cache. */ | ||
uint32_t ctrlb = NVMCTRL->CTRLB.reg; | ||
NVMCTRL->STATUS.reg |= NVMCTRL_STATUS_MASK; | ||
NVMCTRL->CTRLB.reg |= NVMCTRL_CTRLB_CACHEDIS | NVMCTRL_CTRLB_MANW; | ||
NVMCTRL->ADDR.reg = NVMCTRL_FUSES_BOOTPROT_ADDR / 2; | ||
|
||
/* Erase the row, flush the page cache. */ | ||
NVMCTRL->CTRLA.reg = NVMCTRL_CTRLA_CMDEX_KEY | NVMCTRL_CTRLA_CMD_EAR; | ||
while (NVMCTRL->INTFLAG.bit.READY == 0) {} | ||
NVMCTRL->CTRLA.reg = NVMCTRL_CTRLA_CMDEX_KEY | NVMCTRL_CTRLA_CMD_PBC; | ||
while (NVMCTRL->INTFLAG.bit.READY == 0) {} | ||
|
||
/* Write the data - must be done in 16 or 32 bit chunks. */ | ||
((uint32_t*)NVMCTRL_USER)[0] = ((uint32_t*)&fuses)[0]; | ||
((uint32_t*)NVMCTRL_USER)[1] = ((uint32_t*)&fuses)[1]; | ||
|
||
/* Write the page. */ | ||
NVMCTRL->CTRLA.reg = NVMCTRL_CTRLA_CMDEX_KEY | NVMCTRL_CTRLA_CMD_WAP; | ||
while (NVMCTRL->INTFLAG.bit.READY == 0) {} | ||
|
||
/* Restore saved CTRLB value. */ | ||
NVMCTRL->CTRLB.reg = ctrlb; | ||
|
||
__enable_irq(); | ||
} | ||
|
||
/* Private functions */ | ||
|
||
static void print_fuses() { | ||
printf("Fuses: 0x%8x%8x\n", ((uint32_t*)NVMCTRL_USER)[1], ((uint32_t*)NVMCTRL_USER)[0]); | ||
printf("- BOOTPROT: 0x%x\n", NVM_USER->USER_ROW.bit.BOOTPROT); | ||
printf("- EEPROM: 0x%x\n", NVM_USER->USER_ROW.bit.EEPROM); | ||
printf("- BOD33_LEVEL: 0x%x\n", NVM_USER->USER_ROW.bit.BOD33_LEVEL); | ||
printf("- BOD33_ENABLE: 0x%x\n", NVM_USER->USER_ROW.bit.BOD33_ENABLE); | ||
printf("- BOD33_ACTION: 0x%x\n", NVM_USER->USER_ROW.bit.BOD33_ACTION); | ||
printf("- WDT_ENABLE: 0x%x\n", NVM_USER->USER_ROW.bit.WDT_ENABLE); | ||
printf("- WDT_ALWAYSON: 0x%x\n", NVM_USER->USER_ROW.bit.WDT_ALWAYSON); | ||
printf("- WDT_PERIOD: 0x%x\n", NVM_USER->USER_ROW.bit.WDT_PERIOD); | ||
printf("- WDT_WINDOW: 0x%x\n", NVM_USER->USER_ROW.bit.WDT_WINDOW); | ||
printf("- WDT_EWOFFSET: 0x%x\n", NVM_USER->USER_ROW.bit.WDT_EWOFFSET); | ||
printf("- WDT_WEN: 0x%x\n", NVM_USER->USER_ROW.bit.WDT_WEN); | ||
printf("- BOD33_HYST: 0x%x\n", NVM_USER->USER_ROW.bit.BOD33_HYST); | ||
printf("- LOCK: 0x%x\n", NVM_USER->USER_ROW.bit.LOCK); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
/* | ||
Copyright (c) 2021 Alethea Katherine Flowers. | ||
Published under the standard MIT License. | ||
Full text available at: https://opensource.org/licenses/MIT | ||
*/ | ||
|
||
#pragma once | ||
|
||
/* Checks/sets SAMD21 fuses (NVM USER ROW) */ | ||
|
||
#include "sam.h" | ||
#include <stdint.h> | ||
|
||
/* | ||
Struct mapping for the User Row, since it's not included in the normal CMSIS headers for the SAMD21. | ||
See datasheet section 10.3.1 for full details. | ||
*/ | ||
// clang-format off | ||
typedef union { | ||
struct { | ||
uint64_t BOOTPROT:3; /*!< bit: 0.. 2 Used to select one of eight different bootloader sizes. */ | ||
uint64_t :1; /*!< bit: 3 Reserved */ | ||
uint64_t EEPROM:3; /*!< bit: 4.. 6 Used to select one of eight different EEPROM sizes. */ | ||
uint64_t :1; /*!< bit: 7 Reserved */ | ||
uint64_t BOD33_LEVEL:6; /*!< bit: 8..13 BOD33 threshold Level at power on. */ | ||
uint64_t BOD33_ENABLE:1; /*!< bit: 14 BOD33 enable at power on. */ | ||
uint64_t BOD33_ACTION:2; /*!< bit: 15..16 BOD33 action at power on. */ | ||
uint64_t :8; /*!< bit: 17..24 Reserved: Voltage Regulator Internal BOD (BOD12) configuration. These bits are written in production and must not be changed. */ | ||
uint64_t WDT_ENABLE:1; /*!< bit: 25 WDT enable at power on. */ | ||
uint64_t WDT_ALWAYSON:1; /*!< bit: 26 WDT always on at power on. */ | ||
uint64_t WDT_PERIOD:4; /*!< bit: 27..30 WDT period at power on. */ | ||
uint64_t WDT_WINDOW:4; /*!< bit: 31..34 WDT window at power on. */ | ||
uint64_t WDT_EWOFFSET:4; /*!< bit: 35..38 WDT early warning interrupt time at power on. */ | ||
uint64_t WDT_WEN:1; /*!< bit: 39 WDT timer window mode enable at power on. */ | ||
uint64_t BOD33_HYST:1; /*!< bit: 40 BOD33 hysteresis configuration at power on. */ | ||
uint64_t :1; /*!< bit: 41 Reserved: Voltage Regulator Internal BOD(BOD12) configuration. This bit is written in production and must not be changed. */ | ||
uint64_t :6; /*!< bit: 42..47 Reserved */ | ||
uint64_t LOCK:16; /*!< bit: 48..63 NVM Region Lock Bits. */ | ||
} bit; | ||
uint64_t reg; | ||
} USER_ROW_Type; | ||
// clang-format on | ||
|
||
typedef struct { | ||
__IO USER_ROW_Type USER_ROW; | ||
} NVM_USER_Type; | ||
|
||
#define NVM_USER ((NVM_USER_Type*)NVMCTRL_USER) | ||
|
||
void gem_fuses_check(); | ||
void gem_fuses_write(NVM_USER_Type fuses); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters