diff mbox series

[01/24] rtw88: report correct tx status if mac80211 requested one

Message ID 1548937297-14660-2-git-send-email-yhchuang@realtek.com (mailing list archive)
State Changes Requested
Delegated to: Kalle Valo
Headers show
Series rtw88: major fixes for 8822c to have stable functionalities | expand

Commit Message

Tony Chuang Jan. 31, 2019, 12:21 p.m. UTC
From: Yan-Hsuan Chuang <yhchuang@realtek.com>

Before this commit, driver always reports IEEE80211_TX_STAT_ACK for
every tx packet, but it will confuse the mac80211 stack for connection
monitor system. mac80211 stack needs correct ack information about some
specific packets such as prop_req, null, auth, assoc, in order to know
if AP is alive. And for such packets, mac80211 will pass a tx flag
IEEE80211_TX_CTL_REQ_TX_STATUS to driver. Driver then need to request a
tx report from hardware.

The tx report is not passed by hardware with the tx'ed packet, it is
passed through C2H. So driver need to queue the packets that require
correct tx report and upon the tx report is received, report to mac80211
stack, with the frame is acked or not.

In case of driver missed the C2H report, setup a 500ms timer to purge
the tx report skb queue (500ms is time mac80211 used as probe_time).

Signed-off-by: Yan-Hsuan Chuang <yhchuang@realtek.com>
---
 drivers/net/wireless/realtek/rtw88/fw.c   | 21 ++++++-
 drivers/net/wireless/realtek/rtw88/fw.h   |  8 +++
 drivers/net/wireless/realtek/rtw88/main.c | 10 ++++
 drivers/net/wireless/realtek/rtw88/main.h | 13 +++++
 drivers/net/wireless/realtek/rtw88/pci.c  |  8 ++-
 drivers/net/wireless/realtek/rtw88/pci.h  |  1 +
 drivers/net/wireless/realtek/rtw88/tx.c   | 96 +++++++++++++++++++++++++++++++
 drivers/net/wireless/realtek/rtw88/tx.h   |  8 +++
 8 files changed, 163 insertions(+), 2 deletions(-)

Comments

Brian Norris Feb. 9, 2019, 3:08 a.m. UTC | #1
On Thu, Jan 31, 2019 at 08:21:14PM +0800, yhchuang@realtek.com wrote:
> From: Yan-Hsuan Chuang <yhchuang@realtek.com>
> 
> Before this commit, driver always reports IEEE80211_TX_STAT_ACK for
> every tx packet, but it will confuse the mac80211 stack for connection
> monitor system. mac80211 stack needs correct ack information about some
> specific packets such as prop_req, null, auth, assoc, in order to know
> if AP is alive. And for such packets, mac80211 will pass a tx flag
> IEEE80211_TX_CTL_REQ_TX_STATUS to driver. Driver then need to request a
> tx report from hardware.

I think you're misinterpreting the mac80211 semantics here. This flag
isn't for the driver to determine whether or not it should report ACKs
-- it's to help ensure that status reports *really* make it back up to
the upper layers (and don't get dropped).

On the contrary, if you look at __ieee80211_tx_status(), it's expecting
that everything that has IEEE80211_HW_REPORTS_TX_ACK_STATUS will report
an appropriate IEEE80211_TX_STAT_ACK status. The logic is basically:

	if (ieee80211_hw_check(&local->hw, REPORTS_TX_ACK_STATUS))
		if (!(info->flags & IEEE80211_TX_STAT_ACK))
			ieee80211_lost_packet(sta, info);

That explains why I see almost every packet get reported as lost in `iw
wlan0 station dump`.

> The tx report is not passed by hardware with the tx'ed packet, it is
> passed through C2H. So driver need to queue the packets that require
> correct tx report and upon the tx report is received, report to mac80211
> stack, with the frame is acked or not.
> 
> In case of driver missed the C2H report, setup a 500ms timer to purge
> the tx report skb queue (500ms is time mac80211 used as probe_time).
> 
> Signed-off-by: Yan-Hsuan Chuang <yhchuang@realtek.com>
> ---
>  drivers/net/wireless/realtek/rtw88/fw.c   | 21 ++++++-
>  drivers/net/wireless/realtek/rtw88/fw.h   |  8 +++
>  drivers/net/wireless/realtek/rtw88/main.c | 10 ++++
>  drivers/net/wireless/realtek/rtw88/main.h | 13 +++++
>  drivers/net/wireless/realtek/rtw88/pci.c  |  8 ++-
>  drivers/net/wireless/realtek/rtw88/pci.h  |  1 +
>  drivers/net/wireless/realtek/rtw88/tx.c   | 96 +++++++++++++++++++++++++++++++
>  drivers/net/wireless/realtek/rtw88/tx.h   |  8 +++
>  8 files changed, 163 insertions(+), 2 deletions(-)

...

> diff --git a/drivers/net/wireless/realtek/rtw88/pci.c b/drivers/net/wireless/realtek/rtw88/pci.c
> index ef3c9bb..7de4638 100644
> --- a/drivers/net/wireless/realtek/rtw88/pci.c
> +++ b/drivers/net/wireless/realtek/rtw88/pci.c
> @@ -585,6 +585,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 */
> @@ -716,8 +717,13 @@ 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);

This reporting code appears to be very buggy. At least, it's extremely
easy to hit the WARN() you've inserted ("purge skb(s) not reported by
firmware"), which means that the TX reporting queue is not getting
responses for a lot of packets.

So it's not clear if you should be trying to accurately report
everything (even if your firmware status reports are unreliable), or if
you should just drop the REPORTS_TX_ACK_STATUS feature.

> +			continue;
> +		}
>  		ieee80211_tx_info_clear_status(info);
> -		info->flags |= IEEE80211_TX_STAT_ACK;
>  		ieee80211_tx_status_irqsafe(hw, skb);

One other problem with your code is that it doesn't check for
IEEE80211_TX_CTL_NO_ACK anywhere. With that flag, you should be
reporting IEEE80211_TX_STAT_NOACK_TRANSMITTED instead of
IEEE80211_TX_STAT_ACK.

>  	}
>  

Brian
Tony Chuang Feb. 11, 2019, 4:31 a.m. UTC | #2
> -----Original Message-----
> From: Brian Norris [mailto:briannorris@chromium.org]
> 
> On Thu, Jan 31, 2019 at 08:21:14PM +0800, yhchuang@realtek.com wrote:
> > From: Yan-Hsuan Chuang <yhchuang@realtek.com>
> >
> > Before this commit, driver always reports IEEE80211_TX_STAT_ACK for
> > every tx packet, but it will confuse the mac80211 stack for connection
> > monitor system. mac80211 stack needs correct ack information about some
> > specific packets such as prop_req, null, auth, assoc, in order to know
> > if AP is alive. And for such packets, mac80211 will pass a tx flag
> > IEEE80211_TX_CTL_REQ_TX_STATUS to driver. Driver then need to request a
> > tx report from hardware.
> 
> I think you're misinterpreting the mac80211 semantics here. This flag
> isn't for the driver to determine whether or not it should report ACKs
> -- it's to help ensure that status reports *really* make it back up to
> the upper layers (and don't get dropped).
> 
> On the contrary, if you look at __ieee80211_tx_status(), it's expecting
> that everything that has IEEE80211_HW_REPORTS_TX_ACK_STATUS will report
> an appropriate IEEE80211_TX_STAT_ACK status. The logic is basically:
> 
> 	if (ieee80211_hw_check(&local->hw, REPORTS_TX_ACK_STATUS))
> 		if (!(info->flags & IEEE80211_TX_STAT_ACK))
> 			ieee80211_lost_packet(sta, info);
> 
> That explains why I see almost every packet get reported as lost in `iw
> wlan0 station dump`.

To fix `iw wlan0 station dump` display, I think I can just restore one line
in pci.c. That is, restore IEEE80211_TX_STAT_ACK flag line:

+			continue;
+		}
  		ieee80211_tx_info_clear_status(info);
-		info->flags |= IEEE80211_TX_STAT_ACK;
  		ieee80211_tx_status_irqsafe(hw, skb)

And with some modifications, such as IEEE80211_TX_CTL_NO_ACK check.
Then we can better reporting ACK status for data frames without
IEEE80211_TX_CTL_REQ_TX_STATUS. This way we can also ensure the
connection monitor can work. (but it will be no loss)


> 
> > The tx report is not passed by hardware with the tx'ed packet, it is
> > passed through C2H. So driver need to queue the packets that require
> > correct tx report and upon the tx report is received, report to mac80211
> > stack, with the frame is acked or not.
> >
> > In case of driver missed the C2H report, setup a 500ms timer to purge
> > the tx report skb queue (500ms is time mac80211 used as probe_time).
> >
> > Signed-off-by: Yan-Hsuan Chuang <yhchuang@realtek.com>
> > ---
> >  drivers/net/wireless/realtek/rtw88/fw.c   | 21 ++++++-
> >  drivers/net/wireless/realtek/rtw88/fw.h   |  8 +++
> >  drivers/net/wireless/realtek/rtw88/main.c | 10 ++++
> >  drivers/net/wireless/realtek/rtw88/main.h | 13 +++++
> >  drivers/net/wireless/realtek/rtw88/pci.c  |  8 ++-
> >  drivers/net/wireless/realtek/rtw88/pci.h  |  1 +
> >  drivers/net/wireless/realtek/rtw88/tx.c   | 96
> +++++++++++++++++++++++++++++++
> >  drivers/net/wireless/realtek/rtw88/tx.h   |  8 +++
> >  8 files changed, 163 insertions(+), 2 deletions(-)
> 
> ...
> 
> > diff --git a/drivers/net/wireless/realtek/rtw88/pci.c
> b/drivers/net/wireless/realtek/rtw88/pci.c
> > index ef3c9bb..7de4638 100644
> > --- a/drivers/net/wireless/realtek/rtw88/pci.c
> > +++ b/drivers/net/wireless/realtek/rtw88/pci.c
> > @@ -585,6 +585,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 */
> > @@ -716,8 +717,13 @@ 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);
> 
> This reporting code appears to be very buggy. At least, it's extremely
> easy to hit the WARN() you've inserted ("purge skb(s) not reported by
> firmware"), which means that the TX reporting queue is not getting
> responses for a lot of packets.

It's not buggy I think, if firmware is not reporting status, something must
go wrong. And after some test I know why you feel it's unreliable.

For WOW implementation, we modified a lot in fw.c functions.
And correct some driver-firmware interface behaviors. To make sure the
firmware is running as expected. But the patches are still holding in my hand.
I can attach them in this patch set, and apparently I should. I will separate
them out of WOW patch set and resend again.

> 
> So it's not clear if you should be trying to accurately report
> everything (even if your firmware status reports are unreliable), or if
> you should just drop the REPORTS_TX_ACK_STATUS feature.

I think we should keep this feature. Because we actually can report status,
despite not for every packet. The only problem is when we use `iw wlan0
station dump` we could get *no* packet loss (like I've mentioned above,
report TX_STAT_ACK for every other packets not have
IEEE80211_TX_CTL_REQ_TX_STATUS). We cannot accurately report
everything by firmware report, it takes too many tx bandwidth, and
the performance will degrade severely. If we really cannot accept reporting
tx status this way, we need to find another way to solve it. That means I
need some time to investigate and test connect monitor system and get
a better report logic if we drop the REPORTS_TX_ACK_STATUS feature.
Or if you have a point of view, we can discuss about it.
Thanks!

> 
> > +			continue;
> > +		}
> >  		ieee80211_tx_info_clear_status(info);
> > -		info->flags |= IEEE80211_TX_STAT_ACK;
> >  		ieee80211_tx_status_irqsafe(hw, skb);
> 
> One other problem with your code is that it doesn't check for
> IEEE80211_TX_CTL_NO_ACK anywhere. With that flag, you should be
> reporting IEEE80211_TX_STAT_NOACK_TRANSMITTED instead of
> IEEE80211_TX_STAT_ACK.

Should add the check with that restored line I mentioned for pci.c

> 
> >  	}
> >
> 
> Brian
> 

Yan-Hsuan
Brian Norris Feb. 11, 2019, 7:21 p.m. UTC | #3
On Sun, Feb 10, 2019 at 8:31 PM Tony Chuang <yhchuang@realtek.com> wrote:
> To fix `iw wlan0 station dump` display, I think I can just restore one line
> in pci.c. That is, restore IEEE80211_TX_STAT_ACK flag line:
>
> +                       continue;
> +               }
>                 ieee80211_tx_info_clear_status(info);
> -               info->flags |= IEEE80211_TX_STAT_ACK;
>                 ieee80211_tx_status_irqsafe(hw, skb)
>
> And with some modifications, such as IEEE80211_TX_CTL_NO_ACK check.
> Then we can better reporting ACK status for data frames without
> IEEE80211_TX_CTL_REQ_TX_STATUS. This way we can also ensure the
> connection monitor can work. (but it will be no loss)

This seems like a small improvement. It means you will almost never
actually report a "drop", but that's still probably better than
*always* reporting drops I think. I also see that there are handful of
older (likely poorly-maintained? or perhaps similarly-constrained)
drivers that have a similar default behavior -- they report ACK by
default, apparently without any particular notice that the packet was
actually ACKed by the receiver.

But I'm not really a mac80211 expert, so someone else may have better
ideas here.

> It's not buggy I think, if firmware is not reporting status, something must
> go wrong. And after some test I know why you feel it's unreliable.
>
> For WOW implementation, we modified a lot in fw.c functions.
> And correct some driver-firmware interface behaviors. To make sure the
> firmware is running as expected. But the patches are still holding in my hand.
> I can attach them in this patch set, and apparently I should. I will separate
> them out of WOW patch set and resend again.

Ah, well I don't know at what point we should cut things off, but I'll
stick to my stated opinion from previously: all bugfixes will help
someone like me. It's tough to differentiate firmware bugs from driver
bugs, especially when I have absolutely no firmware documentation :)

BTW, while we're at it: is this still a reasonable firmware to reference?

https://git.kernel.org/pub/scm/linux/kernel/git/firmware/linux-firmware.git/commit/?id=338684a0c7760644031483311464c7cf5b3aac94
rtw88: Add firmware file for driver rtw88

> I think we should keep this feature. Because we actually can report status,
> despite not for every packet. The only problem is when we use `iw wlan0
> station dump` we could get *no* packet loss (like I've mentioned above,
> report TX_STAT_ACK for every other packets not have
> IEEE80211_TX_CTL_REQ_TX_STATUS). We cannot accurately report
> everything by firmware report, it takes too many tx bandwidth, and
> the performance will degrade severely. If we really cannot accept reporting

Yeah, it does seem like it would be pretty heavyweight to do this C2H
reporting for every frame.

> tx status this way, we need to find another way to solve it. That means I
> need some time to investigate and test connect monitor system and get
> a better report logic if we drop the REPORTS_TX_ACK_STATUS feature.
> Or if you have a point of view, we can discuss about it.
> Thanks!

Regards,
Brian
diff mbox series

Patch

diff --git a/drivers/net/wireless/realtek/rtw88/fw.c b/drivers/net/wireless/realtek/rtw88/fw.c
index 194bb87..8ab1bbd 100644
--- a/drivers/net/wireless/realtek/rtw88/fw.c
+++ b/drivers/net/wireless/realtek/rtw88/fw.c
@@ -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;
diff --git a/drivers/net/wireless/realtek/rtw88/fw.h b/drivers/net/wireless/realtek/rtw88/fw.h
index 6b56020..6b03629 100644
--- a/drivers/net/wireless/realtek/rtw88/fw.h
+++ b/drivers/net/wireless/realtek/rtw88/fw.h
@@ -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;
@@ -87,6 +91,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
diff --git a/drivers/net/wireless/realtek/rtw88/main.c b/drivers/net/wireless/realtek/rtw88/main.c
index 5225dff..ef010dc 100644
--- a/drivers/net/wireless/realtek/rtw88/main.c
+++ b/drivers/net/wireless/realtek/rtw88/main.c
@@ -1081,14 +1081,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);
@@ -1120,10 +1125,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);
diff --git a/drivers/net/wireless/realtek/rtw88/main.h b/drivers/net/wireless/realtek/rtw88/main.h
index b345fe0..6574811 100644
--- a/drivers/net/wireless/realtek/rtw88/main.h
+++ b/drivers/net/wireless/realtek/rtw88/main.h
@@ -447,6 +447,7 @@  struct rtw_tx_pkt_info {
 	u8 qsel;
 	u8 bw;
 	u8 sec_type;
+	u8 sn;
 	bool ampdu_en;
 	u8 ampdu_factor;
 	u8 ampdu_density;
@@ -459,6 +460,7 @@  struct rtw_tx_pkt_info {
 	bool ls;
 	bool fs;
 	bool short_gi;
+	bool report;
 };
 
 struct rtw_rx_pkt_stat {
@@ -547,6 +549,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);
 
@@ -1021,6 +1031,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;
@@ -1107,6 +1119,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);
diff --git a/drivers/net/wireless/realtek/rtw88/pci.c b/drivers/net/wireless/realtek/rtw88/pci.c
index ef3c9bb..7de4638 100644
--- a/drivers/net/wireless/realtek/rtw88/pci.c
+++ b/drivers/net/wireless/realtek/rtw88/pci.c
@@ -585,6 +585,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 */
@@ -716,8 +717,13 @@  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;
+		}
 		ieee80211_tx_info_clear_status(info);
-		info->flags |= IEEE80211_TX_STAT_ACK;
 		ieee80211_tx_status_irqsafe(hw, skb);
 	}
 
diff --git a/drivers/net/wireless/realtek/rtw88/pci.h b/drivers/net/wireless/realtek/rtw88/pci.h
index 0b672f0..1658647 100644
--- a/drivers/net/wireless/realtek/rtw88/pci.h
+++ b/drivers/net/wireless/realtek/rtw88/pci.h
@@ -140,6 +140,7 @@  struct rtw_pci_tx_buffer_desc {
 
 struct rtw_pci_tx_data {
 	dma_addr_t dma;
+	u8 sn;
 };
 
 struct rtw_pci_ring {
diff --git a/drivers/net/wireless/realtek/rtw88/tx.c b/drivers/net/wireless/realtek/rtw88/tx.c
index 49feb02..21516b7 100644
--- a/drivers/net/wireless/realtek/rtw88/tx.c
+++ b/drivers/net/wireless/realtek/rtw88/tx.c
@@ -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;
diff --git a/drivers/net/wireless/realtek/rtw88/tx.h b/drivers/net/wireless/realtek/rtw88/tx.h
index 010a055..6782632 100644
--- a/drivers/net/wireless/realtek/rtw88/tx.h
+++ b/drivers/net/wireless/realtek/rtw88/tx.h
@@ -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);