162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * PCI Message Signaled Interrupt (MSI) - irqdomain support 462306a36Sopenharmony_ci */ 562306a36Sopenharmony_ci#include <linux/acpi_iort.h> 662306a36Sopenharmony_ci#include <linux/irqdomain.h> 762306a36Sopenharmony_ci#include <linux/of_irq.h> 862306a36Sopenharmony_ci 962306a36Sopenharmony_ci#include "msi.h" 1062306a36Sopenharmony_ci 1162306a36Sopenharmony_ciint pci_msi_setup_msi_irqs(struct pci_dev *dev, int nvec, int type) 1262306a36Sopenharmony_ci{ 1362306a36Sopenharmony_ci struct irq_domain *domain; 1462306a36Sopenharmony_ci 1562306a36Sopenharmony_ci domain = dev_get_msi_domain(&dev->dev); 1662306a36Sopenharmony_ci if (domain && irq_domain_is_hierarchy(domain)) 1762306a36Sopenharmony_ci return msi_domain_alloc_irqs_all_locked(&dev->dev, MSI_DEFAULT_DOMAIN, nvec); 1862306a36Sopenharmony_ci 1962306a36Sopenharmony_ci return pci_msi_legacy_setup_msi_irqs(dev, nvec, type); 2062306a36Sopenharmony_ci} 2162306a36Sopenharmony_ci 2262306a36Sopenharmony_civoid pci_msi_teardown_msi_irqs(struct pci_dev *dev) 2362306a36Sopenharmony_ci{ 2462306a36Sopenharmony_ci struct irq_domain *domain; 2562306a36Sopenharmony_ci 2662306a36Sopenharmony_ci domain = dev_get_msi_domain(&dev->dev); 2762306a36Sopenharmony_ci if (domain && irq_domain_is_hierarchy(domain)) { 2862306a36Sopenharmony_ci msi_domain_free_irqs_all_locked(&dev->dev, MSI_DEFAULT_DOMAIN); 2962306a36Sopenharmony_ci } else { 3062306a36Sopenharmony_ci pci_msi_legacy_teardown_msi_irqs(dev); 3162306a36Sopenharmony_ci msi_free_msi_descs(&dev->dev); 3262306a36Sopenharmony_ci } 3362306a36Sopenharmony_ci} 3462306a36Sopenharmony_ci 3562306a36Sopenharmony_ci/** 3662306a36Sopenharmony_ci * pci_msi_domain_write_msg - Helper to write MSI message to PCI config space 3762306a36Sopenharmony_ci * @irq_data: Pointer to interrupt data of the MSI interrupt 3862306a36Sopenharmony_ci * @msg: Pointer to the message 3962306a36Sopenharmony_ci */ 4062306a36Sopenharmony_cistatic void pci_msi_domain_write_msg(struct irq_data *irq_data, struct msi_msg *msg) 4162306a36Sopenharmony_ci{ 4262306a36Sopenharmony_ci struct msi_desc *desc = irq_data_get_msi_desc(irq_data); 4362306a36Sopenharmony_ci 4462306a36Sopenharmony_ci /* 4562306a36Sopenharmony_ci * For MSI-X desc->irq is always equal to irq_data->irq. For 4662306a36Sopenharmony_ci * MSI only the first interrupt of MULTI MSI passes the test. 4762306a36Sopenharmony_ci */ 4862306a36Sopenharmony_ci if (desc->irq == irq_data->irq) 4962306a36Sopenharmony_ci __pci_write_msi_msg(desc, msg); 5062306a36Sopenharmony_ci} 5162306a36Sopenharmony_ci 5262306a36Sopenharmony_ci/** 5362306a36Sopenharmony_ci * pci_msi_domain_calc_hwirq - Generate a unique ID for an MSI source 5462306a36Sopenharmony_ci * @desc: Pointer to the MSI descriptor 5562306a36Sopenharmony_ci * 5662306a36Sopenharmony_ci * The ID number is only used within the irqdomain. 5762306a36Sopenharmony_ci */ 5862306a36Sopenharmony_cistatic irq_hw_number_t pci_msi_domain_calc_hwirq(struct msi_desc *desc) 5962306a36Sopenharmony_ci{ 6062306a36Sopenharmony_ci struct pci_dev *dev = msi_desc_to_pci_dev(desc); 6162306a36Sopenharmony_ci 6262306a36Sopenharmony_ci return (irq_hw_number_t)desc->msi_index | 6362306a36Sopenharmony_ci pci_dev_id(dev) << 11 | 6462306a36Sopenharmony_ci ((irq_hw_number_t)(pci_domain_nr(dev->bus) & 0xFFFFFFFF)) << 27; 6562306a36Sopenharmony_ci} 6662306a36Sopenharmony_ci 6762306a36Sopenharmony_cistatic void pci_msi_domain_set_desc(msi_alloc_info_t *arg, 6862306a36Sopenharmony_ci struct msi_desc *desc) 6962306a36Sopenharmony_ci{ 7062306a36Sopenharmony_ci arg->desc = desc; 7162306a36Sopenharmony_ci arg->hwirq = pci_msi_domain_calc_hwirq(desc); 7262306a36Sopenharmony_ci} 7362306a36Sopenharmony_ci 7462306a36Sopenharmony_cistatic struct msi_domain_ops pci_msi_domain_ops_default = { 7562306a36Sopenharmony_ci .set_desc = pci_msi_domain_set_desc, 7662306a36Sopenharmony_ci}; 7762306a36Sopenharmony_ci 7862306a36Sopenharmony_cistatic void pci_msi_domain_update_dom_ops(struct msi_domain_info *info) 7962306a36Sopenharmony_ci{ 8062306a36Sopenharmony_ci struct msi_domain_ops *ops = info->ops; 8162306a36Sopenharmony_ci 8262306a36Sopenharmony_ci if (ops == NULL) { 8362306a36Sopenharmony_ci info->ops = &pci_msi_domain_ops_default; 8462306a36Sopenharmony_ci } else { 8562306a36Sopenharmony_ci if (ops->set_desc == NULL) 8662306a36Sopenharmony_ci ops->set_desc = pci_msi_domain_set_desc; 8762306a36Sopenharmony_ci } 8862306a36Sopenharmony_ci} 8962306a36Sopenharmony_ci 9062306a36Sopenharmony_cistatic void pci_msi_domain_update_chip_ops(struct msi_domain_info *info) 9162306a36Sopenharmony_ci{ 9262306a36Sopenharmony_ci struct irq_chip *chip = info->chip; 9362306a36Sopenharmony_ci 9462306a36Sopenharmony_ci BUG_ON(!chip); 9562306a36Sopenharmony_ci if (!chip->irq_write_msi_msg) 9662306a36Sopenharmony_ci chip->irq_write_msi_msg = pci_msi_domain_write_msg; 9762306a36Sopenharmony_ci if (!chip->irq_mask) 9862306a36Sopenharmony_ci chip->irq_mask = pci_msi_mask_irq; 9962306a36Sopenharmony_ci if (!chip->irq_unmask) 10062306a36Sopenharmony_ci chip->irq_unmask = pci_msi_unmask_irq; 10162306a36Sopenharmony_ci} 10262306a36Sopenharmony_ci 10362306a36Sopenharmony_ci/** 10462306a36Sopenharmony_ci * pci_msi_create_irq_domain - Create a MSI interrupt domain 10562306a36Sopenharmony_ci * @fwnode: Optional fwnode of the interrupt controller 10662306a36Sopenharmony_ci * @info: MSI domain info 10762306a36Sopenharmony_ci * @parent: Parent irq domain 10862306a36Sopenharmony_ci * 10962306a36Sopenharmony_ci * Updates the domain and chip ops and creates a MSI interrupt domain. 11062306a36Sopenharmony_ci * 11162306a36Sopenharmony_ci * Returns: 11262306a36Sopenharmony_ci * A domain pointer or NULL in case of failure. 11362306a36Sopenharmony_ci */ 11462306a36Sopenharmony_cistruct irq_domain *pci_msi_create_irq_domain(struct fwnode_handle *fwnode, 11562306a36Sopenharmony_ci struct msi_domain_info *info, 11662306a36Sopenharmony_ci struct irq_domain *parent) 11762306a36Sopenharmony_ci{ 11862306a36Sopenharmony_ci if (WARN_ON(info->flags & MSI_FLAG_LEVEL_CAPABLE)) 11962306a36Sopenharmony_ci info->flags &= ~MSI_FLAG_LEVEL_CAPABLE; 12062306a36Sopenharmony_ci 12162306a36Sopenharmony_ci if (info->flags & MSI_FLAG_USE_DEF_DOM_OPS) 12262306a36Sopenharmony_ci pci_msi_domain_update_dom_ops(info); 12362306a36Sopenharmony_ci if (info->flags & MSI_FLAG_USE_DEF_CHIP_OPS) 12462306a36Sopenharmony_ci pci_msi_domain_update_chip_ops(info); 12562306a36Sopenharmony_ci 12662306a36Sopenharmony_ci /* Let the core code free MSI descriptors when freeing interrupts */ 12762306a36Sopenharmony_ci info->flags |= MSI_FLAG_FREE_MSI_DESCS; 12862306a36Sopenharmony_ci 12962306a36Sopenharmony_ci info->flags |= MSI_FLAG_ACTIVATE_EARLY | MSI_FLAG_DEV_SYSFS; 13062306a36Sopenharmony_ci if (IS_ENABLED(CONFIG_GENERIC_IRQ_RESERVATION_MODE)) 13162306a36Sopenharmony_ci info->flags |= MSI_FLAG_MUST_REACTIVATE; 13262306a36Sopenharmony_ci 13362306a36Sopenharmony_ci /* PCI-MSI is oneshot-safe */ 13462306a36Sopenharmony_ci info->chip->flags |= IRQCHIP_ONESHOT_SAFE; 13562306a36Sopenharmony_ci /* Let the core update the bus token */ 13662306a36Sopenharmony_ci info->bus_token = DOMAIN_BUS_PCI_MSI; 13762306a36Sopenharmony_ci 13862306a36Sopenharmony_ci return msi_create_irq_domain(fwnode, info, parent); 13962306a36Sopenharmony_ci} 14062306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(pci_msi_create_irq_domain); 14162306a36Sopenharmony_ci 14262306a36Sopenharmony_ci/* 14362306a36Sopenharmony_ci * Per device MSI[-X] domain functionality 14462306a36Sopenharmony_ci */ 14562306a36Sopenharmony_cistatic void pci_device_domain_set_desc(msi_alloc_info_t *arg, struct msi_desc *desc) 14662306a36Sopenharmony_ci{ 14762306a36Sopenharmony_ci arg->desc = desc; 14862306a36Sopenharmony_ci arg->hwirq = desc->msi_index; 14962306a36Sopenharmony_ci} 15062306a36Sopenharmony_ci 15162306a36Sopenharmony_cistatic void pci_irq_mask_msi(struct irq_data *data) 15262306a36Sopenharmony_ci{ 15362306a36Sopenharmony_ci struct msi_desc *desc = irq_data_get_msi_desc(data); 15462306a36Sopenharmony_ci 15562306a36Sopenharmony_ci pci_msi_mask(desc, BIT(data->irq - desc->irq)); 15662306a36Sopenharmony_ci} 15762306a36Sopenharmony_ci 15862306a36Sopenharmony_cistatic void pci_irq_unmask_msi(struct irq_data *data) 15962306a36Sopenharmony_ci{ 16062306a36Sopenharmony_ci struct msi_desc *desc = irq_data_get_msi_desc(data); 16162306a36Sopenharmony_ci 16262306a36Sopenharmony_ci pci_msi_unmask(desc, BIT(data->irq - desc->irq)); 16362306a36Sopenharmony_ci} 16462306a36Sopenharmony_ci 16562306a36Sopenharmony_ci#ifdef CONFIG_GENERIC_IRQ_RESERVATION_MODE 16662306a36Sopenharmony_ci# define MSI_REACTIVATE MSI_FLAG_MUST_REACTIVATE 16762306a36Sopenharmony_ci#else 16862306a36Sopenharmony_ci# define MSI_REACTIVATE 0 16962306a36Sopenharmony_ci#endif 17062306a36Sopenharmony_ci 17162306a36Sopenharmony_ci#define MSI_COMMON_FLAGS (MSI_FLAG_FREE_MSI_DESCS | \ 17262306a36Sopenharmony_ci MSI_FLAG_ACTIVATE_EARLY | \ 17362306a36Sopenharmony_ci MSI_FLAG_DEV_SYSFS | \ 17462306a36Sopenharmony_ci MSI_REACTIVATE) 17562306a36Sopenharmony_ci 17662306a36Sopenharmony_cistatic const struct msi_domain_template pci_msi_template = { 17762306a36Sopenharmony_ci .chip = { 17862306a36Sopenharmony_ci .name = "PCI-MSI", 17962306a36Sopenharmony_ci .irq_mask = pci_irq_mask_msi, 18062306a36Sopenharmony_ci .irq_unmask = pci_irq_unmask_msi, 18162306a36Sopenharmony_ci .irq_write_msi_msg = pci_msi_domain_write_msg, 18262306a36Sopenharmony_ci .flags = IRQCHIP_ONESHOT_SAFE, 18362306a36Sopenharmony_ci }, 18462306a36Sopenharmony_ci 18562306a36Sopenharmony_ci .ops = { 18662306a36Sopenharmony_ci .set_desc = pci_device_domain_set_desc, 18762306a36Sopenharmony_ci }, 18862306a36Sopenharmony_ci 18962306a36Sopenharmony_ci .info = { 19062306a36Sopenharmony_ci .flags = MSI_COMMON_FLAGS | MSI_FLAG_MULTI_PCI_MSI, 19162306a36Sopenharmony_ci .bus_token = DOMAIN_BUS_PCI_DEVICE_MSI, 19262306a36Sopenharmony_ci }, 19362306a36Sopenharmony_ci}; 19462306a36Sopenharmony_ci 19562306a36Sopenharmony_cistatic void pci_irq_mask_msix(struct irq_data *data) 19662306a36Sopenharmony_ci{ 19762306a36Sopenharmony_ci pci_msix_mask(irq_data_get_msi_desc(data)); 19862306a36Sopenharmony_ci} 19962306a36Sopenharmony_ci 20062306a36Sopenharmony_cistatic void pci_irq_unmask_msix(struct irq_data *data) 20162306a36Sopenharmony_ci{ 20262306a36Sopenharmony_ci pci_msix_unmask(irq_data_get_msi_desc(data)); 20362306a36Sopenharmony_ci} 20462306a36Sopenharmony_ci 20562306a36Sopenharmony_cistatic void pci_msix_prepare_desc(struct irq_domain *domain, msi_alloc_info_t *arg, 20662306a36Sopenharmony_ci struct msi_desc *desc) 20762306a36Sopenharmony_ci{ 20862306a36Sopenharmony_ci /* Don't fiddle with preallocated MSI descriptors */ 20962306a36Sopenharmony_ci if (!desc->pci.mask_base) 21062306a36Sopenharmony_ci msix_prepare_msi_desc(to_pci_dev(desc->dev), desc); 21162306a36Sopenharmony_ci} 21262306a36Sopenharmony_ci 21362306a36Sopenharmony_cistatic const struct msi_domain_template pci_msix_template = { 21462306a36Sopenharmony_ci .chip = { 21562306a36Sopenharmony_ci .name = "PCI-MSIX", 21662306a36Sopenharmony_ci .irq_mask = pci_irq_mask_msix, 21762306a36Sopenharmony_ci .irq_unmask = pci_irq_unmask_msix, 21862306a36Sopenharmony_ci .irq_write_msi_msg = pci_msi_domain_write_msg, 21962306a36Sopenharmony_ci .flags = IRQCHIP_ONESHOT_SAFE, 22062306a36Sopenharmony_ci }, 22162306a36Sopenharmony_ci 22262306a36Sopenharmony_ci .ops = { 22362306a36Sopenharmony_ci .prepare_desc = pci_msix_prepare_desc, 22462306a36Sopenharmony_ci .set_desc = pci_device_domain_set_desc, 22562306a36Sopenharmony_ci }, 22662306a36Sopenharmony_ci 22762306a36Sopenharmony_ci .info = { 22862306a36Sopenharmony_ci .flags = MSI_COMMON_FLAGS | MSI_FLAG_PCI_MSIX | 22962306a36Sopenharmony_ci MSI_FLAG_PCI_MSIX_ALLOC_DYN, 23062306a36Sopenharmony_ci .bus_token = DOMAIN_BUS_PCI_DEVICE_MSIX, 23162306a36Sopenharmony_ci }, 23262306a36Sopenharmony_ci}; 23362306a36Sopenharmony_ci 23462306a36Sopenharmony_cistatic bool pci_match_device_domain(struct pci_dev *pdev, enum irq_domain_bus_token bus_token) 23562306a36Sopenharmony_ci{ 23662306a36Sopenharmony_ci return msi_match_device_irq_domain(&pdev->dev, MSI_DEFAULT_DOMAIN, bus_token); 23762306a36Sopenharmony_ci} 23862306a36Sopenharmony_ci 23962306a36Sopenharmony_cistatic bool pci_create_device_domain(struct pci_dev *pdev, const struct msi_domain_template *tmpl, 24062306a36Sopenharmony_ci unsigned int hwsize) 24162306a36Sopenharmony_ci{ 24262306a36Sopenharmony_ci struct irq_domain *domain = dev_get_msi_domain(&pdev->dev); 24362306a36Sopenharmony_ci 24462306a36Sopenharmony_ci if (!domain || !irq_domain_is_msi_parent(domain)) 24562306a36Sopenharmony_ci return true; 24662306a36Sopenharmony_ci 24762306a36Sopenharmony_ci return msi_create_device_irq_domain(&pdev->dev, MSI_DEFAULT_DOMAIN, tmpl, 24862306a36Sopenharmony_ci hwsize, NULL, NULL); 24962306a36Sopenharmony_ci} 25062306a36Sopenharmony_ci 25162306a36Sopenharmony_ci/** 25262306a36Sopenharmony_ci * pci_setup_msi_device_domain - Setup a device MSI interrupt domain 25362306a36Sopenharmony_ci * @pdev: The PCI device to create the domain on 25462306a36Sopenharmony_ci * 25562306a36Sopenharmony_ci * Return: 25662306a36Sopenharmony_ci * True when: 25762306a36Sopenharmony_ci * - The device does not have a MSI parent irq domain associated, 25862306a36Sopenharmony_ci * which keeps the legacy architecture specific and the global 25962306a36Sopenharmony_ci * PCI/MSI domain models working 26062306a36Sopenharmony_ci * - The MSI domain exists already 26162306a36Sopenharmony_ci * - The MSI domain was successfully allocated 26262306a36Sopenharmony_ci * False when: 26362306a36Sopenharmony_ci * - MSI-X is enabled 26462306a36Sopenharmony_ci * - The domain creation fails. 26562306a36Sopenharmony_ci * 26662306a36Sopenharmony_ci * The created MSI domain is preserved until: 26762306a36Sopenharmony_ci * - The device is removed 26862306a36Sopenharmony_ci * - MSI is disabled and a MSI-X domain is created 26962306a36Sopenharmony_ci */ 27062306a36Sopenharmony_cibool pci_setup_msi_device_domain(struct pci_dev *pdev) 27162306a36Sopenharmony_ci{ 27262306a36Sopenharmony_ci if (WARN_ON_ONCE(pdev->msix_enabled)) 27362306a36Sopenharmony_ci return false; 27462306a36Sopenharmony_ci 27562306a36Sopenharmony_ci if (pci_match_device_domain(pdev, DOMAIN_BUS_PCI_DEVICE_MSI)) 27662306a36Sopenharmony_ci return true; 27762306a36Sopenharmony_ci if (pci_match_device_domain(pdev, DOMAIN_BUS_PCI_DEVICE_MSIX)) 27862306a36Sopenharmony_ci msi_remove_device_irq_domain(&pdev->dev, MSI_DEFAULT_DOMAIN); 27962306a36Sopenharmony_ci 28062306a36Sopenharmony_ci return pci_create_device_domain(pdev, &pci_msi_template, 1); 28162306a36Sopenharmony_ci} 28262306a36Sopenharmony_ci 28362306a36Sopenharmony_ci/** 28462306a36Sopenharmony_ci * pci_setup_msix_device_domain - Setup a device MSI-X interrupt domain 28562306a36Sopenharmony_ci * @pdev: The PCI device to create the domain on 28662306a36Sopenharmony_ci * @hwsize: The size of the MSI-X vector table 28762306a36Sopenharmony_ci * 28862306a36Sopenharmony_ci * Return: 28962306a36Sopenharmony_ci * True when: 29062306a36Sopenharmony_ci * - The device does not have a MSI parent irq domain associated, 29162306a36Sopenharmony_ci * which keeps the legacy architecture specific and the global 29262306a36Sopenharmony_ci * PCI/MSI domain models working 29362306a36Sopenharmony_ci * - The MSI-X domain exists already 29462306a36Sopenharmony_ci * - The MSI-X domain was successfully allocated 29562306a36Sopenharmony_ci * False when: 29662306a36Sopenharmony_ci * - MSI is enabled 29762306a36Sopenharmony_ci * - The domain creation fails. 29862306a36Sopenharmony_ci * 29962306a36Sopenharmony_ci * The created MSI-X domain is preserved until: 30062306a36Sopenharmony_ci * - The device is removed 30162306a36Sopenharmony_ci * - MSI-X is disabled and a MSI domain is created 30262306a36Sopenharmony_ci */ 30362306a36Sopenharmony_cibool pci_setup_msix_device_domain(struct pci_dev *pdev, unsigned int hwsize) 30462306a36Sopenharmony_ci{ 30562306a36Sopenharmony_ci if (WARN_ON_ONCE(pdev->msi_enabled)) 30662306a36Sopenharmony_ci return false; 30762306a36Sopenharmony_ci 30862306a36Sopenharmony_ci if (pci_match_device_domain(pdev, DOMAIN_BUS_PCI_DEVICE_MSIX)) 30962306a36Sopenharmony_ci return true; 31062306a36Sopenharmony_ci if (pci_match_device_domain(pdev, DOMAIN_BUS_PCI_DEVICE_MSI)) 31162306a36Sopenharmony_ci msi_remove_device_irq_domain(&pdev->dev, MSI_DEFAULT_DOMAIN); 31262306a36Sopenharmony_ci 31362306a36Sopenharmony_ci return pci_create_device_domain(pdev, &pci_msix_template, hwsize); 31462306a36Sopenharmony_ci} 31562306a36Sopenharmony_ci 31662306a36Sopenharmony_ci/** 31762306a36Sopenharmony_ci * pci_msi_domain_supports - Check for support of a particular feature flag 31862306a36Sopenharmony_ci * @pdev: The PCI device to operate on 31962306a36Sopenharmony_ci * @feature_mask: The feature mask to check for (full match) 32062306a36Sopenharmony_ci * @mode: If ALLOW_LEGACY this grants the feature when there is no irq domain 32162306a36Sopenharmony_ci * associated to the device. If DENY_LEGACY the lack of an irq domain 32262306a36Sopenharmony_ci * makes the feature unsupported 32362306a36Sopenharmony_ci */ 32462306a36Sopenharmony_cibool pci_msi_domain_supports(struct pci_dev *pdev, unsigned int feature_mask, 32562306a36Sopenharmony_ci enum support_mode mode) 32662306a36Sopenharmony_ci{ 32762306a36Sopenharmony_ci struct msi_domain_info *info; 32862306a36Sopenharmony_ci struct irq_domain *domain; 32962306a36Sopenharmony_ci unsigned int supported; 33062306a36Sopenharmony_ci 33162306a36Sopenharmony_ci domain = dev_get_msi_domain(&pdev->dev); 33262306a36Sopenharmony_ci 33362306a36Sopenharmony_ci if (!domain || !irq_domain_is_hierarchy(domain)) 33462306a36Sopenharmony_ci return mode == ALLOW_LEGACY; 33562306a36Sopenharmony_ci 33662306a36Sopenharmony_ci if (!irq_domain_is_msi_parent(domain)) { 33762306a36Sopenharmony_ci /* 33862306a36Sopenharmony_ci * For "global" PCI/MSI interrupt domains the associated 33962306a36Sopenharmony_ci * msi_domain_info::flags is the authoritative source of 34062306a36Sopenharmony_ci * information. 34162306a36Sopenharmony_ci */ 34262306a36Sopenharmony_ci info = domain->host_data; 34362306a36Sopenharmony_ci supported = info->flags; 34462306a36Sopenharmony_ci } else { 34562306a36Sopenharmony_ci /* 34662306a36Sopenharmony_ci * For MSI parent domains the supported feature set 34762306a36Sopenharmony_ci * is available in the parent ops. This makes checks 34862306a36Sopenharmony_ci * possible before actually instantiating the 34962306a36Sopenharmony_ci * per device domain because the parent is never 35062306a36Sopenharmony_ci * expanding the PCI/MSI functionality. 35162306a36Sopenharmony_ci */ 35262306a36Sopenharmony_ci supported = domain->msi_parent_ops->supported_flags; 35362306a36Sopenharmony_ci } 35462306a36Sopenharmony_ci 35562306a36Sopenharmony_ci return (supported & feature_mask) == feature_mask; 35662306a36Sopenharmony_ci} 35762306a36Sopenharmony_ci 35862306a36Sopenharmony_ci/** 35962306a36Sopenharmony_ci * pci_create_ims_domain - Create a secondary IMS domain for a PCI device 36062306a36Sopenharmony_ci * @pdev: The PCI device to operate on 36162306a36Sopenharmony_ci * @template: The MSI info template which describes the domain 36262306a36Sopenharmony_ci * @hwsize: The size of the hardware entry table or 0 if the domain 36362306a36Sopenharmony_ci * is purely software managed 36462306a36Sopenharmony_ci * @data: Optional pointer to domain specific data to be stored 36562306a36Sopenharmony_ci * in msi_domain_info::data 36662306a36Sopenharmony_ci * 36762306a36Sopenharmony_ci * Return: True on success, false otherwise 36862306a36Sopenharmony_ci * 36962306a36Sopenharmony_ci * An IMS domain is expected to have the following constraints: 37062306a36Sopenharmony_ci * - The index space is managed by the core code 37162306a36Sopenharmony_ci * 37262306a36Sopenharmony_ci * - There is no requirement for consecutive index ranges 37362306a36Sopenharmony_ci * 37462306a36Sopenharmony_ci * - The interrupt chip must provide the following callbacks: 37562306a36Sopenharmony_ci * - irq_mask() 37662306a36Sopenharmony_ci * - irq_unmask() 37762306a36Sopenharmony_ci * - irq_write_msi_msg() 37862306a36Sopenharmony_ci * 37962306a36Sopenharmony_ci * - The interrupt chip must provide the following optional callbacks 38062306a36Sopenharmony_ci * when the irq_mask(), irq_unmask() and irq_write_msi_msg() callbacks 38162306a36Sopenharmony_ci * cannot operate directly on hardware, e.g. in the case that the 38262306a36Sopenharmony_ci * interrupt message store is in queue memory: 38362306a36Sopenharmony_ci * - irq_bus_lock() 38462306a36Sopenharmony_ci * - irq_bus_unlock() 38562306a36Sopenharmony_ci * 38662306a36Sopenharmony_ci * These callbacks are invoked from preemptible task context and are 38762306a36Sopenharmony_ci * allowed to sleep. In this case the mandatory callbacks above just 38862306a36Sopenharmony_ci * store the information. The irq_bus_unlock() callback is supposed 38962306a36Sopenharmony_ci * to make the change effective before returning. 39062306a36Sopenharmony_ci * 39162306a36Sopenharmony_ci * - Interrupt affinity setting is handled by the underlying parent 39262306a36Sopenharmony_ci * interrupt domain and communicated to the IMS domain via 39362306a36Sopenharmony_ci * irq_write_msi_msg(). 39462306a36Sopenharmony_ci * 39562306a36Sopenharmony_ci * The domain is automatically destroyed when the PCI device is removed. 39662306a36Sopenharmony_ci */ 39762306a36Sopenharmony_cibool pci_create_ims_domain(struct pci_dev *pdev, const struct msi_domain_template *template, 39862306a36Sopenharmony_ci unsigned int hwsize, void *data) 39962306a36Sopenharmony_ci{ 40062306a36Sopenharmony_ci struct irq_domain *domain = dev_get_msi_domain(&pdev->dev); 40162306a36Sopenharmony_ci 40262306a36Sopenharmony_ci if (!domain || !irq_domain_is_msi_parent(domain)) 40362306a36Sopenharmony_ci return false; 40462306a36Sopenharmony_ci 40562306a36Sopenharmony_ci if (template->info.bus_token != DOMAIN_BUS_PCI_DEVICE_IMS || 40662306a36Sopenharmony_ci !(template->info.flags & MSI_FLAG_ALLOC_SIMPLE_MSI_DESCS) || 40762306a36Sopenharmony_ci !(template->info.flags & MSI_FLAG_FREE_MSI_DESCS) || 40862306a36Sopenharmony_ci !template->chip.irq_mask || !template->chip.irq_unmask || 40962306a36Sopenharmony_ci !template->chip.irq_write_msi_msg || template->chip.irq_set_affinity) 41062306a36Sopenharmony_ci return false; 41162306a36Sopenharmony_ci 41262306a36Sopenharmony_ci return msi_create_device_irq_domain(&pdev->dev, MSI_SECONDARY_DOMAIN, template, 41362306a36Sopenharmony_ci hwsize, data, NULL); 41462306a36Sopenharmony_ci} 41562306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(pci_create_ims_domain); 41662306a36Sopenharmony_ci 41762306a36Sopenharmony_ci/* 41862306a36Sopenharmony_ci * Users of the generic MSI infrastructure expect a device to have a single ID, 41962306a36Sopenharmony_ci * so with DMA aliases we have to pick the least-worst compromise. Devices with 42062306a36Sopenharmony_ci * DMA phantom functions tend to still emit MSIs from the real function number, 42162306a36Sopenharmony_ci * so we ignore those and only consider topological aliases where either the 42262306a36Sopenharmony_ci * alias device or RID appears on a different bus number. We also make the 42362306a36Sopenharmony_ci * reasonable assumption that bridges are walked in an upstream direction (so 42462306a36Sopenharmony_ci * the last one seen wins), and the much braver assumption that the most likely 42562306a36Sopenharmony_ci * case is that of PCI->PCIe so we should always use the alias RID. This echoes 42662306a36Sopenharmony_ci * the logic from intel_irq_remapping's set_msi_sid(), which presumably works 42762306a36Sopenharmony_ci * well enough in practice; in the face of the horrible PCIe<->PCI-X conditions 42862306a36Sopenharmony_ci * for taking ownership all we can really do is close our eyes and hope... 42962306a36Sopenharmony_ci */ 43062306a36Sopenharmony_cistatic int get_msi_id_cb(struct pci_dev *pdev, u16 alias, void *data) 43162306a36Sopenharmony_ci{ 43262306a36Sopenharmony_ci u32 *pa = data; 43362306a36Sopenharmony_ci u8 bus = PCI_BUS_NUM(*pa); 43462306a36Sopenharmony_ci 43562306a36Sopenharmony_ci if (pdev->bus->number != bus || PCI_BUS_NUM(alias) != bus) 43662306a36Sopenharmony_ci *pa = alias; 43762306a36Sopenharmony_ci 43862306a36Sopenharmony_ci return 0; 43962306a36Sopenharmony_ci} 44062306a36Sopenharmony_ci 44162306a36Sopenharmony_ci/** 44262306a36Sopenharmony_ci * pci_msi_domain_get_msi_rid - Get the MSI requester id (RID) 44362306a36Sopenharmony_ci * @domain: The interrupt domain 44462306a36Sopenharmony_ci * @pdev: The PCI device. 44562306a36Sopenharmony_ci * 44662306a36Sopenharmony_ci * The RID for a device is formed from the alias, with a firmware 44762306a36Sopenharmony_ci * supplied mapping applied 44862306a36Sopenharmony_ci * 44962306a36Sopenharmony_ci * Returns: The RID. 45062306a36Sopenharmony_ci */ 45162306a36Sopenharmony_ciu32 pci_msi_domain_get_msi_rid(struct irq_domain *domain, struct pci_dev *pdev) 45262306a36Sopenharmony_ci{ 45362306a36Sopenharmony_ci struct device_node *of_node; 45462306a36Sopenharmony_ci u32 rid = pci_dev_id(pdev); 45562306a36Sopenharmony_ci 45662306a36Sopenharmony_ci pci_for_each_dma_alias(pdev, get_msi_id_cb, &rid); 45762306a36Sopenharmony_ci 45862306a36Sopenharmony_ci of_node = irq_domain_get_of_node(domain); 45962306a36Sopenharmony_ci rid = of_node ? of_msi_map_id(&pdev->dev, of_node, rid) : 46062306a36Sopenharmony_ci iort_msi_map_id(&pdev->dev, rid); 46162306a36Sopenharmony_ci 46262306a36Sopenharmony_ci return rid; 46362306a36Sopenharmony_ci} 46462306a36Sopenharmony_ci 46562306a36Sopenharmony_ci/** 46662306a36Sopenharmony_ci * pci_msi_get_device_domain - Get the MSI domain for a given PCI device 46762306a36Sopenharmony_ci * @pdev: The PCI device 46862306a36Sopenharmony_ci * 46962306a36Sopenharmony_ci * Use the firmware data to find a device-specific MSI domain 47062306a36Sopenharmony_ci * (i.e. not one that is set as a default). 47162306a36Sopenharmony_ci * 47262306a36Sopenharmony_ci * Returns: The corresponding MSI domain or NULL if none has been found. 47362306a36Sopenharmony_ci */ 47462306a36Sopenharmony_cistruct irq_domain *pci_msi_get_device_domain(struct pci_dev *pdev) 47562306a36Sopenharmony_ci{ 47662306a36Sopenharmony_ci struct irq_domain *dom; 47762306a36Sopenharmony_ci u32 rid = pci_dev_id(pdev); 47862306a36Sopenharmony_ci 47962306a36Sopenharmony_ci pci_for_each_dma_alias(pdev, get_msi_id_cb, &rid); 48062306a36Sopenharmony_ci dom = of_msi_map_get_device_domain(&pdev->dev, rid, DOMAIN_BUS_PCI_MSI); 48162306a36Sopenharmony_ci if (!dom) 48262306a36Sopenharmony_ci dom = iort_get_device_domain(&pdev->dev, rid, 48362306a36Sopenharmony_ci DOMAIN_BUS_PCI_MSI); 48462306a36Sopenharmony_ci return dom; 48562306a36Sopenharmony_ci} 486