mirror of
https://github.com/linux-msm/laptops-kernel.git
synced 2026-08-13 14:19:53 -07:00
Merge tag 'for-6.18/dm-changes' of git://git.kernel.org/pub/scm/linux/kernel/git/device-mapper/linux-dm
Pull device mapper updates from Mikulas Patocka: - a new dm-pcache target for read/write caching on persistent memory - fix typos in docs - misc small refactoring - mark dm-error with DM_TARGET_PASSES_INTEGRITY - dm-request-based: fix NULL pointer dereference and quiesce_depth out of sync - dm-linear: optimize REQ_PREFLUSH - dm-vdo: return error on corrupted metadata - dm-integrity: support asynchronous hash interface * tag 'for-6.18/dm-changes' of git://git.kernel.org/pub/scm/linux/kernel/git/device-mapper/linux-dm: (27 commits) dm raid: use proper md_ro_state enumerators dm-integrity: prefer synchronous hash interface dm-integrity: enable asynchronous hash interface dm-integrity: rename internal_hash dm-integrity: add the "offset" argument dm-integrity: allocate the recalculate buffer with kmalloc dm-integrity: introduce integrity_kmap and integrity_kunmap dm-integrity: replace bvec_kmap_local with kmap_local_page dm-integrity: use internal variable for digestsize dm vdo: return error on corrupted metadata in start_restoring_volume functions dm vdo: Update code to use mem_is_zero dm: optimize REQ_PREFLUSH with data when using the linear target dm-pcache: use int type to store negative error codes dm: fix "writen"->"written" dm-pcache: cleanup: fix coding style report by checkpatch.pl dm-pcache: remove ctrl_lock for pcache_cache_segment dm: fix NULL pointer dereference in __dm_suspend() dm: fix queue start/stop imbalance under suspend/load/resume races dm-pcache: add persistent cache target in device-mapper dm error: mark as DM_TARGET_PASSES_INTEGRITY ...
This commit is contained in:
@@ -3,7 +3,7 @@ dm-delay
|
||||
========
|
||||
|
||||
Device-Mapper's "delay" target delays reads and/or writes
|
||||
and/or flushs and optionally maps them to different devices.
|
||||
and/or flushes and optionally maps them to different devices.
|
||||
|
||||
Arguments::
|
||||
|
||||
@@ -18,7 +18,7 @@ Table line has to either have 3, 6 or 9 arguments:
|
||||
to write and flush operations on optionally different write_device with
|
||||
optionally different sector offset
|
||||
|
||||
9: same as 6 arguments plus define flush_offset and flush_delay explicitely
|
||||
9: same as 6 arguments plus define flush_offset and flush_delay explicitly
|
||||
on/with optionally different flush_device/flush_offset.
|
||||
|
||||
Offsets are specified in sectors.
|
||||
@@ -40,7 +40,7 @@ Example scripts
|
||||
#!/bin/sh
|
||||
#
|
||||
# Create mapped device delaying write and flush operations for 400ms and
|
||||
# splitting reads to device $1 but writes and flushs to different device $2
|
||||
# splitting reads to device $1 but writes and flushes to different device $2
|
||||
# to different offsets of 2048 and 4096 sectors respectively.
|
||||
#
|
||||
dmsetup create delayed --table "0 `blockdev --getsz $1` delay $1 2048 0 $2 4096 400"
|
||||
@@ -48,7 +48,7 @@ Example scripts
|
||||
::
|
||||
#!/bin/sh
|
||||
#
|
||||
# Create mapped device delaying reads for 50ms, writes for 100ms and flushs for 333ms
|
||||
# Create mapped device delaying reads for 50ms, writes for 100ms and flushes for 333ms
|
||||
# onto the same backing device at offset 0 sectors.
|
||||
#
|
||||
dmsetup create delayed --table "0 `blockdev --getsz $1` delay $1 0 50 $2 0 100 $1 0 333"
|
||||
|
||||
@@ -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 segment’s 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
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
.. SPDX-License-Identifier: GPL-2.0-only
|
||||
|
||||
======
|
||||
dm-vdo
|
||||
======
|
||||
|
||||
|
||||
@@ -7133,6 +7133,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
|
||||
|
||||
@@ -688,4 +688,6 @@ config DM_AUDIT
|
||||
|
||||
source "drivers/md/dm-vdo/Kconfig"
|
||||
|
||||
source "drivers/md/dm-pcache/Kconfig"
|
||||
|
||||
endif # MD
|
||||
|
||||
@@ -73,6 +73,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
|
||||
|
||||
@@ -1337,7 +1337,7 @@ static void use_bio(struct dm_buffer *b, enum req_op op, sector_t sector,
|
||||
char *ptr;
|
||||
unsigned int len;
|
||||
|
||||
bio = bio_kmalloc(1, GFP_NOWAIT | __GFP_NORETRY | __GFP_NOWARN);
|
||||
bio = bio_kmalloc(1, GFP_NOWAIT);
|
||||
if (!bio) {
|
||||
use_dmio(b, op, sector, n_sectors, offset, ioprio);
|
||||
return;
|
||||
@@ -1601,18 +1601,18 @@ static struct dm_buffer *__alloc_buffer_wait_no_callback(struct dm_bufio_client
|
||||
* dm-bufio is resistant to allocation failures (it just keeps
|
||||
* one buffer reserved in cases all the allocations fail).
|
||||
* So set flags to not try too hard:
|
||||
* GFP_NOWAIT: don't wait; if we need to sleep we'll release our
|
||||
* mutex and wait ourselves.
|
||||
* GFP_NOWAIT: don't wait and don't print a warning in case of
|
||||
* failure; if we need to sleep we'll release our mutex
|
||||
* and wait ourselves.
|
||||
* __GFP_NORETRY: don't retry and rather return failure
|
||||
* __GFP_NOMEMALLOC: don't use emergency reserves
|
||||
* __GFP_NOWARN: don't print a warning in case of failure
|
||||
*
|
||||
* For debugging, if we set the cache size to 1, no new buffers will
|
||||
* be allocated.
|
||||
*/
|
||||
while (1) {
|
||||
if (dm_bufio_cache_size_latch != 1) {
|
||||
b = alloc_buffer(c, GFP_NOWAIT | __GFP_NORETRY | __GFP_NOMEMALLOC | __GFP_NOWARN);
|
||||
b = alloc_buffer(c, GFP_NOWAIT | __GFP_NORETRY | __GFP_NOMEMALLOC);
|
||||
if (b)
|
||||
return b;
|
||||
}
|
||||
|
||||
@@ -590,7 +590,7 @@ static int h_init(struct smq_hash_table *ht, struct entry_space *es, unsigned in
|
||||
nr_buckets = roundup_pow_of_two(max(nr_entries / 4u, 16u));
|
||||
ht->hash_bits = __ffs(nr_buckets);
|
||||
|
||||
ht->buckets = vmalloc(array_size(nr_buckets, sizeof(*ht->buckets)));
|
||||
ht->buckets = vmalloc_array(nr_buckets, sizeof(*ht->buckets));
|
||||
if (!ht->buckets)
|
||||
return -ENOMEM;
|
||||
|
||||
|
||||
@@ -162,6 +162,7 @@ struct mapped_device {
|
||||
#define DMF_SUSPENDED_INTERNALLY 7
|
||||
#define DMF_POST_SUSPENDING 8
|
||||
#define DMF_EMULATE_ZONE_APPEND 9
|
||||
#define DMF_QUEUE_STOPPED 10
|
||||
|
||||
static inline sector_t dm_get_size(struct mapped_device *md)
|
||||
{
|
||||
@@ -291,6 +292,7 @@ struct dm_io {
|
||||
struct dm_io *next;
|
||||
struct dm_stats_aux stats_aux;
|
||||
blk_status_t status;
|
||||
bool requeue_flush_with_data;
|
||||
atomic_t io_count;
|
||||
struct mapped_device *md;
|
||||
|
||||
|
||||
+31
-39
@@ -45,7 +45,7 @@ static void fix_separator_chars(char **buf)
|
||||
/*
|
||||
* Internal function to allocate memory for IMA measurements.
|
||||
*/
|
||||
static void *dm_ima_alloc(size_t len, gfp_t flags, bool noio)
|
||||
static void *dm_ima_alloc(size_t len, bool noio)
|
||||
{
|
||||
unsigned int noio_flag;
|
||||
void *ptr;
|
||||
@@ -53,7 +53,7 @@ static void *dm_ima_alloc(size_t len, gfp_t flags, bool noio)
|
||||
if (noio)
|
||||
noio_flag = memalloc_noio_save();
|
||||
|
||||
ptr = kzalloc(len, flags);
|
||||
ptr = kzalloc(len, GFP_KERNEL);
|
||||
|
||||
if (noio)
|
||||
memalloc_noio_restore(noio_flag);
|
||||
@@ -68,13 +68,13 @@ static int dm_ima_alloc_and_copy_name_uuid(struct mapped_device *md, char **dev_
|
||||
char **dev_uuid, bool noio)
|
||||
{
|
||||
int r;
|
||||
*dev_name = dm_ima_alloc(DM_NAME_LEN*2, GFP_KERNEL, noio);
|
||||
*dev_name = dm_ima_alloc(DM_NAME_LEN*2, noio);
|
||||
if (!(*dev_name)) {
|
||||
r = -ENOMEM;
|
||||
goto error;
|
||||
}
|
||||
|
||||
*dev_uuid = dm_ima_alloc(DM_UUID_LEN*2, GFP_KERNEL, noio);
|
||||
*dev_uuid = dm_ima_alloc(DM_UUID_LEN*2, noio);
|
||||
if (!(*dev_uuid)) {
|
||||
r = -ENOMEM;
|
||||
goto error;
|
||||
@@ -109,7 +109,7 @@ static int dm_ima_alloc_and_copy_device_data(struct mapped_device *md, char **de
|
||||
if (r)
|
||||
return r;
|
||||
|
||||
*device_data = dm_ima_alloc(DM_IMA_DEVICE_BUF_LEN, GFP_KERNEL, noio);
|
||||
*device_data = dm_ima_alloc(DM_IMA_DEVICE_BUF_LEN, noio);
|
||||
if (!(*device_data)) {
|
||||
r = -ENOMEM;
|
||||
goto error;
|
||||
@@ -153,14 +153,12 @@ static int dm_ima_alloc_and_copy_capacity_str(struct mapped_device *md, char **c
|
||||
|
||||
capacity = get_capacity(md->disk);
|
||||
|
||||
*capacity_str = dm_ima_alloc(DM_IMA_DEVICE_CAPACITY_BUF_LEN, GFP_KERNEL, noio);
|
||||
*capacity_str = dm_ima_alloc(DM_IMA_DEVICE_CAPACITY_BUF_LEN, noio);
|
||||
if (!(*capacity_str))
|
||||
return -ENOMEM;
|
||||
|
||||
scnprintf(*capacity_str, DM_IMA_DEVICE_BUF_LEN, "current_device_capacity=%llu;",
|
||||
capacity);
|
||||
|
||||
return 0;
|
||||
return scnprintf(*capacity_str, DM_IMA_DEVICE_BUF_LEN, "current_device_capacity=%llu;",
|
||||
capacity);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -195,15 +193,15 @@ void dm_ima_measure_on_table_load(struct dm_table *table, unsigned int status_fl
|
||||
const size_t hash_alg_prefix_len = strlen(DM_IMA_TABLE_HASH_ALG) + 1;
|
||||
char table_load_event_name[] = "dm_table_load";
|
||||
|
||||
ima_buf = dm_ima_alloc(DM_IMA_MEASUREMENT_BUF_LEN, GFP_KERNEL, noio);
|
||||
ima_buf = dm_ima_alloc(DM_IMA_MEASUREMENT_BUF_LEN, noio);
|
||||
if (!ima_buf)
|
||||
return;
|
||||
|
||||
target_metadata_buf = dm_ima_alloc(DM_IMA_TARGET_METADATA_BUF_LEN, GFP_KERNEL, noio);
|
||||
target_metadata_buf = dm_ima_alloc(DM_IMA_TARGET_METADATA_BUF_LEN, noio);
|
||||
if (!target_metadata_buf)
|
||||
goto error;
|
||||
|
||||
target_data_buf = dm_ima_alloc(DM_IMA_TARGET_DATA_BUF_LEN, GFP_KERNEL, noio);
|
||||
target_data_buf = dm_ima_alloc(DM_IMA_TARGET_DATA_BUF_LEN, noio);
|
||||
if (!target_data_buf)
|
||||
goto error;
|
||||
|
||||
@@ -218,7 +216,7 @@ void dm_ima_measure_on_table_load(struct dm_table *table, unsigned int status_fl
|
||||
|
||||
shash->tfm = tfm;
|
||||
digest_size = crypto_shash_digestsize(tfm);
|
||||
digest = dm_ima_alloc(digest_size, GFP_KERNEL, noio);
|
||||
digest = dm_ima_alloc(digest_size, noio);
|
||||
if (!digest)
|
||||
goto error;
|
||||
|
||||
@@ -327,7 +325,7 @@ void dm_ima_measure_on_table_load(struct dm_table *table, unsigned int status_fl
|
||||
if (r < 0)
|
||||
goto error;
|
||||
|
||||
digest_buf = dm_ima_alloc((digest_size*2) + hash_alg_prefix_len + 1, GFP_KERNEL, noio);
|
||||
digest_buf = dm_ima_alloc((digest_size*2) + hash_alg_prefix_len + 1, noio);
|
||||
|
||||
if (!digest_buf)
|
||||
goto error;
|
||||
@@ -371,18 +369,18 @@ void dm_ima_measure_on_device_resume(struct mapped_device *md, bool swap)
|
||||
{
|
||||
char *device_table_data, *dev_name = NULL, *dev_uuid = NULL, *capacity_str = NULL;
|
||||
char active[] = "active_table_hash=";
|
||||
unsigned int active_len = strlen(active), capacity_len = 0;
|
||||
unsigned int active_len = strlen(active);
|
||||
unsigned int l = 0;
|
||||
bool noio = true;
|
||||
bool nodata = true;
|
||||
int r;
|
||||
int capacity_len;
|
||||
|
||||
device_table_data = dm_ima_alloc(DM_IMA_DEVICE_BUF_LEN, GFP_KERNEL, noio);
|
||||
device_table_data = dm_ima_alloc(DM_IMA_DEVICE_BUF_LEN, noio);
|
||||
if (!device_table_data)
|
||||
return;
|
||||
|
||||
r = dm_ima_alloc_and_copy_capacity_str(md, &capacity_str, noio);
|
||||
if (r)
|
||||
capacity_len = dm_ima_alloc_and_copy_capacity_str(md, &capacity_str, noio);
|
||||
if (capacity_len < 0)
|
||||
goto error;
|
||||
|
||||
memcpy(device_table_data + l, DM_IMA_VERSION_STR, md->ima.dm_version_str_len);
|
||||
@@ -445,8 +443,7 @@ void dm_ima_measure_on_device_resume(struct mapped_device *md, bool swap)
|
||||
}
|
||||
|
||||
if (nodata) {
|
||||
r = dm_ima_alloc_and_copy_name_uuid(md, &dev_name, &dev_uuid, noio);
|
||||
if (r)
|
||||
if (dm_ima_alloc_and_copy_name_uuid(md, &dev_name, &dev_uuid, noio))
|
||||
goto error;
|
||||
|
||||
l = scnprintf(device_table_data, DM_IMA_DEVICE_BUF_LEN,
|
||||
@@ -454,7 +451,6 @@ void dm_ima_measure_on_device_resume(struct mapped_device *md, bool swap)
|
||||
DM_IMA_VERSION_STR, dev_name, dev_uuid);
|
||||
}
|
||||
|
||||
capacity_len = strlen(capacity_str);
|
||||
memcpy(device_table_data + l, capacity_str, capacity_len);
|
||||
l += capacity_len;
|
||||
|
||||
@@ -483,18 +479,17 @@ void dm_ima_measure_on_device_remove(struct mapped_device *md, bool remove_all)
|
||||
unsigned int device_active_len = strlen(device_active_str);
|
||||
unsigned int device_inactive_len = strlen(device_inactive_str);
|
||||
unsigned int remove_all_len = strlen(remove_all_str);
|
||||
unsigned int capacity_len = 0;
|
||||
unsigned int l = 0;
|
||||
bool noio = true;
|
||||
bool nodata = true;
|
||||
int r;
|
||||
int capacity_len;
|
||||
|
||||
device_table_data = dm_ima_alloc(DM_IMA_DEVICE_BUF_LEN*2, GFP_KERNEL, noio);
|
||||
device_table_data = dm_ima_alloc(DM_IMA_DEVICE_BUF_LEN*2, noio);
|
||||
if (!device_table_data)
|
||||
goto exit;
|
||||
|
||||
r = dm_ima_alloc_and_copy_capacity_str(md, &capacity_str, noio);
|
||||
if (r) {
|
||||
capacity_len = dm_ima_alloc_and_copy_capacity_str(md, &capacity_str, noio);
|
||||
if (capacity_len < 0) {
|
||||
kfree(device_table_data);
|
||||
goto exit;
|
||||
}
|
||||
@@ -570,7 +565,6 @@ void dm_ima_measure_on_device_remove(struct mapped_device *md, bool remove_all)
|
||||
memcpy(device_table_data + l, remove_all ? "y;" : "n;", 2);
|
||||
l += 2;
|
||||
|
||||
capacity_len = strlen(capacity_str);
|
||||
memcpy(device_table_data + l, capacity_str, capacity_len);
|
||||
l += capacity_len;
|
||||
|
||||
@@ -602,20 +596,20 @@ exit:
|
||||
*/
|
||||
void dm_ima_measure_on_table_clear(struct mapped_device *md, bool new_map)
|
||||
{
|
||||
unsigned int l = 0, capacity_len = 0;
|
||||
unsigned int l = 0;
|
||||
char *device_table_data = NULL, *dev_name = NULL, *dev_uuid = NULL, *capacity_str = NULL;
|
||||
char inactive_str[] = "inactive_table_hash=";
|
||||
unsigned int inactive_len = strlen(inactive_str);
|
||||
bool noio = true;
|
||||
bool nodata = true;
|
||||
int r;
|
||||
int capacity_len;
|
||||
|
||||
device_table_data = dm_ima_alloc(DM_IMA_DEVICE_BUF_LEN, GFP_KERNEL, noio);
|
||||
device_table_data = dm_ima_alloc(DM_IMA_DEVICE_BUF_LEN, noio);
|
||||
if (!device_table_data)
|
||||
return;
|
||||
|
||||
r = dm_ima_alloc_and_copy_capacity_str(md, &capacity_str, noio);
|
||||
if (r)
|
||||
capacity_len = dm_ima_alloc_and_copy_capacity_str(md, &capacity_str, noio);
|
||||
if (capacity_len < 0)
|
||||
goto error1;
|
||||
|
||||
memcpy(device_table_data + l, DM_IMA_VERSION_STR, md->ima.dm_version_str_len);
|
||||
@@ -650,7 +644,6 @@ void dm_ima_measure_on_table_clear(struct mapped_device *md, bool new_map)
|
||||
DM_IMA_VERSION_STR, dev_name, dev_uuid);
|
||||
}
|
||||
|
||||
capacity_len = strlen(capacity_str);
|
||||
memcpy(device_table_data + l, capacity_str, capacity_len);
|
||||
l += capacity_len;
|
||||
|
||||
@@ -703,7 +696,7 @@ void dm_ima_measure_on_device_rename(struct mapped_device *md)
|
||||
char *old_device_data = NULL, *new_device_data = NULL, *combined_device_data = NULL;
|
||||
char *new_dev_name = NULL, *new_dev_uuid = NULL, *capacity_str = NULL;
|
||||
bool noio = true;
|
||||
int r, len;
|
||||
int len;
|
||||
|
||||
if (dm_ima_alloc_and_copy_device_data(md, &new_device_data,
|
||||
md->ima.active_table.num_targets, noio))
|
||||
@@ -712,12 +705,11 @@ void dm_ima_measure_on_device_rename(struct mapped_device *md)
|
||||
if (dm_ima_alloc_and_copy_name_uuid(md, &new_dev_name, &new_dev_uuid, noio))
|
||||
goto error;
|
||||
|
||||
combined_device_data = dm_ima_alloc(DM_IMA_DEVICE_BUF_LEN * 2, GFP_KERNEL, noio);
|
||||
combined_device_data = dm_ima_alloc(DM_IMA_DEVICE_BUF_LEN * 2, noio);
|
||||
if (!combined_device_data)
|
||||
goto error;
|
||||
|
||||
r = dm_ima_alloc_and_copy_capacity_str(md, &capacity_str, noio);
|
||||
if (r)
|
||||
if (dm_ima_alloc_and_copy_capacity_str(md, &capacity_str, noio) < 0)
|
||||
goto error;
|
||||
|
||||
old_device_data = md->ima.active_table.device_metadata;
|
||||
|
||||
+269
-90
File diff suppressed because it is too large
Load Diff
@@ -414,7 +414,7 @@ static int log_super(struct log_writes_c *lc)
|
||||
}
|
||||
|
||||
/*
|
||||
* Super sector should be writen in-order, otherwise the
|
||||
* Super sector should be written in-order, otherwise the
|
||||
* nr_entries could be rewritten incorrectly by an old bio.
|
||||
*/
|
||||
wait_for_completion_io(&lc->super_done);
|
||||
|
||||
@@ -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'.
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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 */
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user