summaryrefslogtreecommitdiff
path: root/flashimg/cpu_main/fs_harness_flash_bin.c
blob: 0fa674adb41673d85f5d824ee446978c45ba3872 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
/* flashimg/cpu_main/fs_harness_flash_bin.c - 9P access to flash storage
 *
 * Copyright (C) 2025  Luke T. Shumaker <lukeshu@lukeshu.com>
 * SPDX-License-Identifier: AGPL-3.0-or-later
 */

#include <string.h>

#include <hardware/flash.h>
#include <hardware/watchdog.h>

#define LOG_NAME FLASH
#include <libmisc/log.h>

#include <util9p/static.h>

#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);
}