Improve cake allocator's memory utilisation (#43)
[lunaix-os.git] / lunaix-os / kernel / block / blkbuf.c
1 #include <lunaix/blkbuf.h>
2 #include <lunaix/mm/cake.h>
3 #include <lunaix/mm/valloc.h>
4 #include <lunaix/owloysius.h>
5 #include <lunaix/syslog.h>
6
7 LOG_MODULE("blkbuf")  
8
9 #define bb_cache_obj(bcache) \
10             container_of(bcache, struct blkbuf_cache, cached)
11
12 #define to_blkbuf(bbuf) ((struct blk_buf*)(bbuf))
13
14 static bcache_zone_t bb_zone;
15 static struct cake_pile* bb_pile;
16
17 static inline u64_t
18 __tolba(struct blkbuf_cache* cache, unsigned int blk_id)
19 {
20     return ((u64_t)cache->blksize * (u64_t)blk_id) / cache->blkdev->blk_size;
21 }
22
23 static void
24 __blkbuf_do_sync(struct bcache* bc, unsigned long tag, void* data)
25 {
26     return;
27 }
28
29 static void
30 __blkbuf_sync_callback(struct blkio_req* req)
31 {
32     struct blk_buf* buf;
33
34     buf = (struct blk_buf*)req->evt_args;
35
36     if (req->errcode) {
37         ERROR("sync failed: io error, 0x%x", req->errcode);
38         return;
39     }
40 }
41
42 static void
43 __blkbuf_evict_callback(struct blkio_req* req)
44 {
45     struct blk_buf* buf;
46
47     buf = (struct blk_buf*)req->evt_args;
48
49     if (req->errcode) {
50         ERROR("sync on evict failed (io error, 0x%x)", req->errcode);
51     }
52     
53     vfree(buf->raw);
54     vbuf_free(req->vbuf);
55     cake_release(bb_pile, buf);
56 }
57
58 static void
59 __blkbuf_do_try_release(struct bcache* bc, void* data)
60 {
61     struct blkio_req* req;
62     struct blk_buf* buf;
63
64     buf = (struct blk_buf*)data;
65     req = buf->breq;
66
67     if (llist_empty(&buf->dirty)) {
68         __blkbuf_evict_callback(req);
69         blkio_free_req(req);
70         return;
71     }
72     
73     // since we are evicting, don't care if the sync is failed
74     llist_delete(&buf->dirty);
75
76     blkio_when_completed(req, __blkbuf_evict_callback);
77     blkio_mark_foc(req);
78     blkio_commit(req, 0);
79 }
80
81 static struct bcache_ops cache_ops = {
82     .release_on_evict = __blkbuf_do_try_release,
83     .sync_cached = __blkbuf_do_sync
84 };
85
86 static bbuf_t
87 __blkbuf_take_slow_lockness(struct blkbuf_cache* bc, unsigned int block_id)
88 {
89     struct blk_buf* buf;
90     struct blkio_req* req;
91     struct vecbuf* vbuf;
92     void* data;
93     u64_t lba;
94
95     data = valloc(bc->blksize);
96
97     vbuf = NULL;
98     vbuf_alloc(&vbuf, data, bc->blksize);
99
100     lba = __tolba(bc, block_id);
101     buf = (struct blk_buf*)cake_grab(bb_pile);
102     req = blkio_vreq(vbuf, lba, __blkbuf_sync_callback, buf, 0);
103
104     // give dirty a know state
105     llist_init_head(&buf->dirty);
106
107     blkio_setread(req);
108     blkio_bindctx(req, bc->blkdev->blkio);
109     blkio_commit(req, BLKIO_WAIT);
110
111     if (req->errcode) {
112         ERROR("block io error (0x%x)", req->errcode);
113         cake_release(bb_pile, buf);
114         return (bbuf_t)INVL_BUFFER;
115     }
116
117     buf->raw = data;
118     buf->cobj = bcache_put_and_ref(&bc->cached, block_id, buf);
119     buf->breq = req;
120
121     return buf;
122 }
123
124 struct blkbuf_cache*
125 blkbuf_create(struct block_dev* blkdev, unsigned int blk_size)
126 {
127     struct blkbuf_cache* bb_cache;
128
129     assert(is_pot(blk_size));
130
131     bb_cache = valloc(sizeof(*bb_cache));
132     bb_cache->blkdev = blkdev;
133
134     bcache_init_zone(&bb_cache->cached, bb_zone, 3, -1, blk_size, &cache_ops);
135     llist_init_head(&bb_cache->dirty);
136     mutex_init(&bb_cache->lock);
137
138     return bb_cache;
139 }
140
141 bbuf_t
142 blkbuf_take(struct blkbuf_cache* bc, unsigned int block_id)
143 {
144     bcobj_t cobj;
145     mutex_lock(&bc->lock);
146     if (bcache_tryget(&bc->cached, block_id, &cobj)) {
147         mutex_unlock(&bc->lock);
148         return (bbuf_t)bcached_data(cobj);
149     }
150
151     bbuf_t buf = __blkbuf_take_slow_lockness(bc, block_id);
152     
153     mutex_unlock(&bc->lock);
154     return buf;
155 }
156
157 void
158 blkbuf_put(bbuf_t buf)
159 {
160     if (unlikely(!buf || blkbuf_errbuf(buf))) {
161         return;
162     }
163
164     struct blk_buf* bbuf;
165     bbuf = to_blkbuf(buf);
166
167     bcache_return(bbuf->cobj);
168 }
169
170 void
171 blkbuf_dirty(bbuf_t buf)
172 {
173     assert(buf && !blkbuf_errbuf(buf));
174
175     struct blk_buf* bbuf;
176     struct blkbuf_cache* bc;
177     
178     bbuf = ((struct blk_buf*)buf);
179     bc = bcache_holder_embed(bbuf->cobj, struct blkbuf_cache, cached);
180     
181     mutex_lock(&bc->lock);
182     
183     if (llist_empty(&bbuf->dirty)) {
184         llist_append(&bc->dirty, &bbuf->dirty);
185     }
186
187     mutex_unlock(&bc->lock);
188 }
189
190 static inline void
191 __schedule_sync_event(struct blk_buf* bbuf, bool wait)
192 {
193     struct blkio_req* blkio;
194
195     blkio = bbuf->breq;
196
197     blkio_setwrite(blkio);
198     blkio_commit(blkio, wait ? BLKIO_WAIT : BLKIO_NOWAIT);
199
200     llist_delete(&bbuf->dirty);
201 }
202
203 void
204 blkbuf_schedule_sync(bbuf_t buf)
205 {
206     struct blk_buf* bbuf;
207     bbuf = to_blkbuf(buf);
208
209     __schedule_sync_event(bbuf, false);
210 }
211
212 bool
213 blkbuf_syncall(struct blkbuf_cache* bc, bool async)
214 {
215     struct blk_buf *pos, *n;
216
217     mutex_lock(&bc->lock);
218
219     llist_for_each(pos, n, &bc->dirty, dirty) {
220         __schedule_sync_event(pos, !async);
221     }
222
223     mutex_unlock(&bc->lock);
224
225     if (async) {
226         return true;
227     }
228
229     return llist_empty(&bc->dirty);
230 }
231
232 void
233 blkbuf_release(struct blkbuf_cache* bc)
234 {
235     bcache_free(&bc->cached);
236     vfree(bc);
237 }
238
239 static void
240 __init_blkbuf()
241 {
242     bb_zone = bcache_create_zone("blk_buf");
243     bb_pile = cake_new_pile("blk_buf", sizeof(struct blk_buf), 1, 0);
244 }
245 owloysius_fetch_init(__init_blkbuf, on_earlyboot)