| // Copyright 2018 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <sys.h> |
| |
| #include <ftl_private.h> |
| #include <ftl_mc.h> |
| #include <fsprivate.h> |
| |
| // Configuration |
| #define MC_DEBUG FALSE |
| |
| // Macro Definitions |
| #define MC_HASH(num, width) ((num) % (width)) |
| |
| // Type Declarations |
| struct ftlmc_cache_entry { |
| CircLink lru_link; // least recently used list entry |
| ftlmcEntry* next_hash; // hash bucket list is double linked list |
| ftlmcEntry* prev_hash; |
| ftlmcEntry** hash_head; // pointer to head of hash table list |
| ui32* data; // pointer to map page contents |
| ui32 mpn; // map page number |
| ui16 dirty; // status of page - dirty/clean |
| }; |
| |
| // Local Function Definitions |
| |
| #if MC_DEBUG |
| // check_cache: Debug function to check cache consistency |
| // |
| static void check_cache(FTLMC* cache, ui32 mpn) { |
| uint bin, count = 0; |
| ftlmcEntry* entry; |
| CircLink* link; |
| |
| // Loop over map page cache LRU list. |
| for (link = CIRC_LIST_HEAD(&cache->lru_list);; link = link->next_bck) { |
| // Break at list end. |
| if (CIRC_LIST_AT_END(link, &cache->lru_list)) |
| break; |
| |
| // Convert 'lru_link' pointer to entry pointer. |
| entry = (ftlmcEntry*)((ui8*)link - offsetof(ftlmcEntry, lru_link)); |
| |
| // Check if entry is used. |
| if (entry->mpn != (ui32)-1) { |
| bin = MC_HASH(entry->mpn, cache->num_mpgs); |
| if (entry->hash_head != &cache->hash_tbl[bin]) { |
| printf("\nFTL MAP CACHE: mpn = %u hash_head != hash()\n", mpn); |
| PfAssert(0); |
| } |
| if (entry->hash_head[0] == NULL) { |
| printf("\nFTL MAP CACHE: mpn = %u hash_head is NULL!\n", mpn); |
| PfAssert(0); |
| } |
| } |
| } |
| |
| if (count > 1) { |
| printf("\nFTL MAP CACHE: mpn = %u is cached %d times\n", mpn, count); |
| PfAssert(0); |
| } |
| } |
| #endif // MC_DEBUG |
| |
| // in_cache: Check if map page is in the cache |
| // |
| // Inputs: cache = cache handle |
| // mpn = map page number |
| // |
| // Returns: Pointer to entry if cached, NULL otherwise |
| // |
| static ftlmcEntry* in_cache(FTLMC* cache, ui32 mpn) { |
| uint bin; |
| ftlmcEntry* entry; |
| |
| #if MC_DEBUG |
| check_cache(cache, mpn); |
| #endif |
| |
| // Search hash bin for matching page number. |
| bin = MC_HASH(mpn, cache->num_mpgs); |
| for (entry = cache->hash_tbl[bin]; entry; entry = entry->next_hash) |
| if (entry->mpn == mpn) |
| return entry; |
| |
| // Page is not in cache, return NULL. |
| return NULL; |
| } |
| |
| // Global Function Definitions |
| |
| // ftlmcRAM: Return RAM used FTL cache |
| // |
| // Input: cache = cache handle or NULL |
| // |
| // Returns: RAM usage in bytes |
| // |
| ui32 ftlmcRAM(const FTLMC* cache) { |
| return cache |
| ? sizeof(FTLMC) + |
| cache->num_mpgs * (sizeof(ftlmcEntry) + cache->mpg_sz + sizeof(ftlmcEntry*)) |
| : 0; |
| } |
| |
| // ftlmcNew: Create a new FTL map page cache instance. |
| // |
| // Inputs: ftl = handle to FTL using this cache |
| // num_mpgs = number of map pages the cache holds |
| // wf = write map page function in case of cache miss |
| // rf = read map page function in case of cache miss |
| // mpg_sz = map page size in bytes |
| // |
| // Returns: Handle to new cache if successful, NULL on error |
| // |
| FTLMC* ftlmcNew(void* ftl, ui32 num_mpgs, ftlmcFuncW wf, ftlmcFuncR rf, ui32 mpg_sz) { |
| FTLMC* cache; |
| size_t size; |
| void* mem; |
| |
| // Allocate memory for the cache control block, entries, and hash |
| // table. Return NULL if unable. |
| size = sizeof(FTLMC) + num_mpgs * (sizeof(ftlmcEntry) + sizeof(ftlmcEntry*)); |
| mem = FsCalloc(1, size); |
| if (mem == NULL) |
| return NULL; |
| |
| // Assign the allocated memory to cache data structures. |
| cache = mem; |
| mem = cache + 1; |
| cache->entry = mem; |
| mem = cache->entry + num_mpgs; |
| cache->hash_tbl = mem; |
| |
| // Initialize the cache values and callback pointers. |
| cache->ftl = ftl; |
| cache->read = rf; |
| cache->write = wf; |
| cache->num_mpgs = num_mpgs; |
| cache->mpg_sz = mpg_sz; |
| |
| // Allocate aligned memory for MPN contents. Return NULL if unable. |
| mem = FsAalloc(num_mpgs * mpg_sz); |
| if (mem == NULL) { |
| FsFree(cache); |
| return NULL; |
| } |
| |
| // Initialize the cache and return cache handle. |
| cache->entry[0].data = mem; |
| ftlmcInit(cache); |
| return cache; |
| } |
| |
| // ftlmcInit: Initialize the cache |
| // |
| // Input: cache = handle to cache |
| // |
| void ftlmcInit(FTLMC* cache) { |
| ui32 i, vpns_per_mpg = cache->mpg_sz / sizeof(ui32); |
| ui32* mem = cache->entry[0].data; |
| |
| // Initialize cache's least recently used list. |
| CIRC_LIST_INIT(&cache->lru_list); |
| |
| // Loop to initialize each cache entry. |
| for (i = 0; i < cache->num_mpgs; ++i, mem += vpns_per_mpg) { |
| // Assign pointer to cache entry's page data buffer. |
| cache->entry[i].data = mem; |
| |
| // Initialize entry as unused and clean. |
| cache->entry[i].mpn = (ui32)-1; |
| cache->entry[i].dirty = FALSE; |
| |
| // Append entry to cache's least recently used list. |
| CIRC_LIST_APPEND(&cache->entry[i].lru_link, &cache->lru_list); |
| |
| // Initialize the entry hash table pointers. |
| cache->entry[i].hash_head = NULL; |
| cache->hash_tbl[i] = NULL; |
| } |
| |
| // There are no dirty entries at this point. |
| cache->num_dirty = 0; |
| } //lint !e429 |
| |
| // ftlmcDelete: Delete an FTL map page cache. |
| // |
| // Input: cache_ptr = pointer to cache handle |
| // |
| void ftlmcDelete(FTLMC** cache_ptr) { |
| FTLMC* cache = *cache_ptr; |
| |
| // Deallocate all memory associated with the cache. |
| FsAfreeClear(&cache->entry[0].data); |
| FsFree(cache); |
| |
| // Reset pointer to cache handle. |
| *cache_ptr = NULL; |
| } |
| |
| // ftlmcGetPage: Retrieve contents of map page via cache |
| // |
| // Inputs: cache = cache handle |
| // mpn = map page number |
| // new_ptr = new map flag address if read, else NULL |
| // Output: *new_ptr = TRUE if new mapping for read |
| // |
| // Returns: Pointer to map page data on success, NULL on error |
| // |
| void* ftlmcGetPage(FTLMC* cache, ui32 mpn, int* new_ptr) { |
| ftlmcEntry* entry; |
| CircLink* link; |
| uint bin; |
| |
| #if MC_DEBUG |
| check_cache(cache, mpn); |
| #endif |
| |
| // Check if specified page is in the cache. |
| entry = in_cache(cache, mpn); |
| if (entry) { |
| // Move entry to LRU list tail (MRU position). |
| CIRC_NODE_REMOVE(&entry->lru_link); |
| CIRC_LIST_APPEND(&entry->lru_link, &cache->lru_list); |
| |
| // If reading, flag that this page is mapped (because cached). |
| if (new_ptr) |
| *new_ptr = FALSE; |
| |
| // Else writing. If entry was clean, mark it dirty. |
| else if (entry->dirty == FALSE) { |
| ++cache->num_dirty; |
| PfAssert(cache->num_dirty <= cache->num_mpgs); |
| entry->dirty = TRUE; |
| } |
| |
| // Return pointer to page data. |
| return entry->data; |
| } |
| |
| // Not cached. Search LRU list for least recently used clean entry |
| // If none found, use least recently used entry (head of LRU list). |
| for (link = CIRC_LIST_HEAD(&cache->lru_list);; link = link->next_bck) { |
| // Check if at end of least recently used list. |
| if (CIRC_LIST_AT_END(link, &cache->lru_list)) { |
| // Re-use cache's least recently used entry (its LRU head). |
| link = CIRC_LIST_HEAD(&cache->lru_list); |
| |
| // Convert 'lru_link' pointer to entry pointer. |
| entry = (void*)((ui8*)link - offsetof(ftlmcEntry, lru_link)); |
| |
| // Check if page is dirty. |
| if (entry->dirty) { |
| // Write old page contents. Return NULL if error. |
| if (cache->write(cache->ftl, entry->mpn, entry->data)) |
| return NULL; |
| |
| // Mark page clean and decrement dirty count. |
| entry->dirty = FALSE; |
| PfAssert(cache->num_dirty); |
| --cache->num_dirty; |
| } |
| |
| // Break to use this entry. |
| break; |
| } |
| |
| // Convert 'lru_link' pointer to entry pointer. |
| entry = (ftlmcEntry*)((ui8*)link - offsetof(ftlmcEntry, lru_link)); |
| |
| // Break to use this entry if it is clean. |
| if (entry->dirty == FALSE) |
| break; |
| } |
| |
| // Move entry to LRU list tail (MRU position). |
| CIRC_NODE_REMOVE(&entry->lru_link); |
| CIRC_LIST_APPEND(&entry->lru_link, &cache->lru_list); |
| |
| // If the entry is in the hash table, remove it from there. |
| if (entry->hash_head) { |
| // If entry is not first, update previous one, else update head. |
| if (entry->prev_hash) |
| entry->prev_hash->next_hash = entry->next_hash; |
| else |
| *(entry->hash_head) = entry->next_hash; |
| |
| // If there is a next entry (entry is not end-of-list), update it. |
| if (entry->next_hash) |
| entry->next_hash->prev_hash = entry->prev_hash; |
| } |
| |
| // Read new page into the cache. Return NULL if error. |
| if (cache->read(cache->ftl, mpn, entry->data, new_ptr)) |
| return NULL; |
| |
| // Set cache entry with new page info. |
| entry->mpn = mpn; |
| |
| // Add new entry to the hash table at beginning of its bucket list. |
| entry->prev_hash = NULL; |
| bin = MC_HASH(mpn, cache->num_mpgs); |
| if (cache->hash_tbl[bin]) { |
| entry->next_hash = cache->hash_tbl[bin]; |
| cache->hash_tbl[bin]->prev_hash = entry; |
| } else { |
| entry->next_hash = NULL; |
| } |
| cache->hash_tbl[bin] = entry; |
| entry->hash_head = &cache->hash_tbl[bin]; |
| |
| // If map page write, mark entry as dirty and adjust dirty count. |
| if (new_ptr == NULL) { |
| entry->dirty = TRUE; |
| ++cache->num_dirty; |
| PfAssert(cache->num_dirty <= cache->num_mpgs); |
| } |
| |
| // Return pointer to page data. |
| return entry->data; |
| } |
| |
| // ftlmcFlushPage: Search cache for dirty page to flush |
| // |
| // Inputs: cache = cache handle |
| // mpn = MPN to flush |
| // |
| // Returns: 0 on success, -1 on failure |
| // |
| int ftlmcFlushPage(FTLMC* cache, ui32 mpn) { |
| ftlmcEntry* entry; |
| |
| // Check if specified page is cached. |
| entry = in_cache(cache, mpn); |
| if (entry) { |
| // Move entry to LRU list head (LRU position). |
| CIRC_NODE_REMOVE(&entry->lru_link); |
| CIRC_LIST_INSERT(&entry->lru_link, &cache->lru_list); |
| |
| // Check if page is dirty. |
| if (entry->dirty) |
| { |
| // Mark clean, adjust dirty count, save page, and return status. |
| entry->dirty = FALSE; |
| PfAssert(cache->num_dirty); |
| --cache->num_dirty; |
| return cache->write(cache->ftl, entry->mpn, entry->data); |
| } |
| } |
| |
| // Return success. |
| return 0; |
| } |
| |
| // ftlmcFlushMap: Flush all dirty map pages |
| // |
| // Input: cache = cache handle |
| // |
| // Returns: 0 on success, -1 on failure |
| // |
| int ftlmcFlushMap(FTLMC* cache) { |
| ftlmcEntry* entry = &cache->entry[0]; |
| |
| // Loop over cache entries, flushing each dirty one. |
| for (; entry < &cache->entry[cache->num_mpgs]; ++entry) { |
| // Break if all map pages are clean. |
| if (cache->num_dirty == 0) |
| break; |
| |
| // If this page is dirty, mark it clean and save it. |
| if (entry->dirty) { |
| entry->dirty = FALSE; |
| PfAssert(cache->num_dirty); |
| --cache->num_dirty; |
| if (cache->write(cache->ftl, entry->mpn, entry->data)) |
| return -1; |
| } |
| } |
| |
| // No more dirty entries at this point. Return success. |
| PfAssert(cache->num_dirty == 0); |
| return 0; |
| } |
| |
| // ftlmcInCache: Check if map page to be written is in the cache |
| // |
| // Inputs: cache = cache handle |
| // mpn = map page number |
| // |
| // Returns: Pointer to page data, NULL if page not in cache |
| // |
| ui32* ftlmcInCache(FTLMC* cache, ui32 mpn) { |
| ftlmcEntry* entry; |
| |
| #if MC_DEBUG |
| check_cache(cache, mpn); |
| #endif |
| |
| // Check if specified page is in the cache. |
| entry = in_cache(cache, mpn); |
| if (entry) { |
| // Move entry to LRU list head (LRU position). |
| CIRC_NODE_REMOVE(&entry->lru_link); |
| CIRC_LIST_INSERT(&entry->lru_link, &cache->lru_list); |
| |
| // If dirty, mark page clean and decrement dirty count. |
| if (entry->dirty) { |
| entry->dirty = FALSE; |
| PfAssert(cache->num_dirty); |
| --cache->num_dirty; |
| } |
| |
| // Return pointer to cached page's data. |
| return entry->data; |
| } |
| |
| // Page is not in cache, return NULL. |
| return NULL; |
| } |
| |