Added IPoIB backport for RHEL7.6
authorVladimir Sokolovsky <vlad@mellanox.com>
Wed, 28 Aug 2019 16:41:26 +0000 (11:41 -0500)
committerVladimir Sokolovsky <vlad@mellanox.com>
Wed, 28 Aug 2019 16:41:45 +0000 (11:41 -0500)
Signed-off-by: Vladimir Sokolovsky <vlad@mellanox.com>
patches/0005-BACKPORT-ipoib.patch [new file with mode: 0644]

diff --git a/patches/0005-BACKPORT-ipoib.patch b/patches/0005-BACKPORT-ipoib.patch
new file mode 100644 (file)
index 0000000..466b25f
--- /dev/null
@@ -0,0 +1,535 @@
+From: Vladimir Sokolovsky <vlad@mellanox.com>
+Subject: [PATCH] BACKPORT: ipoib
+
+Signed-off-by: Vladimir Sokolovsky <vlad@mellanox.com>
+---
+ drivers/infiniband/ulp/ipoib/ipoib_main.c    | 169 ++++++++++++++++++-
+ drivers/infiniband/ulp/ipoib/ipoib_netlink.c |  23 ++-
+ drivers/infiniband/ulp/ipoib/ipoib_vlan.c    |   4 +
+ 3 files changed, 193 insertions(+), 3 deletions(-)
+
+diff --git a/drivers/infiniband/ulp/ipoib/ipoib_main.c b/drivers/infiniband/ulp/ipoib/ipoib_main.c
+index xxxxxxx..xxxxxxx 100644
+--- a/drivers/infiniband/ulp/ipoib/ipoib_main.c
++++ b/drivers/infiniband/ulp/ipoib/ipoib_main.c
+@@ -112,8 +112,12 @@ static struct ib_client ipoib_client = {
+ static int ipoib_netdev_event(struct notifier_block *this,
+                             unsigned long event, void *ptr)
+ {
++#ifdef HAVE_NETDEV_NOTIFIER_INFO
+       struct netdev_notifier_info *ni = ptr;
+       struct net_device *dev = ni->dev;
++#else
++      struct net_device *dev = ptr;
++#endif
+       if (dev->netdev_ops->ndo_open != ipoib_open)
+               return NOTIFY_DONE;
+@@ -167,7 +171,11 @@ int ipoib_open(struct net_device *dev)
+                       if (flags & IFF_UP)
+                               continue;
++#ifdef HAVE_DEV_CHANGE_FLAGS_HAS_3_PARAMS
+                       dev_change_flags(cpriv->dev, flags | IFF_UP, NULL);
++#else
++                      dev_change_flags(cpriv->dev, flags | IFF_UP);
++#endif
+               }
+               up_read(&priv->vlan_rwsem);
+       }
+@@ -207,7 +215,11 @@ static int ipoib_stop(struct net_device *dev)
+                       if (!(flags & IFF_UP))
+                               continue;
++#ifdef HAVE_DEV_CHANGE_FLAGS_HAS_3_PARAMS
+                       dev_change_flags(cpriv->dev, flags & ~IFF_UP, NULL);
++#else
++                      dev_change_flags(cpriv->dev, flags & ~IFF_UP);
++#endif
+               }
+               up_read(&priv->vlan_rwsem);
+       }
+@@ -254,14 +266,21 @@ static int ipoib_change_mtu(struct net_device *dev, int new_mtu)
+                               "link layer MTU - 4 (%u)\n", priv->mcast_mtu);
+       new_mtu = min(priv->mcast_mtu, priv->admin_mtu);
+-
++#ifdef HAVE_NDO_CHANGE_MTU_EXTENDED
++      if (priv->rn_ops->extended.ndo_change_mtu) {
++#else
+       if (priv->rn_ops->ndo_change_mtu) {
++#endif
+               bool carrier_status = netif_carrier_ok(dev);
+               netif_carrier_off(dev);
+               /* notify lower level on the real mtu */
++#ifdef HAVE_NDO_CHANGE_MTU_EXTENDED
++              ret = priv->rn_ops->extended.ndo_change_mtu(dev, new_mtu);
++#else
+               ret = priv->rn_ops->ndo_change_mtu(dev, new_mtu);
++#endif
+               if (carrier_status)
+                       netif_carrier_on(dev);
+@@ -272,15 +291,42 @@ static int ipoib_change_mtu(struct net_device *dev, int new_mtu)
+       return ret;
+ }
++#ifdef HAVE_NDO_GET_STATS64_RET_VOID
+ static void ipoib_get_stats(struct net_device *dev,
+                           struct rtnl_link_stats64 *stats)
++#elif defined(HAVE_NDO_GET_STATS64)
++static struct rtnl_link_stats64 * ipoib_get_stats(struct net_device *dev,
++                                                struct rtnl_link_stats64 *stats)
++#else
++static struct net_device_stats *  ipoib_get_stats(struct net_device *dev)
++#endif
+ {
+       struct ipoib_dev_priv *priv = ipoib_priv(dev);
++#if !defined(HAVE_NDO_GET_STATS64) && !defined(HAVE_NDO_GET_STATS64_RET_VOID)
++      struct net_device_stats *stats = &priv->ret_stats;
++#endif
++#ifdef HAVE_NDO_GET_STATS64_RET_VOID
+       if (priv->rn_ops->ndo_get_stats64)
+               priv->rn_ops->ndo_get_stats64(dev, stats);
+       else
+               netdev_stats_to_stats64(stats, &dev->stats);
++#elif defined(HAVE_NDO_GET_STATS64)
++      if (priv->rn_ops->ndo_get_stats64) {
++              return priv->rn_ops->ndo_get_stats64(dev, stats);
++      } else {
++              netdev_stats_to_stats64(stats,
++                                      &dev->stats);
++      return stats;
++      }
++#else
++      if (priv->rn_ops->ndo_get_stats) {
++              return priv->rn_ops->ndo_get_stats(dev);
++              } else {
++                      memcpy(stats, &dev->stats, sizeof(priv->ret_stats));
++                      return stats;
++              }
++#endif
+ }
+ /* Called with an RCU read lock taken */
+@@ -299,9 +345,21 @@ static bool ipoib_is_dev_match_addr_rcu(const struct sockaddr *addr,
+               if (!in_dev)
+                       return false;
++#ifdef HAVE_INET_CONFIRM_ADDR_EXPORTED
++#ifdef HAVE_INET_CONFIRM_ADDR_5_PARAMS
+               ret_addr = inet_confirm_addr(net, in_dev, 0,
+                                            addr_in->sin_addr.s_addr,
+                                            RT_SCOPE_HOST);
++#else
++              ret_addr = inet_confirm_addr(in_dev, 0,
++                                           addr_in->sin_addr.s_addr,
++                                           RT_SCOPE_HOST);
++#endif
++#else
++              ret_addr = confirm_addr_indev(in_dev, 0,
++                                            addr_in->sin_addr.s_addr,
++                                            RT_SCOPE_HOST);
++#endif
+               in_dev_put(in_dev);
+               if (ret_addr)
+                       return true;
+@@ -346,6 +404,7 @@ struct ipoib_walk_data {
+       struct net_device *result;
+ };
++#ifdef HAVE_NETDEV_WALK_ALL_UPPER_DEV_RCU
+ static int ipoib_upper_walk(struct net_device *upper, void *_data)
+ {
+       struct ipoib_walk_data *data = _data;
+@@ -359,6 +418,7 @@ static int ipoib_upper_walk(struct net_device *upper, void *_data)
+       return ret;
+ }
++#endif
+ /**
+  * Find a net_device matching the given address, which is an upper device of
+@@ -375,6 +435,9 @@ static struct net_device *ipoib_get_net_dev_match_addr(
+       struct ipoib_walk_data data = {
+               .addr = addr,
+       };
++#ifndef HAVE_NETDEV_WALK_ALL_UPPER_DEV_RCU
++      struct net_device *upper;
++#endif
+       rcu_read_lock();
+       if (ipoib_is_dev_match_addr_rcu(addr, dev)) {
+@@ -383,7 +446,18 @@ static struct net_device *ipoib_get_net_dev_match_addr(
+               goto out;
+       }
++#ifdef HAVE_NETDEV_WALK_ALL_UPPER_DEV_RCU
+       netdev_walk_all_upper_dev_rcu(dev, ipoib_upper_walk, &data);
++#else
++      for_each_netdev(&init_net, upper) {
++                if (ipoib_is_dev_match_addr_rcu(addr, upper)) {
++                        dev_hold(upper);
++                        data.result = upper;
++                        break;
++                }
++        }
++#endif
++
+ out:
+       rcu_read_unlock();
+       return data.result;
+@@ -704,7 +778,7 @@ static void push_pseudo_header(struct sk_buff *skb, const char *daddr)
+ {
+       struct ipoib_pseudo_header *phdr;
+-      phdr = skb_push(skb, sizeof(*phdr));
++      phdr = (struct ipoib_pseudo_header *)skb_push(skb, sizeof(*phdr));
+       memcpy(phdr->hwaddr, daddr, INFINIBAND_ALEN);
+ }
+@@ -1823,7 +1897,11 @@ static void ipoib_parent_unregister_pre(struct net_device *ndev)
+        * running ensures the it will not add more work.
+        */
+       rtnl_lock();
++#ifdef HAVE_DEV_CHANGE_FLAGS_HAS_3_PARAMS
+       dev_change_flags(priv->dev, priv->dev->flags & ~IFF_UP, NULL);
++#else
++      dev_change_flags(priv->dev, priv->dev->flags & ~IFF_UP);
++#endif
+       rtnl_unlock();
+       /* ipoib_event() cannot be running once this returns */
+@@ -1841,12 +1919,19 @@ static void ipoib_set_dev_features(struct ipoib_dev_priv *priv)
+       priv->hca_caps = priv->ca->attrs.device_cap_flags;
+       if (priv->hca_caps & IB_DEVICE_UD_IP_CSUM) {
++#ifdef HAVE_NETDEV_HW_FEATURES
+               priv->dev->hw_features |= NETIF_F_IP_CSUM | NETIF_F_RXCSUM;
+               if (priv->hca_caps & IB_DEVICE_UD_TSO)
+                       priv->dev->hw_features |= NETIF_F_TSO;
+               priv->dev->features |= priv->dev->hw_features;
++#else
++              priv->dev->features |= NETIF_F_IP_CSUM | NETIF_F_RXCSUM;
++
++              if (priv->hca_caps & IB_DEVICE_UD_TSO)
++                      priv->dev->features |= NETIF_F_TSO;
++#endif
+       }
+ }
+@@ -1915,7 +2000,9 @@ static int ipoib_ndo_init(struct net_device *ndev)
+       /* MTU will be reset when mcast join happens */
+       ndev->mtu = IPOIB_UD_MTU(priv->max_ib_mtu);
+       priv->mcast_mtu = priv->admin_mtu = ndev->mtu;
++#ifdef HAVE_NET_DEVICE_MIN_MAX_MTU
+       ndev->max_mtu = IPOIB_CM_MTU;
++#endif
+       ndev->neigh_priv_len = sizeof(struct ipoib_neigh);
+@@ -1961,6 +2048,7 @@ static void ipoib_ndo_uninit(struct net_device *dev)
+        * ipoib_remove_one guarantees the children are removed before the
+        * parent, and that is the only place where a parent can be removed.
+        */
++#ifdef HAVE_NET_DEVICE_NEEDS_FREE_NETDEV
+       WARN_ON(!list_empty(&priv->child_intfs));
+       if (priv->parent) {
+@@ -1970,6 +2058,7 @@ static void ipoib_ndo_uninit(struct net_device *dev)
+               list_del(&priv->list);
+               up_write(&ppriv->vlan_rwsem);
+       }
++#endif
+       ipoib_neigh_hash_uninit(dev);
+@@ -2009,6 +2098,7 @@ static int ipoib_get_vf_config(struct net_device *dev, int vf,
+       return 0;
+ }
++#ifdef HAVE_NDO_SET_VF_GUID
+ static int ipoib_set_vf_guid(struct net_device *dev, int vf, u64 guid, int type)
+ {
+       struct ipoib_dev_priv *priv = ipoib_priv(dev);
+@@ -2018,6 +2108,7 @@ static int ipoib_set_vf_guid(struct net_device *dev, int vf, u64 guid, int type)
+       return ib_set_vf_guid(priv->ca, vf, priv->port, guid, type);
+ }
++#endif
+ static int ipoib_get_vf_stats(struct net_device *dev, int vf,
+                             struct ifla_vf_stats *vf_stats)
+@@ -2036,7 +2127,11 @@ static const struct net_device_ops ipoib_netdev_ops_pf = {
+       .ndo_uninit              = ipoib_ndo_uninit,
+       .ndo_open                = ipoib_open,
+       .ndo_stop                = ipoib_stop,
++#ifdef HAVE_NDO_CHANGE_MTU_EXTENDED
++      .extended.ndo_change_mtu = ipoib_change_mtu,
++#else
+       .ndo_change_mtu          = ipoib_change_mtu,
++#endif
+       .ndo_fix_features        = ipoib_fix_features,
+       .ndo_start_xmit          = ipoib_start_xmit,
+       .ndo_tx_timeout          = ipoib_timeout,
+@@ -2045,10 +2140,19 @@ static const struct net_device_ops ipoib_netdev_ops_pf = {
+       .ndo_set_vf_link_state   = ipoib_set_vf_link_state,
+       .ndo_get_vf_config       = ipoib_get_vf_config,
+       .ndo_get_vf_stats        = ipoib_get_vf_stats,
++#ifdef HAVE_NDO_SET_VF_GUID
+       .ndo_set_vf_guid         = ipoib_set_vf_guid,
++#endif
+       .ndo_set_mac_address     = ipoib_set_mac,
++#if defined(HAVE_NDO_GET_STATS64) || defined(HAVE_NDO_GET_STATS64_RET_VOID)
+       .ndo_get_stats64         = ipoib_get_stats,
++#else
++      .ndo_get_stats           = ipoib_get_stats,
++#endif
+       .ndo_do_ioctl            = ipoib_ioctl,
++#ifdef HAVE_NET_DEVICE_OPS_EXTENDED
++      .ndo_size = sizeof(struct net_device_ops),
++#endif
+ };
+ static const struct net_device_ops ipoib_netdev_ops_vf = {
+@@ -2056,14 +2160,27 @@ static const struct net_device_ops ipoib_netdev_ops_vf = {
+       .ndo_uninit              = ipoib_ndo_uninit,
+       .ndo_open                = ipoib_open,
+       .ndo_stop                = ipoib_stop,
++#ifdef HAVE_NDO_CHANGE_MTU_RH74
++      .ndo_change_mtu_rh74     = ipoib_change_mtu,
++#elif defined(HAVE_NDO_CHANGE_MTU_EXTENDED)
++      .extended.ndo_change_mtu = ipoib_change_mtu,
++#else
+       .ndo_change_mtu          = ipoib_change_mtu,
++#endif
+       .ndo_fix_features        = ipoib_fix_features,
+       .ndo_start_xmit          = ipoib_start_xmit,
+       .ndo_tx_timeout          = ipoib_timeout,
+       .ndo_set_rx_mode         = ipoib_set_mcast_list,
+       .ndo_get_iflink          = ipoib_get_iflink,
++#if defined(HAVE_NDO_GET_STATS64) || defined(HAVE_NDO_GET_STATS64_RET_VOID)
+       .ndo_get_stats64         = ipoib_get_stats,
++#else
++      .ndo_get_stats           = ipoib_get_stats,
++#endif
+       .ndo_do_ioctl            = ipoib_ioctl,
++#ifdef HAVE_NET_DEVICE_OPS_EXTENDED
++      .ndo_size = sizeof(struct net_device_ops),
++#endif
+ };
+ void ipoib_setup_common(struct net_device *dev)
+@@ -2091,7 +2208,9 @@ void ipoib_setup_common(struct net_device *dev)
+        * consistently to unify all the various unregister paths, including
+        * those connected to rtnl_link_ops which require it.
+        */
++#ifdef HAVE_NET_DEVICE_NEEDS_FREE_NETDEV
+       dev->needs_free_netdev = true;
++#endif
+ }
+ static void ipoib_build_priv(struct net_device *dev)
+@@ -2131,12 +2250,22 @@ static struct net_device *ipoib_alloc_netdev(struct ib_device *hca, u8 port,
+       struct net_device *dev;
+       dev = rdma_alloc_netdev(hca, port, RDMA_NETDEV_IPOIB, name,
++#ifdef NET_NAME_UNKNOWN
+                               NET_NAME_UNKNOWN, ipoib_setup_common);
++#else
++                              0, ipoib_setup_common);
++#endif
+       if (!IS_ERR(dev) || PTR_ERR(dev) != -EOPNOTSUPP)
+               return dev;
++#ifdef HAVE_ALLOC_NETDEV_4P
+       dev = alloc_netdev(sizeof(struct rdma_netdev), name, NET_NAME_UNKNOWN,
+                          ipoib_setup_common);
++#else
++      dev = alloc_netdev_mqs((int)sizeof(struct rdma_netdev), name,
++                             NET_NAME_UNKNOWN, ipoib_setup_common,
++                             1, 1);
++#endif
+       if (!dev)
+               return ERR_PTR(-ENOMEM);
+       return dev;
+@@ -2157,7 +2286,11 @@ int ipoib_intf_init(struct ib_device *hca, u8 port, const char *name,
+       priv->port = port;
+       rc = rdma_init_netdev(hca, port, RDMA_NETDEV_IPOIB, name,
++#ifdef NET_NAME_UNKNOWN
+                             NET_NAME_UNKNOWN, ipoib_setup_common, dev);
++#else
++                            0, ipoib_setup_common, dev);
++#endif
+       if (rc) {
+               if (rc != -EOPNOTSUPP)
+                       goto out;
+@@ -2182,8 +2315,10 @@ int ipoib_intf_init(struct ib_device *hca, u8 port, const char *name,
+        * being set, so we force it to NULL here and handle manually until it
+        * is safe to turn on.
+        */
++#ifdef HAVE_NET_DEVICE_NEEDS_FREE_NETDEV
+       priv->next_priv_destructor = dev->priv_destructor;
+       dev->priv_destructor = NULL;
++#endif
+       ipoib_build_priv(dev);
+@@ -2222,6 +2357,7 @@ void ipoib_intf_free(struct net_device *dev)
+ {
+       struct ipoib_dev_priv *priv = ipoib_priv(dev);
+       struct rdma_netdev *rn = netdev_priv(dev);
++#ifdef HAVE_NET_DEVICE_NEEDS_FREE_NETDEV
+       dev->priv_destructor = priv->next_priv_destructor;
+       if (dev->priv_destructor)
+@@ -2232,6 +2368,7 @@ void ipoib_intf_free(struct net_device *dev)
+        * attempt to call priv_destructor twice, prevent that from happening.
+        */
+       dev->priv_destructor = NULL;
++#endif
+       /* unregister/destroy is very complicated. Make bugs more obvious. */
+       rn->clnt_priv = NULL;
+@@ -2485,7 +2622,9 @@ static struct net_device *ipoib_add_port(const char *format,
+        * ipoib_parent_unregister_pre(). Instead handle it manually and only
+        * enter priv_destructor mode once we are completely registered.
+        */
++#ifdef HAVE_NET_DEVICE_NEEDS_FREE_NETDEV
+       ndev->priv_destructor = ipoib_intf_free;
++#endif
+       if (ipoib_intercept_dev_id_attr(ndev))
+               goto sysfs_failed;
+@@ -2557,11 +2696,37 @@ static void ipoib_remove_one(struct ib_device *device, void *client_data)
+               list_for_each_entry_safe(cpriv, tcpriv, &priv->child_intfs,
+                                        list)
++#ifdef HAVE_NET_DEVICE_HAS_CLOSE_LIST
+                       unregister_netdevice_queue(cpriv->dev, &head);
+               unregister_netdevice_queue(priv->dev, &head);
++#else
++                      unregister_netdevice(cpriv->dev);
++              unregister_netdevice(priv->dev);
++#endif
++#ifdef HAVE_NET_DEVICE_HAS_CLOSE_LIST
+               unregister_netdevice_many(&head);
++#endif
+               rtnl_unlock();
++#ifndef HAVE_NET_DEVICE_NEEDS_FREE_NETDEV
++              /* Free parent resources after rtnl_unlock to
++               * avoid ipoib_get_iflink panic.
++               */
++              list_for_each_entry_safe(cpriv, tcpriv, &priv->child_intfs,
++                                       list)
++              {
++                      down_write(&priv->vlan_rwsem);
++                      list_del(&cpriv->list);
++                      up_write(&priv->vlan_rwsem);
++                      rdma_uninit_netdev(cpriv->ca, cpriv->dev, cpriv->port,
++                                         RDMA_NETDEV_IPOIB);
++                      ipoib_intf_free(cpriv->dev);
++              }
++
++              rdma_uninit_netdev(priv->ca, priv->dev, priv->port,
++                                 RDMA_NETDEV_IPOIB);
++              ipoib_intf_free(priv->dev);
++#endif
+       }
+       kfree(dev_list);
+diff --git a/drivers/infiniband/ulp/ipoib/ipoib_netlink.c b/drivers/infiniband/ulp/ipoib/ipoib_netlink.c
+index xxxxxxx..xxxxxxx 100644
+--- a/drivers/infiniband/ulp/ipoib/ipoib_netlink.c
++++ b/drivers/infiniband/ulp/ipoib/ipoib_netlink.c
+@@ -64,9 +64,14 @@ nla_put_failure:
+       return -EMSGSIZE;
+ }
++#if defined(HAVE_RTNL_LINK_OPS_NEWLINK_5_PARAMS)
+ static int ipoib_changelink(struct net_device *dev, struct nlattr *tb[],
+                           struct nlattr *data[],
+                           struct netlink_ext_ack *extack)
++#else
++static int ipoib_changelink(struct net_device *dev, struct nlattr *tb[],
++                          struct nlattr *data[])
++#endif
+ {
+       u16 mode, umcast;
+       int ret = 0;
+@@ -93,9 +98,17 @@ out_err:
+       return ret;
+ }
++#if defined(HAVE_RTNL_LINK_OPS_NEWLINK_5_PARAMS)
+ static int ipoib_new_child_link(struct net *src_net, struct net_device *dev,
+                               struct nlattr *tb[], struct nlattr *data[],
+                               struct netlink_ext_ack *extack)
++#elif defined(HAVE_RTNL_LINK_OPS_NEWLINK_4_PARAMS)
++static int ipoib_new_child_link(struct net *src_net, struct net_device *dev,
++                              struct nlattr *tb[], struct nlattr *data[])
++#else
++static int ipoib_new_child_link(struct net_device *dev,
++                              struct nlattr *tb[], struct nlattr *data[])
++#endif
+ {
+       struct net_device *pdev;
+       struct ipoib_dev_priv *ppriv;
+@@ -104,8 +117,12 @@ static int ipoib_new_child_link(struct net *src_net, struct net_device *dev,
+       if (!tb[IFLA_LINK])
+               return -EINVAL;
+-
++#ifdef HAVE_RTNL_LINK_OPS_NEWLINK_4_PARAMS
+       pdev = __dev_get_by_index(src_net, nla_get_u32(tb[IFLA_LINK]));
++#else
++      pdev = __dev_get_by_index(dev_net(dev), nla_get_u32(tb[IFLA_LINK]));
++#endif
++
+       if (!pdev || pdev->type != ARPHRD_INFINIBAND)
+               return -ENODEV;
+@@ -134,7 +151,11 @@ static int ipoib_new_child_link(struct net *src_net, struct net_device *dev,
+               return err;
+       if (data) {
++#if defined(HAVE_RTNL_LINK_OPS_NEWLINK_5_PARAMS)
+               err = ipoib_changelink(dev, tb, data, extack);
++#else
++              err = ipoib_changelink(dev, tb, data);
++#endif
+               if (err) {
+                       unregister_netdevice(dev);
+                       return err;
+diff --git a/drivers/infiniband/ulp/ipoib/ipoib_vlan.c b/drivers/infiniband/ulp/ipoib/ipoib_vlan.c
+index xxxxxxx..xxxxxxx 100644
+--- a/drivers/infiniband/ulp/ipoib/ipoib_vlan.c
++++ b/drivers/infiniband/ulp/ipoib/ipoib_vlan.c
+@@ -104,7 +104,9 @@ int __ipoib_vlan_add(struct ipoib_dev_priv *ppriv, struct ipoib_dev_priv *priv,
+        * We do not need to touch priv if register_netdevice fails, so just
+        * always use this flow.
+        */
++#ifdef HAVE_NET_DEVICE_NEEDS_FREE_NETDEV
+       ndev->priv_destructor = ipoib_intf_free;
++#endif
+       /*
+        * Racing with unregister of the parent must be prevented by the
+@@ -157,8 +159,10 @@ sysfs_failed:
+       return -ENOMEM;
+ out_early:
++#ifdef HAVE_NET_DEVICE_NEEDS_FREE_NETDEV
+       if (ndev->priv_destructor)
+               ndev->priv_destructor(ndev);
++#endif
+       return result;
+ }