fix: RTL-SDR bug fixes and accuracy improvements
This commit is contained in:
@@ -12,4 +12,4 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#define MONTAUK_BUILD_NUMBER 8
|
||||
#define MONTAUK_BUILD_NUMBER 9
|
||||
|
||||
@@ -155,7 +155,12 @@ namespace Drivers::Radio::Sdr {
|
||||
uint32_t space = RING_BYTES - r.count;
|
||||
uint32_t n = len;
|
||||
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;
|
||||
if (first > n) first = n;
|
||||
@@ -241,6 +246,9 @@ namespace Drivers::Radio::Sdr {
|
||||
int Start(int handle) {
|
||||
Receiver* r = Lookup(handle, true);
|
||||
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->head = r->count = 0; // discard stale samples before (re)starting
|
||||
|
||||
@@ -120,12 +120,16 @@ namespace Drivers::USB::Radio {
|
||||
return WriteReg(d, reg, merged);
|
||||
}
|
||||
|
||||
// Read `len` bytes; the R820T always returns starting at register 0. The
|
||||
// status registers are bit-reversed on the wire, so the PLL/VCO read paths
|
||||
// un-reverse each byte to recover the logical value.
|
||||
// Read `len` bytes starting at register 0. The read pointer must be set
|
||||
// to 0 with an address-only write first: a preceding register write leaves
|
||||
// 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) {
|
||||
uint8_t raw[16];
|
||||
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;
|
||||
for (uint8_t i = 0; i < len; i++) out[i] = BitRev(raw[i]);
|
||||
return true;
|
||||
@@ -135,6 +139,79 @@ namespace Drivers::USB::Radio {
|
||||
// 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) {
|
||||
// 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
|
||||
@@ -161,13 +238,21 @@ namespace Drivers::USB::Radio {
|
||||
d.hasLock = false;
|
||||
d.inited = false;
|
||||
|
||||
// Load the init register block. The IF low-pass filter is left at the
|
||||
// init-array default (widest practical for the SDR receive path); a
|
||||
// dedicated bandwidth calibration is not performed.
|
||||
// Load the init register block, then bring the IF filter and the
|
||||
// receive-path operating point to the SDR profile (incl. the 56 MHz
|
||||
// filter calibration).
|
||||
if (!Write(d, 0x05, kInitArray, sizeof(kInitArray))) {
|
||||
KernelLogStream(ERROR, "R820T") << "init register write failed";
|
||||
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;
|
||||
KernelLogStream(OK, "R820T") << "Tuner initialised (xtal="
|
||||
@@ -208,9 +293,8 @@ namespace Drivers::USB::Radio {
|
||||
static bool SetPll(R820tDev& d, uint64_t freqHz) {
|
||||
const uint32_t vcoMinKhz = 1770000; // 1.77 GHz
|
||||
const uint32_t vcoMaxKhz = vcoMinKhz * 2; // 3.54 GHz
|
||||
uint32_t pllRef = d.xtal;
|
||||
uint32_t pllRefKhz = (d.xtal + 500) / 1000;
|
||||
uint32_t freqKhz = (uint32_t)((freqHz + 500) / 1000);
|
||||
uint32_t pllRef = d.xtal;
|
||||
uint32_t freqKhz = (uint32_t)((freqHz + 500) / 1000);
|
||||
|
||||
bool ok = true;
|
||||
ok &= WriteRegMask(d, 0x10, 0x00, 0x10); // refdiv = /1
|
||||
@@ -240,28 +324,21 @@ namespace Drivers::USB::Radio {
|
||||
ok &= WriteRegMask(d, 0x10, (uint8_t)(divNum << 5), 0xe0);
|
||||
|
||||
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
|
||||
uint8_t ni = (uint8_t)((nint - 13) / 4);
|
||||
uint8_t si = (uint8_t)(nint - 4 * ni - 13);
|
||||
ok &= WriteReg(d, 0x14, (uint8_t)(ni + (si << 6)));
|
||||
|
||||
// Sigma-delta fractional path.
|
||||
ok &= WriteRegMask(d, 0x12, vcoFra ? 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;
|
||||
}
|
||||
// Sigma-delta fractional path (powered down when the fraction is 0).
|
||||
ok &= WriteRegMask(d, 0x12, sdm ? 0x00 : 0x08, 0x08);
|
||||
ok &= WriteReg(d, 0x16, (uint8_t)(sdm >> 8));
|
||||
ok &= WriteReg(d, 0x15, (uint8_t)(sdm & 0xff));
|
||||
|
||||
@@ -309,9 +386,10 @@ namespace Drivers::USB::Radio {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Air-in vs Cable-1 input select crosses over at 345 MHz.
|
||||
uint8_t airCable1In = (rfHz > 345000000ull) ? 0x00 : 0x60;
|
||||
WriteRegMask(d, 0x05, airCable1In, 0x60);
|
||||
// NOTE: the Air-in vs Cable-1 input switch at 345 MHz applies to the
|
||||
// R828D only. The R820T/T2 uses the air input exclusively (selected
|
||||
// during init); writing the Cable-1 select on an R820T disconnects the
|
||||
// antenna input below 345 MHz.
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -64,6 +64,8 @@ namespace Drivers::USB::Radio {
|
||||
static int g_ppm = 0;
|
||||
static int g_manual = 0; // tuner gain mode (0=auto)
|
||||
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_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;
|
||||
}
|
||||
|
||||
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) {
|
||||
if (!g_ctlBuf) return false;
|
||||
uint16_t waddr = (uint16_t)((addr << 8) | 0x20);
|
||||
uint16_t index = (uint16_t)(0x10 | page);
|
||||
g_ctlBuf[0] = (len == 1) ? (uint8_t)(val & 0xff) : (uint8_t)(val >> 8);
|
||||
g_ctlBuf[1] = (uint8_t)(val & 0xff);
|
||||
return Xhci::ControlTransfer(g_slotId, CTRL_OUT, 0, waddr, index, len,
|
||||
g_ctlBuf, false) == Xhci::CC_SUCCESS;
|
||||
bool ok = Xhci::ControlTransfer(g_slotId, CTRL_OUT, 0, waddr, index, len,
|
||||
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) {
|
||||
@@ -246,9 +260,16 @@ namespace Drivers::USB::Radio {
|
||||
|
||||
static int DoSetFreq(uint64_t hz) {
|
||||
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);
|
||||
bool ok = R820tSetFreq(g_tuner, hz);
|
||||
SetI2cRepeater(false);
|
||||
if (ok) g_lastFreq = hz;
|
||||
return ok ? 0 : -1;
|
||||
}
|
||||
|
||||
@@ -256,9 +277,12 @@ namespace Drivers::USB::Radio {
|
||||
if (!EnsureInit()) return -1;
|
||||
// The RTL2832 resampler does not cover 300k..900k.
|
||||
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;
|
||||
DemodWrite(1, 0x9f, (uint16_t)((ratio >> 16) & 0xffff), 2);
|
||||
DemodWrite(1, 0xa1, (uint16_t)(ratio & 0xffff), 2);
|
||||
@@ -266,7 +290,7 @@ namespace Drivers::USB::Radio {
|
||||
ApplySampleFreqCorrection();
|
||||
DemodWrite(1, 0x01, 0x14, 1); // soft reset
|
||||
DemodWrite(1, 0x01, 0x10, 1);
|
||||
SetIfFreq(R82XX_IF);
|
||||
SetIfFreq(g_directSamp ? (uint32_t)g_lastFreq : R82XX_IF);
|
||||
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_tuner.xtal = g_rtlXtal;
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -310,18 +337,25 @@ namespace Drivers::USB::Radio {
|
||||
SetI2cRepeater(true);
|
||||
R820tStandby(g_tuner);
|
||||
SetI2cRepeater(false);
|
||||
DemodWrite(1, 0xb1, 0x1b, 1); // zero-IF path
|
||||
DemodWrite(0, 0x08, 0x4d, 1);
|
||||
DemodWrite(1, 0xb1, 0x1a, 1); // disable zero-IF
|
||||
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
|
||||
SetIfFreq(0);
|
||||
DemodWrite(1, 0x15, 0x00, 1);
|
||||
g_directSamp = mode;
|
||||
// Tuning now happens in the DDC; carry the current frequency over.
|
||||
SetIfFreq((uint32_t)g_lastFreq);
|
||||
} else {
|
||||
// Restore the R820T2 low-IF receive path.
|
||||
DemodWrite(1, 0xb1, 0x1a, 1);
|
||||
DemodWrite(0, 0x08, 0x4d, 1);
|
||||
DemodWrite(0, 0x06, 0x80, 1);
|
||||
// Restore the R820T2 low-IF receive path. Standby powered the
|
||||
// tuner down, so it needs a full re-initialisation.
|
||||
SetI2cRepeater(true);
|
||||
bool ok = R820tInit(g_tuner, g_slotId, g_rtlXtal, R82XX_IF);
|
||||
SetI2cRepeater(false);
|
||||
if (!ok) return -1;
|
||||
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;
|
||||
}
|
||||
@@ -447,11 +481,10 @@ namespace Drivers::USB::Radio {
|
||||
bool IsRtlSdr(uint16_t vid, uint16_t pid) {
|
||||
if (vid != 0x0bda) return false; // Realtek Semiconductor
|
||||
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 0x2838: // RTL2838 (most RTL-SDR.com dongles)
|
||||
case 0x2831: // RTL2831U
|
||||
case 0x2837: // RTL2832U variant
|
||||
case 0x2834: // RTL2832U variant
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
@@ -473,6 +506,8 @@ namespace Drivers::USB::Radio {
|
||||
g_rtlXtal = RTL_XTAL;
|
||||
g_manual = 0;
|
||||
g_gain = 0;
|
||||
g_directSamp = 0;
|
||||
g_lastFreq = 0;
|
||||
g_tuner = R820tDev{};
|
||||
|
||||
g_ctlBuf = (uint8_t*)Memory::g_pfa->AllocateZeroed();
|
||||
|
||||
@@ -1024,6 +1024,10 @@ namespace Drivers::USB::Xhci {
|
||||
if (slotId == 0 || slotId > MAX_SLOTS || !g_devices[slotId].Active) return;
|
||||
UsbDeviceInfo& dev = g_devices[slotId];
|
||||
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 > BULK_IN_POOL_MAX) numBuffers = BULK_IN_POOL_MAX;
|
||||
@@ -1060,7 +1064,36 @@ namespace Drivers::USB::Xhci {
|
||||
if (slotId == 0 || slotId > MAX_SLOTS) return;
|
||||
// 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.
|
||||
bool wasArmed = 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);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user