From 5cd5c2e6bebf3fbafca08457c8d2b8bad63e9fed Mon Sep 17 00:00:00 2001 From: Daniel Hammer Date: Sat, 29 Aug 2026 10:02:32 +0200 Subject: [PATCH] fix: move RTL-SDR to userspace --- kernel/src/Api/BuildNo.hpp | 2 +- kernel/src/Api/Sdr.hpp | 55 -- kernel/src/Api/Syscall.cpp | 59 +- kernel/src/Api/Syscall.hpp | 100 +-- kernel/src/Api/Usb.hpp | 44 ++ kernel/src/Drivers/Radio/Sdr.cpp | 359 ----------- kernel/src/Drivers/Radio/Sdr.hpp | 119 ---- kernel/src/Drivers/USB/Radio/R820t.hpp | 61 -- kernel/src/Drivers/USB/Radio/RtlSdr.cpp | 575 ------------------ kernel/src/Drivers/USB/Radio/RtlSdr.hpp | 37 -- kernel/src/Drivers/USB/UsbDevice.cpp | 74 ++- kernel/src/Drivers/USB/UserUsb.cpp | 381 ++++++++++++ kernel/src/Drivers/USB/UserUsb.hpp | 38 ++ kernel/src/Drivers/USB/Xhci.cpp | 54 +- kernel/src/Drivers/USB/Xhci.hpp | 7 +- kernel/src/Sched/Scheduler.cpp | 6 + programs/GNUmakefile | 15 +- programs/include/Api/Syscall.hpp | 94 +-- programs/include/libc/montauk.h | 99 ++- programs/include/montauk/syscall.h | 73 +-- programs/include/rtlsdr/rtlsdr.h | 42 ++ programs/man/rtlsdr.3 | 44 ++ programs/man/syscalls.2 | 90 +-- programs/src/rtlsdr/Makefile | 32 + .../src/rtlsdr/r820t.cpp | 41 +- programs/src/rtlsdr/r820t.hpp | 28 + programs/src/rtlsdr/rtlsdr.cpp | 491 +++++++++++++++ programs/src/sdr/Makefile | 22 + programs/src/sdr/main.cpp | 105 +++- template/sysroot/include/Api/Syscall.hpp | 94 +-- template/sysroot/include/libc/montauk.h | 99 ++- template/sysroot/include/montauk/syscall.h | 73 +-- template/sysroot/include/rtlsdr/rtlsdr.h | 36 ++ tests/Makefile | 11 +- tests/rtlsdr_r820t_test.cpp | 54 ++ 35 files changed, 1922 insertions(+), 1592 deletions(-) delete mode 100644 kernel/src/Api/Sdr.hpp create mode 100644 kernel/src/Api/Usb.hpp delete mode 100644 kernel/src/Drivers/Radio/Sdr.cpp delete mode 100644 kernel/src/Drivers/Radio/Sdr.hpp delete mode 100644 kernel/src/Drivers/USB/Radio/R820t.hpp delete mode 100644 kernel/src/Drivers/USB/Radio/RtlSdr.cpp delete mode 100644 kernel/src/Drivers/USB/Radio/RtlSdr.hpp create mode 100644 kernel/src/Drivers/USB/UserUsb.cpp create mode 100644 kernel/src/Drivers/USB/UserUsb.hpp create mode 100644 programs/include/rtlsdr/rtlsdr.h create mode 100644 programs/man/rtlsdr.3 create mode 100644 programs/src/rtlsdr/Makefile rename kernel/src/Drivers/USB/Radio/R820t.cpp => programs/src/rtlsdr/r820t.cpp (95%) create mode 100644 programs/src/rtlsdr/r820t.hpp create mode 100644 programs/src/rtlsdr/rtlsdr.cpp create mode 100644 programs/src/sdr/Makefile create mode 100644 template/sysroot/include/rtlsdr/rtlsdr.h create mode 100644 tests/rtlsdr_r820t_test.cpp diff --git a/kernel/src/Api/BuildNo.hpp b/kernel/src/Api/BuildNo.hpp index ceebd47..5378e53 100644 --- a/kernel/src/Api/BuildNo.hpp +++ b/kernel/src/Api/BuildNo.hpp @@ -12,4 +12,4 @@ #pragma once -#define MONTAUK_BUILD_NUMBER 151 +#define MONTAUK_BUILD_NUMBER 155 diff --git a/kernel/src/Api/Sdr.hpp b/kernel/src/Api/Sdr.hpp deleted file mode 100644 index ed8ec45..0000000 --- a/kernel/src/Api/Sdr.hpp +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Sdr.hpp - * Software-defined radio receive syscalls. - * SYS_SDR_COUNT / INFO / OPEN / CLOSE / START / STOP / READ / SETPARAM / GETPARAM - * Thin syscall layer over the generic SDR subsystem (Drivers::Radio::Sdr). - * Copyright (c) 2026 Daniel Hammer -*/ - -#pragma once -#include -#include - -#include "Syscall.hpp" - -namespace montauk::abi { - - static int64_t Sys_SdrCount() { - return (int64_t)Drivers::Radio::Sdr::Count(); - } - - static int64_t Sys_SdrInfo(int index, SdrDeviceInfo* out) { - if (!out) return -1; - return Drivers::Radio::Sdr::GetInfo(index, out) ? 0 : -1; - } - - static int64_t Sys_SdrOpen(int index) { - return (int64_t)Drivers::Radio::Sdr::Open(index); - } - - static int64_t Sys_SdrClose(int handle) { - return (int64_t)Drivers::Radio::Sdr::Close(handle); - } - - static int64_t Sys_SdrStart(int handle) { - return (int64_t)Drivers::Radio::Sdr::Start(handle); - } - - static int64_t Sys_SdrStop(int handle) { - return (int64_t)Drivers::Radio::Sdr::Stop(handle); - } - - static int64_t Sys_SdrRead(int handle, uint8_t* buf, uint32_t len) { - if (!buf) return -1; - return (int64_t)Drivers::Radio::Sdr::Read(handle, buf, len); - } - - static int64_t Sys_SdrSetParam(int handle, int param, uint64_t value) { - return (int64_t)Drivers::Radio::Sdr::SetParam(handle, param, value); - } - - static int64_t Sys_SdrGetParam(int handle, int param) { - return (int64_t)Drivers::Radio::Sdr::GetParam(handle, param); - } - -} diff --git a/kernel/src/Api/Syscall.cpp b/kernel/src/Api/Syscall.cpp index c392599..918ce3d 100644 --- a/kernel/src/Api/Syscall.cpp +++ b/kernel/src/Api/Syscall.cpp @@ -33,7 +33,7 @@ #include "Window.hpp" // SYS_WINCREATE, SYS_WINDESTROY, SYS_WINPRESENT, SYS_WINPOLL, SYS_WINENUM, SYS_WINMAP, SYS_WINSENDEVENT, SYS_WINRESIZE, SYS_WINSETCURSOR, SYS_WINSETFLAGS, SYS_WINSETSCALE, SYS_WINGETSCALE #include "Audio.hpp" // SYS_AUDIOOPEN, SYS_AUDIOCLOSE, SYS_AUDIOWRITE, SYS_AUDIOCTL #include "BluetoothSyscall.hpp" // SYS_BTSCAN, SYS_BTCONNECT, SYS_BTDISCONNECT, SYS_BTLIST, SYS_BTINFO -#include "Sdr.hpp" // SYS_SDR_COUNT, SYS_SDR_INFO, SYS_SDR_OPEN, SYS_SDR_CLOSE, SYS_SDR_START, SYS_SDR_STOP, SYS_SDR_READ, SYS_SDR_SETPARAM, SYS_SDR_GETPARAM +#include "Usb.hpp" // generic process-owned USB interface access #include "WifiSyscall.hpp" // SYS_WIFI_SCAN, SYS_WIFI_INFO, SYS_WIFI_CONNECT, SYS_WIFI_DISCONNECT #include "IpcSyscall.hpp" // SYS_DUPHANDLE, SYS_WAIT_HANDLE, SYS_STREAM_CREATE, SYS_STREAM_READ, SYS_STREAM_WRITE, SYS_MAILBOX_CREATE, SYS_MAILBOX_SEND, SYS_MAILBOX_RECV, SYS_WAITSET_CREATE, SYS_WAITSET_ADD, SYS_WAITSET_REMOVE, SYS_WAITSET_WAIT, SYS_PROC_OPEN, SYS_SURFACE_CREATE, SYS_SURFACE_MAP, SYS_SURFACE_RESIZE #include "LibSyscall.hpp" // SYS_LOAD_LIB, SYS_UNLOAD_LIB, SYS_DLSYM @@ -420,26 +420,41 @@ namespace montauk::abi { return Sys_AudioList((AudioStreamInfo*)frame->arg1, (int)frame->arg2); case SYS_AUDIOWAIT: return Sys_AudioWait(frame->arg1, frame->arg2); - case SYS_SDR_COUNT: - return Sys_SdrCount(); - case SYS_SDR_INFO: - if (!UserMemory::Writable(frame->arg2)) return -1; - return Sys_SdrInfo((int)frame->arg1, (SdrDeviceInfo*)frame->arg2); - case SYS_SDR_OPEN: - return Sys_SdrOpen((int)frame->arg1); - case SYS_SDR_CLOSE: - return Sys_SdrClose((int)frame->arg1); - case SYS_SDR_START: - return Sys_SdrStart((int)frame->arg1); - case SYS_SDR_STOP: - return Sys_SdrStop((int)frame->arg1); - case SYS_SDR_READ: - if (!UserMemory::Range(frame->arg2, frame->arg3, true)) return -1; - return Sys_SdrRead((int)frame->arg1, (uint8_t*)frame->arg2, (uint32_t)frame->arg3); - case SYS_SDR_SETPARAM: - return Sys_SdrSetParam((int)frame->arg1, (int)frame->arg2, frame->arg3); - case SYS_SDR_GETPARAM: - return Sys_SdrGetParam((int)frame->arg1, (int)frame->arg2); + case SYS_USB_LIST: { + if ((int64_t)frame->arg2 < 0) return USB_ERR_INVALID; + uint64_t maxCount = frame->arg2 > 16 ? 16 : frame->arg2; + if (!UserMemory::Range(frame->arg1, + maxCount * sizeof(UsbInterfaceInfo), true)) return USB_ERR_INVALID; + return Sys_UsbList((UsbInterfaceInfo*)frame->arg1, (int)maxCount); + } + case SYS_USB_CLAIM: + if (frame->arg1 == 0 || frame->arg1 > 16 || frame->arg2 > 255) + return USB_ERR_INVALID; + return Sys_UsbClaim((uint8_t)frame->arg1, (uint8_t)frame->arg2); + case SYS_USB_CLOSE: + return Sys_UsbClose((int)frame->arg1); + case SYS_USB_CONTROL: { + if (!UserMemory::Readable(frame->arg2)) return USB_ERR_INVALID; + UsbControlRequest request = *(const UsbControlRequest*)frame->arg2; + if (frame->arg4 > 4096 || frame->arg4 != request.length) return USB_ERR_INVALID; + bool deviceToHost = (request.requestType & 0x80) != 0; + if (frame->arg4 != 0 && + !UserMemory::Range(frame->arg3, frame->arg4, deviceToHost)) return USB_ERR_INVALID; + return Sys_UsbControl((int)frame->arg1, &request, + (void*)frame->arg3, (uint32_t)frame->arg4); + } + case SYS_USB_BULK_IN_START: + if (frame->arg2 > 0xffffffffULL || frame->arg3 > 0xffffffffULL) + return USB_ERR_INVALID; + return Sys_UsbBulkInStart((int)frame->arg1, (uint32_t)frame->arg2, + (uint32_t)frame->arg3); + case SYS_USB_BULK_IN_STOP: + return Sys_UsbBulkInStop((int)frame->arg1); + case SYS_USB_BULK_IN_READ: + if (frame->arg3 > 0xffffffffULL) return USB_ERR_INVALID; + if (!UserMemory::Range(frame->arg2, frame->arg3, true)) return USB_ERR_INVALID; + return Sys_UsbBulkInRead((int)frame->arg1, (uint8_t*)frame->arg2, + (uint32_t)frame->arg3); case SYS_POWERINFO: if (!UserMemory::Writable(frame->arg1)) return -1; return Sys_PowerInfo((PowerInfo*)frame->arg1); @@ -655,7 +670,7 @@ namespace montauk::abi { Kt::KernelLogStream(Kt::OK, "Syscall") << "SYSCALL/SYSRET initialized (LSTAR=" << kcp::hex << (uint64_t)SyscallEntry << kcp::dec << ", " - << (SYS_TERMINAL_ATTACHED + 1) << " syscall slots)"; + << (SYS_USB_BULK_IN_READ + 1) << " syscall slots)"; } } diff --git a/kernel/src/Api/Syscall.hpp b/kernel/src/Api/Syscall.hpp index 7ac5c31..e63d78c 100644 --- a/kernel/src/Api/Syscall.hpp +++ b/kernel/src/Api/Syscall.hpp @@ -275,16 +275,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; /* Power.hpp -- CPU power/thermal status */ static constexpr uint64_t SYS_POWERINFO = 149; // (PowerInfo*) -> 0, -1 unsupported @@ -327,17 +327,26 @@ namespace montauk::abi { static constexpr uint64_t SYS_SETENVIRON = 172; static constexpr uint64_t SYS_SPAWN_ENV = 173; - // 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 + /* Generic userspace USB interface access */ + static constexpr uint64_t SYS_USB_LIST = 178; // (UsbInterfaceInfo*, max) -> count + static constexpr uint64_t SYS_USB_CLAIM = 179; // (slot, interface) -> owned handle + static constexpr uint64_t SYS_USB_CLOSE = 180; // (handle) + static constexpr uint64_t SYS_USB_CONTROL = 181; // (handle, UsbControlRequest*, data, len) + static constexpr uint64_t SYS_USB_BULK_IN_START = 182; // (handle, transferBytes, buffers) + static constexpr uint64_t SYS_USB_BULK_IN_STOP = 183; // (handle) + static constexpr uint64_t SYS_USB_BULK_IN_READ = 184; // (handle, data, len) -> bytes - // Sample formats reported in SdrDeviceInfo.sampleFormat. - static constexpr uint8_t SDR_FORMAT_CU8 = 0; // 8-bit unsigned interleaved I/Q + + // Generic USB errors. Claims are restricted to interfaces without a + // bound in-kernel class driver and are owned by the claiming process. + 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, @@ -658,23 +667,40 @@ 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; - }; + // One USB interface currently represented by the xHCI device table. A + // nonzero kernelDriverBound interface cannot be claimed by userspace. + struct UsbInterfaceInfo { + uint8_t slotId; + uint8_t portId; + uint8_t speed; // xHCI speed ID + uint8_t interfaceNumber; + uint16_t vendorId; + uint16_t productId; + uint8_t deviceClass; + uint8_t interfaceClass; + uint8_t interfaceSubClass; + uint8_t interfaceProtocol; + uint8_t bulkInEndpoint; // USB address, including direction bit + uint8_t bulkOutEndpoint; + uint16_t bulkInMaxPacket; + uint16_t bulkOutMaxPacket; + uint8_t kernelDriverBound; + uint8_t claimed; + uint8_t _reserved[4]; + } __attribute__((packed)); + + // Standard USB setup packet fields. requestType bit 7 determines the data + // direction. length must match the data length passed to SYS_USB_CONTROL. + 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; diff --git a/kernel/src/Api/Usb.hpp b/kernel/src/Api/Usb.hpp new file mode 100644 index 0000000..9cde2fa --- /dev/null +++ b/kernel/src/Api/Usb.hpp @@ -0,0 +1,44 @@ +/* + * Usb.hpp + * Generic userspace USB interface syscall layer. + * Copyright (c) 2026 Daniel Hammer +*/ + +#pragma once + +#include + +namespace montauk::abi { + + static int64_t Sys_UsbList(UsbInterfaceInfo* out, int maxCount) { + return Drivers::USB::UserUsb::List(out, maxCount); + } + + static int64_t Sys_UsbClaim(uint8_t slotId, uint8_t interfaceNumber) { + return Drivers::USB::UserUsb::Claim(slotId, interfaceNumber); + } + + static int64_t Sys_UsbClose(int handle) { + return Drivers::USB::UserUsb::Close(handle); + } + + static int64_t Sys_UsbControl(int handle, const UsbControlRequest* request, + void* data, uint32_t dataLen) { + if (!request) return USB_ERR_INVALID; + return Drivers::USB::UserUsb::Control(handle, *request, data, dataLen); + } + + static int64_t Sys_UsbBulkInStart(int handle, uint32_t transferBytes, + uint32_t bufferCount) { + return Drivers::USB::UserUsb::StartBulkIn(handle, transferBytes, bufferCount); + } + + static int64_t Sys_UsbBulkInStop(int handle) { + return Drivers::USB::UserUsb::StopBulkIn(handle); + } + + static int64_t Sys_UsbBulkInRead(int handle, uint8_t* out, uint32_t maxLen) { + return Drivers::USB::UserUsb::ReadBulkIn(handle, out, maxLen); + } + +} diff --git a/kernel/src/Drivers/Radio/Sdr.cpp b/kernel/src/Drivers/Radio/Sdr.cpp deleted file mode 100644 index f8cb1c2..0000000 --- a/kernel/src/Drivers/Radio/Sdr.cpp +++ /dev/null @@ -1,359 +0,0 @@ -/* - * Sdr.cpp - * Generic software-defined radio receive subsystem. - * Copyright (c) 2026 Daniel Hammer -*/ - -#include "Sdr.hpp" -#include -#include -#include -#include - -using namespace Kt; - -namespace Drivers::Radio::Sdr { - - // I/Q ring size per receiver. 256 KiB is ~62 ms of jitter buffer at - // 2.048 Msps (2 bytes/sample), which comfortably absorbs scheduling gaps - // between a userspace reader's polls. - static constexpr uint32_t RING_BYTES = 256 * 1024; - - struct Receiver { - bool used; - bool opened; - bool streaming; - - char name[64]; - char tuner[32]; - char serial[32]; - uint64_t freqMin, freqMax; - uint32_t sampleRateMin, sampleRateMax; - int gains[MAX_GAINS]; - uint32_t numGains; - uint8_t format; - - ReceiverOps ops; - void* ctx; - - // Last-requested configuration (cached for GETPARAM readback). - uint64_t freq; - uint32_t sampleRate; - int gainMode; // 0 = auto, 1 = manual - int gain; // tenths of dB - int ppm; - int agc; - int directSamp; - - // I/Q ring buffer (byte FIFO). - uint8_t* ring; - uint32_t head; // write position - uint32_t count; // bytes currently queued - uint64_t totalBytes; // lifetime sample bytes delivered - uint64_t droppedBytes; // bytes dropped on overflow - kcp::Spinlock lock; - }; - - static Receiver g_rx[MAX_RECEIVERS]; - - // ------------------------------------------------------------------------- - // Helpers - // ------------------------------------------------------------------------- - - static void CopyStr(char* dst, uint32_t cap, const char* src) { - uint32_t i = 0; - if (src) { - for (; i < cap - 1 && src[i]; i++) dst[i] = src[i]; - } - dst[i] = '\0'; - } - - static Receiver* Lookup(int handle, bool needOpen) { - if (handle < 0 || handle >= MAX_RECEIVERS) return nullptr; - Receiver& r = g_rx[handle]; - if (!r.used) return nullptr; - if (needOpen && !r.opened) return nullptr; - return &r; - } - - // ------------------------------------------------------------------------- - // Driver-facing API - // ------------------------------------------------------------------------- - - int Register(const ReceiverDesc& desc) { - for (int i = 0; i < MAX_RECEIVERS; i++) { - if (g_rx[i].used) continue; - Receiver& r = g_rx[i]; - - // Reset everything except the (non-copyable) spinlock instance. - r.opened = false; - r.streaming = false; - CopyStr(r.name, sizeof(r.name), desc.name); - CopyStr(r.tuner, sizeof(r.tuner), desc.tuner); - CopyStr(r.serial, sizeof(r.serial), desc.serial); - r.freqMin = desc.freqMin; - r.freqMax = desc.freqMax; - r.sampleRateMin = desc.sampleRateMin; - r.sampleRateMax = desc.sampleRateMax; - r.numGains = desc.numGains > MAX_GAINS ? MAX_GAINS : desc.numGains; - for (uint32_t g = 0; g < r.numGains; g++) r.gains[g] = desc.gains[g]; - r.format = desc.format; - r.ops = desc.ops; - r.ctx = desc.ctx; - - r.freq = (desc.freqMin + desc.freqMax) / 2; - r.sampleRate = desc.sampleRateMax; - r.gainMode = 0; - r.gain = 0; - r.ppm = 0; - r.agc = 0; - r.directSamp = 0; - - r.ring = nullptr; - r.head = r.count = 0; - r.totalBytes = r.droppedBytes = 0; - - r.used = true; // publish last - KernelLogStream(OK, "SDR") << "Registered receiver " << (uint64_t)i - << ": " << r.name << " / " << r.tuner; - return i; - } - KernelLogStream(WARNING, "SDR") << "No free receiver slot for " << desc.name; - return -1; - } - - void Unregister(int idx) { - if (idx < 0 || idx >= MAX_RECEIVERS) return; - Receiver& r = g_rx[idx]; - if (!r.used) return; - - if (r.streaming && r.ops.Stop) r.ops.Stop(r.ctx); - - r.lock.Acquire(); - r.streaming = false; - r.opened = false; - r.used = false; - uint8_t* ring = r.ring; - r.ring = nullptr; - r.head = r.count = 0; - r.lock.Release(); - - if (ring) Memory::g_heap->Free(ring); - KernelLogStream(INFO, "SDR") << "Unregistered receiver " << (uint64_t)idx; - } - - void PushSamples(int idx, const uint8_t* data, uint32_t len) { - if (idx < 0 || idx >= MAX_RECEIVERS || !data || len == 0) return; - Receiver& r = g_rx[idx]; - - r.lock.Acquire(); - // Re-validate under the lock: Unregister() clears these and frees the - // ring while holding the same lock, so an in-flight USB completion can - // never write into a freed buffer. - if (!r.used || !r.ring) { r.lock.Release(); return; } - - uint32_t space = RING_BYTES - r.count; - uint32_t n = len; - uint32_t dropped = 0; - if (n > space) { - // Truncate to a whole number of I/Q byte pairs: dropping an odd - // count would swap I and Q for the rest of the stream. - n = space & ~1u; - dropped = len - n; - } - - uint32_t first = RING_BYTES - r.head; - if (first > n) first = n; - memcpy(r.ring + r.head, data, first); - if (n > first) memcpy(r.ring, data + first, n - first); - - r.head = (r.head + n) % RING_BYTES; - r.count += n; - r.totalBytes += n; - r.droppedBytes += dropped; - r.lock.Release(); - } - - bool IsStreaming(int idx) { - if (idx < 0 || idx >= MAX_RECEIVERS) return false; - return g_rx[idx].used && g_rx[idx].streaming; - } - - // ------------------------------------------------------------------------- - // Syscall-facing API - // ------------------------------------------------------------------------- - - int Count() { - int n = 0; - for (int i = 0; i < MAX_RECEIVERS; i++) if (g_rx[i].used) n++; - return n; - } - - bool GetInfo(int idx, montauk::abi::SdrDeviceInfo* out) { - Receiver* r = Lookup(idx, false); - if (!r || !out) return false; - - memset(out, 0, sizeof(*out)); - CopyStr(out->name, sizeof(out->name), r->name); - CopyStr(out->tuner, sizeof(out->tuner), r->tuner); - CopyStr(out->serial, sizeof(out->serial), r->serial); - out->freqMin = r->freqMin; - out->freqMax = r->freqMax; - out->sampleRateMin = r->sampleRateMin; - out->sampleRateMax = r->sampleRateMax; - out->numGains = r->numGains; - for (uint32_t g = 0; g < r->numGains && g < 32; g++) out->gains[g] = r->gains[g]; - out->sampleFormat = r->format; - out->present = 1; - out->streaming = r->streaming ? 1 : 0; - return true; - } - - int Open(int idx) { - Receiver* r = Lookup(idx, false); - if (!r) return -1; - - // Single-user OS: an Open always claims the device, reclaiming it from a - // previous owner that exited without closing. - if (r->streaming && r->ops.Stop) r->ops.Stop(r->ctx); - - if (!r->ring) { - r->ring = (uint8_t*)Memory::g_heap->Request(RING_BYTES); - if (!r->ring) { - KernelLogStream(ERROR, "SDR") << "Ring alloc failed for receiver " - << (uint64_t)idx; - return -1; - } - } - - r->lock.Acquire(); - r->head = r->count = 0; - r->lock.Release(); - r->streaming = false; - r->opened = true; - return idx; // handle == index - } - - int Close(int handle) { - Receiver* r = Lookup(handle, true); - if (!r) return -1; - if (r->streaming && r->ops.Stop) r->ops.Stop(r->ctx); - r->streaming = false; - r->opened = false; - return 0; - } - - int Start(int handle) { - Receiver* r = Lookup(handle, true); - if (!r) return -1; - // Already streaming: a second Start must not re-arm the driver's - // transfer pool (it would double-queue every buffer). - if (r->streaming) return 0; - - r->lock.Acquire(); - r->head = r->count = 0; // discard stale samples before (re)starting - r->lock.Release(); - - int rc = r->ops.Start ? r->ops.Start(r->ctx) : -1; - if (rc == 0) r->streaming = true; - return rc; - } - - int Stop(int handle) { - Receiver* r = Lookup(handle, true); - if (!r) return -1; - int rc = r->ops.Stop ? r->ops.Stop(r->ctx) : 0; - r->streaming = false; - return rc; - } - - int Read(int handle, uint8_t* buf, uint32_t len) { - Receiver* r = Lookup(handle, true); - if (!r || !buf || !r->ring) return -1; - if (len == 0) return 0; - - // Give the driver a process-context tick (e.g. USB stall recovery) - // before draining; do this outside the ring lock since it may issue - // blocking USB commands. - if (r->streaming && r->ops.Service) r->ops.Service(r->ctx); - - r->lock.Acquire(); - uint32_t n = r->count < len ? r->count : len; - uint32_t tail = (r->head + RING_BYTES - r->count) % RING_BYTES; - uint32_t first = RING_BYTES - tail; - if (first > n) first = n; - memcpy(buf, r->ring + tail, first); - if (n > first) memcpy(buf + first, r->ring, n - first); - r->count -= n; - r->lock.Release(); - return (int)n; - } - - uint32_t Available(int handle) { - Receiver* r = Lookup(handle, true); - if (!r) return 0; - return r->count; - } - - int64_t SetParam(int handle, int param, uint64_t value) { - Receiver* r = Lookup(handle, true); - if (!r) return -1; - - switch (param) { - case montauk::abi::SDR_PARAM_FREQ: - if (!r->ops.SetFreq) return -1; - if (r->ops.SetFreq(r->ctx, value) != 0) return -1; - r->freq = value; - return 0; - case montauk::abi::SDR_PARAM_SAMPLE_RATE: - if (!r->ops.SetSampleRate) return -1; - if (r->ops.SetSampleRate(r->ctx, (uint32_t)value) != 0) return -1; - r->sampleRate = (uint32_t)value; - return 0; - case montauk::abi::SDR_PARAM_GAIN_MODE: - if (!r->ops.SetGainMode) return -1; - if (r->ops.SetGainMode(r->ctx, (int)value) != 0) return -1; - r->gainMode = (int)value ? 1 : 0; - return 0; - case montauk::abi::SDR_PARAM_GAIN: - if (!r->ops.SetGain) return -1; - if (r->ops.SetGain(r->ctx, (int)(int64_t)value) != 0) return -1; - r->gain = (int)(int64_t)value; - return 0; - case montauk::abi::SDR_PARAM_FREQ_CORR: - if (!r->ops.SetFreqCorrection) return -1; - if (r->ops.SetFreqCorrection(r->ctx, (int)(int64_t)value) != 0) return -1; - r->ppm = (int)(int64_t)value; - return 0; - case montauk::abi::SDR_PARAM_AGC: - if (!r->ops.SetAgc) return -1; - if (r->ops.SetAgc(r->ctx, (int)value) != 0) return -1; - r->agc = (int)value ? 1 : 0; - return 0; - case montauk::abi::SDR_PARAM_DIRECT_SAMP: - if (!r->ops.SetDirectSampling) return -1; - if (r->ops.SetDirectSampling(r->ctx, (int)value) != 0) return -1; - r->directSamp = (int)value; - return 0; - default: - return -1; - } - } - - int64_t GetParam(int handle, int param) { - Receiver* r = Lookup(handle, true); - if (!r) return -1; - - switch (param) { - case montauk::abi::SDR_PARAM_FREQ: return (int64_t)r->freq; - case montauk::abi::SDR_PARAM_SAMPLE_RATE: return (int64_t)r->sampleRate; - case montauk::abi::SDR_PARAM_GAIN_MODE: return r->gainMode; - case montauk::abi::SDR_PARAM_GAIN: return r->gain; - case montauk::abi::SDR_PARAM_FREQ_CORR: return r->ppm; - case montauk::abi::SDR_PARAM_AGC: return r->agc; - case montauk::abi::SDR_PARAM_DIRECT_SAMP: return r->directSamp; - default: return -1; - } - } - -} diff --git a/kernel/src/Drivers/Radio/Sdr.hpp b/kernel/src/Drivers/Radio/Sdr.hpp deleted file mode 100644 index 3fedd04..0000000 --- a/kernel/src/Drivers/Radio/Sdr.hpp +++ /dev/null @@ -1,119 +0,0 @@ -/* - * Sdr.hpp - * Generic software-defined radio (SDR) receive subsystem. - * - * Hardware-agnostic registry of radio receivers. A concrete driver (e.g. the - * RTL-SDR USB driver) registers itself as a receiver by supplying an ops table - * and a private context pointer; it then pushes demodulated baseband I/Q - * samples into a per-receiver ring buffer via PushSamples(). Userspace reaches - * this layer through the SYS_SDR_* syscalls and drains the ring with Read(). - * - * The native sample format is CU8 -- 8-bit unsigned interleaved I/Q -- which is - * what the RTL2832U produces; other formats can be advertised per receiver via - * SdrDeviceInfo.sampleFormat. - * Copyright (c) 2026 Daniel Hammer -*/ - -#pragma once -#include -#include -#include - -namespace Drivers::Radio::Sdr { - - static constexpr int MAX_RECEIVERS = 4; - static constexpr int MAX_GAINS = 32; - - // ------------------------------------------------------------------------- - // Receiver ops table -- implemented by a concrete driver. - // All calls happen in process/syscall context (never from the sample - // callback), so they may block on USB control transfers. Each returns 0 on - // success, negative on error. ctx is the receiver's private pointer. - // ------------------------------------------------------------------------- - - struct ReceiverOps { - int (*SetFreq)(void* ctx, uint64_t hz); - int (*SetSampleRate)(void* ctx, uint32_t hz); - int (*SetGainMode)(void* ctx, int manual); // 0 = auto/AGC, 1 = manual - int (*SetGain)(void* ctx, int tenthsDb); - int (*SetFreqCorrection)(void* ctx, int ppm); - int (*SetAgc)(void* ctx, int on); // demod digital AGC - int (*SetDirectSampling)(void* ctx, int mode); // 0=off,1=I,2=Q - int (*Start)(void* ctx); // arm streaming - int (*Stop)(void* ctx); // halt streaming - // Optional: process-context housekeeping invoked from Read() while - // streaming (e.g. USB stall recovery that cannot run in the ISR). May - // be null. - void (*Service)(void* ctx); - }; - - // Static description a driver supplies at registration time. - struct ReceiverDesc { - const char* name; // e.g. "Realtek RTL2832U" - const char* tuner; // e.g. "Rafael Micro R820T2" - const char* serial; // bus location / serial string (may be null) - uint64_t freqMin; // Hz - uint64_t freqMax; // Hz - uint32_t sampleRateMin; - uint32_t sampleRateMax; - const int* gains; // table of tenths-of-dB gain steps (may be null) - uint32_t numGains; - uint8_t format; // montauk::abi::SDR_FORMAT_* - ReceiverOps ops; - void* ctx; - }; - - // ========================================================================= - // Driver-facing API - // ========================================================================= - - // Register a receiver. Returns its index [0, MAX_RECEIVERS) or -1 if full. - int Register(const ReceiverDesc& desc); - - // Remove a receiver (e.g. on USB unplug). Stops streaming and frees the - // ring. Safe to call with an out-of-range / already-removed index. - void Unregister(int idx); - - // Push baseband sample bytes into a receiver's ring buffer. Called from the - // driver's USB completion callback (possibly interrupt context); never - // allocates or blocks. Bytes that do not fit are dropped (counted). - void PushSamples(int idx, const uint8_t* data, uint32_t len); - - // True if the receiver is currently in the streaming state (used by drivers - // to decide whether to re-arm USB transfers). - bool IsStreaming(int idx); - - // ========================================================================= - // Syscall-facing API - // ========================================================================= - - // Number of registered receivers. - int Count(); - - // Fill out an info struct for receiver idx. Returns false if idx invalid. - bool GetInfo(int idx, montauk::abi::SdrDeviceInfo* out); - - // Claim a receiver for use. Returns a handle (== idx) or -1 on failure. - int Open(int idx); - - // Release a receiver (stops streaming). Returns 0 on success. - int Close(int handle); - - // Begin / end sample delivery. Returns 0 on success, negative on error. - int Start(int handle); - int Stop(int handle); - - // Copy up to len bytes of buffered I/Q out of the ring. Non-blocking; - // returns the number of bytes copied (0 when nothing is queued). - int Read(int handle, uint8_t* buf, uint32_t len); - - // Number of sample bytes currently queued in the ring. - uint32_t Available(int handle); - - // Set / get a tunable parameter (montauk::abi::SDR_PARAM_*). SetParam - // returns 0 on success; GetParam returns the cached value or negative on - // error. - int64_t SetParam(int handle, int param, uint64_t value); - int64_t GetParam(int handle, int param); - -} diff --git a/kernel/src/Drivers/USB/Radio/R820t.hpp b/kernel/src/Drivers/USB/Radio/R820t.hpp deleted file mode 100644 index 25be5c1..0000000 --- a/kernel/src/Drivers/USB/Radio/R820t.hpp +++ /dev/null @@ -1,61 +0,0 @@ -/* - * R820t.hpp - * Rafael Micro R820T / R820T2 silicon tuner. - * - * The tuner sits on the RTL2832U's I2C bus; all register traffic is carried - * by the demod's I2C repeater (managed by the RtlSdr layer, which enables the - * repeater around every call here). This module owns the tuner-side logic: - * the init register array, the PLL/VCO frequency synthesis, the RF tracking - * filter / mux band selection, and the LNA/Mixer/VGA gain stages. - * - * Register/algorithm facts follow the publicly documented R820T programming - * model (as used by osmocom rtl-sdr); the implementation here is original. - * Copyright (c) 2026 Daniel Hammer -*/ - -#pragma once -#include - -namespace Drivers::USB::Radio { - - // I2C bus address of the tuner on the RTL2832U (8-bit form). - static constexpr uint8_t R820T_I2C_ADDR = 0x34; - // Chip-id register (reg 0) reads back this value for an R820T/R820T2. - static constexpr uint8_t R820T_CHECK_VAL = 0x69; - - // First writable register; the 27-entry shadow covers regs 0x05..0x1f. - static constexpr uint8_t R820T_REG_SHADOW_START = 5; - static constexpr uint8_t R820T_NUM_REGS = 27; - - struct R820tDev { - uint8_t slotId; - uint32_t xtal; // reference crystal, Hz (28.8 MHz on RTL-SDR) - uint32_t intFreq; // IF the demod expects the signal at, Hz (3.57 MHz) - uint8_t regs[32]; // register shadow (index == register number) - bool hasLock; // PLL lock state after the last tune - bool inited; - }; - - // Detect an R820T/R820T2 on the demod I2C bus. The caller must have the - // demod's I2C repeater enabled. Returns true if the chip id matches. - bool R820tDetect(uint8_t slotId); - - // Initialise the tuner (writes the init register array + base setup). The - // caller must have the I2C repeater enabled. Returns true on success. - bool R820tInit(R820tDev& d, uint8_t slotId, uint32_t xtal, uint32_t intFreq); - - // Tune to an RF center frequency (Hz). Programs the RF mux band and the PLL - // for an LO of rfHz + intFreq. Updates d.hasLock. Repeater must be on. - bool R820tSetFreq(R820tDev& d, uint64_t rfHz); - - // Configure gain. manual==0 puts LNA/mixer in AGC; manual!=0 selects the - // closest fixed gain to tenthsDb from the LNA+mixer step tables. - bool R820tSetGain(R820tDev& d, int manual, int tenthsDb); - - // Put the tuner into standby (mute / power down). - void R820tStandby(R820tDev& d); - - // The discrete gain table (tenths of dB), for advertising to userspace. - const int* R820tGainTable(int* count); - -} diff --git a/kernel/src/Drivers/USB/Radio/RtlSdr.cpp b/kernel/src/Drivers/USB/Radio/RtlSdr.cpp deleted file mode 100644 index f91714c..0000000 --- a/kernel/src/Drivers/USB/Radio/RtlSdr.cpp +++ /dev/null @@ -1,575 +0,0 @@ -/* - * RtlSdr.cpp - * Realtek RTL2832U + R820T2 SDR receiver driver. - * Copyright (c) 2026 Daniel Hammer -*/ - -#include "RtlSdr.hpp" -#include "R820t.hpp" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -using namespace Kt; - -namespace Drivers::USB::Radio { - - // ========================================================================= - // 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; // 28.8 MHz reference - static constexpr uint32_t R82XX_IF = 3570000; // IF the demod expects - static constexpr uint32_t TWO_POW22 = 1u << 22; - - // ========================================================================= - // Driver state (single instance -- the common RTL-SDR case) - // ========================================================================= - - static bool g_present = false; - static bool g_hwInited = false; - static bool g_streaming = false; - static uint8_t g_slotId = 0; - static int g_rxIndex = -1; - - static uint8_t* g_ctlBuf = nullptr; // HHDM page for control transfers - static kcp::Mutex g_ctlLock; // serialises register access - - static R820tDev g_tuner{}; - static uint32_t g_rtlXtal = RTL_XTAL; // adjusted by ppm correction - static int g_ppm = 0; - static int g_manual = 0; // tuner gain mode (0=auto) - static int g_gain = 0; // tuner gain, tenths of dB - static int g_directSamp = 0; // 0=tuner path, 1=I ADC, 2=Q ADC - static uint64_t g_lastFreq = 0; // last successfully tuned freq (Hz) - - // Bulk-IN streaming geometry. We keep BULK_POOL_BUFS transfers of - // BULK_XFER_LEN bytes outstanding at once (multi-URB), so the RTL2832U FIFO - // always has a TRB to DMA into and never overflows in the window between a - // completion and its re-arm -- the single-outstanding scheme dropped ~88% of - // samples at 2.048 Msps for exactly that reason. 4 KiB == one DMA page; - // 16 x 4 KiB == 64 KiB in flight, ~16 ms of slack at 4 MB/s. - static constexpr uint32_t BULK_XFER_LEN = 4096; - static constexpr uint32_t BULK_POOL_BUFS = 16; - - // Set by the bulk-IN completion callback when the endpoint halts (cc=6 - // STALL etc.). Recovery (Reset Endpoint) needs a command wait and so must - // run in process context -- serviced from Read() via OpService(). - static std::atomic g_bulkStalled{false}; - - // ========================================================================= - // Low-level register access (control transfers via EP0) - // ========================================================================= - - 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 Xhci::ControlTransfer(g_slotId, CTRL_OUT, 0, addr, index, len, - g_ctlBuf, false) == Xhci::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; - Xhci::ControlTransfer(g_slotId, 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 = Xhci::ControlTransfer(g_slotId, CTRL_OUT, 0, waddr, index, len, - g_ctlBuf, false) == Xhci::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(uint8_t slotId, 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 = Xhci::ControlTransfer(slotId, CTRL_OUT, 0, i2cAddr, index, len, - g_ctlBuf, false); - if (cc != Xhci::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 == Xhci::CC_SUCCESS; - } - - bool RtlI2cRead(uint8_t slotId, 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 = Xhci::ControlTransfer(slotId, CTRL_IN, 0, i2cAddr, index, len, - g_ctlBuf, true); - if (cc != Xhci::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_slotId); - bool ok = detected && R820tInit(g_tuner, g_slotId, 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_present || !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_slotId; - 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_slotId, 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 void TransferCallback(uint8_t slotId, uint8_t epDci, - const uint8_t* data, uint32_t length, - uint32_t /*completionCode*/) { - if (slotId != g_slotId) return; - auto* dev = Xhci::GetDevice(slotId); - if (!dev) return; - - uint8_t bulkInDci = dev->BulkInEpNum ? (uint8_t)(dev->BulkInEpNum * 2 + 1) : 0; - if (epDci != bulkInDci || !g_streaming) return; - - if (data) { - // Deliver samples only. The xHCI layer owns the multi-buffer pool - // (StartBulkInStream) and re-arms this very buffer automatically once - // we return; re-queuing here would double-arm the pool and lap the - // ring. PushSamples copies out synchronously, so the buffer is free - // to be re-armed the instant this returns. - if (length > 0) - Drivers::Radio::Sdr::PushSamples(g_rxIndex, data, length); - } else { - // Error (data==nullptr), e.g. cc=6 STALL: the endpoint is halted. - // Clearing it requires a Reset Endpoint command that waits on the - // event ring, which is unsafe here (we are inside PollEvents). - // Flag it for process-context recovery in OpService(), which resets - // the endpoint and re-primes the whole pool. - g_bulkStalled.store(true, std::memory_order_relaxed); - } - } - - static int DoStart() { - if (!EnsureInit()) return -1; - - // Reset endpoint-A FIFO so streaming starts on a clean boundary. - RegWrite(BLOCK_USB, USB_EPA_CTL, 0x1002, 2); // hold + reset - RegWrite(BLOCK_USB, USB_EPA_CTL, 0x0000, 2); // release - - g_bulkStalled.store(false, std::memory_order_relaxed); - g_streaming = true; - Xhci::RegisterTransferCallback(g_slotId, TransferCallback); - - auto* dev = Xhci::GetDevice(g_slotId); - if (dev && dev->BulkInEpNum) - Xhci::StartBulkInStream(g_slotId, BULK_XFER_LEN, BULK_POOL_BUFS); - return 0; - } - - static int DoStop() { - g_streaming = false; - g_bulkStalled.store(false, std::memory_order_relaxed); - // Disarm the multi-buffer rotation so no further transfers re-arm, then - // hold/reset the FIFO so the device stops producing samples. - Xhci::StopBulkInStream(g_slotId); - if (g_ctlBuf) RegWrite(BLOCK_USB, USB_EPA_CTL, 0x1002, 2); - return 0; - } - - // Process-context housekeeping called from Read(): recover a halted bulk-IN - // endpoint (Reset Endpoint + Set TR Dequeue) and re-arm reception. - static void DoService() { - if (!g_bulkStalled.load(std::memory_order_relaxed)) return; - g_ctlLock.Acquire(); - if (g_streaming) { - Xhci::ResetBulkInEndpoint(g_slotId); - Xhci::PrimeBulkInStream(g_slotId); - } - g_bulkStalled.store(false, std::memory_order_relaxed); - g_ctlLock.Release(); - - static uint32_t recoveries = 0; - if (recoveries < 5) { - recoveries++; - KernelLogStream(INFO, "RTL-SDR") << "bulk IN stall recovered (" - << (uint64_t)recoveries << ")"; - } - } - - // ========================================================================= - // Ops table wrappers (lock the control path) - // ========================================================================= - - static int OpSetFreq(void*, uint64_t hz) { - g_ctlLock.Acquire(); int r = DoSetFreq(hz); g_ctlLock.Release(); return r; - } - static int OpSetSampleRate(void*, uint32_t hz) { - g_ctlLock.Acquire(); int r = DoSetSampleRate(hz); g_ctlLock.Release(); return r; - } - static int OpSetGainMode(void*, int manual) { - g_ctlLock.Acquire(); int r = DoSetGainMode(manual); g_ctlLock.Release(); return r; - } - static int OpSetGain(void*, int tenths) { - g_ctlLock.Acquire(); int r = DoSetGain(tenths); g_ctlLock.Release(); return r; - } - static int OpSetFreqCorrection(void*, int ppm) { - g_ctlLock.Acquire(); int r = DoSetFreqCorrection(ppm); g_ctlLock.Release(); return r; - } - static int OpSetAgc(void*, int on) { - g_ctlLock.Acquire(); int r = DoSetAgc(on); g_ctlLock.Release(); return r; - } - static int OpSetDirectSampling(void*, int mode) { - g_ctlLock.Acquire(); int r = DoSetDirectSampling(mode); g_ctlLock.Release(); return r; - } - static int OpStart(void*) { - g_ctlLock.Acquire(); int r = DoStart(); g_ctlLock.Release(); return r; - } - static int OpStop(void*) { - g_ctlLock.Acquire(); int r = DoStop(); g_ctlLock.Release(); return r; - } - // DoService does its own locking (around the reset), so OpService must not - // take g_ctlLock here. - static void OpService(void*) { DoService(); } - - // ========================================================================= - // USB enumeration hooks - // ========================================================================= - - bool IsRtlSdr(uint16_t vid, uint16_t pid) { - if (vid != 0x0bda) return false; // Realtek Semiconductor - switch (pid) { - // Only the two known RTL2832U ids. In particular 0x2831 is the - // RTL2831U, a DIFFERENT demod this driver cannot program. - case 0x2832: // RTL2832U (generic) - case 0x2838: // RTL2838 (most RTL-SDR.com dongles) - return true; - default: - return false; - } - } - - void RegisterDevice(uint8_t slotId) { - if (g_present) { - KernelLogStream(WARNING, "RTL-SDR") - << "second RTL-SDR ignored (single instance), slot " << (uint64_t)slotId; - return; - } - - g_slotId = slotId; - g_present = true; - g_hwInited = false; - g_streaming = false; - g_ppm = 0; - g_rtlXtal = RTL_XTAL; - g_manual = 0; - g_gain = 0; - g_directSamp = 0; - g_lastFreq = 0; - g_tuner = R820tDev{}; - - g_ctlBuf = (uint8_t*)Memory::g_pfa->AllocateZeroed(); - if (!g_ctlBuf) { - KernelLogStream(ERROR, "RTL-SDR") << "control buffer alloc failed"; - g_present = false; - return; - } - - int gainCount = 0; - const int* gains = R820tGainTable(&gainCount); - - Drivers::Radio::Sdr::ReceiverDesc desc{}; - desc.name = "Realtek RTL2832U"; - desc.tuner = "Rafael Micro R820T2"; - desc.serial = "USB RTL-SDR"; - desc.freqMin = 24000000ull; - desc.freqMax = 1766000000ull; - desc.sampleRateMin = 225001; - desc.sampleRateMax = 3200000; - desc.gains = gains; - desc.numGains = (uint32_t)gainCount; - desc.format = montauk::abi::SDR_FORMAT_CU8; - desc.ops.SetFreq = OpSetFreq; - desc.ops.SetSampleRate = OpSetSampleRate; - desc.ops.SetGainMode = OpSetGainMode; - desc.ops.SetGain = OpSetGain; - desc.ops.SetFreqCorrection = OpSetFreqCorrection; - desc.ops.SetAgc = OpSetAgc; - desc.ops.SetDirectSampling = OpSetDirectSampling; - desc.ops.Start = OpStart; - desc.ops.Stop = OpStop; - desc.ops.Service = OpService; - desc.ctx = nullptr; - - g_rxIndex = Drivers::Radio::Sdr::Register(desc); - if (g_rxIndex < 0) { - KernelLogStream(ERROR, "RTL-SDR") << "SDR registration failed"; - Memory::g_pfa->Free(g_ctlBuf); - g_ctlBuf = nullptr; - g_present = false; - return; - } - - KernelLogStream(OK, "RTL-SDR") << "RTL-SDR on slot " << (uint64_t)slotId - << " registered as receiver " << (uint64_t)g_rxIndex; - } - - void UnregisterDevice(uint8_t slotId) { - if (!g_present || slotId != g_slotId) return; - - g_streaming = false; - if (g_rxIndex >= 0) Drivers::Radio::Sdr::Unregister(g_rxIndex); - g_rxIndex = -1; - g_present = false; - g_hwInited = false; - - if (g_ctlBuf) { - Memory::g_pfa->Free(g_ctlBuf); - g_ctlBuf = nullptr; - } - KernelLogStream(INFO, "RTL-SDR") << "RTL-SDR removed from slot " << (uint64_t)slotId; - } - -} diff --git a/kernel/src/Drivers/USB/Radio/RtlSdr.hpp b/kernel/src/Drivers/USB/Radio/RtlSdr.hpp deleted file mode 100644 index da11765..0000000 --- a/kernel/src/Drivers/USB/Radio/RtlSdr.hpp +++ /dev/null @@ -1,37 +0,0 @@ -/* - * RtlSdr.hpp - * Realtek RTL2832U + R820T2 software-defined-radio receiver (RTL-SDR). - * - * The RTL2832U is a DVB-T demodulator that, in raw mode, streams 8-bit - * unsigned I/Q samples over a USB bulk-IN endpoint. This driver brings up the - * demodulator + R820T2 tuner, configures the resampler / IF, and feeds the - * bulk-IN samples into the generic SDR receive subsystem (Drivers::Radio::Sdr), - * which userspace reaches through the SYS_SDR_* syscalls. - * Copyright (c) 2026 Daniel Hammer -*/ - -#pragma once -#include - -namespace Drivers::USB::Radio { - - // True if a USB VID:PID identifies a supported RTL2832U-based SDR dongle. - bool IsRtlSdr(uint16_t vid, uint16_t pid); - - // Called by USB enumeration once the bulk-IN endpoint has been configured. - // Registers a receiver with the SDR subsystem; the demod/tuner are brought - // up lazily on first use (in process context, never from the USB poll path). - void RegisterDevice(uint8_t slotId); - - // Tear down on unplug. - void UnregisterDevice(uint8_t slotId); - - // ------------------------------------------------------------------------- - // I2C facade used by the R820T2 tuner module. These carry the tuner's - // register traffic over the demod's I2C block. The control-transfer mutex - // is held by the calling op wrapper, so these do not lock themselves. - // ------------------------------------------------------------------------- - bool RtlI2cWrite(uint8_t slotId, uint8_t i2cAddr, const uint8_t* buf, uint8_t len); - bool RtlI2cRead(uint8_t slotId, uint8_t i2cAddr, uint8_t* buf, uint8_t len); - -} diff --git a/kernel/src/Drivers/USB/UsbDevice.cpp b/kernel/src/Drivers/USB/UsbDevice.cpp index d94909d..3ae2f8c 100644 --- a/kernel/src/Drivers/USB/UsbDevice.cpp +++ b/kernel/src/Drivers/USB/UsbDevice.cpp @@ -10,7 +10,6 @@ #include "HidMouse.hpp" #include "MassStorage.hpp" #include "Bluetooth/Bluetooth.hpp" -#include "Radio/RtlSdr.hpp" #include #include #include @@ -351,11 +350,6 @@ namespace Drivers::USB::UsbDevice { bool foundBulkOut = false; uint16_t hidReportDescLen = 0; - // RTL-SDR dongles are vendor-class (0xFF) devices identified by VID:PID. - // They expose a single bulk-IN endpoint that streams raw I/Q samples; - // treat them like the other bulk-capable class drivers below. - bool foundRadio = Drivers::USB::Radio::IsRtlSdr(devDesc.idVendor, devDesc.idProduct); - while (offset + 2 <= totalLen) { uint8_t len = cfgBuf[offset]; uint8_t type = cfgBuf[offset + 1]; @@ -423,8 +417,8 @@ namespace Drivers::USB::UsbDevice { foundEp = true; } - // Bluetooth, Mass Storage and RTL-SDR bulk endpoints - if (foundBt || currentMsc || foundRadio) { + // Bulk endpoints needed by in-kernel Bluetooth and storage drivers. + if (foundBt || currentMsc) { if (isIn && xferType == EP_XFER_INTERRUPT && !foundEp) { // HCI event pipe (interrupt IN) dev->InterruptEpNum = ep->bEndpointAddress & 0x0F; @@ -457,6 +451,51 @@ namespace Drivers::USB::UsbDevice { foundBt = true; } + // No in-kernel class driver recognized this device. Preserve the first + // interface and its bulk endpoints so a userspace driver can claim it. + // The xHCI slot model currently stores one interface; a future model + // can retain every alternate/interface without changing the userspace + // claim ABI, which already names the interface number explicitly. + bool knownInterface = foundBt || foundMsc || + dev->InterfaceClass == CLASS_HID; + if (!knownInterface) { + bool inFirstInterface = false; + bool haveFirstInterface = false; + offset = 0; + while (offset + 2 <= totalLen) { + uint8_t len = cfgBuf[offset]; + uint8_t type = cfgBuf[offset + 1]; + if (len == 0 || offset + len > totalLen) break; + + if (type == DESC_INTERFACE && + offset + sizeof(InterfaceDescriptor) <= totalLen) { + if (haveFirstInterface) break; + auto* iface = (InterfaceDescriptor*)&cfgBuf[offset]; + dev->InterfaceClass = iface->bInterfaceClass; + dev->InterfaceSubClass = iface->bInterfaceSubClass; + dev->InterfaceProtocol = iface->bInterfaceProtocol; + dev->InterfaceNumber = iface->bInterfaceNumber; + haveFirstInterface = true; + inFirstInterface = true; + } else if (inFirstInterface && type == DESC_ENDPOINT && + offset + sizeof(EndpointDescriptor) <= totalLen) { + auto* ep = (EndpointDescriptor*)&cfgBuf[offset]; + uint8_t xferType = ep->bmAttributes & EP_XFER_TYPE_MASK; + bool isIn = (ep->bEndpointAddress & EP_DIR_IN) != 0; + if (xferType == EP_XFER_BULK && isIn && !foundBulkIn) { + dev->BulkInEpNum = ep->bEndpointAddress & 0x0F; + dev->BulkInMaxPacket = ep->wMaxPacketSize & 0x7FF; + foundBulkIn = true; + } else if (xferType == EP_XFER_BULK && !isIn && !foundBulkOut) { + dev->BulkOutEpNum = ep->bEndpointAddress & 0x0F; + dev->BulkOutMaxPacket = ep->wMaxPacketSize & 0x7FF; + foundBulkOut = true; + } + } + offset += len; + } + } + // ----------------------------------------------------------------- // Step 8: SET_CONFIGURATION // ----------------------------------------------------------------- @@ -657,15 +696,20 @@ namespace Drivers::USB::UsbDevice { // ----------------------------------------------------------------- // Step 13: Register with the appropriate class driver // ----------------------------------------------------------------- - if (dev->InterfaceClass == CLASS_HID && dev->InterfaceProtocol == PROTOCOL_KEYBOARD) { + if (foundEp && dev->InterfaceClass == CLASS_HID && + dev->InterfaceProtocol == PROTOCOL_KEYBOARD) { + dev->KernelDriverBound = true; HidKeyboard::RegisterDevice(slotId); KernelLogStream(OK, "USB") << "Slot " << (uint64_t)slotId << ": HID Boot Keyboard"; - } else if (dev->InterfaceClass == CLASS_HID && dev->InterfaceProtocol == PROTOCOL_MOUSE) { + } else if (foundEp && dev->InterfaceClass == CLASS_HID && + dev->InterfaceProtocol == PROTOCOL_MOUSE) { + dev->KernelDriverBound = true; HidMouse::RegisterDevice(slotId); KernelLogStream(OK, "USB") << "Slot " << (uint64_t)slotId << ": HID Boot Mouse"; } else if (dev->InterfaceClass == CLASS_WIRELESS && dev->InterfaceSubClass == SUBCLASS_RF && dev->InterfaceProtocol == PROTOCOL_BLUETOOTH) { + dev->KernelDriverBound = true; Bluetooth::RegisterAdapter(slotId); KernelLogStream(OK, "USB") << "Slot " << (uint64_t)slotId << ": Bluetooth Adapter" << " VID:" << base::hex << (uint64_t)dev->VendorId @@ -674,15 +718,10 @@ namespace Drivers::USB::UsbDevice { dev->InterfaceSubClass == SUBCLASS_SCSI && dev->InterfaceProtocol == PROTOCOL_BULK_ONLY && foundMsc && foundBulkIn && foundBulkOut) { + dev->KernelDriverBound = true; MassStorage::RegisterDevice(slotId); KernelLogStream(OK, "USB") << "Slot " << (uint64_t)slotId << ": USB Mass Storage"; - } else if (foundRadio && foundBulkIn) { - Drivers::USB::Radio::RegisterDevice(slotId); - KernelLogStream(OK, "USB") << "Slot " << (uint64_t)slotId - << ": RTL-SDR receiver" - << " VID:" << base::hex << (uint64_t)dev->VendorId - << " PID:" << (uint64_t)dev->ProductId << base::dec; } else if (foundEp) { KernelLogStream(INFO, "USB") << "Slot " << (uint64_t)slotId << ": USB device, class=" << (uint64_t)dev->InterfaceClass @@ -692,6 +731,9 @@ namespace Drivers::USB::UsbDevice { << ": Non-HID device, class=" << (uint64_t)devDesc.bDeviceClass; } + // Publish to userspace only after endpoint configuration and kernel + // class-driver binding decisions are complete. + dev->Ready = true; return slotId; } diff --git a/kernel/src/Drivers/USB/UserUsb.cpp b/kernel/src/Drivers/USB/UserUsb.cpp new file mode 100644 index 0000000..fd0d177 --- /dev/null +++ b/kernel/src/Drivers/USB/UserUsb.cpp @@ -0,0 +1,381 @@ +/* + * UserUsb.cpp + * Process-owned access to unbound USB interfaces. + * Copyright (c) 2026 Daniel Hammer +*/ + +#include "UserUsb.hpp" + +#include "Xhci.hpp" +#include +#include +#include +#include +#include + +namespace Drivers::USB::UserUsb { + + static constexpr int MaxClaims = Xhci::MAX_SLOTS; + static constexpr uint32_t RingBytes = 256 * 1024; + static constexpr uint32_t MaxControlBytes = 4096; + + struct ClaimState { + bool active; + bool connected; + bool streaming; + uint8_t slotId; + uint8_t interfaceNumber; + uint16_t generation; + int ownerPid; + + uint8_t* ring; + uint32_t head; + uint32_t count; + uint64_t droppedBytes; + uint32_t lastCompletionCode; + kcp::Spinlock ringLock; + }; + + static ClaimState g_claims[MaxClaims]; + // Serializes process-context operations and prevents a sibling thread from + // closing a claim while another syscall is using its backing state. + static kcp::Mutex g_claimsLock; + + static int MakeHandle(int index, uint16_t generation) { + return ((int)generation << 8) | index; + } + + static ClaimState* LookupLocked(int handle, bool requireConnected = true) { + int index = handle & 0xff; + uint16_t generation = (uint16_t)((uint32_t)handle >> 8); + if (index < 0 || index >= MaxClaims || generation == 0) return nullptr; + + ClaimState& claim = g_claims[index]; + if (!claim.active || claim.generation != generation) return nullptr; + if (claim.ownerPid != Sched::GetCurrentPid()) return nullptr; + if (requireConnected && !claim.connected) return nullptr; + return &claim; + } + + static bool SlotClaimedLocked(uint8_t slotId) { + for (int i = 0; i < MaxClaims; i++) { + if (g_claims[i].active && g_claims[i].connected && + g_claims[i].slotId == slotId) return true; + } + return false; + } + + static void CopyInterfaceInfo(uint8_t slotId, const Xhci::UsbDeviceInfo& dev, + montauk::abi::UsbInterfaceInfo& out) { + memset(&out, 0, sizeof(out)); + out.slotId = slotId; + out.portId = dev.PortId; + out.speed = (uint8_t)dev.Speed; + out.interfaceNumber = dev.InterfaceNumber; + out.vendorId = dev.VendorId; + out.productId = dev.ProductId; + out.deviceClass = dev.DeviceClass; + out.interfaceClass = dev.InterfaceClass; + out.interfaceSubClass = dev.InterfaceSubClass; + out.interfaceProtocol = dev.InterfaceProtocol; + out.bulkInEndpoint = dev.BulkInEpNum ? (uint8_t)(0x80 | dev.BulkInEpNum) : 0; + out.bulkOutEndpoint = dev.BulkOutEpNum; + out.bulkInMaxPacket = dev.BulkInMaxPacket; + out.bulkOutMaxPacket = dev.BulkOutMaxPacket; + out.kernelDriverBound = dev.KernelDriverBound ? 1 : 0; + out.claimed = SlotClaimedLocked(slotId) ? 1 : 0; + } + + int List(montauk::abi::UsbInterfaceInfo* out, int maxCount) { + if (!out || maxCount <= 0) return 0; + g_claimsLock.Acquire(); + int count = 0; + for (uint8_t slot = 1; slot <= Xhci::MAX_SLOTS && count < maxCount; slot++) { + Xhci::UsbDeviceInfo* dev = Xhci::GetDevice(slot); + if (!dev || !dev->Active || !dev->Ready) continue; + CopyInterfaceInfo(slot, *dev, out[count++]); + } + g_claimsLock.Release(); + return count; + } + + int Claim(uint8_t slotId, uint8_t interfaceNumber) { + int ownerPid = Sched::GetCurrentPid(); + if (ownerPid < 0 || slotId == 0 || slotId > Xhci::MAX_SLOTS) return montauk::abi::USB_ERR_INVALID; + + g_claimsLock.Acquire(); + Xhci::UsbDeviceInfo* dev = Xhci::GetDevice(slotId); + if (!dev || !dev->Active || !dev->Ready || + dev->InterfaceNumber != interfaceNumber) { + g_claimsLock.Release(); + return montauk::abi::USB_ERR_NOT_FOUND; + } + if (dev->KernelDriverBound) { + g_claimsLock.Release(); + return montauk::abi::USB_ERR_KERNEL_BOUND; + } + if (SlotClaimedLocked(slotId)) { + g_claimsLock.Release(); + return montauk::abi::USB_ERR_BUSY; + } + + for (int i = 0; i < MaxClaims; i++) { + ClaimState& claim = g_claims[i]; + if (claim.active) continue; + uint16_t generation = (uint16_t)(claim.generation + 1); + // Keep the encoded int handle positive so conventional `h < 0` + // error checks remain valid in userspace. + if (generation == 0 || generation > 0x7fff) generation = 1; + claim.active = true; + claim.connected = true; + claim.streaming = false; + claim.slotId = slotId; + claim.interfaceNumber = interfaceNumber; + claim.generation = generation; + claim.ownerPid = ownerPid; + claim.ring = nullptr; + claim.head = 0; + claim.count = 0; + claim.droppedBytes = 0; + claim.lastCompletionCode = Xhci::CC_SUCCESS; + int handle = MakeHandle(i, generation); + g_claimsLock.Release(); + return handle; + } + + g_claimsLock.Release(); + return montauk::abi::USB_ERR_NO_RESOURCES; + } + + static void TransferCallback(uint8_t slotId, uint8_t epDci, + const uint8_t* data, uint32_t length, + uint32_t completionCode) { + for (int i = 0; i < MaxClaims; i++) { + ClaimState& claim = g_claims[i]; + claim.ringLock.Acquire(); + if (!claim.active || !claim.connected || !claim.streaming || + claim.slotId != slotId) { + claim.ringLock.Release(); + continue; + } + + Xhci::UsbDeviceInfo* dev = Xhci::GetDevice(slotId); + uint8_t expectedDci = (dev && dev->BulkInEpNum) + ? (uint8_t)(dev->BulkInEpNum * 2 + 1) : 0; + claim.lastCompletionCode = completionCode; + if (epDci != expectedDci || !data || length == 0 || !claim.ring) { + claim.ringLock.Release(); + return; + } + + uint32_t space = RingBytes - claim.count; + uint32_t copied = length < space ? length : space; + uint32_t first = RingBytes - claim.head; + if (first > copied) first = copied; + memcpy(claim.ring + claim.head, data, first); + if (copied > first) memcpy(claim.ring, data + first, copied - first); + claim.head = (claim.head + copied) % RingBytes; + claim.count += copied; + claim.droppedBytes += length - copied; + claim.ringLock.Release(); + return; + } + } + + static void CloseLocked(ClaimState& claim) { + claim.ringLock.Acquire(); + bool connected = claim.connected; + bool wasStreaming = claim.streaming; + uint8_t slotId = claim.slotId; + claim.streaming = false; + claim.active = false; + claim.connected = false; + claim.ownerPid = -1; + uint8_t* ring = claim.ring; + claim.ring = nullptr; + claim.head = claim.count = 0; + claim.ringLock.Release(); + + // Mark the claim inactive before stopping: PollEvents may dispatch a + // late completion from StopBulkInStream, and the callback must drop it. + if (connected && wasStreaming) Xhci::StopBulkInStream(slotId); + if (connected) Xhci::RegisterTransferCallback(slotId, nullptr); + if (ring) Memory::g_heap->Free(ring); + } + + int Close(int handle) { + g_claimsLock.Acquire(); + ClaimState* claim = LookupLocked(handle, false); + if (!claim) { + g_claimsLock.Release(); + return montauk::abi::USB_ERR_INVALID; + } + CloseLocked(*claim); + g_claimsLock.Release(); + return 0; + } + + int Control(int handle, const montauk::abi::UsbControlRequest& request, + void* data, uint32_t dataLen) { + if (dataLen != request.length || dataLen > MaxControlBytes || + (dataLen != 0 && data == nullptr)) return montauk::abi::USB_ERR_INVALID; + + g_claimsLock.Acquire(); + ClaimState* claim = LookupLocked(handle); + if (!claim) { + g_claimsLock.Release(); + return montauk::abi::USB_ERR_DISCONNECTED; + } + + void* dma = nullptr; + if (dataLen != 0) { + dma = Memory::g_pfa->AllocateZeroed(); + if (!dma) { + g_claimsLock.Release(); + return montauk::abi::USB_ERR_NO_RESOURCES; + } + if ((request.requestType & 0x80) == 0) memcpy(dma, data, dataLen); + } + + uint32_t cc = Xhci::ControlTransfer(claim->slotId, request.requestType, + request.request, request.value, request.index, request.length, + dma, (request.requestType & 0x80) != 0); + if ((cc == Xhci::CC_SUCCESS || cc == Xhci::CC_SHORT_PACKET) && + dataLen != 0 && (request.requestType & 0x80) != 0) { + memcpy(data, dma, dataLen); + } + if (dma) Memory::g_pfa->Free(dma); + g_claimsLock.Release(); + return (cc == Xhci::CC_SUCCESS || cc == Xhci::CC_SHORT_PACKET) + ? 0 : montauk::abi::USB_ERR_IO; + } + + int StartBulkIn(int handle, uint32_t transferBytes, uint32_t bufferCount) { + if (transferBytes == 0 || transferBytes > 4096 || + bufferCount == 0 || bufferCount > 16) return montauk::abi::USB_ERR_INVALID; + + g_claimsLock.Acquire(); + ClaimState* claim = LookupLocked(handle); + if (!claim) { + g_claimsLock.Release(); + return montauk::abi::USB_ERR_DISCONNECTED; + } + Xhci::UsbDeviceInfo* dev = Xhci::GetDevice(claim->slotId); + if (!dev || !dev->BulkInEpNum || !dev->BulkInRing) { + g_claimsLock.Release(); + return montauk::abi::USB_ERR_UNSUPPORTED; + } + if (claim->streaming) { + g_claimsLock.Release(); + return 0; + } + if (!claim->ring) claim->ring = (uint8_t*)Memory::g_heap->Request(RingBytes); + if (!claim->ring) { + g_claimsLock.Release(); + return montauk::abi::USB_ERR_NO_RESOURCES; + } + + claim->ringLock.Acquire(); + claim->head = claim->count = 0; + claim->droppedBytes = 0; + claim->lastCompletionCode = Xhci::CC_SUCCESS; + claim->streaming = true; + claim->ringLock.Release(); + Xhci::RegisterTransferCallback(claim->slotId, TransferCallback); + Xhci::StartBulkInStream(claim->slotId, transferBytes, bufferCount); + if (!Xhci::IsBulkInStreamActive(claim->slotId)) { + claim->ringLock.Acquire(); + claim->streaming = false; + claim->ringLock.Release(); + Xhci::RegisterTransferCallback(claim->slotId, nullptr); + g_claimsLock.Release(); + return montauk::abi::USB_ERR_NO_RESOURCES; + } + g_claimsLock.Release(); + return 0; + } + + int StopBulkIn(int handle) { + g_claimsLock.Acquire(); + ClaimState* claim = LookupLocked(handle, false); + if (!claim) { + g_claimsLock.Release(); + return montauk::abi::USB_ERR_INVALID; + } + claim->ringLock.Acquire(); + bool stop = claim->connected && claim->streaming; + claim->streaming = false; + claim->ringLock.Release(); + if (stop) Xhci::StopBulkInStream(claim->slotId); + g_claimsLock.Release(); + return claim->connected ? 0 : montauk::abi::USB_ERR_DISCONNECTED; + } + + int ReadBulkIn(int handle, uint8_t* out, uint32_t maxLen) { + if (!out && maxLen != 0) return montauk::abi::USB_ERR_INVALID; + g_claimsLock.Acquire(); + ClaimState* claim = LookupLocked(handle, false); + if (!claim) { + g_claimsLock.Release(); + return montauk::abi::USB_ERR_INVALID; + } + if (!claim->connected && claim->count == 0) { + g_claimsLock.Release(); + return montauk::abi::USB_ERR_DISCONNECTED; + } + // Failed completions halt the endpoint and cannot be repaired from the + // xHCI event callback. Recover in process context so userspace drivers + // do not need a host-controller-specific reset API. + claim->ringLock.Acquire(); + bool recover = claim->connected && claim->streaming && + claim->lastCompletionCode != Xhci::CC_SUCCESS && + claim->lastCompletionCode != Xhci::CC_SHORT_PACKET; + if (recover) claim->lastCompletionCode = Xhci::CC_SUCCESS; + claim->ringLock.Release(); + if (recover) { + Xhci::ResetBulkInEndpoint(claim->slotId); + Xhci::PrimeBulkInStream(claim->slotId); + } + if (!claim->ring || maxLen == 0) { + g_claimsLock.Release(); + return 0; + } + claim->ringLock.Acquire(); + uint32_t copied = claim->count < maxLen ? claim->count : maxLen; + uint32_t tail = (claim->head + RingBytes - claim->count) % RingBytes; + uint32_t first = RingBytes - tail; + if (first > copied) first = copied; + memcpy(out, claim->ring + tail, first); + if (copied > first) memcpy(out + first, claim->ring, copied - first); + claim->count -= copied; + claim->ringLock.Release(); + g_claimsLock.Release(); + return (int)copied; + } + + void ReleaseAllForPid(int pid) { + if (pid < 0) return; + g_claimsLock.Acquire(); + for (int i = 0; i < MaxClaims; i++) { + if (g_claims[i].active && g_claims[i].ownerPid == pid) CloseLocked(g_claims[i]); + } + g_claimsLock.Release(); + } + + void DeviceDisconnected(uint8_t slotId) { + // Hot-unplug runs in deferred kernel context. Do not take the + // process-operation mutex: a control syscall may be polling the same + // event queue. The per-claim spinlock is enough to make callbacks and + // reads observe the disconnect atomically. + for (int i = 0; i < MaxClaims; i++) { + ClaimState& claim = g_claims[i]; + claim.ringLock.Acquire(); + if (claim.active && claim.slotId == slotId) { + claim.connected = false; + claim.streaming = false; + } + claim.ringLock.Release(); + } + } + +} diff --git a/kernel/src/Drivers/USB/UserUsb.hpp b/kernel/src/Drivers/USB/UserUsb.hpp new file mode 100644 index 0000000..0d52a29 --- /dev/null +++ b/kernel/src/Drivers/USB/UserUsb.hpp @@ -0,0 +1,38 @@ +/* + * UserUsb.hpp + * Process-owned access to unbound USB interfaces. + * Copyright (c) 2026 Daniel Hammer +*/ + +#pragma once + +#include +#include + +namespace Drivers::USB::UserUsb { + + // Enumerate the interfaces represented by the xHCI device table. + int List(montauk::abi::UsbInterfaceInfo* out, int maxCount); + + // Exclusively claim an interface that has no in-kernel class driver. + // The returned handle is generation checked and belongs to the calling + // process. The current xHCI device model represents one interface per + // slot, so a claim is presently exclusive for the whole device slot. + int Claim(uint8_t slotId, uint8_t interfaceNumber); + int Close(int handle); + + // Issue an EP0 control request. bmRequestType supplies the direction; + // dataLen is limited to one DMA page. + int Control(int handle, const montauk::abi::UsbControlRequest& request, + void* data, uint32_t dataLen); + + // Continuous bulk-IN streaming into a kernel ring. Read is non-blocking. + int StartBulkIn(int handle, uint32_t transferBytes, uint32_t bufferCount); + int StopBulkIn(int handle); + int ReadBulkIn(int handle, uint8_t* out, uint32_t maxLen); + + // Lifetime hooks used by process teardown and USB hot-unplug. + void ReleaseAllForPid(int pid); + void DeviceDisconnected(uint8_t slotId); + +} diff --git a/kernel/src/Drivers/USB/Xhci.cpp b/kernel/src/Drivers/USB/Xhci.cpp index 3a8fef9..2491379 100644 --- a/kernel/src/Drivers/USB/Xhci.cpp +++ b/kernel/src/Drivers/USB/Xhci.cpp @@ -10,7 +10,7 @@ #include "HidKeyboard.hpp" #include "HidMouse.hpp" #include "MassStorage.hpp" -#include "Radio/RtlSdr.hpp" +#include "UserUsb.hpp" #include #include #include @@ -167,8 +167,7 @@ namespace Drivers::USB::Xhci { // nesting (a callback invoked from THIS core's PollEvents); a different // core merely polling must not make a process-context transfer skip its // wait -- that returned CC_SUCCESS before the device filled the buffer - // (observed as garbled RTL-SDR register reads while the BT firmware - // download was polling on another core). + // (observed as corrupted USB control reads while another core was polling). static std::atomic g_pollOwnerCpu{-1}; // Serialises non-nested (waiting) control transfers so only one EP0 @@ -198,7 +197,7 @@ namespace Drivers::USB::Xhci { // re-arms the SAME buffer at the ring tail, so the endpoint is never without // a place to DMA. This closes the gap that single-outstanding bulk IN leaves // between completion and re-arm, during which the device FIFO overflows - // (the RTL-SDR ~88% sample-drop at 2.048 Msps). PoolCount==0 => the legacy + // under sustained high-rate input. PoolCount==0 selects the legacy // single-buffer path above (used by Bluetooth ACL), unchanged. static constexpr uint32_t BULK_IN_POOL_MAX = 16; static uint8_t* g_bulkInPool[MAX_SLOTS + 1][BULK_IN_POOL_MAX] = {}; @@ -206,6 +205,7 @@ namespace Drivers::USB::Xhci { static uint32_t g_bulkInPoolCount[MAX_SLOTS + 1] = {}; // outstanding URBs (0=off) static uint32_t g_bulkInPoolHead[MAX_SLOTS + 1] = {}; // next buffer to complete static uint32_t g_bulkInPoolXferLen[MAX_SLOTS + 1] = {}; // bytes per transfer + static kcp::Spinlock g_bulkInPoolLocks[MAX_SLOTS + 1]; // Transfer callbacks for non-HID class drivers (per slot) static TransferCallback g_transferCallbacks[MAX_SLOTS + 1] = {}; @@ -590,15 +590,19 @@ namespace Drivers::USB::Xhci { // buffer is safe -- it will not be DMA'd into again // until the other PoolCount-1 transfers ahead of it // complete (~PoolCount ms of slack). - uint32_t i = g_bulkInPoolHead[slotId]; - uint32_t reqLen = g_bulkInPoolXferLen[slotId]; - uint32_t len = (residual < reqLen) ? (reqLen - residual) : 0; - g_transferCallbacks[slotId](slotId, epDci, - g_bulkInPool[slotId][i], len, completionCode); - QueueBulkInTransfer(slotId, g_bulkInPool[slotId][i], - g_bulkInPoolPhys[slotId][i], reqLen); - g_bulkInPoolHead[slotId] = - (i + 1) % g_bulkInPoolCount[slotId]; + g_bulkInPoolLocks[slotId].Acquire(); + uint32_t poolCount = g_bulkInPoolCount[slotId]; + if (poolCount > 0) { + uint32_t i = g_bulkInPoolHead[slotId]; + uint32_t reqLen = g_bulkInPoolXferLen[slotId]; + uint32_t len = (residual < reqLen) ? (reqLen - residual) : 0; + g_transferCallbacks[slotId](slotId, epDci, + g_bulkInPool[slotId][i], len, completionCode); + QueueBulkInTransfer(slotId, g_bulkInPool[slotId][i], + g_bulkInPoolPhys[slotId][i], reqLen); + g_bulkInPoolHead[slotId] = (i + 1) % poolCount; + } + g_bulkInPoolLocks[slotId].Release(); } else if (epDci == bulkInDci && g_transferCallbacks[slotId]) { // Bulk IN — dispatch via registered callback. // len = actually-transferred bytes (requested - @@ -1149,13 +1153,18 @@ namespace Drivers::USB::Xhci { // single-buffer start relies on. void PrimeBulkInStream(uint8_t slotId) { if (slotId == 0 || slotId > MAX_SLOTS || !g_devices[slotId].Active) return; + g_bulkInPoolLocks[slotId].Acquire(); uint32_t n = g_bulkInPoolCount[slotId]; - if (n == 0) return; + if (n == 0) { + g_bulkInPoolLocks[slotId].Release(); + return; + } g_bulkInPoolHead[slotId] = 0; uint32_t len = g_bulkInPoolXferLen[slotId]; for (uint32_t i = 0; i < n; i++) QueueBulkInTransfer(slotId, g_bulkInPool[slotId][i], g_bulkInPoolPhys[slotId][i], len); + g_bulkInPoolLocks[slotId].Release(); } void StartBulkInStream(uint8_t slotId, uint32_t xferLen, uint32_t numBuffers) { @@ -1195,15 +1204,27 @@ namespace Drivers::USB::Xhci { for (uint32_t i = 0; i < numBuffers; i++) QueueBulkInTransfer(slotId, g_bulkInPool[slotId][i], g_bulkInPoolPhys[slotId][i], xferLen); + g_bulkInPoolLocks[slotId].Acquire(); g_bulkInPoolCount[slotId] = numBuffers; + g_bulkInPoolLocks[slotId].Release(); + } + + bool IsBulkInStreamActive(uint8_t slotId) { + if (slotId == 0 || slotId > MAX_SLOTS) return false; + g_bulkInPoolLocks[slotId].Acquire(); + bool active = g_bulkInPoolCount[slotId] != 0; + g_bulkInPoolLocks[slotId].Release(); + return active; } void StopBulkInStream(uint8_t slotId) { if (slotId == 0 || slotId > MAX_SLOTS) return; // Disarm the rotation; any late completion now takes the (no-op for SDR) // legacy path and is not re-armed. Buffers are retained for reuse. + g_bulkInPoolLocks[slotId].Acquire(); bool wasArmed = g_bulkInPoolCount[slotId] != 0; g_bulkInPoolCount[slotId] = 0; + g_bulkInPoolLocks[slotId].Release(); if (!wasArmed) return; // Flush the up-to-PoolCount TRBs still pending on the ring: Stop @@ -1437,9 +1458,7 @@ namespace Drivers::USB::Xhci { } static void UnregisterClassDriver(uint8_t slotId, const UsbDeviceInfo& dev) { - if (Radio::IsRtlSdr(dev.VendorId, dev.ProductId)) { - Radio::UnregisterDevice(slotId); - } else if (dev.InterfaceClass == UsbDevice::CLASS_MASS_STORAGE) { + if (dev.InterfaceClass == UsbDevice::CLASS_MASS_STORAGE) { MassStorage::UnregisterDevice(slotId); } else if (dev.InterfaceClass == UsbDevice::CLASS_HID && dev.InterfaceProtocol == UsbDevice::PROTOCOL_KEYBOARD) { @@ -1559,6 +1578,7 @@ namespace Drivers::USB::Xhci { // Device disconnected — deactivate its slot for (uint8_t s = 1; s <= MAX_SLOTS; s++) { if (g_devices[s].Active && g_devices[s].PortId == port + 1) { + UserUsb::DeviceDisconnected(s); UnregisterClassDriver(s, g_devices[s]); g_devices[s].Active = false; g_transferCallbacks[s] = nullptr; diff --git a/kernel/src/Drivers/USB/Xhci.hpp b/kernel/src/Drivers/USB/Xhci.hpp index 0e6e5af..9304e42 100644 --- a/kernel/src/Drivers/USB/Xhci.hpp +++ b/kernel/src/Drivers/USB/Xhci.hpp @@ -233,6 +233,7 @@ namespace Drivers::USB::Xhci { struct UsbDeviceInfo { bool Active; + bool Ready; // descriptors/endpoints and binding are complete uint8_t PortId; uint32_t Speed; uint16_t VendorId; @@ -242,6 +243,7 @@ namespace Drivers::USB::Xhci { uint8_t InterfaceProtocol; uint8_t InterfaceNumber; uint8_t DeviceClass; // bDeviceClass from device descriptor + bool KernelDriverBound; // unavailable to a userspace interface claim // Interrupt IN endpoint uint8_t InterruptEpNum; // Endpoint number (1-15) @@ -329,7 +331,7 @@ namespace Drivers::USB::Xhci { // Clear a halted bulk IN endpoint (Reset Endpoint + Set TR Dequeue) without // re-arming. Must be called from process context (it issues commands that // wait on the event ring); the caller re-arms with QueueBulkInTransfer. - // Used for SDR stream stall recovery (RTL2832 bulk IN can STALL on start). + // Used by generic process-owned bulk streams after a transfer stall. void ResetBulkInEndpoint(uint8_t slotId); // Clear a halted bulk OUT endpoint and discard the errored/queued TRBs. @@ -352,10 +354,11 @@ namespace Drivers::USB::Xhci { // as it completes. The slot's registered transfer callback receives every // buffer's data but must NOT re-arm itself (the event handler does). This // eliminates the FIFO-overflow gap of single-outstanding bulk IN. Use for - // sustained high-rate sources (RTL-SDR I/Q). PrimeBulkInStream re-queues the + // sustained high-rate sources. PrimeBulkInStream re-queues the // whole pool after a stall reset; StopBulkInStream disarms the rotation. // All three are process-context calls. void StartBulkInStream(uint8_t slotId, uint32_t xferLen, uint32_t numBuffers); + bool IsBulkInStreamActive(uint8_t slotId); void PrimeBulkInStream(uint8_t slotId); void StopBulkInStream(uint8_t slotId); diff --git a/kernel/src/Sched/Scheduler.cpp b/kernel/src/Sched/Scheduler.cpp index 4a17853..9ed462e 100644 --- a/kernel/src/Sched/Scheduler.cpp +++ b/kernel/src/Sched/Scheduler.cpp @@ -27,6 +27,7 @@ #include #include #include +#include // Assembly: context switch with CR3 and FPU state parameters extern "C" void SchedContextSwitch(uint64_t* oldRsp, uint64_t newRsp, uint64_t newCR3, @@ -1607,6 +1608,11 @@ namespace Sched { // never stranded on the invisible buffer (no-op for non-owners). Drivers::Graphics::IntelGPU::OnProcessExit(exitingPid); + // USB interface claims are process-owned capabilities. Closing them + // here stops DMA streaming and releases exclusivity even when an app + // exits without calling usb_close(). + Drivers::USB::UserUsb::ReleaseAllForPid(exitingPid); + // Release process-scoped IPC handles/mappings before tearing down the address space. Ipc::CleanupProcessSlot(slot, exitingPid, proc.pml4Phys); montauk::abi::CleanupHeapForSlot(slot, proc.pml4Phys); diff --git a/programs/GNUmakefile b/programs/GNUmakefile index 4061b8a..05480d8 100644 --- a/programs/GNUmakefile +++ b/programs/GNUmakefile @@ -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//. @@ -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 diff --git a/programs/include/Api/Syscall.hpp b/programs/include/Api/Syscall.hpp index 7d87f9f..0814b5c 100644 --- a/programs/include/Api/Syscall.hpp +++ b/programs/include/Api/Syscall.hpp @@ -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; diff --git a/programs/include/libc/montauk.h b/programs/include/libc/montauk.h index 7f20e03..dd49f4c 100644 --- a/programs/include/libc/montauk.h +++ b/programs/include/libc/montauk.h @@ -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 ==================================================================== */ diff --git a/programs/include/montauk/syscall.h b/programs/include/montauk/syscall.h index 1069447..e963fa3 100644 --- a/programs/include/montauk/syscall.h +++ b/programs/include/montauk/syscall.h @@ -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 diff --git a/programs/include/rtlsdr/rtlsdr.h b/programs/include/rtlsdr/rtlsdr.h new file mode 100644 index 0000000..bd4188a --- /dev/null +++ b/programs/include/rtlsdr/rtlsdr.h @@ -0,0 +1,42 @@ +#pragma once + +#include + +#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 diff --git a/programs/man/rtlsdr.3 b/programs/man/rtlsdr.3 new file mode 100644 index 0000000..d304278 --- /dev/null +++ b/programs/man/rtlsdr.3 @@ -0,0 +1,44 @@ +.TH RTLSDR 3 +.SH NAME +rtlsdr.lib - userspace RTL2832U and R820T2 radio driver +.SH SYNOPSIS +.nf +#include +.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. diff --git a/programs/man/syscalls.2 b/programs/man/syscalls.2 index a8e3240..cd9e3e0 100644 --- a/programs/man/syscalls.2 +++ b/programs/man/syscalls.2 @@ -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) diff --git a/programs/src/rtlsdr/Makefile b/programs/src/rtlsdr/Makefile new file mode 100644 index 0000000..636da5d --- /dev/null +++ b/programs/src/rtlsdr/Makefile @@ -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) diff --git a/kernel/src/Drivers/USB/Radio/R820t.cpp b/programs/src/rtlsdr/r820t.cpp similarity index 95% rename from kernel/src/Drivers/USB/Radio/R820t.cpp rename to programs/src/rtlsdr/r820t.cpp index c1e5bc8..6914225 100644 --- a/kernel/src/Drivers/USB/Radio/R820t.cpp +++ b/programs/src/rtlsdr/r820t.cpp @@ -1,20 +1,17 @@ -/* - * R820t.cpp - * Rafael Micro R820T / R820T2 silicon tuner driver. - * Copyright (c) 2026 Daniel Hammer -*/ +#include "r820t.hpp" -#include "R820t.hpp" -#include "RtlSdr.hpp" // I2C facade (RtlI2cWrite / RtlI2cRead) -#include -#include +namespace { +struct LogSink { template 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 -using namespace Kt; +namespace rtlsdr_internal { -namespace Drivers::USB::Radio { - - // ========================================================================= - // Tuner constant tables // ========================================================================= // Initial register values for registers 0x05..0x1f (27 registers). Written @@ -97,7 +94,7 @@ namespace Drivers::USB::Radio { 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.slotId, R820T_I2C_ADDR, buf, (uint8_t)(size + 1))) + 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]; @@ -129,8 +126,8 @@ namespace Drivers::USB::Radio { uint8_t raw[16]; if (len > sizeof(raw)) len = sizeof(raw); uint8_t ptr = 0x00; - if (!RtlI2cWrite(d.slotId, R820T_I2C_ADDR, &ptr, 1)) return false; - if (!RtlI2cRead(d.slotId, R820T_I2C_ADDR, raw, len)) return false; + 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; } @@ -212,16 +209,16 @@ namespace Drivers::USB::Radio { return ok; } - bool R820tDetect(uint8_t slotId) { + 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(slotId, R820T_I2C_ADDR, &ptr, 1); + RtlI2cWrite(owner, R820T_I2C_ADDR, &ptr, 1); uint8_t raw[1] = {0}; - if (!RtlI2cRead(slotId, R820T_I2C_ADDR, raw, 1)) { + if (!RtlI2cRead(owner, R820T_I2C_ADDR, raw, 1)) { KernelLogStream(WARNING, "R820T") << "id read failed (I2C transfer error)"; return false; } @@ -231,8 +228,8 @@ namespace Drivers::USB::Radio { return raw[0] == R820T_CHECK_VAL; } - bool R820tInit(R820tDev& d, uint8_t slotId, uint32_t xtal, uint32_t intFreq) { - d.slotId = slotId; + 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; diff --git a/programs/src/rtlsdr/r820t.hpp b/programs/src/rtlsdr/r820t.hpp new file mode 100644 index 0000000..f2621d8 --- /dev/null +++ b/programs/src/rtlsdr/r820t.hpp @@ -0,0 +1,28 @@ +#pragma once +#include + +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); +} diff --git a/programs/src/rtlsdr/rtlsdr.cpp b/programs/src/rtlsdr/rtlsdr.cpp new file mode 100644 index 0000000..349db71 --- /dev/null +++ b/programs/src/rtlsdr/rtlsdr.cpp @@ -0,0 +1,491 @@ +#include +#include +#include +#include "r820t.hpp" + +struct rtlsdr_device { int marker; }; + +namespace rtlsdr_internal { +namespace { +struct LogSink { template 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); +} diff --git a/programs/src/sdr/Makefile b/programs/src/sdr/Makefile new file mode 100644 index 0000000..9a4e652 --- /dev/null +++ b/programs/src/sdr/Makefile @@ -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) diff --git a/programs/src/sdr/main.cpp b/programs/src/sdr/main.cpp index be55ecb..3329852 100644 --- a/programs/src/sdr/main.cpp +++ b/programs/src/sdr/main.cpp @@ -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 #include +#include +#include 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(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); } diff --git a/template/sysroot/include/Api/Syscall.hpp b/template/sysroot/include/Api/Syscall.hpp index 7d87f9f..0814b5c 100644 --- a/template/sysroot/include/Api/Syscall.hpp +++ b/template/sysroot/include/Api/Syscall.hpp @@ -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; diff --git a/template/sysroot/include/libc/montauk.h b/template/sysroot/include/libc/montauk.h index 7f20e03..dd49f4c 100644 --- a/template/sysroot/include/libc/montauk.h +++ b/template/sysroot/include/libc/montauk.h @@ -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 ==================================================================== */ diff --git a/template/sysroot/include/montauk/syscall.h b/template/sysroot/include/montauk/syscall.h index 1069447..e963fa3 100644 --- a/template/sysroot/include/montauk/syscall.h +++ b/template/sysroot/include/montauk/syscall.h @@ -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 diff --git a/template/sysroot/include/rtlsdr/rtlsdr.h b/template/sysroot/include/rtlsdr/rtlsdr.h new file mode 100644 index 0000000..bebc465 --- /dev/null +++ b/template/sysroot/include/rtlsdr/rtlsdr.h @@ -0,0 +1,36 @@ +#pragma once +#include +#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 diff --git a/tests/Makefile b/tests/Makefile index 88cf963..e38ec7d 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -3,10 +3,11 @@ CXXFLAGS ?= -std=c++20 -O2 -Wall -Wextra HTTP_TEST := http_test SVG_TEST := svg_renderer_test +RTLSDR_TEST := rtlsdr_r820t_test .PHONY: all check clean -all: $(HTTP_TEST) $(SVG_TEST) +all: $(HTTP_TEST) $(SVG_TEST) $(RTLSDR_TEST) $(HTTP_TEST): http_test.cpp $(CXX) $(CXXFLAGS) -I../programs/include \ @@ -15,9 +16,13 @@ $(HTTP_TEST): http_test.cpp $(SVG_TEST): svg_renderer_test.cpp ../programs/include/gui/svg.hpp $(CXX) $(CXXFLAGS) -I../programs/include $< -o $@ -check: $(HTTP_TEST) $(SVG_TEST) +$(RTLSDR_TEST): rtlsdr_r820t_test.cpp ../programs/src/rtlsdr/r820t.cpp + $(CXX) $(CXXFLAGS) -I../programs/src/rtlsdr $^ -o $@ + +check: $(HTTP_TEST) $(SVG_TEST) $(RTLSDR_TEST) ./$(HTTP_TEST) ./$(SVG_TEST) + ./$(RTLSDR_TEST) clean: - rm -f $(HTTP_TEST) $(SVG_TEST) + rm -f $(HTTP_TEST) $(SVG_TEST) $(RTLSDR_TEST) diff --git a/tests/rtlsdr_r820t_test.cpp b/tests/rtlsdr_r820t_test.cpp new file mode 100644 index 0000000..967eb3e --- /dev/null +++ b/tests/rtlsdr_r820t_test.cpp @@ -0,0 +1,54 @@ +#include "../programs/src/rtlsdr/r820t.hpp" +#include +#include +#include +#include + +struct rtlsdr_device {}; + +struct I2cMessage { + uint8_t address; + std::vector data; +}; +static std::vector g_writes; + +namespace rtlsdr_internal { +bool RtlI2cWrite(rtlsdr_device*, uint8_t address, const uint8_t* data, uint8_t len) { + g_writes.push_back({address, std::vector(data, data + len)}); + return true; +} + +bool RtlI2cRead(rtlsdr_device*, uint8_t, uint8_t* data, uint8_t len) { + std::memset(data, 0, len); + if (len == 1) data[0] = R820T_CHECK_VAL; + if (len == 5) data[4] = 0x80; // bit-reversed logical value 0x01 + if (len == 3) data[2] = 0x02; // bit-reversed PLL lock bit 0x40 + return true; +} +} + +int main() { + using namespace rtlsdr_internal; + rtlsdr_device owner; + assert(R820tDetect(&owner)); + + R820tDev tuner{}; + assert(R820tInit(tuner, &owner, 28800000, 3570000)); + assert(tuner.inited); + assert(R820tSetFreq(tuner, 100100000)); + assert(tuner.hasLock); + assert(R820tSetGain(tuner, 0, 0)); + assert(R820tSetGain(tuner, 1, 297)); + R820tStandby(tuner); + + int gainCount = 0; + const int* gains = R820tGainTable(&gainCount); + assert(gainCount == 29); + assert(gains[0] == 0 && gains[gainCount - 1] == 496); + assert(!g_writes.empty()); + for (const auto& message : g_writes) { + assert(message.address == R820T_I2C_ADDR); + assert(!message.data.empty() && message.data.size() <= 8); + } + std::puts("rtlsdr R820T transport test passed"); +}