diff --git a/demo/main.cpp b/demo/main.cpp index adbc3dd..298f3c0 100644 --- a/demo/main.cpp +++ b/demo/main.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include @@ -28,6 +29,22 @@ static void packetProcessor(const Packet &packet) { printf("RX pkt #%d (len=%zu)\n", g_rx_count, packet.Data.size()); fflush(stdout); } + /* TX-validation hook: detect frames whose SA matches the txdemo's hardcoded + * injected beacon (57:42:75:05:d6:00). When running this RX demo against + * one adapter while WiFiDriverTxDemo runs against another on the same + * channel, each hit confirms an injected frame made it over the air. */ + if (packet.Data.size() >= 16) { + static const uint8_t kTxSa[6] = {0x57, 0x42, 0x75, 0x05, 0xd6, 0x00}; + if (std::memcmp(packet.Data.data() + 10, kTxSa, 6) == 0) { + static int hits = 0; + ++hits; + if (hits <= 10 || hits % 100 == 0) { + printf("txdemo SA match: hits=%d total_rx=%d len=%zu\n", + hits, g_rx_count, packet.Data.size()); + fflush(stdout); + } + } + } } int main() { diff --git a/src/HalModule.cpp b/src/HalModule.cpp index e19ce75..5b32166 100644 --- a/src/HalModule.cpp +++ b/src/HalModule.cpp @@ -9,6 +9,40 @@ #include "rtl8812a_hal.h" #include "rtl8812a_spec.h" +/* 8814AU register + page constants extracted from upstream + * hal/rtl8814a_spec.h and hal/rtl8814a_hal.h. Inlined here because the + * upstream headers pull in kernel-only deps (drv_conf.h, hal_data.h). */ +namespace rtl8814a { +constexpr uint16_t REG_FIFOPAGE_INFO_1_8814A = 0x0230; +constexpr uint16_t REG_FIFOPAGE_INFO_2_8814A = 0x0234; +constexpr uint16_t REG_FIFOPAGE_INFO_3_8814A = 0x0238; +constexpr uint16_t REG_FIFOPAGE_INFO_4_8814A = 0x023C; +constexpr uint16_t REG_FIFOPAGE_INFO_5_8814A = 0x0240; +constexpr uint16_t REG_RQPN_CTRL_2_8814A = 0x022C; +constexpr uint16_t REG_FIFOPAGE_CTRL_2_8814A = 0x0204; +constexpr uint16_t REG_TXPKTBUF_BCNQ_BDNY_8814A = 0x0424; +constexpr uint16_t REG_TXPKTBUF_BCNQ1_BDNY_8814A = 0x0426; /* spec calls it +2 */ +constexpr uint16_t REG_MGQ_PGBNDY_8814A = 0x047A; +constexpr uint16_t REG_RXFF_PTR_8814A = 0x011C; + +constexpr uint32_t HPQ_PGNUM_8814A = 0x20; /* 32 pages per queue (USB) */ +constexpr uint32_t LPQ_PGNUM_8814A = 0x20; +constexpr uint32_t NPQ_PGNUM_8814A = 0x20; +constexpr uint32_t EPQ_PGNUM_8814A = 0x20; +constexpr uint32_t BCNQ_PAGE_NUM_8814 = 0x08; +constexpr uint32_t WOWLAN_PAGE_NUM_8814 = 0x00; +constexpr uint32_t TXPKT_PGNUM_8814A = + 2048 - BCNQ_PAGE_NUM_8814 - WOWLAN_PAGE_NUM_8814; +constexpr uint32_t PUB_PGNUM_8814A = TXPKT_PGNUM_8814A - HPQ_PGNUM_8814A - + NPQ_PGNUM_8814A - LPQ_PGNUM_8814A - + EPQ_PGNUM_8814A; +constexpr uint16_t TX_PAGE_BOUNDARY_8814A = TXPKT_PGNUM_8814A; +constexpr uint16_t WMM_NORMAL_TX_PAGE_BOUNDARY_8814A = TXPKT_PGNUM_8814A + 1; + +constexpr uint32_t MAX_RX_DMA_BUFFER_SIZE_8814A = 0x5C00; +constexpr uint16_t RX_DMA_BOUNDARY_8814A = MAX_RX_DMA_BUFFER_SIZE_8814A - 1; +} // namespace rtl8814a + #include #include #include @@ -108,11 +142,60 @@ bool HalModule::rtl8812au_hal_init() { PHY_MACConfig8812(); - _InitQueueReservedPage_8812AUsb(); - _InitTxBufferBoundary_8812AUsb(); - _InitQueuePriority_8812AUsb(); - _InitPageBoundary_8812AUsb(); - _InitTransferPageSize_8812AUsb(); + if (is_8814a) { + /* 8814AU has its own TX FIFO page allocation: 2048 total pages vs 8812's + * 256, set via 32-bit FIFOPAGE_INFO_{1..5} regs + 16-bit BCNQ/MGQ page + * boundaries. The 8812 path uses 8-bit REG_RQPN / REG_BCNQ_BDNY etc. + * which silently no-op on 8814 — that leaves HPQ with 0 pages, so MGT + * frames submitted via bulk OUT have nowhere to land and the chip never + * drains the EP (USB bulk OUT times out). */ + _InitQueueReservedPage_8814AUsb(); + /* TX buffer boundary is set inside _InitQueueReservedPage_8814AUsb. Skip + * _InitTxBufferBoundary_8812AUsb. */ + _InitQueuePriority_8812AUsb(); /* dispatches on CHIP_8814A internally */ + _InitPageBoundary_8814AUsb(); + /* _InitTransferPageSize_8814AUsb is a no-op upstream. */ + + /* 8814AU auto-LLT trigger via 32-bit BIT16 of REG_AUTO_LLT_8814A (0x0208, + * aliased as REG_TDECTRL). The generic Realtek bit definition is + * BIT_AUTO_INIT_LLT = BIT(16) (see hal_com_reg.h). The upstream OOT + * code at rtl8814a_hal_init.c::InitLLTTable8814A writes BIT0 of an 8-bit + * read at 0x208 — that's a different bit entirely; empirically the + * trigger never fires (auto-LLT "completes in 0 polls" because BIT0 + * was never set). Use the correct BIT(16) trigger as a 32-bit RMW. + * Without auto-LLT, the chip's TX FIFO page-count regs we just set are + * advertised but no free-page list is linked, so the chip's TX queues + * have nowhere to store inbound bulk-OUT frames and vendor-control + * transfers to OUT EPs time out forever. */ + constexpr uint16_t REG_AUTO_LLT_8814A = 0x0208; + constexpr uint32_t AUTO_INIT_LLT_BIT = 1u << 16; /* renamed: hal_com_reg.h's BIT_AUTO_INIT_LLT macro collides */ + uint32_t llt = _device.rtw_read32(REG_AUTO_LLT_8814A); + _logger->info("8814A auto-LLT pre REG_AUTO_LLT=0x{:08x}", llt); + _device.rtw_write32(REG_AUTO_LLT_8814A, llt | AUTO_INIT_LLT_BIT); + int polls = 0; + do { + llt = _device.rtw_read32(REG_AUTO_LLT_8814A); + if (!(llt & AUTO_INIT_LLT_BIT)) + break; + std::this_thread::sleep_for(std::chrono::milliseconds(2)); + ++polls; + } while (polls < 200); + if (llt & AUTO_INIT_LLT_BIT) { + _logger->error("8814A auto-LLT did not complete (REG_AUTO_LLT=0x{:08x} " + "after {} polls)", + llt, polls); + } else { + _logger->info( + "8814A auto-LLT completed in {} polls (REG_AUTO_LLT=0x{:08x})", polls, + llt); + } + } else { + _InitQueueReservedPage_8812AUsb(); + _InitTxBufferBoundary_8812AUsb(); + _InitQueuePriority_8812AUsb(); + _InitPageBoundary_8812AUsb(); + _InitTransferPageSize_8812AUsb(); + } // Get Rx PHY status in order to report RSSI and others. _InitDriverInfoSize_8812A(DRVINFO_SZ); @@ -199,7 +282,19 @@ bool HalModule::rtl8812au_hal_init() { // HW SEQ CTRL // set 0x0 to 0xFF by tynli. Default enable HW SEQ NUM. - _device.rtw_write8(REG_HWSEQ_CTRL, 0xFF); + // On 8814 the chip rejects 8-bit access at offset 0x423 (byte 3 of + // FWHW_TXQ_CTRL@0x420) — the rtw_write8 at this address silently no-ops + // and rtw_read8 returns 0 regardless of the actual byte. Verified by the + // diag dump: rtw_read32(0x420) shows byte3=0x03 but rtw_read8(0x423)=0x00. + // Use a 32-bit aligned RMW so the chip's USB controller handles it as a + // word-sized access. + if (is_8814a) { + uint32_t txqctl = _device.rtw_read32(REG_FWHW_TXQ_CTRL); + txqctl = (txqctl & 0x00FFFFFFu) | (0xFFu << 24); + _device.rtw_write32(REG_FWHW_TXQ_CTRL, txqctl); + } else { + _device.rtw_write8(REG_HWSEQ_CTRL, 0xFF); + } // Disable BAR, suggested by Scott // 2010.04.09 add by hpfan @@ -243,14 +338,62 @@ bool HalModule::rtl8812au_hal_init() { * (clearing DMA + protocol + scheduler) — verified via post-init pyusb * probe showing REG_CR=0xc0. And REG_RXFLTMAP2 read back as 0 instead * of the 0xFFFF we want for monitor-mode data-frame acceptance. Force - * the final state here so RX bulk IN actually moves frames. */ - uint16_t cr_final = (uint16_t)(HCI_TXDMA_EN | HCI_RXDMA_EN | TXDMA_EN | - RXDMA_EN | PROTOCOL_EN | SCHEDULE_EN | - MACTXEN | MACRXEN); + * the final state here so RX bulk IN actually moves frames. + * + * 8814 hypothesis: firmware programs REG_CR after it boots and may set + * bits beyond our 0x00FF mask (ENSEC=BIT9, CALTMR_EN=BIT10 — both in + * the upstream _InitPowerOn_8814AU OR-mask, neither in our cr_final). + * Forcing 0x00FF on 8814 could clobber fw-set high bits that gate TX. + * Read current REG_CR first, then OR in our minimum-required bits + * instead of clobbering the whole word. */ + uint16_t cr_observed = _device.rtw_read16(REG_CR); + uint16_t cr_min = (uint16_t)(HCI_TXDMA_EN | HCI_RXDMA_EN | TXDMA_EN | + RXDMA_EN | PROTOCOL_EN | SCHEDULE_EN | + MACTXEN | MACRXEN); + uint16_t cr_final = static_cast(cr_observed | cr_min); _device.rtw_write16(REG_CR, cr_final); _device.rtw_write16(REG_RXFLTMAP2, 0xFFFF); - _logger->info("post-init final: REG_CR=0x{:04x} REG_RXFLTMAP2=0xFFFF", - cr_final); + _logger->info( + "post-init final: REG_CR observed=0x{:04x} written=0x{:04x} " + "REG_RXFLTMAP2=0xFFFF", + cr_observed, cr_final); + + if (is_8814a) { + /* TX-validation diagnostic. Read back the registers that gate USB→TX + * dataflow to confirm what state the chip is actually in at the end of + * init. One log per register to dodge the Logger format helper's + * placeholder-overflow truncation. */ + using namespace rtl8814a; + _logger->info("8814A TX-state CR = 0x{:04x}", _device.rtw_read16(REG_CR)); + _logger->info("8814A TX-state TXPAUSE(0x522) = 0x{:02x}", + _device.rtw_read8(0x0522)); + _logger->info("8814A TX-state FWHW_TXQ_CTRL(0x420) = 0x{:08x}", + _device.rtw_read32(0x0420)); + _logger->info("8814A TX-state FIFOPAGE_CTRL_2 = 0x{:08x}", + _device.rtw_read32(REG_FIFOPAGE_CTRL_2_8814A)); + _logger->info("8814A TX-state MGQ_PGBNDY = 0x{:04x}", + _device.rtw_read16(REG_MGQ_PGBNDY_8814A)); + _logger->info("8814A TX-state FIFOPAGE_INFO_1(HPQ) = 0x{:08x}", + _device.rtw_read32(REG_FIFOPAGE_INFO_1_8814A)); + _logger->info("8814A TX-state FIFOPAGE_INFO_5(PUB) = 0x{:08x}", + _device.rtw_read32(REG_FIFOPAGE_INFO_5_8814A)); + _logger->info("8814A TX-state MCUFWDL = 0x{:08x}", + _device.rtw_read32(0x0080)); + _logger->info("8814A TX-state TXDMA_STATUS(0x210) = 0x{:08x}", + _device.rtw_read32(0x0210)); + _logger->info("8814A TX-state TXDMA_OFFSET_CHK(0x20C) = 0x{:08x}", + _device.rtw_read32(0x020C)); + /* 8-bit read of 0x423 is unreliable on 8814; surface both 8-bit and the + * byte-3 of the 32-bit FWHW_TXQ_CTRL word for comparison. */ + _logger->info("8814A TX-state HWSEQ_CTRL(0x423,8bit) = 0x{:02x}", + _device.rtw_read8(0x0423)); + _logger->info("8814A TX-state HWSEQ_CTRL(byte3 of 0x420 32bit) = 0x{:02x}", + (_device.rtw_read32(REG_FWHW_TXQ_CTRL) >> 24) & 0xFF); + _logger->info("8814A TX-state TCR(0x604) = 0x{:08x}", + _device.rtw_read32(0x0604)); + _logger->info("8814A TX-state RCR(0x608) = 0x{:08x}", + _device.rtw_read32(0x0608)); + } return true; } @@ -1035,6 +1178,45 @@ void HalModule::_InitPageBoundary_8812AUsb() { _device.rtw_write16((REG_TRXFF_BNDY + 2), RX_DMA_BOUNDARY_8812); } +void HalModule::_InitQueueReservedPage_8814AUsb() { + using namespace rtl8814a; + /* Port of upstream _InitQueueReservedPage_8814AUsb (hal/rtl8814a/usb/ + * usb_halinit.c). 8814 uses 32-bit FIFOPAGE_INFO regs to set per-queue + * page counts and 16-bit boundary registers. The 8812 8-bit REG_RQPN / + * REG_BCNQ_BDNY equivalents don't exist on 8814, so reusing the 8812 + * path leaves HPQ/NPQ/LPQ with zero pages and TX bulk OUT stalls. */ + _device.rtw_write32(REG_FIFOPAGE_INFO_1_8814A, HPQ_PGNUM_8814A); + _device.rtw_write32(REG_FIFOPAGE_INFO_2_8814A, LPQ_PGNUM_8814A); + _device.rtw_write32(REG_FIFOPAGE_INFO_3_8814A, NPQ_PGNUM_8814A); + _device.rtw_write32(REG_FIFOPAGE_INFO_4_8814A, EPQ_PGNUM_8814A); + _device.rtw_write32(REG_FIFOPAGE_INFO_5_8814A, PUB_PGNUM_8814A); + + _device.rtw_write32(REG_RQPN_CTRL_2_8814A, 0x80000000); + + uint16_t txpktbuf_bndy = registry_priv::wifi_spec + ? WMM_NORMAL_TX_PAGE_BOUNDARY_8814A + : TX_PAGE_BOUNDARY_8814A; + + _device.rtw_write16(REG_TXPKTBUF_BCNQ_BDNY_8814A, txpktbuf_bndy); + _device.rtw_write16(REG_TXPKTBUF_BCNQ1_BDNY_8814A, txpktbuf_bndy); + _device.rtw_write16(REG_MGQ_PGBNDY_8814A, txpktbuf_bndy); + + /* Head page of BCNQ + BCNQ1 packets. */ + _device.rtw_write16(REG_FIFOPAGE_CTRL_2_8814A, txpktbuf_bndy); + _device.rtw_write16(REG_FIFOPAGE_CTRL_2_8814A + 2, txpktbuf_bndy); + + _logger->info( + "8814A queue reserved pages: HPQ/LPQ/NPQ/EPQ={:#x} PUB={:#x} bndy={:#x}", + HPQ_PGNUM_8814A, PUB_PGNUM_8814A, txpktbuf_bndy); +} + +void HalModule::_InitPageBoundary_8814AUsb() { + using namespace rtl8814a; + /* Port of upstream _InitPageBoundary_8814AUsb. Single 16-bit write to + * REG_RXFF_PTR_8814A. The 8812 path writes REG_TRXFF_BNDY+2 instead. */ + _device.rtw_write16(REG_RXFF_PTR_8814A, RX_DMA_BOUNDARY_8814A); +} + void HalModule::_InitTransferPageSize_8812AUsb() { uint8_t value8 = _PSTX(PBP_512); _device.rtw_write8(REG_PBP, value8); diff --git a/src/HalModule.h b/src/HalModule.h index 348627c..0c083e7 100644 --- a/src/HalModule.h +++ b/src/HalModule.h @@ -88,6 +88,8 @@ class HalModule { void _InitNormalChipRegPriority_8814AUsb(uint16_t beQ, uint16_t bkQ, uint16_t viQ, uint16_t voQ, uint16_t mgtQ, uint16_t hiQ); + void _InitQueueReservedPage_8814AUsb(); + void _InitPageBoundary_8814AUsb(); void init_hi_queue_config_8812a_usb(); void _InitPageBoundary_8812AUsb(); void _InitTransferPageSize_8812AUsb(); diff --git a/src/RtlUsbAdapter.cpp b/src/RtlUsbAdapter.cpp index 99a8122..0057ff2 100644 --- a/src/RtlUsbAdapter.cpp +++ b/src/RtlUsbAdapter.cpp @@ -1,6 +1,7 @@ #include "RtlUsbAdapter.h" #include +#include #if defined(__ANDROID__) || defined(_MSC_VER) || defined(__APPLE__) #include #else @@ -350,7 +351,35 @@ bool RtlUsbAdapter::send_packet(uint8_t *packet, size_t length) { return false; } - libusb_fill_bulk_transfer(transfer, _dev_handle, 0x02, packet, length, + /* On the FIRST send only, dump chip state via vendor reads. Surfaces any + * register clobber between init-end and first TX (e.g. SetMonitorChannel + * could be resetting REG_CR or related). */ + static bool first_dump = true; + if (first_dump) { + first_dump = false; + uint16_t cr = rtw_read16(0x0100); + uint8_t txpause = rtw_read8(0x0522); + uint32_t txdma_off_chk = rtw_read32(0x020C); + uint32_t fwhw_txq = rtw_read32(0x0420); + uint32_t mcufwdl = rtw_read32(0x0080); + uint32_t hci_susp = rtw_read32(0xFE10); /* USB_HCPWM / USB suspend ctrl */ + _logger->info("pre-1st-TX: CR=0x{:04x} TXPAUSE=0x{:02x} TXDMA_OFFC=0x{:08x}", + cr, txpause, txdma_off_chk); + _logger->info("pre-1st-TX: FWHW_TXQ=0x{:08x} MCUFWDL=0x{:08x} HCIPWR=0x{:08x}", + fwhw_txq, mcufwdl, hci_susp); + } + + /* DEVOURER_TX_EP=0xNN overrides the default bulk OUT endpoint (0x02 = HQ). + * Diagnostic hook for 8814 TX validation — lets us bisect EP routing + * without rebuilding (0x03=NQ, 0x04=LQ on the 8814's 3-OUT-EP layout). */ + static uint8_t tx_ep = []() { + if (const char *ep_env = std::getenv("DEVOURER_TX_EP")) { + return static_cast(std::strtoul(ep_env, nullptr, 0)); + } + return static_cast(0x02); + }(); + + libusb_fill_bulk_transfer(transfer, _dev_handle, tx_ep, packet, length, transfer_callback, (void *)(_logger.get()), USB_TIMEOUT); auto start = std::chrono::high_resolution_clock::now(); diff --git a/txdemo/main.cpp b/txdemo/main.cpp index 1460fd0..bb5efb2 100644 --- a/txdemo/main.cpp +++ b/txdemo/main.cpp @@ -1,7 +1,12 @@ #include +#include +#include +#include +#include #include #include #include +#include #if defined(_MSC_VER) #include @@ -15,6 +20,7 @@ #include #include #else + #include #include #endif @@ -23,29 +29,33 @@ #include "WiFiDriver.h" #include "logger.h" -#include - - -// #define USB_VENDOR_ID 0x0bda -// #define USB_PRODUCT_ID 0x8812 - -void printHexArray(const uint8_t *array, size_t length) { - for (size_t i = 0; i < length; ++i) { - // Print each byte as a two-digit hexadecimal number - std::cout << "0x" << std::hex << std::setw(2) << std::setfill('0') - << static_cast(array[i]); - - // Print a space between bytes, but not after the last byte - if (i < length - 1) { - std::cout << " "; +#define USB_VENDOR_ID 0x0bda + +/* Known USB product IDs for the Realtek Jaguar family — same set as the RX + * demo (demo/main.cpp). */ +static constexpr uint16_t kRealtekProductIds[] = { + 0x8812, 0x0811, 0xa811, 0xb811, 0x8813, +}; + +static int g_rx_count = 0; +static void packetProcessor(const Packet &packet) { + ++g_rx_count; + /* Surface frames whose source MAC matches the txdemo's injected beacon + * (57:42:75:05:d6:00). The 802.11 header starts at packet.Data[0]; SA is + * at bytes [10..15] for a non-FromDS, non-ToDS frame. */ + if (packet.Data.size() >= 16) { + static const uint8_t kTxSa[6] = {0x57, 0x42, 0x75, 0x05, 0xd6, 0x00}; + if (std::memcmp(packet.Data.data() + 10, kTxSa, 6) == 0) { + static int hits = 0; + ++hits; + printf("RX from txdemo SA: hits=%d total_rx=%d len=%zu\n", + hits, g_rx_count, packet.Data.size()); + fflush(stdout); } } - std::cout << std::dec << std::endl; // Reset to decimal formatting } -static void packetProcessor(const Packet &packet) {} - -void usb_event_loop(Logger_t _logger,libusb_context* ctx){ +void usb_event_loop(Logger_t _logger, libusb_context *ctx) { while (true) { int r = libusb_handle_events(ctx); if (r < 0) { @@ -56,72 +66,112 @@ void usb_event_loop(Logger_t _logger,libusb_context* ctx){ } int main(int argc, char **argv) { - libusb_context *context; - libusb_device_handle *handle; - libusb_device *device; - struct libusb_device_descriptor desc; - uint8_t usb_frame[10000]; - unsigned char buffer[256]; - struct tx_desc *ptxdesc; - int fd; + libusb_context *context = nullptr; + libusb_device_handle *handle = nullptr; + int rc; auto logger = std::make_shared(); - // fd from argv is provided by termux on Android - // https://wiki.termux.com/wiki/Termux-usb - fd = atoi(argv[1]); - logger->info("got fd {}", fd); - - // libusb_set_option(context, LIBUSB_OPTION_LOG_LEVEL, - // LIBUSB_LOG_LEVEL_DEBUG); - libusb_set_option(NULL, LIBUSB_OPTION_NO_DEVICE_DISCOVERY); - libusb_set_option(NULL, LIBUSB_OPTION_WEAK_AUTHORITY); - - int rc = libusb_init(&context); - - rc = libusb_wrap_sys_device(context, (intptr_t)fd, &handle); - - device = libusb_get_device(handle); - - rc = libusb_get_device_descriptor(device, &desc); + /* Two modes: + * 1. Termux/Android: argv[1] = numeric USB fd (wrapped via + * libusb_wrap_sys_device). + * 2. Linux/macOS: no argv (or non-numeric) — open by VID/PID using the + * same list as the RX demo. DEVOURER_PID=0xNNNN restricts to a single + * PID. */ + long fd = (argc >= 2) ? std::strtol(argv[1], nullptr, 0) : 0; + const bool termux_mode = (fd > 0); + + if (termux_mode) { + logger->info("Termux mode: wrapping fd {}", fd); + libusb_set_option(NULL, LIBUSB_OPTION_NO_DEVICE_DISCOVERY); + libusb_set_option(NULL, LIBUSB_OPTION_WEAK_AUTHORITY); + rc = libusb_init(&context); + rc = libusb_wrap_sys_device(context, (intptr_t)fd, &handle); + } else { + rc = libusb_init(&context); + if (rc < 0) return rc; + + const char *pid_env = std::getenv("DEVOURER_PID"); + uint16_t target_pid = 0; + if (pid_env != nullptr) { + target_pid = static_cast(std::strtoul(pid_env, nullptr, 0)); + logger->info("DEVOURER_PID={:04x} (limiting to this PID)", target_pid); + } + for (uint16_t pid : kRealtekProductIds) { + if (target_pid != 0 && pid != target_pid) continue; + handle = libusb_open_device_with_vid_pid(context, USB_VENDOR_ID, pid); + if (handle != NULL) { + logger->info("Opened Realtek device {:04x}:{:04x}", USB_VENDOR_ID, pid); + break; + } + } + if (handle == NULL) { + logger->error("No supported Realtek device found under VID {:04x}", + USB_VENDOR_ID); + libusb_exit(context); + return 1; + } + } - logger->info("Vendor/Product ID:{:04x}:{:04x}", desc.idVendor, + libusb_device *device = libusb_get_device(handle); + libusb_device_descriptor desc{}; + libusb_get_device_descriptor(device, &desc); + logger->info("Vendor/Product ID: {:04x}:{:04x}", desc.idVendor, desc.idProduct); + if (libusb_kernel_driver_active(handle, 0)) { - rc = libusb_detach_kernel_driver(handle, 0); // detach driver - logger->error("libusb_detach_kernel_driver: {}", rc); + rc = libusb_detach_kernel_driver(handle, 0); + if (rc != 0) logger->error("libusb_detach_kernel_driver: {}", rc); + } + + if (!termux_mode && !std::getenv("DEVOURER_SKIP_RESET")) { + libusb_reset_device(handle); } + rc = libusb_claim_interface(handle, 0); + assert(rc == 0); WiFiDriver wifi_driver{logger}; auto rtlDevice = wifi_driver.CreateRtlDevice(handle); - pid_t fpid; - fpid = fork(); - rtlDevice->SetTxPower(40); - if (fpid == 0) { - - rtlDevice->Init(packetProcessor, SelectedChannel{ - .Channel = static_cast(161), - .ChannelOffset = 0, - .ChannelWidth = CHANNEL_WIDTH_20, - }); - - return 1; + int channel = 161; + if (const char *ch_env = std::getenv("DEVOURER_CHANNEL")) { + channel = std::atoi(ch_env); + logger->info("DEVOURER_CHANNEL set — tuning TX to channel {}", channel); } - // Loop for sending packets + rtlDevice->SetTxPower(40); - rtlDevice->InitWrite(SelectedChannel{ - .Channel = static_cast(161), - .ChannelOffset = 0, - .ChannelWidth = CHANNEL_WIDTH_20}); + /* The original txdemo forked an RX child and a TX parent on the same + * libusb handle. That pattern is Termux-specific (libusb_wrap_sys_device + * keeps the kernel fd shared across fork); on a regular Linux libusb + * context after fork(), both processes race on the same URB submission + * queue and the first vendor request after fork tends to fail with + * "rtw_read: iostream error". Skip the fork unless DEVOURER_TX_WITH_RX=1 + * is explicitly set (Termux callers can opt back in). For cross-adapter + * TX validation a second RX process on a separate adapter is what you + * want anyway. */ + if (std::getenv("DEVOURER_TX_WITH_RX")) { + pid_t fpid = fork(); + if (fpid == 0) { + rtlDevice->Init(packetProcessor, + SelectedChannel{ + .Channel = static_cast(channel), + .ChannelOffset = 0, + .ChannelWidth = CHANNEL_WIDTH_20, + }); + return 1; + } + } + rtlDevice->InitWrite(SelectedChannel{ + .Channel = static_cast(channel), + .ChannelOffset = 0, + .ChannelWidth = CHANNEL_WIDTH_20}); sleep(5); - // A new thread starts the libusb event loop - std::thread usb_thread(usb_event_loop,logger,context); + std::thread usb_thread(usb_event_loop, logger, context); uint8_t beacon_frame[] = { 0x00, 0x00, 0x0d, 0x00, 0x00, 0x80, 0x08, 0x00, 0x08, 0x00, 0x37, 0x00, 0x01, // radiotap header @@ -136,16 +186,20 @@ int main(int argc, char **argv) { 0xcd, 0xce, 0x4e, 0x35, 0xd9, 0x85, 0x9a, 0xcf, 0x4d, 0x48, 0x4c, 0x8f, 0x28, 0x6f, 0x10, 0xb0, 0xa9, 0x5d, 0xbf, 0xcb, 0x6f}; - int actual_length = 0; - + long tx_count = 0; while (true) { rc = rtlDevice->send_packet(beacon_frame, sizeof(beacon_frame)); + ++tx_count; + if (tx_count <= 10 || tx_count % 500 == 0) { + printf("TX #%ld rc=%d\n", tx_count, rc); + fflush(stdout); + } + std::this_thread::sleep_for(std::chrono::milliseconds(2)); /* ~500 fps, gentle on USB bulk EP */ } rc = libusb_release_interface(handle, 0); assert(rc == 0); libusb_close(handle); - libusb_exit(context); return 0; }