blob: d370c036cffccbf777bd69e2d0508396fa2106ea [file] [log] [blame]
/*
* Broadcom Dongle Host Driver (DHD),
* Linux-specific network interface for transmit(tx) path
*
* Copyright (C) 2022, Broadcom.
*
* Unless you and Broadcom execute a separate written software license
* agreement governing use of this software, this software is licensed to you
* under the terms of the GNU General Public License version 2 (the "GPL"),
* available at http://www.broadcom.com/licenses/GPLv2.php, with the
* following added to such license:
*
* As a special exception, the copyright holders of this software give you
* permission to link this software with independent modules, and to copy and
* distribute the resulting executable under terms of your choice, provided that
* you also meet, for each linked independent module, the terms and conditions of
* the license of that module. An independent module is a module which is not
* derived from this software. The special exception does not apply to any
* modifications of the software.
*
*
* <<Broadcom-WL-IPTag/Open:>>
*
* $Id$
*/
#include <typedefs.h>
#include <linuxver.h>
#include <osl.h>
#ifdef SHOW_LOGTRACE
#include <linux/syscalls.h>
#include <event_log.h>
#endif /* SHOW_LOGTRACE */
#ifdef PCIE_FULL_DONGLE
#include <bcmmsgbuf.h>
#endif /* PCIE_FULL_DONGLE */
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/skbuff.h>
#include <linux/netdevice.h>
#include <linux/inetdevice.h>
#include <linux/rtnetlink.h>
#include <linux/etherdevice.h>
#include <linux/random.h>
#include <linux/spinlock.h>
#include <linux/ethtool.h>
#include <linux/fcntl.h>
#include <linux/fs.h>
#include <linux/ip.h>
#include <linux/reboot.h>
#include <linux/notifier.h>
#include <linux/irq.h>
#if defined(CONFIG_TIZEN)
#include <linux/net_stat_tizen.h>
#endif /* CONFIG_TIZEN */
#include <net/addrconf.h>
#ifdef ENABLE_ADAPTIVE_SCHED
#include <linux/cpufreq.h>
#endif /* ENABLE_ADAPTIVE_SCHED */
#include <linux/rtc.h>
#include <linux/namei.h>
#include <asm/uaccess.h>
#include <asm/unaligned.h>
#include <dhd_linux_priv.h>
#include <epivers.h>
#include <bcmutils.h>
#include <bcmendian.h>
#include <bcmdevs.h>
#include <bcmdevs_legacy.h> /* need to still support chips no longer in trunk firmware */
#include <bcmiov.h>
#include <bcmstdlib_s.h>
#include <ethernet.h>
#include <bcmevent.h>
#include <vlan.h>
#include <802.3.h>
#include <dhd_linux_wq.h>
#include <dhd.h>
#include <dhd_linux.h>
#include <dhd_linux_pktdump.h>
#ifdef DHD_WET
#include <dhd_wet.h>
#endif /* DHD_WET */
#ifdef PCIE_FULL_DONGLE
#include <dhd_flowring.h>
#endif
#include <dhd_bus.h>
#include <dhd_proto.h>
#include <dhd_dbg.h>
#include <dhd_dbg_ring.h>
#include <dhd_debug.h>
#if defined(WL_CFG80211)
#include <wl_cfg80211.h>
#endif /* WL_CFG80211 */
#ifdef PNO_SUPPORT
#include <dhd_pno.h>
#endif
#ifdef RTT_SUPPORT
#include <dhd_rtt.h>
#endif
#ifdef DHD_TIMESYNC
#include <dhd_timesync.h>
#include <linux/ip.h>
#include <net/icmp.h>
#endif /* DHD_TIMESYNC */
#include <dhd_linux_sock_qos.h>
#ifdef CONFIG_COMPAT
#include <linux/compat.h>
#endif
#ifdef DHD_WMF
#include <dhd_wmf_linux.h>
#endif /* DHD_WMF */
#ifdef DHD_L2_FILTER
#include <bcmicmp.h>
#include <bcm_l2_filter.h>
#include <dhd_l2_filter.h>
#endif /* DHD_L2_FILTER */
#ifdef DHD_PSTA
#include <dhd_psta.h>
#endif /* DHD_PSTA */
#ifdef AMPDU_VO_ENABLE
/* XXX: Enabling VO AMPDU to reduce FER */
#include <802.1d.h>
#endif /* AMPDU_VO_ENABLE */
#if defined(DHDTCPACK_SUPPRESS) || defined(DHDTCPSYNC_FLOOD_BLK)
#include <dhd_ip.h>
#endif /* DHDTCPACK_SUPPRESS || DHDTCPSYNC_FLOOD_BLK */
#include <dhd_daemon.h>
#ifdef DHD_PKT_LOGGING
#include <dhd_pktlog.h>
#endif /* DHD_PKT_LOGGING */
#ifdef DHD_4WAYM4_FAIL_DISCONNECT
#include <eapol.h>
#endif /* DHD_4WAYM4_FAIL_DISCONNECT */
#ifdef ENABLE_DHD_GRO
#include <net/sch_generic.h>
#endif /* ENABLE_DHD_GRO */
#ifdef FIX_CPU_MIN_CLOCK
#include <linux/pm_qos.h>
#endif /* FIX_CPU_MIN_CLOCK */
#ifdef PROP_TXSTATUS
#include <wlfc_proto.h>
#include <dhd_wlfc.h>
#endif
#include <wl_android.h>
#define WME_PRIO2AC(prio) wme_fifo2ac[prio2fifo[(prio)]]
void
dhd_tx_stop_queues(struct net_device *net)
{
#ifdef DHD_MQ
netif_tx_stop_all_queues(net);
#else
netif_stop_queue(net);
#endif
}
void
dhd_tx_start_queues(struct net_device *net)
{
#ifdef DHD_MQ
netif_tx_wake_all_queues(net);
#else
netif_wake_queue(net);
#endif
}
int
BCMFASTPATH(__dhd_sendpkt)(dhd_pub_t *dhdp, int ifidx, void *pktbuf)
{
int ret = BCME_OK;
dhd_info_t *dhd = (dhd_info_t *)(dhdp->info);
struct ether_header *eh = NULL;
uint8 pkt_flow_prio;
dhd_if_t *ifp;
unsigned long flags;
uint datalen;
DHD_GENERAL_LOCK(dhdp, flags);
ifp = dhd_get_ifp(dhdp, ifidx);
if (!ifp || ifp->del_in_progress) {
DHD_ERROR(("%s: ifp:%p del_in_progress:%d\n",
__FUNCTION__, ifp, ifp ? ifp->del_in_progress : 0));
DHD_GENERAL_UNLOCK(dhdp, flags);
PKTCFREE(dhdp->osh, pktbuf, TRUE);
return -ENODEV;
}
DHD_GENERAL_UNLOCK(dhdp, flags);
/* Reject if down */
if (!dhdp->up || (dhdp->busstate == DHD_BUS_DOWN)) {
/* free the packet here since the caller won't */
PKTCFREE(dhdp->osh, pktbuf, TRUE);
return -ENODEV;
}
#ifdef PCIE_FULL_DONGLE
if (dhdp->busstate == DHD_BUS_SUSPEND) {
DHD_ERROR(("%s : pcie is still in suspend state!!\n", __FUNCTION__));
PKTCFREE(dhdp->osh, pktbuf, TRUE);
return NETDEV_TX_BUSY;
}
#endif /* PCIE_FULL_DONGLE */
/* Reject if pktlen > MAX_MTU_SZ */
if (PKTLEN(dhdp->osh, pktbuf) > MAX_MTU_SZ) {
/* free the packet here since the caller won't */
dhdp->tx_big_packets++;
PKTCFREE(dhdp->osh, pktbuf, TRUE);
return BCME_ERROR;
}
datalen = PKTLEN(dhdp->osh, pktbuf);
#ifdef DHD_L2_FILTER
/* if dhcp_unicast is enabled, we need to convert the */
/* broadcast DHCP ACK/REPLY packets to Unicast. */
if (ifp->dhcp_unicast) {
uint8* mac_addr;
uint8* ehptr = NULL;
int ret;
ret = bcm_l2_filter_get_mac_addr_dhcp_pkt(dhdp->osh, pktbuf, ifidx, &mac_addr);
if (ret == BCME_OK) {
/* if given mac address having valid entry in sta list
* copy the given mac address, and return with BCME_OK
*/
if (dhd_find_sta(dhdp, ifidx, mac_addr)) {
ehptr = PKTDATA(dhdp->osh, pktbuf);
bcopy(mac_addr, ehptr + ETHER_DEST_OFFSET, ETHER_ADDR_LEN);
}
}
}
if (ifp->grat_arp && DHD_IF_ROLE_AP(dhdp, ifidx)) {
if (bcm_l2_filter_gratuitous_arp(dhdp->osh, pktbuf) == BCME_OK) {
PKTCFREE(dhdp->osh, pktbuf, TRUE);
return BCME_ERROR;
}
}
if (ifp->parp_enable && DHD_IF_ROLE_AP(dhdp, ifidx)) {
ret = dhd_l2_filter_pkt_handle(dhdp, ifidx, pktbuf, TRUE);
/* Drop the packets if l2 filter has processed it already
* otherwise continue with the normal path
*/
if (ret == BCME_OK) {
PKTCFREE(dhdp->osh, pktbuf, TRUE);
return BCME_ERROR;
}
}
#endif /* DHD_L2_FILTER */
/* Update multicast statistic */
if (PKTLEN(dhdp->osh, pktbuf) >= ETHER_HDR_LEN) {
uint8 *pktdata = (uint8 *)PKTDATA(dhdp->osh, pktbuf);
eh = (struct ether_header *)pktdata;
if (ETHER_ISMULTI(eh->ether_dhost))
dhdp->tx_multicast++;
if (ntoh16(eh->ether_type) == ETHER_TYPE_802_1X) {
#ifdef DHD_LOSSLESS_ROAMING
uint8 prio = (uint8)PKTPRIO(pktbuf);
/* back up 802.1x's priority */
dhdp->prio_8021x = prio;
#endif /* DHD_LOSSLESS_ROAMING */
DBG_EVENT_LOG(dhdp, WIFI_EVENT_DRIVER_EAPOL_FRAME_TRANSMIT_REQUESTED);
atomic_inc(&dhd->pend_8021x_cnt);
#if defined(WL_CFG80211) && defined(WL_WPS_SYNC)
wl_handle_wps_states(dhd_idx2net(dhdp, ifidx),
pktdata, PKTLEN(dhdp->osh, pktbuf), TRUE);
#endif /* WL_CFG80211 && WL_WPS_SYNC */
}
} else {
PKTCFREE(dhdp->osh, pktbuf, TRUE);
return BCME_ERROR;
}
#if (defined(BCM_ROUTER_DHD) && defined(QOS_MAP_SET))
if (ifp->qosmap_up_table_enable) {
pktsetprio_qms(pktbuf, ifp->qosmap_up_table, FALSE);
}
else
#endif
{
/* Look into the packet and update the packet priority */
#ifndef PKTPRIO_OVERRIDE
/* XXX RB:6270 Ignore skb->priority from TCP/IP stack */
if (PKTPRIO(pktbuf) == 0)
#endif /* !PKTPRIO_OVERRIDE */
{
#if (!defined(BCM_ROUTER_DHD) && (defined(QOS_MAP_SET) || \
defined(WL_CUSTOM_MAPPING_OF_DSCP)))
pktsetprio_qms(pktbuf, wl_get_up_table(dhdp, ifidx), FALSE);
#else
/* For LLR, pkt prio will be changed to 7(NC) here */
pktsetprio(pktbuf, FALSE);
#endif /* QOS_MAP_SET || WL_CUSTOM_MAPPING_OF_DSCP */
}
#ifndef PKTPRIO_OVERRIDE
else {
/* Some protocols like OZMO use priority values from 256..263.
* these are magic values to indicate a specific 802.1d priority.
* make sure that priority field is in range of 0..7
*/
PKTSETPRIO(pktbuf, PKTPRIO(pktbuf) & 0x7);
}
#endif /* !PKTPRIO_OVERRIDE */
}
#if defined(BCM_ROUTER_DHD)
traffic_mgmt_pkt_set_prio(dhdp, pktbuf);
#endif /* BCM_ROUTER_DHD */
BCM_REFERENCE(pkt_flow_prio);
/* Intercept and create Socket level statistics */
/*
* TODO: Some how moving this code block above the pktsetprio code
* is resetting the priority back to 0, but this does not happen for
* packets generated from iperf uisng -S option. Can't understand why.
*/
dhd_update_sock_flows(dhd, pktbuf);
#ifdef SUPPORT_SET_TID
dhd_set_tid_based_on_uid(dhdp, pktbuf);
#endif /* SUPPORT_SET_TID */
#ifdef PCIE_FULL_DONGLE
/*
* Lkup the per interface hash table, for a matching flowring. If one is not
* available, allocate a unique flowid and add a flowring entry.
* The found or newly created flowid is placed into the pktbuf's tag.
*/
#ifdef DHD_TX_PROFILE
if (dhdp->tx_profile_enab && dhdp->num_profiles > 0 &&
dhd_protocol_matches_profile(PKTDATA(dhdp->osh, pktbuf),
PKTLEN(dhdp->osh, pktbuf), dhdp->protocol_filters,
dhdp->host_sfhllc_supported)) {
/* we only have support for one tx_profile at the moment */
/* tagged packets must be put into TID 6 */
pkt_flow_prio = PRIO_8021D_VO;
} else
#endif /* defined(DHD_TX_PROFILE) */
{
pkt_flow_prio = dhdp->flow_prio_map[(PKTPRIO(pktbuf))];
}
ret = dhd_flowid_update(dhdp, ifidx, pkt_flow_prio, pktbuf);
if (ret != BCME_OK) {
PKTCFREE(dhd->pub.osh, pktbuf, TRUE);
return ret;
}
#endif /* PCIE_FULL_DONGLE */
#ifdef PROP_TXSTATUS
if (dhd_wlfc_is_supported(dhdp)) {
/* store the interface ID */
DHD_PKTTAG_SETIF(PKTTAG(pktbuf), ifidx);
/* store destination MAC in the tag as well */
DHD_PKTTAG_SETDSTN(PKTTAG(pktbuf), eh->ether_dhost);
/* decide which FIFO this packet belongs to */
if (ETHER_ISMULTI(eh->ether_dhost))
/* one additional queue index (highest AC + 1) is used for bc/mc queue */
DHD_PKTTAG_SETFIFO(PKTTAG(pktbuf), AC_COUNT);
else
DHD_PKTTAG_SETFIFO(PKTTAG(pktbuf), WME_PRIO2AC(PKTPRIO(pktbuf)));
} else
#endif /* PROP_TXSTATUS */
{
/* If the protocol uses a data header, apply it */
dhd_prot_hdrpush(dhdp, ifidx, pktbuf);
}
/* Use bus module to send data frame */
#ifdef BCMDBUS
#ifdef PROP_TXSTATUS
if (dhd_wlfc_commit_packets(dhdp, (f_commitpkt_t)dhd_dbus_txdata,
dhdp, pktbuf, TRUE) == WLFC_UNSUPPORTED) {
/* non-proptxstatus way */
ret = dhd_dbus_txdata(dhdp, pktbuf);
}
#else
ret = dhd_dbus_txdata(dhdp, pktbuf);
#endif /* PROP_TXSTATUS */
if (ret)
PKTCFREE(dhdp->osh, pktbuf, TRUE);
#else
#ifdef PROP_TXSTATUS
{
if (dhd_wlfc_commit_packets(dhdp, (f_commitpkt_t)dhd_bus_txdata,
dhdp->bus, pktbuf, TRUE) == WLFC_UNSUPPORTED) {
/* non-proptxstatus way */
#ifdef BCMPCIE
ret = dhd_bus_txdata(dhdp->bus, pktbuf, (uint8)ifidx);
#else
ret = dhd_bus_txdata(dhdp->bus, pktbuf);
#endif /* BCMPCIE */
}
}
#else
#ifdef BCMPCIE
ret = dhd_bus_txdata(dhdp->bus, pktbuf, (uint8)ifidx);
#else
ret = dhd_bus_txdata(dhdp->bus, pktbuf);
#endif /* BCMPCIE */
#endif /* PROP_TXSTATUS */
#endif /* BCMDBUS */
/* Update the packet counters here, as it is called for LB Tx and non-LB Tx too */
if (ret) {
ifp->stats.tx_dropped++;
dhdp->tx_dropped++;
} else {
#ifdef PROP_TXSTATUS
/* tx_packets counter can counted only when wlfc is disabled */
if (!dhd_wlfc_is_supported(dhdp))
#endif
{
dhdp->tx_packets++;
dhdp->tx_bytes += datalen;
ifp->stats.tx_packets++;
ifp->stats.tx_bytes += datalen;
}
dhdp->actual_tx_pkts++;
}
return ret;
}
int
BCMFASTPATH(dhd_sendpkt)(dhd_pub_t *dhdp, int ifidx, void *pktbuf)
{
int ret = 0;
unsigned long flags;
dhd_if_t *ifp;
DHD_GENERAL_LOCK(dhdp, flags);
ifp = dhd_get_ifp(dhdp, ifidx);
if (!ifp || ifp->del_in_progress) {
DHD_ERROR(("%s: ifp:%p del_in_progress:%d\n",
__FUNCTION__, ifp, ifp ? ifp->del_in_progress : 0));
DHD_GENERAL_UNLOCK(dhdp, flags);
PKTCFREE(dhdp->osh, pktbuf, TRUE);
return -ENODEV;
}
if (DHD_BUS_CHECK_DOWN_OR_DOWN_IN_PROGRESS(dhdp)) {
DHD_ERROR(("%s: returning as busstate=%d\n",
__FUNCTION__, dhdp->busstate));
DHD_GENERAL_UNLOCK(dhdp, flags);
PKTCFREE(dhdp->osh, pktbuf, TRUE);
return -ENODEV;
}
DHD_IF_SET_TX_ACTIVE(ifp, DHD_TX_SEND_PKT);
DHD_BUS_BUSY_SET_IN_SEND_PKT(dhdp);
DHD_GENERAL_UNLOCK(dhdp, flags);
#ifdef DHD_PCIE_RUNTIMEPM
if (dhdpcie_runtime_bus_wake(dhdp, FALSE, __builtin_return_address(0))) {
DHD_ERROR(("%s : pcie is still in suspend state!!\n", __FUNCTION__));
PKTCFREE(dhdp->osh, pktbuf, TRUE);
ret = -EBUSY;
goto exit;
}
#endif /* DHD_PCIE_RUNTIMEPM */
DHD_GENERAL_LOCK(dhdp, flags);
if (DHD_BUS_CHECK_SUSPEND_OR_SUSPEND_IN_PROGRESS(dhdp)) {
DHD_ERROR(("%s: bus is in suspend(%d) or suspending(0x%x) state!!\n",
__FUNCTION__, dhdp->busstate, dhdp->dhd_bus_busy_state));
DHD_BUS_BUSY_CLEAR_IN_SEND_PKT(dhdp);
DHD_IF_CLR_TX_ACTIVE(ifp, DHD_TX_SEND_PKT);
dhd_os_tx_completion_wake(dhdp);
dhd_os_busbusy_wake(dhdp);
DHD_GENERAL_UNLOCK(dhdp, flags);
PKTCFREE(dhdp->osh, pktbuf, TRUE);
return -ENODEV;
}
DHD_GENERAL_UNLOCK(dhdp, flags);
ret = __dhd_sendpkt(dhdp, ifidx, pktbuf);
#ifdef DHD_PCIE_RUNTIMEPM
exit:
#endif
DHD_GENERAL_LOCK(dhdp, flags);
DHD_BUS_BUSY_CLEAR_IN_SEND_PKT(dhdp);
DHD_IF_CLR_TX_ACTIVE(ifp, DHD_TX_SEND_PKT);
dhd_os_tx_completion_wake(dhdp);
dhd_os_busbusy_wake(dhdp);
DHD_GENERAL_UNLOCK(dhdp, flags);
return ret;
}
#ifdef DHD_MQ
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 14, 0))
static uint16
BCMFASTPATH(dhd_select_queue)(struct net_device *net, struct sk_buff *skb,
void *accel_priv, select_queue_fallback_t fallback)
#else
static uint16
BCMFASTPATH(dhd_select_queue)(struct net_device *net, struct sk_buff *skb)
#endif /* LINUX_VERSION_CODE */
{
dhd_info_t *dhd_info = DHD_DEV_INFO(net);
dhd_pub_t *dhdp = &dhd_info->pub;
uint16 prio = 0;
BCM_REFERENCE(dhd_info);
BCM_REFERENCE(dhdp);
BCM_REFERENCE(prio);
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 14, 0))
if (mq_select_disable) {
/* if driver side queue selection is disabled via sysfs, call the kernel
* supplied fallback function to select the queue, which is usually
* '__netdev_pick_tx()' in net/core/dev.c
*/
return fallback(net, skb);
}
#endif /* LINUX_VERSION */
prio = dhdp->flow_prio_map[skb->priority];
if (prio < AC_COUNT)
return prio;
else
return AC_BK;
}
#endif /* DHD_MQ */
netdev_tx_t
BCMFASTPATH(dhd_start_xmit)(struct sk_buff *skb, struct net_device *net)
{
int ret;
void *pktbuf;
dhd_info_t *dhd = DHD_DEV_INFO(net);
dhd_if_t *ifp = NULL;
int ifidx;
unsigned long flags;
#if !defined(BCM_ROUTER_DHD)
uint8 htsfdlystat_sz = 0;
#endif /* ! BCM_ROUTER_DHD */
#ifdef DHD_WMF
struct ether_header *eh;
uint8 *iph;
#endif /* DHD_WMF */
#if defined(DHD_MQ) && defined(DHD_MQ_STATS)
int qidx = 0;
int cpuid = 0;
int prio = 0;
#endif /* DHD_MQ && DHD_MQ_STATS */
DHD_TRACE(("%s: Enter\n", __FUNCTION__));
#if defined(DHD_MQ) && defined(DHD_MQ_STATS)
qidx = skb_get_queue_mapping(skb);
/* if in a non pre-emptable context, smp_processor_id can be used
* else get_cpu and put_cpu should be used
*/
if (!CAN_SLEEP()) {
cpuid = smp_processor_id();
}
else {
cpuid = get_cpu();
put_cpu();
}
prio = dhd->pub.flow_prio_map[skb->priority];
DHD_TRACE(("%s: Q idx = %d, CPU = %d, prio = %d \n", __FUNCTION__,
qidx, cpuid, prio));
dhd->pktcnt_qac_histo[qidx][prio]++;
dhd->pktcnt_per_ac[prio]++;
dhd->cpu_qstats[qidx][cpuid]++;
#endif /* DHD_MQ && DHD_MQ_STATS */
if (dhd_query_bus_erros(&dhd->pub)) {
return -ENODEV;
}
DHD_GENERAL_LOCK(&dhd->pub, flags);
DHD_BUS_BUSY_SET_IN_TX(&dhd->pub);
DHD_GENERAL_UNLOCK(&dhd->pub, flags);
#ifdef DHD_PCIE_RUNTIMEPM
if (dhdpcie_runtime_bus_wake(&dhd->pub, FALSE, dhd_start_xmit)) {
/* In order to avoid pkt loss. Return NETDEV_TX_BUSY until run-time resumed. */
/* stop the network queue temporarily until resume done */
DHD_GENERAL_LOCK(&dhd->pub, flags);
if (!dhdpcie_is_resume_done(&dhd->pub)) {
dhd_bus_stop_queue(dhd->pub.bus);
}
DHD_BUS_BUSY_CLEAR_IN_TX(&dhd->pub);
dhd_os_busbusy_wake(&dhd->pub);
DHD_GENERAL_UNLOCK(&dhd->pub, flags);
return NETDEV_TX_BUSY;
}
#endif /* DHD_PCIE_RUNTIMEPM */
DHD_GENERAL_LOCK(&dhd->pub, flags);
if (DHD_BUS_CHECK_SUSPEND_OR_SUSPEND_IN_PROGRESS(&dhd->pub)) {
DHD_ERROR_RLMT(("%s: bus is in suspend(%d) or suspending(0x%x) state!!\n",
__FUNCTION__, dhd->pub.busstate, dhd->pub.dhd_bus_busy_state));
DHD_BUS_BUSY_CLEAR_IN_TX(&dhd->pub);
#ifdef PCIE_FULL_DONGLE
/* Stop tx queues if suspend is in progress or suspended */
if (DHD_BUS_CHECK_ANY_SUSPEND_IN_PROGRESS(&dhd->pub) ||
(dhd->pub.busstate == DHD_BUS_SUSPEND &&
!DHD_BUS_BUSY_CHECK_RESUME_IN_PROGRESS(&dhd->pub))) {
dhd_bus_stop_queue(dhd->pub.bus);
}
#endif /* PCIE_FULL_DONGLE */
dhd_os_busbusy_wake(&dhd->pub);
DHD_GENERAL_UNLOCK(&dhd->pub, flags);
return NETDEV_TX_BUSY;
}
DHD_OS_WAKE_LOCK(&dhd->pub);
#if defined(DHD_HANG_SEND_UP_TEST)
if (dhd->pub.req_hang_type == HANG_REASON_BUS_DOWN) {
DHD_ERROR(("%s: making DHD_BUS_DOWN\n", __FUNCTION__));
dhd->pub.busstate = DHD_BUS_DOWN;
}
#endif /* DHD_HANG_SEND_UP_TEST */
/* Reject if down */
/* XXX kernel panic issue when first bootup time,
* rmmod without interface down make unnecessary hang event.
*/
if (dhd->pub.hang_was_sent || DHD_BUS_CHECK_DOWN_OR_DOWN_IN_PROGRESS(&dhd->pub)) {
DHD_ERROR(("%s: xmit rejected pub.up=%d busstate=%d \n",
__FUNCTION__, dhd->pub.up, dhd->pub.busstate));
dhd_tx_stop_queues(net);
/* Send Event when bus down detected during data session */
if (dhd->pub.up && !dhd->pub.hang_was_sent) {
DHD_ERROR(("%s: Event HANG sent up\n", __FUNCTION__));
dhd->pub.hang_reason = HANG_REASON_BUS_DOWN;
net_os_send_hang_message(net);
}
DHD_BUS_BUSY_CLEAR_IN_TX(&dhd->pub);
dhd_os_busbusy_wake(&dhd->pub);
DHD_GENERAL_UNLOCK(&dhd->pub, flags);
DHD_OS_WAKE_UNLOCK(&dhd->pub);
return NETDEV_TX_BUSY;
}
ifp = DHD_DEV_IFP(net);
ifidx = DHD_DEV_IFIDX(net);
#ifdef DHD_BUZZZ_LOG_ENABLED
BUZZZ_LOG(START_XMIT_BGN, 2, (uint32)ifidx, (uintptr)skb);
#endif /* DHD_BUZZZ_LOG_ENABLED */
if (ifidx == DHD_BAD_IF) {
DHD_ERROR(("%s: bad ifidx %d\n", __FUNCTION__, ifidx));
dhd_tx_stop_queues(net);
DHD_BUS_BUSY_CLEAR_IN_TX(&dhd->pub);
dhd_os_busbusy_wake(&dhd->pub);
DHD_GENERAL_UNLOCK(&dhd->pub, flags);
DHD_OS_WAKE_UNLOCK(&dhd->pub);
return NETDEV_TX_BUSY;
}
#ifdef RPM_FAST_TRIGGER
/* Xmit is running reset RPM fast trigger */
if (dhd->pub.rpm_fast_trigger) {
DHD_ERROR(("%s: Reset RPM fast trigger\n", __FUNCTION__));
dhd->pub.rpm_fast_trigger = FALSE;
}
#endif /* RPM_FAST_TRIGGER */
DHD_GENERAL_UNLOCK(&dhd->pub, flags);
/* If tput test is in progress */
if (dhd->pub.tput_data.tput_test_running) {
return NETDEV_TX_BUSY;
}
ASSERT(ifidx == dhd_net2idx(dhd, net));
ASSERT((ifp != NULL) && ((ifidx < DHD_MAX_IFS) && (ifp == dhd->iflist[ifidx])));
bcm_object_trace_opr(skb, BCM_OBJDBG_ADD_PKT, __FUNCTION__, __LINE__);
/* re-align socket buffer if "skb->data" is odd address */
if (((unsigned long)(skb->data)) & 0x1) {
unsigned char *data = skb->data;
uint32 length = skb->len;
PKTPUSH(dhd->pub.osh, skb, 1);
memmove(skb->data, data, length);
PKTSETLEN(dhd->pub.osh, skb, length);
}
/* Make sure there's enough room for any header */
#if !defined(BCM_ROUTER_DHD)
if (skb_headroom(skb) < dhd->pub.hdrlen + htsfdlystat_sz) {
struct sk_buff *skb2;
DHD_INFO(("%s: insufficient headroom\n",
dhd_ifname(&dhd->pub, ifidx)));
dhd->pub.tx_realloc++;
bcm_object_trace_opr(skb, BCM_OBJDBG_REMOVE, __FUNCTION__, __LINE__);
skb2 = skb_realloc_headroom(skb, dhd->pub.hdrlen + htsfdlystat_sz);
dev_kfree_skb(skb);
if ((skb = skb2) == NULL) {
DHD_ERROR(("%s: skb_realloc_headroom failed\n",
dhd_ifname(&dhd->pub, ifidx)));
ret = -ENOMEM;
goto done;
}
bcm_object_trace_opr(skb, BCM_OBJDBG_ADD_PKT, __FUNCTION__, __LINE__);
}
#endif /* !BCM_ROUTER_DHD */
/* Convert to packet */
if (!(pktbuf = PKTFRMNATIVE(dhd->pub.osh, skb))) {
DHD_ERROR(("%s: PKTFRMNATIVE failed\n",
dhd_ifname(&dhd->pub, ifidx)));
bcm_object_trace_opr(skb, BCM_OBJDBG_REMOVE, __FUNCTION__, __LINE__);
dev_kfree_skb_any(skb);
ret = -ENOMEM;
goto done;
}
#ifdef DHD_WET
/* wet related packet proto manipulation should be done in DHD
since dongle doesn't have complete payload
*/
if (WET_ENABLED(&dhd->pub) &&
(dhd_wet_send_proc(dhd->pub.wet_info, pktbuf, &pktbuf) < 0)) {
DHD_INFO(("%s:%s: wet send proc failed\n",
__FUNCTION__, dhd_ifname(&dhd->pub, ifidx)));
PKTFREE(dhd->pub.osh, pktbuf, FALSE);
ret = -EFAULT;
goto done;
}
#endif /* DHD_WET */
#ifdef DHD_WMF
eh = (struct ether_header *)PKTDATA(dhd->pub.osh, pktbuf);
iph = (uint8 *)eh + ETHER_HDR_LEN;
/* WMF processing for multicast packets
* Only IPv4 packets are handled
*/
if (ifp->wmf.wmf_enable && (ntoh16(eh->ether_type) == ETHER_TYPE_IP) &&
(IP_VER(iph) == IP_VER_4) && (ETHER_ISMULTI(eh->ether_dhost) ||
((IPV4_PROT(iph) == IP_PROT_IGMP) && dhd->pub.wmf_ucast_igmp))) {
#if defined(DHD_IGMP_UCQUERY) || defined(DHD_UCAST_UPNP)
void *sdu_clone;
bool ucast_convert = FALSE;
#ifdef DHD_UCAST_UPNP
uint32 dest_ip;
dest_ip = ntoh32(*((uint32 *)(iph + IPV4_DEST_IP_OFFSET)));
ucast_convert = dhd->pub.wmf_ucast_upnp && MCAST_ADDR_UPNP_SSDP(dest_ip);
#endif /* DHD_UCAST_UPNP */
#ifdef DHD_IGMP_UCQUERY
ucast_convert |= dhd->pub.wmf_ucast_igmp_query &&
(IPV4_PROT(iph) == IP_PROT_IGMP) &&
(*(iph + IPV4_HLEN(iph)) == IGMPV2_HOST_MEMBERSHIP_QUERY);
#endif /* DHD_IGMP_UCQUERY */
if (ucast_convert) {
dhd_sta_t *sta;
unsigned long flags;
struct list_head snapshot_list;
struct list_head *wmf_ucforward_list;
ret = NETDEV_TX_OK;
/* For non BCM_GMAC3 platform we need a snapshot sta_list to
* resolve double DHD_IF_STA_LIST_LOCK call deadlock issue.
*/
wmf_ucforward_list = DHD_IF_WMF_UCFORWARD_LOCK(dhd, ifp, &snapshot_list);
GCC_DIAGNOSTIC_PUSH_SUPPRESS_CAST();
/* Convert upnp/igmp query to unicast for each assoc STA */
list_for_each_entry(sta, wmf_ucforward_list, list) {
GCC_DIAGNOSTIC_POP();
/* Skip sending to proxy interfaces of proxySTA */
if (sta->psta_prim != NULL && !ifp->wmf_psta_disable) {
continue;
}
if ((sdu_clone = PKTDUP(dhd->pub.osh, pktbuf)) == NULL) {
ret = WMF_NOP;
break;
}
dhd_wmf_forward(ifp->wmf.wmfh, sdu_clone, 0, sta, 1);
}
DHD_IF_WMF_UCFORWARD_UNLOCK(dhd, wmf_ucforward_list);
DHD_GENERAL_LOCK(&dhd->pub, flags);
DHD_BUS_BUSY_CLEAR_IN_TX(&dhd->pub);
dhd_os_busbusy_wake(&dhd->pub);
DHD_GENERAL_UNLOCK(&dhd->pub, flags);
DHD_OS_WAKE_UNLOCK(&dhd->pub);
if (ret == NETDEV_TX_OK)
PKTFREE(dhd->pub.osh, pktbuf, TRUE);
return ret;
} else
#endif /* defined(DHD_IGMP_UCQUERY) || defined(DHD_UCAST_UPNP) */
{
/* There will be no STA info if the packet is coming from LAN host
* Pass as NULL
*/
ret = dhd_wmf_packets_handle(&dhd->pub, pktbuf, NULL, ifidx, 0);
switch (ret) {
case WMF_TAKEN:
case WMF_DROP:
/* Either taken by WMF or we should drop it.
* Exiting send path
*/
DHD_GENERAL_LOCK(&dhd->pub, flags);
DHD_BUS_BUSY_CLEAR_IN_TX(&dhd->pub);
dhd_os_busbusy_wake(&dhd->pub);
DHD_GENERAL_UNLOCK(&dhd->pub, flags);
DHD_OS_WAKE_UNLOCK(&dhd->pub);
return NETDEV_TX_OK;
default:
/* Continue the transmit path */
break;
}
}
}
#endif /* DHD_WMF */
#ifdef DHD_PSTA
/* PSR related packet proto manipulation should be done in DHD
* since dongle doesn't have complete payload
*/
if (PSR_ENABLED(&dhd->pub) &&
#ifdef BCM_ROUTER_DHD
!(ifp->primsta_dwds) &&
#endif /* BCM_ROUTER_DHD */
(dhd_psta_proc(&dhd->pub, ifidx, &pktbuf, TRUE) < 0)) {
DHD_ERROR(("%s:%s: psta send proc failed\n", __FUNCTION__,
dhd_ifname(&dhd->pub, ifidx)));
}
#endif /* DHD_PSTA */
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 19, 0) && defined(DHD_TCP_PACING_SHIFT)
#ifndef DHD_DEFAULT_TCP_PACING_SHIFT
#define DHD_DEFAULT_TCP_PACING_SHIFT 7
#endif /* DHD_DEFAULT_TCP_PACING_SHIFT */
if (skb->sk) {
sk_pacing_shift_update(skb->sk, DHD_DEFAULT_TCP_PACING_SHIFT);
}
#endif /* LINUX_VERSION_CODE >= 4.19.0 && DHD_TCP_PACING_SHIFT */
#ifdef DHDTCPSYNC_FLOOD_BLK
if (dhd_tcpdata_get_flag(&dhd->pub, pktbuf) == FLAG_SYNCACK) {
ifp->tsyncack_txed ++;
}
#endif /* DHDTCPSYNC_FLOOD_BLK */
#ifdef DHDTCPACK_SUPPRESS
if (dhd->pub.tcpack_sup_mode == TCPACK_SUP_HOLD) {
/* If this packet has been hold or got freed, just return */
if (dhd_tcpack_hold(&dhd->pub, pktbuf, ifidx)) {
ret = 0;
goto done;
}
} else {
/* If this packet has replaced another packet and got freed, just return */
if (dhd_tcpack_suppress(&dhd->pub, pktbuf)) {
ret = 0;
goto done;
}
}
#endif /* DHDTCPACK_SUPPRESS */
/*
* If Load Balance is enabled queue the packet
* else send directly from here.
*/
#if defined(DHD_LB_TXP)
ret = dhd_lb_sendpkt(dhd, net, ifidx, pktbuf);
#else
ret = __dhd_sendpkt(&dhd->pub, ifidx, pktbuf);
#endif
BCM_REFERENCE(ret);
done:
DHD_GENERAL_LOCK(&dhd->pub, flags);
DHD_BUS_BUSY_CLEAR_IN_TX(&dhd->pub);
DHD_IF_CLR_TX_ACTIVE(ifp, DHD_TX_START_XMIT);
dhd_os_tx_completion_wake(&dhd->pub);
dhd_os_busbusy_wake(&dhd->pub);
DHD_GENERAL_UNLOCK(&dhd->pub, flags);
DHD_OS_WAKE_UNLOCK(&dhd->pub);
#ifdef DHD_BUZZZ_LOG_ENABLED
BUZZZ_LOG(START_XMIT_END, 0);
#endif /* DHD_BUZZZ_LOG_ENABLED */
/* Return ok: we always eat the packet */
return NETDEV_TX_OK;
}
static void
__dhd_txflowcontrol(dhd_pub_t *dhdp, struct net_device *net, bool state)
{
if (net->reg_state != NETREG_REGISTERED) {
return;
}
if (state == ON) {
if (!netif_queue_stopped(net)) {
DHD_TXFLOWCTL(("%s: Stop Netif Queue\n", __FUNCTION__));
netif_stop_queue(net);
} else {
DHD_LOG_MEM(("%s: Netif Queue already stopped\n", __FUNCTION__));
}
}
if (state == OFF) {
if (netif_queue_stopped(net)) {
DHD_TXFLOWCTL(("%s: Start Netif Queue\n", __FUNCTION__));
netif_wake_queue(net);
} else {
DHD_LOG_MEM(("%s: Netif Queue already started\n", __FUNCTION__));
}
}
}
void
dhd_txflowcontrol(dhd_pub_t *dhdp, int ifidx, bool state)
{
struct net_device *net;
dhd_info_t *dhd = dhdp->info;
int i;
DHD_TRACE(("%s: Enter\n", __FUNCTION__));
ASSERT(dhd);
#ifdef DHD_LOSSLESS_ROAMING
/* block flowcontrol during roaming */
if ((dhdp->dequeue_prec_map == (1 << dhdp->flow_prio_map[PRIO_8021D_NC])) && (state == ON))
{
DHD_ERROR_RLMT(("%s: Roaming in progress, cannot stop network queue (0x%x:%d)\n",
__FUNCTION__, dhdp->dequeue_prec_map, dhdp->flow_prio_map[PRIO_8021D_NC]));
return;
}
#endif
if (ifidx == ALL_INTERFACES) {
for (i = 0; i < DHD_MAX_IFS; i++) {
if (dhd->iflist[i]) {
net = dhd->iflist[i]->net;
__dhd_txflowcontrol(dhdp, net, state);
}
}
} else {
if (dhd->iflist[ifidx]) {
net = dhd->iflist[ifidx]->net;
__dhd_txflowcontrol(dhdp, net, state);
}
}
dhdp->txoff = state;
}
void
dhd_txcomplete(dhd_pub_t *dhdp, void *txp, bool success)
{
dhd_info_t *dhd = (dhd_info_t *)(dhdp->info);
struct ether_header *eh;
uint16 type;
if (dhdp->tput_data.tput_test_running) {
dhdp->batch_tx_pkts_cmpl++;
/* don't count the stop pkt */
if (success &&
dhdp->batch_tx_pkts_cmpl <= dhdp->batch_tx_num_pkts)
dhdp->tput_data.pkts_good++;
else if (!success)
dhdp->tput_data.pkts_bad++;
/* we dont care for the stop packet in tput test */
if (dhdp->batch_tx_pkts_cmpl == dhdp->batch_tx_num_pkts) {
dhdp->tput_stop_ts = OSL_SYSUPTIME_US();
dhdp->tput_data.pkts_cmpl += dhdp->batch_tx_pkts_cmpl;
dhdp->tput_data.num_pkts += dhdp->batch_tx_num_pkts;
dhd_os_tput_test_wake(dhdp);
}
}
/* XXX where does this stuff belong to? */
dhd_prot_hdrpull(dhdp, NULL, txp, NULL, NULL);
/* XXX Use packet tag when it is available to identify its type */
eh = (struct ether_header *)PKTDATA(dhdp->osh, txp);
type = ntoh16(eh->ether_type);
if (type == ETHER_TYPE_802_1X) {
atomic_dec(&dhd->pend_8021x_cnt);
}
#ifdef PROP_TXSTATUS
if (dhdp->wlfc_state && (dhdp->proptxstatus_mode != WLFC_FCMODE_NONE)) {
dhd_if_t *ifp = dhd->iflist[DHD_PKTTAG_IF(PKTTAG(txp))];
uint datalen = PKTLEN(dhd->pub.osh, txp);
if (ifp != NULL) {
if (success) {
dhd->pub.tx_packets++;
dhd->pub.tx_bytes += datalen;
ifp->stats.tx_packets++;
ifp->stats.tx_bytes += datalen;
} else {
ifp->stats.tx_dropped++;
}
}
}
#endif
if (success) {
dhd->pub.tot_txcpl++;
}
}
void
dhd_os_tx_completion_wake(dhd_pub_t *dhd)
{
/* Call wmb() to make sure before waking up the other event value gets updated */
OSL_SMP_WMB();
wake_up(&dhd->tx_completion_wait);
}
void
dhd_os_sdlock_txq(dhd_pub_t *pub)
{
dhd_info_t *dhd;
dhd = (dhd_info_t *)(pub->info);
#ifdef BCMDBUS
spin_lock_irqsave(&dhd->txqlock, dhd->txqlock_flags);
#else
spin_lock_bh(&dhd->txqlock);
#endif
}
void
dhd_os_sdunlock_txq(dhd_pub_t *pub)
{
dhd_info_t *dhd;
dhd = (dhd_info_t *)(pub->info);
#ifdef BCMDBUS
spin_unlock_irqrestore(&dhd->txqlock, dhd->txqlock_flags);
#else
spin_unlock_bh(&dhd->txqlock);
#endif
}
void
dhd_txfl_wake_lock_timeout(dhd_pub_t *pub, int val)
{
#ifdef CONFIG_HAS_WAKELOCK
dhd_info_t *dhd = (dhd_info_t *)(pub->info);
if (dhd) {
dhd_wake_lock_timeout(dhd->wl_txflwake, msecs_to_jiffies(val));
}
#endif /* CONFIG_HAS_WAKE_LOCK */
}
#ifdef DHD_4WAYM4_FAIL_DISCONNECT
void
dhd_eap_txcomplete(dhd_pub_t *dhdp, void *txp, bool success, int ifidx)
{
dhd_info_t *dhd = (dhd_info_t *)(dhdp->info);
struct ether_header *eh;
uint16 type;
/* XXX where does this stuff belong to? */
dhd_prot_hdrpull(dhdp, NULL, txp, NULL, NULL);
/* XXX Use packet tag when it is available to identify its type */
eh = (struct ether_header *)PKTDATA(dhdp->osh, txp);
type = ntoh16(eh->ether_type);
if (type == ETHER_TYPE_802_1X) {
if (dhd_is_4way_msg((uint8 *)eh) == EAPOL_4WAY_M4) {
dhd_if_t *ifp = NULL;
ifp = dhd->iflist[ifidx];
if (!ifp || !ifp->net) {
return;
}
if (!success) {
DHD_INFO(("%s: M4 TX failed on %d.\n",
__FUNCTION__, ifidx));
OSL_ATOMIC_SET(dhdp->osh, &ifp->m4state, M4_TXFAILED);
schedule_delayed_work(&ifp->m4state_work,
msecs_to_jiffies(MAX_4WAY_TIMEOUT_MS));
} else {
cancel_delayed_work(&ifp->m4state_work);
}
}
}
}
#endif /* DHD_4WAYM4_FAIL_DISCONNECT */
void
dhd_handle_pktdata(dhd_pub_t *dhdp, int ifidx, void *pkt, uint8 *pktdata, uint32 pktid,
uint32 pktlen, uint16 *pktfate, uint8 *dhd_udr, uint8 *dhd_igmp,
bool tx, int pkt_wake, bool pkt_log)
{
struct ether_header *eh;
uint16 ether_type;
uint32 pkthash;
uint8 pkt_type = PKT_TYPE_DATA;
#ifdef DHD_PKT_LOGGING_DBGRING
bool verbose_logging = FALSE;
dhd_dbg_ring_t *ring;
ring = &dhdp->dbg->dbg_rings[PACKET_LOG_RING_ID];
#endif /* DHD_PKT_LOGGING_DBGRING */
if (!pktdata || pktlen < ETHER_HDR_LEN) {
return;
}
eh = (struct ether_header *)pktdata;
ether_type = ntoh16(eh->ether_type);
/* Check packet type */
if (dhd_check_ip_prot(pktdata, ether_type)) {
if (dhd_check_dhcp(pktdata)) {
pkt_type = PKT_TYPE_DHCP;
} else if (dhd_check_icmp(pktdata)) {
pkt_type = PKT_TYPE_ICMP;
} else if (dhd_check_dns(pktdata)) {
pkt_type = PKT_TYPE_DNS;
}
}
else if (ether_type == ETHER_TYPE_IPV6) {
if (dhd_check_icmpv6(pktdata, pktlen)) {
pkt_type = PKT_TYPE_ICMPV6;
}
}
else if (dhd_check_arp(pktdata, ether_type)) {
pkt_type = PKT_TYPE_ARP;
}
else if (ether_type == ETHER_TYPE_802_1X) {
pkt_type = PKT_TYPE_EAP;
}
#ifdef DHD_PKT_LOGGING_DBGRING
do {
if (!OSL_ATOMIC_READ(dhdp->osh, &dhdp->pktlog->enable)) {
struct dhd_pktlog_ring *pktlog_ring;
pktlog_ring = dhdp->pktlog->pktlog_ring;
if (pktlog_ring->pktcount <= DHD_PACKET_LOG_RING_RESUME_THRESHOLD) {
dhd_pktlog_resume(dhdp);
} else {
/* If pktlog disabled(suspeneded), only allowed TXS update */
if (tx && pktfate) {
DHD_PKTLOG_TXS(dhdp, pkt, pktdata, pktid, *pktfate);
pkthash = __dhd_dbg_pkt_hash((uintptr_t)pkt, pktid);
}
dhd_os_dbg_urgent_pullreq(dhdp->dbg->private,
PACKET_LOG_RING_ID);
break;
}
}
/* Allow logging for all packets without pktlog filter */
if (ring->log_level == RING_LOG_LEVEL_EXCESSIVE) {
verbose_logging = TRUE;
pkt_log = TRUE;
/* Not allow logging for any packets */
} else if (ring->log_level == RING_LOG_LEVEL_NONE) {
verbose_logging = FALSE;
pkt_log = FALSE;
}
#endif /* DHD_PKT_LOGGING_DBGRING */
#ifdef DHD_SBN
/* Set UDR based on packet type */
if (dhd_udr && (pkt_type == PKT_TYPE_DHCP ||
pkt_type == PKT_TYPE_DNS ||
pkt_type == PKT_TYPE_ARP)) {
*dhd_udr = TRUE;
}
#endif /* DHD_SBN */
#ifdef DHD_PKT_LOGGING
#ifdef DHD_SKIP_PKTLOGGING_FOR_DATA_PKTS
if (pkt_type != PKT_TYPE_DATA)
#else
#ifdef DHD_PKT_LOGGING_DBGRING
if ((verbose_logging == TRUE) || (pkt_type != PKT_TYPE_DATA))
#endif /* DHD_PKT_LOGGING_DBGRING */
#endif /* DHD_PKT_LOGGING */
{
if (pkt_log) {
if (tx) {
if (pktfate) {
/* Tx status */
DHD_PKTLOG_TXS(dhdp, pkt, pktdata, pktid, *pktfate);
} else {
/* Tx packet */
DHD_PKTLOG_TX(dhdp, pkt, pktdata, pktid);
}
pkthash = __dhd_dbg_pkt_hash((uintptr_t)pkt, pktid);
} else {
struct sk_buff *skb = (struct sk_buff *)pkt;
if (pkt_wake) {
DHD_PKTLOG_WAKERX(dhdp, skb, pktdata);
} else {
DHD_PKTLOG_RX(dhdp, skb, pktdata);
}
}
}
}
#endif /* DHD_PKT_LOGGING */
#ifdef DHD_PKT_LOGGING_DBGRING
} while (FALSE);
#endif /* DHD_PKT_LOGGING_DBGRING */
/* Dump packet data */
switch (pkt_type) {
case PKT_TYPE_DHCP:
dhd_dhcp_dump(dhdp, ifidx, pktdata, tx, &pkthash, pktfate);
dhd_send_supp_dhcp(dhdp, ifidx, pktdata, tx, pktfate);
break;
case PKT_TYPE_ICMP:
dhd_icmp_dump(dhdp, ifidx, pktdata, tx, &pkthash, pktfate);
break;
case PKT_TYPE_DNS:
dhd_dns_dump(dhdp, ifidx, pktdata, tx, &pkthash, pktfate);
break;
case PKT_TYPE_ARP:
dhd_arp_dump(dhdp, ifidx, pktdata, tx, &pkthash, pktfate);
break;
case PKT_TYPE_EAP:
dhd_dump_eapol_message(dhdp, ifidx, pktdata, pktlen, tx, &pkthash, pktfate);
dhd_send_supp_eap(dhdp, ifidx, pktdata, pktlen, tx, pktfate);
break;
default:
break;
}
}