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

Where the next copy of the output binding goes in the IOmcu's flash. More...

#include <stdbool.h>
#include <stdint.h>

Go to the source code of this file.

Data Structures

struct  out_store_rec_t
 One record slot, as the caller found it in flash. More...
struct  out_store_write_t
 Where the next save goes. More...

Functions

int out_store_map_newest (const out_store_rec_t *recs, uint8_t sectors, uint8_t per_sector)
 The live record: the valid one with the highest sequence number.
bool out_store_map_write (const out_store_rec_t *recs, uint8_t sectors, uint8_t per_sector, out_store_write_t *out)
 Plan the next save.
bool out_store_map_write_keep (const out_store_rec_t *recs, uint8_t sectors, uint8_t per_sector, int keep, out_store_write_t *out)
 The same, with sector keep never erased while no record is valid: it holds the live record of the layout before this one (a one-page slot of version 7 or older, out_store_rec.h), which the store reads until its first record of the new layout is written.
int out_store_map_stale (const out_store_rec_t *recs, uint8_t sectors, uint8_t per_sector)
 A sector worth erasing now, ahead of the save that would have to wait for it, or -1 when there is none.
int out_store_map_stale_keep (const out_store_rec_t *recs, uint8_t sectors, uint8_t per_sector, int keep)
 The same, never offering sector keep while no record is valid (see out_store_map_write_keep()).
bool out_store_intact (uint32_t zeros, uint32_t zeros_inv, uint32_t counted)
 Whether a record was written whole and has stayed as it was written.

Detailed Description

Where the next copy of the output binding goes in the IOmcu's flash.

The store is a run of record slots laid across two or more flash sectors. A save programs one slot. A sector is erased only when nothing in it is still wanted, and the erase can be taken before the save that needs it. This file is that arithmetic – which record is the live one, which slot the next save takes, which sector can be erased now – with no flash API, no addresses and no clock in it, so the host suite holds it.

Why not one record rewritten in place. Erasing a NOR (not-or) flash sector is one command to the die, and the die cannot be read while it runs; the IOmcu executes from that die, so an erase per save is an erase window per save with the core answering nothing. On the bring-up module a save that erased and programmed inside one window measured 19,178us, against a CAN (Controller Area Network) frame time of about 130us and two receive buffers in the controller. Neither half of that window was timed on its own: a page program is one page rather than one sector, and out_store_last_program_us() and out_store_last_erase_us() are what will separate them.

What a power cut leaves behind:

During a program. That slot ends up neither erased nor valid, and the record before it is still the newest valid one, so the load returns the binding from before the save. A store of one record rewritten in place has nothing to return there.

During an erase. The sector being erased never holds the newest record: out_store_map_stale() offers a sector only while the live record is in another one, and out_store_map_write() erases only a sector it is leaving. With one sector configured there is nowhere else for the record to be and that guarantee is gone; the IOmcu configures two.

Both guarantees need a partly written record to be rejected, and a checksum is not enough for either. An erase lifts bits towards 0xFF and a program clears them towards their value, so a record caught in one is a record with bits that are not what was written; for it to be rejected it has to fail its check, which a 16-bit checksum does with probability 65535/65536 per candidate rather than always. The number of candidates is enormous, so that is a likelihood and not a guarantee.

out_store_intact() is what makes it one. The caller writes the number of 0 bits the record holds, beside that number's own complement, and counts them back on the way in. Every partial write leaves fewer 0 bits than the count says – an erase because it set bits, a program because it has not finished clearing them – and the count itself cannot be faked, because keeping a complement pair across a partial write would need a bit cleared to pay for one that was set, or set to pay for one that was cleared.

SPDX-License-Identifier: MIT

Function Documentation

◆ out_store_intact()

bool out_store_intact ( uint32_t zeros,
uint32_t zeros_inv,
uint32_t counted )

Whether a record was written whole and has stayed as it was written.

zeros is the number of 0 bits the record held when it was written, zeros_inv is ~zeros, and counted is the number of 0 bits it holds now. The pair says the count itself is untouched; the count says the rest of the record is.

A flash erase only sets bits and a program only clears them, so either one interrupted leaves a record with fewer 0 bits than it was written with. Neither can fake the pair: a bit set in zeros would have to be paid for by a bit cleared in zeros_inv, and a bit cleared by one set, and no single flash operation does both.

This is a stronger check than the record's checksum, and it answers a different question. The checksum says the bytes are the ones that were meant; this says the write that put them there finished.

Parameters
zerosNumber of 0 bits when the record was written.
zeros_invComplement of zeros, as written beside it.
countedNumber of 0 bits the record holds now.
Returns
true when zeros_inv is the complement of zeros and counted equals zeros, false otherwise.

◆ out_store_map_newest()

int out_store_map_newest ( const out_store_rec_t * recs,
uint8_t sectors,
uint8_t per_sector )

The live record: the valid one with the highest sequence number.

-1 when none of them is valid, which is a store that has never been written, one written by a build with a different record in it, and one whose only records are torn. The caller keeps its defaults for all three.

The comparison is a plain unsigned one rather than a wrap-safe difference. The sequence starts at 1 and counts saves; a sector pair of 32 slots at 100,000 erase cycles a sector accepts about 3.2 million saves before the part wears out, and a uint32_t counts to 4,294,967,295.

Parameters
recsThe record slots, sectors * per_sector of them.
sectorsNumber of sectors.
per_sectorRecord slots per sector.
Returns
Index of the live slot, 0 to sectors * per_sector - 1. -1 when no slot is valid, or when the geometry is not usable: recs is NULL, a count is 0, or the total is above 255.

◆ out_store_map_stale()

int out_store_map_stale ( const out_store_rec_t * recs,
uint8_t sectors,
uint8_t per_sector )

A sector worth erasing now, ahead of the save that would have to wait for it, or -1 when there is none.

A sector qualifies when it does not hold the live record and is not already erased. Erasing it costs the same window as erasing it later; the point of taking it early is that the caller chooses the moment, and that the save which finds the sector ready costs a page program alone.

Parameters
recsThe record slots, sectors * per_sector of them.
sectorsNumber of sectors.
per_sectorRecord slots per sector.
Returns
Index of the sector to erase, 0 to sectors - 1. -1 when no sector qualifies, or when the geometry is not usable (see out_store_map_newest()).

◆ out_store_map_stale_keep()

int out_store_map_stale_keep ( const out_store_rec_t * recs,
uint8_t sectors,
uint8_t per_sector,
int keep )

The same, never offering sector keep while no record is valid (see out_store_map_write_keep()).

Parameters
recsThe record slots, sectors * per_sector of them.
sectorsNumber of sectors.
per_sectorRecord slots per sector.
keepSector never offered while no record is valid, or -1 for none.
Returns
Index of the sector to erase, or -1 as out_store_map_stale() gives.

◆ out_store_map_write()

bool out_store_map_write ( const out_store_rec_t * recs,
uint8_t sectors,
uint8_t per_sector,
out_store_write_t * out )

Plan the next save.

The slot after the live record, while its sector has an erased slot left; then the first sector that is already erased; then the next sector, which has to be erased first. A slot that is neither erased nor valid is a torn write and is skipped rather than programmed over: bits only clear, so a second record written into a used slot is the two of them anded together.

False when out is NULL or the geometry is empty; the store is otherwise always placeable, because a full store erases a sector to make room.

Parameters
recsThe record slots, sectors * per_sector of them.
sectorsNumber of sectors.
per_sectorRecord slots per sector.
outReceives the plan: the slot to program, its sequence number and the sector to erase first, if any. Not changed when false is returned.
Returns
true when the plan is in out. false when out is NULL or the geometry is not usable (recs NULL, a count of 0, or a total above 255).

◆ out_store_map_write_keep()

bool out_store_map_write_keep ( const out_store_rec_t * recs,
uint8_t sectors,
uint8_t per_sector,
int keep,
out_store_write_t * out )

The same, with sector keep never erased while no record is valid: it holds the live record of the layout before this one (a one-page slot of version 7 or older, out_store_rec.h), which the store reads until its first record of the new layout is written.

The first other sector is erased instead. -1 keeps none, as out_store_map_write().

Parameters
recsThe record slots, sectors * per_sector of them.
sectorsNumber of sectors.
per_sectorRecord slots per sector.
keepSector to keep while no record is valid, or -1 to keep none. With no valid record and no erased slot, the erase goes to sector 1 when keep is 0 and sectors is above 1, and to sector 0 otherwise.
outReceives the plan. Not changed when the call returns false.
Returns
true when the plan is in out, false when out is NULL or the geometry is not usable (see out_store_map_write()).