@@ -8,6 +8,25 @@
#include "reg.h"
#include "debug.h"
+void rtw_fw_c2h_cmd_handle_ext(struct rtw_dev *rtwdev, struct sk_buff *skb)
+{
+ struct rtw_c2h_cmd *c2h;
+ u32 pkt_offset;
+ u8 sub_cmd_id;
+
+ pkt_offset = *((u32 *)skb->cb);
+ c2h = (struct rtw_c2h_cmd *)(skb->data + pkt_offset);
+ sub_cmd_id = c2h->payload[0];
+
+ switch (sub_cmd_id) {
+ case C2H_CCX_RPT:
+ rtw_tx_report_handle(rtwdev, skb);
+ break;
+ default:
+ break;
+ }
+}
+
void rtw_fw_c2h_cmd_handle(struct rtw_dev *rtwdev, struct sk_buff *skb)
{
struct rtw_c2h_cmd *c2h;
@@ -23,7 +42,7 @@ void rtw_fw_c2h_cmd_handle(struct rtw_dev *rtwdev, struct sk_buff *skb)
switch (c2h->id) {
case C2H_HALMAC:
- /* halmac needs rx_desc + c2h payload */
+ rtw_fw_c2h_cmd_handle_ext(rtwdev, skb);
break;
default:
break;
@@ -40,6 +40,10 @@ enum rtw_c2h_cmd_id {
C2H_HALMAC = 0xff,
};
+enum rtw_c2h_cmd_id_ext {
+ C2H_CCX_RPT = 0x0f,
+};
+
struct rtw_c2h_cmd {
u8 id;
u8 seq;
@@ -80,6 +84,10 @@ struct rtw_rsvd_page {
bool add_txdesc;
};
+/* C2H */
+#define GET_CCX_REPORT_SEQNUM(c2h_payload) (c2h_payload[8] & 0xfc)
+#define GET_CCX_REPORT_STATUS(c2h_payload) (c2h_payload[9] & 0xc0)
+
/* PKT H2C */
#define H2C_PKT_CMD_ID 0xFF
#define H2C_PKT_CATEGORY 0x01
@@ -1082,14 +1082,19 @@ int rtw_core_init(struct rtw_dev *rtwdev)
INIT_LIST_HEAD(&rtwdev->rsvd_page_list);
+ timer_setup(&rtwdev->tx_report.purge_timer,
+ rtw_tx_report_purge_timer, 0);
+
INIT_DELAYED_WORK(&rtwdev->watch_dog_work, rtw_watch_dog_work);
INIT_DELAYED_WORK(&rtwdev->lps_work, rtw_lps_work);
INIT_WORK(&rtwdev->c2h_work, rtw_c2h_work);
skb_queue_head_init(&rtwdev->c2h_queue);
+ skb_queue_head_init(&rtwdev->tx_report.queue);
spin_lock_init(&rtwdev->dm_lock);
spin_lock_init(&rtwdev->rf_lock);
spin_lock_init(&rtwdev->h2c.lock);
+ spin_lock_init(&rtwdev->tx_report.q_lock);
mutex_init(&rtwdev->mutex);
mutex_init(&rtwdev->hal.tx_power_mutex);
@@ -1121,10 +1126,15 @@ void rtw_core_deinit(struct rtw_dev *rtwdev)
{
struct rtw_fw_state *fw = &rtwdev->fw;
struct rtw_rsvd_page *rsvd_pkt, *tmp;
+ unsigned long flags;
if (fw->firmware)
release_firmware(fw->firmware);
+ spin_lock_irqsave(&rtwdev->tx_report.q_lock, flags);
+ skb_queue_purge(&rtwdev->tx_report.queue);
+ spin_unlock_irqrestore(&rtwdev->tx_report.q_lock, flags);
+
list_for_each_entry_safe(rsvd_pkt, tmp, &rtwdev->rsvd_page_list, list) {
list_del(&rsvd_pkt->list);
kfree(rsvd_pkt);
@@ -448,6 +448,7 @@ struct rtw_tx_pkt_info {
u8 qsel;
u8 bw;
u8 sec_type;
+ u8 sn;
bool ampdu_en;
u8 ampdu_factor;
u8 ampdu_density;
@@ -460,6 +461,7 @@ struct rtw_tx_pkt_info {
bool ls;
bool fs;
bool short_gi;
+ bool report;
};
struct rtw_rx_pkt_stat {
@@ -548,6 +550,14 @@ struct rtw_sec_desc {
DECLARE_BITMAP(cam_map, RTW_MAX_SEC_CAM_NUM);
};
+struct rtw_tx_report {
+ /* protect the tx report queue */
+ spinlock_t q_lock;
+ struct sk_buff_head queue;
+ atomic_t sn;
+ struct timer_list purge_timer;
+};
+
#define RTW_BC_MC_MACID 1
DECLARE_EWMA(rssi, 10, 16);
@@ -1020,6 +1030,8 @@ struct rtw_dev {
struct sk_buff_head c2h_queue;
struct work_struct c2h_work;
+ struct rtw_tx_report tx_report;
+
struct {
/* incicate the mail box to use with fw */
u8 last_box_num;
@@ -1106,6 +1118,7 @@ void rtw_get_channel_params(struct cfg80211_chan_def *chandef,
void rtw_set_channel(struct rtw_dev *rtwdev);
void rtw_vif_port_config(struct rtw_dev *rtwdev, struct rtw_vif *rtwvif,
u32 config);
+void rtw_tx_report_purge_timer(struct timer_list *t);
void rtw_update_sta_info(struct rtw_dev *rtwdev, struct rtw_sta_info *si);
int rtw_core_start(struct rtw_dev *rtwdev);
void rtw_core_stop(struct rtw_dev *rtwdev);
@@ -598,6 +598,7 @@ static int rtw_pci_xmit(struct rtw_dev *rtwdev,
tx_data = rtw_pci_get_tx_data(skb);
tx_data->dma = dma;
+ tx_data->sn = pkt_info->sn;
skb_queue_tail(&ring->queue, skb);
/* kick off tx queue */
@@ -729,8 +730,20 @@ static void rtw_pci_tx_isr(struct rtw_dev *rtwdev, struct rtw_pci *rtwpci,
skb_pull(skb, rtwdev->chip->tx_pkt_desc_sz);
info = IEEE80211_SKB_CB(skb);
+
+ /* enqueue to wait for tx report */
+ if (info->flags & IEEE80211_TX_CTL_REQ_TX_STATUS) {
+ rtw_tx_report_enqueue(rtwdev, skb, tx_data->sn);
+ continue;
+ }
+
+ /* always ACK for others, then they won't be marked as drop */
+ if (info->flags & IEEE80211_TX_CTL_NO_ACK)
+ info->flags |= IEEE80211_TX_STAT_NOACK_TRANSMITTED;
+ else
+ info->flags |= IEEE80211_TX_STAT_ACK;
+
ieee80211_tx_info_clear_status(info);
- info->flags |= IEEE80211_TX_STAT_ACK;
ieee80211_tx_status_irqsafe(hw, skb);
}
@@ -146,6 +146,7 @@ struct rtw_pci_tx_buffer_desc {
struct rtw_pci_tx_data {
dma_addr_t dma;
+ u8 sn;
};
struct rtw_pci_ring {
@@ -4,6 +4,7 @@
#include "main.h"
#include "tx.h"
+#include "fw.h"
#include "ps.h"
static
@@ -55,6 +56,8 @@ void rtw_tx_fill_tx_desc(struct rtw_tx_pkt_info *pkt_info, struct sk_buff *skb)
SET_TX_DESC_AGG_EN(txdesc, pkt_info->ampdu_en);
SET_TX_DESC_LS(txdesc, pkt_info->ls);
SET_TX_DESC_DATA_SHORT(txdesc, pkt_info->short_gi);
+ SET_TX_DESC_SPE_RPT(txdesc, pkt_info->report);
+ SET_TX_DESC_SW_DEFINE(txdesc, pkt_info->sn);
}
EXPORT_SYMBOL(rtw_tx_fill_tx_desc);
@@ -128,6 +131,96 @@ static u8 get_highest_vht_tx_rate(struct rtw_dev *rtwdev,
return rate;
}
+static void rtw_tx_report_enable(struct rtw_dev *rtwdev,
+ struct rtw_tx_pkt_info *pkt_info)
+{
+ struct rtw_tx_report *tx_report = &rtwdev->tx_report;
+
+ /* [11:8], reserved, fills with zero
+ * [7:2], tx report sequence number
+ * [1:0], firmware use, fills with zero
+ */
+ pkt_info->sn = (atomic_inc_return(&tx_report->sn) << 2) & 0xfc;
+ pkt_info->report = true;
+}
+
+void rtw_tx_report_purge_timer(struct timer_list *t)
+{
+ struct rtw_dev *rtwdev = from_timer(rtwdev, t, tx_report.purge_timer);
+ struct rtw_tx_report *tx_report = &rtwdev->tx_report;
+ unsigned long flags;
+
+ if (skb_queue_len(&tx_report->queue) == 0)
+ return;
+
+ WARN(1, "purge skb(s) not reported by firmware\n");
+
+ spin_lock_irqsave(&tx_report->q_lock, flags);
+ skb_queue_purge(&tx_report->queue);
+ spin_unlock_irqrestore(&tx_report->q_lock, flags);
+}
+
+void rtw_tx_report_enqueue(struct rtw_dev *rtwdev, struct sk_buff *skb, u8 sn)
+{
+ struct rtw_tx_report *tx_report = &rtwdev->tx_report;
+ unsigned long flags;
+ u8 *drv_data;
+
+ /* pass sn to tx report handler through driver data */
+ drv_data = (u8 *)IEEE80211_SKB_CB(skb)->status.status_driver_data;
+ *drv_data = sn;
+
+ spin_lock_irqsave(&tx_report->q_lock, flags);
+ __skb_queue_tail(&tx_report->queue, skb);
+ spin_unlock_irqrestore(&tx_report->q_lock, flags);
+
+ mod_timer(&tx_report->purge_timer, jiffies + RTW_TX_PROBE_TIMEOUT);
+}
+EXPORT_SYMBOL(rtw_tx_report_enqueue);
+
+static void rtw_tx_report_tx_status(struct rtw_dev *rtwdev,
+ struct sk_buff *skb, bool acked)
+{
+ struct ieee80211_tx_info *info;
+
+ info = IEEE80211_SKB_CB(skb);
+ ieee80211_tx_info_clear_status(info);
+ if (acked)
+ info->flags |= IEEE80211_TX_STAT_ACK;
+ else
+ info->flags &= ~IEEE80211_TX_STAT_ACK;
+
+ ieee80211_tx_status_irqsafe(rtwdev->hw, skb);
+}
+
+void rtw_tx_report_handle(struct rtw_dev *rtwdev, struct sk_buff *skb)
+{
+ struct rtw_tx_report *tx_report = &rtwdev->tx_report;
+ struct rtw_c2h_cmd *c2h;
+ struct sk_buff *cur, *tmp;
+ unsigned long flags;
+ u32 pkt_offset;
+ u8 sn, st;
+ u8 *n;
+
+ pkt_offset = *((u32 *)skb->cb);
+ c2h = (struct rtw_c2h_cmd *)(skb->data + pkt_offset);
+
+ sn = GET_CCX_REPORT_SEQNUM(c2h->payload);
+ st = GET_CCX_REPORT_STATUS(c2h->payload);
+
+ spin_lock_irqsave(&tx_report->q_lock, flags);
+ skb_queue_walk_safe(&tx_report->queue, cur, tmp) {
+ n = (u8 *)IEEE80211_SKB_CB(cur)->status.status_driver_data;
+ if (*n == sn) {
+ __skb_unlink(cur, &tx_report->queue);
+ rtw_tx_report_tx_status(rtwdev, cur, st == 0);
+ break;
+ }
+ }
+ spin_unlock_irqrestore(&tx_report->q_lock, flags);
+}
+
static void rtw_tx_mgmt_pkt_info_update(struct rtw_dev *rtwdev,
struct rtw_tx_pkt_info *pkt_info,
struct ieee80211_tx_control *control,
@@ -241,6 +334,9 @@ void rtw_tx_pkt_info_update(struct rtw_dev *rtwdev,
bmc = is_broadcast_ether_addr(hdr->addr1) ||
is_multicast_ether_addr(hdr->addr1);
+ if (info->flags & IEEE80211_TX_CTL_REQ_TX_STATUS)
+ rtw_tx_report_enable(rtwdev, pkt_info);
+
pkt_info->bmc = bmc;
pkt_info->sec_type = sec_type;
pkt_info->tx_pkt_size = skb->len;
@@ -7,6 +7,8 @@
#define RTK_TX_MAX_AGG_NUM_MASK 0x1f
+#define RTW_TX_PROBE_TIMEOUT msecs_to_jiffies(500)
+
#define SET_TX_DESC_TXPKTSIZE(txdesc, value) \
le32p_replace_bits((__le32 *)(txdesc) + 0x00, value, GENMASK(15, 0))
#define SET_TX_DESC_OFFSET(txdesc, value) \
@@ -45,6 +47,10 @@
le32p_replace_bits((__le32 *)(txdesc) + 0x00, value, BIT(26))
#define SET_TX_DESC_DATA_SHORT(txdesc, value) \
le32p_replace_bits((__le32 *)(txdesc) + 0x05, value, BIT(4))
+#define SET_TX_DESC_SPE_RPT(tx_desc, value) \
+ le32p_replace_bits((__le32 *)(txdesc) + 0x02, value, BIT(19))
+#define SET_TX_DESC_SW_DEFINE(tx_desc, value) \
+ le32p_replace_bits((__le32 *)(txdesc) + 0x06, value, GENMASK(11, 0))
enum rtw_tx_desc_queue_select {
TX_DESC_QSEL_TID0 = 0,
@@ -74,6 +80,8 @@ void rtw_tx_pkt_info_update(struct rtw_dev *rtwdev,
struct ieee80211_tx_control *control,
struct sk_buff *skb);
void rtw_tx_fill_tx_desc(struct rtw_tx_pkt_info *pkt_info, struct sk_buff *skb);
+void rtw_tx_report_enqueue(struct rtw_dev *rtwdev, struct sk_buff *skb, u8 sn);
+void rtw_tx_report_handle(struct rtw_dev *rtwdev, struct sk_buff *skb);
void rtw_rsvd_page_pkt_info_update(struct rtw_dev *rtwdev,
struct rtw_tx_pkt_info *pkt_info,
struct sk_buff *skb);