/* 掉电不丢区的假实现 —— 一块 RAM,但**按 NOR flash 的规矩来**。 三条规矩,缺一条就会让 evlog 里最难查的那类错在宿主机上永远复现不出来: 1. 写 = **按位与**(`*p &= v`),不是赋值。往没擦过的槽里再写一条记录, 真板上得到的是两条记录的与(多半是一条校验不过的垃圾),而**不报错**; 2. 擦 = 整个扇区变 0xFF,粒度就是扇区; 3. **`sim_hal_reset()` 会把这块区擦干净(= 一块新板子), `sim_hal_reboot()` 不会(= 掉电重启)。** §10.7 的验收「掉电重启后能读回」 全靠这两个的区别,别把 store 的清理塞进 reboot 里。 还多做一件真板会做而假实现容易漏的事:**掉电可以打断一次写** (`sim_store_tear_next_write()`),落进去的是前面若干字节,后面还是 0xFF。 这正是 evlog 每条记录都要带校验字节的理由。 */ #include #include "hal_store.h" #include "sim_hal.h" static uint8_t s_mem[SIM_STORE_SECTORS * SIM_STORE_SECTOR_BYTES]; static bool s_inited; static bool s_formatted; /* 这块「flash」出过厂没有(全 0xFF 才是出厂态) */ static uint32_t s_writes; static uint32_t s_erases; static uint32_t s_bytes; static int s_fail_in; /* >0:第 n 次写整条失败;0 = 不注入 */ static uint32_t s_tear_at; /* >0:下一次写只落这么多字节然后失败 */ void sim_store_reset_internal(void); /* 只给 sim_hal_reset() 用:连内容一起擦 */ void sim_store_reboot_internal(void); /* 只给 sim_hal_reboot() 用:内容留着 */ static void wipe(void) { memset(s_mem, 0xFF, sizeof(s_mem)); s_formatted = true; } void sim_store_reset_internal(void) { wipe(); s_inited = false; s_writes = 0; s_erases = 0; s_bytes = 0; s_fail_in = 0; s_tear_at = 0; } void sim_store_reboot_internal(void) { /* 掉电重启:CPU 侧什么都不剩,flash 里的内容一个字节不变。 */ if (!s_formatted) { wipe(); /* 从来没 reset 过的进程:至少给它一块出厂态的 flash */ } s_inited = false; s_writes = 0; s_erases = 0; s_bytes = 0; s_fail_in = 0; s_tear_at = 0; } hal_err_t hal_store_init(void) { if (!s_formatted) { wipe(); } s_inited = true; return HAL_OK; } void hal_store_geometry(hal_store_geom_t *g) { if (g == NULL) { return; } g->sector_bytes = s_inited ? SIM_STORE_SECTOR_BYTES : 0u; g->sectors = s_inited ? SIM_STORE_SECTORS : 0u; } static bool range_ok(uint32_t off, uint32_t len) { if (len > sizeof(s_mem) || off > sizeof(s_mem) - len) { return false; } return true; } hal_err_t hal_store_read(uint32_t off, void *buf, uint32_t len) { if (!s_inited) { return HAL_ERR_STATE; } if (buf == NULL || !range_ok(off, len)) { return HAL_ERR_ARG; } memcpy(buf, &s_mem[off], len); return HAL_OK; } hal_err_t hal_store_write(uint32_t off, const void *buf, uint32_t len) { if (!s_inited) { return HAL_ERR_STATE; } if (buf == NULL || !range_ok(off, len) || (off % 4u) != 0u || (len % 4u) != 0u) { return HAL_ERR_ARG; } uint32_t n = len; bool fail = false; if (s_fail_in > 0 && --s_fail_in == 0) { return HAL_ERR_BUS; /* 一个字节都没落进去 */ } if (s_tear_at > 0u) { n = (s_tear_at < len) ? s_tear_at : len; s_tear_at = 0u; fail = true; /* 掉电:前 n 个字节落进去了,然后就没有然后了 */ } const uint8_t *src = (const uint8_t *)buf; for (uint32_t i = 0; i < n; i++) { s_mem[off + i] &= src[i]; /* ⚠️ NOR:只能 1→0 */ } s_writes++; s_bytes += n; return fail ? HAL_ERR_BUS : HAL_OK; } hal_err_t hal_store_erase(uint32_t sector) { if (!s_inited) { return HAL_ERR_STATE; } if (sector >= SIM_STORE_SECTORS) { return HAL_ERR_ARG; } memset(&s_mem[sector * SIM_STORE_SECTOR_BYTES], 0xFF, SIM_STORE_SECTOR_BYTES); s_erases++; return HAL_OK; } /* ---- 假板子这一侧的控制面 ------------------------------------------------ */ void sim_store_fail_next_write(int nth) { s_fail_in = nth; } void sim_store_tear_next_write(uint32_t bytes) { s_tear_at = bytes; } uint32_t sim_store_writes(void) { return s_writes; } uint32_t sim_store_erases(void) { return s_erases; } uint32_t sim_store_bytes(void) { return s_bytes; } uint8_t sim_store_byte(uint32_t off) { return (off < sizeof(s_mem)) ? s_mem[off] : 0xFFu; }