feat(plat/arm): improve dram bank lookup

Next generation neoverse reference designs, such as RD-V3, can have
more than two non-contiguous DRAM banks. On such platforms, these
banks are spread across the system memory map.

This change modifies the common platform granule handlers to more
efficiently translate address-to-index and index-to-address on
platforms with a large number of DRAM banks. To support this arm
platforms have now have a common config option to select the
maximum number of DRAM banks - PLAT_ARM_MAX_DRAM_BANKS.

Change-Id: I6d5b02f8d3052d6b189f21ef48d1f684acb63b86
Signed-off-by: Harry Moulton <harry.moulton@arm.com>
Signed-off-by: Vivek Gautam <vivek.gautam@arm.com>
diff --git a/docs/getting_started/build-options.rst b/docs/getting_started/build-options.rst
index 4718195..42c798d 100644
--- a/docs/getting_started/build-options.rst
+++ b/docs/getting_started/build-options.rst
@@ -291,7 +291,8 @@
    RMM_CBMC_VIEWER_OUTPUT	,ON | OFF		,OFF			,"Generate report of CBMC results using the tool cbmc-viewer"
    RMM_CBMC_SINGLE_TESTBENCH	,			,OFF			,"Run CBMC on a single testbench instead on all of them"
    RMM_CCA_DA			,ON | OFF		,OFF			,"Enable Device Assignment support in RMM (experimental)"
-   ATTEST_PLAT_TOKEN_SIZE	,       		,0x1000			,"Maximum size in bytes expected for the Attestation platform token"
+   ATTEST_PLAT_TOKEN_SIZE	,			,0x1000			,"Maximum size in bytes expected for the Attestation platform token"
+   PLAT_ARM_MAX_DRAM_BANKS	,			,2			,"Maximum number of DRAM banks allowed in Arm platform layer"
 
 .. _llvm_build:
 
diff --git a/plat/arm/CMakeLists.txt b/plat/arm/CMakeLists.txt
index f06d63e..1360191 100644
--- a/plat/arm/CMakeLists.txt
+++ b/plat/arm/CMakeLists.txt
@@ -3,6 +3,15 @@
 # SPDX-FileCopyrightText: Copyright TF-RMM Contributors.
 #
 
+#
+# Additional config for maximum number of DRAM banks allowed to be managed
+#
+arm_config_option(
+    NAME PLAT_ARM_MAX_DRAM_BANKS
+    HELP "Maximum number of DRAM banks allowed in Arm platform layer"
+    TYPE STRING
+    DEFAULT 0x2)
+
 add_library(rmm-plat-arm)
 
 #
@@ -13,6 +22,13 @@
 add_subdirectory("${RMM_SOURCE_DIR}/drivers/pl011" ${RMM_BINARY_DIR}/drivers/pl011)
 add_subdirectory("${RMM_SOURCE_DIR}/plat/common" ${RMM_BINARY_DIR}/plat/common)
 
+if(PLAT_ARM_MAX_DRAM_BANKS EQUAL 0x0)
+    message(FATAL_ERROR "Invalid PLAT_ARM_MAX_DRAM_BANKS")
+endif()
+
+target_compile_definitions(rmm-plat-arm
+    PRIVATE "PLAT_ARM_MAX_DRAM_BANKS=U(${PLAT_ARM_MAX_DRAM_BANKS})")
+
 target_link_libraries(rmm-plat-arm
     PRIVATE rmm-driver-pl011
             rmm-lib
diff --git a/plat/arm/src/arm_granule.c b/plat/arm/src/arm_granule.c
index 983b6ee..a674794 100644
--- a/plat/arm/src/arm_granule.c
+++ b/plat/arm/src/arm_granule.c
@@ -17,33 +17,86 @@
 
 unsigned long plat_granule_addr_to_idx(unsigned long addr)
 {
+	const struct arm_dram_layout *dram = &arm_dram;
+	unsigned long r, l = 0UL;
+
 	if (!GRANULE_ALIGNED(addr)) {
 		return UINT64_MAX;
 	}
 
-	if ((addr >= arm_dram.arm_bank[0].start_addr) &&
-	    (addr <= arm_dram.arm_bank[0].end_addr)) {
-		return (addr - arm_dram.arm_bank[0].start_addr) / GRANULE_SIZE;
-	}
+	assert(dram->num_banks > 0UL);
+	assert(dram->num_banks <= PLAT_ARM_MAX_DRAM_BANKS);
+	r = dram->num_banks - 1UL;
 
-	if ((arm_dram.arm_bank[1].start_addr != 0UL) &&
-	    (addr >= arm_dram.arm_bank[1].start_addr) &&
-	    (addr <= arm_dram.arm_bank[1].end_addr)) {
-		return ((addr - arm_dram.arm_bank[1].start_addr) /
-			GRANULE_SIZE) + arm_dram.idx_bank_1;
-	}
+	/*
+	 * Use a binary search rather than a linear one to locate the bank which
+	 * the address falls within, then use the start_gran_idx (which is a
+	 * cumulative idx from previous dram banks) to calculate the required
+	 * granule index.
+	 */
+	while (l <= r) {
+		const struct arm_dram_bank *bank;
+		unsigned long i;
 
+		i = l + ((r - l) / 2UL);
+		assert(i < PLAT_ARM_MAX_DRAM_BANKS);
+
+		bank = &dram->bank[i];
+
+		if (addr < bank->base) {
+			if (i == 0UL) {
+				break;
+			}
+			r = i - 1UL;
+		} else if (addr > (bank->base + bank->size - 1UL)) {
+			l = i + 1UL;
+		} else {
+			return (bank->start_gran_idx +
+			      ((addr - bank->base) >> GRANULE_SHIFT));
+		}
+	}
 	return UINT64_MAX;
 }
 
 unsigned long plat_granule_idx_to_addr(unsigned long idx)
 {
-	assert(idx < arm_dram.num_granules);
+	const struct arm_dram_layout *dram = &arm_dram;
+	unsigned long r, l = 0UL, addr = 0UL;
 
-	if (idx < arm_dram.idx_bank_1) {
-		return arm_dram.arm_bank[0].start_addr + (idx * GRANULE_SIZE);
+	assert(dram->num_banks > 0UL);
+	assert(idx < dram->num_granules);
+
+	r = dram->num_banks - 1UL;
+
+	/*
+	 * Calculate the start and end granule index of each bank using the
+	 * start_gran_idx (which is a cumulative idx from previous dram banks)
+	 * and then check whether the given index falls within it.
+	 */
+	while (l <= r) {
+		const struct arm_dram_bank *bank;
+		unsigned long i;
+		unsigned long idx_start, idx_end;
+
+		i = l + ((r - l) / 2UL);
+		assert(i < PLAT_ARM_MAX_DRAM_BANKS);
+
+		bank = &dram->bank[i];
+
+		idx_start = bank->start_gran_idx;
+		idx_end = idx_start + (bank->size >> GRANULE_SHIFT) - 1UL;
+
+		if (idx < idx_start) {
+			assert(i != 0UL);
+			r = i - 1UL;
+		} else if (idx > idx_end) {
+			l = i + 1UL;
+		} else {
+			addr = bank->base + ((idx - idx_start) << GRANULE_SHIFT);
+			break;
+		}
 	}
-
-	return arm_dram.arm_bank[1].start_addr +
-			((idx - arm_dram.idx_bank_1) * GRANULE_SIZE);
+	/* Assert that the search was successful */
+	assert(l <= r);
+	return addr;
 }
diff --git a/plat/arm/src/arm_memory.c b/plat/arm/src/arm_memory.c
index f534572..c528f0d 100644
--- a/plat/arm/src/arm_memory.c
+++ b/plat/arm/src/arm_memory.c
@@ -8,42 +8,39 @@
 #include <assert.h>
 #include <rmm_el3_ifc.h>
 
-COMPILER_ASSERT(MAX_DRAM_NUM_BANKS == 2UL);
-
 void arm_set_dram_layout(struct ns_dram_info *plat_dram)
 {
-	uint64_t num_banks, num_granules = 0UL;
 	struct ns_dram_bank *bank_ptr;
 	struct arm_dram_layout *dram_ptr = arm_get_dram_layout();
+	uint64_t num_banks, num_granules;
 
 	assert(!is_mmu_enabled());
 
 	/* Number of banks */
 	num_banks = plat_dram->num_banks;
-	assert(num_banks <= MAX_DRAM_NUM_BANKS);
+	assert(num_banks > 0UL);
+	assert(num_banks <= PLAT_ARM_MAX_DRAM_BANKS);
 
 	/* Pointer to dram_bank[] array */
 	bank_ptr = plat_dram->banks;
 
+	num_granules = 0UL;
+
 	for (unsigned long i = 0UL; i < num_banks; i++) {
-		uintptr_t start = bank_ptr->base;
+		uint64_t base = bank_ptr->base;
 		uint64_t size = bank_ptr->size;
-		uintptr_t end = start + size - 1UL;
 
-		if (i == 1UL) {
-			/* Start granule index in bank 1 */
-			dram_ptr->idx_bank_1 = num_granules;
-		}
+		dram_ptr->bank[i].base = base;
+		dram_ptr->bank[i].size = size;
+		dram_ptr->bank[i].start_gran_idx = num_granules;
 
-		/* Total number of granules */
-		num_granules += (size / GRANULE_SIZE);
-
-		dram_ptr->arm_bank[i].start_addr = start;
-		dram_ptr->arm_bank[i].end_addr = end;
-
+		num_granules += (size >> GRANULE_SHIFT);
 		bank_ptr++;
 	}
 
+	assert(num_granules <= RMM_MAX_GRANULES);
+
+	dram_ptr->num_banks = num_banks;
 	dram_ptr->num_granules = num_granules;
 
 	inv_dcache_range((uintptr_t)dram_ptr, sizeof(struct arm_dram_layout));
diff --git a/plat/arm/src/arm_setup.c b/plat/arm/src/arm_setup.c
index e17dccf..ef58384 100644
--- a/plat/arm/src/arm_setup.c
+++ b/plat/arm/src/arm_setup.c
@@ -112,7 +112,7 @@
 	 * to the platform DRAM info structure
 	 */
 	ret = rmm_el3_ifc_get_dram_data_validated_pa(
-					MAX_DRAM_NUM_BANKS,
+					PLAT_ARM_MAX_DRAM_BANKS,
 					&plat_dram);
 	if (ret != E_RMM_BOOT_SUCCESS) {
 		ERROR("DRAM data error\n");
diff --git a/plat/arm/src/include/arm_dram.h b/plat/arm/src/include/arm_dram.h
index 468bc94..0103599 100644
--- a/plat/arm/src/include/arm_dram.h
+++ b/plat/arm/src/include/arm_dram.h
@@ -10,19 +10,19 @@
 #include <stddef.h>
 #include <stdint.h>
 
-/* Maximum number of DRAM banks supported */
-#define MAX_DRAM_NUM_BANKS	2UL
-
-/* Arm runtime structures */
+/* Arm platform dram management structures */
 struct arm_dram_bank {
-	uintptr_t start_addr;		/* start address */
-	uintptr_t end_addr;		/* end address */
+	uint64_t base;			/* bank base address */
+	uint64_t size;			/* size of this bank */
+	/* This idx is a cumulative granule count of previous banks */
+	uint64_t start_gran_idx;	/* Start granule index for this bank */
 };
 
 struct arm_dram_layout {
-	unsigned long idx_bank_1;	/* start granule index in bank 1 */
 	unsigned long num_granules;	/* number of granules */
-	struct arm_dram_bank arm_bank[MAX_DRAM_NUM_BANKS];
+	unsigned long num_banks;	/* number of dram banks */
+	struct arm_dram_bank bank[PLAT_ARM_MAX_DRAM_BANKS];
+					/* Sorted array of DRAM banks */
 };
 
 void arm_set_dram_layout(struct ns_dram_info *plat_dram);