[PATCH v2 07/23] drm/msm/dcp: Handle asynchronous HFI responses
From: Jayant Shekhar
Date: Mon Oct 26 2026 - 02:27:06 EST
Handle HFI messages that are not synchronous command
completions. Capability discovery, resource voting, and display power
control produce asynchronous responses that must not be mistaken for
ordinary command replies.
Dispatch display events, cache capability payloads, deliver
power-control notifications, and defer resource-vote processing while
preserving the existing completion path for synchronous commands.
Assisted-by: LLM
Signed-off-by: Jayant Shekhar <jayant.shekhar@xxxxxxxxxxxxxxxx>
---
drivers/gpu/drm/msm/dcp/dcp_hfi.h | 49 ++++++
drivers/gpu/drm/msm/dcp/dcp_hfi_core.c | 298 +++++++++++++++++++++++++++++++++
2 files changed, 347 insertions(+)
diff --git a/drivers/gpu/drm/msm/dcp/dcp_hfi.h b/drivers/gpu/drm/msm/dcp/dcp_hfi.h
index 23e1cd39b89d..055e9c584892 100644
--- a/drivers/gpu/drm/msm/dcp/dcp_hfi.h
+++ b/drivers/gpu/drm/msm/dcp/dcp_hfi.h
@@ -19,11 +19,24 @@
#include <linux/workqueue.h>
#include <linux/xarray.h>
+#include <drm/drm_mipi_dsi.h>
+#include <drm/drm_modes.h>
+
#include "hfi/hfi_packet.h"
#include "hfi/hfi_pack_unpack_common.h"
#include "hfi/hfi_packer.h"
#include "hfi/hfi_unpacker.h"
+#include "hfi/hfi_commands_device.h"
+#include "hfi/hfi_commands_display.h"
+#include "hfi/hfi_commands_panel.h"
+#include "hfi/hfi_commands_display_events.h"
#include "hfi/hfi_defs_common.h"
+#include "hfi/hfi_defs_display.h"
+#include "hfi/hfi_defs_device.h"
+#include "hfi/hfi_defs_panel.h"
+#include "hfi/hfi_properties_device.h"
+#include "hfi/hfi_properties_display.h"
+#include "hfi/hfi_properties_panel.h"
struct dcp_drv;
@@ -35,6 +48,19 @@ struct dcp_drv;
#define DCP_MAX_DISPLAYS 4
#define DCP_HFI_CAPS_BUF_SIZE 4096
+#define DCP_HFI_RESOURCE_VOTE_BUF_SIZE 256
+
+/* Wire-format resource vote entry. */
+struct dcp_resource_vote_pair {
+ u32 key;
+ u64 value;
+};
+
+struct dcp_resource_vote_wire {
+ __le32 key;
+ __le64 value;
+} __packed;
+
struct dcp_hfi_caps_cache {
struct completion device_done;
struct completion display_done;
@@ -135,6 +161,24 @@ struct dcp_hfi {
atomic_t packet_id;
/* Blocks new work once transport teardown begins. */
atomic_t shutdown_in_progress;
+ struct work_struct resource_vote_work;
+ /* Protects resource_vote_buf, resource_vote_len, and callback state. */
+ spinlock_t resource_vote_lock;
+ u8 resource_vote_buf[DCP_HFI_RESOURCE_VOTE_BUF_SIZE];
+ u32 resource_vote_len;
+ u32 resource_vote_generation;
+ void (*resource_vote_cb)(struct dcp_drv *drv,
+ const struct dcp_resource_vote_pair *pairs,
+ u32 count);
+ /* Protects capability payload buffers during event delivery. */
+ spinlock_t caps_lock;
+ /* Serializes capability transactions. */
+ struct mutex caps_cache_lock;
+ struct dcp_hfi_caps_cache caps_cache;
+
+ void *power_control_priv;
+ void (*power_control_cb)(void *priv, u32 display_id,
+ u32 power_mask, bool enable, u32 packet_id);
};
int dcp_hfi_init(struct dcp_drv *drv, struct virtio_device *vdev,
@@ -167,3 +211,8 @@ int dcp_hfi_send_no_wait_atomic_flags(struct dcp_hfi *hfi, u32 cmd,
enum hfi_packet_payload_type payload_type,
void *payload, u32 payload_size, u32 tx_flags);
#endif /* __DCP_HFI_H__ */
+void dcp_hfi_set_power_control_cb(struct dcp_hfi *hfi, void *priv,
+ void (*cb)(void *priv, u32 display_id,
+ u32 power_mask, bool enable,
+ u32 packet_id));
+void dcp_hfi_clear_power_control_cb(struct dcp_hfi *hfi, void *priv);
diff --git a/drivers/gpu/drm/msm/dcp/dcp_hfi_core.c b/drivers/gpu/drm/msm/dcp/dcp_hfi_core.c
index 9f7ac798e13c..774a5afac0f0 100644
--- a/drivers/gpu/drm/msm/dcp/dcp_hfi_core.c
+++ b/drivers/gpu/drm/msm/dcp/dcp_hfi_core.c
@@ -9,6 +9,7 @@
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/kernel.h>
+#include <linux/unaligned.h>
#include <linux/slab.h>
#include <linux/scatterlist.h>
#include <linux/spinlock.h>
@@ -21,8 +22,13 @@
#include "hfi/hfi_packet.h"
#include "hfi/hfi_packer.h"
#include "hfi/hfi_unpacker.h"
+#include "hfi/hfi_commands_device.h"
+#include "hfi/hfi_commands_display.h"
+#include "hfi/hfi_commands_display_events.h"
#include "hfi/hfi_defs_common.h"
+#include "hfi/hfi_defs_display.h"
+static void dcp_hfi_resource_vote_work(struct work_struct *work);
static int dcp_hfi_prepost_resp_bufs(struct dcp_hfi *hfi);
u32 dcp_hfi_next_packet_id(struct dcp_hfi *hfi)
@@ -227,6 +233,218 @@ void dcp_hfi_cmd_callback(struct virtqueue *vq)
spin_unlock_irqrestore(&hfi->cmd_lock, flags);
}
+static void dcp_hfi_store_caps_payload(struct dcp_hfi *hfi,
+ struct completion *done,
+ u8 *dst, u32 *dst_len,
+ const struct hfi_packet_info *pkt_info,
+ const char *name)
+{
+ u32 n;
+
+ if (!pkt_info->payload_ptr || !pkt_info->payload_size) {
+ dev_dbg(hfi->dev, "%s: empty payload\n", name);
+ return;
+ }
+
+ if (pkt_info->payload_size > DCP_HFI_CAPS_BUF_SIZE) {
+ dev_warn(hfi->dev, "%s: payload too large: %u\n",
+ name, pkt_info->payload_size);
+ return;
+ }
+
+ n = pkt_info->payload_size;
+ memcpy(dst, pkt_info->payload_ptr, n);
+ *dst_len = n;
+ if (done)
+ complete(done);
+ dev_dbg(hfi->dev, "%s: stored %u bytes\n", name, n);
+}
+
+/* Route asynchronous events and capability packets before flag handling. */
+static bool dcp_hfi_dispatch_event(struct dcp_hfi *hfi,
+ const struct hfi_packet_info *pkt_info)
+{
+ switch (pkt_info->cmd) {
+ case HFI_COMMAND_DISPLAY_EVENT_VSYNC:
+ return true;
+
+ case HFI_COMMAND_DISPLAY_EVENT_FRAME_SCAN_START:
+ return true;
+
+ case HFI_COMMAND_DISPLAY_EVENT_FRAME_SCAN_COMPLETE:
+ return true;
+
+ case HFI_COMMAND_DISPLAY_EVENT_HPD_STATUS:
+ return true;
+
+ case HFI_COMMAND_DISPLAY_EVENT_PANEL_DEAD:
+ dev_err(hfi->dev,
+ "DCP: panel dead display %u\n",
+ pkt_info->id & 0xFFFF);
+ return true;
+
+ case HFI_COMMAND_DISPLAY_EVENT_HW_RECOVERY:
+ dev_err(hfi->dev,
+ "DCP: HW recovery display %u\n",
+ pkt_info->id & 0xFFFF);
+ return true;
+
+ case HFI_COMMAND_DISPLAY_POWER_CONTROL: {
+ u32 display_id = pkt_info->id & 0xFFFF;
+ u32 power_mask = 0;
+ bool enable = true;
+ unsigned long flags;
+ void *power_priv;
+ void (*power_cb)(void *priv, u32 display_id,
+ u32 power_mask, bool enable, u32 packet_id);
+
+ if (pkt_info->payload_ptr &&
+ pkt_info->payload_size >= 2 * sizeof(u32)) {
+ const u32 *p = pkt_info->payload_ptr;
+
+ power_mask = p[0];
+ enable = (p[1] == HFI_TRUE);
+ }
+
+ spin_lock_irqsave(&hfi->resp_drain_lock, flags);
+ power_cb = hfi->power_control_cb;
+ power_priv = hfi->power_control_priv;
+ spin_unlock_irqrestore(&hfi->resp_drain_lock, flags);
+
+ if (power_cb)
+ power_cb(power_priv, display_id, power_mask, enable,
+ pkt_info->packet_id);
+ return true;
+ }
+
+ case HFI_COMMAND_DEVICE_INIT_DEVICE_CAPS: {
+ unsigned long flags;
+
+ spin_lock_irqsave(&hfi->caps_lock, flags);
+ dcp_hfi_store_caps_payload(hfi, &hfi->caps_cache.device_done,
+ hfi->caps_cache.device_buf,
+ &hfi->caps_cache.device_len,
+ pkt_info, "DEVICE_INIT_DEVICE_CAPS");
+ spin_unlock_irqrestore(&hfi->caps_lock, flags);
+ return true;
+ }
+
+ case HFI_COMMAND_DEVICE_INIT_DISPLAY_CAPS: {
+ unsigned long flags;
+
+ spin_lock_irqsave(&hfi->caps_lock, flags);
+ dcp_hfi_store_caps_payload(hfi, &hfi->caps_cache.display_done,
+ hfi->caps_cache.display_buf,
+ &hfi->caps_cache.display_len,
+ pkt_info, "DEVICE_INIT_DISPLAY_CAPS");
+ spin_unlock_irqrestore(&hfi->caps_lock, flags);
+ return true;
+ }
+
+ case HFI_COMMAND_DEVICE_INIT_VIG_CAPS: {
+ unsigned long flags;
+
+ spin_lock_irqsave(&hfi->caps_lock, flags);
+ dcp_hfi_store_caps_payload(hfi, NULL, hfi->caps_cache.vig_buf,
+ &hfi->caps_cache.vig_len,
+ pkt_info, "VIG_CAPS");
+ hfi->caps_cache.pipe_caps_received |= BIT(0);
+ spin_unlock_irqrestore(&hfi->caps_lock, flags);
+ return true;
+ }
+
+ case HFI_COMMAND_DEVICE_INIT_DMA_CAPS: {
+ unsigned long flags;
+
+ spin_lock_irqsave(&hfi->caps_lock, flags);
+ dcp_hfi_store_caps_payload(hfi, NULL, hfi->caps_cache.dma_buf,
+ &hfi->caps_cache.dma_len,
+ pkt_info, "DMA_CAPS");
+ hfi->caps_cache.pipe_caps_received |= BIT(1);
+ spin_unlock_irqrestore(&hfi->caps_lock, flags);
+ return true;
+ }
+
+ case HFI_COMMAND_DEVICE_INIT_VIG_R1_CAPS: {
+ unsigned long flags;
+
+ spin_lock_irqsave(&hfi->caps_lock, flags);
+ dcp_hfi_store_caps_payload(hfi, NULL, hfi->caps_cache.vig_r1_buf,
+ &hfi->caps_cache.vig_r1_len,
+ pkt_info, "VIG_R1_CAPS");
+ hfi->caps_cache.pipe_caps_received |= BIT(2);
+ spin_unlock_irqrestore(&hfi->caps_lock, flags);
+ return true;
+ }
+
+ case HFI_COMMAND_DEVICE_INIT_DMA_R1_CAPS: {
+ unsigned long flags;
+
+ spin_lock_irqsave(&hfi->caps_lock, flags);
+ dcp_hfi_store_caps_payload(hfi, NULL, hfi->caps_cache.dma_r1_buf,
+ &hfi->caps_cache.dma_r1_len,
+ pkt_info, "DMA_R1_CAPS");
+ hfi->caps_cache.pipe_caps_received |= BIT(3);
+ spin_unlock_irqrestore(&hfi->caps_lock, flags);
+ return true;
+ }
+
+ case HFI_COMMAND_DEVICE_INIT_COMMON_LAYER_CAPS: {
+ unsigned long flags;
+
+ spin_lock_irqsave(&hfi->caps_lock, flags);
+ hfi->caps_cache.pipe_caps_received |= BIT(4);
+ spin_unlock_irqrestore(&hfi->caps_lock, flags);
+ dev_dbg(hfi->dev,
+ "COMMON_LAYER_CAPS: received (size=%u)\n",
+ pkt_info->payload_size);
+ return true;
+ }
+
+ case HFI_COMMAND_DEVICE_INIT_DISPLAY_WB_CAPS: {
+ unsigned long flags;
+
+ spin_lock_irqsave(&hfi->caps_lock, flags);
+ hfi->caps_cache.pipe_caps_received |= BIT(5);
+ spin_unlock_irqrestore(&hfi->caps_lock, flags);
+ dev_dbg(hfi->dev,
+ "DISPLAY_WB_CAPS: received (size=%u) — all pipe caps received\n",
+ pkt_info->payload_size);
+ return true;
+ }
+
+ case HFI_COMMAND_DEVICE_CALLBACK_RESOURCE_VOTE: {
+ unsigned long vote_flags;
+ u32 payload_size = pkt_info->payload_size;
+ bool valid = pkt_info->payload_ptr && payload_size &&
+ payload_size <= DCP_HFI_RESOURCE_VOTE_BUF_SIZE &&
+ payload_size % sizeof(struct dcp_resource_vote_wire) == 0;
+
+ if (!valid) {
+ dev_warn_ratelimited(hfi->dev,
+ "dropping malformed RESOURCE_VOTE payload size=%u max=%u\n",
+ payload_size, DCP_HFI_RESOURCE_VOTE_BUF_SIZE);
+ payload_size = 0;
+ }
+
+ spin_lock_irqsave(&hfi->resource_vote_lock, vote_flags);
+ if (payload_size)
+ memcpy(hfi->resource_vote_buf, pkt_info->payload_ptr,
+ payload_size);
+ hfi->resource_vote_len = payload_size;
+ hfi->resource_vote_generation++;
+ spin_unlock_irqrestore(&hfi->resource_vote_lock, vote_flags);
+
+ if (payload_size)
+ schedule_work(&hfi->resource_vote_work);
+ return true;
+ }
+
+ default:
+ return false;
+ }
+}
+
void dcp_hfi_resp_callback(struct virtqueue *vq)
{
struct dcp_drv *drv = vq->vdev->priv;
@@ -297,6 +515,8 @@ void dcp_hfi_resp_callback(struct virtqueue *vq)
"HFI error: cmd=0x%x pkt_id=%u\n",
pkt_info.cmd, pkt_info.packet_id);
dcp_hfi_complete_pending(hfi, &pkt_info, -EIO);
+ } else if (dcp_hfi_dispatch_event(hfi, &pkt_info)) {
+ /* Event dispatch runs before flag handling. */
} else if (pkt_info.flags & HFI_RX_FLAGS_SUCCESS) {
if (pkt_info.flags & HFI_RX_FLAGS_INFORMATION)
dev_dbg(hfi->dev,
@@ -639,6 +859,47 @@ int dcp_hfi_send_no_wait(struct dcp_hfi *hfi,
payload_type, payload, payload_size, HFI_TX_FLAGS_NONE);
}
+static void dcp_hfi_resource_vote_work(struct work_struct *work)
+{
+ struct dcp_hfi *hfi = container_of(work, struct dcp_hfi,
+ resource_vote_work);
+ u8 buf[DCP_HFI_RESOURCE_VOTE_BUF_SIZE];
+ struct dcp_resource_vote_pair pairs[DCP_HFI_RESOURCE_VOTE_BUF_SIZE /
+ sizeof(struct dcp_resource_vote_wire)];
+ const struct dcp_resource_vote_wire *wire;
+ u32 len, generation, count, i;
+ bool dirty;
+ unsigned long flags;
+
+ do {
+ spin_lock_irqsave(&hfi->resource_vote_lock, flags);
+ len = hfi->resource_vote_len;
+ generation = hfi->resource_vote_generation;
+ if (len)
+ memcpy(buf, hfi->resource_vote_buf, len);
+ spin_unlock_irqrestore(&hfi->resource_vote_lock, flags);
+
+ if (!len || !hfi->resource_vote_cb)
+ return;
+ if (len % sizeof(struct dcp_resource_vote_wire)) {
+ dev_warn_ratelimited(hfi->dev,
+ "dropping malformed RESOURCE_VOTE size=%u\n", len);
+ return;
+ }
+ count = len / sizeof(struct dcp_resource_vote_wire);
+ wire = (const struct dcp_resource_vote_wire *)buf;
+ for (i = 0; i < count; i++) {
+ pairs[i].key = le32_to_cpu(wire[i].key);
+ pairs[i].value = get_unaligned_le64(&wire[i].value);
+ }
+
+ hfi->resource_vote_cb(hfi->drv, pairs, count);
+ spin_lock_irqsave(&hfi->resource_vote_lock, flags);
+ dirty = generation != hfi->resource_vote_generation;
+ spin_unlock_irqrestore(&hfi->resource_vote_lock, flags);
+ } while (dirty);
+}
+
static void dcp_hfi_free_pool(struct dcp_hfi *hfi, struct dcp_hfi_buf *pool,
u32 count)
{
@@ -726,7 +987,10 @@ int dcp_hfi_init(struct dcp_drv *drv,
spin_lock_init(&hfi->cmd_lock);
spin_lock_init(&hfi->pending_lock);
+ spin_lock_init(&hfi->resource_vote_lock);
spin_lock_init(&hfi->resp_drain_lock);
+ spin_lock_init(&hfi->caps_lock);
+ mutex_init(&hfi->caps_cache_lock);
atomic_set(&hfi->packet_id, 0);
atomic_set(&hfi->shutdown_in_progress, 0);
@@ -735,8 +999,11 @@ int dcp_hfi_init(struct dcp_drv *drv,
xa_init(&hfi->pending_xa);
+ INIT_WORK(&hfi->resource_vote_work, dcp_hfi_resource_vote_work);
INIT_DELAYED_WORK(&hfi->resp_repost_work, dcp_hfi_resp_repost_work);
INIT_LIST_HEAD(&hfi->resp_repost_list);
+ hfi->resource_vote_len = 0;
+ hfi->resource_vote_generation = 0;
hfi->cmd_pool = devm_kcalloc(hfi->dev, DCP_HFI_CMD_POOL_SIZE,
sizeof(*hfi->cmd_pool), GFP_KERNEL);
@@ -784,6 +1051,7 @@ int dcp_hfi_init(struct dcp_drv *drv,
err:
atomic_set(&hfi->shutdown_in_progress, 1);
+ cancel_work_sync(&hfi->resource_vote_work);
cancel_delayed_work_sync(&hfi->resp_repost_work);
while (virtqueue_detach_unused_buf(hfi->cmd_vq))
;
@@ -796,6 +1064,31 @@ int dcp_hfi_init(struct dcp_drv *drv,
return ret;
}
+void dcp_hfi_set_power_control_cb(struct dcp_hfi *hfi, void *priv,
+ void (*cb)(void *priv, u32 display_id,
+ u32 power_mask, bool enable,
+ u32 packet_id))
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&hfi->resp_drain_lock, flags);
+ hfi->power_control_priv = priv;
+ hfi->power_control_cb = cb;
+ spin_unlock_irqrestore(&hfi->resp_drain_lock, flags);
+}
+
+void dcp_hfi_clear_power_control_cb(struct dcp_hfi *hfi, void *priv)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&hfi->resp_drain_lock, flags);
+ if (hfi->power_control_priv == priv) {
+ hfi->power_control_priv = NULL;
+ hfi->power_control_cb = NULL;
+ }
+ spin_unlock_irqrestore(&hfi->resp_drain_lock, flags);
+}
+
void dcp_hfi_stop(struct dcp_hfi *hfi)
{
unsigned long flags;
@@ -804,6 +1097,7 @@ void dcp_hfi_stop(struct dcp_hfi *hfi)
atomic_set(&hfi->shutdown_in_progress, 1);
spin_unlock_irqrestore(&hfi->cmd_lock, flags);
+ cancel_work_sync(&hfi->resource_vote_work);
cancel_delayed_work_sync(&hfi->resp_repost_work);
dcp_hfi_cancel_pending(hfi, -ENODEV);
}
@@ -829,6 +1123,10 @@ void dcp_hfi_quiesce_queues(struct dcp_hfi *hfi)
void dcp_hfi_deinit(struct dcp_hfi *hfi)
{
+ mutex_lock(&hfi->caps_cache_lock);
+ memset(&hfi->caps_cache, 0, sizeof(hfi->caps_cache));
+ mutex_unlock(&hfi->caps_cache_lock);
+
xa_destroy(&hfi->pending_xa);
dcp_hfi_free_pool(hfi, hfi->cmd_pool, DCP_HFI_CMD_POOL_SIZE);
--
2.43.0