PCI/MSI: Enhance core to support hierarchy irqdomain
Enhance PCI MSI core to support hierarchy irqdomain, so the common code can be shared across architectures. [ tglx: Extracted and combined from several patches ] Signed-off-by: Jiang Liu <jiang.liu@linux.intel.com> Cc: Bjorn Helgaas <bhelgaas@google.com> Cc: Grant Likely <grant.likely@linaro.org> Cc: Marc Zyngier <marc.zyngier@arm.com> Cc: Yingjoe Chen <yingjoe.chen@mediatek.com> Cc: Yijing Wang <wangyijing@huawei.com> Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
This commit is contained in:
parent
38b6a1cf3e
commit
3878eaefb8
|
@ -17,6 +17,11 @@ config PCI_MSI
|
|||
|
||||
If you don't know what to do here, say Y.
|
||||
|
||||
config PCI_MSI_IRQ_DOMAIN
|
||||
bool
|
||||
depends on PCI_MSI
|
||||
select GENERIC_MSI_IRQ_DOMAIN
|
||||
|
||||
config PCI_DEBUG
|
||||
bool "PCI Debugging"
|
||||
depends on PCI && DEBUG_KERNEL
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
#include <linux/errno.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/irqdomain.h>
|
||||
|
||||
#include "pci.h"
|
||||
|
||||
|
@ -1091,3 +1092,170 @@ int pci_enable_msix_range(struct pci_dev *dev, struct msix_entry *entries,
|
|||
return nvec;
|
||||
}
|
||||
EXPORT_SYMBOL(pci_enable_msix_range);
|
||||
|
||||
#ifdef CONFIG_PCI_MSI_IRQ_DOMAIN
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
void pci_msi_domain_write_msg(struct irq_data *irq_data, struct msi_msg *msg)
|
||||
{
|
||||
struct msi_desc *desc = irq_data->msi_desc;
|
||||
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* pci_msi_domain_calc_hwirq - Generate a unique ID for an MSI source
|
||||
* @dev: Pointer to the PCI device
|
||||
* @desc: Pointer to the msi descriptor
|
||||
*
|
||||
* The ID number is only used within the irqdomain.
|
||||
*/
|
||||
irq_hw_number_t pci_msi_domain_calc_hwirq(struct pci_dev *dev,
|
||||
struct msi_desc *desc)
|
||||
{
|
||||
return (irq_hw_number_t)desc->msi_attrib.entry_nr |
|
||||
PCI_DEVID(dev->bus->number, dev->devfn) << 11 |
|
||||
(pci_domain_nr(dev->bus) & 0xFFFFFFFF) << 27;
|
||||
}
|
||||
|
||||
static inline bool pci_msi_desc_is_multi_msi(struct msi_desc *desc)
|
||||
{
|
||||
return !desc->msi_attrib.is_msix && desc->nvec_used > 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* pci_msi_domain_check_cap - Verify that @domain supports the capabilities for @dev
|
||||
* @domain: The interrupt domain to check
|
||||
* @info: The domain info for verification
|
||||
* @dev: The device to check
|
||||
*
|
||||
* Returns:
|
||||
* 0 if the functionality is supported
|
||||
* 1 if Multi MSI is requested, but the domain does not support it
|
||||
* -ENOTSUPP otherwise
|
||||
*/
|
||||
int pci_msi_domain_check_cap(struct irq_domain *domain,
|
||||
struct msi_domain_info *info, struct device *dev)
|
||||
{
|
||||
struct msi_desc *desc = first_pci_msi_entry(to_pci_dev(dev));
|
||||
|
||||
/* Special handling to support pci_enable_msi_range() */
|
||||
if (pci_msi_desc_is_multi_msi(desc) &&
|
||||
!(info->flags & MSI_FLAG_MULTI_PCI_MSI))
|
||||
return 1;
|
||||
else if (desc->msi_attrib.is_msix && !(info->flags & MSI_FLAG_PCI_MSIX))
|
||||
return -ENOTSUPP;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int pci_msi_domain_handle_error(struct irq_domain *domain,
|
||||
struct msi_desc *desc, int error)
|
||||
{
|
||||
/* Special handling to support pci_enable_msi_range() */
|
||||
if (pci_msi_desc_is_multi_msi(desc) && error == -ENOSPC)
|
||||
return 1;
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
#ifdef GENERIC_MSI_DOMAIN_OPS
|
||||
static void pci_msi_domain_set_desc(msi_alloc_info_t *arg,
|
||||
struct msi_desc *desc)
|
||||
{
|
||||
arg->desc = desc;
|
||||
arg->hwirq = pci_msi_domain_calc_hwirq(msi_desc_to_pci_dev(desc),
|
||||
desc);
|
||||
}
|
||||
#else
|
||||
#define pci_msi_domain_set_desc NULL
|
||||
#endif
|
||||
|
||||
static struct msi_domain_ops pci_msi_domain_ops_default = {
|
||||
.set_desc = pci_msi_domain_set_desc,
|
||||
.msi_check = pci_msi_domain_check_cap,
|
||||
.handle_error = pci_msi_domain_handle_error,
|
||||
};
|
||||
|
||||
static void pci_msi_domain_update_dom_ops(struct msi_domain_info *info)
|
||||
{
|
||||
struct msi_domain_ops *ops = info->ops;
|
||||
|
||||
if (ops == NULL) {
|
||||
info->ops = &pci_msi_domain_ops_default;
|
||||
} else {
|
||||
if (ops->set_desc == NULL)
|
||||
ops->set_desc = pci_msi_domain_set_desc;
|
||||
if (ops->msi_check == NULL)
|
||||
ops->msi_check = pci_msi_domain_check_cap;
|
||||
if (ops->handle_error == NULL)
|
||||
ops->handle_error = pci_msi_domain_handle_error;
|
||||
}
|
||||
}
|
||||
|
||||
static void pci_msi_domain_update_chip_ops(struct msi_domain_info *info)
|
||||
{
|
||||
struct irq_chip *chip = info->chip;
|
||||
|
||||
BUG_ON(!chip);
|
||||
if (!chip->irq_write_msi_msg)
|
||||
chip->irq_write_msi_msg = pci_msi_domain_write_msg;
|
||||
}
|
||||
|
||||
/**
|
||||
* pci_msi_create_irq_domain - Creat a MSI interrupt domain
|
||||
* @node: Optional device-tree node of the interrupt controller
|
||||
* @info: MSI domain info
|
||||
* @parent: Parent irq domain
|
||||
*
|
||||
* Updates the domain and chip ops and creates a MSI interrupt domain.
|
||||
*
|
||||
* Returns:
|
||||
* A domain pointer or NULL in case of failure.
|
||||
*/
|
||||
struct irq_domain *pci_msi_create_irq_domain(struct device_node *node,
|
||||
struct msi_domain_info *info,
|
||||
struct irq_domain *parent)
|
||||
{
|
||||
if (info->flags & MSI_FLAG_USE_DEF_DOM_OPS)
|
||||
pci_msi_domain_update_dom_ops(info);
|
||||
if (info->flags & MSI_FLAG_USE_DEF_CHIP_OPS)
|
||||
pci_msi_domain_update_chip_ops(info);
|
||||
|
||||
return msi_create_irq_domain(node, info, parent);
|
||||
}
|
||||
|
||||
/**
|
||||
* pci_msi_domain_alloc_irqs - Allocate interrupts for @dev in @domain
|
||||
* @domain: The interrupt domain to allocate from
|
||||
* @dev: The device for which to allocate
|
||||
* @nvec: The number of interrupts to allocate
|
||||
* @type: Unused to allow simpler migration from the arch_XXX interfaces
|
||||
*
|
||||
* Returns:
|
||||
* A virtual interrupt number or an error code in case of failure
|
||||
*/
|
||||
int pci_msi_domain_alloc_irqs(struct irq_domain *domain, struct pci_dev *dev,
|
||||
int nvec, int type)
|
||||
{
|
||||
return msi_domain_alloc_irqs(domain, &dev->dev, nvec);
|
||||
}
|
||||
|
||||
/**
|
||||
* pci_msi_domain_free_irqs - Free interrupts for @dev in @domain
|
||||
* @domain: The interrupt domain
|
||||
* @dev: The device for which to free interrupts
|
||||
*/
|
||||
void pci_msi_domain_free_irqs(struct irq_domain *domain, struct pci_dev *dev)
|
||||
{
|
||||
msi_domain_free_irqs(domain, &dev->dev);
|
||||
}
|
||||
#endif /* CONFIG_PCI_MSI_IRQ_DOMAIN */
|
||||
|
|
|
@ -220,4 +220,18 @@ struct msi_domain_info *msi_get_domain_info(struct irq_domain *domain);
|
|||
|
||||
#endif /* CONFIG_GENERIC_MSI_IRQ_DOMAIN */
|
||||
|
||||
#ifdef CONFIG_PCI_MSI_IRQ_DOMAIN
|
||||
void pci_msi_domain_write_msg(struct irq_data *irq_data, struct msi_msg *msg);
|
||||
struct irq_domain *pci_msi_create_irq_domain(struct device_node *node,
|
||||
struct msi_domain_info *info,
|
||||
struct irq_domain *parent);
|
||||
int pci_msi_domain_alloc_irqs(struct irq_domain *domain, struct pci_dev *dev,
|
||||
int nvec, int type);
|
||||
void pci_msi_domain_free_irqs(struct irq_domain *domain, struct pci_dev *dev);
|
||||
irq_hw_number_t pci_msi_domain_calc_hwirq(struct pci_dev *dev,
|
||||
struct msi_desc *desc);
|
||||
int pci_msi_domain_check_cap(struct irq_domain *domain,
|
||||
struct msi_domain_info *info, struct device *dev);
|
||||
#endif /* CONFIG_PCI_MSI_IRQ_DOMAIN */
|
||||
|
||||
#endif /* LINUX_MSI_H */
|
||||
|
|
Loading…
Reference in New Issue