mirror of
https://github.com/SCST-project/scst.git
synced 2026-09-29 11:15:48 +00:00
According to kernel documentation, this flag should be set if the workqueue will be involved in the kernel's memory reclamation flow. Since it is not, there is no need for the driver's WQ to have this flag set so remove it.
1876 lines
47 KiB
C
1876 lines
47 KiB
C
/*
|
|
* isert_rdma.c
|
|
* 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/kernel.h>
|
|
#include <linux/vmalloc.h>
|
|
#include <linux/in.h>
|
|
#include <linux/in6.h>
|
|
#ifndef INSIDE_KERNEL_TREE
|
|
#include <linux/version.h>
|
|
#endif
|
|
#include <rdma/ib_cm.h>
|
|
|
|
#include "isert_dbg.h"
|
|
#include "iser.h"
|
|
#include "isert.h"
|
|
#include "iser_datamover.h"
|
|
|
|
#define ISER_CQ_ENTRIES (128 * 1024)
|
|
#define ISER_LISTEN_BACKLOG 8
|
|
|
|
static DEFINE_MUTEX(dev_list_mutex);
|
|
|
|
static void isert_portal_free(struct isert_portal *portal);
|
|
static struct rdma_cm_id *
|
|
isert_setup_id(struct isert_portal *portal);
|
|
|
|
static int isert_num_recv_posted_on_err(struct ib_recv_wr *first_ib_wr,
|
|
BAD_WR_MODIFIER struct ib_recv_wr *bad_wr)
|
|
{
|
|
struct ib_recv_wr *wr;
|
|
int num_posted = 0;
|
|
|
|
for (wr = first_ib_wr; wr != NULL && wr != bad_wr; wr = wr->next)
|
|
num_posted++;
|
|
|
|
return num_posted;
|
|
}
|
|
|
|
int isert_post_recv(struct isert_conn *isert_conn,
|
|
struct isert_wr *first_wr,
|
|
int num_wr)
|
|
{
|
|
struct ib_recv_wr *first_ib_wr = &first_wr->recv_wr;
|
|
BAD_WR_MODIFIER struct ib_recv_wr *bad_wr;
|
|
int num_posted;
|
|
int err;
|
|
|
|
TRACE_ENTRY();
|
|
|
|
#ifdef CONFIG_SCST_EXTRACHECKS
|
|
if (test_bit(ISERT_DRAIN_POSTED, &isert_conn->flags)) {
|
|
PRINT_ERROR("conn:%p post recv after drain", isert_conn);
|
|
BUG();
|
|
}
|
|
#endif
|
|
|
|
err = ib_post_recv(isert_conn->qp, first_ib_wr, &bad_wr);
|
|
if (unlikely(err)) {
|
|
num_posted = isert_num_recv_posted_on_err(first_ib_wr, bad_wr);
|
|
|
|
PRINT_ERROR("conn:%p recv posted:%d/%d 1st wr_id:0x%llx sz:%d err:%d",
|
|
isert_conn, num_posted, num_wr, first_ib_wr->wr_id,
|
|
first_ib_wr->sg_list->length, err);
|
|
}
|
|
|
|
TRACE_EXIT_RES(err);
|
|
return err;
|
|
}
|
|
|
|
static int isert_num_send_posted_on_err(struct ib_send_wr *first_ib_wr,
|
|
BAD_WR_MODIFIER struct ib_send_wr *bad_wr)
|
|
{
|
|
struct ib_send_wr *wr;
|
|
int num_posted = 0;
|
|
|
|
for (wr = first_ib_wr; wr != NULL && wr != bad_wr; wr = wr->next)
|
|
num_posted++;
|
|
|
|
return num_posted;
|
|
}
|
|
|
|
int isert_post_send(struct isert_conn *isert_conn,
|
|
struct isert_wr *first_wr,
|
|
int num_wr)
|
|
{
|
|
#ifdef USE_PRE_440_WR_STRUCTURE
|
|
struct ib_send_wr *first_ib_wr = &first_wr->send_wr;
|
|
#else
|
|
struct ib_send_wr *first_ib_wr = &first_wr->send_wr.wr;
|
|
#endif
|
|
BAD_WR_MODIFIER struct ib_send_wr *bad_wr;
|
|
int num_posted;
|
|
int err;
|
|
|
|
TRACE_ENTRY();
|
|
|
|
#ifdef CONFIG_SCST_EXTRACHECKS
|
|
if (test_bit(ISERT_DRAIN_POSTED, &isert_conn->flags)) {
|
|
PRINT_ERROR("conn:%p post send after drain", isert_conn);
|
|
BUG();
|
|
}
|
|
#endif
|
|
|
|
err = ib_post_send(isert_conn->qp, first_ib_wr, &bad_wr);
|
|
if (unlikely(err)) {
|
|
num_posted = isert_num_send_posted_on_err(first_ib_wr, bad_wr);
|
|
|
|
PRINT_ERROR("conn:%p send posted:%d/%d bad wr_id:0x%llx sz:%d num_sge: %d err:%d",
|
|
isert_conn, num_posted, num_wr, bad_wr->wr_id,
|
|
bad_wr->sg_list->length, bad_wr->num_sge, err);
|
|
}
|
|
|
|
TRACE_EXIT_RES(err);
|
|
return err;
|
|
}
|
|
|
|
static void isert_post_drain_sq(struct isert_conn *isert_conn)
|
|
{
|
|
BAD_WR_MODIFIER struct ib_send_wr *bad_wr;
|
|
struct isert_wr *drain_wr_sq = &isert_conn->drain_wr_sq;
|
|
int err;
|
|
|
|
isert_wr_set_fields(drain_wr_sq, isert_conn, NULL);
|
|
drain_wr_sq->wr_op = ISER_WR_SEND;
|
|
#ifdef USE_PRE_440_WR_STRUCTURE
|
|
drain_wr_sq->send_wr.wr_id = _ptr_to_u64(drain_wr_sq);
|
|
drain_wr_sq->send_wr.opcode = IB_WR_SEND;
|
|
err = ib_post_send(isert_conn->qp,
|
|
&drain_wr_sq->send_wr, &bad_wr);
|
|
#else
|
|
drain_wr_sq->send_wr.wr.wr_id = _ptr_to_u64(drain_wr_sq);
|
|
drain_wr_sq->send_wr.wr.opcode = IB_WR_SEND;
|
|
err = ib_post_send(isert_conn->qp,
|
|
&drain_wr_sq->send_wr.wr, &bad_wr);
|
|
#endif
|
|
if (unlikely(err)) {
|
|
PRINT_ERROR("Failed to post drain wr to send queue, err:%d",
|
|
err);
|
|
/*
|
|
* We need to decrement iser_conn->kref in order to be able to
|
|
* clean up the connection.
|
|
*/
|
|
set_bit(ISERT_DRAINED_SQ, &isert_conn->flags);
|
|
if (test_bit(ISERT_DRAINED_RQ, &isert_conn->flags))
|
|
isert_sched_conn_free(isert_conn);
|
|
}
|
|
}
|
|
|
|
static void isert_post_drain_rq(struct isert_conn *isert_conn)
|
|
{
|
|
BAD_WR_MODIFIER struct ib_recv_wr *bad_wr;
|
|
struct isert_wr *drain_wr_rq = &isert_conn->drain_wr_rq;
|
|
int err;
|
|
|
|
isert_wr_set_fields(drain_wr_rq, isert_conn, NULL);
|
|
drain_wr_rq->wr_op = ISER_WR_RECV;
|
|
drain_wr_rq->recv_wr.wr_id = _ptr_to_u64(drain_wr_rq);
|
|
err = ib_post_recv(isert_conn->qp,
|
|
&drain_wr_rq->recv_wr, &bad_wr);
|
|
if (unlikely(err)) {
|
|
PRINT_ERROR("Failed to post drain wr to receive queue, err:%d",
|
|
err);
|
|
set_bit(ISERT_DRAINED_RQ, &isert_conn->flags);
|
|
if (test_bit(ISERT_DRAINED_SQ, &isert_conn->flags))
|
|
isert_sched_conn_free(isert_conn);
|
|
}
|
|
}
|
|
|
|
void isert_post_drain(struct isert_conn *isert_conn)
|
|
{
|
|
if (!test_and_set_bit(ISERT_DRAIN_POSTED, &isert_conn->flags)) {
|
|
mutex_lock(&isert_conn->state_mutex);
|
|
isert_conn->state = ISER_CONN_CLOSING;
|
|
isert_post_drain_rq(isert_conn);
|
|
isert_post_drain_sq(isert_conn);
|
|
mutex_unlock(&isert_conn->state_mutex);
|
|
}
|
|
}
|
|
|
|
void isert_conn_disconnect(struct isert_conn *isert_conn)
|
|
{
|
|
int err;
|
|
|
|
mutex_lock(&isert_conn->state_mutex);
|
|
if (isert_conn->state != ISER_CONN_CLOSING) {
|
|
isert_conn->state = ISER_CONN_CLOSING;
|
|
|
|
err = rdma_disconnect(isert_conn->cm_id);
|
|
|
|
if (unlikely(err))
|
|
PRINT_ERROR("Failed to rdma disconnect, err:%d", err);
|
|
}
|
|
mutex_unlock(&isert_conn->state_mutex);
|
|
}
|
|
|
|
static int isert_pdu_handle_hello_req(struct isert_cmnd *pdu)
|
|
{
|
|
PRINT_INFO("iSER Hello not supported");
|
|
return -EINVAL; /* meanwhile disconnect immediately */
|
|
}
|
|
|
|
static int isert_pdu_handle_login_req(struct isert_cmnd *isert_pdu)
|
|
{
|
|
return isert_login_req_rx(&isert_pdu->iscsi);
|
|
}
|
|
|
|
static int isert_pdu_handle_text(struct isert_cmnd *pdu)
|
|
{
|
|
struct iscsi_cmnd *iscsi_cmnd = &pdu->iscsi;
|
|
|
|
iscsi_cmnd->sg_cnt = pdu->buf.sg_cnt;
|
|
iscsi_cmnd->sg = pdu->buf.sg;
|
|
return isert_login_req_rx(iscsi_cmnd);
|
|
}
|
|
|
|
static int isert_pdu_handle_nop_out(struct isert_cmnd *pdu)
|
|
{
|
|
return isert_pdu_rx(&pdu->iscsi);
|
|
}
|
|
|
|
static int isert_pdu_handle_scsi_cmd(struct isert_cmnd *pdu)
|
|
{
|
|
return isert_pdu_rx(&pdu->iscsi);
|
|
}
|
|
|
|
static int isert_pdu_handle_tm_func(struct isert_cmnd *pdu)
|
|
{
|
|
return isert_pdu_rx(&pdu->iscsi);
|
|
}
|
|
|
|
static int isert_pdu_handle_data_out(struct isert_cmnd *pdu)
|
|
{
|
|
PRINT_INFO("iser iscsi data out not supported");
|
|
return -EINVAL; /* meanwhile disconnect immediately */
|
|
}
|
|
|
|
static int isert_pdu_handle_logout(struct isert_cmnd *pdu)
|
|
{
|
|
return isert_pdu_rx(&pdu->iscsi);
|
|
}
|
|
|
|
static int isert_pdu_handle_snack(struct isert_cmnd *pdu)
|
|
{
|
|
PRINT_INFO("iser iscsi SNACK not supported");
|
|
return -EINVAL; /* meanwhile disconnect immediately */
|
|
}
|
|
|
|
static void isert_rx_pdu_parse_headers(struct isert_cmnd *isert_pdu)
|
|
{
|
|
struct iscsi_cmnd *iscsi_cmnd = &isert_pdu->iscsi;
|
|
struct isert_buf *isert_buf = &isert_pdu->buf;
|
|
u8 *addr = isert_buf->addr;
|
|
struct isert_hdr *isert_hdr = (struct isert_hdr *)addr;
|
|
struct iscsi_hdr *bhs = (struct iscsi_hdr *)(addr + sizeof(*isert_hdr));
|
|
unsigned int data_offset = ISER_HDRS_SZ;
|
|
unsigned int ahssize;
|
|
|
|
TRACE_ENTRY();
|
|
|
|
isert_pdu->isert_hdr = isert_hdr;
|
|
isert_pdu->isert_opcode = isert_hdr->flags & 0xf0;
|
|
isert_pdu->is_rstag_valid = isert_hdr->flags & ISER_RSV ? 1 : 0;
|
|
isert_pdu->is_wstag_valid = isert_hdr->flags & ISER_WSV ? 1 : 0;
|
|
|
|
if (isert_pdu->is_rstag_valid) {
|
|
isert_pdu->rem_read_stag = be32_to_cpu(isert_hdr->read_stag);
|
|
isert_pdu->rem_read_va = be64_to_cpu(isert_hdr->read_va);
|
|
}
|
|
|
|
if (isert_pdu->is_wstag_valid) {
|
|
isert_pdu->rem_write_stag = be32_to_cpu(isert_hdr->write_stag);
|
|
isert_pdu->rem_write_va = be64_to_cpu(isert_hdr->write_va);
|
|
}
|
|
|
|
isert_pdu->bhs = bhs;
|
|
isert_pdu->iscsi_opcode = bhs->opcode & ISCSI_OPCODE_MASK;
|
|
|
|
memcpy(&iscsi_cmnd->pdu.bhs, bhs, sizeof(iscsi_cmnd->pdu.bhs));
|
|
iscsi_cmnd_get_length(&iscsi_cmnd->pdu); /* get ahssize and datasize */
|
|
|
|
ahssize = isert_pdu->iscsi.pdu.ahssize;
|
|
if (likely(!ahssize)) {
|
|
isert_pdu->ahs = NULL;
|
|
} else {
|
|
isert_pdu->ahs = addr + ISER_HDRS_SZ;
|
|
data_offset += ahssize;
|
|
}
|
|
iscsi_cmnd->pdu.ahs = isert_pdu->ahs;
|
|
|
|
iscsi_cmnd->bufflen = iscsi_cmnd->pdu.datasize;
|
|
iscsi_cmnd->bufflen = (iscsi_cmnd->bufflen + 3) & ~3;
|
|
if (iscsi_cmnd->bufflen) {
|
|
iscsi_cmnd->sg_cnt = isert_pdu->buf.sg_cnt;
|
|
iscsi_cmnd->sg = isert_pdu->buf.sg;
|
|
} else {
|
|
iscsi_cmnd->sg = NULL;
|
|
}
|
|
|
|
TRACE_EXIT();
|
|
}
|
|
|
|
static void isert_dma_sync_data_for_cpu(struct ib_device *ib_dev,
|
|
struct ib_sge *sge, size_t size)
|
|
{
|
|
size_t to_sync = size > (PAGE_SIZE - ISER_HDRS_SZ) ?
|
|
(PAGE_SIZE - ISER_HDRS_SZ) : size;
|
|
ib_dma_sync_single_for_cpu(ib_dev, sge->addr + ISER_HDRS_SZ,
|
|
to_sync,
|
|
DMA_FROM_DEVICE);
|
|
|
|
size -= to_sync;
|
|
while (size) {
|
|
++sge;
|
|
to_sync = size > PAGE_SIZE ? PAGE_SIZE : size;
|
|
ib_dma_sync_single_for_cpu(ib_dev, sge->addr,
|
|
to_sync,
|
|
DMA_FROM_DEVICE);
|
|
|
|
size -= to_sync;
|
|
}
|
|
}
|
|
|
|
static void isert_recv_completion_handler(struct isert_wr *wr)
|
|
{
|
|
struct isert_cmnd *pdu = wr->pdu;
|
|
struct ib_sge *sge = wr->sge_list;
|
|
struct ib_device *ib_dev = wr->isert_dev->ib_dev;
|
|
int err;
|
|
|
|
TRACE_ENTRY();
|
|
|
|
ib_dma_sync_single_for_cpu(ib_dev, sge->addr,
|
|
ISER_HDRS_SZ,
|
|
DMA_FROM_DEVICE);
|
|
isert_rx_pdu_parse_headers(pdu);
|
|
isert_dma_sync_data_for_cpu(ib_dev, sge, pdu->iscsi.pdu.datasize +
|
|
pdu->iscsi.pdu.ahssize);
|
|
|
|
switch (pdu->isert_opcode) {
|
|
case ISER_ISCSI_CTRL:
|
|
switch (pdu->iscsi_opcode) {
|
|
case ISCSI_OP_NOP_OUT:
|
|
err = isert_pdu_handle_nop_out(pdu);
|
|
break;
|
|
case ISCSI_OP_SCSI_CMD:
|
|
err = isert_pdu_handle_scsi_cmd(pdu);
|
|
break;
|
|
case ISCSI_OP_SCSI_TASK_MGT_MSG:
|
|
err = isert_pdu_handle_tm_func(pdu);
|
|
break;
|
|
case ISCSI_OP_LOGIN_CMD:
|
|
err = isert_pdu_handle_login_req(pdu);
|
|
break;
|
|
case ISCSI_OP_TEXT_CMD:
|
|
err = isert_pdu_handle_text(pdu);
|
|
break;
|
|
case ISCSI_OP_SCSI_DATA_OUT:
|
|
err = isert_pdu_handle_data_out(pdu);
|
|
break;
|
|
case ISCSI_OP_LOGOUT_CMD:
|
|
err = isert_pdu_handle_logout(pdu);
|
|
break;
|
|
case ISCSI_OP_SNACK_CMD:
|
|
err = isert_pdu_handle_snack(pdu);
|
|
break;
|
|
default:
|
|
PRINT_ERROR("Unexpected iscsi opcode:0x%x",
|
|
pdu->iscsi_opcode);
|
|
err = -EINVAL;
|
|
break;
|
|
}
|
|
break;
|
|
case ISER_HELLO:
|
|
err = isert_pdu_handle_hello_req(pdu);
|
|
break;
|
|
default:
|
|
PRINT_ERROR("malformed isert_hdr, iser op:%x flags 0x%02x",
|
|
pdu->isert_opcode, pdu->isert_hdr->flags);
|
|
err = -EINVAL;
|
|
break;
|
|
}
|
|
|
|
if (unlikely(err)) {
|
|
PRINT_ERROR("err:%d while handling iser pdu", err);
|
|
isert_conn_disconnect(wr->conn);
|
|
}
|
|
|
|
TRACE_EXIT();
|
|
}
|
|
|
|
static void isert_send_completion_handler(struct isert_wr *wr)
|
|
{
|
|
struct isert_cmnd *isert_pdu = wr->pdu;
|
|
struct iscsi_cmnd *iscsi_pdu = &isert_pdu->iscsi;
|
|
struct iscsi_cmnd *iscsi_req_pdu = iscsi_pdu->parent_req;
|
|
struct isert_cmnd *isert_req_pdu = container_of(iscsi_req_pdu,
|
|
struct isert_cmnd, iscsi);
|
|
|
|
|
|
TRACE_ENTRY();
|
|
|
|
if (iscsi_req_pdu && iscsi_req_pdu->bufflen &&
|
|
isert_req_pdu->is_rstag_valid)
|
|
isert_data_in_sent(iscsi_req_pdu);
|
|
|
|
isert_pdu_sent(iscsi_pdu);
|
|
|
|
TRACE_EXIT();
|
|
}
|
|
|
|
static void isert_rdma_rd_completion_handler(struct isert_wr *wr)
|
|
{
|
|
struct isert_buf *isert_buf = wr->buf;
|
|
struct isert_device *isert_dev = wr->isert_dev;
|
|
struct ib_device *ib_dev = isert_dev->ib_dev;
|
|
|
|
ib_dma_unmap_sg(ib_dev, isert_buf->sg, isert_buf->sg_cnt,
|
|
isert_buf->dma_dir);
|
|
isert_buf->sg_cnt = 0;
|
|
|
|
isert_data_out_ready(&wr->pdu->iscsi);
|
|
}
|
|
|
|
static void isert_rdma_wr_completion_handler(struct isert_wr *wr)
|
|
{
|
|
struct isert_buf *isert_buf = wr->buf;
|
|
struct isert_device *isert_dev = wr->isert_dev;
|
|
struct ib_device *ib_dev = isert_dev->ib_dev;
|
|
|
|
ib_dma_unmap_sg(ib_dev, isert_buf->sg, isert_buf->sg_cnt,
|
|
isert_buf->dma_dir);
|
|
isert_buf->sg_cnt = 0;
|
|
|
|
isert_data_in_sent(&wr->pdu->iscsi);
|
|
}
|
|
|
|
static void isert_handle_wc(struct ib_wc *wc)
|
|
{
|
|
struct isert_wr *wr = _u64_to_ptr(wc->wr_id);
|
|
struct isert_conn *isert_conn;
|
|
|
|
TRACE_ENTRY();
|
|
|
|
switch (wr->wr_op) {
|
|
case ISER_WR_RECV:
|
|
isert_conn = wr->conn;
|
|
if (unlikely(isert_conn->state == ISER_CONN_HANDSHAKE)) {
|
|
isert_conn->state = ISER_CONN_ACTIVE;
|
|
isert_conn->saved_wr = wr;
|
|
PRINT_INFO("iser rx pdu before conn established, pdu saved");
|
|
break;
|
|
}
|
|
isert_recv_completion_handler(wr);
|
|
break;
|
|
case ISER_WR_SEND:
|
|
isert_send_completion_handler(wr);
|
|
break;
|
|
case ISER_WR_RDMA_WRITE:
|
|
isert_rdma_wr_completion_handler(wr);
|
|
break;
|
|
case ISER_WR_RDMA_READ:
|
|
isert_rdma_rd_completion_handler(wr);
|
|
break;
|
|
default:
|
|
isert_conn = wr->conn;
|
|
PRINT_ERROR("unexpected work req op:%d, wc op:%d, wc:%p wr_id:%p conn:%p",
|
|
wr->wr_op, wc->opcode, wc, wr, isert_conn);
|
|
if (isert_conn)
|
|
isert_conn_disconnect(isert_conn);
|
|
break;
|
|
}
|
|
|
|
TRACE_EXIT();
|
|
}
|
|
|
|
static const char *wr_status_str(enum ib_wc_status status)
|
|
{
|
|
switch (status) {
|
|
case IB_WC_SUCCESS:
|
|
return "WC_SUCCESS";
|
|
|
|
case IB_WC_LOC_LEN_ERR:
|
|
return "WC_LOC_LEN_ERR";
|
|
|
|
case IB_WC_LOC_QP_OP_ERR:
|
|
return "WC_LOC_QP_OP_ERR";
|
|
|
|
case IB_WC_LOC_EEC_OP_ERR:
|
|
return "WC_LOC_EEC_OP_ERR";
|
|
|
|
case IB_WC_LOC_PROT_ERR:
|
|
return "WC_LOC_PROT_ERR";
|
|
|
|
case IB_WC_WR_FLUSH_ERR:
|
|
return "WC_WR_FLUSH_ERR";
|
|
|
|
case IB_WC_MW_BIND_ERR:
|
|
return "WC_MW_BIND_ERR";
|
|
|
|
case IB_WC_BAD_RESP_ERR:
|
|
return "WC_BAD_RESP_ERR";
|
|
|
|
case IB_WC_LOC_ACCESS_ERR:
|
|
return "WC_LOC_ACCESS_ERR";
|
|
|
|
case IB_WC_REM_INV_REQ_ERR:
|
|
return "WC_REM_INV_REQ_ERR";
|
|
|
|
case IB_WC_REM_ACCESS_ERR:
|
|
return "WC_REM_ACCESS_ERR";
|
|
|
|
case IB_WC_REM_OP_ERR:
|
|
return "WC_REM_OP_ERR";
|
|
|
|
case IB_WC_RETRY_EXC_ERR:
|
|
return "WC_RETRY_EXC_ERR";
|
|
|
|
case IB_WC_RNR_RETRY_EXC_ERR:
|
|
return "WC_RNR_RETRY_EXC_ERR";
|
|
|
|
case IB_WC_LOC_RDD_VIOL_ERR:
|
|
return "WC_LOC_RDD_VIOL_ERR";
|
|
|
|
case IB_WC_REM_INV_RD_REQ_ERR:
|
|
return "WC_REM_INV_RD_REQ_ERR";
|
|
|
|
case IB_WC_REM_ABORT_ERR:
|
|
return "WC_REM_ABORT_ERR";
|
|
|
|
case IB_WC_INV_EECN_ERR:
|
|
return "WC_INV_EECN_ERR";
|
|
|
|
case IB_WC_INV_EEC_STATE_ERR:
|
|
return "WC_INV_EEC_STATE_ERR";
|
|
|
|
case IB_WC_FATAL_ERR:
|
|
return "WC_FATAL_ERR";
|
|
|
|
case IB_WC_RESP_TIMEOUT_ERR:
|
|
return "WC_RESP_TIMEOUT_ERR";
|
|
|
|
case IB_WC_GENERAL_ERR:
|
|
return "WC_GENERAL_ERR";
|
|
|
|
default:
|
|
return "UNKNOWN";
|
|
}
|
|
}
|
|
|
|
static void isert_discon_do_work(struct work_struct *work)
|
|
{
|
|
struct isert_conn *isert_conn =
|
|
container_of(work, struct isert_conn, discon_work);
|
|
|
|
/* notify upper layer */
|
|
isert_connection_closed(&isert_conn->iscsi);
|
|
}
|
|
|
|
static void isert_sched_discon(struct isert_conn *isert_conn)
|
|
{
|
|
INIT_WORK(&isert_conn->discon_work, isert_discon_do_work);
|
|
isert_conn_queue_work(&isert_conn->discon_work);
|
|
}
|
|
|
|
static void isert_conn_drained_do_work(struct work_struct *work)
|
|
{
|
|
struct isert_conn *isert_conn =
|
|
container_of(work, struct isert_conn, drain_work);
|
|
|
|
isert_put_conn(isert_conn);
|
|
}
|
|
|
|
static void isert_sched_conn_drained(struct isert_conn *isert_conn)
|
|
{
|
|
INIT_WORK(&isert_conn->drain_work, isert_conn_drained_do_work);
|
|
isert_conn_queue_work(&isert_conn->drain_work);
|
|
}
|
|
|
|
static void isert_conn_closed_do_work(struct work_struct *work)
|
|
{
|
|
struct isert_conn *isert_conn =
|
|
container_of(work, struct isert_conn, close_work);
|
|
|
|
if (!test_bit(ISERT_CONNECTION_ABORTED, &isert_conn->flags))
|
|
isert_connection_abort(&isert_conn->iscsi);
|
|
|
|
isert_put_conn(isert_conn);
|
|
}
|
|
|
|
static void isert_sched_conn_closed(struct isert_conn *isert_conn)
|
|
{
|
|
INIT_WORK(&isert_conn->close_work, isert_conn_closed_do_work);
|
|
isert_conn_queue_work(&isert_conn->close_work);
|
|
}
|
|
|
|
static void isert_release_work(struct work_struct *work)
|
|
{
|
|
struct isert_conn *isert_conn =
|
|
container_of(work, struct isert_conn, release_work);
|
|
|
|
isert_put_conn(isert_conn);
|
|
}
|
|
|
|
void isert_sched_conn_free(struct isert_conn *isert_conn)
|
|
{
|
|
INIT_WORK(&isert_conn->release_work, isert_release_work);
|
|
isert_conn_queue_work(&isert_conn->release_work);
|
|
}
|
|
|
|
static void isert_handle_wc_error(struct ib_wc *wc)
|
|
{
|
|
struct isert_wr *wr = _u64_to_ptr(wc->wr_id);
|
|
struct isert_cmnd *isert_pdu = wr->pdu;
|
|
struct isert_conn *isert_conn = wr->conn;
|
|
struct isert_buf *isert_buf = wr->buf;
|
|
struct isert_device *isert_dev = wr->isert_dev;
|
|
struct ib_device *ib_dev = isert_dev->ib_dev;
|
|
u32 num_sge;
|
|
|
|
TRACE_ENTRY();
|
|
|
|
if (wc->status != IB_WC_WR_FLUSH_ERR)
|
|
PRINT_ERROR("conn:%p wr_id:0x%p status:%s vendor_err:0x%0x",
|
|
isert_conn, wr, wr_status_str(wc->status),
|
|
wc->vendor_err);
|
|
|
|
if (!test_bit(ISERT_CONNECTION_ABORTED, &isert_conn->flags))
|
|
if (!test_and_set_bit(ISERT_DISCON_CALLED, &isert_conn->flags))
|
|
isert_sched_discon(isert_conn);
|
|
|
|
switch (wr->wr_op) {
|
|
case ISER_WR_SEND:
|
|
#ifdef USE_PRE_440_WR_STRUCTURE
|
|
num_sge = wr->send_wr.num_sge;
|
|
#else
|
|
num_sge = wr->send_wr.wr.num_sge;
|
|
#endif
|
|
if (unlikely(num_sge == 0)) { /* Drain WR */
|
|
set_bit(ISERT_DRAINED_SQ, &isert_conn->flags);
|
|
if (test_bit(ISERT_DRAINED_RQ, &isert_conn->flags))
|
|
isert_sched_conn_drained(isert_conn);
|
|
} else if (!isert_pdu->is_fake_rx) {
|
|
isert_pdu_err(&isert_pdu->iscsi);
|
|
}
|
|
break;
|
|
case ISER_WR_RDMA_READ:
|
|
if (isert_buf->sg_cnt != 0) {
|
|
ib_dma_unmap_sg(ib_dev, isert_buf->sg,
|
|
isert_buf->sg_cnt, isert_buf->dma_dir);
|
|
isert_buf->sg_cnt = 0;
|
|
}
|
|
if (!isert_pdu->is_fake_rx)
|
|
isert_pdu_err(&isert_pdu->iscsi);
|
|
break;
|
|
case ISER_WR_RECV:
|
|
/* this should be the Flush, no task has been created yet */
|
|
num_sge = wr->recv_wr.num_sge;
|
|
if (unlikely(num_sge == 0)) { /* Drain WR */
|
|
set_bit(ISERT_DRAINED_RQ, &isert_conn->flags);
|
|
if (test_bit(ISERT_DRAINED_SQ, &isert_conn->flags))
|
|
isert_sched_conn_drained(isert_conn);
|
|
}
|
|
break;
|
|
case ISER_WR_RDMA_WRITE:
|
|
if (isert_buf->sg_cnt != 0) {
|
|
ib_dma_unmap_sg(ib_dev, isert_buf->sg,
|
|
isert_buf->sg_cnt, isert_buf->dma_dir);
|
|
isert_buf->sg_cnt = 0;
|
|
}
|
|
/*
|
|
* RDMA-WR and SEND response of a READ task
|
|
* are sent together, so when receiving RDMA-WR error,
|
|
* wait until SEND error arrives to complete the task.
|
|
*/
|
|
break;
|
|
default:
|
|
PRINT_ERROR("unexpected opcode %d, wc:%p wr_id:%p conn:%p",
|
|
wr->wr_op, wc, wr, isert_conn);
|
|
break;
|
|
}
|
|
|
|
TRACE_EXIT();
|
|
}
|
|
|
|
static int isert_poll_cq(struct isert_cq *cq)
|
|
{
|
|
int err;
|
|
struct ib_wc *wc, *last_wc;
|
|
|
|
TRACE_ENTRY();
|
|
|
|
do {
|
|
err = ib_poll_cq(cq->cq, ARRAY_SIZE(cq->wc), cq->wc);
|
|
last_wc = &cq->wc[err];
|
|
for (wc = cq->wc; wc < last_wc; ++wc) {
|
|
if (likely(wc->status == IB_WC_SUCCESS))
|
|
isert_handle_wc(wc);
|
|
else
|
|
isert_handle_wc_error(wc);
|
|
}
|
|
|
|
} while (err > 0);
|
|
|
|
TRACE_EXIT_RES(err);
|
|
return err;
|
|
}
|
|
|
|
/* callback function for isert_dev->[cq]->cq_comp_work */
|
|
static void isert_cq_comp_work_cb(struct work_struct *work)
|
|
{
|
|
struct isert_cq *cq_desc =
|
|
container_of(work, struct isert_cq, cq_comp_work);
|
|
int ret;
|
|
|
|
TRACE_ENTRY();
|
|
|
|
ret = isert_poll_cq(cq_desc);
|
|
if (unlikely(ret < 0)) { /* poll error */
|
|
PRINT_ERROR("ib_poll_cq failed");
|
|
goto out;
|
|
}
|
|
|
|
ib_req_notify_cq(cq_desc->cq,
|
|
IB_CQ_NEXT_COMP | IB_CQ_REPORT_MISSED_EVENTS);
|
|
/*
|
|
* not all HCAs support IB_CQ_REPORT_MISSED_EVENTS,
|
|
* so we need to make sure we don't miss any events between
|
|
* last call to ib_poll_cq() and ib_req_notify_cq()
|
|
*/
|
|
isert_poll_cq(cq_desc);
|
|
|
|
out:
|
|
TRACE_EXIT();
|
|
return;
|
|
}
|
|
|
|
static void isert_cq_comp_handler(struct ib_cq *cq, void *context)
|
|
{
|
|
struct isert_cq *cq_desc = context;
|
|
|
|
queue_work_on(smp_processor_id(), cq_desc->cq_workqueue,
|
|
&cq_desc->cq_comp_work);
|
|
}
|
|
|
|
static const char *ib_event_type_str(enum ib_event_type ev_type)
|
|
{
|
|
switch (ev_type) {
|
|
case IB_EVENT_COMM_EST:
|
|
return "COMM_EST";
|
|
case IB_EVENT_QP_FATAL:
|
|
return "QP_FATAL";
|
|
case IB_EVENT_QP_REQ_ERR:
|
|
return "QP_REQ_ERR";
|
|
case IB_EVENT_QP_ACCESS_ERR:
|
|
return "QP_ACCESS_ERR";
|
|
case IB_EVENT_SQ_DRAINED:
|
|
return "SQ_DRAINED";
|
|
case IB_EVENT_PATH_MIG:
|
|
return "PATH_MIG";
|
|
case IB_EVENT_PATH_MIG_ERR:
|
|
return "PATH_MIG_ERR";
|
|
case IB_EVENT_QP_LAST_WQE_REACHED:
|
|
return "QP_LAST_WQE_REACHED";
|
|
case IB_EVENT_CQ_ERR:
|
|
return "CQ_ERR";
|
|
case IB_EVENT_SRQ_ERR:
|
|
return "SRQ_ERR";
|
|
case IB_EVENT_SRQ_LIMIT_REACHED:
|
|
return "SRQ_LIMIT_REACHED";
|
|
case IB_EVENT_PORT_ACTIVE:
|
|
return "PORT_ACTIVE";
|
|
case IB_EVENT_PORT_ERR:
|
|
return "PORT_ERR";
|
|
case IB_EVENT_LID_CHANGE:
|
|
return "LID_CHANGE";
|
|
case IB_EVENT_PKEY_CHANGE:
|
|
return "PKEY_CHANGE";
|
|
case IB_EVENT_SM_CHANGE:
|
|
return "SM_CHANGE";
|
|
case IB_EVENT_CLIENT_REREGISTER:
|
|
return "CLIENT_REREGISTER";
|
|
case IB_EVENT_DEVICE_FATAL:
|
|
return "DEVICE_FATAL";
|
|
default:
|
|
return "UNKNOWN";
|
|
}
|
|
}
|
|
|
|
static void isert_async_evt_handler(struct ib_event *async_ev, void *context)
|
|
{
|
|
struct isert_cq *cq = context;
|
|
struct isert_device *isert_dev = cq->dev;
|
|
struct ib_device *ib_dev = isert_dev->ib_dev;
|
|
char *dev_name = ib_dev->name;
|
|
enum ib_event_type ev_type = async_ev->event;
|
|
struct isert_conn *isert_conn;
|
|
|
|
TRACE_ENTRY();
|
|
|
|
switch (ev_type) {
|
|
case IB_EVENT_COMM_EST:
|
|
isert_conn = async_ev->element.qp->qp_context;
|
|
PRINT_INFO("conn:0x%p cm_id:0x%p dev:%s, QP evt: %s",
|
|
isert_conn, isert_conn->cm_id, dev_name,
|
|
ib_event_type_str(IB_EVENT_COMM_EST));
|
|
/* force "connection established" event */
|
|
rdma_notify(isert_conn->cm_id, IB_EVENT_COMM_EST);
|
|
break;
|
|
|
|
/* rest of QP-related events */
|
|
case IB_EVENT_QP_FATAL:
|
|
case IB_EVENT_QP_REQ_ERR:
|
|
case IB_EVENT_QP_ACCESS_ERR:
|
|
case IB_EVENT_SQ_DRAINED:
|
|
case IB_EVENT_PATH_MIG:
|
|
case IB_EVENT_PATH_MIG_ERR:
|
|
case IB_EVENT_QP_LAST_WQE_REACHED:
|
|
isert_conn = async_ev->element.qp->qp_context;
|
|
PRINT_ERROR("conn:0x%p cm_id:0x%p dev:%s, QP evt: %s",
|
|
isert_conn, isert_conn->cm_id, dev_name,
|
|
ib_event_type_str(ev_type));
|
|
break;
|
|
|
|
/* CQ-related events */
|
|
case IB_EVENT_CQ_ERR:
|
|
PRINT_ERROR("dev:%s CQ evt: %s", dev_name,
|
|
ib_event_type_str(ev_type));
|
|
break;
|
|
|
|
/* SRQ events */
|
|
case IB_EVENT_SRQ_ERR:
|
|
case IB_EVENT_SRQ_LIMIT_REACHED:
|
|
PRINT_ERROR("dev:%s SRQ evt: %s", dev_name,
|
|
ib_event_type_str(ev_type));
|
|
break;
|
|
|
|
/* Port events */
|
|
case IB_EVENT_PORT_ACTIVE:
|
|
case IB_EVENT_PORT_ERR:
|
|
case IB_EVENT_LID_CHANGE:
|
|
case IB_EVENT_PKEY_CHANGE:
|
|
case IB_EVENT_SM_CHANGE:
|
|
case IB_EVENT_CLIENT_REREGISTER:
|
|
PRINT_ERROR("dev:%s port:%d evt: %s",
|
|
dev_name, async_ev->element.port_num,
|
|
ib_event_type_str(ev_type));
|
|
break;
|
|
|
|
/* HCA events */
|
|
case IB_EVENT_DEVICE_FATAL:
|
|
PRINT_ERROR("dev:%s HCA evt: %s", dev_name,
|
|
ib_event_type_str(ev_type));
|
|
break;
|
|
|
|
default:
|
|
PRINT_ERROR("dev:%s evt: %s", dev_name,
|
|
ib_event_type_str(ev_type));
|
|
break;
|
|
}
|
|
|
|
TRACE_EXIT();
|
|
}
|
|
|
|
static struct isert_device *isert_device_create(struct ib_device *ib_dev)
|
|
{
|
|
struct isert_device *isert_dev;
|
|
int cqe_num, err;
|
|
struct ib_pd *pd;
|
|
#ifndef IB_PD_HAS_LOCAL_DMA_LKEY
|
|
struct ib_mr *mr;
|
|
#endif
|
|
struct ib_cq *cq;
|
|
char wq_name[64];
|
|
int i, j;
|
|
|
|
TRACE_ENTRY();
|
|
|
|
isert_dev = kzalloc(sizeof(*isert_dev), GFP_KERNEL);
|
|
if (unlikely(isert_dev == NULL)) {
|
|
PRINT_ERROR("Failed to allocate iser dev");
|
|
err = -ENOMEM;
|
|
goto out;
|
|
}
|
|
|
|
#ifdef HAVE_IB_QUERY_DEVICE
|
|
err = ib_query_device(ib_dev, &isert_dev->device_attr);
|
|
if (unlikely(err)) {
|
|
PRINT_ERROR("Failed to query device, err: %d", err);
|
|
goto free_isert_dev;
|
|
}
|
|
#else
|
|
isert_dev->device_attr = ib_dev->attrs;
|
|
#endif
|
|
|
|
isert_dev->num_cqs = min_t(int, num_online_cpus(),
|
|
ib_dev->num_comp_vectors);
|
|
|
|
isert_dev->cq_qps = kcalloc(isert_dev->num_cqs,
|
|
sizeof(*isert_dev->cq_qps),
|
|
GFP_KERNEL);
|
|
if (unlikely(isert_dev->cq_qps == NULL)) {
|
|
PRINT_ERROR("Failed to allocate %d iser cq_qps",
|
|
isert_dev->num_cqs);
|
|
err = -ENOMEM;
|
|
goto free_isert_dev;
|
|
}
|
|
|
|
isert_dev->cq_desc = vmalloc_array(isert_dev->num_cqs, sizeof(*isert_dev->cq_desc));
|
|
if (unlikely(isert_dev->cq_desc == NULL)) {
|
|
PRINT_ERROR("Failed to allocate %zd bytes for iser cq_desc",
|
|
sizeof(*isert_dev->cq_desc) * isert_dev->num_cqs);
|
|
err = -ENOMEM;
|
|
goto fail_alloc_cq_desc;
|
|
}
|
|
|
|
pd = ib_alloc_pd(ib_dev, 0);
|
|
if (IS_ERR(pd)) {
|
|
err = PTR_ERR(pd);
|
|
PRINT_ERROR("Failed to alloc iser dev pd, err:%d", err);
|
|
goto fail_pd;
|
|
}
|
|
|
|
#ifndef IB_PD_HAS_LOCAL_DMA_LKEY
|
|
mr = ib_get_dma_mr(pd, IB_ACCESS_LOCAL_WRITE);
|
|
if (IS_ERR(mr)) {
|
|
err = PTR_ERR(mr);
|
|
PRINT_ERROR("Failed to get dma mr, err: %d", err);
|
|
goto fail_mr;
|
|
}
|
|
#endif
|
|
|
|
cqe_num = min(isert_dev->device_attr.max_cqe, ISER_CQ_ENTRIES);
|
|
cqe_num = cqe_num / isert_dev->num_cqs;
|
|
|
|
#ifdef CONFIG_SCST_EXTRACHECKS
|
|
if (isert_dev->device_attr.max_cqe == 0)
|
|
PRINT_ERROR("Zero max_cqe encountered: you may have a compilation problem");
|
|
#endif
|
|
|
|
for (i = 0; i < isert_dev->num_cqs; ++i) {
|
|
struct isert_cq *cq_desc = &isert_dev->cq_desc[i];
|
|
|
|
cq_desc->dev = isert_dev;
|
|
cq_desc->idx = i;
|
|
INIT_WORK(&cq_desc->cq_comp_work, isert_cq_comp_work_cb);
|
|
|
|
snprintf(wq_name, sizeof(wq_name), "isert_cq_%p", cq_desc);
|
|
cq_desc->cq_workqueue = alloc_workqueue(wq_name,
|
|
WQ_CPU_INTENSIVE|
|
|
WQ_MEM_RECLAIM, 1);
|
|
if (unlikely(!cq_desc->cq_workqueue)) {
|
|
PRINT_ERROR("Failed to alloc iser cq work queue for dev:%s",
|
|
ib_dev->name);
|
|
err = -ENOMEM;
|
|
goto fail_cq;
|
|
}
|
|
|
|
#ifndef IB_CREATE_CQ_HAS_INIT_ATTR
|
|
cq = ib_create_cq(ib_dev,
|
|
isert_cq_comp_handler,
|
|
isert_async_evt_handler,
|
|
cq_desc, /* context */
|
|
cqe_num,
|
|
i); /* completion vector */
|
|
#else
|
|
{
|
|
struct ib_cq_init_attr ia = {
|
|
.cqe = cqe_num,
|
|
.comp_vector = i,
|
|
};
|
|
cq = ib_create_cq(ib_dev,
|
|
isert_cq_comp_handler,
|
|
isert_async_evt_handler,
|
|
cq_desc, /* context */
|
|
&ia);
|
|
}
|
|
#endif
|
|
if (IS_ERR(cq)) {
|
|
cq_desc->cq = NULL;
|
|
err = PTR_ERR(cq);
|
|
PRINT_ERROR("Failed to create iser dev cq, err:%d",
|
|
err);
|
|
goto fail_cq;
|
|
}
|
|
|
|
cq_desc->cq = cq;
|
|
err = ib_req_notify_cq(cq, IB_CQ_NEXT_COMP |
|
|
IB_CQ_REPORT_MISSED_EVENTS);
|
|
if (unlikely(err)) {
|
|
PRINT_ERROR("Failed to request notify cq, err: %d",
|
|
err);
|
|
goto fail_cq;
|
|
}
|
|
}
|
|
|
|
isert_dev->ib_dev = ib_dev;
|
|
isert_dev->pd = pd;
|
|
#ifndef IB_PD_HAS_LOCAL_DMA_LKEY
|
|
isert_dev->mr = mr;
|
|
isert_dev->lkey = mr->lkey;
|
|
#else
|
|
isert_dev->lkey = pd->local_dma_lkey;
|
|
#endif
|
|
|
|
INIT_LIST_HEAD(&isert_dev->conn_list);
|
|
|
|
lockdep_assert_held(&dev_list_mutex);
|
|
|
|
isert_dev_list_add(isert_dev);
|
|
|
|
PRINT_INFO("iser created device:%p", isert_dev);
|
|
return isert_dev;
|
|
|
|
fail_cq:
|
|
for (j = 0; j <= i; ++j) {
|
|
if (isert_dev->cq_desc[j].cq)
|
|
ib_destroy_cq(isert_dev->cq_desc[j].cq);
|
|
if (isert_dev->cq_desc[j].cq_workqueue)
|
|
destroy_workqueue(isert_dev->cq_desc[j].cq_workqueue);
|
|
}
|
|
#ifndef IB_PD_HAS_LOCAL_DMA_LKEY
|
|
ib_dereg_mr(mr);
|
|
fail_mr:
|
|
#endif
|
|
ib_dealloc_pd(pd);
|
|
fail_pd:
|
|
vfree(isert_dev->cq_desc);
|
|
fail_alloc_cq_desc:
|
|
kfree(isert_dev->cq_qps);
|
|
free_isert_dev:
|
|
kfree(isert_dev);
|
|
out:
|
|
TRACE_EXIT_RES(err);
|
|
return ERR_PTR(err);
|
|
}
|
|
|
|
static void isert_device_release(struct isert_device *isert_dev)
|
|
{
|
|
#ifndef IB_PD_HAS_LOCAL_DMA_LKEY
|
|
int err;
|
|
#endif
|
|
int i;
|
|
|
|
TRACE_ENTRY();
|
|
|
|
lockdep_assert_held(&dev_list_mutex);
|
|
|
|
isert_dev_list_remove(isert_dev); /* remove from global list */
|
|
|
|
for (i = 0; i < isert_dev->num_cqs; ++i) {
|
|
struct isert_cq *cq_desc = &isert_dev->cq_desc[i];
|
|
|
|
cancel_work_sync(&cq_desc->cq_comp_work);
|
|
|
|
ib_destroy_cq(cq_desc->cq);
|
|
destroy_workqueue(cq_desc->cq_workqueue);
|
|
}
|
|
|
|
#ifndef IB_PD_HAS_LOCAL_DMA_LKEY
|
|
err = ib_dereg_mr(isert_dev->mr);
|
|
if (unlikely(err))
|
|
PRINT_ERROR("Failed to destroy mr, err:%d", err);
|
|
#endif
|
|
ib_dealloc_pd(isert_dev->pd);
|
|
|
|
vfree(isert_dev->cq_desc);
|
|
isert_dev->cq_desc = NULL;
|
|
|
|
kfree(isert_dev->cq_qps);
|
|
isert_dev->cq_qps = NULL;
|
|
|
|
kfree(isert_dev);
|
|
|
|
TRACE_EXIT();
|
|
}
|
|
|
|
static int isert_get_cq_idx(struct isert_device *isert_dev)
|
|
{
|
|
int i, min_idx;
|
|
|
|
min_idx = 0;
|
|
mutex_lock(&dev_list_mutex);
|
|
for (i = 0; i < isert_dev->num_cqs; ++i)
|
|
if (isert_dev->cq_qps[i] < isert_dev->cq_qps[min_idx])
|
|
min_idx = i;
|
|
isert_dev->cq_qps[min_idx]++;
|
|
mutex_unlock(&dev_list_mutex);
|
|
|
|
return min_idx;
|
|
}
|
|
|
|
static int isert_conn_qp_create(struct isert_conn *isert_conn)
|
|
{
|
|
struct rdma_cm_id *cm_id = isert_conn->cm_id;
|
|
struct isert_device *isert_dev = isert_conn->isert_dev;
|
|
struct ib_qp_init_attr qp_attr;
|
|
int err;
|
|
int cq_idx;
|
|
int max_wr = ISER_MAX_WCE;
|
|
|
|
TRACE_ENTRY();
|
|
|
|
cq_idx = isert_get_cq_idx(isert_dev);
|
|
|
|
memset(&qp_attr, 0, sizeof(qp_attr));
|
|
|
|
qp_attr.event_handler = isert_async_evt_handler;
|
|
qp_attr.qp_context = isert_conn;
|
|
qp_attr.send_cq = isert_dev->cq_desc[cq_idx].cq;
|
|
qp_attr.recv_cq = isert_dev->cq_desc[cq_idx].cq;
|
|
|
|
isert_conn->cq_desc = &isert_dev->cq_desc[cq_idx];
|
|
|
|
qp_attr.cap.max_send_sge = isert_conn->max_sge;
|
|
qp_attr.cap.max_recv_sge = 3;
|
|
qp_attr.sq_sig_type = IB_SIGNAL_REQ_WR;
|
|
qp_attr.qp_type = IB_QPT_RC;
|
|
|
|
do {
|
|
if (max_wr < ISER_MIN_SQ_SIZE) {
|
|
PRINT_ERROR("Failed to create qp, not enough memory");
|
|
goto fail_create_qp;
|
|
}
|
|
|
|
qp_attr.cap.max_send_wr = max_wr;
|
|
qp_attr.cap.max_recv_wr = max_wr;
|
|
|
|
err = rdma_create_qp(cm_id, isert_dev->pd, &qp_attr);
|
|
if (err && err != -ENOMEM) {
|
|
PRINT_ERROR("Failed to create qp, err:%d", err);
|
|
goto fail_create_qp;
|
|
}
|
|
|
|
max_wr /= 2;
|
|
} while (err == -ENOMEM);
|
|
|
|
isert_conn->qp = cm_id->qp;
|
|
|
|
PRINT_INFO("iser created cm_id:%p qp:0x%X", cm_id, cm_id->qp->qp_num);
|
|
|
|
out:
|
|
TRACE_EXIT_RES(err);
|
|
return err;
|
|
|
|
fail_create_qp:
|
|
mutex_lock(&dev_list_mutex);
|
|
isert_dev->cq_qps[cq_idx]--;
|
|
mutex_unlock(&dev_list_mutex);
|
|
goto out;
|
|
}
|
|
|
|
static void
|
|
isert_init_conn(struct isert_conn *isert_conn)
|
|
{
|
|
isert_conn->state = ISER_CONN_INIT;
|
|
INIT_LIST_HEAD(&isert_conn->rx_buf_list);
|
|
INIT_LIST_HEAD(&isert_conn->tx_free_list);
|
|
INIT_LIST_HEAD(&isert_conn->tx_busy_list);
|
|
spin_lock_init(&isert_conn->tx_lock);
|
|
spin_lock_init(&isert_conn->post_recv_lock);
|
|
init_waitqueue_head(&isert_conn->rem_wait);
|
|
kref_init(&isert_conn->kref);
|
|
mutex_init(&isert_conn->state_mutex);
|
|
}
|
|
|
|
static struct isert_conn *isert_conn_create(struct rdma_cm_id *cm_id,
|
|
struct isert_device *isert_dev)
|
|
{
|
|
struct isert_conn *isert_conn;
|
|
int err;
|
|
struct isert_cq *cq;
|
|
|
|
TRACE_ENTRY();
|
|
|
|
isert_conn = isert_conn_zalloc();
|
|
if (unlikely(!isert_conn)) {
|
|
PRINT_ERROR("Unable to allocate iser conn, cm_id:%p", cm_id);
|
|
err = -ENOMEM;
|
|
goto fail_alloc;
|
|
}
|
|
|
|
isert_init_conn(isert_conn);
|
|
|
|
isert_conn->cm_id = cm_id;
|
|
isert_conn->isert_dev = isert_dev;
|
|
|
|
/*
|
|
* A quote from the OFED 1.5.3.1 release notes
|
|
* (docs/release_notes/mthca_release_notes.txt), section "Known Issues":
|
|
* In mem-free devices, RC QPs can be created with a maximum of
|
|
* (max_sge - 1) entries only; UD QPs can be created with a maximum of
|
|
* (max_sge - 3) entries.
|
|
* A quote from the OFED 1.2.5 release notes
|
|
* (docs/mthca_release_notes.txt), section "Known Issues":
|
|
* In mem-free devices, RC QPs can be created with a maximum of
|
|
* (max_sge - 3) entries only.
|
|
*/
|
|
#ifndef HAVE_DEV_ATTR_MAX_RECV_SGE
|
|
isert_conn->max_sge = isert_dev->device_attr.max_sge - 3;
|
|
#else
|
|
isert_conn->max_sge = isert_dev->device_attr.max_recv_sge - 3;
|
|
#endif
|
|
|
|
if (WARN_ON(isert_conn->max_sge < 1)) {
|
|
err = -ENODEV;
|
|
goto fail_login_req_pdu;
|
|
}
|
|
|
|
isert_conn->login_req_pdu = isert_rx_pdu_alloc(isert_conn,
|
|
ISER_MAX_LOGIN_RDSL);
|
|
if (unlikely(!isert_conn->login_req_pdu)) {
|
|
PRINT_ERROR("Failed to init login req rx pdu");
|
|
err = -ENOMEM;
|
|
goto fail_login_req_pdu;
|
|
}
|
|
|
|
isert_conn->login_rsp_pdu = isert_tx_pdu_alloc(isert_conn,
|
|
ISER_MAX_LOGIN_RDSL);
|
|
if (unlikely(!isert_conn->login_rsp_pdu)) {
|
|
PRINT_ERROR("Failed to init login rsp tx pdu");
|
|
err = -ENOMEM;
|
|
goto fail_login_rsp_pdu;
|
|
}
|
|
|
|
err = isert_conn_qp_create(isert_conn);
|
|
if (unlikely(err))
|
|
goto fail_qp;
|
|
|
|
err = isert_post_recv(isert_conn, &isert_conn->login_req_pdu->wr[0], 1);
|
|
if (unlikely(err)) {
|
|
PRINT_ERROR("Failed to post recv login req rx buf, err:%d",
|
|
err);
|
|
goto fail_post_recv;
|
|
}
|
|
|
|
TRACE_EXIT();
|
|
return isert_conn;
|
|
|
|
fail_post_recv:
|
|
cq = isert_conn->qp->recv_cq->cq_context;
|
|
mutex_lock(&dev_list_mutex);
|
|
isert_dev->cq_qps[cq->idx]--;
|
|
mutex_unlock(&dev_list_mutex);
|
|
rdma_destroy_qp(isert_conn->cm_id);
|
|
fail_qp:
|
|
isert_pdu_free(isert_conn->login_rsp_pdu);
|
|
fail_login_rsp_pdu:
|
|
isert_pdu_free(isert_conn->login_req_pdu);
|
|
fail_login_req_pdu:
|
|
isert_conn_kfree(isert_conn);
|
|
fail_alloc:
|
|
TRACE_EXIT_RES(err);
|
|
return ERR_PTR(err);
|
|
}
|
|
|
|
static void isert_deref_device(struct isert_device *isert_dev)
|
|
{
|
|
isert_dev->refcnt--;
|
|
if (isert_dev->refcnt == 0)
|
|
isert_device_release(isert_dev);
|
|
}
|
|
|
|
static void isert_release_kref(struct kref *kref)
|
|
{
|
|
struct isert_conn_dev *dev;
|
|
struct isert_conn *isert_conn =
|
|
container_of(kref, struct isert_conn, kref);
|
|
struct isert_device *isert_dev = isert_conn->isert_dev;
|
|
struct isert_cq *cq = isert_conn->qp->recv_cq->cq_context;
|
|
|
|
TRACE_ENTRY();
|
|
|
|
PRINT_INFO("free conn:%p", isert_conn);
|
|
|
|
isert_free_conn_resources(isert_conn);
|
|
|
|
if (isert_conn->cm_id &&
|
|
!atomic_read(&isert_conn->dev_removed)) {
|
|
rdma_destroy_id(isert_conn->cm_id);
|
|
isert_conn->cm_id = NULL;
|
|
}
|
|
|
|
dev = isert_get_priv(&isert_conn->iscsi);
|
|
if (dev) {
|
|
isert_del_timer(dev);
|
|
set_bit(ISERT_CONN_PASSED, &dev->flags);
|
|
dev->conn = NULL;
|
|
dev->state = CS_DISCONNECTED;
|
|
}
|
|
|
|
ib_destroy_qp(isert_conn->qp);
|
|
isert_conn->qp = NULL;
|
|
|
|
mutex_lock(&dev_list_mutex);
|
|
isert_conn->portal->refcnt--;
|
|
isert_dev->cq_qps[cq->idx]--;
|
|
if (test_bit(ISERT_IN_PORTAL_LIST, &isert_conn->flags))
|
|
list_del(&isert_conn->portal_node);
|
|
isert_deref_device(isert_dev);
|
|
if (unlikely(isert_conn->portal->state == ISERT_PORTAL_INACTIVE))
|
|
isert_portal_free(isert_conn->portal);
|
|
mutex_unlock(&dev_list_mutex);
|
|
|
|
|
|
if (atomic_read(&isert_conn->dev_removed)) {
|
|
atomic_set(&isert_conn->dev_removed, 0);
|
|
wake_up_interruptible(&isert_conn->rem_wait);
|
|
} else {
|
|
isert_conn_kfree(isert_conn);
|
|
module_put(THIS_MODULE);
|
|
}
|
|
|
|
TRACE_EXIT();
|
|
}
|
|
|
|
void isert_put_conn(struct isert_conn *isert_conn)
|
|
{
|
|
sBUG_ON(kref_read(&isert_conn->kref) == 0);
|
|
kref_put(&isert_conn->kref, isert_release_kref);
|
|
}
|
|
|
|
static int isert_cm_disconnected_handler(struct rdma_cm_id *cm_id,
|
|
struct rdma_cm_event *event)
|
|
{
|
|
struct isert_conn *isert_conn = cm_id->qp->qp_context;
|
|
|
|
if (!test_and_set_bit(ISERT_CONNECTION_CLOSE, &isert_conn->flags))
|
|
isert_sched_conn_closed(isert_conn);
|
|
return 0;
|
|
}
|
|
|
|
static void isert_immediate_conn_close(struct isert_conn *isert_conn)
|
|
{
|
|
set_bit(ISERT_CONNECTION_ABORTED, &isert_conn->flags);
|
|
set_bit(ISERT_CONNECTION_CLOSE, &isert_conn->flags);
|
|
isert_conn->state = ISER_CONN_CLOSING;
|
|
/*
|
|
* reaching here must be with the isert_conn refcount of 3,
|
|
* one from the init and two from the connect request,
|
|
* thus it is safe to deref directly before the sched_conn_free.
|
|
*/
|
|
isert_put_conn(isert_conn);
|
|
isert_put_conn(isert_conn);
|
|
isert_sched_conn_free(isert_conn);
|
|
}
|
|
|
|
static int isert_cm_conn_req_handler(struct rdma_cm_id *cm_id,
|
|
struct rdma_cm_event *event)
|
|
{
|
|
/* passed in rdma_create_id */
|
|
struct isert_portal *portal = cm_id->context;
|
|
struct ib_device *ib_dev = cm_id->device;
|
|
struct isert_device *isert_dev;
|
|
struct isert_conn *isert_conn;
|
|
struct rdma_conn_param *ini_conn_param;
|
|
struct rdma_conn_param tgt_conn_param;
|
|
struct isert_cm_hdr cm_hdr = { 0 };
|
|
int err;
|
|
|
|
TRACE_ENTRY();
|
|
|
|
if (unlikely(!try_module_get(THIS_MODULE))) {
|
|
err = -EINVAL;
|
|
goto out;
|
|
}
|
|
|
|
mutex_lock(&dev_list_mutex);
|
|
isert_dev = isert_device_find(ib_dev);
|
|
if (!isert_dev) {
|
|
isert_dev = isert_device_create(ib_dev);
|
|
if (IS_ERR(isert_dev)) {
|
|
err = PTR_ERR(isert_dev);
|
|
mutex_unlock(&dev_list_mutex);
|
|
goto fail_dev_create;
|
|
}
|
|
}
|
|
isert_dev->refcnt++;
|
|
mutex_unlock(&dev_list_mutex);
|
|
|
|
isert_conn = isert_conn_create(cm_id, isert_dev);
|
|
if (IS_ERR(isert_conn)) {
|
|
err = PTR_ERR(isert_conn);
|
|
goto fail_conn_create;
|
|
}
|
|
|
|
isert_conn->state = ISER_CONN_HANDSHAKE;
|
|
isert_conn->portal = portal;
|
|
|
|
mutex_lock(&dev_list_mutex);
|
|
portal->refcnt++;
|
|
mutex_unlock(&dev_list_mutex);
|
|
|
|
/* initiator is dst, target is src */
|
|
memcpy(&isert_conn->peer_addr, &cm_id->route.addr.dst_addr,
|
|
sizeof(isert_conn->peer_addr));
|
|
memcpy(&isert_conn->self_addr, &cm_id->route.addr.src_addr,
|
|
sizeof(isert_conn->self_addr));
|
|
|
|
ini_conn_param = &event->param.conn;
|
|
memset(&tgt_conn_param, 0, sizeof(tgt_conn_param));
|
|
tgt_conn_param.flow_control =
|
|
ini_conn_param->flow_control;
|
|
tgt_conn_param.rnr_retry_count =
|
|
ini_conn_param->rnr_retry_count;
|
|
|
|
tgt_conn_param.initiator_depth =
|
|
isert_dev->device_attr.max_qp_init_rd_atom;
|
|
if (tgt_conn_param.initiator_depth > ini_conn_param->initiator_depth)
|
|
tgt_conn_param.initiator_depth =
|
|
ini_conn_param->initiator_depth;
|
|
|
|
tgt_conn_param.private_data_len = sizeof(cm_hdr);
|
|
tgt_conn_param.private_data = &cm_hdr;
|
|
cm_hdr.flags = ISER_ZBVA_NOT_SUPPORTED | ISER_SEND_W_INV_NOT_SUPPORTED;
|
|
|
|
kref_get(&isert_conn->kref);
|
|
kref_get(&isert_conn->kref);
|
|
|
|
err = rdma_accept(cm_id, &tgt_conn_param);
|
|
if (unlikely(err)) {
|
|
PRINT_ERROR("Failed to accept conn request, err:%d conn:%p",
|
|
err, isert_conn);
|
|
goto fail_accept;
|
|
}
|
|
|
|
PRINT_INFO("iser accepted connection cm_id:%p %pISpc->%pISpc",
|
|
cm_id, &isert_conn->peer_addr, &isert_conn->self_addr);
|
|
|
|
mutex_lock(&dev_list_mutex);
|
|
list_add_tail(&isert_conn->portal_node, &portal->conn_list);
|
|
set_bit(ISERT_IN_PORTAL_LIST, &isert_conn->flags);
|
|
mutex_unlock(&dev_list_mutex);
|
|
|
|
out:
|
|
TRACE_EXIT_RES(err);
|
|
return err;
|
|
|
|
fail_accept:
|
|
isert_sched_conn_free(isert_conn);
|
|
err = 0;
|
|
goto out;
|
|
|
|
fail_conn_create:
|
|
mutex_lock(&dev_list_mutex);
|
|
isert_deref_device(isert_dev);
|
|
mutex_unlock(&dev_list_mutex);
|
|
fail_dev_create:
|
|
rdma_reject(cm_id, NULL, 0
|
|
#if RDMA_REJECT_HAS_FOUR_ARGS
|
|
, IB_CM_REJ_CONSUMER_DEFINED
|
|
#endif
|
|
);
|
|
module_put(THIS_MODULE);
|
|
goto out;
|
|
}
|
|
|
|
static int isert_cm_connect_handler(struct rdma_cm_id *cm_id,
|
|
struct rdma_cm_event *event)
|
|
{
|
|
struct isert_conn *isert_conn = cm_id->qp->qp_context;
|
|
int push_saved_pdu = 0;
|
|
int ret = 0;
|
|
|
|
TRACE_ENTRY();
|
|
|
|
mutex_lock(&isert_conn->state_mutex);
|
|
if (isert_conn->state == ISER_CONN_HANDSHAKE)
|
|
isert_conn->state = ISER_CONN_ACTIVE;
|
|
else if (isert_conn->state == ISER_CONN_ACTIVE)
|
|
push_saved_pdu = 1;
|
|
else if (isert_conn->state == ISER_CONN_CLOSING)
|
|
goto out;
|
|
|
|
ret = isert_get_addr_size((struct sockaddr *)&isert_conn->peer_addr,
|
|
&isert_conn->peer_addrsz);
|
|
if (unlikely(ret))
|
|
goto out;
|
|
|
|
/* notify upper layer */
|
|
ret = isert_conn_established(&isert_conn->iscsi,
|
|
(struct sockaddr *)&isert_conn->peer_addr,
|
|
isert_conn->peer_addrsz);
|
|
if (unlikely(ret))
|
|
goto out;
|
|
|
|
if (push_saved_pdu) {
|
|
PRINT_INFO("iser push saved rx pdu");
|
|
isert_recv_completion_handler(isert_conn->saved_wr);
|
|
isert_conn->saved_wr = NULL;
|
|
}
|
|
|
|
out:
|
|
mutex_unlock(&isert_conn->state_mutex);
|
|
TRACE_EXIT_RES(ret);
|
|
return ret;
|
|
}
|
|
|
|
#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 2, 0) && \
|
|
(!defined(RHEL_MAJOR) || RHEL_MAJOR -0 < 7)
|
|
static const char *const cma_events[] = {
|
|
[RDMA_CM_EVENT_ADDR_RESOLVED] = "address resolved",
|
|
[RDMA_CM_EVENT_ADDR_ERROR] = "address error",
|
|
[RDMA_CM_EVENT_ROUTE_RESOLVED] = "route resolved ",
|
|
[RDMA_CM_EVENT_ROUTE_ERROR] = "route error",
|
|
[RDMA_CM_EVENT_CONNECT_REQUEST] = "connect request",
|
|
[RDMA_CM_EVENT_CONNECT_RESPONSE] = "connect response",
|
|
[RDMA_CM_EVENT_CONNECT_ERROR] = "connect error",
|
|
[RDMA_CM_EVENT_UNREACHABLE] = "unreachable",
|
|
[RDMA_CM_EVENT_REJECTED] = "rejected",
|
|
[RDMA_CM_EVENT_ESTABLISHED] = "established",
|
|
[RDMA_CM_EVENT_DISCONNECTED] = "disconnected",
|
|
[RDMA_CM_EVENT_DEVICE_REMOVAL] = "device removal",
|
|
[RDMA_CM_EVENT_MULTICAST_JOIN] = "multicast join",
|
|
[RDMA_CM_EVENT_MULTICAST_ERROR] = "multicast error",
|
|
[RDMA_CM_EVENT_ADDR_CHANGE] = "address change",
|
|
[RDMA_CM_EVENT_TIMEWAIT_EXIT] = "timewait exit",
|
|
};
|
|
|
|
static const char *rdma_event_msg(enum rdma_cm_event_type event)
|
|
{
|
|
size_t index = event;
|
|
|
|
return (index < ARRAY_SIZE(cma_events) && cma_events[index]) ?
|
|
cma_events[index] : "unrecognized event";
|
|
}
|
|
#endif
|
|
|
|
static int isert_handle_failure(struct isert_conn *conn)
|
|
{
|
|
isert_conn_disconnect(conn);
|
|
return 0;
|
|
}
|
|
|
|
static void isert_portal_reinit_id_work(struct work_struct *w)
|
|
{
|
|
struct isert_portal *portal = container_of(w, struct isert_portal, work);
|
|
|
|
rdma_destroy_id(portal->cm_id);
|
|
|
|
portal->cm_id = isert_setup_id(portal);
|
|
if (IS_ERR(portal->cm_id)) {
|
|
PRINT_ERROR("Failed to create rdma id, err:%ld\n",
|
|
PTR_ERR(portal->cm_id));
|
|
portal->cm_id = NULL;
|
|
}
|
|
}
|
|
|
|
static int isert_cm_evt_listener_handler(struct rdma_cm_id *cm_id,
|
|
enum rdma_cm_event_type event)
|
|
{
|
|
struct isert_portal *portal;
|
|
int ret = -1;
|
|
|
|
portal = cm_id->context;
|
|
|
|
switch (event) {
|
|
case RDMA_CM_EVENT_DEVICE_REMOVAL:
|
|
portal->cm_id = NULL;
|
|
break;
|
|
case RDMA_CM_EVENT_ADDR_CHANGE:
|
|
queue_work(portal->reinit_id_wq, &portal->work);
|
|
ret = 0;
|
|
break;
|
|
default:
|
|
PRINT_INFO("Listener event:%s(%d), ignored",
|
|
rdma_event_msg(event), event);
|
|
break;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int isert_cm_disconnect_handler(struct rdma_cm_id *cm_id,
|
|
enum rdma_cm_event_type event)
|
|
{
|
|
struct isert_conn *isert_conn = cm_id->qp->qp_context;
|
|
|
|
isert_conn_disconnect(isert_conn);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int isert_cm_evt_handler(struct rdma_cm_id *cm_id,
|
|
struct rdma_cm_event *cm_ev)
|
|
{
|
|
enum rdma_cm_event_type ev_type;
|
|
struct isert_portal *portal;
|
|
int err = -EINVAL;
|
|
|
|
TRACE_ENTRY();
|
|
|
|
ev_type = cm_ev->event;
|
|
portal = cm_id->context;
|
|
PRINT_INFO("isert_cm_evt:%s(%d) status:%d portal:%p cm_id:%p",
|
|
rdma_event_msg(ev_type), ev_type, cm_ev->status,
|
|
portal, cm_id);
|
|
|
|
if (portal->cm_id == cm_id) {
|
|
err = isert_cm_evt_listener_handler(cm_id, ev_type);
|
|
goto out;
|
|
}
|
|
|
|
switch (ev_type) {
|
|
case RDMA_CM_EVENT_CONNECT_REQUEST:
|
|
err = isert_cm_conn_req_handler(cm_id, cm_ev);
|
|
if (unlikely(err))
|
|
PRINT_ERROR("Failed to handle RDMA_CM_EVENT_CONNECT_REQUEST, err:%d",
|
|
err);
|
|
break;
|
|
|
|
case RDMA_CM_EVENT_ESTABLISHED:
|
|
err = isert_cm_connect_handler(cm_id, cm_ev);
|
|
if (unlikely(err))
|
|
err = isert_handle_failure(cm_id->qp->qp_context);
|
|
break;
|
|
|
|
case RDMA_CM_EVENT_CONNECT_ERROR:
|
|
case RDMA_CM_EVENT_REJECTED:
|
|
err = isert_cm_disconnect_handler(cm_id, ev_type);
|
|
break;
|
|
|
|
case RDMA_CM_EVENT_ADDR_CHANGE:
|
|
case RDMA_CM_EVENT_DISCONNECTED:
|
|
case RDMA_CM_EVENT_TIMEWAIT_EXIT:
|
|
isert_cm_disconnect_handler(cm_id, ev_type);
|
|
err = isert_cm_disconnected_handler(cm_id, cm_ev);
|
|
break;
|
|
case RDMA_CM_EVENT_DEVICE_REMOVAL: {
|
|
struct isert_conn *isert_conn = cm_id->qp->qp_context;
|
|
|
|
atomic_set(&isert_conn->dev_removed, 1);
|
|
|
|
isert_cm_disconnect_handler(cm_id, ev_type);
|
|
isert_cm_disconnected_handler(cm_id, cm_ev);
|
|
|
|
wait_event_interruptible(isert_conn->rem_wait,
|
|
!atomic_read(&isert_conn->dev_removed));
|
|
|
|
isert_conn_kfree(isert_conn);
|
|
module_put(THIS_MODULE);
|
|
/*
|
|
* return non-zero from the callback to destroy
|
|
* the rdma cm id
|
|
*/
|
|
err = 1;
|
|
|
|
break;
|
|
}
|
|
case RDMA_CM_EVENT_MULTICAST_JOIN:
|
|
case RDMA_CM_EVENT_MULTICAST_ERROR:
|
|
PRINT_ERROR("UD-related event:%d, ignored", ev_type);
|
|
break;
|
|
|
|
case RDMA_CM_EVENT_ADDR_RESOLVED:
|
|
case RDMA_CM_EVENT_ADDR_ERROR:
|
|
case RDMA_CM_EVENT_ROUTE_RESOLVED:
|
|
case RDMA_CM_EVENT_ROUTE_ERROR:
|
|
case RDMA_CM_EVENT_CONNECT_RESPONSE:
|
|
PRINT_ERROR("Active side event:%d, ignored", ev_type);
|
|
break;
|
|
|
|
/* We can receive this instead of RDMA_CM_EVENT_ESTABLISHED */
|
|
case RDMA_CM_EVENT_UNREACHABLE:
|
|
{
|
|
struct isert_conn *isert_conn =
|
|
cm_id->qp->qp_context;
|
|
|
|
mutex_lock(&isert_conn->state_mutex);
|
|
if (isert_conn->state != ISER_CONN_CLOSING)
|
|
isert_immediate_conn_close(isert_conn);
|
|
mutex_unlock(&isert_conn->state_mutex);
|
|
err = 0;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
PRINT_ERROR("Illegal event:%d, ignored", ev_type);
|
|
break;
|
|
}
|
|
|
|
out:
|
|
TRACE_EXIT_RES(err);
|
|
return err;
|
|
}
|
|
|
|
static struct rdma_cm_id *
|
|
isert_setup_id(struct isert_portal *portal)
|
|
{
|
|
struct rdma_cm_id *id;
|
|
struct sockaddr *sa;
|
|
int ret;
|
|
|
|
sa = (struct sockaddr *)&portal->addr;
|
|
|
|
#if !RDMA_CREATE_ID_TAKES_NET_ARG
|
|
id = rdma_create_id(isert_cm_evt_handler, portal, RDMA_PS_TCP,
|
|
IB_QPT_RC);
|
|
#else
|
|
id = rdma_create_id(iscsi_net_ns, isert_cm_evt_handler, portal,
|
|
RDMA_PS_TCP, IB_QPT_RC);
|
|
#endif
|
|
if (IS_ERR(id)) {
|
|
ret = PTR_ERR(id);
|
|
PRINT_ERROR("Failed to create rdma id, err:%d", ret);
|
|
goto out;
|
|
}
|
|
|
|
/*
|
|
* Allow both IPv4 and IPv6 sockets to bind a single port
|
|
* at the same time.
|
|
*/
|
|
ret = rdma_set_afonly(id, 1);
|
|
if (ret) {
|
|
PRINT_ERROR("Failed to set afonly, err:%d", ret);
|
|
goto out_id;
|
|
}
|
|
|
|
ret = rdma_bind_addr(id, sa);
|
|
if (ret) {
|
|
PRINT_ERROR("Failed to bind rdma addr, err:%d", ret);
|
|
goto out_id;
|
|
}
|
|
|
|
ret = rdma_listen(id, ISER_LISTEN_BACKLOG);
|
|
if (ret) {
|
|
PRINT_ERROR("Failed rdma listen, err:%d", ret);
|
|
goto out_id;
|
|
}
|
|
|
|
PRINT_INFO("iser portal with cm_id %p listens on %pISpc", id, sa);
|
|
|
|
return id;
|
|
|
|
out_id:
|
|
rdma_destroy_id(id);
|
|
out:
|
|
return ERR_PTR(ret);
|
|
}
|
|
|
|
/* create a portal, after listening starts all events
|
|
* are received in isert_cm_evt_handler()
|
|
*/
|
|
struct isert_portal *isert_portal_create(struct sockaddr *sa, size_t addr_len)
|
|
{
|
|
struct isert_portal *portal;
|
|
struct rdma_cm_id *cm_id;
|
|
int err;
|
|
|
|
if (unlikely(!try_module_get(THIS_MODULE))) {
|
|
PRINT_ERROR("Unable increment module reference");
|
|
portal = ERR_PTR(-EINVAL);
|
|
goto out;
|
|
}
|
|
|
|
portal = kzalloc(sizeof(*portal), GFP_KERNEL);
|
|
if (unlikely(!portal)) {
|
|
PRINT_ERROR("Unable to allocate struct portal");
|
|
portal = ERR_PTR(-ENOMEM);
|
|
goto err_alloc;
|
|
}
|
|
|
|
portal->reinit_id_wq = alloc_ordered_workqueue("isert_reinit_id_wq", 0);
|
|
if (unlikely(!portal->reinit_id_wq)) {
|
|
PRINT_ERROR("Unable to allocate reinit workqueue");
|
|
err = -ENOMEM;
|
|
goto free_portal;
|
|
}
|
|
|
|
INIT_WORK(&portal->work, isert_portal_reinit_id_work);
|
|
|
|
INIT_LIST_HEAD(&portal->conn_list);
|
|
memcpy(&portal->addr, sa, addr_len);
|
|
|
|
cm_id = isert_setup_id(portal);
|
|
if (IS_ERR(cm_id)) {
|
|
err = PTR_ERR(cm_id);
|
|
PRINT_ERROR("Failed to create rdma id, err:%d", err);
|
|
goto free_wq;
|
|
}
|
|
|
|
portal->cm_id = cm_id;
|
|
isert_portal_list_add(portal);
|
|
|
|
PRINT_INFO("Created iser portal cm_id:%p", cm_id);
|
|
out:
|
|
return portal;
|
|
|
|
free_wq:
|
|
destroy_workqueue(portal->reinit_id_wq);
|
|
free_portal:
|
|
kfree(portal);
|
|
portal = ERR_PTR(err);
|
|
err_alloc:
|
|
module_put(THIS_MODULE);
|
|
goto out;
|
|
}
|
|
|
|
static void isert_portal_free(struct isert_portal *portal)
|
|
{
|
|
lockdep_assert_held(&dev_list_mutex);
|
|
|
|
if (portal->refcnt > 0)
|
|
return;
|
|
|
|
destroy_workqueue(portal->reinit_id_wq);
|
|
|
|
kfree(portal);
|
|
module_put(THIS_MODULE);
|
|
|
|
isert_decrease_portal_cnt();
|
|
}
|
|
|
|
void isert_portal_release(struct isert_portal *portal)
|
|
{
|
|
struct isert_conn *conn;
|
|
|
|
PRINT_INFO("iser portal cm_id:%p releasing", portal->cm_id);
|
|
|
|
if (portal->cm_id) {
|
|
rdma_destroy_id(portal->cm_id);
|
|
portal->cm_id = NULL;
|
|
}
|
|
|
|
isert_portal_list_remove(portal);
|
|
|
|
mutex_lock(&dev_list_mutex);
|
|
list_for_each_entry(conn, &portal->conn_list, portal_node) {
|
|
isert_conn_disconnect(conn);
|
|
if (!test_and_set_bit(ISERT_CONNECTION_CLOSE, &conn->flags))
|
|
isert_sched_conn_closed(conn);
|
|
}
|
|
portal->state = ISERT_PORTAL_INACTIVE;
|
|
isert_portal_free(portal);
|
|
mutex_unlock(&dev_list_mutex);
|
|
|
|
PRINT_INFO("done releasing portal %p", portal);
|
|
}
|
|
|
|
struct isert_portal *isert_portal_start(struct sockaddr *sa, size_t addr_len)
|
|
{
|
|
return isert_portal_create(sa, addr_len);
|
|
}
|