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_types.h>
39#include <ext4_misc.h>
40#include <ext4_errno.h>
41#include <ext4_debug.h>
42
43#include <ext4_blockdev.h>
44#include <ext4_fs.h>
45#include <ext4_journal.h>
46
47#include <string.h>
48#include <stdlib.h>
49
50static void ext4_bdif_lock(struct ext4_blockdev *bdev)
51{
52 if (!bdev->bdif->lock)
53 return;
54
55 int r = bdev->bdif->lock(bdev);
56 ext4_assert(r == EOK);
57}
58
59static void ext4_bdif_unlock(struct ext4_blockdev *bdev)
60{
61 if (!bdev->bdif->unlock)
62 return;
63
64 int r = bdev->bdif->unlock(bdev);
65 ext4_assert(r == EOK);
66}
67
68static int ext4_bdif_bread(struct ext4_blockdev *bdev, void *buf,
69 uint64_t blk_id, uint32_t blk_cnt)
70{
71 ext4_bdif_lock(bdev);
72 int r = bdev->bdif->bread(bdev, buf, blk_id, blk_cnt);
73 bdev->bdif->bread_ctr++;
74 ext4_bdif_unlock(bdev);
75 return r;
76}
77
78static int ext4_bdif_bwrite(struct ext4_blockdev *bdev, const void *buf,
79 uint64_t blk_id, uint32_t blk_cnt)
80{
81 ext4_bdif_lock(bdev);
82 int r = bdev->bdif->bwrite(bdev, buf, blk_id, blk_cnt);
83 bdev->bdif->bwrite_ctr++;
84 ext4_bdif_unlock(bdev);
85 return r;
86}
87
88int ext4_block_init(struct ext4_blockdev *bdev)
89{
90 int rc;
91 ext4_assert(bdev);
92 ext4_assert(bdev->bdif);
93 ext4_assert(bdev->bdif->open &&
94 bdev->bdif->close &&
95 bdev->bdif->bread &&
96 bdev->bdif->bwrite);
97
98 if (bdev->bdif->ph_refctr) {
99 bdev->bdif->ph_refctr++;
100 return EOK;
101 }
102
103 /*Low level block init*/
104 rc = bdev->bdif->open(bdev);
105 if (rc != EOK)
106 return rc;
107
108 bdev->bdif->ph_refctr = 1;
109 return EOK;
110}
111
112int ext4_block_bind_bcache(struct ext4_blockdev *bdev, struct ext4_bcache *bc)
113{
114 ext4_assert(bdev && bc);
115 bdev->bc = bc;
116 bc->bdev = bdev;
117 return EOK;
118}
119
120void ext4_block_set_lb_size(struct ext4_blockdev *bdev, uint32_t lb_bsize)
121{
122 /*Logical block size has to be multiply of physical */
123 ext4_assert(!(lb_bsize % bdev->bdif->ph_bsize));
124
125 bdev->lg_bsize = lb_bsize;
126 bdev->lg_bcnt = bdev->part_size / lb_bsize;
127}
128
129int ext4_block_fini(struct ext4_blockdev *bdev)
130{
131 ext4_assert(bdev);
132
133 if (!bdev->bdif->ph_refctr)
134 return EOK;
135
136 bdev->bdif->ph_refctr--;
137 if (bdev->bdif->ph_refctr)
138 return EOK;
139
140 /*Low level block fini*/
141 return bdev->bdif->close(bdev);
142}
143
144int ext4_block_flush_buf(struct ext4_blockdev *bdev, struct ext4_buf *buf)
145{
146 int r;
147 struct ext4_bcache *bc = bdev->bc;
148
149 if (ext4_bcache_test_flag(buf, BC_DIRTY) &&
150 ext4_bcache_test_flag(buf, BC_UPTODATE)) {
151 r = ext4_blocks_set_direct(bdev, buf: buf->data, lba: buf->lba, cnt: 1);
152 if (r) {
153 if (buf->end_write) {
154 bc->dont_shake = true;
155 buf->end_write(bc, buf, r, buf->end_write_arg);
156 bc->dont_shake = false;
157 }
158
159 return r;
160 }
161
162 ext4_bcache_remove_dirty_node(bc, buf);
163 ext4_bcache_clear_flag(buf, BC_DIRTY);
164 if (buf->end_write) {
165 bc->dont_shake = true;
166 buf->end_write(bc, buf, r, buf->end_write_arg);
167 bc->dont_shake = false;
168 }
169 }
170 return EOK;
171}
172
173int ext4_block_flush_lba(struct ext4_blockdev *bdev, uint64_t lba)
174{
175 int r = EOK;
176 struct ext4_buf *buf;
177 struct ext4_block b;
178 buf = ext4_bcache_find_get(bc: bdev->bc, b: &b, lba);
179 if (buf) {
180 r = ext4_block_flush_buf(bdev, buf);
181 ext4_bcache_free(bc: bdev->bc, b: &b);
182 }
183 return r;
184}
185
186int ext4_block_cache_shake(struct ext4_blockdev *bdev)
187{
188 int r = EOK;
189 struct ext4_buf *buf;
190 if (bdev->bc->dont_shake)
191 return EOK;
192
193 bdev->bc->dont_shake = true;
194
195 while (!RB_EMPTY(&bdev->bc->lru_root) &&
196 ext4_bcache_is_full(bc: bdev->bc)) {
197
198 buf = ext4_buf_lowest_lru(bc: bdev->bc);
199 ext4_assert(buf);
200 if (ext4_bcache_test_flag(buf, BC_DIRTY)) {
201 r = ext4_block_flush_buf(bdev, buf);
202 if (r != EOK)
203 break;
204
205 }
206
207 ext4_bcache_drop_buf(bc: bdev->bc, buf);
208 }
209 bdev->bc->dont_shake = false;
210 return r;
211}
212
213int ext4_block_get_noread(struct ext4_blockdev *bdev, struct ext4_block *b,
214 uint64_t lba)
215{
216 bool is_new;
217 int r;
218
219 ext4_assert(bdev && b);
220
221 if (!bdev->bdif->ph_refctr)
222 return EIO;
223
224 if (!(lba < bdev->lg_bcnt))
225 return ENXIO;
226
227 b->lb_id = lba;
228
229 /*If cache is full we have to (flush and) drop it anyway :(*/
230 r = ext4_block_cache_shake(bdev);
231 if (r != EOK)
232 return r;
233
234 r = ext4_bcache_alloc(bc: bdev->bc, b, is_new: &is_new);
235 if (r != EOK)
236 return r;
237
238 if (!b->data)
239 return ENOMEM;
240
241 return EOK;
242}
243
244int ext4_block_get(struct ext4_blockdev *bdev, struct ext4_block *b,
245 uint64_t lba)
246{
247 int r = ext4_block_get_noread(bdev, b, lba);
248 if (r != EOK)
249 return r;
250
251 if (ext4_bcache_test_flag(b->buf, BC_UPTODATE)) {
252 /* Data in the cache is up-to-date.
253 * Reading from physical device is not required */
254 return EOK;
255 }
256
257 r = ext4_blocks_get_direct(bdev, buf: b->data, lba, cnt: 1);
258 if (r != EOK) {
259 ext4_bcache_free(bc: bdev->bc, b);
260 b->lb_id = 0;
261 return r;
262 }
263
264 /* Mark buffer up-to-date, since
265 * fresh data is read from physical device just now. */
266 ext4_bcache_set_flag(b->buf, BC_UPTODATE);
267 return EOK;
268}
269
270int ext4_block_set(struct ext4_blockdev *bdev, struct ext4_block *b)
271{
272 ext4_assert(bdev && b);
273 ext4_assert(b->buf);
274
275 if (!bdev->bdif->ph_refctr)
276 return EIO;
277
278 return ext4_bcache_free(bc: bdev->bc, b);
279}
280
281int ext4_blocks_get_direct(struct ext4_blockdev *bdev, void *buf, uint64_t lba,
282 uint32_t cnt)
283{
284 uint64_t pba;
285 uint32_t pb_cnt;
286
287 ext4_assert(bdev && buf);
288
289 pba = (lba * bdev->lg_bsize + bdev->part_offset) / bdev->bdif->ph_bsize;
290 pb_cnt = bdev->lg_bsize / bdev->bdif->ph_bsize;
291
292 return ext4_bdif_bread(bdev, buf, blk_id: pba, blk_cnt: pb_cnt * cnt);
293}
294
295int ext4_blocks_set_direct(struct ext4_blockdev *bdev, const void *buf,
296 uint64_t lba, uint32_t cnt)
297{
298 uint64_t pba;
299 uint32_t pb_cnt;
300
301 ext4_assert(bdev && buf);
302
303 pba = (lba * bdev->lg_bsize + bdev->part_offset) / bdev->bdif->ph_bsize;
304 pb_cnt = bdev->lg_bsize / bdev->bdif->ph_bsize;
305
306 return ext4_bdif_bwrite(bdev, buf, blk_id: pba, blk_cnt: pb_cnt * cnt);
307}
308
309int ext4_block_writebytes(struct ext4_blockdev *bdev, uint64_t off,
310 const void *buf, uint32_t len)
311{
312 uint64_t block_idx;
313 uint32_t blen;
314 uint32_t unalg;
315 int r = EOK;
316
317 const uint8_t *p = (void *)buf;
318
319 ext4_assert(bdev && buf);
320
321 if (!bdev->bdif->ph_refctr)
322 return EIO;
323
324 if (off + len > bdev->part_size)
325 return EINVAL; /*Ups. Out of range operation*/
326
327 block_idx = ((off + bdev->part_offset) / bdev->bdif->ph_bsize);
328
329 /*OK lets deal with the first possible unaligned block*/
330 unalg = (off & (bdev->bdif->ph_bsize - 1));
331 if (unalg) {
332
333 uint32_t wlen = (bdev->bdif->ph_bsize - unalg) > len
334 ? len
335 : (bdev->bdif->ph_bsize - unalg);
336
337 r = ext4_bdif_bread(bdev, buf: bdev->bdif->ph_bbuf, blk_id: block_idx, blk_cnt: 1);
338 if (r != EOK)
339 return r;
340
341 memcpy(dest: bdev->bdif->ph_bbuf + unalg, src: p, size: wlen);
342 r = ext4_bdif_bwrite(bdev, buf: bdev->bdif->ph_bbuf, blk_id: block_idx, blk_cnt: 1);
343 if (r != EOK)
344 return r;
345
346 p += wlen;
347 len -= wlen;
348 block_idx++;
349 }
350
351 /*Aligned data*/
352 blen = len / bdev->bdif->ph_bsize;
353 if (blen != 0) {
354 r = ext4_bdif_bwrite(bdev, buf: p, blk_id: block_idx, blk_cnt: blen);
355 if (r != EOK)
356 return r;
357
358 p += bdev->bdif->ph_bsize * blen;
359 len -= bdev->bdif->ph_bsize * blen;
360
361 block_idx += blen;
362 }
363
364 /*Rest of the data*/
365 if (len) {
366 r = ext4_bdif_bread(bdev, buf: bdev->bdif->ph_bbuf, blk_id: block_idx, blk_cnt: 1);
367 if (r != EOK)
368 return r;
369
370 memcpy(dest: bdev->bdif->ph_bbuf, src: p, size: len);
371 r = ext4_bdif_bwrite(bdev, buf: bdev->bdif->ph_bbuf, blk_id: block_idx, blk_cnt: 1);
372 if (r != EOK)
373 return r;
374 }
375
376 return r;
377}
378
379int ext4_block_readbytes(struct ext4_blockdev *bdev, uint64_t off, void *buf,
380 uint32_t len)
381{
382 uint64_t block_idx;
383 uint32_t blen;
384 uint32_t unalg;
385 int r = EOK;
386
387 uint8_t *p = (void *)buf;
388
389 ext4_assert(bdev && buf);
390
391 if (!bdev->bdif->ph_refctr)
392 return EIO;
393
394 if (off + len > bdev->part_size)
395 return EINVAL; /*Ups. Out of range operation*/
396
397 block_idx = ((off + bdev->part_offset) / bdev->bdif->ph_bsize);
398
399 /*OK lets deal with the first possible unaligned block*/
400 unalg = (off & (bdev->bdif->ph_bsize - 1));
401 if (unalg) {
402
403 uint32_t rlen = (bdev->bdif->ph_bsize - unalg) > len
404 ? len
405 : (bdev->bdif->ph_bsize - unalg);
406
407 r = ext4_bdif_bread(bdev, buf: bdev->bdif->ph_bbuf, blk_id: block_idx, blk_cnt: 1);
408 if (r != EOK)
409 return r;
410
411 memcpy(dest: p, src: bdev->bdif->ph_bbuf + unalg, size: rlen);
412
413 p += rlen;
414 len -= rlen;
415 block_idx++;
416 }
417
418 /*Aligned data*/
419 blen = len / bdev->bdif->ph_bsize;
420
421 if (blen != 0) {
422 r = ext4_bdif_bread(bdev, buf: p, blk_id: block_idx, blk_cnt: blen);
423 if (r != EOK)
424 return r;
425
426 p += bdev->bdif->ph_bsize * blen;
427 len -= bdev->bdif->ph_bsize * blen;
428
429 block_idx += blen;
430 }
431
432 /*Rest of the data*/
433 if (len) {
434 r = ext4_bdif_bread(bdev, buf: bdev->bdif->ph_bbuf, blk_id: block_idx, blk_cnt: 1);
435 if (r != EOK)
436 return r;
437
438 memcpy(dest: p, src: bdev->bdif->ph_bbuf, size: len);
439 }
440
441 return r;
442}
443
444int ext4_block_cache_flush(struct ext4_blockdev *bdev)
445{
446 while (!SLIST_EMPTY(&bdev->bc->dirty_list)) {
447 int r;
448 struct ext4_buf *buf = SLIST_FIRST(&bdev->bc->dirty_list);
449 ext4_assert(buf);
450 r = ext4_block_flush_buf(bdev, buf);
451 if (r != EOK)
452 return r;
453
454 }
455 return EOK;
456}
457
458int ext4_block_cache_write_back(struct ext4_blockdev *bdev, uint8_t on_off)
459{
460 if (on_off)
461 bdev->cache_write_back++;
462
463 if (!on_off && bdev->cache_write_back)
464 bdev->cache_write_back--;
465
466 if (bdev->cache_write_back)
467 return EOK;
468
469 /*Flush data in all delayed cache blocks*/
470 return ext4_block_cache_flush(bdev);
471}
472
473/**
474 * @}
475 */
476