When you take over an unfamiliar Linux machine — a new server rack, a hand-me-down workstation, a cloud instance with a mystery instance type — the first thing you need is a clear picture of the hardware underneath the OS. Kernel logs tell part of the story, but four command-line tools give you the complete picture: lspci, dmidecode, lshw, and inxi. Each reads from different sources, covers different hardware layers, and serves a different use case. This article covers all four in enough depth to use them confidently in production.
Most of these commands either require root or produce significantly richer output when run as root. Where sudo matters, it's noted explicitly. On systems with the wheel group configured for sudo access, substitute as appropriate for your environment.
Click any node above to see what it represents and how it connects to the toolchain.
lspci: Reading the PCI Bus
lspci is part of the pciutils package, maintained by Martin Mares at github.com/pciutils/pciutils. The current release is pciutils 3.14.0 (June 21, 2025), which added VirtIO SharedMemory capability decoding, Physical Layer speeds from 16 to 64 GT/s, Flit Mode decoding, and new PCI class and capability definitions from PCI Code and ID Assignment rev 1.18. The previous 3.13.0 release added CXL 1.1 link status decoding and introduced pcilmr for PCIe link margin testing. It also ships setpci for writing PCI config registers and update-pciids for refreshing the local device name database. lspci itself queries the kernel\'s PCI subsystem — specifically /sys/bus/pci/devices/ and /proc/bus/pci — to enumerate every device connected over PCI, PCIe, or a PCI bridge. That covers the vast majority of modern system components: GPUs, NVMe controllers, network adapters, USB host controllers, SATA controllers, and sound cards.
The lspci(8) man page describes the tool as a utility for displaying information about PCI buses and the devices connected to them. — lspci(8) man page, pciutils project (Martin Mares, pciutils/pciutils)
On Debian/Ubuntu systems, install it with apt install pciutils. On Fedora/RHEL it comes pre-installed; on Arch, pacman -S pciutils. Basic output is available to non-root users, but some configuration space fields — particularly extended config space — are restricted to root.
The default output is deliberately compact — one line per device showing the bus address, device class, and a short description. For production use you'll almost always want more:
# Verbose output -- device capabilities, interrupt lines, memory regions $ lspci -v # Show kernel driver in use and all available kernel modules for each device $ lspci -k # Show numeric vendor:device IDs alongside human-readable names $ lspci -nn # Machine-readable output -- useful for scripting $ lspci -mm # Show bus tree topology $ lspci -t # Query the online PCI ID database for unknown device names # Results are cached in $XDG_CACHE_HOME/pci-ids for subsequent runs $ lspci -q # Combine: full verbose output with driver info and numeric IDs $ lspci -nnkv # Maximum verbosity (three levels) -- hex config space dump; root only for full output $ sudo lspci -vvv # Hex dump of the standard config space header (64 bytes, or 128 bytes for CardBus bridges) $ sudo lspci -x # Hex dump of the full 256-byte legacy config space -- DANGEROUS on rare buggy devices $ sudo lspci -xxx # Hex dump of the full 4096-byte EXTENDED config space (PCIe/PCI-X 2.0 only) # Only works via linux_sysfs backend -- not available via legacy I/O port access $ sudo lspci -xxxx # Bus-centric view: shows IRQs and addresses as the PCI bus sees them, # NOT as remapped by the kernel -- useful for IOMMU/VFIO passthrough debugging $ lspci -b # Always show PCI domain numbers (suppressed by default on single-domain machines) $ lspci -D # Bus mapping mode: brute-force scan including devices behind misconfigured bridges # WARNING: root only; CAN CRASH machines with buggy devices; debugging use only $ sudo lspci -M
The -k flag is particularly useful when troubleshooting driver issues. It shows both the kernel driver currently bound to the device and any other kernel modules that could handle it. This is how you quickly confirm whether your 10GbE adapter is running ixgbe or mlx5_core, and whether the right one is actually loaded.
PCI Configuration Space: Three Tiers You Need to Know
Every PCI and PCIe device exposes configuration registers that lspci reads. There are actually three distinct tiers, and which flags you use determines which tier you're reading. The standard header (-x) covers only the first 64 bytes — the mandatory header fields: vendor ID, device ID, command/status registers, BAR addresses, and interrupt lines. The legacy full config space (-xxx) extends this to 256 bytes and includes the PCI capability linked list (MSI, power management, PCIe link capability). The extended config space (-xxxx) reaches the full 4096 bytes defined by the PCIe Enhanced Configuration Access Mechanism (ECAM) — this is where AER (Advanced Error Reporting), ASPM (Active State Power Management), ARI, and other PCIe-specific extended capabilities live.
Two things are worth knowing precisely here. First, -xxxx access works only via the linux_sysfs backend — it is not available through legacy I/O port access (mechanism 1 or 2) because ECAM maps each device's 4 KiB config space into a contiguous 256 MiB physical memory window. Second, the -xxx flag carries a documented warning in the lspci man page: some PCI devices crash when arbitrary portions of their config space are read, because the read triggers side effects in the device's state machine. This is noted as "stupid" behavior but acknowledged as real. The same risk does not apply to -xxxx (extended space) because PCIe devices are required to handle reads to undefined registers gracefully by returning 0xFFFF.
When lspci -vvv output shows a <?> marker inside a capability block, it means the tool encountered a register it could not decode — typically because the vendor has not published the capability structure definition. The <?> is not an error; it is a transparency marker in the source code signaling that more data exists but the authors lacked the documentation to interpret it. If you see it on a device you own, the raw bytes are still present in the hex dump and cross-referenceable against the device's datasheet.
Filtering lspci Output
On dense server hardware with dozens of PCI devices, piping through grep is common. But lspci also has a built-in filter with -d for vendor and device ID, and -s for bus address. The -d flag accepts the format vendor:device using the numeric IDs shown by -nn:
# Show only NVIDIA devices (vendor ID 10de) $ lspci -d 10de: -nnkv # Show only network controllers $ lspci | grep -i 'network\|ethernet\|wireless' # Show only storage controllers $ lspci | grep -i 'sata\|nvme\|raid\|storage' # Examine a specific device by bus address (e.g., 03:00.0) $ lspci -s 03:00.0 -vvv
The PCI ID database lspci uses to translate numeric IDs to vendor names lives at /usr/share/misc/pci.ids or /usr/share/hwdata/pci.ids. If a device shows as Unknown device, run sudo update-pciids to pull a fresh copy from the pci-ids.ucw.cz database. Alternatively, lspci -q queries the central database over the network in real time without updating the local file — useful on air-gapped systems where you want a one-off lookup without modifying system files. This matters on new hardware where the distro's packaged database predates the device.
Four lspci Behaviors That Most Users Have Never Encountered
Offline analysis with -F. You can run lspci -x on a target machine to dump the raw PCI configuration registers to a file, then copy that file to another machine and re-analyze it with lspci -F <file> — no hardware access required. The man page describes this specifically as useful for analyzing user-supplied bug reports: you can display the hardware configuration in any form without requesting more dumps from the user. This is the PCI equivalent of dmidecode --from-dump and is entirely absent from most lspci guides.
Three levels of config space hex dumps. lspci -x dumps the first 64 bytes of PCI configuration space (128 bytes for CardBus bridges) — the mandatory standard header fields. lspci -xxx dumps the whole PCI configuration space (256 bytes for standard PCI, covering the capability linked list) — but the man page carries a specific warning: some devices crash when arbitrary portions of their config space are read because the read triggers side effects in the device's state machine. This is noted as "stupid" behavior but acknowledged as real. This is why -xxx is root-only. lspci -xxxx dumps the extended 4096-byte configuration space available on PCI-X 2.0 and PCIe buses — this is where AER, ASPM, ARI, and other PCIe-specific extended capabilities live, and it requires the linux_sysfs backend. Note: there is no -xx flag in lspci. For bug reporting, the official guidance from the pciutils project is to include lspci -vvx or ideally lspci -vvxxx — but with that caveat about rare devices.
Bus-centric view with -b. By default, lspci reports IRQ numbers and memory addresses as seen by the kernel — after IOMMU remapping and any PCIe address translation. lspci -b switches to the bus-centric view, showing values as seen by the PCI cards themselves. The difference matters when debugging VFIO passthrough, IOMMU groups, or MSI-X interrupt routing, where the kernel's view of an address and the device's self-reported address can diverge.
PCI domain suppression and -D. On systems with only a single host bridge (domain 0), lspci silently omits the domain number from bus addresses. This means the address 03:00.0 is actually 0000:03:00.0. On systems with multiple host bridges — common in dual-socket servers and some high-end workstations — each bridge can address its own PCI domain, numbered 0x0000 to 0xffff. Pass -D to always show domain numbers. Without it, a multi-domain system will suppress domain 0 but show all others, making the output asymmetric in ways that can confuse scripting.
What Does It Mean When a PCI Device Has No Kernel Driver?
When lspci -k shows a device with Kernel modules: listed but no Kernel driver in use: line, the device is present on the bus but unclaimed — the kernel can see it but nothing is bound to it. This is a distinct state from a device that simply has no driver module available at all. The distinction matters when troubleshooting why hardware appears in lspci output but isn't functioning as a usable interface.
# Show all devices; look for missing "Kernel driver in use" lines $ lspci -k # Check kernel messages for device enumeration errors or driver failures $ sudo dmesg | grep -i 'pci\|error\|fail' | grep -v 'BIOS' # Check a specific device for error state by bus address $ lspci -s 03:00.0 -vvv | grep -i 'status\|error\|correctable' # Manually trigger driver binding for an unclaimed device # Replace <driver> and <BDF> with the module name and bus:device.function $ sudo modprobe <driver> $ echo "0000:<BDF>" | sudo tee /sys/bus/pci/drivers/<driver>/bind
An unclaimed device is not automatically a problem — some devices are intentionally left unbound so they can be passed through to a VM via VFIO, or because no in-tree driver exists and an out-of-tree module needs to be installed. The core workflow is: confirm the device is visible with lspci -k, check dmesg for probe errors from the expected driver, and verify the module is loaded with lsmod | grep <driver>. If the module is loaded but the device is still unclaimed, check one non-obvious sysfs path before assuming the driver is broken: cat /sys/bus/pci/devices/0000:<BDF>/driver_override. If that file contains any value other than (null), it takes precedence over all automatic driver matching and will silently block binding regardless of what modules are loaded. Clear it with echo "(null)" | sudo tee /sys/bus/pci/devices/0000:<BDF>/driver_override, then request re-evaluation with echo "0000:<BDF>" | sudo tee /sys/bus/pci/drivers_probe. If the module is loaded, driver_override is clear, and the device still will not bind, the kernel ring buffer will explain why — the most common remaining cause is a failed probe due to a missing firmware blob or an unrecognized device ID.
dmidecode: Reading BIOS and SMBIOS Tables
dmidecode reads the SMBIOS (System Management BIOS) table — a firmware-level data structure that the BIOS or UEFI populates with information about the hardware platform. This includes data that the OS kernel doesn't expose directly: the exact memory module part numbers, chassis serial numbers, BIOS version and release date, processor socket designations, and slot configurations. The current upstream release is dmidecode 3.7 (December 18, 2025), maintained by Jean Delvare and hosted at nongnu.org/dmidecode. Version 3.7 adds support for SMBIOS 3.8.0 and 3.9.0 — the most recent SMBIOS specifications — along with eight new HPE-specific structure types, and binary unit prefixes in all size output (so memory sizes now display as GiB rather than GB). One compatibility change in 3.7 is worth knowing before you script against its output: the Type 0 record header was renamed from "BIOS Information" to "Firmware Information." The -t bios type keyword still works and still selects the correct record — only the displayed header string changed. Any script that grepped for the literal string "BIOS Information" to detect the start of that block will silently fail against dmidecode 3.7 output and needs to be updated to match "Firmware Information" instead. Earlier releases (3.5, 3.6) remain in many distribution repositories; version 3.6 was the first to support SMBIOS 3.6.0 and 3.7.0, including LoongArch processor support and the new --list-strings / --list-types flags.
The dmidecode(8) man page describes the tool as a DMI table decoder that renders hardware component descriptions, serial numbers, and BIOS revision data in human-readable form — without probing the actual hardware. The man page explicitly cautions that this also means the presented data may be unreliable. — dmidecode(8) man page, Debian
On modern kernels, dmidecode first tries /sys/firmware/dmi/tables/smbios_entry_point and /sys/firmware/dmi/tables/DMI (sysfs paths), then falls back to reading directly from /dev/mem if sysfs access fails. Both paths require sudo. SMBIOS and DMI are closely related standards, both developed by the DMTF (Distributed Management Task Force). The terms are often used interchangeably in practice.
Without flags, dmidecode dumps the entire SMBIOS table — hundreds of lines covering every hardware type. In practice you'll nearly always filter by type:
# BIOS version, release date, and firmware revision $ sudo dmidecode -t bios # System manufacturer, product name, serial number, UUID $ sudo dmidecode -t system # Baseboard (motherboard) manufacturer and product $ sudo dmidecode -t baseboard # Processor socket, speed, core/thread count $ sudo dmidecode -t processor # Memory slots: size, speed, part number, manufacturer per DIMM $ sudo dmidecode -t memory # Physical expansion slot types and status (occupied/available) $ sudo dmidecode -t slot # Chassis type, serial, lock status $ sudo dmidecode -t chassis
Starting with dmidecode 3.7 (December 2025), the Type 0 record header in dmidecode -t bios output changed from BIOS Information to Firmware Information. The -t bios keyword still selects the correct record — only the display string changed. If any script you maintain parses dmidecode -t bios output and looks for the literal string "BIOS Information" to locate the start of that block, it will silently produce no output on systems running dmidecode 3.7 and must be updated to match "Firmware Information" instead. Similarly, the 3.7 release switched all size fields to binary unit prefixes: memory sizes now appear as GiB rather than GB. Scripts that parse size values need to handle both forms for cross-version compatibility.
The memory type output deserves special attention. Unlike /proc/meminfo which only tells you total and available RAM, dmidecode -t memory enumerates every physical memory slot on the board — including empty ones — and for populated slots shows the speed, size, ECC capability, manufacturer, part number, and serial number. This is how you verify that a server has the right DIMMs installed before a warranty claim, or confirm that a memory upgrade actually landed in the correct channels for interleaving.
Memory Device Array Handle: 0x1000 Error Information Handle: 0x1100 Total Width: 72 bits Data Width: 64 bits Size: 32 GiB Form Factor: DIMM Locator: DIMM_A1 Bank Locator: NODE 1 CHANNEL A DIMM 0 Type: DDR5 Speed: 4800 MT/s Manufacturer: Samsung Serial Number: 3A45BE91 Part Number: M321R4GA3BB6-CQKOL Configured Memory Speed: 4800 MT/s ECC: Yes
The numeric type codes are an alternative to keyword strings. Type 0 is BIOS, type 17 is memory device, type 4 is processor, and so on. The full table is in the dmidecode man page. You can also use sudo dmidecode -t 17 instead of sudo dmidecode -t memory — both work.
The dmidecode UUID Byte-Order Problem
The UUID reported by dmidecode is not always byte-for-byte identical to what other tools report for the same machine, and the reason is a long-standing ambiguity in the SMBIOS specification. SMBIOS 2.1 introduced the UUID field but said only "16 bytes" with no byte order defined. RFC 4122 specifies big-endian (network byte order) for all UUID fields. SMBIOS 2.6, however, clarified that the first three fields — time_low, time_mid, and time_hi_and_version — should be stored as little-endian, matching the convention already used by ACPI, UEFI, and Windows.
The practical result: dmidecode applies byte-swapping to the first three UUID fields only when the system's SMBIOS version is 2.6 or later. On pre-2.6 systems it uses RFC 4122 order (no swap). The Linux kernel's dmi_scan.c follows the same logic. This matters in production when correlating a machine's UUID across management tools — VMware, for instance, populates SMBIOS with pre-swapped bytes, so on ESX guests with SMBIOS 2.7, the UUID in dmidecode output will differ from what VMware's own tools report. You can verify the SMBIOS version on your system with sudo dmidecode | head -3, which prints the entry point line immediately.
SMBIOS Type 41 and Predictable Network Interface Names
When administrators wonder why lspci shows an onboard NIC but udev names it eno1 instead of ens3 or enp2s0, the answer is SMBIOS type 41. The --type baseboard keyword in dmidecode does not just cover type 2 (baseboard) — it expands to types 2, 10, and 41. Type 41 is "Onboard Devices Extended Information" and stores the reference designation string, device kind (ethernet, SATA, USB, etc.), instance number, and PCI bus address for each onboard device. When this record exists and is populated, udev reads it and assigns the enoX naming scheme (onboard index) rather than falling back to slot or path-based naming. This is why servers with well-populated SMBIOS tables get stable, predictable interface names while VMs and budget hardware without type 41 entries may get ens or enp names instead.
dmidecode 3.7 (December 18, 2025) introduced a user-visible label change: the output field previously shown as BIOS Information is now rendered as Firmware Information, and the BIOS Revision field is now Firmware Revision. If you have scripts or monitoring tools that parse dmidecode output by string-matching the old labels, they will silently fail on systems running dmidecode 3.7. Check your installed version with dmidecode --version and update any parsers accordingly. The 3.7 release also switched to binary unit prefixes (MiB, GiB) for size values, replacing the former decimal-labelled output. Size values themselves are unchanged; only the units label differs.
# Check SMBIOS version (first 3 lines) -- determines UUID byte-swap behavior $ sudo dmidecode | head -3 # Type 41: onboard device records -- what udev reads for enoX interface naming $ sudo dmidecode -t 41 # Types 128-255 are OEM-specific; displayed but only decoded if vendor contributed docs $ sudo dmidecode -t 130 # Capture a binary SMBIOS dump for offline analysis or reproducibility $ sudo dmidecode --dump-bin /tmp/smbios-$(hostname).bin # Replay a binary dump -- no root needed, useful for offline auditing $ dmidecode --from-dump /tmp/smbios-hostname.bin
The --dump-bin / --from-dump pair is underused but valuable for fleet work: capture a binary SMBIOS snapshot from each server at provisioning time, commit it to configuration management, and replay it on any workstation without root. This also lets you diff SMBIOS tables across similar hardware to spot firmware inconsistencies — for example, confirming that two nominally identical server models have the same BIOS revision and type 17 memory configuration before deploying them into the same pool.
SMBIOS data is written by the firmware vendor and reliability varies widely. The Debian dmidecode man page states: "More often than not, information contained in the DMI tables is inaccurate, incomplete or simply wrong." Budget hardware frequently ships with placeholder strings like "To Be Filled By O.E.M." for fields including product names and serial numbers. Additionally, dmidecode reports what the firmware claims about the hardware — not what the kernel is actually using. Always cross-reference critical values (especially memory speed) against what the kernel negotiated, visible in dmesg | grep -i ddr or the UEFI setup screen.
Extracting Specific Fields for Scripting
For automation — generating asset inventory reports, pre-flight checks before OS deployment, or populating a CMDB — you can combine dmidecode with string selection to extract clean values:
# Get just the system serial number $ sudo dmidecode -s system-serial-number # Get the BIOS version string $ sudo dmidecode -s bios-version # Get the system product name (model) $ sudo dmidecode -s system-product-name # List all available -s keywords $ sudo dmidecode --string help
The -s flag outputs a single clean string with no surrounding text — ideal for shell variable assignment or piping into a configuration management tool. This pairs well with bash scripting for inventory automation across multiple hosts.
Binary Dumps, Handle Cross-References, and the UUID Byte-Swap Problem
Offline binary dumps. dmidecode has a direct parallel to lspci -F: you can dump the entire SMBIOS table to a binary file with sudo dmidecode --dump-bin dmi.bin, then read it back on any machine with dmidecode --from-dump dmi.bin — without root, without hardware access. The target file must not already exist. This is the correct way to collect SMBIOS data from a production server for offline analysis or long-term archival alongside the rest of your inventory snapshot. It is also how you reproduce a firmware bug report without giving an analyst access to the system.
# On the target machine -- dump SMBIOS table to binary (target file must not exist) $ sudo dmidecode --dump-bin /tmp/smbios.bin # Transfer smbios.bin to another machine, then analyze without root or hardware $ dmidecode --from-dump smbios.bin -t memory $ dmidecode --from-dump smbios.bin -t bios # Access OEM-specific strings by index (first OEM string is index 1) $ sudo dmidecode --oem-string 1 # Count how many OEM strings the firmware exposes $ sudo dmidecode --oem-string count # Filter by handle number -- useful when cross-referencing records $ sudo dmidecode --handle 0x0027
The handle cross-reference system. Every SMBIOS record has a handle — a unique 16-bit identifier. Records routinely reference each other by handle: processor records (type 4) point to their associated cache records (type 7) by handle number. When dmidecode shows a line like L1 Cache Handle: 0x0025, that 0x0025 is the handle of the corresponding type-7 cache record. You can jump directly to that record with sudo dmidecode --handle 0x0025. This cross-reference structure is how SMBIOS encodes which cache belongs to which socket on a multi-processor board — something the flat type-filtered output obscures.
The UUID byte-swap problem. The system UUID returned by sudo dmidecode -s system-uuid may not match what other tools report, and the cause is a known specification inconsistency. Before SMBIOS 2.6, the UUID was interpreted according to RFC 4122 with no byte-swapping. Starting with SMBIOS 2.6, the specification requires the first three fields to be stored as little-endian (byte-swapped). In practice, many hardware vendors before 2.6 did not byte-swap, so the spec change broke compatibility with existing deployments. The current dmidecode behavior — and the Linux kernel's behavior — is to apply no swap for SMBIOS versions below 2.6 and to apply the little-endian interpretation for 2.6 and later. If you see UUID mismatches between dmidecode, the UEFI setup screen, and your hypervisor, this is almost certainly why. Check sudo dmidecode -t system | grep "SMBIOS" at the top of the output to see which spec version your firmware reports.
Type 126, 127, and OEM types. The SMBIOS specification reserves type 126 as a disabled-entry marker and type 127 as the end-of-table marker. Types 128 through 255 are OEM-specific. dmidecode will display OEM type entries by default but can only decode them when the vendor has contributed parsing code — otherwise you see raw hex. Some vendors use these aggressively: the source changelog records support for Acer-specific type 170, for example. If your dmidecode output contains unfamiliar type numbers in the 128–255 range, they are vendor extensions and the data is only interpretable with vendor documentation.
Reading DMI Strings Without Root
If you need basic system identification without elevated privileges, the Linux kernel exposes many individual DMI fields at /sys/devices/virtual/dmi/id/, and several are world-readable by default. The official dmidecode man page notes this explicitly: "on Linux, most of these strings can alternatively be read directly from sysfs, typically from files under /sys/devices/virtual/dmi/id. Most of these files are even readable by regular users." This makes the path genuinely useful for non-root monitoring agents and CI pipeline hardware checks:
# List all available DMI sysfs fields $ ls /sys/devices/virtual/dmi/id/ # System model name -- readable without sudo on most distributions $ cat /sys/devices/virtual/dmi/id/product_name # BIOS version $ cat /sys/devices/virtual/dmi/id/bios_version # System vendor / manufacturer $ cat /sys/devices/virtual/dmi/id/sys_vendor # Board name $ cat /sys/devices/virtual/dmi/id/board_name # Product serial number -- typically restricted to root even via sysfs $ sudo cat /sys/devices/virtual/dmi/id/product_serial
This sysfs path is the same source dmidecode itself prefers on modern kernels. The advantage here is privilege granularity: you can expose only the fields a monitoring agent needs without granting full dmidecode access or requiring sudo at all. Serial number and UUID fields remain restricted to root on most distributions for obvious privacy reasons.
lshw: The Full Hardware Tree
lshw (Hardware Lister) is written and maintained by Lyonel Vincent. The current stable release is B.02.20 (January 6, 2024, tagged at commit 4c6497c). It takes a broader approach than the previous two tools, aggregating data from multiple sources simultaneously: /proc, /sys, DMI tables, PCI configuration space, and USB descriptors. The result is a structured hardware tree covering the entire system — firmware, CPU, memory, storage controllers and attached disks, network interfaces, PCI devices, USB buses, and more. According to the official lshw documentation, it supports DMI on x86 and EFI (IA-64) systems and also works on some ARM and PowerPC machines. It is the only tool in this group that combines PCI, DMI, and kernel-reported data in a single pass.
Install it with apt install lshw, dnf install lshw, or pacman -S lshw depending on your distribution. Running it as root gives the most complete output — without root, it will warn you and produce partial data.
All four tools behave differently inside virtual machines and containers, and understanding the difference matters before trusting the output. Inside a VM (KVM, VMware, Hyper-V, VirtualBox), lspci shows virtual PCI devices — paravirtual NICs, virtio controllers, emulated adapters — not the host's physical hardware. dmidecode reads whatever SMBIOS strings the hypervisor has populated, which may be accurate system information, placeholder strings, or deliberately obscured data depending on cloud provider policy. AWS, for example, populates SMBIOS product names; many other providers return generic strings. lshw and inxi aggregate from the same virtualized views. Inside a container (Docker, LXC, systemd-nspawn), lspci and dmidecode typically require --privileged mode or specific capability grants (CAP_SYS_RAWIO) and will fail or return incomplete data without them. If you need to inspect the host's physical hardware from a container, the correct approach is to run the commands on the host directly, not inside the container.
The default text output is thorough but long. For most use cases, the short table or class-filtered output is more practical:
# Condensed table: class, description, product, vendor, logical name $ sudo lshw -short # Filter to a specific hardware class $ sudo lshw -class network $ sudo lshw -class storage $ sudo lshw -class disk $ sudo lshw -class processor $ sudo lshw -class memory # Export as HTML report (good for documentation) $ sudo lshw -html > hardware-report.html # Export as XML -- structured format, widely supported by data processing tools $ sudo lshw -xml > hardware.xml # Export as JSON for machine processing $ sudo lshw -json > hardware.json # Sanitize output -- strips IP addresses, serial numbers, UUIDs for safe sharing $ sudo lshw -sanitize # Quiet mode -- suppress warnings about /proc access $ sudo lshw -quiet # Bus-info format -- shows PCI/SCSI/USB addresses alongside device names # Useful for correlating lshw entries directly with lspci bus addresses $ sudo lshw -businfo # Disable SPD probing -- avoids slow or unstable SPD reads on some hardware $ sudo lshw -disable spd # Disable DMI entirely -- useful when SMBIOS data is known-bad or causing hangs $ sudo lshw -disable dmi # Dump to SQLite database for programmatic querying $ sudo lshw -dump /var/log/hardware-$(hostname).db
The -short output is often the fastest way to orient yourself on an unfamiliar system. It condenses everything into a readable table with hardware paths that reflect the physical bus hierarchy — you can see at a glance that your NVMe is on PCIe bus 1, your SATA controller is on bus 0, and your dual-port NIC is in slot 3.
-businfo vs -short, and Disabling Specific Probes
The -businfo output format is distinct from -short and specifically useful when you need to cross-reference lshw entries with lspci or other bus-aware tools. While -short uses lshw's internal hardware path notation (like /0/100/1c/0), -businfo shows the actual bus address in PCI notation (pci@0000:00:1c.0), USB topology notation (usb@1:2), or SCSI address — the same identifiers you'd use in lspci -s or udevadm. This makes it the right format when you're correlating lshw network output with kernel interface names or matching storage devices to their controller paths.
lshw's -disable flag is rarely documented in tutorials but matters in practice. The tool has modular probes it runs in sequence: dmi, spd (Serial Presence Detect for memory modules), pci, cpuid, memory, usb, scsi, network, and others. On systems where a specific probe hangs — particularly SPD probing on some embedded or non-standard memory configurations, or DMI on systems with partially broken SMBIOS tables — you can disable only that probe while keeping all others active. This is more surgical than running without root and avoids having lshw hang indefinitely waiting for a probe that will never complete.
H/W path Device Class Description ======================================================== system PowerEdge R750 /0 bus 0XDYHM /0/0 memory 128GiB System Memory /0/0/0 memory 32GiB DIMM DDR5 4800 MHz /0/0/1 memory 32GiB DIMM DDR5 4800 MHz /0/1 processor Intel Xeon Gold 6338 /0/100 bridge Sky Lake PCIe Controller /0/100/1/0 storage NVMe SSD Controller /0/100/1/0/0 /dev/nvme0 disk 1.92TB Samsung PM9A3 /0/100/1c/0 eno1 network Intel X550 10GbE
Using lshw for Network Interface Inventory
The network class filter is especially useful when documenting server NICs before configuration. It shows the interface name, MAC address, driver, firmware version, link speed capability, and whether auto-negotiation is enabled — all the information you need before writing an /etc/network/interfaces or configuring Ubuntu Server network management rules:
$ sudo lshw -class network *-network description: Ethernet interface product: Ethernet Controller X550 vendor: Intel Corporation logical name: eno1 version: 01 serial: a4:bf:01:23:45:67 capacity: 10Gbit/s driver: ixgbe firmware: 0x800012c3 link: yes autonegotiation: on
The JSON output from sudo lshw -json is well-structured and easy to parse with jq. For fleet-wide hardware inventory, you can script this across hosts with SSH and aggregate the JSON into a central database. The XML output (-xml) is an equally valid alternative if your tooling prefers XML — it uses the same hardware tree structure and is often the preferred format for integration with configuration management platforms like Ansible or Puppet. Pair either format with port scanning tools to correlate network interface data with active services.
inxi: The System Summary Tool
inxi began as a fork of infobash, the original bash/gawk sysinfo script by Michiel de Boer (locsmif). The fork is maintained by Harald Hope and the project's own documentation describes the transition: the Gawk/Bash version was rewritten entirely in Perl 5 in early 2018, becoming version 3.0.0 — the current stable line. The official documentation (smxi.org) describes it as "a command line system information tool built for console and IRC" that is also "used as a debugging tool for forum technical support to quickly ascertain users' system configurations and hardware." That use case defines its design: where lspci, dmidecode, and lshw give you raw hardware data, inxi is designed to produce readable, shareable system summaries. It aggregates from lspci, dmidecode, /proc, /sys, and several other sources, then formats the output with human-readable labels and sensible defaults. The current upstream version as of late 2025 is 3.3.40.
It's the tool you paste into a forum post or bug report when you need to describe your system quickly. It's also useful for sysadmins who need a fast overview without parsing walls of raw data.
Install it with apt install inxi, dnf install inxi, or pacman -S inxi. The packaged version in older LTS releases can be significantly behind upstream — if you need current features, the project supports self-updating with sudo inxi -U (where not disabled by your distribution's maintainer).
The standard starting point for full output is inxi -exz: -e (or --expanded) for the full expanded report, -x for extra detail, and -z to anonymize private data like serial numbers and MAC addresses. The older -Fxz form is still widely documented and still works, but the -F/--full flag is officially deprecated in inxi 3.x. The inxi options documentation states it plainly: "-F, --full Deprecated. See -e/--expanded." The formal deprecation was made explicit in version 3.3.37 (January 6, 2025), described in the changelog as "changing the long incorrect -F/--full to -e/--expanded, which is more accurate." The release notes add that -F/--full will remain functional until the updated inxi reaches stable distribution repositories. Use inxi -exz going forward, particularly if you are writing documentation, scripts, or support guides.
# Full expanded summary with extra details, private data anonymized (current preferred form) $ inxi -exz # Legacy equivalent -- still works but -F is deprecated in inxi 3.x $ inxi -Fxz # CPU info only $ inxi -C # Graphics/GPU only $ inxi -G # Memory info (requires sudo for full DIMM detail via dmidecode) $ sudo inxi -m # Network interfaces only $ inxi -N # Disk/storage summary $ inxi -D # RAID arrays if present $ inxi -R # Temperatures, fan speeds, voltages (requires lm-sensors) $ inxi -s # Running processes $ inxi -t # Repository and package info $ inxi -r # Output as JSON -- useful for parsing inxi data programmatically $ inxi -exz --output json --output-file /tmp/system-info.json # Check which optional dependencies are missing for full output $ inxi --recommends # Strip color codes for log capture or diffing $ inxi -exz --no-color # Per-core instantaneous MHz from /sys/devices/system/cpu/cpu*/cpufreq/scaling_cur_freq # Shows live p-state per core, scaling driver, and governor -- not BIOS-reported speed $ inxi -C -xxx # Physical PCI slot status: type, speed, populated/empty (requires root; reads SMBIOS type 9) $ sudo inxi --slots # Full display/monitor data: chroma coordinates, modelines, EDID errors (triggers -G) $ inxi --edid # Show WAN + LAN IPs (requires ifconfig or ip tool) -- filtered by -z, use -Z to override $ inxi -i # Partition UUIDs -- combine with -p for full partition + UUID view $ inxi -pu
The sensor output (-s) is particularly useful when you suspect thermal issues — it aggregates CPU, GPU, and motherboard temperatures into a single readable view without needing to install and configure additional monitoring tools.
inxi Precision Details That Change How You Read the Output
The verbosity scale goes to 8. Most guides show -v 4 or -v 6, but the scale runs from 0 to 8. Each level is strictly additive. Level 5 adds memory (-m), sensors (-s), audio, and partition UUIDs. Level 7 adds full CPU flags (-f), logical devices (-L), and network IP data (-i), and forces Bluetooth and RAID sections. Level 8 — the maximum — adds PCI slots (--slots), GPU EDID data (--edid), repos (-r), and a full process table. In practice, inxi -v 8 is the closest thing to a complete system audit in a single command, but it requires root for the --slots and EDID sections to return full data.
inxi --slots is not the same as lspci -t. The --slots flag (also triggered by -v 8 and -a) reports PCI slot type, link speed capability, and current occupancy status — drawing from SMBIOS type-9 data via dmidecode, not from the kernel\'s PCI subsystem. This means it shows physical slots including empty ones, their rated speed (PCIe 4.0 x16, M.2 Socket 3, etc.), and whether the slot is currently in use. It requires root and will show reduced output without it. Neither lspci nor lshw -short alone gives you the empty-slot picture.
What (check) and (est) mean in memory output. When sudo inxi -m shows a memory speed or capacity followed by note: check, inxi has detected that the dmidecode value looks implausible — for example, a speed of 61,910 MT/s is physically impossible and signals corrupted or firmware-invented SMBIOS data. The annotation note: est means inxi estimated the figure from /sys/devices/system/memory block counts rather than from dmidecode, because it was running without root or because the SMBIOS data was absent. The inxi documentation explicitly states it cannot obtain truly reliable RAM data from any available source and that "a significant percentage of users will have either a wrong max module size or max capacity" from the SMBIOS values. Cross-check against dmesg | grep -i ddr to see what speed the memory controller actually negotiated.
Granular output filtering for public sharing. The -z flag anonymizes MAC addresses, IPs, and serial numbers, but inxi has finer-grained filter flags that are rarely documented outside the man page. --zv (or --filter-vulnerabilities) strips the CPU vulnerability report from -Ca output — useful when sharing system specs publicly without exposing which Spectre/Meltdown mitigations are active or absent. --zl filters partition labels, --zu filters partition and board UUIDs, and --za is a shortcut that triggers all four filters simultaneously. The inverse, -Z, disables all IRC and filter-mode output suppression — which inxi applies automatically when it detects it's running in an IRC context.
Sensor fallback path. inxi's sensor output does not require lm-sensors to function. If lm-sensors is absent or unconfigured, inxi automatically falls back to reading /sys/class/hwmon directly. The two sources do not always agree on sensor names or values. You can explicitly compare them with inxi -s --force sensors-sys to force the sysfs path, or use --sensors-exclude <name> to drop a noisy sensor from the report without removing lm-sensors entirely.
# Maximum verbosity -- everything inxi can report (requires root for slots/EDID) $ sudo inxi -v 8 -z --no-color # PCI slot occupancy and link speeds from SMBIOS data (empty slots included) $ sudo inxi --slots # Memory report with full DIMM detail -- watch for (check) and (est) annotations $ sudo inxi -m # Strip everything: MACs, IPs, serials, UUIDs, labels, and vulnerability report $ inxi -exz --za --zv --no-color # Force sysfs sensor path (compare against lm-sensors output) $ inxi -s --force sensors-sys # Force dmidecode as data source for machine info instead of /sys $ sudo inxi -M --dmidecode
System: Host: web01 Kernel: 6.8.0-55-generic x86_64 OS: Ubuntu 24.04.2 LTS CPU: Info: 32-core Intel Xeon Gold 6338 [MT MCP] Speed: 2100 MHz min/max: 800/3200 MHz Graphics: Device-1: ASPEED AST2600 driver: ast Network: Device-1: Intel Ethernet X550 driver: ixgbe IF: eno1 state: up speed: 10000 Mbps duplex: full Device-2: Intel Ethernet X550 driver: ixgbe IF: eno2 state: down Drives: Local Storage: total: 3.84 TiB ID-1: /dev/nvme0n1 vendor: Samsung model: MZQL21T9HCJR size: 1.92 TiB ID-2: /dev/nvme1n1 vendor: Samsung model: MZQL21T9HCJR size: 1.92 TiB Partition: ID-1: / size: 1.82 TiB used: 214.3 GiB (11.5%) fs: ext4 Memory: RAM: total: 125.72 GiB used: 22.14 GiB (17.6%) Info: Processes: 412 Uptime: 47 days Shell: bash
inxi on Minimal Server Installations
On minimal server installs, inxi may produce warnings about missing optional dependencies. These are informational — it will still output what it can. Run inxi --recommends to get a full list of what's installed and what's missing, with package names for your distribution. For full functionality including temperature readings, install lm-sensors and run sudo sensors-detect. For SMART disk health data alongside the drive listing, install smartmontools. For full memory slot detail pulled from dmidecode, run inxi -m with sudo.
On headless servers where terminal color codes cause problems in logs or monitoring systems, run inxi -exz --no-color to strip ANSI escape sequences from the output. This also makes the output easier to diff across runs when tracking hardware changes over time. For fully machine-readable output suitable for parsing or storage, --output json produces a structured JSON document that covers all the same fields as the standard summary.
inxi -C -xxx: Per-Core Live Frequency and P-State Detail
When inxi shows CPU speed, the default output is an average. Adding three x modifiers (inxi -C -xxx) switches to per-core instantaneous frequency by reading /sys/devices/system/cpu/cpu*/cpufreq/scaling_cur_freq for each logical core. This reports the live p-state — the frequency the kernel's scaling driver has actually requested at the moment inxi runs, not the processor's rated speed or the BIOS-reported maximum. The output also shows the active scaling driver (intel_pstate, amd-pstate-epp, acpi-cpufreq, etc.) and the governor (powersave, performance, schedutil). This level of detail is relevant when diagnosing thermal throttling — if cores under load show frequencies well below the rated max, the governor or EPP (Energy Performance Preference) setting is likely the constraint rather than hardware temperature alone.
inxi --slots: Physical PCI Slot Status
There is an important distinction between what lspci shows and what inxi --slots shows. lspci reports only devices that are physically present and recognized by the kernel — empty slots are invisible to it. inxi --slots (which requires root) reads SMBIOS type 9 records via dmidecode and reports every physical expansion slot on the board: its type (PCIe x16, PCIe x4, M.2, etc.), bus speed, and whether it is currently populated or empty. This is the only way to answer "how many PCIe x16 slots does this board have and how many are in use" without opening the chassis. The slot data comes from firmware, so it is subject to the usual SMBIOS accuracy caveats — some vendors populate type 9 accurately; others do not.
inxi's Automatic Privacy Filtering in IRC Mode
A behavior that is easy to miss: when inxi detects it is running inside an IRC client, it automatically strips MAC addresses, WAN and LAN IP addresses, and the home directory path from all output. This IRC auto-filter is why inxi output pasted into support forums is safe by default. The -z flag triggers this same filtering when running outside IRC — which is why inxi -exz is the standard form for shareable output. The inverse flag, -Z, overrides the filter and forces sensitive data back into output — useful in a private debug session but should never be used for public forum posts. The --za flag is an even more aggressive filter that strips everything potentially identifying, including hostnames.
Combining All Four Tools: A Hardware Inventory Script
Each of these tools covers a different layer of the system. When you need a comprehensive snapshot — for provisioning a new server, documenting a bare-metal machine before an OS migration, or building a hardware audit trail — running all four in sequence and capturing the output gives you something no single tool provides on its own.
The following script captures everything to timestamped files. It requires root for dmidecode and lshw full output, so run it as root or via sudo:
#!/bin/bash # hw-inventory.sh -- full hardware snapshot using lspci, dmidecode, lshw, inxi # Run as root or with sudo for complete output OUTDIR="/var/log/hw-inventory/$(date +%Y%m%d-%H%M%S)" mkdir -p "$OUTDIR" # 1. PCI device tree with driver bindings and numeric IDs lspci -nnkv > "$OUTDIR/lspci-nnkv.txt" # 2. PCI bus topology lspci -t > "$OUTDIR/lspci-tree.txt" # 3. Full SMBIOS table dmidecode > "$OUTDIR/dmidecode-full.txt" # 4. Key SMBIOS fields for quick reference for TYPE in bios system baseboard processor memory chassis slot; do dmidecode -t "$TYPE" > "$OUTDIR/dmidecode-${TYPE}.txt" done # 5. Full hardware tree as JSON -- machine-parseable lshw -json > "$OUTDIR/lshw.json" # 6. Condensed hardware table for quick review lshw -short > "$OUTDIR/lshw-short.txt" # 7. Human-readable summary, no private data inxi -exz --no-color > "$OUTDIR/inxi-summary.txt" # 8. Record kernel version and cmdline for context uname -r > "$OUTDIR/kernel-version.txt" cat /proc/cmdline >> "$OUTDIR/kernel-version.txt" echo "Hardware inventory written to: $OUTDIR"
This produces a self-contained directory of timestamped files you can archive, commit to a configuration management repository, or diff against a future run to detect hardware changes — added DIMMs, replaced NICs, updated BIOS firmware. The JSON from lshw is particularly useful here: you can parse it with jq to extract specific fields or load the full inventory into a CMDB.
To compare hardware inventories across two snapshots and see what changed — new device, removed DIMM, BIOS update — run diff -u lshw-short.txt.old lshw-short.txt. For structural differences in the JSON output, diff <(jq -S . lshw.json.old) <(jq -S . lshw.json) gives you a sorted, normalized diff that surfaces field-level changes. This turns these snapshot files into a lightweight hardware change log with no additional tooling required.
lspci output — what is its current state?The driver probed the device and rejected it. Get the exact vendor:device pair with lspci -s <BDF> -nn — the bracketed code like [8086:15f3] is what you need. Cross-reference it against the driver's pci_device_id table in the kernel source under drivers/. If the ID is absent, there are two distinct situations: if an existing driver handles close relatives of the device, the fix is adding the new ID — you can test this immediately without recompiling by running echo "<vendor> <device>" | sudo tee /sys/bus/pci/drivers/<driver>/new_id, which tells the driver to attempt binding to that ID. If dmesg shows the module loaded and attempted to probe but rejected the device explicitly (a message from the driver itself, not from the PCI subsystem), the device revision may require a driver update — check whether the ID was added in a later kernel version using the upstream kernel git history at git.kernel.org with git log --all --oneline -- drivers/ filtered by the vendor ID string. Your distro's backport repositories or DKMS packages are the fastest path to a newer driver without a full kernel upgrade.
Try sudo modprobe <driver_name> where the driver name comes from the Kernel modules: line in lspci -k. If it loads successfully, the device should bind immediately. To persist the module load across reboots, add the module name to /etc/modules-load.d/<name>.conf. If modprobe fails, check sudo dmesg immediately after — it will explain why the load was rejected. If the module loads but the device remains unclaimed, check a non-obvious cause: a previous tool or boot configuration may have set driver_override on the device. Run cat /sys/bus/pci/devices/0000:<BDF>/driver_override — if that file contains any value other than (null), it will silently prevent automatic binding to any other driver. Clear it with echo "(null)" | sudo tee /sys/bus/pci/devices/0000:<BDF>/driver_override, then trigger a rebind with echo "0000:<BDF>" | sudo tee /sys/bus/pci/drivers_probe. Also check /etc/modprobe.d/ for any blacklist entries for the driver that may be preventing autoloading at boot, and verify no vfio-pci.ids= parameter in /proc/cmdline is claiming the device at the kernel commandline level.
The driver loaded but cannot initialize the device without binary firmware. The dmesg line itself tells you the exact filename the kernel requested — the path after firmware: failed to load maps directly to /lib/firmware/, so you know precisely what file is missing without guessing. Before installing packages, run modinfo <driver> | grep firmware to get the complete list of firmware files the module can require across all device revisions. Install the appropriate firmware package: on Debian/Ubuntu, sudo apt install firmware-linux-nonfree or the vendor-specific package (e.g., firmware-iwlwifi for Intel WiFi, firmware-amdgpu for AMD graphics). On Fedora/RHEL, check the linux-firmware package. After installing, reload the module with sudo modprobe -r <driver> && sudo modprobe <driver> — a reboot is not required. If the distro's firmware package version predates the device (common with newer WiFi cards and recent GPU silicon), the upstream linux-firmware git repository at git.kernel.org carries the latest blobs — you can copy the specific file directly to /lib/firmware/ without replacing the whole package. Also run sudo fwupd refresh && sudo fwupdmgr update for firmware that ships through the Linux Vendor Firmware Service (LVFS), which covers a large range of network adapters and storage controllers from major vendors.
lspci -s <BDF> -vvv show?AER errors are hardware-level PCIe link errors logged in the device's extended config space. In lspci -s <BDF> -vvv output, look specifically at the UESta (Uncorrectable Error Status), CEsta (Correctable Error Status), and AERCap fields — these tell you the exact error type, not just whether errors occurred. Correctable errors like replay timer timeouts (RlRo) or bad TLPs (BadTLP) indicate lane signal integrity issues and are survivable; the link is retrying. Uncorrectable errors (UESta with any bits set, especially FatalErr) indicate serious link integrity or DMA errors that the hardware cannot recover from. Run sudo dmesg | grep -i aer for the kernel's own AER report, which includes the error classification (correctable, non-fatal uncorrectable, fatal) and whether the driver's error handler was invoked. For persistent correctable errors: reseat the card, inspect the slot contacts, and try a different PCIe slot to isolate whether the fault follows the card or the slot. For uncorrectable errors on hardware that worked before, the card or the slot is failing — the kernel's aer_driver_reset may have attempted recovery, visible in dmesg. Use rasdaemon for persistent AER logging beyond the kernel ring buffer, since dmesg output is lost across reboots.
Read the lspci -s <BDF> -vvv output carefully: it shows both LnkCap (what the device can do) and LnkSta (what was negotiated). The gap between them is your exact diagnostic. A device showing LnkCap: Speed 16GT/s, Width x4 alongside LnkSta: Speed 5GT/s, Width x1 negotiated to Gen2 x1 — that is almost always a lane signal integrity failure or a slot with only one lane electrically wired regardless of physical size, which is common on budget boards that have a physical x16 connector but only four or fewer lanes actually connected. Check BIOS/UEFI settings for PCIe slot bifurcation configuration, which controls how a physical x16 slot's lanes are split among devices. To test whether the trained state was a one-time negotiation failure rather than a hard limit, you can request a link retrain without rebooting: echo 1 | sudo tee /sys/bus/pci/devices/0000:<BDF>/link/retrain (the device will briefly disappear from the bus and re-enumerate). Check sudo dmesg | grep -i 'pcie\|link training' immediately after for the new negotiation result. If the card retains the degraded link on retrain and behaves the same in another slot, the device itself has a lane initialization issue — common on some NVMe adapters that do not correctly assert the PCIe reset signal during hot-plug scenarios.
Collect the full relevant dmesg section with context: sudo dmesg -T | grep -A5 -B5 <driver_name> gives you five lines before and after each match so you can see what triggered each error rather than isolated lines. For network drivers, ethtool -S <iface> exposes the hardware's own error counters (rx_errors, tx_dropped, etc.) independently of dmesg and persists across ring buffer rotation — this is often where intermittent errors first surface. For NVMe, sudo nvme error-log /dev/nvme0 reads the device's internal error log, which the controller maintains across reboots and captures error types that the kernel driver may not surface in dmesg. For SATA, sudo smartctl -x /dev/sdX provides the SMART extended self-test log and reallocated sector counts. Firmware updates via sudo fwupdmgr update resolve a significant share of driver-level errors that are not hardware failures — particularly for NVMe controllers and network adapters where the vendor ships microcode fixes through LVFS. If errors persist after a firmware update and a kernel upgrade, and the device passes vendor diagnostic tools, the problem may be a resource conflict: check sudo dmesg | grep -i 'iommu\|interrupt\|irq' around the device's BDF address for any IOMMU remapping warnings or IRQ allocation failures that indicate the device is sharing an interrupt line with a conflicting device.
No Kernel modules: line means the running kernel has no in-tree driver that matches this device's PCI ID. This is expected for very new hardware, proprietary devices, or specialized PCIe expansion cards. Two situations require different approaches: if a driver for a related device exists and just lacks this device's ID in its pci_device_id table, you can test binding without recompiling by writing the vendor:device pair to the driver's new_id sysfs file: echo "<vendor_hex> <device_hex>" | sudo tee /sys/bus/pci/drivers/<driver>/new_id — if the device responds correctly, this approach works and you can make it persistent via a udev rule. To check whether support was added in a newer kernel, get the numeric IDs from lspci -nn, then search the upstream kernel source for that vendor ID string in the driver directory — the quickest path is searching git.kernel.org or elixir.bootlin.com cross-reference, which lets you search all pci_device_id tables across the entire kernel tree. If no in-tree driver exists at all, check whether an out-of-tree DKMS module is available from the vendor — run uname -r to confirm your kernel version before installing, since DKMS modules must be built against the exact running kernel headers (linux-headers-$(uname -r)).
vfio-pci is bound when a device has been explicitly assigned to a VM via VFIO passthrough, or when a persistent vfio-pci binding is configured via a vfio-pci.ids= kernel parameter or a udev rule. Check /etc/modprobe.d/ for options lines passing device IDs to vfio-pci, and check /proc/cmdline for vfio-pci.ids=. Before rebinding to the native driver, verify the IOMMU group situation: even after removing the vfio-pci binding, if the device shares an IOMMU group with other devices and that group is still in an assigned state (e.g., a VM that was not cleanly shut down), rebinding the native driver may silently fail or cause instability. Check the group with ls /sys/kernel/iommu_groups/*/devices/ to find which group contains your device's BDF address. If the group is clear, remove the vfio binding by writing the BDF to /sys/bus/pci/drivers/vfio-pci/unbind, then explicitly request rebinding: echo "0000:<BDF>" | sudo tee /sys/bus/pci/drivers_probe. If you prefer pci-stub rather than vfio-pci for reserving a device without VM assignment overhead, it achieves the same isolation effect and is simpler to remove.
Choosing the Right Tool
The four tools overlap in some areas but each has a clear primary use case. Understanding where each one draws its data from is the key to choosing correctly:
| Tool | Primary Data Source | Root Needed? | Best For |
|---|---|---|---|
| lspci | /sys/bus/pci, /proc/bus/pci | No (basic) | Driver bindings, PCI topology |
| dmidecode | /sys/firmware/dmi/tables, /dev/mem | Yes | BIOS version, DIMM part numbers, serials |
| lshw | /proc, /sys, DMI, PCI, USB | Yes (full) | Complete hardware inventory, JSON/XML export |
| inxi | Aggregates from all above + lm-sensors | No (sudo for DIMM detail) | Shareable summaries, quick health checks |
- lspci — when you need PCI/PCIe device details, driver bindings, or bus topology. Reads from
/sys/bus/pci/devices/and/proc/bus/pci. Fast, no root required for basic output, and the correct first stop for driver troubleshooting. Pairs well with reviewing systemd units that manage hardware-dependent services. - dmidecode — when you need firmware-level data: BIOS version, memory module part numbers, chassis serials, processor socket info. Reads from
/sys/firmware/dmi/tables(preferred) or/dev/mem. Requires root. The authoritative source for SMBIOS data — but remember the official caveat that this data is often inaccurate. Essential for warranty work, asset management, and pre-deployment verification. For non-root scripts, use/sys/devices/virtual/dmi/id/for basic strings. - lshw — when you need a comprehensive hardware inventory across all subsystems in a single command. Reads from
/proc,/sys, DMI tables, PCI configuration space, and USB descriptors. Best for documentation, onboarding unfamiliar hardware, and generating exportable reports in HTML, XML, or JSON. The structured outputs integrate well with configuration management tooling. For kernel-level analysis, see also Linux kernel tuning for high-traffic servers. - inxi — when you need a quick readable summary, especially for sharing. Aggregates from lspci, dmidecode, /proc, /sys, and other sources. The
-zflag makes it safe to paste publicly. The sensor integration makes it a quick health check without custom monitoring queries. Useinxi -exzas the current preferred full-summary command — the older-Fxzform still works but-F/--fullis officially deprecated in inxi 3.x. Its--output jsonflag is useful for lightweight programmatic consumption of system info.
The most productive habit is running
sudo lshw -shortandinxi -exzas part of any new-system checklist. Two commands, two minutes, complete hardware picture.
How to Inspect Linux Hardware From the Command Line
Step 1: Query PCI devices with lspci
Run lspci to list all PCI and PCIe devices. Add -k to show kernel drivers in use, -nn to include vendor and device ID codes alongside human-readable names, or -q to query the online PCI ID database for any unrecognized devices. Use lspci -nnkv for the most complete single-pass view of PCI hardware and its driver bindings. If a device shows as unknown, run sudo update-pciids first.
Step 2: Read BIOS and SMBIOS data with dmidecode
Run sudo dmidecode to dump the full SMBIOS table. Use the -t flag with a type keyword such as sudo dmidecode -t memory to filter output to a specific hardware category. Use -s with a string keyword such as sudo dmidecode -s system-serial-number for clean single-value output suitable for scripting. For non-root access to basic system identity strings, read directly from /sys/devices/virtual/dmi/id/.
Step 3: Generate a full hardware tree with lshw
Run sudo lshw -short for a condensed hardware table showing bus hierarchy, or sudo lshw -class network to filter by a specific hardware class. Use sudo lshw -json or sudo lshw -xml to export machine-readable output for inventory automation, or sudo lshw -html for a documentation report. Add -sanitize when sharing output to strip serial numbers and IP addresses.
Step 4: Get a system summary with inxi
Run inxi -exz for a full expanded system summary with extra detail and private information anonymized. The -e/--expanded flag is the current replacement for the deprecated -F/--full in inxi 3.x — the older inxi -Fxz still works but -F is formally deprecated per the inxi options documentation. Use component-specific flags like inxi -G for graphics, inxi -N for network, or inxi -s for thermal sensor readings. Add --no-color when capturing output for logs or monitoring pipelines, or --output json for structured machine-readable output. Run inxi --recommends to check which optional packages you need for full functionality.
lspci -k on a server. A 10GbE NIC shows a Kernel modules: line but no Kernel driver in use: line. What is the most precise description of the device's state?-F / --full as deprecated. What is the correct replacement command for a shareable expanded system summary with private data filtered?lspci -xxxx to read extended PCIe config space, but it fails. lspci -xxx works, however. What is the most likely explanation?Frequently Asked Questions
What is the difference between lspci and lshw on Linux?
lspci shows only PCI and PCIe-connected devices by querying the kernel\'s PCI subsystem via /sys/bus/pci/devices/ and /proc/bus/pci. lshw produces a full hardware tree covering CPU, memory, storage, firmware, and more by reading from /proc, /sys, DMI tables, PCI configuration space, and USB descriptors in a single pass. Use lspci for a fast PCI-only view — particularly for driver troubleshooting — and lshw when you need a comprehensive hardware inventory across all subsystems.
Why does dmidecode require sudo on Linux?
dmidecode reads the SMBIOS table, attempting /sys/firmware/dmi/tables/smbios_entry_point and /sys/firmware/dmi/tables/DMI first, then falling back to /dev/mem if sysfs access fails. Both paths are root-only on most distributions. If you only need basic system identity strings without root, many fields are exposed at /sys/devices/virtual/dmi/id/ and are readable by regular users — for example, cat /sys/devices/virtual/dmi/id/product_name returns the system model without sudo.
Does inxi work on all Linux distributions?
inxi is a Perl 5 script available in the repositories of many major distributions including Debian, Ubuntu, Fedora, Arch, and openSUSE. It can be installed via apt, dnf, pacman, or zypper. On minimal server installs it may not be present by default and requires manual installation. It produces reduced output if optional dependencies like lm-sensors or smartmontools are absent — run inxi --recommends to see exactly what's missing. Note that distribution-packaged versions of inxi can be significantly out of date, particularly in LTS releases.
Can I read DMI data without root on Linux?
Yes, for many common identity strings. The kernel exposes individual DMI fields at /sys/devices/virtual/dmi/id/, and several are world-readable. Fields like product_name, bios_version, sys_vendor, and board_name are typically accessible without sudo. Serial number and UUID fields (product_serial, product_uuid) are usually restricted to root. Full SMBIOS table decoding — with all type records and cross-references — still requires dmidecode with root.
How do I find which kernel driver is bound to a PCI device?
Run lspci -k to show each PCI device alongside its currently bound kernel driver and any other kernel modules capable of handling it. For a specific device by bus address, use lspci -s 03:00.0 -k. This is the fastest way to confirm whether the expected driver is loaded, whether a device is unclaimed, or whether a competing module needs to be blacklisted. The output field labeled Kernel driver in use is what the kernel has actually bound; Kernel modules lists everything available.
Is inxi -Fxz still the right command, or should I use inxi -exz?
In inxi 3.x, the -F/--full flag is officially deprecated in favor of -e/--expanded. The replacement command is inxi -exz. The -F flag still works and you will find it in many guides and forum posts, but the official inxi options documentation states it clearly: "-F, --full Deprecated. See -e/--expanded." The deprecation was formalized in version 3.3.37 (January 6, 2025) — the changelog describes it as "changing the long incorrect -F/--full to -e/--expanded, which is more accurate." The release notes add that -F/--full will keep working until the updated inxi version reaches the stable repos of the major distributions, so existing scripts will not break immediately. For maximum accuracy with current inxi, prefer inxi -exz — especially when writing documentation, scripts, or support articles.
Do these tools work inside a VM or Docker container?
They run, but they report virtualized data rather than the host's physical hardware. Inside a VM, lspci lists virtual PCI devices (virtio controllers, emulated adapters) as presented by the hypervisor. dmidecode reads whatever SMBIOS strings the hypervisor has populated — cloud providers vary; some populate accurate strings, others return placeholder values. lshw and inxi aggregate from the same virtualized sources. Inside a Docker container or unprivileged LXC container, lspci and dmidecode will typically fail or return incomplete data without --privileged mode or explicit capability grants (CAP_SYS_RAWIO). If you need to inspect the host's physical hardware from a containerized environment, run the commands directly on the host.
A PCI device shows up in lspci but isn't working — what do I check?
Run lspci -k and look for the device. If it has a Kernel modules: line but no Kernel driver in use: line, the device is present but unclaimed — the kernel sees it but no driver has bound to it. Before assuming the driver is broken, check cat /sys/bus/pci/devices/0000:<BDF>/driver_override — a non-null value there silently overrides all automatic driver matching. Clear it if set, then trigger re-evaluation with echo "0000:<BDF>" | sudo tee /sys/bus/pci/drivers_probe. If driver_override is clear, check sudo dmesg | grep -i error for probe failures from the expected driver module, and verify the module is loaded with lsmod | grep <driver>. If the module is not loaded, sudo modprobe <driver> may be all that's needed. Common reasons a loaded module still won't bind: a missing firmware blob (dmesg will show firmware: failed to load <path> with the exact filename), a device ID the driver version doesn't recognize (test with echo "<vendor> <device>" | sudo tee /sys/bus/pci/drivers/<driver>/new_id), or a vfio-pci.ids= parameter in /proc/cmdline claiming the device at kernel boot. For a device that is bound but misbehaving, lspci -s <BDF> -vvv shows the AER error counters in the PCIe status registers — look at UESta for uncorrectable errors and CEsta for correctable ones, both of which indicate link-level problems.
How do I check which version of these tools I have installed?
Each tool exposes its version differently. For lspci: lspci --version prints the pciutils version. For dmidecode: dmidecode --version. For lshw: sudo lshw -version (note the single dash). For inxi: inxi --version or inxi -v. The package manager is also reliable: dpkg -l pciutils dmidecode lshw inxi on Debian/Ubuntu, or rpm -q pciutils dmidecode lshw inxi on Fedora/RHEL. Distribution-packaged versions of inxi in particular can be significantly behind upstream — if you need current features, check whether sudo inxi -U is permitted by your distribution's package maintainer.
Why does dmidecode -t bios show "Firmware Information" instead of "BIOS Information"?
This changed in dmidecode 3.7 (December 18, 2025). The SMBIOS Type 0 record header was renamed from "BIOS Information" to "Firmware Information" to more accurately reflect that modern platforms use UEFI firmware rather than legacy BIOS. The -t bios type keyword still works and still selects the correct record — only the displayed header string changed. If you have any script that grep-matches "BIOS Information" to locate the start of that block, it will silently produce no output on dmidecode 3.7 and needs to be updated to match "Firmware Information" instead. The same release also switched all size fields to binary unit prefixes, so dmidecode -t memory output now shows sizes as GiB rather than GB. Distributions that have not yet packaged 3.7 will still show the old strings; run dmidecode --version to confirm which version is installed before writing scripts that depend on these fields.