rcbench
Servo and ESC test bench: panel, IOmcu and shared C
Loading...
Searching...
No Matches
out_store.c File Reference

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

Detailed Description

The output configuration in flash.

See out_store.h.

SPDX-License-Identifier: MIT

Macro Definition Documentation

◆ STORE_FLASH_BYTES

#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.

Enumeration Type Documentation

◆ 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.

Function Documentation

◆ clock_ms()

uint32_t clock_ms ( void )
static

Reads the clock in milliseconds since boot.

Returns
The time since boot, in milliseconds.

◆ erase_sector()

bool erase_sector ( uint8_t sector,
uint32_t * window_us )
static

Erases one sector of the store inside a flash window.

Parameters
sectorSector index within the store.
window_usReceives how long the window held interrupts off, in microseconds. Written only when the erase ran.
Returns
True when the erase ran.

◆ flash_op_run()

void flash_op_run ( void * arg)
static

Runs the operation and records how long the window held interrupts off.

Parameters
argThe flash_op_t to run.

◆ flash_window()

bool flash_window ( flash_op_t * op)
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.

Parameters
opThe operation. Its done field is set.
Returns
True when the operation ran.

◆ load_from()

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 )
static

The newest record of n slots of bytes, min_version to max_version, into out; its slot, or -1 when none is valid.

Parameters
recsReceives the survey of the slots.
nNumber of slots.
per_sectorSlots per sector.
bytesBytes per slot.
min_versionLowest record version taken.
max_versionHighest record version taken.
outReceives the configuration.
Returns
The slot the record was read from, or -1 when none is valid.

◆ out_store_flash_call()

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.

Parameters
fnThe function to run inside the window. NULL is refused.
argPassed to fn.
Returns
True when fn ran. False when no window may open.

◆ out_store_flash_erase()

bool out_store_flash_erase ( uint32_t off)

Erase the 4KiB sector at flash offset off, below the store.

True when the erase ran.

Parameters
offFlash offset of the sector, in bytes. An offset that is not a sector boundary, or that is at or past the store, is refused.
Returns
True when the erase ran.

◆ out_store_flash_program()

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.

Parameters
offFlash offset, in bytes. A null data, a len that is not a whole number of pages, or a range past the store is refused.
dataThe bytes to program.
lenNumber of bytes, at most one sector.
Returns
True when the program ran.

◆ out_store_is_off()

bool out_store_is_off ( void )

Whether out_store_off() has been called.

Returns
True when out_store_off() has been called.

◆ out_store_last_erase_us()

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.

Returns
The duration of the last erase window, in microseconds.

◆ out_store_last_program_us()

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.

Returns
The duration of the last program window, in microseconds.

◆ out_store_last_record()

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.

Returns
The record slot index.

◆ out_store_load()

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.

Parameters
outReceives the configuration. Untouched when the function returns false.
Returns
True when a record was read. False when no slot holds a valid one.

◆ out_store_off()

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.

◆ out_store_pending()

bool out_store_pending ( void )

Whether a save has been asked for and not yet reached flash.

Returns
True while a save is pending.

◆ out_store_reclaim()

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.

Parameters
drivingTrue while the bank is driving.
quiet_msMilliseconds since the last frame on the bus.
Returns
True on the pass that erased a sector.

◆ out_store_save()

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.

Parameters
cfgThe configuration to save.
now_msThe pass's clock in milliseconds. It restarts the settle window.

◆ out_store_tick()

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.

Parameters
drivingTrue while the bank is driving.
quiet_msMilliseconds since the last frame on the bus.
now_msThe pass's clock in milliseconds.
Returns
What the pass did.

◆ out_store_window_open()

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.

Returns
True when a window may open.

◆ program_record()

bool program_record ( uint8_t slot,
uint32_t * window_us )
static

The staged slot, both of its pages, in one window.

Parameters
slotSlot index within the store.
window_usReceives how long the window held interrupts off, in microseconds. Written only when the program ran.
Returns
True when the program ran.

◆ refusing()

bool refusing ( void )
static

Tells whether windows are refused because core 1 did not stop.

Returns
True while a window is refused. The refusal lapses after OUT_STORE_REFUSED_WAIT_MS.

◆ slot_at()

const uint8_t * slot_at ( uint8_t slot,
uint32_t bytes )
static

Returns the address of a record slot in the store, read through the uncached window.

Parameters
slotSlot index.
bytesBytes per slot.
Returns
The address of the slot.

◆ survey()

void survey ( out_store_rec_t * recs,
uint8_t n,
uint32_t bytes,
uint16_t min_version,
uint16_t max_version )
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.

Parameters
recsReceives one entry per slot.
nNumber of slots.
bytesBytes per slot.
min_versionLowest record version taken.
max_versionHighest record version taken.

Variable Documentation

◆ s_keep_sector

int s_keep_sector = -1
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.

◆ s_last_erase_us

uint32_t s_last_erase_us
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.

◆ s_pending

bool s_pending
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).

◆ s_reclaim_wanted

bool s_reclaim_wanted = true
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.