/* * Intel MIC Platform Software Stack (MPSS) * * Copyright(c) 2014 Intel Corporation. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 2, as * published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * Intel SCIF driver. * */ #include #include "scif_main.h" #include "scif_map.h" static const char * const scif_ep_states[] = { "Unbound", "Bound", "Listening", "Connected", "Connecting", "Mapping", "Closing", "Close Listening", "Disconnected", "Zombie"}; enum conn_async_state { ASYNC_CONN_IDLE = 1, /* ep setup for async connect */ ASYNC_CONN_INPROGRESS, /* async connect in progress */ ASYNC_CONN_FLUSH_WORK /* async work flush in progress */ }; scif_epd_t scif_open(void) { struct scif_endpt *ep; might_sleep(); ep = kzalloc(sizeof(*ep), GFP_KERNEL); if (!ep) goto err_ep_alloc; ep->qp_info.qp = kzalloc(sizeof(*ep->qp_info.qp), GFP_KERNEL); if (!ep->qp_info.qp) goto err_qp_alloc; spin_lock_init(&ep->lock); mutex_init(&ep->sendlock); mutex_init(&ep->recvlock); ep->state = SCIFEP_UNBOUND; dev_dbg(scif_info.mdev.this_device, "SCIFAPI open: ep %p success\n", ep); return ep; err_qp_alloc: kfree(ep); err_ep_alloc: return NULL; } EXPORT_SYMBOL_GPL(scif_open); /* * scif_disconnect_ep - Disconnects the endpoint if found * @epd: The end point returned from scif_open() */ static struct scif_endpt *scif_disconnect_ep(struct scif_endpt *ep) { struct scifmsg msg; struct scif_endpt *fep = NULL; struct scif_endpt *tmpep; struct list_head *pos, *tmpq; int err; /* * Wake up any threads blocked in send()/recv() before closing * out the connection. Grabbing and releasing the send/recv lock * will ensure that any blocked senders/receivers have exited for * Ring 0 endpoints. It is a Ring 0 bug to call send/recv after * close. Ring 3 endpoints are not affected since close will not * be called while there are IOCTLs executing. */ wake_up_interruptible(&ep->sendwq); wake_up_interruptible(&ep->recvwq); mutex_lock(&ep->sendlock); mutex_unlock(&ep->sendlock); mutex_lock(&ep->recvlock); mutex_unlock(&ep->recvlock); /* Remove from the connected list */ mutex_lock(&scif_info.connlock); list_for_each_safe(pos, tmpq, &scif_info.connected) { tmpep = list_entry(pos, struct scif_endpt, list); if (tmpep == ep) { list_del(pos); fep = tmpep; spin_lock(&ep->lock); break; } } if (!fep) { /* * The other side has completed the disconnect before * the end point can be removed from the list. Therefore * the ep lock is not locked, traverse the disconnected * list to find the endpoint and release the conn lock. */ list_for_each_safe(pos, tmpq, &scif_info.disconnected) { tmpep = list_entry(pos, struct scif_endpt, list); if (tmpep == ep) { list_del(pos); break; } } mutex_unlock(&scif_info.connlock); return NULL; } init_completion(&ep->discon); msg.uop = SCIF_DISCNCT; msg.src = ep->port; msg.dst = ep->peer; msg.payload[0] = (u64)ep; msg.payload[1] = ep->remote_ep; err = scif_nodeqp_send(ep->remote_dev, &msg); spin_unlock(&ep->lock); mutex_unlock(&scif_info.connlock); if (!err) /* Wait for the remote node to respond with SCIF_DISCNT_ACK */ wait_for_completion_timeout(&ep->discon, SCIF_NODE_ALIVE_TIMEOUT); return ep; } int scif_close(scif_epd_t epd) { struct scif_endpt *ep = (struct scif_endpt *)epd; struct scif_endpt *tmpep; struct list_head *pos, *tmpq; enum scif_epd_state oldstate; bool flush_conn; dev_dbg(scif_info.mdev.this_device, "SCIFAPI close: ep %p %s\n", ep, scif_ep_states[ep->state]); might_sleep(); spin_lock(&ep->lock); flush_conn = (ep->conn_async_state == ASYNC_CONN_INPROGRESS); spin_unlock(&ep->lock); if (flush_conn) flush_work(&scif_info.conn_work); spin_lock(&ep->lock); oldstate = ep->state; ep->state = SCIFEP_CLOSING; switch (oldstate) { case SCIFEP_ZOMBIE: case SCIFEP_DISCONNECTED: spin_unlock(&ep->lock); /* Remove from the disconnected list */ mutex_lock(&scif_info.connlock); list_for_each_safe(pos, tmpq, &scif_info.disconnected) { tmpep = list_entry(pos, struct scif_endpt, list); if (tmpep == ep) { list_del(pos); break; } } mutex_unlock(&scif_info.connlock); break; case SCIFEP_UNBOUND: case SCIFEP_BOUND: case SCIFEP_CONNECTING: spin_unlock(&ep->lock); break; case SCIFEP_MAPPING: case SCIFEP_CONNECTED: case SCIFEP_CLOSING: { spin_unlock(&ep->lock); scif_disconnect_ep(ep); break; } case SCIFEP_LISTENING: case SCIFEP_CLLISTEN: { struct scif_conreq *conreq; struct scifmsg msg; struct scif_endpt *aep; spin_unlock(&ep->lock); spin_lock(&scif_info.eplock); /* remove from listen list */ list_for_each_safe(pos, tmpq, &scif_info.listen) { tmpep = list_entry(pos, struct scif_endpt, list); if (tmpep == ep) list_del(pos); } /* Remove any dangling accepts */ while (ep->acceptcnt) { aep = list_first_entry(&ep->li_accept, struct scif_endpt, liacceptlist); list_del(&aep->liacceptlist); scif_put_port(aep->port.port); list_for_each_safe(pos, tmpq, &scif_info.uaccept) { tmpep = list_entry(pos, struct scif_endpt, miacceptlist); if (tmpep == aep) { list_del(pos); break; } } spin_unlock(&scif_info.eplock); mutex_lock(&scif_info.connlock); list_for_each_safe(pos, tmpq, &scif_info.connected) { tmpep = list_entry(pos, struct scif_endpt, list); if (tmpep == aep) { list_del(pos); break; } } list_for_each_safe(pos, tmpq, &scif_info.disconnected) { tmpep = list_entry(pos, struct scif_endpt, list); if (tmpep == aep) { list_del(pos); break; } } mutex_unlock(&scif_info.connlock); scif_teardown_ep(aep); spin_lock(&scif_info.eplock); scif_add_epd_to_zombie_list(aep, SCIF_EPLOCK_HELD); ep->acceptcnt--; } spin_lock(&ep->lock); spin_unlock(&scif_info.eplock); /* Remove and reject any pending connection requests. */ while (ep->conreqcnt) { conreq = list_first_entry(&ep->conlist, struct scif_conreq, list); list_del(&conreq->list); msg.uop = SCIF_CNCT_REJ; msg.dst.node = conreq->msg.src.node; msg.dst.port = conreq->msg.src.port; msg.payload[0] = conreq->msg.payload[0]; msg.payload[1] = conreq->msg.payload[1]; /* * No Error Handling on purpose for scif_nodeqp_send(). * If the remote node is lost we still want free the * connection requests on the self node. */ scif_nodeqp_send(&scif_dev[conreq->msg.src.node], &msg); ep->conreqcnt--; kfree(conreq); } spin_unlock(&ep->lock); /* If a kSCIF accept is waiting wake it up */ wake_up_interruptible(&ep->conwq); break; } } scif_put_port(ep->port.port); scif_teardown_ep(ep); scif_add_epd_to_zombie_list(ep, !SCIF_EPLOCK_HELD); return 0; } EXPORT_SYMBOL_GPL(scif_close); /** * scif_flush() - Wakes up any blocking accepts. The endpoint will no longer * accept new connections. * @epd: The end point returned from scif_open() */ int __scif_flush(scif_epd_t epd) { struct scif_endpt *ep = (struct scif_endpt *)epd; switch (ep->state) { case SCIFEP_LISTENING: { ep->state = SCIFEP_CLLISTEN; /* If an accept is waiting wake it up */ wake_up_interruptible(&ep->conwq); break; } default: break; } return 0; } int scif_bind(scif_epd_t epd, u16 pn) { struct scif_endpt *ep = (struct scif_endpt *)epd; int ret = 0; int tmp; dev_dbg(scif_info.mdev.this_device, "SCIFAPI bind: ep %p %s requested port number %d\n", ep, scif_ep_states[ep->state], pn); if (pn) { /* * Similar to IETF RFC 1700, SCIF ports below * SCIF_ADMIN_PORT_END can only be bound by system (or root) * processes or by processes executed by privileged users. */ if (pn < SCIF_ADMIN_PORT_END && !capable(CAP_SYS_ADMIN)) { ret = -EACCES; goto scif_bind_admin_exit; } } spin_lock(&ep->lock); if (ep->state == SCIFEP_BOUND) { ret = -EINVAL; goto scif_bind_exit; } else if (ep->state != SCIFEP_UNBOUND) { ret = -EISCONN; goto scif_bind_exit; } if (pn) { tmp = scif_rsrv_port(pn); if (tmp != pn) { ret = -EINVAL; goto scif_bind_exit; } } else { pn = scif_get_new_port(); if (!pn) { ret = -ENOSPC; goto scif_bind_exit; } } ep->state = SCIFEP_BOUND; ep->port.node = scif_info.nodeid; ep->port.port = pn; ep->conn_async_state = ASYNC_CONN_IDLE; ret = pn; dev_dbg(scif_info.mdev.this_device, "SCIFAPI bind: bound to port number %d\n", pn); scif_bind_exit: spin_unlock(&ep->lock); scif_bind_admin_exit: return ret; } EXPORT_SYMBOL_GPL(scif_bind); int scif_listen(scif_epd_t epd, int backlog) { struct scif_endpt *ep = (struct scif_endpt *)epd; dev_dbg(scif_info.mdev.this_device, "SCIFAPI listen: ep %p %s\n", ep, scif_ep_states[ep->state]); spin_lock(&ep->lock); switch (ep->state) { case SCIFEP_ZOMBIE: case SCIFEP_CLOSING: case SCIFEP_CLLISTEN: case SCIFEP_UNBOUND: case SCIFEP_DISCONNECTED: spin_unlock(&ep->lock); return -EINVAL; case SCIFEP_LISTENING: case SCIFEP_CONNECTED: case SCIFEP_CONNECTING: case SCIFEP_MAPPING: spin_unlock(&ep->lock); return -EISCONN; case SCIFEP_BOUND: break; } ep->state = SCIFEP_LISTENING; ep->backlog = backlog; ep->conreqcnt = 0; ep->acceptcnt = 0; INIT_LIST_HEAD(&ep->conlist); init_waitqueue_head(&ep->conwq); INIT_LIST_HEAD(&ep->li_accept); spin_unlock(&ep->lock); /* * Listen status is complete so delete the qp information not needed * on a listen before placing on the list of listening ep's */ scif_teardown_ep(ep); ep->qp_info.qp = NULL; spin_lock(&scif_info.eplock); list_add_tail(&ep->list, &scif_info.listen); spin_unlock(&scif_info.eplock); return 0; } EXPORT_SYMBOL_GPL(scif_listen);