| // SPDX-License-Identifier: GPL-2.0-only | |
| /* | |
| * Copyright 2023 Red Hat | |
| */ | |
| #include "packer.h" | |
| #include <linux/atomic.h> | |
| #include <linux/blkdev.h> | |
| #include "logger.h" | |
| #include "memory-alloc.h" | |
| #include "permassert.h" | |
| #include "string-utils.h" | |
| #include "admin-state.h" | |
| #include "completion.h" | |
| #include "constants.h" | |
| #include "data-vio.h" | |
| #include "dedupe.h" | |
| #include "encodings.h" | |
| #include "io-submitter.h" | |
| #include "physical-zone.h" | |
| #include "status-codes.h" | |
| #include "vdo.h" | |
| #include "vio.h" | |
| static const struct version_number COMPRESSED_BLOCK_1_0 = { | |
| .major_version = 1, | |
| .minor_version = 0, | |
| }; | |
| #define COMPRESSED_BLOCK_1_0_SIZE (4 + 4 + (2 * VDO_MAX_COMPRESSION_SLOTS)) | |
| /** | |
| * vdo_get_compressed_block_fragment() - Get a reference to a compressed fragment from a compressed | |
| * block. | |
| * @mapping_state: The mapping state describing the fragment. | |
| * @block: The compressed block that was read from disk. | |
| * @fragment_offset: The offset of the fragment within the compressed block. | |
| * @fragment_size: The size of the fragment. | |
| * | |
| * Return: If a valid compressed fragment is found, VDO_SUCCESS; otherwise, VDO_INVALID_FRAGMENT if | |
| * the fragment is invalid. | |
| */ | |
| int vdo_get_compressed_block_fragment(enum block_mapping_state mapping_state, | |
| struct compressed_block *block, | |
| u16 *fragment_offset, u16 *fragment_size) | |
| { | |
| u16 compressed_size; | |
| u16 offset = 0; | |
| unsigned int i; | |
| u8 slot; | |
| struct version_number version; | |
| if (!vdo_is_state_compressed(mapping_state)) | |
| return VDO_INVALID_FRAGMENT; | |
| version = vdo_unpack_version_number(block->header.version); | |
| if (!vdo_are_same_version(version, COMPRESSED_BLOCK_1_0)) | |
| return VDO_INVALID_FRAGMENT; | |
| slot = mapping_state - VDO_MAPPING_STATE_COMPRESSED_BASE; | |
| if (slot >= VDO_MAX_COMPRESSION_SLOTS) | |
| return VDO_INVALID_FRAGMENT; | |
| compressed_size = __le16_to_cpu(block->header.sizes[slot]); | |
| for (i = 0; i < slot; i++) { | |
| offset += __le16_to_cpu(block->header.sizes[i]); | |
| if (offset >= VDO_COMPRESSED_BLOCK_DATA_SIZE) | |
| return VDO_INVALID_FRAGMENT; | |
| } | |
| if ((offset + compressed_size) > VDO_COMPRESSED_BLOCK_DATA_SIZE) | |
| return VDO_INVALID_FRAGMENT; | |
| *fragment_offset = offset; | |
| *fragment_size = compressed_size; | |
| return VDO_SUCCESS; | |
| } | |
| /** | |
| * assert_on_packer_thread() - Check that we are on the packer thread. | |
| * @packer: The packer. | |
| * @caller: The function which is asserting. | |
| */ | |
| static inline void assert_on_packer_thread(struct packer *packer, const char *caller) | |
| { | |
| VDO_ASSERT_LOG_ONLY((vdo_get_callback_thread_id() == packer->thread_id), | |
| "%s() called from packer thread", caller); | |
| } | |
| /** | |
| * insert_in_sorted_list() - Insert a bin to the list. | |
| * @packer: The packer. | |
| * @bin: The bin to move to its sorted position. | |
| * | |
| * The list is in ascending order of free space. Since all bins are already in the list, this | |
| * actually moves the bin to the correct position in the list. | |
| */ | |
| static void insert_in_sorted_list(struct packer *packer, struct packer_bin *bin) | |
| { | |
| struct packer_bin *active_bin; | |
| list_for_each_entry(active_bin, &packer->bins, list) | |
| if (active_bin->free_space > bin->free_space) { | |
| list_move_tail(&bin->list, &active_bin->list); | |
| return; | |
| } | |
| list_move_tail(&bin->list, &packer->bins); | |
| } | |
| /** | |
| * make_bin() - Allocate a bin and put it into the packer's list. | |
| * @packer: The packer. | |
| */ | |
| static int __must_check make_bin(struct packer *packer) | |
| { | |
| struct packer_bin *bin; | |
| int result; | |
| result = vdo_allocate_extended(VDO_MAX_COMPRESSION_SLOTS, incoming, __func__, &bin); | |
| if (result != VDO_SUCCESS) | |
| return result; | |
| bin->free_space = VDO_COMPRESSED_BLOCK_DATA_SIZE; | |
| INIT_LIST_HEAD(&bin->list); | |
| list_add_tail(&bin->list, &packer->bins); | |
| return VDO_SUCCESS; | |
| } | |
| /** | |
| * vdo_make_packer() - Make a new block packer. | |
| * | |
| * @vdo: The vdo to which this packer belongs. | |
| * @bin_count: The number of partial bins to keep in memory. | |
| * @packer_ptr: A pointer to hold the new packer. | |
| * | |
| * Return: VDO_SUCCESS or an error | |
| */ | |
| int vdo_make_packer(struct vdo *vdo, block_count_t bin_count, struct packer **packer_ptr) | |
| { | |
| struct packer *packer; | |
| block_count_t i; | |
| int result; | |
| result = vdo_allocate(1, __func__, &packer); | |
| if (result != VDO_SUCCESS) | |
| return result; | |
| packer->thread_id = vdo->thread_config.packer_thread; | |
| packer->size = bin_count; | |
| INIT_LIST_HEAD(&packer->bins); | |
| vdo_set_admin_state_code(&packer->state, VDO_ADMIN_STATE_NORMAL_OPERATION); | |
| for (i = 0; i < bin_count; i++) { | |
| result = make_bin(packer); | |
| if (result != VDO_SUCCESS) { | |
| vdo_free_packer(packer); | |
| return result; | |
| } | |
| } | |
| /* | |
| * The canceled bin can hold up to half the number of user vios. Every canceled vio in the | |
| * bin must have a canceler for which it is waiting, and any canceler will only have | |
| * canceled one lock holder at a time. | |
| */ | |
| result = vdo_allocate_extended(MAXIMUM_VDO_USER_VIOS / 2, incoming, __func__, | |
| &packer->canceled_bin); | |
| if (result != VDO_SUCCESS) { | |
| vdo_free_packer(packer); | |
| return result; | |
| } | |
| result = vdo_make_default_thread(vdo, packer->thread_id); | |
| if (result != VDO_SUCCESS) { | |
| vdo_free_packer(packer); | |
| return result; | |
| } | |
| *packer_ptr = packer; | |
| return VDO_SUCCESS; | |
| } | |
| /** | |
| * vdo_free_packer() - Free a block packer. | |
| * @packer: The packer to free. | |
| */ | |
| void vdo_free_packer(struct packer *packer) | |
| { | |
| struct packer_bin *bin, *tmp; | |
| if (packer == NULL) | |
| return; | |
| list_for_each_entry_safe(bin, tmp, &packer->bins, list) { | |
| list_del_init(&bin->list); | |
| vdo_free(bin); | |
| } | |
| vdo_free(vdo_forget(packer->canceled_bin)); | |
| vdo_free(packer); | |
| } | |
| /** | |
| * get_packer_from_data_vio() - Get the packer from a data_vio. | |
| * @data_vio: The data_vio. | |
| * | |
| * Return: The packer from the VDO to which the data_vio belongs. | |
| */ | |
| static inline struct packer *get_packer_from_data_vio(struct data_vio *data_vio) | |
| { | |
| return vdo_from_data_vio(data_vio)->packer; | |
| } | |
| /** | |
| * vdo_get_packer_statistics() - Get the current statistics from the packer. | |
| * @packer: The packer to query. | |
| * | |
| * Return: a copy of the current statistics for the packer. | |
| */ | |
| struct packer_statistics vdo_get_packer_statistics(const struct packer *packer) | |
| { | |
| const struct packer_statistics *stats = &packer->statistics; | |
| return (struct packer_statistics) { | |
| .compressed_fragments_written = READ_ONCE(stats->compressed_fragments_written), | |
| .compressed_blocks_written = READ_ONCE(stats->compressed_blocks_written), | |
| .compressed_fragments_in_packer = READ_ONCE(stats->compressed_fragments_in_packer), | |
| }; | |
| } | |
| /** | |
| * abort_packing() - Abort packing a data_vio. | |
| * @data_vio: The data_vio to abort. | |
| */ | |
| static void abort_packing(struct data_vio *data_vio) | |
| { | |
| struct packer *packer = get_packer_from_data_vio(data_vio); | |
| WRITE_ONCE(packer->statistics.compressed_fragments_in_packer, | |
| packer->statistics.compressed_fragments_in_packer - 1); | |
| write_data_vio(data_vio); | |
| } | |
| /** | |
| * release_compressed_write_waiter() - Update a data_vio for which a successful compressed write | |
| * has completed and send it on its way. | |
| * @data_vio: The data_vio to release. | |
| * @allocation: The allocation to which the compressed block was written. | |
| */ | |
| static void release_compressed_write_waiter(struct data_vio *data_vio, | |
| struct allocation *allocation) | |
| { | |
| data_vio->new_mapped = (struct zoned_pbn) { | |
| .pbn = allocation->pbn, | |
| .zone = allocation->zone, | |
| .state = data_vio->compression.slot + VDO_MAPPING_STATE_COMPRESSED_BASE, | |
| }; | |
| vdo_share_compressed_write_lock(data_vio, allocation->lock); | |
| update_metadata_for_data_vio_write(data_vio, allocation->lock); | |
| } | |
| /** | |
| * finish_compressed_write() - Finish a compressed block write. | |
| * @completion: The compressed write completion. | |
| * | |
| * This callback is registered in continue_after_allocation(). | |
| */ | |
| static void finish_compressed_write(struct vdo_completion *completion) | |
| { | |
| struct data_vio *agent = as_data_vio(completion); | |
| struct data_vio *client, *next; | |
| assert_data_vio_in_allocated_zone(agent); | |
| /* | |
| * Process all the non-agent waiters first to ensure that the pbn lock can not be released | |
| * until all of them have had a chance to journal their increfs. | |
| */ | |
| for (client = agent->compression.next_in_batch; client != NULL; client = next) { | |
| next = client->compression.next_in_batch; | |
| release_compressed_write_waiter(client, &agent->allocation); | |
| } | |
| completion->error_handler = handle_data_vio_error; | |
| release_compressed_write_waiter(agent, &agent->allocation); | |
| } | |
| static void handle_compressed_write_error(struct vdo_completion *completion) | |
| { | |
| struct data_vio *agent = as_data_vio(completion); | |
| struct allocation *allocation = &agent->allocation; | |
| struct data_vio *client, *next; | |
| if (vdo_requeue_completion_if_needed(completion, allocation->zone->thread_id)) | |
| return; | |
| update_vio_error_stats(as_vio(completion), | |
| "Completing compressed write vio for physical block %llu with error", | |
| (unsigned long long) allocation->pbn); | |
| for (client = agent->compression.next_in_batch; client != NULL; client = next) { | |
| next = client->compression.next_in_batch; | |
| write_data_vio(client); | |
| } | |
| /* Now that we've released the batch from the packer, forget the error and continue on. */ | |
| vdo_reset_completion(completion); | |
| completion->error_handler = handle_data_vio_error; | |
| write_data_vio(agent); | |
| } | |
| /** | |
| * add_to_bin() - Put a data_vio in a specific packer_bin in which it will definitely fit. | |
| * @bin: The bin in which to put the data_vio. | |
| * @data_vio: The data_vio to add. | |
| */ | |
| static void add_to_bin(struct packer_bin *bin, struct data_vio *data_vio) | |
| { | |
| data_vio->compression.bin = bin; | |
| data_vio->compression.slot = bin->slots_used; | |
| bin->incoming[bin->slots_used++] = data_vio; | |
| } | |
| /** | |
| * remove_from_bin() - Get the next data_vio whose compression has not been canceled from a bin. | |
| * @packer: The packer. | |
| * @bin: The bin from which to get a data_vio. | |
| * | |
| * Any canceled data_vios will be moved to the canceled bin. | |
| * Return: An uncanceled data_vio from the bin or NULL if there are none. | |
| */ | |
| static struct data_vio *remove_from_bin(struct packer *packer, struct packer_bin *bin) | |
| { | |
| while (bin->slots_used > 0) { | |
| struct data_vio *data_vio = bin->incoming[--bin->slots_used]; | |
| if (!advance_data_vio_compression_stage(data_vio).may_not_compress) { | |
| data_vio->compression.bin = NULL; | |
| return data_vio; | |
| } | |
| add_to_bin(packer->canceled_bin, data_vio); | |
| } | |
| /* The bin is now empty. */ | |
| bin->free_space = VDO_COMPRESSED_BLOCK_DATA_SIZE; | |
| return NULL; | |
| } | |
| /** | |
| * initialize_compressed_block() - Initialize a compressed block. | |
| * @block: The compressed block to initialize. | |
| * @size: The size of the agent's fragment. | |
| * | |
| * This method initializes the compressed block in the compressed write agent. Because the | |
| * compressor already put the agent's compressed fragment at the start of the compressed block's | |
| * data field, it needn't be copied. So all we need do is initialize the header and set the size of | |
| * the agent's fragment. | |
| */ | |
| static void initialize_compressed_block(struct compressed_block *block, u16 size) | |
| { | |
| /* | |
| * Make sure the block layout isn't accidentally changed by changing the length of the | |
| * block header. | |
| */ | |
| BUILD_BUG_ON(sizeof(struct compressed_block_header) != COMPRESSED_BLOCK_1_0_SIZE); | |
| block->header.version = vdo_pack_version_number(COMPRESSED_BLOCK_1_0); | |
| block->header.sizes[0] = __cpu_to_le16(size); | |
| } | |
| /** | |
| * pack_fragment() - Pack a data_vio's fragment into the compressed block in which it is already | |
| * known to fit. | |
| * @compression: The agent's compression_state to pack in to. | |
| * @data_vio: The data_vio to pack. | |
| * @offset: The offset into the compressed block at which to pack the fragment. | |
| * @slot: The slot number in the compressed block. | |
| * @block: The compressed block which will be written out when batch is fully packed. | |
| * | |
| * Return: The new amount of space used. | |
| */ | |
| static block_size_t __must_check pack_fragment(struct compression_state *compression, | |
| struct data_vio *data_vio, | |
| block_size_t offset, slot_number_t slot, | |
| struct compressed_block *block) | |
| { | |
| struct compression_state *to_pack = &data_vio->compression; | |
| char *fragment = to_pack->block->data; | |
| to_pack->next_in_batch = compression->next_in_batch; | |
| compression->next_in_batch = data_vio; | |
| to_pack->slot = slot; | |
| block->header.sizes[slot] = __cpu_to_le16(to_pack->size); | |
| memcpy(&block->data[offset], fragment, to_pack->size); | |
| return (offset + to_pack->size); | |
| } | |
| /** | |
| * compressed_write_end_io() - The bio_end_io for a compressed block write. | |
| * @bio: The bio for the compressed write. | |
| */ | |
| static void compressed_write_end_io(struct bio *bio) | |
| { | |
| struct data_vio *data_vio = vio_as_data_vio(bio->bi_private); | |
| vdo_count_completed_bios(bio); | |
| set_data_vio_allocated_zone_callback(data_vio, finish_compressed_write); | |
| continue_data_vio_with_error(data_vio, blk_status_to_errno(bio->bi_status)); | |
| } | |
| /** | |
| * write_bin() - Write out a bin. | |
| * @packer: The packer. | |
| * @bin: The bin to write. | |
| */ | |
| static void write_bin(struct packer *packer, struct packer_bin *bin) | |
| { | |
| int result; | |
| block_size_t offset; | |
| slot_number_t slot = 1; | |
| struct compression_state *compression; | |
| struct compressed_block *block; | |
| struct data_vio *agent = remove_from_bin(packer, bin); | |
| struct data_vio *client; | |
| struct packer_statistics *stats; | |
| if (agent == NULL) | |
| return; | |
| compression = &agent->compression; | |
| compression->slot = 0; | |
| block = compression->block; | |
| initialize_compressed_block(block, compression->size); | |
| offset = compression->size; | |
| while ((client = remove_from_bin(packer, bin)) != NULL) | |
| offset = pack_fragment(compression, client, offset, slot++, block); | |
| /* | |
| * If the batch contains only a single vio, then we save nothing by saving the compressed | |
| * form. Continue processing the single vio in the batch. | |
| */ | |
| if (slot == 1) { | |
| abort_packing(agent); | |
| return; | |
| } | |
| if (slot < VDO_MAX_COMPRESSION_SLOTS) { | |
| /* Clear out the sizes of the unused slots */ | |
| memset(&block->header.sizes[slot], 0, | |
| (VDO_MAX_COMPRESSION_SLOTS - slot) * sizeof(__le16)); | |
| } | |
| agent->vio.completion.error_handler = handle_compressed_write_error; | |
| if (vdo_is_read_only(vdo_from_data_vio(agent))) { | |
| continue_data_vio_with_error(agent, VDO_READ_ONLY); | |
| return; | |
| } | |
| result = vio_reset_bio(&agent->vio, (char *) block, compressed_write_end_io, | |
| REQ_OP_WRITE, agent->allocation.pbn); | |
| if (result != VDO_SUCCESS) { | |
| continue_data_vio_with_error(agent, result); | |
| return; | |
| } | |
| /* | |
| * Once the compressed write is submitted, the fragments are no longer in the packer, so | |
| * update stats now. | |
| */ | |
| stats = &packer->statistics; | |
| WRITE_ONCE(stats->compressed_fragments_in_packer, | |
| (stats->compressed_fragments_in_packer - slot)); | |
| WRITE_ONCE(stats->compressed_fragments_written, | |
| (stats->compressed_fragments_written + slot)); | |
| WRITE_ONCE(stats->compressed_blocks_written, | |
| stats->compressed_blocks_written + 1); | |
| vdo_submit_data_vio(agent); | |
| } | |
| /** | |
| * add_data_vio_to_packer_bin() - Add a data_vio to a bin's incoming queue | |
| * @packer: The packer. | |
| * @bin: The bin to which to add the data_vio. | |
| * @data_vio: The data_vio to add to the bin's queue. | |
| * | |
| * Adds a data_vio to a bin's incoming queue, handles logical space change, and calls physical | |
| * space processor. | |
| */ | |
| static void add_data_vio_to_packer_bin(struct packer *packer, struct packer_bin *bin, | |
| struct data_vio *data_vio) | |
| { | |
| /* If the selected bin doesn't have room, start a new batch to make room. */ | |
| if (bin->free_space < data_vio->compression.size) | |
| write_bin(packer, bin); | |
| add_to_bin(bin, data_vio); | |
| bin->free_space -= data_vio->compression.size; | |
| /* If we happen to exactly fill the bin, start a new batch. */ | |
| if ((bin->slots_used == VDO_MAX_COMPRESSION_SLOTS) || | |
| (bin->free_space == 0)) | |
| write_bin(packer, bin); | |
| /* Now that we've finished changing the free space, restore the sort order. */ | |
| insert_in_sorted_list(packer, bin); | |
| } | |
| /** | |
| * select_bin() - Select the bin that should be used to pack the compressed data in a data_vio with | |
| * other data_vios. | |
| * @packer: The packer. | |
| * @data_vio: The data_vio. | |
| */ | |
| static struct packer_bin * __must_check select_bin(struct packer *packer, | |
| struct data_vio *data_vio) | |
| { | |
| /* | |
| * First best fit: select the bin with the least free space that has enough room for the | |
| * compressed data in the data_vio. | |
| */ | |
| struct packer_bin *bin, *fullest_bin; | |
| list_for_each_entry(bin, &packer->bins, list) { | |
| if (bin->free_space >= data_vio->compression.size) | |
| return bin; | |
| } | |
| /* | |
| * None of the bins have enough space for the data_vio. We're not allowed to create new | |
| * bins, so we have to overflow one of the existing bins. It's pretty intuitive to select | |
| * the fullest bin, since that "wastes" the least amount of free space in the compressed | |
| * block. But if the space currently used in the fullest bin is smaller than the compressed | |
| * size of the incoming block, it seems wrong to force that bin to write when giving up on | |
| * compressing the incoming data_vio would likewise "waste" the least amount of free space. | |
| */ | |
| fullest_bin = list_first_entry(&packer->bins, struct packer_bin, list); | |
| if (data_vio->compression.size >= | |
| (VDO_COMPRESSED_BLOCK_DATA_SIZE - fullest_bin->free_space)) | |
| return NULL; | |
| /* | |
| * The fullest bin doesn't have room, but writing it out and starting a new batch with the | |
| * incoming data_vio will increase the packer's free space. | |
| */ | |
| return fullest_bin; | |
| } | |
| /** | |
| * vdo_attempt_packing() - Attempt to rewrite the data in this data_vio as part of a compressed | |
| * block. | |
| * @data_vio: The data_vio to pack. | |
| */ | |
| void vdo_attempt_packing(struct data_vio *data_vio) | |
| { | |
| int result; | |
| struct packer_bin *bin; | |
| struct data_vio_compression_status status = get_data_vio_compression_status(data_vio); | |
| struct packer *packer = get_packer_from_data_vio(data_vio); | |
| assert_on_packer_thread(packer, __func__); | |
| result = VDO_ASSERT((status.stage == DATA_VIO_COMPRESSING), | |
| "attempt to pack data_vio not ready for packing, stage: %u", | |
| status.stage); | |
| if (result != VDO_SUCCESS) | |
| return; | |
| /* | |
| * Increment whether or not this data_vio will be packed or not since abort_packing() | |
| * always decrements the counter. | |
| */ | |
| WRITE_ONCE(packer->statistics.compressed_fragments_in_packer, | |
| packer->statistics.compressed_fragments_in_packer + 1); | |
| /* | |
| * If packing of this data_vio is disallowed for administrative reasons, give up before | |
| * making any state changes. | |
| */ | |
| if (!vdo_is_state_normal(&packer->state) || | |
| (data_vio->flush_generation < packer->flush_generation)) { | |
| abort_packing(data_vio); | |
| return; | |
| } | |
| /* | |
| * The advance_data_vio_compression_stage() check here verifies that the data_vio is | |
| * allowed to be compressed (if it has already been canceled, we'll fall out here). Once | |
| * the data_vio is in the DATA_VIO_PACKING state, it must be guaranteed to be put in a bin | |
| * before any more requests can be processed by the packer thread. Otherwise, a canceling | |
| * data_vio could attempt to remove the canceled data_vio from the packer and fail to | |
| * rendezvous with it. Thus, we must call select_bin() first to ensure that we will | |
| * actually add the data_vio to a bin before advancing to the DATA_VIO_PACKING stage. | |
| */ | |
| bin = select_bin(packer, data_vio); | |
| if ((bin == NULL) || | |
| (advance_data_vio_compression_stage(data_vio).stage != DATA_VIO_PACKING)) { | |
| abort_packing(data_vio); | |
| return; | |
| } | |
| add_data_vio_to_packer_bin(packer, bin, data_vio); | |
| } | |
| /** | |
| * check_for_drain_complete() - Check whether the packer has drained. | |
| * @packer: The packer. | |
| */ | |
| static void check_for_drain_complete(struct packer *packer) | |
| { | |
| if (vdo_is_state_draining(&packer->state) && (packer->canceled_bin->slots_used == 0)) | |
| vdo_finish_draining(&packer->state); | |
| } | |
| /** | |
| * write_all_non_empty_bins() - Write out all non-empty bins on behalf of a flush or suspend. | |
| * @packer: The packer being flushed. | |
| */ | |
| static void write_all_non_empty_bins(struct packer *packer) | |
| { | |
| struct packer_bin *bin; | |
| list_for_each_entry(bin, &packer->bins, list) | |
| write_bin(packer, bin); | |
| /* | |
| * We don't need to re-sort the bin here since this loop will make every bin have | |
| * the same amount of free space, so every ordering is sorted. | |
| */ | |
| check_for_drain_complete(packer); | |
| } | |
| /** | |
| * vdo_flush_packer() - Request that the packer flush asynchronously. | |
| * @packer: The packer to flush. | |
| * | |
| * All bins with at least two compressed data blocks will be written out, and any solitary pending | |
| * VIOs will be released from the packer. While flushing is in progress, any VIOs submitted to | |
| * vdo_attempt_packing() will be continued immediately without attempting to pack them. | |
| */ | |
| void vdo_flush_packer(struct packer *packer) | |
| { | |
| assert_on_packer_thread(packer, __func__); | |
| if (vdo_is_state_normal(&packer->state)) | |
| write_all_non_empty_bins(packer); | |
| } | |
| /** | |
| * vdo_remove_lock_holder_from_packer() - Remove a lock holder from the packer. | |
| * @completion: The data_vio which needs a lock held by a data_vio in the packer. The data_vio's | |
| * compression.lock_holder field will point to the data_vio to remove. | |
| */ | |
| void vdo_remove_lock_holder_from_packer(struct vdo_completion *completion) | |
| { | |
| struct data_vio *data_vio = as_data_vio(completion); | |
| struct packer *packer = get_packer_from_data_vio(data_vio); | |
| struct data_vio *lock_holder; | |
| struct packer_bin *bin; | |
| slot_number_t slot; | |
| assert_data_vio_in_packer_zone(data_vio); | |
| lock_holder = vdo_forget(data_vio->compression.lock_holder); | |
| bin = lock_holder->compression.bin; | |
| VDO_ASSERT_LOG_ONLY((bin != NULL), "data_vio in packer has a bin"); | |
| slot = lock_holder->compression.slot; | |
| bin->slots_used--; | |
| if (slot < bin->slots_used) { | |
| bin->incoming[slot] = bin->incoming[bin->slots_used]; | |
| bin->incoming[slot]->compression.slot = slot; | |
| } | |
| lock_holder->compression.bin = NULL; | |
| lock_holder->compression.slot = 0; | |
| if (bin != packer->canceled_bin) { | |
| bin->free_space += lock_holder->compression.size; | |
| insert_in_sorted_list(packer, bin); | |
| } | |
| abort_packing(lock_holder); | |
| check_for_drain_complete(packer); | |
| } | |
| /** | |
| * vdo_increment_packer_flush_generation() - Increment the flush generation in the packer. | |
| * @packer: The packer. | |
| * | |
| * This will also cause the packer to flush so that any VIOs from previous generations will exit | |
| * the packer. | |
| */ | |
| void vdo_increment_packer_flush_generation(struct packer *packer) | |
| { | |
| assert_on_packer_thread(packer, __func__); | |
| packer->flush_generation++; | |
| vdo_flush_packer(packer); | |
| } | |
| /** Implements vdo_admin_initiator_fn. */ | |
| static void initiate_drain(struct admin_state *state) | |
| { | |
| struct packer *packer = container_of(state, struct packer, state); | |
| write_all_non_empty_bins(packer); | |
| } | |
| /** | |
| * vdo_drain_packer() - Drain the packer by preventing any more VIOs from entering the packer and | |
| * then flushing. | |
| * @packer: The packer to drain. | |
| * @completion: The completion to finish when the packer has drained. | |
| */ | |
| void vdo_drain_packer(struct packer *packer, struct vdo_completion *completion) | |
| { | |
| assert_on_packer_thread(packer, __func__); | |
| vdo_start_draining(&packer->state, VDO_ADMIN_STATE_SUSPENDING, completion, | |
| initiate_drain); | |
| } | |
| /** | |
| * vdo_resume_packer() - Resume a packer which has been suspended. | |
| * @packer: The packer to resume. | |
| * @parent: The completion to finish when the packer has resumed. | |
| */ | |
| void vdo_resume_packer(struct packer *packer, struct vdo_completion *parent) | |
| { | |
| assert_on_packer_thread(packer, __func__); | |
| vdo_continue_completion(parent, vdo_resume_if_quiescent(&packer->state)); | |
| } | |
| static void dump_packer_bin(const struct packer_bin *bin, bool canceled) | |
| { | |
| if (bin->slots_used == 0) | |
| /* Don't dump empty bins. */ | |
| return; | |
| vdo_log_info(" %sBin slots_used=%u free_space=%zu", | |
| (canceled ? "Canceled" : ""), bin->slots_used, bin->free_space); | |
| /* | |
| * FIXME: dump vios in bin->incoming? The vios should have been dumped from the vio pool. | |
| * Maybe just dump their addresses so it's clear they're here? | |
| */ | |
| } | |
| /** | |
| * vdo_dump_packer() - Dump the packer. | |
| * @packer: The packer. | |
| * | |
| * Context: dumps in a thread-unsafe fashion. | |
| */ | |
| void vdo_dump_packer(const struct packer *packer) | |
| { | |
| struct packer_bin *bin; | |
| vdo_log_info("packer"); | |
| vdo_log_info(" flushGeneration=%llu state %s packer_bin_count=%llu", | |
| (unsigned long long) packer->flush_generation, | |
| vdo_get_admin_state_code(&packer->state)->name, | |
| (unsigned long long) packer->size); | |
| list_for_each_entry(bin, &packer->bins, list) | |
| dump_packer_bin(bin, false); | |
| dump_packer_bin(packer->canceled_bin, true); | |
| } |