Merge tag 'media/v5.8-1' of git://git.kernel.org/pub/scm/linux/kernel/git/mchehab...
[linux-2.6-microblaze.git] / net / smc / smc_llc.c
index de73432..391237b 100644 (file)
@@ -159,6 +159,8 @@ struct smc_llc_qentry {
        union smc_llc_msg msg;
 };
 
+static void smc_llc_enqueue(struct smc_link *link, union smc_llc_msg *llc);
+
 struct smc_llc_qentry *smc_llc_flow_qentry_clr(struct smc_llc_flow *flow)
 {
        struct smc_llc_qentry *qentry = flow->qentry;
@@ -359,7 +361,6 @@ static int smc_llc_add_pending_send(struct smc_link *link,
 int smc_llc_send_confirm_link(struct smc_link *link,
                              enum smc_llc_reqresp reqresp)
 {
-       struct smc_link_group *lgr = smc_get_lgr(link);
        struct smc_llc_msg_confirm_link *confllc;
        struct smc_wr_tx_pend_priv *pend;
        struct smc_wr_buf *wr_buf;
@@ -380,7 +381,7 @@ int smc_llc_send_confirm_link(struct smc_link *link,
        memcpy(confllc->sender_gid, link->gid, SMC_GID_SIZE);
        hton24(confllc->sender_qp_num, link->roce_qp->qp_num);
        confllc->link_num = link->link_id;
-       memcpy(confllc->link_uid, lgr->id, SMC_LGR_ID_SIZE);
+       memcpy(confllc->link_uid, link->link_uid, SMC_LGR_ID_SIZE);
        confllc->max_links = SMC_LLC_ADD_LNK_MAX_LINKS;
        /* send llc message */
        rc = smc_wr_tx_send(link, pend);
@@ -558,6 +559,25 @@ static int smc_llc_send_message(struct smc_link *link, void *llcbuf)
        return smc_wr_tx_send(link, pend);
 }
 
+/* schedule an llc send on link, may wait for buffers,
+ * and wait for send completion notification.
+ * @return 0 on success
+ */
+static int smc_llc_send_message_wait(struct smc_link *link, void *llcbuf)
+{
+       struct smc_wr_tx_pend_priv *pend;
+       struct smc_wr_buf *wr_buf;
+       int rc;
+
+       if (!smc_link_usable(link))
+               return -ENOLINK;
+       rc = smc_llc_add_pending_send(link, &wr_buf, &pend);
+       if (rc)
+               return rc;
+       memcpy(wr_buf, llcbuf, sizeof(union smc_llc_msg));
+       return smc_wr_tx_send_wait(link, pend, SMC_LLC_WAIT_TIME);
+}
+
 /********************************* receive ***********************************/
 
 static int smc_llc_alloc_alt_link(struct smc_link_group *lgr,
@@ -730,7 +750,6 @@ static int smc_llc_cli_conf_link(struct smc_link *link,
                                 enum smc_lgr_type lgr_new_t)
 {
        struct smc_link_group *lgr = link->lgr;
-       struct smc_llc_msg_del_link *del_llc;
        struct smc_llc_qentry *qentry = NULL;
        int rc = 0;
 
@@ -744,12 +763,12 @@ static int smc_llc_cli_conf_link(struct smc_link *link,
        }
        if (qentry->msg.raw.hdr.common.type != SMC_LLC_CONFIRM_LINK) {
                /* received DELETE_LINK instead */
-               del_llc = &qentry->msg.delete_link;
                qentry->msg.raw.hdr.flags |= SMC_LLC_FLAG_RESP;
                smc_llc_send_message(link, &qentry->msg);
                smc_llc_flow_qentry_del(&lgr->llc_flow_lcl);
                return -ENOLINK;
        }
+       smc_llc_save_peer_uid(qentry);
        smc_llc_flow_qentry_del(&lgr->llc_flow_lcl);
 
        rc = smc_ib_modify_qp_rts(link_new);
@@ -775,7 +794,11 @@ static int smc_llc_cli_conf_link(struct smc_link *link,
                return -ENOLINK;
        }
        smc_llc_link_active(link_new);
-       lgr->type = lgr_new_t;
+       if (lgr_new_t == SMC_LGR_ASYMMETRIC_LOCAL ||
+           lgr_new_t == SMC_LGR_ASYMMETRIC_PEER)
+               smcr_lgr_set_type_asym(lgr, lgr_new_t, link_new->link_idx);
+       else
+               smcr_lgr_set_type(lgr, lgr_new_t);
        return 0;
 }
 
@@ -820,7 +843,8 @@ int smc_llc_cli_add_link(struct smc_link *link, struct smc_llc_qentry *qentry)
        if (rc)
                goto out_reject;
        smc_llc_save_add_link_info(lnk_new, llc);
-       lnk_new->link_id = llc->link_num;
+       lnk_new->link_id = llc->link_num;       /* SMC server assigns link id */
+       smc_llc_link_set_uid(lnk_new);
 
        rc = smc_ib_ready_link(lnk_new);
        if (rc)
@@ -844,7 +868,7 @@ int smc_llc_cli_add_link(struct smc_link *link, struct smc_llc_qentry *qentry)
        if (!rc)
                goto out;
 out_clear_lnk:
-       smcr_link_clear(lnk_new);
+       smcr_link_clear(lnk_new, false);
 out_reject:
        smc_llc_cli_add_link_reject(qentry);
 out:
@@ -863,6 +887,97 @@ static void smc_llc_process_cli_add_link(struct smc_link_group *lgr)
        mutex_unlock(&lgr->llc_conf_mutex);
 }
 
+static int 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_usable(&lgr->lnk[i]))
+                       continue;
+               link_count++;
+       }
+       return link_count;
+}
+
+/* find the asymmetric link when 3 links are established  */
+static struct 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];
+}
+
+static void smc_llc_delete_asym_link(struct smc_link_group *lgr)
+{
+       struct smc_link *lnk_new = NULL, *lnk_asym;
+       struct smc_llc_qentry *qentry;
+       int rc;
+
+       lnk_asym = smc_llc_find_asym_link(lgr);
+       if (!lnk_asym)
+               return; /* no asymmetric link */
+       if (!smc_link_downing(&lnk_asym->state))
+               return;
+       lnk_new = smc_switch_conns(lgr, lnk_asym, false);
+       smc_wr_tx_wait_no_pending_sends(lnk_asym);
+       if (!lnk_new)
+               goto out_free;
+       /* change flow type from ADD_LINK into DEL_LINK */
+       lgr->llc_flow_lcl.type = SMC_LLC_FLOW_DEL_LINK;
+       rc = smc_llc_send_delete_link(lnk_new, lnk_asym->link_id, SMC_LLC_REQ,
+                                     true, SMC_LLC_DEL_NO_ASYM_NEEDED);
+       if (rc) {
+               smcr_link_down_cond(lnk_new);
+               goto out_free;
+       }
+       qentry = smc_llc_wait(lgr, lnk_new, SMC_LLC_WAIT_TIME,
+                             SMC_LLC_DELETE_LINK);
+       if (!qentry) {
+               smcr_link_down_cond(lnk_new);
+               goto out_free;
+       }
+       smc_llc_flow_qentry_del(&lgr->llc_flow_lcl);
+out_free:
+       smcr_link_clear(lnk_asym, true);
+}
+
 static int smc_llc_srv_rkey_exchange(struct smc_link *link,
                                     struct smc_link *link_new)
 {
@@ -904,6 +1019,38 @@ out:
        return rc;
 }
 
+static int smc_llc_srv_conf_link(struct smc_link *link,
+                                struct smc_link *link_new,
+                                enum smc_lgr_type lgr_new_t)
+{
+       struct smc_link_group *lgr = link->lgr;
+       struct smc_llc_qentry *qentry = NULL;
+       int rc;
+
+       /* send CONFIRM LINK request over the RoCE fabric */
+       rc = smc_llc_send_confirm_link(link_new, SMC_LLC_REQ);
+       if (rc)
+               return -ENOLINK;
+       /* receive CONFIRM LINK response over the RoCE fabric */
+       qentry = smc_llc_wait(lgr, link, SMC_LLC_WAIT_FIRST_TIME,
+                             SMC_LLC_CONFIRM_LINK);
+       if (!qentry) {
+               /* send DELETE LINK */
+               smc_llc_send_delete_link(link, link_new->link_id, SMC_LLC_REQ,
+                                        false, SMC_LLC_DEL_LOST_PATH);
+               return -ENOLINK;
+       }
+       smc_llc_save_peer_uid(qentry);
+       smc_llc_link_active(link_new);
+       if (lgr_new_t == SMC_LGR_ASYMMETRIC_LOCAL ||
+           lgr_new_t == SMC_LGR_ASYMMETRIC_PEER)
+               smcr_lgr_set_type_asym(lgr, lgr_new_t, link_new->link_idx);
+       else
+               smcr_lgr_set_type(lgr, lgr_new_t);
+       smc_llc_flow_qentry_del(&lgr->llc_flow_lcl);
+       return 0;
+}
+
 int smc_llc_srv_add_link(struct smc_link *link)
 {
        enum smc_lgr_type lgr_new_t = SMC_LGR_SYMMETRIC;
@@ -967,12 +1114,12 @@ int smc_llc_srv_add_link(struct smc_link *link)
        rc = smc_llc_srv_rkey_exchange(link, link_new);
        if (rc)
                goto out_err;
-       /* tbd: rc = smc_llc_srv_conf_link(link, link_new, lgr_new_t); */
+       rc = smc_llc_srv_conf_link(link, link_new, lgr_new_t);
        if (rc)
                goto out_err;
        return 0;
 out_err:
-       smcr_link_clear(link_new);
+       smcr_link_clear(link_new, false);
        return rc;
 }
 
@@ -987,11 +1134,22 @@ static void smc_llc_process_srv_add_link(struct smc_link_group *lgr)
        rc = smc_llc_srv_add_link(link);
        if (!rc && lgr->type == SMC_LGR_SYMMETRIC) {
                /* delete any asymmetric link */
-               /* tbd: smc_llc_delete_asym_link(lgr); */
+               smc_llc_delete_asym_link(lgr);
        }
        mutex_unlock(&lgr->llc_conf_mutex);
 }
 
+/* enqueue a local add_link req to trigger a new add_link flow, only as SERV */
+void smc_llc_srv_add_link_local(struct smc_link *link)
+{
+       struct smc_llc_msg_add_link add_llc = {0};
+
+       add_llc.hd.length = sizeof(add_llc);
+       add_llc.hd.common.type = SMC_LLC_ADD_LINK;
+       /* no dev and port needed, we as server ignore client data anyway */
+       smc_llc_enqueue(link, (union smc_llc_msg *)&add_llc);
+}
+
 /* worker to process an add link message */
 static void smc_llc_add_link_work(struct work_struct *work)
 {
@@ -1012,22 +1170,186 @@ out:
        smc_llc_flow_stop(lgr, &lgr->llc_flow_lcl);
 }
 
-static void smc_llc_rx_delete_link(struct smc_link *link,
-                                  struct smc_llc_msg_del_link *llc)
+/* enqueue a local del_link msg to trigger a new del_link flow,
+ * called only for role SMC_SERV
+ */
+void smc_llc_srv_delete_link_local(struct smc_link *link, u8 del_link_id)
 {
-       struct smc_link_group *lgr = smc_get_lgr(link);
+       struct smc_llc_msg_del_link del_llc = {0};
+
+       del_llc.hd.length = sizeof(del_llc);
+       del_llc.hd.common.type = SMC_LLC_DELETE_LINK;
+       del_llc.link_num = del_link_id;
+       del_llc.reason = htonl(SMC_LLC_DEL_LOST_PATH);
+       del_llc.hd.flags |= SMC_LLC_FLAG_DEL_LINK_ORDERLY;
+       smc_llc_enqueue(link, (union smc_llc_msg *)&del_llc);
+}
 
-       smc_lgr_forget(lgr);
-       if (lgr->role == SMC_SERV) {
-               /* client asks to delete this link, send request */
-               smc_llc_send_delete_link(link, 0, SMC_LLC_REQ, true,
-                                        SMC_LLC_DEL_PROG_INIT_TERM);
-       } else {
-               /* server requests to delete this link, send response */
-               smc_llc_send_delete_link(link, 0, SMC_LLC_RESP, true,
-                                        SMC_LLC_DEL_PROG_INIT_TERM);
+static void smc_llc_process_cli_delete_link(struct smc_link_group *lgr)
+{
+       struct smc_link *lnk_del = NULL, *lnk_asym, *lnk;
+       struct smc_llc_msg_del_link *del_llc;
+       struct smc_llc_qentry *qentry;
+       int active_links;
+       int lnk_idx;
+
+       qentry = smc_llc_flow_qentry_clr(&lgr->llc_flow_lcl);
+       lnk = qentry->link;
+       del_llc = &qentry->msg.delete_link;
+
+       if (del_llc->hd.flags & SMC_LLC_FLAG_DEL_LINK_ALL) {
+               smc_lgr_terminate_sched(lgr);
+               goto out;
+       }
+       mutex_lock(&lgr->llc_conf_mutex);
+       /* delete single link */
+       for (lnk_idx = 0; lnk_idx < SMC_LINKS_PER_LGR_MAX; lnk_idx++) {
+               if (lgr->lnk[lnk_idx].link_id != del_llc->link_num)
+                       continue;
+               lnk_del = &lgr->lnk[lnk_idx];
+               break;
+       }
+       del_llc->hd.flags |= SMC_LLC_FLAG_RESP;
+       if (!lnk_del) {
+               /* link was not found */
+               del_llc->reason = htonl(SMC_LLC_DEL_NOLNK);
+               smc_llc_send_message(lnk, &qentry->msg);
+               goto out_unlock;
+       }
+       lnk_asym = smc_llc_find_asym_link(lgr);
+
+       del_llc->reason = 0;
+       smc_llc_send_message(lnk, &qentry->msg); /* response */
+
+       if (smc_link_downing(&lnk_del->state)) {
+               smc_switch_conns(lgr, lnk_del, false);
+               smc_wr_tx_wait_no_pending_sends(lnk_del);
+       }
+       smcr_link_clear(lnk_del, true);
+
+       active_links = smc_llc_active_link_count(lgr);
+       if (lnk_del == lnk_asym) {
+               /* expected deletion of asym link, don't change lgr state */
+       } else if (active_links == 1) {
+               smcr_lgr_set_type(lgr, SMC_LGR_SINGLE);
+       } else if (!active_links) {
+               smcr_lgr_set_type(lgr, SMC_LGR_NONE);
+               smc_lgr_terminate_sched(lgr);
+       }
+out_unlock:
+       mutex_unlock(&lgr->llc_conf_mutex);
+out:
+       kfree(qentry);
+}
+
+/* try to send a DELETE LINK ALL request on any active link,
+ * waiting for send completion
+ */
+void smc_llc_send_link_delete_all(struct smc_link_group *lgr, bool ord, u32 rsn)
+{
+       struct smc_llc_msg_del_link delllc = {0};
+       int i;
+
+       delllc.hd.common.type = SMC_LLC_DELETE_LINK;
+       delllc.hd.length = sizeof(delllc);
+       if (ord)
+               delllc.hd.flags |= SMC_LLC_FLAG_DEL_LINK_ORDERLY;
+       delllc.hd.flags |= SMC_LLC_FLAG_DEL_LINK_ALL;
+       delllc.reason = htonl(rsn);
+
+       for (i = 0; i < SMC_LINKS_PER_LGR_MAX; i++) {
+               if (!smc_link_usable(&lgr->lnk[i]))
+                       continue;
+               if (!smc_llc_send_message_wait(&lgr->lnk[i], &delllc))
+                       break;
        }
-       smcr_link_down_cond(link);
+}
+
+static void 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;
+
+       mutex_lock(&lgr->llc_conf_mutex);
+       qentry = smc_llc_flow_qentry_clr(&lgr->llc_flow_lcl);
+       lnk = qentry->link;
+       del_llc = &qentry->msg.delete_link;
+
+       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)) {
+               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
+                * initiate the DELETE_LINK processing), or a locally
+                * enqueued DELETE_LINK request (forward it)
+                */
+               if (!smc_llc_send_message(lnk, &qentry->msg)) {
+                       struct smc_llc_qentry *qentry2;
+
+                       qentry2 = smc_llc_wait(lgr, lnk, SMC_LLC_WAIT_TIME,
+                                              SMC_LLC_DELETE_LINK);
+                       if (qentry2)
+                               smc_llc_flow_qentry_del(&lgr->llc_flow_lcl);
+               }
+       }
+       smcr_link_clear(lnk_del, true);
+
+       active_links = smc_llc_active_link_count(lgr);
+       if (active_links == 1) {
+               smcr_lgr_set_type(lgr, SMC_LGR_SINGLE);
+       } else if (!active_links) {
+               smcr_lgr_set_type(lgr, SMC_LGR_NONE);
+               smc_lgr_terminate_sched(lgr);
+       }
+
+       if (lgr->type == SMC_LGR_SINGLE && !list_empty(&lgr->list)) {
+               /* trigger setup of asymm alt link */
+               smc_llc_srv_add_link_local(lnk);
+       }
+out:
+       mutex_unlock(&lgr->llc_conf_mutex);
+       kfree(qentry);
+}
+
+static void smc_llc_delete_link_work(struct work_struct *work)
+{
+       struct smc_link_group *lgr = container_of(work, struct smc_link_group,
+                                                 llc_del_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_delete_link(lgr);
+       else
+               smc_llc_process_srv_delete_link(lgr);
+out:
+       smc_llc_flow_stop(lgr, &lgr->llc_flow_lcl);
 }
 
 /* process a confirm_rkey request from peer, remote flow */
@@ -1094,6 +1416,14 @@ static void smc_llc_rmt_delete_rkey(struct smc_link_group *lgr)
        smc_llc_flow_qentry_del(&lgr->llc_flow_rmt);
 }
 
+static void smc_llc_protocol_violation(struct smc_link_group *lgr, u8 type)
+{
+       pr_warn_ratelimited("smc: SMC-R lg %*phN LLC protocol violation: "
+                           "llc_type %d\n", SMC_LGR_ID_SIZE, &lgr->id, type);
+       smc_llc_set_termination_rsn(lgr, SMC_LLC_DEL_PROT_VIOL);
+       smc_lgr_terminate_sched(lgr);
+}
+
 /* flush the llc event queue */
 static void smc_llc_event_flush(struct smc_link_group *lgr)
 {
@@ -1149,8 +1479,30 @@ static void smc_llc_event_handler(struct smc_llc_qentry *qentry)
                }
                break;
        case SMC_LLC_DELETE_LINK:
-               smc_llc_rx_delete_link(link, &llc->delete_link);
-               break;
+               if (lgr->role == SMC_CLNT) {
+                       /* server requests to delete this link, send response */
+                       if (lgr->llc_flow_lcl.type != SMC_LLC_FLOW_NONE) {
+                               /* DEL LINK REQ during ADD LINK SEQ */
+                               smc_llc_flow_qentry_set(&lgr->llc_flow_lcl,
+                                                       qentry);
+                               wake_up_interruptible(&lgr->llc_waiter);
+                       } else if (smc_llc_flow_start(&lgr->llc_flow_lcl,
+                                                     qentry)) {
+                               schedule_work(&lgr->llc_del_link_work);
+                       }
+               } else {
+                       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_interruptible(&lgr->llc_waiter);
+                       } else if (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)) {
@@ -1172,6 +1524,9 @@ static void smc_llc_event_handler(struct smc_llc_qentry *qentry)
                        smc_llc_flow_stop(lgr, &lgr->llc_flow_rmt);
                }
                return;
+       default:
+               smc_llc_protocol_violation(lgr, llc->raw.hdr.common.type);
+               break;
        }
 out:
        kfree(qentry);
@@ -1219,6 +1574,7 @@ static void smc_llc_rx_response(struct smc_link *link,
                        complete(&link->llc_testlink_resp);
                break;
        case SMC_LLC_ADD_LINK:
+       case SMC_LLC_DELETE_LINK:
        case SMC_LLC_CONFIRM_LINK:
        case SMC_LLC_ADD_LINK_CONT:
        case SMC_LLC_CONFIRM_RKEY:
@@ -1227,13 +1583,12 @@ static void smc_llc_rx_response(struct smc_link *link,
                smc_llc_flow_qentry_set(&link->lgr->llc_flow_lcl, qentry);
                wake_up_interruptible(&link->lgr->llc_waiter);
                return;
-       case SMC_LLC_DELETE_LINK:
-               if (link->lgr->role == SMC_SERV)
-                       smc_lgr_schedule_free_work_fast(link->lgr);
-               break;
        case SMC_LLC_CONFIRM_RKEY_CONT:
                /* not used because max links is 3 */
                break;
+       default:
+               smc_llc_protocol_violation(link->lgr, llc_type);
+               break;
        }
        kfree(qentry);
 }
@@ -1318,6 +1673,7 @@ void smc_llc_lgr_init(struct smc_link_group *lgr, struct smc_sock *smc)
 
        INIT_WORK(&lgr->llc_event_work, smc_llc_event_work);
        INIT_WORK(&lgr->llc_add_link_work, smc_llc_add_link_work);
+       INIT_WORK(&lgr->llc_del_link_work, smc_llc_delete_link_work);
        INIT_LIST_HEAD(&lgr->llc_event_q);
        spin_lock_init(&lgr->llc_event_q_lock);
        spin_lock_init(&lgr->llc_flow_lock);
@@ -1333,6 +1689,7 @@ void smc_llc_lgr_clear(struct smc_link_group *lgr)
        wake_up_interruptible_all(&lgr->llc_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;
@@ -1348,6 +1705,12 @@ int smc_llc_link_init(struct smc_link *link)
 
 void smc_llc_link_active(struct smc_link *link)
 {
+       pr_warn_ratelimited("smc: SMC-R lg %*phN link added: id %*phN, "
+                           "peerid %*phN, ibdev %s, ibport %d\n",
+                           SMC_LGR_ID_SIZE, &link->lgr->id,
+                           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 * HZ;
@@ -1357,8 +1720,15 @@ void smc_llc_link_active(struct smc_link *link)
 }
 
 /* called in worker context */
-void smc_llc_link_clear(struct smc_link *link)
+void smc_llc_link_clear(struct smc_link *link, bool log)
 {
+       if (log)
+               pr_warn_ratelimited("smc: SMC-R lg %*phN link removed: id %*phN"
+                                   ", peerid %*phN, ibdev %s, ibport %d\n",
+                                   SMC_LGR_ID_SIZE, &link->lgr->id,
+                                   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);
        smc_wr_wakeup_reg_wait(link);
@@ -1414,12 +1784,29 @@ out:
        return rc;
 }
 
+void smc_llc_link_set_uid(struct smc_link *link)
+{
+       __be32 link_uid;
+
+       link_uid = htonl(*((u32 *)link->lgr->id) + link->link_id);
+       memcpy(link->link_uid, &link_uid, SMC_LGR_ID_SIZE);
+}
+
+/* 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 */
+       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;
        return 0;