Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit a64142eb authored by Sanjay Dwivedi's avatar Sanjay Dwivedi
Browse files

soc: qcom: pil: Add support to load PIL blobs in parallel



Loading of the subsystem firmware blobs into the memory is
one of the major bottlenecks during the subsystem's boot
operation. Hence, optimizing the path such that the blobs
are loaded in parallel and making the operation faster.
Fix use-after-free issue when loading firmware images.

Currently, the function pil_load_segs() allocates memory, creates
kthreads, and calls wait_for_completion_interruptible() to synchronize
the threads. However, if the completion received a terminate signal,
the allocated memory that is being used by the threads (under execution)
is freed. When the thread(s) tries to access this memory, it leads to
a use-after-free issue.

As a solution, replace the kthreads and completions with work items
as this provides an implicit synchronization. After spawning the
work items, we simply now call flush_work() which waits until
the thread's execution is finished, thus avoiding the use-after-free
issue. The PIL code creates it own high priority & unbounded workqueue
to schedule the work items.

Change-Id: I6a28f46a53c05e16e5f29ad975879e336a16f0cd
Signed-off-by: default avatarSanjay Dwivedi <sanjaykd@codeaurora.org>
parent 91c82cb6
Loading
Loading
Loading
Loading
+6 −0
Original line number Diff line number Diff line
@@ -70,6 +70,12 @@ Optional properties:
- qcom,ignore-ssr-failure: Boolean. If set, SSR failures are not considered fatal.
- qcom,mas-crypto: Reference to the bus master of crypto core.

- qcom,sequential-fw-load: Boolean. If set, PIL loads the firmware image blobs in a
				serial fashion. Else, they are loaded in
				parallel. The property is specially useful for
				low-end (single core) systems to prevent it from
				degrading the performance.

Example:
	qcom,venus@fdce0000 {
		compatible = "qcom,pil-tz-generic";
+111 −4
Original line number Diff line number Diff line
/* Copyright (c) 2010-2018, The Linux Foundation. All rights reserved.
/* Copyright (c) 2010-2019, The Linux Foundation. All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 and
@@ -36,6 +36,7 @@
#include <soc/qcom/subsystem_restart.h>
#include <soc/qcom/secure_buffer.h>
#include <soc/qcom/smem.h>
#include <linux/kthread.h>

#include <linux/uaccess.h>
#include <asm/setup.h>
@@ -70,6 +71,9 @@ static int proxy_timeout_ms = -1;
module_param(proxy_timeout_ms, int, 0644);

static bool disable_timeouts;

static struct workqueue_struct *pil_wq;

/**
 * struct pil_mdt - Representation of <name>.mdt file in memory
 * @hdr: ELF32 header
@@ -127,6 +131,7 @@ struct pil_priv {
	struct wakeup_source ws;
	char wname[32];
	struct pil_desc *desc;
	int num_segs;
	struct list_head segs;
	phys_addr_t entry_addr;
	phys_addr_t base_addr;
@@ -723,6 +728,7 @@ static int pil_init_mmap(struct pil_desc *desc, const struct pil_mdt *mdt,
	pil_info(desc, "loading from %pa to %pa\n", &priv->region_start,
							&priv->region_end);

	priv->num_segs = 0;
	for (i = 0; i < mdt->hdr.e_phnum; i++) {
		phdr = &mdt->phdr[i];
		if (!segment_is_loadable(phdr))
@@ -733,6 +739,7 @@ static int pil_init_mmap(struct pil_desc *desc, const struct pil_mdt *mdt,
			return PTR_ERR(seg);

		list_add_tail(&seg->list, &priv->segs);
		priv->num_segs++;
	}
	list_sort(NULL, &priv->segs, pil_cmp_seg);

@@ -922,6 +929,9 @@ static int pil_parse_devicetree(struct pil_desc *desc)
		}
	}
	desc->proxy_unvote_irq = clk_ready;

	desc->sequential_load = of_property_read_bool(ofnode,
						"qcom,sequential-fw-load");
	return 0;
}

@@ -945,6 +955,89 @@ static int pil_notify_aop(struct pil_desc *desc, char *status)
/* Synchronize request_firmware() with suspend */
static DECLARE_RWSEM(pil_pm_rwsem);

struct pil_seg_data {
	struct pil_desc *desc;
	struct pil_seg *seg;
	struct work_struct load_seg_work;
	int retval;
};

static void pil_load_seg_work_fn(struct work_struct *work)
{
	struct pil_seg_data *pil_seg_data = container_of(work,
							struct pil_seg_data,
							load_seg_work);
	struct pil_desc *desc = pil_seg_data->desc;
	struct pil_seg *seg = pil_seg_data->seg;

	pil_seg_data->retval = pil_load_seg(desc, seg);
}

static int pil_load_segs(struct pil_desc *desc)
{
	int ret = 0;
	int seg_id = 0;
	struct pil_priv *priv = desc->priv;
	struct pil_seg_data *pil_seg_data;
	struct pil_seg *seg;
	unsigned long *err_map;

	err_map = kcalloc(BITS_TO_LONGS(priv->num_segs), sizeof(unsigned long),
				GFP_KERNEL);
	if (!err_map)
		return -ENOMEM;

	pil_seg_data = kcalloc(priv->num_segs, sizeof(*pil_seg_data),
				GFP_KERNEL);
	if (!pil_seg_data) {
		ret = -ENOMEM;
		goto out;
	}

	/* Initialize and spawn a thread for each segment */
	list_for_each_entry(seg, &desc->priv->segs, list) {
		pil_seg_data[seg_id].desc = desc;
		pil_seg_data[seg_id].seg = seg;

		INIT_WORK(&pil_seg_data[seg_id].load_seg_work,
				pil_load_seg_work_fn);
		queue_work(pil_wq, &pil_seg_data[seg_id].load_seg_work);

		seg_id++;
	}

	bitmap_zero(err_map, priv->num_segs);

	/* Wait for the parallel loads to finish */
	seg_id = 0;
	list_for_each_entry(seg, &desc->priv->segs, list) {
		flush_work(&pil_seg_data[seg_id].load_seg_work);

		/*
		 * Don't exit if one of the thread fails. Wait for others to
		 * complete. Bitmap the return codes we get from the threads.
		 */
		if (pil_seg_data[seg_id].retval) {
			pil_err(desc,
				"Failed to load the segment[%d]. ret = %d\n",
				seg_id, pil_seg_data[seg_id].retval);
			__set_bit(seg_id, err_map);
		}

		seg_id++;
	}

	kfree(pil_seg_data);

	/* Each segment can fail due to different reason. Send a generic err */
	if (!bitmap_empty(err_map, priv->num_segs))
		ret = -EFAULT;

out:
	kfree(err_map);
	return ret;
}

/**
 * pil_boot() - Load a peripheral image into memory and boot it
 * @desc: descriptor from pil_desc_init()
@@ -955,9 +1048,9 @@ int pil_boot(struct pil_desc *desc)
{
	int ret;
	char fw_name[30];
	struct pil_seg *seg;
	const struct pil_mdt *mdt;
	const struct elf32_hdr *ehdr;
	struct pil_seg *seg;
	const struct firmware *fw;
	struct pil_priv *priv = desc->priv;
	bool mem_protect = false;
@@ -1066,11 +1159,18 @@ int pil_boot(struct pil_desc *desc)
	}

	trace_pil_event("before_load_seg", desc);

	if (desc->sequential_load) {
		list_for_each_entry(seg, &desc->priv->segs, list) {
			ret = pil_load_seg(desc, seg);
			if (ret)
				goto err_deinit_image;
		}
	} else {
		ret = pil_load_segs(desc);
		if (ret)
			goto err_deinit_image;
	}

	if (desc->subsys_vmid > 0) {
		trace_pil_event("before_reclaim_mem", desc);
@@ -1364,6 +1464,11 @@ static int __init msm_pil_init(void)
		pr_err("SMEM is not initialized.\n");
		return -EPROBE_DEFER;
	}

	pil_wq = alloc_workqueue("pil_workqueue", WQ_HIGHPRI | WQ_UNBOUND, 0);
	if (!pil_wq)
		pr_warn("pil: Defaulting to sequential firmware loading.\n");

out:
	return register_pm_notifier(&pil_pm_notifier);
}
@@ -1371,6 +1476,8 @@ device_initcall(msm_pil_init);

static void __exit msm_pil_exit(void)
{
	if (pil_wq)
		destroy_workqueue(pil_wq);
	unregister_pm_notifier(&pil_pm_notifier);
	if (pil_info_base)
		iounmap(pil_info_base);
+4 −1
Original line number Diff line number Diff line
/* Copyright (c) 2010-2018, The Linux Foundation. All rights reserved.
/* Copyright (c) 2010-2019, The Linux Foundation. All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 and
@@ -41,6 +41,8 @@ struct pil_priv;
 * @modem_ssr: true if modem is restarting, false if booting for first time.
 * @clear_fw_region: Clear fw region on failure in loading.
 * @subsys_vmid: memprot id for the subsystem.
 * @sequential_load: Load the firmware blobs sequentially if set. Else, load
 * them in parallel.
 */
struct pil_desc {
	const char *name;
@@ -67,6 +69,7 @@ struct pil_desc {
	struct md_ss_toc *minidump_ss;
	struct md_ss_toc *minidump_pdr;
	int minidump_id;
	bool sequential_load;
};

/**