Skip to content

feat: driver/sdwire: support unprogrammed FT200X EEPROM + macOS storage/mux fixes#748

Open
mmahut wants to merge 1 commit into
jumpstarter-dev:mainfrom
mmahut:mmahut/sdwire
Open

feat: driver/sdwire: support unprogrammed FT200X EEPROM + macOS storage/mux fixes#748
mmahut wants to merge 1 commit into
jumpstarter-dev:mainfrom
mmahut:mmahut/sdwire

Conversation

@mmahut
Copy link
Copy Markdown
Contributor

@mmahut mmahut commented Jun 5, 2026

Recently, I got an unprogrammed unit from 3mdeb, which did not work out of the box. It had the factory-default FTDI FT200X EEPROM (0x0403/0x6015) rather than the Samsung VID/PID set by sd-mux-ctrl --init. Also, previously the driver was Linux-only (pyudev).

To elaborate:

1. Unprogrammed FT200X support
Also matches 0x0403/0x6015, but only when an explicit serial is configured -- a bare FT200X has no reliable runtime signature, so requiring a serial avoids false-positive matches on unrelated FTDI devices. Also raises a clear error whe multiple programmed SD Wires are present with no serial (instead of silently binding the first).

2. macOS storage discovery
effective_storage_device() now uses system_profiler SPUSBDataType on Darwin (pyudev is Linux-only). The SD reader is correlated to its FT200X as a sibling under the same internal hub, and the disk node is read from the reader's nested
Media[].bsd_name, so the right disk is targeted when several SD Wires are attached. Reads/writes retry discovery (USB re-enumerates after a switch) and fail with a clear error instead of a TypeError.

3. macOS dut() ejects before switching
The mux only switches when the SD bus is idle; diskutil eject (SCSI STOP UNIT) idles it. If the disk can't be determined, dut() aborts rather than risk corrupting a mounted volume.

4. macOS host() power-cycles the reader's hub port
eject leaves the SMSC reader stopped, so host() routes the card back first and then power-cycles port 1 of this SD Wire's internal hub (scoped by USB topology, so other attached devices aren't disturbed) to force a clean re-enumeration without a physical replug.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jun 5, 2026

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 15c428d6-a6cc-4479-b96b-d7f8d574b2ed

📥 Commits

Reviewing files that changed from the base of the PR and between 07d9d0a and 84b2797.

📒 Files selected for processing (4)
  • python/packages/jumpstarter-driver-sdwire/README.md
  • python/packages/jumpstarter-driver-sdwire/jumpstarter_driver_sdwire/driver.py
  • python/packages/jumpstarter-driver-sdwire/jumpstarter_driver_sdwire/driver_test.py
  • python/packages/jumpstarter/jumpstarter/common/storage.py
✅ Files skipped from review due to trivial changes (1)
  • python/packages/jumpstarter/jumpstarter/common/storage.py
🚧 Files skipped from review as they are similar to previous changes (2)
  • python/packages/jumpstarter-driver-sdwire/jumpstarter_driver_sdwire/driver_test.py
  • python/packages/jumpstarter-driver-sdwire/jumpstarter_driver_sdwire/driver.py

📝 Walkthrough

Walkthrough

Refactors SDWire device discovery with platform-specific helpers, adds polling/await helpers for host-side storage availability, updates host/DUT switching with macOS SMSC power-cycling and eject semantics, widens common storage helper types, and adds comprehensive unit tests and a README doctest tweak.

Changes

SDWire Storage Discovery and Switching Refactor

Layer / File(s) Summary
Storage device type widening
python/packages/jumpstarter/jumpstarter/common/storage.py
wait_for_storage_device, write_to_storage_device, and read_from_storage_device now accept str | os.PathLike and return str | os.PathLike where applicable.
Platform-aware device and storage discovery
python/packages/jumpstarter-driver-sdwire/jumpstarter_driver_sdwire/driver.py
Added platform-specific discovery and SMSC hub helpers, Linux pyudev and macOS system_profiler storage lookup, and refactored device enumeration into _find_device to separate programmed sd-wire from FT200X.
Storage polling and await helpers
python/packages/jumpstarter-driver-sdwire/jumpstarter_driver_sdwire/driver.py
Added _poll_storage_device and _await_storage_device to retry discovery until a timeout for macOS re-enumeration and async waits.
Host/DUT switching with power-cycle orchestration
python/packages/jumpstarter-driver-sdwire/jumpstarter_driver_sdwire/driver.py
host() routes selection then power-cycles SMSC port on macOS; dut() finds/ejects the host disk via diskutil, waits, then switches to DUT with error handling and delays.
Write/read operations with await
python/packages/jumpstarter-driver-sdwire/jumpstarter_driver_sdwire/driver.py, python/packages/jumpstarter-driver-sdwire/jumpstarter_driver_sdwire/driver_test.py
write() and read() now await the host-side storage device within storage_timeout, raising RuntimeError on timeout before invoking write_to_storage_device/read_from_storage_device.
Discovery and polling tests
python/packages/jumpstarter-driver-sdwire/jumpstarter_driver_sdwire/driver_test.py
Tests for macOS storage correlation by serial, USB device selection rules (FT200X serial requirement, programmed-device ambiguity), and polling/await retry and timeout behavior.
DUT switching and write-error tests
python/packages/jumpstarter-driver-sdwire/jumpstarter_driver_sdwire/driver_test.py
Tests validating eject-failure abort, successful eject then DUT switch, and write() raising a clear RuntimeError when storage never appears.
Host-mode orchestration and power-cycle scoping tests
python/packages/jumpstarter-driver-sdwire/jumpstarter_driver_sdwire/driver_test.py
Tests ensuring selection happens before SMSC power-cycle on Darwin, skipping power-cycle off Darwin, and scoping power-cycle to matching parent hub topology.
Configuration doctest update
python/packages/jumpstarter-driver-sdwire/README.md
Doctest now hides output and uses ellipsis masking for the FileNotFoundError message expected when sd-wire discovery fails.

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • mangelajo
  • NickCao

"A rabbit hops through code with glee,
finding sd-wire disks on macOS tree.
With polling, power-cycles, serials in sight,
the driver sleeps until the disk is right.
Hooray — small hops, big fixes, and delight!"

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 1.89% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main changes: unprogrammed FT200X EEPROM support and macOS storage/mux fixes, which are the primary objectives of the changeset.
Description check ✅ Passed The description comprehensively explains all major changes across files: FT200X support, macOS storage discovery, macOS dut() eject behavior, and macOS host() power-cycling, all directly related to the changeset.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@raballew raballew requested a review from mangelajo June 5, 2026 15:18
@raballew
Copy link
Copy Markdown
Member

raballew commented Jun 5, 2026

@mangelajo assigned you for reviews as it seems to be macos focused which i can not really test

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
python/packages/jumpstarter-driver-sdwire/jumpstarter_driver_sdwire/driver.py (1)

72-73: ⚡ Quick win

Avoid silent exception swallowing during hub power-cycle.

Line 72-Line 73 hides USB control failures completely, which makes host re-enumeration timeouts hard to diagnose. Log the exception before continuing best-effort behavior.

Proposed refactor
-    except Exception:
-        pass  # best-effort; caller raises if the disk never appears
+    except Exception as e:
+        if logger:
+            logger.warning(f"failed to power-cycle SMSC hub port: {e}")
+        # best-effort; caller raises if the disk never appears
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@python/packages/jumpstarter-driver-sdwire/jumpstarter_driver_sdwire/driver.py`
around lines 72 - 73, The except Exception: pass in
jumpstarter_driver_sdwire/driver.py (the hub power-cycle USB control block)
silently swallows errors; change it to log the exception before continuing
(e.g., use logging.getLogger(__name__).exception(...) or an existing
module/class logger like self.logger.exception) with a clear message such as
"hub power-cycle USB control failed" and then keep the best-effort behavior so
the caller still handles timeout.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@python/packages/jumpstarter-driver-sdwire/jumpstarter_driver_sdwire/driver.py`:
- Around line 83-84: The failure isn't due to converting dev.bus/dev.address to
strings—pyudev accepts ints—so leave the
pyudev.Enumerator.match_attribute("busnum", dev.bus) / match_attribute("devnum",
dev.address") calls as-is, and instead remove the silent broad excepts in
_find_storage_device_linux and _power_cycle_smsc_port: replace "except
Exception: pass" with either narrowly typed exception handlers for the expected
errors or catch Exception but log the caught exception via the module logger
(e.g., logger.exception or logger.error with exception info) so failures are
observable and traceable.

---

Nitpick comments:
In
`@python/packages/jumpstarter-driver-sdwire/jumpstarter_driver_sdwire/driver.py`:
- Around line 72-73: The except Exception: pass in
jumpstarter_driver_sdwire/driver.py (the hub power-cycle USB control block)
silently swallows errors; change it to log the exception before continuing
(e.g., use logging.getLogger(__name__).exception(...) or an existing
module/class logger like self.logger.exception) with a clear message such as
"hub power-cycle USB control failed" and then keep the best-effort behavior so
the caller still handles timeout.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: e7df7584-c909-48e2-9056-c7654fe223e6

📥 Commits

Reviewing files that changed from the base of the PR and between 00bf61a and 43315f2.

📒 Files selected for processing (4)
  • python/packages/jumpstarter-driver-sdwire/README.md
  • python/packages/jumpstarter-driver-sdwire/jumpstarter_driver_sdwire/driver.py
  • python/packages/jumpstarter-driver-sdwire/jumpstarter_driver_sdwire/driver_test.py
  • python/packages/jumpstarter/jumpstarter/common/storage.py

Copy link
Copy Markdown
Member

@mangelajo mangelajo left a comment

Choose a reason for hiding this comment

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

Thank you @mmahut this looks great, only a few comments, and we need to make the linter + ty + typos CI work.

I didn't even dare to fight the OSX battle for this, but I see there was more to it than I even expected. Thanks for making this better!

for block in filter(lambda d: d.subsystem == "block", udevice.parent.children):
for storage_device in filter(lambda link: link.startswith("/dev/disk/by-diskseq/"), block.device_links):
return storage_device
except Exception:
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.

Can we be more explicit about Exception capture? this will also probably fail our linter.

return node.get("serial_num") == serial
vid = node.get("vendor_id", "").lower()
pid = node.get("product_id", "").lower()
return ("0x04e8" in vid and "0x6001" in pid) or ("0x0403" in vid and "0x6015" in pid)
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.

can we create/use constants for the VID/PIDs ?

# The disk node lives under the reader's nested "Media" list, not at top level.
vid = node.get("vendor_id", "").lower()
pid = node.get("product_id", "").lower()
if not ("0x0424" in vid and "0x4050" in pid):
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.

same

try:
import json

out = subprocess.check_output(
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.

nice! :)

@mangelajo
Copy link
Copy Markdown
Member

@mmahut where can I ship you a bunch of Jumpstarter stickers? :D totally deserved! (ping me at majopela@redhat.com ) if you're interested.

Cheers and thanks again!

@mmahut
Copy link
Copy Markdown
Contributor Author

mmahut commented Jun 8, 2026

@mangelajo thank you for the great review! I have addressed these comments and force pushed.

Also, I will never say no to free stickers! :D

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@python/packages/jumpstarter-driver-sdwire/README.md`:
- Line 25: Update the doctest path in the README where
ExporterConfigV1Alpha1DriverInstance.from_path(...) is used: change the YAML
path from "source/api-reference/drivers/sdwire.yaml" to
"source/reference/package-apis/drivers/sdwire.yaml" so the doctest points at the
actual driver YAML location; ensure the line containing
ExporterConfigV1Alpha1DriverInstance.from_path("...").instantiate() is updated
accordingly to match other driver READMEs.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 5bc76f3d-7af3-4f52-810a-5ec24837c0ad

📥 Commits

Reviewing files that changed from the base of the PR and between 43315f2 and 07d9d0a.

📒 Files selected for processing (4)
  • python/packages/jumpstarter-driver-sdwire/README.md
  • python/packages/jumpstarter-driver-sdwire/jumpstarter_driver_sdwire/driver.py
  • python/packages/jumpstarter-driver-sdwire/jumpstarter_driver_sdwire/driver_test.py
  • python/packages/jumpstarter/jumpstarter/common/storage.py
✅ Files skipped from review due to trivial changes (1)
  • python/packages/jumpstarter/jumpstarter/common/storage.py
🚧 Files skipped from review as they are similar to previous changes (2)
  • python/packages/jumpstarter-driver-sdwire/jumpstarter_driver_sdwire/driver_test.py
  • python/packages/jumpstarter-driver-sdwire/jumpstarter_driver_sdwire/driver.py

Comment thread python/packages/jumpstarter-driver-sdwire/README.md Outdated
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