/* * scst_tape.c * * Copyright (C) 2004 - 2018 Vladislav Bolkhovitin * 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 #include #include #include #include #define LOG_PREFIX "dev_tape" #ifdef INSIDE_KERNEL_TREE #include #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 = -1; /* not used */ 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 #if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 25) ); #else , NULL); #endif 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 = -1; /* not used */ 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 = -1; /* not used */ 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);