target: Refactor MODE SENSE emulation

Convert spc_emulate_modesense() to use a table of mode pages, rather
than a switch statement.  This makes it possible to add more pages
sanely -- in particular we no longer need to make sure we keep the
0x3f (return all mode pages) case in sync.

While we're touching this code, make our MODE SENSE emulation a bit
better in a couple of ways:
 - When the initiator passes PC == 1 asking for changeable values,
   return all 0s to show we don't support setting anything.
 - Return a block descriptor for disk devices.

(nab: fix up device attribute references to use dev->dev_attrib
      in for-next code)

Signed-off-by: Roland Dreier <roland@purestorage.com>
Signed-off-by: Nicholas Bellinger <nab@linux-iscsi.org>
This commit is contained in:
Roland Dreier 2012-10-31 09:16:48 -07:00 committed by Nicholas Bellinger
parent 1f981de55a
commit d4b2b86719

View File

@ -639,18 +639,28 @@ out:
return ret;
}
static int spc_modesense_rwrecovery(unsigned char *p)
static int spc_modesense_rwrecovery(struct se_device *dev, u8 pc, u8 *p)
{
p[0] = 0x01;
p[1] = 0x0a;
/* No changeable values for now */
if (pc == 1)
goto out;
out:
return 12;
}
static int spc_modesense_control(struct se_device *dev, unsigned char *p)
static int spc_modesense_control(struct se_device *dev, u8 pc, u8 *p)
{
p[0] = 0x0a;
p[1] = 0x0a;
/* No changeable values for now */
if (pc == 1)
goto out;
p[2] = 2;
/*
* From spc4r23, 7.4.7 Control mode page
@ -729,20 +739,37 @@ static int spc_modesense_control(struct se_device *dev, unsigned char *p)
p[9] = 0xff;
p[11] = 30;
out:
return 12;
}
static int spc_modesense_caching(struct se_device *dev, unsigned char *p)
static int spc_modesense_caching(struct se_device *dev, u8 pc, u8 *p)
{
p[0] = 0x08;
p[1] = 0x12;
/* No changeable values for now */
if (pc == 1)
goto out;
if (dev->dev_attrib.emulate_write_cache > 0)
p[2] = 0x04; /* Write Cache Enable */
p[12] = 0x20; /* Disabled Read Ahead */
out:
return 20;
}
static struct {
uint8_t page;
uint8_t subpage;
int (*emulate)(struct se_device *, u8, unsigned char *);
} modesense_handlers[] = {
{ .page = 0x01, .subpage = 0x00, .emulate = spc_modesense_rwrecovery },
{ .page = 0x08, .subpage = 0x00, .emulate = spc_modesense_caching },
{ .page = 0x0a, .subpage = 0x00, .emulate = spc_modesense_control },
};
static void spc_modesense_write_protect(unsigned char *buf, int type)
{
/*
@ -769,77 +796,167 @@ static void spc_modesense_dpofua(unsigned char *buf, int type)
}
}
static int spc_modesense_blockdesc(unsigned char *buf, u64 blocks, u32 block_size)
{
*buf++ = 8;
put_unaligned_be32(min(blocks, 0xffffffffull), buf);
buf += 4;
put_unaligned_be32(block_size, buf);
return 9;
}
static int spc_modesense_long_blockdesc(unsigned char *buf, u64 blocks, u32 block_size)
{
if (blocks <= 0xffffffff)
return spc_modesense_blockdesc(buf + 3, blocks, block_size) + 3;
*buf++ = 1; /* LONGLBA */
buf += 2;
*buf++ = 16;
put_unaligned_be64(blocks, buf);
buf += 12;
put_unaligned_be32(block_size, buf);
return 17;
}
static int spc_emulate_modesense(struct se_cmd *cmd)
{
struct se_device *dev = cmd->se_dev;
char *cdb = cmd->t_task_cdb;
unsigned char *rbuf;
unsigned char *buf, *map_buf;
int type = dev->transport->get_device_type(dev);
int ten = (cmd->t_task_cdb[0] == MODE_SENSE_10);
u32 offset = ten ? 8 : 4;
bool dbd = !!(cdb[1] & 0x08);
bool llba = ten ? !!(cdb[1] & 0x10) : false;
u8 pc = cdb[2] >> 6;
u8 page = cdb[2] & 0x3f;
u8 subpage = cdb[3];
int length = 0;
unsigned char buf[SE_MODE_PAGE_BUF];
int ret;
int i;
memset(buf, 0, SE_MODE_PAGE_BUF);
map_buf = transport_kmap_data_sg(cmd);
switch (cdb[2] & 0x3f) {
case 0x01:
length = spc_modesense_rwrecovery(&buf[offset]);
break;
case 0x08:
length = spc_modesense_caching(dev, &buf[offset]);
break;
case 0x0a:
length = spc_modesense_control(dev, &buf[offset]);
break;
case 0x3f:
length = spc_modesense_rwrecovery(&buf[offset]);
length += spc_modesense_caching(dev, &buf[offset+length]);
length += spc_modesense_control(dev, &buf[offset+length]);
break;
default:
pr_err("MODE SENSE: unimplemented page/subpage: 0x%02x/0x%02x\n",
cdb[2] & 0x3f, cdb[3]);
cmd->scsi_sense_reason = TCM_UNKNOWN_MODE_PAGE;
return -EINVAL;
}
offset += length;
if (ten) {
offset -= 2;
buf[0] = (offset >> 8) & 0xff;
buf[1] = offset & 0xff;
offset += 2;
if ((cmd->se_lun->lun_access & TRANSPORT_LUNFLAGS_READ_ONLY) ||
(cmd->se_deve &&
(cmd->se_deve->lun_flags & TRANSPORT_LUNFLAGS_READ_ONLY)))
spc_modesense_write_protect(&buf[3], type);
if ((dev->dev_attrib.emulate_write_cache > 0) &&
(dev->dev_attrib.emulate_fua_write > 0))
spc_modesense_dpofua(&buf[3], type);
/*
* If SCF_PASSTHROUGH_SG_TO_MEM_NOALLOC is not set, then we
* know we actually allocated a full page. Otherwise, if the
* data buffer is too small, allocate a temporary buffer so we
* don't have to worry about overruns in all our INQUIRY
* emulation handling.
*/
if (cmd->data_length < SE_MODE_PAGE_BUF &&
(cmd->se_cmd_flags & SCF_PASSTHROUGH_SG_TO_MEM_NOALLOC)) {
buf = kzalloc(SE_MODE_PAGE_BUF, GFP_KERNEL);
if (!buf) {
transport_kunmap_data_sg(cmd);
cmd->scsi_sense_reason = TCM_LOGICAL_UNIT_COMMUNICATION_FAILURE;
return -ENOMEM;
}
} else {
offset -= 1;
buf[0] = offset & 0xff;
offset += 1;
if ((cmd->se_lun->lun_access & TRANSPORT_LUNFLAGS_READ_ONLY) ||
(cmd->se_deve &&
(cmd->se_deve->lun_flags & TRANSPORT_LUNFLAGS_READ_ONLY)))
spc_modesense_write_protect(&buf[2], type);
if ((dev->dev_attrib.emulate_write_cache > 0) &&
(dev->dev_attrib.emulate_fua_write > 0))
spc_modesense_dpofua(&buf[2], type);
buf = map_buf;
}
rbuf = transport_kmap_data_sg(cmd);
if (rbuf) {
memcpy(rbuf, buf, min(offset, cmd->data_length));
transport_kunmap_data_sg(cmd);
length = ten ? 2 : 1;
/* DEVICE-SPECIFIC PARAMETER */
if ((cmd->se_lun->lun_access & TRANSPORT_LUNFLAGS_READ_ONLY) ||
(cmd->se_deve &&
(cmd->se_deve->lun_flags & TRANSPORT_LUNFLAGS_READ_ONLY)))
spc_modesense_write_protect(&buf[length], type);
if ((dev->dev_attrib.emulate_write_cache > 0) &&
(dev->dev_attrib.emulate_fua_write > 0))
spc_modesense_dpofua(&buf[length], type);
++length;
/* BLOCK DESCRIPTOR */
/*
* For now we only include a block descriptor for disk (SBC)
* devices; other command sets use a slightly different format.
*/
if (!dbd && type == TYPE_DISK) {
u64 blocks = dev->transport->get_blocks(dev);
u32 block_size = dev->dev_attrib.block_size;
if (ten) {
if (llba) {
length += spc_modesense_long_blockdesc(&buf[length],
blocks, block_size);
} else {
length += 3;
length += spc_modesense_blockdesc(&buf[length],
blocks, block_size);
}
} else {
length += spc_modesense_blockdesc(&buf[length], blocks,
block_size);
}
} else {
if (ten)
length += 4;
else
length += 1;
}
if (page == 0x3f) {
if (subpage != 0x00 && subpage != 0xff) {
cmd->scsi_sense_reason = TCM_INVALID_CDB_FIELD;
length = -EINVAL;
goto out;
}
for (i = 0; i < ARRAY_SIZE(modesense_handlers); ++i) {
/*
* Tricky way to say all subpage 00h for
* subpage==0, all subpages for subpage==0xff
* (and we just checked above that those are
* the only two possibilities).
*/
if ((modesense_handlers[i].subpage & ~subpage) == 0) {
ret = modesense_handlers[i].emulate(dev, pc, &buf[length]);
if (!ten && length + ret >= 255)
break;
length += ret;
}
}
goto set_length;
}
for (i = 0; i < ARRAY_SIZE(modesense_handlers); ++i)
if (modesense_handlers[i].page == page &&
modesense_handlers[i].subpage == subpage) {
length += modesense_handlers[i].emulate(dev, pc, &buf[length]);
goto set_length;
}
/*
* We don't intend to implement:
* - obsolete page 03h "format parameters" (checked by Solaris)
*/
if (page != 0x03)
pr_err("MODE SENSE: unimplemented page/subpage: 0x%02x/0x%02x\n",
page, subpage);
cmd->scsi_sense_reason = TCM_UNKNOWN_MODE_PAGE;
return -EINVAL;
set_length:
if (ten)
put_unaligned_be16(length - 2, buf);
else
buf[0] = length - 1;
out:
if (buf != map_buf) {
memcpy(map_buf, buf, cmd->data_length);
kfree(buf);
}
transport_kunmap_data_sg(cmd);
target_complete_cmd(cmd, GOOD);
return 0;
}