ext4_journal: simple handling on superblock.
[lwext4.git] / lwext4 / ext4_blockdev.c
1 /*
2  * Copyright (c) 2013 Grzegorz Kostka (kostka.grzegorz@gmail.com)
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  *
9  * - Redistributions of source code must retain the above copyright
10  *   notice, this list of conditions and the following disclaimer.
11  * - Redistributions in binary form must reproduce the above copyright
12  *   notice, this list of conditions and the following disclaimer in the
13  *   documentation and/or other materials provided with the distribution.
14  * - The name of the author may not be used to endorse or promote products
15  *   derived from this software without specific prior written permission.
16  *
17  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27  */
28
29 /** @addtogroup lwext4
30  * @{
31  */
32 /**
33  * @file  ext4_blockdev.c
34  * @brief Block device module.
35  */
36
37 #include "ext4_config.h"
38 #include "ext4_blockdev.h"
39 #include "ext4_errno.h"
40 #include "ext4_debug.h"
41
42 #include <string.h>
43 #include <stdlib.h>
44
45 int ext4_block_init(struct ext4_blockdev *bdev)
46 {
47         int rc;
48         ext4_assert(bdev);
49
50         ext4_assert(bdev->open && bdev->close && bdev->bread && bdev->bwrite);
51
52         /*Low level block init*/
53         rc = bdev->open(bdev);
54         if (rc != EOK)
55                 return rc;
56
57         bdev->flags |= EXT4_BDEV_INITIALIZED;
58
59         return EOK;
60 }
61
62 int ext4_block_bind_bcache(struct ext4_blockdev *bdev, struct ext4_bcache *bc)
63 {
64         ext4_assert(bdev && bc);
65         bdev->bc = bc;
66         return EOK;
67 }
68
69 void ext4_block_set_lb_size(struct ext4_blockdev *bdev, uint64_t lb_bsize)
70 {
71         /*Logical block size has to be multiply of physical */
72         ext4_assert(!(lb_bsize % bdev->ph_bsize));
73
74         bdev->lg_bsize = lb_bsize;
75         bdev->lg_bcnt = (bdev->ph_bcnt * bdev->ph_bsize) / lb_bsize;
76 }
77
78 int ext4_block_fini(struct ext4_blockdev *bdev)
79 {
80         ext4_assert(bdev);
81
82         bdev->flags &= ~(EXT4_BDEV_INITIALIZED);
83
84         /*Low level block fini*/
85         return bdev->close(bdev);
86 }
87
88 static int
89 ext4_block_flush_buf(struct ext4_blockdev *bdev, struct ext4_buf *buf)
90 {
91         int r;
92         struct ext4_bcache *bc = bdev->bc;
93         /*Only flushing unreferenced buffer is allowed.*/
94         ext4_assert(!buf->refctr);
95
96         if (ext4_bcache_test_flag(buf, BC_DIRTY)) {
97                 r = ext4_blocks_set_direct(bdev, buf->data, buf->lba, 1);
98                 if (r)
99                         return r;
100
101                 SLIST_REMOVE(&bc->dirty_list,
102                                 buf,
103                                 ext4_buf,
104                                 dirty_node);
105                 ext4_bcache_clear_flag(buf, BC_DIRTY);
106         }
107         return EOK;
108 }
109
110 int ext4_block_cache_shake(struct ext4_blockdev *bdev)
111 {
112         struct ext4_buf *buf;
113         while (!RB_EMPTY(&bdev->bc->lru_root) &&
114                 ext4_bcache_is_full(bdev->bc)) {
115                 
116                 buf = ext4_buf_lowest_lru(bdev->bc);
117                 ext4_assert(buf);
118                 if (ext4_bcache_test_flag(buf, BC_DIRTY)) {
119                         int r = ext4_block_flush_buf(bdev, buf);
120                         if (r != EOK)
121                                 return r;
122
123                 }
124
125                 ext4_bcache_drop_buf(bdev->bc, buf);
126         }
127         return EOK;
128 }
129
130 int ext4_block_get_noread(struct ext4_blockdev *bdev, struct ext4_block *b,
131                           uint64_t lba)
132 {
133         bool is_new;
134         int r;
135
136         ext4_assert(bdev && b);
137
138         if (!(bdev->flags & EXT4_BDEV_INITIALIZED))
139                 return EIO;
140
141         if (!(lba < bdev->lg_bcnt))
142                 return ERANGE;
143
144         b->dirty = 0;
145         b->lb_id = lba;
146
147         /*If cache is full we have to (flush and) drop it anyway :(*/
148         r = ext4_block_cache_shake(bdev);
149         if (r != EOK)
150                 return r;
151
152         r = ext4_bcache_alloc(bdev->bc, b, &is_new);
153         if (r != EOK)
154                 return r;
155
156         if (!b->data)
157                 return ENOMEM;
158
159         return EOK;
160 }
161
162 int ext4_block_get(struct ext4_blockdev *bdev, struct ext4_block *b,
163                    uint64_t lba)
164 {
165         uint64_t pba;
166         uint32_t pb_cnt;
167         int r = ext4_block_get_noread(bdev, b, lba);
168         if (r != EOK)
169                 return r;
170
171         if (b->uptodate) {
172                 /* Data in the cache is up-to-date.
173                  * Reading from physical device is not required */
174                 return EOK;
175         }
176
177         pba = (lba * bdev->lg_bsize) / bdev->ph_bsize;
178         pb_cnt = bdev->lg_bsize / bdev->ph_bsize;
179
180         r = bdev->bread(bdev, b->data, pba, pb_cnt);
181
182         if (r != EOK) {
183                 ext4_bcache_free(bdev->bc, b);
184                 b->lb_id = 0;
185                 return r;
186         }
187
188         /* Mark buffer up-to-date, since
189          * fresh data is read from physical device just now. */
190         ext4_bcache_set_flag(b->buf, BC_UPTODATE);
191         b->uptodate = true;
192         bdev->bread_ctr++;
193         return EOK;
194 }
195
196 int ext4_block_set(struct ext4_blockdev *bdev, struct ext4_block *b)
197 {
198         uint64_t pba;
199         uint32_t pb_cnt;
200         int r;
201
202         ext4_assert(bdev && b);
203         ext4_assert(b->buf);
204
205         if (!(bdev->flags & EXT4_BDEV_INITIALIZED))
206                 return EIO;
207
208         /*Free cache delay mode*/
209         if (bdev->cache_write_back) {
210
211                 /*Free cache block and mark as free delayed*/
212                 return ext4_bcache_free(bdev->bc, b);
213         }
214
215         if (b->buf->refctr > 1)
216                 return ext4_bcache_free(bdev->bc, b);
217
218         /*We handle the dirty flag ourselves.*/
219         if (ext4_bcache_test_flag(b->buf, BC_DIRTY) || b->dirty) {
220                 b->uptodate = true;
221                 ext4_bcache_set_flag(b->buf, BC_UPTODATE);
222
223                 pba = (b->lb_id * bdev->lg_bsize) / bdev->ph_bsize;
224                 pb_cnt = bdev->lg_bsize / bdev->ph_bsize;
225
226                 r = bdev->bwrite(bdev, b->data, pba, pb_cnt);
227                 ext4_bcache_clear_flag(b->buf, BC_DIRTY);
228                 if (r != EOK) {
229                         b->dirty = true;
230                         ext4_bcache_free(bdev->bc, b);
231                         return r;
232                 }
233
234                 b->dirty = false;
235                 bdev->bwrite_ctr++;
236         }
237         ext4_bcache_free(bdev->bc, b);
238         return EOK;
239 }
240
241 int ext4_blocks_get_direct(struct ext4_blockdev *bdev, void *buf, uint64_t lba,
242                            uint32_t cnt)
243 {
244         uint64_t pba;
245         uint32_t pb_cnt;
246
247         ext4_assert(bdev && buf);
248
249         pba = (lba * bdev->lg_bsize) / bdev->ph_bsize;
250         pb_cnt = bdev->lg_bsize / bdev->ph_bsize;
251
252         bdev->bread_ctr++;
253         return bdev->bread(bdev, buf, pba, pb_cnt * cnt);
254 }
255
256 int ext4_blocks_set_direct(struct ext4_blockdev *bdev, const void *buf,
257                            uint64_t lba, uint32_t cnt)
258 {
259         uint64_t pba;
260         uint32_t pb_cnt;
261
262         ext4_assert(bdev && buf);
263
264         pba = (lba * bdev->lg_bsize) / bdev->ph_bsize;
265         pb_cnt = bdev->lg_bsize / bdev->ph_bsize;
266
267         bdev->bwrite_ctr++;
268
269         return bdev->bwrite(bdev, buf, pba, pb_cnt * cnt);
270 }
271
272 int ext4_block_writebytes(struct ext4_blockdev *bdev, uint64_t off,
273                           const void *buf, uint32_t len)
274 {
275         uint64_t block_idx;
276         uint64_t block_end;
277         uint32_t blen;
278         uint32_t unalg;
279         int r = EOK;
280
281         const uint8_t *p = (void *)buf;
282
283         ext4_assert(bdev && buf);
284
285         if (!(bdev->flags & EXT4_BDEV_INITIALIZED))
286                 return EIO;
287
288         block_idx = off / bdev->ph_bsize;
289         block_end = block_idx + len / bdev->ph_bsize;
290
291         if (!(block_end < bdev->ph_bcnt))
292                 return EINVAL; /*Ups. Out of range operation*/
293
294         /*OK lets deal with the first possible unaligned block*/
295         unalg = (off & (bdev->ph_bsize - 1));
296         if (unalg) {
297
298                 uint32_t wlen = (bdev->ph_bsize - unalg) > len
299                                     ? len
300                                     : (bdev->ph_bsize - unalg);
301
302                 r = bdev->bread(bdev, bdev->ph_bbuf, block_idx, 1);
303                 if (r != EOK)
304                         return r;
305
306                 memcpy(bdev->ph_bbuf + unalg, p, wlen);
307
308                 r = bdev->bwrite(bdev, bdev->ph_bbuf, block_idx, 1);
309                 if (r != EOK)
310                         return r;
311
312                 p += wlen;
313                 len -= wlen;
314                 block_idx++;
315         }
316
317         /*Aligned data*/
318         blen = len / bdev->ph_bsize;
319         r = bdev->bwrite(bdev, p, block_idx, blen);
320         if (r != EOK)
321                 return r;
322
323         p += bdev->ph_bsize * blen;
324         len -= bdev->ph_bsize * blen;
325
326         block_idx += blen;
327
328         /*Rest of the data*/
329         if (len) {
330                 r = bdev->bread(bdev, bdev->ph_bbuf, block_idx, 1);
331                 if (r != EOK)
332                         return r;
333
334                 memcpy(bdev->ph_bbuf, p, len);
335
336                 r = bdev->bwrite(bdev, bdev->ph_bbuf, block_idx, 1);
337                 if (r != EOK)
338                         return r;
339         }
340
341         return r;
342 }
343
344 int ext4_block_readbytes(struct ext4_blockdev *bdev, uint64_t off, void *buf,
345                          uint32_t len)
346 {
347         uint64_t block_idx;
348         uint64_t block_end;
349         uint32_t blen;
350         uint32_t unalg;
351         int r = EOK;
352
353         uint8_t *p = (void *)buf;
354
355         ext4_assert(bdev && buf);
356
357         if (!(bdev->flags & EXT4_BDEV_INITIALIZED))
358                 return EIO;
359
360         block_idx = off / bdev->ph_bsize;
361         block_end = block_idx + len / bdev->ph_bsize;
362
363         if (!(block_end < bdev->ph_bcnt))
364                 return EINVAL; /*Ups. Out of range operation*/
365
366         /*OK lets deal with the first possible unaligned block*/
367         unalg = (off & (bdev->ph_bsize - 1));
368         if (unalg) {
369
370                 uint32_t rlen = (bdev->ph_bsize - unalg) > len
371                                     ? len
372                                     : (bdev->ph_bsize - unalg);
373
374                 r = bdev->bread(bdev, bdev->ph_bbuf, block_idx, 1);
375                 if (r != EOK)
376                         return r;
377
378                 memcpy(p, bdev->ph_bbuf + unalg, rlen);
379
380                 p += rlen;
381                 len -= rlen;
382                 block_idx++;
383         }
384
385         /*Aligned data*/
386         blen = len / bdev->ph_bsize;
387
388         r = bdev->bread(bdev, p, block_idx, blen);
389         if (r != EOK)
390                 return r;
391
392         p += bdev->ph_bsize * blen;
393         len -= bdev->ph_bsize * blen;
394
395         block_idx += blen;
396
397         /*Rest of the data*/
398         if (len) {
399                 r = bdev->bread(bdev, bdev->ph_bbuf, block_idx, 1);
400                 if (r != EOK)
401                         return r;
402
403                 memcpy(p, bdev->ph_bbuf, len);
404         }
405
406         return r;
407 }
408
409 int ext4_block_cache_write_back(struct ext4_blockdev *bdev, uint8_t on_off)
410 {
411         int r;
412         struct ext4_buf *buf;
413
414         if (on_off)
415                 bdev->cache_write_back++;
416
417         if (!on_off && bdev->cache_write_back)
418                 bdev->cache_write_back--;
419
420         if (bdev->cache_write_back)
421                 return EOK;
422
423         /*Flush all delayed cache blocks*/
424         while (!SLIST_EMPTY(&bdev->bc->dirty_list)) {
425                 
426                 buf = SLIST_FIRST(&bdev->bc->dirty_list);
427                 ext4_assert(buf);
428                 r = ext4_block_flush_buf(bdev, buf);
429                 if (r != EOK)
430                         return r;
431
432         }
433         return EOK;
434 }
435
436 /**
437  * @}
438  */