From 3e529f57931417120fab700afeef6e49553250d5 Mon Sep 17 00:00:00 2001 From: Muhammad Amirul Asyraf Mohamad Jamian Date: Thu, 16 Apr 2026 00:22:06 -0700 Subject: firmware: stratix10-svc: Return -EOPNOTSUPP when ATF async unsupported Add a 'supported' flag to struct stratix10_async_ctrl to indicate whether the secure firmware supports SIP SVC v3 asynchronous communication. When the ATF version check in stratix10_svc_async_init() fails, set supported=false and return -EOPNOTSUPP instead of -EINVAL. This allows callers to distinguish between "async not supported by this ATF version" (-EOPNOTSUPP) and "programming error / bad argument" (-EINVAL), and take appropriate action (e.g. fall back to synchronous V1 SMC path) rather than treating both as fatal. Also update stratix10_svc_add_async_client() to return -EOPNOTSUPP immediately when async is not supported, rather than -EINVAL from the !actrl->initialized check, so client drivers receive a consistent and meaningful error code. This patch is a prerequisite for the following fix and must be applied together with it to correctly restore functionality on old ATF versions. Fixes: bcb9f4f07061 ("firmware: stratix10-svc: Add support for async communication") Cc: stable@vger.kernel.org Suggested-by: Anders Hedlund Signed-off-by: Mahesh Rao Signed-off-by: Muhammad Amirul Asyraf Mohamad Jamian Signed-off-by: Dinh Nguyen --- drivers/firmware/stratix10-svc.c | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/drivers/firmware/stratix10-svc.c b/drivers/firmware/stratix10-svc.c index e9e35d67ef96..8a4f18602f36 100644 --- a/drivers/firmware/stratix10-svc.c +++ b/drivers/firmware/stratix10-svc.c @@ -212,6 +212,7 @@ struct stratix10_async_chan { /** * struct stratix10_async_ctrl - Control structure for Stratix10 * asynchronous operations + * @supported: Flag indicating whether the system supports async operations * @initialized: Flag indicating whether the control structure has * been initialized * @invoke_fn: Function pointer for invoking Stratix10 service calls @@ -228,6 +229,7 @@ struct stratix10_async_chan { */ struct stratix10_async_ctrl { + bool supported; bool initialized; void (*invoke_fn)(struct stratix10_async_ctrl *actrl, const struct arm_smccc_1_2_regs *args, @@ -1103,6 +1105,7 @@ EXPORT_SYMBOL_GPL(stratix10_svc_request_channel_byname); * Return: 0 on success, or a negative error code on failure: * -EINVAL if the channel is NULL or the async controller is * not initialized. + * -EOPNOTSUPP if async operations are not supported. * -EALREADY if the async channel is already allocated. * -ENOMEM if memory allocation fails. * Other negative values if ID allocation fails. @@ -1121,6 +1124,9 @@ int stratix10_svc_add_async_client(struct stratix10_svc_chan *chan, ctrl = chan->ctrl; actrl = &ctrl->actrl; + if (!actrl->supported) + return -EOPNOTSUPP; + if (!actrl->initialized) { dev_err(ctrl->dev, "Async controller not initialized\n"); return -EINVAL; @@ -1562,6 +1568,7 @@ static inline void stratix10_smc_1_2(struct stratix10_async_ctrl *actrl, * initialized, -ENOMEM if memory allocation fails, * -EADDRINUSE if the client ID is already reserved, or other * negative error codes on failure. + * -EOPNOTSUPP if system doesn't support async operations. */ static int stratix10_svc_async_init(struct stratix10_svc_controller *controller) { @@ -1585,10 +1592,12 @@ static int stratix10_svc_async_init(struct stratix10_svc_controller *controller) !(res.a1 > ASYNC_ATF_MINIMUM_MAJOR_VERSION || (res.a1 == ASYNC_ATF_MINIMUM_MAJOR_VERSION && res.a2 >= ASYNC_ATF_MINIMUM_MINOR_VERSION))) { - dev_err(dev, - "Intel Service Layer Driver: ATF version is not compatible for async operation\n"); - return -EINVAL; + dev_info(dev, + "Intel Service Layer Driver: ATF version is not compatible for async operation\n"); + actrl->supported = false; + return -EOPNOTSUPP; } + actrl->supported = true; actrl->invoke_fn = stratix10_smc_1_2; -- cgit v1.2.3 From 371aa062219a0af108fb8992f0759d1bac1e8c91 Mon Sep 17 00:00:00 2001 From: Muhammad Amirul Asyraf Mohamad Jamian Date: Thu, 16 Apr 2026 00:22:07 -0700 Subject: firmware: stratix10-svc: Don't fail probe when async ops unsupported When the ATF version is too old to support SIP SVC v3 asynchronous operations (e.g. ATF 2.5), stratix10_svc_async_init() returns -EOPNOTSUPP. The probe function currently treats any non-zero return as fatal and aborts, logging: stratix10-svc firmware:svc: Intel Service Layer Driver: ATF version \ is not compatible for async operation stratix10-svc firmware:svc: probe with driver stratix10-svc failed \ with error -95 This prevents the SVC driver from loading entirely, causing all dependent client drivers (hwmon, RSU, FCS) to also fail to probe even though they can operate correctly via the synchronous V1 SMC path. Fix this by treating -EOPNOTSUPP from stratix10_svc_async_init() as a non-fatal degraded condition. The driver loads in sync-only mode and logs: stratix10-svc firmware:svc: Intel Service Layer Driver Initialized \ (sync-only mode) Fixes: bcb9f4f07061 ("firmware: stratix10-svc: Add support for async communication") Cc: stable@vger.kernel.org Signed-off-by: Muhammad Amirul Asyraf Mohamad Jamian Signed-off-by: Dinh Nguyen --- drivers/firmware/stratix10-svc.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/firmware/stratix10-svc.c b/drivers/firmware/stratix10-svc.c index 8a4f18602f36..39eb78f5905b 100644 --- a/drivers/firmware/stratix10-svc.c +++ b/drivers/firmware/stratix10-svc.c @@ -1961,10 +1961,14 @@ static int stratix10_svc_drv_probe(struct platform_device *pdev) init_completion(&controller->complete_status); ret = stratix10_svc_async_init(controller); - if (ret) { + if (ret == -EOPNOTSUPP) { + dev_info(dev, "Intel Service Layer Driver Initialized (sync-only mode)\n"); + } else if (ret) { dev_dbg(dev, "Intel Service Layer Driver: Error on stratix10_svc_async_init %d\n", ret); goto err_destroy_pool; + } else { + dev_info(dev, "Intel Service Layer Driver Initialized\n"); } fifo_size = sizeof(struct stratix10_svc_data) * SVC_NUM_DATA_IN_FIFO; -- cgit v1.2.3 From bfd2eb9bba548a8f63c3339bb1fb9a2031a42d86 Mon Sep 17 00:00:00 2001 From: Dinh Nguyen Date: Wed, 20 May 2026 21:54:57 -0500 Subject: firmware: stratix10-rsu: Fix NULL deref on rsu_send_msg() timeout in probe rsu_send_msg() can return -ETIMEDOUT when wait_for_completion_interruptible_timeout() fires while the SMC call is still pending. In stratix10_rsu_probe(), the error paths for COMMAND_RSU_DCMF_VERSION, COMMAND_RSU_DCMF_STATUS, COMMAND_RSU_MAX_RETRY and COMMAND_RSU_GET_SPT_TABLE call stratix10_svc_free_channel() - which sets chan->scl to NULL - but then fall through and queue the next request on the same channel. The next svc kthread that runs will dereference pdata->chan->scl in its receive callback path, triggering a NULL pointer dereference identical to the one fixed by commit c45f7263100c ("firmware: stratix10-rsu: Fix NULL pointer dereference when RSU is disabled") for the COMMAND_RSU_STATUS path. Apply the same cleanup pattern to the remaining failure paths: remove the async client, free the channel, and return early so no further messages are queued on a channel whose scl has been cleared. While at it, clean up stratix10_rsu_probe() in two ways without changing behavior: - Drop redundant zero-initialization of fields already cleared by devm_kzalloc(): client.receive_cb, status.* and spt0/1_address (INVALID_SPT_ADDRESS is 0x0). - Replace five identical 3-line error-cleanup blocks (stratix10_svc_remove_async_client() + stratix10_svc_free_channel() + return ret) with goto labels (remove_async_client, free_channel), matching the standard kernel resource-unwinding pattern and making it easier to extend the probe sequence without forgetting matching cleanup. Also move init_completion() next to mutex_init() so sync-primitive initialization is grouped before anything that could trigger a callback. Fixes: 15847537b623 ("firmware: stratix10-rsu: Migrate RSU driver to use stratix10 asynchronous framework.") Cc: stable@kernel.org Assisted-by: Claude:claude-4.7-opus-high Cursor Signed-off-by: Dinh Nguyen --- v2: Add a minor clean-up of the function stratix10_rsu_probe() to have a centralize exit for all the rsu_send_async_msg() and rsu_send_msg(). --- drivers/firmware/stratix10-rsu.c | 45 ++++++++++++++++++---------------------- 1 file changed, 20 insertions(+), 25 deletions(-) diff --git a/drivers/firmware/stratix10-rsu.c b/drivers/firmware/stratix10-rsu.c index e1912108a0fe..2a7a0f774389 100644 --- a/drivers/firmware/stratix10-rsu.c +++ b/drivers/firmware/stratix10-rsu.c @@ -723,15 +723,9 @@ static int stratix10_rsu_probe(struct platform_device *pdev) return -ENOMEM; priv->client.dev = dev; - priv->client.receive_cb = NULL; priv->client.priv = priv; - priv->status.current_image = 0; - priv->status.fail_image = 0; - priv->status.error_location = 0; - priv->status.error_details = 0; - priv->status.version = 0; - priv->status.state = 0; priv->retry_counter = INVALID_RETRY_COUNTER; + priv->max_retry = INVALID_RETRY_COUNTER; priv->dcmf_version.dcmf0 = INVALID_DCMF_VERSION; priv->dcmf_version.dcmf1 = INVALID_DCMF_VERSION; priv->dcmf_version.dcmf2 = INVALID_DCMF_VERSION; @@ -740,11 +734,11 @@ static int stratix10_rsu_probe(struct platform_device *pdev) priv->dcmf_status.dcmf1 = INVALID_DCMF_STATUS; priv->dcmf_status.dcmf2 = INVALID_DCMF_STATUS; priv->dcmf_status.dcmf3 = INVALID_DCMF_STATUS; - priv->max_retry = INVALID_RETRY_COUNTER; - priv->spt0_address = INVALID_SPT_ADDRESS; - priv->spt1_address = INVALID_SPT_ADDRESS; + /* spt0/1_address and status fields default to 0 from kzalloc */ mutex_init(&priv->lock); + init_completion(&priv->completion); + priv->chan = stratix10_svc_request_channel_byname(&priv->client, SVC_CLIENT_RSU); if (IS_ERR(priv->chan)) { @@ -756,11 +750,9 @@ static int stratix10_rsu_probe(struct platform_device *pdev) ret = stratix10_svc_add_async_client(priv->chan, false); if (ret) { dev_err(dev, "failed to add async client\n"); - stratix10_svc_free_channel(priv->chan); - return ret; + goto free_channel; } - init_completion(&priv->completion); platform_set_drvdata(pdev, priv); /* get the initial state from firmware */ @@ -768,41 +760,44 @@ static int stratix10_rsu_probe(struct platform_device *pdev) rsu_async_status_callback); if (ret) { dev_err(dev, "Error, getting RSU status %i\n", ret); - stratix10_svc_remove_async_client(priv->chan); - stratix10_svc_free_channel(priv->chan); - return ret; + goto remove_async_client; } /* get DCMF version from firmware */ - ret = rsu_send_msg(priv, COMMAND_RSU_DCMF_VERSION, - 0, rsu_dcmf_version_callback); + ret = rsu_send_msg(priv, COMMAND_RSU_DCMF_VERSION, 0, + rsu_dcmf_version_callback); if (ret) { dev_err(dev, "Error, getting DCMF version %i\n", ret); - stratix10_svc_free_channel(priv->chan); + goto remove_async_client; } - ret = rsu_send_msg(priv, COMMAND_RSU_DCMF_STATUS, - 0, rsu_dcmf_status_callback); + ret = rsu_send_msg(priv, COMMAND_RSU_DCMF_STATUS, 0, + rsu_dcmf_status_callback); if (ret) { dev_err(dev, "Error, getting DCMF status %i\n", ret); - stratix10_svc_free_channel(priv->chan); + goto remove_async_client; } ret = rsu_send_msg(priv, COMMAND_RSU_MAX_RETRY, 0, rsu_max_retry_callback); if (ret) { dev_err(dev, "Error, getting RSU max retry %i\n", ret); - stratix10_svc_free_channel(priv->chan); + goto remove_async_client; } - ret = rsu_send_async_msg(dev, priv, COMMAND_RSU_GET_SPT_TABLE, 0, rsu_async_get_spt_table_callback); if (ret) { dev_err(dev, "Error, getting SPT table %i\n", ret); - stratix10_svc_free_channel(priv->chan); + goto remove_async_client; } + return 0; + +remove_async_client: + stratix10_svc_remove_async_client(priv->chan); +free_channel: + stratix10_svc_free_channel(priv->chan); return ret; } -- cgit v1.2.3 From ea41020b9018e31c2ea7e9d89021e3e6d7470883 Mon Sep 17 00:00:00 2001 From: Andre Heider Date: Sat, 30 May 2026 21:43:39 +0100 Subject: nvmem: layouts: onie-tlv: fix hang on unknown types The EEPROM on my board has a vendor specific entry of type 0x41. When stumbling upon that, this driver hangs in an endless loop. Fix it by keep incrementing the offset on unknown entries, so the loop will eventually stop. Fixes: d3c0d12f6474 ("nvmem: layouts: onie-tlv: Add new layout driver") Cc: Stable@vger.kernel.org Signed-off-by: Andre Heider Reviewed-by: Miquel Raynal Signed-off-by: Srinivas Kandagatla Link: https://patch.msgid.link/20260530204340.116743-2-srini@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/nvmem/layouts/onie-tlv.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/nvmem/layouts/onie-tlv.c b/drivers/nvmem/layouts/onie-tlv.c index 0967a32319a2..8b0f3c1b8a0e 100644 --- a/drivers/nvmem/layouts/onie-tlv.c +++ b/drivers/nvmem/layouts/onie-tlv.c @@ -119,7 +119,7 @@ static int onie_tlv_add_cells(struct device *dev, struct nvmem_device *nvmem, cell.name = onie_tlv_cell_name(tlv.type); if (!cell.name) - continue; + goto next; cell.offset = hdr_len + offset + sizeof(tlv.type) + sizeof(tlv.len); cell.bytes = tlv.len; @@ -132,6 +132,7 @@ static int onie_tlv_add_cells(struct device *dev, struct nvmem_device *nvmem, return ret; } +next: offset += sizeof(tlv) + tlv.len; } -- cgit v1.2.3 From 5b6b6fc491899d583eaa75344e094796ae9b530b Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Sat, 30 May 2026 21:43:40 +0100 Subject: nvmem: core: fix use-after-free bugs in error paths Fix several instances of error paths in which we call __nvmem_device_put() - which may end up freeing the underlying memory and other resources - and then keep on using the nvmem structure. Always put the reference to the nvmem device as the last step before returning the error code. Cc: stable@vger.kernel.org Fixes: 7ae6478b304b ("nvmem: core: rework nvmem cell instance creation") Fixes: e888d445ac33 ("nvmem: resolve cells from DT at registration time") Signed-off-by: Bartosz Golaszewski Signed-off-by: Srinivas Kandagatla Link: https://patch.msgid.link/20260530204340.116743-3-srini@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/nvmem/core.c | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/drivers/nvmem/core.c b/drivers/nvmem/core.c index 311cb2e5a5c0..e871181751f3 100644 --- a/drivers/nvmem/core.c +++ b/drivers/nvmem/core.c @@ -1468,18 +1468,16 @@ struct nvmem_cell *of_nvmem_cell_get(struct device_node *np, const char *id) cell_entry = nvmem_find_cell_entry_by_node(nvmem, cell_np); of_node_put(cell_np); if (!cell_entry) { - __nvmem_device_put(nvmem); nvmem_layout_module_put(nvmem); - if (nvmem->layout) - return ERR_PTR(-EPROBE_DEFER); - else - return ERR_PTR(-ENOENT); + ret = nvmem->layout ? -EPROBE_DEFER : -ENOENT; + __nvmem_device_put(nvmem); + return ERR_PTR(ret); } cell = nvmem_create_cell(cell_entry, id, cell_index); if (IS_ERR(cell)) { - __nvmem_device_put(nvmem); nvmem_layout_module_put(nvmem); + __nvmem_device_put(nvmem); } return cell; @@ -1593,8 +1591,8 @@ void nvmem_cell_put(struct nvmem_cell *cell) kfree_const(cell->id); kfree(cell); - __nvmem_device_put(nvmem); nvmem_layout_module_put(nvmem); + __nvmem_device_put(nvmem); } EXPORT_SYMBOL_GPL(nvmem_cell_put); -- cgit v1.2.3 From 120134fe75c6b0ae38f14eb8b548ad1e5761f912 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Sat, 30 May 2026 21:44:14 +0100 Subject: slimbus: qcom-ngd-ctrl: fix OF node refcount Platform devices created with platform_device_alloc() call platform_device_release() when the last reference to the device's kobject is dropped. This function calls of_node_put() unconditionally. This works fine for devices created with platform_device_register_full() but users of the split approach (platform_device_alloc() + platform_device_add()) must bump the reference of the of_node they assign manually. Add the missing call to of_node_get(). Cc: stable@vger.kernel.org Fixes: 917809e2280b ("slimbus: ngd: Add qcom SLIMBus NGD driver") Signed-off-by: Bartosz Golaszewski Signed-off-by: Srinivas Kandagatla Link: https://patch.msgid.link/20260530204421.116824-2-srini@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/slimbus/qcom-ngd-ctrl.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/slimbus/qcom-ngd-ctrl.c b/drivers/slimbus/qcom-ngd-ctrl.c index 1ed6be6e85d2..428266949fdd 100644 --- a/drivers/slimbus/qcom-ngd-ctrl.c +++ b/drivers/slimbus/qcom-ngd-ctrl.c @@ -1542,7 +1542,7 @@ static int of_qcom_slim_ngd_register(struct device *parent, kfree(ngd); return ret; } - ngd->pdev->dev.of_node = node; + ngd->pdev->dev.of_node = of_node_get(node); ctrl->ngd = ngd; ret = platform_device_add(ngd->pdev); -- cgit v1.2.3 From 8663e8334d7b6007f5d8a4e5dd270246f35107a6 Mon Sep 17 00:00:00 2001 From: Bjorn Andersson Date: Sat, 30 May 2026 21:44:15 +0100 Subject: slimbus: qcom-ngd-ctrl: Fix up platform_driver registration Device drivers should not invoke platform_driver_register()/unregister() in their probe and remove paths. They should further not rely on platform_driver_unregister() as their only means of "deleting" their child devices. Introduce a helper to unregister the child device and move the platform_driver_register()/unregister() to module_init()/exit(). Fixes: 917809e2280b ("slimbus: ngd: Add qcom SLIMBus NGD driver") Cc: stable@vger.kernel.org Reviewed-by: Dmitry Baryshkov Reviewed-by: Mukesh Ojha Signed-off-by: Bjorn Andersson Signed-off-by: Srinivas Kandagatla Link: https://patch.msgid.link/20260530204421.116824-3-srini@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/slimbus/qcom-ngd-ctrl.c | 36 +++++++++++++++++++++++++++++++++--- 1 file changed, 33 insertions(+), 3 deletions(-) diff --git a/drivers/slimbus/qcom-ngd-ctrl.c b/drivers/slimbus/qcom-ngd-ctrl.c index 428266949fdd..47ba9fb34d25 100644 --- a/drivers/slimbus/qcom-ngd-ctrl.c +++ b/drivers/slimbus/qcom-ngd-ctrl.c @@ -1560,6 +1560,13 @@ static int of_qcom_slim_ngd_register(struct device *parent, return -ENODEV; } +static void qcom_slim_ngd_unregister(struct qcom_slim_ngd_ctrl *ctrl) +{ + struct qcom_slim_ngd *ngd = ctrl->ngd; + + platform_device_del(ngd->pdev); +} + static int qcom_slim_ngd_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; @@ -1662,7 +1669,6 @@ static int qcom_slim_ngd_ctrl_probe(struct platform_device *pdev) goto err_pdr_lookup; } - platform_driver_register(&qcom_slim_ngd_driver); return of_qcom_slim_ngd_register(dev, ctrl); err_pdr_alloc: @@ -1676,7 +1682,9 @@ err_pdr_lookup: static void qcom_slim_ngd_ctrl_remove(struct platform_device *pdev) { - platform_driver_unregister(&qcom_slim_ngd_driver); + struct qcom_slim_ngd_ctrl *ctrl = platform_get_drvdata(pdev); + + qcom_slim_ngd_unregister(ctrl); } static void qcom_slim_ngd_remove(struct platform_device *pdev) @@ -1752,6 +1760,28 @@ static struct platform_driver qcom_slim_ngd_driver = { }, }; -module_platform_driver(qcom_slim_ngd_ctrl_driver); +static int qcom_slim_ngd_init(void) +{ + int ret; + + ret = platform_driver_register(&qcom_slim_ngd_driver); + if (ret) + return ret; + + ret = platform_driver_register(&qcom_slim_ngd_ctrl_driver); + if (ret) + platform_driver_unregister(&qcom_slim_ngd_driver); + + return ret; +} + +static void qcom_slim_ngd_exit(void) +{ + platform_driver_unregister(&qcom_slim_ngd_ctrl_driver); + platform_driver_unregister(&qcom_slim_ngd_driver); +} + +module_init(qcom_slim_ngd_init); +module_exit(qcom_slim_ngd_exit); MODULE_LICENSE("GPL v2"); MODULE_DESCRIPTION("Qualcomm SLIMBus NGD controller"); -- cgit v1.2.3 From 2c22ff152d380ec3d3af099fa05d0ac5ca9b4c1e Mon Sep 17 00:00:00 2001 From: Bjorn Andersson Date: Sat, 30 May 2026 21:44:16 +0100 Subject: slimbus: qcom-ngd-ctrl: Fix probe error path ordering qcom_slim_ngd_ctrl_probe() first registers the SSR callback then allocates the PDR context, as such the error path needs to come in opposite order to allow us to unroll each step. Fixes: 16f14551d0df ("slimbus: qcom-ngd: cleanup in probe error path") Cc: stable@vger.kernel.org Reviewed-by: Dmitry Baryshkov Reviewed-by: Mukesh Ojha Signed-off-by: Bjorn Andersson Signed-off-by: Srinivas Kandagatla Link: https://patch.msgid.link/20260530204421.116824-4-srini@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/slimbus/qcom-ngd-ctrl.c | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/drivers/slimbus/qcom-ngd-ctrl.c b/drivers/slimbus/qcom-ngd-ctrl.c index 47ba9fb34d25..6b91a6f11cb6 100644 --- a/drivers/slimbus/qcom-ngd-ctrl.c +++ b/drivers/slimbus/qcom-ngd-ctrl.c @@ -1660,22 +1660,21 @@ static int qcom_slim_ngd_ctrl_probe(struct platform_device *pdev) if (IS_ERR(ctrl->pdr)) { ret = dev_err_probe(dev, PTR_ERR(ctrl->pdr), "Failed to init PDR handle\n"); - goto err_pdr_alloc; + goto err_unregister_ssr; } pds = pdr_add_lookup(ctrl->pdr, "avs/audio", "msm/adsp/audio_pd"); if (IS_ERR(pds) && PTR_ERR(pds) != -EALREADY) { ret = dev_err_probe(dev, PTR_ERR(pds), "pdr add lookup failed\n"); - goto err_pdr_lookup; + goto err_pdr_release; } return of_qcom_slim_ngd_register(dev, ctrl); -err_pdr_alloc: - qcom_unregister_ssr_notifier(ctrl->notifier, &ctrl->nb); - -err_pdr_lookup: +err_pdr_release: pdr_handle_release(ctrl->pdr); +err_unregister_ssr: + qcom_unregister_ssr_notifier(ctrl->notifier, &ctrl->nb); return ret; } -- cgit v1.2.3 From 960b53a3f76fa214c2fc493734ae7b3c5e713bbf Mon Sep 17 00:00:00 2001 From: Bjorn Andersson Date: Sat, 30 May 2026 21:44:17 +0100 Subject: slimbus: qcom-ngd-ctrl: Correct PDR and SSR cleanup ownership PDR and SSR callbacks are registred from the controller probe function, but currently released from the child device's remove function. The remove() function should only be unwinding what was done in the same device's probe() function. Fixes: 917809e2280b ("slimbus: ngd: Add qcom SLIMBus NGD driver") Cc: stable@vger.kernel.org Reviewed-by: Dmitry Baryshkov Reviewed-by: Mukesh Ojha Signed-off-by: Bjorn Andersson Signed-off-by: Srinivas Kandagatla Link: https://patch.msgid.link/20260530204421.116824-5-srini@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/slimbus/qcom-ngd-ctrl.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/slimbus/qcom-ngd-ctrl.c b/drivers/slimbus/qcom-ngd-ctrl.c index 6b91a6f11cb6..e9238927cd2a 100644 --- a/drivers/slimbus/qcom-ngd-ctrl.c +++ b/drivers/slimbus/qcom-ngd-ctrl.c @@ -1683,6 +1683,9 @@ static void qcom_slim_ngd_ctrl_remove(struct platform_device *pdev) { struct qcom_slim_ngd_ctrl *ctrl = platform_get_drvdata(pdev); + pdr_handle_release(ctrl->pdr); + qcom_unregister_ssr_notifier(ctrl->notifier, &ctrl->nb); + qcom_slim_ngd_unregister(ctrl); } @@ -1691,8 +1694,6 @@ static void qcom_slim_ngd_remove(struct platform_device *pdev) struct qcom_slim_ngd_ctrl *ctrl = platform_get_drvdata(pdev); pm_runtime_disable(&pdev->dev); - pdr_handle_release(ctrl->pdr); - qcom_unregister_ssr_notifier(ctrl->notifier, &ctrl->nb); qcom_slim_ngd_enable(ctrl, false); qcom_slim_ngd_exit_dma(ctrl); qcom_slim_ngd_qmi_svc_event_deinit(&ctrl->qmi); -- cgit v1.2.3 From 2a9d50e9ea406e0c8735938484adc20515ef1b47 Mon Sep 17 00:00:00 2001 From: Bjorn Andersson Date: Sat, 30 May 2026 21:44:18 +0100 Subject: slimbus: qcom-ngd-ctrl: Register callbacks after creating the ngd When the remoteproc starts in parallel with the NGD driver being probed, or the remoteproc is already up when the PDR lookup is being registered, or in the theoretical event that we get an interrupt from the hardware, these callbacks will operate on uninitialized data. This result in issues to boot the affected boards. One such example can be seen in the following fault, where qcom_slim_ngd_ssr_pdr_notify() schedules work on the NULL ngd_up_work. [ 21.858578] ------------[ cut here ]------------ [ 21.858745] WARNING: kernel/workqueue.c:2338 at __queue_work+0x5e0/0x790, CPU#2: kworker/2:2/116 ... [ 21.859251] Call trace: [ 21.859255] __queue_work+0x5e0/0x790 (P) [ 21.859265] queue_work_on+0x6c/0xf0 [ 21.859273] qcom_slim_ngd_ssr_pdr_notify+0x110/0x150 [slim_qcom_ngd_ctrl] [ 21.859304] qcom_slim_ngd_ssr_notify+0x24/0x40 [slim_qcom_ngd_ctrl] [ 21.859318] notifier_call_chain+0xa4/0x230 [ 21.859329] srcu_notifier_call_chain+0x64/0xb8 [ 21.859338] ssr_notify_start+0x40/0x78 [qcom_common] [ 21.859355] rproc_start+0x130/0x230 [ 21.859367] rproc_boot+0x3d4/0x518 ... Move the enablement of interrupts, and the registration of SSR and PDR until after the NGD device has been registered. This could be further refined by moving initialization to the control driver probe and by removing the platform driver model from the picture. Fixes: 917809e2280b ("slimbus: ngd: Add qcom SLIMBus NGD driver") Cc: stable@vger.kernel.org Reviewed-by: Mukesh Ojha Signed-off-by: Bjorn Andersson Signed-off-by: Srinivas Kandagatla Link: https://patch.msgid.link/20260530204421.116824-6-srini@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/slimbus/qcom-ngd-ctrl.c | 45 ++++++++++++++++++++++++----------------- 1 file changed, 26 insertions(+), 19 deletions(-) diff --git a/drivers/slimbus/qcom-ngd-ctrl.c b/drivers/slimbus/qcom-ngd-ctrl.c index e9238927cd2a..c1f26b7de519 100644 --- a/drivers/slimbus/qcom-ngd-ctrl.c +++ b/drivers/slimbus/qcom-ngd-ctrl.c @@ -1609,6 +1609,7 @@ static int qcom_slim_ngd_ctrl_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct qcom_slim_ngd_ctrl *ctrl; + int irq; int ret; struct pdr_service *pds; @@ -1622,20 +1623,16 @@ static int qcom_slim_ngd_ctrl_probe(struct platform_device *pdev) if (IS_ERR(ctrl->base)) return PTR_ERR(ctrl->base); - ret = platform_get_irq(pdev, 0); - if (ret < 0) - return ret; + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; - ret = devm_request_irq(dev, ret, qcom_slim_ngd_interrupt, - IRQF_TRIGGER_HIGH, "slim-ngd", ctrl); + ret = devm_request_irq(dev, irq, qcom_slim_ngd_interrupt, + IRQF_TRIGGER_HIGH | IRQF_NO_AUTOEN, + "slim-ngd", ctrl); if (ret) return dev_err_probe(&pdev->dev, ret, "request IRQ failed\n"); - ctrl->nb.notifier_call = qcom_slim_ngd_ssr_notify; - ctrl->notifier = qcom_register_ssr_notifier("lpass", &ctrl->nb); - if (IS_ERR(ctrl->notifier)) - return PTR_ERR(ctrl->notifier); - ctrl->dev = dev; ctrl->framer.rootfreq = SLIM_ROOT_FREQ >> 3; ctrl->framer.superfreq = @@ -1657,24 +1654,34 @@ static int qcom_slim_ngd_ctrl_probe(struct platform_device *pdev) init_completion(&ctrl->qmi_up); ctrl->pdr = pdr_handle_alloc(slim_pd_status, ctrl); - if (IS_ERR(ctrl->pdr)) { - ret = dev_err_probe(dev, PTR_ERR(ctrl->pdr), - "Failed to init PDR handle\n"); - goto err_unregister_ssr; - } + if (IS_ERR(ctrl->pdr)) + return dev_err_probe(dev, PTR_ERR(ctrl->pdr), "Failed to init PDR handle\n"); + + ret = of_qcom_slim_ngd_register(dev, ctrl); + if (ret) + goto err_pdr_release; pds = pdr_add_lookup(ctrl->pdr, "avs/audio", "msm/adsp/audio_pd"); if (IS_ERR(pds) && PTR_ERR(pds) != -EALREADY) { ret = dev_err_probe(dev, PTR_ERR(pds), "pdr add lookup failed\n"); - goto err_pdr_release; + goto err_unregister_ngd; + } + + ctrl->nb.notifier_call = qcom_slim_ngd_ssr_notify; + ctrl->notifier = qcom_register_ssr_notifier("lpass", &ctrl->nb); + if (IS_ERR(ctrl->notifier)) { + ret = PTR_ERR(ctrl->notifier); + goto err_unregister_ngd; } - return of_qcom_slim_ngd_register(dev, ctrl); + enable_irq(irq); + return 0; + +err_unregister_ngd: + qcom_slim_ngd_unregister(ctrl); err_pdr_release: pdr_handle_release(ctrl->pdr); -err_unregister_ssr: - qcom_unregister_ssr_notifier(ctrl->notifier, &ctrl->nb); return ret; } -- cgit v1.2.3 From 07c564ea5fb859b7381429de935d5df4781947c6 Mon Sep 17 00:00:00 2001 From: Bjorn Andersson Date: Sat, 30 May 2026 21:44:19 +0100 Subject: slimbus: qcom-ngd-ctrl: Initialize controller resources in controller The work structs and work queue are controller resources, create and destroy them in the controller context. Creating them as part of the child device's probe path seems to be okay now that the controller's probe has been updated, but if for some reason the child does not probe successfully a SSR or PDR notification will schedule_work() on an uninitialized "ngd_up_work". Move the initialization of these controller resources to the controller probe function to avoid any issues, and to clarify the ownership. Fixes: 917809e2280b ("slimbus: ngd: Add qcom SLIMBus NGD driver") Cc: stable@vger.kernel.org Reviewed-by: Dmitry Baryshkov Reviewed-by: Mukesh Ojha Signed-off-by: Bjorn Andersson Signed-off-by: Srinivas Kandagatla Link: https://patch.msgid.link/20260530204421.116824-7-srini@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/slimbus/qcom-ngd-ctrl.c | 38 ++++++++++++++++---------------------- 1 file changed, 16 insertions(+), 22 deletions(-) diff --git a/drivers/slimbus/qcom-ngd-ctrl.c b/drivers/slimbus/qcom-ngd-ctrl.c index c1f26b7de519..540a62999655 100644 --- a/drivers/slimbus/qcom-ngd-ctrl.c +++ b/drivers/slimbus/qcom-ngd-ctrl.c @@ -1582,25 +1582,8 @@ static int qcom_slim_ngd_probe(struct platform_device *pdev) pm_runtime_enable(dev); pm_runtime_get_noresume(dev); ret = qcom_slim_ngd_qmi_svc_event_init(ctrl); - if (ret) { + if (ret) dev_err(&pdev->dev, "QMI service registration failed:%d", ret); - return ret; - } - - INIT_WORK(&ctrl->m_work, qcom_slim_ngd_master_worker); - INIT_WORK(&ctrl->ngd_up_work, qcom_slim_ngd_up_worker); - ctrl->mwq = create_singlethread_workqueue("ngd_master"); - if (!ctrl->mwq) { - dev_err(&pdev->dev, "Failed to start master worker\n"); - ret = -ENOMEM; - goto wq_err; - } - - return 0; -wq_err: - qcom_slim_ngd_qmi_svc_event_deinit(&ctrl->qmi); - if (ctrl->mwq) - destroy_workqueue(ctrl->mwq); return ret; } @@ -1653,9 +1636,18 @@ static int qcom_slim_ngd_ctrl_probe(struct platform_device *pdev) init_completion(&ctrl->qmi.qmi_comp); init_completion(&ctrl->qmi_up); + INIT_WORK(&ctrl->m_work, qcom_slim_ngd_master_worker); + INIT_WORK(&ctrl->ngd_up_work, qcom_slim_ngd_up_worker); + + ctrl->mwq = create_singlethread_workqueue("ngd_master"); + if (!ctrl->mwq) + return dev_err_probe(dev, -ENOMEM, "Failed to start master worker\n"); + ctrl->pdr = pdr_handle_alloc(slim_pd_status, ctrl); - if (IS_ERR(ctrl->pdr)) - return dev_err_probe(dev, PTR_ERR(ctrl->pdr), "Failed to init PDR handle\n"); + if (IS_ERR(ctrl->pdr)) { + ret = dev_err_probe(dev, PTR_ERR(ctrl->pdr), "Failed to init PDR handle\n"); + goto err_destroy_mwq; + } ret = of_qcom_slim_ngd_register(dev, ctrl); if (ret) @@ -1682,6 +1674,8 @@ err_unregister_ngd: qcom_slim_ngd_unregister(ctrl); err_pdr_release: pdr_handle_release(ctrl->pdr); +err_destroy_mwq: + destroy_workqueue(ctrl->mwq); return ret; } @@ -1694,6 +1688,8 @@ static void qcom_slim_ngd_ctrl_remove(struct platform_device *pdev) qcom_unregister_ssr_notifier(ctrl->notifier, &ctrl->nb); qcom_slim_ngd_unregister(ctrl); + + destroy_workqueue(ctrl->mwq); } static void qcom_slim_ngd_remove(struct platform_device *pdev) @@ -1704,8 +1700,6 @@ static void qcom_slim_ngd_remove(struct platform_device *pdev) qcom_slim_ngd_enable(ctrl, false); qcom_slim_ngd_exit_dma(ctrl); qcom_slim_ngd_qmi_svc_event_deinit(&ctrl->qmi); - if (ctrl->mwq) - destroy_workqueue(ctrl->mwq); kfree(ctrl->ngd); ctrl->ngd = NULL; -- cgit v1.2.3 From 6a003446b725c44b9e3ffa111b0effbaa2d43085 Mon Sep 17 00:00:00 2001 From: Bjorn Andersson Date: Sat, 30 May 2026 21:44:20 +0100 Subject: slimbus: qcom-ngd-ctrl: Balance pm_runtime enablement for NGD The pm_runtime_enable() and pm_runtime_use_autosuspend() calls are supposed to be balanced on exit, add these calls. Fixes: 917809e2280b ("slimbus: ngd: Add qcom SLIMBus NGD driver") Cc: stable@vger.kernel.org Signed-off-by: Bjorn Andersson Signed-off-by: Srinivas Kandagatla Link: https://patch.msgid.link/20260530204421.116824-8-srini@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/slimbus/qcom-ngd-ctrl.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/slimbus/qcom-ngd-ctrl.c b/drivers/slimbus/qcom-ngd-ctrl.c index 540a62999655..8b207efeadbc 100644 --- a/drivers/slimbus/qcom-ngd-ctrl.c +++ b/drivers/slimbus/qcom-ngd-ctrl.c @@ -1582,8 +1582,11 @@ static int qcom_slim_ngd_probe(struct platform_device *pdev) pm_runtime_enable(dev); pm_runtime_get_noresume(dev); ret = qcom_slim_ngd_qmi_svc_event_init(ctrl); - if (ret) + if (ret) { dev_err(&pdev->dev, "QMI service registration failed:%d", ret); + pm_runtime_dont_use_autosuspend(dev); + pm_runtime_disable(dev); + } return ret; } @@ -1696,6 +1699,7 @@ static void qcom_slim_ngd_remove(struct platform_device *pdev) { struct qcom_slim_ngd_ctrl *ctrl = platform_get_drvdata(pdev); + pm_runtime_dont_use_autosuspend(&pdev->dev); pm_runtime_disable(&pdev->dev); qcom_slim_ngd_enable(ctrl, false); qcom_slim_ngd_exit_dma(ctrl); -- cgit v1.2.3 From 55f2ea9ff83cc27a85526b14bc9b32f96a08d6ec Mon Sep 17 00:00:00 2001 From: Bjorn Andersson Date: Sat, 30 May 2026 21:44:21 +0100 Subject: slimbus: qcom-ngd-ctrl: Avoid ABBA on tx_lock/ctrl->lock During the SSR/PDR down notification the tx_lock is taken with the intent to provide synchronization with active DMA transfers. But during this period qcom_slim_ngd_down() is invoked, which ends up in slim_report_absent(), which takes the slim_controller lock. In multiple other codepaths these two locks are taken in the opposite order (i.e. slim_controller then tx_lock). The result is a lockdep splat, and a possible deadlock: rprocctl/449 is trying to acquire lock: ffff00009793e620 (&ctrl->lock){+.+.}-{4:4}, at: slim_report_absent (drivers/slimbus/core.c:322) slimbus but task is already holding lock: ffff00009793fb50 (&ctrl->tx_lock){+.+.}-{4:4}, at: qcom_slim_ngd_ssr_pdr_notify (drivers/slimbus/qcom-ngd-ctrl.c:1475) slim_qcom_ngd_ctrl which lock already depends on the new lock. Possible unsafe locking scenario: CPU0 CPU1 ---- ---- lock(&ctrl->tx_lock); lock(&ctrl->lock); lock(&ctrl->tx_lock); lock(&ctrl->lock); The assumption is that the comment refers to the desire to not call qcom_slim_ngd_exit_dma() while we have an ongoing DMA TX transaction. But any such transaction is initiated and completed within a single qcom_slim_ngd_xfer_msg(). Prior to calling qcom_slim_ngd_exit_dma() the slim_controller is torn down, all child devices are notified that the slimbus is gone and the child devices are removed. Stop taking the tx_lock in qcom_slim_ngd_ssr_pdr_notify() to avoid the deadlock. Fixes: a899d324863a ("slimbus: qcom-ngd-ctrl: add Sub System Restart support") Cc: stable@vger.kernel.org Signed-off-by: Bjorn Andersson Signed-off-by: Srinivas Kandagatla Link: https://patch.msgid.link/20260530204421.116824-9-srini@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/slimbus/qcom-ngd-ctrl.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/drivers/slimbus/qcom-ngd-ctrl.c b/drivers/slimbus/qcom-ngd-ctrl.c index 8b207efeadbc..3071e46d03be 100644 --- a/drivers/slimbus/qcom-ngd-ctrl.c +++ b/drivers/slimbus/qcom-ngd-ctrl.c @@ -1471,15 +1471,12 @@ static int qcom_slim_ngd_ssr_pdr_notify(struct qcom_slim_ngd_ctrl *ctrl, switch (action) { case QCOM_SSR_BEFORE_SHUTDOWN: case SERVREG_SERVICE_STATE_DOWN: - /* Make sure the last dma xfer is finished */ - mutex_lock(&ctrl->tx_lock); if (ctrl->state != QCOM_SLIM_NGD_CTRL_DOWN) { pm_runtime_get_noresume(ctrl->ctrl.dev); ctrl->state = QCOM_SLIM_NGD_CTRL_DOWN; qcom_slim_ngd_down(ctrl); qcom_slim_ngd_exit_dma(ctrl); } - mutex_unlock(&ctrl->tx_lock); break; case QCOM_SSR_AFTER_POWERUP: case SERVREG_SERVICE_STATE_UP: -- cgit v1.2.3 From e85eb5feca8e254905ffa6c57a3c99c89a674a0f Mon Sep 17 00:00:00 2001 From: Anandu Krishnan E Date: Sat, 30 May 2026 21:45:25 +0100 Subject: misc: fastrpc: fix use-after-free of fastrpc_user in workqueue context MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit There is a race between fastrpc_device_release() and the workqueue that processes DSP responses. When the user closes the file descriptor, fastrpc_device_release() frees the fastrpc_user structure. Concurrently, an in-flight DSP invocation can complete and fastrpc_rpmsg_callback() schedules context cleanup via schedule_work(&ctx->put_work). If the workqueue runs fastrpc_context_free() in parallel with or after fastrpc_device_release() has freed the user structure, it dereferences the freed fastrpc_user. Depending on the state of the context at the time of the race, any one of the following accesses can be hit: 1. fastrpc_buf_free() calls fastrpc_ipa_to_dma_addr(buf->fl->cctx, ...) to strip the SID bits from the stored IOVA before passing the physical address to dma_free_coherent(). 2. fastrpc_free_map() reads map->fl->cctx->vmperms[0].vmid to reconstruct the source permission bitmask needed for the qcom_scm_assign_mem() call that returns memory from the DSP VM back to HLOS. 3. fastrpc_free_map() acquires map->fl->lock to safely remove the map node from the fl->maps list. The resulting use-after-free manifests as: pc : fastrpc_buf_free+0x38/0x80 [fastrpc] lr : fastrpc_context_free+0xa8/0x1b0 [fastrpc] fastrpc_context_free+0xa8/0x1b0 [fastrpc] fastrpc_context_put_wq+0x78/0xa0 [fastrpc] process_one_work+0x180/0x450 worker_thread+0x26c/0x388 Add kref-based reference counting to fastrpc_user. Have each invoke context take a reference on the user at allocation time and release it when the context is freed. Release the initial reference in fastrpc_device_release() at file close. Move the teardown of the user structure — freeing pending contexts, maps, mmaps, and the channel context reference — into the kref release callback fastrpc_user_free(), so that it runs only when the last reference is dropped, regardless of whether that happens at device close or after the final in-flight context completes. Fixes: 6cffd79504ce ("misc: fastrpc: Add support for dmabuf exporter") Cc: stable@kernel.org Signed-off-by: Anandu Krishnan E Signed-off-by: Srinivas Kandagatla Link: https://patch.msgid.link/20260530204528.116920-2-srini@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/misc/fastrpc.c | 75 ++++++++++++++++++++++++++++++++++---------------- 1 file changed, 52 insertions(+), 23 deletions(-) diff --git a/drivers/misc/fastrpc.c b/drivers/misc/fastrpc.c index 1080f9acf70a..48f8262af539 100644 --- a/drivers/misc/fastrpc.c +++ b/drivers/misc/fastrpc.c @@ -310,6 +310,8 @@ struct fastrpc_user { spinlock_t lock; /* lock for allocations */ struct mutex mutex; + /* Reference count */ + struct kref refcount; }; /* Extract SMMU PA from consolidated IOVA */ @@ -497,15 +499,57 @@ static void fastrpc_channel_ctx_put(struct fastrpc_channel_ctx *cctx) kref_put(&cctx->refcount, fastrpc_channel_ctx_free); } +static void fastrpc_context_put(struct fastrpc_invoke_ctx *ctx); + +static void fastrpc_user_free(struct kref *ref) +{ + struct fastrpc_user *fl = container_of(ref, struct fastrpc_user, refcount); + struct fastrpc_invoke_ctx *ctx, *n; + struct fastrpc_map *map, *m; + struct fastrpc_buf *buf, *b; + + if (fl->init_mem) + fastrpc_buf_free(fl->init_mem); + + list_for_each_entry_safe(ctx, n, &fl->pending, node) { + list_del(&ctx->node); + fastrpc_context_put(ctx); + } + + list_for_each_entry_safe(map, m, &fl->maps, node) + fastrpc_map_put(map); + + list_for_each_entry_safe(buf, b, &fl->mmaps, node) { + list_del(&buf->node); + fastrpc_buf_free(buf); + } + + fastrpc_channel_ctx_put(fl->cctx); + mutex_destroy(&fl->mutex); + kfree(fl); +} + +static void fastrpc_user_get(struct fastrpc_user *fl) +{ + kref_get(&fl->refcount); +} + +static void fastrpc_user_put(struct fastrpc_user *fl) +{ + kref_put(&fl->refcount, fastrpc_user_free); +} + static void fastrpc_context_free(struct kref *ref) { struct fastrpc_invoke_ctx *ctx; struct fastrpc_channel_ctx *cctx; + struct fastrpc_user *fl; unsigned long flags; int i; ctx = container_of(ref, struct fastrpc_invoke_ctx, refcount); cctx = ctx->cctx; + fl = ctx->fl; for (i = 0; i < ctx->nbufs; i++) fastrpc_map_put(ctx->maps[i]); @@ -521,6 +565,8 @@ static void fastrpc_context_free(struct kref *ref) kfree(ctx->olaps); kfree(ctx); + /* Release the reference taken in fastrpc_context_alloc() */ + fastrpc_user_put(fl); fastrpc_channel_ctx_put(cctx); } @@ -628,6 +674,8 @@ static struct fastrpc_invoke_ctx *fastrpc_context_alloc( /* Released in fastrpc_context_put() */ fastrpc_channel_ctx_get(cctx); + /* Take a reference to user, released in fastrpc_context_free() */ + fastrpc_user_get(user); ctx->sc = sc; ctx->retval = -1; @@ -658,6 +706,7 @@ err_idr: spin_lock(&user->lock); list_del(&ctx->node); spin_unlock(&user->lock); + fastrpc_user_put(user); fastrpc_channel_ctx_put(cctx); kfree(ctx->maps); kfree(ctx->olaps); @@ -1579,9 +1628,6 @@ static int fastrpc_device_release(struct inode *inode, struct file *file) { struct fastrpc_user *fl = (struct fastrpc_user *)file->private_data; struct fastrpc_channel_ctx *cctx = fl->cctx; - struct fastrpc_invoke_ctx *ctx, *n; - struct fastrpc_map *map, *m; - struct fastrpc_buf *buf, *b; unsigned long flags; fastrpc_release_current_dsp_process(fl); @@ -1590,28 +1636,10 @@ static int fastrpc_device_release(struct inode *inode, struct file *file) list_del(&fl->user); spin_unlock_irqrestore(&cctx->lock, flags); - if (fl->init_mem) - fastrpc_buf_free(fl->init_mem); - - list_for_each_entry_safe(ctx, n, &fl->pending, node) { - list_del(&ctx->node); - fastrpc_context_put(ctx); - } - - list_for_each_entry_safe(map, m, &fl->maps, node) - fastrpc_map_put(map); - - list_for_each_entry_safe(buf, b, &fl->mmaps, node) { - list_del(&buf->node); - fastrpc_buf_free(buf); - } - fastrpc_session_free(cctx, fl->sctx); - fastrpc_channel_ctx_put(cctx); - - mutex_destroy(&fl->mutex); - kfree(fl); file->private_data = NULL; + /* Release the reference taken in fastrpc_device_open */ + fastrpc_user_put(fl); return 0; } @@ -1655,6 +1683,7 @@ static int fastrpc_device_open(struct inode *inode, struct file *filp) spin_lock_irqsave(&cctx->lock, flags); list_add_tail(&fl->user, &cctx->users); spin_unlock_irqrestore(&cctx->lock, flags); + kref_init(&fl->refcount); return 0; } -- cgit v1.2.3 From 464c6ad2aa16e1e1df9d559289199356493d1e00 Mon Sep 17 00:00:00 2001 From: Junrui Luo Date: Sat, 30 May 2026 21:45:26 +0100 Subject: misc: fastrpc: fix DMA address corruption due to find_vma misuse fastrpc_get_args() uses find_vma() to look up the VMA for a user-provided pointer and compute a DMA address offset. When the address falls in a gap before the returned VMA, (ptr & PAGE_MASK) - vma->vm_start underflows, corrupting the DMA address sent to the DSP. Replace find_vma() with vma_lookup(), which returns NULL when the address is not contained within any VMA. Cc: stable@vger.kernel.org Fixes: 80f3afd72bd4 ("misc: fastrpc: consider address offset before sending to DSP") Reported-by: Yuhao Jiang Signed-off-by: Junrui Luo Reviewed-by: Dmitry Baryshkov Signed-off-by: Srinivas Kandagatla Link: https://patch.msgid.link/20260530204528.116920-3-srini@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/misc/fastrpc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/misc/fastrpc.c b/drivers/misc/fastrpc.c index 48f8262af539..cca7489605c5 100644 --- a/drivers/misc/fastrpc.c +++ b/drivers/misc/fastrpc.c @@ -1090,7 +1090,7 @@ static int fastrpc_get_args(u32 kernel, struct fastrpc_invoke_ctx *ctx) pages[i].addr = ctx->maps[i]->dma_addr; mmap_read_lock(current->mm); - vma = find_vma(current->mm, ctx->args[i].ptr); + vma = vma_lookup(current->mm, ctx->args[i].ptr); if (vma) pages[i].addr += (ctx->args[i].ptr & PAGE_MASK) - vma->vm_start; -- cgit v1.2.3 From 5401fb4fe10fac6134c308495df18ed74aebb9c4 Mon Sep 17 00:00:00 2001 From: Mukesh Ojha Date: Sat, 30 May 2026 21:45:27 +0100 Subject: misc: fastrpc: Fix NULL pointer dereference in rpmsg callback A NULL pointer dereference was observed on Hawi at boot when the DSP sends a glink message before fastrpc_rpmsg_probe() has completed initialization: Unable to handle kernel NULL pointer dereference at virtual address 0000000000000178 pc : _raw_spin_lock_irqsave+0x34/0x8c lr : fastrpc_rpmsg_callback+0x3c/0xcc [fastrpc] ... Call trace: _raw_spin_lock_irqsave+0x34/0x8c (P) fastrpc_rpmsg_callback+0x3c/0xcc [fastrpc] qcom_glink_native_rx+0x538/0x6a4 qcom_glink_smem_intr+0x14/0x24 [qcom_glink_smem] The faulting address 0x178 corresponds to the lock variable inside struct fastrpc_channel_ctx, confirming that cctx is NULL when fastrpc_rpmsg_callback() attempts to take the spinlock. There are two issues here. First, dev_set_drvdata() is called before spin_lock_init() and idr_init(), leaving a window where the callback can retrieve a valid cctx pointer but operate on an uninitialized spinlock. Second, the rpmsg channel becomes live as soon as the driver is bound, so fastrpc_rpmsg_callback() can fire before dev_set_drvdata() is called at all, resulting in dev_get_drvdata() returning NULL. Fix both issues by moving all cctx initialization ahead of dev_set_drvdata() so the structure is fully initialized before it becomes visible to the callback, and add a NULL check in fastrpc_rpmsg_callback() as a guard against any remaining window. Fixes: f6f9279f2bf0 ("misc: fastrpc: Add Qualcomm fastrpc basic driver model") Cc: stable@vger.kernel.org Signed-off-by: Mukesh Ojha Reviewed-by: Bjorn Andersson Signed-off-by: Srinivas Kandagatla Link: https://patch.msgid.link/20260530204528.116920-4-srini@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/misc/fastrpc.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/misc/fastrpc.c b/drivers/misc/fastrpc.c index cca7489605c5..47cf3d21b51d 100644 --- a/drivers/misc/fastrpc.c +++ b/drivers/misc/fastrpc.c @@ -2460,7 +2460,6 @@ static int fastrpc_rpmsg_probe(struct rpmsg_device *rpdev) kref_init(&data->refcount); - dev_set_drvdata(&rpdev->dev, data); rdev->dma_mask = &data->dma_mask; dma_set_mask_and_coherent(rdev, DMA_BIT_MASK(32)); INIT_LIST_HEAD(&data->users); @@ -2469,6 +2468,7 @@ static int fastrpc_rpmsg_probe(struct rpmsg_device *rpdev) idr_init(&data->ctx_idr); data->domain_id = domain_id; data->rpdev = rpdev; + dev_set_drvdata(&rpdev->dev, data); err = of_platform_populate(rdev->of_node, NULL, NULL, rdev); if (err) @@ -2542,6 +2542,9 @@ static int fastrpc_rpmsg_callback(struct rpmsg_device *rpdev, void *data, if (len < sizeof(*rsp)) return -EINVAL; + if (!cctx) + return -ENODEV; + ctxid = ((rsp->ctx & FASTRPC_CTXID_MASK) >> 4); spin_lock_irqsave(&cctx->lock, flags); -- cgit v1.2.3 From 07ebe87915d8accdaba20c4f88c5ae430fe62fbb Mon Sep 17 00:00:00 2001 From: Zhenghang Xiao Date: Sat, 30 May 2026 21:45:28 +0100 Subject: misc: fastrpc: fix use-after-free race in fastrpc_map_create fastrpc_map_lookup returns a raw pointer after releasing fl->lock. The caller fastrpc_map_create then calls fastrpc_map_get (kref_get_unless_zero) on this unprotected pointer. A concurrent MEM_UNMAP can free the map between the lock release and the kref operation, resulting in a use-after-free on the freed slab object. Restore the take_ref parameter to fastrpc_map_lookup so the reference is acquired atomically under fl->lock before the pointer is exposed to the caller. Fixes: 10df039834f8 ("misc: fastrpc: Skip reference for DMA handles") Cc: stable@vger.kernel.org Signed-off-by: Zhenghang Xiao Signed-off-by: Srinivas Kandagatla Link: https://patch.msgid.link/20260530204528.116920-5-srini@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/misc/fastrpc.c | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/drivers/misc/fastrpc.c b/drivers/misc/fastrpc.c index 47cf3d21b51d..f3a49384586d 100644 --- a/drivers/misc/fastrpc.c +++ b/drivers/misc/fastrpc.c @@ -388,7 +388,7 @@ static int fastrpc_map_get(struct fastrpc_map *map) static int fastrpc_map_lookup(struct fastrpc_user *fl, int fd, - struct fastrpc_map **ppmap) + struct fastrpc_map **ppmap, bool take_ref) { struct fastrpc_map *map = NULL; struct dma_buf *buf; @@ -403,6 +403,12 @@ static int fastrpc_map_lookup(struct fastrpc_user *fl, int fd, if (map->fd != fd || map->buf != buf) continue; + if (take_ref) { + ret = fastrpc_map_get(map); + if (ret) + break; + } + *ppmap = map; ret = 0; break; @@ -920,19 +926,10 @@ get_err: static int fastrpc_map_create(struct fastrpc_user *fl, int fd, u64 len, u32 attr, struct fastrpc_map **ppmap) { - struct fastrpc_session_ctx *sess = fl->sctx; - int err = 0; - - if (!fastrpc_map_lookup(fl, fd, ppmap)) { - if (!fastrpc_map_get(*ppmap)) - return 0; - dev_dbg(sess->dev, "%s: Failed to get map fd=%d\n", - __func__, fd); - } - - err = fastrpc_map_attach(fl, fd, len, attr, ppmap); + if (!fastrpc_map_lookup(fl, fd, ppmap, true)) + return 0; - return err; + return fastrpc_map_attach(fl, fd, len, attr, ppmap); } /* @@ -1202,7 +1199,7 @@ cleanup_fdlist: for (i = 0; i < FASTRPC_MAX_FDLIST; i++) { if (!fdlist[i]) break; - if (!fastrpc_map_lookup(fl, (int)fdlist[i], &mmap)) + if (!fastrpc_map_lookup(fl, (int)fdlist[i], &mmap, false)) fastrpc_map_put(mmap); } -- cgit v1.2.3