Skip to content

Commit

Permalink
libata: Add support for SCT Write Same
Browse files Browse the repository at this point in the history
SATA drives may support write same via SCT. This is useful
for setting the drive contents to a specific pattern (0's).

Translate a SCSI WRITE SAME 16 command to be either a DSM TRIM
command or an SCT Write Same command.

Based on the UNMAP flag:
  - When set translate to DSM TRIM
  - When not set translate to SCT Write Same

Signed-off-by: Shaun Tancheff <shaun.tancheff@seagate.com>
Reviewed-by: Hannes Reinecke <hare@suse.com>
Acked-by: Tejun Heo <tj@kernel.org>
  • Loading branch information
Shaun Tancheff authored and Tejun Heo committed Aug 25, 2016
1 parent 9379e6b commit 7b20309
Show file tree
Hide file tree
Showing 2 changed files with 213 additions and 29 deletions.
199 changes: 170 additions & 29 deletions drivers/ata/libata-scsi.c
Original file line number Diff line number Diff line change
Expand Up @@ -1159,8 +1159,6 @@ static void ata_scsi_sdev_config(struct scsi_device *sdev)
{
sdev->use_10_for_rw = 1;
sdev->use_10_for_ms = 1;
sdev->no_report_opcodes = 1;
sdev->no_write_same = 1;

/* Schedule policy is determined by ->qc_defer() callback and
* it needs to see every deferred qc. Set dev_blocked to 1 to
Expand Down Expand Up @@ -3287,7 +3285,7 @@ static unsigned int ata_scsi_pass_thru(struct ata_queued_cmd *qc)
* @cmd: SCSI command being translated
* @num: Maximum number of entries (nominally 64).
* @sector: Starting sector
* @count: Total Range of request
* @count: Total Range of request in logical sectors
*
* Rewrite the WRITE SAME descriptor to be a DSM TRIM little-endian formatted
* descriptor.
Expand Down Expand Up @@ -3330,6 +3328,45 @@ static unsigned int ata_format_dsm_trim_descr(struct scsi_cmnd *cmd, u32 num,
return used_bytes;
}

/**
* ata_format_dsm_trim_descr() - SATL Write Same to ATA SCT Write Same
* @cmd: SCSI command being translated
* @lba: Starting sector
* @num: Number of logical sectors to be zero'd.
*
* Rewrite the WRITE SAME descriptor to be an SCT Write Same formatted
* descriptor.
* NOTE: Writes a pattern (0's) in the foreground.
* Large write-same requents can timeout.
*/
static void ata_format_sct_write_same(struct scsi_cmnd *cmd, u64 lba, u64 num)
{
u16 *sctpg;
unsigned long flags;

spin_lock_irqsave(&ata_scsi_rbuf_lock, flags);
sctpg = ((void *)ata_scsi_rbuf);

put_unaligned_le16(0x0002, &sctpg[0]); /* SCT_ACT_WRITE_SAME */
put_unaligned_le16(0x0101, &sctpg[1]); /* WRITE PTRN FG */
put_unaligned_le64(lba, &sctpg[2]);
put_unaligned_le64(num, &sctpg[6]);
put_unaligned_le32(0u, &sctpg[10]);

sg_copy_from_buffer(scsi_sglist(cmd), scsi_sg_count(cmd), sctpg, 512);
spin_unlock_irqrestore(&ata_scsi_rbuf_lock, flags);
}

/**
* ata_scsi_write_same_xlat() - SATL Write Same to ATA SCT Write Same
* @qc: Command to be translated
*
* Translate a SCSI WRITE SAME command to be either a DSM TRIM command or
* an SCT Write Same command.
* Based on WRITE SAME has the UNMAP flag
* When set translate to DSM TRIM
* When clear translate to SCT Write Same
*/
static unsigned int ata_scsi_write_same_xlat(struct ata_queued_cmd *qc)
{
struct ata_taskfile *tf = &qc->tf;
Expand All @@ -3342,6 +3379,7 @@ static unsigned int ata_scsi_write_same_xlat(struct ata_queued_cmd *qc)
u32 size;
u16 fp;
u8 bp = 0xff;
u8 unmap = cdb[1] & 0x8;

/* we may not issue DMA commands if no DMA mode is set */
if (unlikely(!dev->dma_mode))
Expand All @@ -3353,11 +3391,26 @@ static unsigned int ata_scsi_write_same_xlat(struct ata_queued_cmd *qc)
}
scsi_16_lba_len(cdb, &block, &n_block);

/* for now we only support WRITE SAME with the unmap bit set */
if (unlikely(!(cdb[1] & 0x8))) {
fp = 1;
bp = 3;
goto invalid_fld;
if (unmap) {
/* If trim is not enabled the cmd is invalid. */
if ((dev->horkage & ATA_HORKAGE_NOTRIM) ||
!ata_id_has_trim(dev->id)) {
fp = 1;
bp = 3;
goto invalid_fld;
}
/* If the request is too large the cmd is invalid */
if (n_block > 0xffff * trmax) {
fp = 2;
goto invalid_fld;
}
} else {
/* If write same is not available the cmd is invalid */
if (!ata_id_sct_write_same(dev->id)) {
fp = 1;
bp = 3;
goto invalid_fld;
}
}

/*
Expand All @@ -3367,30 +3420,42 @@ static unsigned int ata_scsi_write_same_xlat(struct ata_queued_cmd *qc)
if (!scsi_sg_count(scmd))
goto invalid_param_len;

if (n_block <= 0xffff * trmax) {
if (unmap) {
size = ata_format_dsm_trim_descr(scmd, trmax, block, n_block);
if (ata_ncq_enabled(dev) && ata_fpdma_dsm_supported(dev)) {
/* Newer devices support queued TRIM commands */
tf->protocol = ATA_PROT_NCQ;
tf->command = ATA_CMD_FPDMA_SEND;
tf->hob_nsect = ATA_SUBCMD_FPDMA_SEND_DSM & 0x1f;
tf->nsect = qc->tag << 3;
tf->hob_feature = (size / 512) >> 8;
tf->feature = size / 512;

tf->auxiliary = 1;
} else {
tf->protocol = ATA_PROT_DMA;
tf->hob_feature = 0;
tf->feature = ATA_DSM_TRIM;
tf->hob_nsect = (size / 512) >> 8;
tf->nsect = size / 512;
tf->command = ATA_CMD_DSM;
}
} else {
fp = 2;
goto invalid_fld;
}

if (ata_ncq_enabled(dev) && ata_fpdma_dsm_supported(dev)) {
/* Newer devices support queued TRIM commands */
tf->protocol = ATA_PROT_NCQ;
tf->command = ATA_CMD_FPDMA_SEND;
tf->hob_nsect = ATA_SUBCMD_FPDMA_SEND_DSM & 0x1f;
tf->nsect = qc->tag << 3;
tf->hob_feature = (size / 512) >> 8;
tf->feature = size / 512;
ata_format_sct_write_same(scmd, block, n_block);

tf->auxiliary = 1;
} else {
tf->protocol = ATA_PROT_DMA;
tf->hob_feature = 0;
tf->feature = ATA_DSM_TRIM;
tf->hob_nsect = (size / 512) >> 8;
tf->nsect = size / 512;
tf->command = ATA_CMD_DSM;
tf->feature = 0;
tf->hob_nsect = 0;
tf->nsect = 1;
tf->lbah = 0;
tf->lbam = 0;
tf->lbal = ATA_CMD_STANDBYNOW1;
tf->hob_lbah = 0;
tf->hob_lbam = 0;
tf->hob_lbal = 0;
tf->device = ATA_CMD_STANDBYNOW1;
tf->protocol = ATA_PROT_DMA;
tf->command = ATA_CMD_WRITE_LOG_DMA_EXT;
}

tf->flags |= ATA_TFLAG_ISADDR | ATA_TFLAG_DEVICE | ATA_TFLAG_LBA48 |
Expand All @@ -3413,6 +3478,76 @@ static unsigned int ata_scsi_write_same_xlat(struct ata_queued_cmd *qc)
return 1;
}

/**
* ata_scsiop_maint_in - Simulate a subset of MAINTENANCE_IN
* @args: device MAINTENANCE_IN data / SCSI command of interest.
* @rbuf: Response buffer, to which simulated SCSI cmd output is sent.
*
* Yields a subset to satisfy scsi_report_opcode()
*
* LOCKING:
* spin_lock_irqsave(host lock)
*/
static unsigned int ata_scsiop_maint_in(struct ata_scsi_args *args, u8 *rbuf)
{
struct ata_device *dev = args->dev;
u8 *cdb = args->cmd->cmnd;
u8 supported = 0;
unsigned int err = 0;

if (cdb[2] != 1) {
ata_dev_warn(dev, "invalid command format %d\n", cdb[2]);
err = 2;
goto out;
}
switch (cdb[3]) {
case INQUIRY:
case MODE_SENSE:
case MODE_SENSE_10:
case READ_CAPACITY:
case SERVICE_ACTION_IN_16:
case REPORT_LUNS:
case REQUEST_SENSE:
case SYNCHRONIZE_CACHE:
case REZERO_UNIT:
case SEEK_6:
case SEEK_10:
case TEST_UNIT_READY:
case SEND_DIAGNOSTIC:
case MAINTENANCE_IN:
case READ_6:
case READ_10:
case READ_16:
case WRITE_6:
case WRITE_10:
case WRITE_16:
case ATA_12:
case ATA_16:
case VERIFY:
case VERIFY_16:
case MODE_SELECT:
case MODE_SELECT_10:
case START_STOP:
supported = 3;
break;
case WRITE_SAME_16:
if (ata_id_sct_write_same(dev->id))
supported = 3;
break;
case ZBC_IN:
case ZBC_OUT:
if (ata_id_zoned_cap(dev->id) ||
dev->class == ATA_DEV_ZAC)
supported = 3;
break;
default:
break;
}
out:
rbuf[1] = supported; /* supported */
return err;
}

/**
* ata_scsi_report_zones_complete - convert ATA output
* @qc: command structure returning the data
Expand Down Expand Up @@ -4193,6 +4328,13 @@ void ata_scsi_simulate(struct ata_device *dev, struct scsi_cmnd *cmd)
ata_scsi_invalid_field(dev, cmd, 1);
break;

case MAINTENANCE_IN:
if (scsicmd[1] == MI_REPORT_SUPPORTED_OPERATION_CODES)
ata_scsi_rbuf_fill(&args, ata_scsiop_maint_in);
else
ata_scsi_invalid_field(dev, cmd, 1);
break;

/* all other commands */
default:
ata_scsi_set_sense(dev, cmd, ILLEGAL_REQUEST, 0x20, 0x0);
Expand Down Expand Up @@ -4225,7 +4367,6 @@ int ata_scsi_add_hosts(struct ata_host *host, struct scsi_host_template *sht)
shost->max_lun = 1;
shost->max_channel = 1;
shost->max_cmd_len = 16;
shost->no_write_same = 1;

/* Schedule policy is determined by ->qc_defer()
* callback and it needs to see every deferred qc.
Expand Down
43 changes: 43 additions & 0 deletions include/linux/ata.h
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ enum {
ATA_ID_CFA_KEY_MGMT = 162,
ATA_ID_CFA_MODES = 163,
ATA_ID_DATA_SET_MGMT = 169,
ATA_ID_SCT_CMD_XPORT = 206,
ATA_ID_ROT_SPEED = 217,
ATA_ID_PIO4 = (1 << 1),

Expand Down Expand Up @@ -788,6 +789,48 @@ static inline bool ata_id_sense_reporting_enabled(const u16 *id)
return id[ATA_ID_COMMAND_SET_4] & (1 << 6);
}

/**
*
* Word: 206 - SCT Command Transport
* 15:12 - Vendor Specific
* 11:6 - Reserved
* 5 - SCT Command Transport Data Tables supported
* 4 - SCT Command Transport Features Control supported
* 3 - SCT Command Transport Error Recovery Control supported
* 2 - SCT Command Transport Write Same supported
* 1 - SCT Command Transport Long Sector Access supported
* 0 - SCT Command Transport supported
*/
static inline bool ata_id_sct_data_tables(const u16 *id)
{
return id[ATA_ID_SCT_CMD_XPORT] & (1 << 5) ? true : false;
}

static inline bool ata_id_sct_features_ctrl(const u16 *id)
{
return id[ATA_ID_SCT_CMD_XPORT] & (1 << 4) ? true : false;
}

static inline bool ata_id_sct_error_recovery_ctrl(const u16 *id)
{
return id[ATA_ID_SCT_CMD_XPORT] & (1 << 3) ? true : false;
}

static inline bool ata_id_sct_write_same(const u16 *id)
{
return id[ATA_ID_SCT_CMD_XPORT] & (1 << 2) ? true : false;
}

static inline bool ata_id_sct_long_sector_access(const u16 *id)
{
return id[ATA_ID_SCT_CMD_XPORT] & (1 << 1) ? true : false;
}

static inline bool ata_id_sct_supported(const u16 *id)
{
return id[ATA_ID_SCT_CMD_XPORT] & (1 << 0) ? true : false;
}

/**
* ata_id_major_version - get ATA level of drive
* @id: Identify data
Expand Down

0 comments on commit 7b20309

Please sign in to comment.