Skip to content

Fix USB report framing and guard configuration writes - #25

Open
elstonj wants to merge 4 commits into
mini-box:masterfrom
elstonj:usb-reliability-fixes
Open

elstonj wants to merge 4 commits into
mini-box:masterfrom
elstonj:usb-reliability-fixes

Conversation

@elstonj

@elstonj elstonj commented Sep 16, 2026

Copy link
Copy Markdown

Four fixes found while bringing up an OpenUPS (v1, firmware 1.5) on Linux with libusb-0.1. The first two are USB framing bugs that affect every model; the others prevent a corrupted configuration being written to flash.

usb_set_altinterface() loses the first report of every session

open() calls usb_set_altinterface(), which resets the host's data toggle. The UPS firmware keeps its own, so the first OUT report after opening is silently dropped and its read times out. On the OpenUPS this reproduced on nearly every open (0 of 8 clean opens before, 15 of 15 after).

This is also why GetStatus() had the clock report commented out as "breaks further communication": the lost report was the cause, not the clock request. With the toggle left alone it reads fine, so the fuel-gauge capacity is available again.

A late reply leaves every later reply one request behind

If a read times out, the reply still arrives and stays queued, including across a reopen. From then on every reply answers the previous request. A status read still appears to work because replies are dispatched by type, but a configuration read is silently shifted. drain() discards anything queued when the device is opened.

A shifted configuration read could be written back to flash

Each page reply was stored at the address that was requested rather than the one the device echoed, so one late reply shifted the whole configuration. -i then wrote that shifted copy back, calibration block included. The reply's echoed address is now checked and the page retried.

Only the offset within the settings block is compared, because on the OpenUPS the echoed page alternates between 0x74xx and 0x78xx from one configuration write to the next.

-i with -s wrote zeros over every unlisted setting

-i overlays the file on the settings read from the device, but -s skips that read, so everything the file does not list was written as zero, including calibration. -i now refuses unless the configuration was read completely.

Testing

  • OpenUPS (0xd004, firmware 1.5): all four fixes, including repeated open/close cycles, configuration read/write with verified readback, and a stale-reply injection test that previously corrupted the readback.
  • OpenUPS2 (0xd005): detection, status polling, repeated opens, configuration read, and the same stale-reply test. Read-only, nothing written to it.
  • DCDC-USB and NUC-UPS: not tested. They share the usbhid change but no model-specific code was touched.

usb_set_altinterface() resets the host's data toggle, but the UPS firmware
keeps its own, so the first OUT report after opening the device was silently
dropped. A reply that arrives after its read timed out also stays queued and
makes every later reply answer the previous request, which corrupts a
configuration read. Drop the altinterface call and drain the endpoint on open.
A page reply that answers an earlier request was stored at the address that
was asked for, so one late reply shifted the whole configuration. Check the
address the device echoes, retry the page, and record whether every page was
read.
-i overlays the file on the settings read from the device, so with -s (which
skips the read) it wrote zeros over every setting the file does not list,
including the calibration block. Refuse the write unless the configuration was
read completely.
The clock report carries the fuel-gauge capacity. It was commented out as
breaking further communication, which was the lost first report of the
session; with that fixed it reads fine. Skip parsing a failed read rather
than parsing a stale buffer.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants