ata: libata-scsi: add support for the GET PHYSICAL ELEMENT STATUS command

Define the translation for the GET PHYSICAL ELEMENT STATUS command
(SERVICE ACTION IN command with service action
SAI_GET_PHYSICAL_ELEMENT_STATUS) into the ATA command
ATA_CMD_GET_PHYS_ELEMENT_STATUS with the new function
ata_scsi_get_phys_element_status_xlat(). The reply of this function also
needs translation from little endian to big endian. This is done with the
completion callback ata_scsi_get_phys_element_status_complete().

The array of supported commands ata_supported_cmds is modified to add a
new entry for this command. ata_scsi_cmd_is_supported() is also modified
to correctly handle this new entry depending on the target device flag
ATA_DFLAG_DEPOP being set.

Signed-off-by: Damien Le Moal <dlemoal@kernel.org>
Reviewed-by: Martin K. Petersen <martin.petersen@oracle.com>
Reviewed-by: Hannes Reinecke <hare@kernel.org>
This commit is contained in:
Damien Le Moal 2026-06-19 17:25:37 +09:00
parent 4ba5eb54c2
commit 0f0bfa1bca
2 changed files with 142 additions and 0 deletions

View File

@ -3724,6 +3724,11 @@ static const struct ata_scsi_cmd ata_supported_cmds[] = {
.sa_valid = true,
.sa = SAI_READ_CAPACITY_16
},
{
.op = SERVICE_ACTION_IN_16, .cdb_len = 16,
.sa_valid = true,
.sa = SAI_GET_PHYSICAL_ELEMENT_STATUS
},
{ .op = REPORT_LUNS, .cdb_len = 12 },
{ .op = ATA_12, .cdb_len = 12 },
{ .op = SECURITY_PROTOCOL_IN, .cdb_len = 12 },
@ -3804,6 +3809,14 @@ static bool ata_scsi_cmd_is_supported(struct ata_device *dev, u8 op, u16 sa,
case ZBC_IN:
case ZBC_OUT:
return ata_dev_is_zoned(dev);
case SERVICE_ACTION_IN_16:
switch (sa) {
case SAI_GET_PHYSICAL_ELEMENT_STATUS:
return dev->flags & ATA_DFLAG_DEPOP;
default:
return true;
}
break;
case SECURITY_PROTOCOL_IN:
case SECURITY_PROTOCOL_OUT:
return dev->flags & ATA_DFLAG_TRUSTED;
@ -4575,6 +4588,126 @@ static unsigned int ata_scsi_security_inout_xlat(struct ata_queued_cmd *qc)
return 0;
}
/*
* Convert T-13 little-endian field representation of GET PHYSICAL ELEMENT
* STATUS DMA command reply into T-10 big-endian field representation.
*/
static void ata_scsi_get_phys_element_status_complete(struct ata_queued_cmd *qc)
{
struct scsi_cmnd *scmd = qc->scsicmd;
struct sg_mapping_iter miter;
unsigned int bytes = 0;
lockdep_assert_held(qc->ap->lock);
sg_miter_start(&miter, scsi_sglist(scmd), scsi_sg_count(scmd),
SG_MITER_TO_SG | SG_MITER_ATOMIC);
while (sg_miter_next(&miter)) {
unsigned int offset = 0;
if (bytes == 0) {
u32 num_desc, num_desc_returned, id;
u16 max_depop, cur_depop;
char *hdr;
/* Swizzle the header */
hdr = miter.addr;
num_desc = get_unaligned_le32(&hdr[0]);
num_desc_returned = get_unaligned_le32(&hdr[4]);
id = get_unaligned_le32(&hdr[8]);
max_depop = get_unaligned_le16(&hdr[12]);
cur_depop = get_unaligned_le16(&hdr[14]);
put_unaligned_be32(num_desc, &hdr[0]);
put_unaligned_be32(num_desc_returned, &hdr[4]);
put_unaligned_be32(id, &hdr[8]);
put_unaligned_be16(max_depop, &hdr[12]);
put_unaligned_be16(cur_depop, &hdr[14]);
offset += 32;
bytes += 32;
}
/* Swizzle the descriptors. */
while (offset < miter.length) {
char *desc;
u32 id;
u8 type;
desc = miter.addr + offset;
id = get_unaligned_le32(&desc[4]);
put_unaligned_be32(id, &desc[4]);
type = desc[14];
if (type == SCSI_PHYS_ELEM_TYPE_ALL_ACCESS_STORAGE) {
u64 capacity = get_unaligned_le64(&desc[16]);
put_unaligned_be64(capacity, &desc[16]);
} else {
u64 num_zones;
id = get_unaligned_le32(&desc[16]);
num_zones = get_unaligned_le64(&desc[24]);
put_unaligned_be32(id, &desc[16]);
put_unaligned_be64(num_zones, &desc[24]);
}
offset += 32;
bytes += 32;
}
}
sg_miter_stop(&miter);
ata_scsi_qc_complete(qc);
}
static unsigned int
ata_scsi_get_phys_element_status_xlat(struct ata_queued_cmd *qc)
{
struct scsi_cmnd *scmd = qc->scsicmd;
const u8 *cdb = scmd->cmnd;
struct ata_device *dev = qc->dev;
struct ata_taskfile *tf = &qc->tf;
u32 starting_element, len;
/* ATA_CMD_GET_PHYS_ELEMENT_STATUS is a DMA command. */
if (!(dev->flags & ATA_DFLAG_DEPOP) || !ata_dma_enabled(dev)) {
ata_scsi_set_sense(dev, scmd, ILLEGAL_REQUEST, 0x20, 0x0);
return 1;
}
len = get_unaligned_be32(&cdb[10]) / ATA_SECT_SIZE;
if (!len || len > U16_MAX) {
ata_scsi_set_invalid_field(dev, scmd, 10, 0);
return 1;
}
tf->protocol = ATA_PROT_DMA;
tf->command = ATA_CMD_GET_PHYS_ELEMENT_STATUS;
tf->hob_feature = cdb[14];
tf->hob_nsect = (len >> 8) & 0xff;
tf->nsect = len & 0xff;
starting_element = get_unaligned_be32(&cdb[6]);
if (starting_element) {
tf->hob_lbal = (starting_element >> 24) & 0xff;
tf->lbah = (starting_element >> 16) & 0xff;
tf->lbam = (starting_element >> 8) & 0xff;
tf->lbal = starting_element & 0xff;
}
tf->device = ATA_LBA;
tf->flags |= ATA_TFLAG_ISADDR | ATA_TFLAG_DEVICE | ATA_TFLAG_LBA48;
ata_qc_set_pc_nbytes(qc);
qc->flags |= ATA_QCFLAG_RESULT_TF;
qc->complete_fn = ata_scsi_get_phys_element_status_complete;
return 0;
}
/**
* ata_scsi_var_len_cdb_xlat - SATL variable length CDB to Handler
* @qc: Command to be translated
@ -4618,6 +4751,8 @@ static unsigned int ata_scsi_var_len_cdb_xlat(struct ata_queued_cmd *qc)
static inline ata_xlat_func_t ata_get_xlat_func(struct ata_device *dev,
u8 *cdb)
{
u8 sa;
switch (cdb[0]) {
case READ_6:
case READ_10:
@ -4652,6 +4787,12 @@ static inline ata_xlat_func_t ata_get_xlat_func(struct ata_device *dev,
case MODE_SELECT_10:
return ata_scsi_mode_select_xlat;
case SERVICE_ACTION_IN_16:
sa = cdb[1] & 0x1f;
if (sa == SAI_GET_PHYSICAL_ELEMENT_STATUS)
return ata_scsi_get_phys_element_status_xlat;
break;
case ZBC_IN:
return ata_scsi_zbc_in_xlat;

View File

@ -289,6 +289,7 @@ enum {
ATA_CMD_SANITIZE_DEVICE = 0xB4,
ATA_CMD_ZAC_MGMT_IN = 0x4A,
ATA_CMD_ZAC_MGMT_OUT = 0x9F,
ATA_CMD_GET_PHYS_ELEMENT_STATUS = 0x12,
/* marked obsolete in the ATA/ATAPI-7 spec */
ATA_CMD_RESTORE = 0x10,