diff --git a/Makefile b/Makefile
index 547b5843f2ab8d66f7ea4acc3d47bae8d905371a..5e865417f3f800d2fb3b01a6a3a1ec8bb4901b6b 100644
--- a/Makefile
+++ b/Makefile
@@ -621,6 +621,12 @@ ifeq ($(MEASURED_BOOT),1)
     endif
 endif
 
+ifeq (${ARM_XLAT_TABLES_LIB_V1}, 1)
+    ifeq (${ALLOW_RO_XLAT_TABLES}, 1)
+        $(error "ALLOW_RO_XLAT_TABLES requires translation tables library v2")
+    endif
+endif
+
 ################################################################################
 # Process platform overrideable behaviour
 ################################################################################
@@ -747,6 +753,7 @@ endif
 # Build options checks
 ################################################################################
 
+$(eval $(call assert_boolean,ALLOW_RO_XLAT_TABLES))
 $(eval $(call assert_boolean,COLD_BOOT_SINGLE_CPU))
 $(eval $(call assert_boolean,CREATE_KEYS))
 $(eval $(call assert_boolean,CTX_INCLUDE_AARCH32_REGS))
@@ -814,6 +821,7 @@ endif
 # platform to overwrite the default options
 ################################################################################
 
+$(eval $(call add_define,ALLOW_RO_XLAT_TABLES))
 $(eval $(call add_define,ARM_ARCH_MAJOR))
 $(eval $(call add_define,ARM_ARCH_MINOR))
 $(eval $(call add_define,COLD_BOOT_SINGLE_CPU))
diff --git a/include/lib/xlat_tables/xlat_tables_v2.h b/include/lib/xlat_tables/xlat_tables_v2.h
index 0e099987e40eeaaa3028e120f4195fbfbfa33077..a80fab073d38060b3db3281670d5df7b75376cd3 100644
--- a/include/lib/xlat_tables/xlat_tables_v2.h
+++ b/include/lib/xlat_tables/xlat_tables_v2.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
  */
@@ -345,6 +345,16 @@ int xlat_change_mem_attributes_ctx(const xlat_ctx_t *ctx, uintptr_t base_va,
 				   size_t size, uint32_t attr);
 int xlat_change_mem_attributes(uintptr_t base_va, size_t size, uint32_t attr);
 
+#if PLAT_RO_XLAT_TABLES
+/*
+ * Change the memory attributes of the memory region encompassing the higher
+ * level translation tables to secure read-only data.
+ *
+ * Return 0 on success, a negative error code on error.
+ */
+int xlat_make_tables_readonly(void);
+#endif
+
 /*
  * Query the memory attributes of a memory page in a set of translation tables.
  *
diff --git a/include/lib/xlat_tables/xlat_tables_v2_helpers.h b/include/lib/xlat_tables/xlat_tables_v2_helpers.h
index b17b71a872004aae44aedd27d0a91daa0ec551e6..c88fa4dd5f876debe3839e78517146343ab74f87 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 @@ struct xlat_ctx {
 	 */
 	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 @@ struct xlat_ctx {
 	/* 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 @@ struct xlat_ctx {
 	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__*/
diff --git a/include/plat/arm/common/plat_arm.h b/include/plat/arm/common/plat_arm.h
index 025a64fa28e801d6554075783c9a10171b9d0fd3..862e73af1e9a8f27fc38bf18db04ff8c8a7aa4ec 100644
--- a/include/plat/arm/common/plat_arm.h
+++ b/include/plat/arm/common/plat_arm.h
@@ -236,6 +236,11 @@ int arm_get_mbedtls_heap(void **heap_addr, size_t *heap_size);
  */
 void arm_free_init_memory(void);
 
+/*
+ * Make the higher level translation tables read-only
+ */
+void arm_xlat_make_tables_readonly(void);
+
 /*
  * Mandatory functions required in ARM standard platforms
  */
diff --git a/lib/aarch64/cache_helpers.S b/lib/aarch64/cache_helpers.S
index 9ef8ca79bfe49f067044d4ee513743e1dda7afdf..de9c8e4f0154f4fcfaac96d7195b948c64542cd4 100644
--- a/lib/aarch64/cache_helpers.S
+++ b/lib/aarch64/cache_helpers.S
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2013-2019, ARM Limited and Contributors. All rights reserved.
+ * Copyright (c) 2013-2020, ARM Limited and Contributors. All rights reserved.
  *
  * SPDX-License-Identifier: BSD-3-Clause
  */
@@ -30,7 +30,7 @@ loop_\op:
 	dc	\op, x0
 	add	x0, x0, x2
 	cmp	x0, x1
-	b.lo    loop_\op
+	b.lo	loop_\op
 	dsb	sy
 exit_loop_\op:
 	ret
@@ -140,7 +140,7 @@ loop3_\_op:
 level_done:
 	add	x10, x10, #2		// increment cache number
 	cmp	x3, x10
-	b.hi    loop1
+	b.hi	loop1
 	msr	csselr_el1, xzr		// select cache level 0 in csselr
 	dsb	sy			// barrier to complete final cache operation
 	isb
diff --git a/lib/xlat_tables_v2/ro_xlat_tables.mk b/lib/xlat_tables_v2/ro_xlat_tables.mk
new file mode 100644
index 0000000000000000000000000000000000000000..7991e1afde33fbd8f7d2fe94a9b74b135c0c0a74
--- /dev/null
+++ b/lib/xlat_tables_v2/ro_xlat_tables.mk
@@ -0,0 +1,37 @@
+#
+# Copyright (c) 2020, ARM Limited. All rights reserved.
+#
+# SPDX-License-Identifier: BSD-3-Clause
+#
+
+ifeq (${USE_DEBUGFS}, 1)
+    $(error "Debugfs requires functionality from the dynamic translation \
+             library and is incompatible with ALLOW_RO_XLAT_TABLES.")
+endif
+
+ifeq (${ARCH},aarch32)
+    ifeq (${RESET_TO_SP_MIN},1)
+       $(error "RESET_TO_SP_MIN requires functionality from the dynamic \
+                translation library and is incompatible with \
+                ALLOW_RO_XLAT_TABLES.")
+    endif
+else # if AArch64
+    ifeq (${PLAT},tegra)
+        $(error "Tegra requires functionality from the dynamic translation \
+                 library and is incompatible with ALLOW_RO_XLAT_TABLES.")
+    endif
+    ifeq (${RESET_TO_BL31},1)
+        $(error "RESET_TO_BL31 requires functionality from the dynamic \
+                 translation library and is incompatible with \
+                 ALLOW_RO_XLAT_TABLES.")
+    endif
+    ifeq (${SPD},trusty)
+        $(error "Trusty requires functionality from the dynamic translation \
+                 library and is incompatible with ALLOW_RO_XLAT_TABLES.")
+    endif
+    ifeq (${SPM_MM},1)
+        $(error "SPM_MM requires functionality to change memory region \
+                 attributes, which is not possible once the translation tables \
+                 have been made read-only.")
+    endif
+endif
diff --git a/lib/xlat_tables_v2/xlat_tables.mk b/lib/xlat_tables_v2/xlat_tables.mk
index c946315bf8294d6454d9b0343dd57dc66fe58055..bcc3e68d89c0a864e101e44821975c2f76d7a573 100644
--- a/lib/xlat_tables_v2/xlat_tables.mk
+++ b/lib/xlat_tables_v2/xlat_tables.mk
@@ -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
 #
@@ -13,3 +13,7 @@ XLAT_TABLES_LIB_SRCS	:=	$(addprefix lib/xlat_tables_v2/,	\
 
 XLAT_TABLES_LIB_V2	:=	1
 $(eval $(call add_define,XLAT_TABLES_LIB_V2))
+
+ifeq (${ALLOW_RO_XLAT_TABLES}, 1)
+    include lib/xlat_tables_v2/ro_xlat_tables.mk
+endif
diff --git a/lib/xlat_tables_v2/xlat_tables_context.c b/lib/xlat_tables_v2/xlat_tables_context.c
index f4b64b33f090ea2e09d49f72842a95dfa8109775..adca57875b4fea46a1db001c69a32fd4e08a1f54 100644
--- a/lib/xlat_tables_v2/xlat_tables_context.c
+++ b/lib/xlat_tables_v2/xlat_tables_context.c
@@ -1,9 +1,10 @@
 /*
- * 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
  */
 
+#include <arch_helpers.h>
 #include <assert.h>
 
 #include <platform_def.h>
@@ -24,8 +25,14 @@ uint64_t mmu_cfg_params[MMU_CFG_PARAM_MAX];
  * Allocate and initialise the default translation context for the BL image
  * currently executing.
  */
+#if PLAT_RO_XLAT_TABLES
+REGISTER_XLAT_CONTEXT_RO_BASE_TABLE(tf, MAX_MMAP_REGIONS, MAX_XLAT_TABLES,
+		PLAT_VIRT_ADDR_SPACE_SIZE, PLAT_PHY_ADDR_SPACE_SIZE,
+		EL_REGIME_INVALID, "xlat_table");
+#else
 REGISTER_XLAT_CONTEXT(tf, MAX_MMAP_REGIONS, MAX_XLAT_TABLES,
 		PLAT_VIRT_ADDR_SPACE_SIZE, PLAT_PHY_ADDR_SPACE_SIZE);
+#endif
 
 void mmap_add_region(unsigned long long base_pa, uintptr_t base_va, size_t size,
 		     unsigned int attr)
@@ -119,6 +126,75 @@ int xlat_change_mem_attributes(uintptr_t base_va, size_t size, uint32_t attr)
 	return xlat_change_mem_attributes_ctx(&tf_xlat_ctx, base_va, size, attr);
 }
 
+#if PLAT_RO_XLAT_TABLES
+/* Change the memory attributes of the descriptors which resolve the address
+ * range that belongs to the translation tables themselves, which are by default
+ * mapped as part of read-write data in the BL image's memory.
+ *
+ * Since the translation tables map themselves via these level 3 (page)
+ * descriptors, any change applied to them with the MMU on would introduce a
+ * chicken and egg problem because of the break-before-make sequence.
+ * Eventually, it would reach the descriptor that resolves the very table it
+ * belongs to and the invalidation (break step) would cause the subsequent write
+ * (make step) to it to generate an MMU fault. Therefore, the MMU is disabled
+ * before making the change.
+ *
+ * No assumption is made about what data this function needs, therefore all the
+ * caches are flushed in order to ensure coherency. A future optimization would
+ * be to only flush the required data to main memory.
+ */
+int xlat_make_tables_readonly(void)
+{
+	assert(tf_xlat_ctx.initialized == true);
+#ifdef __aarch64__
+	if (tf_xlat_ctx.xlat_regime == EL1_EL0_REGIME) {
+		disable_mmu_el1();
+	} else if (tf_xlat_ctx.xlat_regime == EL3_REGIME) {
+		disable_mmu_el3();
+	} else {
+		assert(tf_xlat_ctx.xlat_regime == EL2_REGIME);
+		return -1;
+	}
+
+	/* Flush all caches. */
+	dcsw_op_all(DCCISW);
+#else /* !__aarch64__ */
+	assert(tf_xlat_ctx.xlat_regime == EL1_EL0_REGIME);
+	/* On AArch32, we flush the caches before disabling the MMU. The reason
+	 * for this is that the dcsw_op_all AArch32 function pushes some
+	 * registers onto the stack under the assumption that it is writing to
+	 * cache, which is not true with the MMU off. This would result in the
+	 * stack becoming corrupted and a wrong/junk value for the LR being
+	 * restored at the end of the routine.
+	 */
+	dcsw_op_all(DC_OP_CISW);
+	disable_mmu_secure();
+#endif
+
+	int rc = xlat_change_mem_attributes_ctx(&tf_xlat_ctx,
+				(uintptr_t)tf_xlat_ctx.tables,
+				tf_xlat_ctx.tables_num * XLAT_TABLE_SIZE,
+				MT_RO_DATA | MT_SECURE);
+
+#ifdef __aarch64__
+	if (tf_xlat_ctx.xlat_regime == EL1_EL0_REGIME) {
+		enable_mmu_el1(0U);
+	} else {
+		assert(tf_xlat_ctx.xlat_regime == EL3_REGIME);
+		enable_mmu_el3(0U);
+	}
+#else /* !__aarch64__ */
+	enable_mmu_svc_mon(0U);
+#endif
+
+	if (rc == 0) {
+		tf_xlat_ctx.readonly_tables = true;
+	}
+
+	return rc;
+}
+#endif /* PLAT_RO_XLAT_TABLES */
+
 /*
  * If dynamic allocation of new regions is disabled then by the time we call the
  * function enabling the MMU, we'll have registered all the memory regions to
diff --git a/make_helpers/defaults.mk b/make_helpers/defaults.mk
index e8e990d451c834b79ddbf7bbdc6e876d4d6631ed..60958a1d1a73ceb238a9d6687a207489ada24e35 100644
--- a/make_helpers/defaults.mk
+++ b/make_helpers/defaults.mk
@@ -207,6 +207,13 @@ USE_FCONF_BASED_IO		:= 0
 # Build option to choose whether Trusted Firmware uses library at ROM
 USE_ROMLIB			:= 0
 
+# Build option to choose whether the xlat tables of BL images can be read-only.
+# Note that this only serves as a higher level option to PLAT_RO_XLAT_TABLES,
+# which is the per BL-image option that actually enables the read-only tables
+# API. The reason for having this additional option is to have a common high
+# level makefile where we can check for incompatible features/build options.
+ALLOW_RO_XLAT_TABLES		:= 0
+
 # Chain of trust.
 COT				:= tbbr
 
diff --git a/plat/arm/board/fvp/platform.mk b/plat/arm/board/fvp/platform.mk
index 4176968f895cd855723c30d8c4c1a340a8d4ee1c..05c11ce52219829ccda316ae71c357090e89e7bc 100644
--- a/plat/arm/board/fvp/platform.mk
+++ b/plat/arm/board/fvp/platform.mk
@@ -292,7 +292,7 @@ ifeq (${ARCH},aarch32)
     ifeq (${RESET_TO_SP_MIN},1)
         BL32_CFLAGS	+=	-DPLAT_XLAT_TABLES_DYNAMIC=1
     endif
-else # if AArch64
+else # AArch64
     ifeq (${RESET_TO_BL31},1)
         BL31_CFLAGS	+=	-DPLAT_XLAT_TABLES_DYNAMIC=1
     endif
@@ -301,6 +301,17 @@ else # if AArch64
     endif
 endif
 
+ifeq (${ALLOW_RO_XLAT_TABLES}, 1)
+    ifeq (${ARCH},aarch32)
+        BL32_CFLAGS	+=	-DPLAT_RO_XLAT_TABLES=1
+    else # AArch64
+        BL31_CFLAGS	+=	-DPLAT_RO_XLAT_TABLES=1
+        ifeq (${SPD},tspd)
+            BL32_CFLAGS	+=	-DPLAT_RO_XLAT_TABLES=1
+        endif
+    endif
+endif
+
 ifeq (${USE_DEBUGFS},1)
     BL31_CFLAGS	+=	-DPLAT_XLAT_TABLES_DYNAMIC=1
 endif
diff --git a/plat/arm/board/juno/platform.mk b/plat/arm/board/juno/platform.mk
index 27650d2668aed2b50b49332be01ff00539c243f0..f07c1b1638fb00e48dd14fc9ab2efc51b253ffc6 100644
--- a/plat/arm/board/juno/platform.mk
+++ b/plat/arm/board/juno/platform.mk
@@ -155,6 +155,14 @@ else
     endif
 endif
 
+ifeq (${ALLOW_RO_XLAT_TABLES}, 1)
+    ifeq (${JUNO_AARCH32_EL3_RUNTIME}, 1)
+        BL32_CFLAGS	+=	-DPLAT_RO_XLAT_TABLES=1
+    else
+        BL31_CFLAGS	+=	-DPLAT_RO_XLAT_TABLES=1
+    endif
+endif
+
 # Add the FDT_SOURCES and options for Dynamic Config
 FDT_SOURCES		+=	plat/arm/board/juno/fdts/${PLAT}_fw_config.dts
 TB_FW_CONFIG		:=	${BUILD_PLAT}/fdts/${PLAT}_fw_config.dtb
diff --git a/plat/arm/common/arm_bl31_setup.c b/plat/arm/common/arm_bl31_setup.c
index c135d7f2d61f4eaf8bdc74afb99eea087a554a5d..85535c11a9aef33a88f408e9a3ece326e858688e 100644
--- a/plat/arm/common/arm_bl31_setup.c
+++ b/plat/arm/common/arm_bl31_setup.c
@@ -256,9 +256,14 @@ void arm_bl31_plat_runtime_setup(void)
 
 	/* Initialize the runtime console */
 	arm_console_runtime_init();
+
 #if RECLAIM_INIT_CODE
 	arm_free_init_memory();
 #endif
+
+#if PLAT_RO_XLAT_TABLES
+	arm_xlat_make_tables_readonly();
+#endif
 }
 
 #if RECLAIM_INIT_CODE
diff --git a/plat/arm/common/arm_common.c b/plat/arm/common/arm_common.c
index d1e9620de980d172aa28505cff63209d8b51ccd0..d1eee08d127400dd9aa7530a46367c1e3b2b3c88 100644
--- a/plat/arm/common/arm_common.c
+++ b/plat/arm/common/arm_common.c
@@ -25,6 +25,26 @@
  * conflicts with the definition in plat/common. */
 #pragma weak plat_get_syscnt_freq2
 
+/*******************************************************************************
+ * Changes the memory attributes for the region of mapped memory where the BL
+ * image's translation tables are located such that the tables will have
+ * read-only permissions.
+ ******************************************************************************/
+#if PLAT_RO_XLAT_TABLES
+void arm_xlat_make_tables_readonly(void)
+{
+	int rc = xlat_make_tables_readonly();
+
+	if (rc != 0) {
+		ERROR("Failed to make translation tables read-only at EL%u.\n",
+		      get_current_el());
+		panic();
+	}
+
+	INFO("Translation tables are now read-only at EL%u.\n",
+	     get_current_el());
+}
+#endif
 
 void arm_setup_romlib(void)
 {
diff --git a/plat/arm/common/sp_min/arm_sp_min_setup.c b/plat/arm/common/sp_min/arm_sp_min_setup.c
index 0cc746b104f79c01451a9798ec2bbbb3dbd4c7bf..cbbdfa21b917b757aad533c4b757de994b7f9864 100644
--- a/plat/arm/common/sp_min/arm_sp_min_setup.c
+++ b/plat/arm/common/sp_min/arm_sp_min_setup.c
@@ -167,6 +167,10 @@ void arm_sp_min_plat_runtime_setup(void)
 {
 	/* Initialize the runtime console */
 	arm_console_runtime_init();
+
+#if PLAT_RO_XLAT_TABLES
+	arm_xlat_make_tables_readonly();
+#endif
 }
 
 /*******************************************************************************
diff --git a/plat/arm/common/tsp/arm_tsp_setup.c b/plat/arm/common/tsp/arm_tsp_setup.c
index aefdf89c7082365f7a3b11bbfac4660ebac593de..5dd964de2edf4a5aca7a53a9cc241246dc89a3a9 100644
--- a/plat/arm/common/tsp/arm_tsp_setup.c
+++ b/plat/arm/common/tsp/arm_tsp_setup.c
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2015-2019, ARM Limited and Contributors. All rights reserved.
+ * Copyright (c) 2015-2020, ARM Limited and Contributors. All rights reserved.
  *
  * SPDX-License-Identifier: BSD-3-Clause
  */
@@ -79,4 +79,8 @@ void tsp_plat_arch_setup(void)
 
 	setup_page_tables(bl_regions, plat_arm_get_mmap());
 	enable_mmu_el1(0);
+
+#if PLAT_RO_XLAT_TABLES
+	arm_xlat_make_tables_readonly();
+#endif
 }