* [PATCH 1 of 6] scsi: Host protection capabilities
2008-07-10 2:48 [PATCH 0 of 6] SCSI Data Integrity Support Martin K. Petersen
@ 2008-07-10 2:48 ` Martin K. Petersen
2008-07-10 2:48 ` [PATCH 2 of 6] scsi: Command protection operation Martin K. Petersen
` (4 subsequent siblings)
5 siblings, 0 replies; 8+ messages in thread
From: Martin K. Petersen @ 2008-07-10 2:48 UTC (permalink / raw)
To: James.Bottomley, linux-scsi
Controllers that support protection information must indicate this to
the SCSI midlayer so that the ULD can prepare scsi_cmnds accordingly.
This patch implements a host mask and various types of protection:
- DIF Type 1-3 (between HBA and disk)
- DIX Type 0-3 (between OS and HBA)
The patch also allows the HBA to set the guard type to something
different than the T10-mandated CRC.
Signed-off-by: Martin K. Petersen <martin.petersen@oracle.com>
---
2 files changed, 88 insertions(+)
drivers/scsi/scsi_sysfs.c | 4 ++
include/scsi/scsi_host.h | 84 +++++++++++++++++++++++++++++++++++++++++++++
diff --git a/drivers/scsi/scsi_sysfs.c b/drivers/scsi/scsi_sysfs.c
--- a/drivers/scsi/scsi_sysfs.c
+++ b/drivers/scsi/scsi_sysfs.c
@@ -249,6 +249,8 @@ shost_rd_attr(can_queue, "%hd\n");
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(prot_capabilities, "%u\n");
+shost_rd_attr(prot_guard_type, "%hd\n");
shost_rd_attr2(proc_name, hostt->proc_name, "%s\n");
static struct attribute *scsi_sysfs_shost_attrs[] = {
@@ -263,6 +265,8 @@ static struct attribute *scsi_sysfs_shos
&dev_attr_hstate.attr,
&dev_attr_supported_mode.attr,
&dev_attr_active_mode.attr,
+ &dev_attr_prot_capabilities.attr,
+ &dev_attr_prot_guard_type.attr,
NULL
};
diff --git a/include/scsi/scsi_host.h b/include/scsi/scsi_host.h
--- a/include/scsi/scsi_host.h
+++ b/include/scsi/scsi_host.h
@@ -636,6 +636,10 @@ struct Scsi_Host {
*/
unsigned int max_host_blocked;
+ /* Protection Information */
+ unsigned int prot_capabilities;
+ unsigned char prot_guard_type;
+
/*
* q used for scsi_tgt msgs, async events or any other requests that
* need to be processed in userspace
@@ -756,6 +760,86 @@ extern void scsi_free_host_dev(struct sc
extern void scsi_free_host_dev(struct scsi_device *);
extern struct scsi_device *scsi_get_host_dev(struct Scsi_Host *);
+/*
+ * DIF defines the exchange of protection information between
+ * initiator and SBC block device.
+ *
+ * DIX defines the exchange of protection information between OS and
+ * initiator.
+ */
+enum scsi_host_prot_capabilities {
+ SHOST_DIF_TYPE1_PROTECTION = 1 << 0, /* T10 DIF Type 1 */
+ SHOST_DIF_TYPE2_PROTECTION = 1 << 1, /* T10 DIF Type 2 */
+ SHOST_DIF_TYPE3_PROTECTION = 1 << 2, /* T10 DIF Type 3 */
+
+ SHOST_DIX_TYPE0_PROTECTION = 1 << 3, /* DIX between OS and HBA only */
+ SHOST_DIX_TYPE1_PROTECTION = 1 << 4, /* DIX with DIF Type 1 */
+ SHOST_DIX_TYPE2_PROTECTION = 1 << 5, /* DIX with DIF Type 2 */
+ SHOST_DIX_TYPE3_PROTECTION = 1 << 6, /* DIX with DIF Type 3 */
+};
+
+/*
+ * SCSI hosts which support the Data Integrity Extensions must
+ * indicate their capabilities by setting the prot_capabilities using
+ * this call.
+ */
+static inline void scsi_host_set_prot(struct Scsi_Host *shost, unsigned int mask)
+{
+ shost->prot_capabilities = mask;
+}
+
+static inline unsigned int scsi_host_get_prot(struct Scsi_Host *shost)
+{
+ return shost->prot_capabilities;
+}
+
+static inline unsigned int scsi_host_dif_capable(struct Scsi_Host *shost, unsigned int target_type)
+{
+ switch (target_type) {
+ case 1: return shost->prot_capabilities & SHOST_DIF_TYPE1_PROTECTION;
+ case 2: return shost->prot_capabilities & SHOST_DIF_TYPE2_PROTECTION;
+ case 3: return shost->prot_capabilities & SHOST_DIF_TYPE3_PROTECTION;
+ }
+
+ return 0;
+}
+
+static inline unsigned int scsi_host_dix_capable(struct Scsi_Host *shost, unsigned int target_type)
+{
+ switch (target_type) {
+ case 0: return shost->prot_capabilities & SHOST_DIX_TYPE0_PROTECTION;
+ case 1: return shost->prot_capabilities & SHOST_DIX_TYPE1_PROTECTION;
+ case 2: return shost->prot_capabilities & SHOST_DIX_TYPE2_PROTECTION;
+ case 3: return shost->prot_capabilities & SHOST_DIX_TYPE3_PROTECTION;
+ }
+
+ return 0;
+}
+
+/*
+ * All DIX-capable initiators must support the T10-mandated CRC
+ * checksum. Controllers can optionally implement the IP checksum
+ * scheme which has much lower impact on system performance. Note
+ * that the main rationale for the checksum is to match integrity
+ * metadata with data. Detecting bit errors are a job for ECC memory
+ * and buses.
+ */
+
+enum scsi_host_guard_type {
+ SHOST_DIX_GUARD_CRC = 1 << 0,
+ SHOST_DIX_GUARD_IP = 1 << 1,
+};
+
+static inline void scsi_host_set_guard(struct Scsi_Host *shost, unsigned char type)
+{
+ shost->prot_guard_type = type;
+}
+
+static inline unsigned char scsi_host_get_guard(struct Scsi_Host *shost)
+{
+ return shost->prot_guard_type;
+}
+
/* legacy interfaces */
extern struct Scsi_Host *scsi_register(struct scsi_host_template *, int);
extern void scsi_unregister(struct Scsi_Host *);
^ permalink raw reply [flat|nested] 8+ messages in thread* [PATCH 2 of 6] scsi: Command protection operation
2008-07-10 2:48 [PATCH 0 of 6] SCSI Data Integrity Support Martin K. Petersen
2008-07-10 2:48 ` [PATCH 1 of 6] scsi: Host protection capabilities Martin K. Petersen
@ 2008-07-10 2:48 ` Martin K. Petersen
2008-07-10 2:48 ` [PATCH 3 of 6] scsi: Support devices with protection information Martin K. Petersen
` (3 subsequent siblings)
5 siblings, 0 replies; 8+ messages in thread
From: Martin K. Petersen @ 2008-07-10 2:48 UTC (permalink / raw)
To: James.Bottomley, linux-scsi
Controllers that support DMA of protection information must be told
explicitly how to handle the I/O. The controller has no knowledge of
the protection capabilities of the target device so this information
must be passed in the scsi_cmnd.
The protection operation tells the HBA whether to generate, strip or
verify protection information.
When a scsi_cmnd is reused for error handling, the protection
operation must be cleared and saved while error handling is in
progress.
Signed-off-by: Martin K. Petersen <martin.petersen@oracle.com>
---
3 files changed, 32 insertions(+)
drivers/scsi/scsi_error.c | 3 +++
include/scsi/scsi_cmnd.h | 28 ++++++++++++++++++++++++++++
include/scsi/scsi_eh.h | 1 +
diff --git a/drivers/scsi/scsi_error.c b/drivers/scsi/scsi_error.c
--- a/drivers/scsi/scsi_error.c
+++ b/drivers/scsi/scsi_error.c
@@ -653,7 +653,9 @@ void scsi_eh_prep_cmnd(struct scsi_cmnd
ses->sdb = scmd->sdb;
ses->next_rq = scmd->request->next_rq;
ses->result = scmd->result;
+ ses->prot_op = scmd->prot_op;
+ scmd->prot_op = SCSI_PROT_NORMAL;
scmd->cmnd = ses->eh_cmnd;
memset(scmd->cmnd, 0, BLK_MAX_CDB);
memset(&scmd->sdb, 0, sizeof(scmd->sdb));
@@ -711,6 +713,7 @@ void scsi_eh_restore_cmnd(struct scsi_cm
scmd->sdb = ses->sdb;
scmd->request->next_rq = ses->next_rq;
scmd->result = ses->result;
+ scmd->prot_op = ses->prot_op;
}
EXPORT_SYMBOL(scsi_eh_restore_cmnd);
diff --git a/include/scsi/scsi_cmnd.h b/include/scsi/scsi_cmnd.h
--- a/include/scsi/scsi_cmnd.h
+++ b/include/scsi/scsi_cmnd.h
@@ -77,6 +77,8 @@ struct scsi_cmnd {
int retries;
int allowed;
int timeout_per_command;
+
+ unsigned char prot_op;
unsigned short cmd_len;
enum dma_data_direction sc_data_direction;
@@ -209,4 +211,30 @@ static inline int scsi_sg_copy_to_buffer
buf, buflen);
}
+/*
+ * The operations below are hints that tell the controller driver how
+ * to handle I/Os with DIF or similar types of protection information.
+ */
+
+enum scsi_prot_operations {
+ /* Normal I/O */
+ SCSI_PROT_NORMAL = 0,
+
+ /* OS-HBA: Protected, HBA-Target: Unprotected */
+ SCSI_PROT_READ_INSERT,
+ SCSI_PROT_WRITE_STRIP,
+
+ /* OS-HBA: Unprotected, HBA-Target: Protected */
+ SCSI_PROT_READ_STRIP,
+ SCSI_PROT_WRITE_INSERT,
+
+ /* OS-HBA: Protected, HBA-Target: Protected */
+ SCSI_PROT_READ_PASS,
+ SCSI_PROT_WRITE_PASS,
+
+ /* OS-HBA: Protected, HBA-Target: Protected, checksum conversion */
+ SCSI_PROT_READ_CONVERT,
+ SCSI_PROT_WRITE_CONVERT,
+};
+
#endif /* _SCSI_SCSI_CMND_H */
diff --git a/include/scsi/scsi_eh.h b/include/scsi/scsi_eh.h
--- a/include/scsi/scsi_eh.h
+++ b/include/scsi/scsi_eh.h
@@ -75,6 +75,7 @@ struct scsi_eh_save {
int result;
enum dma_data_direction data_direction;
unsigned char cmd_len;
+ unsigned char prot_op;
unsigned char *cmnd;
struct scsi_data_buffer sdb;
struct request *next_rq;
^ permalink raw reply [flat|nested] 8+ messages in thread* [PATCH 3 of 6] scsi: Support devices with protection information
2008-07-10 2:48 [PATCH 0 of 6] SCSI Data Integrity Support Martin K. Petersen
2008-07-10 2:48 ` [PATCH 1 of 6] scsi: Host protection capabilities Martin K. Petersen
2008-07-10 2:48 ` [PATCH 2 of 6] scsi: Command protection operation Martin K. Petersen
@ 2008-07-10 2:48 ` Martin K. Petersen
2008-07-10 2:48 ` [PATCH 4 of 6] scsi: Do not retry a request whose data integrity check failed Martin K. Petersen
` (2 subsequent siblings)
5 siblings, 0 replies; 8+ messages in thread
From: Martin K. Petersen @ 2008-07-10 2:48 UTC (permalink / raw)
To: James.Bottomley, linux-scsi
Implement support for DMA of protection information for devices that
are data integrity capable.
- Add support for mapping an extra scatter-gather list containing
the protection information.
- Handle protection scsi_data_buffer in command allocation.
- Preallocate protection scsi_data_buffer in command freelist if host
is DIX (integrity DMA) capable.
- Accessor function for checking whether a device has protection
enabled.
Signed-off-by: Martin K. Petersen <martin.petersen@oracle.com>
---
7 files changed, 97 insertions(+), 11 deletions(-)
drivers/scsi/scsi.c | 47 ++++++++++++++++++++++++++++++++++++++-----
drivers/scsi/scsi_error.c | 2 -
drivers/scsi/scsi_lib.c | 27 ++++++++++++++++++++++--
drivers/scsi/scsi_priv.h | 1
drivers/scsi/scsi_tgt_lib.c | 2 -
include/scsi/scsi_cmnd.h | 24 ++++++++++++++++++++-
include/scsi/scsi_device.h | 5 ++++
diff --git a/drivers/scsi/scsi.c b/drivers/scsi/scsi.c
--- a/drivers/scsi/scsi.c
+++ b/drivers/scsi/scsi.c
@@ -199,24 +199,60 @@ scsi_pool_free_command(struct scsi_host_
{
kmem_cache_free(pool->sense_slab, cmd->sense_buffer);
kmem_cache_free(pool->cmd_slab, cmd);
+
+ if (cmd->prot_sdb)
+ kmem_cache_free(scsi_sdb_cache, cmd->prot_sdb);
+}
+
+/**
+ * scsi_host_alloc_command - internal function to allocate command
+ * @shost: SCSI host whose pool to allocate from
+ * @gfp_mask: mask for the allocation
+ *
+ * Returns a fully allocated command with sense buffer and protection
+ * data buffer (where applicable) or NULL on failure
+ */
+static struct scsi_cmnd *
+scsi_host_alloc_command(struct Scsi_Host *shost, gfp_t gfp_mask)
+{
+ struct scsi_cmnd *cmd;
+
+ cmd = scsi_pool_alloc_command(shost->cmd_pool, gfp_mask);
+ if (!cmd)
+ return NULL;
+
+ if (scsi_host_get_prot(shost) >= SHOST_DIX_TYPE0_PROTECTION) {
+ cmd->prot_sdb = kmem_cache_zalloc(scsi_sdb_cache, gfp_mask);
+
+ if (!cmd->prot_sdb) {
+ scsi_pool_free_command(shost->cmd_pool, cmd);
+ return NULL;
+ }
+ }
+
+ return cmd;
}
/**
* __scsi_get_command - Allocate a struct scsi_cmnd
* @shost: host to transmit command
* @gfp_mask: allocation mask
+ * @protection: allocate protection sdb
*
* Description: allocate a struct scsi_cmd from host's slab, recycling from the
* host's free_list if necessary.
*/
-struct scsi_cmnd *__scsi_get_command(struct Scsi_Host *shost, gfp_t gfp_mask)
+struct scsi_cmnd *__scsi_get_command(struct Scsi_Host *shost, gfp_t gfp_mask, int protection)
{
struct scsi_cmnd *cmd;
unsigned char *buf;
cmd = scsi_pool_alloc_command(shost->cmd_pool, gfp_mask);
- if (unlikely(!cmd)) {
+ if (protection)
+ cmd->prot_sdb = kmem_cache_zalloc(scsi_sdb_cache, gfp_mask);
+
+ if (unlikely(!cmd) || unlikely(protection && cmd->prot_sdb == NULL)) {
unsigned long flags;
spin_lock_irqsave(&shost->free_list_lock, flags);
@@ -242,10 +278,11 @@ EXPORT_SYMBOL_GPL(__scsi_get_command);
* scsi_get_command - Allocate and setup a scsi command block
* @dev: parent scsi device
* @gfp_mask: allocator flags
+ * @protection: allocate protection sdb
*
* Returns: The allocated scsi command structure.
*/
-struct scsi_cmnd *scsi_get_command(struct scsi_device *dev, gfp_t gfp_mask)
+struct scsi_cmnd *scsi_get_command(struct scsi_device *dev, gfp_t gfp_mask, int protection)
{
struct scsi_cmnd *cmd;
@@ -253,7 +290,7 @@ struct scsi_cmnd *scsi_get_command(struc
if (!get_device(&dev->sdev_gendev))
return NULL;
- cmd = __scsi_get_command(dev->host, gfp_mask);
+ cmd = __scsi_get_command(dev->host, gfp_mask, protection);
if (likely(cmd != NULL)) {
unsigned long flags;
@@ -457,7 +494,7 @@ int scsi_setup_command_freelist(struct S
/*
* Get one backup command for this host.
*/
- cmd = scsi_pool_alloc_command(shost->cmd_pool, gfp_mask);
+ cmd = scsi_host_alloc_command(shost, gfp_mask);
if (!cmd) {
scsi_put_host_cmd_pool(gfp_mask);
shost->cmd_pool = NULL;
diff --git a/drivers/scsi/scsi_error.c b/drivers/scsi/scsi_error.c
--- a/drivers/scsi/scsi_error.c
+++ b/drivers/scsi/scsi_error.c
@@ -1769,7 +1769,7 @@ int
int
scsi_reset_provider(struct scsi_device *dev, int flag)
{
- struct scsi_cmnd *scmd = scsi_get_command(dev, GFP_KERNEL);
+ struct scsi_cmnd *scmd = scsi_get_command(dev, GFP_KERNEL, 0);
struct Scsi_Host *shost = dev->host;
struct request req;
unsigned long flags;
diff --git a/drivers/scsi/scsi_lib.c b/drivers/scsi/scsi_lib.c
--- a/drivers/scsi/scsi_lib.c
+++ b/drivers/scsi/scsi_lib.c
@@ -65,7 +65,7 @@ static struct scsi_host_sg_pool scsi_sg_
};
#undef SP
-static struct kmem_cache *scsi_sdb_cache;
+struct kmem_cache *scsi_sdb_cache;
static void scsi_run_queue(struct request_queue *q);
@@ -778,6 +778,9 @@ void scsi_release_buffers(struct scsi_cm
kmem_cache_free(scsi_sdb_cache, bidi_sdb);
cmd->request->next_rq->special = NULL;
}
+
+ if (scsi_prot_sg_count(cmd))
+ scsi_free_sgtable(cmd->prot_sdb);
}
EXPORT_SYMBOL(scsi_release_buffers);
@@ -1063,6 +1066,26 @@ int scsi_init_io(struct scsi_cmnd *cmd,
goto err_exit;
}
+ if (blk_integrity_rq(cmd->request)) {
+ struct scsi_data_buffer *prot_sdb = cmd->prot_sdb;
+ int ivecs, count;
+
+ BUG_ON(prot_sdb == NULL);
+ ivecs = blk_rq_count_integrity_sg(cmd->request);
+
+ if (scsi_alloc_sgtable(prot_sdb, ivecs, gfp_mask)) {
+ error = BLKPREP_DEFER;
+ goto err_exit;
+ }
+
+ count = blk_rq_map_integrity_sg(cmd->request,
+ prot_sdb->table.sgl);
+ BUG_ON(unlikely(count > ivecs));
+
+ cmd->prot_sdb = prot_sdb;
+ cmd->prot_sdb->table.nents = count;
+ }
+
return BLKPREP_OK ;
err_exit:
@@ -1082,7 +1105,7 @@ static struct scsi_cmnd *scsi_get_cmd_fr
struct scsi_cmnd *cmd;
if (!req->special) {
- cmd = scsi_get_command(sdev, GFP_ATOMIC);
+ cmd = scsi_get_command(sdev, GFP_ATOMIC, blk_integrity_rq(req));
if (unlikely(!cmd))
return NULL;
req->special = cmd;
diff --git a/drivers/scsi/scsi_priv.h b/drivers/scsi/scsi_priv.h
--- a/drivers/scsi/scsi_priv.h
+++ b/drivers/scsi/scsi_priv.h
@@ -77,6 +77,7 @@ struct request_queue;
struct request_queue;
struct request;
extern int scsi_prep_fn(struct request_queue *, struct request *);
+extern struct kmem_cache *scsi_sdb_cache;
/* scsi_proc.c */
#ifdef CONFIG_SCSI_PROC_FS
diff --git a/drivers/scsi/scsi_tgt_lib.c b/drivers/scsi/scsi_tgt_lib.c
--- a/drivers/scsi/scsi_tgt_lib.c
+++ b/drivers/scsi/scsi_tgt_lib.c
@@ -99,7 +99,7 @@ struct scsi_cmnd *scsi_host_get_command(
if (!rq)
goto free_tcmd;
- cmd = __scsi_get_command(shost, gfp_mask);
+ cmd = __scsi_get_command(shost, gfp_mask, 0);
if (!cmd)
goto release_rq;
diff --git a/include/scsi/scsi_cmnd.h b/include/scsi/scsi_cmnd.h
--- a/include/scsi/scsi_cmnd.h
+++ b/include/scsi/scsi_cmnd.h
@@ -90,6 +90,8 @@ struct scsi_cmnd {
/* These elements define the operation we ultimately want to perform */
struct scsi_data_buffer sdb;
+ struct scsi_data_buffer *prot_sdb;
+
unsigned underflow; /* Return error if less than
this amount is transferred */
@@ -131,8 +133,8 @@ struct scsi_cmnd {
unsigned char tag; /* SCSI-II queued command tag */
};
-extern struct scsi_cmnd *scsi_get_command(struct scsi_device *, gfp_t);
-extern struct scsi_cmnd *__scsi_get_command(struct Scsi_Host *, gfp_t);
+extern struct scsi_cmnd *scsi_get_command(struct scsi_device *, gfp_t, int);
+extern struct scsi_cmnd *__scsi_get_command(struct Scsi_Host *, gfp_t, int);
extern void scsi_put_command(struct scsi_cmnd *);
extern void __scsi_put_command(struct Scsi_Host *, struct scsi_cmnd *,
struct device *);
@@ -237,4 +239,22 @@ enum scsi_prot_operations {
SCSI_PROT_WRITE_CONVERT,
};
+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)
+
#endif /* _SCSI_SCSI_CMND_H */
diff --git a/include/scsi/scsi_device.h b/include/scsi/scsi_device.h
--- a/include/scsi/scsi_device.h
+++ b/include/scsi/scsi_device.h
@@ -393,6 +393,11 @@ static inline int scsi_device_enclosure(
return sdev->inquiry[6] & (1<<6);
}
+static inline int scsi_device_protection(struct scsi_device *sdev)
+{
+ return sdev->inquiry[5] & (1<<0);
+}
+
#define MODULE_ALIAS_SCSI_DEVICE(type) \
MODULE_ALIAS("scsi:t-" __stringify(type) "*")
#define SCSI_DEVICE_MODALIAS_FMT "scsi:t-0x%02x"
^ permalink raw reply [flat|nested] 8+ messages in thread* [PATCH 4 of 6] scsi: Do not retry a request whose data integrity check failed
2008-07-10 2:48 [PATCH 0 of 6] SCSI Data Integrity Support Martin K. Petersen
` (2 preceding siblings ...)
2008-07-10 2:48 ` [PATCH 3 of 6] scsi: Support devices with protection information Martin K. Petersen
@ 2008-07-10 2:48 ` Martin K. Petersen
2008-07-10 2:48 ` [PATCH 5 of 6] sd: Identify DIF protection type and application tag ownership Martin K. Petersen
2008-07-10 2:48 ` [PATCH 6 of 6] sd: Support for SCSI disk (SBC) Data Integrity Field Martin K. Petersen
5 siblings, 0 replies; 8+ messages in thread
From: Martin K. Petersen @ 2008-07-10 2:48 UTC (permalink / raw)
To: James.Bottomley, linux-scsi
If initiator or target reject the I/O due to DIF errors there is no
point in retrying.
Signed-off-by: Martin K. Petersen <martin.petersen@oracle.com>
---
2 files changed, 10 insertions(+), 4 deletions(-)
drivers/scsi/scsi_error.c | 3 +++
drivers/scsi/scsi_lib.c | 11 +++++++----
diff --git a/drivers/scsi/scsi_error.c b/drivers/scsi/scsi_error.c
--- a/drivers/scsi/scsi_error.c
+++ b/drivers/scsi/scsi_error.c
@@ -333,6 +333,9 @@ static int scsi_check_sense(struct scsi_
return /* soft_error */ SUCCESS;
case ABORTED_COMMAND:
+ if (sshdr.asc == 0x10) /* DIF */
+ return SUCCESS;
+
return NEEDS_RETRY;
case NOT_READY:
case UNIT_ATTENTION:
diff --git a/drivers/scsi/scsi_lib.c b/drivers/scsi/scsi_lib.c
--- a/drivers/scsi/scsi_lib.c
+++ b/drivers/scsi/scsi_lib.c
@@ -941,11 +941,14 @@ void scsi_io_completion(struct scsi_cmnd
* 6-byte command.
*/
scsi_requeue_command(q, cmd);
- return;
- } else {
+ } else if (sshdr.asc == 0x10) /* DIX */
+ scsi_end_request(cmd, -EIO, good_bytes, 0);
+ else
scsi_end_request(cmd, -EIO, this_count, 1);
- return;
- }
+ return;
+ case ABORTED_COMMAND:
+ if (sshdr.asc == 0x10) /* DIF */
+ scsi_end_request(cmd, -EIO, good_bytes, 0);
break;
case NOT_READY:
/* If the device is in the process of becoming
^ permalink raw reply [flat|nested] 8+ messages in thread* [PATCH 5 of 6] sd: Identify DIF protection type and application tag ownership
2008-07-10 2:48 [PATCH 0 of 6] SCSI Data Integrity Support Martin K. Petersen
` (3 preceding siblings ...)
2008-07-10 2:48 ` [PATCH 4 of 6] scsi: Do not retry a request whose data integrity check failed Martin K. Petersen
@ 2008-07-10 2:48 ` Martin K. Petersen
2008-07-10 2:48 ` [PATCH 6 of 6] sd: Support for SCSI disk (SBC) Data Integrity Field Martin K. Petersen
5 siblings, 0 replies; 8+ messages in thread
From: Martin K. Petersen @ 2008-07-10 2:48 UTC (permalink / raw)
To: James.Bottomley, linux-scsi
If a disk is formatted with protection information (Inquiry bit
PROTECT=1) it is required to support Read Capacity(16). Force use of
the 16-bit command in this case and extract the P_TYPE field which
indicates whether the disk is formatted using DIF Type 1, 2 or 3.
The ATO (App Tag Own) bit in the Control Mode Page indicates whether
the storage device or the initiator own the contents of the
DIF application tag.
Signed-off-by: Martin K. Petersen <martin.petersen@oracle.com>
---
2 files changed, 141 insertions(+), 4 deletions(-)
drivers/scsi/sd.c | 122 +++++++++++++++++++++++++++++++++++++++++++++++++++--
drivers/scsi/sd.h | 23 +++++++++
diff --git a/drivers/scsi/sd.c b/drivers/scsi/sd.c
--- a/drivers/scsi/sd.c
+++ b/drivers/scsi/sd.c
@@ -234,6 +234,24 @@ sd_show_allow_restart(struct device *dev
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),
@@ -242,6 +260,8 @@ static struct device_attribute sd_disk_a
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,
};
@@ -1163,6 +1183,49 @@ sd_spinup_disk(struct scsi_disk *sdkp)
}
}
+
+/*
+ * Determine whether disk supports Data Integrity Field.
+ */
+void sd_read_protection_type(struct scsi_disk *sdkp, unsigned char *buffer)
+{
+ struct scsi_device *sdp = sdkp->device;
+ u8 type;
+
+ if (scsi_device_protection(sdp) == 0 || (buffer[12] & 1) == 0)
+ type = 0;
+ else
+ type = ((buffer[12] >> 1) & 7) + 1; /* P_TYPE 0 = Type 1 */
+
+ switch (type) {
+ case SD_DIF_TYPE0_PROTECTION:
+ sdkp->protection_type = 0;
+ break;
+
+ case SD_DIF_TYPE1_PROTECTION:
+ case SD_DIF_TYPE3_PROTECTION:
+ sdkp->protection_type = type;
+ break;
+
+ case SD_DIF_TYPE2_PROTECTION:
+ sd_printk(KERN_ERR, sdkp, "formatted with DIF Type 2 " \
+ "protection which is currently unsupported. " \
+ "Disabling disk!\n");
+ goto disable;
+
+ default:
+ sd_printk(KERN_ERR, sdkp, "formatted with unknown " \
+ "protection type %d. Disabling disk!\n", type);
+ goto disable;
+ }
+
+ return;
+
+disable:
+ sdkp->protection_type = 0;
+ sdkp->capacity = 0;
+}
+
/*
* read disk capacity
*/
@@ -1172,7 +1235,8 @@ sd_read_capacity(struct scsi_disk *sdkp,
unsigned char cmd[16];
int the_result, retries;
int sector_size = 0;
- int longrc = 0;
+ /* Force READ CAPACITY(16) when PROTECT=1 */
+ int longrc = scsi_device_protection(sdkp->device) ? 1 : 0;
struct scsi_sense_hdr sshdr;
int sense_valid = 0;
struct scsi_device *sdp = sdkp->device;
@@ -1184,8 +1248,8 @@ repeat:
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 +1257,7 @@ repeat:
}
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 +1332,8 @@ repeat:
sector_size = (buffer[8] << 24) |
(buffer[9] << 16) | (buffer[10] << 8) | buffer[11];
+
+ sd_read_protection_type(sdkp, buffer);
}
/* Some devices return the total number of sectors, not the
@@ -1529,6 +1595,52 @@ defaults:
sdkp->DPOFUA = 0;
}
+/*
+ * The ATO bit indicates whether the DIF application tag is available
+ * for use by the operating system.
+ */
+void sd_read_app_tag_own(struct scsi_disk *sdkp, unsigned char *buffer)
+{
+ int res, offset;
+ struct scsi_device *sdp = sdkp->device;
+ struct scsi_mode_data data;
+ struct scsi_sense_hdr sshdr;
+
+ if (sdp->type != TYPE_DISK)
+ return;
+
+ if (sdkp->protection_type == 0)
+ return;
+
+ res = scsi_mode_sense(sdp, 1, 0x0a, buffer, 36, SD_TIMEOUT,
+ SD_MAX_RETRIES, &data, &sshdr);
+
+ if (!scsi_status_is_good(res) || !data.header_length ||
+ data.length < 6) {
+ sd_printk(KERN_WARNING, sdkp,
+ "getting Control mode page failed, assume no ATO\n");
+
+ if (scsi_sense_valid(&sshdr))
+ sd_print_sense_hdr(sdkp, &sshdr);
+
+ return;
+ }
+
+ offset = data.header_length + data.block_descriptor_length;
+
+ if ((buffer[offset] & 0x3f) != 0x0a) {
+ sd_printk(KERN_ERR, sdkp, "ATO Got wrong page\n");
+ return;
+ }
+
+ if ((buffer[offset + 5] & 0x80) == 0)
+ return;
+
+ sdkp->ATO = 1;
+
+ return;
+}
+
/**
* sd_revalidate_disk - called the first time a new disk is seen,
* performs disk spin up, read_capacity, etc.
@@ -1565,6 +1677,7 @@ static int sd_revalidate_disk(struct gen
sdkp->write_prot = 0;
sdkp->WCE = 0;
sdkp->RCD = 0;
+ sdkp->ATO = 0;
sd_spinup_disk(sdkp);
@@ -1576,6 +1689,7 @@ static int sd_revalidate_disk(struct gen
sd_read_capacity(sdkp, buffer);
sd_read_write_protect_flag(sdkp, buffer);
sd_read_cache_type(sdkp, buffer);
+ sd_read_app_tag_own(sdkp, buffer);
}
/*
diff --git a/drivers/scsi/sd.h b/drivers/scsi/sd.h
--- a/drivers/scsi/sd.h
+++ b/drivers/scsi/sd.h
@@ -41,7 +41,9 @@ struct scsi_disk {
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 */
@@ -59,4 +61,25 @@ static inline struct scsi_disk *scsi_dis
(sdsk)->disk->disk_name, ##a) : \
sdev_printk(prefix, (sdsk)->device, fmt, ##a)
+/*
+ * A DIF-capable target device can be formatted with different
+ * protection schemes. Currently 0 through 3 are defined:
+ *
+ * Type 0 is regular (unprotected) I/O
+ *
+ * Type 1 defines the contents of the guard and reference tags
+ *
+ * Type 2 defines the contents of the guard and reference tags and
+ * uses 32-byte commands to seed the latter
+ *
+ * Type 3 defines the contents of the guard tag only
+ */
+
+enum sd_dif_target_protection_types {
+ SD_DIF_TYPE0_PROTECTION = 0x0,
+ SD_DIF_TYPE1_PROTECTION = 0x1,
+ SD_DIF_TYPE2_PROTECTION = 0x2,
+ SD_DIF_TYPE3_PROTECTION = 0x3,
+};
+
#endif /* _SCSI_DISK_H */
^ permalink raw reply [flat|nested] 8+ messages in thread* [PATCH 6 of 6] sd: Support for SCSI disk (SBC) Data Integrity Field
2008-07-10 2:48 [PATCH 0 of 6] SCSI Data Integrity Support Martin K. Petersen
` (4 preceding siblings ...)
2008-07-10 2:48 ` [PATCH 5 of 6] sd: Identify DIF protection type and application tag ownership Martin K. Petersen
@ 2008-07-10 2:48 ` Martin K. Petersen
5 siblings, 0 replies; 8+ messages in thread
From: Martin K. Petersen @ 2008-07-10 2:48 UTC (permalink / raw)
To: James.Bottomley, linux-scsi
Support for controllers and disks that implement DIF protection
information:
- During command preparation the RDPROTECT/WRPROTECT must be set
correctly if the target has DIF enabled.
- READ(6) and WRITE(6) are not supported when DIF is on.
- The controller must be told how to handle the I/O via the
protection operation field in scsi_cmnd.
- sd_dif.c implements the functions required to prepare and complete
requests with protection information attached.
- Refactor the I/O completion code that extracts the failed LBA.
Signed-off-by: Martin K. Petersen <martin.petersen@oracle.com>
---
5 files changed, 642 insertions(+), 40 deletions(-)
drivers/scsi/Kconfig | 1
drivers/scsi/Makefile | 2
drivers/scsi/sd.c | 121 +++++++----
drivers/scsi/sd.h | 25 ++
drivers/scsi/sd_dif.c | 533 +++++++++++++++++++++++++++++++++++++++++++++++++
diff --git a/drivers/scsi/Kconfig b/drivers/scsi/Kconfig
--- a/drivers/scsi/Kconfig
+++ b/drivers/scsi/Kconfig
@@ -63,6 +63,7 @@ config BLK_DEV_SD
config BLK_DEV_SD
tristate "SCSI disk support"
depends on SCSI
+ select CRC_T10DIF
---help---
If you want to use SCSI hard disks, Fibre Channel disks,
Serial ATA (SATA) or Parallel ATA (PATA) hard disks,
diff --git a/drivers/scsi/Makefile b/drivers/scsi/Makefile
--- a/drivers/scsi/Makefile
+++ b/drivers/scsi/Makefile
@@ -149,6 +149,8 @@ scsi_tgt-y += scsi_tgt_lib.o scsi_tgt_
scsi_tgt-y += scsi_tgt_lib.o scsi_tgt_if.o
sd_mod-objs := sd.o
+sd_mod-$(CONFIG_BLK_DEV_INTEGRITY) += sd_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 \
diff --git a/drivers/scsi/sd.c b/drivers/scsi/sd.c
--- a/drivers/scsi/sd.c
+++ b/drivers/scsi/sd.c
@@ -374,6 +374,7 @@ static int sd_prep_fn(struct request_que
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;
@@ -390,6 +391,7 @@ static int sd_prep_fn(struct request_que
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 */
@@ -479,6 +481,11 @@ static int sd_prep_fn(struct request_que
}
SCpnt->cmnd[0] = WRITE_6;
SCpnt->sc_data_direction = DMA_TO_DEVICE;
+
+ if (blk_integrity_rq(rq) &&
+ sd_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;
@@ -493,8 +500,12 @@ static int sd_prep_fn(struct request_que
"writing" : "reading", this_count,
rq->nr_sectors));
- SCpnt->cmnd[1] = 0;
-
+ /* Set RDPROTECT/WRPROTECT if disk is formatted with DIF */
+ if (scsi_host_dif_capable(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;
@@ -512,6 +523,7 @@ static int sd_prep_fn(struct request_que
SCpnt->cmnd[13] = (unsigned char) this_count & 0xff;
SCpnt->cmnd[14] = SCpnt->cmnd[15] = 0;
} else if ((this_count > 0xff) || (block > 0x1fffff) ||
+ scsi_device_protection(SCpnt->device) ||
SCpnt->device->use_10_for_rw) {
if (this_count > 0xffff)
this_count = 0xffff;
@@ -545,6 +557,10 @@ static int sd_prep_fn(struct request_que
SCpnt->cmnd[5] = 0;
}
SCpnt->sdb.length = this_count * sdp->sector_size;
+
+ /* If DIF or DIX is enabled, tell HBA how to handle request */
+ if (sdkp->protection_type || scsi_prot_sg_count(SCpnt))
+ sd_dif_op(SCpnt, sdkp->protection_type, scsi_prot_sg_count(SCpnt));
/*
* We shouldn't disconnect in the middle of a sector, so with a dumb
@@ -940,6 +956,48 @@ static struct block_device_operations sd
.revalidate_disk = sd_revalidate_disk,
};
+static unsigned int sd_completed_bytes(struct scsi_cmnd *scmd)
+{
+ u64 start_lba = scmd->request->sector;
+ u64 end_lba = scmd->request->sector + (scsi_bufflen(scmd) / 512);
+ u64 bad_lba;
+ int info_valid;
+
+ if (!blk_fs_request(scmd->request))
+ return 0;
+
+ info_valid = scsi_get_sense_info_fld(scmd->sense_buffer,
+ SCSI_SENSE_BUFFERSIZE,
+ &bad_lba);
+ if (!info_valid)
+ return 0;
+
+ if (scsi_bufflen(scmd) <= scmd->device->sector_size)
+ return 0;
+
+ if (scmd->device->sector_size < 512) {
+ /* only legitimate sector_size here is 256 */
+ start_lba <<= 1;
+ end_lba <<= 1;
+ } else {
+ /* be careful ... don't want any overflows */
+ u64 factor = scmd->device->sector_size / 512;
+ do_div(start_lba, factor);
+ do_div(end_lba, factor);
+ }
+
+ /* The bad lba was reported incorrectly, we have no idea where
+ * the error is.
+ */
+ if (bad_lba < start_lba || bad_lba >= end_lba)
+ return 0;
+
+ /* This computation should always be done in terms of
+ * the resolution of the device's medium.
+ */
+ return (bad_lba - start_lba) * scmd->device->sector_size;
+}
+
/**
* sd_done - bottom half handler: called when the lower level
* driver has completed (successfully or otherwise) a scsi command.
@@ -950,15 +1008,10 @@ static int sd_done(struct scsi_cmnd *SCp
static int sd_done(struct scsi_cmnd *SCpnt)
{
int result = SCpnt->result;
- unsigned int xfer_size = scsi_bufflen(SCpnt);
- unsigned int good_bytes = result ? 0 : xfer_size;
- u64 start_lba = SCpnt->request->sector;
- u64 end_lba = SCpnt->request->sector + (xfer_size / 512);
- u64 bad_lba;
+ unsigned int good_bytes = result ? 0 : scsi_bufflen(SCpnt);
struct scsi_sense_hdr sshdr;
int sense_valid = 0;
int sense_deferred = 0;
- int info_valid;
if (result) {
sense_valid = scsi_command_normalize_sense(SCpnt, &sshdr);
@@ -983,36 +1036,7 @@ static int sd_done(struct scsi_cmnd *SCp
switch (sshdr.sense_key) {
case HARDWARE_ERROR:
case MEDIUM_ERROR:
- if (!blk_fs_request(SCpnt->request))
- goto out;
- info_valid = scsi_get_sense_info_fld(SCpnt->sense_buffer,
- SCSI_SENSE_BUFFERSIZE,
- &bad_lba);
- if (!info_valid)
- goto out;
- if (xfer_size <= SCpnt->device->sector_size)
- goto out;
- if (SCpnt->device->sector_size < 512) {
- /* only legitimate sector_size here is 256 */
- start_lba <<= 1;
- end_lba <<= 1;
- } else {
- /* be careful ... don't want any overflows */
- u64 factor = SCpnt->device->sector_size / 512;
- do_div(start_lba, factor);
- do_div(end_lba, factor);
- }
-
- if (bad_lba < start_lba || bad_lba >= end_lba)
- /* the bad lba was reported incorrectly, we have
- * no idea where the error is
- */
- goto out;
-
- /* This computation should always be done in terms of
- * the resolution of the device's medium.
- */
- good_bytes = (bad_lba - start_lba)*SCpnt->device->sector_size;
+ good_bytes = sd_completed_bytes(SCpnt);
break;
case RECOVERED_ERROR:
case NO_SENSE:
@@ -1022,10 +1046,23 @@ static int sd_done(struct scsi_cmnd *SCp
scsi_print_sense("sd", SCpnt);
SCpnt->result = 0;
memset(SCpnt->sense_buffer, 0, SCSI_SENSE_BUFFERSIZE);
- good_bytes = xfer_size;
+ good_bytes = scsi_bufflen(SCpnt);
+ break;
+ case ABORTED_COMMAND:
+ if (sshdr.asc == 0x10) { /* DIF: Disk detected corruption */
+ scsi_print_result(SCpnt);
+ scsi_print_sense("sd", SCpnt);
+ good_bytes = sd_completed_bytes(SCpnt);
+ }
break;
case ILLEGAL_REQUEST:
- if (SCpnt->device->use_10_for_rw &&
+ if (sshdr.asc == 0x10) { /* DIX: HBA detected corruption */
+ scsi_print_result(SCpnt);
+ scsi_print_sense("sd", SCpnt);
+ return 0;
+ }
+ if (!scsi_device_protection(SCpnt->device) &&
+ SCpnt->device->use_10_for_rw &&
(SCpnt->cmnd[0] == READ_10 ||
SCpnt->cmnd[0] == WRITE_10))
SCpnt->device->use_10_for_rw = 0;
@@ -1038,6 +1075,9 @@ static int sd_done(struct scsi_cmnd *SCp
break;
}
out:
+ if (rq_data_dir(SCpnt->request) == READ && scsi_prot_sg_count(SCpnt))
+ sd_dif_complete(SCpnt, good_bytes);
+
return good_bytes;
}
@@ -1823,6 +1863,7 @@ static int sd_probe(struct device *dev)
dev_set_drvdata(dev, sdkp);
add_disk(gd);
+ sd_dif_config_host(sdkp);
sd_printk(KERN_NOTICE, sdkp, "Attached SCSI %sdisk\n",
sdp->removable ? "removable " : "");
diff --git a/drivers/scsi/sd.h b/drivers/scsi/sd.h
--- a/drivers/scsi/sd.h
+++ b/drivers/scsi/sd.h
@@ -82,4 +82,29 @@ enum sd_dif_target_protection_types {
SD_DIF_TYPE3_PROTECTION = 0x3,
};
+/*
+ * Data Integrity Field tuple.
+ */
+struct sd_dif_tuple {
+ __be16 guard_tag; /* Checksum */
+ __be16 app_tag; /* Opaque storage */
+ __be32 ref_tag; /* Target LBA or indirect LBA */
+};
+
+#if defined(CONFIG_BLK_DEV_INTEGRITY)
+
+extern void sd_dif_op(struct scsi_cmnd *, unsigned int, unsigned int);
+extern void sd_dif_config_host(struct scsi_disk *);
+extern int sd_dif_prepare(struct request *rq, sector_t, unsigned int);
+extern void sd_dif_complete(struct scsi_cmnd *, unsigned int);
+
+#else /* CONFIG_BLK_DEV_INTEGRITY */
+
+#define sd_dif_op(a, b, c) do { } while (0)
+#define sd_dif_config_host(a) do { } while (0)
+#define sd_dif_prepare(a, b, c) (0)
+#define sd_dif_complete(a, b) (0)
+
+#endif /* CONFIG_BLK_DEV_INTEGRITY */
+
#endif /* _SCSI_DISK_H */
diff --git a/drivers/scsi/sd_dif.c b/drivers/scsi/sd_dif.c
new file mode 100644
--- /dev/null
+++ b/drivers/scsi/sd_dif.c
@@ -0,0 +1,533 @@
+/*
+ * sd_dif.c - SCSI Data Integrity Field
+ *
+ * Copyright (C) 2007, 2008 Oracle Corporation
+ * Written by: Martin K. Petersen <martin.petersen@oracle.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License version
+ * 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; see the file COPYING. If not, write to
+ * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139,
+ * USA.
+ *
+ */
+
+#include <linux/blkdev.h>
+#include <linux/crc-t10dif.h>
+
+#include <scsi/scsi.h>
+#include <scsi/scsi_cmnd.h>
+#include <scsi/scsi_dbg.h>
+#include <scsi/scsi_device.h>
+#include <scsi/scsi_driver.h>
+#include <scsi/scsi_eh.h>
+#include <scsi/scsi_host.h>
+#include <scsi/scsi_ioctl.h>
+#include <scsi/scsicam.h>
+
+#include <net/checksum.h>
+
+#include "sd.h"
+
+typedef __u16 (csum_fn) (void *, unsigned int);
+
+static __u16 sd_dif_crc_fn(void *data, unsigned int len)
+{
+ return cpu_to_be16(crc_t10dif(data, len));
+}
+
+static __u16 sd_dif_ip_fn(void *data, unsigned int len)
+{
+ return ip_compute_csum(data, len);
+}
+
+/*
+ * Type 1 and Type 2 protection use the same format: 16 bit guard tag,
+ * 16 bit app tag, 32 bit reference tag.
+ */
+static void sd_dif_type1_generate(struct blk_integrity_exchg *bix, csum_fn *fn)
+{
+ void *buf = bix->data_buf;
+ struct sd_dif_tuple *sdt = bix->prot_buf;
+ sector_t sector = bix->sector;
+ unsigned int i;
+
+ for (i = 0 ; i < bix->data_size ; i += bix->sector_size, sdt++) {
+ sdt->guard_tag = fn(buf, bix->sector_size);
+ sdt->ref_tag = cpu_to_be32(sector & 0xffffffff);
+ sdt->app_tag = 0;
+
+ buf += bix->sector_size;
+ sector++;
+ }
+}
+
+static void sd_dif_type1_generate_crc(struct blk_integrity_exchg *bix)
+{
+ sd_dif_type1_generate(bix, sd_dif_crc_fn);
+}
+
+static void sd_dif_type1_generate_ip(struct blk_integrity_exchg *bix)
+{
+ sd_dif_type1_generate(bix, sd_dif_ip_fn);
+}
+
+static int sd_dif_type1_verify(struct blk_integrity_exchg *bix, csum_fn *fn)
+{
+ void *buf = bix->data_buf;
+ struct sd_dif_tuple *sdt = bix->prot_buf;
+ sector_t sector = bix->sector;
+ unsigned int i;
+ __u16 csum;
+
+ for (i = 0 ; i < bix->data_size ; i += bix->sector_size, sdt++) {
+ /* Unwritten sectors */
+ if (sdt->app_tag == 0xffff)
+ return 0;
+
+ /* Bad ref tag received from disk */
+ if (sdt->ref_tag == 0xffffffff) {
+ printk(KERN_ERR
+ "%s: bad phys ref tag on sector %lu\n",
+ bix->disk_name, (unsigned long)sector);
+ return -EIO;
+ }
+
+ if (be32_to_cpu(sdt->ref_tag) != (sector & 0xffffffff)) {
+ printk(KERN_ERR
+ "%s: ref tag error on sector %lu (rcvd %u)\n",
+ bix->disk_name, (unsigned long)sector,
+ be32_to_cpu(sdt->ref_tag));
+ return -EIO;
+ }
+
+ csum = fn(buf, bix->sector_size);
+
+ if (sdt->guard_tag != csum) {
+ printk(KERN_ERR "%s: guard tag error on sector %lu " \
+ "(rcvd %04x, data %04x)\n", bix->disk_name,
+ (unsigned long)sector,
+ be16_to_cpu(sdt->guard_tag), be16_to_cpu(csum));
+ return -EIO;
+ }
+
+ buf += bix->sector_size;
+ sector++;
+ }
+
+ return 0;
+}
+
+static int sd_dif_type1_verify_crc(struct blk_integrity_exchg *bix)
+{
+ return sd_dif_type1_verify(bix, sd_dif_crc_fn);
+}
+
+static int sd_dif_type1_verify_ip(struct blk_integrity_exchg *bix)
+{
+ return sd_dif_type1_verify(bix, sd_dif_ip_fn);
+}
+
+/*
+ * Functions for interleaving and deinterleaving application tags
+ */
+static void sd_dif_type1_set_tag(void *prot, void *tag_buf, unsigned int sectors)
+{
+ struct sd_dif_tuple *sdt = prot;
+ char *tag = tag_buf;
+ unsigned int i, j;
+
+ for (i = 0, j = 0 ; i < sectors ; i++, j += 2, sdt++) {
+ sdt->app_tag = tag[j] << 8 | tag[j+1];
+ BUG_ON(sdt->app_tag == 0xffff);
+ }
+}
+
+static void sd_dif_type1_get_tag(void *prot, void *tag_buf, unsigned int sectors)
+{
+ struct sd_dif_tuple *sdt = prot;
+ char *tag = tag_buf;
+ unsigned int i, j;
+
+ for (i = 0, j = 0 ; i < sectors ; i++, j += 2, sdt++) {
+ tag[j] = (sdt->app_tag & 0xff00) >> 8;
+ tag[j+1] = sdt->app_tag & 0xff;
+ }
+}
+
+static struct blk_integrity dif_type1_integrity_crc = {
+ .name = "T10-DIF-TYPE1-CRC",
+ .generate_fn = sd_dif_type1_generate_crc,
+ .verify_fn = sd_dif_type1_verify_crc,
+ .get_tag_fn = sd_dif_type1_get_tag,
+ .set_tag_fn = sd_dif_type1_set_tag,
+ .tuple_size = sizeof(struct sd_dif_tuple),
+ .tag_size = 0,
+};
+
+static struct blk_integrity dif_type1_integrity_ip = {
+ .name = "T10-DIF-TYPE1-IP",
+ .generate_fn = sd_dif_type1_generate_ip,
+ .verify_fn = sd_dif_type1_verify_ip,
+ .get_tag_fn = sd_dif_type1_get_tag,
+ .set_tag_fn = sd_dif_type1_set_tag,
+ .tuple_size = sizeof(struct sd_dif_tuple),
+ .tag_size = 0,
+};
+
+
+/*
+ * Type 3 protection has a 16-bit guard tag and 16 + 32 bits of opaque
+ * tag space.
+ */
+static void sd_dif_type3_generate(struct blk_integrity_exchg *bix, csum_fn *fn)
+{
+ void *buf = bix->data_buf;
+ struct sd_dif_tuple *sdt = bix->prot_buf;
+ unsigned int i;
+
+ for (i = 0 ; i < bix->data_size ; i += bix->sector_size, sdt++) {
+ sdt->guard_tag = fn(buf, bix->sector_size);
+ sdt->ref_tag = 0;
+ sdt->app_tag = 0;
+
+ buf += bix->sector_size;
+ }
+}
+
+static void sd_dif_type3_generate_crc(struct blk_integrity_exchg *bix)
+{
+ sd_dif_type3_generate(bix, sd_dif_crc_fn);
+}
+
+static void sd_dif_type3_generate_ip(struct blk_integrity_exchg *bix)
+{
+ sd_dif_type3_generate(bix, sd_dif_ip_fn);
+}
+
+static int sd_dif_type3_verify(struct blk_integrity_exchg *bix, csum_fn *fn)
+{
+ void *buf = bix->data_buf;
+ struct sd_dif_tuple *sdt = bix->prot_buf;
+ sector_t sector = bix->sector;
+ unsigned int i;
+ __u16 csum;
+
+ for (i = 0 ; i < bix->data_size ; i += bix->sector_size, sdt++) {
+ /* Unwritten sectors */
+ if (sdt->app_tag == 0xffff && sdt->ref_tag == 0xffffffff)
+ return 0;
+
+ csum = fn(buf, bix->sector_size);
+
+ if (sdt->guard_tag != csum) {
+ printk(KERN_ERR "%s: guard tag error on sector %lu " \
+ "(rcvd %04x, data %04x)\n", bix->disk_name,
+ (unsigned long)sector,
+ be16_to_cpu(sdt->guard_tag), be16_to_cpu(csum));
+ return -EIO;
+ }
+
+ buf += bix->sector_size;
+ sector++;
+ }
+
+ return 0;
+}
+
+static int sd_dif_type3_verify_crc(struct blk_integrity_exchg *bix)
+{
+ return sd_dif_type3_verify(bix, sd_dif_crc_fn);
+}
+
+static int sd_dif_type3_verify_ip(struct blk_integrity_exchg *bix)
+{
+ return sd_dif_type3_verify(bix, sd_dif_ip_fn);
+}
+
+static void sd_dif_type3_set_tag(void *prot, void *tag_buf, unsigned int sectors)
+{
+ struct sd_dif_tuple *sdt = prot;
+ char *tag = tag_buf;
+ unsigned int i, j;
+
+ for (i = 0, j = 0 ; i < sectors ; i++, j += 6, sdt++) {
+ sdt->app_tag = tag[j] << 8 | tag[j+1];
+ sdt->ref_tag = tag[j+2] << 24 | tag[j+3] << 16 |
+ tag[j+4] << 8 | tag[j+5];
+ }
+}
+
+static void sd_dif_type3_get_tag(void *prot, void *tag_buf, unsigned int sectors)
+{
+ struct sd_dif_tuple *sdt = prot;
+ char *tag = tag_buf;
+ unsigned int i, j;
+
+ for (i = 0, j = 0 ; i < sectors ; i++, j += 2, sdt++) {
+ tag[j] = (sdt->app_tag & 0xff00) >> 8;
+ tag[j+1] = sdt->app_tag & 0xff;
+ tag[j+2] = (sdt->ref_tag & 0xff000000) >> 24;
+ tag[j+3] = (sdt->ref_tag & 0xff0000) >> 16;
+ tag[j+4] = (sdt->ref_tag & 0xff00) >> 8;
+ tag[j+5] = sdt->ref_tag & 0xff;
+ BUG_ON(sdt->app_tag == 0xffff || sdt->ref_tag == 0xffffffff);
+ }
+}
+
+static struct blk_integrity dif_type3_integrity_crc = {
+ .name = "T10-DIF-TYPE3-CRC",
+ .generate_fn = sd_dif_type3_generate_crc,
+ .verify_fn = sd_dif_type3_verify_crc,
+ .get_tag_fn = sd_dif_type3_get_tag,
+ .set_tag_fn = sd_dif_type3_set_tag,
+ .tuple_size = sizeof(struct sd_dif_tuple),
+ .tag_size = 0,
+};
+
+static struct blk_integrity dif_type3_integrity_ip = {
+ .name = "T10-DIF-TYPE3-IP",
+ .generate_fn = sd_dif_type3_generate_ip,
+ .verify_fn = sd_dif_type3_verify_ip,
+ .get_tag_fn = sd_dif_type3_get_tag,
+ .set_tag_fn = sd_dif_type3_set_tag,
+ .tuple_size = sizeof(struct sd_dif_tuple),
+ .tag_size = 0,
+};
+
+/*
+ * Configure exchange of protection information between OS and HBA.
+ */
+void sd_dif_config_host(struct scsi_disk *sdkp)
+{
+ struct scsi_device *sdp = sdkp->device;
+ struct gendisk *disk = sdkp->disk;
+ u8 type = sdkp->protection_type;
+
+ /* If this HBA doesn't support DIX, resort to normal I/O or DIF */
+ if (scsi_host_dix_capable(sdp->host, type) == 0) {
+
+ if (type == SD_DIF_TYPE0_PROTECTION)
+ return;
+
+ if (scsi_host_dif_capable(sdp->host, type) == 0) {
+ sd_printk(KERN_INFO, sdkp, "Type %d protection " \
+ "unsupported by HBA. Disabling DIF.\n", type);
+ sdkp->protection_type = 0;
+ return;
+ }
+
+ sd_printk(KERN_INFO, sdkp, "Enabling DIF Type %d protection\n",
+ type);
+
+ return;
+ }
+
+ /* Enable DMA of protection information */
+ if (scsi_host_get_guard(sdkp->device->host) & SHOST_DIX_GUARD_IP)
+ if (type == SD_DIF_TYPE3_PROTECTION)
+ blk_integrity_register(disk, &dif_type3_integrity_ip);
+ else
+ blk_integrity_register(disk, &dif_type1_integrity_ip);
+ else
+ if (type == SD_DIF_TYPE3_PROTECTION)
+ blk_integrity_register(disk, &dif_type3_integrity_crc);
+ else
+ blk_integrity_register(disk, &dif_type1_integrity_crc);
+
+ sd_printk(KERN_INFO, sdkp,
+ "Enabling %s integrity protection\n", disk->integrity->name);
+
+ /* Signal to block layer that we support sector tagging */
+ if (type && sdkp->ATO) {
+ if (type == SD_DIF_TYPE3_PROTECTION)
+ disk->integrity->tag_size = sizeof(u16) + sizeof(u32);
+ else
+ disk->integrity->tag_size = sizeof(u16);
+
+ sd_printk(KERN_INFO, sdkp, "DIF application tag size %u\n",
+ disk->integrity->tag_size);
+ }
+}
+
+/*
+ * DIF DMA operation magic decoder ring.
+ */
+void sd_dif_op(struct scsi_cmnd *scmd, unsigned int dif, unsigned int dix)
+{
+ int csum_convert;
+
+ /* Convert checksum? */
+ if (scsi_host_get_guard(scmd->device->host) != SHOST_DIX_GUARD_CRC)
+ csum_convert = 1;
+ else
+ csum_convert = 0;
+
+ switch (scmd->cmnd[0]) {
+ case READ_10:
+ case READ_12:
+ case READ_16:
+ if (dif && dix)
+ if (csum_convert)
+ scmd->prot_op = SCSI_PROT_READ_CONVERT;
+ else
+ scmd->prot_op = SCSI_PROT_READ_PASS;
+ else if (dif && !dix)
+ scmd->prot_op = SCSI_PROT_READ_STRIP;
+ else if (!dif && dix)
+ scmd->prot_op = SCSI_PROT_READ_INSERT;
+
+ break;
+
+ case WRITE_10:
+ case WRITE_12:
+ case WRITE_16:
+ if (dif && dix)
+ if (csum_convert)
+ scmd->prot_op = SCSI_PROT_WRITE_CONVERT;
+ else
+ scmd->prot_op = SCSI_PROT_WRITE_PASS;
+ else if (dif && !dix)
+ scmd->prot_op = SCSI_PROT_WRITE_INSERT;
+ else if (!dif && dix)
+ scmd->prot_op = SCSI_PROT_WRITE_STRIP;
+
+ break;
+ }
+}
+
+/*
+ * The virtual start sector is the one that was originally submitted
+ * by the block layer. Due to partitioning, MD/DM cloning, etc. the
+ * actual physical start sector is likely to be different. Remap
+ * protection information to match the physical LBA.
+ *
+ * From a protocol perspective there's a slight difference between
+ * Type 1 and 2. The latter uses 32-byte CDBs exclusively, and the
+ * reference tag is seeded in the CDB. This gives us the potential to
+ * avoid virt->phys remapping during write. However, at read time we
+ * don't know whether the virt sector is the same as when we wrote it
+ * (we could be reading from real disk as opposed to MD/DM device. So
+ * we always remap Type 2 making it identical to Type 1.
+ *
+ * Type 3 does not have a reference tag so no remapping is required.
+ */
+int sd_dif_prepare(struct request *rq, sector_t hw_sector, unsigned int sector_sz)
+{
+ const int tuple_sz = sizeof(struct sd_dif_tuple);
+ struct bio *bio;
+ struct scsi_disk *sdkp;
+ struct sd_dif_tuple *sdt;
+ unsigned int i, j;
+ u32 phys, virt;
+
+ /* Already remapped? */
+ if (rq->cmd_flags & REQ_INTEGRITY)
+ return 0;
+
+ sdkp = rq->bio->bi_bdev->bd_disk->private_data;
+
+ if (sdkp->protection_type == SD_DIF_TYPE3_PROTECTION)
+ return 0;
+
+ rq->cmd_flags |= REQ_INTEGRITY;
+ phys = hw_sector & 0xffffffff;
+
+ __rq_for_each_bio(bio, rq) {
+ struct bio_vec *iv;
+
+ virt = bio->bi_integrity->bip_sector & 0xffffffff;
+
+ bip_for_each_vec(iv, bio->bi_integrity, i) {
+ sdt = kmap_atomic(iv->bv_page, KM_USER0)
+ + iv->bv_offset;
+
+ for (j = 0 ; j < iv->bv_len ; j += tuple_sz, sdt++) {
+
+ if (be32_to_cpu(sdt->ref_tag) != virt)
+ goto error;
+
+ sdt->ref_tag = cpu_to_be32(phys);
+ virt++;
+ phys++;
+ }
+
+ kunmap_atomic(sdt, KM_USER0);
+ }
+ }
+
+ return 0;
+
+error:
+ kunmap_atomic(sdt, KM_USER0);
+ sd_printk(KERN_ERR, sdkp, "%s: virt %u, phys %u, ref %u\n",
+ __func__, virt, phys, be32_to_cpu(sdt->ref_tag));
+
+ return -EIO;
+}
+
+/*
+ * Remap physical sector values in the reference tag to the virtual
+ * values expected by the block layer.
+ */
+void sd_dif_complete(struct scsi_cmnd *scmd, unsigned int good_bytes)
+{
+ const int tuple_sz = sizeof(struct sd_dif_tuple);
+ struct scsi_disk *sdkp;
+ struct bio *bio;
+ struct sd_dif_tuple *sdt;
+ unsigned int i, j, sectors, sector_sz;
+ u32 phys, virt;
+
+ sdkp = scsi_disk(scmd->request->rq_disk);
+
+ if (sdkp->protection_type == SD_DIF_TYPE3_PROTECTION || good_bytes == 0)
+ return;
+
+ sector_sz = scmd->device->sector_size;
+ sectors = good_bytes / sector_sz;
+
+ phys = scmd->request->sector & 0xffffffff;
+ if (sector_sz == 4096)
+ phys >>= 3;
+
+ __rq_for_each_bio(bio, scmd->request) {
+ struct bio_vec *iv;
+
+ virt = bio->bi_integrity->bip_sector & 0xffffffff;
+
+ bip_for_each_vec(iv, bio->bi_integrity, i) {
+ sdt = kmap_atomic(iv->bv_page, KM_USER0)
+ + iv->bv_offset;
+
+ for (j = 0 ; j < iv->bv_len ; j += tuple_sz, sdt++) {
+
+ if (sectors == 0) {
+ kunmap_atomic(sdt, KM_USER0);
+ return;
+ }
+
+ if (be32_to_cpu(sdt->ref_tag) != phys &&
+ sdt->app_tag != 0xffff)
+ sdt->ref_tag = 0xffffffff; /* Bad ref */
+ else
+ sdt->ref_tag = cpu_to_be32(virt);
+
+ virt++;
+ phys++;
+ sectors--;
+ }
+
+ kunmap_atomic(sdt, KM_USER0);
+ }
+ }
+}
+
^ permalink raw reply [flat|nested] 8+ messages in thread