2022-03-31 09:01:53

by Maciej W. Rozycki

[permalink] [raw]
Subject: [PATCH v4 0/5] x86/PCI: Improve $PIR and add $IRT PIRQ routing support

Hi,

This is an updated patch series with a fix applied to what is now 3/5 for
a bug that was reported by Dmitry (thanks!) having triggered with his
x86-64 setup and then nailed down by Linus (also thanks!). Additionally
as suggested by Linus a fix has been added, for a preexisting problem with
the routing table not being verified to stay whole within the BIOS memory
range, that has become 1/5, expanding the whole series from 4 to 5 changes
now. In the course of that addition a couple of coding style issues have
been consumed, shrinking what has now become 5/5. The cover letter has
been updated accordingly throughout.

First 1/5 handles $PIR PIRQ routing tables that lack router device
information, fixing the inability to route interrupts with a system using
the SiS85C497 ISA bridge.

Then 2/5 adds support for the $IRT PIRQ routing table format invented by
AMI before Microsoft has come up with its own $PIR format. These formats
are very similar to each other, but the $IRT format does not provide for
router device information, so this change relies on 1/5. It has turned
out needed to route interrupts with a system using the ALi M1487 ISA Bus
Controller device, discussed earlier on in a discussion thread around:
<https://lore.kernel.org/linux-pci/[email protected]/>. This
change has been verified with an artificially created $IRT table.

Then 3/5 adds a range check for

Then 3/4 corrects our link value interpretation for said M1487 device
according to Nikolai's findings with his system reported here:
<https://lore.kernel.org/linux-pci/[email protected]/>.

Finally 4/4 corrects a couple of coding style issues around though not
immediately within code changed by 2/4 so as to make the style consistent.

See individual change descriptions for further details.

Credit to Michal (cc-ed) for helping me chase documentation for the
$IRT table format.

Please apply.

Maciej


2022-03-31 09:35:27

by Maciej W. Rozycki

[permalink] [raw]
Subject: [PATCH v4 3/5] x86/PCI: Add $IRT PIRQ routing table support

Handle the $IRT PCI IRQ Routing Table format used by AMI for its BCP
(BIOS Configuration Program) external tool meant for tweaking BIOS
structures without the need to rebuild it from sources[1].

The $IRT format has been invented by AMI before Microsoft has come up
with its $PIR format and a $IRT table is therefore there in some systems
that lack a $PIR table, such as the DataExpert EXP8449 mainboard based
on the ALi FinALi 486 chipset (M1489/M1487), which predates DMI 2.0 and
cannot therefore be easily identified at run time.

Unlike with the $PIR format there is no alignment guarantee as to the
placement of the $IRT table, so scan the whole BIOS area bytewise.

Credit to Michal Necasek for helping me chase documentation for the
format.

References:

[1] "What is BCP? - AMI", <https://www.ami.com/what-is-bcp/>

Signed-off-by: Maciej W. Rozycki <[email protected]>
Cc: Michal Necasek <[email protected]>
---
Changes from v3:

- Correct the BIOS memory scan such as to verify that the PCI IRQ Routing
Table header as well as individual slot entries are all wholly contained
within the BIOS memory area.

- Following commit 5224f7909617 ("treewide: Replace zero-length arrays
with flexible-array members") also make `slots' in `irt_routing_table' a
flexible-array member.

New change in v3.
---
arch/x86/include/asm/pci_x86.h | 9 ++++
arch/x86/pci/irq.c | 76 +++++++++++++++++++++++++++++++++++++++++
2 files changed, 85 insertions(+)

linux-x86-pirq-irt.diff
Index: linux-macro/arch/x86/include/asm/pci_x86.h
===================================================================
--- linux-macro.orig/arch/x86/include/asm/pci_x86.h
+++ linux-macro/arch/x86/include/asm/pci_x86.h
@@ -93,6 +93,15 @@ struct irq_routing_table {
struct irq_info slots[];
} __attribute__((packed));

+struct irt_routing_table {
+ u32 signature; /* IRT_SIGNATURE should be here */
+ u8 size; /* Number of entries provided */
+ u8 used; /* Number of entries actually used */
+ u16 exclusive_irqs; /* IRQs devoted exclusively to
+ PCI usage */
+ struct irq_info slots[];
+} __attribute__((packed));
+
extern unsigned int pcibios_irq_mask;

extern raw_spinlock_t pci_config_lock;
Index: linux-macro/arch/x86/pci/irq.c
===================================================================
--- linux-macro.orig/arch/x86/pci/irq.c
+++ linux-macro/arch/x86/pci/irq.c
@@ -25,6 +25,8 @@
#define PIRQ_SIGNATURE (('$' << 0) + ('P' << 8) + ('I' << 16) + ('R' << 24))
#define PIRQ_VERSION 0x0100

+#define IRT_SIGNATURE (('$' << 0) + ('I' << 8) + ('R' << 16) + ('T' << 24))
+
static int broken_hp_bios_irq9;
static int acer_tm360_irqrouting;

@@ -93,7 +95,74 @@ static inline struct irq_routing_table *
return NULL;
}

+/*
+ * Handle the $IRT PCI IRQ Routing Table format used by AMI for its BCP
+ * (BIOS Configuration Program) external tool meant for tweaking BIOS
+ * structures without the need to rebuild it from sources. The $IRT
+ * format has been invented by AMI before Microsoft has come up with its
+ * $PIR format and a $IRT table is therefore there in some systems that
+ * lack a $PIR table.
+ *
+ * It uses the same PCI BIOS 2.1 format for interrupt routing entries
+ * themselves but has a different simpler header prepended instead,
+ * occupying 8 bytes, where a `$IRT' signature is followed by one byte
+ * specifying the total number of interrupt routing entries allocated in
+ * the table, then one byte specifying the actual number of entries used
+ * (which the BCP tool can take advantage of when modifying the table),
+ * and finally a 16-bit word giving the IRQs devoted exclusively to PCI.
+ * Unlike with the $PIR table there is no alignment guarantee.
+ *
+ * Given the similarity of the two formats the $IRT one is trivial to
+ * convert to the $PIR one, which we do here, except that obviously we
+ * have no information as to the router device to use, but we can handle
+ * it by matching PCI device IDs actually seen on the bus against ones
+ * that our individual routers recognise.
+ *
+ * Reportedly there is another $IRT table format where a 16-bit word
+ * follows the header instead that points to interrupt routing entries
+ * in a $PIR table provided elsewhere. In that case this code will not
+ * be reached though as the $PIR table will have been chosen instead.
+ */
+static inline struct irq_routing_table *pirq_convert_irt_table(u8 *addr,
+ u8 *limit)
+{
+ struct irt_routing_table *ir;
+ struct irq_routing_table *rt;
+ u16 size;
+ u8 sum;
+ int i;
+
+ ir = (struct irt_routing_table *)addr;
+ if (ir->signature != IRT_SIGNATURE || !ir->used || ir->size < ir->used)
+ return NULL;
+
+ size = sizeof(*ir) + ir->used * sizeof(ir->slots[0]);
+ if (size > limit - addr)
+ return NULL;
+
+ DBG(KERN_DEBUG "PCI: $IRT Interrupt Routing Table found at 0x%lx\n",
+ __pa(ir));
+
+ size = sizeof(*rt) + ir->used * sizeof(rt->slots[0]);
+ rt = kzalloc(size, GFP_KERNEL);
+ if (!rt)
+ return NULL;
+
+ rt->signature = PIRQ_SIGNATURE;
+ rt->version = PIRQ_VERSION;
+ rt->size = size;
+ rt->exclusive_irqs = ir->exclusive_irqs;
+ for (i = 0; i < ir->used; i++)
+ rt->slots[i] = ir->slots[i];
+
+ addr = (u8 *)rt;
+ sum = 0;
+ for (i = 0; i < size; i++)
+ sum += addr[i];
+ rt->checksum = -sum;

+ return rt;
+}

/*
* Search 0xf0000 -- 0xfffff for the PCI IRQ Routing Table.
@@ -120,6 +189,13 @@ static struct irq_routing_table * __init
if (rt)
return rt;
}
+ for (addr = bios_start;
+ addr < bios_end - sizeof(struct irt_routing_table);
+ addr++) {
+ rt = pirq_convert_irt_table(addr, bios_end);
+ if (rt)
+ return rt;
+ }
return NULL;
}

2022-03-31 09:50:49

by Maciej W. Rozycki

[permalink] [raw]
Subject: [PATCH v4 2/5] x86/PCI: Handle PIRQ routing tables with no router device given

PIRQ routing tables provided by the PCI BIOS usually specify the PCI
vendor:device ID as well as the bus address of the device implementing
the PIRQ router, e.g.:

PCI: Interrupt Routing Table found at 0xc00fde10
[...]
PCI: Attempting to find IRQ router for [8086:7000]
pci 0000:00:07.0: PIIX/ICH IRQ router [8086:7000]

however in some cases they do not, in which case we fail to match the
router handler, e.g.:

PCI: Interrupt Routing Table found at 0xc00fdae0
[...]
PCI: Attempting to find IRQ router for [0000:0000]
PCI: Interrupt router not found at 00:00

This is because we always match the vendor:device ID and the bus address
literally, even if they are all zeros.

Handle this case then and iterate over all PCI devices until we find a
matching router handler if the vendor ID given by the routing table is
the invalid value of zero:

PCI: Attempting to find IRQ router for [0000:0000]
PCI: Trying IRQ router for [1039:0496]
pci 0000:00:05.0: SiS85C497 IRQ router [1039:0496]

Signed-off-by: Maciej W. Rozycki <[email protected]>
Tested-by: Nikolai Zhubr <[email protected]>
---
No change from v3.

Changes from v2:

- Document new output in the change description.

- Add Nikolai's Tested-by annotation.

Changes from v1:

- Preinitialise `dev' in `pirq_find_router' for `for_each_pci_dev'.

- Avoid calling `pirq_try_router' with null `dev'.
---
arch/x86/pci/irq.c | 64 ++++++++++++++++++++++++++++++++++++-----------------
1 file changed, 44 insertions(+), 20 deletions(-)

linux-x86-pirq-router-nodev.diff
Index: linux-macro/arch/x86/pci/irq.c
===================================================================
--- linux-macro.orig/arch/x86/pci/irq.c
+++ linux-macro/arch/x86/pci/irq.c
@@ -1182,10 +1182,32 @@ static struct pci_dev *pirq_router_dev;
* chipset" ?
*/

+static bool __init pirq_try_router(struct irq_router *r,
+ struct irq_routing_table *rt,
+ struct pci_dev *dev)
+{
+ struct irq_router_handler *h;
+
+ DBG(KERN_DEBUG "PCI: Trying IRQ router for [%04x:%04x]\n",
+ dev->vendor, dev->device);
+
+ for (h = pirq_routers; h->vendor; h++) {
+ /* First look for a router match */
+ if (rt->rtr_vendor == h->vendor &&
+ h->probe(r, dev, rt->rtr_device))
+ return true;
+ /* Fall back to a device match */
+ if (dev->vendor == h->vendor &&
+ h->probe(r, dev, dev->device))
+ return true;
+ }
+ return false;
+}
+
static void __init pirq_find_router(struct irq_router *r)
{
struct irq_routing_table *rt = pirq_table;
- struct irq_router_handler *h;
+ struct pci_dev *dev;

#ifdef CONFIG_PCI_BIOS
if (!rt->signature) {
@@ -1204,27 +1226,29 @@ static void __init pirq_find_router(stru
DBG(KERN_DEBUG "PCI: Attempting to find IRQ router for [%04x:%04x]\n",
rt->rtr_vendor, rt->rtr_device);

- pirq_router_dev = pci_get_domain_bus_and_slot(0, rt->rtr_bus,
- rt->rtr_devfn);
- if (!pirq_router_dev) {
- DBG(KERN_DEBUG "PCI: Interrupt router not found at "
- "%02x:%02x\n", rt->rtr_bus, rt->rtr_devfn);
- return;
+ /* Use any vendor:device provided by the routing table or try all. */
+ if (rt->rtr_vendor) {
+ dev = pci_get_domain_bus_and_slot(0, rt->rtr_bus,
+ rt->rtr_devfn);
+ if (dev && pirq_try_router(r, rt, dev))
+ pirq_router_dev = dev;
+ } else {
+ dev = NULL;
+ for_each_pci_dev(dev) {
+ if (pirq_try_router(r, rt, dev)) {
+ pirq_router_dev = dev;
+ break;
+ }
+ }
}

- for (h = pirq_routers; h->vendor; h++) {
- /* First look for a router match */
- if (rt->rtr_vendor == h->vendor &&
- h->probe(r, pirq_router_dev, rt->rtr_device))
- break;
- /* Fall back to a device match */
- if (pirq_router_dev->vendor == h->vendor &&
- h->probe(r, pirq_router_dev, pirq_router_dev->device))
- break;
- }
- dev_info(&pirq_router_dev->dev, "%s IRQ router [%04x:%04x]\n",
- pirq_router.name,
- pirq_router_dev->vendor, pirq_router_dev->device);
+ if (pirq_router_dev)
+ dev_info(&pirq_router_dev->dev, "%s IRQ router [%04x:%04x]\n",
+ pirq_router.name,
+ pirq_router_dev->vendor, pirq_router_dev->device);
+ else
+ DBG(KERN_DEBUG "PCI: Interrupt router not found at "
+ "%02x:%02x\n", rt->rtr_bus, rt->rtr_devfn);

/* The device remains referenced for the kernel lifetime */
}

2022-03-31 11:18:50

by Maciej W. Rozycki

[permalink] [raw]
Subject: [PATCH v4 4/5] x86/PCI: Fix ALi M1487 (IBC) PIRQ router link value interpretation

Fix an issue with commit 1ce849c75534 ("x86/PCI: Add support for the ALi
M1487 (IBC) PIRQ router") and correct ALi M1487 (IBC) PIRQ router link
value (`pirq' cookie) interpretation according to findings in the BIOS.

Credit to Nikolai Zhubr for the detective work as to the bit layout.

Signed-off-by: Maciej W. Rozycki <[email protected]>
Cc: Nikolai Zhubr <[email protected]>
Fixes: 1ce849c75534 ("x86/PCI: Add support for the ALi M1487 (IBC) PIRQ router")
Cc: [email protected] # v5.15+
---
No change from v3.

New change in v3.
---
arch/x86/pci/irq.c | 19 ++++++++++++++++---
1 file changed, 16 insertions(+), 3 deletions(-)

linux-x86-pirq-router-finali-link.diff
Index: linux-macro/arch/x86/pci/irq.c
===================================================================
--- linux-macro.orig/arch/x86/pci/irq.c
+++ linux-macro/arch/x86/pci/irq.c
@@ -337,6 +337,15 @@ static void write_pc_conf_nybble(u8 base
pc_conf_set(reg, x);
}

+/*
+ * FinALi pirq rules are as follows:
+ *
+ * - bit 0 selects between INTx Routing Table Mapping Registers,
+ *
+ * - bit 3 selects the nibble within the INTx Routing Table Mapping Register,
+ *
+ * - bits 7:4 map to bits 3:0 of the PCI INTx Sensitivity Register.
+ */
static int pirq_finali_get(struct pci_dev *router, struct pci_dev *dev,
int pirq)
{
@@ -344,11 +353,13 @@ static int pirq_finali_get(struct pci_de
0, 9, 3, 10, 4, 5, 7, 6, 0, 11, 0, 12, 0, 14, 0, 15
};
unsigned long flags;
+ u8 index;
u8 x;

+ index = (pirq & 1) << 1 | (pirq & 8) >> 3;
raw_spin_lock_irqsave(&pc_conf_lock, flags);
pc_conf_set(PC_CONF_FINALI_LOCK, PC_CONF_FINALI_LOCK_KEY);
- x = irqmap[read_pc_conf_nybble(PC_CONF_FINALI_PCI_INTX_RT1, pirq - 1)];
+ x = irqmap[read_pc_conf_nybble(PC_CONF_FINALI_PCI_INTX_RT1, index)];
pc_conf_set(PC_CONF_FINALI_LOCK, 0);
raw_spin_unlock_irqrestore(&pc_conf_lock, flags);
return x;
@@ -362,13 +373,15 @@ static int pirq_finali_set(struct pci_de
};
u8 val = irqmap[irq];
unsigned long flags;
+ u8 index;

if (!val)
return 0;

+ index = (pirq & 1) << 1 | (pirq & 8) >> 3;
raw_spin_lock_irqsave(&pc_conf_lock, flags);
pc_conf_set(PC_CONF_FINALI_LOCK, PC_CONF_FINALI_LOCK_KEY);
- write_pc_conf_nybble(PC_CONF_FINALI_PCI_INTX_RT1, pirq - 1, val);
+ write_pc_conf_nybble(PC_CONF_FINALI_PCI_INTX_RT1, index, val);
pc_conf_set(PC_CONF_FINALI_LOCK, 0);
raw_spin_unlock_irqrestore(&pc_conf_lock, flags);
return 1;
@@ -377,7 +390,7 @@ static int pirq_finali_set(struct pci_de
static int pirq_finali_lvl(struct pci_dev *router, struct pci_dev *dev,
int pirq, int irq)
{
- u8 mask = ~(1u << (pirq - 1));
+ u8 mask = ~((pirq & 0xf0u) >> 4);
unsigned long flags;
u8 trig;

2022-03-31 12:07:27

by Maciej W. Rozycki

[permalink] [raw]
Subject: [PATCH v4 1/5] x86/PCI: Add PIRQ routing table range checks

Verify that the PCI IRQ Routing Table header as well as individual slot
entries are all wholly contained within the BIOS memory area. Do not
even call the checksum calculator if the header would overrun the area
and then bail out early if any slot would.

Signed-off-by: Maciej W. Rozycki <[email protected]>
---
New change in v4.
---
arch/x86/pci/irq.c | 17 ++++++++++++-----
1 file changed, 12 insertions(+), 5 deletions(-)

linux-x86-pirq-range-check.diff
Index: linux-macro/arch/x86/pci/irq.c
===================================================================
--- linux-macro.orig/arch/x86/pci/irq.c
+++ linux-macro/arch/x86/pci/irq.c
@@ -68,7 +68,8 @@ void (*pcibios_disable_irq)(struct pci_d
* and perform checksum verification.
*/

-static inline struct irq_routing_table *pirq_check_routing_table(u8 *addr)
+static inline struct irq_routing_table *pirq_check_routing_table(u8 *addr,
+ u8 *limit)
{
struct irq_routing_table *rt;
int i;
@@ -78,7 +79,8 @@ static inline struct irq_routing_table *
if (rt->signature != PIRQ_SIGNATURE ||
rt->version != PIRQ_VERSION ||
rt->size % 16 ||
- rt->size < sizeof(struct irq_routing_table))
+ rt->size < sizeof(struct irq_routing_table) ||
+ (limit && rt->size > limit - addr))
return NULL;
sum = 0;
for (i = 0; i < rt->size; i++)
@@ -99,17 +101,22 @@ static inline struct irq_routing_table *

static struct irq_routing_table * __init pirq_find_routing_table(void)
{
+ u8 * const bios_start = (u8 *)__va(0xf0000);
+ u8 * const bios_end = (u8 *)__va(0x100000);
u8 *addr;
struct irq_routing_table *rt;

if (pirq_table_addr) {
- rt = pirq_check_routing_table((u8 *) __va(pirq_table_addr));
+ rt = pirq_check_routing_table((u8 *)__va(pirq_table_addr),
+ NULL);
if (rt)
return rt;
printk(KERN_WARNING "PCI: PIRQ table NOT found at pirqaddr\n");
}
- for (addr = (u8 *) __va(0xf0000); addr < (u8 *) __va(0x100000); addr += 16) {
- rt = pirq_check_routing_table(addr);
+ for (addr = bios_start;
+ addr < bios_end - sizeof(struct irq_routing_table);
+ addr += 16) {
+ rt = pirq_check_routing_table(addr, bios_end);
if (rt)
return rt;
}

2022-03-31 17:07:43

by Maciej W. Rozycki

[permalink] [raw]
Subject: [PATCH v4 5/5] x86/PCI: Fix coding style in PIRQ table verification

Remove an extraneous space with a cast in `pirq_check_routing_table'.

Signed-off-by: Maciej W. Rozycki <[email protected]>
---
Changes from v3:

- Remove parts obsoleted by 1/5.

New change in v3.
---
arch/x86/pci/irq.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)

Index: linux-macro/arch/x86/pci/irq.c
===================================================================
--- linux-macro.orig/arch/x86/pci/irq.c
+++ linux-macro/arch/x86/pci/irq.c
@@ -77,7 +77,7 @@ static inline struct irq_routing_table *
int i;
u8 sum;

- rt = (struct irq_routing_table *) addr;
+ rt = (struct irq_routing_table *)addr;
if (rt->signature != PIRQ_SIGNATURE ||
rt->version != PIRQ_VERSION ||
rt->size % 16 ||

2022-04-02 15:49:22

by Dmitry Osipenko

[permalink] [raw]
Subject: Re: [PATCH v4 3/5] x86/PCI: Add $IRT PIRQ routing table support

On 3/31/22 10:11, Maciej W. Rozycki wrote:
> Handle the $IRT PCI IRQ Routing Table format used by AMI for its BCP
> (BIOS Configuration Program) external tool meant for tweaking BIOS
> structures without the need to rebuild it from sources[1].
>
> The $IRT format has been invented by AMI before Microsoft has come up
> with its $PIR format and a $IRT table is therefore there in some systems
> that lack a $PIR table, such as the DataExpert EXP8449 mainboard based
> on the ALi FinALi 486 chipset (M1489/M1487), which predates DMI 2.0 and
> cannot therefore be easily identified at run time.
>
> Unlike with the $PIR format there is no alignment guarantee as to the
> placement of the $IRT table, so scan the whole BIOS area bytewise.
>
> Credit to Michal Necasek for helping me chase documentation for the
> format.
>
> References:
>
> [1] "What is BCP? - AMI", <https://www.ami.com/what-is-bcp/>
>
> Signed-off-by: Maciej W. Rozycki <[email protected]>
> Cc: Michal Necasek <[email protected]>
> ---
> Changes from v3:
>
> - Correct the BIOS memory scan such as to verify that the PCI IRQ Routing
> Table header as well as individual slot entries are all wholly contained
> within the BIOS memory area.
>
> - Following commit 5224f7909617 ("treewide: Replace zero-length arrays
> with flexible-array members") also make `slots' in `irt_routing_table' a
> flexible-array member.
>
> New change in v3.
> ---
> arch/x86/include/asm/pci_x86.h | 9 ++++
> arch/x86/pci/irq.c | 76 +++++++++++++++++++++++++++++++++++++++++
> 2 files changed, 85 insertions(+)

Tested-by: Dmitry Osipenko <[email protected]> # crosvm

Subject: [tip: x86/irq] x86/PCI: Add $IRT PIRQ routing table support

The following commit has been merged into the x86/irq branch of tip:

Commit-ID: b584db0c84db5ed9230356d5fa6610de55d297e6
Gitweb: https://git.kernel.org/tip/b584db0c84db5ed9230356d5fa6610de55d297e6
Author: Maciej W. Rozycki <[email protected]>
AuthorDate: Thu, 31 Mar 2022 08:11:05 +01:00
Committer: Thomas Gleixner <[email protected]>
CommitterDate: Sun, 10 Apr 2022 12:48:14 +02:00

x86/PCI: Add $IRT PIRQ routing table support

Handle the $IRT PCI IRQ Routing Table format used by AMI for its BCP
(BIOS Configuration Program) external tool meant for tweaking BIOS
structures without the need to rebuild it from sources[1].

The $IRT format has been invented by AMI before Microsoft has come up
with its $PIR format and a $IRT table is therefore there in some systems
that lack a $PIR table, such as the DataExpert EXP8449 mainboard based
on the ALi FinALi 486 chipset (M1489/M1487), which predates DMI 2.0 and
cannot therefore be easily identified at run time.

Unlike with the $PIR format there is no alignment guarantee as to the
placement of the $IRT table, so scan the whole BIOS area bytewise.

Credit to Michal Necasek for helping me chase documentation for the
format.

References:

[1] "What is BCP? - AMI", <https://www.ami.com/what-is-bcp/>

Signed-off-by: Maciej W. Rozycki <[email protected]>
Signed-off-by: Thomas Gleixner <[email protected]>
Tested-by: Dmitry Osipenko <[email protected]> # crosvm
Link: https://lore.kernel.org/r/[email protected]

---
arch/x86/include/asm/pci_x86.h | 9 ++++-
arch/x86/pci/irq.c | 76 +++++++++++++++++++++++++++++++++-
2 files changed, 85 insertions(+)

diff --git a/arch/x86/include/asm/pci_x86.h b/arch/x86/include/asm/pci_x86.h
index a0627df..1307cd6 100644
--- a/arch/x86/include/asm/pci_x86.h
+++ b/arch/x86/include/asm/pci_x86.h
@@ -93,6 +93,15 @@ struct irq_routing_table {
struct irq_info slots[];
} __attribute__((packed));

+struct irt_routing_table {
+ u32 signature; /* IRT_SIGNATURE should be here */
+ u8 size; /* Number of entries provided */
+ u8 used; /* Number of entries actually used */
+ u16 exclusive_irqs; /* IRQs devoted exclusively to
+ PCI usage */
+ struct irq_info slots[];
+} __attribute__((packed));
+
extern unsigned int pcibios_irq_mask;

extern raw_spinlock_t pci_config_lock;
diff --git a/arch/x86/pci/irq.c b/arch/x86/pci/irq.c
index d4ecf88..4a5e80f 100644
--- a/arch/x86/pci/irq.c
+++ b/arch/x86/pci/irq.c
@@ -25,6 +25,8 @@
#define PIRQ_SIGNATURE (('$' << 0) + ('P' << 8) + ('I' << 16) + ('R' << 24))
#define PIRQ_VERSION 0x0100

+#define IRT_SIGNATURE (('$' << 0) + ('I' << 8) + ('R' << 16) + ('T' << 24))
+
static int broken_hp_bios_irq9;
static int acer_tm360_irqrouting;

@@ -93,7 +95,74 @@ static inline struct irq_routing_table *pirq_check_routing_table(u8 *addr,
return NULL;
}

+/*
+ * Handle the $IRT PCI IRQ Routing Table format used by AMI for its BCP
+ * (BIOS Configuration Program) external tool meant for tweaking BIOS
+ * structures without the need to rebuild it from sources. The $IRT
+ * format has been invented by AMI before Microsoft has come up with its
+ * $PIR format and a $IRT table is therefore there in some systems that
+ * lack a $PIR table.
+ *
+ * It uses the same PCI BIOS 2.1 format for interrupt routing entries
+ * themselves but has a different simpler header prepended instead,
+ * occupying 8 bytes, where a `$IRT' signature is followed by one byte
+ * specifying the total number of interrupt routing entries allocated in
+ * the table, then one byte specifying the actual number of entries used
+ * (which the BCP tool can take advantage of when modifying the table),
+ * and finally a 16-bit word giving the IRQs devoted exclusively to PCI.
+ * Unlike with the $PIR table there is no alignment guarantee.
+ *
+ * Given the similarity of the two formats the $IRT one is trivial to
+ * convert to the $PIR one, which we do here, except that obviously we
+ * have no information as to the router device to use, but we can handle
+ * it by matching PCI device IDs actually seen on the bus against ones
+ * that our individual routers recognise.
+ *
+ * Reportedly there is another $IRT table format where a 16-bit word
+ * follows the header instead that points to interrupt routing entries
+ * in a $PIR table provided elsewhere. In that case this code will not
+ * be reached though as the $PIR table will have been chosen instead.
+ */
+static inline struct irq_routing_table *pirq_convert_irt_table(u8 *addr,
+ u8 *limit)
+{
+ struct irt_routing_table *ir;
+ struct irq_routing_table *rt;
+ u16 size;
+ u8 sum;
+ int i;
+
+ ir = (struct irt_routing_table *)addr;
+ if (ir->signature != IRT_SIGNATURE || !ir->used || ir->size < ir->used)
+ return NULL;
+
+ size = sizeof(*ir) + ir->used * sizeof(ir->slots[0]);
+ if (size > limit - addr)
+ return NULL;
+
+ DBG(KERN_DEBUG "PCI: $IRT Interrupt Routing Table found at 0x%lx\n",
+ __pa(ir));
+
+ size = sizeof(*rt) + ir->used * sizeof(rt->slots[0]);
+ rt = kzalloc(size, GFP_KERNEL);
+ if (!rt)
+ return NULL;

+ rt->signature = PIRQ_SIGNATURE;
+ rt->version = PIRQ_VERSION;
+ rt->size = size;
+ rt->exclusive_irqs = ir->exclusive_irqs;
+ for (i = 0; i < ir->used; i++)
+ rt->slots[i] = ir->slots[i];
+
+ addr = (u8 *)rt;
+ sum = 0;
+ for (i = 0; i < size; i++)
+ sum += addr[i];
+ rt->checksum = -sum;
+
+ return rt;
+}

/*
* Search 0xf0000 -- 0xfffff for the PCI IRQ Routing Table.
@@ -120,6 +189,13 @@ static struct irq_routing_table * __init pirq_find_routing_table(void)
if (rt)
return rt;
}
+ for (addr = bios_start;
+ addr < bios_end - sizeof(struct irt_routing_table);
+ addr++) {
+ rt = pirq_convert_irt_table(addr, bios_end);
+ if (rt)
+ return rt;
+ }
return NULL;
}

Subject: [tip: x86/irq] x86/PCI: Handle PIRQ routing tables with no router device given

The following commit has been merged into the x86/irq branch of tip:

Commit-ID: ac7cd5e16df8696c39e29b03dfedf069a025b822
Gitweb: https://git.kernel.org/tip/ac7cd5e16df8696c39e29b03dfedf069a025b822
Author: Maciej W. Rozycki <[email protected]>
AuthorDate: Thu, 31 Mar 2022 08:11:01 +01:00
Committer: Thomas Gleixner <[email protected]>
CommitterDate: Sun, 10 Apr 2022 12:48:14 +02:00

x86/PCI: Handle PIRQ routing tables with no router device given

PIRQ routing tables provided by the PCI BIOS usually specify the PCI
vendor:device ID as well as the bus address of the device implementing
the PIRQ router, e.g.:

PCI: Interrupt Routing Table found at 0xc00fde10
[...]
PCI: Attempting to find IRQ router for [8086:7000]
pci 0000:00:07.0: PIIX/ICH IRQ router [8086:7000]

however in some cases they do not, in which case we fail to match the
router handler, e.g.:

PCI: Interrupt Routing Table found at 0xc00fdae0
[...]
PCI: Attempting to find IRQ router for [0000:0000]
PCI: Interrupt router not found at 00:00

This is because we always match the vendor:device ID and the bus address
literally, even if they are all zeros.

Handle this case then and iterate over all PCI devices until we find a
matching router handler if the vendor ID given by the routing table is
the invalid value of zero:

PCI: Attempting to find IRQ router for [0000:0000]
PCI: Trying IRQ router for [1039:0496]
pci 0000:00:05.0: SiS85C497 IRQ router [1039:0496]

Signed-off-by: Maciej W. Rozycki <[email protected]>
Signed-off-by: Thomas Gleixner <[email protected]>
Tested-by: Nikolai Zhubr <[email protected]>
Link: https://lore.kernel.org/r/[email protected]

---
arch/x86/pci/irq.c | 64 ++++++++++++++++++++++++++++++---------------
1 file changed, 44 insertions(+), 20 deletions(-)

diff --git a/arch/x86/pci/irq.c b/arch/x86/pci/irq.c
index ef97b26..d4ecf88 100644
--- a/arch/x86/pci/irq.c
+++ b/arch/x86/pci/irq.c
@@ -1175,10 +1175,32 @@ static struct pci_dev *pirq_router_dev;
* chipset" ?
*/

+static bool __init pirq_try_router(struct irq_router *r,
+ struct irq_routing_table *rt,
+ struct pci_dev *dev)
+{
+ struct irq_router_handler *h;
+
+ DBG(KERN_DEBUG "PCI: Trying IRQ router for [%04x:%04x]\n",
+ dev->vendor, dev->device);
+
+ for (h = pirq_routers; h->vendor; h++) {
+ /* First look for a router match */
+ if (rt->rtr_vendor == h->vendor &&
+ h->probe(r, dev, rt->rtr_device))
+ return true;
+ /* Fall back to a device match */
+ if (dev->vendor == h->vendor &&
+ h->probe(r, dev, dev->device))
+ return true;
+ }
+ return false;
+}
+
static void __init pirq_find_router(struct irq_router *r)
{
struct irq_routing_table *rt = pirq_table;
- struct irq_router_handler *h;
+ struct pci_dev *dev;

#ifdef CONFIG_PCI_BIOS
if (!rt->signature) {
@@ -1197,27 +1219,29 @@ static void __init pirq_find_router(struct irq_router *r)
DBG(KERN_DEBUG "PCI: Attempting to find IRQ router for [%04x:%04x]\n",
rt->rtr_vendor, rt->rtr_device);

- pirq_router_dev = pci_get_domain_bus_and_slot(0, rt->rtr_bus,
- rt->rtr_devfn);
- if (!pirq_router_dev) {
- DBG(KERN_DEBUG "PCI: Interrupt router not found at "
- "%02x:%02x\n", rt->rtr_bus, rt->rtr_devfn);
- return;
+ /* Use any vendor:device provided by the routing table or try all. */
+ if (rt->rtr_vendor) {
+ dev = pci_get_domain_bus_and_slot(0, rt->rtr_bus,
+ rt->rtr_devfn);
+ if (dev && pirq_try_router(r, rt, dev))
+ pirq_router_dev = dev;
+ } else {
+ dev = NULL;
+ for_each_pci_dev(dev) {
+ if (pirq_try_router(r, rt, dev)) {
+ pirq_router_dev = dev;
+ break;
+ }
+ }
}

- for (h = pirq_routers; h->vendor; h++) {
- /* First look for a router match */
- if (rt->rtr_vendor == h->vendor &&
- h->probe(r, pirq_router_dev, rt->rtr_device))
- break;
- /* Fall back to a device match */
- if (pirq_router_dev->vendor == h->vendor &&
- h->probe(r, pirq_router_dev, pirq_router_dev->device))
- break;
- }
- dev_info(&pirq_router_dev->dev, "%s IRQ router [%04x:%04x]\n",
- pirq_router.name,
- pirq_router_dev->vendor, pirq_router_dev->device);
+ if (pirq_router_dev)
+ dev_info(&pirq_router_dev->dev, "%s IRQ router [%04x:%04x]\n",
+ pirq_router.name,
+ pirq_router_dev->vendor, pirq_router_dev->device);
+ else
+ DBG(KERN_DEBUG "PCI: Interrupt router not found at "
+ "%02x:%02x\n", rt->rtr_bus, rt->rtr_devfn);

/* The device remains referenced for the kernel lifetime */
}

Subject: [tip: x86/irq] x86/PCI: Fix ALi M1487 (IBC) PIRQ router link value interpretation

The following commit has been merged into the x86/irq branch of tip:

Commit-ID: 4969e223b109754c2340a26bba9b1cf44f0cba9b
Gitweb: https://git.kernel.org/tip/4969e223b109754c2340a26bba9b1cf44f0cba9b
Author: Maciej W. Rozycki <[email protected]>
AuthorDate: Thu, 31 Mar 2022 08:11:10 +01:00
Committer: Thomas Gleixner <[email protected]>
CommitterDate: Sun, 10 Apr 2022 12:48:15 +02:00

x86/PCI: Fix ALi M1487 (IBC) PIRQ router link value interpretation

Fix an issue with commit 1ce849c75534 ("x86/PCI: Add support for the ALi
M1487 (IBC) PIRQ router") and correct ALi M1487 (IBC) PIRQ router link
value (`pirq' cookie) interpretation according to findings in the BIOS.

Credit to Nikolai Zhubr for the detective work as to the bit layout.

Fixes: 1ce849c75534 ("x86/PCI: Add support for the ALi M1487 (IBC) PIRQ router")
Signed-off-by: Maciej W. Rozycki <[email protected]>
Signed-off-by: Thomas Gleixner <[email protected]>
Link: https://lore.kernel.org/r/[email protected]

---
arch/x86/pci/irq.c | 19 ++++++++++++++++---
1 file changed, 16 insertions(+), 3 deletions(-)

diff --git a/arch/x86/pci/irq.c b/arch/x86/pci/irq.c
index 4a5e80f..ceac715 100644
--- a/arch/x86/pci/irq.c
+++ b/arch/x86/pci/irq.c
@@ -337,6 +337,15 @@ static void write_pc_conf_nybble(u8 base, u8 index, u8 val)
pc_conf_set(reg, x);
}

+/*
+ * FinALi pirq rules are as follows:
+ *
+ * - bit 0 selects between INTx Routing Table Mapping Registers,
+ *
+ * - bit 3 selects the nibble within the INTx Routing Table Mapping Register,
+ *
+ * - bits 7:4 map to bits 3:0 of the PCI INTx Sensitivity Register.
+ */
static int pirq_finali_get(struct pci_dev *router, struct pci_dev *dev,
int pirq)
{
@@ -344,11 +353,13 @@ static int pirq_finali_get(struct pci_dev *router, struct pci_dev *dev,
0, 9, 3, 10, 4, 5, 7, 6, 0, 11, 0, 12, 0, 14, 0, 15
};
unsigned long flags;
+ u8 index;
u8 x;

+ index = (pirq & 1) << 1 | (pirq & 8) >> 3;
raw_spin_lock_irqsave(&pc_conf_lock, flags);
pc_conf_set(PC_CONF_FINALI_LOCK, PC_CONF_FINALI_LOCK_KEY);
- x = irqmap[read_pc_conf_nybble(PC_CONF_FINALI_PCI_INTX_RT1, pirq - 1)];
+ x = irqmap[read_pc_conf_nybble(PC_CONF_FINALI_PCI_INTX_RT1, index)];
pc_conf_set(PC_CONF_FINALI_LOCK, 0);
raw_spin_unlock_irqrestore(&pc_conf_lock, flags);
return x;
@@ -362,13 +373,15 @@ static int pirq_finali_set(struct pci_dev *router, struct pci_dev *dev,
};
u8 val = irqmap[irq];
unsigned long flags;
+ u8 index;

if (!val)
return 0;

+ index = (pirq & 1) << 1 | (pirq & 8) >> 3;
raw_spin_lock_irqsave(&pc_conf_lock, flags);
pc_conf_set(PC_CONF_FINALI_LOCK, PC_CONF_FINALI_LOCK_KEY);
- write_pc_conf_nybble(PC_CONF_FINALI_PCI_INTX_RT1, pirq - 1, val);
+ write_pc_conf_nybble(PC_CONF_FINALI_PCI_INTX_RT1, index, val);
pc_conf_set(PC_CONF_FINALI_LOCK, 0);
raw_spin_unlock_irqrestore(&pc_conf_lock, flags);
return 1;
@@ -377,7 +390,7 @@ static int pirq_finali_set(struct pci_dev *router, struct pci_dev *dev,
static int pirq_finali_lvl(struct pci_dev *router, struct pci_dev *dev,
int pirq, int irq)
{
- u8 mask = ~(1u << (pirq - 1));
+ u8 mask = ~((pirq & 0xf0u) >> 4);
unsigned long flags;
u8 trig;

Subject: [tip: x86/irq] x86/PCI: Add PIRQ routing table range checks

The following commit has been merged into the x86/irq branch of tip:

Commit-ID: 5d64089aa4a5bd3d7e00e3d6ddf4943dd34627b3
Gitweb: https://git.kernel.org/tip/5d64089aa4a5bd3d7e00e3d6ddf4943dd34627b3
Author: Maciej W. Rozycki <[email protected]>
AuthorDate: Thu, 31 Mar 2022 08:10:55 +01:00
Committer: Thomas Gleixner <[email protected]>
CommitterDate: Sun, 10 Apr 2022 12:48:14 +02:00

x86/PCI: Add PIRQ routing table range checks

Verify that the PCI IRQ Routing Table header as well as individual slot
entries are all wholly contained within the BIOS memory area. Do not
even call the checksum calculator if the header would overrun the area
and then bail out early if any slot would.

Signed-off-by: Maciej W. Rozycki <[email protected]>
Signed-off-by: Thomas Gleixner <[email protected]>
Link: https://lore.kernel.org/r/[email protected]

---
arch/x86/pci/irq.c | 17 ++++++++++++-----
1 file changed, 12 insertions(+), 5 deletions(-)

diff --git a/arch/x86/pci/irq.c b/arch/x86/pci/irq.c
index 4b0e008..ef97b26 100644
--- a/arch/x86/pci/irq.c
+++ b/arch/x86/pci/irq.c
@@ -68,7 +68,8 @@ void (*pcibios_disable_irq)(struct pci_dev *dev) = pirq_disable_irq;
* and perform checksum verification.
*/

-static inline struct irq_routing_table *pirq_check_routing_table(u8 *addr)
+static inline struct irq_routing_table *pirq_check_routing_table(u8 *addr,
+ u8 *limit)
{
struct irq_routing_table *rt;
int i;
@@ -78,7 +79,8 @@ static inline struct irq_routing_table *pirq_check_routing_table(u8 *addr)
if (rt->signature != PIRQ_SIGNATURE ||
rt->version != PIRQ_VERSION ||
rt->size % 16 ||
- rt->size < sizeof(struct irq_routing_table))
+ rt->size < sizeof(struct irq_routing_table) ||
+ (limit && rt->size > limit - addr))
return NULL;
sum = 0;
for (i = 0; i < rt->size; i++)
@@ -99,17 +101,22 @@ static inline struct irq_routing_table *pirq_check_routing_table(u8 *addr)

static struct irq_routing_table * __init pirq_find_routing_table(void)
{
+ u8 * const bios_start = (u8 *)__va(0xf0000);
+ u8 * const bios_end = (u8 *)__va(0x100000);
u8 *addr;
struct irq_routing_table *rt;

if (pirq_table_addr) {
- rt = pirq_check_routing_table((u8 *) __va(pirq_table_addr));
+ rt = pirq_check_routing_table((u8 *)__va(pirq_table_addr),
+ NULL);
if (rt)
return rt;
printk(KERN_WARNING "PCI: PIRQ table NOT found at pirqaddr\n");
}
- for (addr = (u8 *) __va(0xf0000); addr < (u8 *) __va(0x100000); addr += 16) {
- rt = pirq_check_routing_table(addr);
+ for (addr = bios_start;
+ addr < bios_end - sizeof(struct irq_routing_table);
+ addr += 16) {
+ rt = pirq_check_routing_table(addr, bios_end);
if (rt)
return rt;
}

Subject: [tip: x86/irq] x86/PCI: Fix coding style in PIRQ table verification

The following commit has been merged into the x86/irq branch of tip:

Commit-ID: c25f23459c117d950e657458b0d3dcaaf9039ec9
Gitweb: https://git.kernel.org/tip/c25f23459c117d950e657458b0d3dcaaf9039ec9
Author: Maciej W. Rozycki <[email protected]>
AuthorDate: Thu, 31 Mar 2022 08:11:14 +01:00
Committer: Thomas Gleixner <[email protected]>
CommitterDate: Sun, 10 Apr 2022 12:48:15 +02:00

x86/PCI: Fix coding style in PIRQ table verification

Remove an extraneous space with a cast in `pirq_check_routing_table'.

Signed-off-by: Maciej W. Rozycki <[email protected]>
Signed-off-by: Thomas Gleixner <[email protected]>
Link: https://lore.kernel.org/r/[email protected]

---
arch/x86/pci/irq.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/arch/x86/pci/irq.c b/arch/x86/pci/irq.c
index ceac715..a498b84 100644
--- a/arch/x86/pci/irq.c
+++ b/arch/x86/pci/irq.c
@@ -77,7 +77,7 @@ static inline struct irq_routing_table *pirq_check_routing_table(u8 *addr,
int i;
u8 sum;

- rt = (struct irq_routing_table *) addr;
+ rt = (struct irq_routing_table *)addr;
if (rt->signature != PIRQ_SIGNATURE ||
rt->version != PIRQ_VERSION ||
rt->size % 16 ||