[libata] scsi: implement MODE SELECT command

The cache_type file in sysfs lets users configure the disk cache in
write-through or write-back modes.  However, ata disks do not support
writing to the file because they do not implement the MODE SELECT
command.

This patch adds a translation from MODE SELECT (for the caching page
only) to the ATA SET FEATURES command.  The set of changeable parameters
answered by MODE SENSE is also adjusted accordingly.

Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
Signed-off-by: Jeff Garzik <jgarzik@redhat.com>
This commit is contained in:
Paolo Bonzini 2012-07-05 14:18:21 +02:00 committed by Jeff Garzik
parent 6ca8e79466
commit 1b26d29ccd

View File

@ -2243,7 +2243,7 @@ static void modecpy(u8 *dest, const u8 *src, int n, bool changeable)
static unsigned int ata_msense_caching(u16 *id, u8 *buf, bool changeable) static unsigned int ata_msense_caching(u16 *id, u8 *buf, bool changeable)
{ {
modecpy(buf, def_cache_mpage, sizeof(def_cache_mpage), changeable); modecpy(buf, def_cache_mpage, sizeof(def_cache_mpage), changeable);
if (!changeable && ata_id_wcache_enabled(id)) if (changeable || ata_id_wcache_enabled(id))
buf[2] |= (1 << 2); /* write cache enable */ buf[2] |= (1 << 2); /* write cache enable */
if (!changeable && !ata_id_rahead_enabled(id)) if (!changeable && !ata_id_rahead_enabled(id))
buf[12] |= (1 << 5); /* disable read ahead */ buf[12] |= (1 << 5); /* disable read ahead */
@ -3106,6 +3106,188 @@ static unsigned int ata_scsi_write_same_xlat(struct ata_queued_cmd *qc)
return 1; return 1;
} }
/**
* ata_mselect_caching - Simulate MODE SELECT for caching info page
* @qc: Storage for translated ATA taskfile
* @buf: input buffer
* @len: number of valid bytes in the input buffer
*
* Prepare a taskfile to modify caching information for the device.
*
* LOCKING:
* None.
*/
static int ata_mselect_caching(struct ata_queued_cmd *qc,
const u8 *buf, int len)
{
struct ata_taskfile *tf = &qc->tf;
struct ata_device *dev = qc->dev;
char mpage[CACHE_MPAGE_LEN];
u8 wce;
/*
* The first two bytes of def_cache_mpage are a header, so offsets
* in mpage are off by 2 compared to buf. Same for len.
*/
if (len != CACHE_MPAGE_LEN - 2)
return -EINVAL;
wce = buf[0] & (1 << 2);
/*
* Check that read-only bits are not modified.
*/
ata_msense_caching(dev->id, mpage, false);
mpage[2] &= ~(1 << 2);
mpage[2] |= wce;
if (memcmp(mpage + 2, buf, CACHE_MPAGE_LEN - 2) != 0)
return -EINVAL;
tf->flags |= ATA_TFLAG_DEVICE | ATA_TFLAG_ISADDR;
tf->protocol = ATA_PROT_NODATA;
tf->nsect = 0;
tf->command = ATA_CMD_SET_FEATURES;
tf->feature = wce ? SETFEATURES_WC_ON : SETFEATURES_WC_OFF;
return 0;
}
/**
* ata_scsiop_mode_select - Simulate MODE SELECT 6, 10 commands
* @qc: Storage for translated ATA taskfile
*
* Converts a MODE SELECT command to an ATA SET FEATURES taskfile.
* Assume this is invoked for direct access devices (e.g. disks) only.
* There should be no block descriptor for other device types.
*
* LOCKING:
* spin_lock_irqsave(host lock)
*/
static unsigned int ata_scsi_mode_select_xlat(struct ata_queued_cmd *qc)
{
struct scsi_cmnd *scmd = qc->scsicmd;
const u8 *cdb = scmd->cmnd;
const u8 *p;
u8 pg, spg;
unsigned six_byte, pg_len, hdr_len, bd_len;
int len;
VPRINTK("ENTER\n");
six_byte = (cdb[0] == MODE_SELECT);
if (six_byte) {
if (scmd->cmd_len < 5)
goto invalid_fld;
len = cdb[4];
hdr_len = 4;
} else {
if (scmd->cmd_len < 9)
goto invalid_fld;
len = (cdb[7] << 8) + cdb[8];
hdr_len = 8;
}
/* We only support PF=1, SP=0. */
if ((cdb[1] & 0x11) != 0x10)
goto invalid_fld;
/* Test early for possible overrun. */
if (!scsi_sg_count(scmd) || scsi_sglist(scmd)->length < len)
goto invalid_param_len;
p = page_address(sg_page(scsi_sglist(scmd)));
/* Move past header and block descriptors. */
if (len < hdr_len)
goto invalid_param_len;
if (six_byte)
bd_len = p[3];
else
bd_len = (p[6] << 8) + p[7];
len -= hdr_len;
p += hdr_len;
if (len < bd_len)
goto invalid_param_len;
if (bd_len != 0 && bd_len != 8)
goto invalid_param;
len -= bd_len;
p += bd_len;
if (len == 0)
goto skip;
/* Parse both possible formats for the mode page headers. */
pg = p[0] & 0x3f;
if (p[0] & 0x40) {
if (len < 4)
goto invalid_param_len;
spg = p[1];
pg_len = (p[2] << 8) | p[3];
p += 4;
len -= 4;
} else {
if (len < 2)
goto invalid_param_len;
spg = 0;
pg_len = p[1];
p += 2;
len -= 2;
}
/*
* No mode subpages supported (yet) but asking for _all_
* subpages may be valid
*/
if (spg && (spg != ALL_SUB_MPAGES))
goto invalid_param;
if (pg_len > len)
goto invalid_param_len;
switch (pg) {
case CACHE_MPAGE:
if (ata_mselect_caching(qc, p, pg_len) < 0)
goto invalid_param;
break;
default: /* invalid page code */
goto invalid_param;
}
/*
* Only one page has changeable data, so we only support setting one
* page at a time.
*/
if (len > pg_len)
goto invalid_param;
return 0;
invalid_fld:
/* "Invalid field in CDB" */
ata_scsi_set_sense(scmd, ILLEGAL_REQUEST, 0x24, 0x0);
return 1;
invalid_param:
/* "Invalid field in parameter list" */
ata_scsi_set_sense(scmd, ILLEGAL_REQUEST, 0x26, 0x0);
return 1;
invalid_param_len:
/* "Parameter list length error" */
ata_scsi_set_sense(scmd, ILLEGAL_REQUEST, 0x1a, 0x0);
return 1;
skip:
scmd->result = SAM_STAT_GOOD;
return 1;
}
/** /**
* ata_get_xlat_func - check if SCSI to ATA translation is possible * ata_get_xlat_func - check if SCSI to ATA translation is possible
* @dev: ATA device * @dev: ATA device
@ -3146,6 +3328,11 @@ static inline ata_xlat_func_t ata_get_xlat_func(struct ata_device *dev, u8 cmd)
case ATA_16: case ATA_16:
return ata_scsi_pass_thru; return ata_scsi_pass_thru;
case MODE_SELECT:
case MODE_SELECT_10:
return ata_scsi_mode_select_xlat;
break;
case START_STOP: case START_STOP:
return ata_scsi_start_stop_xlat; return ata_scsi_start_stop_xlat;
} }
@ -3338,11 +3525,6 @@ void ata_scsi_simulate(struct ata_device *dev, struct scsi_cmnd *cmd)
ata_scsi_rbuf_fill(&args, ata_scsiop_mode_sense); ata_scsi_rbuf_fill(&args, ata_scsiop_mode_sense);
break; break;
case MODE_SELECT: /* unconditionally return */
case MODE_SELECT_10: /* bad-field-in-cdb */
ata_scsi_invalid_field(cmd);
break;
case READ_CAPACITY: case READ_CAPACITY:
ata_scsi_rbuf_fill(&args, ata_scsiop_read_cap); ata_scsi_rbuf_fill(&args, ata_scsiop_read_cap);
break; break;