blob: 771059a8eb7d7f76eb38652edbce9bfdb5ee89b3 [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
/* Authors: Cheng Xu <chengyou@linux.alibaba.com> */
/* Kai Shen <kaishen@linux.alibaba.com> */
/* Copyright (c) 2020-2022, Alibaba Group. */
/* Authors: Bernard Metzler <bmt@zurich.ibm.com> */
/* Fredy Neeser */
/* Greg Joyce <greg@opengridcomputing.com> */
/* Copyright (c) 2008-2019, IBM Corporation */
/* Copyright (c) 2017, Open Grid Computing, Inc. */
#include <linux/workqueue.h>
#include <trace/events/sock.h>
#include "erdma.h"
#include "erdma_cm.h"
#include "erdma_verbs.h"
static struct workqueue_struct *erdma_cm_wq;
static void erdma_cm_llp_state_change(struct sock *sk);
static void erdma_cm_llp_data_ready(struct sock *sk);
static void erdma_cm_llp_error_report(struct sock *sk);
static void erdma_sk_assign_cm_upcalls(struct sock *sk)
{
write_lock_bh(&sk->sk_callback_lock);
sk->sk_state_change = erdma_cm_llp_state_change;
sk->sk_data_ready = erdma_cm_llp_data_ready;
sk->sk_error_report = erdma_cm_llp_error_report;
write_unlock_bh(&sk->sk_callback_lock);
}
static void erdma_sk_save_upcalls(struct sock *sk)
{
struct erdma_cep *cep = sk_to_cep(sk);
write_lock_bh(&sk->sk_callback_lock);
cep->sk_state_change = sk->sk_state_change;
cep->sk_data_ready = sk->sk_data_ready;
cep->sk_error_report = sk->sk_error_report;
write_unlock_bh(&sk->sk_callback_lock);
}
static void erdma_sk_restore_upcalls(struct sock *sk, struct erdma_cep *cep)
{
sk->sk_state_change = cep->sk_state_change;
sk->sk_data_ready = cep->sk_data_ready;
sk->sk_error_report = cep->sk_error_report;
sk->sk_user_data = NULL;
}
static void erdma_socket_disassoc(struct socket *s)
{
struct sock *sk = s->sk;
struct erdma_cep *cep;
if (sk) {
write_lock_bh(&sk->sk_callback_lock);
cep = sk_to_cep(sk);
if (cep) {
erdma_sk_restore_upcalls(sk, cep);
erdma_cep_put(cep);
} else {
WARN_ON_ONCE(1);
}
write_unlock_bh(&sk->sk_callback_lock);
} else {
WARN_ON_ONCE(1);
}
}
static void erdma_cep_socket_assoc(struct erdma_cep *cep, struct socket *s)
{
cep->sock = s;
erdma_cep_get(cep);
s->sk->sk_user_data = cep;
erdma_sk_save_upcalls(s->sk);
erdma_sk_assign_cm_upcalls(s->sk);
}
static void erdma_disassoc_listen_cep(struct erdma_cep *cep)
{
if (cep->listen_cep) {
erdma_cep_put(cep->listen_cep);
cep->listen_cep = NULL;
}
}
static struct erdma_cep *erdma_cep_alloc(struct erdma_dev *dev)
{
struct erdma_cep *cep = kzalloc(sizeof(*cep), GFP_KERNEL);
unsigned long flags;
if (!cep)
return NULL;
INIT_LIST_HEAD(&cep->listenq);
INIT_LIST_HEAD(&cep->devq);
INIT_LIST_HEAD(&cep->work_freelist);
kref_init(&cep->ref);
cep->state = ERDMA_EPSTATE_IDLE;
init_waitqueue_head(&cep->waitq);
spin_lock_init(&cep->lock);
cep->dev = dev;
spin_lock_irqsave(&dev->lock, flags);
list_add_tail(&cep->devq, &dev->cep_list);
spin_unlock_irqrestore(&dev->lock, flags);
return cep;
}
static void erdma_cm_free_work(struct erdma_cep *cep)
{
struct list_head *w, *tmp;
struct erdma_cm_work *work;
list_for_each_safe(w, tmp, &cep->work_freelist) {
work = list_entry(w, struct erdma_cm_work, list);
list_del(&work->list);
kfree(work);
}
}
static void erdma_cancel_mpatimer(struct erdma_cep *cep)
{
spin_lock_bh(&cep->lock);
if (cep->mpa_timer) {
if (cancel_delayed_work(&cep->mpa_timer->work)) {
erdma_cep_put(cep);
kfree(cep->mpa_timer);
}
cep->mpa_timer = NULL;
}
spin_unlock_bh(&cep->lock);
}
static void erdma_put_work(struct erdma_cm_work *work)
{
INIT_LIST_HEAD(&work->list);
spin_lock_bh(&work->cep->lock);
list_add(&work->list, &work->cep->work_freelist);
spin_unlock_bh(&work->cep->lock);
}
static void erdma_cep_set_inuse(struct erdma_cep *cep)
{
unsigned long flags;
spin_lock_irqsave(&cep->lock, flags);
while (cep->in_use) {
spin_unlock_irqrestore(&cep->lock, flags);
wait_event_interruptible(cep->waitq, !cep->in_use);
if (signal_pending(current))
flush_signals(current);
spin_lock_irqsave(&cep->lock, flags);
}
cep->in_use = 1;
spin_unlock_irqrestore(&cep->lock, flags);
}
static void erdma_cep_set_free(struct erdma_cep *cep)
{
unsigned long flags;
spin_lock_irqsave(&cep->lock, flags);
cep->in_use = 0;
spin_unlock_irqrestore(&cep->lock, flags);
wake_up(&cep->waitq);
}
static void __erdma_cep_dealloc(struct kref *ref)
{
struct erdma_cep *cep = container_of(ref, struct erdma_cep, ref);
struct erdma_dev *dev = cep->dev;
unsigned long flags;
WARN_ON(cep->listen_cep);
kfree(cep->private_data);
kfree(cep->mpa.pdata);
spin_lock_bh(&cep->lock);
if (!list_empty(&cep->work_freelist))
erdma_cm_free_work(cep);
spin_unlock_bh(&cep->lock);
spin_lock_irqsave(&dev->lock, flags);
list_del(&cep->devq);
spin_unlock_irqrestore(&dev->lock, flags);
kfree(cep);
}
static struct erdma_cm_work *erdma_get_work(struct erdma_cep *cep)
{
struct erdma_cm_work *work = NULL;
spin_lock_bh(&cep->lock);
if (!list_empty(&cep->work_freelist)) {
work = list_entry(cep->work_freelist.next, struct erdma_cm_work,
list);
list_del_init(&work->list);
}
spin_unlock_bh(&cep->lock);
return work;
}
static int erdma_cm_alloc_work(struct erdma_cep *cep, int num)
{
struct erdma_cm_work *work;
while (num--) {
work = kmalloc(sizeof(*work), GFP_KERNEL);
if (!work) {
if (!(list_empty(&cep->work_freelist)))
erdma_cm_free_work(cep);
return -ENOMEM;
}
work->cep = cep;
INIT_LIST_HEAD(&work->list);
list_add(&work->list, &cep->work_freelist);
}
return 0;
}
static int erdma_cm_upcall(struct erdma_cep *cep, enum iw_cm_event_type reason,
int status)
{
struct iw_cm_event event;
struct iw_cm_id *cm_id;
memset(&event, 0, sizeof(event));
event.status = status;
event.event = reason;
if (reason == IW_CM_EVENT_CONNECT_REQUEST) {
event.provider_data = cep;
cm_id = cep->listen_cep->cm_id;
event.ird = cep->dev->attrs.max_ird;
event.ord = cep->dev->attrs.max_ord;
} else {
cm_id = cep->cm_id;
}
if (reason == IW_CM_EVENT_CONNECT_REQUEST ||
reason == IW_CM_EVENT_CONNECT_REPLY) {
u16 pd_len = be16_to_cpu(cep->mpa.hdr.params.pd_len);
if (pd_len && cep->mpa.pdata) {
event.private_data_len = pd_len;
event.private_data = cep->mpa.pdata;
}
getname_local(cep->sock, &event.local_addr);
getname_peer(cep->sock, &event.remote_addr);
}
return cm_id->event_handler(cm_id, &event);
}
void erdma_qp_cm_drop(struct erdma_qp *qp)
{
struct erdma_cep *cep = qp->cep;
if (!qp->cep)
return;
erdma_cep_set_inuse(cep);
/* already closed. */
if (cep->state == ERDMA_EPSTATE_CLOSED)
goto out;
if (cep->cm_id) {
switch (cep->state) {
case ERDMA_EPSTATE_AWAIT_MPAREP:
erdma_cm_upcall(cep, IW_CM_EVENT_CONNECT_REPLY,
-EINVAL);
break;
case ERDMA_EPSTATE_RDMA_MODE:
erdma_cm_upcall(cep, IW_CM_EVENT_CLOSE, 0);
break;
case ERDMA_EPSTATE_IDLE:
case ERDMA_EPSTATE_LISTENING:
case ERDMA_EPSTATE_CONNECTING:
case ERDMA_EPSTATE_AWAIT_MPAREQ:
case ERDMA_EPSTATE_RECVD_MPAREQ:
case ERDMA_EPSTATE_CLOSED:
default:
break;
}
cep->cm_id->rem_ref(cep->cm_id);
cep->cm_id = NULL;
erdma_cep_put(cep);
}
cep->state = ERDMA_EPSTATE_CLOSED;
if (cep->sock) {
erdma_socket_disassoc(cep->sock);
sock_release(cep->sock);
cep->sock = NULL;
}
if (cep->qp) {
cep->qp = NULL;
erdma_qp_put(qp);
}
out:
erdma_cep_set_free(cep);
}
void erdma_cep_put(struct erdma_cep *cep)
{
WARN_ON(kref_read(&cep->ref) < 1);
kref_put(&cep->ref, __erdma_cep_dealloc);
}
void erdma_cep_get(struct erdma_cep *cep)
{
kref_get(&cep->ref);
}
static int erdma_send_mpareqrep(struct erdma_cep *cep, const void *pdata,
u8 pd_len)
{
struct socket *s = cep->sock;
struct mpa_rr *rr = &cep->mpa.hdr;
struct kvec iov[3];
struct msghdr msg;
int iovec_num = 0;
int ret;
int mpa_len;
memset(&msg, 0, sizeof(msg));
rr->params.pd_len = cpu_to_be16(pd_len);
iov[iovec_num].iov_base = rr;
iov[iovec_num].iov_len = sizeof(*rr);
iovec_num++;
mpa_len = sizeof(*rr);
iov[iovec_num].iov_base = &cep->mpa.ext_data;
iov[iovec_num].iov_len = sizeof(cep->mpa.ext_data);
iovec_num++;
mpa_len += sizeof(cep->mpa.ext_data);
if (pd_len) {
iov[iovec_num].iov_base = (char *)pdata;
iov[iovec_num].iov_len = pd_len;
mpa_len += pd_len;
iovec_num++;
}
ret = kernel_sendmsg(s, &msg, iov, iovec_num, mpa_len);
return ret < 0 ? ret : 0;
}
static inline int ksock_recv(struct socket *sock, char *buf, size_t size,
int flags)
{
struct kvec iov = { buf, size };
struct msghdr msg = { .msg_name = NULL, .msg_flags = flags };
return kernel_recvmsg(sock, &msg, &iov, 1, size, flags);
}
static int __recv_mpa_hdr(struct erdma_cep *cep, int hdr_rcvd, char *hdr,
int hdr_size, int *rcvd_out)
{
struct socket *s = cep->sock;
int rcvd;
*rcvd_out = 0;
if (hdr_rcvd < hdr_size) {
rcvd = ksock_recv(s, hdr + hdr_rcvd, hdr_size - hdr_rcvd,
MSG_DONTWAIT);
if (rcvd == -EAGAIN)
return -EAGAIN;
if (rcvd <= 0)
return -ECONNABORTED;
hdr_rcvd += rcvd;
*rcvd_out = rcvd;
if (hdr_rcvd < hdr_size)
return -EAGAIN;
}
return 0;
}
static void __mpa_rr_set_revision(__be16 *bits, u8 rev)
{
*bits = (*bits & ~MPA_RR_MASK_REVISION) |
(cpu_to_be16(rev) & MPA_RR_MASK_REVISION);
}
static u8 __mpa_rr_revision(__be16 mpa_rr_bits)
{
__be16 rev = mpa_rr_bits & MPA_RR_MASK_REVISION;
return (u8)be16_to_cpu(rev);
}
static void __mpa_ext_set_cc(__be32 *bits, u32 cc)
{
*bits = (*bits & ~MPA_EXT_FLAG_CC) |
(cpu_to_be32(cc) & MPA_EXT_FLAG_CC);
}
static u8 __mpa_ext_cc(__be32 mpa_ext_bits)
{
__be32 cc = mpa_ext_bits & MPA_EXT_FLAG_CC;
return (u8)be32_to_cpu(cc);
}
/*
* Receive MPA Request/Reply header.
*
* Returns 0 if complete MPA Request/Reply haeder including
* eventual private data was received. Returns -EAGAIN if
* header was partially received or negative error code otherwise.
*
* Context: May be called in process context only
*/
static int erdma_recv_mpa_rr(struct erdma_cep *cep)
{
struct mpa_rr *hdr = &cep->mpa.hdr;
struct socket *s = cep->sock;
u16 pd_len;
int rcvd, to_rcv, ret, pd_rcvd;
if (cep->mpa.bytes_rcvd < sizeof(struct mpa_rr)) {
ret = __recv_mpa_hdr(cep, cep->mpa.bytes_rcvd,
(char *)&cep->mpa.hdr,
sizeof(struct mpa_rr), &rcvd);
cep->mpa.bytes_rcvd += rcvd;
if (ret)
return ret;
}
if (be16_to_cpu(hdr->params.pd_len) > MPA_MAX_PRIVDATA ||
__mpa_rr_revision(hdr->params.bits) != MPA_REVISION_EXT_1)
return -EPROTO;
if (cep->mpa.bytes_rcvd - sizeof(struct mpa_rr) <
sizeof(struct erdma_mpa_ext)) {
ret = __recv_mpa_hdr(
cep, cep->mpa.bytes_rcvd - sizeof(struct mpa_rr),
(char *)&cep->mpa.ext_data,
sizeof(struct erdma_mpa_ext), &rcvd);
cep->mpa.bytes_rcvd += rcvd;
if (ret)
return ret;
}
pd_len = be16_to_cpu(hdr->params.pd_len);
pd_rcvd = cep->mpa.bytes_rcvd - sizeof(struct mpa_rr) -
sizeof(struct erdma_mpa_ext);
to_rcv = pd_len - pd_rcvd;
if (!to_rcv) {
/*
* We have received the whole MPA Request/Reply message.
* Check against peer protocol violation.
*/
u32 word;
ret = __recv_mpa_hdr(cep, 0, (char *)&word, sizeof(word),
&rcvd);
if (ret == -EAGAIN && rcvd == 0)
return 0;
if (ret)
return ret;
return -EPROTO;
}
/*
* At this point, MPA header has been fully received, and pd_len != 0.
* So, begin to receive private data.
*/
if (!cep->mpa.pdata) {
cep->mpa.pdata = kmalloc(pd_len + 4, GFP_KERNEL);
if (!cep->mpa.pdata)
return -ENOMEM;
}
rcvd = ksock_recv(s, cep->mpa.pdata + pd_rcvd, to_rcv + 4,
MSG_DONTWAIT);
if (rcvd < 0)
return rcvd;
if (rcvd > to_rcv)
return -EPROTO;
cep->mpa.bytes_rcvd += rcvd;
if (to_rcv == rcvd)
return 0;
return -EAGAIN;
}
/*
* erdma_proc_mpareq()
*
* Read MPA Request from socket and signal new connection to IWCM
* if success. Caller must hold lock on corresponding listening CEP.
*/
static int erdma_proc_mpareq(struct erdma_cep *cep)
{
struct mpa_rr *req;
int ret;
ret = erdma_recv_mpa_rr(cep);
if (ret)
return ret;
req = &cep->mpa.hdr;
if (memcmp(req->key, MPA_KEY_REQ, MPA_KEY_SIZE))
return -EPROTO;
memcpy(req->key, MPA_KEY_REP, MPA_KEY_SIZE);
/* Currently does not support marker and crc. */
if (req->params.bits & MPA_RR_FLAG_MARKERS ||
req->params.bits & MPA_RR_FLAG_CRC)
goto reject_conn;
cep->state = ERDMA_EPSTATE_RECVD_MPAREQ;
/* Keep reference until IWCM accepts/rejects */
erdma_cep_get(cep);
ret = erdma_cm_upcall(cep, IW_CM_EVENT_CONNECT_REQUEST, 0);
if (ret)
erdma_cep_put(cep);
return ret;
reject_conn:
req->params.bits &= ~MPA_RR_FLAG_MARKERS;
req->params.bits |= MPA_RR_FLAG_REJECT;
req->params.bits &= ~MPA_RR_FLAG_CRC;
kfree(cep->mpa.pdata);
cep->mpa.pdata = NULL;
erdma_send_mpareqrep(cep, NULL, 0);
return -EOPNOTSUPP;
}
static int erdma_proc_mpareply(struct erdma_cep *cep)
{
struct erdma_qp_attrs qp_attrs;
struct erdma_qp *qp = cep->qp;
struct mpa_rr *rep;
int ret;
ret = erdma_recv_mpa_rr(cep);
if (ret)
goto out_err;
erdma_cancel_mpatimer(cep);
rep = &cep->mpa.hdr;
if (memcmp(rep->key, MPA_KEY_REP, MPA_KEY_SIZE)) {
ret = -EPROTO;
goto out_err;
}
if (rep->params.bits & MPA_RR_FLAG_REJECT) {
erdma_cm_upcall(cep, IW_CM_EVENT_CONNECT_REPLY, -ECONNRESET);
return -ECONNRESET;
}
/* Currently does not support marker and crc. */
if ((rep->params.bits & MPA_RR_FLAG_MARKERS) ||
(rep->params.bits & MPA_RR_FLAG_CRC)) {
erdma_cm_upcall(cep, IW_CM_EVENT_CONNECT_REPLY, -ECONNREFUSED);
return -EINVAL;
}
memset(&qp_attrs, 0, sizeof(qp_attrs));
qp_attrs.irq_size = cep->ird;
qp_attrs.orq_size = cep->ord;
qp_attrs.state = ERDMA_QP_STATE_RTS;
down_write(&qp->state_lock);
if (qp->attrs.state > ERDMA_QP_STATE_RTR) {
ret = -EINVAL;
up_write(&qp->state_lock);
goto out_err;
}
qp->attrs.qp_type = ERDMA_QP_ACTIVE;
if (__mpa_ext_cc(cep->mpa.ext_data.bits) != qp->attrs.cc)
qp->attrs.cc = COMPROMISE_CC;
ret = erdma_modify_qp_internal(qp, &qp_attrs,
ERDMA_QP_ATTR_STATE |
ERDMA_QP_ATTR_LLP_HANDLE |
ERDMA_QP_ATTR_MPA);
up_write(&qp->state_lock);
if (!ret) {
ret = erdma_cm_upcall(cep, IW_CM_EVENT_CONNECT_REPLY, 0);
if (!ret)
cep->state = ERDMA_EPSTATE_RDMA_MODE;
return 0;
}
out_err:
if (ret != -EAGAIN)
erdma_cm_upcall(cep, IW_CM_EVENT_CONNECT_REPLY, -EINVAL);
return ret;
}
static void erdma_accept_newconn(struct erdma_cep *cep)
{
struct socket *s = cep->sock;
struct socket *new_s = NULL;
struct erdma_cep *new_cep = NULL;
int ret = 0;
if (cep->state != ERDMA_EPSTATE_LISTENING)
goto error;
new_cep = erdma_cep_alloc(cep->dev);
if (!new_cep)
goto error;
/*
* 4: Allocate a sufficient number of work elements
* to allow concurrent handling of local + peer close
* events, MPA header processing + MPA timeout.
*/
if (erdma_cm_alloc_work(new_cep, 4) != 0)
goto error;
/*
* Copy saved socket callbacks from listening CEP
* and assign new socket with new CEP
*/
new_cep->sk_state_change = cep->sk_state_change;
new_cep->sk_data_ready = cep->sk_data_ready;
new_cep->sk_error_report = cep->sk_error_report;
ret = kernel_accept(s, &new_s, O_NONBLOCK);
if (ret != 0)
goto error;
new_cep->sock = new_s;
erdma_cep_get(new_cep);
new_s->sk->sk_user_data = new_cep;
tcp_sock_set_nodelay(new_s->sk);
new_cep->state = ERDMA_EPSTATE_AWAIT_MPAREQ;
ret = erdma_cm_queue_work(new_cep, ERDMA_CM_WORK_MPATIMEOUT);
if (ret)
goto error;
new_cep->listen_cep = cep;
erdma_cep_get(cep);
if (atomic_read(&new_s->sk->sk_rmem_alloc)) {
/* MPA REQ already queued */
erdma_cep_set_inuse(new_cep);
ret = erdma_proc_mpareq(new_cep);
if (ret != -EAGAIN) {
erdma_cep_put(cep);
new_cep->listen_cep = NULL;
if (ret) {
erdma_cep_set_free(new_cep);
goto error;
}
}
erdma_cep_set_free(new_cep);
}
return;
error:
if (new_cep) {
new_cep->state = ERDMA_EPSTATE_CLOSED;
erdma_cancel_mpatimer(new_cep);
erdma_cep_put(new_cep);
new_cep->sock = NULL;
}
if (new_s) {
erdma_socket_disassoc(new_s);
sock_release(new_s);
}
}
static int erdma_newconn_connected(struct erdma_cep *cep)
{
int ret = 0;
cep->mpa.hdr.params.bits = 0;
__mpa_rr_set_revision(&cep->mpa.hdr.params.bits, MPA_REVISION_EXT_1);
memcpy(cep->mpa.hdr.key, MPA_KEY_REQ, MPA_KEY_SIZE);
cep->mpa.ext_data.cookie = cpu_to_be32(cep->qp->attrs.cookie);
__mpa_ext_set_cc(&cep->mpa.ext_data.bits, cep->qp->attrs.cc);
ret = erdma_send_mpareqrep(cep, cep->private_data, cep->pd_len);
cep->state = ERDMA_EPSTATE_AWAIT_MPAREP;
cep->mpa.hdr.params.pd_len = 0;
if (ret >= 0)
ret = erdma_cm_queue_work(cep, ERDMA_CM_WORK_MPATIMEOUT);
return ret;
}
static void erdma_cm_work_handler(struct work_struct *w)
{
struct erdma_cm_work *work;
struct erdma_cep *cep;
int release_cep = 0, ret = 0;
work = container_of(w, struct erdma_cm_work, work.work);
cep = work->cep;
erdma_cep_set_inuse(cep);
switch (work->type) {
case ERDMA_CM_WORK_CONNECTED:
erdma_cancel_mpatimer(cep);
if (cep->state == ERDMA_EPSTATE_CONNECTING) {
ret = erdma_newconn_connected(cep);
if (ret) {
erdma_cm_upcall(cep, IW_CM_EVENT_CONNECT_REPLY,
-EIO);
release_cep = 1;
}
}
break;
case ERDMA_CM_WORK_CONNECTTIMEOUT:
if (cep->state == ERDMA_EPSTATE_CONNECTING) {
cep->mpa_timer = NULL;
erdma_cm_upcall(cep, IW_CM_EVENT_CONNECT_REPLY,
-ETIMEDOUT);
release_cep = 1;
}
break;
case ERDMA_CM_WORK_ACCEPT:
erdma_accept_newconn(cep);
break;
case ERDMA_CM_WORK_READ_MPAHDR:
if (cep->state == ERDMA_EPSTATE_AWAIT_MPAREQ) {
if (cep->listen_cep) {
erdma_cep_set_inuse(cep->listen_cep);
if (cep->listen_cep->state ==
ERDMA_EPSTATE_LISTENING)
ret = erdma_proc_mpareq(cep);
else
ret = -EFAULT;
erdma_cep_set_free(cep->listen_cep);
if (ret != -EAGAIN) {
erdma_cep_put(cep->listen_cep);
cep->listen_cep = NULL;
if (ret)
erdma_cep_put(cep);
}
}
} else if (cep->state == ERDMA_EPSTATE_AWAIT_MPAREP) {
ret = erdma_proc_mpareply(cep);
}
if (ret && ret != -EAGAIN)
release_cep = 1;
break;
case ERDMA_CM_WORK_CLOSE_LLP:
if (cep->cm_id)
erdma_cm_upcall(cep, IW_CM_EVENT_CLOSE, 0);
release_cep = 1;
break;
case ERDMA_CM_WORK_PEER_CLOSE:
if (cep->cm_id) {
if (cep->state == ERDMA_EPSTATE_CONNECTING ||
cep->state == ERDMA_EPSTATE_AWAIT_MPAREP) {
/*
* MPA reply not received, but connection drop
*/
erdma_cm_upcall(cep, IW_CM_EVENT_CONNECT_REPLY,
-ECONNRESET);
} else if (cep->state == ERDMA_EPSTATE_RDMA_MODE) {
/*
* NOTE: IW_CM_EVENT_DISCONNECT is given just
* to transition IWCM into CLOSING.
*/
erdma_cm_upcall(cep, IW_CM_EVENT_DISCONNECT, 0);
erdma_cm_upcall(cep, IW_CM_EVENT_CLOSE, 0);
}
} else if (cep->state == ERDMA_EPSTATE_AWAIT_MPAREQ) {
/* Socket close before MPA request received. */
erdma_disassoc_listen_cep(cep);
erdma_cep_put(cep);
}
release_cep = 1;
break;
case ERDMA_CM_WORK_MPATIMEOUT:
cep->mpa_timer = NULL;
if (cep->state == ERDMA_EPSTATE_AWAIT_MPAREP) {
/*
* MPA request timed out:
* Hide any partially received private data and signal
* timeout
*/
cep->mpa.hdr.params.pd_len = 0;
if (cep->cm_id)
erdma_cm_upcall(cep, IW_CM_EVENT_CONNECT_REPLY,
-ETIMEDOUT);
release_cep = 1;
} else if (cep->state == ERDMA_EPSTATE_AWAIT_MPAREQ) {
/* No MPA req received after peer TCP stream setup. */
erdma_disassoc_listen_cep(cep);
erdma_cep_put(cep);
release_cep = 1;
}
break;
default:
WARN(1, "Undefined CM work type: %d\n", work->type);
}
if (release_cep) {
erdma_cancel_mpatimer(cep);
cep->state = ERDMA_EPSTATE_CLOSED;
if (cep->qp) {
struct erdma_qp *qp = cep->qp;
/*
* Serialize a potential race with application
* closing the QP and calling erdma_qp_cm_drop()
*/
erdma_qp_get(qp);
erdma_cep_set_free(cep);
erdma_qp_llp_close(qp);
erdma_qp_put(qp);
erdma_cep_set_inuse(cep);
cep->qp = NULL;
erdma_qp_put(qp);
}
if (cep->sock) {
erdma_socket_disassoc(cep->sock);
sock_release(cep->sock);
cep->sock = NULL;
}
if (cep->cm_id) {
cep->cm_id->rem_ref(cep->cm_id);
cep->cm_id = NULL;
if (cep->state != ERDMA_EPSTATE_LISTENING)
erdma_cep_put(cep);
}
}
erdma_cep_set_free(cep);
erdma_put_work(work);
erdma_cep_put(cep);
}
int erdma_cm_queue_work(struct erdma_cep *cep, enum erdma_work_type type)
{
struct erdma_cm_work *work = erdma_get_work(cep);
unsigned long delay = 0;
if (!work)
return -ENOMEM;
work->type = type;
work->cep = cep;
erdma_cep_get(cep);
INIT_DELAYED_WORK(&work->work, erdma_cm_work_handler);
if (type == ERDMA_CM_WORK_MPATIMEOUT) {
cep->mpa_timer = work;
if (cep->state == ERDMA_EPSTATE_AWAIT_MPAREP)
delay = MPAREP_TIMEOUT;
else
delay = MPAREQ_TIMEOUT;
} else if (type == ERDMA_CM_WORK_CONNECTTIMEOUT) {
cep->mpa_timer = work;
delay = CONNECT_TIMEOUT;
}
queue_delayed_work(erdma_cm_wq, &work->work, delay);
return 0;
}
static void erdma_cm_llp_data_ready(struct sock *sk)
{
struct erdma_cep *cep;
trace_sk_data_ready(sk);
read_lock(&sk->sk_callback_lock);
cep = sk_to_cep(sk);
if (!cep)
goto out;
if (cep->state == ERDMA_EPSTATE_AWAIT_MPAREQ ||
cep->state == ERDMA_EPSTATE_AWAIT_MPAREP)
erdma_cm_queue_work(cep, ERDMA_CM_WORK_READ_MPAHDR);
out:
read_unlock(&sk->sk_callback_lock);
}
static void erdma_cm_llp_error_report(struct sock *sk)
{
struct erdma_cep *cep = sk_to_cep(sk);
if (cep)
cep->sk_error_report(sk);
}
static void erdma_cm_llp_state_change(struct sock *sk)
{
struct erdma_cep *cep;
void (*orig_state_change)(struct sock *sk);
read_lock(&sk->sk_callback_lock);
cep = sk_to_cep(sk);
if (!cep) {
read_unlock(&sk->sk_callback_lock);
return;
}
orig_state_change = cep->sk_state_change;
switch (sk->sk_state) {
case TCP_ESTABLISHED:
if (cep->state == ERDMA_EPSTATE_CONNECTING)
erdma_cm_queue_work(cep, ERDMA_CM_WORK_CONNECTED);
else
erdma_cm_queue_work(cep, ERDMA_CM_WORK_ACCEPT);
break;
case TCP_CLOSE:
case TCP_CLOSE_WAIT:
if (cep->state != ERDMA_EPSTATE_LISTENING)
erdma_cm_queue_work(cep, ERDMA_CM_WORK_PEER_CLOSE);
break;
default:
break;
}
read_unlock(&sk->sk_callback_lock);
orig_state_change(sk);
}
static int kernel_bindconnect(struct socket *s, struct sockaddr *laddr,
int laddrlen, struct sockaddr *raddr,
int raddrlen, int flags)
{
int ret;
sock_set_reuseaddr(s->sk);
ret = s->ops->bind(s, laddr, laddrlen);
if (ret)
return ret;
ret = s->ops->connect(s, raddr, raddrlen, flags);
return ret < 0 ? ret : 0;
}
int erdma_connect(struct iw_cm_id *id, struct iw_cm_conn_param *params)
{
struct erdma_dev *dev = to_edev(id->device);
struct erdma_qp *qp;
struct erdma_cep *cep = NULL;
struct socket *s = NULL;
struct sockaddr *laddr = (struct sockaddr *)&id->m_local_addr;
struct sockaddr *raddr = (struct sockaddr *)&id->m_remote_addr;
u16 pd_len = params->private_data_len;
int ret;
if (pd_len > MPA_MAX_PRIVDATA)
return -EINVAL;
if (params->ird > dev->attrs.max_ird ||
params->ord > dev->attrs.max_ord)
return -EINVAL;
if (laddr->sa_family != AF_INET || raddr->sa_family != AF_INET)
return -EAFNOSUPPORT;
qp = find_qp_by_qpn(dev, params->qpn);
if (!qp)
return -ENOENT;
erdma_qp_get(qp);
ret = sock_create(AF_INET, SOCK_STREAM, IPPROTO_TCP, &s);
if (ret < 0)
goto error_put_qp;
cep = erdma_cep_alloc(dev);
if (!cep) {
ret = -ENOMEM;
goto error_release_sock;
}
erdma_cep_set_inuse(cep);
/* Associate QP with CEP */
erdma_cep_get(cep);
qp->cep = cep;
cep->qp = qp;
/* Associate cm_id with CEP */
id->add_ref(id);
cep->cm_id = id;
/*
* 6: Allocate a sufficient number of work elements
* to allow concurrent handling of local + peer close
* events, MPA header processing + MPA timeout, connected event
* and connect timeout.
*/
ret = erdma_cm_alloc_work(cep, 6);
if (ret != 0) {
ret = -ENOMEM;
goto error_release_cep;
}
cep->ird = params->ird;
cep->ord = params->ord;
cep->state = ERDMA_EPSTATE_CONNECTING;
erdma_cep_socket_assoc(cep, s);
if (pd_len) {
cep->pd_len = pd_len;
cep->private_data = kmalloc(pd_len, GFP_KERNEL);
if (!cep->private_data) {
ret = -ENOMEM;
goto error_disassoc;
}
memcpy(cep->private_data, params->private_data,
params->private_data_len);
}
ret = kernel_bindconnect(s, laddr, sizeof(*laddr), raddr,
sizeof(*raddr), O_NONBLOCK);
if (ret != -EINPROGRESS && ret != 0) {
goto error_disassoc;
} else if (ret == 0) {
ret = erdma_cm_queue_work(cep, ERDMA_CM_WORK_CONNECTED);
if (ret)
goto error_disassoc;
} else {
ret = erdma_cm_queue_work(cep, ERDMA_CM_WORK_CONNECTTIMEOUT);
if (ret)
goto error_disassoc;
}
erdma_cep_set_free(cep);
return 0;
error_disassoc:
kfree(cep->private_data);
cep->private_data = NULL;
cep->pd_len = 0;
erdma_socket_disassoc(s);
error_release_cep:
/* disassoc with cm_id */
cep->cm_id = NULL;
id->rem_ref(id);
/* disassoc with qp */
qp->cep = NULL;
erdma_cep_put(cep);
cep->qp = NULL;
cep->state = ERDMA_EPSTATE_CLOSED;
erdma_cep_set_free(cep);
/* release the cep. */
erdma_cep_put(cep);
error_release_sock:
if (s)
sock_release(s);
error_put_qp:
erdma_qp_put(qp);
return ret;
}
int erdma_accept(struct iw_cm_id *id, struct iw_cm_conn_param *params)
{
struct erdma_dev *dev = to_edev(id->device);
struct erdma_cep *cep = (struct erdma_cep *)id->provider_data;
struct erdma_qp *qp;
struct erdma_qp_attrs qp_attrs;
int ret;
erdma_cep_set_inuse(cep);
erdma_cep_put(cep);
/* Free lingering inbound private data */
if (cep->mpa.hdr.params.pd_len) {
cep->mpa.hdr.params.pd_len = 0;
kfree(cep->mpa.pdata);
cep->mpa.pdata = NULL;
}
erdma_cancel_mpatimer(cep);
if (cep->state != ERDMA_EPSTATE_RECVD_MPAREQ) {
erdma_cep_set_free(cep);
erdma_cep_put(cep);
return -ECONNRESET;
}
qp = find_qp_by_qpn(dev, params->qpn);
if (!qp)
return -ENOENT;
erdma_qp_get(qp);
down_write(&qp->state_lock);
if (qp->attrs.state > ERDMA_QP_STATE_RTR) {
ret = -EINVAL;
up_write(&qp->state_lock);
goto error;
}
if (params->ord > dev->attrs.max_ord ||
params->ird > dev->attrs.max_ord) {
ret = -EINVAL;
up_write(&qp->state_lock);
goto error;
}
if (params->private_data_len > MPA_MAX_PRIVDATA) {
ret = -EINVAL;
up_write(&qp->state_lock);
goto error;
}
cep->ird = params->ird;
cep->ord = params->ord;
cep->cm_id = id;
id->add_ref(id);
memset(&qp_attrs, 0, sizeof(qp_attrs));
qp_attrs.orq_size = params->ord;
qp_attrs.irq_size = params->ird;
qp_attrs.state = ERDMA_QP_STATE_RTS;
/* Associate QP with CEP */
erdma_cep_get(cep);
qp->cep = cep;
cep->qp = qp;
cep->state = ERDMA_EPSTATE_RDMA_MODE;
qp->attrs.qp_type = ERDMA_QP_PASSIVE;
qp->attrs.pd_len = params->private_data_len;
if (qp->attrs.cc != __mpa_ext_cc(cep->mpa.ext_data.bits))
qp->attrs.cc = COMPROMISE_CC;
/* move to rts */
ret = erdma_modify_qp_internal(qp, &qp_attrs,
ERDMA_QP_ATTR_STATE |
ERDMA_QP_ATTR_ORD |
ERDMA_QP_ATTR_LLP_HANDLE |
ERDMA_QP_ATTR_IRD |
ERDMA_QP_ATTR_MPA);
up_write(&qp->state_lock);
if (ret)
goto error;
cep->mpa.ext_data.bits = 0;
__mpa_ext_set_cc(&cep->mpa.ext_data.bits, qp->attrs.cc);
cep->mpa.ext_data.cookie = cpu_to_be32(cep->qp->attrs.cookie);
ret = erdma_send_mpareqrep(cep, params->private_data,
params->private_data_len);
if (!ret) {
ret = erdma_cm_upcall(cep, IW_CM_EVENT_ESTABLISHED, 0);
if (ret)
goto error;
erdma_cep_set_free(cep);
return 0;
}
error:
erdma_socket_disassoc(cep->sock);
sock_release(cep->sock);
cep->sock = NULL;
cep->state = ERDMA_EPSTATE_CLOSED;
if (cep->cm_id) {
cep->cm_id->rem_ref(id);
cep->cm_id = NULL;
}
if (qp->cep) {
erdma_cep_put(cep);
qp->cep = NULL;
}
cep->qp = NULL;
erdma_qp_put(qp);
erdma_cep_set_free(cep);
erdma_cep_put(cep);
return ret;
}
int erdma_reject(struct iw_cm_id *id, const void *pdata, u8 plen)
{
struct erdma_cep *cep = (struct erdma_cep *)id->provider_data;
erdma_cep_set_inuse(cep);
erdma_cep_put(cep);
erdma_cancel_mpatimer(cep);
if (cep->state != ERDMA_EPSTATE_RECVD_MPAREQ) {
erdma_cep_set_free(cep);
erdma_cep_put(cep);
return -ECONNRESET;
}
if (__mpa_rr_revision(cep->mpa.hdr.params.bits) == MPA_REVISION_EXT_1) {
cep->mpa.hdr.params.bits |= MPA_RR_FLAG_REJECT; /* reject */
erdma_send_mpareqrep(cep, pdata, plen);
}
erdma_socket_disassoc(cep->sock);
sock_release(cep->sock);
cep->sock = NULL;
cep->state = ERDMA_EPSTATE_CLOSED;
erdma_cep_set_free(cep);
erdma_cep_put(cep);
return 0;
}
int erdma_create_listen(struct iw_cm_id *id, int backlog)
{
struct socket *s;
struct erdma_cep *cep = NULL;
int ret = 0;
struct erdma_dev *dev = to_edev(id->device);
int addr_family = id->local_addr.ss_family;
struct sockaddr_in *laddr = &to_sockaddr_in(id->local_addr);
if (addr_family != AF_INET)
return -EAFNOSUPPORT;
ret = sock_create(addr_family, SOCK_STREAM, IPPROTO_TCP, &s);
if (ret < 0)
return ret;
sock_set_reuseaddr(s->sk);
/* For wildcard addr, limit binding to current device only */
if (ipv4_is_zeronet(laddr->sin_addr.s_addr))
s->sk->sk_bound_dev_if = dev->netdev->ifindex;
ret = s->ops->bind(s, (struct sockaddr *)laddr,
sizeof(struct sockaddr_in));
if (ret)
goto error;
cep = erdma_cep_alloc(dev);
if (!cep) {
ret = -ENOMEM;
goto error;
}
erdma_cep_socket_assoc(cep, s);
ret = erdma_cm_alloc_work(cep, backlog);
if (ret)
goto error;
ret = s->ops->listen(s, backlog);
if (ret)
goto error;
cep->cm_id = id;
id->add_ref(id);
if (!id->provider_data) {
id->provider_data =
kmalloc(sizeof(struct list_head), GFP_KERNEL);
if (!id->provider_data) {
ret = -ENOMEM;
goto error;
}
INIT_LIST_HEAD((struct list_head *)id->provider_data);
}
list_add_tail(&cep->listenq, (struct list_head *)id->provider_data);
cep->state = ERDMA_EPSTATE_LISTENING;
return 0;
error:
if (cep) {
erdma_cep_set_inuse(cep);
if (cep->cm_id) {
cep->cm_id->rem_ref(cep->cm_id);
cep->cm_id = NULL;
}
cep->sock = NULL;
erdma_socket_disassoc(s);
cep->state = ERDMA_EPSTATE_CLOSED;
erdma_cep_set_free(cep);
erdma_cep_put(cep);
}
sock_release(s);
return ret;
}
static void erdma_drop_listeners(struct iw_cm_id *id)
{
struct list_head *p, *tmp;
/*
* In case of a wildcard rdma_listen on a multi-homed device,
* a listener's IWCM id is associated with more than one listening CEP.
*/
list_for_each_safe(p, tmp, (struct list_head *)id->provider_data) {
struct erdma_cep *cep =
list_entry(p, struct erdma_cep, listenq);
list_del(p);
erdma_cep_set_inuse(cep);
if (cep->cm_id) {
cep->cm_id->rem_ref(cep->cm_id);
cep->cm_id = NULL;
}
if (cep->sock) {
erdma_socket_disassoc(cep->sock);
sock_release(cep->sock);
cep->sock = NULL;
}
cep->state = ERDMA_EPSTATE_CLOSED;
erdma_cep_set_free(cep);
erdma_cep_put(cep);
}
}
int erdma_destroy_listen(struct iw_cm_id *id)
{
if (!id->provider_data)
return 0;
erdma_drop_listeners(id);
kfree(id->provider_data);
id->provider_data = NULL;
return 0;
}
int erdma_cm_init(void)
{
erdma_cm_wq = create_singlethread_workqueue("erdma_cm_wq");
if (!erdma_cm_wq)
return -ENOMEM;
return 0;
}
void erdma_cm_exit(void)
{
if (erdma_cm_wq)
destroy_workqueue(erdma_cm_wq);
}