| /* |
| * 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); |