blob: b3f4106395dfaf814d4df9921d5ee9aad5b271e7 [file] [log] [blame]
/*
* Copyright (c) 2011, 2015-2017 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.
*/
#include <adf_nbuf.h> /* adf_nbuf_t */
#include <ol_htt_rx_api.h> /* htt_rx_pn_t, etc. */
#include <ol_ctrl_txrx_api.h> /* ol_rx_err */
#include <ol_txrx_internal.h> /* ol_rx_mpdu_list_next */
#include <ol_txrx_types.h> /* ol_txrx_vdev_t, etc. */
#include <ol_rx_pn.h> /* our own defs */
#include <ol_rx_fwd.h> /* ol_rx_fwd_check */
#include <ol_rx.h> /* ol_rx_deliver */
/* add the MSDUs from this MPDU to the list of good frames */
#define ADD_MPDU_TO_LIST(head, tail, mpdu, mpdu_tail) do { \
if (!head) { \
head = mpdu; \
} else { \
adf_nbuf_set_next(tail, mpdu); \
} \
tail = mpdu_tail; \
} while(0);
int ol_rx_pn_cmp24(
union htt_rx_pn_t *new_pn,
union htt_rx_pn_t *old_pn,
int is_unicast,
int opmode)
{
return ((new_pn->pn24 & 0xffffff) <= (old_pn->pn24 & 0xffffff));
}
int ol_rx_pn_cmp48(
union htt_rx_pn_t *new_pn,
union htt_rx_pn_t *old_pn,
int is_unicast,
int opmode)
{
return
((new_pn->pn48 & 0xffffffffffffULL) <=
(old_pn->pn48 & 0xffffffffffffULL));
}
int ol_rx_pn_wapi_cmp(
union htt_rx_pn_t *new_pn,
union htt_rx_pn_t *old_pn,
int is_unicast,
int opmode)
{
int pn_is_replay = 0;
if (new_pn->pn128[1] == old_pn->pn128[1]) {
pn_is_replay = (new_pn->pn128[0] <= old_pn->pn128[0]);
} else {
pn_is_replay = (new_pn->pn128[1] < old_pn->pn128[1]);
}
if (is_unicast) {
if (opmode == wlan_op_mode_ap) {
pn_is_replay |= ((new_pn->pn128[0] & 0x1ULL) != 0);
} else {
pn_is_replay |= ((new_pn->pn128[0] & 0x1ULL) != 1);
}
}
return pn_is_replay;
}
adf_nbuf_t
ol_rx_pn_check_base(
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;
union htt_rx_pn_t *last_pn;
adf_nbuf_t out_list_head = NULL;
adf_nbuf_t out_list_tail = NULL;
adf_nbuf_t mpdu;
int index; /* unicast vs. multicast */
int pn_len;
void *rx_desc;
int last_pn_valid;
enum pn_replay_type replay_type = OL_RX_OTHER_REPLAYS;
/* Make sure host pn check is not redundant */
if ((adf_os_atomic_read(&peer->fw_pn_check)) ||
(vdev->opmode == wlan_op_mode_ibss)) {
return msdu_list;
}
/* First, check whether the PN check applies */
rx_desc = htt_rx_msdu_desc_retrieve(pdev->htt_pdev, msdu_list);
adf_os_assert(htt_rx_msdu_has_wlan_mcast_flag(pdev->htt_pdev, rx_desc));
index = htt_rx_msdu_is_wlan_mcast(pdev->htt_pdev, rx_desc) ?
txrx_sec_mcast : txrx_sec_ucast;
pn_len = pdev->rx_pn[peer->security[index].sec_type].len;
if (peer->security[index].sec_type == htt_sec_type_tkip ||
peer->security[index].sec_type == htt_sec_type_tkip_nomic) {
replay_type = OL_RX_TKIP_REPLAYS;
} else if (peer->security[index].sec_type == htt_sec_type_aes_ccmp) {
replay_type = OL_RX_CCMP_REPLAYS;
}
if (pn_len == 0) {
return msdu_list;
}
last_pn_valid = peer->tids_last_pn_valid[tid];
last_pn = &peer->tids_last_pn[tid];
mpdu = msdu_list;
while (mpdu) {
adf_nbuf_t mpdu_tail, next_mpdu;
union htt_rx_pn_t new_pn;
int pn_is_replay = 0;
rx_desc = htt_rx_msdu_desc_retrieve(pdev->htt_pdev, mpdu);
/*
* Find the last MSDU within this MPDU, and
* the find the first MSDU within the next MPDU.
*/
ol_rx_mpdu_list_next(pdev, mpdu, &mpdu_tail, &next_mpdu);
/* Don't check the PN replay for non-encrypted frames */
if (!htt_rx_mpdu_is_encrypted(pdev->htt_pdev, rx_desc)) {
ADD_MPDU_TO_LIST(out_list_head, out_list_tail, mpdu, mpdu_tail);
mpdu = next_mpdu;
continue;
}
/* retrieve PN from rx descriptor */
htt_rx_mpdu_desc_pn(pdev->htt_pdev, rx_desc, &new_pn, pn_len);
/* if there was no prior PN, there's nothing to check */
if (last_pn_valid) {
pn_is_replay = pdev->rx_pn[peer->security[index].sec_type].cmp(
&new_pn, last_pn, index == txrx_sec_ucast, vdev->opmode);
} else {
last_pn_valid = peer->tids_last_pn_valid[tid] = 1;
}
if (pn_is_replay) {
adf_nbuf_t msdu;
static u_int32_t last_pncheck_print_time = 0;
int log_level;
u_int32_t current_time_ms;
/*
* This MPDU failed the PN check:
* 1. Notify the control SW of the PN failure
* (so countermeasures can be taken, if necessary)
* 2. Discard all the MSDUs from this MPDU.
*/
msdu = mpdu;
current_time_ms = adf_os_ticks_to_msecs(adf_os_ticks());
if (TXRX_PN_CHECK_FAILURE_PRINT_PERIOD_MS <
(current_time_ms - last_pncheck_print_time)) {
last_pncheck_print_time = current_time_ms;
log_level = TXRX_PRINT_LEVEL_WARN;
}
else {
log_level = TXRX_PRINT_LEVEL_INFO2;
}
TXRX_PRINT(log_level,
"PN check failed - TID %d, peer %pK "
"(%02x:%02x:%02x:%02x:%02x:%02x) %s\n"
" old PN (u64 x2)= 0x%08llx %08llx (LSBs = %lld)\n"
" new PN (u64 x2)= 0x%08llx %08llx (LSBs = %lld)\n"
" new seq num = %d\n",
tid, peer,
peer->mac_addr.raw[0], peer->mac_addr.raw[1],
peer->mac_addr.raw[2], peer->mac_addr.raw[3],
peer->mac_addr.raw[4], peer->mac_addr.raw[5],
(index == txrx_sec_ucast) ? "ucast" : "mcast",
last_pn->pn128[1],
last_pn->pn128[0],
last_pn->pn128[0] & 0xffffffffffffULL,
new_pn.pn128[1],
new_pn.pn128[0],
new_pn.pn128[0] & 0xffffffffffffULL,
htt_rx_mpdu_desc_seq_num(pdev->htt_pdev, rx_desc));
#if defined(ENABLE_RX_PN_TRACE)
ol_rx_pn_trace_display(pdev, 1);
#endif /* ENABLE_RX_PN_TRACE */
ol_rx_err(
pdev->ctrl_pdev,
vdev->vdev_id, peer->mac_addr.raw, tid,
htt_rx_mpdu_desc_tsf32(pdev->htt_pdev, rx_desc),
OL_RX_ERR_PN, mpdu, NULL, 0);
/* free all MSDUs within this MPDU */
do {
adf_nbuf_t next_msdu;
OL_RX_ERR_STATISTICS_1(pdev, vdev, peer, rx_desc, OL_RX_ERR_PN);
next_msdu = adf_nbuf_next(msdu);
htt_rx_desc_frame_free(pdev->htt_pdev, msdu);
pdev->pn_replays[replay_type]++;
if (msdu == mpdu_tail) {
break;
} else {
msdu = next_msdu;
}
} while (1);
} else {
ADD_MPDU_TO_LIST(out_list_head, out_list_tail, mpdu, mpdu_tail);
/*
* Remember the new PN.
* For simplicity, just do 2 64-bit word copies to cover the worst
* case (WAPI), regardless of the length of the PN.
* This is more efficient than doing a conditional branch to copy
* only the relevant portion.
*/
last_pn->pn128[0] = new_pn.pn128[0];
last_pn->pn128[1] = new_pn.pn128[1];
OL_RX_PN_TRACE_ADD(pdev, peer, tid, rx_desc);
}
mpdu = next_mpdu;
}
/* make sure the list is null-terminated */
if (out_list_tail) {
adf_nbuf_set_next(out_list_tail, NULL);
}
return out_list_head;
}
void
ol_rx_pn_check(
struct ol_txrx_vdev_t *vdev,
struct ol_txrx_peer_t *peer,
unsigned tid,
adf_nbuf_t msdu_list)
{
msdu_list = ol_rx_pn_check_base(vdev, peer, tid, msdu_list);
ol_rx_fwd_check(vdev, peer, tid, msdu_list);
}
void
ol_rx_pn_check_only(
struct ol_txrx_vdev_t *vdev,
struct ol_txrx_peer_t *peer,
unsigned tid,
adf_nbuf_t msdu_list)
{
msdu_list = ol_rx_pn_check_base(vdev, peer, tid, msdu_list);
ol_rx_deliver(vdev, peer, tid, msdu_list);
}
#if defined(ENABLE_RX_PN_TRACE)
A_STATUS
ol_rx_pn_trace_attach(ol_txrx_pdev_handle pdev)
{
int num_elems;
num_elems = 1 << TXRX_RX_PN_TRACE_SIZE_LOG2;
pdev->rx_pn_trace.idx = 0;
pdev->rx_pn_trace.cnt = 0;
pdev->rx_pn_trace.mask = num_elems - 1;
pdev->rx_pn_trace.data = adf_os_mem_alloc(
pdev->osdev, sizeof(*pdev->rx_pn_trace.data) * num_elems);
if (! pdev->rx_pn_trace.data) {
return A_ERROR;
}
return A_OK;
}
void
ol_rx_pn_trace_detach(ol_txrx_pdev_handle pdev)
{
adf_os_mem_free(pdev->rx_pn_trace.data);
}
void
ol_rx_pn_trace_add(
struct ol_txrx_pdev_t *pdev,
struct ol_txrx_peer_t *peer,
u_int16_t tid,
void *rx_desc)
{
u_int32_t idx = pdev->rx_pn_trace.idx;
union htt_rx_pn_t pn;
u_int32_t pn32;
u_int16_t seq_num;
u_int8_t unicast;
htt_rx_mpdu_desc_pn(pdev->htt_pdev, rx_desc, &pn, 48);
pn32 = pn.pn48 & 0xffffffff;
seq_num = htt_rx_mpdu_desc_seq_num(pdev->htt_pdev, rx_desc);
unicast = ! htt_rx_msdu_is_wlan_mcast(pdev->htt_pdev, rx_desc);
pdev->rx_pn_trace.data[idx].peer = peer;
pdev->rx_pn_trace.data[idx].tid = tid;
pdev->rx_pn_trace.data[idx].seq_num = seq_num;
pdev->rx_pn_trace.data[idx].unicast = unicast;
pdev->rx_pn_trace.data[idx].pn32 = pn32;
pdev->rx_pn_trace.cnt++;
idx++;
pdev->rx_pn_trace.idx = idx & pdev->rx_pn_trace.mask;
}
void
ol_rx_pn_trace_display(ol_txrx_pdev_handle pdev, int just_once)
{
static int print_count = 0;
u_int32_t i, start, end;
u_int64_t cnt;
int elems;
int limit = 0; /* move this to the arg list? */
if (print_count != 0 && just_once) {
return;
}
print_count++;
end = pdev->rx_pn_trace.idx;
if (pdev->rx_pn_trace.cnt <= pdev->rx_pn_trace.mask) {
/* trace log has not yet wrapped around - start at the top */
start = 0;
cnt = 0;
} else {
start = end;
cnt = pdev->rx_pn_trace.cnt - (pdev->rx_pn_trace.mask + 1);
}
elems = (end - 1 - start) & pdev->rx_pn_trace.mask;
if (limit > 0 && elems > limit) {
int delta;
delta = elems - limit;
start += delta;
start &= pdev->rx_pn_trace.mask;
cnt += delta;
}
i = start;
VOS_TRACE(VOS_MODULE_ID_TXRX, VOS_TRACE_LEVEL_INFO,
" seq PN\n");
VOS_TRACE(VOS_MODULE_ID_TXRX, VOS_TRACE_LEVEL_INFO,
" count idx peer tid uni num LSBs\n");
do {
VOS_TRACE(VOS_MODULE_ID_TXRX, VOS_TRACE_LEVEL_INFO,
" %6lld %4d %pK %2d %d %4d %8d\n",
cnt, i,
pdev->rx_pn_trace.data[i].peer,
pdev->rx_pn_trace.data[i].tid,
pdev->rx_pn_trace.data[i].unicast,
pdev->rx_pn_trace.data[i].seq_num,
pdev->rx_pn_trace.data[i].pn32);
cnt++;
i++;
i &= pdev->rx_pn_trace.mask;
} while (i != end);
}
#endif /* ENABLE_RX_PN_TRACE */