| /* 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 <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?). |
| * |
| * 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. */ |
| uint32_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) |
| { |
| uint32_t bar_val; |
| 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); |
| 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); |
| /* read next bar */ |
| bar_val = pci_getbar(pcidev, i + 1); |
| /* 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; |
| } |
| } |
| |
| 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) |
| { |
| uint32_t result = 0; |
| uint16_t dev_id, ven_id; |
| struct pci_device *pcidev; |
| int max_nr_func; |
| /* In earlier days bus address 0xff caused problems so we only iterated |
| * to PCI_MAX_BUS - 1, but this should no longer be an issue. Old |
| * comment: phantoms at 0xff */ |
| for (int i = 0; i < PCI_MAX_BUS; i++) { |
| 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 the first function doesn't exist then no |
| * device is connected, but there can be gaps in |
| * the other function numbers. Eg. 0,2,3 is ok. |
| * */ |
| if (ven_id == INVALID_VENDOR_ID) { |
| if (k == 0) |
| break; |
| continue; |
| } |
| 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 */ |
| 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); |
| /* 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); |
| __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); |
| #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, uint32_t reg) |
| { |
| return (uint32_t)(((uint32_t)bus << 16) | |
| ((uint32_t)dev << 11) | |
| ((uint32_t)func << 8) | |
| (reg & 0xfc) | |
| ((reg & 0xf00) << 16) |/* extended PCI CFG space... */ |
| 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, uint32_t offset) |
| { |
| uint32_t ret; |
| |
| spin_lock_irqsave(&pci_lock); |
| outl(PCI_CONFIG_ADDR, pci_config_addr(bus, dev, func, offset)); |
| 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 |
| * PCI lets you write back). */ |
| 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); |
| } |
| |
| 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)); |
| 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, |
| 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); |
| } |
| |
| 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)); |
| 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, |
| 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) |
| { |
| return pci_read32(pcidev->bus, pcidev->dev, pcidev->func, offset); |
| } |
| |
| void pcidev_write32(struct pci_device *pcidev, uint32_t offset, uint32_t value) |
| { |
| pci_write32(pcidev->bus, pcidev->dev, pcidev->func, offset, value); |
| } |
| |
| uint16_t pcidev_read16(struct pci_device *pcidev, uint32_t offset) |
| { |
| return pci_read16(pcidev->bus, pcidev->dev, pcidev->func, offset); |
| } |
| |
| void pcidev_write16(struct pci_device *pcidev, uint32_t offset, uint16_t value) |
| { |
| pci_write16(pcidev->bus, pcidev->dev, pcidev->func, offset, value); |
| } |
| |
| uint8_t pcidev_read8(struct pci_device *pcidev, uint32_t offset) |
| { |
| return pci_read8(pcidev->bus, pcidev->dev, pcidev->func, offset); |
| } |
| |
| void pcidev_write8(struct pci_device *pcidev, uint32_t offset, uint8_t value) |
| { |
| pci_write8(pcidev->bus, pcidev->dev, pcidev->func, offset, value); |
| } |
| |
| /* 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%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); |
| /* Takes up two bars */ |
| if (bar_is_64) { |
| assert(!pcidev->bar[i].mmio_base32); |
| i++; |
| } |
| } |
| } |
| 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); |
| spin_unlock_irqsave(&pcidev->lock); |
| } |
| |
| struct pci_device *pci_match_tbdf(int tbdf) |
| { |
| struct pci_device *search; |
| int bus, dev, func; |
| |
| bus = BUSBNO(tbdf); |
| dev = BUSDNO(tbdf); |
| func = BUSFNO(tbdf); |
| |
| STAILQ_FOREACH(search, &pci_devices, all_dev) { |
| if ((search->bus == bus) && |
| (search->dev == dev) && |
| (search->func == func)) |
| return search; |
| } |
| 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); |
| } |
| |
| /* The following were ported from Linux: |
| * |
| * pci_set_cacheline_size |
| * pci_set_mwi |
| * pci_clear_mwi |
| */ |
| int pci_set_cacheline_size(struct pci_device *dev) |
| { |
| uint8_t cl_sz; |
| uint8_t pci_cache_line_size = ARCH_CL_SIZE >> 2; |
| |
| cl_sz = pcidev_read8(dev, PCI_CACHE_LINE_SIZE); |
| /* Validate current setting: the PCI_CACHE_LINE_SIZE must be equal to or |
| * multiple of the right value. */ |
| if (cl_sz >= pci_cache_line_size && (cl_sz % pci_cache_line_size) == 0) |
| return 0; |
| pcidev_write8(dev, PCI_CACHE_LINE_SIZE, pci_cache_line_size); |
| cl_sz = pcidev_read8(dev, PCI_CACHE_LINE_SIZE); |
| if (cl_sz == pci_cache_line_size) |
| return 0; |
| printk("PCI device %s does not support cache line size of %d\n", |
| dev->name, pci_cache_line_size << 2); |
| return -EINVAL; |
| } |
| |
| int pci_set_mwi(struct pci_device *dev) |
| { |
| int rc; |
| uint16_t cmd; |
| |
| rc = pci_set_cacheline_size(dev); |
| if (rc) |
| return rc; |
| cmd = pcidev_read16(dev, PCI_COMMAND); |
| if (!(cmd & PCI_COMMAND_INVALIDATE)) { |
| cmd |= PCI_COMMAND_INVALIDATE; |
| pcidev_write16(dev, PCI_COMMAND, cmd); |
| } |
| return 0; |
| } |
| |
| void pci_clear_mwi(struct pci_device *dev) |
| { |
| uint16_t cmd; |
| |
| cmd = pcidev_read16(dev, PCI_COMMAND); |
| if (cmd & PCI_COMMAND_INVALIDATE) { |
| cmd &= ~PCI_COMMAND_INVALIDATE; |
| pcidev_write16(dev, PCI_COMMAND, cmd); |
| } |
| } |