Commit 7b203094 authored by Shaun Tancheff's avatar Shaun Tancheff Committed by Tejun Heo

libata: Add support for SCT Write Same

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: default avatarShaun Tancheff <shaun.tancheff@seagate.com>
Reviewed-by: default avatarHannes Reinecke <hare@suse.com>
Acked-by: default avatarTejun Heo <tj@kernel.org>
parent 9379e6b8
...@@ -1159,8 +1159,6 @@ static void ata_scsi_sdev_config(struct scsi_device *sdev) ...@@ -1159,8 +1159,6 @@ static void ata_scsi_sdev_config(struct scsi_device *sdev)
{ {
sdev->use_10_for_rw = 1; sdev->use_10_for_rw = 1;
sdev->use_10_for_ms = 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 /* Schedule policy is determined by ->qc_defer() callback and
* it needs to see every deferred qc. Set dev_blocked to 1 to * it needs to see every deferred qc. Set dev_blocked to 1 to
...@@ -3287,7 +3285,7 @@ static unsigned int ata_scsi_pass_thru(struct ata_queued_cmd *qc) ...@@ -3287,7 +3285,7 @@ static unsigned int ata_scsi_pass_thru(struct ata_queued_cmd *qc)
* @cmd: SCSI command being translated * @cmd: SCSI command being translated
* @num: Maximum number of entries (nominally 64). * @num: Maximum number of entries (nominally 64).
* @sector: Starting sector * @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 * Rewrite the WRITE SAME descriptor to be a DSM TRIM little-endian formatted
* descriptor. * descriptor.
...@@ -3330,6 +3328,45 @@ static unsigned int ata_format_dsm_trim_descr(struct scsi_cmnd *cmd, u32 num, ...@@ -3330,6 +3328,45 @@ static unsigned int ata_format_dsm_trim_descr(struct scsi_cmnd *cmd, u32 num,
return used_bytes; 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) static unsigned int ata_scsi_write_same_xlat(struct ata_queued_cmd *qc)
{ {
struct ata_taskfile *tf = &qc->tf; struct ata_taskfile *tf = &qc->tf;
...@@ -3342,6 +3379,7 @@ static unsigned int ata_scsi_write_same_xlat(struct ata_queued_cmd *qc) ...@@ -3342,6 +3379,7 @@ static unsigned int ata_scsi_write_same_xlat(struct ata_queued_cmd *qc)
u32 size; u32 size;
u16 fp; u16 fp;
u8 bp = 0xff; u8 bp = 0xff;
u8 unmap = cdb[1] & 0x8;
/* we may not issue DMA commands if no DMA mode is set */ /* we may not issue DMA commands if no DMA mode is set */
if (unlikely(!dev->dma_mode)) if (unlikely(!dev->dma_mode))
...@@ -3353,12 +3391,27 @@ static unsigned int ata_scsi_write_same_xlat(struct ata_queued_cmd *qc) ...@@ -3353,12 +3391,27 @@ static unsigned int ata_scsi_write_same_xlat(struct ata_queued_cmd *qc)
} }
scsi_16_lba_len(cdb, &block, &n_block); scsi_16_lba_len(cdb, &block, &n_block);
/* for now we only support WRITE SAME with the unmap bit set */ if (unmap) {
if (unlikely(!(cdb[1] & 0x8))) { /* 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; fp = 1;
bp = 3; bp = 3;
goto invalid_fld; goto invalid_fld;
} }
}
/* /*
* WRITE SAME always has a sector sized buffer as payload, this * WRITE SAME always has a sector sized buffer as payload, this
...@@ -3367,13 +3420,8 @@ static unsigned int ata_scsi_write_same_xlat(struct ata_queued_cmd *qc) ...@@ -3367,13 +3420,8 @@ static unsigned int ata_scsi_write_same_xlat(struct ata_queued_cmd *qc)
if (!scsi_sg_count(scmd)) if (!scsi_sg_count(scmd))
goto invalid_param_len; goto invalid_param_len;
if (n_block <= 0xffff * trmax) { if (unmap) {
size = ata_format_dsm_trim_descr(scmd, trmax, block, n_block); size = ata_format_dsm_trim_descr(scmd, trmax, block, n_block);
} else {
fp = 2;
goto invalid_fld;
}
if (ata_ncq_enabled(dev) && ata_fpdma_dsm_supported(dev)) { if (ata_ncq_enabled(dev) && ata_fpdma_dsm_supported(dev)) {
/* Newer devices support queued TRIM commands */ /* Newer devices support queued TRIM commands */
tf->protocol = ATA_PROT_NCQ; tf->protocol = ATA_PROT_NCQ;
...@@ -3392,6 +3440,23 @@ static unsigned int ata_scsi_write_same_xlat(struct ata_queued_cmd *qc) ...@@ -3392,6 +3440,23 @@ static unsigned int ata_scsi_write_same_xlat(struct ata_queued_cmd *qc)
tf->nsect = size / 512; tf->nsect = size / 512;
tf->command = ATA_CMD_DSM; tf->command = ATA_CMD_DSM;
} }
} else {
ata_format_sct_write_same(scmd, block, n_block);
tf->hob_feature = 0;
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 | tf->flags |= ATA_TFLAG_ISADDR | ATA_TFLAG_DEVICE | ATA_TFLAG_LBA48 |
ATA_TFLAG_WRITE; ATA_TFLAG_WRITE;
...@@ -3413,6 +3478,76 @@ static unsigned int ata_scsi_write_same_xlat(struct ata_queued_cmd *qc) ...@@ -3413,6 +3478,76 @@ static unsigned int ata_scsi_write_same_xlat(struct ata_queued_cmd *qc)
return 1; 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 * ata_scsi_report_zones_complete - convert ATA output
* @qc: command structure returning the data * @qc: command structure returning the data
...@@ -4193,6 +4328,13 @@ void ata_scsi_simulate(struct ata_device *dev, struct scsi_cmnd *cmd) ...@@ -4193,6 +4328,13 @@ void ata_scsi_simulate(struct ata_device *dev, struct scsi_cmnd *cmd)
ata_scsi_invalid_field(dev, cmd, 1); ata_scsi_invalid_field(dev, cmd, 1);
break; 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 */ /* all other commands */
default: default:
ata_scsi_set_sense(dev, cmd, ILLEGAL_REQUEST, 0x20, 0x0); ata_scsi_set_sense(dev, cmd, ILLEGAL_REQUEST, 0x20, 0x0);
...@@ -4225,7 +4367,6 @@ int ata_scsi_add_hosts(struct ata_host *host, struct scsi_host_template *sht) ...@@ -4225,7 +4367,6 @@ int ata_scsi_add_hosts(struct ata_host *host, struct scsi_host_template *sht)
shost->max_lun = 1; shost->max_lun = 1;
shost->max_channel = 1; shost->max_channel = 1;
shost->max_cmd_len = 16; shost->max_cmd_len = 16;
shost->no_write_same = 1;
/* Schedule policy is determined by ->qc_defer() /* Schedule policy is determined by ->qc_defer()
* callback and it needs to see every deferred qc. * callback and it needs to see every deferred qc.
......
...@@ -105,6 +105,7 @@ enum { ...@@ -105,6 +105,7 @@ enum {
ATA_ID_CFA_KEY_MGMT = 162, ATA_ID_CFA_KEY_MGMT = 162,
ATA_ID_CFA_MODES = 163, ATA_ID_CFA_MODES = 163,
ATA_ID_DATA_SET_MGMT = 169, ATA_ID_DATA_SET_MGMT = 169,
ATA_ID_SCT_CMD_XPORT = 206,
ATA_ID_ROT_SPEED = 217, ATA_ID_ROT_SPEED = 217,
ATA_ID_PIO4 = (1 << 1), ATA_ID_PIO4 = (1 << 1),
...@@ -788,6 +789,48 @@ static inline bool ata_id_sense_reporting_enabled(const u16 *id) ...@@ -788,6 +789,48 @@ static inline bool ata_id_sense_reporting_enabled(const u16 *id)
return id[ATA_ID_COMMAND_SET_4] & (1 << 6); 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 * ata_id_major_version - get ATA level of drive
* @id: Identify data * @id: Identify data
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment