1 /*
2 * Copyright (c) 2009-2015 Travis Geiselbrecht
3 *
4 * Use of this source code is governed by a MIT-style
5 * license that can be found in the LICENSE file or at
6 * https://opensource.org/licenses/MIT
7 */
8 #include <lib/bio.h>
9
10 #include <stdlib.h>
11 #include <lk/debug.h>
12 #include <lk/trace.h>
13 #include <lk/err.h>
14 #include <string.h>
15 #include <assert.h>
16 #include <lk/list.h>
17 #include <lk/pow2.h>
18 #include <kernel/mutex.h>
19 #include <lk/init.h>
20 #include <arch/atomic.h>
21
22 #define LOCAL_TRACE 0
23
24 static struct {
25 struct list_node list;
26 mutex_t lock;
27 } bdevs = {
28 .list = LIST_INITIAL_VALUE(bdevs.list),
29 .lock = MUTEX_INITIAL_VALUE(bdevs.lock),
30 };
31
32 /* default implementation is to use the read_block hook to 'deblock' the device */
bio_default_read(struct bdev * dev,void * _buf,off_t offset,size_t len)33 static ssize_t bio_default_read(struct bdev *dev, void *_buf, off_t offset, size_t len) {
34 uint8_t *buf = (uint8_t *)_buf;
35 ssize_t bytes_read = 0;
36 bnum_t block;
37 ssize_t err = 0;
38 STACKBUF_DMA_ALIGN(temp, dev->block_size); // temporary buffer for partial block transfers
39
40 /* find the starting block */
41 block = divpow2(offset, dev->block_shift);
42
43 LTRACEF("buf %p, offset %lld, block %u, len %zd\n", buf, offset, block, len);
44 /* handle partial first block */
45 if ((offset % dev->block_size) != 0) {
46 /* read in the block */
47 err = bio_read_block(dev, temp, block, 1);
48 if (err < 0) {
49 goto err;
50 } else if ((size_t)err != dev->block_size) {
51 err = ERR_IO;
52 goto err;
53 }
54
55 /* copy what we need */
56 size_t block_offset = offset % dev->block_size;
57 size_t tocopy = MIN(dev->block_size - block_offset, len);
58 memcpy(buf, temp + block_offset, tocopy);
59
60 /* increment our buffers */
61 buf += tocopy;
62 len -= tocopy;
63 bytes_read += tocopy;
64 block++;
65 }
66
67 LTRACEF("buf %p, block %u, len %zd\n", buf, block, len);
68
69 // If the device requires alignment AND our buffer is not alread aligned.
70 bool requires_alignment =
71 (dev->flags & BIO_FLAG_CACHE_ALIGNED_READS) &&
72 (IS_ALIGNED((size_t)buf, CACHE_LINE) == false);
73 /* handle middle blocks */
74 if (requires_alignment) {
75 while (len >= dev->block_size) {
76 /* do the middle reads */
77 err = bio_read_block(dev, temp, block, 1);
78 if (err < 0) {
79 goto err;
80 } else if ((size_t)err != dev->block_size) {
81 err = ERR_IO;
82 goto err;
83 }
84 memcpy(buf, temp, dev->block_size);
85
86 buf += dev->block_size;
87 len -= dev->block_size;
88 bytes_read += dev->block_size;
89 block++;
90 }
91 } else {
92 uint32_t num_blocks = divpow2(len, dev->block_shift);
93 err = bio_read_block(dev, buf, block, num_blocks);
94 if (err < 0) {
95 goto err;
96 } else if ((size_t)err != dev->block_size * num_blocks) {
97 err = ERR_IO;
98 goto err;
99 }
100 buf += err;
101 len -= err;
102 bytes_read += err;
103 block += num_blocks;
104 }
105
106 LTRACEF("buf %p, block %u, len %zd\n", buf, block, len);
107 /* handle partial last block */
108 if (len > 0) {
109 /* read the block */
110 err = bio_read_block(dev, temp, block, 1);
111 if (err < 0) {
112 goto err;
113 } else if ((size_t)err != dev->block_size) {
114 err = ERR_IO;
115 goto err;
116 }
117
118 /* copy the partial block from our temp buffer */
119 memcpy(buf, temp, len);
120
121 bytes_read += len;
122 }
123
124 err:
125 /* return error or bytes read */
126 return (err >= 0) ? bytes_read : err;
127 }
128
bio_default_write(struct bdev * dev,const void * _buf,off_t offset,size_t len)129 static ssize_t bio_default_write(struct bdev *dev, const void *_buf, off_t offset, size_t len) {
130 const uint8_t *buf = (const uint8_t *)_buf;
131 ssize_t bytes_written = 0;
132 bnum_t block;
133 ssize_t err = 0;
134 STACKBUF_DMA_ALIGN(temp, dev->block_size); // temporary buffer for partial block transfers
135
136 /* find the starting block */
137 block = divpow2(offset, dev->block_shift);
138
139 LTRACEF("buf %p, offset %lld, block %u, len %zd\n", buf, offset, block, len);
140 /* handle partial first block */
141 if ((offset % dev->block_size) != 0) {
142 /* read in the block */
143 err = bio_read_block(dev, temp, block, 1);
144 if (err < 0) {
145 goto err;
146 } else if ((size_t)err != dev->block_size) {
147 err = ERR_IO;
148 goto err;
149 }
150
151 /* copy what we need */
152 size_t block_offset = offset % dev->block_size;
153 size_t tocopy = MIN(dev->block_size - block_offset, len);
154 memcpy(temp + block_offset, buf, tocopy);
155
156 /* write it back out */
157 err = bio_write_block(dev, temp, block, 1);
158 if (err < 0) {
159 goto err;
160 } else if ((size_t)err != dev->block_size) {
161 err = ERR_IO;
162 goto err;
163 }
164
165 /* increment our buffers */
166 buf += tocopy;
167 len -= tocopy;
168 bytes_written += tocopy;
169 block++;
170 }
171
172 LTRACEF("buf %p, block %u, len %zd\n", buf, block, len);
173
174 // If the device requires alignment AND our buffer is not alread aligned.
175 bool requires_alignment =
176 (dev->flags & BIO_FLAG_CACHE_ALIGNED_WRITES) &&
177 (IS_ALIGNED((size_t)buf, CACHE_LINE) == false);
178
179 /* handle middle blocks */
180 if (requires_alignment) {
181 while (len >= dev->block_size) {
182 /* do the middle reads */
183 memcpy(temp, buf, dev->block_size);
184 err = bio_write_block(dev, temp, block, 1);
185 if (err < 0) {
186 goto err;
187 } else if ((size_t)err != dev->block_size) {
188 err = ERR_IO;
189 goto err;
190 }
191
192 buf += dev->block_size;
193 len -= dev->block_size;
194 bytes_written += dev->block_size;
195 block++;
196 }
197 } else {
198 uint32_t block_count = divpow2(len, dev->block_shift);
199 err = bio_write_block(dev, buf, block, block_count);
200 if (err < 0) {
201 goto err;
202 } else if ((size_t)err != dev->block_size * block_count) {
203 err = ERR_IO;
204 goto err;
205 }
206
207 DEBUG_ASSERT((size_t)err == (block_count * dev->block_size));
208
209 buf += err;
210 len -= err;
211 bytes_written += err;
212 block += block_count;
213 }
214
215 LTRACEF("buf %p, block %u, len %zd\n", buf, block, len);
216 /* handle partial last block */
217 if (len > 0) {
218 /* read the block */
219 err = bio_read_block(dev, temp, block, 1);
220 if (err < 0) {
221 goto err;
222 } else if ((size_t)err != dev->block_size) {
223 err = ERR_IO;
224 goto err;
225 }
226
227 /* copy the partial block from our temp buffer */
228 memcpy(temp, buf, len);
229
230 /* write it back out */
231 err = bio_write_block(dev, temp, block, 1);
232 if (err < 0) {
233 goto err;
234 } else if ((size_t)err != dev->block_size) {
235 err = ERR_IO;
236 goto err;
237 }
238
239 bytes_written += len;
240 }
241
242 err:
243 /* return error or bytes written */
244 return (err >= 0) ? bytes_written : err;
245 }
246
bio_default_erase(struct bdev * dev,off_t offset,size_t len)247 static ssize_t bio_default_erase(struct bdev *dev, off_t offset, size_t len) {
248 /* default erase operation is to just write zeros over the device */
249 STACKBUF_DMA_ALIGN(erase_buf, dev->block_size);
250
251 memset(erase_buf, dev->erase_byte, dev->block_size);
252
253 ssize_t erased = 0;
254 size_t remaining = len;
255 off_t pos = offset;
256 while (remaining > 0) {
257 size_t towrite = MIN(remaining, dev->block_size);
258
259 ssize_t written = bio_write(dev, erase_buf, pos, towrite);
260 if (written < 0)
261 return written;
262
263 erased += written;
264 pos += written;
265 remaining -= written;
266
267 if ((size_t)written < towrite)
268 break;
269 }
270
271 return erased;
272 }
273
bio_default_read_block(struct bdev * dev,void * buf,bnum_t block,uint count)274 static ssize_t bio_default_read_block(struct bdev *dev, void *buf, bnum_t block, uint count) {
275 return ERR_NOT_SUPPORTED;
276 }
277
bio_default_write_block(struct bdev * dev,const void * buf,bnum_t block,uint count)278 static ssize_t bio_default_write_block(struct bdev *dev, const void *buf, bnum_t block, uint count) {
279 return ERR_NOT_SUPPORTED;
280 }
281
bdev_inc_ref(bdev_t * dev)282 static void bdev_inc_ref(bdev_t *dev) {
283 LTRACEF("Add ref \"%s\" %d -> %d\n", dev->name, dev->ref, dev->ref + 1);
284 atomic_add(&dev->ref, 1);
285 }
286
bdev_dec_ref(bdev_t * dev)287 static void bdev_dec_ref(bdev_t *dev) {
288 int oldval = atomic_add(&dev->ref, -1);
289
290 LTRACEF("Dec ref \"%s\" %d -> %d\n", dev->name, oldval, dev->ref);
291
292 if (oldval == 1) {
293 // last ref, remove it
294 DEBUG_ASSERT(!list_in_list(&dev->node));
295
296 TRACEF("last ref, removing (%s)\n", dev->name);
297
298 // call the close hook if it exists
299 if (dev->close)
300 dev->close(dev);
301
302 free(dev->name);
303 }
304 }
305
bio_trim_range(const bdev_t * dev,off_t offset,size_t len)306 size_t bio_trim_range(const bdev_t *dev, off_t offset, size_t len) {
307 /* range check */
308 if (offset < 0)
309 return 0;
310 if (offset >= dev->total_size)
311 return 0;
312 if (len == 0)
313 return 0;
314 if ((off_t)(offset + len) > dev->total_size)
315 len = dev->total_size - offset;
316
317 return len;
318 }
319
bio_trim_block_range(const bdev_t * dev,bnum_t block,uint count)320 uint bio_trim_block_range(const bdev_t *dev, bnum_t block, uint count) {
321 if (block > dev->block_count)
322 return 0;
323 if (count == 0)
324 return 0;
325 if (block + count > dev->block_count)
326 count = dev->block_count - block;
327
328 return count;
329 }
330
bio_open(const char * name)331 bdev_t *bio_open(const char *name) {
332 bdev_t *bdev = NULL;
333
334 LTRACEF(" '%s'\n", name);
335
336 /* see if it's in our list */
337 bdev_t *entry;
338 mutex_acquire(&bdevs.lock);
339 list_for_every_entry(&bdevs.list, entry, bdev_t, node) {
340 DEBUG_ASSERT(entry->ref > 0);
341 if (!strcmp(entry->name, name)) {
342 bdev = entry;
343 bdev_inc_ref(bdev);
344 break;
345 }
346 }
347 mutex_release(&bdevs.lock);
348
349 return bdev;
350 }
351
bio_close(bdev_t * dev)352 void bio_close(bdev_t *dev) {
353 DEBUG_ASSERT(dev);
354 LTRACEF(" '%s'\n", dev->name);
355 bdev_dec_ref(dev);
356 }
357
bio_read(bdev_t * dev,void * buf,off_t offset,size_t len)358 ssize_t bio_read(bdev_t *dev, void *buf, off_t offset, size_t len) {
359 LTRACEF("dev '%s', buf %p, offset %lld, len %zd\n", dev->name, buf, offset, len);
360
361 DEBUG_ASSERT(dev && dev->ref > 0);
362 DEBUG_ASSERT(buf);
363
364 /* range check */
365 len = bio_trim_range(dev, offset, len);
366 if (len == 0)
367 return 0;
368
369 return dev->read(dev, buf, offset, len);
370 }
371
bio_read_async(bdev_t * dev,void * buf,off_t offset,size_t len,bio_async_callback_t callback,void * callback_context)372 status_t bio_read_async(bdev_t *dev, void *buf, off_t offset, size_t len,
373 bio_async_callback_t callback, void *callback_context) {
374 LTRACEF("dev '%s', buf %p, offset %lld, len %zd\n", dev->name, buf, offset,
375 len);
376
377 DEBUG_ASSERT(dev && dev->ref > 0);
378 DEBUG_ASSERT(buf);
379 if (dev->read_async == NULL) {
380 return ERR_NOT_SUPPORTED;
381 }
382
383 /* range check */
384 len = bio_trim_range(dev, offset, len);
385 if (len == 0) {
386 return 0;
387 }
388
389 return dev->read_async(dev, buf, offset, len, callback, callback_context);
390 }
391
bio_read_block(bdev_t * dev,void * buf,bnum_t block,uint count)392 ssize_t bio_read_block(bdev_t *dev, void *buf, bnum_t block, uint count) {
393 LTRACEF("dev '%s', buf %p, block %d, count %u\n", dev->name, buf, block, count);
394
395 DEBUG_ASSERT(dev && dev->ref > 0);
396 DEBUG_ASSERT(buf);
397
398 /* range check */
399 count = bio_trim_block_range(dev, block, count);
400 if (count == 0)
401 return 0;
402
403 return dev->read_block(dev, buf, block, count);
404 }
405
bio_write(bdev_t * dev,const void * buf,off_t offset,size_t len)406 ssize_t bio_write(bdev_t *dev, const void *buf, off_t offset, size_t len) {
407 LTRACEF("dev '%s', buf %p, offset %lld, len %zd\n", dev->name, buf, offset, len);
408
409 DEBUG_ASSERT(dev && dev->ref > 0);
410 DEBUG_ASSERT(buf);
411
412 /* range check */
413 len = bio_trim_range(dev, offset, len);
414 if (len == 0)
415 return 0;
416
417 return dev->write(dev, buf, offset, len);
418 }
419
bio_write_block(bdev_t * dev,const void * buf,bnum_t block,uint count)420 ssize_t bio_write_block(bdev_t *dev, const void *buf, bnum_t block, uint count) {
421 LTRACEF("dev '%s', buf %p, block %d, count %u\n", dev->name, buf, block, count);
422
423 DEBUG_ASSERT(dev && dev->ref > 0);
424 DEBUG_ASSERT(buf);
425
426 /* range check */
427 count = bio_trim_block_range(dev, block, count);
428 if (count == 0)
429 return 0;
430
431 return dev->write_block(dev, buf, block, count);
432 }
433
bio_erase(bdev_t * dev,off_t offset,size_t len)434 ssize_t bio_erase(bdev_t *dev, off_t offset, size_t len) {
435 LTRACEF("dev '%s', offset %lld, len %zd\n", dev->name, offset, len);
436
437 DEBUG_ASSERT(dev && dev->ref > 0);
438
439 /* range check */
440 len = bio_trim_range(dev, offset, len);
441 if (len == 0)
442 return 0;
443
444 return dev->erase(dev, offset, len);
445 }
446
bio_ioctl(bdev_t * dev,int request,void * argp)447 int bio_ioctl(bdev_t *dev, int request, void *argp) {
448 LTRACEF("dev '%s', request %08x, argp %p\n", dev->name, request, argp);
449
450 if (dev->ioctl == NULL) {
451 return ERR_NOT_SUPPORTED;
452 } else {
453 return dev->ioctl(dev, request, argp);
454 }
455 }
456
bio_initialize_bdev(bdev_t * dev,const char * name,size_t block_size,bnum_t block_count,size_t geometry_count,const bio_erase_geometry_info_t * geometry,const uint32_t flags)457 void bio_initialize_bdev(bdev_t *dev,
458 const char *name,
459 size_t block_size,
460 bnum_t block_count,
461 size_t geometry_count,
462 const bio_erase_geometry_info_t *geometry,
463 const uint32_t flags) {
464 DEBUG_ASSERT(dev);
465 DEBUG_ASSERT(name);
466
467 // Block size must be finite powers of 2
468 DEBUG_ASSERT(block_size && ispow2(block_size));
469
470 list_clear_node(&dev->node);
471 dev->name = strdup(name);
472 dev->block_size = block_size;
473 dev->block_count = block_count;
474 dev->block_shift = log2_uint(block_size);
475 dev->total_size = (off_t)block_count << dev->block_shift;
476 dev->geometry_count = geometry_count;
477 dev->geometry = geometry;
478 dev->erase_byte = 0;
479 dev->ref = 0;
480 dev->flags = flags;
481
482 #if DEBUG
483 // If we have been supplied information about our erase geometry, sanity
484 // check it in debug builds.
485 if (geometry_count && geometry) {
486 for (size_t i = 0; i < geometry_count; ++i) {
487 bio_erase_geometry_info_t *info = geometry + i;
488
489 // Erase sizes must be powers of two and agree with the supplied erase shift.
490 DEBUG_ASSERT(info->erase_size);
491 DEBUG_ASSERT(info->erase_size == ((size_t)1 << info->erase_shift));
492
493 info->start = desc->start;
494 info->erase_size = desc->erase_size;
495 info->erase_shift = log2_uint(desc->erase_size);
496 info->size = ((off_t)desc->block_count) << desc->block_size;
497
498 // Make sure that region is aligned on both a program and erase block boundary.
499 DEBUG_ASSERT(!(info->start & (((off_t)1 << info->block_shift) - 1)));
500 DEBUG_ASSERT(!(info->start & (((off_t)1 << info->erase_shift) - 1)));
501
502 // Make sure that region's length is an integral multiple of both the
503 // program and erase block size.
504 DEBUG_ASSERT(!(info->size & (((off_t)1 << dev->block_shift) - 1)));
505 DEBUG_ASSERT(!(info->size & (((off_t)1 << info->erase_shift) - 1)));
506 }
507
508 // Make sure that none of the regions overlap each other and that they are
509 // listed in ascending order.
510 for (size_t i = 0; (i + 1) < geometry_count; ++i) {
511 bio_geometry_info_t *r1 = dev->geometry + i;
512 bio_geometry_info_t *r2 = dev->geometry + i + 1;
513 DEBUG_ASSERT(r1->start <= r2->start);
514
515 for (size_t j = (i + 1); j < geometry_count; ++j) {
516 bio_geometry_info_t *r2 = dev->geometry + j;
517 DEBUG_ASSERT(!bio_does_overlap(r1->start, r1->size, r2->start, r2->size));
518 }
519 }
520 }
521 #endif
522
523 /* set up the default hooks, the sub driver should override the block operations at least */
524 dev->read = bio_default_read;
525 dev->read_block = bio_default_read_block;
526 dev->write = bio_default_write;
527 dev->write_block = bio_default_write_block;
528 dev->erase = bio_default_erase;
529 dev->close = NULL;
530 }
531
bio_register_device(bdev_t * dev)532 void bio_register_device(bdev_t *dev) {
533 DEBUG_ASSERT(dev);
534
535 LTRACEF(" '%s'\n", dev->name);
536
537 bdev_inc_ref(dev);
538
539 mutex_acquire(&bdevs.lock);
540 list_add_tail(&bdevs.list, &dev->node);
541 mutex_release(&bdevs.lock);
542 }
543
bio_unregister_device(bdev_t * dev)544 void bio_unregister_device(bdev_t *dev) {
545 DEBUG_ASSERT(dev);
546
547 LTRACEF(" '%s'\n", dev->name);
548
549 // remove it from the list
550 mutex_acquire(&bdevs.lock);
551 list_delete(&dev->node);
552 mutex_release(&bdevs.lock);
553
554 bdev_dec_ref(dev); // remove the ref the list used to have
555 }
556
bio_iter_devices(bool (* callback)(void *,bdev_t *),void * cookie)557 void bio_iter_devices(bool (*callback)(void *, bdev_t *), void *cookie) {
558 bdev_t *entry = NULL;
559 mutex_acquire(&bdevs.lock);
560 list_for_every_entry(&bdevs.list, entry, bdev_t, node) {
561 if (!callback(cookie, entry)) {
562 break;
563 }
564 }
565 mutex_release(&bdevs.lock);
566 }
567
bio_dump_devices(void)568 void bio_dump_devices(void) {
569 printf("block devices:\n");
570 bdev_t *entry;
571 mutex_acquire(&bdevs.lock);
572 list_for_every_entry(&bdevs.list, entry, bdev_t, node) {
573
574 printf("\t%s, size %lld, bsize %zd, ref %d",
575 entry->name, entry->total_size, entry->block_size, entry->ref);
576
577 if (!entry->geometry_count || !entry->geometry) {
578 printf(" (no erase geometry)\n");
579 } else {
580 for (size_t i = 0; i < entry->geometry_count; ++i) {
581 const bio_erase_geometry_info_t *geo = entry->geometry + i;
582 printf("\n\t\terase_region[%zu] : start %lld size %lld erase size %zu",
583 i, geo->start, geo->size, geo->erase_size);
584
585 }
586 }
587
588 printf("\n");
589 }
590 mutex_release(&bdevs.lock);
591 }
592