blob: c42c5d6c4e4dcf871ca17906be52ee31852fba76 [file] [log] [blame]
/*
* scst_disk.c
*
* Copyright (C) 2004 - 2018 Vladislav Bolkhovitin <vst@vlnb.net>
* Copyright (C) 2004 - 2005 Leonid Stoljar
* Copyright (C) 2007 - 2018 Western Digital Corporation
*
* SCSI disk (type 0) dev handler
* &
* SCSI disk (type 0) "performance" device handler (skip all READ and WRITE
* operations).
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation, version 2
* of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/blkdev.h>
#include <scsi/scsi_host.h>
#include <linux/slab.h>
#include <asm/unaligned.h>
#define LOG_PREFIX "dev_disk"
#ifdef INSIDE_KERNEL_TREE
#include <scst/scst.h>
#else
#include "scst.h"
#endif
#include "scst_dev_handler.h"
# define DISK_NAME "dev_disk"
# define DISK_PERF_NAME "dev_disk_perf"
#define DISK_DEF_BLOCK_SHIFT 9
static int disk_attach(struct scst_device *dev)
{
int res, rc;
uint8_t cmd[10];
const int buffer_size = 512;
uint8_t *buffer = NULL;
int retries;
unsigned char sense_buffer[SCSI_SENSE_BUFFERSIZE];
TRACE_ENTRY();
if (dev->scsi_dev == NULL ||
dev->scsi_dev->type != dev->type) {
PRINT_ERROR("%s", "SCSI device not define or illegal type");
res = -ENODEV;
goto out;
}
buffer = kmalloc(buffer_size, GFP_KERNEL);
if (!buffer) {
PRINT_ERROR("Buffer memory allocation (size %d) failure",
buffer_size);
res = -ENOMEM;
goto out;
}
/* Clear any existing UA's and get disk capacity (disk block size) */
memset(cmd, 0, sizeof(cmd));
cmd[0] = READ_CAPACITY;
cmd[1] = (dev->scsi_dev->scsi_level <= SCSI_2) ?
((dev->scsi_dev->lun << 5) & 0xe0) : 0;
retries = SCST_DEV_RETRIES_ON_UA;
while (1) {
memset(buffer, 0, buffer_size);
memset(sense_buffer, 0, sizeof(sense_buffer));
TRACE_DBG("%s", "Doing READ_CAPACITY");
rc = scst_scsi_execute(dev->scsi_dev, cmd, DMA_FROM_DEVICE,
buffer, buffer_size, sense_buffer,
SCST_GENERIC_DISK_REG_TIMEOUT, 3, 0);
TRACE_DBG("READ_CAPACITY done: %x", rc);
if ((rc == 0) ||
!scst_analyze_sense(sense_buffer,
sizeof(sense_buffer), SCST_SENSE_KEY_VALID,
UNIT_ATTENTION, 0, 0))
break;
if (!--retries) {
PRINT_ERROR("UA not clear after %d retries",
SCST_DEV_RETRIES_ON_UA);
res = -ENODEV;
goto out_free_buf;
}
}
if (rc == 0) {
uint32_t sector_size = get_unaligned_be32(&buffer[4]);
if (sector_size == 0)
dev->block_shift = DISK_DEF_BLOCK_SHIFT;
else
dev->block_shift = scst_calc_block_shift(sector_size);
if (dev->block_shift < 9) {
PRINT_ERROR("READ CAPACITY reported an invalid sector size: %d",
sector_size);
res = -EINVAL;
goto out_free_buf;
}
} else {
dev->block_shift = DISK_DEF_BLOCK_SHIFT;
TRACE(TRACE_MINOR, "Read capacity failed: %x, using default "
"sector size %d", rc, dev->block_shift);
PRINT_BUFF_FLAG(TRACE_MINOR, "Returned sense", sense_buffer,
sizeof(sense_buffer));
}
dev->block_size = 1 << dev->block_shift;
res = scst_obtain_device_parameters(dev, NULL);
if (res != 0) {
PRINT_ERROR("Failed to obtain control parameters for device "
"%s", dev->virt_name);
goto out_free_buf;
}
out_free_buf:
kfree(buffer);
out:
TRACE_EXIT_RES(res);
return res;
}
static void disk_detach(struct scst_device *dev)
{
/* Nothing to do */
return;
}
static int disk_parse(struct scst_cmd *cmd)
{
struct scst_tgt *tgt = cmd->tgt;
int res = SCST_CMD_STATE_DEFAULT, rc;
rc = scst_sbc_generic_parse(cmd);
if (rc != 0) {
res = scst_get_cmd_abnormal_done_state(cmd);
goto out;
}
if (unlikely(tgt->tgt_forward_src && cmd->op_flags & SCST_LOCAL_CMD)) {
switch (cmd->cdb[0]) {
case COMPARE_AND_WRITE:
case EXTENDED_COPY:
case RECEIVE_COPY_RESULTS:
TRACE_DBG("Clearing LOCAL CMD flag for cmd %p "
"(op %s)", cmd, cmd->op_name);
cmd->op_flags &= ~SCST_LOCAL_CMD;
break;
}
}
cmd->retries = SCST_PASSTHROUGH_RETRIES;
out:
return res;
}
static void disk_set_block_shift(struct scst_cmd *cmd, int block_shift)
{
struct scst_device *dev = cmd->dev;
int new_block_shift;
/*
* No need for locks here, since *_detach() can not be
* called, when there are existing commands.
*/
new_block_shift = block_shift ? : DISK_DEF_BLOCK_SHIFT;
if (dev->block_shift != new_block_shift) {
PRINT_INFO("%s: Changed block shift from %d into %d / %d",
dev->virt_name, dev->block_shift, block_shift,
new_block_shift);
dev->block_shift = new_block_shift;
dev->block_size = 1 << dev->block_shift;
}
return;
}
static int disk_done(struct scst_cmd *cmd)
{
int res = SCST_CMD_STATE_DEFAULT;
TRACE_ENTRY();
res = scst_block_generic_dev_done(cmd, disk_set_block_shift);
TRACE_EXIT_RES(res);
return res;
}
static bool disk_on_sg_tablesize_low(struct scst_cmd *cmd)
{
bool res;
TRACE_ENTRY();
switch (cmd->cdb[0]) {
case WRITE_6:
case READ_6:
case WRITE_10:
case READ_10:
case WRITE_VERIFY:
case WRITE_12:
case READ_12:
case WRITE_VERIFY_12:
case WRITE_16:
case READ_16:
case WRITE_VERIFY_16:
res = true;
/* See comment in disk_exec */
cmd->inc_expected_sn_on_done = 1;
break;
default:
res = false;
break;
}
TRACE_EXIT_RES(res);
return res;
}
struct disk_work {
struct scst_cmd *cmd;
struct completion disk_work_cmpl;
int result;
unsigned int left;
int64_t save_lba;
int save_len;
struct scatterlist *save_sg;
int save_sg_cnt;
};
static void disk_restore_sg(struct disk_work *work)
{
scst_set_cdb_lba(work->cmd, work->save_lba);
scst_set_cdb_transf_len(work->cmd, work->save_len);
work->cmd->sg = work->save_sg;
work->cmd->sg_cnt = work->save_sg_cnt;
return;
}
static void disk_cmd_done(void *data, char *sense, int result, int resid)
{
struct disk_work *work = data;
TRACE_ENTRY();
TRACE_DBG("work %p, cmd %p, left %d, result %d, sense %p, resid %d",
work, work->cmd, work->left, result, sense, resid);
WARN_ON_ONCE(IS_ERR_VALUE((long)result));
if ((result & 0xff) == SAM_STAT_GOOD)
goto out_complete;
work->result = result;
disk_restore_sg(work);
scst_pass_through_cmd_done(work->cmd, sense, result, resid + work->left);
out_complete:
complete_all(&work->disk_work_cmpl);
TRACE_EXIT();
return;
}
/* Executes command and split CDB, if necessary */
static enum scst_exec_res disk_exec(struct scst_cmd *cmd)
{
struct scst_tgt *tgt = cmd->tgt;
enum scst_exec_res res;
int rc;
struct disk_work work;
struct scst_device *dev = cmd->dev;
unsigned int offset, cur_len; /* in blocks */
struct scatterlist *sg, *start_sg;
int cur_sg_cnt;
int sg_tablesize = cmd->dev->scsi_dev->host->sg_tablesize;
unsigned int max_sectors;
int num, j, block_shift = dev->block_shift;
TRACE_ENTRY();
if (unlikely(tgt->tgt_forward_src && cmd->op_flags & SCST_LOCAL_CMD)) {
switch (cmd->cdb[0]) {
case RESERVE:
case RESERVE_10:
case RELEASE:
case RELEASE_10:
TRACE_DBG("Skipping LOCAL cmd %p (op %s)",
cmd, cmd->op_name);
goto out_done;
case PERSISTENT_RESERVE_IN:
case PERSISTENT_RESERVE_OUT:
sBUG();
break;
}
}
/*
* For PC requests we are going to submit max_hw_sectors used instead
* of max_sectors.
*/
max_sectors = queue_max_hw_sectors(dev->scsi_dev->request_queue);
if (unlikely(max_sectors < (PAGE_SIZE >> block_shift))) {
PRINT_ERROR("Too low max sectors: %u << %u < %lu", max_sectors,
block_shift, PAGE_SIZE);
goto out_error;
}
if (unlikely((cmd->bufflen >> block_shift) > max_sectors)) {
if ((cmd->out_bufflen >> block_shift) > max_sectors) {
PRINT_ERROR("Too limited max_sectors %d for "
"bidirectional cmd %p (op %s, out_bufflen %d)",
max_sectors, cmd, scst_get_opcode_name(cmd),
cmd->out_bufflen);
/* Let lower level handle it */
res = SCST_EXEC_NOT_COMPLETED;
goto out;
}
goto split;
}
if (cmd->sg_cnt <= sg_tablesize) {
res = SCST_EXEC_NOT_COMPLETED;
goto out;
}
split:
sBUG_ON(cmd->out_sg_cnt > sg_tablesize);
sBUG_ON((cmd->out_bufflen >> block_shift) > max_sectors);
/*
* We don't support changing BIDI CDBs (see disk_on_sg_tablesize_low()),
* so use only sg_cnt
*/
memset(&work, 0, sizeof(work));
work.cmd = cmd;
work.save_sg = cmd->sg;
work.save_sg_cnt = cmd->sg_cnt;
work.save_lba = cmd->lba;
work.save_len = cmd->bufflen;
EXTRACHECKS_BUG_ON(work.save_len < 0);
cmd->status = 0;
cmd->msg_status = 0;
cmd->host_status = DID_OK;
cmd->driver_status = 0;
TRACE_DBG("cmd %p, save_sg %p, save_sg_cnt %d, save_lba %lld, "
"save_len %d (sg_tablesize %d, max_sectors %d, block_shift %d, "
"sizeof(*sg) 0x%zx)", cmd, work.save_sg, work.save_sg_cnt,
(unsigned long long)work.save_lba, work.save_len,
sg_tablesize, max_sectors, block_shift, sizeof(*sg));
/*
* If we submit all chunks async'ly, it will be very not trivial what
* to do if several of them finish with sense or residual. So, let's
* do it synchronously.
*/
num = 1;
j = 0;
offset = 0;
cur_len = 0;
sg = work.save_sg;
start_sg = sg;
cur_sg_cnt = 0;
while (1) {
unsigned int l;
if (unlikely(sg_is_chain(&sg[j]))) {
bool reset_start_sg = (start_sg == &sg[j]);
sg = sg_chain_ptr(&sg[j]);
j = 0;
if (reset_start_sg)
start_sg = sg;
}
l = sg[j].length >> block_shift;
cur_len += l;
cur_sg_cnt++;
TRACE_DBG("l %d, j %d, num %d, offset %d, cur_len %d, "
"cur_sg_cnt %d, start_sg %p", l, j, num, offset,
cur_len, cur_sg_cnt, start_sg);
if (((num % sg_tablesize) == 0) ||
(num == work.save_sg_cnt) ||
(cur_len >= max_sectors)) {
TRACE_DBG("%s", "Execing...");
scst_set_cdb_lba(work.cmd, work.save_lba + offset);
scst_set_cdb_transf_len(work.cmd, cur_len);
cmd->sg = start_sg;
cmd->sg_cnt = cur_sg_cnt;
work.left = work.save_len - (offset + cur_len);
init_completion(&work.disk_work_cmpl);
rc = scst_scsi_exec_async(cmd, &work, disk_cmd_done);
if (unlikely(rc != 0)) {
PRINT_ERROR("scst_scsi_exec_async() failed: %d", rc);
goto out_err_restore;
}
wait_for_completion(&work.disk_work_cmpl);
if (work.result != SAM_STAT_GOOD) {
/* cmd can be already dead */
res = SCST_EXEC_COMPLETED;
goto out;
}
offset += cur_len;
cur_len = 0;
cur_sg_cnt = 0;
start_sg = &sg[j+1];
if (num == work.save_sg_cnt)
break;
}
num++;
j++;
}
cmd->completed = 1;
out_restore:
disk_restore_sg(&work);
out_done:
res = SCST_EXEC_COMPLETED;
cmd->scst_cmd_done(cmd, SCST_CMD_STATE_DEFAULT, SCST_CONTEXT_SAME);
out:
TRACE_EXIT_RES(res);
return res;
out_err_restore:
scst_set_cmd_error(cmd, SCST_LOAD_SENSE(scst_sense_internal_failure));
goto out_restore;
out_error:
scst_set_cmd_error(cmd, SCST_LOAD_SENSE(scst_sense_hardw_error));
goto out_done;
}
static enum scst_exec_res disk_perf_exec(struct scst_cmd *cmd)
{
enum scst_exec_res res;
int opcode = cmd->cdb[0];
TRACE_ENTRY();
cmd->status = 0;
cmd->msg_status = 0;
cmd->host_status = DID_OK;
cmd->driver_status = 0;
switch (opcode) {
case WRITE_6:
case WRITE_10:
case WRITE_12:
case WRITE_16:
case READ_6:
case READ_10:
case READ_12:
case READ_16:
case WRITE_VERIFY:
case WRITE_VERIFY_12:
case WRITE_VERIFY_16:
goto out_complete;
}
res = SCST_EXEC_NOT_COMPLETED;
out:
TRACE_EXIT_RES(res);
return res;
out_complete:
cmd->completed = 1;
res = SCST_EXEC_COMPLETED;
cmd->scst_cmd_done(cmd, SCST_CMD_STATE_DEFAULT, SCST_CONTEXT_SAME);
goto out;
}
static struct scst_dev_type disk_devtype = {
.name = DISK_NAME,
.type = TYPE_DISK,
.threads_num = 1,
.parse_atomic = 1,
.dev_done_atomic = 1,
.attach = disk_attach,
.detach = disk_detach,
.parse = disk_parse,
.exec = disk_exec,
.on_sg_tablesize_low = disk_on_sg_tablesize_low,
.dev_done = disk_done,
#if defined(CONFIG_SCST_DEBUG) || defined(CONFIG_SCST_TRACING)
.default_trace_flags = SCST_DEFAULT_DEV_LOG_FLAGS,
.trace_flags = &trace_flag,
#endif
};
static struct scst_dev_type disk_devtype_perf = {
.name = DISK_PERF_NAME,
.type = TYPE_DISK,
.parse_atomic = 1,
.dev_done_atomic = 1,
.attach = disk_attach,
.detach = disk_detach,
.parse = disk_parse,
.exec = disk_perf_exec,
.dev_done = disk_done,
.on_sg_tablesize_low = disk_on_sg_tablesize_low,
#if defined(CONFIG_SCST_DEBUG) || defined(CONFIG_SCST_TRACING)
.default_trace_flags = SCST_DEFAULT_DEV_LOG_FLAGS,
.trace_flags = &trace_flag,
#endif
};
static int __init init_scst_disk_driver(void)
{
int res = 0;
TRACE_ENTRY();
disk_devtype.module = THIS_MODULE;
res = scst_register_dev_driver(&disk_devtype);
if (res < 0)
goto out;
disk_devtype_perf.module = THIS_MODULE;
res = scst_register_dev_driver(&disk_devtype_perf);
if (res < 0)
goto out_unreg;
out:
TRACE_EXIT_RES(res);
return res;
out_unreg:
scst_unregister_dev_driver(&disk_devtype);
goto out;
}
static void __exit exit_scst_disk_driver(void)
{
TRACE_ENTRY();
scst_unregister_dev_driver(&disk_devtype_perf);
scst_unregister_dev_driver(&disk_devtype);
TRACE_EXIT();
return;
}
module_init(init_scst_disk_driver);
module_exit(exit_scst_disk_driver);
MODULE_AUTHOR("Vladislav Bolkhovitin & Leonid Stoljar");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("SCSI disk (type 0) dev handler for SCST");
MODULE_VERSION(SCST_VERSION_STRING);
MODULE_IMPORT_NS(SCST);