| /* |
| * This file is part of iser target kernel module. |
| * |
| * Copyright (c) 2013 - 2014 Mellanox Technologies. All rights reserved. |
| * Copyright (c) 2013 - 2014 Yan Burman (yanb@mellanox.com) |
| * |
| * This software is available to you under a choice of one of two |
| * licenses. You may choose to be licensed under the terms of the GNU |
| * General Public License (GPL) Version 2, available from the file |
| * COPYING in the main directory of this source tree, or the |
| * OpenIB.org BSD license below: |
| * |
| * Redistribution and use in source and binary forms, with or |
| * without modification, are permitted provided that the following |
| * conditions are met: |
| * |
| * - Redistributions of source code must retain the above |
| * copyright notice, this list of conditions and the following |
| * disclaimer. |
| * |
| * - Redistributions in binary form must reproduce the above |
| * copyright notice, this list of conditions and the following |
| * disclaimer in the documentation and/or other materials |
| * provided with the distribution. |
| * |
| * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
| * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
| * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |
| * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS |
| * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN |
| * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN |
| * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
| * SOFTWARE. |
| */ |
| |
| #include <linux/module.h> |
| #include <linux/moduleparam.h> |
| #include <linux/init.h> |
| #include <linux/in.h> |
| #include <linux/in6.h> |
| |
| #ifdef INSIDE_KERNEL_TREE |
| #include <scst/iscsit_transport.h> |
| #else |
| #include "iscsit_transport.h" |
| #endif |
| #include "isert_dbg.h" |
| #include "isert.h" |
| #include "iser.h" |
| #include "iser_datamover.h" |
| |
| #if defined(CONFIG_SCST_DEBUG) || defined(CONFIG_SCST_TRACING) |
| unsigned long isert_trace_flag = ISERT_DEFAULT_LOG_FLAGS; |
| #endif |
| |
| static unsigned int isert_nr_devs = ISERT_NR_DEVS; |
| module_param(isert_nr_devs, uint, S_IRUGO); |
| MODULE_PARM_DESC(isert_nr_devs, |
| "Maximum concurrent number of connection requests to handle (up to 999)."); |
| |
| static void isert_mark_conn_closed(struct iscsi_conn *conn, int flags) |
| { |
| TRACE_ENTRY(); |
| if (flags & ISCSI_CONN_ACTIVE_CLOSE) |
| conn->active_close = 1; |
| if (flags & ISCSI_CONN_DELETING) |
| conn->deleting = 1; |
| |
| conn->read_state = 0; |
| |
| if (!conn->closing) { |
| conn->closing = 1; |
| schedule_work(&conn->close_work); |
| } |
| |
| TRACE_EXIT(); |
| } |
| |
| static void isert_close_conn(struct iscsi_conn *conn, int flags) |
| { |
| struct isert_conn_dev *dev; |
| |
| dev = isert_get_priv(conn); |
| if (dev) |
| dev->state = CS_DISCONNECTED; |
| } |
| |
| static int isert_receive_cmnd_data(struct iscsi_cmnd *cmnd) |
| { |
| #ifdef CONFIG_SCST_EXTRACHECKS |
| if (cmnd->scst_state == ISCSI_CMD_STATE_RX_CMD) |
| TRACE_DBG("cmnd %p is still in RX_CMD state", |
| cmnd); |
| #endif |
| EXTRACHECKS_BUG_ON(cmnd->scst_state != ISCSI_CMD_STATE_AFTER_PREPROC); |
| return 0; |
| } |
| |
| static void isert_update_len_sn(struct iscsi_cmnd *cmnd) |
| { |
| TRACE_ENTRY(); |
| |
| iscsi_cmnd_set_length(&cmnd->pdu); |
| switch (cmnd_opcode(cmnd)) { |
| case ISCSI_OP_NOP_IN: |
| if (cmnd->pdu.bhs.itt == ISCSI_RESERVED_TAG) |
| cmnd->pdu.bhs.sn = (__force u32)cmnd_set_sn(cmnd, 0); |
| else |
| cmnd_set_sn(cmnd, 1); |
| break; |
| case ISCSI_OP_SCSI_RSP: |
| cmnd_set_sn(cmnd, 1); |
| break; |
| case ISCSI_OP_SCSI_TASK_MGT_RSP: |
| cmnd_set_sn(cmnd, 1); |
| break; |
| case ISCSI_OP_TEXT_RSP: |
| cmnd_set_sn(cmnd, 1); |
| break; |
| case ISCSI_OP_SCSI_DATA_IN: |
| { |
| struct iscsi_data_in_hdr *rsp = |
| (struct iscsi_data_in_hdr *)&cmnd->pdu.bhs; |
| |
| cmnd_set_sn(cmnd, (rsp->flags & ISCSI_FLG_FINAL) ? 1 : 0); |
| break; |
| } |
| case ISCSI_OP_LOGOUT_RSP: |
| cmnd_set_sn(cmnd, 1); |
| break; |
| case ISCSI_OP_R2T: |
| cmnd->pdu.bhs.sn = (__force u32)cmnd_set_sn(cmnd, 0); |
| break; |
| case ISCSI_OP_ASYNC_MSG: |
| cmnd_set_sn(cmnd, 1); |
| break; |
| case ISCSI_OP_REJECT: |
| cmnd_set_sn(cmnd, 1); |
| break; |
| default: |
| PRINT_ERROR("Unexpected cmnd op %x", cmnd_opcode(cmnd)); |
| break; |
| } |
| |
| TRACE_EXIT(); |
| } |
| |
| static int isert_process_all_writes(struct iscsi_conn *conn) |
| { |
| struct iscsi_cmnd *cmnd; |
| int res = 0; |
| |
| TRACE_ENTRY(); |
| |
| while ((cmnd = iscsi_get_send_cmnd(conn)) != NULL) { |
| isert_update_len_sn(cmnd); |
| conn_get(conn); |
| isert_pdu_tx(cmnd); |
| } |
| |
| TRACE_EXIT_RES(res); |
| return res; |
| } |
| |
| static int isert_send_locally(struct iscsi_cmnd *req, unsigned int cmd_count) |
| { |
| int res = 0; |
| |
| TRACE_ENTRY(); |
| |
| req_cmnd_pre_release(req); |
| res = isert_process_all_writes(req->conn); |
| cmnd_put(req); |
| |
| TRACE_EXIT_RES(res); |
| return res; |
| } |
| |
| static struct iscsi_cmnd *isert_cmnd_alloc(struct iscsi_conn *conn, |
| struct iscsi_cmnd *parent) |
| { |
| struct iscsi_cmnd *cmnd; |
| |
| TRACE_ENTRY(); |
| |
| if (likely(parent)) |
| cmnd = isert_alloc_scsi_rsp_pdu(conn); |
| else |
| cmnd = isert_alloc_scsi_fake_pdu(conn); |
| |
| iscsi_cmnd_init(conn, cmnd, parent); |
| |
| TRACE_EXIT(); |
| return cmnd; |
| } |
| |
| static void isert_cmnd_free(struct iscsi_cmnd *cmnd) |
| { |
| struct isert_cmnd *isert_cmnd = container_of(cmnd, struct isert_cmnd, |
| iscsi); |
| |
| TRACE_ENTRY(); |
| |
| #ifdef CONFIG_SCST_EXTRACHECKS |
| if (unlikely(cmnd->on_write_list || cmnd->on_write_timeout_list)) { |
| struct iscsi_scsi_cmd_hdr *req = cmnd_hdr(cmnd); |
| |
| PRINT_CRIT_ERROR("cmnd %p still on some list?, %x, %x, %x, %x, %x, %x, %x", |
| cmnd, req->opcode, req->scb[0], |
| req->flags, req->itt, be32_to_cpu(req->data_length), |
| req->cmd_sn, |
| be32_to_cpu((__force __be32)(cmnd->pdu.datasize))); |
| |
| if (unlikely(cmnd->parent_req)) { |
| struct iscsi_scsi_cmd_hdr *preq = |
| cmnd_hdr(cmnd->parent_req); |
| PRINT_CRIT_ERROR("%p %x %u", preq, preq->opcode, |
| preq->scb[0]); |
| } |
| sBUG(); |
| } |
| #endif |
| if (cmnd->parent_req || isert_cmnd->is_fake_rx) |
| isert_release_tx_pdu(cmnd); |
| else |
| isert_release_rx_pdu(cmnd); |
| |
| TRACE_EXIT(); |
| } |
| |
| static void isert_preprocessing_done(struct iscsi_cmnd *req) |
| { |
| req->scst_state = ISCSI_CMD_STATE_AFTER_PREPROC; |
| } |
| |
| static void isert_set_sense_data(struct iscsi_cmnd *rsp, |
| const u8 *sense_buf, int sense_len) |
| { |
| u8 *buf; |
| |
| buf = sg_virt(rsp->sg) + ISER_HDRS_SZ; |
| |
| memcpy(buf, &rsp->sense_hdr, sizeof(rsp->sense_hdr)); |
| memcpy(&buf[sizeof(rsp->sense_hdr)], sense_buf, sense_len); |
| } |
| |
| static void isert_set_req_data(struct iscsi_cmnd *req, struct iscsi_cmnd *rsp) |
| { |
| memcpy(sg_virt(rsp->sg) + ISER_HDRS_SZ, |
| sg_virt(req->sg) + ISER_HDRS_SZ, req->bufflen); |
| rsp->bufflen = req->bufflen; |
| } |
| |
| static void isert_send_data_rsp(struct iscsi_cmnd *req, u8 *sense, |
| int sense_len, u8 status, int is_send_status) |
| { |
| struct iscsi_cmnd *rsp; |
| |
| TRACE_ENTRY(); |
| |
| sBUG_ON(!is_send_status); |
| |
| rsp = create_status_rsp(req, status, sense, sense_len); |
| |
| isert_update_len_sn(rsp); |
| |
| conn_get(rsp->conn); |
| if (status != SAM_STAT_CHECK_CONDITION) |
| isert_send_data_in(req, rsp); |
| else |
| isert_pdu_tx(rsp); |
| |
| TRACE_EXIT(); |
| } |
| |
| static void isert_make_conn_wr_active(struct iscsi_conn *conn) |
| { |
| isert_process_all_writes(conn); |
| } |
| |
| static int isert_conn_activate(struct iscsi_conn *conn) |
| { |
| return 0; |
| } |
| |
| static void isert_free_conn(struct iscsi_conn *conn) |
| { |
| isert_free_connection(conn); |
| } |
| |
| void isert_handle_close_connection(struct iscsi_conn *conn) |
| { |
| isert_mark_conn_closed(conn, 0); |
| /* |
| * Take care of case where our connection is being closed without |
| * being connected to a session - if connection allocation failed for |
| * some reason. |
| */ |
| if (unlikely(!conn->session)) |
| isert_free_connection(conn); |
| else |
| start_close_conn(conn); |
| } |
| |
| int isert_pdu_rx(struct iscsi_cmnd *cmnd) |
| { |
| int res = 0; |
| scst_data_direction dir; |
| |
| TRACE_ENTRY(); |
| |
| #ifdef CONFIG_SCST_EXTRACHECKS |
| cmnd->conn->rd_task = current; |
| #endif |
| iscsi_cmnd_init(cmnd->conn, cmnd, NULL); |
| cmnd_rx_start(cmnd); |
| |
| if (unlikely(!cmnd->scst_cmd)) { |
| cmnd_rx_end(cmnd); |
| goto out; |
| } |
| |
| if (unlikely(scst_cmd_prelim_completed(cmnd->scst_cmd) || |
| unlikely(cmnd->prelim_compl_flags != 0))) { |
| set_bit(ISCSI_CMD_PRELIM_COMPLETED, &cmnd->prelim_compl_flags); |
| cmnd_rx_end(cmnd); |
| goto out; |
| } |
| |
| dir = scst_cmd_get_data_direction(cmnd->scst_cmd); |
| |
| if (dir & SCST_DATA_WRITE) { |
| res = iscsi_cmnd_set_write_buf(cmnd); |
| if (unlikely(res)) |
| goto out; |
| res = isert_request_data_out(cmnd); |
| cmnd->r2t_len_to_receive = 0; |
| cmnd->r2t_len_to_send = 0; |
| cmnd->outstanding_r2t = 0; |
| } else { |
| cmnd_rx_end(cmnd); |
| } |
| |
| out: |
| TRACE_EXIT_RES(res); |
| return res; |
| } |
| |
| int isert_data_out_ready(struct iscsi_cmnd *cmnd) |
| { |
| int res = 0; |
| |
| TRACE_ENTRY(); |
| #ifdef CONFIG_SCST_EXTRACHECKS |
| cmnd->conn->rd_task = current; |
| #endif |
| cmnd_rx_end(cmnd); |
| |
| TRACE_EXIT_RES(res); |
| return res; |
| } |
| |
| int isert_data_in_sent(struct iscsi_cmnd *din) |
| { |
| return 0; |
| } |
| |
| void isert_pdu_err(struct iscsi_cmnd *pdu) |
| { |
| struct iscsi_conn *conn = pdu->conn; |
| |
| if (!conn->session) /* we are still in login phase */ |
| return; |
| |
| if (pdu->parent_req) { |
| rsp_cmnd_release(pdu); |
| conn_put(conn); |
| } else { |
| /* |
| * we will get multiple pdu errors |
| * for same PDU with multiple RDMAs case |
| */ |
| if (pdu->on_write_timeout_list) |
| req_cmnd_release_force(pdu); |
| } |
| } |
| |
| int isert_pdu_sent(struct iscsi_cmnd *pdu) |
| { |
| struct iscsi_conn *conn = pdu->conn; |
| int res = 0; |
| |
| TRACE_ENTRY(); |
| |
| if (unlikely(pdu->should_close_conn)) { |
| if (pdu->should_close_all_conn) { |
| struct iscsi_target *target = |
| pdu->conn->session->target; |
| |
| PRINT_INFO("Closing all connections for target %x at initiator's %s request", |
| target->tid, conn->session->initiator_name); |
| mutex_lock(&target->target_mutex); |
| target_del_all_sess(target, 0); |
| mutex_unlock(&target->target_mutex); |
| } else { |
| PRINT_INFO("Closing connection %p at initiator's %s request", |
| conn, conn->session->initiator_name); |
| mark_conn_closed(conn); |
| } |
| } |
| |
| /* we may get NULL parent req for login response */ |
| if (likely(pdu->parent_req)) { |
| rsp_cmnd_release(pdu); |
| conn_put(conn); |
| } |
| |
| TRACE_EXIT_RES(res); |
| return res; |
| } |
| |
| static ssize_t isert_get_initiator_ip(struct iscsi_conn *conn, |
| char *buf, int size) |
| { |
| int pos; |
| struct sockaddr_storage ss; |
| size_t addr_len; |
| |
| TRACE_ENTRY(); |
| |
| isert_get_peer_addr(conn, (struct sockaddr *)&ss, &addr_len); |
| |
| switch (ss.ss_family) { |
| case AF_INET: |
| pos = scnprintf(buf, size, |
| "%pI4", &((struct sockaddr_in *)&ss)->sin_addr.s_addr); |
| break; |
| #if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE) |
| case AF_INET6: |
| pos = scnprintf(buf, size, "[%pI6]", |
| &((struct sockaddr_in6 *)&ss)->sin6_addr); |
| break; |
| #endif |
| default: |
| pos = scnprintf(buf, size, "Unknown family %d", |
| ss.ss_family); |
| break; |
| } |
| |
| TRACE_EXIT_RES(pos); |
| return pos; |
| } |
| |
| static struct iscsit_transport isert_transport = { |
| .owner = THIS_MODULE, |
| .name = "iSER", |
| .transport_type = ISCSI_RDMA, |
| .iscsit_conn_alloc = isert_conn_alloc, |
| .iscsit_conn_activate = isert_conn_activate, |
| .iscsit_conn_free = isert_free_conn, |
| .iscsit_alloc_cmd = isert_cmnd_alloc, |
| .iscsit_free_cmd = isert_cmnd_free, |
| .iscsit_preprocessing_done = isert_preprocessing_done, |
| .iscsit_send_data_rsp = isert_send_data_rsp, |
| .iscsit_make_conn_wr_active = isert_make_conn_wr_active, |
| .iscsit_get_initiator_ip = isert_get_initiator_ip, |
| .iscsit_send_locally = isert_send_locally, |
| .iscsit_mark_conn_closed = isert_mark_conn_closed, |
| .iscsit_conn_close = isert_close_conn, |
| .iscsit_set_sense_data = isert_set_sense_data, |
| .iscsit_set_req_data = isert_set_req_data, |
| .iscsit_receive_cmnd_data = isert_receive_cmnd_data, |
| .iscsit_close_all_portals = isert_close_all_portals, |
| }; |
| |
| static void isert_cleanup_module(void) |
| { |
| iscsit_unreg_transport(&isert_transport); |
| isert_cleanup_login_devs(); |
| } |
| |
| static int __init isert_init_module(void) |
| { |
| int ret; |
| |
| if (isert_nr_devs > 999) { |
| PRINT_ERROR("Invalid argument for isert_nr_devs provided: %d", |
| isert_nr_devs); |
| ret = -EINVAL; |
| goto out; |
| } |
| |
| ret = iscsit_reg_transport(&isert_transport); |
| if (unlikely(ret)) |
| goto out; |
| |
| ret = isert_init_login_devs(isert_nr_devs); |
| |
| out: |
| return ret; |
| } |
| |
| MODULE_AUTHOR("Yan Burman"); |
| MODULE_LICENSE("Dual BSD/GPL"); |
| MODULE_IMPORT_NS(SCST); |
| #define DRV_VERSION "3.7.0" "#" __stringify(OFED_FLAVOR) |
| #define DRV_RELDATE "26 December 2022" |
| MODULE_DESCRIPTION("iSER target transport driver " |
| "v" DRV_VERSION " (" DRV_RELDATE ")"); |
| module_init(isert_init_module); |
| module_exit(isert_cleanup_module); |