All of lore.kernel.org
 help / color / mirror / Atom feed
From: Fabien Dessenne <fabien.dessenne@st.com>
To: u-boot@lists.denx.de
Subject: [U-Boot] [PATCH v2 1/6] remoteproc: elf_loader: Add elf resource table load support
Date: Wed, 30 Oct 2019 14:38:28 +0100	[thread overview]
Message-ID: <1572442713-26353-2-git-send-email-fabien.dessenne@st.com> (raw)
In-Reply-To: <1572442713-26353-1-git-send-email-fabien.dessenne@st.com>

Add rproc_elf_load_rsc_table(), which searches for a resource table in
an elf64/elf32 image, and if found, copies it to device memory.
Add also the elf32 and elf64 variants of this API.
Add a test for this.

Signed-off-by: Fabien Dessenne <fabien.dessenne@st.com>
---
 drivers/remoteproc/rproc-elf-loader.c | 269 ++++++++++++++++++++++++++++++++++
 include/remoteproc.h                  |  70 +++++++++
 test/dm/remoteproc.c                  |  91 ++++++++++--
 3 files changed, 419 insertions(+), 11 deletions(-)

diff --git a/drivers/remoteproc/rproc-elf-loader.c b/drivers/remoteproc/rproc-elf-loader.c
index b38a226..9127ea5 100644
--- a/drivers/remoteproc/rproc-elf-loader.c
+++ b/drivers/remoteproc/rproc-elf-loader.c
@@ -7,6 +7,39 @@
 #include <elf.h>
 #include <remoteproc.h>
 
+/**
+ * struct resource_table - firmware resource table header
+ * @ver: version number
+ * @num: number of resource entries
+ * @reserved: reserved (must be zero)
+ * @offset: array of offsets pointing at the various resource entries
+ *
+ * A resource table is essentially a list of system resources required
+ * by the remote processor. It may also include configuration entries.
+ * If needed, the remote processor firmware should contain this table
+ * as a dedicated ".resource_table" ELF section.
+ *
+ * Some resources entries are mere announcements, where the host is informed
+ * of specific remoteproc configuration. Other entries require the host to
+ * do something (e.g. allocate a system resource). Sometimes a negotiation
+ * is expected, where the firmware requests a resource, and once allocated,
+ * the host should provide back its details (e.g. address of an allocated
+ * memory region).
+ *
+ * The header of the resource table, as expressed by this structure,
+ * contains a version number (should we need to change this format in the
+ * future), the number of available resource entries, and their offsets
+ * in the table.
+ *
+ * Immediately following this header are the resource entries themselves.
+ */
+struct resource_table {
+	u32 ver;
+	u32 num;
+	u32 reserved[2];
+	u32 offset[0];
+} __packed;
+
 /* Basic function to verify ELF32 image format */
 int rproc_elf32_sanity_check(ulong addr, ulong size)
 {
@@ -275,3 +308,239 @@ ulong rproc_elf_get_boot_addr(struct udevice *dev, ulong addr)
 	else
 		return rproc_elf32_get_boot_addr(addr);
 }
+
+/*
+ * Search for the resource table in an ELF32 image.
+ * Returns the address of the resource table section if found, NULL if there is
+ * no resource table section, or error pointer.
+ */
+static Elf32_Shdr *rproc_elf32_find_rsc_table(struct udevice *dev,
+					      ulong fw_addr, ulong fw_size)
+{
+	int ret;
+	unsigned int i;
+	const char *name_table;
+	struct resource_table *table;
+	const u8 *elf_data = (void *)fw_addr;
+	Elf32_Ehdr *ehdr = (Elf32_Ehdr *)fw_addr;
+	Elf32_Shdr *shdr;
+
+	ret = rproc_elf32_sanity_check(fw_addr, fw_size);
+	if (ret) {
+		pr_debug("Invalid ELF32 Image %d\n", ret);
+		return ERR_PTR(ret);
+	}
+
+	/* look for the resource table and handle it */
+	shdr = (Elf32_Shdr *)(elf_data + ehdr->e_shoff);
+	name_table = (const char *)(elf_data +
+				    shdr[ehdr->e_shstrndx].sh_offset);
+
+	for (i = 0; i < ehdr->e_shnum; i++, shdr++) {
+		u32 size = shdr->sh_size;
+		u32 offset = shdr->sh_offset;
+
+		if (strcmp(name_table + shdr->sh_name, ".resource_table"))
+			continue;
+
+		table = (struct resource_table *)(elf_data + offset);
+
+		/* make sure we have the entire table */
+		if (offset + size > fw_size) {
+			pr_debug("resource table truncated\n");
+			return ERR_PTR(-ENOSPC);
+		}
+
+		/* make sure table has at least the header */
+		if (sizeof(*table) > size) {
+			pr_debug("header-less resource table\n");
+			return ERR_PTR(-ENOSPC);
+		}
+
+		/* we don't support any version beyond the first */
+		if (table->ver != 1) {
+			pr_debug("unsupported fw ver: %d\n", table->ver);
+			return ERR_PTR(-EPROTONOSUPPORT);
+		}
+
+		/* make sure reserved bytes are zeroes */
+		if (table->reserved[0] || table->reserved[1]) {
+			pr_debug("non zero reserved bytes\n");
+			return ERR_PTR(-EBADF);
+		}
+
+		/* make sure the offsets array isn't truncated */
+		if (table->num * sizeof(table->offset[0]) +
+				 sizeof(*table) > size) {
+			pr_debug("resource table incomplete\n");
+			return ERR_PTR(-ENOSPC);
+		}
+
+		return shdr;
+	}
+
+	return NULL;
+}
+
+/* Load the resource table from an ELF32 image */
+int rproc_elf32_load_rsc_table(struct udevice *dev, ulong fw_addr,
+			       ulong fw_size, ulong *rsc_addr, ulong *rsc_size)
+{
+	const struct dm_rproc_ops *ops;
+	Elf32_Shdr *shdr;
+	void *src, *dst;
+
+	shdr = rproc_elf32_find_rsc_table(dev, fw_addr, fw_size);
+	if (!shdr)
+		return -ENODATA;
+	if (IS_ERR(shdr))
+		return PTR_ERR(shdr);
+
+	ops = rproc_get_ops(dev);
+	*rsc_addr = (ulong)shdr->sh_addr;
+	*rsc_size = (ulong)shdr->sh_size;
+
+	src = (void *)fw_addr + shdr->sh_offset;
+	if (ops->device_to_virt)
+		dst = (void *)ops->device_to_virt(dev, *rsc_addr, *rsc_size);
+	else
+		dst = (void *)rsc_addr;
+
+	dev_dbg(dev, "Loading resource table to 0x%8lx (%ld bytes)\n",
+		(ulong)dst, *rsc_size);
+
+	memcpy(dst, src, *rsc_size);
+	flush_cache(rounddown((unsigned long)dst, ARCH_DMA_MINALIGN),
+		    roundup((unsigned long)dst + *rsc_size,
+			    ARCH_DMA_MINALIGN) -
+		    rounddown((unsigned long)dst, ARCH_DMA_MINALIGN));
+
+	return 0;
+}
+
+/*
+ * Search for the resource table in an ELF64 image.
+ * Returns the address of the resource table section if found, NULL if there is
+ * no resource table section, or error pointer.
+ */
+static Elf64_Shdr *rproc_elf64_find_rsc_table(struct udevice *dev,
+					      ulong fw_addr, ulong fw_size)
+{
+	int ret;
+	unsigned int i;
+	const char *name_table;
+	struct resource_table *table;
+	const u8 *elf_data = (void *)fw_addr;
+	Elf64_Ehdr *ehdr = (Elf64_Ehdr *)fw_addr;
+	Elf64_Shdr *shdr;
+
+	ret = rproc_elf64_sanity_check(fw_addr, fw_size);
+	if (ret) {
+		pr_debug("Invalid ELF64 Image %d\n", ret);
+		return ERR_PTR(ret);
+	}
+
+	/* look for the resource table and handle it */
+	shdr = (Elf64_Shdr *)(elf_data + ehdr->e_shoff);
+	name_table = (const char *)(elf_data +
+				    shdr[ehdr->e_shstrndx].sh_offset);
+
+	for (i = 0; i < ehdr->e_shnum; i++, shdr++) {
+		u64 size = shdr->sh_size;
+		u64 offset = shdr->sh_offset;
+
+		if (strcmp(name_table + shdr->sh_name, ".resource_table"))
+			continue;
+
+		table = (struct resource_table *)(elf_data + offset);
+
+		/* make sure we have the entire table */
+		if (offset + size > fw_size) {
+			pr_debug("resource table truncated\n");
+			return ERR_PTR(-ENOSPC);
+		}
+
+		/* make sure table has at least the header */
+		if (sizeof(*table) > size) {
+			pr_debug("header-less resource table\n");
+			return ERR_PTR(-ENOSPC);
+		}
+
+		/* we don't support any version beyond the first */
+		if (table->ver != 1) {
+			pr_debug("unsupported fw ver: %d\n", table->ver);
+			return ERR_PTR(-EPROTONOSUPPORT);
+		}
+
+		/* make sure reserved bytes are zeroes */
+		if (table->reserved[0] || table->reserved[1]) {
+			pr_debug("non zero reserved bytes\n");
+			return ERR_PTR(-EBADF);
+		}
+
+		/* make sure the offsets array isn't truncated */
+		if (table->num * sizeof(table->offset[0]) +
+				 sizeof(*table) > size) {
+			pr_debug("resource table incomplete\n");
+			return ERR_PTR(-ENOSPC);
+		}
+
+		return shdr;
+	}
+
+	return NULL;
+}
+
+/* Load the resource table from an ELF64 image */
+int rproc_elf64_load_rsc_table(struct udevice *dev, ulong fw_addr,
+			       ulong fw_size, ulong *rsc_addr, ulong *rsc_size)
+{
+	const struct dm_rproc_ops *ops;
+	Elf64_Shdr *shdr;
+	void *src, *dst;
+
+	shdr = rproc_elf64_find_rsc_table(dev, fw_addr, fw_size);
+	if (!shdr)
+		return -ENODATA;
+	if (IS_ERR(shdr))
+		return PTR_ERR(shdr);
+
+	ops = rproc_get_ops(dev);
+	*rsc_addr = (ulong)shdr->sh_addr;
+	*rsc_size = (ulong)shdr->sh_size;
+
+	src = (void *)fw_addr + shdr->sh_offset;
+	if (ops->device_to_virt)
+		dst = (void *)ops->device_to_virt(dev, *rsc_addr, *rsc_size);
+	else
+		dst = (void *)rsc_addr;
+
+	dev_dbg(dev, "Loading resource table to 0x%8lx (%ld bytes)\n",
+		(ulong)dst, *rsc_size);
+
+	memcpy(dst, src, *rsc_size);
+	flush_cache(rounddown((unsigned long)dst, ARCH_DMA_MINALIGN),
+		    roundup((unsigned long)dst + *rsc_size,
+			    ARCH_DMA_MINALIGN) -
+		    rounddown((unsigned long)dst, ARCH_DMA_MINALIGN));
+
+	return 0;
+}
+
+/* Load the resource table from an ELF32 or ELF64 image */
+int rproc_elf_load_rsc_table(struct udevice *dev, ulong fw_addr,
+			     ulong fw_size, ulong *rsc_addr, ulong *rsc_size)
+
+{
+	Elf32_Ehdr *ehdr = (Elf32_Ehdr *)fw_addr;
+
+	if (!fw_addr)
+		return -EFAULT;
+
+	if (ehdr->e_ident[EI_CLASS] == ELFCLASS64)
+		return rproc_elf64_load_rsc_table(dev, fw_addr, fw_size,
+						  rsc_addr, rsc_size);
+	else
+		return rproc_elf32_load_rsc_table(dev, fw_addr, fw_size,
+						  rsc_addr, rsc_size);
+}
diff --git a/include/remoteproc.h b/include/remoteproc.h
index 046cd9e..a903acb 100644
--- a/include/remoteproc.h
+++ b/include/remoteproc.h
@@ -277,6 +277,64 @@ int rproc_elf_load_image(struct udevice *dev, unsigned long addr, ulong size);
  * image.
  */
 ulong rproc_elf_get_boot_addr(struct udevice *dev, ulong addr);
+
+/**
+ * rproc_elf32_load_rsc_table() - load the resource table from an ELF32 image
+ *
+ * Search for the resource table in an ELF32 image, and if found, copy it to
+ * device memory.
+ *
+ * @dev:	device loading the resource table
+ * @fw_addr:	ELF image address
+ * @fw_size:	size of the ELF image
+ * @rsc_addr:	pointer to the found resource table address. Updated on
+ *		operation success
+ * @rsc_size:	pointer to the found resource table size. Updated on operation
+ *		success
+ *
+ * @return 0 if a valid resource table is successfully loaded, -ENODATA if there
+ * is no resource table (which is optional), or another appropriate error value.
+ */
+int rproc_elf32_load_rsc_table(struct udevice *dev, ulong fw_addr,
+			       ulong fw_size, ulong *rsc_addr, ulong *rsc_size);
+/**
+ * rproc_elf64_load_rsc_table() - load the resource table from an ELF64 image
+ *
+ * Search for the resource table in an ELF64 image, and if found, copy it to
+ * device memory.
+ *
+ * @dev:	device loading the resource table
+ * @fw_addr:	ELF image address
+ * @fw_size:	size of the ELF image
+ * @rsc_addr:	pointer to the found resource table address. Updated on
+ *		operation success
+ * @rsc_size:	pointer to the found resource table size. Updated on operation
+ *		success
+ *
+ * @return 0 if a valid resource table is successfully loaded, -ENODATA if there
+ * is no resource table (which is optional), or another appropriate error value.
+ */
+int rproc_elf64_load_rsc_table(struct udevice *dev, ulong fw_addr,
+			       ulong fw_size, ulong *rsc_addr, ulong *rsc_size);
+/**
+ * rproc_elf_load_rsc_table() - load the resource table from an ELF image
+ *
+ * Auto detects if the image is ELF32 or ELF64 image and search accordingly for
+ * the resource table, and if found, copy it to device memory.
+ *
+ * @dev:	device loading the resource table
+ * @fw_addr:	ELF image address
+ * @fw_size:	size of the ELF image
+ * @rsc_addr:	pointer to the found resource table address. Updated on
+ *		operation success
+ * @rsc_size:	pointer to the found resource table size. Updated on operation
+ *		success
+ *
+ * @return 0 if a valid resource table is successfully loaded, -ENODATA if there
+ * is no resource table (which is optional), or another appropriate error value.
+ */
+int rproc_elf_load_rsc_table(struct udevice *dev, ulong fw_addr,
+			     ulong fw_size, ulong *rsc_addr, ulong *rsc_size);
 #else
 static inline int rproc_init(void) { return -ENOSYS; }
 static inline int rproc_dev_init(int id) { return -ENOSYS; }
@@ -304,6 +362,18 @@ static inline int rproc_elf_load_image(struct udevice *dev, ulong addr,
 { return -ENOSYS; }
 static inline ulong rproc_elf_get_boot_addr(struct udevice *dev, ulong addr)
 { return 0; }
+static inline int rproc_elf32_load_rsc_table(struct udevice *dev, ulong fw_addr,
+					     ulong fw_size, ulong *rsc_addr,
+					     ulong *rsc_size)
+{ return -ENOSYS; }
+static inline int rproc_elf64_load_rsc_table(struct udevice *dev, ulong fw_addr,
+					     ulong fw_size, ulong *rsc_addr,
+					     ulong *rsc_size)
+{ return -ENOSYS; }
+static inline int rproc_elf_load_rsc_table(struct udevice *dev, ulong fw_addr,
+					   ulong fw_size, ulong *rsc_addr,
+					   ulong *rsc_size)
+{ return -ENOSYS; }
 #endif
 
 #endif	/* _RPROC_H_ */
diff --git a/test/dm/remoteproc.c b/test/dm/remoteproc.c
index 1d9a9b3..4067596 100644
--- a/test/dm/remoteproc.c
+++ b/test/dm/remoteproc.c
@@ -103,8 +103,8 @@ static int dm_test_remoteproc_elf(struct unit_test_state *uts)
 		0x00, 0x00, 0x00, 0x08,
 		/* phoff (program header offset @ 0x40)*/
 		0x40, 0x00, 0x00, 0x00,
-		/* shoff (section header offset : none) */
-		0x00, 0x00, 0x00, 0x00,
+		/* shoff (section header offset @ 0x90) */
+		0x90, 0x00, 0x00, 0x00,
 		/* flags */
 		0x00, 0x00, 0x00, 0x00,
 		/* ehsize (elf header size = 0x34) */
@@ -113,16 +113,17 @@ static int dm_test_remoteproc_elf(struct unit_test_state *uts)
 		0x20, 0x00,
 		/* phnum (program header number : 1) */
 		0x01, 0x00,
-		/* shentsize (section heade size : none) */
-		0x00, 0x00,
-		/* shnum (section header number: none) */
-		0x00, 0x00,
-		/* shstrndx (section header name section index: none) */
-		0x00, 0x00,
+		/* shentsize (section header size : 40 bytes) */
+		0x28, 0x00,
+		/* shnum (section header number: 3) */
+		0x02, 0x00,
+		/* shstrndx (section header name section index: 1) */
+		0x01, 0x00,
 		/* padding */
 		0x00, 0x00, 0x00, 0x00,
 		0x00, 0x00, 0x00, 0x00,
 		0x00, 0x00, 0x00, 0x00,
+
 		/* @0x40 - PROGRAM HEADER TABLE - */
 		/* type : PT_LOAD */
 		0x01, 0x00, 0x00, 0x00,
@@ -140,14 +141,63 @@ static int dm_test_remoteproc_elf(struct unit_test_state *uts)
 		0x05, 0x00, 0x00, 0x00,
 		/* padding */
 		0x00, 0x00, 0x00, 0x00,
+
+		/* @0x60 - RESOURCE TABLE SECTION - */
+		/* version */
+		0x01, 0x00, 0x00, 0x00,
+		/* num (0, no entries) */
+		0x00, 0x00, 0x00, 0x00,
+		/* Reserved */
+		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+
+		/* @0x70 - SECTION'S NAMES SECTION - */
+		/* section 0 name (".shrtrtab") */
+		0x2e, 0x73, 0x68, 0x73, 0x74, 0x72, 0x74, 0x61, 0x62, 0x00,
+		/* section 1 name (".resource_table") */
+		0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f,
+		0x74, 0x61, 0x62, 0x6c, 0x65, 0x00,
+		/* padding */
+		0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+
+		/* @0x90 - SECTION HEADER TABLE - */
+		/* Section 0 : resource table header */
+		/* sh_name - index into section header string table section */
+		0x0a, 0x00, 0x00, 0x00,
+		/* sh_type and sh_flags */
+		0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
+		/* sh_addr = where the resource table has to be copied to */
+		0x00, 0x00, 0x00, 0x00,
+		/* sh_offset = 0x60 */
+		0x60, 0x00, 0x00, 0x00,
+		/* sh_size = 16 bytes */
+		0x10, 0x00, 0x00, 0x00,
+		/* sh_link, sh_info, sh_addralign, sh_entsize */
+		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+		/* Section 1 : section's names section header */
+		/* sh_name - index into section header string table section */
+		0x00, 0x00, 0x00, 0x00,
+		/* sh_type and sh_flags */
+		0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+		/* sh_addr  */
+		0x00, 0x00, 0x00, 0x00,
+		/* sh_offset = 0x70 */
+		0x70, 0x00, 0x00, 0x00,
+		/* sh_size = 27 bytes */
+		0x1b, 0x00, 0x00, 0x00,
+		/* sh_link, sh_info, sh_addralign, sh_entsize */
+		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
 	};
 	unsigned int size = ARRAY_SIZE(valid_elf32);
 	struct udevice *dev;
-	phys_addr_t loaded_firmware_paddr;
-	void *loaded_firmware;
-	u32 loaded_firmware_size;
+	phys_addr_t loaded_firmware_paddr, loaded_rsc_table_paddr;
+	void *loaded_firmware, *loaded_rsc_table;
+	u32 loaded_firmware_size, rsc_table_size;
+	ulong rsc_addr, rsc_size;
 	Elf32_Ehdr *ehdr = (Elf32_Ehdr *)valid_elf32;
 	Elf32_Phdr *phdr = (Elf32_Phdr *)(valid_elf32 + ehdr->e_phoff);
+	Elf32_Shdr *shdr = (Elf32_Shdr *)(valid_elf32 + ehdr->e_shoff);
 
 	ut_assertok(uclass_get_device(UCLASS_REMOTEPROC, 0, &dev));
 
@@ -178,6 +228,25 @@ static int dm_test_remoteproc_elf(struct unit_test_state *uts)
 		    0x08000000);
 	unmap_physmem(loaded_firmware, MAP_NOCACHE);
 
+	/* Resource table */
+	shdr->sh_addr = CONFIG_SYS_SDRAM_BASE;
+	rsc_table_size = shdr->sh_size;
+
+	loaded_rsc_table_paddr = shdr->sh_addr + DEVICE_TO_PHYSICAL_OFFSET;
+	loaded_rsc_table = map_physmem(loaded_rsc_table_paddr,
+				       rsc_table_size, MAP_NOCACHE);
+	ut_assertnonnull(loaded_rsc_table);
+	memset(loaded_rsc_table, 0, rsc_table_size);
+
+	/* Load and verify */
+	ut_assertok(rproc_elf32_load_rsc_table(dev, (ulong)valid_elf32, size,
+					       &rsc_addr, &rsc_size));
+	ut_asserteq(rsc_addr, CONFIG_SYS_SDRAM_BASE);
+	ut_asserteq(rsc_size, rsc_table_size);
+	ut_assertok(memcmp(loaded_firmware, valid_elf32 + shdr->sh_offset,
+			   shdr->sh_size));
+	unmap_physmem(loaded_firmware, MAP_NOCACHE);
+
 	/* Invalid ELF Magic */
 	valid_elf32[0] = 0;
 	ut_asserteq(-EPROTONOSUPPORT,
-- 
2.7.4

  reply	other threads:[~2019-10-30 13:38 UTC|newest]

Thread overview: 20+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2019-10-30 13:38 [U-Boot] [PATCH v2 0/6] remoteproc: add elf resource table loader Fabien Dessenne
2019-10-30 13:38 ` Fabien Dessenne [this message]
2019-11-25 17:04   ` [U-Boot] [PATCH v2 1/6] remoteproc: elf_loader: Add elf resource table load support Patrick DELAUNAY
2019-11-26  3:14   ` Lokesh Vutla
2020-01-08 20:11   ` Tom Rini
2019-10-30 13:38 ` [U-Boot] [PATCH v2 2/6] stm32mp1: declare backup registers for coprocessor Fabien Dessenne
2019-11-25 16:54   ` Patrick DELAUNAY
2020-01-08 20:11   ` Tom Rini
2019-10-30 13:38 ` [U-Boot] [PATCH v2 3/6] stm32mp1: reset coprocessor status at cold boot Fabien Dessenne
2019-11-25 16:55   ` Patrick DELAUNAY
2020-01-08 20:11   ` Tom Rini
2019-10-30 13:38 ` [U-Boot] [PATCH v2 4/6] remoteproc: stm32: track the coprocessor state in a backup register Fabien Dessenne
2019-11-25 16:56   ` Patrick DELAUNAY
2020-01-08 20:12   ` Tom Rini
2019-10-30 13:38 ` [U-Boot] [PATCH v2 5/6] stm32mp1: remove copro_state environment variable Fabien Dessenne
2019-11-25 16:56   ` Patrick DELAUNAY
2020-01-08 20:12   ` Tom Rini
2019-10-30 13:38 ` [U-Boot] [PATCH v2 6/6] remoteproc: stm32: load resource table from firmware Fabien Dessenne
2019-11-25 16:56   ` Patrick DELAUNAY
2020-01-08 20:12   ` Tom Rini

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=1572442713-26353-2-git-send-email-fabien.dessenne@st.com \
    --to=fabien.dessenne@st.com \
    --cc=u-boot@lists.denx.de \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.