pci: add support for MMIO config space

MMIO config space has two benefits: it does not require the global PCI
lock, and it easily works with extended PCI config space (i.e. above 255).

For whatever reason, I had an old note that said you could use PIO for
the extended config space.  I probably got that from looking at Linux or
something.  It might have worked on some older machines for me; I don't
recall.  But it certainly does not work with all machines.  Maybe it was
an AMD/Intel thing.

I left support for PIO in case we run into a weird machine that doesn't
have the ACPI MCFG table or for debugging.  Though even my QEMU has an
MCFG.  We can remove it if it is a pain - maybe when we make PCI more
architecture-independent.  Right now it is x86-specific, both in PIO and
MMIO ops.

Signed-off-by: Barret Rhoden <brho@cs.berkeley.edu>
diff --git a/kern/arch/x86/pci.c b/kern/arch/x86/pci.c
index 334def4..ed90424 100644
--- a/kern/arch/x86/pci.c
+++ b/kern/arch/x86/pci.c
@@ -14,6 +14,7 @@
 #include <mm.h>
 #include <arch/pci_defs.h>
 #include <ros/errno.h>
+#include <acpi.h>
 
 /* List of all discovered devices */
 struct pcidev_stailq pci_devices = STAILQ_HEAD_INITIALIZER(pci_devices);
@@ -197,6 +198,18 @@
 	}
 }
 
+static uintptr_t pci_get_mmio_cfg(struct pci_device *pcidev)
+{
+	physaddr_t paddr;
+
+	paddr = acpi_pci_get_mmio_cfg_addr(0 /* segment for legacy PCI enum*/,
+					  pcidev->bus, pcidev->dev,
+					  pcidev->func);
+	if (!paddr)
+		return 0;
+	return vmap_pmem_nocache(paddr, 4096);
+}
+
 /* Scans the PCI bus.  Won't actually work for anything other than bus 0, til we
  * sort out how to handle bridge devices. */
 void pci_init(void)
@@ -238,6 +251,8 @@
 					 pcidev->dev, pcidev->func);
 				pcidev->dev_id = dev_id;
 				pcidev->ven_id = ven_id;
+				/* Set up the MMIO CFG before using accessors */
+				pcidev->mmio_cfg = pci_get_mmio_cfg(pcidev);
 				/* Get the Class/subclass */
 				pcidev->class =
 					pcidev_read8(pcidev, PCI_CLASS_REG);
@@ -375,40 +390,101 @@
 	spin_unlock_irqsave(&pci_lock);
 }
 
+/* Some AMD processors require using eax for MMIO config ops. */
+static uint32_t pci_cfg_mmio_read32(uintptr_t mmio_cfg, uint32_t offset)
+{
+	uint32_t val;
+
+	asm volatile("movl (%1),%0" : "=a"(val) : "g"(mmio_cfg + offset));
+	return val;
+}
+
+static void pci_cfg_mmio_write32(uintptr_t mmio_cfg, uint32_t offset,
+				 uint32_t val)
+{
+	asm volatile("movl %0,(%1)" : : "a"(val), "g"(mmio_cfg + offset));
+}
+
+static uint16_t pci_cfg_mmio_read16(uintptr_t mmio_cfg, uint32_t offset)
+{
+	uint16_t val;
+
+	asm volatile("movw (%1),%0" : "=a"(val) : "g"(mmio_cfg + offset));
+	return val;
+}
+
+static void pci_cfg_mmio_write16(uintptr_t mmio_cfg, uint32_t offset,
+				 uint16_t val)
+{
+	asm volatile("movw %0,(%1)" : : "a"(val), "g"(mmio_cfg + offset));
+}
+
+static uint8_t pci_cfg_mmio_read8(uintptr_t mmio_cfg, uint32_t offset)
+{
+	uint8_t val;
+
+	asm volatile("movb (%1),%0" : "=a"(val) : "g"(mmio_cfg + offset));
+	return val;
+}
+
+static void pci_cfg_mmio_write8(uintptr_t mmio_cfg, uint32_t offset,
+				uint8_t val)
+{
+	asm volatile("movb %0,(%1)" : : "a"(val), "g"(mmio_cfg + offset));
+}
+
 uint32_t pcidev_read32(struct pci_device *pcidev, uint32_t offset)
 {
-	return pci_cfg_pio_read32(pcidev->bus, pcidev->dev, pcidev->func,
-				  offset);
+	if (pcidev->mmio_cfg)
+		return pci_cfg_mmio_read32(pcidev->mmio_cfg, offset);
+	else
+		return pci_cfg_pio_read32(pcidev->bus, pcidev->dev,
+					  pcidev->func, offset);
 }
 
 void pcidev_write32(struct pci_device *pcidev, uint32_t offset, uint32_t value)
 {
-	pci_cfg_pio_write32(pcidev->bus, pcidev->dev, pcidev->func, offset,
-			    value);
+	if (pcidev->mmio_cfg)
+		pci_cfg_mmio_write32(pcidev->mmio_cfg, offset, value);
+	else
+		pci_cfg_pio_write32(pcidev->bus, pcidev->dev, pcidev->func,
+				    offset, value);
 }
 
 uint16_t pcidev_read16(struct pci_device *pcidev, uint32_t offset)
 {
-	return pci_cfg_pio_read16(pcidev->bus, pcidev->dev, pcidev->func,
-				  offset);
+	if (pcidev->mmio_cfg)
+		return pci_cfg_mmio_read16(pcidev->mmio_cfg, offset);
+	else
+		return pci_cfg_pio_read16(pcidev->bus, pcidev->dev,
+					  pcidev->func, offset);
 }
 
 void pcidev_write16(struct pci_device *pcidev, uint32_t offset, uint16_t value)
 {
-	pci_cfg_pio_write16(pcidev->bus, pcidev->dev, pcidev->func, offset,
-			    value);
+	if (pcidev->mmio_cfg)
+		pci_cfg_mmio_write16(pcidev->mmio_cfg, offset, value);
+	else
+		pci_cfg_pio_write16(pcidev->bus, pcidev->dev, pcidev->func,
+				    offset, value);
 }
 
 uint8_t pcidev_read8(struct pci_device *pcidev, uint32_t offset)
 {
-	return pci_cfg_pio_read8(pcidev->bus, pcidev->dev, pcidev->func,
-				 offset);
+	if (pcidev->mmio_cfg)
+		return pci_cfg_mmio_read8(pcidev->mmio_cfg, offset);
+	else
+		return pci_cfg_pio_read8(pcidev->bus, pcidev->dev, pcidev->func,
+					 offset);
 }
 
 void pcidev_write8(struct pci_device *pcidev, uint32_t offset, uint8_t value)
 {
-	pci_cfg_pio_write8(pcidev->bus, pcidev->dev, pcidev->func, offset,
-			   value);
+	if (pcidev->mmio_cfg)
+		pci_cfg_mmio_write8(pcidev->mmio_cfg, offset, value);
+	else
+		pci_cfg_pio_write8(pcidev->bus, pcidev->dev, pcidev->func,
+				   offset, value);
 }
 
 /* Helper to get the class description strings.  Adapted from
diff --git a/kern/arch/x86/pci.h b/kern/arch/x86/pci.h
index 89c355a..55bfb93 100644
--- a/kern/arch/x86/pci.h
+++ b/kern/arch/x86/pci.h
@@ -171,6 +171,7 @@
 	SLIST_ENTRY(pci_device)		irq_dev; /* list of all devs on irq */
 	char				name[9];
 	spinlock_t			lock;
+	uintptr_t			mmio_cfg;
 	void				*dev_data; /* device private pointer */
 	struct device			device;
 	bool				in_use;	/* prevent double discovery */