mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-05-16 07:51:31 -04:00
This is a better solution than EXPORT_SYMBOL_FOR_MODULES(__sym, "cifs,ksmbd") as it makes it possible to rebuild smbdirect.ko against a running kernel and then load the existing cifs.ko and ksmbd.ko from the running kernel. Suggested-by: Christoph Hellwig <hch@infradead.org> Link: https://lore.kernel.org/linux-cifs/aehrPuY60VMcYGU8@infradead.org/ Cc: Steve French <smfrench@gmail.com> Cc: Tom Talpey <tom@talpey.com> Cc: Long Li <longli@microsoft.com> Cc: Namjae Jeon <linkinjeon@kernel.org> Cc: Christoph Hellwig <hch@infradead.org> Cc: linux-cifs@vger.kernel.org Cc: samba-technical@lists.samba.org Signed-off-by: Stefan Metzmacher <metze@samba.org> Signed-off-by: Steve French <stfrench@microsoft.com>
744 lines
20 KiB
C
744 lines
20 KiB
C
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
/*
|
|
* Copyright (C) 2017, Microsoft Corporation.
|
|
* Copyright (c) 2025, Stefan Metzmacher
|
|
*/
|
|
|
|
#include "internal.h"
|
|
|
|
bool smbdirect_frwr_is_supported(const struct ib_device_attr *attrs)
|
|
{
|
|
/*
|
|
* Test if FRWR (Fast Registration Work Requests) is supported on the
|
|
* device This implementation requires FRWR on RDMA read/write return
|
|
* value: true if it is supported
|
|
*/
|
|
|
|
if (!(attrs->device_cap_flags & IB_DEVICE_MEM_MGT_EXTENSIONS))
|
|
return false;
|
|
if (attrs->max_fast_reg_page_list_len == 0)
|
|
return false;
|
|
return true;
|
|
}
|
|
EXPORT_SYMBOL_GPL(smbdirect_frwr_is_supported);
|
|
|
|
static void smbdirect_socket_cleanup_work(struct work_struct *work);
|
|
|
|
static int smbdirect_socket_rdma_event_handler(struct rdma_cm_id *id,
|
|
struct rdma_cm_event *event)
|
|
{
|
|
struct smbdirect_socket *sc = id->context;
|
|
int ret = -ESTALE;
|
|
|
|
/*
|
|
* This should be replaced before any real work
|
|
* starts! So it should never be called!
|
|
*/
|
|
|
|
if (event->event == RDMA_CM_EVENT_DEVICE_REMOVAL)
|
|
ret = -ENETDOWN;
|
|
if (IS_ERR(SMBDIRECT_DEBUG_ERR_PTR(event->status)))
|
|
ret = event->status;
|
|
pr_err("%s (first_error=%1pe, expected=%s) => event=%s status=%d => ret=%1pe\n",
|
|
smbdirect_socket_status_string(sc->status),
|
|
SMBDIRECT_DEBUG_ERR_PTR(sc->first_error),
|
|
rdma_event_msg(sc->rdma.expected_event),
|
|
rdma_event_msg(event->event),
|
|
event->status,
|
|
SMBDIRECT_DEBUG_ERR_PTR(ret));
|
|
WARN_ONCE(1, "%s should not be called!\n", __func__);
|
|
sc->rdma.cm_id = NULL;
|
|
return -ESTALE;
|
|
}
|
|
|
|
int smbdirect_socket_init_new(struct net *net, struct smbdirect_socket *sc)
|
|
{
|
|
struct rdma_cm_id *id;
|
|
int ret;
|
|
|
|
smbdirect_socket_init(sc);
|
|
|
|
id = rdma_create_id(net,
|
|
smbdirect_socket_rdma_event_handler,
|
|
sc,
|
|
RDMA_PS_TCP,
|
|
IB_QPT_RC);
|
|
if (IS_ERR(id)) {
|
|
pr_err("%s: rdma_create_id() failed %1pe\n", __func__, id);
|
|
return PTR_ERR(id);
|
|
}
|
|
|
|
ret = rdma_set_afonly(id, 1);
|
|
if (ret) {
|
|
rdma_destroy_id(id);
|
|
pr_err("%s: rdma_set_afonly() failed %1pe\n",
|
|
__func__, SMBDIRECT_DEBUG_ERR_PTR(ret));
|
|
return ret;
|
|
}
|
|
|
|
sc->rdma.cm_id = id;
|
|
|
|
INIT_WORK(&sc->disconnect_work, smbdirect_socket_cleanup_work);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int smbdirect_socket_create_kern(struct net *net, struct smbdirect_socket **_sc)
|
|
{
|
|
struct smbdirect_socket *sc;
|
|
int ret;
|
|
|
|
ret = -ENOMEM;
|
|
sc = kzalloc_obj(*sc);
|
|
if (!sc)
|
|
goto alloc_failed;
|
|
|
|
ret = smbdirect_socket_init_new(net, sc);
|
|
if (ret)
|
|
goto init_failed;
|
|
|
|
kref_init(&sc->refs.destroy);
|
|
|
|
*_sc = sc;
|
|
return 0;
|
|
|
|
init_failed:
|
|
kfree(sc);
|
|
alloc_failed:
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL_GPL(smbdirect_socket_create_kern);
|
|
|
|
int smbdirect_socket_init_accepting(struct rdma_cm_id *id, struct smbdirect_socket *sc)
|
|
{
|
|
smbdirect_socket_init(sc);
|
|
|
|
sc->rdma.cm_id = id;
|
|
sc->rdma.cm_id->context = sc;
|
|
sc->rdma.cm_id->event_handler = smbdirect_socket_rdma_event_handler;
|
|
|
|
sc->ib.dev = sc->rdma.cm_id->device;
|
|
|
|
INIT_WORK(&sc->disconnect_work, smbdirect_socket_cleanup_work);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int smbdirect_socket_create_accepting(struct rdma_cm_id *id, struct smbdirect_socket **_sc)
|
|
{
|
|
struct smbdirect_socket *sc;
|
|
int ret;
|
|
|
|
ret = -ENOMEM;
|
|
sc = kzalloc_obj(*sc);
|
|
if (!sc)
|
|
goto alloc_failed;
|
|
|
|
ret = smbdirect_socket_init_accepting(id, sc);
|
|
if (ret)
|
|
goto init_failed;
|
|
|
|
kref_init(&sc->refs.destroy);
|
|
|
|
*_sc = sc;
|
|
return 0;
|
|
|
|
init_failed:
|
|
kfree(sc);
|
|
alloc_failed:
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL_GPL(smbdirect_socket_create_accepting);
|
|
|
|
int smbdirect_socket_set_initial_parameters(struct smbdirect_socket *sc,
|
|
const struct smbdirect_socket_parameters *sp)
|
|
{
|
|
/*
|
|
* This is only allowed before connect or accept
|
|
*/
|
|
WARN_ONCE(sc->status != SMBDIRECT_SOCKET_CREATED,
|
|
"status=%s first_error=%1pe",
|
|
smbdirect_socket_status_string(sc->status),
|
|
SMBDIRECT_DEBUG_ERR_PTR(sc->first_error));
|
|
if (sc->status != SMBDIRECT_SOCKET_CREATED)
|
|
return -EINVAL;
|
|
|
|
if (sp->flags & ~SMBDIRECT_FLAG_PORT_RANGE_MASK)
|
|
return -EINVAL;
|
|
|
|
if (sp->initiator_depth > U8_MAX)
|
|
return -EINVAL;
|
|
if (sp->responder_resources > U8_MAX)
|
|
return -EINVAL;
|
|
|
|
if (sp->flags & SMBDIRECT_FLAG_PORT_RANGE_ONLY_IB &&
|
|
sp->flags & SMBDIRECT_FLAG_PORT_RANGE_ONLY_IW)
|
|
return -EINVAL;
|
|
else if (sp->flags & SMBDIRECT_FLAG_PORT_RANGE_ONLY_IB)
|
|
rdma_restrict_node_type(sc->rdma.cm_id, RDMA_NODE_IB_CA);
|
|
else if (sp->flags & SMBDIRECT_FLAG_PORT_RANGE_ONLY_IW)
|
|
rdma_restrict_node_type(sc->rdma.cm_id, RDMA_NODE_RNIC);
|
|
|
|
/*
|
|
* Make a copy of the callers parameters
|
|
* from here we only work on the copy
|
|
*
|
|
* TODO: do we want consistency checking?
|
|
*/
|
|
sc->parameters = *sp;
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_GPL(smbdirect_socket_set_initial_parameters);
|
|
|
|
const struct smbdirect_socket_parameters *
|
|
smbdirect_socket_get_current_parameters(struct smbdirect_socket *sc)
|
|
{
|
|
return &sc->parameters;
|
|
}
|
|
EXPORT_SYMBOL_GPL(smbdirect_socket_get_current_parameters);
|
|
|
|
int smbdirect_socket_set_kernel_settings(struct smbdirect_socket *sc,
|
|
enum ib_poll_context poll_ctx,
|
|
gfp_t gfp_mask)
|
|
{
|
|
/*
|
|
* This is only allowed before connect or accept
|
|
*/
|
|
WARN_ONCE(sc->status != SMBDIRECT_SOCKET_CREATED,
|
|
"status=%s first_error=%1pe",
|
|
smbdirect_socket_status_string(sc->status),
|
|
SMBDIRECT_DEBUG_ERR_PTR(sc->first_error));
|
|
if (sc->status != SMBDIRECT_SOCKET_CREATED)
|
|
return -EINVAL;
|
|
|
|
sc->ib.poll_ctx = poll_ctx;
|
|
|
|
sc->send_io.mem.gfp_mask = gfp_mask;
|
|
sc->recv_io.mem.gfp_mask = gfp_mask;
|
|
sc->rw_io.mem.gfp_mask = gfp_mask;
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_GPL(smbdirect_socket_set_kernel_settings);
|
|
|
|
void smbdirect_socket_set_logging(struct smbdirect_socket *sc,
|
|
void *private_ptr,
|
|
bool (*needed)(struct smbdirect_socket *sc,
|
|
void *private_ptr,
|
|
unsigned int lvl,
|
|
unsigned int cls),
|
|
void (*vaprintf)(struct smbdirect_socket *sc,
|
|
const char *func,
|
|
unsigned int line,
|
|
void *private_ptr,
|
|
unsigned int lvl,
|
|
unsigned int cls,
|
|
struct va_format *vaf))
|
|
{
|
|
sc->logging.private_ptr = private_ptr;
|
|
sc->logging.needed = needed;
|
|
sc->logging.vaprintf = vaprintf;
|
|
}
|
|
EXPORT_SYMBOL_GPL(smbdirect_socket_set_logging);
|
|
|
|
static void smbdirect_socket_wake_up_all(struct smbdirect_socket *sc)
|
|
{
|
|
/*
|
|
* Wake up all waiters in all wait queues
|
|
* in order to notice the broken connection.
|
|
*/
|
|
wake_up_all(&sc->status_wait);
|
|
wake_up_all(&sc->listen.wait_queue);
|
|
wake_up_all(&sc->send_io.bcredits.wait_queue);
|
|
wake_up_all(&sc->send_io.lcredits.wait_queue);
|
|
wake_up_all(&sc->send_io.credits.wait_queue);
|
|
wake_up_all(&sc->send_io.pending.zero_wait_queue);
|
|
wake_up_all(&sc->recv_io.reassembly.wait_queue);
|
|
wake_up_all(&sc->rw_io.credits.wait_queue);
|
|
wake_up_all(&sc->mr_io.ready.wait_queue);
|
|
}
|
|
|
|
void __smbdirect_socket_schedule_cleanup(struct smbdirect_socket *sc,
|
|
const char *macro_name,
|
|
unsigned int lvl,
|
|
const char *func,
|
|
unsigned int line,
|
|
int error,
|
|
enum smbdirect_socket_status *force_status)
|
|
{
|
|
struct smbdirect_socket *psc, *tsc;
|
|
unsigned long flags;
|
|
bool was_first = false;
|
|
|
|
if (!sc->first_error) {
|
|
___smbdirect_log_generic(sc, func, line,
|
|
lvl,
|
|
SMBDIRECT_LOG_RDMA_EVENT,
|
|
"%s(%1pe%s%s) called from %s in line=%u status=%s\n",
|
|
macro_name,
|
|
SMBDIRECT_DEBUG_ERR_PTR(error),
|
|
force_status ? ", " : "",
|
|
force_status ? smbdirect_socket_status_string(*force_status) : "",
|
|
func, line,
|
|
smbdirect_socket_status_string(sc->status));
|
|
if (error)
|
|
sc->first_error = error;
|
|
else
|
|
sc->first_error = -ECONNABORTED;
|
|
was_first = true;
|
|
}
|
|
|
|
/*
|
|
* make sure other work (than disconnect_work)
|
|
* is not queued again but here we don't block and avoid
|
|
* disable[_delayed]_work_sync()
|
|
*/
|
|
disable_work(&sc->connect.work);
|
|
disable_work(&sc->recv_io.posted.refill_work);
|
|
disable_work(&sc->idle.immediate_work);
|
|
sc->idle.keepalive = SMBDIRECT_KEEPALIVE_NONE;
|
|
disable_delayed_work(&sc->idle.timer_work);
|
|
|
|
/*
|
|
* In case we were a listener we need to
|
|
* disconnect all pending and ready sockets
|
|
*
|
|
* First we move ready sockets to pending again.
|
|
*/
|
|
spin_lock_irqsave(&sc->listen.lock, flags);
|
|
list_splice_init(&sc->listen.ready, &sc->listen.pending);
|
|
list_for_each_entry_safe(psc, tsc, &sc->listen.pending, accept.list)
|
|
smbdirect_socket_schedule_cleanup(psc, sc->first_error);
|
|
spin_unlock_irqrestore(&sc->listen.lock, flags);
|
|
|
|
switch (sc->status) {
|
|
case SMBDIRECT_SOCKET_RESOLVE_ADDR_FAILED:
|
|
case SMBDIRECT_SOCKET_RESOLVE_ROUTE_FAILED:
|
|
case SMBDIRECT_SOCKET_RDMA_CONNECT_FAILED:
|
|
case SMBDIRECT_SOCKET_NEGOTIATE_FAILED:
|
|
case SMBDIRECT_SOCKET_ERROR:
|
|
case SMBDIRECT_SOCKET_DISCONNECTING:
|
|
case SMBDIRECT_SOCKET_DISCONNECTED:
|
|
case SMBDIRECT_SOCKET_DESTROYED:
|
|
/*
|
|
* Keep the current error status
|
|
*/
|
|
break;
|
|
|
|
case SMBDIRECT_SOCKET_RESOLVE_ADDR_NEEDED:
|
|
case SMBDIRECT_SOCKET_RESOLVE_ADDR_RUNNING:
|
|
sc->status = SMBDIRECT_SOCKET_RESOLVE_ADDR_FAILED;
|
|
break;
|
|
|
|
case SMBDIRECT_SOCKET_RESOLVE_ROUTE_NEEDED:
|
|
case SMBDIRECT_SOCKET_RESOLVE_ROUTE_RUNNING:
|
|
sc->status = SMBDIRECT_SOCKET_RESOLVE_ROUTE_FAILED;
|
|
break;
|
|
|
|
case SMBDIRECT_SOCKET_RDMA_CONNECT_NEEDED:
|
|
case SMBDIRECT_SOCKET_RDMA_CONNECT_RUNNING:
|
|
sc->status = SMBDIRECT_SOCKET_RDMA_CONNECT_FAILED;
|
|
break;
|
|
|
|
case SMBDIRECT_SOCKET_NEGOTIATE_NEEDED:
|
|
case SMBDIRECT_SOCKET_NEGOTIATE_RUNNING:
|
|
sc->status = SMBDIRECT_SOCKET_NEGOTIATE_FAILED;
|
|
break;
|
|
|
|
case SMBDIRECT_SOCKET_CREATED:
|
|
case SMBDIRECT_SOCKET_LISTENING:
|
|
sc->status = SMBDIRECT_SOCKET_DISCONNECTED;
|
|
break;
|
|
|
|
case SMBDIRECT_SOCKET_CONNECTED:
|
|
sc->status = SMBDIRECT_SOCKET_ERROR;
|
|
break;
|
|
}
|
|
|
|
if (force_status && (was_first || *force_status > sc->status))
|
|
sc->status = *force_status;
|
|
|
|
/*
|
|
* Wake up all waiters in all wait queues
|
|
* in order to notice the broken connection.
|
|
*/
|
|
smbdirect_socket_wake_up_all(sc);
|
|
|
|
queue_work(sc->workqueues.cleanup, &sc->disconnect_work);
|
|
}
|
|
|
|
static void smbdirect_socket_cleanup_work(struct work_struct *work)
|
|
{
|
|
struct smbdirect_socket *sc =
|
|
container_of(work, struct smbdirect_socket, disconnect_work);
|
|
struct smbdirect_socket *psc, *tsc;
|
|
unsigned long flags;
|
|
|
|
/*
|
|
* This should not never be called in an interrupt!
|
|
*/
|
|
WARN_ON_ONCE(in_interrupt());
|
|
|
|
if (!sc->first_error) {
|
|
smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_ERR,
|
|
"%s called with first_error==0\n",
|
|
smbdirect_socket_status_string(sc->status));
|
|
|
|
sc->first_error = -ECONNABORTED;
|
|
}
|
|
|
|
/*
|
|
* make sure this and other work is not queued again
|
|
* but here we don't block and avoid
|
|
* disable[_delayed]_work_sync()
|
|
*/
|
|
disable_work(&sc->disconnect_work);
|
|
disable_work(&sc->connect.work);
|
|
disable_work(&sc->recv_io.posted.refill_work);
|
|
disable_work(&sc->idle.immediate_work);
|
|
sc->idle.keepalive = SMBDIRECT_KEEPALIVE_NONE;
|
|
disable_delayed_work(&sc->idle.timer_work);
|
|
|
|
/*
|
|
* In case we were a listener we need to
|
|
* disconnect all pending and ready sockets
|
|
*
|
|
* First we move ready sockets to pending again.
|
|
*/
|
|
spin_lock_irqsave(&sc->listen.lock, flags);
|
|
list_splice_init(&sc->listen.ready, &sc->listen.pending);
|
|
list_for_each_entry_safe(psc, tsc, &sc->listen.pending, accept.list)
|
|
smbdirect_socket_schedule_cleanup(psc, sc->first_error);
|
|
spin_unlock_irqrestore(&sc->listen.lock, flags);
|
|
|
|
switch (sc->status) {
|
|
case SMBDIRECT_SOCKET_NEGOTIATE_NEEDED:
|
|
case SMBDIRECT_SOCKET_NEGOTIATE_RUNNING:
|
|
case SMBDIRECT_SOCKET_NEGOTIATE_FAILED:
|
|
case SMBDIRECT_SOCKET_CONNECTED:
|
|
case SMBDIRECT_SOCKET_ERROR:
|
|
sc->status = SMBDIRECT_SOCKET_DISCONNECTING;
|
|
/*
|
|
* Make sure we hold the callback lock
|
|
* im order to coordinate with the
|
|
* rdma_event handlers, typically
|
|
* smbdirect_connection_rdma_event_handler(),
|
|
* and smbdirect_socket_destroy().
|
|
*
|
|
* So that the order of ib_drain_qp()
|
|
* and rdma_disconnect() is controlled
|
|
* by the mutex.
|
|
*/
|
|
rdma_lock_handler(sc->rdma.cm_id);
|
|
rdma_disconnect(sc->rdma.cm_id);
|
|
rdma_unlock_handler(sc->rdma.cm_id);
|
|
break;
|
|
|
|
case SMBDIRECT_SOCKET_CREATED:
|
|
case SMBDIRECT_SOCKET_LISTENING:
|
|
case SMBDIRECT_SOCKET_RESOLVE_ADDR_NEEDED:
|
|
case SMBDIRECT_SOCKET_RESOLVE_ADDR_RUNNING:
|
|
case SMBDIRECT_SOCKET_RESOLVE_ADDR_FAILED:
|
|
case SMBDIRECT_SOCKET_RESOLVE_ROUTE_NEEDED:
|
|
case SMBDIRECT_SOCKET_RESOLVE_ROUTE_RUNNING:
|
|
case SMBDIRECT_SOCKET_RESOLVE_ROUTE_FAILED:
|
|
case SMBDIRECT_SOCKET_RDMA_CONNECT_NEEDED:
|
|
case SMBDIRECT_SOCKET_RDMA_CONNECT_RUNNING:
|
|
case SMBDIRECT_SOCKET_RDMA_CONNECT_FAILED:
|
|
/*
|
|
* rdma_{accept,connect}() never reached
|
|
* RDMA_CM_EVENT_ESTABLISHED
|
|
*/
|
|
sc->status = SMBDIRECT_SOCKET_DISCONNECTED;
|
|
break;
|
|
|
|
case SMBDIRECT_SOCKET_DISCONNECTING:
|
|
case SMBDIRECT_SOCKET_DISCONNECTED:
|
|
case SMBDIRECT_SOCKET_DESTROYED:
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* Wake up all waiters in all wait queues
|
|
* in order to notice the broken connection.
|
|
*/
|
|
smbdirect_socket_wake_up_all(sc);
|
|
}
|
|
|
|
static void smbdirect_socket_destroy(struct smbdirect_socket *sc)
|
|
{
|
|
struct smbdirect_socket *psc, *tsc;
|
|
size_t psockets;
|
|
struct smbdirect_recv_io *recv_io;
|
|
struct smbdirect_recv_io *recv_tmp;
|
|
LIST_HEAD(all_list);
|
|
unsigned long flags;
|
|
|
|
smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_INFO,
|
|
"status=%s first_error=%1pe",
|
|
smbdirect_socket_status_string(sc->status),
|
|
SMBDIRECT_DEBUG_ERR_PTR(sc->first_error));
|
|
|
|
/*
|
|
* This should not never be called in an interrupt!
|
|
*/
|
|
WARN_ON_ONCE(in_interrupt());
|
|
|
|
if (sc->status == SMBDIRECT_SOCKET_DESTROYED)
|
|
return;
|
|
|
|
WARN_ONCE(sc->status != SMBDIRECT_SOCKET_DISCONNECTED,
|
|
"status=%s first_error=%1pe",
|
|
smbdirect_socket_status_string(sc->status),
|
|
SMBDIRECT_DEBUG_ERR_PTR(sc->first_error));
|
|
|
|
/*
|
|
* The listener should clear this before we reach this
|
|
*/
|
|
WARN_ONCE(sc->accept.listener,
|
|
"status=%s first_error=%1pe",
|
|
smbdirect_socket_status_string(sc->status),
|
|
SMBDIRECT_DEBUG_ERR_PTR(sc->first_error));
|
|
|
|
/*
|
|
* Wake up all waiters in all wait queues
|
|
* in order to notice the broken connection.
|
|
*
|
|
* Most likely this was already called via
|
|
* smbdirect_socket_cleanup_work(), but call it again...
|
|
*/
|
|
smbdirect_socket_wake_up_all(sc);
|
|
|
|
disable_work_sync(&sc->disconnect_work);
|
|
disable_work_sync(&sc->connect.work);
|
|
disable_work_sync(&sc->recv_io.posted.refill_work);
|
|
disable_work_sync(&sc->idle.immediate_work);
|
|
disable_delayed_work_sync(&sc->idle.timer_work);
|
|
|
|
if (sc->rdma.cm_id)
|
|
rdma_lock_handler(sc->rdma.cm_id);
|
|
|
|
if (sc->ib.qp) {
|
|
smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_INFO,
|
|
"drain qp\n");
|
|
ib_drain_qp(sc->ib.qp);
|
|
}
|
|
|
|
/*
|
|
* In case we were a listener we need to
|
|
* disconnect all pending and ready sockets
|
|
*
|
|
* We move ready sockets to pending again.
|
|
*/
|
|
spin_lock_irqsave(&sc->listen.lock, flags);
|
|
list_splice_tail_init(&sc->listen.ready, &all_list);
|
|
list_splice_tail_init(&sc->listen.pending, &all_list);
|
|
spin_unlock_irqrestore(&sc->listen.lock, flags);
|
|
psockets = list_count_nodes(&all_list);
|
|
if (sc->listen.backlog != -1) /* was a listener */
|
|
smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_INFO,
|
|
"release %zu pending sockets\n", psockets);
|
|
list_for_each_entry_safe(psc, tsc, &all_list, accept.list) {
|
|
list_del_init(&psc->accept.list);
|
|
psc->accept.listener = NULL;
|
|
smbdirect_socket_release(psc);
|
|
}
|
|
if (sc->listen.backlog != -1) /* was a listener */
|
|
smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_INFO,
|
|
"released %zu pending sockets\n", psockets);
|
|
INIT_LIST_HEAD(&all_list);
|
|
|
|
/* It's not possible for upper layer to get to reassembly */
|
|
if (sc->listen.backlog == -1) /* was not a listener */
|
|
smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_INFO,
|
|
"drain the reassembly queue\n");
|
|
spin_lock_irqsave(&sc->recv_io.reassembly.lock, flags);
|
|
list_splice_tail_init(&sc->recv_io.reassembly.list, &all_list);
|
|
spin_unlock_irqrestore(&sc->recv_io.reassembly.lock, flags);
|
|
list_for_each_entry_safe(recv_io, recv_tmp, &all_list, list)
|
|
smbdirect_connection_put_recv_io(recv_io);
|
|
sc->recv_io.reassembly.data_length = 0;
|
|
|
|
if (sc->listen.backlog == -1) /* was not a listener */
|
|
smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_INFO,
|
|
"freeing mr list\n");
|
|
smbdirect_connection_destroy_mr_list(sc);
|
|
|
|
if (sc->listen.backlog == -1) /* was not a listener */
|
|
smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_INFO,
|
|
"destroying qp\n");
|
|
smbdirect_connection_destroy_qp(sc);
|
|
if (sc->rdma.cm_id) {
|
|
rdma_unlock_handler(sc->rdma.cm_id);
|
|
smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_INFO,
|
|
"destroying cm_id\n");
|
|
rdma_destroy_id(sc->rdma.cm_id);
|
|
sc->rdma.cm_id = NULL;
|
|
}
|
|
|
|
if (sc->listen.backlog == -1) /* was not a listener */
|
|
smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_INFO,
|
|
"destroying mem pools\n");
|
|
smbdirect_connection_destroy_mem_pools(sc);
|
|
|
|
sc->status = SMBDIRECT_SOCKET_DESTROYED;
|
|
|
|
smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_INFO,
|
|
"rdma session destroyed\n");
|
|
}
|
|
|
|
void smbdirect_socket_destroy_sync(struct smbdirect_socket *sc)
|
|
{
|
|
smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_INFO,
|
|
"status=%s first_error=%1pe",
|
|
smbdirect_socket_status_string(sc->status),
|
|
SMBDIRECT_DEBUG_ERR_PTR(sc->first_error));
|
|
|
|
/*
|
|
* This should not never be called in an interrupt!
|
|
*/
|
|
WARN_ON_ONCE(in_interrupt());
|
|
|
|
/*
|
|
* First we try to disable the work
|
|
* without disable_work_sync() in a
|
|
* non blocking way, if it's already
|
|
* running it will be handles by
|
|
* disable_work_sync() below.
|
|
*
|
|
* Here we just want to make sure queue_work() in
|
|
* smbdirect_socket_schedule_cleanup_lvl()
|
|
* is a no-op.
|
|
*/
|
|
disable_work(&sc->disconnect_work);
|
|
|
|
if (!sc->first_error)
|
|
/*
|
|
* SMBDIRECT_LOG_INFO is enough here
|
|
* as this is the typical case where
|
|
* we terminate the connection ourself.
|
|
*/
|
|
smbdirect_socket_schedule_cleanup_lvl(sc,
|
|
SMBDIRECT_LOG_INFO,
|
|
-ESHUTDOWN);
|
|
|
|
smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_INFO,
|
|
"cancelling and disable disconnect_work\n");
|
|
disable_work_sync(&sc->disconnect_work);
|
|
|
|
smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_INFO,
|
|
"destroying rdma session\n");
|
|
if (sc->status < SMBDIRECT_SOCKET_DISCONNECTING)
|
|
smbdirect_socket_cleanup_work(&sc->disconnect_work);
|
|
if (sc->status < SMBDIRECT_SOCKET_DISCONNECTED) {
|
|
smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_INFO,
|
|
"wait for transport being disconnected\n");
|
|
wait_event(sc->status_wait, sc->status == SMBDIRECT_SOCKET_DISCONNECTED);
|
|
smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_INFO,
|
|
"waited for transport being disconnected\n");
|
|
}
|
|
|
|
/*
|
|
* Once we reached SMBDIRECT_SOCKET_DISCONNECTED,
|
|
* we should call smbdirect_socket_destroy()
|
|
*/
|
|
smbdirect_socket_destroy(sc);
|
|
smbdirect_log_rdma_event(sc, SMBDIRECT_LOG_INFO,
|
|
"status=%s first_error=%1pe",
|
|
smbdirect_socket_status_string(sc->status),
|
|
SMBDIRECT_DEBUG_ERR_PTR(sc->first_error));
|
|
}
|
|
|
|
int smbdirect_socket_bind(struct smbdirect_socket *sc, struct sockaddr *addr)
|
|
{
|
|
int ret;
|
|
|
|
if (sc->status != SMBDIRECT_SOCKET_CREATED)
|
|
return -EINVAL;
|
|
|
|
ret = rdma_bind_addr(sc->rdma.cm_id, addr);
|
|
if (ret)
|
|
return ret;
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_GPL(smbdirect_socket_bind);
|
|
|
|
void smbdirect_socket_shutdown(struct smbdirect_socket *sc)
|
|
{
|
|
smbdirect_socket_schedule_cleanup_lvl(sc, SMBDIRECT_LOG_INFO, -ESHUTDOWN);
|
|
}
|
|
EXPORT_SYMBOL_GPL(smbdirect_socket_shutdown);
|
|
|
|
static void smbdirect_socket_release_disconnect(struct kref *kref)
|
|
{
|
|
struct smbdirect_socket *sc =
|
|
container_of(kref, struct smbdirect_socket, refs.disconnect);
|
|
|
|
/*
|
|
* For now do a sync disconnect/destroy
|
|
*/
|
|
smbdirect_socket_destroy_sync(sc);
|
|
}
|
|
|
|
static void smbdirect_socket_release_destroy(struct kref *kref)
|
|
{
|
|
struct smbdirect_socket *sc =
|
|
container_of(kref, struct smbdirect_socket, refs.destroy);
|
|
|
|
/*
|
|
* Do a sync disconnect/destroy...
|
|
* hopefully a no-op, as it should be already
|
|
* in DESTROYED state, before we free the memory.
|
|
*/
|
|
smbdirect_socket_destroy_sync(sc);
|
|
kfree(sc);
|
|
}
|
|
|
|
void smbdirect_socket_release(struct smbdirect_socket *sc)
|
|
{
|
|
/*
|
|
* We expect only 1 disconnect reference
|
|
* and if it is already 0, it's a use after free!
|
|
*/
|
|
WARN_ON_ONCE(kref_read(&sc->refs.disconnect) != 1);
|
|
WARN_ON(!kref_put(&sc->refs.disconnect, smbdirect_socket_release_disconnect));
|
|
|
|
/*
|
|
* This may not trigger smbdirect_socket_release_destroy(),
|
|
* if struct smbdirect_socket is embedded in another structure
|
|
* indicated by REFCOUNT_MAX.
|
|
*/
|
|
kref_put(&sc->refs.destroy, smbdirect_socket_release_destroy);
|
|
}
|
|
EXPORT_SYMBOL_GPL(smbdirect_socket_release);
|
|
|
|
int smbdirect_socket_wait_for_credits(struct smbdirect_socket *sc,
|
|
enum smbdirect_socket_status expected_status,
|
|
int unexpected_errno,
|
|
wait_queue_head_t *waitq,
|
|
atomic_t *total_credits,
|
|
int needed)
|
|
{
|
|
int ret;
|
|
|
|
if (WARN_ON_ONCE(needed < 0))
|
|
return -EINVAL;
|
|
|
|
do {
|
|
if (atomic_sub_return(needed, total_credits) >= 0)
|
|
return 0;
|
|
|
|
atomic_add(needed, total_credits);
|
|
ret = wait_event_interruptible(*waitq,
|
|
atomic_read(total_credits) >= needed ||
|
|
sc->status != expected_status);
|
|
|
|
if (sc->status != expected_status)
|
|
return unexpected_errno;
|
|
else if (ret < 0)
|
|
return ret;
|
|
} while (true);
|
|
}
|