1 /* 2 * Page cache for QEMU 3 * The cache is base on a hash of the page address 4 * 5 * Copyright 2012 Red Hat, Inc. and/or its affiliates 6 * 7 * Authors: 8 * Orit Wasserman <owasserm@redhat.com> 9 * 10 * This work is licensed under the terms of the GNU GPL, version 2 or later. 11 * See the COPYING file in the top-level directory. 12 * 13 */ 14 15 #include "qemu/osdep.h" 16 17 #include "qemu-common.h" 18 #include "qemu/host-utils.h" 19 #include "migration/page_cache.h" 20 21 #ifdef DEBUG_CACHE 22 #define DPRINTF(fmt, ...) \ 23 do { fprintf(stdout, "cache: " fmt, ## __VA_ARGS__); } while (0) 24 #else 25 #define DPRINTF(fmt, ...) \ 26 do { } while (0) 27 #endif 28 29 /* the page in cache will not be replaced in two cycles */ 30 #define CACHED_PAGE_LIFETIME 2 31 32 typedef struct CacheItem CacheItem; 33 34 struct CacheItem { 35 uint64_t it_addr; 36 uint64_t it_age; 37 uint8_t *it_data; 38 }; 39 40 struct PageCache { 41 CacheItem *page_cache; 42 unsigned int page_size; 43 int64_t max_num_items; 44 int64_t num_items; 45 }; 46 47 PageCache *cache_init(int64_t num_pages, unsigned int page_size) 48 { 49 int64_t i; 50 51 PageCache *cache; 52 53 if (num_pages <= 0) { 54 DPRINTF("invalid number of pages\n"); 55 return NULL; 56 } 57 58 /* We prefer not to abort if there is no memory */ 59 cache = g_try_malloc(sizeof(*cache)); 60 if (!cache) { 61 DPRINTF("Failed to allocate cache\n"); 62 return NULL; 63 } 64 /* round down to the nearest power of 2 */ 65 if (!is_power_of_2(num_pages)) { 66 num_pages = pow2floor(num_pages); 67 DPRINTF("rounding down to %" PRId64 "\n", num_pages); 68 } 69 cache->page_size = page_size; 70 cache->num_items = 0; 71 cache->max_num_items = num_pages; 72 73 DPRINTF("Setting cache buckets to %" PRId64 "\n", cache->max_num_items); 74 75 /* We prefer not to abort if there is no memory */ 76 cache->page_cache = g_try_malloc((cache->max_num_items) * 77 sizeof(*cache->page_cache)); 78 if (!cache->page_cache) { 79 DPRINTF("Failed to allocate cache->page_cache\n"); 80 g_free(cache); 81 return NULL; 82 } 83 84 for (i = 0; i < cache->max_num_items; i++) { 85 cache->page_cache[i].it_data = NULL; 86 cache->page_cache[i].it_age = 0; 87 cache->page_cache[i].it_addr = -1; 88 } 89 90 return cache; 91 } 92 93 void cache_fini(PageCache *cache) 94 { 95 int64_t i; 96 97 g_assert(cache); 98 g_assert(cache->page_cache); 99 100 for (i = 0; i < cache->max_num_items; i++) { 101 g_free(cache->page_cache[i].it_data); 102 } 103 104 g_free(cache->page_cache); 105 cache->page_cache = NULL; 106 g_free(cache); 107 } 108 109 static size_t cache_get_cache_pos(const PageCache *cache, 110 uint64_t address) 111 { 112 g_assert(cache->max_num_items); 113 return (address / cache->page_size) & (cache->max_num_items - 1); 114 } 115 116 static CacheItem *cache_get_by_addr(const PageCache *cache, uint64_t addr) 117 { 118 size_t pos; 119 120 g_assert(cache); 121 g_assert(cache->page_cache); 122 123 pos = cache_get_cache_pos(cache, addr); 124 125 return &cache->page_cache[pos]; 126 } 127 128 uint8_t *get_cached_data(const PageCache *cache, uint64_t addr) 129 { 130 return cache_get_by_addr(cache, addr)->it_data; 131 } 132 133 bool cache_is_cached(const PageCache *cache, uint64_t addr, 134 uint64_t current_age) 135 { 136 CacheItem *it; 137 138 it = cache_get_by_addr(cache, addr); 139 140 if (it->it_addr == addr) { 141 /* update the it_age when the cache hit */ 142 it->it_age = current_age; 143 return true; 144 } 145 return false; 146 } 147 148 int cache_insert(PageCache *cache, uint64_t addr, const uint8_t *pdata, 149 uint64_t current_age) 150 { 151 152 CacheItem *it; 153 154 /* actual update of entry */ 155 it = cache_get_by_addr(cache, addr); 156 157 if (it->it_data && it->it_addr != addr && 158 it->it_age + CACHED_PAGE_LIFETIME > current_age) { 159 /* the cache page is fresh, don't replace it */ 160 return -1; 161 } 162 /* allocate page */ 163 if (!it->it_data) { 164 it->it_data = g_try_malloc(cache->page_size); 165 if (!it->it_data) { 166 DPRINTF("Error allocating page\n"); 167 return -1; 168 } 169 cache->num_items++; 170 } 171 172 memcpy(it->it_data, pdata, cache->page_size); 173 174 it->it_age = current_age; 175 it->it_addr = addr; 176 177 return 0; 178 } 179