summaryrefslogtreecommitdiff
path: root/drivers
diff options
context:
space:
mode:
authorMark Brown <broonie@kernel.org>2026-10-03 00:55:29 +0200
committerMark Brown <broonie@kernel.org>2026-10-03 00:55:29 +0200
commit15423fdbbc0ebe35300958679580fadee60972c8 (patch)
tree0ad162394671088ca9f89eebdc060d408a84da14 /drivers
parentdff88f4aab307adbe6ff3d02f0ef8c3247f10368 (diff)
parent5f7775e2a346257aa13128feb2f640cb5a2d6e5b (diff)
downloadlinux-next-15423fdbbc0ebe35300958679580fadee60972c8.tar.gz
linux-next-15423fdbbc0ebe35300958679580fadee60972c8.zip
Merge branch 'for-next' of https://git.kernel.org/pub/scm/linux/kernel/git/remoteproc/linux.git
Diffstat (limited to 'drivers')
-rw-r--r--drivers/remoteproc/Kconfig11
-rw-r--r--drivers/remoteproc/imx_dsp_rproc.c7
-rw-r--r--drivers/remoteproc/imx_rproc.c51
-rw-r--r--drivers/remoteproc/omap_remoteproc.c12
-rw-r--r--drivers/remoteproc/qcom_q6v5_adsp.c2
-rw-r--r--drivers/remoteproc/qcom_q6v5_pas.c166
-rw-r--r--drivers/remoteproc/remoteproc_core.c3
-rw-r--r--drivers/remoteproc/remoteproc_debugfs.c6
-rw-r--r--drivers/remoteproc/remoteproc_elf_loader.c23
-rw-r--r--drivers/remoteproc/remoteproc_sysfs.c7
-rw-r--r--drivers/remoteproc/stm32_rproc.c2
-rw-r--r--drivers/remoteproc/ti_k3_common.c10
-rw-r--r--drivers/remoteproc/ti_k3_common.h4
-rw-r--r--drivers/remoteproc/ti_k3_dsp_remoteproc.c7
-rw-r--r--drivers/remoteproc/ti_k3_m4_remoteproc.c4
-rw-r--r--drivers/remoteproc/ti_k3_r5_remoteproc.c9
-rw-r--r--drivers/remoteproc/xlnx_r5_remoteproc.c23
-rw-r--r--drivers/rpmsg/virtio_rpmsg_bus.c156
-rw-r--r--drivers/soc/qcom/Kconfig12
-rw-r--r--drivers/soc/qcom/Makefile1
-rw-r--r--drivers/soc/qcom/qmi_tmd.c595
21 files changed, 974 insertions, 137 deletions
diff --git a/drivers/remoteproc/Kconfig b/drivers/remoteproc/Kconfig
index 5b56b2dcc725..70dec8c3fe4e 100644
--- a/drivers/remoteproc/Kconfig
+++ b/drivers/remoteproc/Kconfig
@@ -83,9 +83,7 @@ config OMAP_REMOTEPROC
select OMAP2PLUS_MBOX
help
Say y here to support OMAP's remote processors (dual M3
- and DSP on OMAP4) via the remote processor framework.
-
- Currently only supported on OMAP4.
+ and DSP) via the remote processor framework.
Usually you want to say Y here, in order to enable multimedia
use-cases to run on your platform (multimedia codecs are
@@ -108,7 +106,7 @@ config OMAP_REMOTEPROC_WATCHDOG
config WKUP_M3_RPROC
tristate "AMx3xx Wakeup M3 remoteproc support"
- depends on SOC_AM33XX || SOC_AM43XX
+ depends on SOC_AM33XX || SOC_AM43XX || COMPILE_TEST
help
Say y here to support Wakeup M3 remote processor on TI AM33xx
and AM43xx family of SoCs.
@@ -120,7 +118,7 @@ config WKUP_M3_RPROC
config DA8XX_REMOTEPROC
tristate "DA8xx/OMAP-L13x remoteproc support"
- depends on ARCH_DAVINCI_DA8XX
+ depends on ARCH_DAVINCI_DA8XX || COMPILE_TEST
depends on DMA_CMA
help
Say y here to support DA8xx/OMAP-L13x remote processors via the
@@ -141,7 +139,7 @@ config DA8XX_REMOTEPROC
config KEYSTONE_REMOTEPROC
tristate "Keystone Remoteproc support"
- depends on ARCH_KEYSTONE
+ depends on ARCH_KEYSTONE || COMPILE_TEST
help
Say Y here here to support Keystone remote processors (DSP)
via the remote processor framework.
@@ -238,6 +236,7 @@ config QCOM_Q6V5_PAS
select QCOM_PIL_INFO
select QCOM_MDT_LOADER
select QCOM_Q6V5_COMMON
+ select QCOM_QMI_TMD if NET
select QCOM_RPROC_COMMON
select QCOM_SCM
select QCOM_PAS
diff --git a/drivers/remoteproc/imx_dsp_rproc.c b/drivers/remoteproc/imx_dsp_rproc.c
index fd60c67ba8a9..96d619daf3f5 100644
--- a/drivers/remoteproc/imx_dsp_rproc.c
+++ b/drivers/remoteproc/imx_dsp_rproc.c
@@ -726,14 +726,17 @@ static int imx_dsp_rproc_prepare(struct rproc *rproc)
struct device *dev = rproc->dev.parent;
int ret;
+ ret = pm_runtime_resume_and_get(dev);
+ if (ret < 0)
+ return ret;
+
ret = imx_dsp_rproc_add_carveout(priv);
if (ret) {
dev_err(dev, "failed on imx_dsp_rproc_add_carveout\n");
+ pm_runtime_put_sync(dev);
return ret;
}
- pm_runtime_get_sync(dev);
-
return 0;
}
diff --git a/drivers/remoteproc/imx_rproc.c b/drivers/remoteproc/imx_rproc.c
index 745ce52cd822..581bb07f5893 100644
--- a/drivers/remoteproc/imx_rproc.c
+++ b/drivers/remoteproc/imx_rproc.c
@@ -24,7 +24,6 @@
#include <linux/regmap.h>
#include <linux/remoteproc.h>
#include <linux/scmi_imx_protocol.h>
-#include <linux/workqueue.h>
#include "imx_rproc.h"
#include "remoteproc_internal.h"
@@ -115,8 +114,6 @@ struct imx_rproc {
struct mbox_client cl;
struct mbox_chan *tx_ch;
struct mbox_chan *rx_ch;
- struct work_struct rproc_work;
- struct workqueue_struct *workqueue;
void __iomem *rsc_table;
struct imx_sc_ipc *ipc_handle;
struct notifier_block rproc_nb;
@@ -540,6 +537,7 @@ static int imx_rproc_da_to_sys(struct imx_rproc *priv, u64 da,
/* parse address translation table */
for (i = 0; i < dcfg->att_size; i++) {
const struct imx_rproc_att *att = &dcfg->att[i];
+ u64 offset;
/*
* Ignore entries not belong to current core:
@@ -552,9 +550,11 @@ static int imx_rproc_da_to_sys(struct imx_rproc *priv, u64 da,
continue;
}
- if (da >= att->da && da + len < att->da + att->size) {
- unsigned int offset = da - att->da;
+ if (da < att->da)
+ continue;
+ offset = da - att->da;
+ if (offset <= att->size && len <= att->size - offset) {
*sys = att->sa + offset;
if (is_iomem)
*is_iomem = att->flags & ATT_IOMEM;
@@ -585,9 +585,14 @@ static void *imx_rproc_da_to_va(struct rproc *rproc, u64 da, size_t len, bool *i
return NULL;
for (i = 0; i < IMX_RPROC_MEM_MAX; i++) {
- if (sys >= priv->mem[i].sys_addr && sys + len <
- priv->mem[i].sys_addr + priv->mem[i].size) {
- unsigned int offset = sys - priv->mem[i].sys_addr;
+ u64 offset;
+
+ if (sys < priv->mem[i].sys_addr)
+ continue;
+
+ offset = sys - priv->mem[i].sys_addr;
+ if (offset <= priv->mem[i].size &&
+ len <= priv->mem[i].size - offset) {
/* __force to make sparse happy with type conversion */
va = (__force void *)(priv->mem[i].cpu_addr + offset);
break;
@@ -860,21 +865,11 @@ static int imx_rproc_notified_idr_cb(int id, void *ptr, void *data)
return 0;
}
-static void imx_rproc_vq_work(struct work_struct *work)
-{
- struct imx_rproc *priv = container_of(work, struct imx_rproc,
- rproc_work);
- struct rproc *rproc = priv->rproc;
-
- idr_for_each(&rproc->notifyids, imx_rproc_notified_idr_cb, rproc);
-}
-
static void imx_rproc_rx_callback(struct mbox_client *cl, void *msg)
{
struct rproc *rproc = dev_get_drvdata(cl->dev);
- struct imx_rproc *priv = rproc->priv;
- queue_work(priv->workqueue, &priv->rproc_work);
+ idr_for_each(&rproc->notifyids, imx_rproc_notified_idr_cb, rproc);
}
static int imx_rproc_xtr_mbox_init(struct rproc *rproc, bool tx_block)
@@ -1239,13 +1234,6 @@ static int imx_rproc_sys_off_handler(struct sys_off_data *data)
return NOTIFY_DONE;
}
-static void imx_rproc_destroy_workqueue(void *data)
-{
- struct workqueue_struct *workqueue = data;
-
- destroy_workqueue(workqueue);
-}
-
static int imx_rproc_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
@@ -1273,17 +1261,6 @@ static int imx_rproc_probe(struct platform_device *pdev)
priv->ops = dcfg->ops;
dev_set_drvdata(dev, rproc);
- priv->workqueue = create_workqueue(dev_name(dev));
- if (!priv->workqueue) {
- dev_err(dev, "cannot create workqueue\n");
- return -ENOMEM;
- }
-
- ret = devm_add_action_or_reset(dev, imx_rproc_destroy_workqueue, priv->workqueue);
- if (ret)
- return dev_err_probe(dev, ret, "Failed to add devm destroy workqueue action\n");
-
- INIT_WORK(&priv->rproc_work, imx_rproc_vq_work);
ret = imx_rproc_xtr_mbox_init(rproc, true);
if (ret)
diff --git a/drivers/remoteproc/omap_remoteproc.c b/drivers/remoteproc/omap_remoteproc.c
index 6ed0f28edac9..1e96506ce7ca 100644
--- a/drivers/remoteproc/omap_remoteproc.c
+++ b/drivers/remoteproc/omap_remoteproc.c
@@ -499,7 +499,7 @@ static void omap_rproc_mbox_callback(struct mbox_client *client, void *data)
client);
struct device *dev = oproc->rproc->dev.parent;
const char *name = oproc->rproc->name;
- u32 msg = (u32)data;
+ mbox_msg_t msg = omap_mbox_from_message(data);
dev_dbg(dev, "mbox msg: 0x%x\n", msg);
@@ -550,7 +550,7 @@ static void omap_rproc_kick(struct rproc *rproc, int vqid)
}
/* send the index of the triggered virtqueue in the mailbox payload */
- ret = mbox_send_message(oproc->mbox, (void *)vqid);
+ ret = mbox_send_message(oproc->mbox, omap_mbox_to_message(vqid));
if (ret < 0)
dev_err(dev, "failed to send mailbox message, status = %d\n",
ret);
@@ -628,7 +628,7 @@ static int omap_rproc_start(struct rproc *rproc)
* Note that the reply will _not_ arrive immediately: this message
* will wait in the mailbox fifo until the remote processor is booted.
*/
- ret = mbox_send_message(oproc->mbox, (void *)RP_MBOX_ECHO_REQUEST);
+ ret = mbox_send_message(oproc->mbox, omap_mbox_to_message(RP_MBOX_ECHO_REQUEST));
if (ret < 0) {
dev_err(dev, "mbox_send_message failed: %d\n", ret);
goto put_mbox;
@@ -777,13 +777,13 @@ static int _omap_rproc_suspend(struct rproc *rproc, bool auto_suspend)
struct omap_rproc *oproc = rproc->priv;
unsigned long to = msecs_to_jiffies(DEF_SUSPEND_TIMEOUT);
unsigned long ta = jiffies + to;
- u32 suspend_msg = auto_suspend ?
+ mbox_msg_t suspend_msg = auto_suspend ?
RP_MBOX_SUSPEND_AUTO : RP_MBOX_SUSPEND_SYSTEM;
int ret;
reinit_completion(&oproc->pm_comp);
oproc->suspend_acked = false;
- ret = mbox_send_message(oproc->mbox, (void *)suspend_msg);
+ ret = mbox_send_message(oproc->mbox, omap_mbox_to_message(suspend_msg));
if (ret < 0) {
dev_err(dev, "PM mbox_send_message failed: %d\n", ret);
return ret;
@@ -1208,7 +1208,7 @@ static int omap_rproc_of_get_internal_memories(struct platform_device *pdev,
oproc->mem[i].dev_addr = data->mems[i].dev_addr;
oproc->mem[i].size = resource_size(res);
- dev_dbg(dev, "memory %8s: bus addr %pa size 0x%x va %p da 0x%x\n",
+ dev_dbg(dev, "memory %8s: bus addr %pa size 0x%zx va %p da 0x%x\n",
data->mems[i].name, &oproc->mem[i].bus_addr,
oproc->mem[i].size, oproc->mem[i].cpu_addr,
oproc->mem[i].dev_addr);
diff --git a/drivers/remoteproc/qcom_q6v5_adsp.c b/drivers/remoteproc/qcom_q6v5_adsp.c
index 2e5fb5954fa9..46d9169a37f6 100644
--- a/drivers/remoteproc/qcom_q6v5_adsp.c
+++ b/drivers/remoteproc/qcom_q6v5_adsp.c
@@ -430,7 +430,7 @@ static int adsp_start(struct rproc *rproc)
goto disable_adsp_clks;
}
- ret = qcom_q6v5_wait_for_start(&adsp->q6v5, msecs_to_jiffies(5 * HZ));
+ ret = qcom_q6v5_wait_for_start(&adsp->q6v5, msecs_to_jiffies(5000));
if (ret == -ETIMEDOUT) {
dev_err(adsp->dev, "start timed out\n");
goto disable_adsp_clks;
diff --git a/drivers/remoteproc/qcom_q6v5_pas.c b/drivers/remoteproc/qcom_q6v5_pas.c
index a005546c265d..2e1e39826ffa 100644
--- a/drivers/remoteproc/qcom_q6v5_pas.c
+++ b/drivers/remoteproc/qcom_q6v5_pas.c
@@ -5,6 +5,7 @@
* Copyright (C) 2016 Linaro Ltd
* Copyright (C) 2014 Sony Mobile Communications AB
* Copyright (c) 2012-2013, The Linux Foundation. All rights reserved.
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
*/
#include <linux/clk.h>
@@ -26,8 +27,11 @@
#include <linux/regulator/consumer.h>
#include <linux/remoteproc.h>
#include <linux/soc/qcom/mdt_loader.h>
+#include <linux/soc/qcom/qmi_tmd.h>
#include <linux/soc/qcom/smem.h>
#include <linux/soc/qcom/smem_state.h>
+#include <dt-bindings/power/qcom,rpmhpd.h>
+#include <dt-bindings/thermal/qcom,pas.h>
#include "qcom_common.h"
#include "qcom_pil_info.h"
@@ -38,6 +42,16 @@
#define MAX_ASSIGN_COUNT 3
+/**
+ * struct tmd_name - TMD device name to cooling-device index mapping
+ * @name: TMD device name
+ * @id: Cooling-device index used as #cooling-cells cell 0 in DT
+ */
+struct tmd_name {
+ const char *name;
+ int id;
+};
+
struct qcom_pas_data {
int crash_reason_smem;
const char *firmware_name;
@@ -51,6 +65,8 @@ struct qcom_pas_data {
bool decrypt_shutdown;
char **proxy_pd_names;
+ const unsigned int *proxy_pd_performance_states;
+ unsigned int num_proxy_pd_performance_states;
const char *load_state;
const char *ssr_name;
@@ -58,6 +74,10 @@ struct qcom_pas_data {
int ssctl_id;
unsigned int smem_host_id;
+ unsigned int tmd_instance_id;
+ const struct tmd_name *tmd_name;
+ int num_tmd;
+
int region_assign_idx;
int region_assign_count;
bool region_assign_shared;
@@ -79,6 +99,7 @@ struct qcom_pas {
struct regulator *px_supply;
struct device *proxy_pds[3];
+ const unsigned int *proxy_pd_performance_states;
int proxy_pd_count;
@@ -123,6 +144,8 @@ struct qcom_pas {
struct qcom_pas_context *pas_ctx;
struct qcom_pas_context *dtb_pas_ctx;
+
+ struct qmi_tmd_client *tmd_inst;
};
static void qcom_pas_segment_dump(struct rproc *rproc,
@@ -167,7 +190,12 @@ static int qcom_pas_pds_enable(struct qcom_pas *pas, struct device **pds,
int i;
for (i = 0; i < pd_count; i++) {
- dev_pm_genpd_set_performance_state(pds[i], INT_MAX);
+ unsigned int state = INT_MAX;
+
+ if (pas->proxy_pd_performance_states)
+ state = pas->proxy_pd_performance_states[i];
+
+ dev_pm_genpd_set_performance_state(pds[i], state);
ret = pm_runtime_get_sync(pds[i]);
if (ret < 0) {
pm_runtime_put_noidle(pds[i]);
@@ -818,6 +846,64 @@ static void qcom_pas_unassign_memory_region(struct qcom_pas *pas)
}
}
+static int qcom_pas_setup_tmd(struct qcom_pas *pas, const struct qcom_pas_data *desc)
+{
+ struct qmi_tmd_client *tmd_inst;
+ const struct tmd_name *tmd;
+ const char **tmd_names;
+ int i, ret;
+
+ if (!device_property_present(pas->dev, "#cooling-cells"))
+ return 0;
+
+ if (!desc->tmd_name || desc->num_tmd == 0)
+ return 0;
+
+ tmd_names = devm_kcalloc(pas->dev, desc->num_tmd,
+ sizeof(*tmd_names), GFP_KERNEL);
+ if (!tmd_names)
+ return -ENOMEM;
+
+ for (i = 0; i < desc->num_tmd; i++) {
+ tmd = &desc->tmd_name[i];
+
+ if (tmd->id >= desc->num_tmd) {
+ dev_err(pas->dev, "Invalid TMD id %d for '%s'\n",
+ tmd->id, tmd->name);
+ return -EINVAL;
+ }
+
+ if (tmd_names[tmd->id]) {
+ dev_err(pas->dev, "Duplicate TMD id %d for '%s'\n",
+ tmd->id, tmd->name);
+ return -EINVAL;
+ }
+
+ tmd_names[tmd->id] = tmd->name;
+ }
+
+ for (i = 0; i < desc->num_tmd; i++) {
+ if (!tmd_names[i]) {
+ dev_err(pas->dev, "Missing TMD mapping for id %d\n", i);
+ return -EINVAL;
+ }
+ }
+
+ tmd_inst = qmi_tmd_init(pas->dev, desc->tmd_instance_id, tmd_names,
+ desc->num_tmd);
+ if (IS_ERR(tmd_inst)) {
+ ret = PTR_ERR(tmd_inst);
+ if (ret == -ENODEV)
+ return 0;
+
+ return ret;
+ }
+
+ pas->tmd_inst = tmd_inst;
+
+ return 0;
+}
+
static int qcom_pas_probe(struct platform_device *pdev)
{
const struct qcom_pas_data *desc;
@@ -873,6 +959,7 @@ static int qcom_pas_probe(struct platform_device *pdev)
pas->info_name = desc->sysmon_name;
pas->smem_host_id = desc->smem_host_id;
pas->decrypt_shutdown = desc->decrypt_shutdown;
+ pas->proxy_pd_performance_states = desc->proxy_pd_performance_states;
pas->region_assign_idx = desc->region_assign_idx;
pas->region_assign_count = min_t(int, MAX_ASSIGN_COUNT, desc->region_assign_count);
pas->region_assign_vmid = desc->region_assign_vmid;
@@ -908,6 +995,15 @@ static int qcom_pas_probe(struct platform_device *pdev)
goto unassign_mem;
pas->proxy_pd_count = ret;
+ if (desc->proxy_pd_performance_states &&
+ desc->num_proxy_pd_performance_states != pas->proxy_pd_count) {
+ dev_err(&pdev->dev,
+ "proxy_pd_performance_states count %u != pd count %d\n",
+ desc->num_proxy_pd_performance_states, pas->proxy_pd_count);
+ ret = -EINVAL;
+ goto detach_proxy_pds;
+ }
+
ret = qcom_q6v5_init(&pas->q6v5, pdev, rproc, desc->crash_reason_smem,
desc->load_state, qcom_pas_handover);
if (ret)
@@ -931,16 +1027,24 @@ static int qcom_pas_probe(struct platform_device *pdev)
if (desc->early_boot)
pas->rproc->state = RPROC_DETACHED;
- ret = rproc_add(rproc);
+ ret = qcom_pas_setup_tmd(pas, desc);
if (ret)
goto remove_ssr_sysmon;
+ ret = rproc_add(rproc);
+ if (ret)
+ goto remove_setup_tmd;
+
node = of_get_compatible_child(pdev->dev.of_node, "qcom,bam-dmux");
pas->bam_dmux = of_platform_device_create(node, NULL, &pdev->dev);
of_node_put(node);
return 0;
+remove_setup_tmd:
+ if (pas->tmd_inst)
+ qmi_tmd_exit(pas->tmd_inst);
+
remove_ssr_sysmon:
qcom_remove_ssr_subdev(rproc, &pas->ssr_subdev);
qcom_remove_sysmon_subdev(pas->sysmon);
@@ -968,6 +1072,9 @@ static void qcom_pas_remove(struct platform_device *pdev)
rproc_del(pas->rproc);
+ if (pas->tmd_inst)
+ qmi_tmd_exit(pas->tmd_inst);
+
qcom_q6v5_deinit(&pas->q6v5);
qcom_pas_unassign_memory_region(pas);
qcom_remove_glink_subdev(pas->rproc, &pas->glink_subdev);
@@ -979,6 +1086,15 @@ static void qcom_pas_remove(struct platform_device *pdev)
device_init_wakeup(pas->dev, false);
}
+static const struct tmd_name cdsp_tmd_name[] = {
+ { .name = "cdsp_sw", .id = QCOM_TMD_CDSP_SW },
+};
+
+static const struct tmd_name modem_tmd_name[] = {
+ { .name = "pa", .id = QCOM_TMD_PA },
+ { .name = "modem", .id = QCOM_TMD_MODEM },
+};
+
static const struct qcom_pas_data adsp_resource_init = {
.crash_reason_smem = 423,
.firmware_name = "adsp.mdt",
@@ -1136,6 +1252,9 @@ static const struct qcom_pas_data sa8775p_cdsp0_resource = {
.ssr_name = "cdsp",
.sysmon_name = "cdsp",
.ssctl_id = 0x17,
+ .tmd_instance_id = 0x43,
+ .tmd_name = cdsp_tmd_name,
+ .num_tmd = ARRAY_SIZE(cdsp_tmd_name),
};
static const struct qcom_pas_data sa8775p_cdsp1_resource = {
@@ -1154,6 +1273,9 @@ static const struct qcom_pas_data sa8775p_cdsp1_resource = {
.ssr_name = "cdsp1",
.sysmon_name = "cdsp1",
.ssctl_id = 0x20,
+ .tmd_instance_id = 0x44,
+ .tmd_name = cdsp_tmd_name,
+ .num_tmd = ARRAY_SIZE(cdsp_tmd_name),
};
static const struct qcom_pas_data sdm845_cdsp_resource_init = {
@@ -1181,6 +1303,9 @@ static const struct qcom_pas_data sm6350_cdsp_resource = {
.ssr_name = "cdsp",
.sysmon_name = "cdsp",
.ssctl_id = 0x17,
+ .tmd_instance_id = 0x43,
+ .tmd_name = cdsp_tmd_name,
+ .num_tmd = ARRAY_SIZE(cdsp_tmd_name),
};
static const struct qcom_pas_data sm8150_cdsp_resource = {
@@ -1196,6 +1321,9 @@ static const struct qcom_pas_data sm8150_cdsp_resource = {
.ssr_name = "cdsp",
.sysmon_name = "cdsp",
.ssctl_id = 0x17,
+ .tmd_instance_id = 0x43,
+ .tmd_name = cdsp_tmd_name,
+ .num_tmd = ARRAY_SIZE(cdsp_tmd_name),
};
static const struct qcom_pas_data sm8250_cdsp_resource = {
@@ -1280,6 +1408,9 @@ static const struct qcom_pas_data x1e80100_cdsp_resource = {
.ssr_name = "cdsp",
.sysmon_name = "cdsp",
.ssctl_id = 0x17,
+ .tmd_instance_id = 0x43,
+ .tmd_name = cdsp_tmd_name,
+ .num_tmd = ARRAY_SIZE(cdsp_tmd_name),
};
static const struct qcom_pas_data sm8350_cdsp_resource = {
@@ -1348,6 +1479,9 @@ static const struct qcom_pas_data mpss_resource_init = {
.ssr_name = "mpss",
.sysmon_name = "modem",
.ssctl_id = 0x12,
+ .tmd_instance_id = 0x0,
+ .tmd_name = modem_tmd_name,
+ .num_tmd = ARRAY_SIZE(modem_tmd_name),
};
static const struct qcom_pas_data sc8180x_mpss_resource = {
@@ -1798,6 +1932,33 @@ static const struct qcom_pas_data glymur_soccp_resource = {
.needs_tzmem = true,
};
+static const struct qcom_pas_data hawi_cdsp_resource = {
+ .crash_reason_smem = 601,
+ .firmware_name = "cdsp.mdt",
+ .dtb_firmware_name = "cdsp_dtb.mdt",
+ .pas_id = 18,
+ .dtb_pas_id = 0x25,
+ .minidump_id = 7,
+ .auto_boot = true,
+ .proxy_pd_names = (char*[]){
+ "cx",
+ "mxc",
+ "nsp",
+ NULL
+ },
+ .proxy_pd_performance_states = (const unsigned int[]){
+ RPMH_REGULATOR_LEVEL_TURBO,
+ RPMH_REGULATOR_LEVEL_TURBO,
+ RPMH_REGULATOR_LEVEL_NOM,
+ },
+ .num_proxy_pd_performance_states = 3,
+ .load_state = "cdsp",
+ .ssr_name = "cdsp",
+ .sysmon_name = "cdsp",
+ .ssctl_id = 0x17,
+ .smem_host_id = 5,
+};
+
static const struct qcom_pas_data eliza_cdsp_resource = {
.crash_reason_smem = 601,
.firmware_name = "cdsp.mbn",
@@ -1827,6 +1988,7 @@ static const struct of_device_id qcom_pas_of_match[] = {
{ .compatible = "qcom,eliza-adsp-pas", .data = &sm8550_adsp_resource },
{ .compatible = "qcom,eliza-cdsp-pas", .data = &eliza_cdsp_resource },
{ .compatible = "qcom,glymur-soccp-pas", .data = &glymur_soccp_resource },
+ { .compatible = "qcom,hawi-cdsp-pas", .data = &hawi_cdsp_resource },
{ .compatible = "qcom,kaanapali-soccp-pas", .data = &kaanapali_soccp_resource },
{ .compatible = "qcom,milos-adsp-pas", .data = &sm8550_adsp_resource },
{ .compatible = "qcom,milos-cdsp-pas", .data = &milos_cdsp_resource },
diff --git a/drivers/remoteproc/remoteproc_core.c b/drivers/remoteproc/remoteproc_core.c
index d131b82af320..4bf69003b7da 100644
--- a/drivers/remoteproc/remoteproc_core.c
+++ b/drivers/remoteproc/remoteproc_core.c
@@ -574,6 +574,9 @@ static int rproc_handle_trace(struct rproc *rproc, void *ptr,
/* create the debugfs entry */
trace->tfile = rproc_create_trace_file(name, rproc, trace);
+ /* keep the name for the diagnostic in rproc_trace_read() */
+ strscpy(trace->trace_mem.name, name, sizeof(trace->trace_mem.name));
+
list_add_tail(&trace->node, &rproc->traces);
rproc->num_traces++;
diff --git a/drivers/remoteproc/remoteproc_debugfs.c b/drivers/remoteproc/remoteproc_debugfs.c
index b86c1d09c70c..e8e7b6a3769b 100644
--- a/drivers/remoteproc/remoteproc_debugfs.c
+++ b/drivers/remoteproc/remoteproc_debugfs.c
@@ -347,8 +347,8 @@ static int rproc_rsc_table_show(struct seq_file *seq, void *p)
}
break;
default:
- seq_printf(seq, "Unknown resource type found: %d [hdr: %pK]\n",
- hdr->type, hdr);
+ seq_printf(seq, "Unknown resource type found: %d\n",
+ hdr->type);
break;
}
}
@@ -367,7 +367,7 @@ static int rproc_carveouts_show(struct seq_file *seq, void *p)
list_for_each_entry(carveout, &rproc->carveouts, node) {
seq_puts(seq, "Carveout memory entry:\n");
seq_printf(seq, "\tName: %s\n", carveout->name);
- seq_printf(seq, "\tVirtual address: %pK\n", carveout->va);
+ seq_printf(seq, "\tVirtual address: %p\n", carveout->va);
seq_printf(seq, "\tDMA address: %pad\n", &carveout->dma);
seq_printf(seq, "\tDevice address: 0x%x\n", carveout->da);
seq_printf(seq, "\tLength: 0x%zx Bytes\n\n", carveout->len);
diff --git a/drivers/remoteproc/remoteproc_elf_loader.c b/drivers/remoteproc/remoteproc_elf_loader.c
index 94177e416047..da3cddbe7d4c 100644
--- a/drivers/remoteproc/remoteproc_elf_loader.c
+++ b/drivers/remoteproc/remoteproc_elf_loader.c
@@ -46,8 +46,9 @@ int rproc_elf_sanity_check(struct rproc *rproc, const struct firmware *fw)
struct elf32_hdr *ehdr;
u32 elf_shdr_get_size;
u64 phoff, shoff;
+ size_t shend;
char class;
- u16 phnum;
+ u16 phnum, shnum, shstrndx;
if (!fw) {
dev_err(dev, "failed to load %s\n", name);
@@ -90,9 +91,27 @@ int rproc_elf_sanity_check(struct rproc *rproc, const struct firmware *fw)
phoff = elf_hdr_get_e_phoff(class, fw->data);
shoff = elf_hdr_get_e_shoff(class, fw->data);
phnum = elf_hdr_get_e_phnum(class, fw->data);
+ shnum = elf_hdr_get_e_shnum(class, fw->data);
+ shstrndx = elf_hdr_get_e_shstrndx(class, fw->data);
elf_shdr_get_size = elf_size_of_shdr(class);
- if (fw->size < shoff + elf_shdr_get_size) {
+ /* keeps shoff in size_t range for the two bounds below */
+ if (shoff > fw->size) {
+ dev_err(dev, "Section header table is out of bounds\n");
+ return -EINVAL;
+ }
+
+ if (shnum) {
+ shend = size_add(size_mul(elf_shdr_get_size, shnum), shoff);
+ if (shend > fw->size) {
+ dev_err(dev, "Section headers are out of bounds\n");
+ return -EINVAL;
+ }
+ }
+
+ /* find_table() reads the header at shstrndx even with no sections */
+ shend = size_add(size_mul(elf_shdr_get_size, (size_t)shstrndx + 1), shoff);
+ if (shend > fw->size) {
dev_err(dev, "Image is too small\n");
return -EINVAL;
}
diff --git a/drivers/remoteproc/remoteproc_sysfs.c b/drivers/remoteproc/remoteproc_sysfs.c
index 925b0cdbe577..c37736ff4acc 100644
--- a/drivers/remoteproc/remoteproc_sysfs.c
+++ b/drivers/remoteproc/remoteproc_sysfs.c
@@ -76,7 +76,7 @@ static const char * const rproc_coredump_str[] = {
[RPROC_COREDUMP_INLINE] = "inline",
};
-/* Expose the current coredump configuration via debugfs */
+/* Expose the current coredump configuration via sysfs */
static ssize_t coredump_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
@@ -87,14 +87,15 @@ static ssize_t coredump_show(struct device *dev,
/*
* By writing to the 'coredump' sysfs entry, we control the behavior of the
- * coredump mechanism dynamically. The default value of this entry is "default".
+ * coredump mechanism dynamically. The default value of this entry is
+ * "disabled".
*
* The 'coredump' sysfs entry supports these commands:
*
* disabled: This is the default coredump mechanism. Recovery will proceed
* without collecting any dump.
*
- * default: When the remoteproc crashes the entire coredump will be
+ * enabled: When the remoteproc crashes the entire coredump will be
* copied to a separate buffer and exposed to userspace.
*
* inline: The coredump will not be copied to a separate buffer and the
diff --git a/drivers/remoteproc/stm32_rproc.c b/drivers/remoteproc/stm32_rproc.c
index 1fe4cdc0a13a..a47e040c6db8 100644
--- a/drivers/remoteproc/stm32_rproc.c
+++ b/drivers/remoteproc/stm32_rproc.c
@@ -675,7 +675,7 @@ static int stm32_rproc_parse_dt(struct platform_device *pdev,
int err, irq;
irq = platform_get_irq_optional(pdev, 0);
- if (irq == -EPROBE_DEFER)
+ if (irq < 0 && irq != -ENXIO)
return irq;
if (irq > 0) {
diff --git a/drivers/remoteproc/ti_k3_common.c b/drivers/remoteproc/ti_k3_common.c
index 3cb8ae5d72f6..4b6da3363f6c 100644
--- a/drivers/remoteproc/ti_k3_common.c
+++ b/drivers/remoteproc/ti_k3_common.c
@@ -54,7 +54,7 @@ void k3_rproc_mbox_callback(struct mbox_client *client, void *data)
struct k3_rproc *kproc = container_of(client, struct k3_rproc, client);
struct device *dev = kproc->rproc->dev.parent;
struct rproc *rproc = kproc->rproc;
- u32 msg = (u32)(uintptr_t)(data);
+ mbox_msg_t msg = omap_mbox_from_message(data);
dev_dbg(dev, "mbox msg: 0x%x\n", msg);
@@ -94,15 +94,9 @@ void k3_rproc_kick(struct rproc *rproc, int vqid)
{
struct k3_rproc *kproc = rproc->priv;
struct device *dev = kproc->dev;
- u32 msg = (u32)vqid;
int ret;
- /*
- * Send the index of the triggered virtqueue in the mailbox payload.
- * NOTE: msg is cast to uintptr_t to prevent compiler warnings when
- * void* is 64bit. It is safely cast back to u32 in the mailbox driver.
- */
- ret = mbox_send_message(kproc->mbox, (void *)(uintptr_t)msg);
+ ret = mbox_send_message(kproc->mbox, omap_mbox_to_message(vqid));
if (ret < 0)
dev_err(dev, "failed to send mailbox message, status = %d\n",
ret);
diff --git a/drivers/remoteproc/ti_k3_common.h b/drivers/remoteproc/ti_k3_common.h
index aee3c28dbe51..c8430e42225f 100644
--- a/drivers/remoteproc/ti_k3_common.h
+++ b/drivers/remoteproc/ti_k3_common.h
@@ -21,6 +21,10 @@
#ifndef REMOTEPROC_TI_K3_COMMON_H
#define REMOTEPROC_TI_K3_COMMON_H
+#include <linux/mailbox_client.h>
+#include <linux/platform_device.h>
+#include <linux/types.h>
+
#define KEYSTONE_RPROC_LOCAL_ADDRESS_MASK (SZ_16M - 1)
/**
diff --git a/drivers/remoteproc/ti_k3_dsp_remoteproc.c b/drivers/remoteproc/ti_k3_dsp_remoteproc.c
index d6ceea6dc920..6582c7404e54 100644
--- a/drivers/remoteproc/ti_k3_dsp_remoteproc.c
+++ b/drivers/remoteproc/ti_k3_dsp_remoteproc.c
@@ -7,11 +7,8 @@
*/
#include <linux/io.h>
-#include <linux/mailbox_client.h>
#include <linux/module.h>
#include <linux/of.h>
-#include <linux/of_reserved_mem.h>
-#include <linux/omap-mailbox.h>
#include <linux/platform_device.h>
#include <linux/remoteproc.h>
#include <linux/reset.h>
@@ -154,6 +151,10 @@ static int k3_dsp_rproc_probe(struct platform_device *pdev)
dev_info(dev, "configured DSP for remoteproc mode\n");
}
+ ret = dma_coerce_mask_and_coherent(&rproc->dev, DMA_BIT_MASK(48));
+ if (ret)
+ dev_warn(dev, "Failed to set DMA mask (%d)\n", ret);
+
ret = devm_rproc_add(dev, rproc);
if (ret)
return dev_err_probe(dev, ret, "failed to add register device with remoteproc core\n");
diff --git a/drivers/remoteproc/ti_k3_m4_remoteproc.c b/drivers/remoteproc/ti_k3_m4_remoteproc.c
index 3a11fd24eb52..4c46f4a256a9 100644
--- a/drivers/remoteproc/ti_k3_m4_remoteproc.c
+++ b/drivers/remoteproc/ti_k3_m4_remoteproc.c
@@ -7,10 +7,8 @@
*/
#include <linux/io.h>
-#include <linux/mailbox_client.h>
#include <linux/module.h>
-#include <linux/of_address.h>
-#include <linux/of_reserved_mem.h>
+#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/remoteproc.h>
#include <linux/reset.h>
diff --git a/drivers/remoteproc/ti_k3_r5_remoteproc.c b/drivers/remoteproc/ti_k3_r5_remoteproc.c
index b1d04d082e44..d7d9756afed0 100644
--- a/drivers/remoteproc/ti_k3_r5_remoteproc.c
+++ b/drivers/remoteproc/ti_k3_r5_remoteproc.c
@@ -6,17 +6,12 @@
* Suman Anna <s-anna@ti.com>
*/
-#include <linux/dma-mapping.h>
#include <linux/err.h>
#include <linux/interrupt.h>
-#include <linux/kernel.h>
-#include <linux/mailbox_client.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_address.h>
-#include <linux/of_reserved_mem.h>
#include <linux/of_platform.h>
-#include <linux/omap-mailbox.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/remoteproc.h>
@@ -1047,6 +1042,10 @@ static int k3_r5_cluster_rproc_init(struct platform_device *pdev)
goto out;
}
+ ret = dma_coerce_mask_and_coherent(&rproc->dev, DMA_BIT_MASK(48));
+ if (ret)
+ dev_warn(dev, "Failed to set DMA mask (%d)\n", ret);
+
/* K3 R5s have a Region Address Translator (RAT) but no MMU */
rproc->has_iommu = false;
/* error recovery is not supported at present */
diff --git a/drivers/remoteproc/xlnx_r5_remoteproc.c b/drivers/remoteproc/xlnx_r5_remoteproc.c
index c685bb5fa62c..a9873a37dd8f 100644
--- a/drivers/remoteproc/xlnx_r5_remoteproc.c
+++ b/drivers/remoteproc/xlnx_r5_remoteproc.c
@@ -318,6 +318,8 @@ static struct mbox_info *zynqmp_r5_setup_mbox(struct device *cdev)
if (!ipi)
return NULL;
+ INIT_WORK(&ipi->mbox_work, handle_event_notified);
+
mbox_cl = &ipi->mbox_cl;
mbox_cl->rx_callback = zynqmp_r5_mb_rx_cb;
mbox_cl->tx_block = false;
@@ -329,6 +331,7 @@ static struct mbox_info *zynqmp_r5_setup_mbox(struct device *cdev)
ipi->tx_chan = mbox_request_channel_byname(mbox_cl, "tx");
if (IS_ERR(ipi->tx_chan)) {
ipi->tx_chan = NULL;
+ cancel_work_sync(&ipi->mbox_work);
kfree(ipi);
dev_warn(cdev, "mbox tx channel request failed\n");
return NULL;
@@ -339,13 +342,12 @@ static struct mbox_info *zynqmp_r5_setup_mbox(struct device *cdev)
mbox_free_channel(ipi->tx_chan);
ipi->rx_chan = NULL;
ipi->tx_chan = NULL;
+ cancel_work_sync(&ipi->mbox_work);
kfree(ipi);
dev_warn(cdev, "mbox rx channel request failed\n");
return NULL;
}
- INIT_WORK(&ipi->mbox_work, handle_event_notified);
-
return ipi;
}
@@ -364,6 +366,8 @@ static void zynqmp_r5_free_mbox(struct mbox_info *ipi)
ipi->rx_chan = NULL;
}
+ cancel_work_sync(&ipi->mbox_work);
+
kfree(ipi);
}
@@ -381,7 +385,10 @@ static void zynqmp_r5_rproc_kick(struct rproc *rproc, int vqid)
int ret;
ipi = r5_core->ipi;
- if (!ipi)
+ if (!ipi || !ipi->tx_chan)
+ return;
+
+ if (mbox_chan_tx_slots_available(ipi->tx_chan) == 0)
return;
mb_msg = (struct zynqmp_ipi_message *)ipi->tx_mc_buf;
@@ -568,7 +575,7 @@ static int tcm_mem_map(struct rproc *rproc,
return -ENOMEM;
/* Update memory entry va */
- mem->va = (void *)va;
+ mem->va = (__force void *)va;
/* clear TCMs */
memset_io(va, 0, mem->len);
@@ -746,7 +753,7 @@ static struct resource_table *zynqmp_r5_get_loaded_rsc_table(struct rproc *rproc
*size = r5_core->rsc_tbl_size;
- return (struct resource_table *)r5_core->rsc_tbl_va;
+ return (__force struct resource_table *)r5_core->rsc_tbl_va;
}
static int zynqmp_r5_get_rsc_table_va(struct zynqmp_r5_core *r5_core)
@@ -770,7 +777,7 @@ static int zynqmp_r5_get_rsc_table_va(struct zynqmp_r5_core *r5_core)
return -EINVAL;
}
- rsc_data_va = (struct rsc_tbl_data *)ioremap_wc(res_mem.start,
+ rsc_data_va = (__force struct rsc_tbl_data *)ioremap_wc(res_mem.start,
sizeof(struct rsc_tbl_data));
if (!rsc_data_va) {
dev_err(dev, "failed to map resource table data address\n");
@@ -794,7 +801,7 @@ static int zynqmp_r5_get_rsc_table_va(struct zynqmp_r5_core *r5_core)
return -EINVAL;
}
- rsc_tbl_addr = (struct resource_table *)r5_core->rsc_tbl_va;
+ rsc_tbl_addr = (__force struct resource_table *)r5_core->rsc_tbl_va;
/*
* As of now resource table version 1 is expected. Don't fail to attach
@@ -834,7 +841,7 @@ static int zynqmp_r5_handle_rsc(struct rproc *rproc, u32 rsc_type, void *rsc,
int offset, int avail)
{
struct zynqmp_r5_core *r5_core = rproc->priv;
- void *rsc_offset = (r5_core->rsc_tbl_va + offset);
+ void *rsc_offset = (__force void *)(r5_core->rsc_tbl_va + offset);
if (rsc_type != XLNX_RPROC_FW_CRASH_REPORT)
return RSC_IGNORED;
diff --git a/drivers/rpmsg/virtio_rpmsg_bus.c b/drivers/rpmsg/virtio_rpmsg_bus.c
index 1b8bb05924af..40ec93fedde9 100644
--- a/drivers/rpmsg/virtio_rpmsg_bus.c
+++ b/drivers/rpmsg/virtio_rpmsg_bus.c
@@ -20,6 +20,7 @@
#include <linux/rpmsg.h>
#include <linux/rpmsg/byteorder.h>
#include <linux/rpmsg/ns.h>
+#include <linux/rpmsg/virtio_rpmsg.h>
#include <linux/scatterlist.h>
#include <linux/slab.h>
#include <linux/sched.h>
@@ -35,13 +36,15 @@
* @vdev: the virtio device
* @rvq: rx virtqueue
* @svq: tx virtqueue
- * @rbufs: kernel address of rx buffers
- * @sbufs: kernel address of tx buffers
- * @num_bufs: total number of buffers for rx and tx
- * @buf_size: size of one rx or tx buffer
- * @last_sbuf: index of last tx buffer used
+ * @rx_bufs: kernel address of rx buffers
+ * @tx_bufs: kernel address of tx buffers
+ * @num_rx_buf: total number of rx buffers
+ * @num_tx_buf: total number of tx buffers
+ * @rx_buf_size: size of one rx buffer
+ * @tx_buf_size: size of one tx buffer
+ * @last_tx_buf: index of last tx buffer used
* @bufs_dma: dma base addr of the buffers
- * @tx_lock: protects svq and sbufs, to allow concurrent senders.
+ * @tx_lock: protects svq and tx_bufs, to allow concurrent senders.
* sending a message might require waking up a dozing remote
* processor, which involves sleeping, hence the mutex.
* @endpoints: idr of local endpoints, allows fast retrieval
@@ -55,10 +58,12 @@
struct virtproc_info {
struct virtio_device *vdev;
struct virtqueue *rvq, *svq;
- void *rbufs, *sbufs;
- unsigned int num_bufs;
- unsigned int buf_size;
- int last_sbuf;
+ void *rx_bufs, *tx_bufs;
+ unsigned int num_rx_buf;
+ unsigned int num_tx_buf;
+ unsigned int rx_buf_size;
+ unsigned int tx_buf_size;
+ int last_tx_buf;
dma_addr_t bufs_dma;
struct mutex tx_lock;
struct idr endpoints;
@@ -66,9 +71,6 @@ struct virtproc_info {
wait_queue_head_t sendq;
};
-/* The feature bitmap for virtio rpmsg */
-#define VIRTIO_RPMSG_F_NS 0 /* RP supports name service notifications */
-
/**
* struct rpmsg_hdr - common header for all rpmsg messages
* @src: source address
@@ -110,7 +112,7 @@ struct virtio_rpmsg_channel {
/*
* We're allocating buffers of 512 bytes each for communications. The
* number of buffers will be computed from the number of buffers supported
- * by the vring, upto a maximum of 512 buffers (256 in each direction).
+ * by the vring, up to a maximum of 256 in each direction.
*
* Each buffer will have 16 bytes for the msg header and 496 bytes for
* the payload.
@@ -125,8 +127,8 @@ struct virtio_rpmsg_channel {
* can change this without changing anything in the firmware of the remote
* processor.
*/
-#define MAX_RPMSG_NUM_BUFS (512)
-#define MAX_RPMSG_BUF_SIZE (512)
+#define MAX_RPMSG_NUM_BUFS (256)
+#define DEFAULT_RPMSG_BUF_SIZE (512)
/*
* Local addresses are dynamically allocated on-demand.
@@ -439,12 +441,9 @@ static void *get_a_tx_buf(struct virtproc_info *vrp)
mutex_lock(&vrp->tx_lock);
- /*
- * either pick the next unused tx buffer
- * (half of our buffers are used for sending messages)
- */
- if (vrp->last_sbuf < vrp->num_bufs / 2)
- ret = vrp->sbufs + vrp->buf_size * vrp->last_sbuf++;
+ /* either pick the next unused tx buffer */
+ if (vrp->last_tx_buf < vrp->num_tx_buf)
+ ret = vrp->tx_bufs + vrp->tx_buf_size * vrp->last_tx_buf++;
/* or recycle a used one */
else
ret = virtqueue_get_buf(vrp->svq, &len);
@@ -514,7 +513,7 @@ static int rpmsg_send_offchannel_raw(struct rpmsg_device *rpdev,
* messaging), or to improve the buffer allocator, to support
* variable-length buffer sizes.
*/
- if (len > vrp->buf_size - sizeof(struct rpmsg_hdr)) {
+ if (len > vrp->tx_buf_size - sizeof(struct rpmsg_hdr)) {
dev_err(dev, "message is too big (%d)\n", len);
return -EMSGSIZE;
}
@@ -630,11 +629,10 @@ static __poll_t virtio_rpmsg_poll(struct rpmsg_endpoint *ept, struct file *filp,
/*
* check for a free buffer, either:
- * - we haven't used all of the available transmit buffers (half of the
- * allocated buffers are used for transmit, hence num_bufs / 2), or,
+ * - we haven't used all of the available transmit buffers or,
* - we ask the virtqueue if there's a buffer available
*/
- if (vrp->last_sbuf < vrp->num_bufs / 2 ||
+ if (vrp->last_tx_buf < vrp->num_tx_buf ||
!virtqueue_enable_cb(vrp->svq))
mask |= EPOLLOUT;
@@ -648,7 +646,7 @@ static ssize_t virtio_rpmsg_get_mtu(struct rpmsg_endpoint *ept)
struct rpmsg_device *rpdev = ept->rpdev;
struct virtio_rpmsg_channel *vch = to_virtio_rpmsg_channel(rpdev);
- return vch->vrp->buf_size - sizeof(struct rpmsg_hdr);
+ return vch->vrp->tx_buf_size - sizeof(struct rpmsg_hdr);
}
static int rpmsg_recv_single(struct virtproc_info *vrp, struct device *dev,
@@ -674,7 +672,7 @@ static int rpmsg_recv_single(struct virtproc_info *vrp, struct device *dev,
* We currently use fixed-sized buffers, so trivially sanitize
* the reported payload length.
*/
- if (len > vrp->buf_size ||
+ if (len > vrp->rx_buf_size ||
msg_len > (len - sizeof(struct rpmsg_hdr))) {
dev_warn(dev, "inbound msg too big: (%d, %d)\n", len, msg_len);
return -EINVAL;
@@ -707,7 +705,7 @@ static int rpmsg_recv_single(struct virtproc_info *vrp, struct device *dev,
dev_warn_ratelimited(dev, "msg received with no recipient\n");
/* publish the real size of the buffer */
- rpmsg_sg_init(&sg, msg, vrp->buf_size);
+ rpmsg_sg_init(&sg, msg, vrp->rx_buf_size);
/* add the buffer back to the remote processor's virtqueue */
err = virtqueue_add_inbuf(vrp->rvq, &sg, 1, msg, GFP_KERNEL);
@@ -811,6 +809,65 @@ static void rpmsg_virtio_del_ctrl_dev(struct rpmsg_device *rpdev_ctrl)
device_unregister(&rpdev_ctrl->dev);
}
+static int rpmsg_virtio_get_buf_size(struct virtproc_info *vrp)
+{
+ struct virtio_device *vdev;
+ u8 version;
+ u16 size;
+
+ vdev = vrp->vdev;
+
+ /*
+ * If VIRTIO_RPMSG_F_BUFSZ feature is supported, then configure buf
+ * size from virtio device config space from the resource table.
+ * If the feature is not supported, then assign default buf size.
+ */
+ if (virtio_has_feature(vdev, VIRTIO_RPMSG_F_BUFSZ)) {
+ virtio_cread(vdev, struct virtio_rpmsg_config,
+ version, &version);
+
+ /* for now we support only v1 */
+ if (version != RPMSG_VDEV_CONFIG_V1) {
+ dev_err(&vdev->dev,
+ "unsupported vdev config version %u\n", version);
+ return -EINVAL;
+ }
+
+ /* size of the config space must match */
+ virtio_cread(vdev, struct virtio_rpmsg_config,
+ size, &size);
+ if (size != sizeof(struct virtio_rpmsg_config)) {
+ dev_err(&vdev->dev, "invalid size of vdev config %u\n",
+ size);
+ return -EINVAL;
+ }
+
+ /* note: tx and rx are defined from remote view */
+ virtio_cread(vdev, struct virtio_rpmsg_config,
+ txbuf_size, &vrp->rx_buf_size);
+ virtio_cread(vdev, struct virtio_rpmsg_config,
+ rxbuf_size, &vrp->tx_buf_size);
+
+ /* The buffers must hold at least the rpmsg header */
+ if (vrp->rx_buf_size < sizeof(struct rpmsg_hdr) ||
+ vrp->tx_buf_size < sizeof(struct rpmsg_hdr)) {
+ dev_err(&vdev->dev,
+ "bad vdev config: rx buf sz = %u, tx buf sz = %u\n",
+ vrp->rx_buf_size, vrp->tx_buf_size);
+ return -EINVAL;
+ }
+
+ dev_dbg(&vdev->dev,
+ "vdev config: ver=%u, rx sz = 0x%x, tx sz = 0x%x\n",
+ version, vrp->rx_buf_size, vrp->tx_buf_size);
+ } else {
+ vrp->rx_buf_size = DEFAULT_RPMSG_BUF_SIZE;
+ vrp->tx_buf_size = DEFAULT_RPMSG_BUF_SIZE;
+ }
+
+ return 0;
+}
+
static int rpmsg_probe(struct virtio_device *vdev)
{
struct virtqueue_info vqs_info[] = {
@@ -845,19 +902,23 @@ static int rpmsg_probe(struct virtio_device *vdev)
vrp->rvq = vqs[0];
vrp->svq = vqs[1];
- /* we expect symmetric tx/rx vrings */
- WARN_ON(virtqueue_get_vring_size(vrp->rvq) !=
- virtqueue_get_vring_size(vrp->svq));
-
/* we need less buffers if vrings are small */
- if (virtqueue_get_vring_size(vrp->rvq) < MAX_RPMSG_NUM_BUFS / 2)
- vrp->num_bufs = virtqueue_get_vring_size(vrp->rvq) * 2;
+ if (virtqueue_get_vring_size(vrp->rvq) < MAX_RPMSG_NUM_BUFS)
+ vrp->num_rx_buf = virtqueue_get_vring_size(vrp->rvq);
+ else
+ vrp->num_rx_buf = MAX_RPMSG_NUM_BUFS;
+
+ if (virtqueue_get_vring_size(vrp->svq) < MAX_RPMSG_NUM_BUFS)
+ vrp->num_tx_buf = virtqueue_get_vring_size(vrp->svq);
else
- vrp->num_bufs = MAX_RPMSG_NUM_BUFS;
+ vrp->num_tx_buf = MAX_RPMSG_NUM_BUFS;
- vrp->buf_size = MAX_RPMSG_BUF_SIZE;
+ err = rpmsg_virtio_get_buf_size(vrp);
+ if (err)
+ goto vqs_del;
- total_buf_space = vrp->num_bufs * vrp->buf_size;
+ total_buf_space = (vrp->num_rx_buf * vrp->rx_buf_size) +
+ (vrp->num_tx_buf * vrp->tx_buf_size);
/* allocate coherent memory for the buffers */
bufs_va = dma_alloc_coherent(vdev->dev.parent,
@@ -871,18 +932,17 @@ static int rpmsg_probe(struct virtio_device *vdev)
dev_dbg(&vdev->dev, "buffers: va %p, dma %pad\n",
bufs_va, &vrp->bufs_dma);
- /* half of the buffers is dedicated for RX */
- vrp->rbufs = bufs_va;
+ /* first part of the buffers is dedicated for RX */
+ vrp->rx_bufs = bufs_va;
- /* and half is dedicated for TX */
- vrp->sbufs = bufs_va + total_buf_space / 2;
+ vrp->tx_bufs = bufs_va + (vrp->num_rx_buf * vrp->rx_buf_size);
/* set up the receive buffers */
- for (i = 0; i < vrp->num_bufs / 2; i++) {
+ for (i = 0; i < vrp->num_rx_buf; i++) {
struct scatterlist sg;
- void *cpu_addr = vrp->rbufs + i * vrp->buf_size;
+ void *cpu_addr = vrp->rx_bufs + i * vrp->rx_buf_size;
- rpmsg_sg_init(&sg, cpu_addr, vrp->buf_size);
+ rpmsg_sg_init(&sg, cpu_addr, vrp->rx_buf_size);
err = virtqueue_add_inbuf(vrp->rvq, &sg, 1, cpu_addr,
GFP_KERNEL);
@@ -965,7 +1025,8 @@ static int rpmsg_remove_device(struct device *dev, void *data)
static void rpmsg_remove(struct virtio_device *vdev)
{
struct virtproc_info *vrp = vdev->priv;
- size_t total_buf_space = vrp->num_bufs * vrp->buf_size;
+ size_t total_buf_space = (vrp->num_rx_buf * vrp->rx_buf_size) +
+ (vrp->num_tx_buf * vrp->tx_buf_size);
int ret;
virtio_reset_device(vdev);
@@ -979,7 +1040,7 @@ static void rpmsg_remove(struct virtio_device *vdev)
vdev->config->del_vqs(vrp->vdev);
dma_free_coherent(vdev->dev.parent, total_buf_space,
- vrp->rbufs, vrp->bufs_dma);
+ vrp->rx_bufs, vrp->bufs_dma);
kfree(vrp);
}
@@ -991,6 +1052,7 @@ static struct virtio_device_id id_table[] = {
static unsigned int features[] = {
VIRTIO_RPMSG_F_NS,
+ VIRTIO_RPMSG_F_BUFSZ,
};
static struct virtio_driver virtio_ipc_driver = {
diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig
index 9f703261d7ac..535c8619197b 100644
--- a/drivers/soc/qcom/Kconfig
+++ b/drivers/soc/qcom/Kconfig
@@ -318,6 +318,18 @@ config QCOM_QMI_HELPERS
tristate
depends on NET
+config QCOM_QMI_TMD
+ tristate "Qualcomm remote subsystem TMD" if COMPILE_TEST
+ depends on NET
+ depends on ARCH_QCOM || COMPILE_TEST
+ select QRTR
+ select QCOM_QMI_HELPERS
+ help
+ This enables Qualcomm Messaging Interface (QMI) based Thermal Mitigation
+ Device (TMD) support for Qualcomm remote subsystems. It manages
+ TMD messaging and handles QMI communication with remote processors
+ to exchange mitigation state and apply thermal mitigation requests.
+
config QCOM_UBWC_CONFIG
tristate
depends on QCOM_SMEM
diff --git a/drivers/soc/qcom/Makefile b/drivers/soc/qcom/Makefile
index 798643be3590..bddd6f0a5836 100644
--- a/drivers/soc/qcom/Makefile
+++ b/drivers/soc/qcom/Makefile
@@ -14,6 +14,7 @@ obj-$(CONFIG_QCOM_PMIC_GLINK) += pmic_glink.o
obj-$(CONFIG_QCOM_PMIC_GLINK) += pmic_glink_altmode.o
obj-$(CONFIG_QCOM_PMIC_PDCHARGER_ULOG) += pmic_pdcharger_ulog.o
CFLAGS_pmic_pdcharger_ulog.o := -I$(src)
+obj-$(CONFIG_QCOM_QMI_TMD) += qmi_tmd.o
obj-$(CONFIG_QCOM_QMI_HELPERS) += qmi_helpers.o
qmi_helpers-y += qmi_encdec.o qmi_interface.o
obj-$(CONFIG_QCOM_RAMP_CTRL) += ramp_controller.o
diff --git a/drivers/soc/qcom/qmi_tmd.c b/drivers/soc/qcom/qmi_tmd.c
new file mode 100644
index 000000000000..54e5ce8dc462
--- /dev/null
+++ b/drivers/soc/qcom/qmi_tmd.c
@@ -0,0 +1,595 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2025, Linaro Limited
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ *
+ * QMI Thermal Mitigation Device (TMD).
+ * Provides cooling device support for remote subsystems
+ * running the TMD service via QMI.
+ */
+#include <linux/cleanup.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/module.h>
+#include <linux/net.h>
+#include <linux/of.h>
+#include <linux/slab.h>
+#include <linux/soc/qcom/qmi.h>
+#include <linux/soc/qcom/qmi_tmd.h>
+#include <linux/thermal.h>
+
+#define QMI_TMD_SERVICE_VERS_V01 0x01
+
+#define QMI_TMD_SET_LEVEL_REQ 0x0021
+#define QMI_TMD_GET_DEV_LIST_REQ 0x0020
+
+#define QMI_TMD_DEV_ID_LEN_MAX 32
+#define QMI_TMD_DEV_LIST_MAX 32
+#define QMI_TMD_RESP_TIMEOUT msecs_to_jiffies(100)
+#define TMD_GET_LEVEL_REQ_MAX_LEN 36
+#define TMD_SET_LEVEL_REQ_MAX_LEN 40
+
+#define TMD_GET_DEV_LIST_REQ_MAX_LEN 0
+#define TMD_GET_DEV_LIST_RESP_MAX_LEN 1099
+
+struct tmd_dev_id {
+ char mitigation_dev_id[QMI_TMD_DEV_ID_LEN_MAX + 1];
+};
+
+static const struct qmi_elem_info tmd_dev_id_ei[] = {
+ {
+ .data_type = QMI_STRING,
+ .elem_len = QMI_TMD_DEV_ID_LEN_MAX + 1,
+ .elem_size = sizeof(char),
+ .array_type = NO_ARRAY,
+ .tlv_type = 0,
+ .offset = offsetof(struct tmd_dev_id,
+ mitigation_dev_id),
+ },
+ {
+ .data_type = QMI_EOTI,
+ .array_type = NO_ARRAY,
+ .tlv_type = QMI_COMMON_TLV_TYPE,
+ },
+};
+
+struct tmd_dev_list {
+ struct tmd_dev_id mitigation_dev_id;
+ u8 max_mitigation_level;
+};
+
+static const struct qmi_elem_info tmd_dev_list_ei[] = {
+ {
+ .data_type = QMI_STRUCT,
+ .elem_len = 1,
+ .elem_size = sizeof(struct tmd_dev_id),
+ .array_type = NO_ARRAY,
+ .tlv_type = 0,
+ .offset = offsetof(struct tmd_dev_list,
+ mitigation_dev_id),
+ .ei_array = tmd_dev_id_ei,
+ },
+ {
+ .data_type = QMI_UNSIGNED_1_BYTE,
+ .elem_len = 1,
+ .elem_size = sizeof(uint8_t),
+ .array_type = NO_ARRAY,
+ .tlv_type = 0,
+ .offset = offsetof(struct tmd_dev_list,
+ max_mitigation_level),
+ },
+ {
+ .data_type = QMI_EOTI,
+ .array_type = NO_ARRAY,
+ .tlv_type = QMI_COMMON_TLV_TYPE,
+ },
+};
+
+struct tmd_get_dev_list_req {
+ char placeholder;
+};
+
+static const struct qmi_elem_info tmd_get_dev_list_req_ei[] = {
+ {
+ .data_type = QMI_EOTI,
+ .array_type = NO_ARRAY,
+ .tlv_type = QMI_COMMON_TLV_TYPE,
+ },
+};
+
+struct tmd_get_dev_list_resp {
+ struct qmi_response_type_v01 resp;
+ u8 mitigation_device_list_valid;
+ u32 mitigation_device_list_len;
+ struct tmd_dev_list
+ mitigation_device_list[QMI_TMD_DEV_LIST_MAX];
+};
+
+static const struct qmi_elem_info tmd_get_dev_list_resp_ei[] = {
+ {
+ .data_type = QMI_STRUCT,
+ .elem_len = 1,
+ .elem_size = sizeof(struct qmi_response_type_v01),
+ .array_type = NO_ARRAY,
+ .tlv_type = 0x02,
+ .offset = offsetof(struct tmd_get_dev_list_resp,
+ resp),
+ .ei_array = qmi_response_type_v01_ei,
+ },
+ {
+ .data_type = QMI_OPT_FLAG,
+ .elem_len = 1,
+ .elem_size = sizeof(uint8_t),
+ .array_type = NO_ARRAY,
+ .tlv_type = 0x10,
+ .offset = offsetof(struct tmd_get_dev_list_resp,
+ mitigation_device_list_valid),
+ },
+ {
+ .data_type = QMI_DATA_LEN,
+ .elem_len = 1,
+ .elem_size = sizeof(uint8_t),
+ .array_type = NO_ARRAY,
+ .tlv_type = 0x10,
+ .offset = offsetof(struct tmd_get_dev_list_resp,
+ mitigation_device_list_len),
+ },
+ {
+ .data_type = QMI_STRUCT,
+ .elem_len = QMI_TMD_DEV_LIST_MAX,
+ .elem_size = sizeof(struct tmd_dev_list),
+ .array_type = VAR_LEN_ARRAY,
+ .tlv_type = 0x10,
+ .offset = offsetof(struct tmd_get_dev_list_resp,
+ mitigation_device_list),
+ .ei_array = tmd_dev_list_ei,
+ },
+ {
+ .data_type = QMI_EOTI,
+ .array_type = NO_ARRAY,
+ .tlv_type = QMI_COMMON_TLV_TYPE,
+ },
+};
+
+struct tmd_set_level_req {
+ struct tmd_dev_id mitigation_dev_id;
+ u8 mitigation_level;
+};
+
+static const struct qmi_elem_info tmd_set_level_req_ei[] = {
+ {
+ .data_type = QMI_STRUCT,
+ .elem_len = 1,
+ .elem_size = sizeof(struct tmd_dev_id),
+ .array_type = NO_ARRAY,
+ .tlv_type = 0x01,
+ .offset = offsetof(struct tmd_set_level_req,
+ mitigation_dev_id),
+ .ei_array = tmd_dev_id_ei,
+ },
+ {
+ .data_type = QMI_UNSIGNED_1_BYTE,
+ .elem_len = 1,
+ .elem_size = sizeof(uint8_t),
+ .array_type = NO_ARRAY,
+ .tlv_type = 0x02,
+ .offset = offsetof(struct tmd_set_level_req,
+ mitigation_level),
+ },
+ {
+ .data_type = QMI_EOTI,
+ .array_type = NO_ARRAY,
+ .tlv_type = QMI_COMMON_TLV_TYPE,
+ },
+};
+
+struct tmd_set_level_resp {
+ struct qmi_response_type_v01 resp;
+};
+
+static const struct qmi_elem_info tmd_set_level_resp_ei[] = {
+ {
+ .data_type = QMI_STRUCT,
+ .elem_len = 1,
+ .elem_size = sizeof(struct qmi_response_type_v01),
+ .array_type = NO_ARRAY,
+ .tlv_type = 0x02,
+ .offset = offsetof(struct tmd_set_level_resp, resp),
+ .ei_array = qmi_response_type_v01_ei,
+ },
+ {
+ .data_type = QMI_EOTI,
+ .array_type = NO_ARRAY,
+ .tlv_type = QMI_COMMON_TLV_TYPE,
+ },
+};
+
+/**
+ * struct qmi_tmd - A TMD cooling device
+ * @name: The name of this tmd shared by the remote subsystem
+ * @cdev: Thermal cooling device handle
+ * @cur_state: The current mitigation state
+ * @max_state: The maximum state
+ * @qmi_tmd_cli: Parent QMI TMD client
+ */
+struct qmi_tmd {
+ const char *name;
+ struct thermal_cooling_device *cdev;
+ unsigned int cur_state;
+ unsigned int max_state;
+ struct qmi_tmd_client *qmi_tmd_cli;
+};
+
+/**
+ * struct qmi_tmd_client - QMI TMD client state
+ * @dev: Device associated with this instance
+ * @handle: QMI connection handle
+ * @mutex: Serializes QMI request/response sequences (qmi_txn_init,
+ * qmi_send_request) during DSP subsystem restart and
+ * protects @connection_active and @exiting
+ * @connection_active: Whether or not we're connected to the QMI TMD service
+ * @exiting: Whether or not teardown has started
+ * @svc_arrive_work: Work item for initialising when the TMD service starts
+ * @num_tmds: Number of tmds described in the device tree
+ * @tmds: An array of tmd structures
+ */
+struct qmi_tmd_client {
+ struct device *dev;
+ struct qmi_handle handle;
+ /* protects QMI transactions, connection_active and exiting */
+ struct mutex mutex;
+ bool connection_active;
+ bool exiting;
+ struct work_struct svc_arrive_work;
+ int num_tmds;
+ struct qmi_tmd tmds[] __counted_by(num_tmds);
+};
+
+/* Notify the remote subsystem of the requested cooling state */
+static int qmi_tmd_send_state_request(struct qmi_tmd *tmd, int state)
+{
+ struct tmd_set_level_resp resp = { 0 };
+ struct tmd_set_level_req req = { 0 };
+ struct qmi_tmd_client *qmi_tmd_cli = tmd->qmi_tmd_cli;
+ struct qmi_txn txn;
+ int ret = 0;
+
+ if (!qmi_tmd_cli->connection_active)
+ return -ENOTCONN;
+
+ strscpy(req.mitigation_dev_id.mitigation_dev_id, tmd->name,
+ QMI_TMD_DEV_ID_LEN_MAX + 1);
+ req.mitigation_level = state;
+
+ ret = qmi_txn_init(&qmi_tmd_cli->handle, &txn,
+ tmd_set_level_resp_ei, &resp);
+ if (ret < 0) {
+ dev_err(qmi_tmd_cli->dev, "qmi set state %d txn init failed for %s ret %d\n",
+ state, tmd->name, ret);
+ return ret;
+ }
+
+ ret = qmi_send_request(&qmi_tmd_cli->handle, NULL, &txn,
+ QMI_TMD_SET_LEVEL_REQ,
+ TMD_SET_LEVEL_REQ_MAX_LEN,
+ tmd_set_level_req_ei, &req);
+ if (ret < 0) {
+ dev_err(qmi_tmd_cli->dev, "qmi set state %d txn send failed for %s ret %d\n",
+ state, tmd->name, ret);
+ qmi_txn_cancel(&txn);
+ return ret;
+ }
+
+ ret = qmi_txn_wait(&txn, QMI_TMD_RESP_TIMEOUT);
+ if (ret < 0) {
+ dev_err(qmi_tmd_cli->dev, "qmi set state %d txn wait failed for %s ret %d\n",
+ state, tmd->name, ret);
+ return ret;
+ }
+
+ if (resp.resp.result != QMI_RESULT_SUCCESS_V01) {
+ dev_err(qmi_tmd_cli->dev,
+ "qmi set state %d failed for %s result %#x error %#x\n",
+ state, tmd->name,
+ resp.resp.result, resp.resp.error);
+ return -EREMOTEIO;
+ }
+
+ dev_dbg(qmi_tmd_cli->dev, "Requested state %d/%d for %s\n", state,
+ tmd->max_state, tmd->name);
+
+ tmd->cur_state = state;
+
+ return 0;
+}
+
+static int qmi_tmd_get_max_state(struct thermal_cooling_device *cdev,
+ unsigned long *state)
+{
+ struct qmi_tmd *tmd = cdev->devdata;
+
+ *state = tmd->max_state;
+
+ return 0;
+}
+
+static int qmi_tmd_get_cur_state(struct thermal_cooling_device *cdev,
+ unsigned long *state)
+{
+ struct qmi_tmd *tmd = cdev->devdata;
+
+ /* cur_state is protected by thermal core's cdev->lock */
+ *state = tmd->cur_state;
+
+ return 0;
+}
+
+static int qmi_tmd_set_cur_state(struct thermal_cooling_device *cdev,
+ unsigned long state)
+{
+ struct qmi_tmd *tmd = cdev->devdata;
+
+ if (state > tmd->max_state)
+ return -EINVAL;
+
+ /* cur_state is protected by thermal core's cdev->lock */
+ if (tmd->cur_state == state)
+ return 0;
+
+ guard(mutex)(&tmd->qmi_tmd_cli->mutex);
+
+ return qmi_tmd_send_state_request(tmd, state);
+}
+
+static const struct thermal_cooling_device_ops qmi_tmd_cooling_ops = {
+ .get_max_state = qmi_tmd_get_max_state,
+ .get_cur_state = qmi_tmd_get_cur_state,
+ .set_cur_state = qmi_tmd_set_cur_state,
+};
+
+static int qmi_tmd_register(struct qmi_tmd_client *qmi_tmd_cli,
+ const char *label, u8 max_state)
+{
+ struct device *dev = qmi_tmd_cli->dev;
+ struct qmi_tmd *tmd;
+ int index;
+
+ for (index = 0; index < qmi_tmd_cli->num_tmds; index++) {
+ tmd = &qmi_tmd_cli->tmds[index];
+
+ if (!strncasecmp(tmd->name, label,
+ QMI_TMD_DEV_ID_LEN_MAX + 1))
+ goto found;
+ }
+
+ dev_dbg(qmi_tmd_cli->dev,
+ "TMD '%s' available in firmware but not specified in DT\n",
+ label);
+ return 0;
+
+found:
+ tmd->max_state = max_state;
+
+ /*
+ * If the cooling device already exists then the QMI service went away and
+ * came back. So just make sure the current cooling device state is
+ * reflected on the remote side and then return.
+ */
+ if (tmd->cdev) {
+ guard(mutex)(&qmi_tmd_cli->mutex);
+
+ return qmi_tmd_send_state_request(tmd, tmd->cur_state);
+ }
+
+ tmd->cdev = thermal_of_cooling_device_register(dev->of_node, index,
+ label, tmd, &qmi_tmd_cooling_ops);
+ if (IS_ERR(tmd->cdev)) {
+ int ret = PTR_ERR(tmd->cdev);
+
+ tmd->cdev = NULL;
+ return ret;
+ }
+
+ return 0;
+}
+
+static void qmi_tmd_unregister(struct qmi_tmd_client *qmi_tmd_cli)
+{
+ struct qmi_tmd *tmd;
+ int index;
+
+ for (index = 0; index < qmi_tmd_cli->num_tmds; index++) {
+ tmd = &qmi_tmd_cli->tmds[index];
+
+ if (!tmd->cdev)
+ continue;
+
+ thermal_cooling_device_unregister(tmd->cdev);
+ tmd->cdev = NULL;
+ }
+}
+
+static void qmi_tmd_svc_arrive(struct work_struct *work)
+{
+ struct qmi_tmd_client *qmi_tmd_cli =
+ container_of(work, struct qmi_tmd_client, svc_arrive_work);
+
+ struct tmd_get_dev_list_req req = { 0 };
+ struct tmd_get_dev_list_resp *resp __free(kfree) = NULL;
+ int ret, i;
+ struct qmi_txn txn;
+
+ resp = kzalloc_obj(*resp, GFP_KERNEL);
+ if (!resp) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ scoped_guard(mutex, &qmi_tmd_cli->mutex) {
+ if (qmi_tmd_cli->exiting)
+ return;
+
+ ret = qmi_txn_init(&qmi_tmd_cli->handle, &txn,
+ tmd_get_dev_list_resp_ei, resp);
+ if (ret < 0)
+ goto out;
+
+ ret = qmi_send_request(&qmi_tmd_cli->handle, NULL, &txn,
+ QMI_TMD_GET_DEV_LIST_REQ,
+ TMD_GET_DEV_LIST_REQ_MAX_LEN,
+ tmd_get_dev_list_req_ei, &req);
+ if (ret < 0) {
+ qmi_txn_cancel(&txn);
+ goto out;
+ }
+
+ ret = qmi_txn_wait(&txn, QMI_TMD_RESP_TIMEOUT);
+ if (ret < 0)
+ goto out;
+
+ if (resp->resp.result != QMI_RESULT_SUCCESS_V01) {
+ ret = -EPROTO;
+ goto out;
+ }
+
+ qmi_tmd_cli->connection_active = true;
+ }
+
+ for (i = 0; i < resp->mitigation_device_list_len; i++) {
+ struct tmd_dev_list *device =
+ &resp->mitigation_device_list[i];
+
+ ret = qmi_tmd_register(qmi_tmd_cli,
+ device->mitigation_dev_id.mitigation_dev_id,
+ device->max_mitigation_level);
+ if (ret)
+ break;
+ }
+
+out:
+ if (ret)
+ dev_err(qmi_tmd_cli->dev, "Failed to initialize TMD service: %d\n", ret);
+}
+
+static void qmi_tmd_del_server(struct qmi_handle *qmi, struct qmi_service *service)
+{
+ struct qmi_tmd_client *qmi_tmd_cli =
+ container_of(qmi, struct qmi_tmd_client, handle);
+
+ scoped_guard(mutex, &qmi_tmd_cli->mutex) {
+ qmi_tmd_cli->connection_active = false;
+ }
+}
+
+static int qmi_tmd_new_server(struct qmi_handle *qmi, struct qmi_service *service)
+{
+ struct sockaddr_qrtr sq = { AF_QIPCRTR, service->node, service->port };
+ struct qmi_tmd_client *qmi_tmd_cli;
+ int ret;
+
+ qmi_tmd_cli = container_of(qmi, struct qmi_tmd_client, handle);
+
+ scoped_guard(mutex, &qmi_tmd_cli->mutex) {
+ if (qmi_tmd_cli->exiting)
+ return 0;
+
+ ret = kernel_connect(qmi->sock, (struct sockaddr_unsized *)&sq,
+ sizeof(sq), 0);
+ }
+
+ if (ret < 0) {
+ dev_err(qmi_tmd_cli->dev, "QMI connect failed for node %u port %u: %d\n",
+ service->node, service->port, ret);
+ return ret;
+ }
+
+ queue_work(system_highpri_wq, &qmi_tmd_cli->svc_arrive_work);
+
+ return 0;
+}
+
+static const struct qmi_ops qmi_tmd_ops = {
+ .new_server = qmi_tmd_new_server,
+ .del_server = qmi_tmd_del_server,
+};
+
+/**
+ * qmi_tmd_init() - Initialize QMI TMD instance
+ * @dev: Device pointer
+ * @instance_id: QMI service instance ID for the remote subsystem
+ * @tmd_names: Array of TMD names
+ * @num_tmds: Number of TMD names
+ *
+ * Context: Must be called from probe context.
+ *
+ * Return: Pointer to qmi_tmd_client on success, ERR_PTR on failure
+ */
+struct qmi_tmd_client *qmi_tmd_init(struct device *dev,
+ unsigned int instance_id,
+ const char * const *tmd_names,
+ int num_tmds)
+{
+ struct qmi_tmd_client *qmi_tmd_cli;
+ int ret, i;
+
+ if (!dev || !tmd_names || num_tmds <= 0)
+ return ERR_PTR(-EINVAL);
+
+ qmi_tmd_cli = devm_kzalloc(dev, struct_size(qmi_tmd_cli, tmds, num_tmds), GFP_KERNEL);
+ if (!qmi_tmd_cli)
+ return ERR_PTR(-ENOMEM);
+
+ qmi_tmd_cli->dev = dev;
+ qmi_tmd_cli->num_tmds = num_tmds;
+ mutex_init(&qmi_tmd_cli->mutex);
+ INIT_WORK(&qmi_tmd_cli->svc_arrive_work, qmi_tmd_svc_arrive);
+
+ for (i = 0; i < num_tmds; i++) {
+ qmi_tmd_cli->tmds[i].name = tmd_names[i];
+ qmi_tmd_cli->tmds[i].qmi_tmd_cli = qmi_tmd_cli;
+ }
+
+ ret = qmi_handle_init(&qmi_tmd_cli->handle,
+ TMD_GET_DEV_LIST_RESP_MAX_LEN,
+ &qmi_tmd_ops, NULL);
+ if (ret < 0)
+ return ERR_PTR(dev_err_probe(dev, ret, "QMI handle init failed\n"));
+
+ ret = qmi_add_lookup(&qmi_tmd_cli->handle, QMI_SERVICE_ID_TMD,
+ QMI_TMD_SERVICE_VERS_V01, instance_id);
+ if (ret < 0) {
+ dev_err_probe(dev, ret, "QMI add lookup failed\n");
+ goto err_release_handle;
+ }
+
+ return qmi_tmd_cli;
+
+err_release_handle:
+ qmi_handle_release(&qmi_tmd_cli->handle);
+
+ return ERR_PTR(ret);
+}
+EXPORT_SYMBOL_GPL(qmi_tmd_init);
+
+/**
+ * qmi_tmd_exit() - Deinitialize QMI TMD instance
+ * @qmi_tmd_cli: QMI TMD client to deinitialize
+ */
+void qmi_tmd_exit(struct qmi_tmd_client *qmi_tmd_cli)
+{
+ if (!qmi_tmd_cli)
+ return;
+
+ scoped_guard(mutex, &qmi_tmd_cli->mutex) {
+ qmi_tmd_cli->exiting = true;
+ qmi_tmd_cli->connection_active = false;
+ }
+
+ qmi_handle_release(&qmi_tmd_cli->handle);
+ cancel_work_sync(&qmi_tmd_cli->svc_arrive_work);
+ qmi_tmd_unregister(qmi_tmd_cli);
+}
+EXPORT_SYMBOL_GPL(qmi_tmd_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Qualcomm QMI Thermal Mitigation support");