| // SPDX-License-Identifier: GPL-2.0-or-later | |
| #include <linux/blkdev.h> | |
| #include "../dm-core.h" | |
| #include "pcache_internal.h" | |
| #include "cache_dev.h" | |
| #include "backing_dev.h" | |
| #include "cache.h" | |
| #include "dm_pcache.h" | |
| static struct kmem_cache *backing_req_cache; | |
| static struct kmem_cache *backing_bvec_cache; | |
| static void backing_dev_exit(struct pcache_backing_dev *backing_dev) | |
| { | |
| mempool_exit(&backing_dev->req_pool); | |
| mempool_exit(&backing_dev->bvec_pool); | |
| } | |
| static void req_submit_fn(struct work_struct *work); | |
| static void req_complete_fn(struct work_struct *work); | |
| static int backing_dev_init(struct dm_pcache *pcache) | |
| { | |
| struct pcache_backing_dev *backing_dev = &pcache->backing_dev; | |
| int ret; | |
| ret = mempool_init_slab_pool(&backing_dev->req_pool, 128, backing_req_cache); | |
| if (ret) | |
| goto err; | |
| ret = mempool_init_slab_pool(&backing_dev->bvec_pool, 128, backing_bvec_cache); | |
| if (ret) | |
| goto req_pool_exit; | |
| INIT_LIST_HEAD(&backing_dev->submit_list); | |
| INIT_LIST_HEAD(&backing_dev->complete_list); | |
| spin_lock_init(&backing_dev->submit_lock); | |
| spin_lock_init(&backing_dev->complete_lock); | |
| INIT_WORK(&backing_dev->req_submit_work, req_submit_fn); | |
| INIT_WORK(&backing_dev->req_complete_work, req_complete_fn); | |
| atomic_set(&backing_dev->inflight_reqs, 0); | |
| init_waitqueue_head(&backing_dev->inflight_wq); | |
| return 0; | |
| req_pool_exit: | |
| mempool_exit(&backing_dev->req_pool); | |
| err: | |
| return ret; | |
| } | |
| int backing_dev_start(struct dm_pcache *pcache) | |
| { | |
| struct pcache_backing_dev *backing_dev = &pcache->backing_dev; | |
| int ret; | |
| ret = backing_dev_init(pcache); | |
| if (ret) | |
| return ret; | |
| backing_dev->dev_size = bdev_nr_sectors(backing_dev->dm_dev->bdev); | |
| return 0; | |
| } | |
| void backing_dev_stop(struct dm_pcache *pcache) | |
| { | |
| struct pcache_backing_dev *backing_dev = &pcache->backing_dev; | |
| /* | |
| * There should not be any new request comming, just wait | |
| * inflight requests done. | |
| */ | |
| wait_event(backing_dev->inflight_wq, | |
| atomic_read(&backing_dev->inflight_reqs) == 0); | |
| flush_work(&backing_dev->req_submit_work); | |
| flush_work(&backing_dev->req_complete_work); | |
| backing_dev_exit(backing_dev); | |
| } | |
| /* pcache_backing_dev_req functions */ | |
| void backing_dev_req_end(struct pcache_backing_dev_req *backing_req) | |
| { | |
| struct pcache_backing_dev *backing_dev = backing_req->backing_dev; | |
| if (backing_req->end_req) | |
| backing_req->end_req(backing_req, backing_req->ret); | |
| switch (backing_req->type) { | |
| case BACKING_DEV_REQ_TYPE_REQ: | |
| if (backing_req->req.upper_req) | |
| pcache_req_put(backing_req->req.upper_req, backing_req->ret); | |
| break; | |
| case BACKING_DEV_REQ_TYPE_KMEM: | |
| if (backing_req->kmem.bvecs != backing_req->kmem.inline_bvecs) | |
| mempool_free(backing_req->kmem.bvecs, &backing_dev->bvec_pool); | |
| break; | |
| default: | |
| BUG(); | |
| } | |
| mempool_free(backing_req, &backing_dev->req_pool); | |
| if (atomic_dec_and_test(&backing_dev->inflight_reqs)) | |
| wake_up(&backing_dev->inflight_wq); | |
| } | |
| static void req_complete_fn(struct work_struct *work) | |
| { | |
| struct pcache_backing_dev *backing_dev = container_of(work, struct pcache_backing_dev, req_complete_work); | |
| struct pcache_backing_dev_req *backing_req; | |
| LIST_HEAD(tmp_list); | |
| spin_lock_irq(&backing_dev->complete_lock); | |
| list_splice_init(&backing_dev->complete_list, &tmp_list); | |
| spin_unlock_irq(&backing_dev->complete_lock); | |
| while (!list_empty(&tmp_list)) { | |
| backing_req = list_first_entry(&tmp_list, | |
| struct pcache_backing_dev_req, node); | |
| list_del_init(&backing_req->node); | |
| backing_dev_req_end(backing_req); | |
| } | |
| } | |
| static void backing_dev_bio_end(struct bio *bio) | |
| { | |
| struct pcache_backing_dev_req *backing_req = bio->bi_private; | |
| struct pcache_backing_dev *backing_dev = backing_req->backing_dev; | |
| unsigned long flags; | |
| backing_req->ret = blk_status_to_errno(bio->bi_status); | |
| spin_lock_irqsave(&backing_dev->complete_lock, flags); | |
| list_move_tail(&backing_req->node, &backing_dev->complete_list); | |
| queue_work(BACKING_DEV_TO_PCACHE(backing_dev)->task_wq, &backing_dev->req_complete_work); | |
| spin_unlock_irqrestore(&backing_dev->complete_lock, flags); | |
| } | |
| static void req_submit_fn(struct work_struct *work) | |
| { | |
| struct pcache_backing_dev *backing_dev = container_of(work, struct pcache_backing_dev, req_submit_work); | |
| struct pcache_backing_dev_req *backing_req; | |
| LIST_HEAD(tmp_list); | |
| spin_lock(&backing_dev->submit_lock); | |
| list_splice_init(&backing_dev->submit_list, &tmp_list); | |
| spin_unlock(&backing_dev->submit_lock); | |
| while (!list_empty(&tmp_list)) { | |
| backing_req = list_first_entry(&tmp_list, | |
| struct pcache_backing_dev_req, node); | |
| list_del_init(&backing_req->node); | |
| submit_bio_noacct(&backing_req->bio); | |
| } | |
| } | |
| void backing_dev_req_submit(struct pcache_backing_dev_req *backing_req, bool direct) | |
| { | |
| struct pcache_backing_dev *backing_dev = backing_req->backing_dev; | |
| if (direct) { | |
| submit_bio_noacct(&backing_req->bio); | |
| return; | |
| } | |
| spin_lock(&backing_dev->submit_lock); | |
| list_add_tail(&backing_req->node, &backing_dev->submit_list); | |
| queue_work(BACKING_DEV_TO_PCACHE(backing_dev)->task_wq, &backing_dev->req_submit_work); | |
| spin_unlock(&backing_dev->submit_lock); | |
| } | |
| static void bio_map(struct bio *bio, void *base, size_t size) | |
| { | |
| struct page *page; | |
| unsigned int offset; | |
| unsigned int len; | |
| if (!is_vmalloc_addr(base)) { | |
| page = virt_to_page(base); | |
| offset = offset_in_page(base); | |
| BUG_ON(!bio_add_page(bio, page, size, offset)); | |
| return; | |
| } | |
| flush_kernel_vmap_range(base, size); | |
| while (size) { | |
| page = vmalloc_to_page(base); | |
| offset = offset_in_page(base); | |
| len = min_t(size_t, PAGE_SIZE - offset, size); | |
| BUG_ON(!bio_add_page(bio, page, len, offset)); | |
| size -= len; | |
| base += len; | |
| } | |
| } | |
| static struct pcache_backing_dev_req *req_type_req_alloc(struct pcache_backing_dev *backing_dev, | |
| struct pcache_backing_dev_req_opts *opts) | |
| { | |
| struct pcache_request *pcache_req = opts->req.upper_req; | |
| struct pcache_backing_dev_req *backing_req; | |
| struct bio *orig = pcache_req->bio; | |
| backing_req = mempool_alloc(&backing_dev->req_pool, opts->gfp_mask); | |
| if (!backing_req) | |
| return NULL; | |
| memset(backing_req, 0, sizeof(struct pcache_backing_dev_req)); | |
| bio_init_clone(backing_dev->dm_dev->bdev, &backing_req->bio, orig, opts->gfp_mask); | |
| backing_req->type = BACKING_DEV_REQ_TYPE_REQ; | |
| backing_req->backing_dev = backing_dev; | |
| atomic_inc(&backing_dev->inflight_reqs); | |
| return backing_req; | |
| } | |
| static struct pcache_backing_dev_req *kmem_type_req_alloc(struct pcache_backing_dev *backing_dev, | |
| struct pcache_backing_dev_req_opts *opts) | |
| { | |
| struct pcache_backing_dev_req *backing_req; | |
| u32 n_vecs = bio_add_max_vecs(opts->kmem.data, opts->kmem.len); | |
| backing_req = mempool_alloc(&backing_dev->req_pool, opts->gfp_mask); | |
| if (!backing_req) | |
| return NULL; | |
| memset(backing_req, 0, sizeof(struct pcache_backing_dev_req)); | |
| if (n_vecs > BACKING_DEV_REQ_INLINE_BVECS) { | |
| backing_req->kmem.bvecs = mempool_alloc(&backing_dev->bvec_pool, opts->gfp_mask); | |
| if (!backing_req->kmem.bvecs) | |
| goto free_backing_req; | |
| } else { | |
| backing_req->kmem.bvecs = backing_req->kmem.inline_bvecs; | |
| } | |
| backing_req->kmem.n_vecs = n_vecs; | |
| backing_req->type = BACKING_DEV_REQ_TYPE_KMEM; | |
| backing_req->backing_dev = backing_dev; | |
| atomic_inc(&backing_dev->inflight_reqs); | |
| return backing_req; | |
| free_backing_req: | |
| mempool_free(backing_req, &backing_dev->req_pool); | |
| return NULL; | |
| } | |
| struct pcache_backing_dev_req *backing_dev_req_alloc(struct pcache_backing_dev *backing_dev, | |
| struct pcache_backing_dev_req_opts *opts) | |
| { | |
| if (opts->type == BACKING_DEV_REQ_TYPE_REQ) | |
| return req_type_req_alloc(backing_dev, opts); | |
| if (opts->type == BACKING_DEV_REQ_TYPE_KMEM) | |
| return kmem_type_req_alloc(backing_dev, opts); | |
| BUG(); | |
| } | |
| static void req_type_req_init(struct pcache_backing_dev_req *backing_req, | |
| struct pcache_backing_dev_req_opts *opts) | |
| { | |
| struct pcache_request *pcache_req = opts->req.upper_req; | |
| struct bio *clone; | |
| u32 off = opts->req.req_off; | |
| u32 len = opts->req.len; | |
| clone = &backing_req->bio; | |
| BUG_ON(off & SECTOR_MASK); | |
| BUG_ON(len & SECTOR_MASK); | |
| bio_trim(clone, off >> SECTOR_SHIFT, len >> SECTOR_SHIFT); | |
| clone->bi_iter.bi_sector = (pcache_req->off + off) >> SECTOR_SHIFT; | |
| clone->bi_private = backing_req; | |
| clone->bi_end_io = backing_dev_bio_end; | |
| INIT_LIST_HEAD(&backing_req->node); | |
| backing_req->end_req = opts->end_fn; | |
| pcache_req_get(pcache_req); | |
| backing_req->req.upper_req = pcache_req; | |
| backing_req->req.bio_off = off; | |
| } | |
| static void kmem_type_req_init(struct pcache_backing_dev_req *backing_req, | |
| struct pcache_backing_dev_req_opts *opts) | |
| { | |
| struct pcache_backing_dev *backing_dev = backing_req->backing_dev; | |
| struct bio *backing_bio; | |
| bio_init(&backing_req->bio, backing_dev->dm_dev->bdev, backing_req->kmem.bvecs, | |
| backing_req->kmem.n_vecs, opts->kmem.opf); | |
| backing_bio = &backing_req->bio; | |
| bio_map(backing_bio, opts->kmem.data, opts->kmem.len); | |
| backing_bio->bi_iter.bi_sector = (opts->kmem.backing_off) >> SECTOR_SHIFT; | |
| backing_bio->bi_private = backing_req; | |
| backing_bio->bi_end_io = backing_dev_bio_end; | |
| INIT_LIST_HEAD(&backing_req->node); | |
| backing_req->end_req = opts->end_fn; | |
| backing_req->priv_data = opts->priv_data; | |
| } | |
| void backing_dev_req_init(struct pcache_backing_dev_req *backing_req, | |
| struct pcache_backing_dev_req_opts *opts) | |
| { | |
| if (opts->type == BACKING_DEV_REQ_TYPE_REQ) | |
| return req_type_req_init(backing_req, opts); | |
| if (opts->type == BACKING_DEV_REQ_TYPE_KMEM) | |
| return kmem_type_req_init(backing_req, opts); | |
| BUG(); | |
| } | |
| struct pcache_backing_dev_req *backing_dev_req_create(struct pcache_backing_dev *backing_dev, | |
| struct pcache_backing_dev_req_opts *opts) | |
| { | |
| struct pcache_backing_dev_req *backing_req; | |
| backing_req = backing_dev_req_alloc(backing_dev, opts); | |
| if (!backing_req) | |
| return NULL; | |
| backing_dev_req_init(backing_req, opts); | |
| return backing_req; | |
| } | |
| void backing_dev_flush(struct pcache_backing_dev *backing_dev) | |
| { | |
| blkdev_issue_flush(backing_dev->dm_dev->bdev); | |
| } | |
| int pcache_backing_init(void) | |
| { | |
| u32 max_bvecs = (PCACHE_CACHE_SUBTREE_SIZE >> PAGE_SHIFT) + 1; | |
| int ret; | |
| backing_req_cache = KMEM_CACHE(pcache_backing_dev_req, 0); | |
| if (!backing_req_cache) { | |
| ret = -ENOMEM; | |
| goto err; | |
| } | |
| backing_bvec_cache = kmem_cache_create("pcache-bvec-slab", | |
| max_bvecs * sizeof(struct bio_vec), | |
| 0, 0, NULL); | |
| if (!backing_bvec_cache) { | |
| ret = -ENOMEM; | |
| goto destroy_req_cache; | |
| } | |
| return 0; | |
| destroy_req_cache: | |
| kmem_cache_destroy(backing_req_cache); | |
| err: | |
| return ret; | |
| } | |
| void pcache_backing_exit(void) | |
| { | |
| kmem_cache_destroy(backing_bvec_cache); | |
| kmem_cache_destroy(backing_req_cache); | |
| } |