cbfstool: Make add-stage support multiple loadable segments

For x86 eXecute-In-Place (XIP) pre-memory `.data` section support, we
have to use an extra segment as the VMA/LMA of the data is different
than the VMA/LMA of the code.

To support this requirement, this patch makes cbfstool:
1. Allow the load of an ELF with an extra segment
2. Makes add-stage for XIP (cf. parse_elf_to_xip_stage()) write its
   content to the output binary.

To prevent the creation of unsuitable binaries, cbfstool verifies that
the LMA addresses of the segments are consecutives.

TEST=XIP pre-memory stages with a `.data` section have the `.data`
     section covered by a second segment properly included right after
     the code.

Change-Id: I480b4b047546c8aa4e12dfb688e0299f80283234
Signed-off-by: Jeremy Compostella <jeremy.compostella@intel.com>
Reviewed-on: https://review.coreboot.org/c/coreboot/+/77584
Reviewed-by: Julius Werner <jwerner@chromium.org>
Tested-by: build bot (Jenkins) <no-reply@coreboot.org>
diff --git a/util/cbfstool/cbfs-mkstage.c b/util/cbfstool/cbfs-mkstage.c
index 54150cc..8129f0b 100644
--- a/util/cbfstool/cbfs-mkstage.c
+++ b/util/cbfstool/cbfs-mkstage.c
@@ -288,6 +288,46 @@
 	return 0;
 }
 
+/* Returns a NULL-terminated list of loadable segments.  Returns NULL if no
+ * loadable segments were found or if two consecutive segments are not
+ * consecutive in their physical address space.
+ */
+static Elf64_Phdr **find_loadable_segments(struct parsed_elf *pelf)
+{
+	Elf64_Phdr **phdrs = NULL;
+	Elf64_Phdr *prev = NULL, *cur;
+	size_t size = 1, i;
+
+	for (i = 0; i < pelf->ehdr.e_phnum; i++, prev = cur) {
+		cur = &pelf->phdr[i];
+
+		if (cur->p_type != PT_LOAD || cur->p_memsz == 0)
+			continue;
+
+		phdrs = realloc(phdrs, sizeof(*phdrs) * ++size);
+		if (!phdrs) {
+			ERROR("Memory allocation failed\n");
+			return NULL;
+		}
+		phdrs[size - 2] = cur;
+
+		if (!prev)
+			continue;
+
+		if (prev->p_paddr + prev->p_memsz != cur->p_paddr ||
+		    prev->p_filesz != prev->p_memsz) {
+			ERROR("Loadable segments physical addresses should "
+			      "be consecutive\n");
+			free(phdrs);
+			return NULL;
+		}
+	}
+
+	if (phdrs)
+		phdrs[size - 1] = NULL;
+	return phdrs;
+}
+
 int parse_elf_to_xip_stage(const struct buffer *input, struct buffer *output,
 			   uint32_t location, const char *ignore_sections,
 			   struct cbfs_file_attr_stageheader *stageheader)
@@ -296,11 +336,13 @@
 	struct rmod_context *rmodctx;
 	struct reloc_filter filter;
 	struct parsed_elf *pelf;
-	uint32_t adjustment;
+	uint32_t adjustment, memsz = 0;
 	struct buffer binput;
 	struct buffer boutput;
+	Elf64_Phdr **toload, **phdr;
 	Elf64_Xword i;
 	int ret = -1;
+	size_t filesz = 0;
 
 	rmodctx = &xipctx.rmodctx;
 	pelf = &rmodctx->pelf;
@@ -324,27 +366,37 @@
 	if (rmodule_collect_relocations(rmodctx, &filter))
 		goto out;
 
-	if (buffer_create(output, pelf->phdr->p_filesz, input->name) != 0) {
+	toload = find_loadable_segments(pelf);
+	if (!toload)
+		goto out;
+
+	for (phdr = toload; *phdr; phdr++)
+		filesz += (*phdr)->p_filesz;
+	if (buffer_create(output, filesz, input->name) != 0) {
 		ERROR("Unable to allocate memory: %m\n");
 		goto out;
 	}
 	buffer_clone(&boutput, output);
-	memset(buffer_get(&boutput), 0, pelf->phdr->p_filesz);
+	memset(buffer_get(&boutput), 0, filesz);
 	buffer_set_size(&boutput, 0);
 
-	/* Single loadable segment. The entire segment moves to final
-	 * location from based on virtual address of loadable segment. */
+	/* The program segment moves to final location from based on virtual
+	 * address of loadable segment. */
 	adjustment = location - pelf->phdr->p_vaddr;
 	DEBUG("Relocation adjustment: %08x\n", adjustment);
 
+	for (phdr = toload; *phdr; phdr++)
+		memsz += (*phdr)->p_memsz;
 	fill_cbfs_stageheader(stageheader,
 			      (uint32_t)pelf->ehdr.e_entry + adjustment,
 			      (uint32_t)pelf->phdr->p_vaddr + adjustment,
-			      pelf->phdr->p_memsz);
-	/* Need an adjustable buffer. */
-	buffer_clone(&binput, input);
-	buffer_seek(&binput, pelf->phdr->p_offset);
-	bputs(&boutput, buffer_get(&binput), pelf->phdr->p_filesz);
+			      memsz);
+	for (phdr = toload; *phdr; phdr++) {
+		/* Need an adjustable buffer. */
+		buffer_clone(&binput, input);
+		buffer_seek(&binput, (*phdr)->p_offset);
+		bputs(&boutput, buffer_get(&binput), (*phdr)->p_filesz);
+	}
 
 	buffer_clone(&boutput, output);
 
diff --git a/util/cbfstool/rmodule.c b/util/cbfstool/rmodule.c
index 02f1d90..b21f777 100644
--- a/util/cbfstool/rmodule.c
+++ b/util/cbfstool/rmodule.c
@@ -247,13 +247,13 @@
 	for (i = 0; i < pelf->ehdr.e_phnum; i++) {
 		if (pelf->phdr[i].p_type != PT_LOAD)
 			continue;
-		phdr = &pelf->phdr[i];
+		if (!phdr)
+			phdr = &pelf->phdr[i];
 		nsegments++;
 	}
 
-	if (nsegments != 1) {
-		ERROR("Unexpected number of loadable segments: %d.\n",
-		      nsegments);
+	if (nsegments == 0) {
+		ERROR("No loadable segment found.\n");
 		return -1;
 	}
 
@@ -262,6 +262,7 @@
 	     (long long)phdr->p_memsz);
 
 	ctx->phdr = phdr;
+	ctx->nsegments = nsegments;
 
 	return 0;
 }
@@ -269,18 +270,17 @@
 static int
 filter_relocation_sections(struct rmod_context *ctx)
 {
-	int i;
+	int i, j;
 	const char *shstrtab;
 	struct parsed_elf *pelf;
 	const Elf64_Phdr *phdr;
 
 	pelf = &ctx->pelf;
-	phdr = ctx->phdr;
 	shstrtab = buffer_get(pelf->strtabs[pelf->ehdr.e_shstrndx]);
 
 	/*
 	 * Find all relocation sections that contain relocation entries
-	 * for sections that fall within the bounds of the segment. For
+	 * for sections that fall within the bounds of the segments. For
 	 * easier processing the pointer to the relocation array for the
 	 * sections that don't fall within the loadable program are NULL'd
 	 * out.
@@ -319,11 +319,18 @@
 			continue;
 		}
 
-		if (shdr->sh_addr < phdr->p_vaddr ||
-		    ((shdr->sh_addr + shdr->sh_size) >
-		     (phdr->p_vaddr + phdr->p_memsz))) {
+		for (j = 0; j < pelf->ehdr.e_phnum; j++) {
+			phdr = &pelf->phdr[j];
+			if (phdr->p_type == PT_LOAD &&
+			    shdr->sh_addr >= phdr->p_vaddr &&
+			    ((shdr->sh_addr + shdr->sh_size) <=
+			     (phdr->p_vaddr + phdr->p_memsz)))
+				break;
+		}
+		if (j == pelf->ehdr.e_phnum) {
 			ERROR("Relocations being applied to section %d not "
-			      "within segment region.\n", sh_info);
+			      "within segments region.\n", sh_info);
+			pelf->relocs[i] = NULL;
 			return -1;
 		}
 	}
@@ -488,6 +495,11 @@
 	Elf64_Addr addr;
 	Elf64_Ehdr ehdr;
 
+	if (ctx->nsegments != 1) {
+		ERROR("Multiple loadable segments is not supported.\n");
+		return -1;
+	}
+
 	bit64 = ctx->pelf.ehdr.e_ident[EI_CLASS] == ELFCLASS64;
 
 	/*
diff --git a/util/cbfstool/rmodule.h b/util/cbfstool/rmodule.h
index ec0971e..231160a 100644
--- a/util/cbfstool/rmodule.h
+++ b/util/cbfstool/rmodule.h
@@ -29,6 +29,8 @@
 	struct parsed_elf pelf;
 	/* Program segment. */
 	Elf64_Phdr *phdr;
+	/* Number of loadable segments. */
+	size_t nsegments;
 	/* Symbol string table. */
 	char *strtab;