Re: [PATCH v3 2/5] ata: libata-scsi: refactor ata_scsi_report_supported_opcodes()

Hannes Reinecke <[email protected]>
Newsgroups org.kernel.vger.linux-ide
Message-ID <[email protected]>
On 7/3/26 6:05 AM, Damien Le Moal wrote:
> ata_scsi_report_supported_opcodes() is very limited in functionality as it
> lacks support for the all command format and also does not handle
> correctly commands that have a service action.
> 
> In preparation for adding these missing features, refactor how
> ata_scsi_report_supported_opcodes() operates to make modifications and
> extensions easier. To do so, introduce the array of supported commands
> ata_supported_cmds. This array entries are of type struct ata_scsi_cmd.
> This structure stores the operation code, CDB length, and the service
> action of a supported SCSI command that libata SAT can translate or
> emulate. Since some service actions (e.g. ZI_REPORT_ZONES) can have a
> value of 0, the field sa_valid of struct ata_scsi_cmd is used to indicate
> if the sa field is valid, or if it should be ignored.
> 
> The helper function ata_scsi_get_supported_cmd() is implemented to search
> for a particular command by opcode in this array. This function is used in
> ata_scsi_cmd_is_supported() together with a struct ata_scsi_cmd_support to
> check based on the target device features if the specified command is
> supported.
> 
> ata_scsi_cmd_is_supported() is used as the main function in
> ata_scsi_report_supported_opcodes() to determine if a particular command
> is supported and fill the command reply rbuf as needed. In the case of a
> command that is not supported, the support field is set to 1 as specified
> in SPC, indicating that the command is not supported.
> 
> Of note is that the old ata_scsi_report_supported_opcodes() code did not
> handle the VARIABLE_LENGTH_CMD/ATA_32 command which is supported and
> translated by libata-scsi. The ata_supported_cmds array includes this
> command. As commented in the code, WRITE_SAME_16 is left out of the array
> of supported command ata_scsi_write_same_xlat() prevents the use of this
> command as a passthrough command.
> 
> Signed-off-by: Damien Le Moal <[email protected]>
> ---
>   drivers/ata/libata-scsi.c | 201 ++++++++++++++++++++++++++++----------
>   1 file changed, 147 insertions(+), 54 deletions(-)
> 
> diff --git a/drivers/ata/libata-scsi.c b/drivers/ata/libata-scsi.c
> index a86721b5df7f..e502b8702f98 100644
> --- a/drivers/ata/libata-scsi.c
> +++ b/drivers/ata/libata-scsi.c
> @@ -3590,87 +3590,180 @@ static unsigned int ata_scsi_write_same_xlat(struct ata_queued_cmd *qc)
>   	return 1;
>   }
>   
> -static unsigned int ata_scsi_report_supported_opcodes(struct ata_device *dev,
> -						      struct scsi_cmnd *cmd,
> -						      u8 *rbuf)
> +struct ata_scsi_cmd {
> +	u8 op;
> +	u8 cdb_len;
> +	bool sa_valid;
> +	u16 sa;
> +};
> +
> +/*
> + * Array of commands supported with translation or emulation, sorted in
> + * ascending opcode and service action order. All of these commands are
> + * processed either in ata_xlat_func() or in ata_scsi_simulate();
> + *
> + * Note: commands that are not fully supported may be left out of this array
> + * so that they are not reported as supported for passthrough but still
> + * available through the block layer. For now, this includes the following
> + * commands:
> + *  - WRITE_SAME_16: ata_scsi_write_same_xlat() forbids passthrough commands
> + */
> +static const struct ata_scsi_cmd ata_supported_cmds[] = {
> +	{	.op = TEST_UNIT_READY,		.cdb_len = 6	},
> +	{	.op = REZERO_UNIT,		.cdb_len = 6	},
> +	{	.op = REQUEST_SENSE,		.cdb_len = 6	},
> +	{	.op = READ_6,			.cdb_len = 6	},
> +	{	.op = WRITE_6,			.cdb_len = 6	},
> +	{	.op = SEEK_6,			.cdb_len = 6	},
> +	{	.op = INQUIRY,			.cdb_len = 6	},
> +	{	.op = MODE_SELECT,		.cdb_len = 6	},
> +	{	.op = MODE_SENSE,		.cdb_len = 6	},
> +	{	.op = START_STOP,		.cdb_len = 6	},
> +	{	.op = SEND_DIAGNOSTIC,		.cdb_len = 6	},
> +	{	.op = READ_CAPACITY,		.cdb_len = 10	},
> +	{	.op = READ_10,			.cdb_len = 10	},
> +	{	.op = WRITE_10,			.cdb_len = 10	},
> +	{	.op = SEEK_10,			.cdb_len = 10	},
> +	{	.op = VERIFY,			.cdb_len = 10	},
> +	{	.op = SYNCHRONIZE_CACHE,	.cdb_len = 10	},
> +	{	.op = MODE_SELECT_10,		.cdb_len = 10	},
> +	{	.op = MODE_SENSE_10,		.cdb_len = 10	},
> +	{
> +		.op = VARIABLE_LENGTH_CMD,	.cdb_len = 32,
> +		.sa_valid = true,
> +		.sa = ATA_32
> +	},
> +	{	.op = ATA_16,			.cdb_len = 16	},
> +	{	.op = READ_16,			.cdb_len = 16	},
> +	{	.op = WRITE_16,			.cdb_len = 16	},
> +	{	.op = VERIFY_16,		.cdb_len = 16	},
> +	{	.op = SYNCHRONIZE_CACHE_16,	.cdb_len = 16	},
> +	{
> +		.op = ZBC_OUT,			.cdb_len = 16,
> +		.sa_valid = true,
> +		.sa = ZO_CLOSE_ZONE
> +	},
> +	{
> +		.op = ZBC_OUT,			.cdb_len = 16,
> +		.sa_valid = true,
> +		.sa = ZO_FINISH_ZONE
> +	},
> +	{
> +		.op = ZBC_OUT,			.cdb_len = 16,
> +		.sa_valid = true,
> +		.sa = ZO_OPEN_ZONE
> +	},
> +	{
> +		.op = ZBC_OUT,			.cdb_len = 16,
> +		.sa_valid = true,
> +		.sa = ZO_RESET_WRITE_POINTER
> +	},
> +	{
> +		.op = ZBC_IN,			.cdb_len = 16,
> +		.sa_valid = true,
> +		.sa = ZI_REPORT_ZONES
> +	},
> +	{
> +		.op = SERVICE_ACTION_IN_16,	.cdb_len = 16,
> +		.sa_valid = true,
> +		.sa = SAI_READ_CAPACITY_16
> +	},
> +	{	.op = REPORT_LUNS,		.cdb_len = 12	},
> +	{	.op = ATA_12,			.cdb_len = 12	},
> +	{	.op = SECURITY_PROTOCOL_IN,	.cdb_len = 12	},
> +	{
> +		.op = MAINTENANCE_IN,		.cdb_len = 12,
> +		.sa_valid = true,
> +		.sa = MI_REPORT_SUPPORTED_OPERATION_CODES
> +	},
> +	{	.op = SECURITY_PROTOCOL_OUT,	.cdb_len = 12	},
> +};
> +

What an unusual formatting. I would have gone for multi-line
entries even for the simple ones to keep formatting the same
for all entries.
But hey.

> +static const struct ata_scsi_cmd *ata_scsi_get_supported_cmd(u8 op)
>   {
> -	u8 *cdb = cmd->cmnd;
> -	u8 supported = 0, cdlp = 0, rwcdlp = 0;
> +	const struct ata_scsi_cmd *cmd;
> +	int i;
>   
> -	if (cdb[2] != 1 && cdb[2] != 3) {
> -		ata_dev_warn(dev, "invalid command format %d\n", cdb[2]);
> -		ata_scsi_set_invalid_field(dev, cmd, 2, 0xff);
> -		return 0;
> +	for (i = 0; i < ARRAY_SIZE(ata_supported_cmds); i++) {
> +		cmd = &ata_supported_cmds[i];
> +		if (cmd->op == op)
> +			return cmd;
>   	}
>   
> -	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 SYNCHRONIZE_CACHE_16:
> -	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 WRITE_6:
> -	case WRITE_10:
> -	case ATA_12:
> -	case ATA_16:
> -	case VERIFY:
> -	case VERIFY_16:
> -	case MODE_SELECT:
> -	case MODE_SELECT_10:
> -	case START_STOP:
> -		supported = 3;
> -		break;
> +	return NULL;
> +}
> +
> +struct ata_scsi_cmd_support {
> +	u8 cdlp;
> +	u8 rwcdlp;
> +};
> +
> +static bool ata_scsi_cmd_is_supported(struct ata_device *dev, u8 op,
> +				      struct ata_scsi_cmd_support *sup)
> +{
> +	const struct ata_scsi_cmd *cmd;
> +
> +	/* First, see if we support the command. */
> +	cmd = ata_scsi_get_supported_cmd(op);
> +	if (!cmd)
> +		return false;
> +
> +	/* Now refine the support report depending on the device features. */
> +	memset(sup, 0, sizeof(*sup));
> +	switch (op) {
>   	case READ_16:
> -		supported = 3;
>   		if (dev->flags & ATA_DFLAG_CDL) {
>   			/*
>   			 * CDL read descriptors map to the T2A page, that is,
>   			 * rwcdlp = 0x01 and cdlp = 0x01
>   			 */
> -			rwcdlp = 0x01;
> -			cdlp = 0x01 << 3;
> +			sup->rwcdlp = 0x01;
> +			sup->cdlp = 0x01;
>   		}
> -		break;
> +		return true;

That is confusing. You return 'true' here, yet once you break out
of the switch the function will return 'true', too.
Please stay with the 'break' here.

>   	case WRITE_16:
> -		supported = 3;
>   		if (dev->flags & ATA_DFLAG_CDL) {
>   			/*
>   			 * CDL write descriptors map to the T2B page, that is,
>   			 * rwcdlp = 0x01 and cdlp = 0x02
>   			 */
> -			rwcdlp = 0x01;
> -			cdlp = 0x02 << 3;
> +			sup->rwcdlp = 0x01;
> +			sup->cdlp = 0x02;
>   		}
> -		break;
> +		return true;

Same here.

>   	case ZBC_IN:
>   	case ZBC_OUT:
> -		if (ata_dev_is_zoned(dev))
> -			supported = 3;
> -		break;
> +		return ata_dev_is_zoned(dev);
>   	case SECURITY_PROTOCOL_IN:
>   	case SECURITY_PROTOCOL_OUT:
> -		if (dev->flags & ATA_DFLAG_TRUSTED)
> -			supported = 3;
> -		break;
> +		return dev->flags & ATA_DFLAG_TRUSTED;
>   	default:
>   		break;
>   	}
>   
> +	return true;
> +}
> +
> +static unsigned int ata_scsi_report_supported_opcodes(struct ata_device *dev,
> +						      struct scsi_cmnd *cmd,
> +						      u8 *rbuf)
> +{
> +	struct ata_scsi_cmd_support sup;
> +	u8 *cdb = cmd->cmnd;
> +
> +	if (cdb[2] != 1 && cdb[2] != 3) {
> +		ata_dev_warn(dev, "invalid command format %d\n", cdb[2]);
> +		ata_scsi_set_invalid_field(dev, cmd, 2, 0xff);
> +		return 0;
> +	}
> +
>   	/* One command format */
> -	rbuf[0] = rwcdlp;
> -	rbuf[1] = cdlp | supported;
> +	if (ata_scsi_cmd_is_supported(dev, cdb[3], &sup)) {
> +		rbuf[0] = sup.rwcdlp;
> +		rbuf[1] = (sup.cdlp << 3) | 0x03;
> +	} else {
> +		rbuf[1] = 0x01;
> +	}
>   
>   	return 4;
>   }

Cheers,

Hannes
-- 
Dr. Hannes Reinecke                  Kernel Storage Architect
[email protected]                                +49 911 74053 688
SUSE Software Solutions GmbH, Frankenstr. 146, 90461 Nürnberg
HRB 36809 (AG Nürnberg), GF: I. Totev, A. McDonald, W. Knoblich
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.