1bae9a0aaSDmitry Fomichev // SPDX-License-Identifier: GPL-2.0-only
23b1a94c8SDamien Le Moal /*
33b1a94c8SDamien Le Moal * Copyright (C) 2017 Western Digital Corporation or its affiliates.
43b1a94c8SDamien Le Moal *
53b1a94c8SDamien Le Moal * This file is released under the GPL.
63b1a94c8SDamien Le Moal */
73b1a94c8SDamien Le Moal
83b1a94c8SDamien Le Moal #include "dm-zoned.h"
93b1a94c8SDamien Le Moal
103b1a94c8SDamien Le Moal #include <linux/module.h>
113b1a94c8SDamien Le Moal
123b1a94c8SDamien Le Moal #define DM_MSG_PREFIX "zoned reclaim"
133b1a94c8SDamien Le Moal
143b1a94c8SDamien Le Moal struct dmz_reclaim {
153b1a94c8SDamien Le Moal struct dmz_metadata *metadata;
163b1a94c8SDamien Le Moal
173b1a94c8SDamien Le Moal struct delayed_work work;
183b1a94c8SDamien Le Moal struct workqueue_struct *wq;
193b1a94c8SDamien Le Moal
203b1a94c8SDamien Le Moal struct dm_kcopyd_client *kc;
213b1a94c8SDamien Le Moal struct dm_kcopyd_throttle kc_throttle;
223b1a94c8SDamien Le Moal int kc_err;
233b1a94c8SDamien Le Moal
24f97809aeSHannes Reinecke int dev_idx;
25f97809aeSHannes Reinecke
263b1a94c8SDamien Le Moal unsigned long flags;
273b1a94c8SDamien Le Moal
283b1a94c8SDamien Le Moal /* Last target access time */
293b1a94c8SDamien Le Moal unsigned long atime;
303b1a94c8SDamien Le Moal };
313b1a94c8SDamien Le Moal
323b1a94c8SDamien Le Moal /*
333b1a94c8SDamien Le Moal * Reclaim state flags.
343b1a94c8SDamien Le Moal */
353b1a94c8SDamien Le Moal enum {
363b1a94c8SDamien Le Moal DMZ_RECLAIM_KCOPY,
373b1a94c8SDamien Le Moal };
383b1a94c8SDamien Le Moal
393b1a94c8SDamien Le Moal /*
403b1a94c8SDamien Le Moal * Number of seconds of target BIO inactivity to consider the target idle.
413b1a94c8SDamien Le Moal */
423b1a94c8SDamien Le Moal #define DMZ_IDLE_PERIOD (10UL * HZ)
433b1a94c8SDamien Le Moal
443b1a94c8SDamien Le Moal /*
453b1a94c8SDamien Le Moal * Percentage of unmapped (free) random zones below which reclaim starts
463b1a94c8SDamien Le Moal * even if the target is busy.
473b1a94c8SDamien Le Moal */
4834f5affdSHannes Reinecke #define DMZ_RECLAIM_LOW_UNMAP_ZONES 30
493b1a94c8SDamien Le Moal
503b1a94c8SDamien Le Moal /*
513b1a94c8SDamien Le Moal * Percentage of unmapped (free) random zones above which reclaim will
523b1a94c8SDamien Le Moal * stop if the target is busy.
533b1a94c8SDamien Le Moal */
5434f5affdSHannes Reinecke #define DMZ_RECLAIM_HIGH_UNMAP_ZONES 50
553b1a94c8SDamien Le Moal
563b1a94c8SDamien Le Moal /*
573b1a94c8SDamien Le Moal * Align a sequential zone write pointer to chunk_block.
583b1a94c8SDamien Le Moal */
dmz_reclaim_align_wp(struct dmz_reclaim * zrc,struct dm_zone * zone,sector_t block)593b1a94c8SDamien Le Moal static int dmz_reclaim_align_wp(struct dmz_reclaim *zrc, struct dm_zone *zone,
603b1a94c8SDamien Le Moal sector_t block)
613b1a94c8SDamien Le Moal {
623b1a94c8SDamien Le Moal struct dmz_metadata *zmd = zrc->metadata;
638f22272aSHannes Reinecke struct dmz_dev *dev = zone->dev;
643b1a94c8SDamien Le Moal sector_t wp_block = zone->wp_block;
653b1a94c8SDamien Le Moal unsigned int nr_blocks;
663b1a94c8SDamien Le Moal int ret;
673b1a94c8SDamien Le Moal
683b1a94c8SDamien Le Moal if (wp_block == block)
693b1a94c8SDamien Le Moal return 0;
703b1a94c8SDamien Le Moal
713b1a94c8SDamien Le Moal if (wp_block > block)
723b1a94c8SDamien Le Moal return -EIO;
733b1a94c8SDamien Le Moal
743b1a94c8SDamien Le Moal /*
753b1a94c8SDamien Le Moal * Zeroout the space between the write
763b1a94c8SDamien Le Moal * pointer and the requested position.
773b1a94c8SDamien Le Moal */
783b1a94c8SDamien Le Moal nr_blocks = block - wp_block;
796c805f77SHannes Reinecke ret = blkdev_issue_zeroout(dev->bdev,
803b1a94c8SDamien Le Moal dmz_start_sect(zmd, zone) + dmz_blk2sect(wp_block),
814218a955SDamien Le Moal dmz_blk2sect(nr_blocks), GFP_NOIO, 0);
823b1a94c8SDamien Le Moal if (ret) {
836c805f77SHannes Reinecke dmz_dev_err(dev,
843b1a94c8SDamien Le Moal "Align zone %u wp %llu to %llu (wp+%u) blocks failed %d",
85b7122873SHannes Reinecke zone->id, (unsigned long long)wp_block,
863b1a94c8SDamien Le Moal (unsigned long long)block, nr_blocks, ret);
876c805f77SHannes Reinecke dmz_check_bdev(dev);
883b1a94c8SDamien Le Moal return ret;
893b1a94c8SDamien Le Moal }
903b1a94c8SDamien Le Moal
913b1a94c8SDamien Le Moal zone->wp_block = block;
923b1a94c8SDamien Le Moal
933b1a94c8SDamien Le Moal return 0;
943b1a94c8SDamien Le Moal }
953b1a94c8SDamien Le Moal
963b1a94c8SDamien Le Moal /*
973b1a94c8SDamien Le Moal * dm_kcopyd_copy end notification.
983b1a94c8SDamien Le Moal */
dmz_reclaim_kcopy_end(int read_err,unsigned long write_err,void * context)993b1a94c8SDamien Le Moal static void dmz_reclaim_kcopy_end(int read_err, unsigned long write_err,
1003b1a94c8SDamien Le Moal void *context)
1013b1a94c8SDamien Le Moal {
1023b1a94c8SDamien Le Moal struct dmz_reclaim *zrc = context;
1033b1a94c8SDamien Le Moal
1043b1a94c8SDamien Le Moal if (read_err || write_err)
1053b1a94c8SDamien Le Moal zrc->kc_err = -EIO;
1063b1a94c8SDamien Le Moal else
1073b1a94c8SDamien Le Moal zrc->kc_err = 0;
1083b1a94c8SDamien Le Moal
1093b1a94c8SDamien Le Moal clear_bit_unlock(DMZ_RECLAIM_KCOPY, &zrc->flags);
1103b1a94c8SDamien Le Moal smp_mb__after_atomic();
1113b1a94c8SDamien Le Moal wake_up_bit(&zrc->flags, DMZ_RECLAIM_KCOPY);
1123b1a94c8SDamien Le Moal }
1133b1a94c8SDamien Le Moal
1143b1a94c8SDamien Le Moal /*
1153b1a94c8SDamien Le Moal * Copy valid blocks of src_zone into dst_zone.
1163b1a94c8SDamien Le Moal */
dmz_reclaim_copy(struct dmz_reclaim * zrc,struct dm_zone * src_zone,struct dm_zone * dst_zone)1173b1a94c8SDamien Le Moal static int dmz_reclaim_copy(struct dmz_reclaim *zrc,
1183b1a94c8SDamien Le Moal struct dm_zone *src_zone, struct dm_zone *dst_zone)
1193b1a94c8SDamien Le Moal {
1203b1a94c8SDamien Le Moal struct dmz_metadata *zmd = zrc->metadata;
1213b1a94c8SDamien Le Moal struct dm_io_region src, dst;
1223b1a94c8SDamien Le Moal sector_t block = 0, end_block;
1233b1a94c8SDamien Le Moal sector_t nr_blocks;
1243b1a94c8SDamien Le Moal sector_t src_zone_block;
1253b1a94c8SDamien Le Moal sector_t dst_zone_block;
1263b1a94c8SDamien Le Moal unsigned long flags = 0;
1273b1a94c8SDamien Le Moal int ret;
1283b1a94c8SDamien Le Moal
1293b1a94c8SDamien Le Moal if (dmz_is_seq(src_zone))
1303b1a94c8SDamien Le Moal end_block = src_zone->wp_block;
1313b1a94c8SDamien Le Moal else
13236820560SHannes Reinecke end_block = dmz_zone_nr_blocks(zmd);
1333b1a94c8SDamien Le Moal src_zone_block = dmz_start_block(zmd, src_zone);
1343b1a94c8SDamien Le Moal dst_zone_block = dmz_start_block(zmd, dst_zone);
1353b1a94c8SDamien Le Moal
1363b1a94c8SDamien Le Moal if (dmz_is_seq(dst_zone))
137*db2351ebSMikulas Patocka flags |= BIT(DM_KCOPYD_WRITE_SEQ);
1383b1a94c8SDamien Le Moal
1393b1a94c8SDamien Le Moal while (block < end_block) {
1408f22272aSHannes Reinecke if (src_zone->dev->flags & DMZ_BDEV_DYING)
1416c805f77SHannes Reinecke return -EIO;
1428f22272aSHannes Reinecke if (dst_zone->dev->flags & DMZ_BDEV_DYING)
14375d66ffbSDmitry Fomichev return -EIO;
14475d66ffbSDmitry Fomichev
145a16b7deeSHannes Reinecke if (dmz_reclaim_should_terminate(src_zone))
146a16b7deeSHannes Reinecke return -EINTR;
147a16b7deeSHannes Reinecke
1483b1a94c8SDamien Le Moal /* Get a valid region from the source zone */
1493b1a94c8SDamien Le Moal ret = dmz_first_valid_block(zmd, src_zone, &block);
1503b1a94c8SDamien Le Moal if (ret <= 0)
1513b1a94c8SDamien Le Moal return ret;
1523b1a94c8SDamien Le Moal nr_blocks = ret;
1533b1a94c8SDamien Le Moal
1543b1a94c8SDamien Le Moal /*
1553b1a94c8SDamien Le Moal * If we are writing in a sequential zone, we must make sure
1563b1a94c8SDamien Le Moal * that writes are sequential. So Zeroout any eventual hole
1573b1a94c8SDamien Le Moal * between writes.
1583b1a94c8SDamien Le Moal */
1593b1a94c8SDamien Le Moal if (dmz_is_seq(dst_zone)) {
1603b1a94c8SDamien Le Moal ret = dmz_reclaim_align_wp(zrc, dst_zone, block);
1613b1a94c8SDamien Le Moal if (ret)
1623b1a94c8SDamien Le Moal return ret;
1633b1a94c8SDamien Le Moal }
1643b1a94c8SDamien Le Moal
1658f22272aSHannes Reinecke src.bdev = src_zone->dev->bdev;
1663b1a94c8SDamien Le Moal src.sector = dmz_blk2sect(src_zone_block + block);
1673b1a94c8SDamien Le Moal src.count = dmz_blk2sect(nr_blocks);
1683b1a94c8SDamien Le Moal
1698f22272aSHannes Reinecke dst.bdev = dst_zone->dev->bdev;
1703b1a94c8SDamien Le Moal dst.sector = dmz_blk2sect(dst_zone_block + block);
1713b1a94c8SDamien Le Moal dst.count = src.count;
1723b1a94c8SDamien Le Moal
1733b1a94c8SDamien Le Moal /* Copy the valid region */
1743b1a94c8SDamien Le Moal set_bit(DMZ_RECLAIM_KCOPY, &zrc->flags);
1757209049dSMike Snitzer dm_kcopyd_copy(zrc->kc, &src, 1, &dst, flags,
1763b1a94c8SDamien Le Moal dmz_reclaim_kcopy_end, zrc);
1773b1a94c8SDamien Le Moal
1783b1a94c8SDamien Le Moal /* Wait for copy to complete */
1793b1a94c8SDamien Le Moal wait_on_bit_io(&zrc->flags, DMZ_RECLAIM_KCOPY,
1803b1a94c8SDamien Le Moal TASK_UNINTERRUPTIBLE);
1813b1a94c8SDamien Le Moal if (zrc->kc_err)
1823b1a94c8SDamien Le Moal return zrc->kc_err;
1833b1a94c8SDamien Le Moal
1843b1a94c8SDamien Le Moal block += nr_blocks;
1853b1a94c8SDamien Le Moal if (dmz_is_seq(dst_zone))
1863b1a94c8SDamien Le Moal dst_zone->wp_block = block;
1873b1a94c8SDamien Le Moal }
1883b1a94c8SDamien Le Moal
1893b1a94c8SDamien Le Moal return 0;
1903b1a94c8SDamien Le Moal }
1913b1a94c8SDamien Le Moal
1923b1a94c8SDamien Le Moal /*
1933b1a94c8SDamien Le Moal * Move valid blocks of dzone buffer zone into dzone (after its write pointer)
1943b1a94c8SDamien Le Moal * and free the buffer zone.
1953b1a94c8SDamien Le Moal */
dmz_reclaim_buf(struct dmz_reclaim * zrc,struct dm_zone * dzone)1963b1a94c8SDamien Le Moal static int dmz_reclaim_buf(struct dmz_reclaim *zrc, struct dm_zone *dzone)
1973b1a94c8SDamien Le Moal {
1983b1a94c8SDamien Le Moal struct dm_zone *bzone = dzone->bzone;
1993b1a94c8SDamien Le Moal sector_t chunk_block = dzone->wp_block;
2003b1a94c8SDamien Le Moal struct dmz_metadata *zmd = zrc->metadata;
2013b1a94c8SDamien Le Moal int ret;
2023b1a94c8SDamien Le Moal
203f97809aeSHannes Reinecke DMDEBUG("(%s/%u): Chunk %u, move buf zone %u (weight %u) to data zone %u (weight %u)",
204f97809aeSHannes Reinecke dmz_metadata_label(zmd), zrc->dev_idx,
205b7122873SHannes Reinecke dzone->chunk, bzone->id, dmz_weight(bzone),
206b7122873SHannes Reinecke dzone->id, dmz_weight(dzone));
2073b1a94c8SDamien Le Moal
2083b1a94c8SDamien Le Moal /* Flush data zone into the buffer zone */
2093b1a94c8SDamien Le Moal ret = dmz_reclaim_copy(zrc, bzone, dzone);
2103b1a94c8SDamien Le Moal if (ret < 0)
2113b1a94c8SDamien Le Moal return ret;
2123b1a94c8SDamien Le Moal
2133b1a94c8SDamien Le Moal dmz_lock_flush(zmd);
2143b1a94c8SDamien Le Moal
2153b1a94c8SDamien Le Moal /* Validate copied blocks */
2163b1a94c8SDamien Le Moal ret = dmz_merge_valid_blocks(zmd, bzone, dzone, chunk_block);
2173b1a94c8SDamien Le Moal if (ret == 0) {
2183b1a94c8SDamien Le Moal /* Free the buffer zone */
21936820560SHannes Reinecke dmz_invalidate_blocks(zmd, bzone, 0, dmz_zone_nr_blocks(zmd));
2203b1a94c8SDamien Le Moal dmz_lock_map(zmd);
2213b1a94c8SDamien Le Moal dmz_unmap_zone(zmd, bzone);
2223b1a94c8SDamien Le Moal dmz_unlock_zone_reclaim(dzone);
2233b1a94c8SDamien Le Moal dmz_free_zone(zmd, bzone);
2243b1a94c8SDamien Le Moal dmz_unlock_map(zmd);
2253b1a94c8SDamien Le Moal }
2263b1a94c8SDamien Le Moal
2273b1a94c8SDamien Le Moal dmz_unlock_flush(zmd);
2283b1a94c8SDamien Le Moal
229b234c6d7SDmitry Fomichev return ret;
2303b1a94c8SDamien Le Moal }
2313b1a94c8SDamien Le Moal
2323b1a94c8SDamien Le Moal /*
2333b1a94c8SDamien Le Moal * Merge valid blocks of dzone into its buffer zone and free dzone.
2343b1a94c8SDamien Le Moal */
dmz_reclaim_seq_data(struct dmz_reclaim * zrc,struct dm_zone * dzone)2353b1a94c8SDamien Le Moal static int dmz_reclaim_seq_data(struct dmz_reclaim *zrc, struct dm_zone *dzone)
2363b1a94c8SDamien Le Moal {
2373b1a94c8SDamien Le Moal unsigned int chunk = dzone->chunk;
2383b1a94c8SDamien Le Moal struct dm_zone *bzone = dzone->bzone;
2393b1a94c8SDamien Le Moal struct dmz_metadata *zmd = zrc->metadata;
2403b1a94c8SDamien Le Moal int ret = 0;
2413b1a94c8SDamien Le Moal
242f97809aeSHannes Reinecke DMDEBUG("(%s/%u): Chunk %u, move data zone %u (weight %u) to buf zone %u (weight %u)",
243f97809aeSHannes Reinecke dmz_metadata_label(zmd), zrc->dev_idx,
244b7122873SHannes Reinecke chunk, dzone->id, dmz_weight(dzone),
245b7122873SHannes Reinecke bzone->id, dmz_weight(bzone));
2463b1a94c8SDamien Le Moal
2473b1a94c8SDamien Le Moal /* Flush data zone into the buffer zone */
2483b1a94c8SDamien Le Moal ret = dmz_reclaim_copy(zrc, dzone, bzone);
2493b1a94c8SDamien Le Moal if (ret < 0)
2503b1a94c8SDamien Le Moal return ret;
2513b1a94c8SDamien Le Moal
2523b1a94c8SDamien Le Moal dmz_lock_flush(zmd);
2533b1a94c8SDamien Le Moal
2543b1a94c8SDamien Le Moal /* Validate copied blocks */
2553b1a94c8SDamien Le Moal ret = dmz_merge_valid_blocks(zmd, dzone, bzone, 0);
2563b1a94c8SDamien Le Moal if (ret == 0) {
2573b1a94c8SDamien Le Moal /*
2583b1a94c8SDamien Le Moal * Free the data zone and remap the chunk to
2593b1a94c8SDamien Le Moal * the buffer zone.
2603b1a94c8SDamien Le Moal */
26136820560SHannes Reinecke dmz_invalidate_blocks(zmd, dzone, 0, dmz_zone_nr_blocks(zmd));
2623b1a94c8SDamien Le Moal dmz_lock_map(zmd);
2633b1a94c8SDamien Le Moal dmz_unmap_zone(zmd, bzone);
2643b1a94c8SDamien Le Moal dmz_unmap_zone(zmd, dzone);
2653b1a94c8SDamien Le Moal dmz_unlock_zone_reclaim(dzone);
2663b1a94c8SDamien Le Moal dmz_free_zone(zmd, dzone);
2673b1a94c8SDamien Le Moal dmz_map_zone(zmd, bzone, chunk);
2683b1a94c8SDamien Le Moal dmz_unlock_map(zmd);
2693b1a94c8SDamien Le Moal }
2703b1a94c8SDamien Le Moal
2713b1a94c8SDamien Le Moal dmz_unlock_flush(zmd);
2723b1a94c8SDamien Le Moal
273b234c6d7SDmitry Fomichev return ret;
2743b1a94c8SDamien Le Moal }
2753b1a94c8SDamien Le Moal
2763b1a94c8SDamien Le Moal /*
2773b1a94c8SDamien Le Moal * Move valid blocks of the random data zone dzone into a free sequential zone.
2783b1a94c8SDamien Le Moal * Once blocks are moved, remap the zone chunk to the sequential zone.
2793b1a94c8SDamien Le Moal */
dmz_reclaim_rnd_data(struct dmz_reclaim * zrc,struct dm_zone * dzone)2803b1a94c8SDamien Le Moal static int dmz_reclaim_rnd_data(struct dmz_reclaim *zrc, struct dm_zone *dzone)
2813b1a94c8SDamien Le Moal {
2823b1a94c8SDamien Le Moal unsigned int chunk = dzone->chunk;
2833b1a94c8SDamien Le Moal struct dm_zone *szone = NULL;
2843b1a94c8SDamien Le Moal struct dmz_metadata *zmd = zrc->metadata;
2853b1a94c8SDamien Le Moal int ret;
286c5c78859SHannes Reinecke int alloc_flags = DMZ_ALLOC_SEQ;
2873b1a94c8SDamien Le Moal
28890a9b869SHannes Reinecke /* Get a free random or sequential zone */
2893b1a94c8SDamien Le Moal dmz_lock_map(zmd);
290c5c78859SHannes Reinecke again:
29122c1ef66SHannes Reinecke szone = dmz_alloc_zone(zmd, zrc->dev_idx,
29222c1ef66SHannes Reinecke alloc_flags | DMZ_ALLOC_RECLAIM);
293c5c78859SHannes Reinecke if (!szone && alloc_flags == DMZ_ALLOC_SEQ && dmz_nr_cache_zones(zmd)) {
294c5c78859SHannes Reinecke alloc_flags = DMZ_ALLOC_RND;
295c5c78859SHannes Reinecke goto again;
296c5c78859SHannes Reinecke }
2973b1a94c8SDamien Le Moal dmz_unlock_map(zmd);
2983b1a94c8SDamien Le Moal if (!szone)
2993b1a94c8SDamien Le Moal return -ENOSPC;
3003b1a94c8SDamien Le Moal
301f97809aeSHannes Reinecke DMDEBUG("(%s/%u): Chunk %u, move %s zone %u (weight %u) to %s zone %u",
302f97809aeSHannes Reinecke dmz_metadata_label(zmd), zrc->dev_idx, chunk,
30334f5affdSHannes Reinecke dmz_is_cache(dzone) ? "cache" : "rnd",
30434f5affdSHannes Reinecke dzone->id, dmz_weight(dzone),
30534f5affdSHannes Reinecke dmz_is_rnd(szone) ? "rnd" : "seq", szone->id);
3063b1a94c8SDamien Le Moal
3073b1a94c8SDamien Le Moal /* Flush the random data zone into the sequential zone */
3083b1a94c8SDamien Le Moal ret = dmz_reclaim_copy(zrc, dzone, szone);
3093b1a94c8SDamien Le Moal
3103b1a94c8SDamien Le Moal dmz_lock_flush(zmd);
3113b1a94c8SDamien Le Moal
3123b1a94c8SDamien Le Moal if (ret == 0) {
3133b1a94c8SDamien Le Moal /* Validate copied blocks */
3143b1a94c8SDamien Le Moal ret = dmz_copy_valid_blocks(zmd, dzone, szone);
3153b1a94c8SDamien Le Moal }
3163b1a94c8SDamien Le Moal if (ret) {
3173b1a94c8SDamien Le Moal /* Free the sequential zone */
3183b1a94c8SDamien Le Moal dmz_lock_map(zmd);
3193b1a94c8SDamien Le Moal dmz_free_zone(zmd, szone);
3203b1a94c8SDamien Le Moal dmz_unlock_map(zmd);
3213b1a94c8SDamien Le Moal } else {
3223b1a94c8SDamien Le Moal /* Free the data zone and remap the chunk */
32336820560SHannes Reinecke dmz_invalidate_blocks(zmd, dzone, 0, dmz_zone_nr_blocks(zmd));
3243b1a94c8SDamien Le Moal dmz_lock_map(zmd);
3253b1a94c8SDamien Le Moal dmz_unmap_zone(zmd, dzone);
3263b1a94c8SDamien Le Moal dmz_unlock_zone_reclaim(dzone);
3273b1a94c8SDamien Le Moal dmz_free_zone(zmd, dzone);
3283b1a94c8SDamien Le Moal dmz_map_zone(zmd, szone, chunk);
3293b1a94c8SDamien Le Moal dmz_unlock_map(zmd);
3303b1a94c8SDamien Le Moal }
3313b1a94c8SDamien Le Moal
3323b1a94c8SDamien Le Moal dmz_unlock_flush(zmd);
3333b1a94c8SDamien Le Moal
334b234c6d7SDmitry Fomichev return ret;
3353b1a94c8SDamien Le Moal }
3363b1a94c8SDamien Le Moal
3373b1a94c8SDamien Le Moal /*
3383b1a94c8SDamien Le Moal * Reclaim an empty zone.
3393b1a94c8SDamien Le Moal */
dmz_reclaim_empty(struct dmz_reclaim * zrc,struct dm_zone * dzone)3403b1a94c8SDamien Le Moal static void dmz_reclaim_empty(struct dmz_reclaim *zrc, struct dm_zone *dzone)
3413b1a94c8SDamien Le Moal {
3423b1a94c8SDamien Le Moal struct dmz_metadata *zmd = zrc->metadata;
3433b1a94c8SDamien Le Moal
3443b1a94c8SDamien Le Moal dmz_lock_flush(zmd);
3453b1a94c8SDamien Le Moal dmz_lock_map(zmd);
3463b1a94c8SDamien Le Moal dmz_unmap_zone(zmd, dzone);
3473b1a94c8SDamien Le Moal dmz_unlock_zone_reclaim(dzone);
3483b1a94c8SDamien Le Moal dmz_free_zone(zmd, dzone);
3493b1a94c8SDamien Le Moal dmz_unlock_map(zmd);
3503b1a94c8SDamien Le Moal dmz_unlock_flush(zmd);
3513b1a94c8SDamien Le Moal }
3523b1a94c8SDamien Le Moal
3533b1a94c8SDamien Le Moal /*
35490a9b869SHannes Reinecke * Test if the target device is idle.
35590a9b869SHannes Reinecke */
dmz_target_idle(struct dmz_reclaim * zrc)35690a9b869SHannes Reinecke static inline int dmz_target_idle(struct dmz_reclaim *zrc)
35790a9b869SHannes Reinecke {
35890a9b869SHannes Reinecke return time_is_before_jiffies(zrc->atime + DMZ_IDLE_PERIOD);
35990a9b869SHannes Reinecke }
36090a9b869SHannes Reinecke
36190a9b869SHannes Reinecke /*
3623b1a94c8SDamien Le Moal * Find a candidate zone for reclaim and process it.
3633b1a94c8SDamien Le Moal */
dmz_do_reclaim(struct dmz_reclaim * zrc)364b234c6d7SDmitry Fomichev static int dmz_do_reclaim(struct dmz_reclaim *zrc)
3653b1a94c8SDamien Le Moal {
3663b1a94c8SDamien Le Moal struct dmz_metadata *zmd = zrc->metadata;
3673b1a94c8SDamien Le Moal struct dm_zone *dzone;
3683b1a94c8SDamien Le Moal struct dm_zone *rzone;
3693b1a94c8SDamien Le Moal unsigned long start;
3703b1a94c8SDamien Le Moal int ret;
3713b1a94c8SDamien Le Moal
3723b1a94c8SDamien Le Moal /* Get a data zone */
37369875d44SHannes Reinecke dzone = dmz_get_zone_for_reclaim(zmd, zrc->dev_idx,
37469875d44SHannes Reinecke dmz_target_idle(zrc));
375c3ff479dSHannes Reinecke if (!dzone) {
376f97809aeSHannes Reinecke DMDEBUG("(%s/%u): No zone found to reclaim",
377f97809aeSHannes Reinecke dmz_metadata_label(zmd), zrc->dev_idx);
378489dc0f0SHannes Reinecke return -EBUSY;
379c3ff479dSHannes Reinecke }
380c69cb1d1SDamien Le Moal rzone = dzone;
3813b1a94c8SDamien Le Moal
3823b1a94c8SDamien Le Moal start = jiffies;
38334f5affdSHannes Reinecke if (dmz_is_cache(dzone) || dmz_is_rnd(dzone)) {
3843b1a94c8SDamien Le Moal if (!dmz_weight(dzone)) {
3853b1a94c8SDamien Le Moal /* Empty zone */
3863b1a94c8SDamien Le Moal dmz_reclaim_empty(zrc, dzone);
3873b1a94c8SDamien Le Moal ret = 0;
3883b1a94c8SDamien Le Moal } else {
3893b1a94c8SDamien Le Moal /*
3903b1a94c8SDamien Le Moal * Reclaim the random data zone by moving its
3913b1a94c8SDamien Le Moal * valid data blocks to a free sequential zone.
3923b1a94c8SDamien Le Moal */
3933b1a94c8SDamien Le Moal ret = dmz_reclaim_rnd_data(zrc, dzone);
3943b1a94c8SDamien Le Moal }
3953b1a94c8SDamien Le Moal } else {
3963b1a94c8SDamien Le Moal struct dm_zone *bzone = dzone->bzone;
3973b1a94c8SDamien Le Moal sector_t chunk_block = 0;
3983b1a94c8SDamien Le Moal
3993b1a94c8SDamien Le Moal ret = dmz_first_valid_block(zmd, bzone, &chunk_block);
4003b1a94c8SDamien Le Moal if (ret < 0)
4013b1a94c8SDamien Le Moal goto out;
4023b1a94c8SDamien Le Moal
4033b1a94c8SDamien Le Moal if (ret == 0 || chunk_block >= dzone->wp_block) {
4043b1a94c8SDamien Le Moal /*
4053b1a94c8SDamien Le Moal * The buffer zone is empty or its valid blocks are
4063b1a94c8SDamien Le Moal * after the data zone write pointer.
4073b1a94c8SDamien Le Moal */
4083b1a94c8SDamien Le Moal ret = dmz_reclaim_buf(zrc, dzone);
4093b1a94c8SDamien Le Moal rzone = bzone;
4103b1a94c8SDamien Le Moal } else {
4113b1a94c8SDamien Le Moal /*
4123b1a94c8SDamien Le Moal * Reclaim the data zone by merging it into the
4133b1a94c8SDamien Le Moal * buffer zone so that the buffer zone itself can
4143b1a94c8SDamien Le Moal * be later reclaimed.
4153b1a94c8SDamien Le Moal */
4163b1a94c8SDamien Le Moal ret = dmz_reclaim_seq_data(zrc, dzone);
4173b1a94c8SDamien Le Moal }
4183b1a94c8SDamien Le Moal }
4193b1a94c8SDamien Le Moal out:
4203b1a94c8SDamien Le Moal if (ret) {
421c3ff479dSHannes Reinecke if (ret == -EINTR)
422f97809aeSHannes Reinecke DMDEBUG("(%s/%u): reclaim zone %u interrupted",
423f97809aeSHannes Reinecke dmz_metadata_label(zmd), zrc->dev_idx,
424f97809aeSHannes Reinecke rzone->id);
425c3ff479dSHannes Reinecke else
426f97809aeSHannes Reinecke DMDEBUG("(%s/%u): Failed to reclaim zone %u, err %d",
427f97809aeSHannes Reinecke dmz_metadata_label(zmd), zrc->dev_idx,
428f97809aeSHannes Reinecke rzone->id, ret);
4293b1a94c8SDamien Le Moal dmz_unlock_zone_reclaim(dzone);
430b234c6d7SDmitry Fomichev return ret;
4313b1a94c8SDamien Le Moal }
4323b1a94c8SDamien Le Moal
433b234c6d7SDmitry Fomichev ret = dmz_flush_metadata(zrc->metadata);
434b234c6d7SDmitry Fomichev if (ret) {
435f97809aeSHannes Reinecke DMDEBUG("(%s/%u): Metadata flush for zone %u failed, err %d",
436f97809aeSHannes Reinecke dmz_metadata_label(zmd), zrc->dev_idx, rzone->id, ret);
437b234c6d7SDmitry Fomichev return ret;
438b234c6d7SDmitry Fomichev }
4393b1a94c8SDamien Le Moal
440f97809aeSHannes Reinecke DMDEBUG("(%s/%u): Reclaimed zone %u in %u ms",
441f97809aeSHannes Reinecke dmz_metadata_label(zmd), zrc->dev_idx,
442b7122873SHannes Reinecke rzone->id, jiffies_to_msecs(jiffies - start));
443b234c6d7SDmitry Fomichev return 0;
4443b1a94c8SDamien Le Moal }
4453b1a94c8SDamien Le Moal
dmz_reclaim_percentage(struct dmz_reclaim * zrc)44634f5affdSHannes Reinecke static unsigned int dmz_reclaim_percentage(struct dmz_reclaim *zrc)
44734f5affdSHannes Reinecke {
44834f5affdSHannes Reinecke struct dmz_metadata *zmd = zrc->metadata;
44934f5affdSHannes Reinecke unsigned int nr_cache = dmz_nr_cache_zones(zmd);
45034f5affdSHannes Reinecke unsigned int nr_unmap, nr_zones;
45134f5affdSHannes Reinecke
45234f5affdSHannes Reinecke if (nr_cache) {
45334f5affdSHannes Reinecke nr_zones = nr_cache;
45434f5affdSHannes Reinecke nr_unmap = dmz_nr_unmap_cache_zones(zmd);
45534f5affdSHannes Reinecke } else {
456bd82fdabSHannes Reinecke nr_zones = dmz_nr_rnd_zones(zmd, zrc->dev_idx);
457bd82fdabSHannes Reinecke nr_unmap = dmz_nr_unmap_rnd_zones(zmd, zrc->dev_idx);
45834f5affdSHannes Reinecke }
459174364f6SDamien Le Moal if (nr_unmap <= 1)
460174364f6SDamien Le Moal return 0;
46134f5affdSHannes Reinecke return nr_unmap * 100 / nr_zones;
46234f5affdSHannes Reinecke }
46334f5affdSHannes Reinecke
4643b1a94c8SDamien Le Moal /*
4653b1a94c8SDamien Le Moal * Test if reclaim is necessary.
4663b1a94c8SDamien Le Moal */
dmz_should_reclaim(struct dmz_reclaim * zrc,unsigned int p_unmap)46734f5affdSHannes Reinecke static bool dmz_should_reclaim(struct dmz_reclaim *zrc, unsigned int p_unmap)
4683b1a94c8SDamien Le Moal {
469f97809aeSHannes Reinecke unsigned int nr_reclaim;
47090a9b869SHannes Reinecke
471bd82fdabSHannes Reinecke nr_reclaim = dmz_nr_rnd_zones(zrc->metadata, zrc->dev_idx);
472f97809aeSHannes Reinecke
473f97809aeSHannes Reinecke if (dmz_nr_cache_zones(zrc->metadata)) {
474f97809aeSHannes Reinecke /*
475f97809aeSHannes Reinecke * The first device in a multi-device
476f97809aeSHannes Reinecke * setup only contains cache zones, so
477f97809aeSHannes Reinecke * never start reclaim there.
478f97809aeSHannes Reinecke */
479f97809aeSHannes Reinecke if (zrc->dev_idx == 0)
480f97809aeSHannes Reinecke return false;
48190a9b869SHannes Reinecke nr_reclaim += dmz_nr_cache_zones(zrc->metadata);
482f97809aeSHannes Reinecke }
48390a9b869SHannes Reinecke
4843b1a94c8SDamien Le Moal /* Reclaim when idle */
48590a9b869SHannes Reinecke if (dmz_target_idle(zrc) && nr_reclaim)
4863b1a94c8SDamien Le Moal return true;
4873b1a94c8SDamien Le Moal
48834f5affdSHannes Reinecke /* If there are still plenty of cache zones, do not reclaim */
48934f5affdSHannes Reinecke if (p_unmap >= DMZ_RECLAIM_HIGH_UNMAP_ZONES)
4903b1a94c8SDamien Le Moal return false;
4913b1a94c8SDamien Le Moal
4923b1a94c8SDamien Le Moal /*
49334f5affdSHannes Reinecke * If the percentage of unmapped cache zones is low,
4943b1a94c8SDamien Le Moal * reclaim even if the target is busy.
4953b1a94c8SDamien Le Moal */
49634f5affdSHannes Reinecke return p_unmap <= DMZ_RECLAIM_LOW_UNMAP_ZONES;
4973b1a94c8SDamien Le Moal }
4983b1a94c8SDamien Le Moal
4993b1a94c8SDamien Le Moal /*
5003b1a94c8SDamien Le Moal * Reclaim work function.
5013b1a94c8SDamien Le Moal */
dmz_reclaim_work(struct work_struct * work)5023b1a94c8SDamien Le Moal static void dmz_reclaim_work(struct work_struct *work)
5033b1a94c8SDamien Le Moal {
5043b1a94c8SDamien Le Moal struct dmz_reclaim *zrc = container_of(work, struct dmz_reclaim, work.work);
5053b1a94c8SDamien Le Moal struct dmz_metadata *zmd = zrc->metadata;
506ce34c9b4SWei Yongjun unsigned int p_unmap;
507b234c6d7SDmitry Fomichev int ret;
5083b1a94c8SDamien Le Moal
509d0e21ce4SHannes Reinecke if (dmz_dev_is_dying(zmd))
51075d66ffbSDmitry Fomichev return;
51175d66ffbSDmitry Fomichev
51234f5affdSHannes Reinecke p_unmap = dmz_reclaim_percentage(zrc);
51334f5affdSHannes Reinecke if (!dmz_should_reclaim(zrc, p_unmap)) {
5143b1a94c8SDamien Le Moal mod_delayed_work(zrc->wq, &zrc->work, DMZ_IDLE_PERIOD);
5153b1a94c8SDamien Le Moal return;
5163b1a94c8SDamien Le Moal }
5173b1a94c8SDamien Le Moal
5183b1a94c8SDamien Le Moal /*
5193b1a94c8SDamien Le Moal * We need to start reclaiming random zones: set up zone copy
5203b1a94c8SDamien Le Moal * throttling to either go fast if we are very low on random zones
5213b1a94c8SDamien Le Moal * and slower if there are still some free random zones to avoid
5223b1a94c8SDamien Le Moal * as much as possible to negatively impact the user workload.
5233b1a94c8SDamien Le Moal */
52434f5affdSHannes Reinecke if (dmz_target_idle(zrc) || p_unmap < DMZ_RECLAIM_LOW_UNMAP_ZONES / 2) {
5253b1a94c8SDamien Le Moal /* Idle or very low percentage: go fast */
5263b1a94c8SDamien Le Moal zrc->kc_throttle.throttle = 100;
5273b1a94c8SDamien Le Moal } else {
5283b1a94c8SDamien Le Moal /* Busy but we still have some random zone: throttle */
52934f5affdSHannes Reinecke zrc->kc_throttle.throttle = min(75U, 100U - p_unmap / 2);
5303b1a94c8SDamien Le Moal }
5313b1a94c8SDamien Le Moal
532f97809aeSHannes Reinecke DMDEBUG("(%s/%u): Reclaim (%u): %s, %u%% free zones (%u/%u cache %u/%u random)",
533f97809aeSHannes Reinecke dmz_metadata_label(zmd), zrc->dev_idx,
5343b1a94c8SDamien Le Moal zrc->kc_throttle.throttle,
5353b1a94c8SDamien Le Moal (dmz_target_idle(zrc) ? "Idle" : "Busy"),
53634f5affdSHannes Reinecke p_unmap, dmz_nr_unmap_cache_zones(zmd),
53734f5affdSHannes Reinecke dmz_nr_cache_zones(zmd),
538bd82fdabSHannes Reinecke dmz_nr_unmap_rnd_zones(zmd, zrc->dev_idx),
539bd82fdabSHannes Reinecke dmz_nr_rnd_zones(zmd, zrc->dev_idx));
5403b1a94c8SDamien Le Moal
541b234c6d7SDmitry Fomichev ret = dmz_do_reclaim(zrc);
542a16b7deeSHannes Reinecke if (ret && ret != -EINTR) {
543d0e21ce4SHannes Reinecke if (!dmz_check_dev(zmd))
54475d66ffbSDmitry Fomichev return;
54575d66ffbSDmitry Fomichev }
5463b1a94c8SDamien Le Moal
5473b1a94c8SDamien Le Moal dmz_schedule_reclaim(zrc);
5483b1a94c8SDamien Le Moal }
5493b1a94c8SDamien Le Moal
5503b1a94c8SDamien Le Moal /*
5513b1a94c8SDamien Le Moal * Initialize reclaim.
5523b1a94c8SDamien Le Moal */
dmz_ctr_reclaim(struct dmz_metadata * zmd,struct dmz_reclaim ** reclaim,int idx)5536c805f77SHannes Reinecke int dmz_ctr_reclaim(struct dmz_metadata *zmd,
554f97809aeSHannes Reinecke struct dmz_reclaim **reclaim, int idx)
5553b1a94c8SDamien Le Moal {
5563b1a94c8SDamien Le Moal struct dmz_reclaim *zrc;
5573b1a94c8SDamien Le Moal int ret;
5583b1a94c8SDamien Le Moal
5593b1a94c8SDamien Le Moal zrc = kzalloc(sizeof(struct dmz_reclaim), GFP_KERNEL);
5603b1a94c8SDamien Le Moal if (!zrc)
5613b1a94c8SDamien Le Moal return -ENOMEM;
5623b1a94c8SDamien Le Moal
5633b1a94c8SDamien Le Moal zrc->metadata = zmd;
5643b1a94c8SDamien Le Moal zrc->atime = jiffies;
565f97809aeSHannes Reinecke zrc->dev_idx = idx;
5663b1a94c8SDamien Le Moal
5673b1a94c8SDamien Le Moal /* Reclaim kcopyd client */
5683b1a94c8SDamien Le Moal zrc->kc = dm_kcopyd_client_create(&zrc->kc_throttle);
5693b1a94c8SDamien Le Moal if (IS_ERR(zrc->kc)) {
5703b1a94c8SDamien Le Moal ret = PTR_ERR(zrc->kc);
5713b1a94c8SDamien Le Moal zrc->kc = NULL;
5723b1a94c8SDamien Le Moal goto err;
5733b1a94c8SDamien Le Moal }
5743b1a94c8SDamien Le Moal
5753b1a94c8SDamien Le Moal /* Reclaim work */
5763b1a94c8SDamien Le Moal INIT_DELAYED_WORK(&zrc->work, dmz_reclaim_work);
577f97809aeSHannes Reinecke zrc->wq = alloc_ordered_workqueue("dmz_rwq_%s_%d", WQ_MEM_RECLAIM,
578f97809aeSHannes Reinecke dmz_metadata_label(zmd), idx);
5793b1a94c8SDamien Le Moal if (!zrc->wq) {
5803b1a94c8SDamien Le Moal ret = -ENOMEM;
5813b1a94c8SDamien Le Moal goto err;
5823b1a94c8SDamien Le Moal }
5833b1a94c8SDamien Le Moal
5843b1a94c8SDamien Le Moal *reclaim = zrc;
5853b1a94c8SDamien Le Moal queue_delayed_work(zrc->wq, &zrc->work, 0);
5863b1a94c8SDamien Le Moal
5873b1a94c8SDamien Le Moal return 0;
5883b1a94c8SDamien Le Moal err:
5893b1a94c8SDamien Le Moal if (zrc->kc)
5903b1a94c8SDamien Le Moal dm_kcopyd_client_destroy(zrc->kc);
5913b1a94c8SDamien Le Moal kfree(zrc);
5923b1a94c8SDamien Le Moal
5933b1a94c8SDamien Le Moal return ret;
5943b1a94c8SDamien Le Moal }
5953b1a94c8SDamien Le Moal
5963b1a94c8SDamien Le Moal /*
5973b1a94c8SDamien Le Moal * Terminate reclaim.
5983b1a94c8SDamien Le Moal */
dmz_dtr_reclaim(struct dmz_reclaim * zrc)5993b1a94c8SDamien Le Moal void dmz_dtr_reclaim(struct dmz_reclaim *zrc)
6003b1a94c8SDamien Le Moal {
6013b1a94c8SDamien Le Moal cancel_delayed_work_sync(&zrc->work);
6023b1a94c8SDamien Le Moal destroy_workqueue(zrc->wq);
6033b1a94c8SDamien Le Moal dm_kcopyd_client_destroy(zrc->kc);
6043b1a94c8SDamien Le Moal kfree(zrc);
6053b1a94c8SDamien Le Moal }
6063b1a94c8SDamien Le Moal
6073b1a94c8SDamien Le Moal /*
6083b1a94c8SDamien Le Moal * Suspend reclaim.
6093b1a94c8SDamien Le Moal */
dmz_suspend_reclaim(struct dmz_reclaim * zrc)6103b1a94c8SDamien Le Moal void dmz_suspend_reclaim(struct dmz_reclaim *zrc)
6113b1a94c8SDamien Le Moal {
6123b1a94c8SDamien Le Moal cancel_delayed_work_sync(&zrc->work);
6133b1a94c8SDamien Le Moal }
6143b1a94c8SDamien Le Moal
6153b1a94c8SDamien Le Moal /*
6163b1a94c8SDamien Le Moal * Resume reclaim.
6173b1a94c8SDamien Le Moal */
dmz_resume_reclaim(struct dmz_reclaim * zrc)6183b1a94c8SDamien Le Moal void dmz_resume_reclaim(struct dmz_reclaim *zrc)
6193b1a94c8SDamien Le Moal {
6203b1a94c8SDamien Le Moal queue_delayed_work(zrc->wq, &zrc->work, DMZ_IDLE_PERIOD);
6213b1a94c8SDamien Le Moal }
6223b1a94c8SDamien Le Moal
6233b1a94c8SDamien Le Moal /*
6243b1a94c8SDamien Le Moal * BIO accounting.
6253b1a94c8SDamien Le Moal */
dmz_reclaim_bio_acc(struct dmz_reclaim * zrc)6263b1a94c8SDamien Le Moal void dmz_reclaim_bio_acc(struct dmz_reclaim *zrc)
6273b1a94c8SDamien Le Moal {
6283b1a94c8SDamien Le Moal zrc->atime = jiffies;
6293b1a94c8SDamien Le Moal }
6303b1a94c8SDamien Le Moal
6313b1a94c8SDamien Le Moal /*
6323b1a94c8SDamien Le Moal * Start reclaim if necessary.
6333b1a94c8SDamien Le Moal */
dmz_schedule_reclaim(struct dmz_reclaim * zrc)6343b1a94c8SDamien Le Moal void dmz_schedule_reclaim(struct dmz_reclaim *zrc)
6353b1a94c8SDamien Le Moal {
63634f5affdSHannes Reinecke unsigned int p_unmap = dmz_reclaim_percentage(zrc);
63734f5affdSHannes Reinecke
63834f5affdSHannes Reinecke if (dmz_should_reclaim(zrc, p_unmap))
6393b1a94c8SDamien Le Moal mod_delayed_work(zrc->wq, &zrc->work, 0);
6403b1a94c8SDamien Le Moal }
641