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().

-- 
With best wishes
Dmitry

Reply via email to