PCI: xilinx-nwl: Fix Multi MSI data programming [Linux 4.9.187]

This Linux kernel change "PCI: xilinx-nwl: Fix Multi MSI data programming" is included in the Linux 4.9.187 release. This change is authored by Bharat Kumar Gogada <bharat.kumar.gogada [at] xilinx.com> on Wed Jun 12 15:47:59 2019 +0530. The commit for this change in Linux stable tree is 5086e47 (patch) which is from upstream commit 181fa43. The same Linux upstream change may have been applied to various maintained Linux releases and you can find all Linux releases containing changes from upstream 181fa43.

PCI: xilinx-nwl: Fix Multi MSI data programming

[ Upstream commit 181fa434d0514e40ebf6e9721f2b72700287b6e2 ]

According to the PCI Local Bus specification Revision 3.0,
section 6.8.1.3 (Message Control for MSI), endpoints that
are Multiple Message Capable as defined by bits [3:1] in
the Message Control for MSI can request a number of vectors
that is power of two aligned.

As specified in section 6.8.1.6 "Message data for MSI", the Multiple
Message Enable field (bits [6:4] of the Message Control register)
defines the number of low order message data bits the function is
permitted to modify to generate its system software allocated
vectors.

The MSI controller in the Xilinx NWL PCIe controller supports a number
of MSI vectors specified through a bitmap and the hwirq number for an
MSI, that is the value written in the MSI data TLP is determined by
the bitmap allocation.

For instance, in a situation where two endpoints sitting on
the PCI bus request the following MSI configuration, with
the current PCI Xilinx bitmap allocation code (that does not
align MSI vector allocation on a power of two boundary):

Endpoint #1: Requesting 1 MSI vector - allocated bitmap bits 0
Endpoint #2: Requesting 2 MSI vectors - allocated bitmap bits [1,2]

The bitmap value(s) corresponds to the hwirq number that is programmed
into the Message Data for MSI field in the endpoint MSI capability
and is detected by the root complex to fire the corresponding
MSI irqs. The value written in Message Data for MSI field corresponds
to the first bit allocated in the bitmap for Multi MSI vectors.

The current Xilinx NWL MSI allocation code allows a bitmap allocation
that is not a power of two boundaries, so endpoint #2, is allowed to
toggle Message Data bit[0] to differentiate between its two vectors
(meaning that the MSI data will be respectively 0x0 and 0x1 for the two
vectors allocated to endpoint #2).

This clearly aliases with the Endpoint #1 vector allocation, resulting
in a broken Multi MSI implementation.

Update the code to allocate MSI bitmap ranges with a power of two
alignment, fixing the bug.

Fixes: ab597d35ef11 ("PCI: xilinx-nwl: Add support for Xilinx NWL PCIe Host Controller")
Suggested-by: Marc Zyngier <marc.zyngier@arm.com>
Signed-off-by: Bharat Kumar Gogada <bharat.kumar.gogada@xilinx.com>
[lorenzo.pieralisi@arm.com: updated commit log]
Signed-off-by: Lorenzo Pieralisi <lorenzo.pieralisi@arm.com>
Acked-by: Marc Zyngier <marc.zyngier@arm.com>
Signed-off-by: Sasha Levin <sashal@kernel.org>

There are 11 lines of Linux source code added/deleted in this change. Code changes to Linux kernel are as follows.

 drivers/pci/host/pcie-xilinx-nwl.c | 11 +++++------
 1 file changed, 5 insertions(+), 6 deletions(-)

diff --git a/drivers/pci/host/pcie-xilinx-nwl.c b/drivers/pci/host/pcie-xilinx-nwl.c
index 94fdd29..3bba87a 100644
--- a/drivers/pci/host/pcie-xilinx-nwl.c
+++ b/drivers/pci/host/pcie-xilinx-nwl.c
@@ -456,15 +456,13 @@ static int nwl_irq_domain_alloc(struct irq_domain *domain, unsigned int virq,
    int i;

    mutex_lock(&msi->lock);
-   bit = bitmap_find_next_zero_area(msi->bitmap, INT_PCI_MSI_NR, 0,
-                    nr_irqs, 0);
-   if (bit >= INT_PCI_MSI_NR) {
+   bit = bitmap_find_free_region(msi->bitmap, INT_PCI_MSI_NR,
+                     get_count_order(nr_irqs));
+   if (bit < 0) {
        mutex_unlock(&msi->lock);
        return -ENOSPC;
    }

-   bitmap_set(msi->bitmap, bit, nr_irqs);
-
    for (i = 0; i < nr_irqs; i++) {
        irq_domain_set_info(domain, virq + i, bit + i, &nwl_irq_chip,
                domain->host_data, handle_simple_irq,
@@ -482,7 +480,8 @@ static void nwl_irq_domain_free(struct irq_domain *domain, unsigned int virq,
    struct nwl_msi *msi = &pcie->msi;

    mutex_lock(&msi->lock);
-   bitmap_clear(msi->bitmap, data->hwirq, nr_irqs);
+   bitmap_release_region(msi->bitmap, data->hwirq,
+                 get_count_order(nr_irqs));
    mutex_unlock(&msi->lock);
 }

Leave a Reply

Your email address will not be published. Required fields are marked *