pci: Add helper functions for internal driver BAR handling

Add functions to verify and obtain PCI BARs (Base Address Registers).
These new functions check that the requested BAR is of the right type
and appears valid.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
diff --git a/src/hw/pci.c b/src/hw/pci.c
index a241d06..86b7d54 100644
--- a/src/hw/pci.c
+++ b/src/hw/pci.c
@@ -10,6 +10,7 @@
 #include "pci.h" // pci_config_writel
 #include "pci_regs.h" // PCI_VENDOR_ID
 #include "romfile.h" // romfile_loadint
+#include "stacks.h" // wait_preempt
 #include "string.h" // memset
 #include "util.h" // udelay
 #include "x86.h" // outl
@@ -271,6 +272,63 @@
     return pci_config_readb(pci->bdf, base) != 0;
 }
 
+// Enable PCI bus-mastering (ie, DMA) support on a pci device
+void
+pci_enable_busmaster(struct pci_device *pci)
+{
+    ASSERT32FLAT();
+    wait_preempt();
+    pci_config_maskw(pci->bdf, PCI_COMMAND, 0, PCI_COMMAND_MASTER);
+}
+
+// Verify an IO bar and return it to the caller
+u16
+pci_enable_iobar(struct pci_device *pci, u32 addr)
+{
+    ASSERT32FLAT();
+    wait_preempt();
+    u32 bar = pci_config_readl(pci->bdf, addr);
+    if (!(bar & PCI_BASE_ADDRESS_SPACE_IO)) {
+        warn_internalerror();
+        return 0;
+    }
+    bar &= PCI_BASE_ADDRESS_IO_MASK;
+    if (bar == 0 || bar > 0xffff) {
+        warn_internalerror();
+        return 0;
+    }
+    pci_config_maskw(pci->bdf, PCI_COMMAND, 0, PCI_COMMAND_IO);
+    return bar;
+}
+
+// Verify a memory bar and return it to the caller
+void *
+pci_enable_membar(struct pci_device *pci, u32 addr)
+{
+    ASSERT32FLAT();
+    wait_preempt();
+    u32 bar = pci_config_readl(pci->bdf, addr);
+    if (bar & PCI_BASE_ADDRESS_SPACE_IO) {
+        warn_internalerror();
+        return NULL;
+    }
+    if (bar & PCI_BASE_ADDRESS_MEM_TYPE_64) {
+        u32 high = pci_config_readl(pci->bdf, addr+4);
+        if (high) {
+            dprintf(1, "Can not map memory bar over 4Gig\n");
+            return NULL;
+        }
+    }
+    bar &= PCI_BASE_ADDRESS_MEM_MASK;
+    if (bar + 4*1024*1024 < 20*1024*1024) {
+        // Bar doesn't look valid (it is in last 4M or first 16M)
+        warn_internalerror();
+        return NULL;
+    }
+    pci_config_maskw(pci->bdf, PCI_COMMAND, 0, PCI_COMMAND_MEMORY);
+    return (void*)bar;
+}
+
 void
 pci_reboot(void)
 {
diff --git a/src/hw/pci.h b/src/hw/pci.h
index fc5e7b9..8e39753 100644
--- a/src/hw/pci.h
+++ b/src/hw/pci.h
@@ -126,6 +126,9 @@
 u8 pci_find_capability(struct pci_device *pci, u8 cap_id, u8 cap);
 int pci_bridge_has_region(struct pci_device *pci,
                           enum pci_region_type region_type);
+void pci_enable_busmaster(struct pci_device *pci);
+u16 pci_enable_iobar(struct pci_device *pci, u32 addr);
+void *pci_enable_membar(struct pci_device *pci, u32 addr);
 void pci_reboot(void);
 
 #endif