fix: move RTL-SDR to userspace

This commit is contained in:
2026-08-29 10:02:32 +02:00
parent b1f1cfe32b
commit 5cd5c2e6be
35 changed files with 1922 additions and 1592 deletions
+12 -3
View File
@@ -49,7 +49,7 @@ BINDIR := bin
PROGRAMS := $(notdir $(wildcard src/*))
# Programs with custom Makefiles (built separately).
CUSTOM_BUILDS := 2048 fetch wiki wikipedia weather imageviewer fontpreview spreadsheet wordprocessor pdfviewer disks devexplorer installer audio music video bluetooth network display keyboard sshserver terminal syslog procmgr powermgr calculator charmap desktop login shell paint tcc lua screenshot texteditor mandelbrot printers timezone printd printctl dialogs libloader crashpad sshd
CUSTOM_BUILDS := 2048 fetch wiki wikipedia weather imageviewer fontpreview spreadsheet wordprocessor pdfviewer disks devexplorer installer audio music video bluetooth network display keyboard sshserver terminal syslog procmgr powermgr calculator charmap desktop login shell paint tcc lua screenshot texteditor mandelbrot printers timezone printd printctl dialogs libloader crashpad sshd rtlsdr sdr
SYSTEM_PROGRAMS := $(filter-out $(CUSTOM_BUILDS),$(PROGRAMS))
# Build targets: system programs go to bin/os/, apps go to bin/apps/<name>/.
@@ -96,9 +96,9 @@ WPDIR := data/wallpapers
WPSRC := $(wildcard $(WPDIR)/*.jpg)
WPDST := $(patsubst $(WPDIR)/%,$(BINDIR)/os/wallpapers/%,$(WPSRC))
.PHONY: all clean 2048 fetch wiki wikipedia weather imageviewer fontpreview spreadsheet wordprocessor pdfviewer disks devexplorer installer audio music video bluetooth network display keyboard sshserver sshd terminal syslog procmgr powermgr calculator charmap login desktop shell paint tcc lua screenshot texteditor mandelbrot printers timezone printd printctl dialogs icons fonts configs osdata bearssl libc tls libjpeg libjpegwrite install-apps libloader crashpad check-syscalls gen-syscalls
.PHONY: all clean 2048 fetch wiki wikipedia weather imageviewer fontpreview spreadsheet wordprocessor pdfviewer disks devexplorer installer audio music video bluetooth network display keyboard sshserver sshd terminal syslog procmgr powermgr calculator charmap login desktop shell paint tcc lua screenshot texteditor mandelbrot printers timezone printd printctl dialogs icons fonts configs osdata bearssl libc tls libjpeg libjpegwrite install-apps libloader crashpad rtlsdr sdr check-syscalls gen-syscalls
all: bearssl libc libjpeg libjpegwrite tls libloader devkit $(TARGETS) fetch wiki wikipedia weather imageviewer fontpreview spreadsheet wordprocessor pdfviewer disks devexplorer installer audio music video bluetooth network display sshserver terminal syslog procmgr powermgr calculator charmap 2048 paint tcc lua screenshot texteditor mandelbrot printers timezone printd printctl dialogs login desktop shell sshd icons fonts install-apps crashpad $(MANDST) $(WWWDST) $(CA_CERTS) $(CONFIGDST) $(OSDATADST) $(FWDST) $(LICDST) $(WPDST)
all: bearssl libc libjpeg libjpegwrite tls libloader rtlsdr sdr devkit $(TARGETS) fetch wiki wikipedia weather imageviewer fontpreview spreadsheet wordprocessor pdfviewer disks devexplorer installer audio music video bluetooth network display sshserver terminal syslog procmgr powermgr calculator charmap 2048 paint tcc lua screenshot texteditor mandelbrot printers timezone printd printctl dialogs login desktop shell sshd icons fonts install-apps crashpad $(MANDST) $(WWWDST) $(CA_CERTS) $(CONFIGDST) $(OSDATADST) $(FWDST) $(LICDST) $(WPDST)
# Build BearSSL static library (cross-compiled for freestanding x86_64).
BEARSSL_INCLUDES := -isystem $(shell cd .. && pwd)/kernel/freestnd-c-hdrs/x86_64/include -isystem $(abspath include/libc)
@@ -139,6 +139,12 @@ tls: bearssl libc
libloader: libc
$(MAKE) -C src/libloader
rtlsdr: libc
$(MAKE) -C src/rtlsdr
sdr: libc libloader rtlsdr
$(MAKE) -C src/sdr
# Build fetch via its own Makefile (depends on bearssl, libc, and tls).
fetch: bearssl libc tls
$(MAKE) -C src/fetch
@@ -423,6 +429,7 @@ ifneq ($(wildcard $(NATIVE_BIN)/as),)
cp -r include/libc/. $(BINDIR)/sdk/include/
cp -r include/montauk $(BINDIR)/sdk/include/montauk
cp -r include/Api $(BINDIR)/sdk/include/Api
cp -r include/rtlsdr $(BINDIR)/sdk/include/rtlsdr
cp -r ../kernel/freestnd-cxx-hdrs/x86_64/include/. $(BINDIR)/sdk/include/
# liblibc-full.a, not liblibc.a: on-OS builds cannot pass
# -Wl,-u,_pf_putfloat, so the sysroot ships the variant with the printf
@@ -512,3 +519,5 @@ clean:
$(MAKE) -C src/dialogs clean
$(MAKE) -C src/crashpad clean
$(MAKE) -C src/libloader clean
$(MAKE) -C src/rtlsdr clean
$(MAKE) -C src/sdr clean
+57 -37
View File
@@ -191,16 +191,16 @@ 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
/* Reserved: former SDR API. Kept unavailable to preserve ABI numbering. */
static constexpr uint64_t SYS_RESERVED_140 = 140;
static constexpr uint64_t SYS_RESERVED_141 = 141;
static constexpr uint64_t SYS_RESERVED_142 = 142;
static constexpr uint64_t SYS_RESERVED_143 = 143;
static constexpr uint64_t SYS_RESERVED_144 = 144;
static constexpr uint64_t SYS_RESERVED_145 = 145;
static constexpr uint64_t SYS_RESERVED_146 = 146;
static constexpr uint64_t SYS_RESERVED_147 = 147;
static constexpr uint64_t SYS_RESERVED_148 = 148;
// CPU power/thermal status
static constexpr uint64_t SYS_POWERINFO = 149; // (PowerInfo*) -> 0, -1 unsupported
@@ -239,20 +239,27 @@ namespace montauk::abi {
static constexpr uint64_t SYS_SETENVIRON = 172;
static constexpr uint64_t SYS_SPAWN_ENV = 173;
/* Generic userspace USB interface access */
static constexpr uint64_t SYS_USB_LIST = 178;
static constexpr uint64_t SYS_USB_CLAIM = 179;
static constexpr uint64_t SYS_USB_CLOSE = 180;
static constexpr uint64_t SYS_USB_CONTROL = 181;
static constexpr uint64_t SYS_USB_BULK_IN_START = 182;
static constexpr uint64_t SYS_USB_BULK_IN_STOP = 183;
static constexpr uint64_t SYS_USB_BULK_IN_READ = 184;
static constexpr uint64_t SYS_LOG_WRITE = 176; // (logMessage) -> 0
static constexpr uint64_t SYS_TERMINAL_ATTACHED = 177; // () -> 1 when connected to a userspace terminal
// 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
static constexpr int USB_ERR_INVALID = -1;
static constexpr int USB_ERR_BUSY = -2;
static constexpr int USB_ERR_DISCONNECTED = -3;
static constexpr int USB_ERR_UNSUPPORTED = -4;
static constexpr int USB_ERR_IO = -5;
static constexpr int USB_ERR_NO_RESOURCES = -6;
static constexpr int USB_ERR_NOT_FOUND = -7;
static constexpr int USB_ERR_KERNEL_BOUND = -8;
// Graceful power-off request actions (SYS_POWER_REQUEST). The desktop posts
// a pending action and exits; login.elf reads it, runs the shutdown stages,
@@ -576,23 +583,36 @@ 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 UsbInterfaceInfo {
uint8_t slotId;
uint8_t portId;
uint8_t speed;
uint8_t interfaceNumber;
uint16_t vendorId;
uint16_t productId;
uint8_t deviceClass;
uint8_t interfaceClass;
uint8_t interfaceSubClass;
uint8_t interfaceProtocol;
uint8_t bulkInEndpoint;
uint8_t bulkOutEndpoint;
uint16_t bulkInMaxPacket;
uint16_t bulkOutMaxPacket;
uint8_t kernelDriverBound;
uint8_t claimed;
uint8_t _reserved[4];
} __attribute__((packed));
struct UsbControlRequest {
uint8_t requestType;
uint8_t request;
uint16_t value;
uint16_t index;
uint16_t length;
} __attribute__((packed));
static_assert(sizeof(UsbInterfaceInfo) == 24);
static_assert(sizeof(UsbControlRequest) == 8);
// Wi-Fi security suites reported in WifiNetwork.security.
static constexpr uint8_t WIFI_SEC_OPEN = 0;
+90 -9
View File
@@ -163,15 +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
#define MTK_SYS_RESERVED_140 140
#define MTK_SYS_RESERVED_141 141
#define MTK_SYS_RESERVED_142 142
#define MTK_SYS_RESERVED_143 143
#define MTK_SYS_RESERVED_144 144
#define MTK_SYS_RESERVED_145 145
#define MTK_SYS_RESERVED_146 146
#define MTK_SYS_RESERVED_147 147
#define MTK_SYS_RESERVED_148 148
#define MTK_SYS_POWERINFO 149
#define MTK_SYS_FBFLIP 150
#define MTK_SYS_GETEXECPATH 151
@@ -201,6 +201,13 @@ extern "C" {
#define MTK_SYS_KILLSESSION 175
#define MTK_SYS_LOG_WRITE 176
#define MTK_SYS_TERMINAL_ATTACHED 177
#define MTK_SYS_USB_LIST 178
#define MTK_SYS_USB_CLAIM 179
#define MTK_SYS_USB_CLOSE 180
#define MTK_SYS_USB_CONTROL 181
#define MTK_SYS_USB_BULK_IN_START 182
#define MTK_SYS_USB_BULK_IN_STOP 183
#define MTK_SYS_USB_BULK_IN_READ 184
/* @SYSCALLS-END */
#define MTK_SOCK_TCP 1
@@ -210,6 +217,14 @@ extern "C" {
#define MTK_IPC_SIGNAL_PEER_CLOSED (1u << 2)
#define MTK_IPC_SIGNAL_EXITED (1u << 3)
#define MTK_IPC_SIGNAL_READY (1u << 4)
#define MTK_USB_ERR_INVALID (-1)
#define MTK_USB_ERR_BUSY (-2)
#define MTK_USB_ERR_DISCONNECTED (-3)
#define MTK_USB_ERR_UNSUPPORTED (-4)
#define MTK_USB_ERR_IO (-5)
#define MTK_USB_ERR_NO_RESOURCES (-6)
#define MTK_USB_ERR_NOT_FOUND (-7)
#define MTK_USB_ERR_KERNEL_BOUND (-8)
/* Window event types */
#define MTK_EVENT_KEY 0
@@ -314,6 +329,34 @@ typedef struct {
uint32_t dns_server;
} mtk_netcfg;
typedef struct __attribute__((packed)) {
uint8_t slot_id;
uint8_t port_id;
uint8_t speed;
uint8_t interface_number;
uint16_t vendor_id;
uint16_t product_id;
uint8_t device_class;
uint8_t interface_class;
uint8_t interface_subclass;
uint8_t interface_protocol;
uint8_t bulk_in_endpoint;
uint8_t bulk_out_endpoint;
uint16_t bulk_in_max_packet;
uint16_t bulk_out_max_packet;
uint8_t kernel_driver_bound;
uint8_t claimed;
uint8_t reserved[4];
} mtk_usb_interface_info;
typedef struct __attribute__((packed)) {
uint8_t request_type;
uint8_t request;
uint16_t value;
uint16_t index;
uint16_t length;
} mtk_usb_control_request;
typedef struct {
int32_t pid;
int32_t parent_pid;
@@ -778,6 +821,44 @@ static inline void mtk_get_netcfg(mtk_netcfg *out) {
_mtk_syscall1(MTK_SYS_GETNETCFG, (long)out);
}
/* ====================================================================
Generic USB interface access
==================================================================== */
static inline int mtk_usb_list(mtk_usb_interface_info *out, int max_count) {
return (int)_mtk_syscall2(MTK_SYS_USB_LIST, (long)out, (long)max_count);
}
static inline int mtk_usb_claim(uint8_t slot_id, uint8_t interface_number) {
return (int)_mtk_syscall2(MTK_SYS_USB_CLAIM, (long)slot_id,
(long)interface_number);
}
static inline int mtk_usb_close(int handle) {
return (int)_mtk_syscall1(MTK_SYS_USB_CLOSE, (long)handle);
}
static inline int mtk_usb_control(int handle, const mtk_usb_control_request *request,
void *data, uint32_t data_len) {
return (int)_mtk_syscall4(MTK_SYS_USB_CONTROL, (long)handle, (long)request,
(long)data, (long)data_len);
}
static inline int mtk_usb_bulk_in_start(int handle, uint32_t transfer_bytes,
uint32_t buffer_count) {
return (int)_mtk_syscall3(MTK_SYS_USB_BULK_IN_START, (long)handle,
(long)transfer_bytes, (long)buffer_count);
}
static inline int mtk_usb_bulk_in_stop(int handle) {
return (int)_mtk_syscall1(MTK_SYS_USB_BULK_IN_STOP, (long)handle);
}
static inline int mtk_usb_bulk_in_read(int handle, void *data, uint32_t data_len) {
return (int)_mtk_syscall3(MTK_SYS_USB_BULK_IN_READ, (long)handle,
(long)data, (long)data_len);
}
/* ====================================================================
Audio
==================================================================== */
+24 -49
View File
@@ -661,61 +661,36 @@ namespace montauk {
(uint64_t)maxCount);
}
// 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);
// Generic USB access. Only interfaces without a bound kernel class driver
// may be claimed. Handles belong to the claiming process and are released
// automatically when it exits.
inline int usb_list(montauk::abi::UsbInterfaceInfo* buf, int maxCount) {
return (int)syscall2(montauk::abi::SYS_USB_LIST, (uint64_t)buf,
(uint64_t)maxCount);
}
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 usb_claim(uint8_t slotId, uint8_t interfaceNumber) {
return (int)syscall2(montauk::abi::SYS_USB_CLAIM, (uint64_t)slotId,
(uint64_t)interfaceNumber);
}
inline int sdr_open(int index) {
return (int)syscall1(montauk::abi::SYS_SDR_OPEN, (uint64_t)index);
inline int usb_close(int handle) {
return (int)syscall1(montauk::abi::SYS_USB_CLOSE, (uint64_t)handle);
}
inline int sdr_close(int handle) {
return (int)syscall1(montauk::abi::SYS_SDR_CLOSE, (uint64_t)handle);
inline int usb_control(int handle, const montauk::abi::UsbControlRequest* request,
void* data, uint32_t dataLen) {
return (int)syscall4(montauk::abi::SYS_USB_CONTROL, (uint64_t)handle,
(uint64_t)request, (uint64_t)data, (uint64_t)dataLen);
}
inline int sdr_start(int handle) {
return (int)syscall1(montauk::abi::SYS_SDR_START, (uint64_t)handle);
inline int usb_bulk_in_start(int handle, uint32_t transferBytes,
uint32_t bufferCount) {
return (int)syscall3(montauk::abi::SYS_USB_BULK_IN_START, (uint64_t)handle,
(uint64_t)transferBytes, (uint64_t)bufferCount);
}
inline int sdr_stop(int handle) {
return (int)syscall1(montauk::abi::SYS_SDR_STOP, (uint64_t)handle);
inline int usb_bulk_in_stop(int handle) {
return (int)syscall1(montauk::abi::SYS_USB_BULK_IN_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);
inline int usb_bulk_in_read(int handle, void* data, uint32_t dataLen) {
return (int)syscall3(montauk::abi::SYS_USB_BULK_IN_READ, (uint64_t)handle,
(uint64_t)data, (uint64_t)dataLen);
}
// Kernel introspection
+42
View File
@@ -0,0 +1,42 @@
#pragma once
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
typedef struct rtlsdr_device rtlsdr_device;
typedef struct rtlsdr_device_info {
char name[64];
char tuner[32];
char serial[32];
uint64_t freq_min;
uint64_t freq_max;
uint32_t sample_rate_min;
uint32_t sample_rate_max;
uint32_t num_gains;
int32_t gains[32];
} rtlsdr_device_info;
int rtlsdr_count(void);
int rtlsdr_get_device_info(int index, rtlsdr_device_info* out);
int rtlsdr_open(rtlsdr_device** out, int index);
int rtlsdr_close(rtlsdr_device* dev);
int rtlsdr_set_center_freq(rtlsdr_device* dev, uint64_t hz);
uint64_t rtlsdr_get_center_freq(const rtlsdr_device* dev);
int rtlsdr_set_sample_rate(rtlsdr_device* dev, uint32_t hz);
uint32_t rtlsdr_get_sample_rate(const rtlsdr_device* dev);
int rtlsdr_set_tuner_gain_mode(rtlsdr_device* dev, int manual);
int rtlsdr_set_tuner_gain(rtlsdr_device* dev, int tenths_db);
int rtlsdr_set_freq_correction(rtlsdr_device* dev, int ppm);
int rtlsdr_set_agc_mode(rtlsdr_device* dev, int on);
int rtlsdr_set_direct_sampling(rtlsdr_device* dev, int mode);
int rtlsdr_start(rtlsdr_device* dev);
int rtlsdr_stop(rtlsdr_device* dev);
int rtlsdr_read(rtlsdr_device* dev, void* data, uint32_t length);
#ifdef __cplusplus
}
#endif
+44
View File
@@ -0,0 +1,44 @@
.TH RTLSDR 3
.SH NAME
rtlsdr.lib - userspace RTL2832U and R820T2 radio driver
.SH SYNOPSIS
.nf
#include <rtlsdr/rtlsdr.h>
.fi
.SH DESCRIPTION
The library at
.B 0:/os/rtlsdr.lib
drives supported Realtek RTL2832U USB receivers from userspace through the
generic process-owned USB API. Supported USB product IDs are 0bda:2832 and
0bda:2838. Samples are returned as interleaved unsigned 8-bit I/Q pairs.
Applications load the library with libloader, resolve the
.B rtlsdr_*
symbols declared in the header, enumerate devices, open one, configure it,
start bulk reception, read samples, stop, and close it. Claims are exclusive
and are released by the kernel if the process exits or the device disconnects.
.SH API
.B rtlsdr_count
and
.B rtlsdr_get_device_info
enumerate compatible unbound USB interfaces.
.B rtlsdr_open
claims an interface and returns an opaque device pointer.
.B rtlsdr_set_center_freq,
.B rtlsdr_set_sample_rate,
.B rtlsdr_set_tuner_gain_mode,
.B rtlsdr_set_tuner_gain,
.B rtlsdr_set_freq_correction,
.B rtlsdr_set_agc_mode,
and
.B rtlsdr_set_direct_sampling
configure the receiver.
.B rtlsdr_start,
.B rtlsdr_read,
and
.B rtlsdr_stop
control non-blocking reception.
.SH NOTES
Syscall numbers 140 through 148 are reserved former SDR slots and are not an
API. Device-specific control and tuner programming live entirely in this
library.
+47 -43
View File
@@ -650,58 +650,62 @@
Forget a paired device; it must re-pair next time.
int montauk::bt_forget(const uint8_t* bdAddr);
.SH SOFTWARE-DEFINED RADIO
Receive-only SDR API. Receivers are enumerated by index in
[0, SYS_SDR_COUNT); SYS_SDR_OPEN returns a handle used by the
rest of the calls. Samples are delivered as interleaved 8-bit
unsigned I/Q (CU8, SDR_FORMAT_CU8) from the device's ring
buffer. Backed by an RTL-SDR (RTL2832U + R820T2) driver.
.SH GENERIC USB INTERFACES
Process-owned access to USB interfaces that do not have a bound kernel
class driver. Claims are exclusive and are released automatically at
process exit. The current xHCI device model records one interface per
device slot, so claiming that interface temporarily claims the whole slot.
Kernel-owned HID, Bluetooth, mass-storage, and RTL-SDR interfaces are
visible in SYS_USB_LIST but cannot be claimed.
.B SYS_SDR_COUNT (140)
Number of available SDR receivers.
int montauk::sdr_count();
.B SYS_USB_LIST (178)
List currently connected USB interfaces. Each UsbInterfaceInfo contains
stable identifiers for the current connection, endpoint addresses, maximum
packet sizes, and kernelDriverBound/claimed flags.
int montauk::usb_list(montauk::abi::UsbInterfaceInfo* buf, int maxCount);
.B SYS_SDR_INFO (141)
Get static/dynamic info for one receiver by index (name, tuner,
frequency/sample-rate ranges, gain steps, present/streaming
flags).
int montauk::sdr_info(int index, montauk::abi::SdrDeviceInfo* out);
.B SYS_USB_CLAIM (179)
Exclusively claim an unbound interface. Returns a generation-checked handle
owned by the calling process.
int montauk::usb_claim(uint8_t slotId, uint8_t interfaceNumber);
.B SYS_SDR_OPEN (142)
Open a receiver by index. Returns a handle.
int montauk::sdr_open(int index);
.B SYS_USB_CLOSE (180)
Stop active transfers and release a USB claim.
int montauk::usb_close(int handle);
.B SYS_SDR_CLOSE (143)
Close a receiver handle.
int montauk::sdr_close(int handle);
.B SYS_USB_CONTROL (181)
Execute a USB control transfer on endpoint zero. The requestType direction
bit determines whether data is read or written. request.length must equal
dataLen; control payloads are currently limited to 4096 bytes.
int montauk::usb_control(int handle, const montauk::abi::UsbControlRequest* request,
void* data, uint32_t dataLen);
.B SYS_SDR_START (144)
Begin streaming samples.
int montauk::sdr_start(int handle);
.B SYS_USB_BULK_IN_START (182)
Start a continuous bulk-IN transfer pool. transferBytes is 1..4096 and
bufferCount is 1..16. Completed data is copied into a 256 KiB per-claim
ring buffer.
int montauk::usb_bulk_in_start(int handle, uint32_t transferBytes,
uint32_t bufferCount);
.B SYS_SDR_STOP (145)
Stop streaming samples.
int montauk::sdr_stop(int handle);
.B SYS_USB_BULK_IN_STOP (183)
Stop continuous bulk-IN transfers without releasing the claim.
int montauk::usb_bulk_in_stop(int handle);
.B SYS_SDR_READ (146)
Non-blocking read of queued I/Q samples. Returns bytes copied.
int montauk::sdr_read(int handle, void* buf, uint32_t len);
.B SYS_USB_BULK_IN_READ (184)
Non-blocking read from the bulk-IN ring. Returns bytes copied, zero when no
data is queued, or USB_ERR_DISCONNECTED after queued data has been drained.
int montauk::usb_bulk_in_read(int handle, void* data, uint32_t dataLen);
.B SYS_SDR_SETPARAM (147)
Set a tunable parameter (see SDR_PARAM_* below).
int montauk::sdr_set_param(int handle, int param, uint64_t value);
Errors are USB_ERR_INVALID (-1), USB_ERR_BUSY (-2),
USB_ERR_DISCONNECTED (-3), USB_ERR_UNSUPPORTED (-4), USB_ERR_IO (-5),
USB_ERR_NO_RESOURCES (-6), USB_ERR_NOT_FOUND (-7), and
USB_ERR_KERNEL_BOUND (-8).
.B SYS_SDR_GETPARAM (148)
Get a tunable parameter's current value.
int64_t montauk::sdr_get_param(int handle, int param);
Parameters (montauk::abi::SDR_PARAM_*): FREQ (center frequency,
Hz), SAMPLE_RATE (Hz), GAIN_MODE (0=auto/AGC, 1=manual), GAIN
(tenths of dB), FREQ_CORR (ppm), AGC (demod digital AGC, 0/1),
DIRECT_SAMP (0=off, 1=I, 2=Q). Convenience wrappers exist for
each: sdr_set_freq/sdr_get_freq, sdr_set_sample_rate/
sdr_get_sample_rate, sdr_set_gain_mode, sdr_set_gain,
sdr_set_freq_correction, sdr_set_agc.
.SH RESERVED SYSCALL NUMBERS
Syscall numbers 140 through 148 are reserved. They were used by an
experimental kernel SDR API and are intentionally not dispatched or exposed
through userspace syscall wrappers. RTL-SDR support is provided by
0:/os/rtlsdr.lib over the generic userspace USB API.
.SH CLIPBOARD
.B SYS_CLIPBOARD_SET_TEXT (119)
+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);
}
+22
View File
@@ -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
View File
@@ -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);
}