Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

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

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
Loading
Loading
Loading
Loading
+170 −29
Original line number Diff line number Diff line
@@ -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,12 +3391,27 @@ 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))) {
	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;
		}
	}

	/*
	 * 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)
	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);
	} 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;
@@ -3392,6 +3440,23 @@ static unsigned int ata_scsi_write_same_xlat(struct ata_queued_cmd *qc)
			tf->nsect = size / 512;
			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 |
		     ATA_TFLAG_WRITE;
@@ -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
@@ -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.
+43 −0
Original line number Diff line number Diff line
@@ -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),

@@ -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