1 /*
2 * Copyright (c) 2006-2022 Ali Mashtizadeh
3 * All rights reserved.
4 */
5
6 #include <stdbool.h>
7 #include <stdint.h>
8 #include <string.h>
9
10 #include <sys/kassert.h>
11 #include <sys/kdebug.h>
12 #include <sys/kmem.h>
13 #include <sys/spinlock.h>
14 #include <sys/disk.h>
15 #include <sys/bufcache.h>
16 #include <errno.h>
17
18 Spinlock cacheLock;
19 XMem *diskBuf;
20
21 static TAILQ_HEAD(CacheHashTable, BufCacheEntry) *hashTable;
22 static TAILQ_HEAD(LRUCacheList, BufCacheEntry) lruList;
23 static uint64_t cacheHit;
24 static uint64_t cacheMiss;
25 static uint64_t cacheAlloc;
26 static Slab cacheEntrySlab;
27
28 DEFINE_SLAB(BufCacheEntry, &cacheEntrySlab);
29
30 #define CACHESIZE (16*1024*1024)
31 #define HASHTABLEENTRIES 128
32 #define BLOCKSIZE (16*1024)
33
34 /**
35 * BufCache_Init --
36 *
37 * Initialize the system buffer cache.
38 */
39 void
BufCache_Init()40 BufCache_Init()
41 {
42 int i;
43
44 Spinlock_Init(&cacheLock, "BufCache Lock", SPINLOCK_TYPE_NORMAL);
45
46 diskBuf = XMem_New();
47 if (!diskBuf)
48 Panic("BufCache: Cannot create XMem region\n");
49
50 if (!XMem_Allocate(diskBuf, CACHESIZE))
51 Panic("BufCache: Cannot back XMem region\n");
52
53 TAILQ_INIT(&lruList);
54
55 hashTable = PAlloc_AllocPage();
56 if (!hashTable)
57 Panic("BufCache: Cannot allocate hash table\n");
58 for (i = 0; i < HASHTABLEENTRIES; i++) {
59 TAILQ_INIT(&hashTable[i]);
60 }
61
62 Slab_Init(&cacheEntrySlab, "BufCacheEntry Slab", sizeof(BufCacheEntry), 16);
63
64 // Initialize cache
65 uintptr_t bufBase = XMem_GetBase(diskBuf);
66 for (i = 0; i < CACHESIZE/BLOCKSIZE; i++) {
67 BufCacheEntry *e = BufCacheEntry_Alloc();
68 if (!e) {
69 Panic("BufCache: Cannot allocate cache entry\n");
70 }
71
72 memset(e, 0, sizeof(*e));
73 e->disk = NULL;
74 e->buffer = (void *)(bufBase + BLOCKSIZE * i);
75 TAILQ_INSERT_TAIL(&lruList, e, lruEntry);
76 }
77
78 cacheHit = 0;
79 cacheMiss = 0;
80 cacheAlloc = 0;
81 }
82
83 /**
84 * BufCacheLookup --
85 *
86 * Looks up a buffer cache entry that can be used by BufCache_Alloc or
87 * BufCache_Read to allocate the underlying buffer.
88 *
89 * @param [in] disk Disk object
90 * @param [in] diskOffset Block offset within the disk
91 * @param [out] entry If successful, this contains the buffer cache entry.
92 *
93 * @retval 0 if successful
94 * @return ENOENT if not present.
95 */
96 static int
BufCacheLookup(Disk * disk,uint64_t diskOffset,BufCacheEntry ** entry)97 BufCacheLookup(Disk *disk, uint64_t diskOffset, BufCacheEntry **entry)
98 {
99 struct CacheHashTable *table;
100 BufCacheEntry *e;
101
102 // Check hash table
103 table = &hashTable[diskOffset % HASHTABLEENTRIES];
104 TAILQ_FOREACH(e, table, htEntry) {
105 if (e->disk == disk && e->diskOffset == diskOffset) {
106 e->refCount++;
107 if (e->refCount == 1) {
108 TAILQ_REMOVE(&lruList, e, lruEntry);
109 }
110 *entry = e;
111 return 0;
112 }
113 }
114
115 *entry = NULL;
116 return ENOENT;
117 }
118
119 /**
120 * BufCacheAlloc --
121 *
122 * Allocates a buffer cache entry that can be used by BufCache_Alloc or
123 * BufCache_Read to allocate the underlying buffer..
124 *
125 * @param [in] disk Disk object
126 * @param [in] diskOffset Block offset within the disk
127 * @param [out] entry If successful, this contains the buffer cache entry.
128 *
129 * @retval 0 if successful.
130 * @return ENOMEM if there's no buffer cache entries free.
131 */
132 static int
BufCacheAlloc(Disk * disk,uint64_t diskOffset,BufCacheEntry ** entry)133 BufCacheAlloc(Disk *disk, uint64_t diskOffset, BufCacheEntry **entry)
134 {
135 struct CacheHashTable *table;
136 BufCacheEntry *e;
137
138 // Allocate from LRU list
139 e = TAILQ_FIRST(&lruList);
140 if (e == NULL) {
141 kprintf("BufCache: No space left!\n");
142 return ENOMEM;
143 }
144 TAILQ_REMOVE(&lruList, e, lruEntry);
145
146 // Remove from hash table
147 if (e->disk != NULL) {
148 table = &hashTable[e->diskOffset % HASHTABLEENTRIES];
149 TAILQ_REMOVE(table, e, htEntry);
150 }
151
152 // Initialize
153 e->disk = disk;
154 e->diskOffset = diskOffset;
155 e->refCount = 1;
156
157 // Reinsert into hash table
158 table = &hashTable[diskOffset % HASHTABLEENTRIES];
159 TAILQ_INSERT_HEAD(table, e, htEntry);
160 *entry = e;
161
162 return 0;
163 }
164
165 /**
166 * BufCache_Alloc --
167 *
168 * Allocate a buffer cache entry to allow writing new data to disk.
169 *
170 * @param [in] disk Disk object
171 * @param [in] diskOffset Block offset within the disk
172 * @param [out] entry If successful, this contains the buffer cache entry.
173 *
174 * @retval 0 if successful
175 * @return Otherwise returns an error code.
176 */
177 int
BufCache_Alloc(Disk * disk,uint64_t diskOffset,BufCacheEntry ** entry)178 BufCache_Alloc(Disk *disk, uint64_t diskOffset, BufCacheEntry **entry)
179 {
180 int status;
181
182 Spinlock_Lock(&cacheLock);
183
184 status = BufCacheLookup(disk, diskOffset, entry);
185 if (*entry == NULL) {
186 status = BufCacheAlloc(disk, diskOffset, entry);
187 }
188
189 cacheAlloc++;
190
191 Spinlock_Unlock(&cacheLock);
192
193 return status;
194 }
195
196 /**
197 * BufCache_Release --
198 *
199 * Release a buffer cache entry. If no other references are held the
200 * buffer cache entry is placed on the LRU list.
201 *
202 * @param [in] entry Buffer cache entry.
203 */
204 void
BufCache_Release(BufCacheEntry * entry)205 BufCache_Release(BufCacheEntry *entry)
206 {
207 Spinlock_Lock(&cacheLock);
208
209 entry->refCount--;
210 if (entry->refCount == 0) {
211 TAILQ_INSERT_TAIL(&lruList, entry, lruEntry);
212 }
213
214 Spinlock_Unlock(&cacheLock);
215 }
216
217 /**
218 * BufCache_Read --
219 *
220 * Read block from disk into the buffer cache.
221 *
222 * @param [in] disk Disk object
223 * @param [in] diskOffset Block offset within the disk
224 * @param [out] entry If successful, this contains the buffer cache entry.
225 * @retval 0 if successful
226 * @return Otherwise returns an error code.
227 */
228 int
BufCache_Read(Disk * disk,uint64_t diskOffset,BufCacheEntry ** entry)229 BufCache_Read(Disk *disk, uint64_t diskOffset, BufCacheEntry **entry)
230 {
231 int status;
232 void *buf;
233 SGArray sga;
234
235 Spinlock_Lock(&cacheLock);
236 status = BufCacheLookup(disk, diskOffset, entry);
237 if (*entry != NULL) {
238 cacheHit++;
239 Spinlock_Unlock(&cacheLock);
240 return status;
241 }
242 cacheMiss++;
243
244 status = BufCacheAlloc(disk, diskOffset, entry);
245 if (status != 0) {
246 Spinlock_Unlock(&cacheLock);
247 return status;
248 }
249
250 buf = (*entry)->buffer;
251 SGArray_Init(&sga);
252 SGArray_Append(&sga, diskOffset, BLOCKSIZE);
253
254 /*
255 * XXX: Need to avoid holding cacheLock while reading from the disk, but
256 * need ensure other cores doesn't issue a read to the same block.
257 */
258 status = Disk_Read(disk, buf, &sga, NULL, NULL);
259 Spinlock_Unlock(&cacheLock);
260
261 return status;
262 }
263
264 /**
265 * BufCache_Write --
266 *
267 * Write a buffer cache entry to disk.
268 *
269 * @retval 0 if successful
270 * @return Otherwise an error code is returned.
271 */
272 int
BufCache_Write(BufCacheEntry * entry)273 BufCache_Write(BufCacheEntry *entry)
274 {
275 void *buf = entry->buffer;
276 SGArray sga;
277
278 SGArray_Init(&sga);
279 SGArray_Append(&sga, entry->diskOffset, BLOCKSIZE);
280
281 return Disk_Write(entry->disk, buf, &sga, NULL, NULL);
282 }
283
284 static void
Debug_BufCache(int argc,const char * argv[])285 Debug_BufCache(int argc, const char *argv[])
286 {
287 kprintf("Hits: %lld\n", cacheHit);
288 kprintf("Misses: %lld\n", cacheMiss);
289 kprintf("Allocations: %lld\n", cacheAlloc);
290 }
291
292 REGISTER_DBGCMD(diskcache, "Display disk cache statistics", Debug_BufCache);
293
294