Skip to content

Windows: support only libusb-1.0 (WinUSB) for USB drivers, update wdi-simple - #3791

Open
nmbro wants to merge 10 commits into
networkupstools:masterfrom
nmbro:windows-libusb1
Open

nmbro wants to merge 10 commits into
networkupstools:masterfrom
nmbro:windows-libusb1

Conversation

@nmbro

@nmbro nmbro commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor

Stacked on #3782 (configure-libusb-auto-fallback): its two commits
(build: accept libusb-1.0 from 1.0.9 on, build: fall back to libusb-0.1 if libusb-1.0 is too old) are at the bottom of this branch.
Please review #3782 first. The commits for this PR are the last five.
I'll rebase once #3782 is merged.

What and why

On Windows, libusb-0.1 (libusb-win32 with libusb0.sys, or
libusb-compat) has nothing over libusb-1.0 with the WinUSB driver.
It also lacks the async and threading support some drivers need
(e.g. apcmicrolink). This branch makes libusb-1.0 the only USB
backend for MinGW/MSYS2 builds.

  1. scripts/Windows: update wdi-simple for WinUSB
    • The driver installer still installed libusb0.sys, which
      libusb-1.0 cannot use without libusbK.dll. It now installs
      WinUSB by default, with libusbK as an option.
    • Bug fixes:
      • It looped until a 0xFFFF/0xFFFF entry, but the generated
        nutscan-usb.h table ends with {0, 0}, so it read past the
        end.
      • It could return an uninitialized value.
      • It passed an extra printf argument.
      • It never parsed its options or freed the device list.
    • Composite devices are now matched on their first interface.
    • The README was rewritten for current libwdi (the old SourceForge
      wiki and ddk_build.cmd are gone). The WiX installer text now
      points at Zadig/WinUSB.
  2. build: remove libusb-0.1 support for Windows
    • configure refuses --with-usb=libusb-0.1 for mingw targets,
      ignores an auto-detected libusb-0.1 there, and no longer looks
      for lusb0_usb.h or in /mingw64/lib.
    • libusb0.c errors out on WIN32 and loses its Windows-only code.
    • blazer_usb.c no longer skips LIBUSB_ERROR_OVERFLOW on
      Windows; that was a libusb-win32 limitation.
    • Docs, nut-usbinfo.pl and AppVeyor drop libusb-win32 and
      libusb-compat.
    • NEWS and UPGRADING are updated: packagers depend on libusb-1.0,
      and devices bound to libusb0.sys must be re-bound to WinUSB.
  3. fix(wincompat): drop libusb-win32 ETIMEDOUT fallback
    • #define ETIMEDOUT 116 came from libusb-win32's error.h.
      MinGW's errno.h defines it as 138, so the fallback was dead
      code, and with libusb-0.1 gone its FIXME no longer applies.
  4. docs(install): document WinUSB/Zadig setup for USB devices
  5. fix(nut-scanner): call libusb-1.0 methods with LIBUSB_CALL
    • Found while testing 32-bit builds of this branch: nut-scanner -U
      crashed (SIGSEGV in libusb_get_device_descriptor@8). On 32-bit
      Windows, libusb-1.0 exports its methods as __stdcall
      (LIBUSB_CALL is WINAPI there), but nut-scanner called them
      through default cdecl function pointers, which misaligned the
      stack. The pointers are now declared with LIBUSB_CALL.
    • LIBUSB_CALL is empty on other platforms, and 64-bit Windows has
      a single calling convention, so nothing else changes.
    • This was already broken on master. It matters more now that
      libusb-1.0 is the only USB backend on Windows.

Closes: #1507

Testing

Native MSYS2 UCRT64 on Windows 11 (fresh install), gcc 16.2.0 (Rev6),
libusb 1.0.30. Configured with
--with-usb --enable-Werror CFLAGS='-g -O2 -D_WIN32_WINNT=0x0601'
(Windows 7 API level):

  • configure: build USB drivers: yes (libusb-1.0).
  • make with -Werror: no warnings.
    • Caveat: on master, UCRT64 -Werror builds also fail in
      common/strptime.c ('tzname' is deprecated ... UCRT). That is
      unrelated to this PR and fixed in a separate branch. For this
      test, only that file was temporarily replaced with the fixed one.
  • make check: 16 passed, 1 skipped (become_user() needs root).
    make check took about 2 minutes.

Real hardware: APC Smart-UPS 500 (051d:0003, FW UPS 16.1 / ID=1052),
bound to WinUSB with Zadig, read-only:

  • nut-scanner -U found it: driver = "usbhid-ups"
    (alternately apc_modbus apcmicrolink), vendorid = "051D",
    productid = "0003", with serial and bus. Exit code 0.
  • apcmicrolink -d 1 -x port=auto: Microlink HID tunnel found (Output
    Report 0x90, Input Report 0x89), ups.status: OL,
    battery.charge: 100, driver.version.usb: libusb-1.0.30 (API: 0x0100010C). Exit code 0.
  • usbhid-ups -d 1 -x port=auto, for comparison: APC HID 0.102
    subdriver, ups.status: OL, battery.runtime: 1348, the same libusb
    version. Exit code 0.
  • Earlier (on the first commits): apcmicrolink and nut-scanner with
    the same UPS on Windows 10.

32-bit: MSYS2 MINGW32 (i686, MSVCRT), gcc 16.2.0, libusb 1.0.30 built
from source (MSYS2 no longer packages libusb for MINGW32), same
configure flags:

  • Two existing master bugs had to be worked around with files from a
    separate branch with Windows fixes; they are unrelated to this PR:
    • configure fails when CFLAGS are set, because
      m4/nut_stash_warnings.m4 overwrites the TMP environment variable,
      and gcc then cannot create its temporary files.
    • PRIuSIZE is hard-coded "llu" for all Windows builds, but size_t
      is 32 bits on WIN32, so 2 tests fail (parseconf-test,
      nutdrv_qx_cypress_utest).
  • With those two fixes: make with -Werror gives no warnings, and
    make check passes 14 of 14 tests (1 skipped, become_user()).
  • Real UPS, read-only, 32-bit binaries:
    • apcmicrolink and usbhid-ups: ups.status: OL, libusb-1.0.30.
    • nut-scanner -U: crashed before commit 5; finds the UPS with it.
  • Timings (8-core machine): configure 290 s, make 323 s, make check
    187 s with the PRIuSIZE failures and 110 s without them.
  • Commit 5 was also rebuilt for UCRT64 (64-bit) with -Werror: no
    warnings, and nut-scanner -U still finds the UPS.

Not tested:

  • The 64-bit MINGW64 (MSVCRT) environment locally: current MSYS2 no
    longer packages libusb for MINGW64 (only for UCRT64 and CLANG64).
    AppVeyor still builds there, with the libusb package from its older
    MSYS2 image, so CI covers that build.
  • wdi-simple binding a device itself: Zadig was used.
  • The WiX installer.
  • Any device other than this UPS.
  • AppVeyor CI on this branch, which runs once the PR is opened.

AI disclosure

Prepared with Claude Code (Claude Opus 5.5, model claude-opus-5-5):
code, docs, test runs and this description. I reviewed the changes and
ran the hardware tests with my own UPS. I take responsibility for the
submission.

🤖 Generated with Claude Code

nmbro and others added 7 commits October 19, 2026 02:31
configure required libusb_strerror() (libusb 1.0.16+, see networkupstools#509) to use
the libusb-1.0 API, so e.g. the libusb1 1.0.9 package of CentOS 6 was
rejected. Building against it showed only one other method missing:
libusb_get_version().

* configure: accept libusb_error_name() (1.0.9+) when libusb_strerror()
  is missing, and check for libusb_get_version().
* usb-common.h: map libusb_strerror() to libusb_error_name() when the
  former is missing; messages then show the error code name.
* libusb1.c: without libusb_get_version(), report the libusb version
  in driver.version.usb as unknown.
* nut-scanner: when the libusb loaded at run time lacks
  libusb_strerror(), use libusb_error_name(); treat a missing
  libusb_get_version() as an unknown (old) version instead of failing
  to load the library. Mirror struct libusb_version for builds against
  headers that lack it.

Tested by building against the headers and libraries from the CentOS
6.10 RPMs (host GCC), and by running nut-scanner built against libusb
1.0.27 with the 1.0.9 library and vice versa. Not tested with devices.

Refs: networkupstools#3781

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: nmbro <nmbro@users.noreply.github.com>
With --with-usb=auto (or yes), configure picks libusb-1.0 whenever
pkg-config knows it. If that libusb-1.0 then failed the usability
checks, the libusb-0.1 checks were never tried and configure silently
built no USB drivers, even with a usable libusb-0.1 installed.

When the libusb-1.0 picked by preference does not link with
libusb_init() and libusb_error_name() (libusb 1.0.9+), and a libusb-0.1
was also detected, switch to libusb-0.1 before computing the flags and
say so in a notice. An explicit --with-usb=libusb-1.0, or custom
--with-usb-includes/--with-usb-libs, still fails as before.

Tested with the CentOS 6.0 libusb1 1.0.3 + libusb 0.1.12 RPM contents
(falls back, builds all drivers), 1.0.9 + 0.1.12 (keeps 1.0.9), and the
host libusb 1.0.27 (unchanged), including a cached configure re-run.

Closes: networkupstools#3781

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: nmbro <nmbro@users.noreply.github.com>
The USB driver installer for Windows still installed the libusb-win32
libusb0.sys driver, which libusb-1.0 cannot use without libusbK.dll.
Install WinUSB by default instead, with libusbK as an option.

Also fix bugs in it: it looped until a 0xFFFF/0xFFFF entry, but the
generated nutscan-usb.h table now ends with {0, 0}, so it read past
the end; it could return an uninitialized value; it passed an extra
printf argument; it never parsed its options or freed the device list.
Composite devices are now matched on their first interface.

Rewrite the README for current libwdi (the old SourceForge wiki and
ddk_build.cmd are gone, and nutscan-usb.h now needs NUT's config.h),
and point the WiX installer text at Zadig/WinUSB.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017MHaw4FzL87GMq4cHFMVzt
Signed-off-by: nmbro <nmbro@users.noreply.github.com>
libusb-0.1 (libusb-win32 or libusb-compat) brings nothing to Windows
builds that libusb-1.0 with the WinUSB driver does not, and lacks the
async and threading support some drivers (e.g. apcmicrolink) need.
libusb-1.0 builds were tested with apcmicrolink and nut-scanner against
a real UPS on Windows 10.

configure now refuses --with-usb=libusb-0.1 for mingw targets, ignores
an auto-detected libusb-0.1 there, and no longer looks for the
libusb-win32 lusb0_usb.h header or in /mingw64/lib. libusb0.c errors
out on WIN32 and loses its Windows-only code paths; blazer_usb.c no
longer skips LIBUSB_ERROR_OVERFLOW on Windows (that was a libusb-win32
limitation). Docs and AppVeyor drop libusb-win32 and libusb-compat.

Closes: networkupstools#1507

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017MHaw4FzL87GMq4cHFMVzt
Signed-off-by: nmbro <nmbro@users.noreply.github.com>
Value 116 came from libusb-win32 error.h; with libusb-0.1 support
removed for Windows it only risks clashing with MinGW errno.h (138).

Signed-off-by: nmbro <nmbro@users.noreply.github.com>
Add a Windows USB section; mark the historic MSI notes as
libusb-win32 era and point to the WiX installer.

Refs networkupstools#1485

Signed-off-by: nmbro <nmbro@users.noreply.github.com>
On 32-bit Windows, libusb-1.0 exports its methods as __stdcall
(LIBUSB_CALL is WINAPI there). nut-scanner called them through
default (cdecl) function pointers, which misaligned the stack:
"nut-scanner -U" crashed in libusb_get_device_descriptor().

Declare the pointers (and the fallback string-descriptor method
assigned to one of them) with LIBUSB_CALL for libusb-1.0 builds.
It expands to nothing on other platforms, and 64-bit Windows has
a single calling convention, so only 32-bit Windows changes.

Signed-off-by: nmbro <nmbro@users.noreply.github.com>
@github-actions

github-actions Bot commented Oct 20, 2026 •

Copy link
Copy Markdown

A ZIP file with standard source tarball and another tarball with pre-built docs for commit e2ed286 is temporarily available: NUT-tarballs-PR-3791.zip.

@jimklimov jimklimov added Windows USB portability We want NUT to build and run everywhere possible labels Oct 21, 2026
@jimklimov jimklimov added this to the 2.8.6 milestone Oct 21, 2026
@jimklimov jimklimov added documentation CI Entries related to continuous integration infrastructure (here CI = tools + scripts + recipes) labels Oct 21, 2026

@jimklimov jimklimov left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Quite solid, thanks!

Resolve conflicts with master in NEWS.adoc, UPGRADING.adoc (both sides
added entries; keep both) and docs/nut.dict (word count header, set
to the merged word count). Add the PR number to this branch's NEWS
and UPGRADING references, and one to the nut-scanner LIBUSB_CALL
bullet which had none.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: nmbro <nmbro@users.noreply.github.com>
@github-actions github-actions Bot deleted a comment from AppVeyorBot Oct 21, 2026
@nmbro
nmbro marked this pull request as ready for review October 21, 2026 16:21
The libusb-1.0.9 and libusb-0.1 fallback entries come from the commits of PR networkupstools#3782, which this branch builds on; use the same references there and here, so either PR merges cleanly after the other.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: nmbro <nmbro@users.noreply.github.com>
@github-actions github-actions Bot deleted a comment from AppVeyorBot Oct 23, 2026
@AppVeyorBot

Copy link
Copy Markdown

✅ Build nut 2.8.5.5888-master completed (commit 19c34d3158 by @jimklimov)

@AppVeyorBot

Copy link
Copy Markdown

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

Labels

CI Entries related to continuous integration infrastructure (here CI = tools + scripts + recipes) documentation portability We want NUT to build and run everywhere possible USB Windows

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Revise libusb-1.0 usage in Windows branch

3 participants