fix: RTL-SDR bug fixes and accuracy improvements

This commit is contained in:
2026-07-03 10:36:41 +02:00
parent 5363ca5265
commit 46c56b057b
5 changed files with 201 additions and 47 deletions
+1 -1
View File
@@ -12,4 +12,4 @@
#pragma once #pragma once
#define MONTAUK_BUILD_NUMBER 8 #define MONTAUK_BUILD_NUMBER 9
+9 -1
View File
@@ -155,7 +155,12 @@ namespace Drivers::Radio::Sdr {
uint32_t space = RING_BYTES - r.count; uint32_t space = RING_BYTES - r.count;
uint32_t n = len; uint32_t n = len;
uint32_t dropped = 0; uint32_t dropped = 0;
if (n > space) { dropped = n - space; n = space; } if (n > space) {
// Truncate to a whole number of I/Q byte pairs: dropping an odd
// count would swap I and Q for the rest of the stream.
n = space & ~1u;
dropped = len - n;
}
uint32_t first = RING_BYTES - r.head; uint32_t first = RING_BYTES - r.head;
if (first > n) first = n; if (first > n) first = n;
@@ -241,6 +246,9 @@ namespace Drivers::Radio::Sdr {
int Start(int handle) { int Start(int handle) {
Receiver* r = Lookup(handle, true); Receiver* r = Lookup(handle, true);
if (!r) return -1; if (!r) return -1;
// Already streaming: a second Start must not re-arm the driver's
// transfer pool (it would double-queue every buffer).
if (r->streaming) return 0;
r->lock.Acquire(); r->lock.Acquire();
r->head = r->count = 0; // discard stale samples before (re)starting r->head = r->count = 0; // discard stale samples before (re)starting
+104 -26
View File
@@ -120,12 +120,16 @@ namespace Drivers::USB::Radio {
return WriteReg(d, reg, merged); return WriteReg(d, reg, merged);
} }
// Read `len` bytes; the R820T always returns starting at register 0. The // Read `len` bytes starting at register 0. The read pointer must be set
// status registers are bit-reversed on the wire, so the PLL/VCO read paths // to 0 with an address-only write first: a preceding register write leaves
// un-reverse each byte to recover the logical value. // it pointing past the last register written, which would return the wrong
// registers here. The status registers are bit-reversed on the wire, so
// the PLL/VCO read paths un-reverse each byte to recover the logical value.
static bool Read(R820tDev& d, uint8_t* out, uint8_t len) { static bool Read(R820tDev& d, uint8_t* out, uint8_t len) {
uint8_t raw[16]; uint8_t raw[16];
if (len > sizeof(raw)) len = sizeof(raw); if (len > sizeof(raw)) len = sizeof(raw);
uint8_t ptr = 0x00;
if (!RtlI2cWrite(d.slotId, R820T_I2C_ADDR, &ptr, 1)) return false;
if (!RtlI2cRead(d.slotId, R820T_I2C_ADDR, raw, len)) return false; if (!RtlI2cRead(d.slotId, R820T_I2C_ADDR, raw, len)) return false;
for (uint8_t i = 0; i < len; i++) out[i] = BitRev(raw[i]); for (uint8_t i = 0; i < len; i++) out[i] = BitRev(raw[i]);
return true; return true;
@@ -135,6 +139,79 @@ namespace Drivers::USB::Radio {
// Detection / init // Detection / init
// ========================================================================= // =========================================================================
static bool SetPll(R820tDev& d, uint64_t freqHz); // defined below
// IF filter setup for the SDR receive path (the "BW < 6 MHz" digital-TV
// profile): calibrate the filter at a 56 MHz LO, then program the filter
// code, bandwidth / HP corner, image-rejection side and filter gain.
static bool ApplyIfFilterConfig(R820tDev& d) {
const uint8_t filtGain = 0x10; // +3 dB, 6 MHz on
const uint8_t imgR = 0x00; // image negative
const uint8_t filtQ = 0x10; // low Q
const uint8_t hpCor = 0x6b; // 1.7 MHz disable, +2 cap, 1.0 MHz corner
bool ok = true;
ok &= WriteRegMask(d, 0x0c, 0x00, 0x0f); // init flag & xtal check result
ok &= WriteRegMask(d, 0x13, 49, 0x3f); // version number
ok &= WriteRegMask(d, 0x1d, 0x00, 0x38); // LT gain test
// Filter calibration: park the PLL at 56 MHz, pulse the calibration
// trigger, read the resulting filter code back (status reg 4, low
// nibble). One retry; 0x0f means the calibration failed (use 0).
uint8_t calCode = 0;
for (int i = 0; i < 2; i++) {
ok &= WriteRegMask(d, 0x0b, hpCor, 0x60); // filt_cap
ok &= WriteRegMask(d, 0x0f, 0x04, 0x04); // calibration clock on
ok &= WriteRegMask(d, 0x10, 0x00, 0x03); // xtal cap 0 pF for PLL
SetPll(d, 56000000ull); // lock not required here
ok &= WriteRegMask(d, 0x0b, 0x10, 0x10); // start trigger
ok &= WriteRegMask(d, 0x0b, 0x00, 0x10); // stop trigger
ok &= WriteRegMask(d, 0x0f, 0x00, 0x04); // calibration clock off
uint8_t data[5] = {0};
if (!Read(d, data, sizeof(data))) return false;
calCode = (uint8_t)(data[4] & 0x0f);
if (calCode && calCode != 0x0f) break;
}
if (calCode == 0x0f) calCode = 0;
ok &= WriteRegMask(d, 0x0a, (uint8_t)(filtQ | calCode), 0x1f);
ok &= WriteRegMask(d, 0x0b, hpCor, 0xef); // bandwidth, filter gain, HP corner
ok &= WriteRegMask(d, 0x07, imgR, 0x80); // image rejection side
ok &= WriteRegMask(d, 0x06, filtGain, 0x30);// filt_3dB
ok &= WriteRegMask(d, 0x1e, 0x60, 0x60); // channel filter extension @ LNA max-1
ok &= WriteRegMask(d, 0x05, 0x01, 0x80); // loop-through
ok &= WriteRegMask(d, 0x1f, 0x00, 0x80); // loop-through attenuation enable
ok &= WriteRegMask(d, 0x0f, 0x00, 0x80); // filter extension widest: off
ok &= WriteRegMask(d, 0x19, 0x60, 0x60); // RF poly filter current: min
return ok;
}
// Receive-path operating point for the digital/SDR profile: LNA and mixer
// detector top points and thresholds, input select, charge-pump and
// divider-buffer currents, AGC clock rate.
static bool ApplySysFreqConfig(R820tDev& d) {
bool ok = true;
ok &= WriteRegMask(d, 0x1d, 0xe5, 0xc7); // LNA top (detect bw 3, top 4)
ok &= WriteRegMask(d, 0x1c, 0x24, 0xf8); // mixer top 13, top-1, low-discharge
ok &= WriteReg(d, 0x0d, 0x53); // LNA vth 0.84 / vtl 0.64
ok &= WriteReg(d, 0x0e, 0x75); // mixer vth 1.04 / vtl 0.84
ok &= WriteRegMask(d, 0x05, 0x00, 0x60); // air-in input select
ok &= WriteRegMask(d, 0x06, 0x00, 0x08); // cable-2 input off
ok &= WriteRegMask(d, 0x11, 0x38, 0x38); // charge-pump current: auto
ok &= WriteRegMask(d, 0x17, 0x30, 0x30); // divider buffer current 150u
ok &= WriteRegMask(d, 0x0a, 0x40, 0x60); // filter current: low
ok &= WriteRegMask(d, 0x1d, 0x00, 0x38); // LNA top: lowest
ok &= WriteRegMask(d, 0x1c, 0x00, 0x04); // normal mode
ok &= WriteRegMask(d, 0x06, 0x00, 0x40); // pre-detect off
ok &= WriteRegMask(d, 0x1a, 0x30, 0x30); // AGC clock 250 Hz
ok &= WriteRegMask(d, 0x1d, 0x18, 0x38); // LNA top = 3
ok &= WriteRegMask(d, 0x1c, 0x24, 0x04); // mixer top bit
ok &= WriteRegMask(d, 0x1e, 0x0e, 0x1f); // LNA discharge current 14
ok &= WriteRegMask(d, 0x1a, 0x20, 0x30); // AGC clock 60 Hz
return ok;
}
bool R820tDetect(uint8_t slotId) { bool R820tDetect(uint8_t slotId) {
// Match the reference driver's chip-id probe: set the read pointer to // Match the reference driver's chip-id probe: set the read pointer to
// register 0, then read one byte *without* bit-reversal and compare it // register 0, then read one byte *without* bit-reversal and compare it
@@ -161,13 +238,21 @@ namespace Drivers::USB::Radio {
d.hasLock = false; d.hasLock = false;
d.inited = false; d.inited = false;
// Load the init register block. The IF low-pass filter is left at the // Load the init register block, then bring the IF filter and the
// init-array default (widest practical for the SDR receive path); a // receive-path operating point to the SDR profile (incl. the 56 MHz
// dedicated bandwidth calibration is not performed. // filter calibration).
if (!Write(d, 0x05, kInitArray, sizeof(kInitArray))) { if (!Write(d, 0x05, kInitArray, sizeof(kInitArray))) {
KernelLogStream(ERROR, "R820T") << "init register write failed"; KernelLogStream(ERROR, "R820T") << "init register write failed";
return false; return false;
} }
if (!ApplyIfFilterConfig(d)) {
KernelLogStream(ERROR, "R820T") << "IF filter configuration failed";
return false;
}
if (!ApplySysFreqConfig(d)) {
KernelLogStream(ERROR, "R820T") << "receive path configuration failed";
return false;
}
d.inited = true; d.inited = true;
KernelLogStream(OK, "R820T") << "Tuner initialised (xtal=" KernelLogStream(OK, "R820T") << "Tuner initialised (xtal="
@@ -209,7 +294,6 @@ namespace Drivers::USB::Radio {
const uint32_t vcoMinKhz = 1770000; // 1.77 GHz const uint32_t vcoMinKhz = 1770000; // 1.77 GHz
const uint32_t vcoMaxKhz = vcoMinKhz * 2; // 3.54 GHz const uint32_t vcoMaxKhz = vcoMinKhz * 2; // 3.54 GHz
uint32_t pllRef = d.xtal; uint32_t pllRef = d.xtal;
uint32_t pllRefKhz = (d.xtal + 500) / 1000;
uint32_t freqKhz = (uint32_t)((freqHz + 500) / 1000); uint32_t freqKhz = (uint32_t)((freqHz + 500) / 1000);
bool ok = true; bool ok = true;
@@ -240,28 +324,21 @@ namespace Drivers::USB::Radio {
ok &= WriteRegMask(d, 0x10, (uint8_t)(divNum << 5), 0xe0); ok &= WriteRegMask(d, 0x10, (uint8_t)(divNum << 5), 0xe0);
uint64_t vcoFreq = freqHz * (uint64_t)mixDiv; uint64_t vcoFreq = freqHz * (uint64_t)mixDiv;
uint32_t nint = (uint32_t)(vcoFreq / (2u * pllRef));
uint32_t vcoFra = (uint32_t)((vcoFreq - 2ull * pllRef * nint) / 1000); // Exact fractional-N split: vcoDiv = round(65536 * vcoFreq / (2*ref)).
// The top bits are the integer divider, the low 16 bits feed the
// sigma-delta modulator directly (no iterative approximation).
uint64_t vcoDiv = (pllRef + 65536ull * vcoFreq) / (2ull * pllRef);
uint32_t nint = (uint32_t)(vcoDiv >> 16);
uint16_t sdm = (uint16_t)(vcoDiv & 0xffff);
if (nint < 13) nint = 13; // keep ni/si arithmetic well-defined if (nint < 13) nint = 13; // keep ni/si arithmetic well-defined
uint8_t ni = (uint8_t)((nint - 13) / 4); uint8_t ni = (uint8_t)((nint - 13) / 4);
uint8_t si = (uint8_t)(nint - 4 * ni - 13); uint8_t si = (uint8_t)(nint - 4 * ni - 13);
ok &= WriteReg(d, 0x14, (uint8_t)(ni + (si << 6))); ok &= WriteReg(d, 0x14, (uint8_t)(ni + (si << 6)));
// Sigma-delta fractional path. // Sigma-delta fractional path (powered down when the fraction is 0).
ok &= WriteRegMask(d, 0x12, vcoFra ? 0x00 : 0x08, 0x08); ok &= WriteRegMask(d, 0x12, sdm ? 0x00 : 0x08, 0x08);
uint16_t nSdm = 2;
uint16_t sdm = 0;
while (vcoFra > 1) {
uint32_t step = 2 * pllRefKhz / nSdm;
if (vcoFra > step) {
sdm = (uint16_t)(sdm + 32768 / (nSdm / 2));
vcoFra -= step;
if (nSdm >= 0x8000) break;
}
nSdm <<= 1;
}
ok &= WriteReg(d, 0x16, (uint8_t)(sdm >> 8)); ok &= WriteReg(d, 0x16, (uint8_t)(sdm >> 8));
ok &= WriteReg(d, 0x15, (uint8_t)(sdm & 0xff)); ok &= WriteReg(d, 0x15, (uint8_t)(sdm & 0xff));
@@ -309,9 +386,10 @@ namespace Drivers::USB::Radio {
return false; return false;
} }
// Air-in vs Cable-1 input select crosses over at 345 MHz. // NOTE: the Air-in vs Cable-1 input switch at 345 MHz applies to the
uint8_t airCable1In = (rfHz > 345000000ull) ? 0x00 : 0x60; // R828D only. The R820T/T2 uses the air input exclusively (selected
WriteRegMask(d, 0x05, airCable1In, 0x60); // during init); writing the Cable-1 select on an R820T disconnects the
// antenna input below 345 MHz.
return true; return true;
} }
+51 -16
View File
@@ -64,6 +64,8 @@ namespace Drivers::USB::Radio {
static int g_ppm = 0; static int g_ppm = 0;
static int g_manual = 0; // tuner gain mode (0=auto) static int g_manual = 0; // tuner gain mode (0=auto)
static int g_gain = 0; // tuner gain, tenths of dB static int g_gain = 0; // tuner gain, tenths of dB
static int g_directSamp = 0; // 0=tuner path, 1=I ADC, 2=Q ADC
static uint64_t g_lastFreq = 0; // last successfully tuned freq (Hz)
// Bulk-IN streaming geometry. We keep BULK_POOL_BUFS transfers of // Bulk-IN streaming geometry. We keep BULK_POOL_BUFS transfers of
// BULK_XFER_LEN bytes outstanding at once (multi-URB), so the RTL2832U FIFO // BULK_XFER_LEN bytes outstanding at once (multi-URB), so the RTL2832U FIFO
@@ -92,14 +94,26 @@ namespace Drivers::USB::Radio {
g_ctlBuf, false) == Xhci::CC_SUCCESS; g_ctlBuf, false) == Xhci::CC_SUCCESS;
} }
static uint8_t DemodRead(uint8_t page, uint16_t addr) {
if (!g_ctlBuf) return 0;
uint16_t raddr = (uint16_t)((addr << 8) | 0x20);
g_ctlBuf[0] = 0;
Xhci::ControlTransfer(g_slotId, CTRL_IN, 0, raddr, page, 1, g_ctlBuf, true);
return g_ctlBuf[0];
}
static bool DemodWrite(uint8_t page, uint16_t addr, uint16_t val, uint8_t len) { static bool DemodWrite(uint8_t page, uint16_t addr, uint16_t val, uint8_t len) {
if (!g_ctlBuf) return false; if (!g_ctlBuf) return false;
uint16_t waddr = (uint16_t)((addr << 8) | 0x20); uint16_t waddr = (uint16_t)((addr << 8) | 0x20);
uint16_t index = (uint16_t)(0x10 | page); uint16_t index = (uint16_t)(0x10 | page);
g_ctlBuf[0] = (len == 1) ? (uint8_t)(val & 0xff) : (uint8_t)(val >> 8); g_ctlBuf[0] = (len == 1) ? (uint8_t)(val & 0xff) : (uint8_t)(val >> 8);
g_ctlBuf[1] = (uint8_t)(val & 0xff); g_ctlBuf[1] = (uint8_t)(val & 0xff);
return Xhci::ControlTransfer(g_slotId, CTRL_OUT, 0, waddr, index, len, bool ok = Xhci::ControlTransfer(g_slotId, CTRL_OUT, 0, waddr, index, len,
g_ctlBuf, false) == Xhci::CC_SUCCESS; g_ctlBuf, false) == Xhci::CC_SUCCESS;
// Dummy status read after every demod write (reference behaviour);
// acts as a write barrier so the register latches before the next op.
DemodRead(0x0a, 0x01);
return ok;
} }
static void SetI2cRepeater(bool on) { static void SetI2cRepeater(bool on) {
@@ -246,9 +260,16 @@ namespace Drivers::USB::Radio {
static int DoSetFreq(uint64_t hz) { static int DoSetFreq(uint64_t hz) {
if (!EnsureInit()) return -1; if (!EnsureInit()) return -1;
if (g_directSamp) {
// Tuner is bypassed: tuning is the demod's digital downconverter.
SetIfFreq((uint32_t)hz);
g_lastFreq = hz;
return 0;
}
SetI2cRepeater(true); SetI2cRepeater(true);
bool ok = R820tSetFreq(g_tuner, hz); bool ok = R820tSetFreq(g_tuner, hz);
SetI2cRepeater(false); SetI2cRepeater(false);
if (ok) g_lastFreq = hz;
return ok ? 0 : -1; return ok ? 0 : -1;
} }
@@ -256,9 +277,12 @@ namespace Drivers::USB::Radio {
if (!EnsureInit()) return -1; if (!EnsureInit()) return -1;
// The RTL2832 resampler does not cover 300k..900k. // The RTL2832 resampler does not cover 300k..900k.
if (rate <= 225000 || rate > 3200000 || if (rate <= 225000 || rate > 3200000 ||
(rate > 300000 && rate < 900000)) return -1; (rate > 300000 && rate <= 900000)) return -1;
uint32_t ratio = (uint32_t)(((uint64_t)g_rtlXtal * TWO_POW22) / rate); // The ratio uses the NOMINAL crystal frequency: ppm correction is
// applied by the demod's sample-frequency-offset registers below, so
// baking it into the ratio too would correct the rate twice.
uint32_t ratio = (uint32_t)(((uint64_t)RTL_XTAL * TWO_POW22) / rate);
ratio &= 0x0ffffffc; ratio &= 0x0ffffffc;
DemodWrite(1, 0x9f, (uint16_t)((ratio >> 16) & 0xffff), 2); DemodWrite(1, 0x9f, (uint16_t)((ratio >> 16) & 0xffff), 2);
DemodWrite(1, 0xa1, (uint16_t)(ratio & 0xffff), 2); DemodWrite(1, 0xa1, (uint16_t)(ratio & 0xffff), 2);
@@ -266,7 +290,7 @@ namespace Drivers::USB::Radio {
ApplySampleFreqCorrection(); ApplySampleFreqCorrection();
DemodWrite(1, 0x01, 0x14, 1); // soft reset DemodWrite(1, 0x01, 0x14, 1); // soft reset
DemodWrite(1, 0x01, 0x10, 1); DemodWrite(1, 0x01, 0x10, 1);
SetIfFreq(R82XX_IF); SetIfFreq(g_directSamp ? (uint32_t)g_lastFreq : R82XX_IF);
return 0; return 0;
} }
@@ -295,6 +319,9 @@ namespace Drivers::USB::Radio {
g_rtlXtal = (uint32_t)((int64_t)RTL_XTAL + (int64_t)RTL_XTAL * ppm / 1000000); g_rtlXtal = (uint32_t)((int64_t)RTL_XTAL + (int64_t)RTL_XTAL * ppm / 1000000);
g_tuner.xtal = g_rtlXtal; g_tuner.xtal = g_rtlXtal;
ApplySampleFreqCorrection(); ApplySampleFreqCorrection();
// The tuner PLL (and, in direct mode, the DDC) derive from the xtal;
// retune so the new correction actually takes effect.
if (g_lastFreq) return DoSetFreq(g_lastFreq);
return 0; return 0;
} }
@@ -310,18 +337,25 @@ namespace Drivers::USB::Radio {
SetI2cRepeater(true); SetI2cRepeater(true);
R820tStandby(g_tuner); R820tStandby(g_tuner);
SetI2cRepeater(false); SetI2cRepeater(false);
DemodWrite(1, 0xb1, 0x1b, 1); // zero-IF path DemodWrite(1, 0xb1, 0x1a, 1); // disable zero-IF
DemodWrite(0, 0x08, 0x4d, 1); DemodWrite(1, 0x15, 0x00, 1); // no spectrum inversion
DemodWrite(0, 0x08, 0x4d, 1); // In-phase ADC input
DemodWrite(0, 0x06, (mode == 2) ? 0x90 : 0x80, 1); // Q vs I ADC DemodWrite(0, 0x06, (mode == 2) ? 0x90 : 0x80, 1); // Q vs I ADC
SetIfFreq(0); g_directSamp = mode;
DemodWrite(1, 0x15, 0x00, 1); // Tuning now happens in the DDC; carry the current frequency over.
SetIfFreq((uint32_t)g_lastFreq);
} else { } else {
// Restore the R820T2 low-IF receive path. // Restore the R820T2 low-IF receive path. Standby powered the
DemodWrite(1, 0xb1, 0x1a, 1); // tuner down, so it needs a full re-initialisation.
DemodWrite(0, 0x08, 0x4d, 1); SetI2cRepeater(true);
DemodWrite(0, 0x06, 0x80, 1); bool ok = R820tInit(g_tuner, g_slotId, g_rtlXtal, R82XX_IF);
SetI2cRepeater(false);
if (!ok) return -1;
SetIfFreq(R82XX_IF); SetIfFreq(R82XX_IF);
DemodWrite(1, 0x15, 0x01, 1); DemodWrite(1, 0x15, 0x01, 1); // enable spectrum inversion
DemodWrite(0, 0x06, 0x80, 1); // default ADC I/Q datapath
g_directSamp = 0;
if (g_lastFreq) return DoSetFreq(g_lastFreq);
} }
return 0; return 0;
} }
@@ -447,11 +481,10 @@ namespace Drivers::USB::Radio {
bool IsRtlSdr(uint16_t vid, uint16_t pid) { bool IsRtlSdr(uint16_t vid, uint16_t pid) {
if (vid != 0x0bda) return false; // Realtek Semiconductor if (vid != 0x0bda) return false; // Realtek Semiconductor
switch (pid) { switch (pid) {
// Only the two known RTL2832U ids. In particular 0x2831 is the
// RTL2831U, a DIFFERENT demod this driver cannot program.
case 0x2832: // RTL2832U (generic) case 0x2832: // RTL2832U (generic)
case 0x2838: // RTL2838 (most RTL-SDR.com dongles) case 0x2838: // RTL2838 (most RTL-SDR.com dongles)
case 0x2831: // RTL2831U
case 0x2837: // RTL2832U variant
case 0x2834: // RTL2832U variant
return true; return true;
default: default:
return false; return false;
@@ -473,6 +506,8 @@ namespace Drivers::USB::Radio {
g_rtlXtal = RTL_XTAL; g_rtlXtal = RTL_XTAL;
g_manual = 0; g_manual = 0;
g_gain = 0; g_gain = 0;
g_directSamp = 0;
g_lastFreq = 0;
g_tuner = R820tDev{}; g_tuner = R820tDev{};
g_ctlBuf = (uint8_t*)Memory::g_pfa->AllocateZeroed(); g_ctlBuf = (uint8_t*)Memory::g_pfa->AllocateZeroed();
+33
View File
@@ -1024,6 +1024,10 @@ namespace Drivers::USB::Xhci {
if (slotId == 0 || slotId > MAX_SLOTS || !g_devices[slotId].Active) return; if (slotId == 0 || slotId > MAX_SLOTS || !g_devices[slotId].Active) return;
UsbDeviceInfo& dev = g_devices[slotId]; UsbDeviceInfo& dev = g_devices[slotId];
if (!dev.BulkInRing || dev.BulkInEpNum == 0) return; if (!dev.BulkInRing || dev.BulkInEpNum == 0) return;
// Already armed: priming again would put a second transfer on the ring
// for every pool buffer and desync the completion rotation (callbacks
// would be handed the wrong buffer). Callers must Stop first.
if (g_bulkInPoolCount[slotId] != 0) return;
if (numBuffers < 1) numBuffers = 1; if (numBuffers < 1) numBuffers = 1;
if (numBuffers > BULK_IN_POOL_MAX) numBuffers = BULK_IN_POOL_MAX; if (numBuffers > BULK_IN_POOL_MAX) numBuffers = BULK_IN_POOL_MAX;
@@ -1060,7 +1064,36 @@ namespace Drivers::USB::Xhci {
if (slotId == 0 || slotId > MAX_SLOTS) return; if (slotId == 0 || slotId > MAX_SLOTS) return;
// Disarm the rotation; any late completion now takes the (no-op for SDR) // Disarm the rotation; any late completion now takes the (no-op for SDR)
// legacy path and is not re-armed. Buffers are retained for reuse. // legacy path and is not re-armed. Buffers are retained for reuse.
bool wasArmed = g_bulkInPoolCount[slotId] != 0;
g_bulkInPoolCount[slotId] = 0; g_bulkInPoolCount[slotId] = 0;
if (!wasArmed) return;
// Flush the up-to-PoolCount TRBs still pending on the ring: Stop
// Endpoint, then Set TR Dequeue to the enqueue pointer. Without this a
// later Start would stack a fresh pool behind the stale TRBs and
// overflow the 32-entry transfer ring (16 stale + 16 new > 31 usable),
// wrapping the enqueue pointer onto still-pending TRBs.
UsbDeviceInfo& dev = g_devices[slotId];
if (!dev.Active || dev.BulkInEpNum == 0 || !dev.BulkInRing) return;
uint8_t dci = dev.BulkInEpNum * 2 + 1;
TRB stopTrb = {};
stopTrb.Control = (TRB_STOP_ENDPOINT << TRB_TYPE_SHIFT)
| ((uint32_t)slotId << 24)
| ((uint32_t)dci << 16);
SendCommand(stopTrb);
uint64_t newDeq = dev.BulkInRingPhys
+ (uint64_t)dev.BulkInRingEnqueue * sizeof(TRB);
if (dev.BulkInRingCCS) newDeq |= 1; // DCS bit
TRB deqTrb = {};
deqTrb.Parameter0 = (uint32_t)(newDeq & 0xFFFFFFFF);
deqTrb.Parameter1 = (uint32_t)(newDeq >> 32);
deqTrb.Control = (TRB_SET_TR_DEQUEUE << TRB_TYPE_SHIFT)
| ((uint32_t)slotId << 24)
| ((uint32_t)dci << 16);
SendCommand(deqTrb);
} }
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------