arm: Assertion failure in do_ats_write() when executing ATS instructions with two-stage translation
## Host environment
- Operating system:
Rocky Linux 8 / AlmaLinux 8
- OS/kernel version:
4.18.0-348.el8.x86_64
- Architecture:
x86_64
- QEMU flavor:
qemu-system-aarch64
- QEMU version:
10.0.2
- QEMU command line: qemu-system-aarch64 -machine virt,virtualization=on,secure=on,gic-version=4 -cpu max -smp 4 -kernel my_elf_image
## Emulated/Virtualized environment
- Operating system: Proprietary bare-meta (guest with EL2 support)
- OS/kernel version: N/A (affects any guest using ATS instructions)
- Architecture: AArch64
## Description of problem
QEMU crashes with assertion failure when executing ATS (Address Translation Service) instructions in guests with two-stage translation enabled:
```
../target/arm/helper.c:3512: do_ats_write: Assertion `!res.cacheattrs.is_s2_format' failed.
Aborted (core dumped)
```
## Steps to reproduce
1. Configure QEMU with nested virtualization enabled
2. Boot a guest that uses EL2 (hypervisor mode)
3. Guest executes an ATS instruction (e.g., `AT S1E1R` or `AT S1E1W`)
4. QEMU crashes with assertion failure
## Additional information
**GDB Backtrace:**
```
#4 do_ats_write (env=0x55bfbec48000, value=52776665415888,
access_type=MMU_DATA_STORE, mmu_idx=ARMMMUIdx_E10_1, ss=ARMSS_Realm)
at ../target/arm/helper.c:3512
#5 ats_write64 (env=0x55bfbec48000, ri=0x55bfbecf8760, value=52776665415888)
at ../target/arm/helper.c:3838
```
**GDB Analysis at crash:**
```
(gdb) p ret
$1 = true
(gdb) p res.cacheattrs.is_s2_format
$2 = true
(gdb) p fi
$3 = {type = ARMFault_Permission, gpcf = GPCF_None, s2addr = 52776665415888,
paddr = 0, paddr_space = ARMSS_Secure, level = 3, domain = 0,
stage2 = true, s1ptw = false, s1ns = false, ea = false}
```
The fault info shows `stage2 = true` and `type = ARMFault_Permission`, indicating Stage 2 page table walk completed but returned a permission fault.
**Versions tested:**
- v9.2.4 (affected) - target/arm/helper.c line 3512
- v10.0.2 (affected) - target/arm/helper.c line 3512
- master branch as of 2026-03-10 (affected) - target/arm/tcg/cpregs-at.c line 41
**Root cause:** In `target/arm/ptw.c`, when a two-stage translation's Stage 2 fails (e.g., permission fault), `get_phys_addr_twostage()` returns early without calling `combine_cacheattrs()`:
```c
ret = get_phys_addr_nogpc(env, ptw, ipa, access_type,
memop, result, fi);
fi->s2addr = ipa;
/* Combine the S1 and S2 perms. */
result->f.prot &= s1_prot;
/* If S2 fails, return early. */
if (ret) {
return ret; // <-- Bug: is_s2_format still true from S2 page walk!
}
// ... combine_cacheattrs() called here on success path ...
result->cacheattrs = combine_cacheattrs(hcr, cacheattrs1,
result->cacheattrs);
```
During Stage 2 page table walks, `is_s2_format` is set to `true`. When the walk succeeds, `combine_cacheattrs()` resets it to `false`. But when Stage 2 fails, the early return skips this reset, leaving `is_s2_format = true` in the result returned to `do_ats_write()`.
**GDB trace showing Stage 2 permission fault:**
```
fi.type = ARMFault_Permission
fi.stage2 = true
fi.level = 3
res.cacheattrs.is_s2_format = true // <-- Never reset to false!
```
**Proposed fix:**
```
diff --git a/target/arm/ptw.c b/target/arm/ptw.c
index 4330900acd..32e8d9e5c1 100644
--- a/target/arm/ptw.c
+++ b/target/arm/ptw.c
@@ -3320,6 +3320,7 @@ static bool get_phys_addr_twostage(CPUARMState *env, S1Translate *ptw,
result->f.prot &= s1_prot;
/* If S2 fails, return early. */
if (ret) {
+ result->cacheattrs.is_s2_format = false;
return ret;
}
```
```
issue