|  | /* Copyright (c) 2009, 2010 The Regents of the University of California | 
|  | * See LICENSE for details. | 
|  | * | 
|  | * Barret Rhoden <brho@cs.berkeley.edu> | 
|  | * Original by Paul Pearce <pearce@eecs.berkeley.edu> */ | 
|  |  | 
|  | #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> | 
|  |  | 
|  | /* 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; | 
|  | 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 >> 16; | 
|  | ven_id = result & 0xffff; | 
|  | /* Skip invalid IDs (not a device) */ | 
|  | if (ven_id == INVALID_VENDOR_ID) | 
|  | 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 */ | 
|  | 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 */ | 
|  | /* This is the PIC IRQ the device is wired to */ | 
|  | pcidev->irqline = pcidev_read8(pcidev, PCI_IRQLINE_STD); | 
|  | /* This is the interrupt pin the device uses (INTA# - INTD#) */ | 
|  | 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; | 
|  | } | 
|  | /* 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"; | 
|  | } | 
|  | 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(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, 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(uint8_t bus, uint8_t dev, uint8_t func, uint8_t offset, | 
|  | uint32_t value) | 
|  | { | 
|  | 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, 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, 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 type; | 
|  | if (bar >= MAX_PCI_BAR) | 
|  | panic("Nonexistant bar requested!"); | 
|  | type = pcidev_read8(pcidev, PCI_HEADER_REG); | 
|  | /* 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, | 
|  | char **subclass, char **progif) | 
|  | { | 
|  | int	i ; | 
|  | *class = *subclass = *progif = ""; | 
|  |  | 
|  | for (i = 0; i < PCI_CLASSCODETABLE_LEN; i++) { | 
|  | if (PciClassCodeTable[i].BaseClass == pcidev->class) { | 
|  | if (!(**class)) | 
|  | *class = PciClassCodeTable[i].BaseDesc; | 
|  | if (PciClassCodeTable[i].SubClass == pcidev->subclass) { | 
|  | if (!(**subclass)) | 
|  | *subclass = PciClassCodeTable[i].SubDesc; | 
|  | if (PciClassCodeTable[i].ProgIf == pcidev->progif) { | 
|  | *progif = PciClassCodeTable[i].ProgDesc; | 
|  | break ; | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | /* Helper to get the vendor and device description strings */ | 
|  | static void pcidev_get_devdesc(struct pci_device *pcidev, char **vend_short, | 
|  | char **vend_full, char **chip, char **chip_desc) | 
|  | { | 
|  | int	i ; | 
|  | *vend_short = *vend_full = *chip = *chip_desc = ""; | 
|  |  | 
|  | for (i = 0; i < PCI_VENTABLE_LEN; i++) { | 
|  | if (PciVenTable[i].VenId == pcidev->ven_id) { | 
|  | *vend_short = PciVenTable[i].VenShort; | 
|  | *vend_full = PciVenTable[i].VenFull; | 
|  | break ; | 
|  | } | 
|  | } | 
|  | for (i = 0; i < PCI_DEVTABLE_LEN; i++) { | 
|  | if ((PciDevTable[i].VenId == pcidev->ven_id) && | 
|  | (PciDevTable[i].DevId == pcidev->dev_id)) { | 
|  | *chip = PciDevTable[i].Chip; | 
|  | *chip_desc = PciDevTable[i].ChipDesc; | 
|  | break ; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | /* Prints info (like lspci) for a device */ | 
|  | void pcidev_print_info(struct pci_device *pcidev, int verbosity) | 
|  | { | 
|  | char *ven_sht, *ven_fl, *chip, *chip_txt, *class, *subcl, *progif; | 
|  | 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: %s\n", | 
|  | pcidev->bus, | 
|  | pcidev->dev, | 
|  | pcidev->func, | 
|  | subcl, | 
|  | ven_sht, | 
|  | chip, | 
|  | 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); | 
|  | } |