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