First version
[3rdparty/ote_partner/tlk.git] / lib / bio / bio.c
1 /*
2  * Copyright (c) 2009 Travis Geiselbrecht
3  *
4  * Permission is hereby granted, free of charge, to any person obtaining
5  * a copy of this software and associated documentation files
6  * (the "Software"), to deal in the Software without restriction,
7  * including without limitation the rights to use, copy, modify, merge,
8  * publish, distribute, sublicense, and/or sell copies of the Software,
9  * and to permit persons to whom the Software is furnished to do so,
10  * subject to the following conditions:
11  *
12  * The above copyright notice and this permission notice shall be
13  * included in all copies or substantial portions of the Software.
14  *
15  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16  * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
18  * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
19  * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
20  * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
21  * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22  */
23 #include <stdlib.h>
24 #include <debug.h>
25 #include <err.h>
26 #include <string.h>
27 #include <assert.h>
28 #include <list.h>
29 #include <lib/bio.h>
30 #include <kernel/mutex.h>
31
32 #define LOCAL_TRACE 0
33
34 struct bdev_struct {
35         struct list_node list;
36         mutex_t lock;
37 };
38
39 static struct bdev_struct *bdevs;
40
41 /* default implementation is to use the read_block hook to 'deblock' the device */
42 static ssize_t bio_default_read(struct bdev *dev, void *_buf, off_t offset, size_t len)
43 {
44         uint8_t *buf = (uint8_t *)_buf;
45         ssize_t bytes_read = 0;
46         bnum_t block;
47         int err = 0;
48         STACKBUF_DMA_ALIGN(temp, dev->block_size); // temporary buffer for partial block transfers
49
50         /* find the starting block */
51         block = offset / dev->block_size;
52
53         LTRACEF("buf %p, offset %lld, block %u, len %zd\n", buf, offset, block, len);
54         /* handle partial first block */
55         if ((offset % dev->block_size) != 0) {
56                 /* read in the block */
57                 err = bio_read_block(dev, temp, block, 1);
58                 if (err < 0)
59                         goto err;
60
61                 /* copy what we need */
62                 size_t block_offset = offset % dev->block_size;
63                 size_t tocopy = MIN(dev->block_size - block_offset, len);
64                 memcpy(buf, temp + block_offset, tocopy);
65
66                 /* increment our buffers */
67                 buf += tocopy;
68                 len -= tocopy;
69                 bytes_read += tocopy;
70                 block++;
71         }
72
73         LTRACEF("buf %p, block %u, len %zd\n", buf, block, len);
74         /* handle middle blocks */
75         if (len >= dev->block_size) {
76                 /* do the middle reads */
77                 size_t block_count = len / dev->block_size;
78                 err = bio_read_block(dev, buf, block, block_count);
79                 if (err < 0)
80                         goto err;
81
82                 /* increment our buffers */
83                 size_t bytes = block_count * dev->block_size;
84                 DEBUG_ASSERT(bytes <= len);
85
86                 buf += bytes;
87                 len -= bytes;
88                 bytes_read += bytes;
89                 block += block_count;
90         }
91
92         LTRACEF("buf %p, block %u, len %zd\n", buf, block, len);
93         /* handle partial last block */
94         if (len > 0) {
95                 /* read the block */
96                 err = bio_read_block(dev, temp, block, 1);
97                 if (err < 0)
98                         goto err;
99
100                 /* copy the partial block from our temp buffer */
101                 memcpy(buf, temp, len);
102
103                 bytes_read += len;
104         }
105
106 err:
107         /* return error or bytes read */
108         return (err >= 0) ? bytes_read : err;
109 }
110
111 static ssize_t bio_default_write(struct bdev *dev, const void *_buf, off_t offset, size_t len)
112 {
113         const uint8_t *buf = (const uint8_t *)_buf;
114         ssize_t bytes_written = 0;
115         bnum_t block;
116         int err = 0;
117         STACKBUF_DMA_ALIGN(temp, dev->block_size); // temporary buffer for partial block transfers
118
119         /* find the starting block */
120         block = offset / dev->block_size;
121
122         LTRACEF("buf %p, offset %lld, block %u, len %zd\n", buf, offset, block, len);
123         /* handle partial first block */
124         if ((offset % dev->block_size) != 0) {
125                 /* read in the block */
126                 err = bio_read_block(dev, temp, block, 1);
127                 if (err < 0)
128                         goto err;
129
130                 /* copy what we need */
131                 size_t block_offset = offset % dev->block_size;
132                 size_t tocopy = MIN(dev->block_size - block_offset, len);
133                 memcpy(temp + block_offset, buf, tocopy);
134
135                 /* write it back out */
136                 err = bio_write_block(dev, temp, block, 1);
137                 if (err < 0)
138                         goto err;
139
140                 /* increment our buffers */
141                 buf += tocopy;
142                 len -= tocopy;
143                 bytes_written += tocopy;
144                 block++;
145         }
146
147         LTRACEF("buf %p, block %u, len %zd\n", buf, block, len);
148         /* handle middle blocks */
149         if (len >= dev->block_size) {
150                 /* do the middle writes */
151                 size_t block_count = len / dev->block_size;
152                 err = bio_write_block(dev, buf, block, block_count);
153                 if (err < 0)
154                         goto err;
155
156                 /* increment our buffers */
157                 size_t bytes = block_count * dev->block_size;
158                 DEBUG_ASSERT(bytes <= len);
159
160                 buf += bytes;
161                 len -= bytes;
162                 bytes_written += bytes;
163                 block += block_count;
164         }
165
166         LTRACEF("buf %p, block %u, len %zd\n", buf, block, len);
167         /* handle partial last block */
168         if (len > 0) {
169                 /* read the block */
170                 err = bio_read_block(dev, temp, block, 1);
171                 if (err < 0)
172                         goto err;
173
174                 /* copy the partial block from our temp buffer */
175                 memcpy(temp, buf, len);
176
177                 /* write it back out */
178                 err = bio_write_block(dev, temp, block, 1);
179                 if (err < 0)
180                         goto err;
181
182                 bytes_written += len;
183         }
184
185 err:
186         /* return error or bytes written */
187         return (err >= 0) ? bytes_written : err;
188 }
189
190 static ssize_t bio_default_erase(struct bdev *dev, off_t offset, size_t len)
191 {
192         /* default erase operation is to just write zeros over the device */
193 #define ERASE_BUF_SIZE 4096
194         uint8_t *zero_buf;
195
196         zero_buf = calloc(1, ERASE_BUF_SIZE);
197
198         size_t remaining = len;
199         off_t pos = offset;
200         while (remaining > 0) {
201                 ssize_t towrite = MIN(remaining, ERASE_BUF_SIZE);
202
203                 ssize_t written = bio_write(dev, zero_buf, pos, towrite);
204                 if (written < 0)
205                         return pos;
206
207                 pos += written;
208                 remaining -= written;
209
210                 if (written < towrite)
211                         return pos;
212         }
213
214         return len;
215 }
216
217 static ssize_t bio_default_read_block(struct bdev *dev, void *buf, bnum_t block, uint count)
218 {
219         panic("%s no reasonable default operation\n", __PRETTY_FUNCTION__);
220 }
221
222 static ssize_t bio_default_write_block(struct bdev *dev, const void *buf, bnum_t block, uint count)
223 {
224         panic("%s no reasonable default operation\n", __PRETTY_FUNCTION__);
225 }
226
227 static void bdev_inc_ref(bdev_t *dev)
228 {
229         atomic_add(&dev->ref, 1);
230 }
231
232 static void bdev_dec_ref(bdev_t *dev)
233 {
234         int oldval = atomic_add(&dev->ref, -1);
235         if (oldval == 1) {
236                 // last ref, remove it
237                 DEBUG_ASSERT(!list_in_list(&dev->node));
238
239                 TRACEF("last ref, removing (%s)\n", dev->name);
240
241                 // call the close hook if it exists
242                 if (dev->close)
243                         dev->close(dev);
244
245                 free(dev->name);
246                 free(dev);
247         }
248 }
249
250 bdev_t *bio_open(const char *name)
251 {
252         bdev_t *bdev = NULL;
253
254         /* see if it's in our list */
255         bdev_t *entry;
256         mutex_acquire(&bdevs->lock);
257         list_for_every_entry(&bdevs->list, entry, bdev_t, node) {
258                 DEBUG_ASSERT(entry->ref > 0);
259                 if (!strcmp(entry->name, name)) {
260                         bdev = entry;
261                         bdev_inc_ref(bdev);
262                         break;
263                 }
264         }
265         mutex_release(&bdevs->lock);
266
267         return bdev;
268 }
269
270 void bio_close(bdev_t *dev)
271 {
272         DEBUG_ASSERT(dev);
273
274         bdev_dec_ref(dev);
275 }
276
277 ssize_t bio_read(bdev_t *dev, void *buf, off_t offset, size_t len)
278 {
279         LTRACEF("dev '%s', buf %p, offset %lld, len %zd\n", dev->name, buf, offset, len);
280
281         DEBUG_ASSERT(dev->ref > 0);     
282
283         /* range check */
284         if (offset < 0)
285                 return -1;
286         if (offset >= dev->size)
287                 return 0;
288         if (len == 0)
289                 return 0;
290         if (offset + len > dev->size)
291                 len = dev->size - offset;
292
293         return dev->read(dev, buf, offset, len);
294 }
295
296 ssize_t bio_read_block(bdev_t *dev, void *buf, bnum_t block, uint count)
297 {
298         LTRACEF("dev '%s', buf %p, block %d, count %u\n", dev->name, buf, block, count);
299                 
300         DEBUG_ASSERT(dev->ref > 0);
301
302         /* range check */
303         if (block > dev->block_count)
304                 return 0;
305         if (count == 0)
306                 return 0;
307         if (block + count > dev->block_count)
308                 count = dev->block_count - block;
309
310         return dev->read_block(dev, buf, block, count);
311 }
312
313 ssize_t bio_write(bdev_t *dev, const void *buf, off_t offset, size_t len)
314 {
315         LTRACEF("dev '%s', buf %p, offset %lld, len %zd\n", dev->name, buf, offset, len);
316                 
317         DEBUG_ASSERT(dev->ref > 0);
318
319         /* range check */
320         if (offset < 0)
321                 return -1;
322         if (offset >= dev->size)
323                 return 0;
324         if (len == 0)
325                 return 0;
326         if (offset + len > dev->size)
327                 len = dev->size - offset;
328
329         return dev->write(dev, buf, offset, len);
330 }
331
332 ssize_t bio_write_block(bdev_t *dev, const void *buf, bnum_t block, uint count)
333 {
334         LTRACEF("dev '%s', buf %p, block %d, count %u\n", dev->name, buf, block, count);
335
336         DEBUG_ASSERT(dev->ref > 0);
337
338         /* range check */
339         if (block > dev->block_count)
340                 return 0;
341         if (count == 0)
342                 return 0;
343         if (block + count > dev->block_count)
344                 count = dev->block_count - block;
345
346         return dev->write_block(dev, buf, block, count);
347 }
348
349 ssize_t bio_erase(bdev_t *dev, off_t offset, size_t len)
350 {
351         LTRACEF("dev '%s', offset %lld, len %zd\n", dev->name, offset, len);
352                 
353         DEBUG_ASSERT(dev->ref > 0);
354
355         /* range check */
356         if (offset < 0)
357                 return -1;
358         if (offset >= dev->size)
359                 return 0;
360         if (len == 0)
361                 return 0;
362         if (offset + len > dev->size)
363                 len = dev->size - offset;
364
365         return dev->erase(dev, offset, len);
366 }
367
368 int bio_ioctl(bdev_t *dev, int request, void *argp)
369 {
370         LTRACEF("dev '%s', request %08x, argp %p\n", dev->name, request, argp);
371
372         if (dev->ioctl == NULL) {
373                 return ERR_NOT_SUPPORTED;
374         } else {
375                 return dev->ioctl(dev, request, argp);
376         }
377 }
378
379 void bio_initialize_bdev(bdev_t *dev, const char *name, size_t block_size, bnum_t block_count)
380 {
381         DEBUG_ASSERT(dev);
382         DEBUG_ASSERT(name);
383         DEBUG_ASSERT(block_size == 512); // XXX can only deal with 512 for now
384
385         list_clear_node(&dev->node);
386         dev->name = strdup(name);
387         dev->block_size = block_size;
388         dev->block_count = block_count;
389         dev->size = (off_t)block_count * block_size;
390         dev->ref = 0;
391
392         /* set up the default hooks, the sub driver should override the block operations at least */
393         dev->read = bio_default_read;
394         dev->read_block = bio_default_read_block;
395         dev->write = bio_default_write;
396         dev->write_block = bio_default_write_block;
397         dev->erase = bio_default_erase;
398         dev->close = NULL;
399 }
400
401 void bio_register_device(bdev_t *dev)
402 {
403         DEBUG_ASSERT(dev);
404
405         LTRACEF(" '%s'\n", dev->name);
406
407         bdev_inc_ref(dev);
408
409         mutex_acquire(&bdevs->lock);
410         list_add_head(&bdevs->list, &dev->node);
411         mutex_release(&bdevs->lock);
412 }
413
414 void bio_unregister_device(bdev_t *dev)
415 {
416         DEBUG_ASSERT(dev);
417
418         LTRACEF(" '%s'\n", dev->name);
419
420         // remove it from the list
421         mutex_acquire(&bdevs->lock);
422         list_delete(&dev->node);
423         mutex_release(&bdevs->lock);
424
425         bdev_dec_ref(dev); // remove the ref the list used to have
426 }
427
428 void bio_dump_devices(void)
429 {
430         printf("block devices:\n");
431         bdev_t *entry;
432         mutex_acquire(&bdevs->lock);
433         list_for_every_entry(&bdevs->list, entry, bdev_t, node) {
434                 printf("\t%s, size %lld, bsize %zd, ref %d\n", entry->name, entry->size, entry->block_size, entry->ref);
435         }
436         mutex_release(&bdevs->lock);
437 }
438
439 void bio_init(void)
440 {
441         bdevs = malloc(sizeof(*bdevs));
442
443         list_initialize(&bdevs->list);
444         mutex_init(&bdevs->lock);
445 }
446