|
rcbench
Servo and ESC test bench: panel, IOmcu and shared C
|
The output configuration in flash. More...
#include "out_store.h"#include <stddef.h>#include <string.h>#include "hardware/flash.h"#include "hardware/sync.h"#include "pico/flash.h"#include "pico/stdlib.h"#include "out_store_map.h"Data Structures | |
| struct | flash_op_t |
| One erase or one program, run inside a flash window. More... | |
| union | [union].s_page |
| The staged page, as a union rather than a byte array with a cast. More... | |
Macros | |
| #define | STORE_FLASH_BYTES (4u * 1024u * 1024u) |
| Where it goes: the data partition "store" of partitions.json, two sectors at 4MiB - 16KiB (0x3FC000), below the last 8KiB the layout leaves unused. | |
| #define | STORE_SECTORS OUT_STORE_SECTORS |
| #define | STORE_SLOT_BYTES OUT_STORE_SLOT_BYTES |
| #define | STORE_RECORDS (FLASH_SECTOR_SIZE / STORE_SLOT_BYTES) |
| #define | STORE_SLOTS (STORE_SECTORS * STORE_RECORDS) |
| #define | STORE_V7_BYTES OUT_STORE_SLOT_BYTES_V7 |
| The one-page slots of version 7 and older, read until the first version 8 record is written (out_store_rec.h). | |
| #define | STORE_V7_RECORDS (FLASH_SECTOR_SIZE / STORE_V7_BYTES) |
| #define | STORE_V7_SLOTS (STORE_SECTORS * STORE_V7_RECORDS) |
| #define | STORE_OFFSET IOMCU_STORE_OFFSET |
| #define | STORE_OLD_OFFSET (STORE_FLASH_BYTES - STORE_SECTORS * FLASH_SECTOR_SIZE) |
| Where a build without a partition table kept the store: the last two sectors of the 4MiB. Read once, by out_store_load(), when the store holds no record yet, so the bindings survive the USB load that installs the partition table. Never written. | |
| #define | STORE_READ_BASE XIP_NOCACHE_NOALLOC_NOTRANSLATE_BASE |
Enumerations | |
| enum | store_mode_t { STORE_ALONE = 0 , STORE_PARKABLE , STORE_OFF } |
| How a window opens, by where core 1 stands (out_store.h): More... | |
Functions | |
| static const uint8_t * | slot_at (uint8_t slot, uint32_t bytes) |
| Returns the address of a record slot in the store, read through the uncached window. | |
| static void | survey (out_store_rec_t *recs, uint8_t n, uint32_t bytes, uint16_t min_version, uint16_t max_version) |
What is in the store, read out of flash rather than remembered: n slots of bytes, a record of min_version to max_version taken. | |
| static void | flash_op_run (void *arg) |
| Runs the operation and records how long the window held interrupts off. | |
| void | out_store_core1_parkable (void) |
| Core 1 registered for the flash lock-out: every window from now on through flash_safe_execute(), which parks it in RAM. | |
| void | out_store_off (void) |
| Switch the store's writing off for this boot: core 1 was launched and did not register for the flash lock-out. | |
| bool | out_store_is_off (void) |
| Whether out_store_off() has been called. | |
| static uint32_t | clock_ms (void) |
| Reads the clock in milliseconds since boot. | |
| static bool | refusing (void) |
| Tells whether windows are refused because core 1 did not stop. | |
| static bool | flash_window (flash_op_t *op) |
| True when the operation ran. | |
| static bool | erase_sector (uint8_t sector, uint32_t *window_us) |
| Erases one sector of the store inside a flash window. | |
| static bool | program_record (uint8_t slot, uint32_t *window_us) |
| The staged slot, both of its pages, in one window. | |
| static int | load_from (out_store_rec_t *recs, uint8_t n, uint8_t per_sector, uint32_t bytes, uint16_t min_version, uint16_t max_version, out_store_t *out) |
The newest record of n slots of bytes, min_version to max_version, into out; its slot, or -1 when none is valid. | |
| bool | out_store_load (out_store_t *out) |
| Read what was saved. | |
| void | out_store_save (const out_store_t *cfg, uint32_t now_ms) |
Ask for cfg to be written. | |
| bool | out_store_pending (void) |
| Whether a save has been asked for and not yet reached flash. | |
| uint32_t | out_store_last_erase_us (void) |
| How long the last sector erase held interrupts off, in microseconds. | |
| uint32_t | out_store_last_program_us (void) |
| How long the last page program held interrupts off, in microseconds. | |
| uint8_t | out_store_last_record (void) |
| Which of the 16 record slots the store last used (of the 32 one-page slots before version 8 while the boot read a record of that layout). | |
| out_store_step_t | out_store_tick (bool driving, uint32_t quiet_ms, uint32_t now_ms) |
| Take a deferred save if it is safe to. | |
| bool | out_store_reclaim (bool driving, uint32_t quiet_ms) |
| Erase a sector that holds nothing still wanted. | |
| bool | out_store_window_open (void) |
| Whether a window may open now: the store is not off, no save is waiting, and core 1 did not refuse the last one. | |
| bool | out_store_flash_erase (uint32_t off) |
Erase the 4KiB sector at flash offset off, below the store. | |
| bool | out_store_flash_program (uint32_t off, const uint8_t *data, uint32_t len) |
Program len bytes, whole 256-byte pages up to one sector, at flash offset off, below the store. | |
| bool | out_store_flash_call (void(*fn)(void *), void *arg) |
Run fn inside a window: the boot ROM's BUY, which rewrites flash. | |
Variables | |
| static bool | s_pending |
| Two flash pages hold a version 8 record, and a save programs both in one window. | |
| static uint32_t | s_asked_ms |
| static out_store_t | s_want |
| static bool | s_have_saved |
| static out_store_t | s_saved |
| static uint8_t | s_last_record |
| static uint32_t | s_last_erase_us |
| How long the last erase and the last program each took, with interrupts off. | |
| static uint32_t | s_last_program_us |
| static bool | s_reclaim_wanted = true |
| Whether the store is worth looking at for a sector to erase. | |
| static union | s_page |
| static int | s_keep_sector = -1 |
| The live record of the layout before version 8, by sector, while the store holds no version 8 record: never erased until the first one is written. | |
| static uint32_t | s_read_offset = STORE_OFFSET |
| The store read is at STORE_OFFSET, or at STORE_OLD_OFFSET while out_store_load() looks there. | |
| static bool | s_refused |
| When core 1 last failed to stop, and whether that is recent. | |
| static uint32_t | s_refused_ms |
| static store_mode_t | s_mode = STORE_ALONE |
| #define STORE_FLASH_BYTES (4u * 1024u * 1024u) |
Where it goes: the data partition "store" of partitions.json, two sectors at 4MiB - 16KiB (0x3FC000), below the last 8KiB the layout leaves unused.
The build reads the offset out of that file (IOMCU_STORE_OFFSET).
Read by its storage address, through the XIP (execute in place) window that neither translates nor caches. The image runs from slot A or B and the boot ROM maps that slot to XIP_BASE, so XIP_BASE + offset is an address inside the running slot, not the store. Uncached, because an erase or a program does not pass through the cache.
Two sectors rather than one. A record is only ever written into an erased slot and a sector is only ever erased while the live record is in the other one, so a power cut during either leaves the binding from before the save readable. See out_store_map.h.
Not PICO_FLASH_SIZE_BYTES. The build's default board file is a pimoroni_pico_plus2_rp2350 and claims sixteen megabytes, while the module has four; the partition table is written for the four.
| enum store_mode_t |
How a window opens, by where core 1 stands (out_store.h):
ALONE core 1 has not been launched. Interrupts off on this core and the operation run directly: nothing else executes from flash. flash_safe_execute() is not used here, because whether it lets this through depends on a pico-sdk option. In 2.3.0, multicore_lockout_ready() answers true for a core 1 that never ran only while PICO_MULTICORE_LOCKOUT_BEFORE_CORE1_STARTED is set (multicore.c:374-381; it defaults to 1). Without it, default_enter_safe_zone_timeout_ms() reaches the assert(false) at flash.c:189-190 under PICO_FLASH_ASSERT_ON_UNSAFE, or refuses the window. PARKABLE core 1 registered for the lock-out (out_store_core1_parkable()): every window through flash_safe_execute(), which parks it. OFF core 1 was launched and did not register (out_store_off()): no window opens at all, since the direct path would fault a core 1 executing from flash and flash_safe_execute() would take that same assert, or refuse.
|
static |
Reads the clock in milliseconds since boot.
|
static |
Erases one sector of the store inside a flash window.
| sector | Sector index within the store. |
| window_us | Receives how long the window held interrupts off, in microseconds. Written only when the erase ran. |
|
static |
Runs the operation and records how long the window held interrupts off.
| arg | The flash_op_t to run. |
|
static |
True when the operation ran.
flash_safe_execute() answers a timeout on the way in (nothing ran) and on the way out (it ran) with the same code, so the operation says for itself.
| op | The operation. Its done field is set. |
|
static |
The newest record of n slots of bytes, min_version to max_version, into out; its slot, or -1 when none is valid.
| recs | Receives the survey of the slots. |
| n | Number of slots. |
| per_sector | Slots per sector. |
| bytes | Bytes per slot. |
| min_version | Lowest record version taken. |
| max_version | Highest record version taken. |
| out | Receives the configuration. |
| bool out_store_flash_call | ( | void(* | fn )(void *), |
| void * | arg ) |
Run fn inside a window: the boot ROM's BUY, which rewrites flash.
True when it ran.
| fn | The function to run inside the window. NULL is refused. |
| arg | Passed to fn. |
fn ran. False when no window may open. | bool out_store_flash_erase | ( | uint32_t | off | ) |
Erase the 4KiB sector at flash offset off, below the store.
True when the erase ran.
| off | Flash offset of the sector, in bytes. An offset that is not a sector boundary, or that is at or past the store, is refused. |
| bool out_store_flash_program | ( | uint32_t | off, |
| const uint8_t * | data, | ||
| uint32_t | len ) |
Program len bytes, whole 256-byte pages up to one sector, at flash offset off, below the store.
True when the program ran.
| off | Flash offset, in bytes. A null data, a len that is not a whole number of pages, or a range past the store is refused. |
| data | The bytes to program. |
| len | Number of bytes, at most one sector. |
| bool out_store_is_off | ( | void | ) |
Whether out_store_off() has been called.
| uint32_t out_store_last_erase_us | ( | void | ) |
How long the last sector erase held interrupts off, in microseconds.
Zero until one has run. The erase on its own is not measured: what the bring-up module printed was 19,174 to 19,186us over eight saves, and each of those windows held an erase and a page program together. This is what separates the two.
| uint32_t out_store_last_program_us | ( | void | ) |
How long the last page program held interrupts off, in microseconds.
Zero until one has run. It is not measured. It is the window every save pays, so it is the number that says whether a save can still cost a frame.
| uint8_t out_store_last_record | ( | void | ) |
Which of the 16 record slots the store last used (of the 32 one-page slots before version 8 while the boot read a record of that layout).
The slot the last save was programmed into. out_store_load() sets it as well, so before the first save of a run it is the slot the boot read the binding from, and it is zero when neither has happened. The console line prints it: it is what shows the store advancing towards its next erase.
| bool out_store_load | ( | out_store_t * | out | ) |
Read what was saved.
Takes the valid record with the highest sequence number across both sectors. A record of an earlier version (out_store_rec.h) is read too, with what it does not carry – the supply's wiring before version 4, the current monitors' set-up before version 5 – disabled. Returns false when no slot holds one – a store never written, one written by a build with a record this one does not read, or one whose records are all torn – and then out is untouched, so the caller keeps the defaults it already had.
| out | Receives the configuration. Untouched when the function returns false. |
| void out_store_off | ( | void | ) |
Switch the store's writing off for this boot: core 1 was launched and did not register for the flash lock-out.
A direct window would fault core 1 executing from flash, and flash_safe_execute() would refuse, or in a build with PICO_FLASH_ASSERT_ON_UNSAFE and asserts on, stop the core. Afterwards no erase or program runs and flash_safe_execute() is never called: out_store_save() takes nothing, so nothing waits for a save that cannot come, out_store_tick() is idle and out_store_reclaim() false. What was loaded stays readable. Nothing switches it on again.
| bool out_store_pending | ( | void | ) |
Whether a save has been asked for and not yet reached flash.
| bool out_store_reclaim | ( | bool | driving, |
| uint32_t | quiet_ms ) |
Erase a sector that holds nothing still wanted.
The window is the same length wherever it is taken; the point is that the caller picks the moment, and that the save which later finds the sector ready costs a page program alone. Returns true on the pass that erased.
A window core 1 did not stop for erases nothing and returns false.
One call erases at most one sector, and the call after it looks again. Nothing to do is the ordinary answer: one save in eight leaves a sector behind. A store whose sectors all hold records this build cannot read – an earlier record version – has one to reclaim per sector, so a caller that wants them all takes them in as many calls.
| driving | True while the bank is driving. |
| quiet_ms | Milliseconds since the last frame on the bus. |
| void out_store_save | ( | const out_store_t * | cfg, |
| uint32_t | now_ms ) |
Ask for cfg to be written.
Returns immediately. Nothing reaches flash until out_store_tick() finds the bank idle, the request settled and the bus quiet, and a request that matches what is already saved is dropped: a record written with the same content would spend a slot to change nothing.
now_ms restarts the settle window. The two pages that describe the outputs are written in two transactions – CHAN_CFG, then OUTPUTS – and saving between them would record a new channel configuration against the slots from before it, which is what would come back at the next boot. Waiting for the writes to stop is what keeps the pair together.
| cfg | The configuration to save. |
| now_ms | The pass's clock in milliseconds. It restarts the settle window. |
| out_store_step_t out_store_tick | ( | bool | driving, |
| uint32_t | quiet_ms, | ||
| uint32_t | now_ms ) |
Take a deferred save if it is safe to.
Call every pass with whether the bank is driving, how long the bus has been quiet, and the clock of the pass. A save that needs an erase first erases on one pass and writes on a later one, so the caller services the CAN controller between the two windows rather than holding both off at once.
| driving | True while the bank is driving. |
| quiet_ms | Milliseconds since the last frame on the bus. |
| now_ms | The pass's clock in milliseconds. |
| bool out_store_window_open | ( | void | ) |
Whether a window may open now: the store is not off, no save is waiting, and core 1 did not refuse the last one.
|
static |
The staged slot, both of its pages, in one window.
| slot | Slot index within the store. |
| window_us | Receives how long the window held interrupts off, in microseconds. Written only when the program ran. |
|
static |
Tells whether windows are refused because core 1 did not stop.
|
static |
Returns the address of a record slot in the store, read through the uncached window.
| slot | Slot index. |
| bytes | Bytes per slot. |
|
static |
What is in the store, read out of flash rather than remembered: n slots of bytes, a record of min_version to max_version taken.
Eight kilobytes of uncached reads per call, and a call happens only when something is about to be written. Holding the answer in RAM instead would be a second copy of the truth that a torn write could disagree with.
| recs | Receives one entry per slot. |
| n | Number of slots. |
| bytes | Bytes per slot. |
| min_version | Lowest record version taken. |
| max_version | Highest record version taken. |
|
static |
The live record of the layout before version 8, by sector, while the store holds no version 8 record: never erased until the first one is written.
-1 for none.
|
static |
How long the last erase and the last program each took, with interrupts off.
Two numbers rather than one, because they answer different questions. The program is what every save costs; the erase is what one save in sixteen costs, or none of them when out_store_reclaim() has been given a quiet moment first. Measured rather than compensated for: what a window does to the link is decided by its length, and the length belongs to the flash part.
|
static |
Two flash pages hold a version 8 record, and a save programs both in one window.
The record's layout, its checksum and its zero count are out_store_rec.c's (host-tested); the checksum is the link's, because a half-written record and a corrupt frame are the same problem and there is no reason for two answers to it.
The checksum is not what says the record is whole, though, and a power cut can leave one that is not. Two ways, and they are opposites:
During an erase. Bits lift towards 0xFF, so a superseded record's sequence number can grow past the live one's and outrank it. During a program. Bits fall towards their value, so a record whose first page has landed and whose second has not ranks above the record before it and carries a configuration that is part 0xFF.
A 16-bit checksum makes each unlikely rather than impossible. The zero count is what makes it impossible: zeros is how many 0 bits the record holds from crc to its end, across both pages, written beside its own complement. An erase sets bits and a program clears them, so either one cut off leaves fewer 0 bits than zeros says, and neither can keep the pair: an erase would have to clear a bit to pay for one it set, a program set one to pay for one it cleared (out_store_map.h).
|
static |
Whether the store is worth looking at for a sector to erase.
True at boot and after every record written, false once a look has found nothing. Without it out_store_reclaim() would read eight kilobytes of flash on every pass of a loop that turns over in microseconds, to answer a question that changes once per sixteen saves.