diff --git a/debian/copyright b/debian/copyright index 86fa5b56e..5e351cfd4 100644 --- a/debian/copyright +++ b/debian/copyright @@ -220,6 +220,7 @@ Files: scst/Makefile scst/src/scst_dlm.c scst/src/scst_dlm.h scst/src/scst_lib.c + scst/src/scst_local_cmd.c scst/src/scst_main.c scst/src/scst_mem.c scst/src/scst_mem.h diff --git a/scripts/generate-kernel-patch b/scripts/generate-kernel-patch index 8800e589e..9391f413e 100755 --- a/scripts/generate-kernel-patch +++ b/scripts/generate-kernel-patch @@ -288,7 +288,8 @@ scst/include/scst_event.h scst/include/backport.h" scst_04_main="scst/src/scst_main.c scst/src/scst_module.c scst/src/scst_priv.h \ scst/src/scst_copy_mgr.c scst/src/scst_dlm.c scst/src/scst_dlm.h \ scst/src/scst_event.c scst/src/scst_no_dlm.c" -scst_05_targ="scst/src/scst_targ.c" +scst_05_targ="scst/src/scst_targ.c scst/src/scst_local_cmd.c \ +scst/src/scst_local_cmd.h" scst_06_lib="scst/src/scst_lib.c" scst_07_pres="scst/src/scst_pres.h scst/src/scst_pres.c" scst_08_sysfs="scst/src/scst_sysfs.c" diff --git a/scst/src/Makefile b/scst/src/Makefile index 621d089a1..60e07fa00 100644 --- a/scst/src/Makefile +++ b/scst/src/Makefile @@ -62,6 +62,7 @@ obj-m := scst.o scst-y += scst_main.o scst-y += scst_targ.o +scst-y += scst_local_cmd.o scst-y += scst_lib.o #scst-y += scst_proc.o scst-y += scst_sysfs.o diff --git a/scst/src/scst_local_cmd.c b/scst/src/scst_local_cmd.c new file mode 100644 index 000000000..72826f61e --- /dev/null +++ b/scst/src/scst_local_cmd.c @@ -0,0 +1,939 @@ +/* + * scst_local_cmd.c + * + * Copyright (C) 2004 - 2018 Vladislav Bolkhovitin + * Copyright (C) 2007 - 2018 Western Digital Corporation + * Copyright (C) 2008 - 2020 Bart Van Assche + * + * 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. + */ + +#ifdef INSIDE_KERNEL_TREE +#include +#else +#include "scst.h" +#endif +#include "scst_local_cmd.h" +#include "scst_priv.h" +#include "scst_pres.h" + +enum scst_exec_res scst_report_luns_local(struct scst_cmd *cmd) +{ + enum scst_exec_res res = SCST_EXEC_COMPLETED; + int dev_cnt = 0; + int buffer_size; + int i; + struct scst_tgt_dev *tgt_dev = NULL; + uint8_t *buffer; + int offs, overflow = 0; + + TRACE_ENTRY(); + + cmd->status = 0; + cmd->msg_status = 0; + cmd->host_status = DID_OK; + cmd->driver_status = 0; + + if ((cmd->cdb[2] != 0) && (cmd->cdb[2] != 2)) { + TRACE(TRACE_MINOR, "Unsupported SELECT REPORT value %#x in " + "REPORT LUNS command", cmd->cdb[2]); + scst_set_invalid_field_in_cdb(cmd, 2, 0); + goto out_compl; + } + + buffer_size = scst_get_buf_full_sense(cmd, &buffer); + if (unlikely(buffer_size <= 0)) + goto out_compl; + + if (buffer_size < 16) { + scst_set_invalid_field_in_cdb(cmd, 6, 0); + goto out_put_err; + } + + memset(buffer, 0, buffer_size); + offs = 8; + + rcu_read_lock(); + for (i = 0; i < SESS_TGT_DEV_LIST_HASH_SIZE; i++) { + struct list_head *head = &cmd->sess->sess_tgt_dev_list[i]; + + list_for_each_entry_rcu(tgt_dev, head, + sess_tgt_dev_list_entry) { + struct scst_tgt_dev_UA *ua; + + if (!overflow) { + if ((buffer_size - offs) < 8) { + overflow = 1; + goto inc_dev_cnt; + } + *(__force __be64 *)&buffer[offs] + = scst_pack_lun(tgt_dev->lun, + cmd->sess->acg->addr_method); + offs += 8; + } +inc_dev_cnt: + dev_cnt++; + + /* Clear sense_reported_luns_data_changed UA. */ + spin_lock_bh(&tgt_dev->tgt_dev_lock); + list_for_each_entry(ua, &tgt_dev->UA_list, + UA_list_entry) { + if (scst_analyze_sense(ua->UA_sense_buffer, + ua->UA_valid_sense_len, + SCST_SENSE_ALL_VALID, + SCST_LOAD_SENSE(scst_sense_reported_luns_data_changed))) { + TRACE_DBG("Freeing not needed " + "REPORTED LUNS DATA CHANGED UA " + "%p", ua); + scst_tgt_dev_del_free_UA(tgt_dev, ua); + break; + } + } + spin_unlock_bh(&tgt_dev->tgt_dev_lock); + } + } + rcu_read_unlock(); + + /* Set the response header */ + dev_cnt *= 8; + put_unaligned_be32(dev_cnt, buffer); + + scst_put_buf_full(cmd, buffer); + + dev_cnt += 8; + if (dev_cnt < cmd->resp_data_len) + scst_set_resp_data_len(cmd, dev_cnt); + +out_compl: + cmd->completed = 1; + + /* Report the result */ + cmd->scst_cmd_done(cmd, SCST_CMD_STATE_DEFAULT, SCST_CONTEXT_SAME); + + TRACE_EXIT_RES(res); + return res; + +out_put_err: + scst_put_buf_full(cmd, buffer); + goto out_compl; +} + +enum scst_exec_res scst_request_sense_local(struct scst_cmd *cmd) +{ + enum scst_exec_res res = SCST_EXEC_COMPLETED; + struct scst_tgt_dev *tgt_dev = cmd->tgt_dev; + uint8_t *buffer; + int buffer_size = 0, sl = 0; + + TRACE_ENTRY(); + + cmd->status = 0; + cmd->msg_status = 0; + cmd->host_status = DID_OK; + cmd->driver_status = 0; + + buffer_size = scst_get_buf_full_sense(cmd, &buffer); + if (unlikely(buffer_size <= 0)) + goto out_compl; + + memset(buffer, 0, buffer_size); + + spin_lock_bh(&tgt_dev->tgt_dev_lock); + + if (tgt_dev->tgt_dev_valid_sense_len == 0) { + if (test_bit(SCST_TGT_DEV_UA_PENDING, &cmd->tgt_dev->tgt_dev_flags)) { + int rc, size = sizeof(tgt_dev->tgt_dev_sense); + uint8_t *buf; + + spin_unlock_bh(&tgt_dev->tgt_dev_lock); + + buf = kzalloc(size, GFP_KERNEL); + if (buf == NULL) + goto out_put_busy; + + rc = scst_set_pending_UA(cmd, buf, &size); + + spin_lock_bh(&tgt_dev->tgt_dev_lock); + + if (rc == 0) { + if (tgt_dev->tgt_dev_valid_sense_len == 0) { + tgt_dev->tgt_dev_valid_sense_len = size; + memcpy(tgt_dev->tgt_dev_sense, buf, size); + } else { + /* + * Yes, we can loose some of UA data + * here, if UA size is bigger, than + * size, i.e. tgt_dev_sense. + */ + scst_requeue_ua(cmd, buf, size); + } + } + + kfree(buf); + } + if (tgt_dev->tgt_dev_valid_sense_len == 0) + goto out_unlock_put_not_completed; + } + + TRACE(TRACE_SCSI, "%s: Returning stored/UA sense", cmd->op_name); + + if (((scst_sense_response_code(tgt_dev->tgt_dev_sense) == 0x70) || + (scst_sense_response_code(tgt_dev->tgt_dev_sense) == 0x71)) && + (cmd->cdb[1] & 1)) { + PRINT_WARNING("%s: Fixed format of the saved sense, but " + "descriptor format requested. Conversion will " + "truncated data", cmd->op_name); + PRINT_BUFFER("Original sense", tgt_dev->tgt_dev_sense, + tgt_dev->tgt_dev_valid_sense_len); + + buffer_size = min(SCST_STANDARD_SENSE_LEN, buffer_size); + sl = scst_set_sense(buffer, buffer_size, true, + tgt_dev->tgt_dev_sense[2], tgt_dev->tgt_dev_sense[12], + tgt_dev->tgt_dev_sense[13]); + } else if (((scst_sense_response_code(tgt_dev->tgt_dev_sense) == 0x72) || + (scst_sense_response_code(tgt_dev->tgt_dev_sense) == 0x73)) && + !(cmd->cdb[1] & 1)) { + PRINT_WARNING("%s: Descriptor format of the " + "saved sense, but fixed format requested. Conversion " + "will truncate data", cmd->op_name); + PRINT_BUFFER("Original sense", tgt_dev->tgt_dev_sense, + tgt_dev->tgt_dev_valid_sense_len); + + buffer_size = min(SCST_STANDARD_SENSE_LEN, buffer_size); + sl = scst_set_sense(buffer, buffer_size, false, + tgt_dev->tgt_dev_sense[1], tgt_dev->tgt_dev_sense[2], + tgt_dev->tgt_dev_sense[3]); + } else { + if (buffer_size >= tgt_dev->tgt_dev_valid_sense_len) + sl = tgt_dev->tgt_dev_valid_sense_len; + else { + sl = buffer_size; + TRACE(TRACE_SCSI|TRACE_MINOR, "%s: Being returned sense " + "truncated to size %d (needed %d)", cmd->op_name, + buffer_size, tgt_dev->tgt_dev_valid_sense_len); + } + memcpy(buffer, tgt_dev->tgt_dev_sense, sl); + } + + tgt_dev->tgt_dev_valid_sense_len = 0; + + spin_unlock_bh(&tgt_dev->tgt_dev_lock); + + scst_put_buf_full(cmd, buffer); + + scst_set_resp_data_len(cmd, sl); + +out_compl: + cmd->completed = 1; + + /* Report the result */ + cmd->scst_cmd_done(cmd, SCST_CMD_STATE_DEFAULT, SCST_CONTEXT_SAME); + +out: + TRACE_EXIT_RES(res); + return res; + +out_put_busy: + scst_put_buf_full(cmd, buffer); + scst_set_busy(cmd); + goto out_compl; + +out_unlock_put_not_completed: + spin_unlock_bh(&tgt_dev->tgt_dev_lock); + scst_put_buf_full(cmd, buffer); + res = SCST_EXEC_NOT_COMPLETED; + goto out; +} + +static int scst_report_supported_tm_fns(struct scst_cmd *cmd) +{ + int res = SCST_EXEC_COMPLETED; + int length, resp_len = 0; + uint8_t *address; + uint8_t buf[16]; + + TRACE_ENTRY(); + + length = scst_get_buf_full_sense(cmd, &address); + TRACE_DBG("length %d", length); + if (unlikely(length <= 0)) + goto out_compl; + + memset(buf, 0, sizeof(buf)); + + buf[0] = 0xF8; /* ATS, ATSS, CACAS, CTSS, LURS */ + buf[1] = 0; + if ((cmd->cdb[2] & 0x80) == 0) + resp_len = 4; + else { + buf[3] = 0x0C; +#if 1 + buf[4] = 1; /* TMFTMOV */ + buf[6] = 0xA0; /* ATTS, CACATS */ + put_unaligned_be32(300, &buf[8]); /* long timeout - 30 sec. */ + put_unaligned_be32(150, &buf[12]); /* short timeout - 15 sec. */ +#endif + resp_len = 16; + } + + if (length > resp_len) + length = resp_len; + memcpy(address, buf, length); + + scst_put_buf_full(cmd, address); + if (length < cmd->resp_data_len) + scst_set_resp_data_len(cmd, length); + +out_compl: + cmd->completed = 1; + + /* Report the result */ + cmd->scst_cmd_done(cmd, SCST_CMD_STATE_DEFAULT, SCST_CONTEXT_SAME); + + TRACE_EXIT_RES(res); + return res; +} + +static int scst_report_supported_opcodes(struct scst_cmd *cmd) +{ + int res = SCST_EXEC_COMPLETED; + int length, buf_len, i, offs; + uint8_t *address; + uint8_t *buf; + bool inline_buf; + bool rctd = cmd->cdb[2] >> 7; + int options = cmd->cdb[2] & 7; + int req_opcode = cmd->cdb[3]; + int req_sa = get_unaligned_be16(&cmd->cdb[4]); + const struct scst_opcode_descriptor *op = NULL; + const struct scst_opcode_descriptor **supp_opcodes = NULL; + int supp_opcodes_cnt, rc; + + TRACE_ENTRY(); + + /* get_cdb_info_min() ensures that get_supported_opcodes is not NULL here */ + + rc = cmd->devt->get_supported_opcodes(cmd, &supp_opcodes, &supp_opcodes_cnt); + if (rc != 0) + goto out_compl; + + TRACE_DBG("cmd %p, options %d, req_opcode %x, req_sa %x, rctd %d", + cmd, options, req_opcode, req_sa, rctd); + + switch (options) { + case 0: /* all */ + buf_len = 4; + for (i = 0; i < supp_opcodes_cnt; i++) { + buf_len += 8; + if (rctd) + buf_len += 12; + } + break; + case 1: + buf_len = 0; + for (i = 0; i < supp_opcodes_cnt; i++) { + if (req_opcode == supp_opcodes[i]->od_opcode) { + op = supp_opcodes[i]; + if (op->od_serv_action_valid) { + TRACE(TRACE_MINOR, "Requested opcode %x " + "with unexpected service action " + "(dev %s, initiator %s)", + req_opcode, cmd->dev->virt_name, + cmd->sess->initiator_name); + scst_set_invalid_field_in_cdb(cmd, 2, + SCST_INVAL_FIELD_BIT_OFFS_VALID | 0); + goto out_compl; + } + buf_len = 4 + op->od_cdb_size; + if (rctd) + buf_len += 12; + break; + } + } + if (op == NULL) { + TRACE(TRACE_MINOR, "Requested opcode %x not found " + "(dev %s, initiator %s)", req_opcode, + cmd->dev->virt_name, cmd->sess->initiator_name); + buf_len = 4; + } + break; + case 2: + buf_len = 0; + for (i = 0; i < supp_opcodes_cnt; i++) { + if (req_opcode == supp_opcodes[i]->od_opcode) { + op = supp_opcodes[i]; + if (!op->od_serv_action_valid) { + TRACE(TRACE_MINOR, "Requested opcode %x " + "without expected service action " + "(dev %s, initiator %s)", + req_opcode, cmd->dev->virt_name, + cmd->sess->initiator_name); + scst_set_invalid_field_in_cdb(cmd, 2, + SCST_INVAL_FIELD_BIT_OFFS_VALID | 0); + goto out_compl; + } + if (req_sa != op->od_serv_action) { + op = NULL; /* reset it */ + continue; + } + buf_len = 4 + op->od_cdb_size; + if (rctd) + buf_len += 12; + break; + } + } + if (op == NULL) { + TRACE(TRACE_MINOR, "Requested opcode %x/%x not found " + "(dev %s, initiator %s)", req_opcode, req_sa, + cmd->dev->virt_name, cmd->sess->initiator_name); + buf_len = 4; + } + break; + default: + PRINT_ERROR("REPORT SUPPORTED OPERATION CODES: REPORTING OPTIONS " + "%x not supported (dev %s, initiator %s)", options, + cmd->dev->virt_name, cmd->sess->initiator_name); + scst_set_invalid_field_in_cdb(cmd, 2, + SCST_INVAL_FIELD_BIT_OFFS_VALID | 0); + goto out_compl; + } + + length = scst_get_buf_full_sense(cmd, &address); + TRACE_DBG("length %d, buf_len %d, op %p", length, buf_len, op); + if (unlikely(length <= 0)) + goto out_compl; + + if (length >= buf_len) { + buf = address; + inline_buf = true; + } else { + buf = vmalloc(buf_len); /* it can be big */ + if (buf == NULL) { + PRINT_ERROR("Unable to allocate REPORT SUPPORTED " + "OPERATION CODES buffer with size %d", buf_len); + scst_set_busy(cmd); + goto out_err_put; + } + inline_buf = false; + } + + memset(buf, 0, buf_len); + + switch (options) { + case 0: /* all */ + put_unaligned_be32(buf_len - 4, &buf[0]); + offs = 4; + for (i = 0; i < supp_opcodes_cnt; i++) { + op = supp_opcodes[i]; + buf[offs] = op->od_opcode; + if (op->od_serv_action_valid) { + put_unaligned_be16(op->od_serv_action, &buf[offs + 2]); + buf[offs + 5] |= 1; + } + put_unaligned_be16(op->od_cdb_size, &buf[offs + 6]); + offs += 8; + if (rctd) { + buf[(offs - 8) + 5] |= 2; + buf[offs + 1] = 0xA; + buf[offs + 3] = op->od_comm_specific_timeout; + put_unaligned_be32(op->od_nominal_timeout, &buf[offs + 4]); + put_unaligned_be32(op->od_recommended_timeout, &buf[offs + 8]); + offs += 12; + } + } + break; + case 1: + case 2: + if (op != NULL) { + buf[1] |= op->od_support; + put_unaligned_be16(op->od_cdb_size, &buf[2]); + memcpy(&buf[4], op->od_cdb_usage_bits, op->od_cdb_size); + if (rctd) { + buf[1] |= 0x80; + offs = 4 + op->od_cdb_size; + buf[offs + 1] = 0xA; + buf[offs + 3] = op->od_comm_specific_timeout; + put_unaligned_be32(op->od_nominal_timeout, &buf[offs + 4]); + put_unaligned_be32(op->od_recommended_timeout, &buf[offs + 8]); + } + } + break; + default: + sBUG(); + } + + if (length > buf_len) + length = buf_len; + if (!inline_buf) { + memcpy(address, buf, length); + vfree(buf); + } + + scst_put_buf_full(cmd, address); + if (length < cmd->resp_data_len) + scst_set_resp_data_len(cmd, length); + +out_compl: + if ((supp_opcodes != NULL) && (cmd->devt->put_supported_opcodes != NULL)) + cmd->devt->put_supported_opcodes(cmd, supp_opcodes, supp_opcodes_cnt); + + cmd->completed = 1; + + /* Report the result */ + cmd->scst_cmd_done(cmd, SCST_CMD_STATE_DEFAULT, SCST_CONTEXT_SAME); + + TRACE_EXIT_RES(res); + return res; + +out_err_put: + scst_put_buf_full(cmd, address); + goto out_compl; +} + +enum scst_exec_res scst_maintenance_in(struct scst_cmd *cmd) +{ + enum scst_exec_res res; + + TRACE_ENTRY(); + + switch (cmd->cdb[1] & 0x1f) { + case MI_REPORT_SUPPORTED_TASK_MANAGEMENT_FUNCTIONS: + res = scst_report_supported_tm_fns(cmd); + break; + case MI_REPORT_SUPPORTED_OPERATION_CODES: + res = scst_report_supported_opcodes(cmd); + break; + default: + res = SCST_EXEC_NOT_COMPLETED; + break; + } + + TRACE_EXIT_RES(res); + return res; +} + +enum scst_exec_res scst_reserve_local(struct scst_cmd *cmd) +{ + enum scst_exec_res res = SCST_EXEC_NOT_COMPLETED; + struct scst_device *dev; + struct scst_lksb pr_lksb; + + TRACE_ENTRY(); + + if (cmd->sess->sess_mq) { + PRINT_WARNING_ONCE("MQ session (%p) from initiator %s (tgt %s), " + "reservations not supported", cmd->sess, + cmd->sess->initiator_name, cmd->sess->tgt->tgt_name); + scst_set_cmd_error(cmd, SCST_LOAD_SENSE(scst_sense_invalid_opcode)); + goto out_done; + } + + if ((cmd->cdb[0] == RESERVE_10) && (cmd->cdb[2] & SCST_RES_3RDPTY)) { + PRINT_ERROR("RESERVE_10: 3rdPty RESERVE not implemented " + "(lun=%lld)", (unsigned long long)cmd->lun); + scst_set_invalid_field_in_cdb(cmd, 2, + SCST_INVAL_FIELD_BIT_OFFS_VALID | 4); + goto out_done; + } + + dev = cmd->dev; + + /* + * There's no need to block this device, even for + * SCST_TST_0_SINGLE_TASK_SET, or anyhow else protect reservations + * changes, because: + * + * 1. The reservation changes are (rather) atomic, i.e., in contrast + * to persistent reservations, don't have any invalid intermediate + * states during being changed. + * + * 2. It's a duty of initiators to ensure order of regular commands + * around the reservation command either by ORDERED attribute, or by + * queue draining, or etc. For case of SCST_TST_0_SINGLE_TASK_SET + * there are no target drivers which can ensure even for ORDERED + * commands order of their delivery, so, because initiators know + * it, also there's no point to do any extra protection actions. + */ + + if (!list_empty(&dev->dev_registrants_list)) { + if (scst_pr_crh_case(cmd)) + goto out_completed; + else { + scst_set_cmd_error_status(cmd, + SAM_STAT_RESERVATION_CONFLICT); + goto out_done; + } + } + + scst_res_lock(dev, &pr_lksb); + if (scst_is_not_reservation_holder(dev, cmd->sess)) { + scst_res_unlock(dev, &pr_lksb); + scst_set_cmd_error_status(cmd, SAM_STAT_RESERVATION_CONFLICT); + goto out_done; + } + scst_reserve_dev(dev, cmd->sess); + scst_res_unlock(dev, &pr_lksb); + +out: + TRACE_EXIT_RES(res); + return res; + +out_completed: + cmd->completed = 1; + +out_done: + /* Report the result */ + cmd->scst_cmd_done(cmd, SCST_CMD_STATE_DEFAULT, SCST_CONTEXT_SAME); + res = SCST_EXEC_COMPLETED; + goto out; +} + +enum scst_exec_res scst_release_local(struct scst_cmd *cmd) +{ + enum scst_exec_res res = SCST_EXEC_NOT_COMPLETED; + struct scst_device *dev; + struct scst_lksb pr_lksb; + + TRACE_ENTRY(); + + if (cmd->sess->sess_mq) { + PRINT_WARNING_ONCE("MQ session (%p) from initiator %s (tgt %s), " + "reservations not supported", cmd->sess, + cmd->sess->initiator_name, cmd->sess->tgt->tgt_name); + scst_set_cmd_error(cmd, SCST_LOAD_SENSE(scst_sense_invalid_opcode)); + goto out_done; + } + + dev = cmd->dev; + + /* + * See comment in scst_reserve_local() why no dev blocking or any + * other protection is needed here. + */ + + if (!list_empty(&dev->dev_registrants_list)) { + if (scst_pr_crh_case(cmd)) + goto out_completed; + else { + scst_set_cmd_error_status(cmd, + SAM_STAT_RESERVATION_CONFLICT); + goto out_done; + } + } + + scst_res_lock(dev, &pr_lksb); + + /* + * The device could be RELEASED behind us, if RESERVING session + * is closed (see scst_free_tgt_dev()), but this actually doesn't + * matter, so use lock and no retest for DEV_RESERVED bits again + */ + if (scst_is_not_reservation_holder(dev, cmd->sess)) { + /* + * SPC-2 requires to report SCSI status GOOD if a RELEASE + * command fails because a reservation is held by another + * session. + */ + res = SCST_EXEC_COMPLETED; + cmd->status = 0; + cmd->msg_status = 0; + cmd->host_status = DID_OK; + cmd->driver_status = 0; + cmd->completed = 1; + } else { + scst_clear_dev_reservation(dev); + } + + scst_res_unlock(dev, &pr_lksb); + + if (res == SCST_EXEC_COMPLETED) + goto out_done; + +out: + TRACE_EXIT_RES(res); + return res; + +out_completed: + cmd->completed = 1; + +out_done: + res = SCST_EXEC_COMPLETED; + /* Report the result */ + cmd->scst_cmd_done(cmd, SCST_CMD_STATE_DEFAULT, SCST_CONTEXT_SAME); + goto out; +} + +/* No locks, no IRQ or IRQ-disabled context allowed */ +enum scst_exec_res scst_persistent_reserve_in_local(struct scst_cmd *cmd) +{ + struct scst_device *dev; + struct scst_tgt_dev *tgt_dev; + struct scst_session *session; + int action; + uint8_t *buffer; + int buffer_size; + + TRACE_ENTRY(); + + EXTRACHECKS_BUG_ON(scst_cmd_atomic(cmd)); + + dev = cmd->dev; + tgt_dev = cmd->tgt_dev; + session = cmd->sess; + + if (session->sess_mq) { + PRINT_WARNING_ONCE("MQ session %p from initiator %s (tgt %s), " + "persistent reservations not supported", session, + session->initiator_name, session->tgt->tgt_name); + scst_set_cmd_error(cmd, SCST_LOAD_SENSE(scst_sense_invalid_opcode)); + goto out_done; + } + + if (unlikely(dev->not_pr_supporting_tgt_devs_num != 0)) { + PRINT_WARNING("Persistent Reservation command %s refused for " + "device %s, because the device has not supporting PR " + "transports connected", scst_get_opcode_name(cmd), + dev->virt_name); + scst_set_cmd_error(cmd, + SCST_LOAD_SENSE(scst_sense_invalid_opcode)); + goto out_done; + } + + if (scst_dev_reserved(dev)) { + TRACE_PR("PR command rejected, because device %s holds regular " + "reservation", dev->virt_name); + scst_set_cmd_error_status(cmd, SAM_STAT_RESERVATION_CONFLICT); + goto out_done; + } + +#ifndef CONFIG_SCST_FORWARD_MODE_PASS_THROUGH + if (dev->scsi_dev != NULL) { + PRINT_WARNING("PR commands for pass-through devices not " + "supported (device %s)", dev->virt_name); + scst_set_cmd_error(cmd, + SCST_LOAD_SENSE(scst_sense_invalid_opcode)); + goto out_done; + } +#endif + + buffer_size = scst_get_buf_full_sense(cmd, &buffer); + if (unlikely(buffer_size <= 0)) + goto out_done; + + scst_pr_read_lock(dev); + + /* We can be aborted by another PR command while waiting for the lock */ + if (unlikely(test_bit(SCST_CMD_ABORTED, &cmd->cmd_flags))) { + TRACE_MGMT_DBG("ABORTED set, aborting cmd %p", cmd); + goto out_unlock; + } + + action = cmd->cdb[1] & 0x1f; + + TRACE(TRACE_SCSI, "PR IN action %x for '%s' (LUN %llx) from '%s'", + action, dev->virt_name, tgt_dev->lun, session->initiator_name); + + switch (action) { + case PR_READ_KEYS: + scst_pr_read_keys(cmd, buffer, buffer_size); + break; + case PR_READ_RESERVATION: + scst_pr_read_reservation(cmd, buffer, buffer_size); + break; + case PR_REPORT_CAPS: + scst_pr_report_caps(cmd, buffer, buffer_size); + break; + case PR_READ_FULL_STATUS: + scst_pr_read_full_status(cmd, buffer, buffer_size); + break; + default: + PRINT_ERROR("Unsupported action %x", action); + goto out_unsup_act; + } + +out_complete: + cmd->completed = 1; + +out_unlock: + scst_pr_read_unlock(dev); + + scst_put_buf_full(cmd, buffer); + +out_done: + cmd->scst_cmd_done(cmd, SCST_CMD_STATE_DEFAULT, SCST_CONTEXT_SAME); + + TRACE_EXIT_RES(SCST_EXEC_COMPLETED); + return SCST_EXEC_COMPLETED; + +out_unsup_act: + scst_set_invalid_field_in_cdb(cmd, 1, + SCST_INVAL_FIELD_BIT_OFFS_VALID | 0); + goto out_complete; +} + +/* No locks, no IRQ or IRQ-disabled context allowed */ +enum scst_exec_res scst_persistent_reserve_out_local(struct scst_cmd *cmd) +{ + enum scst_exec_res res = SCST_EXEC_COMPLETED; + struct scst_device *dev; + struct scst_tgt_dev *tgt_dev; + struct scst_session *session; + int action; + uint8_t *buffer; + int buffer_size; + struct scst_lksb pr_lksb; + bool aborted = false; + + TRACE_ENTRY(); + + EXTRACHECKS_BUG_ON(scst_cmd_atomic(cmd)); + + dev = cmd->dev; + tgt_dev = cmd->tgt_dev; + session = cmd->sess; + + if (session->sess_mq) { + PRINT_WARNING_ONCE("MQ session (%p) from initiator %s (tgt %s), " + "persistent reservations not supported", session, + session->initiator_name, session->tgt->tgt_name); + scst_set_cmd_error(cmd, SCST_LOAD_SENSE(scst_sense_invalid_opcode)); + goto out_done; + } + + if (unlikely(dev->not_pr_supporting_tgt_devs_num != 0)) { + PRINT_WARNING("Persistent Reservation command %s refused for " + "device %s, because the device has not supporting PR " + "transports connected", scst_get_opcode_name(cmd), + dev->virt_name); + scst_set_cmd_error(cmd, SCST_LOAD_SENSE(scst_sense_invalid_opcode)); + goto out_done; + } + + action = cmd->cdb[1] & 0x1f; + + TRACE(TRACE_SCSI, "PR OUT action %x for '%s' (LUN %llx) from '%s'", + action, dev->virt_name, tgt_dev->lun, session->initiator_name); + + if (scst_dev_reserved(dev)) { + TRACE_PR("PR command rejected, because device %s holds regular " + "reservation", dev->virt_name); + scst_set_cmd_error_status(cmd, SAM_STAT_RESERVATION_CONFLICT); + goto out_done; + } + + buffer_size = scst_get_buf_full_sense(cmd, &buffer); + if (unlikely(buffer_size <= 0)) + goto out_done; + + dev->cl_ops->pr_write_lock(dev, &pr_lksb); + + /* + * Check if tgt_dev already registered. Also by this check we make + * sure that table "PERSISTENT RESERVE OUT service actions that are + * allowed in the presence of various reservations" is honored. + * REGISTER AND MOVE and RESERVE will be additionally checked for + * conflicts later. + */ + if ((action != PR_REGISTER) && (action != PR_REGISTER_AND_IGNORE) && + (tgt_dev->registrant == NULL)) { + TRACE_PR("'%s' not registered", cmd->sess->initiator_name); + scst_set_cmd_error_status(cmd, SAM_STAT_RESERVATION_CONFLICT); + goto out_unlock; + } + + /* Check scope */ + if ((action != PR_REGISTER) && (action != PR_REGISTER_AND_IGNORE) && + (action != PR_CLEAR) && (cmd->cdb[2] >> 4) != SCOPE_LU) { + TRACE_PR("Scope must be SCOPE_LU for action %x", action); + scst_set_invalid_field_in_cdb(cmd, 2, + SCST_INVAL_FIELD_BIT_OFFS_VALID | 4); + goto out_unlock; + } + + /* Check SPEC_I_PT (PR_REGISTER_AND_MOVE has another format) */ + if ((action != PR_REGISTER) && (action != PR_REGISTER_AND_MOVE) && + ((buffer[20] >> 3) & 0x01)) { + TRACE_PR("SPEC_I_PT must be zero for action %x", action); + scst_set_invalid_field_in_parm_list(cmd, 20, + SCST_INVAL_FIELD_BIT_OFFS_VALID | 3); + goto out_unlock; + } + + /* Check ALL_TG_PT (PR_REGISTER_AND_MOVE has another format) */ + if ((action != PR_REGISTER) && (action != PR_REGISTER_AND_IGNORE) && + (action != PR_REGISTER_AND_MOVE) && ((buffer[20] >> 2) & 0x01)) { + TRACE_PR("ALL_TG_PT must be zero for action %x", action); + scst_set_invalid_field_in_parm_list(cmd, 20, + SCST_INVAL_FIELD_BIT_OFFS_VALID | 2); + goto out_unlock; + } + + /* We can be aborted by another PR command while waiting for the lock */ + aborted = test_bit(SCST_CMD_ABORTED, &cmd->cmd_flags); + if (unlikely(aborted)) { + TRACE_MGMT_DBG("ABORTED set, aborting cmd %p", cmd); + goto out_unlock; + } + + switch (action) { + case PR_REGISTER: + scst_pr_register(cmd, buffer, buffer_size); + break; + case PR_RESERVE: + scst_pr_reserve(cmd, buffer, buffer_size); + break; + case PR_RELEASE: + scst_pr_release(cmd, buffer, buffer_size); + break; + case PR_CLEAR: + scst_pr_clear(cmd, buffer, buffer_size); + break; + case PR_PREEMPT: + scst_pr_preempt(cmd, buffer, buffer_size); + break; + case PR_PREEMPT_AND_ABORT: + scst_pr_preempt_and_abort(cmd, buffer, buffer_size); + break; + case PR_REGISTER_AND_IGNORE: + scst_pr_register_and_ignore(cmd, buffer, buffer_size); + break; + case PR_REGISTER_AND_MOVE: + scst_pr_register_and_move(cmd, buffer, buffer_size); + break; + default: + scst_set_invalid_field_in_cdb(cmd, 1, + SCST_INVAL_FIELD_BIT_OFFS_VALID | 0); + goto out_unlock; + } + + if (cmd->status == SAM_STAT_GOOD) + scst_pr_sync_device_file(dev); + + if ((cmd->devt->pr_cmds_notifications) && + (cmd->status == SAM_STAT_GOOD)) /* sync file may change status */ + res = SCST_EXEC_NOT_COMPLETED; + +out_unlock: + dev->cl_ops->pr_write_unlock(dev, &pr_lksb); + + scst_put_buf_full(cmd, buffer); + +out_done: + if (res == SCST_EXEC_COMPLETED) { + if (!aborted) + cmd->completed = 1; + cmd->scst_cmd_done(cmd, SCST_CMD_STATE_DEFAULT, + SCST_CONTEXT_SAME); + } + + TRACE_EXIT_RES(res); + return res; +} diff --git a/scst/src/scst_local_cmd.h b/scst/src/scst_local_cmd.h new file mode 100644 index 000000000..89809167b --- /dev/null +++ b/scst/src/scst_local_cmd.h @@ -0,0 +1,20 @@ +#ifndef _SCST_LOCAL_CMD_H_ +#define _SCST_LOCAL_CMD_H_ + +enum scst_exec_res; +struct scst_cmd; + +enum scst_exec_res scst_cm_ext_copy_exec(struct scst_cmd *cmd); +enum scst_exec_res scst_cm_rcv_copy_res_exec(struct scst_cmd *cmd); +enum scst_exec_res scst_cmp_wr_local(struct scst_cmd *cmd); +enum scst_exec_res scst_maintenance_in(struct scst_cmd *cmd); +enum scst_exec_res scst_persistent_reserve_in_local(struct scst_cmd *cmd); +enum scst_exec_res scst_persistent_reserve_out_local(struct scst_cmd *cmd); +enum scst_exec_res scst_release_local(struct scst_cmd *cmd); +enum scst_exec_res scst_release_local(struct scst_cmd *cmd); +enum scst_exec_res scst_report_luns_local(struct scst_cmd *cmd); +enum scst_exec_res scst_request_sense_local(struct scst_cmd *cmd); +enum scst_exec_res scst_reserve_local(struct scst_cmd *cmd); +enum scst_exec_res scst_reserve_local(struct scst_cmd *cmd); + +#endif /* _SCST_LOCAL_CMD_H_ */ diff --git a/scst/src/scst_targ.c b/scst/src/scst_targ.c index 62a351d10..12ea2ce1e 100644 --- a/scst/src/scst_targ.c +++ b/scst/src/scst_targ.c @@ -39,6 +39,7 @@ #else #include "scst.h" #endif +#include "scst_local_cmd.h" #include "scst_priv.h" #include "scst_pres.h" @@ -2330,920 +2331,6 @@ static void scst_cmd_done_local(struct scst_cmd *cmd, int next_state, return; } -static enum scst_exec_res scst_report_luns_local(struct scst_cmd *cmd) -{ - enum scst_exec_res res = SCST_EXEC_COMPLETED; - int dev_cnt = 0; - int buffer_size; - int i; - struct scst_tgt_dev *tgt_dev = NULL; - uint8_t *buffer; - int offs, overflow = 0; - - TRACE_ENTRY(); - - cmd->status = 0; - cmd->msg_status = 0; - cmd->host_status = DID_OK; - cmd->driver_status = 0; - - if ((cmd->cdb[2] != 0) && (cmd->cdb[2] != 2)) { - TRACE(TRACE_MINOR, "Unsupported SELECT REPORT value %#x in " - "REPORT LUNS command", cmd->cdb[2]); - scst_set_invalid_field_in_cdb(cmd, 2, 0); - goto out_compl; - } - - buffer_size = scst_get_buf_full_sense(cmd, &buffer); - if (unlikely(buffer_size <= 0)) - goto out_compl; - - if (buffer_size < 16) { - scst_set_invalid_field_in_cdb(cmd, 6, 0); - goto out_put_err; - } - - memset(buffer, 0, buffer_size); - offs = 8; - - rcu_read_lock(); - for (i = 0; i < SESS_TGT_DEV_LIST_HASH_SIZE; i++) { - struct list_head *head = &cmd->sess->sess_tgt_dev_list[i]; - - list_for_each_entry_rcu(tgt_dev, head, - sess_tgt_dev_list_entry) { - struct scst_tgt_dev_UA *ua; - - if (!overflow) { - if ((buffer_size - offs) < 8) { - overflow = 1; - goto inc_dev_cnt; - } - *(__force __be64 *)&buffer[offs] - = scst_pack_lun(tgt_dev->lun, - cmd->sess->acg->addr_method); - offs += 8; - } -inc_dev_cnt: - dev_cnt++; - - /* Clear sense_reported_luns_data_changed UA. */ - spin_lock_bh(&tgt_dev->tgt_dev_lock); - list_for_each_entry(ua, &tgt_dev->UA_list, - UA_list_entry) { - if (scst_analyze_sense(ua->UA_sense_buffer, - ua->UA_valid_sense_len, - SCST_SENSE_ALL_VALID, - SCST_LOAD_SENSE(scst_sense_reported_luns_data_changed))) { - TRACE_DBG("Freeing not needed " - "REPORTED LUNS DATA CHANGED UA " - "%p", ua); - scst_tgt_dev_del_free_UA(tgt_dev, ua); - break; - } - } - spin_unlock_bh(&tgt_dev->tgt_dev_lock); - } - } - rcu_read_unlock(); - - /* Set the response header */ - dev_cnt *= 8; - put_unaligned_be32(dev_cnt, buffer); - - scst_put_buf_full(cmd, buffer); - - dev_cnt += 8; - if (dev_cnt < cmd->resp_data_len) - scst_set_resp_data_len(cmd, dev_cnt); - -out_compl: - cmd->completed = 1; - - /* Report the result */ - cmd->scst_cmd_done(cmd, SCST_CMD_STATE_DEFAULT, SCST_CONTEXT_SAME); - - TRACE_EXIT_RES(res); - return res; - -out_put_err: - scst_put_buf_full(cmd, buffer); - goto out_compl; -} - -static enum scst_exec_res scst_request_sense_local(struct scst_cmd *cmd) -{ - enum scst_exec_res res = SCST_EXEC_COMPLETED; - struct scst_tgt_dev *tgt_dev = cmd->tgt_dev; - uint8_t *buffer; - int buffer_size = 0, sl = 0; - - TRACE_ENTRY(); - - cmd->status = 0; - cmd->msg_status = 0; - cmd->host_status = DID_OK; - cmd->driver_status = 0; - - buffer_size = scst_get_buf_full_sense(cmd, &buffer); - if (unlikely(buffer_size <= 0)) - goto out_compl; - - memset(buffer, 0, buffer_size); - - spin_lock_bh(&tgt_dev->tgt_dev_lock); - - if (tgt_dev->tgt_dev_valid_sense_len == 0) { - if (test_bit(SCST_TGT_DEV_UA_PENDING, &cmd->tgt_dev->tgt_dev_flags)) { - int rc, size = sizeof(tgt_dev->tgt_dev_sense); - uint8_t *buf; - - spin_unlock_bh(&tgt_dev->tgt_dev_lock); - - buf = kzalloc(size, GFP_KERNEL); - if (buf == NULL) - goto out_put_busy; - - rc = scst_set_pending_UA(cmd, buf, &size); - - spin_lock_bh(&tgt_dev->tgt_dev_lock); - - if (rc == 0) { - if (tgt_dev->tgt_dev_valid_sense_len == 0) { - tgt_dev->tgt_dev_valid_sense_len = size; - memcpy(tgt_dev->tgt_dev_sense, buf, size); - } else { - /* - * Yes, we can loose some of UA data - * here, if UA size is bigger, than - * size, i.e. tgt_dev_sense. - */ - scst_requeue_ua(cmd, buf, size); - } - } - - kfree(buf); - } - if (tgt_dev->tgt_dev_valid_sense_len == 0) - goto out_unlock_put_not_completed; - } - - TRACE(TRACE_SCSI, "%s: Returning stored/UA sense", cmd->op_name); - - if (((scst_sense_response_code(tgt_dev->tgt_dev_sense) == 0x70) || - (scst_sense_response_code(tgt_dev->tgt_dev_sense) == 0x71)) && - (cmd->cdb[1] & 1)) { - PRINT_WARNING("%s: Fixed format of the saved sense, but " - "descriptor format requested. Conversion will " - "truncated data", cmd->op_name); - PRINT_BUFFER("Original sense", tgt_dev->tgt_dev_sense, - tgt_dev->tgt_dev_valid_sense_len); - - buffer_size = min(SCST_STANDARD_SENSE_LEN, buffer_size); - sl = scst_set_sense(buffer, buffer_size, true, - tgt_dev->tgt_dev_sense[2], tgt_dev->tgt_dev_sense[12], - tgt_dev->tgt_dev_sense[13]); - } else if (((scst_sense_response_code(tgt_dev->tgt_dev_sense) == 0x72) || - (scst_sense_response_code(tgt_dev->tgt_dev_sense) == 0x73)) && - !(cmd->cdb[1] & 1)) { - PRINT_WARNING("%s: Descriptor format of the " - "saved sense, but fixed format requested. Conversion " - "will truncate data", cmd->op_name); - PRINT_BUFFER("Original sense", tgt_dev->tgt_dev_sense, - tgt_dev->tgt_dev_valid_sense_len); - - buffer_size = min(SCST_STANDARD_SENSE_LEN, buffer_size); - sl = scst_set_sense(buffer, buffer_size, false, - tgt_dev->tgt_dev_sense[1], tgt_dev->tgt_dev_sense[2], - tgt_dev->tgt_dev_sense[3]); - } else { - if (buffer_size >= tgt_dev->tgt_dev_valid_sense_len) - sl = tgt_dev->tgt_dev_valid_sense_len; - else { - sl = buffer_size; - TRACE(TRACE_SCSI|TRACE_MINOR, "%s: Being returned sense " - "truncated to size %d (needed %d)", cmd->op_name, - buffer_size, tgt_dev->tgt_dev_valid_sense_len); - } - memcpy(buffer, tgt_dev->tgt_dev_sense, sl); - } - - tgt_dev->tgt_dev_valid_sense_len = 0; - - spin_unlock_bh(&tgt_dev->tgt_dev_lock); - - scst_put_buf_full(cmd, buffer); - - scst_set_resp_data_len(cmd, sl); - -out_compl: - cmd->completed = 1; - - /* Report the result */ - cmd->scst_cmd_done(cmd, SCST_CMD_STATE_DEFAULT, SCST_CONTEXT_SAME); - -out: - TRACE_EXIT_RES(res); - return res; - -out_put_busy: - scst_put_buf_full(cmd, buffer); - scst_set_busy(cmd); - goto out_compl; - -out_unlock_put_not_completed: - spin_unlock_bh(&tgt_dev->tgt_dev_lock); - scst_put_buf_full(cmd, buffer); - res = SCST_EXEC_NOT_COMPLETED; - goto out; -} - -static int scst_report_supported_tm_fns(struct scst_cmd *cmd) -{ - int res = SCST_EXEC_COMPLETED; - int length, resp_len = 0; - uint8_t *address; - uint8_t buf[16]; - - TRACE_ENTRY(); - - length = scst_get_buf_full_sense(cmd, &address); - TRACE_DBG("length %d", length); - if (unlikely(length <= 0)) - goto out_compl; - - memset(buf, 0, sizeof(buf)); - - buf[0] = 0xF8; /* ATS, ATSS, CACAS, CTSS, LURS */ - buf[1] = 0; - if ((cmd->cdb[2] & 0x80) == 0) - resp_len = 4; - else { - buf[3] = 0x0C; -#if 1 - buf[4] = 1; /* TMFTMOV */ - buf[6] = 0xA0; /* ATTS, CACATS */ - put_unaligned_be32(300, &buf[8]); /* long timeout - 30 sec. */ - put_unaligned_be32(150, &buf[12]); /* short timeout - 15 sec. */ -#endif - resp_len = 16; - } - - if (length > resp_len) - length = resp_len; - memcpy(address, buf, length); - - scst_put_buf_full(cmd, address); - if (length < cmd->resp_data_len) - scst_set_resp_data_len(cmd, length); - -out_compl: - cmd->completed = 1; - - /* Report the result */ - cmd->scst_cmd_done(cmd, SCST_CMD_STATE_DEFAULT, SCST_CONTEXT_SAME); - - TRACE_EXIT_RES(res); - return res; -} - -static int scst_report_supported_opcodes(struct scst_cmd *cmd) -{ - int res = SCST_EXEC_COMPLETED; - int length, buf_len, i, offs; - uint8_t *address; - uint8_t *buf; - bool inline_buf; - bool rctd = cmd->cdb[2] >> 7; - int options = cmd->cdb[2] & 7; - int req_opcode = cmd->cdb[3]; - int req_sa = get_unaligned_be16(&cmd->cdb[4]); - const struct scst_opcode_descriptor *op = NULL; - const struct scst_opcode_descriptor **supp_opcodes = NULL; - int supp_opcodes_cnt, rc; - - TRACE_ENTRY(); - - /* get_cdb_info_min() ensures that get_supported_opcodes is not NULL here */ - - rc = cmd->devt->get_supported_opcodes(cmd, &supp_opcodes, &supp_opcodes_cnt); - if (rc != 0) - goto out_compl; - - TRACE_DBG("cmd %p, options %d, req_opcode %x, req_sa %x, rctd %d", - cmd, options, req_opcode, req_sa, rctd); - - switch (options) { - case 0: /* all */ - buf_len = 4; - for (i = 0; i < supp_opcodes_cnt; i++) { - buf_len += 8; - if (rctd) - buf_len += 12; - } - break; - case 1: - buf_len = 0; - for (i = 0; i < supp_opcodes_cnt; i++) { - if (req_opcode == supp_opcodes[i]->od_opcode) { - op = supp_opcodes[i]; - if (op->od_serv_action_valid) { - TRACE(TRACE_MINOR, "Requested opcode %x " - "with unexpected service action " - "(dev %s, initiator %s)", - req_opcode, cmd->dev->virt_name, - cmd->sess->initiator_name); - scst_set_invalid_field_in_cdb(cmd, 2, - SCST_INVAL_FIELD_BIT_OFFS_VALID | 0); - goto out_compl; - } - buf_len = 4 + op->od_cdb_size; - if (rctd) - buf_len += 12; - break; - } - } - if (op == NULL) { - TRACE(TRACE_MINOR, "Requested opcode %x not found " - "(dev %s, initiator %s)", req_opcode, - cmd->dev->virt_name, cmd->sess->initiator_name); - buf_len = 4; - } - break; - case 2: - buf_len = 0; - for (i = 0; i < supp_opcodes_cnt; i++) { - if (req_opcode == supp_opcodes[i]->od_opcode) { - op = supp_opcodes[i]; - if (!op->od_serv_action_valid) { - TRACE(TRACE_MINOR, "Requested opcode %x " - "without expected service action " - "(dev %s, initiator %s)", - req_opcode, cmd->dev->virt_name, - cmd->sess->initiator_name); - scst_set_invalid_field_in_cdb(cmd, 2, - SCST_INVAL_FIELD_BIT_OFFS_VALID | 0); - goto out_compl; - } - if (req_sa != op->od_serv_action) { - op = NULL; /* reset it */ - continue; - } - buf_len = 4 + op->od_cdb_size; - if (rctd) - buf_len += 12; - break; - } - } - if (op == NULL) { - TRACE(TRACE_MINOR, "Requested opcode %x/%x not found " - "(dev %s, initiator %s)", req_opcode, req_sa, - cmd->dev->virt_name, cmd->sess->initiator_name); - buf_len = 4; - } - break; - default: - PRINT_ERROR("REPORT SUPPORTED OPERATION CODES: REPORTING OPTIONS " - "%x not supported (dev %s, initiator %s)", options, - cmd->dev->virt_name, cmd->sess->initiator_name); - scst_set_invalid_field_in_cdb(cmd, 2, - SCST_INVAL_FIELD_BIT_OFFS_VALID | 0); - goto out_compl; - } - - length = scst_get_buf_full_sense(cmd, &address); - TRACE_DBG("length %d, buf_len %d, op %p", length, buf_len, op); - if (unlikely(length <= 0)) - goto out_compl; - - if (length >= buf_len) { - buf = address; - inline_buf = true; - } else { - buf = vmalloc(buf_len); /* it can be big */ - if (buf == NULL) { - PRINT_ERROR("Unable to allocate REPORT SUPPORTED " - "OPERATION CODES buffer with size %d", buf_len); - scst_set_busy(cmd); - goto out_err_put; - } - inline_buf = false; - } - - memset(buf, 0, buf_len); - - switch (options) { - case 0: /* all */ - put_unaligned_be32(buf_len - 4, &buf[0]); - offs = 4; - for (i = 0; i < supp_opcodes_cnt; i++) { - op = supp_opcodes[i]; - buf[offs] = op->od_opcode; - if (op->od_serv_action_valid) { - put_unaligned_be16(op->od_serv_action, &buf[offs + 2]); - buf[offs + 5] |= 1; - } - put_unaligned_be16(op->od_cdb_size, &buf[offs + 6]); - offs += 8; - if (rctd) { - buf[(offs - 8) + 5] |= 2; - buf[offs + 1] = 0xA; - buf[offs + 3] = op->od_comm_specific_timeout; - put_unaligned_be32(op->od_nominal_timeout, &buf[offs + 4]); - put_unaligned_be32(op->od_recommended_timeout, &buf[offs + 8]); - offs += 12; - } - } - break; - case 1: - case 2: - if (op != NULL) { - buf[1] |= op->od_support; - put_unaligned_be16(op->od_cdb_size, &buf[2]); - memcpy(&buf[4], op->od_cdb_usage_bits, op->od_cdb_size); - if (rctd) { - buf[1] |= 0x80; - offs = 4 + op->od_cdb_size; - buf[offs + 1] = 0xA; - buf[offs + 3] = op->od_comm_specific_timeout; - put_unaligned_be32(op->od_nominal_timeout, &buf[offs + 4]); - put_unaligned_be32(op->od_recommended_timeout, &buf[offs + 8]); - } - } - break; - default: - sBUG(); - } - - if (length > buf_len) - length = buf_len; - if (!inline_buf) { - memcpy(address, buf, length); - vfree(buf); - } - - scst_put_buf_full(cmd, address); - if (length < cmd->resp_data_len) - scst_set_resp_data_len(cmd, length); - -out_compl: - if ((supp_opcodes != NULL) && (cmd->devt->put_supported_opcodes != NULL)) - cmd->devt->put_supported_opcodes(cmd, supp_opcodes, supp_opcodes_cnt); - - cmd->completed = 1; - - /* Report the result */ - cmd->scst_cmd_done(cmd, SCST_CMD_STATE_DEFAULT, SCST_CONTEXT_SAME); - - TRACE_EXIT_RES(res); - return res; - -out_err_put: - scst_put_buf_full(cmd, address); - goto out_compl; -} - -static enum scst_exec_res scst_maintenance_in(struct scst_cmd *cmd) -{ - enum scst_exec_res res; - - TRACE_ENTRY(); - - switch (cmd->cdb[1] & 0x1f) { - case MI_REPORT_SUPPORTED_TASK_MANAGEMENT_FUNCTIONS: - res = scst_report_supported_tm_fns(cmd); - break; - case MI_REPORT_SUPPORTED_OPERATION_CODES: - res = scst_report_supported_opcodes(cmd); - break; - default: - res = SCST_EXEC_NOT_COMPLETED; - break; - } - - TRACE_EXIT_RES(res); - return res; -} - -static enum scst_exec_res scst_reserve_local(struct scst_cmd *cmd) -{ - enum scst_exec_res res = SCST_EXEC_NOT_COMPLETED; - struct scst_device *dev; - struct scst_lksb pr_lksb; - - TRACE_ENTRY(); - - if (cmd->sess->sess_mq) { - PRINT_WARNING_ONCE("MQ session (%p) from initiator %s (tgt %s), " - "reservations not supported", cmd->sess, - cmd->sess->initiator_name, cmd->sess->tgt->tgt_name); - scst_set_cmd_error(cmd, SCST_LOAD_SENSE(scst_sense_invalid_opcode)); - goto out_done; - } - - if ((cmd->cdb[0] == RESERVE_10) && (cmd->cdb[2] & SCST_RES_3RDPTY)) { - PRINT_ERROR("RESERVE_10: 3rdPty RESERVE not implemented " - "(lun=%lld)", (unsigned long long)cmd->lun); - scst_set_invalid_field_in_cdb(cmd, 2, - SCST_INVAL_FIELD_BIT_OFFS_VALID | 4); - goto out_done; - } - - dev = cmd->dev; - - /* - * There's no need to block this device, even for - * SCST_TST_0_SINGLE_TASK_SET, or anyhow else protect reservations - * changes, because: - * - * 1. The reservation changes are (rather) atomic, i.e., in contrast - * to persistent reservations, don't have any invalid intermediate - * states during being changed. - * - * 2. It's a duty of initiators to ensure order of regular commands - * around the reservation command either by ORDERED attribute, or by - * queue draining, or etc. For case of SCST_TST_0_SINGLE_TASK_SET - * there are no target drivers which can ensure even for ORDERED - * commands order of their delivery, so, because initiators know - * it, also there's no point to do any extra protection actions. - */ - - if (!list_empty(&dev->dev_registrants_list)) { - if (scst_pr_crh_case(cmd)) - goto out_completed; - else { - scst_set_cmd_error_status(cmd, - SAM_STAT_RESERVATION_CONFLICT); - goto out_done; - } - } - - scst_res_lock(dev, &pr_lksb); - if (scst_is_not_reservation_holder(dev, cmd->sess)) { - scst_res_unlock(dev, &pr_lksb); - scst_set_cmd_error_status(cmd, SAM_STAT_RESERVATION_CONFLICT); - goto out_done; - } - scst_reserve_dev(dev, cmd->sess); - scst_res_unlock(dev, &pr_lksb); - -out: - TRACE_EXIT_RES(res); - return res; - -out_completed: - cmd->completed = 1; - -out_done: - /* Report the result */ - cmd->scst_cmd_done(cmd, SCST_CMD_STATE_DEFAULT, SCST_CONTEXT_SAME); - res = SCST_EXEC_COMPLETED; - goto out; -} - -static enum scst_exec_res scst_release_local(struct scst_cmd *cmd) -{ - enum scst_exec_res res = SCST_EXEC_NOT_COMPLETED; - struct scst_device *dev; - struct scst_lksb pr_lksb; - - TRACE_ENTRY(); - - if (cmd->sess->sess_mq) { - PRINT_WARNING_ONCE("MQ session (%p) from initiator %s (tgt %s), " - "reservations not supported", cmd->sess, - cmd->sess->initiator_name, cmd->sess->tgt->tgt_name); - scst_set_cmd_error(cmd, SCST_LOAD_SENSE(scst_sense_invalid_opcode)); - goto out_done; - } - - dev = cmd->dev; - - /* - * See comment in scst_reserve_local() why no dev blocking or any - * other protection is needed here. - */ - - if (!list_empty(&dev->dev_registrants_list)) { - if (scst_pr_crh_case(cmd)) - goto out_completed; - else { - scst_set_cmd_error_status(cmd, - SAM_STAT_RESERVATION_CONFLICT); - goto out_done; - } - } - - scst_res_lock(dev, &pr_lksb); - - /* - * The device could be RELEASED behind us, if RESERVING session - * is closed (see scst_free_tgt_dev()), but this actually doesn't - * matter, so use lock and no retest for DEV_RESERVED bits again - */ - if (scst_is_not_reservation_holder(dev, cmd->sess)) { - /* - * SPC-2 requires to report SCSI status GOOD if a RELEASE - * command fails because a reservation is held by another - * session. - */ - res = SCST_EXEC_COMPLETED; - cmd->status = 0; - cmd->msg_status = 0; - cmd->host_status = DID_OK; - cmd->driver_status = 0; - cmd->completed = 1; - } else { - scst_clear_dev_reservation(dev); - } - - scst_res_unlock(dev, &pr_lksb); - - if (res == SCST_EXEC_COMPLETED) - goto out_done; - -out: - TRACE_EXIT_RES(res); - return res; - -out_completed: - cmd->completed = 1; - -out_done: - res = SCST_EXEC_COMPLETED; - /* Report the result */ - cmd->scst_cmd_done(cmd, SCST_CMD_STATE_DEFAULT, SCST_CONTEXT_SAME); - goto out; -} - -/* No locks, no IRQ or IRQ-disabled context allowed */ -static enum scst_exec_res scst_persistent_reserve_in_local(struct scst_cmd *cmd) -{ - struct scst_device *dev; - struct scst_tgt_dev *tgt_dev; - struct scst_session *session; - int action; - uint8_t *buffer; - int buffer_size; - - TRACE_ENTRY(); - - EXTRACHECKS_BUG_ON(scst_cmd_atomic(cmd)); - - dev = cmd->dev; - tgt_dev = cmd->tgt_dev; - session = cmd->sess; - - if (session->sess_mq) { - PRINT_WARNING_ONCE("MQ session %p from initiator %s (tgt %s), " - "persistent reservations not supported", session, - session->initiator_name, session->tgt->tgt_name); - scst_set_cmd_error(cmd, SCST_LOAD_SENSE(scst_sense_invalid_opcode)); - goto out_done; - } - - if (unlikely(dev->not_pr_supporting_tgt_devs_num != 0)) { - PRINT_WARNING("Persistent Reservation command %s refused for " - "device %s, because the device has not supporting PR " - "transports connected", scst_get_opcode_name(cmd), - dev->virt_name); - scst_set_cmd_error(cmd, - SCST_LOAD_SENSE(scst_sense_invalid_opcode)); - goto out_done; - } - - if (scst_dev_reserved(dev)) { - TRACE_PR("PR command rejected, because device %s holds regular " - "reservation", dev->virt_name); - scst_set_cmd_error_status(cmd, SAM_STAT_RESERVATION_CONFLICT); - goto out_done; - } - -#ifndef CONFIG_SCST_FORWARD_MODE_PASS_THROUGH - if (dev->scsi_dev != NULL) { - PRINT_WARNING("PR commands for pass-through devices not " - "supported (device %s)", dev->virt_name); - scst_set_cmd_error(cmd, - SCST_LOAD_SENSE(scst_sense_invalid_opcode)); - goto out_done; - } -#endif - - buffer_size = scst_get_buf_full_sense(cmd, &buffer); - if (unlikely(buffer_size <= 0)) - goto out_done; - - scst_pr_read_lock(dev); - - /* We can be aborted by another PR command while waiting for the lock */ - if (unlikely(test_bit(SCST_CMD_ABORTED, &cmd->cmd_flags))) { - TRACE_MGMT_DBG("ABORTED set, aborting cmd %p", cmd); - goto out_unlock; - } - - action = cmd->cdb[1] & 0x1f; - - TRACE(TRACE_SCSI, "PR IN action %x for '%s' (LUN %llx) from '%s'", - action, dev->virt_name, tgt_dev->lun, session->initiator_name); - - switch (action) { - case PR_READ_KEYS: - scst_pr_read_keys(cmd, buffer, buffer_size); - break; - case PR_READ_RESERVATION: - scst_pr_read_reservation(cmd, buffer, buffer_size); - break; - case PR_REPORT_CAPS: - scst_pr_report_caps(cmd, buffer, buffer_size); - break; - case PR_READ_FULL_STATUS: - scst_pr_read_full_status(cmd, buffer, buffer_size); - break; - default: - PRINT_ERROR("Unsupported action %x", action); - goto out_unsup_act; - } - -out_complete: - cmd->completed = 1; - -out_unlock: - scst_pr_read_unlock(dev); - - scst_put_buf_full(cmd, buffer); - -out_done: - cmd->scst_cmd_done(cmd, SCST_CMD_STATE_DEFAULT, SCST_CONTEXT_SAME); - - TRACE_EXIT_RES(SCST_EXEC_COMPLETED); - return SCST_EXEC_COMPLETED; - -out_unsup_act: - scst_set_invalid_field_in_cdb(cmd, 1, - SCST_INVAL_FIELD_BIT_OFFS_VALID | 0); - goto out_complete; -} - -/* No locks, no IRQ or IRQ-disabled context allowed */ -static enum scst_exec_res -scst_persistent_reserve_out_local(struct scst_cmd *cmd) -{ - enum scst_exec_res res = SCST_EXEC_COMPLETED; - struct scst_device *dev; - struct scst_tgt_dev *tgt_dev; - struct scst_session *session; - int action; - uint8_t *buffer; - int buffer_size; - struct scst_lksb pr_lksb; - bool aborted = false; - - TRACE_ENTRY(); - - EXTRACHECKS_BUG_ON(scst_cmd_atomic(cmd)); - - dev = cmd->dev; - tgt_dev = cmd->tgt_dev; - session = cmd->sess; - - if (session->sess_mq) { - PRINT_WARNING_ONCE("MQ session (%p) from initiator %s (tgt %s), " - "persistent reservations not supported", session, - session->initiator_name, session->tgt->tgt_name); - scst_set_cmd_error(cmd, SCST_LOAD_SENSE(scst_sense_invalid_opcode)); - goto out_done; - } - - if (unlikely(dev->not_pr_supporting_tgt_devs_num != 0)) { - PRINT_WARNING("Persistent Reservation command %s refused for " - "device %s, because the device has not supporting PR " - "transports connected", scst_get_opcode_name(cmd), - dev->virt_name); - scst_set_cmd_error(cmd, SCST_LOAD_SENSE(scst_sense_invalid_opcode)); - goto out_done; - } - - action = cmd->cdb[1] & 0x1f; - - TRACE(TRACE_SCSI, "PR OUT action %x for '%s' (LUN %llx) from '%s'", - action, dev->virt_name, tgt_dev->lun, session->initiator_name); - - if (scst_dev_reserved(dev)) { - TRACE_PR("PR command rejected, because device %s holds regular " - "reservation", dev->virt_name); - scst_set_cmd_error_status(cmd, SAM_STAT_RESERVATION_CONFLICT); - goto out_done; - } - - buffer_size = scst_get_buf_full_sense(cmd, &buffer); - if (unlikely(buffer_size <= 0)) - goto out_done; - - dev->cl_ops->pr_write_lock(dev, &pr_lksb); - - /* - * Check if tgt_dev already registered. Also by this check we make - * sure that table "PERSISTENT RESERVE OUT service actions that are - * allowed in the presence of various reservations" is honored. - * REGISTER AND MOVE and RESERVE will be additionally checked for - * conflicts later. - */ - if ((action != PR_REGISTER) && (action != PR_REGISTER_AND_IGNORE) && - (tgt_dev->registrant == NULL)) { - TRACE_PR("'%s' not registered", cmd->sess->initiator_name); - scst_set_cmd_error_status(cmd, SAM_STAT_RESERVATION_CONFLICT); - goto out_unlock; - } - - /* Check scope */ - if ((action != PR_REGISTER) && (action != PR_REGISTER_AND_IGNORE) && - (action != PR_CLEAR) && (cmd->cdb[2] >> 4) != SCOPE_LU) { - TRACE_PR("Scope must be SCOPE_LU for action %x", action); - scst_set_invalid_field_in_cdb(cmd, 2, - SCST_INVAL_FIELD_BIT_OFFS_VALID | 4); - goto out_unlock; - } - - /* Check SPEC_I_PT (PR_REGISTER_AND_MOVE has another format) */ - if ((action != PR_REGISTER) && (action != PR_REGISTER_AND_MOVE) && - ((buffer[20] >> 3) & 0x01)) { - TRACE_PR("SPEC_I_PT must be zero for action %x", action); - scst_set_invalid_field_in_parm_list(cmd, 20, - SCST_INVAL_FIELD_BIT_OFFS_VALID | 3); - goto out_unlock; - } - - /* Check ALL_TG_PT (PR_REGISTER_AND_MOVE has another format) */ - if ((action != PR_REGISTER) && (action != PR_REGISTER_AND_IGNORE) && - (action != PR_REGISTER_AND_MOVE) && ((buffer[20] >> 2) & 0x01)) { - TRACE_PR("ALL_TG_PT must be zero for action %x", action); - scst_set_invalid_field_in_parm_list(cmd, 20, - SCST_INVAL_FIELD_BIT_OFFS_VALID | 2); - goto out_unlock; - } - - /* We can be aborted by another PR command while waiting for the lock */ - aborted = test_bit(SCST_CMD_ABORTED, &cmd->cmd_flags); - if (unlikely(aborted)) { - TRACE_MGMT_DBG("ABORTED set, aborting cmd %p", cmd); - goto out_unlock; - } - - switch (action) { - case PR_REGISTER: - scst_pr_register(cmd, buffer, buffer_size); - break; - case PR_RESERVE: - scst_pr_reserve(cmd, buffer, buffer_size); - break; - case PR_RELEASE: - scst_pr_release(cmd, buffer, buffer_size); - break; - case PR_CLEAR: - scst_pr_clear(cmd, buffer, buffer_size); - break; - case PR_PREEMPT: - scst_pr_preempt(cmd, buffer, buffer_size); - break; - case PR_PREEMPT_AND_ABORT: - scst_pr_preempt_and_abort(cmd, buffer, buffer_size); - break; - case PR_REGISTER_AND_IGNORE: - scst_pr_register_and_ignore(cmd, buffer, buffer_size); - break; - case PR_REGISTER_AND_MOVE: - scst_pr_register_and_move(cmd, buffer, buffer_size); - break; - default: - scst_set_invalid_field_in_cdb(cmd, 1, - SCST_INVAL_FIELD_BIT_OFFS_VALID | 0); - goto out_unlock; - } - - if (cmd->status == SAM_STAT_GOOD) - scst_pr_sync_device_file(dev); - - if ((cmd->devt->pr_cmds_notifications) && - (cmd->status == SAM_STAT_GOOD)) /* sync file may change status */ - res = SCST_EXEC_NOT_COMPLETED; - -out_unlock: - dev->cl_ops->pr_write_unlock(dev, &pr_lksb); - - scst_put_buf_full(cmd, buffer); - -out_done: - if (res == SCST_EXEC_COMPLETED) { - if (!aborted) - cmd->completed = 1; - cmd->scst_cmd_done(cmd, SCST_CMD_STATE_DEFAULT, - SCST_CONTEXT_SAME); - } - - TRACE_EXIT_RES(res); - return res; -} - /* * __scst_check_local_events() - check if there are any local SCSI events *