| // SPDX-License-Identifier: GPL-2.0 | |
| /* | |
| * Copyright (c) 2012 - 2018 Microchip Technology Inc., and its subsidiaries. | |
| * All rights reserved. | |
| */ | |
| #include "cfg80211.h" | |
| struct wilc_wfi_radiotap_hdr { | |
| struct ieee80211_radiotap_header_fixed hdr; | |
| u8 rate; | |
| } __packed; | |
| struct wilc_wfi_radiotap_cb_hdr { | |
| struct ieee80211_radiotap_header_fixed hdr; | |
| u8 rate; | |
| u8 dump; | |
| u16 tx_flags; | |
| } __packed; | |
| #define TX_RADIOTAP_PRESENT ((1 << IEEE80211_RADIOTAP_RATE) | \ | |
| (1 << IEEE80211_RADIOTAP_TX_FLAGS)) | |
| void wilc_wfi_monitor_rx(struct net_device *mon_dev, u8 *buff, u32 size) | |
| { | |
| u32 header, pkt_offset; | |
| struct sk_buff *skb = NULL; | |
| struct wilc_wfi_radiotap_hdr *hdr; | |
| struct wilc_wfi_radiotap_cb_hdr *cb_hdr; | |
| if (!mon_dev) | |
| return; | |
| if (!netif_running(mon_dev)) | |
| return; | |
| /* Get WILC header */ | |
| header = get_unaligned_le32(buff - HOST_HDR_OFFSET); | |
| /* | |
| * The packet offset field contain info about what type of management | |
| * the frame we are dealing with and ack status | |
| */ | |
| pkt_offset = FIELD_GET(WILC_PKT_HDR_OFFSET_FIELD, header); | |
| if (pkt_offset & IS_MANAGMEMENT_CALLBACK) { | |
| /* hostapd callback mgmt frame */ | |
| skb = dev_alloc_skb(size + sizeof(*cb_hdr)); | |
| if (!skb) | |
| return; | |
| skb_put_data(skb, buff, size); | |
| cb_hdr = skb_push(skb, sizeof(*cb_hdr)); | |
| memset(cb_hdr, 0, sizeof(*cb_hdr)); | |
| cb_hdr->hdr.it_version = 0; /* PKTHDR_RADIOTAP_VERSION; */ | |
| cb_hdr->hdr.it_len = cpu_to_le16(sizeof(*cb_hdr)); | |
| cb_hdr->hdr.it_present = cpu_to_le32(TX_RADIOTAP_PRESENT); | |
| cb_hdr->rate = 5; | |
| if (pkt_offset & IS_MGMT_STATUS_SUCCES) { | |
| /* success */ | |
| cb_hdr->tx_flags = IEEE80211_RADIOTAP_F_TX_RTS; | |
| } else { | |
| cb_hdr->tx_flags = IEEE80211_RADIOTAP_F_TX_FAIL; | |
| } | |
| } else { | |
| skb = dev_alloc_skb(size + sizeof(*hdr)); | |
| if (!skb) | |
| return; | |
| skb_put_data(skb, buff, size); | |
| hdr = skb_push(skb, sizeof(*hdr)); | |
| memset(hdr, 0, sizeof(struct wilc_wfi_radiotap_hdr)); | |
| hdr->hdr.it_version = 0; /* PKTHDR_RADIOTAP_VERSION; */ | |
| hdr->hdr.it_len = cpu_to_le16(sizeof(*hdr)); | |
| hdr->hdr.it_present = cpu_to_le32 | |
| (1 << IEEE80211_RADIOTAP_RATE); | |
| hdr->rate = 5; | |
| } | |
| skb->dev = mon_dev; | |
| skb_reset_mac_header(skb); | |
| skb->ip_summed = CHECKSUM_UNNECESSARY; | |
| skb->pkt_type = PACKET_OTHERHOST; | |
| skb->protocol = htons(ETH_P_802_2); | |
| memset(skb->cb, 0, sizeof(skb->cb)); | |
| netif_rx(skb); | |
| } | |
| struct tx_complete_mon_data { | |
| int size; | |
| void *buff; | |
| }; | |
| static void mgmt_tx_complete(void *priv, int status) | |
| { | |
| struct tx_complete_mon_data *pv_data = priv; | |
| /* | |
| * in case of fully hosting mode, the freeing will be done | |
| * in response to the cfg packet | |
| */ | |
| kfree(pv_data->buff); | |
| kfree(pv_data); | |
| } | |
| static int mon_mgmt_tx(struct net_device *dev, const u8 *buf, size_t len) | |
| { | |
| struct tx_complete_mon_data *mgmt_tx = NULL; | |
| if (!dev) | |
| return -EFAULT; | |
| netif_stop_queue(dev); | |
| mgmt_tx = kmalloc_obj(*mgmt_tx, GFP_ATOMIC); | |
| if (!mgmt_tx) | |
| return -ENOMEM; | |
| mgmt_tx->buff = kmemdup(buf, len, GFP_ATOMIC); | |
| if (!mgmt_tx->buff) { | |
| kfree(mgmt_tx); | |
| return -ENOMEM; | |
| } | |
| mgmt_tx->size = len; | |
| wilc_wlan_txq_add_mgmt_pkt(dev, mgmt_tx, mgmt_tx->buff, mgmt_tx->size, | |
| mgmt_tx_complete); | |
| netif_wake_queue(dev); | |
| return 0; | |
| } | |
| static netdev_tx_t wilc_wfi_mon_xmit(struct sk_buff *skb, | |
| struct net_device *dev) | |
| { | |
| u32 rtap_len, ret = 0; | |
| struct wilc_wfi_mon_priv *mon_priv; | |
| struct sk_buff *skb2; | |
| struct wilc_wfi_radiotap_cb_hdr *cb_hdr; | |
| u8 srcadd[ETH_ALEN]; | |
| u8 bssid[ETH_ALEN]; | |
| mon_priv = netdev_priv(dev); | |
| if (!mon_priv) | |
| return -EFAULT; | |
| rtap_len = ieee80211_get_radiotap_len(skb->data); | |
| if (skb->len < rtap_len) | |
| return -1; | |
| skb_pull(skb, rtap_len); | |
| if (skb->data[0] == 0xc0 && is_broadcast_ether_addr(&skb->data[4])) { | |
| skb2 = dev_alloc_skb(skb->len + sizeof(*cb_hdr)); | |
| if (!skb2) | |
| return -ENOMEM; | |
| skb_put_data(skb2, skb->data, skb->len); | |
| cb_hdr = skb_push(skb2, sizeof(*cb_hdr)); | |
| memset(cb_hdr, 0, sizeof(struct wilc_wfi_radiotap_cb_hdr)); | |
| cb_hdr->hdr.it_version = 0; /* PKTHDR_RADIOTAP_VERSION; */ | |
| cb_hdr->hdr.it_len = cpu_to_le16(sizeof(*cb_hdr)); | |
| cb_hdr->hdr.it_present = cpu_to_le32(TX_RADIOTAP_PRESENT); | |
| cb_hdr->rate = 5; | |
| cb_hdr->tx_flags = 0x0004; | |
| skb2->dev = dev; | |
| skb_reset_mac_header(skb2); | |
| skb2->ip_summed = CHECKSUM_UNNECESSARY; | |
| skb2->pkt_type = PACKET_OTHERHOST; | |
| skb2->protocol = htons(ETH_P_802_2); | |
| memset(skb2->cb, 0, sizeof(skb2->cb)); | |
| netif_rx(skb2); | |
| return 0; | |
| } | |
| skb->dev = mon_priv->real_ndev; | |
| ether_addr_copy(srcadd, &skb->data[10]); | |
| ether_addr_copy(bssid, &skb->data[16]); | |
| /* | |
| * Identify if data or mgmt packet, if source address and bssid | |
| * fields are equal send it to mgmt frames handler | |
| */ | |
| if (!(memcmp(srcadd, bssid, 6))) { | |
| ret = mon_mgmt_tx(mon_priv->real_ndev, skb->data, skb->len); | |
| if (ret) | |
| netdev_err(dev, "fail to mgmt tx\n"); | |
| dev_kfree_skb(skb); | |
| } else { | |
| ret = wilc_mac_xmit(skb, mon_priv->real_ndev); | |
| } | |
| return ret; | |
| } | |
| static const struct net_device_ops wilc_wfi_netdev_ops = { | |
| .ndo_start_xmit = wilc_wfi_mon_xmit, | |
| }; | |
| struct net_device *wilc_wfi_init_mon_interface(struct wilc *wl, | |
| const char *name, | |
| struct net_device *real_dev) | |
| { | |
| struct wilc_wfi_mon_priv *priv; | |
| /* If monitor interface is already initialized, return it */ | |
| if (wl->monitor_dev) | |
| return wl->monitor_dev; | |
| wl->monitor_dev = alloc_etherdev(sizeof(struct wilc_wfi_mon_priv)); | |
| if (!wl->monitor_dev) | |
| return NULL; | |
| wl->monitor_dev->type = ARPHRD_IEEE80211_RADIOTAP; | |
| strscpy(wl->monitor_dev->name, name, IFNAMSIZ); | |
| wl->monitor_dev->netdev_ops = &wilc_wfi_netdev_ops; | |
| wl->monitor_dev->needs_free_netdev = true; | |
| if (register_netdevice(wl->monitor_dev)) { | |
| netdev_err(real_dev, "register_netdevice failed\n"); | |
| free_netdev(wl->monitor_dev); | |
| return NULL; | |
| } | |
| priv = netdev_priv(wl->monitor_dev); | |
| priv->real_ndev = real_dev; | |
| return wl->monitor_dev; | |
| } | |
| void wilc_wfi_deinit_mon_interface(struct wilc *wl, bool rtnl_locked) | |
| { | |
| if (!wl->monitor_dev) | |
| return; | |
| if (rtnl_locked) | |
| unregister_netdevice(wl->monitor_dev); | |
| else | |
| unregister_netdev(wl->monitor_dev); | |
| wl->monitor_dev = NULL; | |
| } |