Re: [kvm-unit-tests PATCH v2] x86: pci: Support unaligned register access in PCI config read/write

David Matlack <[email protected]>
Newsgroups org.kernel.vger.kvm
Message-ID <[email protected]>
On 2026-08-04 11:23 PM, Irene Wang wrote:
> Per the PCI Local Bus Specification (Section 3.2.2.3.2, "Configuration
> Mechanism #1"), port 0xCF8 (CONFIG_ADDRESS) requires a DWORD-aligned
> register offset (bits [1:0] = 00b), while byte and word offsets within
> the DWORD must be selected via data port 0xCFC + (reg & 3).
> 
> Previously, PCI_CONF1_ADDRESS did not clear bits [1:0], and 8-bit /
> 16-bit helpers always accessed base port 0xCFC directly. This worked
> in QEMU because its PCI host bridge emulation preserves unaligned bits
> in CONFIG_ADDRESS and uses them during CONFIG_DATA accesses. However,
> in strictly spec-compliant VMMs (and potentially real hardware), bits
> [1:0] of 0xCF8 are ignored, causing non-aligned reads/writes at port
> 0xCFC to erroneously target byte 0 of the DWORD. In practice, this
> causes pci_find_dev() to read the vendor ID twice instead of reading
> the vendor ID and device ID.
> 
> Fix this by masking `reg` with `~3` in PCI_CONF1_ADDRESS and adding
> the `(reg & 3)` offset to the CONFIG_DATA port for 8-bit and 16-bit
> accessors, ensuring compatibility across QEMU and other VMMs. Additionally,
> assert that 16-bit accesses do not use an offset of 3, as reading/writing a
> word across DWORD boundaries (port 0xCFC + 3) is invalid per spec.
> 
> Assisted-by: Gemini:gemini-3.6-flash
> Signed-off-by: Irene Wang <[email protected]>
> ---
> v1 -> v2:
>  - Enclose macro parameter `dev` in parentheses in PCI_CONF1_ADDRESS.
>  - Add assert((reg & 3) != 3) in pci_config_readw() and pci_config_writew()
>    to guard against illegal 16-bit accesses crossing DWORD boundaries.
> 
> Link to v1: https://lore.kernel.org/kvm/[email protected]/
> 
>  lib/x86/asm/pci.h | 12 +++++++-----
>  1 file changed, 7 insertions(+), 5 deletions(-)
> 
> diff --git a/lib/x86/asm/pci.h b/lib/x86/asm/pci.h
> index 03e55c27..da0b59a3 100644
> --- a/lib/x86/asm/pci.h
> +++ b/lib/x86/asm/pci.h
> @@ -9,18 +9,19 @@
>  #include "pci.h"
>  #include "x86/asm/io.h"
>  
> -#define PCI_CONF1_ADDRESS(dev, reg)	((0x1 << 31) | (dev << 8) | reg)
> +#define PCI_CONF1_ADDRESS(dev, reg)	((0x1 << 31) | ((dev) << 8) | ((reg) & ~3))

A comment here would be useful. Spec-compliant host bridges (including
emulations) should ignore the lower 2 bits so clearing it should
technically be unnecessary. The reason we have to clear it is because
QEMU is not spec-compliant and will consume those 2 bits.

>  
>  static inline uint8_t pci_config_readb(pcidevaddr_t dev, uint8_t reg)
>  {
>      outl(PCI_CONF1_ADDRESS(dev, reg), 0xCF8);
> -    return inb(0xCFC);
> +    return inb(0xCFC + (reg & 3));
>  }
>  
>  static inline uint16_t pci_config_readw(pcidevaddr_t dev, uint8_t reg)
>  {
> +    assert((reg & 3) != 3);
>      outl(PCI_CONF1_ADDRESS(dev, reg), 0xCF8);
> -    return inw(0xCFC);
> +    return inw(0xCFC + (reg & 3));
>  }
>  
>  static inline uint32_t pci_config_readl(pcidevaddr_t dev, uint8_t reg)
> @@ -33,14 +34,15 @@ static inline void pci_config_writeb(pcidevaddr_t dev, uint8_t reg,
>                                       uint8_t val)
>  {
>      outl(PCI_CONF1_ADDRESS(dev, reg), 0xCF8);
> -    outb(val, 0xCFC);
> +    outb(val, 0xCFC + (reg & 3));
>  }
>  
>  static inline void pci_config_writew(pcidevaddr_t dev, uint8_t reg,
>                                       uint16_t val)
>  {
> +    assert((reg & 3) != 3);

Should assert((reg & 3) == 0) be added to the readl/writel routines as
well?

>      outl(PCI_CONF1_ADDRESS(dev, reg), 0xCF8);
> -    outw(val, 0xCFC);
> +    outw(val, 0xCFC + (reg & 3));
>  }
>  
>  static inline void pci_config_writel(pcidevaddr_t dev, uint8_t reg,
> -- 
> 2.55.0.571.g244d577d93-goog
>
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.