/* flashimg/cpu_main/fs_harness_flash_bin.c - 9P access to flash storage * * Copyright (C) 2025 Luke T. Shumaker * SPDX-License-Identifier: AGPL-3.0-or-later */ #include #include #include #define LOG_NAME FLASH #include #include #define IMPLEMENTATION_FOR_FLASHIO_H YES /* so we can reuse the buffer */ #include "flashio.h" #define IMPLEMENTATION_FOR_FS_HARNESS_FLASH_BIN_H YES #include "fs_harness_flash_bin.h" LO_IMPLEMENTATION_C(lib9p_srv_file, struct flash_file, flash_file); LO_IMPLEMENTATION_STATIC(lib9p_srv_fio, struct flash_file, flash_file); #define DATA_START ((const char *)(XIP_NOALLOC_BASE)) #define DATA_SIZE PICO_FLASH_SIZE_BYTES #define DATA_HSIZE (DATA_SIZE/2) static_assert(DATA_SIZE % FLASH_SECTOR_SIZE == 0); static_assert(DATA_HSIZE % FLASH_SECTOR_SIZE == 0); /* There are some memcpy()s (and memcmp()s?) in here that maybe should * be replaced with SSI DMA. */ /* ab_flash_* (mid-level utilities for our A/B write scheme) ******************/ /** * Copy the upper half of flash to the lower half of flash, then reboot. * * @param buf : a scratch buffer that is at least FLASH_SECTOR_SIZE */ [[noreturn]] static void __no_inline_not_in_flash_func(ab_flash_finalize)(uint8_t *buf) { assert(buf); log_infoln("copying upper flash to lower flash..."); cr_save_and_disable_interrupts(); for (size_t off = 0; off < DATA_HSIZE; off += FLASH_SECTOR_SIZE) { memcpy(buf, DATA_START+DATA_HSIZE+off, FLASH_SECTOR_SIZE); if (memcmp(DATA_START+off, buf, FLASH_SECTOR_SIZE) == 0) continue; flash_range_erase(off, FLASH_SECTOR_SIZE); flash_range_program(off, buf, FLASH_SECTOR_SIZE); } log_infoln("rebooting..."); watchdog_reboot(0, 0, 300); for (;;) asm volatile ("nop"); } /** * Set the upper half of flash to all "1" bits. * * @param buf : a scratch buffer that is at least FLASH_SECTOR_SIZE */ static void ab_flash_initialize(void) { log_infoln("erasing upper flash..."); for (size_t off = DATA_HSIZE; off < DATA_SIZE; off += FLASH_SECTOR_SIZE) { bool saved = cr_save_and_disable_interrupts(); flash_range_erase(off, FLASH_SECTOR_SIZE); cr_restore_interrupts(saved); } log_debugln("... erased"); } /* srv_file *******************************************************************/ #ifdef NDEBUG #define flash_file_assert(self) ((void)0) #else void flash_file_assert(struct flash_file *self) { assert(self); assert(self->name); assert(self->io); } #endif void flash_file_free(struct flash_file *self) { flash_file_assert(self); } struct lib9p_qid flash_file_qid(struct flash_file *self) { flash_file_assert(self); return (struct lib9p_qid){ .type = LIB9P_QT_FILE|LIB9P_QT_EXCL|LIB9P_QT_APPEND, .vers = 1, .path = self->pathnum, }; } error flash_file_stat(struct flash_file *self, struct lib9p_srv_ctx *ctx, struct lib9p_srv_stat *out) { flash_file_assert(self); assert(ctx); assert(out); *out = ((struct lib9p_srv_stat){ .qid = flash_file_qid(self), .mode = LIB9P_DM_EXCL|LIB9P_DM_APPEND|0666, .atime_sec = UTIL9P_ATIME, .mtime_sec = UTIL9P_MTIME, .size = DATA_SIZE, .name = lib9p_str(self->name), .owner_uid = { .name = lib9p_str("root"), .num = 0 }, .owner_gid = { .name = lib9p_str("root"), .num = 0 }, .last_modifier_uid = { .name = lib9p_str("root"), .num = 0 }, .extension = lib9p_str(NULL), }); return ERROR_NULL; } error flash_file_wstat(struct flash_file *self, struct lib9p_srv_ctx *ctx, struct lib9p_srv_stat) { flash_file_assert(self); assert(ctx); return error_new(E_POSIX_EROFS, "read-only part of filesystem"); } error flash_file_remove(struct flash_file *self, struct lib9p_srv_ctx *ctx) { flash_file_assert(self); assert(ctx); return error_new(E_POSIX_EROFS, "read-only part of filesystem"); } LIB9P_SRV_NOTDIR(, struct flash_file, flash_file); static error flash_handle_ihex_data(void *, uint32_t off, uint8_t count, uint8_t *dat); static error flash_handle_ihex_eof(void *); lib9p_srv_fio_or_error flash_file_fopen(struct flash_file *self, struct lib9p_srv_ctx *ctx, bool LM_UNUSED(rd), bool wr, bool LM_UNUSED(trunc)) { flash_file_assert(self); assert(ctx); if (wr) ab_flash_initialize(); memset(&self->ihex, 0, sizeof(self->ihex)); self->ihex.handle_arg = self; self->ihex.handle_data = flash_handle_ihex_data; self->ihex.handle_eof = flash_handle_ihex_eof; return ERROR_NEW_VAL(lib9p_srv_fio, LO_BOX(lib9p_srv_fio, self)); } /* srv_fio ********************************************************************/ static struct lib9p_qid flash_file_ioqid(struct flash_file *self) { return flash_file_qid(self); } static uint32_t flash_file_iounit(struct flash_file *self) { flash_file_assert(self); return FLASH_SECTOR_SIZE; } static void flash_file_iofree(struct flash_file *self) { flash_file_assert(self); error err = flashio_flush(self->io); assert(ERROR_IS_NULL(err)); err = ihex_decoder_close(&self->ihex); assert(ERROR_IS_NULL(err)); if (self->finalize) ab_flash_finalize(self->io->dat); } static error flash_file_pread(struct flash_file *self, struct lib9p_srv_ctx *ctx, lo_interface io_writer dst, uint64_t byte_offset, uint32_t byte_count) { flash_file_assert(self); assert(ctx); return flashio_pread_to(self->io, dst, byte_offset, byte_count).err; } static error flash_handle_ihex_data(void *_self, uint32_t off, uint8_t count, uint8_t *dat) { struct flash_file *self = _self; if (off < XIP_BASE || off >= XIP_BASE + DATA_HSIZE) return error_new(E_POSIX_ENOSPC, "cannot write outside of the first half of the chip: ", (base16_u32_, off), " is outside of [", (base16_u32_, XIP_BASE), ",", (base16_u32_, XIP_BASE + DATA_HSIZE), ")"); return flashio_pwritev(self->io, &((struct wr_iovec){.iov_write_from = dat, .iov_len = count}), 1, off - XIP_BASE + DATA_HSIZE).err; } static error flash_handle_ihex_eof(void *_self) { struct flash_file *self = _self; self->finalize = true; return ERROR_NULL; } /* TODO: Also support uf2, not just ihex. */ static uint32_t_or_error flash_file_pwrite(struct flash_file *self, struct lib9p_srv_ctx *ctx, const void *buf, uint32_t byte_count, uint64_t LM_UNUSED(byte_offset)) { flash_file_assert(self); assert(ctx); size_t_and_error r = ihex_decoder_writev(&self->ihex, &((struct wr_iovec){.iov_write_from = buf, .iov_len = byte_count}), 1); if (r.size_t == 0 && !ERROR_IS_NULL(r.err)) return ERROR_NEW_ERR(uint32_t, r.err); return ERROR_NEW_VAL(uint32_t, (uint32_t) r.size_t); }