Provide SCSI <-> ATA translation layer for ZBC commands: - Report Zones - Open, Close, Reset and Finish zones Signed-off-by: Shaun Tancheff <shaun.tancheff@xxxxxxxxxxx> --- drivers/ata/libata-scsi.c | 167 ++++++++++++++++++++++++++++++++++++++++++++++ include/linux/ata.h | 18 +++++ include/scsi/scsi_proto.h | 14 ++++ 3 files changed, 199 insertions(+) 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 -- 1.9.1 -- To unsubscribe from this list: send the line "unsubscribe linux-scsi" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html