fix: move RTL-SDR to userspace
This commit is contained in:
1 parent
b1f1cfe32b
commit
5cd5c2e6be
35 files changed
+1922
-1592
No files matched your search
@@ -0,0 +1,32 @@
|
||||
MAKEFLAGS += -rR
|
||||
.SUFFIXES:
|
||||
TOOLCHAIN_PREFIX := $(shell cd ../../.. && pwd)/toolchain/local/bin/x86_64-montauk-
|
||||
CXX := $(TOOLCHAIN_PREFIX)g++
|
||||
CC := $(TOOLCHAIN_PREFIX)gcc
|
||||
LD := $(TOOLCHAIN_PREFIX)ld
|
||||
STRIP := $(TOOLCHAIN_PREFIX)strip
|
||||
PROG_INC := ../../include
|
||||
OBJDIR := obj
|
||||
LIBOUTDIR := ../../bin/os
|
||||
LIBC_SRC := ../../lib/libc/libc.c
|
||||
GCC_INCLUDE := $(shell $(CC) -print-file-name=include)
|
||||
CXXFLAGS := -std=gnu++20 -g -O2 -pipe -Wall -Wextra -ffreestanding -fno-stack-protector -fno-stack-check -fPIC -fvisibility=hidden -fno-rtti -fno-exceptions -ffunction-sections -fdata-sections -mno-80387 -mno-mmx -mno-sse -mno-sse2 -MMD -MP -I $(PROG_INC) -isystem $(PROG_INC)/libc
|
||||
LIBC_CFLAGS := -std=gnu11 -O2 -ffreestanding -fno-stack-protector -fno-stack-check -fPIC -fvisibility=hidden -ffunction-sections -fdata-sections -isystem $(PROG_INC)/libc -isystem $(GCC_INCLUDE)
|
||||
LDFLAGS := -shared --build-id=none --gc-sections --hash-style=sysv -m elf_x86_64
|
||||
OBJS := $(OBJDIR)/rtlsdr.o $(OBJDIR)/r820t.o
|
||||
TARGET := $(LIBOUTDIR)/rtlsdr.lib
|
||||
.PHONY: all clean
|
||||
all: $(TARGET)
|
||||
$(TARGET): $(OBJS) $(OBJDIR)/libc_pic.o Makefile
|
||||
mkdir -p $(LIBOUTDIR)
|
||||
$(LD) $(LDFLAGS) $(OBJS) $(OBJDIR)/libc_pic.o -o $@
|
||||
$(STRIP) --strip-debug $@
|
||||
$(OBJDIR)/%.o: %.cpp Makefile
|
||||
mkdir -p $(OBJDIR)
|
||||
$(CXX) $(CXXFLAGS) -c $< -o $@
|
||||
$(OBJDIR)/libc_pic.o: $(LIBC_SRC) Makefile
|
||||
mkdir -p $(OBJDIR)
|
||||
$(CC) $(LIBC_CFLAGS) -c $(LIBC_SRC) -o $@
|
||||
-include $(OBJS:.o=.d)
|
||||
clean:
|
||||
rm -rf $(OBJDIR) $(TARGET)
|
||||
@@ -0,0 +1,445 @@
|
||||
#include "r820t.hpp"
|
||||
|
||||
namespace {
|
||||
struct LogSink { template<class T> LogSink& operator<<(const T&) { return *this; } };
|
||||
namespace base { static constexpr int hex = 0; static constexpr int dec = 0; }
|
||||
}
|
||||
#define KernelLogStream(level, tag) LogSink{}
|
||||
#define WARNING 0
|
||||
#define INFO 0
|
||||
#define ERROR 0
|
||||
#define OK 0
|
||||
|
||||
namespace rtlsdr_internal {
|
||||
|
||||
// =========================================================================
|
||||
|
||||
// Initial register values for registers 0x05..0x1f (27 registers). Written
|
||||
// verbatim at init; the shadow cache then tracks read-modify-writes.
|
||||
static const uint8_t kInitArray[R820T_NUM_REGS] = {
|
||||
0x83, 0x32, 0x75, // 0x05 - 0x07
|
||||
0xc0, 0x40, 0xd6, 0x6c, // 0x08 - 0x0b
|
||||
0xf5, 0x63, 0x75, 0x68, // 0x0c - 0x0f
|
||||
0x6c, 0x83, 0x80, 0x00, // 0x10 - 0x13
|
||||
0x0f, 0x00, 0xc0, 0x30, // 0x14 - 0x17
|
||||
0x48, 0xcc, 0x60, 0x00, // 0x18 - 0x1b
|
||||
0x54, 0xae, 0x4a, 0xc0 // 0x1c - 0x1f
|
||||
};
|
||||
|
||||
// RF tracking-filter / mux band selection, keyed by LO frequency in MHz.
|
||||
struct FreqRange {
|
||||
uint32_t freqMhz;
|
||||
uint8_t openD; // reg 0x17 bit 3 (open drain)
|
||||
uint8_t rfMuxPoly; // reg 0x1a bits (RF mux + poly)
|
||||
uint8_t tfC; // reg 0x1b (tracking filter band)
|
||||
};
|
||||
|
||||
static const FreqRange kFreqRanges[] = {
|
||||
{ 0, 0x08, 0x02, 0xdf },
|
||||
{ 50, 0x08, 0x02, 0xbe },
|
||||
{ 55, 0x08, 0x02, 0x8b },
|
||||
{ 60, 0x08, 0x02, 0x7b },
|
||||
{ 65, 0x08, 0x02, 0x69 },
|
||||
{ 70, 0x08, 0x02, 0x58 },
|
||||
{ 75, 0x00, 0x02, 0x44 },
|
||||
{ 80, 0x00, 0x02, 0x44 },
|
||||
{ 90, 0x00, 0x02, 0x34 },
|
||||
{ 100, 0x00, 0x02, 0x34 },
|
||||
{ 110, 0x00, 0x02, 0x24 },
|
||||
{ 120, 0x00, 0x02, 0x24 },
|
||||
{ 140, 0x00, 0x02, 0x14 },
|
||||
{ 180, 0x00, 0x02, 0x13 },
|
||||
{ 220, 0x00, 0x02, 0x13 },
|
||||
{ 250, 0x00, 0x02, 0x11 },
|
||||
{ 280, 0x00, 0x02, 0x00 },
|
||||
{ 310, 0x00, 0x41, 0x00 },
|
||||
{ 450, 0x00, 0x41, 0x00 },
|
||||
{ 588, 0x00, 0x40, 0x00 },
|
||||
{ 650, 0x00, 0x40, 0x00 },
|
||||
};
|
||||
|
||||
// Composite gain steps (tenths of dB) reachable by walking the LNA + mixer
|
||||
// gain stages together. Advertised to userspace as the discrete gain set.
|
||||
static const int kGains[] = {
|
||||
0, 9, 14, 27, 37, 77, 87, 125, 144, 157,
|
||||
166, 197, 207, 229, 254, 280, 297, 328,
|
||||
338, 364, 372, 386, 402, 421, 434, 439,
|
||||
445, 480, 496,
|
||||
};
|
||||
|
||||
// Per-index increments of the LNA and mixer gain stages (tenths of dB).
|
||||
static const int kLnaGainSteps[] = { 0, 9, 13, 40, 38, 13, 31, 22,
|
||||
26, 31, 26, 14, 19, 5, 35, 13 };
|
||||
static const int kMixerGainSteps[] = { 0, 5, 10, 10, 19, 9, 10, 25,
|
||||
17, 10, 8, 16, 13, 6, 3, -8 };
|
||||
|
||||
// =========================================================================
|
||||
// Low-level register access (through the demod's I2C repeater)
|
||||
// =========================================================================
|
||||
|
||||
static uint8_t BitRev(uint8_t b) {
|
||||
static const uint8_t lut[16] = {
|
||||
0x0, 0x8, 0x4, 0xc, 0x2, 0xa, 0x6, 0xe,
|
||||
0x1, 0x9, 0x5, 0xd, 0x3, 0xb, 0x7, 0xf,
|
||||
};
|
||||
return (uint8_t)((lut[b & 0xf] << 4) | lut[b >> 4]);
|
||||
}
|
||||
|
||||
// Write `len` register values starting at `reg`. The RTL2832 I2C path is
|
||||
// chunked to 7 values per transfer (8-byte message incl. the start reg).
|
||||
static bool Write(R820tDev& d, uint8_t reg, const uint8_t* val, uint8_t len) {
|
||||
uint8_t buf[8];
|
||||
uint8_t pos = 0;
|
||||
while (len > 0) {
|
||||
uint8_t size = len > 7 ? 7 : len;
|
||||
buf[0] = reg;
|
||||
for (uint8_t i = 0; i < size; i++) buf[1 + i] = val[pos + i];
|
||||
if (!RtlI2cWrite(d.owner, R820T_I2C_ADDR, buf, (uint8_t)(size + 1)))
|
||||
return false;
|
||||
for (uint8_t i = 0; i < size; i++)
|
||||
if ((reg + i) < 32) d.regs[reg + i] = val[pos + i];
|
||||
pos += size;
|
||||
reg = (uint8_t)(reg + size);
|
||||
len = (uint8_t)(len - size);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool WriteReg(R820tDev& d, uint8_t reg, uint8_t val) {
|
||||
return Write(d, reg, &val, 1);
|
||||
}
|
||||
|
||||
// Read-modify-write using the register shadow as the source of truth for the
|
||||
// bits outside `mask`.
|
||||
static bool WriteRegMask(R820tDev& d, uint8_t reg, uint8_t val, uint8_t mask) {
|
||||
uint8_t cur = (reg < 32) ? d.regs[reg] : 0;
|
||||
uint8_t merged = (uint8_t)((cur & ~mask) | (val & mask));
|
||||
return WriteReg(d, reg, merged);
|
||||
}
|
||||
|
||||
// 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.owner, R820T_I2C_ADDR, &ptr, 1)) return false;
|
||||
if (!RtlI2cRead(d.owner, R820T_I2C_ADDR, raw, len)) return false;
|
||||
for (uint8_t i = 0; i < len; i++) out[i] = BitRev(raw[i]);
|
||||
return true;
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// 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(rtlsdr_device* owner) {
|
||||
// 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
|
||||
// to the raw R820T id. (The bit-reversal only applies to the status
|
||||
// registers read during tuning, not to this id check.)
|
||||
uint8_t ptr = 0x00;
|
||||
RtlI2cWrite(owner, R820T_I2C_ADDR, &ptr, 1);
|
||||
|
||||
uint8_t raw[1] = {0};
|
||||
if (!RtlI2cRead(owner, R820T_I2C_ADDR, raw, 1)) {
|
||||
KernelLogStream(WARNING, "R820T") << "id read failed (I2C transfer error)";
|
||||
return false;
|
||||
}
|
||||
KernelLogStream(INFO, "R820T") << "chip id = 0x" << base::hex
|
||||
<< (uint64_t)raw[0] << base::dec
|
||||
<< (raw[0] == R820T_CHECK_VAL ? " (R820T/R820T2)" : " (unrecognised)");
|
||||
return raw[0] == R820T_CHECK_VAL;
|
||||
}
|
||||
|
||||
bool R820tInit(R820tDev& d, rtlsdr_device* owner, uint32_t xtal, uint32_t intFreq) {
|
||||
d.owner = owner;
|
||||
d.xtal = xtal;
|
||||
d.intFreq = intFreq;
|
||||
d.hasLock = false;
|
||||
d.inited = false;
|
||||
|
||||
// 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="
|
||||
<< (uint64_t)xtal << " IF=" << (uint64_t)intFreq << ")";
|
||||
return true;
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// RF mux / tracking filter band
|
||||
// =========================================================================
|
||||
|
||||
static bool SetMux(R820tDev& d, uint64_t loHz) {
|
||||
uint32_t loMhz = (uint32_t)(loHz / 1000000);
|
||||
|
||||
unsigned idx = 0;
|
||||
const unsigned n = sizeof(kFreqRanges) / sizeof(kFreqRanges[0]);
|
||||
for (; idx < n - 1; idx++) {
|
||||
if (loMhz < kFreqRanges[idx + 1].freqMhz) break;
|
||||
}
|
||||
const FreqRange& r = kFreqRanges[idx];
|
||||
|
||||
bool ok = true;
|
||||
ok &= WriteRegMask(d, 0x17, r.openD, 0x08); // open drain
|
||||
ok &= WriteRegMask(d, 0x1a, r.rfMuxPoly, 0xc3); // RF mux + poly
|
||||
ok &= WriteReg(d, 0x1b, r.tfC); // tracking-filter band
|
||||
// Default XTAL cap (high-cap-0p selection) and the unused LNA/mixer
|
||||
// top registers.
|
||||
ok &= WriteRegMask(d, 0x10, 0x00, 0x0b);
|
||||
ok &= WriteRegMask(d, 0x08, 0x00, 0x3f);
|
||||
ok &= WriteRegMask(d, 0x09, 0x00, 0x3f);
|
||||
return ok;
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// PLL / VCO frequency synthesis
|
||||
// =========================================================================
|
||||
|
||||
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 freqKhz = (uint32_t)((freqHz + 500) / 1000);
|
||||
|
||||
bool ok = true;
|
||||
ok &= WriteRegMask(d, 0x10, 0x00, 0x10); // refdiv = /1
|
||||
ok &= WriteRegMask(d, 0x1a, 0x00, 0x0c); // pll autotune 128 kHz
|
||||
ok &= WriteRegMask(d, 0x12, 0x80, 0xe0); // VCO current = 100
|
||||
|
||||
// Pick the smallest mixer divider that lands the VCO in range.
|
||||
uint8_t mixDiv = 2;
|
||||
uint8_t divNum = 0;
|
||||
while (mixDiv <= 64) {
|
||||
if ((uint64_t)freqKhz * mixDiv >= vcoMinKhz &&
|
||||
(uint64_t)freqKhz * mixDiv < vcoMaxKhz) {
|
||||
uint8_t divBuf = mixDiv;
|
||||
while (divBuf > 2) { divBuf >>= 1; divNum++; }
|
||||
break;
|
||||
}
|
||||
mixDiv <<= 1;
|
||||
}
|
||||
|
||||
// VCO fine-tune feedback adjusts the divider selection.
|
||||
uint8_t data[5] = {0};
|
||||
if (!Read(d, data, sizeof(data))) return false;
|
||||
uint8_t vcoPowerRef = 2;
|
||||
uint8_t vcoFineTune = (uint8_t)((data[4] & 0x30) >> 4);
|
||||
if (vcoFineTune > vcoPowerRef && divNum > 0) divNum--;
|
||||
else if (vcoFineTune < vcoPowerRef) divNum++;
|
||||
ok &= WriteRegMask(d, 0x10, (uint8_t)(divNum << 5), 0xe0);
|
||||
|
||||
uint64_t vcoFreq = freqHz * (uint64_t)mixDiv;
|
||||
|
||||
// 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 (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));
|
||||
|
||||
// Confirm lock; bump the VCO current once if the first read is not
|
||||
// locked.
|
||||
d.hasLock = false;
|
||||
uint8_t lk[3] = {0};
|
||||
for (int i = 0; i < 2; i++) {
|
||||
if (!Read(d, lk, sizeof(lk))) {
|
||||
KernelLogStream(WARNING, "R820T") << "PLL status read failed";
|
||||
return false;
|
||||
}
|
||||
if (lk[2] & 0x40) { d.hasLock = true; break; }
|
||||
if (i == 0) WriteRegMask(d, 0x12, 0x60, 0xe0); // raise VCO current
|
||||
}
|
||||
|
||||
KernelLogStream(INFO, "R820T") << "PLL nint=" << (uint64_t)nint
|
||||
<< " sdm=0x" << base::hex << (uint64_t)sdm
|
||||
<< " mixDiv=" << base::dec << (uint64_t)mixDiv
|
||||
<< " status=0x" << base::hex << (uint64_t)lk[2] << base::dec
|
||||
<< (d.hasLock ? " LOCKED" : " UNLOCKED");
|
||||
|
||||
ok &= WriteRegMask(d, 0x1a, 0x08, 0x08); // pll autotune 8 kHz
|
||||
return ok && d.hasLock;
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Public tuner control
|
||||
// =========================================================================
|
||||
|
||||
bool R820tSetFreq(R820tDev& d, uint64_t rfHz) {
|
||||
if (!d.inited) {
|
||||
KernelLogStream(WARNING, "R820T") << "SetFreq: tuner not inited";
|
||||
return false;
|
||||
}
|
||||
uint64_t loHz = rfHz + d.intFreq;
|
||||
|
||||
if (!SetMux(d, loHz)) {
|
||||
KernelLogStream(WARNING, "R820T") << "SetFreq: mux write failed";
|
||||
return false;
|
||||
}
|
||||
if (!SetPll(d, loHz)) {
|
||||
KernelLogStream(WARNING, "R820T") << "PLL not locked at "
|
||||
<< (uint64_t)(rfHz / 1000) << " kHz";
|
||||
return false;
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
bool R820tSetGain(R820tDev& d, int manual, int tenthsDb) {
|
||||
if (!d.inited) {
|
||||
KernelLogStream(WARNING, "R820T") << "SetGain: tuner not inited";
|
||||
return false;
|
||||
}
|
||||
bool ok = true;
|
||||
|
||||
if (manual) {
|
||||
// LNA + mixer to manual; VGA to a fixed mid value.
|
||||
ok &= WriteRegMask(d, 0x05, 0x10, 0x10); // LNA AGC off
|
||||
ok &= WriteRegMask(d, 0x07, 0x00, 0x10); // mixer AGC off
|
||||
ok &= WriteRegMask(d, 0x0c, 0x08, 0x9f); // VGA = 16.3 dB
|
||||
|
||||
int total = 0;
|
||||
uint8_t lnaIndex = 0, mixIndex = 0;
|
||||
for (int i = 0; i < 15; i++) {
|
||||
if (total >= tenthsDb) break;
|
||||
total += kLnaGainSteps[++lnaIndex];
|
||||
if (total >= tenthsDb) break;
|
||||
total += kMixerGainSteps[++mixIndex];
|
||||
}
|
||||
ok &= WriteRegMask(d, 0x05, lnaIndex, 0x0f);
|
||||
ok &= WriteRegMask(d, 0x07, mixIndex, 0x0f);
|
||||
} else {
|
||||
ok &= WriteRegMask(d, 0x05, 0x00, 0x10); // LNA AGC on
|
||||
ok &= WriteRegMask(d, 0x07, 0x10, 0x10); // mixer AGC on
|
||||
ok &= WriteRegMask(d, 0x0c, 0x0b, 0x9f); // VGA = 26.5 dB
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
||||
void R820tStandby(R820tDev& d) {
|
||||
if (!d.inited) return;
|
||||
// Documented R820T standby register values (mute + power-down).
|
||||
WriteReg(d, 0x06, 0xb1);
|
||||
WriteReg(d, 0x05, 0xa0);
|
||||
WriteReg(d, 0x07, 0x3a);
|
||||
WriteReg(d, 0x08, 0x40);
|
||||
WriteReg(d, 0x09, 0xc0);
|
||||
WriteReg(d, 0x0a, 0x36);
|
||||
WriteReg(d, 0x0c, 0x35);
|
||||
WriteReg(d, 0x0f, 0x68);
|
||||
WriteReg(d, 0x11, 0x03);
|
||||
WriteReg(d, 0x17, 0xf4);
|
||||
WriteReg(d, 0x19, 0x0c);
|
||||
}
|
||||
|
||||
const int* R820tGainTable(int* count) {
|
||||
if (count) *count = (int)(sizeof(kGains) / sizeof(kGains[0]));
|
||||
return kGains;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
|
||||
struct rtlsdr_device;
|
||||
|
||||
namespace rtlsdr_internal {
|
||||
static constexpr uint8_t R820T_I2C_ADDR = 0x34;
|
||||
static constexpr uint8_t R820T_CHECK_VAL = 0x69;
|
||||
static constexpr uint8_t R820T_NUM_REGS = 27;
|
||||
|
||||
struct R820tDev {
|
||||
rtlsdr_device* owner;
|
||||
uint32_t xtal;
|
||||
uint32_t intFreq;
|
||||
uint8_t regs[32];
|
||||
bool hasLock;
|
||||
bool inited;
|
||||
};
|
||||
|
||||
bool RtlI2cWrite(rtlsdr_device* owner, uint8_t addr, const uint8_t* data, uint8_t len);
|
||||
bool RtlI2cRead(rtlsdr_device* owner, uint8_t addr, uint8_t* data, uint8_t len);
|
||||
bool R820tDetect(rtlsdr_device* owner);
|
||||
bool R820tInit(R820tDev& dev, rtlsdr_device* owner, uint32_t xtal, uint32_t intFreq);
|
||||
bool R820tSetFreq(R820tDev& dev, uint64_t hz);
|
||||
bool R820tSetGain(R820tDev& dev, int manual, int tenthsDb);
|
||||
void R820tStandby(R820tDev& dev);
|
||||
const int* R820tGainTable(int* count);
|
||||
}
|
||||
@@ -0,0 +1,491 @@
|
||||
#include <rtlsdr/rtlsdr.h>
|
||||
#include <montauk/syscall.h>
|
||||
#include <libc/string.h>
|
||||
#include "r820t.hpp"
|
||||
|
||||
struct rtlsdr_device { int marker; };
|
||||
|
||||
namespace rtlsdr_internal {
|
||||
namespace {
|
||||
struct LogSink { template<class T> LogSink& operator<<(const T&) { return *this; } };
|
||||
namespace base { static constexpr int hex=0; static constexpr int dec=0; }
|
||||
}
|
||||
#define KernelLogStream(level, tag) LogSink{}
|
||||
#define WARNING 0
|
||||
#define INFO 0
|
||||
#define ERROR 0
|
||||
#define OK 0
|
||||
|
||||
static rtlsdr_device g_storage{};
|
||||
static rtlsdr_device* g_openDev = nullptr;
|
||||
static int g_usbHandle = -1;
|
||||
static bool g_hwInited = false;
|
||||
static bool g_streaming = false;
|
||||
static uint8_t g_ctlStorage[4096]{};
|
||||
static uint8_t* g_ctlBuf = g_ctlStorage;
|
||||
static R820tDev g_tuner{};
|
||||
static uint32_t g_rtlXtal = 28800000;
|
||||
static int g_ppm = 0;
|
||||
static int g_manual = 0;
|
||||
static int g_gain = 0;
|
||||
static int g_directSamp = 0;
|
||||
static uint64_t g_lastFreq = 0;
|
||||
static uint32_t g_sampleRate = 0;
|
||||
|
||||
namespace UsbCompat {
|
||||
static constexpr uint32_t CC_SUCCESS = 1;
|
||||
static uint32_t ControlTransfer(rtlsdr_device*, uint8_t requestType,
|
||||
uint8_t request, uint16_t value, uint16_t index, uint16_t length,
|
||||
uint8_t* data, bool) {
|
||||
montauk::abi::UsbControlRequest setup{requestType, request, value, index, length};
|
||||
return montauk::usb_control(g_usbHandle, &setup, data, length) == 0 ? CC_SUCCESS : 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// =========================================================================
|
||||
// Constants
|
||||
// =========================================================================
|
||||
|
||||
// Vendor control-transfer request types (vendor, host<->device).
|
||||
static constexpr uint8_t CTRL_OUT = 0x40; // host-to-device, vendor
|
||||
static constexpr uint8_t CTRL_IN = 0xC0; // device-to-host, vendor
|
||||
|
||||
// RTL2832U register blocks (high byte of wIndex; OR 0x10 to write).
|
||||
static constexpr uint8_t BLOCK_USB = 1;
|
||||
static constexpr uint8_t BLOCK_SYS = 2;
|
||||
static constexpr uint8_t BLOCK_IIC = 6;
|
||||
|
||||
// USB / system register addresses.
|
||||
static constexpr uint16_t USB_EPA_CTL = 0x2148;
|
||||
static constexpr uint16_t USB_EPA_MAXPKT = 0x2158;
|
||||
static constexpr uint16_t USB_SYSCTL = 0x2000;
|
||||
static constexpr uint16_t SYS_DEMOD_CTL = 0x3000;
|
||||
static constexpr uint16_t SYS_DEMOD_CTL1 = 0x300b;
|
||||
static constexpr uint32_t RTL_XTAL = 28800000;
|
||||
static constexpr uint32_t R82XX_IF = 3570000;
|
||||
static constexpr uint32_t TWO_POW22 = 1u << 22;
|
||||
|
||||
static bool RegWrite(uint8_t block, uint16_t addr, uint16_t val, uint8_t len) {
|
||||
if (!g_ctlBuf) return false;
|
||||
g_ctlBuf[0] = (len == 1) ? (uint8_t)(val & 0xff) : (uint8_t)(val >> 8);
|
||||
g_ctlBuf[1] = (uint8_t)(val & 0xff);
|
||||
uint16_t index = (uint16_t)((block << 8) | 0x10);
|
||||
return UsbCompat::ControlTransfer(g_openDev, CTRL_OUT, 0, addr, index, len,
|
||||
g_ctlBuf, false) == UsbCompat::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;
|
||||
UsbCompat::ControlTransfer(g_openDev, 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);
|
||||
bool ok = UsbCompat::ControlTransfer(g_openDev, CTRL_OUT, 0, waddr, index, len,
|
||||
g_ctlBuf, false) == UsbCompat::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) {
|
||||
DemodWrite(1, 0x01, on ? 0x18 : 0x10, 1);
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// I2C facade for the tuner module
|
||||
// =========================================================================
|
||||
|
||||
bool RtlI2cWrite(rtlsdr_device* owner, uint8_t i2cAddr, const uint8_t* buf, uint8_t len) {
|
||||
if (!g_ctlBuf || len == 0 || len > 64) return false;
|
||||
memcpy(g_ctlBuf, buf, len);
|
||||
uint16_t index = (uint16_t)((BLOCK_IIC << 8) | 0x10);
|
||||
uint32_t cc = UsbCompat::ControlTransfer(owner, CTRL_OUT, 0, i2cAddr, index, len,
|
||||
g_ctlBuf, false);
|
||||
if (cc != UsbCompat::CC_SUCCESS)
|
||||
KernelLogStream(WARNING, "RTL-SDR") << "I2C write cc=" << (uint64_t)cc
|
||||
<< " reg=0x" << base::hex << (uint64_t)buf[0]
|
||||
<< " len=" << base::dec << (uint64_t)len;
|
||||
return cc == UsbCompat::CC_SUCCESS;
|
||||
}
|
||||
|
||||
bool RtlI2cRead(rtlsdr_device* owner, uint8_t i2cAddr, uint8_t* buf, uint8_t len) {
|
||||
if (!g_ctlBuf || len == 0 || len > 64) return false;
|
||||
uint16_t index = (uint16_t)(BLOCK_IIC << 8);
|
||||
uint32_t cc = UsbCompat::ControlTransfer(owner, CTRL_IN, 0, i2cAddr, index, len,
|
||||
g_ctlBuf, true);
|
||||
if (cc != UsbCompat::CC_SUCCESS) {
|
||||
KernelLogStream(WARNING, "RTL-SDR") << "I2C read cc=" << (uint64_t)cc
|
||||
<< " len=" << (uint64_t)len;
|
||||
return false;
|
||||
}
|
||||
memcpy(buf, g_ctlBuf, len);
|
||||
return true;
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Demodulator bring-up
|
||||
// =========================================================================
|
||||
|
||||
// The 16-tap default FIR (8x int8 then 8x int12) used for the SDR/FM path.
|
||||
static void SetFir() {
|
||||
static const int fir[16] = {
|
||||
-54, -36, -41, -40, -32, -14, 14, 53,
|
||||
101, 156, 215, 273, 327, 372, 404, 421,
|
||||
};
|
||||
uint8_t buf[20];
|
||||
for (int i = 0; i < 8; i++) buf[i] = (uint8_t)(fir[i] & 0xff);
|
||||
for (int i = 0; i < 8; i += 2) {
|
||||
int v0 = fir[8 + i];
|
||||
int v1 = fir[8 + i + 1];
|
||||
buf[8 + i * 3 / 2] = (uint8_t)((v0 >> 4) & 0xff);
|
||||
buf[8 + i * 3 / 2 + 1] = (uint8_t)(((v0 << 4) | ((v1 >> 8) & 0x0f)) & 0xff);
|
||||
buf[8 + i * 3 / 2 + 2] = (uint8_t)(v1 & 0xff);
|
||||
}
|
||||
for (int i = 0; i < 20; i++) DemodWrite(1, (uint16_t)(0x1c + i), buf[i], 1);
|
||||
}
|
||||
|
||||
static bool BasebandInit() {
|
||||
// USB FIFO / endpoint A setup.
|
||||
RegWrite(BLOCK_USB, USB_SYSCTL, 0x09, 1);
|
||||
RegWrite(BLOCK_USB, USB_EPA_MAXPKT, 0x0002, 2);
|
||||
RegWrite(BLOCK_USB, USB_EPA_CTL, 0x1002, 2);
|
||||
|
||||
// Power on the demod.
|
||||
RegWrite(BLOCK_SYS, SYS_DEMOD_CTL1, 0x22, 1);
|
||||
RegWrite(BLOCK_SYS, SYS_DEMOD_CTL, 0xe8, 1);
|
||||
|
||||
// Soft-reset the demod state machine.
|
||||
DemodWrite(1, 0x01, 0x14, 1);
|
||||
DemodWrite(1, 0x01, 0x10, 1);
|
||||
|
||||
// Disable spectrum inversion + clear DDC shift / IF registers.
|
||||
DemodWrite(1, 0x15, 0x00, 1);
|
||||
DemodWrite(1, 0x16, 0x0000, 2);
|
||||
for (int i = 0; i < 6; i++) DemodWrite(1, (uint16_t)(0x16 + i), 0x00, 1);
|
||||
|
||||
SetFir();
|
||||
|
||||
DemodWrite(0, 0x19, 0x05, 1); // enable SDR mode, disable DAGC
|
||||
DemodWrite(1, 0x93, 0xf0, 1);
|
||||
DemodWrite(1, 0x94, 0x0f, 1);
|
||||
DemodWrite(1, 0x11, 0x00, 1); // disable AGC loop
|
||||
DemodWrite(1, 0x04, 0x00, 1);
|
||||
DemodWrite(0, 0x61, 0x60, 1); // disable PID filter
|
||||
DemodWrite(0, 0x06, 0x80, 1); // default ADC I/Q datapath
|
||||
DemodWrite(1, 0xb1, 0x1b, 1); // zero-IF + DC cancel + IQ comp/est
|
||||
DemodWrite(0, 0x0d, 0x83, 1); // disable clock output on TP_CK0
|
||||
return true;
|
||||
}
|
||||
|
||||
// Set the digital downconversion IF frequency the demod searches at.
|
||||
static void SetIfFreq(uint32_t freq) {
|
||||
int32_t ifv = (int32_t)(-(int64_t)((uint64_t)freq * TWO_POW22 / g_rtlXtal));
|
||||
DemodWrite(1, 0x19, (uint16_t)((ifv >> 16) & 0x3f), 1);
|
||||
DemodWrite(1, 0x1a, (uint16_t)((ifv >> 8) & 0xff), 1);
|
||||
DemodWrite(1, 0x1b, (uint16_t)(ifv & 0xff), 1);
|
||||
}
|
||||
|
||||
static void ApplySampleFreqCorrection() {
|
||||
int32_t offs = (int32_t)(-(int64_t)g_ppm * (1 << 24) / 1000000);
|
||||
DemodWrite(1, 0x3f, (uint16_t)(offs & 0xff), 1);
|
||||
DemodWrite(1, 0x3e, (uint16_t)((offs >> 8) & 0x3f), 1);
|
||||
}
|
||||
|
||||
static bool TunerInit() {
|
||||
SetI2cRepeater(true);
|
||||
// Retry detection a few times: an I2C read can transiently come back
|
||||
// wrong if it raced another core's USB activity around bring-up.
|
||||
bool detected = false;
|
||||
for (int attempt = 0; attempt < 4 && !detected; attempt++)
|
||||
detected = R820tDetect(g_openDev);
|
||||
bool ok = detected && R820tInit(g_tuner, g_openDev, g_rtlXtal, R82XX_IF);
|
||||
SetI2cRepeater(false);
|
||||
if (!detected) {
|
||||
KernelLogStream(WARNING, "RTL-SDR") << "no R820T2 tuner found on I2C";
|
||||
return false;
|
||||
}
|
||||
if (!ok) return false;
|
||||
|
||||
// Demod path for the R820T2 low-IF tuner.
|
||||
DemodWrite(1, 0xb1, 0x1a, 1); // disable zero-IF mode
|
||||
DemodWrite(0, 0x08, 0x4d, 1); // enable In-phase ADC input only
|
||||
SetIfFreq(R82XX_IF);
|
||||
DemodWrite(1, 0x15, 0x01, 1); // enable spectrum inversion
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool EnsureInit() {
|
||||
if (g_hwInited) return true;
|
||||
if (!g_openDev || !g_ctlBuf) return false;
|
||||
if (!BasebandInit()) return false;
|
||||
if (!TunerInit()) return false;
|
||||
g_hwInited = true;
|
||||
KernelLogStream(OK, "RTL-SDR") << "Demod + tuner brought up on slot "
|
||||
<< (uint64_t)g_openDev;
|
||||
return true;
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Tuning / configuration (each holds g_ctlLock via the op wrappers)
|
||||
// =========================================================================
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
static int DoSetSampleRate(uint32_t rate) {
|
||||
if (!EnsureInit()) return -1;
|
||||
// The RTL2832 resampler does not cover 300k..900k.
|
||||
if (rate <= 225000 || rate > 3200000 ||
|
||||
(rate > 300000 && rate <= 900000)) return -1;
|
||||
|
||||
// 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);
|
||||
|
||||
ApplySampleFreqCorrection();
|
||||
DemodWrite(1, 0x01, 0x14, 1); // soft reset
|
||||
DemodWrite(1, 0x01, 0x10, 1);
|
||||
SetIfFreq(g_directSamp ? (uint32_t)g_lastFreq : R82XX_IF);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int DoSetGainMode(int manual) {
|
||||
if (!EnsureInit()) return -1;
|
||||
g_manual = manual ? 1 : 0;
|
||||
SetI2cRepeater(true);
|
||||
bool ok = R820tSetGain(g_tuner, g_manual, g_gain);
|
||||
SetI2cRepeater(false);
|
||||
return ok ? 0 : -1;
|
||||
}
|
||||
|
||||
static int DoSetGain(int tenths) {
|
||||
if (!EnsureInit()) return -1;
|
||||
g_gain = tenths;
|
||||
g_manual = 1; // selecting an explicit gain implies manual mode
|
||||
SetI2cRepeater(true);
|
||||
bool ok = R820tSetGain(g_tuner, 1, g_gain);
|
||||
SetI2cRepeater(false);
|
||||
return ok ? 0 : -1;
|
||||
}
|
||||
|
||||
static int DoSetFreqCorrection(int ppm) {
|
||||
if (!EnsureInit()) return -1;
|
||||
g_ppm = ppm;
|
||||
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;
|
||||
}
|
||||
|
||||
static int DoSetAgc(int on) {
|
||||
if (!EnsureInit()) return -1;
|
||||
return DemodWrite(0, 0x19, on ? 0x25 : 0x05, 1) ? 0 : -1;
|
||||
}
|
||||
|
||||
static int DoSetDirectSampling(int mode) {
|
||||
if (!EnsureInit()) return -1;
|
||||
if (mode) {
|
||||
// Bypass the tuner and digitise the ADC input directly.
|
||||
SetI2cRepeater(true);
|
||||
R820tStandby(g_tuner);
|
||||
SetI2cRepeater(false);
|
||||
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
|
||||
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. Standby powered the
|
||||
// tuner down, so it needs a full re-initialisation.
|
||||
SetI2cRepeater(true);
|
||||
bool ok = R820tInit(g_tuner, g_openDev, g_rtlXtal, R82XX_IF);
|
||||
SetI2cRepeater(false);
|
||||
if (!ok) return -1;
|
||||
SetIfFreq(R82XX_IF);
|
||||
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;
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Streaming
|
||||
// =========================================================================
|
||||
|
||||
|
||||
static bool IsSupported(uint16_t vid, uint16_t pid) {
|
||||
return vid == 0x0bda && (pid == 0x2832 || pid == 0x2838);
|
||||
}
|
||||
|
||||
static int FindInterface(int wanted, montauk::abi::UsbInterfaceInfo* out) {
|
||||
montauk::abi::UsbInterfaceInfo interfaces[32]{};
|
||||
int count = montauk::usb_list(interfaces, 32);
|
||||
int matched = 0;
|
||||
for (int i = 0; i < count; ++i) {
|
||||
if (!IsSupported(interfaces[i].vendorId, interfaces[i].productId)) continue;
|
||||
if (matched++ == wanted) { if (out) *out = interfaces[i]; return 0; }
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void CopyText(char* dst, uint32_t cap, const char* src) {
|
||||
uint32_t i = 0;
|
||||
while (i + 1 < cap && src[i]) { dst[i] = src[i]; ++i; }
|
||||
dst[i] = 0;
|
||||
}
|
||||
|
||||
static void FillInfo(rtlsdr_device_info* out) {
|
||||
*out = {};
|
||||
CopyText(out->name, sizeof(out->name), "Realtek RTL2832U");
|
||||
CopyText(out->tuner, sizeof(out->tuner), "Rafael Micro R820T2");
|
||||
CopyText(out->serial, sizeof(out->serial), "USB RTL-SDR");
|
||||
out->freq_min = 24000000ull;
|
||||
out->freq_max = 1766000000ull;
|
||||
out->sample_rate_min = 225001;
|
||||
out->sample_rate_max = 3200000;
|
||||
int n = 0;
|
||||
const int* gains = R820tGainTable(&n);
|
||||
out->num_gains = n < 32 ? (uint32_t)n : 32;
|
||||
for (uint32_t i = 0; i < out->num_gains; ++i) out->gains[i] = gains[i];
|
||||
}
|
||||
|
||||
} // namespace rtlsdr_internal
|
||||
|
||||
#define RTLSDR_EXPORT extern "C" __attribute__((visibility("default")))
|
||||
|
||||
RTLSDR_EXPORT int rtlsdr_count(void) {
|
||||
montauk::abi::UsbInterfaceInfo info{};
|
||||
int n = 0;
|
||||
while (rtlsdr_internal::FindInterface(n, &info) == 0) ++n;
|
||||
return n;
|
||||
}
|
||||
|
||||
RTLSDR_EXPORT int rtlsdr_get_device_info(int index, rtlsdr_device_info* out) {
|
||||
if (!out || index < 0 || rtlsdr_internal::FindInterface(index, nullptr) != 0) return -1;
|
||||
rtlsdr_internal::FillInfo(out);
|
||||
return 0;
|
||||
}
|
||||
|
||||
RTLSDR_EXPORT int rtlsdr_open(rtlsdr_device** out, int index) {
|
||||
using namespace rtlsdr_internal;
|
||||
if (!out || index < 0 || g_openDev) return -1;
|
||||
montauk::abi::UsbInterfaceInfo info{};
|
||||
if (FindInterface(index, &info) != 0 || info.kernelDriverBound || info.claimed ||
|
||||
!info.bulkInEndpoint) return -1;
|
||||
int handle = montauk::usb_claim(info.slotId, info.interfaceNumber);
|
||||
if (handle < 0) return handle;
|
||||
g_usbHandle = handle;
|
||||
g_openDev = &g_storage;
|
||||
g_storage.marker = 0x52544c53;
|
||||
g_hwInited = false;
|
||||
g_streaming = false;
|
||||
g_rtlXtal = RTL_XTAL;
|
||||
g_ppm = g_manual = g_gain = g_directSamp = 0;
|
||||
g_lastFreq = 0;
|
||||
g_sampleRate = 0;
|
||||
g_tuner = {};
|
||||
*out = g_openDev;
|
||||
return 0;
|
||||
}
|
||||
|
||||
RTLSDR_EXPORT int rtlsdr_close(rtlsdr_device* dev) {
|
||||
using namespace rtlsdr_internal;
|
||||
if (!dev || dev != g_openDev) return -1;
|
||||
if (g_streaming) {
|
||||
g_streaming = false;
|
||||
montauk::usb_bulk_in_stop(g_usbHandle);
|
||||
RegWrite(BLOCK_USB, USB_EPA_CTL, 0x1002, 2);
|
||||
}
|
||||
int result = montauk::usb_close(g_usbHandle);
|
||||
g_usbHandle = -1;
|
||||
g_openDev = nullptr;
|
||||
g_hwInited = false;
|
||||
return result;
|
||||
}
|
||||
|
||||
RTLSDR_EXPORT int rtlsdr_set_center_freq(rtlsdr_device* dev, uint64_t hz) {
|
||||
return dev == rtlsdr_internal::g_openDev ? rtlsdr_internal::DoSetFreq(hz) : -1;
|
||||
}
|
||||
RTLSDR_EXPORT uint64_t rtlsdr_get_center_freq(const rtlsdr_device* dev) {
|
||||
return dev == rtlsdr_internal::g_openDev ? rtlsdr_internal::g_lastFreq : 0;
|
||||
}
|
||||
RTLSDR_EXPORT int rtlsdr_set_sample_rate(rtlsdr_device* dev, uint32_t hz) {
|
||||
if (dev != rtlsdr_internal::g_openDev) return -1;
|
||||
int result = rtlsdr_internal::DoSetSampleRate(hz);
|
||||
if (result == 0) rtlsdr_internal::g_sampleRate = hz;
|
||||
return result;
|
||||
}
|
||||
RTLSDR_EXPORT uint32_t rtlsdr_get_sample_rate(const rtlsdr_device* dev) {
|
||||
return dev == rtlsdr_internal::g_openDev ? rtlsdr_internal::g_sampleRate : 0;
|
||||
}
|
||||
RTLSDR_EXPORT int rtlsdr_set_tuner_gain_mode(rtlsdr_device* dev, int manual) {
|
||||
return dev == rtlsdr_internal::g_openDev ? rtlsdr_internal::DoSetGainMode(manual) : -1;
|
||||
}
|
||||
RTLSDR_EXPORT int rtlsdr_set_tuner_gain(rtlsdr_device* dev, int gain) {
|
||||
return dev == rtlsdr_internal::g_openDev ? rtlsdr_internal::DoSetGain(gain) : -1;
|
||||
}
|
||||
RTLSDR_EXPORT int rtlsdr_set_freq_correction(rtlsdr_device* dev, int ppm) {
|
||||
return dev == rtlsdr_internal::g_openDev ? rtlsdr_internal::DoSetFreqCorrection(ppm) : -1;
|
||||
}
|
||||
RTLSDR_EXPORT int rtlsdr_set_agc_mode(rtlsdr_device* dev, int on) {
|
||||
return dev == rtlsdr_internal::g_openDev ? rtlsdr_internal::DoSetAgc(on) : -1;
|
||||
}
|
||||
RTLSDR_EXPORT int rtlsdr_set_direct_sampling(rtlsdr_device* dev, int mode) {
|
||||
if (mode < 0 || mode > 2) return -1;
|
||||
return dev == rtlsdr_internal::g_openDev ? rtlsdr_internal::DoSetDirectSampling(mode) : -1;
|
||||
}
|
||||
RTLSDR_EXPORT int rtlsdr_start(rtlsdr_device* dev) {
|
||||
using namespace rtlsdr_internal;
|
||||
if (dev != g_openDev || !EnsureInit()) return -1;
|
||||
RegWrite(BLOCK_USB, USB_EPA_CTL, 0x1002, 2);
|
||||
RegWrite(BLOCK_USB, USB_EPA_CTL, 0x0000, 2);
|
||||
int result = montauk::usb_bulk_in_start(g_usbHandle, 4096, 16);
|
||||
if (result == 0) g_streaming = true;
|
||||
return result;
|
||||
}
|
||||
RTLSDR_EXPORT int rtlsdr_stop(rtlsdr_device* dev) {
|
||||
using namespace rtlsdr_internal;
|
||||
if (dev != g_openDev) return -1;
|
||||
g_streaming = false;
|
||||
int result = montauk::usb_bulk_in_stop(g_usbHandle);
|
||||
RegWrite(BLOCK_USB, USB_EPA_CTL, 0x1002, 2);
|
||||
return result;
|
||||
}
|
||||
RTLSDR_EXPORT int rtlsdr_read(rtlsdr_device* dev, void* data, uint32_t length) {
|
||||
using namespace rtlsdr_internal;
|
||||
if (dev != g_openDev || !g_streaming) return -1;
|
||||
return montauk::usb_bulk_in_read(g_usbHandle, data, length);
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
MAKEFLAGS += -rR
|
||||
.SUFFIXES:
|
||||
TOOLCHAIN_PREFIX := $(shell cd ../../.. && pwd)/toolchain/local/bin/x86_64-montauk-
|
||||
CXX := $(TOOLCHAIN_PREFIX)g++
|
||||
PROG_INC := ../../include
|
||||
LINK_LD := ../../link.ld
|
||||
OBJDIR := obj
|
||||
TARGET := ../../bin/os/sdr.elf
|
||||
LIBS := ../../lib/libloader/liblibloader.a ../../lib/libc/liblibc.a
|
||||
CXXFLAGS := -std=gnu++20 -g -O2 -pipe -Wall -Wextra -ffreestanding -fno-stack-protector -fno-stack-check -fno-rtti -fno-exceptions -ffunction-sections -fdata-sections -mno-80387 -mno-mmx -mno-sse -mno-sse2 -I $(PROG_INC) -isystem $(PROG_INC)/libc
|
||||
LDFLAGS := -nostdlib -Wl,--gc-sections -T $(LINK_LD)
|
||||
.PHONY: all clean
|
||||
all: $(TARGET)
|
||||
$(TARGET): $(OBJDIR)/main.o $(LIBS) $(LINK_LD) Makefile
|
||||
mkdir -p ../../bin/os
|
||||
$(CXX) $(CXXFLAGS) $(LDFLAGS) $(OBJDIR)/main.o $(LIBS) -o $@
|
||||
$(OBJDIR)/main.o: main.cpp Makefile
|
||||
mkdir -p $(OBJDIR)
|
||||
$(CXX) $(CXXFLAGS) -MMD -MP -c $< -o $@
|
||||
-include $(OBJDIR)/main.d
|
||||
clean:
|
||||
rm -rf $(OBJDIR) $(TARGET)
|
||||
+75
-30
@@ -2,7 +2,7 @@
|
||||
* main.cpp
|
||||
* sdr - software-defined radio receive demo.
|
||||
*
|
||||
* Exercises the generic SDR Rx API end to end: enumerate receivers, open one,
|
||||
* Exercises the userspace rtlsdr.lib driver end to end: enumerate receivers, open one,
|
||||
* tune it, configure sample rate / gain, stream raw I/Q for a short window,
|
||||
* and report basic signal statistics. The RTL-SDR (RTL2832U/R820T2) driver
|
||||
* provides the receiver; with no dongle attached the demo reports that and
|
||||
@@ -17,9 +17,49 @@
|
||||
|
||||
#include <montauk/syscall.h>
|
||||
#include <montauk/string.h>
|
||||
#include <rtlsdr/rtlsdr.h>
|
||||
#include <libloader/libloader.h>
|
||||
|
||||
using namespace montauk;
|
||||
|
||||
struct RtlApi {
|
||||
int (*count)();
|
||||
int (*get_info)(int, rtlsdr_device_info*);
|
||||
int (*open)(rtlsdr_device**, int);
|
||||
int (*close)(rtlsdr_device*);
|
||||
int (*set_freq)(rtlsdr_device*, uint64_t);
|
||||
uint64_t (*get_freq)(const rtlsdr_device*);
|
||||
int (*set_rate)(rtlsdr_device*, uint32_t);
|
||||
uint32_t (*get_rate)(const rtlsdr_device*);
|
||||
int (*set_gain_mode)(rtlsdr_device*, int);
|
||||
int (*set_ppm)(rtlsdr_device*, int);
|
||||
int (*start)(rtlsdr_device*);
|
||||
int (*stop)(rtlsdr_device*);
|
||||
int (*read)(rtlsdr_device*, void*, uint32_t);
|
||||
};
|
||||
static RtlApi g_rtl{};
|
||||
|
||||
static bool load_rtlsdr() {
|
||||
LibHandle* lib = libloader::dlopen("0:/os/rtlsdr.lib");
|
||||
if (!lib) return false;
|
||||
#define LOAD(field, symbol) g_rtl.field = reinterpret_cast<decltype(g_rtl.field)>(libloader::dlsym(lib, symbol)); if (!g_rtl.field) return false
|
||||
LOAD(count, "rtlsdr_count");
|
||||
LOAD(get_info, "rtlsdr_get_device_info");
|
||||
LOAD(open, "rtlsdr_open");
|
||||
LOAD(close, "rtlsdr_close");
|
||||
LOAD(set_freq, "rtlsdr_set_center_freq");
|
||||
LOAD(get_freq, "rtlsdr_get_center_freq");
|
||||
LOAD(set_rate, "rtlsdr_set_sample_rate");
|
||||
LOAD(get_rate, "rtlsdr_get_sample_rate");
|
||||
LOAD(set_gain_mode, "rtlsdr_set_tuner_gain_mode");
|
||||
LOAD(set_ppm, "rtlsdr_set_freq_correction");
|
||||
LOAD(start, "rtlsdr_start");
|
||||
LOAD(stop, "rtlsdr_stop");
|
||||
LOAD(read, "rtlsdr_read");
|
||||
#undef LOAD
|
||||
return true;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Small integer / formatting helpers (freestanding; no libc printf)
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -105,17 +145,17 @@ static bool next_token(const char** rest, char* tokbuf, int cap) {
|
||||
|
||||
static uint8_t g_iq[64 * 1024]; // I/Q read buffer (CU8)
|
||||
|
||||
static void print_receiver(int idx, const montauk::abi::SdrDeviceInfo& info) {
|
||||
static void print_receiver(int idx, const rtlsdr_device_info& info) {
|
||||
print(" ["); put_u64((uint64_t)idx); print("] ");
|
||||
print(info.name);
|
||||
print(" tuner="); print(info.tuner);
|
||||
if (info.serial[0]) { print(" ("); print(info.serial); print(")"); }
|
||||
print("\n freq ");
|
||||
put_mhz(info.freqMin); print(" - "); put_mhz(info.freqMax);
|
||||
print(" rate "); put_u64(info.sampleRateMin); print(" - ");
|
||||
put_u64(info.sampleRateMax); print(" Hz\n");
|
||||
print(" gains ("); put_u64(info.numGains); print(" steps):");
|
||||
uint32_t shown = info.numGains < 32 ? info.numGains : 32;
|
||||
put_mhz(info.freq_min); print(" - "); put_mhz(info.freq_max);
|
||||
print(" rate "); put_u64(info.sample_rate_min); print(" - ");
|
||||
put_u64(info.sample_rate_max); print(" Hz\n");
|
||||
print(" gains ("); put_u64(info.num_gains); print(" steps):");
|
||||
uint32_t shown = info.num_gains < 32 ? info.num_gains : 32;
|
||||
for (uint32_t g = 0; g < shown; g++) {
|
||||
putchar(' ');
|
||||
put_i64(info.gains[g] / 10);
|
||||
@@ -138,9 +178,13 @@ extern "C" void _start() {
|
||||
if (next_token(&rest, tok, sizeof(tok))) rateHz = (uint32_t)parse_u64(tok);
|
||||
|
||||
print("=== MontaukOS SDR receive demo ===\n\n");
|
||||
if (!load_rtlsdr()) {
|
||||
print("sdr: could not load 0:/os/rtlsdr.lib\n");
|
||||
montauk::exit(1);
|
||||
}
|
||||
|
||||
// --- Enumerate -------------------------------------------------------
|
||||
int count = montauk::sdr_count();
|
||||
int count = g_rtl.count();
|
||||
print("SDR receivers detected: "); put_u64((uint64_t)count); print("\n");
|
||||
if (count <= 0) {
|
||||
print("\nNo SDR receivers are connected.\n");
|
||||
@@ -148,49 +192,50 @@ extern "C" void _start() {
|
||||
montauk::exit(0);
|
||||
}
|
||||
|
||||
montauk::abi::SdrDeviceInfo info;
|
||||
rtlsdr_device_info info;
|
||||
for (int i = 0; i < count; i++) {
|
||||
if (montauk::sdr_info(i, &info) == 0) print_receiver(i, info);
|
||||
if (g_rtl.get_info(i, &info) == 0) print_receiver(i, info);
|
||||
}
|
||||
|
||||
// --- Open + configure receiver 0 ------------------------------------
|
||||
int idx = 0;
|
||||
if (montauk::sdr_info(idx, &info) != 0) {
|
||||
if (g_rtl.get_info(idx, &info) != 0) {
|
||||
print("\nsdr: failed to query receiver info\n");
|
||||
montauk::exit(1);
|
||||
}
|
||||
|
||||
int h = montauk::sdr_open(idx);
|
||||
if (h < 0) {
|
||||
rtlsdr_device* dev = nullptr;
|
||||
int open_result = g_rtl.open(&dev, idx);
|
||||
if (open_result < 0) {
|
||||
print("\nsdr: failed to open receiver 0\n");
|
||||
montauk::exit(1);
|
||||
}
|
||||
print("\nOpened receiver 0 (handle "); put_u64((uint64_t)h); print(")\n");
|
||||
print("\nOpened receiver 0\n");
|
||||
|
||||
// Clamp the requested tuning to the receiver's advertised limits.
|
||||
if (freqHz < info.freqMin) freqHz = info.freqMin;
|
||||
if (freqHz > info.freqMax) freqHz = info.freqMax;
|
||||
if (rateHz < info.sampleRateMin) rateHz = info.sampleRateMin;
|
||||
if (rateHz > info.sampleRateMax) rateHz = info.sampleRateMax;
|
||||
if (freqHz < info.freq_min) freqHz = info.freq_min;
|
||||
if (freqHz > info.freq_max) freqHz = info.freq_max;
|
||||
if (rateHz < info.sample_rate_min) rateHz = info.sample_rate_min;
|
||||
if (rateHz > info.sample_rate_max) rateHz = info.sample_rate_max;
|
||||
|
||||
print("Configuring: ");
|
||||
if (montauk::sdr_set_sample_rate(h, rateHz) != 0)
|
||||
if (g_rtl.set_rate(dev, rateHz) != 0)
|
||||
print("\n warning: sample rate rejected");
|
||||
if (montauk::sdr_set_freq_correction(h, 0) != 0) { /* optional */ }
|
||||
if (montauk::sdr_set_gain_mode(h, 0) != 0) // 0 = auto/AGC
|
||||
if (g_rtl.set_ppm(dev, 0) != 0) { /* optional */ }
|
||||
if (g_rtl.set_gain_mode(dev, 0) != 0) // 0 = auto/AGC
|
||||
print("\n warning: gain mode rejected");
|
||||
if (montauk::sdr_set_freq(h, freqHz) != 0)
|
||||
if (g_rtl.set_freq(dev, freqHz) != 0)
|
||||
print("\n warning: tune rejected (PLL may be unlocked)");
|
||||
|
||||
print("\n center : "); put_mhz(montauk::sdr_get_freq(h)); print("\n");
|
||||
print(" rate : "); put_msps(montauk::sdr_get_sample_rate(h));
|
||||
print(" ("); put_u64(montauk::sdr_get_sample_rate(h)); print(" Hz)\n");
|
||||
print("\n center : "); put_mhz(g_rtl.get_freq(dev)); print("\n");
|
||||
print(" rate : "); put_msps(g_rtl.get_rate(dev));
|
||||
print(" ("); put_u64(g_rtl.get_rate(dev)); print(" Hz)\n");
|
||||
print(" gain : auto (AGC)\n");
|
||||
|
||||
// --- Stream ----------------------------------------------------------
|
||||
if (montauk::sdr_start(h) != 0) {
|
||||
if (g_rtl.start(dev) != 0) {
|
||||
print("\nsdr: failed to start streaming\n");
|
||||
montauk::sdr_close(h);
|
||||
g_rtl.close(dev);
|
||||
montauk::exit(1);
|
||||
}
|
||||
print("\nStreaming I/Q for ~2s ...\n");
|
||||
@@ -203,7 +248,7 @@ extern "C" void _start() {
|
||||
uint64_t start = montauk::get_milliseconds();
|
||||
uint64_t lastReport = start;
|
||||
while (montauk::get_milliseconds() - start < 2000) {
|
||||
int n = montauk::sdr_read(h, g_iq, sizeof(g_iq));
|
||||
int n = g_rtl.read(dev, g_iq, sizeof(g_iq));
|
||||
if (n <= 0) { montauk::sleep_ms(20); continue; }
|
||||
|
||||
// CU8: interleaved 8-bit unsigned I/Q, 127.5 == zero.
|
||||
@@ -227,7 +272,7 @@ extern "C" void _start() {
|
||||
}
|
||||
}
|
||||
|
||||
montauk::sdr_stop(h);
|
||||
g_rtl.stop(dev);
|
||||
|
||||
// --- Report ----------------------------------------------------------
|
||||
uint64_t elapsed = montauk::get_milliseconds() - start;
|
||||
@@ -254,7 +299,7 @@ extern "C" void _start() {
|
||||
print(" peak |sample|: "); put_u64((uint64_t)peak); print(" / 128\n");
|
||||
}
|
||||
|
||||
montauk::sdr_close(h);
|
||||
g_rtl.close(dev);
|
||||
print("\nClosed receiver. Done.\n");
|
||||
montauk::exit(0);
|
||||
}
|
||||
Reference in new issue
Block a user