Code that handles parsing raw Rx data buffer from HW and, splitting
it in to SKBs and handing them to MAC80211.
Rx handling starts at cc33xx_rx. Full SKBs are stored at
cc->deferred_rx_queue from where they are handed to MAC80211 by calling
cc->netstack_work (cc33xx_netstack_work @ main.c). This allows
calling ieee80211_rx_ni while new data is being read from HW.
Signed-off-by: Michael Nemanov <michael.nemanov@ti.com>
---
drivers/net/wireless/ti/cc33xx/rx.c | 388 ++++++++++++++++++++++++++++
drivers/net/wireless/ti/cc33xx/rx.h | 86 ++++++
2 files changed, 474 insertions(+)
create mode 100644 drivers/net/wireless/ti/cc33xx/rx.c
create mode 100644 drivers/net/wireless/ti/cc33xx/rx.h
diff --git a/drivers/net/wireless/ti/cc33xx/rx.c b/drivers/net/wireless/ti/cc33xx/rx.c
new file mode 100644
index 000000000000..b6ee293fbb0b
--- /dev/null
+++ b/drivers/net/wireless/ti/cc33xx/rx.c
@@ -0,0 +1,388 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2022-2024 Texas Instruments Incorporated - https://www.ti.com/
+ */
+
+#include "acx.h"
+#include "rx.h"
+#include "tx.h"
+#include "io.h"
+
+#define RSSI_LEVEL_BITMASK 0x7F
+#define ANT_DIVERSITY_BITMASK BIT(7)
+#define ANT_DIVERSITY_SHIFT 7
+
+/* Construct the rx status structure for upper layers */
+static void cc33xx_rx_status(struct cc33xx *cc,
+ struct cc33xx_rx_descriptor *desc,
+ struct ieee80211_rx_status *status,
+ u8 beacon, u8 probe_rsp)
+{
+ memset(status, 0, sizeof(struct ieee80211_rx_status));
+
+ if ((desc->flags & CC33XX_RX_DESC_BAND_MASK) == CC33XX_RX_DESC_BAND_BG)
+ status->band = NL80211_BAND_2GHZ;
+ else if ((desc->flags & CC33XX_RX_DESC_BAND_MASK) == CC33XX_RX_DESC_BAND_J)
+ status->band = NL80211_BAND_2GHZ;
+ else if ((desc->flags & CC33XX_RX_DESC_BAND_MASK) == CC33XX_RX_DESC_BAND_A)
+ status->band = NL80211_BAND_5GHZ;
+ else
+ status->band = NL80211_BAND_5GHZ; /* todo -Should be 6GHZ when added */
+
+ status->rate_idx = cc33xx_rate_to_idx(cc, desc->rate, status->band);
+
+ if (desc->frame_format == CC33xx_VHT)
+ status->encoding = RX_ENC_VHT;
+ else if ((desc->frame_format == CC33xx_HT_MF) ||
+ (desc->frame_format == CC33xx_HT_GF))
+ status->encoding = RX_ENC_HT;
+ else if ((desc->frame_format == CC33xx_B_SHORT) ||
+ (desc->frame_format == CC33xx_B_LONG) ||
+ (desc->frame_format == CC33xx_LEGACY_OFDM))
+ status->encoding = RX_ENC_LEGACY;
+ else
+ status->encoding = RX_ENC_HE;
+
+ /* Read the signal level and antenna diversity indication.
+ * The msb in the signal level is always set as it is a
+ * negative number.
+ * The antenna indication is the msb of the rssi.
+ */
+ status->signal = ((desc->rssi & RSSI_LEVEL_BITMASK) | BIT(7));
+ status->antenna = ((desc->rssi & ANT_DIVERSITY_BITMASK) >> ANT_DIVERSITY_SHIFT);
+ status->freq = ieee80211_channel_to_frequency(desc->channel,
+ status->band);
+
+ if (desc->flags & CC33XX_RX_DESC_ENCRYPT_MASK) {
+ u8 desc_err_code = desc->status & CC33XX_RX_DESC_STATUS_MASK;
+
+ /* Frame is sent to driver with the IV (for PN replay check)
+ * but without the MIC
+ */
+ status->flag |= RX_FLAG_MMIC_STRIPPED |
+ RX_FLAG_DECRYPTED | RX_FLAG_MIC_STRIPPED;
+
+ if (unlikely(desc_err_code & CC33XX_RX_DESC_MIC_FAIL)) {
+ status->flag |= RX_FLAG_MMIC_ERROR;
+ cc33xx_warning("Michael MIC error. Desc: 0x%x",
+ desc_err_code);
+ }
+ }
+
+ if (beacon || probe_rsp)
+ status->boottime_ns = ktime_get_boottime_ns();
+
+ if (beacon)
+ cc33xx_set_pending_regdomain_ch(cc, (u16)desc->channel,
+ status->band);
+ status->nss = 1;
+}
+
+/* Copy part\ all of the descriptor. Allocate skb, or drop corrupted packet
+ */
+static int cc33xx_rx_get_packet_descriptor(struct cc33xx *cc, u8 *raw_buffer_ptr,
+ u16 *raw_buffer_len)
+{
+ u16 missing_desc_bytes;
+ u16 available_desc_bytes;
+ u16 pkt_data_len;
+ struct sk_buff *skb;
+ u16 prev_buffer_len = *raw_buffer_len;
+
+ missing_desc_bytes = sizeof(struct cc33xx_rx_descriptor);
+ missing_desc_bytes -= cc->partial_rx.handled_bytes;
+ available_desc_bytes = min(*raw_buffer_len, missing_desc_bytes);
+ memcpy(((u8 *)(&cc->partial_rx.desc)) + cc->partial_rx.handled_bytes,
+ raw_buffer_ptr, available_desc_bytes);
+
+ /* If descriptor was not completed */
+ if (available_desc_bytes != missing_desc_bytes) {
+ cc->partial_rx.handled_bytes += *raw_buffer_len;
+ cc->partial_rx.status = CURR_RX_DESC;
+ *raw_buffer_len = 0;
+ goto out;
+ } else {
+ cc->partial_rx.handled_bytes += available_desc_bytes;
+ *raw_buffer_len -= available_desc_bytes;
+ }
+
+ /* Descriptor was fully copied */
+ pkt_data_len = cc->partial_rx.original_bytes;
+ pkt_data_len -= sizeof(struct cc33xx_rx_descriptor);
+
+ if (unlikely(cc->partial_rx.desc.status & CC33XX_RX_DESC_DECRYPT_FAIL)) {
+ cc33xx_warning("corrupted packet in RX: status: 0x%x len: %d",
+ cc->partial_rx.desc.status & CC33XX_RX_DESC_STATUS_MASK,
+ pkt_data_len);
+
+ /* If frame can be fully dropped */
+ if (pkt_data_len <= *raw_buffer_len) {
+ *raw_buffer_len -= pkt_data_len;
+ cc->partial_rx.status = CURR_RX_START;
+ } else {
+ cc->partial_rx.handled_bytes += *raw_buffer_len;
+ cc->partial_rx.status = CURR_RX_DROP;
+ *raw_buffer_len = 0;
+ }
+ goto out;
+ }
+
+ skb = __dev_alloc_skb(pkt_data_len, GFP_KERNEL);
+ if (!skb) {
+ cc33xx_error("Couldn't allocate RX frame");
+ /* If frame can be fully dropped */
+ if (pkt_data_len <= *raw_buffer_len) {
+ *raw_buffer_len -= pkt_data_len;
+ cc->partial_rx.status = CURR_RX_START;
+ } else {
+ /* Dropped partial frame */
+ cc->partial_rx.handled_bytes += *raw_buffer_len;
+ cc->partial_rx.status = CURR_RX_DROP;
+ *raw_buffer_len = 0;
+ }
+ goto out;
+ }
+
+ cc->partial_rx.skb = skb;
+ cc->partial_rx.status = CURR_RX_DATA;
+
+out:
+ /* Function return the amount of consumed bytes */
+ return (prev_buffer_len - *raw_buffer_len);
+}
+
+/* Copy part or all of the packet's data. push skb to queue if possible */
+static int cc33xx_rx_get_packet_data(struct cc33xx *cc, u8 *raw_buffer_ptr,
+ u16 *raw_buffer_len)
+{
+ u16 missing_data_bytes;
+ u16 available_data_bytes;
+ u32 defer_count;
+ enum cc33xx_rx_buf_align rx_align;
+ u16 extra_bytes;
+ struct ieee80211_hdr *hdr;
+ u8 beacon = 0;
+ u8 is_probe_resp = 0;
+ u16 seq_num;
+ u16 prev_buffer_len = *raw_buffer_len;
+
+ missing_data_bytes = cc->partial_rx.original_bytes;
+ missing_data_bytes -= cc->partial_rx.handled_bytes;
+ available_data_bytes = min(missing_data_bytes, *raw_buffer_len);
+
+ skb_put_data(cc->partial_rx.skb, raw_buffer_ptr, available_data_bytes);
+
+ /* Check if we didn't manage to copy the entire packet - got out,
+ * continue next time
+ */
+ if (available_data_bytes != missing_data_bytes) {
+ cc->partial_rx.handled_bytes += *raw_buffer_len;
+ cc->partial_rx.status = CURR_RX_DATA;
+ *raw_buffer_len = 0;
+ goto out;
+ } else {
+ *raw_buffer_len -= available_data_bytes;
+ }
+
+ /* Data fully copied */
+
+ rx_align = cc->partial_rx.desc.header_alignment;
+ if (rx_align == CC33XX_RX_BUF_PADDED)
+ skb_pull(cc->partial_rx.skb, RX_BUF_ALIGN);
+
+ extra_bytes = cc->partial_rx.desc.pad_len;
+ if (extra_bytes != 0)
+ skb_trim(cc->partial_rx.skb,
+ cc->partial_rx.skb->len - extra_bytes);
+
+ hdr = (struct ieee80211_hdr *)cc->partial_rx.skb->data;
+
+ if (ieee80211_is_beacon(hdr->frame_control))
+ beacon = 1;
+ if (ieee80211_is_probe_resp(hdr->frame_control))
+ is_probe_resp = 1;
+
+ cc33xx_rx_status(cc, &cc->partial_rx.desc,
+ IEEE80211_SKB_RXCB(cc->partial_rx.skb),
+ beacon, is_probe_resp);
+
+ seq_num = (le16_to_cpu(hdr->seq_ctrl) & IEEE80211_SCTL_SEQ) >> 4;
+ cc33xx_debug(DEBUG_RX, "rx skb 0x%p: %d B %s seq %d link id %d",
+ cc->partial_rx.skb,
+ cc->partial_rx.skb->len - cc->partial_rx.desc.pad_len,
+ beacon ? "beacon" : "", seq_num, cc->partial_rx.desc.hlid);
+
+ cc33xx_debug(DEBUG_RX, "rx frame. frame type 0x%x, frame length 0x%x, frame address 0x%lx",
+ hdr->frame_control, cc->partial_rx.skb->len,
+ (unsigned long)cc->partial_rx.skb->data);
+
+ /* Adding frame to queue */
+ skb_queue_tail(&cc->deferred_rx_queue, cc->partial_rx.skb);
+ cc->rx_counter++;
+ cc->partial_rx.status = CURR_RX_START;
+
+ /* Make sure the deferred queues don't get too long */
+ defer_count = skb_queue_len(&cc->deferred_tx_queue);
+ defer_count += skb_queue_len(&cc->deferred_rx_queue);
+ if (defer_count >= CC33XX_RX_QUEUE_MAX_LEN)
+ cc33xx_flush_deferred_work(cc);
+ else
+ queue_work(cc->freezable_netstack_wq, &cc->netstack_work);
+
+out:
+ return (prev_buffer_len - *raw_buffer_len);
+}
+
+static int cc33xx_rx_drop_packet_data(struct cc33xx *cc, u8 *raw_buffer_ptr,
+ u16 *raw_buffer_len)
+{
+ u16 prev_buffer_len = *raw_buffer_len;
+
+ /* Can we drop the entire frame ? */
+ if (*raw_buffer_len >=
+ (cc->partial_rx.original_bytes - cc->partial_rx.handled_bytes)) {
+ *raw_buffer_len -= cc->partial_rx.original_bytes -
+ cc->partial_rx.handled_bytes;
+ cc->partial_rx.handled_bytes = 0;
+ cc->partial_rx.status = CURR_RX_START;
+ } else {
+ cc->partial_rx.handled_bytes += *raw_buffer_len;
+ *raw_buffer_len = 0;
+ }
+
+ return (prev_buffer_len - *raw_buffer_len);
+}
+
+/* Handle single packet from the RX buffer. We don't have to be aligned to
+ * packet boundary (buffer may start \ end in the middle of packet)
+ */
+static void cc33xx_rx_handle_packet(struct cc33xx *cc, u8 *raw_buffer_ptr,
+ u16 *raw_buffer_len)
+{
+ struct cc33xx_rx_descriptor *desc;
+ u16 consumed_bytes;
+
+ if (cc->partial_rx.status == CURR_RX_START) {
+ WARN_ON(*raw_buffer_len < 2);
+ desc = (struct cc33xx_rx_descriptor *)raw_buffer_ptr;
+ cc->partial_rx.original_bytes = le16_to_cpu(desc->length);
+ cc->partial_rx.handled_bytes = 0;
+ cc->partial_rx.status = CURR_RX_DESC;
+
+ cc33xx_debug(DEBUG_RX, "rx frame. desc length 0x%x, alignment 0x%x, padding 0x%x",
+ desc->length, desc->header_alignment, desc->pad_len);
+ }
+
+ /* start \ continue copy descriptor */
+ if (cc->partial_rx.status == CURR_RX_DESC) {
+ consumed_bytes = cc33xx_rx_get_packet_descriptor(cc,
+ raw_buffer_ptr,
+ raw_buffer_len);
+ raw_buffer_ptr += consumed_bytes;
+ }
+
+ /* Check if we are in the middle of dropped packet */
+ if (unlikely(cc->partial_rx.status == CURR_RX_DROP)) {
+ consumed_bytes = cc33xx_rx_drop_packet_data(cc, raw_buffer_ptr,
+ raw_buffer_len);
+ raw_buffer_ptr += consumed_bytes;
+ }
+
+ /* start \ continue copy descriptor */
+ if (cc->partial_rx.status == CURR_RX_DATA) {
+ consumed_bytes = cc33xx_rx_get_packet_data(cc, raw_buffer_ptr,
+ raw_buffer_len);
+ raw_buffer_ptr += consumed_bytes;
+ }
+}
+
+/* It is assumed that SDIO buffer was read prior to this function (data buffer
+ * is read along with the status). The RX function gets pointer to the RX data
+ * and its length. This buffer may contain unknown number of packets, separated
+ * by hif descriptor and 0-3 bytes padding if required.
+ * The last packet may be truncated in the middle, and should be saved for next
+ * iteration.
+ */
+int cc33xx_rx(struct cc33xx *cc, u8 *rx_buf_ptr, u16 rx_buf_len)
+{
+ u16 local_rx_buffer_len = rx_buf_len;
+ u16 pkt_offset = 0;
+ u16 consumed_bytes;
+ u16 prev_rx_buf_len;
+
+ /* Split data into separate packets */
+ while (local_rx_buffer_len > 0) {
+ cc33xx_debug(DEBUG_RX, "start loop. buffer length %d",
+ local_rx_buffer_len);
+
+ /* the handle data call can only fail in memory-outage
+ * conditions, in that case the received frame will just
+ * be dropped.
+ */
+ prev_rx_buf_len = local_rx_buffer_len;
+ cc33xx_rx_handle_packet(cc, rx_buf_ptr + pkt_offset,
+ &local_rx_buffer_len);
+ consumed_bytes = prev_rx_buf_len - local_rx_buffer_len;
+
+ pkt_offset += consumed_bytes;
+
+ cc33xx_debug(DEBUG_RX, "end rx loop. buffer length %d, packet counter %d, current packet status %d",
+ local_rx_buffer_len, cc->rx_counter,
+ cc->partial_rx.status);
+ }
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+int cc33xx_rx_filter_enable(struct cc33xx *cc, int index, bool enable,
+ struct cc33xx_rx_filter *filter)
+{
+ int ret;
+
+ if (!!test_bit(index, cc->rx_filter_enabled) == enable) {
+ cc33xx_warning("Request to enable an already enabled rx filter %d",
+ index);
+ return 0;
+ }
+
+ ret = cc33xx_acx_set_rx_filter(cc, index, enable, filter);
+
+ if (ret) {
+ cc33xx_error("Failed to %s rx data filter %d (err=%d)",
+ enable ? "enable" : "disable", index, ret);
+ return ret;
+ }
+
+ if (enable)
+ __set_bit(index, cc->rx_filter_enabled);
+ else
+ __clear_bit(index, cc->rx_filter_enabled);
+
+ return 0;
+}
+
+int cc33xx_rx_filter_clear_all(struct cc33xx *cc)
+{
+ int i, ret = 0;
+
+ for (i = 0; i < CC33XX_MAX_RX_FILTERS; i++) {
+ if (!test_bit(i, cc->rx_filter_enabled))
+ continue;
+ ret = cc33xx_rx_filter_enable(cc, i, 0, NULL);
+ if (ret)
+ goto out;
+ }
+
+out:
+ return ret;
+}
+#else
+int cc33xx_rx_filter_enable(struct cc33xx *cc, int index, bool enable,
+ struct cc33xx_rx_filter *filter)
+{
+ return 0;
+}
+
+int cc33xx_rx_filter_clear_all(struct cc33xx *cc) { return 0; }
+#endif /* CONFIG_PM */
diff --git a/drivers/net/wireless/ti/cc33xx/rx.h b/drivers/net/wireless/ti/cc33xx/rx.h
new file mode 100644
index 000000000000..46ff6867749f
--- /dev/null
+++ b/drivers/net/wireless/ti/cc33xx/rx.h
@@ -0,0 +1,86 @@
+/* SPDX-License-Identifier: GPL-2.0-only
+ *
+ * Copyright (C) 2022-2024 Texas Instruments Incorporated - https://www.ti.com/
+ */
+
+#ifndef __RX_H__
+#define __RX_H__
+
+/* RX Descriptor flags:
+ *
+ * Bits 0-1 - band
+ * Bit 2 - STBC
+ * Bit 3 - A-MPDU
+ * Bit 4 - HT
+ * Bits 5-7 - encryption
+ */
+#define CC33XX_RX_DESC_BAND_MASK 0x03
+#define CC33XX_RX_DESC_ENCRYPT_MASK 0xE0
+
+#define CC33XX_RX_DESC_BAND_BG 0x00
+#define CC33XX_RX_DESC_BAND_J 0x01
+#define CC33XX_RX_DESC_BAND_A 0x02
+
+/* RX Descriptor status
+ *
+ * Bits 0-2 - error code
+ * Bits 3-5 - process_id tag (AP mode FW)
+ * Bits 6-7 - reserved
+ */
+enum {
+ CC33XX_RX_DESC_SUCCESS = 0x00,
+ CC33XX_RX_DESC_DECRYPT_FAIL = 0x01,
+ CC33XX_RX_DESC_MIC_FAIL = 0x02,
+ CC33XX_RX_DESC_STATUS_MASK = 0x07
+};
+
+/* Account for the padding inserted by the FW in case of RX_ALIGNMENT
+ * or for fixing alignment in case the packet wasn't aligned.
+ */
+#define RX_BUF_ALIGN 2
+
+/* Describes the alignment state of a Rx buffer */
+enum cc33xx_rx_buf_align {
+ CC33XX_RX_BUF_ALIGNED,
+ CC33XX_RX_BUF_UNALIGNED,
+ CC33XX_RX_BUF_PADDED,
+};
+
+enum cc33xx_rx_curr_status {
+ CURR_RX_START,
+ CURR_RX_DROP,
+ CURR_RX_DESC,
+ CURR_RX_DATA
+};
+
+struct cc33xx_rx_descriptor {
+ __le16 length;
+ u8 header_alignment;
+ u8 status;
+ __le32 timestamp;
+
+ u8 flags;
+ u8 rate;
+ u8 channel;
+ s8 rssi;
+ u8 snr;
+
+ u8 hlid;
+ u8 pad_len;
+ u8 frame_format;
+} __packed;
+
+struct partial_rx_frame {
+ struct sk_buff *skb;
+ struct cc33xx_rx_descriptor desc;
+ u16 handled_bytes;
+ u16 original_bytes; /* including descriptor */
+ enum cc33xx_rx_curr_status status;
+};
+
+int cc33xx_rx(struct cc33xx *cc, u8 *rx_buf_ptr, u16 rx_buf_len);
+int cc33xx_rx_filter_enable(struct cc33xx *cc, int index, bool enable,
+ struct cc33xx_rx_filter *filter);
+int cc33xx_rx_filter_clear_all(struct cc33xx *cc);
+
+#endif /* __RX_H__ */
--
2.34.1
© 2016 - 2024 Red Hat, Inc.