@@ -4190,6 +4190,14 @@
bridge windows. This is the default on modern
hardware. If you need to use this, please report
a bug to <linux-pci@vger.kernel.org>.
+ prtlast If the _PRT ACPI method returns duplicate
+ IRQ routing entries, use the last matching entry
+ for a given device. If the platform may be
+ affected by this problem, an error message is
+ printed to dmesg - this parameter is
+ ineffective otherwise. If you need to use this,
+ please report a bug to
+ <linux-pci@vger.kernel.org>.
routeirq Do IRQ routing for all PCI devices.
This is normally done in pci_enable_device(),
so this option is a temporary workaround
@@ -196,12 +196,73 @@ static int acpi_pci_irq_check_entry(acpi_handle handle, struct pci_dev *dev,
return 0;
}
+extern bool pci_prtlast;
+
+static const struct dmi_system_id pci_prtlast_dmi[] = {
+ {
+ .ident = "Dell Latitude E6500",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
+ DMI_MATCH(DMI_PRODUCT_NAME, "Latitude E6500"),
+ },
+ },
+ { }
+};
+
+static bool acpi_pci_prt_use_last(struct acpi_prt_entry *curr,
+ const char *current_source,
+ const char *previous_match_source,
+ int previous_match_index)
+{
+ bool ret;
+ const struct dmi_system_id *id;
+ const int msg_bufsize = 512;
+ char *msg = kmalloc(msg_bufsize, GFP_KERNEL);
+
+ if (!msg)
+ return false;
+
+ snprintf(msg, msg_bufsize,
+ FW_BUG
+ "ACPI _PRT returned duplicate IRQ routing entries for PCI device "
+ "%04x:%02x:%02x[INT%c]: %s[%d] and %s[%d]. ",
+ curr->id.segment, curr->id.bus, curr->id.device,
+ pin_name(curr->pin),
+ previous_match_source, previous_match_index,
+ current_source, curr->index);
+
+ id = dmi_first_match(pci_prtlast_dmi);
+
+ if (id) {
+ pr_warn("%s%s detected, using last entry.\n",
+ msg, id->ident);
+
+ ret = true;
+ } else if (pci_prtlast) {
+ pr_err(
+"%sUsing last entry, as directed on the command line. If this helps, report a bug.\n",
+ msg);
+
+ ret = true;
+ } else {
+ pr_err("%sIf necessary, use \"pci=prtlast\" and report a bug.\n",
+ msg);
+
+ ret = false;
+ }
+
+ kfree(msg);
+ return ret;
+}
+
static int acpi_pci_irq_find_prt_entry(struct pci_dev *dev,
- int pin, struct acpi_prt_entry **entry_ptr)
+ int pin, struct acpi_prt_entry **entry_ptr_out)
{
acpi_status status;
struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
struct acpi_pci_routing_table *entry;
+ struct acpi_prt_entry *match = NULL;
+ const char *match_source = NULL;
acpi_handle handle = NULL;
if (dev->bus->bridge)
@@ -219,13 +280,33 @@ static int acpi_pci_irq_find_prt_entry(struct pci_dev *dev,
entry = buffer.pointer;
while (entry && (entry->length > 0)) {
- if (!acpi_pci_irq_check_entry(handle, dev, pin,
- entry, entry_ptr))
- break;
+ struct acpi_prt_entry *curr;
+
+ if (!acpi_pci_irq_check_entry(handle, dev, pin, entry, &curr)) {
+ if (!match) {
+ // first match
+ match = curr;
+ match_source = entry->source;
+ } else if (!acpi_pci_prt_use_last(curr,
+ entry->source,
+ match_source,
+ match->index)) {
+ // duplicates found, use first entry
+ kfree(curr);
+ } else {
+ // duplicates found, use last entry
+ kfree(match);
+ match = curr;
+ match_source = entry->source;
+ }
+ }
+
entry = (struct acpi_pci_routing_table *)
((unsigned long)entry + entry->length);
}
+ *entry_ptr_out = match;
+
kfree(buffer.pointer);
return 0;
}
@@ -155,6 +155,11 @@ static bool pci_bridge_d3_disable;
/* Force bridge_d3 for all PCIe ports */
static bool pci_bridge_d3_force;
+#ifdef CONFIG_ACPI
+/* Use the last matching entry from the table returned by the _PRT ACPI method. */
+bool pci_prtlast;
+#endif
+
static int __init pcie_port_pm_setup(char *str)
{
if (!strcmp(str, "off"))
@@ -6896,6 +6901,10 @@ static int __init pci_setup(char *str)
pci_add_flags(PCI_SCAN_ALL_PCIE_DEVS);
} else if (!strncmp(str, "disable_acs_redir=", 18)) {
disable_acs_redir_param = str + 18;
+#ifdef CONFIG_ACPI
+ } else if (!strncmp(str, "prtlast", 7)) {
+ pci_prtlast = true;
+#endif
} else {
pr_err("PCI: Unknown option `%s'\n", str);
}
On some platforms, the ACPI _PRT function returns duplicate interrupt routing entries. Linux uses the first matching entry, but sometimes the second matching entry contains the correct interrupt vector. This happens on a Dell Latitude E6500 laptop with the i2c-i801 Intel SMBus controller. This controller was nonfunctional unless its interrupt usage was disabled (using the "disable_features=0x10" module parameter). After investigation, it turned out that the driver was using an incorrect interrupt vector: in lspci output for this device there was: Interrupt: pin B routed to IRQ 19 but after running i2cdetect (without using any i2c-i801 module parameters) the following was logged to dmesg: [...] [ 132.248657] i801_smbus 0000:00:1f.3: Timeout waiting for interrupt! [ 132.248669] i801_smbus 0000:00:1f.3: Transaction timeout [ 132.452649] i801_smbus 0000:00:1f.3: Timeout waiting for interrupt! [ 132.452662] i801_smbus 0000:00:1f.3: Transaction timeout [ 132.467682] irq 17: nobody cared (try booting with the "irqpoll" option) The existence of duplicate entries returned from _PRT was confirmed in the ACPI DSTD table, which contains: Name (API0, Package (0x1D) { Package (0x04) { 0x001FFFFF, 0x01, 0x00, 0x13 }, Package (0x04) { 0x001FFFFF, 0x00, 0x00, 0x10 }, Package (0x04) { 0x001FFFFF, 0x01, 0x00, 0x11 }, [...] }) Method (_PRT, 0, NotSerialized) // _PRT: PCI Routing Table { Local0 = API0 /* \_SB_.PCI0.API0 */ If (!GPIC) { Local0 = PIC0 /* \_SB_.PCI0.PIC0 */ } Return (Local0) } Linux used the first matching entry, which was incorrect. In order not to disrupt existing systems, use the first matching entry unless the pci=prtlast kernel parameter is used or a Dell Latitude E6500 laptop is detected. Disclaimer: there is nothing really interesting connected to the SMBus controller on this laptop, but this change may help other systems. Signed-off-by: Mateusz Jończyk <mat.jonczyk@o2.pl> Cc: Jonathan Corbet <corbet@lwn.net> Cc: Bjorn Helgaas <bhelgaas@google.com> Cc: "Rafael J. Wysocki" <rafael@kernel.org> Cc: Len Brown <lenb@kernel.org> Cc: Borislav Petkov <bp@suse.de> --- .../admin-guide/kernel-parameters.txt | 8 ++ drivers/acpi/pci_irq.c | 89 ++++++++++++++++++- drivers/pci/pci.c | 9 ++ 3 files changed, 102 insertions(+), 4 deletions(-) base-commit: 7e18e42e4b280c85b76967a9106a13ca61c16179