On Mon, Oct 26, 2026 at 11:51:29AM +0530, Jayant Shekhar wrote:
Add the Qualcomm-specific VirtIO transport required by DCP firmware.
The transport sends an IPCC power notification before accessing the
VirtIO MMIO registers, then registers a VirtIO device directly.
Implement MMIO configuration access, feature negotiation, virtqueue
setup, interrupt handling, reset, DMA setup, and suspend/resume
sequencing in the transport. Keep power-management ordering in the
transport so VirtIO queues are quiesced before DCP power changes.
Assisted-by: LLM
Signed-off-by: Jayant Shekhar <[email protected]>
---
drivers/gpu/drm/msm/Makefile | 1 +
drivers/gpu/drm/msm/dcp/dcp_virtio.h | 23 +
drivers/gpu/drm/msm/dcp/dcp_virtio_transport.c | 643 +++++++++++++++++++++++++
This is a separate VirtIO host driver. Please move it to drivers/virtio
like the rest of the VirtIO hosts. It has nothing to do with the drm
subsystem now.
3 files changed, 667 insertions(+)
diff --git a/drivers/gpu/drm/msm/Makefile b/drivers/gpu/drm/msm/Makefile
index 9f0a9bfeb2a7..e1a995a8c980 100644
--- a/drivers/gpu/drm/msm/Makefile
+++ b/drivers/gpu/drm/msm/Makefile
@@ -85,6 +85,7 @@ msm-display-$(CONFIG_DRM_MSM_DCP) += \
dcp/dcp_hfi_plane.o \
dcp/dcp_ipcc.o \
dcp/dcp_encoder.o \
+ dcp/dcp_virtio_transport.o \
dcp/hfi/hfi_packer.o \
dcp/hfi/hfi_unpacker.o \
dcp/hfi/hfi_utils.o
diff --git a/drivers/gpu/drm/msm/dcp/dcp_virtio.h
b/drivers/gpu/drm/msm/dcp/dcp_virtio.h
new file mode 100644
index 000000000000..a9c3b9aff61d
--- /dev/null
+++ b/drivers/gpu/drm/msm/dcp/dcp_virtio.h
@@ -0,0 +1,23 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ */
+
+#ifndef __DCP_VIRTIO_H__
+#define __DCP_VIRTIO_H__
+
+#include <linux/device.h>
+#include <linux/virtio.h>
+
+struct dcp_drv;
+struct dcp_ipcc;
+
+struct dcp_ipcc *dcp_virtio_get_ipcc(struct virtio_device *vdev);
What is the use for it? The DCP driver should not communicate to the
IPCC, it's a part of the underlying VirtIO mechanism. If we export DCP
to the VM there will be no IPCC anymore.
+
+int dcp_virtio_transport_register(void);
+void dcp_virtio_transport_unregister(void);
+
+int dcp_virtio_register(void);
+void dcp_virtio_unregister(void);
Unused, remove these two.
+
+#endif /* __DCP_VIRTIO_H__ */
diff --git a/drivers/gpu/drm/msm/dcp/dcp_virtio_transport.c
b/drivers/gpu/drm/msm/dcp/dcp_virtio_transport.c
new file mode 100644
index 000000000000..02c0e2a9fafe
--- /dev/null
+++ b/drivers/gpu/drm/msm/dcp/dcp_virtio_transport.c
@@ -0,0 +1,643 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ *
+ * Qualcomm DCP VirtIO transport.
+ */
+
+#include <linux/acpi.h>
+#include <linux/delay.h>
+#include <linux/dma-mapping.h>
+#include <linux/highmem.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/pm.h>
+#include <linux/slab.h>
+#include <linux/virtio.h>
+#include <linux/virtio_config.h>
+#include <uapi/linux/virtio_mmio.h>
+#include <linux/virtio_ring.h>
+
+#include "dcp_ipcc.h"
+#include "dcp_virtio.h"
+
+#define VIRTIO_MMIO_VRING_ALIGN PAGE_SIZE
+
+#define to_dcp_virtio_transport(_plat_dev) \
+ container_of(_plat_dev, struct dcp_virtio_transport, vdev)
+
+struct dcp_virtio_transport {
+ struct virtio_device vdev;
+ struct platform_device *pdev;
+
+ void __iomem *base;
+ unsigned long version;
+
+ bool wake_irq_enabled;
+ struct dcp_ipcc ipcc;
+};
+
+static u64 dcp_virtio_get_features(struct virtio_device *vdev)
+{
+ struct dcp_virtio_transport *transport = to_dcp_virtio_transport(vdev);
+ u64 features;
+
+ writel(1, transport->base + VIRTIO_MMIO_DEVICE_FEATURES_SEL);
+ features = readl(transport->base + VIRTIO_MMIO_DEVICE_FEATURES);
+ features <<= 32;
+
+ writel(0, transport->base + VIRTIO_MMIO_DEVICE_FEATURES_SEL);
+ features |= readl(transport->base + VIRTIO_MMIO_DEVICE_FEATURES);
Can we turn virtio-mmio into the library code instead of copy-pasting
everything here?
+
+ return features;
+}
+
[...]
+
+ magic = readl(transport->base + VIRTIO_MMIO_MAGIC_VALUE);
+ if (magic != ('v' | 'i' << 8 | 'r' << 16 | 't' << 24)) {
Is it really a correct magic here? should it be a different magic as you
are not just a virtio-mmio?
+ dev_warn(&pdev->dev, "Wrong magic value 0x%08lx!\n", magic);
+ ret = -ENODEV;
+ goto err_node;
+ }
+
+ transport->version = readl(transport->base + VIRTIO_MMIO_VERSION);
+ if (transport->version < 1 || transport->version > 3) {
Isn't transport version fixed for the DCP purposes? Why do we need to
determine it again at the runtime?
+ dev_err(&pdev->dev, "Version %ld not supported!\n",
+ transport->version);
+ ret = -ENXIO;
+ goto err_node;
+ }
+
+ transport->vdev.id.device =
+ readl(transport->base + VIRTIO_MMIO_DEVICE_ID);
+ if (!transport->vdev.id.device) {
+ ret = -ENODEV;
+ goto err_node;
+ }
+ transport->vdev.id.vendor =
+ readl(transport->base + VIRTIO_MMIO_VENDOR_ID);
+
+ if (transport->version == 1) {
+ writel(PAGE_SIZE,
+ transport->base + VIRTIO_MMIO_GUEST_PAGE_SIZE);
+ ret = dma_set_mask(&pdev->dev, DMA_BIT_MASK(64));
+ if (!ret)
+ ret = dma_set_coherent_mask(&pdev->dev,
+ DMA_BIT_MASK(32 +
PAGE_SHIFT));
+ } else {
+ ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64));
+ }
+ if (ret)
+ ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32));
+ if (ret)
+ dev_warn(&pdev->dev,
+ "Failed to enable 64-bit or 32-bit DMA. Trying to continue,
but this might not work.\n");
+
+ ret = register_virtio_device(&transport->vdev);
+ if (ret) {
+ put_device(&transport->vdev.dev);
+ dcp_ipcc_deinit(&transport->ipcc);
+ platform_set_drvdata(pdev, NULL);
+ return ret;
+ }
+
+ return 0;
+
+err_node:
+err_ipcc:
+ dcp_ipcc_deinit(&transport->ipcc);
+err_free:
+ platform_set_drvdata(pdev, NULL);
+ return ret;
+}
+
+static void dcp_virtio_transport_remove(struct platform_device *pdev)
+{
+ struct dcp_virtio_transport *transport = platform_get_drvdata(pdev);
+
+ unregister_virtio_device(&transport->vdev);
+ platform_set_drvdata(pdev, NULL);
+ dcp_ipcc_deinit(&transport->ipcc);
+}
+
+static int dcp_virtio_transport_suspend(struct device *dev)
+{
+ struct dcp_virtio_transport *transport = dev_get_drvdata(dev);
+ int ret;
+
+ ret = virtio_device_freeze(&transport->vdev);
+ if (ret)
+ return ret;
+
+ ret = dcp_ipcc_enable_irq_wake(&transport->ipcc);
+ if (ret)
+ goto err_restore;
+
+ ret = dcp_ipcc_send_power_notify(&transport->ipcc);
+ if (ret) {
+ dcp_ipcc_disable_irq_wake(&transport->ipcc);
+ goto err_restore;
+ }
+
+ return 0;
+
+err_restore:
+ virtio_device_restore(&transport->vdev);
+ return ret;
+}
+
+static int dcp_virtio_transport_resume(struct device *dev)
+{
+ struct dcp_virtio_transport *transport = dev_get_drvdata(dev);
+ int ret;
+
+ ret = dcp_ipcc_send_power_notify(&transport->ipcc);
+ if (ret)
+ return ret;
+
+ dcp_ipcc_disable_irq_wake(&transport->ipcc);
+ if (transport->version == 1)
+ writel(PAGE_SIZE,
+ transport->base + VIRTIO_MMIO_GUEST_PAGE_SIZE);
+
+ return virtio_device_restore(&transport->vdev);
+}
+
+static const struct dev_pm_ops dcp_virtio_transport_pm_ops = {
+ .suspend = dcp_virtio_transport_suspend,
+ .resume = dcp_virtio_transport_resume,
+};
+
+static const struct of_device_id dcp_virtio_transport_of_match[] = {
+ { .compatible = "qcom,dcp-virtio" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, dcp_virtio_transport_of_match);
+
+static struct platform_driver dcp_virtio_transport_driver = {
+ .probe = dcp_virtio_transport_probe,
+ .remove = dcp_virtio_transport_remove,
+ .driver = {
+ .name = "qcom-dcp-virtio",
+ .of_match_table = dcp_virtio_transport_of_match,
+ .pm = &dcp_virtio_transport_pm_ops,
+ },
+};
+
+int dcp_virtio_transport_register(void)
+{
+ return platform_driver_register(&dcp_virtio_transport_driver);
+}
+
+void dcp_virtio_transport_unregister(void)
+{
+ platform_driver_unregister(&dcp_virtio_transport_driver);
+}
Turn it into a separate driver, use module_platform_driver().