/* Copyright(c) 2019-2020  Realtek Corporation
  */
 
+#include <linux/if_arp.h>
 #include "cam.h"
 #include "chan.h"
 #include "coex.h"
        u8 action;
 } __packed __aligned(2);
 
+struct rtw89_arp_rsp {
+       struct ieee80211_hdr_3addr addr;
+       u8 llc_hdr[sizeof(rfc1042_header)];
+       __be16 llc_type;
+       struct arphdr arp_hdr;
+       u8 sender_hw[ETH_ALEN];
+       __be32 sender_ip;
+       u8 target_hw[ETH_ALEN];
+       __be32 target_ip;
+} __packed __aligned(2);
+
 static const u8 mss_signature[] = {0x4D, 0x53, 0x53, 0x4B, 0x50, 0x4F, 0x4F, 0x4C};
 
 union rtw89_fw_element_arg {
        return skb;
 }
 
+static struct sk_buff *rtw89_arp_response_get(struct rtw89_dev *rtwdev,
+                                             struct rtw89_vif *rtwvif)
+{
+       struct rtw89_wow_param *rtw_wow = &rtwdev->wow;
+       struct rtw89_arp_rsp *arp_skb;
+       struct arphdr *arp_hdr;
+       struct sk_buff *skb;
+       __le16 fc;
+
+       skb = dev_alloc_skb(sizeof(struct rtw89_arp_rsp));
+       if (!skb)
+               return NULL;
+
+       arp_skb = skb_put_zero(skb, sizeof(*arp_skb));
+
+       if (rtw_wow->ptk_alg)
+               fc = cpu_to_le16(IEEE80211_FTYPE_DATA | IEEE80211_FCTL_TODS |
+                                IEEE80211_FCTL_PROTECTED);
+       else
+               fc = cpu_to_le16(IEEE80211_FTYPE_DATA | IEEE80211_FCTL_TODS);
+
+       arp_skb->addr.frame_control = fc;
+       ether_addr_copy(arp_skb->addr.addr1, rtwvif->bssid);
+       ether_addr_copy(arp_skb->addr.addr2, rtwvif->mac_addr);
+       ether_addr_copy(arp_skb->addr.addr3, rtwvif->bssid);
+
+       memcpy(arp_skb->llc_hdr, rfc1042_header, sizeof(rfc1042_header));
+       arp_skb->llc_type = htons(ETH_P_ARP);
+
+       arp_hdr = &arp_skb->arp_hdr;
+       arp_hdr->ar_hrd = htons(ARPHRD_ETHER);
+       arp_hdr->ar_pro = htons(ETH_P_IP);
+       arp_hdr->ar_hln = ETH_ALEN;
+       arp_hdr->ar_pln = 4;
+       arp_hdr->ar_op = htons(ARPOP_REPLY);
+
+       ether_addr_copy(arp_skb->sender_hw, rtwvif->mac_addr);
+       arp_skb->sender_ip = rtwvif->ip_addr;
+
+       return skb;
+}
+
 static int rtw89_fw_h2c_add_general_pkt(struct rtw89_dev *rtwdev,
                                        struct rtw89_vif *rtwvif,
                                        enum rtw89_fw_pkt_ofld_type type,
        case RTW89_PKT_OFLD_TYPE_SA_QUERY:
                skb = rtw89_sa_query_get(rtwdev, rtwvif);
                break;
+       case RTW89_PKT_OFLD_TYPE_ARP_RSP:
+               skb = rtw89_arp_response_get(rtwdev, rtwvif);
+               break;
        default:
                goto err;
        }
        return ret;
 }
 
+int rtw89_fw_h2c_arp_offload(struct rtw89_dev *rtwdev, struct rtw89_vif *rtwvif,
+                            bool enable)
+{
+       struct rtw89_h2c_arp_offload *h2c;
+       u32 len = sizeof(*h2c);
+       struct sk_buff *skb;
+       u8 pkt_id = 0;
+       int ret;
+
+       if (enable) {
+               ret = rtw89_fw_h2c_add_general_pkt(rtwdev, rtwvif,
+                                                  RTW89_PKT_OFLD_TYPE_ARP_RSP,
+                                                  &pkt_id);
+               if (ret)
+                       return ret;
+       }
+
+       skb = rtw89_fw_h2c_alloc_skb_with_hdr(rtwdev, len);
+       if (!skb) {
+               rtw89_err(rtwdev, "failed to alloc skb for arp offload\n");
+               return -ENOMEM;
+       }
+
+       skb_put(skb, len);
+       h2c = (struct rtw89_h2c_arp_offload *)skb->data;
+
+       h2c->w0 = le32_encode_bits(enable, RTW89_H2C_ARP_OFFLOAD_W0_ENABLE) |
+                 le32_encode_bits(0, RTW89_H2C_ARP_OFFLOAD_W0_ACTION) |
+                 le32_encode_bits(rtwvif->mac_id, RTW89_H2C_ARP_OFFLOAD_W0_MACID) |
+                 le32_encode_bits(pkt_id, RTW89_H2C_ARP_OFFLOAD_W0_PKT_ID);
+
+       rtw89_h2c_pkt_set_hdr(rtwdev, skb, FWCMD_TYPE_H2C,
+                             H2C_CAT_MAC,
+                             H2C_CL_MAC_WOW,
+                             H2C_FUNC_ARP_OFLD, 0, 1,
+                             len);
+
+       ret = rtw89_h2c_tx(rtwdev, skb, false);
+       if (ret) {
+               rtw89_err(rtwdev, "failed to send h2c\n");
+               goto fail;
+       }
+
+       return 0;
+
+fail:
+       dev_kfree_skb_any(skb);
+
+       return ret;
+}
+
 #define H2C_DISCONNECT_DETECT_LEN 8
 int rtw89_fw_h2c_disconnect_detect(struct rtw89_dev *rtwdev,
                                   struct rtw89_vif *rtwvif, bool enable)
 
 #define RTW89_H2C_WOW_GTK_OFLD_W1_PMF_BIP_SEC_ALGO GENMASK(9, 8)
 #define RTW89_H2C_WOW_GTK_OFLD_W1_ALGO_AKM_SUIT GENMASK(17, 10)
 
+struct rtw89_h2c_arp_offload {
+       __le32 w0;
+       __le32 w1;
+} __packed;
+
+#define RTW89_H2C_ARP_OFFLOAD_W0_ENABLE BIT(0)
+#define RTW89_H2C_ARP_OFFLOAD_W0_ACTION BIT(1)
+#define RTW89_H2C_ARP_OFFLOAD_W0_MACID GENMASK(23, 16)
+#define RTW89_H2C_ARP_OFFLOAD_W0_PKT_ID GENMASK(31, 24)
+#define RTW89_H2C_ARP_OFFLOAD_W1_CONTENT GENMASK(31, 0)
+
 enum rtw89_btc_btf_h2c_class {
        BTFC_SET = 0x10,
        BTFC_GET = 0x11,
        H2C_FUNC_DISCONNECT_DETECT      = 0x1,
        H2C_FUNC_WOW_GLOBAL             = 0x2,
        H2C_FUNC_GTK_OFLD               = 0x3,
+       H2C_FUNC_ARP_OFLD               = 0x4,
        H2C_FUNC_WAKEUP_CTRL            = 0x8,
        H2C_FUNC_WOW_CAM_UPD            = 0xC,
        H2C_FUNC_AOAC_REPORT_REQ        = 0xD,
                                 struct rtw89_vif *rtwvif, bool enable);
 int rtw89_fw_h2c_keep_alive(struct rtw89_dev *rtwdev, struct rtw89_vif *rtwvif,
                            bool enable);
+int rtw89_fw_h2c_arp_offload(struct rtw89_dev *rtwdev,
+                            struct rtw89_vif *rtwvif, bool enable);
 int rtw89_fw_h2c_disconnect_detect(struct rtw89_dev *rtwdev,
                                   struct rtw89_vif *rtwvif, bool enable);
 int rtw89_fw_h2c_wow_global(struct rtw89_dev *rtwdev, struct rtw89_vif *rtwvif,