On Thu, Oct 15, 2026 at 01:08:23PM +0530, Jayant Shekhar wrote:
> DCP firmware owns the display hardware, so the MSM DRM driver needs a
> transport for HFI commands, responses, and asynchronous display events.
> 
> Add the VirtIO command and response queues used to communicate with DCP.
> Allocate command, response, and capability buffers through the DMA API
> using the firmware-visible IOVA window. Track synchronous commands and
> asynchronous responses, dispatch HFI callbacks, and synchronize queue
> teardown with in-flight transactions.
> 
> Signed-off-by: Jayant Shekhar <[email protected]>
> Assisted-by: LLM

You haven't read Documentation/process/. Sad.

> diff --git a/drivers/gpu/drm/msm/dcp/dcp_drv.h 
> b/drivers/gpu/drm/msm/dcp/dcp_drv.h
> new file mode 100644
> index 000000000000..bf6c34af43a0
> --- /dev/null
> +++ b/drivers/gpu/drm/msm/dcp/dcp_drv.h
> @@ -0,0 +1,30 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +/*
> + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
> + */
> +
> +#ifndef __DCP_DRV_H__
> +#define __DCP_DRV_H__
> +
> +#include <linux/device.h>
> +#include <linux/dma-mapping.h>
> +#include <linux/notifier.h>
> +#include <linux/platform_device.h>
> +#include <linux/virtio.h>

Are all these headers required for this header?

> +
> +#include "dcp_hfi.h"
> +
> +struct dcp_dpu_kms;
> +
> +struct dcp_drv {
> +     struct virtio_device    *vdev;
> +     struct platform_device  *dcp_subsystem_dev;
> +     struct device_dma_parameters dcp_subsystem_dma_parms;
> +     struct device_node      *dcp_subsystem_node;
> +     struct dcp_hfi          *hfi;
> +     struct dcp_dpu_kms      *kms;
> +     struct dcp_caps          caps;
> +     struct notifier_block    panic_notifier;
> +};
> +
> +#endif /* __DCP_DRV_H__ */



> +
> +#define DCP_MAX_DISPLAYS     4
> +
> +#define DCP_FEAT_DSC         BIT(0)
> +#define DCP_FEAT_HDR         BIT(1)
> +#define DCP_FEAT_SRC_SPLIT   BIT(3)
> +#define DCP_FEAT_CURSOR              BIT(4)

What kind of features are those?

> +
> +#define DCP_MAX_SSPP_COUNT   16
> +#define DCP_MAX_VIG_PIPES    DCP_MAX_SSPP_COUNT
> +#define DCP_MAX_DMA_PIPES    DCP_MAX_SSPP_COUNT
> +
> +#define DCP_MAX_BLOCKS                       8
> +
> +/* Pipe indices encode the rectangle ID in bits 31:24. */
> +#define DCP_REC_ID(x)                (((x) & 0xff000000) >> 24)
> +
> +/* Capabilities parsed from DEVICE_INIT responses. */
> +struct dcp_caps {
> +     u32 dcp_hw_rev;
> +     u32 hw_rev;
> +     u32 fw_rev;
> +
> +     u32 num_vig_pipes;
> +     u32 vig_pipe_ids[DCP_MAX_SSPP_COUNT];
> +     u32 virt_vig_count;
> +     u32 vig_r1_indices[DCP_MAX_SSPP_COUNT];
> +
> +     u32 num_dma_pipes;
> +     u32 dma_pipe_ids[DCP_MAX_SSPP_COUNT];
> +     u32 virt_dma_count;
> +     u32 dma_r1_indices[DCP_MAX_SSPP_COUNT];
> +
> +     u32 num_crtcs;

CRTCs? Or mixers?

> +
> +     u32 dsi_count;
> +     u32 dsi_indices[DCP_MAX_BLOCKS];
> +     u32 max_dsi_resolution;

If these are parsed, why is it still u32 rather than two resolutions?

> +
> +     u32 dp_count;
> +     u32 dp_indices[DCP_MAX_BLOCKS];

Unused, drop

> +
> +     u32 active_pipes_mask[DCP_MAX_DISPLAYS];

Unused. Drop all unused fields.

> +
> +     u32 max_width;
> +     u32 max_height;

max width/height of... what?

> +     u32 max_planes;
> +     u32 features;
> +     bool valid;

How can it be invalid?

> +
> +     bool dyn_clk_support;

??

> +};
> +
> +struct dcp_hfi_buf {
> +     void *data;
> +     dma_addr_t dma_addr;
> +     size_t size;
> +     struct scatterlist sg;
> +     struct dcp_hfi_completion *comp;
> +     struct list_head node;
> +};
> +
> +struct dcp_hfi_completion {
> +     struct completion done;
> +     int status;
> +     void *resp_data;
> +     size_t resp_len;
> +     u32 packet_id;
> +};
> +
> +#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 {
> +     struct device *dev;
> +     struct dcp_drv *drv;
> +
> +     struct device *dma_dev;
> +
> +     struct virtqueue *cmd_vq;
> +     struct virtqueue *resp_vq;
> +
> +     struct dcp_hfi_buf *cmd_pool;
> +     struct list_head cmd_free_list;
> +     /* Protects cmd_free_list and command buffer ownership. */
> +     spinlock_t cmd_lock;
> +
> +     struct dcp_hfi_buf *resp_pool;
> +     /* Protects response queue draining and power callback publication. */
> +     spinlock_t resp_drain_lock;
> +     struct list_head resp_repost_list;
> +     struct delayed_work resp_repost_work;
> +     struct xarray pending_xa;
> +     /* Protects pending_xa. */
> +     spinlock_t pending_lock;
> +     atomic_t packet_id;
> +     atomic_t shutdown_in_progress;

This all demands at least some description or kernel docs.

> +
> +     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 caps_cache pointer and its lifetime. */
> +     spinlock_t caps_lock;
> +     /* Serializes capability transactions and cache detach/free. */
> +     struct mutex caps_cache_lock;
> +     struct dcp_hfi_caps_cache *caps_cache;

What's the point of keeping it as a pointer? Just embed it here and drop
the locks. Read the caps while the devices is not visible so that there
are no concurrent users.

> +
> +     void (*hotplug_cb)(struct dcp_drv *drv, u32 intf_idx, bool connected);

Why do we need these callbacks? Can you call the functions directly?

> +     void (*error_cb)(struct dcp_drv *drv, u32 display_id, u32 error_code);
> +
> +     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,
> +              struct virtqueue *cmd_vq, struct virtqueue *resp_vq,
> +              struct device *dma_dev);
> +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);
> +void dcp_hfi_stop(struct dcp_hfi *hfi);
> +void dcp_hfi_quiesce_queues(struct dcp_hfi *hfi);
> +void dcp_hfi_deinit(struct dcp_hfi *hfi);
> +
> +void dcp_hfi_resp_callback(struct virtqueue *vq);
> +void dcp_hfi_cmd_callback(struct virtqueue *vq);
> +
> +int dcp_hfi_send_sync(struct dcp_hfi *hfi, u32 cmd,
> +                   enum hfi_cmd_buff_type cmd_type, u32 object_id,
> +                   enum hfi_packet_payload_type payload_type, void *payload,
> +                   u32 payload_size, u32 tx_flags, void *resp_data,
> +                   size_t resp_len, unsigned int timeout_ms);
> +
> +int dcp_hfi_send_no_wait(struct dcp_hfi *hfi, u32 cmd,
> +                      enum hfi_cmd_buff_type cmd_type, u32 object_id,
> +                      enum hfi_packet_payload_type payload_type, void 
> *payload,
> +                      u32 payload_size);
> +
> +int dcp_hfi_send_no_wait_flags(struct dcp_hfi *hfi, u32 cmd,
> +                            enum hfi_cmd_buff_type cmd_type, u32 object_id,
> +                            enum hfi_packet_payload_type payload_type,
> +                            void *payload, u32 payload_size, u32 tx_flags);
> +int dcp_hfi_send_no_wait_atomic_flags(struct dcp_hfi *hfi, u32 cmd,
> +                                   enum hfi_cmd_buff_type cmd_type, u32 
> object_id,
> +                                    enum hfi_packet_payload_type 
> payload_type,
> +                                    void *payload, u32 payload_size, u32 
> tx_flags);

You haven't really followed the internal review feedback. You have been
asked to review the indentation. You didnt.

> +#endif /* __DCP_HFI_H__ */
> diff --git a/drivers/gpu/drm/msm/dcp/dcp_hfi_core.c 
> b/drivers/gpu/drm/msm/dcp/dcp_hfi_core.c
> new file mode 100644
> index 000000000000..bf27cbf9b8a4
> --- /dev/null
> +++ b/drivers/gpu/drm/msm/dcp/dcp_hfi_core.c
> @@ -0,0 +1,1198 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
> + *
> + * DCP HFI core transport.
> + */
> +
> +#include <linux/completion.h>
> +#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>
> +#include <linux/virtio.h>
> +#include <linux/workqueue.h>
> +#include <uapi/linux/virtio_mmio.h>
> +
> +#include "dcp_hfi.h"
> +#include "dcp_hfi_priv.h"
> +#include "dcp_drv.h"
> +#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)
> +{
> +     u32 id;
> +
> +     id = (u32)atomic_inc_return(&hfi->packet_id);
> +     if (id == 0)
> +             id = (u32)atomic_inc_return(&hfi->packet_id);

What if something looks at your counter inbetween? Two atomic operations
aren't atomic.

> +     return id;
> +}
> +
> +#define DCP_HFI_BUF_ALLOC_RETRIES    3       /* Bound command-path stalls. */
> +#define DCP_HFI_BUF_ALLOC_SLEEP_US_MIN       30000
> +#define DCP_HFI_BUF_ALLOC_SLEEP_US_MAX       40000
> +
> +static struct dcp_hfi_buf *_dcp_hfi_buf_alloc(struct dcp_hfi *hfi,
> +                                           struct list_head *free_list,
> +                                            spinlock_t *lock,
> +                                            const char *pool_name)
> +{
> +     struct dcp_hfi_buf *buf = NULL;
> +     unsigned long flags;
> +     int retry;
> +
> +     if (atomic_read(&hfi->shutdown_in_progress)) {

This is called drm_dev_get().

> +             dev_dbg(hfi->dev,
> +                     "HFI shutdown in progress — refusing %s buffer 
> allocation\n",
> +                     pool_name);
> +             return NULL;
> +     }
> +
> +     for (retry = 0; retry <= DCP_HFI_BUF_ALLOC_RETRIES; retry++) {
> +             if (atomic_read(&hfi->shutdown_in_progress))
> +                     return NULL;
> +
> +             spin_lock_irqsave(lock, flags);
> +             if (!list_empty(free_list)) {
> +                     buf = list_first_entry(free_list,
> +                                            struct dcp_hfi_buf, node);
> +                     list_del(&buf->node);
> +             }
> +             spin_unlock_irqrestore(lock, flags);
> +
> +             if (buf)
> +                     return buf;
> +
> +             if (retry < DCP_HFI_BUF_ALLOC_RETRIES)
> +                     usleep_range(DCP_HFI_BUF_ALLOC_SLEEP_US_MIN,
> +                                  DCP_HFI_BUF_ALLOC_SLEEP_US_MAX);
> +     }
> +
> +     dev_warn_ratelimited(hfi->dev,
> +                          "HFI %s TX pool exhausted after %d retries — 
> command dropped\n",
> +                          pool_name, DCP_HFI_BUF_ALLOC_RETRIES);
> +     return NULL;
> +}
> +
> +static void _dcp_hfi_buf_free(struct dcp_hfi *hfi, struct dcp_hfi_buf *buf,
> +                           struct list_head *free_list, spinlock_t *lock)
> +{
> +     unsigned long flags;
> +
> +     buf->comp = NULL;
> +     spin_lock_irqsave(lock, flags);
> +     list_add_tail(&buf->node, free_list);
> +     spin_unlock_irqrestore(lock, flags);

Just call _dcp_hfi_buf_free_locked() under the lock.

> +}
> +
> +static void _dcp_hfi_buf_free_locked(struct dcp_hfi_buf *buf,
> +                                  struct list_head *free_list)
> +{
> +     buf->comp = NULL;
> +     list_add_tail(&buf->node, free_list);
> +}
> +

[..]

> +
> +     case HFI_COMMAND_DISPLAY_EVENT_HPD_STATUS: {
> +             u32 intf_idx = pkt_info->id & 0xFFFF;
> +             bool connected = false;
> +
> +             if (pkt_info->payload_ptr &&
> +                 pkt_info->payload_size >=
> +                 sizeof(struct hfi_display_hpd_status)) {
> +                     struct hfi_display_hpd_status *hpd =
> +                             pkt_info->payload_ptr;
> +                     connected = (hpd->dp_state ==
> +                                  HFI_DP_STATE_CONNECTED);
> +             }
> +             if (hfi->hotplug_cb)
> +                     hfi->hotplug_cb(hfi->drv, intf_idx, connected);
> +             return true;
> +     }
> +
> +     case HFI_COMMAND_DISPLAY_EVENT_PANEL_DEAD:
> +             dev_err(hfi->dev,
> +                     "DCP: panel dead display %u\n",
> +                     pkt_info->id & 0xFFFF);
> +             if (hfi->error_cb)
> +                     hfi->error_cb(hfi->drv, pkt_info->id & 0xFFFF, 0);
> +             return true;
> +
> +     case HFI_COMMAND_DISPLAY_EVENT_HW_RECOVERY:
> +             dev_err(hfi->dev,
> +                     "DCP: HW recovery display %u\n",
> +                     pkt_info->id & 0xFFFF);
> +             if (hfi->error_cb)
> +                     hfi->error_cb(hfi->drv, pkt_info->id & 0xFFFF, 1);

Yep... Drop the callbacks. I think it was also a part of the review.

> +             return true;
> +

Can you split the patches? It really becomes troublesome to review.

-- 
With best wishes
Dmitry

Reply via email to