Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 83 additions & 0 deletions demo/main.cpp
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
#include <atomic>
#include <cassert>
#include <chrono>
#include <cstdlib>
#include <cstring>
#include <memory>
#include <thread>

#include <libusb.h>

#include "FrameParser.h"
#include "RtlJaguarDevice.h"
#include "RtlUsbAdapter.h"
#include "WiFiDriver.h"

Expand All @@ -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 `<devourer-c2h>` 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 `<devourer-queue>` 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<uint32_t>(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("<devourer-c2h>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<uint16_t>(h[3] | (h[4] << 8));
uint32_t qt_us = static_cast<uint32_t>(qt_raw) * 256u;
uint8_t rate = h[5];
printf("<devourer-tx-status>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("<devourer>RX pkt #%d (len=%zu)\n", g_rx_count, packet.Data.size());
fflush(stdout);
Expand Down Expand Up @@ -238,6 +296,31 @@ int main() {

WiFiDriver wifi_driver(logger);
auto rtlDevice = wifi_driver.CreateRtlDevice(dev_handle);
g_rtl_device = rtlDevice.get();
std::atomic<bool> 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("<devourer-queue>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;
Expand Down
19 changes: 15 additions & 4 deletions src/FrameParser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -228,11 +228,22 @@ std::vector<Packet> FrameParser::recvbuf2recvframe(std::span<uint8_t> 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");
}
Expand Down
57 changes: 57 additions & 0 deletions src/RtlJaguarDevice.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,16 @@
#include "EepromManager.h"
#include "RadioManagementModule.h"

#include <chrono>
#include <cstdlib>

/* 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<EepromManager>(device, logger)},
Expand Down Expand Up @@ -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<uint32_t, 5> RtlJaguarDevice::get_queue_depth() const {
std::array<uint32_t, 5> out{};
for (size_t i = 0; i < 5; ++i) {
out[i] = _qd_snap[i].load(std::memory_order_relaxed);
}
return out;
}
22 changes: 22 additions & 0 deletions src/RtlJaguarDevice.h
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
#ifndef RTL_JAGUAR_DEVICE_H
#define RTL_JAGUAR_DEVICE_H

#include <array>
#include <atomic>
#include <functional>
#include <iostream>
#include <iomanip>
#include <thread>

#include "logger.h"
#include "HalModule.h"
Expand Down Expand Up @@ -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);
Expand All @@ -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<uint32_t, 5> get_queue_depth() const;

private:
void StartWithMonitorMode(SelectedChannel selectedChannel);
bool NetDevOpen(SelectedChannel selectedChannel);

std::array<std::atomic<uint32_t>, 5> _qd_snap{};
std::thread _qd_thread;
std::atomic<bool> _qd_stop{false};
};

/* Backwards-compatibility alias. External callers using the old name still
Expand Down
31 changes: 31 additions & 0 deletions txdemo/stream_duplex_demo/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,38 @@ static bool read_exact(FILE *f, void *buf, size_t n) {
static std::mutex g_print_mu;
static std::atomic<long> 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<std::mutex> lk(g_print_mu);
std::printf("<devourer-c2h>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<uint16_t>(h[3] | (h[4] << 8));
uint32_t qt_us = static_cast<uint32_t>(qt_raw) * 256u;
uint8_t rate = h[5];
std::printf("<devourer-tx-status>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;
Expand Down
Loading