Quelle data-vio.c
Sprache: C
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright 2023 Red Hat
*/
#include "data-vio.h"
#include <linux/atomic.h>
#include <linux/bio.h>
#include <linux/blkdev.h>
#include <linux/delay.h>
#include <linux/device-mapper.h>
#include <linux/jiffies.h>
#include <linux/kernel.h>
#include <linux/list.h>
#include <linux/lz4.h>
#include <linux/minmax.h>
#include <linux/sched.h>
#include <linux/spinlock.h>
#include <linux/wait.h>
#include "logger.h"
#include "memory-alloc.h"
#include "murmurhash3.h"
#include "permassert.h"
#include "block-map.h"
#include "dump.h"
#include "encodings.h"
#include "int-map.h"
#include "io-submitter.h"
#include "logical-zone.h"
#include "packer.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 : Bio flags .
*
* For certain flags set on user bios , if the user bio has not yet been acknowledged , setting those
* flags on our own bio ( s ) for that request may help underlying layers better fulfill the user
* bio ' s needs . This constant contains the aggregate of those flags ; VDO strips all the other
* flags , as they convey incorrect information .
*
* These flags are always irrelevant if we have already finished the user bio as they are only
* hints on IO importance . If VDO has finished the user bio , any remaining IO done doesn ' t care how
* important finishing the finished bio was .
*
* Note that bio . c contains the complete list of flags we believe may be set ; the following list
* explains the action taken with each of those flags VDO could receive :
*
* * REQ_SYNC : Passed down if the user bio is not yet completed , since it indicates the user bio
* completion is required for further work to be done by the issuer .
* * REQ_META : Passed down if the user bio is not yet completed , since it may mean the lower layer
* treats it as more urgent , similar to REQ_SYNC .
* * REQ_PRIO : Passed down if the user bio is not yet completed , since it indicates the user bio is
* important .
* * REQ_NOMERGE : Set only if the incoming bio was split ; irrelevant to VDO IO .
* * REQ_IDLE : Set if the incoming bio had more IO quickly following ; VDO ' s IO pattern doesn ' t
* match incoming IO , so this flag is incorrect for it .
* * REQ_FUA : Handled separately , and irrelevant to VDO IO otherwise .
* * REQ_RAHEAD : Passed down , as , for reads , it indicates trivial importance .
* * REQ_BACKGROUND : Not passed down , as VIOs are a limited resource and VDO needs them recycled
* ASAP to service heavy load , which is the only place where REQ_BACKGROUND might aid in load
* prioritization .
*/
static blk_opf_t PASSTHROUGH_FLAGS = (REQ_PRIO | REQ_META | REQ_SYNC | REQ_RAHEAD);
/**
* DOC :
*
* The data_vio_pool maintains the pool of data_vios which a vdo uses to service incoming bios . For
* correctness , and in order to avoid potentially expensive or blocking memory allocations during
* normal operation , the number of concurrently active data_vios is capped . Furthermore , in order
* to avoid starvation of reads and writes , at most 75 % of the data_vios may be used for
* discards . The data_vio_pool is responsible for enforcing these limits . Threads submitting bios
* for which a data_vio or discard permit are not available will block until the necessary
* resources are available . The pool is also responsible for distributing resources to blocked
* threads and waking them . Finally , the pool attempts to batch the work of recycling data_vios by
* performing the work of actually assigning resources to blocked threads or placing data_vios back
* into the pool on a single cpu at a time .
*
* The pool contains two " limiters " , one for tracking data_vios and one for tracking discard
* permits . The limiters also provide safe cross - thread access to pool statistics without the need
* to take the pool ' s lock . When a thread submits a bio to a vdo device , it will first attempt to
* get a discard permit if it is a discard , and then to get a data_vio . If the necessary resources
* are available , the incoming bio will be assigned to the acquired data_vio , and it will be
* launched . However , if either of these are unavailable , the arrival time of the bio is recorded
* in the bio ' s bi_private field , the bio and its submitter are both queued on the appropriate
* limiter and the submitting thread will then put itself to sleep . ( note that this mechanism will
* break if jiffies are only 32 bits . )
*
* Whenever a data_vio has completed processing for the bio it was servicing , release_data_vio ( )
* will be called on it . This function will add the data_vio to a funnel queue , and then check the
* state of the pool . If the pool is not currently processing released data_vios , the pool ' s
* completion will be enqueued on a cpu queue . This obviates the need for the releasing threads to
* hold the pool ' s lock , and also batches release work while avoiding starvation of the cpu
* threads .
*
* Whenever the pool ' s completion is run on a cpu thread , it calls process_release_callback ( ) which
* processes a batch of returned data_vios ( currently at most 32 ) from the pool ' s funnel queue . For
* each data_vio , it first checks whether that data_vio was processing a discard . If so , and there
* is a blocked bio waiting for a discard permit , that permit is notionally transferred to the
* eldest discard waiter , and that waiter is moved to the end of the list of discard bios waiting
* for a data_vio . If there are no discard waiters , the discard permit is returned to the pool .
* Next , the data_vio is assigned to the oldest blocked bio which either has a discard permit , or
* doesn ' t need one and relaunched . If neither of these exist , the data_vio is returned to the
* pool . Finally , if any waiting bios were launched , the threads which blocked trying to submit
* them are awakened .
*/
#define DATA_VIO_RELEASE_BATCH_SIZE 128
static const unsigned int VDO_SECTORS_PER_BLOCK_MASK = VDO_SECTORS_PER_BLOCK - 1 ;
static const u32 COMPRESSION_STATUS_MASK = 0 xff;
static const u32 MAY_NOT_COMPRESS_MASK = 0 x80000000;
struct limiter;
typedef void (*assigner_fn)(struct limiter *limiter);
/* Bookkeeping structure for a single type of resource. */
struct limiter {
/* The data_vio_pool to which this limiter belongs */
struct data_vio_pool *pool;
/* The maximum number of data_vios available */
data_vio_count_t limit;
/* The number of resources in use */
data_vio_count_t busy;
/* The maximum number of resources ever simultaneously in use */
data_vio_count_t max_busy;
/* The number of resources to release */
data_vio_count_t release_count;
/* The number of waiters to wake */
data_vio_count_t wake_count;
/* The list of waiting bios which are known to process_release_callback() */
struct bio_list waiters;
/* The list of waiting bios which are not yet known to process_release_callback() */
struct bio_list new_waiters;
/* The list of waiters which have their permits */
struct bio_list *permitted_waiters;
/* The function for assigning a resource to a waiter */
assigner_fn assigner;
/* The queue of blocked threads */
wait_queue_head_t blocked_threads;
/* The arrival time of the eldest waiter */
u64 arrival;
};
/*
* A data_vio_pool is a collection of preallocated data_vios which may be acquired from any thread ,
* and are released in batches .
*/
struct data_vio_pool {
/* Completion for scheduling releases */
struct vdo_completion completion;
/* The administrative state of the pool */
struct admin_state state;
/* Lock protecting the pool */
spinlock_t lock;
/* The main limiter controlling the total data_vios in the pool. */
struct limiter limiter;
/* The limiter controlling data_vios for discard */
struct limiter discard_limiter;
/* The list of bios which have discard permits but still need a data_vio */
struct bio_list permitted_discards;
/* The list of available data_vios */
struct list_head available;
/* The queue of data_vios waiting to be returned to the pool */
struct funnel_queue *queue;
/* Whether the pool is processing, or scheduled to process releases */
atomic_t processing;
/* The data vios in the pool */
struct data_vio data_vios[];
};
static const char * const ASYNC_OPERATION_NAMES[] = {
"launch" ,
"acknowledge_write" ,
"acquire_hash_lock" ,
"attempt_logical_block_lock" ,
"lock_duplicate_pbn" ,
"check_for_duplication" ,
"cleanup" ,
"compress_data_vio" ,
"find_block_map_slot" ,
"get_mapped_block_for_read" ,
"get_mapped_block_for_write" ,
"hash_data_vio" ,
"journal_remapping" ,
"vdo_attempt_packing" ,
"put_mapped_block" ,
"read_data_vio" ,
"update_dedupe_index" ,
"update_reference_counts" ,
"verify_duplication" ,
"write_data_vio" ,
};
/* The steps taken cleaning up a VIO, in the order they are performed. */
enum data_vio_cleanup_stage {
VIO_CLEANUP_START,
VIO_RELEASE_HASH_LOCK = VIO_CLEANUP_START,
VIO_RELEASE_ALLOCATED,
VIO_RELEASE_RECOVERY_LOCKS,
VIO_RELEASE_LOGICAL,
VIO_CLEANUP_DONE
};
static inline struct data_vio_pool * __must_check
as_data_vio_pool(struct vdo_completion *completion)
{
vdo_assert_completion_type(completion, VDO_DATA_VIO_POOL_COMPLETION);
return container_of(completion, struct data_vio_pool, completion);
}
static inline u64 get_arrival_time(struct bio *bio)
{
return (u64) bio->bi_private;
}
/**
* check_for_drain_complete_locked ( ) - Check whether a data_vio_pool has no outstanding data_vios
* or waiters while holding the pool ' s lock .
*/
static bool check_for_drain_complete_locked(struct data_vio_pool *pool)
{
if (pool->limiter.busy > 0 )
return false ;
VDO_ASSERT_LOG_ONLY((pool->discard_limiter.busy == 0 ),
"no outstanding discard permits" );
return (bio_list_empty(&pool->limiter.new_waiters) &&
bio_list_empty(&pool->discard_limiter.new_waiters));
}
static void initialize_lbn_lock(struct data_vio *data_vio, logical_block_number_t lbn)
{
struct vdo *vdo = vdo_from_data_vio(data_vio);
zone_count_t zone_number;
struct lbn_lock *lock = &data_vio->logical;
lock->lbn = lbn;
lock->locked = false ;
vdo_waitq_init(&lock->waiters);
zone_number = vdo_compute_logical_zone(data_vio);
lock->zone = &vdo->logical_zones->zones[zone_number];
}
static void launch_locked_request(struct data_vio *data_vio)
{
data_vio->logical.locked = true ;
if (data_vio->write) {
struct vdo *vdo = vdo_from_data_vio(data_vio);
if (vdo_is_read_only(vdo)) {
continue_data_vio_with_error(data_vio, VDO_READ_ONLY);
return ;
}
}
data_vio->last_async_operation = VIO_ASYNC_OP_FIND_BLOCK_MAP_SLOT;
vdo_find_block_map_slot(data_vio);
}
static void acknowledge_data_vio(struct data_vio *data_vio)
{
struct vdo *vdo = vdo_from_data_vio(data_vio);
struct bio *bio = data_vio->user_bio;
int error = vdo_status_to_errno(data_vio->vio.completion.result);
if (bio == NULL)
return ;
VDO_ASSERT_LOG_ONLY((data_vio->remaining_discard <=
(u32) (VDO_BLOCK_SIZE - data_vio->offset)),
"data_vio to acknowledge is not an incomplete discard" );
data_vio->user_bio = NULL;
vdo_count_bios(&vdo->stats.bios_acknowledged, bio);
if (data_vio->is_partial)
vdo_count_bios(&vdo->stats.bios_acknowledged_partial, bio);
bio->bi_status = errno_to_blk_status(error);
bio_endio(bio);
}
static void copy_to_bio(struct bio *bio, char *data_ptr)
{
struct bio_vec biovec;
struct bvec_iter iter;
bio_for_each_segment(biovec, bio, iter) {
memcpy_to_bvec(&biovec, data_ptr);
data_ptr += biovec.bv_len;
}
}
struct data_vio_compression_status get_data_vio_compression_status(struct data_vio *data_vio)
{
u32 packed = atomic_read(&data_vio->compression.status);
/* pairs with cmpxchg in set_data_vio_compression_status */
smp_rmb();
return (struct data_vio_compression_status) {
.stage = packed & COMPRESSION_STATUS_MASK,
.may_not_compress = ((packed & MAY_NOT_COMPRESS_MASK) != 0 ),
};
}
/**
* pack_status ( ) - Convert a data_vio_compression_status into a u32 which may be stored
* atomically .
* @ status : The state to convert .
*
* Return : The compression state packed into a u32 .
*/
static u32 __must_check pack_status(struct data_vio_compression_status status)
{
return status.stage | (status.may_not_compress ? MAY_NOT_COMPRESS_MASK : 0 );
}
/**
* set_data_vio_compression_status ( ) - Set the compression status of a data_vio .
* @ data_vio : The data_vio to change .
* @ status : The expected current status of the data_vio .
* @ new_status : The status to set .
*
* Return : true if the new status was set , false if the data_vio ' s compression status did not
* match the expected state , and so was left unchanged .
*/
static bool __must_check
set_data_vio_compression_status(struct data_vio *data_vio,
struct data_vio_compression_status status,
struct data_vio_compression_status new_status)
{
u32 actual;
u32 expected = pack_status(status);
u32 replacement = pack_status(new_status);
/*
* Extra barriers because this was original developed using a CAS operation that implicitly
* had them .
*/
smp_mb__before_atomic();
actual = atomic_cmpxchg(&data_vio->compression.status, expected, replacement);
/* same as before_atomic */
smp_mb__after_atomic();
return (expected == actual);
}
struct data_vio_compression_status advance_data_vio_compression_stage(struct data_vio *data_vio)
{
for (;;) {
struct data_vio_compression_status status =
get_data_vio_compression_status(data_vio);
struct data_vio_compression_status new_status = status;
if (status.stage == DATA_VIO_POST_PACKER) {
/* We're already in the last stage. */
return status;
}
if (status.may_not_compress) {
/*
* Compression has been dis - allowed for this VIO , so skip the rest of the
* path and go to the end .
*/
new_status.stage = DATA_VIO_POST_PACKER;
} else {
/* Go to the next state. */
new_status.stage++;
}
if (set_data_vio_compression_status(data_vio, status, new_status))
return new_status;
/* Another thread changed the status out from under us so try again. */
}
}
/**
* cancel_data_vio_compression ( ) - Prevent this data_vio from being compressed or packed .
*
* Return : true if the data_vio is in the packer and the caller was the first caller to cancel it .
*/
bool cancel_data_vio_compression(struct data_vio *data_vio)
{
struct data_vio_compression_status status, new_status;
for (;;) {
status = get_data_vio_compression_status(data_vio);
if (status.may_not_compress || (status.stage == DATA_VIO_POST_PACKER)) {
/* This data_vio is already set up to not block in the packer. */
break ;
}
new_status.stage = status.stage;
new_status.may_not_compress = true ;
if (set_data_vio_compression_status(data_vio, status, new_status))
break ;
}
return ((status.stage == DATA_VIO_PACKING) && !status.may_not_compress);
}
/**
* attempt_logical_block_lock ( ) - Attempt to acquire the lock on a logical block .
* @ completion : The data_vio for an external data request as a completion .
*
* This is the start of the path for all external requests . It is registered in launch_data_vio ( ) .
*/
static void attempt_logical_block_lock(struct vdo_completion *completion)
{
struct data_vio *data_vio = as_data_vio(completion);
struct lbn_lock *lock = &data_vio->logical;
struct vdo *vdo = vdo_from_data_vio(data_vio);
struct data_vio *lock_holder;
int result;
assert_data_vio_in_logical_zone(data_vio);
if (data_vio->logical.lbn >= vdo->states.vdo.config.logical_blocks) {
continue_data_vio_with_error(data_vio, VDO_OUT_OF_RANGE);
return ;
}
result = vdo_int_map_put(lock->zone->lbn_operations, lock->lbn,
data_vio, false , (void **) &lock_holder);
if (result != VDO_SUCCESS) {
continue_data_vio_with_error(data_vio, result);
return ;
}
if (lock_holder == NULL) {
/* We got the lock */
launch_locked_request(data_vio);
return ;
}
result = VDO_ASSERT(lock_holder->logical.locked, "logical block lock held" );
if (result != VDO_SUCCESS) {
continue_data_vio_with_error(data_vio, result);
return ;
}
/*
* If the new request is a pure read request ( not read - modify - write ) and the lock_holder is
* writing and has received an allocation , service the read request immediately by copying
* data from the lock_holder to avoid having to flush the write out of the packer just to
* prevent the read from waiting indefinitely . If the lock_holder does not yet have an
* allocation , prevent it from blocking in the packer and wait on it . This is necessary in
* order to prevent returning data that may not have actually been written .
*/
if (!data_vio->write && READ_ONCE(lock_holder->allocation_succeeded)) {
copy_to_bio(data_vio->user_bio, lock_holder->vio.data + data_vio->offset);
acknowledge_data_vio(data_vio);
complete_data_vio(completion);
return ;
}
data_vio->last_async_operation = VIO_ASYNC_OP_ATTEMPT_LOGICAL_BLOCK_LOCK;
vdo_waitq_enqueue_waiter(&lock_holder->logical.waiters, &data_vio->waiter);
/*
* Prevent writes and read - modify - writes from blocking indefinitely on lock holders in the
* packer .
*/
if (lock_holder->write && cancel_data_vio_compression(lock_holder)) {
data_vio->compression.lock_holder = lock_holder;
launch_data_vio_packer_callback(data_vio,
vdo_remove_lock_holder_from_packer);
}
}
/**
* launch_data_vio ( ) - ( Re ) initialize a data_vio to have a new logical block number , keeping the
* same parent and other state and send it on its way .
*/
static void launch_data_vio(struct data_vio *data_vio, logical_block_number_t lbn)
{
struct vdo_completion *completion = &data_vio->vio.completion;
/*
* Clearing the tree lock must happen before initializing the LBN lock , which also adds
* information to the tree lock .
*/
memset(&data_vio->tree_lock, 0 , sizeof (data_vio->tree_lock));
initialize_lbn_lock(data_vio, lbn);
INIT_LIST_HEAD(&data_vio->hash_lock_entry);
INIT_LIST_HEAD(&data_vio->write_entry);
memset(&data_vio->allocation, 0 , sizeof (data_vio->allocation));
data_vio->is_duplicate = false ;
memset(&data_vio->record_name, 0 , sizeof (data_vio->record_name));
memset(&data_vio->duplicate, 0 , sizeof (data_vio->duplicate));
vdo_reset_completion(&data_vio->decrement_completion);
vdo_reset_completion(completion);
completion->error_handler = handle_data_vio_error;
set_data_vio_logical_callback(data_vio, attempt_logical_block_lock);
vdo_enqueue_completion(completion, VDO_DEFAULT_Q_MAP_BIO_PRIORITY);
}
static bool is_zero_block(char *block)
{
int i;
for (i = 0 ; i < VDO_BLOCK_SIZE; i += sizeof (u64)) {
if (*((u64 *) &block[i]))
return false ;
}
return true ;
}
static void copy_from_bio(struct bio *bio, char *data_ptr)
{
struct bio_vec biovec;
struct bvec_iter iter;
bio_for_each_segment(biovec, bio, iter) {
memcpy_from_bvec(data_ptr, &biovec);
data_ptr += biovec.bv_len;
}
}
static void launch_bio(struct vdo *vdo, struct data_vio *data_vio, struct bio *bio)
{
logical_block_number_t lbn;
/*
* Zero out the fields which don ' t need to be preserved ( i . e . which are not pointers to
* separately allocated objects ) .
*/
memset(data_vio, 0 , offsetof(struct data_vio, vio));
memset(&data_vio->compression, 0 , offsetof(struct compression_state, block));
data_vio->user_bio = bio;
data_vio->offset = to_bytes(bio->bi_iter.bi_sector & VDO_SECTORS_PER_BLOCK_MASK);
data_vio->is_partial = (bio->bi_iter.bi_size < VDO_BLOCK_SIZE) || (data_vio->offset != 0 );
/*
* Discards behave very differently than other requests when coming in from device - mapper .
* We have to be able to handle any size discards and various sector offsets within a
* block .
*/
if (bio_op(bio) == REQ_OP_DISCARD) {
data_vio->remaining_discard = bio->bi_iter.bi_size;
data_vio->write = true ;
data_vio->is_discard = true ;
if (data_vio->is_partial) {
vdo_count_bios(&vdo->stats.bios_in_partial, bio);
data_vio->read = true ;
}
} else if (data_vio->is_partial) {
vdo_count_bios(&vdo->stats.bios_in_partial, bio);
data_vio->read = true ;
if (bio_data_dir(bio) == WRITE)
data_vio->write = true ;
} else if (bio_data_dir(bio) == READ) {
data_vio->read = true ;
} else {
/*
* Copy the bio data to a char array so that we can continue to use the data after
* we acknowledge the bio .
*/
copy_from_bio(bio, data_vio->vio.data);
data_vio->is_zero = is_zero_block(data_vio->vio.data);
data_vio->write = true ;
}
if (data_vio->user_bio->bi_opf & REQ_FUA)
data_vio->fua = true ;
lbn = (bio->bi_iter.bi_sector - vdo->starting_sector_offset) / VDO_SECTORS_PER_BLOCK;
launch_data_vio(data_vio, lbn);
}
static void assign_data_vio(struct limiter *limiter, struct data_vio *data_vio)
{
struct bio *bio = bio_list_pop(limiter->permitted_waiters);
launch_bio(limiter->pool->completion.vdo, data_vio, bio);
limiter->wake_count++;
bio = bio_list_peek(limiter->permitted_waiters);
limiter->arrival = ((bio == NULL) ? U64_MAX : get_arrival_time(bio));
}
static void assign_discard_permit(struct limiter *limiter)
{
struct bio *bio = bio_list_pop(&limiter->waiters);
if (limiter->arrival == U64_MAX)
limiter->arrival = get_arrival_time(bio);
bio_list_add(limiter->permitted_waiters, bio);
}
static void get_waiters(struct limiter *limiter)
{
bio_list_merge_init(&limiter->waiters, &limiter->new_waiters);
}
static inline struct data_vio *get_available_data_vio(struct data_vio_pool *pool)
{
struct data_vio *data_vio =
list_first_entry(&pool->available, struct data_vio, pool_entry);
list_del_init(&data_vio->pool_entry);
return data_vio;
}
static void assign_data_vio_to_waiter(struct limiter *limiter)
{
assign_data_vio(limiter, get_available_data_vio(limiter->pool));
}
static void update_limiter(struct limiter *limiter)
{
struct bio_list *waiters = &limiter->waiters;
data_vio_count_t available = limiter->limit - limiter->busy;
VDO_ASSERT_LOG_ONLY((limiter->release_count <= limiter->busy),
"Release count %u is not more than busy count %u" ,
limiter->release_count, limiter->busy);
get_waiters(limiter);
for (; (limiter->release_count > 0 ) && !bio_list_empty(waiters); limiter->release_count--)
limiter->assigner(limiter);
if (limiter->release_count > 0 ) {
WRITE_ONCE(limiter->busy, limiter->busy - limiter->release_count);
limiter->release_count = 0 ;
return ;
}
for (; (available > 0 ) && !bio_list_empty(waiters); available--)
limiter->assigner(limiter);
WRITE_ONCE(limiter->busy, limiter->limit - available);
if (limiter->max_busy < limiter->busy)
WRITE_ONCE(limiter->max_busy, limiter->busy);
}
/**
* schedule_releases ( ) - Ensure that release processing is scheduled .
*
* If this call switches the state to processing , enqueue . Otherwise , some other thread has already
* done so .
*/
static void schedule_releases(struct data_vio_pool *pool)
{
/* Pairs with the barrier in process_release_callback(). */
smp_mb__before_atomic();
if (atomic_cmpxchg(&pool->processing, false , true ))
return ;
pool->completion.requeue = true ;
vdo_launch_completion_with_priority(&pool->completion,
CPU_Q_COMPLETE_VIO_PRIORITY);
}
static void reuse_or_release_resources(struct data_vio_pool *pool,
struct data_vio *data_vio,
struct list_head *returned)
{
if (data_vio->remaining_discard > 0 ) {
if (bio_list_empty(&pool->discard_limiter.waiters)) {
/* Return the data_vio's discard permit. */
pool->discard_limiter.release_count++;
} else {
assign_discard_permit(&pool->discard_limiter);
}
}
if (pool->limiter.arrival < pool->discard_limiter.arrival) {
assign_data_vio(&pool->limiter, data_vio);
} else if (pool->discard_limiter.arrival < U64_MAX) {
assign_data_vio(&pool->discard_limiter, data_vio);
} else {
list_add(&data_vio->pool_entry, returned);
pool->limiter.release_count++;
}
}
/**
* process_release_callback ( ) - Process a batch of data_vio releases .
* @ completion : The pool with data_vios to release .
*/
static void process_release_callback(struct vdo_completion *completion)
{
struct data_vio_pool *pool = as_data_vio_pool(completion);
bool reschedule;
bool drained;
data_vio_count_t processed;
data_vio_count_t to_wake;
data_vio_count_t discards_to_wake;
LIST_HEAD(returned);
spin_lock(&pool->lock);
get_waiters(&pool->discard_limiter);
get_waiters(&pool->limiter);
spin_unlock(&pool->lock);
if (pool->limiter.arrival == U64_MAX) {
struct bio *bio = bio_list_peek(&pool->limiter.waiters);
if (bio != NULL)
pool->limiter.arrival = get_arrival_time(bio);
}
for (processed = 0 ; processed < DATA_VIO_RELEASE_BATCH_SIZE; processed++) {
struct data_vio *data_vio;
struct funnel_queue_entry *entry = vdo_funnel_queue_poll(pool->queue);
if (entry == NULL)
break ;
data_vio = as_data_vio(container_of(entry, struct vdo_completion,
work_queue_entry_link));
acknowledge_data_vio(data_vio);
reuse_or_release_resources(pool, data_vio, &returned);
}
spin_lock(&pool->lock);
/*
* There is a race where waiters could be added while we are in the unlocked section above .
* Those waiters could not see the resources we are now about to release , so we assign
* those resources now as we have no guarantee of being rescheduled . This is handled in
* update_limiter ( ) .
*/
update_limiter(&pool->discard_limiter);
list_splice(&returned, &pool->available);
update_limiter(&pool->limiter);
to_wake = pool->limiter.wake_count;
pool->limiter.wake_count = 0 ;
discards_to_wake = pool->discard_limiter.wake_count;
pool->discard_limiter.wake_count = 0 ;
atomic_set(&pool->processing, false );
/* Pairs with the barrier in schedule_releases(). */
smp_mb();
reschedule = !vdo_is_funnel_queue_empty(pool->queue);
drained = (!reschedule &&
vdo_is_state_draining(&pool->state) &&
check_for_drain_complete_locked(pool));
spin_unlock(&pool->lock);
if (to_wake > 0 )
wake_up_nr(&pool->limiter.blocked_threads, to_wake);
if (discards_to_wake > 0 )
wake_up_nr(&pool->discard_limiter.blocked_threads, discards_to_wake);
if (reschedule)
schedule_releases(pool);
else if (drained)
vdo_finish_draining(&pool->state);
}
static void initialize_limiter(struct limiter *limiter, struct data_vio_pool *pool,
assigner_fn assigner, data_vio_count_t limit)
{
limiter->pool = pool;
limiter->assigner = assigner;
limiter->limit = limit;
limiter->arrival = U64_MAX;
init_waitqueue_head(&limiter->blocked_threads);
}
/**
* initialize_data_vio ( ) - Allocate the components of a data_vio .
*
* The caller is responsible for cleaning up the data_vio on error .
*
* Return : VDO_SUCCESS or an error .
*/
static int initialize_data_vio(struct data_vio *data_vio, struct vdo *vdo)
{
struct bio *bio;
int result;
BUILD_BUG_ON(VDO_BLOCK_SIZE > PAGE_SIZE);
result = vdo_allocate_memory(VDO_BLOCK_SIZE, 0 , "data_vio data" ,
&data_vio->vio.data);
if (result != VDO_SUCCESS)
return vdo_log_error_strerror(result,
"data_vio data allocation failure" );
result = vdo_allocate_memory(VDO_BLOCK_SIZE, 0 , "compressed block" ,
&data_vio->compression.block);
if (result != VDO_SUCCESS) {
return vdo_log_error_strerror(result,
"data_vio compressed block allocation failure" );
}
result = vdo_allocate_memory(VDO_BLOCK_SIZE, 0 , "vio scratch" ,
&data_vio->scratch_block);
if (result != VDO_SUCCESS)
return vdo_log_error_strerror(result,
"data_vio scratch allocation failure" );
result = vdo_create_bio(&bio);
if (result != VDO_SUCCESS)
return vdo_log_error_strerror(result,
"data_vio data bio allocation failure" );
vdo_initialize_completion(&data_vio->decrement_completion, vdo,
VDO_DECREMENT_COMPLETION);
initialize_vio(&data_vio->vio, bio, 1 , VIO_TYPE_DATA, VIO_PRIORITY_DATA, vdo);
return VDO_SUCCESS;
}
static void destroy_data_vio(struct data_vio *data_vio)
{
if (data_vio == NULL)
return ;
vdo_free_bio(vdo_forget(data_vio->vio.bio));
vdo_free(vdo_forget(data_vio->vio.data));
vdo_free(vdo_forget(data_vio->compression.block));
vdo_free(vdo_forget(data_vio->scratch_block));
}
/**
* make_data_vio_pool ( ) - Initialize a data_vio pool .
* @ vdo : The vdo to which the pool will belong .
* @ pool_size : The number of data_vios in the pool .
* @ discard_limit : The maximum number of data_vios which may be used for discards .
* @ pool_ptr : A pointer to hold the newly allocated pool .
*/
int make_data_vio_pool(struct vdo *vdo, data_vio_count_t pool_size,
data_vio_count_t discard_limit, struct data_vio_pool **pool_ptr)
{
int result;
struct data_vio_pool *pool;
data_vio_count_t i;
result = vdo_allocate_extended(struct data_vio_pool, pool_size, struct data_vio,
__func__, &pool);
if (result != VDO_SUCCESS)
return result;
VDO_ASSERT_LOG_ONLY((discard_limit <= pool_size),
"discard limit does not exceed pool size" );
initialize_limiter(&pool->discard_limiter, pool, assign_discard_permit,
discard_limit);
pool->discard_limiter.permitted_waiters = &pool->permitted_discards;
initialize_limiter(&pool->limiter, pool, assign_data_vio_to_waiter, pool_size);
pool->limiter.permitted_waiters = &pool->limiter.waiters;
INIT_LIST_HEAD(&pool->available);
spin_lock_init(&pool->lock);
vdo_set_admin_state_code(&pool->state, VDO_ADMIN_STATE_NORMAL_OPERATION);
vdo_initialize_completion(&pool->completion, vdo, VDO_DATA_VIO_POOL_COMPLETION);
vdo_prepare_completion(&pool->completion, process_release_callback,
process_release_callback, vdo->thread_config.cpu_thread,
NULL);
result = vdo_make_funnel_queue(&pool->queue);
if (result != VDO_SUCCESS) {
free_data_vio_pool(vdo_forget(pool));
return result;
}
for (i = 0 ; i < pool_size; i++) {
struct data_vio *data_vio = &pool->data_vios[i];
result = initialize_data_vio(data_vio, vdo);
if (result != VDO_SUCCESS) {
destroy_data_vio(data_vio);
free_data_vio_pool(pool);
return result;
}
list_add(&data_vio->pool_entry, &pool->available);
}
*pool_ptr = pool;
return VDO_SUCCESS;
}
/**
* free_data_vio_pool ( ) - Free a data_vio_pool and the data_vios in it .
*
* All data_vios must be returned to the pool before calling this function .
*/
void free_data_vio_pool(struct data_vio_pool *pool)
{
struct data_vio *data_vio, *tmp;
if (pool == NULL)
return ;
/*
* Pairs with the barrier in process_release_callback ( ) . Possibly not needed since it
* caters to an enqueue vs . free race .
*/
smp_mb();
BUG_ON(atomic_read(&pool->processing));
spin_lock(&pool->lock);
VDO_ASSERT_LOG_ONLY((pool->limiter.busy == 0 ),
"data_vio pool must not have %u busy entries when being freed" ,
pool->limiter.busy);
VDO_ASSERT_LOG_ONLY((bio_list_empty(&pool->limiter.waiters) &&
bio_list_empty(&pool->limiter.new_waiters)),
"data_vio pool must not have threads waiting to read or write when being freed" );
VDO_ASSERT_LOG_ONLY((bio_list_empty(&pool->discard_limiter.waiters) &&
bio_list_empty(&pool->discard_limiter.new_waiters)),
"data_vio pool must not have threads waiting to discard when being freed" );
spin_unlock(&pool->lock);
list_for_each_entry_safe(data_vio, tmp, &pool->available, pool_entry) {
list_del_init(&data_vio->pool_entry);
destroy_data_vio(data_vio);
}
vdo_free_funnel_queue(vdo_forget(pool->queue));
vdo_free(pool);
}
static bool acquire_permit(struct limiter *limiter)
{
if (limiter->busy >= limiter->limit)
return false ;
WRITE_ONCE(limiter->busy, limiter->busy + 1 );
if (limiter->max_busy < limiter->busy)
WRITE_ONCE(limiter->max_busy, limiter->busy);
return true ;
}
static void wait_permit(struct limiter *limiter, struct bio *bio)
__releases(&limiter->pool->lock)
{
DEFINE_WAIT(wait);
bio_list_add(&limiter->new_waiters, bio);
prepare_to_wait_exclusive(&limiter->blocked_threads, &wait,
TASK_UNINTERRUPTIBLE);
spin_unlock(&limiter->pool->lock);
io_schedule();
finish_wait(&limiter->blocked_threads, &wait);
}
/**
* vdo_launch_bio ( ) - Acquire a data_vio from the pool , assign the bio to it , and launch it .
*
* This will block if data_vios or discard permits are not available .
*/
void vdo_launch_bio(struct data_vio_pool *pool, struct bio *bio)
{
struct data_vio *data_vio;
VDO_ASSERT_LOG_ONLY(!vdo_is_state_quiescent(&pool->state),
"data_vio_pool not quiescent on acquire" );
bio->bi_private = (void *) jiffies;
spin_lock(&pool->lock);
if ((bio_op(bio) == REQ_OP_DISCARD) &&
!acquire_permit(&pool->discard_limiter)) {
wait_permit(&pool->discard_limiter, bio);
return ;
}
if (!acquire_permit(&pool->limiter)) {
wait_permit(&pool->limiter, bio);
return ;
}
data_vio = get_available_data_vio(pool);
spin_unlock(&pool->lock);
launch_bio(pool->completion.vdo, data_vio, bio);
}
/* Implements vdo_admin_initiator_fn. */
static void initiate_drain(struct admin_state *state)
{
bool drained;
struct data_vio_pool *pool = container_of(state, struct data_vio_pool, state);
spin_lock(&pool->lock);
drained = check_for_drain_complete_locked(pool);
spin_unlock(&pool->lock);
if (drained)
vdo_finish_draining(state);
}
static void assert_on_vdo_cpu_thread(const struct vdo *vdo, const char *name)
{
VDO_ASSERT_LOG_ONLY((vdo_get_callback_thread_id() == vdo->thread_config.cpu_thread),
"%s called on cpu thread" , name);
}
/**
* drain_data_vio_pool ( ) - Wait asynchronously for all data_vios to be returned to the pool .
* @ completion : The completion to notify when the pool has drained .
*/
void drain_data_vio_pool(struct data_vio_pool *pool, struct vdo_completion *completion)
{
assert_on_vdo_cpu_thread(completion->vdo, __func__);
vdo_start_draining(&pool->state, VDO_ADMIN_STATE_SUSPENDING, completion,
initiate_drain);
}
/**
* resume_data_vio_pool ( ) - Resume a data_vio pool .
* @ completion : The completion to notify when the pool has resumed .
*/
void resume_data_vio_pool(struct data_vio_pool *pool, struct vdo_completion *completion)
{
assert_on_vdo_cpu_thread(completion->vdo, __func__);
vdo_continue_completion(completion, vdo_resume_if_quiescent(&pool->state));
}
static void dump_limiter(const char *name, struct limiter *limiter)
{
vdo_log_info("%s: %u of %u busy (max %u), %s" , name, limiter->busy,
limiter->limit, limiter->max_busy,
((bio_list_empty(&limiter->waiters) &&
bio_list_empty(&limiter->new_waiters)) ?
"no waiters" : "has waiters" ));
}
/**
* dump_data_vio_pool ( ) - Dump a data_vio pool to the log .
* @ dump_vios : Whether to dump the details of each busy data_vio as well .
*/
void dump_data_vio_pool(struct data_vio_pool *pool, bool dump_vios)
{
/*
* In order that syslog can empty its buffer , sleep after 35 elements for 4 ms ( till the
* second clock tick ) . These numbers were picked based on experiments with lab machines .
*/
static const int ELEMENTS_PER_BATCH = 35 ;
static const int SLEEP_FOR_SYSLOG = 4000 ;
if (pool == NULL)
return ;
spin_lock(&pool->lock);
dump_limiter("data_vios" , &pool->limiter);
dump_limiter("discard permits" , &pool->discard_limiter);
if (dump_vios) {
int i;
int dumped = 0 ;
for (i = 0 ; i < pool->limiter.limit; i++) {
struct data_vio *data_vio = &pool->data_vios[i];
if (!list_empty(&data_vio->pool_entry))
continue ;
dump_data_vio(data_vio);
if (++dumped >= ELEMENTS_PER_BATCH) {
spin_unlock(&pool->lock);
dumped = 0 ;
fsleep(SLEEP_FOR_SYSLOG);
spin_lock(&pool->lock);
}
}
}
spin_unlock(&pool->lock);
}
data_vio_count_t get_data_vio_pool_active_requests(struct data_vio_pool *pool)
{
return READ_ONCE(pool->limiter.busy);
}
data_vio_count_t get_data_vio_pool_request_limit(struct data_vio_pool *pool)
{
return READ_ONCE(pool->limiter.limit);
}
data_vio_count_t get_data_vio_pool_maximum_requests(struct data_vio_pool *pool)
{
return READ_ONCE(pool->limiter.max_busy);
}
static void update_data_vio_error_stats(struct data_vio *data_vio)
{
u8 index = 0 ;
static const char * const operations[] = {
[0 ] = "empty" ,
[1 ] = "read" ,
[2 ] = "write" ,
[3 ] = "read-modify-write" ,
[5 ] = "read+fua" ,
[6 ] = "write+fua" ,
[7 ] = "read-modify-write+fua" ,
};
if (data_vio->read)
index = 1 ;
if (data_vio->write)
index += 2 ;
if (data_vio->fua)
index += 4 ;
update_vio_error_stats(&data_vio->vio,
"Completing %s vio for LBN %llu with error after %s" ,
operations[index],
(unsigned long long ) data_vio->logical.lbn,
get_data_vio_operation_name(data_vio));
}
static void perform_cleanup_stage(struct data_vio *data_vio,
enum data_vio_cleanup_stage stage);
/**
* release_allocated_lock ( ) - Release the PBN lock and / or the reference on the allocated block at
* the end of processing a data_vio .
*/
static void release_allocated_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, false );
perform_cleanup_stage(data_vio, VIO_RELEASE_RECOVERY_LOCKS);
}
/** release_lock() - Release an uncontended LBN lock. */
static void release_lock(struct data_vio *data_vio, struct lbn_lock *lock)
{
struct int_map *lock_map = lock->zone->lbn_operations;
struct data_vio *lock_holder;
if (!lock->locked) {
/* The lock is not locked, so it had better not be registered in the lock map. */
struct data_vio *lock_holder = vdo_int_map_get(lock_map, lock->lbn);
VDO_ASSERT_LOG_ONLY((data_vio != lock_holder),
"no logical block lock held for block %llu" ,
(unsigned long long ) lock->lbn);
return ;
}
/* Release the lock by removing the lock from the map. */
lock_holder = vdo_int_map_remove(lock_map, lock->lbn);
VDO_ASSERT_LOG_ONLY((data_vio == lock_holder),
"logical block lock mismatch for block %llu" ,
(unsigned long long ) lock->lbn);
lock->locked = false ;
}
/** transfer_lock() - Transfer a contended LBN lock to the eldest waiter. */
static void transfer_lock(struct data_vio *data_vio, struct lbn_lock *lock)
{
struct data_vio *lock_holder, *next_lock_holder;
int result;
VDO_ASSERT_LOG_ONLY(lock->locked, "lbn_lock with waiters is not locked" );
/* Another data_vio is waiting for the lock, transfer it in a single lock map operation. */
next_lock_holder =
vdo_waiter_as_data_vio(vdo_waitq_dequeue_waiter(&lock->waiters));
/* Transfer the remaining lock waiters to the next lock holder. */
vdo_waitq_transfer_all_waiters(&lock->waiters,
&next_lock_holder->logical.waiters);
result = vdo_int_map_put(lock->zone->lbn_operations, lock->lbn,
next_lock_holder, true , (void **) &lock_holder);
if (result != VDO_SUCCESS) {
continue_data_vio_with_error(next_lock_holder, result);
return ;
}
VDO_ASSERT_LOG_ONLY((lock_holder == data_vio),
"logical block lock mismatch for block %llu" ,
(unsigned long long ) lock->lbn);
lock->locked = false ;
/*
* If there are still waiters , other data_vios must be trying to get the lock we just
* transferred . We must ensure that the new lock holder doesn ' t block in the packer .
*/
if (vdo_waitq_has_waiters(&next_lock_holder->logical.waiters))
cancel_data_vio_compression(next_lock_holder);
/*
* Avoid stack overflow on lock transfer .
* FIXME : this is only an issue in the 1 thread config .
*/
next_lock_holder->vio.completion.requeue = true ;
launch_locked_request(next_lock_holder);
}
/**
* release_logical_lock ( ) - Release the logical block lock and flush generation lock at the end of
* processing a data_vio .
*/
static void release_logical_lock(struct vdo_completion *completion)
{
struct data_vio *data_vio = as_data_vio(completion);
struct lbn_lock *lock = &data_vio->logical;
assert_data_vio_in_logical_zone(data_vio);
if (vdo_waitq_has_waiters(&lock->waiters))
transfer_lock(data_vio, lock);
else
release_lock(data_vio, lock);
vdo_release_flush_generation_lock(data_vio);
perform_cleanup_stage(data_vio, VIO_CLEANUP_DONE);
}
/** clean_hash_lock() - Release the hash lock at the end of processing a data_vio. */
static void clean_hash_lock(struct vdo_completion *completion)
{
struct data_vio *data_vio = as_data_vio(completion);
assert_data_vio_in_hash_zone(data_vio);
if (completion->result != VDO_SUCCESS) {
vdo_clean_failed_hash_lock(data_vio);
return ;
}
vdo_release_hash_lock(data_vio);
perform_cleanup_stage(data_vio, VIO_RELEASE_LOGICAL);
}
/**
* finish_cleanup ( ) - Make some assertions about a data_vio which has finished cleaning up .
*
* If it is part of a multi - block discard , starts on the next block , otherwise , returns it to the
* pool .
*/
static void finish_cleanup(struct data_vio *data_vio)
{
struct vdo_completion *completion = &data_vio->vio.completion;
u32 discard_size = min_t(u32, data_vio->remaining_discard,
VDO_BLOCK_SIZE - data_vio->offset);
VDO_ASSERT_LOG_ONLY(data_vio->allocation.lock == NULL,
"complete data_vio has no allocation lock" );
VDO_ASSERT_LOG_ONLY(data_vio->hash_lock == NULL,
"complete data_vio has no hash lock" );
if ((data_vio->remaining_discard <= discard_size) ||
(completion->result != VDO_SUCCESS)) {
struct data_vio_pool *pool = completion->vdo->data_vio_pool;
vdo_funnel_queue_put(pool->queue, &completion->work_queue_entry_link);
schedule_releases(pool);
return ;
}
data_vio->remaining_discard -= discard_size;
data_vio->is_partial = (data_vio->remaining_discard < VDO_BLOCK_SIZE);
data_vio->read = data_vio->is_partial;
data_vio->offset = 0 ;
completion->requeue = true ;
data_vio->first_reference_operation_complete = false ;
launch_data_vio(data_vio, data_vio->logical.lbn + 1 );
}
/** perform_cleanup_stage() - Perform the next step in the process of cleaning up a data_vio. */
static void perform_cleanup_stage(struct data_vio *data_vio,
enum data_vio_cleanup_stage stage)
{
struct vdo *vdo = vdo_from_data_vio(data_vio);
switch (stage) {
case VIO_RELEASE_HASH_LOCK:
if (data_vio->hash_lock != NULL) {
launch_data_vio_hash_zone_callback(data_vio, clean_hash_lock);
return ;
}
fallthrough;
case VIO_RELEASE_ALLOCATED:
if (data_vio_has_allocation(data_vio)) {
launch_data_vio_allocated_zone_callback(data_vio,
release_allocated_lock);
return ;
}
fallthrough;
case VIO_RELEASE_RECOVERY_LOCKS:
if ((data_vio->recovery_sequence_number > 0 ) &&
(READ_ONCE(vdo->read_only_notifier.read_only_error) == VDO_SUCCESS) &&
(data_vio->vio.completion.result != VDO_READ_ONLY))
vdo_log_warning("VDO not read-only when cleaning data_vio with RJ lock" );
fallthrough;
case VIO_RELEASE_LOGICAL:
launch_data_vio_logical_callback(data_vio, release_logical_lock);
return ;
default :
finish_cleanup(data_vio);
}
}
void complete_data_vio(struct vdo_completion *completion)
{
struct data_vio *data_vio = as_data_vio(completion);
completion->error_handler = NULL;
data_vio->last_async_operation = VIO_ASYNC_OP_CLEANUP;
perform_cleanup_stage(data_vio,
(data_vio->write ? VIO_CLEANUP_START : VIO_RELEASE_LOGICAL));
}
static void enter_read_only_mode(struct vdo_completion *completion)
{
if (vdo_is_read_only(completion->vdo))
return ;
if (completion->result != VDO_READ_ONLY) {
struct data_vio *data_vio = as_data_vio(completion);
vdo_log_error_strerror(completion->result,
"Preparing to enter read-only mode: data_vio for LBN %llu (becoming mapped to %llu, previously mapped to %llu, allocated %llu) is completing with a fatal error after operation %s" ,
(unsigned long long ) data_vio->logical.lbn,
(unsigned long long ) data_vio->new_mapped.pbn,
(unsigned long long ) data_vio->mapped.pbn,
(unsigned long long ) data_vio->allocation.pbn,
get_data_vio_operation_name(data_vio));
}
vdo_enter_read_only_mode(completion->vdo, completion->result);
}
void handle_data_vio_error(struct vdo_completion *completion)
{
struct data_vio *data_vio = as_data_vio(completion);
if ((completion->result == VDO_READ_ONLY) || (data_vio->user_bio == NULL))
enter_read_only_mode(completion);
update_data_vio_error_stats(data_vio);
complete_data_vio(completion);
}
/**
* get_data_vio_operation_name ( ) - Get the name of the last asynchronous operation performed on a
* data_vio .
*/
const char *get_data_vio_operation_name(struct data_vio *data_vio)
{
BUILD_BUG_ON((MAX_VIO_ASYNC_OPERATION_NUMBER - MIN_VIO_ASYNC_OPERATION_NUMBER) !=
ARRAY_SIZE(ASYNC_OPERATION_NAMES));
return ((data_vio->last_async_operation < MAX_VIO_ASYNC_OPERATION_NUMBER) ?
ASYNC_OPERATION_NAMES[data_vio->last_async_operation] :
"unknown async operation" );
}
/**
* data_vio_allocate_data_block ( ) - Allocate a data block .
*
* @ write_lock_type : The type of write lock to obtain on the block .
* @ callback : The callback which will attempt an allocation in the current zone and continue if it
* succeeds .
* @ error_handler : The handler for errors while allocating .
*/
void data_vio_allocate_data_block(struct data_vio *data_vio,
enum pbn_lock_type write_lock_type,
vdo_action_fn callback, vdo_action_fn error_handler)
{
struct allocation *allocation = &data_vio->allocation;
VDO_ASSERT_LOG_ONLY((allocation->pbn == VDO_ZERO_BLOCK),
"data_vio does not have an allocation" );
allocation->write_lock_type = write_lock_type;
allocation->zone = vdo_get_next_allocation_zone(data_vio->logical.zone);
allocation->first_allocation_zone = allocation->zone->zone_number;
data_vio->vio.completion.error_handler = error_handler;
launch_data_vio_allocated_zone_callback(data_vio, callback);
}
/**
* release_data_vio_allocation_lock ( ) - Release the PBN lock on a data_vio ' s allocated block .
* @ reset : If true , the allocation will be reset ( i . e . any allocated pbn will be forgotten ) .
*
* If the reference to the locked block is still provisional , it will be released as well .
*/
void release_data_vio_allocation_lock(struct data_vio *data_vio, bool reset)
{
struct allocation *allocation = &data_vio->allocation;
physical_block_number_t locked_pbn = allocation->pbn;
assert_data_vio_in_allocated_zone(data_vio);
if (reset || vdo_pbn_lock_has_provisional_reference(allocation->lock))
allocation->pbn = VDO_ZERO_BLOCK;
vdo_release_physical_zone_pbn_lock(allocation->zone, locked_pbn,
vdo_forget(allocation->lock));
}
/**
* uncompress_data_vio ( ) - Uncompress the data a data_vio has just read .
* @ mapping_state : The mapping state indicating which fragment to decompress .
* @ buffer : The buffer to receive the uncompressed data .
*/
int uncompress_data_vio(struct data_vio *data_vio,
enum block_mapping_state mapping_state, char *buffer)
{
int size;
u16 fragment_offset, fragment_size;
struct compressed_block *block = data_vio->compression.block;
int result = vdo_get_compressed_block_fragment(mapping_state, block,
&fragment_offset, &fragment_size);
if (result != VDO_SUCCESS) {
vdo_log_debug("%s: compressed fragment error %d" , __func__, result);
return result;
}
size = LZ4_decompress_safe((block->data + fragment_offset), buffer,
fragment_size, VDO_BLOCK_SIZE);
if (size != VDO_BLOCK_SIZE) {
vdo_log_debug("%s: lz4 error" , __func__);
return VDO_INVALID_FRAGMENT;
}
return VDO_SUCCESS;
}
/**
* modify_for_partial_write ( ) - Do the modify - write part of a read - modify - write cycle .
* @ completion : The data_vio which has just finished its read .
*
* This callback is registered in read_block ( ) .
*/
static void modify_for_partial_write(struct vdo_completion *completion)
{
struct data_vio *data_vio = as_data_vio(completion);
char *data = data_vio->vio.data;
struct bio *bio = data_vio->user_bio;
assert_data_vio_on_cpu_thread(data_vio);
if (bio_op(bio) == REQ_OP_DISCARD) {
memset(data + data_vio->offset, '\0' , min_t(u32,
data_vio->remaining_discard,
VDO_BLOCK_SIZE - data_vio->offset));
} else {
copy_from_bio(bio, data + data_vio->offset);
}
data_vio->is_zero = is_zero_block(data);
data_vio->read = false ;
launch_data_vio_logical_callback(data_vio,
continue_data_vio_with_block_map_slot);
}
static void complete_read(struct vdo_completion *completion)
{
struct data_vio *data_vio = as_data_vio(completion);
char *data = data_vio->vio.data;
bool compressed = vdo_is_state_compressed(data_vio->mapped.state);
assert_data_vio_on_cpu_thread(data_vio);
if (compressed) {
int result = uncompress_data_vio(data_vio, data_vio->mapped.state, data);
if (result != VDO_SUCCESS) {
continue_data_vio_with_error(data_vio, result);
return ;
}
}
if (data_vio->write) {
modify_for_partial_write(completion);
return ;
}
if (compressed || data_vio->is_partial)
copy_to_bio(data_vio->user_bio, data + data_vio->offset);
acknowledge_data_vio(data_vio);
complete_data_vio(completion);
}
static void read_endio(struct bio *bio)
{
struct data_vio *data_vio = vio_as_data_vio(bio->bi_private);
int result = blk_status_to_errno(bio->bi_status);
vdo_count_completed_bios(bio);
if (result != VDO_SUCCESS) {
continue_data_vio_with_error(data_vio, result);
return ;
}
launch_data_vio_cpu_callback(data_vio, complete_read,
CPU_Q_COMPLETE_READ_PRIORITY);
}
static void complete_zero_read(struct vdo_completion *completion)
{
struct data_vio *data_vio = as_data_vio(completion);
assert_data_vio_on_cpu_thread(data_vio);
if (data_vio->is_partial) {
memset(data_vio->vio.data, 0 , VDO_BLOCK_SIZE);
if (data_vio->write) {
modify_for_partial_write(completion);
return ;
}
} else {
zero_fill_bio(data_vio->user_bio);
}
complete_read(completion);
}
/**
* read_block ( ) - Read a block asynchronously .
*
* This is the callback registered in read_block_mapping ( ) .
*/
static void read_block(struct vdo_completion *completion)
{
struct data_vio *data_vio = as_data_vio(completion);
struct vio *vio = as_vio(completion);
int result = VDO_SUCCESS;
if (data_vio->mapped.pbn == VDO_ZERO_BLOCK) {
launch_data_vio_cpu_callback(data_vio, complete_zero_read,
CPU_Q_COMPLETE_VIO_PRIORITY);
return ;
}
data_vio->last_async_operation = VIO_ASYNC_OP_READ_DATA_VIO;
if (vdo_is_state_compressed(data_vio->mapped.state)) {
result = vio_reset_bio(vio, (char *) data_vio->compression.block,
read_endio, REQ_OP_READ, data_vio->mapped.pbn);
} else {
blk_opf_t opf = ((data_vio->user_bio->bi_opf & PASSTHROUGH_FLAGS) | REQ_OP_READ);
if (data_vio->is_partial) {
result = vio_reset_bio(vio, vio->data, read_endio, opf,
data_vio->mapped.pbn);
} else {
/* A full 4k read. Use the incoming bio to avoid having to copy the data */
bio_reset(vio->bio, vio->bio->bi_bdev, opf);
bio_init_clone(data_vio->user_bio->bi_bdev, vio->bio,
data_vio->user_bio, GFP_KERNEL);
/* Copy over the original bio iovec and opflags. */
vdo_set_bio_properties(vio->bio, vio, read_endio, opf,
data_vio->mapped.pbn);
}
}
if (result != VDO_SUCCESS) {
continue_data_vio_with_error(data_vio, result);
return ;
}
vdo_submit_data_vio(data_vio);
}
static inline struct data_vio *
reference_count_update_completion_as_data_vio(struct vdo_completion *completion)
{
if (completion->type == VIO_COMPLETION)
return as_data_vio(completion);
return container_of(completion, struct data_vio, decrement_completion);
}
/**
* update_block_map ( ) - Rendezvous of the data_vio and decrement completions after each has
* made its reference updates . Handle any error from either , or proceed
* to updating the block map .
* @ completion : The completion of the write in progress .
*/
static void update_block_map(struct vdo_completion *completion)
{
struct data_vio *data_vio = reference_count_update_completion_as_data_vio(completion);
assert_data_vio_in_logical_zone(data_vio);
if (!data_vio->first_reference_operation_complete) {
/* Rendezvous, we're first */
data_vio->first_reference_operation_complete = true ;
return ;
}
completion = &data_vio->vio.completion;
vdo_set_completion_result(completion, data_vio->decrement_completion.result);
if (completion->result != VDO_SUCCESS) {
handle_data_vio_error(completion);
return ;
}
completion->error_handler = handle_data_vio_error;
if (data_vio->hash_lock != NULL)
set_data_vio_hash_zone_callback(data_vio, vdo_continue_hash_lock);
else
completion->callback = complete_data_vio;
data_vio->last_async_operation = VIO_ASYNC_OP_PUT_MAPPED_BLOCK;
vdo_put_mapped_block(data_vio);
}
static void decrement_reference_count(struct vdo_completion *completion)
{
struct data_vio *data_vio = container_of(completion, struct data_vio,
decrement_completion);
assert_data_vio_in_mapped_zone(data_vio);
vdo_set_completion_callback(completion, update_block_map,
data_vio->logical.zone->thread_id);
completion->error_handler = update_block_map;
vdo_modify_reference_count(completion, &data_vio->decrement_updater);
}
static void increment_reference_count(struct vdo_completion *completion)
{
struct data_vio *data_vio = as_data_vio(completion);
assert_data_vio_in_new_mapped_zone(data_vio);
if (data_vio->downgrade_allocation_lock) {
/*
* Now that the data has been written , it ' s safe to deduplicate against the
* block . Downgrade the allocation lock to a read lock so it can be used later by
* the hash lock . This is done here since it needs to happen sometime before we
* return to the hash zone , and we are currently on the correct thread . For
* compressed blocks , the downgrade will have already been done .
*/
vdo_downgrade_pbn_write_lock(data_vio->allocation.lock, false );
}
set_data_vio_logical_callback(data_vio, update_block_map);
completion->error_handler = update_block_map;
vdo_modify_reference_count(completion, &data_vio->increment_updater);
}
/** journal_remapping() - Add a recovery journal entry for a data remapping. */
static void journal_remapping(struct vdo_completion *completion)
{
struct data_vio *data_vio = as_data_vio(completion);
assert_data_vio_in_journal_zone(data_vio);
data_vio->decrement_updater.operation = VDO_JOURNAL_DATA_REMAPPING;
data_vio->decrement_updater.zpbn = data_vio->mapped;
if (data_vio->new_mapped.pbn == VDO_ZERO_BLOCK) {
data_vio->first_reference_operation_complete = true ;
if (data_vio->mapped.pbn == VDO_ZERO_BLOCK)
set_data_vio_logical_callback(data_vio, update_block_map);
} else {
set_data_vio_new_mapped_zone_callback(data_vio,
increment_reference_count);
}
if (data_vio->mapped.pbn == VDO_ZERO_BLOCK) {
data_vio->first_reference_operation_complete = true ;
} else {
vdo_set_completion_callback(&data_vio->decrement_completion,
decrement_reference_count,
data_vio->mapped.zone->thread_id);
}
data_vio->last_async_operation = VIO_ASYNC_OP_JOURNAL_REMAPPING;
vdo_add_recovery_journal_entry(completion->vdo->recovery_journal, data_vio);
}
/**
* read_old_block_mapping ( ) - Get the previous PBN / LBN mapping of an in - progress write .
*
* Gets the previous PBN mapped to this LBN from the block map , so as to make an appropriate
* journal entry referencing the removal of this LBN - > PBN mapping .
*/
static void read_old_block_mapping(struct vdo_completion *completion)
{
struct data_vio *data_vio = as_data_vio(completion);
assert_data_vio_in_logical_zone(data_vio);
data_vio->last_async_operation = VIO_ASYNC_OP_GET_MAPPED_BLOCK_FOR_WRITE;
set_data_vio_journal_callback(data_vio, journal_remapping);
vdo_get_mapped_block(data_vio);
}
void update_metadata_for_data_vio_write(struct data_vio *data_vio, struct pbn_lock *lock)
{
data_vio->increment_updater = (struct reference_updater) {
.operation = VDO_JOURNAL_DATA_REMAPPING,
.increment = true ,
.zpbn = data_vio->new_mapped,
.lock = lock,
};
launch_data_vio_logical_callback(data_vio, read_old_block_mapping);
}
/**
* pack_compressed_data ( ) - Attempt to pack the compressed data_vio into a block .
*
* This is the callback registered in launch_compress_data_vio ( ) .
*/
static void pack_compressed_data(struct vdo_completion *completion)
{
struct data_vio *data_vio = as_data_vio(completion);
assert_data_vio_in_packer_zone(data_vio);
if (!vdo_get_compressing(vdo_from_data_vio(data_vio)) ||
get_data_vio_compression_status(data_vio).may_not_compress) {
write_data_vio(data_vio);
return ;
}
data_vio->last_async_operation = VIO_ASYNC_OP_ATTEMPT_PACKING;
vdo_attempt_packing(data_vio);
}
/**
* compress_data_vio ( ) - Do the actual work of compressing the data on a CPU queue .
*
* This callback is registered in launch_compress_data_vio ( ) .
*/
static void compress_data_vio(struct vdo_completion *completion)
{
struct data_vio *data_vio = as_data_vio(completion);
int size;
assert_data_vio_on_cpu_thread(data_vio);
/*
* By putting the compressed data at the start of the compressed block data field , we won ' t
* need to copy it if this data_vio becomes a compressed write agent .
*/
size = LZ4_compress_default(data_vio->vio.data,
data_vio->compression.block->data, VDO_BLOCK_SIZE,
VDO_MAX_COMPRESSED_FRAGMENT_SIZE,
(char *) vdo_get_work_queue_private_data());
if ((size > 0 ) && (size < VDO_COMPRESSED_BLOCK_DATA_SIZE)) {
data_vio->compression.size = size;
launch_data_vio_packer_callback(data_vio, pack_compressed_data);
return ;
}
write_data_vio(data_vio);
}
/**
* launch_compress_data_vio ( ) - Continue a write by attempting to compress the data .
*
* This is a re - entry point to vio_write used by hash locks .
*/
void launch_compress_data_vio(struct data_vio *data_vio)
{
VDO_ASSERT_LOG_ONLY(!data_vio->is_duplicate, "compressing a non-duplicate block" );
VDO_ASSERT_LOG_ONLY(data_vio->hash_lock != NULL,
"data_vio to compress has a hash_lock" );
VDO_ASSERT_LOG_ONLY(data_vio_has_allocation(data_vio),
"data_vio to compress has an allocation" );
/*
* There are 4 reasons why a data_vio which has reached this point will not be eligible for
* compression :
*
* 1 ) Since data_vios can block indefinitely in the packer , it would be bad to do so if the
* write request also requests FUA .
*
* 2 ) A data_vio should not be compressed when compression is disabled for the vdo .
*
* 3 ) A data_vio could be doing a partial write on behalf of a larger discard which has not
* yet been acknowledged and hence blocking in the packer would be bad .
*
* 4 ) Some other data_vio may be waiting on this data_vio in which case blocking in the
* packer would also be bad .
*/
if (data_vio->fua ||
!vdo_get_compressing(vdo_from_data_vio(data_vio)) ||
((data_vio->user_bio != NULL) && (bio_op(data_vio->user_bio) == REQ_OP_DISCARD)) ||
(advance_data_vio_compression_stage(data_vio).stage != DATA_VIO_COMPRESSING)) {
write_data_vio(data_vio);
return ;
}
data_vio->last_async_operation = VIO_ASYNC_OP_COMPRESS_DATA_VIO;
launch_data_vio_cpu_callback(data_vio, compress_data_vio,
CPU_Q_COMPRESS_BLOCK_PRIORITY);
}
/**
* hash_data_vio ( ) - Hash the data in a data_vio and set the hash zone ( which also flags the record
* name as set ) .
* This callback is registered in prepare_for_dedupe ( ) .
*/
static void hash_data_vio(struct vdo_completion *completion)
{
struct data_vio *data_vio = as_data_vio(completion);
assert_data_vio_on_cpu_thread(data_vio);
VDO_ASSERT_LOG_ONLY(!data_vio->is_zero, "zero blocks should not be hashed" );
murmurhash3_128(data_vio->vio.data, VDO_BLOCK_SIZE, 0 x62ea60be,
&data_vio->record_name);
data_vio->hash_zone = vdo_select_hash_zone(vdo_from_data_vio(data_vio)->hash_zones,
&data_vio->record_name);
data_vio->last_async_operation = VIO_ASYNC_OP_ACQUIRE_VDO_HASH_LOCK;
launch_data_vio_hash_zone_callback(data_vio, vdo_acquire_hash_lock);
}
/** prepare_for_dedupe() - Prepare for the dedupe path after attempting to get an allocation. */
static void prepare_for_dedupe(struct data_vio *data_vio)
{
/* We don't care what thread we are on. */
VDO_ASSERT_LOG_ONLY(!data_vio->is_zero, "must not prepare to dedupe zero blocks" );
/*
* Before we can dedupe , we need to know the record name , so the first
* step is to hash the block data .
*/
data_vio->last_async_operation = VIO_ASYNC_OP_HASH_DATA_VIO;
launch_data_vio_cpu_callback(data_vio, hash_data_vio, CPU_Q_HASH_BLOCK_PRIORITY);
}
/**
* write_bio_finished ( ) - This is the bio_end_io function registered in write_block ( ) to be called
* when a data_vio ' s write to the underlying storage has completed .
*/
static void write_bio_finished(struct bio *bio)
{
struct data_vio *data_vio = vio_as_data_vio((struct vio *) bio->bi_private);
vdo_count_completed_bios(bio);
vdo_set_completion_result(&data_vio->vio.completion,
blk_status_to_errno(bio->bi_status));
data_vio->downgrade_allocation_lock = true ;
update_metadata_for_data_vio_write(data_vio, data_vio->allocation.lock);
}
/** write_data_vio() - Write a data block to storage without compression. */
void write_data_vio(struct data_vio *data_vio)
{
struct data_vio_compression_status status, new_status;
int result;
if (!data_vio_has_allocation(data_vio)) {
/*
* There was no space to write this block and we failed to deduplicate or compress
* it .
*/
continue_data_vio_with_error(data_vio, VDO_NO_SPACE);
return ;
}
new_status = (struct data_vio_compression_status) {
.stage = DATA_VIO_POST_PACKER,
.may_not_compress = true ,
};
do {
status = get_data_vio_compression_status(data_vio);
} while ((status.stage != DATA_VIO_POST_PACKER) &&
!set_data_vio_compression_status(data_vio, status, new_status));
/* Write the data from the data block buffer. */
result = vio_reset_bio(&data_vio->vio, data_vio->vio.data,
write_bio_finished, REQ_OP_WRITE,
data_vio->allocation.pbn);
if (result != VDO_SUCCESS) {
continue_data_vio_with_error(data_vio, result);
return ;
}
data_vio->last_async_operation = VIO_ASYNC_OP_WRITE_DATA_VIO;
vdo_submit_data_vio(data_vio);
}
/**
* acknowledge_write_callback ( ) - Acknowledge a write to the requestor .
*
* This callback is registered in allocate_block ( ) and continue_write_with_block_map_slot ( ) .
*/
static void acknowledge_write_callback(struct vdo_completion *completion)
{
struct data_vio *data_vio = as_data_vio(completion);
struct vdo *vdo = completion->vdo;
VDO_ASSERT_LOG_ONLY((!vdo_uses_bio_ack_queue(vdo) ||
(vdo_get_callback_thread_id() == vdo->thread_config.bio_ack_thread)),
"%s() called on bio ack queue" , __func__);
VDO_ASSERT_LOG_ONLY(data_vio_has_flush_generation_lock(data_vio),
"write VIO to be acknowledged has a flush generation lock" );
acknowledge_data_vio(data_vio);
if (data_vio->new_mapped.pbn == VDO_ZERO_BLOCK) {
/* This is a zero write or discard */
update_metadata_for_data_vio_write(data_vio, NULL);
return ;
}
prepare_for_dedupe(data_vio);
}
/**
* allocate_block ( ) - Attempt to allocate a block in the current allocation zone .
*
* This callback is registered in continue_write_with_block_map_slot ( ) .
*/
static void allocate_block(struct vdo_completion *completion)
{
struct data_vio *data_vio = as_data_vio(completion);
assert_data_vio_in_allocated_zone(data_vio);
if (!vdo_allocate_block_in_zone(data_vio))
return ;
completion->error_handler = handle_data_vio_error;
WRITE_ONCE(data_vio->allocation_succeeded, true );
data_vio->new_mapped = (struct zoned_pbn) {
.zone = data_vio->allocation.zone,
.pbn = data_vio->allocation.pbn,
.state = VDO_MAPPING_STATE_UNCOMPRESSED,
};
if (data_vio->fua ||
data_vio->remaining_discard > (u32) (VDO_BLOCK_SIZE - data_vio->offset)) {
prepare_for_dedupe(data_vio);
return ;
}
data_vio->last_async_operation = VIO_ASYNC_OP_ACKNOWLEDGE_WRITE;
launch_data_vio_on_bio_ack_queue(data_vio, acknowledge_write_callback);
}
/**
* handle_allocation_error ( ) - Handle an error attempting to allocate a block .
*
* This error handler is registered in continue_write_with_block_map_slot ( ) .
*/
static void handle_allocation_error(struct vdo_completion *completion)
{
struct data_vio *data_vio = as_data_vio(completion);
if (completion->result == VDO_NO_SPACE) {
/* We failed to get an allocation, but we can try to dedupe. */
vdo_reset_completion(completion);
completion->error_handler = handle_data_vio_error;
prepare_for_dedupe(data_vio);
return ;
}
/* We got a "real" error, not just a failure to allocate, so fail the request. */
handle_data_vio_error(completion);
}
static int assert_is_discard(struct data_vio *data_vio)
{
int result = VDO_ASSERT(data_vio->is_discard,
"data_vio with no block map page is a discard" );
return ((result == VDO_SUCCESS) ? result : VDO_READ_ONLY);
}
/**
* continue_data_vio_with_block_map_slot ( ) - Read the data_vio ' s mapping from the block map .
*
* This callback is registered in launch_read_data_vio ( ) .
*/
void continue_data_vio_with_block_map_slot(struct vdo_completion *completion)
{
struct data_vio *data_vio = as_data_vio(completion);
assert_data_vio_in_logical_zone(data_vio);
if (data_vio->read) {
set_data_vio_logical_callback(data_vio, read_block);
data_vio->last_async_operation = VIO_ASYNC_OP_GET_MAPPED_BLOCK_FOR_READ;
vdo_get_mapped_block(data_vio);
return ;
}
vdo_acquire_flush_generation_lock(data_vio);
if (data_vio->tree_lock.tree_slots[0 ].block_map_slot.pbn == VDO_ZERO_BLOCK) {
/*
* This is a discard for a block on a block map page which has not been allocated , so
* there ' s nothing more we need to do .
*/
completion->callback = complete_data_vio;
continue_data_vio_with_error(data_vio, assert_is_discard(data_vio));
return ;
}
/*
* We need an allocation if this is neither a full - block discard nor a
* full - block zero write .
*/
if (!data_vio->is_zero && (!data_vio->is_discard || data_vio->is_partial)) {
data_vio_allocate_data_block(data_vio, VIO_WRITE_LOCK, allocate_block,
handle_allocation_error);
return ;
}
/*
* We don ' t need to write any data , so skip allocation and just update the block map and
* reference counts ( via the journal ) .
*/
data_vio->new_mapped.pbn = VDO_ZERO_BLOCK;
if (data_vio->is_zero)
data_vio->new_mapped.state = VDO_MAPPING_STATE_UNCOMPRESSED;
if (data_vio->remaining_discard > (u32) (VDO_BLOCK_SIZE - data_vio->offset)) {
/* This is not the final block of a discard so we can't acknowledge it yet. */
update_metadata_for_data_vio_write(data_vio, NULL);
return ;
}
data_vio->last_async_operation = VIO_ASYNC_OP_ACKNOWLEDGE_WRITE;
launch_data_vio_on_bio_ack_queue(data_vio, acknowledge_write_callback);
}
Messung V0.5 in Prozent C=97 H=93 G=94
¤ Dauer der Verarbeitung: 0.41 Sekunden
(vorverarbeitet am 2026-09-29)
¤
*© Formatika GbR, Deutschland
2026-10-10
Neuigkeiten
Aktuelles
Motto des Tages
Open Source Software
Jenseits des Üblichen ....
Besucherstatistik
Statistik
#Sources=1126864
#Domains=1897691