master
c 629 lines 20.7 KB
Raw
1 /*
2 * QOS SMMUv3 Module
3 *
4 * This module provides SMMUv3-specific helper functions for libqos tests,
5 * encapsulating SMMUv3 setup, and assertions.
6 *
7 * Copyright (c) 2026 Phytium Technology
8 *
9 * Author:
10 * Tao Tang <tangtao1634@phytium.com.cn>
11 *
12 * SPDX-License-Identifier: GPL-2.0-or-later
13 */
14
15 #include "qemu/osdep.h"
16 #include "hw/arm/smmuv3-common.h"
17 #include "tests/qtest/libqos/pci.h"
18 #include "qos-iommu-testdev.h"
19 #include "qos-smmuv3.h"
20
21 #define QSMMU_STE_S2T0SZ_VAL 0x14
22
23 /* Apply space offset to address */
24 static inline uint64_t qsmmu_apply_space_offs(QSMMUSpace sp,
25 uint64_t address)
26 {
27 return address + qsmmu_space_offset(sp);
28 }
29
30 uint32_t qsmmu_expected_dma_result(QSMMUTestContext *ctx)
31 {
32 /* Currently only non-secure space is supported. */
33 if (ctx->tx_space != QSMMU_SPACE_NONSECURE) {
34 return ITD_DMA_ERR_TX_FAIL;
35 }
36 return ctx->config.expected_result;
37 }
38
39 uint32_t qsmmu_build_dma_attrs(QSMMUSpace space)
40 {
41 uint32_t attrs = 0;
42 switch (space) {
43 case QSMMU_SPACE_NONSECURE:
44 /* Non-secure: secure=0, space=1, space_valid=1 */
45 attrs = ITD_ATTRS_SET_SECURE(attrs, 0);
46 attrs = ITD_ATTRS_SET_SPACE(attrs, QSMMU_SPACE_NONSECURE);
47 attrs = ITD_ATTRS_SET_SPACE_VALID(attrs, 1);
48 break;
49 default:
50 g_assert_not_reached();
51 }
52
53 return attrs;
54 }
55
56 uint32_t qsmmu_setup_and_enable_translation(QSMMUTestContext *ctx)
57 {
58 uint32_t build_result;
59
60 /* Build page tables and SMMU structures first */
61 build_result = qsmmu_build_translation(
62 ctx->qts, ctx->config.trans_mode,
63 ctx->tx_space, ctx->sid);
64 if (build_result != 0) {
65 g_test_message("Build failed: mode=%u sid=%u status=0x%x",
66 ctx->config.trans_mode, ctx->sid, build_result);
67 ctx->trans_status = build_result;
68 return ctx->trans_status;
69 }
70
71 /* Program SMMU registers for the appropriate security space */
72 qsmmu_program_regs(ctx->qts, ctx->smmu_base, ctx->tx_space);
73
74 ctx->trans_status = 0;
75 return ctx->trans_status;
76 }
77
78 static bool qsmmu_validate_test_result(QSMMUTestContext *ctx)
79 {
80 uint32_t expected = qsmmu_expected_dma_result(ctx);
81 g_test_message("-> Validating result: expected=0x%x actual=0x%x",
82 expected, ctx->dma_result);
83 return (ctx->dma_result == expected);
84 }
85
86 QSMMUSpace qsmmu_sec_sid_to_space(QSMMUSecSID sec_sid)
87 {
88 switch (sec_sid) {
89 case QSMMU_SEC_SID_NONSECURE:
90 return QSMMU_SPACE_NONSECURE;
91 default:
92 g_assert_not_reached();
93 }
94 }
95
96 uint64_t qsmmu_space_offset(QSMMUSpace sp)
97 {
98 switch (sp) {
99 case QSMMU_SPACE_NONSECURE:
100 return QSMMU_SPACE_OFFS_NS;
101 default:
102 g_assert_not_reached();
103 }
104 }
105
106 static uint32_t qsmmu_single_translation_setup(void *opaque)
107 {
108 return qsmmu_setup_and_enable_translation(opaque);
109 }
110
111 static uint32_t qsmmu_single_translation_attrs(void *opaque)
112 {
113 QSMMUTestContext *ctx = opaque;
114
115 return qsmmu_build_dma_attrs(ctx->tx_space);
116 }
117
118 static bool qsmmu_single_translation_validate(void *opaque)
119 {
120 return qsmmu_validate_test_result(opaque);
121 }
122
123 static void qsmmu_single_translation_report(void *opaque,
124 uint32_t dma_result)
125 {
126 QSMMUTestContext *ctx = opaque;
127
128 if (dma_result != 0) {
129 g_test_message("DMA failed: mode=%u result=0x%x",
130 ctx->config.trans_mode, dma_result);
131 } else {
132 g_test_message("-> DMA succeeded: mode=%u",
133 ctx->config.trans_mode);
134 }
135 }
136
137 void qsmmu_run_translation_case(QTestState *qts, QPCIDevice *dev,
138 QPCIBar bar, uint64_t smmu_base,
139 const QSMMUTestConfig *cfg)
140 {
141 QSMMUTestContext ctx = {
142 .qts = qts,
143 .dev = dev,
144 .bar = bar,
145 .smmu_base = smmu_base,
146 .config = *cfg,
147 .sid = dev->devfn,
148 .tx_space = qsmmu_sec_sid_to_space(cfg->sec_sid),
149 };
150
151 QOSIOMMUTestdevDmaCfg dma = {
152 .dev = dev,
153 .bar = bar,
154 .iova = QSMMU_IOVA,
155 .gpa = ctx.config.dma_gpa,
156 .len = ctx.config.dma_len,
157 };
158
159 qtest_memset(qts, cfg->dma_gpa, 0x00, cfg->dma_len);
160 qos_iommu_testdev_single_translation(&dma, &ctx,
161 qsmmu_single_translation_setup,
162 qsmmu_single_translation_attrs,
163 qsmmu_single_translation_validate,
164 qsmmu_single_translation_report,
165 &ctx.dma_result);
166
167 if (ctx.dma_result == 0 && ctx.config.expected_result == 0) {
168 g_autofree uint8_t *buf = NULL;
169
170 buf = g_malloc(ctx.config.dma_len);
171 qtest_memread(ctx.qts, ctx.config.dma_gpa, buf, ctx.config.dma_len);
172
173 for (int i = 0; i < ctx.config.dma_len; i++) {
174 uint8_t expected;
175
176 expected = (ITD_DMA_WRITE_VAL >> ((i % 4) * 8)) & 0xff;
177 g_assert_cmpuint(buf[i], ==, expected);
178 }
179 }
180 }
181
182 uint32_t qsmmu_build_translation(QTestState *qts, QSMMUTransMode mode,
183 QSMMUSpace tx_space, uint32_t sid)
184 {
185 uint64_t ste_addr, ste_addr_real, cd_addr_real;
186 uint64_t cd_ttb, vttb, vttb_real;
187 uint8_t nscfg0, nscfg1;
188 QSMMUSpace build_space;
189 size_t ste_cd_entry_bytes = sizeof(STE);
190 STE ste;
191 CD cd;
192
193 build_space = tx_space;
194 if (build_space != QSMMU_SPACE_NONSECURE) {
195 return 0xdeadbeafu;
196 }
197
198 /* Build STE image */
199 memset(&ste, 0, sizeof(ste));
200 switch (mode) {
201 case QSMMU_TM_S1_ONLY:
202 STE_SET_CONFIG(&ste, 0x5);
203 break;
204 case QSMMU_TM_S2_ONLY:
205 STE_SET_CONFIG(&ste, 0x6);
206 break;
207 case QSMMU_TM_NESTED:
208 default:
209 STE_SET_CONFIG(&ste, 0x7);
210 break;
211 }
212
213 STE_SET_VALID(&ste, 1);
214 STE_SET_S2T0SZ(&ste, QSMMU_STE_S2T0SZ_VAL);
215 STE_SET_S2SL0(&ste, 0x2);
216 STE_SET_S2TG(&ste, 0);
217 STE_SET_S2PS(&ste, 0x5);
218 STE_SET_S2AA64(&ste, 1);
219 STE_SET_S2ENDI(&ste, 0);
220 STE_SET_S2AFFD(&ste, 0);
221
222 /*
223 * The consistent policy also extends to pointer fetches. For cases that
224 * require reading STE.S1ContextPtr or STE.S2TTB, we still follow the same
225 * policy:
226 * - The PA space security attribute of the address pointed to
227 * (e.g., the CD or S2L1 table) must also match the input 'SEC_SID'.
228 */
229 cd_addr_real = qsmmu_apply_space_offs(build_space, QSMMU_CD_GPA);
230 STE_SET_CTXPTR(&ste, cd_addr_real);
231
232 vttb = QSMMU_VTTB;
233 vttb_real = qsmmu_apply_space_offs(build_space, vttb);
234 STE_SET_S2TTB(&ste, vttb_real);
235
236 ste_addr = sid * ste_cd_entry_bytes + QSMMU_STR_TAB_BASE;
237 ste_addr_real = qsmmu_apply_space_offs(build_space, ste_addr);
238
239 /* Write STE to memory */
240 for (int i = 0; i < ARRAY_SIZE(ste.word); i++) {
241 qtest_writel(qts, ste_addr_real + i * 4, ste.word[i]);
242 }
243
244 switch (tx_space) {
245 case QSMMU_SPACE_NONSECURE:
246 nscfg0 = 0x1;
247 nscfg1 = 0x1;
248 break;
249 default:
250 g_assert_not_reached();
251 }
252 /* Build CD image for S1 path if needed */
253 if (mode != QSMMU_TM_S2_ONLY) {
254 memset(&cd, 0, sizeof(cd));
255
256 CD_SET_ASID(&cd, 0x1e20);
257 CD_SET_AARCH64(&cd, 1);
258 CD_SET_VALID(&cd, 1);
259 CD_SET_A(&cd, 1);
260 CD_SET_S(&cd, 0);
261 CD_SET_HD(&cd, 0);
262 CD_SET_HA(&cd, 0);
263 CD_SET_IPS(&cd, 0x4);
264 CD_SET_TBI(&cd, 0x0);
265 CD_SET_AFFD(&cd, 0x0);
266 CD_SET_EPD(&cd, 0, 0x0);
267 CD_SET_EPD(&cd, 1, 0x1);
268 CD_SET_TSZ(&cd, 0, 0x10);
269 CD_SET_TG(&cd, 0, 0x0);
270 CD_SET_ENDI(&cd, 0x0);
271
272 CD_SET_NSCFG(&cd, 0, nscfg0);
273 CD_SET_NSCFG(&cd, 1, nscfg1);
274 CD_SET_R(&cd, 0x1);
275 cd_ttb = vttb_real;
276 CD_SET_TTB(&cd, 0, cd_ttb);
277
278 for (int i = 0; i < ARRAY_SIZE(cd.word); i++) {
279 /* TODO: Maybe need more work to write to secure RAM in future */
280 qtest_writel(qts, cd_addr_real + i * 4, cd.word[i]);
281 g_assert_cmpint(qtest_readl(qts, cd_addr_real + i * 4), ==,
282 cd.word[i]);
283 }
284 }
285
286 qsmmu_setup_translation_tables(qts, QSMMU_IOVA, build_space,
287 false, mode);
288 /* Nested extras: CD S2 tables */
289 if (mode == QSMMU_TM_NESTED) {
290 /*
291 * Extra Stage 2 page tables is needed if
292 * SMMUTranslationClass == SMMU_CLASS_CD
293 * as smmuv3_do_translate would translate an IPA of the CD to the final
294 * output CD after a Stage 2 translation.
295 */
296 qsmmu_setup_translation_tables(qts, cd_addr_real, build_space,
297 true, mode);
298 }
299
300 return 0;
301 }
302
303 uint64_t qsmmu_bank_base(uint64_t base, QSMMUSpace sp)
304 {
305 switch (sp) {
306 case QSMMU_SPACE_NONSECURE:
307 return base;
308 default:
309 g_assert_not_reached();
310 }
311 }
312
313 void qsmmu_program_bank(QTestState *qts, uint64_t bank_base, QSMMUSpace sp)
314 {
315 uint64_t cmdq_base, eventq_base, strtab_base;
316
317 qtest_writel(qts, bank_base + A_GBPA, 0x80000000); /* UPDATE */
318 qtest_writel(qts, bank_base + A_CR0, 0x0); /* Disable */
319 qtest_writel(qts, bank_base + A_CR1, 0x0d75); /* Config */
320
321 /* CMDQ_BASE: add address-space offset*/
322 cmdq_base = qsmmu_apply_space_offs(sp, QSMMU_CMDQ_BASE_ADDR);
323 cmdq_base |= 0x0a; /* Size and valid bits */
324 qtest_writeq(qts, bank_base + A_CMDQ_BASE, cmdq_base);
325
326 qtest_writel(qts, bank_base + A_CMDQ_CONS, 0x0);
327 qtest_writel(qts, bank_base + A_CMDQ_PROD, 0x0);
328
329 /* EVENTQ_BASE: add address-space offset */
330 eventq_base = qsmmu_apply_space_offs(sp, QSMMU_EVENTQ_BASE_ADDR);
331 eventq_base |= 0x0a; /* Size and valid bits */
332 qtest_writeq(qts, bank_base + A_EVENTQ_BASE, eventq_base);
333
334 qtest_writel(qts, bank_base + A_EVENTQ_PROD, 0x0);
335 qtest_writel(qts, bank_base + A_EVENTQ_CONS, 0x0);
336
337 /* STRTAB_BASE_CFG: linear stream table, LOG2SIZE=5 */
338 qtest_writel(qts, bank_base + A_STRTAB_BASE_CFG, 0x5);
339
340 /* STRTAB_BASE: add address-space offset */
341 strtab_base = qsmmu_apply_space_offs(sp, QSMMU_STR_TAB_BASE);
342 qtest_writeq(qts, bank_base + A_STRTAB_BASE, strtab_base);
343
344 /* CR0: Enable SMMU with appropriate flags */
345 qtest_writel(qts, bank_base + A_CR0, 0xd);
346 }
347
348 void qsmmu_program_regs(QTestState *qts, uint64_t smmu_base, QSMMUSpace space)
349 {
350 uint64_t sp_base;
351 /* Always program Non-Secure bank first */
352 uint64_t ns_base = qsmmu_bank_base(smmu_base, QSMMU_SPACE_NONSECURE);
353 qsmmu_program_bank(qts, ns_base, QSMMU_SPACE_NONSECURE);
354
355 /* Program the requested space if different from Non-Secure */
356 sp_base = qsmmu_bank_base(smmu_base, space);
357 if (sp_base != ns_base) {
358 qsmmu_program_bank(qts, sp_base, space);
359 }
360 }
361
362 static uint32_t qsmmu_get_table_index(uint64_t addr, int level)
363 {
364 switch (level) {
365 case 0:
366 return (addr >> 39) & 0x1ff;
367 case 1:
368 return (addr >> 30) & 0x1ff;
369 case 2:
370 return (addr >> 21) & 0x1ff;
371 case 3:
372 return (addr >> 12) & 0x1ff;
373 default:
374 g_assert_not_reached();
375 }
376 }
377
378 static uint64_t qsmmu_get_table_addr(uint64_t base, int level, uint64_t iova)
379 {
380 uint32_t index = qsmmu_get_table_index(iova, level);
381 return (base & QSMMU_PTE_MASK) + (index * 8);
382 }
383
384 /*
385 * qsmmu_get_pte_attrs - Calculate the S1 leaf PTE value
386 *
387 * IOMMU need to set different attributes for PTEs based on the translation mode
388 */
389 static uint64_t qsmmu_get_pte_attrs(QSMMUTransMode mode, bool is_leaf,
390 QSMMUSpace space)
391 {
392 uint64_t rw_mask = QSMMU_LEAF_PTE_RW_MASK;
393 uint64_t ro_mask = QSMMU_LEAF_PTE_RO_MASK;
394 uint64_t non_leaf_mask = QSMMU_NON_LEAF_PTE_MASK;
395
396 switch (space) {
397 case QSMMU_SPACE_NONSECURE:
398 break;
399 default:
400 g_assert_not_reached();
401 }
402
403 if (!is_leaf) {
404 return non_leaf_mask;
405 }
406
407 /* For leaf PTE */
408 if (mode == QSMMU_TM_NESTED || mode == QSMMU_TM_S1_ONLY) {
409 return rw_mask;
410 }
411
412 return ro_mask;
413 }
414
415 /*
416 * qsmmu_setup_s2_walk_for_ipa - Setup Stage 2 page table walk for an IPA
417 *
418 * @qts: QTest state handle
419 * @space: Security space
420 * @ipa: Intermediate Physical Address to translate
421 * @s2_vttb: Stage 2 VTTB (page table base)
422 * @mode: Translation mode
423 * @is_final: Whether this is the final S2 walk (not nested within S1)
424 *
425 * Calculates and writes a 4-level Stage 2 page table walk for the given IPA.
426 * This function dynamically generates and writes all page table entries
427 * (L0-L3) to guest memory based on the input IPA and configuration.
428 */
429 static void qsmmu_setup_s2_walk_for_ipa(QTestState *qts,
430 QSMMUSpace space,
431 uint64_t ipa,
432 uint64_t s2_vttb,
433 QSMMUTransMode mode,
434 bool is_final)
435 {
436 uint64_t all_s2_l0_pte_val;
437 uint64_t all_s2_l1_pte_val;
438 uint64_t all_s2_l2_pte_val;
439 uint64_t all_s2_l3_pte_val;
440 uint64_t s2_l0_addr, s2_l1_addr, s2_l2_addr, s2_l3_addr;
441
442 /* Shared intermediate PTE values for all S2 walks */
443 all_s2_l0_pte_val = qsmmu_apply_space_offs(
444 space, QSMMU_L0_PTE_VAL | qsmmu_get_pte_attrs(mode, false, space));
445 all_s2_l1_pte_val = qsmmu_apply_space_offs(
446 space, QSMMU_L1_PTE_VAL | qsmmu_get_pte_attrs(mode, false, space));
447 all_s2_l2_pte_val = qsmmu_apply_space_offs(
448 space, QSMMU_L2_PTE_VAL | qsmmu_get_pte_attrs(mode, false, space));
449
450 /* Stage 2 Level 0 */
451 s2_l0_addr = qsmmu_get_table_addr(s2_vttb, 0, ipa);
452 qtest_writeq(qts, s2_l0_addr, all_s2_l0_pte_val);
453
454 /* Stage 2 Level 1 */
455 s2_l1_addr = qsmmu_get_table_addr(all_s2_l0_pte_val, 1, ipa);
456 qtest_writeq(qts, s2_l1_addr, all_s2_l1_pte_val);
457
458 /* Stage 2 Level 2 */
459 s2_l2_addr = qsmmu_get_table_addr(all_s2_l1_pte_val, 2, ipa);
460 qtest_writeq(qts, s2_l2_addr, all_s2_l2_pte_val);
461
462 /* Stage 2 Level 3 (leaf) */
463 s2_l3_addr = qsmmu_get_table_addr(all_s2_l2_pte_val, 3, ipa);
464
465 /*
466 * Stage 2 L3 PTE attributes depend on the context:
467 * - For nested S1 table address translations (!is_final):
468 * Use LEAF attrs (0x763) because these PTEs map S1 table pages directly
469 * - For final S2 walk (is_final):
470 * Use TABLE attrs (0x7e3) for the final IPA→PA mapping
471 */
472 if (!is_final) {
473 all_s2_l3_pte_val =
474 (ipa & QSMMU_PTE_MASK) |
475 qsmmu_get_pte_attrs(QSMMU_TM_NESTED, true, space);
476 } else {
477 all_s2_l3_pte_val =
478 (ipa & QSMMU_PTE_MASK) |
479 qsmmu_get_pte_attrs(QSMMU_TM_S2_ONLY, true, space);
480 }
481
482 qtest_writeq(qts, s2_l3_addr, all_s2_l3_pte_val);
483 }
484
485 /*
486 * qsmmu_setup_s1_level_with_nested_s2 - Setup S1 level with nested S2 walk
487 *
488 * @qts: QTest state handle
489 * @space: Security space
490 * @s1_level: Stage 1 level (0-3)
491 * @s1_pte_addr: Stage 1 PTE address (as IPA)
492 * @s1_pte_val: Stage 1 PTE value to write
493 * @s2_vttb: Stage 2 VTTB for nested translation
494 * @mode: Translation mode
495 *
496 * For nested translation, each S1 table access requires a full S2 walk
497 * to translate the S1 table's IPA to PA. This function performs the nested
498 * S2 walk and writes the S1 PTE value to guest memory.
499 */
500 static void qsmmu_setup_s1_level_with_nested_s2(QTestState *qts,
501 QSMMUSpace space,
502 int s1_level,
503 uint64_t s1_pte_addr,
504 uint64_t s1_pte_val,
505 uint64_t s2_vttb,
506 QSMMUTransMode mode)
507 {
508 /*
509 * Perform nested S2 walk to translate S1 table IPA to PA.
510 * This is always needed for S1_ONLY/S2_ONLY/NESTED modes because:
511 * - S1_ONLY: Needs S2 tables for "IPA as PA" mapping (for testing)
512 * - S2_ONLY: Needs S2 tables for direct translation
513 * - NESTED: Needs S2 tables for nested translation
514 */
515 qsmmu_setup_s2_walk_for_ipa(qts, space, s1_pte_addr,
516 s2_vttb, mode, false);
517
518 /* Write the S1 PTE value */
519 qtest_writeq(qts, s1_pte_addr, s1_pte_val);
520 }
521
522 /*
523 * qsmmu_setup_translation_tables - Setup SMMU translation tables
524 *
525 * The 'SEC_SID' represents the input security state of the device/transaction,
526 * whether it's a static Secure state or a dynamically-switched Realm state.
527 * SEC_SID has been converted to the corresponding Security Space (QSMMUSpace)
528 * before calling this function.
529 *
530 * In a real SMMU translation, this input security state does not unilaterally
531 * determine the output Physical Address (PA) space. The output PA space is
532 * ultimately determined by attributes encountered during the page table walk,
533 * such as NSCFG and NSTable.
534 *
535 * However, for the specific context of testing the SMMU with the iommu-testdev,
536 * and to simplify the future support for Secure and Realm states, we adopt a
537 * consistent policy:
538 *
539 * - We always ensure that the page table attributes (e.g., nscfg, nstable)
540 * *match* the input 'SEC_SID' of the test case.
541 *
542 * For example: If 'SEC_SID' is Non-Secure, the corresponding nscfg and nstable
543 * attributes in the translation tables will always be set to 1.
544 *
545 */
546 void qsmmu_setup_translation_tables(QTestState *qts,
547 uint64_t iova,
548 QSMMUSpace space,
549 bool is_cd,
550 QSMMUTransMode mode)
551 {
552 uint64_t all_s2_l0_pte_val, all_s2_l1_pte_val, all_s2_l2_pte_val;
553 uint64_t s1_vttb, s2_vttb, s1_leaf_pte_val;
554 uint64_t l0_addr, l1_addr, l2_addr, l3_addr;
555
556 g_test_message("Begin of construction: IOVA=0x%" PRIx64
557 " mode=%d is_building_CD=%s ===",
558 iova, mode, is_cd ? "yes" : "no");
559
560 /* Initialize shared S2 PTE values used across all walks */
561 all_s2_l0_pte_val = qsmmu_apply_space_offs(
562 space, QSMMU_L0_PTE_VAL | qsmmu_get_pte_attrs(mode, false, space));
563 all_s2_l1_pte_val = qsmmu_apply_space_offs(
564 space, QSMMU_L1_PTE_VAL | qsmmu_get_pte_attrs(mode, false, space));
565 all_s2_l2_pte_val = qsmmu_apply_space_offs(
566 space, QSMMU_L2_PTE_VAL | qsmmu_get_pte_attrs(mode, false, space));
567
568 /* Both S1 and S2 share the same VTTB base */
569 s1_vttb = qsmmu_apply_space_offs(space, QSMMU_VTTB & QSMMU_PTE_MASK);
570 s2_vttb = s1_vttb;
571
572 if (!is_cd) {
573 /*
574 * Setup Stage 1 page tables with nested Stage 2 walks.
575 * For each S1 level (L0-L3), we need to:
576 * 1. Calculate S1 PTE address (as IPA)
577 * 2. Perform nested S2 walk to translate that IPA to PA
578 * 3. Write the S1 PTE value
579 */
580
581 /* Stage 1 Level 0 */
582 l0_addr = qsmmu_get_table_addr(s1_vttb, 0, iova);
583 qsmmu_setup_s1_level_with_nested_s2(qts, space, 0, l0_addr,
584 all_s2_l0_pte_val, s2_vttb, mode);
585
586 /* Stage 1 Level 1 */
587 l1_addr = qsmmu_get_table_addr(all_s2_l0_pte_val & QSMMU_PTE_MASK,
588 1, iova);
589 qsmmu_setup_s1_level_with_nested_s2(qts, space, 1, l1_addr,
590 all_s2_l1_pte_val, s2_vttb, mode);
591
592 /* Stage 1 Level 2 */
593 l2_addr = qsmmu_get_table_addr(all_s2_l1_pte_val & QSMMU_PTE_MASK,
594 2, iova);
595 qsmmu_setup_s1_level_with_nested_s2(qts, space, 2, l2_addr,
596 all_s2_l2_pte_val, s2_vttb, mode);
597
598 /* Stage 1 Level 3 (leaf) */
599 l3_addr = qsmmu_get_table_addr(all_s2_l2_pte_val & QSMMU_PTE_MASK,
600 3, iova);
601
602 s1_leaf_pte_val = qsmmu_apply_space_offs(
603 space, QSMMU_L3_PTE_VAL | qsmmu_get_pte_attrs(mode, true, space)
604 );
605
606 qsmmu_setup_s1_level_with_nested_s2(qts, space, 3, l3_addr,
607 s1_leaf_pte_val, s2_vttb, mode);
608 } else {
609 /*
610 * For CD address translation, we start directly with the IPA.
611 */
612 s1_leaf_pte_val = iova | qsmmu_get_pte_attrs(QSMMU_TM_NESTED,
613 false, space);
614 }
615
616 /*
617 * Final Stage 2 walk: Translate the result from Stage 1.
618 * - For S1_ONLY: This is skipped in hardware but we set it up for testing
619 * - For S2_ONLY: This is the only walk
620 * - For NESTED: This translates the IPA from S1 to final PA
621 * - For CD address (is_cd=true): This is a table address, use !is_final
622 */
623 qsmmu_setup_s2_walk_for_ipa(qts, space, s1_leaf_pte_val, s2_vttb,
624 mode, !is_cd);
625
626 /* Calculate and log final translated PA */
627 g_test_message("End of construction: PA=0x%llx ===",
628 (s1_leaf_pte_val & QSMMU_PTE_MASK) + (iova & 0xfff));
629 }