hw/arm/smmuv3: Translate CD and TT using stage-2 table

According to ARM SMMU architecture specification (ARM IHI 0070 F.b),
In "5.2 Stream Table Entry":
 [51:6] S1ContextPtr
 If Config[1] == 1 (stage 2 enabled), this pointer is an IPA translated by
 stage 2 and the programmed value must be within the range of the IAS.

In "5.4.1 CD notes":
 The translation table walks performed from TTB0 or TTB1 are always performed
 in IPA space if stage 2 translations are enabled.

This patch implements translation of the S1 context descriptor pointer and
TTBx base addresses through the S2 stage (IPA -> PA)

smmuv3_do_translate() is updated to have one arg which is translation
class, this is useful to:
 - Decide wether a translation is stage-2 only or use the STE config.
 - Populate the class in case of faults, WALK_EABT is left unchanged
   for stage-1 as it is always IN, while stage-2 would match the
   used class (TT, IN, CD), this will change slightly when the ptw
   supports nested translation as it can also issue TT event with
   class IN.

In case for stage-2 only translation, used in the context of nested
translation, the stage and asid are saved and restored before and
after calling smmu_translate().

Translating CD or TTBx can fail for the following reasons:
1) Large address size: This is described in
   (3.4.3 Address sizes of SMMU-originated accesses)
   - For CD ptr larger than IAS, for SMMUv3.1, it can trigger either
     C_BAD_STE or Translation fault, we implement the latter as it
     requires no extra code.
   - For TTBx, if larger than the effective stage 1 output address size, it
     triggers C_BAD_CD.

2) Faults from PTWs (7.3 Event records)
   - F_ADDR_SIZE: large address size after first level causes stage 2 Address
     Size fault (Also in 3.4.3 Address sizes of SMMU-originated accesses)
   - F_PERMISSION: Same as an address translation. However, when
     CLASS == CD, the access is implicitly Data and a read.
   - F_ACCESS: Same as an address translation.
   - F_TRANSLATION: Same as an address translation.
   - F_WALK_EABT: Same as an address translation.
  These are already implemented in the PTW logic, so no extra handling
  required.

As in CD and TTBx translation context, the iova is not known, setting
the InputAddr was removed from "smmuv3_do_translate" and set after
from "smmuv3_translate" with the new function "smmuv3_fixup_event"

Signed-off-by: Mostafa Saleh <smostafa@google.com>
Reviewed-by: Jean-Philippe Brucker <jean-philippe@linaro.org>
Reviewed-by: Eric Auger <eric.auger@redhat.com>
Reviewed-by: Alex Bennée <alex.bennee@linaro.org>
Message-id: 20240715084519.1189624-9-smostafa@google.com
Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
This commit is contained in:
Mostafa Saleh 2024-07-15 08:45:08 +00:00 committed by Peter Maydell
parent ec31ef9115
commit 9dd6aa9b05

View File

@ -337,14 +337,35 @@ static int smmu_get_ste(SMMUv3State *s, dma_addr_t addr, STE *buf,
} }
static SMMUTranslationStatus smmuv3_do_translate(SMMUv3State *s, hwaddr addr,
SMMUTransCfg *cfg,
SMMUEventInfo *event,
IOMMUAccessFlags flag,
SMMUTLBEntry **out_entry,
SMMUTranslationClass class);
/* @ssid > 0 not supported yet */ /* @ssid > 0 not supported yet */
static int smmu_get_cd(SMMUv3State *s, STE *ste, uint32_t ssid, static int smmu_get_cd(SMMUv3State *s, STE *ste, SMMUTransCfg *cfg,
CD *buf, SMMUEventInfo *event) uint32_t ssid, CD *buf, SMMUEventInfo *event)
{ {
dma_addr_t addr = STE_CTXPTR(ste); dma_addr_t addr = STE_CTXPTR(ste);
int ret, i; int ret, i;
SMMUTranslationStatus status;
SMMUTLBEntry *entry;
trace_smmuv3_get_cd(addr); trace_smmuv3_get_cd(addr);
if (cfg->stage == SMMU_NESTED) {
status = smmuv3_do_translate(s, addr, cfg, event,
IOMMU_RO, &entry, SMMU_CLASS_CD);
/* Same PTW faults are reported but with CLASS = CD. */
if (status != SMMU_TRANS_SUCCESS) {
return -EINVAL;
}
addr = CACHED_ENTRY_TO_ADDR(entry, addr);
}
/* TODO: guarantee 64-bit single-copy atomicity */ /* TODO: guarantee 64-bit single-copy atomicity */
ret = dma_memory_read(&address_space_memory, addr, buf, sizeof(*buf), ret = dma_memory_read(&address_space_memory, addr, buf, sizeof(*buf),
MEMTXATTRS_UNSPECIFIED); MEMTXATTRS_UNSPECIFIED);
@ -659,10 +680,13 @@ static int smmu_find_ste(SMMUv3State *s, uint32_t sid, STE *ste,
return 0; return 0;
} }
static int decode_cd(SMMUTransCfg *cfg, CD *cd, SMMUEventInfo *event) static int decode_cd(SMMUv3State *s, SMMUTransCfg *cfg,
CD *cd, SMMUEventInfo *event)
{ {
int ret = -EINVAL; int ret = -EINVAL;
int i; int i;
SMMUTranslationStatus status;
SMMUTLBEntry *entry;
if (!CD_VALID(cd) || !CD_AARCH64(cd)) { if (!CD_VALID(cd) || !CD_AARCH64(cd)) {
goto bad_cd; goto bad_cd;
@ -713,9 +737,26 @@ static int decode_cd(SMMUTransCfg *cfg, CD *cd, SMMUEventInfo *event)
tt->tsz = tsz; tt->tsz = tsz;
tt->ttb = CD_TTB(cd, i); tt->ttb = CD_TTB(cd, i);
if (tt->ttb & ~(MAKE_64BIT_MASK(0, cfg->oas))) { if (tt->ttb & ~(MAKE_64BIT_MASK(0, cfg->oas))) {
goto bad_cd; goto bad_cd;
} }
/* Translate the TTBx, from IPA to PA if nesting is enabled. */
if (cfg->stage == SMMU_NESTED) {
status = smmuv3_do_translate(s, tt->ttb, cfg, event, IOMMU_RO,
&entry, SMMU_CLASS_TT);
/*
* Same PTW faults are reported but with CLASS = TT.
* If TTBx is larger than the effective stage 1 output addres
* size, it reports C_BAD_CD, which is handled by the above case.
*/
if (status != SMMU_TRANS_SUCCESS) {
return -EINVAL;
}
tt->ttb = CACHED_ENTRY_TO_ADDR(entry, tt->ttb);
}
tt->had = CD_HAD(cd, i); tt->had = CD_HAD(cd, i);
trace_smmuv3_decode_cd_tt(i, tt->tsz, tt->ttb, tt->granule_sz, tt->had); trace_smmuv3_decode_cd_tt(i, tt->tsz, tt->ttb, tt->granule_sz, tt->had);
} }
@ -767,12 +808,12 @@ static int smmuv3_decode_config(IOMMUMemoryRegion *mr, SMMUTransCfg *cfg,
return 0; return 0;
} }
ret = smmu_get_cd(s, &ste, 0 /* ssid */, &cd, event); ret = smmu_get_cd(s, &ste, cfg, 0 /* ssid */, &cd, event);
if (ret) { if (ret) {
return ret; return ret;
} }
return decode_cd(cfg, &cd, event); return decode_cd(s, cfg, &cd, event);
} }
/** /**
@ -832,58 +873,80 @@ static SMMUTranslationStatus smmuv3_do_translate(SMMUv3State *s, hwaddr addr,
SMMUTransCfg *cfg, SMMUTransCfg *cfg,
SMMUEventInfo *event, SMMUEventInfo *event,
IOMMUAccessFlags flag, IOMMUAccessFlags flag,
SMMUTLBEntry **out_entry) SMMUTLBEntry **out_entry,
SMMUTranslationClass class)
{ {
SMMUPTWEventInfo ptw_info = {}; SMMUPTWEventInfo ptw_info = {};
SMMUState *bs = ARM_SMMU(s); SMMUState *bs = ARM_SMMU(s);
SMMUTLBEntry *cached_entry = NULL; SMMUTLBEntry *cached_entry = NULL;
int asid, stage;
bool desc_s2_translation = class != SMMU_CLASS_IN;
/*
* The function uses the argument class to identify which stage is used:
* - CLASS = IN: Means an input translation, determine the stage from STE.
* - CLASS = CD: Means the addr is an IPA of the CD, and it would be
* translated using the stage-2.
* - CLASS = TT: Means the addr is an IPA of the stage-1 translation table
* and it would be translated using the stage-2.
* For the last 2 cases instead of having intrusive changes in the common
* logic, we modify the cfg to be a stage-2 translation only in case of
* nested, and then restore it after.
*/
if (desc_s2_translation) {
asid = cfg->asid;
stage = cfg->stage;
cfg->asid = -1;
cfg->stage = SMMU_STAGE_2;
}
cached_entry = smmu_translate(bs, cfg, addr, flag, &ptw_info); cached_entry = smmu_translate(bs, cfg, addr, flag, &ptw_info);
if (desc_s2_translation) {
cfg->asid = asid;
cfg->stage = stage;
}
if (!cached_entry) { if (!cached_entry) {
/* All faults from PTW has S2 field. */ /* All faults from PTW has S2 field. */
event->u.f_walk_eabt.s2 = (ptw_info.stage == SMMU_STAGE_2); event->u.f_walk_eabt.s2 = (ptw_info.stage == SMMU_STAGE_2);
switch (ptw_info.type) { switch (ptw_info.type) {
case SMMU_PTW_ERR_WALK_EABT: case SMMU_PTW_ERR_WALK_EABT:
event->type = SMMU_EVT_F_WALK_EABT; event->type = SMMU_EVT_F_WALK_EABT;
event->u.f_walk_eabt.addr = addr;
event->u.f_walk_eabt.rnw = flag & 0x1; event->u.f_walk_eabt.rnw = flag & 0x1;
event->u.f_walk_eabt.class = (ptw_info.stage == SMMU_STAGE_2) ? event->u.f_walk_eabt.class = (ptw_info.stage == SMMU_STAGE_2) ?
SMMU_CLASS_IN : SMMU_CLASS_TT; class : SMMU_CLASS_TT;
event->u.f_walk_eabt.addr2 = ptw_info.addr; event->u.f_walk_eabt.addr2 = ptw_info.addr;
break; break;
case SMMU_PTW_ERR_TRANSLATION: case SMMU_PTW_ERR_TRANSLATION:
if (PTW_RECORD_FAULT(cfg)) { if (PTW_RECORD_FAULT(cfg)) {
event->type = SMMU_EVT_F_TRANSLATION; event->type = SMMU_EVT_F_TRANSLATION;
event->u.f_translation.addr = addr;
event->u.f_translation.addr2 = ptw_info.addr; event->u.f_translation.addr2 = ptw_info.addr;
event->u.f_translation.class = SMMU_CLASS_IN; event->u.f_translation.class = class;
event->u.f_translation.rnw = flag & 0x1; event->u.f_translation.rnw = flag & 0x1;
} }
break; break;
case SMMU_PTW_ERR_ADDR_SIZE: case SMMU_PTW_ERR_ADDR_SIZE:
if (PTW_RECORD_FAULT(cfg)) { if (PTW_RECORD_FAULT(cfg)) {
event->type = SMMU_EVT_F_ADDR_SIZE; event->type = SMMU_EVT_F_ADDR_SIZE;
event->u.f_addr_size.addr = addr;
event->u.f_addr_size.addr2 = ptw_info.addr; event->u.f_addr_size.addr2 = ptw_info.addr;
event->u.f_addr_size.class = SMMU_CLASS_IN; event->u.f_addr_size.class = class;
event->u.f_addr_size.rnw = flag & 0x1; event->u.f_addr_size.rnw = flag & 0x1;
} }
break; break;
case SMMU_PTW_ERR_ACCESS: case SMMU_PTW_ERR_ACCESS:
if (PTW_RECORD_FAULT(cfg)) { if (PTW_RECORD_FAULT(cfg)) {
event->type = SMMU_EVT_F_ACCESS; event->type = SMMU_EVT_F_ACCESS;
event->u.f_access.addr = addr;
event->u.f_access.addr2 = ptw_info.addr; event->u.f_access.addr2 = ptw_info.addr;
event->u.f_access.class = SMMU_CLASS_IN; event->u.f_access.class = class;
event->u.f_access.rnw = flag & 0x1; event->u.f_access.rnw = flag & 0x1;
} }
break; break;
case SMMU_PTW_ERR_PERMISSION: case SMMU_PTW_ERR_PERMISSION:
if (PTW_RECORD_FAULT(cfg)) { if (PTW_RECORD_FAULT(cfg)) {
event->type = SMMU_EVT_F_PERMISSION; event->type = SMMU_EVT_F_PERMISSION;
event->u.f_permission.addr = addr;
event->u.f_permission.addr2 = ptw_info.addr; event->u.f_permission.addr2 = ptw_info.addr;
event->u.f_permission.class = SMMU_CLASS_IN; event->u.f_permission.class = class;
event->u.f_permission.rnw = flag & 0x1; event->u.f_permission.rnw = flag & 0x1;
} }
break; break;
@ -896,6 +959,27 @@ static SMMUTranslationStatus smmuv3_do_translate(SMMUv3State *s, hwaddr addr,
return SMMU_TRANS_SUCCESS; return SMMU_TRANS_SUCCESS;
} }
/*
* Sets the InputAddr for an SMMU_TRANS_ERROR, as it can't be
* set from all contexts, as smmuv3_get_config() can return
* translation faults in case of nested translation (for CD
* and TTBx). But in that case the iova is not known.
*/
static void smmuv3_fixup_event(SMMUEventInfo *event, hwaddr iova)
{
switch (event->type) {
case SMMU_EVT_F_WALK_EABT:
case SMMU_EVT_F_TRANSLATION:
case SMMU_EVT_F_ADDR_SIZE:
case SMMU_EVT_F_ACCESS:
case SMMU_EVT_F_PERMISSION:
event->u.f_walk_eabt.addr = iova;
break;
default:
break;
}
}
/* Entry point to SMMU, does everything. */ /* Entry point to SMMU, does everything. */
static IOMMUTLBEntry smmuv3_translate(IOMMUMemoryRegion *mr, hwaddr addr, static IOMMUTLBEntry smmuv3_translate(IOMMUMemoryRegion *mr, hwaddr addr,
IOMMUAccessFlags flag, int iommu_idx) IOMMUAccessFlags flag, int iommu_idx)
@ -944,7 +1028,8 @@ static IOMMUTLBEntry smmuv3_translate(IOMMUMemoryRegion *mr, hwaddr addr,
goto epilogue; goto epilogue;
} }
status = smmuv3_do_translate(s, addr, cfg, &event, flag, &cached_entry); status = smmuv3_do_translate(s, addr, cfg, &event, flag,
&cached_entry, SMMU_CLASS_IN);
epilogue: epilogue:
qemu_mutex_unlock(&s->mutex); qemu_mutex_unlock(&s->mutex);
@ -975,6 +1060,7 @@ epilogue:
entry.perm); entry.perm);
break; break;
case SMMU_TRANS_ERROR: case SMMU_TRANS_ERROR:
smmuv3_fixup_event(&event, addr);
qemu_log_mask(LOG_GUEST_ERROR, qemu_log_mask(LOG_GUEST_ERROR,
"%s translation failed for iova=0x%"PRIx64" (%s)\n", "%s translation failed for iova=0x%"PRIx64" (%s)\n",
mr->parent_obj.name, addr, smmu_event_string(event.type)); mr->parent_obj.name, addr, smmu_event_string(event.type));