i40iw: Add IP addr handling on netdev events
authorMustafa Ismail <mustafa.ismail@intel.com>
Wed, 30 Nov 2016 21:07:30 +0000 (15:07 -0600)
committerDoug Ledford <dledford@redhat.com>
Mon, 5 Dec 2016 21:09:41 +0000 (16:09 -0500)
Disable listeners and disconnect all connected QPs on
a netdev interface down event. On an interface up event,
the listeners are re-enabled.

Signed-off-by: Mustafa Ismail <mustafa.ismail@intel.com>
Signed-off-by: Shiraz Saleem <shiraz.saleem@intel.com>
Signed-off-by: Doug Ledford <dledford@redhat.com>
drivers/infiniband/hw/i40iw/i40iw_cm.c
drivers/infiniband/hw/i40iw/i40iw_cm.h
drivers/infiniband/hw/i40iw/i40iw_utils.c

index 11ef0b09c8433689dc2b4714dedf3d90751f2bd1..93ae764fc44e1ef0af55cee790a81fb1cb571347 100644 (file)
@@ -1556,9 +1556,15 @@ static enum i40iw_status_code i40iw_del_multiple_qhash(
                memcpy(cm_info->loc_addr, child_listen_node->loc_addr,
                       sizeof(cm_info->loc_addr));
                cm_info->vlan_id = child_listen_node->vlan_id;
-               ret = i40iw_manage_qhash(iwdev, cm_info,
-                                        I40IW_QHASH_TYPE_TCP_SYN,
-                                        I40IW_QHASH_MANAGE_TYPE_DELETE, NULL, false);
+               if (child_listen_node->qhash_set) {
+                       ret = i40iw_manage_qhash(iwdev, cm_info,
+                                                I40IW_QHASH_TYPE_TCP_SYN,
+                                                I40IW_QHASH_MANAGE_TYPE_DELETE,
+                                                NULL, false);
+                       child_listen_node->qhash_set = false;
+               } else {
+                       ret = I40IW_SUCCESS;
+               }
                i40iw_debug(&iwdev->sc_dev,
                            I40IW_DEBUG_CM,
                            "freed pointer = %p\n",
@@ -1687,6 +1693,7 @@ static enum i40iw_status_code i40iw_add_mqh_6(struct i40iw_device *iwdev,
                                                         I40IW_QHASH_MANAGE_TYPE_ADD,
                                                         NULL, true);
                                if (!ret) {
+                                       child_listen_node->qhash_set = true;
                                        spin_lock_irqsave(&iwdev->cm_core.listen_list_lock, flags);
                                        list_add(&child_listen_node->child_listen_list,
                                                 &cm_parent_listen_node->child_listen_list);
@@ -1765,6 +1772,7 @@ static enum i40iw_status_code i40iw_add_mqh_4(
                                                         NULL,
                                                         true);
                                if (!ret) {
+                                       child_listen_node->qhash_set = true;
                                        spin_lock_irqsave(&iwdev->cm_core.listen_list_lock, flags);
                                        list_add(&child_listen_node->child_listen_list,
                                                 &cm_parent_listen_node->child_listen_list);
@@ -4129,6 +4137,73 @@ static void i40iw_cm_post_event(struct i40iw_cm_event *event)
        queue_work(event->cm_node->cm_core->event_wq, &event->event_work);
 }
 
+/**
+ * i40iw_qhash_ctrl - enable/disable qhash for list
+ * @iwdev: device pointer
+ * @parent_listen_node: parent listen node
+ * @nfo: cm info node
+ * @ipaddr: Pointer to IPv4 or IPv6 address
+ * @ipv4: flag indicating IPv4 when true
+ * @ifup: flag indicating interface up when true
+ *
+ * Enables or disables the qhash for the node in the child
+ * listen list that matches ipaddr. If no matching IP was found
+ * it will allocate and add a new child listen node to the
+ * parent listen node. The listen_list_lock is assumed to be
+ * held when called.
+ */
+static void i40iw_qhash_ctrl(struct i40iw_device *iwdev,
+                            struct i40iw_cm_listener *parent_listen_node,
+                            struct i40iw_cm_info *nfo,
+                            u32 *ipaddr, bool ipv4, bool ifup)
+{
+       struct list_head *child_listen_list = &parent_listen_node->child_listen_list;
+       struct i40iw_cm_listener *child_listen_node;
+       struct list_head *pos, *tpos;
+       enum i40iw_status_code ret;
+       bool node_allocated = false;
+       enum i40iw_quad_hash_manage_type op =
+               ifup ? I40IW_QHASH_MANAGE_TYPE_ADD : I40IW_QHASH_MANAGE_TYPE_DELETE;
+
+       list_for_each_safe(pos, tpos, child_listen_list) {
+               child_listen_node =
+                       list_entry(pos,
+                                  struct i40iw_cm_listener,
+                                  child_listen_list);
+               if (!memcmp(child_listen_node->loc_addr, ipaddr, ipv4 ? 4 : 16))
+                       goto set_qhash;
+       }
+
+       /* if not found then add a child listener if interface is going up */
+       if (!ifup)
+               return;
+       child_listen_node = kzalloc(sizeof(*child_listen_node), GFP_ATOMIC);
+       if (!child_listen_node)
+               return;
+       node_allocated = true;
+       memcpy(child_listen_node, parent_listen_node, sizeof(*child_listen_node));
+
+       memcpy(child_listen_node->loc_addr, ipaddr,  ipv4 ? 4 : 16);
+
+set_qhash:
+       memcpy(nfo->loc_addr,
+              child_listen_node->loc_addr,
+              sizeof(nfo->loc_addr));
+       nfo->vlan_id = child_listen_node->vlan_id;
+       ret = i40iw_manage_qhash(iwdev, nfo,
+                                I40IW_QHASH_TYPE_TCP_SYN,
+                                op,
+                                NULL, false);
+       if (!ret) {
+               child_listen_node->qhash_set = ifup;
+               if (node_allocated)
+                       list_add(&child_listen_node->child_listen_list,
+                                &parent_listen_node->child_listen_list);
+       } else if (node_allocated) {
+               kfree(child_listen_node);
+       }
+}
+
 /**
  * i40iw_cm_disconnect_all - disconnect all connected qp's
  * @iwdev: device pointer
@@ -4159,3 +4234,60 @@ void i40iw_cm_disconnect_all(struct i40iw_device *iwdev)
                i40iw_rem_ref_cm_node(cm_node);
        }
 }
+
+/**
+ * i40iw_ifdown_notify - process an ifdown on an interface
+ * @iwdev: device pointer
+ * @ipaddr: Pointer to IPv4 or IPv6 address
+ * @ipv4: flag indicating IPv4 when true
+ * @ifup: flag indicating interface up when true
+ */
+void i40iw_if_notify(struct i40iw_device *iwdev, struct net_device *netdev,
+                    u32 *ipaddr, bool ipv4, bool ifup)
+{
+       struct i40iw_cm_core *cm_core = &iwdev->cm_core;
+       unsigned long flags;
+       struct i40iw_cm_listener *listen_node;
+       static const u32 ip_zero[4] = { 0, 0, 0, 0 };
+       struct i40iw_cm_info nfo;
+       u16 vlan_id = rdma_vlan_dev_vlan_id(netdev);
+       enum i40iw_status_code ret;
+       enum i40iw_quad_hash_manage_type op =
+               ifup ? I40IW_QHASH_MANAGE_TYPE_ADD : I40IW_QHASH_MANAGE_TYPE_DELETE;
+
+       /* Disable or enable qhash for listeners */
+       spin_lock_irqsave(&cm_core->listen_list_lock, flags);
+       list_for_each_entry(listen_node, &cm_core->listen_nodes, list) {
+               if (vlan_id == listen_node->vlan_id &&
+                   (!memcmp(listen_node->loc_addr, ipaddr, ipv4 ? 4 : 16) ||
+                   !memcmp(listen_node->loc_addr, ip_zero, ipv4 ? 4 : 16))) {
+                       memcpy(nfo.loc_addr, listen_node->loc_addr,
+                              sizeof(nfo.loc_addr));
+                       nfo.loc_port = listen_node->loc_port;
+                       nfo.ipv4 = listen_node->ipv4;
+                       nfo.vlan_id = listen_node->vlan_id;
+                       nfo.user_pri = listen_node->user_pri;
+                       if (!list_empty(&listen_node->child_listen_list)) {
+                               i40iw_qhash_ctrl(iwdev,
+                                                listen_node,
+                                                &nfo,
+                                                ipaddr, ipv4, ifup);
+                       } else if (memcmp(listen_node->loc_addr, ip_zero,
+                                         ipv4 ? 4 : 16)) {
+                               ret = i40iw_manage_qhash(iwdev,
+                                                        &nfo,
+                                                        I40IW_QHASH_TYPE_TCP_SYN,
+                                                        op,
+                                                        NULL,
+                                                        false);
+                               if (!ret)
+                                       listen_node->qhash_set = ifup;
+                       }
+               }
+       }
+       spin_unlock_irqrestore(&cm_core->listen_list_lock, flags);
+
+       /* disconnect any connected qp's on ifdown */
+       if (!ifup)
+               i40iw_cm_disconnect_all(iwdev);
+}
index 0381b7f5e20dc415922577542a0c9141e951b469..49ed7a52a84dc51e80cd747c7aef2a487f1b67b0 100644 (file)
@@ -444,5 +444,7 @@ int i40iw_arp_table(struct i40iw_device *iwdev,
                    u8 *mac_addr,
                    u32 action);
 
+void i40iw_if_notify(struct i40iw_device *iwdev, struct net_device *netdev,
+                    u32 *ipaddr, bool ipv4, bool ifup);
 void i40iw_cm_disconnect_all(struct i40iw_device *iwdev);
 #endif /* I40IW_CM_H */
index 58151280828d72c8462f516453a8b18639e4bfc1..641f00f3dda11b90260187edaf5c5229311affd6 100644 (file)
@@ -153,6 +153,7 @@ int i40iw_inetaddr_event(struct notifier_block *notifier,
        struct i40iw_device *iwdev;
        struct i40iw_handler *hdl;
        u32 local_ipaddr;
+       u32 action = I40IW_ARP_ADD;
 
        hdl = i40iw_find_netdev(event_netdev);
        if (!hdl)
@@ -164,44 +165,25 @@ int i40iw_inetaddr_event(struct notifier_block *notifier,
        if (netdev != event_netdev)
                return NOTIFY_DONE;
 
+       if (upper_dev)
+               local_ipaddr = ntohl(
+                       ((struct in_device *)upper_dev->ip_ptr)->ifa_list->ifa_address);
+       else
+               local_ipaddr = ntohl(ifa->ifa_address);
        switch (event) {
        case NETDEV_DOWN:
-               if (upper_dev)
-                       local_ipaddr = ntohl(
-                               ((struct in_device *)upper_dev->ip_ptr)->ifa_list->ifa_address);
-               else
-                       local_ipaddr = ntohl(ifa->ifa_address);
-               i40iw_manage_arp_cache(iwdev,
-                                      netdev->dev_addr,
-                                      &local_ipaddr,
-                                      true,
-                                      I40IW_ARP_DELETE);
-               return NOTIFY_OK;
+               action = I40IW_ARP_DELETE;
+               /* Fall through */
        case NETDEV_UP:
-               if (upper_dev)
-                       local_ipaddr = ntohl(
-                               ((struct in_device *)upper_dev->ip_ptr)->ifa_list->ifa_address);
-               else
-                       local_ipaddr = ntohl(ifa->ifa_address);
-               i40iw_manage_arp_cache(iwdev,
-                                      netdev->dev_addr,
-                                      &local_ipaddr,
-                                      true,
-                                      I40IW_ARP_ADD);
-               break;
+               /* Fall through */
        case NETDEV_CHANGEADDR:
-               /* Add the address to the IP table */
-               if (upper_dev)
-                       local_ipaddr = ntohl(
-                               ((struct in_device *)upper_dev->ip_ptr)->ifa_list->ifa_address);
-               else
-                       local_ipaddr = ntohl(ifa->ifa_address);
-
                i40iw_manage_arp_cache(iwdev,
                                       netdev->dev_addr,
                                       &local_ipaddr,
                                       true,
-                                      I40IW_ARP_ADD);
+                                      action);
+               i40iw_if_notify(iwdev, netdev, &local_ipaddr, true,
+                               (action == I40IW_ARP_ADD) ? true : false);
                break;
        default:
                break;
@@ -225,6 +207,7 @@ int i40iw_inet6addr_event(struct notifier_block *notifier,
        struct i40iw_device *iwdev;
        struct i40iw_handler *hdl;
        u32 local_ipaddr6[4];
+       u32 action = I40IW_ARP_ADD;
 
        hdl = i40iw_find_netdev(event_netdev);
        if (!hdl)
@@ -235,24 +218,21 @@ int i40iw_inet6addr_event(struct notifier_block *notifier,
        if (netdev != event_netdev)
                return NOTIFY_DONE;
 
+       i40iw_copy_ip_ntohl(local_ipaddr6, ifa->addr.in6_u.u6_addr32);
        switch (event) {
        case NETDEV_DOWN:
-               i40iw_copy_ip_ntohl(local_ipaddr6, ifa->addr.in6_u.u6_addr32);
-               i40iw_manage_arp_cache(iwdev,
-                                      netdev->dev_addr,
-                                      local_ipaddr6,
-                                      false,
-                                      I40IW_ARP_DELETE);
-               return NOTIFY_OK;
+               action = I40IW_ARP_DELETE;
+               /* Fall through */
        case NETDEV_UP:
                /* Fall through */
        case NETDEV_CHANGEADDR:
-               i40iw_copy_ip_ntohl(local_ipaddr6, ifa->addr.in6_u.u6_addr32);
                i40iw_manage_arp_cache(iwdev,
                                       netdev->dev_addr,
                                       local_ipaddr6,
                                       false,
-                                      I40IW_ARP_ADD);
+                                      action);
+               i40iw_if_notify(iwdev, netdev, local_ipaddr6, false,
+                               (action == I40IW_ARP_ADD) ? true : false);
                break;
        default:
                break;