blob: 65699963b9b0143a7c543bc420725be69f72880c [file] [log] [blame]
/*
* scst_modisk.c
*
* Copyright (C) 2004 - 2018 Vladislav Bolkhovitin <vst@vlnb.net>
* Copyright (C) 2004 - 2005 Leonid Stoljar
* Copyright (C) 2007 - 2018 Western Digital Corporation
*
* SCSI MO disk (type 7) dev handler
* &
* SCSI MO disk (type 7) "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 <scsi/scsi_host.h>
#include <linux/slab.h>
#include <asm/unaligned.h>
#define LOG_PREFIX "dev_modisk"
#ifdef INSIDE_KERNEL_TREE
#include <scst/scst.h>
#else
#include "scst.h"
#endif
#include "scst_dev_handler.h"
# define MODISK_NAME "dev_modisk"
# define MODISK_PERF_NAME "dev_modisk_perf"
#define MODISK_DEF_BLOCK_SHIFT 10
static int modisk_attach(struct scst_device *);
static void modisk_detach(struct scst_device *);
static int modisk_parse(struct scst_cmd *);
static int modisk_done(struct scst_cmd *);
static enum scst_exec_res modisk_perf_exec(struct scst_cmd *);
static struct scst_dev_type modisk_devtype = {
.name = MODISK_NAME,
.type = TYPE_MOD,
.threads_num = 1,
.parse_atomic = 1,
.dev_done_atomic = 1,
.attach = modisk_attach,
.detach = modisk_detach,
.parse = modisk_parse,
.dev_done = modisk_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 modisk_devtype_perf = {
.name = MODISK_PERF_NAME,
.type = TYPE_MOD,
.parse_atomic = 1,
.dev_done_atomic = 1,
.attach = modisk_attach,
.detach = modisk_detach,
.parse = modisk_parse,
.dev_done = modisk_done,
.exec = modisk_perf_exec,
#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_modisk_driver(void)
{
int res = 0;
TRACE_ENTRY();
modisk_devtype.module = THIS_MODULE;
res = scst_register_dev_driver(&modisk_devtype);
if (res < 0)
goto out;
modisk_devtype_perf.module = THIS_MODULE;
res = scst_register_dev_driver(&modisk_devtype_perf);
if (res < 0)
goto out_unreg;
out:
TRACE_EXIT_RES(res);
return res;
out_unreg:
scst_unregister_dev_driver(&modisk_devtype);
goto out;
}
static void __exit exit_scst_modisk_driver(void)
{
TRACE_ENTRY();
scst_unregister_dev_driver(&modisk_devtype_perf);
scst_unregister_dev_driver(&modisk_devtype);
TRACE_EXIT();
return;
}
module_init(init_scst_modisk_driver);
module_exit(exit_scst_modisk_driver);
static int modisk_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;
}
dev->block_shift = MODISK_DEF_BLOCK_SHIFT;
dev->block_size = 1 << dev->block_shift;
/*
* If the device is offline, don't try to read capacity or any
* of the other stuff
*/
if (dev->scsi_dev->sdev_state == SDEV_OFFLINE) {
TRACE_DBG("%s", "Device is offline");
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 modisk capacity (modisk 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_MODISK_REG_TIMEOUT, 3, 0);
TRACE_DBG("READ_CAPACITY done: %x", rc);
if (!rc || !scst_analyze_sense(sense_buffer,
sizeof(sense_buffer), SCST_SENSE_KEY_VALID,
UNIT_ATTENTION, 0, 0))
break;
if (!--retries) {
PRINT_ERROR("UA not cleared 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 = MODISK_DEF_BLOCK_SHIFT;
else
dev->block_shift = scst_calc_block_shift(sector_size);
TRACE_DBG("Sector size is %i scsi_level %d(SCSI_2 %d)",
sector_size, dev->scsi_dev->scsi_level, SCSI_2);
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 = MODISK_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: %x", dev->virt_name, res);
goto out_free_buf;
}
out_free_buf:
kfree(buffer);
out:
TRACE_EXIT_RES(res);
return res;
}
static void modisk_detach(struct scst_device *dev)
{
/* Nothing to do */
return;
}
static int modisk_parse(struct scst_cmd *cmd)
{
int res = SCST_CMD_STATE_DEFAULT, rc;
rc = scst_modisk_generic_parse(cmd);
if (rc != 0) {
res = scst_get_cmd_abnormal_done_state(cmd);
goto out;
}
cmd->retries = SCST_PASSTHROUGH_RETRIES;
out:
return res;
}
static void modisk_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 ? : MODISK_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 modisk_done(struct scst_cmd *cmd)
{
int res;
TRACE_ENTRY();
res = scst_block_generic_dev_done(cmd, modisk_set_block_shift);
TRACE_EXIT_RES(res);
return res;
}
static enum scst_exec_res modisk_perf_exec(struct scst_cmd *cmd)
{
enum scst_exec_res res = SCST_EXEC_NOT_COMPLETED;
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:
cmd->completed = 1;
goto out_done;
}
out:
TRACE_EXIT_RES(res);
return res;
out_done:
res = SCST_EXEC_COMPLETED;
cmd->scst_cmd_done(cmd, SCST_CMD_STATE_DEFAULT, SCST_CONTEXT_SAME);
goto out;
}
MODULE_AUTHOR("Vladislav Bolkhovitin & Leonid Stoljar");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("SCSI MO disk (type 7) dev handler for SCST");
MODULE_VERSION(SCST_VERSION_STRING);
MODULE_IMPORT_NS(SCST);