feat: add RTL-SDR, R820t drivers, radio APIs, and sdr demo tool
This commit is contained in:
@@ -189,6 +189,29 @@ namespace montauk::abi {
|
||||
static constexpr uint64_t SYS_BTBONDS = 138;
|
||||
static constexpr uint64_t SYS_BTFORGET = 139;
|
||||
|
||||
/* Sdr.hpp -- software-defined radio receive API */
|
||||
static constexpr uint64_t SYS_SDR_COUNT = 140; // number of receivers
|
||||
static constexpr uint64_t SYS_SDR_INFO = 141; // (index, SdrDeviceInfo*)
|
||||
static constexpr uint64_t SYS_SDR_OPEN = 142; // (index) -> handle
|
||||
static constexpr uint64_t SYS_SDR_CLOSE = 143; // (handle)
|
||||
static constexpr uint64_t SYS_SDR_START = 144; // (handle) begin streaming
|
||||
static constexpr uint64_t SYS_SDR_STOP = 145; // (handle) stop streaming
|
||||
static constexpr uint64_t SYS_SDR_READ = 146; // (handle, buf, len) -> bytes
|
||||
static constexpr uint64_t SYS_SDR_SETPARAM = 147; // (handle, param, value)
|
||||
static constexpr uint64_t SYS_SDR_GETPARAM = 148; // (handle, param) -> value
|
||||
|
||||
// Tunable parameters (for SYS_SDR_SETPARAM / SYS_SDR_GETPARAM).
|
||||
static constexpr int SDR_PARAM_FREQ = 0; // center frequency, Hz
|
||||
static constexpr int SDR_PARAM_SAMPLE_RATE = 1; // sample rate, Hz
|
||||
static constexpr int SDR_PARAM_GAIN_MODE = 2; // 0 = auto/AGC, 1 = manual
|
||||
static constexpr int SDR_PARAM_GAIN = 3; // tuner gain, tenths of dB
|
||||
static constexpr int SDR_PARAM_FREQ_CORR = 4; // frequency correction, ppm
|
||||
static constexpr int SDR_PARAM_AGC = 5; // demod digital AGC, 0/1
|
||||
static constexpr int SDR_PARAM_DIRECT_SAMP = 6; // direct sampling: 0=off,1=I,2=Q
|
||||
|
||||
// Sample formats reported in SdrDeviceInfo.sampleFormat.
|
||||
static constexpr uint8_t SDR_FORMAT_CU8 = 0; // 8-bit unsigned interleaved I/Q
|
||||
|
||||
// Graceful power-off request actions (SYS_POWER_REQUEST). The desktop posts
|
||||
// a pending action and exits; login.elf reads it, runs the shutdown stages,
|
||||
// then issues the matching SYS_SHUTDOWN / SYS_RESET.
|
||||
@@ -446,6 +469,24 @@ namespace montauk::abi {
|
||||
uint8_t _pad[2];
|
||||
};
|
||||
|
||||
// Software-defined radio receiver description (returned by SYS_SDR_INFO).
|
||||
struct SdrDeviceInfo {
|
||||
char name[64]; // e.g. "Realtek RTL2832U"
|
||||
char tuner[32]; // e.g. "Rafael Micro R820T2"
|
||||
char serial[32]; // device serial / bus location
|
||||
uint64_t freqMin; // minimum tunable center frequency, Hz
|
||||
uint64_t freqMax; // maximum tunable center frequency, Hz
|
||||
uint32_t sampleRateMin; // minimum sample rate, Hz
|
||||
uint32_t sampleRateMax; // maximum sample rate, Hz
|
||||
uint32_t numGains; // number of discrete tuner gain steps
|
||||
int32_t gains[32]; // available gains, tenths of dB
|
||||
uint8_t sampleFormat; // SDR_FORMAT_*
|
||||
uint8_t present; // 1 if the underlying hardware is connected
|
||||
uint8_t streaming; // 1 if currently delivering samples
|
||||
uint8_t _pad;
|
||||
uint32_t _pad2;
|
||||
};
|
||||
|
||||
struct ThermalInfo {
|
||||
char name[32]; // short zone name (e.g. "THRM", "TZ00")
|
||||
int32_t temperature; // tenths of degrees Celsius, or -1 if unavailable
|
||||
|
||||
@@ -163,6 +163,15 @@ extern "C" {
|
||||
#define MTK_SYS_BTSETADDR 137
|
||||
#define MTK_SYS_BTBONDS 138
|
||||
#define MTK_SYS_BTFORGET 139
|
||||
#define MTK_SYS_SDR_COUNT 140
|
||||
#define MTK_SYS_SDR_INFO 141
|
||||
#define MTK_SYS_SDR_OPEN 142
|
||||
#define MTK_SYS_SDR_CLOSE 143
|
||||
#define MTK_SYS_SDR_START 144
|
||||
#define MTK_SYS_SDR_STOP 145
|
||||
#define MTK_SYS_SDR_READ 146
|
||||
#define MTK_SYS_SDR_SETPARAM 147
|
||||
#define MTK_SYS_SDR_GETPARAM 148
|
||||
/* @SYSCALLS-END */
|
||||
|
||||
#define MTK_SOCK_TCP 1
|
||||
|
||||
@@ -557,6 +557,63 @@ namespace montauk {
|
||||
return (int)syscall1(montauk::abi::SYS_BTINFO, (uint64_t)buf);
|
||||
}
|
||||
|
||||
// Software-defined radio (Rx). Receivers are identified by index [0, count);
|
||||
// open() returns a handle used by the rest of the calls. Samples are read
|
||||
// as interleaved 8-bit unsigned I/Q (CU8) from the device's ring buffer.
|
||||
inline int sdr_count() {
|
||||
return (int)syscall0(montauk::abi::SYS_SDR_COUNT);
|
||||
}
|
||||
inline int sdr_info(int index, montauk::abi::SdrDeviceInfo* out) {
|
||||
return (int)syscall2(montauk::abi::SYS_SDR_INFO, (uint64_t)index, (uint64_t)out);
|
||||
}
|
||||
inline int sdr_open(int index) {
|
||||
return (int)syscall1(montauk::abi::SYS_SDR_OPEN, (uint64_t)index);
|
||||
}
|
||||
inline int sdr_close(int handle) {
|
||||
return (int)syscall1(montauk::abi::SYS_SDR_CLOSE, (uint64_t)handle);
|
||||
}
|
||||
inline int sdr_start(int handle) {
|
||||
return (int)syscall1(montauk::abi::SYS_SDR_START, (uint64_t)handle);
|
||||
}
|
||||
inline int sdr_stop(int handle) {
|
||||
return (int)syscall1(montauk::abi::SYS_SDR_STOP, (uint64_t)handle);
|
||||
}
|
||||
// Non-blocking: copies up to len bytes of queued I/Q, returns bytes copied.
|
||||
inline int sdr_read(int handle, void* buf, uint32_t len) {
|
||||
return (int)syscall3(montauk::abi::SYS_SDR_READ, (uint64_t)handle, (uint64_t)buf, (uint64_t)len);
|
||||
}
|
||||
inline int sdr_set_param(int handle, int param, uint64_t value) {
|
||||
return (int)syscall3(montauk::abi::SYS_SDR_SETPARAM, (uint64_t)handle, (uint64_t)param, value);
|
||||
}
|
||||
inline int64_t sdr_get_param(int handle, int param) {
|
||||
return syscall2(montauk::abi::SYS_SDR_GETPARAM, (uint64_t)handle, (uint64_t)param);
|
||||
}
|
||||
// Convenience wrappers over sdr_set_param / sdr_get_param.
|
||||
inline int sdr_set_freq(int handle, uint64_t hz) {
|
||||
return sdr_set_param(handle, montauk::abi::SDR_PARAM_FREQ, hz);
|
||||
}
|
||||
inline uint64_t sdr_get_freq(int handle) {
|
||||
return (uint64_t)sdr_get_param(handle, montauk::abi::SDR_PARAM_FREQ);
|
||||
}
|
||||
inline int sdr_set_sample_rate(int handle, uint32_t hz) {
|
||||
return sdr_set_param(handle, montauk::abi::SDR_PARAM_SAMPLE_RATE, hz);
|
||||
}
|
||||
inline uint32_t sdr_get_sample_rate(int handle) {
|
||||
return (uint32_t)sdr_get_param(handle, montauk::abi::SDR_PARAM_SAMPLE_RATE);
|
||||
}
|
||||
inline int sdr_set_gain_mode(int handle, int manual) {
|
||||
return sdr_set_param(handle, montauk::abi::SDR_PARAM_GAIN_MODE, (uint64_t)manual);
|
||||
}
|
||||
inline int sdr_set_gain(int handle, int tenthsDb) {
|
||||
return sdr_set_param(handle, montauk::abi::SDR_PARAM_GAIN, (uint64_t)(int64_t)tenthsDb);
|
||||
}
|
||||
inline int sdr_set_freq_correction(int handle, int ppm) {
|
||||
return sdr_set_param(handle, montauk::abi::SDR_PARAM_FREQ_CORR, (uint64_t)(int64_t)ppm);
|
||||
}
|
||||
inline int sdr_set_agc(int handle, int on) {
|
||||
return sdr_set_param(handle, montauk::abi::SDR_PARAM_AGC, (uint64_t)on);
|
||||
}
|
||||
|
||||
// Kernel introspection
|
||||
inline void memstats(montauk::abi::MemStats* out) { syscall1(montauk::abi::SYS_MEMSTATS, (uint64_t)out); }
|
||||
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,260 @@
|
||||
/*
|
||||
* main.cpp
|
||||
* sdr - software-defined radio receive demo.
|
||||
*
|
||||
* Exercises the generic SDR Rx API 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
|
||||
* exits cleanly.
|
||||
*
|
||||
* Usage:
|
||||
* sdr tune 100.100 MHz at 2.048 Msps (defaults)
|
||||
* sdr <freqMHz> e.g. "sdr 433.92"
|
||||
* sdr <freqMHz> <rateHz> e.g. "sdr 100.1 2400000"
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#include <montauk/syscall.h>
|
||||
#include <montauk/string.h>
|
||||
|
||||
using namespace montauk;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Small integer / formatting helpers (freestanding; no libc printf)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static void put_u64(uint64_t n) {
|
||||
char buf[24];
|
||||
int i = 0;
|
||||
if (n == 0) { putchar('0'); return; }
|
||||
while (n) { buf[i++] = (char)('0' + (n % 10)); n /= 10; }
|
||||
while (i) putchar(buf[--i]);
|
||||
}
|
||||
|
||||
static void put_i64(int64_t n) {
|
||||
if (n < 0) { putchar('-'); n = -n; }
|
||||
put_u64((uint64_t)n);
|
||||
}
|
||||
|
||||
// Render a frequency in Hz as "NNN.NNN MHz".
|
||||
static void put_mhz(uint64_t hz) {
|
||||
uint64_t mhz = hz / 1000000;
|
||||
uint64_t kfrac = (hz % 1000000) / 1000; // 3-digit kHz remainder
|
||||
put_u64(mhz);
|
||||
putchar('.');
|
||||
if (kfrac < 100) putchar('0');
|
||||
if (kfrac < 10) putchar('0');
|
||||
put_u64(kfrac);
|
||||
print(" MHz");
|
||||
}
|
||||
|
||||
// Render an integer sample rate as "N.NNN Msps".
|
||||
static void put_msps(uint64_t hz) {
|
||||
uint64_t whole = hz / 1000000;
|
||||
uint64_t frac = (hz % 1000000) / 1000;
|
||||
put_u64(whole);
|
||||
putchar('.');
|
||||
if (frac < 100) putchar('0');
|
||||
if (frac < 10) putchar('0');
|
||||
put_u64(frac);
|
||||
print(" Msps");
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Argument parsing
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static uint64_t parse_mhz_to_hz(const char* s) {
|
||||
uint64_t intpart = 0;
|
||||
while (*s >= '0' && *s <= '9') { intpart = intpart * 10 + (uint64_t)(*s - '0'); s++; }
|
||||
uint64_t hz = intpart * 1000000;
|
||||
if (*s == '.') {
|
||||
s++;
|
||||
uint64_t scale = 100000; // first fractional digit == 100 kHz
|
||||
while (*s >= '0' && *s <= '9' && scale > 0) {
|
||||
hz += (uint64_t)(*s - '0') * scale;
|
||||
scale /= 10;
|
||||
s++;
|
||||
}
|
||||
}
|
||||
return hz;
|
||||
}
|
||||
|
||||
static uint64_t parse_u64(const char* s) {
|
||||
uint64_t v = 0;
|
||||
while (*s >= '0' && *s <= '9') { v = v * 10 + (uint64_t)(*s - '0'); s++; }
|
||||
return v;
|
||||
}
|
||||
|
||||
// Copy the next whitespace-delimited token into tokbuf; advance *rest past it.
|
||||
// Returns false when no token remains.
|
||||
static bool next_token(const char** rest, char* tokbuf, int cap) {
|
||||
const char* s = skip_spaces(*rest);
|
||||
if (*s == '\0') { *rest = s; return false; }
|
||||
int i = 0;
|
||||
while (*s && *s != ' ' && i < cap - 1) tokbuf[i++] = *s++;
|
||||
tokbuf[i] = '\0';
|
||||
*rest = s;
|
||||
return true;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Demo
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static uint8_t g_iq[64 * 1024]; // I/Q read buffer (CU8)
|
||||
|
||||
static void print_receiver(int idx, const montauk::abi::SdrDeviceInfo& 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;
|
||||
for (uint32_t g = 0; g < shown; g++) {
|
||||
putchar(' ');
|
||||
put_i64(info.gains[g] / 10);
|
||||
putchar('.');
|
||||
put_u64((uint64_t)(info.gains[g] % 10));
|
||||
}
|
||||
print(" dB\n");
|
||||
}
|
||||
|
||||
extern "C" void _start() {
|
||||
char args[128];
|
||||
int alen = montauk::getargs(args, sizeof(args));
|
||||
const char* rest = (alen > 0) ? args : "";
|
||||
|
||||
uint64_t freqHz = 100100000ull; // 100.100 MHz default (FM broadcast band)
|
||||
uint32_t rateHz = 2048000; // 2.048 Msps default
|
||||
|
||||
char tok[64];
|
||||
if (next_token(&rest, tok, sizeof(tok))) freqHz = parse_mhz_to_hz(tok);
|
||||
if (next_token(&rest, tok, sizeof(tok))) rateHz = (uint32_t)parse_u64(tok);
|
||||
|
||||
print("=== MontaukOS SDR receive demo ===\n\n");
|
||||
|
||||
// --- Enumerate -------------------------------------------------------
|
||||
int count = montauk::sdr_count();
|
||||
print("SDR receivers detected: "); put_u64((uint64_t)count); print("\n");
|
||||
if (count <= 0) {
|
||||
print("\nNo SDR receivers are connected.\n");
|
||||
print("Plug in an RTL-SDR dongle (Realtek RTL2832U + R820T2) and retry.\n");
|
||||
montauk::exit(0);
|
||||
}
|
||||
|
||||
montauk::abi::SdrDeviceInfo info;
|
||||
for (int i = 0; i < count; i++) {
|
||||
if (montauk::sdr_info(i, &info) == 0) print_receiver(i, info);
|
||||
}
|
||||
|
||||
// --- Open + configure receiver 0 ------------------------------------
|
||||
int idx = 0;
|
||||
if (montauk::sdr_info(idx, &info) != 0) {
|
||||
print("\nsdr: failed to query receiver info\n");
|
||||
montauk::exit(1);
|
||||
}
|
||||
|
||||
int h = montauk::sdr_open(idx);
|
||||
if (h < 0) {
|
||||
print("\nsdr: failed to open receiver 0\n");
|
||||
montauk::exit(1);
|
||||
}
|
||||
print("\nOpened receiver 0 (handle "); put_u64((uint64_t)h); print(")\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;
|
||||
|
||||
print("Configuring: ");
|
||||
if (montauk::sdr_set_sample_rate(h, 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
|
||||
print("\n warning: gain mode rejected");
|
||||
if (montauk::sdr_set_freq(h, 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(" gain : auto (AGC)\n");
|
||||
|
||||
// --- Stream ----------------------------------------------------------
|
||||
if (montauk::sdr_start(h) != 0) {
|
||||
print("\nsdr: failed to start streaming\n");
|
||||
montauk::sdr_close(h);
|
||||
montauk::exit(1);
|
||||
}
|
||||
print("\nStreaming I/Q for ~2s ...\n");
|
||||
|
||||
uint64_t totalBytes = 0;
|
||||
int64_t sumI = 0, sumQ = 0;
|
||||
uint64_t sumPower = 0;
|
||||
uint32_t peak = 0;
|
||||
|
||||
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));
|
||||
if (n <= 0) { montauk::sleep_ms(20); continue; }
|
||||
|
||||
// CU8: interleaved 8-bit unsigned I/Q, 127.5 == zero.
|
||||
for (int k = 0; k + 1 < n; k += 2) {
|
||||
int i = (int)g_iq[k] - 127;
|
||||
int q = (int)g_iq[k + 1] - 127;
|
||||
sumI += i;
|
||||
sumQ += q;
|
||||
sumPower += (uint64_t)(i * i + q * q);
|
||||
uint32_t mi = (uint32_t)(i < 0 ? -i : i);
|
||||
uint32_t mq = (uint32_t)(q < 0 ? -q : q);
|
||||
if (mi > peak) peak = mi;
|
||||
if (mq > peak) peak = mq;
|
||||
}
|
||||
totalBytes += (uint64_t)n;
|
||||
|
||||
uint64_t now = montauk::get_milliseconds();
|
||||
if (now - lastReport >= 500) {
|
||||
print(" ... "); put_u64(totalBytes / 1024); print(" KiB\n");
|
||||
lastReport = now;
|
||||
}
|
||||
}
|
||||
|
||||
montauk::sdr_stop(h);
|
||||
|
||||
// --- Report ----------------------------------------------------------
|
||||
uint64_t elapsed = montauk::get_milliseconds() - start;
|
||||
if (elapsed == 0) elapsed = 1;
|
||||
uint64_t pairs = totalBytes / 2;
|
||||
|
||||
print("\n--- capture summary ---\n");
|
||||
print(" bytes read : "); put_u64(totalBytes);
|
||||
print(" ("); put_u64(pairs); print(" I/Q samples)\n");
|
||||
print(" throughput : "); put_u64((totalBytes * 1000) / elapsed / 1024);
|
||||
print(" KiB/s\n");
|
||||
|
||||
if (pairs == 0) {
|
||||
print(" no samples were delivered.\n");
|
||||
print(" (expected under emulation / with no dongle actively streaming.)\n");
|
||||
} else {
|
||||
int64_t meanI = sumI / (int64_t)pairs;
|
||||
int64_t meanQ = sumQ / (int64_t)pairs;
|
||||
uint64_t avgPower = sumPower / pairs;
|
||||
print(" DC offset : I="); put_i64(meanI);
|
||||
print(" Q="); put_i64(meanQ); print("\n");
|
||||
print(" avg power : "); put_u64(avgPower);
|
||||
print(" (per-sample I^2+Q^2)\n");
|
||||
print(" peak |sample|: "); put_u64((uint64_t)peak); print(" / 128\n");
|
||||
}
|
||||
|
||||
montauk::sdr_close(h);
|
||||
print("\nClosed receiver. Done.\n");
|
||||
montauk::exit(0);
|
||||
}
|
||||
Reference in New Issue
Block a user