root/drivers/pci/msi/irqdomain.c
// SPDX-License-Identifier: GPL-2.0
/*
 * PCI Message Signaled Interrupt (MSI) - irqdomain support
 */
#include <linux/acpi_iort.h>
#include <linux/irqdomain.h>
#include <linux/of_irq.h>

#include "msi.h"

int pci_msi_setup_msi_irqs(struct pci_dev *dev, int nvec, int type)
{
        struct irq_domain *domain;

        domain = dev_get_msi_domain(&dev->dev);
        if (domain && irq_domain_is_hierarchy(domain))
                return msi_domain_alloc_irqs_all_locked(&dev->dev, MSI_DEFAULT_DOMAIN, nvec);

        return pci_msi_legacy_setup_msi_irqs(dev, nvec, type);
}

void pci_msi_teardown_msi_irqs(struct pci_dev *dev)
{
        struct irq_domain *domain;

        domain = dev_get_msi_domain(&dev->dev);
        if (domain && irq_domain_is_hierarchy(domain)) {
                msi_domain_free_irqs_all_locked(&dev->dev, MSI_DEFAULT_DOMAIN);
        } else {
                pci_msi_legacy_teardown_msi_irqs(dev);
                msi_free_msi_descs(&dev->dev);
        }
}

/**
 * pci_msi_domain_write_msg - Helper to write MSI message to PCI config space
 * @irq_data:   Pointer to interrupt data of the MSI interrupt
 * @msg:        Pointer to the message
 */
static void pci_msi_domain_write_msg(struct irq_data *irq_data, struct msi_msg *msg)
{
        struct msi_desc *desc = irq_data_get_msi_desc(irq_data);

        /*
         * For MSI-X desc->irq is always equal to irq_data->irq. For
         * MSI only the first interrupt of MULTI MSI passes the test.
         */
        if (desc->irq == irq_data->irq)
                __pci_write_msi_msg(desc, msg);
}

/*
 * Per device MSI[-X] domain functionality
 */
static void pci_device_domain_set_desc(msi_alloc_info_t *arg, struct msi_desc *desc)
{
        arg->desc = desc;
        arg->hwirq = desc->msi_index;
}

static void cond_shutdown_parent(struct irq_data *data)
{
        struct msi_domain_info *info = data->domain->host_data;

        if (unlikely(info->flags & MSI_FLAG_PCI_MSI_STARTUP_PARENT))
                irq_chip_shutdown_parent(data);
        else if (unlikely(info->flags & MSI_FLAG_PCI_MSI_MASK_PARENT))
                irq_chip_mask_parent(data);
}

static unsigned int cond_startup_parent(struct irq_data *data)
{
        struct msi_domain_info *info = data->domain->host_data;

        if (unlikely(info->flags & MSI_FLAG_PCI_MSI_STARTUP_PARENT))
                return irq_chip_startup_parent(data);
        else if (unlikely(info->flags & MSI_FLAG_PCI_MSI_MASK_PARENT))
                irq_chip_unmask_parent(data);

        return 0;
}

static void pci_irq_shutdown_msi(struct irq_data *data)
{
        struct msi_desc *desc = irq_data_get_msi_desc(data);

        pci_msi_mask(desc, BIT(data->irq - desc->irq));
        cond_shutdown_parent(data);
}

static unsigned int pci_irq_startup_msi(struct irq_data *data)
{
        struct msi_desc *desc = irq_data_get_msi_desc(data);
        unsigned int ret = cond_startup_parent(data);

        pci_msi_unmask(desc, BIT(data->irq - desc->irq));
        return ret;
}

static void pci_irq_mask_msi(struct irq_data *data)
{
        struct msi_desc *desc = irq_data_get_msi_desc(data);

        pci_msi_mask(desc, BIT(data->irq - desc->irq));
}

static void pci_irq_unmask_msi(struct irq_data *data)
{
        struct msi_desc *desc = irq_data_get_msi_desc(data);

        pci_msi_unmask(desc, BIT(data->irq - desc->irq));
}

#ifdef CONFIG_GENERIC_IRQ_RESERVATION_MODE
# define MSI_REACTIVATE         MSI_FLAG_MUST_REACTIVATE
#else
# define MSI_REACTIVATE         0
#endif

#define MSI_COMMON_FLAGS        (MSI_FLAG_FREE_MSI_DESCS |      \
                                 MSI_FLAG_ACTIVATE_EARLY |      \
                                 MSI_FLAG_DEV_SYSFS |           \
                                 MSI_REACTIVATE)

static const struct msi_domain_template pci_msi_template = {
        .chip = {
                .name                   = "PCI-MSI",
                .irq_startup            = pci_irq_startup_msi,
                .irq_shutdown           = pci_irq_shutdown_msi,
                .irq_mask               = pci_irq_mask_msi,
                .irq_unmask             = pci_irq_unmask_msi,
                .irq_write_msi_msg      = pci_msi_domain_write_msg,
                .flags                  = IRQCHIP_ONESHOT_SAFE,
        },

        .ops = {
                .set_desc               = pci_device_domain_set_desc,
        },

        .info = {
                .flags                  = MSI_COMMON_FLAGS | MSI_FLAG_MULTI_PCI_MSI,
                .bus_token              = DOMAIN_BUS_PCI_DEVICE_MSI,
        },
};

static void pci_irq_shutdown_msix(struct irq_data *data)
{
        pci_msix_mask(irq_data_get_msi_desc(data));
        cond_shutdown_parent(data);
}

static unsigned int pci_irq_startup_msix(struct irq_data *data)
{
        unsigned int ret = cond_startup_parent(data);

        pci_msix_unmask(irq_data_get_msi_desc(data));
        return ret;
}

static void pci_irq_mask_msix(struct irq_data *data)
{
        pci_msix_mask(irq_data_get_msi_desc(data));
}

static void pci_irq_unmask_msix(struct irq_data *data)
{
        pci_msix_unmask(irq_data_get_msi_desc(data));
}

void pci_msix_prepare_desc(struct irq_domain *domain, msi_alloc_info_t *arg,
                           struct msi_desc *desc)
{
        /* Don't fiddle with preallocated MSI descriptors */
        if (!desc->pci.mask_base)
                msix_prepare_msi_desc(to_pci_dev(desc->dev), desc);
}
EXPORT_SYMBOL_GPL(pci_msix_prepare_desc);

static const struct msi_domain_template pci_msix_template = {
        .chip = {
                .name                   = "PCI-MSIX",
                .irq_startup            = pci_irq_startup_msix,
                .irq_shutdown           = pci_irq_shutdown_msix,
                .irq_mask               = pci_irq_mask_msix,
                .irq_unmask             = pci_irq_unmask_msix,
                .irq_write_msi_msg      = pci_msi_domain_write_msg,
                .flags                  = IRQCHIP_ONESHOT_SAFE,
        },

        .ops = {
                .prepare_desc           = pci_msix_prepare_desc,
                .set_desc               = pci_device_domain_set_desc,
        },

        .info = {
                .flags                  = MSI_COMMON_FLAGS | MSI_FLAG_PCI_MSIX |
                                          MSI_FLAG_PCI_MSIX_ALLOC_DYN,
                .bus_token              = DOMAIN_BUS_PCI_DEVICE_MSIX,
        },
};

static bool pci_match_device_domain(struct pci_dev *pdev, enum irq_domain_bus_token bus_token)
{
        return msi_match_device_irq_domain(&pdev->dev, MSI_DEFAULT_DOMAIN, bus_token);
}

static bool pci_create_device_domain(struct pci_dev *pdev, const struct msi_domain_template *tmpl,
                                     unsigned int hwsize)
{
        struct irq_domain *domain = dev_get_msi_domain(&pdev->dev);

        if (!domain || !irq_domain_is_msi_parent(domain))
                return true;

        return msi_create_device_irq_domain(&pdev->dev, MSI_DEFAULT_DOMAIN, tmpl,
                                            hwsize, NULL, NULL);
}

/**
 * pci_setup_msi_device_domain - Setup a device MSI interrupt domain
 * @pdev:       The PCI device to create the domain on
 * @hwsize:     The maximum number of MSI vectors
 *
 * Return:
 *  True when:
 *      - The device does not have a MSI parent irq domain associated,
 *        which keeps the legacy architecture specific and the global
 *        PCI/MSI domain models working
 *      - The MSI domain exists already
 *      - The MSI domain was successfully allocated
 *  False when:
 *      - MSI-X is enabled
 *      - The domain creation fails.
 *
 * The created MSI domain is preserved until:
 *      - The device is removed
 *      - MSI is disabled and a MSI-X domain is created
 */
bool pci_setup_msi_device_domain(struct pci_dev *pdev, unsigned int hwsize)
{
        if (WARN_ON_ONCE(pdev->msix_enabled))
                return false;

        if (pci_match_device_domain(pdev, DOMAIN_BUS_PCI_DEVICE_MSI))
                return true;
        if (pci_match_device_domain(pdev, DOMAIN_BUS_PCI_DEVICE_MSIX))
                msi_remove_device_irq_domain(&pdev->dev, MSI_DEFAULT_DOMAIN);

        return pci_create_device_domain(pdev, &pci_msi_template, hwsize);
}

/**
 * pci_setup_msix_device_domain - Setup a device MSI-X interrupt domain
 * @pdev:       The PCI device to create the domain on
 * @hwsize:     The size of the MSI-X vector table
 *
 * Return:
 *  True when:
 *      - The device does not have a MSI parent irq domain associated,
 *        which keeps the legacy architecture specific and the global
 *        PCI/MSI domain models working
 *      - The MSI-X domain exists already
 *      - The MSI-X domain was successfully allocated
 *  False when:
 *      - MSI is enabled
 *      - The domain creation fails.
 *
 * The created MSI-X domain is preserved until:
 *      - The device is removed
 *      - MSI-X is disabled and a MSI domain is created
 */
bool pci_setup_msix_device_domain(struct pci_dev *pdev, unsigned int hwsize)
{
        if (WARN_ON_ONCE(pdev->msi_enabled))
                return false;

        if (pci_match_device_domain(pdev, DOMAIN_BUS_PCI_DEVICE_MSIX))
                return true;
        if (pci_match_device_domain(pdev, DOMAIN_BUS_PCI_DEVICE_MSI))
                msi_remove_device_irq_domain(&pdev->dev, MSI_DEFAULT_DOMAIN);

        return pci_create_device_domain(pdev, &pci_msix_template, hwsize);
}

/**
 * pci_msi_domain_supports - Check for support of a particular feature flag
 * @pdev:               The PCI device to operate on
 * @feature_mask:       The feature mask to check for (full match)
 * @mode:               If ALLOW_LEGACY this grants the feature when there is no irq domain
 *                      associated to the device. If DENY_LEGACY the lack of an irq domain
 *                      makes the feature unsupported
 */
bool pci_msi_domain_supports(struct pci_dev *pdev, unsigned int feature_mask,
                             enum support_mode mode)
{
        struct msi_domain_info *info;
        struct irq_domain *domain;
        unsigned int supported;

        domain = dev_get_msi_domain(&pdev->dev);

        if (!domain || !irq_domain_is_hierarchy(domain)) {
                if (IS_ENABLED(CONFIG_PCI_MSI_ARCH_FALLBACKS))
                        return mode == ALLOW_LEGACY;
                return false;
        }

        if (!irq_domain_is_msi_parent(domain)) {
                /*
                 * For "global" PCI/MSI interrupt domains the associated
                 * msi_domain_info::flags is the authoritative source of
                 * information.
                 */
                info = domain->host_data;
                supported = info->flags;
        } else {
                /*
                 * For MSI parent domains the supported feature set
                 * is available in the parent ops. This makes checks
                 * possible before actually instantiating the
                 * per device domain because the parent is never
                 * expanding the PCI/MSI functionality.
                 */
                supported = domain->msi_parent_ops->supported_flags;
        }

        return (supported & feature_mask) == feature_mask;
}

/*
 * Users of the generic MSI infrastructure expect a device to have a single ID,
 * so with DMA aliases we have to pick the least-worst compromise. Devices with
 * DMA phantom functions tend to still emit MSIs from the real function number,
 * so we ignore those and only consider topological aliases where either the
 * alias device or RID appears on a different bus number. We also make the
 * reasonable assumption that bridges are walked in an upstream direction (so
 * the last one seen wins), and the much braver assumption that the most likely
 * case is that of PCI->PCIe so we should always use the alias RID. This echoes
 * the logic from intel_irq_remapping's set_msi_sid(), which presumably works
 * well enough in practice; in the face of the horrible PCIe<->PCI-X conditions
 * for taking ownership all we can really do is close our eyes and hope...
 */
static int get_msi_id_cb(struct pci_dev *pdev, u16 alias, void *data)
{
        u32 *pa = data;
        u8 bus = PCI_BUS_NUM(*pa);

        if (pdev->bus->number != bus || PCI_BUS_NUM(alias) != bus)
                *pa = alias;

        return 0;
}

/**
 * pci_msi_domain_get_msi_rid - Get the MSI requester id (RID)
 * @domain:     The interrupt domain
 * @pdev:       The PCI device.
 *
 * The RID for a device is formed from the alias, with a firmware
 * supplied mapping applied
 *
 * Returns: The RID.
 */
u32 pci_msi_domain_get_msi_rid(struct irq_domain *domain, struct pci_dev *pdev)
{
        struct device_node *of_node;
        u32 rid = pci_dev_id(pdev);

        pci_for_each_dma_alias(pdev, get_msi_id_cb, &rid);

        of_node = irq_domain_get_of_node(domain);
        rid = of_node ? of_msi_xlate(&pdev->dev, &of_node, rid) :
                        iort_msi_map_id(&pdev->dev, rid);

        return rid;
}

/**
 * pci_msi_map_rid_ctlr_node - Get the MSI controller fwnode_handle and MSI requester id (RID)
 * @domain:     The interrupt domain
 * @pdev:       The PCI device
 * @node:       Pointer to store the MSI controller fwnode_handle
 *
 * Use the firmware data to find the MSI controller fwnode_handle for @pdev.
 * If found map the RID and initialize @node with it. @node value must
 * be set to NULL on entry.
 *
 * Returns: The RID.
 */
u32 pci_msi_map_rid_ctlr_node(struct irq_domain *domain, struct pci_dev *pdev,
                              struct fwnode_handle **node)
{
        u32 rid = pci_dev_id(pdev);

        pci_for_each_dma_alias(pdev, get_msi_id_cb, &rid);

        /* Check whether the domain fwnode is an OF node */
        if (irq_domain_get_of_node(domain)) {
                struct device_node *msi_ctlr_node = NULL;

                rid = of_msi_xlate(&pdev->dev, &msi_ctlr_node, rid);
                if (msi_ctlr_node)
                        *node = of_fwnode_handle(msi_ctlr_node);
        } else {
                rid = iort_msi_xlate(&pdev->dev, rid, node);
        }

        return rid;
}

/**
 * pci_msi_get_device_domain - Get the MSI domain for a given PCI device
 * @pdev:       The PCI device
 *
 * Use the firmware data to find a device-specific MSI domain
 * (i.e. not one that is set as a default).
 *
 * Returns: The corresponding MSI domain or NULL if none has been found.
 */
struct irq_domain *pci_msi_get_device_domain(struct pci_dev *pdev)
{
        struct irq_domain *dom;
        u32 rid = pci_dev_id(pdev);

        pci_for_each_dma_alias(pdev, get_msi_id_cb, &rid);
        dom = of_msi_map_get_device_domain(&pdev->dev, rid, DOMAIN_BUS_PCI_MSI);
        if (!dom)
                dom = iort_get_device_domain(&pdev->dev, rid,
                                             DOMAIN_BUS_PCI_MSI);
        return dom;
}