diff options
author | Roland Dreier <roland@purestorage.com> | 2012-10-31 20:16:48 +0400 |
---|---|---|
committer | Nicholas Bellinger <nab@linux-iscsi.org> | 2012-11-07 08:55:46 +0400 |
commit | d4b2b867193c157f0ac8c10137e287a90ed4e5d5 (patch) | |
tree | f1bd32ea59f0b69ac1d1c028912d42514196d013 /drivers/target | |
parent | 1f981de55a46777ca0da93af5faf7d3f6e7e7000 (diff) | |
download | linux-d4b2b867193c157f0ac8c10137e287a90ed4e5d5.tar.xz |
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>
Diffstat (limited to 'drivers/target')
-rw-r--r-- | drivers/target/target_core_spc.c | 235 |
1 files changed, 176 insertions, 59 deletions
diff --git a/drivers/target/target_core_spc.c b/drivers/target/target_core_spc.c index 71d440f0573e..8ccfe00c51d7 100644 --- a/drivers/target/target_core_spc.c +++ b/drivers/target/target_core_spc.c @@ -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; + /* + * 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 { + buf = map_buf; } - 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); + + 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 { - 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); + if (ten) + length += 4; + else + length += 1; } - rbuf = transport_kmap_data_sg(cmd); - if (rbuf) { - memcpy(rbuf, buf, min(offset, cmd->data_length)); - transport_kunmap_data_sg(cmd); + 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; } |