Skip to content

Instantly share code, notes, and snippets.

@JaciBrunning
Last active May 25, 2024 13:20
Show Gist options
  • Save JaciBrunning/6be34dfd11b7b8cfa0aab57ad260c518 to your computer and use it in GitHub Desktop.
Save JaciBrunning/6be34dfd11b7b8cfa0aab57ad260c518 to your computer and use it in GitHub Desktop.
USB -> PCIe -> IOMMU viewer for linux
lsusb = `lsusb`.split("\n").reject { |x| x.include?("root") }.map do |e|
ar = e.scan(/Bus (\d+) Device \d+: ID [0-9a-f:]+ (.*)/).flatten
ar[0] = "usb#{ar[0].to_i}"
ar
end
sysbus = `ls -l /sys/bus/usb/devices`.split("\n").map do |e|
e.scan(/(usb\d+) -> .+\/([0-9a-f:\.]+)\/usb\d+/).flatten
end.reject { |x| x.empty? }
iommu = Dir.glob("/sys/kernel/iommu_groups/*/devices/*").map do |e|
arr = e.scan(/iommu_groups\/(\d+)\/devices\/([0-9a-f:\.]+)/).flatten
arr[0] = arr[0].to_i
pci = `lspci -nns #{arr[1]}`.strip.scan(/\[([0-9a-f:]+)\]/).flatten.last
[arr, pci].flatten
end
mapped = {}
lsusb.map do |usb, name|
mapped[usb] ||= { devices: [] }
pci = sysbus.find { |x| x.first == usb }[1]
mmu = iommu.find { |x| x[1] == pci }
mapped[usb][:devices] << name
mapped[usb][:pci] = pci
mapped[usb][:pcidetail] = mmu.last
mapped[usb][:iommu] = mmu.first
mapped[usb][:iommu_count] = iommu.find_all { |x| x.first == mmu.first }.count
end
mapped.sort.each do |k, v|
puts k
puts " - PCI: #{v[:pci]} [#{v[:pcidetail]}]"
puts " - IOMMU: Group #{v[:iommu]} (#{v[:iommu_count]} device(s) on group)"
puts " - Devices:"
puts v[:devices].map { |x| " - #{x}" }.join("\n")
end
@JaciBrunning
Copy link
Author

JaciBrunning commented Nov 27, 2018

Designed to identify what USB controller is used in each IOMMU group for guest isolation with VFIO

Example output:

➜ ruby usbiommu.rb
usb1
 - PCI: 0000:02:00.0 [1022:43b9]
 - IOMMU: Group 15 (12 device(s) on group)
 - Devices:
  - Corsair
usb3
 - PCI: 0000:04:00.0 [1b21:1242]
 - IOMMU: Group 15 (12 device(s) on group)
 - Devices:
  - Cambridge Silicon Radio, Ltd Bluetooth Dongle (HCI mode)
usb5
 - PCI: 0000:0c:00.3 [1022:145c]
 - IOMMU: Group 20 (1 device(s) on group)
 - Devices:
  - Holtek Semiconductor, Inc.
  - Microsoft Corp. Wheel Mouse Optical
  - Kingston Technology

@regunakyle
Copy link

Sorry for necro, but I wrote a Python version of your script (so that I don't have to install ruby just for running the script).
Here it is:

#!/usr/bin/env python3

# This is a Python rewrite this of Ruby script:
# https://gist.github.com/JaciBrunning/6be34dfd11b7b8cfa0aab57ad260c518

import re
import subprocess
from pathlib import Path


def flatten(xss):
    return [x for xs in xss for x in xs]


lsusb_re = re.compile(r"Bus (\d+) Device \d+: ID [0-9a-f:]+ (.*)")
lsusb = []
for usb in subprocess.run("lsusb", capture_output=True, encoding="utf-8").stdout.split(
    "\n"
):
    if "root" not in usb and lsusb_re.search(usb):
        match = flatten(lsusb_re.findall(usb))
        match[0] = f"usb{int(match[0])}"
        lsusb.append(match)


sysbus_re = re.compile(r"(usb\d+) -> .+\/([0-9a-f:\.]+)\/usb\d+")
sysbus_list = flatten(
    [
        sysbus_re.findall(sysbus)
        for sysbus in subprocess.run(
            ["ls", "-l", "/sys/bus/usb/devices"], capture_output=True, encoding="utf-8"
        ).stdout.split("\n")
        if sysbus_re.search(sysbus)
    ]
)

iommu_re = re.compile(r"iommu_groups\/(\d+)\/devices\/([0-9a-f:\.]+)")
pci_re = re.compile(r"\[([0-9a-f:]+)\]")
iommu_list = []
for path in Path("/sys/kernel/iommu_groups").glob("*/devices/*"):
    if iommu_re.search(str(path)):
        match = flatten(iommu_re.findall(str(path)))
        output = subprocess.run(
            ["lspci", "-nns", match[1]], capture_output=True, encoding="utf-8"
        ).stdout
        if pci_re.search(output):
            match.append(pci_re.findall(output)[-1])
            iommu_list.append(match)

mapped = {}
for usb in lsusb:
    if usb[0] not in mapped:
        mapped[usb[0]] = {"devices": []}
        mapped[usb[0]]["iommu_count"] = 0

        iommu_group = -1

        for sysbus in sysbus_list:
            if sysbus[0] == usb[0]:
                mapped[usb[0]]["pci"] = sysbus[1]
                break

        for iommu in iommu_list:
            if iommu[1] == mapped[usb[0]]["pci"]:
                mapped[usb[0]]["iommu"] = iommu[0]
                mapped[usb[0]]["pcidetail"] = iommu[-1]
                iommu_group = iommu[0]
                break

        for iommu in iommu_list:
            if iommu[0] == iommu_group:
                mapped[usb[0]]["iommu_count"] += 1

    mapped[usb[0]]["devices"].append(usb[1])


for usb_id, usb_dict in mapped.items():
    devices = ""
    for device in usb_dict["devices"]:
        devices += f"\n  - {device}"

    print(f"""{usb_id}
 - PCI: {usb_dict["pci"]} [{usb_dict["pcidetail"]}]
 - IOMMU: Group {usb_dict["iommu"]} ({usb_dict["iommu_count"]} device(s) in the same IOMMU group)
 - USB devices: {devices}""")

Sample output:

usb1
 - PCI: 0000:04:00.0 [1b21:3241]
 - IOMMU: Group 23 (1 device(s) in the same IOMMU group)
 - USB devices: 
  - Genesys Logic, Inc. Hub
  - Genesys Logic, Inc. SD Card Reader and Writer
  - Microsoft Corp. Xbox360 Controller
usb3
 - PCI: 0000:07:00.1 [1022:149c]
 - IOMMU: Group 20 (4 device(s) in the same IOMMU group)
 - USB devices: 
  - Integrated Technology Express, Inc. RGB LED Controller
  - Genesys Logic, Inc. Hub
  - Genesys Logic, Inc. Hub
  - JMTek, LLC. USB PnP Audio Device
usb4
 - PCI: 0000:07:00.1 [1022:149c]
 - IOMMU: Group 20 (4 device(s) in the same IOMMU group)
 - USB devices: 
  - Genesys Logic, Inc. GL3523 Hub
  - SanDisk Corp. SanDisk 3.2 Gen1
usb5
 - PCI: 0000:07:00.3 [1022:149c]
 - IOMMU: Group 20 (4 device(s) in the same IOMMU group)
 - USB devices: 
  - Intel Corp. AX200 Bluetooth
  - Genesys Logic, Inc. Hub
  - Keychron Keychron K8 Pro
  - Logitech, Inc. Logi Bolt Receiver

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment