Add support for pci config space access via mmconfig bar. Enable for qemu q35 chipset. Main advantage is that we need only one instead of two io operations per config space access, which translates to one instead of two vmexits for virtualization.
Signed-off-by: Gerd Hoffmann kraxel@redhat.com --- src/hw/pci.h | 1 + src/fw/pciinit.c | 1 + src/hw/pci.c | 54 ++++++++++++++++++++++++++++++++++++++++++------ 3 files changed, 50 insertions(+), 6 deletions(-)
diff --git a/src/hw/pci.h b/src/hw/pci.h index 2e30e28918a0..01c51f705a00 100644 --- a/src/hw/pci.h +++ b/src/hw/pci.h @@ -39,6 +39,7 @@ u32 pci_config_readl(u16 bdf, u32 addr); u16 pci_config_readw(u16 bdf, u32 addr); u8 pci_config_readb(u16 bdf, u32 addr); void pci_config_maskw(u16 bdf, u32 addr, u16 off, u16 on); +void pci_enable_mmconfig(u64 addr, const char *name); u8 pci_find_capability(u16 bdf, u8 cap_id, u8 cap); int pci_next(int bdf, int bus); int pci_probe_host(void); diff --git a/src/fw/pciinit.c b/src/fw/pciinit.c index d5e87f00f164..d25931bb0573 100644 --- a/src/fw/pciinit.c +++ b/src/fw/pciinit.c @@ -480,6 +480,7 @@ static void mch_mmconfig_setup(u16 bdf) pci_config_writel(bdf, Q35_HOST_BRIDGE_PCIEXBAR, 0); pci_config_writel(bdf, Q35_HOST_BRIDGE_PCIEXBAR + 4, upper); pci_config_writel(bdf, Q35_HOST_BRIDGE_PCIEXBAR, lower); + pci_enable_mmconfig(Q35_HOST_BRIDGE_PCIEXBAR_ADDR, "q35"); }
static void mch_mem_addr_setup(struct pci_device *dev, void *arg) diff --git a/src/hw/pci.c b/src/hw/pci.c index 9855badbc7da..3b8f43cbe5dc 100644 --- a/src/hw/pci.c +++ b/src/hw/pci.c @@ -14,38 +14,71 @@ #define PORT_PCI_CMD 0x0cf8 #define PORT_PCI_DATA 0x0cfc
+static u32 mmconfig; + +static void *pci_mmconfig_addr(u16 bdf, u32 addr) +{ + if (!mmconfig) + return NULL; + return (void*)(mmconfig + ((u32)bdf << 12) + addr); +} + void pci_config_writel(u16 bdf, u32 addr, u32 val) { - outl(0x80000000 | (bdf << 8) | (addr & 0xfc), PORT_PCI_CMD); - outl(val, PORT_PCI_DATA); + void *mmcfg = pci_mmconfig_addr(bdf, addr); + if (mmcfg) { + writel(mmcfg, val); + } else { + outl(0x80000000 | (bdf << 8) | (addr & 0xfc), PORT_PCI_CMD); + outl(val, PORT_PCI_DATA); + } }
void pci_config_writew(u16 bdf, u32 addr, u16 val) { - outl(0x80000000 | (bdf << 8) | (addr & 0xfc), PORT_PCI_CMD); - outw(val, PORT_PCI_DATA + (addr & 2)); + void *mmcfg = pci_mmconfig_addr(bdf, addr); + if (mmcfg) { + writew(mmcfg, val); + } else { + outl(0x80000000 | (bdf << 8) | (addr & 0xfc), PORT_PCI_CMD); + outw(val, PORT_PCI_DATA + (addr & 2)); + } }
void pci_config_writeb(u16 bdf, u32 addr, u8 val) { - outl(0x80000000 | (bdf << 8) | (addr & 0xfc), PORT_PCI_CMD); - outb(val, PORT_PCI_DATA + (addr & 3)); + void *mmcfg = pci_mmconfig_addr(bdf, addr); + if (mmcfg) { + writeb(mmcfg, val); + } else { + outl(0x80000000 | (bdf << 8) | (addr & 0xfc), PORT_PCI_CMD); + outb(val, PORT_PCI_DATA + (addr & 3)); + } }
u32 pci_config_readl(u16 bdf, u32 addr) { + void *mmcfg = pci_mmconfig_addr(bdf, addr); + if (mmcfg) + return readl(mmcfg); outl(0x80000000 | (bdf << 8) | (addr & 0xfc), PORT_PCI_CMD); return inl(PORT_PCI_DATA); }
u16 pci_config_readw(u16 bdf, u32 addr) { + void *mmcfg = pci_mmconfig_addr(bdf, addr); + if (mmcfg) + return readw(mmcfg); outl(0x80000000 | (bdf << 8) | (addr & 0xfc), PORT_PCI_CMD); return inw(PORT_PCI_DATA + (addr & 2)); }
u8 pci_config_readb(u16 bdf, u32 addr) { + void *mmcfg = pci_mmconfig_addr(bdf, addr); + if (mmcfg) + return readb(mmcfg); outl(0x80000000 | (bdf << 8) | (addr & 0xfc), PORT_PCI_CMD); return inb(PORT_PCI_DATA + (addr & 3)); } @@ -58,6 +91,15 @@ pci_config_maskw(u16 bdf, u32 addr, u16 off, u16 on) pci_config_writew(bdf, addr, val); }
+void +pci_enable_mmconfig(u64 addr, const char *name) +{ + if (addr >= 0x100000000ll) + return; + dprintf(1, "PCIe: using %s mmconfig at 0x%llx\n", name, addr); + mmconfig = addr; +} + u8 pci_find_capability(u16 bdf, u8 cap_id, u8 cap) { int i;
On Thu, Mar 19, 2020 at 10:21:55AM +0100, Gerd Hoffmann wrote:
Add support for pci config space access via mmconfig bar. Enable for qemu q35 chipset. Main advantage is that we need only one instead of two io operations per config space access, which translates to one instead of two vmexits for virtualization.
Signed-off-by: Gerd Hoffmann kraxel@redhat.com
src/hw/pci.h | 1 + src/fw/pciinit.c | 1 + src/hw/pci.c | 54 ++++++++++++++++++++++++++++++++++++++++++------ 3 files changed, 50 insertions(+), 6 deletions(-)
Great!
Reviewed-by: Stefan Hajnoczi stefanha@redhat.com
On Thu, Mar 19, 2020 at 10:21:55AM +0100, Gerd Hoffmann wrote:
Add support for pci config space access via mmconfig bar. Enable for qemu q35 chipset. Main advantage is that we need only one instead of two io operations per config space access, which translates to one instead of two vmexits for virtualization.
Signed-off-by: Gerd Hoffmann kraxel@redhat.com
src/hw/pci.h | 1 + src/fw/pciinit.c | 1 + src/hw/pci.c | 54 ++++++++++++++++++++++++++++++++++++++++++------ 3 files changed, 50 insertions(+), 6 deletions(-)
diff --git a/src/hw/pci.h b/src/hw/pci.h index 2e30e28918a0..01c51f705a00 100644 --- a/src/hw/pci.h +++ b/src/hw/pci.h @@ -39,6 +39,7 @@ u32 pci_config_readl(u16 bdf, u32 addr); u16 pci_config_readw(u16 bdf, u32 addr); u8 pci_config_readb(u16 bdf, u32 addr); void pci_config_maskw(u16 bdf, u32 addr, u16 off, u16 on); +void pci_enable_mmconfig(u64 addr, const char *name); u8 pci_find_capability(u16 bdf, u8 cap_id, u8 cap); int pci_next(int bdf, int bus); int pci_probe_host(void); diff --git a/src/fw/pciinit.c b/src/fw/pciinit.c index d5e87f00f164..d25931bb0573 100644 --- a/src/fw/pciinit.c +++ b/src/fw/pciinit.c @@ -480,6 +480,7 @@ static void mch_mmconfig_setup(u16 bdf) pci_config_writel(bdf, Q35_HOST_BRIDGE_PCIEXBAR, 0); pci_config_writel(bdf, Q35_HOST_BRIDGE_PCIEXBAR + 4, upper); pci_config_writel(bdf, Q35_HOST_BRIDGE_PCIEXBAR, lower);
- pci_enable_mmconfig(Q35_HOST_BRIDGE_PCIEXBAR_ADDR, "q35");
}
static void mch_mem_addr_setup(struct pci_device *dev, void *arg) diff --git a/src/hw/pci.c b/src/hw/pci.c index 9855badbc7da..3b8f43cbe5dc 100644 --- a/src/hw/pci.c +++ b/src/hw/pci.c @@ -14,38 +14,71 @@ #define PORT_PCI_CMD 0x0cf8 #define PORT_PCI_DATA 0x0cfc
+static u32 mmconfig;
+static void *pci_mmconfig_addr(u16 bdf, u32 addr) +{
- if (!mmconfig)
return NULL;
- return (void*)(mmconfig + ((u32)bdf << 12) + addr);
+}
void pci_config_writel(u16 bdf, u32 addr, u32 val) {
- outl(0x80000000 | (bdf << 8) | (addr & 0xfc), PORT_PCI_CMD);
- outl(val, PORT_PCI_DATA);
- void *mmcfg = pci_mmconfig_addr(bdf, addr);
- if (mmcfg) {
writel(mmcfg, val);
- } else {
outl(0x80000000 | (bdf << 8) | (addr & 0xfc), PORT_PCI_CMD);
outl(val, PORT_PCI_DATA);
- }
}
The pci_config_writeX() functions are called from 16-bit mode and 32-big segment mode, so this doesn't look correct to me.
What device would call the pci_config_writeX() functions frequently enough that performance would matter?
Thanks, -Kevin
void pci_config_writew(u16 bdf, u32 addr, u16 val) {
- outl(0x80000000 | (bdf << 8) | (addr & 0xfc), PORT_PCI_CMD);
- outw(val, PORT_PCI_DATA + (addr & 2));
- void *mmcfg = pci_mmconfig_addr(bdf, addr);
- if (mmcfg) {
writew(mmcfg, val);
- } else {
outl(0x80000000 | (bdf << 8) | (addr & 0xfc), PORT_PCI_CMD);
outw(val, PORT_PCI_DATA + (addr & 2));
- }
}
void pci_config_writeb(u16 bdf, u32 addr, u8 val) {
- outl(0x80000000 | (bdf << 8) | (addr & 0xfc), PORT_PCI_CMD);
- outb(val, PORT_PCI_DATA + (addr & 3));
- void *mmcfg = pci_mmconfig_addr(bdf, addr);
- if (mmcfg) {
writeb(mmcfg, val);
- } else {
outl(0x80000000 | (bdf << 8) | (addr & 0xfc), PORT_PCI_CMD);
outb(val, PORT_PCI_DATA + (addr & 3));
- }
}
u32 pci_config_readl(u16 bdf, u32 addr) {
- void *mmcfg = pci_mmconfig_addr(bdf, addr);
- if (mmcfg)
outl(0x80000000 | (bdf << 8) | (addr & 0xfc), PORT_PCI_CMD); return inl(PORT_PCI_DATA);return readl(mmcfg);
}
u16 pci_config_readw(u16 bdf, u32 addr) {
- void *mmcfg = pci_mmconfig_addr(bdf, addr);
- if (mmcfg)
outl(0x80000000 | (bdf << 8) | (addr & 0xfc), PORT_PCI_CMD); return inw(PORT_PCI_DATA + (addr & 2));return readw(mmcfg);
}
u8 pci_config_readb(u16 bdf, u32 addr) {
- void *mmcfg = pci_mmconfig_addr(bdf, addr);
- if (mmcfg)
outl(0x80000000 | (bdf << 8) | (addr & 0xfc), PORT_PCI_CMD); return inb(PORT_PCI_DATA + (addr & 3));return readb(mmcfg);
} @@ -58,6 +91,15 @@ pci_config_maskw(u16 bdf, u32 addr, u16 off, u16 on) pci_config_writew(bdf, addr, val); }
+void +pci_enable_mmconfig(u64 addr, const char *name) +{
- if (addr >= 0x100000000ll)
return;
- dprintf(1, "PCIe: using %s mmconfig at 0x%llx\n", name, addr);
- mmconfig = addr;
+}
u8 pci_find_capability(u16 bdf, u8 cap_id, u8 cap) { int i; -- 2.18.2 _______________________________________________ SeaBIOS mailing list -- seabios@seabios.org To unsubscribe send an email to seabios-leave@seabios.org
+static void *pci_mmconfig_addr(u16 bdf, u32 addr) +{
- if (!mmconfig)
return NULL;
- return (void*)(mmconfig + ((u32)bdf << 12) + addr);
+}
void pci_config_writel(u16 bdf, u32 addr, u32 val) {
- outl(0x80000000 | (bdf << 8) | (addr & 0xfc), PORT_PCI_CMD);
- outl(val, PORT_PCI_DATA);
- void *mmcfg = pci_mmconfig_addr(bdf, addr);
- if (mmcfg) {
writel(mmcfg, val);
- } else {
outl(0x80000000 | (bdf << 8) | (addr & 0xfc), PORT_PCI_CMD);
outl(val, PORT_PCI_DATA);
- }
}
The pci_config_writeX() functions are called from 16-bit mode and 32-big segment mode, so this doesn't look correct to me.
Oh. Wasn't aware of that.
What device would call the pci_config_writeX() functions frequently enough that performance would matter?
I expect most vmexists come from PCI bar configuration in POST which runs in 32bit mode. I think we can restrict this to 32bit mode without loosing much.
cheers, Gerd
From efd637133232fc8beec3a2f8bb4af505192758d1 Mon Sep 17 00:00:00 2001 From: Gerd Hoffmann kraxel@redhat.com Date: Fri, 20 Mar 2020 06:45:10 +0100 Subject: [PATCH] no mmconfig in 16bit mode
--- src/hw/pci.c | 2 ++ 1 file changed, 2 insertions(+)
diff --git a/src/hw/pci.c b/src/hw/pci.c index 3b8f43cbe5dc..9ae57ff28707 100644 --- a/src/hw/pci.c +++ b/src/hw/pci.c @@ -18,6 +18,8 @@ static u32 mmconfig;
static void *pci_mmconfig_addr(u16 bdf, u32 addr) { + if (MODESEGMENT) + return NULL; if (!mmconfig) return NULL; return (void*)(mmconfig + ((u32)bdf << 12) + addr);
On Fri, Mar 20, 2020 at 06:48:53AM +0100, Gerd Hoffmann wrote:
+static void *pci_mmconfig_addr(u16 bdf, u32 addr) +{
- if (!mmconfig)
return NULL;
- return (void*)(mmconfig + ((u32)bdf << 12) + addr);
+}
void pci_config_writel(u16 bdf, u32 addr, u32 val) {
- outl(0x80000000 | (bdf << 8) | (addr & 0xfc), PORT_PCI_CMD);
- outl(val, PORT_PCI_DATA);
- void *mmcfg = pci_mmconfig_addr(bdf, addr);
- if (mmcfg) {
writel(mmcfg, val);
- } else {
outl(0x80000000 | (bdf << 8) | (addr & 0xfc), PORT_PCI_CMD);
outl(val, PORT_PCI_DATA);
- }
}
The pci_config_writeX() functions are called from 16-bit mode and 32-big segment mode, so this doesn't look correct to me.
Oh. Wasn't aware of that.
What device would call the pci_config_writeX() functions frequently enough that performance would matter?
I expect most vmexists come from PCI bar configuration in POST which runs in 32bit mode. I think we can restrict this to 32bit mode without loosing much.
What is the time improvement for using mmconfig?
--- a/src/hw/pci.c +++ b/src/hw/pci.c @@ -18,6 +18,8 @@ static u32 mmconfig;
static void *pci_mmconfig_addr(u16 bdf, u32 addr) {
- if (MODESEGMENT)
if (!mmconfig) return NULL; return (void*)(mmconfig + ((u32)bdf << 12) + addr);return NULL;
FWIW, given the low-level nature of these functions, it may be worth using wrappers like the following:
void pci_config_writel(u16 bdf, u32 addr, u32 val) { if (!MODESEGMENT && mmconfig) pci_mmconfig_writel(bdf, addr, val); else pci_ioconfig_writel(bdf, addr, val); }
-Kevin
FWIW, given the low-level nature of these functions, it may be worth using wrappers like the following:
void pci_config_writel(u16 bdf, u32 addr, u32 val) { if (!MODESEGMENT && mmconfig) pci_mmconfig_writel(bdf, addr, val); else pci_ioconfig_writel(bdf, addr, val); }
New pci_mmconfig_* + pci_ioconfig_* functions don't look useful to me, given that they end up being one-liners. Making the !MODESEGMENT && mmconfig condition more visible looks useful to me though, I'll try than in v2 ...
cheers, Gerd