Something went wrong. Try again.
Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506// 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")) { if (!argc) { *error = "Missing value for cache_mode"; return -EINVAL; } 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")) { if (!argc) { *error = "Missing value for data_crc"; return -EINVAL; } 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_obj(struct dm_pcache); 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");