Bootloaders may leave display controllers scanning out a boot splash while the kernel boots. Enabling translation for them without special care during that time would cause translation faults.
Because of this, tegra_smmu_def_domain_type has kept every device on an identity domain at boot, and TegraDRM has parallel custom IOMMU domain management code paths for these clients. Mark the dc and dcb swgroups on Tegra30, Tegra114, Tegra124 and Tegra210 with a new defer_enable flag. tegra_smmu_enable() sets those swgroups up as usual, ASID and per-client enables and all, but leaves SMMU_ASID_ENABLE clear, so the clients stay in bypass. tegra_dc_probe() then calls tegra_smmu_enable_translation() once it has asserted the display controller's reset (and set up its final IOMMU domain), at which point the hardware is guaranteed not to be fetching and translation can be turned on. If the display driver never probes, the clients simply stay in bypass, which is what they do today. Signed-off-by: Mikko Perttunen <[email protected]> --- drivers/gpu/drm/tegra/dc.c | 8 +++++++ drivers/iommu/tegra-smmu.c | 51 ++++++++++++++++++++++++++++++++++++++++- drivers/memory/tegra/tegra114.c | 6 +++-- drivers/memory/tegra/tegra124.c | 6 +++-- drivers/memory/tegra/tegra210.c | 6 +++-- drivers/memory/tegra/tegra30.c | 6 +++-- include/soc/tegra/mc.h | 13 +++++++++++ 7 files changed, 87 insertions(+), 9 deletions(-) diff --git a/drivers/gpu/drm/tegra/dc.c b/drivers/gpu/drm/tegra/dc.c index 0b3fcc7011b3..2ae4da07bbc9 100644 --- a/drivers/gpu/drm/tegra/dc.c +++ b/drivers/gpu/drm/tegra/dc.c @@ -19,6 +19,7 @@ #include <linux/reset.h> #include <soc/tegra/common.h> +#include <soc/tegra/mc.h> #include <soc/tegra/pmc.h> #include <drm/drm_atomic.h> @@ -2751,6 +2752,13 @@ static int tegra_dc_init(struct host1x_client *client) return err; } + /* + * Enable translation now that the final domain is attached. + * The hardware was reset at probe and has not been programmed + * since. + */ + tegra_smmu_enable_translation(client->dev); + if (dc->soc->wgrps) primary = tegra_dc_add_shared_planes(drm, dc); else diff --git a/drivers/iommu/tegra-smmu.c b/drivers/iommu/tegra-smmu.c index 67e7a7b925f0..6636a044905f 100644 --- a/drivers/iommu/tegra-smmu.c +++ b/drivers/iommu/tegra-smmu.c @@ -368,7 +368,13 @@ static void tegra_smmu_enable(struct tegra_smmu *smmu, unsigned int swgroup, value = smmu_readl(smmu, group->reg); value &= ~SMMU_ASID_MASK; value |= SMMU_ASID_VALUE(asid); - value |= SMMU_ASID_ENABLE; + /* + * A deferred client is set up completely, but with translation + * gated off, so it stays in bypass until its driver calls + * tegra_smmu_enable_translation(). + */ + if (!group->defer_enable) + value |= SMMU_ASID_ENABLE; smmu_writel(smmu, value, group->reg); } else { pr_warn("%s group from swgroup %u not found\n", __func__, @@ -417,6 +423,49 @@ static void tegra_smmu_disable(struct tegra_smmu *smmu, unsigned int swgroup, } } +/** + * tegra_smmu_enable_translation - Release a client's swgroups to translation + * @dev: memory client device, already quiesced by its driver + * + * Enables translation for swgroups which were left in bypass at attach time + * because the client could still have been running from a pre-kernel + * configuration. Must be called after @dev has been attached to the domain it + * will use. Safe to call for any device; clients which were not deferred are + * unaffected. + */ +void tegra_smmu_enable_translation(struct device *dev) +{ + struct iommu_fwspec *fwspec = dev_iommu_fwspec_get(dev); + struct tegra_smmu *smmu = dev_iommu_priv_get(dev); + struct iommu_domain *domain; + unsigned int i; + + if (!fwspec || !smmu) + return; + + domain = iommu_get_domain_for_dev(dev); + if (!domain || !(domain->type & __IOMMU_DOMAIN_PAGING)) + return; + + mutex_lock(&smmu->lock); + + for (i = 0; i < fwspec->num_ids; i++) { + const struct tegra_smmu_swgroup *group; + u32 value; + + group = tegra_smmu_find_swgroup(smmu, fwspec->ids[i]); + if (!group || !group->defer_enable) + continue; + + value = smmu_readl(smmu, group->reg); + value |= SMMU_ASID_ENABLE; + smmu_writel(smmu, value, group->reg); + } + + mutex_unlock(&smmu->lock); +} +EXPORT_SYMBOL_GPL(tegra_smmu_enable_translation); + static int tegra_smmu_as_prepare(struct tegra_smmu *smmu, struct tegra_smmu_as *as) { diff --git a/drivers/memory/tegra/tegra114.c b/drivers/memory/tegra/tegra114.c index 2cb153091228..2883c4f91144 100644 --- a/drivers/memory/tegra/tegra114.c +++ b/drivers/memory/tegra/tegra114.c @@ -1028,8 +1028,10 @@ static const struct tegra_mc_client tegra114_mc_clients[] = { }; static const struct tegra_smmu_swgroup tegra114_swgroups[] = { - { .name = "dc", .swgroup = TEGRA_SWGROUP_DC, .reg = 0x240 }, - { .name = "dcb", .swgroup = TEGRA_SWGROUP_DCB, .reg = 0x244 }, + { .name = "dc", .swgroup = TEGRA_SWGROUP_DC, .reg = 0x240, + .defer_enable = true }, + { .name = "dcb", .swgroup = TEGRA_SWGROUP_DCB, .reg = 0x244, + .defer_enable = true }, { .name = "epp", .swgroup = TEGRA_SWGROUP_EPP, .reg = 0x248 }, { .name = "g2", .swgroup = TEGRA_SWGROUP_G2, .reg = 0x24c }, { .name = "avpc", .swgroup = TEGRA_SWGROUP_AVPC, .reg = 0x23c }, diff --git a/drivers/memory/tegra/tegra124.c b/drivers/memory/tegra/tegra124.c index df87c5038625..a38fbf2baf6c 100644 --- a/drivers/memory/tegra/tegra124.c +++ b/drivers/memory/tegra/tegra124.c @@ -1070,8 +1070,10 @@ static const struct tegra_mc_client tegra124_mc_clients[] = { }; static const struct tegra_smmu_swgroup tegra124_swgroups[] = { - { .name = "dc", .swgroup = TEGRA_SWGROUP_DC, .reg = 0x240 }, - { .name = "dcb", .swgroup = TEGRA_SWGROUP_DCB, .reg = 0x244 }, + { .name = "dc", .swgroup = TEGRA_SWGROUP_DC, .reg = 0x240, + .defer_enable = true }, + { .name = "dcb", .swgroup = TEGRA_SWGROUP_DCB, .reg = 0x244, + .defer_enable = true }, { .name = "afi", .swgroup = TEGRA_SWGROUP_AFI, .reg = 0x238 }, { .name = "avpc", .swgroup = TEGRA_SWGROUP_AVPC, .reg = 0x23c }, { .name = "hda", .swgroup = TEGRA_SWGROUP_HDA, .reg = 0x254 }, diff --git a/drivers/memory/tegra/tegra210.c b/drivers/memory/tegra/tegra210.c index f58f3ef6f681..92e809cd7c26 100644 --- a/drivers/memory/tegra/tegra210.c +++ b/drivers/memory/tegra/tegra210.c @@ -1166,8 +1166,10 @@ static const struct tegra_mc_client tegra210_mc_clients[] = { static const struct tegra_smmu_swgroup tegra210_swgroups[] = { { .name = "afi", .swgroup = TEGRA_SWGROUP_AFI, .reg = 0x238 }, { .name = "avpc", .swgroup = TEGRA_SWGROUP_AVPC, .reg = 0x23c }, - { .name = "dc", .swgroup = TEGRA_SWGROUP_DC, .reg = 0x240 }, - { .name = "dcb", .swgroup = TEGRA_SWGROUP_DCB, .reg = 0x244 }, + { .name = "dc", .swgroup = TEGRA_SWGROUP_DC, .reg = 0x240, + .defer_enable = true }, + { .name = "dcb", .swgroup = TEGRA_SWGROUP_DCB, .reg = 0x244, + .defer_enable = true }, { .name = "hc", .swgroup = TEGRA_SWGROUP_HC, .reg = 0x250 }, { .name = "hda", .swgroup = TEGRA_SWGROUP_HDA, .reg = 0x254 }, { .name = "isp2", .swgroup = TEGRA_SWGROUP_ISP2, .reg = 0x258 }, diff --git a/drivers/memory/tegra/tegra30.c b/drivers/memory/tegra/tegra30.c index 8389e3af0121..0ee4715413ba 100644 --- a/drivers/memory/tegra/tegra30.c +++ b/drivers/memory/tegra/tegra30.c @@ -1139,8 +1139,10 @@ static const struct tegra_mc_client tegra30_mc_clients[] = { }; static const struct tegra_smmu_swgroup tegra30_swgroups[] = { - { .name = "dc", .swgroup = TEGRA_SWGROUP_DC, .reg = 0x240 }, - { .name = "dcb", .swgroup = TEGRA_SWGROUP_DCB, .reg = 0x244 }, + { .name = "dc", .swgroup = TEGRA_SWGROUP_DC, .reg = 0x240, + .defer_enable = true }, + { .name = "dcb", .swgroup = TEGRA_SWGROUP_DCB, .reg = 0x244, + .defer_enable = true }, { .name = "epp", .swgroup = TEGRA_SWGROUP_EPP, .reg = 0x248 }, { .name = "g2", .swgroup = TEGRA_SWGROUP_G2, .reg = 0x24c }, { .name = "mpe", .swgroup = TEGRA_SWGROUP_MPE, .reg = 0x264 }, diff --git a/include/soc/tegra/mc.h b/include/soc/tegra/mc.h index 25d465d70493..ea67972fd585 100644 --- a/include/soc/tegra/mc.h +++ b/include/soc/tegra/mc.h @@ -70,6 +70,14 @@ struct tegra_smmu_swgroup { const char *name; unsigned int swgroup; unsigned int reg; + + /* + * Set for clients which may already be running when the kernel takes + * over, e.g. display controllers scanning out a boot splash. They are + * left in bypass until their driver calls + * tegra_smmu_enable_translation(). + */ + bool defer_enable; }; struct tegra_smmu_group_soc { @@ -103,6 +111,7 @@ struct tegra_smmu *tegra_smmu_probe(struct device *dev, const struct tegra_smmu_soc *soc, struct tegra_mc *mc); void tegra_smmu_remove(struct tegra_smmu *smmu); +void tegra_smmu_enable_translation(struct device *dev); #else static inline struct tegra_smmu * tegra_smmu_probe(struct device *dev, const struct tegra_smmu_soc *soc, @@ -114,6 +123,10 @@ tegra_smmu_probe(struct device *dev, const struct tegra_smmu_soc *soc, static inline void tegra_smmu_remove(struct tegra_smmu *smmu) { } + +static inline void tegra_smmu_enable_translation(struct device *dev) +{ +} #endif struct tegra_mc_reset { -- 2.55.0
