Created
September 27, 2025 03:30
-
-
Save aarmea/0b13d2dbc655be171e5496fa1f657895 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env bash | |
| set -euo pipefail | |
| # usb-port-to-pci.sh | |
| # Map a USB device/port to the backing PCI USB controller (BDF like 0000:03:00.0). | |
| # Usage: | |
| # ./usb-port-to-pci.sh --watch | |
| # ./usb-port-to-pci.sh 1:3 # BUS:DEV from `lsusb` | |
| # ./usb-port-to-pci.sh 046d:c332 # VID:PID from `lsusb` | |
| # ./usb-port-to-pci.sh --bus 1 # show which PCI controller owns USB bus 1 | |
| # | |
| # Tip: run with sudo for best results (some sysfs is root-readable). | |
| is_pci_dir() { | |
| local d="$1" | |
| [[ -L "$d/subsystem" && "$(readlink -f "$d/subsystem")" == "/sys/bus/pci" ]] | |
| } | |
| find_pcidir_for_sysfs_node() { | |
| # Walk up from a usb device sysfs node to its PCI parent dir | |
| local node="$1" | |
| local cur | |
| cur="$(readlink -f "$node")" | |
| while [[ "$cur" != "/" ]]; do | |
| if is_pci_dir "$cur"; then | |
| echo "$cur" | |
| return 0 | |
| fi | |
| cur="$(dirname "$cur")" | |
| done | |
| return 1 | |
| } | |
| describe_pcidir() { | |
| local pcidir="$1" | |
| local bdf vendor device | |
| bdf="$(basename "$pcidir")" | |
| vendor="$(sed 's/^0x//' "$pcidir/vendor" 2>/dev/null || true)" | |
| device="$(sed 's/^0x//' "$pcidir/device" 2>/dev/null || true)" | |
| echo "PCI controller: $bdf (vendor:device ${vendor}:${device})" | |
| if command -v lspci >/dev/null 2>&1; then | |
| echo "lspci: $(lspci -nn -s "$bdf" || true)" | |
| fi | |
| } | |
| find_usb_sysfs_by_busdev() { | |
| local bus="${1%:*}" dev="${1#*:}" | |
| # Locate a usb device dir whose busnum/devnum match | |
| local d | |
| for d in /sys/bus/usb/devices/*; do | |
| [[ -f "$d/busnum" && -f "$d/devnum" ]] || continue | |
| if [[ "$(cat "$d/busnum")" == "$bus" && "$(cat "$d/devnum")" == "$dev" ]]; then | |
| echo "$d" | |
| return 0 | |
| fi | |
| done | |
| return 1 | |
| } | |
| find_usb_sysfs_by_vidpid() { | |
| local vidpid="$1" | |
| local vid="${vidpid%:*}" pid="${vidpid#*:}" | |
| vid="${vid,,}"; pid="${pid,,}" | |
| local d | |
| for d in /sys/bus/usb/devices/*; do | |
| [[ -f "$d/idVendor" && -f "$d/idProduct" ]] || continue | |
| if [[ "$(tr 'A-Z' 'a-z' < "$d/idVendor")" == "$vid" && \ | |
| "$(tr 'A-Z' 'a-z' < "$d/idProduct")" == "$pid" ]]; then | |
| echo "$d" | |
| return 0 | |
| fi | |
| done | |
| return 1 | |
| } | |
| map_bus_to_pci() { | |
| local bus="$1" | |
| local root="/sys/bus/usb/devices/usb${bus}" | |
| if [[ ! -e "$root" ]]; then | |
| echo "USB bus ${bus} not found" >&2 | |
| exit 1 | |
| fi | |
| local pcidir | |
| pcidir="$(find_pcidir_for_sysfs_node "$root")" || { | |
| echo "Could not find PCI parent for USB bus ${bus}" >&2; exit 1; } | |
| describe_pcidir "$pcidir" | |
| } | |
| watch_mode() { | |
| echo "Watching for USB device add events… (Ctrl-C to quit)" | |
| udevadm monitor --kernel --subsystem-match=usb | \ | |
| while read -r line; do | |
| if grep -q "add" <<<"$line"; then | |
| # pick the newest usb device with devpath (DEVTYPE=usb_device) | |
| local last | |
| last="$(ls -1dt /sys/bus/usb/devices/* 2>/dev/null | head -n1 || true)" | |
| [[ -n "$last" ]] || continue | |
| if [[ -f "$last/busnum" && -f "$last/devnum" ]]; then | |
| local bus dev pcidir | |
| bus="$(cat "$last/busnum")" | |
| dev="$(cat "$last/devnum")" | |
| echo | |
| echo "Detected device on USB ${bus}:${dev} ($(cat "$last/idVendor" 2>/dev/null):$(cat "$last/idProduct" 2>/dev/null))" | |
| pcidir="$(find_pcidir_for_sysfs_node "$last")" || { echo "No PCI parent found"; continue; } | |
| describe_pcidir "$pcidir" | |
| fi | |
| fi | |
| done | |
| } | |
| if [[ "${1:-}" == "--watch" ]]; then | |
| watch_mode | |
| exit 0 | |
| elif [[ "${1:-}" == "--bus" && -n "${2:-}" ]]; then | |
| map_bus_to_pci "$2" | |
| exit 0 | |
| elif [[ -n "${1:-}" ]]; then | |
| arg="$1" | |
| usbdir="" | |
| if [[ "$arg" =~ ^[0-9]+:[0-9]+$ ]]; then | |
| usbdir="$(find_usb_sysfs_by_busdev "$arg" || true)" | |
| elif [[ "$arg" =~ ^[0-9a-fA-F]{4}:[0-9a-fA-F]{4}$ ]]; then | |
| usbdir="$(find_usb_sysfs_by_vidpid "$arg" || true)" | |
| else | |
| echo "Unrecognized argument: $arg" >&2 | |
| exit 2 | |
| fi | |
| if [[ -z "$usbdir" ]]; then | |
| echo "Could not locate USB device for '$arg'." >&2 | |
| exit 1 | |
| fi | |
| pcidir="$(find_pcidir_for_sysfs_node "$usbdir")" || { echo "No PCI parent found"; exit 1; } | |
| echo "USB device sysfs: $usbdir" | |
| describe_pcidir "$pcidir" | |
| exit 0 | |
| fi | |
| # Default: list every USB root bus -> PCI controller | |
| echo "USB root buses -> PCI controllers:" | |
| for root in /sys/bus/usb/devices/usb*; do | |
| [[ -f "$root/busnum" ]] || continue | |
| bus="$(cat "$root/busnum")" | |
| pcidir="$(find_pcidir_for_sysfs_node "$root" || true)" | |
| if [[ -n "$pcidir" ]]; then | |
| printf " Bus %s -> %s\n" "$bus" "$(basename "$pcidir")" | |
| fi | |
| done |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment