fix: move RTL-SDR to userspace

This commit is contained in:
daniel committed 2026-08-29 10:02:32 +02:00
1 parent b1f1cfe32b
commit 5cd5c2e6be
35 files changed
+1922 -1592

No files matched your search

+32
View File
@@ -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)
+445
View File
@@ -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;
}
}
+28
View File
@@ -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);
}
+491
View File
@@ -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);
}