Martin Roth (martin.roth@se-eng.com) just uploaded a new patch set to gerrit, which you can find at http://review.coreboot.org/5734
-gerrit
commit d1ce947b01fd89147ed37d425982902bcacab250 Author: Mike Loptien mike.loptien@se-eng.com Date: Mon May 12 21:46:31 2014 -0600
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 --- src/device/pci_device.c | 141 +++++++++++++++++++++++++++++++++++++++++++++++ src/include/device/pci.h | 3 + 2 files changed, 144 insertions(+)
diff --git a/src/device/pci_device.c b/src/device/pci_device.c index f09fcaa..588e297 100644 --- a/src/device/pci_device.c +++ b/src/device/pci_device.c @@ -1303,6 +1303,147 @@ 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 ("NO PIN", "PIN A" - "PIN D") corresponding to the pin number + */ +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 + * where PIN A = 0 ... PIN_D = 3 + * + * 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 + */ +int swizzle_irq_pins(device_t dev, device_t *parent_bdg) +{ + device_t parent; /* Our current devices parent device */ + device_t child; /* The child device of the parent */ + int parent_bus = 0; /* Parent Bus number */ + int parent_devfn = 0; /* Parent Device and Function number */ + int child_devfn = 0; /* Child Device and Function number */ + int 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_bdg = 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 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. + */ +int get_irq_pins(device_t dev, device_t *parent_bdg) +{ + int bus = 0; /* The bus this device is on */ + int devfn = 0; /* This device's device and function numbers */ + int int_pin = 0; /* Interrupt pin used by the device */ + int target_pin = 0; /* Interrupt pin we want to assign an IRQ to */ + + bus = dev->bus->secondary; + devfn = dev->path.pci.devfn; + + /* Make sure this device is enabled and has a valid INT_PIN */ + if (!(dev->enabled && (dev->path.type == DEVICE_PATH_PCI))) + return -1; + + /* Get the interrupt pin that this dev uses, 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:%02Xh.%02Xh 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 5594d29..f4dcd4d 100644 --- a/src/include/device/pci.h +++ b/src/include/device/pci.h @@ -82,6 +82,9 @@ 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_irq_pins(device_t dev, device_t *parent_bdg); +int swizzle_irq_pins(device_t dev, device_t *parent_bdg); void pci_assign_irqs(unsigned bus, unsigned slot, const unsigned char pIntAtoD[4]);