brcmfmac: add neighbor discovery offload ip address table configuration
authorFranky Lin <frankyl@broadcom.com>
Wed, 17 Feb 2016 10:26:55 +0000 (11:26 +0100)
committerKalle Valo <kvalo@codeaurora.org>
Mon, 7 Mar 2016 12:14:48 +0000 (14:14 +0200)
Configure ipv6 address for neighbor discovery offload ip table in
firmware obtained through ipv6 address notification callback.

Reviewed-by: Hante Meuleman <meuleman@broadcom.com>
Reviewed-by: Pieter-Paul Giesberts <pieterpg@broadcom.com>
Signed-off-by: Franky Lin <frankyl@broadcom.com>
Signed-off-by: Arend van Spriel <arend@broadcom.com>
Signed-off-by: Kalle Valo <kvalo@codeaurora.org>
drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c
drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.c
drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.h

index 6cc8fdc..71f1fdf 100644 (file)
@@ -456,7 +456,7 @@ send_key_to_dongle(struct brcmf_if *ifp, struct brcmf_wsec_key *key)
 }
 
 static s32
-brcmf_configure_arp_offload(struct brcmf_if *ifp, bool enable)
+brcmf_configure_arp_nd_offload(struct brcmf_if *ifp, bool enable)
 {
        s32 err;
        u32 mode;
@@ -484,6 +484,15 @@ brcmf_configure_arp_offload(struct brcmf_if *ifp, bool enable)
                                  enable, mode);
        }
 
+       err = brcmf_fil_iovar_int_set(ifp, "ndoe", enable);
+       if (err) {
+               brcmf_dbg(TRACE, "failed to configure (%d) ND offload err = %d\n",
+                         enable, err);
+               err = 0;
+       } else
+               brcmf_dbg(TRACE, "successfully configured (%d) ND offload to 0x%x\n",
+                         enable, mode);
+
        return err;
 }
 
@@ -3543,7 +3552,7 @@ static s32 brcmf_cfg80211_resume(struct wiphy *wiphy)
                brcmf_report_wowl_wakeind(wiphy, ifp);
                brcmf_fil_iovar_int_set(ifp, "wowl_clear", 0);
                brcmf_config_wowl_pattern(ifp, "clr", NULL, 0, NULL, 0);
-               brcmf_configure_arp_offload(ifp, true);
+               brcmf_configure_arp_nd_offload(ifp, true);
                brcmf_fil_cmd_int_set(ifp, BRCMF_C_SET_PM,
                                      cfg->wowl.pre_pmmode);
                cfg->wowl.active = false;
@@ -3567,7 +3576,7 @@ static void brcmf_configure_wowl(struct brcmf_cfg80211_info *cfg,
 
        brcmf_dbg(TRACE, "Suspend, wowl config.\n");
 
-       brcmf_configure_arp_offload(ifp, false);
+       brcmf_configure_arp_nd_offload(ifp, false);
        brcmf_fil_cmd_int_get(ifp, BRCMF_C_GET_PM, &cfg->wowl.pre_pmmode);
        brcmf_fil_cmd_int_set(ifp, BRCMF_C_SET_PM, PM_MAX);
 
@@ -4336,7 +4345,7 @@ brcmf_cfg80211_start_ap(struct wiphy *wiphy, struct net_device *ndev,
 
        if (!mbss) {
                brcmf_set_mpc(ifp, 0);
-               brcmf_configure_arp_offload(ifp, false);
+               brcmf_configure_arp_nd_offload(ifp, false);
        }
 
        /* find the RSN_IE */
@@ -4482,7 +4491,7 @@ brcmf_cfg80211_start_ap(struct wiphy *wiphy, struct net_device *ndev,
 exit:
        if ((err) && (!mbss)) {
                brcmf_set_mpc(ifp, 1);
-               brcmf_configure_arp_offload(ifp, true);
+               brcmf_configure_arp_nd_offload(ifp, true);
        }
        return err;
 }
@@ -4540,7 +4549,7 @@ static int brcmf_cfg80211_stop_ap(struct wiphy *wiphy, struct net_device *ndev)
                        brcmf_err("bss_enable config failed %d\n", err);
        }
        brcmf_set_mpc(ifp, 1);
-       brcmf_configure_arp_offload(ifp, true);
+       brcmf_configure_arp_nd_offload(ifp, true);
        clear_bit(BRCMF_VIF_STATUS_AP_CREATED, &ifp->vif->sme_state);
        brcmf_net_setcarrier(ifp, false);
 
@@ -6287,7 +6296,7 @@ static s32 brcmf_config_dongle(struct brcmf_cfg80211_info *cfg)
        if (err)
                goto default_conf_out;
 
-       brcmf_configure_arp_offload(ifp, true);
+       brcmf_configure_arp_nd_offload(ifp, true);
 
        cfg->dongle_up = true;
 default_conf_out:
index ed9998b..9507cc9 100644 (file)
@@ -20,6 +20,8 @@
 #include <linux/inetdevice.h>
 #include <net/cfg80211.h>
 #include <net/rtnetlink.h>
+#include <net/addrconf.h>
+#include <net/ipv6.h>
 #include <brcmu_utils.h>
 #include <brcmu_wifi.h>
 
@@ -172,6 +174,35 @@ _brcmf_set_mac_address(struct work_struct *work)
        }
 }
 
+#if IS_ENABLED(CONFIG_IPV6)
+static void _brcmf_update_ndtable(struct work_struct *work)
+{
+       struct brcmf_if *ifp;
+       int i, ret;
+
+       ifp = container_of(work, struct brcmf_if, ndoffload_work);
+
+       /* clear the table in firmware */
+       ret = brcmf_fil_iovar_data_set(ifp, "nd_hostip_clear", NULL, 0);
+       if (ret) {
+               brcmf_dbg(TRACE, "fail to clear nd ip table err:%d\n", ret);
+               return;
+       }
+
+       for (i = 0; i < ifp->ipv6addr_idx; i++) {
+               ret = brcmf_fil_iovar_data_set(ifp, "nd_hostip",
+                                              &ifp->ipv6_addr_tbl[i],
+                                              sizeof(struct in6_addr));
+               if (ret)
+                       brcmf_err("add nd ip err %d\n", ret);
+       }
+}
+#else
+static void _brcmf_update_ndtable(struct work_struct *work)
+{
+}
+#endif
+
 static int brcmf_netdev_set_mac_address(struct net_device *ndev, void *addr)
 {
        struct brcmf_if *ifp = netdev_priv(ndev);
@@ -685,6 +716,7 @@ int brcmf_net_attach(struct brcmf_if *ifp, bool rtnl_locked)
 
        INIT_WORK(&ifp->setmacaddr_work, _brcmf_set_mac_address);
        INIT_WORK(&ifp->multicast_work, _brcmf_set_multicast_list);
+       INIT_WORK(&ifp->ndoffload_work, _brcmf_update_ndtable);
 
        if (rtnl_locked)
                err = register_netdevice(ndev);
@@ -884,6 +916,7 @@ static void brcmf_del_if(struct brcmf_pub *drvr, s32 bsscfgidx)
                if (ifp->ndev->netdev_ops == &brcmf_netdev_ops_pri) {
                        cancel_work_sync(&ifp->setmacaddr_work);
                        cancel_work_sync(&ifp->multicast_work);
+                       cancel_work_sync(&ifp->ndoffload_work);
                }
                brcmf_net_detach(ifp->ndev);
        } else {
@@ -1025,6 +1058,56 @@ static int brcmf_inetaddr_changed(struct notifier_block *nb,
 }
 #endif
 
+#if IS_ENABLED(CONFIG_IPV6)
+static int brcmf_inet6addr_changed(struct notifier_block *nb,
+                                  unsigned long action, void *data)
+{
+       struct brcmf_pub *drvr = container_of(nb, struct brcmf_pub,
+                                             inet6addr_notifier);
+       struct inet6_ifaddr *ifa = data;
+       struct brcmf_if *ifp;
+       int i;
+       struct in6_addr *table;
+
+       /* Only handle primary interface */
+       ifp = drvr->iflist[0];
+       if (!ifp)
+               return NOTIFY_DONE;
+       if (ifp->ndev != ifa->idev->dev)
+               return NOTIFY_DONE;
+
+       table = ifp->ipv6_addr_tbl;
+       for (i = 0; i < NDOL_MAX_ENTRIES; i++)
+               if (ipv6_addr_equal(&ifa->addr, &table[i]))
+                       break;
+
+       switch (action) {
+       case NETDEV_UP:
+               if (i == NDOL_MAX_ENTRIES) {
+                       if (ifp->ipv6addr_idx < NDOL_MAX_ENTRIES) {
+                               table[ifp->ipv6addr_idx++] = ifa->addr;
+                       } else {
+                               for (i = 0; i < NDOL_MAX_ENTRIES - 1; i++)
+                                       table[i] = table[i + 1];
+                               table[NDOL_MAX_ENTRIES - 1] = ifa->addr;
+                       }
+               }
+               break;
+       case NETDEV_DOWN:
+               if (i < NDOL_MAX_ENTRIES)
+                       for (; i < ifp->ipv6addr_idx; i++)
+                               table[i] = table[i + 1];
+               break;
+       default:
+               break;
+       }
+
+       schedule_work(&ifp->ndoffload_work);
+
+       return NOTIFY_OK;
+}
+#endif
+
 int brcmf_attach(struct device *dev)
 {
        struct brcmf_pub *drvr = NULL;
@@ -1164,30 +1247,41 @@ int brcmf_bus_start(struct device *dev)
 #ifdef CONFIG_INET
        drvr->inetaddr_notifier.notifier_call = brcmf_inetaddr_changed;
        ret = register_inetaddr_notifier(&drvr->inetaddr_notifier);
+       if (ret)
+               goto fail;
+
+#if IS_ENABLED(CONFIG_IPV6)
+       drvr->inet6addr_notifier.notifier_call = brcmf_inet6addr_changed;
+       ret = register_inet6addr_notifier(&drvr->inet6addr_notifier);
+       if (ret) {
+               unregister_inetaddr_notifier(&drvr->inetaddr_notifier);
+               goto fail;
+       }
 #endif
+#endif /* CONFIG_INET */
+
+       return 0;
 
 fail:
-       if (ret < 0) {
-               brcmf_err("failed: %d\n", ret);
-               if (drvr->config) {
-                       brcmf_cfg80211_detach(drvr->config);
-                       drvr->config = NULL;
-               }
-               if (drvr->fws) {
-                       brcmf_fws_del_interface(ifp);
-                       brcmf_fws_deinit(drvr);
-               }
-               if (ifp)
-                       brcmf_net_detach(ifp->ndev);
-               if (p2p_ifp)
-                       brcmf_net_detach(p2p_ifp->ndev);
-               drvr->iflist[0] = NULL;
-               drvr->iflist[1] = NULL;
-               if (brcmf_ignoring_probe_fail(drvr))
-                       ret = 0;
-               return ret;
+       brcmf_err("failed: %d\n", ret);
+       if (drvr->config) {
+               brcmf_cfg80211_detach(drvr->config);
+               drvr->config = NULL;
        }
-       return 0;
+       if (drvr->fws) {
+               brcmf_fws_del_interface(ifp);
+               brcmf_fws_deinit(drvr);
+       }
+       if (ifp)
+               brcmf_net_detach(ifp->ndev);
+       if (p2p_ifp)
+               brcmf_net_detach(p2p_ifp->ndev);
+       drvr->iflist[0] = NULL;
+       drvr->iflist[1] = NULL;
+       if (brcmf_ignoring_probe_fail(drvr))
+               ret = 0;
+
+       return ret;
 }
 
 void brcmf_bus_add_txhdrlen(struct device *dev, uint len)
@@ -1237,6 +1331,10 @@ void brcmf_detach(struct device *dev)
        unregister_inetaddr_notifier(&drvr->inetaddr_notifier);
 #endif
 
+#if IS_ENABLED(CONFIG_IPV6)
+       unregister_inet6addr_notifier(&drvr->inet6addr_notifier);
+#endif
+
        /* stop firmware event handling */
        brcmf_fweh_detach(drvr);
        if (drvr->config)
index 8f39435..500dac6 100644 (file)
@@ -48,6 +48,8 @@
  */
 #define BRCMF_DRIVER_FIRMWARE_VERSION_LEN      32
 
+#define NDOL_MAX_ENTRIES       8
+
 /**
  * struct brcmf_ampdu_rx_reorder - AMPDU receive reorder info
  *
@@ -143,6 +145,7 @@ struct brcmf_pub {
 #endif
 
        struct notifier_block inetaddr_notifier;
+       struct notifier_block inet6addr_notifier;
        struct brcmf_mp_device *settings;
 };
 
@@ -175,6 +178,7 @@ enum brcmf_netif_stop_reason {
  * @stats: interface specific network statistics.
  * @setmacaddr_work: worker object for setting mac address.
  * @multicast_work: worker object for multicast provisioning.
+ * @ndoffload_work: worker object for neighbor discovery offload configuration.
  * @fws_desc: interface specific firmware-signalling descriptor.
  * @ifidx: interface index in device firmware.
  * @bsscfgidx: index of bss associated with this interface.
@@ -191,6 +195,7 @@ struct brcmf_if {
        struct net_device_stats stats;
        struct work_struct setmacaddr_work;
        struct work_struct multicast_work;
+       struct work_struct ndoffload_work;
        struct brcmf_fws_mac_descriptor *fws_desc;
        int ifidx;
        s32 bsscfgidx;
@@ -199,6 +204,8 @@ struct brcmf_if {
        spinlock_t netif_stop_lock;
        atomic_t pend_8021x_cnt;
        wait_queue_head_t pend_8021x_wait;
+       struct in6_addr ipv6_addr_tbl[NDOL_MAX_ENTRIES];
+       u8 ipv6addr_idx;
 };
 
 struct brcmf_skb_reorder_data {