// SPDX-License-Identifier: GPL-2.0-or-later #include #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); }