34cb86854c47e3a5633ead00986e932f18742592
[monolithium.git] / kernel / src / cache.c
1 /*
2  * cache.c
3  *
4  * Copyright (C) 2016 Aleksandar Andrejevic <theflash@sdf.lonestar.org>
5  *
6  * This program is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU Affero General Public License as
8  * published by the Free Software Foundation, either version 3 of the
9  * License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU Affero General Public License for more details.
15  *
16  * You should have received a copy of the GNU Affero General Public License
17  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
18  */
19
20 #include <device.h>
21 #include <cache.h>
22 #include <heap.h>
23
24 static void flush_cache_internal(cache_descriptor_t *cache, avl_tree_t *node, void *context)
25 {
26     if (node == NULL) return;
27
28     cache_entry_t *entry = CONTAINER_OF(node, cache_entry_t, tree);
29     if (entry->dirty)
30     {
31         dword_t written;
32         dword_t ret = cache->write_proc(context, entry->data, entry->tree.key * cache->block_size, cache->block_size, &written);
33         if ((ret == ERR_SUCCESS) || (written == cache->block_size)) entry->dirty = FALSE;
34     }
35
36     flush_cache_internal(cache, node->left, context);
37     flush_cache_internal(cache, node->right, context);
38 }
39
40 static void cleanup_cache_internal(avl_tree_t *node)
41 {
42     if (node == NULL) return;
43
44     cleanup_cache_internal(node->left);
45     cleanup_cache_internal(node->right);
46
47     cache_entry_t *entry = CONTAINER_OF(node, cache_entry_t, tree);
48     heap_free(&evictable_heap, entry);
49 }
50
51 dword_t cleanup_cache(cache_descriptor_t *cache)
52 {
53     cleanup_cache_internal(cache->root);
54     cache->root = NULL;
55
56     return ERR_SUCCESS;
57 }
58
59 dword_t read_cache(cache_descriptor_t *cache, void *context, byte_t *buffer, qword_t offset, dword_t length, dword_t *bytes_read)
60 {
61     dword_t ret = ERR_SUCCESS;
62     qword_t i;
63     qword_t first_block = offset / (qword_t)cache->block_size;
64     qword_t last_block = (offset + length - 1) / (qword_t)cache->block_size;
65     bool_t exclusive = FALSE;
66
67     acquire_resource_shared(&cache->resource);
68
69     for (i = first_block; i <= last_block; i++)
70     {
71         dword_t start_offset = 0, bytes_to_copy = cache->block_size;
72         avl_tree_t *element = avl_tree_lookup(cache->root, i);
73
74         if (element == NULL && !exclusive)
75         {
76             release_resource(&cache->resource);
77             acquire_resource_exclusive(&cache->resource);
78             exclusive = TRUE;
79             element = avl_tree_lookup(cache->root, i);
80         }
81
82         if (element == NULL)
83         {
84             cache_entry_t *new_entry = (cache_entry_t*)heap_alloc(&evictable_heap, sizeof(cache_entry_t) + cache->block_size);
85             if (new_entry != NULL)
86             {
87                 new_entry->tree.key = i;
88                 new_entry->dirty = FALSE;
89             }
90
91             ret = cache->read_proc(context, new_entry->data, i * (qword_t)cache->block_size, cache->block_size, NULL);
92             if (ret != ERR_SUCCESS)
93             {
94                 heap_free(&evictable_heap, new_entry);
95                 break;
96             }
97
98             avl_tree_insert(&cache->root, &new_entry->tree);
99             element = &new_entry->tree;
100         }
101
102         cache_entry_t *entry = CONTAINER_OF(element, cache_entry_t, tree);
103         if (i == first_block) start_offset = (dword_t)(offset % (qword_t)cache->block_size);
104         if (i == last_block) bytes_to_copy = (dword_t)((offset + length - 1) % (qword_t)cache->block_size) - start_offset + 1;
105
106         memcpy(&buffer[(i - first_block) * cache->block_size], &entry->data[start_offset], bytes_to_copy);
107         *bytes_read += bytes_to_copy;
108     }
109
110     release_resource(&cache->resource);
111     return ret;
112 }
113
114 dword_t write_cache(cache_descriptor_t *cache, void *context, const byte_t *buffer, qword_t offset, dword_t length, dword_t *bytes_written)
115 {
116     dword_t ret = ERR_SUCCESS;
117     qword_t i;
118     qword_t first_block = offset / (qword_t)cache->block_size;
119     qword_t last_block = (offset + length - 1) / (qword_t)cache->block_size;
120
121     acquire_resource_exclusive(&cache->resource);
122
123     for (i = first_block; i <= last_block; i++)
124     {
125         dword_t start_offset = 0, bytes_to_copy = cache->block_size;
126         avl_tree_t *element = avl_tree_lookup(cache->root, i);
127
128         if (element == NULL)
129         {
130             cache_entry_t *new_entry = (cache_entry_t*)heap_alloc(&evictable_heap, sizeof(cache_entry_t) + cache->block_size);
131             if (new_entry == NULL)
132             {
133                 ret = ERR_NOMEMORY;
134                 break;
135             }
136
137             new_entry->tree.key = i;
138             new_entry->dirty = FALSE;
139
140             ret = cache->read_proc(context, new_entry->data, i * (qword_t)cache->block_size, cache->block_size, NULL);
141             if (ret != ERR_SUCCESS)
142             {
143                 heap_free(&evictable_heap, new_entry);
144                 break;
145             }
146
147             avl_tree_insert(&cache->root, &new_entry->tree);
148             element = &new_entry->tree;
149         }
150
151         cache_entry_t *entry = CONTAINER_OF(element, cache_entry_t, tree);
152         if (i == first_block) start_offset = (dword_t)(offset % (qword_t)cache->block_size);
153         if (i == last_block) bytes_to_copy = (dword_t)((offset + length) % (qword_t)cache->block_size) - start_offset;
154
155         memcpy(&entry->data[start_offset], &buffer[(i - first_block) * cache->block_size], bytes_to_copy);
156         *bytes_written += bytes_to_copy;
157
158         if (cache->flags & CACHE_WRITE_THROUGH)
159         {
160             dword_t written;
161             ret = cache->write_proc(context, entry->data, i * (qword_t)cache->block_size, cache->block_size, &written);
162             if ((ret != ERR_SUCCESS) || (written != cache->block_size)) entry->dirty = TRUE;
163         }
164         else
165         {
166             entry->dirty = TRUE;
167         }
168     }
169
170     release_resource(&cache->resource);
171     return ret;
172 }
173
174 dword_t flush_cache(cache_descriptor_t *cache, void *context)
175 {
176     flush_cache_internal(cache, cache->root, context);
177     return ERR_SUCCESS;
178 }