usermode-linux-gpib — macOS / Linux / Windows userspace port of the NI USB-GPIB driver
=======================================================================================

This project compiles the Linux NI USB-GPIB kernel driver (ni_usb_gpib.c
from linux-gpib 4.3.6 / git 9813ff8f) as a native userspace executable on
macOS, Linux, and Windows.  All Linux kernel APIs are replaced by platform
shims selected at compile time via compat.h:

  osx_compat.h  — macOS and Linux (pthreads + POSIX + libusb-1.0)
  win_compat.h  — Windows (Win32 SRWLOCK + CONDITION_VARIABLE + CreateThread + libusb-1.0)

Upstream driver files are minimally patched (Linux-only headers guarded with
#ifdef __KERNEL__ / #ifdef __linux__; see "Upstream source" below).


Supported hardware
------------------
  NI USB-B       (VID 0x3923 PID 0x702a)
  NI USB-HS      (VID 0x3923 PID 0x709b)
  NI USB-HS+     (VID 0x3923 PID 0x7618)
  KUSB-488A      (VID 0x3923 PID 0x725c)
  MC USB-488     (VID 0x3923 PID 0x725d)


Prerequisites
-------------
macOS (Apple Silicon or Intel):
  Xcode Command Line Tools or clang
  libusb-1.0 via MacPorts:

      sudo port install libusb

  Headers:  /opt/local/include/libusb-1.0/libusb.h
  Library:  /opt/local/lib/libusb-1.0.dylib

Linux:
  clang or gcc, libusb-1.0-dev (e.g. sudo apt install libusb-1.0-0-dev)

Windows (MinGW-w64 or Clang):
  MinGW-w64 toolchain or LLVM/Clang for Windows
  libusb-1.0 Windows binaries from libusb.info


Build
-----
  C++ entry point (main.cpp) — macOS / Linux command line:

      clang  -I. -Ilinux-gpib -I/opt/local/include -Wno-unused-function -Wno-visibility \
             -std=gnu11 -c linux-gpib/ni_usb_gpib.c gpib_stubs.c
      clang++ -I. -Ilinux-gpib -I/opt/local/include -Wno-unused-function -Wno-visibility \
              -std=gnu++17 -c main.cpp NiGpibDriver.cpp
      clang++ ni_usb_gpib.o gpib_stubs.o main.o NiGpibDriver.o \
              -L/opt/local/lib -lusb-1.0 -lpthread -o ni_usb_gpib

  linux-gpib/ni_usb_gpib.c and gpib_stubs.c must be compiled as C (not C++)
  because nec7210.h uses 'private' as a struct field name.

  C entry point (main.c) — macOS / Linux command line:

      clang -I. -Ilinux-gpib -I/opt/local/include -Wno-unused-function -Wno-visibility \
            -std=gnu11 \
            main.c linux-gpib/ni_usb_gpib.c gpib_stubs.c \
            -L/opt/local/lib -lusb-1.0 -lpthread \
            -o ni_usb_gpib

  Windows (MinGW-w64 / Clang) — C entry point:

      clang -I. -Ilinux-gpib -I<libusb-include> -Wno-unused-function -Wno-visibility \
            -std=gnu11 \
            main.c linux-gpib/ni_usb_gpib.c gpib_stubs.c \
            -L<libusb-lib> -lusb-1.0 -o ni_usb_gpib.exe

  Qt Creator:  open usermode-linux-gpib.pro and build normally.
               The .pro uses main.cpp by default.

  Tests — mock mode (no hardware required):

      make test_concurrent   # build only
      make test              # build and run

  Tests — real hardware mode:

      make test_hw           # builds test_concurrent_hw and runs against addr 12
      HW_ADDR=5 make test_hw # override instrument address

  The hardware binary (test_concurrent_hw) links the real driver and runs
  timed *IDN? and serialPoll sequences against the connected instrument,
  printing min/avg/max per-call latency.


Usage
-----
  ./ni_usb_gpib                        detect adapter, print ready message
  ./ni_usb_gpib <addr>                 send *IDN? to <addr>, EOI terminator
  ./ni_usb_gpib <addr> <command>       send command, EOI terminator
  ./ni_usb_gpib <addr> <command> cr    send command with CR terminator (no EOI)
  ./ni_usb_gpib <addr> <command> lf    send command with LF terminator (no EOI)
  ./ni_usb_gpib <addr> <command> crlf  send command with CR+LF terminator
  ./ni_usb_gpib <addr> spoll           serial poll <addr>, print status byte
  ./ni_usb_gpib <addr> ifc             assert Interface Clear (resets the bus)

  <command> defaults to *IDN? when omitted.  The terminator keyword may also
  be given without a command (e.g. ./ni_usb_gpib <addr> cr sends *IDN? with CR).

  Trailing CR/LF are stripped from responses before display.

  Without hardware connected the program exits with:
      No supported NI USB-GPIB adapter found.


Linux userspace permissions
---------------------------
  libusb requires either root or a udev rule granting access to the device.
  Without this, libusb_claim_interface() fails with LIBUSB_ERROR_ACCESS.

  Option A — run as root (simplest):

      sudo ./ni_usb_gpib [addr]

  Option B — add a udev rule (recommended, persistent):

      echo 'SUBSYSTEM=="usb", ATTR{idVendor}=="3923", MODE="0666"' | \
          sudo tee /etc/udev/rules.d/99-ni-usb-gpib.rules
      sudo udevadm control --reload-rules && sudo udevadm trigger

  Option C — if the ni_usb_gpib kernel module is already loaded it will have
  claimed the interface.  Unload it first:

      sudo modprobe -r ni_usb_gpib
      sudo modprobe -r gpib_common      # if needed

  The code calls libusb_set_auto_detach_kernel_driver() which will attempt the
  detach automatically, but this still requires elevated permissions unless the
  udev rule above is in place.


macOS permissions
-----------------
  libusb requires exclusive access to the USB interface.  If NI-488.2 (or any
  other IOKit driver) is installed and has claimed the interface, libusb will
  print:

      libusb_claim_interface(0) failed: LIBUSB_ERROR_ACCESS
        Hint: another driver (e.g. NI-488.2) owns the interface.
        Try running with sudo, or unload the NI kernel extension first.

  Option A — run as root (simplest, works immediately):

      sudo ./ni_usb_gpib [addr]

  Option B — unload the NI kernel extension for the session:

      sudo kextunload -b com.ni.ni488   # bundle ID may vary; check with:
      kextstat | grep -i ni             # … then rerun without sudo

  Option C — on a Mac without NI-488.2 installed (e.g. Apple Silicon running
  only this port), no special privileges are needed.


Architecture
------------
  compat.h       Platform dispatcher.  Includes win_compat.h on _WIN32,
                 osx_compat.h otherwise.

  osx_compat.h   macOS / Linux shim.  Replaces all Linux kernel APIs used by
                 ni_usb_gpib.c: basic types, kmalloc/kfree, list_head,
                 spinlock/mutex/wait_queue/completion (pthreads), timer_list
                 (passive: records expiry but does not spawn a thread — the
                 libusb bulk-transfer timeout makes the timer unnecessary in
                 practice), and the full USB layer (struct urb; bulk URBs run
                 synchronously via libusb_bulk_transfer; interrupt URBs get one
                 persistent background pthread).

  win_compat.h   Windows shim.  Same API surface as osx_compat.h using Win32
                 SRWLOCK + CONDITION_VARIABLE + CreateThread instead of
                 pthreads.  Targets Windows 7+ via MinGW-w64 or Clang.

  linux-gpib/ni_usb_gpib.c
                 Upstream kernel driver source, compiled as-is with
                 compat.h as the first include.

  gpib_stubs.c   Userspace replacements for gpib_common.ko symbols
                 (gpib_register_driver, push_gpib_event, …) plus the
                 g_ni_usb_driver / g_ni_gpib_interface globals that capture
                 the driver's registered pointers during module init.

  NiGpibDriver.h/cpp
                 C++ RAII wrapper around the driver lifecycle.  Public API:
                   open()           — find USB adapter, init, probe, attach
                   openForTest()    — inject mock interface (unit testing)
                   close()          — detach and release all resources
                   interfaceClear() — assert IFC for ≥100 µs (bus reset)
                   deviceClear()    — DCL (all devices) or SDC (one device)
                   enableRemote()   — assert / de-assert REN
                   send()           — addressed write with configurable terminator
                   read()           — read until EOI, EOS byte, or fixed length
                   readEOS()        — read with explicit EOS character
                   query()          — send() followed by read()
                   serialPoll()     — read status byte (STB) from a device
                   checkSRQ()       — read BSR register to check SRQ line
                 open() performs the full init sequence: attach → REN → IFC,
                 matching what gpib_config does by default.

  main.cpp       C++ command-line entry point, uses NiGpibDriver.
  main.c         Plain-C equivalent entry point.

  interrupt.h    Root-level compat shim; #include_next guarded with
                 #ifdef __linux__ so it is a no-op on macOS/Windows.

  test_concurrent.cpp
                 Test suite.  Mock mode (default): injects a fake
                 gpib_interface to test send/read/query/serialPoll without
                 hardware.  Real hardware mode (-DREAL_HARDWARE): links the
                 actual driver and measures per-call latency against the
                 connected instrument.  Build with Makefile targets
                 test_concurrent (mock) and test_concurrent_hw (hardware).


Upstream source
---------------
  linux-gpib/  — driver source and headers from linux-gpib 4.3.6 (git 9813ff8f)
                 Minimally patched to compile outside the kernel (Linux-only
                 headers wrapped with #ifdef __KERNEL__ or #ifdef __linux__;
                 struct tags added to anonymous typedefs needed for forward
                 declarations; see CLAUDE.md for the full patch list).

License
-------
  GPL-2.0-or-later.  See COPYING.
  Upstream linux-gpib files retain their original copyright and license
  (see linux-gpib/README and linux-gpib/COPYING).
