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:
lspci and lsusb output meaningfullyPCI (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.
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)
0000, unless you have multiple PCIe root complexeslspci
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 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.
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 is a hierarchical tree bus designed for hot-pluggable peripherals: keyboards, storage, cameras, serial adapters, etc.
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
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 (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 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 (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.
| 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 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 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