mirror of
https://github.com/projectacrn/acrn-hypervisor.git
synced 2025-06-22 05:30:24 +00:00
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>
2012 lines
54 KiB
C
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 *)¬ify, 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);
|