Skip to content

apcmicrolink: back off failed session starts, don't write from missing pages - #3806

Open
JayToltTech wants to merge 4 commits into
networkupstools:masterfrom
JayToltTech:apcmicrolink-retry-guards
Open

JayToltTech wants to merge 4 commits into
networkupstools:masterfrom
JayToltTech:apcmicrolink-retry-guards

Conversation

@JayToltTech

Copy link
Copy Markdown

An SCL500RM1UC (FW UPS 16.5) here on USB reset itself every 4-5 minutes for about 20 minutes this morning, taking everything plugged into it down each time. The driver logged could not complete Microlink startup on every boot in that window, and the resets stopped when I pulled the USB cable. I can't prove the driver caused it, there's no event log I can get off this unit, but reading the code turned up three things I'd rather not have pointed at a UPS that's already misbehaving.

  • After a failed start, upsdrv_updateinfo() retries the handshake every second forever, and every session that gets far enough sends the slave password again. The comment in microlink_authenticate() already suspects a failed handshake of locking out comms until a device reset.
  • microlink_update_blob() zero-fills pages the device never sent, and the descriptor and every write address come from that blob. This unit reports 103 pages and never sends 0x5B.
  • With the descriptor incomplete and the device still answering, the startup loop in upsdrv_initinfo() never exits, since consecutive_timeouts never climbs.

A commit for each, plus the version bump. Retries back off 1 to 60 s. The slave password goes out at most once per 5 minutes, not at all if AUTH_STATUS already reads valid, and only once the pages it's computed from have arrived. Writes are refused when their page never arrived, and startup gives up after 60 s and drops to the HID fallback.

Was going to require every page before accepting the descriptor, but on this unit that never completes, so the checks are per page instead.

Ran it on the SCL500RM1UC for 5 minutes in the foreground at -DD, and it's been in service since. The descriptor comes up with 268 usages despite the missing page. The only write is the 4-byte slave password to page 101, and AUTH_STATUS reads 0x0001 3.5 s later, with no reconnects and no resets. Against a fake Microlink device on a serial pty, master sends 186 INITs in 183 s of silence and this sends 7, then reconnects when the device comes back. A missing page outside the descriptor doesn't block startup, and a descriptor running into a missing page stops at 60 s.

I haven't seen the write refusal or the auth hold-off fire on real hardware, and it's one UPS model. Each commit builds on its own and make spellcheck passes.

Claude Code was used for the code review, the patch and the testing.

Once a session drops into the HID fallback, `upsdrv_updateinfo()` retries the Microlink handshake every `MLINK_SESSION_RETRY_INTERVAL_SEC`, which is 1 s, forever.  Every retry is a STOP and an INIT, and every one that gets far enough sends the slave password again.

On an SCL500RM1UC (FW UPS 16.5) here the UPS reset every 4-5 minutes for about 20 minutes while the driver logged a failed Microlink startup on each boot.  It stopped when I pulled the USB cable.  I can't prove the retry loop is what did it, but a device that's already refusing sessions shouldn't get probed once a second.

This doubles the wait after each failed retry, 1 s up to `MLINK_SESSION_RETRY_MAX_SEC` (60 s), and resets it once a session comes up.  Status still publishes from the HID fallback on every poll in between.

Against a fake Microlink device on a pty that goes silent mid-session, master sent 186 INITs in 183 s and this sends 7, at 1/2/4/8/16/32/60 s.  It reconnects when the device comes back, up to 60 s later than master would.

Signed-off-by: Jay Beavers <jay@tolttechnologies.com>
`microlink_update_blob()` zero-fills any page the device hasn't sent, and the descriptor is parsed from that blob and every write takes its address from it.  The SCL500RM1UC here reports 103 pages and never sends 0x5B, so the blob always has a hole in it.

Was going to require every page before accepting the descriptor, but on this unit that never completes.  So this checks the pages that matter instead.  The descriptor's own bytes have to come from received pages, and `microlink_send_descriptor_write()` refuses a write whose field crosses a page or sits on a page that never arrived.

It also caps the startup loop in `upsdrv_initinfo()` at `MLINK_STARTUP_MAX_SEC` (60 s) once a session answers.  A device that keeps replying but never completes the descriptor holds startup forever otherwise, since `consecutive_timeouts` never climbs.  After the cap it drops to the HID fallback like any other failed startup.

On the real unit the descriptor comes up with 268 usages despite the missing page.  Against a fake device with a missing page outside the descriptor, startup completes, and with the descriptor running into the missing page it stops at 60 s.  I haven't seen the write refusal fire on real hardware.

Signed-off-by: Jay Beavers <jay@tolttechnologies.com>
The comment in `microlink_authenticate()` already suspects a failed handshake of locking out comms until a device reset.  But each new session sends the slave password again, so a device stuck refusing sessions gets one per retry.

This sends it at most once per `MLINK_AUTH_MIN_INTERVAL_SEC` (300 s) across sessions.  It's skipped when AUTH_STATUS already reads valid, and held off until the pages with the serial number and master password it's computed from have actually been received.

On the SCL500RM1UC here the reply goes out once at startup, to page 101, and AUTH_STATUS reads 0x0001 about 3.5 s later.  The hold-off paths haven't fired on real hardware, and the fake device I tested against has no auth.

Signed-off-by: Jay Beavers <jay@tolttechnologies.com>
Copilot AI balanced review requested due to automatic review settings October 29, 2026 18:39

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has insufficient remaining quota.

@github-actions

github-actions Bot commented Oct 29, 2026 •

Copy link
Copy Markdown

Preparing a ZIP file with standard source tarball and another tarball with pre-built docs for commit 894469a ...

Signed-off-by: Jay Beavers <jay@tolttechnologies.com>
@JayToltTech
JayToltTech force-pushed the apcmicrolink-retry-guards branch from 36d26a3 to 894469a Compare October 29, 2026 18:39
@AppVeyorBot

Copy link
Copy Markdown

✅ Build nut 2.8.5.5932-master completed (commit b5e55082e1 by @JayToltTech)

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.

3 participants