ext4_bcache: remove free_delay member from ext4_bcache.
[lwext4.git] / lwext4 / ext4_bcache.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_bcache.c
34  * @brief Block cache allocator.
35  */
36
37 #include "ext4_config.h"
38 #include "ext4_bcache.h"
39 #include "ext4_debug.h"
40 #include "ext4_errno.h"
41
42 #include <string.h>
43 #include <stdlib.h>
44
45 static int
46 ext4_bcache_lba_compare(struct ext4_buf *a,
47                         struct ext4_buf *b)
48 {
49         return a->lba - b->lba;
50 }
51
52 static int
53 ext4_bcache_lru_compare(struct ext4_buf *a,
54                         struct ext4_buf *b)
55 {
56         return a->lru_id - b->lru_id;
57 }
58
59 RB_GENERATE_INTERNAL(ext4_buf_lba, ext4_buf, lba_node,
60                      ext4_bcache_lba_compare, static inline)
61 RB_GENERATE_INTERNAL(ext4_buf_lru, ext4_buf, lru_node,
62                      ext4_bcache_lru_compare, static inline)
63
64 int ext4_bcache_init_dynamic(struct ext4_bcache *bc, uint32_t cnt,
65                              uint32_t itemsize)
66 {
67         ext4_assert(bc && cnt && itemsize);
68
69         memset(bc, 0, sizeof(struct ext4_bcache));
70
71         bc->cnt = cnt;
72         bc->itemsize = itemsize;
73         bc->ref_blocks = 0;
74         bc->max_ref_blocks = 0;
75
76         return EOK;
77 }
78
79 int ext4_bcache_fini_dynamic(struct ext4_bcache *bc)
80 {
81         memset(bc, 0, sizeof(struct ext4_bcache));
82         return EOK;
83 }
84
85 /**@brief:
86  *
87  *  This is ext4_bcache, the module handling basic buffer-cache stuff.
88  *
89  *  Buffers in a bcache are sorted by their LBA and stored in a
90  *  RB-Tree(lba_root).
91  *
92  *  Bcache also maintains another RB-Tree(lru_root) right now, where
93  *  buffers are sorted by their LRU id.
94  *
95  *  A singly-linked list is used to track those dirty buffers which are
96  *  ready to be flushed. (Those buffers which are dirty but also referenced
97  *  are not considered ready to be flushed.)
98  *
99  *  When a buffer is not referenced, it will be stored in both lba_root
100  *  and lru_root, while it will only be stored in lba_root when it is
101  *  referenced.
102  */
103
104 static struct ext4_buf *
105 ext4_buf_alloc(struct ext4_bcache *bc, uint64_t lba)
106 {
107         void *data;
108         struct ext4_buf *buf;
109         data = malloc(bc->itemsize);
110         if (!data)
111                 return NULL;
112
113         buf = calloc(1, sizeof(struct ext4_buf));
114         if (!buf) {
115                 free(data);
116                 return NULL;
117         }
118
119         buf->lba = lba;
120         buf->data = data;
121         return buf;
122 }
123
124 static void ext4_buf_free(struct ext4_buf *buf)
125 {
126         free(buf->data);
127         free(buf);
128 }
129
130 static struct ext4_buf *
131 ext4_buf_lookup(struct ext4_bcache *bc, uint64_t lba)
132 {
133         struct ext4_buf tmp = {
134                 .lba = lba
135         };
136
137         return RB_FIND(ext4_buf_lba, &bc->lba_root, &tmp);
138 }
139
140 struct ext4_buf *ext4_buf_lowest_lru(struct ext4_bcache *bc)
141 {
142         return RB_MIN(ext4_buf_lru, &bc->lru_root);
143 }
144
145 void ext4_bcache_drop_buf(struct ext4_bcache *bc, struct ext4_buf *buf)
146 {
147         /*Cannot drop any referenced buffers.*/
148         ext4_assert(!buf->refctr);
149
150         RB_REMOVE(ext4_buf_lba, &bc->lba_root, buf);
151         RB_REMOVE(ext4_buf_lru, &bc->lru_root, buf);
152
153         /*Forcibly drop dirty buffer.*/
154         if (ext4_bcache_test_flag(buf, BC_DIRTY))
155                 SLIST_REMOVE(&bc->dirty_list,
156                              buf,
157                              ext4_buf,
158                              dirty_node);
159
160         ext4_buf_free(buf);
161         bc->ref_blocks--;
162 }
163
164 int ext4_bcache_alloc(struct ext4_bcache *bc, struct ext4_block *b,
165                       bool *is_new)
166 {
167         /* Try to search the buffer with exaxt LBA. */
168         struct ext4_buf *buf = ext4_buf_lookup(bc, b->lb_id);
169         if (buf) {
170                 /* If buffer is not referenced. */
171                 if (!buf->refctr) {
172                         /* Assign new value to LRU id and increment LRU counter
173                          * by 1*/
174                         buf->lru_id = ++bc->lru_ctr;
175                         RB_REMOVE(ext4_buf_lru, &bc->lru_root, buf);
176                         if (ext4_bcache_test_flag(buf, BC_DIRTY))
177                                 SLIST_REMOVE(&bc->dirty_list,
178                                              buf,
179                                              ext4_buf,
180                                              dirty_node);
181
182                 }
183
184                 buf->refctr++;
185
186                 b->uptodate = ext4_bcache_test_flag(buf, BC_UPTODATE);
187                 /* Right now we don't propagate the dirty flag from ext4_buf to
188                  * ext4_block. */
189                 b->dirty = false;
190                 b->buf = buf;
191                 b->data = buf->data;
192
193                 *is_new = false;
194                 return EOK;
195         }
196
197         /* We need to allocate one buffer.*/
198         buf = ext4_buf_alloc(bc, b->lb_id);
199         if (!buf)
200                 return ENOMEM;
201
202         RB_INSERT(ext4_buf_lba, &bc->lba_root, buf);
203         /* One more buffer in bcache now. :-) */
204         bc->ref_blocks++;
205
206         buf->refctr = 1;
207         /* Assign new value to LRU id and increment LRU counter
208          * by 1*/
209         buf->lru_id = ++bc->lru_ctr;
210
211         b->uptodate = false;
212         b->dirty = false;
213         b->buf = buf;
214         b->data = buf->data;
215
216         *is_new = true;
217         return EOK;
218 }
219
220 int ext4_bcache_free(struct ext4_bcache *bc, struct ext4_block *b)
221 {
222         struct ext4_buf *buf = b->buf;
223
224         ext4_assert(bc && b);
225
226         /*Check if valid.*/
227         ext4_assert(b->lb_id);
228
229         /*Block should have a valid pointer to ext4_buf.*/
230         ext4_assert(buf);
231
232         /*Check if someone don't try free unreferenced block cache.*/
233         ext4_assert(buf->refctr);
234
235         /*Just decrease reference counter*/
236         buf->refctr--;
237
238         /* If buffer is modified, buf will be mark up-to-date and dirty. */
239         if (b->dirty) {
240                 ext4_bcache_set_flag(buf, BC_DIRTY);
241                 ext4_bcache_set_flag(buf, BC_UPTODATE);
242                 b->uptodate = true;
243         }
244         /* Someone might want to drop this buffer from bcache. */
245         if (!b->uptodate)
246                 ext4_bcache_clear_flag(buf, BC_UPTODATE);
247
248         /* We are the last one touching this buffer, do the cleanups. */
249         if (!buf->refctr) {
250                 RB_INSERT(ext4_buf_lru, &bc->lru_root, buf);
251                 /* This buffer is ready to be flushed. */
252                 if (ext4_bcache_test_flag(buf, BC_DIRTY))
253                         SLIST_INSERT_HEAD(&bc->dirty_list, buf, dirty_node);
254
255                 /* The buffer is invalidated...drop it. */
256                 if (!ext4_bcache_test_flag(buf, BC_UPTODATE))
257                         ext4_bcache_drop_buf(bc, buf);
258
259         }
260
261         b->lb_id = 0;
262         b->data = 0;
263         b->uptodate = false;
264         b->dirty = false;
265
266         return EOK;
267 }
268
269 bool ext4_bcache_is_full(struct ext4_bcache *bc)
270 {
271         return (bc->cnt <= bc->ref_blocks);
272 }
273
274 /**
275  * @}
276  */