[PATCH] drivers: mdt_loader: Add parallel blobs loading capability

From: Rishabh Bhatnagar
Date: Wed Dec 09 2020 - 21:35:20 EST


Add capability to load blobs parallely during loading
of firmware. Create a high priority unbound workqueue and
schedule work items to load the firmware blobs parallely.
This helps in improving firmware loading times.

Signed-off-by: Rishabh Bhatnagar <rishabhb@xxxxxxxxxxxxxx>
---
drivers/soc/qcom/mdt_loader.c | 147 ++++++++++++++++++++++++++++++------------
1 file changed, 107 insertions(+), 40 deletions(-)

diff --git a/drivers/soc/qcom/mdt_loader.c b/drivers/soc/qcom/mdt_loader.c
index 24cd193..4e5d2aa 100644
--- a/drivers/soc/qcom/mdt_loader.c
+++ b/drivers/soc/qcom/mdt_loader.c
@@ -17,6 +17,26 @@
#include <linux/slab.h>
#include <linux/soc/qcom/mdt_loader.h>

+static struct workqueue_struct *mdt_wq;
+
+struct fw_desc {
+ size_t mem_size;
+ void *mem_region;
+ const struct firmware *fw;
+ char *fw_name;
+ struct device *dev;
+ size_t fw_name_len;
+ phys_addr_t mem_reloc;
+};
+
+struct mdt_seg_data {
+ struct work_struct load_seg_work;
+ const struct elf32_phdr *phdr;
+ int seg_num;
+ int result;
+ struct fw_desc *desc;
+};
+
static bool mdt_phdr_valid(const struct elf32_phdr *phdr)
{
if (phdr->p_type != PT_LOAD)
@@ -126,6 +146,62 @@ void *qcom_mdt_read_metadata(const struct firmware *fw, size_t *data_len)
}
EXPORT_SYMBOL_GPL(qcom_mdt_read_metadata);

+static void mdt_load_seg_work_fn(struct work_struct *work)
+{
+ struct mdt_seg_data *mdt_seg_data = container_of(work, struct mdt_seg_data, load_seg_work);
+ struct fw_desc *desc = mdt_seg_data->desc;
+ int seg_num = mdt_seg_data->seg_num;
+ const struct elf32_phdr *phdr = mdt_seg_data->phdr;
+ const struct firmware *seg_fw;
+ ssize_t offset;
+ void *ptr;
+ int ret;
+ char *fw_name = kstrdup(desc->fw_name, GFP_KERNEL);
+
+ if (!mdt_phdr_valid(phdr))
+ goto fw_free;
+
+ offset = phdr->p_paddr - desc->mem_reloc;
+ if (offset < 0 || offset + phdr->p_memsz > desc->mem_size) {
+ dev_err(desc->dev, "segment outside memory range\n");
+ mdt_seg_data->result = -EINVAL;
+ goto fw_free;
+ }
+
+ ptr = desc->mem_region + offset;
+
+ if (phdr->p_filesz && phdr->p_offset < desc->fw->size) {
+ /* Firmware is large enough to be non-split */
+ if (phdr->p_offset + phdr->p_filesz > desc->fw->size) {
+ dev_err(desc->dev,
+ "failed to load segment %d from truncated file %s\n",
+ seg_num, desc->fw_name);
+ mdt_seg_data->result = -EINVAL;
+ goto fw_free;
+ }
+
+ memcpy(ptr, desc->fw->data + phdr->p_offset, phdr->p_filesz);
+ } else if (phdr->p_filesz) {
+ /* Firmware not large enough, load split-out segments */
+ sprintf(fw_name + desc->fw_name_len - 3, "b%02d", seg_num);
+ ret = request_firmware_into_buf(&seg_fw, fw_name, desc->dev,
+ ptr, phdr->p_filesz);
+ if (ret) {
+ dev_err(desc->dev, "failed to load %s\n", fw_name);
+ mdt_seg_data->result = -EINVAL;
+ goto fw_free;
+ }
+
+ release_firmware(seg_fw);
+ }
+
+ if (phdr->p_memsz > phdr->p_filesz)
+ memset(ptr + phdr->p_filesz, 0, phdr->p_memsz - phdr->p_filesz);
+
+fw_free:
+ kfree(fw_name);
+}
+
static int __qcom_mdt_load(struct device *dev, const struct firmware *fw,
const char *firmware, int pas_id, void *mem_region,
phys_addr_t mem_phys, size_t mem_size,
@@ -134,19 +210,18 @@ static int __qcom_mdt_load(struct device *dev, const struct firmware *fw,
const struct elf32_phdr *phdrs;
const struct elf32_phdr *phdr;
const struct elf32_hdr *ehdr;
- const struct firmware *seg_fw;
+ struct mdt_seg_data *segs;
phys_addr_t mem_reloc;
phys_addr_t min_addr = PHYS_ADDR_MAX;
phys_addr_t max_addr = 0;
size_t metadata_len;
size_t fw_name_len;
- ssize_t offset;
void *metadata;
char *fw_name;
bool relocate = false;
- void *ptr;
int ret = 0;
int i;
+ struct fw_desc *desc = kzalloc(sizeof(*desc), GFP_KERNEL);

if (!fw || !mem_region || !mem_phys || !mem_size)
return -EINVAL;
@@ -217,54 +292,46 @@ static int __qcom_mdt_load(struct device *dev, const struct firmware *fw,
mem_reloc = mem_phys;
}

- for (i = 0; i < ehdr->e_phnum; i++) {
- phdr = &phdrs[i];
+ if (!mdt_wq)
+ mdt_wq = alloc_workqueue("mdt_workqueue", WQ_HIGHPRI | WQ_UNBOUND, 0);

- if (!mdt_phdr_valid(phdr))
- continue;
+ segs = kcalloc(ehdr->e_phnum, sizeof(struct mdt_seg_data), GFP_KERNEL);

- offset = phdr->p_paddr - mem_reloc;
- if (offset < 0 || offset + phdr->p_memsz > mem_size) {
- dev_err(dev, "segment outside memory range\n");
- ret = -EINVAL;
- break;
- }
+ /* Fill in the firmware descriptor to be used by individual work items */
+ desc->mem_size = mem_size;
+ desc->mem_region = mem_region;
+ desc->fw = fw;
+ desc->fw_name_len = fw_name_len;
+ desc->fw_name = fw_name;
+ desc->dev = dev;
+ desc->mem_reloc = mem_reloc;

- ptr = mem_region + offset;
-
- if (phdr->p_filesz && phdr->p_offset < fw->size) {
- /* Firmware is large enough to be non-split */
- if (phdr->p_offset + phdr->p_filesz > fw->size) {
- dev_err(dev,
- "failed to load segment %d from truncated file %s\n",
- i, firmware);
- ret = -EINVAL;
- break;
- }
-
- memcpy(ptr, fw->data + phdr->p_offset, phdr->p_filesz);
- } else if (phdr->p_filesz) {
- /* Firmware not large enough, load split-out segments */
- sprintf(fw_name + fw_name_len - 3, "b%02d", i);
- ret = request_firmware_into_buf(&seg_fw, fw_name, dev,
- ptr, phdr->p_filesz);
- if (ret) {
- dev_err(dev, "failed to load %s\n", fw_name);
- break;
- }
-
- release_firmware(seg_fw);
- }
+ /* Queue in individual work item for each firmware blob */
+ for (i = 0; i < ehdr->e_phnum; i++) {
+ INIT_WORK(&segs[i].load_seg_work, mdt_load_seg_work_fn);
+ segs[i].seg_num = i;
+ segs[i].phdr = &phdrs[i];
+ segs[i].desc = desc;
+ queue_work(mdt_wq, &segs[i].load_seg_work);
+ }

- if (phdr->p_memsz > phdr->p_filesz)
- memset(ptr + phdr->p_filesz, 0, phdr->p_memsz - phdr->p_filesz);
+ /* Wait for the parallel loads to finish */
+ for (i = 0; i < ehdr->e_phnum; i++) {
+ flush_work(&segs[i].load_seg_work);
+ ret |= segs[i].result;
}

+ /* Different blobs can fail with different errors, so return a generic error */
+ if (ret)
+ ret = -EFAULT;
+
if (reloc_base)
*reloc_base = mem_reloc;

out:
kfree(fw_name);
+ kfree(desc);
+ kfree(segs);

return ret;
}
--
The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum,
a Linux Foundation Collaborative Project