| /* |
| * scst_disk.c |
| * |
| * Copyright (C) 2004 - 2018 Vladislav Bolkhovitin <vst@vlnb.net> |
| * 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 <linux/module.h> |
| #include <linux/init.h> |
| #include <linux/blkdev.h> |
| #include <scsi/scsi_host.h> |
| #include <linux/slab.h> |
| #include <asm/unaligned.h> |
| |
| #define LOG_PREFIX "dev_disk" |
| |
| #ifdef INSIDE_KERNEL_TREE |
| #include <scst/scst.h> |
| #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]; |
| |
| 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)); |
| |
| TRACE_DBG("%s", "Doing READ_CAPACITY"); |
| rc = scst_scsi_execute(dev->scsi_dev, cmd, DMA_FROM_DEVICE, |
| 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) |
| { |
| struct scst_tgt *tgt = cmd->tgt; |
| 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; |
| } |
| |
| if (unlikely(tgt->tgt_forward_src && 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; |
| } |
| } |
| |
| 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; |
| } |
| |
| 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 ((result & 0xff) == SAM_STAT_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) |
| { |
| struct scst_tgt *tgt = cmd->tgt; |
| 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(); |
| |
| if (unlikely(tgt->tgt_forward_src && 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; |
| } |
| } |
| |
| /* |
| * 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; |
| } |
| |
| |
| 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, |
| .exec = disk_exec, |
| .on_sg_tablesize_low = disk_on_sg_tablesize_low, |
| .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, |
| .on_sg_tablesize_low = disk_on_sg_tablesize_low, |
| #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); |
| MODULE_IMPORT_NS(SCST); |