| // SPDX-License-Identifier: GPL-2.0-only | |
| /* | |
| * Copyright 2023 Red Hat | |
| */ | |
| #include "block-map.h" | |
| #include <linux/bio.h> | |
| #include <linux/ratelimit.h> | |
| #include "errors.h" | |
| #include "logger.h" | |
| #include "memory-alloc.h" | |
| #include "permassert.h" | |
| #include "action-manager.h" | |
| #include "admin-state.h" | |
| #include "completion.h" | |
| #include "constants.h" | |
| #include "data-vio.h" | |
| #include "encodings.h" | |
| #include "io-submitter.h" | |
| #include "physical-zone.h" | |
| #include "recovery-journal.h" | |
| #include "slab-depot.h" | |
| #include "status-codes.h" | |
| #include "types.h" | |
| #include "vdo.h" | |
| #include "vio.h" | |
| #include "wait-queue.h" | |
| /** | |
| * DOC: Block map eras | |
| * | |
| * The block map era, or maximum age, is used as follows: | |
| * | |
| * Each block map page, when dirty, records the earliest recovery journal block sequence number of | |
| * the changes reflected in that dirty block. Sequence numbers are classified into eras: every | |
| * @maximum_age sequence numbers, we switch to a new era. Block map pages are assigned to eras | |
| * according to the sequence number they record. | |
| * | |
| * In the current (newest) era, block map pages are not written unless there is cache pressure. In | |
| * the next oldest era, each time a new journal block is written 1/@maximum_age of the pages in | |
| * this era are issued for write. In all older eras, pages are issued for write immediately. | |
| */ | |
| struct page_descriptor { | |
| root_count_t root_index; | |
| height_t height; | |
| page_number_t page_index; | |
| slot_number_t slot; | |
| } __packed; | |
| union page_key { | |
| struct page_descriptor descriptor; | |
| u64 key; | |
| }; | |
| struct write_if_not_dirtied_context { | |
| struct block_map_zone *zone; | |
| u8 generation; | |
| }; | |
| struct block_map_tree_segment { | |
| struct tree_page *levels[VDO_BLOCK_MAP_TREE_HEIGHT]; | |
| }; | |
| struct block_map_tree { | |
| struct block_map_tree_segment *segments; | |
| }; | |
| struct forest { | |
| struct block_map *map; | |
| size_t segments; | |
| struct boundary *boundaries; | |
| struct tree_page **pages; | |
| struct block_map_tree trees[]; | |
| }; | |
| struct cursor_level { | |
| page_number_t page_index; | |
| slot_number_t slot; | |
| }; | |
| struct cursors; | |
| struct cursor { | |
| struct vdo_waiter waiter; | |
| struct block_map_tree *tree; | |
| height_t height; | |
| struct cursors *parent; | |
| struct boundary boundary; | |
| struct cursor_level levels[VDO_BLOCK_MAP_TREE_HEIGHT]; | |
| struct pooled_vio *vio; | |
| }; | |
| struct cursors { | |
| struct block_map_zone *zone; | |
| struct vio_pool *pool; | |
| vdo_entry_callback_fn entry_callback; | |
| struct vdo_completion *completion; | |
| root_count_t active_roots; | |
| struct cursor cursors[]; | |
| }; | |
| static const physical_block_number_t NO_PAGE = 0xFFFFFFFFFFFFFFFF; | |
| /* Used to indicate that the page holding the location of a tree root has been "loaded". */ | |
| static const physical_block_number_t VDO_INVALID_PBN = 0xFFFFFFFFFFFFFFFF; | |
| const struct block_map_entry UNMAPPED_BLOCK_MAP_ENTRY = { | |
| .mapping_state = VDO_MAPPING_STATE_UNMAPPED & 0x0F, | |
| .pbn_high_nibble = 0, | |
| .pbn_low_word = __cpu_to_le32(VDO_ZERO_BLOCK & UINT_MAX), | |
| }; | |
| #define LOG_INTERVAL 4000 | |
| #define DISPLAY_INTERVAL 100000 | |
| /* | |
| * For adjusting VDO page cache statistic fields which are only mutated on the logical zone thread. | |
| * Prevents any compiler shenanigans from affecting other threads reading those stats. | |
| */ | |
| #define ADD_ONCE(value, delta) WRITE_ONCE(value, (value) + (delta)) | |
| static inline bool is_dirty(const struct page_info *info) | |
| { | |
| return info->state == PS_DIRTY; | |
| } | |
| static inline bool is_present(const struct page_info *info) | |
| { | |
| return (info->state == PS_RESIDENT) || (info->state == PS_DIRTY); | |
| } | |
| static inline bool is_in_flight(const struct page_info *info) | |
| { | |
| return (info->state == PS_INCOMING) || (info->state == PS_OUTGOING); | |
| } | |
| static inline bool is_incoming(const struct page_info *info) | |
| { | |
| return info->state == PS_INCOMING; | |
| } | |
| static inline bool is_outgoing(const struct page_info *info) | |
| { | |
| return info->state == PS_OUTGOING; | |
| } | |
| static inline bool is_valid(const struct page_info *info) | |
| { | |
| return is_present(info) || is_outgoing(info); | |
| } | |
| static char *get_page_buffer(struct page_info *info) | |
| { | |
| struct vdo_page_cache *cache = info->cache; | |
| return &cache->pages[(info - cache->infos) * VDO_BLOCK_SIZE]; | |
| } | |
| static inline struct vdo_page_completion *page_completion_from_waiter(struct vdo_waiter *waiter) | |
| { | |
| struct vdo_page_completion *completion; | |
| if (waiter == NULL) | |
| return NULL; | |
| completion = container_of(waiter, struct vdo_page_completion, waiter); | |
| vdo_assert_completion_type(&completion->completion, VDO_PAGE_COMPLETION); | |
| return completion; | |
| } | |
| /** | |
| * initialize_info() - Initialize all page info structures and put them on the free list. | |
| * @cache: The page cache. | |
| * | |
| * Return: VDO_SUCCESS or an error. | |
| */ | |
| static int initialize_info(struct vdo_page_cache *cache) | |
| { | |
| struct page_info *info; | |
| INIT_LIST_HEAD(&cache->free_list); | |
| for (info = cache->infos; info < cache->infos + cache->page_count; info++) { | |
| int result; | |
| info->cache = cache; | |
| info->state = PS_FREE; | |
| info->pbn = NO_PAGE; | |
| result = create_metadata_vio(cache->vdo, VIO_TYPE_BLOCK_MAP, | |
| VIO_PRIORITY_METADATA, info, | |
| get_page_buffer(info), &info->vio); | |
| if (result != VDO_SUCCESS) | |
| return result; | |
| /* The thread ID should never change. */ | |
| info->vio->completion.callback_thread_id = cache->zone->thread_id; | |
| INIT_LIST_HEAD(&info->state_entry); | |
| list_add_tail(&info->state_entry, &cache->free_list); | |
| INIT_LIST_HEAD(&info->lru_entry); | |
| } | |
| return VDO_SUCCESS; | |
| } | |
| /** | |
| * allocate_cache_components() - Allocate components of the cache which require their own | |
| * allocation. | |
| * @cache: The page cache. | |
| * | |
| * The caller is responsible for all clean up on errors. | |
| * | |
| * Return: VDO_SUCCESS or an error code. | |
| */ | |
| static int __must_check allocate_cache_components(struct vdo_page_cache *cache) | |
| { | |
| u64 size = cache->page_count * (u64) VDO_BLOCK_SIZE; | |
| int result; | |
| result = vdo_allocate(cache->page_count, "page infos", &cache->infos); | |
| if (result != VDO_SUCCESS) | |
| return result; | |
| result = vdo_allocate_memory(size, VDO_BLOCK_SIZE, "cache pages", &cache->pages); | |
| if (result != VDO_SUCCESS) | |
| return result; | |
| result = vdo_int_map_create(cache->page_count, &cache->page_map); | |
| if (result != VDO_SUCCESS) | |
| return result; | |
| return initialize_info(cache); | |
| } | |
| /** | |
| * assert_on_cache_thread() - Assert that a function has been called on the VDO page cache's | |
| * thread. | |
| * @cache: The page cache. | |
| * @function_name: The funtion name to report if the assertion fails. | |
| */ | |
| static inline void assert_on_cache_thread(struct vdo_page_cache *cache, | |
| const char *function_name) | |
| { | |
| thread_id_t thread_id = vdo_get_callback_thread_id(); | |
| VDO_ASSERT_LOG_ONLY((thread_id == cache->zone->thread_id), | |
| "%s() must only be called on cache thread %d, not thread %d", | |
| function_name, cache->zone->thread_id, thread_id); | |
| } | |
| /** assert_io_allowed() - Assert that a page cache may issue I/O. */ | |
| static inline void assert_io_allowed(struct vdo_page_cache *cache) | |
| { | |
| VDO_ASSERT_LOG_ONLY(!vdo_is_state_quiescent(&cache->zone->state), | |
| "VDO page cache may issue I/O"); | |
| } | |
| /** report_cache_pressure() - Log and, if enabled, report cache pressure. */ | |
| static void report_cache_pressure(struct vdo_page_cache *cache) | |
| { | |
| ADD_ONCE(cache->stats.cache_pressure, 1); | |
| if (cache->waiter_count > cache->page_count) { | |
| if ((cache->pressure_report % LOG_INTERVAL) == 0) | |
| vdo_log_info("page cache pressure %u", cache->stats.cache_pressure); | |
| if (++cache->pressure_report >= DISPLAY_INTERVAL) | |
| cache->pressure_report = 0; | |
| } | |
| } | |
| /** | |
| * get_page_state_name() - Return the name of a page state. | |
| * @state: The page state to describe. | |
| * | |
| * If the page state is invalid a static string is returned and the invalid state is logged. | |
| * | |
| * Return: A pointer to a static page state name. | |
| */ | |
| static const char * __must_check get_page_state_name(enum vdo_page_buffer_state state) | |
| { | |
| int result; | |
| static const char * const state_names[] = { | |
| "FREE", "INCOMING", "FAILED", "RESIDENT", "DIRTY", "OUTGOING" | |
| }; | |
| BUILD_BUG_ON(ARRAY_SIZE(state_names) != PAGE_STATE_COUNT); | |
| result = VDO_ASSERT(state < ARRAY_SIZE(state_names), | |
| "Unknown page_state value %d", state); | |
| if (result != VDO_SUCCESS) | |
| return "[UNKNOWN PAGE STATE]"; | |
| return state_names[state]; | |
| } | |
| /** | |
| * update_counter() - Update the counter associated with a given state. | |
| * @info: The page info to count. | |
| * @delta: The delta to apply to the counter. | |
| */ | |
| static void update_counter(struct page_info *info, s32 delta) | |
| { | |
| struct block_map_statistics *stats = &info->cache->stats; | |
| switch (info->state) { | |
| case PS_FREE: | |
| ADD_ONCE(stats->free_pages, delta); | |
| return; | |
| case PS_INCOMING: | |
| ADD_ONCE(stats->incoming_pages, delta); | |
| return; | |
| case PS_OUTGOING: | |
| ADD_ONCE(stats->outgoing_pages, delta); | |
| return; | |
| case PS_FAILED: | |
| ADD_ONCE(stats->failed_pages, delta); | |
| return; | |
| case PS_RESIDENT: | |
| ADD_ONCE(stats->clean_pages, delta); | |
| return; | |
| case PS_DIRTY: | |
| ADD_ONCE(stats->dirty_pages, delta); | |
| return; | |
| default: | |
| return; | |
| } | |
| } | |
| /** update_lru() - Update the lru information for an active page. */ | |
| static void update_lru(struct page_info *info) | |
| { | |
| if (info->cache->lru_list.prev != &info->lru_entry) | |
| list_move_tail(&info->lru_entry, &info->cache->lru_list); | |
| } | |
| /** | |
| * set_info_state() - Set the state of a page_info and put it on the right list, adjusting | |
| * counters. | |
| * @info: The page info to update. | |
| * @new_state: The new state to set. | |
| */ | |
| static void set_info_state(struct page_info *info, enum vdo_page_buffer_state new_state) | |
| { | |
| if (new_state == info->state) | |
| return; | |
| update_counter(info, -1); | |
| info->state = new_state; | |
| update_counter(info, 1); | |
| switch (info->state) { | |
| case PS_FREE: | |
| case PS_FAILED: | |
| list_move_tail(&info->state_entry, &info->cache->free_list); | |
| return; | |
| case PS_OUTGOING: | |
| list_move_tail(&info->state_entry, &info->cache->outgoing_list); | |
| return; | |
| case PS_DIRTY: | |
| return; | |
| default: | |
| list_del_init(&info->state_entry); | |
| } | |
| } | |
| /** set_info_pbn() - Set the pbn for an info, updating the map as needed. */ | |
| static int __must_check set_info_pbn(struct page_info *info, physical_block_number_t pbn) | |
| { | |
| struct vdo_page_cache *cache = info->cache; | |
| /* Either the new or the old page number must be NO_PAGE. */ | |
| int result = VDO_ASSERT((pbn == NO_PAGE) || (info->pbn == NO_PAGE), | |
| "Must free a page before reusing it."); | |
| if (result != VDO_SUCCESS) | |
| return result; | |
| if (info->pbn != NO_PAGE) | |
| vdo_int_map_remove(cache->page_map, info->pbn); | |
| info->pbn = pbn; | |
| if (pbn != NO_PAGE) { | |
| result = vdo_int_map_put(cache->page_map, pbn, info, true, NULL); | |
| if (result != VDO_SUCCESS) | |
| return result; | |
| } | |
| return VDO_SUCCESS; | |
| } | |
| /** reset_page_info() - Reset page info to represent an unallocated page. */ | |
| static int reset_page_info(struct page_info *info) | |
| { | |
| int result; | |
| result = VDO_ASSERT(info->busy == 0, "VDO Page must not be busy"); | |
| if (result != VDO_SUCCESS) | |
| return result; | |
| result = VDO_ASSERT(!vdo_waitq_has_waiters(&info->waiting), | |
| "VDO Page must not have waiters"); | |
| if (result != VDO_SUCCESS) | |
| return result; | |
| result = set_info_pbn(info, NO_PAGE); | |
| set_info_state(info, PS_FREE); | |
| list_del_init(&info->lru_entry); | |
| return result; | |
| } | |
| /** | |
| * find_free_page() - Find a free page. | |
| * @cache: The page cache. | |
| * | |
| * Return: A pointer to the page info structure (if found), NULL otherwise. | |
| */ | |
| static struct page_info * __must_check find_free_page(struct vdo_page_cache *cache) | |
| { | |
| struct page_info *info; | |
| info = list_first_entry_or_null(&cache->free_list, struct page_info, | |
| state_entry); | |
| if (info != NULL) | |
| list_del_init(&info->state_entry); | |
| return info; | |
| } | |
| /** | |
| * find_page() - Find the page info (if any) associated with a given pbn. | |
| * @cache: The page cache. | |
| * @pbn: The absolute physical block number of the page. | |
| * | |
| * Return: The page info for the page if available, or NULL if not. | |
| */ | |
| static struct page_info * __must_check find_page(struct vdo_page_cache *cache, | |
| physical_block_number_t pbn) | |
| { | |
| if ((cache->last_found != NULL) && (cache->last_found->pbn == pbn)) | |
| return cache->last_found; | |
| cache->last_found = vdo_int_map_get(cache->page_map, pbn); | |
| return cache->last_found; | |
| } | |
| /** | |
| * select_lru_page() - Determine which page is least recently used. | |
| * @cache: The page cache. | |
| * | |
| * Picks the least recently used from among the non-busy entries at the front of each of the lru | |
| * list. Since whenever we mark a page busy we also put it to the end of the list it is unlikely | |
| * that the entries at the front are busy unless the queue is very short, but not impossible. | |
| * | |
| * Return: A pointer to the info structure for a relevant page, or NULL if no such page can be | |
| * found. The page can be dirty or resident. | |
| */ | |
| static struct page_info * __must_check select_lru_page(struct vdo_page_cache *cache) | |
| { | |
| struct page_info *info; | |
| list_for_each_entry(info, &cache->lru_list, lru_entry) | |
| if ((info->busy == 0) && !is_in_flight(info)) | |
| return info; | |
| return NULL; | |
| } | |
| /* ASYNCHRONOUS INTERFACE BEYOND THIS POINT */ | |
| /** | |
| * complete_with_page() - Helper to complete the VDO Page Completion request successfully. | |
| * @info: The page info representing the result page. | |
| * @vdo_page_comp: The VDO page completion to complete. | |
| */ | |
| static void complete_with_page(struct page_info *info, | |
| struct vdo_page_completion *vdo_page_comp) | |
| { | |
| bool available = vdo_page_comp->writable ? is_present(info) : is_valid(info); | |
| if (!available) { | |
| vdo_log_error_strerror(VDO_BAD_PAGE, | |
| "Requested cache page %llu in state %s is not %s", | |
| (unsigned long long) info->pbn, | |
| get_page_state_name(info->state), | |
| vdo_page_comp->writable ? "present" : "valid"); | |
| vdo_fail_completion(&vdo_page_comp->completion, VDO_BAD_PAGE); | |
| return; | |
| } | |
| vdo_page_comp->info = info; | |
| vdo_page_comp->ready = true; | |
| vdo_finish_completion(&vdo_page_comp->completion); | |
| } | |
| /** | |
| * complete_waiter_with_error() - Complete a page completion with an error code. | |
| * @waiter: The page completion, as a waiter. | |
| * @result_ptr: A pointer to the error code. | |
| * | |
| * Implements waiter_callback_fn. | |
| */ | |
| static void complete_waiter_with_error(struct vdo_waiter *waiter, void *result_ptr) | |
| { | |
| int *result = result_ptr; | |
| vdo_fail_completion(&page_completion_from_waiter(waiter)->completion, *result); | |
| } | |
| /** | |
| * complete_waiter_with_page() - Complete a page completion with a page. | |
| * @waiter: The page completion, as a waiter. | |
| * @page_info: The page info to complete with. | |
| * | |
| * Implements waiter_callback_fn. | |
| */ | |
| static void complete_waiter_with_page(struct vdo_waiter *waiter, void *page_info) | |
| { | |
| complete_with_page(page_info, page_completion_from_waiter(waiter)); | |
| } | |
| /** | |
| * distribute_page_over_waitq() - Complete a waitq of VDO page completions with a page result. | |
| * @info: The loaded page info. | |
| * @waitq: The list of waiting data_vios. | |
| * | |
| * Upon completion the waitq will be empty. | |
| * | |
| * Return: The number of pages distributed. | |
| */ | |
| static unsigned int distribute_page_over_waitq(struct page_info *info, | |
| struct vdo_wait_queue *waitq) | |
| { | |
| size_t num_pages; | |
| update_lru(info); | |
| num_pages = vdo_waitq_num_waiters(waitq); | |
| /* | |
| * Increment the busy count once for each pending completion so that this page does not | |
| * stop being busy until all completions have been processed. | |
| */ | |
| info->busy += num_pages; | |
| vdo_waitq_notify_all_waiters(waitq, complete_waiter_with_page, info); | |
| return num_pages; | |
| } | |
| /** | |
| * set_persistent_error() - Set a persistent error which all requests will receive in the future. | |
| * @cache: The page cache. | |
| * @context: A string describing what triggered the error. | |
| * @result: The error result to set on the cache. | |
| * | |
| * Once triggered, all enqueued completions will get this error. Any future requests will result in | |
| * this error as well. | |
| */ | |
| static void set_persistent_error(struct vdo_page_cache *cache, const char *context, | |
| int result) | |
| { | |
| struct page_info *info; | |
| /* If we're already read-only, there's no need to log. */ | |
| struct vdo *vdo = cache->vdo; | |
| if ((result != VDO_READ_ONLY) && !vdo_is_read_only(vdo)) { | |
| vdo_log_error_strerror(result, "VDO Page Cache persistent error: %s", | |
| context); | |
| vdo_enter_read_only_mode(vdo, result); | |
| } | |
| assert_on_cache_thread(cache, __func__); | |
| vdo_waitq_notify_all_waiters(&cache->free_waiters, | |
| complete_waiter_with_error, &result); | |
| cache->waiter_count = 0; | |
| for (info = cache->infos; info < cache->infos + cache->page_count; info++) { | |
| vdo_waitq_notify_all_waiters(&info->waiting, | |
| complete_waiter_with_error, &result); | |
| } | |
| } | |
| /** | |
| * validate_completed_page() - Check that a page completion which is being freed to the cache | |
| * referred to a valid page and is in a valid state. | |
| * @completion: The page completion to check. | |
| * @writable: Whether a writable page is required. | |
| * | |
| * Return: VDO_SUCCESS if the page was valid, otherwise as error | |
| */ | |
| static int __must_check validate_completed_page(struct vdo_page_completion *completion, | |
| bool writable) | |
| { | |
| int result; | |
| result = VDO_ASSERT(completion->ready, "VDO Page completion not ready"); | |
| if (result != VDO_SUCCESS) | |
| return result; | |
| result = VDO_ASSERT(completion->info != NULL, | |
| "VDO Page Completion must be complete"); | |
| if (result != VDO_SUCCESS) | |
| return result; | |
| result = VDO_ASSERT(completion->info->pbn == completion->pbn, | |
| "VDO Page Completion pbn must be consistent"); | |
| if (result != VDO_SUCCESS) | |
| return result; | |
| result = VDO_ASSERT(is_valid(completion->info), | |
| "VDO Page Completion page must be valid"); | |
| if (result != VDO_SUCCESS) | |
| return result; | |
| if (writable) { | |
| result = VDO_ASSERT(completion->writable, | |
| "VDO Page Completion must be writable"); | |
| if (result != VDO_SUCCESS) | |
| return result; | |
| } | |
| return VDO_SUCCESS; | |
| } | |
| static void check_for_drain_complete(struct block_map_zone *zone) | |
| { | |
| if (vdo_is_state_draining(&zone->state) && | |
| (zone->active_lookups == 0) && | |
| !vdo_waitq_has_waiters(&zone->flush_waiters) && | |
| !is_vio_pool_busy(zone->vio_pool) && | |
| (zone->page_cache.outstanding_reads == 0) && | |
| (zone->page_cache.outstanding_writes == 0)) { | |
| vdo_finish_draining_with_result(&zone->state, | |
| (vdo_is_read_only(zone->block_map->vdo) ? | |
| VDO_READ_ONLY : VDO_SUCCESS)); | |
| } | |
| } | |
| static void enter_zone_read_only_mode(struct block_map_zone *zone, int result) | |
| { | |
| vdo_enter_read_only_mode(zone->block_map->vdo, result); | |
| /* | |
| * We are in read-only mode, so we won't ever write any page out. | |
| * Just take all waiters off the waitq so the zone can drain. | |
| */ | |
| vdo_waitq_init(&zone->flush_waiters); | |
| check_for_drain_complete(zone); | |
| } | |
| static bool __must_check | |
| validate_completed_page_or_enter_read_only_mode(struct vdo_page_completion *completion, | |
| bool writable) | |
| { | |
| int result = validate_completed_page(completion, writable); | |
| if (result == VDO_SUCCESS) | |
| return true; | |
| enter_zone_read_only_mode(completion->info->cache->zone, result); | |
| return false; | |
| } | |
| /** | |
| * handle_load_error() - Handle page load errors. | |
| * @completion: The page read vio. | |
| */ | |
| static void handle_load_error(struct vdo_completion *completion) | |
| { | |
| int result = completion->result; | |
| struct page_info *info = completion->parent; | |
| struct vdo_page_cache *cache = info->cache; | |
| assert_on_cache_thread(cache, __func__); | |
| vio_record_metadata_io_error(as_vio(completion)); | |
| vdo_enter_read_only_mode(cache->zone->block_map->vdo, result); | |
| ADD_ONCE(cache->stats.failed_reads, 1); | |
| set_info_state(info, PS_FAILED); | |
| vdo_waitq_notify_all_waiters(&info->waiting, complete_waiter_with_error, &result); | |
| reset_page_info(info); | |
| /* | |
| * Don't decrement until right before calling check_for_drain_complete() to | |
| * ensure that the above work can't cause the page cache to be freed out from under us. | |
| */ | |
| cache->outstanding_reads--; | |
| check_for_drain_complete(cache->zone); | |
| } | |
| /** | |
| * page_is_loaded() - Callback used when a page has been loaded. | |
| * @completion: The vio which has loaded the page. Its parent is the page_info. | |
| */ | |
| static void page_is_loaded(struct vdo_completion *completion) | |
| { | |
| struct page_info *info = completion->parent; | |
| struct vdo_page_cache *cache = info->cache; | |
| nonce_t nonce = info->cache->zone->block_map->nonce; | |
| struct block_map_page *page; | |
| enum block_map_page_validity validity; | |
| assert_on_cache_thread(cache, __func__); | |
| page = (struct block_map_page *) get_page_buffer(info); | |
| validity = vdo_validate_block_map_page(page, nonce, info->pbn); | |
| if (validity == VDO_BLOCK_MAP_PAGE_BAD) { | |
| physical_block_number_t pbn = vdo_get_block_map_page_pbn(page); | |
| int result = vdo_log_error_strerror(VDO_BAD_PAGE, | |
| "Expected page %llu but got page %llu instead", | |
| (unsigned long long) info->pbn, | |
| (unsigned long long) pbn); | |
| vdo_continue_completion(completion, result); | |
| return; | |
| } | |
| if (validity == VDO_BLOCK_MAP_PAGE_INVALID) | |
| vdo_format_block_map_page(page, nonce, info->pbn, false); | |
| info->recovery_lock = 0; | |
| set_info_state(info, PS_RESIDENT); | |
| distribute_page_over_waitq(info, &info->waiting); | |
| /* | |
| * Don't decrement until right before calling check_for_drain_complete() to | |
| * ensure that the above work can't cause the page cache to be freed out from under us. | |
| */ | |
| cache->outstanding_reads--; | |
| check_for_drain_complete(cache->zone); | |
| } | |
| /** | |
| * handle_rebuild_read_error() - Handle a read error during a read-only rebuild. | |
| * @completion: The page load completion. | |
| */ | |
| static void handle_rebuild_read_error(struct vdo_completion *completion) | |
| { | |
| struct page_info *info = completion->parent; | |
| struct vdo_page_cache *cache = info->cache; | |
| assert_on_cache_thread(cache, __func__); | |
| /* | |
| * We are doing a read-only rebuild, so treat this as a successful read | |
| * of an uninitialized page. | |
| */ | |
| vio_record_metadata_io_error(as_vio(completion)); | |
| ADD_ONCE(cache->stats.failed_reads, 1); | |
| memset(get_page_buffer(info), 0, VDO_BLOCK_SIZE); | |
| vdo_reset_completion(completion); | |
| page_is_loaded(completion); | |
| } | |
| static void load_cache_page_endio(struct bio *bio) | |
| { | |
| struct vio *vio = bio->bi_private; | |
| struct page_info *info = vio->completion.parent; | |
| continue_vio_after_io(vio, page_is_loaded, info->cache->zone->thread_id); | |
| } | |
| /** | |
| * launch_page_load() - Begin the process of loading a page. | |
| * @info: The page info to launch. | |
| * @pbn: The absolute physical block number of the page to load. | |
| * | |
| * Return: VDO_SUCCESS or an error code. | |
| */ | |
| static int __must_check launch_page_load(struct page_info *info, | |
| physical_block_number_t pbn) | |
| { | |
| int result; | |
| vdo_action_fn callback; | |
| struct vdo_page_cache *cache = info->cache; | |
| assert_io_allowed(cache); | |
| result = set_info_pbn(info, pbn); | |
| if (result != VDO_SUCCESS) | |
| return result; | |
| result = VDO_ASSERT((info->busy == 0), "Page is not busy before loading."); | |
| if (result != VDO_SUCCESS) | |
| return result; | |
| set_info_state(info, PS_INCOMING); | |
| cache->outstanding_reads++; | |
| ADD_ONCE(cache->stats.pages_loaded, 1); | |
| callback = (cache->rebuilding ? handle_rebuild_read_error : handle_load_error); | |
| vdo_submit_metadata_vio(info->vio, pbn, load_cache_page_endio, | |
| callback, REQ_OP_READ | REQ_PRIO); | |
| return VDO_SUCCESS; | |
| } | |
| static void write_pages(struct vdo_completion *completion); | |
| /** handle_flush_error() - Handle errors flushing the layer. */ | |
| static void handle_flush_error(struct vdo_completion *completion) | |
| { | |
| struct page_info *info = completion->parent; | |
| vio_record_metadata_io_error(as_vio(completion)); | |
| set_persistent_error(info->cache, "flush failed", completion->result); | |
| write_pages(completion); | |
| } | |
| static void flush_endio(struct bio *bio) | |
| { | |
| struct vio *vio = bio->bi_private; | |
| struct page_info *info = vio->completion.parent; | |
| continue_vio_after_io(vio, write_pages, info->cache->zone->thread_id); | |
| } | |
| /** save_pages() - Attempt to save the outgoing pages by first flushing the layer. */ | |
| static void save_pages(struct vdo_page_cache *cache) | |
| { | |
| struct page_info *info; | |
| struct vio *vio; | |
| if ((cache->pages_in_flush > 0) || (cache->pages_to_flush == 0)) | |
| return; | |
| assert_io_allowed(cache); | |
| info = list_first_entry(&cache->outgoing_list, struct page_info, state_entry); | |
| cache->pages_in_flush = cache->pages_to_flush; | |
| cache->pages_to_flush = 0; | |
| ADD_ONCE(cache->stats.flush_count, 1); | |
| vio = info->vio; | |
| /* | |
| * We must make sure that the recovery journal entries that changed these pages were | |
| * successfully persisted, and thus must issue a flush before each batch of pages is | |
| * written to ensure this. | |
| */ | |
| vdo_submit_flush_vio(vio, flush_endio, handle_flush_error); | |
| } | |
| /** | |
| * schedule_page_save() - Add a page to the outgoing list of pages waiting to be saved. | |
| * @info: The page info to save. | |
| * | |
| * Once in the list, a page may not be used until it has been written out. | |
| */ | |
| static void schedule_page_save(struct page_info *info) | |
| { | |
| if (info->busy > 0) { | |
| info->write_status = WRITE_STATUS_DEFERRED; | |
| return; | |
| } | |
| info->cache->pages_to_flush++; | |
| info->cache->outstanding_writes++; | |
| set_info_state(info, PS_OUTGOING); | |
| } | |
| /** | |
| * launch_page_save() - Add a page to outgoing pages waiting to be saved, and then start saving | |
| * pages if another save is not in progress. | |
| * @info: The page info to save. | |
| */ | |
| static void launch_page_save(struct page_info *info) | |
| { | |
| schedule_page_save(info); | |
| save_pages(info->cache); | |
| } | |
| /** | |
| * completion_needs_page() - Determine whether a given vdo_page_completion (as a waiter) is | |
| * requesting a given page number. | |
| * @waiter: The page completion waiter to check. | |
| * @context: A pointer to the pbn of the desired page. | |
| * | |
| * Implements waiter_match_fn. | |
| * | |
| * Return: true if the page completion is for the desired page number. | |
| */ | |
| static bool completion_needs_page(struct vdo_waiter *waiter, void *context) | |
| { | |
| physical_block_number_t *pbn = context; | |
| return (page_completion_from_waiter(waiter)->pbn == *pbn); | |
| } | |
| /** | |
| * allocate_free_page() - Allocate a free page to the first completion in the waiting queue, and | |
| * any other completions that match it in page number. | |
| * @info: The page info to allocate a page for. | |
| */ | |
| static void allocate_free_page(struct page_info *info) | |
| { | |
| int result; | |
| struct vdo_waiter *oldest_waiter; | |
| physical_block_number_t pbn; | |
| struct vdo_page_cache *cache = info->cache; | |
| assert_on_cache_thread(cache, __func__); | |
| if (!vdo_waitq_has_waiters(&cache->free_waiters)) { | |
| if (cache->stats.cache_pressure > 0) { | |
| vdo_log_info("page cache pressure relieved"); | |
| WRITE_ONCE(cache->stats.cache_pressure, 0); | |
| } | |
| return; | |
| } | |
| result = reset_page_info(info); | |
| if (result != VDO_SUCCESS) { | |
| set_persistent_error(cache, "cannot reset page info", result); | |
| return; | |
| } | |
| oldest_waiter = vdo_waitq_get_first_waiter(&cache->free_waiters); | |
| pbn = page_completion_from_waiter(oldest_waiter)->pbn; | |
| /* | |
| * Remove all entries which match the page number in question and push them onto the page | |
| * info's waitq. | |
| */ | |
| vdo_waitq_dequeue_matching_waiters(&cache->free_waiters, completion_needs_page, | |
| &pbn, &info->waiting); | |
| cache->waiter_count -= vdo_waitq_num_waiters(&info->waiting); | |
| result = launch_page_load(info, pbn); | |
| if (result != VDO_SUCCESS) { | |
| vdo_waitq_notify_all_waiters(&info->waiting, | |
| complete_waiter_with_error, &result); | |
| } | |
| } | |
| /** | |
| * discard_a_page() - Begin the process of discarding a page. | |
| * @cache: The page cache. | |
| * | |
| * If no page is discardable, increments a count of deferred frees so that the next release of a | |
| * page which is no longer busy will kick off another discard cycle. This is an indication that the | |
| * cache is not big enough. | |
| * | |
| * If the selected page is not dirty, immediately allocates the page to the oldest completion | |
| * waiting for a free page. | |
| */ | |
| static void discard_a_page(struct vdo_page_cache *cache) | |
| { | |
| struct page_info *info = select_lru_page(cache); | |
| if (info == NULL) { | |
| report_cache_pressure(cache); | |
| return; | |
| } | |
| if (!is_dirty(info)) { | |
| allocate_free_page(info); | |
| return; | |
| } | |
| VDO_ASSERT_LOG_ONLY(!is_in_flight(info), | |
| "page selected for discard is not in flight"); | |
| cache->discard_count++; | |
| info->write_status = WRITE_STATUS_DISCARD; | |
| launch_page_save(info); | |
| } | |
| static void discard_page_for_completion(struct vdo_page_completion *vdo_page_comp) | |
| { | |
| struct vdo_page_cache *cache = vdo_page_comp->cache; | |
| cache->waiter_count++; | |
| vdo_waitq_enqueue_waiter(&cache->free_waiters, &vdo_page_comp->waiter); | |
| discard_a_page(cache); | |
| } | |
| /** | |
| * discard_page_if_needed() - Helper used to trigger a discard if the cache needs another free | |
| * page. | |
| * @cache: The page cache. | |
| */ | |
| static void discard_page_if_needed(struct vdo_page_cache *cache) | |
| { | |
| if (cache->waiter_count > cache->discard_count) | |
| discard_a_page(cache); | |
| } | |
| /** | |
| * write_has_finished() - Inform the cache that a write has finished (possibly with an error). | |
| * @info: The info structure for the page whose write just completed. | |
| * | |
| * Return: true if the page write was a discard. | |
| */ | |
| static bool write_has_finished(struct page_info *info) | |
| { | |
| bool was_discard = (info->write_status == WRITE_STATUS_DISCARD); | |
| assert_on_cache_thread(info->cache, __func__); | |
| info->cache->outstanding_writes--; | |
| info->write_status = WRITE_STATUS_NORMAL; | |
| return was_discard; | |
| } | |
| /** | |
| * handle_page_write_error() - Handler for page write errors. | |
| * @completion: The page write vio. | |
| */ | |
| static void handle_page_write_error(struct vdo_completion *completion) | |
| { | |
| int result = completion->result; | |
| struct page_info *info = completion->parent; | |
| struct vdo_page_cache *cache = info->cache; | |
| vio_record_metadata_io_error(as_vio(completion)); | |
| /* If we're already read-only, write failures are to be expected. */ | |
| if (result != VDO_READ_ONLY) { | |
| vdo_log_ratelimit(vdo_log_error, | |
| "failed to write block map page %llu", | |
| (unsigned long long) info->pbn); | |
| } | |
| set_info_state(info, PS_DIRTY); | |
| ADD_ONCE(cache->stats.failed_writes, 1); | |
| set_persistent_error(cache, "cannot write page", result); | |
| if (!write_has_finished(info)) | |
| discard_page_if_needed(cache); | |
| check_for_drain_complete(cache->zone); | |
| } | |
| static void page_is_written_out(struct vdo_completion *completion); | |
| static void write_cache_page_endio(struct bio *bio) | |
| { | |
| struct vio *vio = bio->bi_private; | |
| struct page_info *info = vio->completion.parent; | |
| continue_vio_after_io(vio, page_is_written_out, info->cache->zone->thread_id); | |
| } | |
| /** | |
| * page_is_written_out() - Callback used when a page has been written out. | |
| * @completion: The vio which wrote the page. Its parent is a page_info. | |
| */ | |
| static void page_is_written_out(struct vdo_completion *completion) | |
| { | |
| bool was_discard, reclaimed; | |
| u32 reclamations; | |
| struct page_info *info = completion->parent; | |
| struct vdo_page_cache *cache = info->cache; | |
| struct block_map_page *page = (struct block_map_page *) get_page_buffer(info); | |
| if (!page->header.initialized) { | |
| page->header.initialized = true; | |
| vdo_submit_metadata_vio(info->vio, info->pbn, | |
| write_cache_page_endio, | |
| handle_page_write_error, | |
| REQ_OP_WRITE | REQ_PRIO | REQ_PREFLUSH); | |
| return; | |
| } | |
| /* Handle journal updates and torn write protection. */ | |
| vdo_release_recovery_journal_block_reference(cache->zone->block_map->journal, | |
| info->recovery_lock, | |
| VDO_ZONE_TYPE_LOGICAL, | |
| cache->zone->zone_number); | |
| info->recovery_lock = 0; | |
| was_discard = write_has_finished(info); | |
| reclaimed = (!was_discard || (info->busy > 0) || vdo_waitq_has_waiters(&info->waiting)); | |
| set_info_state(info, PS_RESIDENT); | |
| reclamations = distribute_page_over_waitq(info, &info->waiting); | |
| ADD_ONCE(cache->stats.reclaimed, reclamations); | |
| if (was_discard) | |
| cache->discard_count--; | |
| if (reclaimed) | |
| discard_page_if_needed(cache); | |
| else | |
| allocate_free_page(info); | |
| check_for_drain_complete(cache->zone); | |
| } | |
| /** | |
| * write_pages() - Write the batch of pages which were covered by the layer flush which just | |
| * completed. | |
| * @flush_completion: The flush vio. | |
| * | |
| * This callback is registered in save_pages(). | |
| */ | |
| static void write_pages(struct vdo_completion *flush_completion) | |
| { | |
| struct vdo_page_cache *cache = ((struct page_info *) flush_completion->parent)->cache; | |
| /* | |
| * We need to cache these two values on the stack since it is possible for the last | |
| * page info to cause the page cache to get freed. Hence once we launch the last page, | |
| * it may be unsafe to dereference the cache. | |
| */ | |
| bool has_unflushed_pages = (cache->pages_to_flush > 0); | |
| page_count_t pages_in_flush = cache->pages_in_flush; | |
| cache->pages_in_flush = 0; | |
| while (pages_in_flush-- > 0) { | |
| struct page_info *info = | |
| list_first_entry(&cache->outgoing_list, struct page_info, | |
| state_entry); | |
| list_del_init(&info->state_entry); | |
| if (vdo_is_read_only(info->cache->vdo)) { | |
| struct vdo_completion *completion = &info->vio->completion; | |
| vdo_reset_completion(completion); | |
| completion->callback = page_is_written_out; | |
| completion->error_handler = handle_page_write_error; | |
| vdo_fail_completion(completion, VDO_READ_ONLY); | |
| continue; | |
| } | |
| ADD_ONCE(info->cache->stats.pages_saved, 1); | |
| vdo_submit_metadata_vio(info->vio, info->pbn, write_cache_page_endio, | |
| handle_page_write_error, REQ_OP_WRITE | REQ_PRIO); | |
| } | |
| if (has_unflushed_pages) { | |
| /* | |
| * If there are unflushed pages, the cache can't have been freed, so this call is | |
| * safe. | |
| */ | |
| save_pages(cache); | |
| } | |
| } | |
| /** | |
| * vdo_release_page_completion() - Release a VDO Page Completion. | |
| * @completion: The page completion to release. | |
| * | |
| * The page referenced by this completion (if any) will no longer be held busy by this completion. | |
| * If a page becomes discardable and there are completions awaiting free pages then a new round of | |
| * page discarding is started. | |
| */ | |
| void vdo_release_page_completion(struct vdo_completion *completion) | |
| { | |
| struct page_info *discard_info = NULL; | |
| struct vdo_page_completion *page_completion = as_vdo_page_completion(completion); | |
| struct vdo_page_cache *cache; | |
| if (completion->result == VDO_SUCCESS) { | |
| if (!validate_completed_page_or_enter_read_only_mode(page_completion, false)) | |
| return; | |
| if (--page_completion->info->busy == 0) | |
| discard_info = page_completion->info; | |
| } | |
| VDO_ASSERT_LOG_ONLY((page_completion->waiter.next_waiter == NULL), | |
| "Page being released after leaving all queues"); | |
| page_completion->info = NULL; | |
| cache = page_completion->cache; | |
| assert_on_cache_thread(cache, __func__); | |
| if (discard_info != NULL) { | |
| if (discard_info->write_status == WRITE_STATUS_DEFERRED) { | |
| discard_info->write_status = WRITE_STATUS_NORMAL; | |
| launch_page_save(discard_info); | |
| } | |
| /* | |
| * if there are excess requests for pages (that have not already started discards) | |
| * we need to discard some page (which may be this one) | |
| */ | |
| discard_page_if_needed(cache); | |
| } | |
| } | |
| static void load_page_for_completion(struct page_info *info, | |
| struct vdo_page_completion *vdo_page_comp) | |
| { | |
| int result; | |
| vdo_waitq_enqueue_waiter(&info->waiting, &vdo_page_comp->waiter); | |
| result = launch_page_load(info, vdo_page_comp->pbn); | |
| if (result != VDO_SUCCESS) { | |
| vdo_waitq_notify_all_waiters(&info->waiting, | |
| complete_waiter_with_error, &result); | |
| } | |
| } | |
| /** | |
| * vdo_get_page() - Initialize a page completion and get a block map page. | |
| * @page_completion: The vdo_page_completion to initialize. | |
| * @zone: The block map zone of the desired page. | |
| * @pbn: The absolute physical block of the desired page. | |
| * @writable: Whether the page can be modified. | |
| * @parent: The object to notify when the fetch is complete. | |
| * @callback: The notification callback. | |
| * @error_handler: The handler for fetch errors. | |
| * @requeue: Whether we must requeue when notifying the parent. | |
| * | |
| * May cause another page to be discarded (potentially writing a dirty page) and the one nominated | |
| * by the completion to be loaded from disk. When the callback is invoked, the page will be | |
| * resident in the cache and marked busy. All callers must call vdo_release_page_completion() | |
| * when they are done with the page to clear the busy mark. | |
| */ | |
| void vdo_get_page(struct vdo_page_completion *page_completion, | |
| struct block_map_zone *zone, physical_block_number_t pbn, | |
| bool writable, void *parent, vdo_action_fn callback, | |
| vdo_action_fn error_handler, bool requeue) | |
| { | |
| struct vdo_page_cache *cache = &zone->page_cache; | |
| struct vdo_completion *completion = &page_completion->completion; | |
| struct page_info *info; | |
| assert_on_cache_thread(cache, __func__); | |
| VDO_ASSERT_LOG_ONLY((page_completion->waiter.next_waiter == NULL), | |
| "New page completion was not already on a wait queue"); | |
| *page_completion = (struct vdo_page_completion) { | |
| .pbn = pbn, | |
| .writable = writable, | |
| .cache = cache, | |
| }; | |
| vdo_initialize_completion(completion, cache->vdo, VDO_PAGE_COMPLETION); | |
| vdo_prepare_completion(completion, callback, error_handler, | |
| cache->zone->thread_id, parent); | |
| completion->requeue = requeue; | |
| if (page_completion->writable && vdo_is_read_only(cache->vdo)) { | |
| vdo_fail_completion(completion, VDO_READ_ONLY); | |
| return; | |
| } | |
| if (page_completion->writable) | |
| ADD_ONCE(cache->stats.write_count, 1); | |
| else | |
| ADD_ONCE(cache->stats.read_count, 1); | |
| info = find_page(cache, page_completion->pbn); | |
| if (info != NULL) { | |
| /* The page is in the cache already. */ | |
| if ((info->write_status == WRITE_STATUS_DEFERRED) || | |
| is_incoming(info) || | |
| (is_outgoing(info) && page_completion->writable)) { | |
| /* The page is unusable until it has finished I/O. */ | |
| ADD_ONCE(cache->stats.wait_for_page, 1); | |
| vdo_waitq_enqueue_waiter(&info->waiting, &page_completion->waiter); | |
| return; | |
| } | |
| if (is_valid(info)) { | |
| /* The page is usable. */ | |
| ADD_ONCE(cache->stats.found_in_cache, 1); | |
| if (!is_present(info)) | |
| ADD_ONCE(cache->stats.read_outgoing, 1); | |
| update_lru(info); | |
| info->busy++; | |
| complete_with_page(info, page_completion); | |
| return; | |
| } | |
| /* Something horrible has gone wrong. */ | |
| VDO_ASSERT_LOG_ONLY(false, "Info found in a usable state."); | |
| } | |
| /* The page must be fetched. */ | |
| info = find_free_page(cache); | |
| if (info != NULL) { | |
| ADD_ONCE(cache->stats.fetch_required, 1); | |
| load_page_for_completion(info, page_completion); | |
| return; | |
| } | |
| /* The page must wait for a page to be discarded. */ | |
| ADD_ONCE(cache->stats.discard_required, 1); | |
| discard_page_for_completion(page_completion); | |
| } | |
| /** | |
| * vdo_request_page_write() - Request that a VDO page be written out as soon as it is not busy. | |
| * @completion: The vdo_page_completion containing the page. | |
| */ | |
| void vdo_request_page_write(struct vdo_completion *completion) | |
| { | |
| struct page_info *info; | |
| struct vdo_page_completion *vdo_page_comp = as_vdo_page_completion(completion); | |
| if (!validate_completed_page_or_enter_read_only_mode(vdo_page_comp, true)) | |
| return; | |
| info = vdo_page_comp->info; | |
| set_info_state(info, PS_DIRTY); | |
| launch_page_save(info); | |
| } | |
| /** | |
| * vdo_get_cached_page() - Get the block map page from a page completion. | |
| * @completion: A vdo page completion whose callback has been called. | |
| * @page_ptr: A pointer to hold the page | |
| * | |
| * Return: VDO_SUCCESS or an error | |
| */ | |
| int vdo_get_cached_page(struct vdo_completion *completion, | |
| struct block_map_page **page_ptr) | |
| { | |
| int result; | |
| struct vdo_page_completion *vpc; | |
| vpc = as_vdo_page_completion(completion); | |
| result = validate_completed_page(vpc, true); | |
| if (result == VDO_SUCCESS) | |
| *page_ptr = (struct block_map_page *) get_page_buffer(vpc->info); | |
| return result; | |
| } | |
| /** | |
| * vdo_invalidate_page_cache() - Invalidate all entries in the VDO page cache. | |
| * @cache: The page cache. | |
| * | |
| * There must not be any dirty pages in the cache. | |
| * | |
| * Return: A success or error code. | |
| */ | |
| int vdo_invalidate_page_cache(struct vdo_page_cache *cache) | |
| { | |
| struct page_info *info; | |
| assert_on_cache_thread(cache, __func__); | |
| /* Make sure we don't throw away any dirty pages. */ | |
| for (info = cache->infos; info < cache->infos + cache->page_count; info++) { | |
| int result = VDO_ASSERT(!is_dirty(info), "cache must have no dirty pages"); | |
| if (result != VDO_SUCCESS) | |
| return result; | |
| } | |
| /* Reset the page map by re-allocating it. */ | |
| vdo_int_map_free(vdo_forget(cache->page_map)); | |
| return vdo_int_map_create(cache->page_count, &cache->page_map); | |
| } | |
| /** | |
| * get_tree_page_by_index() - Get the tree page for a given height and page index. | |
| * @forest: The block map forest. | |
| * @root_index: The root index of the tree to search. | |
| * @height: The height in the tree. | |
| * @page_index: The page index. | |
| * | |
| * Return: The requested page. | |
| */ | |
| static struct tree_page * __must_check get_tree_page_by_index(struct forest *forest, | |
| root_count_t root_index, | |
| height_t height, | |
| page_number_t page_index) | |
| { | |
| page_number_t offset = 0; | |
| size_t segment; | |
| for (segment = 0; segment < forest->segments; segment++) { | |
| page_number_t border = forest->boundaries[segment].levels[height - 1]; | |
| if (page_index < border) { | |
| struct block_map_tree *tree = &forest->trees[root_index]; | |
| return &(tree->segments[segment].levels[height - 1][page_index - offset]); | |
| } | |
| offset = border; | |
| } | |
| return NULL; | |
| } | |
| /* Get the page referred to by the lock's tree slot at its current height. */ | |
| static inline struct tree_page *get_tree_page(const struct block_map_zone *zone, | |
| const struct tree_lock *lock) | |
| { | |
| return get_tree_page_by_index(zone->block_map->forest, lock->root_index, | |
| lock->height, | |
| lock->tree_slots[lock->height].page_index); | |
| } | |
| /** vdo_copy_valid_page() - Validate and copy a buffer to a page. */ | |
| bool vdo_copy_valid_page(char *buffer, nonce_t nonce, | |
| physical_block_number_t pbn, | |
| struct block_map_page *page) | |
| { | |
| struct block_map_page *loaded = (struct block_map_page *) buffer; | |
| enum block_map_page_validity validity = | |
| vdo_validate_block_map_page(loaded, nonce, pbn); | |
| if (validity == VDO_BLOCK_MAP_PAGE_VALID) { | |
| memcpy(page, loaded, VDO_BLOCK_SIZE); | |
| return true; | |
| } | |
| if (validity == VDO_BLOCK_MAP_PAGE_BAD) { | |
| vdo_log_error_strerror(VDO_BAD_PAGE, | |
| "Expected page %llu but got page %llu instead", | |
| (unsigned long long) pbn, | |
| (unsigned long long) vdo_get_block_map_page_pbn(loaded)); | |
| } | |
| return false; | |
| } | |
| /** | |
| * in_cyclic_range() - Check whether the given value is between the lower and upper bounds, within | |
| * a cyclic range of values from 0 to (modulus - 1). | |
| * @lower: The lowest value to accept. | |
| * @value: The value to check. | |
| * @upper: The highest value to accept. | |
| * @modulus: The size of the cyclic space, no more than 2^15. | |
| * | |
| * The value and both bounds must be smaller than the modulus. | |
| * | |
| * Return: true if the value is in range. | |
| */ | |
| static bool in_cyclic_range(u16 lower, u16 value, u16 upper, u16 modulus) | |
| { | |
| if (value < lower) | |
| value += modulus; | |
| if (upper < lower) | |
| upper += modulus; | |
| return (value <= upper); | |
| } | |
| /** | |
| * is_not_older() - Check whether a generation is strictly older than some other generation in the | |
| * context of a zone's current generation range. | |
| * @zone: The zone in which to do the comparison. | |
| * @a: The generation in question. | |
| * @b: The generation to compare to. | |
| * | |
| * Return: true if generation @a is not strictly older than generation @b in the context of @zone | |
| */ | |
| static bool __must_check is_not_older(struct block_map_zone *zone, u8 a, u8 b) | |
| { | |
| int result; | |
| result = VDO_ASSERT((in_cyclic_range(zone->oldest_generation, a, zone->generation, 1 << 8) && | |
| in_cyclic_range(zone->oldest_generation, b, zone->generation, 1 << 8)), | |
| "generation(s) %u, %u are out of range [%u, %u]", | |
| a, b, zone->oldest_generation, zone->generation); | |
| if (result != VDO_SUCCESS) { | |
| enter_zone_read_only_mode(zone, result); | |
| return true; | |
| } | |
| return in_cyclic_range(b, a, zone->generation, 1 << 8); | |
| } | |
| static void release_generation(struct block_map_zone *zone, u8 generation) | |
| { | |
| int result; | |
| result = VDO_ASSERT((zone->dirty_page_counts[generation] > 0), | |
| "dirty page count underflow for generation %u", generation); | |
| if (result != VDO_SUCCESS) { | |
| enter_zone_read_only_mode(zone, result); | |
| return; | |
| } | |
| zone->dirty_page_counts[generation]--; | |
| while ((zone->dirty_page_counts[zone->oldest_generation] == 0) && | |
| (zone->oldest_generation != zone->generation)) | |
| zone->oldest_generation++; | |
| } | |
| static void set_generation(struct block_map_zone *zone, struct tree_page *page, | |
| u8 new_generation) | |
| { | |
| u32 new_count; | |
| int result; | |
| bool decrement_old = vdo_waiter_is_waiting(&page->waiter); | |
| u8 old_generation = page->generation; | |
| if (decrement_old && (old_generation == new_generation)) | |
| return; | |
| page->generation = new_generation; | |
| new_count = ++zone->dirty_page_counts[new_generation]; | |
| result = VDO_ASSERT((new_count != 0), "dirty page count overflow for generation %u", | |
| new_generation); | |
| if (result != VDO_SUCCESS) { | |
| enter_zone_read_only_mode(zone, result); | |
| return; | |
| } | |
| if (decrement_old) | |
| release_generation(zone, old_generation); | |
| } | |
| static void write_page(struct tree_page *tree_page, struct pooled_vio *vio); | |
| /* Implements waiter_callback_fn */ | |
| static void write_page_callback(struct vdo_waiter *waiter, void *context) | |
| { | |
| write_page(container_of(waiter, struct tree_page, waiter), context); | |
| } | |
| static void acquire_vio(struct vdo_waiter *waiter, struct block_map_zone *zone) | |
| { | |
| waiter->callback = write_page_callback; | |
| acquire_vio_from_pool(zone->vio_pool, waiter); | |
| } | |
| /* Return: true if all possible generations were not already active */ | |
| static bool attempt_increment(struct block_map_zone *zone) | |
| { | |
| u8 generation = zone->generation + 1; | |
| if (zone->oldest_generation == generation) | |
| return false; | |
| zone->generation = generation; | |
| return true; | |
| } | |
| /* Launches a flush if one is not already in progress. */ | |
| static void enqueue_page(struct tree_page *page, struct block_map_zone *zone) | |
| { | |
| if ((zone->flusher == NULL) && attempt_increment(zone)) { | |
| zone->flusher = page; | |
| acquire_vio(&page->waiter, zone); | |
| return; | |
| } | |
| vdo_waitq_enqueue_waiter(&zone->flush_waiters, &page->waiter); | |
| } | |
| static void write_page_if_not_dirtied(struct vdo_waiter *waiter, void *context) | |
| { | |
| struct tree_page *page = container_of(waiter, struct tree_page, waiter); | |
| struct write_if_not_dirtied_context *write_context = context; | |
| if (page->generation == write_context->generation) { | |
| acquire_vio(waiter, write_context->zone); | |
| return; | |
| } | |
| enqueue_page(page, write_context->zone); | |
| } | |
| static void return_to_pool(struct block_map_zone *zone, struct pooled_vio *vio) | |
| { | |
| return_vio_to_pool(vio); | |
| check_for_drain_complete(zone); | |
| } | |
| /* This callback is registered in write_initialized_page(). */ | |
| static void finish_page_write(struct vdo_completion *completion) | |
| { | |
| bool dirty; | |
| struct vio *vio = as_vio(completion); | |
| struct pooled_vio *pooled = container_of(vio, struct pooled_vio, vio); | |
| struct tree_page *page = completion->parent; | |
| struct block_map_zone *zone = pooled->context; | |
| vdo_release_recovery_journal_block_reference(zone->block_map->journal, | |
| page->writing_recovery_lock, | |
| VDO_ZONE_TYPE_LOGICAL, | |
| zone->zone_number); | |
| dirty = (page->writing_generation != page->generation); | |
| release_generation(zone, page->writing_generation); | |
| page->writing = false; | |
| if (zone->flusher == page) { | |
| struct write_if_not_dirtied_context context = { | |
| .zone = zone, | |
| .generation = page->writing_generation, | |
| }; | |
| vdo_waitq_notify_all_waiters(&zone->flush_waiters, | |
| write_page_if_not_dirtied, &context); | |
| if (dirty && attempt_increment(zone)) { | |
| write_page(page, pooled); | |
| return; | |
| } | |
| zone->flusher = NULL; | |
| } | |
| if (dirty) { | |
| enqueue_page(page, zone); | |
| } else if ((zone->flusher == NULL) && vdo_waitq_has_waiters(&zone->flush_waiters) && | |
| attempt_increment(zone)) { | |
| zone->flusher = container_of(vdo_waitq_dequeue_waiter(&zone->flush_waiters), | |
| struct tree_page, waiter); | |
| write_page(zone->flusher, pooled); | |
| return; | |
| } | |
| return_to_pool(zone, pooled); | |
| } | |
| static void handle_write_error(struct vdo_completion *completion) | |
| { | |
| int result = completion->result; | |
| struct vio *vio = as_vio(completion); | |
| struct pooled_vio *pooled = container_of(vio, struct pooled_vio, vio); | |
| struct block_map_zone *zone = pooled->context; | |
| vio_record_metadata_io_error(vio); | |
| enter_zone_read_only_mode(zone, result); | |
| return_to_pool(zone, pooled); | |
| } | |
| static void write_page_endio(struct bio *bio); | |
| static void write_initialized_page(struct vdo_completion *completion) | |
| { | |
| struct vio *vio = as_vio(completion); | |
| struct pooled_vio *pooled = container_of(vio, struct pooled_vio, vio); | |
| struct block_map_zone *zone = pooled->context; | |
| struct tree_page *tree_page = completion->parent; | |
| struct block_map_page *page = (struct block_map_page *) vio->data; | |
| blk_opf_t operation = REQ_OP_WRITE | REQ_PRIO; | |
| /* | |
| * Now that we know the page has been written at least once, mark the copy we are writing | |
| * as initialized. | |
| */ | |
| page->header.initialized = true; | |
| if (zone->flusher == tree_page) | |
| operation |= REQ_PREFLUSH; | |
| vdo_submit_metadata_vio(vio, vdo_get_block_map_page_pbn(page), | |
| write_page_endio, handle_write_error, | |
| operation); | |
| } | |
| static void write_page_endio(struct bio *bio) | |
| { | |
| struct pooled_vio *vio = bio->bi_private; | |
| struct block_map_zone *zone = vio->context; | |
| struct block_map_page *page = (struct block_map_page *) vio->vio.data; | |
| continue_vio_after_io(&vio->vio, | |
| (page->header.initialized ? | |
| finish_page_write : write_initialized_page), | |
| zone->thread_id); | |
| } | |
| static void write_page(struct tree_page *tree_page, struct pooled_vio *vio) | |
| { | |
| struct vdo_completion *completion = &vio->vio.completion; | |
| struct block_map_zone *zone = vio->context; | |
| struct block_map_page *page = vdo_as_block_map_page(tree_page); | |
| if ((zone->flusher != tree_page) && | |
| is_not_older(zone, tree_page->generation, zone->generation)) { | |
| /* | |
| * This page was re-dirtied after the last flush was issued, hence we need to do | |
| * another flush. | |
| */ | |
| enqueue_page(tree_page, zone); | |
| return_to_pool(zone, vio); | |
| return; | |
| } | |
| completion->parent = tree_page; | |
| memcpy(vio->vio.data, tree_page->page_buffer, VDO_BLOCK_SIZE); | |
| completion->callback_thread_id = zone->thread_id; | |
| tree_page->writing = true; | |
| tree_page->writing_generation = tree_page->generation; | |
| tree_page->writing_recovery_lock = tree_page->recovery_lock; | |
| /* Clear this now so that we know this page is not on any dirty list. */ | |
| tree_page->recovery_lock = 0; | |
| /* | |
| * We've already copied the page into the vio which will write it, so if it was not yet | |
| * initialized, the first write will indicate that (for torn write protection). It is now | |
| * safe to mark it as initialized in memory since if the write fails, the in memory state | |
| * will become irrelevant. | |
| */ | |
| if (page->header.initialized) { | |
| write_initialized_page(completion); | |
| return; | |
| } | |
| page->header.initialized = true; | |
| vdo_submit_metadata_vio(&vio->vio, vdo_get_block_map_page_pbn(page), | |
| write_page_endio, handle_write_error, | |
| REQ_OP_WRITE | REQ_PRIO); | |
| } | |
| /* Release a lock on a page which was being loaded or allocated. */ | |
| static void release_page_lock(struct data_vio *data_vio, char *what) | |
| { | |
| struct block_map_zone *zone; | |
| struct tree_lock *lock_holder; | |
| struct tree_lock *lock = &data_vio->tree_lock; | |
| VDO_ASSERT_LOG_ONLY(lock->locked, | |
| "release of unlocked block map page %s for key %llu in tree %u", | |
| what, (unsigned long long) lock->key, lock->root_index); | |
| zone = data_vio->logical.zone->block_map_zone; | |
| lock_holder = vdo_int_map_remove(zone->loading_pages, lock->key); | |
| VDO_ASSERT_LOG_ONLY((lock_holder == lock), | |
| "block map page %s mismatch for key %llu in tree %u", | |
| what, (unsigned long long) lock->key, lock->root_index); | |
| lock->locked = false; | |
| } | |
| static void finish_lookup(struct data_vio *data_vio, int result) | |
| { | |
| data_vio->tree_lock.height = 0; | |
| --data_vio->logical.zone->block_map_zone->active_lookups; | |
| set_data_vio_logical_callback(data_vio, continue_data_vio_with_block_map_slot); | |
| data_vio->vio.completion.error_handler = handle_data_vio_error; | |
| continue_data_vio_with_error(data_vio, result); | |
| } | |
| static void abort_lookup_for_waiter(struct vdo_waiter *waiter, void *context) | |
| { | |
| struct data_vio *data_vio = vdo_waiter_as_data_vio(waiter); | |
| int result = *((int *) context); | |
| if (!data_vio->write) { | |
| if (result == VDO_NO_SPACE) | |
| result = VDO_SUCCESS; | |
| } else if (result != VDO_NO_SPACE) { | |
| result = VDO_READ_ONLY; | |
| } | |
| finish_lookup(data_vio, result); | |
| } | |
| static void abort_lookup(struct data_vio *data_vio, int result, char *what) | |
| { | |
| if (result != VDO_NO_SPACE) | |
| enter_zone_read_only_mode(data_vio->logical.zone->block_map_zone, result); | |
| if (data_vio->tree_lock.locked) { | |
| release_page_lock(data_vio, what); | |
| vdo_waitq_notify_all_waiters(&data_vio->tree_lock.waiters, | |
| abort_lookup_for_waiter, | |
| &result); | |
| } | |
| finish_lookup(data_vio, result); | |
| } | |
| static void abort_load(struct data_vio *data_vio, int result) | |
| { | |
| abort_lookup(data_vio, result, "load"); | |
| } | |
| static bool __must_check is_invalid_tree_entry(const struct vdo *vdo, | |
| const struct data_location *mapping, | |
| height_t height) | |
| { | |
| if (!vdo_is_valid_location(mapping) || | |
| vdo_is_state_compressed(mapping->state) || | |
| (vdo_is_mapped_location(mapping) && (mapping->pbn == VDO_ZERO_BLOCK))) | |
| return true; | |
| /* Roots aren't physical data blocks, so we can't check their PBNs. */ | |
| if (height == VDO_BLOCK_MAP_TREE_HEIGHT) | |
| return false; | |
| return !vdo_is_physical_data_block(vdo->depot, mapping->pbn); | |
| } | |
| static void load_block_map_page(struct block_map_zone *zone, struct data_vio *data_vio); | |
| static void allocate_block_map_page(struct block_map_zone *zone, | |
| struct data_vio *data_vio); | |
| static void continue_with_loaded_page(struct data_vio *data_vio, | |
| struct block_map_page *page) | |
| { | |
| struct tree_lock *lock = &data_vio->tree_lock; | |
| struct block_map_tree_slot slot = lock->tree_slots[lock->height]; | |
| struct data_location mapping = | |
| vdo_unpack_block_map_entry(&page->entries[slot.block_map_slot.slot]); | |
| if (is_invalid_tree_entry(vdo_from_data_vio(data_vio), &mapping, lock->height)) { | |
| vdo_log_error_strerror(VDO_BAD_MAPPING, | |
| "Invalid block map tree PBN: %llu with state %u for page index %u at height %u", | |
| (unsigned long long) mapping.pbn, mapping.state, | |
| lock->tree_slots[lock->height - 1].page_index, | |
| lock->height - 1); | |
| abort_load(data_vio, VDO_BAD_MAPPING); | |
| return; | |
| } | |
| if (!vdo_is_mapped_location(&mapping)) { | |
| /* The page we need is unallocated */ | |
| allocate_block_map_page(data_vio->logical.zone->block_map_zone, | |
| data_vio); | |
| return; | |
| } | |
| lock->tree_slots[lock->height - 1].block_map_slot.pbn = mapping.pbn; | |
| if (lock->height == 1) { | |
| finish_lookup(data_vio, VDO_SUCCESS); | |
| return; | |
| } | |
| /* We know what page we need to load next */ | |
| load_block_map_page(data_vio->logical.zone->block_map_zone, data_vio); | |
| } | |
| static void continue_load_for_waiter(struct vdo_waiter *waiter, void *context) | |
| { | |
| struct data_vio *data_vio = vdo_waiter_as_data_vio(waiter); | |
| data_vio->tree_lock.height--; | |
| continue_with_loaded_page(data_vio, context); | |
| } | |
| static void finish_block_map_page_load(struct vdo_completion *completion) | |
| { | |
| physical_block_number_t pbn; | |
| struct tree_page *tree_page; | |
| struct block_map_page *page; | |
| nonce_t nonce; | |
| struct vio *vio = as_vio(completion); | |
| struct pooled_vio *pooled = vio_as_pooled_vio(vio); | |
| struct data_vio *data_vio = completion->parent; | |
| struct block_map_zone *zone = pooled->context; | |
| struct tree_lock *tree_lock = &data_vio->tree_lock; | |
| tree_lock->height--; | |
| pbn = tree_lock->tree_slots[tree_lock->height].block_map_slot.pbn; | |
| tree_page = get_tree_page(zone, tree_lock); | |
| page = (struct block_map_page *) tree_page->page_buffer; | |
| nonce = zone->block_map->nonce; | |
| if (!vdo_copy_valid_page(vio->data, nonce, pbn, page)) | |
| vdo_format_block_map_page(page, nonce, pbn, false); | |
| return_vio_to_pool(pooled); | |
| /* Release our claim to the load and wake any waiters */ | |
| release_page_lock(data_vio, "load"); | |
| vdo_waitq_notify_all_waiters(&tree_lock->waiters, continue_load_for_waiter, page); | |
| continue_with_loaded_page(data_vio, page); | |
| } | |
| static void handle_io_error(struct vdo_completion *completion) | |
| { | |
| int result = completion->result; | |
| struct vio *vio = as_vio(completion); | |
| struct pooled_vio *pooled = container_of(vio, struct pooled_vio, vio); | |
| struct data_vio *data_vio = completion->parent; | |
| vio_record_metadata_io_error(vio); | |
| return_vio_to_pool(pooled); | |
| abort_load(data_vio, result); | |
| } | |
| static void load_page_endio(struct bio *bio) | |
| { | |
| struct vio *vio = bio->bi_private; | |
| struct data_vio *data_vio = vio->completion.parent; | |
| continue_vio_after_io(vio, finish_block_map_page_load, | |
| data_vio->logical.zone->thread_id); | |
| } | |
| static void load_page(struct vdo_waiter *waiter, void *context) | |
| { | |
| struct pooled_vio *pooled = context; | |
| struct data_vio *data_vio = vdo_waiter_as_data_vio(waiter); | |
| struct tree_lock *lock = &data_vio->tree_lock; | |
| physical_block_number_t pbn = lock->tree_slots[lock->height - 1].block_map_slot.pbn; | |
| pooled->vio.completion.parent = data_vio; | |
| vdo_submit_metadata_vio(&pooled->vio, pbn, load_page_endio, | |
| handle_io_error, REQ_OP_READ | REQ_PRIO); | |
| } | |
| /* | |
| * If the page is already locked, queue up to wait for the lock to be released. If the lock is | |
| * acquired, @data_vio->tree_lock.locked will be true. | |
| */ | |
| static int attempt_page_lock(struct block_map_zone *zone, struct data_vio *data_vio) | |
| { | |
| int result; | |
| struct tree_lock *lock_holder; | |
| struct tree_lock *lock = &data_vio->tree_lock; | |
| height_t height = lock->height; | |
| struct block_map_tree_slot tree_slot = lock->tree_slots[height]; | |
| union page_key key; | |
| key.descriptor = (struct page_descriptor) { | |
| .root_index = lock->root_index, | |
| .height = height, | |
| .page_index = tree_slot.page_index, | |
| .slot = tree_slot.block_map_slot.slot, | |
| }; | |
| lock->key = key.key; | |
| result = vdo_int_map_put(zone->loading_pages, lock->key, | |
| lock, false, (void **) &lock_holder); | |
| if (result != VDO_SUCCESS) | |
| return result; | |
| if (lock_holder == NULL) { | |
| /* We got the lock */ | |
| data_vio->tree_lock.locked = true; | |
| return VDO_SUCCESS; | |
| } | |
| /* Someone else is loading or allocating the page we need */ | |
| vdo_waitq_enqueue_waiter(&lock_holder->waiters, &data_vio->waiter); | |
| return VDO_SUCCESS; | |
| } | |
| /* Load a block map tree page from disk, for the next level in the data vio tree lock. */ | |
| static void load_block_map_page(struct block_map_zone *zone, struct data_vio *data_vio) | |
| { | |
| int result; | |
| result = attempt_page_lock(zone, data_vio); | |
| if (result != VDO_SUCCESS) { | |
| abort_load(data_vio, result); | |
| return; | |
| } | |
| if (data_vio->tree_lock.locked) { | |
| data_vio->waiter.callback = load_page; | |
| acquire_vio_from_pool(zone->vio_pool, &data_vio->waiter); | |
| } | |
| } | |
| static void allocation_failure(struct vdo_completion *completion) | |
| { | |
| struct data_vio *data_vio = as_data_vio(completion); | |
| if (vdo_requeue_completion_if_needed(completion, | |
| data_vio->logical.zone->thread_id)) | |
| return; | |
| abort_lookup(data_vio, completion->result, "allocation"); | |
| } | |
| static void continue_allocation_for_waiter(struct vdo_waiter *waiter, void *context) | |
| { | |
| struct data_vio *data_vio = vdo_waiter_as_data_vio(waiter); | |
| struct tree_lock *tree_lock = &data_vio->tree_lock; | |
| physical_block_number_t pbn = *((physical_block_number_t *) context); | |
| tree_lock->height--; | |
| data_vio->tree_lock.tree_slots[tree_lock->height].block_map_slot.pbn = pbn; | |
| if (tree_lock->height == 0) { | |
| finish_lookup(data_vio, VDO_SUCCESS); | |
| return; | |
| } | |
| allocate_block_map_page(data_vio->logical.zone->block_map_zone, data_vio); | |
| } | |
| /** expire_oldest_list() - Expire the oldest list. */ | |
| static void expire_oldest_list(struct dirty_lists *dirty_lists) | |
| { | |
| block_count_t i = dirty_lists->offset++; | |
| dirty_lists->oldest_period++; | |
| if (!list_empty(&dirty_lists->eras[i][VDO_TREE_PAGE])) { | |
| list_splice_tail_init(&dirty_lists->eras[i][VDO_TREE_PAGE], | |
| &dirty_lists->expired[VDO_TREE_PAGE]); | |
| } | |
| if (!list_empty(&dirty_lists->eras[i][VDO_CACHE_PAGE])) { | |
| list_splice_tail_init(&dirty_lists->eras[i][VDO_CACHE_PAGE], | |
| &dirty_lists->expired[VDO_CACHE_PAGE]); | |
| } | |
| if (dirty_lists->offset == dirty_lists->maximum_age) | |
| dirty_lists->offset = 0; | |
| } | |
| /** update_period() - Update the dirty_lists period if necessary. */ | |
| static void update_period(struct dirty_lists *dirty, sequence_number_t period) | |
| { | |
| while (dirty->next_period <= period) { | |
| if ((dirty->next_period - dirty->oldest_period) == dirty->maximum_age) | |
| expire_oldest_list(dirty); | |
| dirty->next_period++; | |
| } | |
| } | |
| /** write_expired_elements() - Write out the expired list. */ | |
| static void write_expired_elements(struct block_map_zone *zone) | |
| { | |
| struct tree_page *page, *ttmp; | |
| struct page_info *info, *ptmp; | |
| struct list_head *expired; | |
| u8 generation = zone->generation; | |
| expired = &zone->dirty_lists->expired[VDO_TREE_PAGE]; | |
| list_for_each_entry_safe(page, ttmp, expired, entry) { | |
| int result; | |
| list_del_init(&page->entry); | |
| result = VDO_ASSERT(!vdo_waiter_is_waiting(&page->waiter), | |
| "Newly expired page not already waiting to write"); | |
| if (result != VDO_SUCCESS) { | |
| enter_zone_read_only_mode(zone, result); | |
| continue; | |
| } | |
| set_generation(zone, page, generation); | |
| if (!page->writing) | |
| enqueue_page(page, zone); | |
| } | |
| expired = &zone->dirty_lists->expired[VDO_CACHE_PAGE]; | |
| list_for_each_entry_safe(info, ptmp, expired, state_entry) { | |
| list_del_init(&info->state_entry); | |
| schedule_page_save(info); | |
| } | |
| save_pages(&zone->page_cache); | |
| } | |
| /** | |
| * add_to_dirty_lists() - Add an element to the dirty lists. | |
| * @zone: The zone in which we are operating. | |
| * @entry: The list entry of the element to add. | |
| * @type: The type of page. | |
| * @old_period: The period in which the element was previously dirtied, or 0 if it was not dirty. | |
| * @new_period: The period in which the element has now been dirtied, or 0 if it does not hold a | |
| * lock. | |
| */ | |
| static void add_to_dirty_lists(struct block_map_zone *zone, | |
| struct list_head *entry, | |
| enum block_map_page_type type, | |
| sequence_number_t old_period, | |
| sequence_number_t new_period) | |
| { | |
| struct dirty_lists *dirty_lists = zone->dirty_lists; | |
| if ((old_period == new_period) || ((old_period != 0) && (old_period < new_period))) | |
| return; | |
| if (new_period < dirty_lists->oldest_period) { | |
| list_move_tail(entry, &dirty_lists->expired[type]); | |
| } else { | |
| update_period(dirty_lists, new_period); | |
| list_move_tail(entry, | |
| &dirty_lists->eras[new_period % dirty_lists->maximum_age][type]); | |
| } | |
| write_expired_elements(zone); | |
| } | |
| /* | |
| * Record the allocation in the tree and wake any waiters now that the write lock has been | |
| * released. | |
| */ | |
| static void finish_block_map_allocation(struct vdo_completion *completion) | |
| { | |
| physical_block_number_t pbn; | |
| struct tree_page *tree_page; | |
| struct block_map_page *page; | |
| sequence_number_t old_lock; | |
| struct data_vio *data_vio = as_data_vio(completion); | |
| struct block_map_zone *zone = data_vio->logical.zone->block_map_zone; | |
| struct tree_lock *tree_lock = &data_vio->tree_lock; | |
| height_t height = tree_lock->height; | |
| assert_data_vio_in_logical_zone(data_vio); | |
| tree_page = get_tree_page(zone, tree_lock); | |
| pbn = tree_lock->tree_slots[height - 1].block_map_slot.pbn; | |
| /* Record the allocation. */ | |
| page = (struct block_map_page *) tree_page->page_buffer; | |
| old_lock = tree_page->recovery_lock; | |
| vdo_update_block_map_page(page, data_vio, pbn, | |
| VDO_MAPPING_STATE_UNCOMPRESSED, | |
| &tree_page->recovery_lock); | |
| if (vdo_waiter_is_waiting(&tree_page->waiter)) { | |
| /* This page is waiting to be written out. */ | |
| if (zone->flusher != tree_page) { | |
| /* | |
| * The outstanding flush won't cover the update we just made, | |
| * so mark the page as needing another flush. | |
| */ | |
| set_generation(zone, tree_page, zone->generation); | |
| } | |
| } else { | |
| /* Put the page on a dirty list */ | |
| if (old_lock == 0) | |
| INIT_LIST_HEAD(&tree_page->entry); | |
| add_to_dirty_lists(zone, &tree_page->entry, VDO_TREE_PAGE, | |
| old_lock, tree_page->recovery_lock); | |
| } | |
| tree_lock->height--; | |
| if (height > 1) { | |
| /* Format the interior node we just allocated (in memory). */ | |
| tree_page = get_tree_page(zone, tree_lock); | |
| vdo_format_block_map_page(tree_page->page_buffer, | |
| zone->block_map->nonce, | |
| pbn, false); | |
| } | |
| /* Release our claim to the allocation and wake any waiters */ | |
| release_page_lock(data_vio, "allocation"); | |
| vdo_waitq_notify_all_waiters(&tree_lock->waiters, | |
| continue_allocation_for_waiter, &pbn); | |
| if (tree_lock->height == 0) { | |
| finish_lookup(data_vio, VDO_SUCCESS); | |
| return; | |
| } | |
| allocate_block_map_page(zone, data_vio); | |
| } | |
| static void release_block_map_write_lock(struct vdo_completion *completion) | |
| { | |
| struct data_vio *data_vio = as_data_vio(completion); | |
| assert_data_vio_in_allocated_zone(data_vio); | |
| release_data_vio_allocation_lock(data_vio, true); | |
| launch_data_vio_logical_callback(data_vio, finish_block_map_allocation); | |
| } | |
| /* | |
| * Newly allocated block map pages are set to have to MAXIMUM_REFERENCES after they are journaled, | |
| * to prevent deduplication against the block after we release the write lock on it, but before we | |
| * write out the page. | |
| */ | |
| static void set_block_map_page_reference_count(struct vdo_completion *completion) | |
| { | |
| struct data_vio *data_vio = as_data_vio(completion); | |
| assert_data_vio_in_allocated_zone(data_vio); | |
| completion->callback = release_block_map_write_lock; | |
| vdo_modify_reference_count(completion, &data_vio->increment_updater); | |
| } | |
| static void journal_block_map_allocation(struct vdo_completion *completion) | |
| { | |
| struct data_vio *data_vio = as_data_vio(completion); | |
| assert_data_vio_in_journal_zone(data_vio); | |
| set_data_vio_allocated_zone_callback(data_vio, | |
| set_block_map_page_reference_count); | |
| vdo_add_recovery_journal_entry(completion->vdo->recovery_journal, data_vio); | |
| } | |
| static void allocate_block(struct vdo_completion *completion) | |
| { | |
| struct data_vio *data_vio = as_data_vio(completion); | |
| struct tree_lock *lock = &data_vio->tree_lock; | |
| physical_block_number_t pbn; | |
| assert_data_vio_in_allocated_zone(data_vio); | |
| if (!vdo_allocate_block_in_zone(data_vio)) | |
| return; | |
| pbn = data_vio->allocation.pbn; | |
| lock->tree_slots[lock->height - 1].block_map_slot.pbn = pbn; | |
| data_vio->increment_updater = (struct reference_updater) { | |
| .operation = VDO_JOURNAL_BLOCK_MAP_REMAPPING, | |
| .increment = true, | |
| .zpbn = { | |
| .pbn = pbn, | |
| .state = VDO_MAPPING_STATE_UNCOMPRESSED, | |
| }, | |
| .lock = data_vio->allocation.lock, | |
| }; | |
| launch_data_vio_journal_callback(data_vio, journal_block_map_allocation); | |
| } | |
| static void allocate_block_map_page(struct block_map_zone *zone, | |
| struct data_vio *data_vio) | |
| { | |
| int result; | |
| if (!data_vio->write || data_vio->is_discard) { | |
| /* This is a pure read or a discard, so there's nothing left to do here. */ | |
| finish_lookup(data_vio, VDO_SUCCESS); | |
| return; | |
| } | |
| result = attempt_page_lock(zone, data_vio); | |
| if (result != VDO_SUCCESS) { | |
| abort_lookup(data_vio, result, "allocation"); | |
| return; | |
| } | |
| if (!data_vio->tree_lock.locked) | |
| return; | |
| data_vio_allocate_data_block(data_vio, VIO_BLOCK_MAP_WRITE_LOCK, | |
| allocate_block, allocation_failure); | |
| } | |
| /** | |
| * vdo_find_block_map_slot() - Find the block map slot in which the block map entry for a data_vio | |
| * resides and cache that result in the data_vio. | |
| * @data_vio: The data vio. | |
| * | |
| * All ancestors in the tree will be allocated or loaded, as needed. | |
| */ | |
| void vdo_find_block_map_slot(struct data_vio *data_vio) | |
| { | |
| page_number_t page_index; | |
| struct block_map_tree_slot tree_slot; | |
| struct data_location mapping; | |
| struct block_map_page *page = NULL; | |
| struct tree_lock *lock = &data_vio->tree_lock; | |
| struct block_map_zone *zone = data_vio->logical.zone->block_map_zone; | |
| zone->active_lookups++; | |
| if (vdo_is_state_draining(&zone->state)) { | |
| finish_lookup(data_vio, VDO_SHUTTING_DOWN); | |
| return; | |
| } | |
| lock->tree_slots[0].block_map_slot.slot = | |
| data_vio->logical.lbn % VDO_BLOCK_MAP_ENTRIES_PER_PAGE; | |
| page_index = (lock->tree_slots[0].page_index / zone->block_map->root_count); | |
| tree_slot = (struct block_map_tree_slot) { | |
| .page_index = page_index / VDO_BLOCK_MAP_ENTRIES_PER_PAGE, | |
| .block_map_slot = { | |
| .pbn = 0, | |
| .slot = page_index % VDO_BLOCK_MAP_ENTRIES_PER_PAGE, | |
| }, | |
| }; | |
| for (lock->height = 1; lock->height <= VDO_BLOCK_MAP_TREE_HEIGHT; lock->height++) { | |
| physical_block_number_t pbn; | |
| lock->tree_slots[lock->height] = tree_slot; | |
| page = (struct block_map_page *) (get_tree_page(zone, lock)->page_buffer); | |
| pbn = vdo_get_block_map_page_pbn(page); | |
| if (pbn != VDO_ZERO_BLOCK) { | |
| lock->tree_slots[lock->height].block_map_slot.pbn = pbn; | |
| break; | |
| } | |
| /* Calculate the index and slot for the next level. */ | |
| tree_slot.block_map_slot.slot = | |
| tree_slot.page_index % VDO_BLOCK_MAP_ENTRIES_PER_PAGE; | |
| tree_slot.page_index = tree_slot.page_index / VDO_BLOCK_MAP_ENTRIES_PER_PAGE; | |
| } | |
| /* The page at this height has been allocated and loaded. */ | |
| mapping = vdo_unpack_block_map_entry(&page->entries[tree_slot.block_map_slot.slot]); | |
| if (is_invalid_tree_entry(vdo_from_data_vio(data_vio), &mapping, lock->height)) { | |
| vdo_log_error_strerror(VDO_BAD_MAPPING, | |
| "Invalid block map tree PBN: %llu with state %u for page index %u at height %u", | |
| (unsigned long long) mapping.pbn, mapping.state, | |
| lock->tree_slots[lock->height - 1].page_index, | |
| lock->height - 1); | |
| abort_load(data_vio, VDO_BAD_MAPPING); | |
| return; | |
| } | |
| if (!vdo_is_mapped_location(&mapping)) { | |
| /* The page we want one level down has not been allocated, so allocate it. */ | |
| allocate_block_map_page(zone, data_vio); | |
| return; | |
| } | |
| lock->tree_slots[lock->height - 1].block_map_slot.pbn = mapping.pbn; | |
| if (lock->height == 1) { | |
| /* This is the ultimate block map page, so we're done */ | |
| finish_lookup(data_vio, VDO_SUCCESS); | |
| return; | |
| } | |
| /* We know what page we need to load. */ | |
| load_block_map_page(zone, data_vio); | |
| } | |
| /* | |
| * Find the PBN of a leaf block map page. This method may only be used after all allocated tree | |
| * pages have been loaded, otherwise, it may give the wrong answer (0). | |
| */ | |
| physical_block_number_t vdo_find_block_map_page_pbn(struct block_map *map, | |
| page_number_t page_number) | |
| { | |
| struct data_location mapping; | |
| struct tree_page *tree_page; | |
| struct block_map_page *page; | |
| root_count_t root_index = page_number % map->root_count; | |
| page_number_t page_index = page_number / map->root_count; | |
| slot_number_t slot = page_index % VDO_BLOCK_MAP_ENTRIES_PER_PAGE; | |
| page_index /= VDO_BLOCK_MAP_ENTRIES_PER_PAGE; | |
| tree_page = get_tree_page_by_index(map->forest, root_index, 1, page_index); | |
| page = (struct block_map_page *) tree_page->page_buffer; | |
| if (!page->header.initialized) | |
| return VDO_ZERO_BLOCK; | |
| mapping = vdo_unpack_block_map_entry(&page->entries[slot]); | |
| if (!vdo_is_valid_location(&mapping) || vdo_is_state_compressed(mapping.state)) | |
| return VDO_ZERO_BLOCK; | |
| return mapping.pbn; | |
| } | |
| /* | |
| * Write a tree page or indicate that it has been re-dirtied if it is already being written. This | |
| * method is used when correcting errors in the tree during read-only rebuild. | |
| */ | |
| void vdo_write_tree_page(struct tree_page *page, struct block_map_zone *zone) | |
| { | |
| bool waiting = vdo_waiter_is_waiting(&page->waiter); | |
| if (waiting && (zone->flusher == page)) | |
| return; | |
| set_generation(zone, page, zone->generation); | |
| if (waiting || page->writing) | |
| return; | |
| enqueue_page(page, zone); | |
| } | |
| static int make_segment(struct forest *old_forest, block_count_t new_pages, | |
| struct boundary *new_boundary, struct forest *forest) | |
| { | |
| size_t index = (old_forest == NULL) ? 0 : old_forest->segments; | |
| struct tree_page *page_ptr; | |
| page_count_t segment_sizes[VDO_BLOCK_MAP_TREE_HEIGHT]; | |
| height_t height; | |
| root_count_t root; | |
| int result; | |
| forest->segments = index + 1; | |
| result = vdo_allocate(forest->segments, "forest boundary array", &forest->boundaries); | |
| if (result != VDO_SUCCESS) | |
| return result; | |
| result = vdo_allocate(forest->segments, "forest page pointers", &forest->pages); | |
| if (result != VDO_SUCCESS) | |
| return result; | |
| result = vdo_allocate(new_pages, "new forest pages", &forest->pages[index]); | |
| if (result != VDO_SUCCESS) | |
| return result; | |
| if (index > 0) { | |
| memcpy(forest->boundaries, old_forest->boundaries, | |
| index * sizeof(struct boundary)); | |
| memcpy(forest->pages, old_forest->pages, | |
| index * sizeof(struct tree_page *)); | |
| } | |
| memcpy(&(forest->boundaries[index]), new_boundary, sizeof(struct boundary)); | |
| for (height = 0; height < VDO_BLOCK_MAP_TREE_HEIGHT; height++) { | |
| segment_sizes[height] = new_boundary->levels[height]; | |
| if (index > 0) | |
| segment_sizes[height] -= old_forest->boundaries[index - 1].levels[height]; | |
| } | |
| page_ptr = forest->pages[index]; | |
| for (root = 0; root < forest->map->root_count; root++) { | |
| struct block_map_tree_segment *segment; | |
| struct block_map_tree *tree = &(forest->trees[root]); | |
| height_t height; | |
| result = vdo_allocate(forest->segments, "tree root segments", &tree->segments); | |
| if (result != VDO_SUCCESS) | |
| return result; | |
| if (index > 0) { | |
| memcpy(tree->segments, old_forest->trees[root].segments, | |
| index * sizeof(struct block_map_tree_segment)); | |
| } | |
| segment = &(tree->segments[index]); | |
| for (height = 0; height < VDO_BLOCK_MAP_TREE_HEIGHT; height++) { | |
| if (segment_sizes[height] == 0) | |
| continue; | |
| segment->levels[height] = page_ptr; | |
| if (height == (VDO_BLOCK_MAP_TREE_HEIGHT - 1)) { | |
| /* Record the root. */ | |
| struct block_map_page *page = | |
| vdo_format_block_map_page(page_ptr->page_buffer, | |
| forest->map->nonce, | |
| VDO_INVALID_PBN, true); | |
| page->entries[0] = | |
| vdo_pack_block_map_entry(forest->map->root_origin + root, | |
| VDO_MAPPING_STATE_UNCOMPRESSED); | |
| } | |
| page_ptr += segment_sizes[height]; | |
| } | |
| } | |
| return VDO_SUCCESS; | |
| } | |
| static void deforest(struct forest *forest, size_t first_page_segment) | |
| { | |
| root_count_t root; | |
| if (forest->pages != NULL) { | |
| size_t segment; | |
| for (segment = first_page_segment; segment < forest->segments; segment++) | |
| vdo_free(forest->pages[segment]); | |
| vdo_free(forest->pages); | |
| } | |
| for (root = 0; root < forest->map->root_count; root++) | |
| vdo_free(forest->trees[root].segments); | |
| vdo_free(forest->boundaries); | |
| vdo_free(forest); | |
| } | |
| /** | |
| * make_forest() - Make a collection of trees for a block_map, expanding the existing forest if | |
| * there is one. | |
| * @map: The block map. | |
| * @entries: The number of entries the block map will hold. | |
| * | |
| * Return: VDO_SUCCESS or an error. | |
| */ | |
| static int make_forest(struct block_map *map, block_count_t entries) | |
| { | |
| struct forest *forest, *old_forest = map->forest; | |
| struct boundary new_boundary, *old_boundary = NULL; | |
| block_count_t new_pages; | |
| int result; | |
| if (old_forest != NULL) | |
| old_boundary = &(old_forest->boundaries[old_forest->segments - 1]); | |
| new_pages = vdo_compute_new_forest_pages(map->root_count, old_boundary, | |
| entries, &new_boundary); | |
| if (new_pages == 0) { | |
| map->next_entry_count = entries; | |
| return VDO_SUCCESS; | |
| } | |
| result = vdo_allocate_extended(map->root_count, trees, __func__, &forest); | |
| if (result != VDO_SUCCESS) | |
| return result; | |
| forest->map = map; | |
| result = make_segment(old_forest, new_pages, &new_boundary, forest); | |
| if (result != VDO_SUCCESS) { | |
| deforest(forest, forest->segments - 1); | |
| return result; | |
| } | |
| map->next_forest = forest; | |
| map->next_entry_count = entries; | |
| return VDO_SUCCESS; | |
| } | |
| /** | |
| * replace_forest() - Replace a block_map's forest with the already-prepared larger forest. | |
| * @map: The block map. | |
| */ | |
| static void replace_forest(struct block_map *map) | |
| { | |
| if (map->next_forest != NULL) { | |
| if (map->forest != NULL) | |
| deforest(map->forest, map->forest->segments); | |
| map->forest = vdo_forget(map->next_forest); | |
| } | |
| map->entry_count = map->next_entry_count; | |
| map->next_entry_count = 0; | |
| } | |
| /** | |
| * finish_cursor() - Finish the traversal of a single tree. If it was the last cursor, finish the | |
| * traversal. | |
| * @cursor: The cursor to complete. | |
| */ | |
| static void finish_cursor(struct cursor *cursor) | |
| { | |
| struct cursors *cursors = cursor->parent; | |
| struct vdo_completion *completion = cursors->completion; | |
| return_vio_to_pool(vdo_forget(cursor->vio)); | |
| if (--cursors->active_roots > 0) | |
| return; | |
| vdo_free(cursors); | |
| vdo_finish_completion(completion); | |
| } | |
| static void traverse(struct cursor *cursor); | |
| /** | |
| * continue_traversal() - Continue traversing a block map tree. | |
| * @completion: The VIO doing a read or write. | |
| */ | |
| static void continue_traversal(struct vdo_completion *completion) | |
| { | |
| vio_record_metadata_io_error(as_vio(completion)); | |
| traverse(completion->parent); | |
| } | |
| /** | |
| * finish_traversal_load() - Continue traversing a block map tree now that a page has been loaded. | |
| * @completion: The VIO doing the read. | |
| */ | |
| static void finish_traversal_load(struct vdo_completion *completion) | |
| { | |
| struct cursor *cursor = completion->parent; | |
| height_t height = cursor->height; | |
| struct cursor_level *level = &cursor->levels[height]; | |
| struct tree_page *tree_page = | |
| &(cursor->tree->segments[0].levels[height][level->page_index]); | |
| struct block_map_page *page = (struct block_map_page *) tree_page->page_buffer; | |
| vdo_copy_valid_page(cursor->vio->vio.data, | |
| cursor->parent->zone->block_map->nonce, | |
| pbn_from_vio_bio(cursor->vio->vio.bio), page); | |
| traverse(cursor); | |
| } | |
| static void traversal_endio(struct bio *bio) | |
| { | |
| struct vio *vio = bio->bi_private; | |
| struct cursor *cursor = vio->completion.parent; | |
| continue_vio_after_io(vio, finish_traversal_load, | |
| cursor->parent->zone->thread_id); | |
| } | |
| /** | |
| * traverse() - Traverse a single block map tree. | |
| * @cursor: A cursor tracking traversal progress. | |
| * | |
| * This is the recursive heart of the traversal process. | |
| */ | |
| static void traverse(struct cursor *cursor) | |
| { | |
| for (; cursor->height < VDO_BLOCK_MAP_TREE_HEIGHT; cursor->height++) { | |
| height_t height = cursor->height; | |
| struct cursor_level *level = &cursor->levels[height]; | |
| struct tree_page *tree_page = | |
| &(cursor->tree->segments[0].levels[height][level->page_index]); | |
| struct block_map_page *page = (struct block_map_page *) tree_page->page_buffer; | |
| if (!page->header.initialized) | |
| continue; | |
| for (; level->slot < VDO_BLOCK_MAP_ENTRIES_PER_PAGE; level->slot++) { | |
| struct cursor_level *next_level; | |
| page_number_t entry_index = | |
| (VDO_BLOCK_MAP_ENTRIES_PER_PAGE * level->page_index) + level->slot; | |
| struct data_location location = | |
| vdo_unpack_block_map_entry(&page->entries[level->slot]); | |
| if (!vdo_is_valid_location(&location)) { | |
| /* This entry is invalid, so remove it from the page. */ | |
| page->entries[level->slot] = UNMAPPED_BLOCK_MAP_ENTRY; | |
| vdo_write_tree_page(tree_page, cursor->parent->zone); | |
| continue; | |
| } | |
| if (!vdo_is_mapped_location(&location)) | |
| continue; | |
| /* Erase mapped entries past the end of the logical space. */ | |
| if (entry_index >= cursor->boundary.levels[height]) { | |
| page->entries[level->slot] = UNMAPPED_BLOCK_MAP_ENTRY; | |
| vdo_write_tree_page(tree_page, cursor->parent->zone); | |
| continue; | |
| } | |
| if (cursor->height < VDO_BLOCK_MAP_TREE_HEIGHT - 1) { | |
| int result = cursor->parent->entry_callback(location.pbn, | |
| cursor->parent->completion); | |
| if (result != VDO_SUCCESS) { | |
| page->entries[level->slot] = UNMAPPED_BLOCK_MAP_ENTRY; | |
| vdo_write_tree_page(tree_page, cursor->parent->zone); | |
| continue; | |
| } | |
| } | |
| if (cursor->height == 0) | |
| continue; | |
| cursor->height--; | |
| next_level = &cursor->levels[cursor->height]; | |
| next_level->page_index = entry_index; | |
| next_level->slot = 0; | |
| level->slot++; | |
| vdo_submit_metadata_vio(&cursor->vio->vio, location.pbn, | |
| traversal_endio, continue_traversal, | |
| REQ_OP_READ | REQ_PRIO); | |
| return; | |
| } | |
| } | |
| finish_cursor(cursor); | |
| } | |
| /** | |
| * launch_cursor() - Start traversing a single block map tree now that the cursor has a VIO with | |
| * which to load pages. | |
| * @waiter: The parent of the cursor to launch. | |
| * @context: The pooled_vio just acquired. | |
| * | |
| * Implements waiter_callback_fn. | |
| */ | |
| static void launch_cursor(struct vdo_waiter *waiter, void *context) | |
| { | |
| struct cursor *cursor = container_of(waiter, struct cursor, waiter); | |
| struct pooled_vio *pooled = context; | |
| cursor->vio = pooled; | |
| pooled->vio.completion.parent = cursor; | |
| pooled->vio.completion.callback_thread_id = cursor->parent->zone->thread_id; | |
| traverse(cursor); | |
| } | |
| /** | |
| * compute_boundary() - Compute the number of pages used at each level of the given root's tree. | |
| * @map: The block map. | |
| * @root_index: The tree root index. | |
| * | |
| * Return: The list of page counts as a boundary structure. | |
| */ | |
| static struct boundary compute_boundary(struct block_map *map, root_count_t root_index) | |
| { | |
| struct boundary boundary; | |
| height_t height; | |
| page_count_t leaf_pages = vdo_compute_block_map_page_count(map->entry_count); | |
| /* | |
| * Compute the leaf pages for this root. If the number of leaf pages does not distribute | |
| * evenly, we must determine if this root gets an extra page. Extra pages are assigned to | |
| * roots starting from tree 0. | |
| */ | |
| page_count_t last_tree_root = (leaf_pages - 1) % map->root_count; | |
| page_count_t level_pages = leaf_pages / map->root_count; | |
| if (root_index <= last_tree_root) | |
| level_pages++; | |
| for (height = 0; height < VDO_BLOCK_MAP_TREE_HEIGHT - 1; height++) { | |
| boundary.levels[height] = level_pages; | |
| level_pages = DIV_ROUND_UP(level_pages, VDO_BLOCK_MAP_ENTRIES_PER_PAGE); | |
| } | |
| /* The root node always exists, even if the root is otherwise unused. */ | |
| boundary.levels[VDO_BLOCK_MAP_TREE_HEIGHT - 1] = 1; | |
| return boundary; | |
| } | |
| /** | |
| * vdo_traverse_forest() - Walk the entire forest of a block map. | |
| * @map: The block map. | |
| * @callback: A function to call with the pbn of each allocated node in the forest. | |
| * @completion: The completion to notify on each traversed PBN, and when traversal completes. | |
| */ | |
| void vdo_traverse_forest(struct block_map *map, vdo_entry_callback_fn callback, | |
| struct vdo_completion *completion) | |
| { | |
| root_count_t root; | |
| struct cursors *cursors; | |
| int result; | |
| result = vdo_allocate_extended(map->root_count, cursors, __func__, &cursors); | |
| if (result != VDO_SUCCESS) { | |
| vdo_fail_completion(completion, result); | |
| return; | |
| } | |
| cursors->zone = &map->zones[0]; | |
| cursors->pool = cursors->zone->vio_pool; | |
| cursors->entry_callback = callback; | |
| cursors->completion = completion; | |
| cursors->active_roots = map->root_count; | |
| for (root = 0; root < map->root_count; root++) { | |
| struct cursor *cursor = &cursors->cursors[root]; | |
| *cursor = (struct cursor) { | |
| .tree = &map->forest->trees[root], | |
| .height = VDO_BLOCK_MAP_TREE_HEIGHT - 1, | |
| .parent = cursors, | |
| .boundary = compute_boundary(map, root), | |
| }; | |
| cursor->waiter.callback = launch_cursor; | |
| acquire_vio_from_pool(cursors->pool, &cursor->waiter); | |
| } | |
| } | |
| /** | |
| * initialize_block_map_zone() - Initialize the per-zone portions of the block map. | |
| * @map: The block map. | |
| * @zone_number: The zone to initialize. | |
| * @cache_size: The total block map cache size. | |
| * @maximum_age: The number of journal blocks before a dirtied page is considered old and must be | |
| * written out. | |
| */ | |
| static int __must_check initialize_block_map_zone(struct block_map *map, | |
| zone_count_t zone_number, | |
| page_count_t cache_size, | |
| block_count_t maximum_age) | |
| { | |
| int result; | |
| block_count_t i; | |
| struct vdo *vdo = map->vdo; | |
| struct block_map_zone *zone = &map->zones[zone_number]; | |
| BUILD_BUG_ON(sizeof(struct page_descriptor) != sizeof(u64)); | |
| zone->zone_number = zone_number; | |
| zone->thread_id = vdo->thread_config.logical_threads[zone_number]; | |
| zone->block_map = map; | |
| result = vdo_allocate_extended(maximum_age, eras, __func__, &zone->dirty_lists); | |
| if (result != VDO_SUCCESS) | |
| return result; | |
| zone->dirty_lists->maximum_age = maximum_age; | |
| INIT_LIST_HEAD(&zone->dirty_lists->expired[VDO_TREE_PAGE]); | |
| INIT_LIST_HEAD(&zone->dirty_lists->expired[VDO_CACHE_PAGE]); | |
| for (i = 0; i < maximum_age; i++) { | |
| INIT_LIST_HEAD(&zone->dirty_lists->eras[i][VDO_TREE_PAGE]); | |
| INIT_LIST_HEAD(&zone->dirty_lists->eras[i][VDO_CACHE_PAGE]); | |
| } | |
| result = vdo_int_map_create(VDO_LOCK_MAP_CAPACITY, &zone->loading_pages); | |
| if (result != VDO_SUCCESS) | |
| return result; | |
| result = make_vio_pool(vdo, BLOCK_MAP_VIO_POOL_SIZE, 1, | |
| zone->thread_id, VIO_TYPE_BLOCK_MAP_INTERIOR, | |
| VIO_PRIORITY_METADATA, zone, &zone->vio_pool); | |
| if (result != VDO_SUCCESS) | |
| return result; | |
| vdo_set_admin_state_code(&zone->state, VDO_ADMIN_STATE_NORMAL_OPERATION); | |
| zone->page_cache.zone = zone; | |
| zone->page_cache.vdo = vdo; | |
| zone->page_cache.page_count = cache_size / map->zone_count; | |
| zone->page_cache.stats.free_pages = zone->page_cache.page_count; | |
| result = allocate_cache_components(&zone->page_cache); | |
| if (result != VDO_SUCCESS) | |
| return result; | |
| /* initialize empty circular queues */ | |
| INIT_LIST_HEAD(&zone->page_cache.lru_list); | |
| INIT_LIST_HEAD(&zone->page_cache.outgoing_list); | |
| return VDO_SUCCESS; | |
| } | |
| /* Implements vdo_zone_thread_getter_fn */ | |
| static thread_id_t get_block_map_zone_thread_id(void *context, zone_count_t zone_number) | |
| { | |
| struct block_map *map = context; | |
| return map->zones[zone_number].thread_id; | |
| } | |
| /* Implements vdo_action_preamble_fn */ | |
| static void prepare_for_era_advance(void *context, struct vdo_completion *parent) | |
| { | |
| struct block_map *map = context; | |
| map->current_era_point = map->pending_era_point; | |
| vdo_finish_completion(parent); | |
| } | |
| /* Implements vdo_zone_action_fn */ | |
| static void advance_block_map_zone_era(void *context, zone_count_t zone_number, | |
| struct vdo_completion *parent) | |
| { | |
| struct block_map *map = context; | |
| struct block_map_zone *zone = &map->zones[zone_number]; | |
| update_period(zone->dirty_lists, map->current_era_point); | |
| write_expired_elements(zone); | |
| vdo_finish_completion(parent); | |
| } | |
| /* | |
| * Schedule an era advance if necessary. This method should not be called directly. Rather, call | |
| * vdo_schedule_default_action() on the block map's action manager. | |
| * | |
| * Implements vdo_action_scheduler_fn. | |
| */ | |
| static bool schedule_era_advance(void *context) | |
| { | |
| struct block_map *map = context; | |
| if (map->current_era_point == map->pending_era_point) | |
| return false; | |
| return vdo_schedule_action(map->action_manager, prepare_for_era_advance, | |
| advance_block_map_zone_era, NULL, NULL); | |
| } | |
| static void uninitialize_block_map_zone(struct block_map_zone *zone) | |
| { | |
| struct vdo_page_cache *cache = &zone->page_cache; | |
| vdo_free(vdo_forget(zone->dirty_lists)); | |
| free_vio_pool(vdo_forget(zone->vio_pool)); | |
| vdo_int_map_free(vdo_forget(zone->loading_pages)); | |
| if (cache->infos != NULL) { | |
| struct page_info *info; | |
| for (info = cache->infos; info < cache->infos + cache->page_count; info++) | |
| free_vio(vdo_forget(info->vio)); | |
| } | |
| vdo_int_map_free(vdo_forget(cache->page_map)); | |
| vdo_free(vdo_forget(cache->infos)); | |
| vdo_free(vdo_forget(cache->pages)); | |
| } | |
| void vdo_free_block_map(struct block_map *map) | |
| { | |
| zone_count_t zone; | |
| if (map == NULL) | |
| return; | |
| for (zone = 0; zone < map->zone_count; zone++) | |
| uninitialize_block_map_zone(&map->zones[zone]); | |
| vdo_abandon_block_map_growth(map); | |
| if (map->forest != NULL) | |
| deforest(vdo_forget(map->forest), 0); | |
| vdo_free(vdo_forget(map->action_manager)); | |
| vdo_free(map); | |
| } | |
| /* @journal may be NULL. */ | |
| int vdo_decode_block_map(struct block_map_state_2_0 state, block_count_t logical_blocks, | |
| struct vdo *vdo, struct recovery_journal *journal, | |
| nonce_t nonce, page_count_t cache_size, block_count_t maximum_age, | |
| struct block_map **map_ptr) | |
| { | |
| struct block_map *map; | |
| int result; | |
| zone_count_t zone = 0; | |
| BUILD_BUG_ON(VDO_BLOCK_MAP_ENTRIES_PER_PAGE != | |
| ((VDO_BLOCK_SIZE - sizeof(struct block_map_page)) / | |
| sizeof(struct block_map_entry))); | |
| result = VDO_ASSERT(cache_size > 0, "block map cache size is specified"); | |
| if (result != VDO_SUCCESS) | |
| return result; | |
| result = vdo_allocate_extended(vdo->thread_config.logical_zone_count, | |
| zones, __func__, &map); | |
| if (result != VDO_SUCCESS) | |
| return result; | |
| map->vdo = vdo; | |
| map->root_origin = state.root_origin; | |
| map->root_count = state.root_count; | |
| map->entry_count = logical_blocks; | |
| map->journal = journal; | |
| map->nonce = nonce; | |
| result = make_forest(map, map->entry_count); | |
| if (result != VDO_SUCCESS) { | |
| vdo_free_block_map(map); | |
| return result; | |
| } | |
| replace_forest(map); | |
| map->zone_count = vdo->thread_config.logical_zone_count; | |
| for (zone = 0; zone < map->zone_count; zone++) { | |
| result = initialize_block_map_zone(map, zone, cache_size, maximum_age); | |
| if (result != VDO_SUCCESS) { | |
| vdo_free_block_map(map); | |
| return result; | |
| } | |
| } | |
| result = vdo_make_action_manager(map->zone_count, get_block_map_zone_thread_id, | |
| vdo_get_recovery_journal_thread_id(journal), | |
| map, schedule_era_advance, vdo, | |
| &map->action_manager); | |
| if (result != VDO_SUCCESS) { | |
| vdo_free_block_map(map); | |
| return result; | |
| } | |
| *map_ptr = map; | |
| return VDO_SUCCESS; | |
| } | |
| struct block_map_state_2_0 vdo_record_block_map(const struct block_map *map) | |
| { | |
| return (struct block_map_state_2_0) { | |
| .flat_page_origin = VDO_BLOCK_MAP_FLAT_PAGE_ORIGIN, | |
| /* This is the flat page count, which has turned out to always be 0. */ | |
| .flat_page_count = 0, | |
| .root_origin = map->root_origin, | |
| .root_count = map->root_count, | |
| }; | |
| } | |
| /* The block map needs to know the journals' sequence number to initialize the eras. */ | |
| void vdo_initialize_block_map_from_journal(struct block_map *map, | |
| struct recovery_journal *journal) | |
| { | |
| zone_count_t z = 0; | |
| map->current_era_point = vdo_get_recovery_journal_current_sequence_number(journal); | |
| map->pending_era_point = map->current_era_point; | |
| for (z = 0; z < map->zone_count; z++) { | |
| struct dirty_lists *dirty_lists = map->zones[z].dirty_lists; | |
| VDO_ASSERT_LOG_ONLY(dirty_lists->next_period == 0, "current period not set"); | |
| dirty_lists->oldest_period = map->current_era_point; | |
| dirty_lists->next_period = map->current_era_point + 1; | |
| dirty_lists->offset = map->current_era_point % dirty_lists->maximum_age; | |
| } | |
| } | |
| /* Compute the logical zone for the LBN of a data vio. */ | |
| zone_count_t vdo_compute_logical_zone(struct data_vio *data_vio) | |
| { | |
| struct block_map *map = vdo_from_data_vio(data_vio)->block_map; | |
| struct tree_lock *tree_lock = &data_vio->tree_lock; | |
| page_number_t page_number = data_vio->logical.lbn / VDO_BLOCK_MAP_ENTRIES_PER_PAGE; | |
| tree_lock->tree_slots[0].page_index = page_number; | |
| tree_lock->root_index = page_number % map->root_count; | |
| return (tree_lock->root_index % map->zone_count); | |
| } | |
| void vdo_advance_block_map_era(struct block_map *map, | |
| sequence_number_t recovery_block_number) | |
| { | |
| if (map == NULL) | |
| return; | |
| map->pending_era_point = recovery_block_number; | |
| vdo_schedule_default_action(map->action_manager); | |
| } | |
| /* Implements vdo_admin_initiator_fn */ | |
| static void initiate_drain(struct admin_state *state) | |
| { | |
| struct block_map_zone *zone = container_of(state, struct block_map_zone, state); | |
| VDO_ASSERT_LOG_ONLY((zone->active_lookups == 0), | |
| "%s() called with no active lookups", __func__); | |
| if (!vdo_is_state_suspending(state)) { | |
| while (zone->dirty_lists->oldest_period < zone->dirty_lists->next_period) | |
| expire_oldest_list(zone->dirty_lists); | |
| write_expired_elements(zone); | |
| } | |
| check_for_drain_complete(zone); | |
| } | |
| /* Implements vdo_zone_action_fn. */ | |
| static void drain_zone(void *context, zone_count_t zone_number, | |
| struct vdo_completion *parent) | |
| { | |
| struct block_map *map = context; | |
| struct block_map_zone *zone = &map->zones[zone_number]; | |
| vdo_start_draining(&zone->state, | |
| vdo_get_current_manager_operation(map->action_manager), | |
| parent, initiate_drain); | |
| } | |
| void vdo_drain_block_map(struct block_map *map, const struct admin_state_code *operation, | |
| struct vdo_completion *parent) | |
| { | |
| vdo_schedule_operation(map->action_manager, operation, NULL, drain_zone, NULL, | |
| parent); | |
| } | |
| /* Implements vdo_zone_action_fn. */ | |
| static void resume_block_map_zone(void *context, zone_count_t zone_number, | |
| struct vdo_completion *parent) | |
| { | |
| struct block_map *map = context; | |
| struct block_map_zone *zone = &map->zones[zone_number]; | |
| vdo_fail_completion(parent, vdo_resume_if_quiescent(&zone->state)); | |
| } | |
| void vdo_resume_block_map(struct block_map *map, struct vdo_completion *parent) | |
| { | |
| vdo_schedule_operation(map->action_manager, VDO_ADMIN_STATE_RESUMING, | |
| NULL, resume_block_map_zone, NULL, parent); | |
| } | |
| /* Allocate an expanded collection of trees, for a future growth. */ | |
| int vdo_prepare_to_grow_block_map(struct block_map *map, | |
| block_count_t new_logical_blocks) | |
| { | |
| if (map->next_entry_count == new_logical_blocks) | |
| return VDO_SUCCESS; | |
| if (map->next_entry_count > 0) | |
| vdo_abandon_block_map_growth(map); | |
| if (new_logical_blocks < map->entry_count) { | |
| map->next_entry_count = map->entry_count; | |
| return VDO_SUCCESS; | |
| } | |
| return make_forest(map, new_logical_blocks); | |
| } | |
| /* Implements vdo_action_preamble_fn */ | |
| static void grow_forest(void *context, struct vdo_completion *completion) | |
| { | |
| replace_forest(context); | |
| vdo_finish_completion(completion); | |
| } | |
| /* Requires vdo_prepare_to_grow_block_map() to have been previously called. */ | |
| void vdo_grow_block_map(struct block_map *map, struct vdo_completion *parent) | |
| { | |
| vdo_schedule_operation(map->action_manager, | |
| VDO_ADMIN_STATE_SUSPENDED_OPERATION, | |
| grow_forest, NULL, NULL, parent); | |
| } | |
| void vdo_abandon_block_map_growth(struct block_map *map) | |
| { | |
| struct forest *forest = vdo_forget(map->next_forest); | |
| if (forest != NULL) | |
| deforest(forest, forest->segments - 1); | |
| map->next_entry_count = 0; | |
| } | |
| /* Release the page completion and then continue the requester. */ | |
| static inline void finish_processing_page(struct vdo_completion *completion, int result) | |
| { | |
| struct vdo_completion *parent = completion->parent; | |
| vdo_release_page_completion(completion); | |
| vdo_continue_completion(parent, result); | |
| } | |
| static void handle_page_error(struct vdo_completion *completion) | |
| { | |
| finish_processing_page(completion, completion->result); | |
| } | |
| /* Fetch the mapping page for a block map update, and call the provided handler when fetched. */ | |
| static void fetch_mapping_page(struct data_vio *data_vio, bool modifiable, | |
| vdo_action_fn action) | |
| { | |
| struct block_map_zone *zone = data_vio->logical.zone->block_map_zone; | |
| if (vdo_is_state_draining(&zone->state)) { | |
| continue_data_vio_with_error(data_vio, VDO_SHUTTING_DOWN); | |
| return; | |
| } | |
| vdo_get_page(&data_vio->page_completion, zone, | |
| data_vio->tree_lock.tree_slots[0].block_map_slot.pbn, | |
| modifiable, &data_vio->vio.completion, | |
| action, handle_page_error, false); | |
| } | |
| /** | |
| * clear_mapped_location() - Clear a data_vio's mapped block location, setting it to be unmapped. | |
| * @data_vio: The data vio. | |
| * | |
| * This indicates the block map entry for the logical block is either unmapped or corrupted. | |
| */ | |
| static void clear_mapped_location(struct data_vio *data_vio) | |
| { | |
| data_vio->mapped = (struct zoned_pbn) { | |
| .state = VDO_MAPPING_STATE_UNMAPPED, | |
| }; | |
| } | |
| /** | |
| * set_mapped_location() - Decode and validate a block map entry, and set the mapped location of a | |
| * data_vio. | |
| * @data_vio: The data vio. | |
| * @entry: The new mapped entry to set. | |
| * | |
| * Return: VDO_SUCCESS or VDO_BAD_MAPPING if the map entry is invalid or an error code for any | |
| * other failure | |
| */ | |
| static int __must_check set_mapped_location(struct data_vio *data_vio, | |
| const struct block_map_entry *entry) | |
| { | |
| /* Unpack the PBN for logging purposes even if the entry is invalid. */ | |
| struct data_location mapped = vdo_unpack_block_map_entry(entry); | |
| if (vdo_is_valid_location(&mapped)) { | |
| int result; | |
| result = vdo_get_physical_zone(vdo_from_data_vio(data_vio), | |
| mapped.pbn, &data_vio->mapped.zone); | |
| if (result == VDO_SUCCESS) { | |
| data_vio->mapped.pbn = mapped.pbn; | |
| data_vio->mapped.state = mapped.state; | |
| return VDO_SUCCESS; | |
| } | |
| /* | |
| * Return all errors not specifically known to be errors from validating the | |
| * location. | |
| */ | |
| if ((result != VDO_OUT_OF_RANGE) && (result != VDO_BAD_MAPPING)) | |
| return result; | |
| } | |
| /* | |
| * Log the corruption even if we wind up ignoring it for write VIOs, converting all cases | |
| * to VDO_BAD_MAPPING. | |
| */ | |
| vdo_log_error_strerror(VDO_BAD_MAPPING, | |
| "PBN %llu with state %u read from the block map was invalid", | |
| (unsigned long long) mapped.pbn, mapped.state); | |
| /* | |
| * A read VIO has no option but to report the bad mapping--reading zeros would be hiding | |
| * known data loss. | |
| */ | |
| if (!data_vio->write) | |
| return VDO_BAD_MAPPING; | |
| /* | |
| * A write VIO only reads this mapping to decref the old block. Treat this as an unmapped | |
| * entry rather than fail the write. | |
| */ | |
| clear_mapped_location(data_vio); | |
| return VDO_SUCCESS; | |
| } | |
| /* This callback is registered in vdo_get_mapped_block(). */ | |
| static void get_mapping_from_fetched_page(struct vdo_completion *completion) | |
| { | |
| int result; | |
| struct vdo_page_completion *vpc = as_vdo_page_completion(completion); | |
| const struct block_map_page *page; | |
| const struct block_map_entry *entry; | |
| struct data_vio *data_vio = as_data_vio(completion->parent); | |
| struct block_map_tree_slot *tree_slot; | |
| if (completion->result != VDO_SUCCESS) { | |
| finish_processing_page(completion, completion->result); | |
| return; | |
| } | |
| result = validate_completed_page(vpc, false); | |
| if (result != VDO_SUCCESS) { | |
| finish_processing_page(completion, result); | |
| return; | |
| } | |
| page = (const struct block_map_page *) get_page_buffer(vpc->info); | |
| tree_slot = &data_vio->tree_lock.tree_slots[0]; | |
| entry = &page->entries[tree_slot->block_map_slot.slot]; | |
| result = set_mapped_location(data_vio, entry); | |
| finish_processing_page(completion, result); | |
| } | |
| void vdo_update_block_map_page(struct block_map_page *page, struct data_vio *data_vio, | |
| physical_block_number_t pbn, | |
| enum block_mapping_state mapping_state, | |
| sequence_number_t *recovery_lock) | |
| { | |
| struct block_map_zone *zone = data_vio->logical.zone->block_map_zone; | |
| struct block_map *block_map = zone->block_map; | |
| struct recovery_journal *journal = block_map->journal; | |
| sequence_number_t old_locked, new_locked; | |
| struct tree_lock *tree_lock = &data_vio->tree_lock; | |
| /* Encode the new mapping. */ | |
| page->entries[tree_lock->tree_slots[tree_lock->height].block_map_slot.slot] = | |
| vdo_pack_block_map_entry(pbn, mapping_state); | |
| /* Adjust references on the recovery journal blocks. */ | |
| old_locked = *recovery_lock; | |
| new_locked = data_vio->recovery_sequence_number; | |
| if ((old_locked == 0) || (old_locked > new_locked)) { | |
| vdo_acquire_recovery_journal_block_reference(journal, new_locked, | |
| VDO_ZONE_TYPE_LOGICAL, | |
| zone->zone_number); | |
| if (old_locked > 0) { | |
| vdo_release_recovery_journal_block_reference(journal, old_locked, | |
| VDO_ZONE_TYPE_LOGICAL, | |
| zone->zone_number); | |
| } | |
| *recovery_lock = new_locked; | |
| } | |
| /* | |
| * FIXME: explain this more | |
| * Release the transferred lock from the data_vio. | |
| */ | |
| vdo_release_journal_entry_lock(journal, new_locked); | |
| data_vio->recovery_sequence_number = 0; | |
| } | |
| static void put_mapping_in_fetched_page(struct vdo_completion *completion) | |
| { | |
| struct data_vio *data_vio = as_data_vio(completion->parent); | |
| sequence_number_t old_lock; | |
| struct vdo_page_completion *vpc; | |
| struct page_info *info; | |
| int result; | |
| if (completion->result != VDO_SUCCESS) { | |
| finish_processing_page(completion, completion->result); | |
| return; | |
| } | |
| vpc = as_vdo_page_completion(completion); | |
| result = validate_completed_page(vpc, true); | |
| if (result != VDO_SUCCESS) { | |
| finish_processing_page(completion, result); | |
| return; | |
| } | |
| info = vpc->info; | |
| old_lock = info->recovery_lock; | |
| vdo_update_block_map_page((struct block_map_page *) get_page_buffer(info), | |
| data_vio, data_vio->new_mapped.pbn, | |
| data_vio->new_mapped.state, &info->recovery_lock); | |
| set_info_state(info, PS_DIRTY); | |
| add_to_dirty_lists(info->cache->zone, &info->state_entry, | |
| VDO_CACHE_PAGE, old_lock, info->recovery_lock); | |
| finish_processing_page(completion, VDO_SUCCESS); | |
| } | |
| /* Read a stored block mapping into a data_vio. */ | |
| void vdo_get_mapped_block(struct data_vio *data_vio) | |
| { | |
| if (data_vio->tree_lock.tree_slots[0].block_map_slot.pbn == VDO_ZERO_BLOCK) { | |
| /* | |
| * We know that the block map page for this LBN has not been allocated, so the | |
| * block must be unmapped. | |
| */ | |
| clear_mapped_location(data_vio); | |
| continue_data_vio(data_vio); | |
| return; | |
| } | |
| fetch_mapping_page(data_vio, false, get_mapping_from_fetched_page); | |
| } | |
| /* Update a stored block mapping to reflect a data_vio's new mapping. */ | |
| void vdo_put_mapped_block(struct data_vio *data_vio) | |
| { | |
| fetch_mapping_page(data_vio, true, put_mapping_in_fetched_page); | |
| } | |
| struct block_map_statistics vdo_get_block_map_statistics(struct block_map *map) | |
| { | |
| zone_count_t zone = 0; | |
| struct block_map_statistics totals; | |
| memset(&totals, 0, sizeof(struct block_map_statistics)); | |
| for (zone = 0; zone < map->zone_count; zone++) { | |
| const struct block_map_statistics *stats = | |
| &(map->zones[zone].page_cache.stats); | |
| totals.dirty_pages += READ_ONCE(stats->dirty_pages); | |
| totals.clean_pages += READ_ONCE(stats->clean_pages); | |
| totals.free_pages += READ_ONCE(stats->free_pages); | |
| totals.failed_pages += READ_ONCE(stats->failed_pages); | |
| totals.incoming_pages += READ_ONCE(stats->incoming_pages); | |
| totals.outgoing_pages += READ_ONCE(stats->outgoing_pages); | |
| totals.cache_pressure += READ_ONCE(stats->cache_pressure); | |
| totals.read_count += READ_ONCE(stats->read_count); | |
| totals.write_count += READ_ONCE(stats->write_count); | |
| totals.failed_reads += READ_ONCE(stats->failed_reads); | |
| totals.failed_writes += READ_ONCE(stats->failed_writes); | |
| totals.reclaimed += READ_ONCE(stats->reclaimed); | |
| totals.read_outgoing += READ_ONCE(stats->read_outgoing); | |
| totals.found_in_cache += READ_ONCE(stats->found_in_cache); | |
| totals.discard_required += READ_ONCE(stats->discard_required); | |
| totals.wait_for_page += READ_ONCE(stats->wait_for_page); | |
| totals.fetch_required += READ_ONCE(stats->fetch_required); | |
| totals.pages_loaded += READ_ONCE(stats->pages_loaded); | |
| totals.pages_saved += READ_ONCE(stats->pages_saved); | |
| totals.flush_count += READ_ONCE(stats->flush_count); | |
| } | |
| return totals; | |
| } |