lkml.org 
[lkml]   [2023]   [Jun]   [24]   [last100]   RSS Feed
Views: [wrap][no wrap]   [headers]  [forward] 
 
Messages in this thread
/
Date
SubjectRe: [PATCH V2 10/13] remoteproc: qcom: Add Hexagon based multipd rproc driver
From
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@quicinc.com>
> ---
> 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

\
 
 \ /
  Last update: 2023-06-24 09:28    [W:0.258 / U:0.700 seconds]
©2003-2020 Jasper Spaans|hosted at Digital Ocean and TransIP|Read the blog|Advertise on this site