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