mirror of
https://github.com/a52xq-dev/kernel.git
synced 2026-10-18 21:58:11 +07:00
- A major update for DM cache that reduces the latency for deciding
whether blocks should migrate to/from the cache. The bio-prison-v2 interface supports this improvement by enabling direct dispatch of work to workqueues rather than having to delay the actual work dispatch to the DM cache core. So the dm-cache policies are much more nimble by being able to drive IO as they see fit. One immediate benefit from the improved latency is a cache that should be much more adaptive to changing workloads. - Add a new DM integrity target that emulates a block device that has additional per-sector tags that can be used for storing integrity information. - Add a new authenticated encryption feature to the DM crypt target that builds on the capabilities provided by the DM integrity target. - Add MD interface for switching the raid4/5/6 journal mode and update the DM raid target to use it to enable aid4/5/6 journal write-back support. - Switch the DM verity target over to using the asynchronous hash crypto API (this helps work better with architectures that have access to off-CPU algorithm providers, which should reduce CPU utilization). - Various request-based DM and DM multipath fixes and improvements from Bart and Christoph. - A DM thinp target fix for a bio structure leak that occurs for each discard IFF discard passdown is enabled. - A fix for a possible deadlock in DM bufio and a fix to re-check the new buffer allocation watermark in the face of competing admin changes to the 'max_cache_size_bytes' tunable. - A couple DM core cleanups. -----BEGIN PGP SIGNATURE----- Version: GnuPG v1 iQEcBAABAgAGBQJZB6vtAAoJEMUj8QotnQNaoicIALuZTLElgAzxzA28cfk1+1Ea Gd09CfJ3M6cvk/YGUU7WwiSYIwu16yOJALG4sLcYnEmUCzvKfFPcl/RpeSJHPpYM 0aVXa6NIJw7K2r3C17toiK2DRMHYw6QU843WeWI93vBW13lDJklNJL9fM7GBEOLH NMSNw2mAq9ajtLlnJhM3ZfhloA7/u/jektvlBO1AA3RQ5Kx1cXVXFPqN7FdRfcqp 4RuEMe9faAadlXLsj3bia5IBmF/W0Qza6JilP+NLKLWB4fm7LZDjN/k+TsHWMa9e cGR73TgUGLMBJX+sDJy8R3oeBG9JZkFVkD7I30eCjzyhSOs/54XNYQ23EkqHJU0= =9Ryi -----END PGP SIGNATURE----- Merge tag 'for-4.12/dm-changes' of git://git.kernel.org/pub/scm/linux/kernel/git/device-mapper/linux-dm Pull device mapper updates from Mike Snitzer: - A major update for DM cache that reduces the latency for deciding whether blocks should migrate to/from the cache. The bio-prison-v2 interface supports this improvement by enabling direct dispatch of work to workqueues rather than having to delay the actual work dispatch to the DM cache core. So the dm-cache policies are much more nimble by being able to drive IO as they see fit. One immediate benefit from the improved latency is a cache that should be much more adaptive to changing workloads. - Add a new DM integrity target that emulates a block device that has additional per-sector tags that can be used for storing integrity information. - Add a new authenticated encryption feature to the DM crypt target that builds on the capabilities provided by the DM integrity target. - Add MD interface for switching the raid4/5/6 journal mode and update the DM raid target to use it to enable aid4/5/6 journal write-back support. - Switch the DM verity target over to using the asynchronous hash crypto API (this helps work better with architectures that have access to off-CPU algorithm providers, which should reduce CPU utilization). - Various request-based DM and DM multipath fixes and improvements from Bart and Christoph. - A DM thinp target fix for a bio structure leak that occurs for each discard IFF discard passdown is enabled. - A fix for a possible deadlock in DM bufio and a fix to re-check the new buffer allocation watermark in the face of competing admin changes to the 'max_cache_size_bytes' tunable. - A couple DM core cleanups. * tag 'for-4.12/dm-changes' of git://git.kernel.org/pub/scm/linux/kernel/git/device-mapper/linux-dm: (50 commits) dm bufio: check new buffer allocation watermark every 30 seconds dm bufio: avoid a possible ABBA deadlock dm mpath: make it easier to detect unintended I/O request flushes dm mpath: cleanup QUEUE_IF_NO_PATH bit manipulation by introducing assign_bit() dm mpath: micro-optimize the hot path relative to MPATHF_QUEUE_IF_NO_PATH dm: introduce enum dm_queue_mode to cleanup related code dm mpath: verify __pg_init_all_paths locking assumptions at runtime dm: verify suspend_locking assumptions at runtime dm block manager: remove an unused argument from dm_block_manager_create() dm rq: check blk_mq_register_dev() return value in dm_mq_init_request_queue() dm mpath: delay requeuing while path initialization is in progress dm mpath: avoid that path removal can trigger an infinite loop dm mpath: split and rename activate_path() to prepare for its expanded use dm ioctl: prevent stack leak in dm ioctl call dm integrity: use previously calculated log2 of sectors_per_block dm integrity: use hex2bin instead of open-coded variant dm crypt: replace custom implementation of hex2bin() dm crypt: remove obsolete references to per-CPU state dm verity: switch to using asynchronous hash crypto API dm crypt: use WQ_HIGHPRI for the IO and crypt workqueues ...
This commit is contained in:
commit
d35a878ae1
45 changed files with 7640 additions and 3025 deletions
|
|
@ -11,14 +11,31 @@ Parameters: <cipher> <key> <iv_offset> <device path> \
|
|||
<offset> [<#opt_params> <opt_params>]
|
||||
|
||||
<cipher>
|
||||
Encryption cipher and an optional IV generation mode.
|
||||
(In format cipher[:keycount]-chainmode-ivmode[:ivopts]).
|
||||
Examples:
|
||||
des
|
||||
aes-cbc-essiv:sha256
|
||||
twofish-ecb
|
||||
Encryption cipher, encryption mode and Initial Vector (IV) generator.
|
||||
|
||||
/proc/crypto contains supported crypto modes
|
||||
The cipher specifications format is:
|
||||
cipher[:keycount]-chainmode-ivmode[:ivopts]
|
||||
Examples:
|
||||
aes-cbc-essiv:sha256
|
||||
aes-xts-plain64
|
||||
serpent-xts-plain64
|
||||
|
||||
Cipher format also supports direct specification with kernel crypt API
|
||||
format (selected by capi: prefix). The IV specification is the same
|
||||
as for the first format type.
|
||||
This format is mainly used for specification of authenticated modes.
|
||||
|
||||
The crypto API cipher specifications format is:
|
||||
capi:cipher_api_spec-ivmode[:ivopts]
|
||||
Examples:
|
||||
capi:cbc(aes)-essiv:sha256
|
||||
capi:xts(aes)-plain64
|
||||
Examples of authenticated modes:
|
||||
capi:gcm(aes)-random
|
||||
capi:authenc(hmac(sha256),xts(aes))-random
|
||||
capi:rfc7539(chacha20,poly1305)-random
|
||||
|
||||
The /proc/crypto contains a list of curently loaded crypto modes.
|
||||
|
||||
<key>
|
||||
Key used for encryption. It is encoded either as a hexadecimal number
|
||||
|
|
@ -93,6 +110,32 @@ submit_from_crypt_cpus
|
|||
thread because it benefits CFQ to have writes submitted using the
|
||||
same context.
|
||||
|
||||
integrity:<bytes>:<type>
|
||||
The device requires additional <bytes> metadata per-sector stored
|
||||
in per-bio integrity structure. This metadata must by provided
|
||||
by underlying dm-integrity target.
|
||||
|
||||
The <type> can be "none" if metadata is used only for persistent IV.
|
||||
|
||||
For Authenticated Encryption with Additional Data (AEAD)
|
||||
the <type> is "aead". An AEAD mode additionally calculates and verifies
|
||||
integrity for the encrypted device. The additional space is then
|
||||
used for storing authentication tag (and persistent IV if needed).
|
||||
|
||||
sector_size:<bytes>
|
||||
Use <bytes> as the encryption unit instead of 512 bytes sectors.
|
||||
This option can be in range 512 - 4096 bytes and must be power of two.
|
||||
Virtual device will announce this size as a minimal IO and logical sector.
|
||||
|
||||
iv_large_sectors
|
||||
IV generators will use sector number counted in <sector_size> units
|
||||
instead of default 512 bytes sectors.
|
||||
|
||||
For example, if <sector_size> is 4096 bytes, plain64 IV for the second
|
||||
sector will be 8 (without flag) and 1 if iv_large_sectors is present.
|
||||
The <iv_offset> must be multiple of <sector_size> (in 512 bytes units)
|
||||
if this flag is specified.
|
||||
|
||||
Example scripts
|
||||
===============
|
||||
LUKS (Linux Unified Key Setup) is now the preferred way to set up disk
|
||||
|
|
|
|||
199
Documentation/device-mapper/dm-integrity.txt
Normal file
199
Documentation/device-mapper/dm-integrity.txt
Normal file
|
|
@ -0,0 +1,199 @@
|
|||
The dm-integrity target emulates a block device that has additional
|
||||
per-sector tags that can be used for storing integrity information.
|
||||
|
||||
A general problem with storing integrity tags with every sector is that
|
||||
writing the sector and the integrity tag must be atomic - i.e. in case of
|
||||
crash, either both sector and integrity tag or none of them is written.
|
||||
|
||||
To guarantee write atomicity, the dm-integrity target uses journal, it
|
||||
writes sector data and integrity tags into a journal, commits the journal
|
||||
and then copies the data and integrity tags to their respective location.
|
||||
|
||||
The dm-integrity target can be used with the dm-crypt target - in this
|
||||
situation the dm-crypt target creates the integrity data and passes them
|
||||
to the dm-integrity target via bio_integrity_payload attached to the bio.
|
||||
In this mode, the dm-crypt and dm-integrity targets provide authenticated
|
||||
disk encryption - if the attacker modifies the encrypted device, an I/O
|
||||
error is returned instead of random data.
|
||||
|
||||
The dm-integrity target can also be used as a standalone target, in this
|
||||
mode it calculates and verifies the integrity tag internally. In this
|
||||
mode, the dm-integrity target can be used to detect silent data
|
||||
corruption on the disk or in the I/O path.
|
||||
|
||||
|
||||
When loading the target for the first time, the kernel driver will format
|
||||
the device. But it will only format the device if the superblock contains
|
||||
zeroes. If the superblock is neither valid nor zeroed, the dm-integrity
|
||||
target can't be loaded.
|
||||
|
||||
To use the target for the first time:
|
||||
1. overwrite the superblock with zeroes
|
||||
2. load the dm-integrity target with one-sector size, the kernel driver
|
||||
will format the device
|
||||
3. unload the dm-integrity target
|
||||
4. read the "provided_data_sectors" value from the superblock
|
||||
5. load the dm-integrity target with the the target size
|
||||
"provided_data_sectors"
|
||||
6. if you want to use dm-integrity with dm-crypt, load the dm-crypt target
|
||||
with the size "provided_data_sectors"
|
||||
|
||||
|
||||
Target arguments:
|
||||
|
||||
1. the underlying block device
|
||||
|
||||
2. the number of reserved sector at the beginning of the device - the
|
||||
dm-integrity won't read of write these sectors
|
||||
|
||||
3. the size of the integrity tag (if "-" is used, the size is taken from
|
||||
the internal-hash algorithm)
|
||||
|
||||
4. mode:
|
||||
D - direct writes (without journal) - in this mode, journaling is
|
||||
not used and data sectors and integrity tags are written
|
||||
separately. In case of crash, it is possible that the data
|
||||
and integrity tag doesn't match.
|
||||
J - journaled writes - data and integrity tags are written to the
|
||||
journal and atomicity is guaranteed. In case of crash,
|
||||
either both data and tag or none of them are written. The
|
||||
journaled mode degrades write throughput twice because the
|
||||
data have to be written twice.
|
||||
R - recovery mode - in this mode, journal is not replayed,
|
||||
checksums are not checked and writes to the device are not
|
||||
allowed. This mode is useful for data recovery if the
|
||||
device cannot be activated in any of the other standard
|
||||
modes.
|
||||
|
||||
5. the number of additional arguments
|
||||
|
||||
Additional arguments:
|
||||
|
||||
journal_sectors:number
|
||||
The size of journal, this argument is used only if formatting the
|
||||
device. If the device is already formatted, the value from the
|
||||
superblock is used.
|
||||
|
||||
interleave_sectors:number
|
||||
The number of interleaved sectors. This values is rounded down to
|
||||
a power of two. If the device is already formatted, the value from
|
||||
the superblock is used.
|
||||
|
||||
buffer_sectors:number
|
||||
The number of sectors in one buffer. The value is rounded down to
|
||||
a power of two.
|
||||
|
||||
The tag area is accessed using buffers, the buffer size is
|
||||
configurable. The large buffer size means that the I/O size will
|
||||
be larger, but there could be less I/Os issued.
|
||||
|
||||
journal_watermark:number
|
||||
The journal watermark in percents. When the size of the journal
|
||||
exceeds this watermark, the thread that flushes the journal will
|
||||
be started.
|
||||
|
||||
commit_time:number
|
||||
Commit time in milliseconds. When this time passes, the journal is
|
||||
written. The journal is also written immediatelly if the FLUSH
|
||||
request is received.
|
||||
|
||||
internal_hash:algorithm(:key) (the key is optional)
|
||||
Use internal hash or crc.
|
||||
When this argument is used, the dm-integrity target won't accept
|
||||
integrity tags from the upper target, but it will automatically
|
||||
generate and verify the integrity tags.
|
||||
|
||||
You can use a crc algorithm (such as crc32), then integrity target
|
||||
will protect the data against accidental corruption.
|
||||
You can also use a hmac algorithm (for example
|
||||
"hmac(sha256):0123456789abcdef"), in this mode it will provide
|
||||
cryptographic authentication of the data without encryption.
|
||||
|
||||
When this argument is not used, the integrity tags are accepted
|
||||
from an upper layer target, such as dm-crypt. The upper layer
|
||||
target should check the validity of the integrity tags.
|
||||
|
||||
journal_crypt:algorithm(:key) (the key is optional)
|
||||
Encrypt the journal using given algorithm to make sure that the
|
||||
attacker can't read the journal. You can use a block cipher here
|
||||
(such as "cbc(aes)") or a stream cipher (for example "chacha20",
|
||||
"salsa20", "ctr(aes)" or "ecb(arc4)").
|
||||
|
||||
The journal contains history of last writes to the block device,
|
||||
an attacker reading the journal could see the last sector nubmers
|
||||
that were written. From the sector numbers, the attacker can infer
|
||||
the size of files that were written. To protect against this
|
||||
situation, you can encrypt the journal.
|
||||
|
||||
journal_mac:algorithm(:key) (the key is optional)
|
||||
Protect sector numbers in the journal from accidental or malicious
|
||||
modification. To protect against accidental modification, use a
|
||||
crc algorithm, to protect against malicious modification, use a
|
||||
hmac algorithm with a key.
|
||||
|
||||
This option is not needed when using internal-hash because in this
|
||||
mode, the integrity of journal entries is checked when replaying
|
||||
the journal. Thus, modified sector number would be detected at
|
||||
this stage.
|
||||
|
||||
block_size:number
|
||||
The size of a data block in bytes. The larger the block size the
|
||||
less overhead there is for per-block integrity metadata.
|
||||
Supported values are 512, 1024, 2048 and 4096 bytes. If not
|
||||
specified the default block size is 512 bytes.
|
||||
|
||||
The journal mode (D/J), buffer_sectors, journal_watermark, commit_time can
|
||||
be changed when reloading the target (load an inactive table and swap the
|
||||
tables with suspend and resume). The other arguments should not be changed
|
||||
when reloading the target because the layout of disk data depend on them
|
||||
and the reloaded target would be non-functional.
|
||||
|
||||
|
||||
The layout of the formatted block device:
|
||||
* reserved sectors (they are not used by this target, they can be used for
|
||||
storing LUKS metadata or for other purpose), the size of the reserved
|
||||
area is specified in the target arguments
|
||||
* superblock (4kiB)
|
||||
* magic string - identifies that the device was formatted
|
||||
* version
|
||||
* log2(interleave sectors)
|
||||
* integrity tag size
|
||||
* the number of journal sections
|
||||
* provided data sectors - the number of sectors that this target
|
||||
provides (i.e. the size of the device minus the size of all
|
||||
metadata and padding). The user of this target should not send
|
||||
bios that access data beyond the "provided data sectors" limit.
|
||||
* flags - a flag is set if journal_mac is used
|
||||
* journal
|
||||
The journal is divided into sections, each section contains:
|
||||
* metadata area (4kiB), it contains journal entries
|
||||
every journal entry contains:
|
||||
* logical sector (specifies where the data and tag should
|
||||
be written)
|
||||
* last 8 bytes of data
|
||||
* integrity tag (the size is specified in the superblock)
|
||||
every metadata sector ends with
|
||||
* mac (8-bytes), all the macs in 8 metadata sectors form a
|
||||
64-byte value. It is used to store hmac of sector
|
||||
numbers in the journal section, to protect against a
|
||||
possibility that the attacker tampers with sector
|
||||
numbers in the journal.
|
||||
* commit id
|
||||
* data area (the size is variable; it depends on how many journal
|
||||
entries fit into the metadata area)
|
||||
every sector in the data area contains:
|
||||
* data (504 bytes of data, the last 8 bytes are stored in
|
||||
the journal entry)
|
||||
* commit id
|
||||
To test if the whole journal section was written correctly, every
|
||||
512-byte sector of the journal ends with 8-byte commit id. If the
|
||||
commit id matches on all sectors in a journal section, then it is
|
||||
assumed that the section was written correctly. If the commit id
|
||||
doesn't match, the section was written partially and it should not
|
||||
be replayed.
|
||||
* one or more runs of interleaved tags and data. Each run contains:
|
||||
* tag area - it contains integrity tags. There is one tag for each
|
||||
sector in the data area
|
||||
* data area - it contains data sectors. The number of data sectors
|
||||
in one run must be a power of two. log2 of this value is stored
|
||||
in the superblock.
|
||||
|
|
@ -170,6 +170,13 @@ The target is named "raid" and it accepts the following parameters:
|
|||
Takeover/reshape is not possible with a raid4/5/6 journal device;
|
||||
it has to be deconfigured before requesting these.
|
||||
|
||||
[journal_mode <mode>]
|
||||
This option sets the caching mode on journaled raid4/5/6 raid sets
|
||||
(see 'journal_dev <dev>' above) to 'writethrough' or 'writeback'.
|
||||
If 'writeback' is selected the journal device has to be resilient
|
||||
and must not suffer from the 'write hole' problem itself (e.g. use
|
||||
raid1 or raid10) to avoid a single point of failure.
|
||||
|
||||
<#raid_devs>: The number of devices composing the array.
|
||||
Each device consists of two entries. The first is the device
|
||||
containing the metadata (if any); the second is the one containing the
|
||||
|
|
@ -254,7 +261,8 @@ recovery. Here is a fuller description of the individual fields:
|
|||
<data_offset> The current data offset to the start of the user data on
|
||||
each component device of a raid set (see the respective
|
||||
raid parameter to support out-of-place reshaping).
|
||||
<journal_char> 'A' - active raid4/5/6 journal device.
|
||||
<journal_char> 'A' - active write-through journal device.
|
||||
'a' - active write-back journal device.
|
||||
'D' - dead journal device.
|
||||
'-' - no journal device.
|
||||
|
||||
|
|
@ -331,3 +339,7 @@ Version History
|
|||
'D' on the status line. If '- -' is passed into the constructor, emit
|
||||
'- -' on the table line and '-' as the status line health character.
|
||||
1.10.0 Add support for raid4/5/6 journal device
|
||||
1.10.1 Fix data corruption on reshape request
|
||||
1.11.0 Fix table line argument order
|
||||
(wrong raid10_copies/raid10_format sequence)
|
||||
1.11.1 Add raid4/5/6 journal write-back support via journal_mode option
|
||||
|
|
|
|||
|
|
@ -325,14 +325,6 @@ config DM_CACHE_SMQ
|
|||
of less memory utilization, improved performance and increased
|
||||
adaptability in the face of changing workloads.
|
||||
|
||||
config DM_CACHE_CLEANER
|
||||
tristate "Cleaner Cache Policy (EXPERIMENTAL)"
|
||||
depends on DM_CACHE
|
||||
default y
|
||||
---help---
|
||||
A simple cache policy that writes back all data to the
|
||||
origin. Used when decommissioning a dm-cache.
|
||||
|
||||
config DM_ERA
|
||||
tristate "Era target (EXPERIMENTAL)"
|
||||
depends on BLK_DEV_DM
|
||||
|
|
@ -365,6 +357,7 @@ config DM_LOG_USERSPACE
|
|||
config DM_RAID
|
||||
tristate "RAID 1/4/5/6/10 target"
|
||||
depends on BLK_DEV_DM
|
||||
select MD_RAID0
|
||||
select MD_RAID1
|
||||
select MD_RAID10
|
||||
select MD_RAID456
|
||||
|
|
@ -508,4 +501,14 @@ config DM_LOG_WRITES
|
|||
|
||||
If unsure, say N.
|
||||
|
||||
config DM_INTEGRITY
|
||||
tristate "Integrity target"
|
||||
depends on BLK_DEV_DM
|
||||
select BLK_DEV_INTEGRITY
|
||||
select DM_BUFIO
|
||||
select CRYPTO
|
||||
select ASYNC_XOR
|
||||
---help---
|
||||
This is the integrity target.
|
||||
|
||||
endif # MD
|
||||
|
|
|
|||
|
|
@ -11,10 +11,11 @@ dm-snapshot-y += dm-snap.o dm-exception-store.o dm-snap-transient.o \
|
|||
dm-mirror-y += dm-raid1.o
|
||||
dm-log-userspace-y \
|
||||
+= dm-log-userspace-base.o dm-log-userspace-transfer.o
|
||||
dm-bio-prison-y += dm-bio-prison-v1.o dm-bio-prison-v2.o
|
||||
dm-thin-pool-y += dm-thin.o dm-thin-metadata.o
|
||||
dm-cache-y += dm-cache-target.o dm-cache-metadata.o dm-cache-policy.o
|
||||
dm-cache-y += dm-cache-target.o dm-cache-metadata.o dm-cache-policy.o \
|
||||
dm-cache-background-tracker.o
|
||||
dm-cache-smq-y += dm-cache-policy-smq.o
|
||||
dm-cache-cleaner-y += dm-cache-policy-cleaner.o
|
||||
dm-era-y += dm-era-target.o
|
||||
dm-verity-y += dm-verity-target.o
|
||||
md-mod-y += md.o bitmap.o
|
||||
|
|
@ -56,9 +57,9 @@ obj-$(CONFIG_DM_THIN_PROVISIONING) += dm-thin-pool.o
|
|||
obj-$(CONFIG_DM_VERITY) += dm-verity.o
|
||||
obj-$(CONFIG_DM_CACHE) += dm-cache.o
|
||||
obj-$(CONFIG_DM_CACHE_SMQ) += dm-cache-smq.o
|
||||
obj-$(CONFIG_DM_CACHE_CLEANER) += dm-cache-cleaner.o
|
||||
obj-$(CONFIG_DM_ERA) += dm-era.o
|
||||
obj-$(CONFIG_DM_LOG_WRITES) += dm-log-writes.o
|
||||
obj-$(CONFIG_DM_INTEGRITY) += dm-integrity.o
|
||||
|
||||
ifeq ($(CONFIG_DM_UEVENT),y)
|
||||
dm-mod-objs += dm-uevent.o
|
||||
|
|
|
|||
464
drivers/md/dm-bio-prison-v1.c
Normal file
464
drivers/md/dm-bio-prison-v1.c
Normal file
|
|
@ -0,0 +1,464 @@
|
|||
/*
|
||||
* Copyright (C) 2012 Red Hat, Inc.
|
||||
*
|
||||
* This file is released under the GPL.
|
||||
*/
|
||||
|
||||
#include "dm.h"
|
||||
#include "dm-bio-prison-v1.h"
|
||||
#include "dm-bio-prison-v2.h"
|
||||
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/mempool.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
/*----------------------------------------------------------------*/
|
||||
|
||||
#define MIN_CELLS 1024
|
||||
|
||||
struct dm_bio_prison {
|
||||
spinlock_t lock;
|
||||
mempool_t *cell_pool;
|
||||
struct rb_root cells;
|
||||
};
|
||||
|
||||
static struct kmem_cache *_cell_cache;
|
||||
|
||||
/*----------------------------------------------------------------*/
|
||||
|
||||
/*
|
||||
* @nr_cells should be the number of cells you want in use _concurrently_.
|
||||
* Don't confuse it with the number of distinct keys.
|
||||
*/
|
||||
struct dm_bio_prison *dm_bio_prison_create(void)
|
||||
{
|
||||
struct dm_bio_prison *prison = kmalloc(sizeof(*prison), GFP_KERNEL);
|
||||
|
||||
if (!prison)
|
||||
return NULL;
|
||||
|
||||
spin_lock_init(&prison->lock);
|
||||
|
||||
prison->cell_pool = mempool_create_slab_pool(MIN_CELLS, _cell_cache);
|
||||
if (!prison->cell_pool) {
|
||||
kfree(prison);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
prison->cells = RB_ROOT;
|
||||
|
||||
return prison;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(dm_bio_prison_create);
|
||||
|
||||
void dm_bio_prison_destroy(struct dm_bio_prison *prison)
|
||||
{
|
||||
mempool_destroy(prison->cell_pool);
|
||||
kfree(prison);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(dm_bio_prison_destroy);
|
||||
|
||||
struct dm_bio_prison_cell *dm_bio_prison_alloc_cell(struct dm_bio_prison *prison, gfp_t gfp)
|
||||
{
|
||||
return mempool_alloc(prison->cell_pool, gfp);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(dm_bio_prison_alloc_cell);
|
||||
|
||||
void dm_bio_prison_free_cell(struct dm_bio_prison *prison,
|
||||
struct dm_bio_prison_cell *cell)
|
||||
{
|
||||
mempool_free(cell, prison->cell_pool);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(dm_bio_prison_free_cell);
|
||||
|
||||
static void __setup_new_cell(struct dm_cell_key *key,
|
||||
struct bio *holder,
|
||||
struct dm_bio_prison_cell *cell)
|
||||
{
|
||||
memcpy(&cell->key, key, sizeof(cell->key));
|
||||
cell->holder = holder;
|
||||
bio_list_init(&cell->bios);
|
||||
}
|
||||
|
||||
static int cmp_keys(struct dm_cell_key *lhs,
|
||||
struct dm_cell_key *rhs)
|
||||
{
|
||||
if (lhs->virtual < rhs->virtual)
|
||||
return -1;
|
||||
|
||||
if (lhs->virtual > rhs->virtual)
|
||||
return 1;
|
||||
|
||||
if (lhs->dev < rhs->dev)
|
||||
return -1;
|
||||
|
||||
if (lhs->dev > rhs->dev)
|
||||
return 1;
|
||||
|
||||
if (lhs->block_end <= rhs->block_begin)
|
||||
return -1;
|
||||
|
||||
if (lhs->block_begin >= rhs->block_end)
|
||||
return 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __bio_detain(struct dm_bio_prison *prison,
|
||||
struct dm_cell_key *key,
|
||||
struct bio *inmate,
|
||||
struct dm_bio_prison_cell *cell_prealloc,
|
||||
struct dm_bio_prison_cell **cell_result)
|
||||
{
|
||||
int r;
|
||||
struct rb_node **new = &prison->cells.rb_node, *parent = NULL;
|
||||
|
||||
while (*new) {
|
||||
struct dm_bio_prison_cell *cell =
|
||||
container_of(*new, struct dm_bio_prison_cell, node);
|
||||
|
||||
r = cmp_keys(key, &cell->key);
|
||||
|
||||
parent = *new;
|
||||
if (r < 0)
|
||||
new = &((*new)->rb_left);
|
||||
else if (r > 0)
|
||||
new = &((*new)->rb_right);
|
||||
else {
|
||||
if (inmate)
|
||||
bio_list_add(&cell->bios, inmate);
|
||||
*cell_result = cell;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
__setup_new_cell(key, inmate, cell_prealloc);
|
||||
*cell_result = cell_prealloc;
|
||||
|
||||
rb_link_node(&cell_prealloc->node, parent, new);
|
||||
rb_insert_color(&cell_prealloc->node, &prison->cells);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int bio_detain(struct dm_bio_prison *prison,
|
||||
struct dm_cell_key *key,
|
||||
struct bio *inmate,
|
||||
struct dm_bio_prison_cell *cell_prealloc,
|
||||
struct dm_bio_prison_cell **cell_result)
|
||||
{
|
||||
int r;
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&prison->lock, flags);
|
||||
r = __bio_detain(prison, key, inmate, cell_prealloc, cell_result);
|
||||
spin_unlock_irqrestore(&prison->lock, flags);
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
int dm_bio_detain(struct dm_bio_prison *prison,
|
||||
struct dm_cell_key *key,
|
||||
struct bio *inmate,
|
||||
struct dm_bio_prison_cell *cell_prealloc,
|
||||
struct dm_bio_prison_cell **cell_result)
|
||||
{
|
||||
return bio_detain(prison, key, inmate, cell_prealloc, cell_result);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(dm_bio_detain);
|
||||
|
||||
int dm_get_cell(struct dm_bio_prison *prison,
|
||||
struct dm_cell_key *key,
|
||||
struct dm_bio_prison_cell *cell_prealloc,
|
||||
struct dm_bio_prison_cell **cell_result)
|
||||
{
|
||||
return bio_detain(prison, key, NULL, cell_prealloc, cell_result);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(dm_get_cell);
|
||||
|
||||
/*
|
||||
* @inmates must have been initialised prior to this call
|
||||
*/
|
||||
static void __cell_release(struct dm_bio_prison *prison,
|
||||
struct dm_bio_prison_cell *cell,
|
||||
struct bio_list *inmates)
|
||||
{
|
||||
rb_erase(&cell->node, &prison->cells);
|
||||
|
||||
if (inmates) {
|
||||
if (cell->holder)
|
||||
bio_list_add(inmates, cell->holder);
|
||||
bio_list_merge(inmates, &cell->bios);
|
||||
}
|
||||
}
|
||||
|
||||
void dm_cell_release(struct dm_bio_prison *prison,
|
||||
struct dm_bio_prison_cell *cell,
|
||||
struct bio_list *bios)
|
||||
{
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&prison->lock, flags);
|
||||
__cell_release(prison, cell, bios);
|
||||
spin_unlock_irqrestore(&prison->lock, flags);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(dm_cell_release);
|
||||
|
||||
/*
|
||||
* Sometimes we don't want the holder, just the additional bios.
|
||||
*/
|
||||
static void __cell_release_no_holder(struct dm_bio_prison *prison,
|
||||
struct dm_bio_prison_cell *cell,
|
||||
struct bio_list *inmates)
|
||||
{
|
||||
rb_erase(&cell->node, &prison->cells);
|
||||
bio_list_merge(inmates, &cell->bios);
|
||||
}
|
||||
|
||||
void dm_cell_release_no_holder(struct dm_bio_prison *prison,
|
||||
struct dm_bio_prison_cell *cell,
|
||||
struct bio_list *inmates)
|
||||
{
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&prison->lock, flags);
|
||||
__cell_release_no_holder(prison, cell, inmates);
|
||||
spin_unlock_irqrestore(&prison->lock, flags);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(dm_cell_release_no_holder);
|
||||
|
||||
void dm_cell_error(struct dm_bio_prison *prison,
|
||||
struct dm_bio_prison_cell *cell, int error)
|
||||
{
|
||||
struct bio_list bios;
|
||||
struct bio *bio;
|
||||
|
||||
bio_list_init(&bios);
|
||||
dm_cell_release(prison, cell, &bios);
|
||||
|
||||
while ((bio = bio_list_pop(&bios))) {
|
||||
bio->bi_error = error;
|
||||
bio_endio(bio);
|
||||
}
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(dm_cell_error);
|
||||
|
||||
void dm_cell_visit_release(struct dm_bio_prison *prison,
|
||||
void (*visit_fn)(void *, struct dm_bio_prison_cell *),
|
||||
void *context,
|
||||
struct dm_bio_prison_cell *cell)
|
||||
{
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&prison->lock, flags);
|
||||
visit_fn(context, cell);
|
||||
rb_erase(&cell->node, &prison->cells);
|
||||
spin_unlock_irqrestore(&prison->lock, flags);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(dm_cell_visit_release);
|
||||
|
||||
static int __promote_or_release(struct dm_bio_prison *prison,
|
||||
struct dm_bio_prison_cell *cell)
|
||||
{
|
||||
if (bio_list_empty(&cell->bios)) {
|
||||
rb_erase(&cell->node, &prison->cells);
|
||||
return 1;
|
||||
}
|
||||
|
||||
cell->holder = bio_list_pop(&cell->bios);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int dm_cell_promote_or_release(struct dm_bio_prison *prison,
|
||||
struct dm_bio_prison_cell *cell)
|
||||
{
|
||||
int r;
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&prison->lock, flags);
|
||||
r = __promote_or_release(prison, cell);
|
||||
spin_unlock_irqrestore(&prison->lock, flags);
|
||||
|
||||
return r;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(dm_cell_promote_or_release);
|
||||
|
||||
/*----------------------------------------------------------------*/
|
||||
|
||||
#define DEFERRED_SET_SIZE 64
|
||||
|
||||
struct dm_deferred_entry {
|
||||
struct dm_deferred_set *ds;
|
||||
unsigned count;
|
||||
struct list_head work_items;
|
||||
};
|
||||
|
||||
struct dm_deferred_set {
|
||||
spinlock_t lock;
|
||||
unsigned current_entry;
|
||||
unsigned sweeper;
|
||||
struct dm_deferred_entry entries[DEFERRED_SET_SIZE];
|
||||
};
|
||||
|
||||
struct dm_deferred_set *dm_deferred_set_create(void)
|
||||
{
|
||||
int i;
|
||||
struct dm_deferred_set *ds;
|
||||
|
||||
ds = kmalloc(sizeof(*ds), GFP_KERNEL);
|
||||
if (!ds)
|
||||
return NULL;
|
||||
|
||||
spin_lock_init(&ds->lock);
|
||||
ds->current_entry = 0;
|
||||
ds->sweeper = 0;
|
||||
for (i = 0; i < DEFERRED_SET_SIZE; i++) {
|
||||
ds->entries[i].ds = ds;
|
||||
ds->entries[i].count = 0;
|
||||
INIT_LIST_HEAD(&ds->entries[i].work_items);
|
||||
}
|
||||
|
||||
return ds;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(dm_deferred_set_create);
|
||||
|
||||
void dm_deferred_set_destroy(struct dm_deferred_set *ds)
|
||||
{
|
||||
kfree(ds);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(dm_deferred_set_destroy);
|
||||
|
||||
struct dm_deferred_entry *dm_deferred_entry_inc(struct dm_deferred_set *ds)
|
||||
{
|
||||
unsigned long flags;
|
||||
struct dm_deferred_entry *entry;
|
||||
|
||||
spin_lock_irqsave(&ds->lock, flags);
|
||||
entry = ds->entries + ds->current_entry;
|
||||
entry->count++;
|
||||
spin_unlock_irqrestore(&ds->lock, flags);
|
||||
|
||||
return entry;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(dm_deferred_entry_inc);
|
||||
|
||||
static unsigned ds_next(unsigned index)
|
||||
{
|
||||
return (index + 1) % DEFERRED_SET_SIZE;
|
||||
}
|
||||
|
||||
static void __sweep(struct dm_deferred_set *ds, struct list_head *head)
|
||||
{
|
||||
while ((ds->sweeper != ds->current_entry) &&
|
||||
!ds->entries[ds->sweeper].count) {
|
||||
list_splice_init(&ds->entries[ds->sweeper].work_items, head);
|
||||
ds->sweeper = ds_next(ds->sweeper);
|
||||
}
|
||||
|
||||
if ((ds->sweeper == ds->current_entry) && !ds->entries[ds->sweeper].count)
|
||||
list_splice_init(&ds->entries[ds->sweeper].work_items, head);
|
||||
}
|
||||
|
||||
void dm_deferred_entry_dec(struct dm_deferred_entry *entry, struct list_head *head)
|
||||
{
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&entry->ds->lock, flags);
|
||||
BUG_ON(!entry->count);
|
||||
--entry->count;
|
||||
__sweep(entry->ds, head);
|
||||
spin_unlock_irqrestore(&entry->ds->lock, flags);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(dm_deferred_entry_dec);
|
||||
|
||||
/*
|
||||
* Returns 1 if deferred or 0 if no pending items to delay job.
|
||||
*/
|
||||
int dm_deferred_set_add_work(struct dm_deferred_set *ds, struct list_head *work)
|
||||
{
|
||||
int r = 1;
|
||||
unsigned long flags;
|
||||
unsigned next_entry;
|
||||
|
||||
spin_lock_irqsave(&ds->lock, flags);
|
||||
if ((ds->sweeper == ds->current_entry) &&
|
||||
!ds->entries[ds->current_entry].count)
|
||||
r = 0;
|
||||
else {
|
||||
list_add(work, &ds->entries[ds->current_entry].work_items);
|
||||
next_entry = ds_next(ds->current_entry);
|
||||
if (!ds->entries[next_entry].count)
|
||||
ds->current_entry = next_entry;
|
||||
}
|
||||
spin_unlock_irqrestore(&ds->lock, flags);
|
||||
|
||||
return r;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(dm_deferred_set_add_work);
|
||||
|
||||
/*----------------------------------------------------------------*/
|
||||
|
||||
static int __init dm_bio_prison_init_v1(void)
|
||||
{
|
||||
_cell_cache = KMEM_CACHE(dm_bio_prison_cell, 0);
|
||||
if (!_cell_cache)
|
||||
return -ENOMEM;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void dm_bio_prison_exit_v1(void)
|
||||
{
|
||||
kmem_cache_destroy(_cell_cache);
|
||||
_cell_cache = NULL;
|
||||
}
|
||||
|
||||
static int (*_inits[])(void) __initdata = {
|
||||
dm_bio_prison_init_v1,
|
||||
dm_bio_prison_init_v2,
|
||||
};
|
||||
|
||||
static void (*_exits[])(void) = {
|
||||
dm_bio_prison_exit_v1,
|
||||
dm_bio_prison_exit_v2,
|
||||
};
|
||||
|
||||
static int __init dm_bio_prison_init(void)
|
||||
{
|
||||
const int count = ARRAY_SIZE(_inits);
|
||||
|
||||
int r, i;
|
||||
|
||||
for (i = 0; i < count; i++) {
|
||||
r = _inits[i]();
|
||||
if (r)
|
||||
goto bad;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
bad:
|
||||
while (i--)
|
||||
_exits[i]();
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
static void __exit dm_bio_prison_exit(void)
|
||||
{
|
||||
int i = ARRAY_SIZE(_exits);
|
||||
|
||||
while (i--)
|
||||
_exits[i]();
|
||||
}
|
||||
|
||||
/*
|
||||
* module hooks
|
||||
*/
|
||||
module_init(dm_bio_prison_init);
|
||||
module_exit(dm_bio_prison_exit);
|
||||
|
||||
MODULE_DESCRIPTION(DM_NAME " bio prison");
|
||||
MODULE_AUTHOR("Joe Thornber <dm-devel@redhat.com>");
|
||||
MODULE_LICENSE("GPL");
|
||||
138
drivers/md/dm-bio-prison-v1.h
Normal file
138
drivers/md/dm-bio-prison-v1.h
Normal file
|
|
@ -0,0 +1,138 @@
|
|||
/*
|
||||
* Copyright (C) 2011-2017 Red Hat, Inc.
|
||||
*
|
||||
* This file is released under the GPL.
|
||||
*/
|
||||
|
||||
#ifndef DM_BIO_PRISON_H
|
||||
#define DM_BIO_PRISON_H
|
||||
|
||||
#include "persistent-data/dm-block-manager.h" /* FIXME: for dm_block_t */
|
||||
#include "dm-thin-metadata.h" /* FIXME: for dm_thin_id */
|
||||
|
||||
#include <linux/bio.h>
|
||||
#include <linux/rbtree.h>
|
||||
|
||||
/*----------------------------------------------------------------*/
|
||||
|
||||
/*
|
||||
* Sometimes we can't deal with a bio straight away. We put them in prison
|
||||
* where they can't cause any mischief. Bios are put in a cell identified
|
||||
* by a key, multiple bios can be in the same cell. When the cell is
|
||||
* subsequently unlocked the bios become available.
|
||||
*/
|
||||
struct dm_bio_prison;
|
||||
|
||||
/*
|
||||
* Keys define a range of blocks within either a virtual or physical
|
||||
* device.
|
||||
*/
|
||||
struct dm_cell_key {
|
||||
int virtual;
|
||||
dm_thin_id dev;
|
||||
dm_block_t block_begin, block_end;
|
||||
};
|
||||
|
||||
/*
|
||||
* Treat this as opaque, only in header so callers can manage allocation
|
||||
* themselves.
|
||||
*/
|
||||
struct dm_bio_prison_cell {
|
||||
struct list_head user_list; /* for client use */
|
||||
struct rb_node node;
|
||||
|
||||
struct dm_cell_key key;
|
||||
struct bio *holder;
|
||||
struct bio_list bios;
|
||||
};
|
||||
|
||||
struct dm_bio_prison *dm_bio_prison_create(void);
|
||||
void dm_bio_prison_destroy(struct dm_bio_prison *prison);
|
||||
|
||||
/*
|
||||
* These two functions just wrap a mempool. This is a transitory step:
|
||||
* Eventually all bio prison clients should manage their own cell memory.
|
||||
*
|
||||
* Like mempool_alloc(), dm_bio_prison_alloc_cell() can only fail if called
|
||||
* in interrupt context or passed GFP_NOWAIT.
|
||||
*/
|
||||
struct dm_bio_prison_cell *dm_bio_prison_alloc_cell(struct dm_bio_prison *prison,
|
||||
gfp_t gfp);
|
||||
void dm_bio_prison_free_cell(struct dm_bio_prison *prison,
|
||||
struct dm_bio_prison_cell *cell);
|
||||
|
||||
/*
|
||||
* Creates, or retrieves a cell that overlaps the given key.
|
||||
*
|
||||
* Returns 1 if pre-existing cell returned, zero if new cell created using
|
||||
* @cell_prealloc.
|
||||
*/
|
||||
int dm_get_cell(struct dm_bio_prison *prison,
|
||||
struct dm_cell_key *key,
|
||||
struct dm_bio_prison_cell *cell_prealloc,
|
||||
struct dm_bio_prison_cell **cell_result);
|
||||
|
||||
/*
|
||||
* An atomic op that combines retrieving or creating a cell, and adding a
|
||||
* bio to it.
|
||||
*
|
||||
* Returns 1 if the cell was already held, 0 if @inmate is the new holder.
|
||||
*/
|
||||
int dm_bio_detain(struct dm_bio_prison *prison,
|
||||
struct dm_cell_key *key,
|
||||
struct bio *inmate,
|
||||
struct dm_bio_prison_cell *cell_prealloc,
|
||||
struct dm_bio_prison_cell **cell_result);
|
||||
|
||||
void dm_cell_release(struct dm_bio_prison *prison,
|
||||
struct dm_bio_prison_cell *cell,
|
||||
struct bio_list *bios);
|
||||
void dm_cell_release_no_holder(struct dm_bio_prison *prison,
|
||||
struct dm_bio_prison_cell *cell,
|
||||
struct bio_list *inmates);
|
||||
void dm_cell_error(struct dm_bio_prison *prison,
|
||||
struct dm_bio_prison_cell *cell, int error);
|
||||
|
||||
/*
|
||||
* Visits the cell and then releases. Guarantees no new inmates are
|
||||
* inserted between the visit and release.
|
||||
*/
|
||||
void dm_cell_visit_release(struct dm_bio_prison *prison,
|
||||
void (*visit_fn)(void *, struct dm_bio_prison_cell *),
|
||||
void *context, struct dm_bio_prison_cell *cell);
|
||||
|
||||
/*
|
||||
* Rather than always releasing the prisoners in a cell, the client may
|
||||
* want to promote one of them to be the new holder. There is a race here
|
||||
* though between releasing an empty cell, and other threads adding new
|
||||
* inmates. So this function makes the decision with its lock held.
|
||||
*
|
||||
* This function can have two outcomes:
|
||||
* i) An inmate is promoted to be the holder of the cell (return value of 0).
|
||||
* ii) The cell has no inmate for promotion and is released (return value of 1).
|
||||
*/
|
||||
int dm_cell_promote_or_release(struct dm_bio_prison *prison,
|
||||
struct dm_bio_prison_cell *cell);
|
||||
|
||||
/*----------------------------------------------------------------*/
|
||||
|
||||
/*
|
||||
* We use the deferred set to keep track of pending reads to shared blocks.
|
||||
* We do this to ensure the new mapping caused by a write isn't performed
|
||||
* until these prior reads have completed. Otherwise the insertion of the
|
||||
* new mapping could free the old block that the read bios are mapped to.
|
||||
*/
|
||||
|
||||
struct dm_deferred_set;
|
||||
struct dm_deferred_entry;
|
||||
|
||||
struct dm_deferred_set *dm_deferred_set_create(void);
|
||||
void dm_deferred_set_destroy(struct dm_deferred_set *ds);
|
||||
|
||||
struct dm_deferred_entry *dm_deferred_entry_inc(struct dm_deferred_set *ds);
|
||||
void dm_deferred_entry_dec(struct dm_deferred_entry *entry, struct list_head *head);
|
||||
int dm_deferred_set_add_work(struct dm_deferred_set *ds, struct list_head *work);
|
||||
|
||||
/*----------------------------------------------------------------*/
|
||||
|
||||
#endif
|
||||
369
drivers/md/dm-bio-prison-v2.c
Normal file
369
drivers/md/dm-bio-prison-v2.c
Normal file
|
|
@ -0,0 +1,369 @@
|
|||
/*
|
||||
* Copyright (C) 2012-2017 Red Hat, Inc.
|
||||
*
|
||||
* This file is released under the GPL.
|
||||
*/
|
||||
|
||||
#include "dm.h"
|
||||
#include "dm-bio-prison-v2.h"
|
||||
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/mempool.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/rwsem.h>
|
||||
|
||||
/*----------------------------------------------------------------*/
|
||||
|
||||
#define MIN_CELLS 1024
|
||||
|
||||
struct dm_bio_prison_v2 {
|
||||
struct workqueue_struct *wq;
|
||||
|
||||
spinlock_t lock;
|
||||
mempool_t *cell_pool;
|
||||
struct rb_root cells;
|
||||
};
|
||||
|
||||
static struct kmem_cache *_cell_cache;
|
||||
|
||||
/*----------------------------------------------------------------*/
|
||||
|
||||
/*
|
||||
* @nr_cells should be the number of cells you want in use _concurrently_.
|
||||
* Don't confuse it with the number of distinct keys.
|
||||
*/
|
||||
struct dm_bio_prison_v2 *dm_bio_prison_create_v2(struct workqueue_struct *wq)
|
||||
{
|
||||
struct dm_bio_prison_v2 *prison = kmalloc(sizeof(*prison), GFP_KERNEL);
|
||||
|
||||
if (!prison)
|
||||
return NULL;
|
||||
|
||||
prison->wq = wq;
|
||||
spin_lock_init(&prison->lock);
|
||||
|
||||
prison->cell_pool = mempool_create_slab_pool(MIN_CELLS, _cell_cache);
|
||||
if (!prison->cell_pool) {
|
||||
kfree(prison);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
prison->cells = RB_ROOT;
|
||||
|
||||
return prison;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(dm_bio_prison_create_v2);
|
||||
|
||||
void dm_bio_prison_destroy_v2(struct dm_bio_prison_v2 *prison)
|
||||
{
|
||||
mempool_destroy(prison->cell_pool);
|
||||
kfree(prison);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(dm_bio_prison_destroy_v2);
|
||||
|
||||
struct dm_bio_prison_cell_v2 *dm_bio_prison_alloc_cell_v2(struct dm_bio_prison_v2 *prison, gfp_t gfp)
|
||||
{
|
||||
return mempool_alloc(prison->cell_pool, gfp);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(dm_bio_prison_alloc_cell_v2);
|
||||
|
||||
void dm_bio_prison_free_cell_v2(struct dm_bio_prison_v2 *prison,
|
||||
struct dm_bio_prison_cell_v2 *cell)
|
||||
{
|
||||
mempool_free(cell, prison->cell_pool);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(dm_bio_prison_free_cell_v2);
|
||||
|
||||
static void __setup_new_cell(struct dm_cell_key_v2 *key,
|
||||
struct dm_bio_prison_cell_v2 *cell)
|
||||
{
|
||||
memset(cell, 0, sizeof(*cell));
|
||||
memcpy(&cell->key, key, sizeof(cell->key));
|
||||
bio_list_init(&cell->bios);
|
||||
}
|
||||
|
||||
static int cmp_keys(struct dm_cell_key_v2 *lhs,
|
||||
struct dm_cell_key_v2 *rhs)
|
||||
{
|
||||
if (lhs->virtual < rhs->virtual)
|
||||
return -1;
|
||||
|
||||
if (lhs->virtual > rhs->virtual)
|
||||
return 1;
|
||||
|
||||
if (lhs->dev < rhs->dev)
|
||||
return -1;
|
||||
|
||||
if (lhs->dev > rhs->dev)
|
||||
return 1;
|
||||
|
||||
if (lhs->block_end <= rhs->block_begin)
|
||||
return -1;
|
||||
|
||||
if (lhs->block_begin >= rhs->block_end)
|
||||
return 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns true if node found, otherwise it inserts a new one.
|
||||
*/
|
||||
static bool __find_or_insert(struct dm_bio_prison_v2 *prison,
|
||||
struct dm_cell_key_v2 *key,
|
||||
struct dm_bio_prison_cell_v2 *cell_prealloc,
|
||||
struct dm_bio_prison_cell_v2 **result)
|
||||
{
|
||||
int r;
|
||||
struct rb_node **new = &prison->cells.rb_node, *parent = NULL;
|
||||
|
||||
while (*new) {
|
||||
struct dm_bio_prison_cell_v2 *cell =
|
||||
container_of(*new, struct dm_bio_prison_cell_v2, node);
|
||||
|
||||
r = cmp_keys(key, &cell->key);
|
||||
|
||||
parent = *new;
|
||||
if (r < 0)
|
||||
new = &((*new)->rb_left);
|
||||
|
||||
else if (r > 0)
|
||||
new = &((*new)->rb_right);
|
||||
|
||||
else {
|
||||
*result = cell;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
__setup_new_cell(key, cell_prealloc);
|
||||
*result = cell_prealloc;
|
||||
rb_link_node(&cell_prealloc->node, parent, new);
|
||||
rb_insert_color(&cell_prealloc->node, &prison->cells);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool __get(struct dm_bio_prison_v2 *prison,
|
||||
struct dm_cell_key_v2 *key,
|
||||
unsigned lock_level,
|
||||
struct bio *inmate,
|
||||
struct dm_bio_prison_cell_v2 *cell_prealloc,
|
||||
struct dm_bio_prison_cell_v2 **cell)
|
||||
{
|
||||
if (__find_or_insert(prison, key, cell_prealloc, cell)) {
|
||||
if ((*cell)->exclusive_lock) {
|
||||
if (lock_level <= (*cell)->exclusive_level) {
|
||||
bio_list_add(&(*cell)->bios, inmate);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
(*cell)->shared_count++;
|
||||
|
||||
} else
|
||||
(*cell)->shared_count = 1;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool dm_cell_get_v2(struct dm_bio_prison_v2 *prison,
|
||||
struct dm_cell_key_v2 *key,
|
||||
unsigned lock_level,
|
||||
struct bio *inmate,
|
||||
struct dm_bio_prison_cell_v2 *cell_prealloc,
|
||||
struct dm_bio_prison_cell_v2 **cell_result)
|
||||
{
|
||||
int r;
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&prison->lock, flags);
|
||||
r = __get(prison, key, lock_level, inmate, cell_prealloc, cell_result);
|
||||
spin_unlock_irqrestore(&prison->lock, flags);
|
||||
|
||||
return r;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(dm_cell_get_v2);
|
||||
|
||||
static bool __put(struct dm_bio_prison_v2 *prison,
|
||||
struct dm_bio_prison_cell_v2 *cell)
|
||||
{
|
||||
BUG_ON(!cell->shared_count);
|
||||
cell->shared_count--;
|
||||
|
||||
// FIXME: shared locks granted above the lock level could starve this
|
||||
if (!cell->shared_count) {
|
||||
if (cell->exclusive_lock){
|
||||
if (cell->quiesce_continuation) {
|
||||
queue_work(prison->wq, cell->quiesce_continuation);
|
||||
cell->quiesce_continuation = NULL;
|
||||
}
|
||||
} else {
|
||||
rb_erase(&cell->node, &prison->cells);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool dm_cell_put_v2(struct dm_bio_prison_v2 *prison,
|
||||
struct dm_bio_prison_cell_v2 *cell)
|
||||
{
|
||||
bool r;
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&prison->lock, flags);
|
||||
r = __put(prison, cell);
|
||||
spin_unlock_irqrestore(&prison->lock, flags);
|
||||
|
||||
return r;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(dm_cell_put_v2);
|
||||
|
||||
static int __lock(struct dm_bio_prison_v2 *prison,
|
||||
struct dm_cell_key_v2 *key,
|
||||
unsigned lock_level,
|
||||
struct dm_bio_prison_cell_v2 *cell_prealloc,
|
||||
struct dm_bio_prison_cell_v2 **cell_result)
|
||||
{
|
||||
struct dm_bio_prison_cell_v2 *cell;
|
||||
|
||||
if (__find_or_insert(prison, key, cell_prealloc, &cell)) {
|
||||
if (cell->exclusive_lock)
|
||||
return -EBUSY;
|
||||
|
||||
cell->exclusive_lock = true;
|
||||
cell->exclusive_level = lock_level;
|
||||
*cell_result = cell;
|
||||
|
||||
// FIXME: we don't yet know what level these shared locks
|
||||
// were taken at, so have to quiesce them all.
|
||||
return cell->shared_count > 0;
|
||||
|
||||
} else {
|
||||
cell = cell_prealloc;
|
||||
cell->shared_count = 0;
|
||||
cell->exclusive_lock = true;
|
||||
cell->exclusive_level = lock_level;
|
||||
*cell_result = cell;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int dm_cell_lock_v2(struct dm_bio_prison_v2 *prison,
|
||||
struct dm_cell_key_v2 *key,
|
||||
unsigned lock_level,
|
||||
struct dm_bio_prison_cell_v2 *cell_prealloc,
|
||||
struct dm_bio_prison_cell_v2 **cell_result)
|
||||
{
|
||||
int r;
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&prison->lock, flags);
|
||||
r = __lock(prison, key, lock_level, cell_prealloc, cell_result);
|
||||
spin_unlock_irqrestore(&prison->lock, flags);
|
||||
|
||||
return r;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(dm_cell_lock_v2);
|
||||
|
||||
static void __quiesce(struct dm_bio_prison_v2 *prison,
|
||||
struct dm_bio_prison_cell_v2 *cell,
|
||||
struct work_struct *continuation)
|
||||
{
|
||||
if (!cell->shared_count)
|
||||
queue_work(prison->wq, continuation);
|
||||
else
|
||||
cell->quiesce_continuation = continuation;
|
||||
}
|
||||
|
||||
void dm_cell_quiesce_v2(struct dm_bio_prison_v2 *prison,
|
||||
struct dm_bio_prison_cell_v2 *cell,
|
||||
struct work_struct *continuation)
|
||||
{
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&prison->lock, flags);
|
||||
__quiesce(prison, cell, continuation);
|
||||
spin_unlock_irqrestore(&prison->lock, flags);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(dm_cell_quiesce_v2);
|
||||
|
||||
static int __promote(struct dm_bio_prison_v2 *prison,
|
||||
struct dm_bio_prison_cell_v2 *cell,
|
||||
unsigned new_lock_level)
|
||||
{
|
||||
if (!cell->exclusive_lock)
|
||||
return -EINVAL;
|
||||
|
||||
cell->exclusive_level = new_lock_level;
|
||||
return cell->shared_count > 0;
|
||||
}
|
||||
|
||||
int dm_cell_lock_promote_v2(struct dm_bio_prison_v2 *prison,
|
||||
struct dm_bio_prison_cell_v2 *cell,
|
||||
unsigned new_lock_level)
|
||||
{
|
||||
int r;
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&prison->lock, flags);
|
||||
r = __promote(prison, cell, new_lock_level);
|
||||
spin_unlock_irqrestore(&prison->lock, flags);
|
||||
|
||||
return r;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(dm_cell_lock_promote_v2);
|
||||
|
||||
static bool __unlock(struct dm_bio_prison_v2 *prison,
|
||||
struct dm_bio_prison_cell_v2 *cell,
|
||||
struct bio_list *bios)
|
||||
{
|
||||
BUG_ON(!cell->exclusive_lock);
|
||||
|
||||
bio_list_merge(bios, &cell->bios);
|
||||
bio_list_init(&cell->bios);
|
||||
|
||||
if (cell->shared_count) {
|
||||
cell->exclusive_lock = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
rb_erase(&cell->node, &prison->cells);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool dm_cell_unlock_v2(struct dm_bio_prison_v2 *prison,
|
||||
struct dm_bio_prison_cell_v2 *cell,
|
||||
struct bio_list *bios)
|
||||
{
|
||||
bool r;
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&prison->lock, flags);
|
||||
r = __unlock(prison, cell, bios);
|
||||
spin_unlock_irqrestore(&prison->lock, flags);
|
||||
|
||||
return r;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(dm_cell_unlock_v2);
|
||||
|
||||
/*----------------------------------------------------------------*/
|
||||
|
||||
int __init dm_bio_prison_init_v2(void)
|
||||
{
|
||||
_cell_cache = KMEM_CACHE(dm_bio_prison_cell_v2, 0);
|
||||
if (!_cell_cache)
|
||||
return -ENOMEM;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void dm_bio_prison_exit_v2(void)
|
||||
{
|
||||
kmem_cache_destroy(_cell_cache);
|
||||
_cell_cache = NULL;
|
||||
}
|
||||
152
drivers/md/dm-bio-prison-v2.h
Normal file
152
drivers/md/dm-bio-prison-v2.h
Normal file
|
|
@ -0,0 +1,152 @@
|
|||
/*
|
||||
* Copyright (C) 2011-2017 Red Hat, Inc.
|
||||
*
|
||||
* This file is released under the GPL.
|
||||
*/
|
||||
|
||||
#ifndef DM_BIO_PRISON_V2_H
|
||||
#define DM_BIO_PRISON_V2_H
|
||||
|
||||
#include "persistent-data/dm-block-manager.h" /* FIXME: for dm_block_t */
|
||||
#include "dm-thin-metadata.h" /* FIXME: for dm_thin_id */
|
||||
|
||||
#include <linux/bio.h>
|
||||
#include <linux/rbtree.h>
|
||||
#include <linux/workqueue.h>
|
||||
|
||||
/*----------------------------------------------------------------*/
|
||||
|
||||
int dm_bio_prison_init_v2(void);
|
||||
void dm_bio_prison_exit_v2(void);
|
||||
|
||||
/*
|
||||
* Sometimes we can't deal with a bio straight away. We put them in prison
|
||||
* where they can't cause any mischief. Bios are put in a cell identified
|
||||
* by a key, multiple bios can be in the same cell. When the cell is
|
||||
* subsequently unlocked the bios become available.
|
||||
*/
|
||||
struct dm_bio_prison_v2;
|
||||
|
||||
/*
|
||||
* Keys define a range of blocks within either a virtual or physical
|
||||
* device.
|
||||
*/
|
||||
struct dm_cell_key_v2 {
|
||||
int virtual;
|
||||
dm_thin_id dev;
|
||||
dm_block_t block_begin, block_end;
|
||||
};
|
||||
|
||||
/*
|
||||
* Treat this as opaque, only in header so callers can manage allocation
|
||||
* themselves.
|
||||
*/
|
||||
struct dm_bio_prison_cell_v2 {
|
||||
// FIXME: pack these
|
||||
bool exclusive_lock;
|
||||
unsigned exclusive_level;
|
||||
unsigned shared_count;
|
||||
struct work_struct *quiesce_continuation;
|
||||
|
||||
struct rb_node node;
|
||||
struct dm_cell_key_v2 key;
|
||||
struct bio_list bios;
|
||||
};
|
||||
|
||||
struct dm_bio_prison_v2 *dm_bio_prison_create_v2(struct workqueue_struct *wq);
|
||||
void dm_bio_prison_destroy_v2(struct dm_bio_prison_v2 *prison);
|
||||
|
||||
/*
|
||||
* These two functions just wrap a mempool. This is a transitory step:
|
||||
* Eventually all bio prison clients should manage their own cell memory.
|
||||
*
|
||||
* Like mempool_alloc(), dm_bio_prison_alloc_cell_v2() can only fail if called
|
||||
* in interrupt context or passed GFP_NOWAIT.
|
||||
*/
|
||||
struct dm_bio_prison_cell_v2 *dm_bio_prison_alloc_cell_v2(struct dm_bio_prison_v2 *prison,
|
||||
gfp_t gfp);
|
||||
void dm_bio_prison_free_cell_v2(struct dm_bio_prison_v2 *prison,
|
||||
struct dm_bio_prison_cell_v2 *cell);
|
||||
|
||||
/*
|
||||
* Shared locks have a bio associated with them.
|
||||
*
|
||||
* If the lock is granted the caller can continue to use the bio, and must
|
||||
* call dm_cell_put_v2() to drop the reference count when finished using it.
|
||||
*
|
||||
* If the lock cannot be granted then the bio will be tracked within the
|
||||
* cell, and later given to the holder of the exclusive lock.
|
||||
*
|
||||
* See dm_cell_lock_v2() for discussion of the lock_level parameter.
|
||||
*
|
||||
* Compare *cell_result with cell_prealloc to see if the prealloc was used.
|
||||
* If cell_prealloc was used then inmate wasn't added to it.
|
||||
*
|
||||
* Returns true if the lock is granted.
|
||||
*/
|
||||
bool dm_cell_get_v2(struct dm_bio_prison_v2 *prison,
|
||||
struct dm_cell_key_v2 *key,
|
||||
unsigned lock_level,
|
||||
struct bio *inmate,
|
||||
struct dm_bio_prison_cell_v2 *cell_prealloc,
|
||||
struct dm_bio_prison_cell_v2 **cell_result);
|
||||
|
||||
/*
|
||||
* Decrement the shared reference count for the lock. Returns true if
|
||||
* returning ownership of the cell (ie. you should free it).
|
||||
*/
|
||||
bool dm_cell_put_v2(struct dm_bio_prison_v2 *prison,
|
||||
struct dm_bio_prison_cell_v2 *cell);
|
||||
|
||||
/*
|
||||
* Locks a cell. No associated bio. Exclusive locks get priority. These
|
||||
* locks constrain whether the io locks are granted according to level.
|
||||
*
|
||||
* Shared locks will still be granted if the lock_level is > (not = to) the
|
||||
* exclusive lock level.
|
||||
*
|
||||
* If an _exclusive_ lock is already held then -EBUSY is returned.
|
||||
*
|
||||
* Return values:
|
||||
* < 0 - error
|
||||
* 0 - locked; no quiescing needed
|
||||
* 1 - locked; quiescing needed
|
||||
*/
|
||||
int dm_cell_lock_v2(struct dm_bio_prison_v2 *prison,
|
||||
struct dm_cell_key_v2 *key,
|
||||
unsigned lock_level,
|
||||
struct dm_bio_prison_cell_v2 *cell_prealloc,
|
||||
struct dm_bio_prison_cell_v2 **cell_result);
|
||||
|
||||
void dm_cell_quiesce_v2(struct dm_bio_prison_v2 *prison,
|
||||
struct dm_bio_prison_cell_v2 *cell,
|
||||
struct work_struct *continuation);
|
||||
|
||||
/*
|
||||
* Promotes an _exclusive_ lock to a higher lock level.
|
||||
*
|
||||
* Return values:
|
||||
* < 0 - error
|
||||
* 0 - promoted; no quiescing needed
|
||||
* 1 - promoted; quiescing needed
|
||||
*/
|
||||
int dm_cell_lock_promote_v2(struct dm_bio_prison_v2 *prison,
|
||||
struct dm_bio_prison_cell_v2 *cell,
|
||||
unsigned new_lock_level);
|
||||
|
||||
/*
|
||||
* Adds any held bios to the bio list.
|
||||
*
|
||||
* There may be shared locks still held at this point even if you quiesced
|
||||
* (ie. different lock levels).
|
||||
*
|
||||
* Returns true if returning ownership of the cell (ie. you should free
|
||||
* it).
|
||||
*/
|
||||
bool dm_cell_unlock_v2(struct dm_bio_prison_v2 *prison,
|
||||
struct dm_bio_prison_cell_v2 *cell,
|
||||
struct bio_list *bios);
|
||||
|
||||
/*----------------------------------------------------------------*/
|
||||
|
||||
#endif
|
||||
|
|
@ -110,6 +110,8 @@ struct dm_bufio_client {
|
|||
struct rb_root buffer_tree;
|
||||
wait_queue_head_t free_buffer_wait;
|
||||
|
||||
sector_t start;
|
||||
|
||||
int async_write_error;
|
||||
|
||||
struct list_head client_list;
|
||||
|
|
@ -557,8 +559,8 @@ static void dmio_complete(unsigned long error, void *context)
|
|||
b->bio.bi_end_io(&b->bio);
|
||||
}
|
||||
|
||||
static void use_dmio(struct dm_buffer *b, int rw, sector_t block,
|
||||
bio_end_io_t *end_io)
|
||||
static void use_dmio(struct dm_buffer *b, int rw, sector_t sector,
|
||||
unsigned n_sectors, bio_end_io_t *end_io)
|
||||
{
|
||||
int r;
|
||||
struct dm_io_request io_req = {
|
||||
|
|
@ -570,8 +572,8 @@ static void use_dmio(struct dm_buffer *b, int rw, sector_t block,
|
|||
};
|
||||
struct dm_io_region region = {
|
||||
.bdev = b->c->bdev,
|
||||
.sector = block << b->c->sectors_per_block_bits,
|
||||
.count = b->c->block_size >> SECTOR_SHIFT,
|
||||
.sector = sector,
|
||||
.count = n_sectors,
|
||||
};
|
||||
|
||||
if (b->data_mode != DATA_MODE_VMALLOC) {
|
||||
|
|
@ -606,14 +608,14 @@ static void inline_endio(struct bio *bio)
|
|||
end_fn(bio);
|
||||
}
|
||||
|
||||
static void use_inline_bio(struct dm_buffer *b, int rw, sector_t block,
|
||||
bio_end_io_t *end_io)
|
||||
static void use_inline_bio(struct dm_buffer *b, int rw, sector_t sector,
|
||||
unsigned n_sectors, bio_end_io_t *end_io)
|
||||
{
|
||||
char *ptr;
|
||||
int len;
|
||||
|
||||
bio_init(&b->bio, b->bio_vec, DM_BUFIO_INLINE_VECS);
|
||||
b->bio.bi_iter.bi_sector = block << b->c->sectors_per_block_bits;
|
||||
b->bio.bi_iter.bi_sector = sector;
|
||||
b->bio.bi_bdev = b->c->bdev;
|
||||
b->bio.bi_end_io = inline_endio;
|
||||
/*
|
||||
|
|
@ -628,7 +630,7 @@ static void use_inline_bio(struct dm_buffer *b, int rw, sector_t block,
|
|||
* If len < PAGE_SIZE the buffer doesn't cross page boundary.
|
||||
*/
|
||||
ptr = b->data;
|
||||
len = b->c->block_size;
|
||||
len = n_sectors << SECTOR_SHIFT;
|
||||
|
||||
if (len >= PAGE_SIZE)
|
||||
BUG_ON((unsigned long)ptr & (PAGE_SIZE - 1));
|
||||
|
|
@ -640,7 +642,7 @@ static void use_inline_bio(struct dm_buffer *b, int rw, sector_t block,
|
|||
len < PAGE_SIZE ? len : PAGE_SIZE,
|
||||
offset_in_page(ptr))) {
|
||||
BUG_ON(b->c->block_size <= PAGE_SIZE);
|
||||
use_dmio(b, rw, block, end_io);
|
||||
use_dmio(b, rw, sector, n_sectors, end_io);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -651,17 +653,22 @@ static void use_inline_bio(struct dm_buffer *b, int rw, sector_t block,
|
|||
submit_bio(&b->bio);
|
||||
}
|
||||
|
||||
static void submit_io(struct dm_buffer *b, int rw, sector_t block,
|
||||
bio_end_io_t *end_io)
|
||||
static void submit_io(struct dm_buffer *b, int rw, bio_end_io_t *end_io)
|
||||
{
|
||||
unsigned n_sectors;
|
||||
sector_t sector;
|
||||
|
||||
if (rw == WRITE && b->c->write_callback)
|
||||
b->c->write_callback(b);
|
||||
|
||||
if (b->c->block_size <= DM_BUFIO_INLINE_VECS * PAGE_SIZE &&
|
||||
sector = (b->block << b->c->sectors_per_block_bits) + b->c->start;
|
||||
n_sectors = 1 << b->c->sectors_per_block_bits;
|
||||
|
||||
if (n_sectors <= ((DM_BUFIO_INLINE_VECS * PAGE_SIZE) >> SECTOR_SHIFT) &&
|
||||
b->data_mode != DATA_MODE_VMALLOC)
|
||||
use_inline_bio(b, rw, block, end_io);
|
||||
use_inline_bio(b, rw, sector, n_sectors, end_io);
|
||||
else
|
||||
use_dmio(b, rw, block, end_io);
|
||||
use_dmio(b, rw, sector, n_sectors, end_io);
|
||||
}
|
||||
|
||||
/*----------------------------------------------------------------
|
||||
|
|
@ -713,7 +720,7 @@ static void __write_dirty_buffer(struct dm_buffer *b,
|
|||
wait_on_bit_lock_io(&b->state, B_WRITING, TASK_UNINTERRUPTIBLE);
|
||||
|
||||
if (!write_list)
|
||||
submit_io(b, WRITE, b->block, write_endio);
|
||||
submit_io(b, WRITE, write_endio);
|
||||
else
|
||||
list_add_tail(&b->write_list, write_list);
|
||||
}
|
||||
|
|
@ -726,7 +733,7 @@ static void __flush_write_list(struct list_head *write_list)
|
|||
struct dm_buffer *b =
|
||||
list_entry(write_list->next, struct dm_buffer, write_list);
|
||||
list_del(&b->write_list);
|
||||
submit_io(b, WRITE, b->block, write_endio);
|
||||
submit_io(b, WRITE, write_endio);
|
||||
cond_resched();
|
||||
}
|
||||
blk_finish_plug(&plug);
|
||||
|
|
@ -933,10 +940,11 @@ static void __get_memory_limit(struct dm_bufio_client *c,
|
|||
{
|
||||
unsigned long buffers;
|
||||
|
||||
if (ACCESS_ONCE(dm_bufio_cache_size) != dm_bufio_cache_size_latch) {
|
||||
mutex_lock(&dm_bufio_clients_lock);
|
||||
__cache_size_refresh();
|
||||
mutex_unlock(&dm_bufio_clients_lock);
|
||||
if (unlikely(ACCESS_ONCE(dm_bufio_cache_size) != dm_bufio_cache_size_latch)) {
|
||||
if (mutex_trylock(&dm_bufio_clients_lock)) {
|
||||
__cache_size_refresh();
|
||||
mutex_unlock(&dm_bufio_clients_lock);
|
||||
}
|
||||
}
|
||||
|
||||
buffers = dm_bufio_cache_size_per_client >>
|
||||
|
|
@ -1094,7 +1102,7 @@ static void *new_read(struct dm_bufio_client *c, sector_t block,
|
|||
return NULL;
|
||||
|
||||
if (need_submit)
|
||||
submit_io(b, READ, b->block, read_endio);
|
||||
submit_io(b, READ, read_endio);
|
||||
|
||||
wait_on_bit_io(&b->state, B_READING, TASK_UNINTERRUPTIBLE);
|
||||
|
||||
|
|
@ -1164,7 +1172,7 @@ void dm_bufio_prefetch(struct dm_bufio_client *c,
|
|||
dm_bufio_unlock(c);
|
||||
|
||||
if (need_submit)
|
||||
submit_io(b, READ, b->block, read_endio);
|
||||
submit_io(b, READ, read_endio);
|
||||
dm_bufio_release(b);
|
||||
|
||||
cond_resched();
|
||||
|
|
@ -1405,7 +1413,7 @@ retry:
|
|||
old_block = b->block;
|
||||
__unlink_buffer(b);
|
||||
__link_buffer(b, new_block, b->list_mode);
|
||||
submit_io(b, WRITE, new_block, write_endio);
|
||||
submit_io(b, WRITE, write_endio);
|
||||
wait_on_bit_io(&b->state, B_WRITING,
|
||||
TASK_UNINTERRUPTIBLE);
|
||||
__unlink_buffer(b);
|
||||
|
|
@ -1762,6 +1770,12 @@ void dm_bufio_client_destroy(struct dm_bufio_client *c)
|
|||
}
|
||||
EXPORT_SYMBOL_GPL(dm_bufio_client_destroy);
|
||||
|
||||
void dm_bufio_set_sector_offset(struct dm_bufio_client *c, sector_t start)
|
||||
{
|
||||
c->start = start;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(dm_bufio_set_sector_offset);
|
||||
|
||||
static unsigned get_max_age_hz(void)
|
||||
{
|
||||
unsigned max_age = ACCESS_ONCE(dm_bufio_max_age);
|
||||
|
|
@ -1782,9 +1796,17 @@ static void __evict_old_buffers(struct dm_bufio_client *c, unsigned long age_hz)
|
|||
struct dm_buffer *b, *tmp;
|
||||
unsigned retain_target = get_retain_buffers(c);
|
||||
unsigned count;
|
||||
LIST_HEAD(write_list);
|
||||
|
||||
dm_bufio_lock(c);
|
||||
|
||||
__check_watermark(c, &write_list);
|
||||
if (unlikely(!list_empty(&write_list))) {
|
||||
dm_bufio_unlock(c);
|
||||
__flush_write_list(&write_list);
|
||||
dm_bufio_lock(c);
|
||||
}
|
||||
|
||||
count = c->n_buffers[LIST_CLEAN] + c->n_buffers[LIST_DIRTY];
|
||||
list_for_each_entry_safe_reverse(b, tmp, &c->lru[LIST_CLEAN], lru_list) {
|
||||
if (count <= retain_target)
|
||||
|
|
@ -1809,6 +1831,8 @@ static void cleanup_old_buffers(void)
|
|||
|
||||
mutex_lock(&dm_bufio_clients_lock);
|
||||
|
||||
__cache_size_refresh();
|
||||
|
||||
list_for_each_entry(c, &dm_bufio_all_clients, client_list)
|
||||
__evict_old_buffers(c, max_age_hz);
|
||||
|
||||
|
|
|
|||
|
|
@ -31,6 +31,13 @@ dm_bufio_client_create(struct block_device *bdev, unsigned block_size,
|
|||
*/
|
||||
void dm_bufio_client_destroy(struct dm_bufio_client *c);
|
||||
|
||||
/*
|
||||
* Set the sector range.
|
||||
* When this function is called, there must be no I/O in progress on the bufio
|
||||
* client.
|
||||
*/
|
||||
void dm_bufio_set_sector_offset(struct dm_bufio_client *c, sector_t start);
|
||||
|
||||
/*
|
||||
* WARNING: to avoid deadlocks, these conditions are observed:
|
||||
*
|
||||
|
|
|
|||
238
drivers/md/dm-cache-background-tracker.c
Normal file
238
drivers/md/dm-cache-background-tracker.c
Normal file
|
|
@ -0,0 +1,238 @@
|
|||
/*
|
||||
* Copyright (C) 2017 Red Hat. All rights reserved.
|
||||
*
|
||||
* This file is released under the GPL.
|
||||
*/
|
||||
|
||||
#include "dm-cache-background-tracker.h"
|
||||
|
||||
/*----------------------------------------------------------------*/
|
||||
|
||||
#define DM_MSG_PREFIX "dm-background-tracker"
|
||||
|
||||
struct bt_work {
|
||||
struct list_head list;
|
||||
struct rb_node node;
|
||||
struct policy_work work;
|
||||
};
|
||||
|
||||
struct background_tracker {
|
||||
unsigned max_work;
|
||||
atomic_t pending_promotes;
|
||||
atomic_t pending_writebacks;
|
||||
atomic_t pending_demotes;
|
||||
|
||||
struct list_head issued;
|
||||
struct list_head queued;
|
||||
struct rb_root pending;
|
||||
|
||||
struct kmem_cache *work_cache;
|
||||
};
|
||||
|
||||
struct background_tracker *btracker_create(unsigned max_work)
|
||||
{
|
||||
struct background_tracker *b = kmalloc(sizeof(*b), GFP_KERNEL);
|
||||
|
||||
b->max_work = max_work;
|
||||
atomic_set(&b->pending_promotes, 0);
|
||||
atomic_set(&b->pending_writebacks, 0);
|
||||
atomic_set(&b->pending_demotes, 0);
|
||||
|
||||
INIT_LIST_HEAD(&b->issued);
|
||||
INIT_LIST_HEAD(&b->queued);
|
||||
|
||||
b->pending = RB_ROOT;
|
||||
b->work_cache = KMEM_CACHE(bt_work, 0);
|
||||
if (!b->work_cache) {
|
||||
DMERR("couldn't create mempool for background work items");
|
||||
kfree(b);
|
||||
b = NULL;
|
||||
}
|
||||
|
||||
return b;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(btracker_create);
|
||||
|
||||
void btracker_destroy(struct background_tracker *b)
|
||||
{
|
||||
kmem_cache_destroy(b->work_cache);
|
||||
kfree(b);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(btracker_destroy);
|
||||
|
||||
static int cmp_oblock(dm_oblock_t lhs, dm_oblock_t rhs)
|
||||
{
|
||||
if (from_oblock(lhs) < from_oblock(rhs))
|
||||
return -1;
|
||||
|
||||
if (from_oblock(rhs) < from_oblock(lhs))
|
||||
return 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool __insert_pending(struct background_tracker *b,
|
||||
struct bt_work *nw)
|
||||
{
|
||||
int cmp;
|
||||
struct bt_work *w;
|
||||
struct rb_node **new = &b->pending.rb_node, *parent = NULL;
|
||||
|
||||
while (*new) {
|
||||
w = container_of(*new, struct bt_work, node);
|
||||
|
||||
parent = *new;
|
||||
cmp = cmp_oblock(w->work.oblock, nw->work.oblock);
|
||||
if (cmp < 0)
|
||||
new = &((*new)->rb_left);
|
||||
|
||||
else if (cmp > 0)
|
||||
new = &((*new)->rb_right);
|
||||
|
||||
else
|
||||
/* already present */
|
||||
return false;
|
||||
}
|
||||
|
||||
rb_link_node(&nw->node, parent, new);
|
||||
rb_insert_color(&nw->node, &b->pending);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static struct bt_work *__find_pending(struct background_tracker *b,
|
||||
dm_oblock_t oblock)
|
||||
{
|
||||
int cmp;
|
||||
struct bt_work *w;
|
||||
struct rb_node **new = &b->pending.rb_node;
|
||||
|
||||
while (*new) {
|
||||
w = container_of(*new, struct bt_work, node);
|
||||
|
||||
cmp = cmp_oblock(w->work.oblock, oblock);
|
||||
if (cmp < 0)
|
||||
new = &((*new)->rb_left);
|
||||
|
||||
else if (cmp > 0)
|
||||
new = &((*new)->rb_right);
|
||||
|
||||
else
|
||||
break;
|
||||
}
|
||||
|
||||
return *new ? w : NULL;
|
||||
}
|
||||
|
||||
|
||||
static void update_stats(struct background_tracker *b, struct policy_work *w, int delta)
|
||||
{
|
||||
switch (w->op) {
|
||||
case POLICY_PROMOTE:
|
||||
atomic_add(delta, &b->pending_promotes);
|
||||
break;
|
||||
|
||||
case POLICY_DEMOTE:
|
||||
atomic_add(delta, &b->pending_demotes);
|
||||
break;
|
||||
|
||||
case POLICY_WRITEBACK:
|
||||
atomic_add(delta, &b->pending_writebacks);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
unsigned btracker_nr_writebacks_queued(struct background_tracker *b)
|
||||
{
|
||||
return atomic_read(&b->pending_writebacks);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(btracker_nr_writebacks_queued);
|
||||
|
||||
unsigned btracker_nr_demotions_queued(struct background_tracker *b)
|
||||
{
|
||||
return atomic_read(&b->pending_demotes);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(btracker_nr_demotions_queued);
|
||||
|
||||
static bool max_work_reached(struct background_tracker *b)
|
||||
{
|
||||
// FIXME: finish
|
||||
return false;
|
||||
}
|
||||
|
||||
int btracker_queue(struct background_tracker *b,
|
||||
struct policy_work *work,
|
||||
struct policy_work **pwork)
|
||||
{
|
||||
struct bt_work *w;
|
||||
|
||||
if (pwork)
|
||||
*pwork = NULL;
|
||||
|
||||
if (max_work_reached(b))
|
||||
return -ENOMEM;
|
||||
|
||||
w = kmem_cache_alloc(b->work_cache, GFP_NOWAIT);
|
||||
if (!w)
|
||||
return -ENOMEM;
|
||||
|
||||
memcpy(&w->work, work, sizeof(*work));
|
||||
|
||||
if (!__insert_pending(b, w)) {
|
||||
/*
|
||||
* There was a race, we'll just ignore this second
|
||||
* bit of work for the same oblock.
|
||||
*/
|
||||
kmem_cache_free(b->work_cache, w);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (pwork) {
|
||||
*pwork = &w->work;
|
||||
list_add(&w->list, &b->issued);
|
||||
} else
|
||||
list_add(&w->list, &b->queued);
|
||||
update_stats(b, &w->work, 1);
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(btracker_queue);
|
||||
|
||||
/*
|
||||
* Returns -ENODATA if there's no work.
|
||||
*/
|
||||
int btracker_issue(struct background_tracker *b, struct policy_work **work)
|
||||
{
|
||||
struct bt_work *w;
|
||||
|
||||
if (list_empty(&b->queued))
|
||||
return -ENODATA;
|
||||
|
||||
w = list_first_entry(&b->queued, struct bt_work, list);
|
||||
list_move(&w->list, &b->issued);
|
||||
*work = &w->work;
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(btracker_issue);
|
||||
|
||||
void btracker_complete(struct background_tracker *b,
|
||||
struct policy_work *op)
|
||||
{
|
||||
struct bt_work *w = container_of(op, struct bt_work, work);
|
||||
|
||||
update_stats(b, &w->work, -1);
|
||||
rb_erase(&w->node, &b->pending);
|
||||
list_del(&w->list);
|
||||
kmem_cache_free(b->work_cache, w);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(btracker_complete);
|
||||
|
||||
bool btracker_promotion_already_present(struct background_tracker *b,
|
||||
dm_oblock_t oblock)
|
||||
{
|
||||
return __find_pending(b, oblock) != NULL;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(btracker_promotion_already_present);
|
||||
|
||||
/*----------------------------------------------------------------*/
|
||||
46
drivers/md/dm-cache-background-tracker.h
Normal file
46
drivers/md/dm-cache-background-tracker.h
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* Copyright (C) 2017 Red Hat. All rights reserved.
|
||||
*
|
||||
* This file is released under the GPL.
|
||||
*/
|
||||
|
||||
#ifndef DM_CACHE_BACKGROUND_WORK_H
|
||||
#define DM_CACHE_BACKGROUND_WORK_H
|
||||
|
||||
#include <linux/vmalloc.h>
|
||||
#include "dm-cache-policy.h"
|
||||
|
||||
/*----------------------------------------------------------------*/
|
||||
|
||||
struct background_work;
|
||||
struct background_tracker;
|
||||
|
||||
/*
|
||||
* FIXME: discuss lack of locking in all methods.
|
||||
*/
|
||||
struct background_tracker *btracker_create(unsigned max_work);
|
||||
void btracker_destroy(struct background_tracker *b);
|
||||
|
||||
unsigned btracker_nr_writebacks_queued(struct background_tracker *b);
|
||||
unsigned btracker_nr_demotions_queued(struct background_tracker *b);
|
||||
|
||||
/*
|
||||
* returns -EINVAL iff the work is already queued. -ENOMEM if the work
|
||||
* couldn't be queued for another reason.
|
||||
*/
|
||||
int btracker_queue(struct background_tracker *b,
|
||||
struct policy_work *work,
|
||||
struct policy_work **pwork);
|
||||
|
||||
/*
|
||||
* Returns -ENODATA if there's no work.
|
||||
*/
|
||||
int btracker_issue(struct background_tracker *b, struct policy_work **work);
|
||||
void btracker_complete(struct background_tracker *b,
|
||||
struct policy_work *op);
|
||||
bool btracker_promotion_already_present(struct background_tracker *b,
|
||||
dm_oblock_t oblock);
|
||||
|
||||
/*----------------------------------------------------------------*/
|
||||
|
||||
#endif
|
||||
|
|
@ -27,8 +27,6 @@
|
|||
#define MIN_CACHE_VERSION 1
|
||||
#define MAX_CACHE_VERSION 2
|
||||
|
||||
#define CACHE_METADATA_CACHE_SIZE 64
|
||||
|
||||
/*
|
||||
* 3 for btree insert +
|
||||
* 2 for btree lookup used within space map
|
||||
|
|
@ -535,7 +533,6 @@ static int __create_persistent_data_objects(struct dm_cache_metadata *cmd,
|
|||
{
|
||||
int r;
|
||||
cmd->bm = dm_block_manager_create(cmd->bdev, DM_CACHE_METADATA_BLOCK_SIZE << SECTOR_SHIFT,
|
||||
CACHE_METADATA_CACHE_SIZE,
|
||||
CACHE_MAX_CONCURRENT_LOCKS);
|
||||
if (IS_ERR(cmd->bm)) {
|
||||
DMERR("could not create block manager");
|
||||
|
|
|
|||
|
|
@ -50,6 +50,8 @@
|
|||
#define DM_CACHE_FEATURE_COMPAT_RO_SUPP 0UL
|
||||
#define DM_CACHE_FEATURE_INCOMPAT_SUPP 0UL
|
||||
|
||||
struct dm_cache_metadata;
|
||||
|
||||
/*
|
||||
* Reopens or creates a new, empty metadata volume. Returns an ERR_PTR on
|
||||
* failure. If reopening then features must match.
|
||||
|
|
|
|||
|
|
@ -1,469 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2012 Red Hat. All rights reserved.
|
||||
*
|
||||
* writeback cache policy supporting flushing out dirty cache blocks.
|
||||
*
|
||||
* This file is released under the GPL.
|
||||
*/
|
||||
|
||||
#include "dm-cache-policy.h"
|
||||
#include "dm.h"
|
||||
|
||||
#include <linux/hash.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/vmalloc.h>
|
||||
|
||||
/*----------------------------------------------------------------*/
|
||||
|
||||
#define DM_MSG_PREFIX "cache cleaner"
|
||||
|
||||
/* Cache entry struct. */
|
||||
struct wb_cache_entry {
|
||||
struct list_head list;
|
||||
struct hlist_node hlist;
|
||||
|
||||
dm_oblock_t oblock;
|
||||
dm_cblock_t cblock;
|
||||
bool dirty:1;
|
||||
bool pending:1;
|
||||
};
|
||||
|
||||
struct hash {
|
||||
struct hlist_head *table;
|
||||
dm_block_t hash_bits;
|
||||
unsigned nr_buckets;
|
||||
};
|
||||
|
||||
struct policy {
|
||||
struct dm_cache_policy policy;
|
||||
spinlock_t lock;
|
||||
|
||||
struct list_head free;
|
||||
struct list_head clean;
|
||||
struct list_head clean_pending;
|
||||
struct list_head dirty;
|
||||
|
||||
/*
|
||||
* We know exactly how many cblocks will be needed,
|
||||
* so we can allocate them up front.
|
||||
*/
|
||||
dm_cblock_t cache_size, nr_cblocks_allocated;
|
||||
struct wb_cache_entry *cblocks;
|
||||
struct hash chash;
|
||||
};
|
||||
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
/*
|
||||
* Low-level functions.
|
||||
*/
|
||||
static unsigned next_power(unsigned n, unsigned min)
|
||||
{
|
||||
return roundup_pow_of_two(max(n, min));
|
||||
}
|
||||
|
||||
static struct policy *to_policy(struct dm_cache_policy *p)
|
||||
{
|
||||
return container_of(p, struct policy, policy);
|
||||
}
|
||||
|
||||
static struct list_head *list_pop(struct list_head *q)
|
||||
{
|
||||
struct list_head *r = q->next;
|
||||
|
||||
list_del(r);
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
/* Allocate/free various resources. */
|
||||
static int alloc_hash(struct hash *hash, unsigned elts)
|
||||
{
|
||||
hash->nr_buckets = next_power(elts >> 4, 16);
|
||||
hash->hash_bits = __ffs(hash->nr_buckets);
|
||||
hash->table = vzalloc(sizeof(*hash->table) * hash->nr_buckets);
|
||||
|
||||
return hash->table ? 0 : -ENOMEM;
|
||||
}
|
||||
|
||||
static void free_hash(struct hash *hash)
|
||||
{
|
||||
vfree(hash->table);
|
||||
}
|
||||
|
||||
static int alloc_cache_blocks_with_hash(struct policy *p, dm_cblock_t cache_size)
|
||||
{
|
||||
int r = -ENOMEM;
|
||||
|
||||
p->cblocks = vzalloc(sizeof(*p->cblocks) * from_cblock(cache_size));
|
||||
if (p->cblocks) {
|
||||
unsigned u = from_cblock(cache_size);
|
||||
|
||||
while (u--)
|
||||
list_add(&p->cblocks[u].list, &p->free);
|
||||
|
||||
p->nr_cblocks_allocated = 0;
|
||||
|
||||
/* Cache entries hash. */
|
||||
r = alloc_hash(&p->chash, from_cblock(cache_size));
|
||||
if (r)
|
||||
vfree(p->cblocks);
|
||||
}
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
static void free_cache_blocks_and_hash(struct policy *p)
|
||||
{
|
||||
free_hash(&p->chash);
|
||||
vfree(p->cblocks);
|
||||
}
|
||||
|
||||
static struct wb_cache_entry *alloc_cache_entry(struct policy *p)
|
||||
{
|
||||
struct wb_cache_entry *e;
|
||||
|
||||
BUG_ON(from_cblock(p->nr_cblocks_allocated) >= from_cblock(p->cache_size));
|
||||
|
||||
e = list_entry(list_pop(&p->free), struct wb_cache_entry, list);
|
||||
p->nr_cblocks_allocated = to_cblock(from_cblock(p->nr_cblocks_allocated) + 1);
|
||||
|
||||
return e;
|
||||
}
|
||||
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
/* Hash functions (lookup, insert, remove). */
|
||||
static struct wb_cache_entry *lookup_cache_entry(struct policy *p, dm_oblock_t oblock)
|
||||
{
|
||||
struct hash *hash = &p->chash;
|
||||
unsigned h = hash_64(from_oblock(oblock), hash->hash_bits);
|
||||
struct wb_cache_entry *cur;
|
||||
struct hlist_head *bucket = &hash->table[h];
|
||||
|
||||
hlist_for_each_entry(cur, bucket, hlist) {
|
||||
if (cur->oblock == oblock) {
|
||||
/* Move upfront bucket for faster access. */
|
||||
hlist_del(&cur->hlist);
|
||||
hlist_add_head(&cur->hlist, bucket);
|
||||
return cur;
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void insert_cache_hash_entry(struct policy *p, struct wb_cache_entry *e)
|
||||
{
|
||||
unsigned h = hash_64(from_oblock(e->oblock), p->chash.hash_bits);
|
||||
|
||||
hlist_add_head(&e->hlist, &p->chash.table[h]);
|
||||
}
|
||||
|
||||
static void remove_cache_hash_entry(struct wb_cache_entry *e)
|
||||
{
|
||||
hlist_del(&e->hlist);
|
||||
}
|
||||
|
||||
/* Public interface (see dm-cache-policy.h */
|
||||
static int wb_map(struct dm_cache_policy *pe, dm_oblock_t oblock,
|
||||
bool can_block, bool can_migrate, bool discarded_oblock,
|
||||
struct bio *bio, struct policy_locker *locker,
|
||||
struct policy_result *result)
|
||||
{
|
||||
struct policy *p = to_policy(pe);
|
||||
struct wb_cache_entry *e;
|
||||
unsigned long flags;
|
||||
|
||||
result->op = POLICY_MISS;
|
||||
|
||||
if (can_block)
|
||||
spin_lock_irqsave(&p->lock, flags);
|
||||
|
||||
else if (!spin_trylock_irqsave(&p->lock, flags))
|
||||
return -EWOULDBLOCK;
|
||||
|
||||
e = lookup_cache_entry(p, oblock);
|
||||
if (e) {
|
||||
result->op = POLICY_HIT;
|
||||
result->cblock = e->cblock;
|
||||
|
||||
}
|
||||
|
||||
spin_unlock_irqrestore(&p->lock, flags);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int wb_lookup(struct dm_cache_policy *pe, dm_oblock_t oblock, dm_cblock_t *cblock)
|
||||
{
|
||||
int r;
|
||||
struct policy *p = to_policy(pe);
|
||||
struct wb_cache_entry *e;
|
||||
unsigned long flags;
|
||||
|
||||
if (!spin_trylock_irqsave(&p->lock, flags))
|
||||
return -EWOULDBLOCK;
|
||||
|
||||
e = lookup_cache_entry(p, oblock);
|
||||
if (e) {
|
||||
*cblock = e->cblock;
|
||||
r = 0;
|
||||
|
||||
} else
|
||||
r = -ENOENT;
|
||||
|
||||
spin_unlock_irqrestore(&p->lock, flags);
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
static void __set_clear_dirty(struct dm_cache_policy *pe, dm_oblock_t oblock, bool set)
|
||||
{
|
||||
struct policy *p = to_policy(pe);
|
||||
struct wb_cache_entry *e;
|
||||
|
||||
e = lookup_cache_entry(p, oblock);
|
||||
BUG_ON(!e);
|
||||
|
||||
if (set) {
|
||||
if (!e->dirty) {
|
||||
e->dirty = true;
|
||||
list_move(&e->list, &p->dirty);
|
||||
}
|
||||
|
||||
} else {
|
||||
if (e->dirty) {
|
||||
e->pending = false;
|
||||
e->dirty = false;
|
||||
list_move(&e->list, &p->clean);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void wb_set_dirty(struct dm_cache_policy *pe, dm_oblock_t oblock)
|
||||
{
|
||||
struct policy *p = to_policy(pe);
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&p->lock, flags);
|
||||
__set_clear_dirty(pe, oblock, true);
|
||||
spin_unlock_irqrestore(&p->lock, flags);
|
||||
}
|
||||
|
||||
static void wb_clear_dirty(struct dm_cache_policy *pe, dm_oblock_t oblock)
|
||||
{
|
||||
struct policy *p = to_policy(pe);
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&p->lock, flags);
|
||||
__set_clear_dirty(pe, oblock, false);
|
||||
spin_unlock_irqrestore(&p->lock, flags);
|
||||
}
|
||||
|
||||
static void add_cache_entry(struct policy *p, struct wb_cache_entry *e)
|
||||
{
|
||||
insert_cache_hash_entry(p, e);
|
||||
if (e->dirty)
|
||||
list_add(&e->list, &p->dirty);
|
||||
else
|
||||
list_add(&e->list, &p->clean);
|
||||
}
|
||||
|
||||
static int wb_load_mapping(struct dm_cache_policy *pe,
|
||||
dm_oblock_t oblock, dm_cblock_t cblock,
|
||||
uint32_t hint, bool hint_valid)
|
||||
{
|
||||
int r;
|
||||
struct policy *p = to_policy(pe);
|
||||
struct wb_cache_entry *e = alloc_cache_entry(p);
|
||||
|
||||
if (e) {
|
||||
e->cblock = cblock;
|
||||
e->oblock = oblock;
|
||||
e->dirty = false; /* blocks default to clean */
|
||||
add_cache_entry(p, e);
|
||||
r = 0;
|
||||
|
||||
} else
|
||||
r = -ENOMEM;
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
static void wb_destroy(struct dm_cache_policy *pe)
|
||||
{
|
||||
struct policy *p = to_policy(pe);
|
||||
|
||||
free_cache_blocks_and_hash(p);
|
||||
kfree(p);
|
||||
}
|
||||
|
||||
static struct wb_cache_entry *__wb_force_remove_mapping(struct policy *p, dm_oblock_t oblock)
|
||||
{
|
||||
struct wb_cache_entry *r = lookup_cache_entry(p, oblock);
|
||||
|
||||
BUG_ON(!r);
|
||||
|
||||
remove_cache_hash_entry(r);
|
||||
list_del(&r->list);
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
static void wb_remove_mapping(struct dm_cache_policy *pe, dm_oblock_t oblock)
|
||||
{
|
||||
struct policy *p = to_policy(pe);
|
||||
struct wb_cache_entry *e;
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&p->lock, flags);
|
||||
e = __wb_force_remove_mapping(p, oblock);
|
||||
list_add_tail(&e->list, &p->free);
|
||||
BUG_ON(!from_cblock(p->nr_cblocks_allocated));
|
||||
p->nr_cblocks_allocated = to_cblock(from_cblock(p->nr_cblocks_allocated) - 1);
|
||||
spin_unlock_irqrestore(&p->lock, flags);
|
||||
}
|
||||
|
||||
static void wb_force_mapping(struct dm_cache_policy *pe,
|
||||
dm_oblock_t current_oblock, dm_oblock_t oblock)
|
||||
{
|
||||
struct policy *p = to_policy(pe);
|
||||
struct wb_cache_entry *e;
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&p->lock, flags);
|
||||
e = __wb_force_remove_mapping(p, current_oblock);
|
||||
e->oblock = oblock;
|
||||
add_cache_entry(p, e);
|
||||
spin_unlock_irqrestore(&p->lock, flags);
|
||||
}
|
||||
|
||||
static struct wb_cache_entry *get_next_dirty_entry(struct policy *p)
|
||||
{
|
||||
struct list_head *l;
|
||||
struct wb_cache_entry *r;
|
||||
|
||||
if (list_empty(&p->dirty))
|
||||
return NULL;
|
||||
|
||||
l = list_pop(&p->dirty);
|
||||
r = container_of(l, struct wb_cache_entry, list);
|
||||
list_add(l, &p->clean_pending);
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
static int wb_writeback_work(struct dm_cache_policy *pe,
|
||||
dm_oblock_t *oblock,
|
||||
dm_cblock_t *cblock,
|
||||
bool critical_only)
|
||||
{
|
||||
int r = -ENOENT;
|
||||
struct policy *p = to_policy(pe);
|
||||
struct wb_cache_entry *e;
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&p->lock, flags);
|
||||
|
||||
e = get_next_dirty_entry(p);
|
||||
if (e) {
|
||||
*oblock = e->oblock;
|
||||
*cblock = e->cblock;
|
||||
r = 0;
|
||||
}
|
||||
|
||||
spin_unlock_irqrestore(&p->lock, flags);
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
static dm_cblock_t wb_residency(struct dm_cache_policy *pe)
|
||||
{
|
||||
return to_policy(pe)->nr_cblocks_allocated;
|
||||
}
|
||||
|
||||
/* Init the policy plugin interface function pointers. */
|
||||
static void init_policy_functions(struct policy *p)
|
||||
{
|
||||
p->policy.destroy = wb_destroy;
|
||||
p->policy.map = wb_map;
|
||||
p->policy.lookup = wb_lookup;
|
||||
p->policy.set_dirty = wb_set_dirty;
|
||||
p->policy.clear_dirty = wb_clear_dirty;
|
||||
p->policy.load_mapping = wb_load_mapping;
|
||||
p->policy.get_hint = NULL;
|
||||
p->policy.remove_mapping = wb_remove_mapping;
|
||||
p->policy.writeback_work = wb_writeback_work;
|
||||
p->policy.force_mapping = wb_force_mapping;
|
||||
p->policy.residency = wb_residency;
|
||||
p->policy.tick = NULL;
|
||||
}
|
||||
|
||||
static struct dm_cache_policy *wb_create(dm_cblock_t cache_size,
|
||||
sector_t origin_size,
|
||||
sector_t cache_block_size)
|
||||
{
|
||||
int r;
|
||||
struct policy *p = kzalloc(sizeof(*p), GFP_KERNEL);
|
||||
|
||||
if (!p)
|
||||
return NULL;
|
||||
|
||||
init_policy_functions(p);
|
||||
INIT_LIST_HEAD(&p->free);
|
||||
INIT_LIST_HEAD(&p->clean);
|
||||
INIT_LIST_HEAD(&p->clean_pending);
|
||||
INIT_LIST_HEAD(&p->dirty);
|
||||
|
||||
p->cache_size = cache_size;
|
||||
spin_lock_init(&p->lock);
|
||||
|
||||
/* Allocate cache entry structs and add them to free list. */
|
||||
r = alloc_cache_blocks_with_hash(p, cache_size);
|
||||
if (!r)
|
||||
return &p->policy;
|
||||
|
||||
kfree(p);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
static struct dm_cache_policy_type wb_policy_type = {
|
||||
.name = "cleaner",
|
||||
.version = {1, 0, 0},
|
||||
.hint_size = 4,
|
||||
.owner = THIS_MODULE,
|
||||
.create = wb_create
|
||||
};
|
||||
|
||||
static int __init wb_init(void)
|
||||
{
|
||||
int r = dm_cache_policy_register(&wb_policy_type);
|
||||
|
||||
if (r < 0)
|
||||
DMERR("register failed %d", r);
|
||||
else
|
||||
DMINFO("version %u.%u.%u loaded",
|
||||
wb_policy_type.version[0],
|
||||
wb_policy_type.version[1],
|
||||
wb_policy_type.version[2]);
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
static void __exit wb_exit(void)
|
||||
{
|
||||
dm_cache_policy_unregister(&wb_policy_type);
|
||||
}
|
||||
|
||||
module_init(wb_init);
|
||||
module_exit(wb_exit);
|
||||
|
||||
MODULE_AUTHOR("Heinz Mauelshagen <dm-devel@redhat.com>");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_DESCRIPTION("cleaner cache policy");
|
||||
|
|
@ -12,40 +12,59 @@
|
|||
|
||||
/*----------------------------------------------------------------*/
|
||||
|
||||
/*
|
||||
* Little inline functions that simplify calling the policy methods.
|
||||
*/
|
||||
static inline int policy_map(struct dm_cache_policy *p, dm_oblock_t oblock,
|
||||
bool can_block, bool can_migrate, bool discarded_oblock,
|
||||
struct bio *bio, struct policy_locker *locker,
|
||||
struct policy_result *result)
|
||||
static inline int policy_lookup(struct dm_cache_policy *p, dm_oblock_t oblock, dm_cblock_t *cblock,
|
||||
int data_dir, bool fast_copy, bool *background_queued)
|
||||
{
|
||||
return p->map(p, oblock, can_block, can_migrate, discarded_oblock, bio, locker, result);
|
||||
return p->lookup(p, oblock, cblock, data_dir, fast_copy, background_queued);
|
||||
}
|
||||
|
||||
static inline int policy_lookup(struct dm_cache_policy *p, dm_oblock_t oblock, dm_cblock_t *cblock)
|
||||
static inline int policy_lookup_with_work(struct dm_cache_policy *p,
|
||||
dm_oblock_t oblock, dm_cblock_t *cblock,
|
||||
int data_dir, bool fast_copy,
|
||||
struct policy_work **work)
|
||||
{
|
||||
BUG_ON(!p->lookup);
|
||||
return p->lookup(p, oblock, cblock);
|
||||
if (!p->lookup_with_work) {
|
||||
*work = NULL;
|
||||
return p->lookup(p, oblock, cblock, data_dir, fast_copy, NULL);
|
||||
}
|
||||
|
||||
return p->lookup_with_work(p, oblock, cblock, data_dir, fast_copy, work);
|
||||
}
|
||||
|
||||
static inline void policy_set_dirty(struct dm_cache_policy *p, dm_oblock_t oblock)
|
||||
static inline int policy_get_background_work(struct dm_cache_policy *p,
|
||||
bool idle, struct policy_work **result)
|
||||
{
|
||||
if (p->set_dirty)
|
||||
p->set_dirty(p, oblock);
|
||||
return p->get_background_work(p, idle, result);
|
||||
}
|
||||
|
||||
static inline void policy_clear_dirty(struct dm_cache_policy *p, dm_oblock_t oblock)
|
||||
static inline void policy_complete_background_work(struct dm_cache_policy *p,
|
||||
struct policy_work *work,
|
||||
bool success)
|
||||
{
|
||||
if (p->clear_dirty)
|
||||
p->clear_dirty(p, oblock);
|
||||
return p->complete_background_work(p, work, success);
|
||||
}
|
||||
|
||||
static inline void policy_set_dirty(struct dm_cache_policy *p, dm_cblock_t cblock)
|
||||
{
|
||||
p->set_dirty(p, cblock);
|
||||
}
|
||||
|
||||
static inline void policy_clear_dirty(struct dm_cache_policy *p, dm_cblock_t cblock)
|
||||
{
|
||||
p->clear_dirty(p, cblock);
|
||||
}
|
||||
|
||||
static inline int policy_load_mapping(struct dm_cache_policy *p,
|
||||
dm_oblock_t oblock, dm_cblock_t cblock,
|
||||
uint32_t hint, bool hint_valid)
|
||||
bool dirty, uint32_t hint, bool hint_valid)
|
||||
{
|
||||
return p->load_mapping(p, oblock, cblock, hint, hint_valid);
|
||||
return p->load_mapping(p, oblock, cblock, dirty, hint, hint_valid);
|
||||
}
|
||||
|
||||
static inline int policy_invalidate_mapping(struct dm_cache_policy *p,
|
||||
dm_cblock_t cblock)
|
||||
{
|
||||
return p->invalidate_mapping(p, cblock);
|
||||
}
|
||||
|
||||
static inline uint32_t policy_get_hint(struct dm_cache_policy *p,
|
||||
|
|
@ -54,30 +73,6 @@ static inline uint32_t policy_get_hint(struct dm_cache_policy *p,
|
|||
return p->get_hint ? p->get_hint(p, cblock) : 0;
|
||||
}
|
||||
|
||||
static inline int policy_writeback_work(struct dm_cache_policy *p,
|
||||
dm_oblock_t *oblock,
|
||||
dm_cblock_t *cblock,
|
||||
bool critical_only)
|
||||
{
|
||||
return p->writeback_work ? p->writeback_work(p, oblock, cblock, critical_only) : -ENOENT;
|
||||
}
|
||||
|
||||
static inline void policy_remove_mapping(struct dm_cache_policy *p, dm_oblock_t oblock)
|
||||
{
|
||||
p->remove_mapping(p, oblock);
|
||||
}
|
||||
|
||||
static inline int policy_remove_cblock(struct dm_cache_policy *p, dm_cblock_t cblock)
|
||||
{
|
||||
return p->remove_cblock(p, cblock);
|
||||
}
|
||||
|
||||
static inline void policy_force_mapping(struct dm_cache_policy *p,
|
||||
dm_oblock_t current_oblock, dm_oblock_t new_oblock)
|
||||
{
|
||||
return p->force_mapping(p, current_oblock, new_oblock);
|
||||
}
|
||||
|
||||
static inline dm_cblock_t policy_residency(struct dm_cache_policy *p)
|
||||
{
|
||||
return p->residency(p);
|
||||
|
|
@ -107,6 +102,11 @@ static inline int policy_set_config_value(struct dm_cache_policy *p,
|
|||
return p->set_config_value ? p->set_config_value(p, key, value) : -EINVAL;
|
||||
}
|
||||
|
||||
static inline void policy_allow_migrations(struct dm_cache_policy *p, bool allow)
|
||||
{
|
||||
return p->allow_migrations(p, allow);
|
||||
}
|
||||
|
||||
/*----------------------------------------------------------------*/
|
||||
|
||||
/*
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -13,147 +13,94 @@
|
|||
|
||||
/*----------------------------------------------------------------*/
|
||||
|
||||
/* FIXME: make it clear which methods are optional. Get debug policy to
|
||||
* double check this at start.
|
||||
*/
|
||||
|
||||
/*
|
||||
* The cache policy makes the important decisions about which blocks get to
|
||||
* live on the faster cache device.
|
||||
*
|
||||
* When the core target has to remap a bio it calls the 'map' method of the
|
||||
* policy. This returns an instruction telling the core target what to do.
|
||||
*
|
||||
* POLICY_HIT:
|
||||
* That block is in the cache. Remap to the cache and carry on.
|
||||
*
|
||||
* POLICY_MISS:
|
||||
* This block is on the origin device. Remap and carry on.
|
||||
*
|
||||
* POLICY_NEW:
|
||||
* This block is currently on the origin device, but the policy wants to
|
||||
* move it. The core should:
|
||||
*
|
||||
* - hold any further io to this origin block
|
||||
* - copy the origin to the given cache block
|
||||
* - release all the held blocks
|
||||
* - remap the original block to the cache
|
||||
*
|
||||
* POLICY_REPLACE:
|
||||
* This block is currently on the origin device. The policy wants to
|
||||
* move it to the cache, with the added complication that the destination
|
||||
* cache block needs a writeback first. The core should:
|
||||
*
|
||||
* - hold any further io to this origin block
|
||||
* - hold any further io to the origin block that's being written back
|
||||
* - writeback
|
||||
* - copy new block to cache
|
||||
* - release held blocks
|
||||
* - remap bio to cache and reissue.
|
||||
*
|
||||
* Should the core run into trouble while processing a POLICY_NEW or
|
||||
* POLICY_REPLACE instruction it will roll back the policies mapping using
|
||||
* remove_mapping() or force_mapping(). These methods must not fail. This
|
||||
* approach avoids having transactional semantics in the policy (ie, the
|
||||
* core informing the policy when a migration is complete), and hence makes
|
||||
* it easier to write new policies.
|
||||
*
|
||||
* In general policy methods should never block, except in the case of the
|
||||
* map function when can_migrate is set. So be careful to implement using
|
||||
* bounded, preallocated memory.
|
||||
*/
|
||||
enum policy_operation {
|
||||
POLICY_HIT,
|
||||
POLICY_MISS,
|
||||
POLICY_NEW,
|
||||
POLICY_REPLACE
|
||||
};
|
||||
|
||||
/*
|
||||
* When issuing a POLICY_REPLACE the policy needs to make a callback to
|
||||
* lock the block being demoted. This doesn't need to occur during a
|
||||
* writeback operation since the block remains in the cache.
|
||||
*/
|
||||
struct policy_locker;
|
||||
typedef int (*policy_lock_fn)(struct policy_locker *l, dm_oblock_t oblock);
|
||||
|
||||
struct policy_locker {
|
||||
policy_lock_fn fn;
|
||||
POLICY_PROMOTE,
|
||||
POLICY_DEMOTE,
|
||||
POLICY_WRITEBACK
|
||||
};
|
||||
|
||||
/*
|
||||
* This is the instruction passed back to the core target.
|
||||
*/
|
||||
struct policy_result {
|
||||
struct policy_work {
|
||||
enum policy_operation op;
|
||||
dm_oblock_t old_oblock; /* POLICY_REPLACE */
|
||||
dm_cblock_t cblock; /* POLICY_HIT, POLICY_NEW, POLICY_REPLACE */
|
||||
dm_oblock_t oblock;
|
||||
dm_cblock_t cblock;
|
||||
};
|
||||
|
||||
/*
|
||||
* The cache policy object. Just a bunch of methods. It is envisaged that
|
||||
* this structure will be embedded in a bigger, policy specific structure
|
||||
* (ie. use container_of()).
|
||||
* The cache policy object. It is envisaged that this structure will be
|
||||
* embedded in a bigger, policy specific structure (ie. use container_of()).
|
||||
*/
|
||||
struct dm_cache_policy {
|
||||
|
||||
/*
|
||||
* FIXME: make it clear which methods are optional, and which may
|
||||
* block.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Destroys this object.
|
||||
*/
|
||||
void (*destroy)(struct dm_cache_policy *p);
|
||||
|
||||
/*
|
||||
* See large comment above.
|
||||
*
|
||||
* oblock - the origin block we're interested in.
|
||||
*
|
||||
* can_block - indicates whether the current thread is allowed to
|
||||
* block. -EWOULDBLOCK returned if it can't and would.
|
||||
*
|
||||
* can_migrate - gives permission for POLICY_NEW or POLICY_REPLACE
|
||||
* instructions. If denied and the policy would have
|
||||
* returned one of these instructions it should
|
||||
* return -EWOULDBLOCK.
|
||||
*
|
||||
* discarded_oblock - indicates whether the whole origin block is
|
||||
* in a discarded state (FIXME: better to tell the
|
||||
* policy about this sooner, so it can recycle that
|
||||
* cache block if it wants.)
|
||||
* bio - the bio that triggered this call.
|
||||
* result - gets filled in with the instruction.
|
||||
*
|
||||
* May only return 0, or -EWOULDBLOCK (if !can_migrate)
|
||||
*/
|
||||
int (*map)(struct dm_cache_policy *p, dm_oblock_t oblock,
|
||||
bool can_block, bool can_migrate, bool discarded_oblock,
|
||||
struct bio *bio, struct policy_locker *locker,
|
||||
struct policy_result *result);
|
||||
|
||||
/*
|
||||
* Sometimes we want to see if a block is in the cache, without
|
||||
* triggering any update of stats. (ie. it's not a real hit).
|
||||
* Find the location of a block.
|
||||
*
|
||||
* Must not block.
|
||||
*
|
||||
* Returns 0 if in cache, -ENOENT if not, < 0 for other errors
|
||||
* (-EWOULDBLOCK would be typical).
|
||||
* Returns 0 if in cache (cblock will be set), -ENOENT if not, < 0 for
|
||||
* other errors (-EWOULDBLOCK would be typical). data_dir should be
|
||||
* READ or WRITE. fast_copy should be set if migrating this block would
|
||||
* be 'cheap' somehow (eg, discarded data). background_queued will be set
|
||||
* if a migration has just been queued.
|
||||
*/
|
||||
int (*lookup)(struct dm_cache_policy *p, dm_oblock_t oblock, dm_cblock_t *cblock);
|
||||
int (*lookup)(struct dm_cache_policy *p, dm_oblock_t oblock, dm_cblock_t *cblock,
|
||||
int data_dir, bool fast_copy, bool *background_queued);
|
||||
|
||||
void (*set_dirty)(struct dm_cache_policy *p, dm_oblock_t oblock);
|
||||
void (*clear_dirty)(struct dm_cache_policy *p, dm_oblock_t oblock);
|
||||
/*
|
||||
* Sometimes the core target can optimise a migration, eg, the
|
||||
* block may be discarded, or the bio may cover an entire block.
|
||||
* In order to optimise it needs the migration immediately though
|
||||
* so it knows to do something different with the bio.
|
||||
*
|
||||
* This method is optional (policy-internal will fallback to using
|
||||
* lookup).
|
||||
*/
|
||||
int (*lookup_with_work)(struct dm_cache_policy *p,
|
||||
dm_oblock_t oblock, dm_cblock_t *cblock,
|
||||
int data_dir, bool fast_copy,
|
||||
struct policy_work **work);
|
||||
|
||||
/*
|
||||
* Retrieves background work. Returns -ENODATA when there's no
|
||||
* background work.
|
||||
*/
|
||||
int (*get_background_work)(struct dm_cache_policy *p, bool idle,
|
||||
struct policy_work **result);
|
||||
|
||||
/*
|
||||
* You must pass in the same work pointer that you were given, not
|
||||
* a copy.
|
||||
*/
|
||||
void (*complete_background_work)(struct dm_cache_policy *p,
|
||||
struct policy_work *work,
|
||||
bool success);
|
||||
|
||||
void (*set_dirty)(struct dm_cache_policy *p, dm_cblock_t cblock);
|
||||
void (*clear_dirty)(struct dm_cache_policy *p, dm_cblock_t cblock);
|
||||
|
||||
/*
|
||||
* Called when a cache target is first created. Used to load a
|
||||
* mapping from the metadata device into the policy.
|
||||
*/
|
||||
int (*load_mapping)(struct dm_cache_policy *p, dm_oblock_t oblock,
|
||||
dm_cblock_t cblock, uint32_t hint, bool hint_valid);
|
||||
dm_cblock_t cblock, bool dirty,
|
||||
uint32_t hint, bool hint_valid);
|
||||
|
||||
/*
|
||||
* Drops the mapping, irrespective of whether it's clean or dirty.
|
||||
* Returns -ENODATA if cblock is not mapped.
|
||||
*/
|
||||
int (*invalidate_mapping)(struct dm_cache_policy *p, dm_cblock_t cblock);
|
||||
|
||||
/*
|
||||
* Gets the hint for a given cblock. Called in a single threaded
|
||||
|
|
@ -161,36 +108,6 @@ struct dm_cache_policy {
|
|||
*/
|
||||
uint32_t (*get_hint)(struct dm_cache_policy *p, dm_cblock_t cblock);
|
||||
|
||||
/*
|
||||
* Override functions used on the error paths of the core target.
|
||||
* They must succeed.
|
||||
*/
|
||||
void (*remove_mapping)(struct dm_cache_policy *p, dm_oblock_t oblock);
|
||||
void (*force_mapping)(struct dm_cache_policy *p, dm_oblock_t current_oblock,
|
||||
dm_oblock_t new_oblock);
|
||||
|
||||
/*
|
||||
* This is called via the invalidate_cblocks message. It is
|
||||
* possible the particular cblock has already been removed due to a
|
||||
* write io in passthrough mode. In which case this should return
|
||||
* -ENODATA.
|
||||
*/
|
||||
int (*remove_cblock)(struct dm_cache_policy *p, dm_cblock_t cblock);
|
||||
|
||||
/*
|
||||
* Provide a dirty block to be written back by the core target. If
|
||||
* critical_only is set then the policy should only provide work if
|
||||
* it urgently needs it.
|
||||
*
|
||||
* Returns:
|
||||
*
|
||||
* 0 and @cblock,@oblock: block to write back provided
|
||||
*
|
||||
* -ENODATA: no dirty blocks available
|
||||
*/
|
||||
int (*writeback_work)(struct dm_cache_policy *p, dm_oblock_t *oblock, dm_cblock_t *cblock,
|
||||
bool critical_only);
|
||||
|
||||
/*
|
||||
* How full is the cache?
|
||||
*/
|
||||
|
|
@ -202,6 +119,8 @@ struct dm_cache_policy {
|
|||
* queue merging has occurred). To stop the policy being fooled by
|
||||
* these, the core target sends regular tick() calls to the policy.
|
||||
* The policy should only count an entry as hit once per tick.
|
||||
*
|
||||
* This method is optional.
|
||||
*/
|
||||
void (*tick)(struct dm_cache_policy *p, bool can_block);
|
||||
|
||||
|
|
@ -213,6 +132,8 @@ struct dm_cache_policy {
|
|||
int (*set_config_value)(struct dm_cache_policy *p,
|
||||
const char *key, const char *value);
|
||||
|
||||
void (*allow_migrations)(struct dm_cache_policy *p, bool allow);
|
||||
|
||||
/*
|
||||
* Book keeping ptr for the policy register, not for general use.
|
||||
*/
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue