| /* |
| * GPL HEADER START |
| * |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License version 2 only, |
| * as published by the Free Software Foundation. |
| * |
| * This program is distributed in the hope that it will be useful, but |
| * WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * General Public License version 2 for more details (a copy is included |
| * in the LICENSE file that accompanied this code). |
| * |
| * You should have received a copy of the GNU General Public License |
| * version 2 along with this program; If not, see |
| * http://www.sun.com/software/products/lustre/docs/GPLv2.pdf |
| * |
| * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, |
| * CA 95054 USA or visit www.sun.com if you need additional information or |
| * have any questions. |
| * |
| * GPL HEADER END |
| */ |
| /* |
| * Copyright (c) 2003, 2010, Oracle and/or its affiliates. All rights reserved. |
| * Use is subject to license terms. |
| * |
| * Copyright (c) 2011, 2012, Intel Corporation. |
| */ |
| /* |
| * This file is part of Lustre, http://www.lustre.org/ |
| * Lustre is a trademark of Sun Microsystems, Inc. |
| * |
| * lnet/klnds/socklnd/socklnd.c |
| * |
| * Author: Zach Brown <zab@zabbo.net> |
| * Author: Peter J. Braam <braam@clusterfs.com> |
| * Author: Phil Schwan <phil@clusterfs.com> |
| * Author: Eric Barton <eric@bartonsoftware.com> |
| */ |
| |
| #include "socklnd.h" |
| |
| static lnd_t the_ksocklnd; |
| ksock_nal_data_t ksocknal_data; |
| |
| static ksock_interface_t * |
| ksocknal_ip2iface(lnet_ni_t *ni, __u32 ip) |
| { |
| ksock_net_t *net = ni->ni_data; |
| int i; |
| ksock_interface_t *iface; |
| |
| for (i = 0; i < net->ksnn_ninterfaces; i++) { |
| LASSERT(i < LNET_MAX_INTERFACES); |
| iface = &net->ksnn_interfaces[i]; |
| |
| if (iface->ksni_ipaddr == ip) |
| return iface; |
| } |
| |
| return NULL; |
| } |
| |
| static ksock_route_t * |
| ksocknal_create_route (__u32 ipaddr, int port) |
| { |
| ksock_route_t *route; |
| |
| LIBCFS_ALLOC (route, sizeof (*route)); |
| if (route == NULL) |
| return NULL; |
| |
| atomic_set (&route->ksnr_refcount, 1); |
| route->ksnr_peer = NULL; |
| route->ksnr_retry_interval = 0; /* OK to connect at any time */ |
| route->ksnr_ipaddr = ipaddr; |
| route->ksnr_port = port; |
| route->ksnr_scheduled = 0; |
| route->ksnr_connecting = 0; |
| route->ksnr_connected = 0; |
| route->ksnr_deleted = 0; |
| route->ksnr_conn_count = 0; |
| route->ksnr_share_count = 0; |
| |
| return route; |
| } |
| |
| void |
| ksocknal_destroy_route (ksock_route_t *route) |
| { |
| LASSERT (atomic_read(&route->ksnr_refcount) == 0); |
| |
| if (route->ksnr_peer != NULL) |
| ksocknal_peer_decref(route->ksnr_peer); |
| |
| LIBCFS_FREE (route, sizeof (*route)); |
| } |
| |
| static int |
| ksocknal_create_peer (ksock_peer_t **peerp, lnet_ni_t *ni, lnet_process_id_t id) |
| { |
| ksock_net_t *net = ni->ni_data; |
| ksock_peer_t *peer; |
| |
| LASSERT (id.nid != LNET_NID_ANY); |
| LASSERT (id.pid != LNET_PID_ANY); |
| LASSERT (!in_interrupt()); |
| |
| LIBCFS_ALLOC (peer, sizeof (*peer)); |
| if (peer == NULL) |
| return -ENOMEM; |
| |
| peer->ksnp_ni = ni; |
| peer->ksnp_id = id; |
| atomic_set (&peer->ksnp_refcount, 1); /* 1 ref for caller */ |
| peer->ksnp_closing = 0; |
| peer->ksnp_accepting = 0; |
| peer->ksnp_proto = NULL; |
| peer->ksnp_last_alive = 0; |
| peer->ksnp_zc_next_cookie = SOCKNAL_KEEPALIVE_PING + 1; |
| |
| INIT_LIST_HEAD (&peer->ksnp_conns); |
| INIT_LIST_HEAD (&peer->ksnp_routes); |
| INIT_LIST_HEAD (&peer->ksnp_tx_queue); |
| INIT_LIST_HEAD (&peer->ksnp_zc_req_list); |
| spin_lock_init(&peer->ksnp_lock); |
| |
| spin_lock_bh(&net->ksnn_lock); |
| |
| if (net->ksnn_shutdown) { |
| spin_unlock_bh(&net->ksnn_lock); |
| |
| LIBCFS_FREE(peer, sizeof(*peer)); |
| CERROR("Can't create peer: network shutdown\n"); |
| return -ESHUTDOWN; |
| } |
| |
| net->ksnn_npeers++; |
| |
| spin_unlock_bh(&net->ksnn_lock); |
| |
| *peerp = peer; |
| return 0; |
| } |
| |
| void |
| ksocknal_destroy_peer (ksock_peer_t *peer) |
| { |
| ksock_net_t *net = peer->ksnp_ni->ni_data; |
| |
| CDEBUG (D_NET, "peer %s %p deleted\n", |
| libcfs_id2str(peer->ksnp_id), peer); |
| |
| LASSERT (atomic_read (&peer->ksnp_refcount) == 0); |
| LASSERT (peer->ksnp_accepting == 0); |
| LASSERT (list_empty (&peer->ksnp_conns)); |
| LASSERT (list_empty (&peer->ksnp_routes)); |
| LASSERT (list_empty (&peer->ksnp_tx_queue)); |
| LASSERT (list_empty (&peer->ksnp_zc_req_list)); |
| |
| LIBCFS_FREE (peer, sizeof (*peer)); |
| |
| /* NB a peer's connections and routes keep a reference on their peer |
| * until they are destroyed, so we can be assured that _all_ state to |
| * do with this peer has been cleaned up when its refcount drops to |
| * zero. */ |
| spin_lock_bh(&net->ksnn_lock); |
| net->ksnn_npeers--; |
| spin_unlock_bh(&net->ksnn_lock); |
| } |
| |
| ksock_peer_t * |
| ksocknal_find_peer_locked (lnet_ni_t *ni, lnet_process_id_t id) |
| { |
| struct list_head *peer_list = ksocknal_nid2peerlist(id.nid); |
| struct list_head *tmp; |
| ksock_peer_t *peer; |
| |
| list_for_each (tmp, peer_list) { |
| |
| peer = list_entry (tmp, ksock_peer_t, ksnp_list); |
| |
| LASSERT (!peer->ksnp_closing); |
| |
| if (peer->ksnp_ni != ni) |
| continue; |
| |
| if (peer->ksnp_id.nid != id.nid || |
| peer->ksnp_id.pid != id.pid) |
| continue; |
| |
| CDEBUG(D_NET, "got peer [%p] -> %s (%d)\n", |
| peer, libcfs_id2str(id), |
| atomic_read(&peer->ksnp_refcount)); |
| return peer; |
| } |
| return NULL; |
| } |
| |
| ksock_peer_t * |
| ksocknal_find_peer (lnet_ni_t *ni, lnet_process_id_t id) |
| { |
| ksock_peer_t *peer; |
| |
| read_lock(&ksocknal_data.ksnd_global_lock); |
| peer = ksocknal_find_peer_locked(ni, id); |
| if (peer != NULL) /* +1 ref for caller? */ |
| ksocknal_peer_addref(peer); |
| read_unlock(&ksocknal_data.ksnd_global_lock); |
| |
| return peer; |
| } |
| |
| static void |
| ksocknal_unlink_peer_locked (ksock_peer_t *peer) |
| { |
| int i; |
| __u32 ip; |
| ksock_interface_t *iface; |
| |
| for (i = 0; i < peer->ksnp_n_passive_ips; i++) { |
| LASSERT (i < LNET_MAX_INTERFACES); |
| ip = peer->ksnp_passive_ips[i]; |
| |
| iface = ksocknal_ip2iface(peer->ksnp_ni, ip); |
| /* All IPs in peer->ksnp_passive_ips[] come from the |
| * interface list, therefore the call must succeed. */ |
| LASSERT (iface != NULL); |
| |
| CDEBUG(D_NET, "peer=%p iface=%p ksni_nroutes=%d\n", |
| peer, iface, iface->ksni_nroutes); |
| iface->ksni_npeers--; |
| } |
| |
| LASSERT (list_empty(&peer->ksnp_conns)); |
| LASSERT (list_empty(&peer->ksnp_routes)); |
| LASSERT (!peer->ksnp_closing); |
| peer->ksnp_closing = 1; |
| list_del (&peer->ksnp_list); |
| /* lose peerlist's ref */ |
| ksocknal_peer_decref(peer); |
| } |
| |
| static int |
| ksocknal_get_peer_info (lnet_ni_t *ni, int index, |
| lnet_process_id_t *id, __u32 *myip, __u32 *peer_ip, |
| int *port, int *conn_count, int *share_count) |
| { |
| ksock_peer_t *peer; |
| struct list_head *ptmp; |
| ksock_route_t *route; |
| struct list_head *rtmp; |
| int i; |
| int j; |
| int rc = -ENOENT; |
| |
| read_lock(&ksocknal_data.ksnd_global_lock); |
| |
| for (i = 0; i < ksocknal_data.ksnd_peer_hash_size; i++) { |
| |
| list_for_each (ptmp, &ksocknal_data.ksnd_peers[i]) { |
| peer = list_entry (ptmp, ksock_peer_t, ksnp_list); |
| |
| if (peer->ksnp_ni != ni) |
| continue; |
| |
| if (peer->ksnp_n_passive_ips == 0 && |
| list_empty(&peer->ksnp_routes)) { |
| if (index-- > 0) |
| continue; |
| |
| *id = peer->ksnp_id; |
| *myip = 0; |
| *peer_ip = 0; |
| *port = 0; |
| *conn_count = 0; |
| *share_count = 0; |
| rc = 0; |
| goto out; |
| } |
| |
| for (j = 0; j < peer->ksnp_n_passive_ips; j++) { |
| if (index-- > 0) |
| continue; |
| |
| *id = peer->ksnp_id; |
| *myip = peer->ksnp_passive_ips[j]; |
| *peer_ip = 0; |
| *port = 0; |
| *conn_count = 0; |
| *share_count = 0; |
| rc = 0; |
| goto out; |
| } |
| |
| list_for_each (rtmp, &peer->ksnp_routes) { |
| if (index-- > 0) |
| continue; |
| |
| route = list_entry(rtmp, ksock_route_t, |
| ksnr_list); |
| |
| *id = peer->ksnp_id; |
| *myip = route->ksnr_myipaddr; |
| *peer_ip = route->ksnr_ipaddr; |
| *port = route->ksnr_port; |
| *conn_count = route->ksnr_conn_count; |
| *share_count = route->ksnr_share_count; |
| rc = 0; |
| goto out; |
| } |
| } |
| } |
| out: |
| read_unlock(&ksocknal_data.ksnd_global_lock); |
| return rc; |
| } |
| |
| static void |
| ksocknal_associate_route_conn_locked(ksock_route_t *route, ksock_conn_t *conn) |
| { |
| ksock_peer_t *peer = route->ksnr_peer; |
| int type = conn->ksnc_type; |
| ksock_interface_t *iface; |
| |
| conn->ksnc_route = route; |
| ksocknal_route_addref(route); |
| |
| if (route->ksnr_myipaddr != conn->ksnc_myipaddr) { |
| if (route->ksnr_myipaddr == 0) { |
| /* route wasn't bound locally yet (the initial route) */ |
| CDEBUG(D_NET, "Binding %s %pI4h to %pI4h\n", |
| libcfs_id2str(peer->ksnp_id), |
| &route->ksnr_ipaddr, |
| &conn->ksnc_myipaddr); |
| } else { |
| CDEBUG(D_NET, "Rebinding %s %pI4h from %pI4h to %pI4h\n", |
| libcfs_id2str(peer->ksnp_id), |
| &route->ksnr_ipaddr, |
| &route->ksnr_myipaddr, |
| &conn->ksnc_myipaddr); |
| |
| iface = ksocknal_ip2iface(route->ksnr_peer->ksnp_ni, |
| route->ksnr_myipaddr); |
| if (iface != NULL) |
| iface->ksni_nroutes--; |
| } |
| route->ksnr_myipaddr = conn->ksnc_myipaddr; |
| iface = ksocknal_ip2iface(route->ksnr_peer->ksnp_ni, |
| route->ksnr_myipaddr); |
| if (iface != NULL) |
| iface->ksni_nroutes++; |
| } |
| |
| route->ksnr_connected |= (1<<type); |
| route->ksnr_conn_count++; |
| |
| /* Successful connection => further attempts can |
| * proceed immediately */ |
| route->ksnr_retry_interval = 0; |
| } |
| |
| static void |
| ksocknal_add_route_locked (ksock_peer_t *peer, ksock_route_t *route) |
| { |
| struct list_head *tmp; |
| ksock_conn_t *conn; |
| ksock_route_t *route2; |
| |
| LASSERT (!peer->ksnp_closing); |
| LASSERT (route->ksnr_peer == NULL); |
| LASSERT (!route->ksnr_scheduled); |
| LASSERT (!route->ksnr_connecting); |
| LASSERT (route->ksnr_connected == 0); |
| |
| /* LASSERT(unique) */ |
| list_for_each(tmp, &peer->ksnp_routes) { |
| route2 = list_entry(tmp, ksock_route_t, ksnr_list); |
| |
| if (route2->ksnr_ipaddr == route->ksnr_ipaddr) { |
| CERROR("Duplicate route %s %pI4h\n", |
| libcfs_id2str(peer->ksnp_id), |
| &route->ksnr_ipaddr); |
| LBUG(); |
| } |
| } |
| |
| route->ksnr_peer = peer; |
| ksocknal_peer_addref(peer); |
| /* peer's routelist takes over my ref on 'route' */ |
| list_add_tail(&route->ksnr_list, &peer->ksnp_routes); |
| |
| list_for_each(tmp, &peer->ksnp_conns) { |
| conn = list_entry(tmp, ksock_conn_t, ksnc_list); |
| |
| if (conn->ksnc_ipaddr != route->ksnr_ipaddr) |
| continue; |
| |
| ksocknal_associate_route_conn_locked(route, conn); |
| /* keep going (typed routes) */ |
| } |
| } |
| |
| static void |
| ksocknal_del_route_locked (ksock_route_t *route) |
| { |
| ksock_peer_t *peer = route->ksnr_peer; |
| ksock_interface_t *iface; |
| ksock_conn_t *conn; |
| struct list_head *ctmp; |
| struct list_head *cnxt; |
| |
| LASSERT (!route->ksnr_deleted); |
| |
| /* Close associated conns */ |
| list_for_each_safe (ctmp, cnxt, &peer->ksnp_conns) { |
| conn = list_entry(ctmp, ksock_conn_t, ksnc_list); |
| |
| if (conn->ksnc_route != route) |
| continue; |
| |
| ksocknal_close_conn_locked (conn, 0); |
| } |
| |
| if (route->ksnr_myipaddr != 0) { |
| iface = ksocknal_ip2iface(route->ksnr_peer->ksnp_ni, |
| route->ksnr_myipaddr); |
| if (iface != NULL) |
| iface->ksni_nroutes--; |
| } |
| |
| route->ksnr_deleted = 1; |
| list_del (&route->ksnr_list); |
| ksocknal_route_decref(route); /* drop peer's ref */ |
| |
| if (list_empty (&peer->ksnp_routes) && |
| list_empty (&peer->ksnp_conns)) { |
| /* I've just removed the last route to a peer with no active |
| * connections */ |
| ksocknal_unlink_peer_locked (peer); |
| } |
| } |
| |
| int |
| ksocknal_add_peer (lnet_ni_t *ni, lnet_process_id_t id, __u32 ipaddr, int port) |
| { |
| struct list_head *tmp; |
| ksock_peer_t *peer; |
| ksock_peer_t *peer2; |
| ksock_route_t *route; |
| ksock_route_t *route2; |
| int rc; |
| |
| if (id.nid == LNET_NID_ANY || |
| id.pid == LNET_PID_ANY) |
| return -EINVAL; |
| |
| /* Have a brand new peer ready... */ |
| rc = ksocknal_create_peer(&peer, ni, id); |
| if (rc != 0) |
| return rc; |
| |
| route = ksocknal_create_route (ipaddr, port); |
| if (route == NULL) { |
| ksocknal_peer_decref(peer); |
| return -ENOMEM; |
| } |
| |
| write_lock_bh(&ksocknal_data.ksnd_global_lock); |
| |
| /* always called with a ref on ni, so shutdown can't have started */ |
| LASSERT (((ksock_net_t *) ni->ni_data)->ksnn_shutdown == 0); |
| |
| peer2 = ksocknal_find_peer_locked (ni, id); |
| if (peer2 != NULL) { |
| ksocknal_peer_decref(peer); |
| peer = peer2; |
| } else { |
| /* peer table takes my ref on peer */ |
| list_add_tail (&peer->ksnp_list, |
| ksocknal_nid2peerlist (id.nid)); |
| } |
| |
| route2 = NULL; |
| list_for_each (tmp, &peer->ksnp_routes) { |
| route2 = list_entry(tmp, ksock_route_t, ksnr_list); |
| |
| if (route2->ksnr_ipaddr == ipaddr) |
| break; |
| |
| route2 = NULL; |
| } |
| if (route2 == NULL) { |
| ksocknal_add_route_locked(peer, route); |
| route->ksnr_share_count++; |
| } else { |
| ksocknal_route_decref(route); |
| route2->ksnr_share_count++; |
| } |
| |
| write_unlock_bh(&ksocknal_data.ksnd_global_lock); |
| |
| return 0; |
| } |
| |
| static void |
| ksocknal_del_peer_locked (ksock_peer_t *peer, __u32 ip) |
| { |
| ksock_conn_t *conn; |
| ksock_route_t *route; |
| struct list_head *tmp; |
| struct list_head *nxt; |
| int nshared; |
| |
| LASSERT (!peer->ksnp_closing); |
| |
| /* Extra ref prevents peer disappearing until I'm done with it */ |
| ksocknal_peer_addref(peer); |
| |
| list_for_each_safe (tmp, nxt, &peer->ksnp_routes) { |
| route = list_entry(tmp, ksock_route_t, ksnr_list); |
| |
| /* no match */ |
| if (!(ip == 0 || route->ksnr_ipaddr == ip)) |
| continue; |
| |
| route->ksnr_share_count = 0; |
| /* This deletes associated conns too */ |
| ksocknal_del_route_locked (route); |
| } |
| |
| nshared = 0; |
| list_for_each_safe (tmp, nxt, &peer->ksnp_routes) { |
| route = list_entry(tmp, ksock_route_t, ksnr_list); |
| nshared += route->ksnr_share_count; |
| } |
| |
| if (nshared == 0) { |
| /* remove everything else if there are no explicit entries |
| * left */ |
| |
| list_for_each_safe (tmp, nxt, &peer->ksnp_routes) { |
| route = list_entry(tmp, ksock_route_t, ksnr_list); |
| |
| /* we should only be removing auto-entries */ |
| LASSERT(route->ksnr_share_count == 0); |
| ksocknal_del_route_locked (route); |
| } |
| |
| list_for_each_safe (tmp, nxt, &peer->ksnp_conns) { |
| conn = list_entry(tmp, ksock_conn_t, ksnc_list); |
| |
| ksocknal_close_conn_locked(conn, 0); |
| } |
| } |
| |
| ksocknal_peer_decref(peer); |
| /* NB peer unlinks itself when last conn/route is removed */ |
| } |
| |
| static int |
| ksocknal_del_peer (lnet_ni_t *ni, lnet_process_id_t id, __u32 ip) |
| { |
| LIST_HEAD (zombies); |
| struct list_head *ptmp; |
| struct list_head *pnxt; |
| ksock_peer_t *peer; |
| int lo; |
| int hi; |
| int i; |
| int rc = -ENOENT; |
| |
| write_lock_bh(&ksocknal_data.ksnd_global_lock); |
| |
| if (id.nid != LNET_NID_ANY) |
| lo = hi = (int)(ksocknal_nid2peerlist(id.nid) - ksocknal_data.ksnd_peers); |
| else { |
| lo = 0; |
| hi = ksocknal_data.ksnd_peer_hash_size - 1; |
| } |
| |
| for (i = lo; i <= hi; i++) { |
| list_for_each_safe (ptmp, pnxt, |
| &ksocknal_data.ksnd_peers[i]) { |
| peer = list_entry (ptmp, ksock_peer_t, ksnp_list); |
| |
| if (peer->ksnp_ni != ni) |
| continue; |
| |
| if (!((id.nid == LNET_NID_ANY || peer->ksnp_id.nid == id.nid) && |
| (id.pid == LNET_PID_ANY || peer->ksnp_id.pid == id.pid))) |
| continue; |
| |
| ksocknal_peer_addref(peer); /* a ref for me... */ |
| |
| ksocknal_del_peer_locked (peer, ip); |
| |
| if (peer->ksnp_closing && |
| !list_empty(&peer->ksnp_tx_queue)) { |
| LASSERT (list_empty(&peer->ksnp_conns)); |
| LASSERT (list_empty(&peer->ksnp_routes)); |
| |
| list_splice_init(&peer->ksnp_tx_queue, |
| &zombies); |
| } |
| |
| ksocknal_peer_decref(peer); /* ...till here */ |
| |
| rc = 0; /* matched! */ |
| } |
| } |
| |
| write_unlock_bh(&ksocknal_data.ksnd_global_lock); |
| |
| ksocknal_txlist_done(ni, &zombies, 1); |
| |
| return rc; |
| } |
| |
| static ksock_conn_t * |
| ksocknal_get_conn_by_idx (lnet_ni_t *ni, int index) |
| { |
| ksock_peer_t *peer; |
| struct list_head *ptmp; |
| ksock_conn_t *conn; |
| struct list_head *ctmp; |
| int i; |
| |
| read_lock(&ksocknal_data.ksnd_global_lock); |
| |
| for (i = 0; i < ksocknal_data.ksnd_peer_hash_size; i++) { |
| list_for_each (ptmp, &ksocknal_data.ksnd_peers[i]) { |
| peer = list_entry (ptmp, ksock_peer_t, ksnp_list); |
| |
| LASSERT (!peer->ksnp_closing); |
| |
| if (peer->ksnp_ni != ni) |
| continue; |
| |
| list_for_each (ctmp, &peer->ksnp_conns) { |
| if (index-- > 0) |
| continue; |
| |
| conn = list_entry (ctmp, ksock_conn_t, |
| ksnc_list); |
| ksocknal_conn_addref(conn); |
| read_unlock(&ksocknal_data.ksnd_global_lock); |
| return conn; |
| } |
| } |
| } |
| |
| read_unlock(&ksocknal_data.ksnd_global_lock); |
| return NULL; |
| } |
| |
| static ksock_sched_t * |
| ksocknal_choose_scheduler_locked(unsigned int cpt) |
| { |
| struct ksock_sched_info *info = ksocknal_data.ksnd_sched_info[cpt]; |
| ksock_sched_t *sched; |
| int i; |
| |
| LASSERT(info->ksi_nthreads > 0); |
| |
| sched = &info->ksi_scheds[0]; |
| /* |
| * NB: it's safe so far, but info->ksi_nthreads could be changed |
| * at runtime when we have dynamic LNet configuration, then we |
| * need to take care of this. |
| */ |
| for (i = 1; i < info->ksi_nthreads; i++) { |
| if (sched->kss_nconns > info->ksi_scheds[i].kss_nconns) |
| sched = &info->ksi_scheds[i]; |
| } |
| |
| return sched; |
| } |
| |
| static int |
| ksocknal_local_ipvec (lnet_ni_t *ni, __u32 *ipaddrs) |
| { |
| ksock_net_t *net = ni->ni_data; |
| int i; |
| int nip; |
| |
| read_lock(&ksocknal_data.ksnd_global_lock); |
| |
| nip = net->ksnn_ninterfaces; |
| LASSERT (nip <= LNET_MAX_INTERFACES); |
| |
| /* Only offer interfaces for additional connections if I have |
| * more than one. */ |
| if (nip < 2) { |
| read_unlock(&ksocknal_data.ksnd_global_lock); |
| return 0; |
| } |
| |
| for (i = 0; i < nip; i++) { |
| ipaddrs[i] = net->ksnn_interfaces[i].ksni_ipaddr; |
| LASSERT (ipaddrs[i] != 0); |
| } |
| |
| read_unlock(&ksocknal_data.ksnd_global_lock); |
| return nip; |
| } |
| |
| static int |
| ksocknal_match_peerip (ksock_interface_t *iface, __u32 *ips, int nips) |
| { |
| int best_netmatch = 0; |
| int best_xor = 0; |
| int best = -1; |
| int this_xor; |
| int this_netmatch; |
| int i; |
| |
| for (i = 0; i < nips; i++) { |
| if (ips[i] == 0) |
| continue; |
| |
| this_xor = (ips[i] ^ iface->ksni_ipaddr); |
| this_netmatch = ((this_xor & iface->ksni_netmask) == 0) ? 1 : 0; |
| |
| if (!(best < 0 || |
| best_netmatch < this_netmatch || |
| (best_netmatch == this_netmatch && |
| best_xor > this_xor))) |
| continue; |
| |
| best = i; |
| best_netmatch = this_netmatch; |
| best_xor = this_xor; |
| } |
| |
| LASSERT (best >= 0); |
| return best; |
| } |
| |
| static int |
| ksocknal_select_ips(ksock_peer_t *peer, __u32 *peerips, int n_peerips) |
| { |
| rwlock_t *global_lock = &ksocknal_data.ksnd_global_lock; |
| ksock_net_t *net = peer->ksnp_ni->ni_data; |
| ksock_interface_t *iface; |
| ksock_interface_t *best_iface; |
| int n_ips; |
| int i; |
| int j; |
| int k; |
| __u32 ip; |
| __u32 xor; |
| int this_netmatch; |
| int best_netmatch; |
| int best_npeers; |
| |
| /* CAVEAT EMPTOR: We do all our interface matching with an |
| * exclusive hold of global lock at IRQ priority. We're only |
| * expecting to be dealing with small numbers of interfaces, so the |
| * O(n**3)-ness shouldn't matter */ |
| |
| /* Also note that I'm not going to return more than n_peerips |
| * interfaces, even if I have more myself */ |
| |
| write_lock_bh(global_lock); |
| |
| LASSERT (n_peerips <= LNET_MAX_INTERFACES); |
| LASSERT (net->ksnn_ninterfaces <= LNET_MAX_INTERFACES); |
| |
| /* Only match interfaces for additional connections |
| * if I have > 1 interface */ |
| n_ips = (net->ksnn_ninterfaces < 2) ? 0 : |
| MIN(n_peerips, net->ksnn_ninterfaces); |
| |
| for (i = 0; peer->ksnp_n_passive_ips < n_ips; i++) { |
| /* ^ yes really... */ |
| |
| /* If we have any new interfaces, first tick off all the |
| * peer IPs that match old interfaces, then choose new |
| * interfaces to match the remaining peer IPS. |
| * We don't forget interfaces we've stopped using; we might |
| * start using them again... */ |
| |
| if (i < peer->ksnp_n_passive_ips) { |
| /* Old interface. */ |
| ip = peer->ksnp_passive_ips[i]; |
| best_iface = ksocknal_ip2iface(peer->ksnp_ni, ip); |
| |
| } else { |
| /* choose a new interface */ |
| LASSERT (i == peer->ksnp_n_passive_ips); |
| |
| best_iface = NULL; |
| best_netmatch = 0; |
| best_npeers = 0; |
| |
| for (j = 0; j < net->ksnn_ninterfaces; j++) { |
| iface = &net->ksnn_interfaces[j]; |
| ip = iface->ksni_ipaddr; |
| |
| for (k = 0; k < peer->ksnp_n_passive_ips; k++) |
| if (peer->ksnp_passive_ips[k] == ip) |
| break; |
| |
| if (k < peer->ksnp_n_passive_ips) /* using it already */ |
| continue; |
| |
| k = ksocknal_match_peerip(iface, peerips, n_peerips); |
| xor = (ip ^ peerips[k]); |
| this_netmatch = ((xor & iface->ksni_netmask) == 0) ? 1 : 0; |
| |
| if (!(best_iface == NULL || |
| best_netmatch < this_netmatch || |
| (best_netmatch == this_netmatch && |
| best_npeers > iface->ksni_npeers))) |
| continue; |
| |
| best_iface = iface; |
| best_netmatch = this_netmatch; |
| best_npeers = iface->ksni_npeers; |
| } |
| |
| best_iface->ksni_npeers++; |
| ip = best_iface->ksni_ipaddr; |
| peer->ksnp_passive_ips[i] = ip; |
| peer->ksnp_n_passive_ips = i+1; |
| } |
| |
| /* mark the best matching peer IP used */ |
| j = ksocknal_match_peerip(best_iface, peerips, n_peerips); |
| peerips[j] = 0; |
| } |
| |
| /* Overwrite input peer IP addresses */ |
| memcpy(peerips, peer->ksnp_passive_ips, n_ips * sizeof(*peerips)); |
| |
| write_unlock_bh(global_lock); |
| |
| return n_ips; |
| } |
| |
| static void |
| ksocknal_create_routes(ksock_peer_t *peer, int port, |
| __u32 *peer_ipaddrs, int npeer_ipaddrs) |
| { |
| ksock_route_t *newroute = NULL; |
| rwlock_t *global_lock = &ksocknal_data.ksnd_global_lock; |
| lnet_ni_t *ni = peer->ksnp_ni; |
| ksock_net_t *net = ni->ni_data; |
| struct list_head *rtmp; |
| ksock_route_t *route; |
| ksock_interface_t *iface; |
| ksock_interface_t *best_iface; |
| int best_netmatch; |
| int this_netmatch; |
| int best_nroutes; |
| int i; |
| int j; |
| |
| /* CAVEAT EMPTOR: We do all our interface matching with an |
| * exclusive hold of global lock at IRQ priority. We're only |
| * expecting to be dealing with small numbers of interfaces, so the |
| * O(n**3)-ness here shouldn't matter */ |
| |
| write_lock_bh(global_lock); |
| |
| if (net->ksnn_ninterfaces < 2) { |
| /* Only create additional connections |
| * if I have > 1 interface */ |
| write_unlock_bh(global_lock); |
| return; |
| } |
| |
| LASSERT (npeer_ipaddrs <= LNET_MAX_INTERFACES); |
| |
| for (i = 0; i < npeer_ipaddrs; i++) { |
| if (newroute != NULL) { |
| newroute->ksnr_ipaddr = peer_ipaddrs[i]; |
| } else { |
| write_unlock_bh(global_lock); |
| |
| newroute = ksocknal_create_route(peer_ipaddrs[i], port); |
| if (newroute == NULL) |
| return; |
| |
| write_lock_bh(global_lock); |
| } |
| |
| if (peer->ksnp_closing) { |
| /* peer got closed under me */ |
| break; |
| } |
| |
| /* Already got a route? */ |
| route = NULL; |
| list_for_each(rtmp, &peer->ksnp_routes) { |
| route = list_entry(rtmp, ksock_route_t, ksnr_list); |
| |
| if (route->ksnr_ipaddr == newroute->ksnr_ipaddr) |
| break; |
| |
| route = NULL; |
| } |
| if (route != NULL) |
| continue; |
| |
| best_iface = NULL; |
| best_nroutes = 0; |
| best_netmatch = 0; |
| |
| LASSERT (net->ksnn_ninterfaces <= LNET_MAX_INTERFACES); |
| |
| /* Select interface to connect from */ |
| for (j = 0; j < net->ksnn_ninterfaces; j++) { |
| iface = &net->ksnn_interfaces[j]; |
| |
| /* Using this interface already? */ |
| list_for_each(rtmp, &peer->ksnp_routes) { |
| route = list_entry(rtmp, ksock_route_t, |
| ksnr_list); |
| |
| if (route->ksnr_myipaddr == iface->ksni_ipaddr) |
| break; |
| |
| route = NULL; |
| } |
| if (route != NULL) |
| continue; |
| |
| this_netmatch = (((iface->ksni_ipaddr ^ |
| newroute->ksnr_ipaddr) & |
| iface->ksni_netmask) == 0) ? 1 : 0; |
| |
| if (!(best_iface == NULL || |
| best_netmatch < this_netmatch || |
| (best_netmatch == this_netmatch && |
| best_nroutes > iface->ksni_nroutes))) |
| continue; |
| |
| best_iface = iface; |
| best_netmatch = this_netmatch; |
| best_nroutes = iface->ksni_nroutes; |
| } |
| |
| if (best_iface == NULL) |
| continue; |
| |
| newroute->ksnr_myipaddr = best_iface->ksni_ipaddr; |
| best_iface->ksni_nroutes++; |
| |
| ksocknal_add_route_locked(peer, newroute); |
| newroute = NULL; |
| } |
| |
| write_unlock_bh(global_lock); |
| if (newroute != NULL) |
| ksocknal_route_decref(newroute); |
| } |
| |
| int |
| ksocknal_accept (lnet_ni_t *ni, struct socket *sock) |
| { |
| ksock_connreq_t *cr; |
| int rc; |
| __u32 peer_ip; |
| int peer_port; |
| |
| rc = libcfs_sock_getaddr(sock, 1, &peer_ip, &peer_port); |
| LASSERT (rc == 0); /* we succeeded before */ |
| |
| LIBCFS_ALLOC(cr, sizeof(*cr)); |
| if (cr == NULL) { |
| LCONSOLE_ERROR_MSG(0x12f, "Dropping connection request from %pI4h: memory exhausted\n", |
| &peer_ip); |
| return -ENOMEM; |
| } |
| |
| lnet_ni_addref(ni); |
| cr->ksncr_ni = ni; |
| cr->ksncr_sock = sock; |
| |
| spin_lock_bh(&ksocknal_data.ksnd_connd_lock); |
| |
| list_add_tail(&cr->ksncr_list, &ksocknal_data.ksnd_connd_connreqs); |
| wake_up(&ksocknal_data.ksnd_connd_waitq); |
| |
| spin_unlock_bh(&ksocknal_data.ksnd_connd_lock); |
| return 0; |
| } |
| |
| static int |
| ksocknal_connecting (ksock_peer_t *peer, __u32 ipaddr) |
| { |
| ksock_route_t *route; |
| |
| list_for_each_entry (route, &peer->ksnp_routes, ksnr_list) { |
| |
| if (route->ksnr_ipaddr == ipaddr) |
| return route->ksnr_connecting; |
| } |
| return 0; |
| } |
| |
| int |
| ksocknal_create_conn (lnet_ni_t *ni, ksock_route_t *route, |
| struct socket *sock, int type) |
| { |
| rwlock_t *global_lock = &ksocknal_data.ksnd_global_lock; |
| LIST_HEAD (zombies); |
| lnet_process_id_t peerid; |
| struct list_head *tmp; |
| __u64 incarnation; |
| ksock_conn_t *conn; |
| ksock_conn_t *conn2; |
| ksock_peer_t *peer = NULL; |
| ksock_peer_t *peer2; |
| ksock_sched_t *sched; |
| ksock_hello_msg_t *hello; |
| int cpt; |
| ksock_tx_t *tx; |
| ksock_tx_t *txtmp; |
| int rc; |
| int active; |
| char *warn = NULL; |
| |
| active = (route != NULL); |
| |
| LASSERT (active == (type != SOCKLND_CONN_NONE)); |
| |
| LIBCFS_ALLOC(conn, sizeof(*conn)); |
| if (conn == NULL) { |
| rc = -ENOMEM; |
| goto failed_0; |
| } |
| |
| conn->ksnc_peer = NULL; |
| conn->ksnc_route = NULL; |
| conn->ksnc_sock = sock; |
| /* 2 ref, 1 for conn, another extra ref prevents socket |
| * being closed before establishment of connection */ |
| atomic_set (&conn->ksnc_sock_refcount, 2); |
| conn->ksnc_type = type; |
| ksocknal_lib_save_callback(sock, conn); |
| atomic_set (&conn->ksnc_conn_refcount, 1); /* 1 ref for me */ |
| |
| conn->ksnc_rx_ready = 0; |
| conn->ksnc_rx_scheduled = 0; |
| |
| INIT_LIST_HEAD (&conn->ksnc_tx_queue); |
| conn->ksnc_tx_ready = 0; |
| conn->ksnc_tx_scheduled = 0; |
| conn->ksnc_tx_carrier = NULL; |
| atomic_set (&conn->ksnc_tx_nob, 0); |
| |
| LIBCFS_ALLOC(hello, offsetof(ksock_hello_msg_t, |
| kshm_ips[LNET_MAX_INTERFACES])); |
| if (hello == NULL) { |
| rc = -ENOMEM; |
| goto failed_1; |
| } |
| |
| /* stash conn's local and remote addrs */ |
| rc = ksocknal_lib_get_conn_addrs (conn); |
| if (rc != 0) |
| goto failed_1; |
| |
| /* Find out/confirm peer's NID and connection type and get the |
| * vector of interfaces she's willing to let me connect to. |
| * Passive connections use the listener timeout since the peer sends |
| * eagerly */ |
| |
| if (active) { |
| peer = route->ksnr_peer; |
| LASSERT(ni == peer->ksnp_ni); |
| |
| /* Active connection sends HELLO eagerly */ |
| hello->kshm_nips = ksocknal_local_ipvec(ni, hello->kshm_ips); |
| peerid = peer->ksnp_id; |
| |
| write_lock_bh(global_lock); |
| conn->ksnc_proto = peer->ksnp_proto; |
| write_unlock_bh(global_lock); |
| |
| if (conn->ksnc_proto == NULL) { |
| conn->ksnc_proto = &ksocknal_protocol_v3x; |
| #if SOCKNAL_VERSION_DEBUG |
| if (*ksocknal_tunables.ksnd_protocol == 2) |
| conn->ksnc_proto = &ksocknal_protocol_v2x; |
| else if (*ksocknal_tunables.ksnd_protocol == 1) |
| conn->ksnc_proto = &ksocknal_protocol_v1x; |
| #endif |
| } |
| |
| rc = ksocknal_send_hello (ni, conn, peerid.nid, hello); |
| if (rc != 0) |
| goto failed_1; |
| } else { |
| peerid.nid = LNET_NID_ANY; |
| peerid.pid = LNET_PID_ANY; |
| |
| /* Passive, get protocol from peer */ |
| conn->ksnc_proto = NULL; |
| } |
| |
| rc = ksocknal_recv_hello (ni, conn, hello, &peerid, &incarnation); |
| if (rc < 0) |
| goto failed_1; |
| |
| LASSERT (rc == 0 || active); |
| LASSERT (conn->ksnc_proto != NULL); |
| LASSERT (peerid.nid != LNET_NID_ANY); |
| |
| cpt = lnet_cpt_of_nid(peerid.nid); |
| |
| if (active) { |
| ksocknal_peer_addref(peer); |
| write_lock_bh(global_lock); |
| } else { |
| rc = ksocknal_create_peer(&peer, ni, peerid); |
| if (rc != 0) |
| goto failed_1; |
| |
| write_lock_bh(global_lock); |
| |
| /* called with a ref on ni, so shutdown can't have started */ |
| LASSERT (((ksock_net_t *) ni->ni_data)->ksnn_shutdown == 0); |
| |
| peer2 = ksocknal_find_peer_locked(ni, peerid); |
| if (peer2 == NULL) { |
| /* NB this puts an "empty" peer in the peer |
| * table (which takes my ref) */ |
| list_add_tail(&peer->ksnp_list, |
| ksocknal_nid2peerlist(peerid.nid)); |
| } else { |
| ksocknal_peer_decref(peer); |
| peer = peer2; |
| } |
| |
| /* +1 ref for me */ |
| ksocknal_peer_addref(peer); |
| peer->ksnp_accepting++; |
| |
| /* Am I already connecting to this guy? Resolve in |
| * favour of higher NID... */ |
| if (peerid.nid < ni->ni_nid && |
| ksocknal_connecting(peer, conn->ksnc_ipaddr)) { |
| rc = EALREADY; |
| warn = "connection race resolution"; |
| goto failed_2; |
| } |
| } |
| |
| if (peer->ksnp_closing || |
| (active && route->ksnr_deleted)) { |
| /* peer/route got closed under me */ |
| rc = -ESTALE; |
| warn = "peer/route removed"; |
| goto failed_2; |
| } |
| |
| if (peer->ksnp_proto == NULL) { |
| /* Never connected before. |
| * NB recv_hello may have returned EPROTO to signal my peer |
| * wants a different protocol than the one I asked for. |
| */ |
| LASSERT (list_empty(&peer->ksnp_conns)); |
| |
| peer->ksnp_proto = conn->ksnc_proto; |
| peer->ksnp_incarnation = incarnation; |
| } |
| |
| if (peer->ksnp_proto != conn->ksnc_proto || |
| peer->ksnp_incarnation != incarnation) { |
| /* Peer rebooted or I've got the wrong protocol version */ |
| ksocknal_close_peer_conns_locked(peer, 0, 0); |
| |
| peer->ksnp_proto = NULL; |
| rc = ESTALE; |
| warn = peer->ksnp_incarnation != incarnation ? |
| "peer rebooted" : |
| "wrong proto version"; |
| goto failed_2; |
| } |
| |
| switch (rc) { |
| default: |
| LBUG(); |
| case 0: |
| break; |
| case EALREADY: |
| warn = "lost conn race"; |
| goto failed_2; |
| case EPROTO: |
| warn = "retry with different protocol version"; |
| goto failed_2; |
| } |
| |
| /* Refuse to duplicate an existing connection, unless this is a |
| * loopback connection */ |
| if (conn->ksnc_ipaddr != conn->ksnc_myipaddr) { |
| list_for_each(tmp, &peer->ksnp_conns) { |
| conn2 = list_entry(tmp, ksock_conn_t, ksnc_list); |
| |
| if (conn2->ksnc_ipaddr != conn->ksnc_ipaddr || |
| conn2->ksnc_myipaddr != conn->ksnc_myipaddr || |
| conn2->ksnc_type != conn->ksnc_type) |
| continue; |
| |
| /* Reply on a passive connection attempt so the peer |
| * realises we're connected. */ |
| LASSERT (rc == 0); |
| if (!active) |
| rc = EALREADY; |
| |
| warn = "duplicate"; |
| goto failed_2; |
| } |
| } |
| |
| /* If the connection created by this route didn't bind to the IP |
| * address the route connected to, the connection/route matching |
| * code below probably isn't going to work. */ |
| if (active && |
| route->ksnr_ipaddr != conn->ksnc_ipaddr) { |
| CERROR("Route %s %pI4h connected to %pI4h\n", |
| libcfs_id2str(peer->ksnp_id), |
| &route->ksnr_ipaddr, |
| &conn->ksnc_ipaddr); |
| } |
| |
| /* Search for a route corresponding to the new connection and |
| * create an association. This allows incoming connections created |
| * by routes in my peer to match my own route entries so I don't |
| * continually create duplicate routes. */ |
| list_for_each (tmp, &peer->ksnp_routes) { |
| route = list_entry(tmp, ksock_route_t, ksnr_list); |
| |
| if (route->ksnr_ipaddr != conn->ksnc_ipaddr) |
| continue; |
| |
| ksocknal_associate_route_conn_locked(route, conn); |
| break; |
| } |
| |
| conn->ksnc_peer = peer; /* conn takes my ref on peer */ |
| peer->ksnp_last_alive = cfs_time_current(); |
| peer->ksnp_send_keepalive = 0; |
| peer->ksnp_error = 0; |
| |
| sched = ksocknal_choose_scheduler_locked(cpt); |
| sched->kss_nconns++; |
| conn->ksnc_scheduler = sched; |
| |
| conn->ksnc_tx_last_post = cfs_time_current(); |
| /* Set the deadline for the outgoing HELLO to drain */ |
| conn->ksnc_tx_bufnob = sock->sk->sk_wmem_queued; |
| conn->ksnc_tx_deadline = cfs_time_shift(*ksocknal_tunables.ksnd_timeout); |
| mb(); /* order with adding to peer's conn list */ |
| |
| list_add (&conn->ksnc_list, &peer->ksnp_conns); |
| ksocknal_conn_addref(conn); |
| |
| ksocknal_new_packet(conn, 0); |
| |
| conn->ksnc_zc_capable = ksocknal_lib_zc_capable(conn); |
| |
| /* Take packets blocking for this connection. */ |
| list_for_each_entry_safe(tx, txtmp, &peer->ksnp_tx_queue, tx_list) { |
| if (conn->ksnc_proto->pro_match_tx(conn, tx, tx->tx_nonblk) == SOCKNAL_MATCH_NO) |
| continue; |
| |
| list_del (&tx->tx_list); |
| ksocknal_queue_tx_locked (tx, conn); |
| } |
| |
| write_unlock_bh(global_lock); |
| |
| /* We've now got a new connection. Any errors from here on are just |
| * like "normal" comms errors and we close the connection normally. |
| * NB (a) we still have to send the reply HELLO for passive |
| * connections, |
| * (b) normal I/O on the conn is blocked until I setup and call the |
| * socket callbacks. |
| */ |
| |
| CDEBUG(D_NET, "New conn %s p %d.x %pI4h -> %pI4h/%d incarnation:%lld sched[%d:%d]\n", |
| libcfs_id2str(peerid), conn->ksnc_proto->pro_version, |
| &conn->ksnc_myipaddr, &conn->ksnc_ipaddr, |
| conn->ksnc_port, incarnation, cpt, |
| (int)(sched - &sched->kss_info->ksi_scheds[0])); |
| |
| if (active) { |
| /* additional routes after interface exchange? */ |
| ksocknal_create_routes(peer, conn->ksnc_port, |
| hello->kshm_ips, hello->kshm_nips); |
| } else { |
| hello->kshm_nips = ksocknal_select_ips(peer, hello->kshm_ips, |
| hello->kshm_nips); |
| rc = ksocknal_send_hello(ni, conn, peerid.nid, hello); |
| } |
| |
| LIBCFS_FREE(hello, offsetof(ksock_hello_msg_t, |
| kshm_ips[LNET_MAX_INTERFACES])); |
| |
| /* setup the socket AFTER I've received hello (it disables |
| * SO_LINGER). I might call back to the acceptor who may want |
| * to send a protocol version response and then close the |
| * socket; this ensures the socket only tears down after the |
| * response has been sent. */ |
| if (rc == 0) |
| rc = ksocknal_lib_setup_sock(sock); |
| |
| write_lock_bh(global_lock); |
| |
| /* NB my callbacks block while I hold ksnd_global_lock */ |
| ksocknal_lib_set_callback(sock, conn); |
| |
| if (!active) |
| peer->ksnp_accepting--; |
| |
| write_unlock_bh(global_lock); |
| |
| if (rc != 0) { |
| write_lock_bh(global_lock); |
| if (!conn->ksnc_closing) { |
| /* could be closed by another thread */ |
| ksocknal_close_conn_locked(conn, rc); |
| } |
| write_unlock_bh(global_lock); |
| } else if (ksocknal_connsock_addref(conn) == 0) { |
| /* Allow I/O to proceed. */ |
| ksocknal_read_callback(conn); |
| ksocknal_write_callback(conn); |
| ksocknal_connsock_decref(conn); |
| } |
| |
| ksocknal_connsock_decref(conn); |
| ksocknal_conn_decref(conn); |
| return rc; |
| |
| failed_2: |
| if (!peer->ksnp_closing && |
| list_empty (&peer->ksnp_conns) && |
| list_empty (&peer->ksnp_routes)) { |
| list_add(&zombies, &peer->ksnp_tx_queue); |
| list_del_init(&peer->ksnp_tx_queue); |
| ksocknal_unlink_peer_locked(peer); |
| } |
| |
| write_unlock_bh(global_lock); |
| |
| if (warn != NULL) { |
| if (rc < 0) |
| CERROR("Not creating conn %s type %d: %s\n", |
| libcfs_id2str(peerid), conn->ksnc_type, warn); |
| else |
| CDEBUG(D_NET, "Not creating conn %s type %d: %s\n", |
| libcfs_id2str(peerid), conn->ksnc_type, warn); |
| } |
| |
| if (!active) { |
| if (rc > 0) { |
| /* Request retry by replying with CONN_NONE |
| * ksnc_proto has been set already */ |
| conn->ksnc_type = SOCKLND_CONN_NONE; |
| hello->kshm_nips = 0; |
| ksocknal_send_hello(ni, conn, peerid.nid, hello); |
| } |
| |
| write_lock_bh(global_lock); |
| peer->ksnp_accepting--; |
| write_unlock_bh(global_lock); |
| } |
| |
| ksocknal_txlist_done(ni, &zombies, 1); |
| ksocknal_peer_decref(peer); |
| |
| failed_1: |
| if (hello != NULL) |
| LIBCFS_FREE(hello, offsetof(ksock_hello_msg_t, |
| kshm_ips[LNET_MAX_INTERFACES])); |
| |
| LIBCFS_FREE (conn, sizeof(*conn)); |
| |
| failed_0: |
| libcfs_sock_release(sock); |
| return rc; |
| } |
| |
| void |
| ksocknal_close_conn_locked (ksock_conn_t *conn, int error) |
| { |
| /* This just does the immmediate housekeeping, and queues the |
| * connection for the reaper to terminate. |
| * Caller holds ksnd_global_lock exclusively in irq context */ |
| ksock_peer_t *peer = conn->ksnc_peer; |
| ksock_route_t *route; |
| ksock_conn_t *conn2; |
| struct list_head *tmp; |
| |
| LASSERT (peer->ksnp_error == 0); |
| LASSERT (!conn->ksnc_closing); |
| conn->ksnc_closing = 1; |
| |
| /* ksnd_deathrow_conns takes over peer's ref */ |
| list_del (&conn->ksnc_list); |
| |
| route = conn->ksnc_route; |
| if (route != NULL) { |
| /* dissociate conn from route... */ |
| LASSERT (!route->ksnr_deleted); |
| LASSERT ((route->ksnr_connected & (1 << conn->ksnc_type)) != 0); |
| |
| conn2 = NULL; |
| list_for_each(tmp, &peer->ksnp_conns) { |
| conn2 = list_entry(tmp, ksock_conn_t, ksnc_list); |
| |
| if (conn2->ksnc_route == route && |
| conn2->ksnc_type == conn->ksnc_type) |
| break; |
| |
| conn2 = NULL; |
| } |
| if (conn2 == NULL) |
| route->ksnr_connected &= ~(1 << conn->ksnc_type); |
| |
| conn->ksnc_route = NULL; |
| |
| #if 0 /* irrelevant with only eager routes */ |
| /* make route least favourite */ |
| list_del (&route->ksnr_list); |
| list_add_tail (&route->ksnr_list, &peer->ksnp_routes); |
| #endif |
| ksocknal_route_decref(route); /* drop conn's ref on route */ |
| } |
| |
| if (list_empty (&peer->ksnp_conns)) { |
| /* No more connections to this peer */ |
| |
| if (!list_empty(&peer->ksnp_tx_queue)) { |
| ksock_tx_t *tx; |
| |
| LASSERT (conn->ksnc_proto == &ksocknal_protocol_v3x); |
| |
| /* throw them to the last connection..., |
| * these TXs will be send to /dev/null by scheduler */ |
| list_for_each_entry(tx, &peer->ksnp_tx_queue, |
| tx_list) |
| ksocknal_tx_prep(conn, tx); |
| |
| spin_lock_bh(&conn->ksnc_scheduler->kss_lock); |
| list_splice_init(&peer->ksnp_tx_queue, |
| &conn->ksnc_tx_queue); |
| spin_unlock_bh(&conn->ksnc_scheduler->kss_lock); |
| } |
| |
| peer->ksnp_proto = NULL; /* renegotiate protocol version */ |
| peer->ksnp_error = error; /* stash last conn close reason */ |
| |
| if (list_empty (&peer->ksnp_routes)) { |
| /* I've just closed last conn belonging to a |
| * peer with no routes to it */ |
| ksocknal_unlink_peer_locked (peer); |
| } |
| } |
| |
| spin_lock_bh(&ksocknal_data.ksnd_reaper_lock); |
| |
| list_add_tail(&conn->ksnc_list, |
| &ksocknal_data.ksnd_deathrow_conns); |
| wake_up(&ksocknal_data.ksnd_reaper_waitq); |
| |
| spin_unlock_bh(&ksocknal_data.ksnd_reaper_lock); |
| } |
| |
| void |
| ksocknal_peer_failed (ksock_peer_t *peer) |
| { |
| int notify = 0; |
| unsigned long last_alive = 0; |
| |
| /* There has been a connection failure or comms error; but I'll only |
| * tell LNET I think the peer is dead if it's to another kernel and |
| * there are no connections or connection attempts in existence. */ |
| |
| read_lock(&ksocknal_data.ksnd_global_lock); |
| |
| if ((peer->ksnp_id.pid & LNET_PID_USERFLAG) == 0 && |
| list_empty(&peer->ksnp_conns) && |
| peer->ksnp_accepting == 0 && |
| ksocknal_find_connecting_route_locked(peer) == NULL) { |
| notify = 1; |
| last_alive = peer->ksnp_last_alive; |
| } |
| |
| read_unlock(&ksocknal_data.ksnd_global_lock); |
| |
| if (notify) |
| lnet_notify (peer->ksnp_ni, peer->ksnp_id.nid, 0, |
| last_alive); |
| } |
| |
| void |
| ksocknal_finalize_zcreq(ksock_conn_t *conn) |
| { |
| ksock_peer_t *peer = conn->ksnc_peer; |
| ksock_tx_t *tx; |
| ksock_tx_t *tmp; |
| LIST_HEAD (zlist); |
| |
| /* NB safe to finalize TXs because closing of socket will |
| * abort all buffered data */ |
| LASSERT (conn->ksnc_sock == NULL); |
| |
| spin_lock(&peer->ksnp_lock); |
| |
| list_for_each_entry_safe(tx, tmp, &peer->ksnp_zc_req_list, tx_zc_list) { |
| if (tx->tx_conn != conn) |
| continue; |
| |
| LASSERT (tx->tx_msg.ksm_zc_cookies[0] != 0); |
| |
| tx->tx_msg.ksm_zc_cookies[0] = 0; |
| tx->tx_zc_aborted = 1; /* mark it as not-acked */ |
| list_del(&tx->tx_zc_list); |
| list_add(&tx->tx_zc_list, &zlist); |
| } |
| |
| spin_unlock(&peer->ksnp_lock); |
| |
| while (!list_empty(&zlist)) { |
| tx = list_entry(zlist.next, ksock_tx_t, tx_zc_list); |
| |
| list_del(&tx->tx_zc_list); |
| ksocknal_tx_decref(tx); |
| } |
| } |
| |
| void |
| ksocknal_terminate_conn (ksock_conn_t *conn) |
| { |
| /* This gets called by the reaper (guaranteed thread context) to |
| * disengage the socket from its callbacks and close it. |
| * ksnc_refcount will eventually hit zero, and then the reaper will |
| * destroy it. */ |
| ksock_peer_t *peer = conn->ksnc_peer; |
| ksock_sched_t *sched = conn->ksnc_scheduler; |
| int failed = 0; |
| |
| LASSERT(conn->ksnc_closing); |
| |
| /* wake up the scheduler to "send" all remaining packets to /dev/null */ |
| spin_lock_bh(&sched->kss_lock); |
| |
| /* a closing conn is always ready to tx */ |
| conn->ksnc_tx_ready = 1; |
| |
| if (!conn->ksnc_tx_scheduled && |
| !list_empty(&conn->ksnc_tx_queue)) { |
| list_add_tail (&conn->ksnc_tx_list, |
| &sched->kss_tx_conns); |
| conn->ksnc_tx_scheduled = 1; |
| /* extra ref for scheduler */ |
| ksocknal_conn_addref(conn); |
| |
| wake_up (&sched->kss_waitq); |
| } |
| |
| spin_unlock_bh(&sched->kss_lock); |
| |
| /* serialise with callbacks */ |
| write_lock_bh(&ksocknal_data.ksnd_global_lock); |
| |
| ksocknal_lib_reset_callback(conn->ksnc_sock, conn); |
| |
| /* OK, so this conn may not be completely disengaged from its |
| * scheduler yet, but it _has_ committed to terminate... */ |
| conn->ksnc_scheduler->kss_nconns--; |
| |
| if (peer->ksnp_error != 0) { |
| /* peer's last conn closed in error */ |
| LASSERT (list_empty (&peer->ksnp_conns)); |
| failed = 1; |
| peer->ksnp_error = 0; /* avoid multiple notifications */ |
| } |
| |
| write_unlock_bh(&ksocknal_data.ksnd_global_lock); |
| |
| if (failed) |
| ksocknal_peer_failed(peer); |
| |
| /* The socket is closed on the final put; either here, or in |
| * ksocknal_{send,recv}msg(). Since we set up the linger2 option |
| * when the connection was established, this will close the socket |
| * immediately, aborting anything buffered in it. Any hung |
| * zero-copy transmits will therefore complete in finite time. */ |
| ksocknal_connsock_decref(conn); |
| } |
| |
| void |
| ksocknal_queue_zombie_conn (ksock_conn_t *conn) |
| { |
| /* Queue the conn for the reaper to destroy */ |
| |
| LASSERT(atomic_read(&conn->ksnc_conn_refcount) == 0); |
| spin_lock_bh(&ksocknal_data.ksnd_reaper_lock); |
| |
| list_add_tail(&conn->ksnc_list, &ksocknal_data.ksnd_zombie_conns); |
| wake_up(&ksocknal_data.ksnd_reaper_waitq); |
| |
| spin_unlock_bh(&ksocknal_data.ksnd_reaper_lock); |
| } |
| |
| void |
| ksocknal_destroy_conn (ksock_conn_t *conn) |
| { |
| unsigned long last_rcv; |
| |
| /* Final coup-de-grace of the reaper */ |
| CDEBUG (D_NET, "connection %p\n", conn); |
| |
| LASSERT (atomic_read (&conn->ksnc_conn_refcount) == 0); |
| LASSERT (atomic_read (&conn->ksnc_sock_refcount) == 0); |
| LASSERT (conn->ksnc_sock == NULL); |
| LASSERT (conn->ksnc_route == NULL); |
| LASSERT (!conn->ksnc_tx_scheduled); |
| LASSERT (!conn->ksnc_rx_scheduled); |
| LASSERT (list_empty(&conn->ksnc_tx_queue)); |
| |
| /* complete current receive if any */ |
| switch (conn->ksnc_rx_state) { |
| case SOCKNAL_RX_LNET_PAYLOAD: |
| last_rcv = conn->ksnc_rx_deadline - |
| cfs_time_seconds(*ksocknal_tunables.ksnd_timeout); |
| CERROR("Completing partial receive from %s[%d], ip %pI4h:%d, with error, wanted: %d, left: %d, last alive is %ld secs ago\n", |
| libcfs_id2str(conn->ksnc_peer->ksnp_id), conn->ksnc_type, |
| &conn->ksnc_ipaddr, conn->ksnc_port, |
| conn->ksnc_rx_nob_wanted, conn->ksnc_rx_nob_left, |
| cfs_duration_sec(cfs_time_sub(cfs_time_current(), |
| last_rcv))); |
| lnet_finalize (conn->ksnc_peer->ksnp_ni, |
| conn->ksnc_cookie, -EIO); |
| break; |
| case SOCKNAL_RX_LNET_HEADER: |
| if (conn->ksnc_rx_started) |
| CERROR("Incomplete receive of lnet header from %s, ip %pI4h:%d, with error, protocol: %d.x.\n", |
| libcfs_id2str(conn->ksnc_peer->ksnp_id), |
| &conn->ksnc_ipaddr, conn->ksnc_port, |
| conn->ksnc_proto->pro_version); |
| break; |
| case SOCKNAL_RX_KSM_HEADER: |
| if (conn->ksnc_rx_started) |
| CERROR("Incomplete receive of ksock message from %s, ip %pI4h:%d, with error, protocol: %d.x.\n", |
| libcfs_id2str(conn->ksnc_peer->ksnp_id), |
| &conn->ksnc_ipaddr, conn->ksnc_port, |
| conn->ksnc_proto->pro_version); |
| break; |
| case SOCKNAL_RX_SLOP: |
| if (conn->ksnc_rx_started) |
| CERROR("Incomplete receive of slops from %s, ip %pI4h:%d, with error\n", |
| libcfs_id2str(conn->ksnc_peer->ksnp_id), |
| &conn->ksnc_ipaddr, conn->ksnc_port); |
| break; |
| default: |
| LBUG (); |
| break; |
| } |
| |
| ksocknal_peer_decref(conn->ksnc_peer); |
| |
| LIBCFS_FREE (conn, sizeof (*conn)); |
| } |
| |
| int |
| ksocknal_close_peer_conns_locked (ksock_peer_t *peer, __u32 ipaddr, int why) |
| { |
| ksock_conn_t *conn; |
| struct list_head *ctmp; |
| struct list_head *cnxt; |
| int count = 0; |
| |
| list_for_each_safe (ctmp, cnxt, &peer->ksnp_conns) { |
| conn = list_entry (ctmp, ksock_conn_t, ksnc_list); |
| |
| if (ipaddr == 0 || |
| conn->ksnc_ipaddr == ipaddr) { |
| count++; |
| ksocknal_close_conn_locked (conn, why); |
| } |
| } |
| |
| return count; |
| } |
| |
| int |
| ksocknal_close_conn_and_siblings (ksock_conn_t *conn, int why) |
| { |
| ksock_peer_t *peer = conn->ksnc_peer; |
| __u32 ipaddr = conn->ksnc_ipaddr; |
| int count; |
| |
| write_lock_bh(&ksocknal_data.ksnd_global_lock); |
| |
| count = ksocknal_close_peer_conns_locked (peer, ipaddr, why); |
| |
| write_unlock_bh(&ksocknal_data.ksnd_global_lock); |
| |
| return count; |
| } |
| |
| int |
| ksocknal_close_matching_conns (lnet_process_id_t id, __u32 ipaddr) |
| { |
| ksock_peer_t *peer; |
| struct list_head *ptmp; |
| struct list_head *pnxt; |
| int lo; |
| int hi; |
| int i; |
| int count = 0; |
| |
| write_lock_bh(&ksocknal_data.ksnd_global_lock); |
| |
| if (id.nid != LNET_NID_ANY) |
| lo = hi = (int)(ksocknal_nid2peerlist(id.nid) - ksocknal_data.ksnd_peers); |
| else { |
| lo = 0; |
| hi = ksocknal_data.ksnd_peer_hash_size - 1; |
| } |
| |
| for (i = lo; i <= hi; i++) { |
| list_for_each_safe (ptmp, pnxt, |
| &ksocknal_data.ksnd_peers[i]) { |
| |
| peer = list_entry (ptmp, ksock_peer_t, ksnp_list); |
| |
| if (!((id.nid == LNET_NID_ANY || id.nid == peer->ksnp_id.nid) && |
| (id.pid == LNET_PID_ANY || id.pid == peer->ksnp_id.pid))) |
| continue; |
| |
| count += ksocknal_close_peer_conns_locked (peer, ipaddr, 0); |
| } |
| } |
| |
| write_unlock_bh(&ksocknal_data.ksnd_global_lock); |
| |
| /* wildcards always succeed */ |
| if (id.nid == LNET_NID_ANY || id.pid == LNET_PID_ANY || ipaddr == 0) |
| return 0; |
| |
| if (count == 0) |
| return -ENOENT; |
| else |
| return 0; |
| } |
| |
| void |
| ksocknal_notify (lnet_ni_t *ni, lnet_nid_t gw_nid, int alive) |
| { |
| /* The router is telling me she's been notified of a change in |
| * gateway state.... */ |
| lnet_process_id_t id = {0}; |
| |
| id.nid = gw_nid; |
| id.pid = LNET_PID_ANY; |
| |
| CDEBUG (D_NET, "gw %s %s\n", libcfs_nid2str(gw_nid), |
| alive ? "up" : "down"); |
| |
| if (!alive) { |
| /* If the gateway crashed, close all open connections... */ |
| ksocknal_close_matching_conns (id, 0); |
| return; |
| } |
| |
| /* ...otherwise do nothing. We can only establish new connections |
| * if we have autroutes, and these connect on demand. */ |
| } |
| |
| void |
| ksocknal_query (lnet_ni_t *ni, lnet_nid_t nid, unsigned long *when) |
| { |
| int connect = 1; |
| unsigned long last_alive = 0; |
| unsigned long now = cfs_time_current(); |
| ksock_peer_t *peer = NULL; |
| rwlock_t *glock = &ksocknal_data.ksnd_global_lock; |
| lnet_process_id_t id = {.nid = nid, .pid = LUSTRE_SRV_LNET_PID}; |
| |
| read_lock(glock); |
| |
| peer = ksocknal_find_peer_locked(ni, id); |
| if (peer != NULL) { |
| struct list_head *tmp; |
| ksock_conn_t *conn; |
| int bufnob; |
| |
| list_for_each (tmp, &peer->ksnp_conns) { |
| conn = list_entry(tmp, ksock_conn_t, ksnc_list); |
| bufnob = conn->ksnc_sock->sk->sk_wmem_queued; |
| |
| if (bufnob < conn->ksnc_tx_bufnob) { |
| /* something got ACKed */ |
| conn->ksnc_tx_deadline = |
| cfs_time_shift(*ksocknal_tunables.ksnd_timeout); |
| peer->ksnp_last_alive = now; |
| conn->ksnc_tx_bufnob = bufnob; |
| } |
| } |
| |
| last_alive = peer->ksnp_last_alive; |
| if (ksocknal_find_connectable_route_locked(peer) == NULL) |
| connect = 0; |
| } |
| |
| read_unlock(glock); |
| |
| if (last_alive != 0) |
| *when = last_alive; |
| |
| CDEBUG(D_NET, "Peer %s %p, alive %ld secs ago, connect %d\n", |
| libcfs_nid2str(nid), peer, |
| last_alive ? cfs_duration_sec(now - last_alive) : -1, |
| connect); |
| |
| if (!connect) |
| return; |
| |
| ksocknal_add_peer(ni, id, LNET_NIDADDR(nid), lnet_acceptor_port()); |
| |
| write_lock_bh(glock); |
| |
| peer = ksocknal_find_peer_locked(ni, id); |
| if (peer != NULL) |
| ksocknal_launch_all_connections_locked(peer); |
| |
| write_unlock_bh(glock); |
| return; |
| } |
| |
| static void |
| ksocknal_push_peer (ksock_peer_t *peer) |
| { |
| int index; |
| int i; |
| struct list_head *tmp; |
| ksock_conn_t *conn; |
| |
| for (index = 0; ; index++) { |
| read_lock(&ksocknal_data.ksnd_global_lock); |
| |
| i = 0; |
| conn = NULL; |
| |
| list_for_each (tmp, &peer->ksnp_conns) { |
| if (i++ == index) { |
| conn = list_entry (tmp, ksock_conn_t, |
| ksnc_list); |
| ksocknal_conn_addref(conn); |
| break; |
| } |
| } |
| |
| read_unlock(&ksocknal_data.ksnd_global_lock); |
| |
| if (conn == NULL) |
| break; |
| |
| ksocknal_lib_push_conn (conn); |
| ksocknal_conn_decref(conn); |
| } |
| } |
| |
| static int |
| ksocknal_push (lnet_ni_t *ni, lnet_process_id_t id) |
| { |
| ksock_peer_t *peer; |
| struct list_head *tmp; |
| int index; |
| int i; |
| int j; |
| int rc = -ENOENT; |
| |
| for (i = 0; i < ksocknal_data.ksnd_peer_hash_size; i++) { |
| for (j = 0; ; j++) { |
| read_lock(&ksocknal_data.ksnd_global_lock); |
| |
| index = 0; |
| peer = NULL; |
| |
| list_for_each (tmp, &ksocknal_data.ksnd_peers[i]) { |
| peer = list_entry(tmp, ksock_peer_t, |
| ksnp_list); |
| |
| if (!((id.nid == LNET_NID_ANY || |
| id.nid == peer->ksnp_id.nid) && |
| (id.pid == LNET_PID_ANY || |
| id.pid == peer->ksnp_id.pid))) { |
| peer = NULL; |
| continue; |
| } |
| |
| if (index++ == j) { |
| ksocknal_peer_addref(peer); |
| break; |
| } |
| } |
| |
| read_unlock(&ksocknal_data.ksnd_global_lock); |
| |
| if (peer != NULL) { |
| rc = 0; |
| ksocknal_push_peer (peer); |
| ksocknal_peer_decref(peer); |
| } |
| } |
| |
| } |
| |
| return rc; |
| } |
| |
| static int |
| ksocknal_add_interface(lnet_ni_t *ni, __u32 ipaddress, __u32 netmask) |
| { |
| ksock_net_t *net = ni->ni_data; |
| ksock_interface_t *iface; |
| int rc; |
| int i; |
| int j; |
| struct list_head *ptmp; |
| ksock_peer_t *peer; |
| struct list_head *rtmp; |
| ksock_route_t *route; |
| |
| if (ipaddress == 0 || |
| netmask == 0) |
| return -EINVAL; |
| |
| write_lock_bh(&ksocknal_data.ksnd_global_lock); |
| |
| iface = ksocknal_ip2iface(ni, ipaddress); |
| if (iface != NULL) { |
| /* silently ignore dups */ |
| rc = 0; |
| } else if (net->ksnn_ninterfaces == LNET_MAX_INTERFACES) { |
| rc = -ENOSPC; |
| } else { |
| iface = &net->ksnn_interfaces[net->ksnn_ninterfaces++]; |
| |
| iface->ksni_ipaddr = ipaddress; |
| iface->ksni_netmask = netmask; |
| iface->ksni_nroutes = 0; |
| iface->ksni_npeers = 0; |
| |
| for (i = 0; i < ksocknal_data.ksnd_peer_hash_size; i++) { |
| list_for_each(ptmp, &ksocknal_data.ksnd_peers[i]) { |
| peer = list_entry(ptmp, ksock_peer_t, |
| ksnp_list); |
| |
| for (j = 0; j < peer->ksnp_n_passive_ips; j++) |
| if (peer->ksnp_passive_ips[j] == ipaddress) |
| iface->ksni_npeers++; |
| |
| list_for_each(rtmp, &peer->ksnp_routes) { |
| route = list_entry(rtmp, |
| ksock_route_t, |
| ksnr_list); |
| |
| if (route->ksnr_myipaddr == ipaddress) |
| iface->ksni_nroutes++; |
| } |
| } |
| } |
| |
| rc = 0; |
| /* NB only new connections will pay attention to the new interface! */ |
| } |
| |
| write_unlock_bh(&ksocknal_data.ksnd_global_lock); |
| |
| return rc; |
| } |
| |
| static void |
| ksocknal_peer_del_interface_locked(ksock_peer_t *peer, __u32 ipaddr) |
| { |
| struct list_head *tmp; |
| struct list_head *nxt; |
| ksock_route_t *route; |
| ksock_conn_t *conn; |
| int i; |
| int j; |
| |
| for (i = 0; i < peer->ksnp_n_passive_ips; i++) |
| if (peer->ksnp_passive_ips[i] == ipaddr) { |
| for (j = i+1; j < peer->ksnp_n_passive_ips; j++) |
| peer->ksnp_passive_ips[j-1] = |
| peer->ksnp_passive_ips[j]; |
| peer->ksnp_n_passive_ips--; |
| break; |
| } |
| |
| list_for_each_safe(tmp, nxt, &peer->ksnp_routes) { |
| route = list_entry (tmp, ksock_route_t, ksnr_list); |
| |
| if (route->ksnr_myipaddr != ipaddr) |
| continue; |
| |
| if (route->ksnr_share_count != 0) { |
| /* Manually created; keep, but unbind */ |
| route->ksnr_myipaddr = 0; |
| } else { |
| ksocknal_del_route_locked(route); |
| } |
| } |
| |
| list_for_each_safe(tmp, nxt, &peer->ksnp_conns) { |
| conn = list_entry(tmp, ksock_conn_t, ksnc_list); |
| |
| if (conn->ksnc_myipaddr == ipaddr) |
| ksocknal_close_conn_locked (conn, 0); |
| } |
| } |
| |
| static int |
| ksocknal_del_interface(lnet_ni_t *ni, __u32 ipaddress) |
| { |
| ksock_net_t *net = ni->ni_data; |
| int rc = -ENOENT; |
| struct list_head *tmp; |
| struct list_head *nxt; |
| ksock_peer_t *peer; |
| __u32 this_ip; |
| int i; |
| int j; |
| |
| write_lock_bh(&ksocknal_data.ksnd_global_lock); |
| |
| for (i = 0; i < net->ksnn_ninterfaces; i++) { |
| this_ip = net->ksnn_interfaces[i].ksni_ipaddr; |
| |
| if (!(ipaddress == 0 || |
| ipaddress == this_ip)) |
| continue; |
| |
| rc = 0; |
| |
| for (j = i+1; j < net->ksnn_ninterfaces; j++) |
| net->ksnn_interfaces[j-1] = |
| net->ksnn_interfaces[j]; |
| |
| net->ksnn_ninterfaces--; |
| |
| for (j = 0; j < ksocknal_data.ksnd_peer_hash_size; j++) { |
| list_for_each_safe(tmp, nxt, |
| &ksocknal_data.ksnd_peers[j]) { |
| peer = list_entry(tmp, ksock_peer_t, |
| ksnp_list); |
| |
| if (peer->ksnp_ni != ni) |
| continue; |
| |
| ksocknal_peer_del_interface_locked(peer, this_ip); |
| } |
| } |
| } |
| |
| write_unlock_bh(&ksocknal_data.ksnd_global_lock); |
| |
| return rc; |
| } |
| |
| int |
| ksocknal_ctl(lnet_ni_t *ni, unsigned int cmd, void *arg) |
| { |
| lnet_process_id_t id = {0}; |
| struct libcfs_ioctl_data *data = arg; |
| int rc; |
| |
| switch (cmd) { |
| case IOC_LIBCFS_GET_INTERFACE: { |
| ksock_net_t *net = ni->ni_data; |
| ksock_interface_t *iface; |
| |
| read_lock(&ksocknal_data.ksnd_global_lock); |
| |
| if (data->ioc_count >= (__u32)net->ksnn_ninterfaces) { |
| rc = -ENOENT; |
| } else { |
| rc = 0; |
| iface = &net->ksnn_interfaces[data->ioc_count]; |
| |
| data->ioc_u32[0] = iface->ksni_ipaddr; |
| data->ioc_u32[1] = iface->ksni_netmask; |
| data->ioc_u32[2] = iface->ksni_npeers; |
| data->ioc_u32[3] = iface->ksni_nroutes; |
| } |
| |
| read_unlock(&ksocknal_data.ksnd_global_lock); |
| return rc; |
| } |
| |
| case IOC_LIBCFS_ADD_INTERFACE: |
| return ksocknal_add_interface(ni, |
| data->ioc_u32[0], /* IP address */ |
| data->ioc_u32[1]); /* net mask */ |
| |
| case IOC_LIBCFS_DEL_INTERFACE: |
| return ksocknal_del_interface(ni, |
| data->ioc_u32[0]); /* IP address */ |
| |
| case IOC_LIBCFS_GET_PEER: { |
| __u32 myip = 0; |
| __u32 ip = 0; |
| int port = 0; |
| int conn_count = 0; |
| int share_count = 0; |
| |
| rc = ksocknal_get_peer_info(ni, data->ioc_count, |
| &id, &myip, &ip, &port, |
| &conn_count, &share_count); |
| if (rc != 0) |
| return rc; |
| |
| data->ioc_nid = id.nid; |
| data->ioc_count = share_count; |
| data->ioc_u32[0] = ip; |
| data->ioc_u32[1] = port; |
| data->ioc_u32[2] = myip; |
| data->ioc_u32[3] = conn_count; |
| data->ioc_u32[4] = id.pid; |
| return 0; |
| } |
| |
| case IOC_LIBCFS_ADD_PEER: |
| id.nid = data->ioc_nid; |
| id.pid = LUSTRE_SRV_LNET_PID; |
| return ksocknal_add_peer (ni, id, |
| data->ioc_u32[0], /* IP */ |
| data->ioc_u32[1]); /* port */ |
| |
| case IOC_LIBCFS_DEL_PEER: |
| id.nid = data->ioc_nid; |
| id.pid = LNET_PID_ANY; |
| return ksocknal_del_peer (ni, id, |
| data->ioc_u32[0]); /* IP */ |
| |
| case IOC_LIBCFS_GET_CONN: { |
| int txmem; |
| int rxmem; |
| int nagle; |
| ksock_conn_t *conn = ksocknal_get_conn_by_idx (ni, data->ioc_count); |
| |
| if (conn == NULL) |
| return -ENOENT; |
| |
| ksocknal_lib_get_conn_tunables(conn, &txmem, &rxmem, &nagle); |
| |
| data->ioc_count = txmem; |
| data->ioc_nid = conn->ksnc_peer->ksnp_id.nid; |
| data->ioc_flags = nagle; |
| data->ioc_u32[0] = conn->ksnc_ipaddr; |
| data->ioc_u32[1] = conn->ksnc_port; |
| data->ioc_u32[2] = conn->ksnc_myipaddr; |
| data->ioc_u32[3] = conn->ksnc_type; |
| data->ioc_u32[4] = conn->ksnc_scheduler->kss_info->ksi_cpt; |
| data->ioc_u32[5] = rxmem; |
| data->ioc_u32[6] = conn->ksnc_peer->ksnp_id.pid; |
| ksocknal_conn_decref(conn); |
| return 0; |
| } |
| |
| case IOC_LIBCFS_CLOSE_CONNECTION: |
| id.nid = data->ioc_nid; |
| id.pid = LNET_PID_ANY; |
| return ksocknal_close_matching_conns (id, |
| data->ioc_u32[0]); |
| |
| case IOC_LIBCFS_REGISTER_MYNID: |
| /* Ignore if this is a noop */ |
| if (data->ioc_nid == ni->ni_nid) |
| return 0; |
| |
| CERROR("obsolete IOC_LIBCFS_REGISTER_MYNID: %s(%s)\n", |
| libcfs_nid2str(data->ioc_nid), |
| libcfs_nid2str(ni->ni_nid)); |
| return -EINVAL; |
| |
| case IOC_LIBCFS_PUSH_CONNECTION: |
| id.nid = data->ioc_nid; |
| id.pid = LNET_PID_ANY; |
| return ksocknal_push(ni, id); |
| |
| default: |
| return -EINVAL; |
| } |
| /* not reached */ |
| } |
| |
| static void |
| ksocknal_free_buffers (void) |
| { |
| LASSERT (atomic_read(&ksocknal_data.ksnd_nactive_txs) == 0); |
| |
| if (ksocknal_data.ksnd_sched_info != NULL) { |
| struct ksock_sched_info *info; |
| int i; |
| |
| cfs_percpt_for_each(info, i, ksocknal_data.ksnd_sched_info) { |
| if (info->ksi_scheds != NULL) { |
| LIBCFS_FREE(info->ksi_scheds, |
| info->ksi_nthreads_max * |
| sizeof(info->ksi_scheds[0])); |
| } |
| } |
| cfs_percpt_free(ksocknal_data.ksnd_sched_info); |
| } |
| |
| LIBCFS_FREE (ksocknal_data.ksnd_peers, |
| sizeof (struct list_head) * |
| ksocknal_data.ksnd_peer_hash_size); |
| |
| spin_lock(&ksocknal_data.ksnd_tx_lock); |
| |
| if (!list_empty(&ksocknal_data.ksnd_idle_noop_txs)) { |
| struct list_head zlist; |
| ksock_tx_t *tx; |
| |
| list_add(&zlist, &ksocknal_data.ksnd_idle_noop_txs); |
| list_del_init(&ksocknal_data.ksnd_idle_noop_txs); |
| spin_unlock(&ksocknal_data.ksnd_tx_lock); |
| |
| while (!list_empty(&zlist)) { |
| tx = list_entry(zlist.next, ksock_tx_t, tx_list); |
| list_del(&tx->tx_list); |
| LIBCFS_FREE(tx, tx->tx_desc_size); |
| } |
| } else { |
| spin_unlock(&ksocknal_data.ksnd_tx_lock); |
| } |
| } |
| |
| static void |
| ksocknal_base_shutdown(void) |
| { |
| struct ksock_sched_info *info; |
| ksock_sched_t *sched; |
| int i; |
| int j; |
| |
| CDEBUG(D_MALLOC, "before NAL cleanup: kmem %d\n", |
| atomic_read (&libcfs_kmemory)); |
| LASSERT (ksocknal_data.ksnd_nnets == 0); |
| |
| switch (ksocknal_data.ksnd_init) { |
| default: |
| LASSERT (0); |
| |
| case SOCKNAL_INIT_ALL: |
| case SOCKNAL_INIT_DATA: |
| LASSERT (ksocknal_data.ksnd_peers != NULL); |
| for (i = 0; i < ksocknal_data.ksnd_peer_hash_size; i++) { |
| LASSERT (list_empty (&ksocknal_data.ksnd_peers[i])); |
| } |
| |
| LASSERT(list_empty(&ksocknal_data.ksnd_nets)); |
| LASSERT (list_empty (&ksocknal_data.ksnd_enomem_conns)); |
| LASSERT (list_empty (&ksocknal_data.ksnd_zombie_conns)); |
| LASSERT (list_empty (&ksocknal_data.ksnd_connd_connreqs)); |
| LASSERT (list_empty (&ksocknal_data.ksnd_connd_routes)); |
| |
| if (ksocknal_data.ksnd_sched_info != NULL) { |
| cfs_percpt_for_each(info, i, |
| ksocknal_data.ksnd_sched_info) { |
| if (info->ksi_scheds == NULL) |
| continue; |
| |
| for (j = 0; j < info->ksi_nthreads_max; j++) { |
| |
| sched = &info->ksi_scheds[j]; |
| LASSERT(list_empty( |
| &sched->kss_tx_conns)); |
| LASSERT(list_empty( |
| &sched->kss_rx_conns)); |
| LASSERT(list_empty( |
| &sched->kss_zombie_noop_txs)); |
| LASSERT(sched->kss_nconns == 0); |
| } |
| } |
| } |
| |
| /* flag threads to terminate; wake and wait for them to die */ |
| ksocknal_data.ksnd_shuttingdown = 1; |
| wake_up_all(&ksocknal_data.ksnd_connd_waitq); |
| wake_up_all(&ksocknal_data.ksnd_reaper_waitq); |
| |
| if (ksocknal_data.ksnd_sched_info != NULL) { |
| cfs_percpt_for_each(info, i, |
| ksocknal_data.ksnd_sched_info) { |
| if (info->ksi_scheds == NULL) |
| continue; |
| |
| for (j = 0; j < info->ksi_nthreads_max; j++) { |
| sched = &info->ksi_scheds[j]; |
| wake_up_all(&sched->kss_waitq); |
| } |
| } |
| } |
| |
| i = 4; |
| read_lock(&ksocknal_data.ksnd_global_lock); |
| while (ksocknal_data.ksnd_nthreads != 0) { |
| i++; |
| CDEBUG(((i & (-i)) == i) ? D_WARNING : D_NET, /* power of 2? */ |
| "waiting for %d threads to terminate\n", |
| ksocknal_data.ksnd_nthreads); |
| read_unlock(&ksocknal_data.ksnd_global_lock); |
| set_current_state(TASK_UNINTERRUPTIBLE); |
| schedule_timeout(cfs_time_seconds(1)); |
| read_lock(&ksocknal_data.ksnd_global_lock); |
| } |
| read_unlock(&ksocknal_data.ksnd_global_lock); |
| |
| ksocknal_free_buffers(); |
| |
| ksocknal_data.ksnd_init = SOCKNAL_INIT_NOTHING; |
| break; |
| } |
| |
| CDEBUG(D_MALLOC, "after NAL cleanup: kmem %d\n", |
| atomic_read (&libcfs_kmemory)); |
| |
| module_put(THIS_MODULE); |
| } |
| |
| static __u64 |
| ksocknal_new_incarnation (void) |
| { |
| |
| /* The incarnation number is the time this module loaded and it |
| * identifies this particular instance of the socknal. |
| */ |
| return ktime_get_ns(); |
| } |
| |
| static int |
| ksocknal_base_startup(void) |
| { |
| struct ksock_sched_info *info; |
| int rc; |
| int i; |
| |
| LASSERT (ksocknal_data.ksnd_init == SOCKNAL_INIT_NOTHING); |
| LASSERT (ksocknal_data.ksnd_nnets == 0); |
| |
| memset (&ksocknal_data, 0, sizeof (ksocknal_data)); /* zero pointers */ |
| |
| ksocknal_data.ksnd_peer_hash_size = SOCKNAL_PEER_HASH_SIZE; |
| LIBCFS_ALLOC (ksocknal_data.ksnd_peers, |
| sizeof (struct list_head) * |
| ksocknal_data.ksnd_peer_hash_size); |
| if (ksocknal_data.ksnd_peers == NULL) |
| return -ENOMEM; |
| |
| for (i = 0; i < ksocknal_data.ksnd_peer_hash_size; i++) |
| INIT_LIST_HEAD(&ksocknal_data.ksnd_peers[i]); |
| |
| rwlock_init(&ksocknal_data.ksnd_global_lock); |
| INIT_LIST_HEAD(&ksocknal_data.ksnd_nets); |
| |
| spin_lock_init(&ksocknal_data.ksnd_reaper_lock); |
| INIT_LIST_HEAD (&ksocknal_data.ksnd_enomem_conns); |
| INIT_LIST_HEAD (&ksocknal_data.ksnd_zombie_conns); |
| INIT_LIST_HEAD (&ksocknal_data.ksnd_deathrow_conns); |
| init_waitqueue_head(&ksocknal_data.ksnd_reaper_waitq); |
| |
| spin_lock_init(&ksocknal_data.ksnd_connd_lock); |
| INIT_LIST_HEAD (&ksocknal_data.ksnd_connd_connreqs); |
| INIT_LIST_HEAD (&ksocknal_data.ksnd_connd_routes); |
| init_waitqueue_head(&ksocknal_data.ksnd_connd_waitq); |
| |
| spin_lock_init(&ksocknal_data.ksnd_tx_lock); |
| INIT_LIST_HEAD (&ksocknal_data.ksnd_idle_noop_txs); |
| |
| /* NB memset above zeros whole of ksocknal_data */ |
| |
| /* flag lists/ptrs/locks initialised */ |
| ksocknal_data.ksnd_init = SOCKNAL_INIT_DATA; |
| try_module_get(THIS_MODULE); |
| |
| ksocknal_data.ksnd_sched_info = cfs_percpt_alloc(lnet_cpt_table(), |
| sizeof(*info)); |
| if (ksocknal_data.ksnd_sched_info == NULL) |
| goto failed; |
| |
| cfs_percpt_for_each(info, i, ksocknal_data.ksnd_sched_info) { |
| ksock_sched_t *sched; |
| int nthrs; |
| |
| nthrs = cfs_cpt_weight(lnet_cpt_table(), i); |
| if (*ksocknal_tunables.ksnd_nscheds > 0) { |
| nthrs = min(nthrs, *ksocknal_tunables.ksnd_nscheds); |
| } else { |
| /* max to half of CPUs, assume another half should be |
| * reserved for upper layer modules */ |
| nthrs = min(max(SOCKNAL_NSCHEDS, nthrs >> 1), nthrs); |
| } |
| |
| info->ksi_nthreads_max = nthrs; |
| info->ksi_cpt = i; |
| |
| LIBCFS_CPT_ALLOC(info->ksi_scheds, lnet_cpt_table(), i, |
| info->ksi_nthreads_max * sizeof(*sched)); |
| if (info->ksi_scheds == NULL) |
| goto failed; |
| |
| for (; nthrs > 0; nthrs--) { |
| sched = &info->ksi_scheds[nthrs - 1]; |
| |
| sched->kss_info = info; |
| spin_lock_init(&sched->kss_lock); |
| INIT_LIST_HEAD(&sched->kss_rx_conns); |
| INIT_LIST_HEAD(&sched->kss_tx_conns); |
| INIT_LIST_HEAD(&sched->kss_zombie_noop_txs); |
| init_waitqueue_head(&sched->kss_waitq); |
| } |
| } |
| |
| ksocknal_data.ksnd_connd_starting = 0; |
| ksocknal_data.ksnd_connd_failed_stamp = 0; |
| ksocknal_data.ksnd_connd_starting_stamp = get_seconds(); |
| /* must have at least 2 connds to remain responsive to accepts while |
| * connecting */ |
| if (*ksocknal_tunables.ksnd_nconnds < SOCKNAL_CONND_RESV + 1) |
| *ksocknal_tunables.ksnd_nconnds = SOCKNAL_CONND_RESV + 1; |
| |
| if (*ksocknal_tunables.ksnd_nconnds_max < |
| *ksocknal_tunables.ksnd_nconnds) { |
| ksocknal_tunables.ksnd_nconnds_max = |
| ksocknal_tunables.ksnd_nconnds; |
| } |
| |
| for (i = 0; i < *ksocknal_tunables.ksnd_nconnds; i++) { |
| char name[16]; |
| spin_lock_bh(&ksocknal_data.ksnd_connd_lock); |
| ksocknal_data.ksnd_connd_starting++; |
| spin_unlock_bh(&ksocknal_data.ksnd_connd_lock); |
| |
| |
| snprintf(name, sizeof(name), "socknal_cd%02d", i); |
| rc = ksocknal_thread_start(ksocknal_connd, |
| (void *)((ulong_ptr_t)i), name); |
| if (rc != 0) { |
| spin_lock_bh(&ksocknal_data.ksnd_connd_lock); |
| ksocknal_data.ksnd_connd_starting--; |
| spin_unlock_bh(&ksocknal_data.ksnd_connd_lock); |
| CERROR("Can't spawn socknal connd: %d\n", rc); |
| goto failed; |
| } |
| } |
| |
| rc = ksocknal_thread_start(ksocknal_reaper, NULL, "socknal_reaper"); |
| if (rc != 0) { |
| CERROR ("Can't spawn socknal reaper: %d\n", rc); |
| goto failed; |
| } |
| |
| /* flag everything initialised */ |
| ksocknal_data.ksnd_init = SOCKNAL_INIT_ALL; |
| |
| return 0; |
| |
| failed: |
| ksocknal_base_shutdown(); |
| return -ENETDOWN; |
| } |
| |
| static void |
| ksocknal_debug_peerhash (lnet_ni_t *ni) |
| { |
| ksock_peer_t *peer = NULL; |
| struct list_head *tmp; |
| int i; |
| |
| read_lock(&ksocknal_data.ksnd_global_lock); |
| |
| for (i = 0; i < ksocknal_data.ksnd_peer_hash_size; i++) { |
| list_for_each (tmp, &ksocknal_data.ksnd_peers[i]) { |
| peer = list_entry (tmp, ksock_peer_t, ksnp_list); |
| |
| if (peer->ksnp_ni == ni) |
| break; |
| |
| peer = NULL; |
| } |
| } |
| |
| if (peer != NULL) { |
| ksock_route_t *route; |
| ksock_conn_t *conn; |
| |
| CWARN("Active peer on shutdown: %s, ref %d, scnt %d, closing %d, accepting %d, err %d, zcookie %llu, txq %d, zc_req %d\n", |
| libcfs_id2str(peer->ksnp_id), |
| atomic_read(&peer->ksnp_refcount), |
| peer->ksnp_sharecount, peer->ksnp_closing, |
| peer->ksnp_accepting, peer->ksnp_error, |
| peer->ksnp_zc_next_cookie, |
| !list_empty(&peer->ksnp_tx_queue), |
| !list_empty(&peer->ksnp_zc_req_list)); |
| |
| list_for_each (tmp, &peer->ksnp_routes) { |
| route = list_entry(tmp, ksock_route_t, ksnr_list); |
| CWARN("Route: ref %d, schd %d, conn %d, cnted %d, del %d\n", |
| atomic_read(&route->ksnr_refcount), |
| route->ksnr_scheduled, route->ksnr_connecting, |
| route->ksnr_connected, route->ksnr_deleted); |
| } |
| |
| list_for_each (tmp, &peer->ksnp_conns) { |
| conn = list_entry(tmp, ksock_conn_t, ksnc_list); |
| CWARN ("Conn: ref %d, sref %d, t %d, c %d\n", |
| atomic_read(&conn->ksnc_conn_refcount), |
| atomic_read(&conn->ksnc_sock_refcount), |
| conn->ksnc_type, conn->ksnc_closing); |
| } |
| } |
| |
| read_unlock(&ksocknal_data.ksnd_global_lock); |
| return; |
| } |
| |
| void |
| ksocknal_shutdown (lnet_ni_t *ni) |
| { |
| ksock_net_t *net = ni->ni_data; |
| int i; |
| lnet_process_id_t anyid = {0}; |
| |
| anyid.nid = LNET_NID_ANY; |
| anyid.pid = LNET_PID_ANY; |
| |
| LASSERT(ksocknal_data.ksnd_init == SOCKNAL_INIT_ALL); |
| LASSERT(ksocknal_data.ksnd_nnets > 0); |
| |
| spin_lock_bh(&net->ksnn_lock); |
| net->ksnn_shutdown = 1; /* prevent new peers */ |
| spin_unlock_bh(&net->ksnn_lock); |
| |
| /* Delete all peers */ |
| ksocknal_del_peer(ni, anyid, 0); |
| |
| /* Wait for all peer state to clean up */ |
| i = 2; |
| spin_lock_bh(&net->ksnn_lock); |
| while (net->ksnn_npeers != 0) { |
| spin_unlock_bh(&net->ksnn_lock); |
| |
| i++; |
| CDEBUG(((i & (-i)) == i) ? D_WARNING : D_NET, /* power of 2? */ |
| "waiting for %d peers to disconnect\n", |
| net->ksnn_npeers); |
| set_current_state(TASK_UNINTERRUPTIBLE); |
| schedule_timeout(cfs_time_seconds(1)); |
| |
| ksocknal_debug_peerhash(ni); |
| |
| spin_lock_bh(&net->ksnn_lock); |
| } |
| spin_unlock_bh(&net->ksnn_lock); |
| |
| for (i = 0; i < net->ksnn_ninterfaces; i++) { |
| LASSERT (net->ksnn_interfaces[i].ksni_npeers == 0); |
| LASSERT (net->ksnn_interfaces[i].ksni_nroutes == 0); |
| } |
| |
| list_del(&net->ksnn_list); |
| LIBCFS_FREE(net, sizeof(*net)); |
| |
| ksocknal_data.ksnd_nnets--; |
| if (ksocknal_data.ksnd_nnets == 0) |
| ksocknal_base_shutdown(); |
| } |
| |
| static int |
| ksocknal_enumerate_interfaces(ksock_net_t *net) |
| { |
| char **names; |
| int i; |
| int j; |
| int rc; |
| int n; |
| |
| n = libcfs_ipif_enumerate(&names); |
| if (n <= 0) { |
| CERROR("Can't enumerate interfaces: %d\n", n); |
| return n; |
| } |
| |
| for (i = j = 0; i < n; i++) { |
| int up; |
| __u32 ip; |
| __u32 mask; |
| |
| if (!strcmp(names[i], "lo")) /* skip the loopback IF */ |
| continue; |
| |
| rc = libcfs_ipif_query(names[i], &up, &ip, &mask); |
| if (rc != 0) { |
| CWARN("Can't get interface %s info: %d\n", |
| names[i], rc); |
| continue; |
| } |
| |
| if (!up) { |
| CWARN("Ignoring interface %s (down)\n", |
| names[i]); |
| continue; |
| } |
| |
| if (j == LNET_MAX_INTERFACES) { |
| CWARN("Ignoring interface %s (too many interfaces)\n", |
| names[i]); |
| continue; |
| } |
| |
| net->ksnn_interfaces[j].ksni_ipaddr = ip; |
| net->ksnn_interfaces[j].ksni_netmask = mask; |
| strncpy(&net->ksnn_interfaces[j].ksni_name[0], |
| names[i], IFNAMSIZ); |
| j++; |
| } |
| |
| libcfs_ipif_free_enumeration(names, n); |
| |
| if (j == 0) |
| CERROR("Can't find any usable interfaces\n"); |
| |
| return j; |
| } |
| |
| static int |
| ksocknal_search_new_ipif(ksock_net_t *net) |
| { |
| int new_ipif = 0; |
| int i; |
| |
| for (i = 0; i < net->ksnn_ninterfaces; i++) { |
| char *ifnam = &net->ksnn_interfaces[i].ksni_name[0]; |
| char *colon = strchr(ifnam, ':'); |
| int found = 0; |
| ksock_net_t *tmp; |
| int j; |
| |
| if (colon != NULL) /* ignore alias device */ |
| *colon = 0; |
| |
| list_for_each_entry(tmp, &ksocknal_data.ksnd_nets, |
| ksnn_list) { |
| for (j = 0; !found && j < tmp->ksnn_ninterfaces; j++) { |
| char *ifnam2 = |
| &tmp->ksnn_interfaces[j].ksni_name[0]; |
| char *colon2 = strchr(ifnam2, ':'); |
| |
| if (colon2 != NULL) |
| *colon2 = 0; |
| |
| found = strcmp(ifnam, ifnam2) == 0; |
| if (colon2 != NULL) |
| *colon2 = ':'; |
| } |
| if (found) |
| break; |
| } |
| |
| new_ipif += !found; |
| if (colon != NULL) |
| *colon = ':'; |
| } |
| |
| return new_ipif; |
| } |
| |
| static int |
| ksocknal_start_schedulers(struct ksock_sched_info *info) |
| { |
| int nthrs; |
| int rc = 0; |
| int i; |
| |
| if (info->ksi_nthreads == 0) { |
| if (*ksocknal_tunables.ksnd_nscheds > 0) { |
| nthrs = info->ksi_nthreads_max; |
| } else { |
| nthrs = cfs_cpt_weight(lnet_cpt_table(), |
| info->ksi_cpt); |
| nthrs = min(max(SOCKNAL_NSCHEDS, nthrs >> 1), nthrs); |
| nthrs = min(SOCKNAL_NSCHEDS_HIGH, nthrs); |
| } |
| nthrs = min(nthrs, info->ksi_nthreads_max); |
| } else { |
| LASSERT(info->ksi_nthreads <= info->ksi_nthreads_max); |
| /* increase two threads if there is new interface */ |
| nthrs = min(2, info->ksi_nthreads_max - info->ksi_nthreads); |
| } |
| |
| for (i = 0; i < nthrs; i++) { |
| long id; |
| char name[20]; |
| ksock_sched_t *sched; |
| id = KSOCK_THREAD_ID(info->ksi_cpt, info->ksi_nthreads + i); |
| sched = &info->ksi_scheds[KSOCK_THREAD_SID(id)]; |
| snprintf(name, sizeof(name), "socknal_sd%02d_%02d", |
| info->ksi_cpt, (int)(sched - &info->ksi_scheds[0])); |
| |
| rc = ksocknal_thread_start(ksocknal_scheduler, |
| (void *)id, name); |
| if (rc == 0) |
| continue; |
| |
| CERROR("Can't spawn thread %d for scheduler[%d]: %d\n", |
| info->ksi_cpt, info->ksi_nthreads + i, rc); |
| break; |
| } |
| |
| info->ksi_nthreads += i; |
| return rc; |
| } |
| |
| static int |
| ksocknal_net_start_threads(ksock_net_t *net, __u32 *cpts, int ncpts) |
| { |
| int newif = ksocknal_search_new_ipif(net); |
| int rc; |
| int i; |
| |
| LASSERT(ncpts > 0 && ncpts <= cfs_cpt_number(lnet_cpt_table())); |
| |
| for (i = 0; i < ncpts; i++) { |
| struct ksock_sched_info *info; |
| int cpt = (cpts == NULL) ? i : cpts[i]; |
| |
| LASSERT(cpt < cfs_cpt_number(lnet_cpt_table())); |
| info = ksocknal_data.ksnd_sched_info[cpt]; |
| |
| if (!newif && info->ksi_nthreads > 0) |
| continue; |
| |
| rc = ksocknal_start_schedulers(info); |
| if (rc != 0) |
| return rc; |
| } |
| return 0; |
| } |
| |
| int |
| ksocknal_startup (lnet_ni_t *ni) |
| { |
| ksock_net_t *net; |
| int rc; |
| int i; |
| |
| LASSERT (ni->ni_lnd == &the_ksocklnd); |
| |
| if (ksocknal_data.ksnd_init == SOCKNAL_INIT_NOTHING) { |
| rc = ksocknal_base_startup(); |
| if (rc != 0) |
| return rc; |
| } |
| |
| LIBCFS_ALLOC(net, sizeof(*net)); |
| if (net == NULL) |
| goto fail_0; |
| |
| spin_lock_init(&net->ksnn_lock); |
| net->ksnn_incarnation = ksocknal_new_incarnation(); |
| ni->ni_data = net; |
| ni->ni_peertimeout = *ksocknal_tunables.ksnd_peertimeout; |
| ni->ni_maxtxcredits = *ksocknal_tunables.ksnd_credits; |
| ni->ni_peertxcredits = *ksocknal_tunables.ksnd_peertxcredits; |
| ni->ni_peerrtrcredits = *ksocknal_tunables.ksnd_peerrtrcredits; |
| |
| if (ni->ni_interfaces[0] == NULL) { |
| rc = ksocknal_enumerate_interfaces(net); |
| if (rc <= 0) |
| goto fail_1; |
| |
| net->ksnn_ninterfaces = 1; |
| } else { |
| for (i = 0; i < LNET_MAX_INTERFACES; i++) { |
| int up; |
| |
| if (ni->ni_interfaces[i] == NULL) |
| break; |
| |
| rc = libcfs_ipif_query( |
| ni->ni_interfaces[i], &up, |
| &net->ksnn_interfaces[i].ksni_ipaddr, |
| &net->ksnn_interfaces[i].ksni_netmask); |
| |
| if (rc != 0) { |
| CERROR("Can't get interface %s info: %d\n", |
| ni->ni_interfaces[i], rc); |
| goto fail_1; |
| } |
| |
| if (!up) { |
| CERROR("Interface %s is down\n", |
| ni->ni_interfaces[i]); |
| goto fail_1; |
| } |
| |
| strncpy(&net->ksnn_interfaces[i].ksni_name[0], |
| ni->ni_interfaces[i], IFNAMSIZ); |
| } |
| net->ksnn_ninterfaces = i; |
| } |
| |
| /* call it before add it to ksocknal_data.ksnd_nets */ |
| rc = ksocknal_net_start_threads(net, ni->ni_cpts, ni->ni_ncpts); |
| if (rc != 0) |
| goto fail_1; |
| |
| ni->ni_nid = LNET_MKNID(LNET_NIDNET(ni->ni_nid), |
| net->ksnn_interfaces[0].ksni_ipaddr); |
| list_add(&net->ksnn_list, &ksocknal_data.ksnd_nets); |
| |
| ksocknal_data.ksnd_nnets++; |
| |
| return 0; |
| |
| fail_1: |
| LIBCFS_FREE(net, sizeof(*net)); |
| fail_0: |
| if (ksocknal_data.ksnd_nnets == 0) |
| ksocknal_base_shutdown(); |
| |
| return -ENETDOWN; |
| } |
| |
| |
| static void __exit |
| ksocknal_module_fini (void) |
| { |
| lnet_unregister_lnd(&the_ksocklnd); |
| } |
| |
| static int __init |
| ksocknal_module_init (void) |
| { |
| int rc; |
| |
| /* check ksnr_connected/connecting field large enough */ |
| CLASSERT (SOCKLND_CONN_NTYPES <= 4); |
| CLASSERT (SOCKLND_CONN_ACK == SOCKLND_CONN_BULK_IN); |
| |
| /* initialize the_ksocklnd */ |
| the_ksocklnd.lnd_type = SOCKLND; |
| the_ksocklnd.lnd_startup = ksocknal_startup; |
| the_ksocklnd.lnd_shutdown = ksocknal_shutdown; |
| the_ksocklnd.lnd_ctl = ksocknal_ctl; |
| the_ksocklnd.lnd_send = ksocknal_send; |
| the_ksocklnd.lnd_recv = ksocknal_recv; |
| the_ksocklnd.lnd_notify = ksocknal_notify; |
| the_ksocklnd.lnd_query = ksocknal_query; |
| the_ksocklnd.lnd_accept = ksocknal_accept; |
| |
| rc = ksocknal_tunables_init(); |
| if (rc != 0) |
| return rc; |
| |
| lnet_register_lnd(&the_ksocklnd); |
| |
| return 0; |
| } |
| |
| MODULE_AUTHOR("Sun Microsystems, Inc. <http://www.lustre.org/>"); |
| MODULE_DESCRIPTION("Kernel TCP Socket LND v3.0.0"); |
| MODULE_LICENSE("GPL"); |
| MODULE_VERSION("3.0.0"); |
| |
| module_init(ksocknal_module_init); |
| module_exit(ksocknal_module_fini); |