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