/* * scst_disk.c * * Copyright (C) 2004 - 2018 Vladislav Bolkhovitin * Copyright (C) 2004 - 2005 Leonid Stoljar * Copyright (C) 2007 - 2018 Western Digital Corporation * * SCSI disk (type 0) dev handler * & * SCSI disk (type 0) "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 #include #define LOG_PREFIX "dev_disk" #ifdef INSIDE_KERNEL_TREE #include #else #include "scst.h" #endif #include "scst_dev_handler.h" # define DISK_NAME "dev_disk" # define DISK_PERF_NAME "dev_disk_perf" #define DISK_DEF_BLOCK_SHIFT 9 static int disk_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; } 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 disk capacity (disk 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_DISK_REG_TIMEOUT, 3, 0); TRACE_DBG("READ_CAPACITY done: %x", rc); if ((rc == 0) || !scst_analyze_sense(sense_buffer, sizeof(sense_buffer), SCST_SENSE_KEY_VALID, UNIT_ATTENTION, 0, 0)) break; if (!--retries) { PRINT_ERROR("UA not clear 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 = DISK_DEF_BLOCK_SHIFT; else dev->block_shift = scst_calc_block_shift(sector_size); 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 = DISK_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", dev->virt_name); goto out_free_buf; } out_free_buf: kfree(buffer); out: TRACE_EXIT_RES(res); return res; } static void disk_detach(struct scst_device *dev) { /* Nothing to do */ return; } static int disk_parse(struct scst_cmd *cmd) { int res = SCST_CMD_STATE_DEFAULT, rc; rc = scst_sbc_generic_parse(cmd); if (rc != 0) { res = scst_get_cmd_abnormal_done_state(cmd); goto out; } #ifdef CONFIG_SCST_FORWARD_MODE_PASS_THROUGH if (unlikely(cmd->op_flags & SCST_LOCAL_CMD)) { switch (cmd->cdb[0]) { case COMPARE_AND_WRITE: case EXTENDED_COPY: case RECEIVE_COPY_RESULTS: TRACE_DBG("Clearing LOCAL CMD flag for cmd %p " "(op %s)", cmd, cmd->op_name); cmd->op_flags &= ~SCST_LOCAL_CMD; break; } } #endif cmd->retries = SCST_PASSTHROUGH_RETRIES; out: return res; } static void disk_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 ? : DISK_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 disk_done(struct scst_cmd *cmd) { int res = SCST_CMD_STATE_DEFAULT; TRACE_ENTRY(); res = scst_block_generic_dev_done(cmd, disk_set_block_shift); TRACE_EXIT_RES(res); return res; } #if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 30) static bool disk_on_sg_tablesize_low(struct scst_cmd *cmd) { bool res; TRACE_ENTRY(); switch (cmd->cdb[0]) { case WRITE_6: case READ_6: case WRITE_10: case READ_10: case WRITE_VERIFY: case WRITE_12: case READ_12: case WRITE_VERIFY_12: case WRITE_16: case READ_16: case WRITE_VERIFY_16: res = true; /* See comment in disk_exec */ cmd->inc_expected_sn_on_done = 1; break; default: res = false; break; } TRACE_EXIT_RES(res); return res; } struct disk_work { struct scst_cmd *cmd; struct completion disk_work_cmpl; int result; unsigned int left; int64_t save_lba; int save_len; struct scatterlist *save_sg; int save_sg_cnt; }; static void disk_restore_sg(struct disk_work *work) { scst_set_cdb_lba(work->cmd, work->save_lba); scst_set_cdb_transf_len(work->cmd, work->save_len); work->cmd->sg = work->save_sg; work->cmd->sg_cnt = work->save_sg_cnt; return; } static void disk_cmd_done(void *data, char *sense, int result, int resid) { struct disk_work *work = data; TRACE_ENTRY(); TRACE_DBG("work %p, cmd %p, left %d, result %d, sense %p, resid %d", work, work->cmd, work->left, result, sense, resid); WARN_ON_ONCE(IS_ERR_VALUE((long)result)); if (status_byte(result) == GOOD) goto out_complete; work->result = result; disk_restore_sg(work); scst_pass_through_cmd_done(work->cmd, sense, result, resid + work->left); out_complete: complete_all(&work->disk_work_cmpl); TRACE_EXIT(); return; } /* Executes command and split CDB, if necessary */ static enum scst_exec_res disk_exec(struct scst_cmd *cmd) { enum scst_exec_res res; int rc; struct disk_work work; struct scst_device *dev = cmd->dev; unsigned int offset, cur_len; /* in blocks */ struct scatterlist *sg, *start_sg; int cur_sg_cnt; int sg_tablesize = cmd->dev->scsi_dev->host->sg_tablesize; unsigned int max_sectors; int num, j, block_shift = dev->block_shift; TRACE_ENTRY(); #ifdef CONFIG_SCST_FORWARD_MODE_PASS_THROUGH if (unlikely(cmd->op_flags & SCST_LOCAL_CMD)) { switch (cmd->cdb[0]) { case RESERVE: case RESERVE_10: case RELEASE: case RELEASE_10: TRACE_DBG("Skipping LOCAL cmd %p (op %s)", cmd, cmd->op_name); goto out_done; case PERSISTENT_RESERVE_IN: case PERSISTENT_RESERVE_OUT: sBUG(); break; } } #endif /* * For PC requests we are going to submit max_hw_sectors used instead * of max_sectors. */ max_sectors = queue_max_hw_sectors(dev->scsi_dev->request_queue); if (unlikely(max_sectors < (PAGE_SIZE >> block_shift))) { PRINT_ERROR("Too low max sectors: %u << %u < %lu", max_sectors, block_shift, PAGE_SIZE); goto out_error; } if (unlikely((cmd->bufflen >> block_shift) > max_sectors)) { if ((cmd->out_bufflen >> block_shift) > max_sectors) { PRINT_ERROR("Too limited max_sectors %d for " "bidirectional cmd %p (op %s, out_bufflen %d)", max_sectors, cmd, scst_get_opcode_name(cmd), cmd->out_bufflen); /* Let lower level handle it */ res = SCST_EXEC_NOT_COMPLETED; goto out; } goto split; } if (cmd->sg_cnt <= sg_tablesize) { res = SCST_EXEC_NOT_COMPLETED; goto out; } split: sBUG_ON(cmd->out_sg_cnt > sg_tablesize); sBUG_ON((cmd->out_bufflen >> block_shift) > max_sectors); /* * We don't support changing BIDI CDBs (see disk_on_sg_tablesize_low()), * so use only sg_cnt */ memset(&work, 0, sizeof(work)); work.cmd = cmd; work.save_sg = cmd->sg; work.save_sg_cnt = cmd->sg_cnt; work.save_lba = cmd->lba; work.save_len = cmd->bufflen; EXTRACHECKS_BUG_ON(work.save_len < 0); cmd->status = 0; cmd->msg_status = 0; cmd->host_status = DID_OK; cmd->driver_status = 0; TRACE_DBG("cmd %p, save_sg %p, save_sg_cnt %d, save_lba %lld, " "save_len %d (sg_tablesize %d, max_sectors %d, block_shift %d, " "sizeof(*sg) 0x%zx)", cmd, work.save_sg, work.save_sg_cnt, (unsigned long long)work.save_lba, work.save_len, sg_tablesize, max_sectors, block_shift, sizeof(*sg)); /* * If we submit all chunks async'ly, it will be very not trivial what * to do if several of them finish with sense or residual. So, let's * do it synchronously. */ num = 1; j = 0; offset = 0; cur_len = 0; sg = work.save_sg; start_sg = sg; cur_sg_cnt = 0; while (1) { unsigned int l; if (unlikely(sg_is_chain(&sg[j]))) { bool reset_start_sg = (start_sg == &sg[j]); sg = sg_chain_ptr(&sg[j]); j = 0; if (reset_start_sg) start_sg = sg; } l = sg[j].length >> block_shift; cur_len += l; cur_sg_cnt++; TRACE_DBG("l %d, j %d, num %d, offset %d, cur_len %d, " "cur_sg_cnt %d, start_sg %p", l, j, num, offset, cur_len, cur_sg_cnt, start_sg); if (((num % sg_tablesize) == 0) || (num == work.save_sg_cnt) || (cur_len >= max_sectors)) { TRACE_DBG("%s", "Execing..."); scst_set_cdb_lba(work.cmd, work.save_lba + offset); scst_set_cdb_transf_len(work.cmd, cur_len); cmd->sg = start_sg; cmd->sg_cnt = cur_sg_cnt; work.left = work.save_len - (offset + cur_len); init_completion(&work.disk_work_cmpl); rc = scst_scsi_exec_async(cmd, &work, disk_cmd_done); if (unlikely(rc != 0)) { PRINT_ERROR("scst_scsi_exec_async() failed: %d", rc); goto out_err_restore; } wait_for_completion(&work.disk_work_cmpl); if (work.result != SAM_STAT_GOOD) { /* cmd can be already dead */ res = SCST_EXEC_COMPLETED; goto out; } offset += cur_len; cur_len = 0; cur_sg_cnt = 0; start_sg = &sg[j+1]; if (num == work.save_sg_cnt) break; } num++; j++; } cmd->completed = 1; out_restore: disk_restore_sg(&work); out_done: res = SCST_EXEC_COMPLETED; cmd->scst_cmd_done(cmd, SCST_CMD_STATE_DEFAULT, SCST_CONTEXT_SAME); out: TRACE_EXIT_RES(res); return res; out_err_restore: scst_set_cmd_error(cmd, SCST_LOAD_SENSE(scst_sense_internal_failure)); goto out_restore; out_error: scst_set_cmd_error(cmd, SCST_LOAD_SENSE(scst_sense_hardw_error)); goto out_done; } #endif /* LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 30) */ static enum scst_exec_res disk_perf_exec(struct scst_cmd *cmd) { enum scst_exec_res res; 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: case WRITE_VERIFY: case WRITE_VERIFY_12: case WRITE_VERIFY_16: goto out_complete; } res = SCST_EXEC_NOT_COMPLETED; out: TRACE_EXIT_RES(res); return res; out_complete: cmd->completed = 1; res = SCST_EXEC_COMPLETED; cmd->scst_cmd_done(cmd, SCST_CMD_STATE_DEFAULT, SCST_CONTEXT_SAME); goto out; } static struct scst_dev_type disk_devtype = { .name = DISK_NAME, .type = TYPE_DISK, .threads_num = 1, .parse_atomic = 1, .dev_done_atomic = 1, .attach = disk_attach, .detach = disk_detach, .parse = disk_parse, #if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 30) .exec = disk_exec, .on_sg_tablesize_low = disk_on_sg_tablesize_low, #endif .dev_done = disk_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 disk_devtype_perf = { .name = DISK_PERF_NAME, .type = TYPE_DISK, .parse_atomic = 1, .dev_done_atomic = 1, .attach = disk_attach, .detach = disk_detach, .parse = disk_parse, .exec = disk_perf_exec, .dev_done = disk_done, #if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 30) .on_sg_tablesize_low = disk_on_sg_tablesize_low, #endif #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_disk_driver(void) { int res = 0; TRACE_ENTRY(); disk_devtype.module = THIS_MODULE; res = scst_register_dev_driver(&disk_devtype); if (res < 0) goto out; disk_devtype_perf.module = THIS_MODULE; res = scst_register_dev_driver(&disk_devtype_perf); if (res < 0) goto out_unreg; out: TRACE_EXIT_RES(res); return res; out_unreg: scst_unregister_dev_driver(&disk_devtype); goto out; } static void __exit exit_scst_disk_driver(void) { TRACE_ENTRY(); scst_unregister_dev_driver(&disk_devtype_perf); scst_unregister_dev_driver(&disk_devtype); TRACE_EXIT(); return; } module_init(init_scst_disk_driver); module_exit(exit_scst_disk_driver); MODULE_AUTHOR("Vladislav Bolkhovitin & Leonid Stoljar"); MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("SCSI disk (type 0) dev handler for SCST"); MODULE_VERSION(SCST_VERSION_STRING);