On Tue, Jan 07, 2025 at 03:46:43PM +0530, Gokul Sriram Palanisamy wrote:
> From: Vignesh Viswanathan <quic_viswa...@quicinc.com>
> 
> Add support to bring up hexagon based WCSS secure PIL remoteproc.
> IPQ5332, IPQ9574 supports secure PIL remoteproc.

I'd love for this to be extended with a short description of what the
WCSS secure subsystem is, the reason for a new drivers etc. Following
the style of
https://docs.kernel.org/process/submitting-patches.html#describe-your-changes

> 
> Signed-off-by: Vignesh Viswanathan <quic_viswa...@quicinc.com>
> Signed-off-by: Manikanta Mylavarapu <quic_mmani...@quicinc.com>
> Signed-off-by: Gokul Sriram Palanisamy <quic_gokul...@quicinc.com>
> ---
>  drivers/remoteproc/Kconfig              |  22 ++
>  drivers/remoteproc/Makefile             |   1 +
>  drivers/remoteproc/qcom_q6v5_wcss_sec.c | 406 ++++++++++++++++++++++++
>  3 files changed, 429 insertions(+)
>  create mode 100644 drivers/remoteproc/qcom_q6v5_wcss_sec.c
> 
> diff --git a/drivers/remoteproc/Kconfig b/drivers/remoteproc/Kconfig
> index 83962a114dc9..c4e94b15c538 100644
> --- a/drivers/remoteproc/Kconfig
> +++ b/drivers/remoteproc/Kconfig
> @@ -255,6 +255,28 @@ config QCOM_Q6V5_WCSS
>         Hexagon V5 based WCSS remote processors on e.g. IPQ8074.  This is
>         a non-TrustZone wireless subsystem.
>  
> +config QCOM_Q6V5_WCSS_SEC
> +     tristate "Qualcomm Hexagon based WCSS Secure 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

Please review these depends, did you inherit a few too many?

> +     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 remote processors on e.g. IPQ5332.
> +
> +       This is TrustZone wireless subsystem. The firmware is
> +       verified and booted with the help of the Peripheral Authentication
> +       System (PAS) in TrustZone.
> +
>  config QCOM_SYSMON
>       tristate "Qualcomm sysmon driver"
>       depends on RPMSG
> diff --git a/drivers/remoteproc/Makefile b/drivers/remoteproc/Makefile
> index 5ff4e2fee4ab..d4971b672812 100644
> --- a/drivers/remoteproc/Makefile
> +++ b/drivers/remoteproc/Makefile
> @@ -28,6 +28,7 @@ obj-$(CONFIG_QCOM_Q6V5_ADSP)                += 
> qcom_q6v5_adsp.o
>  obj-$(CONFIG_QCOM_Q6V5_MSS)          += qcom_q6v5_mss.o
>  obj-$(CONFIG_QCOM_Q6V5_PAS)          += qcom_q6v5_pas.o
>  obj-$(CONFIG_QCOM_Q6V5_WCSS)         += qcom_q6v5_wcss.o
> +obj-$(CONFIG_QCOM_Q6V5_WCSS_SEC)     += qcom_q6v5_wcss_sec.o
>  obj-$(CONFIG_QCOM_SYSMON)            += qcom_sysmon.o
>  obj-$(CONFIG_QCOM_WCNSS_PIL)         += qcom_wcnss_pil.o
>  qcom_wcnss_pil-y                     += qcom_wcnss.o
> diff --git a/drivers/remoteproc/qcom_q6v5_wcss_sec.c 
> b/drivers/remoteproc/qcom_q6v5_wcss_sec.c
> new file mode 100644
> index 000000000000..ef4e893e37c7
> --- /dev/null
> +++ b/drivers/remoteproc/qcom_q6v5_wcss_sec.c
> @@ -0,0 +1,406 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright (C) 2016-2018 Linaro Ltd.
> + * Copyright (C) 2014 Sony Mobile Communications AB
> + * Copyright (c) 2012-2018 The Linux Foundation. All rights reserved.
> + * Copyright (c) 2024-2025 Qualcomm Innovation Center, Inc. All rights 
> reserved.
> + */
> +#include <linux/clk.h>
> +#include <linux/delay.h>

Please check that all these includes are required.

> +#include <linux/firmware/qcom/qcom_scm.h>
> +#include <linux/interrupt.h>
> +#include <linux/io.h>
> +#include <linux/iopoll.h>
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/of_address.h>
> +#include <linux/of_device.h>
> +#include <linux/of_reserved_mem.h>
> +#include <linux/platform_device.h>
> +#include <linux/reset.h>
> +#include <linux/soc/qcom/mdt_loader.h>
> +#include <linux/soc/qcom/smem.h>
> +#include <linux/soc/qcom/smem_state.h>
> +#include <linux/mailbox_client.h>
> +#include <linux/mailbox/tmelcom-qmp.h>

This will require mailbox maintainer to first accept the tmelcom mailbox
driver, and share a immutable branch with me (or we have to wait until
this include file trickles in).

Please ensure that mailbox maintainer is aware of this request.

> +#include "qcom_common.h"
> +#include "qcom_q6v5.h"
> +
> +#include "qcom_pil_info.h"
> +#include "remoteproc_internal.h"
> +
> +#define WCSS_CRASH_REASON            421
> +
> +#define WCSS_PAS_ID                  0x6
> +#define MPD_WCSS_PAS_ID                      0xD

I like lowercase hex digits.

> +
> +struct wcss_sec {
> +     struct device *dev;
> +     struct qcom_rproc_glink glink_subdev;
> +     struct qcom_rproc_ssr ssr_subdev;
> +     struct qcom_q6v5 q6;
> +     phys_addr_t mem_phys;
> +     phys_addr_t mem_reloc;
> +     void *mem_region;
> +     size_t mem_size;
> +     const struct wcss_data *desc;

Assigned but never used.

> +     const char *fw_name;

Assigned but never used.

> +
> +     struct clk *sleep_clk;

Assigned but never used.

> +
> +     struct mbox_client mbox_client;
> +     struct mbox_chan *mbox_chan;
> +     void *metadata;
> +     size_t metadata_len;
> +};
> +
> +struct wcss_data {
> +     u32 pasid;
> +     const struct rproc_ops *ops;
> +     bool auto_boot;
> +     bool tmelcom;
> +};
> +
> +static int wcss_sec_start(struct rproc *rproc)
> +{
> +     struct wcss_sec *wcss = rproc->priv;
> +     struct device *dev = wcss->dev;
> +     const struct wcss_data *desc = of_device_get_match_data(dev);

Please avoid "parsing" DT in runtime.

> +     struct tmel_sec_auth tsa;
> +     struct tmel_qmp_msg tqm;
> +     int ret;
> +
> +     qcom_q6v5_prepare(&wcss->q6);

It would be sensible to check the return value here.

> +
> +     tsa.data = wcss->metadata;

This looks broken.

wcss->metadata is assigned in wcss_sec_load() only if tmelcom, and in
that code path wcss_sec_load() invokes kfree() on the pointer.

So, as far as I can tell, you're either going to pass NULL here or a
pointer to a freed (and perhaps overwritten) buffer.

> +     tsa.size = wcss->metadata_len;
> +     tsa.pas_id = desc->pasid;
> +     tqm.msg = &tsa;
> +     tqm.msg_id = TMEL_MSG_UID_SECBOOT_SEC_AUTH;
> +
> +     if (desc->tmelcom) {

As I point out below, mbox_chan should probably only be assigned when
desc->tmelcom == true, so you wouldn't even need any additional state,
just check if mbox_chan is valid here.

> +             mbox_send_message(wcss->mbox_chan, (void *)&tqm);

This does return errors as well, perhaps worth checking that as well?

> +     } else {
> +             ret = qcom_scm_pas_auth_and_reset(desc->pasid);

Please confirm that you're not required to keep the metadata buffer
passed to PAS init_image during qcom_mdt_load() alive until this point -
as is required by all modern SDMs.

> +             if (ret) {
> +                     dev_err(dev, "wcss_reset failed\n");
> +                     return ret;
> +             }
> +     }
> +
> +     ret = qcom_q6v5_wait_for_start(&wcss->q6, 5 * HZ);
> +     if (ret == -ETIMEDOUT)
> +             dev_err(dev, "start timed out\n");

Don't you need to qcom_scm_pas_shutdown() here to have QHEEBSP release
the memory back to you?

> +
> +     return ret;
> +}
> +
> +static int wcss_sec_stop(struct rproc *rproc)
> +{
> +     struct wcss_sec *wcss = rproc->priv;
> +     struct device *dev = wcss->dev;
> +     const struct wcss_data *desc = of_device_get_match_data(dev);
> +     struct tmel_sec_auth tsa;
> +     struct tmel_qmp_msg tqm;
> +     int ret;
> +
> +     tsa.pas_id = desc->pasid;

tsa is passing a couple of random values over your mbox. Please
zero-initialize these.

Why is this filled in outside desc->tmelcom, when that's the only place
it's used?

> +     tqm.msg = &tsa;
> +     tqm.msg_id = TMEL_MSG_UID_SECBOOT_SS_TEAR_DOWN;
> +
> +     if (desc->tmelcom) {
> +             mbox_send_message(wcss->mbox_chan, (void *)&tqm);
> +     } else {
> +             ret = qcom_scm_pas_shutdown(desc->pasid);
> +             if (ret) {
> +                     dev_err(dev, "not able to shutdown\n");
> +                     return ret;
> +             }
> +     }
> +
> +     qcom_q6v5_unprepare(&wcss->q6);
> +
> +     return 0;
> +}
> +
> +static void *wcss_sec_da_to_va(struct rproc *rproc, u64 da, size_t len,
> +                            bool *is_iomem)
> +{
> +     struct wcss_sec *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 wcss_sec_load(struct rproc *rproc, const struct firmware *fw)
> +{
> +     struct wcss_sec *wcss = rproc->priv;
> +     struct device *dev = wcss->dev;
> +     const struct wcss_data *desc = of_device_get_match_data(dev);
> +     int ret;
> +
> +     if (desc->tmelcom) {
> +             wcss->metadata = qcom_mdt_read_metadata(fw, &wcss->metadata_len,
> +                                                     rproc->firmware, 
> wcss->dev);
> +             if (IS_ERR(wcss->metadata)) {
> +                     ret = PTR_ERR(wcss->metadata);
> +                     dev_err(wcss->dev, "error %d reading firmware %s 
> metadata\n",
> +                             ret, rproc->firmware);
> +                     return ret;
> +             }
> +
> +             ret = qcom_mdt_load_no_init(wcss->dev, fw, rproc->firmware, 
> desc->pasid,
> +                                         wcss->mem_region, wcss->mem_phys, 
> wcss->mem_size,
> +                                         &wcss->mem_reloc);
> +             kfree(wcss->metadata);
> +     } else {
> +             ret = qcom_mdt_load(dev, fw, rproc->firmware, desc->pasid, 
> wcss->mem_region,
> +                                 wcss->mem_phys, wcss->mem_size, 
> &wcss->mem_reloc);
> +     }
> +
> +     if (ret)
> +             return ret;
> +
> +     qcom_pil_info_store("wcss", wcss->mem_phys, wcss->mem_size);
> +
> +     return ret;

ret can't be anything but 0 here, so better just write that.

> +}
> +
> +static unsigned long wcss_sec_panic(struct rproc *rproc)
> +{
> +     struct wcss_sec *wcss = rproc->priv;
> +
> +     return qcom_q6v5_panic(&wcss->q6);
> +}
> +
> +static void wcss_sec_copy_segment(struct rproc *rproc,
> +                               struct rproc_dump_segment *segment,
> +                               void *dest, size_t offset, size_t size)
> +{
> +     struct wcss_sec *wcss = rproc->priv;
> +     struct device *dev = wcss->dev;
> +     void *ptr;
> +
> +     ptr = ioremap_wc(segment->da, segment->size);
> +     if (!ptr) {
> +             dev_err(dev, "Failed to ioremap segment %pad size %zx\n",

Make that %#zx to ensure that the base 16 size is prefixed with 0x

> +                     &segment->da, segment->size);
> +             return;
> +     }
> +
> +     if (size <= segment->size - offset)

I'd prefer if the expression in the check and access are on the same
form. I.e. test offset + size vs segement->size

> +             memcpy(dest, ptr + offset, size);
> +     else
> +             dev_err(dev, "Copy size greater than segment size. Skipping\n");
> +     iounmap(ptr);
> +}
> +
> +static int wcss_sec_dump_segments(struct rproc *rproc,
> +                               const struct firmware *fw)
> +{
> +     struct device *dev = rproc->dev.parent;
> +     struct reserved_mem *rmem = NULL;
> +     struct device_node *node;
> +     int num_segs, index = 0;
> +     int ret;
> +
> +     /* Parse through additional reserved memory regions for the rproc

Leave first line in multiline comment blank, as the docs says.

> +      * and add them to the coredump segments
> +      */
> +     num_segs = of_count_phandle_with_args(dev->of_node,
> +                                           "memory-region", NULL);
> +     while (index < num_segs) {

You're zero-initializing index above, checking index here and explicitly
increment it at the bottom of the loop. Why isn't this written as a for
loop?

> +             node = of_parse_phandle(dev->of_node,
> +                                     "memory-region", index);
> +             if (!node)
> +                     return -EINVAL;
> +
> +             rmem = of_reserved_mem_lookup(node);
> +             of_node_put(node);
> +             if (!rmem) {
> +                     dev_err(dev, "unable to acquire memory-region index %d 
> num_segs %d\n",
> +                             index, num_segs);
> +                     return -EINVAL;
> +             }
> +
> +             dev_dbg(dev, "Adding segment 0x%pa size 0x%pa",
> +                     &rmem->base, &rmem->size);
> +             ret = rproc_coredump_add_custom_segment(rproc,
> +                                                     rmem->base,
> +                                                     rmem->size,
> +                                                     wcss_sec_copy_segment,
> +                                                     NULL);
> +             if (ret)
> +                     return ret;
> +
> +             index++;
> +     }
> +
> +     return 0;
> +}
> +
> +static const struct rproc_ops wcss_sec_ops = {
> +     .start = wcss_sec_start,
> +     .stop = wcss_sec_stop,
> +     .da_to_va = wcss_sec_da_to_va,
> +     .load = wcss_sec_load,
> +     .get_boot_addr = rproc_elf_get_boot_addr,
> +     .panic = wcss_sec_panic,
> +     .parse_fw = wcss_sec_dump_segments,
> +};
> +
> +static int wcss_sec_alloc_memory_region(struct wcss_sec *wcss)
> +{
> +     struct reserved_mem *rmem = NULL;
> +     struct device_node *node;
> +     struct device *dev = wcss->dev;
> +
> +     node = of_parse_phandle(dev->of_node, "memory-region", 0);
> +     if (!node) {
> +             dev_err(dev, "can't find phandle memory-region\n");
> +             return -EINVAL;
> +     }
> +
> +     rmem = of_reserved_mem_lookup(node);
> +     of_node_put(node);
> +
> +     if (!rmem) {
> +             dev_err(dev, "unable to acquire memory-region\n");
> +             return -EINVAL;
> +     }
> +
> +     wcss->mem_phys = rmem->base;
> +     wcss->mem_reloc = rmem->base;
> +     wcss->mem_size = rmem->size;
> +     wcss->mem_region = devm_ioremap_wc(dev, wcss->mem_phys, wcss->mem_size);
> +     if (!wcss->mem_region) {
> +             dev_err(dev, "unable to map memory region: %pa+%pa\n",
> +                     &rmem->base, &rmem->size);
> +             return -ENOMEM;
> +     }
> +
> +     return 0;
> +}
> +
> +static int wcss_sec_probe(struct platform_device *pdev)
> +{
> +     struct wcss_sec *wcss;
> +     struct rproc *rproc;
> +     const char *fw_name = NULL;
> +     const struct wcss_data *desc = of_device_get_match_data(&pdev->dev);
> +     int ret;
> +
> +     if (!desc)
> +             return -EINVAL;

It shouldn't be possible to get here with desc == NULL, so let the
person have an oops with a callstack to aid debugging. (I.e. remove the
check)

> +
> +     ret = of_property_read_string(pdev->dev.of_node, "firmware-name",
> +                                   &fw_name);
> +     if (ret < 0)
> +             return ret;
> +
> +     rproc = devm_rproc_alloc(&pdev->dev, pdev->name, desc->ops, fw_name,
> +                              sizeof(*wcss));

Not sure how your system composition looks like, but please consider
something like b64b1266d619 ("remoteproc: qcom: pas: Make remoteproc
name human friendly"), to avoid the human-unfriendly pdev->name. (Only
if possible)

> +     if (!rproc) {
> +             dev_err(&pdev->dev, "failed to allocate rproc\n");
> +             return -ENOMEM;
> +     }
> +
> +     wcss = rproc->priv;
> +     wcss->dev = &pdev->dev;
> +     wcss->desc = desc;
> +     wcss->fw_name = fw_name;
> +
> +     ret = wcss_sec_alloc_memory_region(wcss);
> +     if (ret)
> +             return ret;
> +
> +     wcss->sleep_clk = devm_clk_get_optional_enabled(&pdev->dev, "sleep");
> +     if (IS_ERR(wcss->sleep_clk))
> +             return dev_err_probe(&pdev->dev, PTR_ERR(wcss->sleep_clk),
> +                                  "Failed to get sleep clock\n");
> +
> +     ret = qcom_q6v5_init(&wcss->q6, pdev, rproc,
> +                          WCSS_CRASH_REASON, NULL, NULL);
> +     if (ret)
> +             return ret;
> +
> +     qcom_add_glink_subdev(rproc, &wcss->glink_subdev, "q6wcss");
> +     qcom_add_ssr_subdev(rproc, &wcss->ssr_subdev, pdev->name);
> +
> +     rproc->auto_boot = desc->auto_boot;
> +     rproc->dump_conf = RPROC_COREDUMP_INLINE;
> +     rproc_coredump_set_elf_info(rproc, ELFCLASS32, EM_NONE);
> +
> +     ret = devm_rproc_add(&pdev->dev, rproc);

In the event of auto_boot, I believe it should be possible to enter
wcss_sec_load() et al from this point onwards. So, it seems reasonable
to acquire mbox_chan prior to registering the remoteproc.

> +     if (ret)
> +             return ret;
> +
> +     platform_set_drvdata(pdev, rproc);
> +
> +     wcss->mbox_client.dev = wcss->dev;
> +     wcss->mbox_client.knows_txdone = true;
> +     wcss->mbox_client.tx_block = true;
> +     wcss->mbox_chan = mbox_request_channel(&wcss->mbox_client, 0);

"mboxes" is optional in binding, but seems to be required here, but then
mbox_chan is only accessed when tmelcom is true.

Should mbox_request_channel() be made conditional on tmelcom?

> +     if (IS_ERR(wcss->mbox_chan)) {
> +             dev_err(wcss->dev, "mbox chan for IPC is missing\n");
> +             return PTR_ERR(wcss->mbox_chan);
> +     }
> +
> +     return 0;

qcom_q6v5_pas.c was recently updated to clean up various things on
error, please do the same here.

> +}
> +
> +static void wcss_sec_remove(struct platform_device *pdev)
> +{
> +     struct rproc *rproc = platform_get_drvdata(pdev);
> +     struct wcss_sec *wcss = rproc->priv;
> +
> +     qcom_q6v5_deinit(&wcss->q6);
> +     qcom_remove_glink_subdev(rproc, &wcss->glink_subdev);
> +     qcom_remove_ssr_subdev(rproc, &wcss->ssr_subdev);

mbox_chan?

> +}
> +
> +static const struct wcss_data wcss_sec_ipq5332_res_init = {
> +     .pasid = MPD_WCSS_PAS_ID,
> +     .auto_boot = true,
> +     .ops = &wcss_sec_ops,

Please avoid unnecessary flexibility (i.e. ops is always wcss_sec_ops).

> +     .tmelcom = false,
> +};
> +
> +static const struct wcss_data wcss_sec_ipq9574_res_init = {
> +     .pasid = WCSS_PAS_ID,
> +     .auto_boot = true,
> +     .ops = &wcss_sec_ops,
> +     .tmelcom = false,
> +};
> +
> +static const struct wcss_data wcss_sec_ipq5424_res_init = {
> +     .pasid = MPD_WCSS_PAS_ID,
> +     .auto_boot = true,
> +     .ops = &wcss_sec_ops,
> +     .tmelcom = true,
> +};
> +
> +static const struct of_device_id wcss_sec_of_match[] = {
> +     { .compatible = "qcom,ipq5332-wcss-sec-pil", .data = 
> &wcss_sec_ipq5332_res_init },
> +     { .compatible = "qcom,ipq9574-wcss-sec-pil", .data = 
> &wcss_sec_ipq9574_res_init },
> +     { .compatible = "qcom,ipq5424-wcss-sec-pil", .data = 
> &wcss_sec_ipq5424_res_init },

Please sort alphabetically.

> +     { },
> +};
> +MODULE_DEVICE_TABLE(of, wcss_sec_of_match);
> +

Regards,
Bjorn

> +static struct platform_driver wcss_sec_driver = {
> +     .probe = wcss_sec_probe,
> +     .remove = wcss_sec_remove,
> +     .driver = {
> +             .name = "qcom-wcss-secure-pil",
> +             .of_match_table = wcss_sec_of_match,
> +     },
> +};
> +module_platform_driver(wcss_sec_driver);
> +
> +MODULE_DESCRIPTION("Hexagon WCSS Secure Peripheral Image Loader");
> +MODULE_LICENSE("GPL");
> -- 
> 2.34.1
> 

Reply via email to