blob: 1bb03018067ad64ae78ea8175be6a6b1cc27f833 [file] [log] [blame]
/*
* scst_tape.c
*
* Copyright (C) 2004 - 2018 Vladislav Bolkhovitin <vst@vlnb.net>
* Copyright (C) 2004 - 2005 Leonid Stoljar
* Copyright (C) 2007 - 2018 Western Digital Corporation
*
* SCSI tape (type 1) dev handler
* &
* SCSI tape (type 1) "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_tape"
#ifdef INSIDE_KERNEL_TREE
#include <scst/scst.h>
#else
#include "scst.h"
#endif
#include "scst_dev_handler.h"
#define TAPE_NAME "dev_tape"
#define TAPE_PERF_NAME "dev_tape_perf"
#define TAPE_RETRIES 2
#define TAPE_DEF_BLOCK_SIZE 512
/* The fixed bit in READ/WRITE/VERIFY */
#define SILI_BIT 2
static int tape_attach(struct scst_device *);
static void tape_detach(struct scst_device *);
static int tape_parse(struct scst_cmd *);
static int tape_done(struct scst_cmd *);
static enum scst_exec_res tape_perf_exec(struct scst_cmd *);
static struct scst_dev_type tape_devtype = {
.name = TAPE_NAME,
.type = TYPE_TAPE,
.threads_num = 1,
.parse_atomic = 1,
.dev_done_atomic = 1,
.attach = tape_attach,
.detach = tape_detach,
.parse = tape_parse,
.dev_done = tape_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 tape_devtype_perf = {
.name = TAPE_PERF_NAME,
.type = TYPE_TAPE,
.parse_atomic = 1,
.dev_done_atomic = 1,
.attach = tape_attach,
.detach = tape_detach,
.parse = tape_parse,
.dev_done = tape_done,
.exec = tape_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_tape_driver(void)
{
int res = 0;
TRACE_ENTRY();
tape_devtype.module = THIS_MODULE;
res = scst_register_dev_driver(&tape_devtype);
if (res < 0)
goto out;
tape_devtype_perf.module = THIS_MODULE;
res = scst_register_dev_driver(&tape_devtype_perf);
if (res < 0)
goto out_unreg;
out:
TRACE_EXIT_RES(res);
return res;
out_unreg:
scst_unregister_dev_driver(&tape_devtype);
goto out;
}
static void __exit exit_scst_tape_driver(void)
{
TRACE_ENTRY();
scst_unregister_dev_driver(&tape_devtype_perf);
scst_unregister_dev_driver(&tape_devtype);
TRACE_EXIT();
return;
}
module_init(init_scst_tape_driver);
module_exit(exit_scst_tape_driver);
static int tape_attach(struct scst_device *dev)
{
int res, rc;
int retries;
struct scsi_mode_data data;
const int buffer_size = 512;
uint8_t *buffer = NULL;
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_size = TAPE_DEF_BLOCK_SIZE;
dev->block_shift = scst_calc_block_shift(dev->block_size);
buffer = kmalloc(buffer_size, GFP_KERNEL);
if (!buffer) {
PRINT_ERROR("Buffer memory allocation (size %d) failure",
buffer_size);
res = -ENOMEM;
goto out;
}
retries = SCST_DEV_RETRIES_ON_UA;
do {
TRACE_DBG("%s", "Doing TEST_UNIT_READY");
rc = scsi_test_unit_ready(dev->scsi_dev,
SCST_GENERIC_TAPE_SMALL_TIMEOUT, TAPE_RETRIES, NULL);
TRACE_DBG("TEST_UNIT_READY done: %x", rc);
} while ((--retries > 0) && rc);
if (rc) {
PRINT_WARNING("Unit not ready: %x", rc);
/* Let's try not to be too smart and continue processing */
goto obtain;
}
TRACE_DBG("%s", "Doing MODE_SENSE");
rc = scsi_mode_sense(dev->scsi_dev,
((dev->scsi_dev->scsi_level <= SCSI_2) ?
((dev->scsi_dev->lun << 5) & 0xe0) : 0),
0 /* Mode Page 0 */,
buffer, buffer_size,
SCST_GENERIC_TAPE_SMALL_TIMEOUT, TAPE_RETRIES,
&data, NULL);
TRACE_DBG("MODE_SENSE done: %x", rc);
if (rc == 0) {
int medium_type, mode, speed, density;
if (buffer[3] == 8)
dev->block_size = get_unaligned_be24(&buffer[9]);
else
dev->block_size = TAPE_DEF_BLOCK_SIZE;
medium_type = buffer[1];
mode = (buffer[2] & 0x70) >> 4;
speed = buffer[2] & 0x0f;
density = buffer[4];
TRACE_DBG("Tape: lun %lld. bs %d. type 0x%02x mode 0x%02x "
"speed 0x%02x dens 0x%02x", (u64)dev->scsi_dev->lun,
dev->block_size, medium_type, mode, speed, density);
} else {
PRINT_ERROR("MODE_SENSE failed: %x", rc);
res = -ENODEV;
goto out_free_buf;
}
dev->block_shift = scst_calc_block_shift(dev->block_size);
obtain:
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 tape_detach(struct scst_device *dev)
{
/* Nothing to do */
return;
}
static int tape_parse(struct scst_cmd *cmd)
{
int res = SCST_CMD_STATE_DEFAULT, rc;
rc = scst_tape_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 tape_set_block_size(struct scst_cmd *cmd, int block_size)
{
struct scst_device *dev = cmd->dev;
/*
* No need for locks here, since *_detach() can not be called, when
* there are existing commands.
*/
dev->block_size = block_size;
dev->block_shift = scst_calc_block_shift(dev->block_size);
return;
}
static int tape_done(struct scst_cmd *cmd)
{
int opcode = cmd->cdb[0];
int status = cmd->status;
int res = SCST_CMD_STATE_DEFAULT;
TRACE_ENTRY();
if ((status == SAM_STAT_GOOD) || (status == SAM_STAT_CONDITION_MET))
res = scst_tape_generic_dev_done(cmd, tape_set_block_size);
else if ((status == SAM_STAT_CHECK_CONDITION) &&
scst_sense_valid(cmd->sense)) {
TRACE_DBG("Extended sense %x", scst_sense_response_code(cmd->sense));
if (scst_sense_response_code(cmd->sense) != 0x70) {
PRINT_ERROR("Sense format 0x%x is not supported",
scst_sense_response_code(cmd->sense));
scst_set_cmd_error(cmd,
SCST_LOAD_SENSE(scst_sense_internal_failure));
goto out;
}
if (opcode == READ_6 && !(cmd->cdb[1] & SILI_BIT) &&
(cmd->sense[2] & 0xe0)) {
/* EOF, EOM, or ILI */
unsigned int TransferLength, Residue = 0;
if ((cmd->sense[2] & 0x0f) == BLANK_CHECK)
/* No need for EOM in this case */
cmd->sense[2] &= 0xcf;
TransferLength = get_unaligned_be24(&cmd->cdb[2]);
/* Compute the residual count */
if ((cmd->sense[0] & 0x80) != 0)
Residue = get_unaligned_be32(&cmd->sense[3]);
TRACE_DBG("Checking the sense key "
"sn[2]=%x cmd->cdb[0,1]=%x,%x TransLen/Resid"
" %d/%d", (int)cmd->sense[2], cmd->cdb[0],
cmd->cdb[1], TransferLength, Residue);
if (TransferLength > Residue) {
int resp_data_len = TransferLength - Residue;
if (cmd->cdb[1] & 1) {
/*
* No need for locks here, since
* *_detach() can not be called, when
* there are existing commands.
*/
resp_data_len *= cmd->dev->block_size;
}
scst_set_resp_data_len(cmd, resp_data_len);
}
}
}
out:
TRACE_DBG("cmd->is_send_status=%x, cmd->resp_data_len=%d, "
"res=%d", cmd->is_send_status, cmd->resp_data_len, res);
TRACE_EXIT_RES(res);
return res;
}
static enum scst_exec_res tape_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 READ_6:
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 tape (type 1) dev handler for SCST");
MODULE_VERSION(SCST_VERSION_STRING);
MODULE_IMPORT_NS(SCST);