/* * scst_modisk.c * * Copyright (C) 2004 - 2018 Vladislav Bolkhovitin * 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 #include #include #include #include #define LOG_PREFIX "dev_modisk" #ifdef INSIDE_KERNEL_TREE #include #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]; enum dma_data_direction data_dir; 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)); data_dir = SCST_DATA_READ; TRACE_DBG("%s", "Doing READ_CAPACITY"); rc = scst_scsi_execute(dev->scsi_dev, cmd, data_dir, 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);