/**************************************************************************** * * Copyright (c) 2014 - 2016 Samsung Electronics Co., Ltd. All rights reserved * ****************************************************************************/ #include #include "debug.h" #include "dev.h" #include "sap.h" #include "sap_ma.h" #include "hip.h" #include "ba.h" #include "oxygen_ioctl.h" #include "mgt.h" #define SUPPORTED_VERSION 13 #define SUPPORTED_OLD_VERSION 0 static int sap_ma_version_supported(u16 version); static int sap_ma_rx_handler(struct slsi_dev *sdev, struct sk_buff *skb); static int sap_ma_txdone(struct slsi_dev *sdev, u16 colour); static int sap_ma_notifier(struct slsi_dev *sdev, unsigned long event); static struct sap_api sap_ma = { .sap_class = SAP_MA, .sap_version_supported = sap_ma_version_supported, .sap_handler = sap_ma_rx_handler, .sap_versions = { SUPPORTED_VERSION, SUPPORTED_OLD_VERSION }, .sap_txdone = sap_ma_txdone, .sap_notifier = sap_ma_notifier, }; static int sap_ma_notifier(struct slsi_dev *sdev, unsigned long event) { uint vif; SLSI_INFO_NODEV("Notifier event received %s\n", event ? "SCSC_WIFI_FAILURE_RESET" : "SCSC_WIFI_STOP"); if ((event != SCSC_WIFI_STOP) && (event != SCSC_WIFI_FAILURE_RESET)) return -EIO; switch (event) { case SCSC_WIFI_STOP: SLSI_INFO_NODEV("Stop netdev queues\n"); rcu_read_lock(); for (vif = SLSI_NET_INDEX_WLAN; vif <= SLSI_NET_INDEX_P2PX; vif++) { struct net_device *ndev = slsi_get_netdev_rcu(sdev, vif); if (ndev && !netif_queue_stopped(ndev)) netif_tx_stop_all_queues(ndev); } rcu_read_unlock(); break; case SCSC_WIFI_FAILURE_RESET: SLSI_INFO_NODEV("Netdevs queues will not be restarted - recovery will take care of it\n"); break; default: SLSI_INFO_NODEV("Unknown event code %lu\n", event); break; } return 0; } static int sap_ma_version_supported(u16 version) { unsigned int major = SAP_MAJOR(version); unsigned int minor = SAP_MINOR(version); u8 i = 0; SLSI_INFO_NODEV("Reported version: %d.%d\n", major, minor); for (i = 0; i < SAP_MAX_VER; i++) if (sap_ma.sap_versions[i] == major) return 0; SLSI_ERR_NODEV("Version %d.%d Not supported\n", major, minor); return -EINVAL; } static int slsi_rx_amsdu_deaggregate(struct net_device *dev, struct sk_buff *skb) { unsigned int msdu_len; unsigned int subframe_len; int padding; struct sk_buff *subframe = NULL; struct netdev_vif *ndev_vif = netdev_priv(dev); SLSI_NET_DBG3(dev, SLSI_RX, "A-MSDU received, length = %d\n", skb->len); skb_pull(skb, fapi_get_siglen(skb)); while (skb != subframe) { msdu_len = (skb->data[ETH_ALEN * 2] << 8) | skb->data[(ETH_ALEN * 2) + 1]; if ((msdu_len > (ETH_DATA_LEN + LLC_SNAP_HDR_LEN)) || (msdu_len > skb->len) || (msdu_len < (ETH_ZLEN - (ETH_HLEN - LLC_SNAP_HDR_LEN))) || (skb->len < (ETH_ZLEN + LLC_SNAP_HDR_LEN))) { SLSI_NET_ERR(dev, "Wrong MSDU length %d, skb length = %d\n", msdu_len, skb->len); slsi_kfree_skb(skb); return -EINVAL; } subframe_len = msdu_len + (2 * ETH_ALEN) + 2; /* For the last subframe skb length and subframe length will be same */ if (skb->len == subframe_len) { /* Use the original skb for the last subframe */ subframe = skb; /* There is no padding for last subframe */ padding = 0; } else { /* Clone the skb for the subframe */ subframe = slsi_skb_clone(skb, GFP_ATOMIC); if (subframe == NULL) { slsi_kfree_skb(skb); SLSI_NET_ERR(dev, "Failed to clone the skb for amsdu subframe\n"); return -ENOMEM; } padding = (4 - (subframe_len % 4)) & 0x3; } /* Remove the other subframes by adjusting the tail pointer of the cloned skb */ skb_trim(subframe, subframe_len); /* Overwrite LLC+SNAP header with src & dest addr */ SLSI_ETHER_COPY(&subframe->data[14], &subframe->data[6]); SLSI_ETHER_COPY(&subframe->data[8], &subframe->data[0]); /* Remove 8 bytes of LLC+SNAP header */ skb_pull(subframe, LLC_SNAP_HDR_LEN); SLSI_NET_DBG3(dev, SLSI_RX, "msdu_len = %d, subframe_len = %d, padding = %d\n", msdu_len, subframe_len, padding); SLSI_NET_DBG_HEX(dev, SLSI_RX, subframe->data, subframe->len < 64 ? subframe->len : 64, "Subframe before giving to OS:\n"); /* Prepare the skb */ subframe->dev = dev; subframe->ip_summed = CHECKSUM_UNNECESSARY; ndev_vif->stats.rx_bytes += subframe->len; ndev_vif->stats.rx_packets++; dev->last_rx = jiffies; subframe->protocol = eth_type_trans(subframe, dev); /* If this is not the last subframe then move to the next subframe */ if (skb != subframe) skb_pull(skb, (subframe_len + padding)); #ifdef CONFIG_SCSC_WLAN_RX_NAPI slsi_rx_msdu_napi(dev, subframe); #else slsi_dbg_untrack_skb(subframe); netif_rx_ni(subframe); #endif } return 0; } static int slsi_rx_data_process_skb(struct slsi_dev *sdev, struct net_device *dev, struct sk_buff *skb, bool fromBA) { struct netdev_vif *ndev_vif = netdev_priv(dev); struct slsi_peer *peer = NULL; struct ethhdr *ehdr = (struct ethhdr *)fapi_get_data(skb); u16 seq_num; bool skip_ba = fromBA; SLSI_NET_DBG_HEX(dev, SLSI_RX, skb->data, skb->len < 64 ? skb->len : 64, "\n"); if (!((fapi_get_u16(skb, u.ma_unitdata_ind.data_unit_descriptor) == FAPI_DATAUNITDESCRIPTOR_IEEE802_3_FRAME) || (fapi_get_u16(skb, u.ma_unitdata_ind.data_unit_descriptor) == FAPI_DATAUNITDESCRIPTOR_AMSDU))) { WARN_ON(1); slsi_kfree_skb(skb); return -ENOTSUPP; } seq_num = fapi_get_u16(skb, u.ma_unitdata_ind.sequence_number); SLSI_NET_DBG3(dev, SLSI_RX, "ma_unitdata_ind(vif:%d, %pM, datatype:%d, s:%d)\n", fapi_get_vif(skb), ehdr->h_source, fapi_get_u16(skb, u.ma_unitdata_ind.data_unit_descriptor), (seq_num & SLSI_RX_SEQ_NUM_MASK)); peer = slsi_get_peer_from_mac(sdev, dev, ehdr->h_source); if (!peer) { SLSI_NET_DBG1(dev, SLSI_RX, "Packet dropped, peer not found\n"); /* Race in Data plane (Shows up in fw test mode) */ slsi_kfree_skb(skb); return -EINVAL; } /* When TDLS connection has just been closed a few last frame may still arrive from the closed connection. * This frames must not be injected in to the block session with the AP as the sequence numbers are different * that will confuse the BA process. Therefore we have to skip BA for those frames. */ if ((ndev_vif->vif_type == FAPI_VIFTYPE_STATION) && (peer->aid < SLSI_TDLS_PEER_INDEX_MIN) && (seq_num & SLSI_RX_VIA_TDLS_LINK)) { SLSI_NET_WARN(dev, "Packet received from TDLS but no TDLS exists (seq: %x) Skip BA\n", seq_num); skip_ba = true; } /* TDLS is enabled for the PEER but still packet is received through the AP. Process this packet with the AP PEER */ if ((ndev_vif->vif_type == FAPI_VIFTYPE_STATION) && (peer->aid >= SLSI_TDLS_PEER_INDEX_MIN) && (!(seq_num & SLSI_RX_VIA_TDLS_LINK))) { SLSI_NET_DBG2(dev, SLSI_TDLS, "Packet received from TDLS peer through the AP(seq: %x)\n", seq_num); peer = slsi_get_peer_from_qs(sdev, dev, SLSI_STA_PEER_QUEUESET); if (!peer) { SLSI_NET_DBG1(dev, SLSI_RX, "Packet dropped AP peer not found\n"); slsi_kfree_skb(skb); return -EINVAL; } } /* Buffering of the frames before mlme_connected_ind */ if ((ndev_vif->vif_type == FAPI_VIFTYPE_AP || ndev_vif->vif_type == FAPI_VIFTYPE_ADHOC) && (peer->connected_state == SLSI_STA_CONN_STATE_CONNECTING)) { SLSI_DBG2(sdev, SLSI_MLME, "Buffering MA-UnitData FRAMES"); slsi_skb_queue_tail(&peer->buffered_frames, skb); return 1; } /* In ad-hoc mode, we need to check that the destination address is not a * multicast address before allowing the packet to be passed to tbe block-ack * processing code. */ if ((ndev_vif->vif_type == FAPI_VIFTYPE_ADHOC) && is_multicast_ether_addr(ehdr->h_dest)) skip_ba = true; if (!skip_ba && (slsi_ba_check(peer, fapi_get_u16(skb, u.ma_unitdata_ind.priority)))) if (!slsi_ba_process_frame(dev, peer, skb, (seq_num & SLSI_RX_SEQ_NUM_MASK), fapi_get_u16(skb, u.ma_unitdata_ind.priority))) return 1; /* A-MSDU deaggregation */ if (fapi_get_u16(skb, u.ma_unitdata_ind.data_unit_descriptor) == FAPI_DATAUNITDESCRIPTOR_AMSDU) { /* This function will consume the skb */ if (slsi_rx_amsdu_deaggregate(dev, skb)) { ndev_vif->stats.rx_dropped++; if (peer) peer->sinfo.rx_dropped_misc++; } else { if (peer) peer->sinfo.rx_packets++; slsi_wakelock_timeout(&sdev->wlan_wl_to, SLSI_RX_WAKELOCK_TIME); } return 1; } /* strip signal and any signal/bulk roundings/offsets */ skb_pull(skb, fapi_get_siglen(skb)); skb->dev = dev; skb->ip_summed = CHECKSUM_UNNECESSARY; /* Test for an overlength frame */ if (skb->len > (dev->mtu + ETH_HLEN)) { /* A bogus length ethfrm has been encap'd. */ /* Is someone trying an oflow attack? */ SLSI_NET_WARN(dev, "oversize frame (%d > %d)\n", skb->len, dev->mtu + ETH_HLEN); /* Drop the packet and return */ ndev_vif->stats.rx_dropped++; ndev_vif->stats.rx_length_errors++; if (peer) peer->sinfo.rx_dropped_misc++; slsi_kfree_skb(skb); return -EINVAL; } /* In STA mode, the AP relays back our multicast traffic. * Receiving these frames and passing it up confuses some * protocols and applications, notably IPv6 Duplicate * Address Detection. * * So these frames are dropped instead of passing it further. * No need to update the drop statistics as these frames are * locally generated and should not be accounted in reception. */ if (ndev_vif->vif_type == FAPI_VIFTYPE_STATION) { struct ethhdr *ehdr = (struct ethhdr *)(skb->data); if (is_multicast_ether_addr(ehdr->h_dest) && !compare_ether_addr(ehdr->h_source, dev->dev_addr)) { SLSI_NET_DBG2(dev, SLSI_RX, "drop locally generated multicast frame relayed back by AP\n"); slsi_kfree_skb(skb); return -EINVAL; } } if (peer) { peer->sinfo.rx_packets++; peer->sinfo.rx_bytes += skb->len; } ndev_vif->stats.rx_packets++; ndev_vif->stats.rx_bytes += skb->len; dev->last_rx = jiffies; /* Intra BSS */ if (ndev_vif->vif_type == FAPI_VIFTYPE_AP && ndev_vif->peer_sta_records) { struct ethhdr *ehdr = (struct ethhdr *)(skb->data); if (is_multicast_ether_addr(ehdr->h_dest)) { struct sk_buff *rebroadcast_skb = slsi_skb_copy(skb, GFP_KERNEL); skb->protocol = eth_type_trans(skb, dev); if (!rebroadcast_skb) { SLSI_WARN(sdev, "Intra BSS: failed to alloc new SKB for broadcast\n"); return 0; } SLSI_DBG3(sdev, SLSI_RX, "Intra BSS: multicast %pM\n", ehdr->h_dest); rebroadcast_skb->dev = dev; rebroadcast_skb->protocol = cpu_to_be16(ETH_P_802_3); slsi_dbg_untrack_skb(rebroadcast_skb); skb_reset_network_header(rebroadcast_skb); skb_reset_mac_header(rebroadcast_skb); dev_queue_xmit(rebroadcast_skb); return 0; } peer = slsi_get_peer_from_mac(sdev, dev, ehdr->h_dest); if (peer && peer->authorized) { SLSI_DBG3(sdev, SLSI_RX, "Intra BSS: unicast %pM\n", ehdr->h_dest); skb->protocol = cpu_to_be16(ETH_P_802_3); slsi_dbg_untrack_skb(skb); skb_reset_network_header(skb); skb_reset_mac_header(skb); dev_queue_xmit(skb); return 1; } } skb->protocol = eth_type_trans(skb, dev); return 0; } #ifdef CONFIG_SCSC_WLAN_RX_NAPI int slsi_rx_data_napi(struct slsi_dev *sdev, struct net_device *dev, struct sk_buff *skb, bool fromBA) { struct netdev_vif *ndev_vif = netdev_priv(dev); slsi_debug_frame(sdev, dev, skb, "RX"); if (slsi_rx_data_process_skb(sdev, dev, skb, fromBA) == 0) { slsi_skb_queue_tail(&ndev_vif->napi.rx_data, skb); slsi_spinlock_lock(&ndev_vif->napi.lock); if (ndev_vif->napi.interrupt_enabled) { ndev_vif->napi.interrupt_enabled = false; napi_schedule(&ndev_vif->napi.napi); } slsi_spinlock_unlock(&ndev_vif->napi.lock); } return 0; } #endif int slsi_rx_data(struct slsi_dev *sdev, struct net_device *dev, struct sk_buff *skb, bool fromBA) { if (slsi_rx_data_process_skb(sdev, dev, skb, fromBA) == 0) { slsi_dbg_untrack_skb(skb); SLSI_DBG3(sdev, SLSI_RX, "pass %u bytes to local stack\n", skb->len); netif_rx_ni(skb); slsi_wakelock_timeout(&sdev->wlan_wl_to, SLSI_RX_WAKELOCK_TIME); } return 0; } #ifdef CONFIG_SCSC_WLAN_OXYGEN_ENABLE static void slsi_rx_tx_failure_ind(struct slsi_dev *sdev, struct net_device *dev, struct sk_buff *skb) { struct netdev_vif *ndev_vif = netdev_priv(dev); void *addr = NULL; WARN_ON(!SLSI_MUTEX_IS_LOCKED(ndev_vif->vif_mutex)); WARN_ON(ndev_vif->ibss.olsrd_pid == 0); addr = (void *)fapi_get_buff(skb, u.ma_tx_failure_ind.peer_sta_address); SLSI_DBG1(sdev, SLSI_MLME, "Receive tx_fail event from %pM\n", addr); if (ndev_vif->ibss.olsrd_pid > 0) oxygen_netlink_send(ndev_vif->ibss.olsrd_pid, IBSS_EVENT_TXFAIL, ndev_vif->ibss.tx_fail_seq++, (void *)addr, ETH_ALEN); slsi_kfree_skb(skb); SLSI_MUTEX_UNLOCK(ndev_vif->vif_mutex); } #endif static int slsi_rx_data_cfm(struct slsi_dev *sdev, struct net_device *dev, struct sk_buff *skb) { SLSI_NET_DBG1(dev, SLSI_TX, "ma_unitdata_cfm(vif:%d, host_tag:0x%x, status:%d)\n", fapi_get_vif(skb), fapi_get_u16(skb, u.ma_unitdata_cfm.host_tag), fapi_get_u16(skb, u.ma_unitdata_cfm.transmission_status)); slsi_kfree_skb(skb); return 0; } void slsi_rx_netdev_data_work(struct work_struct *work) { struct slsi_skb_work *w = container_of(work, struct slsi_skb_work, work); struct slsi_dev *sdev = w->sdev; struct net_device *dev = w->dev; struct netdev_vif *ndev_vif = netdev_priv(dev); struct sk_buff *skb; if (WARN_ON(!dev)) return; slsi_wakelock(&sdev->wlan_wl); SLSI_MUTEX_LOCK(ndev_vif->vif_mutex); while (1) { if (atomic_read(&ndev_vif->ba_flush)) { atomic_set(&ndev_vif->ba_flush, 0); slsi_ba_process_complete(dev); } skb = slsi_skb_work_dequeue(w); if (!skb) break; slsi_debug_frame(sdev, dev, skb, "RX"); switch (fapi_get_u16(skb, id)) { case MA_UNITDATA_IND: (void)slsi_rx_data(sdev, dev, skb, false); break; case MA_UNITDATA_CFM: { (void)slsi_rx_data_cfm(sdev, dev, skb); } break; #ifdef CONFIG_SCSC_WLAN_OXYGEN_ENABLE case MA_TX_FAILURE_IND: slsi_rx_tx_failure_ind(sdev, dev, skb); break; #endif default: SLSI_DBG1(sdev, SLSI_MLME, "Unexpected Data: 0x%.4x\n", fapi_get_sigid(skb)); slsi_kfree_skb(skb); break; } } SLSI_MUTEX_UNLOCK(ndev_vif->vif_mutex); slsi_wakeunlock(&sdev->wlan_wl); } static int slsi_rx_queue_data(struct slsi_dev *sdev, struct sk_buff *skb) { struct net_device *dev; struct netdev_vif *ndev_vif; int vif; vif = fapi_get_vif(skb); rcu_read_lock(); dev = slsi_get_netdev_rcu(sdev, vif); if (!dev) { SLSI_ERR(sdev, "netdev(%d) No longer exists\n", vif); rcu_read_unlock(); goto err; } ndev_vif = netdev_priv(dev); #ifdef CONFIG_SCSC_WLAN_RX_NAPI slsi_rx_data_napi(sdev, dev, skb, false); #else slsi_skb_work_enqueue(&ndev_vif->rx_data, skb); #endif rcu_read_unlock(); return 0; err: return -EINVAL; } static int sap_ma_rx_handler(struct slsi_dev *sdev, struct sk_buff *skb) { switch (fapi_get_sigid(skb)) { case MA_UNITDATA_IND: case MA_TX_FAILURE_IND: case MA_UNITDATA_CFM: return slsi_rx_queue_data(sdev, skb); case MA_BLOCKACK_IND: /* It is anomolous to handle the MA_BLOCKACK_IND in the * mlme wq. */ return slsi_rx_enqueue_netdev_mlme(sdev, skb, fapi_get_vif(skb)); default: break; } SLSI_ERR_NODEV("Shouldn't be getting here!\n"); return -EINVAL; } /* Adjust the scod value and flow control appropriately. */ static int sap_ma_txdone(struct slsi_dev *sdev, u16 colour) { struct net_device *dev; struct slsi_peer *peer; u16 vif, peer_index, ac; /* Extract information from the coloured mbulk */ /* colour is defined as: */ /* u16 register bits: * 0 - do not use * [2:1] - vif * [7:3] - peer_index * [10:8] - ac queue */ vif = (colour & 0x6) >> 1; peer_index = (colour & 0xf8) >> 3; ac = (colour & 0x300) >> 8; rcu_read_lock(); dev = slsi_get_netdev_rcu(sdev, vif); if (!dev) { SLSI_ERR(sdev, "netdev(%d) No longer exists\n", vif); rcu_read_unlock(); return -EINVAL; } rcu_read_unlock(); if (peer_index <= SLSI_PEER_INDEX_MAX) { /* peer_index = 0 for Multicast queues */ if (peer_index == 0) { struct netdev_vif *ndev_vif = netdev_priv(dev); return scsc_wifi_fcq_receive_data(dev, &ndev_vif->ap.group_data_qs, ac, sdev, vif, peer_index); } peer = slsi_get_peer_from_qs(sdev, dev, MAP_AID_TO_QS(peer_index)); if (peer) { return scsc_wifi_fcq_receive_data(dev, &peer->data_qs, ac, sdev, vif, peer_index); } else { SLSI_DBG3(sdev, SLSI_RX, "peer record NOT found for peer_index=%d\n", peer_index); /* We need to handle this case as special. Peer disappeared bug hip4 * is sending back the colours to free. */ return scsc_wifi_fcq_receive_data_no_peer(dev, ac, sdev, vif, peer_index); } } else { SLSI_ERR(sdev, "illegal peer_index vif=%d peer_index=%d\n", vif, peer_index); } return -EINVAL; } int sap_ma_init(void) { SLSI_INFO_NODEV("Registering SAP\n"); slsi_hip_sap_register(&sap_ma); return 0; } int sap_ma_deinit(void) { SLSI_INFO_NODEV("Unregistering SAP\n"); slsi_hip_sap_unregister(&sap_ma); return 0; }