From ca96ea25e51aeae45b0c947feb432fc74e2d1928 Mon Sep 17 00:00:00 2001 From: Tanmay Shah Date: Fri, 28 Aug 2026 07:58:50 -0700 Subject: rpmsg: virtio_rpmsg_bus: Get buffer size from config space 512 bytes isn't always suitable for all the cases, let firmware maker decide the best value from the resource table. Enabled by VIRTIO_RPMSG_F_BUFSZ feature bit. Signed-off-by: Tanmay Shah Acked-by: Arnaud Pouliquen Link: https://lore.kernel.org/r/20260828145853.2843486-4-tanmay.shah@amd.com Signed-off-by: Mathieu Poirier --- include/linux/rpmsg/virtio_rpmsg.h | 41 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 include/linux/rpmsg/virtio_rpmsg.h (limited to 'include/linux') diff --git a/include/linux/rpmsg/virtio_rpmsg.h b/include/linux/rpmsg/virtio_rpmsg.h new file mode 100644 index 000000000000..0872cbca4593 --- /dev/null +++ b/include/linux/rpmsg/virtio_rpmsg.h @@ -0,0 +1,41 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) Pinecone Inc. 2019 + * Copyright (C) Xiang Xiao + * Copyright (C) Advanced Micro Devices, Inc. 2026 + */ + +#ifndef _LINUX_VIRTIO_RPMSG_H +#define _LINUX_VIRTIO_RPMSG_H + +#include +#include + +/* The feature bitmap for virtio rpmsg */ +#define VIRTIO_RPMSG_F_NS 0 /* RP supports name service notifications */ +#define VIRTIO_RPMSG_F_BUFSZ 1 /* RP advertises RX/TX buffer sizes from config space */ + +/* Version of struct virtio_rpmsg_config understood by this driver */ +#define RPMSG_VDEV_CONFIG_V1 1 + +/** + * struct virtio_rpmsg_config - config space for rpmsg virtio device + * + * @version: version of this structure, currently %RPMSG_VDEV_CONFIG_V1. + * @size: size of this structure in bytes. + * @txbuf_size: Tx buf size from remote's view. For Linux this is rx buf size. + * @rxbuf_size: Rx buf size from remote's view. For Linux this is tx buf size. + * + * This is the configuration structure shared by the device and the driver, + * read when %VIRTIO_RPMSG_F_BUFSZ is negotiated. The fields are laid out so + * the structure is naturally 32-bit aligned. + */ +struct virtio_rpmsg_config { + u8 version; + __virtio16 size; + /* The tx/rx individual buffer size (if VIRTIO_RPMSG_F_BUFSZ) */ + __virtio32 txbuf_size; + __virtio32 rxbuf_size; +} __packed; + +#endif /* _LINUX_VIRTIO_RPMSG_H */ -- cgit v1.2.3 From fa62b6f795c4091bd6a81c8f3ad99c43edce316b Mon Sep 17 00:00:00 2001 From: Andrew Davis Date: Fri, 4 Sep 2026 14:56:33 -0500 Subject: remoteproc: k3-dsp: Fix usage of omap_mbox_message and mbox_msg_t The type of message sent using omap-mailbox is always u32. The definition of mbox_msg_t is uintptr_t which is wrong as that type changes based on the architecture (32bit vs 64bit). Make the type fixed to u32. This then means we need to fix the helper macro omap_mbox_message so it doesn't cast to u32 twice. What this macro should be doing is converting from the message type returned from the mailbox framework into the omap-mailbox type. When fixing this we should add a macro for the other direction (from omap-mailbox type to something that can be used with mbox_send_message). After these changes, make use of the new macros as appropriate. Signed-off-by: Andrew Davis Reviewed-by: Beleswar Padhi Tested-by: Beleswar Padhi Link: https://lore.kernel.org/r/20260904195634.2275867-2-afd@ti.com Signed-off-by: Mathieu Poirier --- drivers/remoteproc/omap_remoteproc.c | 10 +++++----- drivers/remoteproc/ti_k3_common.c | 10 ++-------- include/linux/omap-mailbox.h | 5 +++-- 3 files changed, 10 insertions(+), 15 deletions(-) (limited to 'include/linux') diff --git a/drivers/remoteproc/omap_remoteproc.c b/drivers/remoteproc/omap_remoteproc.c index 6ed0f28edac9..efff95b8e363 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; 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/include/linux/omap-mailbox.h b/include/linux/omap-mailbox.h index 3cc5c4ed7f5a..e45f8c3841c2 100644 --- a/include/linux/omap-mailbox.h +++ b/include/linux/omap-mailbox.h @@ -6,8 +6,9 @@ #ifndef OMAP_MAILBOX_H #define OMAP_MAILBOX_H -typedef uintptr_t mbox_msg_t; +typedef u32 mbox_msg_t; -#define omap_mbox_message(data) (u32)(mbox_msg_t)(data) +#define omap_mbox_to_message(data) ((void *)(uintptr_t)(data)) +#define omap_mbox_from_message(data) ((mbox_msg_t)(uintptr_t)(data)) #endif /* OMAP_MAILBOX_H */ -- cgit v1.2.3 From 2ff581c2185c3797fae2725537c7de1da3bc89fd Mon Sep 17 00:00:00 2001 From: Casey Connolly Date: Sun, 9 Aug 2026 17:58:58 +0530 Subject: soc: qcom: Add QMI TMD support for remote thermal mitigation Add support for Qualcomm Messaging Interface (QMI) based Thermal Mitigation Device (TMD) cooling devices provided by remote subsystems. On Qualcomm platforms where remote processors expose mitigation controls through the TMD QMI service, client drivers need support to discover the service, register cooling devices for available mitigation endpoints, and forward cooling state updates to remote subsystems. Signed-off-by: Casey Connolly Co-developed-by: Daniel Lezcano Signed-off-by: Daniel Lezcano Co-developed-by: Gaurav Kohli Signed-off-by: Gaurav Kohli Link: https://lore.kernel.org/r/20260809-b4-qmi-tmd-v8-2-b15d47adc379@oss.qualcomm.com Signed-off-by: Bjorn Andersson --- MAINTAINERS | 7 + drivers/soc/qcom/Kconfig | 12 + drivers/soc/qcom/Makefile | 1 + drivers/soc/qcom/qmi_tmd.c | 595 +++++++++++++++++++++++++++++++++++++++ include/linux/soc/qcom/qmi.h | 1 + include/linux/soc/qcom/qmi_tmd.h | 36 +++ 6 files changed, 652 insertions(+) create mode 100644 drivers/soc/qcom/qmi_tmd.c create mode 100644 include/linux/soc/qcom/qmi_tmd.h (limited to 'include/linux') diff --git a/MAINTAINERS b/MAINTAINERS index c5d45425abb5..bb15e062b9f0 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -22586,6 +22586,13 @@ F: Documentation/devicetree/bindings/net/qcom,ipq9574-ppe.yaml F: Documentation/networking/device_drivers/ethernet/qualcomm/ppe/ppe.rst F: drivers/net/ethernet/qualcomm/ppe/ +QUALCOMM QMI (REMOTEPROC THERMAL MITIGATION) TMD +M: Gaurav Kohli +L: linux-arm-msm@vger.kernel.org +L: linux-pm@vger.kernel.org +S: Maintained +F: drivers/soc/qcom/qmi_tmd.c + QUALCOMM QSEECOM DRIVER M: Maximilian Luz L: linux-arm-msm@vger.kernel.org diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig index 2b524154d9fb..1d2f1c059112 100644 --- a/drivers/soc/qcom/Kconfig +++ b/drivers/soc/qcom/Kconfig @@ -319,6 +319,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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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"); diff --git a/include/linux/soc/qcom/qmi.h b/include/linux/soc/qcom/qmi.h index b9dcb437a0be..683d27cee413 100644 --- a/include/linux/soc/qcom/qmi.h +++ b/include/linux/soc/qcom/qmi.h @@ -96,6 +96,7 @@ struct qmi_elem_info { * Enumerate the IDs of the QMI services */ #define QMI_SERVICE_ID_TEST 0x0f /* 15 */ +#define QMI_SERVICE_ID_TMD 0x18 /* 24 */ #define QMI_SERVICE_ID_SSCTL 0x2b /* 43 */ #define QMI_SERVICE_ID_IPA 0x31 /* 49 */ #define QMI_SERVICE_ID_SERVREG_LOC 0x40 /* 64 */ diff --git a/include/linux/soc/qcom/qmi_tmd.h b/include/linux/soc/qcom/qmi_tmd.h new file mode 100644 index 000000000000..452f51f23731 --- /dev/null +++ b/include/linux/soc/qcom/qmi_tmd.h @@ -0,0 +1,36 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2025, Linaro Limited + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + * + * QMI Thermal Mitigation Device (TMD) library header. + */ + +#ifndef __QMI_TMD_H__ +#define __QMI_TMD_H__ + +struct device; +struct qmi_tmd_client; + +#if IS_ENABLED(CONFIG_QCOM_QMI_TMD) +struct qmi_tmd_client *qmi_tmd_init(struct device *dev, + unsigned int instance_id, + const char * const *tmd_names, + int num_tmds); + +void qmi_tmd_exit(struct qmi_tmd_client *tmd_cli); +#else +static inline struct qmi_tmd_client *qmi_tmd_init(struct device *dev, + unsigned int instance_id, + const char * const *tmd_names, + int num_tmds) +{ + return NULL; +} + +static inline void qmi_tmd_exit(struct qmi_tmd_client *tmd_cli) +{ +} +#endif + +#endif /* __QMI_TMD_H__ */ -- cgit v1.2.3 From 5490279e827490d4cc8e1bcd048ba129051d59d4 Mon Sep 17 00:00:00 2001 From: Yonghao Zhang Date: Thu, 1 Oct 2026 16:10:56 +0800 Subject: remoteproc: core: Fetch the auto-boot firmware only once Auto-boot for an always-on remote processor registers an asynchronous request_firmware_nowait() whose callback, rproc_auto_boot_callback(), calls rproc_boot() first and only releases the fetched image at the very end of the callback. rproc_boot() does not take this image as an argument: it fetches the very same image again with a synchronous request_firmware(), so every auto-boot reads the firmware image twice and allocates the buffer twice; on kernels with the sysfs fallback enabled, the uevent round-trip is repeated as well. The asynchronous request exists only so that rproc_add() does not block on the filesystem read; the image it fetches is never used. The core already defers rproc_boot() to a worker for detached processors (attach_work). Reuse that worker for offline processors too and drop the asynchronous firmware request: rproc_boot() fetches the image exactly once and dispatches between a firmware boot and an attach based on the processor state. The work is renamed to boot_work to match its widened role. commit 400e64df6b23 ("remoteproc: add framework for controlling remote processors") noted back in 2011 that "we must wait until it completes before we try to unregister the device". rproc_del() now does exactly that: it waits for the boot work with cancel_work_sync(), which also closes the theoretical window in which a pending attach work could outlive the rproc instance. The changed fallback behaviour only matters for legacy configurations. udev dropped its userspace firmware loader back in 2014, as recorded in Documentation/driver-api/firmware/fallback-mechanisms.rst, and the kernel has documented since commit 02c399306826 ("firmware_loader: enhance Kconfig documentation over FW_LOADER") that "Linux no longer relies on or uses a fallback mechanism in userspace". Auto-boot now uses the same synchronous fallback semantics as every other explicit boot source (sysfs, cdev). Signed-off-by: Yonghao Zhang Link: https://lore.kernel.org/r/20261001081056.38293-1-hyz3367@gmail.com Signed-off-by: Mathieu Poirier --- drivers/remoteproc/remoteproc_core.c | 66 ++++++++++-------------------------- include/linux/remoteproc.h | 4 +-- 2 files changed, 19 insertions(+), 51 deletions(-) (limited to 'include/linux') diff --git a/drivers/remoteproc/remoteproc_core.c b/drivers/remoteproc/remoteproc_core.c index 263e12f022ea..b88bf179507d 100644 --- a/drivers/remoteproc/remoteproc_core.c +++ b/drivers/remoteproc/remoteproc_core.c @@ -1662,51 +1662,23 @@ disable_iommu: } /* - * take a firmware and boot it up. - * - * Note: this function is called asynchronously upon registration of the - * remote processor (so we must wait until it completes before we try - * to unregister the device. one other option is just to use kref here, - * that might be cleaner). + * Boot or attach the remote processor in the background: rproc_add() + * runs in probe context and must not block while the firmware image is + * read from storage. rproc_boot() dispatches on the processor state, + * so this covers both a firmware boot and an attach to a processor + * started by another entity. + * + * Note: rproc_del() waits for this work to complete with + * cancel_work_sync(), so the rproc instance remains valid for the + * entire lifetime of this function. */ -static void rproc_auto_boot_callback(const struct firmware *fw, void *context) -{ - struct rproc *rproc = context; - - rproc_boot(rproc); - - release_firmware(fw); -} - -static void rproc_attach_work(struct work_struct *work) +static void rproc_boot_work(struct work_struct *work) { - struct rproc *rproc = container_of(work, struct rproc, attach_work); + struct rproc *rproc = container_of(work, struct rproc, boot_work); rproc_boot(rproc); } -static int rproc_trigger_auto_boot(struct rproc *rproc) -{ - int ret; - - if (rproc->state == RPROC_DETACHED) { - schedule_work(&rproc->attach_work); - return 0; - } - - /* - * We're initiating an asynchronous firmware loading, so we can - * be built-in kernel code, without hanging the boot process. - */ - ret = request_firmware_nowait(THIS_MODULE, FW_ACTION_UEVENT, - rproc->firmware, &rproc->dev, GFP_KERNEL, - rproc, rproc_auto_boot_callback); - if (ret < 0) - dev_err(&rproc->dev, "request_firmware_nowait err: %d\n", ret); - - return ret; -} - static int rproc_stop(struct rproc *rproc, bool crashed) { struct device *dev = &rproc->dev; @@ -2348,11 +2320,8 @@ int rproc_add(struct rproc *rproc) rproc_create_debug_dir(rproc); /* if rproc is marked always-on, request it to boot */ - if (rproc->auto_boot) { - ret = rproc_trigger_auto_boot(rproc); - if (ret < 0) - goto rproc_remove_dev; - } + if (rproc->auto_boot) + schedule_work(&rproc->boot_work); /* expose to rproc_get_by_phandle users */ mutex_lock(&rproc_list_mutex); @@ -2361,10 +2330,6 @@ int rproc_add(struct rproc *rproc) return 0; -rproc_remove_dev: - cancel_work_sync(&rproc->crash_handler); - rproc_delete_debug_dir(rproc); - device_del(dev); rproc_remove_cdev: rproc_char_device_remove(rproc); return ret; @@ -2552,7 +2517,7 @@ struct rproc *rproc_alloc(struct device *dev, const char *name, INIT_LIST_HEAD(&rproc->subdevs); INIT_LIST_HEAD(&rproc->dump_segments); - INIT_WORK(&rproc->attach_work, rproc_attach_work); + INIT_WORK(&rproc->boot_work, rproc_boot_work); INIT_WORK(&rproc->crash_handler, rproc_crash_handler_work); spin_lock_init(&rproc->crash_handler_lock); @@ -2630,6 +2595,9 @@ int rproc_del(struct rproc *rproc) if (cancel_work_sync(&rproc->crash_handler)) pm_relax(rproc->dev.parent); + /* auto-boot may still be fetching firmware: wait for it here */ + cancel_work_sync(&rproc->boot_work); + __rproc_shutdown(rproc, true); rproc_delete_debug_dir(rproc); diff --git a/include/linux/remoteproc.h b/include/linux/remoteproc.h index a44368737b39..d77e24539133 100644 --- a/include/linux/remoteproc.h +++ b/include/linux/remoteproc.h @@ -231,7 +231,7 @@ enum rproc_features { * @subdevs: list of subdevices, to following the running state * @notifyids: idr for dynamically assigning rproc-wide unique notify ids * @index: index of this rproc device - * @attach_work: workqueue for attaching rproc + * @boot_work: workqueue for booting rproc * @crash_handler: workqueue for handling a crash * @crash_handler_lock: serializes crash handler queueing and deletion * @deleting: remoteproc deletion has begun @@ -277,7 +277,7 @@ struct rproc { struct list_head subdevs; struct idr notifyids; int index; - struct work_struct attach_work; + struct work_struct boot_work; struct work_struct crash_handler; spinlock_t crash_handler_lock; bool deleting; -- cgit v1.2.3