From 4ccc803579bef5ded5679d2c00b22d2db299a974 Mon Sep 17 00:00:00 2001 From: "Sergey Semyonov (Serge)" Date: Thu, 30 Sep 2010 18:43:26 +0000 Subject: [PATCH] devman: detect acpi irq git-svn-id: svn://kolibrios.org@1633 a494cfbc-eb01-0410-851d-a64ba20cac60 --- drivers/devman/acpi.c | 6 +- drivers/devman/acpi_bus.h | 7 + drivers/devman/pci/access.c | 2 +- drivers/devman/pci/pci.c | 25 ++ drivers/devman/pci/probe.c | 4 +- drivers/devman/pci_irq.c | 477 ++++++++++++++++++++++++++++++++++++ drivers/devman/pci_root.c | 43 +++- drivers/include/linux/pci.h | 5 +- 8 files changed, 562 insertions(+), 7 deletions(-) diff --git a/drivers/devman/acpi.c b/drivers/devman/acpi.c index 019b463944..2ff04f6103 100644 --- a/drivers/devman/acpi.c +++ b/drivers/devman/acpi.c @@ -65,6 +65,8 @@ static ACPI_HANDLE pci_root_handle; #define acpi_remap( addr ) MapIoMem((void*)(addr),4096, 0x01) +void print_pci_irqs(); + struct acpi_device *acpi_root; @@ -117,7 +119,7 @@ static bool pci_use_crs = false; #define IORESOURCE_BUS 0x00001000 -static LIST_HEAD(acpi_pci_roots); +extern struct list_head acpi_pci_roots; #define ACPI_PCI_ROOT_CLASS "pci_bridge" #define ACPI_PCI_ROOT_DEVICE_NAME "PCI Root Bridge" @@ -651,6 +653,8 @@ u32_t drvEntry(int action, char *cmdline) acpi_init_pci(acpi_root); + print_pci_irqs(); + /* ACPI_HANDLE bus_handle; ACPI_HANDLE pci_root; diff --git a/drivers/devman/acpi_bus.h b/drivers/devman/acpi_bus.h index 433d3227e3..4454186cd3 100644 --- a/drivers/devman/acpi_bus.h +++ b/drivers/devman/acpi_bus.h @@ -146,6 +146,10 @@ struct acpi_pci_root { }; +static inline void *acpi_driver_data(struct acpi_device *d) +{ + return d->driver_data; +} #define acpi_device_bid(d) ((d)->pnp.bus_id) #define acpi_device_adr(d) ((d)->pnp.bus_address) @@ -161,3 +165,6 @@ int acpi_pci_bind_root(struct acpi_device *device); struct pci_dev *acpi_get_pci_dev(ACPI_HANDLE handle); int acpi_is_root_bridge(ACPI_HANDLE handle); +int acpi_pci_link_allocate_irq(ACPI_HANDLE handle, int index, + int *triggering, int *polarity, char **name); + diff --git a/drivers/devman/pci/access.c b/drivers/devman/pci/access.c index 477c0cbeba..386370c2c8 100644 --- a/drivers/devman/pci/access.c +++ b/drivers/devman/pci/access.c @@ -77,7 +77,7 @@ int pci_bus_read_config_word (struct pci_bus *bus, u32 devfn, int pci_bus_read_config_dword (struct pci_bus *bus, u32 devfn, - int pos, u16 *value) + int pos, u32 *value) { if ( pos & 3) return PCIBIOS_BAD_REGISTER_NUMBER; diff --git a/drivers/devman/pci/pci.c b/drivers/devman/pci/pci.c index 1ca9e0f2cc..2269293688 100644 --- a/drivers/devman/pci/pci.c +++ b/drivers/devman/pci/pci.c @@ -262,6 +262,31 @@ int pci_find_ext_capability(struct pci_dev *dev, int cap) return 0; } + +/** + * pci_swizzle_interrupt_pin - swizzle INTx for device behind bridge + * @dev: the PCI device + * @pin: the INTx pin (1=INTA, 2=INTB, 3=INTD, 4=INTD) + * + * Perform INTx swizzling for a device behind one level of bridge. This is + * required by section 9.1 of the PCI-to-PCI bridge specification for devices + * behind bridges on add-in cards. For devices with ARI enabled, the slot + * number is always 0 (see the Implementation Note in section 2.2.8.1 of + * the PCI Express Base Specification, Revision 2.1) + */ +u8 pci_swizzle_interrupt_pin(struct pci_dev *dev, u8 pin) +{ + int slot; + + // if (pci_ari_enabled(dev->bus)) + // slot = 0; + // else + slot = PCI_SLOT(dev->devfn); + + return (((pin - 1) + slot) % 4) + 1; +} + + #if 0 u32 pci_probe = 0; diff --git a/drivers/devman/pci/probe.c b/drivers/devman/pci/probe.c index e910788cdf..826dbebc10 100644 --- a/drivers/devman/pci/probe.c +++ b/drivers/devman/pci/probe.c @@ -875,9 +875,7 @@ static struct pci_dev *pci_scan_device(struct pci_bus *bus, int devfn) /* some broken boards return 0 or ~0 if a slot is empty: */ if (l == 0xffffffff || l == 0x00000000 || - l == 0x0000ffff || l == 0xffff0000 || - (l & 0xffff0000) == 0xffff0000 || - (l & 0x0000ffff) == 0x0000ffff ) + l == 0x0000ffff || l == 0xffff0000) return NULL; /* Configuration request Retry Status */ diff --git a/drivers/devman/pci_irq.c b/drivers/devman/pci_irq.c index f4beba9a1b..96545c03c5 100644 --- a/drivers/devman/pci_irq.c +++ b/drivers/devman/pci_irq.c @@ -29,6 +29,32 @@ static inline char pin_name(int pin) } +/* -------------------------------------------------------------------------- + PCI IRQ Routing Table (PRT) Support + -------------------------------------------------------------------------- */ + +static struct acpi_prt_entry *acpi_pci_irq_find_prt_entry(struct pci_dev *dev, + int pin) +{ + struct acpi_prt_entry *entry; + int segment = pci_domain_nr(dev->bus); + int bus = dev->bus->number; + int device = PCI_SLOT(dev->devfn); + + spin_lock(&acpi_prt_lock); + list_for_each_entry(entry, &acpi_prt_list, list) { + if ((segment == entry->id.Segment) + && (bus == entry->id.Bus) + && (device == entry->id.Device) + && (pin == entry->pin)) { + spin_unlock(&acpi_prt_lock); + return entry; + } + } + spin_unlock(&acpi_prt_lock); + return NULL; +} + static int acpi_pci_irq_add_entry(ACPI_HANDLE handle, struct pci_bus *bus, struct acpi_pci_routing_table *prt) { @@ -129,3 +155,454 @@ int acpi_pci_irq_add_prt(ACPI_HANDLE handle, struct pci_bus *bus) return 0; } +static struct acpi_prt_entry *acpi_pci_irq_lookup(struct pci_dev *dev, int pin) +{ + struct acpi_prt_entry *entry; + struct pci_dev *bridge; + u8 bridge_pin, orig_pin = pin; + + entry = acpi_pci_irq_find_prt_entry(dev, pin); + if (entry) { + ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Found %s[%c] _PRT entry\n", + pci_name(dev), pin_name(pin))); + return entry; + } + + /* + * Attempt to derive an IRQ for this device from a parent bridge's + * PCI interrupt routing entry (eg. yenta bridge and add-in card bridge). + */ + bridge = dev->bus->self; + while (bridge) + { + pin = pci_swizzle_interrupt_pin(dev, pin); + + if ((bridge->class >> 8) == PCI_CLASS_BRIDGE_CARDBUS) { + /* PC card has the same IRQ as its cardbridge */ + bridge_pin = bridge->pin; + if (!bridge_pin) { + ACPI_DEBUG_PRINT((ACPI_DB_INFO, + "No interrupt pin configured for device %s\n", + pci_name(bridge))); + return NULL; + } + pin = bridge_pin; + } + + entry = acpi_pci_irq_find_prt_entry(bridge, pin); + if (entry) { + ACPI_DEBUG_PRINT((ACPI_DB_INFO, + "Derived GSI for %s INT %c from %s\n", + pci_name(dev), pin_name(orig_pin), + pci_name(bridge))); + return entry; + } + + dev = bridge; + bridge = dev->bus->self; + } + + dbgprintf("can't derive routing for PCI INT %c\n", + pin_name(orig_pin)); + return NULL; +} + + +int acpi_get_irq(struct pci_dev *dev) +{ + struct acpi_prt_entry *entry; + int gsi = -1; + u8 pin; + + int triggering = ACPI_LEVEL_SENSITIVE; + int polarity = ACPI_ACTIVE_LOW; + + char *link = NULL; + char link_desc[16]; + int rc; + + pin = dev->pin; + if (!pin) { + dbgprintf(("No interrupt pin configured for device %s\n", + pci_name(dev))); + return 0; + } + + entry = acpi_pci_irq_lookup(dev, pin); + if (!entry) { + /* + * IDE legacy mode controller IRQs are magic. Why do compat + * extensions always make such a nasty mess. + */ + if (dev->class >> 8 == PCI_CLASS_STORAGE_IDE && + (dev->class & 0x05) == 0) + return 0; + } + + if (entry) + { + if (entry->link) + { + gsi = acpi_pci_link_allocate_irq(entry->link, + entry->index, + &triggering, &polarity, + &link); +// dbgprintf("link not implemen\n"); + } + else + gsi = entry->index; + } else + gsi = -1; + +#if 0 + + /* + * No IRQ known to the ACPI subsystem - maybe the BIOS / + * driver reported one, then use it. Exit in any case. + */ + if (gsi < 0) { + u32 dev_gsi; + dev_warn(&dev->dev, "PCI INT %c: no GSI", pin_name(pin)); + /* Interrupt Line values above 0xF are forbidden */ + if (dev->irq > 0 && (dev->irq <= 0xF) && + (acpi_isa_irq_to_gsi(dev->irq, &dev_gsi) == 0)) { + printk(" - using ISA IRQ %d\n", dev->irq); + acpi_register_gsi(&dev->dev, dev_gsi, + ACPI_LEVEL_SENSITIVE, + ACPI_ACTIVE_LOW); + return 0; + } else { + printk("\n"); + return 0; + } + } + + rc = acpi_register_gsi(&dev->dev, gsi, triggering, polarity); + if (rc < 0) { + dev_warn(&dev->dev, "PCI INT %c: failed to register GSI\n", + pin_name(pin)); + return rc; + } + dev->irq = rc; + + if (link) + snprintf(link_desc, sizeof(link_desc), " -> Link[%s]", link); + else + link_desc[0] = '\0'; + + dev_info(&dev->dev, "PCI INT %c%s -> GSI %u (%s, %s) -> IRQ %d\n", + pin_name(pin), link_desc, gsi, + (triggering == ACPI_LEVEL_SENSITIVE) ? "level" : "edge", + (polarity == ACPI_ACTIVE_LOW) ? "low" : "high", dev->irq); +#endif + + return gsi; +} + + +#define ACPI_PCI_LINK_MAX_POSSIBLE 16 + +/* + * If a link is initialized, we never change its active and initialized + * later even the link is disable. Instead, we just repick the active irq + */ +struct acpi_pci_link_irq { + u8 active; /* Current IRQ */ + u8 triggering; /* All IRQs */ + u8 polarity; /* All IRQs */ + u8 resource_type; + u8 possible_count; + u8 possible[ACPI_PCI_LINK_MAX_POSSIBLE]; + u8 initialized:1; + u8 reserved:7; +}; + +struct acpi_pci_link { + struct list_head list; + struct acpi_device *device; + struct acpi_pci_link_irq irq; + int refcnt; +}; + +static LIST_HEAD(acpi_link_list); +static DEFINE_MUTEX(acpi_link_lock); + + +static int acpi_pci_link_set(struct acpi_pci_link *link, int irq) +{ + int result; + ACPI_STATUS status; + struct { + struct acpi_resource res; + struct acpi_resource end; + } *resource; + + ACPI_BUFFER buffer = { 0, NULL }; + + if (!irq) + return -EINVAL; + + resource = kzalloc(sizeof(*resource) + 1, GFP_KERNEL); + if (!resource) + return -ENOMEM; + + buffer.Length = sizeof(*resource) + 1; + buffer.Pointer = resource; + + switch (link->irq.resource_type) { + case ACPI_RESOURCE_TYPE_IRQ: + resource->res.Type = ACPI_RESOURCE_TYPE_IRQ; + resource->res.Length = sizeof(struct acpi_resource); + resource->res.Data.Irq.Triggering = link->irq.triggering; + resource->res.Data.Irq.Polarity = + link->irq.polarity; + if (link->irq.triggering == ACPI_EDGE_SENSITIVE) + resource->res.Data.Irq.Sharable = + ACPI_EXCLUSIVE; + else + resource->res.Data.Irq.Sharable = ACPI_SHARED; + resource->res.Data.Irq.InterruptCount = 1; + resource->res.Data.Irq.Interrupts[0] = irq; + break; + + case ACPI_RESOURCE_TYPE_EXTENDED_IRQ: + resource->res.Type = ACPI_RESOURCE_TYPE_EXTENDED_IRQ; + resource->res.Length = sizeof(struct acpi_resource); + resource->res.Data.ExtendedIrq.ProducerConsumer = + ACPI_CONSUMER; + resource->res.Data.ExtendedIrq.Triggering = + link->irq.triggering; + resource->res.Data.ExtendedIrq.Polarity = + link->irq.polarity; + if (link->irq.triggering == ACPI_EDGE_SENSITIVE) + resource->res.Data.Irq.Sharable = + ACPI_EXCLUSIVE; + else + resource->res.Data.Irq.Sharable = ACPI_SHARED; + resource->res.Data.ExtendedIrq.InterruptCount = 1; + resource->res.Data.ExtendedIrq.Interrupts[0] = irq; + /* ignore resource_source, it's optional */ + break; + default: + printk(KERN_ERR PREFIX "Invalid Resource_type %d\n", link->irq.resource_type); + result = -EINVAL; + goto end; + + } + resource->end.Type = ACPI_RESOURCE_TYPE_END_TAG; + +#if 0 + /* Attempt to set the resource */ + status = acpi_set_current_resources(link->device->handle, &buffer); + + /* check for total failure */ + if (ACPI_FAILURE(status)) { + dbgprintf("%s failure Evaluating _SRS", __FUNCTION__); + result = -ENODEV; + goto end; + } + + /* Query _STA, set device->status */ + result = acpi_bus_get_status(link->device); + if (result) { + printk(KERN_ERR PREFIX "Unable to read status\n"); + goto end; + } + if (!link->device->status.enabled) { + printk(KERN_WARNING PREFIX + "%s [%s] disabled and referenced, BIOS bug\n", + acpi_device_name(link->device), + acpi_device_bid(link->device)); + } + + /* Query _CRS, set link->irq.active */ + result = acpi_pci_link_get_current(link); + if (result) { + goto end; + } + + /* + * Is current setting not what we set? + * set link->irq.active + */ + if (link->irq.active != irq) { + /* + * policy: when _CRS doesn't return what we just _SRS + * assume _SRS worked and override _CRS value. + */ + printk(KERN_WARNING PREFIX + "%s [%s] BIOS reported IRQ %d, using IRQ %d\n", + acpi_device_name(link->device), + acpi_device_bid(link->device), link->irq.active, irq); + link->irq.active = irq; + } +#endif + + ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Set IRQ %d\n", link->irq.active)); + +end: + kfree(resource); + return result; +} + + + +#define ACPI_MAX_IRQS 256 +#define ACPI_MAX_ISA_IRQ 16 + +#define PIRQ_PENALTY_PCI_AVAILABLE (0) +#define PIRQ_PENALTY_PCI_POSSIBLE (16*16) +#define PIRQ_PENALTY_PCI_USING (16*16*16) +#define PIRQ_PENALTY_ISA_TYPICAL (16*16*16*16) +#define PIRQ_PENALTY_ISA_USED (16*16*16*16*16) +#define PIRQ_PENALTY_ISA_ALWAYS (16*16*16*16*16*16) + +static int acpi_irq_penalty[ACPI_MAX_IRQS] = { + PIRQ_PENALTY_ISA_ALWAYS, /* IRQ0 timer */ + PIRQ_PENALTY_ISA_ALWAYS, /* IRQ1 keyboard */ + PIRQ_PENALTY_ISA_ALWAYS, /* IRQ2 cascade */ + PIRQ_PENALTY_ISA_TYPICAL, /* IRQ3 serial */ + PIRQ_PENALTY_ISA_TYPICAL, /* IRQ4 serial */ + PIRQ_PENALTY_ISA_TYPICAL, /* IRQ5 sometimes SoundBlaster */ + PIRQ_PENALTY_ISA_TYPICAL, /* IRQ6 */ + PIRQ_PENALTY_ISA_TYPICAL, /* IRQ7 parallel, spurious */ + PIRQ_PENALTY_ISA_TYPICAL, /* IRQ8 rtc, sometimes */ + PIRQ_PENALTY_PCI_AVAILABLE, /* IRQ9 PCI, often acpi */ + PIRQ_PENALTY_PCI_AVAILABLE, /* IRQ10 PCI */ + PIRQ_PENALTY_PCI_AVAILABLE, /* IRQ11 PCI */ + PIRQ_PENALTY_ISA_USED, /* IRQ12 mouse */ + PIRQ_PENALTY_ISA_USED, /* IRQ13 fpe, sometimes */ + PIRQ_PENALTY_ISA_USED, /* IRQ14 ide0 */ + PIRQ_PENALTY_ISA_USED, /* IRQ15 ide1 */ + /* >IRQ15 */ +}; + + + + +static int acpi_irq_balance = 0; + +static int acpi_pci_link_allocate(struct acpi_pci_link *link) +{ + int irq; + int i; + + if (link->irq.initialized) { + if (link->refcnt == 0) + /* This means the link is disabled but initialized */ + acpi_pci_link_set(link, link->irq.active); + return 0; + } + + /* + * search for active IRQ in list of possible IRQs. + */ + for (i = 0; i < link->irq.possible_count; ++i) { + if (link->irq.active == link->irq.possible[i]) + break; + } + /* + * forget active IRQ that is not in possible list + */ + if (i == link->irq.possible_count) { + printk(KERN_WARNING PREFIX "_CRS %d not found" + " in _PRS\n", link->irq.active); + link->irq.active = 0; + } + + /* + * if active found, use it; else pick entry from end of possible list. + */ + if (link->irq.active) + irq = link->irq.active; + else + irq = link->irq.possible[link->irq.possible_count - 1]; + + if (acpi_irq_balance || !link->irq.active) { + /* + * Select the best IRQ. This is done in reverse to promote + * the use of IRQs 9, 10, 11, and >15. + */ + for (i = (link->irq.possible_count - 1); i >= 0; i--) { + if (acpi_irq_penalty[irq] > + acpi_irq_penalty[link->irq.possible[i]]) + irq = link->irq.possible[i]; + } + } + + /* Attempt to enable the link device at this IRQ. */ + if (acpi_pci_link_set(link, irq)) { + printk(KERN_ERR PREFIX "Unable to set IRQ for %s [%s]. " + "Try pci=noacpi or acpi=off\n", + acpi_device_name(link->device), + acpi_device_bid(link->device)); + return -ENODEV; + } else { + acpi_irq_penalty[link->irq.active] += PIRQ_PENALTY_PCI_USING; + printk(KERN_WARNING PREFIX "%s [%s] enabled at IRQ %d\n", + acpi_device_name(link->device), + acpi_device_bid(link->device), link->irq.active); + } + + link->irq.initialized = 1; + return 0; +} + + +/* + * acpi_pci_link_allocate_irq + * success: return IRQ >= 0 + * failure: return -1 + */ +int acpi_pci_link_allocate_irq(ACPI_HANDLE handle, int index, + int *triggering, int *polarity, char **name) +{ + int result; + struct acpi_device *device; + struct acpi_pci_link *link; + + result = acpi_bus_get_device(handle, &device); + if (result) { + printk(KERN_ERR PREFIX "Invalid link device\n"); + return -1; + } + + link = acpi_driver_data(device); + if (!link) { + printk(KERN_ERR PREFIX "Invalid link context\n"); + return -1; + } + + /* TBD: Support multiple index (IRQ) entries per Link Device */ + if (index) { + printk(KERN_ERR PREFIX "Invalid index %d\n", index); + return -1; + } + + mutex_lock(&acpi_link_lock); + if (acpi_pci_link_allocate(link)) { + mutex_unlock(&acpi_link_lock); + return -1; + } + + if (!link->irq.active) { + mutex_unlock(&acpi_link_lock); + printk(KERN_ERR PREFIX "Link active IRQ is 0!\n"); + return -1; + } + link->refcnt++; + mutex_unlock(&acpi_link_lock); + + if (triggering) + *triggering = link->irq.triggering; + if (polarity) + *polarity = link->irq.polarity; + if (name) + *name = acpi_device_bid(link->device); + ACPI_DEBUG_PRINT((ACPI_DB_INFO, + "Link %s is referenced\n", + acpi_device_bid(link->device))); + return (link->irq.active); +} + + diff --git a/drivers/devman/pci_root.c b/drivers/devman/pci_root.c index 85b3aa38b1..e998017b4e 100644 --- a/drivers/devman/pci_root.c +++ b/drivers/devman/pci_root.c @@ -22,7 +22,7 @@ static const struct acpi_device_ids root_device_ids[] = { {"", 0}, }; -static LIST_HEAD(acpi_pci_roots); +LIST_HEAD(acpi_pci_roots); /** @@ -147,3 +147,44 @@ out: return pdev; } + +static void print_bus_irqs(struct pci_bus *bus) +{ + struct pci_dev *dev; + + list_for_each_entry(dev, &bus->devices, bus_list) + { + dbgprintf("PCI_%x_%x bus:%d devfn: %x bios irq %d acpi irq %d\n", + dev->vendor, dev->device, dev->busnr, dev->devfn, + dev->irq, acpi_get_irq(dev)); + }; +} + +void print_pci_irqs() +{ + struct acpi_pci_root *root; + + ENTER(); + list_for_each_entry(root, &acpi_pci_roots, node) + { + struct pci_bus *pbus, *tbus; + struct pci_dev *dev; + + pbus = root->bus; + + list_for_each_entry(dev, &pbus->devices, bus_list) + { + dbgprintf("PCI_%x_%x bus:%d devfn: %x bios irq %d acpi irq %d\n", + dev->vendor, dev->device, dev->busnr, dev->devfn, + dev->irq, acpi_get_irq(dev)); + }; + + list_for_each_entry(tbus, &pbus->children, node) + { + print_bus_irqs(tbus); + }; + } + LEAVE(); +}; + + diff --git a/drivers/include/linux/pci.h b/drivers/include/linux/pci.h index 17ce37f2c5..839b4f300b 100644 --- a/drivers/include/linux/pci.h +++ b/drivers/include/linux/pci.h @@ -575,6 +575,7 @@ int pci_find_next_ht_capability(struct pci_dev *dev, int pos, int ht_cap); struct pci_bus * pci_find_next_bus(const struct pci_bus *from); unsigned int pci_scan_child_bus(struct pci_bus *bus); void pcibios_fixup_bus(struct pci_bus *b); +u8 pci_swizzle_interrupt_pin(struct pci_dev *dev, u8 pin); static inline bool pci_is_root_bus(struct pci_bus *pbus) @@ -652,7 +653,9 @@ static inline int pci_ats_enabled(struct pci_dev *dev) return 0; } -#define pci_name(x) "radeon" +int acpi_get_irq(struct pci_dev *dev); + +#define pci_name(x) "" #endif //__PCI__H__