fix(gic): add support for passing GIC data from DT in boot flow

This change allows GIC information parsed from DT to be passed and
utilised in the boot flow.

Change-Id: Ifc4ababba02a43b82704f4d8ea2f7b8e085a12d4
Signed-off-by: Jerry Wang <Jerry.Wang4@arm.com>
diff --git a/src/arch/aarch64/plat/interrupts/BUILD.gn b/src/arch/aarch64/plat/interrupts/BUILD.gn
index 32c1896..d0f0304 100644
--- a/src/arch/aarch64/plat/interrupts/BUILD.gn
+++ b/src/arch/aarch64/plat/interrupts/BUILD.gn
@@ -13,4 +13,5 @@
 source_set("gicv3") {
   public_configs = [ "//src/arch/${plat_arch}:config" ]
   sources = [ "gicv3.c" ]
+  deps = [ "//third_party/dtc:libfdt" ]
 }
diff --git a/src/arch/aarch64/plat/interrupts/gicv3.c b/src/arch/aarch64/plat/interrupts/gicv3.c
index 6ae3438..22f999e 100644
--- a/src/arch/aarch64/plat/interrupts/gicv3.c
+++ b/src/arch/aarch64/plat/interrupts/gicv3.c
@@ -6,6 +6,8 @@
  * https://opensource.org/licenses/BSD-3-Clause.
  */
 
+#include <libfdt.h>
+
 #include "hf/check.h"
 #include "hf/cpu.h"
 #include "hf/dlog.h"
@@ -19,6 +21,8 @@
 #include "gicv3_helpers.h"
 #include "msr.h"
 
+#define MAX_CHIPS 16
+
 #define GICD_SIZE (0x10000)
 
 /**
@@ -46,7 +50,6 @@
 
 struct gicv3_driver {
 	uintptr_t dist_base;
-	uintptr_t base_redist_frame;
 	uintptr_t all_redist_frames[MAX_CPUS];
 	struct spinlock lock;
 };
@@ -146,7 +149,7 @@
 void gicv3_enable_interrupt(uint32_t id, uint32_t proc_num)
 {
 	CHECK(plat_gicv3_driver.dist_base != 0U);
-	CHECK(plat_gicv3_driver.base_redist_frame != 0U);
+	CHECK(plat_gicv3_driver.all_redist_frames[proc_num] != 0U);
 	CHECK(proc_num < MAX_CPUS);
 
 	/*
@@ -174,7 +177,7 @@
 void gicv3_disable_interrupt(uint32_t id, uint32_t proc_num)
 {
 	CHECK(plat_gicv3_driver.dist_base != 0U);
-	CHECK(plat_gicv3_driver.base_redist_frame != 0U);
+	CHECK(plat_gicv3_driver.all_redist_frames[proc_num] != 0U);
 	CHECK(proc_num < MAX_CPUS);
 
 	/*
@@ -294,6 +297,103 @@
 	write_msr(ICC_EOIR1_EL1, id);
 }
 
+/**
+ * A copy from libfdt. Dependency of `fdt_redistributor_regions_cells`
+ */
+static int fdt_cells(const void *fdt, int nodeoffset, const char *name)
+{
+	const fdt32_t *c;
+	uint32_t val;
+	int len;
+
+	c = fdt_getprop(fdt, nodeoffset, name, &len);
+	if (!c) {
+		return len;
+	}
+	if (len != sizeof(*c)) {
+		return -FDT_ERR_BADNCELLS;
+	}
+	val = fdt32_to_cpu(*c);
+	if (val > FDT_MAX_NCELLS) {
+		return -FDT_ERR_BADNCELLS;
+	}
+	return (int)val;
+}
+
+static int fdt_redistributor_regions_cells(const void *fdt, int nodeoffset)
+{
+	int val;
+
+	val = fdt_cells(fdt, nodeoffset, "#redistributor-regions");
+	if (val == -FDT_ERR_NOTFOUND) {
+		return 1;
+	}
+	return val;
+}
+
+/**
+ * Retrieves redistributor count for a bus represented in the device tree.
+ * Result is value of '#redistributor-regions' at `node`.
+ * If '#redistributor-regions' is not found, the default value is 1 cell.
+ * Returns true on success, false if an error occurred.
+ */
+static bool fdt_redistributor_regions(const struct fdt_node *node,
+				      uint32_t *count)
+{
+	int c = fdt_redistributor_regions_cells(fdt_base(&node->fdt),
+						node->offset);
+	if (c < 0) {
+		return false;
+	}
+	*count = (uint64_t)c;
+	return true;
+}
+
+static bool fdt_find_gics(const struct fdt *fdt,
+			  struct mem_range *gic_mem_ranges, uint32_t *gic_count)
+{
+	struct fdt_node n;
+	struct memiter data;
+	size_t addr_size;
+	size_t size_size;
+	uint8_t rdist_reg_idx = 0;
+
+	if (!fdt_find_node(fdt, "/interrupt-controller", &n) ||
+	    !fdt_address_size(&n, &addr_size) ||
+	    !fdt_size_size(&n, &size_size) ||
+	    !fdt_redistributor_regions(&n, gic_count)) {
+		dlog_info(
+			"Unable to find '/interrupt-controller. Using default "
+			"configuration.'\n");
+		/*
+		 * Initialise the default GICD, GICR memory ranges and GIC count
+		 */
+		gic_mem_ranges[0].begin = pa_init(GICD_BASE);
+		gic_mem_ranges[0].end = pa_init(GICD_BASE + GICD_SIZE);
+		gic_mem_ranges[1].begin = pa_init(GICR_BASE);
+		gic_mem_ranges[1].end = pa_init(
+			GICR_BASE + GICR_FRAMES * GIC_REDIST_SIZE_PER_PE);
+		*gic_count = 1;
+		return true;
+	}
+	if (!fdt_read_property(&n, "reg", &data)) {
+		dlog_error("Unable to read property 'reg'\n");
+		return false;
+	}
+	/* Traverse all memory ranges within this node. */
+	while (memiter_size(&data)) {
+		uintpaddr_t addr;
+		size_t len;
+
+		CHECK(fdt_parse_number(&data, addr_size, &addr));
+		CHECK(fdt_parse_number(&data, size_size, &len));
+		gic_mem_ranges[rdist_reg_idx].begin = pa_init(addr);
+		gic_mem_ranges[rdist_reg_idx].end = pa_init(addr + len);
+		rdist_reg_idx++;
+	}
+	return true;
+}
+
 uint64_t read_gicr_typer_reg(uintptr_t gicr_frame_addr)
 {
 	return io_read64(IO64_C(gicr_frame_addr + GICR_TYPER));
@@ -329,15 +429,20 @@
 	return arch_affinity_to_core_pos(reg);
 }
 
-static inline void populate_redist_base_addrs(void)
+static inline void populate_redist_base_addrs(struct mem_range *gic_mem_ranges,
+					      uint32_t num_gic_rdist)
 {
 	uintptr_t current_rdist_frame;
 	uint64_t typer_reg;
 	uint32_t core_idx;
+	uint32_t gicr_idx = 0;
 
-	current_rdist_frame = plat_gicv3_driver.base_redist_frame;
+	/*
+	 * GICR mem range starts from index 1. GICD mem range is index 0.
+	 */
+	current_rdist_frame = gic_mem_ranges[gicr_idx + 1].begin.pa;
 
-	while (true) {
+	while (gicr_idx < num_gic_rdist) {
 		typer_reg = read_gicr_typer_reg(current_rdist_frame);
 		core_idx = gicr_affinity_to_core_pos(typer_reg);
 
@@ -351,9 +456,12 @@
 				current_rdist_frame;
 		}
 
-		/* Check if this is the last frame. */
+		/* Check if this is the last GICR frame for the specific chip */
 		if (typer_reg & REDIST_LAST_FRAME_MASK) {
-			return;
+			gicr_idx++;
+			current_rdist_frame =
+				gic_mem_ranges[gicr_idx + 1].begin.pa;
+			continue;
 		}
 
 		current_rdist_frame += GIC_REDIST_FRAMES_OFFSET;
@@ -471,12 +579,14 @@
 #endif /* GIC_EXT_INTID */
 
 bool gicv3_driver_init(struct mm_stage1_locked stage1_locked,
-		       struct mpool *ppool)
+		       struct mpool *ppool, struct mem_range *gic_mem_ranges,
+		       uint32_t num_gic_rdist)
 {
 	void *base_addr;
 	uint32_t gic_version;
 	uint32_t reg_pidr;
 	uint32_t typer_reg;
+	uint32_t gicr_idx;
 
 	base_addr = mm_identity_map(stage1_locked, pa_init(GICD_BASE),
 				    pa_init(GICD_BASE + GICD_SIZE),
@@ -487,23 +597,27 @@
 	}
 
 	plat_gicv3_driver.dist_base = (uintptr_t)base_addr;
-
-	base_addr = mm_identity_map(
-		stage1_locked, pa_init(GICR_BASE),
-		pa_init(GICR_BASE + GICR_FRAMES * GIC_REDIST_SIZE_PER_PE),
-		MM_MODE_R | MM_MODE_W | MM_MODE_D, ppool);
-
-	if (base_addr == NULL) {
-		dlog_error("Could not map GICv3 into Hafnium memory map\n");
-		return false;
-	}
-
 	typer_reg = read_gicd_typer_reg(plat_gicv3_driver.dist_base);
 
 	/* Ensure GIC implementation supports two security states. */
 	CHECK((typer_reg & TYPER_SEC_EXTN) == TYPER_SEC_EXTN);
 
-	plat_gicv3_driver.base_redist_frame = (uintptr_t)base_addr;
+	for (gicr_idx = 0; gicr_idx < num_gic_rdist; gicr_idx++) {
+		/*
+		 * GICR mem range starts from index 1. GICD mem range is index 0
+		 */
+		base_addr = mm_identity_map(
+			stage1_locked, gic_mem_ranges[gicr_idx + 1].begin,
+			gic_mem_ranges[gicr_idx + 1].end,
+			MM_MODE_R | MM_MODE_W | MM_MODE_D, ppool);
+
+		if (base_addr == NULL) {
+			dlog_error(
+				"Could not map GICv3 into Hafnium memory "
+				"map\n");
+			return false;
+		}
+	}
 
 	/* Check GIC version reported by the Peripheral register. */
 	reg_pidr = gicd_read_pidr2(plat_gicv3_driver.dist_base);
@@ -514,7 +628,7 @@
 #elif GIC_VERSION == 4
 	CHECK(gic_version == ARCH_REV_GICV4);
 #endif
-	populate_redist_base_addrs();
+	populate_redist_base_addrs(gic_mem_ranges, num_gic_rdist);
 
 #if GIC_EXT_INTID
 	CHECK((typer_reg & TYPER_ESPI) == TYPER_ESPI);
@@ -527,9 +641,20 @@
 	const struct fdt *fdt, struct mm_stage1_locked stage1_locked,
 	struct mpool *ppool)
 {
-	(void)fdt;
+	struct mem_range gic_mem_ranges[MAX_CHIPS];
+	uint32_t num_gic_rdist;
+	uint32_t chip_idx = 0;
 
-	if (!gicv3_driver_init(stage1_locked, ppool)) {
+	while (chip_idx < MAX_CHIPS) {
+		gic_mem_ranges[chip_idx].begin = pa_init(0);
+		gic_mem_ranges[chip_idx].end = pa_init(0);
+		chip_idx++;
+	}
+
+	fdt_find_gics(fdt, gic_mem_ranges, &num_gic_rdist);
+
+	if (!gicv3_driver_init(stage1_locked, ppool, gic_mem_ranges,
+			       num_gic_rdist)) {
 		dlog_error("Failed to initialize GICv3 driver\n");
 		return false;
 	}
diff --git a/src/init.c b/src/init.c
index b536e04..8b73bed 100644
--- a/src/init.c
+++ b/src/init.c
@@ -160,10 +160,6 @@
 		panic("Could not initialize IOMMUs.");
 	}
 
-	if (!fdt_unmap(&fdt, mm_stage1_locked, &ppool)) {
-		panic("Unable to unmap FDT.");
-	}
-
 	cpu_module_init(params->cpu_ids, params->cpu_count);
 
 	if (!plat_interrupts_controller_driver_init(&fdt, mm_stage1_locked,
@@ -171,6 +167,10 @@
 		panic("Could not initialize Interrupt Controller driver.");
 	}
 
+	if (!fdt_unmap(&fdt, mm_stage1_locked, &ppool)) {
+		panic("Unable to unmap FDT.");
+	}
+
 	/* Load all VMs. */
 	update.reserved_ranges_count = 0;
 	if (!load_vms(mm_stage1_locked, manifest, &cpio, params, &update,