π§©PCI Express for NIC Engineers
From first principles β switched-fabric topology, the layered TLP/DLLP/PHY stack, link training, configuration space, BAR assignment, flow control and DMA β through to the ordering, latency, interrupt, and platform failure modes a NIC engineer must actually master.
A posted MMIO write is fire-and-forget; a non-posted read stalls the core.
How is a PCIe system physically organized, and why is it not a shared bus like conventional PCI?senior
PCIe is a switched point-to-point fabric. A host has one or more root complexes, usually integrated in the CPU, SoC, or chipset. The root complex is the boundary between host CPU/memory address space and the PCIe hierarchy. It contains root ports, and each root port starts a PCIe link toward either an endpoint, such as a NIC, or a switch.
A link is the point-to-point connection between two PCIe ports. A lane is one full-duplex serial unit: one transmit differential pair and one receive differential pair. A link can be x1, x2, x4, x8, x16, and so on, with traffic striped across lanes. A port is the interface on a component that terminates a link and contains the protocol state for that connection.
Switches extend the hierarchy. A switch has one upstream port facing the root complex and one or more downstream ports facing devices or more switches. Optional bridges can connect PCIe to older PCI/PCI-X segments or other interconnects. Endpoints are leaf devices, typically with a Type 0 configuration header; bridges and ports use the Type 1 bridge model.
The key compatibility trick is that PCIe switch ports appear to software as PCIe-to-PCIe bridges. Each downstream port creates a secondary bus, has bridge windows, and forwards transactions according to bus numbers and address ranges. Internally the switch routes packets, but enumeration sees a tree of bridges.
For a NIC engineer, the practical consequence is bandwidth isolation. A NIC on an x8 link is not arbitrating for a shared parallel bus clock with unrelated devices. It has dedicated full-duplex link bandwidth up to the next oversubscribed point, usually a switch uplink or root-port/root-complex resource.
- What is the difference between a link width and a slot's mechanical size?
- Why does a PCIe switch downstream port need bridge configuration registers?
What do the PCIe Transaction, Data Link, and Physical layers each do during a simple MMIO write?senior
Start with software-visible intent. A CPU store to a mapped NIC register reaches the root complex as a memory write request. The Transaction Layer turns that intent into a TLP with a memory-write type, requester identity, address, length, attributes, byte enables, and data payload. This is the layer that owns PCIe transaction semantics: memory reads and writes, configuration requests, completions, messages, ordering attributes, and routing information.
The Data Link Layer then makes that TLP reliable across one link, not across the whole fabric. It assigns a sequence number, appends an LCRC, keeps a replay copy, and checks transmit flow-control credits before sending. Credits are tracked by traffic class and by buffer category such as posted header/data, non-posted header/data, and completion header/data: PH, PD, NPH, NPD, CplH, CplD. The receiver validates the LCRC and sequence, accepts the TLP if it has buffer space, and returns ACK or NAK DLLPs. UpdateFC DLLPs return credits.
The Physical Layer moves bits. Its logical sublayer handles framing, scrambling/encoding appropriate to the negotiated generation, lane striping, deskew, and link-training state such as Detect, Polling, Configuration, L0, and recovery states in the LTSSM. Its electrical sublayer drives and receives the high-speed serial signals.
On the NIC side, the process reverses. The PHY reconstructs the packet stream, the Data Link Layer checks integrity and acknowledges, and the Transaction Layer decodes the memory-write TLP and delivers the register write to the device logic. Reliability for a bad symbol or bad link packet is Data Link/Physical work; the meaning of "write this NIC doorbell address with this value" is Transaction Layer work.
- Why are Data Link Layer ACKs link-local rather than end-to-end?
- Which layer decides that an address targets a memory write rather than a configuration access?
How can PCIe be a switched serial packet fabric while still looking like legacy PCI to software?senior
PCIe preserved the PCI software model. Software still discovers devices by walking a hierarchy of buses, devices, and functions. A requester is identified by a Bus:Device.Function value, with the traditional fields of 8 bus bits, 5 device bits, and 3 function bits within a PCI segment. Devices still expose configuration space, still have standard vendor/device IDs, command and status registers, class codes, BARs, and capability lists. Endpoints use a Type 0 configuration header; bridges and PCIe ports use the Type 1 bridge header with primary, secondary, and subordinate bus numbers plus I/O, memory, and prefetchable-memory windows.
That is why a PCI-aware OS can enumerate PCIe: config reads and writes are still the discovery and resource-assignment mechanism. On PCIe systems the host bridge commonly implements ECAM, where each function has 4 KiB of configuration space and memory accesses to the ECAM aperture are translated by the host bridge into PCIe configuration transactions. The first 256 bytes preserve conventional PCI configuration space; PCIe extended capabilities live above that.
What changed underneath is the transport. The shared parallel bus and sideband pins were replaced by routed TLPs over serial links. Interrupts, errors, and power management are represented through configuration capabilities and messages rather than by copying the old wires. PCIe-era software can use MSI/MSI-X, Advanced Error Reporting, PCIe capability registers such as link status/control, and link power states such as L0, L0s, and L1.
For a NIC driver engineer, the important mental model is: use the normal PCI core to find BARs and capabilities, but remember every MMIO access, DMA request, interrupt message, and error report becomes packet traffic through bridges, switches, and root ports.
- What is ECAM, and why does PCIe configuration space extend beyond 256 bytes?
- Why do PCIe switch ports enumerate as bridges instead of as ordinary endpoints?
What is a PCIe lane, how is link width negotiated, and why does width matter for a high-bandwidth NIC?senior
A PCIe lane is a full-duplex serial path made from two differential pairs: one transmit pair and one receive pair. A link is a bundle of lanes trained together, commonly x1, x2, x4, x8, or x16. Each direction has its own set of wires, so a x8 link has eight TX pairs and eight RX pairs from the point of view of one component.
During link training the two ends exchange ordered sets, discover which lanes are electrically present and usable, assign lane numbers, and negotiate the widest common operational width supported by the port, device, slot wiring, and signal quality. If a card supports x8 but is installed in a slot wired as x4, the link can only train to x4. If some lanes fail training, the link may train at a narrower width rather than fail completely.
Bandwidth scales almost linearly with width because bytes are striped across active lanes. A TLP is not normally sent down one lane as a whole packet; the physical layer distributes successive symbols or bytes across the lane bundle and the receiver deskews and reassembles them. That is why lane-to-lane skew handling is part of the physical layer.
PCIe also tolerates board-layout realities. Lane polarity inversion lets a receiver compensate when the positive and negative sides of a differential pair are swapped. Lane reversal lets a multi-lane link operate when lane ordering is reversed by routing, subject to what the components support.
For a NIC driver or performance engineer, negotiated width is a first check. A x8 adapter running at x4 has roughly half the PCIe bandwidth at the same generation, which can cap DMA throughput or make bidirectional traffic hit the bus limit before the network link is saturated.
- How would you confirm the negotiated width on Linux?
- Why does byte striping require lane deskew at the receiver?
How do PCIe generations, transfer rates, and line encodings translate into usable bandwidth?staff
PCIe generation names hide two separate facts: the raw transfer rate and the encoding or signaling method. GT/s means transfers per second, not directly bytes per second. You must apply the encoding efficiency and then multiply by lane count.
For Gen1 and Gen2, PCIe uses 8b/10b: every 8 data bits are carried in 10 transmitted bits, so useful efficiency is 8/10 = 80% and line-code overhead is 20% of the transmitted stream. Gen1 is 2.5 GT/s, so one lane carries 2.5 * 8/10 = 2.0 Gb/s = 250 MB/s. Gen2 is 5 GT/s, so one lane carries 4.0 Gb/s = 500 MB/s.
Gen3, Gen4, and Gen5 use 128b/130b, so efficiency is 128/130, about 98.46%. Gen3: 8 GT/s * 128/130 / 8 = 984.6 MB/s per lane, about 1 GB/s. Gen4 doubles that to 1969.2 MB/s per lane. Gen5 doubles again to 3938.5 MB/s per lane.
One-direction theoretical payload before TLP/header effects is therefore approximately:
- Gen1
x16:4.0 GB/s - Gen2
x16:8.0 GB/s - Gen3
x16:15.75 GB/s - Gen4
x16:31.51 GB/s - Gen5
x16:63.02 GB/s - Gen6
x16:128 GB/s, or256 GB/sbidirectional by the usual PCIe headline convention
Gen6 is different again: it runs at 64 GT/s, uses PAM4 signaling, and introduces FLIT mode with forward error correction because the raw bit error environment is harsher. Raw GT/s alone is misleading: Gen3 8 GT/s is almost twice Gen2 bandwidth per lane because it both raises rate and removes most 8b/10b overhead. For NIC work, this distinction matters when deciding whether a PCIe link can sustain line-rate DMA after descriptor, header, and cache-coherency overheads.
- Why is 8 GT/s Gen3 not just 60% faster than 5 GT/s Gen2?
- What extra overheads make real DMA bandwidth lower than these theoretical numbers?
What does the PCIe LTSSM do during link training, and how can a link downtrain in speed or width?staff
The Link Training and Status State Machine, or LTSSM, is the physical-layer control state machine that brings a PCIe link from no usable connection to L0, the normal active state. The common high-level path is Detect -> Polling -> Configuration -> L0, with Recovery used for retraining and speed changes. Power-management states include L0s, L1, and L2.
In Detect, a port performs electrical receiver detection to decide whether a link partner is present. In Polling, the two ends exchange training ordered sets, acquire bit and symbol lock, establish lane polarity if needed, and learn basic link capabilities. In Configuration, they assign link and lane numbers, settle the usable width, handle lane reversal where supported, and prepare the data link layer to come up. Once both sides agree, the link enters L0 and TLP traffic can flow.
For Gen3 and later, training also has to deal with high-speed channel loss. The link normally starts at a lower speed, then uses Recovery to change speed and perform equalization. Equalization tunes transmitter and receiver settings so the eye opening is usable at 8 GT/s and above. Recovery is also entered after errors, hot reset, or directed retraining, and can return the link to L0 if training succeeds.
A marginal board, riser, retimer path, dirty connector, or weak endpoint can downtrain. That means the device is capable of, for example, 16 GT/s x8, but the actual trained link becomes 8 GT/s x8, 16 GT/s x4, or worse. Linux exposes this difference with lspci -vv: LnkCap shows the capability advertised by the device or port, while LnkSta shows the current negotiated speed and width. For a NIC, this is often the fastest way to separate a driver throughput issue from a physical interconnect problem.
- Why do Gen3 and later links need equalization during training?
- What would you compare in LnkCap and LnkSta when debugging poor NIC throughput?
What is PCIe configuration space, and why does device discovery start there instead of in a BAR?senior
Each PCIe function has a configuration-space register file that is reachable before the device has any MMIO or I/O address assigned. Conventional PCI defined the first 256 bytes; PCIe keeps that compatible region and extends each function to 4 KB of extended configuration space, normally reached through ECAM. That matters because BARs are not usable until software has discovered, sized, and programmed them.
The first bytes are standardized. At offset 0x00, software reads Vendor ID and Device ID. Vendor ID returning 0xffff means no function responded. The Command register controls enables such as Memory Space, I/O Space, and Bus Master; the Status register reports legacy status and whether a capabilities list exists. Header Type selects the rest of the standard header layout: Type 0 is an endpoint function, while Type 1 is a PCI-to-PCI bridge or downstream port-like bridge header with bus-number and window registers. If Status.Capabilities List is set, Capabilities Pointer links the conventional capability list inside the first 256 bytes; PCIe extended capabilities live in the extended 4 KB space.
Functions are addressed geographically by BDF: 8 bus bits, 5 device bits, and 3 function bits, so one segment can address 256 buses, 32 devices per bus, and 8 functions per device. On older x86 systems, config cycles used I/O ports 0xCF8 and 0xCFC, which address only the conventional space. PCIe ECAM maps config space as memory, conceptually base + bus << 20 + device << 15 + function << 12 + offset.
For a NIC driver engineer, this is the root of trust for discovery: IDs, class code, BAR descriptors, MSI-X capability, PCIe capability, and enable bits all come from config space before any device register block is mapped.
- Why can legacy 0xCF8/0xCFC access not reach most PCIe extended capabilities?
- Which fields tell software whether it is looking at an endpoint or a bridge?
How does firmware or an OS enumerate a PCIe hierarchy and assign bus numbers through bridges?senior
Enumeration is a controlled walk of the PCIe fabric using configuration transactions, not ordinary device MMIO. Software starts at a root bus and scans possible device.function numbers. For each candidate function it reads Vendor ID; a value of 0xffff means no device responded. If function 0 exists and its Header Type has the multifunction bit set, functions 1 through 7 are also probed. Otherwise, the remaining functions for that device can be skipped.
For an endpoint with a Type 0 header, the enumerator records identity, class, capabilities, and resource needs. For a bridge with a Type 1 header, enumeration has to create topology. The bridge contains Primary Bus Number, Secondary Bus Number, and Subordinate Bus Number registers. A typical depth-first algorithm assigns the current upstream bus as Primary, allocates a new bus number as Secondary, temporarily sets Subordinate wide enough to allow probing below it, then recurses onto the secondary bus. When all children below that bridge are found, software tightens Subordinate to the highest bus number actually discovered under that bridge. Those bus numbers tell the bridge which configuration and memory transactions to forward downstream.
This process builds the logical tree that Linux later exposes as devices such as /sys/bus/pci/devices/0000:03:00.0, where 0000 is the domain or segment and 03:00.0 is the BDF. Per-function files such as config, vendor, device, class, resource, and resource0 are derived from the discovered function and assigned resources.
Static scanning becomes less final with hotplug and SR-IOV. Hotplug can add or remove an entire downstream subtree after boot, requiring reserved bus numbers and bridge windows or later rebalancing. SR-IOV can expose many virtual functions under a physical function, so the OS must allocate function numbers, bus space, MMIO apertures, and IOMMU-visible resources after the initial physical scan.
- Why is Subordinate Bus Number usually programmed loosely during probing and tightened afterward?
- What changes when a hotplug slot appears behind an already-enumerated bridge?
How does PCIe BAR sizing and assignment work, including 64-bit BARs and Linux resource0 mappings?senior
A BAR is a request for an address-decoded resource, not the resource contents themselves. During enumeration, software first disables or avoids relying on the decode, saves the current BAR value, writes all 1s to the BAR register, reads the value back, restores the original value, and decodes the hardwired-zero mask. Implemented address bits read back as writable 1s; low address bits that must be zero reveal the required naturally aligned size. For a memory BAR, bits 3:0 are attributes, so the size mask uses the upper bits after masking off those attributes. For an I/O BAR, bit 0 is 1 and the address mask starts above the low I/O attribute bits.
For memory BARs, bit 0 is 0. Bits 2:1 encode the memory type: the important modern cases are 00b for a 32-bit memory BAR and 10b for a 64-bit memory BAR. Bit 3 is the Prefetchable attribute, meaning reads have no side effects and the region may tolerate prefetching and combining according to platform rules. A 64-bit BAR consumes two consecutive 32-bit BAR slots: the lower BAR contains attributes and address bits 31:4, and the next BAR contains address bits 63:32. Software must size and program them as one resource, so a function with a 64-bit BAR0 has no independent BAR1.
After sizing all devices below a bridge, firmware or the OS assigns non-overlapping base addresses from the parent bridge's memory, prefetchable-memory, or I/O windows, then programs those bases into the BARs and enables decode with Command.Memory Space or Command.I/O Space. Alignment equals size because the device decodes a power-of-two aperture by ignoring the low address bits within that aperture.
On Linux, the assigned regions appear in the function's resource file and as mmap-able files such as resource0. A NIC driver or user-space VFIO-style process maps that file to reach the device's MMIO register window after the kernel has performed the BAR assignment.
- Why does a 64-bit BAR consume the next BAR slot?
- What has to be enabled before MMIO accesses to a programmed BAR will decode?
What is inside a PCIe Transaction Layer Packet, and how is it routed through the hierarchy?staff
A PCIe TLP is the transaction-layer unit that carries requests, completions, and messages across the fabric. The Transaction Layer creates a 3 DW or 4 DW header, optionally appends a data payload, and may append a TLP digest for end-to-end CRC (ECRC) when enabled. The Data Link Layer then wraps that TLP for one hop with a sequence number and link CRC (LCRC), and the Physical Layer adds link framing/encoding and sends it over lanes. LCRC is link-local; ECRC, when present, protects the TLP end to end across switches.
The first header fields describe what the packet is. Fmt says whether the header is 3 DW or 4 DW and whether data is present. Type identifies the transaction class, such as Memory Read, Memory Write, I/O, Configuration, Completion, or Message. Length is the payload length in DWORDs, so it describes the data portion, not the total packet size. Request-type headers also carry a Requester ID and Tag; together these identify an outstanding non-posted transaction so returned completions can be matched. Memory requests carry an Address; a 4 DW header is used for a 64-bit address, while a 3 DW memory header carries a 32-bit address. First DW Byte Enable and Last DW Byte Enable qualify which bytes are valid at the edges of a transfer, which matters for unaligned or sub-DWORD accesses.
Routing is part of the header contract:
- Address routing is used for Memory and I/O requests; switches compare the address against downstream windows.
- ID routing uses a Bus/Device/Function identifier, and is used for Configuration traffic and Completions.
- Implicit routing is used by selected Message TLPs whose destination is implied by the message type or direction, such as messages aimed upstream at the Root Complex.
A NIC driver engineer cares because every MMIO register access, DMA descriptor fetch, DMA writeback, interrupt message, and completion ultimately becomes one or more of these header forms moving through address windows and BDF-based routes.
- Why does a 64-bit MMIO address require a different memory request header size?
- Which TLPs normally use ID routing, and why is that natural for completions?
How does PCIe's split-transaction read model work, and why does it make MMIO reads stall while writes usually do not?senior
PCIe is packet-switched, not a shared parallel bus with a blocking read cycle. For a Memory Read, the requester sends a non-posted Memory Read Request TLP containing its Requester ID, a Tag, the target Address, byte enables, and a requested Length. The request is routed by address toward the completer. The requester then waits in the logical sense, but the link is free to carry other packets; nothing holds the interconnect in a bus-cycle state.
Later, the completer returns one or more Completion-with-Data TLPs (CplD). A completion header includes completion information such as Completer ID, Completion Status, byte count/lower address information for data placement, and the original Requester ID plus Tag. Those fields route the completion back by ID and let the requester match it to the outstanding read. Large reads can be split into multiple completions, subject to payload size and boundary rules, so software and hardware must treat a read response as a packet sequence rather than a single electrical turnaround.
The Tag is the key scaling mechanism. A function can keep many non-posted reads outstanding, each with a distinct transaction identity, so latency is hidden by concurrency. NICs depend on this for DMA reads of host descriptors or packet buffers: the device issues multiple reads, then consumes completions as they return.
A posted Memory Write is different: it carries its data in the request TLP and normally has no completion. Once accepted into the PCIe fabric under flow-control and ordering rules, the requester can move on. That is why a CPU MMIO write to a NIC doorbell can retire after being posted, while an MMIO register read must wait for a CplD to make the load architecturally complete. In a datapath, reads expose round-trip PCIe latency; writes are used for doorbells and producer updates because they avoid that request/completion turn.
- What fields let the requester match a completion to the original read?
- Why can a NIC hide DMA-read latency better than a CPU polling an MMIO register?
How does PCIe make TLP delivery reliable across one physical link?staff
PCIe does not treat a TLP as fire-and-forget once it is serialized onto a lane. For each TLP handed down by the Transaction Layer, the Data Link Layer prepends a 12-bit sequence number, with a reserved nibble in the transmitted sequence-number field, and appends a 32-bit Link CRC, LCRC. The receiver's Data Link Layer checks that the sequence number is the expected next value and recomputes the LCRC over the protected TLP contents. If the packet is good, it strips the Data Link additions, passes the TLP upward, and sends an ACK DLLP identifying the acknowledged sequence number.
If the receiver sees corruption or a sequence problem, it sends a NAK DLLP. The transmitter keeps transmitted but not-yet-acknowledged TLPs in a replay buffer. On ACK, it can retire the acknowledged entries. On NAK, or if the replay timer expires before progress is acknowledged, it automatically retransmits the unacknowledged TLPs from the replay buffer. This is why a transient bit error can be corrected below the driver without losing the memory read, DMA write, or completion that was in flight.
The scope is important: this is hop-by-hop reliability between adjacent PCIe ports. A NIC behind a switch has one reliable link to the switch, and the switch has another reliable link upstream. It is distinct from end-to-end ECRC, which the Transaction Layer may use to protect a TLP across routing elements. For a driver engineer, occasional AER Bad TLP, Bad DLLP, or replay-timeout counters can mean the link recovered by replay. A sustained storm means the link is spending real bandwidth on retries and usually points to signal integrity, marginal cabling, riser, slot, power, or speed-width negotiation trouble.
- Why is Data Link replay hop-by-hop instead of end-to-end?
- What changes when ECRC is enabled?
How does PCIe credit-based flow control prevent receiver buffer overrun, and why can it throttle NIC reads?staff
PCIe flow control is proactive. Instead of sending until a receiver drops a frame, each receiver advertises how much buffer space it has for each traffic class and credit category. For ordinary TC0 traffic, the six practical pools are posted header PH, posted data PD, non-posted header NPH, non-posted data NPD, completion header CplH, and completion data CplD. Header credits account for TLP headers; data credits account for payload storage in flow-control units, commonly described as 16 B units with rounding up for partial units.
At link initialization, the peers exchange initial credits with InitFC DLLPs. As the receiver consumes TLPs and frees internal buffer space, it sends UpdateFC DLLPs. The transmitter maintains credit counters and must have enough header and data credits for the specific TLP before it is allowed to send it. A memory write with payload consumes posted header and posted data credits; a memory read request consumes non-posted header credit; a completion with data consumes completion header and completion data credits. If the needed credits are unavailable, the TLP waits locally and some other eligible TLP may be sent.
That is very different from Ethernet congestion behavior: PCIe is designed not to drop and retry merely because the next hop's receive buffer is full. Backpressure appears as the transmitter being unable to launch more TLPs. For a NIC, this matters when tuning host reads, device reads, DMA engines, and queue-depth policy. Large Max Read Request Size values and many outstanding reads can improve bandwidth only if completions can return. If completion header or data credits are tight, or the completer drains slowly, completion traffic stalls and read throughput flattens even though the raw link speed looks sufficient.
- Which credit pools are consumed by a DMA memory read request and by its completion?
- How can increasing outstanding reads stop helping throughput?
How do PCIe memory, I/O, and configuration address spaces differ, and how does a CPU MMIO access reach the right endpoint?senior
PCIe inherited three programmer-visible spaces from PCI: memory space, I/O space, and configuration space. Configuration space is the standardized per-function register file used for discovery and setup: Vendor ID, Device ID, Command, Status, capability lists, and BAR0-style resource descriptors. Legacy I/O Space still exists for compatibility, but it is slow-path and uncommon in modern high-performance devices. NIC datapaths use memory space: device registers, doorbells, queues, and large device windows appear as MMIO ranges assigned to BARs.
A memory BAR is either prefetchable or non-prefetchable. A prefetchable BAR says reads have no side effects and the device tolerates host-side optimizations such as read-ahead, combining, and merging of writes. It is appropriate for RAM-like windows, frame buffers, or large device memory apertures, and it is the kind of resource that can be placed in large prefetchable bridge windows, including 64-bit address windows above 4 GB. A non-prefetchable BAR is for registers: reading a status register might clear bits, pop a FIFO, advance an internal pointer, or return changing state, and posted writes to control registers may have precise device meaning. MMIO control registers must therefore be mapped non-prefetchable.
Routing is address based. Firmware or the OS assigns BAR addresses and programs each root port or PCI-to-PCI bridge with memory base/limit windows, including separate non-prefetchable and prefetchable windows. When the CPU accesses a physical address inside a NIC BAR, the root complex turns that load or store into a PCIe Memory Read or Memory Write Request TLP. Each downstream bridge compares the TLP address with its programmed windows and forwards it only toward the bus containing that endpoint. The endpoint finally decodes the address against its BAR and register map.
- Why is a NIC register BAR normally non-prefetchable?
- What has to be programmed in a PCIe bridge before a device behind it can respond to MMIO?
When a PCIe NIC performs DMA, what address does it put in Memory Read and Memory Write TLPs, and why must drivers use the kernel DMA API?senior
PCIe DMA means the endpoint becomes a requester. After the OS enables bus mastering for the function, a NIC DMA engine can issue Memory Write Request TLPs to place received packet data or completion descriptors in host memory, and Memory Read Request TLPs to fetch transmit packet data, descriptors, or other host-owned structures. The CPU is not copying the packet bytes at that moment; it previously programmed device registers and descriptor rings, then the device initiates PCIe transactions itself.
The address in those TLPs is a DMA address, often represented in Linux as dma_addr_t. It is not a C pointer and is not guaranteed to be the CPU physical address. On a simple system without an IOMMU, the DMA address commonly equals the physical address. With an IOMMU, the device emits an I/O virtual address, or IOVA; the IOMMU translates it to a physical page and enforces permissions and bounds. That is what prevents a buggy or malicious bus-mastering device from freely writing arbitrary RAM.
The kernel DMA API is the contract that creates those device-visible mappings. For streaming buffers, a driver uses calls such as dma_map_single() or page-based variants, receives a dma_addr_t, programs that value into a descriptor, and later synchronizes or unmaps it according to direction. For coherent allocations, dma_alloc_coherent() returns CPU-visible memory plus a stable DMA address suitable for rings or status blocks shared for a long time. Drivers must not hand virt_to_phys() results or raw kernel pointers to hardware because cache maintenance, bounce buffers, address masks, and IOMMU mappings are platform-dependent.
For an ixy-style mempool, the practical model is: allocate or pin packet buffers, keep them resident, map them for DMA, and store both the CPU pointer and the NIC-visible DMA address. The CPU uses the pointer to inspect packets; the NIC uses the DMA address in descriptors.
- What can go wrong if a driver gives hardware a kernel virtual address?
- Why might descriptor rings use coherent DMA while packet payload buffers use streaming DMA mappings?
How did PCIe interrupts evolve from legacy INTx to MSI and MSI-X, and how are they actually delivered?senior
Legacy PCI had physical interrupt pins, but PCIe carries almost everything in-band as packets. For backward compatibility, PCIe emulates the four legacy lines INTA# through INTD# as virtual wires. A function sends an Assert_INTx Message TLP when its legacy interrupt condition becomes active and a Deassert_INTx Message TLP when the level is cleared. That is why legacy INTx is level-sensitive: if the driver does not clear the device cause, the interrupt remains asserted. It is also usually shared, so the OS interrupt handler must ask each possible device, or the specific driver must read its interrupt-status register, to decide whether the interrupt belongs to it.
MSI changed the model. Instead of a virtual wire, the OS programs the device's MSI capability with a Message Address and Message Data value. When the device wants to interrupt, it issues an ordinary posted Memory Write TLP to that address, with the data payload identifying the message/vector. The root complex or interrupt-remapping hardware recognizes that target address and turns the write into a CPU interrupt. There is no dedicated interrupt pin and no completion for the write.
MSI-X keeps the same delivery mechanism but makes it scalable and more controllable. The MSI-X capability points to an MSI-X Table and a Pending Bit Array, both located in device memory space through a memory BAR. Each table entry contains its own lower/upper message address, message data, and vector-control mask bit; the PBA records vectors that became pending while masked. Architecturally, an MSI-X interrupt is still just a posted Memory Write TLP whose address and data came from the selected table entry.
- Why must a legacy INTx driver clear the device interrupt cause before returning from the handler?
- What fields does an MSI-X table entry contain?
Why does an MSI or MSI-X interrupt normally imply that the DMA data it announces is already visible to the CPU?staff
The important trick is that an MSI/MSI-X interrupt is itself a posted Memory Write TLP. A NIC receive path typically performs producer-consumer updates in this order: DMA packet bytes into host buffers, DMA a receive descriptor or completion record that transfers ownership to software, then issue the MSI/MSI-X write. With ordinary PCIe ordering, posted writes from the same requester on the same ordered path are not allowed to pass earlier posted writes when Relaxed Ordering is not used. So the interrupt write that reaches the root complex is ordered behind the earlier DMA writes it is announcing.
That property is the foundation of the classic NIC RX model. The CPU does not poll every cache line of every receive buffer. It sleeps or runs other work, takes the interrupt, then consumes descriptors on the assumption that the device's packet data and ownership/status writes arrived before the interrupt became observable. Without that ordering, an ISR could run, read a fresh descriptor, and still see stale packet bytes or old descriptor fields.
The guarantee is not magic; it depends on the device and fabric preserving the producer-before-doorbell sequence. If the NIC marks the data writes or the MSI write with Relaxed Ordering, uses different traffic classes or virtual channels with no ordering relationship, or sends through platform paths that are not mutually ordered, the interrupt can become visible before the data path has reached coherent memory. Device designers normally avoid that for completion-producing writes, or insert their own ordering point.
Drivers still use barriers and sometimes a status read because PCIe ordering is only one part of the system. The CPU, compiler, cache-coherency domain, IOMMU, and interrupt controller all participate. In Linux-style RX code, a driver commonly reads the descriptor ownership/status, uses a DMA read barrier such as dma_rmb() before trusting the rest of the descriptor or packet metadata, and may read device status to confirm the interrupt cause or serialize with device state.
- How could Relaxed Ordering break an interrupt-after-DMA producer-consumer protocol?
- Why is a CPU-side DMA read barrier still useful if PCIe already ordered the writes?
Walk through the Linux probe path for a PCIe NIC from enumeration to mapped BARs and bus mastering. What ordering of calls matters, and what can go wrong if you skip one?senior
A good probe path is roughly: match vendor/device or class, pci_enable_device_mem(), set DMA masks with dma_set_mask_and_coherent(), request BAR resources with pci_request_regions() or managed variants, map MMIO with pci_iomap()/pcim_iomap() or ioremap(), allocate queues, allocate MSI-X vectors with pci_alloc_irq_vectors(), request IRQs, then pci_set_master() once the device is ready to DMA.
The important details are not just ceremony:
- BAR addresses in config space are not the CPU virtual addresses a driver dereferences; use resource helpers and
ioremap/pci_iomap. They are also not what the device uses for DMA into host memory. pci_set_master()sets the Bus Master Enable bit in the PCI_COMMAND register; without it the root complex drops the endpoint's Memory Write/Read requests, so DMA never leaves the device. Memory Space Enable in the same register gates inbound MMIO to BARs.- Requesting regions catches collisions and documents ownership.
- The DMA mask must be set before mapping buffers, or the DMA API may hand the device addresses it cannot generate, silently routing through SWIOTLB or failing.
- Interrupts should usually be registered while the device is quiesced, with device interrupt causes masked, to avoid early or stale interrupts.
static int nic_probe(struct pci_dev *pdev, const struct pci_device_id *id)
{
int rc;
rc = pcim_enable_device(pdev);
if (rc)
return rc;
rc = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64));
if (rc)
return rc;
rc = pcim_iomap_regions(pdev, BIT(0), "nic");
if (rc)
return rc;
pci_set_master(pdev);
return pci_alloc_irq_vectors(pdev, 1, nr_cpu_ids, PCI_IRQ_MSIX);
}
A staff-level answer also mentions shutdown symmetry: mask device interrupts, stop DMA, synchronize_irq()/free IRQs, quiesce queues, then unmap/free DMA resources. The reverse-order trap is freeing DMA memory while the device can still write to it, or calling pci_clear_master() before DMA has actually drained.
- Where would you put `pci_set_master()` relative to queue initialization?
- What changes if you use managed `pcim_*` helpers?
- How do you debug a device that probes but never DMAs?
Why is an MMIO read from a NIC register much more expensive than an MMIO write, and how should that shape a low-latency datapath?senior
A PCIe MMIO write is usually a posted Memory Write TLP: the CPU/root complex can accept it into a posted-write queue and retire the store before the endpoint has acted on it. An MMIO read is a non-posted Memory Read request: the CPU cannot complete the load until a Completion with Data returns from the device. That round trip crosses the core, the uncore, the root complex, the link, a switch if present, the endpoint, and comes back as a completion. The load instruction stalls retirement the entire time, and because it is a strongly ordered uncached access it cannot be hidden by out-of-order execution the way a cache miss to DRAM can.
For a low-latency NIC datapath, this means:
- Avoid MMIO reads in the hot path; poll host memory rings or completion queues that the NIC writes by DMA.
- Batch doorbell writes, but beware added queueing latency.
- Use readbacks deliberately as flushes only on slow paths such as reset, teardown, or error handling.
- Prefer device designs where status is DMAed to cacheable host memory and the doorbell path is write-only.
Real systems vary, but MMIO reads are commonly on the order of several hundred nanoseconds directly off a root port, rising into microseconds through a switch or busy fabric, while a posted write can be retired by the CPU in tens of nanoseconds. A strong answer hedges those numbers and says to measure with rdtscp around readl() while pinning the CPU, disabling frequency scaling, and checking PCIe topology with lspci -tv.
- When is an MMIO read intentionally useful?
- How would relaxed ordering change, or not change, this?
- How would you measure it without fooling yourself?
Explain posted, non-posted, and completion TLPs using NIC examples. Which path carries a doorbell? Which path carries a register read?senior
A NIC driver doorbell write is normally a posted Memory Write TLP to a BAR. The CPU does not wait for an endpoint completion, so it is low overhead but can be buffered and combined before reaching the NIC.
A register read is a non-posted Memory Read TLP. The endpoint must return a Completion with Data (CplD). Configuration and I/O accesses are also non-posted, which is part of why config space is not a datapath mechanism. A non-posted write (rare on PCIe: I/O writes and config writes) returns a Completion without data (Cpl) carrying only status.
NIC DMA writes to host memory, including RX payload, RX descriptors, or completion entries, are also posted Memory Writes from the endpoint. NIC DMA reads of TX descriptors or packet data are non-posted Memory Reads from the endpoint and consume completion credits and a tag per outstanding request when host memory returns data.
The performance consequence is asymmetric: posted writes stress posted header/data credits and ordering; reads stress request tags (a finite pool, classically 32, extended to 256 or 10-bit/2048 with Extended Tag), non-posted credits, completion credits, completion latency, and Max Read Request Size. In a TX-heavy NIC, descriptor and payload fetch policy often matters as much as raw link bandwidth, because read throughput is gated by (tags x payload) / round-trip-latency long before it is gated by the wire.
- Why can too many outstanding DMA reads hurt tail latency?
- What limits read concurrency besides link bandwidth?
- Where do completions get reordered or blocked?
State the PCIe transaction ordering rules a NIC relies on. Why is the producer-consumer model safe, and what exactly does the Relaxed Ordering attribute relax?staff
PCIe defines ordering within a single Traffic Class per requester using a small set of rules. The ones a NIC depends on:
- A Posted write may not pass another Posted write (writes stay in order). This is what makes the producer-consumer model work: a NIC DMAs the packet payload (posted), then DMAs the completion descriptor or flips an ownership bit (posted), and the consumer is guaranteed to see the payload no later than the descriptor.
- A Read Request must not pass a Posted write. This forces a read to push prior writes ahead of it, so a flushing readback works.
- Completions may pass Posted writes and may pass each other if they are for different requests; completions for the same request stay in order.
- Non-posted requests may be reordered relative to each other fairly freely, which is why you cannot use two reads to order anything.
Relaxed Ordering (the RO attribute bit in the TLP header) relaxes exactly the first rule for the marked transaction: an RO-marked Posted write is allowed to pass earlier Posted writes, and an RO completion may pass earlier writes. This lets switches and the root complex avoid head-of-line blocking, e.g. let bulk payload writes overtake each other into memory. The danger is marking the publish write (the descriptor or ownership flip) Relaxed: then the consumer can observe the descriptor before the payload it points to, which is silent data corruption. Safe designs mark only the payload data writes RO and keep the ownership/status write strongly ordered, or rely on a device that places the publish write last in strong order regardless. ID-Based Ordering (IDO) is a related, safer relaxation that only reorders traffic from different requester IDs.
- Which single rule makes a flushing readback work?
- Why are two reads useless for ordering?
- How is IDO safer than RO for a multi-function NIC?
How do PCIe ordering rules and Relaxed Ordering affect a NIC writing packet data and completion descriptors into host memory? Separate fabric ordering from CPU/cache ordering.staff
The producer-consumer invariant is: the CPU must not observe a completion descriptor that says packet N is ready before the packet bytes and metadata it points at are visible. PCIe's posted-cannot-pass-posted rule preserves this for writes from the same requester in the same TC, but the NIC design and descriptor attributes matter.
Relaxed Ordering can improve throughput by letting switches/root complexes reorder traffic that has no dependency. It is dangerous if applied to the publish write. The common rule is: data DMA may be relaxed if the platform and driver tolerate it, but the final ownership/status write that publishes the buffer should be strongly ordered relative to the data it publishes. Some devices use a separate writeback bit, generation counter, or phase bit specifically as the publish point.
The CPU side is a separate ordering domain. Cache coherence on x86 does not buy you compiler or load ordering: drivers must still use dma_rmb() (or the DMA API sync calls for streaming mappings) between reading the ownership bit and reading the rest of the descriptor, so the compiler and CPU do not hoist the payload load above the ownership load. On coherent x86 dma_rmb() is just a compiler barrier and is nearly free; on weakly ordered ARM it emits a dmb(oshld). Portable driver code cannot assume the cheap case.
The hard failure mode is intermittent stale data: the ring index or valid bit changes, the driver consumes the packet, and only under load or behind a switch does the payload line lag the completion line, or only on ARM does the CPU reorder the loads.
- Would you allow Relaxed Ordering on TX descriptor reads?
- What symptom would a missing `dma_rmb()` produce?
- How could a NIC firmware change accidentally break this?
What are Max Payload Size and Max Read Request Size, and why might a NIC driver or firmware care about both?senior
Max Payload Size (MPS, Device Control bits 7:5) controls the largest data payload an endpoint may put in a single TLP it transmits. It is negotiated to the minimum supported across the whole path; a NIC behind a root port that only does 256B cannot use 512B even if both endpoints support it. Max Read Request Size (MRRS, Device Control bits 14:12) controls the largest Memory Read request the function may issue; completions are split by the completer's Read Completion Boundary (RCB, typically 64 or 128 bytes) and its MPS.
For NICs:
- Larger MPS improves efficiency for DMA writes by amortizing the ~20-24 byte TLP header, but increases serialization delay and can worsen head-of-line blocking for latency-sensitive traffic.
- Larger MRRS improves DMA read throughput for TX payload fetches (fewer requests, fewer tags consumed per byte), but a big read can monopolize completion bandwidth and increase tail latency for small control reads or other functions sharing the link.
- The effective setting is constrained by the whole path: endpoint, root port, switches, and firmware/OS policy. Linux exposes a policy via
pcie_bus_config(e.g.pcie_bus_safe,pcie_bus_perf). - Bugs show up as Malformed TLP, Unsupported Request, Completer Abort, poisoned completions, bad performance, or AER logs, not just clean negotiation failure. An MPS mismatch where a device emits a payload larger than a switch was told to expect is a classic source of Malformed TLP storms.
A senior engineer does not hardcode a magic largest value. They inspect lspci -vv (DevCtl: MaxPayload / MaxReadReq), benchmark small-packet latency and bulk throughput separately, and watch AER/correctable error counters.
- Why can increasing MRRS make p99 latency worse?
- What does `lspci -vv` show for MPS/MRRS?
- What goes wrong if MPS is mismatched across a switch?
MSI versus MSI-X for a multi-queue NIC: walk through how an MSI-X interrupt is actually generated, and what matters beyond the maximum vector count.senior
Both MSI and MSI-X signal an interrupt by having the device issue a posted Memory Write TLP to a special address the OS programmed. The mechanics differ: MSI uses one address with up to 32 vectors encoded by mutating the low bits of a single data value, allocated as a power-of-two block; MSI-X has a per-vector table (in a BAR, located via the Table Offset/BIR in the capability) where each entry is 16 bytes: a 64-bit Message Address, a 32-bit Message Data, and a Vector Control dword whose bit 0 is the per-vector Mask. There is also a Pending Bit Array (PBA): if a vector is masked when the device wants to fire, it sets the PBA bit, and the write is emitted when the vector is unmasked.
MSI-X is preferred for high-end NICs because each vector's address/data and mask are independent, so vectors can be steered to different CPUs and masked individually. The design questions are:
- Do RX/TX queues get one vector per queue pair, or are causes aggregated?
- How is vector affinity aligned with RSS indirection, NAPI instances, IRQ affinity (
/proc/irq/N/smp_affinity), and NUMA locality?pci_alloc_irq_vectors_affinity()lets the kernel spread vectors automatically. - What interrupt moderation policy balances p50 latency against interrupt rate and p99 burst behavior?
- Are interrupts masked at the MSI-X Vector Control bit, the device's own cause/mask register, or both? Masking at the table is expensive (MMIO write); most NICs gate at a device register.
- Does the driver handle lost-edge style races by polling after unmasking, since the message is a one-shot edge with no level to re-assert?
Linux uses pci_alloc_irq_vectors() with PCI_IRQ_MSIX/PCI_IRQ_MSI flags, then pci_irq_vector() and request_irq(). The staff nuance is that interrupts are control-plane hints; the datapath must be correct under polling (NAPI), coalescing, vector migration, and spurious or shared legacy INTx fallback.
- What does the PBA do when a vector is masked?
- Why is masking at the MSI-X table more costly than at a device register?
- What is the unmask/poll race in NAPI?
Doorbell writes are often mapped write-combining for userspace kernel-bypass NICs. What are the benefits and hazards?staff
Write-combining lets the CPU merge adjacent or repeated stores in a WC buffer and drain them as larger PCIe writes. For a userspace TX path this reduces per-packet store overhead and improves doorbell/PIO bandwidth; it is attractive for queue doorbells, PIO send windows, and descriptor push paths where the whole descriptor is written into device memory.
The hazards are all about visibility and ordering:
- A WC store can sit in a CPU buffer indefinitely; it drains on a fence, a serializing event, buffer fill, or an access to a different WC line. Latency is not bounded by the store itself.
- Stores may be coalesced and reordered within the buffer, so a partially written descriptor could drain, and register layout must tolerate combining.
- A later normal (WB) memory store does not order prior WC MMIO stores. On x86 you need
sfence(WC ordering is weaker than the usual TSO); plainmovordering does not apply to WC. - Mapping the wrong BAR region WC breaks registers that require exact width, exact order, or have read side effects.
Kernel drivers should use the correct mapping/accessor pair, for example ioremap_wc()/devm_ioremap_wc() only for regions designed for WC, and writel() (ordered) vs writel_relaxed() (no implicit barrier) deliberately. Kernel-bypass ABIs (verbs, EF_VI, DPDK UIO/VFIO) must document exactly which write publishes the descriptor and what fence is required before ringing the doorbell, since the application, not the kernel, now owns that ordering contract.
- When is `writel_relaxed()` wrong?
- Why does WC need `sfence` when normal stores do not?
- What hardware register design makes WC safe?
What is PCIe peer-to-peer DMA, and why is it hard to make reliable for NIC-to-GPU or NIC-to-NVMe traffic?staff
P2P DMA means one PCIe function directly targets another function's BAR or P2P memory instead of routing data through host DRAM. It can cut copies and DRAM bandwidth pressure, which is attractive for storage, GPU, and AI-cluster data paths (GPUDirect / NVMe-to-NIC).
The hard parts are platform routing and correctness:
- Devices often need to be under the same switch or root port; many root complexes do not route peer traffic at all, or route it but with poor performance.
- ACS (Access Control Services) on the switch/root port can force upstream redirection of P2P requests so they go up to the root complex and back down, defeating the expected path and changing ordering. Direct peer routing usually requires ACS redirect to be off for that path, which weakens isolation.
- IOMMUs translate DMA to system memory; targeting a peer BAR requires the IOVA to map to that BAR (or ACS Direct Translated P2P), not arbitrary support.
- Lifetime and invalidation are difficult: if the provider driver removes, resets (FLR), or re-BARs the device, consumers must stop DMA first or they write into a dead window.
- Security isolation may intentionally block P2P.
In Linux, the PCI P2PDMA layer has pci_p2pdma_add_resource(), pci_p2pmem_find()/pci_p2pmem_alloc(), and pci_p2pdma_distance() to qualify whether a path is supported and how far traffic must travel. A serious design includes topology qualification, fallback to host memory, reset/remove coordination, and counters proving the traffic is not silently hairpinning through the root complex.
- How does ACS force P2P traffic upstream?
- What fallback would you ship?
- How would you prove the traffic path?
What are ATS and ACS, and how do they matter to a high-performance NIC using an IOMMU?staff
ATS (Address Translation Services) lets a PCIe endpoint request translations from the IOMMU and cache them in its own Address Translation Cache (ATC, a device-side TLB). DMA then uses pre-translated addresses (the Translated bit set in the TLP) so the IOMMU does not re-walk on every access, cutting IOTLB pressure. It underpins PASID/PRI shared-virtual-memory designs, where PRI (Page Request Interface) lets the device fault in an unmapped page. The correctness-critical piece is invalidation: when a mapping changes, the IOMMU sends an ATS Invalidation request to the device, and the device must purge matching ATC entries and order all prior uses of those translations ahead of the invalidation completion before acking. A device that returns a stale translation, or acks before draining in-flight DMA, can write to freed or reassigned memory.
ACS (Access Control Services) lives in switches/root ports and controls whether peer traffic is allowed direct, redirected upstream, or blocked. It is essential for isolation and for correct IOMMU grouping: devices that can talk to each other without going through the IOMMU must share an IOMMU group, because the IOMMU cannot otherwise enforce a boundary between them. This is why ACS support directly determines VFIO/SR-IOV assignment granularity.
For a NIC the tradeoff is isolation versus latency/throughput. ATS reduces translation overhead but makes the device partly responsible for translation coherence; a buggy ATC is a security hole (the device asserting Translated bypasses IOMMU permission checks). ACS makes a topology safe for multi-tenant use but can redirect P2P up to the root complex and forces coarser IOMMU groups. A staff answer mentions VFIO, IOMMU groups, invalidation latency under churn, and testing on real topologies rather than assuming a feature bit means the platform path is viable.
- What happens if an ATS translation survives unmap?
- Why does lack of ACS coarsen IOMMU groups?
- Why is a device asserting the Translated bit a trust decision?
A NIC intermittently disappears or throws completion timeouts under load. Walk through completion timeout, the replay timer, and link recovery, and how you'd tell a fabric problem from a device hang.staff
There are two distinct timeout mechanisms, and conflating them sends you down the wrong path.
Completion Timeout is a transaction-layer timer at the requester: after issuing a non-posted request (e.g. a NIC's Memory Read for TX data), if no Completion returns within the programmed window the requester reports a Completion Timeout (an uncorrectable error, surfaced via AER). The range is set in Device Control 2 (Completion Timeout Value field): spec ranges run from Range A (50-100 us) up to Range D (tens of ms to ~64 s), with a typical default in the 50 us-50 ms band. A NIC seeing completion timeouts means its own read to host memory (or to a peer) never came back: causes include an IOMMU fault dropping the request, a switch/path problem, the completer (host or peer) stalling, or too many outstanding reads exhausting credits so requests never get serviced.
Replay timer / ACK-NAK is a data-link-layer mechanism, one layer below. Every TLP is held in the replay buffer until the receiver ACKs it; if the ACK does not arrive in time, or a NAK arrives, the sender replays from the buffer. Excessive replays indicate signal-integrity problems and show up as correctable errors (Bad TLP / Bad DLLP). Repeated failure drives the LTSSM into Recovery to retrain; if retraining fails the link can drop width/speed or go down entirely, which is when the device vanishes from lspci.
To separate them: check lspci -vv AER status for whether errors are correctable link errors (DLLP/TLP, replay, signal integrity) versus uncorrectable transaction errors (Completion Timeout, UR, CA). Look at LnkSta for unexpected speed/width downgrades (a fabric/SI symptom). Correlate timestamps with load, ASPM state, and temperature. A device hang typically shows clean link but timeouts and a stuck queue; a fabric/SI problem shows correctable-error escalation, replays, LTSSM Recovery, and link retraining or surprise removal. Experiments: pin link speed/width lower, disable ASPM, reduce MRRS/outstanding reads, move slots or reseat retimers/cables, and check IOMMU fault logs.
- Which register sets the completion timeout range?
- What does an unexpected LnkSta width/speed downgrade suggest?
- Why can too many outstanding reads cause completion timeouts?
What is ASPM, and how does it bite a latency-sensitive NIC? How would you confirm it's the culprit?senior
ASPM (Active State Power Management) lets the link autonomously enter low-power states during idle: L0s (fast, one-directional electrical idle, exit ~hundreds of ns) and L1 (deeper, both directions, exit often single-digit microseconds), with L1 substates L1.1/L1.2 saving more by gating common-mode/reference clock at the cost of even longer exit. The exit latency is paid on the first transaction after idle.
The NIC impact is tail latency and jitter, not average throughput. A bursty RX pattern lets the link drift to L1 between bursts; the next packet's DMA write or the doorbell's read then eats the L1 exit, adding microseconds to exactly the latency-sensitive first packet. Each device advertises its acceptable exit latency, and the OS is supposed to only enable a state if the path's exit latency is within the endpoint's tolerance, but firmware/BIOS misconfiguration and aggressive platform defaults frequently violate this in practice. This is a classic cause of mysterious p99 spikes that vanish under sustained load (because the link never idles).
To confirm: lspci -vv shows LnkCap (ASPM supported, exit-latency fields) and LnkCtl (ASPM control: enabled state). Toggle it with the pcie_aspm=off kernel parameter or per-device via sysfs (/sys/bus/pci/devices/<bdf>/link/l1_aspm, or the power/control and ASPM policy under /sys/module/pcie_aspm/parameters/policy), then re-measure the latency histogram. If p99/p99.9 collapses with ASPM off but throughput is unchanged, ASPM exit was the cause. The right fix is usually to disable L1(/substates) for that device rather than blanket-disabling ASPM across the platform, since other devices may need the power savings.
- Why does the spike disappear under sustained load?
- Which `lspci` fields show ASPM state and exit latency?
- Why prefer disabling L1 for one device over `pcie_aspm=off` globally?
Your driver needs to reset a wedged NIC function without rebooting. What does an FLR actually do and not do, and what must the driver handle around it?staff
A Function Level Reset (FLR), triggered via the Initiate Function Level Reset bit in the Device Control register (and exposed in Linux through pci_reset_function() / pcie_flr()), resets a single function: it stops DMA, clears the function's internal state and queues, and returns most registers to defaults. Crucially for correctness, the spec requires the function to terminate or complete any outstanding requests so that no Completions arrive for transactions issued before the reset, and to not retain DMA state. The function must signal it can accept FLR and complete it within a bounded time (the spec allows up to 100 ms before config access should succeed again).
What FLR does NOT do is the trap: it does not preserve your configuration. BAR assignments, Command register (Bus Master/Memory Space), MPS/MRRS, MSI-X enable and the entire MSI-X table, ASPM, and AER settings are reset. The standard PCI header plus MSI/MSI-X and PM capabilities must be saved and restored by the OS (pci_save_state() before, pci_restore_state() after); the extended config space and any SR-IOV header are generally NOT restored by FLR handling, so the driver must reprogram device-specific state itself.
What the driver must handle: quiesce first (mask interrupts, stop queues, synchronize_irq()), tear down DMA mappings or ensure the device cannot reference them, save state, issue the reset, restore state, re-enable bus mastering, rebuild rings and re-map DMA, re-init MSI-X (re-request affinity), and re-arm. For SR-IOV, a PF reset implicitly disturbs its VFs; an FLR on a VF assigned to a guest via VFIO must be coordinated so the guest driver re-initializes. Skipping the quiesce means in-flight DMA or a late completion can corrupt memory or land on a half-reset function; skipping save/restore means the function comes back with no BARs and Bus Master Enable cleared, looking 'dead' even though the silicon is fine.
- Why must you `pci_save_state()` before FLR?
- What in-flight hazard does FLR's completion-handling requirement prevent?
- How does a VF FLR interact with a VFIO guest?
AER reports correctable Bad TLP/Bad DLLP and occasional completion timeouts under load on a NIC. How do you debug without immediately blaming the card?senior
AER is PCIe Advanced Error Reporting: it records correctable and uncorrectable link/transaction errors in config space and reports them through the OS. The first step is to capture facts: BDF, link speed/width (LnkSta), MPS/MRRS, AER status/header-log registers, kernel log timing, topology, firmware versions, ASPM state, retimers/cables if external, and whether errors correlate with load, power state, or temperature.
Then separate classes of failure:
- Correctable Bad DLLP/Bad TLP point to the data link layer: signal integrity, a marginal retimer, a dirty connector, or marginal link training. These trigger replays; a rising replay/error rate that escalates to LTSSM Recovery is the signature of an SI problem.
- Completion Timeout (uncorrectable) points to a transaction that never came back: endpoint firmware stall, root-port policy, too many outstanding reads, a device reset mid-flight, an IOMMU/ACS fault dropping the request, or a fabric path problem.
- Unsupported Request or Completer Abort often implicates a bad BAR access, a config access to a disabled function, an MPS/MRRS violation (Malformed TLP), or stale DMA to a removed/FLRed function.
Practical steps include lspci -vvxxxx, decoding the AER header log to recover the offending TLP, setpci only carefully, forcing lower link speed/width to test SI margin, disabling ASPM for an experiment, changing slot/topology, reducing MRRS/outstanding reads, checking IOMMU fault logs, and correlating with the NIC's internal error counters. The goal is to turn a vague PCIe error into a reproducible transaction or link condition.
- What would make you suspect signal integrity?
- What does the AER header log give you?
- How can reducing MRRS help diagnosis?