From ebb0bbef88b3276c01e2d42b71d67c26b4eb938a Mon Sep 17 00:00:00 2001 From: Mukesh Ojha Date: Mon, 5 Jan 2026 18:52:49 +0530 Subject: [PATCH 01/13] dt-bindings: remoteproc: qcom,pas: Add iommus property On most Qualcomm platforms, the Qualcomm Hypervisor Execution Environment (QHEE) is either used as a standalone hypervisor or co-exists as a module with the Gunyah hypervisor. When QHEE is present, it configures firmware streams for remote processors. If QHEE is not available, the operating system must perform these configurations instead and for that remote processor firmware stream should be presented to the operating system. To support this, add the iommus property as an optional property for PAS-supported devices. Acked-by: Rob Herring (Arm) Reviewed-by: Bryan O'Donoghue Signed-off-by: Mukesh Ojha Link: https://lore.kernel.org/r/20260105-kvmrprocv10-v10-1-022e96815380@oss.qualcomm.com Signed-off-by: Bjorn Andersson --- .../devicetree/bindings/remoteproc/qcom,pas-common.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Documentation/devicetree/bindings/remoteproc/qcom,pas-common.yaml b/Documentation/devicetree/bindings/remoteproc/qcom,pas-common.yaml index 63a82e7a8bf8..68c17bf18987 100644 --- a/Documentation/devicetree/bindings/remoteproc/qcom,pas-common.yaml +++ b/Documentation/devicetree/bindings/remoteproc/qcom,pas-common.yaml @@ -44,6 +44,9 @@ properties: - const: stop-ack - const: shutdown-ack + iommus: + maxItems: 1 + power-domains: minItems: 1 maxItems: 3 From 25906ae9b28af23de509cb290a49ddd314f6c432 Mon Sep 17 00:00:00 2001 From: Mukesh Ojha Date: Mon, 5 Jan 2026 18:52:50 +0530 Subject: [PATCH 02/13] firmware: qcom_scm: Remove redundant piece of code The qcom_scm_pas_metadata_release() function already frees the allocated memory and sets ctx->ptr to NULL. Resetting ctx->phys and ctx->size to zero is unnecessary because the context is expected to be discarded after release. Lets removes redundant assignments. Signed-off-by: Mukesh Ojha Link: https://lore.kernel.org/r/20260105-kvmrprocv10-v10-2-022e96815380@oss.qualcomm.com Signed-off-by: Bjorn Andersson --- drivers/firmware/qcom/qcom_scm.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/drivers/firmware/qcom/qcom_scm.c b/drivers/firmware/qcom/qcom_scm.c index 1a6f85e463e0..6461408c58a3 100644 --- a/drivers/firmware/qcom/qcom_scm.c +++ b/drivers/firmware/qcom/qcom_scm.c @@ -650,8 +650,6 @@ void qcom_scm_pas_metadata_release(struct qcom_scm_pas_metadata *ctx) dma_free_coherent(__scm->dev, ctx->size, ctx->ptr, ctx->phys); ctx->ptr = NULL; - ctx->phys = 0; - ctx->size = 0; } EXPORT_SYMBOL_GPL(qcom_scm_pas_metadata_release); From 69054348cc1c2d87acad90aec5e6e0d191012aff Mon Sep 17 00:00:00 2001 From: Mukesh Ojha Date: Mon, 5 Jan 2026 18:52:51 +0530 Subject: [PATCH 03/13] firmware: qcom_scm: Rename peripheral as pas_id Peripheral and pas_id refers to unique id for a subsystem and used only when peripheral authentication service from secure world is utilized. Lets rename peripheral to pas_id to reflect closer to its meaning. Reviewed-by: Bryan O'Donoghue Reviewed-by: Konrad Dybcio Signed-off-by: Mukesh Ojha Link: https://lore.kernel.org/r/20260105-kvmrprocv10-v10-3-022e96815380@oss.qualcomm.com Signed-off-by: Bjorn Andersson --- drivers/firmware/qcom/qcom_scm.c | 30 +++++++++++++------------- include/linux/firmware/qcom/qcom_scm.h | 10 ++++----- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/drivers/firmware/qcom/qcom_scm.c b/drivers/firmware/qcom/qcom_scm.c index 6461408c58a3..1e1057638e98 100644 --- a/drivers/firmware/qcom/qcom_scm.c +++ b/drivers/firmware/qcom/qcom_scm.c @@ -562,7 +562,7 @@ static void qcom_scm_set_download_mode(u32 dload_mode) * qcom_scm_pas_init_image() - Initialize peripheral authentication service * state machine for a given peripheral, using the * metadata - * @peripheral: peripheral id + * @pas_id: peripheral authentication service id * @metadata: pointer to memory containing ELF header, program header table * and optional blob of data used for authenticating the metadata * and the rest of the firmware @@ -575,7 +575,7 @@ static void qcom_scm_set_download_mode(u32 dload_mode) * track the metadata allocation, this needs to be released by invoking * qcom_scm_pas_metadata_release() by the caller. */ -int qcom_scm_pas_init_image(u32 peripheral, const void *metadata, size_t size, +int qcom_scm_pas_init_image(u32 pas_id, const void *metadata, size_t size, struct qcom_scm_pas_metadata *ctx) { dma_addr_t mdata_phys; @@ -585,7 +585,7 @@ int qcom_scm_pas_init_image(u32 peripheral, const void *metadata, size_t size, .svc = QCOM_SCM_SVC_PIL, .cmd = QCOM_SCM_PIL_PAS_INIT_IMAGE, .arginfo = QCOM_SCM_ARGS(2, QCOM_SCM_VAL, QCOM_SCM_RW), - .args[0] = peripheral, + .args[0] = pas_id, .owner = ARM_SMCCC_OWNER_SIP, }; struct qcom_scm_res res; @@ -656,20 +656,20 @@ EXPORT_SYMBOL_GPL(qcom_scm_pas_metadata_release); /** * qcom_scm_pas_mem_setup() - Prepare the memory related to a given peripheral * for firmware loading - * @peripheral: peripheral id + * @pas_id: peripheral authentication service id * @addr: start address of memory area to prepare * @size: size of the memory area to prepare * * Returns 0 on success. */ -int qcom_scm_pas_mem_setup(u32 peripheral, phys_addr_t addr, phys_addr_t size) +int qcom_scm_pas_mem_setup(u32 pas_id, phys_addr_t addr, phys_addr_t size) { int ret; struct qcom_scm_desc desc = { .svc = QCOM_SCM_SVC_PIL, .cmd = QCOM_SCM_PIL_PAS_MEM_SETUP, .arginfo = QCOM_SCM_ARGS(3), - .args[0] = peripheral, + .args[0] = pas_id, .args[1] = addr, .args[2] = size, .owner = ARM_SMCCC_OWNER_SIP, @@ -697,18 +697,18 @@ EXPORT_SYMBOL_GPL(qcom_scm_pas_mem_setup); /** * qcom_scm_pas_auth_and_reset() - Authenticate the given peripheral firmware * and reset the remote processor - * @peripheral: peripheral id + * @pas_id: peripheral authentication service id * * Return 0 on success. */ -int qcom_scm_pas_auth_and_reset(u32 peripheral) +int qcom_scm_pas_auth_and_reset(u32 pas_id) { int ret; struct qcom_scm_desc desc = { .svc = QCOM_SCM_SVC_PIL, .cmd = QCOM_SCM_PIL_PAS_AUTH_AND_RESET, .arginfo = QCOM_SCM_ARGS(1), - .args[0] = peripheral, + .args[0] = pas_id, .owner = ARM_SMCCC_OWNER_SIP, }; struct qcom_scm_res res; @@ -733,18 +733,18 @@ EXPORT_SYMBOL_GPL(qcom_scm_pas_auth_and_reset); /** * qcom_scm_pas_shutdown() - Shut down the remote processor - * @peripheral: peripheral id + * @pas_id: peripheral authentication service id * * Returns 0 on success. */ -int qcom_scm_pas_shutdown(u32 peripheral) +int qcom_scm_pas_shutdown(u32 pas_id) { int ret; struct qcom_scm_desc desc = { .svc = QCOM_SCM_SVC_PIL, .cmd = QCOM_SCM_PIL_PAS_SHUTDOWN, .arginfo = QCOM_SCM_ARGS(1), - .args[0] = peripheral, + .args[0] = pas_id, .owner = ARM_SMCCC_OWNER_SIP, }; struct qcom_scm_res res; @@ -770,18 +770,18 @@ EXPORT_SYMBOL_GPL(qcom_scm_pas_shutdown); /** * qcom_scm_pas_supported() - Check if the peripheral authentication service is * available for the given peripherial - * @peripheral: peripheral id + * @pas_id: peripheral authentication service id * * Returns true if PAS is supported for this peripheral, otherwise false. */ -bool qcom_scm_pas_supported(u32 peripheral) +bool qcom_scm_pas_supported(u32 pas_id) { int ret; struct qcom_scm_desc desc = { .svc = QCOM_SCM_SVC_PIL, .cmd = QCOM_SCM_PIL_PAS_IS_SUPPORTED, .arginfo = QCOM_SCM_ARGS(1), - .args[0] = peripheral, + .args[0] = pas_id, .owner = ARM_SMCCC_OWNER_SIP, }; struct qcom_scm_res res; diff --git a/include/linux/firmware/qcom/qcom_scm.h b/include/linux/firmware/qcom/qcom_scm.h index a55ca771286b..a13f703b16cd 100644 --- a/include/linux/firmware/qcom/qcom_scm.h +++ b/include/linux/firmware/qcom/qcom_scm.h @@ -72,13 +72,13 @@ struct qcom_scm_pas_metadata { ssize_t size; }; -int qcom_scm_pas_init_image(u32 peripheral, const void *metadata, size_t size, +int qcom_scm_pas_init_image(u32 pas_id, const void *metadata, size_t size, struct qcom_scm_pas_metadata *ctx); void qcom_scm_pas_metadata_release(struct qcom_scm_pas_metadata *ctx); -int qcom_scm_pas_mem_setup(u32 peripheral, phys_addr_t addr, phys_addr_t size); -int qcom_scm_pas_auth_and_reset(u32 peripheral); -int qcom_scm_pas_shutdown(u32 peripheral); -bool qcom_scm_pas_supported(u32 peripheral); +int qcom_scm_pas_mem_setup(u32 pas_id, phys_addr_t addr, phys_addr_t size); +int qcom_scm_pas_auth_and_reset(u32 pas_id); +int qcom_scm_pas_shutdown(u32 pas_id); +bool qcom_scm_pas_supported(u32 pas_id); int qcom_scm_io_readl(phys_addr_t addr, unsigned int *val); int qcom_scm_io_writel(phys_addr_t addr, unsigned int val); From ccb7bde5f7cc794dee0cd66fd451cb0e0715712d Mon Sep 17 00:00:00 2001 From: Mukesh Ojha Date: Mon, 5 Jan 2026 18:52:52 +0530 Subject: [PATCH 04/13] firmware: qcom_scm: Introduce PAS context allocator helper function When the Peripheral Authentication Service (PAS) method runs on a SoC where Linux operates at EL2 (i.e., without the Gunyah hypervisor), the reset sequences are handled by TrustZone. In such cases, Linux must perform additional steps before invoking PAS SMC calls, such as creating a SHM bridge. Therefore, PAS SMC calls require awareness and handling of these additional steps when Linux runs at EL2. To support this, there is a need for a data structure that can be initialized prior to invoking any SMC or MDT functions. This structure allows those functions to determine whether they are operating in the presence or absence of the Gunyah hypervisor and behave accordingly. Currently, remoteproc and non-remoteproc subsystems use different variants of the MDT loader helper API, primarily due to differences in metadata context handling. Remoteproc subsystems retain the metadata context until authentication and reset are completed, while non-remoteproc subsystems (e.g., video, graphics, IPA, etc.) do not retain the metadata context and can free it within the qcom_scm_pas_init() call by passing a NULL context parameter and due to these differences, it is not possible to extend metadata context handling to support remoteproc and non remoteproc subsystem use PAS operations, when Linux operates at EL2. Add PAS context data structure allocator helper function. Signed-off-by: Mukesh Ojha Link: https://lore.kernel.org/r/20260105-kvmrprocv10-v10-4-022e96815380@oss.qualcomm.com Signed-off-by: Bjorn Andersson --- drivers/firmware/qcom/qcom_scm.c | 34 ++++++++++++++++++++++++++ include/linux/firmware/qcom/qcom_scm.h | 14 +++++++++++ 2 files changed, 48 insertions(+) diff --git a/drivers/firmware/qcom/qcom_scm.c b/drivers/firmware/qcom/qcom_scm.c index 1e1057638e98..5162c02f5f88 100644 --- a/drivers/firmware/qcom/qcom_scm.c +++ b/drivers/firmware/qcom/qcom_scm.c @@ -558,6 +558,40 @@ static void qcom_scm_set_download_mode(u32 dload_mode) dev_err(__scm->dev, "failed to set download mode: %d\n", ret); } +/** + * devm_qcom_scm_pas_context_alloc() - Allocate peripheral authentication service + * context for a given peripheral + * + * PAS context is device-resource managed, so the caller does not need + * to worry about freeing the context memory. + * + * @dev: PAS firmware device + * @pas_id: peripheral authentication service id + * @mem_phys: Subsystem reserve memory start address + * @mem_size: Subsystem reserve memory size + * + * Returns: The new PAS context, or ERR_PTR() on failure. + */ +struct qcom_scm_pas_context *devm_qcom_scm_pas_context_alloc(struct device *dev, + u32 pas_id, + phys_addr_t mem_phys, + size_t mem_size) +{ + struct qcom_scm_pas_context *ctx; + + ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL); + if (!ctx) + return ERR_PTR(-ENOMEM); + + ctx->dev = dev; + ctx->pas_id = pas_id; + ctx->mem_phys = mem_phys; + ctx->mem_size = mem_size; + + return ctx; +} +EXPORT_SYMBOL_GPL(devm_qcom_scm_pas_context_alloc); + /** * qcom_scm_pas_init_image() - Initialize peripheral authentication service * state machine for a given peripheral, using the diff --git a/include/linux/firmware/qcom/qcom_scm.h b/include/linux/firmware/qcom/qcom_scm.h index a13f703b16cd..5045f8fe876d 100644 --- a/include/linux/firmware/qcom/qcom_scm.h +++ b/include/linux/firmware/qcom/qcom_scm.h @@ -72,6 +72,20 @@ struct qcom_scm_pas_metadata { ssize_t size; }; +struct qcom_scm_pas_context { + struct device *dev; + u32 pas_id; + phys_addr_t mem_phys; + size_t mem_size; + void *ptr; + dma_addr_t phys; + ssize_t size; +}; + +struct qcom_scm_pas_context *devm_qcom_scm_pas_context_alloc(struct device *dev, + u32 pas_id, + phys_addr_t mem_phys, + size_t mem_size); int qcom_scm_pas_init_image(u32 pas_id, const void *metadata, size_t size, struct qcom_scm_pas_metadata *ctx); void qcom_scm_pas_metadata_release(struct qcom_scm_pas_metadata *ctx); From b13d8baf56016e7eec29395b52d18b91df081d48 Mon Sep 17 00:00:00 2001 From: Mukesh Ojha Date: Mon, 5 Jan 2026 18:52:53 +0530 Subject: [PATCH 05/13] remoteproc: pas: Replace metadata context with PAS context structure As a superset of the existing metadata context, the PAS context structure enables both remoteproc and non-remoteproc subsystems to better support scenarios where the SoC runs with or without the Gunyah hypervisor. To reflect this, relevant SCM and metadata functions are updated to incorporate PAS context awareness and remove metadata context data structure completely. Signed-off-by: Mukesh Ojha Link: https://lore.kernel.org/r/20260105-kvmrprocv10-v10-5-022e96815380@oss.qualcomm.com Signed-off-by: Bjorn Andersson --- drivers/firmware/qcom/qcom_scm.c | 8 +++--- drivers/remoteproc/qcom_q6v5_pas.c | 38 ++++++++++++++++++-------- drivers/soc/qcom/mdt_loader.c | 4 +-- include/linux/firmware/qcom/qcom_scm.h | 10 ++----- include/linux/soc/qcom/mdt_loader.h | 6 ++-- 5 files changed, 38 insertions(+), 28 deletions(-) diff --git a/drivers/firmware/qcom/qcom_scm.c b/drivers/firmware/qcom/qcom_scm.c index 5162c02f5f88..4edd475ef848 100644 --- a/drivers/firmware/qcom/qcom_scm.c +++ b/drivers/firmware/qcom/qcom_scm.c @@ -601,7 +601,7 @@ EXPORT_SYMBOL_GPL(devm_qcom_scm_pas_context_alloc); * and optional blob of data used for authenticating the metadata * and the rest of the firmware * @size: size of the metadata - * @ctx: optional metadata context + * @ctx: optional pas context * * Return: 0 on success. * @@ -610,7 +610,7 @@ EXPORT_SYMBOL_GPL(devm_qcom_scm_pas_context_alloc); * qcom_scm_pas_metadata_release() by the caller. */ int qcom_scm_pas_init_image(u32 pas_id, const void *metadata, size_t size, - struct qcom_scm_pas_metadata *ctx) + struct qcom_scm_pas_context *ctx) { dma_addr_t mdata_phys; void *mdata_buf; @@ -674,9 +674,9 @@ EXPORT_SYMBOL_GPL(qcom_scm_pas_init_image); /** * qcom_scm_pas_metadata_release() - release metadata context - * @ctx: metadata context + * @ctx: pas context */ -void qcom_scm_pas_metadata_release(struct qcom_scm_pas_metadata *ctx) +void qcom_scm_pas_metadata_release(struct qcom_scm_pas_context *ctx) { if (!ctx->ptr) return; diff --git a/drivers/remoteproc/qcom_q6v5_pas.c b/drivers/remoteproc/qcom_q6v5_pas.c index 52680ac99589..bfcb65aed008 100644 --- a/drivers/remoteproc/qcom_q6v5_pas.c +++ b/drivers/remoteproc/qcom_q6v5_pas.c @@ -117,8 +117,8 @@ struct qcom_pas { struct qcom_rproc_ssr ssr_subdev; struct qcom_sysmon *sysmon; - struct qcom_scm_pas_metadata pas_metadata; - struct qcom_scm_pas_metadata dtb_pas_metadata; + struct qcom_scm_pas_context *pas_ctx; + struct qcom_scm_pas_context *dtb_pas_ctx; }; static void qcom_pas_segment_dump(struct rproc *rproc, @@ -211,9 +211,9 @@ static int qcom_pas_unprepare(struct rproc *rproc) * auth_and_reset() was successful, but in other cases clean it up * here. */ - qcom_scm_pas_metadata_release(&pas->pas_metadata); + qcom_scm_pas_metadata_release(pas->pas_ctx); if (pas->dtb_pas_id) - qcom_scm_pas_metadata_release(&pas->dtb_pas_metadata); + qcom_scm_pas_metadata_release(pas->dtb_pas_ctx); return 0; } @@ -241,7 +241,7 @@ static int qcom_pas_load(struct rproc *rproc, const struct firmware *fw) ret = qcom_mdt_pas_init(pas->dev, pas->dtb_firmware, pas->dtb_firmware_name, pas->dtb_pas_id, pas->dtb_mem_phys, - &pas->dtb_pas_metadata); + pas->dtb_pas_ctx); if (ret) goto release_dtb_firmware; @@ -255,7 +255,7 @@ static int qcom_pas_load(struct rproc *rproc, const struct firmware *fw) return 0; release_dtb_metadata: - qcom_scm_pas_metadata_release(&pas->dtb_pas_metadata); + qcom_scm_pas_metadata_release(pas->dtb_pas_ctx); release_dtb_firmware: release_firmware(pas->dtb_firmware); @@ -306,7 +306,7 @@ static int qcom_pas_start(struct rproc *rproc) } ret = qcom_mdt_pas_init(pas->dev, pas->firmware, rproc->firmware, pas->pas_id, - pas->mem_phys, &pas->pas_metadata); + pas->mem_phys, pas->pas_ctx); if (ret) goto disable_px_supply; @@ -332,9 +332,9 @@ static int qcom_pas_start(struct rproc *rproc) goto release_pas_metadata; } - qcom_scm_pas_metadata_release(&pas->pas_metadata); + qcom_scm_pas_metadata_release(pas->pas_ctx); if (pas->dtb_pas_id) - qcom_scm_pas_metadata_release(&pas->dtb_pas_metadata); + qcom_scm_pas_metadata_release(pas->dtb_pas_ctx); /* firmware is used to pass reference from qcom_pas_start(), drop it now */ pas->firmware = NULL; @@ -342,9 +342,9 @@ static int qcom_pas_start(struct rproc *rproc) return 0; release_pas_metadata: - qcom_scm_pas_metadata_release(&pas->pas_metadata); + qcom_scm_pas_metadata_release(pas->pas_ctx); if (pas->dtb_pas_id) - qcom_scm_pas_metadata_release(&pas->dtb_pas_metadata); + qcom_scm_pas_metadata_release(pas->dtb_pas_ctx); disable_px_supply: if (pas->px_supply) regulator_disable(pas->px_supply); @@ -760,6 +760,22 @@ static int qcom_pas_probe(struct platform_device *pdev) } qcom_add_ssr_subdev(rproc, &pas->ssr_subdev, desc->ssr_name); + + pas->pas_ctx = devm_qcom_scm_pas_context_alloc(pas->dev, pas->pas_id, + pas->mem_phys, pas->mem_size); + if (IS_ERR(pas->pas_ctx)) { + ret = PTR_ERR(pas->pas_ctx); + goto remove_ssr_sysmon; + } + + pas->dtb_pas_ctx = devm_qcom_scm_pas_context_alloc(pas->dev, pas->dtb_pas_id, + pas->dtb_mem_phys, + pas->dtb_mem_size); + if (IS_ERR(pas->dtb_pas_ctx)) { + ret = PTR_ERR(pas->dtb_pas_ctx); + goto remove_ssr_sysmon; + } + ret = rproc_add(rproc); if (ret) goto remove_ssr_sysmon; diff --git a/drivers/soc/qcom/mdt_loader.c b/drivers/soc/qcom/mdt_loader.c index c239107cb930..b125140100db 100644 --- a/drivers/soc/qcom/mdt_loader.c +++ b/drivers/soc/qcom/mdt_loader.c @@ -234,13 +234,13 @@ EXPORT_SYMBOL_GPL(qcom_mdt_read_metadata); * @fw_name: name of the firmware, for construction of segment file names * @pas_id: PAS identifier * @mem_phys: physical address of allocated memory region - * @ctx: PAS metadata context, to be released by caller + * @ctx: PAS context, ctx->metadata to be released by caller * * Returns 0 on success, negative errno otherwise. */ int qcom_mdt_pas_init(struct device *dev, const struct firmware *fw, const char *fw_name, int pas_id, phys_addr_t mem_phys, - struct qcom_scm_pas_metadata *ctx) + struct qcom_scm_pas_context *ctx) { const struct elf32_phdr *phdrs; const struct elf32_phdr *phdr; diff --git a/include/linux/firmware/qcom/qcom_scm.h b/include/linux/firmware/qcom/qcom_scm.h index 5045f8fe876d..ad69b51fe6fc 100644 --- a/include/linux/firmware/qcom/qcom_scm.h +++ b/include/linux/firmware/qcom/qcom_scm.h @@ -66,12 +66,6 @@ int qcom_scm_set_warm_boot_addr(void *entry); void qcom_scm_cpu_power_down(u32 flags); int qcom_scm_set_remote_state(u32 state, u32 id); -struct qcom_scm_pas_metadata { - void *ptr; - dma_addr_t phys; - ssize_t size; -}; - struct qcom_scm_pas_context { struct device *dev; u32 pas_id; @@ -87,8 +81,8 @@ struct qcom_scm_pas_context *devm_qcom_scm_pas_context_alloc(struct device *dev, phys_addr_t mem_phys, size_t mem_size); int qcom_scm_pas_init_image(u32 pas_id, const void *metadata, size_t size, - struct qcom_scm_pas_metadata *ctx); -void qcom_scm_pas_metadata_release(struct qcom_scm_pas_metadata *ctx); + struct qcom_scm_pas_context *ctx); +void qcom_scm_pas_metadata_release(struct qcom_scm_pas_context *ctx); int qcom_scm_pas_mem_setup(u32 pas_id, phys_addr_t addr, phys_addr_t size); int qcom_scm_pas_auth_and_reset(u32 pas_id); int qcom_scm_pas_shutdown(u32 pas_id); diff --git a/include/linux/soc/qcom/mdt_loader.h b/include/linux/soc/qcom/mdt_loader.h index 8ea8230579a2..07c278841816 100644 --- a/include/linux/soc/qcom/mdt_loader.h +++ b/include/linux/soc/qcom/mdt_loader.h @@ -10,14 +10,14 @@ struct device; struct firmware; -struct qcom_scm_pas_metadata; +struct qcom_scm_pas_context; #if IS_ENABLED(CONFIG_QCOM_MDT_LOADER) ssize_t qcom_mdt_get_size(const struct firmware *fw); int qcom_mdt_pas_init(struct device *dev, const struct firmware *fw, const char *fw_name, int pas_id, phys_addr_t mem_phys, - struct qcom_scm_pas_metadata *pas_metadata_ctx); + struct qcom_scm_pas_context *pas_ctx); int qcom_mdt_load(struct device *dev, const struct firmware *fw, const char *fw_name, int pas_id, void *mem_region, phys_addr_t mem_phys, size_t mem_size, @@ -39,7 +39,7 @@ static inline ssize_t qcom_mdt_get_size(const struct firmware *fw) static inline int qcom_mdt_pas_init(struct device *dev, const struct firmware *fw, const char *fw_name, int pas_id, phys_addr_t mem_phys, - struct qcom_scm_pas_metadata *pas_metadata_ctx) + struct qcom_scm_pas_context *pas_ctx) { return -ENODEV; } From 8a4fcffde6c860c4e9164cf3530c9d97972781dc Mon Sep 17 00:00:00 2001 From: Mukesh Ojha Date: Mon, 5 Jan 2026 18:52:54 +0530 Subject: [PATCH 06/13] soc: qcom: mdtloader: Add PAS context aware qcom_mdt_pas_load() function Introduce a new PAS context-aware function, qcom_mdt_pas_load(), for remote processor drivers. This function utilizes the PAS context pointer returned from qcom_scm_pas_ctx_init() to perform firmware metadata verification and memory setup via SMC calls. The qcom_mdt_pas_load() and qcom_mdt_load() functions are largely similar, but the former is designed for clients using the PAS context-based data structure. Over time, all users of qcom_mdt_load() can be migrated to use qcom_mdt_pas_load() for consistency and improved abstraction. As the remoteproc PAS driver (qcom_q6v5_pas) has already adopted the PAS context-based approach, update it to use qcom_mdt_pas_load(). Reviewed-by: Konrad Dybcio Signed-off-by: Mukesh Ojha Link: https://lore.kernel.org/r/20260105-kvmrprocv10-v10-6-022e96815380@oss.qualcomm.com Signed-off-by: Bjorn Andersson --- drivers/remoteproc/qcom_q6v5_pas.c | 24 +++++----------------- drivers/soc/qcom/mdt_loader.c | 31 +++++++++++++++++++++++++++++ include/linux/soc/qcom/mdt_loader.h | 10 ++++++++++ 3 files changed, 46 insertions(+), 19 deletions(-) diff --git a/drivers/remoteproc/qcom_q6v5_pas.c b/drivers/remoteproc/qcom_q6v5_pas.c index bfcb65aed008..f746d2f39a1d 100644 --- a/drivers/remoteproc/qcom_q6v5_pas.c +++ b/drivers/remoteproc/qcom_q6v5_pas.c @@ -239,15 +239,9 @@ static int qcom_pas_load(struct rproc *rproc, const struct firmware *fw) return ret; } - ret = qcom_mdt_pas_init(pas->dev, pas->dtb_firmware, pas->dtb_firmware_name, - pas->dtb_pas_id, pas->dtb_mem_phys, - pas->dtb_pas_ctx); - if (ret) - goto release_dtb_firmware; - - ret = qcom_mdt_load_no_init(pas->dev, pas->dtb_firmware, pas->dtb_firmware_name, - pas->dtb_mem_region, pas->dtb_mem_phys, - pas->dtb_mem_size, &pas->dtb_mem_reloc); + ret = qcom_mdt_pas_load(pas->dtb_pas_ctx, pas->dtb_firmware, + pas->dtb_firmware_name, pas->dtb_mem_region, + &pas->dtb_mem_reloc); if (ret) goto release_dtb_metadata; } @@ -256,8 +250,6 @@ static int qcom_pas_load(struct rproc *rproc, const struct firmware *fw) release_dtb_metadata: qcom_scm_pas_metadata_release(pas->dtb_pas_ctx); - -release_dtb_firmware: release_firmware(pas->dtb_firmware); return ret; @@ -305,14 +297,8 @@ static int qcom_pas_start(struct rproc *rproc) } } - ret = qcom_mdt_pas_init(pas->dev, pas->firmware, rproc->firmware, pas->pas_id, - pas->mem_phys, pas->pas_ctx); - if (ret) - goto disable_px_supply; - - ret = qcom_mdt_load_no_init(pas->dev, pas->firmware, rproc->firmware, - pas->mem_region, pas->mem_phys, pas->mem_size, - &pas->mem_reloc); + ret = qcom_mdt_pas_load(pas->pas_ctx, pas->firmware, rproc->firmware, + pas->mem_region, &pas->mem_reloc); if (ret) goto release_pas_metadata; diff --git a/drivers/soc/qcom/mdt_loader.c b/drivers/soc/qcom/mdt_loader.c index b125140100db..50c6a3c6b2a3 100644 --- a/drivers/soc/qcom/mdt_loader.c +++ b/drivers/soc/qcom/mdt_loader.c @@ -478,5 +478,36 @@ int qcom_mdt_load(struct device *dev, const struct firmware *fw, } EXPORT_SYMBOL_GPL(qcom_mdt_load); +/** + * qcom_mdt_pas_load - Loads and authenticates the metadata of the firmware + * (typically contained in the .mdt file), followed by loading the actual + * firmware segments (e.g., .bXX files). Authentication of the segments done + * by a separate call. + * + * The PAS context must be initialized using qcom_scm_pas_context_init() + * prior to invoking this function. + * + * @ctx: Pointer to the PAS (Peripheral Authentication Service) context + * @fw: Firmware object representing the .mdt file + * @firmware: Name of the firmware used to construct segment file names + * @mem_region: Memory region allocated for loading the firmware + * @reloc_base: Physical address adjusted after relocation + * + * Return: 0 on success or a negative error code on failure. + */ +int qcom_mdt_pas_load(struct qcom_scm_pas_context *ctx, const struct firmware *fw, + const char *firmware, void *mem_region, phys_addr_t *reloc_base) +{ + int ret; + + ret = qcom_mdt_pas_init(ctx->dev, fw, firmware, ctx->pas_id, ctx->mem_phys, ctx); + if (ret) + return ret; + + return qcom_mdt_load_no_init(ctx->dev, fw, firmware, mem_region, ctx->mem_phys, + ctx->mem_size, reloc_base); +} +EXPORT_SYMBOL_GPL(qcom_mdt_pas_load); + MODULE_DESCRIPTION("Firmware parser for Qualcomm MDT format"); MODULE_LICENSE("GPL v2"); diff --git a/include/linux/soc/qcom/mdt_loader.h b/include/linux/soc/qcom/mdt_loader.h index 07c278841816..7d57746fbbfa 100644 --- a/include/linux/soc/qcom/mdt_loader.h +++ b/include/linux/soc/qcom/mdt_loader.h @@ -23,6 +23,9 @@ int qcom_mdt_load(struct device *dev, const struct firmware *fw, phys_addr_t mem_phys, size_t mem_size, phys_addr_t *reloc_base); +int qcom_mdt_pas_load(struct qcom_scm_pas_context *ctx, const struct firmware *fw, + const char *firmware, void *mem_region, phys_addr_t *reloc_base); + int qcom_mdt_load_no_init(struct device *dev, const struct firmware *fw, const char *fw_name, void *mem_region, phys_addr_t mem_phys, size_t mem_size, @@ -52,6 +55,13 @@ static inline int qcom_mdt_load(struct device *dev, const struct firmware *fw, return -ENODEV; } +static inline int qcom_mdt_pas_load(struct qcom_scm_pas_context *ctx, + const struct firmware *fw, const char *firmware, + void *mem_region, phys_addr_t *reloc_base) +{ + return -ENODEV; +} + static inline int qcom_mdt_load_no_init(struct device *dev, const struct firmware *fw, const char *fw_name, void *mem_region, From 928dbaaa9d89363d79e309ec00c5527ddfbe47c8 Mon Sep 17 00:00:00 2001 From: Mukesh Ojha Date: Mon, 5 Jan 2026 18:52:55 +0530 Subject: [PATCH 07/13] soc: qcom: mdtloader: Remove qcom_mdt_pas_init() from exported symbols qcom_mdt_pas_init() was previously used only by the remoteproc driver (drivers/remoteproc/qcom_q6v5_pas.c). Since that driver has now transitioned to using PAS context-based qcom_mdt_pas_load() function, making qcom_mdt_pas_init() obsolete for external use. Removes qcom_mdt_pas_init() from the list of exported symbols and make it static to limit its scope to internal use within mdtloader. Reviewed-by: Konrad Dybcio Signed-off-by: Mukesh Ojha Link: https://lore.kernel.org/r/20260105-kvmrprocv10-v10-7-022e96815380@oss.qualcomm.com Signed-off-by: Bjorn Andersson --- drivers/soc/qcom/mdt_loader.c | 22 +++++----------------- include/linux/soc/qcom/mdt_loader.h | 10 ---------- 2 files changed, 5 insertions(+), 27 deletions(-) diff --git a/drivers/soc/qcom/mdt_loader.c b/drivers/soc/qcom/mdt_loader.c index 50c6a3c6b2a3..c004d444d698 100644 --- a/drivers/soc/qcom/mdt_loader.c +++ b/drivers/soc/qcom/mdt_loader.c @@ -227,20 +227,9 @@ void *qcom_mdt_read_metadata(const struct firmware *fw, size_t *data_len, } EXPORT_SYMBOL_GPL(qcom_mdt_read_metadata); -/** - * qcom_mdt_pas_init() - initialize PAS region for firmware loading - * @dev: device handle to associate resources with - * @fw: firmware object for the mdt file - * @fw_name: name of the firmware, for construction of segment file names - * @pas_id: PAS identifier - * @mem_phys: physical address of allocated memory region - * @ctx: PAS context, ctx->metadata to be released by caller - * - * Returns 0 on success, negative errno otherwise. - */ -int qcom_mdt_pas_init(struct device *dev, const struct firmware *fw, - const char *fw_name, int pas_id, phys_addr_t mem_phys, - struct qcom_scm_pas_context *ctx) +static int __qcom_mdt_pas_init(struct device *dev, const struct firmware *fw, + const char *fw_name, int pas_id, phys_addr_t mem_phys, + struct qcom_scm_pas_context *ctx) { const struct elf32_phdr *phdrs; const struct elf32_phdr *phdr; @@ -302,7 +291,6 @@ int qcom_mdt_pas_init(struct device *dev, const struct firmware *fw, out: return ret; } -EXPORT_SYMBOL_GPL(qcom_mdt_pas_init); static bool qcom_mdt_bins_are_split(const struct firmware *fw) { @@ -469,7 +457,7 @@ int qcom_mdt_load(struct device *dev, const struct firmware *fw, { int ret; - ret = qcom_mdt_pas_init(dev, fw, fw_name, pas_id, mem_phys, NULL); + ret = __qcom_mdt_pas_init(dev, fw, fw_name, pas_id, mem_phys, NULL); if (ret) return ret; @@ -500,7 +488,7 @@ int qcom_mdt_pas_load(struct qcom_scm_pas_context *ctx, const struct firmware *f { int ret; - ret = qcom_mdt_pas_init(ctx->dev, fw, firmware, ctx->pas_id, ctx->mem_phys, ctx); + ret = __qcom_mdt_pas_init(ctx->dev, fw, firmware, ctx->pas_id, ctx->mem_phys, ctx); if (ret) return ret; diff --git a/include/linux/soc/qcom/mdt_loader.h b/include/linux/soc/qcom/mdt_loader.h index 7d57746fbbfa..82372e0db0a1 100644 --- a/include/linux/soc/qcom/mdt_loader.h +++ b/include/linux/soc/qcom/mdt_loader.h @@ -15,9 +15,6 @@ struct qcom_scm_pas_context; #if IS_ENABLED(CONFIG_QCOM_MDT_LOADER) ssize_t qcom_mdt_get_size(const struct firmware *fw); -int qcom_mdt_pas_init(struct device *dev, const struct firmware *fw, - const char *fw_name, int pas_id, phys_addr_t mem_phys, - struct qcom_scm_pas_context *pas_ctx); int qcom_mdt_load(struct device *dev, const struct firmware *fw, const char *fw_name, int pas_id, void *mem_region, phys_addr_t mem_phys, size_t mem_size, @@ -40,13 +37,6 @@ static inline ssize_t qcom_mdt_get_size(const struct firmware *fw) return -ENODEV; } -static inline int qcom_mdt_pas_init(struct device *dev, const struct firmware *fw, - const char *fw_name, int pas_id, phys_addr_t mem_phys, - struct qcom_scm_pas_context *pas_ctx) -{ - return -ENODEV; -} - static inline int qcom_mdt_load(struct device *dev, const struct firmware *fw, const char *fw_name, int pas_id, void *mem_region, phys_addr_t mem_phys, From 4a7d6a78fbc6527fb1b61944aab00d9cdd1d4f01 Mon Sep 17 00:00:00 2001 From: Mukesh Ojha Date: Mon, 5 Jan 2026 18:52:56 +0530 Subject: [PATCH 08/13] firmware: qcom_scm: Add a prep version of auth_and_reset function For memory passed to TrustZone (TZ), it must either be part of a pool registered with TZ or explicitly registered via SHMbridge SMC calls. When Gunyah hypervisor is present, PAS SMC calls from Linux running at EL1 are trapped by Gunyah running @ EL2, which handles SHMbridge creation for both metadata and remoteproc carveout memory before invoking the calls to TZ. On SoCs running with a non-Gunyah-based hypervisor, Linux must take responsibility for creating the SHM bridge before invoking PAS SMC calls. For the auth_and_reset() call, the remoteproc carveout memory must first be registered with TZ via a SHMbridge SMC call and once authentication and reset are complete, the SHMbridge memory can be deregistered. Introduce qcom_scm_pas_prepare_and_auth_reset(), which sets up the SHM bridge over the remoteproc carveout memory when Linux operates at EL2. This behavior is indicated by a new field added to the PAS context data structure. The function then invokes the auth_and_reset SMC call. Signed-off-by: Mukesh Ojha Link: https://lore.kernel.org/r/20260105-kvmrprocv10-v10-8-022e96815380@oss.qualcomm.com Signed-off-by: Bjorn Andersson --- drivers/firmware/qcom/qcom_scm.c | 47 ++++++++++++++++++++++++++ include/linux/firmware/qcom/qcom_scm.h | 2 ++ 2 files changed, 49 insertions(+) diff --git a/drivers/firmware/qcom/qcom_scm.c b/drivers/firmware/qcom/qcom_scm.c index 4edd475ef848..d3783166fea1 100644 --- a/drivers/firmware/qcom/qcom_scm.c +++ b/drivers/firmware/qcom/qcom_scm.c @@ -765,6 +765,53 @@ disable_clk: } EXPORT_SYMBOL_GPL(qcom_scm_pas_auth_and_reset); +/** + * qcom_scm_pas_prepare_and_auth_reset() - Prepare, authenticate, and reset the + * remote processor + * + * @ctx: Context saved during call to qcom_scm_pas_context_init() + * + * This function performs the necessary steps to prepare a PAS subsystem, + * authenticate it using the provided metadata, and initiate a reset sequence. + * + * It should be used when Linux is in control setting up the IOMMU hardware + * for remote subsystem during secure firmware loading processes. The preparation + * step sets up a shmbridge over the firmware memory before TrustZone accesses the + * firmware memory region for authentication. The authentication step verifies + * the integrity and authenticity of the firmware or configuration using secure + * metadata. Finally, the reset step ensures the subsystem starts in a clean and + * sane state. + * + * Return: 0 on success, negative errno on failure. + */ +int qcom_scm_pas_prepare_and_auth_reset(struct qcom_scm_pas_context *ctx) +{ + u64 handle; + int ret; + + /* + * When Linux running @ EL1, Gunyah hypervisor running @ EL2 traps the + * auth_and_reset call and create an shmbridge on the remote subsystem + * memory region and then invokes a call to TrustZone to authenticate. + */ + if (!ctx->use_tzmem) + return qcom_scm_pas_auth_and_reset(ctx->pas_id); + + /* + * When Linux runs @ EL2 Linux must create the shmbridge itself and then + * subsequently call TrustZone for authenticate and reset. + */ + ret = qcom_tzmem_shm_bridge_create(ctx->mem_phys, ctx->mem_size, &handle); + if (ret) + return ret; + + ret = qcom_scm_pas_auth_and_reset(ctx->pas_id); + qcom_tzmem_shm_bridge_delete(handle); + + return ret; +} +EXPORT_SYMBOL_GPL(qcom_scm_pas_prepare_and_auth_reset); + /** * qcom_scm_pas_shutdown() - Shut down the remote processor * @pas_id: peripheral authentication service id diff --git a/include/linux/firmware/qcom/qcom_scm.h b/include/linux/firmware/qcom/qcom_scm.h index ad69b51fe6fc..d6d83888bb75 100644 --- a/include/linux/firmware/qcom/qcom_scm.h +++ b/include/linux/firmware/qcom/qcom_scm.h @@ -74,6 +74,7 @@ struct qcom_scm_pas_context { void *ptr; dma_addr_t phys; ssize_t size; + bool use_tzmem; }; struct qcom_scm_pas_context *devm_qcom_scm_pas_context_alloc(struct device *dev, @@ -87,6 +88,7 @@ int qcom_scm_pas_mem_setup(u32 pas_id, phys_addr_t addr, phys_addr_t size); int qcom_scm_pas_auth_and_reset(u32 pas_id); int qcom_scm_pas_shutdown(u32 pas_id); bool qcom_scm_pas_supported(u32 pas_id); +int qcom_scm_pas_prepare_and_auth_reset(struct qcom_scm_pas_context *ctx); int qcom_scm_io_readl(phys_addr_t addr, unsigned int *val); int qcom_scm_io_writel(phys_addr_t addr, unsigned int val); From 223a87168030b422dda44c21319122f6328b5867 Mon Sep 17 00:00:00 2001 From: Mukesh Ojha Date: Mon, 5 Jan 2026 18:52:57 +0530 Subject: [PATCH 09/13] firmware: qcom_scm: Refactor qcom_scm_pas_init_image() Refactor qcom_scm_pas_init_image() by moving the memory allocation, copy, and free operations to a higher-level function, and isolate the actual SMC call in a separate function. The main intention is to allow flexibility for different allocators and to respect any constraints that the allocator API may impose before invoking the actual SCM function. Reviewed-by: Bryan O'Donoghue Reviewed-by: Konrad Dybcio Signed-off-by: Mukesh Ojha Link: https://lore.kernel.org/r/20260105-kvmrprocv10-v10-9-022e96815380@oss.qualcomm.com Signed-off-by: Bjorn Andersson --- drivers/firmware/qcom/qcom_scm.c | 58 ++++++++++++++++++-------------- 1 file changed, 33 insertions(+), 25 deletions(-) diff --git a/drivers/firmware/qcom/qcom_scm.c b/drivers/firmware/qcom/qcom_scm.c index d3783166fea1..bc3b8dc7d3e4 100644 --- a/drivers/firmware/qcom/qcom_scm.c +++ b/drivers/firmware/qcom/qcom_scm.c @@ -592,6 +592,37 @@ struct qcom_scm_pas_context *devm_qcom_scm_pas_context_alloc(struct device *dev, } EXPORT_SYMBOL_GPL(devm_qcom_scm_pas_context_alloc); +static int __qcom_scm_pas_init_image(u32 pas_id, dma_addr_t mdata_phys, + struct qcom_scm_res *res) +{ + struct qcom_scm_desc desc = { + .svc = QCOM_SCM_SVC_PIL, + .cmd = QCOM_SCM_PIL_PAS_INIT_IMAGE, + .arginfo = QCOM_SCM_ARGS(2, QCOM_SCM_VAL, QCOM_SCM_RW), + .args[0] = pas_id, + .owner = ARM_SMCCC_OWNER_SIP, + }; + int ret; + + ret = qcom_scm_clk_enable(); + if (ret) + return ret; + + ret = qcom_scm_bw_enable(); + if (ret) + goto disable_clk; + + desc.args[1] = mdata_phys; + + ret = qcom_scm_call(__scm->dev, &desc, res); + qcom_scm_bw_disable(); + +disable_clk: + qcom_scm_clk_disable(); + + return ret; +} + /** * qcom_scm_pas_init_image() - Initialize peripheral authentication service * state machine for a given peripheral, using the @@ -612,17 +643,10 @@ EXPORT_SYMBOL_GPL(devm_qcom_scm_pas_context_alloc); int qcom_scm_pas_init_image(u32 pas_id, const void *metadata, size_t size, struct qcom_scm_pas_context *ctx) { + struct qcom_scm_res res; dma_addr_t mdata_phys; void *mdata_buf; int ret; - struct qcom_scm_desc desc = { - .svc = QCOM_SCM_SVC_PIL, - .cmd = QCOM_SCM_PIL_PAS_INIT_IMAGE, - .arginfo = QCOM_SCM_ARGS(2, QCOM_SCM_VAL, QCOM_SCM_RW), - .args[0] = pas_id, - .owner = ARM_SMCCC_OWNER_SIP, - }; - struct qcom_scm_res res; /* * During the scm call memory protection will be enabled for the meta @@ -643,23 +667,7 @@ int qcom_scm_pas_init_image(u32 pas_id, const void *metadata, size_t size, memcpy(mdata_buf, metadata, size); - ret = qcom_scm_clk_enable(); - if (ret) - goto out; - - ret = qcom_scm_bw_enable(); - if (ret) - goto disable_clk; - - desc.args[1] = mdata_phys; - - ret = qcom_scm_call(__scm->dev, &desc, &res); - qcom_scm_bw_disable(); - -disable_clk: - qcom_scm_clk_disable(); - -out: + ret = __qcom_scm_pas_init_image(pas_id, mdata_phys, &res); if (ret < 0 || !ctx) { dma_free_coherent(__scm->dev, size, mdata_buf, mdata_phys); } else if (ctx) { From b019925838bca1599fa443b34c8ed5876f14f144 Mon Sep 17 00:00:00 2001 From: Mukesh Ojha Date: Mon, 5 Jan 2026 18:52:58 +0530 Subject: [PATCH 10/13] firmware: qcom_scm: Add SHM bridge handling for PAS when running without QHEE On SoCs running with a non-Gunyah-based hypervisor, Linux must take responsibility for creating the SHM bridge both for metadata (before calling qcom_scm_pas_init_image()) and for remoteproc memory (before calling qcom_scm_pas_auth_and_reset()). We have taken care the things required for qcom_scm_pas_auth_and_reset(). Lets put these awareness of above conditions into qcom_scm_pas_init_image() and qcom_scm_pas_metadata_release(). Reviewed-by: Konrad Dybcio Signed-off-by: Mukesh Ojha Link: https://lore.kernel.org/r/20260105-kvmrprocv10-v10-10-022e96815380@oss.qualcomm.com Signed-off-by: Bjorn Andersson --- drivers/firmware/qcom/qcom_scm.c | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/drivers/firmware/qcom/qcom_scm.c b/drivers/firmware/qcom/qcom_scm.c index bc3b8dc7d3e4..4ce892d8fb25 100644 --- a/drivers/firmware/qcom/qcom_scm.c +++ b/drivers/firmware/qcom/qcom_scm.c @@ -623,6 +623,30 @@ disable_clk: return ret; } +static int qcom_scm_pas_prep_and_init_image(struct qcom_scm_pas_context *ctx, + const void *metadata, size_t size) +{ + struct qcom_scm_res res; + phys_addr_t mdata_phys; + void *mdata_buf; + int ret; + + mdata_buf = qcom_tzmem_alloc(__scm->mempool, size, GFP_KERNEL); + if (!mdata_buf) + return -ENOMEM; + + memcpy(mdata_buf, metadata, size); + mdata_phys = qcom_tzmem_to_phys(mdata_buf); + + ret = __qcom_scm_pas_init_image(ctx->pas_id, mdata_phys, &res); + if (ret < 0) + qcom_tzmem_free(mdata_buf); + else + ctx->ptr = mdata_buf; + + return ret ? : res.result[0]; +} + /** * qcom_scm_pas_init_image() - Initialize peripheral authentication service * state machine for a given peripheral, using the @@ -648,6 +672,9 @@ int qcom_scm_pas_init_image(u32 pas_id, const void *metadata, size_t size, void *mdata_buf; int ret; + if (ctx && ctx->use_tzmem) + return qcom_scm_pas_prep_and_init_image(ctx, metadata, size); + /* * During the scm call memory protection will be enabled for the meta * data blob, so make sure it's physically contiguous, 4K aligned and @@ -689,7 +716,10 @@ void qcom_scm_pas_metadata_release(struct qcom_scm_pas_context *ctx) if (!ctx->ptr) return; - dma_free_coherent(__scm->dev, ctx->size, ctx->ptr, ctx->phys); + if (ctx->use_tzmem) + qcom_tzmem_free(ctx->ptr); + else + dma_free_coherent(__scm->dev, ctx->size, ctx->ptr, ctx->phys); ctx->ptr = NULL; } From 8b9d2050cfa0c22c05622df103e366933fc045ed Mon Sep 17 00:00:00 2001 From: Mukesh Ojha Date: Mon, 5 Jan 2026 18:52:59 +0530 Subject: [PATCH 11/13] firmware: qcom_scm: Add qcom_scm_pas_get_rsc_table() to get resource table Qualcomm remote processor may rely on Static and Dynamic resources for it to be functional. Static resources are fixed like for example, memory-mapped addresses required by the subsystem and dynamic resources, such as shared memory in DDR etc., are determined at runtime during the boot process. For most of the Qualcomm SoCs, when run with Gunyah or older QHEE hypervisor, all the resources whether it is static or dynamic, is managed by the hypervisor. Dynamic resources if it is present for a remote processor will always be coming from secure world via SMC call while static resources may be present in remote processor firmware binary or it may be coming qcom_scm_pas_get_rsc_table() SMC call along with dynamic resources. Some of the remote processor drivers, such as video, GPU, IPA, etc., do not check whether resources are present in their remote processor firmware binary. In such cases, the caller of this function should set input_rt and input_rt_size as NULL and zero respectively. Remoteproc framework has method to check whether firmware binary contain resources or not and they should be pass resource table pointer to input_rt and resource table size to input_rt_size and this will be forwarded to TrustZone for authentication. TrustZone will then append the dynamic resources and return the complete resource table in the passed output buffer. More about documentation on resource table format can be found in include/linux/remoteproc.h Signed-off-by: Mukesh Ojha Link: https://lore.kernel.org/r/20260105-kvmrprocv10-v10-11-022e96815380@oss.qualcomm.com Signed-off-by: Bjorn Andersson --- drivers/firmware/qcom/qcom_scm.c | 171 +++++++++++++++++++++++++ drivers/firmware/qcom/qcom_scm.h | 1 + include/linux/firmware/qcom/qcom_scm.h | 4 + 3 files changed, 176 insertions(+) diff --git a/drivers/firmware/qcom/qcom_scm.c b/drivers/firmware/qcom/qcom_scm.c index 4ce892d8fb25..918613d5a151 100644 --- a/drivers/firmware/qcom/qcom_scm.c +++ b/drivers/firmware/qcom/qcom_scm.c @@ -27,6 +27,7 @@ #include #include #include +#include #include #include @@ -111,6 +112,8 @@ enum qcom_scm_qseecom_tz_cmd_info { QSEECOM_TZ_CMD_INFO_VERSION = 3, }; +#define RSCTABLE_BUFFER_NOT_SUFFICIENT 20 + #define QSEECOM_MAX_APP_NAME_SIZE 64 #define SHMBRIDGE_RESULT_NOTSUPP 4 @@ -766,6 +769,174 @@ disable_clk: } EXPORT_SYMBOL_GPL(qcom_scm_pas_mem_setup); +static void *__qcom_scm_pas_get_rsc_table(u32 pas_id, void *input_rt_tzm, + size_t input_rt_size, + size_t *output_rt_size) +{ + struct qcom_scm_desc desc = { + .svc = QCOM_SCM_SVC_PIL, + .cmd = QCOM_SCM_PIL_PAS_GET_RSCTABLE, + .arginfo = QCOM_SCM_ARGS(5, QCOM_SCM_VAL, QCOM_SCM_RO, QCOM_SCM_VAL, + QCOM_SCM_RW, QCOM_SCM_VAL), + .args[0] = pas_id, + .owner = ARM_SMCCC_OWNER_SIP, + }; + struct qcom_scm_res res; + void *output_rt_tzm; + int ret; + + output_rt_tzm = qcom_tzmem_alloc(__scm->mempool, *output_rt_size, GFP_KERNEL); + if (!output_rt_tzm) + return ERR_PTR(-ENOMEM); + + desc.args[1] = qcom_tzmem_to_phys(input_rt_tzm); + desc.args[2] = input_rt_size; + desc.args[3] = qcom_tzmem_to_phys(output_rt_tzm); + desc.args[4] = *output_rt_size; + + /* + * Whether SMC fail or pass, res.result[2] will hold actual resource table + * size. + * + * If passed 'output_rt_size' buffer size is not sufficient to hold the + * resource table TrustZone sends, response code in res.result[1] as + * RSCTABLE_BUFFER_NOT_SUFFICIENT so that caller can retry this SMC call + * with output_rt_tzm buffer with res.result[2] size however, It should not + * be of unresonable size. + */ + ret = qcom_scm_call(__scm->dev, &desc, &res); + if (!ret && res.result[2] > SZ_1G) { + ret = -E2BIG; + goto free_output_rt; + } + + *output_rt_size = res.result[2]; + if (ret && res.result[1] == RSCTABLE_BUFFER_NOT_SUFFICIENT) + ret = -EOVERFLOW; + +free_output_rt: + if (ret) + qcom_tzmem_free(output_rt_tzm); + + return ret ? ERR_PTR(ret) : output_rt_tzm; +} + +/** + * qcom_scm_pas_get_rsc_table() - Retrieve the resource table in passed output buffer + * for a given peripheral. + * + * Qualcomm remote processor may rely on both static and dynamic resources for + * its functionality. Static resources typically refer to memory-mapped addresses + * required by the subsystem and are often embedded within the firmware binary + * and dynamic resources, such as shared memory in DDR etc., are determined at + * runtime during the boot process. + * + * On Qualcomm Technologies devices, it's possible that static resources are not + * embedded in the firmware binary and instead are provided by TrustZone However, + * dynamic resources are always expected to come from TrustZone. This indicates + * that for Qualcomm devices, all resources (static and dynamic) will be provided + * by TrustZone via the SMC call. + * + * If the remote processor firmware binary does contain static resources, they + * should be passed in input_rt. These will be forwarded to TrustZone for + * authentication. TrustZone will then append the dynamic resources and return + * the complete resource table in output_rt_tzm. + * + * If the remote processor firmware binary does not include a resource table, + * the caller of this function should set input_rt as NULL and input_rt_size + * as zero respectively. + * + * More about documentation on resource table data structures can be found in + * include/linux/remoteproc.h + * + * @ctx: PAS context + * @pas_id: peripheral authentication service id + * @input_rt: resource table buffer which is present in firmware binary + * @input_rt_size: size of the resource table present in firmware binary + * @output_rt_size: TrustZone expects caller should pass worst case size for + * the output_rt_tzm. + * + * Return: + * On success, returns a pointer to the allocated buffer containing the final + * resource table and output_rt_size will have actual resource table size from + * TrustZone. The caller is responsible for freeing the buffer. On failure, + * returns ERR_PTR(-errno). + */ +struct resource_table *qcom_scm_pas_get_rsc_table(struct qcom_scm_pas_context *ctx, + void *input_rt, + size_t input_rt_size, + size_t *output_rt_size) +{ + struct resource_table empty_rsc = {}; + size_t size = SZ_16K; + void *output_rt_tzm; + void *input_rt_tzm; + void *tbl_ptr; + int ret; + + ret = qcom_scm_clk_enable(); + if (ret) + return ERR_PTR(ret); + + ret = qcom_scm_bw_enable(); + if (ret) + goto disable_clk; + + /* + * TrustZone can not accept buffer as NULL value as argument hence, + * we need to pass a input buffer indicating that subsystem firmware + * does not have resource table by filling resource table structure. + */ + if (!input_rt) { + input_rt = &empty_rsc; + input_rt_size = sizeof(empty_rsc); + } + + input_rt_tzm = qcom_tzmem_alloc(__scm->mempool, input_rt_size, GFP_KERNEL); + if (!input_rt_tzm) { + ret = -ENOMEM; + goto disable_scm_bw; + } + + memcpy(input_rt_tzm, input_rt, input_rt_size); + + output_rt_tzm = __qcom_scm_pas_get_rsc_table(ctx->pas_id, input_rt_tzm, + input_rt_size, &size); + if (PTR_ERR(output_rt_tzm) == -EOVERFLOW) + /* Try again with the size requested by the TZ */ + output_rt_tzm = __qcom_scm_pas_get_rsc_table(ctx->pas_id, + input_rt_tzm, + input_rt_size, + &size); + if (IS_ERR(output_rt_tzm)) { + ret = PTR_ERR(output_rt_tzm); + goto free_input_rt; + } + + tbl_ptr = kzalloc(size, GFP_KERNEL); + if (!tbl_ptr) { + qcom_tzmem_free(output_rt_tzm); + ret = -ENOMEM; + goto free_input_rt; + } + + memcpy(tbl_ptr, output_rt_tzm, size); + *output_rt_size = size; + qcom_tzmem_free(output_rt_tzm); + +free_input_rt: + qcom_tzmem_free(input_rt_tzm); + +disable_scm_bw: + qcom_scm_bw_disable(); + +disable_clk: + qcom_scm_clk_disable(); + + return ret ? ERR_PTR(ret) : tbl_ptr; +} +EXPORT_SYMBOL_GPL(qcom_scm_pas_get_rsc_table); + /** * qcom_scm_pas_auth_and_reset() - Authenticate the given peripheral firmware * and reset the remote processor diff --git a/drivers/firmware/qcom/qcom_scm.h b/drivers/firmware/qcom/qcom_scm.h index a56c8212cc0c..50d87c628d78 100644 --- a/drivers/firmware/qcom/qcom_scm.h +++ b/drivers/firmware/qcom/qcom_scm.h @@ -105,6 +105,7 @@ int qcom_scm_shm_bridge_enable(struct device *scm_dev); #define QCOM_SCM_PIL_PAS_SHUTDOWN 0x06 #define QCOM_SCM_PIL_PAS_IS_SUPPORTED 0x07 #define QCOM_SCM_PIL_PAS_MSS_RESET 0x0a +#define QCOM_SCM_PIL_PAS_GET_RSCTABLE 0x21 #define QCOM_SCM_SVC_IO 0x05 #define QCOM_SCM_IO_READ 0x01 diff --git a/include/linux/firmware/qcom/qcom_scm.h b/include/linux/firmware/qcom/qcom_scm.h index d6d83888bb75..5747bd191bf1 100644 --- a/include/linux/firmware/qcom/qcom_scm.h +++ b/include/linux/firmware/qcom/qcom_scm.h @@ -88,6 +88,10 @@ int qcom_scm_pas_mem_setup(u32 pas_id, phys_addr_t addr, phys_addr_t size); int qcom_scm_pas_auth_and_reset(u32 pas_id); int qcom_scm_pas_shutdown(u32 pas_id); bool qcom_scm_pas_supported(u32 pas_id); +struct resource_table *qcom_scm_pas_get_rsc_table(struct qcom_scm_pas_context *ctx, + void *input_rt, size_t input_rt_size, + size_t *output_rt_size); + int qcom_scm_pas_prepare_and_auth_reset(struct qcom_scm_pas_context *ctx); int qcom_scm_io_readl(phys_addr_t addr, unsigned int *val); From a4584bff63c8aba994c8cbccc36748226f8f4b21 Mon Sep 17 00:00:00 2001 From: Mukesh Ojha Date: Mon, 5 Jan 2026 18:53:00 +0530 Subject: [PATCH 12/13] remoteproc: pas: Extend parse_fw callback to fetch resources via SMC call Qualcomm remote processor may rely on static and dynamic resources for it to be functional. For most of the Qualcomm SoCs, when run with Gunyah or older QHEE hypervisor, all the resources whether it is static or dynamic, is managed by the hypervisor. Dynamic resources if it is present for a remote processor will always be coming from secure world via SMC call while static resources may be present in remote processor firmware binary or it may be coming from SMC call along with dynamic resources. Remoteproc already has method like rproc_elf_load_rsc_table() to check firmware binary has resources or not and if it is not having then we pass NULL and zero as input resource table and its size argument respectively to qcom_scm_pas_get_rsc_table() and while it has resource present then it should pass the present resources to Trustzone(TZ) so that it could authenticate the present resources and append dynamic resource to return in output_rt argument along with authenticated resources. Extend parse_fw callback to include SMC call to get resources from Trustzone and to leverage resource table parsing and mapping and unmapping code from the remoteproc framework. Reviewed-by: Konrad Dybcio Signed-off-by: Mukesh Ojha Link: https://lore.kernel.org/r/20260105-kvmrprocv10-v10-12-022e96815380@oss.qualcomm.com Signed-off-by: Bjorn Andersson --- drivers/remoteproc/qcom_q6v5_pas.c | 59 +++++++++++++++++++++++++++++- 1 file changed, 57 insertions(+), 2 deletions(-) diff --git a/drivers/remoteproc/qcom_q6v5_pas.c b/drivers/remoteproc/qcom_q6v5_pas.c index f746d2f39a1d..e0c7bf94bfc6 100644 --- a/drivers/remoteproc/qcom_q6v5_pas.c +++ b/drivers/remoteproc/qcom_q6v5_pas.c @@ -413,6 +413,61 @@ static void *qcom_pas_da_to_va(struct rproc *rproc, u64 da, size_t len, bool *is return pas->mem_region + offset; } +static int qcom_pas_parse_firmware(struct rproc *rproc, const struct firmware *fw) +{ + struct qcom_pas *pas = rproc->priv; + struct resource_table *table = NULL; + size_t output_rt_size; + void *output_rt; + size_t table_sz; + int ret; + + ret = qcom_register_dump_segments(rproc, fw); + if (ret) { + dev_err(pas->dev, "Error in registering dump segments\n"); + return ret; + } + + if (!rproc->has_iommu) + return 0; + + ret = rproc_elf_load_rsc_table(rproc, fw); + if (ret) + dev_dbg(&rproc->dev, "Failed to load resource table from firmware\n"); + + table = rproc->table_ptr; + table_sz = rproc->table_sz; + + /* + * The resources consumed by Qualcomm remote processors fall into two categories: + * static (such as the memory carveouts for the rproc firmware) and dynamic (like + * shared memory pools). Both are managed by a Qualcomm hypervisor (such as QHEE + * or Gunyah), if one is present. Otherwise, a resource table must be retrieved + * via an SCM call. That table will list all dynamic resources (if any) and possibly + * the static ones. The static resources may also come from a resource table embedded + * in the rproc firmware instead. + * + * Here, we call rproc_elf_load_rsc_table() to check firmware binary has resources + * or not and if it is not having then we pass NULL and zero as input resource + * table pointer and size respectively to the argument of qcom_scm_pas_get_rsc_table() + * and this is even true for Qualcomm remote processor who does follow remoteproc + * framework. + */ + output_rt = qcom_scm_pas_get_rsc_table(pas->pas_ctx, table, table_sz, &output_rt_size); + ret = IS_ERR(output_rt) ? PTR_ERR(output_rt) : 0; + if (ret) { + dev_err(pas->dev, "Error in getting resource table: %d\n", ret); + return ret; + } + + kfree(rproc->cached_table); + rproc->cached_table = output_rt; + rproc->table_ptr = rproc->cached_table; + rproc->table_sz = output_rt_size; + + return ret; +} + static unsigned long qcom_pas_panic(struct rproc *rproc) { struct qcom_pas *pas = rproc->priv; @@ -425,7 +480,7 @@ static const struct rproc_ops qcom_pas_ops = { .start = qcom_pas_start, .stop = qcom_pas_stop, .da_to_va = qcom_pas_da_to_va, - .parse_fw = qcom_register_dump_segments, + .parse_fw = qcom_pas_parse_firmware, .load = qcom_pas_load, .panic = qcom_pas_panic, }; @@ -435,7 +490,7 @@ static const struct rproc_ops qcom_pas_minidump_ops = { .start = qcom_pas_start, .stop = qcom_pas_stop, .da_to_va = qcom_pas_da_to_va, - .parse_fw = qcom_register_dump_segments, + .parse_fw = qcom_pas_parse_firmware, .load = qcom_pas_load, .panic = qcom_pas_panic, .coredump = qcom_pas_minidump, From 5c720260e840b508053dd5338577e0175ef31739 Mon Sep 17 00:00:00 2001 From: Mukesh Ojha Date: Mon, 5 Jan 2026 18:53:01 +0530 Subject: [PATCH 13/13] remoteproc: qcom: pas: Enable Secure PAS support with IOMMU managed by Linux Most Qualcomm platforms feature Gunyah hypervisor, which typically handles IOMMU configuration. This includes mapping memory regions and device memory resources for remote processors by intercepting qcom_scm_pas_auth_and_reset() calls. These mappings are later removed during teardown. Additionally, SHM bridge setup is required to enable memory protection for both remoteproc metadata and its memory regions. When the aforementioned hypervisor is absent, the operating system must perform these configurations instead. When Linux runs as the hypervisor (@ EL2) on a SoC, it will have its own device tree overlay file that specifies the firmware stream ID now managed by Linux for a particular remote processor. If the iommus property is specified in the remoteproc device tree node, it indicates that IOMMU configuration must be handled by Linux. In this case, the has_iommu flag is set for the remote processor, which ensures that the resource table, carveouts, and SHM bridge are properly configured before memory is passed to TrustZone for authentication. Otherwise, the has_iommu flag remains unset, which indicates default behavior. Enables Secure PAS support for remote processors when IOMMU configuration is managed by Linux. Signed-off-by: Mukesh Ojha Link: https://lore.kernel.org/r/20260105-kvmrprocv10-v10-13-022e96815380@oss.qualcomm.com Signed-off-by: Bjorn Andersson --- drivers/remoteproc/qcom_q6v5_pas.c | 48 ++++++++++++++++++++++++++---- 1 file changed, 43 insertions(+), 5 deletions(-) diff --git a/drivers/remoteproc/qcom_q6v5_pas.c b/drivers/remoteproc/qcom_q6v5_pas.c index e0c7bf94bfc6..46204da046fa 100644 --- a/drivers/remoteproc/qcom_q6v5_pas.c +++ b/drivers/remoteproc/qcom_q6v5_pas.c @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -255,6 +256,22 @@ release_dtb_metadata: return ret; } +static void qcom_pas_unmap_carveout(struct rproc *rproc, phys_addr_t mem_phys, size_t size) +{ + if (rproc->has_iommu) + iommu_unmap(rproc->domain, mem_phys, size); +} + +static int qcom_pas_map_carveout(struct rproc *rproc, phys_addr_t mem_phys, size_t size) +{ + int ret = 0; + + if (rproc->has_iommu) + ret = iommu_map(rproc->domain, mem_phys, mem_phys, size, + IOMMU_READ | IOMMU_WRITE, GFP_KERNEL); + return ret; +} + static int qcom_pas_start(struct rproc *rproc) { struct qcom_pas *pas = rproc->priv; @@ -289,11 +306,15 @@ static int qcom_pas_start(struct rproc *rproc) } if (pas->dtb_pas_id) { - ret = qcom_scm_pas_auth_and_reset(pas->dtb_pas_id); + ret = qcom_pas_map_carveout(rproc, pas->dtb_mem_phys, pas->dtb_mem_size); + if (ret) + goto disable_px_supply; + + ret = qcom_scm_pas_prepare_and_auth_reset(pas->dtb_pas_ctx); if (ret) { dev_err(pas->dev, "failed to authenticate dtb image and release reset\n"); - goto disable_px_supply; + goto unmap_dtb_carveout; } } @@ -304,18 +325,22 @@ static int qcom_pas_start(struct rproc *rproc) qcom_pil_info_store(pas->info_name, pas->mem_phys, pas->mem_size); - ret = qcom_scm_pas_auth_and_reset(pas->pas_id); + ret = qcom_pas_map_carveout(rproc, pas->mem_phys, pas->mem_size); + if (ret) + goto release_pas_metadata; + + ret = qcom_scm_pas_prepare_and_auth_reset(pas->pas_ctx); if (ret) { dev_err(pas->dev, "failed to authenticate image and release reset\n"); - goto release_pas_metadata; + goto unmap_carveout; } ret = qcom_q6v5_wait_for_start(&pas->q6v5, msecs_to_jiffies(5000)); if (ret == -ETIMEDOUT) { dev_err(pas->dev, "start timed out\n"); qcom_scm_pas_shutdown(pas->pas_id); - goto release_pas_metadata; + goto unmap_carveout; } qcom_scm_pas_metadata_release(pas->pas_ctx); @@ -327,10 +352,16 @@ static int qcom_pas_start(struct rproc *rproc) return 0; +unmap_carveout: + qcom_pas_unmap_carveout(rproc, pas->mem_phys, pas->mem_size); release_pas_metadata: qcom_scm_pas_metadata_release(pas->pas_ctx); if (pas->dtb_pas_id) qcom_scm_pas_metadata_release(pas->dtb_pas_ctx); + +unmap_dtb_carveout: + if (pas->dtb_pas_id) + qcom_pas_unmap_carveout(rproc, pas->dtb_mem_phys, pas->dtb_mem_size); disable_px_supply: if (pas->px_supply) regulator_disable(pas->px_supply); @@ -386,8 +417,12 @@ static int qcom_pas_stop(struct rproc *rproc) ret = qcom_scm_pas_shutdown(pas->dtb_pas_id); if (ret) dev_err(pas->dev, "failed to shutdown dtb: %d\n", ret); + + qcom_pas_unmap_carveout(rproc, pas->dtb_mem_phys, pas->dtb_mem_size); } + qcom_pas_unmap_carveout(rproc, pas->mem_phys, pas->mem_size); + handover = qcom_q6v5_unprepare(&pas->q6v5); if (handover) qcom_pas_handover(&pas->q6v5); @@ -738,6 +773,7 @@ static int qcom_pas_probe(struct platform_device *pdev) return -ENOMEM; } + rproc->has_iommu = of_property_present(pdev->dev.of_node, "iommus"); rproc->auto_boot = desc->auto_boot; rproc_coredump_set_elf_info(rproc, ELFCLASS32, EM_NONE); @@ -817,6 +853,8 @@ static int qcom_pas_probe(struct platform_device *pdev) goto remove_ssr_sysmon; } + pas->pas_ctx->use_tzmem = rproc->has_iommu; + pas->dtb_pas_ctx->use_tzmem = rproc->has_iommu; ret = rproc_add(rproc); if (ret) goto remove_ssr_sysmon;