blob: c77a7ea5fd1ae11621f46146085bcec407f7e71e [file] [log] [blame]
/*
* Copyright (c) 2011 The Linux Foundation. All rights reserved.
*
* Previously licensed under the ISC license by Qualcomm Atheros, Inc.
*
*
* Permission to use, copy, modify, and/or distribute this software for
* any purpose with or without fee is hereby granted, provided that the
* above copyright notice and this permission notice appear in all
* copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
* WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
* AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
* DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
* PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
* TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/
/*
* This file was originally distributed by Qualcomm Atheros, Inc.
* under proprietary terms before Copyright ownership was assigned
* to the Linux Foundation.
*/
/* standard header files */
#include <adf_nbuf.h> /* adf_nbuf_map */
#include <adf_os_mem.h> /* adf_os_mem_cmp */
/* external header files */
#include <ol_cfg.h> /* wlan_op_mode_ap, etc. */
#include <ol_htt_rx_api.h> /* htt_rx_msdu_desc_retrieve */
#include <ieee80211_common.h> /* ieee80211_frame, etc. */
/* internal header files */
#include <ol_txrx_types.h> /* ol_txrx_dev_t, etc. */
#include <ol_rx_fwd.h> /* our own defs */
#include <ol_rx.h> /* ol_rx_deliver */
#include <ol_txrx_internal.h> /* TXRX_ASSERT1 */
/*
* Porting from Ap11PrepareForwardedPacket.
* This routine is called when a RX data frame from an associated station is
* to be forwarded to another associated station. We will prepare the
* received packet so that it is suitable for transmission again.
* Check that this Packet is suitable for forwarding. If yes, then
* prepare the new 802.11 header.
*/
static inline
void
ol_ap_fwd_check(struct ol_txrx_vdev_t *vdev, adf_nbuf_t msdu)
{
struct ieee80211_frame *mac_header;
unsigned char tmp_addr[6];
unsigned char type;
unsigned char subtype;
unsigned char fromds;
unsigned char tods;
mac_header = (struct ieee80211_frame *) (adf_nbuf_data(msdu));
TXRX_ASSERT1(mac_header);
type = mac_header->i_fc[0] & IEEE80211_FC0_TYPE_MASK;
subtype = mac_header->i_fc[0] & IEEE80211_FC0_SUBTYPE_MASK;
tods = mac_header->i_fc[1] & IEEE80211_FC1_DIR_TODS;
fromds = mac_header->i_fc[1] & IEEE80211_FC1_DIR_FROMDS;
/*
* Make sure no QOS or any other non-data subtype
* Should be a ToDs data frame.
* Make sure that this frame is unicast and not for us.
* These packets should come up through the normal rx path and not forwarded.
*/
if (type != IEEE80211_FC0_TYPE_DATA ||
subtype != 0x0 ||
((tods != 1) || (fromds != 0)) ||
(adf_os_mem_cmp(
mac_header->i_addr3, vdev->mac_addr.raw, IEEE80211_ADDR_LEN) == 0))
{
#ifdef DEBUG_HOST_RC
TXRX_PRINT(TXRX_PRINT_LEVEL_INFO1, "Exit: %s | Unnecessary to adjust mac header\n", __func__);
#endif
}
else
{
// Flip the ToDs bit to FromDs
mac_header->i_fc[1] &= 0xfe;
mac_header->i_fc[1] |= 0x2;
/*
* Flip the addresses
* (ToDs, addr1, RA=BSSID) move to (FrDs, addr2, TA=BSSID)
* (ToDs, addr2, SA) move to (FrDs, addr3, SA)
* (ToDs, addr3, DA) move to (FrDs, addr1, DA)
*/
memcpy(tmp_addr,
mac_header->i_addr2,
sizeof (tmp_addr));
memcpy(mac_header->i_addr2,
mac_header->i_addr1,
sizeof (tmp_addr));
memcpy(mac_header->i_addr1,
mac_header->i_addr3,
sizeof (tmp_addr));
memcpy(mac_header->i_addr3,
tmp_addr,
sizeof (tmp_addr));
}
}
static inline
void
ol_rx_fwd_to_tx(struct ol_txrx_vdev_t *vdev, adf_nbuf_t msdu)
{
struct ol_txrx_pdev_t *pdev = vdev->pdev;
if (pdev->frame_format == wlan_frm_fmt_native_wifi)
{
ol_ap_fwd_check(vdev, msdu);
}
/*
* Map the netbuf, so it's accessible to the DMA that
* sends it to the target.
*/
adf_nbuf_map_single(pdev->osdev, msdu, ADF_OS_DMA_TO_DEVICE);
adf_nbuf_set_next(msdu, NULL); /* add NULL terminator */
TXRX_STATS_MSDU_INCR(vdev->pdev, rx.forwarded, msdu);
msdu = vdev->tx(vdev, msdu);
if (msdu) {
/*
* The frame was not accepted by the tx.
* We could store the frame and try again later,
* but the simplest solution is to discard the frames.
*/
adf_nbuf_unmap_single(pdev->osdev, msdu, ADF_OS_DMA_TO_DEVICE);
adf_nbuf_free(msdu);
}
}
void
ol_rx_fwd_check(
struct ol_txrx_vdev_t *vdev,
struct ol_txrx_peer_t *peer,
unsigned tid,
adf_nbuf_t msdu_list)
{
struct ol_txrx_pdev_t *pdev = vdev->pdev;
adf_nbuf_t deliver_list_head = NULL;
adf_nbuf_t deliver_list_tail = NULL;
adf_nbuf_t msdu;
msdu = msdu_list;
while (msdu) {
struct ol_txrx_vdev_t *tx_vdev;
void *rx_desc;
/*
* Remember the next list elem, because our processing
* may cause the MSDU to get linked into a different list.
*/
msdu_list = adf_nbuf_next(msdu);
rx_desc = htt_rx_msdu_desc_retrieve(pdev->htt_pdev, msdu);
if (htt_rx_msdu_forward(pdev->htt_pdev, rx_desc)) {
/*
* Use the same vdev that received the frame to
* transmit the frame.
* This is exactly what we want for intra-BSS forwarding,
* like STA-to-STA forwarding and multicast echo.
* If this is a intra-BSS forwarding case (which is not
* currently supported), then the tx vdev is different
* from the rx vdev.
* On the LL host the vdevs are not actually used for tx,
* so it would still work to use the rx vdev rather than
* the tx vdev.
* For HL, the tx classification searches for the DA within
* the given vdev, so we would want to get the DA peer ID
* from the target, so we can locate the tx vdev.
*/
tx_vdev = vdev;
/* Copying TID value of RX packet to forwarded
* packet
*/
adf_nbuf_set_tid(msdu, tid);
/*
* This MSDU needs to be forwarded to the tx path.
* Check whether it also needs to be sent to the OS shim,
* in which case we need to make a copy (or clone?).
*/
if (htt_rx_msdu_discard(pdev->htt_pdev, rx_desc)) {
htt_rx_msdu_desc_free(pdev->htt_pdev, msdu);
ol_rx_fwd_to_tx(tx_vdev, msdu);
msdu = NULL; /* already handled this MSDU */
} else {
adf_nbuf_t copy;
copy = adf_nbuf_copy(msdu);
if (copy) {
ol_rx_fwd_to_tx(tx_vdev, copy);
}
}
}
if (msdu) {
/* send this frame to the OS */
OL_TXRX_LIST_APPEND(deliver_list_head, deliver_list_tail, msdu);
}
msdu = msdu_list;
}
if (deliver_list_head) {
adf_nbuf_set_next(deliver_list_tail, NULL); /* add NULL terminator */
ol_rx_deliver(vdev, peer, tid, deliver_list_head);
}
}