|
@@ -19,6 +19,7 @@
|
|
#include <linux/errno.h>
|
|
#include <linux/errno.h>
|
|
#include <linux/io.h>
|
|
#include <linux/io.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/slab.h>
|
|
|
|
+#include <linux/irqdomain.h>
|
|
|
|
|
|
#include "pci.h"
|
|
#include "pci.h"
|
|
|
|
|
|
@@ -1091,3 +1092,170 @@ int pci_enable_msix_range(struct pci_dev *dev, struct msix_entry *entries,
|
|
return nvec;
|
|
return nvec;
|
|
}
|
|
}
|
|
EXPORT_SYMBOL(pci_enable_msix_range);
|
|
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 */
|