Merge tag 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/dledford/rdma
[cascardo/linux.git] / drivers / infiniband / ulp / ipoib / ipoib_ib.c
index 418e5a1..45c40a1 100644 (file)
@@ -997,6 +997,106 @@ static inline int update_child_pkey(struct ipoib_dev_priv *priv)
        return 0;
 }
 
+/*
+ * returns true if the device address of the ipoib interface has changed and the
+ * new address is a valid one (i.e in the gid table), return false otherwise.
+ */
+static bool ipoib_dev_addr_changed_valid(struct ipoib_dev_priv *priv)
+{
+       union ib_gid search_gid;
+       union ib_gid gid0;
+       union ib_gid *netdev_gid;
+       int err;
+       u16 index;
+       u8 port;
+       bool ret = false;
+
+       netdev_gid = (union ib_gid *)(priv->dev->dev_addr + 4);
+       if (ib_query_gid(priv->ca, priv->port, 0, &gid0, NULL))
+               return false;
+
+       netif_addr_lock(priv->dev);
+
+       /* The subnet prefix may have changed, update it now so we won't have
+        * to do it later
+        */
+       priv->local_gid.global.subnet_prefix = gid0.global.subnet_prefix;
+       netdev_gid->global.subnet_prefix = gid0.global.subnet_prefix;
+       search_gid.global.subnet_prefix = gid0.global.subnet_prefix;
+
+       search_gid.global.interface_id = priv->local_gid.global.interface_id;
+
+       netif_addr_unlock(priv->dev);
+
+       err = ib_find_gid(priv->ca, &search_gid, IB_GID_TYPE_IB,
+                         priv->dev, &port, &index);
+
+       netif_addr_lock(priv->dev);
+
+       if (search_gid.global.interface_id !=
+           priv->local_gid.global.interface_id)
+               /* There was a change while we were looking up the gid, bail
+                * here and let the next work sort this out
+                */
+               goto out;
+
+       /* The next section of code needs some background:
+        * Per IB spec the port GUID can't change if the HCA is powered on.
+        * port GUID is the basis for GID at index 0 which is the basis for
+        * the default device address of a ipoib interface.
+        *
+        * so it seems the flow should be:
+        * if user_changed_dev_addr && gid in gid tbl
+        *      set bit dev_addr_set
+        *      return true
+        * else
+        *      return false
+        *
+        * The issue is that there are devices that don't follow the spec,
+        * they change the port GUID when the HCA is powered, so in order
+        * not to break userspace applications, We need to check if the
+        * user wanted to control the device address and we assume that
+        * if he sets the device address back to be based on GID index 0,
+        * he no longer wishs to control it.
+        *
+        * If the user doesn't control the the device address,
+        * IPOIB_FLAG_DEV_ADDR_SET is set and ib_find_gid failed it means
+        * the port GUID has changed and GID at index 0 has changed
+        * so we need to change priv->local_gid and priv->dev->dev_addr
+        * to reflect the new GID.
+        */
+       if (!test_bit(IPOIB_FLAG_DEV_ADDR_SET, &priv->flags)) {
+               if (!err && port == priv->port) {
+                       set_bit(IPOIB_FLAG_DEV_ADDR_SET, &priv->flags);
+                       if (index == 0)
+                               clear_bit(IPOIB_FLAG_DEV_ADDR_CTRL,
+                                         &priv->flags);
+                       else
+                               set_bit(IPOIB_FLAG_DEV_ADDR_CTRL, &priv->flags);
+                       ret = true;
+               } else {
+                       ret = false;
+               }
+       } else {
+               if (!err && port == priv->port) {
+                       ret = true;
+               } else {
+                       if (!test_bit(IPOIB_FLAG_DEV_ADDR_CTRL, &priv->flags)) {
+                               memcpy(&priv->local_gid, &gid0,
+                                      sizeof(priv->local_gid));
+                               memcpy(priv->dev->dev_addr + 4, &gid0,
+                                      sizeof(priv->local_gid));
+                               ret = true;
+                       }
+               }
+       }
+
+out:
+       netif_addr_unlock(priv->dev);
+
+       return ret;
+}
+
 static void __ipoib_ib_dev_flush(struct ipoib_dev_priv *priv,
                                enum ipoib_flush_level level,
                                int nesting)
@@ -1018,6 +1118,9 @@ static void __ipoib_ib_dev_flush(struct ipoib_dev_priv *priv,
 
        if (!test_bit(IPOIB_FLAG_INITIALIZED, &priv->flags) &&
            level != IPOIB_FLUSH_HEAVY) {
+               /* Make sure the dev_addr is set even if not flushing */
+               if (level == IPOIB_FLUSH_LIGHT)
+                       ipoib_dev_addr_changed_valid(priv);
                ipoib_dbg(priv, "Not flushing - IPOIB_FLAG_INITIALIZED not set.\n");
                return;
        }
@@ -1029,7 +1132,8 @@ static void __ipoib_ib_dev_flush(struct ipoib_dev_priv *priv,
                                update_parent_pkey(priv);
                        else
                                update_child_pkey(priv);
-               }
+               } else if (level == IPOIB_FLUSH_LIGHT)
+                       ipoib_dev_addr_changed_valid(priv);
                ipoib_dbg(priv, "Not flushing - IPOIB_FLAG_ADMIN_UP not set.\n");
                return;
        }
@@ -1081,7 +1185,8 @@ static void __ipoib_ib_dev_flush(struct ipoib_dev_priv *priv,
        if (test_bit(IPOIB_FLAG_ADMIN_UP, &priv->flags)) {
                if (level >= IPOIB_FLUSH_NORMAL)
                        ipoib_ib_dev_up(dev);
-               ipoib_mcast_restart_task(&priv->restart_task);
+               if (ipoib_dev_addr_changed_valid(priv))
+                       ipoib_mcast_restart_task(&priv->restart_task);
        }
 }