PCI cleanup
authorBarret Rhoden <brho@cs.berkeley.edu>
Mon, 28 Oct 2013 22:54:15 +0000 (15:54 -0700)
committerBarret Rhoden <brho@cs.berkeley.edu>
Thu, 16 Jan 2014 19:18:58 +0000 (11:18 -0800)
Includes helper functions for reading/writing 16/8 bit registers in the config
space and the ensuing cleanup.

Also includes better detection of multifunction devices; we only scan
for them if we know we have a multifunction, instead of scanning all the
time.  We also were missing out on the header type frequently, since we
didn't mask 0x7c.

kern/arch/x86/pci.c
kern/arch/x86/pci.h

index 898c58f..4284bbf 100644 (file)
@@ -47,6 +47,7 @@ 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;
@@ -83,15 +84,17 @@ 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;
+                                       break;  /* skip functions too, they won't exist */
                                pcidev = kzmalloc(sizeof(struct pci_device), 0);
                                pcidev->bus = i;
                                pcidev->dev = j;
@@ -99,23 +102,22 @@ void pci_init(void) {
                                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;
+                               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;
                                }
-                               switch ((pcidev_read32(pcidev, PCI_HEADER_REG) >> 16) & 0xff) {
+                               /* bottom 7 bits are header type */
+                               switch (pcidev_read8(pcidev, PCI_HEADER_REG) & 0x7c) {
                                        case 0x00:
                                                pcidev->header_type = STD_PCI_DEV;
                                                break;
@@ -135,52 +137,117 @@ void pci_init(void) {
                                #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);
 }
 
+/* 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;
+       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;
@@ -327,7 +394,6 @@ void pcidev_print_info(struct pci_device *pcidev, int verbosity)
 
 void pci_set_bus_master(struct pci_device *pcidev)
 {
-       pcidev_write32(pcidev, PCI_STAT_CMD_REG,
-                      pcidev_read32(pcidev, PCI_STAT_CMD_REG) |
-                      PCI_CMD_BUS_MAS);
+       pcidev_write16(pcidev, PCI_CMD_REG, pcidev_read16(pcidev, PCI_CMD_REG) |
+                                           PCI_CMD_BUS_MAS);
 }
index 07da58f..85c70a7 100644 (file)
 
 #define pci_debug(...)  printk(__VA_ARGS__)  
 
-// Macro for creating the address fed to the PCI config register 
-// TODO: get rid of this, in favor of the helpers
-#define MK_CONFIG_ADDR(BUS, DEV, FUNC, REG) (unsigned long)(((BUS) << 16)   |  \
-                                                            ((DEV) << 11)   |  \
-                                                            ((FUNC) << 8)   |  \
-                                                            ((REG) & 0xfc)  |  \
-                                                            (0x80000000))
-
 #define PCI_CONFIG_ADDR     0xCF8
 #define PCI_CONFIG_DATA     0xCFC
 #define INVALID_VENDOR_ID   0xFFFF
 /* TODO: gut this (when the IOAPIC is fixed) */
 #define INVALID_BUS                    0xFFFF
 
-#define PCI_IRQLINE_MASK       0x000000ff
-#define PCI_IRQPIN_MASK                0x0000ff00
-#define PCI_IRQPIN_SHFT                8
-#define PCI_VENDOR_MASK                0xffff
-#define PCI_DEVICE_OFFSET      0x10
-
 #define PCI_NOINT                      0x00
 #define PCI_INTA                       0x01
 #define PCI_INTB                       0x02
 #define PCI_INTD                       0x04
 
 /* PCI Register Config Space */
-#define PCI_DEV_VEND_REG       0x00
-#define PCI_STAT_CMD_REG       0x04
-#define PCI_CLASS_REG          0x08
-#define PCI_HEADER_REG         0x0c
+#define PCI_DEV_VEND_REG       0x00    /* for the 32 bit read of dev/vend */
+#define PCI_VENDID_REG         0x00
+#define PCI_DEVID_REG          0x02
+#define PCI_CMD_REG                    0x04
+#define PCI_STATUS_REG         0x06
+#define PCI_REVID_REG          0x08
+#define PCI_PROGIF_REG         0x09
+#define PCI_SUBCLASS_REG       0x0a
+#define PCI_CLASS_REG          0x0b
+#define PCI_CLSZ_REG           0x0c
+#define PCI_LATTIM_REG         0x0d
+#define PCI_HEADER_REG         0x0e
+#define PCI_BIST_REG           0x0f
 /* Config space for header type 0x00  (Standard) */
 #define PCI_BAR0_STD           0x10
 #define PCI_BAR1_STD           0x14
 #define PCI_BAR5_STD           0x24
 #define PCI_BAR_OFF                    0x04
 #define PCI_CARDBUS_STD                0x28
-#define PCI_SUBSYSTEM_STD      0x2C
+#define PCI_SUBSYSVEN_STD      0x2c
+#define PCI_SUBSYSID_STD       0x2e
 #define PCI_EXPROM_STD         0x30
 #define PCI_CAPAB_STD          0x34
-#define PCI_IRQ_STD                    0x3c
+#define PCI_IRQLINE_STD                0x3c
+#define PCI_IRQPIN_STD         0x3d
+#define PCI_MINGRNT_STD                0x3e
+#define PCI_MAXLAT_STD         0x3f
 /* Config space for header type 0x01 (PCI-PCI bridge) */
+/* None of these have been used, so if you use them, check them against
+ * http://wiki.osdev.org/PCI#PCI_Device_Structure */
 #define PCI_BAR0_BR                    0x10
 #define PCI_BAR1_BR                    0x14
-#define PCI_BUSINFO_BR         0x18
-#define PCI_IOINFO_BR          0x1c
-#define PCI_MEM_BR                     0x20
-#define PCI_MEM_PRFC_BR                0x24
-#define PCI_PRFC_BASE_BR       0x28
-#define PCI_PRFC_LIM_BR                0x2C
-#define PCI_IO_LIM_BR          0x30
+#define PCI_BUS1_BR                    0x18
+#define PCI_BUS2_BR                    0x19
+#define PCI_SUBBUS_BR          0x1a
+#define PCI_LATTIM2_BR         0x1b
+#define PCI_IOBASE_BR          0x1c
+#define PCI_IOLIM_BR           0x1d
+#define PCI_STATUS2_BR         0x1e
+#define PCI_MEMBASE_BR         0x20
+#define PCI_MEMLIM_BR          0x22
+#define PCI_PREMEMBASE_BR      0x24
+#define PCI_PREMEMLIM_BR       0x26
+#define PCI_PREBASEUP32_BR     0x28
+#define PCI_PRELIMUP32_BR      0x2c
+#define PCI_IOBASEUP16_BR      0x30
+#define PCI_IOLIMUP16_BR       0x32
 #define PCI_CAPAB_BR           0x34
-#define PCI_IRQ_BDG_BR         0x3c
+#define PCI_EXPROM_BR          0x38
+#define PCI_IRQLINE_BR         0x3c
+#define PCI_IRQPIN_BR          0x3d
+#define PCI_BDGCTL_BR          0x3e
 /* Config space for header type 0x02 (PCI-Cardbus bridge) */
+/* None of these have been used, so if you use them, check them against
+ * http://wiki.osdev.org/PCI#PCI_Device_Structure */
 #define PCI_SOC_BASE_CB                0x10
-#define PCI_SEC_STAT_CB                0x14
-#define PCI_BUS_INFO_CB                0x18
+#define PCI_OFF_CAP_CB         0x14
+#define PCI_SEC_STAT_CB                0x16
+#define PCI_BUS_NR_CB          0x18
+#define PCI_CARDBUS_NR_CB      0x19
+#define PCI_SUBBUS_NR_CB       0x1a
+#define PCI_CARD_LAT_CB                0x1b
 #define PCI_MEM_BASE0_CB       0x1c
 #define PCI_MEM_LIMIT0_CB      0x20
 #define PCI_MEM_BASE1_CB       0x24
 #define PCI_IO_LIMIT0_CB       0x30
 #define PCI_IO_BASE1_CB                0x34
 #define PCI_IO_LIMIT1_CB       0x38
-#define PCI_IRQ_CB                     0x3c
-#define PCI_SUBSYS_CB          0x40
+#define PCI_IRQLINE_CB         0x3c
+#define PCI_IRQPIN_CB          0x3d
+#define PCI_BDGCTL_CB          0x3e
+#define PCI_SUBDEVID_CB                0x40
+#define PCI_SUBVENID_CB                0x42
 #define PCI_16BIT_CB           0x44
 
-/* Legacy Paul-mapping */
-#define PCI_IRQ_REG                    PCI_IRQ_STD
-
 /* Command Register Flags */
 #define PCI_CMD_IO_SPC         (1 << 0)
 #define PCI_CMD_MEM_SPC                (1 << 1)
@@ -170,16 +188,19 @@ extern struct pcidev_stailq pci_devices;
 
 void pci_init(void);
 void pcidev_print_info(struct pci_device *pcidev, int verbosity);
+uint32_t pci_config_addr(uint8_t bus, uint8_t dev, uint8_t func, uint8_t reg);
 
 /* Read and write helpers (Eventually, we should have these be statics, since no
  * device should touch PCI config space). */
-uint32_t pci_read32(unsigned short bus, unsigned short dev, unsigned short func,
-                    unsigned short offset);
-void pci_write32(unsigned short bus, unsigned short dev, unsigned short func,
-                    unsigned short offset, uint32_t value);
-uint32_t pcidev_read32(struct pci_device *pcidev, unsigned short offset);
-void pcidev_write32(struct pci_device *pcidev, unsigned short offset,
-                    uint32_t value);
+uint32_t pci_read32(uint8_t bus, uint8_t dev, uint8_t func, uint8_t offset);
+void pci_write32(uint8_t bus, uint8_t dev, uint8_t func, uint8_t offset,
+                 uint32_t value);
+uint32_t pcidev_read32(struct pci_device *pcidev, uint8_t offset);
+void pcidev_write32(struct pci_device *pcidev, uint8_t offset, uint32_t value);
+uint16_t pcidev_read16(struct pci_device *pcidev, uint8_t offset);
+void pcidev_write16(struct pci_device *pcidev, uint8_t offset, uint16_t value);
+uint8_t pcidev_read8(struct pci_device *pcidev, uint8_t offset);
+void pcidev_write8(struct pci_device *pcidev, uint8_t offset, uint8_t value);
 
 /* BAR helpers, some more helpful than others. */
 uint32_t pci_membar_get_sz(struct pci_device *pcidev, int bar);