// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2017 Western Digital Corporation or its affiliates.
*
* This file is released under the GPL.
*/
#include "dm-zoned.h"
#include <linux/module.h>
#define DM_MSG_PREFIX "zoned"
#define DMZ_MIN_BIOS 8192
/*
* Zone BIO context.
*/
struct dmz_bioctx {
struct dmz_dev *dev;
struct dm_zone *zone;
struct bio *bio;
refcount_t ref;
};
/*
* Chunk work descriptor.
*/
struct dm_chunk_work {
struct work_struct work;
refcount_t refcount;
struct dmz_target *target;
unsigned int chunk;
struct bio_list bio_list;
};
/*
* Target descriptor.
*/
struct dmz_target {
struct dm_dev **ddev;
unsigned int nr_ddevs;
unsigned int flags;
/* Zoned block device information */
struct dmz_dev *dev;
/* For metadata handling */
struct dmz_metadata *metadata;
/* For chunk work */
struct radix_tree_root chunk_rxtree;
struct workqueue_struct *chunk_wq;
struct mutex chunk_lock;
/* For cloned BIOs to zones */
struct bio_set bio_set;
/* For flush */
spinlock_t flush_lock;
struct bio_list flush_list;
struct delayed_work flush_work;
struct workqueue_struct *flush_wq;
};
/*
* Flush intervals (seconds).
*/
#define DMZ_FLUSH_PERIOD (10 * HZ)
/*
* Target BIO completion.
*/
static inline void dmz_bio_endio(struct bio *bio, blk_status_t status)
{
struct dmz_bioctx *bioctx =
dm_per_bio_data(bio, sizeof(struct dmz_bioctx));
if (status != BLK_STS_OK && bio->bi_status == BLK_STS_OK)
bio->bi_status = status;
if (bioctx->dev && bio->bi_status != BLK_STS_OK)
bioctx->dev->flags |= DMZ_CHECK_BDEV;
if (refcount_dec_and_test(&bioctx->ref)) {
struct dm_zone *zone = bioctx->zone;
if (zone) {
if (bio->bi_status != BLK_STS_OK &&
bio_op(bio) == REQ_OP_WRITE &&
dmz_is_seq(zone))
set_bit(DMZ_SEQ_WRITE_ERR, &zone->flags);
dmz_deactivate_zone(zone);
}
bio_endio(bio);
}
}
/*
* Completion callback for an internally cloned target BIO. This terminates the
* target BIO when there are no more references to its context.
*/
static void dmz_clone_endio(struct bio *clone)
{
struct dmz_bioctx *bioctx = clone->bi_private;
blk_status_t status = clone->bi_status;
bio_put(clone);
dmz_bio_endio(bioctx->bio, status);
}
/*
* Issue a clone of a target BIO. The clone may only partially process the
* original target BIO.
*/
static int dmz_submit_bio(struct dmz_target *dmz, struct dm_zone *zone,
struct bio *bio, sector_t chunk_block,
unsigned int nr_blocks)
{
struct dmz_bioctx *bioctx =
dm_per_bio_data(bio, sizeof(struct dmz_bioctx));
struct dmz_dev *dev = zone->dev;
struct bio *clone;
if (dev->flags & DMZ_BDEV_DYING)
return -EIO;
clone = bio_clone_fast(bio, GFP_NOIO, &dmz->bio_set);
if (!clone)
return -ENOMEM;
bio_set_dev(clone, dev->bdev);
bioctx->dev = dev;
clone->bi_iter.bi_sector =
dmz_start_sect(dmz->metadata, zone) + dmz_blk2sect(chunk_block);
clone->bi_iter.bi_size = dmz_blk2sect(nr_blocks) << SECTOR_SHIFT;
clone->bi_end_io = dmz_clone_endio;
clone->bi_private = bioctx;
bio_advance(bio, clone->bi_iter.bi_size);
refcount_inc(&bioctx->ref);
submit_bio_noacct(clone);
if (bio_op(bio) == REQ_OP_WRITE && dmz_is_seq(zone))
zone->wp_block += nr_blocks;
return 0;
}
/*
* Zero out pages of discarded blocks accessed by a read BIO.
*/
static void dmz_handle_read_zero(struct dmz_target *dmz, struct bio *bio,
sector_t chunk_block, unsigned int nr_blocks)
{
unsigned int