+/*
+ * Set a single ni as preferred, provided no preferred ni is already
+ * defined. Only to be used for non-multi-rail peer_ni.
+ */
+int
+lnet_peer_ni_set_non_mr_pref_nid(struct lnet_peer_ni *lpni, lnet_nid_t nid)
+{
+ int rc = 0;
+
+ spin_lock(&lpni->lpni_lock);
+ if (nid == LNET_NID_ANY) {
+ rc = -EINVAL;
+ } else if (lpni->lpni_pref_nnids > 0) {
+ rc = -EPERM;
+ } else if (lpni->lpni_pref_nnids == 0) {
+ lpni->lpni_pref.nid = nid;
+ lpni->lpni_pref_nnids = 1;
+ lpni->lpni_state |= LNET_PEER_NI_NON_MR_PREF;
+ }
+ spin_unlock(&lpni->lpni_lock);
+
+ CDEBUG(D_NET, "peer %s nid %s: %d\n",
+ libcfs_nid2str(lpni->lpni_nid), libcfs_nid2str(nid), rc);
+ return rc;
+}
+
+/*
+ * Clear the preferred NID from a non-multi-rail peer_ni, provided
+ * this preference was set by lnet_peer_ni_set_non_mr_pref_nid().
+ */
+int
+lnet_peer_ni_clr_non_mr_pref_nid(struct lnet_peer_ni *lpni)
+{
+ int rc = 0;
+
+ spin_lock(&lpni->lpni_lock);
+ if (lpni->lpni_state & LNET_PEER_NI_NON_MR_PREF) {
+ lpni->lpni_pref_nnids = 0;
+ lpni->lpni_state &= ~LNET_PEER_NI_NON_MR_PREF;
+ } else if (lpni->lpni_pref_nnids == 0) {
+ rc = -ENOENT;
+ } else {
+ rc = -EPERM;
+ }
+ spin_unlock(&lpni->lpni_lock);
+
+ CDEBUG(D_NET, "peer %s: %d\n",
+ libcfs_nid2str(lpni->lpni_nid), rc);
+ return rc;
+}
+
+/*
+ * Clear the preferred NIDs from a non-multi-rail peer.
+ */
+void
+lnet_peer_clr_non_mr_pref_nids(struct lnet_peer *lp)
+{
+ struct lnet_peer_ni *lpni = NULL;
+
+ while ((lpni = lnet_get_next_peer_ni_locked(lp, NULL, lpni)) != NULL)
+ lnet_peer_ni_clr_non_mr_pref_nid(lpni);
+}
+
+int
+lnet_peer_add_pref_nid(struct lnet_peer_ni *lpni, lnet_nid_t nid)
+{
+ lnet_nid_t *nids = NULL;
+ lnet_nid_t *oldnids = NULL;
+ struct lnet_peer *lp = lpni->lpni_peer_net->lpn_peer;
+ int size;
+ int i;
+ int rc = 0;
+
+ if (nid == LNET_NID_ANY) {
+ rc = -EINVAL;
+ goto out;
+ }
+
+ if (lpni->lpni_pref_nnids == 1 && lpni->lpni_pref.nid == nid) {
+ rc = -EEXIST;
+ goto out;
+ }
+
+ /* A non-MR node may have only one preferred NI per peer_ni */
+ if (lpni->lpni_pref_nnids > 0) {
+ if (!(lp->lp_state & LNET_PEER_MULTI_RAIL)) {
+ rc = -EPERM;
+ goto out;
+ }
+ }
+
+ if (lpni->lpni_pref_nnids != 0) {
+ size = sizeof(*nids) * (lpni->lpni_pref_nnids + 1);
+ LIBCFS_CPT_ALLOC(nids, lnet_cpt_table(), lpni->lpni_cpt, size);
+ if (!nids) {
+ rc = -ENOMEM;
+ goto out;
+ }
+ for (i = 0; i < lpni->lpni_pref_nnids; i++) {
+ if (lpni->lpni_pref.nids[i] == nid) {
+ LIBCFS_FREE(nids, size);
+ rc = -EEXIST;
+ goto out;
+ }
+ nids[i] = lpni->lpni_pref.nids[i];
+ }
+ nids[i] = nid;
+ }
+
+ lnet_net_lock(LNET_LOCK_EX);
+ spin_lock(&lpni->lpni_lock);
+ if (lpni->lpni_pref_nnids == 0) {
+ lpni->lpni_pref.nid = nid;
+ } else {
+ oldnids = lpni->lpni_pref.nids;
+ lpni->lpni_pref.nids = nids;
+ }
+ lpni->lpni_pref_nnids++;
+ lpni->lpni_state &= ~LNET_PEER_NI_NON_MR_PREF;
+ spin_unlock(&lpni->lpni_lock);
+ lnet_net_unlock(LNET_LOCK_EX);
+
+ if (oldnids) {
+ size = sizeof(*nids) * (lpni->lpni_pref_nnids - 1);
+ LIBCFS_FREE(oldnids, sizeof(*oldnids) * size);
+ }
+out:
+ if (rc == -EEXIST && (lpni->lpni_state & LNET_PEER_NI_NON_MR_PREF)) {
+ spin_lock(&lpni->lpni_lock);
+ lpni->lpni_state &= ~LNET_PEER_NI_NON_MR_PREF;
+ spin_unlock(&lpni->lpni_lock);
+ }
+ CDEBUG(D_NET, "peer %s nid %s: %d\n",
+ libcfs_nid2str(lp->lp_primary_nid), libcfs_nid2str(nid), rc);
+ return rc;
+}
+
+int
+lnet_peer_del_pref_nid(struct lnet_peer_ni *lpni, lnet_nid_t nid)
+{
+ lnet_nid_t *nids = NULL;
+ lnet_nid_t *oldnids = NULL;
+ struct lnet_peer *lp = lpni->lpni_peer_net->lpn_peer;
+ int size;
+ int i, j;
+ int rc = 0;
+
+ if (lpni->lpni_pref_nnids == 0) {
+ rc = -ENOENT;
+ goto out;
+ }
+
+ if (lpni->lpni_pref_nnids == 1) {
+ if (lpni->lpni_pref.nid != nid) {
+ rc = -ENOENT;
+ goto out;
+ }
+ } else if (lpni->lpni_pref_nnids == 2) {
+ if (lpni->lpni_pref.nids[0] != nid &&
+ lpni->lpni_pref.nids[1] != nid) {
+ rc = -ENOENT;
+ goto out;
+ }
+ } else {
+ size = sizeof(*nids) * (lpni->lpni_pref_nnids - 1);
+ LIBCFS_CPT_ALLOC(nids, lnet_cpt_table(), lpni->lpni_cpt, size);
+ if (!nids) {
+ rc = -ENOMEM;
+ goto out;
+ }
+ for (i = 0, j = 0; i < lpni->lpni_pref_nnids; i++) {
+ if (lpni->lpni_pref.nids[i] != nid)
+ continue;
+ nids[j++] = lpni->lpni_pref.nids[i];
+ }
+ /* Check if we actually removed a nid. */
+ if (j == lpni->lpni_pref_nnids) {
+ LIBCFS_FREE(nids, size);
+ rc = -ENOENT;
+ goto out;
+ }
+ }
+
+ lnet_net_lock(LNET_LOCK_EX);
+ spin_lock(&lpni->lpni_lock);
+ if (lpni->lpni_pref_nnids == 1) {
+ lpni->lpni_pref.nid = LNET_NID_ANY;
+ } else if (lpni->lpni_pref_nnids == 2) {
+ oldnids = lpni->lpni_pref.nids;
+ if (oldnids[0] == nid)
+ lpni->lpni_pref.nid = oldnids[1];
+ else
+ lpni->lpni_pref.nid = oldnids[2];
+ } else {
+ oldnids = lpni->lpni_pref.nids;
+ lpni->lpni_pref.nids = nids;
+ }
+ lpni->lpni_pref_nnids--;
+ lpni->lpni_state &= ~LNET_PEER_NI_NON_MR_PREF;
+ spin_unlock(&lpni->lpni_lock);
+ lnet_net_unlock(LNET_LOCK_EX);
+
+ if (oldnids) {
+ size = sizeof(*nids) * (lpni->lpni_pref_nnids + 1);
+ LIBCFS_FREE(oldnids, sizeof(*oldnids) * size);
+ }
+out:
+ CDEBUG(D_NET, "peer %s nid %s: %d\n",
+ libcfs_nid2str(lp->lp_primary_nid), libcfs_nid2str(nid), rc);
+ return rc;
+}
+