Add flags that VMs need. But many other things do as well.
[akaros.git] / kern / arch / x86 / pci.c
index 36e6425..4284bbf 100644 (file)
 
 #include <arch/x86.h>
 #include <arch/pci.h>
+#include <trap.h>
 #include <stdio.h>
 #include <string.h>
 #include <assert.h>
 #include <kmalloc.h>
 #include <arch/pci_defs.h>
 
-/* Which pci devices hang off of which irqs */
-/* TODO: make this an array of SLISTs (pain from ioapic.c, etc...) */
-struct pci_device *irq_pci_map[NUM_IRQS] = {0};
-
 /* List of all discovered devices */
 struct pcidev_stailq pci_devices = STAILQ_HEAD_INITIALIZER(pci_devices);
 
+static char STD_PCI_DEV[] = "Standard PCI Device";
+static char PCI2PCI[] = "PCI-to-PCI Bridge";
+static char PCI2CARDBUS[] = "PCI-Cardbus Bridge";
+
+/* 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)
+{
+       /* save the old value, write all 1s, invert, add 1, restore.
+        * http://wiki.osdev.org/PCI for details. */
+       uint8_t bar_off = PCI_BAR0_STD + bar * PCI_BAR_OFF;
+       uint32_t old_val = pcidev_read32(pcidev, bar_off);
+       uint32_t retval;
+       pcidev_write32(pcidev, bar_off, 0xffffffff);
+       /* Don't forget to mask the lower 3 bits! */
+       retval = pcidev_read32(pcidev, bar_off) & PCI_BAR_MEM_MASK;
+       retval = ~retval + 1;
+       pcidev_write32(pcidev, bar_off, old_val);
+       return retval;
+}
+
+/* process the bars.  these will tell us what address space (PIO or memory) and
+ * 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)
+{
+       /* only handling standards for now */
+       uint32_t bar_val;
+       int max_bars = pcidev->header_type == STD_PCI_DEV ? MAX_PCI_BAR : 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);
+               pcidev->bar[i].raw_bar = bar_val;
+               if (!bar_val)   /* (0 denotes no valid data) */
+                       continue;
+               if (pci_is_iobar(bar_val)) {
+                       pcidev->bar[i].pio_base = pci_getiobar32(bar_val);
+               } 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);
+                       } else if (pci_is_membar64(bar_val)) {
+                               /* 64 bit, the lower 32 are in this bar, the upper
+                                * are in the next bar */
+                               pcidev->bar[i].mmio_base64 = bar_val & PCI_BAR_MEM_MASK;
+                               assert(i < max_bars - 1);
+                               bar_val = pci_getbar(pcidev, i + 1);    /* read next bar */
+                               /* 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);
+                               i++;
+                       }
+               }
+               /* this will track the maximum bar we've had.  it'll include the 64 bit
+                * uppers, as well as devices that have only higher numbered bars. */
+               pcidev->nr_bars = i + 1;
+       }
+}
+
 /* 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) {
        uint32_t result = 0;
        uint16_t dev_id, ven_id;
        struct pci_device *pcidev;
-       for (int i = 0; i < PCI_MAX_BUS - 1; i++)       /* phantoms at 0xff */
-               for (int j = 0; j < PCI_MAX_DEV; j++)
-                       for (int k = 0; k < PCI_MAX_FUNC; k++) {
+       int max_nr_func;
+       for (int i = 0; i < PCI_MAX_BUS - 1; i++) {     /* phantoms at 0xff */
+               for (int j = 0; j < PCI_MAX_DEV; j++) {
+                       max_nr_func = 1;
+                       for (int k = 0; k < max_nr_func; k++) {
                                result = pci_read32(i, j, k, PCI_DEV_VEND_REG);
-                               dev_id = result >> PCI_DEVICE_OFFSET;
-                               ven_id = result & PCI_VENDOR_MASK;
+                               dev_id = result >> 16;
+                               ven_id = result & 0xffff;
                                /* Skip invalid IDs (not a device) */
                                if (ven_id == INVALID_VENDOR_ID) 
-                                       continue;
-                               pcidev = kmalloc(sizeof(struct pci_device), 0);
+                                       break;  /* skip functions too, they won't exist */
+                               pcidev = kzmalloc(sizeof(struct pci_device), 0);
                                pcidev->bus = i;
                                pcidev->dev = j;
                                pcidev->func = k;
                                pcidev->dev_id = dev_id;
                                pcidev->ven_id = ven_id;
                                /* Get the Class/subclass */
-                               result = pcidev_read32(pcidev, PCI_CLASS_REG);
-                               pcidev->class = result >> 24;
-                               pcidev->subclass = (result >> 16) & 0xff;
-                               pcidev->progif = (result >> 8) & 0xff;
+                               pcidev->class = pcidev_read8(pcidev, PCI_CLASS_REG);
+                               pcidev->subclass = pcidev_read8(pcidev, PCI_SUBCLASS_REG);
+                               pcidev->progif = pcidev_read8(pcidev, PCI_PROGIF_REG);
                                /* All device types (0, 1, 2) have the IRQ in the same place */
-                               result = pcidev_read32(pcidev, PCI_IRQ_STD);
                                /* This is the PIC IRQ the device is wired to */
-                               pcidev->irqline = result & PCI_IRQLINE_MASK;
+                               pcidev->irqline = pcidev_read8(pcidev, PCI_IRQLINE_STD);
                                /* This is the interrupt pin the device uses (INTA# - INTD#) */
-                               pcidev->irqpin = (result & PCI_IRQPIN_MASK) >> PCI_IRQPIN_SHFT;
-                               #ifdef CONFIG_PCI_VERBOSE
-                               pcidev_print_info(pcidev, 4);
-                               #else
-                               pcidev_print_info(pcidev, 0);
-                               #endif /* CONFIG_PCI_VERBOSE */
+                               pcidev->irqpin = pcidev_read8(pcidev, PCI_IRQPIN_STD);
                                if (pcidev->irqpin != PCI_NOINT) {
                                        /* TODO: use a list (check for collisions for now) (massive
                                         * collisions on a desktop with bridge IRQs. */
                                        //assert(!irq_pci_map[pcidev->irqline]);
                                        irq_pci_map[pcidev->irqline] = pcidev;
                                }
-                               /* Loop over the BARs Right now we don't do anything useful with
-                                * this data.  This is legacy code in which I pulled data from
-                                * the BARS during NIC development At some point we will have to
-                                * use this, so the code is still here. */
-                               
-                               // Note: These magic numbers are from the PCI spec (according to OSDev).
-                               #if 0
-                               #ifdef CHECK_BARS
-                               for (int k = 0; k <= 5; k++) {
-                                       reg = 4 + k;
-                                       address = MK_CONFIG_ADDR(bus, dev, func, reg << 2);     
-                               outl(PCI_CONFIG_ADDR, address);
-                               result = inl(PCI_CONFIG_DATA);
-                                       
-                                       if (result == 0) // (0 denotes no valid data)
-                                               continue;
-
-                                       // Read the bottom bit of the BAR. 
-                                       if (result & PCI_BAR_IO_MASK) {
-                                               result = result & PCI_IO_MASK;
-                                               pci_debug("-->BAR%u: %s --> %x\n", k, "IO", result);
-                                       } else {
-                                               result = result & PCI_MEM_MASK;
-                                               pci_debug("-->BAR%u: %s --> %x\n", k, "MEM", result);
-                                       }                                       
+                               /* bottom 7 bits are header type */
+                               switch (pcidev_read8(pcidev, PCI_HEADER_REG) & 0x7c) {
+                                       case 0x00:
+                                               pcidev->header_type = STD_PCI_DEV;
+                                               break;
+                                       case 0x01:
+                                               pcidev->header_type = PCI2PCI;
+                                               break;
+                                       case 0x02:
+                                               pcidev->header_type = PCI2CARDBUS;
+                                               break;
+                                       default:
+                                               pcidev->header_type = "Unknown Header Type";
                                }
-                               #endif
-                               #endif
-                               
+                               pci_handle_bars(pcidev);
                                STAILQ_INSERT_TAIL(&pci_devices, pcidev, all_dev);
+                               #ifdef CONFIG_PCI_VERBOSE
+                               pcidev_print_info(pcidev, 4);
+                               #else
+                               pcidev_print_info(pcidev, 0);
+                               #endif /* CONFIG_PCI_VERBOSE */
+                               /* Top bit determines if we have multiple functions on this
+                                * device.  We can't just check for more functions, since
+                                * non-multifunction devices exist that respond to different
+                                * functions with the same underlying device (same bars etc).
+                                * Note that this style allows for devices that only report
+                                * multifunction in the first function's header. */
+                               if (pcidev_read8(pcidev, PCI_HEADER_REG) & 0x80)
+                                       max_nr_func = PCI_MAX_FUNC;
                        }
+               }
+       }
+}
+
+uint32_t pci_config_addr(uint8_t bus, uint8_t dev, uint8_t func, uint8_t reg)
+{
+       return (uint32_t)(((uint32_t)bus << 16) |
+                         ((uint32_t)dev << 11) |
+                         ((uint32_t)func << 8) |
+                         (reg & 0xfc) | 0x80000000);
 }
 
 /* Helper to read 32 bits from the config space of B:D:F.  'Offset' is how far
  * into the config space we offset before reading, aka: where we are reading. */
-uint32_t pci_read32(unsigned short bus, unsigned short dev, unsigned short func,
-                    unsigned short offset)
+uint32_t pci_read32(uint8_t bus, uint8_t dev, uint8_t func, uint8_t offset)
 {
        /* Send type 1 requests for everything beyond bus 0.  Note this does nothing
         * until we configure the PCI bridges (which we don't do yet). */
        if (bus !=  0)
                offset |= 0x1;
-       outl(PCI_CONFIG_ADDR, MK_CONFIG_ADDR(bus, dev, func, offset));
+       outl(PCI_CONFIG_ADDR, pci_config_addr(bus, dev, func, offset));
        return inl(PCI_CONFIG_DATA);
 }
 
 /* Same, but writes (doing 32bit at a time).  Never actually tested (not sure if
  * PCI lets you write back). */
-void pci_write32(unsigned short bus, unsigned short dev, unsigned short func,
-                    unsigned short offset, uint32_t value)
+void pci_write32(uint8_t bus, uint8_t dev, uint8_t func, uint8_t offset,
+                 uint32_t value)
 {
-       outl(PCI_CONFIG_ADDR, MK_CONFIG_ADDR(bus, dev, func, offset));
+       outl(PCI_CONFIG_ADDR, pci_config_addr(bus, dev, func, offset));
        outl(PCI_CONFIG_DATA, value);
 }
 
 /* Helper to read from a specific device's config space. */
-uint32_t pcidev_read32(struct pci_device *pcidev, unsigned short offset)
+uint32_t pcidev_read32(struct pci_device *pcidev, uint8_t offset)
 {
        return pci_read32(pcidev->bus, pcidev->dev, pcidev->func, offset);
 }
 
 /* Helper to write to a specific device */
-void pcidev_write32(struct pci_device *pcidev, unsigned short offset,
-                    uint32_t value)
+void pcidev_write32(struct pci_device *pcidev, uint8_t offset, uint32_t value)
 {
        pci_write32(pcidev->bus, pcidev->dev, pcidev->func, offset, value);
 }
 
-/* Gets any old raw bar. */
+/* For the 16 and 8 functions, we need to access on 32 bit alignments, then
+ * figure out which byte/word we need to read/write.  & 0xfc will give us the 4
+ * byte aligned offset to access in PCI space.  & 0x3 will give the offset
+ * within the 32 bits (number of bytes).  When writing, we also need to x-out
+ * any existing values (and not just |=). */
+
+/* Returns the 32-bit addr/offset needed to access 'offset'. */
+static inline uint8_t __pci_off32(uint8_t offset)
+{
+       return offset & 0xfc;
+}
+
+/* Returns the number of bits needed to shift to get the offset's spot in a 32
+ * bit config register. */
+static inline uint8_t __pci_shift_for(uint8_t offset)
+{
+       return (offset & 0x3) * 8;
+}
+
+uint16_t pcidev_read16(struct pci_device *pcidev, uint8_t offset)
+{
+       uint32_t retval = pcidev_read32(pcidev, __pci_off32(offset));
+       /* 0x2 would work here, since offset & 0x3 should be 0 or 2 */
+       retval >>= __pci_shift_for(offset);
+       return (uint16_t)(retval & 0xffff);
+}
+
+void pcidev_write16(struct pci_device *pcidev, uint8_t offset, uint16_t value)
+{
+       uint32_t readval = pcidev_read32(pcidev, __pci_off32(offset));
+       uint32_t writeval = (uint32_t)value << __pci_shift_for(offset);
+       readval &= ~(0xffff << __pci_shift_for(offset));
+       pcidev_write32(pcidev, __pci_off32(offset), readval | writeval);
+}
+
+uint8_t pcidev_read8(struct pci_device *pcidev, uint8_t offset)
+{
+       uint32_t retval = pcidev_read32(pcidev, __pci_off32(offset));
+       retval >>= __pci_shift_for(offset);
+       return (uint8_t)(retval & 0xff);
+}
+
+void pcidev_write8(struct pci_device *pcidev, uint8_t offset, uint8_t value)
+{
+       uint32_t readval = pcidev_read32(pcidev, __pci_off32(offset));
+       uint32_t writeval = (uint32_t)value << __pci_shift_for(offset);
+       readval &= ~(0xff << __pci_shift_for(offset));
+       pcidev_write32(pcidev, __pci_off32(offset), readval | writeval);
+}
+
+/* Gets any old raw bar, with some catches based on type. */
 uint32_t pci_getbar(struct pci_device *pcidev, unsigned int bar)
 {
-       uint32_t value, type;
-       if (bar > 5)
+       uint32_t type;
+       if (bar >= MAX_PCI_BAR)
                panic("Nonexistant bar requested!");
-       value = pcidev_read32(pcidev, PCI_HEADER_REG);
-       type = (value >> 16) & 0xff;
+       type = pcidev_read8(pcidev, PCI_HEADER_REG);
        /* Only types 0 and 1 have BARS */
        if ((type != 0x00) && (type != 0x01))
                return 0;
@@ -153,6 +263,20 @@ 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)
 {
@@ -225,25 +349,51 @@ void pcidev_print_info(struct pci_device *pcidev, int verbosity)
        pcidev_get_cldesc(pcidev, &class, &subcl, &progif);
        pcidev_get_devdesc(pcidev, &ven_sht, &ven_fl, &chip, &chip_txt);
 
-       printk("%02x:%02x.%x %s: %s %s %s\n",
+       printk("%02x:%02x.%x %s: %s %s %s: %s\n",
               pcidev->bus,
               pcidev->dev,
               pcidev->func,
               subcl,
               ven_sht,
               chip,
-              chip_txt);
-       if (verbosity > 1)
-               printk("        IRQ: %02d IRQ pin: %02p\n",
-                      pcidev->irqline,
-                      pcidev->irqpin);
-       if (verbosity > 2)
-               printk("        Vendor Id: %04p Device Id: %04p\n",
-                      pcidev->ven_id,
-                      pcidev->dev_id);
-       if (verbosity > 3)
-               printk("        %s %s %s\n",
-                      class,
-                      progif,
-                      ven_fl);
+              chip_txt,
+                  pcidev->header_type);
+       if (verbosity < 1)      /* whatever */
+               return;
+       printk("\tIRQ: %02d IRQ pin: 0x%02x\n",
+              pcidev->irqline,
+              pcidev->irqpin);
+       printk("\tVendor Id: 0x%04x Device Id: 0x%04x\n",
+              pcidev->ven_id,
+              pcidev->dev_id);
+       printk("\t%s %s %s\n",
+              class,
+              progif,
+              ven_fl);
+       for (int i = 0; i < pcidev->nr_bars; i++) {
+               if (pcidev->bar[i].raw_bar == 0)
+                       continue;
+               printk("\tBAR %d: ", i);
+               if (pci_is_iobar(pcidev->bar[i].raw_bar)) {
+                       assert(pcidev->bar[i].pio_base);
+                       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",
+                              bar_is_64 ? pcidev->bar[i].mmio_base64 :
+                                          pcidev->bar[i].mmio_base32,
+                              pcidev->bar[i].mmio_sz);
+                       /* Takes up two bars */
+                       if (bar_is_64) {
+                               assert(!pcidev->bar[i].mmio_base32);    /* double-check */
+                               i++;
+                       }
+               }
+       }
+}
+
+void pci_set_bus_master(struct pci_device *pcidev)
+{
+       pcidev_write16(pcidev, PCI_CMD_REG, pcidev_read16(pcidev, PCI_CMD_REG) |
+                                           PCI_CMD_BUS_MAS);
 }