// 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 #include "ftl_mc.h" #include // Configuration #define MC_DEBUG FALSE // Symbol Definitions // States allowed for a cached MPN #define FTLMC_CLEAN 0 #define FTLMC_DIRTY 1 // 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 status; // status of page - dirty/clean }; // Local Function Definitions // hash: Hash based on the page number // // Inputs: mpn = map page number to be hashed // num_mpgs = size of hash table in number of map pages // // Returns: Index into the hash table where value gets hashed // static ui32 hash(ui32 mpn, ui32 num_mpgs) { return (19823u * mpn + 321043U) % num_mpgs; } #if MC_DEBUG // check_cache: Debug function to check cache consistency // static void check_cache(FTLMC* cache, ui32 mpn) { int 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) { if (entry->hash_head != &cache->hash_tbl[hash(entry->mpn, cache->num_mpgs)]) { printf("\nFTL MAP CACHE: mpn = %u hash_head != hash()\n", mpn); exit(-1); } if (entry->hash_head[0] == NULL) { printf("\nFTL MAP CACHE: mpn = %u hash_head is NULL!\n", mpn); exit(-1); } } } if (count > 1) { printf("\nFTL MAP CACHE: mpn = %u is cached %d times\n", mpn, count); exit(-1); } } #endif // MC_DEBUG // 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 instance of an FTL map pages cache // // 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; ui32* data_space; // Allocate space to hold the cache structure. Return NULL if unable. cache = FsCalloc(1, sizeof(FTLMC)); if (cache == NULL) return NULL; // Set the cache FTL handle and read and write functions. cache->ftl = ftl; cache->read_TFS = rf; cache->write_TFS = wf; // Set the number of pages and their size. cache->num_mpgs = num_mpgs; cache->mpg_sz = mpg_sz; // Allocate memory for entries and hash table. Return NULL if unable. cache->entry = FsCalloc(num_mpgs, sizeof(ftlmcEntry)); if (cache->entry == NULL) { FsFree(cache); return NULL; } cache->hash_tbl = FsCalloc(num_mpgs, sizeof(ftlmcEntry*)); if (cache->hash_tbl == NULL) { FsFree(cache->entry); FsFree(cache); return NULL; } // Allocate memory to hold MPN contents. Return NULL if unable. data_space = FsAalloc(num_mpgs * mpg_sz); if (data_space == NULL) { FsFree(cache->hash_tbl); FsFree(cache->entry); FsFree(cache); return NULL; } // Initialize the cache and return cache handle. cache->entry[0].data = data_space; 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* memp = 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, memp += vpns_per_mpg) { // Assign pointer to cache entry's page data buffer. cache->entry[i].data = memp; // Initialize entry as unused and clean. cache->entry[i].mpn = (ui32)-1; cache->entry[i].status = FTLMC_CLEAN; // 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 cache for map pages // // Input: cache_ptr = pointer to cache handle // void ftlmcDelete(FTLMC** cache_ptr) { FTLMC* cache = *cache_ptr; // Deallocate all the memory associated with the cache. FsAfreeClear(&cache->entry[0].data); FsFree(cache->entry); FsFree(cache->hash_tbl); 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 hash_ndx; #if MC_DEBUG check_cache(cache, mpn); #endif // Find page's hash table entry. entry = cache->hash_tbl[hash(mpn, cache->num_mpgs)]; // Search hash entry for matching page number. for (; entry; entry = entry->next_hash) { // Check if page numbers match. if (entry->mpn == mpn) { // Move entry to end of LRU list. 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->status == FTLMC_CLEAN) { ++cache->num_dirty; PfAssert(cache->num_dirty <= cache->num_mpgs); entry->status = FTLMC_DIRTY; } // 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->status == FTLMC_DIRTY) { // Write old page contents. Return NULL if error. if (cache->write_TFS(cache->ftl, entry->mpn, entry->data)) return NULL; // Mark page clean and decrement dirty count. entry->status = FTLMC_CLEAN; 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->status == FTLMC_CLEAN) break; } // Move entry to end of LRU list. 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_TFS(cache->ftl, mpn, entry->data, new_ptr)) return NULL; // Set cache entry with new page info. entry->mpn = mpn; // Determine location in hash table for page. hash_ndx = hash(mpn, cache->num_mpgs); // Add new entry into the hash table. entry->prev_hash = NULL; if (cache->hash_tbl[hash_ndx]) { entry->next_hash = cache->hash_tbl[hash_ndx]; cache->hash_tbl[hash_ndx]->prev_hash = entry; } else entry->next_hash = NULL; cache->hash_tbl[hash_ndx] = entry; entry->hash_head = &cache->hash_tbl[hash_ndx]; // If map page write, mark entry as dirty and adjust dirty count. if (new_ptr == NULL) { entry->status = FTLMC_DIRTY; ++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 = &cache->entry[0]; // Loop over cache entries, looking for page to flush. for (; entry < &cache->entry[cache->num_mpgs]; ++entry) { // Continue if not a page number match. if (entry->mpn != mpn) continue; // Break if page is clean. if (entry->status == FTLMC_CLEAN) break; // Mark clean, adjust dirty count, save page, and return status. entry->status = FTLMC_CLEAN; PfAssert(cache->num_dirty); --cache->num_dirty; return cache->write_TFS(cache->ftl, entry->mpn, entry->data); } // Return success. return 0; } // ftlmcFlushMaps: Flush all dirty map pages // // Input: cache = cache handle // // Returns: 0 on success, -1 on failure // int ftlmcFlushMaps(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, save it and mark it clean. if (entry->status == FTLMC_DIRTY) { entry->status = FTLMC_CLEAN; PfAssert(cache->num_dirty); --cache->num_dirty; if (cache->write_TFS(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 // Find page's hash table entry. entry = cache->hash_tbl[hash(mpn, cache->num_mpgs)]; // Search hash entry for matching page number. for (; entry; entry = entry->next_hash) { // Check if page numbers match. if (entry->mpn == mpn) { // If not clean, mark page clean and decrement dirty count. if (entry->status != FTLMC_CLEAN) { entry->status = FTLMC_CLEAN; 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; }