Read-only xlat tables for BL31 memory

This patch introduces a build flag which allows the xlat tables
to be mapped in a read-only region within BL31 memory. It makes it
much harder for someone who has acquired the ability to write to
arbitrary secure memory addresses to gain control of the
translation tables.

The memory attributes of the descriptors describing the tables
themselves are changed to read-only secure data. This change
happens at the end of BL31 runtime setup. Until this point, the
tables have read-write permissions. This gives a window of
opportunity for changes to be made to the tables with the MMU on
(e.g. reclaiming init code). No changes can be made to the tables
with the MMU turned on from this point onwards. This change is also
enabled for sp_min and tspd.

To make all this possible, the base table was moved to .rodata. The
penalty we pay is that now .rodata must be aligned to the size of
the base table (512B alignment). Still, this is better than putting
the base table with the higher level tables in the xlat_table
section, as that would cost us a full 4KB page.

Changing the tables from read-write to read-only cannot be done with
the MMU on, as the break-before-make sequence would invalidate the
descriptor which resolves the level 3 page table where that very
descriptor is located. This would make the translation required for
writing the changes impossible, generating an MMU fault.

The caches are also flushed.

Signed-off-by: Petre-Ionut Tudor <petre-ionut.tudor@arm.com>
Change-Id: Ibe5de307e6dc94c67d6186139ac3973516430466
diff --git a/include/lib/xlat_tables/xlat_tables_v2_helpers.h b/include/lib/xlat_tables/xlat_tables_v2_helpers.h
index b17b71a..c88fa4d 100644
--- a/include/lib/xlat_tables/xlat_tables_v2_helpers.h
+++ b/include/lib/xlat_tables/xlat_tables_v2_helpers.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2017-2018, ARM Limited and Contributors. All rights reserved.
+ * Copyright (c) 2017-2020, ARM Limited and Contributors. All rights reserved.
  *
  * SPDX-License-Identifier: BSD-3-Clause
  */
@@ -70,6 +70,9 @@
 	 */
 	uint64_t (*tables)[XLAT_TABLE_ENTRIES];
 	int tables_num;
+#if PLAT_RO_XLAT_TABLES
+	bool readonly_tables;
+#endif
 	/*
 	 * Keep track of how many regions are mapped in each table. The base
 	 * table can't be unmapped so it isn't needed to keep track of it.
@@ -122,6 +125,14 @@
 	/* do nothing */
 #endif /* PLAT_XLAT_TABLES_DYNAMIC */
 
+#if PLAT_RO_XLAT_TABLES
+#define XLAT_CTX_INIT_TABLE_ATTR()					\
+	.readonly_tables = false,
+#else
+#define XLAT_CTX_INIT_TABLE_ATTR()
+	/* do nothing */
+#endif
+
 #define REGISTER_XLAT_CONTEXT_FULL_SPEC(_ctx_name, _mmap_count,		\
 			_xlat_tables_count, _virt_addr_space_size,	\
 			_phy_addr_space_size, _xlat_regime, _section_name)\
@@ -142,22 +153,63 @@
 	XLAT_ALLOC_DYNMAP_STRUCT(_ctx_name, _xlat_tables_count)		\
 									\
 	static xlat_ctx_t _ctx_name##_xlat_ctx = {			\
-		.va_max_address = (_virt_addr_space_size) - 1UL,	\
 		.pa_max_address = (_phy_addr_space_size) - 1ULL,	\
+		.va_max_address = (_virt_addr_space_size) - 1UL,	\
 		.mmap = _ctx_name##_mmap,				\
 		.mmap_num = (_mmap_count),				\
-		.base_level = GET_XLAT_TABLE_LEVEL_BASE(_virt_addr_space_size),\
+		.tables = _ctx_name##_xlat_tables,			\
+		.tables_num = _xlat_tables_count,			\
+		 XLAT_CTX_INIT_TABLE_ATTR()				\
+		 XLAT_REGISTER_DYNMAP_STRUCT(_ctx_name)			\
+		.next_table = 0,					\
 		.base_table = _ctx_name##_base_xlat_table,		\
 		.base_table_entries =					\
 			GET_NUM_BASE_LEVEL_ENTRIES(_virt_addr_space_size),\
+		.max_pa = 0U,						\
+		.max_va = 0U,						\
+		.base_level = GET_XLAT_TABLE_LEVEL_BASE(_virt_addr_space_size),\
+		.initialized = false,					\
+		.xlat_regime = (_xlat_regime)				\
+	}
+
+#define REGISTER_XLAT_CONTEXT_RO_BASE_TABLE(_ctx_name, _mmap_count,	\
+			_xlat_tables_count, _virt_addr_space_size,	\
+			_phy_addr_space_size, _xlat_regime, _section_name)\
+	CASSERT(CHECK_PHY_ADDR_SPACE_SIZE(_phy_addr_space_size),	\
+		assert_invalid_physical_addr_space_sizefor_##_ctx_name);\
+									\
+	static mmap_region_t _ctx_name##_mmap[_mmap_count + 1];		\
+									\
+	static uint64_t _ctx_name##_xlat_tables[_xlat_tables_count]	\
+		[XLAT_TABLE_ENTRIES]					\
+		__aligned(XLAT_TABLE_SIZE) __section(_section_name);	\
+									\
+	static uint64_t _ctx_name##_base_xlat_table			\
+		[GET_NUM_BASE_LEVEL_ENTRIES(_virt_addr_space_size)]	\
+		__aligned(GET_NUM_BASE_LEVEL_ENTRIES(_virt_addr_space_size)\
+			* sizeof(uint64_t))				\
+		__section(".rodata");					\
+									\
+	XLAT_ALLOC_DYNMAP_STRUCT(_ctx_name, _xlat_tables_count)		\
+									\
+	static xlat_ctx_t _ctx_name##_xlat_ctx = {			\
+		.pa_max_address = (_phy_addr_space_size) - 1ULL,	\
+		.va_max_address = (_virt_addr_space_size) - 1UL,	\
+		.mmap = _ctx_name##_mmap,				\
+		.mmap_num = (_mmap_count),				\
 		.tables = _ctx_name##_xlat_tables,			\
 		.tables_num = _xlat_tables_count,			\
+		 XLAT_CTX_INIT_TABLE_ATTR()				\
 		 XLAT_REGISTER_DYNMAP_STRUCT(_ctx_name)			\
-		.xlat_regime = (_xlat_regime),				\
+		.next_table = 0,					\
+		.base_table = _ctx_name##_base_xlat_table,		\
+		.base_table_entries =					\
+			GET_NUM_BASE_LEVEL_ENTRIES(_virt_addr_space_size),\
 		.max_pa = 0U,						\
 		.max_va = 0U,						\
-		.next_table = 0,					\
+		.base_level = GET_XLAT_TABLE_LEVEL_BASE(_virt_addr_space_size),\
 		.initialized = false,					\
+		.xlat_regime = (_xlat_regime)				\
 	}
 
 #endif /*__ASSEMBLER__*/