dm-pcache: add persistent cache target in device-mapper

This patch introduces dm-pcache, a new DM target that places a DAX-
capable persistent-memory device in front of any slower block device and
uses it as a high-throughput, low-latency  cache.

Design highlights
-----------------
- DAX data path – data is copied directly between DRAM and the pmem
  mapping, bypassing the block layer’s overhead.

- Segmented, crash-consistent layout
  - all layout metadata are dual-replicated CRC-protected.
  - atomic kset flushes; key replay on mount guarantees cache integrity
    even after power loss.

- Striped multi-tree index
  - Multi‑tree indexing for high parallelism.
  - overlap-resolution logic ensures non-intersecting cached extents.

- Background services
  - write-back worker flushes dirty keys in order, preserving backing-device
    crash consistency. This is important for checkpoint in cloud storage.
  - garbage collector reclaims clean segments when utilisation exceeds a
    tunable threshold.

- Data integrity – optional CRC32 on cached payload; metadata always protected.

Comparison with existing block-level caches
---------------------------------------------------------------------------------------------------------------------------------
| Feature                          | pcache (this patch)             | bcache                       | dm-writecache             |
|----------------------------------|---------------------------------|------------------------------|---------------------------|
| pmem access method               | DAX                             | bio (block I/O)              | DAX                       |
| Write latency (4 K rand-write)   | ~5 µs                           | ~20 µs                       | ~5 µs                     |
| Concurrency                      | multi subtree index             | global index tree            | single tree + wc_lock     |
| IOPS (4K randwrite, 32 numjobs)  | 2.1 M                           | 352 K                        | 283 K                     |
| Read-cache support               | YES                             | YES                          | NO                        |
| Deployment                       | no re-format of backend         | backend devices must be      | no re-format of backend   |
|                                  |                                 | reformatted                  |                           |
| Write-back ordering              | log-structured;                 | no ordering guarantee        | no ordering guarantee     |
|                                  | preserves app-IO-order          |                              |                           |
| Data integrity checks            | metadata + data CRC(optional)   | metadata CRC only            | none                      |
---------------------------------------------------------------------------------------------------------------------------------

Signed-off-by: Dongsheng Yang <dongsheng.yang@linux.dev>
Signed-off-by: Mikulas Patocka <mpatocka@redhat.com>
This commit is contained in:
Dongsheng Yang
2025-08-25 15:25:29 +02:00
committed by Mikulas Patocka
parent 499cbe0f2f
commit 1d57628ff9
23 changed files with 5450 additions and 0 deletions
@@ -0,0 +1,202 @@
.. SPDX-License-Identifier: GPL-2.0
=================================
dm-pcache — Persistent Cache
=================================
*Author: Dongsheng Yang <dongsheng.yang@linux.dev>*
This document describes *dm-pcache*, a Device-Mapper target that lets a
byte-addressable *DAX* (persistent-memory, “pmem”) region act as a
high-performance, crash-persistent cache in front of a slower block
device. The code lives in `drivers/md/dm-pcache/`.
Quick feature summary
=====================
* *Write-back* caching (only mode currently supported).
* *16 MiB segments* allocated on the pmem device.
* *Data CRC32* verification (optional, per cache).
* Crash-safe: every metadata structure is duplicated (`PCACHE_META_INDEX_MAX
== 2`) and protected with CRC+sequence numbers.
* *Multi-tree indexing* (indexing trees sharded by logical address) for high PMem parallelism
* Pure *DAX path* I/O no extra BIO round-trips
* *Log-structured write-back* that preserves backend crash-consistency
Constructor
===========
::
pcache <cache_dev> <backing_dev> [<number_of_optional_arguments> <cache_mode writeback> <data_crc true|false>]
========================= ====================================================
``cache_dev`` Any DAX-capable block device (``/dev/pmem0``…).
All metadata *and* cached blocks are stored here.
``backing_dev`` The slow block device to be cached.
``cache_mode`` Optional, Only ``writeback`` is accepted at the
moment.
``data_crc`` Optional, default to ``false``
* ``true`` store CRC32 for every cached entry
and verify on reads
* ``false`` skip CRC (faster)
========================= ====================================================
Example
-------
.. code-block:: shell
dmsetup create pcache_sdb --table \
"0 $(blockdev --getsz /dev/sdb) pcache /dev/pmem0 /dev/sdb 4 cache_mode writeback data_crc true"
The first time a pmem device is used, dm-pcache formats it automatically
(super-block, cache_info, etc.).
Status line
===========
``dmsetup status <device>`` (``STATUSTYPE_INFO``) prints:
::
<sb_flags> <seg_total> <cache_segs> <segs_used> \
<gc_percent> <cache_flags> \
<key_head_seg>:<key_head_off> \
<dirty_tail_seg>:<dirty_tail_off> \
<key_tail_seg>:<key_tail_off>
Field meanings
--------------
=============================== =============================================
``sb_flags`` Super-block flags (e.g. endian marker).
``seg_total`` Number of physical *pmem* segments.
``cache_segs`` Number of segments used for cache.
``segs_used`` Segments currently allocated (bitmap weight).
``gc_percent`` Current GC high-water mark (0-90).
``cache_flags`` Bit 0 DATA_CRC enabled
Bit 1 INIT_DONE (cache initialised)
Bits 2-5 cache mode (0 == WB).
``key_head`` Where new key-sets are being written.
``dirty_tail`` First dirty key-set that still needs
write-back to the backing device.
``key_tail`` First key-set that may be reclaimed by GC.
=============================== =============================================
Messages
========
*Change GC trigger*
::
dmsetup message <dev> 0 gc_percent <0-90>
Theory of operation
===================
Sub-devices
-----------
==================== =========================================================
backing_dev Any block device (SSD/HDD/loop/LVM, etc.).
cache_dev DAX device; must expose direct-access memory.
==================== =========================================================
Segments and key-sets
---------------------
* The pmem space is divided into *16 MiB segments*.
* Each write allocates space from a per-CPU *data_head* inside a segment.
* A *cache-key* records a logical range on the origin and where it lives
inside pmem (segment + offset + generation).
* 128 keys form a *key-set* (kset); ksets are written sequentially in pmem
and are themselves crash-safe (CRC).
* The pair *(key_tail, dirty_tail)* delimit clean/dirty and live/dead ksets.
Write-back
----------
Dirty keys are queued into a tree; a background worker copies data
back to the backing_dev and advances *dirty_tail*. A FLUSH/FUA bio from the
upper layers forces an immediate metadata commit.
Garbage collection
------------------
GC starts when ``segs_used >= seg_total * gc_percent / 100``. It walks
from *key_tail*, frees segments whose every key has been invalidated, and
advances *key_tail*.
CRC verification
----------------
If ``data_crc is enabled`` dm-pcache computes a CRC32 over every cached data
range when it is inserted and stores it in the on-media key. Reads
validate the CRC before copying to the caller.
Failure handling
================
* *pmem media errors* all metadata copies are read with
``copy_mc_to_kernel``; an uncorrectable error logs and aborts initialisation.
* *Cache full* if no free segment can be found, writes return ``-EBUSY``;
dm-pcache retries internally (request deferral).
* *System crash* on attach, the driver replays ksets from *key_tail* to
rebuild the in-core trees; every segments generation guards against
use-after-free keys.
Limitations & TODO
==================
* Only *write-back* mode; other modes planned.
* Only FIFO cache invalidate; other (LRU, ARC...) planned.
* Table reload is not supported currently.
* Discard planned.
Example workflow
================
.. code-block:: shell
# 1. Create devices
dmsetup create pcache_sdb --table \
"0 $(blockdev --getsz /dev/sdb) pcache /dev/pmem0 /dev/sdb 4 cache_mode writeback data_crc true"
# 2. Put a filesystem on top
mkfs.ext4 /dev/mapper/pcache_sdb
mount /dev/mapper/pcache_sdb /mnt
# 3. Tune GC threshold to 80 %
dmsetup message pcache_sdb 0 gc_percent 80
# 4. Observe status
watch -n1 'dmsetup status pcache_sdb'
# 5. Shutdown
umount /mnt
dmsetup remove pcache_sdb
``dm-pcache`` is under active development; feedback, bug reports and patches
are very welcome!
@@ -18,6 +18,7 @@ Device Mapper
dm-integrity
dm-io
dm-log
dm-pcache
dm-queue-length
dm-raid
dm-service-time
+8
View File
@@ -7051,6 +7051,14 @@ S: Maintained
F: Documentation/admin-guide/device-mapper/vdo*.rst
F: drivers/md/dm-vdo/
DEVICE-MAPPER PCACHE TARGET
M: Dongsheng Yang <dongsheng.yang@linux.dev>
M: Zheng Gu <cengku@gmail.com>
L: dm-devel@lists.linux.dev
S: Maintained
F: Documentation/admin-guide/device-mapper/dm-pcache.rst
F: drivers/md/dm-pcache/
DEVLINK
M: Jiri Pirko <jiri@resnulli.us>
L: netdev@vger.kernel.org
+2
View File
@@ -659,4 +659,6 @@ config DM_AUDIT
source "drivers/md/dm-vdo/Kconfig"
source "drivers/md/dm-pcache/Kconfig"
endif # MD
+1
View File
@@ -71,6 +71,7 @@ obj-$(CONFIG_DM_RAID) += dm-raid.o
obj-$(CONFIG_DM_THIN_PROVISIONING) += dm-thin-pool.o
obj-$(CONFIG_DM_VERITY) += dm-verity.o
obj-$(CONFIG_DM_VDO) += dm-vdo/
obj-$(CONFIG_DM_PCACHE) += dm-pcache/
obj-$(CONFIG_DM_CACHE) += dm-cache.o
obj-$(CONFIG_DM_CACHE_SMQ) += dm-cache-smq.o
obj-$(CONFIG_DM_EBS) += dm-ebs.o
+17
View File
@@ -0,0 +1,17 @@
config DM_PCACHE
tristate "Persistent cache for Block Device (Experimental)"
depends on BLK_DEV_DM
depends on DEV_DAX
help
PCACHE provides a mechanism to use persistent memory (e.g., CXL persistent memory,
DAX-enabled devices) as a high-performance cache layer in front of
traditional block devices such as SSDs or HDDs.
PCACHE is implemented as a kernel module that integrates with the block
layer and supports direct access (DAX) to persistent memory for low-latency,
byte-addressable caching.
Note: This feature is experimental and should be tested thoroughly
before use in production environments.
If unsure, say 'N'.
+3
View File
@@ -0,0 +1,3 @@
dm-pcache-y := dm_pcache.o cache_dev.o segment.o backing_dev.o cache.o cache_gc.o cache_writeback.o cache_segment.o cache_key.o cache_req.o
obj-m += dm-pcache.o
+374
View File
@@ -0,0 +1,374 @@
// 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);
}
+127
View File
@@ -0,0 +1,127 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#ifndef _BACKING_DEV_H
#define _BACKING_DEV_H
#include <linux/device-mapper.h>
#include "pcache_internal.h"
struct pcache_backing_dev_req;
typedef void (*backing_req_end_fn_t)(struct pcache_backing_dev_req *backing_req, int ret);
#define BACKING_DEV_REQ_TYPE_REQ 1
#define BACKING_DEV_REQ_TYPE_KMEM 2
#define BACKING_DEV_REQ_INLINE_BVECS 4
struct pcache_request;
struct pcache_backing_dev_req {
u8 type;
struct bio bio;
struct pcache_backing_dev *backing_dev;
void *priv_data;
backing_req_end_fn_t end_req;
struct list_head node;
int ret;
union {
struct {
struct pcache_request *upper_req;
u32 bio_off;
} req;
struct {
struct bio_vec inline_bvecs[BACKING_DEV_REQ_INLINE_BVECS];
struct bio_vec *bvecs;
u32 n_vecs;
} kmem;
};
};
struct pcache_backing_dev {
struct pcache_cache *cache;
struct dm_dev *dm_dev;
mempool_t req_pool;
mempool_t bvec_pool;
struct list_head submit_list;
spinlock_t submit_lock;
struct work_struct req_submit_work;
struct list_head complete_list;
spinlock_t complete_lock;
struct work_struct req_complete_work;
atomic_t inflight_reqs;
wait_queue_head_t inflight_wq;
u64 dev_size;
};
struct dm_pcache;
int backing_dev_start(struct dm_pcache *pcache);
void backing_dev_stop(struct dm_pcache *pcache);
struct pcache_backing_dev_req_opts {
u32 type;
union {
struct {
struct pcache_request *upper_req;
u32 req_off;
u32 len;
} req;
struct {
void *data;
blk_opf_t opf;
u32 len;
u64 backing_off;
} kmem;
};
gfp_t gfp_mask;
backing_req_end_fn_t end_fn;
void *priv_data;
};
static inline u32 backing_dev_req_coalesced_max_len(const void *data, u32 len)
{
const void *p = data;
u32 done = 0, in_page, to_advance;
struct page *first_page, *next_page;
if (!is_vmalloc_addr(data))
return len;
first_page = vmalloc_to_page(p);
advance:
in_page = PAGE_SIZE - offset_in_page(p);
to_advance = min_t(u32, in_page, len - done);
done += to_advance;
p += to_advance;
if (done == len)
return done;
next_page = vmalloc_to_page(p);
if (zone_device_pages_have_same_pgmap(first_page, next_page))
goto advance;
return done;
}
void backing_dev_req_submit(struct pcache_backing_dev_req *backing_req, bool direct);
void backing_dev_req_end(struct pcache_backing_dev_req *backing_req);
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_dev_req_alloc(struct pcache_backing_dev *backing_dev,
struct pcache_backing_dev_req_opts *opts);
void backing_dev_req_init(struct pcache_backing_dev_req *backing_req,
struct pcache_backing_dev_req_opts *opts);
void backing_dev_flush(struct pcache_backing_dev *backing_dev);
int pcache_backing_init(void);
void pcache_backing_exit(void);
#endif /* _BACKING_DEV_H */
+445
View File
@@ -0,0 +1,445 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include <linux/blk_types.h>
#include "cache.h"
#include "cache_dev.h"
#include "backing_dev.h"
#include "dm_pcache.h"
struct kmem_cache *key_cache;
static inline struct pcache_cache_info *get_cache_info_addr(struct pcache_cache *cache)
{
return cache->cache_info_addr + cache->info_index;
}
static void cache_info_write(struct pcache_cache *cache)
{
struct pcache_cache_info *cache_info = &cache->cache_info;
cache_info->header.seq++;
cache_info->header.crc = pcache_meta_crc(&cache_info->header,
sizeof(struct pcache_cache_info));
memcpy_flushcache(get_cache_info_addr(cache), cache_info,
sizeof(struct pcache_cache_info));
cache->info_index = (cache->info_index + 1) % PCACHE_META_INDEX_MAX;
}
static void cache_info_init_default(struct pcache_cache *cache);
static int cache_info_init(struct pcache_cache *cache, struct pcache_cache_options *opts)
{
struct dm_pcache *pcache = CACHE_TO_PCACHE(cache);
struct pcache_cache_info *cache_info_addr;
cache_info_addr = pcache_meta_find_latest(&cache->cache_info_addr->header,
sizeof(struct pcache_cache_info),
PCACHE_CACHE_INFO_SIZE,
&cache->cache_info);
if (IS_ERR(cache_info_addr))
return PTR_ERR(cache_info_addr);
if (cache_info_addr) {
if (opts->data_crc !=
(cache->cache_info.flags & PCACHE_CACHE_FLAGS_DATA_CRC)) {
pcache_dev_err(pcache, "invalid option for data_crc: %s, expected: %s",
opts->data_crc ? "true" : "false",
cache->cache_info.flags & PCACHE_CACHE_FLAGS_DATA_CRC ? "true" : "false");
return -EINVAL;
}
return 0;
}
/* init cache_info for new cache */
cache_info_init_default(cache);
cache_mode_set(cache, opts->cache_mode);
if (opts->data_crc)
cache->cache_info.flags |= PCACHE_CACHE_FLAGS_DATA_CRC;
return 0;
}
static void cache_info_set_gc_percent(struct pcache_cache_info *cache_info, u8 percent)
{
cache_info->flags &= ~PCACHE_CACHE_FLAGS_GC_PERCENT_MASK;
cache_info->flags |= FIELD_PREP(PCACHE_CACHE_FLAGS_GC_PERCENT_MASK, percent);
}
int pcache_cache_set_gc_percent(struct pcache_cache *cache, u8 percent)
{
if (percent > PCACHE_CACHE_GC_PERCENT_MAX || percent < PCACHE_CACHE_GC_PERCENT_MIN)
return -EINVAL;
mutex_lock(&cache->cache_info_lock);
cache_info_set_gc_percent(&cache->cache_info, percent);
cache_info_write(cache);
mutex_unlock(&cache->cache_info_lock);
return 0;
}
void cache_pos_encode(struct pcache_cache *cache,
struct pcache_cache_pos_onmedia *pos_onmedia_base,
struct pcache_cache_pos *pos, u64 seq, u32 *index)
{
struct pcache_cache_pos_onmedia pos_onmedia;
struct pcache_cache_pos_onmedia *pos_onmedia_addr = pos_onmedia_base + *index;
pos_onmedia.cache_seg_id = pos->cache_seg->cache_seg_id;
pos_onmedia.seg_off = pos->seg_off;
pos_onmedia.header.seq = seq;
pos_onmedia.header.crc = cache_pos_onmedia_crc(&pos_onmedia);
memcpy_flushcache(pos_onmedia_addr, &pos_onmedia, sizeof(struct pcache_cache_pos_onmedia));
pmem_wmb();
*index = (*index + 1) % PCACHE_META_INDEX_MAX;
}
int cache_pos_decode(struct pcache_cache *cache,
struct pcache_cache_pos_onmedia *pos_onmedia,
struct pcache_cache_pos *pos, u64 *seq, u32 *index)
{
struct pcache_cache_pos_onmedia latest, *latest_addr;
latest_addr = pcache_meta_find_latest(&pos_onmedia->header,
sizeof(struct pcache_cache_pos_onmedia),
sizeof(struct pcache_cache_pos_onmedia),
&latest);
if (IS_ERR(latest_addr))
return PTR_ERR(latest_addr);
if (!latest_addr)
return -EIO;
pos->cache_seg = &cache->segments[latest.cache_seg_id];
pos->seg_off = latest.seg_off;
*seq = latest.header.seq;
*index = (latest_addr - pos_onmedia);
return 0;
}
static inline void cache_info_set_seg_id(struct pcache_cache *cache, u32 seg_id)
{
cache->cache_info.seg_id = seg_id;
}
static int cache_init(struct dm_pcache *pcache)
{
struct pcache_cache *cache = &pcache->cache;
struct pcache_backing_dev *backing_dev = &pcache->backing_dev;
struct pcache_cache_dev *cache_dev = &pcache->cache_dev;
int ret;
cache->segments = kvcalloc(cache_dev->seg_num, sizeof(struct pcache_cache_segment), GFP_KERNEL);
if (!cache->segments) {
ret = -ENOMEM;
goto err;
}
cache->seg_map = kvcalloc(BITS_TO_LONGS(cache_dev->seg_num), sizeof(unsigned long), GFP_KERNEL);
if (!cache->seg_map) {
ret = -ENOMEM;
goto free_segments;
}
cache->backing_dev = backing_dev;
cache->cache_dev = &pcache->cache_dev;
cache->n_segs = cache_dev->seg_num;
atomic_set(&cache->gc_errors, 0);
spin_lock_init(&cache->seg_map_lock);
spin_lock_init(&cache->key_head_lock);
mutex_init(&cache->cache_info_lock);
mutex_init(&cache->key_tail_lock);
mutex_init(&cache->dirty_tail_lock);
mutex_init(&cache->writeback_lock);
INIT_DELAYED_WORK(&cache->writeback_work, cache_writeback_fn);
INIT_DELAYED_WORK(&cache->gc_work, pcache_cache_gc_fn);
INIT_WORK(&cache->clean_work, clean_fn);
return 0;
free_segments:
kvfree(cache->segments);
err:
return ret;
}
static void cache_exit(struct pcache_cache *cache)
{
kvfree(cache->seg_map);
kvfree(cache->segments);
}
static void cache_info_init_default(struct pcache_cache *cache)
{
struct pcache_cache_info *cache_info = &cache->cache_info;
cache_info->header.seq = 0;
cache_info->n_segs = cache->cache_dev->seg_num;
cache_info_set_gc_percent(cache_info, PCACHE_CACHE_GC_PERCENT_DEFAULT);
}
static int cache_tail_init(struct pcache_cache *cache)
{
struct dm_pcache *pcache = CACHE_TO_PCACHE(cache);
bool new_cache = !(cache->cache_info.flags & PCACHE_CACHE_FLAGS_INIT_DONE);
if (new_cache) {
__set_bit(0, cache->seg_map);
cache->key_head.cache_seg = &cache->segments[0];
cache->key_head.seg_off = 0;
cache_pos_copy(&cache->key_tail, &cache->key_head);
cache_pos_copy(&cache->dirty_tail, &cache->key_head);
cache_encode_dirty_tail(cache);
cache_encode_key_tail(cache);
} else {
if (cache_decode_key_tail(cache) || cache_decode_dirty_tail(cache)) {
pcache_dev_err(pcache, "Corrupted key tail or dirty tail.\n");
return -EIO;
}
}
return 0;
}
static int get_seg_id(struct pcache_cache *cache,
struct pcache_cache_segment *prev_cache_seg,
bool new_cache, u32 *seg_id)
{
struct dm_pcache *pcache = CACHE_TO_PCACHE(cache);
struct pcache_cache_dev *cache_dev = cache->cache_dev;
int ret;
if (new_cache) {
ret = cache_dev_get_empty_segment_id(cache_dev, seg_id);
if (ret) {
pcache_dev_err(pcache, "no available segment\n");
goto err;
}
if (prev_cache_seg)
cache_seg_set_next_seg(prev_cache_seg, *seg_id);
else
cache_info_set_seg_id(cache, *seg_id);
} else {
if (prev_cache_seg) {
struct pcache_segment_info *prev_seg_info;
prev_seg_info = &prev_cache_seg->cache_seg_info;
if (!segment_info_has_next(prev_seg_info)) {
ret = -EFAULT;
goto err;
}
*seg_id = prev_cache_seg->cache_seg_info.next_seg;
} else {
*seg_id = cache->cache_info.seg_id;
}
}
return 0;
err:
return ret;
}
static int cache_segs_init(struct pcache_cache *cache)
{
struct pcache_cache_segment *prev_cache_seg = NULL;
struct pcache_cache_info *cache_info = &cache->cache_info;
bool new_cache = !(cache->cache_info.flags & PCACHE_CACHE_FLAGS_INIT_DONE);
u32 seg_id;
int ret;
u32 i;
for (i = 0; i < cache_info->n_segs; i++) {
ret = get_seg_id(cache, prev_cache_seg, new_cache, &seg_id);
if (ret)
goto err;
ret = cache_seg_init(cache, seg_id, i, new_cache);
if (ret)
goto err;
prev_cache_seg = &cache->segments[i];
}
return 0;
err:
return ret;
}
static int cache_init_req_keys(struct pcache_cache *cache, u32 n_paral)
{
struct dm_pcache *pcache = CACHE_TO_PCACHE(cache);
u32 n_subtrees;
int ret;
u32 i, cpu;
/* Calculate number of cache trees based on the device size */
n_subtrees = DIV_ROUND_UP(cache->dev_size << SECTOR_SHIFT, PCACHE_CACHE_SUBTREE_SIZE);
ret = cache_tree_init(cache, &cache->req_key_tree, n_subtrees);
if (ret)
goto err;
cache->n_ksets = n_paral;
cache->ksets = kvcalloc(cache->n_ksets, PCACHE_KSET_SIZE, GFP_KERNEL);
if (!cache->ksets) {
ret = -ENOMEM;
goto req_tree_exit;
}
/*
* Initialize each kset with a spinlock and delayed work for flushing.
* Each kset is associated with one queue to ensure independent handling
* of cache keys across multiple queues, maximizing multiqueue concurrency.
*/
for (i = 0; i < cache->n_ksets; i++) {
struct pcache_cache_kset *kset = get_kset(cache, i);
kset->cache = cache;
spin_lock_init(&kset->kset_lock);
INIT_DELAYED_WORK(&kset->flush_work, kset_flush_fn);
}
cache->data_heads = alloc_percpu(struct pcache_cache_data_head);
if (!cache->data_heads) {
ret = -ENOMEM;
goto free_kset;
}
for_each_possible_cpu(cpu) {
struct pcache_cache_data_head *h =
per_cpu_ptr(cache->data_heads, cpu);
h->head_pos.cache_seg = NULL;
}
/*
* Replay persisted cache keys using cache_replay.
* This function loads and replays cache keys from previously stored
* ksets, allowing the cache to restore its state after a restart.
*/
ret = cache_replay(cache);
if (ret) {
pcache_dev_err(pcache, "failed to replay keys\n");
goto free_heads;
}
return 0;
free_heads:
free_percpu(cache->data_heads);
free_kset:
kvfree(cache->ksets);
req_tree_exit:
cache_tree_exit(&cache->req_key_tree);
err:
return ret;
}
static void cache_destroy_req_keys(struct pcache_cache *cache)
{
u32 i;
for (i = 0; i < cache->n_ksets; i++) {
struct pcache_cache_kset *kset = get_kset(cache, i);
cancel_delayed_work_sync(&kset->flush_work);
}
free_percpu(cache->data_heads);
kvfree(cache->ksets);
cache_tree_exit(&cache->req_key_tree);
}
int pcache_cache_start(struct dm_pcache *pcache)
{
struct pcache_backing_dev *backing_dev = &pcache->backing_dev;
struct pcache_cache *cache = &pcache->cache;
struct pcache_cache_options *opts = &pcache->opts;
int ret;
ret = cache_init(pcache);
if (ret)
return ret;
cache->cache_info_addr = CACHE_DEV_CACHE_INFO(cache->cache_dev);
cache->cache_ctrl = CACHE_DEV_CACHE_CTRL(cache->cache_dev);
backing_dev->cache = cache;
cache->dev_size = backing_dev->dev_size;
ret = cache_info_init(cache, opts);
if (ret)
goto cache_exit;
ret = cache_segs_init(cache);
if (ret)
goto cache_exit;
ret = cache_tail_init(cache);
if (ret)
goto cache_exit;
ret = cache_init_req_keys(cache, num_online_cpus());
if (ret)
goto cache_exit;
ret = cache_writeback_init(cache);
if (ret)
goto destroy_keys;
cache->cache_info.flags |= PCACHE_CACHE_FLAGS_INIT_DONE;
cache_info_write(cache);
queue_delayed_work(cache_get_wq(cache), &cache->gc_work, 0);
return 0;
destroy_keys:
cache_destroy_req_keys(cache);
cache_exit:
cache_exit(cache);
return ret;
}
void pcache_cache_stop(struct dm_pcache *pcache)
{
struct pcache_cache *cache = &pcache->cache;
cache_flush(cache);
cancel_delayed_work_sync(&cache->gc_work);
flush_work(&cache->clean_work);
cache_writeback_exit(cache);
if (cache->req_key_tree.n_subtrees)
cache_destroy_req_keys(cache);
cache_exit(cache);
}
struct workqueue_struct *cache_get_wq(struct pcache_cache *cache)
{
struct dm_pcache *pcache = CACHE_TO_PCACHE(cache);
return pcache->task_wq;
}
int pcache_cache_init(void)
{
key_cache = KMEM_CACHE(pcache_cache_key, 0);
if (!key_cache)
return -ENOMEM;
return 0;
}
void pcache_cache_exit(void)
{
kmem_cache_destroy(key_cache);
}
File diff suppressed because it is too large Load Diff
+303
View File
@@ -0,0 +1,303 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include <linux/blkdev.h>
#include <linux/dax.h>
#include <linux/vmalloc.h>
#include <linux/parser.h>
#include "cache_dev.h"
#include "backing_dev.h"
#include "cache.h"
#include "dm_pcache.h"
static void cache_dev_dax_exit(struct pcache_cache_dev *cache_dev)
{
if (cache_dev->use_vmap)
vunmap(cache_dev->mapping);
}
static int build_vmap(struct dax_device *dax_dev, long total_pages, void **vaddr)
{
struct page **pages;
long i = 0, chunk;
unsigned long pfn;
int ret;
pages = vmalloc_array(total_pages, sizeof(struct page *));
if (!pages)
return -ENOMEM;
do {
chunk = dax_direct_access(dax_dev, i, total_pages - i,
DAX_ACCESS, NULL, &pfn);
if (chunk <= 0) {
ret = chunk ? chunk : -EINVAL;
goto out_free;
}
if (!pfn_valid(pfn)) {
ret = -EOPNOTSUPP;
goto out_free;
}
while (chunk-- && i < total_pages) {
pages[i++] = pfn_to_page(pfn);
pfn++;
if (!(i & 15))
cond_resched();
}
} while (i < total_pages);
*vaddr = vmap(pages, total_pages, VM_MAP, PAGE_KERNEL);
if (!*vaddr) {
ret = -ENOMEM;
goto out_free;
}
ret = 0;
out_free:
vfree(pages);
return ret;
}
static int cache_dev_dax_init(struct pcache_cache_dev *cache_dev)
{
struct dm_pcache *pcache = CACHE_DEV_TO_PCACHE(cache_dev);
struct dax_device *dax_dev;
long total_pages, mapped_pages;
u64 bdev_size;
void *vaddr;
int ret;
int id;
unsigned long pfn;
dax_dev = cache_dev->dm_dev->dax_dev;
/* total size check */
bdev_size = bdev_nr_bytes(cache_dev->dm_dev->bdev);
if (bdev_size < PCACHE_CACHE_DEV_SIZE_MIN) {
pcache_dev_err(pcache, "dax device is too small, required at least %llu",
PCACHE_CACHE_DEV_SIZE_MIN);
ret = -ENOSPC;
goto out;
}
total_pages = bdev_size >> PAGE_SHIFT;
/* attempt: direct-map the whole range */
id = dax_read_lock();
mapped_pages = dax_direct_access(dax_dev, 0, total_pages,
DAX_ACCESS, &vaddr, &pfn);
if (mapped_pages < 0) {
pcache_dev_err(pcache, "dax_direct_access failed: %ld\n", mapped_pages);
ret = mapped_pages;
goto unlock;
}
if (!pfn_valid(pfn)) {
ret = -EOPNOTSUPP;
goto unlock;
}
if (mapped_pages == total_pages) {
/* success: contiguous direct mapping */
cache_dev->mapping = vaddr;
} else {
/* need vmap fallback */
ret = build_vmap(dax_dev, total_pages, &vaddr);
if (ret) {
pcache_dev_err(pcache, "vmap fallback failed: %d\n", ret);
goto unlock;
}
cache_dev->mapping = vaddr;
cache_dev->use_vmap = true;
}
dax_read_unlock(id);
return 0;
unlock:
dax_read_unlock(id);
out:
return ret;
}
void cache_dev_zero_range(struct pcache_cache_dev *cache_dev, void *pos, u32 size)
{
memset(pos, 0, size);
dax_flush(cache_dev->dm_dev->dax_dev, pos, size);
}
static int sb_read(struct pcache_cache_dev *cache_dev, struct pcache_sb *sb)
{
struct pcache_sb *sb_addr = CACHE_DEV_SB(cache_dev);
if (copy_mc_to_kernel(sb, sb_addr, sizeof(struct pcache_sb)))
return -EIO;
return 0;
}
static void sb_write(struct pcache_cache_dev *cache_dev, struct pcache_sb *sb)
{
struct pcache_sb *sb_addr = CACHE_DEV_SB(cache_dev);
memcpy_flushcache(sb_addr, sb, sizeof(struct pcache_sb));
pmem_wmb();
}
static int sb_init(struct pcache_cache_dev *cache_dev, struct pcache_sb *sb)
{
struct dm_pcache *pcache = CACHE_DEV_TO_PCACHE(cache_dev);
u64 nr_segs;
u64 cache_dev_size;
u64 magic;
u32 flags = 0;
magic = le64_to_cpu(sb->magic);
if (magic)
return -EEXIST;
cache_dev_size = bdev_nr_bytes(file_bdev(cache_dev->dm_dev->bdev_file));
if (cache_dev_size < PCACHE_CACHE_DEV_SIZE_MIN) {
pcache_dev_err(pcache, "dax device is too small, required at least %llu",
PCACHE_CACHE_DEV_SIZE_MIN);
return -ENOSPC;
}
nr_segs = (cache_dev_size - PCACHE_SEGMENTS_OFF) / ((PCACHE_SEG_SIZE));
#if defined(__BYTE_ORDER) ? (__BIG_ENDIAN == __BYTE_ORDER) : defined(__BIG_ENDIAN)
flags |= PCACHE_SB_F_BIGENDIAN;
#endif
sb->flags = cpu_to_le32(flags);
sb->magic = cpu_to_le64(PCACHE_MAGIC);
sb->seg_num = cpu_to_le32(nr_segs);
sb->crc = cpu_to_le32(crc32c(PCACHE_CRC_SEED, (void *)(sb) + 4, sizeof(struct pcache_sb) - 4));
cache_dev_zero_range(cache_dev, CACHE_DEV_CACHE_INFO(cache_dev),
PCACHE_CACHE_INFO_SIZE * PCACHE_META_INDEX_MAX +
PCACHE_CACHE_CTRL_SIZE);
return 0;
}
static int sb_validate(struct pcache_cache_dev *cache_dev, struct pcache_sb *sb)
{
struct dm_pcache *pcache = CACHE_DEV_TO_PCACHE(cache_dev);
u32 flags;
u32 crc;
if (le64_to_cpu(sb->magic) != PCACHE_MAGIC) {
pcache_dev_err(pcache, "unexpected magic: %llx\n",
le64_to_cpu(sb->magic));
return -EINVAL;
}
crc = crc32c(PCACHE_CRC_SEED, (void *)(sb) + 4, sizeof(struct pcache_sb) - 4);
if (crc != le32_to_cpu(sb->crc)) {
pcache_dev_err(pcache, "corrupted sb: %u, expected: %u\n", crc, le32_to_cpu(sb->crc));
return -EINVAL;
}
flags = le32_to_cpu(sb->flags);
#if defined(__BYTE_ORDER) ? (__BIG_ENDIAN == __BYTE_ORDER) : defined(__BIG_ENDIAN)
if (!(flags & PCACHE_SB_F_BIGENDIAN)) {
pcache_dev_err(pcache, "cache_dev is not big endian\n");
return -EINVAL;
}
#else
if (flags & PCACHE_SB_F_BIGENDIAN) {
pcache_dev_err(pcache, "cache_dev is big endian\n");
return -EINVAL;
}
#endif
return 0;
}
static int cache_dev_init(struct pcache_cache_dev *cache_dev, u32 seg_num)
{
cache_dev->seg_num = seg_num;
cache_dev->seg_bitmap = kvcalloc(BITS_TO_LONGS(cache_dev->seg_num), sizeof(unsigned long), GFP_KERNEL);
if (!cache_dev->seg_bitmap)
return -ENOMEM;
return 0;
}
static void cache_dev_exit(struct pcache_cache_dev *cache_dev)
{
kvfree(cache_dev->seg_bitmap);
}
void cache_dev_stop(struct dm_pcache *pcache)
{
struct pcache_cache_dev *cache_dev = &pcache->cache_dev;
cache_dev_exit(cache_dev);
cache_dev_dax_exit(cache_dev);
}
int cache_dev_start(struct dm_pcache *pcache)
{
struct pcache_cache_dev *cache_dev = &pcache->cache_dev;
struct pcache_sb sb;
bool format = false;
int ret;
mutex_init(&cache_dev->seg_lock);
ret = cache_dev_dax_init(cache_dev);
if (ret) {
pcache_dev_err(pcache, "failed to init cache_dev %s via dax way: %d.",
cache_dev->dm_dev->name, ret);
goto err;
}
ret = sb_read(cache_dev, &sb);
if (ret)
goto dax_release;
if (le64_to_cpu(sb.magic) == 0) {
format = true;
ret = sb_init(cache_dev, &sb);
if (ret < 0)
goto dax_release;
}
ret = sb_validate(cache_dev, &sb);
if (ret)
goto dax_release;
cache_dev->sb_flags = le32_to_cpu(sb.flags);
ret = cache_dev_init(cache_dev, le32_to_cpu(sb.seg_num));
if (ret)
goto dax_release;
if (format)
sb_write(cache_dev, &sb);
return 0;
dax_release:
cache_dev_dax_exit(cache_dev);
err:
return ret;
}
int cache_dev_get_empty_segment_id(struct pcache_cache_dev *cache_dev, u32 *seg_id)
{
int ret;
mutex_lock(&cache_dev->seg_lock);
*seg_id = find_next_zero_bit(cache_dev->seg_bitmap, cache_dev->seg_num, 0);
if (*seg_id == cache_dev->seg_num) {
ret = -ENOSPC;
goto unlock;
}
__set_bit(*seg_id, cache_dev->seg_bitmap);
ret = 0;
unlock:
mutex_unlock(&cache_dev->seg_lock);
return ret;
}
+70
View File
@@ -0,0 +1,70 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#ifndef _PCACHE_CACHE_DEV_H
#define _PCACHE_CACHE_DEV_H
#include <linux/device.h>
#include <linux/device-mapper.h>
#include "pcache_internal.h"
#define PCACHE_MAGIC 0x65B05EFA96C596EFULL
#define PCACHE_SB_OFF (4 * PCACHE_KB)
#define PCACHE_SB_SIZE (4 * PCACHE_KB)
#define PCACHE_CACHE_INFO_OFF (PCACHE_SB_OFF + PCACHE_SB_SIZE)
#define PCACHE_CACHE_INFO_SIZE (4 * PCACHE_KB)
#define PCACHE_CACHE_CTRL_OFF (PCACHE_CACHE_INFO_OFF + (PCACHE_CACHE_INFO_SIZE * PCACHE_META_INDEX_MAX))
#define PCACHE_CACHE_CTRL_SIZE (4 * PCACHE_KB)
#define PCACHE_SEGMENTS_OFF (PCACHE_CACHE_CTRL_OFF + PCACHE_CACHE_CTRL_SIZE)
#define PCACHE_SEG_INFO_SIZE (4 * PCACHE_KB)
#define PCACHE_CACHE_DEV_SIZE_MIN (512 * PCACHE_MB) /* 512 MB */
#define PCACHE_SEG_SIZE (16 * PCACHE_MB) /* Size of each PCACHE segment (16 MB) */
#define CACHE_DEV_SB(cache_dev) ((struct pcache_sb *)(cache_dev->mapping + PCACHE_SB_OFF))
#define CACHE_DEV_CACHE_INFO(cache_dev) ((void *)cache_dev->mapping + PCACHE_CACHE_INFO_OFF)
#define CACHE_DEV_CACHE_CTRL(cache_dev) ((void *)cache_dev->mapping + PCACHE_CACHE_CTRL_OFF)
#define CACHE_DEV_SEGMENTS(cache_dev) ((void *)cache_dev->mapping + PCACHE_SEGMENTS_OFF)
#define CACHE_DEV_SEGMENT(cache_dev, id) ((void *)CACHE_DEV_SEGMENTS(cache_dev) + (u64)id * PCACHE_SEG_SIZE)
/*
* PCACHE SB flags configured during formatting
*
* The PCACHE_SB_F_xxx flags define registration requirements based on cache_dev
* formatting. For a machine to register a cache_dev:
* - PCACHE_SB_F_BIGENDIAN: Requires a big-endian machine.
*/
#define PCACHE_SB_F_BIGENDIAN BIT(0)
struct pcache_sb {
__le32 crc;
__le32 flags;
__le64 magic;
__le32 seg_num;
};
struct pcache_cache_dev {
u32 sb_flags;
u32 seg_num;
void *mapping;
bool use_vmap;
struct dm_dev *dm_dev;
struct mutex seg_lock;
unsigned long *seg_bitmap;
};
struct dm_pcache;
int cache_dev_start(struct dm_pcache *pcache);
void cache_dev_stop(struct dm_pcache *pcache);
void cache_dev_zero_range(struct pcache_cache_dev *cache_dev, void *pos, u32 size);
int cache_dev_get_empty_segment_id(struct pcache_cache_dev *cache_dev, u32 *seg_id);
#endif /* _PCACHE_CACHE_DEV_H */
+170
View File
@@ -0,0 +1,170 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include "cache.h"
#include "backing_dev.h"
#include "cache_dev.h"
#include "dm_pcache.h"
/**
* cache_key_gc - Releases the reference of a cache key segment.
* @cache: Pointer to the pcache_cache structure.
* @key: Pointer to the cache key to be garbage collected.
*
* This function decrements the reference count of the cache segment
* associated with the given key. If the reference count drops to zero,
* the segment may be invalidated and reused.
*/
static void cache_key_gc(struct pcache_cache *cache, struct pcache_cache_key *key)
{
cache_seg_put(key->cache_pos.cache_seg);
}
static bool need_gc(struct pcache_cache *cache, struct pcache_cache_pos *dirty_tail, struct pcache_cache_pos *key_tail)
{
struct dm_pcache *pcache = CACHE_TO_PCACHE(cache);
struct pcache_cache_kset_onmedia *kset_onmedia;
void *dirty_addr, *key_addr;
u32 segs_used, segs_gc_threshold, to_copy;
int ret;
dirty_addr = cache_pos_addr(dirty_tail);
key_addr = cache_pos_addr(key_tail);
if (dirty_addr == key_addr) {
pcache_dev_debug(pcache, "key tail is equal to dirty tail: %u:%u\n",
dirty_tail->cache_seg->cache_seg_id,
dirty_tail->seg_off);
return false;
}
kset_onmedia = (struct pcache_cache_kset_onmedia *)cache->gc_kset_onmedia_buf;
to_copy = min(PCACHE_KSET_ONMEDIA_SIZE_MAX, PCACHE_SEG_SIZE - key_tail->seg_off);
ret = copy_mc_to_kernel(kset_onmedia, key_addr, to_copy);
if (ret) {
pcache_dev_err(pcache, "error to read kset: %d", ret);
return false;
}
/* Check if kset_onmedia is corrupted */
if (kset_onmedia->magic != PCACHE_KSET_MAGIC) {
pcache_dev_debug(pcache, "gc error: magic is not as expected. key_tail: %u:%u magic: %llx, expected: %llx\n",
key_tail->cache_seg->cache_seg_id, key_tail->seg_off,
kset_onmedia->magic, PCACHE_KSET_MAGIC);
return false;
}
/* Verify the CRC of the kset_onmedia */
if (kset_onmedia->crc != cache_kset_crc(kset_onmedia)) {
pcache_dev_debug(pcache, "gc error: crc is not as expected. crc: %x, expected: %x\n",
cache_kset_crc(kset_onmedia), kset_onmedia->crc);
return false;
}
segs_used = bitmap_weight(cache->seg_map, cache->n_segs);
segs_gc_threshold = cache->n_segs * pcache_cache_get_gc_percent(cache) / 100;
if (segs_used < segs_gc_threshold) {
pcache_dev_debug(pcache, "segs_used: %u, segs_gc_threshold: %u\n", segs_used, segs_gc_threshold);
return false;
}
return true;
}
/**
* last_kset_gc - Advances the garbage collection for the last kset.
* @cache: Pointer to the pcache_cache structure.
* @kset_onmedia: Pointer to the kset_onmedia structure for the last kset.
*/
static void last_kset_gc(struct pcache_cache *cache, struct pcache_cache_kset_onmedia *kset_onmedia)
{
struct dm_pcache *pcache = CACHE_TO_PCACHE(cache);
struct pcache_cache_segment *cur_seg, *next_seg;
cur_seg = cache->key_tail.cache_seg;
next_seg = &cache->segments[kset_onmedia->next_cache_seg_id];
mutex_lock(&cache->key_tail_lock);
cache->key_tail.cache_seg = next_seg;
cache->key_tail.seg_off = 0;
cache_encode_key_tail(cache);
mutex_unlock(&cache->key_tail_lock);
pcache_dev_debug(pcache, "gc advance kset seg: %u\n", cur_seg->cache_seg_id);
spin_lock(&cache->seg_map_lock);
__clear_bit(cur_seg->cache_seg_id, cache->seg_map);
spin_unlock(&cache->seg_map_lock);
}
void pcache_cache_gc_fn(struct work_struct *work)
{
struct pcache_cache *cache = container_of(work, struct pcache_cache, gc_work.work);
struct dm_pcache *pcache = CACHE_TO_PCACHE(cache);
struct pcache_cache_pos dirty_tail, key_tail;
struct pcache_cache_kset_onmedia *kset_onmedia;
struct pcache_cache_key_onmedia *key_onmedia;
struct pcache_cache_key *key;
int ret;
int i;
kset_onmedia = (struct pcache_cache_kset_onmedia *)cache->gc_kset_onmedia_buf;
while (true) {
if (pcache_is_stopping(pcache) || atomic_read(&cache->gc_errors))
return;
/* Get new tail positions */
mutex_lock(&cache->dirty_tail_lock);
cache_pos_copy(&dirty_tail, &cache->dirty_tail);
mutex_unlock(&cache->dirty_tail_lock);
mutex_lock(&cache->key_tail_lock);
cache_pos_copy(&key_tail, &cache->key_tail);
mutex_unlock(&cache->key_tail_lock);
if (!need_gc(cache, &dirty_tail, &key_tail))
break;
if (kset_onmedia->flags & PCACHE_KSET_FLAGS_LAST) {
/* Don't move to the next segment if dirty_tail has not moved */
if (dirty_tail.cache_seg == key_tail.cache_seg)
break;
last_kset_gc(cache, kset_onmedia);
continue;
}
for (i = 0; i < kset_onmedia->key_num; i++) {
struct pcache_cache_key key_tmp = { 0 };
key_onmedia = &kset_onmedia->data[i];
key = &key_tmp;
cache_key_init(&cache->req_key_tree, key);
ret = cache_key_decode(cache, key_onmedia, key);
if (ret) {
/* return without re-arm gc work, and prevent future
* gc, because we can't retry the partial-gc-ed kset
*/
atomic_inc(&cache->gc_errors);
pcache_dev_err(pcache, "failed to decode cache key in gc\n");
return;
}
cache_key_gc(cache, key);
}
pcache_dev_debug(pcache, "gc advance: %u:%u %u\n",
key_tail.cache_seg->cache_seg_id,
key_tail.seg_off,
get_kset_onmedia_size(kset_onmedia));
mutex_lock(&cache->key_tail_lock);
cache_pos_advance(&cache->key_tail, get_kset_onmedia_size(kset_onmedia));
cache_encode_key_tail(cache);
mutex_unlock(&cache->key_tail_lock);
}
queue_delayed_work(cache_get_wq(cache), &cache->gc_work, PCACHE_CACHE_GC_INTERVAL);
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+293
View File
@@ -0,0 +1,293 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include "cache_dev.h"
#include "cache.h"
#include "backing_dev.h"
#include "dm_pcache.h"
static inline struct pcache_segment_info *get_seg_info_addr(struct pcache_cache_segment *cache_seg)
{
struct pcache_segment_info *seg_info_addr;
u32 seg_id = cache_seg->segment.seg_id;
void *seg_addr;
seg_addr = CACHE_DEV_SEGMENT(cache_seg->cache->cache_dev, seg_id);
seg_info_addr = seg_addr + PCACHE_SEG_INFO_SIZE * cache_seg->info_index;
return seg_info_addr;
}
static void cache_seg_info_write(struct pcache_cache_segment *cache_seg)
{
struct pcache_segment_info *seg_info_addr;
struct pcache_segment_info *seg_info = &cache_seg->cache_seg_info;
mutex_lock(&cache_seg->info_lock);
seg_info->header.seq++;
seg_info->header.crc = pcache_meta_crc(&seg_info->header, sizeof(struct pcache_segment_info));
seg_info_addr = get_seg_info_addr(cache_seg);
memcpy_flushcache(seg_info_addr, seg_info, sizeof(struct pcache_segment_info));
pmem_wmb();
cache_seg->info_index = (cache_seg->info_index + 1) % PCACHE_META_INDEX_MAX;
mutex_unlock(&cache_seg->info_lock);
}
static int cache_seg_info_load(struct pcache_cache_segment *cache_seg)
{
struct pcache_segment_info *cache_seg_info_addr_base, *cache_seg_info_addr;
struct pcache_cache_dev *cache_dev = cache_seg->cache->cache_dev;
struct dm_pcache *pcache = CACHE_DEV_TO_PCACHE(cache_dev);
u32 seg_id = cache_seg->segment.seg_id;
int ret = 0;
cache_seg_info_addr_base = CACHE_DEV_SEGMENT(cache_dev, seg_id);
mutex_lock(&cache_seg->info_lock);
cache_seg_info_addr = pcache_meta_find_latest(&cache_seg_info_addr_base->header,
sizeof(struct pcache_segment_info),
PCACHE_SEG_INFO_SIZE,
&cache_seg->cache_seg_info);
if (IS_ERR(cache_seg_info_addr)) {
ret = PTR_ERR(cache_seg_info_addr);
goto out;
} else if (!cache_seg_info_addr) {
ret = -EIO;
goto out;
}
cache_seg->info_index = cache_seg_info_addr - cache_seg_info_addr_base;
out:
mutex_unlock(&cache_seg->info_lock);
if (ret)
pcache_dev_err(pcache, "can't read segment info of segment: %u, ret: %d\n",
cache_seg->segment.seg_id, ret);
return ret;
}
static int cache_seg_ctrl_load(struct pcache_cache_segment *cache_seg)
{
struct pcache_cache_seg_ctrl *cache_seg_ctrl = cache_seg->cache_seg_ctrl;
struct pcache_cache_seg_gen cache_seg_gen, *cache_seg_gen_addr;
int ret = 0;
mutex_lock(&cache_seg->ctrl_lock);
cache_seg_gen_addr = pcache_meta_find_latest(&cache_seg_ctrl->gen->header,
sizeof(struct pcache_cache_seg_gen),
sizeof(struct pcache_cache_seg_gen),
&cache_seg_gen);
if (IS_ERR(cache_seg_gen_addr)) {
ret = PTR_ERR(cache_seg_gen_addr);
goto out;
}
if (!cache_seg_gen_addr) {
cache_seg->gen = 0;
cache_seg->gen_seq = 0;
cache_seg->gen_index = 0;
goto out;
}
cache_seg->gen = cache_seg_gen.gen;
cache_seg->gen_seq = cache_seg_gen.header.seq;
cache_seg->gen_index = (cache_seg_gen_addr - cache_seg_ctrl->gen);
out:
mutex_unlock(&cache_seg->ctrl_lock);
return ret;
}
static inline struct pcache_cache_seg_gen *get_cache_seg_gen_addr(struct pcache_cache_segment *cache_seg)
{
struct pcache_cache_seg_ctrl *cache_seg_ctrl = cache_seg->cache_seg_ctrl;
return (cache_seg_ctrl->gen + cache_seg->gen_index);
}
static void cache_seg_ctrl_write(struct pcache_cache_segment *cache_seg)
{
struct pcache_cache_seg_gen cache_seg_gen;
mutex_lock(&cache_seg->ctrl_lock);
cache_seg_gen.gen = cache_seg->gen;
cache_seg_gen.header.seq = ++cache_seg->gen_seq;
cache_seg_gen.header.crc = pcache_meta_crc(&cache_seg_gen.header,
sizeof(struct pcache_cache_seg_gen));
memcpy_flushcache(get_cache_seg_gen_addr(cache_seg), &cache_seg_gen, sizeof(struct pcache_cache_seg_gen));
pmem_wmb();
cache_seg->gen_index = (cache_seg->gen_index + 1) % PCACHE_META_INDEX_MAX;
mutex_unlock(&cache_seg->ctrl_lock);
}
static void cache_seg_ctrl_init(struct pcache_cache_segment *cache_seg)
{
cache_seg->gen = 0;
cache_seg->gen_seq = 0;
cache_seg->gen_index = 0;
cache_seg_ctrl_write(cache_seg);
}
static int cache_seg_meta_load(struct pcache_cache_segment *cache_seg)
{
int ret;
ret = cache_seg_info_load(cache_seg);
if (ret)
goto err;
ret = cache_seg_ctrl_load(cache_seg);
if (ret)
goto err;
return 0;
err:
return ret;
}
/**
* cache_seg_set_next_seg - Sets the ID of the next segment
* @cache_seg: Pointer to the cache segment structure.
* @seg_id: The segment ID to set as the next segment.
*
* A pcache_cache allocates multiple cache segments, which are linked together
* through next_seg. When loading a pcache_cache, the first cache segment can
* be found using cache->seg_id, which allows access to all the cache segments.
*/
void cache_seg_set_next_seg(struct pcache_cache_segment *cache_seg, u32 seg_id)
{
cache_seg->cache_seg_info.flags |= PCACHE_SEG_INFO_FLAGS_HAS_NEXT;
cache_seg->cache_seg_info.next_seg = seg_id;
cache_seg_info_write(cache_seg);
}
int cache_seg_init(struct pcache_cache *cache, u32 seg_id, u32 cache_seg_id,
bool new_cache)
{
struct pcache_cache_dev *cache_dev = cache->cache_dev;
struct pcache_cache_segment *cache_seg = &cache->segments[cache_seg_id];
struct pcache_segment_init_options seg_options = { 0 };
struct pcache_segment *segment = &cache_seg->segment;
int ret;
cache_seg->cache = cache;
cache_seg->cache_seg_id = cache_seg_id;
spin_lock_init(&cache_seg->gen_lock);
atomic_set(&cache_seg->refs, 0);
mutex_init(&cache_seg->info_lock);
mutex_init(&cache_seg->ctrl_lock);
/* init pcache_segment */
seg_options.type = PCACHE_SEGMENT_TYPE_CACHE_DATA;
seg_options.data_off = PCACHE_CACHE_SEG_CTRL_OFF + PCACHE_CACHE_SEG_CTRL_SIZE;
seg_options.seg_id = seg_id;
seg_options.seg_info = &cache_seg->cache_seg_info;
pcache_segment_init(cache_dev, segment, &seg_options);
cache_seg->cache_seg_ctrl = CACHE_DEV_SEGMENT(cache_dev, seg_id) + PCACHE_CACHE_SEG_CTRL_OFF;
if (new_cache) {
cache_dev_zero_range(cache_dev, CACHE_DEV_SEGMENT(cache_dev, seg_id),
PCACHE_SEG_INFO_SIZE * PCACHE_META_INDEX_MAX +
PCACHE_CACHE_SEG_CTRL_SIZE);
cache_seg_ctrl_init(cache_seg);
cache_seg->info_index = 0;
cache_seg_info_write(cache_seg);
/* clear outdated kset in segment */
memcpy_flushcache(segment->data, &pcache_empty_kset, sizeof(struct pcache_cache_kset_onmedia));
pmem_wmb();
} else {
ret = cache_seg_meta_load(cache_seg);
if (ret)
goto err;
}
return 0;
err:
return ret;
}
/**
* get_cache_segment - Retrieves a free cache segment from the cache.
* @cache: Pointer to the cache structure.
*
* This function attempts to find a free cache segment that can be used.
* It locks the segment map and checks for the next available segment ID.
* If a free segment is found, it initializes it and returns a pointer to the
* cache segment structure. Returns NULL if no segments are available.
*/
struct pcache_cache_segment *get_cache_segment(struct pcache_cache *cache)
{
struct pcache_cache_segment *cache_seg;
u32 seg_id;
spin_lock(&cache->seg_map_lock);
again:
seg_id = find_next_zero_bit(cache->seg_map, cache->n_segs, cache->last_cache_seg);
if (seg_id == cache->n_segs) {
/* reset the hint of ->last_cache_seg and retry */
if (cache->last_cache_seg) {
cache->last_cache_seg = 0;
goto again;
}
cache->cache_full = true;
spin_unlock(&cache->seg_map_lock);
return NULL;
}
/*
* found an available cache_seg, mark it used in seg_map
* and update the search hint ->last_cache_seg
*/
__set_bit(seg_id, cache->seg_map);
cache->last_cache_seg = seg_id;
spin_unlock(&cache->seg_map_lock);
cache_seg = &cache->segments[seg_id];
cache_seg->cache_seg_id = seg_id;
return cache_seg;
}
static void cache_seg_gen_increase(struct pcache_cache_segment *cache_seg)
{
spin_lock(&cache_seg->gen_lock);
cache_seg->gen++;
spin_unlock(&cache_seg->gen_lock);
cache_seg_ctrl_write(cache_seg);
}
void cache_seg_get(struct pcache_cache_segment *cache_seg)
{
atomic_inc(&cache_seg->refs);
}
static void cache_seg_invalidate(struct pcache_cache_segment *cache_seg)
{
struct pcache_cache *cache;
cache = cache_seg->cache;
cache_seg_gen_increase(cache_seg);
spin_lock(&cache->seg_map_lock);
if (cache->cache_full)
cache->cache_full = false;
__clear_bit(cache_seg->cache_seg_id, cache->seg_map);
spin_unlock(&cache->seg_map_lock);
pcache_defer_reqs_kick(CACHE_TO_PCACHE(cache));
/* clean_work will clean the bad key in key_tree*/
queue_work(cache_get_wq(cache), &cache->clean_work);
}
void cache_seg_put(struct pcache_cache_segment *cache_seg)
{
if (atomic_dec_and_test(&cache_seg->refs))
cache_seg_invalidate(cache_seg);
}
+261
View File
@@ -0,0 +1,261 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include <linux/bio.h>
#include "cache.h"
#include "backing_dev.h"
#include "cache_dev.h"
#include "dm_pcache.h"
static void writeback_ctx_end(struct pcache_cache *cache, int ret)
{
if (ret && !cache->writeback_ctx.ret) {
pcache_dev_err(CACHE_TO_PCACHE(cache), "writeback error: %d", ret);
cache->writeback_ctx.ret = ret;
}
if (!atomic_dec_and_test(&cache->writeback_ctx.pending))
return;
if (!cache->writeback_ctx.ret) {
backing_dev_flush(cache->backing_dev);
mutex_lock(&cache->dirty_tail_lock);
cache_pos_advance(&cache->dirty_tail, cache->writeback_ctx.advance);
cache_encode_dirty_tail(cache);
mutex_unlock(&cache->dirty_tail_lock);
}
queue_delayed_work(cache_get_wq(cache), &cache->writeback_work, 0);
}
static void writeback_end_req(struct pcache_backing_dev_req *backing_req, int ret)
{
struct pcache_cache *cache = backing_req->priv_data;
mutex_lock(&cache->writeback_lock);
writeback_ctx_end(cache, ret);
mutex_unlock(&cache->writeback_lock);
}
static inline bool is_cache_clean(struct pcache_cache *cache, struct pcache_cache_pos *dirty_tail)
{
struct dm_pcache *pcache = CACHE_TO_PCACHE(cache);
struct pcache_cache_kset_onmedia *kset_onmedia;
u32 to_copy;
void *addr;
int ret;
addr = cache_pos_addr(dirty_tail);
kset_onmedia = (struct pcache_cache_kset_onmedia *)cache->wb_kset_onmedia_buf;
to_copy = min(PCACHE_KSET_ONMEDIA_SIZE_MAX, PCACHE_SEG_SIZE - dirty_tail->seg_off);
ret = copy_mc_to_kernel(kset_onmedia, addr, to_copy);
if (ret) {
pcache_dev_err(pcache, "error to read kset: %d", ret);
return true;
}
/* Check if the magic number matches the expected value */
if (kset_onmedia->magic != PCACHE_KSET_MAGIC) {
pcache_dev_debug(pcache, "dirty_tail: %u:%u magic: %llx, not expected: %llx\n",
dirty_tail->cache_seg->cache_seg_id, dirty_tail->seg_off,
kset_onmedia->magic, PCACHE_KSET_MAGIC);
return true;
}
/* Verify the CRC checksum for data integrity */
if (kset_onmedia->crc != cache_kset_crc(kset_onmedia)) {
pcache_dev_debug(pcache, "dirty_tail: %u:%u crc: %x, not expected: %x\n",
dirty_tail->cache_seg->cache_seg_id, dirty_tail->seg_off,
cache_kset_crc(kset_onmedia), kset_onmedia->crc);
return true;
}
return false;
}
void cache_writeback_exit(struct pcache_cache *cache)
{
cancel_delayed_work_sync(&cache->writeback_work);
backing_dev_flush(cache->backing_dev);
cache_tree_exit(&cache->writeback_key_tree);
}
int cache_writeback_init(struct pcache_cache *cache)
{
int ret;
ret = cache_tree_init(cache, &cache->writeback_key_tree, 1);
if (ret)
goto err;
atomic_set(&cache->writeback_ctx.pending, 0);
/* Queue delayed work to start writeback handling */
queue_delayed_work(cache_get_wq(cache), &cache->writeback_work, 0);
return 0;
err:
return ret;
}
static void cache_key_writeback(struct pcache_cache *cache, struct pcache_cache_key *key)
{
struct pcache_backing_dev_req *writeback_req;
struct pcache_backing_dev_req_opts writeback_req_opts = { 0 };
struct pcache_cache_pos *pos;
void *addr;
u32 seg_remain, req_len, done = 0;
if (cache_key_clean(key))
return;
pos = &key->cache_pos;
seg_remain = cache_seg_remain(pos);
BUG_ON(seg_remain < key->len);
next_req:
addr = cache_pos_addr(pos) + done;
req_len = backing_dev_req_coalesced_max_len(addr, key->len - done);
writeback_req_opts.type = BACKING_DEV_REQ_TYPE_KMEM;
writeback_req_opts.gfp_mask = GFP_NOIO;
writeback_req_opts.end_fn = writeback_end_req;
writeback_req_opts.priv_data = cache;
writeback_req_opts.kmem.data = addr;
writeback_req_opts.kmem.opf = REQ_OP_WRITE;
writeback_req_opts.kmem.len = req_len;
writeback_req_opts.kmem.backing_off = key->off + done;
writeback_req = backing_dev_req_create(cache->backing_dev, &writeback_req_opts);
atomic_inc(&cache->writeback_ctx.pending);
backing_dev_req_submit(writeback_req, true);
done += req_len;
if (done < key->len)
goto next_req;
}
static void cache_wb_tree_writeback(struct pcache_cache *cache, u32 advance)
{
struct pcache_cache_tree *cache_tree = &cache->writeback_key_tree;
struct pcache_cache_subtree *cache_subtree;
struct rb_node *node;
struct pcache_cache_key *key;
u32 i;
cache->writeback_ctx.ret = 0;
cache->writeback_ctx.advance = advance;
atomic_set(&cache->writeback_ctx.pending, 1);
for (i = 0; i < cache_tree->n_subtrees; i++) {
cache_subtree = &cache_tree->subtrees[i];
node = rb_first(&cache_subtree->root);
while (node) {
key = CACHE_KEY(node);
node = rb_next(node);
cache_key_writeback(cache, key);
cache_key_delete(key);
}
}
writeback_ctx_end(cache, 0);
}
static int cache_kset_insert_tree(struct pcache_cache *cache, struct pcache_cache_kset_onmedia *kset_onmedia)
{
struct pcache_cache_key_onmedia *key_onmedia;
struct pcache_cache_subtree *cache_subtree;
struct pcache_cache_key *key;
int ret;
u32 i;
/* Iterate through all keys in the kset and write each back to storage */
for (i = 0; i < kset_onmedia->key_num; i++) {
key_onmedia = &kset_onmedia->data[i];
key = cache_key_alloc(&cache->writeback_key_tree, GFP_NOIO);
ret = cache_key_decode(cache, key_onmedia, key);
if (ret) {
cache_key_put(key);
goto clear_tree;
}
cache_subtree = get_subtree(&cache->writeback_key_tree, key->off);
spin_lock(&cache_subtree->tree_lock);
cache_key_insert(&cache->writeback_key_tree, key, true);
spin_unlock(&cache_subtree->tree_lock);
}
return 0;
clear_tree:
cache_tree_clear(&cache->writeback_key_tree);
return ret;
}
static void last_kset_writeback(struct pcache_cache *cache,
struct pcache_cache_kset_onmedia *last_kset_onmedia)
{
struct dm_pcache *pcache = CACHE_TO_PCACHE(cache);
struct pcache_cache_segment *next_seg;
pcache_dev_debug(pcache, "last kset, next: %u\n", last_kset_onmedia->next_cache_seg_id);
next_seg = &cache->segments[last_kset_onmedia->next_cache_seg_id];
mutex_lock(&cache->dirty_tail_lock);
cache->dirty_tail.cache_seg = next_seg;
cache->dirty_tail.seg_off = 0;
cache_encode_dirty_tail(cache);
mutex_unlock(&cache->dirty_tail_lock);
}
void cache_writeback_fn(struct work_struct *work)
{
struct pcache_cache *cache = container_of(work, struct pcache_cache, writeback_work.work);
struct dm_pcache *pcache = CACHE_TO_PCACHE(cache);
struct pcache_cache_pos dirty_tail;
struct pcache_cache_kset_onmedia *kset_onmedia;
u32 delay;
int ret;
mutex_lock(&cache->writeback_lock);
if (atomic_read(&cache->writeback_ctx.pending))
goto unlock;
if (pcache_is_stopping(pcache))
goto unlock;
kset_onmedia = (struct pcache_cache_kset_onmedia *)cache->wb_kset_onmedia_buf;
mutex_lock(&cache->dirty_tail_lock);
cache_pos_copy(&dirty_tail, &cache->dirty_tail);
mutex_unlock(&cache->dirty_tail_lock);
if (is_cache_clean(cache, &dirty_tail)) {
delay = PCACHE_CACHE_WRITEBACK_INTERVAL;
goto queue_work;
}
if (kset_onmedia->flags & PCACHE_KSET_FLAGS_LAST) {
last_kset_writeback(cache, kset_onmedia);
delay = 0;
goto queue_work;
}
ret = cache_kset_insert_tree(cache, kset_onmedia);
if (ret) {
delay = PCACHE_CACHE_WRITEBACK_INTERVAL;
goto queue_work;
}
cache_wb_tree_writeback(cache, get_kset_onmedia_size(kset_onmedia));
delay = 0;
queue_work:
queue_delayed_work(cache_get_wq(cache), &cache->writeback_work, delay);
unlock:
mutex_unlock(&cache->writeback_lock);
}
+497
View File
@@ -0,0 +1,497 @@
// 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");
+67
View File
@@ -0,0 +1,67 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#ifndef _DM_PCACHE_H
#define _DM_PCACHE_H
#include <linux/device-mapper.h>
#include "../dm-core.h"
#define CACHE_DEV_TO_PCACHE(cache_dev) (container_of(cache_dev, struct dm_pcache, cache_dev))
#define BACKING_DEV_TO_PCACHE(backing_dev) (container_of(backing_dev, struct dm_pcache, backing_dev))
#define CACHE_TO_PCACHE(cache) (container_of(cache, struct dm_pcache, cache))
#define PCACHE_STATE_RUNNING 1
#define PCACHE_STATE_STOPPING 2
struct pcache_cache_dev;
struct pcache_backing_dev;
struct pcache_cache;
struct pcache_cache_options;
struct dm_pcache {
struct dm_target *ti;
struct pcache_cache_dev cache_dev;
struct pcache_backing_dev backing_dev;
struct pcache_cache cache;
struct pcache_cache_options opts;
spinlock_t defered_req_list_lock;
struct list_head defered_req_list;
struct workqueue_struct *task_wq;
struct work_struct defered_req_work;
atomic_t state;
atomic_t inflight_reqs;
wait_queue_head_t inflight_wq;
};
static inline bool pcache_is_stopping(struct dm_pcache *pcache)
{
return (atomic_read(&pcache->state) == PCACHE_STATE_STOPPING);
}
#define pcache_dev_err(pcache, fmt, ...) \
pcache_err("%s " fmt, pcache->ti->table->md->name, ##__VA_ARGS__)
#define pcache_dev_info(pcache, fmt, ...) \
pcache_info("%s " fmt, pcache->ti->table->md->name, ##__VA_ARGS__)
#define pcache_dev_debug(pcache, fmt, ...) \
pcache_debug("%s " fmt, pcache->ti->table->md->name, ##__VA_ARGS__)
struct pcache_request {
struct dm_pcache *pcache;
struct bio *bio;
u64 off;
u32 data_len;
struct kref ref;
int ret;
struct list_head list_node;
};
void pcache_req_get(struct pcache_request *pcache_req);
void pcache_req_put(struct pcache_request *pcache_req, int ret);
void pcache_defer_reqs_kick(struct dm_pcache *pcache);
#endif /* _DM_PCACHE_H */

Some files were not shown because too many files have changed in this diff Show More