xlat_tables_utils.c 14 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/*
 * Copyright (c) 2017-2018, ARM Limited and Contributors. All rights reserved.
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */

#include <arch_helpers.h>
#include <assert.h>
#include <debug.h>
#include <errno.h>
#include <platform_def.h>
#include <types.h>
#include <utils_def.h>
#include <xlat_tables_defs.h>
#include <xlat_tables_v2.h>

#include "xlat_tables_private.h"

#if LOG_LEVEL < LOG_LEVEL_VERBOSE

Antonio Nino Diaz's avatar
Antonio Nino Diaz committed
21
void xlat_mmap_print(__unused const mmap_region_t *mmap)
22
23
24
25
26
27
28
29
30
31
32
{
	/* Empty */
}

void xlat_tables_print(__unused xlat_ctx_t *ctx)
{
	/* Empty */
}

#else /* if LOG_LEVEL >= LOG_LEVEL_VERBOSE */

Antonio Nino Diaz's avatar
Antonio Nino Diaz committed
33
void xlat_mmap_print(const mmap_region_t *mmap)
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
{
	tf_printf("mmap:\n");
	const mmap_region_t *mm = mmap;

	while (mm->size != 0U) {
		tf_printf(" VA:0x%lx  PA:0x%llx  size:0x%zx  attr:0x%x "
			  "granularity:0x%zx\n", mm->base_va, mm->base_pa,
			  mm->size, mm->attr, mm->granularity);
		++mm;
	};
	tf_printf("\n");
}

/* Print the attributes of the specified block descriptor. */
static void xlat_desc_print(const xlat_ctx_t *ctx, uint64_t desc)
{
Antonio Nino Diaz's avatar
Antonio Nino Diaz committed
50
	uint64_t mem_type_index = ATTR_INDEX_GET(desc);
51
52
53
54
55
56
57
58
59
60
61
62
	int xlat_regime = ctx->xlat_regime;

	if (mem_type_index == ATTR_IWBWA_OWBWA_NTR_INDEX) {
		tf_printf("MEM");
	} else if (mem_type_index == ATTR_NON_CACHEABLE_INDEX) {
		tf_printf("NC");
	} else {
		assert(mem_type_index == ATTR_DEVICE_INDEX);
		tf_printf("DEV");
	}

	if (xlat_regime == EL3_REGIME) {
63
		/* For EL3 only check the AP[2] and XN bits. */
Antonio Nino Diaz's avatar
Antonio Nino Diaz committed
64
65
		tf_printf(((desc & LOWER_ATTRS(AP_RO)) != 0ULL) ? "-RO" : "-RW");
		tf_printf(((desc & UPPER_ATTRS(XN)) != 0ULL) ? "-XN" : "-EXEC");
66
	} else {
67
		assert(xlat_regime == EL1_EL0_REGIME);
68
		/*
69
70
71
72
73
74
75
		 * For EL0 and EL1:
		 * - In AArch64 PXN and UXN can be set independently but in
		 *   AArch32 there is no UXN (XN affects both privilege levels).
		 *   For consistency, we set them simultaneously in both cases.
		 * - RO and RW permissions must be the same in EL1 and EL0. If
		 *   EL0 can access that memory region, so can EL1, with the
		 *   same permissions.
76
		 */
77
78
79
80
81
82
#if ENABLE_ASSERTIONS
		uint64_t xn_mask = xlat_arch_regime_get_xn_desc(EL1_EL0_REGIME);
		uint64_t xn_perm = desc & xn_mask;

		assert((xn_perm == xn_mask) || (xn_perm == 0ULL));
#endif
Antonio Nino Diaz's avatar
Antonio Nino Diaz committed
83
		tf_printf(((desc & LOWER_ATTRS(AP_RO)) != 0ULL) ? "-RO" : "-RW");
84
		/* Only check one of PXN and UXN, the other one is the same. */
Antonio Nino Diaz's avatar
Antonio Nino Diaz committed
85
		tf_printf(((desc & UPPER_ATTRS(PXN)) != 0ULL) ? "-XN" : "-EXEC");
86
87
88
89
		/*
		 * Privileged regions can only be accessed from EL1, user
		 * regions can be accessed from EL1 and EL0.
		 */
Antonio Nino Diaz's avatar
Antonio Nino Diaz committed
90
		tf_printf(((desc & LOWER_ATTRS(AP_ACCESS_UNPRIVILEGED)) != 0ULL)
91
			  ? "-USER" : "-PRIV");
92
93
	}

Antonio Nino Diaz's avatar
Antonio Nino Diaz committed
94
	tf_printf(((LOWER_ATTRS(NS) & desc) != 0ULL) ? "-NS" : "-S");
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
}

static const char * const level_spacers[] = {
	"[LV0] ",
	"  [LV1] ",
	"    [LV2] ",
	"      [LV3] "
};

static const char *invalid_descriptors_ommited =
		"%s(%d invalid descriptors omitted)\n";

/*
 * Recursive function that reads the translation tables passed as an argument
 * and prints their status.
 */
Antonio Nino Diaz's avatar
Antonio Nino Diaz committed
111
112
113
static void xlat_tables_print_internal(xlat_ctx_t *ctx, uintptr_t table_base_va,
		const uint64_t *table_base, unsigned int table_entries,
		unsigned int level)
114
115
116
117
118
{
	assert(level <= XLAT_TABLE_LEVEL_MAX);

	uint64_t desc;
	uintptr_t table_idx_va = table_base_va;
Antonio Nino Diaz's avatar
Antonio Nino Diaz committed
119
	unsigned int table_idx = 0U;
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
	size_t level_size = XLAT_BLOCK_SIZE(level);

	/*
	 * Keep track of how many invalid descriptors are counted in a row.
	 * Whenever multiple invalid descriptors are found, only the first one
	 * is printed, and a line is added to inform about how many descriptors
	 * have been omitted.
	 */
	int invalid_row_count = 0;

	while (table_idx < table_entries) {

		desc = table_base[table_idx];

		if ((desc & DESC_MASK) == INVALID_DESC) {

			if (invalid_row_count == 0) {
Antonio Nino Diaz's avatar
Antonio Nino Diaz committed
137
				tf_printf("%sVA:0x%lx size:0x%zx\n",
138
					  level_spacers[level],
Antonio Nino Diaz's avatar
Antonio Nino Diaz committed
139
					  table_idx_va, level_size);
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
			}
			invalid_row_count++;

		} else {

			if (invalid_row_count > 1) {
				tf_printf(invalid_descriptors_ommited,
					  level_spacers[level],
					  invalid_row_count - 1);
			}
			invalid_row_count = 0;

			/*
			 * Check if this is a table or a block. Tables are only
			 * allowed in levels other than 3, but DESC_PAGE has the
			 * same value as DESC_TABLE, so we need to check.
			 */
			if (((desc & DESC_MASK) == TABLE_DESC) &&
					(level < XLAT_TABLE_LEVEL_MAX)) {
				/*
				 * Do not print any PA for a table descriptor,
				 * as it doesn't directly map physical memory
				 * but instead points to the next translation
				 * table in the translation table walk.
				 */
Antonio Nino Diaz's avatar
Antonio Nino Diaz committed
165
				tf_printf("%sVA:0x%lx size:0x%zx\n",
166
					  level_spacers[level],
Antonio Nino Diaz's avatar
Antonio Nino Diaz committed
167
					  table_idx_va, level_size);
168
169
170
171
172

				uintptr_t addr_inner = desc & TABLE_ADDR_MASK;

				xlat_tables_print_internal(ctx, table_idx_va,
					(uint64_t *)addr_inner,
Antonio Nino Diaz's avatar
Antonio Nino Diaz committed
173
					XLAT_TABLE_ENTRIES, level + 1U);
174
			} else {
Antonio Nino Diaz's avatar
Antonio Nino Diaz committed
175
				tf_printf("%sVA:0x%lx PA:0x%llx size:0x%zx ",
176
					  level_spacers[level],
Antonio Nino Diaz's avatar
Antonio Nino Diaz committed
177
178
					  table_idx_va,
					  (uint64_t)(desc & TABLE_ADDR_MASK),
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
					  level_size);
				xlat_desc_print(ctx, desc);
				tf_printf("\n");
			}
		}

		table_idx++;
		table_idx_va += level_size;
	}

	if (invalid_row_count > 1) {
		tf_printf(invalid_descriptors_ommited,
			  level_spacers[level], invalid_row_count - 1);
	}
}

void xlat_tables_print(xlat_ctx_t *ctx)
{
	const char *xlat_regime_str;
Antonio Nino Diaz's avatar
Antonio Nino Diaz committed
198
199
	int used_page_tables;

200
201
202
203
204
205
206
207
208
	if (ctx->xlat_regime == EL1_EL0_REGIME) {
		xlat_regime_str = "1&0";
	} else {
		assert(ctx->xlat_regime == EL3_REGIME);
		xlat_regime_str = "3";
	}
	VERBOSE("Translation tables state:\n");
	VERBOSE("  Xlat regime:     EL%s\n", xlat_regime_str);
	VERBOSE("  Max allowed PA:  0x%llx\n", ctx->pa_max_address);
Antonio Nino Diaz's avatar
Antonio Nino Diaz committed
209
	VERBOSE("  Max allowed VA:  0x%lx\n", ctx->va_max_address);
210
	VERBOSE("  Max mapped PA:   0x%llx\n", ctx->max_pa);
Antonio Nino Diaz's avatar
Antonio Nino Diaz committed
211
	VERBOSE("  Max mapped VA:   0x%lx\n", ctx->max_va);
212

Antonio Nino Diaz's avatar
Antonio Nino Diaz committed
213
214
	VERBOSE("  Initial lookup level: %u\n", ctx->base_level);
	VERBOSE("  Entries @initial lookup level: %u\n",
215
216
217
218
		ctx->base_table_entries);

#if PLAT_XLAT_TABLES_DYNAMIC
	used_page_tables = 0;
Antonio Nino Diaz's avatar
Antonio Nino Diaz committed
219
	for (int i = 0; i < ctx->tables_num; ++i) {
220
221
222
223
224
225
		if (ctx->tables_mapped_regions[i] != 0)
			++used_page_tables;
	}
#else
	used_page_tables = ctx->next_table;
#endif
Antonio Nino Diaz's avatar
Antonio Nino Diaz committed
226
	VERBOSE("  Used %d sub-tables out of %d (spare: %d)\n",
227
228
229
		used_page_tables, ctx->tables_num,
		ctx->tables_num - used_page_tables);

Antonio Nino Diaz's avatar
Antonio Nino Diaz committed
230
	xlat_tables_print_internal(ctx, 0U, ctx->base_table,
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
				   ctx->base_table_entries, ctx->base_level);
}

#endif /* LOG_LEVEL >= LOG_LEVEL_VERBOSE */

/*
 * Do a translation table walk to find the block or page descriptor that maps
 * virtual_addr.
 *
 * On success, return the address of the descriptor within the translation
 * table. Its lookup level is stored in '*out_level'.
 * On error, return NULL.
 *
 * xlat_table_base
 *   Base address for the initial lookup level.
 * xlat_table_base_entries
 *   Number of entries in the translation table for the initial lookup level.
 * virt_addr_space_size
 *   Size in bytes of the virtual address space.
 */
static uint64_t *find_xlat_table_entry(uintptr_t virtual_addr,
				       void *xlat_table_base,
Antonio Nino Diaz's avatar
Antonio Nino Diaz committed
253
				       unsigned int xlat_table_base_entries,
254
				       unsigned long long virt_addr_space_size,
Antonio Nino Diaz's avatar
Antonio Nino Diaz committed
255
				       unsigned int *out_level)
256
257
258
{
	unsigned int start_level;
	uint64_t *table;
Antonio Nino Diaz's avatar
Antonio Nino Diaz committed
259
	unsigned int entries;
260
261
262
263
264
265
266
267
268

	start_level = GET_XLAT_TABLE_LEVEL_BASE(virt_addr_space_size);

	table = xlat_table_base;
	entries = xlat_table_base_entries;

	for (unsigned int level = start_level;
	     level <= XLAT_TABLE_LEVEL_MAX;
	     ++level) {
Antonio Nino Diaz's avatar
Antonio Nino Diaz committed
269
		uint64_t idx, desc, desc_type;
270
271
272

		idx = XLAT_TABLE_IDX(virtual_addr, level);
		if (idx >= entries) {
273
274
			WARN("Missing xlat table entry at address 0x%lx\n",
			     virtual_addr);
275
276
277
278
279
280
281
282
283
284
285
286
287
			return NULL;
		}

		desc = table[idx];
		desc_type = desc & DESC_MASK;

		if (desc_type == INVALID_DESC) {
			VERBOSE("Invalid entry (memory not mapped)\n");
			return NULL;
		}

		if (level == XLAT_TABLE_LEVEL_MAX) {
			/*
288
			 * Only page descriptors allowed at the final lookup
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
			 * level.
			 */
			assert(desc_type == PAGE_DESC);
			*out_level = level;
			return &table[idx];
		}

		if (desc_type == BLOCK_DESC) {
			*out_level = level;
			return &table[idx];
		}

		assert(desc_type == TABLE_DESC);
		table = (uint64_t *)(uintptr_t)(desc & TABLE_ADDR_MASK);
		entries = XLAT_TABLE_ENTRIES;
	}

	/*
	 * This shouldn't be reached, the translation table walk should end at
	 * most at level XLAT_TABLE_LEVEL_MAX and return from inside the loop.
	 */
	assert(0);

	return NULL;
}


static int get_mem_attributes_internal(const xlat_ctx_t *ctx, uintptr_t base_va,
		uint32_t *attributes, uint64_t **table_entry,
Antonio Nino Diaz's avatar
Antonio Nino Diaz committed
318
		unsigned long long *addr_pa, unsigned int *table_level)
319
320
321
{
	uint64_t *entry;
	uint64_t desc;
Antonio Nino Diaz's avatar
Antonio Nino Diaz committed
322
	unsigned int level;
323
324
325
326
327
328
	unsigned long long virt_addr_space_size;

	/*
	 * Sanity-check arguments.
	 */
	assert(ctx != NULL);
Antonio Nino Diaz's avatar
Antonio Nino Diaz committed
329
330
331
	assert(ctx->initialized != 0);
	assert((ctx->xlat_regime == EL1_EL0_REGIME) ||
	       (ctx->xlat_regime == EL3_REGIME));
332

Antonio Nino Diaz's avatar
Antonio Nino Diaz committed
333
334
	virt_addr_space_size = (unsigned long long)ctx->va_max_address + 1ULL;
	assert(virt_addr_space_size > 0U);
335
336
337
338
339
340
341

	entry = find_xlat_table_entry(base_va,
				ctx->base_table,
				ctx->base_table_entries,
				virt_addr_space_size,
				&level);
	if (entry == NULL) {
Antonio Nino Diaz's avatar
Antonio Nino Diaz committed
342
		WARN("Address 0x%lx is not mapped.\n", base_va);
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
		return -EINVAL;
	}

	if (addr_pa != NULL) {
		*addr_pa = *entry & TABLE_ADDR_MASK;
	}

	if (table_entry != NULL) {
		*table_entry = entry;
	}

	if (table_level != NULL) {
		*table_level = level;
	}

	desc = *entry;

#if LOG_LEVEL >= LOG_LEVEL_VERBOSE
	VERBOSE("Attributes: ");
	xlat_desc_print(ctx, desc);
	tf_printf("\n");
#endif /* LOG_LEVEL >= LOG_LEVEL_VERBOSE */

	assert(attributes != NULL);
Antonio Nino Diaz's avatar
Antonio Nino Diaz committed
367
	*attributes = 0U;
368

Antonio Nino Diaz's avatar
Antonio Nino Diaz committed
369
	uint64_t attr_index = (desc >> ATTR_INDEX_SHIFT) & ATTR_INDEX_MASK;
370
371
372
373
374
375
376
377
378
379

	if (attr_index == ATTR_IWBWA_OWBWA_NTR_INDEX) {
		*attributes |= MT_MEMORY;
	} else if (attr_index == ATTR_NON_CACHEABLE_INDEX) {
		*attributes |= MT_NON_CACHEABLE;
	} else {
		assert(attr_index == ATTR_DEVICE_INDEX);
		*attributes |= MT_DEVICE;
	}

Antonio Nino Diaz's avatar
Antonio Nino Diaz committed
380
	uint64_t ap2_bit = (desc >> AP2_SHIFT) & 1U;
381
382
383
384
385

	if (ap2_bit == AP2_RW)
		*attributes |= MT_RW;

	if (ctx->xlat_regime == EL1_EL0_REGIME) {
Antonio Nino Diaz's avatar
Antonio Nino Diaz committed
386
387
		uint64_t ap1_bit = (desc >> AP1_SHIFT) & 1U;

388
389
390
391
		if (ap1_bit == AP1_ACCESS_UNPRIVILEGED)
			*attributes |= MT_USER;
	}

Antonio Nino Diaz's avatar
Antonio Nino Diaz committed
392
	uint64_t ns_bit = (desc >> NS_SHIFT) & 1U;
393

Antonio Nino Diaz's avatar
Antonio Nino Diaz committed
394
	if (ns_bit == 1U)
395
396
397
398
399
400
401
		*attributes |= MT_NS;

	uint64_t xn_mask = xlat_arch_regime_get_xn_desc(ctx->xlat_regime);

	if ((desc & xn_mask) == xn_mask) {
		*attributes |= MT_EXECUTE_NEVER;
	} else {
Antonio Nino Diaz's avatar
Antonio Nino Diaz committed
402
		assert((desc & xn_mask) == 0U);
403
404
405
406
407
408
409
410
411
412
413
414
415
416
	}

	return 0;
}


int get_mem_attributes(const xlat_ctx_t *ctx, uintptr_t base_va,
		       uint32_t *attributes)
{
	return get_mem_attributes_internal(ctx, base_va, attributes,
					   NULL, NULL, NULL);
}


Antonio Nino Diaz's avatar
Antonio Nino Diaz committed
417
int change_mem_attributes(const xlat_ctx_t *ctx,
418
419
420
421
422
423
424
			uintptr_t base_va,
			size_t size,
			uint32_t attr)
{
	/* Note: This implementation isn't optimized. */

	assert(ctx != NULL);
Antonio Nino Diaz's avatar
Antonio Nino Diaz committed
425
	assert(ctx->initialized != 0);
426
427

	unsigned long long virt_addr_space_size =
Antonio Nino Diaz's avatar
Antonio Nino Diaz committed
428
429
		(unsigned long long)ctx->va_max_address + 1U;
	assert(virt_addr_space_size > 0U);
430
431

	if (!IS_PAGE_ALIGNED(base_va)) {
Antonio Nino Diaz's avatar
Antonio Nino Diaz committed
432
433
		WARN("%s: Address 0x%lx is not aligned on a page boundary.\n",
		     __func__, base_va);
434
435
436
		return -EINVAL;
	}

Antonio Nino Diaz's avatar
Antonio Nino Diaz committed
437
	if (size == 0U) {
438
439
440
441
		WARN("%s: Size is 0.\n", __func__);
		return -EINVAL;
	}

Antonio Nino Diaz's avatar
Antonio Nino Diaz committed
442
	if ((size % PAGE_SIZE) != 0U) {
443
444
445
446
447
		WARN("%s: Size 0x%zx is not a multiple of a page size.\n",
		     __func__, size);
		return -EINVAL;
	}

Antonio Nino Diaz's avatar
Antonio Nino Diaz committed
448
	if (((attr & MT_EXECUTE_NEVER) == 0U) && ((attr & MT_RW) != 0U)) {
449
		WARN("%s: Mapping memory as read-write and executable not allowed.\n",
450
451
452
453
		     __func__);
		return -EINVAL;
	}

Antonio Nino Diaz's avatar
Antonio Nino Diaz committed
454
	size_t pages_count = size / PAGE_SIZE;
455

Antonio Nino Diaz's avatar
Antonio Nino Diaz committed
456
457
	VERBOSE("Changing memory attributes of %zu pages starting from address 0x%lx...\n",
		pages_count, base_va);
458
459
460
461
462
463

	uintptr_t base_va_original = base_va;

	/*
	 * Sanity checks.
	 */
Antonio Nino Diaz's avatar
Antonio Nino Diaz committed
464
465
466
467
	for (size_t i = 0U; i < pages_count; ++i) {
		const uint64_t *entry;
		uint64_t desc, attr_index;
		unsigned int level;
468
469
470
471
472
473
474

		entry = find_xlat_table_entry(base_va,
					      ctx->base_table,
					      ctx->base_table_entries,
					      virt_addr_space_size,
					      &level);
		if (entry == NULL) {
Antonio Nino Diaz's avatar
Antonio Nino Diaz committed
475
			WARN("Address 0x%lx is not mapped.\n", base_va);
476
477
478
479
480
481
482
483
484
485
486
			return -EINVAL;
		}

		desc = *entry;

		/*
		 * Check that all the required pages are mapped at page
		 * granularity.
		 */
		if (((desc & DESC_MASK) != PAGE_DESC) ||
			(level != XLAT_TABLE_LEVEL_MAX)) {
Antonio Nino Diaz's avatar
Antonio Nino Diaz committed
487
488
			WARN("Address 0x%lx is not mapped at the right granularity.\n",
			     base_va);
489
490
491
492
493
494
495
496
			WARN("Granularity is 0x%llx, should be 0x%x.\n",
			     (unsigned long long)XLAT_BLOCK_SIZE(level), PAGE_SIZE);
			return -EINVAL;
		}

		/*
		 * If the region type is device, it shouldn't be executable.
		 */
Antonio Nino Diaz's avatar
Antonio Nino Diaz committed
497
		attr_index = (desc >> ATTR_INDEX_SHIFT) & ATTR_INDEX_MASK;
498
		if (attr_index == ATTR_DEVICE_INDEX) {
Antonio Nino Diaz's avatar
Antonio Nino Diaz committed
499
500
501
			if ((attr & MT_EXECUTE_NEVER) == 0U) {
				WARN("Setting device memory as executable at address 0x%lx.",
				     base_va);
502
503
504
505
506
507
508
509
510
511
				return -EINVAL;
			}
		}

		base_va += PAGE_SIZE;
	}

	/* Restore original value. */
	base_va = base_va_original;

Antonio Nino Diaz's avatar
Antonio Nino Diaz committed
512
	for (unsigned int i = 0U; i < pages_count; ++i) {
513

Antonio Nino Diaz's avatar
Antonio Nino Diaz committed
514
515
516
517
		uint32_t old_attr = 0U, new_attr;
		uint64_t *entry = NULL;
		unsigned int level = 0U;
		unsigned long long addr_pa = 0ULL;
518

Antonio Nino Diaz's avatar
Antonio Nino Diaz committed
519
		(void) get_mem_attributes_internal(ctx, base_va, &old_attr,
520
521
522
523
524
525
526
527
528
					    &entry, &addr_pa, &level);

		/*
		 * From attr, only MT_RO/MT_RW, MT_EXECUTE/MT_EXECUTE_NEVER and
		 * MT_USER/MT_PRIVILEGED are taken into account. Any other
		 * information is ignored.
		 */

		/* Clean the old attributes so that they can be rebuilt. */
529
		new_attr = old_attr & ~(MT_RW | MT_EXECUTE_NEVER | MT_USER);
530
531
532
533
534

		/*
		 * Update attributes, but filter out the ones this function
		 * isn't allowed to change.
		 */
535
		new_attr |= attr & (MT_RW | MT_EXECUTE_NEVER | MT_USER);
536
537
538
539
540
541
542
543
544

		/*
		 * The break-before-make sequence requires writing an invalid
		 * descriptor and making sure that the system sees the change
		 * before writing the new descriptor.
		 */
		*entry = INVALID_DESC;

		/* Invalidate any cached copy of this mapping in the TLBs. */
545
		xlat_arch_tlbi_va(base_va, ctx->xlat_regime);
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560

		/* Ensure completion of the invalidation. */
		xlat_arch_tlbi_va_sync();

		/* Write new descriptor */
		*entry = xlat_desc(ctx, new_attr, addr_pa, level);

		base_va += PAGE_SIZE;
	}

	/* Ensure that the last descriptor writen is seen by the system. */
	dsbish();

	return 0;
}