Re: [PATCH v6 2/4] Add support for SCT Write Same
From: Shaun Tancheff
Date: Mon Aug 22 2016 - 15:44:23 EST
On Mon, Aug 22, 2016 at 2:20 PM, Tom Yan <tom.ty89@xxxxxxxxx> wrote:
> On 22 August 2016 at 12:23, Shaun Tancheff <shaun@xxxxxxxxxxxx> wrote:
>> 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@xxxxxxxxxxx>
>> ---
>> v6:
>> - Change to use sg_copy_from_buffer as per Christoph Hellwig <hch@xxxxxx>
>> v5:
>> - Addressed review comments
>> - Report support for ZBC only for zoned devices.
>> - kmap page during rewrite
>> - Fix unmap set to require trim or error, if not unmap then sct write
>> same or error.
>> v4:
>> - Added partial MAINTENANCE_IN opcode simulation
>> - Dropped all changes in drivers/scsi/*
>> - Changed to honor the UNMAP flag -> TRIM, no UNMAP -> SCT.
>> v3:
>> - Demux UNMAP/TRIM from WRITE SAME
>> v2:
>> - Remove fugly ata hacking from sd.c
>>
>> drivers/ata/libata-scsi.c | 199 +++++++++++++++++++++++++++++++++++++++-------
>> include/linux/ata.h | 43 ++++++++++
>> 2 files changed, 213 insertions(+), 29 deletions(-)
>>
>> diff --git a/drivers/ata/libata-scsi.c b/drivers/ata/libata-scsi.c
>> index 7990cb2..ebf1a04 100644
>> --- a/drivers/ata/libata-scsi.c
>> +++ b/drivers/ata/libata-scsi.c
>> @@ -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
>> @@ -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.
>> @@ -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;
>> @@ -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))
>> @@ -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;
>> + }
>
> This response should be generally applied to the Write Same (16)
> translation, since it is required by SBC,
>
>> + } 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;
>> + }
>
> therefore, you should add an n_block check here as well, if you are
> going to advertise an Maximum Write Same Length even when the device
> supports only SCT Write Same but not TRIM. Most likely you would want
> to simply move the existing check one-level up (if the same limit is
> advertised no matter TRIM is supported not or not).
Why would we enforce upper level limits on something that doesn't
have any?
If the upper level, or SG_IO, chooses to set a timeout of 10 hours and
wipe a whole disk it should be free to do so.
>> }
>>
>> /*
>> @@ -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 |
>> @@ -3414,6 +3479,76 @@ invalid_opcode:
>> }
>>
>> /**
>> + * 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
>> *
>> @@ -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);
>> @@ -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.
>> diff --git a/include/linux/ata.h b/include/linux/ata.h
>> index 45a1d71..fdb1803 100644
>> --- a/include/linux/ata.h
>> +++ b/include/linux/ata.h
>> @@ -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),
>>
>> @@ -789,6 +790,48 @@ static inline bool ata_id_sense_reporting_enabled(const u16 *id)
>> }
>>
>> /**
>> + *
>> + * 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
>> *
>> --
>> 2.9.3
>>
--
Shaun Tancheff