375 lines
10 KiB
C
375 lines
10 KiB
C
// 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);
|
|
}
|