Commit 468711a4 authored by Niklas Cassel's avatar Niklas Cassel Committed by Bjorn Helgaas
Browse files

PCI: dwc: ep: Refresh MSI Message Address cache on change



Endpoint drivers use dw_pcie_ep_raise_msi_irq() to raise MSI interrupts to
the host.  After 8719c64e ("PCI: dwc: ep: Cache MSI outbound iATU
mapping"), dw_pcie_ep_raise_msi_irq() caches the Message Address from the
MSI Capability in ep->msi_msg_addr.  But that Message Address is controlled
by the host, and it may change.  For example, if:

  - firmware on the host configures the Message Address and triggers an
    MSI,

  - a driver on the Endpoint raises the MSI via dw_pcie_ep_raise_msi_irq(),
    which caches the Message Address,

  - a kernel on the host reconfigures the Message Address and the host
    kernel driver triggers another MSI,

dw_pcie_ep_raise_msi_irq() notices that the Message Address no longer
matches the cached ep->msi_msg_addr, warns about it, and returns error
instead of raising the MSI.  The host kernel may hang because it never
receives the MSI.

This was seen with the nvmet_pci_epf_driver: the host UEFI performs NVMe
commands, e.g. Identify Controller to get the name of the controller,
nvmet-pci-epf posts the completion queue entry and raises an IRQ using
dw_pcie_ep_raise_msi_irq().  When the host boots Linux, we see a
WARN_ON_ONCE() from dw_pcie_ep_raise_msi_irq(), and the host kernel hangs
because the nvme driver never gets an IRQ.

Remove the warning when dw_pcie_ep_raise_msi_irq() notices that Message
Address has changed, remap using the new address, and update the
ep->msi_msg_addr cache.

Fixes: 8719c64e ("PCI: dwc: ep: Cache MSI outbound iATU mapping")
Signed-off-by: default avatarNiklas Cassel <cassel@kernel.org>
[bhelgaas: commit log]
Signed-off-by: default avatarBjorn Helgaas <bhelgaas@google.com>
Tested-by: default avatarShin'ichiro Kawasaki <shinichiro.kawasaki@wdc.com>
Tested-by: default avatarKoichiro Den <den@valinux.co.jp>
Acked-by: default avatarManivannan Sadhasivam <mani@kernel.org>
Link: https://patch.msgid.link/20260210181225.3926165-2-cassel@kernel.org
parent 56e0a838
Loading
Loading
Loading
Loading
+13 −9
Original line number Diff line number Diff line
@@ -905,6 +905,19 @@ int dw_pcie_ep_raise_msi_irq(struct dw_pcie_ep *ep, u8 func_no,
	 * supported, so we avoid reprogramming the region on every MSI,
	 * specifically unmapping immediately after writel().
	 */
	if (ep->msi_iatu_mapped && (ep->msi_msg_addr != msg_addr ||
				    ep->msi_map_size != map_size)) {
		/*
		 * The host changed the MSI target address or the required
		 * mapping size changed. Reprogramming the iATU when there are
		 * operations in flight is unsafe on this controller. However,
		 * there is no unified way to check if we have operations in
		 * flight, thus we don't know if we should WARN() or not.
		 */
		dw_pcie_ep_unmap_addr(epc, func_no, 0, ep->msi_mem_phys);
		ep->msi_iatu_mapped = false;
	}

	if (!ep->msi_iatu_mapped) {
		ret = dw_pcie_ep_map_addr(epc, func_no, 0,
					  ep->msi_mem_phys, msg_addr,
@@ -915,15 +928,6 @@ int dw_pcie_ep_raise_msi_irq(struct dw_pcie_ep *ep, u8 func_no,
		ep->msi_iatu_mapped = true;
		ep->msi_msg_addr = msg_addr;
		ep->msi_map_size = map_size;
	} else if (WARN_ON_ONCE(ep->msi_msg_addr != msg_addr ||
				ep->msi_map_size != map_size)) {
		/*
		 * The host changed the MSI target address or the required
		 * mapping size changed. Reprogramming the iATU at runtime is
		 * unsafe on this controller, so bail out instead of trying to
		 * update the existing region.
		 */
		return -EINVAL;
	}

	writel(msg_data | (interrupt_num - 1), ep->msi_mem + offset);