Chapter 2 — Hardware Topology: PCI, USB, I2C, SPI and How Devices Connect

Why Topology Matters

Hardware devices don’t connect directly to the CPU. They connect through buses — standardized electrical and logical protocols that allow multiple devices to share a communication channel. Understanding buses tells you:

PCI and PCIe: The High-Speed Backbone

PCI (Peripheral Component Interconnect) and its successor PCIe (PCI Express) are the primary buses for high-speed peripherals in a PC: GPUs, NVMe SSDs, network cards, sound cards.

PCI Addressing

Every PCI device is identified by a triplet: domain:bus:device.function (also written DBDF).

0000:00:00.0   ← domain 0, bus 0, device 0, function 0 (CPU host bridge)
0000:00:1f.2   ← domain 0, bus 0, device 31, function 2 (SATA controller)
0000:01:00.0   ← domain 0, bus 1, device 0, function 0 (often a GPU)

lspci: Listing PCI Devices

lspci

Sample output:

00:00.0 Host bridge: Intel Corporation 8th Gen Core Processor Host Bridge/DRAM Registers
00:02.0 VGA compatible controller: Intel Corporation UHD Graphics 620
00:1f.0 ISA bridge: Intel Corporation Cannon Point-LP LPC Controller
00:1f.2 Memory controller: Intel Corporation Cannon Lake PCH Shared SRAM
00:1f.3 Audio device: Intel Corporation Cannon Point-LP High Definition Audio Controller
01:00.0 3D controller: NVIDIA Corporation GP107M [GeForce GTX 1050 Ti]

For more detail:

lspci -v        # verbose: resources, driver in use
lspci -k        # show driver and modules for each device
lspci -nn       # show vendor:device ID numbers (e.g. [8086:0a0c])
lspci -t        # tree view showing bus hierarchy

The vendor:device ID is critical — it’s how the kernel matches hardware to drivers. [8086:0a0c] means Intel (8086) device 0a0c. The kernel’s module alias database maps these IDs to driver module names.

PCIe vs PCI

PCIe replaced PCI around 2004. Key differences:

Feature PCI PCIe
Topology Shared bus Point-to-point links
Bandwidth 133 MB/s 1–64 GB/s per slot
Configuration Memory-mapped Memory-mapped + enhanced

Despite the name change, Linux still calls everything in this family “PCI” in its kernel APIs.

Where PCI Lives in Sysfs

Each PCI device has a directory in /sys/bus/pci/devices/:

ls /sys/bus/pci/devices/
# 0000:00:00.0  0000:00:02.0  0000:00:1f.0  ...

ls /sys/bus/pci/devices/0000:00:02.0/
# class  config  device  driver  enable  irq  resource  vendor  ...

You can read a device’s vendor and class directly:

cat /sys/bus/pci/devices/0000:00:02.0/vendor   # e.g. 0x8086
cat /sys/bus/pci/devices/0000:00:02.0/class    # e.g. 0x030000 (display)

USB: The Universal Serial Bus

USB is a hierarchical tree bus designed for hot-pluggable peripherals: keyboards, storage, cameras, serial adapters, etc.

USB Topology

USB Host Controller (on motherboard, PCI device)
    │
    ├── Root Hub (built into host controller)
    │       │
    │       ├── USB Hub (optional external hub)
    │       │       ├── Keyboard (endpoint 1)
    │       │       └── Mouse (endpoint 1)
    │       │
    │       └── USB Storage device
    │               ├── Endpoint 0 (control)
    │               └── Endpoint 1 (bulk data)
    │
    └── Another Root Hub port

USB devices have:

lsusb: Listing USB Devices

lsusb
Bus 002 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub
Bus 001 Device 003: ID 046d:c52b Logitech, Inc. Unifying Receiver
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
lsusb -t    # tree view
lsusb -v    # verbose: all descriptors

USB devices appear in /sys/bus/usb/devices/ and in /dev/ as ttyUSBx, ttyACMx, /dev/video0, etc., depending on their driver.

I2C: The Two-Wire Protocol

I2C (Inter-Integrated Circuit) is a low-speed, two-wire serial bus: SDA (data) and SCL (clock). It’s ubiquitous in embedded systems and on single-board computers (Raspberry Pi, BeagleBone).

Typical I2C devices: temperature sensors, OLED displays, accelerometers, RTC chips, EEPROMs.

I2C Characteristics

I2C on Linux

I2C buses appear as /dev/i2c-N:

ls /dev/i2c-*
# /dev/i2c-0  /dev/i2c-1  /dev/i2c-2

# List I2C buses
i2cdetect -l

# Scan bus 1 for devices
i2cdetect -y 1

i2cdetect shows a grid of addresses with -- for empty and a hex address for detected devices.

# Reading from an I2C device in Python
import smbus2
bus = smbus2.SMBus(1)          # /dev/i2c-1
data = bus.read_byte_data(0x48, 0x00)   # address 0x48, register 0

SPI: The Four-Wire Protocol

SPI (Serial Peripheral Interface) is faster than I2C, uses 4 wires: MOSI, MISO, SCLK, CS (chip select). It’s used for high-speed devices: ADCs, DACs, flash memory, LCD displays.

SPI vs I2C

Feature I2C SPI
Wires 2 4+
Speed Up to 1 MHz Up to 50+ MHz
Devices 112 per bus One per CS line
Half/Full duplex Half Full
Usage Sensors, EEPROMs Flash, displays, ADCs

SPI on Linux

SPI devices appear as /dev/spidevN.M (bus N, chip select M):

ls /dev/spi*
# /dev/spidev0.0  /dev/spidev0.1
import spidev
spi = spidev.SpiDev()
spi.open(0, 0)           # bus 0, device 0
spi.max_speed_hz = 1000000
response = spi.xfer2([0x01, 0x02])

The Bus Abstraction in the Linux Kernel

The kernel has a bus subsystem that provides a uniform registration model:

bus_type (pci_bus_type, usb_bus_type, i2c_bus_type, ...)
    │
    ├── devices (hardware found on this bus)
    └── drivers (kernel modules that handle devices)

When a device is discovered on a bus, the kernel iterates over registered drivers and calls each driver’s probe() function until one claims the device. This matching is based on device IDs (PCI vendor/device, USB VID/PID, I2C address).

You can see this matching in /sys/bus/:

ls /sys/bus/
# acpi  cpu  i2c  mdio_bus  pci  platform  spi  usb  ...

ls /sys/bus/pci/drivers/
# ahci  e1000e  i915  nvme  xhci_hcd  ...

Each driver directory contains symlinks to the devices it currently manages.


Previous: Chapter 1 — The Linux Kernel

Next: Chapter 3 — Device Drivers: The Translators

Back to Table of Contents