Skip to content

macOS: bundled librtlsdr is built with DETACH_KERNEL_DRIVER=ON, so no RTL-SDR device can be opened #1462

Description

@ikhavkin

Note: this issue was investigated and drafted by Claude (Anthropic's Claude Code) at my request, running on my own machine against my own hardware. I've reviewed it and I'm filing it under my account. Every command output quoted below is real output from that session, not reconstructed.

Summary

The official macOS builds bundle a librtlsdr that was compiled with -DDETACH_KERNEL_DRIVER=ON. On macOS that makes rtlsdr_open() fail unconditionally, so no RTL-SDR device can ever be opened by the shipped app. The GUI reports this as failed to set samplerate, which hides the actual cause and sends people looking at sample rates, USB ports and cables.

The CLI tools from Homebrew's librtlsdr drive the exact same dongle on the same machine without trouble, which makes it look like a Gqrx configuration problem rather than a packaging one.

Environment

  • Gqrx 2.17.7, official Gqrx-2.17.7-arm64.dmg (installed via the Homebrew cask, which downloads the upstream release asset)
  • macOS 26.5.2, Apple Silicon
  • RTL-SDR Blog V4 (Rafael Micro R828D)

Symptom

Running Gqrx.app/Contents/MacOS/gqrx from a terminal:

gr-osmosdr 0.2.0.0 (0.2.0) gnuradio 3.10.12.0
built-in source types: file rtl rtl_tcp uhd miri hackrf bladerf rfspace airspy airspyhf soapy redpitaya
Using device #0 RTLSDRBlog Blog V4 SN: 00000001
Detaching kernel driver failed!

…after which the source is never constructed and the GUI shows failed to set samplerate.

Root cause

src/librtlsdr.c (osmocom rtl-sdr), in rtlsdr_open():

if (libusb_kernel_driver_active(dev->devh, 0) == 1) {
        dev->driver_active = 1;

#ifdef DETACH_KERNEL_DRIVER
        if (!libusb_detach_kernel_driver(dev->devh, 0)) {
                fprintf(stderr, "Detached kernel driver\n");
        } else {
                fprintf(stderr, "Detaching kernel driver failed!");
                goto err;
        }
#else
        fprintf(stderr, "\nKernel driver is active, or device is "
                        "claimed by second instance of librtlsdr."
                        ...);
#endif

With the macro defined, a failed detach is fatal (goto err). Without it, the same condition is only a warning and the open proceeds.

On macOS the detach can never succeed — dvb_usb_rtl28xxu is a Linux module that doesn't exist here, and libusb's Darwin backend doesn't support detaching. The printed Detaching kernel driver failed! confirms both that libusb_kernel_driver_active() returned 1 and that libusb_detach_kernel_driver() returned non-zero, so the fatal branch is always taken.

Confirming which build is in the DMG — strings on the bundled dylib vs. Homebrew's:

$ strings /Applications/Gqrx.app/Contents/Frameworks/librtlsdr.0.dylib | grep -i detach
Detached kernel driver
Detaching kernel driver failed!          <-- compiled WITH the macro

$ strings /opt/homebrew/lib/librtlsdr.0.dylib | grep -i detach
In the first case, please either detach or blacklist the kernel module
(dvb_usb_rtl28xxu), or enable automatic detaching at compile time.   <-- compiled WITHOUT it

Where the flag comes from

The macOS job in .github/workflows/build.yml gets GNU Radio and gr-osmosdr from conda-forge via micromamba, which pulls conda-forge's librtlsdr transitively. That package is built by conda-forge/rtl-sdr-feedstock, whose recipe/build.sh sets the flag unconditionally for every platform:

cmake_config_args=(
    -DCMAKE_BUILD_TYPE=Release
    -DCMAKE_INSTALL_LIBDIR=lib
    -DCMAKE_INSTALL_PREFIX=$PREFIX
    -DDETACH_KERNEL_DRIVER=ON
    -DINSTALL_UDEV_RULES=OFF
)

Worth noting that ENABLE_ZEROCOPY a few lines below it in the same file is gated on linux-64/linux-ppc64le, so the pattern for a platform-conditional flag already exists there.

For contrast, Gqrx's own Windows job builds rtl-sdr from source and explicitly sets -DDETACH_KERNEL_DRIVER=OFF (.github/workflows/build.yml, around line 233). Only the conda-forge-sourced macOS build carries it.

Verified fix

Replacing the bundled dylib with Homebrew's librtlsdr 2.0.3 (same upstream project, built with the flag off) and re-signing the bundle makes the native rtl=0 device work immediately:

Using device #0 RTLSDRBlog Blog V4 SN: 00000001
Kernel driver is active, or device is claimed by second instance of librtlsdr.
...
Found Rafael Micro R828D tuner
RTL-SDR Blog V4 Detected

Same message, now non-fatal, and the radio runs.

Workaround for other users

No bundle surgery needed — route around the bundled driver with rtl_tcp from a working librtlsdr:

brew install librtlsdr
rtl_tcp -a 127.0.0.1

then set Gqrx's device string to rtl_tcp=127.0.0.1:1234. Gqrx's rtl_tcp source is pure network code and never touches the broken dylib.

(Note that in-place patching of /Applications/Gqrx.app is blocked by macOS App Management protection on macOS 14+, sudo included — the bundle has to be copied somewhere user-owned first, then re-signed.)

Suggested resolution

The real fix belongs in conda-forge/rtl-sdr-feedstock: gate -DDETACH_KERNEL_DRIVER=ON to Linux only. I'm happy to open that issue/PR too if it's useful.

In the meantime the macOS job could build rtl-sdr from source with the flag off, the way the Windows job already does.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions