diff options
author | Mike Loptien <loptienm@gmail.com> | 2014-05-12 21:46:31 -0600 |
---|---|---|
committer | Martin Roth <gaumless@gmail.com> | 2014-05-29 23:24:28 +0200 |
commit | 0f5cf5e45b78d2e6a91d978bb86de5a4ff07c4d5 (patch) | |
tree | 6d8719bec0d6a1bef9af8e228cc1087008e96356 | |
parent | 433659ad1e864808ec30e90a62ecfd711559c5a9 (diff) |
PCI IRQs: Swizzle PCI IRQs for PCI bridges
The PCI Specification states that devices that implement
a bridge and a secondary bus must swizzle (rotate) the
interrupt pins according to the table below:
Child Dev # Child PIN Parent PIN
0,4,8,12... A/B/C/D A/B/C/D
1,5,9,13... A/B/C/D B/C/D/A
2,6,10,14.. A/B/C/D C/D/A/B
3,7,11,15.. A/B/C/D D/A/B/C
Which is also described by this equation:
PIN_parent = (Pin_child + Dev_child) % 4
When a device is found and its bus number is greater than 0,
it is on a bridge and needs to be swizzled. Following the
string of parents up to the root bus and swizzling as we go
gives us the desired swizzling result. When BIOS_SPEW is
defined, it will print out each step of the swizzling process.
Change-Id: Icafeadd01983282c86e25f560c831c9482c74e68
Signed-off-by: Martin Roth <gaumless@gmail.com>
Reviewed-on: http://review.coreboot.org/5734
Tested-by: build bot (Jenkins)
Reviewed-by: Marc Jones <marc.jones@se-eng.com>
Reviewed-by: Mike Loptien <mike.loptien@se-eng.com>
-rw-r--r-- | src/device/pci_device.c | 151 | ||||
-rw-r--r-- | src/include/device/pci.h | 2 |
2 files changed, 153 insertions, 0 deletions
diff --git a/src/device/pci_device.c b/src/device/pci_device.c index f09fcaa8de..3a54c2d5c8 100644 --- a/src/device/pci_device.c +++ b/src/device/pci_device.c @@ -12,6 +12,7 @@ * (Written by Yinghai Lu <yhlu@tyan.com> for Tyan) * Copyright (C) 2005-2009 coresystems GmbH * (Written by Stefan Reinauer <stepan@coresystems.de> for coresystems GmbH) + * Copyright (C) 2014 Sage Electronic Engineering, LLC. */ /* @@ -1303,6 +1304,156 @@ unsigned int pci_domain_scan_bus(device_t dev, unsigned int max) return max; } +/** + * Take an INT_PIN number (0, 1 - 4) and convert + * it to a string ("NO PIN", "PIN A" - "PIN D") + * + * @param pin PCI Interrupt Pin number (0, 1 - 4) + * @return A string corresponding to the pin number or "Invalid" + */ +const char *pin_to_str(int pin) +{ + const char *str[5] = { + "NO PIN", + "PIN A", + "PIN B", + "PIN C", + "PIN D", + }; + + if (pin >= 0 && pin <= 4) + return str[pin]; + else + return "Invalid PIN, not 0 - 4"; +} + +/** + * Get the PCI INT_PIN swizzle for a device defined as: + * pin_parent = (pin_child + devn_child) % 4 + 1 + * where PIN A = 1 ... PIN_D = 4 + * + * Given a PCI device structure 'dev', find the interrupt pin + * that will be triggered on its parent bridge device when + * generating an interrupt. For example: Device 1:3.2 may + * use INT_PIN A but will trigger PIN D on its parent bridge + * device. In this case, this function will return 4 (PIN D). + * + * @param dev A PCI device structure to swizzle interrupt pins for + * @param *parent_bdg The PCI device structure for the bridge + * device 'dev' is attached to + * @return The interrupt pin number (1 - 4) that 'dev' will + * trigger when generating an interrupt + */ +static int swizzle_irq_pins(device_t dev, device_t *parent_bridge) +{ + device_t parent; /* Our current device's parent device */ + device_t child; /* The child device of the parent */ + uint8_t parent_bus = 0; /* Parent Bus number */ + uint16_t parent_devfn = 0; /* Parent Device and Function number */ + uint16_t child_devfn = 0; /* Child Device and Function number */ + uint8_t swizzled_pin = 0; /* Pin swizzled across a bridge */ + + /* Start with PIN A = 0 ... D = 3 */ + swizzled_pin = pci_read_config8(dev, PCI_INTERRUPT_PIN) - 1; + + /* While our current device has parent devices */ + child = dev; + for (parent = child->bus->dev; parent; parent = parent->bus->dev) { + parent_bus = parent->bus->secondary; + parent_devfn = parent->path.pci.devfn; + child_devfn = child->path.pci.devfn; + + /* Swizzle the INT_PIN for any bridges not on root bus */ + swizzled_pin = (PCI_SLOT(child_devfn) + swizzled_pin) % 4; + printk(BIOS_SPEW, "\tWith INT_PIN swizzled to %s\n" + "\tAttached to bridge device %01X:%02Xh.%02Xh\n", + pin_to_str(swizzled_pin + 1), parent_bus, + PCI_SLOT(parent_devfn), PCI_FUNC(parent_devfn)); + + /* Continue until we find the root bus */ + if (parent_bus > 0) { + /* + * We will go on to the next parent so this parent + * becomes the child + */ + child = parent; + continue; + } else { + /* + * Found the root bridge device, + * fill in the structure and exit + */ + *parent_bridge = parent; + break; + } + } + + /* End with PIN A = 1 ... D = 4 */ + return swizzled_pin + 1; +} + +/** + * Given a device structure 'dev', find its interrupt pin + * and its parent bridge 'parent_bdg' device structure. + * If it is behind a bridge, it will return the interrupt + * pin number (1 - 4) of the parent bridge that the device + * interrupt pin has been swizzled to, otherwise it will + * return the interrupt pin that is programmed into the + * PCI config space of the target device. If 'dev' is + * behind a bridge, it will fill in 'parent_bdg' with the + * device structure of the bridge it is behind, otherwise + * it will copy 'dev' into 'parent_bdg'. + * + * @param dev A PCI device structure to get interrupt pins for. + * @param *parent_bdg The PCI device structure for the bridge + * device 'dev' is attached to. + * @return The interrupt pin number (1 - 4) that 'dev' will + * trigger when generating an interrupt. + * Errors: -1 is returned if the device is not enabled + * -2 is returned if a parent bridge could not be found. + */ +int get_pci_irq_pins(device_t dev, device_t *parent_bdg) +{ + uint8_t bus = 0; /* The bus this device is on */ + uint16_t devfn = 0; /* This device's device and function numbers */ + uint8_t int_pin = 0; /* Interrupt pin used by the device */ + uint8_t target_pin = 0; /* Interrupt pin we want to assign an IRQ to */ + + /* Make sure this device is enabled */ + if (!(dev->enabled && (dev->path.type == DEVICE_PATH_PCI))) + return -1; + + bus = dev->bus->secondary; + devfn = dev->path.pci.devfn; + + /* Get and validate the interrupt pin used. Only 1-4 are allowed */ + int_pin = pci_read_config8(dev, PCI_INTERRUPT_PIN); + if (int_pin < 1 || int_pin > 4) + return -1; + + printk(BIOS_SPEW, "PCI IRQ: Found device %01X:%02X.%02X using %s\n", + bus, PCI_SLOT(devfn), PCI_FUNC(devfn), pin_to_str(int_pin)); + + /* If this device is on a bridge, swizzle its INT_PIN */ + if (bus) { + /* Swizzle its INT_PINs */ + target_pin = swizzle_irq_pins(dev, parent_bdg); + + /* Make sure the swizzle returned valid structures */ + if (parent_bdg == NULL) { + printk(BIOS_WARNING, + "Warning: Could not find parent bridge for this device!\n"); + return -2; + } + } else { /* Device is not behind a bridge */ + target_pin = int_pin; /* Return its own interrupt pin */ + *parent_bdg = dev; /* Return its own structure */ + } + + /* Target pin is the interrupt pin we want to assign an IRQ to */ + return target_pin; +} + #if CONFIG_PC80_SYSTEM /** * Assign IRQ numbers. diff --git a/src/include/device/pci.h b/src/include/device/pci.h index 5594d293bd..81759706aa 100644 --- a/src/include/device/pci.h +++ b/src/include/device/pci.h @@ -82,6 +82,8 @@ void pci_dev_set_subsystem(device_t dev, unsigned vendor, unsigned device); void pci_dev_init(struct device *dev); unsigned int pci_match_simple_dev(device_t dev, pci_devfn_t sdev); +const char * pin_to_str(int pin); +int get_pci_irq_pins(device_t dev, device_t *parent_bdg); void pci_assign_irqs(unsigned bus, unsigned slot, const unsigned char pIntAtoD[4]); |