Repository navigation
Conversation
…stic#2) * Seed the SoftDevice RNG from the CRACEN TRNG on every request NRF_EVT_RAND_SEED_REQUEST was answered with FICR DEVICEID XOR the domain 0 GRTC SYSCOUNTER, which is predictable. The SoftDevice RNG feeds LESC key generation. Both the initial seed in begin() and later seed requests now go through seed_softdevice_rng(), which draws from the CRACEN TRNG. The RNG is no longer stopped after seeding, so a reseed cannot cut off key generation running on another task. * Retry the SoftDevice RNG seed until it succeeds A failed seed attempt on NRF_EVT_RAND_SEED_REQUEST left the SoftDevice RNG unseeded. Retry, restarting the CRACEN RNG between attempts so a generator stuck in its error state is reset. * Retry a failed RNG seed without blocking SoC event dispatch f94ff6e retried inline in the seed request handler, so a TRNG that kept failing would stop adafruit_soc_task from dispatching any further SoC events, flash completions included. Mark the seed as pending and retry it between event batches, waking every 10 ms while it is outstanding. * Recover a halted CRACEN RNG in place instead of stopping it 0acc127 recovered a failed seed by calling nRF54Crypto.end(), which disables the RNG while LESC key generation may be reading it on another task. cracen_rng_fill() now soft-resets a generator in its error state while it waits for the FIFO, the same reset cracen_rng_start() already applied, so no caller has to stop the RNG to recover it. The SoC task only retries the seed. * Serialize CRACEN RNG FIFO reads across tasks SoftDevice seed requests and LESC key generation read the RNG from different tasks. A read by one between the other's FIFO level check and its read, or a soft reset clearing the FIFO there, returned a word that is not random. Suspend the scheduler for the duration of each fill. * Limit the CRACEN RNG lock to the FIFO check and read bf63374 suspended the scheduler for a whole fill, including the FIFO polling wait, which can run for RNG_TIMEOUT iterations per word while the generator is failing. Take one word at a time in a critical section that covers only the level check and read, or the reset of a halted generator, and poll outside it.
…eshtastic#1) * Fix tickless idle on the 52-bit GRTC, GPIOTE per port, and the SYSCOUNTER read port_cmsis_systick.c: - grtc_cc_set() wrote CCH = 0. The SYSCOUNTER is 52 bits at 1 MHz and keeps running across soft resets, so it passes 2^32 about 71 minutes after power-on; from then on every compare lands in the past and never fires. The tick only survived while some other interrupt woke the CPU, and the first idle sleep after that never ended. Rebuild the high word from the live counter, carrying when the 32-bit target wrapped. - grtc_counter_get() read SYSCOUNTERL without checking validity. SYSCOUNTERH is latched by the read of SYSCOUNTERL and its reset value already has BUSY set, so re-read the pair until BUSY clears (as nrfx does). A junk read here fed the tick catch-up below. - The tick ISR catch-up had no bound: a bogus counter read stepped the tick by hours. Anything beyond a few seconds is treated as one tick and the tick base is re-anchored; genuine long sleeps are accounted in vPortSuppressTicksAndSleep. WInterrupts.c: - attachInterrupt() only ever used GPIOTE20, but nRF54L routes P0 to GPIOTE30 (4 channels) and P1 to GPIOTE20 (8 channels); P2 has no GPIOTE at all. Pick the instance by port, keep per-instance channel maps, serve both IRQ lines, and return 0 for P2 pins. Verified on an nRF54L15-DK with an SX1262 (Meshtastic): boots cold and warm, tick tracks wall clock, DIO1 interrupts on P0.00. * Bound the SoftDevice flash-completion wait and cap the tickless idle product flash_nrf5x.c: wait_for_async_flash_op_completion() blocked on the completion semaphore with portMAX_DELAY. A lost NRF_EVT_FLASH_OPERATION_* event (seen on nRF54L15 during a BLE connection) parked the writing task forever and, with every task blocked, the tickless idle put the CPU to sleep until an arbitrary wake-up: the firmware froze, BLE included. Wait in bounded slices, drain sd_evt_get() from the waiting task so the completion cannot get stuck behind a task that is not running, hand other SoC events to a weak application hook, and report NRF_ERROR_TIMEOUT when nothing arrives. port_cmsis_systick.c: clamp xExpectedIdleTime before multiplying by the GRTC ticks per systick; with every task blocked the product wrapped and the wake-up compare landed anywhere. * Write the InternalFS pages in place on RRAM and report flash failures to LittleFS The page cache flushed every 256-byte LittleFS block as erase-then-program of its 4 KB page: 32 sd_flash_write() calls filling the page with 0xFF (the nRF54L RRAM has no erase, s145 has no sd_flash_page_erase) followed by the whole page again. A hang or reset between the two left the page at 0xFF; when the page was page 0 both LittleFS superblocks vanished and the next boot reformatted the filesystem (seen on an nRF54L15-DK: the node came back unconfigured with a new identity after an overnight freeze). Flush only the 128-byte chunks that differ from the flash, in place: the other blocks sharing the page are never rewritten, so an interrupted flush can only damage the chunk in flight, which is the power-loss model LittleFS is built for. A boot now costs 11 operations instead of 34 per page. Propagate failures instead of ignoring them: flash_cache_flush() returns false, flash_cache_write() -1 when the flush it forced failed, and the LittleFS prog/ sync callbacks return LFS_ERR_IO so the caller learns the write did not happen. Create the completion semaphore on first use (fal_erase() was the only place that did, and it no longer runs before every program). Re-issue BUSY 20 times 5 ms apart and a lost completion 3 times; any other SoftDevice error is final. Keep a flight recorder (flash_nrf5x_stats) of the SoftDevice flash operations: counts of issued, completed, self-drained, timed-out and failed operations, the last destination and the longest wait, and an in_flight flag, so a stuck firmware can be read from the debugger by symbol. * Sleep the tickless idle with WFI instead of a WFE/ISPR loop WFI completes on any enabled pending interrupt regardless of PRIMASK, so the idle sleep no longer depends on SEVONPEND and on the event latch being in the right state when the loop re-checks ISPR. Same pattern Zephyr uses on this part. * Run the FreeRTOS tick on the application core's GRTC domain The tick used SYSCOUNTER[0], INTEN0 and GRTC_0_IRQn, which belong to domain 0, the FLPR's. The secure application core is domain 2 (GRTC_IRQ_GROUP in nrf54l15_interim.h), and the SoftDevice already uses group 3 for its own compares. Use the SYSCOUNTER index, INTEN group and IRQ of domain 2, and fail the build if the MDK's GRTC_IRQ_GROUP ever disagrees. This is a correctness fix on its own: it did not cure the idle hang seen on the nRF54L15-DK, whose cause was the GRTC sleep timing (next commit). * Set the GRTC sleep timing that nrfx_grtc_init() would Nothing on this core calls nrfx_grtc_init(), so TIMEOUT and WAKETIME kept their reset values, 0 and 1: the SYSCOUNTER stopped the moment the CPU slept and had a single 32 kHz cycle to wake up before a compare. On the nRF54L15-DK the core hung in the tickless idle WFI with the SYSCOUNTER stopped, every 40 min to 3.5 h. In all five hangs caught live, a SoftDevice compare sat 1.5-2 of those cycles past the stop and never fired, and neither did the tick compare 19.5 ms later. Write the values of NRFX_GRTC_SLEEP_DEFAULT_CONFIG (TIMEOUT 5, WAKETIME 4), which is what Zephyr applies on this part. Do it outside the start path, like AUTOEN, since the GRTC survives soft resets. * Cap the tickless idle at half the GRTC compare range grtc_cc_set() places a 32-bit target in the right 2^32 epoch of the 52-bit compare by carrying into the high word when the target wrapped, and it decides that from (int32_t)(val - lo) > 0. That only holds for targets less than 2^31 GRTC ticks ahead. The tickless idle allowed up to 2^32 ticks, about 71 min at 1 MHz, so with every task blocked forever a wrapped target further than ~35 min out kept the current high word, sat in the past, and the CPU slept until some other interrupt woke it. Cap the idle at 0x7FFFFFFF GRTC ticks instead of 0xFFFFFFFF. * Keep a page that failed to flush in the InternalFS cache When the flush forced by a page switch failed, flash_cache_flush() dropped the cached page and flash_cache_write() loaded the next one over it. That page held writes LittleFS had already been told were done, so they were lost, while the write that triggered the flush was cached anyway and could reach flash later despite having returned LFS_ERR_IO. Leave the page cached when its flush fails and return -1 before taking the new write, so the next flush (or sync) writes it again; on RRAM only the chunks that still differ are sent. With that, a failed write in the erase callback no longer stores its 0xFF, so the erase now reports LFS_ERR_IO like prog instead of claiming the block was erased. * Answer RNG seed requests the flash driver drains While it waits for a flash completion, the InternalFS driver drains the SoC event queue itself and passes every non-flash event to flash_nrf5x_soc_event_hook(), which nothing defined. A seed request pulled out there was dropped and never reached adafruit_soc_task(), so sd_rand_seed_set() was not called. Give Bluefruit54Lib a weak default for the hook that seeds the RNG through the same function as the SOC task. It is weak so that an application that reads the SoC events itself (Meshtastic does) can still define its own without a duplicate symbol. * Settle a timed-out flash write before issuing the next one When the wait for a SoftDevice flash completion timed out, the write could still be running. Its completion then arrived later, as a give on the semaphore or as an event in the SoC queue, and the next write (the retry, or any later one) took it for its own and reported success while it was still being written. Count accepted operations and completions instead of taking the semaphore once per operation. The SoftDevice runs one operation at a time and reports them in order, so all accepted operations are finished once the counts match, and the last completion is the current write's. Before issuing, wait (with the same 2 s bound) for any operation left outstanding. If its completion still does not come, issue anyway: BUSY means it is still running and goes through the usual BUSY retries, which no longer wait again; acceptance means it finished and another reader of the SoC queue took its completion without passing it on, so it is written off and counted in flash_nrf5x_stats.written_off. The one case left is a completion counted in the instant between that last check and sd_flash_write(), after it had been missing for more than 2 s. It can end one wait early; the surplus count is then dropped. * Count a flash write as done only once its data has landed After a completion was written off as lost, it could still arrive: the SoftDevice accepting the next write proves the earlier one finished, not that another reader consumed its completion. Counted late, it made the counts match while the new write was still running, so the driver returned success early. The cache could then load another page into the buffer the SoftDevice was still reading from, and write that data at the wrong address. Also require the destination to read back as the source before the wait for a write succeeds. The cache only programs chunks that differ from the flash, so a match means the SoftDevice has written them and is done reading the buffer. A stray completion can now only make the wait look again, never end it early. If every completion is in, the last one reported a failure and the data is not there, the write fails and is retried as before. The only write a match cannot vouch for is an erase chunk over flash that is already 0xFF; its source is a constant, so finishing it later cannot write anything else. * Run the GRTC tick on 64-bit counter and compare values grtc_cc_set() rebuilt the high word of a 32-bit target from the live counter. It only ever carried into the next epoch, never borrowed: a target slightly behind the counter when the low word had just wrapped (the tick ISR held off for more than a period by a SoftDevice interrupt) landed about 2^32 us, 71.6 min, ahead, and the tick stopped. Its SYSCOUNTERH read also ignored BUSY and OVERFLOW. Read the counter with nrfy_grtc_sys_counter_get(), which retries on both and reads this core's domain, and arm compares with the full value through nrfy_grtc_sys_counter_cc_set(). A target already in the past now fires at once; the spurious event that writing CCL before CCH can raise is dropped while the target is still ahead, as nrfx does. That removes the epoch reconstruction, the hand-rolled counter read and the 2^31 cap on the tickless idle. Ticks now stay on a grid (grtc_next_tick) instead of being re-armed one period after the ISR ran. A backlog beyond 4 s is no longer dropped by re-anchoring the tick base: it is caught up 4 s per interrupt and counted in grtc_tick_backlogs. The tickless idle wakes on the grid and never steps past the unblock tick; ticks beyond it are caught up by the tick interrupt. Write the sleep configuration the way nrfx_grtc_sleep_configure() does, with the SYSCOUNTER stopped, using NRFX_GRTC_SLEEP_DEFAULT_CONFIG instead of literal 5/4. The nrfx GRTC driver itself is not enabled in this core. * Mask SoftwareSerial's RX interrupt on the GPIOTE that serves its pin attachInterrupt() returns a channel mask of GPIOTE30 for P0 pins and of GPIOTE20 for P1 pins, but SoftwareSerial applied it to NRF_GPIOTE, which nrf54l_compat.h aliases to GPIOTE20. With the RX pin on P0, write() masked GPIOTE20 channel N instead of the RX channel: the RX edge interrupt kept firing during TX, and whatever P1 pin owned that channel was masked during every byte. Add digitalPinToGpiote() to WInterrupts, using the same pin mapping as attachInterrupt(), and have SoftwareSerial keep the instance its RX pin uses. * Tighten the InternalFS flash retry policy and bound a failing page Write only the words of a chunk that differ from the flash, first to last. A chunk ending in words that already matched could otherwise read back equal while the SoftDevice still had those words to write and was still reading the buffer, so a late completion could end the wait before it was done and the cache could reload the buffer under it. A range already in place is now not written at all, which also covers erasing flash that is already 0xFF. Give each chunk a total budget of 2 s, every attempt and wait included, instead of 2 s per wait: with retries one failing chunk could hold the filesystem lock for about 10 s. Settling an operation left outstanding by a timeout may use up to 0.5 s of it. A failure reported by the SoftDevice (NRF_EVT_FLASH_OPERATION_ERROR, which it raises when it cannot fit the write around radio activity) is now retried like BUSY, up to MAX_RETRY times with a pause, instead of three times back to back, and it counts in flash_nrf5x_stats.errors. A page whose flush keeps failing is dropped after three failures in a row. Kept for ever, it refused every write to any other page, including the erases of InternalFS.format(), and its data does not survive a reboot anyway. Drop the NOR erase-then-program flush: the only flash_cache_t writes in place. --------- Co-authored-by: Thomas Göttgens <tgoettgens@gmail.com>
The GRTC tick grid advanced by portNRF_GRTC_TICKS_PER_SYSTICK, 10^6 / 1024 truncated to 976 us, so 1024 ticks took 999424 us. The tick count, and millis() derived from it, ran 576 ppm fast, about 50 s a day. Count grid ticks from the base set at scheduler start and place tick n at base + ceil(n * 10^6 / configTICK_RATE_HZ) us. Rounding up makes a compare armed at tick n see exactly n ticks due when it fires.
…meshtastic#4) stopListening() detached the RX interrupt but kept _gpiote and _intMask, so write() on an instance that no longer listens still disabled and re-enabled that channel around every byte. Once another pin is attached to the channel, its interrupt was masked during each transmitted byte. Clear _gpiote, which every mask and unmask is guarded on.
…eshtastic#5) A cached page whose flush fails three times in a row is dropped, and with it writes LittleFS was already told had succeeded. Count each drop in flash_nrf5x_stats.pages_dropped, so the loss shows in the flash flight recorder.
Brings ashinn/nrf54lm20a (6c99190: MDK 8.75.1, s145 10.0.1 for every board, LM20A startup/linker, SPI/UARTE pin routing, SAADC per chip) on top of master 79a7ad6 (meshtastic#1-meshtastic#5). Conflicts resolved in favour of master, whose tick and RNG seeding have the long soak on the nRF54L15-DK behind them: - port_cmsis_systick.c, portmacro_cmsis.h, FreeRTOSConfig.h: master's GRTC domain-2 tick (TIMEOUT/WAKETIME 5/4, 1/1024 s grid). The LM20A MDK has the same GRTC_IRQ_GROUP (2) and GRTC_2_IRQn (228), so the domain #error still holds there. Kept 650f534's compile-time check that the tick's CC channel and IRQ group stay clear of the SoftDevice's (SD_GRTC_CC_CHANNELS_USED / SD_GRTC_IRQn_USED). - bluefruit.cpp: master's deferred CRACEN seeding (meshtastic#2); the branch's only change there was its own inline seeding. Also route P3 pin interrupts to GPIOTE20: the LM20 adds P3, which shares GPIOTE20 with P1, and instanceForPin() returned NULL for it.
Vendored as released (src, library.properties, LICENSE, README; no examples). It bundles TinyUSB with the DWC2 port for the nRF54LM20 USBHS; the nRF54 port and the fixes it needs follow in separate commits.
The nRF54LM20's USBHS block is a Synopsys DWC2 core with a high-speed PHY, which TinyUSB drives through its dwc2 port (OPT_MCU_NRF54). - ports/nrf54: tusb_config (device-only CDC at high speed, 512-byte bulk endpoints) and the Arduino port. The USB task waits for VBUS (VREGUSB; bit 2 at +0x400 for the state at start-up, as in Zephyr's quirk for the nRF54L, VBUSDETECTED for a cable plugged in later) and starts PCLK24M (CLOCK XO24MSTART) before the stack, as the DWC2 registers are not accessible earlier. tinyusb_nrf54_stage and two timestamps record the bring-up for reading over SWD. - tusb_config.h / Adafruit_TinyUSB_API.h: select the port for ARDUINO_ARCH_NRF54. - dwc2_nrf.h: also match NRF54LM20A_XXAA (upstream only checks the engineering-sample NRF54LM20A_ENGA_XXAA, so the PHY bring-up compiled to nothing), and take nrfx_coredep.h from either soc/ or lib/ (nrfx 4). PCLK24M keeps the HFXO running, and the SoftDevice, which starts the HFXO itself (sd_softdevice_enable() included) and waits for XOTUNED, never gets one from a crystal that is already running and tuned: it spun in its clock code and the watchdog reset the chip. So the SoftDevice has to hold the HFXO before PCLK24M starts: - Bluefruit: the LF clock configuration and sd_softdevice_enable() move into bluefruit_softdevice_enable(), which only acts the first time; begin() calls it. The TinyUSB port calls it from TinyUSB_Port_InitDevice(), before setup(), when Bluefruit is linked (weak reference), and requests the HFXO through the SoftDevice (sd_clock_hfclk_request) before starting PCLK24M. USB then attaches once and never has to leave the bus to hand the crystal over. - Weak usb_softdevice_pre_enable() / usb_softdevice_post_enable() hooks around sd_softdevice_enable() cover a SoftDevice enabled after USB started: the port detaches, releases PCLK24M across the enable and attaches again. SD_EVT_IRQn is still only enabled by begin(); until then the flash driver drains its own completions (meshtastic meshtastic#1), so this relies on that. Tested on the nRF54LM20-DK with Meshtastic: the SoftDevice up at 426 ms and PCLK24M 1 ms later, the CDC port at 0.7 s with no drop and the whole boot log; 3 boots to 60 s with no watchdog reset; 18 boot-time flash writes through the SoftDevice, 0 timeouts; the Meshtastic CLI and web client over USB, BLE alongside.
- startup_nrf54lm20a.S: give IRQ 90 (USBHS) a weak USBHS_IRQHandler; it sat in the Default_Handler block, so the port's handler was never reached (and was dropped by the linker). - main.cpp: TinyUSB_Device_Init(0) before setup(), as on nRF52. - Arduino.h / Uart.h: with USE_TINYUSB, Serial is the TinyUSB CDC port instead of an alias of the console UART.
nRF54_Bootloader's USB DFU (nRF54LM20-DK) does not fit in 32 KB, so that bootloader takes 64 KB (linker/nrf54lm20a_bl64k.ld) and starts the application at 0x10000. This script is nrf54lm20a_s145_v10.ld with FLASH moved to match; InternalFS, the settings page and the SoftDevice stay where they were. A board selects it with build.arduino.ldscript.
wiring.h declared it but nothing defined it. 0x57 is DFU_MAGIC_UF2_RESET, as in Adafruit's nRF52 bootloader: nRF54_Bootloader with USB (nRF54LM20) then shows its UF2 drive; a bootloader without USB does serial DFU over its UART.
This file contains hidden or 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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Native USB on the nRF54LM20: TinyUSB on the USBHS block, with
Serialas a USB CDC port whenUSE_TINYUSBis set, and the pieces a USB DFU bootloader needs.Commits
ports/nrf54,dwc2_nrf.h, Bluefruit hooks). The USBHS block is a Synopsys DWC2 with a high-speed PHY, driven by TinyUSB's dwc2 port (OPT_MCU_NRF54). The USB task waits for VBUS (VREGUSB) and starts PCLK24M before the stack, as the DWC2 registers are not accessible earlier.dwc2_nrf.hgains the productionNRF54LM20A_XXAAdefine (upstream only checksNRF54LM20A_ENGA_XXAA, so the PHY bring-up compiled to nothing).sd_softdevice_enable()too) restarts the HFXO and waits for XOTUNED, which a running, tuned crystal never raises: the CPU spun in the SoftDevice's clock code until the watchdog reset it. So Bluefruit's SoftDevice enable moves intobluefruit_softdevice_enable()(acts once,begin()calls it), and the port calls it beforesetup()and requests the HFXO through the SoftDevice before starting PCLK24M. USB attaches once and never has to leave the bus. Weakusb_softdevice_pre_enable()/usb_softdevice_post_enable()hooks cover a SoftDevice enabled after USB started.USBHS_IRQHandlerfor IRQ 90 instartup_nrf54lm20a.S(it sat in theDefault_Handlerblock, so the port's handler was never reached),TinyUSB_Device_Init(0)beforesetup()as on nRF52, and withUSE_TINYUSBSerialis the CDC port instead of an alias of the console UART.nrf54lm20a_s145_v10_bl64k.ld: the same layout with the application at0x10000, for boards whose bootloader carries the USB stack and takes 64 KB (see Add the nRF54LM20-DK with DFU over USB (CDC and UF2) nRF54_Bootloader#2). Selected per board withbuild.arduino.ldscript.enterUf2Dfu()was declared inwiring.hbut never defined; it now resets withGPREGRET0x57, which that bootloader answers with its UF2 drive.Base
The branch starts from a merge of meshtastic/nRF54_Arduino
masterintoashinn/nrf54lm20a(the 5 commits #1–meshtastic#5, already merged there). The early SoftDevice enable relies on #1: untilbegin()enablesSD_EVT_IRQn, the flash driver drains its own completions, and Meshtastic writes to flash before it starts BLE. Without #1 this is untested.Tested
On an nRF54LM20-DK (nRF54LM20B) with Meshtastic and andyshinn/nRF54_Bootloader#2:
enterUf2Dfu()into its UF2 drive.Not tested: hot-plugging the cable while the application runs, other boards with USBHS.