@@ -9,3 +9,12 @@ config DRM_VIRTIO_GPU
QEMU based VMMs (like KVM or Xen).
If unsure say M.
+
+config DRM_VIRTIO_GPU_DUMMY
+ tristate "Virtio dummy GPU driver"
+ depends on DRM_VIRTIO_GPU
+ help
+ This add a new virtio GPU device which handles the virtio ring buffers
+ inline so it doesn't rely on VMM to provide the virtio GPU device.
+ Currently it only handle VIRTIO_GPU_CMD_GET_DISPLAY_INFO which is enough
+ for a dummy 2D VGA device.
@@ -8,4 +8,7 @@ virtio-gpu-y := virtgpu_drv.o virtgpu_kms.o virtgpu_gem.o \
virtgpu_fence.o virtgpu_object.o virtgpu_debugfs.o virtgpu_plane.o \
virtgpu_ioctl.o virtgpu_prime.o virtgpu_trace_points.o
+virtio-gpu-dummy-y := virtgpu_dummy.o
+
obj-$(CONFIG_DRM_VIRTIO_GPU) += virtio-gpu.o
+obj-$(CONFIG_DRM_VIRTIO_GPU_DUMMY) += virtio-gpu-dummy.o
new file mode 100644
@@ -0,0 +1,161 @@
+#include <linux/module.h>
+#include <linux/virtio.h>
+#include <linux/virtio_ids.h>
+#include <linux/virtio_config.h>
+#include <linux/virtio_ring.h>
+#include <linux/virtio_gpu.h>
+
+#include "virtgpu_drv.h"
+
+static int virtgpu_dummy_width = 1024;
+static int virtgpu_dummy_height = 768;
+
+MODULE_PARM_DESC(width, "Dummy VGA width");
+module_param_named(width, virtgpu_dummy_width, int, 0400);
+MODULE_PARM_DESC(height, "Dummy VGA height");
+module_param_named(height, virtgpu_dummy_height, int, 0400);
+
+static struct bus_type dummy_bus = {
+ .name = "",
+};
+
+static struct dummy_gpu {
+ struct device *root;
+ struct virtio_device vdev;
+ unsigned char status;
+} dummy;
+
+static u64 dummy_get_features(struct virtio_device *vdev)
+{
+ return 1ULL << VIRTIO_F_VERSION_1;
+}
+
+static int dummy_finalize_features(struct virtio_device *vdev)
+{
+ return 0;
+}
+
+static void dummy_get(struct virtio_device *vdev, unsigned int offset,
+ void *buf, unsigned len)
+{
+ static struct virtio_gpu_config config = {
+ .num_scanouts = 1,
+ };
+ BUG_ON(offset + len > sizeof(config));
+ memcpy(buf, (char *)&config + offset, len);
+}
+
+static u8 dummy_get_status(struct virtio_device *vdev)
+{
+ struct dummy_gpu* gpu = container_of(vdev, struct dummy_gpu, vdev);
+ return gpu->status;
+}
+
+static void dummy_set_status(struct virtio_device *vdev, u8 status)
+{
+ struct dummy_gpu* gpu = container_of(vdev, struct dummy_gpu, vdev);
+ BUG_ON(!status);
+ gpu->status = status;
+}
+
+void process_cmd(struct vring_desc *desc, int idx)
+{
+ // FIXME, use chain to get resp buffer addr
+ char *buf = __va(desc[idx].addr);
+ struct virtio_gpu_vbuffer *vbuf =
+ (struct virtio_gpu_vbuffer *)(buf - sizeof(*vbuf));
+ struct virtio_gpu_ctrl_hdr *cmd_p = (struct virtio_gpu_ctrl_hdr *)buf;
+ struct virtio_gpu_resp_display_info *resp;
+ BUG_ON(vbuf->buf != buf);
+ if (cmd_p->type != cpu_to_le32(VIRTIO_GPU_CMD_GET_DISPLAY_INFO))
+ return;
+ BUG_ON(vbuf->resp_size != sizeof(struct virtio_gpu_resp_display_info));
+ resp = (struct virtio_gpu_resp_display_info *)vbuf->resp_buf;
+ resp->pmodes[0].r.width = virtgpu_dummy_width;
+ resp->pmodes[0].r.height = virtgpu_dummy_height;
+ resp->pmodes[0].enabled = 1;
+}
+
+static bool dummy_notify(struct virtqueue *vq)
+{
+ struct vring *r = (struct vring *)(vq + 1);
+ int used, avail;
+ // FIXME, handle multiple avail and also fix for big endian.
+ used = r->used->idx & (r->num - 1);
+ avail = (r->avail->idx - 1) & (r->num - 1);
+ r->used->ring[used].id = r->avail->ring[avail];
+ r->used->idx++;
+ if (!strcmp(vq->name, "control"))
+ process_cmd(r->desc, r->avail->ring[avail]);
+ vq->callback(vq);
+ return true;
+}
+
+static int dummy_find_vqs(struct virtio_device *vdev, unsigned nvqs,
+ struct virtqueue *vqs[],
+ vq_callback_t * callbacks[],
+ const char *const names[],
+ const bool *ctx, struct irq_affinity *desc)
+{
+ int i, j;
+ for (i = 0; i < nvqs; ++i) {
+ vqs[i] = vring_create_virtqueue(i, 256, SMP_CACHE_BYTES, vdev,
+ true, false, false,
+ dummy_notify, callbacks[i],
+ names[i]);
+ if (!vqs[i])
+ goto err;
+ }
+ return 0;
+err:
+ for (j = 0; j < i; ++j) {
+ vring_del_virtqueue(vqs[j]);
+ vqs[j] = NULL;
+ }
+ return -ENOMEM;
+}
+
+static void dummy_reset(struct virtio_device *vdev)
+{
+}
+
+static const struct virtio_config_ops dummy_vq_ops = {
+ .get_features = dummy_get_features,
+ .finalize_features = dummy_finalize_features,
+ .get = dummy_get,
+ .get_status = dummy_get_status,
+ .set_status = dummy_set_status,
+ .reset = dummy_reset,
+ .find_vqs = dummy_find_vqs,
+};
+
+static int __init virtio_gpu_dummy_init(void)
+{
+ int ret;
+ struct device * root = root_device_register("dummy");
+ if (PTR_ERR_OR_ZERO(root))
+ return PTR_ERR(root);
+ root->bus = &dummy_bus;
+ dummy.vdev.dev.parent = root;
+ dummy.vdev.id.device = VIRTIO_ID_GPU;
+ dummy.vdev.config = &dummy_vq_ops;
+ ret = register_virtio_device(&dummy.vdev);
+ if (ret) {
+ pr_err("Failed to register virtio device %d", ret);
+ root_device_unregister(root);
+ return ret;
+ }
+ dummy.root = root;
+ return 0;
+}
+
+static void virtio_gpu_dummy_exit(void)
+{
+ if (!dummy.root)
+ return;
+ unregister_virtio_device(&dummy.vdev);
+ root_device_unregister(dummy.root);
+}
+
+module_init(virtio_gpu_dummy_init);
+module_exit(virtio_gpu_dummy_exit);
The idea here is: if we run the vm headless, we don't really need to communicate with VMM, and we even don't need any VMM support for virtio-gpu. Of course, only 2d works. But it's enough for some use case. And this looks simpler than vkms. Signed-off-by: Lepton Wu <ytht.net@gmail.com> --- drivers/gpu/drm/virtio/Kconfig | 9 ++ drivers/gpu/drm/virtio/Makefile | 3 + drivers/gpu/drm/virtio/virtgpu_dummy.c | 161 +++++++++++++++++++++++++ 3 files changed, 173 insertions(+) create mode 100644 drivers/gpu/drm/virtio/virtgpu_dummy.c