Commit 4527e837 authored by Linus Torvalds's avatar Linus Torvalds

Merge tag 'irq-msi-2024-03-10' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip

Pull MSI updates from Thomas Gleixner:
 "Updates for the MSI interrupt subsystem and initial RISC-V MSI
  support.

  The core changes have been adopted from previous work which converted
  ARM[64] to the new per device MSI domain model, which was merged to
  support multiple MSI domain per device. The ARM[64] changes are being
  worked on too, but have not been ready yet. The core and platform-MSI
  changes have been split out to not hold up RISC-V and to avoid that
  RISC-V builds on the scheduled for removal interfaces.

  The core support provides new interfaces to handle wire to MSI bridges
  in a straight forward way and introduces new platform-MSI interfaces
  which are built on top of the per device MSI domain model.

  Once ARM[64] is converted over the old platform-MSI interfaces and the
  related ugliness in the MSI core code will be removed.

  The actual MSI parts for RISC-V were finalized late and have been
  post-poned for the next merge window.

  Drivers:

   - Add a new driver for the Andes hart-level interrupt controller

   - Rework the SiFive PLIC driver to prepare for MSI suport

   - Expand the RISC-V INTC driver to support the new RISC-V AIA
     controller which provides the basis for MSI on RISC-V

   - A few fixup for the fallout of the core changes"

* tag 'irq-msi-2024-03-10' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip: (29 commits)
  irqchip/riscv-intc: Fix low-level interrupt handler setup for AIA
  x86/apic/msi: Use DOMAIN_BUS_GENERIC_MSI for HPET/IO-APIC domain search
  genirq/matrix: Dynamic bitmap allocation
  irqchip/riscv-intc: Add support for RISC-V AIA
  irqchip/sifive-plic: Improve locking safety by using irqsave/irqrestore
  irqchip/sifive-plic: Parse number of interrupts and contexts early in plic_probe()
  irqchip/sifive-plic: Cleanup PLIC contexts upon irqdomain creation failure
  irqchip/sifive-plic: Use riscv_get_intc_hwnode() to get parent fwnode
  irqchip/sifive-plic: Use devm_xyz() for managed allocation
  irqchip/sifive-plic: Use dev_xyz() in-place of pr_xyz()
  irqchip/sifive-plic: Convert PLIC driver into a platform driver
  irqchip/riscv-intc: Introduce Andes hart-level interrupt controller
  irqchip/riscv-intc: Allow large non-standard interrupt number
  genirq/irqdomain: Don't call ops->select for DOMAIN_BUS_ANY tokens
  irqchip/imx-intmux: Handle pure domain searches correctly
  genirq/msi: Provide MSI_FLAG_PARENT_PM_DEV
  genirq/irqdomain: Reroute device MSI create_mapping
  genirq/msi: Provide allocation/free functions for "wired" MSI interrupts
  genirq/msi: Optionally use dev->fwnode for device domain
  genirq/msi: Provide DOMAIN_BUS_WIRED_TO_MSI
  ...
parents 02d4df78 678c607e
......@@ -16,8 +16,6 @@
#include <asm/irq_vectors.h>
#define IRQ_MATRIX_BITS NR_VECTORS
#ifndef __ASSEMBLY__
#include <linux/percpu.h>
......
......@@ -2354,7 +2354,7 @@ static int mp_irqdomain_create(int ioapic)
fwspec.param_count = 1;
fwspec.param[0] = mpc_ioapic_id(ioapic);
parent = irq_find_matching_fwspec(&fwspec, DOMAIN_BUS_ANY);
parent = irq_find_matching_fwspec(&fwspec, DOMAIN_BUS_GENERIC_MSI);
if (!parent) {
if (!cfg->dev)
irq_domain_free_fwnode(fn);
......
......@@ -568,7 +568,7 @@ static struct irq_domain *hpet_create_irq_domain(int hpet_id)
fwspec.param_count = 1;
fwspec.param[0] = hpet_id;
parent = irq_find_matching_fwspec(&fwspec, DOMAIN_BUS_ANY);
parent = irq_find_matching_fwspec(&fwspec, DOMAIN_BUS_GENERIC_MSI);
if (!parent) {
irq_domain_free_fwnode(fn);
kfree(domain_info);
......
......@@ -13,6 +13,8 @@
#include <linux/msi.h>
#include <linux/slab.h>
/* Begin of removal area. Once everything is converted over. Cleanup the includes too! */
#define DEV_ID_SHIFT 21
#define MAX_DEV_MSIS (1 << (32 - DEV_ID_SHIFT))
......@@ -204,8 +206,8 @@ static void platform_msi_free_priv_data(struct device *dev)
* Returns:
* Zero for success, or an error code in case of failure
*/
int platform_msi_domain_alloc_irqs(struct device *dev, unsigned int nvec,
irq_write_msi_msg_t write_msi_msg)
static int platform_msi_domain_alloc_irqs(struct device *dev, unsigned int nvec,
irq_write_msi_msg_t write_msi_msg)
{
int err;
......@@ -219,18 +221,6 @@ int platform_msi_domain_alloc_irqs(struct device *dev, unsigned int nvec,
return err;
}
EXPORT_SYMBOL_GPL(platform_msi_domain_alloc_irqs);
/**
* platform_msi_domain_free_irqs - Free MSI interrupts for @dev
* @dev: The device for which to free interrupts
*/
void platform_msi_domain_free_irqs(struct device *dev)
{
msi_domain_free_irqs_all(dev, MSI_DEFAULT_DOMAIN);
platform_msi_free_priv_data(dev);
}
EXPORT_SYMBOL_GPL(platform_msi_domain_free_irqs);
/**
* platform_msi_get_host_data - Query the private data associated with
......@@ -350,3 +340,104 @@ int platform_msi_device_domain_alloc(struct irq_domain *domain, unsigned int vir
return msi_domain_populate_irqs(domain->parent, dev, virq, nr_irqs, &data->arg);
}
/* End of removal area */
/* Real per device domain interfaces */
/*
* This indirection can go when platform_device_msi_init_and_alloc_irqs()
* is switched to a proper irq_chip::irq_write_msi_msg() callback. Keep it
* simple for now.
*/
static void platform_msi_write_msi_msg(struct irq_data *d, struct msi_msg *msg)
{
irq_write_msi_msg_t cb = d->chip_data;
cb(irq_data_get_msi_desc(d), msg);
}
static void platform_msi_set_desc_byindex(msi_alloc_info_t *arg, struct msi_desc *desc)
{
arg->desc = desc;
arg->hwirq = desc->msi_index;
}
static const struct msi_domain_template platform_msi_template = {
.chip = {
.name = "pMSI",
.irq_mask = irq_chip_mask_parent,
.irq_unmask = irq_chip_unmask_parent,
.irq_write_msi_msg = platform_msi_write_msi_msg,
/* The rest is filled in by the platform MSI parent */
},
.ops = {
.set_desc = platform_msi_set_desc_byindex,
},
.info = {
.bus_token = DOMAIN_BUS_DEVICE_MSI,
},
};
/**
* platform_device_msi_init_and_alloc_irqs - Initialize platform device MSI
* and allocate interrupts for @dev
* @dev: The device for which to allocate interrupts
* @nvec: The number of interrupts to allocate
* @write_msi_msg: Callback to write an interrupt message for @dev
*
* Returns:
* Zero for success, or an error code in case of failure
*
* This creates a MSI domain on @dev which has @dev->msi.domain as
* parent. The parent domain sets up the new domain. The domain has
* a fixed size of @nvec. The domain is managed by devres and will
* be removed when the device is removed.
*
* Note: For migration purposes this falls back to the original platform_msi code
* up to the point where all platforms have been converted to the MSI
* parent model.
*/
int platform_device_msi_init_and_alloc_irqs(struct device *dev, unsigned int nvec,
irq_write_msi_msg_t write_msi_msg)
{
struct irq_domain *domain = dev->msi.domain;
if (!domain || !write_msi_msg)
return -EINVAL;
/* Migration support. Will go away once everything is converted */
if (!irq_domain_is_msi_parent(domain))
return platform_msi_domain_alloc_irqs(dev, nvec, write_msi_msg);
/*
* @write_msi_msg is stored in the resulting msi_domain_info::data.
* The underlying domain creation mechanism will assign that
* callback to the resulting irq chip.
*/
if (!msi_create_device_irq_domain(dev, MSI_DEFAULT_DOMAIN,
&platform_msi_template,
nvec, NULL, write_msi_msg))
return -ENODEV;
return msi_domain_alloc_irqs_range(dev, MSI_DEFAULT_DOMAIN, 0, nvec - 1);
}
EXPORT_SYMBOL_GPL(platform_device_msi_init_and_alloc_irqs);
/**
* platform_device_msi_free_irqs_all - Free all interrupts for @dev
* @dev: The device for which to free interrupts
*/
void platform_device_msi_free_irqs_all(struct device *dev)
{
struct irq_domain *domain = dev->msi.domain;
msi_domain_free_irqs_all(dev, MSI_DEFAULT_DOMAIN);
/* Migration support. Will go away once everything is converted */
if (!irq_domain_is_msi_parent(domain))
platform_msi_free_priv_data(dev);
}
EXPORT_SYMBOL_GPL(platform_device_msi_free_irqs_all);
......@@ -747,8 +747,8 @@ static int mv_xor_v2_probe(struct platform_device *pdev)
if (IS_ERR(xor_dev->clk))
return PTR_ERR(xor_dev->clk);
ret = platform_msi_domain_alloc_irqs(&pdev->dev, 1,
mv_xor_v2_set_msi_msg);
ret = platform_device_msi_init_and_alloc_irqs(&pdev->dev, 1,
mv_xor_v2_set_msi_msg);
if (ret)
return ret;
......@@ -851,7 +851,7 @@ static int mv_xor_v2_probe(struct platform_device *pdev)
xor_dev->desc_size * MV_XOR_V2_DESC_NUM,
xor_dev->hw_desq_virt, xor_dev->hw_desq);
free_msi_irqs:
platform_msi_domain_free_irqs(&pdev->dev);
platform_device_msi_free_irqs_all(&pdev->dev);
return ret;
}
......@@ -867,7 +867,7 @@ static void mv_xor_v2_remove(struct platform_device *pdev)
devm_free_irq(&pdev->dev, xor_dev->irq, xor_dev);
platform_msi_domain_free_irqs(&pdev->dev);
platform_device_msi_free_irqs_all(&pdev->dev);
tasklet_kill(&xor_dev->irq_tasklet);
}
......
......@@ -696,7 +696,7 @@ static void hidma_free_msis(struct hidma_dev *dmadev)
devm_free_irq(dev, virq, &dmadev->lldev);
}
platform_msi_domain_free_irqs(dev);
platform_device_msi_free_irqs_all(dev);
#endif
}
......@@ -706,8 +706,8 @@ static int hidma_request_msi(struct hidma_dev *dmadev,
#ifdef CONFIG_GENERIC_MSI_IRQ
int rc, i, virq;
rc = platform_msi_domain_alloc_irqs(&pdev->dev, HIDMA_MSI_INTS,
hidma_write_msi_msg);
rc = platform_device_msi_init_and_alloc_irqs(&pdev->dev, HIDMA_MSI_INTS,
hidma_write_msi_msg);
if (rc)
return rc;
......
......@@ -3125,7 +3125,8 @@ static int arm_smmu_update_gbpa(struct arm_smmu_device *smmu, u32 set, u32 clr)
static void arm_smmu_free_msis(void *data)
{
struct device *dev = data;
platform_msi_domain_free_irqs(dev);
platform_device_msi_free_irqs_all(dev);
}
static void arm_smmu_write_msi_msg(struct msi_desc *desc, struct msi_msg *msg)
......@@ -3166,7 +3167,7 @@ static void arm_smmu_setup_msis(struct arm_smmu_device *smmu)
}
/* Allocate MSIs for evtq, gerror and priq. Ignore cmdq */
ret = platform_msi_domain_alloc_irqs(dev, nvec, arm_smmu_write_msi_msg);
ret = platform_device_msi_init_and_alloc_irqs(dev, nvec, arm_smmu_write_msi_msg);
if (ret) {
dev_warn(dev, "failed to allocate MSIs - falling back to wired irqs\n");
return;
......
......@@ -1691,9 +1691,13 @@ static int gic_irq_domain_select(struct irq_domain *d,
irq_hw_number_t hwirq;
/* Not for us */
if (fwspec->fwnode != d->fwnode)
if (fwspec->fwnode != d->fwnode)
return 0;
/* Handle pure domain searches */
if (!fwspec->param_count)
return d->bus_token == bus_token;
/* If this is not DT, then we have a single domain */
if (!is_of_node(fwspec->fwnode))
return 1;
......
......@@ -166,6 +166,10 @@ static int imx_intmux_irq_select(struct irq_domain *d, struct irq_fwspec *fwspec
if (fwspec->fwnode != d->fwnode)
return false;
/* Handle pure domain searches */
if (!fwspec->param_count)
return d->bus_token == bus_token;
return irqchip_data->chanidx == fwspec->param[1];
}
......
......@@ -17,17 +17,29 @@
#include <linux/module.h>
#include <linux/of.h>
#include <linux/smp.h>
#include <linux/soc/andes/irq.h>
#include <asm/hwcap.h>
static struct irq_domain *intc_domain;
static unsigned int riscv_intc_nr_irqs __ro_after_init = BITS_PER_LONG;
static unsigned int riscv_intc_custom_base __ro_after_init = BITS_PER_LONG;
static unsigned int riscv_intc_custom_nr_irqs __ro_after_init;
static asmlinkage void riscv_intc_irq(struct pt_regs *regs)
{
unsigned long cause = regs->cause & ~CAUSE_IRQ_FLAG;
if (unlikely(cause >= BITS_PER_LONG))
panic("unexpected interrupt cause");
if (generic_handle_domain_irq(intc_domain, cause))
pr_warn_ratelimited("Failed to handle interrupt (cause: %ld)\n", cause);
}
static asmlinkage void riscv_intc_aia_irq(struct pt_regs *regs)
{
unsigned long topi;
generic_handle_domain_irq(intc_domain, cause);
while ((topi = csr_read(CSR_TOPI)))
generic_handle_domain_irq(intc_domain, topi >> TOPI_IID_SHIFT);
}
/*
......@@ -39,12 +51,43 @@ static asmlinkage void riscv_intc_irq(struct pt_regs *regs)
static void riscv_intc_irq_mask(struct irq_data *d)
{
csr_clear(CSR_IE, BIT(d->hwirq));
if (IS_ENABLED(CONFIG_32BIT) && d->hwirq >= BITS_PER_LONG)
csr_clear(CSR_IEH, BIT(d->hwirq - BITS_PER_LONG));
else
csr_clear(CSR_IE, BIT(d->hwirq));
}
static void riscv_intc_irq_unmask(struct irq_data *d)
{
csr_set(CSR_IE, BIT(d->hwirq));
if (IS_ENABLED(CONFIG_32BIT) && d->hwirq >= BITS_PER_LONG)
csr_set(CSR_IEH, BIT(d->hwirq - BITS_PER_LONG));
else
csr_set(CSR_IE, BIT(d->hwirq));
}
static void andes_intc_irq_mask(struct irq_data *d)
{
/*
* Andes specific S-mode local interrupt causes (hwirq)
* are defined as (256 + n) and controlled by n-th bit
* of SLIE.
*/
unsigned int mask = BIT(d->hwirq % BITS_PER_LONG);
if (d->hwirq < ANDES_SLI_CAUSE_BASE)
csr_clear(CSR_IE, mask);
else
csr_clear(ANDES_CSR_SLIE, mask);
}
static void andes_intc_irq_unmask(struct irq_data *d)
{
unsigned int mask = BIT(d->hwirq % BITS_PER_LONG);
if (d->hwirq < ANDES_SLI_CAUSE_BASE)
csr_set(CSR_IE, mask);
else
csr_set(ANDES_CSR_SLIE, mask);
}
static void riscv_intc_irq_eoi(struct irq_data *d)
......@@ -70,12 +113,21 @@ static struct irq_chip riscv_intc_chip = {
.irq_eoi = riscv_intc_irq_eoi,
};
static struct irq_chip andes_intc_chip = {
.name = "RISC-V INTC",
.irq_mask = andes_intc_irq_mask,
.irq_unmask = andes_intc_irq_unmask,
.irq_eoi = riscv_intc_irq_eoi,
};
static int riscv_intc_domain_map(struct irq_domain *d, unsigned int irq,
irq_hw_number_t hwirq)
{
struct irq_chip *chip = d->host_data;
irq_set_percpu_devid(irq);
irq_domain_set_info(d, irq, hwirq, &riscv_intc_chip, d->host_data,
handle_percpu_devid_irq, NULL, NULL);
irq_domain_set_info(d, irq, hwirq, chip, NULL, handle_percpu_devid_irq,
NULL, NULL);
return 0;
}
......@@ -93,6 +145,14 @@ static int riscv_intc_domain_alloc(struct irq_domain *domain,
if (ret)
return ret;
/*
* Only allow hwirq for which we have corresponding standard or
* custom interrupt enable register.
*/
if ((hwirq >= riscv_intc_nr_irqs && hwirq < riscv_intc_custom_base) ||
(hwirq >= riscv_intc_custom_base + riscv_intc_custom_nr_irqs))
return -EINVAL;
for (i = 0; i < nr_irqs; i++) {
ret = riscv_intc_domain_map(domain, virq + i, hwirq + i);
if (ret)
......@@ -113,18 +173,20 @@ static struct fwnode_handle *riscv_intc_hwnode(void)
return intc_domain->fwnode;
}
static int __init riscv_intc_init_common(struct fwnode_handle *fn)
static int __init riscv_intc_init_common(struct fwnode_handle *fn, struct irq_chip *chip)
{
int rc;
intc_domain = irq_domain_create_linear(fn, BITS_PER_LONG,
&riscv_intc_domain_ops, NULL);
intc_domain = irq_domain_create_tree(fn, &riscv_intc_domain_ops, chip);
if (!intc_domain) {
pr_err("unable to add IRQ domain\n");
return -ENXIO;
}
rc = set_handle_irq(&riscv_intc_irq);
if (riscv_isa_extension_available(NULL, SxAIA))
rc = set_handle_irq(&riscv_intc_aia_irq);
else
rc = set_handle_irq(&riscv_intc_irq);
if (rc) {
pr_err("failed to set irq handler\n");
return rc;
......@@ -132,7 +194,11 @@ static int __init riscv_intc_init_common(struct fwnode_handle *fn)
riscv_set_intc_hwnode_fn(riscv_intc_hwnode);
pr_info("%d local interrupts mapped\n", BITS_PER_LONG);
pr_info("%d local interrupts mapped%s\n",
riscv_isa_extension_available(NULL, SxAIA) ? 64 : riscv_intc_nr_irqs,
riscv_isa_extension_available(NULL, SxAIA) ? " using AIA" : "");
if (riscv_intc_custom_nr_irqs)
pr_info("%d custom local interrupts mapped\n", riscv_intc_custom_nr_irqs);
return 0;
}
......@@ -140,8 +206,9 @@ static int __init riscv_intc_init_common(struct fwnode_handle *fn)
static int __init riscv_intc_init(struct device_node *node,
struct device_node *parent)
{
int rc;
struct irq_chip *chip = &riscv_intc_chip;
unsigned long hartid;
int rc;
rc = riscv_of_parent_hartid(node, &hartid);
if (rc < 0) {
......@@ -166,10 +233,17 @@ static int __init riscv_intc_init(struct device_node *node,
return 0;
}
return riscv_intc_init_common(of_node_to_fwnode(node));
if (of_device_is_compatible(node, "andestech,cpu-intc")) {
riscv_intc_custom_base = ANDES_SLI_CAUSE_BASE;
riscv_intc_custom_nr_irqs = ANDES_RV_IRQ_LAST;
chip = &andes_intc_chip;
}
return riscv_intc_init_common(of_node_to_fwnode(node), chip);
}
IRQCHIP_DECLARE(riscv, "riscv,cpu-intc", riscv_intc_init);
IRQCHIP_DECLARE(andes, "andestech,cpu-intc", riscv_intc_init);
#ifdef CONFIG_ACPI
......@@ -196,7 +270,7 @@ static int __init riscv_intc_acpi_init(union acpi_subtable_headers *header,
return -ENOMEM;
}
return riscv_intc_init_common(fn);
return riscv_intc_init_common(fn, &riscv_intc_chip);
}
IRQCHIP_ACPI_DECLARE(riscv_intc, ACPI_MADT_TYPE_RINTC, NULL,
......
This diff is collapsed.
......@@ -1587,8 +1587,8 @@ static int flexrm_mbox_probe(struct platform_device *pdev)
}
/* Allocate platform MSIs for each ring */
ret = platform_msi_domain_alloc_irqs(dev, mbox->num_rings,
flexrm_mbox_msi_write);
ret = platform_device_msi_init_and_alloc_irqs(dev, mbox->num_rings,
flexrm_mbox_msi_write);
if (ret)
goto fail_destroy_cmpl_pool;
......@@ -1641,7 +1641,7 @@ static int flexrm_mbox_probe(struct platform_device *pdev)
fail_free_debugfs_root:
debugfs_remove_recursive(mbox->root);
platform_msi_domain_free_irqs(dev);
platform_device_msi_free_irqs_all(dev);
fail_destroy_cmpl_pool:
dma_pool_destroy(mbox->cmpl_pool);
fail_destroy_bd_pool:
......@@ -1657,7 +1657,7 @@ static void flexrm_mbox_remove(struct platform_device *pdev)
debugfs_remove_recursive(mbox->root);
platform_msi_domain_free_irqs(dev);
platform_device_msi_free_irqs_all(dev);
dma_pool_destroy(mbox->cmpl_pool);
dma_pool_destroy(mbox->bd_pool);
......
......@@ -716,7 +716,7 @@ static void smmu_pmu_free_msis(void *data)
{
struct device *dev = data;
platform_msi_domain_free_irqs(dev);
platform_device_msi_free_irqs_all(dev);
}
static void smmu_pmu_write_msi_msg(struct msi_desc *desc, struct msi_msg *msg)
......@@ -746,7 +746,7 @@ static void smmu_pmu_setup_msi(struct smmu_pmu *pmu)
if (!(readl(pmu->reg_base + SMMU_PMCG_CFGR) & SMMU_PMCG_CFGR_MSI))
return;
ret = platform_msi_domain_alloc_irqs(dev, 1, smmu_pmu_write_msi_msg);
ret = platform_device_msi_init_and_alloc_irqs(dev, 1, smmu_pmu_write_msi_msg);
if (ret) {
dev_warn(dev, "failed to allocate MSIs\n");
return;
......
......@@ -1712,8 +1712,8 @@ static int ufs_qcom_config_esi(struct ufs_hba *hba)
* 2. Poll queues do not need ESI.
*/
nr_irqs = hba->nr_hw_queues - hba->nr_queues[HCTX_TYPE_POLL];
ret = platform_msi_domain_alloc_irqs(hba->dev, nr_irqs,
ufs_qcom_write_msi_msg);
ret = platform_device_msi_init_and_alloc_irqs(hba->dev, nr_irqs,
ufs_qcom_write_msi_msg);
if (ret) {
dev_err(hba->dev, "Failed to request Platform MSI %d\n", ret);
return ret;
......@@ -1742,7 +1742,7 @@ static int ufs_qcom_config_esi(struct ufs_hba *hba)
devm_free_irq(hba->dev, desc->irq, hba);
}
msi_unlock_descs(hba->dev);
platform_msi_domain_free_irqs(hba->dev);
platform_device_msi_free_irqs_all(hba->dev);
} else {
if (host->hw_ver.major == 6 && host->hw_ver.minor == 0 &&
host->hw_ver.step == 0)
......@@ -1818,7 +1818,7 @@ static void ufs_qcom_remove(struct platform_device *pdev)
pm_runtime_get_sync(&(pdev)->dev);
ufshcd_remove(hba);
platform_msi_domain_free_irqs(hba->dev);
platform_device_msi_free_irqs_all(hba->dev);
}
static const struct of_device_id ufs_qcom_of_match[] __maybe_unused = {
......
......@@ -619,6 +619,23 @@ static inline bool irq_domain_is_msi_device(struct irq_domain *domain)
#endif /* CONFIG_IRQ_DOMAIN_HIERARCHY */
#ifdef CONFIG_GENERIC_MSI_IRQ
int msi_device_domain_alloc_wired(struct irq_domain *domain, unsigned int hwirq,
unsigned int type);
void msi_device_domain_free_wired(struct irq_domain *domain, unsigned int virq);
#else
static inline int msi_device_domain_alloc_wired(struct irq_domain *domain, unsigned int hwirq,
unsigned int type)
{
WARN_ON_ONCE(1);
return -EINVAL;
}
static inline void msi_device_domain_free_wired(struct irq_domain *domain, unsigned int virq)
{
WARN_ON_ONCE(1);
}
#endif
#else /* CONFIG_IRQ_DOMAIN */
static inline void irq_dispose_mapping(unsigned int virq) { }
static inline struct irq_domain *irq_find_matching_fwnode(
......
......@@ -26,6 +26,8 @@ enum irq_domain_bus_token {
DOMAIN_BUS_DMAR,
DOMAIN_BUS_AMDVI,
DOMAIN_BUS_PCI_DEVICE_IMS,
DOMAIN_BUS_DEVICE_MSI,
DOMAIN_BUS_WIRED_TO_MSI,
};
#endif /* _LINUX_IRQDOMAIN_DEFS_H */
......@@ -412,6 +412,7 @@ bool arch_restore_msi_irqs(struct pci_dev *dev);
struct irq_domain;
struct irq_domain_ops;
struct irq_chip;
struct irq_fwspec;
struct device_node;
struct fwnode_handle;
struct msi_domain_info;
......@@ -431,6 +432,8 @@ struct msi_domain_info;
* function.
* @msi_post_free: Optional function which is invoked after freeing
* all interrupts.
* @msi_translate: Optional translate callback to support the odd wire to
* MSI bridges, e.g. MBIGEN
*
* @get_hwirq, @msi_init and @msi_free are callbacks used by the underlying
* irqdomain.
......@@ -468,6 +471,8 @@ struct msi_domain_ops {
struct device *dev);
void (*msi_post_free)(struct irq_domain *domain,
struct device *dev);
int (*msi_translate)(struct irq_domain *domain, struct irq_fwspec *fwspec,
irq_hw_number_t *hwirq, unsigned int *type);
};
/**
......@@ -547,6 +552,10 @@ enum {
MSI_FLAG_ALLOC_SIMPLE_MSI_DESCS = (1 << 5),
/* Free MSI descriptors */
MSI_FLAG_FREE_MSI_DESCS = (1 << 6),
/* Use dev->fwnode for MSI device domain creation */
MSI_FLAG_USE_DEV_FWNODE = (1 << 7),
/* Set parent->dev into domain->pm_dev on device domain creation */
MSI_FLAG_PARENT_PM_DEV = (1 << 8),
/* Mask for the generic functionality */
MSI_GENERIC_FLAGS_MASK = GENMASK(15, 0),
......@@ -572,6 +581,11 @@ enum {
* struct msi_parent_ops - MSI parent domain callbacks and configuration info
*
* @supported_flags: Required: The supported MSI flags of the parent domain
* @required_flags: Optional: The required MSI flags of the parent MSI domain
* @bus_select_token: Optional: The bus token of the real parent domain for
* irq_domain::select()
* @bus_select_mask: Optional: A mask of supported BUS_DOMAINs for
* irq_domain::select()
* @prefix: Optional: Prefix for the domain and chip name
* @init_dev_msi_info: Required: Callback for MSI parent domains to setup parent
* domain specific domain flags, domain ops and interrupt chip
......@@ -579,6 +593,9 @@ enum {
*/
struct msi_parent_ops {
u32 supported_flags;
u32 required_flags;
u32 bus_select_token;
u32 bus_select_mask;
const char *prefix;
bool (*init_dev_msi_info)(struct device *dev, struct irq_domain *domain,
struct irq_domain *msi_parent_domain,
......@@ -627,9 +644,6 @@ struct msi_domain_info *msi_get_domain_info(struct irq_domain *domain);
struct irq_domain *platform_msi_create_irq_domain(struct fwnode_handle *fwnode,
struct msi_domain_info *info,
struct irq_domain *parent);
int platform_msi_domain_alloc_irqs(struct device *dev, unsigned int nvec,
irq_write_msi_msg_t write_msi_msg);
void platform_msi_domain_free_irqs(struct device *dev);
/* When an MSI domain is used as an intermediate domain */
int msi_domain_prepare_irqs(struct irq_domain *domain, struct device *dev,
......@@ -656,6 +670,10 @@ int platform_msi_device_domain_alloc(struct irq_domain *domain, unsigned int vir
void platform_msi_device_domain_free(struct irq_domain *domain, unsigned int virq,
unsigned int nvec);
void *platform_msi_get_host_data(struct irq_domain *domain);
/* Per device platform MSI */
int platform_device_msi_init_and_alloc_irqs(struct device *dev, unsigned int nvec,
irq_write_msi_msg_t write_msi_msg);
void platform_device_msi_free_irqs_all(struct device *dev);
bool msi_device_has_isolated_msi(struct device *dev);
#else /* CONFIG_GENERIC_MSI_IRQ */
......
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (C) 2023 Andes Technology Corporation
*/
#ifndef __ANDES_IRQ_H
#define __ANDES_IRQ_H
/* Andes PMU irq number */
#define ANDES_RV_IRQ_PMOVI 18
#define ANDES_RV_IRQ_LAST ANDES_RV_IRQ_PMOVI
#define ANDES_SLI_CAUSE_BASE 256
/* Andes PMU related registers */
#define ANDES_CSR_SLIE 0x9c4
#define ANDES_CSR_SLIP 0x9c5
#define ANDES_CSR_SCOUNTEROF 0x9d4
#endif /* __ANDES_IRQ_H */
......@@ -29,6 +29,7 @@ static int irq_domain_alloc_irqs_locked(struct irq_domain *domain, int irq_base,
unsigned int nr_irqs, int node, void *arg,
bool realloc, const struct irq_affinity_desc *affinity);
static void irq_domain_check_hierarchy(struct irq_domain *domain);
static void irq_domain_free_one_irq(struct irq_domain *domain, unsigned int virq);
struct irqchip_fwid {
struct fwnode_handle fwnode;
......@@ -448,7 +449,7 @@ struct irq_domain *irq_find_matching_fwspec(struct irq_fwspec *fwspec,
*/
mutex_lock(&irq_domain_mutex);
list_for_each_entry(h, &irq_domain_list, link) {
if (h->ops->select && fwspec->param_count)
if (h->ops->select && bus_token != DOMAIN_BUS_ANY)
rc = h->ops->select(h, fwspec, bus_token);
else if (h->ops->match)
rc = h->ops->match(h, to_of_node(fwnode), bus_token);
......@@ -858,8 +859,13 @@ unsigned int irq_create_fwspec_mapping(struct irq_fwspec *fwspec)
}
if (irq_domain_is_hierarchy(domain)) {
virq = irq_domain_alloc_irqs_locked(domain, -1, 1, NUMA_NO_NODE,
fwspec, false, NULL);
if (irq_domain_is_msi_device(domain)) {
mutex_unlock(&domain->root->mutex);
virq = msi_device_domain_alloc_wired(domain, hwirq, type);
mutex_lock(&domain->root->mutex);
} else
virq = irq_domain_alloc_irqs_locked(domain, -1, 1, NUMA_NO_NODE,
fwspec, false, NULL);
if (virq <= 0) {
virq = 0;
goto out;
......@@ -914,7 +920,7 @@ void irq_dispose_mapping(unsigned int virq)
return;
if (irq_domain_is_hierarchy(domain)) {
irq_domain_free_irqs(virq, 1);
irq_domain_free_one_irq(domain, virq);
} else {
irq_domain_disassociate(domain, virq);
irq_free_desc(virq);
......@@ -1755,6 +1761,14 @@ void irq_domain_free_irqs(unsigned int virq, unsigned int nr_irqs)
irq_free_descs(virq, nr_irqs);
}
static void irq_domain_free_one_irq(struct irq_domain *domain, unsigned int virq)
{
if (irq_domain_is_msi_device(domain))
msi_device_domain_free_wired(domain, virq);
else
irq_domain_free_irqs(virq, 1);
}
/**
* irq_domain_alloc_irqs_parent - Allocate interrupts from parent domain
* @domain: Domain below which interrupts must be allocated
......@@ -1907,9 +1921,9 @@ static int irq_domain_alloc_irqs_locked(struct irq_domain *domain, int irq_base,
return -EINVAL;
}
static void irq_domain_check_hierarchy(struct irq_domain *domain)
{
}
static void irq_domain_check_hierarchy(struct irq_domain *domain) { }
static void irq_domain_free_one_irq(struct irq_domain *domain, unsigned int virq) { }
#endif /* CONFIG_IRQ_DOMAIN_HIERARCHY */
#ifdef CONFIG_GENERIC_IRQ_DEBUGFS
......
......@@ -8,8 +8,6 @@
#include <linux/cpu.h>
#include <linux/irq.h>
#define IRQ_MATRIX_SIZE (BITS_TO_LONGS(IRQ_MATRIX_BITS))
struct cpumap {
unsigned int available;
unsigned int allocated;
......@@ -17,8 +15,8 @@ struct cpumap {
unsigned int managed_allocated;
bool initialized;
bool online;
unsigned long alloc_map[IRQ_MATRIX_SIZE];
unsigned long managed_map[IRQ_MATRIX_SIZE];
unsigned long *managed_map;
unsigned long alloc_map[];
};
struct irq_matrix {
......@@ -32,8 +30,8 @@ struct irq_matrix {
unsigned int total_allocated;
unsigned int online_maps;
struct cpumap __percpu *maps;
unsigned long scratch_map[IRQ_MATRIX_SIZE];
unsigned long system_map[IRQ_MATRIX_SIZE];
unsigned long *system_map;
unsigned long scratch_map[];
};
#define CREATE_TRACE_POINTS
......@@ -50,24 +48,32 @@ __init struct irq_matrix *irq_alloc_matrix(unsigned int matrix_bits,
unsigned int alloc_start,
unsigned int alloc_end)
{
unsigned int cpu, matrix_size = BITS_TO_LONGS(matrix_bits);
struct irq_matrix *m;
if (matrix_bits > IRQ_MATRIX_BITS)
return NULL;
m = kzalloc(sizeof(*m), GFP_KERNEL);
m = kzalloc(struct_size(m, scratch_map, matrix_size * 2), GFP_KERNEL);
if (!m)
return NULL;
m->system_map = &m->scratch_map[matrix_size];
m->matrix_bits = matrix_bits;
m->alloc_start = alloc_start;
m->alloc_end = alloc_end;
m->alloc_size = alloc_end - alloc_start;
m->maps = alloc_percpu(*m->maps);
m->maps = __alloc_percpu(struct_size(m->maps, alloc_map, matrix_size * 2),
__alignof__(*m->maps));
if (!m->maps) {
kfree(m);
return NULL;
}
for_each_possible_cpu(cpu) {
struct cpumap *cm = per_cpu_ptr(m->maps, cpu);
cm->managed_map = &cm->alloc_map[matrix_size];
}
return m;
}
......
......@@ -726,11 +726,26 @@ static void msi_domain_free(struct irq_domain *domain, unsigned int virq,
irq_domain_free_irqs_top(domain, virq, nr_irqs);
}
static int msi_domain_translate(struct irq_domain *domain, struct irq_fwspec *fwspec,
irq_hw_number_t *hwirq, unsigned int *type)
{
struct msi_domain_info *info = domain->host_data;
/*
* This will catch allocations through the regular irqdomain path except
* for MSI domains which really support this, e.g. MBIGEN.
*/
if (!info->ops->msi_translate)
return -ENOTSUPP;
return info->ops->msi_translate(domain, fwspec, hwirq, type);
}
static const struct irq_domain_ops msi_domain_ops = {
.alloc = msi_domain_alloc,
.free = msi_domain_free,
.activate = msi_domain_activate,
.deactivate = msi_domain_deactivate,
.translate = msi_domain_translate,
};
static irq_hw_number_t msi_domain_ops_get_hwirq(struct msi_domain_info *info,
......@@ -830,8 +845,11 @@ static struct irq_domain *__msi_create_irq_domain(struct fwnode_handle *fwnode,
domain = irq_domain_create_hierarchy(parent, flags | IRQ_DOMAIN_FLAG_MSI, 0,
fwnode, &msi_domain_ops, info);
if (domain)
if (domain) {
irq_domain_update_bus_token(domain, info->bus_token);
if (info->flags & MSI_FLAG_PARENT_PM_DEV)
domain->pm_dev = parent->pm_dev;
}
return domain;
}
......@@ -945,9 +963,9 @@ bool msi_create_device_irq_domain(struct device *dev, unsigned int domid,
void *chip_data)
{
struct irq_domain *domain, *parent = dev->msi.domain;
const struct msi_parent_ops *pops;
struct fwnode_handle *fwnode, *fwnalloced = NULL;
struct msi_domain_template *bundle;
struct fwnode_handle *fwnode;
const struct msi_parent_ops *pops;
if (!irq_domain_is_msi_parent(parent))
return false;
......@@ -970,7 +988,19 @@ bool msi_create_device_irq_domain(struct device *dev, unsigned int domid,
pops->prefix ? : "", bundle->chip.name, dev_name(dev));
bundle->chip.name = bundle->name;
fwnode = irq_domain_alloc_named_fwnode(bundle->name);
/*
* Using the device firmware node is required for wire to MSI
* device domains so that the existing firmware results in a domain
* match.
* All other device domains like PCI/MSI use the named firmware
* node as they are not guaranteed to have a fwnode. They are never
* looked up and always handled in the context of the device.
*/
if (bundle->info.flags & MSI_FLAG_USE_DEV_FWNODE)
fwnode = dev->fwnode;
else
fwnode = fwnalloced = irq_domain_alloc_named_fwnode(bundle->name);
if (!fwnode)
goto free_bundle;
......@@ -997,7 +1027,7 @@ bool msi_create_device_irq_domain(struct device *dev, unsigned int domid,
fail:
msi_unlock_descs(dev);
free_fwnode:
irq_domain_free_fwnode(fwnode);
irq_domain_free_fwnode(fwnalloced);
free_bundle:
kfree(bundle);
return false;
......@@ -1431,34 +1461,10 @@ int msi_domain_alloc_irqs_all_locked(struct device *dev, unsigned int domid, int
return msi_domain_alloc_locked(dev, &ctrl);
}
/**
* msi_domain_alloc_irq_at - Allocate an interrupt from a MSI interrupt domain at
* a given index - or at the next free index
*
* @dev: Pointer to device struct of the device for which the interrupts
* are allocated
* @domid: Id of the interrupt domain to operate on
* @index: Index for allocation. If @index == %MSI_ANY_INDEX the allocation
* uses the next free index.
* @affdesc: Optional pointer to an interrupt affinity descriptor structure
* @icookie: Optional pointer to a domain specific per instance cookie. If
* non-NULL the content of the cookie is stored in msi_desc::data.
* Must be NULL for MSI-X allocations
*
* This requires a MSI interrupt domain which lets the core code manage the
* MSI descriptors.
*
* Return: struct msi_map
*
* On success msi_map::index contains the allocated index number and
* msi_map::virq the corresponding Linux interrupt number
*
* On failure msi_map::index contains the error code and msi_map::virq
* is %0.
*/
struct msi_map msi_domain_alloc_irq_at(struct device *dev, unsigned int domid, unsigned int index,
const struct irq_affinity_desc *affdesc,
union msi_instance_cookie *icookie)
static struct msi_map __msi_domain_alloc_irq_at(struct device *dev, unsigned int domid,
unsigned int index,
const struct irq_affinity_desc *affdesc,
union msi_instance_cookie *icookie)
{
struct msi_ctrl ctrl = { .domid = domid, .nirqs = 1, };
struct irq_domain *domain;
......@@ -1466,17 +1472,16 @@ struct msi_map msi_domain_alloc_irq_at(struct device *dev, unsigned int domid, u
struct msi_desc *desc;
int ret;
msi_lock_descs(dev);
domain = msi_get_device_domain(dev, domid);
if (!domain) {
map.index = -ENODEV;
goto unlock;
return map;
}
desc = msi_alloc_desc(dev, 1, affdesc);
if (!desc) {
map.index = -ENOMEM;
goto unlock;
return map;
}
if (icookie)
......@@ -1485,7 +1490,7 @@ struct msi_map msi_domain_alloc_irq_at(struct device *dev, unsigned int domid, u
ret = msi_insert_desc(dev, desc, domid, index);
if (ret) {
map.index = ret;
goto unlock;
return map;
}
ctrl.first = ctrl.last = desc->msi_index;
......@@ -1498,11 +1503,90 @@ struct msi_map msi_domain_alloc_irq_at(struct device *dev, unsigned int domid, u
map.index = desc->msi_index;
map.virq = desc->irq;
}
unlock:
return map;
}
/**
* msi_domain_alloc_irq_at - Allocate an interrupt from a MSI interrupt domain at
* a given index - or at the next free index
*
* @dev: Pointer to device struct of the device for which the interrupts
* are allocated
* @domid: Id of the interrupt domain to operate on
* @index: Index for allocation. If @index == %MSI_ANY_INDEX the allocation
* uses the next free index.
* @affdesc: Optional pointer to an interrupt affinity descriptor structure
* @icookie: Optional pointer to a domain specific per instance cookie. If
* non-NULL the content of the cookie is stored in msi_desc::data.
* Must be NULL for MSI-X allocations
*
* This requires a MSI interrupt domain which lets the core code manage the
* MSI descriptors.
*
* Return: struct msi_map
*
* On success msi_map::index contains the allocated index number and
* msi_map::virq the corresponding Linux interrupt number
*
* On failure msi_map::index contains the error code and msi_map::virq
* is %0.
*/
struct msi_map msi_domain_alloc_irq_at(struct device *dev, unsigned int domid, unsigned int index,
const struct irq_affinity_desc *affdesc,
union msi_instance_cookie *icookie)
{
struct msi_map map;
msi_lock_descs(dev);
map = __msi_domain_alloc_irq_at(dev, domid, index, affdesc, icookie);
msi_unlock_descs(dev);
return map;
}
/**
* msi_device_domain_alloc_wired - Allocate a "wired" interrupt on @domain
* @domain: The domain to allocate on
* @hwirq: The hardware interrupt number to allocate for
* @type: The interrupt type
*
* This weirdness supports wire to MSI controllers like MBIGEN.
*
* @hwirq is the hardware interrupt number which is handed in from
* irq_create_fwspec_mapping(). As the wire to MSI domain is sparse, but
* sized in firmware, the hardware interrupt number cannot be used as MSI
* index. For the underlying irq chip the MSI index is irrelevant and
* all it needs is the hardware interrupt number.
*
* To handle this the MSI index is allocated with MSI_ANY_INDEX and the
* hardware interrupt number is stored along with the type information in
* msi_desc::cookie so the underlying interrupt chip and domain code can
* retrieve it.
*
* Return: The Linux interrupt number (> 0) or an error code
*/
int msi_device_domain_alloc_wired(struct irq_domain *domain, unsigned int hwirq,
unsigned int type)
{
unsigned int domid = MSI_DEFAULT_DOMAIN;
union msi_instance_cookie icookie = { };
struct device *dev = domain->dev;
struct msi_map map = { };
if (WARN_ON_ONCE(!dev || domain->bus_token != DOMAIN_BUS_WIRED_TO_MSI))
return -EINVAL;
icookie.value = ((u64)type << 32) | hwirq;
msi_lock_descs(dev);
if (WARN_ON_ONCE(msi_get_device_domain(dev, domid) != domain))
map.index = -EINVAL;
else
map = __msi_domain_alloc_irq_at(dev, domid, MSI_ANY_INDEX, NULL, &icookie);
msi_unlock_descs(dev);
return map.index >= 0 ? map.virq : map.index;
}
static void __msi_domain_free_irqs(struct device *dev, struct irq_domain *domain,
struct msi_ctrl *ctrl)
{
......@@ -1628,6 +1712,30 @@ void msi_domain_free_irqs_all(struct device *dev, unsigned int domid)
msi_unlock_descs(dev);
}
/**
* msi_device_domain_free_wired - Free a wired interrupt in @domain
* @domain: The domain to free the interrupt on
* @virq: The Linux interrupt number to free
*
* This is the counterpart of msi_device_domain_alloc_wired() for the
* weird wired to MSI converting domains.
*/
void msi_device_domain_free_wired(struct irq_domain *domain, unsigned int virq)
{
struct msi_desc *desc = irq_get_msi_desc(virq);
struct device *dev = domain->dev;
if (WARN_ON_ONCE(!dev || !desc || domain->bus_token != DOMAIN_BUS_WIRED_TO_MSI))
return;
msi_lock_descs(dev);
if (!WARN_ON_ONCE(msi_get_device_domain(dev, MSI_DEFAULT_DOMAIN) != domain)) {
msi_domain_free_irqs_range_locked(dev, MSI_DEFAULT_DOMAIN, desc->msi_index,
desc->msi_index);
}
msi_unlock_descs(dev);
}
/**
* msi_get_domain_info - Get the MSI interrupt domain info for @domain
* @domain: The interrupt domain to retrieve data from
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment