Added whitelisting to MSR read/write code
[akaros.git] / kern / arch / x86 / pci.c
index 2cda26b..c9caebe 100644 (file)
 #include <string.h>
 #include <assert.h>
 #include <kmalloc.h>
+#include <mm.h>
 #include <arch/pci_defs.h>
+#include <ros/errno.h>
 
 /* List of all discovered devices */
 struct pcidev_stailq pci_devices = STAILQ_HEAD_INITIALIZER(pci_devices);
 
+/* PCI accesses are two-stage PIO, which need to complete atomically */
+spinlock_t pci_lock = SPINLOCK_INITIALIZER_IRQSAVE;
+
 static char STD_PCI_DEV[] = "Standard PCI Device";
 static char PCI2PCI[] = "PCI-to-PCI Bridge";
 static char PCI2CARDBUS[] = "PCI-Cardbus Bridge";
 
+/* Gets any old raw bar, with some catches based on type. */
+static uint32_t pci_getbar(struct pci_device *pcidev, unsigned int bar)
+{
+       uint8_t type;
+       if (bar >= MAX_PCI_BAR)
+               panic("Nonexistant bar requested!");
+       type = pcidev_read8(pcidev, PCI_HEADER_REG);
+       type &= ~0x80;  /* drop the MF bit */
+       /* Only types 0 and 1 have BARS */
+       if ((type != 0x00) && (type != 0x01))
+               return 0;
+       /* Only type 0 has BAR2 - BAR5 */
+       if ((bar > 1) && (type != 0x00))
+               return 0;
+       return pcidev_read32(pcidev, PCI_BAR0_STD + bar * PCI_BAR_OFF);
+}
+
+/* Determines if a given bar is IO (o/w, it's mem) */
+static bool pci_is_iobar(uint32_t bar)
+{
+       return bar & PCI_BAR_IO;
+}
+
+static bool pci_is_membar32(uint32_t bar)
+{
+       if (pci_is_iobar(bar))
+               return FALSE;
+       return (bar & PCI_MEMBAR_TYPE) == PCI_MEMBAR_32BIT;
+}
+
+static bool pci_is_membar64(uint32_t bar)
+{
+       if (pci_is_iobar(bar))
+               return FALSE;
+       return (bar & PCI_MEMBAR_TYPE) == PCI_MEMBAR_64BIT;
+}
+
+/* Helper to get the address from a membar.  Check the type beforehand */
+static uint32_t pci_getmembar32(uint32_t bar)
+{
+       uint8_t type = bar & PCI_MEMBAR_TYPE;
+       if (type != PCI_MEMBAR_32BIT) {
+               warn("Unhandled PCI membar type: %02p\n", type >> 1);
+               return 0;
+       }
+       return bar & 0xfffffff0;
+}
+
+/* Helper to get the address from an IObar.  Check the type beforehand */
+static uint32_t pci_getiobar32(uint32_t bar)
+{
+       return bar & 0xfffffffc;
+}
+
 /* memory bars have a little dance you go through to detect what the size of the
  * memory region is.  for 64 bit bars, i'm assuming you only need to do this to
- * the lower part (no device will need > 4GB, right?). */
-uint32_t pci_membar_get_sz(struct pci_device *pcidev, int bar)
+ * the lower part (no device will need > 4GB, right?).
+ *
+ * Hold the dev's lock, or o/w avoid sync issues. */
+static uint32_t __pci_membar_get_sz(struct pci_device *pcidev, int bar)
 {
        /* save the old value, write all 1s, invert, add 1, restore.
         * http://wiki.osdev.org/PCI for details. */
@@ -42,11 +103,16 @@ uint32_t pci_membar_get_sz(struct pci_device *pcidev, int bar)
  * where the base is.  fills results into pcidev.  i don't know if you can have
  * multiple bars with conflicting/different regions (like two separate PIO
  * ranges).  I'm assuming you don't, and will warn if we see one. */
-static void pci_handle_bars(struct pci_device *pcidev)
+static void __pci_handle_bars(struct pci_device *pcidev)
 {
-       /* only handling standards for now */
        uint32_t bar_val;
-       int max_bars = pcidev->header_type == STD_PCI_DEV ? MAX_PCI_BAR : 0;
+       int max_bars;
+       if (pcidev->header_type == STD_PCI_DEV)
+               max_bars = MAX_PCI_BAR;
+       else if (pcidev->header_type == PCI2PCI)
+               max_bars = 2;
+       else
+               max_bars = 0;
        /* TODO: consider aborting for classes 00, 05 (memory ctlr), 06 (bridge) */
        for (int i = 0; i < max_bars; i++) {
                bar_val = pci_getbar(pcidev, i);
@@ -58,7 +124,7 @@ static void pci_handle_bars(struct pci_device *pcidev)
                } else {
                        if (pci_is_membar32(bar_val)) {
                                pcidev->bar[i].mmio_base32 = bar_val & PCI_BAR_MEM_MASK;
-                               pcidev->bar[i].mmio_sz = pci_membar_get_sz(pcidev, i);
+                               pcidev->bar[i].mmio_sz = __pci_membar_get_sz(pcidev, i);
                        } else if (pci_is_membar64(bar_val)) {
                                /* 64 bit, the lower 32 are in this bar, the upper
                                 * are in the next bar */
@@ -68,7 +134,7 @@ static void pci_handle_bars(struct pci_device *pcidev)
                                /* note we don't check for IO or memsize.  the entire next bar
                                 * is supposed to be for the upper 32 bits. */
                                pcidev->bar[i].mmio_base64 |= (uint64_t)bar_val << 32;
-                               pcidev->bar[i].mmio_sz = pci_membar_get_sz(pcidev, i);
+                               pcidev->bar[i].mmio_sz = __pci_membar_get_sz(pcidev, i);
                                i++;
                        }
                }
@@ -78,9 +144,46 @@ static void pci_handle_bars(struct pci_device *pcidev)
        }
 }
 
+static void __pci_parse_caps(struct pci_device *pcidev)
+{
+       uint32_t cap_off;       /* not sure if this can be extended from u8 */
+       uint8_t cap_id;
+       if (!(pcidev_read16(pcidev, PCI_STATUS_REG) & (1 << 4)))
+               return;
+       switch (pcidev_read8(pcidev, PCI_HEADER_REG) & 0x7f) {
+               case 0:                         /* etc */
+               case 1:                         /* pci to pci bridge */
+                       cap_off = 0x34;
+                       break;
+               case 2:                         /* cardbus bridge */
+                       cap_off = 0x14;
+                       break;
+               default:
+                       return;
+       }
+       /* initial offset points to the addr of the first cap */
+       cap_off = pcidev_read8(pcidev, cap_off);
+       cap_off &= ~0x3;        /* osdev says the lower 2 bits are reserved */
+       while (cap_off) {
+               cap_id = pcidev_read8(pcidev, cap_off);
+               if (cap_id > PCI_CAP_ID_MAX) {
+                       printk("PCI %x:%x:%x had bad cap 0x%x\n", pcidev->bus, pcidev->dev,
+                              pcidev->func, cap_id);
+                       return;
+               }
+               pcidev->caps[cap_id] = cap_off;
+               cap_off = pcidev_read8(pcidev, cap_off + 1);
+               /* not sure if subsequent caps must be aligned or not */
+               if (cap_off & 0x3)
+                       printk("PCI %x:%x:%x had unaligned cap offset 0x%x\n", pcidev->bus,
+                              pcidev->dev, pcidev->func, cap_off);
+       }
+}
+
 /* 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) {
+void pci_init(void)
+{
        uint32_t result = 0;
        uint16_t dev_id, ven_id;
        struct pci_device *pcidev;
@@ -96,9 +199,14 @@ void pci_init(void) {
                                if (ven_id == INVALID_VENDOR_ID) 
                                        break;  /* skip functions too, they won't exist */
                                pcidev = kzmalloc(sizeof(struct pci_device), 0);
+                               /* we don't need to lock it til we post the pcidev to the list*/
+                               spinlock_init_irqsave(&pcidev->lock);
                                pcidev->bus = i;
                                pcidev->dev = j;
                                pcidev->func = k;
+                               snprintf(pcidev->name, sizeof(pcidev->name),
+                                        "%02x:%02x.%x", pcidev->bus,
+                                        pcidev->dev, pcidev->func);
                                pcidev->dev_id = dev_id;
                                pcidev->ven_id = ven_id;
                                /* Get the Class/subclass */
@@ -124,7 +232,9 @@ void pci_init(void) {
                                        default:
                                                pcidev->header_type = "Unknown Header Type";
                                }
-                               pci_handle_bars(pcidev);
+                               __pci_handle_bars(pcidev);
+                               __pci_parse_caps(pcidev);
+                               /* we're the only writer at this point in the boot process */
                                STAILQ_INSERT_TAIL(&pci_devices, pcidev, all_dev);
                                #ifdef CONFIG_PCI_VERBOSE
                                pcidev_print_info(pcidev, 4);
@@ -158,8 +268,12 @@ uint32_t pci_config_addr(uint8_t bus, uint8_t dev, uint8_t func, uint32_t reg)
  * into the config space we offset before reading, aka: where we are reading. */
 uint32_t pci_read32(uint8_t bus, uint8_t dev, uint8_t func, uint32_t offset)
 {
+       uint32_t ret;
+       spin_lock_irqsave(&pci_lock);
        outl(PCI_CONFIG_ADDR, pci_config_addr(bus, dev, func, offset));
-       return inl(PCI_CONFIG_DATA);
+       ret = inl(PCI_CONFIG_DATA);
+       spin_unlock_irqsave(&pci_lock);
+       return ret;
 }
 
 /* Same, but writes (doing 32bit at a time).  Never actually tested (not sure if
@@ -167,34 +281,48 @@ uint32_t pci_read32(uint8_t bus, uint8_t dev, uint8_t func, uint32_t offset)
 void pci_write32(uint8_t bus, uint8_t dev, uint8_t func, uint32_t offset,
                  uint32_t value)
 {
+       spin_lock_irqsave(&pci_lock);
        outl(PCI_CONFIG_ADDR, pci_config_addr(bus, dev, func, offset));
        outl(PCI_CONFIG_DATA, value);
+       spin_unlock_irqsave(&pci_lock);
 }
 
-uint32_t pci_read16(uint8_t bus, uint8_t dev, uint8_t func, uint32_t offset)
+uint16_t pci_read16(uint8_t bus, uint8_t dev, uint8_t func, uint32_t offset)
 {
+       uint16_t ret;
+       spin_lock_irqsave(&pci_lock);
        outl(PCI_CONFIG_ADDR, pci_config_addr(bus, dev, func, offset));
-       return inw(PCI_CONFIG_DATA + (offset & 2));
+       ret = inw(PCI_CONFIG_DATA + (offset & 2));
+       spin_unlock_irqsave(&pci_lock);
+       return ret;
 }
 
 void pci_write16(uint8_t bus, uint8_t dev, uint8_t func, uint32_t offset,
-                 uint32_t value)
+                 uint16_t value)
 {
+       spin_lock_irqsave(&pci_lock);
        outl(PCI_CONFIG_ADDR, pci_config_addr(bus, dev, func, offset));
        outw(PCI_CONFIG_DATA + (offset & 2), value);
+       spin_unlock_irqsave(&pci_lock);
 }
 
-uint32_t pci_read8(uint8_t bus, uint8_t dev, uint8_t func, uint32_t offset)
+uint8_t pci_read8(uint8_t bus, uint8_t dev, uint8_t func, uint32_t offset)
 {
+       uint8_t ret;
+       spin_lock_irqsave(&pci_lock);
        outl(PCI_CONFIG_ADDR, pci_config_addr(bus, dev, func, offset));
-       return inb(PCI_CONFIG_DATA + (offset & 3));
+       ret = inb(PCI_CONFIG_DATA + (offset & 3));
+       spin_unlock_irqsave(&pci_lock);
+       return ret;
 }
 
 void pci_write8(uint8_t bus, uint8_t dev, uint8_t func, uint32_t offset,
-                uint32_t value)
+                uint8_t value)
 {
+       spin_lock_irqsave(&pci_lock);
        outl(PCI_CONFIG_ADDR, pci_config_addr(bus, dev, func, offset));
        outb(PCI_CONFIG_DATA + (offset & 3), value);
+       spin_unlock_irqsave(&pci_lock);
 }
 
 uint32_t pcidev_read32(struct pci_device *pcidev, uint32_t offset)
@@ -227,60 +355,6 @@ void pcidev_write8(struct pci_device *pcidev, uint32_t offset, uint8_t value)
        pci_write8(pcidev->bus, pcidev->dev, pcidev->func, offset, value);
 }
 
-/* Gets any old raw bar, with some catches based on type. */
-uint32_t pci_getbar(struct pci_device *pcidev, unsigned int bar)
-{
-       uint8_t type;
-       if (bar >= MAX_PCI_BAR)
-               panic("Nonexistant bar requested!");
-       type = pcidev_read8(pcidev, PCI_HEADER_REG);
-       type &= ~0x80;  /* drop the MF bit */
-       /* Only types 0 and 1 have BARS */
-       if ((type != 0x00) && (type != 0x01))
-               return 0;
-       /* Only type 0 has BAR2 - BAR5 */
-       if ((bar > 1) && (type != 0x00))
-               return 0;
-       return pcidev_read32(pcidev, PCI_BAR0_STD + bar * PCI_BAR_OFF);
-}
-
-/* Determines if a given bar is IO (o/w, it's mem) */
-bool pci_is_iobar(uint32_t bar)
-{
-       return bar & PCI_BAR_IO;
-}
-
-bool pci_is_membar32(uint32_t bar)
-{
-       if (pci_is_iobar(bar))
-               return FALSE;
-       return (bar & PCI_MEMBAR_TYPE) == PCI_MEMBAR_32BIT;
-}
-
-bool pci_is_membar64(uint32_t bar)
-{
-       if (pci_is_iobar(bar))
-               return FALSE;
-       return (bar & PCI_MEMBAR_TYPE) == PCI_MEMBAR_64BIT;
-}
-
-/* Helper to get the address from a membar.  Check the type beforehand */
-uint32_t pci_getmembar32(uint32_t bar)
-{
-       uint8_t type = bar & PCI_MEMBAR_TYPE;
-       if (type != PCI_MEMBAR_32BIT) {
-               warn("Unhandled PCI membar type: %02p\n", type >> 1);
-               return 0;
-       }
-       return bar & 0xfffffff0;
-}
-
-/* Helper to get the address from an IObar.  Check the type beforehand */
-uint32_t pci_getiobar32(uint32_t bar)
-{
-       return bar & 0xfffffffc;
-}
-
 /* Helper to get the class description strings.  Adapted from
  * http://www.pcidatabase.com/reports.php?type=c-header */
 static void pcidev_get_cldesc(struct pci_device *pcidev, char **class,
@@ -366,7 +440,8 @@ void pcidev_print_info(struct pci_device *pcidev, int verbosity)
                        printk("IO port 0x%04x\n", pcidev->bar[i].pio_base);
                } else {
                        bool bar_is_64 = pci_is_membar64(pcidev->bar[i].raw_bar);
-                       printk("MMIO Base %p, MMIO Size %p\n",
+                       printk("MMIO Base%s %p, MMIO Size %p\n",
+                              bar_is_64 ? "64" : "32",
                               bar_is_64 ? pcidev->bar[i].mmio_base64 :
                                           pcidev->bar[i].mmio_base32,
                               pcidev->bar[i].mmio_sz);
@@ -377,31 +452,30 @@ void pcidev_print_info(struct pci_device *pcidev, int verbosity)
                        }
                }
        }
+       printk("\tCapabilities:");
+       for (int i = 0; i < PCI_CAP_ID_MAX + 1; i++) {
+               if (pcidev->caps[i])
+                       printk(" 0x%02x", i);
+       }
+       printk("\n");
 }
 
 void pci_set_bus_master(struct pci_device *pcidev)
 {
+       spin_lock_irqsave(&pcidev->lock);
        pcidev_write16(pcidev, PCI_CMD_REG, pcidev_read16(pcidev, PCI_CMD_REG) |
                                            PCI_CMD_BUS_MAS);
+       spin_unlock_irqsave(&pcidev->lock);
 }
 
 void pci_clr_bus_master(struct pci_device *pcidev)
 {
        uint16_t reg;
+       spin_lock_irqsave(&pcidev->lock);
        reg = pcidev_read16(pcidev, PCI_CMD_REG);
        reg &= ~PCI_CMD_BUS_MAS;
        pcidev_write16(pcidev, PCI_CMD_REG, reg);
-}
-
-/* Find up to 'need' unused bars. Needed for MSI-X */
-int pci_find_unused_bars(struct pci_device *dev, int *bars, int need)
-{
-       int i, found;
-       for(i = found = 0; found < need && i < ARRAY_SIZE(dev->bar); i++)
-               if (!dev->bar[i].raw_bar)
-                       bars[found++] = i;
-       return found;
-
+       spin_unlock_irqsave(&pcidev->lock);
 }
 
 struct pci_device *pci_match_tbdf(int tbdf)
@@ -420,3 +494,120 @@ struct pci_device *pci_match_tbdf(int tbdf)
        }
        return NULL;
 }
+
+/* Helper to get the membar value for BAR index bir */
+uintptr_t pci_get_membar(struct pci_device *pcidev, int bir)
+{
+       if (bir >= pcidev->nr_bars)
+               return 0;
+       if (pcidev->bar[bir].mmio_base64) {
+               assert(pci_is_membar64(pcidev->bar[bir].raw_bar));
+               return pcidev->bar[bir].mmio_base64;
+       }
+       /* we can just return mmio_base32, even if it's 0.  but i'd like to do the
+        * assert too. */
+       if (pcidev->bar[bir].mmio_base32) {
+               assert(pci_is_membar32(pcidev->bar[bir].raw_bar));
+               return pcidev->bar[bir].mmio_base32;
+       }
+       return 0;
+}
+
+uintptr_t pci_get_iobar(struct pci_device *pcidev, int bir)
+{
+       if (bir >= pcidev->nr_bars)
+               return 0;
+       /* we can just return pio_base, even if it's 0.  but i'd like to do the
+        * assert too. */
+       if (pcidev->bar[bir].pio_base) {
+               assert(pci_is_iobar(pcidev->bar[bir].raw_bar));
+               return pcidev->bar[bir].pio_base;
+       }
+       return 0;
+}
+
+uint32_t pci_get_membar_sz(struct pci_device *pcidev, int bir)
+{
+       if (bir >= pcidev->nr_bars)
+               return 0;
+       return pcidev->bar[bir].mmio_sz;
+}
+
+uint16_t pci_get_vendor(struct pci_device *pcidev)
+{
+       return pcidev->ven_id;
+}
+
+uint16_t pci_get_device(struct pci_device *pcidev)
+{
+       return pcidev->dev_id;
+}
+
+uint16_t pci_get_subvendor(struct pci_device *pcidev)
+{
+       uint8_t header_type = pcidev_read8(pcidev, PCI_HEADER_REG) & 0x7c;
+       switch (header_type) {
+               case 0x00: /* STD_PCI_DEV */
+                       return pcidev_read16(pcidev, PCI_SUBSYSVEN_STD);
+               case 0x01: /* PCI2PCI */
+                       return -1;
+               case 0x02: /* PCI2CARDBUS */
+                       return pcidev_read16(pcidev, PCI_SUBVENID_CB);
+               default:
+                       warn("Unknown Header Type, %d", header_type);
+       }
+       return -1;
+}
+
+uint16_t pci_get_subdevice(struct pci_device *pcidev)
+{
+       uint8_t header_type = pcidev_read8(pcidev, PCI_HEADER_REG) & 0x7c;
+       switch (header_type) {
+               case 0x00: /* STD_PCI_DEV */
+                       return pcidev_read16(pcidev, PCI_SUBSYSID_STD);
+               case 0x01: /* PCI2PCI */
+                       return -1;
+               case 0x02: /* PCI2CARDBUS */
+                       return pcidev_read16(pcidev, PCI_SUBDEVID_CB);
+               default:
+                       warn("Unknown Header Type, %d", header_type);
+       }
+       return -1;
+}
+
+void pci_dump_config(struct pci_device *pcidev, size_t len)
+{
+       if (len > 256)
+               printk("FYI, printing more than 256 bytes of PCI space\n");
+       printk("PCI Config space for %02x:%02x:%02x\n---------------------\n",
+              pcidev->bus, pcidev->dev, pcidev->func);
+       for (int i = 0; i < len; i += 4)
+               printk("0x%03x | %08x\n", i, pcidev_read32(pcidev, i));
+}
+
+int pci_find_cap(struct pci_device *pcidev, uint8_t cap_id, uint32_t *cap_reg)
+{
+       if (cap_id > PCI_CAP_ID_MAX)
+               return -EINVAL;
+       if (!pcidev->caps[cap_id])
+               return -ENOENT;
+       /* The actual value at caps[id] is the offset in the PCI config space where
+        * that ID was stored.  That's needed for accessing the capability. */
+       if (cap_reg)
+               *cap_reg = pcidev->caps[cap_id];
+       return 0;
+}
+
+unsigned int pci_to_tbdf(struct pci_device *pcidev)
+{
+       return MKBUS(BusPCI, pcidev->bus, pcidev->dev, pcidev->func);
+}
+
+uintptr_t pci_map_membar(struct pci_device *dev, int bir)
+{
+       uintptr_t paddr = pci_get_membar(dev, bir);
+       size_t sz = pci_get_membar_sz(dev, bir);
+       if (!paddr || !sz)
+               return 0;
+       return vmap_pmem_nocache(paddr, sz);
+}