498 lines
11 KiB
C
498 lines
11 KiB
C
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
#include <linux/module.h>
|
|
#include <linux/blkdev.h>
|
|
#include <linux/bio.h>
|
|
|
|
#include "../dm-core.h"
|
|
#include "cache_dev.h"
|
|
#include "backing_dev.h"
|
|
#include "cache.h"
|
|
#include "dm_pcache.h"
|
|
|
|
void pcache_defer_reqs_kick(struct dm_pcache *pcache)
|
|
{
|
|
struct pcache_cache *cache = &pcache->cache;
|
|
|
|
spin_lock(&cache->seg_map_lock);
|
|
if (!cache->cache_full)
|
|
queue_work(pcache->task_wq, &pcache->defered_req_work);
|
|
spin_unlock(&cache->seg_map_lock);
|
|
}
|
|
|
|
static void defer_req(struct pcache_request *pcache_req)
|
|
{
|
|
struct dm_pcache *pcache = pcache_req->pcache;
|
|
|
|
BUG_ON(!list_empty(&pcache_req->list_node));
|
|
|
|
spin_lock(&pcache->defered_req_list_lock);
|
|
list_add(&pcache_req->list_node, &pcache->defered_req_list);
|
|
pcache_defer_reqs_kick(pcache);
|
|
spin_unlock(&pcache->defered_req_list_lock);
|
|
}
|
|
|
|
static void defered_req_fn(struct work_struct *work)
|
|
{
|
|
struct dm_pcache *pcache = container_of(work, struct dm_pcache, defered_req_work);
|
|
struct pcache_request *pcache_req;
|
|
LIST_HEAD(tmp_list);
|
|
int ret;
|
|
|
|
if (pcache_is_stopping(pcache))
|
|
return;
|
|
|
|
spin_lock(&pcache->defered_req_list_lock);
|
|
list_splice_init(&pcache->defered_req_list, &tmp_list);
|
|
spin_unlock(&pcache->defered_req_list_lock);
|
|
|
|
while (!list_empty(&tmp_list)) {
|
|
pcache_req = list_first_entry(&tmp_list,
|
|
struct pcache_request, list_node);
|
|
list_del_init(&pcache_req->list_node);
|
|
pcache_req->ret = 0;
|
|
ret = pcache_cache_handle_req(&pcache->cache, pcache_req);
|
|
if (ret == -EBUSY)
|
|
defer_req(pcache_req);
|
|
else
|
|
pcache_req_put(pcache_req, ret);
|
|
}
|
|
}
|
|
|
|
void pcache_req_get(struct pcache_request *pcache_req)
|
|
{
|
|
kref_get(&pcache_req->ref);
|
|
}
|
|
|
|
static void end_req(struct kref *ref)
|
|
{
|
|
struct pcache_request *pcache_req = container_of(ref, struct pcache_request, ref);
|
|
struct dm_pcache *pcache = pcache_req->pcache;
|
|
struct bio *bio = pcache_req->bio;
|
|
int ret = pcache_req->ret;
|
|
|
|
if (ret == -EBUSY) {
|
|
pcache_req_get(pcache_req);
|
|
defer_req(pcache_req);
|
|
} else {
|
|
bio->bi_status = errno_to_blk_status(ret);
|
|
bio_endio(bio);
|
|
|
|
if (atomic_dec_and_test(&pcache->inflight_reqs))
|
|
wake_up(&pcache->inflight_wq);
|
|
}
|
|
}
|
|
|
|
void pcache_req_put(struct pcache_request *pcache_req, int ret)
|
|
{
|
|
/* Set the return status if it is not already set */
|
|
if (ret && !pcache_req->ret)
|
|
pcache_req->ret = ret;
|
|
|
|
kref_put(&pcache_req->ref, end_req);
|
|
}
|
|
|
|
static bool at_least_one_arg(struct dm_arg_set *as, char **error)
|
|
{
|
|
if (!as->argc) {
|
|
*error = "Insufficient args";
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static int parse_cache_dev(struct dm_pcache *pcache, struct dm_arg_set *as,
|
|
char **error)
|
|
{
|
|
int ret;
|
|
|
|
if (!at_least_one_arg(as, error))
|
|
return -EINVAL;
|
|
ret = dm_get_device(pcache->ti, dm_shift_arg(as),
|
|
BLK_OPEN_READ | BLK_OPEN_WRITE,
|
|
&pcache->cache_dev.dm_dev);
|
|
if (ret) {
|
|
*error = "Error opening cache device";
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int parse_backing_dev(struct dm_pcache *pcache, struct dm_arg_set *as,
|
|
char **error)
|
|
{
|
|
int ret;
|
|
|
|
if (!at_least_one_arg(as, error))
|
|
return -EINVAL;
|
|
|
|
ret = dm_get_device(pcache->ti, dm_shift_arg(as),
|
|
BLK_OPEN_READ | BLK_OPEN_WRITE,
|
|
&pcache->backing_dev.dm_dev);
|
|
if (ret) {
|
|
*error = "Error opening backing device";
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void pcache_init_opts(struct pcache_cache_options *opts)
|
|
{
|
|
opts->cache_mode = PCACHE_CACHE_MODE_WRITEBACK;
|
|
opts->data_crc = false;
|
|
}
|
|
|
|
static int parse_cache_opts(struct dm_pcache *pcache, struct dm_arg_set *as,
|
|
char **error)
|
|
{
|
|
struct pcache_cache_options *opts = &pcache->opts;
|
|
static const struct dm_arg _args[] = {
|
|
{0, 4, "Invalid number of cache option arguments"},
|
|
};
|
|
unsigned int argc;
|
|
const char *arg;
|
|
int ret;
|
|
|
|
pcache_init_opts(opts);
|
|
if (!as->argc)
|
|
return 0;
|
|
|
|
ret = dm_read_arg_group(_args, as, &argc, error);
|
|
if (ret)
|
|
return -EINVAL;
|
|
|
|
while (argc) {
|
|
arg = dm_shift_arg(as);
|
|
argc--;
|
|
|
|
if (!strcmp(arg, "cache_mode")) {
|
|
arg = dm_shift_arg(as);
|
|
if (!strcmp(arg, "writeback")) {
|
|
opts->cache_mode = PCACHE_CACHE_MODE_WRITEBACK;
|
|
} else {
|
|
*error = "Invalid cache mode parameter";
|
|
return -EINVAL;
|
|
}
|
|
argc--;
|
|
} else if (!strcmp(arg, "data_crc")) {
|
|
arg = dm_shift_arg(as);
|
|
if (!strcmp(arg, "true")) {
|
|
opts->data_crc = true;
|
|
} else if (!strcmp(arg, "false")) {
|
|
opts->data_crc = false;
|
|
} else {
|
|
*error = "Invalid data crc parameter";
|
|
return -EINVAL;
|
|
}
|
|
argc--;
|
|
} else {
|
|
*error = "Unrecognised cache option requested";
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int pcache_start(struct dm_pcache *pcache, char **error)
|
|
{
|
|
int ret;
|
|
|
|
ret = cache_dev_start(pcache);
|
|
if (ret) {
|
|
*error = "Failed to start cache dev";
|
|
return ret;
|
|
}
|
|
|
|
ret = backing_dev_start(pcache);
|
|
if (ret) {
|
|
*error = "Failed to start backing dev";
|
|
goto stop_cache;
|
|
}
|
|
|
|
ret = pcache_cache_start(pcache);
|
|
if (ret) {
|
|
*error = "Failed to start pcache";
|
|
goto stop_backing;
|
|
}
|
|
|
|
return 0;
|
|
stop_backing:
|
|
backing_dev_stop(pcache);
|
|
stop_cache:
|
|
cache_dev_stop(pcache);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void pcache_destroy_args(struct dm_pcache *pcache)
|
|
{
|
|
if (pcache->cache_dev.dm_dev)
|
|
dm_put_device(pcache->ti, pcache->cache_dev.dm_dev);
|
|
if (pcache->backing_dev.dm_dev)
|
|
dm_put_device(pcache->ti, pcache->backing_dev.dm_dev);
|
|
}
|
|
|
|
static int pcache_parse_args(struct dm_pcache *pcache, unsigned int argc, char **argv,
|
|
char **error)
|
|
{
|
|
struct dm_arg_set as;
|
|
int ret;
|
|
|
|
as.argc = argc;
|
|
as.argv = argv;
|
|
|
|
/*
|
|
* Parse cache device
|
|
*/
|
|
ret = parse_cache_dev(pcache, &as, error);
|
|
if (ret)
|
|
return ret;
|
|
/*
|
|
* Parse backing device
|
|
*/
|
|
ret = parse_backing_dev(pcache, &as, error);
|
|
if (ret)
|
|
goto out;
|
|
/*
|
|
* Parse optional arguments
|
|
*/
|
|
ret = parse_cache_opts(pcache, &as, error);
|
|
if (ret)
|
|
goto out;
|
|
|
|
return 0;
|
|
out:
|
|
pcache_destroy_args(pcache);
|
|
return ret;
|
|
}
|
|
|
|
static int dm_pcache_ctr(struct dm_target *ti, unsigned int argc, char **argv)
|
|
{
|
|
struct mapped_device *md = ti->table->md;
|
|
struct dm_pcache *pcache;
|
|
int ret;
|
|
|
|
if (md->map) {
|
|
ti->error = "Don't support table loading for live md";
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
/* Allocate memory for the cache structure */
|
|
pcache = kzalloc(sizeof(struct dm_pcache), GFP_KERNEL);
|
|
if (!pcache)
|
|
return -ENOMEM;
|
|
|
|
pcache->task_wq = alloc_workqueue("pcache-%s-wq", WQ_UNBOUND | WQ_MEM_RECLAIM,
|
|
0, md->name);
|
|
if (!pcache->task_wq) {
|
|
ret = -ENOMEM;
|
|
goto free_pcache;
|
|
}
|
|
|
|
spin_lock_init(&pcache->defered_req_list_lock);
|
|
INIT_LIST_HEAD(&pcache->defered_req_list);
|
|
INIT_WORK(&pcache->defered_req_work, defered_req_fn);
|
|
pcache->ti = ti;
|
|
|
|
ret = pcache_parse_args(pcache, argc, argv, &ti->error);
|
|
if (ret)
|
|
goto destroy_wq;
|
|
|
|
ret = pcache_start(pcache, &ti->error);
|
|
if (ret)
|
|
goto destroy_args;
|
|
|
|
ti->num_flush_bios = 1;
|
|
ti->flush_supported = true;
|
|
ti->per_io_data_size = sizeof(struct pcache_request);
|
|
ti->private = pcache;
|
|
atomic_set(&pcache->inflight_reqs, 0);
|
|
atomic_set(&pcache->state, PCACHE_STATE_RUNNING);
|
|
init_waitqueue_head(&pcache->inflight_wq);
|
|
|
|
return 0;
|
|
destroy_args:
|
|
pcache_destroy_args(pcache);
|
|
destroy_wq:
|
|
destroy_workqueue(pcache->task_wq);
|
|
free_pcache:
|
|
kfree(pcache);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void defer_req_stop(struct dm_pcache *pcache)
|
|
{
|
|
struct pcache_request *pcache_req;
|
|
LIST_HEAD(tmp_list);
|
|
|
|
flush_work(&pcache->defered_req_work);
|
|
|
|
spin_lock(&pcache->defered_req_list_lock);
|
|
list_splice_init(&pcache->defered_req_list, &tmp_list);
|
|
spin_unlock(&pcache->defered_req_list_lock);
|
|
|
|
while (!list_empty(&tmp_list)) {
|
|
pcache_req = list_first_entry(&tmp_list,
|
|
struct pcache_request, list_node);
|
|
list_del_init(&pcache_req->list_node);
|
|
pcache_req_put(pcache_req, -EIO);
|
|
}
|
|
}
|
|
|
|
static void dm_pcache_dtr(struct dm_target *ti)
|
|
{
|
|
struct dm_pcache *pcache;
|
|
|
|
pcache = ti->private;
|
|
atomic_set(&pcache->state, PCACHE_STATE_STOPPING);
|
|
defer_req_stop(pcache);
|
|
|
|
wait_event(pcache->inflight_wq,
|
|
atomic_read(&pcache->inflight_reqs) == 0);
|
|
|
|
pcache_cache_stop(pcache);
|
|
backing_dev_stop(pcache);
|
|
cache_dev_stop(pcache);
|
|
|
|
pcache_destroy_args(pcache);
|
|
drain_workqueue(pcache->task_wq);
|
|
destroy_workqueue(pcache->task_wq);
|
|
|
|
kfree(pcache);
|
|
}
|
|
|
|
static int dm_pcache_map_bio(struct dm_target *ti, struct bio *bio)
|
|
{
|
|
struct pcache_request *pcache_req = dm_per_bio_data(bio, sizeof(struct pcache_request));
|
|
struct dm_pcache *pcache = ti->private;
|
|
int ret;
|
|
|
|
pcache_req->pcache = pcache;
|
|
kref_init(&pcache_req->ref);
|
|
pcache_req->ret = 0;
|
|
pcache_req->bio = bio;
|
|
pcache_req->off = (u64)bio->bi_iter.bi_sector << SECTOR_SHIFT;
|
|
pcache_req->data_len = bio->bi_iter.bi_size;
|
|
INIT_LIST_HEAD(&pcache_req->list_node);
|
|
atomic_inc(&pcache->inflight_reqs);
|
|
|
|
ret = pcache_cache_handle_req(&pcache->cache, pcache_req);
|
|
if (ret == -EBUSY)
|
|
defer_req(pcache_req);
|
|
else
|
|
pcache_req_put(pcache_req, ret);
|
|
|
|
return DM_MAPIO_SUBMITTED;
|
|
}
|
|
|
|
static void dm_pcache_status(struct dm_target *ti, status_type_t type,
|
|
unsigned int status_flags, char *result,
|
|
unsigned int maxlen)
|
|
{
|
|
struct dm_pcache *pcache = ti->private;
|
|
struct pcache_cache_dev *cache_dev = &pcache->cache_dev;
|
|
struct pcache_backing_dev *backing_dev = &pcache->backing_dev;
|
|
struct pcache_cache *cache = &pcache->cache;
|
|
unsigned int sz = 0;
|
|
|
|
switch (type) {
|
|
case STATUSTYPE_INFO:
|
|
DMEMIT("%x %u %u %u %u %x %u:%u %u:%u %u:%u",
|
|
cache_dev->sb_flags,
|
|
cache_dev->seg_num,
|
|
cache->n_segs,
|
|
bitmap_weight(cache->seg_map, cache->n_segs),
|
|
pcache_cache_get_gc_percent(cache),
|
|
cache->cache_info.flags,
|
|
cache->key_head.cache_seg->cache_seg_id,
|
|
cache->key_head.seg_off,
|
|
cache->dirty_tail.cache_seg->cache_seg_id,
|
|
cache->dirty_tail.seg_off,
|
|
cache->key_tail.cache_seg->cache_seg_id,
|
|
cache->key_tail.seg_off);
|
|
break;
|
|
case STATUSTYPE_TABLE:
|
|
DMEMIT("%s %s 4 cache_mode writeback crc %s",
|
|
cache_dev->dm_dev->name,
|
|
backing_dev->dm_dev->name,
|
|
cache_data_crc_on(cache) ? "true" : "false");
|
|
break;
|
|
case STATUSTYPE_IMA:
|
|
*result = '\0';
|
|
break;
|
|
}
|
|
}
|
|
|
|
static int dm_pcache_message(struct dm_target *ti, unsigned int argc,
|
|
char **argv, char *result, unsigned int maxlen)
|
|
{
|
|
struct dm_pcache *pcache = ti->private;
|
|
unsigned long val;
|
|
|
|
if (argc != 2)
|
|
goto err;
|
|
|
|
if (!strcasecmp(argv[0], "gc_percent")) {
|
|
if (kstrtoul(argv[1], 10, &val))
|
|
goto err;
|
|
|
|
return pcache_cache_set_gc_percent(&pcache->cache, val);
|
|
}
|
|
err:
|
|
return -EINVAL;
|
|
}
|
|
|
|
static struct target_type dm_pcache_target = {
|
|
.name = "pcache",
|
|
.version = {0, 1, 0},
|
|
.module = THIS_MODULE,
|
|
.features = DM_TARGET_SINGLETON,
|
|
.ctr = dm_pcache_ctr,
|
|
.dtr = dm_pcache_dtr,
|
|
.map = dm_pcache_map_bio,
|
|
.status = dm_pcache_status,
|
|
.message = dm_pcache_message,
|
|
};
|
|
|
|
static int __init dm_pcache_init(void)
|
|
{
|
|
int ret;
|
|
|
|
ret = pcache_backing_init();
|
|
if (ret)
|
|
goto err;
|
|
|
|
ret = pcache_cache_init();
|
|
if (ret)
|
|
goto backing_exit;
|
|
|
|
ret = dm_register_target(&dm_pcache_target);
|
|
if (ret)
|
|
goto cache_exit;
|
|
return 0;
|
|
|
|
cache_exit:
|
|
pcache_cache_exit();
|
|
backing_exit:
|
|
pcache_backing_exit();
|
|
err:
|
|
return ret;
|
|
}
|
|
module_init(dm_pcache_init);
|
|
|
|
static void __exit dm_pcache_exit(void)
|
|
{
|
|
dm_unregister_target(&dm_pcache_target);
|
|
pcache_cache_exit();
|
|
pcache_backing_exit();
|
|
}
|
|
module_exit(dm_pcache_exit);
|
|
|
|
MODULE_DESCRIPTION("dm-pcache Persistent Cache for block device");
|
|
MODULE_AUTHOR("Dongsheng Yang <dongsheng.yang@linux.dev>");
|
|
MODULE_LICENSE("GPL");
|