Skip to content

Commit

Permalink
PCI/MSI: Enhance core to support hierarchy irqdomain
Browse files Browse the repository at this point in the history
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 <[email protected]>
Cc: Bjorn Helgaas <[email protected]>
Cc: Grant Likely <[email protected]>
Cc: Marc Zyngier <[email protected]>
Cc: Yingjoe Chen <[email protected]>
Cc: Yijing Wang <[email protected]>
Signed-off-by: Thomas Gleixner <[email protected]>
  • Loading branch information
Jiang Liu authored and KAGA-KOKO committed Nov 23, 2014
1 parent 38b6a1c commit 3878eae
Show file tree
Hide file tree
Showing 3 changed files with 187 additions and 0 deletions.
5 changes: 5 additions & 0 deletions drivers/pci/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
168 changes: 168 additions & 0 deletions drivers/pci/msi.c
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#include <linux/errno.h>
#include <linux/io.h>
#include <linux/slab.h>
#include <linux/irqdomain.h>

#include "pci.h"

Expand Down Expand Up @@ -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 */
14 changes: 14 additions & 0 deletions include/linux/msi.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 */

0 comments on commit 3878eae

Please sign in to comment.