xref: /netbsd-src/sys/dev/flash/flash_io.c (revision 832e988b9651e3e836291d95b2e32c8c2347de45)
1 /*	$NetBSD: flash_io.c,v 1.7 2025/01/08 11:39:50 andvar Exp $	*/
2 
3 /*-
4  * Copyright (c) 2011 Department of Software Engineering,
5  *		      University of Szeged, Hungary
6  * Copyright (c) 2011 Adam Hoka <ahoka@NetBSD.org>
7  * All rights reserved.
8  *
9  * This code is derived from software contributed to The NetBSD Foundation
10  * by the Department of Software Engineering, University of Szeged, Hungary
11  *
12  * Redistribution and use in source and binary forms, with or without
13  * modification, are permitted provided that the following conditions
14  * are met:
15  * 1. Redistributions of source code must retain the above copyright
16  *    notice, this list of conditions and the following disclaimer.
17  * 2. Redistributions in binary form must reproduce the above copyright
18  *    notice, this list of conditions and the following disclaimer in the
19  *    documentation and/or other materials provided with the distribution.
20  *
21  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
22  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
23  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
24  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
25  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
26  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
27  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
28  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
29  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31  * SUCH DAMAGE.
32  */
33 
34 #include <sys/cdefs.h>
35 __KERNEL_RCSID(0, "$NetBSD: flash_io.c,v 1.7 2025/01/08 11:39:50 andvar Exp $");
36 
37 #include <sys/param.h>
38 #include <sys/buf.h>
39 #include <sys/bufq.h>
40 #include <sys/kernel.h>
41 #include <sys/kmem.h>
42 #include <sys/kthread.h>
43 #include <sys/mutex.h>
44 #include <sys/sysctl.h>
45 
46 #include <dev/flash/flash.h>
47 #include <dev/flash/flash_io.h>
48 
49 #ifdef FLASH_DEBUG
50 extern int flashdebug;
51 #endif
52 
53 int flash_cachesync_timeout = 1;
54 int flash_cachesync_nodenum;
55 
56 void flash_io_read(struct flash_io *, struct buf *);
57 void flash_io_write(struct flash_io *, struct buf *);
58 void flash_io_done(struct flash_io *, struct buf *, int);
59 int flash_io_cache_write(struct flash_io *, flash_addr_t, struct buf *);
60 void flash_io_cache_sync(struct flash_io *);
61 
62 static int
63 flash_timestamp_diff(struct bintime *bt, struct bintime *b2)
64 {
65 	struct bintime b1 = *bt;
66 	struct timeval tv;
67 
68 	bintime_sub(&b1, b2);
69 	bintime2timeval(&b1, &tv);
70 
71 	return tvtohz(&tv);
72 }
73 
74 static flash_addr_t
75 flash_io_getblock(struct flash_io *fio, struct buf *bp)
76 {
77 	flash_off_t block, last;
78 
79 	/* get block number of first byte */
80 	block = bp->b_rawblkno * DEV_BSIZE / fio->fio_if->erasesize;
81 
82 	/* block of the last bite */
83 	last = (bp->b_rawblkno * DEV_BSIZE + bp->b_resid - 1)
84 	    / fio->fio_if->erasesize;
85 
86 	/* spans through multiple blocks, needs special handling */
87 	if (last != block) {
88 		printf("0x%jx -> 0x%jx\n",
89 		    bp->b_rawblkno * DEV_BSIZE,
90 		    bp->b_rawblkno * DEV_BSIZE + bp->b_resid - 1);
91 		panic("TODO: multiple block write. last: %jd, current: %jd",
92 		    (intmax_t )last, (intmax_t )block);
93 	}
94 
95 	return block;
96 }
97 
98 int
99 flash_sync_thread_init(struct flash_io *fio, device_t dev,
100     struct flash_interface *flash_if)
101 {
102 	int error;
103 
104 	FLDPRINTF(("starting flash io thread\n"));
105 
106 	fio->fio_dev = dev;
107 	fio->fio_if = flash_if;
108 
109 	fio->fio_data = kmem_alloc(fio->fio_if->erasesize, KM_SLEEP);
110 
111 	mutex_init(&fio->fio_lock, MUTEX_DEFAULT, IPL_NONE);
112 	cv_init(&fio->fio_cv, "flashcv");
113 
114 	error = bufq_alloc(&fio->fio_bufq, "fcfs", BUFQ_SORT_RAWBLOCK);
115 	if (error)
116 		goto err_bufq;
117 
118 	fio->fio_exiting = false;
119 	fio->fio_write_pending = false;
120 
121 	/* arrange to allocate the kthread */
122 	error = kthread_create(PRI_NONE, KTHREAD_MUSTJOIN | KTHREAD_MPSAFE,
123 	    NULL, flash_sync_thread, fio, &fio->fio_thread, "flashio");
124 
125 	if (!error)
126 		return 0;
127 
128 	bufq_free(fio->fio_bufq);
129 err_bufq:
130 	cv_destroy(&fio->fio_cv);
131 	mutex_destroy(&fio->fio_lock);
132 	kmem_free(fio->fio_data, fio->fio_if->erasesize);
133 
134 	return error;
135 }
136 
137 void
138 flash_sync_thread_destroy(struct flash_io *fio)
139 {
140 	FLDPRINTF(("stopping flash io thread\n"));
141 
142 	mutex_enter(&fio->fio_lock);
143 
144 	fio->fio_exiting = true;
145 	cv_broadcast(&fio->fio_cv);
146 
147 	mutex_exit(&fio->fio_lock);
148 
149 	kthread_join(fio->fio_thread);
150 
151 	kmem_free(fio->fio_data, fio->fio_if->erasesize);
152 	bufq_free(fio->fio_bufq);
153 	mutex_destroy(&fio->fio_lock);
154 	cv_destroy(&fio->fio_cv);
155 }
156 
157 int
158 flash_io_submit(struct flash_io *fio, struct buf *bp)
159 {
160 	FLDPRINTF(("submitting job to flash io thread: %p\n", bp));
161 
162 	if (__predict_false(fio->fio_exiting)) {
163 		flash_io_done(fio, bp, ENODEV);
164 		return ENODEV;
165 	}
166 
167 	if (BUF_ISREAD(bp)) {
168 		FLDPRINTF(("we have a read job\n"));
169 
170 		mutex_enter(&fio->fio_lock);
171 		if (fio->fio_write_pending)
172 			flash_io_cache_sync(fio);
173 		mutex_exit(&fio->fio_lock);
174 
175 		flash_io_read(fio, bp);
176 	} else {
177 		FLDPRINTF(("we have a write job\n"));
178 
179 		flash_io_write(fio, bp);
180 	}
181 	return 0;
182 }
183 
184 int
185 flash_io_cache_write(struct flash_io *fio, flash_addr_t block, struct buf *bp)
186 {
187 	size_t retlen;
188 	flash_addr_t base, offset;
189 	int error;
190 
191 	KASSERT(mutex_owned(&fio->fio_lock));
192 	KASSERT(fio->fio_if->erasesize != 0);
193 
194 	base = block * fio->fio_if->erasesize;
195 	offset = bp->b_rawblkno * DEV_BSIZE - base;
196 
197 	FLDPRINTF(("io cache write, offset: %jd\n", (intmax_t )offset));
198 
199 	if (!fio->fio_write_pending) {
200 		fio->fio_block = block;
201 		/*
202 		 * fill the cache with data from flash,
203 		 * so we dont have to bother with gaps later
204 		 */
205 		FLDPRINTF(("filling buffer from offset %ju\n", (uintmax_t)base));
206 		error = fio->fio_if->read(fio->fio_dev,
207 		    base, fio->fio_if->erasesize,
208 		    &retlen, fio->fio_data);
209 		FLDPRINTF(("cache filled\n"));
210 
211 		if (error)
212 			return error;
213 
214 		fio->fio_write_pending = true;
215 		/* save creation time for aging */
216 		binuptime(&fio->fio_creation);
217 	}
218 	/* copy data to cache */
219 	memcpy(fio->fio_data + offset, bp->b_data, bp->b_resid);
220 	bufq_put(fio->fio_bufq, bp);
221 
222 	/* update timestamp */
223 	binuptime(&fio->fio_last_write);
224 
225 	return 0;
226 }
227 
228 void
229 flash_io_cache_sync(struct flash_io *fio)
230 {
231 	struct flash_erase_instruction ei;
232 	struct buf *bp;
233 	size_t retlen;
234 	flash_addr_t base;
235 	int error;
236 
237 	KASSERT(mutex_owned(&fio->fio_lock));
238 
239 	if (!fio->fio_write_pending) {
240 		FLDPRINTF(("trying to sync with an invalid buffer\n"));
241 		return;
242 	}
243 
244 	base = fio->fio_block * fio->fio_if->erasesize;
245 
246 	FLDPRINTF(("erasing block at 0x%jx\n", (uintmax_t )base));
247 	ei.ei_addr = base;
248 	ei.ei_len = fio->fio_if->erasesize;
249 	ei.ei_callback = NULL;
250 	error = fio->fio_if->erase(fio->fio_dev, &ei);
251 
252 	if (error) {
253 		aprint_error_dev(fio->fio_dev, "cannot erase flash flash!\n");
254 		goto out;
255 	}
256 
257 	FLDPRINTF(("writing %" PRIu32 " bytes to 0x%jx\n",
258 		fio->fio_if->erasesize, (uintmax_t )base));
259 
260 	error = fio->fio_if->write(fio->fio_dev,
261 	    base, fio->fio_if->erasesize, &retlen, fio->fio_data);
262 
263 	if (error || retlen != fio->fio_if->erasesize) {
264 		aprint_error_dev(fio->fio_dev, "can't sync write cache: %d\n", error);
265 		goto out;
266 	}
267 
268 out:
269 	while ((bp = bufq_get(fio->fio_bufq)) != NULL)
270 		flash_io_done(fio, bp, error);
271 
272 	fio->fio_block = -1;
273 	fio->fio_write_pending = false;
274 }
275 
276 void
277 flash_sync_thread(void * arg)
278 {
279 	struct flash_io *fio = arg;
280 	struct bintime now;
281 
282 	mutex_enter(&fio->fio_lock);
283 
284 	while (!fio->fio_exiting) {
285 		cv_timedwait_sig(&fio->fio_cv, &fio->fio_lock, hz / 4);
286 		if (!fio->fio_write_pending) {
287 			continue;
288 		}
289 		/* see if the cache is older than 3 seconds (safety limit),
290 		 * or if we havent touched the cache since more than 1 ms
291 		 */
292 		binuptime(&now);
293 		if (flash_timestamp_diff(&now, &fio->fio_last_write) > hz / 5) {
294 			FLDPRINTF(("syncing write cache after timeout\n"));
295 			flash_io_cache_sync(fio);
296 		} else if (flash_timestamp_diff(&now, &fio->fio_creation)
297 		    > 3 * hz) {
298 			aprint_error_dev(fio->fio_dev,
299 			    "syncing write cache after 3 sec timeout!\n");
300 			flash_io_cache_sync(fio);
301 		}
302 	}
303 
304 	mutex_exit(&fio->fio_lock);
305 
306 	kthread_exit(0);
307 }
308 
309 void
310 flash_io_read(struct flash_io *fio, struct buf *bp)
311 {
312 	size_t retlen;
313 	flash_addr_t offset;
314 	int error;
315 
316 	FLDPRINTF(("flash io read\n"));
317 
318 	offset = bp->b_rawblkno * DEV_BSIZE;
319 
320 	error = fio->fio_if->read(fio->fio_dev, offset, bp->b_resid,
321 	    &retlen, bp->b_data);
322 
323 	flash_io_done(fio, bp, error);
324 }
325 
326 void
327 flash_io_write(struct flash_io *fio, struct buf *bp)
328 {
329 	flash_addr_t block;
330 
331 	FLDPRINTF(("flash io write\n"));
332 
333 	block = flash_io_getblock(fio, bp);
334 	FLDPRINTF(("write to block %jd\n", (intmax_t )block));
335 
336 	mutex_enter(&fio->fio_lock);
337 
338 	if (fio->fio_write_pending && fio->fio_block != block) {
339 		FLDPRINTF(("writing to new block, syncing caches\n"));
340 		flash_io_cache_sync(fio);
341 	}
342 
343 	flash_io_cache_write(fio, block, bp);
344 
345 	mutex_exit(&fio->fio_lock);
346 }
347 
348 void
349 flash_io_done(struct flash_io *fio, struct buf *bp, int error)
350 {
351 	FLDPRINTF(("io done: %p\n", bp));
352 
353 	if (error == 0)
354 		bp->b_resid = 0;
355 
356 	bp->b_error = error;
357 	biodone(bp);
358 }
359 
360 static int
361 sysctl_flash_verify(SYSCTLFN_ARGS)
362 {
363 	int error, t;
364 	struct sysctlnode node;
365 
366 	node = *rnode;
367 	t = *(int *)rnode->sysctl_data;
368 	node.sysctl_data = &t;
369 	error = sysctl_lookup(SYSCTLFN_CALL(&node));
370 	if (error || newp == NULL)
371 		return error;
372 
373 	if (node.sysctl_num == flash_cachesync_nodenum) {
374 		if (t <= 0 || t > 60)
375 			return EINVAL;
376 	} else {
377 		return EINVAL;
378 	}
379 
380 	*(int *)rnode->sysctl_data = t;
381 
382 	return 0;
383 }
384 
385 SYSCTL_SETUP(sysctl_flash, "sysctl flash subtree setup")
386 {
387 	int rc, flash_root_num;
388 	const struct sysctlnode *node;
389 
390 	if ((rc = sysctl_createv(clog, 0, NULL, &node,
391 	    CTLFLAG_PERMANENT, CTLTYPE_NODE, "flash",
392 	    SYSCTL_DESCR("FLASH driver controls"),
393 	    NULL, 0, NULL, 0, CTL_HW, CTL_CREATE, CTL_EOL)) != 0) {
394 		goto error;
395 	}
396 
397 	flash_root_num = node->sysctl_num;
398 
399 	if ((rc = sysctl_createv(clog, 0, NULL, &node,
400 	    CTLFLAG_PERMANENT|CTLFLAG_READWRITE,
401 	    CTLTYPE_INT, "cache_sync_timeout",
402 	    SYSCTL_DESCR("FLASH write cache sync timeout in seconds"),
403 	    sysctl_flash_verify, 0, &flash_cachesync_timeout,
404 	    0, CTL_HW, flash_root_num, CTL_CREATE,
405 	    CTL_EOL)) != 0) {
406 		goto error;
407 	}
408 
409 	flash_cachesync_nodenum = node->sysctl_num;
410 
411 	return;
412 
413 error:
414 	aprint_error("%s: sysctl_createv failed (rc = %d)\n", __func__, rc);
415 }
416