Skip to content

Commit

Permalink
iommu/arm-smmu-v3: Add suspend and resume support
Browse files Browse the repository at this point in the history
Add suspend and resume support for smmuv3. The smmu is stopped when
suspending and started when resuming.

When the smmu is suspended, it is powered off and the registers are
cleared. So saves the msi_msg context during msi interrupt initialization
of smmu. When resume happens it calls arm_smmu_device_reset() to restore
the registers.

Closes: https://gitee.com/openeuler/kernel/issues/I4DZ7Q

Signed-off-by: Bixuan Cui <cuibixuan@huawei.com>
Signed-off-by: Zhou Guanghui <zhouguanghui1@huawei.com>
Reviewed-by: Hanjun Guo <guohanjun@huawei.com>
Signed-off-by: Yang Yingliang <yangyingliang@huawei.com>
Signed-off-by: Wang Xu <wangxu@phytium.com.cn>
Signed-off-by: Li Mingzhe <limingzhe1839@phytium.com.cn>
Signed-off-by: Wang Zhimin <wangzhimin1179@phytium.com.cn>
  • Loading branch information
Wang Xu authored and wangzhimin1179 committed Jun 26, 2024
1 parent 4e5def3 commit 9ce9ded
Show file tree
Hide file tree
Showing 2 changed files with 88 additions and 10 deletions.
97 changes: 87 additions & 10 deletions drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
Original file line number Diff line number Diff line change
Expand Up @@ -3154,6 +3154,13 @@ static void arm_smmu_write_msi_msg(struct msi_desc *desc, struct msi_msg *msg)
doorbell = (((u64)msg->address_hi) << 32) | msg->address_lo;
doorbell &= MSI_CFG0_ADDR_MASK;

#ifdef CONFIG_PM_SLEEP
/* Saves the msg (base addr of msi irq) and restores it during resume */
desc->msg.address_lo = msg->address_lo;
desc->msg.address_hi = msg->address_hi;
desc->msg.data = msg->data;
#endif

writeq_relaxed(doorbell, smmu->base + cfg[0]);
writel_relaxed(msg->data, smmu->base + cfg[1]);
writel_relaxed(ARM_SMMU_MEMATTR_DEVICE_nGnRE, smmu->base + cfg[2]);
Expand Down Expand Up @@ -3196,11 +3203,53 @@ static void arm_smmu_setup_msis(struct arm_smmu_device *smmu)
devm_add_action(dev, arm_smmu_free_msis, dev);
}

static void arm_smmu_setup_unique_irqs(struct arm_smmu_device *smmu)
#ifdef CONFIG_PM_SLEEP
static void arm_smmu_resume_msis(struct arm_smmu_device *smmu)
{
struct msi_desc *desc;
struct device *dev = smmu->dev;

if (!dev->msi.domain)
return;

msi_for_each_desc(desc, dev, MSI_DESC_ASSOCIATED) {
switch (desc->msi_index) {
case EVTQ_MSI_INDEX:
case GERROR_MSI_INDEX:
case PRIQ_MSI_INDEX: {
phys_addr_t *cfg = arm_smmu_msi_cfg[desc->msi_index];
struct msi_msg *msg = &desc->msg;
phys_addr_t doorbell = (((u64)msg->address_hi) << 32) | msg->address_lo;

doorbell &= MSI_CFG0_ADDR_MASK;
writeq_relaxed(doorbell, smmu->base + cfg[0]);
writel_relaxed(msg->data, smmu->base + cfg[1]);
writel_relaxed(ARM_SMMU_MEMATTR_DEVICE_nGnRE,
smmu->base + cfg[2]);
break;
}
default:
break;
}
}
}
#else
static void arm_smmu_resume_msis(struct arm_smmu_device *smmu)
{
}
#endif

static void arm_smmu_setup_unique_irqs(struct arm_smmu_device *smmu, bool resume)
{
int irq, ret;

arm_smmu_setup_msis(smmu);
if (!resume)
arm_smmu_setup_msis(smmu);
else {
/* The irq doesn't need to be re-requested during resume */
arm_smmu_resume_msis(smmu);
return;
}

/* Request interrupt lines */
irq = smmu->evtq.q.irq;
Expand Down Expand Up @@ -3242,7 +3291,7 @@ static void arm_smmu_setup_unique_irqs(struct arm_smmu_device *smmu)
}
}

static int arm_smmu_setup_irqs(struct arm_smmu_device *smmu)
static int arm_smmu_setup_irqs(struct arm_smmu_device *smmu, bool resume)
{
int ret, irq;
u32 irqen_flags = IRQ_CTRL_EVTQ_IRQEN | IRQ_CTRL_GERROR_IRQEN;
Expand All @@ -3269,7 +3318,7 @@ static int arm_smmu_setup_irqs(struct arm_smmu_device *smmu)
if (ret < 0)
dev_warn(smmu->dev, "failed to enable combined irq\n");
} else
arm_smmu_setup_unique_irqs(smmu);
arm_smmu_setup_unique_irqs(smmu, resume);

if (smmu->features & ARM_SMMU_FEAT_PRI)
irqen_flags |= IRQ_CTRL_PRIQ_IRQEN;
Expand All @@ -3294,7 +3343,7 @@ static int arm_smmu_device_disable(struct arm_smmu_device *smmu)
return ret;
}

static int arm_smmu_device_reset(struct arm_smmu_device *smmu, bool bypass)
static int arm_smmu_device_reset(struct arm_smmu_device *smmu, bool resume)
{
int ret;
u32 reg, enables;
Expand Down Expand Up @@ -3402,7 +3451,7 @@ static int arm_smmu_device_reset(struct arm_smmu_device *smmu, bool bypass)
}
}

ret = arm_smmu_setup_irqs(smmu);
ret = arm_smmu_setup_irqs(smmu, resume);
if (ret) {
dev_err(smmu->dev, "failed to setup irqs\n");
return ret;
Expand All @@ -3412,7 +3461,7 @@ static int arm_smmu_device_reset(struct arm_smmu_device *smmu, bool bypass)
enables &= ~(CR0_EVTQEN | CR0_PRIQEN);

/* Enable the SMMU interface, or ensure bypass */
if (!bypass || disable_bypass) {
if (!smmu->bypass || disable_bypass) {
enables |= CR0_SMMUEN;
} else {
ret = arm_smmu_update_gbpa(smmu, 0, GBPA_ABORT);
Expand Down Expand Up @@ -3797,14 +3846,31 @@ static void arm_smmu_rmr_install_bypass_ste(struct arm_smmu_device *smmu)
iort_put_rmr_sids(dev_fwnode(smmu->dev), &rmr_list);
}

#ifdef CONFIG_PM_SLEEP
static int arm_smmu_suspend(struct device *dev)
{
/*
* The smmu is powered off and related registers are automatically
* cleared when suspend. No need to do anything.
*/
return 0;
}
static int arm_smmu_resume(struct device *dev)
{
struct arm_smmu_device *smmu = dev_get_drvdata(dev);

arm_smmu_device_reset(smmu, true);
return 0;
}
#endif

static int arm_smmu_device_probe(struct platform_device *pdev)
{
int irq, ret;
struct resource *res;
resource_size_t ioaddr;
struct arm_smmu_device *smmu;
struct device *dev = &pdev->dev;
bool bypass;

smmu = devm_kzalloc(dev, sizeof(*smmu), GFP_KERNEL);
if (!smmu)
Expand All @@ -3820,7 +3886,7 @@ static int arm_smmu_device_probe(struct platform_device *pdev)
}

/* Set bypass mode according to firmware probing result */
bypass = !!ret;
smmu->bypass = !!ret;

/* Base address */
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
Expand Down Expand Up @@ -3884,7 +3950,7 @@ static int arm_smmu_device_probe(struct platform_device *pdev)
arm_smmu_rmr_install_bypass_ste(smmu);

/* Reset the device */
ret = arm_smmu_device_reset(smmu, bypass);
ret = arm_smmu_device_reset(smmu, false);
if (ret)
return ret;

Expand Down Expand Up @@ -3934,10 +4000,21 @@ static void arm_smmu_driver_unregister(struct platform_driver *drv)
platform_driver_unregister(drv);
}

#ifdef CONFIG_PM_SLEEP
static const struct dev_pm_ops arm_smmu_pm_ops = {
SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(arm_smmu_suspend,
arm_smmu_resume)
};
#define ARM_SMMU_PM_OPS (&arm_smmu_pm_ops)
#else
#define ARM_SMMU_PM_OPS NULL
#endif

static struct platform_driver arm_smmu_driver = {
.driver = {
.name = "arm-smmu-v3",
.of_match_table = arm_smmu_of_match,
.pm = ARM_SMMU_PM_OPS,
.suppress_bind_attrs = true,
},
.probe = arm_smmu_device_probe,
Expand Down
1 change: 1 addition & 0 deletions drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h
Original file line number Diff line number Diff line change
Expand Up @@ -682,6 +682,7 @@ struct arm_smmu_device {

struct rb_root streams;
struct mutex streams_mutex;
bool bypass;
};

struct arm_smmu_stream {
Expand Down

0 comments on commit 9ce9ded

Please sign in to comment.