// SPDX-License-Identifier: GPL-2.0-only/* * Copyright (C) 2011-2012 Red Hat UK. * * This file is released under the GPL. */ #include "dm-thin-metadata.h"#include "dm-bio-prison-v1.h"#include "dm.h" #include <linux/device-mapper.h>#include <linux/dm-io.h>#include <linux/dm-kcopyd.h>#include <linux/jiffies.h>#include <linux/log2.h>#include <linux/list.h>#include <linux/rculist.h>#include <linux/init.h>#include <linux/module.h>#include <linux/slab.h>#include <linux/vmalloc.h>#include <linux/sort.h>#include <linux/rbtree.h> #define DM_MSG_PREFIX "thin" /* * Tunable constants */#define ENDIO_HOOK_POOL_SIZE 1024#define MAPPING_POOL_SIZE 1024#define COMMIT_PERIOD HZ#define NO_SPACE_TIMEOUT_SECS 60 static unsigned int no_space_timeout_secs = NO_SPACE_TIMEOUT_SECS; DECLARE_DM_KCOPYD_THROTTLE_WITH_MODULE_PARM(snapshot_copy_throttle, "A percentage of time allocated for copy on write"); /* * The block size of the device holding pool data must be * between 64KB and 1GB. */#define DATA_DEV_BLOCK_SIZE_MIN_SECTORS (64 * 1024 >> SECTOR_SHIFT)#define DATA_DEV_BLOCK_SIZE_MAX_SECTORS (1024 * 1024 * 1024 >> SECTOR_SHIFT) /* * Device id is restricted to 24 bits. */#define MAX_DEV_ID ((1 << 24) - 1) /* * How do we handle breaking sharing of data blocks? * ================================================= * * We use a standard copy-on-write btree to store the mappings for the * devices (note I'm talking about copy-on-write of the metadata here, not * the data). When you take an internal snapshot you clone the root node * of the origin btree. After this there is no concept of an origin or a * snapshot. They are just two device trees that happen to point to the * same data blocks. * * When we get a write in we decide if it's to a shared data block using * some timestamp magic. If it is, we have to break sharing. * * Let's say we write to a shared block in what was the origin. The * steps are: * * i) plug io further to this physical block. (see bio_prison code). * * ii) quiesce any read io to that shared data block. Obviously * including all devices that share this block. (see dm_deferred_set code) * * iii) copy the data block to a newly allocate block. This step can be * missed out if the io covers the block. (schedule_copy). * * iv) insert the new mapping into the origin's btree * (process_prepared_mapping). This act of inserting breaks some * sharing of btree nodes between the two devices. Breaking sharing only * effects the btree of that specific device. Btrees for the other * devices that share the block never change. The btree for the origin * device as it was after the last commit is untouched, ie. we're using * persistent data structures in the functional programming sense. * * v) unplug io to this physical block, including the io that triggered * the breaking of sharing. * * Steps (ii) and (iii) occur in parallel. * * The metadata _doesn't_ need to be committed before the io continues. We * get away with this because the io is always written to a _new_ block. * If there's a crash, then: * * - The origin mapping will point to the old origin block (the shared * one). This will contain the data as it was before the io that triggered * the breaking of sharing came in. * * - The snap mapping still points to the old block. As it would after * the commit. * * The downside of this scheme is the timestamp magic isn't perfect, and * will continue to think that data block in the snapshot device is shared * even after the write to the origin has broken sharing. I suspect data * blocks will typically be shared by many different devices, so we're * breaking sharing n + 1 times, rather than n, where n is the number of * devices that reference this data block. At the moment I think the * benefits far, far outweigh the disadvantages. */ /*----------------------------------------------------------------*/ /* * Key building. */enum lock_space { VIRTUAL, PHYSICAL}; static bool build_key(struct dm_thin_device *td, enum lock_space ls, dm_block_t b, dm_block_t e, struct dm_cell_key *key){ key->virtual = (ls == VIRTUAL); key->dev = dm_thin_dev_id(td); key->block_begin = b; key->block_end = e; return dm_cell_key_has_valid_range(key);} static void build_data_key(struct dm_thin_device *td, dm_block_t b, struct dm_cell_key *key){ (void) build_key(td, PHYSICAL, b, b + 1llu, key);} static void build_virtual_key(struct dm_thin_device *td, dm_block_t b, struct dm_cell_key *key){ (void) build_key(td, VIRTUAL, b, b + 1llu, key);} /*----------------------------------------------------------------*/ #define THROTTLE_THRESHOLD (1 * HZ) struct throttle { struct rw_semaphore lock; unsigned long threshold; bool throttle_applied;}; static void throttle_init(struct throttle *t){ init_rwsem(&t->lock); t->throttle_applied = false;} static void throttle_work_start(struct throttle *t){ t->threshold = jiffies + THROTTLE_THRESHOLD;} static void throttle_work_update(struct throttle *t){ if (!t->throttle_applied && time_is_before_jiffies(t->threshold)) { down_write(&t->lock); t->throttle_applied = true; }} static void throttle_work_complete(struct throttle *t){ if (t->throttle_applied) { t->throttle_applied = false; up_write(&t->lock); }} static void throttle_lock(struct throttle *t){ down_read(&t->lock);} static void throttle_unlock(struct throttle *t){ up_read(&t->lock);} /*----------------------------------------------------------------*/ /* * A pool device ties together a metadata device and a data device. It * also provides the interface for creating and destroying internal * devices. */struct dm_thin_new_mapping; /* * The pool runs in various modes. Ordered in degraded order for comparisons. */enum pool_mode { PM_WRITE, /* metadata may be changed */ PM_OUT_OF_DATA_SPACE, /* metadata may be changed, though data may not be allocated */ /* * Like READ_ONLY, except may switch back to WRITE on metadata resize. Reported as READ_ONLY. */ PM_OUT_OF_METADATA_SPACE, PM_READ_ONLY, /* metadata may not be changed */ PM_FAIL, /* all I/O fails */}; struct pool_features { enum pool_mode mode; bool zero_new_blocks:1; bool discard_enabled:1; bool discard_passdown:1; bool error_if_no_space:1;}; struct thin_c;typedef void (*process_bio_fn)(struct thin_c *tc, struct bio *bio);typedef void (*process_cell_fn)(struct thin_c *tc, struct dm_bio_prison_cell *cell);typedef void (*process_mapping_fn)(struct dm_thin_new_mapping *m); #define CELL_SORT_ARRAY_SIZE 8192 struct pool { struct list_head list; struct dm_target *ti; /* Only set if a pool target is bound */ struct mapped_device *pool_md; struct block_device *data_dev; struct block_device *md_dev; struct dm_pool_metadata *pmd; dm_block_t low_water_blocks; uint32_t sectors_per_block; int sectors_per_block_shift; struct pool_features pf; bool low_water_triggered:1; /* A dm event has been sent */ bool suspended:1; bool out_of_data_space:1; struct dm_bio_prison *prison; struct dm_kcopyd_client *copier; struct work_struct worker; struct workqueue_struct *wq; struct throttle throttle; struct delayed_work waker; struct delayed_work no_space_timeout; unsigned long last_commit_jiffies; unsigned int ref_count; spinlock_t lock; struct bio_list deferred_flush_bios; struct bio_list deferred_flush_completions; struct list_head prepared_mappings; struct list_head prepared_discards; struct list_head prepared_discards_pt2; struct list_head active_thins; struct dm_deferred_set *shared_read_ds; struct dm_deferred_set *all_io_ds; struct dm_thin_new_mapping *next_mapping; process_bio_fn process_bio; process_bio_fn process_discard; process_cell_fn process_cell; process_cell_fn process_discard_cell; process_mapping_fn process_prepared_mapping; process_mapping_fn process_prepared_discard; process_mapping_fn process_prepared_discard_pt2; struct dm_bio_prison_cell **cell_sort_array; mempool_t mapping_pool;}; static void metadata_operation_failed(struct pool *pool, const char *op, int r); static enum pool_mode get_pool_mode(struct pool *pool){ return pool->pf.mode;} static void notify_of_pool_mode_change(struct pool *pool){ static const char *descs[] = { "write", "out-of-data-space", "read-only", "read-only", "fail" }; const char *extra_desc = NULL; enum pool_mode mode = get_pool_mode(pool); if (mode == PM_OUT_OF_DATA_SPACE) { if (!pool->pf.error_if_no_space) extra_desc = " (queue IO)"; else extra_desc = " (error IO)"; } dm_table_event(pool->ti->table); DMINFO("%s: switching pool to %s%s mode", dm_device_name(pool->pool_md), descs[(int)mode], extra_desc ? : "");} /* * Target context for a pool. */struct pool_c { struct dm_target *ti; struct pool *pool; struct dm_dev *data_dev; struct dm_dev *metadata_dev; dm_block_t low_water_blocks; struct pool_features requested_pf; /* Features requested during table load */ struct pool_features adjusted_pf; /* Features used after adjusting for constituent devices */}; /* * Target context for a thin. */struct thin_c { struct list_head list; struct dm_dev *pool_dev; struct dm_dev *origin_dev; sector_t origin_size; dm_thin_id dev_id; struct pool *pool; struct dm_thin_device *td; struct mapped_device *thin_md; bool requeue_mode:1; spinlock_t lock; struct list_head deferred_cells; struct bio_list deferred_bio_list; struct bio_list retry_on_resume_list; struct rb_root sort_bio_list; /* sorted list of deferred bios */ /* * Ensures the thin is not destroyed until the worker has finished * iterating the active_thins list. */ refcount_t refcount; struct completion can_destroy;}; /*----------------------------------------------------------------*/ static bool block_size_is_power_of_two(struct pool *pool){ return pool->sectors_per_block_shift >= 0;} static sector_t block_to_sectors(struct pool *pool, dm_block_t b){ return block_size_is_power_of_two(pool) ? (b << pool->sectors_per_block_shift) : (b * pool->sectors_per_block);} /*----------------------------------------------------------------*/ struct discard_op { struct thin_c *tc; struct blk_plug plug; struct bio *parent_bio; struct bio *bio;}; static void begin_discard(struct discard_op *op, struct thin_c *tc, struct bio *parent){ BUG_ON(!parent); op->tc = tc; blk_start_plug(&op->plug); op->parent_bio = parent; op->bio = NULL;} static void issue_discard(struct discard_op *op, dm_block_t data_b, dm_block_t data_e){ struct thin_c *tc = op->tc; sector_t s = block_to_sectors(tc->pool, data_b); sector_t len = block_to_sectors(tc->pool, data_e - data_b); __blkdev_issue_discard(tc->pool_dev->bdev, s, len, GFP_NOIO, &op->bio);} static void end_discard(struct discard_op *op, int r){ if (op->bio) { /* * Even if one of the calls to issue_discard failed, we * need to wait for the chain to complete. */ bio_chain(op->bio, op->parent_bio); op->bio->bi_opf = REQ_OP_DISCARD; submit_bio(op->bio); } blk_finish_plug(&op->plug); /* * Even if r is set, there could be sub discards in flight that we * need to wait for. */ if (r && !op->parent_bio->bi_status) op->parent_bio->bi_status = errno_to_blk_status(r); bio_endio(op->parent_bio);} /*----------------------------------------------------------------*/ /* * wake_worker() is used when new work is queued and when pool_resume is * ready to continue deferred IO processing. */static void wake_worker(struct pool *pool){ queue_work(pool->wq, &pool->worker);} /*----------------------------------------------------------------*/ static int bio_detain(struct pool *pool, struct dm_cell_key *key, struct bio *bio, struct dm_bio_prison_cell **cell_result){ int r; struct dm_bio_prison_cell *cell_prealloc; /* * Allocate a cell from the prison's mempool. * This might block but it can't fail. */ cell_prealloc = dm_bio_prison_alloc_cell(pool->prison, GFP_NOIO); r = dm_bio_detain(pool->prison, key, bio, cell_prealloc, cell_result); if (r) { /* * We reused an old cell; we can get rid of * the new one. */ dm_bio_prison_free_cell(pool->prison, cell_prealloc); } return r;} static void cell_release(struct pool *pool, struct dm_bio_prison_cell *cell, struct bio_list *bios){ dm_cell_release(pool->prison, cell, bios); dm_bio_prison_free_cell(pool->prison, cell);} static void cell_visit_release(struct pool *pool, void (*fn)(void *, struct dm_bio_prison_cell *), void *context, struct dm_bio_prison_cell *cell){ dm_cell_visit_release(pool->prison, fn, context, cell); dm_bio_prison_free_cell(pool->prison, cell);} static void cell_release_no_holder(struct pool *pool, struct dm_bio_prison_cell *cell, struct bio_list *bios){ dm_cell_release_no_holder(pool->prison, cell, bios); dm_bio_prison_free_cell(pool->prison, cell);} static void cell_error_with_code(struct pool *pool, struct dm_bio_prison_cell *cell, blk_status_t error_code){ dm_cell_error(pool->prison, cell, error_code); dm_bio_prison_free_cell(pool->prison, cell);} static blk_status_t get_pool_io_error_code(struct pool *pool){Lines 1–500 of 4558. Load more
// SPDX-License-Identifier: GPL-2.0-only /* * Copyright (C) 2011-2012 Red Hat UK. * * This file is released under the GPL. */ #include "dm-thin-metadata.h" #include "dm-bio-prison-v1.h" #include "dm.h" #include <linux/device-mapper.h> #include <linux/dm-io.h> #include <linux/dm-kcopyd.h> #include <linux/jiffies.h> #include <linux/log2.h> #include <linux/list.h> #include <linux/rculist.h> #include <linux/init.h> #include <linux/module.h> #include <linux/slab.h> #include <linux/vmalloc.h> #include <linux/sort.h> #include <linux/rbtree.h> #define DM_MSG_PREFIX "thin" /* * Tunable constants */ #define ENDIO_HOOK_POOL_SIZE 1024 #define MAPPING_POOL_SIZE 1024 #define COMMIT_PERIOD HZ #define NO_SPACE_TIMEOUT_SECS 60 static unsigned int no_space_timeout_secs = NO_SPACE_TIMEOUT_SECS; DECLARE_DM_KCOPYD_THROTTLE_WITH_MODULE_PARM(snapshot_copy_throttle, "A percentage of time allocated for copy on write"); /* * The block size of the device holding pool data must be * between 64KB and 1GB. */ #define DATA_DEV_BLOCK_SIZE_MIN_SECTORS (64 * 1024 >> SECTOR_SHIFT) #define DATA_DEV_BLOCK_SIZE_MAX_SECTORS (1024 * 1024 * 1024 >> SECTOR_SHIFT) /* * Device id is restricted to 24 bits. */ #define MAX_DEV_ID ((1 << 24) - 1) /* * How do we handle breaking sharing of data blocks? * ================================================= * * We use a standard copy-on-write btree to store the mappings for the * devices (note I'm talking about copy-on-write of the metadata here, not * the data). When you take an internal snapshot you clone the root node * of the origin btree. After this there is no concept of an origin or a * snapshot. They are just two device trees that happen to point to the * same data blocks. * * When we get a write in we decide if it's to a shared data block using * some timestamp magic. If it is, we have to break sharing. * * Let's say we write to a shared block in what was the origin. The * steps are: * * i) plug io further to this physical block. (see bio_prison code). * * ii) quiesce any read io to that shared data block. Obviously * including all devices that share this block. (see dm_deferred_set code) * * iii) copy the data block to a newly allocate block. This step can be * missed out if the io covers the block. (schedule_copy). * * iv) insert the new mapping into the origin's btree * (process_prepared_mapping). This act of inserting breaks some * sharing of btree nodes between the two devices. Breaking sharing only * effects the btree of that specific device. Btrees for the other * devices that share the block never change. The btree for the origin * device as it was after the last commit is untouched, ie. we're using * persistent data structures in the functional programming sense. * * v) unplug io to this physical block, including the io that triggered * the breaking of sharing. * * Steps (ii) and (iii) occur in parallel. * * The metadata _doesn't_ need to be committed before the io continues. We * get away with this because the io is always written to a _new_ block. * If there's a crash, then: * * - The origin mapping will point to the old origin block (the shared * one). This will contain the data as it was before the io that triggered * the breaking of sharing came in. * * - The snap mapping still points to the old block. As it would after * the commit. * * The downside of this scheme is the timestamp magic isn't perfect, and * will continue to think that data block in the snapshot device is shared * even after the write to the origin has broken sharing. I suspect data * blocks will typically be shared by many different devices, so we're * breaking sharing n + 1 times, rather than n, where n is the number of * devices that reference this data block. At the moment I think the * benefits far, far outweigh the disadvantages. */ /*----------------------------------------------------------------*/ /* * Key building. */ enum lock_space { VIRTUAL, PHYSICAL }; static bool build_key(struct dm_thin_device *td, enum lock_space ls, dm_block_t b, dm_block_t e, struct dm_cell_key *key) { key->virtual = (ls == VIRTUAL); key->dev = dm_thin_dev_id(td); key->block_begin = b; key->block_end = e; return dm_cell_key_has_valid_range(key); } static void build_data_key(struct dm_thin_device *td, dm_block_t b, struct dm_cell_key *key) { (void) build_key(td, PHYSICAL, b, b + 1llu, key); } static void build_virtual_key(struct dm_thin_device *td, dm_block_t b, struct dm_cell_key *key) { (void) build_key(td, VIRTUAL, b, b + 1llu, key); } /*----------------------------------------------------------------*/ #define THROTTLE_THRESHOLD (1 * HZ) struct throttle { struct rw_semaphore lock; unsigned long threshold; bool throttle_applied; }; static void throttle_init(struct throttle *t) { init_rwsem(&t->lock); t->throttle_applied = false; } static void throttle_work_start(struct throttle *t) { t->threshold = jiffies + THROTTLE_THRESHOLD; } static void throttle_work_update(struct throttle *t) { if (!t->throttle_applied && time_is_before_jiffies(t->threshold)) { down_write(&t->lock); t->throttle_applied = true; } } static void throttle_work_complete(struct throttle *t) { if (t->throttle_applied) { t->throttle_applied = false; up_write(&t->lock); } } static void throttle_lock(struct throttle *t) { down_read(&t->lock); } static void throttle_unlock(struct throttle *t) { up_read(&t->lock); } /*----------------------------------------------------------------*/ /* * A pool device ties together a metadata device and a data device. It * also provides the interface for creating and destroying internal * devices. */ struct dm_thin_new_mapping; /* * The pool runs in various modes. Ordered in degraded order for comparisons. */ enum pool_mode { PM_WRITE, /* metadata may be changed */ PM_OUT_OF_DATA_SPACE, /* metadata may be changed, though data may not be allocated */ /* * Like READ_ONLY, except may switch back to WRITE on metadata resize. Reported as READ_ONLY. */ PM_OUT_OF_METADATA_SPACE, PM_READ_ONLY, /* metadata may not be changed */ PM_FAIL, /* all I/O fails */ }; struct pool_features { enum pool_mode mode; bool zero_new_blocks:1; bool discard_enabled:1; bool discard_passdown:1; bool error_if_no_space:1; }; struct thin_c; typedef void (*process_bio_fn)(struct thin_c *tc, struct bio *bio); typedef void (*process_cell_fn)(struct thin_c *tc, struct dm_bio_prison_cell *cell); typedef void (*process_mapping_fn)(struct dm_thin_new_mapping *m); #define CELL_SORT_ARRAY_SIZE 8192 struct pool { struct list_head list; struct dm_target *ti; /* Only set if a pool target is bound */ struct mapped_device *pool_md; struct block_device *data_dev; struct block_device *md_dev; struct dm_pool_metadata *pmd; dm_block_t low_water_blocks; uint32_t sectors_per_block; int sectors_per_block_shift; struct pool_features pf; bool low_water_triggered:1; /* A dm event has been sent */ bool suspended:1; bool out_of_data_space:1; struct dm_bio_prison *prison; struct dm_kcopyd_client *copier; struct work_struct worker; struct workqueue_struct *wq; struct throttle throttle; struct delayed_work waker; struct delayed_work no_space_timeout; unsigned long last_commit_jiffies; unsigned int ref_count; spinlock_t lock; struct bio_list deferred_flush_bios; struct bio_list deferred_flush_completions; struct list_head prepared_mappings; struct list_head prepared_discards; struct list_head prepared_discards_pt2; struct list_head active_thins; struct dm_deferred_set *shared_read_ds; struct dm_deferred_set *all_io_ds; struct dm_thin_new_mapping *next_mapping; process_bio_fn process_bio; process_bio_fn process_discard; process_cell_fn process_cell; process_cell_fn process_discard_cell; process_mapping_fn process_prepared_mapping; process_mapping_fn process_prepared_discard; process_mapping_fn process_prepared_discard_pt2; struct dm_bio_prison_cell **cell_sort_array; mempool_t mapping_pool; }; static void metadata_operation_failed(struct pool *pool, const char *op, int r); static enum pool_mode get_pool_mode(struct pool *pool) { return pool->pf.mode; } static void notify_of_pool_mode_change(struct pool *pool) { static const char *descs[] = { "write", "out-of-data-space", "read-only", "read-only", "fail" }; const char *extra_desc = NULL; enum pool_mode mode = get_pool_mode(pool); if (mode == PM_OUT_OF_DATA_SPACE) { if (!pool->pf.error_if_no_space) extra_desc = " (queue IO)"; else extra_desc = " (error IO)"; } dm_table_event(pool->ti->table); DMINFO("%s: switching pool to %s%s mode", dm_device_name(pool->pool_md), descs[(int)mode], extra_desc ? : ""); } /* * Target context for a pool. */ struct pool_c { struct dm_target *ti; struct pool *pool; struct dm_dev *data_dev; struct dm_dev *metadata_dev; dm_block_t low_water_blocks; struct pool_features requested_pf; /* Features requested during table load */ struct pool_features adjusted_pf; /* Features used after adjusting for constituent devices */ }; /* * Target context for a thin. */ struct thin_c { struct list_head list; struct dm_dev *pool_dev; struct dm_dev *origin_dev; sector_t origin_size; dm_thin_id dev_id; struct pool *pool; struct dm_thin_device *td; struct mapped_device *thin_md; bool requeue_mode:1; spinlock_t lock; struct list_head deferred_cells; struct bio_list deferred_bio_list; struct bio_list retry_on_resume_list; struct rb_root sort_bio_list; /* sorted list of deferred bios */ /* * Ensures the thin is not destroyed until the worker has finished * iterating the active_thins list. */ refcount_t refcount; struct completion can_destroy; }; /*----------------------------------------------------------------*/ static bool block_size_is_power_of_two(struct pool *pool) { return pool->sectors_per_block_shift >= 0; } static sector_t block_to_sectors(struct pool *pool, dm_block_t b) { return block_size_is_power_of_two(pool) ? (b << pool->sectors_per_block_shift) : (b * pool->sectors_per_block); } /*----------------------------------------------------------------*/ struct discard_op { struct thin_c *tc; struct blk_plug plug; struct bio *parent_bio; struct bio *bio; }; static void begin_discard(struct discard_op *op, struct thin_c *tc, struct bio *parent) { BUG_ON(!parent); op->tc = tc; blk_start_plug(&op->plug); op->parent_bio = parent; op->bio = NULL; } static void issue_discard(struct discard_op *op, dm_block_t data_b, dm_block_t data_e) { struct thin_c *tc = op->tc; sector_t s = block_to_sectors(tc->pool, data_b); sector_t len = block_to_sectors(tc->pool, data_e - data_b); __blkdev_issue_discard(tc->pool_dev->bdev, s, len, GFP_NOIO, &op->bio); } static void end_discard(struct discard_op *op, int r) { if (op->bio) { /* * Even if one of the calls to issue_discard failed, we * need to wait for the chain to complete. */ bio_chain(op->bio, op->parent_bio); op->bio->bi_opf = REQ_OP_DISCARD; submit_bio(op->bio); } blk_finish_plug(&op->plug); /* * Even if r is set, there could be sub discards in flight that we * need to wait for. */ if (r && !op->parent_bio->bi_status) op->parent_bio->bi_status = errno_to_blk_status(r); bio_endio(op->parent_bio); } /*----------------------------------------------------------------*/ /* * wake_worker() is used when new work is queued and when pool_resume is * ready to continue deferred IO processing. */ static void wake_worker(struct pool *pool) { queue_work(pool->wq, &pool->worker); } /*----------------------------------------------------------------*/ static int bio_detain(struct pool *pool, struct dm_cell_key *key, struct bio *bio, struct dm_bio_prison_cell **cell_result) { int r; struct dm_bio_prison_cell *cell_prealloc; /* * Allocate a cell from the prison's mempool. * This might block but it can't fail. */ cell_prealloc = dm_bio_prison_alloc_cell(pool->prison, GFP_NOIO); r = dm_bio_detain(pool->prison, key, bio, cell_prealloc, cell_result); if (r) { /* * We reused an old cell; we can get rid of * the new one. */ dm_bio_prison_free_cell(pool->prison, cell_prealloc); } return r; } static void cell_release(struct pool *pool, struct dm_bio_prison_cell *cell, struct bio_list *bios) { dm_cell_release(pool->prison, cell, bios); dm_bio_prison_free_cell(pool->prison, cell); } static void cell_visit_release(struct pool *pool, void (*fn)(void *, struct dm_bio_prison_cell *), void *context, struct dm_bio_prison_cell *cell) { dm_cell_visit_release(pool->prison, fn, context, cell); dm_bio_prison_free_cell(pool->prison, cell); } static void cell_release_no_holder(struct pool *pool, struct dm_bio_prison_cell *cell, struct bio_list *bios) { dm_cell_release_no_holder(pool->prison, cell, bios); dm_bio_prison_free_cell(pool->prison, cell); } static void cell_error_with_code(struct pool *pool, struct dm_bio_prison_cell *cell, blk_status_t error_code) { dm_cell_error(pool->prison, cell, error_code); dm_bio_prison_free_cell(pool->prison, cell); } static blk_status_t get_pool_io_error_code(struct pool *pool) {