[v1,01/17] block, bdev_filter: enable block device filters
Commit Message
Allows to attach block device filters to the block devices. Kernel
modules can use this functionality to extend the capabilities of the
block layer.
Signed-off-by: Sergei Shtepa <sergei.shtepa@veeam.com>
---
block/bdev.c | 73 +++++++++++++++++++++++++++++++++++++++
block/blk-core.c | 19 ++++++++--
include/linux/blk_types.h | 2 ++
include/linux/blkdev.h | 64 ++++++++++++++++++++++++++++++++++
4 files changed, 156 insertions(+), 2 deletions(-)
@@ -427,6 +427,7 @@ static void init_once(void *data)
static void bdev_evict_inode(struct inode *inode)
{
+ bdev_filter_detach(I_BDEV(inode));
truncate_inode_pages_final(&inode->i_data);
invalidate_inode_buffers(inode); /* is it needed here? */
clear_inode(inode);
@@ -502,6 +503,7 @@ struct block_device *bdev_alloc(struct gendisk *disk, u8 partno)
return NULL;
}
bdev->bd_disk = disk;
+ bdev->bd_filter = NULL;
return bdev;
}
@@ -1092,3 +1094,74 @@ void bdev_statx_dioalign(struct inode *inode, struct kstat *stat)
blkdev_put_no_open(bdev);
}
+
+/**
+ * bdev_filter_attach - Attach a filter to the original block device.
+ * @bdev:
+ * Block device.
+ * @flt:
+ * Pointer to the filter structure.
+ *
+ * Before adding a filter, it is necessary to initialize &struct bdev_filter.
+ *
+ * The bdev_filter_detach() function allows to detach the filter from the block
+ * device.
+ *
+ * Return:
+ * 0 - OK
+ * -EALREADY - a filter with this name already exists
+ */
+int bdev_filter_attach(struct block_device *bdev,
+ struct bdev_filter *flt)
+{
+ int ret = 0;
+
+ blk_mq_freeze_queue(bdev->bd_queue);
+ blk_mq_quiesce_queue(bdev->bd_queue);
+
+ if (bdev->bd_filter)
+ ret = -EALREADY;
+ else
+ bdev->bd_filter = flt;
+
+ blk_mq_unquiesce_queue(bdev->bd_queue);
+ blk_mq_unfreeze_queue(bdev->bd_queue);
+
+ return ret;
+}
+EXPORT_SYMBOL(bdev_filter_attach);
+
+/**
+ * bdev_filter_detach - Detach a filter from the block device.
+ * @bdev:
+ * Block device.
+ *
+ * The filter should be added using the bdev_filter_attach() function.
+ *
+ * Return:
+ * 0 - OK
+ * -ENOENT - the filter was not found in the linked list
+ */
+int bdev_filter_detach(struct block_device *bdev)
+{
+ int ret = 0;
+ struct bdev_filter *flt = NULL;
+
+ blk_mq_freeze_queue(bdev->bd_queue);
+ blk_mq_quiesce_queue(bdev->bd_queue);
+
+ flt = bdev->bd_filter;
+ if (flt)
+ bdev->bd_filter = NULL;
+ else
+ ret = -ENOENT;
+
+ blk_mq_unquiesce_queue(bdev->bd_queue);
+ blk_mq_unfreeze_queue(bdev->bd_queue);
+
+ if (flt)
+ bdev_filter_put(flt);
+
+ return ret;
+}
+EXPORT_SYMBOL(bdev_filter_detach);
@@ -679,9 +679,24 @@ void submit_bio_noacct_nocheck(struct bio *bio)
* to collect a list of requests submited by a ->submit_bio method while
* it is active, and then process them after it returned.
*/
- if (current->bio_list)
+ if (current->bio_list) {
bio_list_add(¤t->bio_list[0], bio);
- else if (!bio->bi_bdev->bd_disk->fops->submit_bio)
+ return;
+ }
+
+ if (bio->bi_bdev->bd_filter && !bio_flagged(bio, BIO_FILTERED)) {
+ bool pass;
+
+ pass = bio->bi_bdev->bd_filter->fops->submit_bio_cb(bio);
+ bio_set_flag(bio, BIO_FILTERED);
+ if (!pass) {
+ bio->bi_status = BLK_STS_OK;
+ bio_endio(bio);
+ return;
+ }
+ }
+
+ if (!bio->bi_bdev->bd_disk->fops->submit_bio)
__submit_bio_noacct_mq(bio);
else
__submit_bio_noacct(bio);
@@ -68,6 +68,7 @@ struct block_device {
#ifdef CONFIG_FAIL_MAKE_REQUEST
bool bd_make_it_fail;
#endif
+ struct bdev_filter *bd_filter;
} __randomize_layout;
#define bdev_whole(_bdev) \
@@ -333,6 +334,7 @@ enum {
BIO_QOS_MERGED, /* but went through rq_qos merge path */
BIO_REMAPPED,
BIO_ZONE_WRITE_LOCKED, /* Owns a zoned device zone write lock */
+ BIO_FILTERED, /* bio has already been filtered */
BIO_FLAG_LAST
};
@@ -1549,4 +1549,68 @@ struct io_comp_batch {
#define DEFINE_IO_COMP_BATCH(name) struct io_comp_batch name = { }
+/**
+ * struct bdev_filter_operations - List of callback functions for the filter.
+ *
+ * @submit_bio_cb:
+ * A callback function for bio processing.
+ * @detach_cb:
+ * A callback function to disable the filter when removing a block
+ * device from the system.
+ */
+struct bdev_filter_operations {
+ bool (*submit_bio_cb)(struct bio *bio);
+ void (*detach_cb)(struct kref *kref);
+};
+/**
+ * struct bdev_filter - Block device filter.
+ *
+ * @kref:
+ * Kernel reference counter.
+ * @fops:
+ * The pointer to &struct bdev_filter_operations with callback
+ * functions for the filter.
+ */
+struct bdev_filter {
+ struct kref kref;
+ const struct bdev_filter_operations *fops;
+};
+/**
+ * bdev_filter_init - Initialization of the filter structure.
+ * @flt:
+ * Pointer to the &struct bdev_filter to be initialized.
+ * @fops:
+ * The callback functions for the filter.
+ */
+static inline void bdev_filter_init(struct bdev_filter *flt,
+ const struct bdev_filter_operations *fops)
+{
+ kref_init(&flt->kref);
+ flt->fops = fops;
+};
+
+/**
+ * bdev_filter_get - Incremnent reference counter.
+ * @flt:
+ * Pointer to the &struct bdev_filter.
+ */
+static inline void bdev_filter_get(struct bdev_filter *flt)
+{
+ kref_get(&flt->kref);
+}
+
+/**
+ * bdev_filter_put - Decrement reference counter and detach filter.
+ * @flt:
+ * Pointer to the &struct bdev_filter.
+ */
+static inline void bdev_filter_put(struct bdev_filter *flt)
+{
+ kref_put(&flt->kref, flt->fops->detach_cb);
+};
+
+int bdev_filter_attach(struct block_device *bdev, struct bdev_filter *flt);
+int bdev_filter_detach(struct block_device *bdev);
+
+
#endif /* _LINUX_BLKDEV_H */