iommu/arm-smmu: Add support for MSI on SMMUv3
Despite being a platform device, the SMMUv3 is capable of signaling interrupts using MSIs. Hook it into the platform MSI framework and enjoy faults being reported in a new and exciting way. Signed-off-by: Marc Zyngier <marc.zyngier@arm.com> [will: tidied up the binding example and reworked most of the code] Signed-off-by: Will Deacon <will.deacon@arm.com>
This commit is contained in:
parent
c88ae5de71
commit
166bdbd231
3 changed files with 117 additions and 8 deletions
|
@ -36,5 +36,24 @@ the PCIe specification.
|
||||||
NOTE: this only applies to the SMMU itself, not
|
NOTE: this only applies to the SMMU itself, not
|
||||||
masters connected upstream of the SMMU.
|
masters connected upstream of the SMMU.
|
||||||
|
|
||||||
|
- msi-parent : See the generic MSI binding described in
|
||||||
|
devicetree/bindings/interrupt-controller/msi.txt
|
||||||
|
for a description of the msi-parent property.
|
||||||
|
|
||||||
- hisilicon,broken-prefetch-cmd
|
- hisilicon,broken-prefetch-cmd
|
||||||
: Avoid sending CMD_PREFETCH_* commands to the SMMU.
|
: Avoid sending CMD_PREFETCH_* commands to the SMMU.
|
||||||
|
|
||||||
|
** Example
|
||||||
|
|
||||||
|
smmu@2b400000 {
|
||||||
|
compatible = "arm,smmu-v3";
|
||||||
|
reg = <0x0 0x2b400000 0x0 0x20000>;
|
||||||
|
interrupts = <GIC_SPI 74 IRQ_TYPE_EDGE_RISING>,
|
||||||
|
<GIC_SPI 75 IRQ_TYPE_EDGE_RISING>,
|
||||||
|
<GIC_SPI 77 IRQ_TYPE_EDGE_RISING>,
|
||||||
|
<GIC_SPI 79 IRQ_TYPE_EDGE_RISING>;
|
||||||
|
interrupt-names = "eventq", "priq", "cmdq-sync", "gerror";
|
||||||
|
dma-coherent;
|
||||||
|
#iommu-cells = <0>;
|
||||||
|
msi-parent = <&its 0xff0000>;
|
||||||
|
};
|
||||||
|
|
|
@ -362,6 +362,7 @@ config ARM_SMMU_V3
|
||||||
depends on ARM64 && PCI
|
depends on ARM64 && PCI
|
||||||
select IOMMU_API
|
select IOMMU_API
|
||||||
select IOMMU_IO_PGTABLE_LPAE
|
select IOMMU_IO_PGTABLE_LPAE
|
||||||
|
select GENERIC_MSI_IRQ_DOMAIN
|
||||||
help
|
help
|
||||||
Support for implementations of the ARM System MMU architecture
|
Support for implementations of the ARM System MMU architecture
|
||||||
version 3 providing translation support to a PCIe root complex.
|
version 3 providing translation support to a PCIe root complex.
|
||||||
|
|
|
@ -26,6 +26,7 @@
|
||||||
#include <linux/iommu.h>
|
#include <linux/iommu.h>
|
||||||
#include <linux/iopoll.h>
|
#include <linux/iopoll.h>
|
||||||
#include <linux/module.h>
|
#include <linux/module.h>
|
||||||
|
#include <linux/msi.h>
|
||||||
#include <linux/of.h>
|
#include <linux/of.h>
|
||||||
#include <linux/of_address.h>
|
#include <linux/of_address.h>
|
||||||
#include <linux/of_platform.h>
|
#include <linux/of_platform.h>
|
||||||
|
@ -402,6 +403,31 @@ enum pri_resp {
|
||||||
PRI_RESP_SUCC,
|
PRI_RESP_SUCC,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum arm_smmu_msi_index {
|
||||||
|
EVTQ_MSI_INDEX,
|
||||||
|
GERROR_MSI_INDEX,
|
||||||
|
PRIQ_MSI_INDEX,
|
||||||
|
ARM_SMMU_MAX_MSIS,
|
||||||
|
};
|
||||||
|
|
||||||
|
static phys_addr_t arm_smmu_msi_cfg[ARM_SMMU_MAX_MSIS][3] = {
|
||||||
|
[EVTQ_MSI_INDEX] = {
|
||||||
|
ARM_SMMU_EVTQ_IRQ_CFG0,
|
||||||
|
ARM_SMMU_EVTQ_IRQ_CFG1,
|
||||||
|
ARM_SMMU_EVTQ_IRQ_CFG2,
|
||||||
|
},
|
||||||
|
[GERROR_MSI_INDEX] = {
|
||||||
|
ARM_SMMU_GERROR_IRQ_CFG0,
|
||||||
|
ARM_SMMU_GERROR_IRQ_CFG1,
|
||||||
|
ARM_SMMU_GERROR_IRQ_CFG2,
|
||||||
|
},
|
||||||
|
[PRIQ_MSI_INDEX] = {
|
||||||
|
ARM_SMMU_PRIQ_IRQ_CFG0,
|
||||||
|
ARM_SMMU_PRIQ_IRQ_CFG1,
|
||||||
|
ARM_SMMU_PRIQ_IRQ_CFG2,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
struct arm_smmu_cmdq_ent {
|
struct arm_smmu_cmdq_ent {
|
||||||
/* Common fields */
|
/* Common fields */
|
||||||
u8 opcode;
|
u8 opcode;
|
||||||
|
@ -2176,6 +2202,72 @@ static int arm_smmu_write_reg_sync(struct arm_smmu_device *smmu, u32 val,
|
||||||
1, ARM_SMMU_POLL_TIMEOUT_US);
|
1, ARM_SMMU_POLL_TIMEOUT_US);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void arm_smmu_free_msis(void *data)
|
||||||
|
{
|
||||||
|
struct device *dev = data;
|
||||||
|
platform_msi_domain_free_irqs(dev);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void arm_smmu_write_msi_msg(struct msi_desc *desc, struct msi_msg *msg)
|
||||||
|
{
|
||||||
|
phys_addr_t doorbell;
|
||||||
|
struct device *dev = msi_desc_to_dev(desc);
|
||||||
|
struct arm_smmu_device *smmu = dev_get_drvdata(dev);
|
||||||
|
phys_addr_t *cfg = arm_smmu_msi_cfg[desc->platform.msi_index];
|
||||||
|
|
||||||
|
doorbell = (((u64)msg->address_hi) << 32) | msg->address_lo;
|
||||||
|
doorbell &= MSI_CFG0_ADDR_MASK << MSI_CFG0_ADDR_SHIFT;
|
||||||
|
|
||||||
|
writeq_relaxed(doorbell, smmu->base + cfg[0]);
|
||||||
|
writel_relaxed(msg->data, smmu->base + cfg[1]);
|
||||||
|
writel_relaxed(MSI_CFG2_MEMATTR_DEVICE_nGnRE, smmu->base + cfg[2]);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void arm_smmu_setup_msis(struct arm_smmu_device *smmu)
|
||||||
|
{
|
||||||
|
struct msi_desc *desc;
|
||||||
|
int ret, nvec = ARM_SMMU_MAX_MSIS;
|
||||||
|
struct device *dev = smmu->dev;
|
||||||
|
|
||||||
|
/* Clear the MSI address regs */
|
||||||
|
writeq_relaxed(0, smmu->base + ARM_SMMU_GERROR_IRQ_CFG0);
|
||||||
|
writeq_relaxed(0, smmu->base + ARM_SMMU_EVTQ_IRQ_CFG0);
|
||||||
|
|
||||||
|
if (smmu->features & ARM_SMMU_FEAT_PRI)
|
||||||
|
writeq_relaxed(0, smmu->base + ARM_SMMU_PRIQ_IRQ_CFG0);
|
||||||
|
else
|
||||||
|
nvec--;
|
||||||
|
|
||||||
|
if (!(smmu->features & ARM_SMMU_FEAT_MSI))
|
||||||
|
return;
|
||||||
|
|
||||||
|
/* Allocate MSIs for evtq, gerror and priq. Ignore cmdq */
|
||||||
|
ret = platform_msi_domain_alloc_irqs(dev, nvec, arm_smmu_write_msi_msg);
|
||||||
|
if (ret) {
|
||||||
|
dev_warn(dev, "failed to allocate MSIs\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for_each_msi_entry(desc, dev) {
|
||||||
|
switch (desc->platform.msi_index) {
|
||||||
|
case EVTQ_MSI_INDEX:
|
||||||
|
smmu->evtq.q.irq = desc->irq;
|
||||||
|
break;
|
||||||
|
case GERROR_MSI_INDEX:
|
||||||
|
smmu->gerr_irq = desc->irq;
|
||||||
|
break;
|
||||||
|
case PRIQ_MSI_INDEX:
|
||||||
|
smmu->priq.q.irq = desc->irq;
|
||||||
|
break;
|
||||||
|
default: /* Unknown */
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Add callback to free MSIs on teardown */
|
||||||
|
devm_add_action(dev, arm_smmu_free_msis, dev);
|
||||||
|
}
|
||||||
|
|
||||||
static int arm_smmu_setup_irqs(struct arm_smmu_device *smmu)
|
static int arm_smmu_setup_irqs(struct arm_smmu_device *smmu)
|
||||||
{
|
{
|
||||||
int ret, irq;
|
int ret, irq;
|
||||||
|
@ -2189,11 +2281,9 @@ static int arm_smmu_setup_irqs(struct arm_smmu_device *smmu)
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Clear the MSI address regs */
|
arm_smmu_setup_msis(smmu);
|
||||||
writeq_relaxed(0, smmu->base + ARM_SMMU_GERROR_IRQ_CFG0);
|
|
||||||
writeq_relaxed(0, smmu->base + ARM_SMMU_EVTQ_IRQ_CFG0);
|
|
||||||
|
|
||||||
/* Request wired interrupt lines */
|
/* Request interrupt lines */
|
||||||
irq = smmu->evtq.q.irq;
|
irq = smmu->evtq.q.irq;
|
||||||
if (irq) {
|
if (irq) {
|
||||||
ret = devm_request_threaded_irq(smmu->dev, irq,
|
ret = devm_request_threaded_irq(smmu->dev, irq,
|
||||||
|
@ -2222,8 +2312,6 @@ static int arm_smmu_setup_irqs(struct arm_smmu_device *smmu)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (smmu->features & ARM_SMMU_FEAT_PRI) {
|
if (smmu->features & ARM_SMMU_FEAT_PRI) {
|
||||||
writeq_relaxed(0, smmu->base + ARM_SMMU_PRIQ_IRQ_CFG0);
|
|
||||||
|
|
||||||
irq = smmu->priq.q.irq;
|
irq = smmu->priq.q.irq;
|
||||||
if (irq) {
|
if (irq) {
|
||||||
ret = devm_request_threaded_irq(smmu->dev, irq,
|
ret = devm_request_threaded_irq(smmu->dev, irq,
|
||||||
|
@ -2597,13 +2685,14 @@ static int arm_smmu_device_dt_probe(struct platform_device *pdev)
|
||||||
if (ret)
|
if (ret)
|
||||||
return ret;
|
return ret;
|
||||||
|
|
||||||
|
/* Record our private device structure */
|
||||||
|
platform_set_drvdata(pdev, smmu);
|
||||||
|
|
||||||
/* Reset the device */
|
/* Reset the device */
|
||||||
ret = arm_smmu_device_reset(smmu);
|
ret = arm_smmu_device_reset(smmu);
|
||||||
if (ret)
|
if (ret)
|
||||||
goto out_free_structures;
|
goto out_free_structures;
|
||||||
|
|
||||||
/* Record our private device structure */
|
|
||||||
platform_set_drvdata(pdev, smmu);
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
out_free_structures:
|
out_free_structures:
|
||||||
|
|
Loading…
Reference in a new issue