ra8-firmware 0.1.0
Bare-metal firmware for the Renesas RA8 family (RA8D2 / RA8P1)
Loading...
Searching...
No Matches
ra8_ftl.c
Go to the documentation of this file.
1
20
21#include "ra8_ftl.h"
22
23#include <stddef.h>
24#include <stdint.h>
25#include <string.h>
26
27#include "ra8_attributes.h"
28#include "ra8_check.h"
29#include "ra8_err.h"
30#include "ra8_io_blockdev.h"
32
34static const char* const s_tag = "ra8_ftl";
35
50
51/* =============================================================================
52 * Physical-block allocation (wear-levelling + reclamation)
53 * =============================================================================
54 */
55
84{
85 RA8_CHECK_NULL_PTR(ftl, s_tag, "ftl must not be nullptr");
86 RA8_CHECK_NULL_PTR(ftl->raw, s_tag, "raw must not be nullptr");
87 for (uint32_t p = 0; p < ftl->physical_blocks; ++p) {
88 if (ftl->pblocks[p].state != (uint8_t)k_ra8_ftl_pstate_stale) {
89 continue;
90 }
91 const ra8_err_t e = ra8_io_blockdev_erase(ftl->raw, p, (uint32_t)k_ra8_ftl_one_block);
92 if (e != k_ra8_ok) {
93 return e;
94 }
95 ftl->pblocks[p].erase_count += 1U;
96 ftl->pblocks[p].state = (uint8_t)k_ra8_ftl_pstate_free;
97 }
98 return k_ra8_ok;
99}
100
128static ra8_err_t internal_pick_free(const ra8_ftl_t* ftl, uint32_t* out)
129{
130 RA8_CHECK_NULL_PTR(ftl, s_tag, "ftl must not be nullptr");
131 RA8_CHECK_NULL_PTR(out, s_tag, "out must not be nullptr");
132 bool found = false;
133 uint32_t best = 0;
134 uint32_t bestc = 0;
135 for (uint32_t p = 0; p < ftl->physical_blocks; ++p) {
136 if (ftl->pblocks[p].state != (uint8_t)k_ra8_ftl_pstate_free) {
137 continue;
138 }
139 if (found) {
140 if (ftl->pblocks[p].erase_count >= bestc) {
141 continue;
142 }
143 }
144 found = true;
145 best = p;
146 bestc = ftl->pblocks[p].erase_count;
147 }
148 if (!found) {
149 return k_ra8_err_no_data;
150 }
151 *out = best;
152 return k_ra8_ok;
153}
154
184static ra8_err_t internal_alloc_blank(ra8_ftl_t* ftl, uint32_t* out)
185{
186 RA8_CHECK_NULL_PTR(ftl, s_tag, "ftl must not be nullptr");
187 RA8_CHECK_NULL_PTR(out, s_tag, "out must not be nullptr");
188 uint32_t phys = 0;
189 ra8_err_t pick = internal_pick_free(ftl, &phys);
190 if (pick == k_ra8_err_no_data) {
191 const ra8_err_t rec = internal_reclaim_stale(ftl);
192 if (rec != k_ra8_ok) {
193 return rec;
194 }
195 pick = internal_pick_free(ftl, &phys);
196 }
197 if (pick == k_ra8_err_no_data) {
198 return k_ra8_err_no_mem;
199 }
200 /* internal_pick_free with non-null args returns only k_ra8_ok or
201 * k_ra8_err_no_data. */
202 if (pick != k_ra8_ok) {
203 return pick; /* GCOVR_EXCL_LINE -- pick_free result set is ok or no-data only */
204 }
205 const ra8_err_t e = ra8_io_blockdev_erase(ftl->raw, phys, (uint32_t)k_ra8_ftl_one_block);
206 if (e != k_ra8_ok) {
207 return e;
208 }
209 ftl->pblocks[phys].erase_count += 1U;
210 *out = phys;
211 return k_ra8_ok;
212}
213
214/* =============================================================================
215 * Single-logical-block read / write
216 * =============================================================================
217 */
218
247static ra8_err_t internal_read_one(const ra8_ftl_t* ftl, uint32_t lbn, uint8_t* dst)
248{
249 RA8_CHECK_NULL_PTR(ftl, s_tag, "ftl must not be nullptr");
250 RA8_CHECK_NULL_PTR(dst, s_tag, "dst must not be nullptr");
251 const uint16_t phys = ftl->map[lbn];
252 if (phys == (uint16_t)k_ra8_ftl_unmapped) {
253 (void)memset(dst, (int)ftl->erase_value, (size_t)k_ra8_io_block_size_bytes);
254 return k_ra8_ok;
255 }
256 return ra8_io_blockdev_read(ftl->raw, phys, (uint32_t)k_ra8_ftl_one_block, dst);
257}
258
289static ra8_err_t internal_write_one(ra8_ftl_t* ftl, uint32_t lbn, const uint8_t* src)
290{
291 RA8_CHECK_NULL_PTR(ftl, s_tag, "ftl must not be nullptr");
292 RA8_CHECK_NULL_PTR(src, s_tag, "src must not be nullptr");
293 uint32_t phys = 0;
294 const ra8_err_t alloc = internal_alloc_blank(ftl, &phys);
295 if (alloc != k_ra8_ok) {
296 return alloc;
297 }
298 const ra8_err_t prog = ra8_io_blockdev_write(ftl->raw, phys, (uint32_t)k_ra8_ftl_one_block, src);
299 if (prog != k_ra8_ok) {
300 return prog;
301 }
302 const uint16_t old = ftl->map[lbn];
303 if (old != (uint16_t)k_ra8_ftl_unmapped) {
304 ftl->pblocks[old].state = (uint8_t)k_ra8_ftl_pstate_stale;
305 }
306 ftl->pblocks[phys].state = (uint8_t)k_ra8_ftl_pstate_live;
307 ftl->map[lbn] = (uint16_t)phys;
308 return k_ra8_ok;
309}
310
311/* =============================================================================
312 * Presented vtable
313 * =============================================================================
314 */
315
341static ra8_err_t internal_bounds(const ra8_ftl_t* ftl, uint32_t lba, uint32_t count)
342{
343 if (count > ftl->logical_blocks) {
345 }
346 if (lba > ftl->logical_blocks - count) {
348 }
349 return k_ra8_ok;
350}
351
380static ra8_err_t internal_dev_read(void* ctx, uint32_t lba, uint32_t count, uint8_t* buf)
381{
382 RA8_CHECK_NULL_PTR(ctx, s_tag, "ctx must not be nullptr");
383 RA8_CHECK_NULL_PTR(buf, s_tag, "buf must not be nullptr");
384 const ra8_ftl_t* ftl = (const ra8_ftl_t*)ctx;
385 const ra8_err_t b = internal_bounds(ftl, lba, count);
386 if (b != k_ra8_ok) {
387 return b;
388 }
389 for (uint32_t i = 0; i < count; ++i) {
390 const size_t off = (size_t)i * (size_t)k_ra8_io_block_size_bytes;
391 const ra8_err_t e = internal_read_one(ftl, lba + i, &buf[off]);
392 if (e != k_ra8_ok) {
393 return e;
394 }
395 }
396 return k_ra8_ok;
397}
398
428static ra8_err_t internal_dev_write(void* ctx, uint32_t lba, uint32_t count, const uint8_t* buf)
429{
430 RA8_CHECK_NULL_PTR(ctx, s_tag, "ctx must not be nullptr");
431 RA8_CHECK_NULL_PTR(buf, s_tag, "buf must not be nullptr");
432 ra8_ftl_t* ftl = (ra8_ftl_t*)ctx;
433 const ra8_err_t b = internal_bounds(ftl, lba, count);
434 if (b != k_ra8_ok) {
435 return b;
436 }
437 for (uint32_t i = 0; i < count; ++i) {
438 const size_t off = (size_t)i * (size_t)k_ra8_io_block_size_bytes;
439 const ra8_err_t e = internal_write_one(ftl, lba + i, &buf[off]);
440 if (e != k_ra8_ok) {
441 return e;
442 }
443 }
444 return k_ra8_ok;
445}
446
475static ra8_err_t internal_dev_erase(void* ctx, uint32_t lba, uint32_t count)
476{
477 RA8_CHECK_NULL_PTR(ctx, s_tag, "ctx must not be nullptr");
478 ra8_ftl_t* ftl = (ra8_ftl_t*)ctx;
479 const ra8_err_t b = internal_bounds(ftl, lba, count);
480 if (b != k_ra8_ok) {
481 return b;
482 }
483 for (uint32_t i = 0; i < count; ++i) {
484 const uint16_t old = ftl->map[lba + i];
485 if (old != (uint16_t)k_ra8_ftl_unmapped) {
486 ftl->pblocks[old].state = (uint8_t)k_ra8_ftl_pstate_stale;
487 ftl->map[lba + i] = (uint16_t)k_ra8_ftl_unmapped;
488 }
489 }
490 return k_ra8_ok;
491}
492
519{
520 RA8_CHECK_NULL_PTR(ctx, s_tag, "ctx must not be nullptr");
521 RA8_CHECK_NULL_PTR(out, s_tag, "out must not be nullptr");
522 const ra8_ftl_t* ftl = (const ra8_ftl_t*)ctx;
523 out->block_count = ftl->logical_blocks;
524 out->erase_unit_blocks = (uint32_t)k_ra8_ftl_one_block;
527 out->erase_value = ftl->erase_value;
528 out->must_erase_before_write = false;
529 out->read_only = false;
530 return k_ra8_ok;
531}
532
558{
559 RA8_CHECK_NULL_PTR(ctx, s_tag, "ctx must not be nullptr");
560 const ra8_ftl_t* ftl = (const ra8_ftl_t*)ctx;
561 RA8_CHECK_NULL_PTR(ftl->raw, s_tag, "raw must not be nullptr");
562 return ra8_io_blockdev_sync(ftl->raw);
563}
564
567 .read = internal_dev_read,
568 .write = internal_dev_write,
569 .erase = internal_dev_erase,
570 .get_caps = internal_dev_get_caps,
571 .sync = internal_dev_sync,
572};
573
574/* =============================================================================
575 * Init / bind / diagnostics
576 * =============================================================================
577 */
578
610 uint32_t logical_blocks,
611 uint32_t physical_blocks,
612 uint8_t* erase_out)
613{
614 RA8_CHECK_NULL_PTR(caps, s_tag, "caps must not be nullptr");
615 RA8_CHECK_NULL_PTR(erase_out, s_tag, "erase_out must not be nullptr");
616 if (caps->read_only) {
618 }
619 if (caps->erase_unit_blocks != (uint32_t)k_ra8_ftl_req_erase_unit) {
621 }
622 if (caps->block_count < physical_blocks) {
624 }
625 if (physical_blocks < logical_blocks + (uint32_t)k_ra8_ftl_min_spare) {
627 }
628 *erase_out = caps->erase_value;
629 return k_ra8_ok;
630}
631
657{
658 RA8_CHECK_NULL_PTR(ftl, s_tag, "ftl must not be nullptr");
659 RA8_CHECK_NULL_PTR(ftl->map, s_tag, "map must not be nullptr");
660 for (uint32_t l = 0; l < ftl->logical_blocks; ++l) {
661 ftl->map[l] = (uint16_t)k_ra8_ftl_unmapped;
662 }
663 for (uint32_t p = 0; p < ftl->physical_blocks; ++p) {
664 ftl->pblocks[p].state = (uint8_t)k_ra8_ftl_pstate_free;
665 ftl->pblocks[p].erase_count = 0U;
666 }
667 return k_ra8_ok;
668}
669
703 const ra8_io_blockdev_t* raw,
704 const uint16_t* map,
705 const ra8_ftl_pblock_t* pblocks,
706 const uint8_t* scratch,
707 uint32_t logical_blocks,
708 uint32_t physical_blocks)
709{
710 RA8_CHECK_NULL_PTR(bd, s_tag, "bd must not be nullptr");
711 RA8_CHECK_NULL_PTR(raw, s_tag, "raw must not be nullptr");
712 RA8_CHECK_NULL_PTR(map, s_tag, "map must not be nullptr");
713 RA8_CHECK_NULL_PTR(pblocks, s_tag, "pblocks must not be nullptr");
714 RA8_CHECK_NULL_PTR(scratch, s_tag, "scratch must not be nullptr");
715 if (logical_blocks == 0U) {
717 }
718 if (physical_blocks > (uint32_t)k_ra8_ftl_max_pblocks) {
720 }
721 return k_ra8_ok;
722}
723
725 const ra8_io_blockdev_t* raw,
726 uint16_t* map,
727 uint32_t logical_blocks,
728 ra8_ftl_pblock_t* pblocks,
729 uint32_t physical_blocks,
730 uint8_t* scratch)
731{
732 const ra8_err_t va =
733 internal_validate_init_args(bd, raw, map, pblocks, scratch, logical_blocks, physical_blocks);
734 if (va != k_ra8_ok) {
735 return va;
736 }
737 ra8_io_blockdev_caps_t caps = {};
738 const ra8_err_t cq = ra8_io_blockdev_get_caps(raw, &caps);
739 if (cq != k_ra8_ok) {
740 return cq;
741 }
742 uint8_t erase_value = 0;
743 const ra8_err_t ck = internal_check_caps(&caps, logical_blocks, physical_blocks, &erase_value);
744 if (ck != k_ra8_ok) {
745 return ck;
746 }
747 bd->raw = raw;
748 bd->map = map;
749 bd->pblocks = pblocks;
750 bd->scratch = scratch;
751 bd->logical_blocks = logical_blocks;
752 bd->physical_blocks = physical_blocks;
753 bd->erase_value = erase_value;
754 return internal_reset_tables(bd);
755}
756
758{
759 RA8_CHECK_NULL_PTR(ftl, s_tag, "ftl must not be nullptr");
760 RA8_CHECK_NULL_PTR(out, s_tag, "out must not be nullptr");
761 if (ftl->raw == nullptr) {
763 }
764 out->iface = &s_ftl_iface;
765 out->ctx = ftl;
766 return k_ra8_ok;
767}
768
769ra8_err_t ra8_ftl_wear_stats(const ra8_ftl_t* ftl, uint32_t* max_out, uint32_t* min_out)
770{
771 RA8_CHECK_NULL_PTR(ftl, s_tag, "ftl must not be nullptr");
772 RA8_CHECK_NULL_PTR(max_out, s_tag, "max_out must not be nullptr");
773 RA8_CHECK_NULL_PTR(min_out, s_tag, "min_out must not be nullptr");
774 if (ftl->pblocks == nullptr) {
776 }
777 uint32_t hi = 0;
778 uint32_t lo = (uint32_t)k_ra8_ftl_count_max;
779 for (uint32_t p = 0; p < ftl->physical_blocks; ++p) {
780 const uint32_t c = ftl->pblocks[p].erase_count;
781 if (c > hi) {
782 hi = c;
783 }
784 if (c < lo) {
785 lo = c;
786 }
787 }
788 *max_out = hi;
789 *min_out = lo;
790 return k_ra8_ok;
791}
792
793ra8_err_t ra8_ftl_phys_of(const ra8_ftl_t* ftl, uint32_t lbn, uint16_t* phys_out)
794{
795 RA8_CHECK_NULL_PTR(ftl, s_tag, "ftl must not be nullptr");
796 RA8_CHECK_NULL_PTR(phys_out, s_tag, "phys_out must not be nullptr");
797 if (ftl->map == nullptr) {
799 }
800 if (lbn >= ftl->logical_blocks) {
802 }
803 *phys_out = ftl->map[lbn];
804 return k_ra8_ok;
805}
static const char * s_tag
Logging / check tag.
Definition ra8_app.c:17
Annotation-attribute framework macros for ra8-firmware.
#define RA8_INTERNAL
Marker that a function is intended to be static (file-local).
Validation and Error-Checking Macros for ra8-firmware.
#define RA8_CHECK_NULL_PTR(ptr, tag, message)
Reject nullptr pointer, returning k_ra8_err_null_ptr.
Definition ra8_check.h:243
Error Code Definitions for ra8-firmware.
@ k_ra8_err_no_mem
Static buffer exhausted (no dynamic memory on this project).
Definition ra8_err.h:142
@ k_ra8_err_no_data
No application data available (e.g.
Definition ra8_err.h:202
@ k_ra8_err_out_of_range
Sensor or peripheral output out of valid range.
Definition ra8_err.h:337
@ k_ra8_err_invalid_arg
Invalid function argument.
Definition ra8_err.h:152
@ k_ra8_err_not_initialized
Module not initialized – _init() not yet called successfully.
Definition ra8_err.h:235
@ k_ra8_ok
Success – operation completed with all postconditions satisfied.
Definition ra8_err.h:119
@ k_ra8_err_invalid_size
Invalid size parameter (too large, too small, or misaligned).
Definition ra8_err.h:167
ra8_err_codes_t ra8_err_t
Canonical error-return type used by every ra8-firmware API.
Definition ra8_err.h:546
void * memset(void *dst, int value, size_t n)
Fill memory with a constant byte value.
static ra8_err_t internal_validate_init_args(const ra8_ftl_t *bd, const ra8_io_blockdev_t *raw, const uint16_t *map, const ra8_ftl_pblock_t *pblocks, const uint8_t *scratch, uint32_t logical_blocks, uint32_t physical_blocks)
Validate the pointer + sizing arguments to ra8_ftl_init.
Definition ra8_ftl.c:702
static ra8_err_t internal_dev_write(void *ctx, uint32_t lba, uint32_t count, const uint8_t *buf)
Presented vtable: write count logical blocks at lba.
Definition ra8_ftl.c:428
static const ra8_io_blockdev_iface_t s_ftl_iface
Presented FTL vtable – a clean free-overwrite block device.
Definition ra8_ftl.c:566
static ra8_err_t internal_check_caps(const ra8_io_blockdev_caps_t *caps, uint32_t logical_blocks, uint32_t physical_blocks, uint8_t *erase_out)
Validate the underlying device's capabilities for FTL use.
Definition ra8_ftl.c:609
static ra8_err_t internal_read_one(const ra8_ftl_t *ftl, uint32_t lbn, uint8_t *dst)
Read one logical block, resolving the map (or erase value if unmapped).
Definition ra8_ftl.c:247
static ra8_err_t internal_write_one(ra8_ftl_t *ftl, uint32_t lbn, const uint8_t *src)
Write one logical block via copy-on-write relocation.
Definition ra8_ftl.c:289
ra8_err_t ra8_ftl_wear_stats(const ra8_ftl_t *ftl, uint32_t *max_out, uint32_t *min_out)
Report the highest per-physical-block erase count seen so far.
Definition ra8_ftl.c:769
static ra8_err_t internal_dev_read(void *ctx, uint32_t lba, uint32_t count, uint8_t *buf)
Presented vtable: read count logical blocks at lba.
Definition ra8_ftl.c:380
static ra8_err_t internal_pick_free(const ra8_ftl_t *ftl, uint32_t *out)
Select the least-erased FREE physical block (wear-levelling).
Definition ra8_ftl.c:128
ra8_err_t ra8_ftl_init(ra8_ftl_t *bd, const ra8_io_blockdev_t *raw, uint16_t *map, uint32_t logical_blocks, ra8_ftl_pblock_t *pblocks, uint32_t physical_blocks, uint8_t *scratch)
Initialise an FTL over an erase-before-write block device.
Definition ra8_ftl.c:724
static ra8_err_t internal_dev_sync(void *ctx)
Presented vtable: flush the underlying device.
Definition ra8_ftl.c:557
static ra8_err_t internal_dev_get_caps(const void *ctx, ra8_io_blockdev_caps_t *out)
Presented vtable: report the FTL's free-overwrite capabilities.
Definition ra8_ftl.c:518
static ra8_err_t internal_dev_erase(void *ctx, uint32_t lba, uint32_t count)
Presented vtable: erase count logical blocks at lba.
Definition ra8_ftl.c:475
ra8_err_t ra8_ftl_as_blockdev(ra8_ftl_t *ftl, ra8_io_blockdev_t *out)
Expose an initialised FTL as a free-overwrite ra8_io_blockdev_t.
Definition ra8_ftl.c:757
static ra8_err_t internal_alloc_blank(ra8_ftl_t *ftl, uint32_t *out)
Allocate a blank physical block, reclaiming stale blocks if needed.
Definition ra8_ftl.c:184
static ra8_err_t internal_reset_tables(ra8_ftl_t *ftl)
Cold-start the FTL bookkeeping tables.
Definition ra8_ftl.c:656
static ra8_err_t internal_bounds(const ra8_ftl_t *ftl, uint32_t lba, uint32_t count)
Reject an out-of-range presented [lba, lba+count) range.
Definition ra8_ftl.c:341
static ra8_err_t internal_reclaim_stale(ra8_ftl_t *ftl)
Reclaim every STALE physical block by erasing it back to FREE.
Definition ra8_ftl.c:83
ra8_err_t ra8_ftl_phys_of(const ra8_ftl_t *ftl, uint32_t lbn, uint16_t *phys_out)
Report the physical block currently backing a logical block.
Definition ra8_ftl.c:793
ra8_ftl_impl_const_t
Implementation-private layout constants.
Definition ra8_ftl.c:45
@ k_ra8_ftl_req_erase_unit
Required raw erase_unit_blocks value.
Definition ra8_ftl.c:47
@ k_ra8_ftl_one_block
Count for a single-block raw transfer.
Definition ra8_ftl.c:46
@ k_ra8_ftl_count_max
Sentinel: highest possible erase count.
Definition ra8_ftl.c:48
Flash Translation Layer – free overwrite over erase-before-write media.
@ k_ra8_ftl_unmapped
map[] sentinel: logical block unwritten.
Definition ra8_ftl.h:119
@ k_ra8_ftl_max_pblocks
Max physical blocks an FTL may wrap.
Definition ra8_ftl.h:120
@ k_ra8_ftl_min_spare
Minimum spare blocks (P - L >= 1).
Definition ra8_ftl.h:121
@ k_ra8_ftl_pstate_free
Blank (reads as erase value); allocatable.
Definition ra8_ftl.h:136
@ k_ra8_ftl_pstate_live
Holds current data for one logical block.
Definition ra8_ftl.h:137
@ k_ra8_ftl_pstate_stale
Superseded; reclaimable by erase to FREE.
Definition ra8_ftl.h:138
ra8_io block-device fabric – one LBA vtable across every storage medium.
ra8_err_t ra8_io_blockdev_sync(const ra8_io_blockdev_t *bd)
Flush any backend write buffering to the medium.
ra8_err_t ra8_io_blockdev_read(const ra8_io_blockdev_t *bd, uint32_t lba, uint32_t count, uint8_t *buf)
Read count logical blocks starting at lba into buf.
struct ra8_io_blockdev_iface ra8_io_blockdev_iface_t
@ k_ra8_io_block_size_bytes
Bytes per logical block (the only size).
ra8_err_t ra8_io_blockdev_write(const ra8_io_blockdev_t *bd, uint32_t lba, uint32_t count, const uint8_t *buf)
Write count logical blocks from buf starting at lba.
ra8_err_t ra8_io_blockdev_get_caps(const ra8_io_blockdev_t *bd, ra8_io_blockdev_caps_t *out)
Report the bound backend's medium capabilities.
ra8_err_t ra8_io_blockdev_erase(const ra8_io_blockdev_t *bd, uint32_t lba, uint32_t count)
Erase count blocks starting at lba to the medium's erase value.
Backend implementation contract for the ra8_io block-device fabric.
Caller-owned metadata for one physical erase block.
Definition ra8_ftl.h:162
uint32_t erase_count
Cumulative erases of this block (wear metric).
Definition ra8_ftl.h:163
uint8_t state
ra8_ftl_pstate_t lifecycle state (private).
Definition ra8_ftl.h:164
Caller-allocated FTL handle binding the wrapper to the raw device.
Definition ra8_ftl.h:190
ra8_ftl_pblock_t * pblocks
Per-physical metadata (private).
Definition ra8_ftl.h:193
uint32_t physical_blocks
Blocks in the raw dev (private).
Definition ra8_ftl.h:196
uint8_t * scratch
512-byte copy scratch (private).
Definition ra8_ftl.h:194
uint16_t * map
logical->physical map (private).
Definition ra8_ftl.h:192
uint8_t erase_value
Raw medium erase byte (private).
Definition ra8_ftl.h:197
const ra8_io_blockdev_t * raw
Underlying erase-before-write dev.
Definition ra8_ftl.h:191
uint32_t logical_blocks
Blocks presented to FAT (private).
Definition ra8_ftl.h:195
Static properties a backend reports about its medium.
uint32_t erase_unit_blocks
Erase granularity, in logical blocks.
uint32_t block_count
Total addressable logical blocks.
bool must_erase_before_write
true => program needs a prior erase.
bool read_only
true => writes/erases are rejected.
uint8_t erase_value
Post-erase byte (ra8_io_erase_value_t).
uint16_t logical_block_bytes
Logical block size (always 512).
uint32_t program_size_bytes
Minimum program granularity in bytes.
Caller-allocated block-device handle binding a backend to its context.
const ra8_io_blockdev_iface_t * iface
Bound backend vtable (private).
void * ctx
Backend-private context (private).