diff --git a/demo/main.cpp b/demo/main.cpp index 1e65e2b..718eb5d 100644 --- a/demo/main.cpp +++ b/demo/main.cpp @@ -1,11 +1,15 @@ +#include #include +#include #include #include #include +#include #include #include "FrameParser.h" +#include "RtlJaguarDevice.h" #include "RtlUsbAdapter.h" #include "WiFiDriver.h" @@ -23,8 +27,62 @@ static constexpr uint16_t kRealtekProductIds[] = { }; static int g_rx_count = 0; +static RtlJaguarDevice *g_rtl_device = nullptr; + +/* DEVOURER_TX_STATUS=1: surface chip-side C2H frames (TX status reports, + * various diagnostic pings) on `` with a raw hex dump, plus + * a best-effort decode of the 8814A TX_RPT payload layout. The C2H + * sub-type ID isn't enumerated in the vendored headers, so the decode is + * speculative — the raw hex stays in the line so an observer can + * validate the sub-type against on-air capture. + * + * DEVOURER_QUEUE_POLL_MS=N: periodic snapshot of the 8814A REG_FIFOPAGE_INFO + * registers, throttled to one `` line per second on RX hook. + * 8814-only (8812/8821 don't expose these registers as per-queue free pages). */ +static const bool g_tx_status_enabled = + std::getenv("DEVOURER_TX_STATUS") != nullptr; +static const uint32_t g_qd_poll_ms = []() -> uint32_t { + const char *e = std::getenv("DEVOURER_QUEUE_POLL_MS"); + return e ? static_cast(std::strtoul(e, nullptr, 0)) : 0u; +}(); + static void packetProcessor(const Packet &packet) { + /* C2H packets carry chip-side status updates, not 802.11 frames. Handle + * them up front so the rest of this function (which assumes a normal + * 802.11 MPDU layout) doesn't try to read SA bytes from a C2H payload. */ + if (packet.RxAtrib.pkt_rpt_type == RX_PACKET_TYPE::C2H_PACKET) { + if (g_tx_status_enabled) { + printf("len=%zu bytes=", packet.Data.size()); + for (size_t i = 0; i < packet.Data.size(); ++i) + printf("%02x", packet.Data[i]); + printf("\n"); + /* Best-effort 8814A TX_RPT decode. The GET_8814A_C2H_TX_RPT_* + * macros (hal/rtl8814a_cmd.h:118-125) read from a "_Header" pointer + * — which, in upstream Realtek code, points one or two bytes past + * the C2H frame start (after cmd_id [+ seq]). We try the two most + * common offsets (1 and 2) and emit each; an observer can pick the + * one whose queue_id / rate / retry values look plausible. */ + if (packet.Data.size() >= 8) { + for (size_t hoff : {size_t(1), size_t(2)}) { + if (packet.Data.size() < hoff + 6) continue; + const uint8_t *h = packet.Data.data() + hoff; + uint8_t queue = h[0] & 0x1f; + uint8_t retry = h[2] & 0x3f; + uint16_t qt_raw = static_cast(h[3] | (h[4] << 8)); + uint32_t qt_us = static_cast(qt_raw) * 256u; + uint8_t rate = h[5]; + printf("hoff=%zu queue=%u retry=%u " + "airtime_us=%u rate=%u\n", + hoff, queue, retry, qt_us, rate); + } + } + fflush(stdout); + } + return; + } + ++g_rx_count; + if (g_rx_count <= 10 || g_rx_count % 100 == 0) { printf("RX pkt #%d (len=%zu)\n", g_rx_count, packet.Data.size()); fflush(stdout); @@ -238,6 +296,31 @@ int main() { WiFiDriver wifi_driver(logger); auto rtlDevice = wifi_driver.CreateRtlDevice(dev_handle); + g_rtl_device = rtlDevice.get(); + std::atomic qd_emitter_stop{false}; + std::thread qd_emitter; + if (g_qd_poll_ms > 0) { + logger->info("DEVOURER_QUEUE_POLL_MS={} — starting queue-depth poller", + g_qd_poll_ms); + rtlDevice->start_queue_depth_poller(g_qd_poll_ms); + /* Self-driven emitter — ticks at the poll cadence so the queue snapshot + * surfaces even when the RX hook is sparse (e.g. broken-RX 8814 cells). + * Idempotent w.r.t. the poller; just reads the atomic snapshot. */ + qd_emitter = std::thread([&qd_emitter_stop]() { + while (!qd_emitter_stop.load()) { + if (g_rtl_device != nullptr) { + auto q = g_rtl_device->get_queue_depth(); + printf("q1=0x%08x q2=0x%08x q3=0x%08x q4=0x%08x " + "q5=0x%08x\n", q[0], q[1], q[2], q[3], q[4]); + fflush(stdout); + } + for (uint32_t slept = 0; slept < g_qd_poll_ms && !qd_emitter_stop.load(); + slept += 50) { + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + } + } + }); + } /* Default channel 36 (5 GHz) for the 8812 reference. Override with * DEVOURER_CHANNEL=N env var (e.g. DEVOURER_CHANNEL=6 for busy 2.4 GHz). */ int channel = 36; diff --git a/src/FrameParser.cpp b/src/FrameParser.cpp index d4120e5..a549753 100644 --- a/src/FrameParser.cpp +++ b/src/FrameParser.cpp @@ -228,11 +228,22 @@ std::vector FrameParser::recvbuf2recvframe(std::span ptr) { ret.back().RxAtrib.evm[3] = driver_data.rxevm_cd[1]; } else { /* pkt_rpt_type == TX_REPORT1-CCX, TX_REPORT2-TX RTP,HIS_REPORT-USB HISR - * RTP */ + * RTP, C2H_PACKET */ if (pattrib.pkt_rpt_type == RX_PACKET_TYPE::C2H_PACKET) { - _logger->info("RX USB C2H_PACKET"); - // rtw_hal_c2h_pkt_pre_hdl(padapter, precvframe.u.hdr.rx_data, - // pattrib.pkt_len); + /* Surface C2H payload bytes as a Packet so the application + * callback can decode them. The C2H frame body lives at the same + * offset as a normal-RX 802.11 frame (shift + drvinfo + RXDESC). + * Layout per upstream Realtek convention: byte 0 = C2H cmd_id + * (sub-type), byte 1 = seq, bytes 2..N = payload. Sub-type IDs + * vary per chip and aren't enumerated in the vendored headers — + * demo/main.cpp does a best-effort decode of the 8814A TX_RPT + * payload via the GET_8814A_C2H_TX_RPT_* macros gated by + * DEVOURER_TX_STATUS=1, plus a raw hex dump so observers can + * validate the sub-type ID against on-air capture. */ + ret.push_back({pattrib, pbuf.subspan(pattrib.shift_sz + + pattrib.drvinfo_sz + + RXDESC_SIZE, + pattrib.pkt_len)}); } else if (pattrib.pkt_rpt_type == RX_PACKET_TYPE::HIS_REPORT) { _logger->info("RX USB HIS_REPORT"); } diff --git a/src/RtlJaguarDevice.cpp b/src/RtlJaguarDevice.cpp index 25b7738..3467717 100644 --- a/src/RtlJaguarDevice.cpp +++ b/src/RtlJaguarDevice.cpp @@ -2,8 +2,16 @@ #include "EepromManager.h" #include "RadioManagementModule.h" +#include #include +/* Per-queue free-page registers (8814A only — 0x0230..0x0240 don't decode + * the same way on 8812/8821). Cross-checked against hal/rtl8814a_spec.h + * and src/HalModule.cpp's REG_FIFOPAGE_INFO_*_8814A constants. */ +static constexpr uint16_t kFifoPageInfoRegs_8814A[5] = { + 0x0230, 0x0234, 0x0238, 0x023C, 0x0240, +}; + RtlJaguarDevice::RtlJaguarDevice(RtlUsbAdapter device, Logger_t logger) : _device{device}, _eepromManager{std::make_shared(device, logger)}, @@ -328,3 +336,52 @@ bool RtlJaguarDevice::NetDevOpen(SelectedChannel selectedChannel) { return true; } + +RtlJaguarDevice::~RtlJaguarDevice() { + _qd_stop.store(true); + if (_qd_thread.joinable()) { + _qd_thread.join(); + } +} + +void RtlJaguarDevice::start_queue_depth_poller(uint32_t interval_ms) { + if (interval_ms == 0) return; + if (_qd_thread.joinable()) { + _logger->warn("queue-depth poller already running"); + return; + } + if (_eepromManager->version_id.ICType != CHIP_8814A) { + _logger->warn( + "DEVOURER_QUEUE_POLL_MS set but chip is not 8814A — REG_FIFOPAGE_INFO_*" + " registers don't decode as per-queue free pages on this chip; poller" + " disabled"); + return; + } + _qd_thread = std::thread([this, interval_ms]() { + /* libusb-1.0 allows concurrent transfers on different endpoints from + * different threads — vendor control (ep 0) doesn't conflict with the + * RX-loop's bulk-IN. The reads here are synchronous control transfers + * via _device.rtw_read32, so no completion-callback plumbing needed. */ + while (!_qd_stop.load()) { + for (size_t i = 0; i < 5; ++i) { + if (_qd_stop.load()) break; + uint32_t v = _device.rtw_read32(kFifoPageInfoRegs_8814A[i]); + _qd_snap[i].store(v, std::memory_order_relaxed); + } + /* Sleep in short slices so destruction doesn't block for a full + * interval after _qd_stop is set. */ + for (uint32_t slept = 0; slept < interval_ms && !_qd_stop.load(); + slept += 50) { + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + } + } + }); +} + +std::array RtlJaguarDevice::get_queue_depth() const { + std::array out{}; + for (size_t i = 0; i < 5; ++i) { + out[i] = _qd_snap[i].load(std::memory_order_relaxed); + } + return out; +} diff --git a/src/RtlJaguarDevice.h b/src/RtlJaguarDevice.h index 2738c31..3cf654e 100644 --- a/src/RtlJaguarDevice.h +++ b/src/RtlJaguarDevice.h @@ -1,9 +1,12 @@ #ifndef RTL_JAGUAR_DEVICE_H #define RTL_JAGUAR_DEVICE_H +#include +#include #include #include #include +#include #include "logger.h" #include "HalModule.h" @@ -36,6 +39,7 @@ class RtlJaguarDevice { public: RtlJaguarDevice(RtlUsbAdapter device, Logger_t logger); + ~RtlJaguarDevice(); void Init(Action_ParsedRadioPacket packetProcessor, SelectedChannel channel); void SetMonitorChannel(SelectedChannel channel); void InitWrite(SelectedChannel channel); @@ -44,9 +48,27 @@ class RtlJaguarDevice { SelectedChannel GetSelectedChannel(); bool should_stop = false; + /* Per-queue free-page snapshot read from REG_FIFOPAGE_INFO_1..5 + * (0x0230 / 0x0234 / 0x0238 / 0x023C / 0x0240). 8814A-only — these + * registers don't exist on 8812 / 8821, so the snapshot stays at all + * zeros on those chips and start_queue_depth_poller() is a no-op. + * + * Polling cadence is set via interval_ms (DEVOURER_QUEUE_POLL_MS in + * the demos). 0 = disabled. The poller spawns a worker thread that + * issues a vendor-control read for each register every interval and + * stores the raw 32-bit word — interpretation (which bits are free + * pages vs reserved) is left to the consumer, since the per-queue + * layout differs slightly between chip cuts. */ + void start_queue_depth_poller(uint32_t interval_ms); + std::array get_queue_depth() const; + private: void StartWithMonitorMode(SelectedChannel selectedChannel); bool NetDevOpen(SelectedChannel selectedChannel); + + std::array, 5> _qd_snap{}; + std::thread _qd_thread; + std::atomic _qd_stop{false}; }; /* Backwards-compatibility alias. External callers using the old name still diff --git a/txdemo/stream_duplex_demo/main.cpp b/txdemo/stream_duplex_demo/main.cpp index d01a986..1cb70f3 100644 --- a/txdemo/stream_duplex_demo/main.cpp +++ b/txdemo/stream_duplex_demo/main.cpp @@ -110,7 +110,38 @@ static bool read_exact(FILE *f, void *buf, size_t n) { static std::mutex g_print_mu; static std::atomic g_rx_hits{0}; +/* DEVOURER_TX_STATUS=1: surface chip-side C2H frames (TX-status reports + * from the same 8812/8821 chip we're TXing on). Best-effort 8814A TX_RPT + * decode mirrors demo/main.cpp; the C2H sub-type ID isn't enumerated in + * the vendored headers so the raw hex stays in the line. */ +static const bool g_tx_status_enabled = + std::getenv("DEVOURER_TX_STATUS") != nullptr; + static void packet_processor(const Packet &packet) { + if (packet.RxAtrib.pkt_rpt_type == RX_PACKET_TYPE::C2H_PACKET) { + if (!g_tx_status_enabled) return; + std::lock_guard lk(g_print_mu); + std::printf("len=%zu bytes=", packet.Data.size()); + for (size_t i = 0; i < packet.Data.size(); ++i) + std::printf("%02x", packet.Data[i]); + std::printf("\n"); + if (packet.Data.size() >= 8) { + for (size_t hoff : {size_t(1), size_t(2)}) { + if (packet.Data.size() < hoff + 6) continue; + const uint8_t *h = packet.Data.data() + hoff; + uint8_t queue = h[0] & 0x1f; + uint8_t retry = h[2] & 0x3f; + uint16_t qt_raw = static_cast(h[3] | (h[4] << 8)); + uint32_t qt_us = static_cast(qt_raw) * 256u; + uint8_t rate = h[5]; + std::printf("hoff=%zu queue=%u retry=%u " + "airtime_us=%u rate=%u\n", + hoff, queue, retry, qt_us, rate); + } + } + std::fflush(stdout); + return; + } if (packet.Data.size() < 16) return; if (std::memcmp(packet.Data.data() + 10, kCanonicalSa, 6) != 0) return; long hits = ++g_rx_hits;