diff --git a/firmware/src/gem.h b/firmware/src/gem.h index e70d95cf..528d708d 100644 --- a/firmware/src/gem.h +++ b/firmware/src/gem.h @@ -10,6 +10,7 @@ #include "gem_clocks.h" #include "gem_config.h" #include "gem_dotstar.h" +#include "gem_fuses.h" #include "gem_i2c.h" #include "gem_led_animation.h" #include "gem_lookup_tables.h" diff --git a/firmware/src/hw/gem_fuses.c b/firmware/src/hw/gem_fuses.c new file mode 100644 index 00000000..ab95ab62 --- /dev/null +++ b/firmware/src/hw/gem_fuses.c @@ -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); +} diff --git a/firmware/src/hw/gem_fuses.h b/firmware/src/hw/gem_fuses.h new file mode 100644 index 00000000..4ec30159 --- /dev/null +++ b/firmware/src/hw/gem_fuses.h @@ -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 + +/* + 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); diff --git a/firmware/src/main.c b/firmware/src/main.c index fa09b39a..a692aeaf 100644 --- a/firmware/src/main.c +++ b/firmware/src/main.c @@ -38,6 +38,9 @@ static void init_() { Core system initialization. */ + /* Before doing *anything*, check the fuses. */ + gem_fuses_check(); + /* Enable the Micro Trace Buffer for better debug stacktraces. */ wntr_mtb_init(); @@ -52,7 +55,7 @@ static void init_() { gem_nvm_init(); /* Tell the world who we are and how we got here. :) */ - printf(wntr_build_info_string()); + printf("%s\n", wntr_build_info_string()); /* Peripheral setup