diff --git a/src/machine/machine_nrf.go b/src/machine/machine_nrf.go index d6d6349f29..1a49d2611d 100644 --- a/src/machine/machine_nrf.go +++ b/src/machine/machine_nrf.go @@ -3,7 +3,9 @@ package machine import ( + "device/arm" "device/nrf" + "errors" "internal/binary" "runtime/interrupt" "unsafe" @@ -380,6 +382,10 @@ func (f flashBlockDevice) ReadAt(p []byte, off int64) (n int, err error) { // If the length of p is not long enough it will be padded with 0xFF bytes. // This method assumes that the destination is already erased. func (f flashBlockDevice) WriteAt(p []byte, off int64) (n int, err error) { + if len(p) == 0 { + return 0, nil // nothing to do (and it would fail in sd_flash_write) + } + if FlashDataStart()+uintptr(off)+uintptr(len(p)) > FlashDataEnd() { return 0, errFlashCannotWritePastEOF } @@ -387,6 +393,34 @@ func (f flashBlockDevice) WriteAt(p []byte, off int64) (n int, err error) { address := FlashDataStart() + uintptr(off) padded := flashPad(p, int(f.WriteBlockSize())) + // When the SoftDevice is enabled, access to the flash is restricted and + // must go through the SoftDevice API. + if isSoftDeviceEnabled() { + // Call sd_flash_write, which is SVC_SOC_BASE + 9 in all the + // SoftDevices I've checked. + // Documentation: + // https://docs.nordicsemi.com/bundle/s140_v6.0.0_api/page/group_n_r_f_s_o_c_f_u_n_c_t_i_o_n_s.html + result := arm.SVCall3(0x20+9, address, &p[0], uint32(len(p))) + if result != 0 { + // Could not queue flash operation? Not sure when this can + // happen. + return 0, flashError + } + + // Wait until the SoftDevice is finished. + flashStatus = flashStatusBusy + for flashStatus == flashStatusBusy { + handleSoftDeviceEvents() + } + + // Check whether the operation was successful. + if flashStatus != flashStatusOk { + flashStatus = flashStatusOk + return 0, flashError + } + return len(p), nil + } + waitWhileFlashBusy() nrf.NVMC.SetCONFIG_WEN(nrf.NVMC_CONFIG_WEN_Wen) @@ -428,6 +462,40 @@ func (f flashBlockDevice) EraseBlockSize() int64 { // supports this. The start and len parameters are in block numbers, use // EraseBlockSize to map addresses to blocks. func (f flashBlockDevice) EraseBlocks(start, len int64) error { + // When the SoftDevice is enabled, access to the flash is restricted and + // must go through the SoftDevice API. + if isSoftDeviceEnabled() { + for i := range uint32(len) { + flashPage := uint32(FlashDataStart())/eraseBlockSizeValue + uint32(start) + i + + // Call sd_flash_page_erase, which is SVC_SOC_BASE + 8 in all the + // SoftDevices I've checked. + // Documentation: + // https://docs.nordicsemi.com/bundle/s140_v6.0.0_api/page/group_n_r_f_s_o_c_f_u_n_c_t_i_o_n_s.html#ga9c93dd94a138ad8b5ed3693ea38ffb3e + result := arm.SVCall1(0x20+8, flashPage) + if result != 0 { + // Could not queue flash operation? Not sure when this can + // happen. + return flashError + } + + // Wait until the SoftDevice is finished. + flashStatus = flashStatusBusy + for flashStatus == flashStatusBusy { + handleSoftDeviceEvents() + } + + // Check whether the operation was successful. + if flashStatus != flashStatusOk { + flashStatus = flashStatusOk + return flashError + } + } + return nil + } + + // SoftDevice is not used or enabled. Use NVIC directly. + address := FlashDataStart() + uintptr(start*f.EraseBlockSize()) waitWhileFlashBusy() @@ -447,3 +515,52 @@ func waitWhileFlashBusy() { for nrf.NVMC.GetREADY() != nrf.NVMC_READY_READY_Ready { } } + +var flashError = errors.New("machine: flash operation failed") + +const ( + flashStatusOk = iota + flashStatusError + flashStatusBusy +) + +var flashStatus uint8 = flashStatusOk + +var sdEvent uint32 + +// Process all queued SoftDevice events. May only be called when the SoftDevice is enabled. +// +// Normally these are handled in the same interrupt where Bluetooth events are +// handled. But in TinyGo, that's complicated. One option would be to put it in +// the tinygo.org/x/bluetooth package, but that would cause a circular +// dependency between the machine and the bluetooth package. Another would be to +// put it here, but let the bluetooth package call handleSoftDeviceEvents, but +// that relies on updating the bluetooth package at the same time. As a +// compromise, these events are handled directly where they are expected (here +// in the machine package). This works in practice since there are only very few +// of such events (at the moment, only flash-related ones which is in the +// machine package anyway). +func handleSoftDeviceEvents() { + for { + var result uintptr + if nrf.Device == "nrf52" || nrf.Device == "nrf52840" || nrf.Device == "nrf52833" { + // sd_evt_get: SOC_SVC_BASE_NOT_AVAILABLE + 31 + result = arm.SVCall1(0x2C+31, &sdEvent) + } else { + return // TODO: nrf51 etc + } + if result != 0 { + // Some error occured. The only possible error is + // NRF_ERROR_NOT_FOUND, which means there are no more events. + return + } + + // The following events are the same numbers in all SoftDevices I've checked. + switch sdEvent { + case 2: // NRF_EVT_FLASH_OPERATION_SUCCESS + flashStatus = flashStatusOk + case 3: // NRF_EVT_FLASH_OPERATION_ERROR + flashStatus = flashStatusError + } + } +} diff --git a/src/machine/machine_nrf_bare.go b/src/machine/machine_nrf_bare.go index b94886ed91..fc0892853b 100644 --- a/src/machine/machine_nrf_bare.go +++ b/src/machine/machine_nrf_bare.go @@ -7,3 +7,7 @@ package machine func GetRNG() (ret uint32, err error) { return getRNG() } + +func isSoftDeviceEnabled() bool { + return false +} diff --git a/src/machine/machine_nrf_sd.go b/src/machine/machine_nrf_sd.go index b816e62ee0..b8fc781eb6 100644 --- a/src/machine/machine_nrf_sd.go +++ b/src/machine/machine_nrf_sd.go @@ -11,9 +11,8 @@ import ( // avoid a heap allocation in GetRNG. var ( - softdeviceEnabled uint8 - bytesAvailable uint8 - buf [4]uint8 + bytesAvailable uint8 + buf [4]uint8 errNoSoftDeviceSupport = errors.New("rng: softdevice not supported on this device") errNotEnoughRandomData = errors.New("rng: not enough random data available") @@ -24,9 +23,7 @@ var ( func GetRNG() (ret uint32, err error) { // First check whether the SoftDevice is enabled. // sd_rand_application_bytes_available_get cannot be called when the SoftDevice is not enabled. - arm.SVCall1(0x12, &softdeviceEnabled) // sd_softdevice_is_enabled - - if softdeviceEnabled == 0 { + if !isSoftDeviceEnabled() { return getRNG() } @@ -57,3 +54,8 @@ func GetRNG() (ret uint32, err error) { return uint32(buf[0]) | uint32(buf[1])<<8 | uint32(buf[2])<<16 | uint32(buf[3])<<24, nil } + +// This function is defined in the runtime, but we need it too. +// +//go:linkname isSoftDeviceEnabled runtime.isSoftDeviceEnabled +func isSoftDeviceEnabled() bool diff --git a/src/runtime/runtime_nrf_softdevice.go b/src/runtime/runtime_nrf_softdevice.go index 3fb6550baa..849ecb5073 100644 --- a/src/runtime/runtime_nrf_softdevice.go +++ b/src/runtime/runtime_nrf_softdevice.go @@ -13,17 +13,24 @@ func sd_app_evt_wait() // This is a global variable to avoid a heap allocation in waitForEvents. var softdeviceEnabled uint8 +// Check whether the SoftDevice is currently enabled. +// This function is also called from the machine package, so the signature has +// to stay consistent. +func isSoftDeviceEnabled() bool { + // First check whether the SoftDevice is enabled. Unfortunately, + // sd_app_evt_wait cannot be called when the SoftDevice is not enabled. + arm.SVCall1(0x12, &softdeviceEnabled) // sd_softdevice_is_enabled + + return softdeviceEnabled != 0 +} + func waitForEvents() { // Call into the SoftDevice to sleep. This is necessary here because a // normal wfe will not put the chip in low power mode (it still consumes // 500µA-1mA). It is really needed to call sd_app_evt_wait for low power // consumption. - // First check whether the SoftDevice is enabled. Unfortunately, - // sd_app_evt_wait cannot be called when the SoftDevice is not enabled. - arm.SVCall1(0x12, &softdeviceEnabled) // sd_softdevice_is_enabled - - if softdeviceEnabled != 0 { + if isSoftDeviceEnabled() { // Now pick the appropriate SVCall number. Hopefully they won't change // in the future with a different SoftDevice version. if nrf.Device == "nrf51" {