Skip to contentSkip to conversation

Block/SCSI Data Integrity Support

PATCH New Martin K. Petersen 3 participants 23 messages Apr 25, 2008 – May 7, 2008

Subsystems (8)
Documentation VFS SCSI Block layer +4 more subsystems
lore
Share & tools
lore
#0 lore raw

PATCH 1/1 Block/SCSI Data Integrity Support

This is the first take of my data integrity patches. It's quite hard to explain everything in a brief couple of paragraphs but I'll try.

There's more information to be found in the docs section at:

http://oss.oracle.com/projects/data-integrity/

Here's the executive summary:

What's This All About?
----------------------

These patches allow data integrity information (checksum and more) to be attached to I/Os at the block/filesystem layers and transferred through the entire I/O stack all the way to the physical storage device.

The integrity metadata can be generated in close proximity to the original data. Capable host adapters, RAID arrays and physical disks can verify the data integrity and abort I/Os in case of a mismatch.

Right now this is SCSI disk only, but similar efforts are in progress for SATA and SCSI tape. With a few minor nits due to protocol limitations, the proposed SATA format is identical to the SCSI ditto for easy interoperability.

T10 DIF
-------

SCSI drives can usually be reformatted to 520-byte sectors, yielding 8 extra bytes per sector. These 8 bytes have traditionally been used by RAID controllers to store internal protection information.

DIF (Data Integrity Field) is an extension to the SCSI Block Commands that standardizes the format of the 8 extra bytes and defines ways to interact with the contents at the protocol level. We refer to the extra information as "integrity metadata" or "IMD".

Each 8-byte DIF tuple is split into three chunks:

- a 16-bit guard tag containing a CRC of the 512-byte data portion of the sector.

- a 16-bit application tag which is up for grabs.

- a 32-bit reference tag which contains an incrementing counter for each sector. For DIF Type 1 it also needs to match the physical LBA on the drive.

There are three types of DIF defined: Type 1, Type 2, and Type 3. My patches are Type 1 only, although Type 3 devices should work. Type 2 depends on 32-byte CDBs and is in progress.

Since the DIF tuple format is standardized, both initiators and targets (as well as potentially transport switches in-between) to verify the integrity of the data going over the bus.

When writing, the HBA will DMA 512-byte sectors from host memory, generate the matching integrity metadata and send out 520-byte sectors on the wire. The disk will verify the integrity of the data before committing it to stable storage.

When reading, the drive will send 520-byte sectors to the HBA. The HBA will verify the data integrity and DMA 512-byte sectors to host memory.

IOW, DIF provides means for added integrity protection between HBA and disk.

Data Integrity Extensions
-------------------------

In order to provide true end-to-end data integrity we need to be able to get access to the integrity metadata from the OS. Dealing with 520-byte sectors is quite inconvenient, so we have worked with HBA manufacturers to separate the data buffer scatter-gather from the integrity metadata scatter-gather.

Also, the CRC16 is somewhat expensive to calculate in software. So we have also allowed alternate checksums to be used. Currently we only support the IP checksum which is fast and cheap to calculate.

These two features and a few more knobs constitute what is known as DIX or the I/O Controller Data Integrity Extensions.

When writing, the HBA will DMA two scatterlists from host memory: One containing the data as usual, and one containing the integrity metadata. The HBA will verify that the two are in agreement and interleave them before sending them out on the wire as 520-byte sectors.

When reading, the disk will return 520-byte sectors, the HBA will verify the integrity, separate IMD from the data, and DMA to the two separate scatterlists in host memory.

SCSI Layer Changes
------------------

At the SCSI level, there are a few changes required to support this:

- an extra scatterlist for the integrity metadata

- tweaks to sd.c to detect and handle disks formatted with DIF

- sd.c must issue the right READ/WRITE commands when DIF is on

- helper functions for HBA drivers

- extra fields in scsi_host to signal the HBA driver's DIF capabilities

Block Layer Changes
-------------------

The main idea of DIF/DIX is to allow integrity metadata to be generated as close to the original data as possible. So in the long run we'd like this to happen in userland. Given mmap(), direct I/O, etc. this obviously poses some challenges. *cough*

For now the integrity metadata is generated at the block layer when an I/O is submitted by the filesystem. There are also functions that allow filesystems to use the application tag to mark sectors for future recovery or similar.

struct bio has been extended with a pointer to a struct bip which in turn contains the integrity metadata. The bip is essentially a trimmed down bio with a bio_vec and some housekeeping.

There are a few hooks inserted in fs/bio.c and block/blk-* to allow integrity metadata to be handled correctly when splitting, cloning and merging. Aside from that, the integrity stuff is completely opaque.

Because we don't want the block layer, filesystems, etc. to know about DIF, DIX, tuple formats, etc. all the functions that interact with the integrity metadata reside in the SCSI layer and are registered via a callback handler template. The block layer changes have been made so that the upcoming standards for data integrity on SATA (T13 External Path Protection) and SCSI tape will fit right in and can register their own handlers.

I have included a more in-depth description of the block layer changes in Documentation/block/data-integrity.txt.

The Patches
-----------

Since this patch set is quite intrusive all across the board, I haven't been able to split it up in incremental pieces. So the patches are more or less a logical grouping and won't work independently.

Right now everything has been set up so it can be toggled with CONFIG_BLK_DEV_INTEGRITY and CONFIG_SCSI_PROTECTION. There are a few places where this makes things really icky in terms of either code or #ifdefs. I'm hoping we can eventually promote the data integrity stuff to become a first class citizen and get rid of that cruft.

Other than that here are the patches. I'm very interested in comments, suggestions, etc.

How To Experiment Without DIF Hardware
--------------------------------------

# modprobe scsi_debug protection=1 guard=1 ato=1 dev_size_mb=1024
Signature · 1 line
Martin K. Petersen	Oracle Linux Engineering
#1 lore raw

PATCH 1/1 Find bio sector offset given idx and offset

2 files changed, 26 insertions(+)

fs/bio.c | 24 ++++++++++++++++++++++++
include/linux/bio.h | 2 ++

Helper function to find the sector offset in a bio given bvec index and page offset.

---

fs/bio.c +24 -0
diff -r b9f133a520ea -r 5d30928a2730 fs/bio.c
@@ -1142,6 +1142,30 @@
 	return bp;
 }
 
+sector_t bio_sector_offset(struct bio *bio, unsigned short index, unsigned int offset)
+{
+	struct bio_vec *bv;
+	unsigned int sector_sz = bio->bi_bdev->bd_disk->queue->hardsect_size;
+	sector_t sectors;
+	int i;
+
+	sectors = 0;
+
+	BUG_ON(index >= bio->bi_vcnt);
+	
+	bio_for_each_segment(bv, bio, i) {
+		if (i == index) {
+			if (offset > bv->bv_offset)
+				sectors += (offset - bv->bv_offset) / sector_sz;
+			return sectors;
+		}
+		
+		sectors += bv->bv_len / sector_sz;
+	}
+	
+	BUG();
+}
+EXPORT_SYMBOL(bio_sector_offset);
 
 /*
  * create memory pools for biovec's in a bio_set.
include/linux/bio.h +2 -0
diff -r b9f133a520ea -r 5d30928a2730 include/linux/bio.h
@@ -315,6 +315,8 @@
 extern int bio_add_pc_page(struct request_queue *, struct bio *, struct page *,
 			   unsigned int, unsigned int);
 extern int bio_get_nr_vecs(struct block_device *);
+extern sector_t bio_sector_offset(struct bio *, unsigned short, unsigned int);
+
 extern struct bio *bio_map_user(struct request_queue *, struct block_device *,
 				unsigned long, unsigned int, int);
 struct sg_iovec;
#2 lore raw

PATCH 1/1 Globalize bio_set and bio_vec_slab

In reply to Martin K. Petersen
2 files changed, 38 insertions(+), 28 deletions(-)
fs/bio.c            |   36 ++++++++----------------------------
include/linux/bio.h |   30 ++++++++++++++++++++++++++++++

Move struct bio_set and biovec_slab definitions to bio.h so they can be used outside of bio.c.

---

fs/bio.c +8 -28
diff -r eb54ccf75103 -r b9f133a520ea fs/bio.c
@@ -28,24 +28,9 @@
 #include <linux/blktrace_api.h>
 #include <scsi/sg.h>		/* for struct sg_iovec */
 
-#define BIO_POOL_SIZE 2
-
 static struct kmem_cache *bio_slab __read_mostly;
 
-#define BIOVEC_NR_POOLS 6
-
-/*
- * a small number of entries is fine, not going to be performance critical.
- * basically we just need to survive
- */
-#define BIO_SPLIT_ENTRIES 2
 mempool_t *bio_split_pool __read_mostly;
-
-struct biovec_slab {
-	int nr_vecs;
-	char *name; 
-	struct kmem_cache *slab;
-};
 
 /*
  * if you change this list, also change bvec_alloc or things will
@@ -60,23 +45,18 @@
 #undef BV
 
 /*
- * bio_set is used to allow other portions of the IO system to
- * allocate their own private memory pools for bio and iovec structures.
- * These memory pools in turn all allocate from the bio_slab
- * and the bvec_slabs[].
- */
-struct bio_set {
-	mempool_t *bio_pool;
-	mempool_t *bvec_pools[BIOVEC_NR_POOLS];
-};
-
-/*
  * fs_bio_set is the bio_set containing bio and iovec memory pools used by
  * IO code that does not need private memory pools.
  */
-static struct bio_set *fs_bio_set;
+struct bio_set *fs_bio_set;
 
-static inline struct bio_vec *bvec_alloc_bs(gfp_t gfp_mask, int nr, unsigned long *idx, struct bio_set *bs)
+inline int bvec_nr_vecs(int idx)
+{
+	return bvec_slabs[idx].nr_vecs;
+}
+EXPORT_SYMBOL(bvec_nr_vecs);
+
+struct bio_vec *bvec_alloc_bs(gfp_t gfp_mask, int nr, unsigned long *idx, struct bio_set *bs)
 {
 	struct bio_vec *bvl;
 
include/linux/bio.h +30 -0
diff -r eb54ccf75103 -r b9f133a520ea include/linux/bio.h
@@ -331,6 +331,36 @@
 				     int, int);
 extern int bio_uncopy_user(struct bio *);
 void zero_fill_bio(struct bio *bio);
+extern struct bio_vec *bvec_alloc_bs(gfp_t, int, unsigned long *, struct bio_set *);
+extern inline int bvec_nr_vecs(int idx);
+
+/*
+ * bio_set is used to allow other portions of the IO system to
+ * allocate their own private memory pools for bio and iovec structures.
+ * These memory pools in turn all allocate from the bio_slab
+ * and the bvec_slabs[].
+ */
+#define BIO_POOL_SIZE 2
+#define BIOVEC_NR_POOLS 6
+
+struct bio_set {
+	mempool_t *bio_pool;
+	mempool_t *bvec_pools[BIOVEC_NR_POOLS];
+};
+
+struct biovec_slab {
+	int nr_vecs;
+	char *name; 
+	struct kmem_cache *slab;
+};
+
+extern struct bio_set *fs_bio_set;
+
+/*
+ * a small number of entries is fine, not going to be performance critical.
+ * basically we just need to survive
+ */
+#define BIO_SPLIT_ENTRIES 2
 
 #ifdef CONFIG_HIGHMEM
 /*
#3 lore raw

PATCH 1/1 Block layer data integrity

In reply to Martin K. Petersen
4 files changed, 822 insertions(+), 3 deletions(-)
fs/Makefile         |    1 
fs/bio-integrity.c  |  712 +++++++++++++++++++++++++++++++++++++++++++++++++++
fs/bio.c            |   27 +
include/linux/bio.h |   85 ++++++

Allows integrity metadata to be attached to a bio.

---

fs/Makefile +1 -0
diff -r 5d30928a2730 -r c4c47b2f1539 fs/Makefile
@@ -19,6 +19,7 @@
 obj-y +=	no-block.o
 endif
 
+obj-$(CONFIG_BLK_DEV_INTEGRITY) += bio-integrity.o
 obj-$(CONFIG_INOTIFY)		+= inotify.o
 obj-$(CONFIG_INOTIFY_USER)	+= inotify_user.o
 obj-$(CONFIG_EPOLL)		+= eventpoll.o
fs/bio-integrity.c new file +712 -0

712 lines not on this page · raw message

fs/bio.c +24 -3
diff -r 5d30928a2730 -r c4c47b2f1539 fs/bio.c
@@ -96,6 +96,9 @@
 
 		mempool_free(bio->bi_io_vec, bio_set->bvec_pools[pool_idx]);
 	}
+
+	if (bio_integrity(bio))
+		bio_integrity_free(bio, bio_set);
 
 	mempool_free(bio, bio_set->bio_pool);
 }
@@ -255,9 +258,19 @@
 {
 	struct bio *b = bio_alloc_bioset(gfp_mask, bio->bi_max_vecs, fs_bio_set);
 
-	if (b) {
-		b->bi_destructor = bio_fs_destructor;
-		__bio_clone(b, bio);
+	if (!b) 
+		return NULL;
+
+	b->bi_destructor = bio_fs_destructor;
+	__bio_clone(b, bio);
+
+	if (bio_integrity(bio)) {
+		int ret;
+
+		ret = bio_integrity_clone(b, bio, fs_bio_set);
+		
+		if (ret < 0)
+			return NULL;
 	}
 
 	return b;
@@ -1139,6 +1152,9 @@
 	bp->bio1.bi_private = bi;
 	bp->bio2.bi_private = pool;
 
+	if (bio_integrity(bi))
+		bio_integrity_split(bi, bp, first_sectors);
+
 	return bp;
 }
 
@@ -1204,6 +1220,7 @@
 	if (bs->bio_pool)
 		mempool_destroy(bs->bio_pool);
 
+	bioset_integrity_free(bs);
 	biovec_free_pools(bs);
 
 	kfree(bs);
@@ -1218,6 +1235,9 @@
 
 	bs->bio_pool = mempool_create_slab_pool(bio_pool_size, bio_slab);
 	if (!bs->bio_pool)
+		goto bad;
+
+	if (bioset_integrity_create(bs, bio_pool_size))
 		goto bad;
 
 	if (!biovec_create_pools(bs, bvec_pool_size))
@@ -1246,6 +1266,7 @@
 {
 	bio_slab = KMEM_CACHE(bio, SLAB_HWCACHE_ALIGN|SLAB_PANIC);
 
+	bio_integrity_init_slab();
 	biovec_init_slabs();
 
 	fs_bio_set = bioset_create(BIO_POOL_SIZE, 2);
include/linux/bio.h +85 -0

120 lines not on this page · raw message

#4 lore raw

PATCH 1/1 Add support for the T10 Data Integrity Field CRC

In reply to Martin K. Petersen
4 files changed, 84 insertions(+)
include/linux/crc-t10dif.h |    8 +++++
lib/Kconfig                |    7 ++++
lib/Makefile               |    1 
lib/crc-t10dif.c           |   68 ++++++++++++++++++++++++++++++++++++++++++++

---

include/linux/crc-t10dif.h new file +8 -0
diff -r 6065be53b4fd -r eb54ccf75103 include/linux/crc-t10dif.h
@@ -0,0 +1,8 @@
+#ifndef _LINUX_CRC_T10DIF_H
+#define _LINUX_CRC_T10DIF_H
+
+#include <linux/types.h>
+
+__u16 crc_t10dif(unsigned char const *, size_t);
+
+#endif
lib/Kconfig +7 -0
diff -r 6065be53b4fd -r eb54ccf75103 lib/Kconfig
@@ -22,6 +22,13 @@
 	  modules require CRC16 functions, but a module built outside
 	  the kernel tree does. Such modules that use library CRC16
 	  functions require M here.
+
+config CRC_T10DIF
+	tristate "CRC calculation for the T10 Data Integrity Field"
+	help
+	  This option is only needed if a module that's not in the
+	  kernel tree needs to calculate CRC checks for use with the
+	  SCSI data integrity subsystem.
 
 config CRC_ITU_T
 	tristate "CRC ITU-T V.41 functions"
lib/Makefile +1 -0
diff -r 6065be53b4fd -r eb54ccf75103 lib/Makefile
@@ -43,6 +43,7 @@
 obj-$(CONFIG_BITREVERSE) += bitrev.o
 obj-$(CONFIG_CRC_CCITT)	+= crc-ccitt.o
 obj-$(CONFIG_CRC16)	+= crc16.o
+obj-$(CONFIG_CRC_T10DIF)+= crc-t10dif.o
 obj-$(CONFIG_CRC_ITU_T)	+= crc-itu-t.o
 obj-$(CONFIG_CRC32)	+= crc32.o
 obj-$(CONFIG_CRC7)	+= crc7.o
lib/crc-t10dif.c new file +68 -0

68 lines not on this page · raw message

#5 lore raw

PATCH 1/1 Block layer data integrity

In reply to Martin K. Petersen
10 files changed, 849 insertions(+)
Documentation/block/data-integrity.txt |  327 +++++++++++++++++++++++++++
block/Kconfig                          |   12 
block/Makefile                         |    1 
block/blk-core.c                       |    7 
block/blk-integrity.c                  |  385 ++++++++++++++++++++++++++++++++
block/blk-merge.c                      |    3 
block/blk.h                            |    8 
block/elevator.c                       |    6 
include/linux/blkdev.h                 |   97 ++++++++
include/linux/genhd.h                  |    3 

Support for merging and mapping bio integrity metadata.

Block device integrity type registration.

---

Documentation/block/data-integrity.txt new file +327 -0
diff -r c4c47b2f1539 -r 26ccaf2ccdc5 Documentation/block/data-integrity.txt
@@ -0,0 +1,327 @@
+----------------------------------------------------------------------
+1. INTRODUCTION
+
+Modern filesystems feature checksumming of data and metadata to
+protect against data corruption.  However, the detection of the
+corruption is done at read time which could potentially be months
+after the data was written.  At that point the original data that the
+application tried to write is most likely lost.
+
+The solution is to ensure that the disk is actually storing what the
+application meant it to.  Recent additions to both the SCSI family
+protocols (SBC Data Integrity Field, SCC protection proposal) as well
+as SATA/T13 (External Path Protection) try to remedy this by adding
+support for appending integrity metadata to an I/O.  The integrity
+metadata (or protection information in SCSI terminology) includes a
+checksum for each sector as well as an incrementing counter that
+ensures the individual sectors are written in the right order.  And
+for some protection schemes also that the I/O is written to the right
+place on disk.
+
+Current storage controllers and devices implement various protective
+measures, for instance checksumming and scrubbing.  But these
+technologies are working in their own isolated domains or at best
+between adjacent nodes in the I/O path.  The interesting thing about
+DIF and the other integrity extensions is that the protection format
+is well defined and every node in the I/O path can verify the
+integrity of the I/O and reject it if corruption is detected.  This
+allows not only corruption prevention but also isolation of the point
+of failure.
+
+----------------------------------------------------------------------
+2. THE DATA INTEGRITY EXTENSIONS
+
+As written, the protocol extensions only protect the path between
+controller and storage device.  However, many controllers actually
+allow the operating system to interact with the integrity metadata
+(IMD).  We have been working with several FC/SAS HBA vendors to enable
+the protection information to be transferred to and from their
+controllers.
+
+The SCSI Data Integrity Field works by appending 8 bytes of protection
+information to each sector.  The data + integrity metadata is stored
+in 520 byte sectors on disk.  Data + IMD are interleaved when
+transferred between the controller and target.  The T13 proposal is
+similar.
+
+Because it is highly inconvenient for operating systems to deal with
+520 (and 4104) byte sectors, we approached several HBA vendors and
+encouraged them to allow separation of the data and integrity metadata
+scatter-gather lists.
+
+The controller will interleave the buffers on write and split them on
+read.  This means that the Linux can DMA the data buffers to and from
+host memory without changes to the page cache.
+
+Also, the 16-bit CRC checksum mandated by both the SCSI and SATA specs
+is somewhat heavy to compute in software.  Benchmarks found that
+calculating this checksum had a significant impact on system
+performance for a number of workloads.  Some controllers allow a
+lighter-weight checksum to be used when interfacing with the operating
+system.  Emulex, for instance, supports the TCP/IP checksum instead.
+The IP checksum received from the OS is converted to the 16-bit CRC
+when writing and vice versa.  This allows the integrity metadata to be
+generated by Linux or the application at very low cost (comparable to
+software RAID5).
+
+The IP checksum is weaker than the CRC in terms of detecting bit
+errors.  However, the strength is really in the separation of the data
+buffers and the integrity metadata.  These two distinct buffers much
+match up for an I/O to complete.
+
+The separation of the data and integrity metadata buffers as well as
+the choice in checksums is referred to as the Data Integrity
+Extensions.  As these extensions are outside the scope of the protocol
+bodies (T10, T13), Oracle and its partners are trying to standardize
+them within the Storage Networking Industry Association.
+
+----------------------------------------------------------------------
+3. KERNEL CHANGES
+
+The data integrity framework in Linux enables protection information
+to be pinned to I/Os and sent to/received from controllers that
+support it.
+
+The advantage to the integrity extensions in SCSI and SATA is that
+they enable us to protect the entire path from application to storage
+device.  However, at the same time this is also the biggest
+disadvantage. It means that the protection information must be in a
+format that can be understood by the disk.
+
+Generally Linux/POSIX applications are agnostic to the intricacies of
+the storage devices they are accessing.  The virtual filesystem switch
+and the block layer make things like hardware sector size and
+transport protocols completely transparent to the application.
+
+However, this level of detail is required when preparing the
+protection information to send to a disk.  Consequently, the very
+concept of an end-to-end protection scheme is a layering violation.
+It is completely unreasonable for an application to be aware whether
+it is accessing a SCSI or SATA disk.
+
+The data integrity support implemented in Linux attempts to hide this
+from the application.  As far as the application (and to some extent
+the kernel) is concerned, the integrity metadata is opaque information
+that's attached to the I/O.
+
+The current implementation allows the block layer to automatically
+generate the protection information for any I/O.  Eventually the
+intent is to move the integrity metadata calculation to userspace for
+user data.  Metadata and other I/O that originates within the kernel
+will still use the automatic generation interface.
+
+Some storage devices allow each hardware sector to be tagged with a
+16-bit value.  The owner of this tag space is the owner of the block
+device.  I.e. the filesystem in most cases.  The filesystem can use
+this extra space to tag sectors as they see fit.  Because the tag
+space is limited, the block interface allows tagging bigger chunks by
+way of interleaving.  This way, 8*16 bits of information can be
+attached to a typical 4KB filesystem block.
+
+This also means that applications such as fsck and mkfs will need
+access to manipulate the tags from user space.  A passthrough
+interface for this is being worked on.
+
+
+----------------------------------------------------------------------
+4. BLOCK LAYER IMPLEMENTATION DETAILS
+
+4.1 BIO
+
+The data integrity patches add a new field to struct bio when
+CONFIG_BLK_DEV_INTEGRITY is enabled.  bio->bi_integrity is a pointer
+to a struct bip which contains the bio integrity payload.  Essentially
+a bip is a trimmed down struct bio which holds a bio_vec containing
+the integrity metadata and the required housekeeping information (bvec
+pool, vector count, etc.)
+
+A kernel subsystem can enable data integrity protection on a bio by
+calling bio_integrity_alloc(bio).  This will allocate and attach the
+bip to the bio.
+
+Individual pages containing integrity metadata can subsequently be
+attached using bio_integrity_add_page().
+
+bio_free() will automatically free the bip.
+
+
+4.2 BLOCK DEVICE
+
+Because the format of the protection data is tied to the physical
+disk, each block device has been extended with a block integrity
+profile (struct blk_integrity).  This optional profile is registered
+with the block layer using blk_integrity_register().
+
+The profile contains callback functions for generating and verifying
+the protection data, as well as getting and setting application tags.
+The profile also contains a few constants to aid in completing,
+merging and splitting the integrity metadata.
+
+Layered block devices will need to pick a profile that's appropriate
+for all subdevices.  blk_integrity_compare() can help with that.  DM
+and MD linear, RAID0 and RAID1 are currently supported.  RAID4/5/6
+will require extra work due to the application tag.
+
+
+----------------------------------------------------------------------
+5.0 BLOCK LAYER INTEGRITY API
+
+5.1 NORMAL FILESYSTEM
+
+    The normal filesystem is unaware that the underlying block device
+    is capable of sending/receiving integrity metadata.  The IMD will
+    be automatically generated by the block layer at submit_bio() time
+    in case of a WRITE.  A READ request will cause the I/O integrity
+    to be verified upon completion.
+
+    IMD generation and verification can be toggled using the
+
+      /sys/class/block/<bdev>/integrity/write_generate
+
+    and
+
+      /sys/class/block/<bdev>/integrity/read_verify
+
+    flags.
+
+
+5.2 INTEGRITY-AWARE FILESYSTEM
+
+    A filesystem that is integrity-aware can prepare I/Os with IMD
+    attached.  It can also use the application tag space if this is
+    supported by the block device.
+
+
+    int bdev_integrity_enabled(block_device, int rw);
+
+      bdev_integrity_enabled() will return 1 if the block device
+      supports integrity metadata transfer for the data direction
+      specified in 'rw'.
+
+      bdev_integrity_enabled() honors the write_generate and
+      read_verify flags in sysfs and will respond accordingly.
+
+
+    int bio_integrity_prep(bio);
+
+      To generate IMD for WRITE and to set up buffers for READ, the
+      filesystem must call bio_integrity_prep(bio).
+
+      Prior to calling this function, the bio data direction and start
+      sector must be set, and the bio should have all data pages
+      added.  It is up to the caller to ensure that the bio does not
+      change while I/O is in progress.
+
+      bio_integrity_prep() should only be called if
+      bio_integrity_enabled() returned 1.
+
+
+    int bio_integrity_tag_size(bio);
+
+      If the filesystem wants to use the application tag space it will
+      first have to find out how much storage space is available.
+      Because tag space is generally limited (usually 2 bytes per
+      sector regardless of sector size), the integrity framework
+      supports interleaving the information between the sectors in an
+      I/O.
+
+      Filesystems can call bio_integrity_tag_size(bio) to find out how
+      many bytes of storage are available for that particular bio.
+
+      Another option is bdev_get_tag_size(block_device) which will
+      return the number of available bytes per hardware sector.
+
+
+    int bio_integrity_set_tag(bio, void *tag_buf, len);
+
+      After a successful return from bio_integrity_prep(),
+      bio_integrity_set_tag() can be used to attach an opaque tag
+      buffer to a bio.  Obviously this only makes sense if the I/O is
+      a WRITE.
+
+
+    int bio_integrity_get_tag(bio, void *tag_buf, len);
+
+      Similarly, at READ I/O completion time the filesystem can
+      retrieve the tag buffer using bio_integrity_get_tag().
+
+
+6.3 PASSING EXISTING INTEGRITY METADATA
+
+    Filesystems that either generate their own integrity metadata or
+    are capable of transferring IMD from user space can use the
+    following calls:
+
+
+    struct bip * bio_integrity_alloc(bio, gfp_mask, nr_pages);
+
+      Allocates the bio integrity payload and hangs it off of the bio.
+      nr_pages indicate how many pages of protection data need to be
+      stored in the integrity bio_vec list (similar to bio_alloc()).
+
+      The integrity payload will be freed at bio_free() time.
+
+
+    int bio_integrity_add_page(bio, page, len, offset);
+
+      Attaches a page containing integrity metadata to an existing
+      bio.  The bio must have an existing bip,
+      i.e. bio_integrity_alloc() must have been called.  For a WRITE,
+      the integrity metadata in the pages must be in a format
+      understood by the target device with the notable exception that
+      the sector numbers will be remapped as the request traverses the
+      I/O stack.  This implies that the pages added using this call
+      will be modified during I/O!  The first reference tag in the
+      integrity metadata must have a value of bip->bip_sector.
+
+      Pages can be added using bio_integrity_add_page() as long as
+      there is room in the bip bio_vec array (nr_pages).
+
+      Upon completion of a READ operation, the attached pages will
+      contain the integrity metadata received from the storage device.
+      It is up to the receiver to process them and verify data
+      integrity upon completion.
+
+
+6.4 REGISTERING A BLOCK DEVICE AS CAPABLE OF EXCHANGING INTEGRITY
+    METADATA
+
+    To enable integrity exchange on a block device the gendisk must be
+    registered as capable:
+
+    int blk_integrity_register(gendisk, blk_integrity);
+
+      The blk_integrity struct is a template and should contain the
+      following:
+
+        static struct blk_integrity my_profile = {
+            .name                   = "STANDARDSBODY-TYPE-VARIANT",
+            .generate_fn            = my_generate_fn,
+       	    .verify_fn              = my_verify_fn,
+       	    .get_tag_fn             = my_get_tag_fn,
+       	    .set_tag_fn             = my_set_tag_fn,
+	    .tuple_size             = sizeof(struct my_tuple_size),
+	    .tag_size               = <tag bytes per hw sector>,
+        };
+
+      'name' is a text string which will be visible in sysfs.  This is
+      part of the userland API so chose it carefully and never change
+      it.  The format is standards body-type-variant.  E.g. T10-DIF-IP
+      or T13-EPP-CRC.
+
+      'generate_fn' generates appropriate integrity metadata (for WRITE).
+
+      'verify_fn' verifies that the data buffer matches the integrity
+      metadata.
+
+      'tuple_size' must be set to match the size of the integrity
+      metadata per sector.  I.e. 8 for DIF and EPP.
+
+      'tag_size' must be set to identify how many bytes of tag space
+      are available per hardware sector.  For DIF this is either 2 or
+      0 depending on the value of the Control Mode Page ATO bit.
+
+      See 6.2 for a description of get_tag_fn and set_tag_fn.
+
+----------------------------------------------------------------------
+2007-12-24 Martin K. Petersen <[email protected]>
block/Kconfig +12 -0
diff -r c4c47b2f1539 -r 26ccaf2ccdc5 block/Kconfig
@@ -81,6 +81,18 @@
 
 	  If unsure, say N.
 
+config BLK_DEV_INTEGRITY
+	bool "Block layer data integrity support"
+	---help---
+	Some storage devices allow extra information to be
+	stored/retrieved to help protect the data.  The block layer
+	data integrity option provides hooks which can be used by
+	filesystems to ensure better data integrity.
+
+	Say yes here if you have a storage device that provides the
+	T10/SCSI Data Integrity Field or the T13/ATA External Path
+	Protection.
+
 endif # BLOCK
 
 config BLOCK_COMPAT
block/Makefile +1 -0
diff -r c4c47b2f1539 -r 26ccaf2ccdc5 block/Makefile
@@ -14,3 +14,4 @@
 
 obj-$(CONFIG_BLK_DEV_IO_TRACE)	+= blktrace.o
 obj-$(CONFIG_BLOCK_COMPAT)	+= compat_ioctl.o
+obj-$(CONFIG_BLK_DEV_INTEGRITY)	+= blk-integrity.o
block/blk-core.c +7 -0
diff -r c4c47b2f1539 -r 26ccaf2ccdc5 block/blk-core.c
@@ -162,6 +162,10 @@
 
 		bio->bi_size -= nbytes;
 		bio->bi_sector += (nbytes >> 9);
+
+		if (bio_integrity(bio))
+			bio_integrity_advance(bio, nbytes);
+
 		if (bio->bi_size == 0)
 			bio_endio(bio, error);
 	} else {
@@ -1389,6 +1393,9 @@
 		 */
 		blk_partition_remap(bio);
 
+		if (bio_integrity_enabled(bio) && bio_integrity_prep(bio))
+			goto end_io;
+
 		if (old_sector != -1)
 			blk_add_trace_remap(q, bio, old_dev, bio->bi_sector,
 					    old_sector);
block/blk-integrity.c new file +385 -0

385 lines not on this page · raw message

block/blk-merge.c +3 -0
diff -r c4c47b2f1539 -r 26ccaf2ccdc5 block/blk-merge.c
@@ -441,6 +441,9 @@
 	    || next->special)
 		return 0;
 
+	if (blk_integrity_rq(req) != blk_integrity_rq(next))
+		return 0;
+
 	/*
 	 * If we are allowed to merge, then append bio list
 	 * from next to rq and release next. merge_requests_fn
block/blk.h +8 -0
diff -r c4c47b2f1539 -r 26ccaf2ccdc5 block/blk.h
@@ -52,4 +52,12 @@
 	return q->nr_congestion_off;
 }
 
+#if defined(CONFIG_BLK_DEV_INTEGRITY)
+
+#define rq_for_each_integrity_segment(bvl, _rq, _iter)		\
+	__rq_for_each_bio(_iter.bio, _rq)			\
+		bip_for_each_vec(bvl, _iter.bio->bi_integrity, _iter.i)
+
+#endif /* BLK_DEV_INTEGRITY */
+
 #endif
block/elevator.c +6 -0
diff -r c4c47b2f1539 -r 26ccaf2ccdc5 block/elevator.c
@@ -84,6 +84,12 @@
 	 * must be same device and not a special request
 	 */
 	if (rq->rq_disk != bio->bi_bdev->bd_disk || rq->special)
+		return 0;
+
+	/*
+	 * only merge integrity protected bio into ditto rq
+	 */
+	if (bio_integrity(bio) != blk_integrity_rq(rq))
 		return 0;
 
 	if (!elv_iosched_allow_merge(rq, bio))
include/linux/blkdev.h +97 -0

115 lines not on this page · raw message

include/linux/genhd.h +3 -0
diff -r c4c47b2f1539 -r 26ccaf2ccdc5 include/linux/genhd.h
@@ -141,6 +141,9 @@
 	struct disk_stats dkstats;
 #endif
 	struct work_struct async_notify;
+#ifdef  CONFIG_BLK_DEV_INTEGRITY
+	struct blk_integrity *integrity;
+#endif
 };
 
 /* 
#10 lore raw

PATCH 1/1 Detect devices with protection information turned on in INQUIRY

In reply to Martin K. Petersen
2 files changed, 4 insertions(+)
drivers/scsi/scsi_scan.c   |    3 +++
include/scsi/scsi_device.h |    1 +

---

drivers/scsi/scsi_scan.c +3 -0
diff -r 26ccaf2ccdc5 -r bfbea544d342 drivers/scsi/scsi_scan.c
@@ -877,6 +877,9 @@
 
 	if (*bflags & BLIST_USE_10_BYTE_MS)
 		sdev->use_10_for_ms = 1;
+
+	if (inq_result[5] & 0x1)
+		sdev->protection = 1;
 
 	/* set the device running here so that slave configure
 	 * may do I/O */
include/scsi/scsi_device.h +1 -0
diff -r 26ccaf2ccdc5 -r bfbea544d342 include/scsi/scsi_device.h
@@ -140,6 +140,7 @@
 	unsigned guess_capacity:1;	/* READ_CAPACITY might be too high by 1 */
 	unsigned retry_hwerror:1;	/* Retry HARDWARE_ERROR */
 	unsigned last_sector_bug:1;	/* Always read last sector in a 1 sector read */
+	unsigned protection:1;		/* Data Integrity Field */
 
 	DECLARE_BITMAP(supported_events, SDEV_EVT_MAXBITS); /* supported events */
 	struct list_head event_list;	/* asserted events */
#11 lore raw

PATCH 1/1 SCSI host driver DIF helpers

In reply to Martin K. Petersen
1 file changed, 23 insertions(+)

include/scsi/sd.h | 23 +++++++++++++++++++++++

HBA drivers need to poke in the CDB to prepare DIF commands. Provide helpers for extracting {RD,WR}PROTECT and start LBA.

---

include/scsi/sd.h +23 -0
diff -r b512cd02f8b0 -r 5262eba570f4 include/scsi/sd.h
@@ -1,5 +1,7 @@
 #ifndef _SCSI_DISK_H
 #define _SCSI_DISK_H
+
+#include <scsi/scsi_cmnd.h>
 
 /*
  * More than enough for everybody ;)  The huge number of majors
@@ -61,4 +63,25 @@
 		    (sdsk)->disk->disk_name, ##a) :			\
 	sdev_printk(prefix, (sdsk)->device, fmt, ##a)
 
+static inline unsigned int sd_protect_field(struct scsi_cmnd *scmd)
+{
+	switch(scmd->cmnd[0]) {
+	case READ_10:
+	case READ_12:
+	case READ_16:
+	case WRITE_10:
+	case WRITE_12:
+	case WRITE_16:
+		return (scmd->cmnd[1] & 0xe0) >> 5;
+
+	default:
+		return -1;
+	}
+}
+
+static inline sector_t sd_start_lba(struct scsi_cmnd *scmd)
+{
+	return scmd->request->sector;
+}
+
 #endif /* _SCSI_DISK_H */
#12 lore raw

PATCH 1/1 Support for the SBC Data Integrity Field format

In reply to Martin K. Petersen
9 files changed, 729 insertions(+)
Documentation/scsi/data-integrity.txt |   57 +++
drivers/scsi/Kconfig                  |    1 
drivers/scsi/Makefile                 |    2 
drivers/scsi/scsi_dif.c               |  496 +++++++++++++++++++++++++++++++++
drivers/scsi/scsi_error.c             |    3 
drivers/scsi/scsi_lib.c               |    4 
drivers/scsi/scsi_sysfs.c             |    4 
include/scsi/scsi_dif.h               |  158 ++++++++++
include/scsi/scsi_host.h              |    4 

---

Documentation/scsi/data-integrity.txt new file +57 -0
diff -r ea489bb64376 -r 7d9a353f8b7c Documentation/scsi/data-integrity.txt
@@ -0,0 +1,57 @@
+----------------------------------------------------------------------
+1.0 INTRODUCTION
+
+For a general overview of the data integrity framework please consult
+Documentation/block/data-integrity.txt.
+
+----------------------------------------------------------------------
+2.0 SCSI LAYER IMPLEMENTATION DETAILS
+
+The scsi_command has been extended with a scatterlist for the
+integrity metadata.  Note that all SCSI mid layer changes refer to
+this using the term "protection information" which is what it is
+called in the T10 spec.
+
+The term DIF (Data Integrity Field) is specific to SCSI disks (SBC).
+The SCSI midlayer doesn't know, or care, about the contents of the
+protection scatterlist, except it calls blk_rq_map_integrity_sg()
+during command initialization.
+
+
+2.1 SCSI DEVICE SCANNING
+
+A SCSI device has the PROTECT bit set in the standard INQUIRY page if
+it supports protection information.  The state of this bit is saved in
+the scsi_device struct.
+
+
+2.2 SCSI DISK SETUP
+
+In the case of a SCSI disk the actual DIF protection format is
+contained in in result of READ CAPACITY(16).  Consequently we have to
+use the 16-byte READ CAPACITY variant if the device is
+protection-capable.
+
+If the device has DIF-enabled we'll negotiate capabilities with the
+HBA.  And if the HBA is capable of protection DMA, the blk_integrity
+profile will be registered.
+
+Currently we only support Type 1 and Type 3.  Type 2 is only defined
+for 32-byte CDBs and is awaiting varlen CDB support.
+
+The controller may support checksum conversion as an optimization.
+Initial benchmarks showed that calculating a 16-bit CRC for each 512
+bytes of an I/O was expensive.  Emulex' hardware had the capability to
+convert an IP checksum to the T10 CRC on the wire.  So as part of the
+negotiation process the checksum algorithm will be selected and the
+blk_integrity profile set accordingly.
+
+----------------------------------------------------------------------
+3.0 HBA INTERFACE
+
+See the following doc:
+
+http://oss.oracle.com/projects/data-integrity/dist/documentation/linux-hba.pdf
+
+----------------------------------------------------------------------
+2007-12-24 Martin K. Petersen <[email protected]>
drivers/scsi/Kconfig +1 -0
diff -r ea489bb64376 -r 7d9a353f8b7c drivers/scsi/Kconfig
@@ -265,6 +265,7 @@
        bool "SCSI Data Integrity Protection"
        depends on SCSI
        depends on BLK_DEV_INTEGRITY
+       select CRC_T10DIF
        help 
 	 Some SCSI devices support data protection features above and
 	 beyond those implemented in the transport.  Select this
drivers/scsi/Makefile +2 -0
diff -r ea489bb64376 -r 7d9a353f8b7c drivers/scsi/Makefile
@@ -148,6 +148,8 @@
 scsi_tgt-y			+= scsi_tgt_lib.o scsi_tgt_if.o
 
 sd_mod-objs	:= sd.o
+sd_mod-$(CONFIG_SCSI_PROTECTION) += scsi_dif.o
+
 sr_mod-objs	:= sr.o sr_ioctl.o sr_vendor.o
 ncr53c8xx-flags-$(CONFIG_SCSI_ZALON) \
 		:= -DCONFIG_NCR53C8XX_PREFETCH -DSCSI_NCR_BIG_ENDIAN \
drivers/scsi/scsi_dif.c new file +496 -0

496 lines not on this page · raw message

drivers/scsi/scsi_error.c +3 -0
diff -r ea489bb64376 -r 7d9a353f8b7c drivers/scsi/scsi_error.c
@@ -333,6 +333,9 @@
 		return /* soft_error */ SUCCESS;
 
 	case ABORTED_COMMAND:
+		if (sshdr.asc == 0x10) /* DIF */
+			return SUCCESS;
+
 		return NEEDS_RETRY;
 	case NOT_READY:
 	case UNIT_ATTENTION:
drivers/scsi/scsi_lib.c +4 -0
diff -r ea489bb64376 -r 7d9a353f8b7c drivers/scsi/scsi_lib.c
@@ -943,6 +943,10 @@
 				scsi_requeue_command(q, cmd);
 				return;
 			} else {
+				if (sshdr.asc == 0x10) { /* DIF */
+					scsi_print_result(cmd);
+					scsi_print_sense("", cmd);
+				}
 				scsi_end_request(cmd, -EIO, this_count, 1);
 				return;
 			}
drivers/scsi/scsi_sysfs.c +4 -0
diff -r ea489bb64376 -r 7d9a353f8b7c drivers/scsi/scsi_sysfs.c
@@ -247,6 +247,8 @@
 shost_rd_attr(can_queue, "%hd\n");
 shost_rd_attr(sg_tablesize, "%hu\n");
 shost_rd_attr(unchecked_isa_dma, "%d\n");
+shost_rd_attr(dif_capabilities, "%hd\n");
+shost_rd_attr(dif_guard_type, "%hd\n");
 shost_rd_attr2(proc_name, hostt->proc_name, "%s\n");
 
 static struct device_attribute *scsi_sysfs_shost_attrs[] = {
@@ -261,6 +263,8 @@
 	&dev_attr_hstate,
 	&dev_attr_supported_mode,
 	&dev_attr_active_mode,
+	&dev_attr_dif_capabilities,
+	&dev_attr_dif_guard_type,
 	NULL
 };
 
include/scsi/scsi_dif.h new file +158 -0

158 lines not on this page · raw message

include/scsi/scsi_host.h +4 -0
diff -r ea489bb64376 -r 7d9a353f8b7c include/scsi/scsi_host.h
@@ -638,6 +638,10 @@
 	 */
 	unsigned int max_host_blocked;
 
+	/* Data Integrity Field */
+	unsigned char dif_capabilities;
+	unsigned char dif_guard_type;
+
 	/*
 	 * q used for scsi_tgt msgs, async events or any other requests that
 	 * need to be processed in userspace
#13 lore raw

PATCH 1/1 Move scsi_disk() accessor function to sd.h

In reply to Martin K. Petersen
2 files changed, 5 insertions(+), 5 deletions(-)
drivers/scsi/sd.c |    5 -----
include/scsi/sd.h |    5 +++++

---

drivers/scsi/sd.c +0 -5
diff -r d13d7571165e -r b512cd02f8b0 drivers/scsi/sd.c
@@ -292,11 +292,6 @@
 		BUG();
 		return 0;	/* shut up gcc */
 	}
-}
-
-static inline struct scsi_disk *scsi_disk(struct gendisk *disk)
-{
-	return container_of(disk->private_data, struct scsi_disk, driver);
 }
 
 static struct scsi_disk *__scsi_disk_get(struct gendisk *disk)
include/scsi/sd.h +5 -0
diff -r d13d7571165e -r b512cd02f8b0 include/scsi/sd.h
@@ -50,6 +50,11 @@
 
 extern void sd_print_sense_hdr(struct scsi_disk *, struct scsi_sense_hdr *);
 
+static inline struct scsi_disk *scsi_disk(struct gendisk *disk)
+{
+	return container_of(disk->private_data, struct scsi_disk, driver);
+}
+
 #define sd_printk(prefix, sdsk, fmt, a...)				\
         (sdsk)->disk ?							\
 	sdev_printk(prefix, (sdsk)->device, "[%s] " fmt,		\
#16 lore raw

PATCH 1/1 Allow sd_print_sense_hdr to be called outside of sd.c

In reply to Martin K. Petersen
2 files changed, 4 insertions(+), 3 deletions(-)
drivers/scsi/sd.c |    5 ++---
include/scsi/sd.h |    2 ++

---

drivers/scsi/sd.c +2 -3
diff -r 7d9a353f8b7c -r d13d7571165e drivers/scsi/sd.c
@@ -96,7 +96,6 @@
 static int sd_done(struct scsi_cmnd *);
 static void sd_read_capacity(struct scsi_disk *sdkp, unsigned char *buffer);
 static void scsi_disk_release(struct device *cdev);
-static void sd_print_sense_hdr(struct scsi_disk *, struct scsi_sense_hdr *);
 static void sd_print_result(struct scsi_disk *, int);
 
 static DEFINE_IDR(sd_index_idr);
@@ -1930,14 +1929,14 @@
 module_init(init_sd);
 module_exit(exit_sd);
 
-static void sd_print_sense_hdr(struct scsi_disk *sdkp,
-			       struct scsi_sense_hdr *sshdr)
+void sd_print_sense_hdr(struct scsi_disk *sdkp, struct scsi_sense_hdr *sshdr)
 {
 	sd_printk(KERN_INFO, sdkp, "");
 	scsi_show_sense_hdr(sshdr);
 	sd_printk(KERN_INFO, sdkp, "");
 	scsi_show_extd_sense(sshdr->asc, sshdr->ascq);
 }
+EXPORT_SYMBOL(sd_print_sense_hdr);
 
 static void sd_print_result(struct scsi_disk *sdkp, int result)
 {
include/scsi/sd.h +2 -0
diff -r 7d9a353f8b7c -r d13d7571165e include/scsi/sd.h
@@ -48,6 +48,8 @@
 };
 #define to_scsi_disk(obj) container_of(obj,struct scsi_disk,dev)
 
+extern void sd_print_sense_hdr(struct scsi_disk *, struct scsi_sense_hdr *);
+
 #define sd_printk(prefix, sdsk, fmt, a...)				\
         (sdsk)->disk ?							\
 	sdev_printk(prefix, (sdsk)->device, "[%s] " fmt,		\
#17 lore raw

PATCH 1/1 SCSI protection information scatterlist handling

In reply to Martin K. Petersen
3 files changed, 86 insertions(+)
drivers/scsi/Kconfig     |   15 +++++++++++++++
drivers/scsi/scsi_lib.c  |   42 ++++++++++++++++++++++++++++++++++++++++++
include/scsi/scsi_cmnd.h |   29 +++++++++++++++++++++++++++++

---

drivers/scsi/Kconfig +15 -0
diff -r f37e1616176b -r ea489bb64376 drivers/scsi/Kconfig
@@ -260,6 +260,21 @@
 	default m
 	depends on SCSI
 	depends on MODULES
+
+config SCSI_PROTECTION
+       bool "SCSI Data Integrity Protection"
+       depends on SCSI
+       depends on BLK_DEV_INTEGRITY
+       help 
+	 Some SCSI devices support data protection features above and
+	 beyond those implemented in the transport.  Select this
+	 option to enable protection information to be transferred to
+	 and from a device.  Specifically, this option will enable DIF
+	 (Data Integrity Field) for SCSI disks.
+
+	 The SCSI protection features depend on the block layer data
+	 integrity infrastructure so the latter must be enabled for
+	 this option to work.
 
 menu "SCSI Transports"
 	depends on SCSI
drivers/scsi/scsi_lib.c +42 -0

60 lines not on this page · raw message

include/scsi/scsi_cmnd.h +29 -0
diff -r f37e1616176b -r ea489bb64376 include/scsi/scsi_cmnd.h
@@ -71,6 +71,9 @@
 
 	/* These elements define the operation we ultimately want to perform */
 	struct scsi_data_buffer sdb;
+#if defined(CONFIG_SCSI_PROTECTION)
+	struct scsi_data_buffer *prot_sdb;
+#endif
 	unsigned underflow;	/* Return error if less than
 				   this amount is transferred */
 
@@ -192,4 +195,30 @@
 				 buf, buflen);
 }
 
+#if defined(CONFIG_SCSI_PROTECTION)
+
+static inline unsigned scsi_prot_sg_count(struct scsi_cmnd *cmd)
+{
+	return cmd->prot_sdb ? cmd->prot_sdb->table.nents : 0;
+}
+
+static inline struct scatterlist *scsi_prot_sglist(struct scsi_cmnd *cmd)
+{
+	return cmd->prot_sdb ? cmd->prot_sdb->table.sgl : NULL;
+}
+
+static inline struct scsi_data_buffer *scsi_prot(struct scsi_cmnd *cmd)
+{
+	return cmd->prot_sdb;
+}
+
+#define scsi_for_each_prot_sg(cmd, sg, nseg, __i)		\
+	for_each_sg(scsi_prot_sglist(cmd), sg, nseg, __i)
+
+#else /* CONFIG_SCSI_PROTECTION */
+
+#define scsi_prot_sg_count(a)		(0)
+
+#endif /* CONFIG_SCSI_PROTECTION */
+
 #endif /* _SCSI_SCSI_CMND_H */
#18 lore raw

PATCH 1/1 Rename scsi_bidi_sdb_cache

In reply to Martin K. Petersen
1 file changed, 13 insertions(+), 13 deletions(-)

drivers/scsi/scsi_lib.c | 26 +++++++++++++-------------

The data integrity changes need to dynamically allocate scsi_data_buffers too. Rename scsi_bidi_sdb_cache for clarity.

---

drivers/scsi/scsi_lib.c +13 -13
diff -r bfbea544d342 -r f37e1616176b drivers/scsi/scsi_lib.c
@@ -65,7 +65,7 @@
 };
 #undef SP
 
-static struct kmem_cache *scsi_bidi_sdb_cache;
+static struct kmem_cache *scsi_sdb_cache;
 
 static void scsi_run_queue(struct request_queue *q);
 
@@ -771,7 +771,7 @@
 		struct scsi_data_buffer *bidi_sdb =
 			cmd->request->next_rq->special;
 		scsi_free_sgtable(bidi_sdb);
-		kmem_cache_free(scsi_bidi_sdb_cache, bidi_sdb);
+		kmem_cache_free(scsi_sdb_cache, bidi_sdb);
 		cmd->request->next_rq->special = NULL;
 	}
 }
@@ -1046,7 +1046,7 @@
 
 	if (blk_bidi_rq(cmd->request)) {
 		struct scsi_data_buffer *bidi_sdb = kmem_cache_zalloc(
-			scsi_bidi_sdb_cache, GFP_ATOMIC);
+			scsi_sdb_cache, GFP_ATOMIC);
 		if (!bidi_sdb) {
 			error = BLKPREP_DEFER;
 			goto err_exit;
@@ -1678,11 +1678,11 @@
 		return -ENOMEM;
 	}
 
-	scsi_bidi_sdb_cache = kmem_cache_create("scsi_bidi_sdb",
-					sizeof(struct scsi_data_buffer),
-					0, 0, NULL);
-	if (!scsi_bidi_sdb_cache) {
-		printk(KERN_ERR "SCSI: can't init scsi bidi sdb cache\n");
+	scsi_sdb_cache = kmem_cache_create("scsi_sdb",
+					   sizeof(struct scsi_data_buffer),
+					   0, 0, NULL);
+	if (!scsi_sdb_cache) {
+		printk(KERN_ERR "SCSI: can't init scsi sdb cache\n");
 		goto cleanup_io_context;
 	}
 
@@ -1695,7 +1695,7 @@
 		if (!sgp->slab) {
 			printk(KERN_ERR "SCSI: can't init sg slab %s\n",
 					sgp->name);
-			goto cleanup_bidi_sdb;
+			goto cleanup_sdb;
 		}
 
 		sgp->pool = mempool_create_slab_pool(SG_MEMPOOL_SIZE,
@@ -1703,13 +1703,13 @@
 		if (!sgp->pool) {
 			printk(KERN_ERR "SCSI: can't init sg mempool %s\n",
 					sgp->name);
-			goto cleanup_bidi_sdb;
+			goto cleanup_sdb;
 		}
 	}
 
 	return 0;
 
-cleanup_bidi_sdb:
+cleanup_sdb:
 	for (i = 0; i < SG_MEMPOOL_NR; i++) {
 		struct scsi_host_sg_pool *sgp = scsi_sg_pools + i;
 		if (sgp->pool)
@@ -1717,7 +1717,7 @@
 		if (sgp->slab)
 			kmem_cache_destroy(sgp->slab);
 	}
-	kmem_cache_destroy(scsi_bidi_sdb_cache);
+	kmem_cache_destroy(scsi_sdb_cache);
 cleanup_io_context:
 	kmem_cache_destroy(scsi_io_context_cache);
 
@@ -1729,7 +1729,7 @@
 	int i;
 
 	kmem_cache_destroy(scsi_io_context_cache);
-	kmem_cache_destroy(scsi_bidi_sdb_cache);
+	kmem_cache_destroy(scsi_sdb_cache);
 
 	for (i = 0; i < SG_MEMPOOL_NR; i++) {
 		struct scsi_host_sg_pool *sgp = scsi_sg_pools + i;
#19 lore raw

PATCH 1/1 Add support for data integrity to DM

In reply to Martin K. Petersen
4 files changed, 60 insertions(+), 4 deletions(-)
drivers/md/dm-table.c         |   35 ++++++++++++++++++++++++++++++++++-
drivers/md/dm.c               |   26 ++++++++++++++++++++++++--
drivers/md/dm.h               |    2 +-
include/linux/device-mapper.h |    1 +

If all subdevices support the same protection format the DM device is flagged as capable.

---

drivers/md/dm-table.c +34 -1
diff -r f32469624774 -r 49afddbc7220 drivers/md/dm-table.c
@@ -897,8 +897,12 @@
 	return &t->targets[(KEYS_PER_NODE * n) + k];
 }
 
-void dm_table_set_restrictions(struct dm_table *t, struct request_queue *q)
+void dm_table_set_restrictions(struct dm_table *t, struct mapped_device *md)
 {
+	struct request_queue *q = dm_queue(md);
+	struct list_head *devices = dm_table_get_devices(t);
+	struct dm_dev *prev, *cur;
+
 	/*
 	 * Make sure we obey the optimistic sub devices
 	 * restrictions.
@@ -916,6 +920,35 @@
 	else
 		q->queue_flags |= (1 << QUEUE_FLAG_CLUSTER);
 
+	/*
+	 * Run through all devices to ensure they have matching
+	 * integrity profile
+	 */
+	cur = prev = NULL;
+
+	list_for_each_entry(cur, devices, list) {
+
+		if (prev && blk_integrity_compare(prev->bdev, cur->bdev) < 0) {
+			printk(KERN_ERR "%s: %s %s Integrity mismatch!\n",
+			       __func__, prev->bdev->bd_disk->disk_name,
+			       cur->bdev->bd_disk->disk_name);
+			return;
+		}
+		prev = cur;
+	}
+
+	/* Register dm device as being integrity capable */
+	if (prev && bdev_get_integrity(prev->bdev)) {
+		struct gendisk *disk = dm_disk(md);
+
+		if (blk_integrity_register(dm_disk(md), 
+					   bdev_get_integrity(prev->bdev)))
+			printk(KERN_ERR "%s: %s Could not register integrity!\n",
+			       __func__, disk->disk_name);
+		else
+			printk(KERN_INFO "Enabling data integrity on %s\n",
+			       disk->disk_name);
+	}
 }
 
 unsigned int dm_table_get_num_targets(struct dm_table *t)
drivers/md/dm.c +24 -2
diff -r f32469624774 -r 49afddbc7220 drivers/md/dm.c
@@ -665,6 +665,12 @@
 	clone->bi_size = to_bytes(len);
 	clone->bi_io_vec->bv_offset = offset;
 	clone->bi_io_vec->bv_len = clone->bi_size;
+	clone->bi_flags |= 1 << BIO_CLONED;
+
+	if (bio_integrity(bio)) {
+		bio_integrity_clone(clone, bio, bs);
+		bio_integrity_trim(clone, bio_sector_offset(bio, idx, offset), len);
+	}
 
 	return clone;
 }
@@ -686,6 +692,13 @@
 	clone->bi_vcnt = idx + bv_count;
 	clone->bi_size = to_bytes(len);
 	clone->bi_flags &= ~(1 << BIO_SEG_VALID);
+
+	if (bio_integrity(bio)) {
+		bio_integrity_clone(clone, bio, bs);
+
+		if (idx != bio->bi_idx || clone->bi_size < bio->bi_size)
+			bio_integrity_trim(clone, bio_sector_offset(bio, idx, 0), len);
+	}
 
 	return clone;
 }
@@ -1059,6 +1072,7 @@
 	md->disk->queue = md->queue;
 	md->disk->private_data = md;
 	sprintf(md->disk->disk_name, "dm-%d", minor);
+	printk(KERN_ERR "DM: Created %s\n", md->disk->disk_name);
 	add_disk(md->disk);
 	format_dev_t(md->name, MKDEV(_major, minor));
 
@@ -1108,6 +1122,7 @@
 	mempool_destroy(md->tio_pool);
 	mempool_destroy(md->io_pool);
 	bioset_free(md->bs);
+	blk_integrity_unregister(md->disk);
 	del_gendisk(md->disk);
 	free_minor(minor);
 
@@ -1151,7 +1166,6 @@
 
 static int __bind(struct mapped_device *md, struct dm_table *t)
 {
-	struct request_queue *q = md->queue;
 	sector_t size;
 
 	size = dm_table_get_size(t);
@@ -1172,7 +1186,7 @@
 
 	write_lock(&md->map_lock);
 	md->map = t;
-	dm_table_set_restrictions(t, q);
+	dm_table_set_restrictions(t, md);
 	write_unlock(&md->map_lock);
 
 	return 0;
@@ -1624,7 +1638,15 @@
  */
 struct gendisk *dm_disk(struct mapped_device *md)
 {
+	BUG_ON(md == NULL);
+	BUG_ON(md->disk == NULL);
+
 	return md->disk;
+}
+
+struct request_queue *dm_queue(struct mapped_device *md)
+{
+	return md->queue;
 }
 
 int dm_suspended(struct mapped_device *md)
drivers/md/dm.h +1 -1
diff -r f32469624774 -r 49afddbc7220 drivers/md/dm.h
@@ -104,7 +104,7 @@
 			     void (*fn)(void *), void *context);
 struct dm_target *dm_table_get_target(struct dm_table *t, unsigned int index);
 struct dm_target *dm_table_find_target(struct dm_table *t, sector_t sector);
-void dm_table_set_restrictions(struct dm_table *t, struct request_queue *q);
+void dm_table_set_restrictions(struct dm_table *t, struct mapped_device *md);
 struct list_head *dm_table_get_devices(struct dm_table *t);
 void dm_table_presuspend_targets(struct dm_table *t);
 void dm_table_postsuspend_targets(struct dm_table *t);
include/linux/device-mapper.h +1 -0
diff -r f32469624774 -r 49afddbc7220 include/linux/device-mapper.h
@@ -194,6 +194,7 @@
 const char *dm_device_name(struct mapped_device *md);
 int dm_copy_name_and_uuid(struct mapped_device *md, char *name, char *uuid);
 struct gendisk *dm_disk(struct mapped_device *md);
+struct request_queue *dm_queue(struct mapped_device *md);
 int dm_suspended(struct mapped_device *md);
 int dm_noflush_suspending(struct dm_target *ti);
 
#20 lore raw

PATCH 1/1 Add support for data integrity to MD

In reply to Martin K. Petersen
1 file changed, 28 insertions(+), 1 deletion(-)

drivers/md/md.c | 29 ++++++++++++++++++++++++++++-

If all subdevices support the same protection format the MD device is flagged as capable.

---

drivers/md/md.c +28 -1
diff -r 49afddbc7220 -r 927db99c5b35 drivers/md/md.c
@@ -3284,7 +3284,7 @@
 	int err;
 	int chunk_size;
 	struct list_head *tmp;
-	mdk_rdev_t *rdev;
+	mdk_rdev_t *rdev, *prev;
 	struct gendisk *disk;
 	struct mdk_personality *pers;
 	char b[BDEVNAME_SIZE];
@@ -3541,6 +3541,32 @@
 	mddev->changed = 1;
 	md_new_event(mddev);
 	kobject_uevent(&mddev->gendisk->dev.kobj, KOBJ_CHANGE);
+
+	prev = NULL;
+
+	/* Data Integrity */
+	rdev_for_each(rdev, tmp, mddev) {
+
+		if (prev && blk_integrity_compare(prev->bdev, rdev->bdev) < 0) {
+			printk(KERN_ERR "%s: %s %s Integrity mismatch!\n",
+			       __func__, prev->bdev->bd_disk->disk_name,
+			       rdev->bdev->bd_disk->disk_name);
+			return 0;
+		}
+
+		prev = rdev;
+	}
+
+	if (prev && bdev_get_integrity(prev->bdev)) {
+
+		if (blk_integrity_register(disk, prev->bdev->bd_disk->integrity))
+			printk(KERN_ERR "%s: %s Could not register integrity!\n",
+			       __func__, disk->disk_name);
+		else
+			printk(KERN_INFO "Enabling data integrity on %s\n",
+			       disk->disk_name);
+	}
+
 	return 0;
 }
 
@@ -3716,6 +3742,7 @@
 		printk(KERN_INFO "md: %s switched to read-only mode.\n",
 			mdname(mddev));
 	err = 0;
+	blk_integrity_unregister(disk);
 	md_new_event(mddev);
 out:
 	return err;
#21 lore raw

PATCH 1/1 Support for SCSI disk (SBC) Data Integrity Field

In reply to Martin K. Petersen
2 files changed, 53 insertions(+), 7 deletions(-)

drivers/scsi/sd.c | 58 ++++++++++++++++++++++++++++++++++++++++++++++-------
include/scsi/sd.h | 2 +

Configure DMA of protection information and issue READ/WRITE commands with RDPROTECT/WRPROTECT set accordingly.

Force READ CAPACITY(16) if the target has the PROTECT bit set and grab an extra byte of response (P_TYPE and PROT_EN are in byte 12).

---

drivers/scsi/sd.c +51 -7
diff -r 5262eba570f4 -r b912d7bb3c47 drivers/scsi/sd.c
@@ -59,6 +59,7 @@
 #include <scsi/scsi_ioctl.h>
 #include <scsi/scsicam.h>
 #include <scsi/sd.h>
+#include <scsi/scsi_dif.h>
 
 #include "scsi_logging.h"
 
@@ -233,6 +234,24 @@
 	return snprintf(buf, 40, "%d\n", sdkp->device->allow_restart);
 }
 
+static ssize_t
+sd_show_protection_type(struct device *dev, struct device_attribute *attr,
+			char *buf)
+{
+	struct scsi_disk *sdkp = to_scsi_disk(dev);
+
+	return snprintf(buf, 20, "%u\n", sdkp->protection_type);
+}
+
+static ssize_t
+sd_show_app_tag_own(struct device *dev, struct device_attribute *attr,
+		    char *buf)
+{
+	struct scsi_disk *sdkp = to_scsi_disk(dev);
+
+	return snprintf(buf, 20, "%u\n", sdkp->ATO);
+}
+
 static struct device_attribute sd_disk_attrs[] = {
 	__ATTR(cache_type, S_IRUGO|S_IWUSR, sd_show_cache_type,
 	       sd_store_cache_type),
@@ -241,6 +260,8 @@
 	       sd_store_allow_restart),
 	__ATTR(manage_start_stop, S_IRUGO|S_IWUSR, sd_show_manage_start_stop,
 	       sd_store_manage_start_stop),
+	__ATTR(protection_type, S_IRUGO, sd_show_protection_type, NULL),
+	__ATTR(app_tag_own, S_IRUGO, sd_show_app_tag_own, NULL),
 	__ATTR_NULL,
 };
 
@@ -353,6 +374,7 @@
 	struct scsi_cmnd *SCpnt;
 	struct scsi_device *sdp = q->queuedata;
 	struct gendisk *disk = rq->rq_disk;
+	struct scsi_disk *sdkp;
 	sector_t block = rq->sector;
 	unsigned int this_count = rq->nr_sectors;
 	unsigned int timeout = sdp->timeout;
@@ -369,6 +391,7 @@
 	if (ret != BLKPREP_OK)
 		goto out;
 	SCpnt = rq->special;
+	sdkp = scsi_disk(disk);
 
 	/* from here on until we're complete, any goto out
 	 * is used for a killable error condition */
@@ -458,6 +481,11 @@
 		}
 		SCpnt->cmnd[0] = WRITE_6;
 		SCpnt->sc_data_direction = DMA_TO_DEVICE;
+
+		if (blk_integrity_rq(rq) && 
+		    scsi_dif_prepare(rq, block, sdp->sector_size) == -EIO)
+			goto out;
+
 	} else if (rq_data_dir(rq) == READ) {
 		SCpnt->cmnd[0] = READ_6;
 		SCpnt->sc_data_direction = DMA_FROM_DEVICE;
@@ -472,8 +500,11 @@
 					"writing" : "reading", this_count,
 					rq->nr_sectors));
 
-	SCpnt->cmnd[1] = 0;
-	
+	if (scsi_host_dif_type(sdp->host, sdkp->protection_type))
+		SCpnt->cmnd[1] = 1 << 5;
+	else
+		SCpnt->cmnd[1] = 0;
+
 	if (block > 0xffffffff) {
 		SCpnt->cmnd[0] += READ_16 - READ_6;
 		SCpnt->cmnd[1] |= blk_fua_rq(rq) ? 0x8 : 0;
@@ -491,6 +522,7 @@
 		SCpnt->cmnd[13] = (unsigned char) this_count & 0xff;
 		SCpnt->cmnd[14] = SCpnt->cmnd[15] = 0;
 	} else if ((this_count > 0xff) || (block > 0x1fffff) ||
+		   SCpnt->device->protection ||
 		   SCpnt->device->use_10_for_rw) {
 		if (this_count > 0xffff)
 			this_count = 0xffff;
@@ -516,6 +548,8 @@
 				    "FUA write on READ/WRITE(6) drive\n");
 			goto out;
 		}
+
+		BUG_ON(sdkp->protection_type);
 
 		SCpnt->cmnd[1] |= (unsigned char) ((block >> 16) & 0x1f);
 		SCpnt->cmnd[2] = (unsigned char) ((block >> 8) & 0xff);
@@ -1005,7 +1039,8 @@
 		good_bytes = xfer_size;
 		break;
 	case ILLEGAL_REQUEST:
-		if (SCpnt->device->use_10_for_rw &&
+		if (SCpnt->device->protection == 0 &&
+		    SCpnt->device->use_10_for_rw &&
 		    (SCpnt->cmnd[0] == READ_10 ||
 		     SCpnt->cmnd[0] == WRITE_10))
 			SCpnt->device->use_10_for_rw = 0;
@@ -1018,6 +1053,9 @@
 		break;
 	}
  out:
+	if (rq_data_dir(SCpnt->request) == READ && scsi_prot_sg_count(SCpnt))
+		scsi_dif_complete(SCpnt, good_bytes);
+
 	return good_bytes;
 }
 
@@ -1172,7 +1210,8 @@
 	unsigned char cmd[16];
 	int the_result, retries;
 	int sector_size = 0;
-	int longrc = 0;
+	/* Force READ CAPACITY(16) when PROTECT=1 */
+	int longrc = sdkp->device->protection ? 1 : 0;
 	struct scsi_sense_hdr sshdr;
 	int sense_valid = 0;
 	struct scsi_device *sdp = sdkp->device;
@@ -1184,8 +1223,8 @@
 			memset((void *) cmd, 0, 16);
 			cmd[0] = SERVICE_ACTION_IN;
 			cmd[1] = SAI_READ_CAPACITY_16;
-			cmd[13] = 12;
-			memset((void *) buffer, 0, 12);
+			cmd[13] = 13;
+			memset((void *) buffer, 0, 13);
 		} else {
 			cmd[0] = READ_CAPACITY;
 			memset((void *) &cmd[1], 0, 9);
@@ -1193,7 +1232,7 @@
 		}
 		
 		the_result = scsi_execute_req(sdp, cmd, DMA_FROM_DEVICE,
-					      buffer, longrc ? 12 : 8, &sshdr,
+					      buffer, longrc ? 13 : 8, &sshdr,
 					      SD_TIMEOUT, SD_MAX_RETRIES);
 
 		if (media_not_present(sdkp, &sshdr))
@@ -1268,6 +1307,8 @@
 			
 		sector_size = (buffer[8] << 24) |
 			(buffer[9] << 16) | (buffer[10] << 8) | buffer[11];
+
+		scsi_dif_config_disk(sdkp, buffer);
 	}	
 
 	/* Some devices return the total number of sectors, not the
@@ -1565,6 +1606,7 @@
 	sdkp->write_prot = 0;
 	sdkp->WCE = 0;
 	sdkp->RCD = 0;
+	sdkp->ATO = 0;
 
 	sd_spinup_disk(sdkp);
 
@@ -1576,6 +1618,7 @@
 		sd_read_capacity(sdkp, buffer);
 		sd_read_write_protect_flag(sdkp, buffer);
 		sd_read_cache_type(sdkp, buffer);
+		scsi_dif_app_tag_own(sdkp, buffer);
 	}
 
 	/*
@@ -1709,6 +1752,7 @@
 
 	dev_set_drvdata(dev, sdkp);
 	add_disk(gd);
+	scsi_dif_config_host(sdkp);
 
 	sd_printk(KERN_NOTICE, sdkp, "Attached SCSI %sdisk\n",
 		  sdp->removable ? "removable " : "");
include/scsi/sd.h +2 -0
diff -r 5262eba570f4 -r b912d7bb3c47 include/scsi/sd.h
@@ -43,7 +43,9 @@
 	u32		index;
 	u8		media_present;
 	u8		write_prot;
+	u8		protection_type;/* Data Integrity Field */
 	unsigned	previous_state : 1;
+	unsigned	ATO : 1;	/* state of disk ATO bit */
 	unsigned	WCE : 1;	/* state of disk WCE bit */
 	unsigned	RCD : 1;	/* state of disk RCD bit, unused */
 	unsigned	DPOFUA : 1;	/* state of disk DPOFUA bit */
#22 lore raw

PATCH 1/1 Implement support for DIF in SCSI debug driver

In reply to Martin K. Petersen
1 file changed, 385 insertions(+), 4 deletions(-)

drivers/scsi/scsi_debug.c | 389 ++++++++++++++++++++++++++++++++++++++++++++-

---

drivers/scsi/scsi_debug.c +385 -4
diff -r b912d7bb3c47 -r f32469624774 drivers/scsi/scsi_debug.c
@@ -40,6 +40,8 @@
 #include <linux/moduleparam.h>
 #include <linux/scatterlist.h>
 #include <linux/blkdev.h>
+#include <linux/crc-t10dif.h>
+#include <linux/ctype.h>
 
 #include <scsi/scsi.h>
 #include <scsi/scsi_cmnd.h>
@@ -47,8 +49,12 @@
 #include <scsi/scsi_host.h>
 #include <scsi/scsicam.h>
 #include <scsi/scsi_eh.h>
+#include <scsi/sd.h>
+#include <scsi/scsi_dif.h>
+#include <scsi/scsi_cmnd.h>
 
 #include <linux/stat.h>
+#include <net/checksum.h>
 
 #include "scsi_logging.h"
 
@@ -94,6 +100,9 @@
 #define DEF_VIRTUAL_GB   0
 #define DEF_FAKE_RW	0
 #define DEF_VPD_USE_HOSTNO 1
+#define DEF_PROTECTION 0
+#define DEF_GUARD 1
+#define DEF_ATO 1
 
 /* bit mask values for scsi_debug_opts */
 #define SCSI_DEBUG_OPT_NOISE   1
@@ -142,6 +151,9 @@
 static int scsi_debug_virtual_gb = DEF_VIRTUAL_GB;
 static int scsi_debug_fake_rw = DEF_FAKE_RW;
 static int scsi_debug_vpd_use_hostno = DEF_VPD_USE_HOSTNO;
+static int scsi_debug_protection = DEF_PROTECTION;
+static int scsi_debug_guard = DEF_GUARD;
+static int scsi_debug_ato = DEF_ATO;
 
 static int scsi_debug_cmnd_count = 0;
 
@@ -207,11 +219,15 @@
 static struct sdebug_queued_cmd queued_arr[SCSI_DEBUG_CANQUEUE];
 
 static unsigned char * fake_storep;	/* ramdisk storage */
+static unsigned char * dif_storep;	/* protection info */
 
 static int num_aborts = 0;
 static int num_dev_resets = 0;
 static int num_bus_resets = 0;
 static int num_host_resets = 0;
+static int dif_writes = 0;
+static int dif_reads = 0;
+static int dif_errors = 0;
 
 static DEFINE_SPINLOCK(queued_arr_lock);
 static DEFINE_RWLOCK(atomic_rw);
@@ -220,6 +236,11 @@
 
 static struct bus_type pseudo_lld_bus;
 
+static inline sector_t dif_offset(sector_t sector)
+{
+	return sector << 3;
+}
+
 static struct device_driver sdebug_driverfs_driver = {
 	.name 		= sdebug_proc_name,
 	.bus		= &pseudo_lld_bus,
@@ -227,6 +248,10 @@
 
 static const int check_condition_result =
 		(DRIVER_SENSE << 24) | SAM_STAT_CHECK_CONDITION;
+
+static const int illegal_condition_result =
+	((DRIVER_SENSE|SUGGEST_DIE) << 24) | (DID_ABORT << 16 ) 
+	| SAM_STAT_CHECK_CONDITION;
 
 static unsigned char ctrl_m_pg[] = {0xa, 10, 2, 0, 0, 0, 0, 0,
 				    0, 0, 0x2, 0x4b};
@@ -720,7 +745,11 @@
 		} else if (0x86 == cmd[2]) { /* extended inquiry */
 			arr[1] = cmd[2];	/*sanity */
 			arr[3] = 0x3c;	/* number of following entries */
-			arr[4] = 0x0;   /* no protection stuff */
+			if (scsi_debug_protection)
+				arr[4] = 0x5;   /* SPT: Type 1 Protection,
+						 * GRD_CHK:1, REF_CHK:1 */
+			else
+				arr[4] = 0x0;   /* no protection stuff */
 			arr[5] = 0x7;   /* head of q, ordered + simple q's */
 		} else if (0x87 == cmd[2]) { /* mode page policy */
 			arr[1] = cmd[2];	/*sanity */
@@ -758,6 +787,7 @@
 	arr[2] = scsi_debug_scsi_level;
 	arr[3] = 2;    /* response_data_format==2 */
 	arr[4] = SDEBUG_LONG_INQ_SZ - 5;
+	arr[5] = scsi_debug_protection ? 1 : 0; /* PROTECT bit */
 	if (0 == scsi_debug_vpd_use_hostno)
 		arr[5] = 0x10; /* claim: implicit TGPS */
 	arr[6] = 0x10; /* claim: MultiP */
@@ -906,6 +936,11 @@
 	arr[9] = (SECT_SIZE_PER(target) >> 16) & 0xff;
 	arr[10] = (SECT_SIZE_PER(target) >> 8) & 0xff;
 	arr[11] = SECT_SIZE_PER(target) & 0xff;
+	
+	if (scsi_debug_protection) {
+		arr[12] = (scsi_debug_protection - 1) << 1; /* P_TYPE */
+		arr[12] |= 1; /* PROT_EN */
+	}
 	return fill_from_dev_buffer(scp, arr,
 				    min(alloc_len, SDEBUG_READCAP16_ARR_SZ));
 }
@@ -1057,6 +1092,10 @@
 		ctrl_m_pg[2] |= 0x4;
 	else
 		ctrl_m_pg[2] &= ~0x4;
+
+	if (scsi_debug_ato)
+		ctrl_m_pg[5] |= 0x80; /* ATO=1 */
+
 	memcpy(p, ctrl_m_pg, sizeof(ctrl_m_pg));
 	if (1 == pcontrol)
 		memcpy(p + 2, ch_ctrl_m_pg, sizeof(ch_ctrl_m_pg));
@@ -1527,6 +1566,75 @@
 	return ret;
 }
 
+#ifdef CONFIG_SCSI_PROTECTION
+static int prot_verify_read(struct scsi_cmnd * SCpnt, sector_t start_sec,
+			    unsigned int sectors)
+{
+	unsigned int i, resid;
+	struct scatterlist *psgl;
+	struct scsi_dif_tuple *sdt;
+	sector_t sector;
+	void *paddr;
+
+	sdt = (struct scsi_dif_tuple *)(dif_storep + dif_offset(start_sec));
+
+	for (i=0 ; i<sectors ; i++) {
+		u16 csum;
+
+		if (sdt[i].app_tag == 0xffff)
+			continue;
+
+		sector = start_sec + i;
+
+		switch (scsi_debug_guard) {
+		case 1:
+			csum = ip_compute_csum(fake_storep + (sector * SECT_SIZE),
+					       SECT_SIZE);
+			break;
+		case 0:
+			csum = cpu_to_be16(crc_t10dif(fake_storep + (sector * SECT_SIZE),
+						      SECT_SIZE));
+			break;
+		default:
+			BUG();
+		}
+
+		if (sdt[i].guard_tag != csum) {
+			printk(KERN_ERR "%s: GUARD check failed on sector %lu " \
+			       "rcvd 0x%04x, data 0x%04x\n", __func__, sector, 
+			       be16_to_cpu(sdt[i].guard_tag), be16_to_cpu(csum));
+			dif_errors++;
+			return 0x01;
+		}
+
+		if (be32_to_cpu(sdt[i].ref_tag) != (sector & 0xffffffff)) {
+			printk(KERN_ERR "%s: REF check failed on sector %lu\n",
+			       __func__, sector);
+			dif_errors++;
+			return 0x03;
+		}
+	}
+
+	resid = sectors * 8; /* Bytes of protection data to copy into sgl */
+	sector = start_sec;
+
+	scsi_for_each_prot_sg(SCpnt, psgl, scsi_prot_sg_count(SCpnt), i) {
+		int len = min(psgl->length, resid);
+
+		paddr = kmap_atomic(sg_page(psgl), KM_IRQ0) + psgl->offset;
+		memcpy(paddr, dif_storep + dif_offset(sector), len);
+
+		sector += len >> 3;
+		resid -= len;
+		kunmap_atomic(sg_page(psgl), KM_IRQ0);
+	}
+
+	dif_reads++;
+
+	return 0;
+}
+#endif
+
 static int resp_read(struct scsi_cmnd *SCpnt, unsigned long long lba,
 		     unsigned int num, struct sdebug_dev_info *devip)
 {
@@ -1554,11 +1662,152 @@
 		}
 		return check_condition_result;
 	}
+
+	/* T10 DIF */
+#ifdef CONFIG_SCSI_PROTECTION
+	if (scsi_debug_protection && scsi_prot_sg_count(SCpnt)) {
+		int prot_ret;
+
+		if ((prot_ret = prot_verify_read(SCpnt, lba, num))) {
+			mk_sense_buffer(devip, ABORTED_COMMAND, 0x10, prot_ret);
+			return illegal_condition_result;
+		}
+	}
+#endif
+
 	read_lock_irqsave(&atomic_rw, iflags);
 	ret = do_device_access(SCpnt, devip, lba, num, 0);
 	read_unlock_irqrestore(&atomic_rw, iflags);
 	return ret;
 }
+
+#ifdef CONFIG_SCSI_PROTECTION
+
+void dump_sector(unsigned char *buf, int len)
+{
+	int i, j;
+
+	printk(KERN_ERR ">>> Sector Dump <<<\n");
+
+	for (i=0 ; i<len ; i+=16) {
+		printk(KERN_ERR "%04d: ", i);
+
+		for (j=0 ; j<16 ; j++) {
+			unsigned char c = buf[i+j];
+			if (c >= 0x20 && c < 0x7e)
+				printk(" %c ",buf[i+j]);
+			else
+				printk("%02x ", buf[i+j]);
+		}
+
+		printk("\n");
+	}
+}
+
+static int prot_verify_write(struct scsi_cmnd * SCpnt, sector_t start_sec,
+			     unsigned int sectors)
+{
+	int i, j, ret;
+	struct scsi_dif_tuple *sdt;
+	struct scatterlist *dsgl = scsi_sglist(SCpnt);
+	struct scatterlist *psgl = scsi_prot_sglist(SCpnt);
+	void *daddr, *paddr;
+	sector_t sector = start_sec;
+	int ppage_offset;
+	unsigned short csum;
+
+	if (((SCpnt->cmnd[1] >> 5) & 7) != 1) {
+		printk(KERN_WARNING "scsi_debug: WRPROTECT != 1\n");
+		return 0;
+	}
+
+	BUG_ON(scsi_sg_count(SCpnt) == 0);
+	BUG_ON(scsi_prot_sg_count(SCpnt) == 0);
+
+	paddr = kmap_atomic(sg_page(psgl), KM_IRQ1) + psgl->offset;
+	ppage_offset = 0;
+
+	/* For each data page */
+	scsi_for_each_sg(SCpnt, dsgl, scsi_sg_count(SCpnt), i) {
+		daddr = kmap_atomic(sg_page(dsgl), KM_IRQ0) + dsgl->offset;
+
+		/* For each sector-sized chunk in data page */
+		for (j=0 ; j<dsgl->length ; j+=SECT_SIZE) {
+
+			/* If we're at the end of the current
+			 * protection page advance to the next one
+			 */
+			if (ppage_offset >= psgl->length) {
+				kunmap_atomic(sg_page(psgl), KM_IRQ1);
+				psgl = sg_next(psgl);
+				BUG_ON(psgl == NULL);
+				paddr = kmap_atomic(sg_page(psgl), KM_IRQ1) 
+					+ psgl->offset;
+				ppage_offset = 0;
+			}
+
+			sdt = paddr + ppage_offset;
+
+			switch (scsi_debug_guard) {
+			case 1:
+				csum = ip_compute_csum(daddr, SECT_SIZE);
+				break;
+			case 0:
+				csum = cpu_to_be16(crc_t10dif(daddr, SECT_SIZE));
+				break;
+			default:
+				BUG();
+				ret = 0;
+				goto out;
+			}
+
+			if (sdt->guard_tag != csum) {
+				printk(KERN_ERR "%s: GUARD check failed on sector %lu " \
+				       "rcvd 0x%04x, calculated 0x%04x\n",
+				       __func__, sector, 
+				       be16_to_cpu(sdt->guard_tag), be16_to_cpu(csum));
+				ret = 0x01;
+				dump_sector(daddr, SECT_SIZE);
+				goto out;
+			}
+
+			if (be32_to_cpu(sdt->ref_tag) != (sector & 0xffffffff)) {
+				printk(KERN_ERR 
+				       "%s: REF check failed on sector %lu\n",
+				       __func__, sector);
+				ret = 0x03;
+				dump_sector(daddr, SECT_SIZE);
+				goto out;
+			}
+
+			/* Would be great to copy this in bigger
+			 * chunks.  However, for the sake of
+			 * correctness we need to verify each sector
+			 * before writing it to "stable" storage
+			 */
+			memcpy(dif_storep + dif_offset(sector), sdt, 8);
+
+			sector++;
+			daddr += SECT_SIZE;
+			ppage_offset += sizeof(struct scsi_dif_tuple);
+		}
+
+		kunmap_atomic(sg_page(dsgl), KM_IRQ0);
+	}
+
+	kunmap_atomic(sg_page(psgl), KM_IRQ1);
+
+	dif_writes++;
+
+	return 0;
+
+out:
+	dif_errors++;
+	kunmap_atomic(sg_page(dsgl), KM_IRQ0);
+	kunmap_atomic(sg_page(psgl), KM_IRQ1);
+	return ret;
+}
+#endif
 
 static int resp_write(struct scsi_cmnd *SCpnt, unsigned long long lba,
 		      unsigned int num, struct sdebug_dev_info *devip)
@@ -1569,6 +1818,18 @@
 	ret = check_device_access_params(devip, lba, num);
 	if (ret)
 		return ret;
+
+#ifdef CONFIG_SCSI_PROTECTION
+	/* T10 DIF */
+	if (scsi_debug_protection && scsi_prot_sg_count(SCpnt)) {
+		int prot_ret;
+
+		if ((prot_ret = prot_verify_write(SCpnt, lba, num))) {
+			mk_sense_buffer(devip, ILLEGAL_REQUEST, 0x10, prot_ret);
+			return illegal_condition_result;
+		}
+	}
+#endif
 
 	write_lock_irqsave(&atomic_rw, iflags);
 	ret = do_device_access(SCpnt, devip, lba, num, 1);
@@ -2085,6 +2346,9 @@
 module_param_named(virtual_gb, scsi_debug_virtual_gb, int, S_IRUGO | S_IWUSR);
 module_param_named(vpd_use_hostno, scsi_debug_vpd_use_hostno, int,
 		   S_IRUGO | S_IWUSR);
+module_param_named(protection, scsi_debug_protection, int, S_IRUGO | S_IWUSR);
+module_param_named(guard, scsi_debug_guard, int, S_IRUGO | S_IWUSR);
+module_param_named(ato, scsi_debug_ato, int, S_IRUGO | S_IWUSR);
 
 MODULE_AUTHOR("Eric Youngdale + Douglas Gilbert");
 MODULE_DESCRIPTION("SCSI debug adapter driver");
@@ -2106,7 +2370,8 @@
 MODULE_PARM_DESC(scsi_level, "SCSI level to simulate(def=5[SPC-3])");
 MODULE_PARM_DESC(virtual_gb, "virtual gigabyte size (def=0 -> use dev_size_mb)");
 MODULE_PARM_DESC(vpd_use_hostno, "0 -> dev ids ignore hostno (def=1 -> unique dev ids)");
-
+MODULE_PARM_DESC(protection, "enable support for data integrity field (def=0)");
+MODULE_PARM_DESC(guard, "protection checksum: 0=crc, 1=ip (def=1)");
 
 static char sdebug_info[256];
 
@@ -2153,13 +2418,14 @@
 	    "delay=%d, max_luns=%d, scsi_level=%d\n"
 	    "sector_size=%d bytes, cylinders=%d, heads=%d, sectors=%d\n"
 	    "number of aborts=%d, device_reset=%d, bus_resets=%d, "
-	    "host_resets=%d\n",
+	    "host_resets=%d\ndif_reads=%d dif_writes=%d dif_errors=%d\n",
 	    SCSI_DEBUG_VERSION, scsi_debug_version_date, scsi_debug_num_tgts,
 	    scsi_debug_dev_size_mb, scsi_debug_opts, scsi_debug_every_nth,
 	    scsi_debug_cmnd_count, scsi_debug_delay,
 	    scsi_debug_max_luns, scsi_debug_scsi_level,
 	    SECT_SIZE, sdebug_cylinders_per, sdebug_heads, sdebug_sectors_per,
-	    num_aborts, num_dev_resets, num_bus_resets, num_host_resets);
+	    num_aborts, num_dev_resets, num_bus_resets, num_host_resets,
+	    dif_reads, dif_writes, dif_errors);
 	if (pos < offset) {
 		len = 0;
 		begin = pos;
@@ -2434,6 +2700,83 @@
 DRIVER_ATTR(vpd_use_hostno, S_IRUGO | S_IWUSR, sdebug_vpd_use_hostno_show,
 	    sdebug_vpd_use_hostno_store);
 
+static ssize_t sdebug_protection_show(struct device_driver * ddp, char * buf)
+{
+        return scnprintf(buf, PAGE_SIZE, "%d\n", scsi_debug_protection);
+}
+
+static ssize_t sdebug_protection_store(struct device_driver * ddp,
+				       const char * buf, size_t count)
+{
+        int n;
+	
+	if ((count > 0) && (1 == sscanf(buf, "%d", &n)) && (n >= 0)) {
+		switch (n) {
+		case 0: /* Type 0 Protection (None) */
+		case 1: /* Type 1 Protection (GRD, APP, REF) */
+		case 2: /* Type 2 Protection (READ32/WRITE32) */
+		case 3: /* Type 3 Protection (GRD only) */
+			scsi_debug_protection = n;
+			return count;
+		default:
+			break;
+		}
+	}
+	return -EINVAL;
+}
+DRIVER_ATTR(protection, S_IRUGO | S_IWUSR, sdebug_protection_show,
+	    sdebug_protection_store);
+
+static ssize_t sdebug_guard_show(struct device_driver * ddp, char * buf)
+{
+        return scnprintf(buf, PAGE_SIZE, "%u\n", scsi_debug_guard);
+}
+
+static ssize_t sdebug_guard_store(struct device_driver * ddp,
+				  const char * buf, size_t count)
+{
+        int n;
+	
+	if ((count > 0) && (1 == sscanf(buf, "%u", &n)) && (n >= 0)) {
+		switch (n) {
+		case 0: /* T10 DIF CRC */
+		case 1: /* IP checksum */
+			scsi_debug_protection = n;
+			return count;
+		default:
+			break;
+		}
+	}
+	return -EINVAL;
+}
+DRIVER_ATTR(guard, S_IRUGO | S_IWUSR, sdebug_guard_show,
+	    sdebug_guard_store);
+
+static ssize_t sdebug_ato_show(struct device_driver * ddp, char * buf)
+{
+        return scnprintf(buf, PAGE_SIZE, "%u\n", scsi_debug_ato);
+}
+
+static ssize_t sdebug_ato_store(struct device_driver * ddp,
+				const char * buf, size_t count)
+{
+        int n;
+	
+	if ((count > 0) && (1 == sscanf(buf, "%u", &n)) && (n >= 0)) {
+		switch (n) {
+		case 0: scsi_debug_ato = 0;
+			return count;
+		case 1: scsi_debug_ato = 1;
+			return count;
+		default:
+			break;
+		}
+	}
+	return -EINVAL;
+}
+DRIVER_ATTR(ato, S_IRUGO | S_IWUSR, sdebug_ato_show,
+	    sdebug_ato_store);
+
 /* Note: The following function creates attribute files in the
    /sys/bus/pseudo/drivers/scsi_debug directory. The advantage of these
    files (over those found in the /sys/module/scsi_debug/parameters
@@ -2499,6 +2842,8 @@
 	int k;
 	int ret;
 
+	ret = 0;
+
 	if (scsi_debug_dev_size_mb < 1)
 		scsi_debug_dev_size_mb = 1;  /* force minimum 1 MB ramdisk */
 	sz = (unsigned long)scsi_debug_dev_size_mb * 1048576;
@@ -2530,6 +2875,25 @@
 	memset(fake_storep, 0, sz);
 	if (scsi_debug_num_parts > 0)
 		sdebug_build_parts(fake_storep, sz);
+
+#ifdef CONFIG_SCSI_PROTECTION
+	if (scsi_debug_protection) {
+		int dif_size;
+
+		dif_size = sdebug_store_sectors * sizeof(struct scsi_dif_tuple);
+		dif_storep = vmalloc(dif_size);
+
+		printk(KERN_ERR "scsi_debug_init: dif_storep %u bytes @ %p\n", 
+		       dif_size, dif_storep);
+
+		if (dif_storep == NULL) {
+			printk(KERN_ERR "scsi_debug_init: out of mem. (DIF)\n");
+			goto free_vm;
+		}
+
+		memset(dif_storep, 0xff, dif_size);
+	}
+#endif
 
 	ret = device_register(&pseudo_primary);
 	if (ret < 0) {
@@ -2583,6 +2947,8 @@
 dev_unreg:
 	device_unregister(&pseudo_primary);
 free_vm:
+	if (dif_storep)
+		vfree(dif_storep);
 	vfree(fake_storep);
 
 	return ret;
@@ -2600,6 +2966,8 @@
 	bus_unregister(&pseudo_lld_bus);
 	device_unregister(&pseudo_primary);
 
+	if (dif_storep)
+		vfree(dif_storep);
 	vfree(fake_storep);
 }
 
@@ -2968,6 +3336,19 @@
 		hpnt->max_id = scsi_debug_num_tgts;
 	hpnt->max_lun = SAM2_WLUN_REPORT_LUNS;	/* = scsi_debug_max_luns; */
 
+#ifdef CONFIG_SCSI_PROTECTION
+	if (scsi_debug_protection) {
+		scsi_host_set_dif_caps(hpnt, 
+				       SHOST_DIF_TYPE1_PROTECTION
+				       |SHOST_DIF_PROTECTION_DMA);
+
+		if (scsi_debug_guard == 1)
+			scsi_host_set_guard_type(hpnt, SCSI_DIF_GUARD_IP);
+		else
+			scsi_host_set_guard_type(hpnt, SCSI_DIF_GUARD_CRC);
+	}
+#endif
+
         error = scsi_add_host(hpnt, &sdbg_host->dev);
         if (error) {
                 printk(KERN_ERR "%s: scsi_add_host failed\n", __FUNCTION__);
#14 lore raw

Re: [PATCH 11 of 16] Move scsi_disk() accessor function to sd.h

In reply to Martin K. Petersen
Quoted 3 lines · Martin K. Petersen
2 files changed, 5 insertions(+), 5 deletions(-)
drivers/scsi/sd.c |    5 -----
include/scsi/sd.h |    5 +++++

Nack. Creating of sd.h was a big mistake already and should be reverse. No one but sd.c has any business playing with these.

#15 lore raw

Re: [PATCH 11 of 16] Move scsi_disk() accessor function to sd.h

Quoted 1 line
Quoted 1 line
Quoted 1 line
Quoted 1 line
Quoted 1 line

"Christoph" == Christoph Hellwig <[email protected]> writes:

Christoph> Nack. Creating of sd.h was a big mistake already and Christoph> should be reverse. No one but sd.c has any business Christoph> playing with these.

DIF is part of SBC hence scsi_dif.c is a logical subset of sd.c.

I could roll scsi_dif.c into sd.c but I don't necessarily think that's prettier.

Maybe I should rename scsi_dif.c to sd_dif.c?

An alternative to exporting scsi_disk() is to introduce a field in scsi_cmnd to let the HBA driver know how to handle the request. That's how I originally did it but didn't want to grow scsi_cmnd when I could avoid it. And it seemed DIF-specific.

However, given the impending "I-can't-believe-it's-not-DIF" extensions for SCSI tape maybe there's merit in having a generic routing field?

Signature · 1 line
Martin K. Petersen	Oracle Linux Engineering
#6 lore raw

Re: [PATCH 05 of 16] Block layer data integrity

In reply to Martin K. Petersen

block/blk-core.c line 1401

Quoted 9 lines · Martin K. Petersen [[email protected]] wrote:
block/blk-core.c +3 -0
 		 */
 		blk_partition_remap(bio);
 
+		if (bio_integrity_enabled(bio) && bio_integrity_prep(bio))
+			goto end_io;
+
 		if (old_sector != -1)
 			blk_add_trace_remap(q, bio, old_dev, bio->bi_sector,
 					    old_sector);

It is expected that the bio's data should NOT be changed until this I/O is sent out to the HBA for WRITES. How do you ensure that applications or file systems don't modify the data of a bio that is in progress?

--Malahal.

#7 lore raw

Re: [PATCH 05 of 16] Block layer data integrity

Quoted 1 line
Quoted 1 line
Quoted 1 line
Quoted 1 line
Quoted 1 line

"Malahal" == malahal <[email protected]> writes:

Quoted 7 lines · Malahal> Martin K. Petersen [[email protected]] wrote:
Quoted 7 lines

*/ blk_partition_remap(bio);

+ if (bio_integrity_enabled(bio) && bio_integrity_prep(bio)) + goto end_io;
+
if (old_sector != -1) blk_add_trace_remap(q, bio, old_dev, bio->bi_sector, old_sector);

Malahal> It is expected that the bio's data should NOT be changed Malahal> until this I/O is sent out to the HBA for WRITES. How do you Malahal> ensure that applications or file systems don't modify the Malahal> data of a bio that is in progress?

As I mentioned on dm-devel a few weeks ago it's a big problem for ext2 in particular due to lack of locking the page down while I/O is in flight. So ext2 fails spectacularly.

I have been unable to trip XFS and btrfs. I had one mismatch with ext3 but that might have been something else. In any case I have not been able to reproduce it. Even with artificially induced delays in queuecommand.

But generally, yes. It is up to the submitter to guarantee that the page isn't being modified during I/O. This requirement will trickle all the way to the top of the stack for things like direct I/O.

Signature · 1 line
Martin K. Petersen	Oracle Linux Engineering
#8 lore raw

Re: [PATCH 05 of 16] Block layer data integrity

Quoted 3 lines · Martin K. Petersen [[email protected]] wrote:

But generally, yes. It is up to the submitter to guarantee that the page isn't being modified during I/O. This requirement will trickle all the way to the top of the stack for things like direct I/O.

Understandable for direct I/O. How does an application using mmap for writing can control this? Does that mean mmap writes are not really supported?

--Malahal.

#9 lore raw

Re: [PATCH 05 of 16] Block layer data integrity

Quoted 1 line
Quoted 1 line
Quoted 1 line
Quoted 1 line
Quoted 1 line

"Malahal" == malahal <[email protected]> writes:

Malahal> Understandable for direct I/O. How does an application using Malahal> mmap for writing can control this?

There's no way to control it :/

Malahal> Does that mean mmap writes are not really supported?

For now, yes. Zach proposed unmapping the pages while I/O is in progress. I'll dig into that can of worms shortly...

Signature · 1 line
Martin K. Petersen	Oracle Linux Engineering