33package machine
44
55import (
6+ "device/arm"
67 "device/nrf"
8+ "errors"
79 "internal/binary"
810 "runtime/interrupt"
911 "unsafe"
@@ -380,13 +382,45 @@ func (f flashBlockDevice) ReadAt(p []byte, off int64) (n int, err error) {
380382// If the length of p is not long enough it will be padded with 0xFF bytes.
381383// This method assumes that the destination is already erased.
382384func (f flashBlockDevice ) WriteAt (p []byte , off int64 ) (n int , err error ) {
385+ if len (p ) == 0 {
386+ return 0 , nil // nothing to do (and it would fail in sd_flash_write)
387+ }
388+
383389 if FlashDataStart ()+ uintptr (off )+ uintptr (len (p )) > FlashDataEnd () {
384390 return 0 , errFlashCannotWritePastEOF
385391 }
386392
387393 address := FlashDataStart () + uintptr (off )
388394 padded := flashPad (p , int (f .WriteBlockSize ()))
389395
396+ // When the SoftDevice is enabled, access to the flash is restricted and
397+ // must go through the SoftDevice API.
398+ if isSoftDeviceEnabled () {
399+ // Call sd_flash_write, which is SVC_SOC_BASE + 9 in all the
400+ // SoftDevices I've checked.
401+ // Documentation:
402+ // 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
403+ result := arm .SVCall3 (0x20 + 9 , address , & p [0 ], uint32 (len (p )))
404+ if result != 0 {
405+ // Could not queue flash operation? Not sure when this can
406+ // happen.
407+ return 0 , flashError
408+ }
409+
410+ // Wait until the SoftDevice is finished.
411+ flashStatus = flashStatusBusy
412+ for flashStatus == flashStatusBusy {
413+ handleSoftDeviceEvents ()
414+ }
415+
416+ // Check whether the operation was successful.
417+ if flashStatus != flashStatusOk {
418+ flashStatus = flashStatusOk
419+ return 0 , flashError
420+ }
421+ return len (p ), nil
422+ }
423+
390424 waitWhileFlashBusy ()
391425
392426 nrf .NVMC .SetCONFIG_WEN (nrf .NVMC_CONFIG_WEN_Wen )
@@ -428,6 +462,40 @@ func (f flashBlockDevice) EraseBlockSize() int64 {
428462// supports this. The start and len parameters are in block numbers, use
429463// EraseBlockSize to map addresses to blocks.
430464func (f flashBlockDevice ) EraseBlocks (start , len int64 ) error {
465+ // When the SoftDevice is enabled, access to the flash is restricted and
466+ // must go through the SoftDevice API.
467+ if isSoftDeviceEnabled () {
468+ for i := range uint32 (len ) {
469+ flashPage := uint32 (FlashDataStart ())/ eraseBlockSizeValue + uint32 (start ) + i
470+
471+ // Call sd_flash_page_erase, which is SVC_SOC_BASE + 8 in all the
472+ // SoftDevices I've checked.
473+ // Documentation:
474+ // 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
475+ result := arm .SVCall1 (0x20 + 8 , flashPage )
476+ if result != 0 {
477+ // Could not queue flash operation? Not sure when this can
478+ // happen.
479+ return flashError
480+ }
481+
482+ // Wait until the SoftDevice is finished.
483+ flashStatus = flashStatusBusy
484+ for flashStatus == flashStatusBusy {
485+ handleSoftDeviceEvents ()
486+ }
487+
488+ // Check whether the operation was successful.
489+ if flashStatus != flashStatusOk {
490+ flashStatus = flashStatusOk
491+ return flashError
492+ }
493+ }
494+ return nil
495+ }
496+
497+ // SoftDevice is not used or enabled. Use NVIC directly.
498+
431499 address := FlashDataStart () + uintptr (start * f .EraseBlockSize ())
432500 waitWhileFlashBusy ()
433501
@@ -447,3 +515,52 @@ func waitWhileFlashBusy() {
447515 for nrf .NVMC .GetREADY () != nrf .NVMC_READY_READY_Ready {
448516 }
449517}
518+
519+ var flashError = errors .New ("machine: flash operation failed" )
520+
521+ const (
522+ flashStatusOk = iota
523+ flashStatusError
524+ flashStatusBusy
525+ )
526+
527+ var flashStatus uint8 = flashStatusOk
528+
529+ var sdEvent uint32
530+
531+ // Process all queued SoftDevice events. May only be called when the SoftDevice is enabled.
532+ //
533+ // Normally these are handled in the same interrupt where Bluetooth events are
534+ // handled. But in TinyGo, that's complicated. One option would be to put it in
535+ // the tinygo.org/x/bluetooth package, but that would cause a circular
536+ // dependency between the machine and the bluetooth package. Another would be to
537+ // put it here, but let the bluetooth package call handleSoftDeviceEvents, but
538+ // that relies on updating the bluetooth package at the same time. As a
539+ // compromise, these events are handled directly where they are expected (here
540+ // in the machine package). This works in practice since there are only very few
541+ // of such events (at the moment, only flash-related ones which is in the
542+ // machine package anyway).
543+ func handleSoftDeviceEvents () {
544+ for {
545+ var result uintptr
546+ if nrf .Device == "nrf52" || nrf .Device == "nrf52840" || nrf .Device == "nrf52833" {
547+ // sd_evt_get: SOC_SVC_BASE_NOT_AVAILABLE + 31
548+ result = arm .SVCall1 (0x2C + 31 , & sdEvent )
549+ } else {
550+ return // TODO: nrf51 etc
551+ }
552+ if result != 0 {
553+ // Some error occured. The only possible error is
554+ // NRF_ERROR_NOT_FOUND, which means there are no more events.
555+ return
556+ }
557+
558+ // The following events are the same numbers in all SoftDevices I've checked.
559+ switch sdEvent {
560+ case 2 : // NRF_EVT_FLASH_OPERATION_SUCCESS
561+ flashStatus = flashStatusOk
562+ case 3 : // NRF_EVT_FLASH_OPERATION_ERROR
563+ flashStatus = flashStatusError
564+ }
565+ }
566+ }
0 commit comments