* [PATCH] libpci: Add new direct HW access method intel-conf1-ext
@ 2026-08-27 23:34 Pali Rohár
0 siblings, 0 replies; only message in thread
From: Pali Rohár @ 2026-08-27 23:34 UTC (permalink / raw)
To: Martin Mareš; +Cc: Filip Štědronský, linux-pci
Introduce the intel-conf1-ext method, an extension of intel-conf1 that
allows access to all PCIe registers, including the extended PCIe space
starting at offset 0x100. This method utilizes four reserved bits above
the bus bits to represent the extended PCIe registers.
Currently, on x86 architectures, this method is supported exclusively
by AMD Family 10h-18h CPUs. Newer AMD CPUs require a setup that has not yet
been documented by AMD and remains unknown. Intel CPUs do not support this
method yet.
---
lib/i386-cpuid.h | 108 +++++++++++++++++++++
lib/i386-ports.c | 245 +++++++++++++++++++++++++++++++++++++++++++++--
lib/init.c | 6 ++
lib/internal.h | 2 +-
lib/pci.h | 1 +
pcilib.man | 4 +
6 files changed, 357 insertions(+), 9 deletions(-)
create mode 100644 lib/i386-cpuid.h
diff --git a/lib/i386-cpuid.h b/lib/i386-cpuid.h
new file mode 100644
index 000000000000..783e9159b5ae
--- /dev/null
+++ b/lib/i386-cpuid.h
@@ -0,0 +1,108 @@
+#if defined(__GNUC__) && ((__GNUC__ == 4 && __GNUC_MINOR__ >= 3) || (__GNUC__ > 4))
+
+/*
+ * GCC's cpuid.h header file is buggy and cannot be included more times.
+ * MinGW's intrin.h header file in some versions includes cpuid.h and
+ * therefore intrin.h and cpuid.h cannot be mixed in one source unit.
+ * libpci's i386-io-windows.h in some cases include intrin.h and therefore
+ * in some configurations it is not possible to include cpuid.h here again.
+ * GCC's cpuid.h since beginning define __cpuid macro, so it could be used
+ * as a include guard. But unfortunately MinGW-w64 since version 10.0.0
+ * automatically undefine macro __cpuid in intrin.h.
+ */
+#if defined(__MINGW64_VERSION_MAJOR) && __MINGW64_VERSION_MAJOR >= 10
+#include <intrin.h>
+#elif !defined(__cpuid)
+#include <cpuid.h>
+#endif
+#ifndef __get_cpuid
+#define __get_cpuid __get_cpuid
+#endif
+
+#elif defined (__GNUC__)
+
+static inline int
+__get_cpuid(unsigned int level, unsigned int *eax, unsigned int *ebx, unsigned int *ecx, unsigned int *edx)
+{
+#ifndef __x86_64__
+ /* cpuid does not have to be supported on 32-bit x86, check for it. */
+ unsigned int eflags_orig, eflags_mod;
+ asm (
+ "pushfl\n\t"
+ "pushfl\n\t"
+ "popl\t%0\n\t"
+ "movl\t%0, %1\n\t"
+ "xorl\t%2, %0\n\t"
+ "pushl\t%0\n\t"
+ "popfl\n\t"
+ "pushfl\n\t"
+ "popl\t%0\n\t"
+ "popfl\n\t"
+ : "=&r" (eflags_mod), "=&r" (eflags_orig)
+ : "i" (0x00200000)
+ );
+ if (!((eflags_mod ^ eflags_orig) & 0x00200000))
+ return 0;
+#endif
+
+ asm ("cpuid\n\t" : "=a" (*eax), "=b" (*ebx), "=c" (*ecx), "=d" (*edx) : "0" (level));
+ return 1;
+}
+#define __get_cpuid __get_cpuid
+
+#elif defined(_MSC_VER)
+
+/* MSVC provides intrinsic __cpuid since version 14.00 included in VS2005. */
+#if _MSC_VER >= 1400
+#pragma intrinsic(__cpuid)
+#else
+static void
+__cpuid(int CPUInfo[4], int InfoType)
+{
+ __asm {
+ mov esi, CPUInfo
+ mov eax, InfoType
+ xor ecx, ecx
+ _emit 0x0F __asm _emit 0xA2 /* cpuid */
+ mov dword ptr [esi + 0], eax
+ mov dword ptr [esi + 4], ebx
+ mov dword ptr [esi + 8], ecx
+ mov dword ptr [esi + 12], edx
+ }
+}
+#endif
+static inline int
+__get_cpuid(unsigned int level, unsigned int *eax, unsigned int *ebx, unsigned int *ecx, unsigned int *edx)
+{
+ int regs[4];
+
+#ifndef _M_AMD64
+ /* cpuid does not have to be supported on 32-bit x86, check for it. */
+ unsigned int eflags_orig, eflags_mod;
+ __asm {
+ pushfd
+ pushfd
+ pop eflags_orig
+ push eflags_orig
+ pop eflags_mod
+ xor eflags_mod, 0x00200000
+ push eflags_mod
+ popfd
+ pushfd
+ pop eflags_mod
+ popfd
+ }
+ if (!((eflags_mod ^ eflags_orig) & 0x00200000))
+ return 0;
+#endif
+
+ __cpuid(regs, level);
+ *eax = regs[0];
+ *ebx = regs[1];
+ *ecx = regs[2];
+ *edx = regs[3];
+ return 1;
+}
+#define __get_cpuid __get_cpuid
+
+#endif
diff --git a/lib/i386-ports.c b/lib/i386-ports.c
index b9d863c0762a..952e74e9688d 100644
--- a/lib/i386-ports.c
+++ b/lib/i386-ports.c
@@ -36,6 +36,8 @@
#error Do not know how to access I/O ports on this OS.
#endif
+#include "i386-cpuid.h"
+
static int conf12_io_enabled = -1; /* -1=haven't tried, 0=failed, 1=succeeded */
static int
@@ -50,7 +52,10 @@ static void
conf12_init(struct pci_access *a)
{
if (!conf12_setup_io(a))
+ {
+ a->debug("\n");
a->error("No permission to access I/O ports (you probably have to be root).");
+ }
}
static void
@@ -103,8 +108,6 @@ intel_sanity_check(struct pci_access *a, struct pci_methods *m)
* Configuration type 1
*/
-#define CONFIG_CMD(bus, device_fn, where) (0x80000000 | (bus << 16) | (device_fn << 8) | (where & ~3))
-
static int
conf1_detect(struct pci_access *a)
{
@@ -132,19 +135,19 @@ conf1_detect(struct pci_access *a)
}
static int
-conf1_read(struct pci_dev *d, int pos, byte *buf, int len)
+conf1_ext_read(struct pci_dev *d, int pos, byte *buf, int len)
{
int addr = 0xcfc + (pos&3);
int res = 1;
- if (d->domain || pos >= 256)
+ if (d->domain || pos >= 4096)
return 0;
if (len != 1 && len != 2 && len != 4)
return pci_generic_block_read(d, pos, buf, len);
intel_io_lock();
- intel_outl(0x80000000 | ((d->bus & 0xff) << 16) | (PCI_DEVFN(d->dev, d->func) << 8) | (pos&~3), 0xcf8);
+ intel_outl(0x80000000 | ((pos & 0xf00) << 16) | ((d->bus & 0xff) << 16) | (PCI_DEVFN(d->dev, d->func) << 8) | (pos & 0xfc), 0xcf8);
switch (len)
{
@@ -164,19 +167,28 @@ conf1_read(struct pci_dev *d, int pos, byte *buf, int len)
}
static int
-conf1_write(struct pci_dev *d, int pos, byte *buf, int len)
+conf1_read(struct pci_dev *d, int pos, byte *buf, int len)
+{
+ if (pos >= 256)
+ return 0;
+
+ return conf1_ext_read(d, pos, buf, len);
+}
+
+static int
+conf1_ext_write(struct pci_dev *d, int pos, byte *buf, int len)
{
int addr = 0xcfc + (pos&3);
int res = 1;
- if (d->domain || pos >= 256)
+ if (d->domain || pos >= 4096)
return 0;
if (len != 1 && len != 2 && len != 4)
return pci_generic_block_write(d, pos, buf, len);
intel_io_lock();
- intel_outl(0x80000000 | ((d->bus & 0xff) << 16) | (PCI_DEVFN(d->dev, d->func) << 8) | (pos&~3), 0xcf8);
+ intel_outl(0x80000000 | ((pos & 0xf00) << 16) | ((d->bus & 0xff) << 16) | (PCI_DEVFN(d->dev, d->func) << 8) | (pos & 0xfc), 0xcf8);
switch (len)
{
@@ -194,6 +206,15 @@ conf1_write(struct pci_dev *d, int pos, byte *buf, int len)
return res;
}
+static int
+conf1_write(struct pci_dev *d, int pos, byte *buf, int len)
+{
+ if (pos >= 256)
+ return 0;
+
+ return conf1_ext_write(d, pos, buf, len);
+}
+
/*
* Configuration type 2. Obsolete and brain-damaged, but existing.
*/
@@ -294,6 +315,202 @@ conf2_write(struct pci_dev *d, int pos, byte *buf, int len)
return res;
}
+static int cpu_detected = -1;
+static int
+conf1_ext_detect_cpu(struct pci_access *a)
+{
+#ifdef __get_cpuid
+ unsigned int eax, ebx, ecx, edx;
+ unsigned int family;
+#endif
+
+ if (cpu_detected >= 0)
+ goto out;
+
+#ifndef __get_cpuid
+ a->debug("...cannot determinate CPU model");
+ cpu_detected = 0;
+ goto out;
+#else
+ if (!__get_cpuid(0, &eax, &ebx, &ecx, &edx))
+ {
+ a->debug("...detected unsupported CPU without cpuid instruction");
+ cpu_detected = 0;
+ goto out;
+ }
+
+ /* Check for AuthenticAMD or HygonGenuine signature. */
+ if ((ebx != 0x68747541 || edx != 0x69746E65 || ecx != 0x444D4163) &&
+ (ebx != 0x6f677948 || edx != 0x6e65476e || ecx != 0x656e6975))
+ {
+ a->debug("...detected unsupported CPU %.4s%.4s%.4s", (char *)&ebx, (char *)&edx, (char *)&ecx);
+ cpu_detected = 0;
+ goto out;
+ }
+
+ /* Get the CPU family number. */
+ if (!__get_cpuid(1, &eax, &ebx, &ecx, &edx))
+ {
+ a->debug("...detected unsupported CPU AMD/Hygon");
+ cpu_detected = 0;
+ goto out;
+ }
+ family = (eax >> 8) & 0xf;
+ if (family == 0xf)
+ family += (eax >> 20) & 0xff;
+
+ /* For now only AMD Family 10h-18h CPUs are supported (see conf1_ext_setup). */
+ if (family < 0x10 || family > 0x18)
+ {
+ a->debug("...detected unsupported CPU AMD Family %xh", family);
+ cpu_detected = 0;
+ goto out;
+ }
+
+ a->debug("...detected supported CPU AMD Family %xh", family);
+ cpu_detected = family;
+ goto out;
+#endif
+
+out:
+ return cpu_detected;
+}
+
+static int
+conf1_ext_setup(struct pci_access *a)
+{
+ const char *reg_name;
+ struct pci_dev d;
+ int off;
+ int bit;
+ u32 val;
+ u32 tmp;
+
+ /*
+ * AMD Family 10h-16h:
+ * EnableCf8ExtCfg bit [46] in NB Configuration 1 [NB_CFG1] register controls
+ * whether CF8 extended configuration cycles are enabled or not. This 64-bit
+ * register is mapped to MSR register [MSRC001_001F] and its upper 32 bits
+ * also to PCI register [D18F3x8C]. That PCI register is in config space of
+ * bus 0x00 device 0x18 function 0x3 offset 0x8c.
+ * AMD Family 17h-18h:
+ * EnableCf8ExtCfg bit [0] in Core Coherent Master Configuration Access
+ * Control register mapped to PCI register [D18F4x044] controls whether CF8
+ * extended configuration cycles are enabled or not. That PCI register is in
+ * config space of bus 0x00 device 0x18 function 0x4 offset 0x44.
+ * AMD Family 19h+:
+ * Not supported for now, seems that EnableCf8ExtCfg bit is by default not
+ * enabled and documentation does not specify where is this register located.
+ * Public references:
+ * BIOS and Kernel Developer's Guide (BKDG) For AMD Family 10h-16h Processors
+ * Non-public/NDA references:
+ * Processor Programming Reference (PPR) for AMD Family 17h Processors
+ */
+
+ memset(&d, 0, sizeof(d));
+ d.bus = 0;
+ d.dev = 0x18;
+ d.func = cpu_detected < 0x17 ? 0x3 : 0x4;
+ off = cpu_detected < 0x17 ? 0x8c : 0x44;
+ bit = cpu_detected < 0x17 ? (46-32) : 0;
+ reg_name = cpu_detected < 0x17 ? "NB_CFG1" : "CCMCAC";
+
+ a->debug("...reading %s", reg_name);
+ if (!conf1_read(&d, off, (byte *)&val, sizeof(val)))
+ {
+ a->debug("...failed");
+ return 0;
+ }
+
+ if (!(val & (1 << bit)))
+ {
+ a->debug("...EnableCf8ExtCfg unset");
+ val |= (1 << bit);
+ a->debug("...setting EnableCf8ExtCfg in %s", reg_name);
+ if (!conf1_write(&d, off, (byte *)&val, sizeof(val)))
+ {
+ a->debug("...failed");
+ return 0;
+ }
+ a->debug("...reading %s", reg_name);
+ if (!conf1_read(&d, off, (byte *)&val, sizeof(val)))
+ {
+ a->debug("...failed");
+ return 0;
+ }
+ a->debug("...verifying EnableCf8ExtCfg");
+ if (!(val & (1 << bit)))
+ {
+ a->debug("...failed");
+ return 0;
+ }
+ a->debug("...passed");
+ }
+ else
+ a->debug("...EnableCf8ExtCfg already set");
+
+ /*
+ * Set CF8 address to the first register from extended config space (0x100)
+ * and verify that address was set the correct value. When Cf8ExtCfg access
+ * is unsupported or not enabled then CF8 address bits for extended config
+ * space cannot be set and are hardwired to zeros.
+ */
+ a->debug("...verifying Cf8ExtCfg access");
+ intel_io_lock();
+ tmp = intel_inl(0xcf8);
+ intel_outl(0x81000000, 0xcf8);
+ val = intel_inl(0xcf8);
+ intel_outl(tmp, 0xcf8);
+ intel_io_unlock();
+ if (val != 0x81000000)
+ {
+ a->debug("...failed");
+ return 0;
+ }
+ a->debug("...passed");
+
+ return 1;
+}
+
+static void
+conf1_ext_init(struct pci_access *a)
+{
+ if (cpu_detected < 0)
+ {
+ a->debug("detecting CPU");
+ conf1_ext_detect_cpu(a);
+ }
+ if (!cpu_detected)
+ {
+ a->debug("\n");
+ a->error("Unsupported CPU for Intel conf1 extended interface (requires AMD Family 10h-18h).");
+ }
+
+ conf12_init(a);
+
+ if (!conf1_ext_setup(a))
+ {
+ a->debug("\n");
+ a->error("Cannot setup Intel conf1 extended interface (probably not supported).");
+ }
+}
+
+static int
+conf1_ext_detect(struct pci_access *a)
+{
+ if (!conf1_ext_detect_cpu(a))
+ return 0;
+
+ if (!conf1_detect(a))
+ return 0;
+
+ if (!conf1_ext_setup(a))
+ return 0;
+
+ a->debug("\n");
+ return 1;
+}
+
struct pci_methods pm_intel_conf1 = {
.name = "intel-conf1",
.help = "Raw I/O port access using Intel conf1 interface",
@@ -306,6 +523,18 @@ struct pci_methods pm_intel_conf1 = {
.write = conf1_write,
};
+struct pci_methods pm_intel_conf1_ext = {
+ .name = "intel-conf1-ext",
+ .help = "Raw I/O port access using Intel conf1 extended interface",
+ .detect = conf1_ext_detect,
+ .init = conf1_ext_init,
+ .cleanup = conf12_cleanup,
+ .scan = pci_generic_scan,
+ .fill_info = pci_generic_fill_info,
+ .read = conf1_ext_read,
+ .write = conf1_ext_write,
+};
+
struct pci_methods pm_intel_conf2 = {
.name = "intel-conf2",
.help = "Raw I/O port access using Intel conf2 interface",
diff --git a/lib/init.c b/lib/init.c
index d20cb9b3096a..92d2ddcdda56 100644
--- a/lib/init.c
+++ b/lib/init.c
@@ -168,6 +168,11 @@ static struct pci_methods *pci_methods[PCI_ACCESS_MAX] = {
#else
NULL,
#endif
+#ifdef PCI_HAVE_PM_INTEL_CONF
+ &pm_intel_conf1_ext,
+#else
+ NULL,
+#endif
};
// If PCI_ACCESS_AUTO is selected, we probe the access methods in this order
@@ -189,6 +194,7 @@ static int probe_sequence[] = {
PCI_ACCESS_RT_THREAD_SMART_DM,
// Low-level methods poking the hardware directly
PCI_ACCESS_ECAM,
+ PCI_ACCESS_I386_TYPE1_EXT,
PCI_ACCESS_I386_TYPE1,
PCI_ACCESS_I386_TYPE2,
PCI_ACCESS_MMIO_TYPE1_EXT,
diff --git a/lib/internal.h b/lib/internal.h
index d889735cf7b4..acc797079cea 100644
--- a/lib/internal.h
+++ b/lib/internal.h
@@ -141,7 +141,7 @@ void pci_free_params(struct pci_access *acc);
void pci_scan_caps(struct pci_dev *, unsigned int want_fields);
void pci_free_caps(struct pci_dev *);
-extern struct pci_methods pm_intel_conf1, pm_intel_conf2, pm_linux_proc,
+extern struct pci_methods pm_intel_conf1_ext, pm_intel_conf1, pm_intel_conf2, pm_linux_proc,
pm_fbsd_device, pm_aix_device, pm_nbsd_libpci, pm_obsd_device,
pm_dump, pm_linux_sysfs, pm_darwin, pm_sylixos_device, pm_hurd,
pm_mmio_conf1, pm_mmio_conf1_ext, pm_ecam,
diff --git a/lib/pci.h b/lib/pci.h
index db0477d385bd..def8222123db 100644
--- a/lib/pci.h
+++ b/lib/pci.h
@@ -57,6 +57,7 @@ enum pci_access_type {
PCI_ACCESS_ECAM, /* PCIe ECAM via /dev/mem */
PCI_ACCESS_AOS_EXPANSION, /* AmigaOS Expansion library */
PCI_ACCESS_RT_THREAD_SMART_DM, /* RT-Thread Smart pci */
+ PCI_ACCESS_I386_TYPE1_EXT, /* i386 ports, type 1 extended */
PCI_ACCESS_MAX
};
diff --git a/pcilib.man b/pcilib.man
index 0e4fe1029c5e..eb600ef11111 100644
--- a/pcilib.man
+++ b/pcilib.man
@@ -35,6 +35,10 @@ to all users, the rest only to root.
Direct hardware access via Intel configuration mechanism 1. Available on i386 and compatibles
on Linux, Solaris/x86, GNU Hurd, Windows, BeOS and Haiku. Requires root privileges.
.TP
+.B intel-conf1-ext
+Direct hardware access via Extended PCIe Intel configuration mechanism 1. Available on
+AMD Family 10h-18h CPUs. Requires root privileges.
+.TP
.B intel-conf2
Direct hardware access via Intel configuration mechanism 2. Available on i386 and compatibles
on Linux, Solaris/x86, GNU Hurd, Windows, BeOS and Haiku. Requires root privileges. Warning: This method
--
2.20.1
^ permalink raw reply related [flat|nested] only message in thread
only message in thread, other threads:[~2026-08-27 23:34 UTC | newest]
Thread overview: (only message) (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-27 23:34 [PATCH] libpci: Add new direct HW access method intel-conf1-ext Pali Rohár
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox