|
4 | 4 | * ============================================================================== |
5 | 5 | * |
6 | 6 | * REFERENCED SPECIFICATIONS & GAPS: |
7 | | - * |
8 | 7 | * 1. docs/hals.md (Flash HAL Backend) |
9 | | - * - Erase-Prereq: Ein Write-Command MUSS fehlschlagen (BOOT_ERR_FLASH_NOT_ERASED), |
10 | | - * falls die Ziel-Speicherzellen nicht 'erased_value' (0xFF) aufweisen. |
11 | | - * - Alignment Rules: Writes, die nicht exakt CHIP_FLASH_WRITE_ALIGN einhalten, |
12 | | - * MÜSSEN hart abweisen werden. |
13 | | - * |
14 | | - * 2. docs/concept_fusion.md |
15 | | - * - In-Place Swap Buffer: Die Flash-Implementierung muss asymmetrisches |
16 | | - * Sector-Erasing unterstützen, da der Puffer vom Rest abweichen kann. |
17 | | - * |
18 | | - * 3. docs/merkle_spec.md |
19 | | - * - GAP-08: Stream-Hashing erzwingt, dass wir via flash.read niemals |
20 | | - * "Out-of-Bound" Page-Räume verlassen dürfen. |
21 | | - * |
| 8 | + * 2. docs/concept_fusion.md (In-Place Swap Buffer) |
| 9 | + * 3. docs/merkle_spec.md (GAP-08: Stream-Hashing) |
22 | 10 | * 4. docs/sandbox_setup.md & docs/testing_requirements.md |
23 | | - * - File-Simulation: Der "Flash" auf der Sandbox MUSS strikt durch eine |
24 | | - * lokale `flash_sim.bin` via stdio.h abgebildet werden, um Persistenz |
25 | | - * über Power-Loss-Crashes hinaus für Integrationstests aufrecht zu erhalten. |
26 | | - * |
27 | | - * TODO: stdio (fopen, fseek, fread, fwrite) Wrapper schreiben. |
28 | 11 | */ |
29 | 12 |
|
30 | 13 | #include "mock_flash.h" |
| 14 | +#include "chip_config.h" |
31 | 15 | #include <stdio.h> |
| 16 | +#include <stdlib.h> |
| 17 | +#include <string.h> |
| 18 | + |
| 19 | +#define DEFAULT_SIM_FILE "flash_sim.bin" |
| 20 | + |
| 21 | +static FILE *flash_file = NULL; |
| 22 | +static const char *sim_filename = DEFAULT_SIM_FILE; |
| 23 | +static uint32_t write_count_limit = 0; |
| 24 | +static uint32_t simulated_writes = 0; |
| 25 | +static uint32_t simulated_vendor_error = 0; |
| 26 | + |
| 27 | +/* [TODO] (GAP-F20): Bit-Rot Simulator. Implementiere Umgebungsvariablen wie TOOB_BITROT_ADDR |
| 28 | + * und TOOB_BITROT_VALUE, um bei mock_flash_read absichtliche Fehler (0x00 / 0xFF Injektion) |
| 29 | + * für SIL-Verifizierungs-Checks zu provozieren (laut testing_requirements.md). */ |
| 30 | + |
| 31 | +static void check_fault_injection(void) { |
| 32 | + if (write_count_limit > 0 && simulated_writes >= write_count_limit) { |
| 33 | + printf("[M-SANDBOX] BROWNOUT SIMULATED! Power loss after %u writes.\n", simulated_writes); |
| 34 | + fflush(stdout); |
| 35 | + exit(1); /* Crash! */ |
| 36 | + } |
| 37 | +} |
| 38 | + |
| 39 | +static boot_status_t mock_flash_init(void) { |
| 40 | + const char *env_file = getenv("TOOB_FLASH_SIM_FILE"); |
| 41 | + if (env_file != NULL) { |
| 42 | + sim_filename = env_file; |
| 43 | + } |
| 44 | + |
| 45 | + const char *env_fail = getenv("TOOB_FAIL_AFTER_WRITES"); |
| 46 | + if (env_fail != NULL) { |
| 47 | + write_count_limit = (uint32_t)strtoul(env_fail, NULL, 10); |
| 48 | + } |
| 49 | + |
| 50 | + flash_file = fopen(sim_filename, "rb+"); |
| 51 | + if (!flash_file) { |
| 52 | + /* File doesn't exist, create it */ |
| 53 | + flash_file = fopen(sim_filename, "wb+"); |
| 54 | + if (!flash_file) { |
| 55 | + return BOOT_ERR_STATE; |
| 56 | + } |
| 57 | + |
| 58 | + /* Initialisieren mit 0xFF (Erase Data) */ |
| 59 | + uint8_t buffer[CHIP_FLASH_PAGE_SIZE]; |
| 60 | + memset(buffer, CHIP_FLASH_ERASURE_MAPPING, sizeof(buffer)); |
| 61 | + |
| 62 | + uint32_t pages = CHIP_FLASH_TOTAL_SIZE / CHIP_FLASH_PAGE_SIZE; |
| 63 | + for (uint32_t i = 0; i < pages; i++) { |
| 64 | + if (fwrite(buffer, 1, sizeof(buffer), flash_file) != sizeof(buffer)) { |
| 65 | + fclose(flash_file); |
| 66 | + flash_file = NULL; |
| 67 | + return BOOT_ERR_STATE; |
| 68 | + } |
| 69 | + } |
| 70 | + } |
| 71 | + fflush(flash_file); |
| 72 | + return BOOT_OK; |
| 73 | +} |
| 74 | + |
| 75 | +static void mock_flash_deinit(void) { |
| 76 | + if (flash_file) { |
| 77 | + fclose(flash_file); |
| 78 | + flash_file = NULL; |
| 79 | + } |
| 80 | +} |
| 81 | + |
| 82 | +static boot_status_t mock_flash_read(uint32_t addr, void *buf, size_t len) { |
| 83 | + if (!flash_file) return BOOT_ERR_STATE; |
| 84 | + if (addr + len > CHIP_FLASH_TOTAL_SIZE) return BOOT_ERR_FLASH_BOUNDS; |
| 85 | + |
| 86 | + if (fseek(flash_file, addr, SEEK_SET) != 0) return BOOT_ERR_FLASH; |
| 87 | + if (fread(buf, 1, len, flash_file) != len) return BOOT_ERR_FLASH; |
| 88 | + |
| 89 | + return BOOT_OK; |
| 90 | +} |
| 91 | + |
| 92 | +static boot_status_t mock_flash_write(uint32_t addr, const void *buf, size_t len) { |
| 93 | + if (!flash_file) return BOOT_ERR_STATE; |
| 94 | + if (addr % CHIP_FLASH_WRITE_ALIGNMENT != 0 || len % CHIP_FLASH_WRITE_ALIGNMENT != 0) { |
| 95 | + return BOOT_ERR_FLASH_ALIGN; |
| 96 | + } |
| 97 | + if (addr + len > CHIP_FLASH_TOTAL_SIZE) return BOOT_ERR_FLASH_BOUNDS; |
| 98 | + |
| 99 | +#ifndef TOOB_FLASH_DISABLE_BLANK_CHECK |
| 100 | + /* [TODO]: Die Spec hals.md fordert aus Performancegründen einen "32-Bit Aligned Word-Check" |
| 101 | + * anstatt des hier genutzten Byte-per-Byte Checks. Muss auf uint32_t Casting optimiert werden. */ |
| 102 | + uint8_t existing[256]; |
| 103 | + size_t remaining = len; |
| 104 | + uint32_t current_addr = addr; |
| 105 | + |
| 106 | + while (remaining > 0) { |
| 107 | + size_t chunk = (remaining > sizeof(existing)) ? sizeof(existing) : remaining; |
| 108 | + if (fseek(flash_file, current_addr, SEEK_SET) != 0) return BOOT_ERR_FLASH; |
| 109 | + if (fread(existing, 1, chunk, flash_file) != chunk) return BOOT_ERR_FLASH; |
| 110 | + |
| 111 | + for (size_t i = 0; i < chunk; i++) { |
| 112 | + if (existing[i] != CHIP_FLASH_ERASURE_MAPPING) { |
| 113 | + return BOOT_ERR_FLASH_NOT_ERASED; |
| 114 | + } |
| 115 | + } |
| 116 | + remaining -= chunk; |
| 117 | + current_addr += chunk; |
| 118 | + } |
| 119 | +#endif |
| 120 | + |
| 121 | + /* Simuliere NOR-Flash Physik: Man kann Bits nur auf 0 ziehen (Logisches AND) */ |
| 122 | + /* [TODO]: KRITISCHE P10 VERLETZUNG! uint8_t buffer_to_write[len] ist ein |
| 123 | + * Variable Length Array (VLA) und verstößt gegen NASA P10 Regeln (Keine laufzeitabhängigen Stack-Allokationen). |
| 124 | + * Dies muss zwingend als Chunked-Loop (z.B. 256 Bytes max) umgeschrieben werden! */ |
| 125 | + uint8_t buffer_to_write[len]; |
| 126 | + const uint8_t *src = (const uint8_t *)buf; |
| 127 | + |
| 128 | + if (fseek(flash_file, addr, SEEK_SET) != 0) return BOOT_ERR_FLASH; |
| 129 | + if (fread(buffer_to_write, 1, len, flash_file) != len) return BOOT_ERR_FLASH; |
| 130 | + |
| 131 | + for (size_t i = 0; i < len; i++) { |
| 132 | + buffer_to_write[i] &= src[i]; |
| 133 | + } |
| 134 | + |
| 135 | + if (fseek(flash_file, addr, SEEK_SET) != 0) return BOOT_ERR_FLASH; |
| 136 | + if (fwrite(buffer_to_write, 1, len, flash_file) != len) return BOOT_ERR_FLASH; |
| 137 | + |
| 138 | + fflush(flash_file); |
| 139 | + |
| 140 | + simulated_writes++; |
| 141 | + check_fault_injection(); |
| 142 | + |
| 143 | + return BOOT_OK; |
| 144 | +} |
| 145 | + |
| 146 | +static boot_status_t mock_flash_erase_sector(uint32_t addr) { |
| 147 | + if (!flash_file) return BOOT_ERR_STATE; |
| 148 | + if (addr % CHIP_FLASH_PAGE_SIZE != 0) return BOOT_ERR_FLASH_ALIGN; |
| 149 | + if (addr >= CHIP_FLASH_TOTAL_SIZE) return BOOT_ERR_FLASH_BOUNDS; |
| 150 | + |
| 151 | + uint8_t erased_block[CHIP_FLASH_PAGE_SIZE]; |
| 152 | + memset(erased_block, CHIP_FLASH_ERASURE_MAPPING, sizeof(erased_block)); |
| 153 | + |
| 154 | + if (fseek(flash_file, addr, SEEK_SET) != 0) return BOOT_ERR_FLASH; |
| 155 | + if (fwrite(erased_block, 1, sizeof(erased_block), flash_file) != sizeof(erased_block)) { |
| 156 | + return BOOT_ERR_FLASH; |
| 157 | + } |
| 158 | + |
| 159 | + fflush(flash_file); |
| 160 | + |
| 161 | + simulated_writes++; |
| 162 | + check_fault_injection(); |
| 163 | + |
| 164 | + return BOOT_OK; |
| 165 | +} |
| 166 | + |
| 167 | +static boot_status_t mock_flash_get_sector_size(uint32_t addr, size_t *size_out) { |
| 168 | + if (addr >= CHIP_FLASH_TOTAL_SIZE) return BOOT_ERR_FLASH_BOUNDS; |
| 169 | + if (size_out) { |
| 170 | + *size_out = CHIP_FLASH_PAGE_SIZE; |
| 171 | + } |
| 172 | + return BOOT_OK; |
| 173 | +} |
| 174 | + |
| 175 | +static boot_status_t mock_flash_set_otfdec_mode(bool enable) { |
| 176 | + /* Sandbox emuliert keine On-The-Fly Entschlüsselung in Software */ |
| 177 | + (void)enable; |
| 178 | + return BOOT_ERR_NOT_SUPPORTED; |
| 179 | +} |
| 180 | + |
| 181 | +static uint32_t mock_flash_get_last_vendor_error(void) { |
| 182 | + uint32_t err = simulated_vendor_error; |
| 183 | + simulated_vendor_error = 0; /* Clear on read */ |
| 184 | + return err; |
| 185 | +} |
| 186 | + |
| 187 | +/* --- Public Utilities --- */ |
| 188 | + |
| 189 | +void mock_flash_reset_to_factory(void) { |
| 190 | + if (flash_file) { |
| 191 | + fclose(flash_file); |
| 192 | + flash_file = NULL; |
| 193 | + } |
| 194 | + remove(sim_filename); |
| 195 | + simulated_writes = 0; |
| 196 | +} |
| 197 | + |
| 198 | +void mock_flash_set_fail_limit(uint32_t limit) { |
| 199 | + write_count_limit = limit; |
| 200 | + simulated_writes = 0; |
| 201 | +} |
| 202 | + |
| 203 | +/* --- Export --- */ |
| 204 | + |
| 205 | +const flash_hal_t sandbox_flash_hal = { |
| 206 | + .version = 0x01000000, |
| 207 | + .init = mock_flash_init, |
| 208 | + .deinit = mock_flash_deinit, |
| 209 | + .read = mock_flash_read, |
| 210 | + .write = mock_flash_write, |
| 211 | + .erase_sector = mock_flash_erase_sector, |
| 212 | + .get_sector_size = mock_flash_get_sector_size, |
| 213 | + .set_otfdec_mode = mock_flash_set_otfdec_mode, |
| 214 | + .get_last_vendor_error = mock_flash_get_last_vendor_error, |
32 | 215 |
|
33 | | -// TODO: Implementiere Flash Operationen und Environment-basierten Crash-Injection Hook (TOOB_FAIL_AFTER) |
| 216 | + .max_sector_size = CHIP_FLASH_MAX_SECTOR_SIZE, |
| 217 | + .total_size = CHIP_FLASH_TOTAL_SIZE, |
| 218 | + .write_align = CHIP_FLASH_WRITE_ALIGNMENT, |
| 219 | + .erased_value = CHIP_FLASH_ERASURE_MAPPING |
| 220 | +}; |
0 commit comments