struct smc_rmb_rtoken { union {
u8 num_rkeys; /* first rtoken byte of CONFIRM LINK msg */ /* is actually the num of rtokens, first */ /* rtoken is always for the current link */
u8 link_id; /* link id of the rtoken */
};
__be32 rmb_key;
__be64 rmb_vaddr;
} __packed; /* format defined in RFC7609 */
if ((msg_type == SMC_LLC_ADD_LINK || msg_type == SMC_LLC_DELETE_LINK) &&
flow_type != msg_type && !lgr->delayed_event) {
lgr->delayed_event = qentry; return;
} /* drop parallel or already-in-progress llc requests */ if (flow_type != msg_type)
pr_warn_once("smc: SMC-R lg %*phN net %llu dropped parallel " "LLC msg: msg %d flow %d role %d\n",
SMC_LGR_ID_SIZE, &lgr->id,
lgr->net->net_cookie,
qentry->msg.raw.hdr.common.type,
flow_type, lgr->role);
kfree(qentry);
}
/* try to start a new llc flow, initiated by an incoming llc msg */ staticbool smc_llc_flow_start(struct smc_llc_flow *flow, struct smc_llc_qentry *qentry)
{ struct smc_link_group *lgr = qentry->link->lgr;
spin_lock_bh(&lgr->llc_flow_lock); if (flow->type) { /* a flow is already active */
smc_llc_flow_parallel(lgr, flow->type, qentry);
spin_unlock_bh(&lgr->llc_flow_lock); returnfalse;
} switch (qentry->msg.raw.hdr.common.llc_type) { case SMC_LLC_ADD_LINK:
flow->type = SMC_LLC_FLOW_ADD_LINK; break; case SMC_LLC_DELETE_LINK:
flow->type = SMC_LLC_FLOW_DEL_LINK; break; case SMC_LLC_CONFIRM_RKEY: case SMC_LLC_DELETE_RKEY:
flow->type = SMC_LLC_FLOW_RKEY; break; default:
flow->type = SMC_LLC_FLOW_NONE;
}
smc_llc_flow_qentry_set(flow, qentry);
spin_unlock_bh(&lgr->llc_flow_lock); returntrue;
}
/* start a new local llc flow, wait till current flow finished */ int smc_llc_flow_initiate(struct smc_link_group *lgr, enum smc_llc_flowtype type)
{ enum smc_llc_flowtype allowed_remote = SMC_LLC_FLOW_NONE; int rc;
/* all flows except confirm_rkey and delete_rkey are exclusive, *confirm/deleterkeyflowscanrunconcurrently(localandremote)
*/ if (type == SMC_LLC_FLOW_RKEY)
allowed_remote = SMC_LLC_FLOW_RKEY;
again: if (list_empty(&lgr->list)) return -ENODEV;
spin_lock_bh(&lgr->llc_flow_lock); if (lgr->llc_flow_lcl.type == SMC_LLC_FLOW_NONE &&
(lgr->llc_flow_rmt.type == SMC_LLC_FLOW_NONE ||
lgr->llc_flow_rmt.type == allowed_remote)) {
lgr->llc_flow_lcl.type = type;
spin_unlock_bh(&lgr->llc_flow_lock); return0;
}
spin_unlock_bh(&lgr->llc_flow_lock);
rc = wait_event_timeout(lgr->llc_flow_waiter, (list_empty(&lgr->list) ||
(lgr->llc_flow_lcl.type == SMC_LLC_FLOW_NONE &&
(lgr->llc_flow_rmt.type == SMC_LLC_FLOW_NONE ||
lgr->llc_flow_rmt.type == allowed_remote))),
SMC_LLC_WAIT_TIME * 10); if (!rc) return -ETIMEDOUT; goto again;
}
/* lnk is optional and used for early wakeup when link goes down, useful in *caseswherewewaitforaresponseonthelinkafterwesentarequest
*/ 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_flow *flow = &lgr->llc_flow_lcl;
u8 rcv_msg;
rc = smc_wr_tx_get_free_slot(link, smc_llc_tx_handler, wr_buf, NULL,
pend); if (rc < 0) return rc;
BUILD_BUG_ON_MSG( sizeof(union smc_llc_msg) > SMC_WR_BUF_SIZE, "must increase SMC_WR_BUF_SIZE to at least sizeof(struct smc_llc_msg)");
BUILD_BUG_ON_MSG( sizeof(union smc_llc_msg) != SMC_WR_TX_SIZE, "must adapt SMC_WR_TX_SIZE to sizeof(struct smc_llc_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_llc_tx_pend) > SMC_WR_TX_PEND_PRIV_SIZE, "must increase SMC_WR_TX_PEND_PRIV_SIZE to at least sizeof(struct smc_llc_tx_pend)"); return0;
}
/* return first buffer from any of the next buf lists */ staticstruct smc_buf_desc *_smc_llc_get_next_rmb(struct smc_link_group *lgr, int *buf_lst)
{ struct smc_buf_desc *buf_pos;
while (*buf_lst < SMC_RMBE_SIZES) {
buf_pos = list_first_entry_or_null(&lgr->rmbs[*buf_lst], struct smc_buf_desc, list); if (buf_pos) return buf_pos;
(*buf_lst)++;
} return NULL;
}
/* return next rmb from buffer lists */ staticstruct smc_buf_desc *smc_llc_get_next_rmb(struct smc_link_group *lgr, int *buf_lst, struct smc_buf_desc *buf_pos)
{ struct smc_buf_desc *buf_next;
if (!buf_pos) return _smc_llc_get_next_rmb(lgr, buf_lst);
down_write(&lgr->llc_conf_mutex); if (smc_llc_is_local_add_link(&qentry->msg))
smc_llc_cli_add_link_invite(qentry->link, qentry); else
smc_llc_cli_add_link(qentry->link, qentry);
up_write(&lgr->llc_conf_mutex);
}
staticint smc_llc_active_link_count(struct smc_link_group *lgr)
{ int i, link_count = 0;
for (i = 0; i < SMC_LINKS_PER_LGR_MAX; i++) { if (!smc_link_active(&lgr->lnk[i])) continue;
link_count++;
} return link_count;
}
/* find the asymmetric link when 3 links are established */ staticstruct smc_link *smc_llc_find_asym_link(struct smc_link_group *lgr)
{ int asym_idx = -ENOENT; int i, j, k; bool found;
/* determine asymmetric link */
found = false; for (i = 0; i < SMC_LINKS_PER_LGR_MAX; i++) { for (j = i + 1; j < SMC_LINKS_PER_LGR_MAX; j++) { if (!smc_link_usable(&lgr->lnk[i]) ||
!smc_link_usable(&lgr->lnk[j])) continue; if (!memcmp(lgr->lnk[i].gid, lgr->lnk[j].gid,
SMC_GID_SIZE)) {
found = true; /* asym_lnk is i or j */ break;
}
} if (found) break;
} if (!found) goto out; /* no asymmetric link */ for (k = 0; k < SMC_LINKS_PER_LGR_MAX; k++) { if (!smc_link_usable(&lgr->lnk[k])) continue; if (k != i &&
!memcmp(lgr->lnk[i].peer_gid, lgr->lnk[k].peer_gid,
SMC_GID_SIZE)) {
asym_idx = i; break;
} if (k != j &&
!memcmp(lgr->lnk[j].peer_gid, lgr->lnk[k].peer_gid,
SMC_GID_SIZE)) {
asym_idx = j; break;
}
}
out: return (asym_idx < 0) ? NULL : &lgr->lnk[asym_idx];
}
down_write(&lgr->llc_conf_mutex);
rc = smc_llc_srv_add_link(link, qentry); if (!rc && lgr->type == SMC_LGR_SYMMETRIC) { /* delete any asymmetric link */
smc_llc_delete_asym_link(lgr);
}
up_write(&lgr->llc_conf_mutex);
kfree(qentry);
}
/* enqueue a local add_link req to trigger a new add_link flow */ void smc_llc_add_link_local(struct smc_link *link)
{ struct smc_llc_msg_add_link add_llc = {};
add_llc.hd.common.llc_type = SMC_LLC_ADD_LINK;
smc_llc_init_msg_hdr(&add_llc.hd, link->lgr, sizeof(add_llc)); /* no dev and port needed */
smc_llc_enqueue(link, (union smc_llc_msg *)&add_llc);
}
/* worker to process an add link message */ staticvoid smc_llc_add_link_work(struct work_struct *work)
{ struct smc_link_group *lgr = container_of(work, struct smc_link_group,
llc_add_link_work);
if (list_empty(&lgr->list)) { /* link group is terminating */
smc_llc_flow_qentry_del(&lgr->llc_flow_lcl); goto out;
}
if (lgr->role == SMC_CLNT)
smc_llc_process_cli_add_link(lgr); else
smc_llc_process_srv_add_link(lgr);
out: if (lgr->llc_flow_lcl.type != SMC_LLC_FLOW_REQ_ADD_LINK)
smc_llc_flow_stop(lgr, &lgr->llc_flow_lcl);
}
/* enqueue a local del_link msg to trigger a new del_link flow, *calledonlyforroleSMC_SERV
*/ void smc_llc_srv_delete_link_local(struct smc_link *link, u8 del_link_id)
{ struct smc_llc_msg_del_link del_llc = {};
/* try to send a DELETE LINK ALL request on any active link, *waitingforsendcompletion
*/ void smc_llc_send_link_delete_all(struct smc_link_group *lgr, bool ord, u32 rsn)
{ struct smc_llc_msg_del_link delllc = {}; int i;
for (i = 0; i < SMC_LINKS_PER_LGR_MAX; i++) { if (!smc_link_sendable(&lgr->lnk[i])) continue; if (!smc_llc_send_message_wait(&lgr->lnk[i], &delllc)) break;
}
}
staticvoid smc_llc_process_srv_delete_link(struct smc_link_group *lgr)
{ struct smc_llc_msg_del_link *del_llc; struct smc_link *lnk, *lnk_del; struct smc_llc_qentry *qentry; int active_links; int i;
if (qentry->msg.delete_link.hd.flags & SMC_LLC_FLAG_DEL_LINK_ALL) { /* delete entire lgr */
smc_llc_send_link_delete_all(lgr, true, ntohl(
qentry->msg.delete_link.reason));
smc_lgr_terminate_sched(lgr); goto out;
} /* delete single link */
lnk_del = NULL; for (i = 0; i < SMC_LINKS_PER_LGR_MAX; i++) { if (lgr->lnk[i].link_id == del_llc->link_num) {
lnk_del = &lgr->lnk[i]; break;
}
} if (!lnk_del) goto out; /* asymmetric link already deleted */
if (smc_link_downing(&lnk_del->state)) { if (smc_switch_conns(lgr, lnk_del, false))
smc_wr_tx_wait_no_pending_sends(lnk_del);
} if (!list_empty(&lgr->list)) { /* qentry is either a request from peer (send it back to *initiatetheDELETE_LINKprocessing),oralocally *enqueuedDELETE_LINKrequest(forwardit)
*/ if (!smc_llc_send_message(lnk, &qentry->msg)) { struct smc_llc_qentry *qentry2;
/* process a confirm_rkey request from peer, remote flow */ staticvoid smc_llc_rmt_conf_rkey(struct smc_link_group *lgr)
{ struct smc_llc_msg_confirm_rkey *llc; struct smc_llc_qentry *qentry; struct smc_link *link; int num_entries; int rk_idx; int i;
qentry = lgr->llc_flow_rmt.qentry;
llc = &qentry->msg.confirm_rkey;
link = qentry->link;
num_entries = llc->rtoken[0].num_rkeys; if (num_entries > SMC_LLC_RKEYS_PER_MSG) goto out_err; /* first rkey entry is for receiving link */
rk_idx = smc_rtoken_add(link,
llc->rtoken[0].rmb_vaddr,
llc->rtoken[0].rmb_key); if (rk_idx < 0) goto out_err;
for (i = 1; i <= min_t(u8, num_entries, SMC_LLC_RKEYS_PER_MSG - 1); i++)
smc_rtoken_set2(lgr, rk_idx, llc->rtoken[i].link_id,
llc->rtoken[i].rmb_vaddr,
llc->rtoken[i].rmb_key); /* max links is 3 so there is no need to support conf_rkey_cont msgs */ goto out;
out_err:
llc->hd.flags |= SMC_LLC_FLAG_RKEY_NEG;
llc->hd.flags |= SMC_LLC_FLAG_RKEY_RETRY;
out:
llc->hd.flags |= SMC_LLC_FLAG_RESP;
smc_llc_init_msg_hdr(&llc->hd, link->lgr, sizeof(*llc));
smc_llc_send_message(link, &qentry->msg);
smc_llc_flow_qentry_del(&lgr->llc_flow_rmt);
}
/* process a delete_rkey request from peer, remote flow */ staticvoid smc_llc_rmt_delete_rkey(struct smc_link_group *lgr)
{ struct smc_llc_msg_delete_rkey *llc; struct smc_llc_qentry *qentry; struct smc_link *link;
u8 err_mask = 0; int i, max;
qentry = lgr->llc_flow_rmt.qentry;
llc = &qentry->msg.delete_rkey;
link = qentry->link;
if (lgr->smc_version == SMC_V2) { struct smc_llc_msg_delete_rkey_v2 *llcv2;
switch (llc->raw.hdr.common.llc_type) { case SMC_LLC_TEST_LINK:
llc->test_link.hd.flags |= SMC_LLC_FLAG_RESP;
smc_llc_send_message(link, llc); break; case SMC_LLC_ADD_LINK: if (list_empty(&lgr->list)) goto out; /* lgr is terminating */ if (lgr->role == SMC_CLNT) { if (smc_llc_is_local_add_link(llc)) { if (lgr->llc_flow_lcl.type ==
SMC_LLC_FLOW_ADD_LINK) break; /* add_link in progress */ if (smc_llc_flow_start(&lgr->llc_flow_lcl,
qentry)) {
schedule_work(&lgr->llc_add_link_work);
} return;
} if (lgr->llc_flow_lcl.type == SMC_LLC_FLOW_ADD_LINK &&
!lgr->llc_flow_lcl.qentry) { /* a flow is waiting for this message */
smc_llc_flow_qentry_set(&lgr->llc_flow_lcl,
qentry);
wake_up(&lgr->llc_msg_waiter); return;
} if (lgr->llc_flow_lcl.type ==
SMC_LLC_FLOW_REQ_ADD_LINK) { /* server started add_link processing */
lgr->llc_flow_lcl.type = SMC_LLC_FLOW_ADD_LINK;
smc_llc_flow_qentry_set(&lgr->llc_flow_lcl,
qentry);
schedule_work(&lgr->llc_add_link_work); return;
} if (smc_llc_flow_start(&lgr->llc_flow_lcl, qentry)) {
schedule_work(&lgr->llc_add_link_work);
}
} elseif (smc_llc_flow_start(&lgr->llc_flow_lcl, qentry)) { /* as smc server, handle client suggestion */
schedule_work(&lgr->llc_add_link_work);
} return; case SMC_LLC_CONFIRM_LINK: case SMC_LLC_ADD_LINK_CONT: if (lgr->llc_flow_lcl.type != SMC_LLC_FLOW_NONE) { /* a flow is waiting for this message */
smc_llc_flow_qentry_set(&lgr->llc_flow_lcl, qentry);
wake_up(&lgr->llc_msg_waiter); return;
} break; case SMC_LLC_DELETE_LINK: if (lgr->llc_flow_lcl.type == SMC_LLC_FLOW_ADD_LINK &&
!lgr->llc_flow_lcl.qentry) { /* DEL LINK REQ during ADD LINK SEQ */
smc_llc_flow_qentry_set(&lgr->llc_flow_lcl, qentry);
wake_up(&lgr->llc_msg_waiter);
} elseif (smc_llc_flow_start(&lgr->llc_flow_lcl, qentry)) {
schedule_work(&lgr->llc_del_link_work);
} return; case SMC_LLC_CONFIRM_RKEY: /* new request from remote, assign to remote flow */ if (smc_llc_flow_start(&lgr->llc_flow_rmt, qentry)) { /* process here, does not wait for more llc msgs */
smc_llc_rmt_conf_rkey(lgr);
smc_llc_flow_stop(lgr, &lgr->llc_flow_rmt);
} return; case SMC_LLC_CONFIRM_RKEY_CONT: /* not used because max links is 3, and 3 rkeys fit into *oneCONFIRM_RKEYmessage
*/ break; case SMC_LLC_DELETE_RKEY: /* new request from remote, assign to remote flow */ if (smc_llc_flow_start(&lgr->llc_flow_rmt, qentry)) { /* process here, does not wait for more llc msgs */
smc_llc_rmt_delete_rkey(lgr);
smc_llc_flow_stop(lgr, &lgr->llc_flow_rmt);
} return; case SMC_LLC_REQ_ADD_LINK: /* handle response here, smc_llc_flow_stop() cannot be called *intaskletcontext
*/ if (lgr->role == SMC_CLNT &&
lgr->llc_flow_lcl.type == SMC_LLC_FLOW_REQ_ADD_LINK &&
(llc->raw.hdr.flags & SMC_LLC_FLAG_RESP)) {
smc_llc_flow_stop(link->lgr, &lgr->llc_flow_lcl);
} elseif (lgr->role == SMC_SERV) { if (smc_llc_flow_start(&lgr->llc_flow_lcl, qentry)) { /* as smc server, handle client suggestion */
lgr->llc_flow_lcl.type = SMC_LLC_FLOW_ADD_LINK;
schedule_work(&lgr->llc_add_link_work);
} return;
} break; default:
smc_llc_protocol_violation(lgr, llc->raw.hdr.common.type); break;
}
out:
kfree(qentry);
}
/* worker to process llc messages on the event queue */ staticvoid smc_llc_event_work(struct work_struct *work)
{ struct smc_link_group *lgr = container_of(work, struct smc_link_group,
llc_event_work); struct smc_llc_qentry *qentry;
if (!lgr->llc_flow_lcl.type && lgr->delayed_event) {
qentry = lgr->delayed_event;
lgr->delayed_event = NULL; if (smc_link_usable(qentry->link))
smc_llc_event_handler(qentry); else
kfree(qentry);
}
switch (llc_type) { case SMC_LLC_TEST_LINK: if (smc_link_active(link))
complete(&link->llc_testlink_resp); break; case SMC_LLC_ADD_LINK: case SMC_LLC_ADD_LINK_CONT: case SMC_LLC_CONFIRM_LINK: if (flowtype != SMC_LLC_FLOW_ADD_LINK || flow->qentry) break; /* drop out-of-flow response */ goto assign; case SMC_LLC_DELETE_LINK: if (flowtype != SMC_LLC_FLOW_DEL_LINK || flow->qentry) break; /* drop out-of-flow response */ goto assign; case SMC_LLC_CONFIRM_RKEY: case SMC_LLC_DELETE_RKEY: if (flowtype != SMC_LLC_FLOW_RKEY || flow->qentry) break; /* drop out-of-flow response */ goto assign; case SMC_LLC_CONFIRM_RKEY_CONT: /* not used because max links is 3 */ break; default:
smc_llc_protocol_violation(link->lgr,
qentry->msg.raw.hdr.common.type); break;
}
kfree(qentry); return;
assign: /* assign responses to the local flow, we requested them */
smc_llc_flow_qentry_set(&link->lgr->llc_flow_lcl, qentry);
wake_up(&link->lgr->llc_msg_waiter);
}
/* called after lgr was removed from lgr_list */ void smc_llc_lgr_clear(struct smc_link_group *lgr)
{
smc_llc_event_flush(lgr);
wake_up_all(&lgr->llc_flow_waiter);
wake_up_all(&lgr->llc_msg_waiter);
cancel_work_sync(&lgr->llc_event_work);
cancel_work_sync(&lgr->llc_add_link_work);
cancel_work_sync(&lgr->llc_del_link_work); if (lgr->delayed_event) {
kfree(lgr->delayed_event);
lgr->delayed_event = NULL;
}
}
int smc_llc_link_init(struct smc_link *link)
{
init_completion(&link->llc_testlink_resp);
INIT_DELAYED_WORK(&link->llc_testlink_wrk, smc_llc_testlink_work); return0;
}
void smc_llc_link_active(struct smc_link *link)
{
pr_warn_ratelimited("smc: SMC-R lg %*phN net %llu link added: id %*phN, " "peerid %*phN, ibdev %s, ibport %d\n",
SMC_LGR_ID_SIZE, &link->lgr->id,
link->lgr->net->net_cookie,
SMC_LGR_ID_SIZE, &link->link_uid,
SMC_LGR_ID_SIZE, &link->peer_link_uid,
link->smcibdev->ibdev->name, link->ibport);
link->state = SMC_LNK_ACTIVE; if (link->lgr->llc_testlink_time) {
link->llc_testlink_time = link->lgr->llc_testlink_time;
schedule_delayed_work(&link->llc_testlink_wrk,
link->llc_testlink_time);
}
}
/* called in worker context */ void smc_llc_link_clear(struct smc_link *link, bool log)
{ if (log)
pr_warn_ratelimited("smc: SMC-R lg %*phN net %llu link removed: id %*phN" ", peerid %*phN, ibdev %s, ibport %d\n",
SMC_LGR_ID_SIZE, &link->lgr->id,
link->lgr->net->net_cookie,
SMC_LGR_ID_SIZE, &link->link_uid,
SMC_LGR_ID_SIZE, &link->peer_link_uid,
link->smcibdev->ibdev->name, link->ibport);
complete(&link->llc_testlink_resp);
cancel_delayed_work_sync(&link->llc_testlink_wrk);
}
/* register a new rtoken at the remote peer (for all links) */ int smc_llc_do_confirm_rkey(struct smc_link *send_link, struct smc_buf_desc *rmb_desc)
{ struct smc_link_group *lgr = send_link->lgr; struct smc_llc_qentry *qentry = NULL; int rc = 0;
rc = smc_llc_send_confirm_rkey(send_link, rmb_desc); if (rc) goto out; /* receive CONFIRM RKEY response from server over RoCE fabric */
qentry = smc_llc_wait(lgr, send_link, SMC_LLC_WAIT_TIME,
SMC_LLC_CONFIRM_RKEY); if (!qentry || (qentry->msg.raw.hdr.flags & SMC_LLC_FLAG_RKEY_NEG))
rc = -EFAULT;
out: if (qentry)
smc_llc_flow_qentry_del(&lgr->llc_flow_lcl); return rc;
}
/* unregister an rtoken at the remote peer */ int smc_llc_do_delete_rkey(struct smc_link_group *lgr, struct smc_buf_desc *rmb_desc)
{ struct smc_llc_qentry *qentry = NULL; struct smc_link *send_link; int rc = 0;
send_link = smc_llc_usable_link(lgr); if (!send_link) return -ENOLINK;
/* protected by llc_flow control */
rc = smc_llc_send_delete_rkey(send_link, rmb_desc); if (rc) goto out; /* receive DELETE RKEY response from server over RoCE fabric */
qentry = smc_llc_wait(lgr, send_link, SMC_LLC_WAIT_TIME,
SMC_LLC_DELETE_RKEY); if (!qentry || (qentry->msg.raw.hdr.flags & SMC_LLC_FLAG_RKEY_NEG))
rc = -EFAULT;
out: if (qentry)
smc_llc_flow_qentry_del(&lgr->llc_flow_lcl); return rc;
}
/* save peers link user id, used for debug purposes */ void smc_llc_save_peer_uid(struct smc_llc_qentry *qentry)
{
memcpy(qentry->link->peer_link_uid, qentry->msg.confirm_link.link_uid,
SMC_LGR_ID_SIZE);
}
/* evaluate confirm link request or response */ int smc_llc_eval_conf_link(struct smc_llc_qentry *qentry, enum smc_llc_reqresp type)
{ if (type == SMC_LLC_REQ) { /* SMC server assigns link_id */
qentry->link->link_id = qentry->msg.confirm_link.link_num;
smc_llc_link_set_uid(qentry->link);
} if (!(qentry->msg.raw.hdr.flags & SMC_LLC_FLAG_NO_RMBE_EYEC)) return -ENOTSUPP; return0;
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.