From 85b6678b6b361bd80c4b6de798d353abd64c38eb Mon Sep 17 00:00:00 2001 From: Shen Mintao Date: Fri, 30 Oct 2026 09:36:36 +0800 Subject: [PATCH] fix: use confirmation descriptors for monitor raw frames Route raw frames through the USB/SDIO confirmation rings, as reported by ggorbi99-dev in issue #123. Keep monitor support opt-in and document the limits of firmware and rate validation. --- drivers/aic8800/aic8800_fdrv/rwnx_tx.c | 6 +- tests/monitor/README.md | 31 ++++++ tests/monitor/test_tx_confirm.py | 128 +++++++++++++++++++++++++ 3 files changed, 163 insertions(+), 2 deletions(-) create mode 100644 tests/monitor/README.md create mode 100644 tests/monitor/test_tx_confirm.py diff --git a/drivers/aic8800/aic8800_fdrv/rwnx_tx.c b/drivers/aic8800/aic8800_fdrv/rwnx_tx.c index 9030899..5dbbfff 100644 --- a/drivers/aic8800/aic8800_fdrv/rwnx_tx.c +++ b/drivers/aic8800/aic8800_fdrv/rwnx_tx.c @@ -642,7 +642,8 @@ void rwnx_tx_push(struct rwnx_hw *rwnx_hw, struct rwnx_txhdr *txhdr, int flags) rwnx_ipc_txdesc_push(rwnx_hw, &sw_txhdr->desc, skb, hw_queue, user); #else #ifdef AICWF_SDIO_SUPPORT - if( ((sw_txhdr->desc.host.flags & TXU_CNTRL_MGMT) && \ + /* Raw monitor frames also need a firmware confirmation-ring descriptor. */ + if( sw_txhdr->raw_frame || ((sw_txhdr->desc.host.flags & TXU_CNTRL_MGMT) && \ ((*(skb->data+sw_txhdr->headroom)==0xd0) || (*(skb->data+sw_txhdr->headroom)==0x10) || (*(skb->data+sw_txhdr->headroom)==0x30))) || \ (sw_txhdr->desc.host.ethertype == 0x8e88) ) { // 0xd0:Action, 0x10:AssocRsp, 0x8e88:EAPOL sw_txhdr->need_cfm = 1; @@ -671,7 +672,8 @@ void rwnx_tx_push(struct rwnx_hw *rwnx_hw, struct rwnx_txhdr *txhdr, int flags) aicwf_frame_tx((void *)(rwnx_hw->sdiodev), skb); #endif #ifdef AICWF_USB_SUPPORT - if( ((sw_txhdr->desc.host.flags & TXU_CNTRL_MGMT) && \ + /* Bit 30's fixed-rate descriptor silently drops injected D80 frames. */ + if( sw_txhdr->raw_frame || ((sw_txhdr->desc.host.flags & TXU_CNTRL_MGMT) && \ ((*(skb->data+sw_txhdr->headroom)==0xd0) || (*(skb->data+sw_txhdr->headroom)==0x10) || (*(skb->data+sw_txhdr->headroom)==0x30))) || \ (sw_txhdr->desc.host.ethertype == 0x8e88) || (sw_txhdr->desc.host.ethertype == 0xb488)) { // 0xd0:Action, 0x10:AssocRsp, 0x8e88:EAPOL, 0xb488: WAPI diff --git a/tests/monitor/README.md b/tests/monitor/README.md new file mode 100644 index 0000000..f8f6955 --- /dev/null +++ b/tests/monitor/README.md @@ -0,0 +1,31 @@ +# Monitor TX confirmation regression + +Run `python3 tests/monitor/test_tx_confirm.py`. The fixture compiles the actual +USB and SDIO descriptor-selection blocks from `rwnx_tx_push()` with mocked +transport functions. It checks raw frames, normal management frames, EAPOL, +USB-specific WAPI handling, ordinary/fixed-rate data, ring registration before +transport submission, and accounting deferred until confirmation. It does not +send frames or model firmware, USB completion, or radio behavior. + +To compare another revision, pass `--source /path/to/rwnx_tx.c`. + +Build the optional monitor path with prepared kernel headers: + +```sh +bash tests/kernel-build/build.sh /path/to/kernel/build CC=gcc-14 CONFIG_RWNX_MON_DATA=y +``` + +`CONFIG_RWNX_MON_XMIT` already defaults to `y`; `CONFIG_RWNX_MON_DATA` remains +opt-in. For hardware verification, re-open the monitor interface before setting +its channel and use a second radio on the same channel to capture a short +beacon/probe-request test on equipment you control. Check both 2.4 and 5 GHz, +normal station traffic after the test, and interface teardown/recreation. + +The confirmation descriptor carries a ring index instead of the fixed-rate +encoding. Radiotap-selected rates are not established by this check. Other +chips, SDIO hardware and arbitrary raw data frames also require hardware +validation. + +Based on ggorbi99-dev's [issue #123](https://github.com/shenmintao/aic8800d80/issues/123). +The contributor reports over-the-air validation on a D80 MCU1 USB adapter; +that report is not a hardware test of these commits or of the `main` firmware. diff --git a/tests/monitor/test_tx_confirm.py b/tests/monitor/test_tx_confirm.py new file mode 100644 index 0000000..c89d943 --- /dev/null +++ b/tests/monitor/test_tx_confirm.py @@ -0,0 +1,128 @@ +#!/usr/bin/env python3 +"""Test descriptor routing using the actual USB/SDIO rwnx_tx_push blocks.""" +from pathlib import Path +import argparse +import os +import subprocess +import tempfile +import unittest + +ROOT = Path(__file__).resolve().parents[2] +parser = argparse.ArgumentParser() +parser.add_argument("--source", type=Path, + default=ROOT / "drivers/aic8800/aic8800_fdrv/rwnx_tx.c") +args, remaining = parser.parse_known_args() + +PRELUDE = r''' +#include +#include +#include +#define TXU_CNTRL_MGMT (1 << 3) +#define AICWFDBG(...) ((void)0) +struct sk_buff { unsigned char data[80]; }; +struct vif { struct { int tx_packets, tx_bytes; } net_stats; }; +struct rwnx_sw_txhdr { + struct { struct { unsigned int flags, ethertype, status_desc_addr, staid; } host; } desc; + int raw_frame, fixed_rate, need_cfm, headroom, frame_len; + unsigned int rate_config; + struct vif *rwnx_vif; +}; +struct env { unsigned int txdesc_free_idx[1]; }; +struct rwnx_hw { + struct env usb_env, sdio_env; + struct { unsigned long last_tx; } stats; + void *usbdev, *sdiodev; +}; +static int registrations, submissions; +static struct sk_buff packet; +static struct rwnx_sw_txhdr hdr; +static struct vif vif; +static struct rwnx_hw hw; +static unsigned long jiffies = 1234; +static void push(struct env *env, int queue, long host_id) { + assert(host_id == (long)&packet && queue == 0 && submissions == 0); + registrations++; env->txdesc_free_idx[0]++; +} +#define aicwf_usb_host_txdesc_push push +#define aicwf_sdio_host_txdesc_push push +static void aicwf_frame_tx(void *dev, struct sk_buff *skb) { + assert(skb == &packet && dev == &hw); + assert(registrations == hdr.need_cfm); + submissions++; +} +static void tx(struct rwnx_hw *rwnx_hw, struct rwnx_sw_txhdr *sw_txhdr, + struct sk_buff *skb) { +''' + +CHECKS = r''' +} +static void check(unsigned char fc, int raw, int mgmt, unsigned int ether, + int fixed_rate, int expect_confirm) { + memset(&hdr, 0, sizeof(hdr)); memset(&hw, 0, sizeof(hw)); + memset(&vif, 0, sizeof(vif)); memset(&packet, 0, sizeof(packet)); + registrations = submissions = 0; + hdr.headroom = 8; packet.data[8] = fc; hdr.frame_len = 24; + hdr.raw_frame = raw; hdr.fixed_rate = fixed_rate; hdr.rate_config = 0x123; + hdr.desc.host.flags = mgmt ? TXU_CNTRL_MGMT : 0; + hdr.desc.host.ethertype = ether; + hdr.rwnx_vif = &vif; + hw.usbdev = hw.sdiodev = &hw; + hw.usb_env.txdesc_free_idx[0] = hw.sdio_env.txdesc_free_idx[0] = 7; + tx(&hw, &hdr, &packet); + assert(submissions == 1 && registrations == expect_confirm); + assert(hdr.need_cfm == expect_confirm); + if (expect_confirm) { + assert(hdr.desc.host.status_desc_addr == ((1U << 31) | 7)); + /* The real confirmation callback accounts for these packets later. */ + assert(!vif.net_stats.tx_packets && !vif.net_stats.tx_bytes); + assert(!hw.stats.last_tx); + } else { + assert(hdr.desc.host.status_desc_addr == (fixed_rate ? ((1U << 30) | 0x123) : 0)); + assert(vif.net_stats.tx_packets == 1 && vif.net_stats.tx_bytes == 24); + assert(hw.stats.last_tx == jiffies); + } + assert(hdr.desc.host.flags == (mgmt ? TXU_CNTRL_MGMT : 0)); +} +int main(void) { + const unsigned char injected[] = {0x80, 0x40, 0xd0, 0xb0, 0xc0, 0x08}; + for (unsigned int i = 0; i < sizeof(injected); i++) { + check(injected[i], 1, 1, 0, 1, 1); + check(injected[i], 1, 1, 0, 0, 1); + } + check(0xd0, 0, 1, 0, 0, 1); + check(0x10, 0, 1, 0, 0, 1); + check(0x30, 0, 1, 0, 0, 1); + check(0x80, 0, 1, 0, 0, 0); + check(0xd0, 0, 0, 0, 0, 0); + check(0x08, 0, 0, 0, 0, 0); + check(0x08, 0, 0, 0, 1, 0); + check(0x08, 0, 0, 0x8e88, 0, 1); +#ifdef AICWF_USB_SUPPORT + check(0x08, 0, 0, 0xb488, 0, 1); +#else + check(0x08, 0, 0, 0xb488, 0, 0); +#endif + return 0; +} +''' + + +class MonitorTxTests(unittest.TestCase): + def test_descriptor_registration_and_accounting(self): + source = args.source.read_text() + start = source.index("#ifdef AICWF_SDIO_SUPPORT", source.index("void rwnx_tx_push(")) + end = source.index("\n#endif", source.index("aicwf_frame_tx((void *)(rwnx_hw->usbdev)", start)) + block = source[start:end] + "\n#endif\n" + for bus in ("USB", "SDIO"): + with self.subTest(bus=bus): + with tempfile.TemporaryDirectory(prefix="aic-monitor-tx-") as temp: + c_file, binary = Path(temp) / "test.c", Path(temp) / "test" + c_file.write_text(PRELUDE + block + CHECKS) + subprocess.run([os.environ.get("CC", "cc"), "-std=gnu11", + "-DAICWF_" + bus + "_SUPPORT", "-o", str(binary), + str(c_file)], check=True) + subprocess.run([str(binary)], check=True) + + +if __name__ == "__main__": + unittest.main(argv=[__file__] + remaining)