diff mbox

[01/12] Add ZBC <-> ZAC xlat support for report, open, close, reset, finish

Message ID 1459746376-27983-2-git-send-email-shaun@tancheff.com
State Not Applicable
Delegated to: David Miller
Headers show

Commit Message

Shaun Tancheff April 4, 2016, 5:06 a.m. UTC
Provide SCSI <-> ATA translation layer for ZBC commands:
  - Report Zones
  - Open, Close, Reset and Finish zones

Signed-off-by: Shaun Tancheff <shaun.tancheff@seagate.com>
---
 drivers/ata/libata-scsi.c | 167 ++++++++++++++++++++++++++++++++++++++++++++++
 include/linux/ata.h       |  18 +++++
 include/scsi/scsi_proto.h |  14 ++++
 3 files changed, 199 insertions(+)
diff mbox

Patch

diff --git a/drivers/ata/libata-scsi.c b/drivers/ata/libata-scsi.c
index e417e1a..a7c5ec8 100644
--- a/drivers/ata/libata-scsi.c
+++ b/drivers/ata/libata-scsi.c
@@ -65,6 +65,7 @@  static struct ata_device *__ata_scsi_find_dev(struct ata_port *ap,
 					const struct scsi_device *scsidev);
 static struct ata_device *ata_scsi_find_dev(struct ata_port *ap,
 					    const struct scsi_device *scsidev);
+static void scsi_16_lba_len(const u8 *cdb, u64 *plba, u32 *plen);
 
 #define RW_RECOVERY_MPAGE 0x1
 #define RW_RECOVERY_MPAGE_LEN 12
@@ -1442,6 +1443,160 @@  static unsigned int ata_scsi_flush_xlat(struct ata_queued_cmd *qc)
 }
 
 /**
+ *	ata_scsi_zone_command_xlat - Translate SCSI Reset Write Pointer command
+ *	@qc: Storage for translated ATA taskfile
+ *
+ *	Sets up an ATA taskfile to issue Reset Write Pointers Ext command.
+ *	May need change when zac specs is available.
+ *
+ *	LOCKING:
+ *	spin_lock_irqsave(host lock)
+ *
+ *	RETURNS:
+ *	Zero on success, non-zero on error.
+ */
+static unsigned int ata_scsi_zone_command_xlat(struct ata_queued_cmd *qc)
+{
+	struct scsi_cmnd *scmd = qc->scsicmd;
+	struct ata_taskfile *tf = &qc->tf;
+	const u8 *cdb = scmd->cmnd;
+	u8 sa; /* service action */
+	u8 all_bit;
+
+	if (scmd->cmd_len < 16)
+		goto invalid_fld;
+
+	sa = cdb[1] & 0x1f;
+
+	if (!(sa == ATA_SUBCMD_CLOSE_ZONES ||
+	      sa == ATA_SUBCMD_FINISH_ZONES ||
+	      sa == ATA_SUBCMD_OPEN_ZONES ||
+	      sa == ATA_SUBCMD_RESET_WP))
+		goto invalid_fld;
+
+	all_bit = cdb[14] & 0x01;
+	if (!all_bit) {
+		struct ata_device *dev = qc->dev;
+		u64 max_lba = dev->n_sectors;     /* Maximal LBA supported */
+		u64 slba;
+		u32 slen;
+
+		scsi_16_lba_len(cdb, &slba, &slen);
+		if (slba > max_lba) {
+			ata_dev_err(dev,
+				"Zone start LBA %llu > %llu (Max LBA)\n",
+				slba, max_lba);
+			goto out_of_range;
+		}
+
+		tf->hob_lbah = (slba >> 40) & 0xff;
+		tf->hob_lbam = (slba >> 32) & 0xff;
+		tf->hob_lbal = (slba >> 24) & 0xff;
+		tf->lbah = (slba >> 16) & 0xff;
+		tf->lbam = (slba >> 8) & 0xff;
+		tf->lbal = slba & 0xff;
+	}
+
+
+	tf->flags |= ATA_TFLAG_DEVICE | ATA_TFLAG_LBA48;
+	tf->protocol = ATA_PROT_NODATA;
+
+	tf->command = ATA_CMD_ZONE_MAN_OUT;
+	tf->feature = sa;
+	tf->hob_feature = all_bit;
+
+	return 0;
+
+ invalid_fld:
+	ata_scsi_set_sense(scmd, ILLEGAL_REQUEST, 0x24, 0x0);
+	/* "Invalid field in cbd" */
+	return 1;
+ out_of_range:
+	ata_scsi_set_sense(scmd, ILLEGAL_REQUEST, 0x21, 0x0);
+	/* LBA out of range */
+	return 1;
+}
+
+/**
+ *	ata_scsi_report_zones_xlat - Translate SCSI Report Zones command
+ *	@qc: Storage for translated ATA taskfile
+ *
+ *	Sets up an ATA taskfile to issue Report Zones Ext command.
+ *	May need change when zac specs is updated.
+ *
+ *	LOCKING:
+ *	spin_lock_irqsave(host lock)
+ *
+ *	RETURNS:
+ *	Zero on success, non-zero on error.
+ */
+static unsigned int ata_scsi_report_zones_xlat(struct ata_queued_cmd *qc)
+{
+	struct ata_device *dev = qc->dev;
+	struct scsi_cmnd *scmd = qc->scsicmd;
+	struct ata_taskfile *tf = &qc->tf;
+	const u8 *cdb = scmd->cmnd;
+	u64 max_lba = dev->n_sectors;     /* Maximal LBA supported */
+	u64 slba;       /* Start LBA in scsi command */
+	u32 alloc_len;  /* Alloc length (in bytes) */
+	u8 reporting_option;
+
+	if (scmd->cmd_len < 16) {
+		ata_dev_err(dev, "ZAC Error: Command length is less than 16\n");
+		goto invalid_fld;
+	}
+	if (unlikely(!dev->dma_mode)) {
+		ata_dev_err(dev, "ZAC Error: No DMA mode is set\n");
+		goto invalid_fld;
+	}
+	if (!scsi_sg_count(scmd)) {
+		ata_dev_err(dev, "ZAC Error: SCSI sg count is zero\n");
+		goto invalid_fld;
+	}
+	scsi_16_lba_len(cdb, &slba, &alloc_len);
+	if (slba > max_lba) {
+		ata_dev_err(dev, "Zone start LBA %llu > %llu (Max LBA)\n",
+			    slba, max_lba);
+		goto out_of_range;
+	}
+
+	reporting_option = cdb[14] & 0x3f;
+
+	tf->flags |= ATA_TFLAG_DEVICE | ATA_TFLAG_LBA48 | ATA_TFLAG_ISADDR;
+	tf->protocol = ATA_PROT_DMA;
+
+	tf->command = ATA_CMD_ZONE_MAN_IN;
+
+	tf->hob_lbah = (slba >> 40) & 0xff;
+	tf->hob_lbam = (slba >> 32) & 0xff;
+	tf->hob_lbal = (slba >> 24) & 0xff;
+	tf->lbah = (slba >> 16) & 0xff;
+	tf->lbam = (slba >> 8) & 0xff;
+	tf->lbal = slba & 0xff;
+
+	tf->feature = 0x00;
+	tf->hob_feature = reporting_option;
+
+	alloc_len    /= 512; /* bytes in scsi, blocks in ata */
+	tf->nsect     = alloc_len & 0xff;
+	tf->hob_nsect = alloc_len >> 8;
+
+	ata_qc_set_pc_nbytes(qc);
+
+	return 0;
+
+ invalid_fld:
+	ata_scsi_set_sense(scmd, ILLEGAL_REQUEST, 0x24, 0x0);
+	/* "Invalid field in cbd" */
+	return 1;
+ out_of_range:
+	ata_scsi_set_sense(scmd, ILLEGAL_REQUEST, 0x21, 0x0);
+	/* LBA out of range */
+	return 1;
+}
+
+
+/**
  *	scsi_6_lba_len - Get LBA and transfer length
  *	@cdb: SCSI command to translate
  *
@@ -2232,12 +2387,17 @@  static unsigned int ata_scsiop_inq_b1(struct ata_scsi_args *args, u8 *rbuf)
 {
 	int form_factor = ata_id_form_factor(args->id);
 	int media_rotation_rate = ata_id_rotation_rate(args->id);
+	bool zac_ha = ata_drive_zac_ha(args->id);
 
 	rbuf[1] = 0xb1;
 	rbuf[3] = 0x3c;
 	rbuf[4] = media_rotation_rate >> 8;
 	rbuf[5] = media_rotation_rate;
 	rbuf[7] = form_factor;
+	if (zac_ha) {
+		rbuf[8] &= 0xcf;
+		rbuf[8] |= 0x10;  /* SBC4: 0x01 for zoned host aware device */
+	}
 
 	return 0;
 }
@@ -3421,6 +3581,13 @@  static inline ata_xlat_func_t ata_get_xlat_func(struct ata_device *dev, u8 cmd)
 
 	case START_STOP:
 		return ata_scsi_start_stop_xlat;
+
+	case ZBC_ACTION:
+		return ata_scsi_zone_command_xlat;
+
+	case ZBC_REPORT_ZONES:
+		return ata_scsi_report_zones_xlat;
+	  break;
 	}
 
 	return NULL;
diff --git a/include/linux/ata.h b/include/linux/ata.h
index c1a2f34..779b7f2 100644
--- a/include/linux/ata.h
+++ b/include/linux/ata.h
@@ -305,6 +305,17 @@  enum {
 	/* marked obsolete in the ATA/ATAPI-7 spec */
 	ATA_CMD_RESTORE		= 0x10,
 
+	/* ZAC commands */
+	ATA_CMD_ZONE_MAN_OUT	= 0x9F,
+
+	ATA_SUBCMD_CLOSE_ZONES	= 0x01,
+	ATA_SUBCMD_FINISH_ZONES	= 0x02,
+	ATA_SUBCMD_OPEN_ZONES	= 0x03,
+	ATA_SUBCMD_RESET_WP	= 0x04,
+
+	ATA_CMD_ZONE_MAN_IN	= 0x4A,
+	ATA_SUBCMD_REP_ZONES	= 0x00,
+
 	/* Subcmds for ATA_CMD_FPDMA_SEND */
 	ATA_SUBCMD_FPDMA_SEND_DSM            = 0x00,
 	ATA_SUBCMD_FPDMA_SEND_WR_LOG_DMA_EXT = 0x02,
@@ -899,6 +910,13 @@  static inline bool ata_drive_40wire_relaxed(const u16 *dev_id)
 	return true;
 }
 
+static inline bool ata_drive_zac_ha(const u16 *dev_id)
+{
+	if ((dev_id[69] & 0x0003) == 0x0001)
+		return true;
+	return false;
+}
+
 static inline int atapi_cdb_len(const u16 *dev_id)
 {
 	u16 tmp = dev_id[ATA_ID_CONFIG] & 0x3;
diff --git a/include/scsi/scsi_proto.h b/include/scsi/scsi_proto.h
index a9fbf1b..dd0e089 100644
--- a/include/scsi/scsi_proto.h
+++ b/include/scsi/scsi_proto.h
@@ -115,6 +115,10 @@ 
 #define VERIFY_16	      0x8f
 #define SYNCHRONIZE_CACHE_16  0x91
 #define WRITE_SAME_16	      0x93
+/* Op codes for Zoned Block Commands */
+#define ZBC_ACTION		0x94
+#define ZBC_REPORT_ZONES	0x95
+
 #define SERVICE_ACTION_BIDIRECTIONAL 0x9d
 #define SERVICE_ACTION_IN_16  0x9e
 #define SERVICE_ACTION_OUT_16 0x9f
@@ -157,6 +161,16 @@ 
 #define	ATA_16		      0x85	/* 16-byte pass-thru */
 #define	ATA_12		      0xa1	/* 12-byte pass-thru */
 
+/* ZBC_ACTION: Values for T-10 ZBC sub action */
+#define ZBC_SA_ZONE_CLOSE	0x01
+#define ZBC_SA_ZONE_FINISH	0x02
+#define ZBC_SA_ZONE_OPEN	0x03
+#define ZBC_SA_RESET_WP		0x04
+
+/* ZBC_REPORT_ZONES: Default report option. T-10 ZBC report zones */
+#define ZBC_REPORT_OPT		0x00
+
+
 /* Vendor specific CDBs start here */
 #define VENDOR_SPECIFIC_CDB 0xc0