Files
rt-thread/components/drivers/virtio/virtio-gpu.c
T
GuEe-GUI 8517f2d086 [dm][virtio] support virtio dm
1. version for v1.2
2. support packed queue
1. support MMIO/PCI over bus
2. new virtio queue api with dma/cpu sync
3. update for virtio device drivers:
  - net
  - block
  - console
  - input
  - gpu
4. new virtio device drivers:
  - 9p
  - crypto
  - rng
  - scmi
  - scsi

Signed-off-by: GuEe-GUI <2991707448@qq.com>
2026-06-15 10:00:57 +08:00

1022 lines
26 KiB
C

/*
* Copyright (c) 2006-2023, RT-Thread Development Team
*
* SPDX-License-Identifier: Apache-2.0
*
* Change Logs:
* Date Author Notes
* 2021-11-11 GuEe-GUI the first version
* 2023-02-25 GuEe-GUI using virtio dm
*/
#include <rthw.h>
#include <rtthread.h>
#include <rtdevice.h>
#define DBG_TAG "virtio.dev.gpu"
#define DBG_LVL DBG_INFO
#include <rtdbg.h>
#include <mmu.h>
#include <cpuport.h>
#include "virtio_internal.h"
#include "virtio_config/virtio-gpu.h"
#define CTRL_QUEUE 0
#define CURSOR_QUEUE 1
struct virtio_gpu
{
struct rt_graphic_device parent;
struct rt_virtio_device *vdev;
struct rt_virtqueue *vqs[2];
rt_bool_t vsync;
rt_uint32_t width;
rt_uint32_t height;
rt_uint32_t pmode_id;
rt_uint32_t num_scanouts;
rt_uint32_t resource_id;
#define PRIMARY_PLANE_RESOURCE_ID 1
#define CURSOR_PLANE_RESOURCE_ID 2
struct rt_work event_work;
struct rt_completion done;
#define CURSOR_WIDTH 64
#define CURSOR_HEIGHT 64
rt_uint8_t *cursor;
};
#define raw_to_virtio_gpu(raw) rt_container_of(raw, struct virtio_gpu, parent)
static void virtio_gpu_ctrl_send_command(struct virtio_gpu *vgpu,
const void *cmd, rt_size_t cmd_len, void *res, rt_size_t res_len)
{
struct rt_virtqueue *vq;
vq = vgpu->vqs[CTRL_QUEUE];
rt_virtqueue_wait_prepare(vq, 2);
rt_virtqueue_add_outbuf(vq, (void *)cmd, cmd_len);
rt_virtqueue_add_inbuf(vq, res, res_len);
rt_virtqueue_kick(vq);
while (!rt_virtqueue_read_buf(vq, RT_NULL))
{
rt_hw_cpu_relax();
}
}
static void virtio_gpu_cursor_send_command(struct virtio_gpu *vgpu,
const void *cmd, rt_size_t cmd_len)
{
struct rt_virtqueue *vq;
vq = vgpu->vqs[CURSOR_QUEUE];
rt_virtqueue_wait_prepare(vq, 1);
rt_virtqueue_add_outbuf(vq, (void *)cmd, cmd_len);
rt_virtqueue_kick(vq);
while (!rt_virtqueue_read_buf(vq, RT_NULL))
{
rt_hw_cpu_relax();
}
}
static rt_uint32_t virtio_gpu_next_resource_id(struct virtio_gpu *vgpu)
{
rt_uint32_t last_resource_id = vgpu->resource_id++;
while (last_resource_id != vgpu->resource_id)
{
if (vgpu->resource_id &&
vgpu->resource_id == PRIMARY_PLANE_RESOURCE_ID &&
vgpu->resource_id == CURSOR_PLANE_RESOURCE_ID)
{
return vgpu->resource_id;
}
++vgpu->resource_id;
}
return 0;
}
static rt_err_t virtio_gpu_create_2d_resource(struct virtio_gpu *vgpu,
enum virtio_gpu_formats format, rt_uint32_t resource_id,
rt_uint32_t width, rt_uint32_t height)
{
struct virtio_gpu_ctrl_hdr res;
struct virtio_gpu_resource_create_2d req;
req.hdr.type = rt_cpu_to_le32(VIRTIO_GPU_CMD_RESOURCE_CREATE_2D);
req.resource_id = rt_cpu_to_le32(resource_id);
req.format = rt_cpu_to_le32(format);
req.width = rt_cpu_to_le32(width);
req.height = rt_cpu_to_le32(height);
virtio_gpu_ctrl_send_command(vgpu, &req, sizeof(req), &res, sizeof(res));
return res.type == rt_cpu_to_le32(VIRTIO_GPU_RESP_OK_NODATA) ? RT_EOK : -RT_ERROR;
}
static rt_err_t virtio_gpu_unref_resource(struct virtio_gpu *vgpu,
rt_uint32_t resource_id)
{
struct virtio_gpu_ctrl_hdr res;
struct virtio_gpu_resource_unref req;
rt_memset(&req, 0, sizeof(req));
req.hdr.type = rt_cpu_to_le32(VIRTIO_GPU_CMD_RESOURCE_UNREF);
req.resource_id = rt_cpu_to_le32(resource_id);
virtio_gpu_ctrl_send_command(vgpu, &req, sizeof(req), &res, sizeof(res));
return res.type == rt_cpu_to_le32(VIRTIO_GPU_RESP_OK_NODATA) ? RT_EOK : -RT_ERROR;
}
static rt_err_t virtio_gpu_attach_backing_resource(struct virtio_gpu *vgpu,
rt_uint32_t resource_id, void *buffer, rt_size_t size)
{
struct virtio_gpu_ctrl_hdr res;
struct
{
struct virtio_gpu_resource_attach_backing req;
struct virtio_gpu_mem_entry mem;
} req;
rt_memset(&req, 0, sizeof(req));
req.req.hdr.type = rt_cpu_to_le32(VIRTIO_GPU_CMD_RESOURCE_ATTACH_BACKING);
req.req.resource_id = rt_cpu_to_le32(resource_id);
req.req.nr_entries = rt_cpu_to_le32(1);
req.mem.addr = rt_cpu_to_le64((rt_ubase_t)rt_kmem_v2p(buffer));
req.mem.length = rt_cpu_to_le32(size);
virtio_gpu_ctrl_send_command(vgpu, &req, sizeof(req), &res, sizeof(res));
return res.type == rt_cpu_to_le32(VIRTIO_GPU_RESP_OK_NODATA) ? RT_EOK : -RT_ERROR;
}
static rt_err_t virtio_gpu_detach_backing_resource(struct virtio_gpu *vgpu,
rt_uint32_t resource_id)
{
struct virtio_gpu_ctrl_hdr res;
struct virtio_gpu_resource_detach_backing req;
rt_memset(&req, 0, sizeof(req));
req.hdr.type = rt_cpu_to_le32(VIRTIO_GPU_CMD_RESOURCE_DETACH_BACKING);
req.resource_id = rt_cpu_to_le32(resource_id);
virtio_gpu_ctrl_send_command(vgpu, &req, sizeof(req), &res, sizeof(res));
return res.type == rt_cpu_to_le32(VIRTIO_GPU_RESP_OK_NODATA) ? RT_EOK : -RT_ERROR;
}
static rt_err_t virtio_gpu_set_scanout(struct virtio_gpu *vgpu,
rt_uint32_t scanout_id, rt_uint32_t resource_id,
rt_uint32_t width, rt_uint32_t height)
{
struct virtio_gpu_ctrl_hdr res;
struct virtio_gpu_set_scanout req;
rt_memset(&req, 0, sizeof(req));
req.hdr.type = rt_cpu_to_le32(VIRTIO_GPU_CMD_SET_SCANOUT);
req.r.width = rt_cpu_to_le32(width);
req.r.height = rt_cpu_to_le32(height);
req.scanout_id = rt_cpu_to_le32(scanout_id);
req.resource_id = rt_cpu_to_le32(resource_id);
virtio_gpu_ctrl_send_command(vgpu, &req, sizeof(req), &res, sizeof(res));
return res.type == rt_cpu_to_le32(VIRTIO_GPU_RESP_OK_NODATA) ? RT_EOK : -RT_ERROR;
}
static rt_err_t virtio_gpu_flush_resource(struct virtio_gpu *vgpu,
rt_uint32_t resource_id,
rt_uint32_t x, rt_uint32_t y, rt_uint32_t width, rt_uint32_t height)
{
struct virtio_gpu_ctrl_hdr res;
struct virtio_gpu_resource_flush req;
rt_memset(&req, 0, sizeof(req));
req.hdr.type = rt_cpu_to_le32(VIRTIO_GPU_CMD_RESOURCE_FLUSH);
req.r.x = rt_cpu_to_le32(x);
req.r.y = rt_cpu_to_le32(y);
req.r.width = rt_cpu_to_le32(width);
req.r.height = rt_cpu_to_le32(height);
req.resource_id = rt_cpu_to_le32(resource_id);
virtio_gpu_ctrl_send_command(vgpu, &req, sizeof(req), &res, sizeof(res));
return res.type == rt_cpu_to_le32(VIRTIO_GPU_RESP_OK_NODATA) ? RT_EOK : -RT_ERROR;
}
static rt_err_t virtio_gpu_transfer_to_host_2d(struct virtio_gpu *vgpu,
rt_uint32_t resource_id,
rt_uint32_t x, rt_uint32_t y, rt_uint32_t width, rt_uint32_t height,
rt_uint64_t offset)
{
struct virtio_gpu_ctrl_hdr res;
struct virtio_gpu_transfer_to_host_2d req;
rt_memset(&req, 0, sizeof(req));
req.hdr.type = rt_cpu_to_le32(VIRTIO_GPU_CMD_TRANSFER_TO_HOST_2D);
req.r.x = rt_cpu_to_le32(x);
req.r.y = rt_cpu_to_le32(y);
req.r.width = rt_cpu_to_le32(width);
req.r.height = rt_cpu_to_le32(height);
req.offset = rt_cpu_to_le64(offset);
req.resource_id = rt_cpu_to_le32(resource_id);
virtio_gpu_ctrl_send_command(vgpu, &req, sizeof(req), &res, sizeof(res));
return res.type == rt_cpu_to_le32(VIRTIO_GPU_RESP_OK_NODATA) ? RT_EOK : -RT_ERROR;
}
static rt_err_t virtio_gpu_update_cursor(struct virtio_gpu *vgpu,
rt_uint32_t scanout_id, rt_uint32_t resource_id,
rt_uint32_t hot_x, rt_uint32_t hot_y)
{
struct virtio_gpu_update_cursor req;
rt_memset(&req, 0, sizeof(req));
req.hdr.type = rt_cpu_to_le32(VIRTIO_GPU_CMD_UPDATE_CURSOR);
req.pos.scanout_id = rt_cpu_to_le32(scanout_id);
req.resource_id = rt_cpu_to_le32(resource_id);
req.hot_x = rt_cpu_to_le32(hot_x);
req.hot_y = rt_cpu_to_le32(hot_y);
virtio_gpu_cursor_send_command(vgpu, &req, sizeof(req));
return RT_EOK;
}
static rt_err_t virtio_gpu_cursor_move(struct virtio_gpu *vgpu,
rt_uint32_t scanout_id, rt_uint32_t resource_id,
rt_uint32_t x, rt_uint32_t y)
{
struct virtio_gpu_update_cursor req;
rt_memset(&req, 0, sizeof(req));
req.hdr.type = rt_cpu_to_le32(VIRTIO_GPU_CMD_MOVE_CURSOR);
req.pos.scanout_id = rt_cpu_to_le32(scanout_id);
req.pos.x = rt_cpu_to_le32(x);
req.pos.y = rt_cpu_to_le32(y);
req.resource_id = rt_cpu_to_le32(resource_id);
virtio_gpu_cursor_send_command(vgpu, &req, sizeof(req));
return RT_EOK;
}
static rt_err_t virtio_gpu_get_edid(struct virtio_gpu *vgpu)
{
rt_err_t err;
struct virtio_gpu_cmd_get_edid req;
struct virtio_gpu_resp_edid *res = rt_malloc(sizeof(*res));
if (!res)
{
return -RT_ENOMEM;
}
rt_memset(&req, 0, sizeof(req));
req.hdr.type = rt_cpu_to_le32(VIRTIO_GPU_CMD_GET_EDID);
req.scanout = rt_cpu_to_le32(vgpu->pmode_id);
virtio_gpu_ctrl_send_command(vgpu, &req, sizeof(req), res, sizeof(*res));
if (res->hdr.type == rt_cpu_to_le32(VIRTIO_GPU_RESP_OK_EDID))
{
rt_memcpy(&vgpu->parent.edid, res->edid,
rt_min_t(rt_size_t, sizeof(vgpu->parent.edid), res->size));
err = RT_EOK;
}
else
{
err = -RT_EIO;
}
rt_free(res);
return err;
}
static rt_err_t virtio_gpu_get_display_info(struct virtio_gpu *vgpu)
{
struct virtio_gpu_ctrl_hdr req;
struct virtio_gpu_resp_display_info info;
rt_memset(&req, 0, sizeof(req));
req.type = rt_cpu_to_le32(VIRTIO_GPU_CMD_GET_DISPLAY_INFO);
virtio_gpu_ctrl_send_command(vgpu, &req, sizeof(req), &info, sizeof(info));
if (info.hdr.type != rt_cpu_to_le32(VIRTIO_GPU_RESP_OK_DISPLAY_INFO))
{
return -RT_EIO;
}
for (int i = 0; i < vgpu->num_scanouts; ++i)
{
if (info.pmodes[i].enabled)
{
struct virtio_gpu_display_one *pmode = &info.pmodes[i];
vgpu->pmode_id = i;
vgpu->width = pmode->r.width;
vgpu->height = pmode->r.height;
break;
}
}
return RT_EOK;
}
static rt_err_t virtio_gpu_resize(struct virtio_gpu *vgpu)
{
rt_err_t err = RT_EOK;
if (rt_virtio_has_feature(vgpu->vdev, VIRTIO_GPU_F_EDID))
{
err = virtio_gpu_get_edid(vgpu);
}
if (!err)
{
err = virtio_gpu_get_display_info(vgpu);
}
return err;
}
static enum virtio_gpu_formats virtio_gpu_raw_modes[] =
{
VIRTIO_GPU_FORMAT_B8G8R8A8_UNORM,
VIRTIO_GPU_FORMAT_R8G8B8A8_UNORM,
};
static rt_uint32_t virtio_gpu_modes[] =
{
RTGRAPHIC_PIXEL_FORMAT_ARGB888,
RTGRAPHIC_PIXEL_FORMAT_ABGR888,
};
typedef char __assert_virtio_gpu_modes[-(RT_ARRAY_SIZE(virtio_gpu_raw_modes) % RT_ARRAY_SIZE(virtio_gpu_modes))];
static enum virtio_gpu_formats virtio_gpu_mode(rt_uint32_t mode)
{
for (int i = 0; i < RT_ARRAY_SIZE(virtio_gpu_modes); ++i)
{
if (virtio_gpu_modes[i] == mode)
{
return virtio_gpu_raw_modes[i];
}
}
LOG_E("BUG of the graphic framework");
RT_ASSERT(0);
return -1;
}
static rt_err_t virtio_gpu_cursor_plane_fb_update(struct rt_graphic_plane *plane,
struct rt_device_rect_info *rect)
{
rt_err_t err;
rt_uint32_t resource_id;
struct virtio_gpu *vgpu = raw_to_virtio_gpu(plane->graphic);
if (!rect->width && !rect->height)
{
return RT_EOK;
}
if (rect->width > CURSOR_WIDTH || rect->height > CURSOR_HEIGHT)
{
return -RT_EINVAL;
}
resource_id = CURSOR_PLANE_RESOURCE_ID;
err = virtio_gpu_transfer_to_host_2d(vgpu, resource_id,
rect->x, rect->y, rect->width, rect->height, 0);
if (!err)
{
err = virtio_gpu_flush_resource(vgpu, resource_id,
rect->x, rect->y, rect->width, rect->height);
}
virtio_gpu_update_cursor(vgpu, vgpu->pmode_id, resource_id, rect->x, rect->y);
virtio_gpu_cursor_move(vgpu, vgpu->pmode_id, resource_id, rect->x, rect->y);
return err;
}
static rt_err_t virtio_gpu_cursor_plane_fb_remap(struct rt_graphic_plane *plane,
rt_uint32_t mode, struct rt_device_rect_info *rect)
{
rt_err_t err;
rt_uint32_t framebuffer_len, bits_per_pixel, line_length, resource_id;
struct virtio_gpu *vgpu = raw_to_virtio_gpu(plane->graphic);
if (rect->width > CURSOR_WIDTH || rect->height > CURSOR_HEIGHT)
{
return -RT_EINVAL;
}
resource_id = CURSOR_PLANE_RESOURCE_ID;
err = virtio_gpu_create_2d_resource(vgpu, virtio_gpu_mode(mode),
resource_id, rect->width, rect->height);
if (err)
{
return err;
}
bits_per_pixel = rt_graphic_mode_bpp(mode);
line_length = rect->width * (bits_per_pixel / 8);
framebuffer_len = line_length * rect->height;
err = virtio_gpu_attach_backing_resource(vgpu, resource_id, vgpu->cursor, framebuffer_len);
if (err)
{
goto _unref_resource;
}
plane->line_length = line_length;
plane->bits_per_pixel = bits_per_pixel;
plane->framebuffer = vgpu->cursor;
plane->screen_len = framebuffer_len;
plane->framebuffer_len = framebuffer_len;
return RT_EOK;
_unref_resource:
virtio_gpu_unref_resource(vgpu, resource_id);
return err;
}
static rt_err_t virtio_gpu_cursor_plane_fb_cleanup(struct rt_graphic_plane *plane)
{
rt_uint32_t resource_id;
struct virtio_gpu *vgpu = raw_to_virtio_gpu(plane->graphic);
if (!plane->framebuffer)
{
return RT_EOK;
}
resource_id = CURSOR_PLANE_RESOURCE_ID;
virtio_gpu_detach_backing_resource(vgpu, resource_id);
virtio_gpu_unref_resource(vgpu, resource_id);
plane->framebuffer = RT_NULL;
return RT_EOK;
}
static const struct rt_graphic_plane_ops virtio_gpu_cursor_plane_ops =
{
.update = virtio_gpu_cursor_plane_fb_update,
.fb_remap = virtio_gpu_cursor_plane_fb_remap,
.fb_cleanup = virtio_gpu_cursor_plane_fb_cleanup,
};
static rt_err_t virtio_gpu_primary_plane_fb_update(struct rt_graphic_plane *plane,
struct rt_device_rect_info *rect)
{
rt_err_t err;
rt_uint32_t resource_id;
struct virtio_gpu *vgpu = raw_to_virtio_gpu(plane->graphic);
vgpu->vsync = RT_FALSE;
resource_id = PRIMARY_PLANE_RESOURCE_ID;
err = virtio_gpu_transfer_to_host_2d(vgpu, resource_id,
rect->x, rect->y, rect->width, rect->height, 0);
if (!err)
{
err = virtio_gpu_flush_resource(vgpu, resource_id,
rect->x, rect->y, rect->width, rect->height);
}
vgpu->vsync = RT_TRUE;
return err;
}
static rt_err_t virtio_gpu_primary_plane_fb_remap(struct rt_graphic_plane *plane,
rt_uint32_t mode, struct rt_device_rect_info *rect)
{
rt_err_t err;
void *framebuffer;
rt_uint32_t framebuffer_len, bits_per_pixel, line_length, resource_id;
struct virtio_gpu *vgpu = raw_to_virtio_gpu(plane->graphic);
resource_id = PRIMARY_PLANE_RESOURCE_ID;
err = virtio_gpu_create_2d_resource(vgpu, virtio_gpu_mode(mode),
resource_id, rect->width, rect->height);
if (err)
{
return err;
}
bits_per_pixel = rt_graphic_mode_bpp(mode);
line_length = rect->width * (bits_per_pixel / 8);
framebuffer_len = line_length * rect->height;
framebuffer = rt_malloc_align(framebuffer_len, ARCH_PAGE_SIZE);
if (!framebuffer)
{
err = -RT_ENOMEM;
goto _unref_resource;
}
err = virtio_gpu_attach_backing_resource(vgpu, resource_id, framebuffer, framebuffer_len);
if (err)
{
goto _free_framebuffer;
}
err = virtio_gpu_set_scanout(vgpu, vgpu->pmode_id, resource_id,
rect->width, rect->height);
if (err)
{
goto _detach_backing;
}
plane->line_length = line_length;
plane->bits_per_pixel = bits_per_pixel;
plane->framebuffer = framebuffer;
plane->screen_len = framebuffer_len;
plane->framebuffer_len = framebuffer_len;
return RT_EOK;
_detach_backing:
virtio_gpu_detach_backing_resource(vgpu, resource_id);
_free_framebuffer:
rt_free_align(framebuffer);
_unref_resource:
virtio_gpu_unref_resource(vgpu, resource_id);
return err;
}
static rt_err_t virtio_gpu_primary_plane_fb_cleanup(struct rt_graphic_plane *plane)
{
rt_uint32_t resource_id;
struct virtio_gpu *vgpu = raw_to_virtio_gpu(plane->graphic);
if (!plane->framebuffer)
{
return RT_EOK;
}
resource_id = PRIMARY_PLANE_RESOURCE_ID;
virtio_gpu_detach_backing_resource(vgpu, resource_id);
virtio_gpu_unref_resource(vgpu, resource_id);
rt_free_align(plane->framebuffer);
plane->framebuffer = RT_NULL;
return RT_EOK;
}
static const struct rt_graphic_plane_ops virtio_gpu_primary_plane_ops =
{
.update = virtio_gpu_primary_plane_fb_update,
.fb_remap = virtio_gpu_primary_plane_fb_remap,
.fb_cleanup = virtio_gpu_primary_plane_fb_cleanup,
};
static rt_err_t virtio_gpu_dpms_switch(struct rt_graphic_device *gdev, rt_uint32_t dpms)
{
rt_uint32_t update_ms = 0;
struct virtio_gpu *vgpu = raw_to_virtio_gpu(gdev);
if (dpms == RT_GRAPHIC_DPMS_ON)
{
update_ms = RT_GRAPHIC_UPDATE_MS;
}
rt_graphic_device_update_auto(&vgpu->parent, update_ms);
return RT_EOK;
}
static rt_err_t virtio_gpu_wait_vsync(struct rt_graphic_device *gdev)
{
struct virtio_gpu *vgpu = raw_to_virtio_gpu(gdev);
while (!vgpu->vsync)
{
rt_hw_cpu_relax();
}
return RT_EOK;
}
static rt_err_t virtio_gpu_virgl_control(struct rt_graphic_device *gdev, int cmd, void *args)
{
rt_err_t err;
struct virtio_gpu *vgpu = raw_to_virtio_gpu(gdev);
switch (cmd)
{
case VIRTIO_GPU_CMD_GET_CAPSET_INFO:
{
struct virtio_gpu_get_capset_info req;
struct virtio_gpu_resp_capset_info *res = args;
rt_memcpy(&req, args, sizeof(req));
virtio_gpu_ctrl_send_command(vgpu, &req, sizeof(req), res, sizeof(*res));
err = res->hdr.type == rt_cpu_to_le32(VIRTIO_GPU_RESP_OK_CAPSET_INFO) ? RT_EOK : -RT_ERROR;
break;
}
case VIRTIO_GPU_CMD_GET_CAPSET:
{
struct virtio_gpu_get_capset req;
struct virtio_gpu_resp_capset *res = args;
rt_memcpy(&req, args, sizeof(req));
virtio_gpu_ctrl_send_command(vgpu, &req, sizeof(req), res, sizeof(*res));
err = res->hdr.type == rt_cpu_to_le32(VIRTIO_GPU_RESP_OK_CAPSET) ? RT_EOK : -RT_ERROR;
break;
}
case VIRTIO_GPU_CMD_CTX_CREATE:
{
struct virtio_gpu_ctrl_hdr res;
struct virtio_gpu_ctx_create *req = args;
virtio_gpu_ctrl_send_command(vgpu, req, sizeof(*req), &res, sizeof(res));
rt_memcpy(args, &res, sizeof(res));
err = res.type == rt_cpu_to_le32(VIRTIO_GPU_RESP_OK_NODATA) ? RT_EOK : -RT_ERROR;
break;
}
case VIRTIO_GPU_CMD_CTX_DESTROY:
{
struct virtio_gpu_ctrl_hdr res;
struct virtio_gpu_ctx_destroy *req = args;
virtio_gpu_ctrl_send_command(vgpu, req, sizeof(*req), &res, sizeof(res));
rt_memcpy(args, &res, sizeof(res));
err = res.type == rt_cpu_to_le32(VIRTIO_GPU_RESP_OK_NODATA) ? RT_EOK : -RT_ERROR;
break;
}
case VIRTIO_GPU_CMD_CTX_ATTACH_RESOURCE:
{
struct virtio_gpu_ctrl_hdr res;
struct virtio_gpu_ctx_resource *req = args;
virtio_gpu_ctrl_send_command(vgpu, req, sizeof(*req), &res, sizeof(res));
rt_memcpy(args, &res, sizeof(res));
err = res.type == rt_cpu_to_le32(VIRTIO_GPU_RESP_OK_NODATA) ? RT_EOK : -RT_ERROR;
break;
}
case VIRTIO_GPU_CMD_CTX_DETACH_RESOURCE:
{
struct virtio_gpu_ctrl_hdr res;
struct virtio_gpu_ctx_resource *req = args;
virtio_gpu_ctrl_send_command(vgpu, req, sizeof(*req), &res, sizeof(res));
rt_memcpy(args, &res, sizeof(res));
err = res.type == rt_cpu_to_le32(VIRTIO_GPU_RESP_OK_NODATA) ? RT_EOK : -RT_ERROR;
break;
}
case VIRTIO_GPU_CMD_RESOURCE_CREATE_3D:
{
struct virtio_gpu_ctrl_hdr res;
struct virtio_gpu_resource_create_3d *req = args;
/*
* Create a new resource id instead of user's. Because the
* `virtio_gpu_resource_create_3d` is base on the `virtio_gpu_ctrl_hdr`,
* user should pick the resource_id by res after request.
*/
req->resource_id = virtio_gpu_next_resource_id(vgpu);
virtio_gpu_ctrl_send_command(vgpu, req, sizeof(*req), &res, sizeof(res));
rt_memcpy(args, &res, sizeof(res));
err = res.type == rt_cpu_to_le32(VIRTIO_GPU_RESP_OK_NODATA) ? RT_EOK : -RT_ERROR;
break;
}
case VIRTIO_GPU_CMD_TRANSFER_TO_HOST_3D:
{
struct virtio_gpu_ctrl_hdr res;
struct virtio_gpu_transfer_host_3d *req = args;
virtio_gpu_ctrl_send_command(vgpu, req, sizeof(*req), &res, sizeof(res));
rt_memcpy(args, &res, sizeof(res));
err = res.type == rt_cpu_to_le32(VIRTIO_GPU_RESP_OK_NODATA) ? RT_EOK : -RT_ERROR;
break;
}
case VIRTIO_GPU_CMD_TRANSFER_FROM_HOST_3D:
{
struct virtio_gpu_ctrl_hdr res;
struct virtio_gpu_transfer_host_3d *req = args;
virtio_gpu_ctrl_send_command(vgpu, req, sizeof(*req), &res, sizeof(res));
rt_memcpy(args, &res, sizeof(res));
err = res.type == rt_cpu_to_le32(VIRTIO_GPU_RESP_OK_NODATA) ? RT_EOK : -RT_ERROR;
break;
}
case VIRTIO_GPU_CMD_SUBMIT_3D:
{
struct virtio_gpu_ctrl_hdr res;
struct virtio_gpu_cmd_submit *req = args;
virtio_gpu_ctrl_send_command(vgpu, req, req->size, &res, sizeof(res));
rt_memcpy(args, &res, sizeof(res));
err = res.type == rt_cpu_to_le32(VIRTIO_GPU_RESP_OK_NODATA) ? RT_EOK : -RT_ERROR;
break;
}
default:
err = -RT_ENOSYS;
break;
}
return err;
}
static const struct rt_graphic_device_ops virtio_gpu_virgl_ops =
{
.dpms_switch = virtio_gpu_dpms_switch,
.wait_vsync = virtio_gpu_wait_vsync,
.control = virtio_gpu_virgl_control,
};
static const struct rt_graphic_device_ops virtio_gpu_ops =
{
.dpms_switch = virtio_gpu_dpms_switch,
.wait_vsync = virtio_gpu_wait_vsync,
};
static void virtio_gpu_event_work(struct rt_work *work, void *work_data)
{
rt_uint32_t events_read, events_clear = 0;
struct virtio_gpu *vgpu = work_data;
struct rt_virtio_device *vdev = vgpu->vdev;
rt_virtio_read_config(vdev, struct virtio_gpu_config, events_read, &events_read);
if (events_read & VIRTIO_GPU_EVENT_DISPLAY)
{
rt_graphic_device_enter(&vgpu->parent);
virtio_gpu_resize(vgpu);
rt_graphic_device_leave(&vgpu->parent);
rt_graphic_device_hotplug_event(&vgpu->parent);
events_clear |= VIRTIO_GPU_EVENT_DISPLAY;
}
rt_virtio_write_config(vdev, struct virtio_gpu_config, events_clear, events_clear);
}
static void virtio_gpu_config_changed(struct rt_virtio_device *vdev)
{
struct virtio_gpu *vgpu = vdev->priv;
rt_work_submit(&vgpu->event_work, 0);
}
static rt_err_t virtio_gpu_vq_init(struct virtio_gpu *vgpu)
{
const char *names[] =
{
"ctrl",
"cursor"
};
rt_virtqueue_callback cbs[] =
{
RT_NULL, /* Just poll */
RT_NULL, /* Just poll */
};
return rt_virtio_virtqueue_install(vgpu->vdev,
RT_ARRAY_SIZE(names), vgpu->vqs, names, cbs);
}
static void virtio_gpu_vq_finit(struct virtio_gpu *vgpu)
{
if (vgpu->vqs[0])
{
rt_virtio_virtqueue_release(vgpu->vdev);
}
}
static void virtio_gpu_unregister(struct virtio_gpu *vgpu)
{
if (rt_virtio_has_feature(vgpu->vdev, VIRTIO_GPU_F_EDID))
{
rt_graphic_device_unregister(&vgpu->parent);
}
else
{
rt_graphic_device_simple_unregister(&vgpu->parent);
}
}
static rt_err_t virtio_gpu_probe(struct rt_virtio_device *vdev)
{
rt_err_t err;
struct rt_graphic_plane *primary_plane, *cursor_plane;
struct virtio_gpu *vgpu = rt_calloc(1, sizeof(*vgpu));
if (!vgpu)
{
return -RT_ENOMEM;
}
vdev->priv = vgpu;
vgpu->vdev = vdev;
vgpu->cursor = rt_malloc_align(
CURSOR_WIDTH * CURSOR_HEIGHT * sizeof(rt_uint32_t), ARCH_PAGE_SIZE);
if (!vgpu->cursor)
{
err = -RT_ENOMEM;
goto _fail;
}
if ((err = virtio_gpu_vq_init(vgpu)))
{
goto _fail;
}
vgpu->vsync = RT_TRUE;
rt_virtio_read_config(vdev, struct virtio_gpu_config, num_scanouts, &vgpu->num_scanouts);
rt_work_init(&vgpu->event_work, virtio_gpu_event_work, vgpu);
virtio_gpu_resize(vgpu);
if (rt_virtio_has_feature(vdev, VIRTIO_GPU_F_VIRGL))
{
vgpu->parent.ops = &virtio_gpu_virgl_ops;
}
else
{
vgpu->parent.ops = &virtio_gpu_ops;
}
if (rt_virtio_has_feature(vdev, VIRTIO_GPU_F_EDID))
{
primary_plane = rt_graphic_device_alloc_plane(&vgpu->parent, 0,
&virtio_gpu_primary_plane_ops,
virtio_gpu_modes, RT_ARRAY_SIZE(virtio_gpu_modes),
RT_GRAPHIC_PLANE_TYPE_PRIMARY);
if (!primary_plane)
{
err = -RT_EIO;
goto _fail;
}
if ((err = rt_graphic_device_add_plane(&vgpu->parent, primary_plane)))
{
goto _free_primary_plane;
}
err = rt_graphic_device_register(&vgpu->parent);
}
else
{
err = rt_graphic_device_simple_register(&vgpu->parent,
vgpu->width, vgpu->height, 0, &virtio_gpu_primary_plane_ops,
virtio_gpu_modes, RT_ARRAY_SIZE(virtio_gpu_modes));
}
if (err)
{
goto _fail;
}
cursor_plane = rt_graphic_device_alloc_plane(&vgpu->parent, 0,
&virtio_gpu_cursor_plane_ops,
virtio_gpu_modes, RT_ARRAY_SIZE(virtio_gpu_modes),
RT_GRAPHIC_PLANE_TYPE_CURSOR);
if (!cursor_plane)
{
err = -RT_EIO;
goto _fail;
}
if ((err = rt_graphic_device_add_plane(&vgpu->parent, cursor_plane)))
{
goto _free_cursor_plane;
}
cursor_plane->width = CURSOR_WIDTH;
cursor_plane->height = CURSOR_HEIGHT;
return RT_EOK;
_free_cursor_plane:
virtio_gpu_unregister(vgpu);
rt_graphic_device_free_plane(cursor_plane);
_free_primary_plane:
rt_graphic_device_free_plane(primary_plane);
_fail:
virtio_gpu_vq_finit(vgpu);
if (vgpu->cursor)
{
rt_free_align(vgpu->cursor);
}
rt_free(vgpu);
return err;
}
static rt_err_t virtio_gpu_remove(struct rt_virtio_device *vdev)
{
struct virtio_gpu *vgpu = vdev->parent.user_data;
virtio_gpu_unregister(vgpu);
virtio_gpu_vq_finit(vgpu);
rt_free_align(vgpu->cursor);
rt_free(vgpu);
return RT_EOK;
}
static const struct rt_virtio_device_id virtio_gpu_ids[] =
{
{ VIRTIO_DEVICE_ID_GPU, VIRTIO_DEVICE_ANY_ID },
{ /* sentinel */ }
};
static struct rt_virtio_driver virtio_gpu_driver =
{
.ids = virtio_gpu_ids,
.features =
RT_BIT(VIRTIO_GPU_F_VIRGL)
| RT_BIT(VIRTIO_GPU_F_EDID)
| RT_BIT(VIRTIO_F_ANY_LAYOUT),
.probe = virtio_gpu_probe,
.remove = virtio_gpu_remove,
.config_changed = virtio_gpu_config_changed,
};
RT_VIRTIO_DRIVER_EXPORT(virtio_gpu_driver);