CfgRd0, CfgRd1, CfgWr0, CfgWr1 — how the 3DW configuration header works, what Type 0 vs Type 1 means, how Bus/Device/Function addressing targets config space, Type 1 to Type 0 conversion at switches, Extended Register Number, and how all of this applies in Gen 6.
Configuration TLPs are how software reads and writes the configuration registers of every PCIe device in the system. During boot, the BIOS or OS uses them to discover every device, assign bus numbers, program Base Address Registers (BARs), enable bus mastering, and set up interrupt routing. At runtime, drivers use them to configure device-specific features.
There are four configuration TLP types:
All four share the same header layout. The only difference is in DW0: Fmt distinguishes read from write, and Type bit[0] distinguishes Type 0 from Type 1. Configuration TLPs are always non-posted — both reads and writes require a completion to return.
The single bit that separates Type 0 from Type 1 is bit 0 of the Type field. Type 0 means “this is the final destination — the device on my Secondary Bus should respond.” Type 1 means “I need to forward this further downstream.” Every bridge or switch port along the path from the Root Complex to the target device converts the Type 1 into a Type 0 at the right moment.
Every configuration request uses a fixed 3DW (12-byte) header — there is no 4DW variant. Configuration space is always addressed by Bus/Device/Function, never by a 64-bit memory address.
Several DW0 fields behave differently for configuration TLPs compared to memory TLPs. These restrictions come directly from Table 5-6 of the MindShare PCIe specification:
| Field | Normal memory TLPs | Configuration TLPs |
|---|---|---|
| TC [22:20] | 0–7 (any traffic class) | Must be 000 (TC 0 only). Ensures config traffic never interferes with high-priority traffic. |
| Attr[2] / IDO [18] | Optional — can be set | Reserved · must be 0. ID-based ordering does not apply. |
| TH [17] | Optional — can be set | Reserved · must be 0. TLP Processing Hints not applicable. |
| Attr[1] / RO [13] | Optional — can be set | Must be 0. Relaxed ordering not applicable to config. |
| Attr[0] / NS [12] | Optional — can be set | Must be 0. No Snoop not applicable to config registers. |
| AT [11:10] | 00, 01, or 10 | Must be 00. Address Translation Services not applicable. |
| Length [9:0] | 1–1024 DW | Always exactly 1 (= 0b00_0000_0001). Config space is always accessed one DW at a time. |
DW1 of a configuration TLP is structured identically to that of a memory request — Requester ID (16-bit BDF), Tag (8-bit), Last DW BE (4-bit), First DW BE (4-bit). The differences are in how the byte enable fields must be set:
| Field | Bytes | Value for config TLPs | Why |
|---|---|---|---|
| Requester ID | 4–5 | BDF of the Root Complex port generating the request | Completion routes back to the RC using this BDF |
| Tag | 6 | 8-bit tag value unique among pending config requests | CplD echoes it to match the completion to the outstanding request |
| Last DW BE | 7 bits [7:4] | Must be 0x0 (all zeros) | Config requests are always 1 DW — there is no “last” DW separate from the “first” DW |
| First DW BE | 7 bits [3:0] | Any pattern, including all zeros, is valid | Selects which bytes within the 1 DW access are valid. All-zero means “no bytes” — effectively a flush operation. |
DW2 of a configuration TLP does not carry a memory address. Instead it carries the Bus/Device/Function number of the target device and the DW offset of the register being accessed within that device’s 4 KB configuration space.
The Register Number field selects which DW of configuration space is being accessed. The two-level addressing system exists for backward compatibility with PCI:
| Addressing mode | ExtRegNo [3:0] | RegNo [7:2] | DW range | Byte range |
|---|---|---|---|---|
| PCI-compatible | 0x0 (must be zero) | 0x00–0x3F (6 bits) | DW 0–63 | Bytes 0–255 (first 256 bytes) |
| PCIe Extended | 0x1–0xF (4 bits) | 0x00–0x3F (6 bits) | DW 64–1023 | Bytes 256–4095 |
Every PCIe function has 4 KB of configuration space, accessed one DW at a time via configuration TLPs. The space is divided into a PCI-compatible region (first 256 bytes) and a PCIe-extended region (bytes 256–4095).
The conversion rule from the MindShare spec (Chapter 5, Configuration Requests section):
target_bus == Secondary → the target device is directly connected. Convert: flip Type bit[0] from 1 to 0. Forward the resulting Type 0 out the downstream port.Secondary < target_bus ≤ Subordinate → the target is further downstream. Forward the Type 1 unchanged.target_bus < Secondary or target_bus > Subordinate → the target is not reachable downstream. Discard (do not generate a UR completion — config targets are expected to sometimes be absent).| target_bus vs bridge registers | Bridge action | TLP type sent downstream |
|---|---|---|
| target_bus == Secondary | Convert: flip Type bit[0] | Type 0 (CfgRd0 / CfgWr0) |
| Secondary < target_bus ≤ Subordinate | Forward unchanged | Type 1 (CfgRd1 / CfgWr1) |
| target_bus outside Secondary–Subordinate range | Discard silently | Not forwarded |
Configuration TLPs use ID routing — they are routed based on the Bus/Device/Function number in DW2, not on a memory address. This makes config routing simpler than memory routing: no Base/Limit register comparison is needed.
Configuration TLPs always generate completions. The completion format is the same standard 3DW completion header described in PCIe-07 — no special config completion type exists.
| Config TLP type | Completion returned | Payload | Notes |
|---|---|---|---|
| CfgRd0 / CfgRd1 | CplD (completion with data) | 1 DW — the register value | Always exactly 1 DW, never split. Byte Count = 4. Lower Address = 0. |
| CfgWr0 / CfgWr1 | Cpl (completion without data) | None | Status-only confirmation. SC = write accepted. UR or CA = write failed. |
| Any config TLP | CRS (Config Request Retry Status) | None (no data with CRS) | Device not yet ready — RC must retry. Device has up to 1 second after reset to stop returning CRS. |
CRS (Config Request Retry Status) is unique to configuration completions — it is not valid in response to memory or IO TLPs. When the RC receives CRS during a CfgRd of the VendorID register and CRS Software Visibility is enabled, it synthesises a VendorID return value of 0x0001 to software — a reserved value that tells the OS “this device is not ready yet, try again later” without blocking the entire boot sequence.
PCIe devices do not have hardwired BDF addresses. A device at the end of a link genuinely does not know its own bus number until the BIOS tells it. The mechanism is elegant: any Type 0 configuration write that reaches a device causes that device to latch the Bus and Device number from DW2 of the CfgWr0 header as its own identity.
Standard PCIe addressing uses 5 bits for Device and 3 bits for Function in DW2. ARI (Alternative Routing Identifier) is an optional PCIe capability that reinterprets DW2 byte 9: instead of 5 bits Device + 3 bits Function, ARI uses 8 bits for Function only (Device is always 0). This extends the per-bus function count from 8 to 256.
| Mode | Byte 9 bits [7:3] | Byte 9 bits [2:0] | Max functions per bus |
|---|---|---|---|
| Standard | Device [4:0] | Function [2:0] | 32 devices × 8 functions = 256 total per bus |
| ARI enabled | Function [7:3] | Function [2:0] | Device is always 0 · 256 functions per bus |
ARI requires support from both the switch downstream port (ARI Forwarding Enabled) and the endpoint (ARI Capable). It is commonly used in SR-IOV scenarios where a single physical function may expose up to 256 virtual functions, all sharing the same PCIe bus.
The configuration TLP header format is completely unchanged in Gen 6. Every byte, every field, and every routing rule described in this post applies identically. Gen 6 changes only the Physical Layer — configuration TLPs are packed into flits the same way any other TLP is.
Gen 6 introduces new PCIe Extended Capability structures that live in the extended config space (bytes 256–4095, requiring ExtRegNo ≠ 0). These include:
All of these are accessed using the same CfgRd0/CfgWr0 TLPs with ExtRegNo set appropriately. From a TLP format perspective, nothing changes — only the register values and capability structures at the destination differ.
| Item | Value / Rule |
|---|---|
| CfgRd0 — Fmt, Type | Fmt=000 · Type=0_0100 · no data · 12 bytes header |
| CfgWr0 — Fmt, Type | Fmt=010 · Type=0_0100 · 1 DW payload · 12 bytes header |
| CfgRd1 — Fmt, Type | Fmt=000 · Type=0_0101 · forwarded until Type 1→0 conversion |
| CfgWr1 — Fmt, Type | Fmt=010 · Type=0_0101 · forwarded then converted |
| Type 0 vs Type 1 | Type bit[0]=0 → Type 0 (device on Secondary Bus responds) · bit[0]=1 → Type 1 (forwarded downstream) |
| TC field | Must be 000 (TC 0 only) for all config TLPs |
| Length field | Always 0b00_0000_0001 (= 1 DW) for all config TLPs |
| RO, NS, IDO, AT bits | All must be 0 for config TLPs |
| Last DW BE | Always 0x0 for config TLPs (Length = 1 DW) |
| First DW BE | Any pattern including all zeros · selects bytes within the 1 DW register access |
| DW2 target address | Bus[31:24] · Device[23:19] · Function[18:16] · Rsvd[15:12] · ExtRegNo[11:8] · RegNo[7:2] · 00 |
| Register address | 10-bit DW address = {ExtRegNo[3:0], RegNo[5:0]} → selects 1 of 1024 DWs in 4 KB config space |
| ExtRegNo = 0 | PCI-compatible config space (bytes 0–255) |
| ExtRegNo ≠ 0 | PCIe extended capabilities (bytes 256–4095) |
| Type 1→0 conversion rule | target_bus == Secondary → convert · Secondary < target ≤ Subordinate → forward Type 1 · outside → discard |
| BDF capture | Any CfgWr0 received by a device causes it to latch its Bus and Device number from DW2 |
| CfgRd completion | CplD with 1 DW data payload · Byte Count = 4 · never split |
| CfgWr completion | Cpl (no data) · status confirms write landed |
| CRS | Config Request Retry Status — config-only · device not ready · RC retries · VendorID = 0x0001 if SW Visibility enabled |
| ARI | Reinterprets Byte 9 as 8-bit Function (Device=0) · 256 functions per bus · used in SR-IOV |
| Gen 6 impact | Zero — header format unchanged · flit packing transparent · new Gen 6 caps accessible via extended config space |