|
rcbench
Servo and ESC test bench: panel, IOmcu and shared C
|
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. | |
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
| 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.
| zeros | Number of 0 bits when the record was written. |
| zeros_inv | Complement of zeros, as written beside it. |
| counted | Number of 0 bits the record holds now. |
zeros_inv is the complement of zeros and counted equals zeros, false otherwise. | 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.
| recs | The record slots, sectors * per_sector of them. |
| sectors | Number of sectors. |
| per_sector | Record slots per sector. |
| 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.
| recs | The record slots, sectors * per_sector of them. |
| sectors | Number of sectors. |
| per_sector | Record slots per sector. |
| 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()).
| recs | The record slots, sectors * per_sector of them. |
| sectors | Number of sectors. |
| per_sector | Record slots per sector. |
| keep | Sector never offered while no record is valid, or -1 for none. |
| 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.
| recs | The record slots, sectors * per_sector of them. |
| sectors | Number of sectors. |
| per_sector | Record slots per sector. |
| out | Receives the plan: the slot to program, its sequence number and the sector to erase first, if any. Not changed when false is returned. |
out. false when out is NULL or the geometry is not usable (recs NULL, a count of 0, or a total above 255). | 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().
| recs | The record slots, sectors * per_sector of them. |
| sectors | Number of sectors. |
| per_sector | Record slots per sector. |
| keep | Sector 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. |
| out | Receives the plan. Not changed when the call returns false. |
out, false when out is NULL or the geometry is not usable (see out_store_map_write()).