linux/drivers/irqchip/irq-gic-v3-its-msi-parent.c
Marc Zyngier c9b4f313f6 irqchip/gic-v3-its: Correctly fish out the DID for platform MSI
Similarly to PCI where msi-map/msi-mask are used to compute the full RID
(aka DID in ITS speak), use the msi-parent as the discovery mechanism,
since there is no way a device can generally express its ID.

However, since switching to a per-device MSI domain model, the domain
passed to its_pmsi_prepare() is the wrong one, and points to the device's
instead of the ITS'. Bad.

Use the parent domain instead, which is the ITS domain.

Fixes: 80b63cc1cc146 ("irqchip/gic-v3-its: Switch platform MSI to MSI parent")
Signed-off-by: Marc Zyngier <maz@kernel.org>
Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
Link: https://lore.kernel.org/r/20240718075804.2245733-1-maz@kernel.org
2024-07-18 20:34:46 +02:00

211 lines
5.9 KiB
C

// SPDX-License-Identifier: GPL-2.0-only
// Copyright (C) 2013-2015 ARM Limited, All Rights Reserved.
// Author: Marc Zyngier <marc.zyngier@arm.com>
// Copyright (C) 2022 Linutronix GmbH
// Copyright (C) 2022 Intel
#include <linux/acpi_iort.h>
#include <linux/pci.h>
#include "irq-gic-common.h"
#include "irq-msi-lib.h"
#define ITS_MSI_FLAGS_REQUIRED (MSI_FLAG_USE_DEF_DOM_OPS | \
MSI_FLAG_USE_DEF_CHIP_OPS | \
MSI_FLAG_PCI_MSI_MASK_PARENT)
#define ITS_MSI_FLAGS_SUPPORTED (MSI_GENERIC_FLAGS_MASK | \
MSI_FLAG_PCI_MSIX | \
MSI_FLAG_MULTI_PCI_MSI)
#ifdef CONFIG_PCI_MSI
static int its_pci_msi_vec_count(struct pci_dev *pdev, void *data)
{
int msi, msix, *count = data;
msi = max(pci_msi_vec_count(pdev), 0);
msix = max(pci_msix_vec_count(pdev), 0);
*count += max(msi, msix);
return 0;
}
static int its_get_pci_alias(struct pci_dev *pdev, u16 alias, void *data)
{
struct pci_dev **alias_dev = data;
*alias_dev = pdev;
return 0;
}
static int its_pci_msi_prepare(struct irq_domain *domain, struct device *dev,
int nvec, msi_alloc_info_t *info)
{
struct pci_dev *pdev, *alias_dev;
struct msi_domain_info *msi_info;
int alias_count = 0, minnvec = 1;
if (!dev_is_pci(dev))
return -EINVAL;
pdev = to_pci_dev(dev);
/*
* If pdev is downstream of any aliasing bridges, take an upper
* bound of how many other vectors could map to the same DevID.
* Also tell the ITS that the signalling will come from a proxy
* device, and that special allocation rules apply.
*/
pci_for_each_dma_alias(pdev, its_get_pci_alias, &alias_dev);
if (alias_dev != pdev) {
if (alias_dev->subordinate)
pci_walk_bus(alias_dev->subordinate,
its_pci_msi_vec_count, &alias_count);
info->flags |= MSI_ALLOC_FLAGS_PROXY_DEVICE;
}
/* ITS specific DeviceID, as the core ITS ignores dev. */
info->scratchpad[0].ul = pci_msi_domain_get_msi_rid(domain->parent, pdev);
/*
* @domain->msi_domain_info->hwsize contains the size of the
* MSI[-X] domain, but vector allocation happens one by one. This
* needs some thought when MSI comes into play as the size of MSI
* might be unknown at domain creation time and therefore set to
* MSI_MAX_INDEX.
*/
msi_info = msi_get_domain_info(domain);
if (msi_info->hwsize > nvec)
nvec = msi_info->hwsize;
/*
* Always allocate a power of 2, and special case device 0 for
* broken systems where the DevID is not wired (and all devices
* appear as DevID 0). For that reason, we generously allocate a
* minimum of 32 MSIs for DevID 0. If you want more because all
* your devices are aliasing to DevID 0, consider fixing your HW.
*/
nvec = max(nvec, alias_count);
if (!info->scratchpad[0].ul)
minnvec = 32;
nvec = max_t(int, minnvec, roundup_pow_of_two(nvec));
msi_info = msi_get_domain_info(domain->parent);
return msi_info->ops->msi_prepare(domain->parent, dev, nvec, info);
}
#else /* CONFIG_PCI_MSI */
#define its_pci_msi_prepare NULL
#endif /* !CONFIG_PCI_MSI */
static int of_pmsi_get_dev_id(struct irq_domain *domain, struct device *dev,
u32 *dev_id)
{
int ret, index = 0;
/* Suck the DeviceID out of the msi-parent property */
do {
struct of_phandle_args args;
ret = of_parse_phandle_with_args(dev->of_node,
"msi-parent", "#msi-cells",
index, &args);
if (args.np == irq_domain_get_of_node(domain)) {
if (WARN_ON(args.args_count != 1))
return -EINVAL;
*dev_id = args.args[0];
break;
}
index++;
} while (!ret);
return ret;
}
int __weak iort_pmsi_get_dev_id(struct device *dev, u32 *dev_id)
{
return -1;
}
static int its_pmsi_prepare(struct irq_domain *domain, struct device *dev,
int nvec, msi_alloc_info_t *info)
{
struct msi_domain_info *msi_info;
u32 dev_id;
int ret;
if (dev->of_node)
ret = of_pmsi_get_dev_id(domain->parent, dev, &dev_id);
else
ret = iort_pmsi_get_dev_id(dev, &dev_id);
if (ret)
return ret;
/* ITS specific DeviceID, as the core ITS ignores dev. */
info->scratchpad[0].ul = dev_id;
/*
* @domain->msi_domain_info->hwsize contains the size of the device
* domain, but vector allocation happens one by one.
*/
msi_info = msi_get_domain_info(domain);
if (msi_info->hwsize > nvec)
nvec = msi_info->hwsize;
/* Allocate at least 32 MSIs, and always as a power of 2 */
nvec = max_t(int, 32, roundup_pow_of_two(nvec));
msi_info = msi_get_domain_info(domain->parent);
return msi_info->ops->msi_prepare(domain->parent,
dev, nvec, info);
}
static bool its_init_dev_msi_info(struct device *dev, struct irq_domain *domain,
struct irq_domain *real_parent, struct msi_domain_info *info)
{
if (!msi_lib_init_dev_msi_info(dev, domain, real_parent, info))
return false;
switch(info->bus_token) {
case DOMAIN_BUS_PCI_DEVICE_MSI:
case DOMAIN_BUS_PCI_DEVICE_MSIX:
/*
* FIXME: This probably should be done after a (not yet
* existing) post domain creation callback once to make
* support for dynamic post-enable MSI-X allocations
* work without having to reevaluate the domain size
* over and over. It is known already at allocation
* time via info->hwsize.
*
* That should work perfectly fine for MSI/MSI-X but needs
* some thoughts for purely software managed MSI domains
* where the index space is only limited artificially via
* %MSI_MAX_INDEX.
*/
info->ops->msi_prepare = its_pci_msi_prepare;
break;
case DOMAIN_BUS_DEVICE_MSI:
case DOMAIN_BUS_WIRED_TO_MSI:
/*
* FIXME: See the above PCI prepare comment. The domain
* size is also known at domain creation time.
*/
info->ops->msi_prepare = its_pmsi_prepare;
break;
default:
/* Confused. How did the lib return true? */
WARN_ON_ONCE(1);
return false;
}
return true;
}
const struct msi_parent_ops gic_v3_its_msi_parent_ops = {
.supported_flags = ITS_MSI_FLAGS_SUPPORTED,
.required_flags = ITS_MSI_FLAGS_REQUIRED,
.bus_select_token = DOMAIN_BUS_NEXUS,
.bus_select_mask = MATCH_PCI_MSI | MATCH_PLATFORM_MSI,
.prefix = "ITS-",
.init_dev_msi_info = its_init_dev_msi_info,
};