342 lines · 8.6 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0/* * blk-integrity.c - Block layer data integrity extensions * * Copyright (C) 2007, 2008 Oracle Corporation * Written by: Martin K. Petersen <martin.petersen@oracle.com> */ #include <linux/blk-integrity.h>#include <linux/backing-dev.h>#include <linux/mempool.h>#include <linux/bio.h>#include <linux/scatterlist.h>#include <linux/export.h>#include <linux/slab.h>#include <linux/t10-pi.h> #include "blk.h" /** * blk_rq_count_integrity_sg - Count number of integrity scatterlist elements * @q:		request queue * @bio:	bio with integrity metadata attached * * Description: Returns the number of elements required in a * scatterlist corresponding to the integrity metadata in a bio. */int blk_rq_count_integrity_sg(struct request_queue *q, struct bio *bio){	struct bio_vec iv, ivprv = { NULL };	unsigned int segments = 0;	unsigned int seg_size = 0;	struct bvec_iter iter;	int prev = 0; 	bio_for_each_integrity_vec(iv, bio, iter) { 		if (prev) {			if (!biovec_phys_mergeable(q, &ivprv, &iv))				goto new_segment;			if (seg_size + iv.bv_len > queue_max_segment_size(q))				goto new_segment; 			seg_size += iv.bv_len;		} else {new_segment:			segments++;			seg_size = iv.bv_len;		} 		prev = 1;		ivprv = iv;	} 	return segments;} int blk_get_meta_cap(struct block_device *bdev, unsigned int cmd,		     struct logical_block_metadata_cap __user *argp){	struct blk_integrity *bi;	struct logical_block_metadata_cap meta_cap = {};	size_t usize = _IOC_SIZE(cmd); 	if (!extensible_ioctl_valid(cmd, FS_IOC_GETLBMD_CAP, LBMD_SIZE_VER0))		return -ENOIOCTLCMD; 	bi = blk_get_integrity(bdev->bd_disk);	if (!bi)		goto out; 	if (bi->flags & BLK_INTEGRITY_DEVICE_CAPABLE)		meta_cap.lbmd_flags |= LBMD_PI_CAP_INTEGRITY;	if (bi->flags & BLK_INTEGRITY_REF_TAG)		meta_cap.lbmd_flags |= LBMD_PI_CAP_REFTAG;	meta_cap.lbmd_interval = 1 << bi->interval_exp;	meta_cap.lbmd_size = bi->metadata_size;	meta_cap.lbmd_pi_size = bi->pi_tuple_size;	meta_cap.lbmd_pi_offset = bi->pi_offset;	meta_cap.lbmd_opaque_size = bi->metadata_size - bi->pi_tuple_size;	if (meta_cap.lbmd_opaque_size && !bi->pi_offset)		meta_cap.lbmd_opaque_offset = bi->pi_tuple_size; 	switch (bi->csum_type) {	case BLK_INTEGRITY_CSUM_NONE:		meta_cap.lbmd_guard_tag_type = LBMD_PI_CSUM_NONE;		break;	case BLK_INTEGRITY_CSUM_IP:		meta_cap.lbmd_guard_tag_type = LBMD_PI_CSUM_IP;		break;	case BLK_INTEGRITY_CSUM_CRC:		meta_cap.lbmd_guard_tag_type = LBMD_PI_CSUM_CRC16_T10DIF;		break;	case BLK_INTEGRITY_CSUM_CRC64:		meta_cap.lbmd_guard_tag_type = LBMD_PI_CSUM_CRC64_NVME;		break;	} 	if (bi->csum_type != BLK_INTEGRITY_CSUM_NONE)		meta_cap.lbmd_app_tag_size = 2; 	if (bi->flags & BLK_INTEGRITY_REF_TAG) {		switch (bi->csum_type) {		case BLK_INTEGRITY_CSUM_CRC64:			meta_cap.lbmd_ref_tag_size =				sizeof_field(struct crc64_pi_tuple, ref_tag);			break;		case BLK_INTEGRITY_CSUM_CRC:		case BLK_INTEGRITY_CSUM_IP:			meta_cap.lbmd_ref_tag_size =				sizeof_field(struct t10_pi_tuple, ref_tag);			break;		default:			break;		}	} out:	return copy_struct_to_user(argp, usize, &meta_cap, sizeof(meta_cap),				   NULL);} int blk_rq_integrity_map_user(struct request *rq, void __user *ubuf,			      ssize_t bytes){	int ret;	struct iov_iter iter; 	iov_iter_ubuf(&iter, rq_data_dir(rq), ubuf, bytes);	ret = bio_integrity_map_user(rq->bio, &iter);	if (ret)		return ret; 	rq->nr_integrity_segments = blk_rq_count_integrity_sg(rq->q, rq->bio);	rq->cmd_flags |= REQ_INTEGRITY;	return 0;}EXPORT_SYMBOL_GPL(blk_rq_integrity_map_user); bool blk_integrity_merge_rq(struct request_queue *q, struct request *req,			    struct request *next){	struct bio_integrity_payload *bip, *bip_next; 	if (blk_integrity_rq(req) == 0 && blk_integrity_rq(next) == 0)		return true; 	if (blk_integrity_rq(req) == 0 || blk_integrity_rq(next) == 0)		return false; 	bip = bio_integrity(req->bio);	bip_next = bio_integrity(next->bio);	if (bip->bip_flags != bip_next->bip_flags)		return false; 	if (bip->bip_flags & BIP_CHECK_APPTAG &&	    bip->app_tag != bip_next->app_tag)		return false; 	if (req->nr_integrity_segments + next->nr_integrity_segments >	    q->limits.max_integrity_segments)		return false; 	if (integrity_req_gap_back_merge(req, next->bio))		return false; 	return true;} bool blk_integrity_merge_bio(struct request_queue *q, struct request *req,			     struct bio *bio){	struct bio_integrity_payload *bip, *bip_bio = bio_integrity(bio);	int nr_integrity_segs; 	if (blk_integrity_rq(req) == 0 && bip_bio == NULL)		return true; 	if (blk_integrity_rq(req) == 0 || bip_bio == NULL)		return false; 	bip = bio_integrity(req->bio);	if (bip->bip_flags != bip_bio->bip_flags)		return false; 	if (bip->bip_flags & BIP_CHECK_APPTAG &&	    bip->app_tag != bip_bio->app_tag)		return false; 	nr_integrity_segs = blk_rq_count_integrity_sg(q, bio);	if (req->nr_integrity_segments + nr_integrity_segs >	    q->limits.max_integrity_segments)		return false; 	return true;} static inline struct blk_integrity *dev_to_bi(struct device *dev){	return &dev_to_disk(dev)->queue->limits.integrity;} const char *blk_integrity_profile_name(struct blk_integrity *bi){	switch (bi->csum_type) {	case BLK_INTEGRITY_CSUM_IP:		if (bi->flags & BLK_INTEGRITY_REF_TAG)			return "T10-DIF-TYPE1-IP";		return "T10-DIF-TYPE3-IP";	case BLK_INTEGRITY_CSUM_CRC:		if (bi->flags & BLK_INTEGRITY_REF_TAG)			return "T10-DIF-TYPE1-CRC";		return "T10-DIF-TYPE3-CRC";	case BLK_INTEGRITY_CSUM_CRC64:		if (bi->flags & BLK_INTEGRITY_REF_TAG)			return "EXT-DIF-TYPE1-CRC64";		return "EXT-DIF-TYPE3-CRC64";	case BLK_INTEGRITY_CSUM_NONE:		break;	} 	return "nop";}EXPORT_SYMBOL_GPL(blk_integrity_profile_name); static ssize_t flag_store(struct device *dev, const char *page, size_t count,		unsigned char flag){	struct request_queue *q = dev_to_disk(dev)->queue;	struct queue_limits lim;	unsigned long val;	int err; 	err = kstrtoul(page, 10, &val);	if (err)		return err; 	/* note that the flags are inverted vs the values in the sysfs files */	lim = queue_limits_start_update(q);	if (val)		lim.integrity.flags &= ~flag;	else		lim.integrity.flags |= flag; 	err = queue_limits_commit_update_frozen(q, &lim);	if (err)		return err;	return count;} static ssize_t flag_show(struct device *dev, char *page, unsigned char flag){	struct blk_integrity *bi = dev_to_bi(dev); 	return sysfs_emit(page, "%d\n", !(bi->flags & flag));} static ssize_t format_show(struct device *dev, struct device_attribute *attr,			   char *page){	struct blk_integrity *bi = dev_to_bi(dev); 	if (!bi->metadata_size)		return sysfs_emit(page, "none\n");	return sysfs_emit(page, "%s\n", blk_integrity_profile_name(bi));} static ssize_t tag_size_show(struct device *dev, struct device_attribute *attr,			     char *page){	struct blk_integrity *bi = dev_to_bi(dev); 	return sysfs_emit(page, "%u\n", bi->tag_size);} static ssize_t protection_interval_bytes_show(struct device *dev,					      struct device_attribute *attr,					      char *page){	struct blk_integrity *bi = dev_to_bi(dev); 	return sysfs_emit(page, "%u\n",			  bi->interval_exp ? 1 << bi->interval_exp : 0);} static ssize_t read_verify_store(struct device *dev,				 struct device_attribute *attr,				 const char *page, size_t count){	return flag_store(dev, page, count, BLK_INTEGRITY_NOVERIFY);} static ssize_t read_verify_show(struct device *dev,				struct device_attribute *attr, char *page){	return flag_show(dev, page, BLK_INTEGRITY_NOVERIFY);} static ssize_t write_generate_store(struct device *dev,				    struct device_attribute *attr,				    const char *page, size_t count){	return flag_store(dev, page, count, BLK_INTEGRITY_NOGENERATE);} static ssize_t write_generate_show(struct device *dev,				   struct device_attribute *attr, char *page){	return flag_show(dev, page, BLK_INTEGRITY_NOGENERATE);} static ssize_t device_is_integrity_capable_show(struct device *dev,						struct device_attribute *attr,						char *page){	struct blk_integrity *bi = dev_to_bi(dev); 	return sysfs_emit(page, "%u\n",			  !!(bi->flags & BLK_INTEGRITY_DEVICE_CAPABLE));} static DEVICE_ATTR_RO(format);static DEVICE_ATTR_RO(tag_size);static DEVICE_ATTR_RO(protection_interval_bytes);static DEVICE_ATTR_RW(read_verify);static DEVICE_ATTR_RW(write_generate);static DEVICE_ATTR_RO(device_is_integrity_capable); static struct attribute *integrity_attrs[] = {	&dev_attr_format.attr,	&dev_attr_tag_size.attr,	&dev_attr_protection_interval_bytes.attr,	&dev_attr_read_verify.attr,	&dev_attr_write_generate.attr,	&dev_attr_device_is_integrity_capable.attr,	NULL}; const struct attribute_group blk_integrity_attr_group = {	.name = "integrity",	.attrs = integrity_attrs,};