Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
+31
View File
@@ -0,0 +1,31 @@
# SPDX-License-Identifier: GPL-2.0-only
config SMC
tristate "SMC socket protocol family"
depends on INET && INFINIBAND && DIBS
help
SMC-R provides a "sockets over RDMA" solution making use of
RDMA over Converged Ethernet (RoCE) technology to upgrade
AF_INET TCP connections transparently.
The Linux implementation of the SMC-R solution is designed as
a separate socket family SMC.
Select this option if you want to run SMC socket applications
config SMC_DIAG
tristate "SMC: socket monitoring interface"
depends on SMC
help
Support for SMC socket monitoring interface used by tools such as
smcss.
if unsure, say Y.
config SMC_HS_CTRL_BPF
bool "Generic eBPF hook for SMC handshake flow"
depends on SMC && BPF_JIT && BPF_SYSCALL
default y
help
SMC_HS_CTRL_BPF enables support to register generic eBPF hook for SMC
handshake flow, which offer much greater flexibility in modifying the behavior
of the SMC protocol stack compared to a complete kernel-based approach. Select
this option if you want filtring the handshake process via eBPF programs.
+9
View File
@@ -0,0 +1,9 @@
# SPDX-License-Identifier: GPL-2.0-only
ccflags-y += -I$(src)
obj-$(CONFIG_SMC) += smc.o
obj-$(CONFIG_SMC_DIAG) += smc_diag.o
smc-y := af_smc.o smc_pnet.o smc_ib.o smc_clc.o smc_core.o smc_wr.o smc_llc.o
smc-y += smc_cdc.o smc_tx.o smc_rx.o smc_close.o smc_ism.o smc_netlink.o smc_stats.o
smc-y += smc_tracepoint.o smc_inet.o
smc-$(CONFIG_SYSCTL) += smc_sysctl.o
smc-$(CONFIG_SMC_HS_CTRL_BPF) += smc_hs_bpf.o
File diff suppressed because it is too large Load Diff
+434
View File
@@ -0,0 +1,434 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Shared Memory Communications over RDMA (SMC-R) and RoCE
*
* Definitions for the SMC module (socket related)
*
* Copyright IBM Corp. 2016
*
* Author(s): Ursula Braun <ubraun@linux.vnet.ibm.com>
*/
#ifndef __SMC_H
#define __SMC_H
#include <linux/socket.h>
#include <linux/types.h>
#include <linux/compiler.h> /* __aligned */
#include <net/genetlink.h>
#include <net/sock.h>
#include "smc_ib.h"
#define SMC_V1 1 /* SMC version V1 */
#define SMC_V2 2 /* SMC version V2 */
#define SMC_RELEASE_0 0
#define SMC_RELEASE_1 1
#define SMC_RELEASE SMC_RELEASE_1 /* the latest release version */
#define SMCPROTO_SMC 0 /* SMC protocol, IPv4 */
#define SMCPROTO_SMC6 1 /* SMC protocol, IPv6 */
#define SMC_AUTOCORKING_DEFAULT_SIZE 0x10000 /* 64K by default */
extern struct proto smc_proto;
extern struct proto smc_proto6;
extern struct smc_hashinfo smc_v4_hashinfo;
extern struct smc_hashinfo smc_v6_hashinfo;
int smc_hash_sk(struct sock *sk);
void smc_unhash_sk(struct sock *sk);
void smc_release_cb(struct sock *sk);
int smc_release(struct socket *sock);
int smc_bind(struct socket *sock, struct sockaddr_unsized *uaddr,
int addr_len);
int smc_connect(struct socket *sock, struct sockaddr_unsized *addr,
int alen, int flags);
int smc_accept(struct socket *sock, struct socket *new_sock,
struct proto_accept_arg *arg);
int smc_getname(struct socket *sock, struct sockaddr *addr,
int peer);
__poll_t smc_poll(struct file *file, struct socket *sock,
poll_table *wait);
int smc_ioctl(struct socket *sock, unsigned int cmd,
unsigned long arg);
int smc_listen(struct socket *sock, int backlog);
int smc_shutdown(struct socket *sock, int how);
int smc_setsockopt(struct socket *sock, int level, int optname,
sockptr_t optval, unsigned int optlen);
int smc_getsockopt(struct socket *sock, int level, int optname,
char __user *optval, int __user *optlen);
int smc_sendmsg(struct socket *sock, struct msghdr *msg, size_t len);
int smc_recvmsg(struct socket *sock, struct msghdr *msg, size_t len,
int flags);
ssize_t smc_splice_read(struct socket *sock, loff_t *ppos,
struct pipe_inode_info *pipe, size_t len,
unsigned int flags);
/* smc sock initialization */
void smc_sk_init(struct net *net, struct sock *sk, int protocol);
/* clcsock initialization */
int smc_create_clcsk(struct net *net, struct sock *sk, int family);
#ifdef ATOMIC64_INIT
#define KERNEL_HAS_ATOMIC64
#endif
enum smc_state { /* possible states of an SMC socket */
SMC_ACTIVE = 1,
SMC_INIT = 2,
SMC_CLOSED = 7,
SMC_LISTEN = 10,
/* normal close */
SMC_PEERCLOSEWAIT1 = 20,
SMC_PEERCLOSEWAIT2 = 21,
SMC_APPFINCLOSEWAIT = 24,
SMC_APPCLOSEWAIT1 = 22,
SMC_APPCLOSEWAIT2 = 23,
SMC_PEERFINCLOSEWAIT = 25,
/* abnormal close */
SMC_PEERABORTWAIT = 26,
SMC_PROCESSABORT = 27,
};
enum smc_supplemental_features {
SMC_SPF_EMULATED_ISM_DEV = 0,
};
#define SMC_FEATURE_MASK \
(BIT(SMC_SPF_EMULATED_ISM_DEV))
struct smc_link_group;
struct smc_wr_rx_hdr { /* common prefix part of LLC and CDC to demultiplex */
union {
u8 type;
#if defined(__BIG_ENDIAN_BITFIELD)
struct {
u8 llc_version:4,
llc_type:4;
};
#elif defined(__LITTLE_ENDIAN_BITFIELD)
struct {
u8 llc_type:4,
llc_version:4;
};
#endif
};
} __aligned(1);
struct smc_cdc_conn_state_flags {
#if defined(__BIG_ENDIAN_BITFIELD)
u8 peer_done_writing : 1; /* Sending done indicator */
u8 peer_conn_closed : 1; /* Peer connection closed indicator */
u8 peer_conn_abort : 1; /* Abnormal close indicator */
u8 reserved : 5;
#elif defined(__LITTLE_ENDIAN_BITFIELD)
u8 reserved : 5;
u8 peer_conn_abort : 1;
u8 peer_conn_closed : 1;
u8 peer_done_writing : 1;
#endif
};
struct smc_cdc_producer_flags {
#if defined(__BIG_ENDIAN_BITFIELD)
u8 write_blocked : 1; /* Writing Blocked, no rx buf space */
u8 urg_data_pending : 1; /* Urgent Data Pending */
u8 urg_data_present : 1; /* Urgent Data Present */
u8 cons_curs_upd_req : 1; /* cursor update requested */
u8 failover_validation : 1;/* message replay due to failover */
u8 reserved : 3;
#elif defined(__LITTLE_ENDIAN_BITFIELD)
u8 reserved : 3;
u8 failover_validation : 1;
u8 cons_curs_upd_req : 1;
u8 urg_data_present : 1;
u8 urg_data_pending : 1;
u8 write_blocked : 1;
#endif
};
/* in host byte order */
union smc_host_cursor { /* SMC cursor - an offset in an RMBE */
struct {
u16 reserved;
u16 wrap; /* window wrap sequence number */
u32 count; /* cursor (= offset) part */
};
#ifdef KERNEL_HAS_ATOMIC64
atomic64_t acurs; /* for atomic processing */
#else
u64 acurs; /* for atomic processing */
#endif
} __aligned(8);
/* in host byte order, except for flag bitfields in network byte order */
struct smc_host_cdc_msg { /* Connection Data Control message */
struct smc_wr_rx_hdr common; /* .type = 0xFE */
u8 len; /* length = 44 */
u16 seqno; /* connection seq # */
u32 token; /* alert_token */
union smc_host_cursor prod; /* producer cursor */
union smc_host_cursor cons; /* consumer cursor,
* piggy backed "ack"
*/
struct smc_cdc_producer_flags prod_flags; /* conn. tx/rx status */
struct smc_cdc_conn_state_flags conn_state_flags; /* peer conn. status*/
u8 reserved[18];
} __aligned(8);
enum smc_urg_state {
SMC_URG_VALID = 1, /* data present */
SMC_URG_NOTYET = 2, /* data pending */
SMC_URG_READ = 3, /* data was already read */
};
struct smc_mark_woken {
bool woken;
void *key;
wait_queue_entry_t wait_entry;
};
struct smc_connection {
struct rb_node alert_node;
struct smc_link_group *lgr; /* link group of connection */
struct smc_link *lnk; /* assigned SMC-R link */
u32 alert_token_local; /* unique conn. id */
u8 peer_rmbe_idx; /* from tcp handshake */
int peer_rmbe_size; /* size of peer rx buffer */
atomic_t peer_rmbe_space;/* remaining free bytes in peer
* rmbe
*/
int rtoken_idx; /* idx to peer RMB rkey/addr */
struct smc_buf_desc *sndbuf_desc; /* send buffer descriptor */
struct smc_buf_desc *rmb_desc; /* RMBE descriptor */
int rmbe_size_comp; /* compressed notation */
int rmbe_update_limit;
/* lower limit for consumer
* cursor update
*/
struct smc_host_cdc_msg local_tx_ctrl; /* host byte order staging
* buffer for CDC msg send
* .prod cf. TCP snd_nxt
* .cons cf. TCP sends ack
*/
union smc_host_cursor local_tx_ctrl_fin;
/* prod crsr - confirmed by peer
*/
union smc_host_cursor tx_curs_prep; /* tx - prepared data
* snd_max..wmem_alloc
*/
union smc_host_cursor tx_curs_sent; /* tx - sent data
* snd_nxt ?
*/
union smc_host_cursor tx_curs_fin; /* tx - confirmed by peer
* snd-wnd-begin ?
*/
atomic_t sndbuf_space; /* remaining space in sndbuf */
u16 tx_cdc_seq; /* sequence # for CDC send */
u16 tx_cdc_seq_fin; /* sequence # - tx completed */
spinlock_t send_lock; /* protect wr_sends */
atomic_t cdc_pend_tx_wr; /* number of pending tx CDC wqe
* - inc when post wqe,
* - dec on polled tx cqe
*/
wait_queue_head_t cdc_pend_tx_wq; /* wakeup on no cdc_pend_tx_wr*/
struct delayed_work tx_work; /* retry of smc_cdc_msg_send */
u32 tx_off; /* base offset in peer rmb */
struct smc_host_cdc_msg local_rx_ctrl; /* filled during event_handl.
* .prod cf. TCP rcv_nxt
* .cons cf. TCP snd_una
*/
union smc_host_cursor rx_curs_confirmed; /* confirmed to peer
* source of snd_una ?
*/
union smc_host_cursor urg_curs; /* points at urgent byte */
enum smc_urg_state urg_state;
bool urg_tx_pend; /* urgent data staged */
bool urg_rx_skip_pend;
/* indicate urgent oob data
* read, but previous regular
* data still pending
*/
char urg_rx_byte; /* urgent byte */
bool tx_in_release_sock;
/* flush pending tx data in
* sock release_cb()
*/
atomic_t bytes_to_rcv; /* arrived data,
* not yet received
*/
atomic_t splice_pending; /* number of spliced bytes
* pending processing
*/
#ifndef KERNEL_HAS_ATOMIC64
spinlock_t acurs_lock; /* protect cursors */
#endif
struct work_struct close_work; /* peer sent some closing */
struct work_struct abort_work; /* abort the connection */
struct tasklet_struct rx_tsklet; /* Receiver tasklet for SMC-D */
u8 rx_off; /* receive offset:
* 0 for SMC-R, 32 for SMC-D
*/
u64 peer_token; /* SMC-D token of peer */
u8 killed : 1; /* abnormal termination */
u8 freed : 1; /* normal termination */
u8 out_of_sync : 1; /* out of sync with peer */
};
struct smc_sock { /* smc sock container */
union {
struct sock sk;
struct inet_sock icsk_inet;
};
struct socket *clcsock; /* internal tcp socket */
void (*clcsk_state_change)(struct sock *sk);
/* original stat_change fct. */
void (*clcsk_data_ready)(struct sock *sk);
/* original data_ready fct. */
void (*clcsk_write_space)(struct sock *sk);
/* original write_space fct. */
void (*clcsk_error_report)(struct sock *sk);
/* original error_report fct. */
struct smc_connection conn; /* smc connection */
struct smc_sock *listen_smc; /* listen parent */
struct work_struct connect_work; /* handle non-blocking connect*/
struct work_struct tcp_listen_work;/* handle tcp socket accepts */
struct work_struct smc_listen_work;/* prepare new accept socket */
struct list_head accept_q; /* sockets to be accepted */
spinlock_t accept_q_lock; /* protects accept_q */
bool limit_smc_hs; /* put constraint on handshake */
bool use_fallback; /* fallback to tcp */
int fallback_rsn; /* reason for fallback */
u32 peer_diagnosis; /* decline reason from peer */
atomic_t queued_smc_hs; /* queued smc handshakes */
struct inet_connection_sock_af_ops af_ops;
const struct inet_connection_sock_af_ops *ori_af_ops;
/* original af ops */
int sockopt_defer_accept;
/* sockopt TCP_DEFER_ACCEPT
* value
*/
u8 wait_close_tx_prepared : 1;
/* shutdown wr or close
* started, waiting for unsent
* data to be sent
*/
u8 connect_nonblock : 1;
/* non-blocking connect in
* flight
*/
struct mutex clcsock_release_lock;
/* protects clcsock of a listen
* socket
* */
};
#define smc_sk(ptr) container_of_const(ptr, struct smc_sock, sk)
static inline void smc_init_saved_callbacks(struct smc_sock *smc)
{
smc->clcsk_state_change = NULL;
smc->clcsk_data_ready = NULL;
smc->clcsk_write_space = NULL;
smc->clcsk_error_report = NULL;
}
static inline struct smc_sock *smc_clcsock_user_data(const struct sock *clcsk)
{
return (struct smc_sock *)
((uintptr_t)clcsk->sk_user_data & ~SK_USER_DATA_NOCOPY);
}
static inline struct smc_sock *smc_clcsock_user_data_rcu(const struct sock *clcsk)
{
return (struct smc_sock *)rcu_dereference_sk_user_data(clcsk);
}
/* save target_cb in saved_cb, and replace target_cb with new_cb */
static inline void smc_clcsock_replace_cb(void (**target_cb)(struct sock *),
void (*new_cb)(struct sock *),
void (**saved_cb)(struct sock *))
{
/* only save once */
if (!*saved_cb)
*saved_cb = *target_cb;
*target_cb = new_cb;
}
/* restore target_cb to saved_cb, and reset saved_cb to NULL */
static inline void smc_clcsock_restore_cb(void (**target_cb)(struct sock *),
void (**saved_cb)(struct sock *))
{
if (!*saved_cb)
return;
*target_cb = *saved_cb;
*saved_cb = NULL;
}
extern struct workqueue_struct *smc_hs_wq; /* wq for handshake work */
extern struct workqueue_struct *smc_close_wq; /* wq for close work */
#define SMC_SYSTEMID_LEN 8
extern u8 local_systemid[SMC_SYSTEMID_LEN]; /* unique system identifier */
#define ntohll(x) be64_to_cpu(x)
#define htonll(x) cpu_to_be64(x)
/* convert an u32 value into network byte order, store it into a 3 byte field */
static inline void hton24(u8 *net, u32 host)
{
__be32 t;
t = cpu_to_be32(host);
memcpy(net, ((u8 *)&t) + 1, 3);
}
/* convert a received 3 byte field into host byte order*/
static inline u32 ntoh24(u8 *net)
{
__be32 t = 0;
memcpy(((u8 *)&t) + 1, net, 3);
return be32_to_cpu(t);
}
#ifdef CONFIG_XFRM
static inline bool using_ipsec(struct smc_sock *smc)
{
return (smc->clcsock->sk->sk_policy[0] ||
smc->clcsock->sk->sk_policy[1]) ? true : false;
}
#else
static inline bool using_ipsec(struct smc_sock *smc)
{
return false;
}
#endif
struct smc_gidlist;
struct sock *smc_accept_dequeue(struct sock *parent, struct socket *new_sock);
void smc_close_non_accepted(struct sock *sk);
void smc_fill_gid_list(struct smc_link_group *lgr,
struct smc_gidlist *gidlist,
struct smc_ib_device *known_dev, u8 *known_gid);
/* smc handshake limitation interface for netlink */
int smc_nl_dump_hs_limitation(struct sk_buff *skb, struct netlink_callback *cb);
int smc_nl_enable_hs_limitation(struct sk_buff *skb, struct genl_info *info);
int smc_nl_disable_hs_limitation(struct sk_buff *skb, struct genl_info *info);
static inline void smc_sock_set_flag(struct sock *sk, enum sock_flags flag)
{
set_bit(flag, &sk->sk_flags);
}
#endif /* __SMC_H */
+530
View File
@@ -0,0 +1,530 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Shared Memory Communications over RDMA (SMC-R) and RoCE
*
* Connection Data Control (CDC)
* handles flow control
*
* Copyright IBM Corp. 2016
*
* Author(s): Ursula Braun <ubraun@linux.vnet.ibm.com>
*/
#include <linux/spinlock.h>
#include "smc.h"
#include "smc_wr.h"
#include "smc_cdc.h"
#include "smc_tx.h"
#include "smc_rx.h"
#include "smc_close.h"
#include "smc_ism.h"
/********************************** send *************************************/
/* handler for send/transmission completion of a CDC msg */
static void smc_cdc_tx_handler(struct smc_wr_tx_pend_priv *pnd_snd,
struct smc_link *link,
enum ib_wc_status wc_status)
{
struct smc_cdc_tx_pend *cdcpend = (struct smc_cdc_tx_pend *)pnd_snd;
struct smc_connection *conn = cdcpend->conn;
struct smc_buf_desc *sndbuf_desc;
struct smc_sock *smc;
int diff;
sndbuf_desc = conn->sndbuf_desc;
smc = container_of(conn, struct smc_sock, conn);
bh_lock_sock(&smc->sk);
if (!wc_status && sndbuf_desc) {
diff = smc_curs_diff(sndbuf_desc->len,
&cdcpend->conn->tx_curs_fin,
&cdcpend->cursor);
/* sndbuf_space is decreased in smc_sendmsg */
smp_mb__before_atomic();
atomic_add(diff, &cdcpend->conn->sndbuf_space);
/* guarantee 0 <= sndbuf_space <= sndbuf_desc->len */
smp_mb__after_atomic();
smc_curs_copy(&conn->tx_curs_fin, &cdcpend->cursor, conn);
smc_curs_copy(&conn->local_tx_ctrl_fin, &cdcpend->p_cursor,
conn);
conn->tx_cdc_seq_fin = cdcpend->ctrl_seq;
}
if (atomic_dec_and_test(&conn->cdc_pend_tx_wr)) {
/* If user owns the sock_lock, mark the connection need sending.
* User context will later try to send when it release sock_lock
* in smc_release_cb()
*/
if (sock_owned_by_user(&smc->sk))
conn->tx_in_release_sock = true;
else
smc_tx_pending(conn);
if (unlikely(wq_has_sleeper(&conn->cdc_pend_tx_wq)))
wake_up(&conn->cdc_pend_tx_wq);
}
WARN_ON(atomic_read(&conn->cdc_pend_tx_wr) < 0);
smc_tx_sndbuf_nonfull(smc);
bh_unlock_sock(&smc->sk);
}
int smc_cdc_get_free_slot(struct smc_connection *conn,
struct smc_link *link,
struct smc_wr_buf **wr_buf,
struct smc_rdma_wr **wr_rdma_buf,
struct smc_cdc_tx_pend **pend)
{
int rc;
rc = smc_wr_tx_get_free_slot(link, smc_cdc_tx_handler, wr_buf,
wr_rdma_buf,
(struct smc_wr_tx_pend_priv **)pend);
if (conn->killed) {
/* abnormal termination */
if (!rc)
smc_wr_tx_put_slot(link,
(struct smc_wr_tx_pend_priv *)(*pend));
rc = -EPIPE;
}
return rc;
}
static inline void smc_cdc_add_pending_send(struct smc_connection *conn,
struct smc_cdc_tx_pend *pend)
{
BUILD_BUG_ON_MSG(
sizeof(struct smc_cdc_msg) > SMC_WR_BUF_SIZE,
"must increase SMC_WR_BUF_SIZE to at least sizeof(struct smc_cdc_msg)");
BUILD_BUG_ON_MSG(
offsetofend(struct smc_cdc_msg, reserved) > SMC_WR_TX_SIZE,
"must adapt SMC_WR_TX_SIZE to sizeof(struct smc_cdc_msg); if not all smc_wr upper layer protocols use the same message size any more, must start to set link->wr_tx_sges[i].length on each individual smc_wr_tx_send()");
BUILD_BUG_ON_MSG(
sizeof(struct smc_cdc_tx_pend) > SMC_WR_TX_PEND_PRIV_SIZE,
"must increase SMC_WR_TX_PEND_PRIV_SIZE to at least sizeof(struct smc_cdc_tx_pend)");
pend->conn = conn;
pend->cursor = conn->tx_curs_sent;
pend->p_cursor = conn->local_tx_ctrl.prod;
pend->ctrl_seq = conn->tx_cdc_seq;
}
int smc_cdc_msg_send(struct smc_connection *conn,
struct smc_wr_buf *wr_buf,
struct smc_cdc_tx_pend *pend)
{
struct smc_link *link = conn->lnk;
union smc_host_cursor cfed;
int rc;
smc_cdc_add_pending_send(conn, pend);
conn->tx_cdc_seq++;
conn->local_tx_ctrl.seqno = conn->tx_cdc_seq;
smc_host_msg_to_cdc((struct smc_cdc_msg *)wr_buf, conn, &cfed);
atomic_inc(&conn->cdc_pend_tx_wr);
smp_mb__after_atomic(); /* Make sure cdc_pend_tx_wr added before post */
rc = smc_wr_tx_send(link, (struct smc_wr_tx_pend_priv *)pend);
if (!rc) {
smc_curs_copy(&conn->rx_curs_confirmed, &cfed, conn);
conn->local_rx_ctrl.prod_flags.cons_curs_upd_req = 0;
} else {
conn->tx_cdc_seq--;
conn->local_tx_ctrl.seqno = conn->tx_cdc_seq;
atomic_dec(&conn->cdc_pend_tx_wr);
}
return rc;
}
/* send a validation msg indicating the move of a conn to an other QP link */
int smcr_cdc_msg_send_validation(struct smc_connection *conn,
struct smc_cdc_tx_pend *pend,
struct smc_wr_buf *wr_buf)
{
struct smc_host_cdc_msg *local = &conn->local_tx_ctrl;
struct smc_link *link = conn->lnk;
struct smc_cdc_msg *peer;
int rc;
peer = (struct smc_cdc_msg *)wr_buf;
peer->common.type = local->common.type;
peer->len = local->len;
peer->seqno = htons(conn->tx_cdc_seq_fin); /* seqno last compl. tx */
peer->token = htonl(local->token);
peer->prod_flags.failover_validation = 1;
/* We need to set pend->conn here to make sure smc_cdc_tx_handler()
* can handle properly
*/
smc_cdc_add_pending_send(conn, pend);
atomic_inc(&conn->cdc_pend_tx_wr);
smp_mb__after_atomic(); /* Make sure cdc_pend_tx_wr added before post */
rc = smc_wr_tx_send(link, (struct smc_wr_tx_pend_priv *)pend);
if (unlikely(rc))
atomic_dec(&conn->cdc_pend_tx_wr);
return rc;
}
static int smcr_cdc_get_slot_and_msg_send(struct smc_connection *conn)
{
struct smc_cdc_tx_pend *pend;
struct smc_wr_buf *wr_buf;
struct smc_link *link;
bool again = false;
int rc;
again:
link = conn->lnk;
if (!smc_wr_tx_link_hold(link))
return -ENOLINK;
rc = smc_cdc_get_free_slot(conn, link, &wr_buf, NULL, &pend);
if (rc)
goto put_out;
spin_lock_bh(&conn->send_lock);
if (link != conn->lnk) {
/* link of connection changed, try again one time*/
spin_unlock_bh(&conn->send_lock);
smc_wr_tx_put_slot(link,
(struct smc_wr_tx_pend_priv *)pend);
smc_wr_tx_link_put(link);
if (again)
return -ENOLINK;
again = true;
goto again;
}
rc = smc_cdc_msg_send(conn, wr_buf, pend);
spin_unlock_bh(&conn->send_lock);
put_out:
smc_wr_tx_link_put(link);
return rc;
}
int smc_cdc_get_slot_and_msg_send(struct smc_connection *conn)
{
int rc;
if (!smc_conn_lgr_valid(conn) ||
(conn->lgr->is_smcd && conn->lgr->peer_shutdown))
return -EPIPE;
if (conn->lgr->is_smcd) {
spin_lock_bh(&conn->send_lock);
rc = smcd_cdc_msg_send(conn);
spin_unlock_bh(&conn->send_lock);
} else {
rc = smcr_cdc_get_slot_and_msg_send(conn);
}
return rc;
}
void smc_cdc_wait_pend_tx_wr(struct smc_connection *conn)
{
wait_event(conn->cdc_pend_tx_wq, !atomic_read(&conn->cdc_pend_tx_wr));
}
/* Send a SMC-D CDC header.
* This increments the free space available in our send buffer.
* Also update the confirmed receive buffer with what was sent to the peer.
*/
int smcd_cdc_msg_send(struct smc_connection *conn)
{
struct smc_sock *smc = container_of(conn, struct smc_sock, conn);
union smc_host_cursor curs;
struct smcd_cdc_msg cdc;
int rc, diff;
memset(&cdc, 0, sizeof(cdc));
cdc.common.type = SMC_CDC_MSG_TYPE;
curs.acurs.counter = atomic64_read(&conn->local_tx_ctrl.prod.acurs);
cdc.prod.wrap = curs.wrap;
cdc.prod.count = curs.count;
curs.acurs.counter = atomic64_read(&conn->local_tx_ctrl.cons.acurs);
cdc.cons.wrap = curs.wrap;
cdc.cons.count = curs.count;
cdc.cons.prod_flags = conn->local_tx_ctrl.prod_flags;
cdc.cons.conn_state_flags = conn->local_tx_ctrl.conn_state_flags;
rc = smcd_tx_ism_write(conn, &cdc, sizeof(cdc), 0, 1);
if (rc)
return rc;
smc_curs_copy(&conn->rx_curs_confirmed, &curs, conn);
conn->local_rx_ctrl.prod_flags.cons_curs_upd_req = 0;
if (smc_ism_support_dmb_nocopy(conn->lgr->smcd))
/* if local sndbuf shares the same memory region with
* peer DMB, then don't update the tx_curs_fin
* and sndbuf_space until peer has consumed the data.
*/
return 0;
/* Calculate transmitted data and increment free send buffer space */
diff = smc_curs_diff(conn->sndbuf_desc->len, &conn->tx_curs_fin,
&conn->tx_curs_sent);
/* increased by confirmed number of bytes */
smp_mb__before_atomic();
atomic_add(diff, &conn->sndbuf_space);
/* guarantee 0 <= sndbuf_space <= sndbuf_desc->len */
smp_mb__after_atomic();
smc_curs_copy(&conn->tx_curs_fin, &conn->tx_curs_sent, conn);
smc_tx_sndbuf_nonfull(smc);
return 0;
}
/********************************* receive ***********************************/
static inline bool smc_cdc_before(u16 seq1, u16 seq2)
{
return (s16)(seq1 - seq2) < 0;
}
static void smc_cdc_handle_urg_data_arrival(struct smc_sock *smc,
int *diff_prod)
{
struct smc_connection *conn = &smc->conn;
char *base;
/* new data included urgent business */
smc_curs_copy(&conn->urg_curs, &conn->local_rx_ctrl.prod, conn);
conn->urg_state = SMC_URG_VALID;
if (!sock_flag(&smc->sk, SOCK_URGINLINE))
/* we'll skip the urgent byte, so don't account for it */
(*diff_prod)--;
base = (char *)conn->rmb_desc->cpu_addr + conn->rx_off;
if (conn->urg_curs.count)
conn->urg_rx_byte = *(base + conn->urg_curs.count - 1);
else
conn->urg_rx_byte = *(base + conn->rmb_desc->len - 1);
sk_send_sigurg(&smc->sk);
}
static void smc_cdc_msg_validate(struct smc_sock *smc, struct smc_cdc_msg *cdc,
struct smc_link *link)
{
struct smc_connection *conn = &smc->conn;
u16 recv_seq = ntohs(cdc->seqno);
s16 diff;
/* check that seqnum was seen before */
diff = conn->local_rx_ctrl.seqno - recv_seq;
if (diff < 0) { /* diff larger than 0x7fff */
/* drop connection */
conn->out_of_sync = 1; /* prevent any further receives */
spin_lock_bh(&conn->send_lock);
conn->local_tx_ctrl.conn_state_flags.peer_conn_abort = 1;
conn->lnk = link;
spin_unlock_bh(&conn->send_lock);
sock_hold(&smc->sk); /* sock_put in abort_work */
if (!queue_work(smc_close_wq, &conn->abort_work))
sock_put(&smc->sk);
}
}
static void smc_cdc_msg_recv_action(struct smc_sock *smc,
struct smc_cdc_msg *cdc)
{
union smc_host_cursor cons_old, prod_old;
struct smc_connection *conn = &smc->conn;
int diff_cons, diff_prod, diff_tx;
smc_curs_copy(&prod_old, &conn->local_rx_ctrl.prod, conn);
smc_curs_copy(&cons_old, &conn->local_rx_ctrl.cons, conn);
smc_cdc_msg_to_host(&conn->local_rx_ctrl, cdc, conn);
diff_cons = smc_curs_diff(conn->peer_rmbe_size, &cons_old,
&conn->local_rx_ctrl.cons);
if (diff_cons) {
/* peer_rmbe_space is decreased during data transfer with RDMA
* write
*/
smp_mb__before_atomic();
atomic_add(diff_cons, &conn->peer_rmbe_space);
/* guarantee 0 <= peer_rmbe_space <= peer_rmbe_size */
smp_mb__after_atomic();
/* if local sndbuf shares the same memory region with
* peer RMB, then update tx_curs_fin and sndbuf_space
* here since peer has already consumed the data.
*/
if (conn->lgr->is_smcd &&
smc_ism_support_dmb_nocopy(conn->lgr->smcd)) {
/* Calculate consumed data and
* increment free send buffer space.
*/
diff_tx = smc_curs_diff(conn->sndbuf_desc->len,
&conn->tx_curs_fin,
&conn->local_rx_ctrl.cons);
/* increase local sndbuf space and fin_curs */
smp_mb__before_atomic();
atomic_add(diff_tx, &conn->sndbuf_space);
/* guarantee 0 <= sndbuf_space <= sndbuf_desc->len */
smp_mb__after_atomic();
smc_curs_copy(&conn->tx_curs_fin,
&conn->local_rx_ctrl.cons, conn);
smc_tx_sndbuf_nonfull(smc);
}
}
diff_prod = smc_curs_diff(conn->rmb_desc->len, &prod_old,
&conn->local_rx_ctrl.prod);
if (diff_prod) {
if (conn->local_rx_ctrl.prod_flags.urg_data_present)
smc_cdc_handle_urg_data_arrival(smc, &diff_prod);
/* bytes_to_rcv is decreased in smc_recvmsg */
smp_mb__before_atomic();
atomic_add(diff_prod, &conn->bytes_to_rcv);
/* guarantee 0 <= bytes_to_rcv <= rmb_desc->len */
smp_mb__after_atomic();
smc->sk.sk_data_ready(&smc->sk);
} else {
if (conn->local_rx_ctrl.prod_flags.write_blocked)
smc->sk.sk_data_ready(&smc->sk);
if (conn->local_rx_ctrl.prod_flags.urg_data_pending)
conn->urg_state = SMC_URG_NOTYET;
}
/* trigger sndbuf consumer: RDMA write into peer RMBE and CDC */
if ((diff_cons && smc_tx_prepared_sends(conn)) ||
conn->local_rx_ctrl.prod_flags.cons_curs_upd_req ||
conn->local_rx_ctrl.prod_flags.urg_data_pending) {
if (!sock_owned_by_user(&smc->sk))
smc_tx_pending(conn);
else
conn->tx_in_release_sock = true;
}
if (diff_cons && conn->urg_tx_pend &&
atomic_read(&conn->peer_rmbe_space) == conn->peer_rmbe_size) {
/* urg data confirmed by peer, indicate we're ready for more */
conn->urg_tx_pend = false;
smc->sk.sk_write_space(&smc->sk);
}
if (conn->local_rx_ctrl.conn_state_flags.peer_conn_abort) {
smc->sk.sk_err = ECONNRESET;
conn->local_tx_ctrl.conn_state_flags.peer_conn_abort = 1;
}
if (smc_cdc_rxed_any_close_or_senddone(conn)) {
smc->sk.sk_shutdown |= RCV_SHUTDOWN;
if (smc->clcsock && smc->clcsock->sk)
smc->clcsock->sk->sk_shutdown |= RCV_SHUTDOWN;
smc_sock_set_flag(&smc->sk, SOCK_DONE);
sock_hold(&smc->sk); /* sock_put in close_work */
if (!queue_work(smc_close_wq, &conn->close_work))
sock_put(&smc->sk);
}
}
/* called under tasklet context */
static void smc_cdc_msg_recv(struct smc_sock *smc, struct smc_cdc_msg *cdc)
{
sock_hold(&smc->sk);
bh_lock_sock(&smc->sk);
smc_cdc_msg_recv_action(smc, cdc);
bh_unlock_sock(&smc->sk);
sock_put(&smc->sk); /* no free sk in softirq-context */
}
/* Schedule a tasklet for this connection. Triggered from the ISM device IRQ
* handler to indicate update in the DMBE.
*
* Context:
* - tasklet context
*/
static void smcd_cdc_rx_tsklet(struct tasklet_struct *t)
{
struct smc_connection *conn = from_tasklet(conn, t, rx_tsklet);
struct smcd_cdc_msg *data_cdc;
struct smcd_cdc_msg cdc;
struct smc_sock *smc;
if (!conn || conn->killed)
return;
data_cdc = (struct smcd_cdc_msg *)conn->rmb_desc->cpu_addr;
smcd_curs_copy(&cdc.prod, &data_cdc->prod, conn);
smcd_curs_copy(&cdc.cons, &data_cdc->cons, conn);
smc = container_of(conn, struct smc_sock, conn);
smc_cdc_msg_recv(smc, (struct smc_cdc_msg *)&cdc);
}
/* Initialize receive tasklet. Called from ISM device IRQ handler to start
* receiver side.
*/
void smcd_cdc_rx_init(struct smc_connection *conn)
{
tasklet_setup(&conn->rx_tsklet, smcd_cdc_rx_tsklet);
}
/***************************** init, exit, misc ******************************/
static void smc_cdc_rx_handler(struct ib_wc *wc, void *buf)
{
struct smc_link *link = (struct smc_link *)wc->qp->qp_context;
struct smc_cdc_msg *cdc = buf;
struct smc_sock *smc = NULL;
struct smc_connection *conn;
struct smc_link_group *lgr;
if (wc->byte_len < offsetof(struct smc_cdc_msg, reserved))
return; /* short message */
if (cdc->len != SMC_WR_TX_SIZE)
return; /* invalid message */
/* lookup connection */
lgr = smc_get_lgr(link);
read_lock_bh(&lgr->conns_lock);
conn = smc_lgr_find_conn(ntohl(cdc->token), lgr);
if (!conn || conn->out_of_sync) {
read_unlock_bh(&lgr->conns_lock);
return;
}
smc = container_of(conn, struct smc_sock, conn);
sock_hold(&smc->sk);
read_unlock_bh(&lgr->conns_lock);
if (cdc->prod_flags.failover_validation) {
smc_cdc_msg_validate(smc, cdc, link);
goto out;
}
if (smc_cdc_before(ntohs(cdc->seqno),
conn->local_rx_ctrl.seqno))
/* received seqno is old */
goto out;
smc_cdc_msg_recv(smc, cdc);
out:
sock_put(&smc->sk);
}
static struct smc_wr_rx_handler smc_cdc_rx_handlers[] = {
{
.handler = smc_cdc_rx_handler,
.type = SMC_CDC_MSG_TYPE
},
{
.handler = NULL,
}
};
int __init smc_cdc_init(void)
{
struct smc_wr_rx_handler *handler;
int rc = 0;
for (handler = smc_cdc_rx_handlers; handler->handler; handler++) {
INIT_HLIST_NODE(&handler->list);
rc = smc_wr_rx_register_handler(handler);
if (rc)
break;
}
return rc;
}
+305
View File
@@ -0,0 +1,305 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Shared Memory Communications over RDMA (SMC-R) and RoCE
*
* Connection Data Control (CDC)
*
* Copyright IBM Corp. 2016
*
* Author(s): Ursula Braun <ubraun@linux.vnet.ibm.com>
*/
#ifndef SMC_CDC_H
#define SMC_CDC_H
#include <linux/kernel.h> /* max_t */
#include <linux/atomic.h>
#include <linux/in.h>
#include <linux/compiler.h>
#include "smc.h"
#include "smc_core.h"
#include "smc_wr.h"
#define SMC_CDC_MSG_TYPE 0xFE
/* in network byte order */
union smc_cdc_cursor { /* SMC cursor */
struct {
__be16 reserved;
__be16 wrap;
__be32 count;
};
#ifdef KERNEL_HAS_ATOMIC64
atomic64_t acurs; /* for atomic processing */
#else
u64 acurs; /* for atomic processing */
#endif
} __aligned(8);
/* in network byte order */
struct smc_cdc_msg {
struct smc_wr_rx_hdr common; /* .type = 0xFE */
u8 len; /* 44 */
__be16 seqno;
__be32 token;
union smc_cdc_cursor prod;
union smc_cdc_cursor cons; /* piggy backed "ack" */
struct smc_cdc_producer_flags prod_flags;
struct smc_cdc_conn_state_flags conn_state_flags;
u8 reserved[18];
};
/* SMC-D cursor format */
union smcd_cdc_cursor {
struct {
u16 wrap;
u32 count;
struct smc_cdc_producer_flags prod_flags;
struct smc_cdc_conn_state_flags conn_state_flags;
} __packed;
#ifdef KERNEL_HAS_ATOMIC64
atomic64_t acurs; /* for atomic processing */
#else
u64 acurs; /* for atomic processing */
#endif
} __aligned(8);
/* CDC message for SMC-D */
struct smcd_cdc_msg {
struct smc_wr_rx_hdr common; /* Type = 0xFE */
u8 res1[7];
union smcd_cdc_cursor prod;
union smcd_cdc_cursor cons;
u8 res3[8];
} __aligned(8);
static inline bool smc_cdc_rxed_any_close(struct smc_connection *conn)
{
return conn->local_rx_ctrl.conn_state_flags.peer_conn_abort ||
conn->local_rx_ctrl.conn_state_flags.peer_conn_closed;
}
static inline bool smc_cdc_rxed_any_close_or_senddone(
struct smc_connection *conn)
{
return smc_cdc_rxed_any_close(conn) ||
conn->local_rx_ctrl.conn_state_flags.peer_done_writing;
}
static inline void smc_curs_add(int size, union smc_host_cursor *curs,
int value)
{
curs->count += value;
if (curs->count >= size) {
curs->wrap++;
curs->count -= size;
}
}
/* Copy cursor src into tgt */
static inline void smc_curs_copy(union smc_host_cursor *tgt,
union smc_host_cursor *src,
struct smc_connection *conn)
{
#ifndef KERNEL_HAS_ATOMIC64
unsigned long flags;
spin_lock_irqsave(&conn->acurs_lock, flags);
tgt->acurs = src->acurs;
spin_unlock_irqrestore(&conn->acurs_lock, flags);
#else
atomic64_set(&tgt->acurs, atomic64_read(&src->acurs));
#endif
}
static inline void smc_curs_copy_net(union smc_cdc_cursor *tgt,
union smc_cdc_cursor *src,
struct smc_connection *conn)
{
#ifndef KERNEL_HAS_ATOMIC64
unsigned long flags;
spin_lock_irqsave(&conn->acurs_lock, flags);
tgt->acurs = src->acurs;
spin_unlock_irqrestore(&conn->acurs_lock, flags);
#else
atomic64_set(&tgt->acurs, atomic64_read(&src->acurs));
#endif
}
static inline void smcd_curs_copy(union smcd_cdc_cursor *tgt,
union smcd_cdc_cursor *src,
struct smc_connection *conn)
{
#ifndef KERNEL_HAS_ATOMIC64
unsigned long flags;
spin_lock_irqsave(&conn->acurs_lock, flags);
tgt->acurs = src->acurs;
spin_unlock_irqrestore(&conn->acurs_lock, flags);
#else
atomic64_set(&tgt->acurs, atomic64_read(&src->acurs));
#endif
}
/* calculate cursor difference between old and new, where old <= new and
* difference cannot exceed size
*/
static inline int smc_curs_diff(unsigned int size,
union smc_host_cursor *old,
union smc_host_cursor *new)
{
if (old->wrap != new->wrap)
return max_t(int, 0,
((size - old->count) + new->count));
return max_t(int, 0, (new->count - old->count));
}
/* calculate cursor difference between old and new - returns negative
* value in case old > new
*/
static inline int smc_curs_comp(unsigned int size,
union smc_host_cursor *old,
union smc_host_cursor *new)
{
if (old->wrap > new->wrap ||
(old->wrap == new->wrap && old->count > new->count))
return -smc_curs_diff(size, new, old);
return smc_curs_diff(size, old, new);
}
/* calculate cursor difference between old and new, where old <= new and
* difference may exceed size
*/
static inline int smc_curs_diff_large(unsigned int size,
union smc_host_cursor *old,
union smc_host_cursor *new)
{
if (old->wrap < new->wrap)
return min_t(int,
(size - old->count) + new->count +
(new->wrap - old->wrap - 1) * size,
size);
if (old->wrap > new->wrap) /* wrap has switched from 0xffff to 0x0000 */
return min_t(int,
(size - old->count) + new->count +
(new->wrap + 0xffff - old->wrap) * size,
size);
return max_t(int, 0, (new->count - old->count));
}
static inline void smc_host_cursor_to_cdc(union smc_cdc_cursor *peer,
union smc_host_cursor *local,
union smc_host_cursor *save,
struct smc_connection *conn)
{
smc_curs_copy(save, local, conn);
peer->count = htonl(save->count);
peer->wrap = htons(save->wrap);
/* peer->reserved = htons(0); must be ensured by caller */
}
static inline void smc_host_msg_to_cdc(struct smc_cdc_msg *peer,
struct smc_connection *conn,
union smc_host_cursor *save)
{
struct smc_host_cdc_msg *local = &conn->local_tx_ctrl;
peer->common.type = local->common.type;
peer->len = local->len;
peer->seqno = htons(local->seqno);
peer->token = htonl(local->token);
smc_host_cursor_to_cdc(&peer->prod, &local->prod, save, conn);
smc_host_cursor_to_cdc(&peer->cons, &local->cons, save, conn);
peer->prod_flags = local->prod_flags;
peer->conn_state_flags = local->conn_state_flags;
}
static inline void smc_cdc_cursor_to_host(union smc_host_cursor *local,
union smc_cdc_cursor *peer,
struct smc_connection *conn)
{
union smc_host_cursor temp, old;
union smc_cdc_cursor net;
smc_curs_copy(&old, local, conn);
smc_curs_copy_net(&net, peer, conn);
temp.count = ntohl(net.count);
temp.wrap = ntohs(net.wrap);
if ((old.wrap > temp.wrap) && temp.wrap)
return;
if ((old.wrap == temp.wrap) &&
(old.count > temp.count))
return;
smc_curs_copy(local, &temp, conn);
}
static inline void smcr_cdc_msg_to_host(struct smc_host_cdc_msg *local,
struct smc_cdc_msg *peer,
struct smc_connection *conn)
{
local->common.type = peer->common.type;
local->len = peer->len;
local->seqno = ntohs(peer->seqno);
local->token = ntohl(peer->token);
smc_cdc_cursor_to_host(&local->prod, &peer->prod, conn);
smc_cdc_cursor_to_host(&local->cons, &peer->cons, conn);
local->prod_flags = peer->prod_flags;
local->conn_state_flags = peer->conn_state_flags;
}
static inline void smcd_cdc_msg_to_host(struct smc_host_cdc_msg *local,
struct smcd_cdc_msg *peer,
struct smc_connection *conn)
{
union smc_host_cursor temp;
temp.wrap = peer->prod.wrap;
temp.count = peer->prod.count;
smc_curs_copy(&local->prod, &temp, conn);
temp.wrap = peer->cons.wrap;
temp.count = peer->cons.count;
smc_curs_copy(&local->cons, &temp, conn);
local->prod_flags = peer->cons.prod_flags;
local->conn_state_flags = peer->cons.conn_state_flags;
}
static inline void smc_cdc_msg_to_host(struct smc_host_cdc_msg *local,
struct smc_cdc_msg *peer,
struct smc_connection *conn)
{
if (conn->lgr->is_smcd)
smcd_cdc_msg_to_host(local, (struct smcd_cdc_msg *)peer, conn);
else
smcr_cdc_msg_to_host(local, peer, conn);
}
struct smc_cdc_tx_pend {
struct smc_connection *conn; /* socket connection */
union smc_host_cursor cursor; /* tx sndbuf cursor sent */
union smc_host_cursor p_cursor; /* rx RMBE cursor produced */
u16 ctrl_seq; /* conn. tx sequence # */
};
int smc_cdc_get_free_slot(struct smc_connection *conn,
struct smc_link *link,
struct smc_wr_buf **wr_buf,
struct smc_rdma_wr **wr_rdma_buf,
struct smc_cdc_tx_pend **pend);
void smc_cdc_wait_pend_tx_wr(struct smc_connection *conn);
int smc_cdc_msg_send(struct smc_connection *conn, struct smc_wr_buf *wr_buf,
struct smc_cdc_tx_pend *pend);
int smc_cdc_get_slot_and_msg_send(struct smc_connection *conn);
int smcd_cdc_msg_send(struct smc_connection *conn);
int smcr_cdc_msg_send_validation(struct smc_connection *conn,
struct smc_cdc_tx_pend *pend,
struct smc_wr_buf *wr_buf);
int smc_cdc_init(void) __init;
void smcd_cdc_rx_init(struct smc_connection *conn);
#endif /* SMC_CDC_H */
File diff suppressed because it is too large Load Diff
+477
View File
@@ -0,0 +1,477 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Shared Memory Communications over RDMA (SMC-R) and RoCE
*
* CLC (connection layer control) handshake over initial TCP socket to
* prepare for RDMA traffic
*
* Copyright IBM Corp. 2016
*
* Author(s): Ursula Braun <ubraun@linux.vnet.ibm.com>
*/
#ifndef _SMC_CLC_H
#define _SMC_CLC_H
#include <rdma/ib_verbs.h>
#include <linux/smc.h>
#include "smc.h"
#include "smc_netlink.h"
#define SMC_CLC_PROPOSAL 0x01
#define SMC_CLC_ACCEPT 0x02
#define SMC_CLC_CONFIRM 0x03
#define SMC_CLC_DECLINE 0x04
#define SMC_TYPE_R 0 /* SMC-R only */
#define SMC_TYPE_D 1 /* SMC-D only */
#define SMC_TYPE_N 2 /* neither SMC-R nor SMC-D */
#define SMC_TYPE_B 3 /* SMC-R and SMC-D */
#define CLC_WAIT_TIME (6 * HZ) /* max. wait time on clcsock */
#define CLC_WAIT_TIME_SHORT HZ /* short wait time on clcsock */
#define SMC_CLC_DECL_MEM 0x01010000 /* insufficient memory resources */
#define SMC_CLC_DECL_TIMEOUT_CL 0x02010000 /* timeout w4 QP confirm link */
#define SMC_CLC_DECL_TIMEOUT_AL 0x02020000 /* timeout w4 QP add link */
#define SMC_CLC_DECL_CNFERR 0x03000000 /* configuration error */
#define SMC_CLC_DECL_PEERNOSMC 0x03010000 /* peer did not indicate SMC */
#define SMC_CLC_DECL_IPSEC 0x03020000 /* IPsec usage */
#define SMC_CLC_DECL_NOSMCDEV 0x03030000 /* no SMC device found (R or D) */
#define SMC_CLC_DECL_NOSMCDDEV 0x03030001 /* no SMC-D device found */
#define SMC_CLC_DECL_NOSMCRDEV 0x03030002 /* no SMC-R device found */
#define SMC_CLC_DECL_NOISM2SUPP 0x03030003 /* hardware has no ISMv2 support */
#define SMC_CLC_DECL_NOV2EXT 0x03030004 /* peer sent no clc v2 extension */
#define SMC_CLC_DECL_NOV2DEXT 0x03030005 /* peer sent no clc SMC-Dv2 ext. */
#define SMC_CLC_DECL_NOSEID 0x03030006 /* peer sent no SEID */
#define SMC_CLC_DECL_NOSMCD2DEV 0x03030007 /* no SMC-Dv2 device found */
#define SMC_CLC_DECL_NOUEID 0x03030008 /* peer sent no UEID */
#define SMC_CLC_DECL_RELEASEERR 0x03030009 /* release version negotiate failed */
#define SMC_CLC_DECL_MAXCONNERR 0x0303000a /* max connections negotiate failed */
#define SMC_CLC_DECL_MAXLINKERR 0x0303000b /* max links negotiate failed */
#define SMC_CLC_DECL_MODEUNSUPP 0x03040000 /* smc modes do not match (R or D)*/
#define SMC_CLC_DECL_RMBE_EC 0x03050000 /* peer has eyecatcher in RMBE */
#define SMC_CLC_DECL_OPTUNSUPP 0x03060000 /* fastopen sockopt not supported */
#define SMC_CLC_DECL_DIFFPREFIX 0x03070000 /* IP prefix / subnet mismatch */
#define SMC_CLC_DECL_GETVLANERR 0x03080000 /* err to get vlan id of ip device*/
#define SMC_CLC_DECL_ISMVLANERR 0x03090000 /* err to reg vlan id on ism dev */
#define SMC_CLC_DECL_NOACTLINK 0x030a0000 /* no active smc-r link in lgr */
#define SMC_CLC_DECL_NOSRVLINK 0x030b0000 /* SMC-R link from srv not found */
#define SMC_CLC_DECL_VERSMISMAT 0x030c0000 /* SMC version mismatch */
#define SMC_CLC_DECL_MAX_DMB 0x030d0000 /* SMC-D DMB limit exceeded */
#define SMC_CLC_DECL_NOROUTE 0x030e0000 /* SMC-Rv2 conn. no route to peer */
#define SMC_CLC_DECL_NOINDIRECT 0x030f0000 /* SMC-Rv2 conn. indirect mismatch*/
#define SMC_CLC_DECL_SYNCERR 0x04000000 /* synchronization error */
#define SMC_CLC_DECL_PEERDECL 0x05000000 /* peer declined during handshake */
#define SMC_CLC_DECL_INTERR 0x09990000 /* internal error */
#define SMC_CLC_DECL_ERR_RTOK 0x09990001 /* rtoken handling failed */
#define SMC_CLC_DECL_ERR_RDYLNK 0x09990002 /* ib ready link failed */
#define SMC_CLC_DECL_ERR_REGBUF 0x09990003 /* reg rdma bufs failed */
#define SMC_FIRST_CONTACT_MASK 0b10 /* first contact bit within typev2 */
struct smc_clc_msg_hdr { /* header1 of clc messages */
u8 eyecatcher[4]; /* eye catcher */
u8 type; /* proposal / accept / confirm / decline */
__be16 length;
#if defined(__BIG_ENDIAN_BITFIELD)
u8 version : 4,
typev2 : 2,
typev1 : 2;
#elif defined(__LITTLE_ENDIAN_BITFIELD)
u8 typev1 : 2,
typev2 : 2,
version : 4;
#endif
} __packed; /* format defined in RFC7609 */
struct smc_clc_msg_trail { /* trailer of clc messages */
u8 eyecatcher[4];
};
struct smc_clc_msg_local { /* header2 of clc messages */
u8 id_for_peer[SMC_SYSTEMID_LEN]; /* unique system id */
u8 gid[16]; /* gid of ib_device port */
u8 mac[6]; /* mac of ib_device port */
};
/* Struct would be 4 byte aligned, but it is used in an array that is sent
* to peers and must conform to RFC7609, hence we need to use packed here.
*/
struct smc_clc_ipv6_prefix {
struct in6_addr prefix;
u8 prefix_len;
} __packed; /* format defined in RFC7609 */
#if defined(__BIG_ENDIAN_BITFIELD)
struct smc_clc_v2_flag {
u8 release : 4,
rsvd : 3,
seid : 1;
};
#elif defined(__LITTLE_ENDIAN_BITFIELD)
struct smc_clc_v2_flag {
u8 seid : 1,
rsvd : 3,
release : 4;
};
#endif
struct smc_clnt_opts_area_hdr {
u8 eid_cnt; /* number of user defined EIDs */
u8 ism_gid_cnt; /* number of ISMv2 GIDs */
u8 reserved1;
struct smc_clc_v2_flag flag;
u8 reserved2[2];
__be16 smcd_v2_ext_offset; /* SMC-Dv2 Extension Offset */
};
struct smc_clc_smcd_gid_chid {
__be64 gid; /* ISM GID */
__be16 chid; /* ISMv2 CHID */
} __packed; /* format defined in
* IBM Shared Memory Communications Version 2
* (https://www.ibm.com/support/pages/node/6326337)
*/
struct smc_clc_v2_extension {
/* New members must be added within the struct_group() macro below. */
struct_group_tagged(smc_clc_v2_extension_fixed, fixed,
struct smc_clnt_opts_area_hdr hdr;
u8 roce[16]; /* RoCEv2 GID */
u8 max_conns;
u8 max_links;
__be16 feature_mask;
u8 reserved[12];
);
u8 user_eids[][SMC_MAX_EID_LEN];
};
static_assert(offsetof(struct smc_clc_v2_extension, user_eids) == sizeof(struct smc_clc_v2_extension_fixed),
"struct member likely outside of struct_group_tagged()");
struct smc_clc_msg_proposal_prefix { /* prefix part of clc proposal message*/
__be32 outgoing_subnet; /* subnet mask */
u8 prefix_len; /* number of significant bits in mask */
u8 reserved[2];
u8 ipv6_prefixes_cnt; /* number of IPv6 prefixes in prefix array */
} __aligned(4);
struct smc_clc_msg_smcd { /* SMC-D GID information */
struct smc_clc_smcd_gid_chid ism; /* ISM native GID+CHID of requester */
__be16 v2_ext_offset; /* SMC Version 2 Extension Offset */
u8 vendor_oui[3]; /* vendor organizationally unique identifier */
u8 vendor_exp_options[5];
u8 reserved[20];
};
struct smc_clc_smcd_v2_extension {
/* New members must be added within the struct_group() macro below. */
struct_group_tagged(smc_clc_smcd_v2_extension_fixed, fixed,
u8 system_eid[SMC_MAX_EID_LEN];
u8 reserved[16];
);
struct smc_clc_smcd_gid_chid gidchid[];
};
static_assert(offsetof(struct smc_clc_smcd_v2_extension, gidchid) == sizeof(struct smc_clc_smcd_v2_extension_fixed),
"struct member likely outside of struct_group_tagged()");
struct smc_clc_msg_proposal { /* clc proposal message sent by Linux */
struct smc_clc_msg_hdr hdr;
struct smc_clc_msg_local lcl;
__be16 iparea_offset; /* offset to IP address information area */
} __aligned(4);
#define SMC_CLC_MAX_V6_PREFIX 8
#define SMC_CLC_MAX_UEID 8
#define SMCD_CLC_MAX_V2_GID_ENTRIES 8 /* max # of CHID-GID entries in CLC
* proposal SMC-Dv2 extension.
* each ISM device takes one entry and
* each Emulated-ISM takes two entries
*/
struct smc_clc_msg_proposal_area {
struct smc_clc_msg_proposal pclc_base;
struct smc_clc_msg_smcd pclc_smcd;
struct smc_clc_msg_proposal_prefix pclc_prfx;
struct smc_clc_ipv6_prefix pclc_prfx_ipv6[SMC_CLC_MAX_V6_PREFIX];
struct smc_clc_v2_extension_fixed pclc_v2_ext;
u8 user_eids[SMC_CLC_MAX_UEID][SMC_MAX_EID_LEN];
struct smc_clc_smcd_v2_extension_fixed pclc_smcd_v2_ext;
struct smc_clc_smcd_gid_chid
pclc_gidchids[SMCD_CLC_MAX_V2_GID_ENTRIES];
struct smc_clc_msg_trail pclc_trl;
};
struct smcr_clc_msg_accept_confirm { /* SMCR accept/confirm */
struct smc_clc_msg_local lcl;
u8 qpn[3]; /* QP number */
__be32 rmb_rkey; /* RMB rkey */
u8 rmbe_idx; /* Index of RMBE in RMB */
__be32 rmbe_alert_token; /* unique connection id */
#if defined(__BIG_ENDIAN_BITFIELD)
u8 rmbe_size : 4, /* buf size (compressed) */
qp_mtu : 4; /* QP mtu */
#elif defined(__LITTLE_ENDIAN_BITFIELD)
u8 qp_mtu : 4,
rmbe_size : 4;
#endif
u8 reserved;
__be64 rmb_dma_addr; /* RMB virtual address */
u8 reserved2;
u8 psn[3]; /* packet sequence number */
} __packed;
struct smcd_clc_msg_accept_confirm_common { /* SMCD accept/confirm */
__be64 gid; /* Sender GID */
__be64 token; /* DMB token */
u8 dmbe_idx; /* DMBE index */
#if defined(__BIG_ENDIAN_BITFIELD)
u8 dmbe_size : 4, /* buf size (compressed) */
reserved3 : 4;
#elif defined(__LITTLE_ENDIAN_BITFIELD)
u8 reserved3 : 4,
dmbe_size : 4;
#endif
u16 reserved4;
__be32 linkid; /* Link identifier */
} __packed;
#define SMC_CLC_OS_ZOS 1
#define SMC_CLC_OS_LINUX 2
#define SMC_CLC_OS_AIX 3
struct smc_clc_first_contact_ext {
#if defined(__BIG_ENDIAN_BITFIELD)
u8 v2_direct : 1,
reserved : 7;
u8 os_type : 4,
release : 4;
#elif defined(__LITTLE_ENDIAN_BITFIELD)
u8 reserved : 7,
v2_direct : 1;
u8 release : 4,
os_type : 4;
#endif
u8 reserved2[2];
u8 hostname[SMC_MAX_HOSTNAME_LEN];
};
struct smc_clc_first_contact_ext_v2x {
struct smc_clc_first_contact_ext fce_v2_base;
union {
struct {
u8 max_conns; /* for SMC-R only */
u8 max_links; /* for SMC-R only */
};
u8 reserved3[2]; /* for SMC-D only */
};
__be16 feature_mask;
__be32 vendor_exp_options;
u8 reserved4[8];
} __packed; /* format defined in
* IBM Shared Memory Communications Version 2 (Third Edition)
* (https://www.ibm.com/support/pages/node/7009315)
*/
struct smc_clc_fce_gid_ext {
u8 gid_cnt;
u8 reserved2[3];
u8 gid[][SMC_GID_SIZE];
};
struct smc_clc_msg_accept_confirm { /* clc accept / confirm message */
struct smc_clc_msg_hdr hdr;
union {
struct { /* SMC-R */
struct smcr_clc_msg_accept_confirm r0;
struct { /* v2 only */
u8 eid[SMC_MAX_EID_LEN];
u8 reserved6[8];
} __packed r1;
};
struct { /* SMC-D */
struct smcd_clc_msg_accept_confirm_common d0;
struct { /* v2 only, but 12 bytes reserved in v1 */
__be16 chid;
u8 eid[SMC_MAX_EID_LEN];
__be64 gid_ext;
} __packed d1;
};
};
};
struct smc_clc_msg_decline { /* clc decline message */
struct smc_clc_msg_hdr hdr;
u8 id_for_peer[SMC_SYSTEMID_LEN]; /* sender peer_id */
__be32 peer_diagnosis; /* diagnosis information */
#if defined(__BIG_ENDIAN_BITFIELD)
u8 os_type : 4,
reserved : 4;
#elif defined(__LITTLE_ENDIAN_BITFIELD)
u8 reserved : 4,
os_type : 4;
#endif
u8 reserved2[3];
struct smc_clc_msg_trail trl; /* eye catcher "SMCD" or "SMCR" EBCDIC */
} __aligned(4);
#define SMC_DECL_DIAG_COUNT_V2 4 /* no. of additional peer diagnosis codes */
struct smc_clc_msg_decline_v2 { /* clc decline message */
struct smc_clc_msg_hdr hdr;
u8 id_for_peer[SMC_SYSTEMID_LEN]; /* sender peer_id */
__be32 peer_diagnosis; /* diagnosis information */
#if defined(__BIG_ENDIAN_BITFIELD)
u8 os_type : 4,
reserved : 4;
#elif defined(__LITTLE_ENDIAN_BITFIELD)
u8 reserved : 4,
os_type : 4;
#endif
u8 reserved2[3];
__be32 peer_diagnosis_v2[SMC_DECL_DIAG_COUNT_V2];
struct smc_clc_msg_trail trl; /* eye catcher "SMCD" or "SMCR" EBCDIC */
} __aligned(4);
/* determine start of the prefix area within the proposal message */
static inline struct smc_clc_msg_proposal_prefix *
smc_clc_proposal_get_prefix(struct smc_clc_msg_proposal *pclc)
{
u16 offset = ntohs(pclc->iparea_offset);
if (offset > sizeof(struct smc_clc_msg_smcd))
return NULL;
return (struct smc_clc_msg_proposal_prefix *)
((u8 *)pclc + sizeof(*pclc) + offset);
}
static inline bool smcr_indicated(int smc_type)
{
return smc_type == SMC_TYPE_R || smc_type == SMC_TYPE_B;
}
static inline bool smcd_indicated(int smc_type)
{
return smc_type == SMC_TYPE_D || smc_type == SMC_TYPE_B;
}
static inline u8 smc_indicated_type(int is_smcd, int is_smcr)
{
if (is_smcd && is_smcr)
return SMC_TYPE_B;
if (is_smcd)
return SMC_TYPE_D;
if (is_smcr)
return SMC_TYPE_R;
return SMC_TYPE_N;
}
/* get SMC-D info from proposal message */
static inline struct smc_clc_msg_smcd *
smc_get_clc_msg_smcd(struct smc_clc_msg_proposal *prop)
{
if (smcd_indicated(prop->hdr.typev1) &&
ntohs(prop->iparea_offset) != sizeof(struct smc_clc_msg_smcd))
return NULL;
return (struct smc_clc_msg_smcd *)(prop + 1);
}
static inline struct smc_clc_v2_extension *
smc_get_clc_v2_ext(struct smc_clc_msg_proposal *prop)
{
struct smc_clc_msg_smcd *prop_smcd = smc_get_clc_msg_smcd(prop);
u16 max_offset;
max_offset = offsetof(struct smc_clc_msg_proposal_area, pclc_v2_ext) -
offsetof(struct smc_clc_msg_proposal_area, pclc_smcd) -
offsetofend(struct smc_clc_msg_smcd, v2_ext_offset);
if (!prop_smcd || !ntohs(prop_smcd->v2_ext_offset) ||
ntohs(prop_smcd->v2_ext_offset) > max_offset)
return NULL;
return (struct smc_clc_v2_extension *)
((u8 *)prop_smcd +
offsetof(struct smc_clc_msg_smcd, v2_ext_offset) +
sizeof(prop_smcd->v2_ext_offset) +
ntohs(prop_smcd->v2_ext_offset));
}
static inline struct smc_clc_smcd_v2_extension *
smc_get_clc_smcd_v2_ext(struct smc_clc_v2_extension *prop_v2ext)
{
u16 max_offset = offsetof(struct smc_clc_msg_proposal_area, pclc_smcd_v2_ext) -
offsetof(struct smc_clc_msg_proposal_area, pclc_v2_ext) -
offsetof(struct smc_clc_v2_extension, hdr) -
offsetofend(struct smc_clnt_opts_area_hdr, smcd_v2_ext_offset);
if (!prop_v2ext)
return NULL;
if (!ntohs(prop_v2ext->hdr.smcd_v2_ext_offset) ||
ntohs(prop_v2ext->hdr.smcd_v2_ext_offset) > max_offset)
return NULL;
return (struct smc_clc_smcd_v2_extension *)
((u8 *)prop_v2ext +
offsetof(struct smc_clc_v2_extension, hdr) +
offsetof(struct smc_clnt_opts_area_hdr, smcd_v2_ext_offset) +
sizeof(prop_v2ext->hdr.smcd_v2_ext_offset) +
ntohs(prop_v2ext->hdr.smcd_v2_ext_offset));
}
static inline struct smc_clc_first_contact_ext *
smc_get_clc_first_contact_ext(struct smc_clc_msg_accept_confirm *clc,
bool is_smcd)
{
int clc_v2_len;
if (clc->hdr.version == SMC_V1 ||
!(clc->hdr.typev2 & SMC_FIRST_CONTACT_MASK))
return NULL;
if (is_smcd)
clc_v2_len =
offsetofend(struct smc_clc_msg_accept_confirm, d1);
else
clc_v2_len =
offsetofend(struct smc_clc_msg_accept_confirm, r1);
return (struct smc_clc_first_contact_ext *)(((u8 *)clc) + clc_v2_len);
}
struct smcd_dev;
struct smc_init_info;
int smc_clc_prfx_match(struct socket *clcsock,
struct smc_clc_msg_proposal_prefix *prop);
int smc_clc_wait_msg(struct smc_sock *smc, void *buf, int buflen,
u8 expected_type, unsigned long timeout);
int smc_clc_send_decline(struct smc_sock *smc, u32 peer_diag_info, u8 version);
int smc_clc_send_proposal(struct smc_sock *smc, struct smc_init_info *ini);
int smc_clc_send_confirm(struct smc_sock *smc, bool clnt_first_contact,
u8 version, u8 *eid, struct smc_init_info *ini);
int smc_clc_send_accept(struct smc_sock *smc, bool srv_first_contact,
u8 version, u8 *negotiated_eid, struct smc_init_info *ini);
int smc_clc_srv_v2x_features_validate(struct smc_sock *smc,
struct smc_clc_msg_proposal *pclc,
struct smc_init_info *ini);
int smc_clc_clnt_v2x_features_validate(struct smc_clc_first_contact_ext *fce,
struct smc_init_info *ini);
int smc_clc_v2x_features_confirm_check(struct smc_clc_msg_accept_confirm *cclc,
struct smc_init_info *ini);
void smc_clc_init(void) __init;
void smc_clc_exit(void);
void smc_clc_get_hostname(u8 **host);
bool smc_clc_match_eid(u8 *negotiated_eid,
struct smc_clc_v2_extension *smc_v2_ext,
u8 *peer_eid, u8 *local_eid);
int smc_clc_ueid_count(void);
int smc_nl_dump_ueid(struct sk_buff *skb, struct netlink_callback *cb);
int smc_nl_add_ueid(struct sk_buff *skb, struct genl_info *info);
int smc_nl_remove_ueid(struct sk_buff *skb, struct genl_info *info);
int smc_nl_flush_ueid(struct sk_buff *skb, struct genl_info *info);
int smc_nl_dump_seid(struct sk_buff *skb, struct netlink_callback *cb);
int smc_nl_enable_seid(struct sk_buff *skb, struct genl_info *info);
int smc_nl_disable_seid(struct sk_buff *skb, struct genl_info *info);
#endif
+506
View File
@@ -0,0 +1,506 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Shared Memory Communications over RDMA (SMC-R) and RoCE
*
* Socket Closing - normal and abnormal
*
* Copyright IBM Corp. 2016
*
* Author(s): Ursula Braun <ubraun@linux.vnet.ibm.com>
*/
#include <linux/workqueue.h>
#include <linux/sched/signal.h>
#include <net/sock.h>
#include <net/tcp.h>
#include "smc.h"
#include "smc_tx.h"
#include "smc_cdc.h"
#include "smc_close.h"
/* release the clcsock that is assigned to the smc_sock */
void smc_clcsock_release(struct smc_sock *smc)
{
struct socket *tcp;
if (smc->listen_smc && current_work() != &smc->smc_listen_work)
cancel_work_sync(&smc->smc_listen_work);
mutex_lock(&smc->clcsock_release_lock);
if (smc->clcsock) {
tcp = smc->clcsock;
smc->clcsock = NULL;
sock_release(tcp);
}
mutex_unlock(&smc->clcsock_release_lock);
}
static void smc_close_cleanup_listen(struct sock *parent)
{
struct sock *sk;
/* Close non-accepted connections */
while ((sk = smc_accept_dequeue(parent, NULL)))
smc_close_non_accepted(sk);
}
/* wait for sndbuf data being transmitted */
static void smc_close_stream_wait(struct smc_sock *smc, long timeout)
{
DEFINE_WAIT_FUNC(wait, woken_wake_function);
struct sock *sk = &smc->sk;
if (!timeout)
return;
if (!smc_tx_prepared_sends(&smc->conn))
return;
/* Send out corked data remaining in sndbuf */
smc_tx_pending(&smc->conn);
smc->wait_close_tx_prepared = 1;
add_wait_queue(sk_sleep(sk), &wait);
while (!signal_pending(current) && timeout) {
int rc;
rc = sk_wait_event(sk, &timeout,
!smc_tx_prepared_sends(&smc->conn) ||
READ_ONCE(sk->sk_err) == ECONNABORTED ||
READ_ONCE(sk->sk_err) == ECONNRESET ||
smc->conn.killed,
&wait);
if (rc)
break;
}
remove_wait_queue(sk_sleep(sk), &wait);
smc->wait_close_tx_prepared = 0;
}
void smc_close_wake_tx_prepared(struct smc_sock *smc)
{
if (smc->wait_close_tx_prepared)
/* wake up socket closing */
smc->sk.sk_state_change(&smc->sk);
}
static int smc_close_wr(struct smc_connection *conn)
{
conn->local_tx_ctrl.conn_state_flags.peer_done_writing = 1;
return smc_cdc_get_slot_and_msg_send(conn);
}
static int smc_close_final(struct smc_connection *conn)
{
if (atomic_read(&conn->bytes_to_rcv))
conn->local_tx_ctrl.conn_state_flags.peer_conn_abort = 1;
else
conn->local_tx_ctrl.conn_state_flags.peer_conn_closed = 1;
if (conn->killed)
return -EPIPE;
return smc_cdc_get_slot_and_msg_send(conn);
}
int smc_close_abort(struct smc_connection *conn)
{
conn->local_tx_ctrl.conn_state_flags.peer_conn_abort = 1;
return smc_cdc_get_slot_and_msg_send(conn);
}
static void smc_close_cancel_work(struct smc_sock *smc)
{
struct sock *sk = &smc->sk;
release_sock(sk);
if (cancel_work_sync(&smc->conn.close_work))
sock_put(sk);
cancel_delayed_work_sync(&smc->conn.tx_work);
lock_sock(sk);
}
/* terminate smc socket abnormally - active abort
* link group is terminated, i.e. RDMA communication no longer possible
*/
void smc_close_active_abort(struct smc_sock *smc)
{
struct sock *sk = &smc->sk;
bool release_clcsock = false;
if (sk->sk_state != SMC_INIT && smc->clcsock && smc->clcsock->sk) {
sk->sk_err = ECONNABORTED;
if (smc->clcsock && smc->clcsock->sk)
tcp_abort(smc->clcsock->sk, ECONNABORTED);
}
switch (sk->sk_state) {
case SMC_ACTIVE:
case SMC_APPCLOSEWAIT1:
case SMC_APPCLOSEWAIT2:
sk->sk_state = SMC_PEERABORTWAIT;
smc_close_cancel_work(smc);
if (sk->sk_state != SMC_PEERABORTWAIT)
break;
sk->sk_state = SMC_CLOSED;
sock_put(sk); /* (postponed) passive closing */
break;
case SMC_PEERCLOSEWAIT1:
case SMC_PEERCLOSEWAIT2:
case SMC_PEERFINCLOSEWAIT:
sk->sk_state = SMC_PEERABORTWAIT;
smc_close_cancel_work(smc);
if (sk->sk_state != SMC_PEERABORTWAIT)
break;
sk->sk_state = SMC_CLOSED;
smc_conn_free(&smc->conn);
release_clcsock = true;
sock_put(sk); /* passive closing */
break;
case SMC_PROCESSABORT:
case SMC_APPFINCLOSEWAIT:
sk->sk_state = SMC_PEERABORTWAIT;
smc_close_cancel_work(smc);
if (sk->sk_state != SMC_PEERABORTWAIT)
break;
sk->sk_state = SMC_CLOSED;
smc_conn_free(&smc->conn);
release_clcsock = true;
break;
case SMC_INIT:
case SMC_PEERABORTWAIT:
case SMC_CLOSED:
break;
}
smc_sock_set_flag(sk, SOCK_DEAD);
sk->sk_state_change(sk);
if (release_clcsock) {
release_sock(sk);
smc_clcsock_release(smc);
lock_sock(sk);
}
}
static inline bool smc_close_sent_any_close(struct smc_connection *conn)
{
return conn->local_tx_ctrl.conn_state_flags.peer_conn_abort ||
conn->local_tx_ctrl.conn_state_flags.peer_conn_closed;
}
int smc_close_active(struct smc_sock *smc)
{
struct smc_cdc_conn_state_flags *txflags =
&smc->conn.local_tx_ctrl.conn_state_flags;
struct smc_connection *conn = &smc->conn;
struct sock *sk = &smc->sk;
int old_state;
long timeout;
int rc = 0;
int rc1 = 0;
timeout = current->flags & PF_EXITING ?
0 : sock_flag(sk, SOCK_LINGER) ?
sk->sk_lingertime : SMC_MAX_STREAM_WAIT_TIMEOUT;
old_state = sk->sk_state;
again:
switch (sk->sk_state) {
case SMC_INIT:
sk->sk_state = SMC_CLOSED;
break;
case SMC_LISTEN:
sk->sk_state = SMC_CLOSED;
sk->sk_state_change(sk); /* wake up accept */
if (smc->clcsock && smc->clcsock->sk) {
write_lock_bh(&smc->clcsock->sk->sk_callback_lock);
smc_clcsock_restore_cb(&smc->clcsock->sk->sk_data_ready,
&smc->clcsk_data_ready);
rcu_assign_sk_user_data(smc->clcsock->sk, NULL);
write_unlock_bh(&smc->clcsock->sk->sk_callback_lock);
rc = kernel_sock_shutdown(smc->clcsock, SHUT_RDWR);
}
smc_close_cleanup_listen(sk);
release_sock(sk);
flush_work(&smc->tcp_listen_work);
lock_sock(sk);
break;
case SMC_ACTIVE:
smc_close_stream_wait(smc, timeout);
release_sock(sk);
cancel_delayed_work_sync(&conn->tx_work);
lock_sock(sk);
if (sk->sk_state == SMC_ACTIVE) {
/* send close request */
rc = smc_close_final(conn);
sk->sk_state = SMC_PEERCLOSEWAIT1;
/* actively shutdown clcsock before peer close it,
* prevent peer from entering TIME_WAIT state.
*/
if (smc->clcsock && smc->clcsock->sk) {
rc1 = kernel_sock_shutdown(smc->clcsock,
SHUT_RDWR);
rc = rc ? rc : rc1;
}
} else {
/* peer event has changed the state */
goto again;
}
break;
case SMC_APPFINCLOSEWAIT:
/* socket already shutdown wr or both (active close) */
if (txflags->peer_done_writing &&
!smc_close_sent_any_close(conn)) {
/* just shutdown wr done, send close request */
rc = smc_close_final(conn);
}
sk->sk_state = SMC_CLOSED;
break;
case SMC_APPCLOSEWAIT1:
case SMC_APPCLOSEWAIT2:
if (!smc_cdc_rxed_any_close(conn))
smc_close_stream_wait(smc, timeout);
release_sock(sk);
cancel_delayed_work_sync(&conn->tx_work);
lock_sock(sk);
if (sk->sk_state != SMC_APPCLOSEWAIT1 &&
sk->sk_state != SMC_APPCLOSEWAIT2)
goto again;
/* confirm close from peer */
rc = smc_close_final(conn);
if (smc_cdc_rxed_any_close(conn)) {
/* peer has closed the socket already */
sk->sk_state = SMC_CLOSED;
sock_put(sk); /* postponed passive closing */
} else {
/* peer has just issued a shutdown write */
sk->sk_state = SMC_PEERFINCLOSEWAIT;
}
break;
case SMC_PEERCLOSEWAIT1:
case SMC_PEERCLOSEWAIT2:
if (txflags->peer_done_writing &&
!smc_close_sent_any_close(conn)) {
/* just shutdown wr done, send close request */
rc = smc_close_final(conn);
}
/* peer sending PeerConnectionClosed will cause transition */
break;
case SMC_PEERFINCLOSEWAIT:
/* peer sending PeerConnectionClosed will cause transition */
break;
case SMC_PROCESSABORT:
rc = smc_close_abort(conn);
sk->sk_state = SMC_CLOSED;
break;
case SMC_PEERABORTWAIT:
sk->sk_state = SMC_CLOSED;
break;
case SMC_CLOSED:
/* nothing to do, add tracing in future patch */
break;
}
if (old_state != sk->sk_state)
sk->sk_state_change(sk);
return rc;
}
static void smc_close_passive_abort_received(struct smc_sock *smc)
{
struct smc_cdc_conn_state_flags *txflags =
&smc->conn.local_tx_ctrl.conn_state_flags;
struct sock *sk = &smc->sk;
switch (sk->sk_state) {
case SMC_INIT:
case SMC_ACTIVE:
case SMC_APPCLOSEWAIT1:
sk->sk_state = SMC_PROCESSABORT;
sock_put(sk); /* passive closing */
break;
case SMC_APPFINCLOSEWAIT:
sk->sk_state = SMC_PROCESSABORT;
break;
case SMC_PEERCLOSEWAIT1:
case SMC_PEERCLOSEWAIT2:
if (txflags->peer_done_writing &&
!smc_close_sent_any_close(&smc->conn))
/* just shutdown, but not yet closed locally */
sk->sk_state = SMC_PROCESSABORT;
else
sk->sk_state = SMC_CLOSED;
sock_put(sk); /* passive closing */
break;
case SMC_APPCLOSEWAIT2:
case SMC_PEERFINCLOSEWAIT:
sk->sk_state = SMC_CLOSED;
sock_put(sk); /* passive closing */
break;
case SMC_PEERABORTWAIT:
sk->sk_state = SMC_CLOSED;
break;
case SMC_PROCESSABORT:
/* nothing to do, add tracing in future patch */
break;
}
}
/* Either some kind of closing has been received: peer_conn_closed,
* peer_conn_abort, or peer_done_writing
* or the link group of the connection terminates abnormally.
*/
static void smc_close_passive_work(struct work_struct *work)
{
struct smc_connection *conn = container_of(work,
struct smc_connection,
close_work);
struct smc_sock *smc = container_of(conn, struct smc_sock, conn);
struct smc_cdc_conn_state_flags *rxflags;
bool release_clcsock = false;
struct sock *sk = &smc->sk;
int old_state;
lock_sock(sk);
old_state = sk->sk_state;
rxflags = &conn->local_rx_ctrl.conn_state_flags;
if (rxflags->peer_conn_abort) {
/* peer has not received all data */
smc_close_passive_abort_received(smc);
release_sock(sk);
cancel_delayed_work_sync(&conn->tx_work);
lock_sock(sk);
goto wakeup;
}
switch (sk->sk_state) {
case SMC_INIT:
sk->sk_state = SMC_APPCLOSEWAIT1;
break;
case SMC_ACTIVE:
sk->sk_state = SMC_APPCLOSEWAIT1;
/* postpone sock_put() for passive closing to cover
* received SEND_SHUTDOWN as well
*/
break;
case SMC_PEERCLOSEWAIT1:
if (rxflags->peer_done_writing)
sk->sk_state = SMC_PEERCLOSEWAIT2;
fallthrough;
/* to check for closing */
case SMC_PEERCLOSEWAIT2:
if (!smc_cdc_rxed_any_close(conn))
break;
if (sock_flag(sk, SOCK_DEAD) &&
smc_close_sent_any_close(conn)) {
/* smc_release has already been called locally */
sk->sk_state = SMC_CLOSED;
} else {
/* just shutdown, but not yet closed locally */
sk->sk_state = SMC_APPFINCLOSEWAIT;
}
sock_put(sk); /* passive closing */
break;
case SMC_PEERFINCLOSEWAIT:
if (smc_cdc_rxed_any_close(conn)) {
sk->sk_state = SMC_CLOSED;
sock_put(sk); /* passive closing */
}
break;
case SMC_APPCLOSEWAIT1:
case SMC_APPCLOSEWAIT2:
/* postpone sock_put() for passive closing to cover
* received SEND_SHUTDOWN as well
*/
break;
case SMC_APPFINCLOSEWAIT:
case SMC_PEERABORTWAIT:
case SMC_PROCESSABORT:
case SMC_CLOSED:
/* nothing to do, add tracing in future patch */
break;
}
wakeup:
sk->sk_data_ready(sk); /* wakeup blocked rcvbuf consumers */
sk->sk_write_space(sk); /* wakeup blocked sndbuf producers */
if (old_state != sk->sk_state) {
sk->sk_state_change(sk);
if ((sk->sk_state == SMC_CLOSED) &&
(sock_flag(sk, SOCK_DEAD) || !sk->sk_socket)) {
smc_conn_free(conn);
if (smc->clcsock)
release_clcsock = true;
}
}
release_sock(sk);
if (release_clcsock)
smc_clcsock_release(smc);
sock_put(sk); /* sock_hold done by schedulers of close_work */
}
int smc_close_shutdown_write(struct smc_sock *smc)
{
struct smc_connection *conn = &smc->conn;
struct sock *sk = &smc->sk;
int old_state;
long timeout;
int rc = 0;
timeout = current->flags & PF_EXITING ?
0 : sock_flag(sk, SOCK_LINGER) ?
sk->sk_lingertime : SMC_MAX_STREAM_WAIT_TIMEOUT;
old_state = sk->sk_state;
again:
switch (sk->sk_state) {
case SMC_ACTIVE:
smc_close_stream_wait(smc, timeout);
release_sock(sk);
cancel_delayed_work_sync(&conn->tx_work);
lock_sock(sk);
if (sk->sk_state != SMC_ACTIVE)
goto again;
/* send close wr request */
rc = smc_close_wr(conn);
sk->sk_state = SMC_PEERCLOSEWAIT1;
break;
case SMC_APPCLOSEWAIT1:
/* passive close */
if (!smc_cdc_rxed_any_close(conn))
smc_close_stream_wait(smc, timeout);
release_sock(sk);
cancel_delayed_work_sync(&conn->tx_work);
lock_sock(sk);
if (sk->sk_state != SMC_APPCLOSEWAIT1)
goto again;
/* confirm close from peer */
rc = smc_close_wr(conn);
sk->sk_state = SMC_APPCLOSEWAIT2;
break;
case SMC_APPCLOSEWAIT2:
case SMC_PEERFINCLOSEWAIT:
case SMC_PEERCLOSEWAIT1:
case SMC_PEERCLOSEWAIT2:
case SMC_APPFINCLOSEWAIT:
case SMC_PROCESSABORT:
case SMC_PEERABORTWAIT:
/* nothing to do, add tracing in future patch */
break;
}
if (old_state != sk->sk_state)
sk->sk_state_change(sk);
return rc;
}
/* Initialize close properties on connection establishment. */
void smc_close_init(struct smc_sock *smc)
{
INIT_WORK(&smc->conn.close_work, smc_close_passive_work);
}
+30
View File
@@ -0,0 +1,30 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Shared Memory Communications over RDMA (SMC-R) and RoCE
*
* Socket Closing
*
* Copyright IBM Corp. 2016
*
* Author(s): Ursula Braun <ubraun@linux.vnet.ibm.com>
*/
#ifndef SMC_CLOSE_H
#define SMC_CLOSE_H
#include <linux/workqueue.h>
#include "smc.h"
#define SMC_MAX_STREAM_WAIT_TIMEOUT (2 * HZ)
#define SMC_CLOSE_SOCK_PUT_DELAY HZ
void smc_close_wake_tx_prepared(struct smc_sock *smc);
int smc_close_active(struct smc_sock *smc);
int smc_close_shutdown_write(struct smc_sock *smc);
void smc_close_init(struct smc_sock *smc);
void smc_clcsock_release(struct smc_sock *smc);
int smc_close_abort(struct smc_connection *conn);
void smc_close_active_abort(struct smc_sock *smc);
#endif /* SMC_CLOSE_H */
File diff suppressed because it is too large Load Diff
+629
View File
@@ -0,0 +1,629 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Shared Memory Communications over RDMA (SMC-R) and RoCE
*
* Definitions for SMC Connections, Link Groups and Links
*
* Copyright IBM Corp. 2016
*
* Author(s): Ursula Braun <ubraun@linux.vnet.ibm.com>
*/
#ifndef _SMC_CORE_H
#define _SMC_CORE_H
#include <linux/atomic.h>
#include <linux/types.h>
#include <linux/smc.h>
#include <linux/pci.h>
#include <rdma/ib_verbs.h>
#include <net/genetlink.h>
#include <net/smc.h>
#include "smc.h"
#include "smc_ib.h"
#include "smc_clc.h"
#define SMC_RMBS_PER_LGR_MAX 255 /* max. # of RMBs per link group */
#define SMC_CONN_PER_LGR_MIN 16 /* min. # of connections per link group */
#define SMC_CONN_PER_LGR_MAX 255 /* max. # of connections per link group,
* also is the default value for SMC-R v1 and v2.0
*/
#define SMC_CONN_PER_LGR_PREFER 255 /* Preferred connections per link group used for
* SMC-R v2.1 and later negotiation, vendors or
* distributions may modify it to a value between
* 16-255 as needed.
*/
#define SMCR_MAX_SEND_WR_DEF 16 /* Default number of work requests per send queue */
#define SMCR_MAX_RECV_WR_DEF 48 /* Default number of work requests per recv queue */
struct smc_lgr_list { /* list of link group definition */
struct list_head list;
spinlock_t lock; /* protects list of link groups */
u32 num; /* unique link group number */
};
enum smc_lgr_role { /* possible roles of a link group */
SMC_CLNT, /* client */
SMC_SERV /* server */
};
enum smc_link_state { /* possible states of a link */
SMC_LNK_UNUSED, /* link is unused */
SMC_LNK_INACTIVE, /* link is inactive */
SMC_LNK_ACTIVATING, /* link is being activated */
SMC_LNK_ACTIVE, /* link is active */
};
#define SMC_WR_BUF_SIZE 48 /* size of work request buffer */
#define SMC_WR_BUF_V2_SIZE 8192 /* size of v2 work request buffer */
struct smc_wr_buf {
u8 raw[SMC_WR_BUF_SIZE];
};
struct smc_wr_v2_buf {
u8 raw[SMC_WR_BUF_V2_SIZE];
};
#define SMC_WR_REG_MR_WAIT_TIME (5 * HZ)/* wait time for ib_wr_reg_mr result */
enum smc_wr_reg_state {
POSTED, /* ib_wr_reg_mr request posted */
CONFIRMED, /* ib_wr_reg_mr response: successful */
FAILED /* ib_wr_reg_mr response: failure */
};
struct smc_rdma_sge { /* sges for RDMA writes */
struct ib_sge wr_tx_rdma_sge[SMC_IB_MAX_SEND_SGE];
};
#define SMC_MAX_RDMA_WRITES 2 /* max. # of RDMA writes per
* message send
*/
struct smc_rdma_sges { /* sges per message send */
struct smc_rdma_sge tx_rdma_sge[SMC_MAX_RDMA_WRITES];
};
struct smc_rdma_wr { /* work requests per message
* send
*/
struct ib_rdma_wr wr_tx_rdma[SMC_MAX_RDMA_WRITES];
};
#define SMC_LGR_ID_SIZE 4
struct smc_link {
struct smc_ib_device *smcibdev; /* ib-device */
u8 ibport; /* port - values 1 | 2 */
struct ib_pd *roce_pd; /* IB protection domain,
* unique for every RoCE QP
*/
struct ib_qp *roce_qp; /* IB queue pair */
struct ib_qp_attr qp_attr; /* IB queue pair attributes */
struct smc_wr_buf *wr_tx_bufs; /* WR send payload buffers */
struct ib_send_wr *wr_tx_ibs; /* WR send meta data */
struct ib_sge *wr_tx_sges; /* WR send gather meta data */
struct smc_rdma_sges *wr_tx_rdma_sges;/*RDMA WRITE gather meta data*/
struct smc_rdma_wr *wr_tx_rdmas; /* WR RDMA WRITE */
struct smc_wr_tx_pend *wr_tx_pends; /* WR send waiting for CQE */
struct completion *wr_tx_compl; /* WR send CQE completion */
/* above four vectors have wr_tx_cnt elements and use the same index */
struct ib_send_wr *wr_tx_v2_ib; /* WR send v2 meta data */
struct ib_sge *wr_tx_v2_sge; /* WR send v2 gather meta data*/
struct smc_wr_tx_pend *wr_tx_v2_pend; /* WR send v2 waiting for CQE */
dma_addr_t wr_tx_dma_addr; /* DMA address of wr_tx_bufs */
dma_addr_t wr_tx_v2_dma_addr; /* DMA address of v2 tx buf*/
atomic_long_t wr_tx_id; /* seq # of last sent WR */
unsigned long *wr_tx_mask; /* bit mask of used indexes */
u32 wr_tx_cnt; /* number of WR send buffers */
wait_queue_head_t wr_tx_wait; /* wait for free WR send buf */
struct {
struct percpu_ref wr_tx_refs;
} ____cacheline_aligned_in_smp;
struct completion tx_ref_comp;
u8 *wr_rx_bufs; /* WR recv payload buffers */
struct ib_recv_wr *wr_rx_ibs; /* WR recv meta data */
struct ib_sge *wr_rx_sges; /* WR recv scatter meta data */
/* above three vectors have wr_rx_cnt elements and use the same index */
int wr_rx_sge_cnt; /* rx sge, V1 is 1, V2 is either 2 or 1 */
int wr_rx_buflen; /* buffer len for the first sge, len for the
* second sge is lgr shared if rx sge is 2.
*/
dma_addr_t wr_rx_dma_addr; /* DMA address of wr_rx_bufs */
dma_addr_t wr_rx_v2_dma_addr; /* DMA address of v2 rx buf*/
u64 wr_rx_id; /* seq # of last recv WR */
u64 wr_rx_id_compl; /* seq # of last completed WR */
u32 wr_rx_cnt; /* number of WR recv buffers */
unsigned long wr_rx_tstamp; /* jiffies when last buf rx */
wait_queue_head_t wr_rx_empty_wait; /* wait for RQ empty */
struct ib_reg_wr wr_reg; /* WR register memory region */
wait_queue_head_t wr_reg_wait; /* wait for wr_reg result */
struct {
struct percpu_ref wr_reg_refs;
} ____cacheline_aligned_in_smp;
struct completion reg_ref_comp;
enum smc_wr_reg_state wr_reg_state; /* state of wr_reg request */
u8 gid[SMC_GID_SIZE];/* gid matching used vlan id*/
u8 sgid_index; /* gid index for vlan id */
u32 peer_qpn; /* QP number of peer */
enum ib_mtu path_mtu; /* used mtu */
enum ib_mtu peer_mtu; /* mtu size of peer */
u32 psn_initial; /* QP tx initial packet seqno */
u32 peer_psn; /* QP rx initial packet seqno */
u8 peer_mac[ETH_ALEN]; /* = gid[8:10||13:15] */
u8 peer_gid[SMC_GID_SIZE]; /* gid of peer*/
u8 link_id; /* unique # within link group */
u8 link_uid[SMC_LGR_ID_SIZE]; /* unique lnk id */
u8 peer_link_uid[SMC_LGR_ID_SIZE]; /* peer uid */
u8 link_idx; /* index in lgr link array */
u8 link_is_asym; /* is link asymmetric? */
u8 clearing : 1; /* link is being cleared */
refcount_t refcnt; /* link reference count */
struct smc_link_group *lgr; /* parent link group */
struct work_struct link_down_wrk; /* wrk to bring link down */
char ibname[IB_DEVICE_NAME_MAX]; /* ib device name */
int ndev_ifidx; /* network device ifindex */
enum smc_link_state state; /* state of link */
struct delayed_work llc_testlink_wrk; /* testlink worker */
struct completion llc_testlink_resp; /* wait for rx of testlink */
int llc_testlink_time; /* testlink interval */
atomic_t conn_cnt; /* connections on this link */
u16 max_send_wr;
u16 max_recv_wr;
};
/* For now we just allow one parallel link per link group. The SMC protocol
* allows more (up to 8).
*/
#define SMC_LINKS_PER_LGR_MAX 3
#define SMC_SINGLE_LINK 0
#define SMC_LINKS_ADD_LNK_MIN 1 /* min. # of links per link group */
#define SMC_LINKS_ADD_LNK_MAX 2 /* max. # of links per link group, also is the
* default value for smc-r v1.0 and v2.0
*/
#define SMC_LINKS_PER_LGR_MAX_PREFER 2 /* Preferred max links per link group used for
* SMC-R v2.1 and later negotiation, vendors or
* distributions may modify it to a value between
* 1-2 as needed.
*/
/* tx/rx buffer list element for sndbufs list and rmbs list of a lgr */
struct smc_buf_desc {
struct list_head list;
void *cpu_addr; /* virtual address of buffer */
struct page *pages;
int len; /* length of buffer */
u32 used; /* currently used / unused */
union {
struct { /* SMC-R */
struct sg_table sgt[SMC_LINKS_PER_LGR_MAX];
/* virtual buffer */
struct ib_mr *mr[SMC_LINKS_PER_LGR_MAX];
/* memory region: for rmb and
* vzalloced sndbuf
* incl. rkey provided to peer
* and lkey provided to local
*/
u32 order; /* allocation order */
u8 is_conf_rkey;
/* confirm_rkey done */
u8 is_reg_mr[SMC_LINKS_PER_LGR_MAX];
/* mem region registered */
u8 is_map_ib[SMC_LINKS_PER_LGR_MAX];
/* mem region mapped to lnk */
u8 is_dma_need_sync;
u8 is_reg_err;
/* buffer registration err */
u8 is_vm;
/* virtually contiguous */
};
struct { /* SMC-D */
/* SMC-D tx buffer */
bool is_attached;
/* no need for explicit writes */
/* SMC-D rx buffer: */
unsigned short sba_idx;
/* SBA index number */
u64 token;
/* DMB token number */
dma_addr_t dma_addr;
/* DMA address */
};
};
};
struct smc_rtoken { /* address/key of remote RMB */
u64 dma_addr;
u32 rkey;
};
#define SMC_BUF_MIN_SIZE 16384 /* minimum size of an RMB */
#define SMC_RMBE_SIZES 16 /* number of distinct RMBE sizes */
/* theoretically, the RFC states that largest size would be 512K,
* i.e. compressed 5 and thus 6 sizes (0..5), despite
* struct smc_clc_msg_accept_confirm.rmbe_size being a 4 bit value (0..15)
*/
struct smcd_dev;
enum smc_lgr_type { /* redundancy state of lgr */
SMC_LGR_NONE, /* no active links, lgr to be deleted */
SMC_LGR_SINGLE, /* 1 active RNIC on each peer */
SMC_LGR_SYMMETRIC, /* 2 active RNICs on each peer */
SMC_LGR_ASYMMETRIC_PEER, /* local has 2, peer 1 active RNICs */
SMC_LGR_ASYMMETRIC_LOCAL, /* local has 1, peer 2 active RNICs */
};
enum smcr_buf_type { /* types of SMC-R sndbufs and RMBs */
SMCR_PHYS_CONT_BUFS = 0,
SMCR_VIRT_CONT_BUFS = 1,
SMCR_MIXED_BUFS = 2,
};
enum smc_llc_flowtype {
SMC_LLC_FLOW_NONE = 0,
SMC_LLC_FLOW_ADD_LINK = 2,
SMC_LLC_FLOW_DEL_LINK = 4,
SMC_LLC_FLOW_REQ_ADD_LINK = 5,
SMC_LLC_FLOW_RKEY = 6,
};
struct smc_llc_qentry;
struct smc_llc_flow {
enum smc_llc_flowtype type;
struct smc_llc_qentry *qentry;
};
struct smc_link_group {
struct list_head list;
struct rb_root conns_all; /* connection tree */
rwlock_t conns_lock; /* protects conns_all */
unsigned int conns_num; /* current # of connections */
unsigned short vlan_id; /* vlan id of link group */
struct list_head sndbufs[SMC_RMBE_SIZES];/* tx buffers */
struct rw_semaphore sndbufs_lock; /* protects tx buffers */
struct list_head rmbs[SMC_RMBE_SIZES]; /* rx buffers */
struct rw_semaphore rmbs_lock; /* protects rx buffers */
u64 alloc_sndbufs; /* stats of tx buffers */
u64 alloc_rmbs; /* stats of rx buffers */
u8 id[SMC_LGR_ID_SIZE]; /* unique lgr id */
struct delayed_work free_work; /* delayed freeing of an lgr */
struct work_struct terminate_work; /* abnormal lgr termination */
struct workqueue_struct *tx_wq; /* wq for conn. tx workers */
u8 sync_err : 1; /* lgr no longer fits to peer */
u8 terminating : 1;/* lgr is terminating */
u8 freeing : 1; /* lgr is being freed */
refcount_t refcnt; /* lgr reference count */
bool is_smcd; /* SMC-R or SMC-D */
u8 smc_version;
u8 negotiated_eid[SMC_MAX_EID_LEN];
u8 peer_os; /* peer operating system */
u8 peer_smc_release;
u8 peer_hostname[SMC_MAX_HOSTNAME_LEN];
union {
struct { /* SMC-R */
enum smc_lgr_role role;
/* client or server */
struct smc_link lnk[SMC_LINKS_PER_LGR_MAX];
/* smc link */
struct smc_wr_v2_buf *wr_rx_buf_v2;
/* WR v2 recv payload buffer */
struct smc_wr_v2_buf *wr_tx_buf_v2;
/* WR v2 send payload buffer */
char peer_systemid[SMC_SYSTEMID_LEN];
/* unique system_id of peer */
struct smc_rtoken rtokens[SMC_RMBS_PER_LGR_MAX]
[SMC_LINKS_PER_LGR_MAX];
/* remote addr/key pairs */
DECLARE_BITMAP(rtokens_used_mask, SMC_RMBS_PER_LGR_MAX);
/* used rtoken elements */
u8 next_link_id;
enum smc_lgr_type type;
enum smcr_buf_type buf_type;
/* redundancy state */
u8 pnet_id[SMC_MAX_PNETID_LEN + 1];
/* pnet id of this lgr */
struct list_head llc_event_q;
/* queue for llc events */
spinlock_t llc_event_q_lock;
/* protects llc_event_q */
struct rw_semaphore llc_conf_mutex;
/* protects lgr reconfig. */
struct work_struct llc_add_link_work;
struct work_struct llc_del_link_work;
struct work_struct llc_event_work;
/* llc event worker */
wait_queue_head_t llc_flow_waiter;
/* w4 next llc event */
wait_queue_head_t llc_msg_waiter;
/* w4 next llc msg */
struct smc_llc_flow llc_flow_lcl;
/* llc local control field */
struct smc_llc_flow llc_flow_rmt;
/* llc remote control field */
struct smc_llc_qentry *delayed_event;
/* arrived when flow active */
spinlock_t llc_flow_lock;
/* protects llc flow */
int llc_testlink_time;
/* link keep alive time */
u32 llc_termination_rsn;
/* rsn code for termination */
u8 nexthop_mac[ETH_ALEN];
u8 uses_gateway;
__be32 saddr;
/* net namespace */
struct net *net;
u8 max_conns;
/* max conn can be assigned to lgr */
u8 max_links;
/* max links can be added in lgr */
u16 max_send_wr;
/* number of WR buffers on send */
u16 max_recv_wr;
/* number of WR buffers on recv */
};
struct { /* SMC-D */
struct smcd_gid peer_gid;
/* Peer GID (remote) */
struct smcd_dev *smcd;
/* ISM device for VLAN reg. */
u8 peer_shutdown : 1;
/* peer triggered shutdownn */
};
};
};
struct smc_clc_msg_local;
#define GID_LIST_SIZE 2
struct smc_gidlist {
u8 len;
u8 list[GID_LIST_SIZE][SMC_GID_SIZE];
};
struct smc_init_info_smcrv2 {
/* Input fields */
__be32 saddr;
struct sock *clc_sk;
__be32 daddr;
/* Output fields when saddr is set */
struct smc_ib_device *ib_dev_v2;
u8 ib_port_v2;
u8 ib_gid_v2[SMC_GID_SIZE];
/* Additional output fields when clc_sk and daddr is set as well */
u8 uses_gateway;
u8 nexthop_mac[ETH_ALEN];
struct smc_gidlist gidlist;
};
#define SMC_MAX_V2_ISM_DEVS SMCD_CLC_MAX_V2_GID_ENTRIES
/* max # of proposed non-native ISM devices,
* which can't exceed the max # of CHID-GID
* entries in CLC proposal SMC-Dv2 extension.
*/
struct smc_init_info {
u8 is_smcd;
u8 smc_type_v1;
u8 smc_type_v2;
u8 release_nr;
u8 max_conns;
u8 max_links;
u8 first_contact_peer;
u8 first_contact_local;
u16 feature_mask;
unsigned short vlan_id;
u32 rc;
u8 negotiated_eid[SMC_MAX_EID_LEN];
/* SMC-R */
u8 smcr_version;
u8 check_smcrv2;
u8 peer_gid[SMC_GID_SIZE];
u8 peer_mac[ETH_ALEN];
u8 peer_systemid[SMC_SYSTEMID_LEN];
struct smc_ib_device *ib_dev;
u8 ib_gid[SMC_GID_SIZE];
u8 ib_port;
u32 ib_clcqpn;
struct smc_init_info_smcrv2 smcrv2;
/* SMC-D */
struct smcd_gid ism_peer_gid[SMC_MAX_V2_ISM_DEVS + 1];
struct smcd_dev *ism_dev[SMC_MAX_V2_ISM_DEVS + 1];
u16 ism_chid[SMC_MAX_V2_ISM_DEVS + 1];
u8 ism_offered_cnt; /* # of ISM devices offered */
u8 ism_selected; /* index of selected ISM dev*/
u8 smcd_version;
};
/* Find the connection associated with the given alert token in the link group.
* To use rbtrees we have to implement our own search core.
* Requires @conns_lock
* @token alert token to search for
* @lgr link group to search in
* Returns connection associated with token if found, NULL otherwise.
*/
static inline struct smc_connection *smc_lgr_find_conn(
u32 token, struct smc_link_group *lgr)
{
struct smc_connection *res = NULL;
struct rb_node *node;
node = lgr->conns_all.rb_node;
while (node) {
struct smc_connection *cur = rb_entry(node,
struct smc_connection, alert_node);
if (cur->alert_token_local > token) {
node = node->rb_left;
} else {
if (cur->alert_token_local < token) {
node = node->rb_right;
} else {
res = cur;
break;
}
}
}
return res;
}
static inline bool smc_conn_lgr_valid(struct smc_connection *conn)
{
return conn->lgr && conn->alert_token_local;
}
/*
* Returns true if the specified link is usable.
*
* usable means the link is ready to receive RDMA messages, map memory
* on the link, etc. This doesn't ensure we are able to send RDMA messages
* on this link, if sending RDMA messages is needed, use smc_link_sendable()
*/
static inline bool smc_link_usable(struct smc_link *lnk)
{
if (lnk->state == SMC_LNK_UNUSED || lnk->state == SMC_LNK_INACTIVE)
return false;
return true;
}
/*
* Returns true if the specified link is ready to receive AND send RDMA
* messages.
*
* For the client side in first contact, the underlying QP may still in
* RESET or RTR when the link state is ACTIVATING, checks in smc_link_usable()
* is not strong enough. For those places that need to send any CDC or LLC
* messages, use smc_link_sendable(), otherwise, use smc_link_usable() instead
*/
static inline bool smc_link_sendable(struct smc_link *lnk)
{
return smc_link_usable(lnk) &&
lnk->qp_attr.cur_qp_state == IB_QPS_RTS;
}
static inline bool smc_link_active(struct smc_link *lnk)
{
return lnk->state == SMC_LNK_ACTIVE;
}
static inline bool smc_link_shared_v2_rxbuf(struct smc_link *lnk)
{
return lnk->wr_rx_sge_cnt > 1;
}
static inline void smc_gid_be16_convert(__u8 *buf, u8 *gid_raw)
{
sprintf(buf, "%04x:%04x:%04x:%04x:%04x:%04x:%04x:%04x",
be16_to_cpu(((__be16 *)gid_raw)[0]),
be16_to_cpu(((__be16 *)gid_raw)[1]),
be16_to_cpu(((__be16 *)gid_raw)[2]),
be16_to_cpu(((__be16 *)gid_raw)[3]),
be16_to_cpu(((__be16 *)gid_raw)[4]),
be16_to_cpu(((__be16 *)gid_raw)[5]),
be16_to_cpu(((__be16 *)gid_raw)[6]),
be16_to_cpu(((__be16 *)gid_raw)[7]));
}
struct smc_pci_dev {
__u32 pci_fid;
__u16 pci_pchid;
__u16 pci_vendor;
__u16 pci_device;
__u8 pci_id[SMC_PCI_ID_STR_LEN];
};
static inline void smc_set_pci_values(struct pci_dev *pci_dev,
struct smc_pci_dev *smc_dev)
{
smc_dev->pci_vendor = pci_dev->vendor;
smc_dev->pci_device = pci_dev->device;
snprintf(smc_dev->pci_id, sizeof(smc_dev->pci_id), "%s",
pci_name(pci_dev));
#if IS_ENABLED(CONFIG_S390)
{ /* Set s390 specific PCI information */
struct zpci_dev *zdev;
zdev = to_zpci(pci_dev);
smc_dev->pci_fid = zdev->fid;
smc_dev->pci_pchid = zdev->pchid;
}
#endif
}
struct smc_sock;
struct smc_clc_msg_accept_confirm;
void smc_lgr_cleanup_early(struct smc_link_group *lgr);
void smc_lgr_terminate_sched(struct smc_link_group *lgr);
void smc_lgr_hold(struct smc_link_group *lgr);
void smc_lgr_put(struct smc_link_group *lgr);
void smcr_port_add(struct smc_ib_device *smcibdev, u8 ibport);
void smcr_port_err(struct smc_ib_device *smcibdev, u8 ibport);
void smc_smcd_terminate(struct smcd_dev *dev, struct smcd_gid *peer_gid,
unsigned short vlan);
void smc_smcd_terminate_all(struct smcd_dev *dev);
void smc_smcr_terminate_all(struct smc_ib_device *smcibdev);
int smc_buf_create(struct smc_sock *smc, bool is_smcd);
int smcd_buf_attach(struct smc_sock *smc);
int smc_uncompress_bufsize(u8 compressed);
int smc_rmb_rtoken_handling(struct smc_connection *conn, struct smc_link *link,
struct smc_clc_msg_accept_confirm *clc);
int smc_rtoken_add(struct smc_link *lnk, __be64 nw_vaddr, __be32 nw_rkey);
int smc_rtoken_delete(struct smc_link *lnk, __be32 nw_rkey);
void smc_rtoken_set(struct smc_link_group *lgr, int link_idx, int link_idx_new,
__be32 nw_rkey_known, __be64 nw_vaddr, __be32 nw_rkey);
void smc_rtoken_set2(struct smc_link_group *lgr, int rtok_idx, int link_id,
__be64 nw_vaddr, __be32 nw_rkey);
void smc_sndbuf_sync_sg_for_device(struct smc_connection *conn);
void smc_rmb_sync_sg_for_cpu(struct smc_connection *conn);
int smc_vlan_by_tcpsk(struct socket *clcsock, struct smc_init_info *ini);
void smc_conn_free(struct smc_connection *conn);
int smc_conn_create(struct smc_sock *smc, struct smc_init_info *ini);
int smc_core_init(void);
void smc_core_exit(void);
int smcr_link_init(struct smc_link_group *lgr, struct smc_link *lnk,
u8 link_idx, struct smc_init_info *ini);
void smcr_link_clear(struct smc_link *lnk, bool log);
void smcr_link_hold(struct smc_link *lnk);
void smcr_link_put(struct smc_link *lnk);
void smc_switch_link_and_count(struct smc_connection *conn,
struct smc_link *to_lnk);
int smcr_buf_map_lgr(struct smc_link *lnk);
int smcr_buf_reg_lgr(struct smc_link *lnk);
void smcr_lgr_set_type(struct smc_link_group *lgr, enum smc_lgr_type new_type);
void smcr_lgr_set_type_asym(struct smc_link_group *lgr,
enum smc_lgr_type new_type, int asym_lnk_idx);
int smcr_link_reg_buf(struct smc_link *link, struct smc_buf_desc *rmb_desc);
struct smc_link *smc_switch_conns(struct smc_link_group *lgr,
struct smc_link *from_lnk, bool is_dev_err);
void smcr_link_down_cond(struct smc_link *lnk);
void smcr_link_down_cond_sched(struct smc_link *lnk);
int smc_nl_get_sys_info(struct sk_buff *skb, struct netlink_callback *cb);
int smcr_nl_get_lgr(struct sk_buff *skb, struct netlink_callback *cb);
int smcr_nl_get_link(struct sk_buff *skb, struct netlink_callback *cb);
int smcd_nl_get_lgr(struct sk_buff *skb, struct netlink_callback *cb);
static inline struct smc_link_group *smc_get_lgr(struct smc_link *link)
{
return link->lgr;
}
#endif
+278
View File
@@ -0,0 +1,278 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Shared Memory Communications over RDMA (SMC-R) and RoCE
*
* Monitoring SMC transport protocol sockets
*
* Copyright IBM Corp. 2016
*
* Author(s): Ursula Braun <ubraun@linux.vnet.ibm.com>
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/init.h>
#include <linux/sock_diag.h>
#include <linux/inet_diag.h>
#include <linux/smc_diag.h>
#include <net/netlink.h>
#include <net/smc.h>
#include "smc.h"
#include "smc_core.h"
#include "smc_ism.h"
struct smc_diag_dump_ctx {
int pos[2];
};
static struct smc_diag_dump_ctx *smc_dump_context(struct netlink_callback *cb)
{
return (struct smc_diag_dump_ctx *)cb->ctx;
}
static void smc_diag_msg_common_fill(struct smc_diag_msg *r, struct sock *sk)
{
struct smc_sock *smc = smc_sk(sk);
memset(r, 0, sizeof(*r));
r->diag_family = sk->sk_family;
sock_diag_save_cookie(sk, r->id.idiag_cookie);
if (!smc->clcsock)
return;
r->id.idiag_sport = htons(smc->clcsock->sk->sk_num);
r->id.idiag_dport = smc->clcsock->sk->sk_dport;
r->id.idiag_if = smc->clcsock->sk->sk_bound_dev_if;
if (sk->sk_protocol == SMCPROTO_SMC) {
r->id.idiag_src[0] = smc->clcsock->sk->sk_rcv_saddr;
r->id.idiag_dst[0] = smc->clcsock->sk->sk_daddr;
#if IS_ENABLED(CONFIG_IPV6)
} else if (sk->sk_protocol == SMCPROTO_SMC6) {
memcpy(&r->id.idiag_src, &smc->clcsock->sk->sk_v6_rcv_saddr,
sizeof(smc->clcsock->sk->sk_v6_rcv_saddr));
memcpy(&r->id.idiag_dst, &smc->clcsock->sk->sk_v6_daddr,
sizeof(smc->clcsock->sk->sk_v6_daddr));
#endif
}
}
static int smc_diag_msg_attrs_fill(struct sock *sk, struct sk_buff *skb,
struct smc_diag_msg *r,
struct user_namespace *user_ns)
{
if (nla_put_u8(skb, SMC_DIAG_SHUTDOWN, sk->sk_shutdown))
return 1;
r->diag_uid = from_kuid_munged(user_ns, sk_uid(sk));
r->diag_inode = sock_i_ino(sk);
return 0;
}
static int __smc_diag_dump(struct sock *sk, struct sk_buff *skb,
struct netlink_callback *cb,
const struct smc_diag_req *req,
struct nlattr *bc)
{
struct smc_sock *smc = smc_sk(sk);
struct smc_diag_fallback fallback;
struct user_namespace *user_ns;
struct smc_diag_msg *r;
struct nlmsghdr *nlh;
nlh = nlmsg_put(skb, NETLINK_CB(cb->skb).portid, cb->nlh->nlmsg_seq,
cb->nlh->nlmsg_type, sizeof(*r), NLM_F_MULTI);
if (!nlh)
return -EMSGSIZE;
r = nlmsg_data(nlh);
smc_diag_msg_common_fill(r, sk);
r->diag_state = sk->sk_state;
if (smc->use_fallback)
r->diag_mode = SMC_DIAG_MODE_FALLBACK_TCP;
else if (smc_conn_lgr_valid(&smc->conn) && smc->conn.lgr->is_smcd)
r->diag_mode = SMC_DIAG_MODE_SMCD;
else
r->diag_mode = SMC_DIAG_MODE_SMCR;
user_ns = sk_user_ns(NETLINK_CB(cb->skb).sk);
if (smc_diag_msg_attrs_fill(sk, skb, r, user_ns))
goto errout;
fallback.reason = smc->fallback_rsn;
fallback.peer_diagnosis = smc->peer_diagnosis;
if (nla_put(skb, SMC_DIAG_FALLBACK, sizeof(fallback), &fallback) < 0)
goto errout;
if ((req->diag_ext & (1 << (SMC_DIAG_CONNINFO - 1))) &&
smc->conn.alert_token_local) {
struct smc_connection *conn = &smc->conn;
struct smc_diag_conninfo cinfo = {
.token = conn->alert_token_local,
.sndbuf_size = conn->sndbuf_desc ?
conn->sndbuf_desc->len : 0,
.rmbe_size = conn->rmb_desc ? conn->rmb_desc->len : 0,
.peer_rmbe_size = conn->peer_rmbe_size,
.rx_prod.wrap = conn->local_rx_ctrl.prod.wrap,
.rx_prod.count = conn->local_rx_ctrl.prod.count,
.rx_cons.wrap = conn->local_rx_ctrl.cons.wrap,
.rx_cons.count = conn->local_rx_ctrl.cons.count,
.tx_prod.wrap = conn->local_tx_ctrl.prod.wrap,
.tx_prod.count = conn->local_tx_ctrl.prod.count,
.tx_cons.wrap = conn->local_tx_ctrl.cons.wrap,
.tx_cons.count = conn->local_tx_ctrl.cons.count,
.tx_prod_flags =
*(u8 *)&conn->local_tx_ctrl.prod_flags,
.tx_conn_state_flags =
*(u8 *)&conn->local_tx_ctrl.conn_state_flags,
.rx_prod_flags = *(u8 *)&conn->local_rx_ctrl.prod_flags,
.rx_conn_state_flags =
*(u8 *)&conn->local_rx_ctrl.conn_state_flags,
.tx_prep.wrap = conn->tx_curs_prep.wrap,
.tx_prep.count = conn->tx_curs_prep.count,
.tx_sent.wrap = conn->tx_curs_sent.wrap,
.tx_sent.count = conn->tx_curs_sent.count,
.tx_fin.wrap = conn->tx_curs_fin.wrap,
.tx_fin.count = conn->tx_curs_fin.count,
};
if (nla_put(skb, SMC_DIAG_CONNINFO, sizeof(cinfo), &cinfo) < 0)
goto errout;
}
if (smc_conn_lgr_valid(&smc->conn) && !smc->conn.lgr->is_smcd &&
(req->diag_ext & (1 << (SMC_DIAG_LGRINFO - 1))) &&
!list_empty(&smc->conn.lgr->list)) {
struct smc_link *link = smc->conn.lnk;
struct smc_diag_lgrinfo linfo = {
.role = smc->conn.lgr->role,
.lnk[0].ibport = link->ibport,
.lnk[0].link_id = link->link_id,
};
memcpy(linfo.lnk[0].ibname, link->smcibdev->ibdev->name,
sizeof(link->smcibdev->ibdev->name));
smc_gid_be16_convert(linfo.lnk[0].gid, link->gid);
smc_gid_be16_convert(linfo.lnk[0].peer_gid, link->peer_gid);
if (nla_put(skb, SMC_DIAG_LGRINFO, sizeof(linfo), &linfo) < 0)
goto errout;
}
if (smc_conn_lgr_valid(&smc->conn) && smc->conn.lgr->is_smcd &&
(req->diag_ext & (1 << (SMC_DIAG_DMBINFO - 1))) &&
!list_empty(&smc->conn.lgr->list) && smc->conn.rmb_desc) {
struct smc_connection *conn = &smc->conn;
struct smcd_diag_dmbinfo dinfo;
struct smcd_dev *smcd = conn->lgr->smcd;
struct smcd_gid smcd_gid;
memset(&dinfo, 0, sizeof(dinfo));
dinfo.linkid = *((u32 *)conn->lgr->id);
dinfo.peer_gid = conn->lgr->peer_gid.gid;
dinfo.peer_gid_ext = conn->lgr->peer_gid.gid_ext;
copy_to_smcdgid(&smcd_gid, &smcd->dibs->gid);
dinfo.my_gid = smcd_gid.gid;
dinfo.my_gid_ext = smcd_gid.gid_ext;
dinfo.token = conn->rmb_desc->token;
dinfo.peer_token = conn->peer_token;
if (nla_put(skb, SMC_DIAG_DMBINFO, sizeof(dinfo), &dinfo) < 0)
goto errout;
}
nlmsg_end(skb, nlh);
return 0;
errout:
nlmsg_cancel(skb, nlh);
return -EMSGSIZE;
}
static int smc_diag_dump_proto(struct proto *prot, struct sk_buff *skb,
struct netlink_callback *cb, int p_type)
{
struct smc_diag_dump_ctx *cb_ctx = smc_dump_context(cb);
struct net *net = sock_net(skb->sk);
int snum = cb_ctx->pos[p_type];
struct nlattr *bc = NULL;
struct hlist_head *head;
int rc = 0, num = 0;
struct sock *sk;
read_lock(&prot->h.smc_hash->lock);
head = &prot->h.smc_hash->ht;
if (hlist_empty(head))
goto out;
sk_for_each(sk, head) {
if (!net_eq(sock_net(sk), net))
continue;
if (num < snum)
goto next;
rc = __smc_diag_dump(sk, skb, cb, nlmsg_data(cb->nlh), bc);
if (rc < 0)
goto out;
next:
num++;
}
out:
read_unlock(&prot->h.smc_hash->lock);
cb_ctx->pos[p_type] = num;
return rc;
}
static int smc_diag_dump(struct sk_buff *skb, struct netlink_callback *cb)
{
int rc = 0;
rc = smc_diag_dump_proto(&smc_proto, skb, cb, SMCPROTO_SMC);
if (!rc)
smc_diag_dump_proto(&smc_proto6, skb, cb, SMCPROTO_SMC6);
return skb->len;
}
static int smc_diag_handler_dump(struct sk_buff *skb, struct nlmsghdr *h)
{
struct net *net = sock_net(skb->sk);
if (h->nlmsg_type == SOCK_DIAG_BY_FAMILY &&
h->nlmsg_flags & NLM_F_DUMP) {
{
struct netlink_dump_control c = {
.dump = smc_diag_dump,
.min_dump_alloc = SKB_WITH_OVERHEAD(32768),
};
return netlink_dump_start(net->diag_nlsk, skb, h, &c);
}
}
return 0;
}
static const struct sock_diag_handler smc_diag_handler = {
.owner = THIS_MODULE,
.family = AF_SMC,
.dump = smc_diag_handler_dump,
};
static int __init smc_diag_init(void)
{
return sock_diag_register(&smc_diag_handler);
}
static void __exit smc_diag_exit(void)
{
sock_diag_unregister(&smc_diag_handler);
}
module_init(smc_diag_init);
module_exit(smc_diag_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("SMC socket monitoring via SOCK_DIAG");
MODULE_ALIAS_NET_PF_PROTO_TYPE(PF_NETLINK, NETLINK_SOCK_DIAG, 43 /* AF_SMC */);
MODULE_ALIAS_GENL_FAMILY(SMCR_GENL_FAMILY_NAME);
+140
View File
@@ -0,0 +1,140 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Shared Memory Communications over RDMA (SMC-R) and RoCE
*
* Generic hook for SMC handshake flow.
*
* Copyright IBM Corp. 2016
* Copyright (c) 2025, Alibaba Inc.
*
* Author: D. Wythe <alibuda@linux.alibaba.com>
*/
#include <linux/bpf_verifier.h>
#include <linux/bpf.h>
#include <linux/btf.h>
#include <linux/rculist.h>
#include "smc_hs_bpf.h"
static DEFINE_SPINLOCK(smc_hs_ctrl_list_lock);
static LIST_HEAD(smc_hs_ctrl_list);
static int smc_hs_ctrl_reg(struct smc_hs_ctrl *ctrl)
{
int ret = 0;
spin_lock(&smc_hs_ctrl_list_lock);
/* already exist or duplicate name */
if (smc_hs_ctrl_find_by_name(ctrl->name))
ret = -EEXIST;
else
list_add_tail_rcu(&ctrl->list, &smc_hs_ctrl_list);
spin_unlock(&smc_hs_ctrl_list_lock);
return ret;
}
static void smc_hs_ctrl_unreg(struct smc_hs_ctrl *ctrl)
{
spin_lock(&smc_hs_ctrl_list_lock);
list_del_rcu(&ctrl->list);
spin_unlock(&smc_hs_ctrl_list_lock);
/* Ensure that all readers to complete */
synchronize_rcu();
}
struct smc_hs_ctrl *smc_hs_ctrl_find_by_name(const char *name)
{
struct smc_hs_ctrl *ctrl;
list_for_each_entry_rcu(ctrl, &smc_hs_ctrl_list, list) {
if (strcmp(ctrl->name, name) == 0)
return ctrl;
}
return NULL;
}
static int __smc_bpf_stub_set_tcp_option(struct tcp_sock *tp) { return 1; }
static int __smc_bpf_stub_set_tcp_option_cond(const struct tcp_sock *tp,
struct inet_request_sock *ireq)
{
return 1;
}
static struct smc_hs_ctrl __smc_bpf_hs_ctrl = {
.syn_option = __smc_bpf_stub_set_tcp_option,
.synack_option = __smc_bpf_stub_set_tcp_option_cond,
};
static int smc_bpf_hs_ctrl_init(struct btf *btf) { return 0; }
static int smc_bpf_hs_ctrl_reg(void *kdata, struct bpf_link *link)
{
if (link)
return -EOPNOTSUPP;
return smc_hs_ctrl_reg(kdata);
}
static void smc_bpf_hs_ctrl_unreg(void *kdata, struct bpf_link *link)
{
smc_hs_ctrl_unreg(kdata);
}
static int smc_bpf_hs_ctrl_init_member(const struct btf_type *t,
const struct btf_member *member,
void *kdata, const void *udata)
{
const struct smc_hs_ctrl *u_ctrl;
struct smc_hs_ctrl *k_ctrl;
u32 moff;
u_ctrl = (const struct smc_hs_ctrl *)udata;
k_ctrl = (struct smc_hs_ctrl *)kdata;
moff = __btf_member_bit_offset(t, member) / 8;
switch (moff) {
case offsetof(struct smc_hs_ctrl, name):
if (bpf_obj_name_cpy(k_ctrl->name, u_ctrl->name,
sizeof(u_ctrl->name)) <= 0)
return -EINVAL;
return 1;
case offsetof(struct smc_hs_ctrl, flags):
if (u_ctrl->flags & ~SMC_HS_CTRL_ALL_FLAGS)
return -EINVAL;
k_ctrl->flags = u_ctrl->flags;
return 1;
default:
break;
}
return 0;
}
static const struct bpf_func_proto *
bpf_smc_hs_func_proto(enum bpf_func_id func_id, const struct bpf_prog *prog)
{
return bpf_base_func_proto(func_id, prog);
}
static const struct bpf_verifier_ops smc_bpf_verifier_ops = {
.get_func_proto = bpf_smc_hs_func_proto,
.is_valid_access = bpf_tracing_btf_ctx_access,
};
static struct bpf_struct_ops bpf_smc_hs_ctrl_ops = {
.name = "smc_hs_ctrl",
.init = smc_bpf_hs_ctrl_init,
.reg = smc_bpf_hs_ctrl_reg,
.unreg = smc_bpf_hs_ctrl_unreg,
.cfi_stubs = &__smc_bpf_hs_ctrl,
.verifier_ops = &smc_bpf_verifier_ops,
.init_member = smc_bpf_hs_ctrl_init_member,
.owner = THIS_MODULE,
};
int bpf_smc_hs_ctrl_init(void)
{
return register_bpf_struct_ops(&bpf_smc_hs_ctrl_ops, smc_hs_ctrl);
}
+31
View File
@@ -0,0 +1,31 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Shared Memory Communications over RDMA (SMC-R) and RoCE
*
* Generic hook for SMC handshake flow.
*
* Copyright IBM Corp. 2016
* Copyright (c) 2025, Alibaba Inc.
*
* Author: D. Wythe <alibuda@linux.alibaba.com>
*/
#ifndef __SMC_HS_CTRL
#define __SMC_HS_CTRL
#include <net/smc.h>
/* Find hs_ctrl by the target name, which required to be a c-string.
* Return NULL if no such ctrl was found,otherwise, return a valid ctrl.
*
* Note: Caller MUST ensure it's was invoked under rcu_read_lock.
*/
struct smc_hs_ctrl *smc_hs_ctrl_find_by_name(const char *name);
#if IS_ENABLED(CONFIG_SMC_HS_CTRL_BPF)
int bpf_smc_hs_ctrl_init(void);
#else
static inline int bpf_smc_hs_ctrl_init(void) { return 0; }
#endif /* CONFIG_SMC_HS_CTRL_BPF */
#endif /* __SMC_HS_CTRL */
File diff suppressed because it is too large Load Diff
+119
View File
@@ -0,0 +1,119 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Shared Memory Communications over RDMA (SMC-R) and RoCE
*
* Definitions for IB environment
*
* Copyright IBM Corp. 2016
*
* Author(s): Ursula Braun <Ursula Braun@linux.vnet.ibm.com>
*/
#ifndef _SMC_IB_H
#define _SMC_IB_H
#include <linux/interrupt.h>
#include <linux/if_ether.h>
#include <linux/mutex.h>
#include <linux/wait.h>
#include <rdma/ib_verbs.h>
#include <net/smc.h>
#define SMC_MAX_PORTS 2 /* Max # of ports */
#define SMC_GID_SIZE sizeof(union ib_gid)
#define SMC_IB_MAX_SEND_SGE 2
struct smc_ib_devices { /* list of smc ib devices definition */
struct list_head list;
struct mutex mutex; /* protects list of smc ib devices */
};
extern struct smc_ib_devices smc_ib_devices; /* list of smc ib devices */
extern struct smc_lgr_list smc_lgr_list; /* list of linkgroups */
struct smc_ib_device { /* ib-device infos for smc */
struct list_head list;
struct ib_device *ibdev;
struct ib_port_attr pattr[SMC_MAX_PORTS]; /* ib dev. port attrs */
struct ib_event_handler event_handler; /* global ib_event handler */
struct ib_cq *roce_cq_send; /* send completion queue */
struct ib_cq *roce_cq_recv; /* recv completion queue */
struct tasklet_struct send_tasklet; /* called by send cq handler */
struct tasklet_struct recv_tasklet; /* called by recv cq handler */
char mac[SMC_MAX_PORTS][ETH_ALEN];
/* mac address per port*/
u8 pnetid[SMC_MAX_PORTS][SMC_MAX_PNETID_LEN];
/* pnetid per port */
bool pnetid_by_user[SMC_MAX_PORTS];
/* pnetid defined by user? */
u8 initialized : 1; /* ib dev CQ, evthdl done */
struct work_struct port_event_work;
unsigned long port_event_mask;
DECLARE_BITMAP(ports_going_away, SMC_MAX_PORTS);
atomic_t lnk_cnt; /* number of links on ibdev */
wait_queue_head_t lnks_deleted; /* wait 4 removal of all links*/
struct mutex mutex; /* protect dev setup+cleanup */
atomic_t lnk_cnt_by_port[SMC_MAX_PORTS];
/* number of links per port */
int ndev_ifidx[SMC_MAX_PORTS]; /* ndev if indexes */
};
static inline __be32 smc_ib_gid_to_ipv4(u8 gid[SMC_GID_SIZE])
{
struct in6_addr *addr6 = (struct in6_addr *)gid;
if (ipv6_addr_v4mapped(addr6) ||
!(addr6->s6_addr32[0] | addr6->s6_addr32[1] | addr6->s6_addr32[2]))
return addr6->s6_addr32[3];
return cpu_to_be32(INADDR_NONE);
}
static inline struct net *smc_ib_net(struct smc_ib_device *smcibdev)
{
if (smcibdev && smcibdev->ibdev)
return read_pnet(&smcibdev->ibdev->coredev.rdma_net);
return NULL;
}
struct smc_init_info_smcrv2;
struct smc_buf_desc;
struct smc_link;
void smc_ib_ndev_change(struct net_device *ndev, unsigned long event);
int smc_ib_register_client(void) __init;
void smc_ib_unregister_client(void);
bool smc_ib_port_active(struct smc_ib_device *smcibdev, u8 ibport);
int smc_ib_buf_map_sg(struct smc_link *lnk,
struct smc_buf_desc *buf_slot,
enum dma_data_direction data_direction);
void smc_ib_buf_unmap_sg(struct smc_link *lnk,
struct smc_buf_desc *buf_slot,
enum dma_data_direction data_direction);
void smc_ib_dealloc_protection_domain(struct smc_link *lnk);
int smc_ib_create_protection_domain(struct smc_link *lnk);
void smc_ib_destroy_queue_pair(struct smc_link *lnk);
int smc_ib_create_queue_pair(struct smc_link *lnk);
int smc_ib_ready_link(struct smc_link *lnk);
int smc_ib_modify_qp_rts(struct smc_link *lnk);
int smc_ib_modify_qp_error(struct smc_link *lnk);
long smc_ib_setup_per_ibdev(struct smc_ib_device *smcibdev);
int smc_ib_get_memory_region(struct ib_pd *pd, int access_flags,
struct smc_buf_desc *buf_slot, u8 link_idx);
void smc_ib_put_memory_region(struct ib_mr *mr);
bool smc_ib_is_sg_need_sync(struct smc_link *lnk,
struct smc_buf_desc *buf_slot);
void smc_ib_sync_sg_for_cpu(struct smc_link *lnk,
struct smc_buf_desc *buf_slot,
enum dma_data_direction data_direction);
void smc_ib_sync_sg_for_device(struct smc_link *lnk,
struct smc_buf_desc *buf_slot,
enum dma_data_direction data_direction);
int smc_ib_determine_gid(struct smc_ib_device *smcibdev, u8 ibport,
unsigned short vlan_id, u8 gid[], u8 *sgid_index,
struct smc_init_info_smcrv2 *smcrv2);
int smc_ib_find_route(struct net *net, __be32 saddr, __be32 daddr,
u8 nexthop_mac[], u8 *uses_gateway);
bool smc_ib_is_valid_local_systemid(void);
int smcr_nl_get_device(struct sk_buff *skb, struct netlink_callback *cb);
#endif
+163
View File
@@ -0,0 +1,163 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Shared Memory Communications over RDMA (SMC-R) and RoCE
*
* Definitions for the IPPROTO_SMC (socket related)
*
* Copyright IBM Corp. 2016, 2018
* Copyright (c) 2024, Alibaba Inc.
*
* Author: D. Wythe <alibuda@linux.alibaba.com>
*/
#include <net/protocol.h>
#include <net/sock.h>
#include "smc_inet.h"
#include "smc.h"
static int smc_inet_init_sock(struct sock *sk);
static struct proto smc_inet_prot = {
.name = "INET_SMC",
.owner = THIS_MODULE,
.init = smc_inet_init_sock,
.hash = smc_hash_sk,
.unhash = smc_unhash_sk,
.release_cb = smc_release_cb,
.obj_size = sizeof(struct smc_sock),
.h.smc_hash = &smc_v4_hashinfo,
.slab_flags = SLAB_TYPESAFE_BY_RCU,
};
static const struct proto_ops smc_inet_stream_ops = {
.family = PF_INET,
.owner = THIS_MODULE,
.release = smc_release,
.bind = smc_bind,
.connect = smc_connect,
.socketpair = sock_no_socketpair,
.accept = smc_accept,
.getname = smc_getname,
.poll = smc_poll,
.ioctl = smc_ioctl,
.listen = smc_listen,
.shutdown = smc_shutdown,
.setsockopt = smc_setsockopt,
.getsockopt = smc_getsockopt,
.sendmsg = smc_sendmsg,
.recvmsg = smc_recvmsg,
.mmap = sock_no_mmap,
.splice_read = smc_splice_read,
};
static struct inet_protosw smc_inet_protosw = {
.type = SOCK_STREAM,
.protocol = IPPROTO_SMC,
.prot = &smc_inet_prot,
.ops = &smc_inet_stream_ops,
};
#if IS_ENABLED(CONFIG_IPV6)
struct smc6_sock {
struct smc_sock smc;
struct ipv6_pinfo inet6;
};
static struct proto smc_inet6_prot = {
.name = "INET6_SMC",
.owner = THIS_MODULE,
.init = smc_inet_init_sock,
.hash = smc_hash_sk,
.unhash = smc_unhash_sk,
.release_cb = smc_release_cb,
.obj_size = sizeof(struct smc6_sock),
.h.smc_hash = &smc_v6_hashinfo,
.slab_flags = SLAB_TYPESAFE_BY_RCU,
.ipv6_pinfo_offset = offsetof(struct smc6_sock, inet6),
};
static const struct proto_ops smc_inet6_stream_ops = {
.family = PF_INET6,
.owner = THIS_MODULE,
.release = smc_release,
.bind = smc_bind,
.connect = smc_connect,
.socketpair = sock_no_socketpair,
.accept = smc_accept,
.getname = smc_getname,
.poll = smc_poll,
.ioctl = smc_ioctl,
.listen = smc_listen,
.shutdown = smc_shutdown,
.setsockopt = smc_setsockopt,
.getsockopt = smc_getsockopt,
.sendmsg = smc_sendmsg,
.recvmsg = smc_recvmsg,
.mmap = sock_no_mmap,
.splice_read = smc_splice_read,
};
static struct inet_protosw smc_inet6_protosw = {
.type = SOCK_STREAM,
.protocol = IPPROTO_SMC,
.prot = &smc_inet6_prot,
.ops = &smc_inet6_stream_ops,
};
#endif /* CONFIG_IPV6 */
static int smc_inet_init_sock(struct sock *sk)
{
struct net *net = sock_net(sk);
/* init common smc sock */
smc_sk_init(net, sk, IPPROTO_SMC);
/* create clcsock */
return smc_create_clcsk(net, sk, sk->sk_family);
}
int __init smc_inet_init(void)
{
int rc;
rc = proto_register(&smc_inet_prot, 1);
if (rc) {
pr_err("%s: proto_register smc_inet_prot fails with %d\n",
__func__, rc);
return rc;
}
/* no return value */
inet_register_protosw(&smc_inet_protosw);
#if IS_ENABLED(CONFIG_IPV6)
rc = proto_register(&smc_inet6_prot, 1);
if (rc) {
pr_err("%s: proto_register smc_inet6_prot fails with %d\n",
__func__, rc);
goto out_inet6_prot;
}
rc = inet6_register_protosw(&smc_inet6_protosw);
if (rc) {
pr_err("%s: inet6_register_protosw smc_inet6_protosw fails with %d\n",
__func__, rc);
goto out_inet6_protosw;
}
return rc;
out_inet6_protosw:
proto_unregister(&smc_inet6_prot);
out_inet6_prot:
inet_unregister_protosw(&smc_inet_protosw);
proto_unregister(&smc_inet_prot);
#endif /* CONFIG_IPV6 */
return rc;
}
void smc_inet_exit(void)
{
#if IS_ENABLED(CONFIG_IPV6)
inet6_unregister_protosw(&smc_inet6_protosw);
proto_unregister(&smc_inet6_prot);
#endif /* CONFIG_IPV6 */
inet_unregister_protosw(&smc_inet_protosw);
proto_unregister(&smc_inet_prot);
}
+22
View File
@@ -0,0 +1,22 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Shared Memory Communications over RDMA (SMC-R) and RoCE
*
* Definitions for the IPPROTO_SMC (socket related)
* Copyright IBM Corp. 2016
* Copyright (c) 2024, Alibaba Inc.
*
* Author: D. Wythe <alibuda@linux.alibaba.com>
*/
#ifndef __INET_SMC
#define __INET_SMC
/* Initialize protocol registration on IPPROTO_SMC,
* @return 0 on success
*/
int smc_inet_init(void);
void smc_inet_exit(void);
#endif /* __INET_SMC */
+644
View File
@@ -0,0 +1,644 @@
// SPDX-License-Identifier: GPL-2.0
/* Shared Memory Communications Direct over ISM devices (SMC-D)
*
* Functions for ISM device.
*
* Copyright IBM Corp. 2018
*/
#include <linux/if_vlan.h>
#include <linux/spinlock.h>
#include <linux/mutex.h>
#include <linux/slab.h>
#include <asm/page.h>
#include "smc.h"
#include "smc_core.h"
#include "smc_ism.h"
#include "smc_pnet.h"
#include "smc_netlink.h"
#include "linux/dibs.h"
struct smcd_dev_list smcd_dev_list = {
.list = LIST_HEAD_INIT(smcd_dev_list.list),
.mutex = __MUTEX_INITIALIZER(smcd_dev_list.mutex)
};
static bool smc_ism_v2_capable;
static u8 smc_ism_v2_system_eid[SMC_MAX_EID_LEN];
static void smcd_register_dev(struct dibs_dev *dibs);
static void smcd_unregister_dev(struct dibs_dev *dibs);
static void smcd_handle_event(struct dibs_dev *dibs,
const struct dibs_event *event);
static void smcd_handle_irq(struct dibs_dev *dibs, unsigned int dmbno,
u16 dmbemask);
static struct dibs_client_ops smc_client_ops = {
.add_dev = smcd_register_dev,
.del_dev = smcd_unregister_dev,
.handle_event = smcd_handle_event,
.handle_irq = smcd_handle_irq,
};
static struct dibs_client smc_dibs_client = {
.name = "SMC-D",
.ops = &smc_client_ops,
};
static void smc_ism_create_system_eid(void)
{
struct smc_ism_seid *seid =
(struct smc_ism_seid *)smc_ism_v2_system_eid;
#if IS_ENABLED(CONFIG_S390)
struct cpuid id;
u16 ident_tail;
char tmp[5];
memcpy(seid->seid_string, "IBM-SYSZ-ISMSEID00000000", 24);
get_cpu_id(&id);
ident_tail = (u16)(id.ident & SMC_ISM_IDENT_MASK);
snprintf(tmp, 5, "%04X", ident_tail);
memcpy(seid->serial_number, tmp, 4);
snprintf(tmp, 5, "%04X", id.machine);
memcpy(seid->type, tmp, 4);
#else
memset(seid, 0, SMC_MAX_EID_LEN);
#endif
}
/* Test if an ISM communication is possible - same CPC */
int smc_ism_cantalk(struct smcd_gid *peer_gid, unsigned short vlan_id,
struct smcd_dev *smcd)
{
struct dibs_dev *dibs = smcd->dibs;
uuid_t ism_rgid;
copy_to_dibsgid(&ism_rgid, peer_gid);
return dibs->ops->query_remote_gid(dibs, &ism_rgid, vlan_id ? 1 : 0,
vlan_id);
}
void smc_ism_get_system_eid(u8 **eid)
{
if (!smc_ism_v2_capable)
*eid = NULL;
else
*eid = smc_ism_v2_system_eid;
}
u16 smc_ism_get_chid(struct smcd_dev *smcd)
{
return smcd->dibs->ops->get_fabric_id(smcd->dibs);
}
/* HW supports ISM V2 and thus System EID is defined */
bool smc_ism_is_v2_capable(void)
{
return smc_ism_v2_capable;
}
void smc_ism_set_v2_capable(void)
{
smc_ism_v2_capable = true;
}
/* Set a connection using this DMBE. */
void smc_ism_set_conn(struct smc_connection *conn)
{
unsigned long flags;
spin_lock_irqsave(&conn->lgr->smcd->lock, flags);
conn->lgr->smcd->conn[conn->rmb_desc->sba_idx] = conn;
spin_unlock_irqrestore(&conn->lgr->smcd->lock, flags);
}
/* Unset a connection using this DMBE. */
void smc_ism_unset_conn(struct smc_connection *conn)
{
unsigned long flags;
if (!conn->rmb_desc)
return;
spin_lock_irqsave(&conn->lgr->smcd->lock, flags);
conn->lgr->smcd->conn[conn->rmb_desc->sba_idx] = NULL;
spin_unlock_irqrestore(&conn->lgr->smcd->lock, flags);
}
/* Register a VLAN identifier with the ISM device. Use a reference count
* and add a VLAN identifier only when the first DMB using this VLAN is
* registered.
*/
int smc_ism_get_vlan(struct smcd_dev *smcd, unsigned short vlanid)
{
struct smc_ism_vlanid *new_vlan, *vlan;
unsigned long flags;
int rc = 0;
if (!vlanid) /* No valid vlan id */
return -EINVAL;
if (!smcd->dibs->ops->add_vlan_id)
return -EOPNOTSUPP;
/* create new vlan entry, in case we need it */
new_vlan = kzalloc_obj(*new_vlan);
if (!new_vlan)
return -ENOMEM;
new_vlan->vlanid = vlanid;
refcount_set(&new_vlan->refcnt, 1);
/* if there is an existing entry, increase count and return */
spin_lock_irqsave(&smcd->lock, flags);
list_for_each_entry(vlan, &smcd->vlan, list) {
if (vlan->vlanid == vlanid) {
refcount_inc(&vlan->refcnt);
kfree(new_vlan);
goto out;
}
}
/* no existing entry found.
* add new entry to device; might fail, e.g., if HW limit reached
*/
if (smcd->dibs->ops->add_vlan_id(smcd->dibs, vlanid)) {
kfree(new_vlan);
rc = -EIO;
goto out;
}
list_add_tail(&new_vlan->list, &smcd->vlan);
out:
spin_unlock_irqrestore(&smcd->lock, flags);
return rc;
}
/* Unregister a VLAN identifier with the ISM device. Use a reference count
* and remove a VLAN identifier only when the last DMB using this VLAN is
* unregistered.
*/
int smc_ism_put_vlan(struct smcd_dev *smcd, unsigned short vlanid)
{
struct smc_ism_vlanid *vlan;
unsigned long flags;
bool found = false;
int rc = 0;
if (!vlanid) /* No valid vlan id */
return -EINVAL;
if (!smcd->dibs->ops->del_vlan_id)
return -EOPNOTSUPP;
spin_lock_irqsave(&smcd->lock, flags);
list_for_each_entry(vlan, &smcd->vlan, list) {
if (vlan->vlanid == vlanid) {
if (!refcount_dec_and_test(&vlan->refcnt))
goto out;
found = true;
break;
}
}
if (!found) {
rc = -ENOENT;
goto out; /* VLAN id not in table */
}
/* Found and the last reference just gone */
if (smcd->dibs->ops->del_vlan_id(smcd->dibs, vlanid))
rc = -EIO;
list_del(&vlan->list);
kfree(vlan);
out:
spin_unlock_irqrestore(&smcd->lock, flags);
return rc;
}
void smc_ism_unregister_dmb(struct smcd_dev *smcd,
struct smc_buf_desc *dmb_desc)
{
struct dibs_dmb dmb;
if (!dmb_desc->dma_addr)
return;
memset(&dmb, 0, sizeof(dmb));
dmb.dmb_tok = dmb_desc->token;
dmb.idx = dmb_desc->sba_idx;
dmb.cpu_addr = dmb_desc->cpu_addr;
dmb.dma_addr = dmb_desc->dma_addr;
dmb.dmb_len = dmb_desc->len;
smcd->dibs->ops->unregister_dmb(smcd->dibs, &dmb);
return;
}
int smc_ism_register_dmb(struct smc_link_group *lgr, int dmb_len,
struct smc_buf_desc *dmb_desc)
{
struct dibs_dev *dibs;
struct dibs_dmb dmb;
int rc;
memset(&dmb, 0, sizeof(dmb));
dmb.dmb_len = dmb_len;
dmb.idx = dmb_desc->sba_idx;
dmb.vlan_id = lgr->vlan_id;
copy_to_dibsgid(&dmb.rgid, &lgr->peer_gid);
dibs = lgr->smcd->dibs;
rc = dibs->ops->register_dmb(dibs, &dmb, &smc_dibs_client);
if (!rc) {
dmb_desc->sba_idx = dmb.idx;
dmb_desc->token = dmb.dmb_tok;
dmb_desc->cpu_addr = dmb.cpu_addr;
dmb_desc->dma_addr = dmb.dma_addr;
dmb_desc->len = dmb.dmb_len;
}
return rc;
}
bool smc_ism_support_dmb_nocopy(struct smcd_dev *smcd)
{
/* for now only loopback-ism supports
* merging sndbuf with peer DMB to avoid
* data copies between them.
*/
return (smcd->dibs->ops->support_mmapped_rdmb &&
smcd->dibs->ops->support_mmapped_rdmb(smcd->dibs));
}
int smc_ism_attach_dmb(struct smcd_dev *dev, u64 token,
struct smc_buf_desc *dmb_desc)
{
struct dibs_dmb dmb;
int rc = 0;
if (!dev->dibs->ops->attach_dmb)
return -EINVAL;
memset(&dmb, 0, sizeof(dmb));
dmb.dmb_tok = token;
rc = dev->dibs->ops->attach_dmb(dev->dibs, &dmb);
if (!rc) {
dmb_desc->sba_idx = dmb.idx;
dmb_desc->token = dmb.dmb_tok;
dmb_desc->cpu_addr = dmb.cpu_addr;
dmb_desc->dma_addr = dmb.dma_addr;
dmb_desc->len = dmb.dmb_len;
dmb_desc->is_attached = true;
}
return rc;
}
int smc_ism_detach_dmb(struct smcd_dev *dev, u64 token)
{
if (!dev->dibs->ops->detach_dmb)
return -EINVAL;
return dev->dibs->ops->detach_dmb(dev->dibs, token);
}
static int smc_nl_handle_smcd_dev(struct smcd_dev *smcd,
struct sk_buff *skb,
struct netlink_callback *cb)
{
char smc_pnet[SMC_MAX_PNETID_LEN + 1];
struct smc_pci_dev smc_pci_dev;
struct nlattr *port_attrs;
struct dibs_dev *dibs;
struct nlattr *attrs;
int use_cnt = 0;
void *nlh;
dibs = smcd->dibs;
nlh = genlmsg_put(skb, NETLINK_CB(cb->skb).portid, cb->nlh->nlmsg_seq,
&smc_gen_nl_family, NLM_F_MULTI,
SMC_NETLINK_GET_DEV_SMCD);
if (!nlh)
goto errmsg;
attrs = nla_nest_start(skb, SMC_GEN_DEV_SMCD);
if (!attrs)
goto errout;
use_cnt = atomic_read(&smcd->lgr_cnt);
if (nla_put_u32(skb, SMC_NLA_DEV_USE_CNT, use_cnt))
goto errattr;
if (nla_put_u8(skb, SMC_NLA_DEV_IS_CRIT, use_cnt > 0))
goto errattr;
memset(&smc_pci_dev, 0, sizeof(smc_pci_dev));
smc_set_pci_values(to_pci_dev(dibs->dev.parent), &smc_pci_dev);
if (nla_put_u32(skb, SMC_NLA_DEV_PCI_FID, smc_pci_dev.pci_fid))
goto errattr;
if (nla_put_u16(skb, SMC_NLA_DEV_PCI_CHID, smc_pci_dev.pci_pchid))
goto errattr;
if (nla_put_u16(skb, SMC_NLA_DEV_PCI_VENDOR, smc_pci_dev.pci_vendor))
goto errattr;
if (nla_put_u16(skb, SMC_NLA_DEV_PCI_DEVICE, smc_pci_dev.pci_device))
goto errattr;
if (nla_put_string(skb, SMC_NLA_DEV_PCI_ID, smc_pci_dev.pci_id))
goto errattr;
port_attrs = nla_nest_start(skb, SMC_NLA_DEV_PORT);
if (!port_attrs)
goto errattr;
if (nla_put_u8(skb, SMC_NLA_DEV_PORT_PNET_USR, smcd->pnetid_by_user))
goto errportattr;
memcpy(smc_pnet, smcd->pnetid, SMC_MAX_PNETID_LEN);
smc_pnet[SMC_MAX_PNETID_LEN] = 0;
if (nla_put_string(skb, SMC_NLA_DEV_PORT_PNETID, smc_pnet))
goto errportattr;
nla_nest_end(skb, port_attrs);
nla_nest_end(skb, attrs);
genlmsg_end(skb, nlh);
return 0;
errportattr:
nla_nest_cancel(skb, port_attrs);
errattr:
nla_nest_cancel(skb, attrs);
errout:
nlmsg_cancel(skb, nlh);
errmsg:
return -EMSGSIZE;
}
static void smc_nl_prep_smcd_dev(struct smcd_dev_list *dev_list,
struct sk_buff *skb,
struct netlink_callback *cb)
{
struct smc_nl_dmp_ctx *cb_ctx = smc_nl_dmp_ctx(cb);
int snum = cb_ctx->pos[0];
struct smcd_dev *smcd;
int num = 0;
mutex_lock(&dev_list->mutex);
list_for_each_entry(smcd, &dev_list->list, list) {
if (num < snum)
goto next;
if (smc_ism_is_loopback(smcd->dibs))
goto next;
if (smc_nl_handle_smcd_dev(smcd, skb, cb))
goto errout;
next:
num++;
}
errout:
mutex_unlock(&dev_list->mutex);
cb_ctx->pos[0] = num;
}
int smcd_nl_get_device(struct sk_buff *skb, struct netlink_callback *cb)
{
smc_nl_prep_smcd_dev(&smcd_dev_list, skb, cb);
return skb->len;
}
struct smc_ism_event_work {
struct work_struct work;
struct smcd_dev *smcd;
struct dibs_event event;
};
#define ISM_EVENT_REQUEST 0x0001
#define ISM_EVENT_RESPONSE 0x0002
#define ISM_EVENT_REQUEST_IR 0x00000001
#define ISM_EVENT_CODE_SHUTDOWN 0x80
#define ISM_EVENT_CODE_TESTLINK 0x83
union smcd_sw_event_info {
u64 info;
struct {
u8 uid[SMC_LGR_ID_SIZE];
unsigned short vlan_id;
u16 code;
};
};
static void smcd_handle_sw_event(struct smc_ism_event_work *wrk)
{
struct dibs_dev *dibs = wrk->smcd->dibs;
union smcd_sw_event_info ev_info;
struct smcd_gid peer_gid;
uuid_t ism_rgid;
copy_to_smcdgid(&peer_gid, &wrk->event.gid);
ev_info.info = wrk->event.data;
switch (wrk->event.subtype) {
case ISM_EVENT_CODE_SHUTDOWN: /* Peer shut down DMBs */
smc_smcd_terminate(wrk->smcd, &peer_gid, ev_info.vlan_id);
break;
case ISM_EVENT_CODE_TESTLINK: /* Activity timer */
if (ev_info.code == ISM_EVENT_REQUEST &&
dibs->ops->signal_event) {
ev_info.code = ISM_EVENT_RESPONSE;
copy_to_dibsgid(&ism_rgid, &peer_gid);
dibs->ops->signal_event(dibs, &ism_rgid,
ISM_EVENT_REQUEST_IR,
ISM_EVENT_CODE_TESTLINK,
ev_info.info);
}
break;
}
}
/* worker for SMC-D events */
static void smc_ism_event_work(struct work_struct *work)
{
struct smc_ism_event_work *wrk =
container_of(work, struct smc_ism_event_work, work);
struct smcd_gid smcd_gid;
copy_to_smcdgid(&smcd_gid, &wrk->event.gid);
switch (wrk->event.type) {
case DIBS_DEV_EVENT: /* GID event, token is peer GID */
smc_smcd_terminate(wrk->smcd, &smcd_gid, VLAN_VID_MASK);
break;
case DIBS_BUF_EVENT:
break;
case DIBS_SW_EVENT: /* Software defined event */
smcd_handle_sw_event(wrk);
break;
}
kfree(wrk);
}
static struct smcd_dev *smcd_alloc_dev(const char *name, int max_dmbs)
{
struct smcd_dev *smcd;
smcd = kzalloc_flex(*smcd, conn, max_dmbs);
if (!smcd)
return NULL;
smcd->event_wq = alloc_ordered_workqueue("ism_evt_wq-%s)",
WQ_MEM_RECLAIM, name);
if (!smcd->event_wq)
goto free_smcd;
spin_lock_init(&smcd->lock);
spin_lock_init(&smcd->lgr_lock);
INIT_LIST_HEAD(&smcd->vlan);
INIT_LIST_HEAD(&smcd->lgr_list);
init_waitqueue_head(&smcd->lgrs_deleted);
return smcd;
free_smcd:
kfree(smcd);
return NULL;
}
static void smcd_register_dev(struct dibs_dev *dibs)
{
struct smcd_dev *smcd, *fentry;
int max_dmbs;
max_dmbs = dibs->ops->max_dmbs();
smcd = smcd_alloc_dev(dev_name(&dibs->dev), max_dmbs);
if (!smcd)
return;
smcd->dibs = dibs;
dibs_set_priv(dibs, &smc_dibs_client, smcd);
if (smc_pnetid_by_dev_port(dibs->dev.parent, 0, smcd->pnetid))
smc_pnetid_by_table_smcd(smcd);
if (smc_ism_is_loopback(dibs) ||
(dibs->ops->add_vlan_id &&
!dibs->ops->add_vlan_id(dibs, ISM_RESERVED_VLANID))) {
smc_ism_set_v2_capable();
}
mutex_lock(&smcd_dev_list.mutex);
/* sort list:
* - devices without pnetid before devices with pnetid;
* - loopback-ism always at the very beginning;
*/
if (!smcd->pnetid[0]) {
fentry = list_first_entry_or_null(&smcd_dev_list.list,
struct smcd_dev, list);
if (fentry && smc_ism_is_loopback(fentry->dibs))
list_add(&smcd->list, &fentry->list);
else
list_add(&smcd->list, &smcd_dev_list.list);
} else {
list_add_tail(&smcd->list, &smcd_dev_list.list);
}
mutex_unlock(&smcd_dev_list.mutex);
if (smc_pnet_is_pnetid_set(smcd->pnetid))
pr_warn_ratelimited("smc: adding smcd device %s with pnetid %.16s%s\n",
dev_name(&dibs->dev), smcd->pnetid,
smcd->pnetid_by_user ?
" (user defined)" :
"");
else
pr_warn_ratelimited("smc: adding smcd device %s without pnetid\n",
dev_name(&dibs->dev));
return;
}
static void smcd_unregister_dev(struct dibs_dev *dibs)
{
struct smcd_dev *smcd = dibs_get_priv(dibs, &smc_dibs_client);
pr_warn_ratelimited("smc: removing smcd device %s\n",
dev_name(&dibs->dev));
smcd->going_away = 1;
smc_smcd_terminate_all(smcd);
mutex_lock(&smcd_dev_list.mutex);
list_del_init(&smcd->list);
mutex_unlock(&smcd_dev_list.mutex);
destroy_workqueue(smcd->event_wq);
kfree(smcd);
}
/* SMCD Device event handler. Called from ISM device interrupt handler.
* Parameters are ism device pointer,
* - event->type (0 --> DMB, 1 --> GID),
* - event->code (event code),
* - event->tok (either DMB token when event type 0, or GID when event type 1)
* - event->time (time of day)
* - event->info (debug info).
*
* Context:
* - Function called in IRQ context from ISM device driver event handler.
*/
static void smcd_handle_event(struct dibs_dev *dibs,
const struct dibs_event *event)
{
struct smcd_dev *smcd = dibs_get_priv(dibs, &smc_dibs_client);
struct smc_ism_event_work *wrk;
if (smcd->going_away)
return;
/* copy event to event work queue, and let it be handled there */
wrk = kmalloc_obj(*wrk, GFP_ATOMIC);
if (!wrk)
return;
INIT_WORK(&wrk->work, smc_ism_event_work);
wrk->smcd = smcd;
wrk->event = *event;
queue_work(smcd->event_wq, &wrk->work);
}
/* SMCD Device interrupt handler. Called from ISM device interrupt handler.
* Parameters are the ism device pointer, DMB number, and the DMBE bitmask.
* Find the connection and schedule the tasklet for this connection.
*
* Context:
* - Function called in IRQ context from ISM device driver IRQ handler.
*/
static void smcd_handle_irq(struct dibs_dev *dibs, unsigned int dmbno,
u16 dmbemask)
{
struct smcd_dev *smcd = dibs_get_priv(dibs, &smc_dibs_client);
struct smc_connection *conn = NULL;
unsigned long flags;
spin_lock_irqsave(&smcd->lock, flags);
conn = smcd->conn[dmbno];
if (conn && !conn->killed)
tasklet_schedule(&conn->rx_tsklet);
spin_unlock_irqrestore(&smcd->lock, flags);
}
int smc_ism_signal_shutdown(struct smc_link_group *lgr)
{
int rc = 0;
union smcd_sw_event_info ev_info;
uuid_t ism_rgid;
if (lgr->peer_shutdown)
return 0;
if (!lgr->smcd->dibs->ops->signal_event)
return 0;
memcpy(ev_info.uid, lgr->id, SMC_LGR_ID_SIZE);
ev_info.vlan_id = lgr->vlan_id;
ev_info.code = ISM_EVENT_REQUEST;
copy_to_dibsgid(&ism_rgid, &lgr->peer_gid);
rc = lgr->smcd->dibs->ops->signal_event(lgr->smcd->dibs, &ism_rgid,
ISM_EVENT_REQUEST_IR,
ISM_EVENT_CODE_SHUTDOWN,
ev_info.info);
return rc;
}
int smc_ism_init(void)
{
int rc = 0;
smc_ism_v2_capable = false;
smc_ism_create_system_eid();
rc = dibs_register_client(&smc_dibs_client);
return rc;
}
void smc_ism_exit(void)
{
dibs_unregister_client(&smc_dibs_client);
}
+123
View File
@@ -0,0 +1,123 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* Shared Memory Communications Direct over ISM devices (SMC-D)
*
* SMC-D ISM device structure definitions.
*
* Copyright IBM Corp. 2018
*/
#ifndef SMCD_ISM_H
#define SMCD_ISM_H
#include <linux/uio.h>
#include <linux/types.h>
#include <linux/mutex.h>
#include <linux/dibs.h>
#include "smc.h"
#define SMC_EMULATED_ISM_CHID_MASK 0xFF00
#define SMC_ISM_IDENT_MASK 0x00FFFF
struct smcd_dev_list { /* List of SMCD devices */
struct list_head list;
struct mutex mutex; /* Protects list of devices */
};
extern struct smcd_dev_list smcd_dev_list; /* list of smcd devices */
struct smc_ism_vlanid { /* VLAN id set on ISM device */
struct list_head list;
unsigned short vlanid; /* Vlan id */
refcount_t refcnt; /* Reference count */
};
struct smc_ism_seid {
u8 seid_string[24];
u8 serial_number[4];
u8 type[4];
};
struct smcd_dev;
int smc_ism_cantalk(struct smcd_gid *peer_gid, unsigned short vlan_id,
struct smcd_dev *dev);
void smc_ism_set_conn(struct smc_connection *conn);
void smc_ism_unset_conn(struct smc_connection *conn);
int smc_ism_get_vlan(struct smcd_dev *dev, unsigned short vlan_id);
int smc_ism_put_vlan(struct smcd_dev *dev, unsigned short vlan_id);
int smc_ism_register_dmb(struct smc_link_group *lgr, int buf_size,
struct smc_buf_desc *dmb_desc);
void smc_ism_unregister_dmb(struct smcd_dev *dev,
struct smc_buf_desc *dmb_desc);
bool smc_ism_support_dmb_nocopy(struct smcd_dev *smcd);
int smc_ism_attach_dmb(struct smcd_dev *dev, u64 token,
struct smc_buf_desc *dmb_desc);
int smc_ism_detach_dmb(struct smcd_dev *dev, u64 token);
int smc_ism_signal_shutdown(struct smc_link_group *lgr);
void smc_ism_get_system_eid(u8 **eid);
u16 smc_ism_get_chid(struct smcd_dev *dev);
bool smc_ism_is_v2_capable(void);
void smc_ism_set_v2_capable(void);
int smc_ism_init(void);
void smc_ism_exit(void);
int smcd_nl_get_device(struct sk_buff *skb, struct netlink_callback *cb);
static inline int smc_ism_write(struct smcd_dev *smcd, u64 dmb_tok,
unsigned int idx, bool sf, unsigned int offset,
void *data, size_t len)
{
int rc;
rc = smcd->dibs->ops->move_data(smcd->dibs, dmb_tok, idx, sf, offset,
data, len);
return rc < 0 ? rc : 0;
}
static inline bool __smc_ism_is_emulated(u16 chid)
{
/* CHIDs in range of 0xFF00 to 0xFFFF are reserved
* for Emulated-ISM device.
*
* loopback-ism: 0xFFFF
* virtio-ism: 0xFF00 ~ 0xFFFE
*/
return ((chid & 0xFF00) == 0xFF00);
}
static inline bool smc_ism_is_emulated(struct smcd_dev *smcd)
{
u16 chid = smcd->dibs->ops->get_fabric_id(smcd->dibs);
return __smc_ism_is_emulated(chid);
}
static inline bool smc_ism_is_loopback(struct dibs_dev *dibs)
{
return (dibs->ops->get_fabric_id(dibs) == DIBS_LOOPBACK_FABRIC);
}
static inline void copy_to_smcdgid(struct smcd_gid *sgid, uuid_t *dibs_gid)
{
__be64 temp;
memcpy(&temp, dibs_gid, sizeof(sgid->gid));
sgid->gid = ntohll(temp);
memcpy(&temp, (uint8_t *)dibs_gid + sizeof(sgid->gid),
sizeof(sgid->gid_ext));
sgid->gid_ext = ntohll(temp);
}
static inline void copy_to_dibsgid(uuid_t *dibs_gid, struct smcd_gid *sgid)
{
__be64 temp;
temp = htonll(sgid->gid);
memcpy(dibs_gid, &temp, sizeof(sgid->gid));
temp = htonll(sgid->gid_ext);
memcpy((uint8_t *)dibs_gid + sizeof(sgid->gid), &temp,
sizeof(sgid->gid_ext));
}
#endif
File diff suppressed because it is too large Load Diff
+120
View File
@@ -0,0 +1,120 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Shared Memory Communications over RDMA (SMC-R) and RoCE
*
* Definitions for LLC (link layer control) message handling
*
* Copyright IBM Corp. 2016
*
* Author(s): Klaus Wacker <Klaus.Wacker@de.ibm.com>
* Ursula Braun <ubraun@linux.vnet.ibm.com>
*/
#ifndef SMC_LLC_H
#define SMC_LLC_H
#include "smc_wr.h"
#define SMC_LLC_FLAG_RESP 0x80
#define SMC_LLC_WAIT_FIRST_TIME (5 * HZ)
#define SMC_LLC_WAIT_TIME (2 * HZ)
#define SMC_LLC_TESTLINK_DEFAULT_TIME (30 * HZ)
enum smc_llc_reqresp {
SMC_LLC_REQ,
SMC_LLC_RESP
};
enum smc_llc_msg_type {
SMC_LLC_CONFIRM_LINK = 0x01,
SMC_LLC_ADD_LINK = 0x02,
SMC_LLC_ADD_LINK_CONT = 0x03,
SMC_LLC_DELETE_LINK = 0x04,
SMC_LLC_REQ_ADD_LINK = 0x05,
SMC_LLC_CONFIRM_RKEY = 0x06,
SMC_LLC_TEST_LINK = 0x07,
SMC_LLC_CONFIRM_RKEY_CONT = 0x08,
SMC_LLC_DELETE_RKEY = 0x09,
/* V2 types */
SMC_LLC_CONFIRM_LINK_V2 = 0x21,
SMC_LLC_ADD_LINK_V2 = 0x22,
SMC_LLC_DELETE_LINK_V2 = 0x24,
SMC_LLC_REQ_ADD_LINK_V2 = 0x25,
SMC_LLC_CONFIRM_RKEY_V2 = 0x26,
SMC_LLC_TEST_LINK_V2 = 0x27,
SMC_LLC_DELETE_RKEY_V2 = 0x29,
};
#define smc_link_downing(state) \
(cmpxchg(state, SMC_LNK_ACTIVE, SMC_LNK_INACTIVE) == SMC_LNK_ACTIVE)
/* LLC DELETE LINK Request Reason Codes */
#define SMC_LLC_DEL_LOST_PATH 0x00010000
#define SMC_LLC_DEL_OP_INIT_TERM 0x00020000
#define SMC_LLC_DEL_PROG_INIT_TERM 0x00030000
#define SMC_LLC_DEL_PROT_VIOL 0x00040000
#define SMC_LLC_DEL_NO_ASYM_NEEDED 0x00050000
/* LLC DELETE LINK Response Reason Codes */
#define SMC_LLC_DEL_NOLNK 0x00100000 /* Unknown Link ID (no link) */
#define SMC_LLC_DEL_NOLGR 0x00200000 /* Unknown Link Group */
/* returns a usable link of the link group, or NULL */
static inline struct smc_link *smc_llc_usable_link(struct smc_link_group *lgr)
{
int i;
for (i = 0; i < SMC_LINKS_PER_LGR_MAX; i++)
if (smc_link_usable(&lgr->lnk[i]))
return &lgr->lnk[i];
return NULL;
}
/* set the termination reason code for the link group */
static inline void smc_llc_set_termination_rsn(struct smc_link_group *lgr,
u32 rsn)
{
if (!lgr->llc_termination_rsn)
lgr->llc_termination_rsn = rsn;
}
/* transmit */
int smc_llc_send_confirm_link(struct smc_link *lnk,
enum smc_llc_reqresp reqresp);
int smc_llc_send_add_link(struct smc_link *link, u8 mac[], u8 gid[],
struct smc_link *link_new,
enum smc_llc_reqresp reqresp);
int smc_llc_send_delete_link(struct smc_link *link, u8 link_del_id,
enum smc_llc_reqresp reqresp, bool orderly,
u32 reason);
void smc_llc_srv_delete_link_local(struct smc_link *link, u8 del_link_id);
void smc_llc_lgr_init(struct smc_link_group *lgr, struct smc_sock *smc);
void smc_llc_lgr_clear(struct smc_link_group *lgr);
int smc_llc_link_init(struct smc_link *link);
void smc_llc_link_active(struct smc_link *link);
void smc_llc_link_clear(struct smc_link *link, bool log);
int smc_llc_do_confirm_rkey(struct smc_link *send_link,
struct smc_buf_desc *rmb_desc);
int smc_llc_do_delete_rkey(struct smc_link_group *lgr,
struct smc_buf_desc *rmb_desc);
int smc_llc_flow_initiate(struct smc_link_group *lgr,
enum smc_llc_flowtype type);
void smc_llc_flow_stop(struct smc_link_group *lgr, struct smc_llc_flow *flow);
int smc_llc_eval_conf_link(struct smc_llc_qentry *qentry,
enum smc_llc_reqresp type);
void smc_llc_link_set_uid(struct smc_link *link);
void smc_llc_save_peer_uid(struct smc_llc_qentry *qentry);
struct smc_llc_qentry *smc_llc_wait(struct smc_link_group *lgr,
struct smc_link *lnk,
int time_out, u8 exp_msg);
struct smc_llc_qentry *smc_llc_flow_qentry_clr(struct smc_llc_flow *flow);
void smc_llc_flow_qentry_del(struct smc_llc_flow *flow);
void smc_llc_send_link_delete_all(struct smc_link_group *lgr, bool ord,
u32 rsn);
int smc_llc_cli_add_link(struct smc_link *link, struct smc_llc_qentry *qentry);
int smc_llc_srv_add_link(struct smc_link *link,
struct smc_llc_qentry *req_qentry);
void smc_llc_add_link_local(struct smc_link *link);
int smc_llc_init(void) __init;
#endif /* SMC_LLC_H */
+157
View File
@@ -0,0 +1,157 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Shared Memory Communications over RDMA (SMC-R) and RoCE
*
* Generic netlink support functions to interact with SMC module
*
* Copyright IBM Corp. 2020
*
* Author(s): Guvenc Gulce <guvenc@linux.ibm.com>
*/
#include <linux/module.h>
#include <linux/list.h>
#include <linux/ctype.h>
#include <linux/mutex.h>
#include <linux/if.h>
#include <linux/smc.h>
#include "smc_core.h"
#include "smc_ism.h"
#include "smc_ib.h"
#include "smc_clc.h"
#include "smc_stats.h"
#include "smc_netlink.h"
const struct nla_policy
smc_gen_ueid_policy[SMC_NLA_EID_TABLE_MAX + 1] = {
[SMC_NLA_EID_TABLE_UNSPEC] = { .type = NLA_UNSPEC },
[SMC_NLA_EID_TABLE_ENTRY] = { .type = NLA_STRING,
.len = SMC_MAX_EID_LEN,
},
};
#define SMC_CMD_MAX_ATTR 1
/* SMC_GENL generic netlink operation definition */
static const struct genl_ops smc_gen_nl_ops[] = {
{
.cmd = SMC_NETLINK_GET_SYS_INFO,
/* can be retrieved by unprivileged users */
.dumpit = smc_nl_get_sys_info,
},
{
.cmd = SMC_NETLINK_GET_LGR_SMCR,
/* can be retrieved by unprivileged users */
.dumpit = smcr_nl_get_lgr,
},
{
.cmd = SMC_NETLINK_GET_LINK_SMCR,
/* can be retrieved by unprivileged users */
.dumpit = smcr_nl_get_link,
},
{
.cmd = SMC_NETLINK_GET_LGR_SMCD,
/* can be retrieved by unprivileged users */
.dumpit = smcd_nl_get_lgr,
},
{
.cmd = SMC_NETLINK_GET_DEV_SMCD,
/* can be retrieved by unprivileged users */
.dumpit = smcd_nl_get_device,
},
{
.cmd = SMC_NETLINK_GET_DEV_SMCR,
/* can be retrieved by unprivileged users */
.dumpit = smcr_nl_get_device,
},
{
.cmd = SMC_NETLINK_GET_STATS,
/* can be retrieved by unprivileged users */
.dumpit = smc_nl_get_stats,
},
{
.cmd = SMC_NETLINK_GET_FBACK_STATS,
/* can be retrieved by unprivileged users */
.dumpit = smc_nl_get_fback_stats,
},
{
.cmd = SMC_NETLINK_DUMP_UEID,
/* can be retrieved by unprivileged users */
.dumpit = smc_nl_dump_ueid,
},
{
.cmd = SMC_NETLINK_ADD_UEID,
.flags = GENL_ADMIN_PERM,
.doit = smc_nl_add_ueid,
.policy = smc_gen_ueid_policy,
},
{
.cmd = SMC_NETLINK_REMOVE_UEID,
.flags = GENL_ADMIN_PERM,
.doit = smc_nl_remove_ueid,
.policy = smc_gen_ueid_policy,
},
{
.cmd = SMC_NETLINK_FLUSH_UEID,
.flags = GENL_ADMIN_PERM,
.doit = smc_nl_flush_ueid,
},
{
.cmd = SMC_NETLINK_DUMP_SEID,
/* can be retrieved by unprivileged users */
.dumpit = smc_nl_dump_seid,
},
{
.cmd = SMC_NETLINK_ENABLE_SEID,
.flags = GENL_ADMIN_PERM,
.doit = smc_nl_enable_seid,
},
{
.cmd = SMC_NETLINK_DISABLE_SEID,
.flags = GENL_ADMIN_PERM,
.doit = smc_nl_disable_seid,
},
{
.cmd = SMC_NETLINK_DUMP_HS_LIMITATION,
/* can be retrieved by unprivileged users */
.dumpit = smc_nl_dump_hs_limitation,
},
{
.cmd = SMC_NETLINK_ENABLE_HS_LIMITATION,
.flags = GENL_ADMIN_PERM,
.doit = smc_nl_enable_hs_limitation,
},
{
.cmd = SMC_NETLINK_DISABLE_HS_LIMITATION,
.flags = GENL_ADMIN_PERM,
.doit = smc_nl_disable_hs_limitation,
},
};
static const struct nla_policy smc_gen_nl_policy[2] = {
[SMC_CMD_MAX_ATTR] = { .type = NLA_REJECT, },
};
/* SMC_GENL family definition */
struct genl_family smc_gen_nl_family __ro_after_init = {
.hdrsize = 0,
.name = SMC_GENL_FAMILY_NAME,
.version = SMC_GENL_FAMILY_VERSION,
.maxattr = SMC_CMD_MAX_ATTR,
.policy = smc_gen_nl_policy,
.netnsok = true,
.module = THIS_MODULE,
.ops = smc_gen_nl_ops,
.n_ops = ARRAY_SIZE(smc_gen_nl_ops),
.resv_start_op = SMC_NETLINK_DISABLE_HS_LIMITATION + 1,
};
int __init smc_nl_init(void)
{
return genl_register_family(&smc_gen_nl_family);
}
void smc_nl_exit(void)
{
genl_unregister_family(&smc_gen_nl_family);
}
+34
View File
@@ -0,0 +1,34 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Shared Memory Communications over RDMA (SMC-R) and RoCE
*
* SMC Generic netlink operations
*
* Copyright IBM Corp. 2020
*
* Author(s): Guvenc Gulce <guvenc@linux.ibm.com>
*/
#ifndef _SMC_NETLINK_H
#define _SMC_NETLINK_H
#include <net/netlink.h>
#include <net/genetlink.h>
extern struct genl_family smc_gen_nl_family;
extern const struct nla_policy smc_gen_ueid_policy[];
struct smc_nl_dmp_ctx {
int pos[3];
};
static inline struct smc_nl_dmp_ctx *smc_nl_dmp_ctx(struct netlink_callback *c)
{
return (struct smc_nl_dmp_ctx *)c->ctx;
}
int smc_nl_init(void) __init;
void smc_nl_exit(void);
#endif
+21
View File
@@ -0,0 +1,21 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* Shared Memory Communications
*
* Network namespace definitions.
*
* Copyright IBM Corp. 2018
*/
#ifndef SMC_NETNS_H
#define SMC_NETNS_H
#include "smc_pnet.h"
extern unsigned int smc_net_id;
/* per-network namespace private data */
struct smc_net {
struct smc_pnettable pnettable;
struct smc_pnetids_ndev pnetids_ndev;
};
#endif
File diff suppressed because it is too large Load Diff
+70
View File
@@ -0,0 +1,70 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Shared Memory Communications over RDMA (SMC-R) and RoCE
*
* PNET table queries
*
* Copyright IBM Corp. 2016
*
* Author(s): Thomas Richter <tmricht@linux.vnet.ibm.com>
*/
#ifndef _SMC_PNET_H
#define _SMC_PNET_H
#include <net/smc.h>
#if IS_ENABLED(CONFIG_HAVE_PNETID)
#include <asm/pnet.h>
#endif
struct smc_ib_device;
struct smcd_dev;
struct smc_init_info;
struct smc_link_group;
/**
* struct smc_pnettable - SMC PNET table anchor
* @lock: Lock for list action
* @pnetlist: List of PNETIDs
*/
struct smc_pnettable {
struct mutex lock;
struct list_head pnetlist;
};
struct smc_pnetids_ndev { /* list of pnetids for net devices in UP state*/
struct list_head list;
rwlock_t lock;
};
struct smc_pnetids_ndev_entry {
struct list_head list;
u8 pnetid[SMC_MAX_PNETID_LEN];
refcount_t refcnt;
};
static inline int smc_pnetid_by_dev_port(struct device *dev,
unsigned short port, u8 *pnetid)
{
#if IS_ENABLED(CONFIG_HAVE_PNETID)
return pnet_id_by_dev_port(dev, port, pnetid);
#else
return -ENOENT;
#endif
}
int smc_pnet_init(void) __init;
int smc_pnet_net_init(struct net *net);
void smc_pnet_exit(void);
void smc_pnet_net_exit(struct net *net);
void smc_pnet_find_roce_resource(struct sock *sk, struct smc_init_info *ini);
void smc_pnet_find_ism_resource(struct sock *sk, struct smc_init_info *ini);
int smc_pnetid_by_table_ib(struct smc_ib_device *smcibdev, u8 ib_port);
int smc_pnetid_by_table_smcd(struct smcd_dev *smcd);
void smc_pnet_find_alt_roce(struct smc_link_group *lgr,
struct smc_init_info *ini,
struct smc_ib_device *known_dev);
bool smc_pnet_is_ndev_pnetid(struct net *net, u8 *pnetid);
bool smc_pnet_is_pnetid_set(u8 *pnetid);
#endif
+528
View File
@@ -0,0 +1,528 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Shared Memory Communications over RDMA (SMC-R) and RoCE
*
* Manage RMBE
* copy new RMBE data into user space
*
* Copyright IBM Corp. 2016
*
* Author(s): Ursula Braun <ubraun@linux.vnet.ibm.com>
*/
#include <linux/net.h>
#include <linux/rcupdate.h>
#include <linux/sched/signal.h>
#include <linux/splice.h>
#include <net/sock.h>
#include <trace/events/sock.h>
#include "smc.h"
#include "smc_core.h"
#include "smc_cdc.h"
#include "smc_tx.h" /* smc_tx_consumer_update() */
#include "smc_rx.h"
#include "smc_stats.h"
#include "smc_tracepoint.h"
/* callback implementation to wakeup consumers blocked with smc_rx_wait().
* indirectly called by smc_cdc_msg_recv_action().
*/
static void smc_rx_wake_up(struct sock *sk)
{
struct socket_wq *wq;
trace_sk_data_ready(sk);
/* derived from sock_def_readable() */
/* called already in smc_listen_work() */
rcu_read_lock();
wq = rcu_dereference(sk->sk_wq);
if (skwq_has_sleeper(wq))
wake_up_interruptible_sync_poll(&wq->wait, EPOLLIN | EPOLLPRI |
EPOLLRDNORM | EPOLLRDBAND);
sk_wake_async_rcu(sk, SOCK_WAKE_WAITD, POLL_IN);
if ((sk->sk_shutdown == SHUTDOWN_MASK) ||
(sk->sk_state == SMC_CLOSED))
sk_wake_async_rcu(sk, SOCK_WAKE_WAITD, POLL_HUP);
rcu_read_unlock();
}
/* Update consumer cursor
* @conn connection to update
* @cons consumer cursor
* @len number of Bytes consumed
* Returns:
* 1 if we should end our receive, 0 otherwise
*/
static int smc_rx_update_consumer(struct smc_sock *smc,
union smc_host_cursor cons, size_t len)
{
struct smc_connection *conn = &smc->conn;
struct sock *sk = &smc->sk;
bool force = false;
int diff, rc = 0;
smc_curs_add(conn->rmb_desc->len, &cons, len);
/* did we process urgent data? */
if (conn->urg_state == SMC_URG_VALID || conn->urg_rx_skip_pend) {
diff = smc_curs_comp(conn->rmb_desc->len, &cons,
&conn->urg_curs);
if (sock_flag(sk, SOCK_URGINLINE)) {
if (diff == 0) {
force = true;
rc = 1;
conn->urg_state = SMC_URG_READ;
}
} else {
if (diff == 1) {
/* skip urgent byte */
force = true;
smc_curs_add(conn->rmb_desc->len, &cons, 1);
conn->urg_rx_skip_pend = false;
} else if (diff < -1)
/* we read past urgent byte */
conn->urg_state = SMC_URG_READ;
}
}
smc_curs_copy(&conn->local_tx_ctrl.cons, &cons, conn);
/* send consumer cursor update if required */
/* similar to advertising new TCP rcv_wnd if required */
smc_tx_consumer_update(conn, force);
return rc;
}
static void smc_rx_update_cons(struct smc_sock *smc, size_t len)
{
struct smc_connection *conn = &smc->conn;
union smc_host_cursor cons;
smc_curs_copy(&cons, &conn->local_tx_ctrl.cons, conn);
smc_rx_update_consumer(smc, cons, len);
}
struct smc_spd_priv {
struct smc_sock *smc;
size_t len;
};
static void smc_rx_pipe_buf_release(struct pipe_inode_info *pipe,
struct pipe_buffer *buf)
{
struct smc_spd_priv *priv = (struct smc_spd_priv *)buf->private;
struct smc_sock *smc = priv->smc;
struct smc_connection *conn;
struct sock *sk = &smc->sk;
if (sk->sk_state == SMC_CLOSED ||
sk->sk_state == SMC_PEERFINCLOSEWAIT ||
sk->sk_state == SMC_APPFINCLOSEWAIT)
goto out;
conn = &smc->conn;
lock_sock(sk);
smc_rx_update_cons(smc, priv->len);
release_sock(sk);
if (atomic_sub_and_test(priv->len, &conn->splice_pending))
smc_rx_wake_up(sk);
out:
kfree(priv);
put_page(buf->page);
sock_put(sk);
}
static bool smc_rx_pipe_buf_get(struct pipe_inode_info *pipe,
struct pipe_buffer *buf)
{
/* smc_spd_priv in buf->private is not shareable; disallow cloning. */
return false;
}
static const struct pipe_buf_operations smc_pipe_ops = {
.release = smc_rx_pipe_buf_release,
.get = smc_rx_pipe_buf_get,
};
static void smc_rx_spd_release(struct splice_pipe_desc *spd,
unsigned int i)
{
struct smc_spd_priv *priv = (struct smc_spd_priv *)spd->partial[i].private;
struct sock *sk = &priv->smc->sk;
kfree(priv);
put_page(spd->pages[i]);
sock_put(sk);
}
static int smc_rx_splice(struct pipe_inode_info *pipe, char *src, size_t len,
struct smc_sock *smc)
{
struct smc_link_group *lgr = smc->conn.lgr;
int offset = offset_in_page(src);
struct partial_page *partial;
struct splice_pipe_desc spd;
struct smc_spd_priv **priv;
struct page **pages;
int bytes, nr_pages;
int i;
nr_pages = !lgr->is_smcd && smc->conn.rmb_desc->is_vm ?
PAGE_ALIGN(len + offset) / PAGE_SIZE : 1;
pages = kzalloc_objs(*pages, nr_pages);
if (!pages)
goto out;
partial = kzalloc_objs(*partial, nr_pages);
if (!partial)
goto out_page;
priv = kzalloc_objs(*priv, nr_pages);
if (!priv)
goto out_part;
for (i = 0; i < nr_pages; i++) {
priv[i] = kzalloc_obj(**priv);
if (!priv[i])
goto out_priv;
}
if (lgr->is_smcd ||
(!lgr->is_smcd && !smc->conn.rmb_desc->is_vm)) {
/* smcd or smcr that uses physically contiguous RMBs */
priv[0]->len = len;
priv[0]->smc = smc;
partial[0].offset = src - (char *)smc->conn.rmb_desc->cpu_addr;
partial[0].len = len;
partial[0].private = (unsigned long)priv[0];
pages[0] = smc->conn.rmb_desc->pages;
} else {
int size, left = len;
void *buf = src;
/* smcr that uses virtually contiguous RMBs*/
for (i = 0; i < nr_pages; i++) {
size = min_t(int, PAGE_SIZE - offset, left);
priv[i]->len = size;
priv[i]->smc = smc;
pages[i] = vmalloc_to_page(buf);
partial[i].offset = offset;
partial[i].len = size;
partial[i].private = (unsigned long)priv[i];
buf += size;
left -= size;
offset = 0;
}
}
for (i = 0; i < nr_pages; i++) {
get_page(pages[i]);
sock_hold(&smc->sk);
}
spd.nr_pages_max = nr_pages;
spd.nr_pages = nr_pages;
spd.pages = pages;
spd.partial = partial;
spd.ops = &smc_pipe_ops;
spd.spd_release = smc_rx_spd_release;
bytes = splice_to_pipe(pipe, &spd);
if (bytes > 0)
atomic_add(bytes, &smc->conn.splice_pending);
kfree(priv);
kfree(partial);
kfree(pages);
return bytes;
out_priv:
for (i = (i - 1); i >= 0; i--)
kfree(priv[i]);
kfree(priv);
out_part:
kfree(partial);
out_page:
kfree(pages);
out:
return -ENOMEM;
}
static int smc_rx_data_available_and_no_splice_pend(struct smc_connection *conn, size_t peeked)
{
return smc_rx_data_available(conn, peeked) &&
!atomic_read(&conn->splice_pending);
}
/* blocks rcvbuf consumer until >=len bytes available or timeout or interrupted
* @smc smc socket
* @timeo pointer to max seconds to wait, pointer to value 0 for no timeout
* @peeked number of bytes already peeked
* @fcrit add'l criterion to evaluate as function pointer
* Returns:
* 1 if at least 1 byte available in rcvbuf or if socket error/shutdown.
* 0 otherwise (nothing in rcvbuf nor timeout, e.g. interrupted).
*/
int smc_rx_wait(struct smc_sock *smc, long *timeo, size_t peeked,
int (*fcrit)(struct smc_connection *conn, size_t baseline))
{
DEFINE_WAIT_FUNC(wait, woken_wake_function);
struct smc_connection *conn = &smc->conn;
struct smc_cdc_conn_state_flags *cflags =
&conn->local_tx_ctrl.conn_state_flags;
struct sock *sk = &smc->sk;
int rc;
if (fcrit(conn, peeked))
return 1;
sk_set_bit(SOCKWQ_ASYNC_WAITDATA, sk);
add_wait_queue(sk_sleep(sk), &wait);
rc = sk_wait_event(sk, timeo,
READ_ONCE(sk->sk_err) ||
cflags->peer_conn_abort ||
READ_ONCE(sk->sk_shutdown) & RCV_SHUTDOWN ||
conn->killed ||
fcrit(conn, peeked),
&wait);
remove_wait_queue(sk_sleep(sk), &wait);
sk_clear_bit(SOCKWQ_ASYNC_WAITDATA, sk);
return rc;
}
static int smc_rx_recv_urg(struct smc_sock *smc, struct msghdr *msg, int len,
int flags)
{
struct smc_connection *conn = &smc->conn;
union smc_host_cursor cons;
struct sock *sk = &smc->sk;
int rc = 0;
if (sock_flag(sk, SOCK_URGINLINE) ||
!(conn->urg_state == SMC_URG_VALID) ||
conn->urg_state == SMC_URG_READ)
return -EINVAL;
SMC_STAT_INC(smc, urg_data_cnt);
if (conn->urg_state == SMC_URG_VALID) {
if (!(flags & MSG_PEEK))
smc->conn.urg_state = SMC_URG_READ;
msg->msg_flags |= MSG_OOB;
if (len > 0) {
if (!(flags & MSG_TRUNC))
rc = memcpy_to_msg(msg, &conn->urg_rx_byte, 1);
len = 1;
smc_curs_copy(&cons, &conn->local_tx_ctrl.cons, conn);
if (smc_curs_diff(conn->rmb_desc->len, &cons,
&conn->urg_curs) > 1)
conn->urg_rx_skip_pend = true;
/* Urgent Byte was already accounted for, but trigger
* skipping the urgent byte in non-inline case
*/
if (!(flags & MSG_PEEK))
smc_rx_update_consumer(smc, cons, 0);
} else {
msg->msg_flags |= MSG_TRUNC;
}
return rc ? -EFAULT : len;
}
if (sk->sk_state == SMC_CLOSED || sk->sk_shutdown & RCV_SHUTDOWN)
return 0;
return -EAGAIN;
}
static bool smc_rx_recvmsg_data_available(struct smc_sock *smc, size_t peeked)
{
struct smc_connection *conn = &smc->conn;
if (smc_rx_data_available(conn, peeked))
return true;
else if (conn->urg_state == SMC_URG_VALID)
/* we received a single urgent Byte - skip */
smc_rx_update_cons(smc, 0);
return false;
}
/* smc_rx_recvmsg - receive data from RMBE
* @msg: copy data to receive buffer
* @pipe: copy data to pipe if set - indicates splice() call
*
* rcvbuf consumer: main API called by socket layer.
* Called under sk lock.
*/
int smc_rx_recvmsg(struct smc_sock *smc, struct msghdr *msg,
struct pipe_inode_info *pipe, size_t len, int flags)
{
size_t copylen, read_done = 0, read_remaining = len, peeked_bytes = 0;
size_t chunk_len, chunk_off, chunk_len_sum;
struct smc_connection *conn = &smc->conn;
int (*func)(struct smc_connection *conn, size_t baseline);
union smc_host_cursor cons;
int readable, chunk;
char *rcvbuf_base;
struct sock *sk;
int splbytes;
long timeo;
int target; /* Read at least these many bytes */
int rc;
if (unlikely(flags & MSG_ERRQUEUE))
return -EINVAL; /* future work for sk.sk_family == AF_SMC */
sk = &smc->sk;
if (sk->sk_state == SMC_LISTEN)
return -ENOTCONN;
if (flags & MSG_OOB)
return smc_rx_recv_urg(smc, msg, len, flags);
timeo = sock_rcvtimeo(sk, flags & MSG_DONTWAIT);
target = sock_rcvlowat(sk, flags & MSG_WAITALL, len);
readable = atomic_read(&conn->bytes_to_rcv);
if (readable >= conn->rmb_desc->len)
SMC_STAT_RMB_RX_FULL(smc, !conn->lnk);
if (len < readable)
SMC_STAT_RMB_RX_SIZE_SMALL(smc, !conn->lnk);
/* we currently use 1 RMBE per RMB, so RMBE == RMB base addr */
rcvbuf_base = conn->rx_off + conn->rmb_desc->cpu_addr;
do { /* while (read_remaining) */
if (read_done >= target || (pipe && read_done))
break;
if (conn->killed)
break;
if (smc_rx_recvmsg_data_available(smc, peeked_bytes))
goto copy;
if (sk->sk_shutdown & RCV_SHUTDOWN) {
/* smc_cdc_msg_recv_action() could have run after
* above smc_rx_recvmsg_data_available()
*/
if (smc_rx_recvmsg_data_available(smc, peeked_bytes))
goto copy;
break;
}
if (read_done) {
if (sk->sk_err ||
sk->sk_state == SMC_CLOSED ||
!timeo ||
signal_pending(current))
break;
} else {
if (sk->sk_err) {
read_done = sock_error(sk);
break;
}
if (sk->sk_state == SMC_CLOSED) {
if (!sock_flag(sk, SOCK_DONE)) {
/* This occurs when user tries to read
* from never connected socket.
*/
read_done = -ENOTCONN;
break;
}
break;
}
if (!timeo)
return -EAGAIN;
if (signal_pending(current)) {
read_done = sock_intr_errno(timeo);
break;
}
}
if (!smc_rx_data_available(conn, peeked_bytes)) {
smc_rx_wait(smc, &timeo, peeked_bytes, smc_rx_data_available);
continue;
}
copy:
/* initialize variables for 1st iteration of subsequent loop */
/* could be just 1 byte, even after waiting on data above */
readable = smc_rx_data_available(conn, peeked_bytes);
splbytes = atomic_read(&conn->splice_pending);
if (!readable || (msg && splbytes)) {
if (splbytes)
func = smc_rx_data_available_and_no_splice_pend;
else
func = smc_rx_data_available;
smc_rx_wait(smc, &timeo, peeked_bytes, func);
continue;
}
smc_curs_copy(&cons, &conn->local_tx_ctrl.cons, conn);
if ((flags & MSG_PEEK) && peeked_bytes)
smc_curs_add(conn->rmb_desc->len, &cons, peeked_bytes);
/* subsequent splice() calls pick up where previous left */
if (splbytes)
smc_curs_add(conn->rmb_desc->len, &cons, splbytes);
if (conn->urg_state == SMC_URG_VALID &&
sock_flag(&smc->sk, SOCK_URGINLINE) &&
readable > 1)
readable--; /* always stop at urgent Byte */
/* not more than what user space asked for */
copylen = min_t(size_t, read_remaining, readable);
/* determine chunks where to read from rcvbuf */
/* either unwrapped case, or 1st chunk of wrapped case */
chunk_len = min_t(size_t, copylen, conn->rmb_desc->len -
cons.count);
chunk_len_sum = chunk_len;
chunk_off = cons.count;
smc_rmb_sync_sg_for_cpu(conn);
for (chunk = 0; chunk < 2; chunk++) {
if (!(flags & MSG_TRUNC)) {
if (msg) {
rc = memcpy_to_msg(msg, rcvbuf_base +
chunk_off,
chunk_len);
} else {
rc = smc_rx_splice(pipe, rcvbuf_base +
chunk_off, chunk_len,
smc);
}
if (rc < 0) {
if (!read_done)
read_done = -EFAULT;
goto out;
}
}
read_remaining -= chunk_len;
read_done += chunk_len;
if (flags & MSG_PEEK)
peeked_bytes += chunk_len;
if (chunk_len_sum == copylen)
break; /* either on 1st or 2nd iteration */
/* prepare next (== 2nd) iteration */
chunk_len = copylen - chunk_len; /* remainder */
chunk_len_sum += chunk_len;
chunk_off = 0; /* modulo offset in recv ring buffer */
}
/* update cursors */
if (!(flags & MSG_PEEK)) {
/* increased in recv tasklet smc_cdc_msg_rcv() */
smp_mb__before_atomic();
atomic_sub(copylen, &conn->bytes_to_rcv);
/* guarantee 0 <= bytes_to_rcv <= rmb_desc->len */
smp_mb__after_atomic();
if (msg && smc_rx_update_consumer(smc, cons, copylen))
goto out;
}
trace_smc_rx_recvmsg(smc, copylen);
} while (read_remaining);
out:
return read_done;
}
/* Initialize receive properties on connection establishment. NB: not __init! */
void smc_rx_init(struct smc_sock *smc)
{
smc->sk.sk_data_ready = smc_rx_wake_up;
atomic_set(&smc->conn.splice_pending, 0);
smc->conn.urg_state = SMC_URG_READ;
}
+31
View File
@@ -0,0 +1,31 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Shared Memory Communications over RDMA (SMC-R) and RoCE
*
* Manage RMBE
*
* Copyright IBM Corp. 2016
*
* Author(s): Ursula Braun <ubraun@linux.vnet.ibm.com>
*/
#ifndef SMC_RX_H
#define SMC_RX_H
#include <linux/socket.h>
#include <linux/types.h>
#include "smc.h"
void smc_rx_init(struct smc_sock *smc);
int smc_rx_recvmsg(struct smc_sock *smc, struct msghdr *msg,
struct pipe_inode_info *pipe, size_t len, int flags);
int smc_rx_wait(struct smc_sock *smc, long *timeo, size_t peeked,
int (*fcrit)(struct smc_connection *conn, size_t baseline));
static inline int smc_rx_data_available(struct smc_connection *conn, size_t peeked)
{
return atomic_read(&conn->bytes_to_rcv) - peeked;
}
#endif /* SMC_RX_H */
+419
View File
@@ -0,0 +1,419 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Shared Memory Communications over RDMA (SMC-R) and RoCE
*
* SMC statistics netlink routines
*
* Copyright IBM Corp. 2021
*
* Author(s): Guvenc Gulce
*/
#include <linux/init.h>
#include <linux/mutex.h>
#include <linux/percpu.h>
#include <linux/ctype.h>
#include <linux/smc.h>
#include <net/genetlink.h>
#include <net/sock.h>
#include "smc_netlink.h"
#include "smc_stats.h"
int smc_stats_init(struct net *net)
{
net->smc.fback_rsn = kzalloc_obj(*net->smc.fback_rsn);
if (!net->smc.fback_rsn)
goto err_fback;
net->smc.smc_stats = alloc_percpu(struct smc_stats);
if (!net->smc.smc_stats)
goto err_stats;
mutex_init(&net->smc.mutex_fback_rsn);
return 0;
err_stats:
kfree(net->smc.fback_rsn);
err_fback:
return -ENOMEM;
}
void smc_stats_exit(struct net *net)
{
kfree(net->smc.fback_rsn);
if (net->smc.smc_stats)
free_percpu(net->smc.smc_stats);
}
static int smc_nl_fill_stats_rmb_data(struct sk_buff *skb,
struct smc_stats *stats, int tech,
int type)
{
struct smc_stats_rmbcnt *stats_rmb_cnt;
struct nlattr *attrs;
if (type == SMC_NLA_STATS_T_TX_RMB_STATS)
stats_rmb_cnt = &stats->smc[tech].rmb_tx;
else
stats_rmb_cnt = &stats->smc[tech].rmb_rx;
attrs = nla_nest_start(skb, type);
if (!attrs)
goto errout;
if (nla_put_u64_64bit(skb, SMC_NLA_STATS_RMB_REUSE_CNT,
stats_rmb_cnt->reuse_cnt,
SMC_NLA_STATS_RMB_PAD))
goto errattr;
if (nla_put_u64_64bit(skb, SMC_NLA_STATS_RMB_SIZE_SM_PEER_CNT,
stats_rmb_cnt->buf_size_small_peer_cnt,
SMC_NLA_STATS_RMB_PAD))
goto errattr;
if (nla_put_u64_64bit(skb, SMC_NLA_STATS_RMB_SIZE_SM_CNT,
stats_rmb_cnt->buf_size_small_cnt,
SMC_NLA_STATS_RMB_PAD))
goto errattr;
if (nla_put_u64_64bit(skb, SMC_NLA_STATS_RMB_FULL_PEER_CNT,
stats_rmb_cnt->buf_full_peer_cnt,
SMC_NLA_STATS_RMB_PAD))
goto errattr;
if (nla_put_u64_64bit(skb, SMC_NLA_STATS_RMB_FULL_CNT,
stats_rmb_cnt->buf_full_cnt,
SMC_NLA_STATS_RMB_PAD))
goto errattr;
if (nla_put_u64_64bit(skb, SMC_NLA_STATS_RMB_ALLOC_CNT,
stats_rmb_cnt->alloc_cnt,
SMC_NLA_STATS_RMB_PAD))
goto errattr;
if (nla_put_u64_64bit(skb, SMC_NLA_STATS_RMB_DGRADE_CNT,
stats_rmb_cnt->dgrade_cnt,
SMC_NLA_STATS_RMB_PAD))
goto errattr;
nla_nest_end(skb, attrs);
return 0;
errattr:
nla_nest_cancel(skb, attrs);
errout:
return -EMSGSIZE;
}
static int smc_nl_fill_stats_bufsize_data(struct sk_buff *skb,
struct smc_stats *stats, int tech,
int type)
{
struct smc_stats_memsize *stats_pload;
struct nlattr *attrs;
if (type == SMC_NLA_STATS_T_TXPLOAD_SIZE)
stats_pload = &stats->smc[tech].tx_pd;
else if (type == SMC_NLA_STATS_T_RXPLOAD_SIZE)
stats_pload = &stats->smc[tech].rx_pd;
else if (type == SMC_NLA_STATS_T_TX_RMB_SIZE)
stats_pload = &stats->smc[tech].tx_rmbsize;
else if (type == SMC_NLA_STATS_T_RX_RMB_SIZE)
stats_pload = &stats->smc[tech].rx_rmbsize;
else
goto errout;
attrs = nla_nest_start(skb, type);
if (!attrs)
goto errout;
if (nla_put_u64_64bit(skb, SMC_NLA_STATS_PLOAD_8K,
stats_pload->buf[SMC_BUF_8K],
SMC_NLA_STATS_PLOAD_PAD))
goto errattr;
if (nla_put_u64_64bit(skb, SMC_NLA_STATS_PLOAD_16K,
stats_pload->buf[SMC_BUF_16K],
SMC_NLA_STATS_PLOAD_PAD))
goto errattr;
if (nla_put_u64_64bit(skb, SMC_NLA_STATS_PLOAD_32K,
stats_pload->buf[SMC_BUF_32K],
SMC_NLA_STATS_PLOAD_PAD))
goto errattr;
if (nla_put_u64_64bit(skb, SMC_NLA_STATS_PLOAD_64K,
stats_pload->buf[SMC_BUF_64K],
SMC_NLA_STATS_PLOAD_PAD))
goto errattr;
if (nla_put_u64_64bit(skb, SMC_NLA_STATS_PLOAD_128K,
stats_pload->buf[SMC_BUF_128K],
SMC_NLA_STATS_PLOAD_PAD))
goto errattr;
if (nla_put_u64_64bit(skb, SMC_NLA_STATS_PLOAD_256K,
stats_pload->buf[SMC_BUF_256K],
SMC_NLA_STATS_PLOAD_PAD))
goto errattr;
if (nla_put_u64_64bit(skb, SMC_NLA_STATS_PLOAD_512K,
stats_pload->buf[SMC_BUF_512K],
SMC_NLA_STATS_PLOAD_PAD))
goto errattr;
if (nla_put_u64_64bit(skb, SMC_NLA_STATS_PLOAD_1024K,
stats_pload->buf[SMC_BUF_1024K],
SMC_NLA_STATS_PLOAD_PAD))
goto errattr;
if (nla_put_u64_64bit(skb, SMC_NLA_STATS_PLOAD_G_1024K,
stats_pload->buf[SMC_BUF_G_1024K],
SMC_NLA_STATS_PLOAD_PAD))
goto errattr;
nla_nest_end(skb, attrs);
return 0;
errattr:
nla_nest_cancel(skb, attrs);
errout:
return -EMSGSIZE;
}
static int smc_nl_fill_stats_tech_data(struct sk_buff *skb,
struct smc_stats *stats, int tech)
{
struct smc_stats_tech *smc_tech;
struct nlattr *attrs;
smc_tech = &stats->smc[tech];
if (tech == SMC_TYPE_D)
attrs = nla_nest_start(skb, SMC_NLA_STATS_SMCD_TECH);
else
attrs = nla_nest_start(skb, SMC_NLA_STATS_SMCR_TECH);
if (!attrs)
goto errout;
if (smc_nl_fill_stats_rmb_data(skb, stats, tech,
SMC_NLA_STATS_T_TX_RMB_STATS))
goto errattr;
if (smc_nl_fill_stats_rmb_data(skb, stats, tech,
SMC_NLA_STATS_T_RX_RMB_STATS))
goto errattr;
if (smc_nl_fill_stats_bufsize_data(skb, stats, tech,
SMC_NLA_STATS_T_TXPLOAD_SIZE))
goto errattr;
if (smc_nl_fill_stats_bufsize_data(skb, stats, tech,
SMC_NLA_STATS_T_RXPLOAD_SIZE))
goto errattr;
if (smc_nl_fill_stats_bufsize_data(skb, stats, tech,
SMC_NLA_STATS_T_TX_RMB_SIZE))
goto errattr;
if (smc_nl_fill_stats_bufsize_data(skb, stats, tech,
SMC_NLA_STATS_T_RX_RMB_SIZE))
goto errattr;
if (nla_put_u64_64bit(skb, SMC_NLA_STATS_T_CLNT_V1_SUCC,
smc_tech->clnt_v1_succ_cnt,
SMC_NLA_STATS_PAD))
goto errattr;
if (nla_put_u64_64bit(skb, SMC_NLA_STATS_T_CLNT_V2_SUCC,
smc_tech->clnt_v2_succ_cnt,
SMC_NLA_STATS_PAD))
goto errattr;
if (nla_put_u64_64bit(skb, SMC_NLA_STATS_T_SRV_V1_SUCC,
smc_tech->srv_v1_succ_cnt,
SMC_NLA_STATS_PAD))
goto errattr;
if (nla_put_u64_64bit(skb, SMC_NLA_STATS_T_SRV_V2_SUCC,
smc_tech->srv_v2_succ_cnt,
SMC_NLA_STATS_PAD))
goto errattr;
if (nla_put_u64_64bit(skb, SMC_NLA_STATS_T_RX_BYTES,
smc_tech->rx_bytes,
SMC_NLA_STATS_PAD))
goto errattr;
if (nla_put_u64_64bit(skb, SMC_NLA_STATS_T_TX_BYTES,
smc_tech->tx_bytes,
SMC_NLA_STATS_PAD))
goto errattr;
if (nla_put_uint(skb, SMC_NLA_STATS_T_RX_RMB_USAGE,
smc_tech->rx_rmbuse))
goto errattr;
if (nla_put_uint(skb, SMC_NLA_STATS_T_TX_RMB_USAGE,
smc_tech->tx_rmbuse))
goto errattr;
if (nla_put_u64_64bit(skb, SMC_NLA_STATS_T_RX_CNT,
smc_tech->rx_cnt,
SMC_NLA_STATS_PAD))
goto errattr;
if (nla_put_u64_64bit(skb, SMC_NLA_STATS_T_TX_CNT,
smc_tech->tx_cnt,
SMC_NLA_STATS_PAD))
goto errattr;
if (nla_put_u64_64bit(skb, SMC_NLA_STATS_T_SENDPAGE_CNT,
0,
SMC_NLA_STATS_PAD))
goto errattr;
if (nla_put_u64_64bit(skb, SMC_NLA_STATS_T_CORK_CNT,
smc_tech->cork_cnt,
SMC_NLA_STATS_PAD))
goto errattr;
if (nla_put_u64_64bit(skb, SMC_NLA_STATS_T_NDLY_CNT,
smc_tech->ndly_cnt,
SMC_NLA_STATS_PAD))
goto errattr;
if (nla_put_u64_64bit(skb, SMC_NLA_STATS_T_SPLICE_CNT,
smc_tech->splice_cnt,
SMC_NLA_STATS_PAD))
goto errattr;
if (nla_put_u64_64bit(skb, SMC_NLA_STATS_T_URG_DATA_CNT,
smc_tech->urg_data_cnt,
SMC_NLA_STATS_PAD))
goto errattr;
nla_nest_end(skb, attrs);
return 0;
errattr:
nla_nest_cancel(skb, attrs);
errout:
return -EMSGSIZE;
}
int smc_nl_get_stats(struct sk_buff *skb,
struct netlink_callback *cb)
{
struct smc_nl_dmp_ctx *cb_ctx = smc_nl_dmp_ctx(cb);
struct net *net = sock_net(skb->sk);
struct smc_stats *stats;
struct nlattr *attrs;
int cpu, i, size;
void *nlh;
u64 *src;
u64 *sum;
if (cb_ctx->pos[0])
goto errmsg;
nlh = genlmsg_put(skb, NETLINK_CB(cb->skb).portid, cb->nlh->nlmsg_seq,
&smc_gen_nl_family, NLM_F_MULTI,
SMC_NETLINK_GET_STATS);
if (!nlh)
goto errmsg;
attrs = nla_nest_start(skb, SMC_GEN_STATS);
if (!attrs)
goto errnest;
stats = kzalloc_obj(*stats);
if (!stats)
goto erralloc;
size = sizeof(*stats) / sizeof(u64);
for_each_possible_cpu(cpu) {
src = (u64 *)per_cpu_ptr(net->smc.smc_stats, cpu);
sum = (u64 *)stats;
for (i = 0; i < size; i++)
*(sum++) += *(src++);
}
if (smc_nl_fill_stats_tech_data(skb, stats, SMC_TYPE_D))
goto errattr;
if (smc_nl_fill_stats_tech_data(skb, stats, SMC_TYPE_R))
goto errattr;
if (nla_put_u64_64bit(skb, SMC_NLA_STATS_CLNT_HS_ERR_CNT,
stats->clnt_hshake_err_cnt,
SMC_NLA_STATS_PAD))
goto errattr;
if (nla_put_u64_64bit(skb, SMC_NLA_STATS_SRV_HS_ERR_CNT,
stats->srv_hshake_err_cnt,
SMC_NLA_STATS_PAD))
goto errattr;
nla_nest_end(skb, attrs);
genlmsg_end(skb, nlh);
cb_ctx->pos[0] = 1;
kfree(stats);
return skb->len;
errattr:
kfree(stats);
erralloc:
nla_nest_cancel(skb, attrs);
errnest:
genlmsg_cancel(skb, nlh);
errmsg:
return skb->len;
}
static int smc_nl_get_fback_details(struct sk_buff *skb,
struct netlink_callback *cb, int pos,
bool is_srv)
{
struct smc_nl_dmp_ctx *cb_ctx = smc_nl_dmp_ctx(cb);
struct net *net = sock_net(skb->sk);
int cnt_reported = cb_ctx->pos[2];
struct smc_stats_fback *trgt_arr;
struct nlattr *attrs;
int rc = 0;
void *nlh;
if (is_srv)
trgt_arr = &net->smc.fback_rsn->srv[0];
else
trgt_arr = &net->smc.fback_rsn->clnt[0];
if (!trgt_arr[pos].fback_code)
return -ENODATA;
nlh = genlmsg_put(skb, NETLINK_CB(cb->skb).portid, cb->nlh->nlmsg_seq,
&smc_gen_nl_family, NLM_F_MULTI,
SMC_NETLINK_GET_FBACK_STATS);
if (!nlh)
goto errmsg;
attrs = nla_nest_start(skb, SMC_GEN_FBACK_STATS);
if (!attrs)
goto errout;
if (nla_put_u8(skb, SMC_NLA_FBACK_STATS_TYPE, is_srv))
goto errattr;
if (!cnt_reported) {
if (nla_put_u64_64bit(skb, SMC_NLA_FBACK_STATS_SRV_CNT,
net->smc.fback_rsn->srv_fback_cnt,
SMC_NLA_FBACK_STATS_PAD))
goto errattr;
if (nla_put_u64_64bit(skb, SMC_NLA_FBACK_STATS_CLNT_CNT,
net->smc.fback_rsn->clnt_fback_cnt,
SMC_NLA_FBACK_STATS_PAD))
goto errattr;
cnt_reported = 1;
}
if (nla_put_u32(skb, SMC_NLA_FBACK_STATS_RSN_CODE,
trgt_arr[pos].fback_code))
goto errattr;
if (nla_put_u16(skb, SMC_NLA_FBACK_STATS_RSN_CNT,
trgt_arr[pos].count))
goto errattr;
cb_ctx->pos[2] = cnt_reported;
nla_nest_end(skb, attrs);
genlmsg_end(skb, nlh);
return rc;
errattr:
nla_nest_cancel(skb, attrs);
errout:
genlmsg_cancel(skb, nlh);
errmsg:
return -EMSGSIZE;
}
int smc_nl_get_fback_stats(struct sk_buff *skb, struct netlink_callback *cb)
{
struct smc_nl_dmp_ctx *cb_ctx = smc_nl_dmp_ctx(cb);
struct net *net = sock_net(skb->sk);
int rc_srv = 0, rc_clnt = 0, k;
int skip_serv = cb_ctx->pos[1];
int snum = cb_ctx->pos[0];
bool is_srv = true;
mutex_lock(&net->smc.mutex_fback_rsn);
for (k = 0; k < SMC_MAX_FBACK_RSN_CNT; k++) {
if (k < snum)
continue;
if (!skip_serv) {
rc_srv = smc_nl_get_fback_details(skb, cb, k, is_srv);
if (rc_srv && rc_srv != -ENODATA)
break;
} else {
skip_serv = 0;
}
rc_clnt = smc_nl_get_fback_details(skb, cb, k, !is_srv);
if (rc_clnt && rc_clnt != -ENODATA) {
skip_serv = 1;
break;
}
if (rc_clnt == -ENODATA && rc_srv == -ENODATA)
break;
}
mutex_unlock(&net->smc.mutex_fback_rsn);
cb_ctx->pos[1] = skip_serv;
cb_ctx->pos[0] = k;
return skb->len;
}
+280
View File
@@ -0,0 +1,280 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Shared Memory Communications over RDMA (SMC-R) and RoCE
*
* Macros for SMC statistics
*
* Copyright IBM Corp. 2021
*
* Author(s): Guvenc Gulce
*/
#ifndef NET_SMC_SMC_STATS_H_
#define NET_SMC_SMC_STATS_H_
#include <linux/init.h>
#include <linux/mutex.h>
#include <linux/percpu.h>
#include <linux/ctype.h>
#include <linux/smc.h>
#include "smc_clc.h"
#define SMC_MAX_FBACK_RSN_CNT 36
enum {
SMC_BUF_8K,
SMC_BUF_16K,
SMC_BUF_32K,
SMC_BUF_64K,
SMC_BUF_128K,
SMC_BUF_256K,
SMC_BUF_512K,
SMC_BUF_1024K,
SMC_BUF_G_1024K,
SMC_BUF_MAX,
};
struct smc_stats_fback {
int fback_code;
u16 count;
};
struct smc_stats_rsn {
struct smc_stats_fback srv[SMC_MAX_FBACK_RSN_CNT];
struct smc_stats_fback clnt[SMC_MAX_FBACK_RSN_CNT];
u64 srv_fback_cnt;
u64 clnt_fback_cnt;
};
struct smc_stats_rmbcnt {
u64 buf_size_small_peer_cnt;
u64 buf_size_small_cnt;
u64 buf_full_peer_cnt;
u64 buf_full_cnt;
u64 reuse_cnt;
u64 alloc_cnt;
u64 dgrade_cnt;
};
struct smc_stats_memsize {
u64 buf[SMC_BUF_MAX];
};
struct smc_stats_tech {
struct smc_stats_memsize tx_rmbsize;
struct smc_stats_memsize rx_rmbsize;
struct smc_stats_memsize tx_pd;
struct smc_stats_memsize rx_pd;
struct smc_stats_rmbcnt rmb_tx;
struct smc_stats_rmbcnt rmb_rx;
u64 clnt_v1_succ_cnt;
u64 clnt_v2_succ_cnt;
u64 srv_v1_succ_cnt;
u64 srv_v2_succ_cnt;
u64 urg_data_cnt;
u64 splice_cnt;
u64 cork_cnt;
u64 ndly_cnt;
u64 rx_bytes;
u64 tx_bytes;
u64 rx_cnt;
u64 tx_cnt;
u64 rx_rmbuse;
u64 tx_rmbuse;
};
struct smc_stats {
struct smc_stats_tech smc[2];
u64 clnt_hshake_err_cnt;
u64 srv_hshake_err_cnt;
};
#define SMC_STAT_PAYLOAD_SUB(_smc_stats, _tech, key, _len, _rc) \
do { \
typeof(_smc_stats) stats = (_smc_stats); \
typeof(_tech) t = (_tech); \
typeof(_len) l = (_len); \
int _pos; \
typeof(_rc) r = (_rc); \
int m = SMC_BUF_MAX - 1; \
this_cpu_inc((*stats).smc[t].key ## _cnt); \
if (r <= 0 || l <= 0) \
break; \
_pos = fls64((l - 1) >> 13); \
_pos = (_pos <= m) ? _pos : m; \
this_cpu_inc((*stats).smc[t].key ## _pd.buf[_pos]); \
this_cpu_add((*stats).smc[t].key ## _bytes, r); \
} \
while (0)
#define SMC_STAT_TX_PAYLOAD(_smc, length, rcode) \
do { \
typeof(_smc) __smc = _smc; \
struct net *_net = sock_net(&__smc->sk); \
struct smc_stats __percpu *_smc_stats = _net->smc.smc_stats; \
typeof(length) _len = (length); \
typeof(rcode) _rc = (rcode); \
bool is_smcd = !__smc->conn.lnk; \
if (is_smcd) \
SMC_STAT_PAYLOAD_SUB(_smc_stats, SMC_TYPE_D, tx, _len, _rc); \
else \
SMC_STAT_PAYLOAD_SUB(_smc_stats, SMC_TYPE_R, tx, _len, _rc); \
} \
while (0)
#define SMC_STAT_RX_PAYLOAD(_smc, length, rcode) \
do { \
typeof(_smc) __smc = _smc; \
struct net *_net = sock_net(&__smc->sk); \
struct smc_stats __percpu *_smc_stats = _net->smc.smc_stats; \
typeof(length) _len = (length); \
typeof(rcode) _rc = (rcode); \
bool is_smcd = !__smc->conn.lnk; \
if (is_smcd) \
SMC_STAT_PAYLOAD_SUB(_smc_stats, SMC_TYPE_D, rx, _len, _rc); \
else \
SMC_STAT_PAYLOAD_SUB(_smc_stats, SMC_TYPE_R, rx, _len, _rc); \
} \
while (0)
#define SMC_STAT_RMB_SIZE_SUB(_smc_stats, _tech, k, _is_add, _len) \
do { \
typeof(_smc_stats) stats = (_smc_stats); \
typeof(_is_add) is_a = (_is_add); \
typeof(_len) _l = (_len); \
typeof(_tech) t = (_tech); \
int _pos; \
int m = SMC_BUF_MAX - 1; \
if (_l <= 0) \
break; \
if (is_a) { \
_pos = fls((_l - 1) >> 13); \
_pos = (_pos <= m) ? _pos : m; \
this_cpu_inc((*stats).smc[t].k ## _rmbsize.buf[_pos]); \
this_cpu_add((*stats).smc[t].k ## _rmbuse, _l); \
} else { \
this_cpu_sub((*stats).smc[t].k ## _rmbuse, _l); \
} \
} \
while (0)
#define SMC_STAT_RMB_SUB(_smc_stats, type, t, key) \
this_cpu_inc((*(_smc_stats)).smc[t].rmb ## _ ## key.type ## _cnt)
#define SMC_STAT_RMB_SIZE(_smc, _is_smcd, _is_rx, _is_add, _len) \
do { \
struct net *_net = sock_net(&(_smc)->sk); \
struct smc_stats __percpu *_smc_stats = _net->smc.smc_stats; \
typeof(_is_add) is_add = (_is_add); \
typeof(_is_smcd) is_d = (_is_smcd); \
typeof(_is_rx) is_r = (_is_rx); \
typeof(_len) l = (_len); \
if ((is_d) && (is_r)) \
SMC_STAT_RMB_SIZE_SUB(_smc_stats, SMC_TYPE_D, rx, is_add, l); \
if ((is_d) && !(is_r)) \
SMC_STAT_RMB_SIZE_SUB(_smc_stats, SMC_TYPE_D, tx, is_add, l); \
if (!(is_d) && (is_r)) \
SMC_STAT_RMB_SIZE_SUB(_smc_stats, SMC_TYPE_R, rx, is_add, l); \
if (!(is_d) && !(is_r)) \
SMC_STAT_RMB_SIZE_SUB(_smc_stats, SMC_TYPE_R, tx, is_add, l); \
} \
while (0)
#define SMC_STAT_RMB(_smc, type, _is_smcd, _is_rx) \
do { \
struct net *net = sock_net(&(_smc)->sk); \
struct smc_stats __percpu *_smc_stats = net->smc.smc_stats; \
typeof(_is_smcd) is_d = (_is_smcd); \
typeof(_is_rx) is_r = (_is_rx); \
if ((is_d) && (is_r)) \
SMC_STAT_RMB_SUB(_smc_stats, type, SMC_TYPE_D, rx); \
if ((is_d) && !(is_r)) \
SMC_STAT_RMB_SUB(_smc_stats, type, SMC_TYPE_D, tx); \
if (!(is_d) && (is_r)) \
SMC_STAT_RMB_SUB(_smc_stats, type, SMC_TYPE_R, rx); \
if (!(is_d) && !(is_r)) \
SMC_STAT_RMB_SUB(_smc_stats, type, SMC_TYPE_R, tx); \
} \
while (0)
#define SMC_STAT_BUF_REUSE(smc, is_smcd, is_rx) \
SMC_STAT_RMB(smc, reuse, is_smcd, is_rx)
#define SMC_STAT_RMB_ALLOC(smc, is_smcd, is_rx) \
SMC_STAT_RMB(smc, alloc, is_smcd, is_rx)
#define SMC_STAT_RMB_DOWNGRADED(smc, is_smcd, is_rx) \
SMC_STAT_RMB(smc, dgrade, is_smcd, is_rx)
#define SMC_STAT_RMB_TX_PEER_FULL(smc, is_smcd) \
SMC_STAT_RMB(smc, buf_full_peer, is_smcd, false)
#define SMC_STAT_RMB_TX_FULL(smc, is_smcd) \
SMC_STAT_RMB(smc, buf_full, is_smcd, false)
#define SMC_STAT_RMB_TX_PEER_SIZE_SMALL(smc, is_smcd) \
SMC_STAT_RMB(smc, buf_size_small_peer, is_smcd, false)
#define SMC_STAT_RMB_TX_SIZE_SMALL(smc, is_smcd) \
SMC_STAT_RMB(smc, buf_size_small, is_smcd, false)
#define SMC_STAT_RMB_RX_SIZE_SMALL(smc, is_smcd) \
SMC_STAT_RMB(smc, buf_size_small, is_smcd, true)
#define SMC_STAT_RMB_RX_FULL(smc, is_smcd) \
SMC_STAT_RMB(smc, buf_full, is_smcd, true)
#define SMC_STAT_INC(_smc, type) \
do { \
typeof(_smc) __smc = _smc; \
bool is_smcd = !(__smc)->conn.lnk; \
struct net *net = sock_net(&(__smc)->sk); \
struct smc_stats __percpu *smc_stats = net->smc.smc_stats; \
if ((is_smcd)) \
this_cpu_inc(smc_stats->smc[SMC_TYPE_D].type); \
else \
this_cpu_inc(smc_stats->smc[SMC_TYPE_R].type); \
} \
while (0)
#define SMC_STAT_CLNT_SUCC_INC(net, _aclc) \
do { \
typeof(_aclc) acl = (_aclc); \
bool is_v2 = (acl->hdr.version == SMC_V2); \
bool is_smcd = (acl->hdr.typev1 == SMC_TYPE_D); \
struct smc_stats __percpu *smc_stats = (net)->smc.smc_stats; \
if (is_v2 && is_smcd) \
this_cpu_inc(smc_stats->smc[SMC_TYPE_D].clnt_v2_succ_cnt); \
else if (is_v2 && !is_smcd) \
this_cpu_inc(smc_stats->smc[SMC_TYPE_R].clnt_v2_succ_cnt); \
else if (!is_v2 && is_smcd) \
this_cpu_inc(smc_stats->smc[SMC_TYPE_D].clnt_v1_succ_cnt); \
else if (!is_v2 && !is_smcd) \
this_cpu_inc(smc_stats->smc[SMC_TYPE_R].clnt_v1_succ_cnt); \
} \
while (0)
#define SMC_STAT_SERV_SUCC_INC(net, _ini) \
do { \
typeof(_ini) i = (_ini); \
bool is_smcd = (i->is_smcd); \
u8 version = is_smcd ? i->smcd_version : i->smcr_version; \
bool is_v2 = (version & SMC_V2); \
typeof(net->smc.smc_stats) smc_stats = (net)->smc.smc_stats; \
if (is_v2 && is_smcd) \
this_cpu_inc(smc_stats->smc[SMC_TYPE_D].srv_v2_succ_cnt); \
else if (is_v2 && !is_smcd) \
this_cpu_inc(smc_stats->smc[SMC_TYPE_R].srv_v2_succ_cnt); \
else if (!is_v2 && is_smcd) \
this_cpu_inc(smc_stats->smc[SMC_TYPE_D].srv_v1_succ_cnt); \
else if (!is_v2 && !is_smcd) \
this_cpu_inc(smc_stats->smc[SMC_TYPE_R].srv_v1_succ_cnt); \
} \
while (0)
int smc_nl_get_stats(struct sk_buff *skb, struct netlink_callback *cb);
int smc_nl_get_fback_stats(struct sk_buff *skb, struct netlink_callback *cb);
int smc_stats_init(struct net *net);
void smc_stats_exit(struct net *net);
#endif /* NET_SMC_SMC_STATS_H_ */
+266
View File
@@ -0,0 +1,266 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Shared Memory Communications over RDMA (SMC-R) and RoCE
*
* smc_sysctl.c: sysctl interface to SMC subsystem.
*
* Copyright (c) 2022, Alibaba Inc.
*
* Author: Tony Lu <tonylu@linux.alibaba.com>
*
*/
#include <linux/init.h>
#include <linux/sysctl.h>
#include <linux/bpf.h>
#include <net/net_namespace.h>
#include "smc.h"
#include "smc_core.h"
#include "smc_llc.h"
#include "smc_sysctl.h"
#include "smc_hs_bpf.h"
static int min_sndbuf = SMC_BUF_MIN_SIZE;
static int min_rcvbuf = SMC_BUF_MIN_SIZE;
static int max_sndbuf = INT_MAX / 2;
static int max_rcvbuf = INT_MAX / 2;
static const int net_smc_wmem_init = (64 * 1024);
static const int net_smc_rmem_init = (64 * 1024);
static int links_per_lgr_min = SMC_LINKS_ADD_LNK_MIN;
static int links_per_lgr_max = SMC_LINKS_ADD_LNK_MAX;
static int conns_per_lgr_min = SMC_CONN_PER_LGR_MIN;
static int conns_per_lgr_max = SMC_CONN_PER_LGR_MAX;
static unsigned int smcr_max_wr_min = 2;
static unsigned int smcr_max_wr_max = 2048;
#if IS_ENABLED(CONFIG_SMC_HS_CTRL_BPF)
static int smc_net_replace_smc_hs_ctrl(struct net *net, const char *name)
{
struct smc_hs_ctrl *ctrl = NULL;
rcu_read_lock();
/* null or empty name ask to clear current ctrl */
if (name && name[0]) {
ctrl = smc_hs_ctrl_find_by_name(name);
if (!ctrl) {
rcu_read_unlock();
return -EINVAL;
}
/* no change, just return */
if (ctrl == rcu_dereference(net->smc.hs_ctrl)) {
rcu_read_unlock();
return 0;
}
if (!bpf_try_module_get(ctrl, ctrl->owner)) {
rcu_read_unlock();
return -EBUSY;
}
}
/* xhcg old ctrl with the new one atomically */
ctrl = unrcu_pointer(xchg(&net->smc.hs_ctrl, RCU_INITIALIZER(ctrl)));
/* release old ctrl */
if (ctrl)
bpf_module_put(ctrl, ctrl->owner);
rcu_read_unlock();
return 0;
}
static int proc_smc_hs_ctrl(const struct ctl_table *ctl, int write,
void *buffer, size_t *lenp, loff_t *ppos)
{
struct net *net = container_of(ctl->data, struct net, smc.hs_ctrl);
char val[SMC_HS_CTRL_NAME_MAX];
const struct ctl_table tbl = {
.data = val,
.maxlen = SMC_HS_CTRL_NAME_MAX,
};
struct smc_hs_ctrl *ctrl;
int ret;
rcu_read_lock();
ctrl = rcu_dereference(net->smc.hs_ctrl);
if (ctrl)
memcpy(val, ctrl->name, sizeof(ctrl->name));
else
val[0] = '\0';
rcu_read_unlock();
ret = proc_dostring(&tbl, write, buffer, lenp, ppos);
if (ret)
return ret;
if (write)
ret = smc_net_replace_smc_hs_ctrl(net, val);
return ret;
}
#endif /* CONFIG_SMC_HS_CTRL_BPF */
static struct ctl_table smc_table[] = {
{
.procname = "autocorking_size",
.data = &init_net.smc.sysctl_autocorking_size,
.maxlen = sizeof(unsigned int),
.mode = 0644,
.proc_handler = proc_douintvec,
},
{
.procname = "smcr_buf_type",
.data = &init_net.smc.sysctl_smcr_buf_type,
.maxlen = sizeof(unsigned int),
.mode = 0644,
.proc_handler = proc_douintvec_minmax,
.extra1 = SYSCTL_ZERO,
.extra2 = SYSCTL_TWO,
},
{
.procname = "smcr_testlink_time",
.data = &init_net.smc.sysctl_smcr_testlink_time,
.maxlen = sizeof(int),
.mode = 0644,
.proc_handler = proc_dointvec_jiffies,
},
{
.procname = "wmem",
.data = &init_net.smc.sysctl_wmem,
.maxlen = sizeof(int),
.mode = 0644,
.proc_handler = proc_dointvec_minmax,
.extra1 = &min_sndbuf,
.extra2 = &max_sndbuf,
},
{
.procname = "rmem",
.data = &init_net.smc.sysctl_rmem,
.maxlen = sizeof(int),
.mode = 0644,
.proc_handler = proc_dointvec_minmax,
.extra1 = &min_rcvbuf,
.extra2 = &max_rcvbuf,
},
{
.procname = "smcr_max_links_per_lgr",
.data = &init_net.smc.sysctl_max_links_per_lgr,
.maxlen = sizeof(int),
.mode = 0644,
.proc_handler = proc_dointvec_minmax,
.extra1 = &links_per_lgr_min,
.extra2 = &links_per_lgr_max,
},
{
.procname = "smcr_max_conns_per_lgr",
.data = &init_net.smc.sysctl_max_conns_per_lgr,
.maxlen = sizeof(int),
.mode = 0644,
.proc_handler = proc_dointvec_minmax,
.extra1 = &conns_per_lgr_min,
.extra2 = &conns_per_lgr_max,
},
{
.procname = "limit_smc_hs",
.data = &init_net.smc.limit_smc_hs,
.maxlen = sizeof(int),
.mode = 0644,
.proc_handler = proc_dointvec_minmax,
.extra1 = SYSCTL_ZERO,
.extra2 = SYSCTL_ONE,
},
{
.procname = "smcr_max_send_wr",
.data = &init_net.smc.sysctl_smcr_max_send_wr,
.maxlen = sizeof(int),
.mode = 0644,
.proc_handler = proc_dointvec_minmax,
.extra1 = &smcr_max_wr_min,
.extra2 = &smcr_max_wr_max,
},
{
.procname = "smcr_max_recv_wr",
.data = &init_net.smc.sysctl_smcr_max_recv_wr,
.maxlen = sizeof(int),
.mode = 0644,
.proc_handler = proc_dointvec_minmax,
.extra1 = &smcr_max_wr_min,
.extra2 = &smcr_max_wr_max,
},
#if IS_ENABLED(CONFIG_SMC_HS_CTRL_BPF)
{
.procname = "hs_ctrl",
.data = &init_net.smc.hs_ctrl,
.mode = 0644,
.maxlen = SMC_HS_CTRL_NAME_MAX,
.proc_handler = proc_smc_hs_ctrl,
},
#endif /* CONFIG_SMC_HS_CTRL_BPF */
};
int __net_init smc_sysctl_net_init(struct net *net)
{
size_t table_size = ARRAY_SIZE(smc_table);
struct ctl_table *table;
table = smc_table;
if (!net_eq(net, &init_net)) {
int i;
#if IS_ENABLED(CONFIG_SMC_HS_CTRL_BPF)
struct smc_hs_ctrl *ctrl;
rcu_read_lock();
ctrl = rcu_dereference(init_net.smc.hs_ctrl);
if (ctrl && ctrl->flags & SMC_HS_CTRL_FLAG_INHERITABLE &&
bpf_try_module_get(ctrl, ctrl->owner))
rcu_assign_pointer(net->smc.hs_ctrl, ctrl);
rcu_read_unlock();
#endif /* CONFIG_SMC_HS_CTRL_BPF */
table = kmemdup(table, sizeof(smc_table), GFP_KERNEL);
if (!table)
goto err_alloc;
for (i = 0; i < table_size; i++)
table[i].data += (void *)net - (void *)&init_net;
}
net->smc.smc_hdr = register_net_sysctl_sz(net, "net/smc", table,
table_size);
if (!net->smc.smc_hdr)
goto err_reg;
net->smc.sysctl_autocorking_size = SMC_AUTOCORKING_DEFAULT_SIZE;
net->smc.sysctl_smcr_buf_type = SMCR_PHYS_CONT_BUFS;
net->smc.sysctl_smcr_testlink_time = SMC_LLC_TESTLINK_DEFAULT_TIME;
WRITE_ONCE(net->smc.sysctl_wmem, net_smc_wmem_init);
WRITE_ONCE(net->smc.sysctl_rmem, net_smc_rmem_init);
net->smc.sysctl_max_links_per_lgr = SMC_LINKS_PER_LGR_MAX_PREFER;
net->smc.sysctl_max_conns_per_lgr = SMC_CONN_PER_LGR_PREFER;
net->smc.sysctl_smcr_max_send_wr = SMCR_MAX_SEND_WR_DEF;
net->smc.sysctl_smcr_max_recv_wr = SMCR_MAX_RECV_WR_DEF;
/* disable handshake limitation by default */
net->smc.limit_smc_hs = 0;
return 0;
err_reg:
if (!net_eq(net, &init_net))
kfree(table);
err_alloc:
#if IS_ENABLED(CONFIG_SMC_HS_CTRL_BPF)
smc_net_replace_smc_hs_ctrl(net, NULL);
#endif /* CONFIG_SMC_HS_CTRL_BPF */
return -ENOMEM;
}
void __net_exit smc_sysctl_net_exit(struct net *net)
{
const struct ctl_table *table;
table = net->smc.smc_hdr->ctl_table_arg;
unregister_net_sysctl_table(net->smc.smc_hdr);
#if IS_ENABLED(CONFIG_SMC_HS_CTRL_BPF)
smc_net_replace_smc_hs_ctrl(net, NULL);
#endif /* CONFIG_SMC_HS_CTRL_BPF */
if (!net_eq(net, &init_net))
kfree(table);
}
+37
View File
@@ -0,0 +1,37 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Shared Memory Communications over RDMA (SMC-R) and RoCE
*
* smc_sysctl.c: sysctl interface to SMC subsystem.
*
* Copyright (c) 2022, Alibaba Inc.
*
* Author: Tony Lu <tonylu@linux.alibaba.com>
*
*/
#ifndef _SMC_SYSCTL_H
#define _SMC_SYSCTL_H
#ifdef CONFIG_SYSCTL
int __net_init smc_sysctl_net_init(struct net *net);
void __net_exit smc_sysctl_net_exit(struct net *net);
#else
static inline int smc_sysctl_net_init(struct net *net)
{
net->smc.sysctl_autocorking_size = SMC_AUTOCORKING_DEFAULT_SIZE;
net->smc.sysctl_max_links_per_lgr = SMC_LINKS_PER_LGR_MAX_PREFER;
net->smc.sysctl_max_conns_per_lgr = SMC_CONN_PER_LGR_PREFER;
net->smc.sysctl_smcr_max_send_wr = SMCR_MAX_SEND_WR_DEF;
net->smc.sysctl_smcr_max_recv_wr = SMCR_MAX_RECV_WR_DEF;
return 0;
}
static inline void smc_sysctl_net_exit(struct net *net) { }
#endif /* CONFIG_SYSCTL */
#endif /* _SMC_SYSCTL_H */
+9
View File
@@ -0,0 +1,9 @@
// SPDX-License-Identifier: GPL-2.0-only
#define CREATE_TRACE_POINTS
#include "smc_tracepoint.h"
EXPORT_TRACEPOINT_SYMBOL(smc_switch_to_fallback);
EXPORT_TRACEPOINT_SYMBOL(smc_tx_sendmsg);
EXPORT_TRACEPOINT_SYMBOL(smc_rx_recvmsg);
EXPORT_TRACEPOINT_SYMBOL(smcr_link_down);
+125
View File
@@ -0,0 +1,125 @@
/* SPDX-License-Identifier: GPL-2.0 */
#undef TRACE_SYSTEM
#define TRACE_SYSTEM smc
#if !defined(_TRACE_SMC_H) || defined(TRACE_HEADER_MULTI_READ)
#define _TRACE_SMC_H
#include <linux/ipv6.h>
#include <linux/tcp.h>
#include <linux/tracepoint.h>
#include <net/ipv6.h>
#include "smc.h"
#include "smc_core.h"
TRACE_EVENT(smc_switch_to_fallback,
TP_PROTO(const struct smc_sock *smc, int fallback_rsn),
TP_ARGS(smc, fallback_rsn),
TP_STRUCT__entry(
__field(const void *, sk)
__field(const void *, clcsk)
__field(u64, net_cookie)
__field(int, fallback_rsn)
),
TP_fast_assign(
const struct sock *sk = &smc->sk;
const struct sock *clcsk = smc->clcsock->sk;
__entry->sk = sk;
__entry->clcsk = clcsk;
__entry->net_cookie = sock_net(sk)->net_cookie;
__entry->fallback_rsn = fallback_rsn;
),
TP_printk("sk=%p clcsk=%p net=%llu fallback_rsn=%d",
__entry->sk, __entry->clcsk,
__entry->net_cookie, __entry->fallback_rsn)
);
DECLARE_EVENT_CLASS(smc_msg_event,
TP_PROTO(const struct smc_sock *smc, size_t len),
TP_ARGS(smc, len),
TP_STRUCT__entry(
__field(const void *, smc)
__field(u64, net_cookie)
__field(size_t, len)
__string(name, smc->conn.lnk ? smc->conn.lnk->ibname : "")
),
TP_fast_assign(
const struct sock *sk = &smc->sk;
__entry->smc = smc;
__entry->net_cookie = sock_net(sk)->net_cookie;
__entry->len = len;
__assign_str(name);
),
TP_printk("smc=%p net=%llu len=%zu dev=%s",
__entry->smc, __entry->net_cookie,
__entry->len, __get_str(name))
);
DEFINE_EVENT(smc_msg_event, smc_tx_sendmsg,
TP_PROTO(const struct smc_sock *smc, size_t len),
TP_ARGS(smc, len)
);
DEFINE_EVENT(smc_msg_event, smc_rx_recvmsg,
TP_PROTO(const struct smc_sock *smc, size_t len),
TP_ARGS(smc, len)
);
TRACE_EVENT(smcr_link_down,
TP_PROTO(const struct smc_link *lnk, void *location),
TP_ARGS(lnk, location),
TP_STRUCT__entry(
__field(const void *, lnk)
__field(const void *, lgr)
__field(u64, net_cookie)
__field(int, state)
__string(name, lnk->ibname)
__field(void *, location)
),
TP_fast_assign(
const struct smc_link_group *lgr = lnk->lgr;
__entry->lnk = lnk;
__entry->lgr = lgr;
__entry->net_cookie = lgr->net->net_cookie;
__entry->state = lnk->state;
__assign_str(name);
__entry->location = location;
),
TP_printk("lnk=%p lgr=%p net=%llu state=%d dev=%s location=%pS",
__entry->lnk, __entry->lgr, __entry->net_cookie,
__entry->state, __get_str(name),
__entry->location)
);
#endif /* _TRACE_SMC_H */
#undef TRACE_INCLUDE_PATH
#define TRACE_INCLUDE_PATH .
#undef TRACE_INCLUDE_FILE
#define TRACE_INCLUDE_FILE smc_tracepoint
#include <trace/define_trace.h>
+737
View File
@@ -0,0 +1,737 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Shared Memory Communications over RDMA (SMC-R) and RoCE
*
* Manage send buffer.
* Producer:
* Copy user space data into send buffer, if send buffer space available.
* Consumer:
* Trigger RDMA write into RMBE of peer and send CDC, if RMBE space available.
*
* Copyright IBM Corp. 2016
*
* Author(s): Ursula Braun <ubraun@linux.vnet.ibm.com>
*/
#include <linux/net.h>
#include <linux/rcupdate.h>
#include <linux/workqueue.h>
#include <linux/sched/signal.h>
#include <net/sock.h>
#include <net/tcp.h>
#include "smc.h"
#include "smc_wr.h"
#include "smc_cdc.h"
#include "smc_close.h"
#include "smc_ism.h"
#include "smc_tx.h"
#include "smc_stats.h"
#include "smc_tracepoint.h"
#define SMC_TX_WORK_DELAY 0
/***************************** sndbuf producer *******************************/
/* callback implementation for sk.sk_write_space()
* to wakeup sndbuf producers that blocked with smc_tx_wait().
* called under sk_socket lock.
*/
static void smc_tx_write_space(struct sock *sk)
{
struct socket *sock = sk->sk_socket;
struct smc_sock *smc = smc_sk(sk);
struct socket_wq *wq;
/* similar to sk_stream_write_space */
if (atomic_read(&smc->conn.sndbuf_space) && sock) {
if (test_bit(SOCK_NOSPACE, &sock->flags))
SMC_STAT_RMB_TX_FULL(smc, !smc->conn.lnk);
clear_bit(SOCK_NOSPACE, &sock->flags);
rcu_read_lock();
wq = rcu_dereference(sk->sk_wq);
if (skwq_has_sleeper(wq))
wake_up_interruptible_poll(&wq->wait,
EPOLLOUT | EPOLLWRNORM |
EPOLLWRBAND);
if (wq && wq->fasync_list && !(sk->sk_shutdown & SEND_SHUTDOWN))
sock_wake_async(wq, SOCK_WAKE_SPACE, POLL_OUT);
rcu_read_unlock();
}
}
/* Wakeup sndbuf producers that blocked with smc_tx_wait().
* Cf. tcp_data_snd_check()=>tcp_check_space()=>tcp_new_space().
*/
void smc_tx_sndbuf_nonfull(struct smc_sock *smc)
{
if (smc->sk.sk_socket &&
test_bit(SOCK_NOSPACE, &smc->sk.sk_socket->flags))
smc->sk.sk_write_space(&smc->sk);
}
/* blocks sndbuf producer until at least one byte of free space available
* or urgent Byte was consumed
*/
static int smc_tx_wait(struct smc_sock *smc, int flags)
{
DEFINE_WAIT_FUNC(wait, woken_wake_function);
struct smc_connection *conn = &smc->conn;
struct sock *sk = &smc->sk;
long timeo;
int rc = 0;
/* similar to sk_stream_wait_memory */
timeo = sock_sndtimeo(sk, flags & MSG_DONTWAIT);
add_wait_queue(sk_sleep(sk), &wait);
while (1) {
sk_set_bit(SOCKWQ_ASYNC_NOSPACE, sk);
if (sk->sk_err ||
(sk->sk_shutdown & SEND_SHUTDOWN) ||
conn->killed ||
conn->local_tx_ctrl.conn_state_flags.peer_done_writing) {
rc = -EPIPE;
break;
}
if (smc_cdc_rxed_any_close(conn)) {
rc = -ECONNRESET;
break;
}
if (!timeo) {
/* ensure EPOLLOUT is subsequently generated */
set_bit(SOCK_NOSPACE, &sk->sk_socket->flags);
rc = -EAGAIN;
break;
}
if (signal_pending(current)) {
rc = sock_intr_errno(timeo);
break;
}
sk_clear_bit(SOCKWQ_ASYNC_NOSPACE, sk);
if (atomic_read(&conn->sndbuf_space) && !conn->urg_tx_pend)
break; /* at least 1 byte of free & no urgent data */
set_bit(SOCK_NOSPACE, &sk->sk_socket->flags);
sk_wait_event(sk, &timeo,
READ_ONCE(sk->sk_err) ||
(READ_ONCE(sk->sk_shutdown) & SEND_SHUTDOWN) ||
smc_cdc_rxed_any_close(conn) ||
(atomic_read(&conn->sndbuf_space) &&
!conn->urg_tx_pend),
&wait);
}
remove_wait_queue(sk_sleep(sk), &wait);
return rc;
}
static bool smc_tx_is_corked(struct smc_sock *smc)
{
struct tcp_sock *tp = tcp_sk(smc->clcsock->sk);
return (tp->nonagle & TCP_NAGLE_CORK) ? true : false;
}
/* If we have pending CDC messages, do not send:
* Because CQE of this CDC message will happen shortly, it gives
* a chance to coalesce future sendmsg() payload in to one RDMA Write,
* without need for a timer, and with no latency trade off.
* Algorithm here:
* 1. First message should never cork
* 2. If we have pending Tx CDC messages, wait for the first CDC
* message's completion
* 3. Don't cork to much data in a single RDMA Write to prevent burst
* traffic, total corked message should not exceed sendbuf/2
*/
static bool smc_should_autocork(struct smc_sock *smc)
{
struct smc_connection *conn = &smc->conn;
int corking_size;
corking_size = min_t(unsigned int, conn->sndbuf_desc->len >> 1,
sock_net(&smc->sk)->smc.sysctl_autocorking_size);
if (atomic_read(&conn->cdc_pend_tx_wr) == 0 ||
smc_tx_prepared_sends(conn) > corking_size)
return false;
return true;
}
static bool smc_tx_should_cork(struct smc_sock *smc, struct msghdr *msg)
{
struct smc_connection *conn = &smc->conn;
if (smc_should_autocork(smc))
return true;
/* for a corked socket defer the RDMA writes if
* sndbuf_space is still available. The applications
* should known how/when to uncork it.
*/
if ((msg->msg_flags & MSG_MORE ||
smc_tx_is_corked(smc)) &&
atomic_read(&conn->sndbuf_space))
return true;
return false;
}
/* sndbuf producer: main API called by socket layer.
* called under sock lock.
*/
int smc_tx_sendmsg(struct smc_sock *smc, struct msghdr *msg, size_t len)
{
size_t copylen, send_done = 0, send_remaining = len;
size_t chunk_len, chunk_off, chunk_len_sum;
struct smc_connection *conn = &smc->conn;
union smc_host_cursor prep;
struct sock *sk = &smc->sk;
char *sndbuf_base;
int tx_cnt_prep;
int writespace;
int rc, chunk;
/* This should be in poll */
sk_clear_bit(SOCKWQ_ASYNC_NOSPACE, sk);
if (sk->sk_err || (sk->sk_shutdown & SEND_SHUTDOWN)) {
rc = -EPIPE;
goto out_err;
}
if (sk->sk_state == SMC_INIT)
return -ENOTCONN;
if (len > conn->sndbuf_desc->len)
SMC_STAT_RMB_TX_SIZE_SMALL(smc, !conn->lnk);
if (len > conn->peer_rmbe_size)
SMC_STAT_RMB_TX_PEER_SIZE_SMALL(smc, !conn->lnk);
if (msg->msg_flags & MSG_OOB)
SMC_STAT_INC(smc, urg_data_cnt);
while (msg_data_left(msg)) {
if (smc->sk.sk_shutdown & SEND_SHUTDOWN ||
(smc->sk.sk_err == ECONNABORTED) ||
conn->killed)
return -EPIPE;
if (smc_cdc_rxed_any_close(conn))
return send_done ?: -ECONNRESET;
if (msg->msg_flags & MSG_OOB)
conn->local_tx_ctrl.prod_flags.urg_data_pending = 1;
if (!atomic_read(&conn->sndbuf_space) || conn->urg_tx_pend) {
if (send_done)
return send_done;
rc = smc_tx_wait(smc, msg->msg_flags);
if (rc)
goto out_err;
continue;
}
/* initialize variables for 1st iteration of subsequent loop */
/* could be just 1 byte, even after smc_tx_wait above */
writespace = atomic_read(&conn->sndbuf_space);
/* not more than what user space asked for */
copylen = min_t(size_t, send_remaining, writespace);
/* determine start of sndbuf */
sndbuf_base = conn->sndbuf_desc->cpu_addr;
smc_curs_copy(&prep, &conn->tx_curs_prep, conn);
tx_cnt_prep = prep.count;
/* determine chunks where to write into sndbuf */
/* either unwrapped case, or 1st chunk of wrapped case */
chunk_len = min_t(size_t, copylen, conn->sndbuf_desc->len -
tx_cnt_prep);
chunk_len_sum = chunk_len;
chunk_off = tx_cnt_prep;
for (chunk = 0; chunk < 2; chunk++) {
rc = memcpy_from_msg(sndbuf_base + chunk_off,
msg, chunk_len);
if (rc) {
smc_sndbuf_sync_sg_for_device(conn);
if (send_done)
return send_done;
goto out_err;
}
send_done += chunk_len;
send_remaining -= chunk_len;
if (chunk_len_sum == copylen)
break; /* either on 1st or 2nd iteration */
/* prepare next (== 2nd) iteration */
chunk_len = copylen - chunk_len; /* remainder */
chunk_len_sum += chunk_len;
chunk_off = 0; /* modulo offset in send ring buffer */
}
smc_sndbuf_sync_sg_for_device(conn);
/* update cursors */
smc_curs_add(conn->sndbuf_desc->len, &prep, copylen);
smc_curs_copy(&conn->tx_curs_prep, &prep, conn);
/* increased in send tasklet smc_cdc_tx_handler() */
smp_mb__before_atomic();
atomic_sub(copylen, &conn->sndbuf_space);
/* guarantee 0 <= sndbuf_space <= sndbuf_desc->len */
smp_mb__after_atomic();
/* since we just produced more new data into sndbuf,
* trigger sndbuf consumer: RDMA write into peer RMBE and CDC
*/
if ((msg->msg_flags & MSG_OOB) && !send_remaining)
conn->urg_tx_pend = true;
/* If we need to cork, do nothing and wait for the next
* sendmsg() call or push on tx completion
*/
if (!smc_tx_should_cork(smc, msg))
smc_tx_sndbuf_nonempty(conn);
trace_smc_tx_sendmsg(smc, copylen);
} /* while (msg_data_left(msg)) */
return send_done;
out_err:
rc = sk_stream_error(sk, msg->msg_flags, rc);
/* make sure we wake any epoll edge trigger waiter */
if (unlikely(rc == -EAGAIN))
sk->sk_write_space(sk);
return rc;
}
/***************************** sndbuf consumer *******************************/
/* sndbuf consumer: actual data transfer of one target chunk with ISM write */
int smcd_tx_ism_write(struct smc_connection *conn, void *data, size_t len,
u32 offset, int signal)
{
int rc;
rc = smc_ism_write(conn->lgr->smcd, conn->peer_token,
conn->peer_rmbe_idx, signal, conn->tx_off + offset,
data, len);
if (rc)
conn->local_tx_ctrl.conn_state_flags.peer_conn_abort = 1;
return rc;
}
/* sndbuf consumer: actual data transfer of one target chunk with RDMA write */
static int smc_tx_rdma_write(struct smc_connection *conn, int peer_rmbe_offset,
int num_sges, struct ib_rdma_wr *rdma_wr)
{
struct smc_link_group *lgr = conn->lgr;
struct smc_link *link = conn->lnk;
int rc;
rdma_wr->wr.wr_id = smc_wr_tx_get_next_wr_id(link);
rdma_wr->wr.num_sge = num_sges;
rdma_wr->remote_addr =
lgr->rtokens[conn->rtoken_idx][link->link_idx].dma_addr +
/* RMBE within RMB */
conn->tx_off +
/* offset within RMBE */
peer_rmbe_offset;
rdma_wr->rkey = lgr->rtokens[conn->rtoken_idx][link->link_idx].rkey;
rc = ib_post_send(link->roce_qp, &rdma_wr->wr, NULL);
if (rc)
smcr_link_down_cond_sched(link);
return rc;
}
/* sndbuf consumer */
static inline void smc_tx_advance_cursors(struct smc_connection *conn,
union smc_host_cursor *prod,
union smc_host_cursor *sent,
size_t len)
{
smc_curs_add(conn->peer_rmbe_size, prod, len);
/* increased in recv tasklet smc_cdc_msg_rcv() */
smp_mb__before_atomic();
/* data in flight reduces usable snd_wnd */
atomic_sub(len, &conn->peer_rmbe_space);
/* guarantee 0 <= peer_rmbe_space <= peer_rmbe_size */
smp_mb__after_atomic();
smc_curs_add(conn->sndbuf_desc->len, sent, len);
}
/* SMC-R helper for smc_tx_rdma_writes() */
static int smcr_tx_rdma_writes(struct smc_connection *conn, size_t len,
size_t src_off, size_t src_len,
size_t dst_off, size_t dst_len,
struct smc_rdma_wr *wr_rdma_buf)
{
struct smc_link *link = conn->lnk;
dma_addr_t dma_addr =
sg_dma_address(conn->sndbuf_desc->sgt[link->link_idx].sgl);
u64 virt_addr = (uintptr_t)conn->sndbuf_desc->cpu_addr;
int src_len_sum = src_len, dst_len_sum = dst_len;
int sent_count = src_off;
int srcchunk, dstchunk;
int num_sges;
int rc;
for (dstchunk = 0; dstchunk < 2; dstchunk++) {
struct ib_rdma_wr *wr = &wr_rdma_buf->wr_tx_rdma[dstchunk];
struct ib_sge *sge = wr->wr.sg_list;
u64 base_addr = dma_addr;
if (dst_len < link->qp_attr.cap.max_inline_data) {
base_addr = virt_addr;
wr->wr.send_flags |= IB_SEND_INLINE;
} else {
wr->wr.send_flags &= ~IB_SEND_INLINE;
}
num_sges = 0;
for (srcchunk = 0; srcchunk < 2; srcchunk++) {
sge[srcchunk].addr = conn->sndbuf_desc->is_vm ?
(virt_addr + src_off) : (base_addr + src_off);
sge[srcchunk].length = src_len;
if (conn->sndbuf_desc->is_vm)
sge[srcchunk].lkey =
conn->sndbuf_desc->mr[link->link_idx]->lkey;
num_sges++;
src_off += src_len;
if (src_off >= conn->sndbuf_desc->len)
src_off -= conn->sndbuf_desc->len;
/* modulo in send ring */
if (src_len_sum == dst_len)
break; /* either on 1st or 2nd iteration */
/* prepare next (== 2nd) iteration */
src_len = dst_len - src_len; /* remainder */
src_len_sum += src_len;
}
rc = smc_tx_rdma_write(conn, dst_off, num_sges, wr);
if (rc)
return rc;
if (dst_len_sum == len)
break; /* either on 1st or 2nd iteration */
/* prepare next (== 2nd) iteration */
dst_off = 0; /* modulo offset in RMBE ring buffer */
dst_len = len - dst_len; /* remainder */
dst_len_sum += dst_len;
src_len = min_t(int, dst_len, conn->sndbuf_desc->len -
sent_count);
src_len_sum = src_len;
}
return 0;
}
/* SMC-D helper for smc_tx_rdma_writes() */
static int smcd_tx_rdma_writes(struct smc_connection *conn, size_t len,
size_t src_off, size_t src_len,
size_t dst_off, size_t dst_len)
{
int src_len_sum = src_len, dst_len_sum = dst_len;
int srcchunk, dstchunk;
int rc;
if (conn->sndbuf_desc->is_attached)
return 0;
for (dstchunk = 0; dstchunk < 2; dstchunk++) {
for (srcchunk = 0; srcchunk < 2; srcchunk++) {
void *data = conn->sndbuf_desc->cpu_addr + src_off;
rc = smcd_tx_ism_write(conn, data, src_len, dst_off +
sizeof(struct smcd_cdc_msg), 0);
if (rc)
return rc;
dst_off += src_len;
src_off += src_len;
if (src_off >= conn->sndbuf_desc->len)
src_off -= conn->sndbuf_desc->len;
/* modulo in send ring */
if (src_len_sum == dst_len)
break; /* either on 1st or 2nd iteration */
/* prepare next (== 2nd) iteration */
src_len = dst_len - src_len; /* remainder */
src_len_sum += src_len;
}
if (dst_len_sum == len)
break; /* either on 1st or 2nd iteration */
/* prepare next (== 2nd) iteration */
dst_off = 0; /* modulo offset in RMBE ring buffer */
dst_len = len - dst_len; /* remainder */
dst_len_sum += dst_len;
src_len = min_t(int, dst_len, conn->sndbuf_desc->len - src_off);
src_len_sum = src_len;
}
return 0;
}
/* sndbuf consumer: prepare all necessary (src&dst) chunks of data transmit;
* usable snd_wnd as max transmit
*/
static int smc_tx_rdma_writes(struct smc_connection *conn,
struct smc_rdma_wr *wr_rdma_buf)
{
size_t len, src_len, dst_off, dst_len; /* current chunk values */
union smc_host_cursor sent, prep, prod, cons;
struct smc_cdc_producer_flags *pflags;
int to_send, rmbespace;
int rc;
/* source: sndbuf */
smc_curs_copy(&sent, &conn->tx_curs_sent, conn);
smc_curs_copy(&prep, &conn->tx_curs_prep, conn);
/* cf. wmem_alloc - (snd_max - snd_una) */
to_send = smc_curs_diff(conn->sndbuf_desc->len, &sent, &prep);
if (to_send <= 0)
return 0;
/* destination: RMBE */
/* cf. snd_wnd */
rmbespace = atomic_read(&conn->peer_rmbe_space);
if (rmbespace <= 0) {
struct smc_sock *smc = container_of(conn, struct smc_sock,
conn);
SMC_STAT_RMB_TX_PEER_FULL(smc, !conn->lnk);
return 0;
}
smc_curs_copy(&prod, &conn->local_tx_ctrl.prod, conn);
smc_curs_copy(&cons, &conn->local_rx_ctrl.cons, conn);
/* if usable snd_wnd closes ask peer to advertise once it opens again */
pflags = &conn->local_tx_ctrl.prod_flags;
pflags->write_blocked = (to_send >= rmbespace);
/* cf. usable snd_wnd */
len = min(to_send, rmbespace);
/* initialize variables for first iteration of subsequent nested loop */
dst_off = prod.count;
if (prod.wrap == cons.wrap) {
/* the filled destination area is unwrapped,
* hence the available free destination space is wrapped
* and we need 2 destination chunks of sum len; start with 1st
* which is limited by what's available in sndbuf
*/
dst_len = min_t(size_t,
conn->peer_rmbe_size - prod.count, len);
} else {
/* the filled destination area is wrapped,
* hence the available free destination space is unwrapped
* and we need a single destination chunk of entire len
*/
dst_len = len;
}
/* dst_len determines the maximum src_len */
if (sent.count + dst_len <= conn->sndbuf_desc->len) {
/* unwrapped src case: single chunk of entire dst_len */
src_len = dst_len;
} else {
/* wrapped src case: 2 chunks of sum dst_len; start with 1st: */
src_len = conn->sndbuf_desc->len - sent.count;
}
if (conn->lgr->is_smcd)
rc = smcd_tx_rdma_writes(conn, len, sent.count, src_len,
dst_off, dst_len);
else
rc = smcr_tx_rdma_writes(conn, len, sent.count, src_len,
dst_off, dst_len, wr_rdma_buf);
if (rc)
return rc;
if (conn->urg_tx_pend && len == to_send)
pflags->urg_data_present = 1;
smc_tx_advance_cursors(conn, &prod, &sent, len);
/* update connection's cursors with advanced local cursors */
smc_curs_copy(&conn->local_tx_ctrl.prod, &prod, conn);
/* dst: peer RMBE */
smc_curs_copy(&conn->tx_curs_sent, &sent, conn);/* src: local sndbuf */
return 0;
}
/* Wakeup sndbuf consumers from any context (IRQ or process)
* since there is more data to transmit; usable snd_wnd as max transmit
*/
static int smcr_tx_sndbuf_nonempty(struct smc_connection *conn)
{
struct smc_cdc_producer_flags *pflags = &conn->local_tx_ctrl.prod_flags;
struct smc_link *link = conn->lnk;
struct smc_rdma_wr *wr_rdma_buf;
struct smc_cdc_tx_pend *pend;
struct smc_wr_buf *wr_buf;
int rc;
if (!link || !smc_wr_tx_link_hold(link))
return -ENOLINK;
rc = smc_cdc_get_free_slot(conn, link, &wr_buf, &wr_rdma_buf, &pend);
if (rc < 0) {
smc_wr_tx_link_put(link);
if (rc == -EBUSY) {
struct smc_sock *smc =
container_of(conn, struct smc_sock, conn);
if (smc->sk.sk_err == ECONNABORTED)
return sock_error(&smc->sk);
if (conn->killed)
return -EPIPE;
rc = 0;
mod_delayed_work(conn->lgr->tx_wq, &conn->tx_work,
SMC_TX_WORK_DELAY);
}
return rc;
}
spin_lock_bh(&conn->send_lock);
if (link != conn->lnk) {
/* link of connection changed, tx_work will restart */
smc_wr_tx_put_slot(link,
(struct smc_wr_tx_pend_priv *)pend);
rc = -ENOLINK;
goto out_unlock;
}
if (!pflags->urg_data_present) {
rc = smc_tx_rdma_writes(conn, wr_rdma_buf);
if (rc) {
smc_wr_tx_put_slot(link,
(struct smc_wr_tx_pend_priv *)pend);
goto out_unlock;
}
}
rc = smc_cdc_msg_send(conn, wr_buf, pend);
if (!rc && pflags->urg_data_present) {
pflags->urg_data_pending = 0;
pflags->urg_data_present = 0;
}
out_unlock:
spin_unlock_bh(&conn->send_lock);
smc_wr_tx_link_put(link);
return rc;
}
static int smcd_tx_sndbuf_nonempty(struct smc_connection *conn)
{
struct smc_cdc_producer_flags *pflags = &conn->local_tx_ctrl.prod_flags;
int rc = 0;
spin_lock_bh(&conn->send_lock);
if (!pflags->urg_data_present)
rc = smc_tx_rdma_writes(conn, NULL);
if (!rc)
rc = smcd_cdc_msg_send(conn);
if (!rc && pflags->urg_data_present) {
pflags->urg_data_pending = 0;
pflags->urg_data_present = 0;
}
spin_unlock_bh(&conn->send_lock);
return rc;
}
int smc_tx_sndbuf_nonempty(struct smc_connection *conn)
{
struct smc_sock *smc = container_of(conn, struct smc_sock, conn);
int rc = 0;
/* No data in the send queue */
if (unlikely(smc_tx_prepared_sends(conn) <= 0))
goto out;
/* Peer don't have RMBE space */
if (unlikely(atomic_read(&conn->peer_rmbe_space) <= 0)) {
SMC_STAT_RMB_TX_PEER_FULL(smc, !conn->lnk);
goto out;
}
if (conn->killed ||
conn->local_rx_ctrl.conn_state_flags.peer_conn_abort) {
rc = -EPIPE; /* connection being aborted */
goto out;
}
if (conn->lgr->is_smcd)
rc = smcd_tx_sndbuf_nonempty(conn);
else
rc = smcr_tx_sndbuf_nonempty(conn);
if (!rc) {
/* trigger socket release if connection is closing */
smc_close_wake_tx_prepared(smc);
}
out:
return rc;
}
/* Wakeup sndbuf consumers from process context
* since there is more data to transmit. The caller
* must hold sock lock.
*/
void smc_tx_pending(struct smc_connection *conn)
{
struct smc_sock *smc = container_of(conn, struct smc_sock, conn);
int rc;
if (smc->sk.sk_err)
return;
rc = smc_tx_sndbuf_nonempty(conn);
if (!rc && conn->local_rx_ctrl.prod_flags.write_blocked &&
!atomic_read(&conn->bytes_to_rcv))
conn->local_rx_ctrl.prod_flags.write_blocked = 0;
}
/* Wakeup sndbuf consumers from process context
* since there is more data to transmit in locked
* sock.
*/
void smc_tx_work(struct work_struct *work)
{
struct smc_connection *conn = container_of(to_delayed_work(work),
struct smc_connection,
tx_work);
struct smc_sock *smc = container_of(conn, struct smc_sock, conn);
lock_sock(&smc->sk);
smc_tx_pending(conn);
release_sock(&smc->sk);
}
void smc_tx_consumer_update(struct smc_connection *conn, bool force)
{
union smc_host_cursor cfed, cons, prod;
int sender_free = conn->rmb_desc->len;
int to_confirm;
smc_curs_copy(&cons, &conn->local_tx_ctrl.cons, conn);
smc_curs_copy(&cfed, &conn->rx_curs_confirmed, conn);
to_confirm = smc_curs_diff(conn->rmb_desc->len, &cfed, &cons);
if (to_confirm > conn->rmbe_update_limit) {
smc_curs_copy(&prod, &conn->local_rx_ctrl.prod, conn);
sender_free = conn->rmb_desc->len -
smc_curs_diff_large(conn->rmb_desc->len,
&cfed, &prod);
}
if (conn->local_rx_ctrl.prod_flags.cons_curs_upd_req ||
force ||
((to_confirm > conn->rmbe_update_limit) &&
((sender_free <= (conn->rmb_desc->len / 2)) ||
conn->local_rx_ctrl.prod_flags.write_blocked))) {
if (conn->killed ||
conn->local_rx_ctrl.conn_state_flags.peer_conn_abort)
return;
if ((smc_cdc_get_slot_and_msg_send(conn) < 0) &&
!conn->killed) {
queue_delayed_work(conn->lgr->tx_wq, &conn->tx_work,
SMC_TX_WORK_DELAY);
return;
}
}
if (conn->local_rx_ctrl.prod_flags.write_blocked &&
!atomic_read(&conn->bytes_to_rcv))
conn->local_rx_ctrl.prod_flags.write_blocked = 0;
}
/***************************** send initialize *******************************/
/* Initialize send properties on connection establishment. NB: not __init! */
void smc_tx_init(struct smc_sock *smc)
{
smc->sk.sk_write_space = smc_tx_write_space;
}
+40
View File
@@ -0,0 +1,40 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Shared Memory Communications over RDMA (SMC-R) and RoCE
*
* Manage send buffer
*
* Copyright IBM Corp. 2016
*
* Author(s): Ursula Braun <ubraun@linux.vnet.ibm.com>
*/
#ifndef SMC_TX_H
#define SMC_TX_H
#include <linux/socket.h>
#include <linux/types.h>
#include "smc.h"
#include "smc_cdc.h"
static inline int smc_tx_prepared_sends(struct smc_connection *conn)
{
union smc_host_cursor sent, prep;
smc_curs_copy(&sent, &conn->tx_curs_sent, conn);
smc_curs_copy(&prep, &conn->tx_curs_prep, conn);
return smc_curs_diff(conn->sndbuf_desc->len, &sent, &prep);
}
void smc_tx_pending(struct smc_connection *conn);
void smc_tx_work(struct work_struct *work);
void smc_tx_init(struct smc_sock *smc);
int smc_tx_sendmsg(struct smc_sock *smc, struct msghdr *msg, size_t len);
int smc_tx_sndbuf_nonempty(struct smc_connection *conn);
void smc_tx_sndbuf_nonfull(struct smc_sock *smc);
void smc_tx_consumer_update(struct smc_connection *conn, bool force);
int smcd_tx_ism_write(struct smc_connection *conn, void *data, size_t len,
u32 offset, int signal);
#endif /* SMC_TX_H */
+933
View File
@@ -0,0 +1,933 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Shared Memory Communications over RDMA (SMC-R) and RoCE
*
* Work Requests exploiting Infiniband API
*
* Work requests (WR) of type ib_post_send or ib_post_recv respectively
* are submitted to either RC SQ or RC RQ respectively
* (reliably connected send/receive queue)
* and become work queue entries (WQEs).
* While an SQ WR/WQE is pending, we track it until transmission completion.
* Through a send or receive completion queue (CQ) respectively,
* we get completion queue entries (CQEs) [aka work completions (WCs)].
* Since the CQ callback is called from IRQ context, we split work by using
* bottom halves implemented by tasklets.
*
* SMC uses this to exchange LLC (link layer control)
* and CDC (connection data control) messages.
*
* Copyright IBM Corp. 2016
*
* Author(s): Steffen Maier <maier@linux.vnet.ibm.com>
*/
#include <linux/atomic.h>
#include <linux/hashtable.h>
#include <linux/wait.h>
#include <rdma/ib_verbs.h>
#include <asm/div64.h>
#include "smc.h"
#include "smc_wr.h"
#define SMC_WR_MAX_POLL_CQE 10 /* max. # of compl. queue elements in 1 poll */
#define SMC_WR_RX_HASH_BITS 4
static DEFINE_HASHTABLE(smc_wr_rx_hash, SMC_WR_RX_HASH_BITS);
static DEFINE_SPINLOCK(smc_wr_rx_hash_lock);
struct smc_wr_tx_pend { /* control data for a pending send request */
u64 wr_id; /* work request id sent */
smc_wr_tx_handler handler;
enum ib_wc_status wc_status; /* CQE status */
struct smc_link *link;
u32 idx;
struct smc_wr_tx_pend_priv priv;
u8 compl_requested;
};
/******************************** send queue *********************************/
/*------------------------------- completion --------------------------------*/
/* returns true if at least one tx work request is pending on the given link */
static inline bool smc_wr_is_tx_pend(struct smc_link *link)
{
return !bitmap_empty(link->wr_tx_mask, link->wr_tx_cnt);
}
/* wait till all pending tx work requests on the given link are completed */
void smc_wr_tx_wait_no_pending_sends(struct smc_link *link)
{
wait_event(link->wr_tx_wait, !smc_wr_is_tx_pend(link));
}
static inline int smc_wr_tx_find_pending_index(struct smc_link *link, u64 wr_id)
{
u32 i;
for (i = 0; i < link->wr_tx_cnt; i++) {
if (link->wr_tx_pends[i].wr_id == wr_id)
return i;
}
return link->wr_tx_cnt;
}
static inline void smc_wr_tx_process_cqe(struct ib_wc *wc)
{
struct smc_wr_tx_pend pnd_snd;
struct smc_link *link;
u32 pnd_snd_idx;
link = wc->qp->qp_context;
if (wc->opcode == IB_WC_REG_MR) {
if (wc->status)
link->wr_reg_state = FAILED;
else
link->wr_reg_state = CONFIRMED;
smc_wr_wakeup_reg_wait(link);
return;
}
pnd_snd_idx = smc_wr_tx_find_pending_index(link, wc->wr_id);
if (pnd_snd_idx == link->wr_tx_cnt) {
if (link->lgr->smc_version != SMC_V2 ||
link->wr_tx_v2_pend->wr_id != wc->wr_id)
return;
link->wr_tx_v2_pend->wc_status = wc->status;
memcpy(&pnd_snd, link->wr_tx_v2_pend, sizeof(pnd_snd));
/* clear the full struct smc_wr_tx_pend including .priv */
memset(link->wr_tx_v2_pend, 0,
sizeof(*link->wr_tx_v2_pend));
memset(link->lgr->wr_tx_buf_v2, 0,
sizeof(*link->lgr->wr_tx_buf_v2));
} else {
link->wr_tx_pends[pnd_snd_idx].wc_status = wc->status;
if (link->wr_tx_pends[pnd_snd_idx].compl_requested)
complete(&link->wr_tx_compl[pnd_snd_idx]);
memcpy(&pnd_snd, &link->wr_tx_pends[pnd_snd_idx],
sizeof(pnd_snd));
/* clear the full struct smc_wr_tx_pend including .priv */
memset(&link->wr_tx_pends[pnd_snd_idx], 0,
sizeof(link->wr_tx_pends[pnd_snd_idx]));
memset(&link->wr_tx_bufs[pnd_snd_idx], 0,
sizeof(link->wr_tx_bufs[pnd_snd_idx]));
if (!test_and_clear_bit(pnd_snd_idx, link->wr_tx_mask))
return;
}
if (wc->status) {
if (link->lgr->smc_version == SMC_V2) {
memset(link->wr_tx_v2_pend, 0,
sizeof(*link->wr_tx_v2_pend));
memset(link->lgr->wr_tx_buf_v2, 0,
sizeof(*link->lgr->wr_tx_buf_v2));
}
/* terminate link */
smcr_link_down_cond_sched(link);
}
if (pnd_snd.handler)
pnd_snd.handler(&pnd_snd.priv, link, wc->status);
wake_up(&link->wr_tx_wait);
}
static void smc_wr_tx_tasklet_fn(struct tasklet_struct *t)
{
struct smc_ib_device *dev = from_tasklet(dev, t, send_tasklet);
struct ib_wc wc[SMC_WR_MAX_POLL_CQE];
int i = 0, rc;
int polled = 0;
again:
polled++;
do {
memset(&wc, 0, sizeof(wc));
rc = ib_poll_cq(dev->roce_cq_send, SMC_WR_MAX_POLL_CQE, wc);
if (polled == 1) {
ib_req_notify_cq(dev->roce_cq_send,
IB_CQ_NEXT_COMP |
IB_CQ_REPORT_MISSED_EVENTS);
}
if (!rc)
break;
for (i = 0; i < rc; i++)
smc_wr_tx_process_cqe(&wc[i]);
} while (rc > 0);
if (polled == 1)
goto again;
}
void smc_wr_tx_cq_handler(struct ib_cq *ib_cq, void *cq_context)
{
struct smc_ib_device *dev = (struct smc_ib_device *)cq_context;
tasklet_schedule(&dev->send_tasklet);
}
/*---------------------------- request submission ---------------------------*/
static inline int smc_wr_tx_get_free_slot_index(struct smc_link *link, u32 *idx)
{
*idx = link->wr_tx_cnt;
if (!smc_link_sendable(link))
return -ENOLINK;
for_each_clear_bit(*idx, link->wr_tx_mask, link->wr_tx_cnt) {
if (!test_and_set_bit(*idx, link->wr_tx_mask))
return 0;
}
*idx = link->wr_tx_cnt;
return -EBUSY;
}
/**
* smc_wr_tx_get_free_slot() - returns buffer for message assembly,
* and sets info for pending transmit tracking
* @link: Pointer to smc_link used to later send the message.
* @handler: Send completion handler function pointer.
* @wr_buf: Out value returns pointer to message buffer.
* @wr_rdma_buf: Out value returns pointer to rdma work request.
* @wr_pend_priv: Out value returns pointer serving as handler context.
*
* Return: 0 on success, or -errno on error.
*/
int smc_wr_tx_get_free_slot(struct smc_link *link,
smc_wr_tx_handler handler,
struct smc_wr_buf **wr_buf,
struct smc_rdma_wr **wr_rdma_buf,
struct smc_wr_tx_pend_priv **wr_pend_priv)
{
struct smc_link_group *lgr = smc_get_lgr(link);
struct smc_wr_tx_pend *wr_pend;
u32 idx = link->wr_tx_cnt;
struct ib_send_wr *wr_ib;
u64 wr_id;
int rc;
*wr_buf = NULL;
*wr_pend_priv = NULL;
if (in_softirq() || lgr->terminating) {
rc = smc_wr_tx_get_free_slot_index(link, &idx);
if (rc)
return rc;
} else {
rc = wait_event_interruptible_timeout(
link->wr_tx_wait,
!smc_link_sendable(link) ||
lgr->terminating ||
(smc_wr_tx_get_free_slot_index(link, &idx) != -EBUSY),
SMC_WR_TX_WAIT_FREE_SLOT_TIME);
if (!rc) {
/* timeout - terminate link */
smcr_link_down_cond_sched(link);
return -EPIPE;
}
if (idx == link->wr_tx_cnt)
return -EPIPE;
}
wr_id = smc_wr_tx_get_next_wr_id(link);
wr_pend = &link->wr_tx_pends[idx];
wr_pend->wr_id = wr_id;
wr_pend->handler = handler;
wr_pend->link = link;
wr_pend->idx = idx;
wr_ib = &link->wr_tx_ibs[idx];
wr_ib->wr_id = wr_id;
*wr_buf = &link->wr_tx_bufs[idx];
if (wr_rdma_buf)
*wr_rdma_buf = &link->wr_tx_rdmas[idx];
*wr_pend_priv = &wr_pend->priv;
return 0;
}
int smc_wr_tx_get_v2_slot(struct smc_link *link,
smc_wr_tx_handler handler,
struct smc_wr_v2_buf **wr_buf,
struct smc_wr_tx_pend_priv **wr_pend_priv)
{
struct smc_wr_tx_pend *wr_pend;
struct ib_send_wr *wr_ib;
u64 wr_id;
if (link->wr_tx_v2_pend->idx == link->wr_tx_cnt)
return -EBUSY;
*wr_buf = NULL;
*wr_pend_priv = NULL;
wr_id = smc_wr_tx_get_next_wr_id(link);
wr_pend = link->wr_tx_v2_pend;
wr_pend->wr_id = wr_id;
wr_pend->handler = handler;
wr_pend->link = link;
wr_pend->idx = link->wr_tx_cnt;
wr_ib = link->wr_tx_v2_ib;
wr_ib->wr_id = wr_id;
*wr_buf = link->lgr->wr_tx_buf_v2;
*wr_pend_priv = &wr_pend->priv;
return 0;
}
int smc_wr_tx_put_slot(struct smc_link *link,
struct smc_wr_tx_pend_priv *wr_pend_priv)
{
struct smc_wr_tx_pend *pend;
pend = container_of(wr_pend_priv, struct smc_wr_tx_pend, priv);
if (pend->idx < link->wr_tx_cnt) {
u32 idx = pend->idx;
/* clear the full struct smc_wr_tx_pend including .priv */
memset(&link->wr_tx_pends[idx], 0,
sizeof(link->wr_tx_pends[idx]));
memset(&link->wr_tx_bufs[idx], 0,
sizeof(link->wr_tx_bufs[idx]));
test_and_clear_bit(idx, link->wr_tx_mask);
wake_up(&link->wr_tx_wait);
return 1;
} else if (link->lgr->smc_version == SMC_V2 &&
pend->idx == link->wr_tx_cnt) {
/* Large v2 buffer */
memset(&link->wr_tx_v2_pend, 0,
sizeof(link->wr_tx_v2_pend));
memset(&link->lgr->wr_tx_buf_v2, 0,
sizeof(link->lgr->wr_tx_buf_v2));
return 1;
}
return 0;
}
/* Send prepared WR slot via ib_post_send.
* @priv: pointer to smc_wr_tx_pend_priv identifying prepared message buffer
*/
int smc_wr_tx_send(struct smc_link *link, struct smc_wr_tx_pend_priv *priv)
{
struct smc_wr_tx_pend *pend;
int rc;
ib_req_notify_cq(link->smcibdev->roce_cq_send,
IB_CQ_NEXT_COMP | IB_CQ_REPORT_MISSED_EVENTS);
pend = container_of(priv, struct smc_wr_tx_pend, priv);
rc = ib_post_send(link->roce_qp, &link->wr_tx_ibs[pend->idx], NULL);
if (rc) {
smc_wr_tx_put_slot(link, priv);
smcr_link_down_cond_sched(link);
}
return rc;
}
int smc_wr_tx_v2_send(struct smc_link *link, struct smc_wr_tx_pend_priv *priv,
int len)
{
int rc;
link->wr_tx_v2_ib->sg_list[0].length = len;
ib_req_notify_cq(link->smcibdev->roce_cq_send,
IB_CQ_NEXT_COMP | IB_CQ_REPORT_MISSED_EVENTS);
rc = ib_post_send(link->roce_qp, link->wr_tx_v2_ib, NULL);
if (rc) {
smc_wr_tx_put_slot(link, priv);
smcr_link_down_cond_sched(link);
}
return rc;
}
/* Send prepared WR slot via ib_post_send and wait for send completion
* notification.
* @priv: pointer to smc_wr_tx_pend_priv identifying prepared message buffer
*/
int smc_wr_tx_send_wait(struct smc_link *link, struct smc_wr_tx_pend_priv *priv,
unsigned long timeout)
{
struct smc_wr_tx_pend *pend;
u32 pnd_idx;
int rc;
pend = container_of(priv, struct smc_wr_tx_pend, priv);
pend->compl_requested = 1;
pnd_idx = pend->idx;
init_completion(&link->wr_tx_compl[pnd_idx]);
rc = smc_wr_tx_send(link, priv);
if (rc)
return rc;
/* wait for completion by smc_wr_tx_process_cqe() */
rc = wait_for_completion_interruptible_timeout(
&link->wr_tx_compl[pnd_idx], timeout);
if (rc <= 0)
rc = -ENODATA;
if (rc > 0)
rc = 0;
return rc;
}
/* Register a memory region and wait for result. */
int smc_wr_reg_send(struct smc_link *link, struct ib_mr *mr)
{
int rc;
ib_req_notify_cq(link->smcibdev->roce_cq_send,
IB_CQ_NEXT_COMP | IB_CQ_REPORT_MISSED_EVENTS);
link->wr_reg_state = POSTED;
link->wr_reg.wr.wr_id = (u64)(uintptr_t)mr;
link->wr_reg.mr = mr;
link->wr_reg.key = mr->rkey;
rc = ib_post_send(link->roce_qp, &link->wr_reg.wr, NULL);
if (rc)
return rc;
percpu_ref_get(&link->wr_reg_refs);
rc = wait_event_interruptible_timeout(link->wr_reg_wait,
(link->wr_reg_state != POSTED),
SMC_WR_REG_MR_WAIT_TIME);
percpu_ref_put(&link->wr_reg_refs);
if (!rc) {
/* timeout - terminate link */
smcr_link_down_cond_sched(link);
return -EPIPE;
}
if (rc == -ERESTARTSYS)
return -EINTR;
switch (link->wr_reg_state) {
case CONFIRMED:
rc = 0;
break;
case FAILED:
rc = -EIO;
break;
case POSTED:
rc = -EPIPE;
break;
}
return rc;
}
/****************************** receive queue ********************************/
int smc_wr_rx_register_handler(struct smc_wr_rx_handler *handler)
{
struct smc_wr_rx_handler *h_iter;
int rc = 0;
spin_lock(&smc_wr_rx_hash_lock);
hash_for_each_possible(smc_wr_rx_hash, h_iter, list, handler->type) {
if (h_iter->type == handler->type) {
rc = -EEXIST;
goto out_unlock;
}
}
hash_add(smc_wr_rx_hash, &handler->list, handler->type);
out_unlock:
spin_unlock(&smc_wr_rx_hash_lock);
return rc;
}
/* Demultiplex a received work request based on the message type to its handler.
* Relies on smc_wr_rx_hash having been completely filled before any IB WRs,
* and not being modified any more afterwards so we don't need to lock it.
*/
static inline void smc_wr_rx_demultiplex(struct ib_wc *wc)
{
struct smc_link *link = (struct smc_link *)wc->qp->qp_context;
struct smc_wr_rx_handler *handler;
struct smc_wr_rx_hdr *wr_rx;
u64 temp_wr_id;
u32 index;
if (wc->byte_len < sizeof(*wr_rx))
return; /* short message */
temp_wr_id = wc->wr_id;
index = do_div(temp_wr_id, link->wr_rx_cnt);
wr_rx = (struct smc_wr_rx_hdr *)(link->wr_rx_bufs + index * link->wr_rx_buflen);
hash_for_each_possible(smc_wr_rx_hash, handler, list, wr_rx->type) {
if (handler->type == wr_rx->type)
handler->handler(wc, wr_rx);
}
}
static inline void smc_wr_rx_process_cqes(struct ib_wc wc[], int num)
{
struct smc_link *link;
int i;
for (i = 0; i < num; i++) {
link = wc[i].qp->qp_context;
link->wr_rx_id_compl = wc[i].wr_id;
if (wc[i].status == IB_WC_SUCCESS) {
link->wr_rx_tstamp = jiffies;
smc_wr_rx_demultiplex(&wc[i]);
smc_wr_rx_post(link); /* refill WR RX */
} else {
/* handle status errors */
switch (wc[i].status) {
case IB_WC_RETRY_EXC_ERR:
case IB_WC_RNR_RETRY_EXC_ERR:
case IB_WC_WR_FLUSH_ERR:
smcr_link_down_cond_sched(link);
if (link->wr_rx_id_compl == link->wr_rx_id)
wake_up(&link->wr_rx_empty_wait);
break;
default:
smc_wr_rx_post(link); /* refill WR RX */
break;
}
}
}
}
static void smc_wr_rx_tasklet_fn(struct tasklet_struct *t)
{
struct smc_ib_device *dev = from_tasklet(dev, t, recv_tasklet);
struct ib_wc wc[SMC_WR_MAX_POLL_CQE];
int polled = 0;
int rc;
again:
polled++;
do {
memset(&wc, 0, sizeof(wc));
rc = ib_poll_cq(dev->roce_cq_recv, SMC_WR_MAX_POLL_CQE, wc);
if (polled == 1) {
ib_req_notify_cq(dev->roce_cq_recv,
IB_CQ_SOLICITED_MASK
| IB_CQ_REPORT_MISSED_EVENTS);
}
if (!rc)
break;
smc_wr_rx_process_cqes(&wc[0], rc);
} while (rc > 0);
if (polled == 1)
goto again;
}
void smc_wr_rx_cq_handler(struct ib_cq *ib_cq, void *cq_context)
{
struct smc_ib_device *dev = (struct smc_ib_device *)cq_context;
tasklet_schedule(&dev->recv_tasklet);
}
int smc_wr_rx_post_init(struct smc_link *link)
{
u32 i;
int rc = 0;
for (i = 0; i < link->wr_rx_cnt; i++)
rc = smc_wr_rx_post(link);
return rc;
}
/***************************** init, exit, misc ******************************/
void smc_wr_remember_qp_attr(struct smc_link *lnk)
{
struct ib_qp_attr *attr = &lnk->qp_attr;
struct ib_qp_init_attr init_attr;
memset(attr, 0, sizeof(*attr));
memset(&init_attr, 0, sizeof(init_attr));
ib_query_qp(lnk->roce_qp, attr,
IB_QP_STATE |
IB_QP_CUR_STATE |
IB_QP_PKEY_INDEX |
IB_QP_PORT |
IB_QP_QKEY |
IB_QP_AV |
IB_QP_PATH_MTU |
IB_QP_TIMEOUT |
IB_QP_RETRY_CNT |
IB_QP_RNR_RETRY |
IB_QP_RQ_PSN |
IB_QP_ALT_PATH |
IB_QP_MIN_RNR_TIMER |
IB_QP_SQ_PSN |
IB_QP_PATH_MIG_STATE |
IB_QP_CAP |
IB_QP_DEST_QPN,
&init_attr);
lnk->wr_tx_cnt = min_t(size_t, lnk->max_send_wr,
lnk->qp_attr.cap.max_send_wr);
lnk->wr_rx_cnt = min_t(size_t, lnk->max_recv_wr,
lnk->qp_attr.cap.max_recv_wr);
}
static void smc_wr_init_sge(struct smc_link *lnk)
{
bool send_inline = (lnk->qp_attr.cap.max_inline_data > SMC_WR_TX_SIZE);
u32 i;
for (i = 0; i < lnk->wr_tx_cnt; i++) {
lnk->wr_tx_sges[i].addr = send_inline ? (uintptr_t)(&lnk->wr_tx_bufs[i]) :
lnk->wr_tx_dma_addr + i * SMC_WR_BUF_SIZE;
lnk->wr_tx_sges[i].length = SMC_WR_TX_SIZE;
lnk->wr_tx_sges[i].lkey = lnk->roce_pd->local_dma_lkey;
lnk->wr_tx_rdma_sges[i].tx_rdma_sge[0].wr_tx_rdma_sge[0].lkey =
lnk->roce_pd->local_dma_lkey;
lnk->wr_tx_rdma_sges[i].tx_rdma_sge[0].wr_tx_rdma_sge[1].lkey =
lnk->roce_pd->local_dma_lkey;
lnk->wr_tx_rdma_sges[i].tx_rdma_sge[1].wr_tx_rdma_sge[0].lkey =
lnk->roce_pd->local_dma_lkey;
lnk->wr_tx_rdma_sges[i].tx_rdma_sge[1].wr_tx_rdma_sge[1].lkey =
lnk->roce_pd->local_dma_lkey;
lnk->wr_tx_ibs[i].next = NULL;
lnk->wr_tx_ibs[i].sg_list = &lnk->wr_tx_sges[i];
lnk->wr_tx_ibs[i].num_sge = 1;
lnk->wr_tx_ibs[i].opcode = IB_WR_SEND;
lnk->wr_tx_ibs[i].send_flags =
IB_SEND_SIGNALED | IB_SEND_SOLICITED;
if (send_inline)
lnk->wr_tx_ibs[i].send_flags |= IB_SEND_INLINE;
lnk->wr_tx_rdmas[i].wr_tx_rdma[0].wr.opcode = IB_WR_RDMA_WRITE;
lnk->wr_tx_rdmas[i].wr_tx_rdma[1].wr.opcode = IB_WR_RDMA_WRITE;
lnk->wr_tx_rdmas[i].wr_tx_rdma[0].wr.sg_list =
lnk->wr_tx_rdma_sges[i].tx_rdma_sge[0].wr_tx_rdma_sge;
lnk->wr_tx_rdmas[i].wr_tx_rdma[1].wr.sg_list =
lnk->wr_tx_rdma_sges[i].tx_rdma_sge[1].wr_tx_rdma_sge;
}
if (lnk->lgr->smc_version == SMC_V2) {
lnk->wr_tx_v2_sge->addr = lnk->wr_tx_v2_dma_addr;
lnk->wr_tx_v2_sge->length = SMC_WR_BUF_V2_SIZE;
lnk->wr_tx_v2_sge->lkey = lnk->roce_pd->local_dma_lkey;
lnk->wr_tx_v2_ib->next = NULL;
lnk->wr_tx_v2_ib->sg_list = lnk->wr_tx_v2_sge;
lnk->wr_tx_v2_ib->num_sge = 1;
lnk->wr_tx_v2_ib->opcode = IB_WR_SEND;
lnk->wr_tx_v2_ib->send_flags =
IB_SEND_SIGNALED | IB_SEND_SOLICITED;
}
/* With SMC-Rv2 there can be messages larger than SMC_WR_TX_SIZE.
* Each ib_recv_wr gets 2 sges, the second one is a spillover buffer
* and the same buffer for all sges. When a larger message arrived then
* the content of the first small sge is copied to the beginning of
* the larger spillover buffer, allowing easy data mapping.
*/
for (i = 0; i < lnk->wr_rx_cnt; i++) {
int x = i * lnk->wr_rx_sge_cnt;
lnk->wr_rx_sges[x].addr =
lnk->wr_rx_dma_addr + i * lnk->wr_rx_buflen;
lnk->wr_rx_sges[x].length = smc_link_shared_v2_rxbuf(lnk) ?
SMC_WR_TX_SIZE : lnk->wr_rx_buflen;
lnk->wr_rx_sges[x].lkey = lnk->roce_pd->local_dma_lkey;
if (lnk->lgr->smc_version == SMC_V2 && smc_link_shared_v2_rxbuf(lnk)) {
lnk->wr_rx_sges[x + 1].addr =
lnk->wr_rx_v2_dma_addr + SMC_WR_TX_SIZE;
lnk->wr_rx_sges[x + 1].length =
SMC_WR_BUF_V2_SIZE - SMC_WR_TX_SIZE;
lnk->wr_rx_sges[x + 1].lkey =
lnk->roce_pd->local_dma_lkey;
}
lnk->wr_rx_ibs[i].next = NULL;
lnk->wr_rx_ibs[i].sg_list = &lnk->wr_rx_sges[x];
lnk->wr_rx_ibs[i].num_sge = lnk->wr_rx_sge_cnt;
}
lnk->wr_reg.wr.next = NULL;
lnk->wr_reg.wr.num_sge = 0;
lnk->wr_reg.wr.send_flags = IB_SEND_SIGNALED;
lnk->wr_reg.wr.opcode = IB_WR_REG_MR;
lnk->wr_reg.access = IB_ACCESS_LOCAL_WRITE | IB_ACCESS_REMOTE_WRITE;
}
void smc_wr_free_link(struct smc_link *lnk)
{
struct ib_device *ibdev;
if (!lnk->smcibdev)
return;
ibdev = lnk->smcibdev->ibdev;
smc_wr_drain_cq(lnk);
smc_wr_wakeup_reg_wait(lnk);
smc_wr_wakeup_tx_wait(lnk);
smc_wr_tx_wait_no_pending_sends(lnk);
percpu_ref_kill(&lnk->wr_reg_refs);
wait_for_completion(&lnk->reg_ref_comp);
percpu_ref_exit(&lnk->wr_reg_refs);
percpu_ref_kill(&lnk->wr_tx_refs);
wait_for_completion(&lnk->tx_ref_comp);
percpu_ref_exit(&lnk->wr_tx_refs);
if (lnk->wr_rx_dma_addr) {
ib_dma_unmap_single(ibdev, lnk->wr_rx_dma_addr,
lnk->wr_rx_buflen * lnk->wr_rx_cnt,
DMA_FROM_DEVICE);
lnk->wr_rx_dma_addr = 0;
}
if (lnk->wr_rx_v2_dma_addr) {
ib_dma_unmap_single(ibdev, lnk->wr_rx_v2_dma_addr,
SMC_WR_BUF_V2_SIZE,
DMA_FROM_DEVICE);
lnk->wr_rx_v2_dma_addr = 0;
}
if (lnk->wr_tx_dma_addr) {
ib_dma_unmap_single(ibdev, lnk->wr_tx_dma_addr,
SMC_WR_BUF_SIZE * lnk->wr_tx_cnt,
DMA_TO_DEVICE);
lnk->wr_tx_dma_addr = 0;
}
if (lnk->wr_tx_v2_dma_addr) {
ib_dma_unmap_single(ibdev, lnk->wr_tx_v2_dma_addr,
SMC_WR_BUF_V2_SIZE,
DMA_TO_DEVICE);
lnk->wr_tx_v2_dma_addr = 0;
}
}
void smc_wr_free_lgr_mem(struct smc_link_group *lgr)
{
if (lgr->smc_version < SMC_V2)
return;
kfree(lgr->wr_rx_buf_v2);
lgr->wr_rx_buf_v2 = NULL;
kfree(lgr->wr_tx_buf_v2);
lgr->wr_tx_buf_v2 = NULL;
}
void smc_wr_free_link_mem(struct smc_link *lnk)
{
kfree(lnk->wr_tx_v2_ib);
lnk->wr_tx_v2_ib = NULL;
kfree(lnk->wr_tx_v2_sge);
lnk->wr_tx_v2_sge = NULL;
kfree(lnk->wr_tx_v2_pend);
lnk->wr_tx_v2_pend = NULL;
kfree(lnk->wr_tx_compl);
lnk->wr_tx_compl = NULL;
kfree(lnk->wr_tx_pends);
lnk->wr_tx_pends = NULL;
bitmap_free(lnk->wr_tx_mask);
lnk->wr_tx_mask = NULL;
kfree(lnk->wr_tx_sges);
lnk->wr_tx_sges = NULL;
kfree(lnk->wr_tx_rdma_sges);
lnk->wr_tx_rdma_sges = NULL;
kfree(lnk->wr_rx_sges);
lnk->wr_rx_sges = NULL;
kfree(lnk->wr_tx_rdmas);
lnk->wr_tx_rdmas = NULL;
kfree(lnk->wr_rx_ibs);
lnk->wr_rx_ibs = NULL;
kfree(lnk->wr_tx_ibs);
lnk->wr_tx_ibs = NULL;
kfree(lnk->wr_tx_bufs);
lnk->wr_tx_bufs = NULL;
kfree(lnk->wr_rx_bufs);
lnk->wr_rx_bufs = NULL;
}
int smc_wr_alloc_lgr_mem(struct smc_link_group *lgr)
{
if (lgr->smc_version < SMC_V2)
return 0;
lgr->wr_rx_buf_v2 = kzalloc(SMC_WR_BUF_V2_SIZE, GFP_KERNEL);
if (!lgr->wr_rx_buf_v2)
return -ENOMEM;
lgr->wr_tx_buf_v2 = kzalloc(SMC_WR_BUF_V2_SIZE, GFP_KERNEL);
if (!lgr->wr_tx_buf_v2) {
kfree(lgr->wr_rx_buf_v2);
return -ENOMEM;
}
return 0;
}
int smc_wr_alloc_link_mem(struct smc_link *link)
{
/* allocate link related memory */
link->wr_tx_bufs = kcalloc(link->max_send_wr,
SMC_WR_BUF_SIZE, GFP_KERNEL);
if (!link->wr_tx_bufs)
goto no_mem;
link->wr_rx_bufs = kcalloc(link->max_recv_wr, link->wr_rx_buflen,
GFP_KERNEL);
if (!link->wr_rx_bufs)
goto no_mem_wr_tx_bufs;
link->wr_tx_ibs = kzalloc_objs(link->wr_tx_ibs[0], link->max_send_wr);
if (!link->wr_tx_ibs)
goto no_mem_wr_rx_bufs;
link->wr_rx_ibs = kzalloc_objs(link->wr_rx_ibs[0], link->max_recv_wr);
if (!link->wr_rx_ibs)
goto no_mem_wr_tx_ibs;
link->wr_tx_rdmas = kzalloc_objs(link->wr_tx_rdmas[0],
link->max_send_wr);
if (!link->wr_tx_rdmas)
goto no_mem_wr_rx_ibs;
link->wr_tx_rdma_sges = kzalloc_objs(link->wr_tx_rdma_sges[0],
link->max_send_wr);
if (!link->wr_tx_rdma_sges)
goto no_mem_wr_tx_rdmas;
link->wr_tx_sges = kzalloc_objs(link->wr_tx_sges[0], link->max_send_wr);
if (!link->wr_tx_sges)
goto no_mem_wr_tx_rdma_sges;
link->wr_rx_sges = kcalloc(link->max_recv_wr,
sizeof(link->wr_rx_sges[0]) * link->wr_rx_sge_cnt,
GFP_KERNEL);
if (!link->wr_rx_sges)
goto no_mem_wr_tx_sges;
link->wr_tx_mask = bitmap_zalloc(link->max_send_wr, GFP_KERNEL);
if (!link->wr_tx_mask)
goto no_mem_wr_rx_sges;
link->wr_tx_pends = kzalloc_objs(link->wr_tx_pends[0],
link->max_send_wr);
if (!link->wr_tx_pends)
goto no_mem_wr_tx_mask;
link->wr_tx_compl = kzalloc_objs(link->wr_tx_compl[0],
link->max_send_wr);
if (!link->wr_tx_compl)
goto no_mem_wr_tx_pends;
if (link->lgr->smc_version == SMC_V2) {
link->wr_tx_v2_ib = kzalloc_obj(*link->wr_tx_v2_ib);
if (!link->wr_tx_v2_ib)
goto no_mem_tx_compl;
link->wr_tx_v2_sge = kzalloc_obj(*link->wr_tx_v2_sge);
if (!link->wr_tx_v2_sge)
goto no_mem_v2_ib;
link->wr_tx_v2_pend = kzalloc_obj(*link->wr_tx_v2_pend);
if (!link->wr_tx_v2_pend)
goto no_mem_v2_sge;
}
return 0;
no_mem_v2_sge:
kfree(link->wr_tx_v2_sge);
no_mem_v2_ib:
kfree(link->wr_tx_v2_ib);
no_mem_tx_compl:
kfree(link->wr_tx_compl);
no_mem_wr_tx_pends:
kfree(link->wr_tx_pends);
no_mem_wr_tx_mask:
kfree(link->wr_tx_mask);
no_mem_wr_rx_sges:
kfree(link->wr_rx_sges);
no_mem_wr_tx_sges:
kfree(link->wr_tx_sges);
no_mem_wr_tx_rdma_sges:
kfree(link->wr_tx_rdma_sges);
no_mem_wr_tx_rdmas:
kfree(link->wr_tx_rdmas);
no_mem_wr_rx_ibs:
kfree(link->wr_rx_ibs);
no_mem_wr_tx_ibs:
kfree(link->wr_tx_ibs);
no_mem_wr_rx_bufs:
kfree(link->wr_rx_bufs);
no_mem_wr_tx_bufs:
kfree(link->wr_tx_bufs);
no_mem:
return -ENOMEM;
}
void smc_wr_remove_dev(struct smc_ib_device *smcibdev)
{
tasklet_kill(&smcibdev->recv_tasklet);
tasklet_kill(&smcibdev->send_tasklet);
}
void smc_wr_add_dev(struct smc_ib_device *smcibdev)
{
tasklet_setup(&smcibdev->recv_tasklet, smc_wr_rx_tasklet_fn);
tasklet_setup(&smcibdev->send_tasklet, smc_wr_tx_tasklet_fn);
}
static void smcr_wr_tx_refs_free(struct percpu_ref *ref)
{
struct smc_link *lnk = container_of(ref, struct smc_link, wr_tx_refs);
complete(&lnk->tx_ref_comp);
}
static void smcr_wr_reg_refs_free(struct percpu_ref *ref)
{
struct smc_link *lnk = container_of(ref, struct smc_link, wr_reg_refs);
complete(&lnk->reg_ref_comp);
}
int smc_wr_create_link(struct smc_link *lnk)
{
struct ib_device *ibdev = lnk->smcibdev->ibdev;
int rc = 0;
smc_wr_tx_set_wr_id(&lnk->wr_tx_id, 0);
lnk->wr_rx_id = 0;
lnk->wr_rx_dma_addr = ib_dma_map_single(
ibdev, lnk->wr_rx_bufs, lnk->wr_rx_buflen * lnk->wr_rx_cnt,
DMA_FROM_DEVICE);
if (ib_dma_mapping_error(ibdev, lnk->wr_rx_dma_addr)) {
lnk->wr_rx_dma_addr = 0;
rc = -EIO;
goto out;
}
if (lnk->lgr->smc_version == SMC_V2) {
if (smc_link_shared_v2_rxbuf(lnk)) {
lnk->wr_rx_v2_dma_addr =
ib_dma_map_single(ibdev, lnk->lgr->wr_rx_buf_v2,
SMC_WR_BUF_V2_SIZE, DMA_FROM_DEVICE);
if (ib_dma_mapping_error(ibdev, lnk->wr_rx_v2_dma_addr)) {
lnk->wr_rx_v2_dma_addr = 0;
rc = -EIO;
goto dma_unmap;
}
}
lnk->wr_tx_v2_dma_addr = ib_dma_map_single(ibdev,
lnk->lgr->wr_tx_buf_v2, SMC_WR_BUF_V2_SIZE,
DMA_TO_DEVICE);
if (ib_dma_mapping_error(ibdev, lnk->wr_tx_v2_dma_addr)) {
lnk->wr_tx_v2_dma_addr = 0;
rc = -EIO;
goto dma_unmap;
}
}
lnk->wr_tx_dma_addr = ib_dma_map_single(
ibdev, lnk->wr_tx_bufs, SMC_WR_BUF_SIZE * lnk->wr_tx_cnt,
DMA_TO_DEVICE);
if (ib_dma_mapping_error(ibdev, lnk->wr_tx_dma_addr)) {
rc = -EIO;
goto dma_unmap;
}
smc_wr_init_sge(lnk);
bitmap_zero(lnk->wr_tx_mask, lnk->max_send_wr);
init_waitqueue_head(&lnk->wr_tx_wait);
rc = percpu_ref_init(&lnk->wr_tx_refs, smcr_wr_tx_refs_free, 0, GFP_KERNEL);
if (rc)
goto dma_unmap;
init_completion(&lnk->tx_ref_comp);
init_waitqueue_head(&lnk->wr_reg_wait);
rc = percpu_ref_init(&lnk->wr_reg_refs, smcr_wr_reg_refs_free, 0, GFP_KERNEL);
if (rc)
goto cancel_ref;
init_completion(&lnk->reg_ref_comp);
init_waitqueue_head(&lnk->wr_rx_empty_wait);
return rc;
cancel_ref:
percpu_ref_exit(&lnk->wr_tx_refs);
dma_unmap:
if (lnk->wr_rx_v2_dma_addr) {
ib_dma_unmap_single(ibdev, lnk->wr_rx_v2_dma_addr,
SMC_WR_BUF_V2_SIZE,
DMA_FROM_DEVICE);
lnk->wr_rx_v2_dma_addr = 0;
}
if (lnk->wr_tx_v2_dma_addr) {
ib_dma_unmap_single(ibdev, lnk->wr_tx_v2_dma_addr,
SMC_WR_BUF_V2_SIZE,
DMA_TO_DEVICE);
lnk->wr_tx_v2_dma_addr = 0;
}
ib_dma_unmap_single(ibdev, lnk->wr_rx_dma_addr,
lnk->wr_rx_buflen * lnk->wr_rx_cnt,
DMA_FROM_DEVICE);
lnk->wr_rx_dma_addr = 0;
out:
return rc;
}
+137
View File
@@ -0,0 +1,137 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Shared Memory Communications over RDMA (SMC-R) and RoCE
*
* Work Requests exploiting Infiniband API
*
* Copyright IBM Corp. 2016
*
* Author(s): Steffen Maier <maier@linux.vnet.ibm.com>
*/
#ifndef SMC_WR_H
#define SMC_WR_H
#include <linux/atomic.h>
#include <rdma/ib_verbs.h>
#include <asm/div64.h>
#include "smc.h"
#include "smc_core.h"
#define SMC_WR_TX_WAIT_FREE_SLOT_TIME (10 * HZ)
#define SMC_WR_TX_SIZE 44 /* actual size of wr_send data (<=SMC_WR_BUF_SIZE) */
#define SMC_WR_TX_PEND_PRIV_SIZE 32
struct smc_wr_tx_pend_priv {
u8 priv[SMC_WR_TX_PEND_PRIV_SIZE];
};
typedef void (*smc_wr_tx_handler)(struct smc_wr_tx_pend_priv *,
struct smc_link *,
enum ib_wc_status);
typedef bool (*smc_wr_tx_filter)(struct smc_wr_tx_pend_priv *,
unsigned long);
typedef void (*smc_wr_tx_dismisser)(struct smc_wr_tx_pend_priv *);
struct smc_wr_rx_handler {
struct hlist_node list; /* hash table collision resolution */
void (*handler)(struct ib_wc *, void *);
u8 type;
};
/* Only used by RDMA write WRs.
* All other WRs (CDC/LLC) use smc_wr_tx_send handling WR_ID implicitly
*/
static inline long smc_wr_tx_get_next_wr_id(struct smc_link *link)
{
return atomic_long_inc_return(&link->wr_tx_id);
}
static inline void smc_wr_tx_set_wr_id(atomic_long_t *wr_tx_id, long val)
{
atomic_long_set(wr_tx_id, val);
}
static inline bool smc_wr_tx_link_hold(struct smc_link *link)
{
if (!smc_link_sendable(link))
return false;
percpu_ref_get(&link->wr_tx_refs);
return true;
}
static inline void smc_wr_tx_link_put(struct smc_link *link)
{
percpu_ref_put(&link->wr_tx_refs);
}
static inline void smc_wr_drain_cq(struct smc_link *lnk)
{
wait_event(lnk->wr_rx_empty_wait, lnk->wr_rx_id_compl == lnk->wr_rx_id);
}
static inline void smc_wr_wakeup_tx_wait(struct smc_link *lnk)
{
wake_up_all(&lnk->wr_tx_wait);
}
static inline void smc_wr_wakeup_reg_wait(struct smc_link *lnk)
{
wake_up(&lnk->wr_reg_wait);
}
/* post a new receive work request to fill a completed old work request entry */
static inline int smc_wr_rx_post(struct smc_link *link)
{
int rc;
u64 wr_id, temp_wr_id;
u32 index;
wr_id = ++link->wr_rx_id; /* tasklet context, thus not atomic */
temp_wr_id = wr_id;
index = do_div(temp_wr_id, link->wr_rx_cnt);
link->wr_rx_ibs[index].wr_id = wr_id;
rc = ib_post_recv(link->roce_qp, &link->wr_rx_ibs[index], NULL);
return rc;
}
int smc_wr_create_link(struct smc_link *lnk);
int smc_wr_alloc_link_mem(struct smc_link *lnk);
int smc_wr_alloc_lgr_mem(struct smc_link_group *lgr);
void smc_wr_free_link(struct smc_link *lnk);
void smc_wr_free_link_mem(struct smc_link *lnk);
void smc_wr_free_lgr_mem(struct smc_link_group *lgr);
void smc_wr_remember_qp_attr(struct smc_link *lnk);
void smc_wr_remove_dev(struct smc_ib_device *smcibdev);
void smc_wr_add_dev(struct smc_ib_device *smcibdev);
int smc_wr_tx_get_free_slot(struct smc_link *link, smc_wr_tx_handler handler,
struct smc_wr_buf **wr_buf,
struct smc_rdma_wr **wrs,
struct smc_wr_tx_pend_priv **wr_pend_priv);
int smc_wr_tx_get_v2_slot(struct smc_link *link,
smc_wr_tx_handler handler,
struct smc_wr_v2_buf **wr_buf,
struct smc_wr_tx_pend_priv **wr_pend_priv);
int smc_wr_tx_put_slot(struct smc_link *link,
struct smc_wr_tx_pend_priv *wr_pend_priv);
int smc_wr_tx_send(struct smc_link *link,
struct smc_wr_tx_pend_priv *wr_pend_priv);
int smc_wr_tx_v2_send(struct smc_link *link,
struct smc_wr_tx_pend_priv *priv, int len);
int smc_wr_tx_send_wait(struct smc_link *link, struct smc_wr_tx_pend_priv *priv,
unsigned long timeout);
void smc_wr_tx_cq_handler(struct ib_cq *ib_cq, void *cq_context);
void smc_wr_tx_wait_no_pending_sends(struct smc_link *link);
int smc_wr_rx_register_handler(struct smc_wr_rx_handler *handler);
int smc_wr_rx_post_init(struct smc_link *link);
void smc_wr_rx_cq_handler(struct ib_cq *ib_cq, void *cq_context);
int smc_wr_reg_send(struct smc_link *link, struct ib_mr *mr);
#endif /* SMC_WR_H */