aboutsummaryrefslogtreecommitdiff
path: root/hw/arm
diff options
context:
space:
mode:
authorMostafa Saleh <smostafa@google.com>2024-07-15 08:45:10 +0000
committerPeter Maydell <peter.maydell@linaro.org>2024-07-18 13:49:29 +0100
commitd7cdf89c276abeb392cbf9b9d0dde5060a8c778f (patch)
treeeafb11691c7e35a20e88efc82bfda6c747adf915 /hw/arm
parent7eb57be1efb8cdcf613fd8b089295ed5a4f48d7d (diff)
downloadqemu-d7cdf89c276abeb392cbf9b9d0dde5060a8c778f.zip
qemu-d7cdf89c276abeb392cbf9b9d0dde5060a8c778f.tar.gz
qemu-d7cdf89c276abeb392cbf9b9d0dde5060a8c778f.tar.bz2
hw/arm/smmu-common: Add support for nested TLB
This patch adds support for nested (combined) TLB entries. The main function combine_tlb() is not used here but in the next patches, but to simplify the patches it is introduced first. Main changes: 1) New field added in the SMMUTLBEntry struct: parent_perm, for nested TLB, holds the stage-2 permission, this can be used to know the origin of a permission fault from a cached entry as caching the “and” of the permissions loses this information. SMMUPTWEventInfo is used to hold information about PTW faults so the event can be populated, the value of stage used to be set based on the current stage for TLB permission faults, however with the parent_perm, it is now set based on which perm has the missing permission When nesting is not enabled it has the same value as perm which doesn't change the logic. 2) As combined TLB implementation is used, the combination logic chooses: - tg and level from the entry which has the smallest addr_mask. - Based on that the iova that would be cached is recalculated. - Translated_addr is chosen from stage-2. Reviewed-by: Eric Auger <eric.auger@redhat.com> Reviewed-by: Jean-Philippe Brucker <jean-philippe@linaro.org> Signed-off-by: Mostafa Saleh <smostafa@google.com> Reviewed-by: Alex Bennée <alex.bennee@linaro.org> Message-id: 20240715084519.1189624-11-smostafa@google.com Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
Diffstat (limited to 'hw/arm')
-rw-r--r--hw/arm/smmu-common.c37
1 files changed, 33 insertions, 4 deletions
diff --git a/hw/arm/smmu-common.c b/hw/arm/smmu-common.c
index e374cae..bf55b9c 100644
--- a/hw/arm/smmu-common.c
+++ b/hw/arm/smmu-common.c
@@ -426,7 +426,8 @@ static int smmu_ptw_64_s1(SMMUTransCfg *cfg,
tlbe->entry.translated_addr = gpa;
tlbe->entry.iova = iova & ~mask;
tlbe->entry.addr_mask = mask;
- tlbe->entry.perm = PTE_AP_TO_PERM(ap);
+ tlbe->parent_perm = PTE_AP_TO_PERM(ap);
+ tlbe->entry.perm = tlbe->parent_perm;
tlbe->level = level;
tlbe->granule = granule_sz;
return 0;
@@ -547,7 +548,8 @@ static int smmu_ptw_64_s2(SMMUTransCfg *cfg,
tlbe->entry.translated_addr = gpa;
tlbe->entry.iova = ipa & ~mask;
tlbe->entry.addr_mask = mask;
- tlbe->entry.perm = s2ap;
+ tlbe->parent_perm = s2ap;
+ tlbe->entry.perm = tlbe->parent_perm;
tlbe->level = level;
tlbe->granule = granule_sz;
return 0;
@@ -562,6 +564,30 @@ error:
return -EINVAL;
}
+/*
+ * combine S1 and S2 TLB entries into a single entry.
+ * As a result the S1 entry is overriden with combined data.
+ */
+static void __attribute__((unused)) combine_tlb(SMMUTLBEntry *tlbe,
+ SMMUTLBEntry *tlbe_s2,
+ dma_addr_t iova,
+ SMMUTransCfg *cfg)
+{
+ if (tlbe_s2->entry.addr_mask < tlbe->entry.addr_mask) {
+ tlbe->entry.addr_mask = tlbe_s2->entry.addr_mask;
+ tlbe->granule = tlbe_s2->granule;
+ tlbe->level = tlbe_s2->level;
+ }
+
+ tlbe->entry.translated_addr = CACHED_ENTRY_TO_ADDR(tlbe_s2,
+ tlbe->entry.translated_addr);
+
+ tlbe->entry.iova = iova & ~tlbe->entry.addr_mask;
+ /* parent_perm has s2 perm while perm keeps s1 perm. */
+ tlbe->parent_perm = tlbe_s2->entry.perm;
+ return;
+}
+
/**
* smmu_ptw - Walk the page tables for an IOVA, according to @cfg
*
@@ -629,9 +655,12 @@ SMMUTLBEntry *smmu_translate(SMMUState *bs, SMMUTransCfg *cfg, dma_addr_t addr,
cached_entry = smmu_iotlb_lookup(bs, cfg, &tt_combined, addr);
if (cached_entry) {
- if ((flag & IOMMU_WO) && !(cached_entry->entry.perm & IOMMU_WO)) {
+ if ((flag & IOMMU_WO) && !(cached_entry->entry.perm &
+ cached_entry->parent_perm & IOMMU_WO)) {
info->type = SMMU_PTW_ERR_PERMISSION;
- info->stage = cfg->stage;
+ info->stage = !(cached_entry->entry.perm & IOMMU_WO) ?
+ SMMU_STAGE_1 :
+ SMMU_STAGE_2;
return NULL;
}
return cached_entry;