Re: [PATCH V2 10/13] remoteproc: qcom: Add Hexagon based multipd rproc driver

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On 22/05/2023 00:28, Manikanta Mylavarapu wrote:
> It adds support to bring up remoteproc's on multipd model.
> Pd means protection domain. It's similar to process in Linux.
> Here QDSP6 processor runs each wifi radio functionality on a
> separate process. One process can't access other process
> resources, so this is termed as PD i.e protection domain.
> 
> Here we have two pd's called root and user pd. We can correlate
> Root pd as root and user pd as user in linux. Root pd has more
> privileges than user pd. Root will provide services to user pd.
> 
> From remoteproc driver perspective, root pd corresponds to QDSP6
> processor bring up and user pd corresponds to Wifi radio (WCSS)
> bring up.
> 
> Here WCSS(user) PD is dependent on Q6(root) PD, so first
> q6 pd should be up before wcss pd. After wcss pd goes down,
> q6 pd should be turned off.
> 
> IPQ5018, IPQ9574 supports multipd remoteproc driver.
> 
> Signed-off-by: Manikanta Mylavarapu <quic_mmanikan@xxxxxxxxxxx>
> ---
> Changes in V2:
> 	- Common functionalities moved to seperate patches
> 	- qcom_get_pd_asid() moved to mpd driver
> 	- Last DMA block alone memset to zero
> 	- Added diagram to show how userpd data is organized and sent to
> 	  trustzone
> 	- Rewritten commit message since most of the content available
> 	  in cover page
> 	- Removed 'remote_id' becuase it's not required for bring up.
> 
>  drivers/remoteproc/Kconfig          |  20 +
>  drivers/remoteproc/Makefile         |   1 +
>  drivers/remoteproc/qcom_common.h    |   9 +
>  drivers/remoteproc/qcom_q6v5_mpd.c  | 677 ++++++++++++++++++++++++++++
>  drivers/soc/qcom/mdt_loader.c       | 332 ++++++++++++++
>  include/linux/soc/qcom/mdt_loader.h |  19 +
>  6 files changed, 1058 insertions(+)
>  create mode 100644 drivers/remoteproc/qcom_q6v5_mpd.c
> 
> diff --git a/drivers/remoteproc/Kconfig b/drivers/remoteproc/Kconfig
> index a850e9f486dd..44af5c36f67e 100644
> --- a/drivers/remoteproc/Kconfig
> +++ b/drivers/remoteproc/Kconfig
> @@ -234,6 +234,26 @@ config QCOM_Q6V5_PAS
>  	  CDSP (Compute DSP), MPSS (Modem Peripheral SubSystem), and
>  	  SLPI (Sensor Low Power Island).
> 
> +config QCOM_Q6V5_MPD
> +	tristate "Qualcomm Hexagon based MPD model Peripheral Image Loader"
> +	depends on OF && ARCH_QCOM
> +	depends on QCOM_SMEM
> +	depends on RPMSG_QCOM_SMD || RPMSG_QCOM_SMD=n
> +	depends on RPMSG_QCOM_GLINK_SMEM || RPMSG_QCOM_GLINK_SMEM=n
> +	depends on QCOM_SYSMON || QCOM_SYSMON=n
> +	depends on RPMSG_QCOM_GLINK || RPMSG_QCOM_GLINK=n
> +	depends on QCOM_AOSS_QMP || QCOM_AOSS_QMP=n
> +	select MFD_SYSCON

Do you really need this?

> +	select QCOM_MDT_LOADER
> +	select QCOM_PIL_INFO
> +	select QCOM_Q6V5_COMMON
> +	select QCOM_RPROC_COMMON
> +	select QCOM_SCM
> +	help
> +	  Say y here to support the Qualcomm Secure Peripheral Image Loader
> +	  for the Hexagon based MultiPD model remote processors on e.g. IPQ5018.
> +	  This is trustZone wireless subsystem.
> +

...

> +	int (*powerup_scm)(u32 peripheral);
> +	int (*powerdown_scm)(u32 peripheral);
> +};
> +
> +/**
> + * qcom_get_pd_asid() - get the pd asid number from DT node

>From node name? NAK. It does not work like that. Node names can change
and you did not define this number as part of ABI.

Probably you wanted unit address.

> + * @node:	device tree node
> + *
> + * Returns asid if node name has 'pd' string
> + */
> +s8 qcom_get_pd_asid(struct device_node *node)
> +{
> +	char *str;
> +	s8 pd_asid;
> +
> +	if (!node)
> +		return -EINVAL;
> +
> +	str = strstr(node->name, "pd");
> +	if (!str)
> +		return 0;
> +
> +	str += strlen("pd") + 1;
> +	return kstrtos8(str, 10, &pd_asid) ? -EINVAL : pd_asid;
> +}
> +EXPORT_SYMBOL(qcom_get_pd_asid);
> +

...

> +
> +static int q6_wcss_spawn_pd(struct rproc *rproc)
> +{
> +	int ret;
> +	struct q6_wcss *wcss = rproc->priv;
> +
> +	ret = qcom_q6v5_request_spawn(&wcss->q6);
> +	if (ret == -ETIMEDOUT) {
> +		pr_err("%s spawn timedout\n", rproc->name);

dev_err

> +		return ret;
> +	}
> +
> +	ret = qcom_q6v5_wait_for_start(&wcss->q6, msecs_to_jiffies(10000));
> +	if (ret == -ETIMEDOUT) {
> +		pr_err("%s start timedout\n", rproc->name);

dev_err

> +		wcss->q6.running = false;
> +		return ret;
> +	}
> +	wcss->q6.running = true;
> +	return ret;
> +}
> +
> +static int wcss_ahb_pcie_pd_start(struct rproc *rproc)
> +{
> +	struct q6_wcss *wcss = rproc->priv;
> +	const struct wcss_data *desc = wcss->desc;
> +	int ret;
> +
> +	if (!desc->reset_seq)
> +		return 0;
> +
> +	if (desc->powerup_scm) {
> +		ret = desc->powerup_scm(desc->pasid);
> +		if (ret) {
> +			dev_err(wcss->dev, "failed to power up pd\n");
> +			return ret;
> +		}
> +	}
> +
> +	ret = q6_wcss_spawn_pd(rproc);
> +	if (ret)
> +		return ret;
> +
> +	wcss->state = WCSS_NORMAL;
> +	return ret;
> +}
> +
> +static int q6_wcss_stop(struct rproc *rproc)
> +{
> +	struct q6_wcss *wcss = rproc->priv;
> +	const struct wcss_data *desc = wcss->desc;
> +	int ret;
> +
> +	ret = qcom_scm_pas_shutdown(desc->pasid);
> +	if (ret) {
> +		dev_err(wcss->dev, "not able to shutdown\n");
> +		return ret;
> +	}
> +	qcom_q6v5_unprepare(&wcss->q6);
> +
> +	return 0;
> +}
> +
> +static int wcss_ahb_pcie_pd_stop(struct rproc *rproc)
> +{
> +	struct q6_wcss *wcss = rproc->priv;
> +	struct rproc *rpd_rproc = dev_get_drvdata(wcss->dev->parent);
> +	const struct wcss_data *desc = wcss->desc;
> +	int ret;
> +
> +	if (!desc->reset_seq)
> +		goto shut_down_rpd;
> +
> +	if (rproc->state != RPROC_CRASHED && wcss->q6.stop_bit) {
> +		ret = qcom_q6v5_request_stop(&wcss->q6, NULL);
> +		if (ret) {
> +			dev_err(&rproc->dev, "pd not stopped\n");
> +			return ret;
> +		}
> +	}
> +
> +	if (desc->powerdown_scm) {
> +		ret = desc->powerdown_scm(desc->pasid);
> +		if (ret) {
> +			dev_err(wcss->dev, "failed to power down pd\n");
> +			return ret;
> +		}
> +	}
> +
> +shut_down_rpd:
> +	rproc_shutdown(rpd_rproc);
> +
> +	wcss->state = WCSS_SHUTDOWN;
> +	return 0;
> +}
> +
> +static void *q6_wcss_da_to_va(struct rproc *rproc, u64 da, size_t len,
> +			      bool *is_iomem)
> +{
> +	struct q6_wcss *wcss = rproc->priv;
> +	int offset;
> +
> +	offset = da - wcss->mem_reloc;
> +	if (offset < 0 || offset + len > wcss->mem_size)
> +		return NULL;
> +
> +	return wcss->mem_region + offset;
> +}
> +
> +static int q6_wcss_load(struct rproc *rproc, const struct firmware *fw)
> +{
> +	struct q6_wcss *wcss = rproc->priv;
> +	const struct firmware *fw_hdl;
> +	int ret;
> +	const struct wcss_data *desc = wcss->desc;
> +	int loop;
> +
> +	ret = qcom_mdt_load(wcss->dev, fw, rproc->firmware,
> +			    desc->pasid, wcss->mem_region,
> +			    wcss->mem_phys, wcss->mem_size,
> +			    &wcss->mem_reloc);
> +	if (ret)
> +		return ret;
> +
> +	for (loop = 1; loop < MAX_FIRMWARE; loop++) {
> +		if (!wcss->firmware[loop])
> +			continue;
> +
> +		ret = request_firmware(&fw_hdl, wcss->firmware[loop],
> +				       wcss->dev);
> +		if (ret)
> +			continue;
> +
> +		ret = qcom_mdt_load_no_init(wcss->dev, fw_hdl,
> +					    wcss->firmware[loop], 0,
> +					    wcss->mem_region,
> +					    wcss->mem_phys,
> +					    wcss->mem_size,
> +					    &wcss->mem_reloc);
> +
> +		release_firmware(fw_hdl);
> +
> +		if (ret) {
> +			dev_err(wcss->dev,
> +				"can't load %s ret:%d\n", wcss->firmware[loop], ret);
> +			return ret;
> +		}
> +	}
> +	return 0;
> +}
> +
> +/* This function load's userpd firmware. Since Userpd depends on rootpd

Use Linux coding style comments.

> + * first bring up root pd and then load. User pd firmware load is required
> + * only during user pd restart because root pd loads user pd FW pil segments
> + * during it's bringup.
> + */
> +static int wcss_ahb_pcie_pd_load(struct rproc *rproc, const struct firmware *fw)
> +{
> +	struct q6_wcss *wcss = rproc->priv, *wcss_rpd;
> +	struct rproc *rpd_rproc = dev_get_drvdata(wcss->dev->parent);
> +	const struct wcss_data *desc = wcss->desc;
> +	int ret;
> +
> +	wcss_rpd = rpd_rproc->priv;
> +
> +	/* Boot rootpd rproc */

That's not helpful - i tcopies the function name.

> +	ret = rproc_boot(rpd_rproc);
> +	if (ret || wcss->state == WCSS_NORMAL)
> +		return ret;
> +
> +	return desc->mdt_load_sec(wcss->dev, fw, rproc->firmware,
> +				  desc->pasid, wcss->mem_region,
> +				  wcss->mem_phys, wcss->mem_size,
> +				  &wcss->mem_reloc);
> +}
> +

...

> +
> +static int q6_get_inbound_irq(struct qcom_q6v5 *q6,
> +			      struct platform_device *pdev,
> +			      const char *int_name,
> +			      irqreturn_t (*handler)(int irq, void *data))
> +{
> +	int ret, irq;
> +	char *interrupt, *tmp = (char *)int_name;
> +	struct q6_wcss *wcss = q6->rproc->priv;
> +
> +	irq = platform_get_irq_byname(pdev, int_name);
> +	if (irq < 0) {
> +		if (irq != -EPROBE_DEFER)
> +			dev_err(&pdev->dev,
> +				"failed to retrieve %s IRQ: %d\n",
> +					int_name, irq);

return dev_err_probe

> +		return irq;
> +	}
> +
> +	if (!strcmp(int_name, "fatal")) {
> +		q6->fatal_irq = irq;
> +	} else if (!strcmp(int_name, "stop-ack")) {
> +		q6->stop_irq = irq;
> +		tmp = "stop_ack";
> +	} else if (!strcmp(int_name, "ready")) {
> +		q6->ready_irq = irq;
> +	} else if (!strcmp(int_name, "handover")) {
> +		q6->handover_irq  = irq;
> +	} else if (!strcmp(int_name, "spawn-ack")) {
> +		q6->spawn_irq = irq;
> +		tmp = "spawn_ack";
> +	} else {
> +		dev_err(&pdev->dev, "unknown interrupt\n");
> +		return -EINVAL;
> +	}

This is over complicated method of getting interrupts. First, you can
pass here address of variable with interrupt to assign (*irq_field =
irq). Second, drop the names and get by index. Entries are fixed, not
flexible.

> +
> +	interrupt = devm_kzalloc(&pdev->dev, BUF_SIZE, GFP_KERNEL);
> +	if (!interrupt)
> +		return -ENOMEM;
> +
> +	snprintf(interrupt, BUF_SIZE, "q6v5_wcss_userpd%d_%s", wcss->pd_asid, tmp);
> +
> +	ret = devm_request_threaded_irq(&pdev->dev, irq,
> +					NULL, handler,
> +					IRQF_TRIGGER_RISING | IRQF_ONESHOT,
> +					interrupt, q6);
> +	if (ret) {
> +		dev_err(&pdev->dev, "failed to acquire %s irq\n", interrupt);
> +		return ret;
> +	}
> +	return 0;
> +}
> +
> +static int q6_get_outbound_irq(struct qcom_q6v5 *q6,
> +			       struct platform_device *pdev,
> +			       const char *int_name)
> +{
> +	struct qcom_smem_state *tmp_state;
> +	unsigned  bit;
> +
> +	tmp_state = qcom_smem_state_get(&pdev->dev, int_name, &bit);
> +	if (IS_ERR(tmp_state)) {
> +		dev_err(&pdev->dev, "failed to acquire %s state\n", int_name);
> +		return PTR_ERR(tmp_state);
> +	}
> +
> +	if (!strcmp(int_name, "stop")) {
> +		q6->state = tmp_state;
> +		q6->stop_bit = bit;
> +	} else if (!strcmp(int_name, "spawn")) {
> +		q6->spawn_state = tmp_state;
> +		q6->spawn_bit = bit;
> +	}
> +
> +	return 0;
> +}
> +
> +static int init_irq(struct qcom_q6v5 *q6,
> +		    struct platform_device *pdev, struct rproc *rproc,
> +		    int crash_reason, const char *load_state,
> +		    void (*handover)(struct qcom_q6v5 *q6))
> +{
> +	int ret;
> +
> +	q6->rproc = rproc;
> +	q6->dev = &pdev->dev;
> +	q6->crash_reason = crash_reason;
> +	q6->handover = handover;
> +
> +	init_completion(&q6->start_done);
> +	init_completion(&q6->stop_done);
> +	init_completion(&q6->spawn_done);
> +
> +	ret = q6_get_inbound_irq(q6, pdev, "fatal",
> +				 q6v5_fatal_interrupt);
> +	if (ret)
> +		return ret;
> +
> +	ret = q6_get_inbound_irq(q6, pdev, "ready",
> +				 q6v5_ready_interrupt);
> +	if (ret)
> +		return ret;
> +
> +	ret = q6_get_inbound_irq(q6, pdev, "stop-ack",
> +				 q6v5_stop_interrupt);
> +	if (ret)
> +		return ret;
> +
> +	ret = q6_get_inbound_irq(q6, pdev, "spawn-ack",
> +				 q6v5_spawn_interrupt);
> +	if (ret)
> +		return ret;
> +
> +	ret = q6_get_outbound_irq(q6, pdev, "stop");
> +	if (ret)
> +		return ret;
> +
> +	ret = q6_get_outbound_irq(q6, pdev, "spawn");
> +	if (ret)
> +		return ret;
> +
> +	return 0;
> +}
> +
> +static int q6_wcss_probe(struct platform_device *pdev)
> +{
> +	const struct wcss_data *desc;
> +	struct q6_wcss *wcss;
> +	struct rproc *rproc;
> +	int ret;
> +	char *subdev_name;
> +	const char **firmware;
> +
> +	desc = of_device_get_match_data(&pdev->dev);
> +	if (!desc)
> +		return -EINVAL;
> +
> +	firmware = devm_kcalloc(&pdev->dev, MAX_FIRMWARE,
> +				sizeof(*firmware), GFP_KERNEL);
> +	if (!firmware)
> +		return -ENOMEM;
> +
> +	ret = of_property_read_string_array(pdev->dev.of_node, "firmware-name",
> +					    firmware, MAX_FIRMWARE);
> +	if (ret < 0)
> +		return ret;
> +
> +	rproc = rproc_alloc(&pdev->dev, pdev->name, desc->ops,
> +			    firmware[0], sizeof(*wcss));
> +	if (!rproc) {
> +		dev_err(&pdev->dev, "failed to allocate rproc\n");

ENOMEM do not print errors. Why do you have to print something here?

> +		return -ENOMEM;
> +	}
> +	wcss = rproc->priv;
> +	wcss->dev = &pdev->dev;
> +	wcss->desc = desc;
> +	wcss->firmware = firmware;
> +
> +	ret = q6_alloc_memory_region(wcss);
> +	if (ret)
> +		goto free_rproc;
> +
> +	wcss->pd_asid = qcom_get_pd_asid(wcss->dev->of_node);
> +	if (wcss->pd_asid < 0)
> +		goto free_rproc;
> +
> +	if (desc->init_irq) {
> +		ret = desc->init_irq(&wcss->q6, pdev, rproc,
> +				     desc->crash_reason_smem, NULL, NULL);
> +		if (ret)
> +			goto free_rproc;
> +	}
> +
> +	if (desc->glink_subdev_required)
> +		qcom_add_glink_subdev(rproc, &wcss->glink_subdev, desc->ssr_name);
> +
> +	subdev_name = (char *)(desc->ssr_name ? desc->ssr_name : pdev->name);
> +	qcom_add_ssr_subdev(rproc, &wcss->ssr_subdev, subdev_name);
> +
> +	rproc->auto_boot = false;
> +	ret = rproc_add(rproc);
> +	if (ret)
> +		goto free_rproc;
> +
> +	platform_set_drvdata(pdev, rproc);
> +
> +	ret = of_platform_populate(wcss->dev->of_node, NULL,
> +				   NULL, wcss->dev);

It is the same probe used for the children, right? So whom do they populate?

> +	if (ret) {
> +		dev_err(&pdev->dev, "failed to populate wcss pd nodes\n");

dev_err_probe

> +		goto free_rproc;
> +	}
> +	return 0;
> +
> +free_rproc:
> +	rproc_free(rproc);
> +
> +	return ret;
> +}
> +
> +static int q6_wcss_remove(struct platform_device *pdev)
> +{
> +	struct rproc *rproc = platform_get_drvdata(pdev);
> +	struct q6_wcss *wcss = rproc->priv;
> +
> +	qcom_q6v5_deinit(&wcss->q6);
> +
> +	rproc_del(rproc);
> +	rproc_free(rproc);
> +
> +	return 0;
> +}



Best regards,
Krzysztof




[Index of Archives]     [Linux ARM Kernel]     [Linux ARM]     [Linux Omap]     [Fedora ARM]     [Linux for Sparc]     [IETF Annouce]     [Security]     [Bugtraq]     [Linux MIPS]     [ECOS]     [Asterisk Internet PBX]     [Linux API]

  Powered by Linux