Skip to content

Instantly share code, notes, and snippets.

@aarmea
Created September 27, 2025 03:30
Show Gist options
  • Select an option

  • Save aarmea/0b13d2dbc655be171e5496fa1f657895 to your computer and use it in GitHub Desktop.

Select an option

Save aarmea/0b13d2dbc655be171e5496fa1f657895 to your computer and use it in GitHub Desktop.
#!/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