mt76: mt7921: add ipv6 NS offload support
authorDeren Wu <deren.wu@mediatek.com>
Wed, 11 May 2022 23:06:36 +0000 (07:06 +0800)
committerFelix Fietkau <nbd@nbd.name>
Fri, 13 May 2022 07:39:35 +0000 (09:39 +0200)
Add ipv6 NS offload for WoWLAN state.

Tested in this way:
1. Put device-A into WoW state.
2. ping6 from device-B to device-A.
3. In sniffer, see Neighbour advertisement from device-A.

Reviewed-by: Sean Wang <sean.wang@mediatek.com>
Signed-off-by: Deren Wu <deren.wu@mediatek.com>
Signed-off-by: Ming Yen Hsieh <mingyen.hsieh@mediatek.com>
Signed-off-by: Felix Fietkau <nbd@nbd.name>
drivers/net/wireless/mediatek/mt76/mt7921/init.c
drivers/net/wireless/mediatek/mt76/mt7921/mac.c
drivers/net/wireless/mediatek/mt76/mt7921/main.c
drivers/net/wireless/mediatek/mt76/mt7921/mcu.c
drivers/net/wireless/mediatek/mt76/mt7921/mt7921.h

index f9e1255bd9c76bc204e5d5abd4e7997f8301d341..4a8675634f80339d35b75d5382e7f9866eff5369 100644 (file)
@@ -264,6 +264,10 @@ int mt7921_register_device(struct mt7921_dev *dev)
        INIT_DELAYED_WORK(&dev->mphy.mac_work, mt7921_mac_work);
        INIT_DELAYED_WORK(&dev->phy.scan_work, mt7921_scan_work);
        INIT_DELAYED_WORK(&dev->coredump.work, mt7921_coredump_work);
+#if IS_ENABLED(CONFIG_IPV6)
+       INIT_WORK(&dev->ipv6_ns_work, mt7921_set_ipv6_ns_work);
+       skb_queue_head_init(&dev->ipv6_ns_list);
+#endif
        skb_queue_head_init(&dev->phy.scan_event_list);
        skb_queue_head_init(&dev->coredump.msg_list);
        INIT_LIST_HEAD(&dev->sta_poll_list);
index e67c3d17d36c8ae38c5abc3bfe9461202538eef2..a630ddbf19e541f65e899f76ee2454c59755f796 100644 (file)
@@ -1726,3 +1726,29 @@ bool mt7921_usb_sdio_tx_status_data(struct mt76_dev *mdev, u8 *update)
        return false;
 }
 EXPORT_SYMBOL_GPL(mt7921_usb_sdio_tx_status_data);
+
+#if IS_ENABLED(CONFIG_IPV6)
+void mt7921_set_ipv6_ns_work(struct work_struct *work)
+{
+       struct mt7921_dev *dev = container_of(work, struct mt7921_dev,
+                                               ipv6_ns_work);
+       struct sk_buff *skb;
+       int ret = 0;
+
+       do {
+               skb = skb_dequeue(&dev->ipv6_ns_list);
+
+               if (!skb)
+                       break;
+
+               mt7921_mutex_acquire(dev);
+               ret = mt76_mcu_skb_send_msg(&dev->mt76, skb,
+                                           MCU_UNI_CMD(OFFLOAD), true);
+               mt7921_mutex_release(dev);
+
+       } while (!ret);
+
+       if (ret)
+               skb_queue_purge(&dev->ipv6_ns_list);
+}
+#endif
index e3db08e3a79f3dc9ad80b8a749a1e3144b37dfcc..80279f342109aac4a0b042a82b3a067c77e4d4d4 100644 (file)
@@ -5,6 +5,7 @@
 #include <linux/platform_device.h>
 #include <linux/pci.h>
 #include <linux/module.h>
+#include <net/ipv6.h>
 #include "mt7921.h"
 #include "mcu.h"
 
@@ -1332,7 +1333,7 @@ static int mt7921_suspend(struct ieee80211_hw *hw,
        clear_bit(MT76_STATE_RUNNING, &phy->mt76->state);
        ieee80211_iterate_active_interfaces(hw,
                                            IEEE80211_IFACE_ITER_RESUME_ALL,
-                                           mt76_connac_mcu_set_suspend_iter,
+                                           mt7921_mcu_set_suspend_iter,
                                            &dev->mphy);
 
        mt7921_mutex_release(dev);
@@ -1407,6 +1408,67 @@ static void mt7921_sta_set_decap_offload(struct ieee80211_hw *hw,
                                             MCU_UNI_CMD(STA_REC_UPDATE));
 }
 
+#if IS_ENABLED(CONFIG_IPV6)
+static void mt7921_ipv6_addr_change(struct ieee80211_hw *hw,
+                                   struct ieee80211_vif *vif,
+                                   struct inet6_dev *idev)
+{
+       struct mt7921_vif *mvif = (struct mt7921_vif *)vif->drv_priv;
+       struct mt7921_dev *dev = mvif->phy->dev;
+       struct inet6_ifaddr *ifa;
+       struct in6_addr ns_addrs[IEEE80211_BSS_ARP_ADDR_LIST_LEN];
+       struct sk_buff *skb;
+       u8 i, idx = 0;
+
+       struct {
+               struct {
+                       u8 bss_idx;
+                       u8 pad[3];
+               } __packed hdr;
+               struct mt76_connac_arpns_tlv arpns;
+       } req_hdr = {
+               .hdr = {
+                       .bss_idx = mvif->mt76.idx,
+               },
+               .arpns = {
+                       .tag = cpu_to_le16(UNI_OFFLOAD_OFFLOAD_ND),
+                       .mode = 2,  /* update */
+                       .option = 1, /* update only */
+               },
+       };
+
+       read_lock_bh(&idev->lock);
+       list_for_each_entry(ifa, &idev->addr_list, if_list) {
+               if (ifa->flags & IFA_F_TENTATIVE)
+                       continue;
+               ns_addrs[idx] = ifa->addr;
+               if (++idx >= IEEE80211_BSS_ARP_ADDR_LIST_LEN)
+                       break;
+       }
+       read_unlock_bh(&idev->lock);
+
+       if (!idx)
+               return;
+
+       skb = __mt76_mcu_msg_alloc(&dev->mt76, NULL, sizeof(req_hdr) +
+                                  idx * sizeof(struct in6_addr), GFP_ATOMIC);
+       if (!skb)
+               return;
+
+       req_hdr.arpns.ips_num = idx;
+       req_hdr.arpns.len = cpu_to_le16(sizeof(struct mt76_connac_arpns_tlv)
+                                       + idx * sizeof(struct in6_addr));
+       skb_put_data(skb, &req_hdr, sizeof(req_hdr));
+
+       for (i = 0; i < idx; i++)
+               skb_put_data(skb, &ns_addrs[i].in6_u, sizeof(struct in6_addr));
+
+       skb_queue_tail(&dev->ipv6_ns_list, skb);
+
+       ieee80211_queue_work(dev->mt76.hw, &dev->ipv6_ns_work);
+}
+#endif
+
 static int mt7921_set_sar_specs(struct ieee80211_hw *hw,
                                const struct cfg80211_sar_specs *sar)
 {
@@ -1452,6 +1514,9 @@ const struct ieee80211_ops mt7921_ops = {
        .sta_pre_rcu_remove = mt76_sta_pre_rcu_remove,
        .set_key = mt7921_set_key,
        .sta_set_decap_offload = mt7921_sta_set_decap_offload,
+#if IS_ENABLED(CONFIG_IPV6)
+       .ipv6_addr_change = mt7921_ipv6_addr_change,
+#endif /* CONFIG_IPV6 */
        .ampdu_action = mt7921_ampdu_action,
        .set_rts_threshold = mt7921_set_rts_threshold,
        .wake_tx_queue = mt76_wake_tx_queue,
index 1ecbbe4fa4988dabc3f660d0320a41ef2eeff5c2..12bab18c417197c84fc3c920b4f37282b8dc5ace 100644 (file)
@@ -224,6 +224,49 @@ exit:
 }
 EXPORT_SYMBOL_GPL(mt7921_mcu_fill_message);
 
+#ifdef CONFIG_PM
+
+static int
+mt7921_mcu_set_ipv6_ns_filter(struct mt76_dev *dev,
+                             struct ieee80211_vif *vif, bool suspend)
+{
+       struct mt7921_vif *mvif = (struct mt7921_vif *)vif->drv_priv;
+       struct {
+               struct {
+                       u8 bss_idx;
+                       u8 pad[3];
+               } __packed hdr;
+               struct mt76_connac_arpns_tlv arpns;
+       } req = {
+               .hdr = {
+                       .bss_idx = mvif->mt76.idx,
+               },
+               .arpns = {
+                       .tag = cpu_to_le16(UNI_OFFLOAD_OFFLOAD_ND),
+                       .len = cpu_to_le16(sizeof(struct mt76_connac_arpns_tlv)),
+                       .mode = suspend,
+               },
+       };
+
+       return mt76_mcu_send_msg(dev, MCU_UNI_CMD_OFFLOAD, &req, sizeof(req),
+                                true);
+}
+
+void mt7921_mcu_set_suspend_iter(void *priv, u8 *mac, struct ieee80211_vif *vif)
+{
+       if (IS_ENABLED(CONFIG_IPV6)) {
+               struct mt76_phy *phy = priv;
+
+               mt7921_mcu_set_ipv6_ns_filter(phy->dev, vif,
+                                             !test_bit(MT76_STATE_RUNNING,
+                                             &phy->state));
+       }
+
+       mt76_connac_mcu_set_suspend_iter(priv, mac, vif);
+}
+
+#endif /* CONFIG_PM */
+
 static void
 mt7921_mcu_scan_event(struct mt7921_dev *dev, struct sk_buff *skb)
 {
index adbdb2e22934c46115683047a5c1750fddb22968..5ca584bb2fc656130d52d30d6d45300dc6fde1a3 100644 (file)
@@ -211,6 +211,10 @@ struct mt7921_dev {
        struct mt76_connac_pm pm;
        struct mt76_connac_coredump coredump;
        const struct mt7921_hif_ops *hif_ops;
+
+       struct work_struct ipv6_ns_work;
+       /* IPv6 addresses for WoWLAN */
+       struct sk_buff_head ipv6_ns_list;
 };
 
 enum {
@@ -449,6 +453,10 @@ int mt7921s_mcu_drv_pmctrl(struct mt7921_dev *dev);
 int mt7921s_mcu_fw_pmctrl(struct mt7921_dev *dev);
 void mt7921_mac_add_txs(struct mt7921_dev *dev, void *data);
 void mt7921_set_runtime_pm(struct mt7921_dev *dev);
+void mt7921_mcu_set_suspend_iter(void *priv, u8 *mac,
+                                struct ieee80211_vif *vif);
+void mt7921_set_ipv6_ns_work(struct work_struct *work);
+
 int mt7921_mcu_set_sniffer(struct mt7921_dev *dev, struct ieee80211_vif *vif,
                           bool enable);