Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions locale/circuitpython.pot
Original file line number Diff line number Diff line change
Expand Up @@ -1033,6 +1033,7 @@ msgstr ""

#: ports/atmel-samd/common-hal/sdioio/SDCard.c
#: ports/espressif/common-hal/qspibus/QSPIBus.c
#: shared-bindings/socketpool/SocketPool.c
msgid "%q failure: %d"
msgstr ""

Expand Down Expand Up @@ -1153,6 +1154,7 @@ msgstr ""

#: ports/espressif/common-hal/_bleio/Adapter.c
#: ports/nordic/common-hal/_bleio/Adapter.c
#: ports/zephyr-cp/common-hal/_bleio/Adapter.c
msgid "Data too large for advertisement packet"
msgstr ""

Expand Down Expand Up @@ -1817,7 +1819,7 @@ msgstr ""
#: ports/raspberrypi/bindings/cyw43/__init__.c py/argcheck.c py/objexcept.c
#: shared-bindings/bitmapfilter/__init__.c shared-bindings/canio/CAN.c
#: shared-bindings/digitalio/Pull.c shared-bindings/supervisor/__init__.c
#: shared-module/audiofilters/Filter.c shared-module/displayio/__init__.c
#: shared-module/audiofilters/__init__.c shared-module/displayio/__init__.c
#: shared-module/synthio/Synthesizer.c
msgid "%q must be of type %q or %q, not %q"
msgstr ""
Expand Down Expand Up @@ -1965,6 +1967,7 @@ msgid "Unable to access unaligned IO register"
msgstr ""

#: ports/raspberrypi/common-hal/memorymap/AddressRange.c
#: supervisor/shared/safe_mode.c
msgid "Unable to write to read-only memory"
msgstr ""

Expand Down Expand Up @@ -2353,7 +2356,7 @@ msgstr ""
msgid "%q length must be %d"
msgstr ""

#: py/argcheck.c shared-module/audiofilters/Filter.c
#: py/argcheck.c shared-module/audiofilters/__init__.c
msgid "%q in %q must be of type %q, not %q"
msgstr ""

Expand Down
6 changes: 6 additions & 0 deletions ports/nordic/mpconfigport.h
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,12 @@
// The firmware space is the space left over between the fixed lower and upper regions.
#define CIRCUITPY_FIRMWARE_SIZE (CIRCUITPY_BLE_CONFIG_START_ADDR - CIRCUITPY_FIRMWARE_START_ADDR)

// nRF52's access control hardware write-protect every part of
// internal flash that CircuitPython does not own
#ifndef CIRCUITPY_NRF_FLASH_PROTECT
#define CIRCUITPY_NRF_FLASH_PROTECT (0)
#endif

#if BOOTLOADER_START_ADDR % FLASH_ERASE_SIZE != 0
#error BOOTLOADER_START_ADDR must be on a flash erase boundary.
#endif
Expand Down
85 changes: 85 additions & 0 deletions ports/nordic/peripherals/nrf/nvm.c
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,25 @@

#define FLASH_PAGE_SIZE (4096)

static bool flash_page_is_ours(uint32_t page_addr) {
if ((page_addr & (FLASH_PAGE_SIZE - 1)) != 0) {
return false;
}
#if CIRCUITPY_INTERNAL_NVM_SIZE > 0
if (page_addr >= CIRCUITPY_INTERNAL_NVM_START_ADDR &&
page_addr < CIRCUITPY_INTERNAL_NVM_START_ADDR + CIRCUITPY_INTERNAL_NVM_SIZE) {
return true;
}
#endif
#if CIRCUITPY_INTERNAL_FLASH_FILESYSTEM_SIZE > 0
if (page_addr >= CIRCUITPY_INTERNAL_FLASH_FILESYSTEM_START_ADDR &&
page_addr < CIRCUITPY_INTERNAL_FLASH_FILESYSTEM_START_ADDR + CIRCUITPY_INTERNAL_FLASH_FILESYSTEM_SIZE) {
return true;
}
#endif
return false;
}

#ifdef BLUETOOTH_SD
#include "ble_drv.h"
#include "nrf_sdm.h"
Expand Down Expand Up @@ -68,13 +87,79 @@ bool sd_flash_write_sync(uint32_t *dest_words, uint32_t *src_words, uint32_t num

#endif

#if CIRCUITPY_NRF_FLASH_PROTECT
static bool page_is_acl_protected(uint32_t page_addr) {
for (size_t slot = 0; slot < ACL_REGIONS_COUNT; slot++) {
uint32_t base = NRF_ACL->ACL[slot].ADDR;
uint32_t size = NRF_ACL->ACL[slot].SIZE;
if (size != 0 && page_addr >= base && page_addr < base + size) {
return true;
}
}
return false;
}

// Claim the next unused ACL region and write-protect addr..addr + size.
static void acl_claim(uint32_t addr, uint32_t size) {
for (size_t slot = 0; slot < ACL_REGIONS_COUNT; slot++) {
if (NRF_ACL->ACL[slot].PERM != 0 || NRF_ACL->ACL[slot].SIZE != 0) {
// Already claimed by the MBR or the bootloader.
continue;
}
NRF_ACL->ACL[slot].ADDR = addr;
NRF_ACL->ACL[slot].SIZE = size;
NRF_ACL->ACL[slot].PERM = ACL_ACL_PERM_WRITE_Disable << ACL_ACL_PERM_WRITE_Pos;

if (NRF_ACL->ACL[slot].ADDR == addr && NRF_ACL->ACL[slot].SIZE == size) {
return;
}
}
}

// Write-protect start..end, one ACL region per run of pages that is not
// already protected.
static void acl_write_protect(uint32_t start, uint32_t end) {
uint32_t run_start = 0;
bool in_run = false;

for (uint32_t page_addr = start; page_addr < end; page_addr += FLASH_PAGE_SIZE) {
if (!page_is_acl_protected(page_addr)) {
if (!in_run) {
run_start = page_addr;
in_run = true;
}
} else if (in_run) {
acl_claim(run_start, page_addr - run_start);
in_run = false;
}
}
if (in_run) {
acl_claim(run_start, end - run_start);
}
}
#endif

void nrf_nvm_protect_init(void) {
#if CIRCUITPY_NRF_FLASH_PROTECT
// Everything below the regions CircuitPython owns: the MBR, the
// SoftDevice, the interrupt vectors and the firmware itself.
acl_write_protect(MBR_START_ADDR, CIRCUITPY_BLE_CONFIG_START_ADDR);
// The bootloader, its copy of the MBR, and its settings page.
acl_write_protect(BOOTLOADER_START_ADDR, FLASH_SIZE);
#endif
}

// The nRF52840 datasheet specifies a maximum of two writes to a flash
// location before an erase is necessary, even if the write is all
// ones (erased state). So we can't avoid erases even if the page
// appears to be already erased (all ones), unless we keep track of
// writes to a page.

bool nrf_nvm_safe_flash_page_write(uint32_t page_addr, uint8_t *data) {
if (!flash_page_is_ours(page_addr)) {
return false;
}

#ifdef BLUETOOTH_SD
if (sd_is_enabled()) {
uint32_t err_code;
Expand Down
4 changes: 4 additions & 0 deletions ports/nordic/peripherals/nrf/nvm.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,7 @@ bool sd_flash_write_sync(uint32_t *dest_words, uint32_t *src_words, uint32_t num
#endif

bool nrf_nvm_safe_flash_page_write(uint32_t page_addr, uint8_t *data);

// Hardware write-protect the parts of internal flash CircuitPython does not
// own, using the ACL peripheral.
void nrf_nvm_protect_init(void);
13 changes: 13 additions & 0 deletions ports/nordic/supervisor/internal_flash.c
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@ static inline uint32_t lba2addr(uint32_t block) {
return CIRCUITPY_INTERNAL_FLASH_FILESYSTEM_START_ADDR + block * FILESYSTEM_BLOCK_SIZE;
}

static bool blocks_in_range(uint32_t lba, uint32_t num_blocks) {
uint32_t block_count = supervisor_flash_get_block_count();
return lba <= block_count && num_blocks <= block_count - lba;
}

void supervisor_flash_init(void) {
}

Expand All @@ -61,6 +66,10 @@ void port_internal_flash_flush(void) {
}

mp_uint_t supervisor_flash_read_blocks(uint8_t *dest, uint32_t block, uint32_t num_blocks) {
if (!blocks_in_range(block, num_blocks)) {
return 1; // failure
}

// Must write out anything in cache before trying to read.
supervisor_flash_flush();

Expand All @@ -70,6 +79,10 @@ mp_uint_t supervisor_flash_read_blocks(uint8_t *dest, uint32_t block, uint32_t n
}

mp_uint_t supervisor_flash_write_blocks(const uint8_t *src, uint32_t lba, uint32_t num_blocks) {
if (!blocks_in_range(lba, num_blocks)) {
return 1; // failure
}

while (num_blocks) {
uint32_t const addr = lba2addr(lba);
uint32_t const page_addr = addr & ~(FLASH_PAGE_SIZE - 1);
Expand Down
17 changes: 17 additions & 0 deletions ports/nordic/supervisor/port.c
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

#include "nrf/cache.h"
#include "nrf/clocks.h"
#include "nrf/nvm.h"
#include "nrf/power.h"
#include "nrf/timers.h"

Expand Down Expand Up @@ -131,6 +132,9 @@ void tick_set_prescaler(uint32_t prescaler_val) {
}

safe_mode_t port_init(void) {
// Lock the flash regions we do not own
nrf_nvm_protect_init();

nrf_peripherals_clocks_init();

// If GPIO voltage is set wrong in UICR, this will fix it, and
Expand Down Expand Up @@ -358,6 +362,19 @@ void port_idle_until_interrupt(void) {

extern void HardFault_Handler(void);
void HardFault_Handler(void) {
#if CIRCUITPY_NRF_FLASH_PROTECT
// A write to an ACL-protected page raises a BusFault, which escalates to
// this HardFault.
if ((SCB->CFSR & SCB_CFSR_BUSFAULTSR_Msk) != 0) {
const bool bfar_in_flash =
(SCB->CFSR & SCB_CFSR_BFARVALID_Msk) != 0 && SCB->BFAR < FLASH_SIZE;
const bool nvmc_armed =
NRF_NVMC->CONFIG != (NVMC_CONFIG_WEN_Ren << NVMC_CONFIG_WEN_Pos);
if (bfar_in_flash || nvmc_armed) {
reset_into_safe_mode(SAFE_MODE_FLASH_WRITE_PROTECTED);
}
}
#endif
reset_into_safe_mode(SAFE_MODE_HARD_FAULT);
while (true) {
asm ("nop;");
Expand Down
3 changes: 3 additions & 0 deletions supervisor/shared/safe_mode.c
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,9 @@ void print_safe_mode_message(safe_mode_t reason) {
message = MP_ERROR_TEXT("You pressed the reset button during boot.");
#endif
break;
case SAFE_MODE_FLASH_WRITE_PROTECTED:
message = MP_ERROR_TEXT("Unable to write to read-only memory");
break;
case SAFE_MODE_NO_CIRCUITPY:
message = MP_ERROR_TEXT("CIRCUITPY drive could not be found or created.");
break;
Expand Down
1 change: 1 addition & 0 deletions supervisor/shared/safe_mode.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ typedef enum {
SAFE_MODE_BROWNOUT,
// alphabetical from here down
SAFE_MODE_FLASH_WRITE_FAIL,
SAFE_MODE_FLASH_WRITE_PROTECTED,
SAFE_MODE_GC_ALLOC_OUTSIDE_VM,
SAFE_MODE_HARD_FAULT,
SAFE_MODE_INTERRUPT_ERROR,
Expand Down
Loading