acrn-hypervisor/devicemodel/hw/pci/virtio/virtio_gpu.c
Zhao Yakui 770ecb399b ACRN:DM:VGPU: Return the parsed scanout_num to virtio_gpu in Guest_VM
Return the parsed scanout_num to guest_vm so that the guest_vm can configure multi-display.

Tracked-On: #7988
Signed-off-by: Zhao Yakui <yakui.zhao@intel.com>
Reviewed-by: Peng Sun <peng.p.sun@linux.intel.com>
2022-08-15 14:44:47 +08:00

2012 lines
54 KiB
C

/*
* Copyright (C) OASIS Open 2018. All rights reserved.
* Copyright (C) 2022 Intel Corporation.
*
* SPDX-License-Identifier: BSD-3-Clause
*
* virtio-gpu device
*
*/
#include <fcntl.h>
#include <sys/ioctl.h>
#include <errno.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <sys/mman.h>
#include <unistd.h>
#include <stdbool.h>
#include <vmmapi.h>
#include <libdrm/drm_fourcc.h>
#include <linux/udmabuf.h>
#include <sys/stat.h>
#include <stdio.h>
#include "dm.h"
#include "pci_core.h"
#include "virtio.h"
#include "vdisplay.h"
#include "console.h"
#include "vga.h"
#include "atomic.h"
/*
* Queue definitions.
*/
#define VIRTIO_GPU_CONTROLQ 0
#define VIRTIO_GPU_CURSORQ 1
#define VIRTIO_GPU_QNUM 2
/*
* Virtqueue size.
*/
#define VIRTIO_GPU_RINGSZ 64
#define VIRTIO_GPU_MAXSEGS 256
/*
* Feature bits
*/
#define VIRTIO_GPU_F_EDID 1
#define VIRTIO_GPU_F_RESOURCE_UUID 2
#define VIRTIO_GPU_F_RESOURCE_BLOB 3
#define VIRTIO_GPU_F_CONTEXT_INIT 4
/*
* Host capabilities
*/
#define VIRTIO_GPU_S_HOSTCAPS (1UL << VIRTIO_F_VERSION_1) | \
(1UL << VIRTIO_GPU_F_EDID)
/*
* Device events
*/
#define VIRTIO_GPU_EVENT_DISPLAY (1 << 0)
/*
* Generic definitions
*/
#define VIRTIO_GPU_MAX_SCANOUTS 16
#define VIRTIO_GPU_FLAG_FENCE (1 << 0)
#define VIRTIO_GPU_FLAG_INFO_RING_IDX (1 << 1)
#define VIRTIO_GPU_FLAG_FENCE (1 << 0)
#define VIRTIO_GPU_VGA_FB_SIZE 16 * MB
#define VIRTIO_GPU_VGA_DMEMSZ 128
#define VIRTIO_GPU_EDID_SIZE 384
#define VIRTIO_GPU_VGA_IOPORT_OFFSET 0x400
#define VIRTIO_GPU_VGA_IOPORT_SIZE (0x3e0 - 0x3c0)
#define VIRTIO_GPU_VGA_VBE_OFFSET 0x500
#define VIRTIO_GPU_VGA_VBE_SIZE (0xb * 2)
#define VIRTIO_GPU_CAP_COMMON_OFFSET 0x1000
#define VIRTIO_GPU_CAP_COMMON_SIZE 0x800
#define VIRTIO_GPU_CAP_ISR_OFFSET 0x1800
#define VIRTIO_GPU_CAP_ISR_SIZE 0x800
/*
* Config space "registers"
*/
struct virtio_gpu_config {
uint32_t events_read;
uint32_t events_clear;
uint32_t num_scanouts;
uint32_t num_capsets;
};
/*
* Common
*/
enum virtio_gpu_ctrl_type {
/* 2d commands */
VIRTIO_GPU_CMD_GET_DISPLAY_INFO = 0x0100,
VIRTIO_GPU_CMD_RESOURCE_CREATE_2D,
VIRTIO_GPU_CMD_RESOURCE_UNREF,
VIRTIO_GPU_CMD_SET_SCANOUT,
VIRTIO_GPU_CMD_RESOURCE_FLUSH,
VIRTIO_GPU_CMD_TRANSFER_TO_HOST_2D,
VIRTIO_GPU_CMD_RESOURCE_ATTACH_BACKING,
VIRTIO_GPU_CMD_RESOURCE_DETACH_BACKING,
VIRTIO_GPU_CMD_GET_CAPSET_INFO,
VIRTIO_GPU_CMD_GET_CAPSET,
VIRTIO_GPU_CMD_GET_EDID,
VIRTIO_GPU_CMD_RESOURCE_ASSIGN_UUID,
VIRTIO_GPU_CMD_RESOURCE_CREATE_BLOB,
VIRTIO_GPU_CMD_SET_SCANOUT_BLOB,
/* cursor commands */
VIRTIO_GPU_CMD_UPDATE_CURSOR = 0x0300,
VIRTIO_GPU_CMD_MOVE_CURSOR,
/* success responses */
VIRTIO_GPU_RESP_OK_NODATA = 0x1100,
VIRTIO_GPU_RESP_OK_DISPLAY_INFO,
VIRTIO_GPU_RESP_OK_CAPSET_INFO,
VIRTIO_GPU_RESP_OK_CAPSET,
VIRTIO_GPU_RESP_OK_EDID,
VIRTIO_GPU_RESP_OK_RESOURCE_UUID,
VIRTIO_GPU_RESP_OK_MAP_INFO,
/* error responses */
VIRTIO_GPU_RESP_ERR_UNSPEC = 0x1200,
VIRTIO_GPU_RESP_ERR_OUT_OF_MEMORY,
VIRTIO_GPU_RESP_ERR_INVALID_SCANOUT_ID,
VIRTIO_GPU_RESP_ERR_INVALID_RESOURCE_ID,
VIRTIO_GPU_RESP_ERR_INVALID_CONTEXT_ID,
VIRTIO_GPU_RESP_ERR_INVALID_PARAMETER,
};
struct virtio_gpu_ctrl_hdr {
uint32_t type;
uint32_t flags;
uint64_t fence_id;
uint32_t ctx_id;
uint8_t ring_idx;
uint8_t padding[3];
};
/*
* Command: VIRTIO_GPU_CMD_GET_EDID
*/
struct virtio_gpu_get_edid {
struct virtio_gpu_ctrl_hdr hdr;
uint32_t scanout;
uint32_t padding;
};
struct virtio_gpu_resp_edid {
struct virtio_gpu_ctrl_hdr hdr;
uint32_t size;
uint32_t padding;
uint8_t edid[1024];
};
/*
* Command: VIRTIO_GPU_CMD_GET_DISPLAY_INFO
*/
struct virtio_gpu_rect {
uint32_t x;
uint32_t y;
uint32_t width;
uint32_t height;
};
struct virtio_gpu_resp_display_info {
struct virtio_gpu_ctrl_hdr hdr;
struct virtio_gpu_display_one {
struct virtio_gpu_rect r;
uint32_t enabled;
uint32_t flags;
} pmodes[VIRTIO_GPU_MAX_SCANOUTS];
};
/*
* Command: VIRTIO_GPU_CMD_RESOURCE_CREATE_2D
*/
enum virtio_gpu_formats {
VIRTIO_GPU_FORMAT_B8G8R8A8_UNORM = 1,
VIRTIO_GPU_FORMAT_B8G8R8X8_UNORM = 2,
VIRTIO_GPU_FORMAT_A8R8G8B8_UNORM = 3,
VIRTIO_GPU_FORMAT_X8R8G8B8_UNORM = 4,
VIRTIO_GPU_FORMAT_R8G8B8A8_UNORM = 67,
VIRTIO_GPU_FORMAT_X8B8G8R8_UNORM = 68,
VIRTIO_GPU_FORMAT_A8B8G8R8_UNORM = 121,
VIRTIO_GPU_FORMAT_R8G8B8X8_UNORM = 134,
};
struct virtio_gpu_resource_create_2d {
struct virtio_gpu_ctrl_hdr hdr;
uint32_t resource_id;
uint32_t format;
uint32_t width;
uint32_t height;
};
struct dma_buf_info {
int32_t ref_count;
int dmabuf_fd;
};
struct virtio_gpu_resource_2d {
uint32_t resource_id;
uint32_t width;
uint32_t height;
uint32_t format;
pixman_image_t *image;
struct iovec *iov;
uint32_t iovcnt;
bool blob;
struct dma_buf_info *dma_info;
LIST_ENTRY(virtio_gpu_resource_2d) link;
};
/*
* Command: VIRTIO_GPU_CMD_RESOURCE_UNREF
*/
struct virtio_gpu_resource_unref {
struct virtio_gpu_ctrl_hdr hdr;
uint32_t resource_id;
uint32_t padding;
};
/*
* Command: VIRTIO_GPU_CMD_RESOURCE_ATTACH_BACKING
*/
struct virtio_gpu_mem_entry {
uint64_t addr;
uint32_t length;
uint32_t padding;
};
struct virtio_gpu_resource_attach_backing {
struct virtio_gpu_ctrl_hdr hdr;
uint32_t resource_id;
uint32_t nr_entries;
};
/*
* Command: VIRTIO_GPU_CMD_RESOURCE_DETACH_BACKING
*/
struct virtio_gpu_resource_detach_backing {
struct virtio_gpu_ctrl_hdr hdr;
uint32_t resource_id;
uint32_t padding;
};
/*
* Command: VIRTIO_GPU_CMD_SET_SCANOUT
*/
struct virtio_gpu_set_scanout {
struct virtio_gpu_ctrl_hdr hdr;
struct virtio_gpu_rect r;
uint32_t scanout_id;
uint32_t resource_id;
};
/*
* Command: VIRTIO_GPU_CMD_TRANSFER_TO_HOST_2D
*/
struct virtio_gpu_transfer_to_host_2d {
struct virtio_gpu_ctrl_hdr hdr;
struct virtio_gpu_rect r;
uint64_t offset;
uint32_t resource_id;
uint32_t padding;
};
/*
* Command: VIRTIO_GPU_CMD_RESOURCE_FLUSH
*/
struct virtio_gpu_resource_flush {
struct virtio_gpu_ctrl_hdr hdr;
struct virtio_gpu_rect r;
uint32_t resource_id;
uint32_t padding;
};
/*
* Command: VIRTIO_GPU_CMD_UPDATE_CURSOR
* Command: VIRTIO_GPU_CMD_MOVE_CURSOR
*/
struct virtio_gpu_cursor_pos {
uint32_t scanout_id;
uint32_t x;
uint32_t y;
uint32_t padding;
};
struct virtio_gpu_update_cursor {
struct virtio_gpu_ctrl_hdr hdr;
struct virtio_gpu_cursor_pos pos;
uint32_t resource_id;
uint32_t hot_x;
uint32_t hot_y;
uint32_t padding;
};
/* If the blob size is less than 16K, it is regarded as the
* cursor_buffer.
* So it is not mapped as dma-buf.
*/
#define CURSOR_BLOB_SIZE (16 * 1024)
/* VIRTIO_GPU_CMD_RESOURCE_CREATE_BLOB */
struct virtio_gpu_resource_create_blob {
struct virtio_gpu_ctrl_hdr hdr;
uint32_t resource_id;
#define VIRTIO_GPU_BLOB_MEM_GUEST 0x0001
#define VIRTIO_GPU_BLOB_FLAG_USE_SHAREABLE 0x0002
/* blob_mem/blob_id is not used */
uint32_t blob_mem;
uint32_t blob_flags;
uint32_t nr_entries;
uint64_t blob_id;
uint64_t size;
/*
* sizeof(nr_entries * virtio_gpu_mem_entry) bytes follow
*/
};
/* VIRTIO_GPU_CMD_SET_SCANOUT_BLOB */
struct virtio_gpu_set_scanout_blob {
struct virtio_gpu_ctrl_hdr hdr;
struct virtio_gpu_rect r;
uint32_t scanout_id;
uint32_t resource_id;
uint32_t width;
uint32_t height;
uint32_t format;
uint32_t padding;
uint32_t strides[4];
uint32_t offsets[4];
};
enum vga_thread_status {
VGA_THREAD_EOL = 0,
VGA_THREAD_RUNNING
};
struct virtio_gpu_scanout {
int scanout_id;
uint32_t resource_id;
struct virtio_gpu_rect scanout_rect;
pixman_image_t *cur_img;
struct dma_buf_info *dma_buf;
bool is_active;
};
/*
* Per-device struct
*/
struct virtio_gpu {
struct virtio_base base;
struct virtio_vq_info vq[VIRTIO_GPU_QNUM];
struct virtio_gpu_config cfg;
pthread_mutex_t mtx;
int vdpy_handle;
LIST_HEAD(,virtio_gpu_resource_2d) r2d_list;
struct vdpy_display_bh ctrl_bh;
struct vdpy_display_bh cursor_bh;
struct vdpy_display_bh vga_bh;
struct vga vga;
pthread_mutex_t vga_thread_mtx;
int32_t vga_thread_status;
uint8_t edid[VIRTIO_GPU_EDID_SIZE];
bool is_blob_supported;
int scanout_num;
struct virtio_gpu_scanout *gpu_scanouts;
};
struct virtio_gpu_command {
struct virtio_gpu_ctrl_hdr hdr;
struct virtio_gpu *gpu;
struct virtio_vq_info *vq;
struct iovec *iov;
uint32_t iovcnt;
bool finished;
uint32_t iolen;
};
static void virtio_gpu_reset(void *vdev);
static int virtio_gpu_cfgread(void *, int, int, uint32_t *);
static int virtio_gpu_cfgwrite(void *, int, int, uint32_t);
static void virtio_gpu_neg_features(void *, uint64_t);
static void virtio_gpu_set_status(void *, uint64_t);
static void * virtio_gpu_vga_render(void *param);
static struct virtio_ops virtio_gpu_ops = {
"virtio-gpu", /* our name */
VIRTIO_GPU_QNUM, /* we currently support 2 virtqueues */
sizeof(struct virtio_gpu_config),/* config reg size */
virtio_gpu_reset, /* reset */
NULL, /* device-wide qnotify */
virtio_gpu_cfgread, /* read PCI config */
virtio_gpu_cfgwrite, /* write PCI config */
virtio_gpu_neg_features, /* apply negotiated features */
virtio_gpu_set_status, /* called on guest set status */
};
static int virtio_gpu_device_cnt = 0;
static inline bool virtio_gpu_blob_supported(struct virtio_gpu *gpu)
{
return gpu->is_blob_supported;
}
static void virtio_gpu_dmabuf_ref(struct dma_buf_info *info)
{
if (!info)
return;
atomic_add_fetch(&info->ref_count, 1);
}
static void virtio_gpu_dmabuf_unref(struct dma_buf_info *info)
{
if (!info)
return;
if (atomic_sub_fetch(&info->ref_count, 1) == 0) {
if (info->dmabuf_fd > 0)
close(info->dmabuf_fd);
free(info);
}
}
static void
virtio_gpu_set_status(void *vdev, uint64_t status)
{
struct virtio_gpu *gpu;
pr_dbg("virtio-gpu setting deivce status 0x%x.\n", status);
gpu = vdev;
gpu->base.status = status;
}
static void
virtio_gpu_reset(void *vdev)
{
struct virtio_gpu *gpu;
struct virtio_gpu_resource_2d *r2d;
pr_dbg("Resetting virtio-gpu device.\n");
gpu = vdev;
while (LIST_FIRST(&gpu->r2d_list)) {
r2d = LIST_FIRST(&gpu->r2d_list);
if (r2d) {
if (r2d->image) {
pixman_image_unref(r2d->image);
r2d->image = NULL;
}
if (r2d->blob) {
virtio_gpu_dmabuf_unref(r2d->dma_info);
r2d->dma_info = NULL;
r2d->blob = false;
}
LIST_REMOVE(r2d, link);
if (r2d->iov) {
free(r2d->iov);
r2d->iov = NULL;
}
free(r2d);
}
}
LIST_INIT(&gpu->r2d_list);
gpu->vga.enable = true;
pthread_mutex_lock(&gpu->vga_thread_mtx);
if (atomic_load(&gpu->vga_thread_status) == VGA_THREAD_EOL) {
atomic_store(&gpu->vga_thread_status, VGA_THREAD_RUNNING);
pthread_create(&gpu->vga.tid, NULL, virtio_gpu_vga_render, (void *)gpu);
}
pthread_mutex_unlock(&gpu->vga_thread_mtx);
virtio_reset_dev(&gpu->base);
}
static int
virtio_gpu_cfgread(void *vdev, int offset, int size, uint32_t *retval)
{
struct virtio_gpu *gpu;
void *ptr;
gpu = vdev;
ptr = (uint8_t *)&gpu->cfg + offset;
memcpy(retval, ptr, size);
return 0;
}
static int
virtio_gpu_cfgwrite(void *vdev, int offset, int size, uint32_t value)
{
struct virtio_gpu *gpu;
void *ptr;
gpu = vdev;
ptr = (uint8_t *)&gpu->cfg + offset;
if (offset == offsetof(struct virtio_gpu_config, events_clear)) {
memcpy(ptr, &value, size);
gpu->cfg.events_read &= ~value;
gpu->cfg.events_clear &= ~value;
}
pr_err("%s: write to read-only registers.\n", __func__);
return 0;
}
static void
virtio_gpu_neg_features(void *vdev, uint64_t negotiated_features)
{
struct virtio_gpu *gpu;
pr_dbg("virtio-gpu driver negotiated feature bits 0x%x.\n",
negotiated_features);
gpu = vdev;
gpu->base.negotiated_caps = negotiated_features;
}
static void
virtio_gpu_update_resp_fence(struct virtio_gpu_ctrl_hdr *hdr,
struct virtio_gpu_ctrl_hdr *resp)
{
if ((hdr == NULL ) || (resp == NULL))
return;
if(hdr->flags & VIRTIO_GPU_FLAG_FENCE) {
resp->flags |= VIRTIO_GPU_FLAG_FENCE;
resp->fence_id = hdr->fence_id;
}
}
static void
virtio_gpu_cmd_unspec(struct virtio_gpu_command *cmd)
{
struct virtio_gpu_ctrl_hdr resp;
pr_info("virtio-gpu: unspec commands received.\n");
memset(&resp, 0, sizeof(resp));
cmd->iolen = sizeof(resp);
resp.type = VIRTIO_GPU_RESP_ERR_UNSPEC;
virtio_gpu_update_resp_fence(&cmd->hdr, &resp);
memcpy(cmd->iov[cmd->iovcnt - 1].iov_base, &resp, sizeof(resp));
}
static void
virtio_gpu_cmd_get_edid(struct virtio_gpu_command *cmd)
{
struct virtio_gpu_get_edid req;
struct virtio_gpu_resp_edid resp;
struct virtio_gpu *gpu;
gpu = cmd->gpu;
memcpy(&req, cmd->iov[0].iov_base, sizeof(req));
cmd->iolen = sizeof(resp);
memset(&resp, 0, sizeof(resp));
virtio_gpu_update_resp_fence(&cmd->hdr, &resp.hdr);
if (req.scanout >= gpu->scanout_num) {
pr_err("%s: Invalid scanout_id %d\n", req.scanout);
resp.hdr.type = VIRTIO_GPU_RESP_ERR_INVALID_SCANOUT_ID;
memcpy(cmd->iov[1].iov_base, &resp, sizeof(resp));
return;
}
/* Only one EDID block is enough */
resp.size = 128;
resp.hdr.type = VIRTIO_GPU_RESP_OK_EDID;
vdpy_get_edid(gpu->vdpy_handle, req.scanout, resp.edid, resp.size);
memcpy(cmd->iov[1].iov_base, &resp, sizeof(resp));
}
static void
virtio_gpu_cmd_get_display_info(struct virtio_gpu_command *cmd)
{
struct virtio_gpu_resp_display_info resp;
struct display_info info;
struct virtio_gpu *gpu;
int i;
gpu = cmd->gpu;
cmd->iolen = sizeof(resp);
memset(&resp, 0, sizeof(resp));
resp.hdr.type = VIRTIO_GPU_RESP_OK_DISPLAY_INFO;
virtio_gpu_update_resp_fence(&cmd->hdr, &resp.hdr);
for (i = 0; i < gpu->scanout_num; i++) {
vdpy_get_display_info(gpu->vdpy_handle, i, &info);
resp.pmodes[i].enabled = 1;
resp.pmodes[i].r.x = 0;
resp.pmodes[i].r.y = 0;
resp.pmodes[i].r.width = info.width;
resp.pmodes[i].r.height = info.height;
}
memcpy(cmd->iov[1].iov_base, &resp, sizeof(resp));
}
static struct virtio_gpu_resource_2d *
virtio_gpu_find_resource_2d(struct virtio_gpu *gpu, uint32_t resource_id)
{
struct virtio_gpu_resource_2d *r2d;
LIST_FOREACH(r2d, &gpu->r2d_list, link) {
if (r2d->resource_id == resource_id) {
return r2d;
}
}
return NULL;
}
static pixman_format_code_t
virtio_gpu_get_pixman_format(uint32_t format)
{
switch (format) {
case VIRTIO_GPU_FORMAT_B8G8R8X8_UNORM:
pr_dbg("%s: format B8G8R8X8.\n", __func__);
return PIXMAN_x8r8g8b8;
case VIRTIO_GPU_FORMAT_B8G8R8A8_UNORM:
pr_dbg("%s: format B8G8R8A8.\n", __func__);
return PIXMAN_a8r8g8b8;
case VIRTIO_GPU_FORMAT_X8R8G8B8_UNORM:
pr_dbg("%s: format X8R8G8B8.\n", __func__);
return PIXMAN_b8g8r8x8;
case VIRTIO_GPU_FORMAT_A8R8G8B8_UNORM:
pr_dbg("%s: format A8R8G8B8.\n", __func__);
return PIXMAN_b8g8r8a8;
case VIRTIO_GPU_FORMAT_R8G8B8X8_UNORM:
pr_dbg("%s: format R8G8B8X8.\n", __func__);
return PIXMAN_x8b8g8r8;
case VIRTIO_GPU_FORMAT_R8G8B8A8_UNORM:
pr_dbg("%s: format R8G8B8A8.\n", __func__);
return PIXMAN_a8b8g8r8;
case VIRTIO_GPU_FORMAT_X8B8G8R8_UNORM:
pr_dbg("%s: format X8B8G8R8.\n", __func__);
return PIXMAN_r8g8b8x8;
case VIRTIO_GPU_FORMAT_A8B8G8R8_UNORM:
pr_dbg("%s: format A8B8G8R8.\n", __func__);
return PIXMAN_r8g8b8a8;
default:
return 0;
}
}
static void
virtio_gpu_update_scanout(struct virtio_gpu *gpu, int scanout_id, int resource_id,
struct virtio_gpu_rect *scan_rect)
{
struct virtio_gpu_scanout *gpu_scanout;
struct virtio_gpu_resource_2d *r2d;
/* as it is already checked, this is not checked again */
gpu_scanout = gpu->gpu_scanouts + scanout_id;
if (gpu_scanout->dma_buf) {
virtio_gpu_dmabuf_unref(gpu_scanout->dma_buf);
gpu_scanout->dma_buf = NULL;
}
if (gpu_scanout->cur_img) {
pixman_image_unref(gpu_scanout->cur_img);
gpu_scanout->cur_img = NULL;
}
gpu_scanout->resource_id = resource_id;
r2d = virtio_gpu_find_resource_2d(gpu, resource_id);
if (r2d) {
gpu_scanout->is_active = true;
if (r2d->blob) {
virtio_gpu_dmabuf_ref(r2d->dma_info);
gpu_scanout->dma_buf = r2d->dma_info;
} else {
pixman_image_ref(r2d->image);
gpu_scanout->cur_img = r2d->image;
}
} else {
gpu_scanout->is_active = false;
}
memcpy(&gpu_scanout->scanout_rect, scan_rect, sizeof(*scan_rect));
}
static void
virtio_gpu_cmd_resource_create_2d(struct virtio_gpu_command *cmd)
{
struct virtio_gpu_resource_create_2d req;
struct virtio_gpu_ctrl_hdr resp;
struct virtio_gpu_resource_2d *r2d;
memcpy(&req, cmd->iov[0].iov_base, sizeof(req));
memset(&resp, 0, sizeof(resp));
r2d = virtio_gpu_find_resource_2d(cmd->gpu, req.resource_id);
if (r2d) {
pr_dbg("%s: resource %d already exists.\n",
__func__, req.resource_id);
resp.type = VIRTIO_GPU_RESP_ERR_INVALID_RESOURCE_ID;
goto response;
}
r2d = (struct virtio_gpu_resource_2d*)calloc(1, \
sizeof(struct virtio_gpu_resource_2d));
r2d->resource_id = req.resource_id;
r2d->width = req.width;
r2d->height = req.height;
r2d->format = virtio_gpu_get_pixman_format(req.format);
r2d->image = pixman_image_create_bits(
r2d->format, r2d->width, r2d->height, NULL, 0);
if (!r2d->image) {
pr_err("%s: could not create resource %d (%d,%d).\n",
__func__,
r2d->resource_id,
r2d->width,
r2d->height);
free(r2d);
resp.type = VIRTIO_GPU_RESP_ERR_OUT_OF_MEMORY;
} else {
resp.type = VIRTIO_GPU_RESP_OK_NODATA;
LIST_INSERT_HEAD(&cmd->gpu->r2d_list, r2d, link);
}
response:
cmd->iolen = sizeof(resp);
virtio_gpu_update_resp_fence(&cmd->hdr, &resp);
memcpy(cmd->iov[1].iov_base, &resp, sizeof(resp));
}
static void
virtio_gpu_cmd_resource_unref(struct virtio_gpu_command *cmd)
{
struct virtio_gpu_resource_unref req;
struct virtio_gpu_ctrl_hdr resp;
struct virtio_gpu_resource_2d *r2d;
memcpy(&req, cmd->iov[0].iov_base, sizeof(req));
memset(&resp, 0, sizeof(resp));
r2d = virtio_gpu_find_resource_2d(cmd->gpu, req.resource_id);
if (r2d) {
if (r2d->image) {
pixman_image_unref(r2d->image);
r2d->image = NULL;
}
if (r2d->blob) {
virtio_gpu_dmabuf_unref(r2d->dma_info);
r2d->dma_info = NULL;
r2d->blob = false;
}
LIST_REMOVE(r2d, link);
if (r2d->iov) {
free(r2d->iov);
r2d->iov = NULL;
}
free(r2d);
resp.type = VIRTIO_GPU_RESP_OK_NODATA;
} else {
pr_err("%s: Illegal resource id %d\n", __func__, req.resource_id);
resp.type = VIRTIO_GPU_RESP_ERR_INVALID_RESOURCE_ID;
}
cmd->iolen = sizeof(resp);
virtio_gpu_update_resp_fence(&cmd->hdr, &resp);
memcpy(cmd->iov[1].iov_base, &resp, sizeof(resp));
}
static void
virtio_gpu_cmd_resource_attach_backing(struct virtio_gpu_command *cmd)
{
struct virtio_gpu_resource_attach_backing req;
struct virtio_gpu_mem_entry *entries;
struct virtio_gpu_resource_2d *r2d;
struct virtio_gpu_ctrl_hdr resp;
int i;
uint8_t *pbuf;
memcpy(&req, cmd->iov[0].iov_base, sizeof(req));
memset(&resp, 0, sizeof(resp));
r2d = virtio_gpu_find_resource_2d(cmd->gpu, req.resource_id);
if (r2d) {
r2d->iov = malloc(req.nr_entries * sizeof(struct iovec));
r2d->iovcnt = req.nr_entries;
entries = malloc(req.nr_entries * sizeof(struct virtio_gpu_mem_entry));
pbuf = (uint8_t*)entries;
for (i = 1; i < (cmd->iovcnt - 1); i++) {
memcpy(pbuf, cmd->iov[i].iov_base, cmd->iov[i].iov_len);
pbuf += cmd->iov[i].iov_len;
}
for (i = 0; i < req.nr_entries; i++) {
r2d->iov[i].iov_base = paddr_guest2host(
cmd->gpu->base.dev->vmctx,
entries[i].addr,
entries[i].length);
r2d->iov[i].iov_len = entries[i].length;
}
free(entries);
} else {
pr_err("%s: Illegal resource id %d\n", __func__, req.resource_id);
resp.type = VIRTIO_GPU_RESP_ERR_INVALID_RESOURCE_ID;
}
cmd->iolen = sizeof(resp);
resp.type = VIRTIO_GPU_RESP_OK_NODATA;
virtio_gpu_update_resp_fence(&cmd->hdr, &resp);
memcpy(cmd->iov[cmd->iovcnt - 1].iov_base, &resp, sizeof(resp));
}
static void
virtio_gpu_cmd_resource_detach_backing(struct virtio_gpu_command *cmd)
{
struct virtio_gpu_resource_detach_backing req;
struct virtio_gpu_resource_2d *r2d;
struct virtio_gpu_ctrl_hdr resp;
memcpy(&req, cmd->iov[0].iov_base, sizeof(req));
memset(&resp, 0, sizeof(resp));
r2d = virtio_gpu_find_resource_2d(cmd->gpu, req.resource_id);
if (r2d && r2d->iov) {
free(r2d->iov);
r2d->iov = NULL;
}
cmd->iolen = sizeof(resp);
resp.type = VIRTIO_GPU_RESP_OK_NODATA;
virtio_gpu_update_resp_fence(&cmd->hdr, &resp);
memcpy(cmd->iov[1].iov_base, &resp, sizeof(resp));
}
static void
virtio_gpu_cmd_set_scanout(struct virtio_gpu_command *cmd)
{
struct virtio_gpu_set_scanout req;
struct virtio_gpu_resource_2d *r2d;
struct virtio_gpu_ctrl_hdr resp;
struct surface surf;
struct virtio_gpu *gpu;
struct virtio_gpu_scanout *gpu_scanout;
int bytes_pp;
gpu = cmd->gpu;
memcpy(&req, cmd->iov[0].iov_base, sizeof(req));
memset(&resp, 0, sizeof(resp));
virtio_gpu_update_resp_fence(&cmd->hdr, &resp);
if (req.scanout_id >= gpu->scanout_num) {
pr_err("%s: Invalid scanout_id %d\n", req.scanout_id);
resp.type = VIRTIO_GPU_RESP_ERR_INVALID_SCANOUT_ID;
memcpy(cmd->iov[1].iov_base, &resp, sizeof(resp));
return;
}
gpu_scanout = gpu->gpu_scanouts + req.scanout_id;
gpu_scanout->scanout_id = req.scanout_id;
r2d = virtio_gpu_find_resource_2d(gpu, req.resource_id);
if ((req.resource_id == 0) || (r2d == NULL)) {
virtio_gpu_update_scanout(gpu, req.scanout_id, 0, &req.r);
vdpy_surface_set(gpu->vdpy_handle, req.scanout_id, NULL);
resp.type = VIRTIO_GPU_RESP_OK_NODATA;
memcpy(cmd->iov[1].iov_base, &resp, sizeof(resp));
return;
}
if ((req.r.x > r2d->width) ||
(req.r.y > r2d->height) ||
(req.r.width > r2d->width) ||
(req.r.height > r2d->height) ||
(req.r.x + req.r.width) > (r2d->width) ||
(req.r.y + req.r.height) > (r2d->height)) {
pr_err("%s: Scanout bound out of underlying resource.\n",
__func__);
resp.type = VIRTIO_GPU_RESP_ERR_INVALID_PARAMETER;
} else {
virtio_gpu_update_scanout(gpu, req.scanout_id, req.resource_id, &req.r);
bytes_pp = PIXMAN_FORMAT_BPP(r2d->format) / 8;
pixman_image_ref(r2d->image);
surf.pixel = pixman_image_get_data(r2d->image);
surf.x = req.r.x;
surf.y = req.r.y;
surf.width = req.r.width;
surf.height = req.r.height;
surf.stride = pixman_image_get_stride(r2d->image);
surf.surf_format = r2d->format;
surf.surf_type = SURFACE_PIXMAN;
surf.pixel += bytes_pp * surf.x + surf.y * surf.stride;
vdpy_surface_set(gpu->vdpy_handle, req.scanout_id, &surf);
pixman_image_unref(r2d->image);
resp.type = VIRTIO_GPU_RESP_OK_NODATA;
}
cmd->iolen = sizeof(resp);
memcpy(cmd->iov[1].iov_base, &resp, sizeof(resp));
if(cmd->gpu->vga.enable) {
cmd->gpu->vga.enable = false;
}
}
static void
virtio_gpu_cmd_transfer_to_host_2d(struct virtio_gpu_command *cmd)
{
struct virtio_gpu_transfer_to_host_2d req;
struct virtio_gpu_resource_2d *r2d;
struct virtio_gpu_ctrl_hdr resp;
uint32_t src_offset, dst_offset, stride, bpp, h;
pixman_format_code_t format;
void *img_data, *dst, *src;
int i, done, bytes, total;
int width, height;
memcpy(&req, cmd->iov[0].iov_base, sizeof(req));
memset(&resp, 0, sizeof(resp));
virtio_gpu_update_resp_fence(&cmd->hdr, &resp);
r2d = virtio_gpu_find_resource_2d(cmd->gpu, req.resource_id);
if (r2d == NULL) {
pr_err("%s: Illegal resource id %d\n", __func__,
req.resource_id);
resp.type = VIRTIO_GPU_RESP_ERR_INVALID_RESOURCE_ID;
memcpy(cmd->iov[1].iov_base, &resp, sizeof(resp));
return;
}
if (r2d->blob) {
resp.type = VIRTIO_GPU_RESP_OK_NODATA;
memcpy(cmd->iov[1].iov_base, &resp, sizeof(resp));
return;
}
if ((req.r.x > r2d->width) ||
(req.r.y > r2d->height) ||
(req.r.width > r2d->width) ||
(req.r.height > r2d->height) ||
(req.r.x + req.r.width > r2d->width) ||
(req.r.y + req.r.height > r2d->height)) {
pr_err("%s: transfer bounds outside resource.\n", __func__);
resp.type = VIRTIO_GPU_RESP_ERR_INVALID_PARAMETER;
} else {
pixman_image_ref(r2d->image);
stride = pixman_image_get_stride(r2d->image);
format = pixman_image_get_format(r2d->image);
bpp = PIXMAN_FORMAT_BPP(format) / 8;
img_data = pixman_image_get_data(r2d->image);
width = (req.r.width < r2d->width) ? req.r.width : r2d->width;
height = (req.r.height < r2d->height) ? req.r.height : r2d->height;
for (h = 0; h < height; h++) {
src_offset = req.offset + stride * h;
dst_offset = (req.r.y + h) * stride + (req.r.x * bpp);
dst = img_data + dst_offset;
done = 0;
total = width * bpp;
for (i = 0; i < r2d->iovcnt; i++) {
if ((r2d->iov[i].iov_base == 0) || (r2d->iov[i].iov_len == 0)) {
continue;
}
if (src_offset < r2d->iov[i].iov_len) {
src = r2d->iov[i].iov_base + src_offset;
bytes = ((total - done) < (r2d->iov[i].iov_len - src_offset)) ?
(total - done) : (r2d->iov[i].iov_len - src_offset);
memcpy((dst + done), src, bytes);
src_offset = 0;
done += bytes;
if (done >= total) {
break;
}
} else {
src_offset -= r2d->iov[i].iov_len;
}
}
}
pixman_image_unref(r2d->image);
resp.type = VIRTIO_GPU_RESP_OK_NODATA;
}
cmd->iolen = sizeof(resp);
memcpy(cmd->iov[1].iov_base, &resp, sizeof(resp));
}
static bool
virtio_gpu_scanout_needs_flush(struct virtio_gpu *gpu,
int scanout_id,
int resource_id,
struct virtio_gpu_rect *flush_rect)
{
struct virtio_gpu_scanout *gpu_scanout;
pixman_region16_t flush_region, final_region, scanout_region;
/* the scanout_id is already checked. So it is ignored in this function */
gpu_scanout = gpu->gpu_scanouts + scanout_id;
/* if the different resource_id is used, flush can be skipped */
if (resource_id != gpu_scanout->resource_id)
return false;
pixman_region_init(&final_region);
pixman_region_init_rect(&scanout_region,
gpu_scanout->scanout_rect.x,
gpu_scanout->scanout_rect.y,
gpu_scanout->scanout_rect.width,
gpu_scanout->scanout_rect.height);
pixman_region_init_rect(&flush_region,
flush_rect->x, flush_rect->y,
flush_rect->width, flush_rect->height);
/* Check intersect region to determine whether scanout_region
* needs to be flushed.
*/
pixman_region_intersect(&final_region, &scanout_region, &flush_region);
/* if intersection_region is empty, it means that the scanout_region is not
* covered by the flushed_region. And it is unnecessary to update
*/
if (pixman_region_not_empty(&final_region))
return true;
else
return false;
}
static void
virtio_gpu_cmd_resource_flush(struct virtio_gpu_command *cmd)
{
struct virtio_gpu_resource_flush req;
struct virtio_gpu_ctrl_hdr resp;
struct virtio_gpu_resource_2d *r2d;
struct surface surf;
struct virtio_gpu *gpu;
int i;
struct virtio_gpu_scanout *gpu_scanout;
int bytes_pp;
gpu = cmd->gpu;
memcpy(&req, cmd->iov[0].iov_base, sizeof(req));
memset(&resp, 0, sizeof(resp));
virtio_gpu_update_resp_fence(&cmd->hdr, &resp);
r2d = virtio_gpu_find_resource_2d(gpu, req.resource_id);
if (r2d == NULL) {
pr_err("%s: Illegal resource id %d\n", __func__,
req.resource_id);
resp.type = VIRTIO_GPU_RESP_ERR_INVALID_RESOURCE_ID;
memcpy(cmd->iov[1].iov_base, &resp, sizeof(resp));
return;
}
if (r2d->blob) {
virtio_gpu_dmabuf_ref(r2d->dma_info);
for (i = 0; i < gpu->scanout_num; i++) {
if (!virtio_gpu_scanout_needs_flush(gpu, i, req.resource_id, &req.r))
continue;
surf.dma_info.dmabuf_fd = r2d->dma_info->dmabuf_fd;
surf.surf_type = SURFACE_DMABUF;
vdpy_surface_update(gpu->vdpy_handle, i, &surf);
}
virtio_gpu_dmabuf_unref(r2d->dma_info);
resp.type = VIRTIO_GPU_RESP_OK_NODATA;
memcpy(cmd->iov[1].iov_base, &resp, sizeof(resp));
return;
}
pixman_image_ref(r2d->image);
bytes_pp = PIXMAN_FORMAT_BPP(r2d->format) / 8;
for (i = 0; i < gpu->scanout_num; i++) {
if (!virtio_gpu_scanout_needs_flush(gpu, i, req.resource_id, &req.r))
continue;
gpu_scanout = gpu->gpu_scanouts + i;
surf.pixel = pixman_image_get_data(r2d->image);
surf.x = gpu_scanout->scanout_rect.x;
surf.y = gpu_scanout->scanout_rect.y;
surf.width = gpu_scanout->scanout_rect.width;
surf.height = gpu_scanout->scanout_rect.height;
surf.stride = pixman_image_get_stride(r2d->image);
surf.surf_format = r2d->format;
surf.surf_type = SURFACE_PIXMAN;
surf.pixel += bytes_pp * surf.x + surf.y * surf.stride;
vdpy_surface_update(gpu->vdpy_handle, i, &surf);
}
pixman_image_unref(r2d->image);
cmd->iolen = sizeof(resp);
resp.type = VIRTIO_GPU_RESP_OK_NODATA;
memcpy(cmd->iov[1].iov_base, &resp, sizeof(resp));
}
static int udmabuf_fd(void)
{
static bool first = true;
static int udmabuf;
if (!first)
return udmabuf;
first = false;
udmabuf = open("/dev/udmabuf", O_RDWR);
if (udmabuf < 0) {
pr_err("Could not open /dev/udmabuf: %s.", strerror(errno));
}
return udmabuf;
}
static struct dma_buf_info *virtio_gpu_create_udmabuf(struct virtio_gpu *gpu,
struct virtio_gpu_mem_entry *entries,
int nr_entries)
{
struct udmabuf_create_list *list;
int udmabuf, i, dmabuf_fd;
struct vm_mem_region ret_region;
bool fail_flag;
struct dma_buf_info *info;
udmabuf = udmabuf_fd();
if (udmabuf < 0) {
return NULL;
}
fail_flag = false;
list = malloc(sizeof(*list) + sizeof(struct udmabuf_create_item) * nr_entries);
info = malloc(sizeof(*info));
if ((info == NULL) || (list == NULL)) {
free(list);
free(info);
return NULL;
}
for (i = 0; i < nr_entries; i++) {
if (vm_find_memfd_region(gpu->base.dev->vmctx,
entries[i].addr,
&ret_region) == false) {
fail_flag = true;
pr_err("%s : Failed to find memfd for %llx.\n",
__func__, entries[i].addr);
break;
}
list->list[i].memfd = ret_region.fd;
list->list[i].offset = ret_region.fd_offset;
list->list[i].size = entries[i].length;
}
list->count = nr_entries;
list->flags = UDMABUF_FLAGS_CLOEXEC;
if (fail_flag) {
dmabuf_fd = -1;
} else {
dmabuf_fd = ioctl(udmabuf, UDMABUF_CREATE_LIST, list);
}
if (dmabuf_fd < 0) {
free(info);
info = NULL;
pr_err("%s : Failed to create the dmabuf. %s\n",
__func__, strerror(errno));
}
if (info) {
info->dmabuf_fd = dmabuf_fd;
atomic_store(&info->ref_count, 1);
}
free(list);
return info;
}
static void
virtio_gpu_cmd_create_blob(struct virtio_gpu_command *cmd)
{
struct virtio_gpu_resource_create_blob req;
struct virtio_gpu_mem_entry *entries;
struct virtio_gpu_resource_2d *r2d;
struct virtio_gpu_ctrl_hdr resp;
int i;
uint8_t *pbuf;
memcpy(&req, cmd->iov[0].iov_base, sizeof(req));
cmd->iolen = sizeof(resp);
memset(&resp, 0, sizeof(resp));
virtio_gpu_update_resp_fence(&cmd->hdr, &resp);
if (req.resource_id == 0) {
pr_dbg("%s : invalid resource id in cmd.\n", __func__);
resp.type = VIRTIO_GPU_RESP_ERR_INVALID_RESOURCE_ID;
memcpy(cmd->iov[cmd->iovcnt - 1].iov_base, &resp, sizeof(resp));
return;
}
if ((req.blob_mem != VIRTIO_GPU_BLOB_MEM_GUEST) ||
(req.blob_flags != VIRTIO_GPU_BLOB_FLAG_USE_SHAREABLE)) {
pr_dbg("%s : invalid create_blob parameter for %d.\n",
__func__, req.resource_id);
resp.type = VIRTIO_GPU_RESP_ERR_INVALID_PARAMETER;
memcpy(cmd->iov[cmd->iovcnt - 1].iov_base, &resp, sizeof(resp));
return;
}
r2d = virtio_gpu_find_resource_2d(cmd->gpu, req.resource_id);
if (r2d) {
pr_dbg("%s : resource %d already exists.\n",
__func__, req.resource_id);
resp.type = VIRTIO_GPU_RESP_ERR_INVALID_RESOURCE_ID;
memcpy(cmd->iov[cmd->iovcnt - 1].iov_base, &resp, sizeof(resp));
return;
}
r2d = (struct virtio_gpu_resource_2d *)calloc(1,
sizeof(struct virtio_gpu_resource_2d));
r2d->resource_id = req.resource_id;
entries = malloc(req.nr_entries * sizeof(struct virtio_gpu_mem_entry));
pbuf = (uint8_t *)entries;
for (i = 1; i < (cmd->iovcnt - 1); i++) {
memcpy(pbuf, cmd->iov[i].iov_base, cmd->iov[i].iov_len);
pbuf += cmd->iov[i].iov_len;
}
if (req.size > CURSOR_BLOB_SIZE) {
/* Try to create the dma buf */
r2d->dma_info = virtio_gpu_create_udmabuf(cmd->gpu,
entries,
req.nr_entries);
if (r2d->dma_info == NULL) {
free(entries);
resp.type = VIRTIO_GPU_RESP_ERR_UNSPEC;
memcpy(cmd->iov[cmd->iovcnt - 1].iov_base, &resp, sizeof(resp));
return;
}
r2d->blob = true;
} else {
/* Cursor resource with 64x64 and PIXMAN_a8r8g8b8 format.
* Or when it fails to create dmabuf
*/
r2d->width = 64;
r2d->height = 64;
r2d->format = PIXMAN_a8r8g8b8;
r2d->image = pixman_image_create_bits(
r2d->format, r2d->width, r2d->height, NULL, 0);
r2d->iov = malloc(req.nr_entries * sizeof(struct iovec));
r2d->iovcnt = req.nr_entries;
for (i = 0; i < req.nr_entries; i++) {
r2d->iov[i].iov_base = paddr_guest2host(
cmd->gpu->base.dev->vmctx,
entries[i].addr,
entries[i].length);
r2d->iov[i].iov_len = entries[i].length;
}
}
free(entries);
resp.type = VIRTIO_GPU_RESP_OK_NODATA;
LIST_INSERT_HEAD(&cmd->gpu->r2d_list, r2d, link);
memcpy(cmd->iov[cmd->iovcnt - 1].iov_base, &resp, sizeof(resp));
}
static void
virtio_gpu_cmd_set_scanout_blob(struct virtio_gpu_command *cmd)
{
struct virtio_gpu_set_scanout_blob req;
struct virtio_gpu_resource_2d *r2d;
struct virtio_gpu_ctrl_hdr resp;
struct surface surf;
uint32_t drm_fourcc;
struct virtio_gpu *gpu;
struct virtio_gpu_scanout *gpu_scanout;
int bytes_pp;
gpu = cmd->gpu;
memset(&surf, 0, sizeof(surf));
memcpy(&req, cmd->iov[0].iov_base, sizeof(req));
cmd->iolen = sizeof(resp);
memset(&resp, 0, sizeof(resp));
virtio_gpu_update_resp_fence(&cmd->hdr, &resp);
if (cmd->gpu->vga.enable) {
cmd->gpu->vga.enable = false;
}
if (req.scanout_id >= gpu->scanout_num) {
pr_err("%s: Invalid scanout_id %d\n", req.scanout_id);
resp.type = VIRTIO_GPU_RESP_ERR_INVALID_SCANOUT_ID;
memcpy(cmd->iov[cmd->iovcnt - 1].iov_base, &resp, sizeof(resp));
return;
}
gpu_scanout = gpu->gpu_scanouts + req.scanout_id;
gpu_scanout->scanout_id = req.scanout_id;
if (req.resource_id == 0) {
virtio_gpu_update_scanout(gpu, req.scanout_id, 0, &req.r);
resp.type = VIRTIO_GPU_RESP_OK_NODATA;
memcpy(cmd->iov[cmd->iovcnt - 1].iov_base, &resp, sizeof(resp));
vdpy_surface_set(gpu->vdpy_handle, req.scanout_id, NULL);
return;
}
r2d = virtio_gpu_find_resource_2d(cmd->gpu, req.resource_id);
if (r2d == NULL) {
resp.type = VIRTIO_GPU_RESP_ERR_INVALID_RESOURCE_ID;
memcpy(cmd->iov[cmd->iovcnt - 1].iov_base, &resp, sizeof(resp));
return;
}
if (r2d->blob == false) {
/* Maybe the resource is not blob, fallback to set_scanout */
virtio_gpu_cmd_set_scanout(cmd);
return;
}
virtio_gpu_update_scanout(gpu, req.scanout_id, req.resource_id, &req.r);
virtio_gpu_dmabuf_ref(r2d->dma_info);
surf.width = req.r.width;
surf.height = req.r.height;
surf.x = req.r.x;
surf.y = req.r.y;
surf.stride = req.strides[0];
surf.dma_info.dmabuf_fd = r2d->dma_info->dmabuf_fd;
surf.surf_type = SURFACE_DMABUF;
bytes_pp = 4;
switch (req.format) {
case VIRTIO_GPU_FORMAT_B8G8R8X8_UNORM:
drm_fourcc = DRM_FORMAT_XRGB8888;
break;
case VIRTIO_GPU_FORMAT_B8G8R8A8_UNORM:
drm_fourcc = DRM_FORMAT_ARGB8888;
break;
case VIRTIO_GPU_FORMAT_R8G8B8A8_UNORM:
drm_fourcc = DRM_FORMAT_ABGR8888;
break;
case VIRTIO_GPU_FORMAT_R8G8B8X8_UNORM:
drm_fourcc = DRM_FORMAT_XBGR8888;
break;
default:
pr_err("%s : unuspported surface format %d.\n",
__func__, req.format);
drm_fourcc = DRM_FORMAT_ARGB8888;
break;
}
surf.dma_info.dmabuf_offset = req.offsets[0] + bytes_pp * surf.x + surf.y * surf.stride;
surf.dma_info.surf_fourcc = drm_fourcc;
vdpy_surface_set(gpu->vdpy_handle, req.scanout_id, &surf);
resp.type = VIRTIO_GPU_RESP_OK_NODATA;
memcpy(cmd->iov[cmd->iovcnt - 1].iov_base, &resp, sizeof(resp));
virtio_gpu_dmabuf_unref(r2d->dma_info);
return;
}
static void
virtio_gpu_ctrl_bh(void *data)
{
struct virtio_gpu *vdev;
struct virtio_vq_info *vq;
struct virtio_gpu_command cmd;
struct iovec iov[VIRTIO_GPU_MAXSEGS];
uint16_t flags[VIRTIO_GPU_MAXSEGS];
int n;
uint16_t idx;
vq = (struct virtio_vq_info *)data;
vdev = (struct virtio_gpu *)(vq->base);
cmd.gpu = vdev;
cmd.iolen = 0;
while (vq_has_descs(vq)) {
n = vq_getchain(vq, &idx, iov, VIRTIO_GPU_MAXSEGS, flags);
if (n < 0) {
pr_err("virtio-gpu: invalid descriptors\n");
return;
}
if (n == 0) {
pr_err("virtio-gpu: get no available descriptors\n");
return;
}
cmd.iovcnt = n;
cmd.iov = iov;
memcpy(&cmd.hdr, iov[0].iov_base,
sizeof(struct virtio_gpu_ctrl_hdr));
switch (cmd.hdr.type) {
case VIRTIO_GPU_CMD_GET_EDID:
virtio_gpu_cmd_get_edid(&cmd);
break;
case VIRTIO_GPU_CMD_GET_DISPLAY_INFO:
virtio_gpu_cmd_get_display_info(&cmd);
break;
case VIRTIO_GPU_CMD_RESOURCE_CREATE_2D:
virtio_gpu_cmd_resource_create_2d(&cmd);
break;
case VIRTIO_GPU_CMD_RESOURCE_UNREF:
virtio_gpu_cmd_resource_unref(&cmd);
break;
case VIRTIO_GPU_CMD_RESOURCE_ATTACH_BACKING:
virtio_gpu_cmd_resource_attach_backing(&cmd);
break;
case VIRTIO_GPU_CMD_RESOURCE_DETACH_BACKING:
virtio_gpu_cmd_resource_detach_backing(&cmd);
break;
case VIRTIO_GPU_CMD_SET_SCANOUT:
virtio_gpu_cmd_set_scanout(&cmd);
break;
case VIRTIO_GPU_CMD_TRANSFER_TO_HOST_2D:
virtio_gpu_cmd_transfer_to_host_2d(&cmd);
break;
case VIRTIO_GPU_CMD_RESOURCE_FLUSH:
virtio_gpu_cmd_resource_flush(&cmd);
break;
case VIRTIO_GPU_CMD_RESOURCE_CREATE_BLOB:
if (!virtio_gpu_blob_supported(vdev)) {
virtio_gpu_cmd_unspec(&cmd);
break;
}
virtio_gpu_cmd_create_blob(&cmd);
break;
case VIRTIO_GPU_CMD_SET_SCANOUT_BLOB:
if (!virtio_gpu_blob_supported(vdev)) {
virtio_gpu_cmd_unspec(&cmd);
break;
}
virtio_gpu_cmd_set_scanout_blob(&cmd);
break;
default:
virtio_gpu_cmd_unspec(&cmd);
break;
}
vq_relchain(vq, idx, cmd.iolen); /* Release the chain */
}
vq_endchains(vq, 1); /* Generate interrupt if appropriate. */
}
static void
virtio_gpu_notify_controlq(void *vdev, struct virtio_vq_info *vq)
{
struct virtio_gpu *gpu;
gpu = (struct virtio_gpu *)vdev;
vdpy_submit_bh(gpu->vdpy_handle, &gpu->ctrl_bh);
}
static void
virtio_gpu_cmd_update_cursor(struct virtio_gpu_command *cmd)
{
struct virtio_gpu_update_cursor req;
struct virtio_gpu_resource_2d *r2d;
struct cursor cur;
struct virtio_gpu *gpu;
gpu = cmd->gpu;
memcpy(&req, cmd->iov[0].iov_base, sizeof(req));
if (req.resource_id > 0) {
r2d = virtio_gpu_find_resource_2d(cmd->gpu, req.resource_id);
if (r2d == NULL) {
pr_err("%s: Illegal resource id %d\n", __func__,
req.resource_id);
return;
}
cur.x = req.pos.x;
cur.y = req.pos.y;
cur.hot_x = req.hot_x;
cur.hot_y = req.hot_y;
cur.width = r2d->width;
cur.height = r2d->height;
pixman_image_ref(r2d->image);
cur.data = pixman_image_get_data(r2d->image);
vdpy_cursor_define(gpu->vdpy_handle, req.pos.scanout_id, &cur);
pixman_image_unref(r2d->image);
}
}
static void
virtio_gpu_cmd_move_cursor(struct virtio_gpu_command *cmd)
{
struct virtio_gpu_update_cursor req;
struct virtio_gpu *gpu;
gpu = cmd->gpu;
memcpy(&req, cmd->iov[0].iov_base, sizeof(req));
vdpy_cursor_move(gpu->vdpy_handle, req.pos.scanout_id, req.pos.x, req.pos.y);
}
static void
virtio_gpu_cursor_bh(void *data)
{
struct virtio_gpu *vdev;
struct virtio_vq_info *vq;
struct virtio_gpu_command cmd;
struct virtio_gpu_ctrl_hdr hdr;
struct iovec iov[VIRTIO_GPU_MAXSEGS];
int n;
uint16_t idx;
vq = (struct virtio_vq_info *)data;
vdev = (struct virtio_gpu *)(vq->base);
cmd.gpu = vdev;
cmd.iolen = 0;
while (vq_has_descs(vq)) {
n = vq_getchain(vq, &idx, iov, VIRTIO_GPU_MAXSEGS, NULL);
if (n < 0) {
pr_err("virtio-gpu: invalid descriptors\n");
return;
}
if (n == 0) {
pr_err("virtio-gpu: get no available descriptors\n");
return;
}
cmd.iovcnt = n;
cmd.iov = iov;
memcpy(&hdr, iov[0].iov_base, sizeof(hdr));
switch (hdr.type) {
case VIRTIO_GPU_CMD_UPDATE_CURSOR:
virtio_gpu_cmd_update_cursor(&cmd);
break;
case VIRTIO_GPU_CMD_MOVE_CURSOR:
virtio_gpu_cmd_move_cursor(&cmd);
break;
default:
break;
}
vq_relchain(vq, idx, cmd.iolen); /* Release the chain */
}
vq_endchains(vq, 1); /* Generate interrupt if appropriate. */
}
static void
virtio_gpu_notify_cursorq(void *vdev, struct virtio_vq_info *vq)
{
struct virtio_gpu *gpu;
gpu = (struct virtio_gpu *)vdev;
vdpy_submit_bh(gpu->vdpy_handle, &gpu->cursor_bh);
}
static void
virtio_gpu_vga_bh(void *param)
{
struct virtio_gpu *gpu;
gpu = (struct virtio_gpu*)param;
if ((gpu->vga.surf.width != gpu->vga.gc->gc_image->width) ||
(gpu->vga.surf.height != gpu->vga.gc->gc_image->height)) {
gpu->vga.surf.width = gpu->vga.gc->gc_image->width;
gpu->vga.surf.height = gpu->vga.gc->gc_image->height;
gpu->vga.surf.stride = gpu->vga.gc->gc_image->width * 4;
gpu->vga.surf.pixel = gpu->vga.gc->gc_image->data;
gpu->vga.surf.surf_format = PIXMAN_a8r8g8b8;
gpu->vga.surf.surf_type = SURFACE_PIXMAN;
vdpy_surface_set(gpu->vdpy_handle, 0, &gpu->vga.surf);
}
vdpy_surface_update(gpu->vdpy_handle, 0, &gpu->vga.surf);
}
static void *
virtio_gpu_vga_render(void *param)
{
struct virtio_gpu *gpu;
gpu = (struct virtio_gpu*)param;
gpu->vga.surf.width = 0;
gpu->vga.surf.stride = 0;
/* The below logic needs to be refined */
while(gpu->vga.enable) {
if(gpu->vga.gc->gc_image->vgamode) {
vga_render(gpu->vga.gc, gpu->vga.dev);
break;
}
if(gpu->vga.gc->gc_image->width != gpu->vga.vberegs.xres ||
gpu->vga.gc->gc_image->height != gpu->vga.vberegs.yres) {
gc_resize(gpu->vga.gc, gpu->vga.vberegs.xres, gpu->vga.vberegs.yres);
}
vdpy_submit_bh(gpu->vdpy_handle, &gpu->vga_bh);
usleep(33000);
}
pthread_mutex_lock(&gpu->vga_thread_mtx);
atomic_store(&gpu->vga_thread_status, VGA_THREAD_EOL);
pthread_mutex_unlock(&gpu->vga_thread_mtx);
return NULL;
}
static int
virtio_gpu_init(struct vmctx *ctx, struct pci_vdev *dev, char *opts)
{
struct virtio_gpu *gpu;
pthread_mutexattr_t attr;
int rc = 0;
struct display_info info;
int prot;
struct virtio_pci_cap cap;
struct virtio_pci_notify_cap notify;
struct virtio_pci_cfg_cap cfg;
if (virtio_gpu_device_cnt) {
pr_err("%s: only 1 virtio-gpu device can be created.\n", __func__);
return -1;
}
virtio_gpu_device_cnt++;
/* allocate the virtio-gpu device */
gpu = calloc(1, sizeof(struct virtio_gpu));
if (!gpu) {
pr_err("%s: out of memory\n", __func__);
return -1;
}
/* init mutex attribute properly to avoid deadlock */
rc = pthread_mutexattr_init(&attr);
if (rc) {
pr_err("%s: mutexattr init failed with error %d.\n",
__func__, rc);
return rc;
}
rc = pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
if (rc) {
pr_err("%s: mutexattr_settype failed with error %d.\n",
__func__, rc);
return rc;
}
rc = pthread_mutex_init(&gpu->mtx, &attr);
if (rc) {
pr_err("%s: pthread_mutex_init failed with error %d.\n",
__func__,rc);
return rc;
}
/* register the virtio_gpu_ops to virtio framework */
virtio_linkup(&gpu->base,
&virtio_gpu_ops,
gpu,
dev,
gpu->vq,
BACKEND_VBSU);
gpu->scanout_num = 1;
gpu->vdpy_handle = vdpy_init(&gpu->scanout_num);
gpu->base.mtx = &gpu->mtx;
gpu->base.device_caps = VIRTIO_GPU_S_HOSTCAPS;
if ((gpu->scanout_num < 0) || (gpu->scanout_num > 2)) {
pr_err("%s: return incorrect scanout num %d\n", gpu->scanout_num);
return -1;
}
gpu->gpu_scanouts = calloc(gpu->scanout_num, sizeof(struct virtio_gpu_scanout));
if (gpu->gpu_scanouts == NULL) {
pr_err("%s: out of memory for gpu_scanouts\n", __func__);
free(gpu);
return -1;
}
if (vm_allow_dmabuf(gpu->base.dev->vmctx)) {
FILE *fp;
char buf[16];
int list_limit;
gpu->is_blob_supported = true;
/* Now the memfd is used by default and it
* is based on Huge_tlb.
* But if both 2M and 1G are used for memory,
* it can't support dmabuf as it is difficult to
* determine whether one memory region is based on 2M or 1G.
*/
fp = fopen("/sys/module/udmabuf/parameters/list_limit", "r");
if (fp) {
memset(buf, 0, sizeof(buf));
rc = fread(buf, sizeof(buf), 1, fp);
fclose(fp);
list_limit = atoi(buf);
if (list_limit < 4096) {
pr_info("udmabuf.list_limit=%d in kernel is too small. "
"Please add udmabuf.list_limit=4096 in kernel "
"boot option to use GPU zero-copy.\n",
list_limit);
gpu->is_blob_supported = false;
}
} else {
pr_info("Zero-copy is disabled. Please check that "
"CONFIG_UDMABUF is enabled in the kernel config.\n");
gpu->is_blob_supported = false;
}
if (gpu->is_blob_supported)
gpu->base.device_caps |= (1UL << VIRTIO_GPU_F_RESOURCE_BLOB);
}
/* set queue size */
gpu->vq[VIRTIO_GPU_CONTROLQ].qsize = VIRTIO_GPU_RINGSZ;
gpu->vq[VIRTIO_GPU_CONTROLQ].notify = virtio_gpu_notify_controlq;
gpu->vq[VIRTIO_GPU_CURSORQ].qsize = VIRTIO_GPU_RINGSZ;
gpu->vq[VIRTIO_GPU_CURSORQ].notify = virtio_gpu_notify_cursorq;
/* Initialize the ctrl/cursor/vga bh_task */
gpu->ctrl_bh.task_cb = virtio_gpu_ctrl_bh;
gpu->ctrl_bh.data = &gpu->vq[VIRTIO_GPU_CONTROLQ];
gpu->cursor_bh.task_cb = virtio_gpu_cursor_bh;
gpu->cursor_bh.data = &gpu->vq[VIRTIO_GPU_CURSORQ];
gpu->vga_bh.task_cb = virtio_gpu_vga_bh;
gpu->vga_bh.data = gpu;
/* prepare the config space */
gpu->cfg.events_read = 0;
gpu->cfg.events_clear = 0;
gpu->cfg.num_scanouts = gpu->scanout_num;
gpu->cfg.num_capsets = 0;
/* config the device id and vendor id according to spec */
pci_set_cfgdata16(dev, PCIR_DEVICE, VIRTIO_DEV_GPU);
pci_set_cfgdata16(dev, PCIR_VENDOR, VIRTIO_VENDOR);
pci_set_cfgdata16(dev, PCIR_REVID, 1);
pci_set_cfgdata8(dev, PCIR_CLASS, PCIC_DISPLAY);
pci_set_cfgdata8(dev, PCIR_SUBCLASS, PCIS_DISPLAY_VGA);
pci_set_cfgdata16(dev, PCIR_SUBDEV_0, VIRTIO_TYPE_GPU);
pci_set_cfgdata16(dev, PCIR_SUBVEND_0, VIRTIO_VENDOR);
LIST_INIT(&gpu->r2d_list);
vdpy_get_display_info(gpu->vdpy_handle, 0, &info);
/*** PCI Config BARs setup ***/
/** BAR0: VGA framebuffer **/
pci_emul_alloc_bar(dev, 0, PCIBAR_MEM32, VIRTIO_GPU_VGA_FB_SIZE);
prot = PROT_READ | PROT_WRITE;
if (vm_map_memseg_vma(ctx, VIRTIO_GPU_VGA_FB_SIZE, dev->bar[0].addr,
(uint64_t)ctx->fb_base, prot) != 0) {
pr_err("%s: fail to map VGA framebuffer to bar0.\n", __func__);
}
/** BAR2: VGA & Virtio Modern regs **/
/* EDID data blob [0x000~0x3ff] */
vdpy_get_edid(gpu->vdpy_handle, 0, gpu->edid, VIRTIO_GPU_EDID_SIZE);
/* VGA ioports regs [0x400~0x41f] */
gpu->vga.gc = gc_init(info.width, info.height, ctx->fb_base);
gpu->vga.dev = vga_init(gpu->vga.gc, 0);
/* Bochs Display regs [0x500~0x516]*/
gpu->vga.vberegs.xres = info.width;
gpu->vga.vberegs.yres = info.height;
gpu->vga.vberegs.bpp = 32;
gpu->vga.vberegs.id = VBE_DISPI_ID0;
gpu->vga.vberegs.video_memory_64k = VIRTIO_GPU_VGA_FB_SIZE >> 16;
/* Virtio Modern capability regs*/
cap.cap_vndr = PCIY_VENDOR;
cap.cap_next = 0;
cap.cap_len = sizeof(cap);
cap.bar = 2;
/* Common configuration regs [0x1000~0x17ff]*/
cap.cfg_type = VIRTIO_PCI_CAP_COMMON_CFG;
cap.offset = VIRTIO_GPU_CAP_COMMON_OFFSET;
cap.length = VIRTIO_GPU_CAP_COMMON_SIZE;
pci_emul_add_capability(dev, (u_char *)&cap, sizeof(cap));
/* ISR status regs [0x1800~0x1fff]*/
cap.cfg_type = VIRTIO_PCI_CAP_ISR_CFG;
cap.offset = VIRTIO_GPU_CAP_ISR_OFFSET;
cap.length = VIRTIO_GPU_CAP_ISR_SIZE;
pci_emul_add_capability(dev, (u_char *)&cap, sizeof(cap));
/* Device configuration regs [0x2000~0x2fff]*/
cap.cfg_type = VIRTIO_PCI_CAP_DEVICE_CFG;
cap.offset = VIRTIO_CAP_DEVICE_OFFSET;
cap.length = VIRTIO_CAP_DEVICE_SIZE;
pci_emul_add_capability(dev, (u_char *)&cap, sizeof(cap));
/* Notification regs [0x3000~0x3fff]*/
notify.cap.cap_vndr = PCIY_VENDOR;
notify.cap.cap_next = 0;
notify.cap.cap_len = sizeof(notify);
notify.cap.cfg_type = VIRTIO_PCI_CAP_NOTIFY_CFG;
notify.cap.bar = 2;
notify.cap.offset = VIRTIO_CAP_NOTIFY_OFFSET;
notify.cap.length = VIRTIO_CAP_NOTIFY_SIZE;
notify.notify_off_multiplier = VIRTIO_MODERN_NOTIFY_OFF_MULT;
pci_emul_add_capability(dev, (u_char *)&notify, sizeof(notify));
/* Alternative configuration access regs */
cfg.cap.cap_vndr = PCIY_VENDOR;
cfg.cap.cap_next = 0;
cfg.cap.cap_len = sizeof(cfg);
cfg.cap.cfg_type = VIRTIO_PCI_CAP_PCI_CFG;
pci_emul_add_capability(dev, (u_char *)&cfg, sizeof(cfg));
pci_emul_alloc_bar(dev, 2, PCIBAR_MEM64, VIRTIO_MODERN_MEM_BAR_SIZE);
rc = virtio_intr_init(&gpu->base, 4, virtio_uses_msix());
if (rc) {
pr_err("%s, interrupt_init failed.\n", __func__);
return rc;
}
rc = virtio_set_modern_pio_bar(&gpu->base, 5);
if (rc) {
pr_err("%s, set modern io bar(BAR5) failed.\n", __func__);
return rc;
}
pthread_mutex_init(&gpu->vga_thread_mtx, NULL);
/* VGA Compablility */
gpu->vga.enable = true;
gpu->vga.surf.width = 0;
gpu->vga.surf.stride = 0;
gpu->vga.surf.height = 0;
gpu->vga.surf.pixel = 0;
atomic_store(&gpu->vga_thread_status, VGA_THREAD_RUNNING);
pthread_create(&gpu->vga.tid, NULL, virtio_gpu_vga_render, (void*)gpu);
return 0;
}
static void
virtio_gpu_deinit(struct vmctx *ctx, struct pci_vdev *dev, char *opts)
{
struct virtio_gpu *gpu;
struct virtio_gpu_resource_2d *r2d;
int i;
gpu = (struct virtio_gpu *)dev->arg;
gpu->vga.enable = false;
pthread_mutex_lock(&gpu->vga_thread_mtx);
if (atomic_load(&gpu->vga_thread_status) != VGA_THREAD_EOL) {
pthread_mutex_unlock(&gpu->vga_thread_mtx);
pthread_join(gpu->vga.tid, NULL);
} else
pthread_mutex_unlock(&gpu->vga_thread_mtx);
if (gpu->vga.dev)
vga_deinit(&gpu->vga);
if (gpu->vga.gc) {
gc_deinit(gpu->vga.gc);
gpu->vga.gc = NULL;
}
for (i=0; i < gpu->scanout_num; i++) {
struct virtio_gpu_scanout *gpu_scanout;
gpu_scanout = gpu->gpu_scanouts + i;
if (gpu_scanout && gpu_scanout->is_active) {
if (gpu_scanout->cur_img) {
pixman_image_unref(gpu_scanout->cur_img);
gpu_scanout->cur_img = NULL;
}
if (gpu_scanout->dma_buf) {
virtio_gpu_dmabuf_unref(gpu_scanout->dma_buf);
gpu_scanout->dma_buf = NULL;
}
gpu_scanout->is_active = false;
}
}
free(gpu->gpu_scanouts);
gpu->gpu_scanouts = NULL;
pthread_mutex_destroy(&gpu->vga_thread_mtx);
while (LIST_FIRST(&gpu->r2d_list)) {
r2d = LIST_FIRST(&gpu->r2d_list);
if (r2d) {
if (r2d->image) {
pixman_image_unref(r2d->image);
r2d->image = NULL;
}
if (r2d->blob) {
virtio_gpu_dmabuf_unref(r2d->dma_info);
r2d->dma_info = NULL;
r2d->blob = false;
}
LIST_REMOVE(r2d, link);
if (r2d->iov) {
free(r2d->iov);
r2d->iov = NULL;
}
free(r2d);
}
}
vdpy_deinit(gpu->vdpy_handle);
if (gpu) {
pthread_mutex_destroy(&gpu->mtx);
free(gpu);
}
virtio_gpu_device_cnt--;
}
uint64_t
virtio_gpu_edid_read(struct vmctx *ctx, int vcpu, struct pci_vdev *dev,
uint64_t offset, int size)
{
struct virtio_gpu *gpu;
uint8_t *p;
uint64_t value;
gpu = (struct virtio_gpu *)dev->arg;
p = (uint8_t *)gpu->edid + offset;
value = 0;
switch (size) {
case 1:
value = *p;
break;
case 2:
value = *(uint16_t *)p;
break;
case 4:
value = *(uint32_t *)p;
break;
case 8:
value = *(uint64_t *)p;
break;
default:
pr_dbg("%s: read unknown size %d\n", __func__, size);
break;
}
return (value);
}
static void
virtio_gpu_write(struct vmctx *ctx, int vcpu, struct pci_vdev *dev,
int baridx, uint64_t offset, int size, uint64_t value)
{
struct virtio_gpu *gpu;
gpu = (struct virtio_gpu *)dev->arg;
if (baridx == 0) {
pr_err("%s: vgafb offset=%d size=%d value=%d.\n", __func__, offset, size, value);
} else if (baridx == 2) {
if ((offset >= 0) && (offset <= VIRTIO_GPU_EDID_SIZE)) {
pr_dbg("%s: EDID region is read-only.\n", __func__);
} else if ((offset >= VIRTIO_GPU_VGA_IOPORT_OFFSET) &&
(offset < (VIRTIO_GPU_VGA_IOPORT_OFFSET +
VIRTIO_GPU_VGA_IOPORT_SIZE))) {
offset -= VIRTIO_GPU_VGA_IOPORT_OFFSET;
vga_ioport_write(ctx, vcpu, &gpu->vga, offset, size,
value);
} else if ((offset >= VIRTIO_GPU_VGA_VBE_OFFSET) &&
(offset < (VIRTIO_GPU_VGA_VBE_OFFSET +
VIRTIO_GPU_VGA_VBE_SIZE))) {
offset -= VIRTIO_GPU_VGA_VBE_OFFSET;
vga_vbe_write(ctx, vcpu, &gpu->vga, offset, size, value);
if ((offset == VBE_DISPI_INDEX_ENABLE) && (value & VBE_DISPI_ENABLED)) {
pthread_mutex_lock(&gpu->vga_thread_mtx);
if (atomic_load(&gpu->vga_thread_status) == VGA_THREAD_EOL) {
atomic_store(&gpu->vga_thread_status,
VGA_THREAD_RUNNING);
pthread_create(&gpu->vga.tid, NULL,
virtio_gpu_vga_render,
(void *)gpu);
}
pthread_mutex_unlock(&gpu->vga_thread_mtx);
}
} else if ((offset >= VIRTIO_GPU_CAP_COMMON_OFFSET) &&
(offset < (VIRTIO_GPU_CAP_COMMON_OFFSET +
VIRTIO_GPU_CAP_COMMON_SIZE))) {
offset -= VIRTIO_GPU_CAP_COMMON_OFFSET;
virtio_common_cfg_write(dev, offset, size, value);
} else if ((offset >= VIRTIO_CAP_DEVICE_OFFSET) &&
(offset < (VIRTIO_CAP_DEVICE_OFFSET +
VIRTIO_CAP_DEVICE_SIZE))) {
offset -= VIRTIO_CAP_DEVICE_OFFSET;
virtio_device_cfg_write(dev, offset, size, value);
} else if ((offset >= VIRTIO_CAP_NOTIFY_OFFSET) &&
(offset < (VIRTIO_CAP_NOTIFY_OFFSET +
VIRTIO_CAP_NOTIFY_SIZE))) {
offset -= VIRTIO_CAP_NOTIFY_OFFSET;
virtio_notify_cfg_write(dev, offset, size, value);
} else {
virtio_pci_write(ctx, vcpu, dev, baridx, offset, size,
value);
}
} else {
virtio_pci_write(ctx, vcpu, dev, baridx, offset, size, value);
}
}
static uint64_t
virtio_gpu_read(struct vmctx *ctx, int vcpu, struct pci_vdev *dev,
int baridx, uint64_t offset, int size)
{
struct virtio_gpu *gpu;
gpu = (struct virtio_gpu *)dev->arg;
if (baridx == 0) {
pr_err("%s: vgafb offset=%d size=%d.\n", __func__, offset, size);
return 0;
} else if (baridx == 2) {
if ((offset >= 0) && (offset <= VIRTIO_GPU_EDID_SIZE)) {
return virtio_gpu_edid_read(ctx, vcpu, dev, offset, size);
} else if ((offset >= VIRTIO_GPU_VGA_IOPORT_OFFSET) &&
(offset < (VIRTIO_GPU_VGA_IOPORT_OFFSET +
VIRTIO_GPU_VGA_IOPORT_SIZE))) {
offset -= VIRTIO_GPU_VGA_IOPORT_OFFSET;
return vga_ioport_read(ctx, vcpu, &gpu->vga, offset, size);
} else if ((offset >= VIRTIO_GPU_VGA_VBE_OFFSET) &&
(offset < (VIRTIO_GPU_VGA_VBE_OFFSET +
VIRTIO_GPU_VGA_VBE_SIZE))) {
offset -= VIRTIO_GPU_VGA_VBE_OFFSET;
return vga_vbe_read(ctx, vcpu, &gpu->vga, offset, size);
} else if ((offset >= VIRTIO_GPU_CAP_COMMON_OFFSET) &&
(offset < (VIRTIO_GPU_CAP_COMMON_OFFSET +
VIRTIO_GPU_CAP_COMMON_SIZE))) {
offset -= VIRTIO_GPU_CAP_COMMON_OFFSET;
return virtio_common_cfg_read(dev, offset, size);
} else if ((offset >= VIRTIO_GPU_CAP_ISR_OFFSET) &&
(offset < (VIRTIO_GPU_CAP_ISR_OFFSET +
VIRTIO_GPU_CAP_ISR_SIZE))) {
offset -= VIRTIO_GPU_CAP_ISR_OFFSET;
return virtio_isr_cfg_read(dev, offset, size);
} else if ((offset >= VIRTIO_CAP_DEVICE_OFFSET) &&
(offset < (VIRTIO_CAP_DEVICE_OFFSET +
VIRTIO_CAP_DEVICE_SIZE))) {
offset -= VIRTIO_CAP_DEVICE_OFFSET;
return virtio_device_cfg_read(dev, offset, size);
} else {
return virtio_pci_read(ctx, vcpu, dev, baridx, offset,
size);
}
} else {
return virtio_pci_read(ctx, vcpu, dev, baridx, offset, size);
}
}
struct pci_vdev_ops pci_ops_virtio_gpu = {
.class_name = "virtio-gpu",
.vdev_init = virtio_gpu_init,
.vdev_deinit = virtio_gpu_deinit,
.vdev_barwrite = virtio_gpu_write,
.vdev_barread = virtio_gpu_read
};
DEFINE_PCI_DEVTYPE(pci_ops_virtio_gpu);