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

Commit 28a3fc22 authored by Hannes Reinecke's avatar Hannes Reinecke Committed by Tejun Heo
Browse files

libata: implement ZBC IN translation



ZAC drives implement a 'ZAC Management In' command template,
which maps onto the ZBC IN command.

Signed-off-by: default avatarHannes Reinecke <hare@suse.de>
Signed-off-by: default avatarTejun Heo <tj@kernel.org>
parent 3a92945b
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -2482,6 +2482,7 @@ const char *ata_get_cmd_descript(u8 command)
		{ ATA_CMD_CFA_WRITE_MULT_NE,	"CFA WRITE MULTIPLE WITHOUT ERASE" },
		{ ATA_CMD_REQ_SENSE_DATA,	"REQUEST SENSE DATA EXT" },
		{ ATA_CMD_SANITIZE_DEVICE,	"SANITIZE DEVICE" },
		{ ATA_CMD_ZAC_MGMT_IN,		"ZAC MANAGEMENT IN" },
		{ ATA_CMD_READ_LONG,		"READ LONG (with retries)" },
		{ ATA_CMD_READ_LONG_ONCE,	"READ LONG (without retries)" },
		{ ATA_CMD_WRITE_LONG,		"WRITE LONG (with retries)" },
+157 −0
Original line number Diff line number Diff line
@@ -3317,6 +3317,160 @@ static unsigned int ata_scsi_write_same_xlat(struct ata_queued_cmd *qc)
	return 1;
}

/**
 *	ata_scsi_report_zones_complete - convert ATA output
 *	@qc: command structure returning the data
 *
 *	Convert T-13 little-endian field representation into
 *	T-10 big-endian field representation.
 *	What a mess.
 */
static void ata_scsi_report_zones_complete(struct ata_queued_cmd *qc)
{
	struct scsi_cmnd *scmd = qc->scsicmd;
	struct sg_mapping_iter miter;
	unsigned long flags;
	unsigned int bytes = 0;

	sg_miter_start(&miter, scsi_sglist(scmd), scsi_sg_count(scmd),
		       SG_MITER_TO_SG | SG_MITER_ATOMIC);

	local_irq_save(flags);
	while (sg_miter_next(&miter)) {
		unsigned int offset = 0;

		if (bytes == 0) {
			char *hdr;
			u32 list_length;
			u64 max_lba, opt_lba;
			u16 same;

			/* Swizzle header */
			hdr = miter.addr;
			list_length = get_unaligned_le32(&hdr[0]);
			same = get_unaligned_le16(&hdr[4]);
			max_lba = get_unaligned_le64(&hdr[8]);
			opt_lba = get_unaligned_le64(&hdr[16]);
			put_unaligned_be32(list_length, &hdr[0]);
			hdr[4] = same & 0xf;
			put_unaligned_be64(max_lba, &hdr[8]);
			put_unaligned_be64(opt_lba, &hdr[16]);
			offset += 64;
			bytes += 64;
		}
		while (offset < miter.length) {
			char *rec;
			u8 cond, type, non_seq, reset;
			u64 size, start, wp;

			/* Swizzle zone descriptor */
			rec = miter.addr + offset;
			type = rec[0] & 0xf;
			cond = (rec[1] >> 4) & 0xf;
			non_seq = (rec[1] & 2);
			reset = (rec[1] & 1);
			size = get_unaligned_le64(&rec[8]);
			start = get_unaligned_le64(&rec[16]);
			wp = get_unaligned_le64(&rec[24]);
			rec[0] = type;
			rec[1] = (cond << 4) | non_seq | reset;
			put_unaligned_be64(size, &rec[8]);
			put_unaligned_be64(start, &rec[16]);
			put_unaligned_be64(wp, &rec[24]);
			WARN_ON(offset + 64 > miter.length);
			offset += 64;
			bytes += 64;
		}
	}
	sg_miter_stop(&miter);
	local_irq_restore(flags);

	ata_scsi_qc_complete(qc);
}

static unsigned int ata_scsi_zbc_in_xlat(struct ata_queued_cmd *qc)
{
	struct ata_taskfile *tf = &qc->tf;
	struct scsi_cmnd *scmd = qc->scsicmd;
	const u8 *cdb = scmd->cmnd;
	u16 sect, fp = (u16)-1;
	u8 sa, options, bp = 0xff;
	u64 block;
	u32 n_block;

	if (unlikely(scmd->cmd_len < 16)) {
		ata_dev_warn(qc->dev, "invalid cdb length %d\n",
			     scmd->cmd_len);
		fp = 15;
		goto invalid_fld;
	}
	scsi_16_lba_len(cdb, &block, &n_block);
	if (n_block != scsi_bufflen(scmd)) {
		ata_dev_warn(qc->dev, "non-matching transfer count (%d/%d)\n",
			     n_block, scsi_bufflen(scmd));
		goto invalid_param_len;
	}
	sa = cdb[1] & 0x1f;
	if (sa != ZI_REPORT_ZONES) {
		ata_dev_warn(qc->dev, "invalid service action %d\n", sa);
		fp = 1;
		goto invalid_fld;
	}
	/*
	 * ZAC allows only for transfers in 512 byte blocks,
	 * and uses a 16 bit value for the transfer count.
	 */
	if ((n_block / 512) > 0xffff || n_block < 512 || (n_block % 512)) {
		ata_dev_warn(qc->dev, "invalid transfer count %d\n", n_block);
		goto invalid_param_len;
	}
	sect = n_block / 512;
	options = cdb[14];

	if (ata_ncq_enabled(qc->dev) &&
	    ata_fpdma_zac_mgmt_in_supported(qc->dev)) {
		tf->protocol = ATA_PROT_NCQ;
		tf->command = ATA_CMD_FPDMA_RECV;
		tf->hob_nsect = ATA_SUBCMD_FPDMA_RECV_ZAC_MGMT_IN & 0x1f;
		tf->nsect = qc->tag << 3;
		tf->feature = sect & 0xff;
		tf->hob_feature = (sect >> 8) & 0xff;
		tf->auxiliary = ATA_SUBCMD_ZAC_MGMT_IN_REPORT_ZONES;
	} else {
		tf->command = ATA_CMD_ZAC_MGMT_IN;
		tf->feature = ATA_SUBCMD_ZAC_MGMT_IN_REPORT_ZONES;
		tf->protocol = ATA_PROT_DMA;
		tf->hob_feature = options;
		tf->hob_nsect = (sect >> 8) & 0xff;
		tf->nsect = sect & 0xff;
	}
	tf->device = ATA_LBA;
	tf->lbah = (block >> 16) & 0xff;
	tf->lbam = (block >> 8) & 0xff;
	tf->lbal = block & 0xff;
	tf->hob_lbah = (block >> 40) & 0xff;
	tf->hob_lbam = (block >> 32) & 0xff;
	tf->hob_lbal = (block >> 24) & 0xff;

	tf->flags |= ATA_TFLAG_ISADDR | ATA_TFLAG_DEVICE | ATA_TFLAG_LBA48;
	qc->flags |= ATA_QCFLAG_RESULT_TF;

	ata_qc_set_pc_nbytes(qc);

	qc->complete_fn = ata_scsi_report_zones_complete;

	return 0;

invalid_fld:
	ata_scsi_set_invalid_field(qc->dev, scmd, fp, bp);
	return 1;

invalid_param_len:
	/* "Parameter list length error" */
	ata_scsi_set_sense(qc->dev, scmd, ILLEGAL_REQUEST, 0x1a, 0x0);
	return 1;
}

/**
 *	ata_mselect_caching - Simulate MODE SELECT for caching info page
 *	@qc: Storage for translated ATA taskfile
@@ -3632,6 +3786,9 @@ static inline ata_xlat_func_t ata_get_xlat_func(struct ata_device *dev, u8 cmd)
		return ata_scsi_mode_select_xlat;
		break;

	case ZBC_IN:
		return ata_scsi_zbc_in_xlat;

	case START_STOP:
		return ata_scsi_start_stop_xlat;
	}
+10 −0
Original line number Diff line number Diff line
@@ -162,6 +162,9 @@ libata_trace_parse_subcmd(struct trace_seq *p, unsigned char cmd,
		case ATA_SUBCMD_FPDMA_RECV_RD_LOG_DMA_EXT:
			trace_seq_printf(p, " READ_LOG_DMA_EXT");
			break;
		case ATA_SUBCMD_FPDMA_RECV_ZAC_MGMT_IN:
			trace_seq_printf(p, " ZAC_MGMT_IN");
			break;
		}
		break;
	case ATA_CMD_FPDMA_SEND:
@@ -187,6 +190,13 @@ libata_trace_parse_subcmd(struct trace_seq *p, unsigned char cmd,
			break;
		}
		break;
	case ATA_CMD_ZAC_MGMT_IN:
		switch (feature) {
		case ATA_SUBCMD_ZAC_MGMT_IN_REPORT_ZONES:
			trace_seq_printf(p, " REPORT_ZONES");
			break;
		}
		break;
	}
	trace_seq_putc(p, 0);

+9 −1
Original line number Diff line number Diff line
@@ -302,12 +302,14 @@ enum {
	ATA_CMD_CFA_WRITE_MULT_NE = 0xCD,
	ATA_CMD_REQ_SENSE_DATA  = 0x0B,
	ATA_CMD_SANITIZE_DEVICE = 0xB4,
	ATA_CMD_ZAC_MGMT_IN	= 0x4A,

	/* marked obsolete in the ATA/ATAPI-7 spec */
	ATA_CMD_RESTORE		= 0x10,

	/* Subcmds for ATA_CMD_FPDMA_RECV */
	ATA_SUBCMD_FPDMA_RECV_RD_LOG_DMA_EXT = 0x01,
	ATA_SUBCMD_FPDMA_RECV_ZAC_MGMT_IN    = 0x02,

	/* Subcmds for ATA_CMD_FPDMA_SEND */
	ATA_SUBCMD_FPDMA_SEND_DSM            = 0x00,
@@ -318,6 +320,9 @@ enum {
	ATA_SUBCMD_NCQ_NON_DATA_SET_FEATURES = 0x05,
	ATA_SUBCMD_NCQ_NON_DATA_ZERO_EXT     = 0x06,

	/* Subcmds for ATA_CMD_ZAC_MGMT_IN */
	ATA_SUBCMD_ZAC_MGMT_IN_REPORT_ZONES = 0x00,

	/* READ_LOG_EXT pages */
	ATA_LOG_DIRECTORY	= 0x0,
	ATA_LOG_SATA_NCQ	= 0x10,
@@ -341,7 +346,10 @@ enum {
	ATA_LOG_NCQ_SEND_RECV_RD_LOG_SUPPORTED  = (1 << 0),
	ATA_LOG_NCQ_SEND_RECV_WR_LOG_OFFSET	= 0x0C,
	ATA_LOG_NCQ_SEND_RECV_WR_LOG_SUPPORTED  = (1 << 0),
	ATA_LOG_NCQ_SEND_RECV_SIZE		= 0x10,
	ATA_LOG_NCQ_SEND_RECV_ZAC_MGMT_OFFSET	= 0x10,
	ATA_LOG_NCQ_SEND_RECV_ZAC_MGMT_OUT_SUPPORTED = (1 << 0),
	ATA_LOG_NCQ_SEND_RECV_ZAC_MGMT_IN_SUPPORTED = (1 << 1),
	ATA_LOG_NCQ_SEND_RECV_SIZE		= 0x14,

	/* NCQ Non-Data log */
	ATA_LOG_NCQ_NON_DATA_SUBCMDS_OFFSET	= 0x00,
+7 −0
Original line number Diff line number Diff line
@@ -1651,6 +1651,13 @@ static inline bool ata_fpdma_read_log_supported(struct ata_device *dev)
		 ATA_LOG_NCQ_SEND_RECV_RD_LOG_SUPPORTED);
}

static inline bool ata_fpdma_zac_mgmt_in_supported(struct ata_device *dev)
{
	return (dev->flags & ATA_DFLAG_NCQ_SEND_RECV) &&
		(dev->ncq_send_recv_cmds[ATA_LOG_NCQ_SEND_RECV_ZAC_MGMT_OFFSET] &
		ATA_LOG_NCQ_SEND_RECV_ZAC_MGMT_IN_SUPPORTED);
}

static inline void ata_qc_set_polling(struct ata_queued_cmd *qc)
{
	qc->tf.ctl |= ATA_NIEN;
Loading