diff --git a/share/man/man4/Makefile b/share/man/man4/Makefile --- a/share/man/man4/Makefile +++ b/share/man/man4/Makefile @@ -570,6 +570,7 @@ virtio_balloon.4 \ virtio_blk.4 \ virtio_console.4 \ + virtio_gpu.4 \ virtio_random.4 \ virtio_scsi.4 \ ${_vmci.4} \ diff --git a/share/man/man4/virtio.4 b/share/man/man4/virtio.4 --- a/share/man/man4/virtio.4 +++ b/share/man/man4/virtio.4 @@ -83,6 +83,10 @@ provided by the .Xr virtio_balloon 4 device driver. +.It Sy GPU +Graphics support is provided by the +.Xr virtio_gpu 4 +device driver. .It Sy SCSI An emulated SCSI HBA is provided by the .Xr virtio_scsi 4 @@ -92,6 +96,7 @@ .Xr virtio_balloon 4 , .Xr virtio_blk 4 , .Xr virtio_console 4 , +.Xr virtio_gpu 4 , .Xr virtio_random 4 , .Xr virtio_scsi 4 , .Xr vtnet 4 diff --git a/share/man/man4/virtio_gpu.4 b/share/man/man4/virtio_gpu.4 new file mode 100644 --- /dev/null +++ b/share/man/man4/virtio_gpu.4 @@ -0,0 +1,54 @@ +.\"- +.\" SPDX-License-Identifier: BSD-2-Clause +.\" +.\" Copyright (c) 2014 Bryan Venteicher +.\" All rights reserved. +.\" Copyright (c) 2023 Arm Ltd +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.Dd August 14, 2023 +.Dt VIRTIO_GPU 4 +.Os +.Sh NAME +.Nm virtio_gpu +.Nd VirtIO GPU driver +.Sh SYNOPSIS +To compile this driver into the kernel, +place the following lines in your +kernel configuration file: +.Bd -ragged -offset indent +.Cd "device virtio_gpu" +.Ed +.Sh DESCRIPTION +The +.Nm +device driver provides support for VirtIO gpu devices to create a +.Xr vt 4 +console. +.Sh SEE ALSO +.Xr virtio 4 +.Xr vt 4 +.Sh HISTORY +The +.Nm +driver first appeared in FreeBSD 14.0. diff --git a/sys/arm64/conf/std.virt b/sys/arm64/conf/std.virt --- a/sys/arm64/conf/std.virt +++ b/sys/arm64/conf/std.virt @@ -19,6 +19,7 @@ device virtio_pci # VirtIO PCI device device virtio_mmio # VirtIO Memory Mapped IO device device virtio_blk # VirtIO Block device +device virtio_gpu # VirtIO GPU device device virtio_scsi # VirtIO SCSI device device vtnet # VirtIO Ethernet device diff --git a/sys/conf/files b/sys/conf/files --- a/sys/conf/files +++ b/sys/conf/files @@ -3399,6 +3399,7 @@ dev/virtio/network/if_vtnet.c optional vtnet dev/virtio/block/virtio_blk.c optional virtio_blk dev/virtio/balloon/virtio_balloon.c optional virtio_balloon +dev/virtio/gpu/virtio_gpu.c optional virtio_gpu dev/virtio/scsi/virtio_scsi.c optional virtio_scsi dev/virtio/random/virtio_random.c optional virtio_random dev/virtio/console/virtio_console.c optional virtio_console diff --git a/sys/dev/virtio/gpu/virtio_gpu.h b/sys/dev/virtio/gpu/virtio_gpu.h new file mode 100644 --- /dev/null +++ b/sys/dev/virtio/gpu/virtio_gpu.h @@ -0,0 +1,454 @@ +/* + * Virtio GPU Device + * + * Copyright Red Hat, Inc. 2013-2014 + * + * Authors: + * Dave Airlie + * Gerd Hoffmann + * + * This header is BSD licensed so anyone can use the definitions + * to implement compatible drivers/servers: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of IBM nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL IBM OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef VIRTIO_GPU_HW_H +#define VIRTIO_GPU_HW_H + +/* + * VIRTIO_GPU_CMD_CTX_* + * VIRTIO_GPU_CMD_*_3D + */ +#define VIRTIO_GPU_F_VIRGL 0 + +/* + * VIRTIO_GPU_CMD_GET_EDID + */ +#define VIRTIO_GPU_F_EDID 1 +/* + * VIRTIO_GPU_CMD_RESOURCE_ASSIGN_UUID + */ +#define VIRTIO_GPU_F_RESOURCE_UUID 2 + +/* + * VIRTIO_GPU_CMD_RESOURCE_CREATE_BLOB + */ +#define VIRTIO_GPU_F_RESOURCE_BLOB 3 +/* + * VIRTIO_GPU_CMD_CREATE_CONTEXT with + * context_init and multiple timelines + */ +#define VIRTIO_GPU_F_CONTEXT_INIT 4 + +enum virtio_gpu_ctrl_type { + VIRTIO_GPU_UNDEFINED = 0, + + /* 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, + + /* 3d commands */ + VIRTIO_GPU_CMD_CTX_CREATE = 0x0200, + VIRTIO_GPU_CMD_CTX_DESTROY, + VIRTIO_GPU_CMD_CTX_ATTACH_RESOURCE, + VIRTIO_GPU_CMD_CTX_DETACH_RESOURCE, + VIRTIO_GPU_CMD_RESOURCE_CREATE_3D, + VIRTIO_GPU_CMD_TRANSFER_TO_HOST_3D, + VIRTIO_GPU_CMD_TRANSFER_FROM_HOST_3D, + VIRTIO_GPU_CMD_SUBMIT_3D, + VIRTIO_GPU_CMD_RESOURCE_MAP_BLOB, + VIRTIO_GPU_CMD_RESOURCE_UNMAP_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, +}; + +enum virtio_gpu_shm_id { + VIRTIO_GPU_SHM_ID_UNDEFINED = 0, + /* + * VIRTIO_GPU_CMD_RESOURCE_MAP_BLOB + * VIRTIO_GPU_CMD_RESOURCE_UNMAP_BLOB + */ + VIRTIO_GPU_SHM_ID_HOST_VISIBLE = 1 +}; + +#define VIRTIO_GPU_FLAG_FENCE (1 << 0) +/* + * If the following flag is set, then ring_idx contains the index + * of the command ring that needs to used when creating the fence + */ +#define VIRTIO_GPU_FLAG_INFO_RING_IDX (1 << 1) + +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]; +}; + +/* data passed in the cursor vq */ + +struct virtio_gpu_cursor_pos { + uint32_t scanout_id; + uint32_t x; + uint32_t y; + uint32_t padding; +}; + +/* VIRTIO_GPU_CMD_UPDATE_CURSOR, VIRTIO_GPU_CMD_MOVE_CURSOR */ +struct virtio_gpu_update_cursor { + struct virtio_gpu_ctrl_hdr hdr; + struct virtio_gpu_cursor_pos pos; /* update & move */ + uint32_t resource_id; /* update only */ + uint32_t hot_x; /* update only */ + uint32_t hot_y; /* update only */ + uint32_t padding; +}; + +/* data passed in the control vq, 2d related */ + +struct virtio_gpu_rect { + uint32_t x; + uint32_t y; + uint32_t width; + uint32_t height; +}; + +/* VIRTIO_GPU_CMD_RESOURCE_UNREF */ +struct virtio_gpu_resource_unref { + struct virtio_gpu_ctrl_hdr hdr; + uint32_t resource_id; + uint32_t padding; +}; + +/* VIRTIO_GPU_CMD_RESOURCE_CREATE_2D: create a 2d resource with a format */ +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; +}; + +/* 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; +}; + +/* 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; +}; + +/* VIRTIO_GPU_CMD_TRANSFER_TO_HOST_2D: simple transfer to_host */ +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; +}; + +struct virtio_gpu_mem_entry { + uint64_t addr; + uint32_t length; + uint32_t padding; +}; + +/* VIRTIO_GPU_CMD_RESOURCE_ATTACH_BACKING */ +struct virtio_gpu_resource_attach_backing { + struct virtio_gpu_ctrl_hdr hdr; + uint32_t resource_id; + uint32_t nr_entries; +}; + +/* 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; +}; + +/* VIRTIO_GPU_RESP_OK_DISPLAY_INFO */ +#define VIRTIO_GPU_MAX_SCANOUTS 16 +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]; +}; + +/* data passed in the control vq, 3d related */ + +struct virtio_gpu_box { + uint32_t x, y, z; + uint32_t w, h, d; +}; + +/* VIRTIO_GPU_CMD_TRANSFER_TO_HOST_3D, VIRTIO_GPU_CMD_TRANSFER_FROM_HOST_3D */ +struct virtio_gpu_transfer_host_3d { + struct virtio_gpu_ctrl_hdr hdr; + struct virtio_gpu_box box; + uint64_t offset; + uint32_t resource_id; + uint32_t level; + uint32_t stride; + uint32_t layer_stride; +}; + +/* VIRTIO_GPU_CMD_RESOURCE_CREATE_3D */ +#define VIRTIO_GPU_RESOURCE_FLAG_Y_0_TOP (1 << 0) +struct virtio_gpu_resource_create_3d { + struct virtio_gpu_ctrl_hdr hdr; + uint32_t resource_id; + uint32_t target; + uint32_t format; + uint32_t bind; + uint32_t width; + uint32_t height; + uint32_t depth; + uint32_t array_size; + uint32_t last_level; + uint32_t nr_samples; + uint32_t flags; + uint32_t padding; +}; + +/* VIRTIO_GPU_CMD_CTX_CREATE */ +#define VIRTIO_GPU_CONTEXT_INIT_CAPSET_ID_MASK 0x000000ff +struct virtio_gpu_ctx_create { + struct virtio_gpu_ctrl_hdr hdr; + uint32_t nlen; + uint32_t context_init; + char debug_name[64]; +}; + +/* VIRTIO_GPU_CMD_CTX_DESTROY */ +struct virtio_gpu_ctx_destroy { + struct virtio_gpu_ctrl_hdr hdr; +}; + +/* VIRTIO_GPU_CMD_CTX_ATTACH_RESOURCE, VIRTIO_GPU_CMD_CTX_DETACH_RESOURCE */ +struct virtio_gpu_ctx_resource { + struct virtio_gpu_ctrl_hdr hdr; + uint32_t resource_id; + uint32_t padding; +}; + +/* VIRTIO_GPU_CMD_SUBMIT_3D */ +struct virtio_gpu_cmd_submit { + struct virtio_gpu_ctrl_hdr hdr; + uint32_t size; + uint32_t padding; +}; + +#define VIRTIO_GPU_CAPSET_VIRGL 1 +#define VIRTIO_GPU_CAPSET_VIRGL2 2 + +/* VIRTIO_GPU_CMD_GET_CAPSET_INFO */ +struct virtio_gpu_get_capset_info { + struct virtio_gpu_ctrl_hdr hdr; + uint32_t capset_index; + uint32_t padding; +}; + +/* VIRTIO_GPU_RESP_OK_CAPSET_INFO */ +struct virtio_gpu_resp_capset_info { + struct virtio_gpu_ctrl_hdr hdr; + uint32_t capset_id; + uint32_t capset_max_version; + uint32_t capset_max_size; + uint32_t padding; +}; + +/* VIRTIO_GPU_CMD_GET_CAPSET */ +struct virtio_gpu_get_capset { + struct virtio_gpu_ctrl_hdr hdr; + uint32_t capset_id; + uint32_t capset_version; +}; + +/* VIRTIO_GPU_RESP_OK_CAPSET */ +struct virtio_gpu_resp_capset { + struct virtio_gpu_ctrl_hdr hdr; + uint8_t capset_data[]; +}; + +/* VIRTIO_GPU_CMD_GET_EDID */ +struct virtio_gpu_cmd_get_edid { + struct virtio_gpu_ctrl_hdr hdr; + uint32_t scanout; + uint32_t padding; +}; + +/* VIRTIO_GPU_RESP_OK_EDID */ +struct virtio_gpu_resp_edid { + struct virtio_gpu_ctrl_hdr hdr; + uint32_t size; + uint32_t padding; + uint8_t edid[1024]; +}; + +#define VIRTIO_GPU_EVENT_DISPLAY (1 << 0) + +struct virtio_gpu_config { + uint32_t events_read; + uint32_t events_clear; + uint32_t num_scanouts; + uint32_t num_capsets; +}; + +/* simple formats for fbcon/X use */ +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, +}; + +/* VIRTIO_GPU_CMD_RESOURCE_ASSIGN_UUID */ +struct virtio_gpu_resource_assign_uuid { + struct virtio_gpu_ctrl_hdr hdr; + uint32_t resource_id; + uint32_t padding; +}; + +/* VIRTIO_GPU_RESP_OK_RESOURCE_UUID */ +struct virtio_gpu_resp_resource_uuid { + struct virtio_gpu_ctrl_hdr hdr; + uint8_t uuid[16]; +}; + +/* 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_MEM_HOST3D 0x0002 +#define VIRTIO_GPU_BLOB_MEM_HOST3D_GUEST 0x0003 + +#define VIRTIO_GPU_BLOB_FLAG_USE_MAPPABLE 0x0001 +#define VIRTIO_GPU_BLOB_FLAG_USE_SHAREABLE 0x0002 +#define VIRTIO_GPU_BLOB_FLAG_USE_CROSS_DEVICE 0x0004 + /* zero is invalid blob mem */ + 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]; +}; + +/* VIRTIO_GPU_CMD_RESOURCE_MAP_BLOB */ +struct virtio_gpu_resource_map_blob { + struct virtio_gpu_ctrl_hdr hdr; + uint32_t resource_id; + uint32_t padding; + uint64_t offset; +}; + +/* VIRTIO_GPU_RESP_OK_MAP_INFO */ +#define VIRTIO_GPU_MAP_CACHE_MASK 0x0f +#define VIRTIO_GPU_MAP_CACHE_NONE 0x00 +#define VIRTIO_GPU_MAP_CACHE_CACHED 0x01 +#define VIRTIO_GPU_MAP_CACHE_UNCACHED 0x02 +#define VIRTIO_GPU_MAP_CACHE_WC 0x03 +struct virtio_gpu_resp_map_info { + struct virtio_gpu_ctrl_hdr hdr; + uint32_t map_info; + uint32_t padding; +}; + +/* VIRTIO_GPU_CMD_RESOURCE_UNMAP_BLOB */ +struct virtio_gpu_resource_unmap_blob { + struct virtio_gpu_ctrl_hdr hdr; + uint32_t resource_id; + uint32_t padding; +}; + +#endif diff --git a/sys/dev/virtio/gpu/virtio_gpu.c b/sys/dev/virtio/gpu/virtio_gpu.c new file mode 100644 --- /dev/null +++ b/sys/dev/virtio/gpu/virtio_gpu.c @@ -0,0 +1,697 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2013, Bryan Venteicher + * All rights reserved. + * Copyright (c) 2023, Arm Ltd + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice unmodified, this list of conditions, and the following + * disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* Driver for VirtIO GPU device. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +#include +#include +#include + +#include +#include +#include + +#include "fb_if.h" + +#define VTGPU_FEATURES 0 + +/* The guest can allocate resource IDs, we only need one */ +#define VTGPU_RESOURCE_ID 1 + +struct vtgpu_softc { + /* Must be first so we can cast from info -> softc */ + struct fb_info vtgpu_fb_info; + struct virtio_gpu_config vtgpu_gpucfg; + + device_t vtgpu_dev; + uint64_t vtgpu_features; + + struct virtqueue *vtgpu_ctrl_vq; + + uint64_t vtgpu_next_fence; + + bool vtgpu_have_fb_info; +}; + +static int vtgpu_modevent(module_t, int, void *); + +static int vtgpu_probe(device_t); +static int vtgpu_attach(device_t); +static int vtgpu_detach(device_t); + +static int vtgpu_negotiate_features(struct vtgpu_softc *); +static int vtgpu_setup_features(struct vtgpu_softc *); +static void vtgpu_read_config(struct vtgpu_softc *, + struct virtio_gpu_config *); +static int vtgpu_alloc_virtqueue(struct vtgpu_softc *); +static int vtgpu_get_display_info(struct vtgpu_softc *); +static int vtgpu_create_2d(struct vtgpu_softc *); +static int vtgpu_attach_backing(struct vtgpu_softc *); +static int vtgpu_set_scanout(struct vtgpu_softc *, uint32_t, uint32_t, + uint32_t, uint32_t); +static int vtgpu_transfer_to_host_2d(struct vtgpu_softc *, uint32_t, + uint32_t, uint32_t, uint32_t); +static int vtgpu_resource_flush(struct vtgpu_softc *, uint32_t, uint32_t, + uint32_t, uint32_t); + +static vd_blank_t vtgpu_fb_blank; +static vd_bitblt_text_t vtgpu_fb_bitblt_text; +static vd_bitblt_bmp_t vtgpu_fb_bitblt_bitmap; +static vd_drawrect_t vtgpu_fb_drawrect; +static vd_setpixel_t vtgpu_fb_setpixel; + +static struct vt_driver vtgpu_fb_driver = { + .vd_name = "virtio_gpu", + .vd_init = vt_fb_init, + .vd_fini = vt_fb_fini, + .vd_blank = vtgpu_fb_blank, + .vd_bitblt_text = vtgpu_fb_bitblt_text, + .vd_invalidate_text = vt_fb_invalidate_text, + .vd_bitblt_bmp = vtgpu_fb_bitblt_bitmap, + .vd_drawrect = vtgpu_fb_drawrect, + .vd_setpixel = vtgpu_fb_setpixel, + .vd_postswitch = vt_fb_postswitch, + .vd_priority = VD_PRIORITY_GENERIC+10, + .vd_fb_ioctl = vt_fb_ioctl, + .vd_fb_mmap = NULL, /* No mmap as we need to signal the host */ + .vd_suspend = vt_fb_suspend, + .vd_resume = vt_fb_resume, +}; + +VT_DRIVER_DECLARE(vt_vtgpu, vtgpu_fb_driver); + +static void +vtgpu_fb_blank(struct vt_device *vd, term_color_t color) +{ + struct vtgpu_softc *sc; + struct fb_info *info; + + info = vd->vd_softc; + sc = (struct vtgpu_softc *)info; + + vt_fb_blank(vd, color); + + vtgpu_transfer_to_host_2d(sc, 0, 0, sc->vtgpu_fb_info.fb_width, + sc->vtgpu_fb_info.fb_height); + vtgpu_resource_flush(sc, 0, 0, sc->vtgpu_fb_info.fb_width, + sc->vtgpu_fb_info.fb_height); +} + +static void +vtgpu_fb_bitblt_text(struct vt_device *vd, const struct vt_window *vw, + const term_rect_t *area) +{ + struct vtgpu_softc *sc; + struct fb_info *info; + int x, y, width, height; + + info = vd->vd_softc; + sc = (struct vtgpu_softc *)info; + + vt_fb_bitblt_text(vd, vw, area); + + x = area->tr_begin.tp_col * vw->vw_font->vf_width + vw->vw_draw_area.tr_begin.tp_col; + y = area->tr_begin.tp_row * vw->vw_font->vf_height + vw->vw_draw_area.tr_begin.tp_row; + width = area->tr_end.tp_col * vw->vw_font->vf_width + vw->vw_draw_area.tr_begin.tp_col - x; + height = area->tr_end.tp_row * vw->vw_font->vf_height + vw->vw_draw_area.tr_begin.tp_row - y; + + vtgpu_transfer_to_host_2d(sc, x, y, width, height); + vtgpu_resource_flush(sc, x, y, width, height); +} + +static void +vtgpu_fb_bitblt_bitmap(struct vt_device *vd, const struct vt_window *vw, + const uint8_t *pattern, const uint8_t *mask, + unsigned int width, unsigned int height, + unsigned int x, unsigned int y, term_color_t fg, term_color_t bg) +{ + struct vtgpu_softc *sc; + struct fb_info *info; + + info = vd->vd_softc; + sc = (struct vtgpu_softc *)info; + + vt_fb_bitblt_bitmap(vd, vw, pattern, mask, width, height, x, y, fg, bg); + + vtgpu_transfer_to_host_2d(sc, x, y, width, height); + vtgpu_resource_flush(sc, x, y, width, height); +} + +static void +vtgpu_fb_drawrect(struct vt_device *vd, int x1, int y1, int x2, int y2, + int fill, term_color_t color) +{ + struct vtgpu_softc *sc; + struct fb_info *info; + int width, height; + + info = vd->vd_softc; + sc = (struct vtgpu_softc *)info; + + vt_fb_drawrect(vd, x1, y1, x2, y2, fill, color); + + width = x2 - x1 + 1; + height = y2 - y1 + 1; + vtgpu_transfer_to_host_2d(sc, x1, y1, width, height); + vtgpu_resource_flush(sc, x1, y1, width, height); +} + +static void +vtgpu_fb_setpixel(struct vt_device *vd, int x, int y, term_color_t color) +{ + struct vtgpu_softc *sc; + struct fb_info *info; + + info = vd->vd_softc; + sc = (struct vtgpu_softc *)info; + + vt_fb_setpixel(vd, x, y, color); + + vtgpu_transfer_to_host_2d(sc, x, y, 1, 1); + vtgpu_resource_flush(sc, x, y, 1, 1); +} + +static struct virtio_feature_desc vtgpu_feature_desc[] = { + { VIRTIO_GPU_F_VIRGL, "VirGL" }, + { VIRTIO_GPU_F_EDID, "EDID" }, + { VIRTIO_GPU_F_RESOURCE_UUID, "ResUUID" }, + { VIRTIO_GPU_F_RESOURCE_BLOB, "ResBlob" }, + { VIRTIO_GPU_F_CONTEXT_INIT, "ContextInit" }, + { 0, NULL } +}; + +static device_method_t vtgpu_methods[] = { + /* Device methods. */ + DEVMETHOD(device_probe, vtgpu_probe), + DEVMETHOD(device_attach, vtgpu_attach), + DEVMETHOD(device_detach, vtgpu_detach), + + DEVMETHOD_END +}; + +static driver_t vtgpu_driver = { + "vtgpu", + vtgpu_methods, + sizeof(struct vtgpu_softc) +}; + +VIRTIO_DRIVER_MODULE(virtio_gpu, vtgpu_driver, vtgpu_modevent, NULL); +MODULE_VERSION(virtio_gpu, 1); +MODULE_DEPEND(virtio_gpu, virtio, 1, 1, 1); + +VIRTIO_SIMPLE_PNPINFO(virtio_gpu, VIRTIO_ID_GPU, + "VirtIO GPU"); + +static int +vtgpu_modevent(module_t mod, int type, void *unused) +{ + int error; + + switch (type) { + case MOD_LOAD: + case MOD_QUIESCE: + case MOD_UNLOAD: + case MOD_SHUTDOWN: + error = 0; + break; + default: + error = EOPNOTSUPP; + break; + } + + return (error); +} + +static int +vtgpu_probe(device_t dev) +{ + return (VIRTIO_SIMPLE_PROBE(dev, virtio_gpu)); +} + +static int +vtgpu_attach(device_t dev) +{ + struct vtgpu_softc *sc; + int error; + + sc = device_get_softc(dev); + sc->vtgpu_have_fb_info = false; + sc->vtgpu_dev = dev; + sc->vtgpu_next_fence = 1; + virtio_set_feature_desc(dev, vtgpu_feature_desc); + + error = vtgpu_setup_features(sc); + if (error != 0) { + device_printf(dev, "cannot setup features\n"); + goto fail; + } + + vtgpu_read_config(sc, &sc->vtgpu_gpucfg); + + error = vtgpu_alloc_virtqueue(sc); + if (error != 0) { + device_printf(dev, "cannot allocate virtqueue\n"); + goto fail; + } + + virtio_setup_intr(dev, INTR_TYPE_TTY); + + /* Read the device info to get the display size */ + error = vtgpu_get_display_info(sc); + if (error != 0) { + goto fail; + } + + /* + * TODO: This doesn't need to be contigmalloc as we + * can use scatter-gather lists. + */ + sc->vtgpu_fb_info.fb_vbase = (vm_offset_t)contigmalloc( + sc->vtgpu_fb_info.fb_size, M_DEVBUF, M_WAITOK|M_ZERO, 0, ~0, 4, 0); + sc->vtgpu_fb_info.fb_pbase = pmap_kextract(sc->vtgpu_fb_info.fb_vbase); + + /* Create the 2d resource */ + error = vtgpu_create_2d(sc); + if (error != 0) { + goto fail; + } + + /* Attach the backing memory */ + error = vtgpu_attach_backing(sc); + if (error != 0) { + goto fail; + } + + /* Set the scanout to link the framebuffer to the display scanout */ + error = vtgpu_set_scanout(sc, 0, 0, sc->vtgpu_fb_info.fb_width, + sc->vtgpu_fb_info.fb_height); + if (error != 0) { + goto fail; + } + + vt_allocate(&vtgpu_fb_driver, &sc->vtgpu_fb_info); + sc->vtgpu_have_fb_info = true; + + error = vtgpu_transfer_to_host_2d(sc, 0, 0, sc->vtgpu_fb_info.fb_width, + sc->vtgpu_fb_info.fb_height); + if (error != 0) + goto fail; + error = vtgpu_resource_flush(sc, 0, 0, sc->vtgpu_fb_info.fb_width, + sc->vtgpu_fb_info.fb_height); + +fail: + if (error != 0) + vtgpu_detach(dev); + + return (error); +} + +static int +vtgpu_detach(device_t dev) +{ + struct vtgpu_softc *sc; + + sc = device_get_softc(dev); + if (sc->vtgpu_have_fb_info) + vt_deallocate(&vtgpu_fb_driver, &sc->vtgpu_fb_info); + if (sc->vtgpu_fb_info.fb_vbase != 0) { + MPASS(sc->vtgpu_fb_info.fb_size != 0); + contigfree((void *)sc->vtgpu_fb_info.fb_vbase, + sc->vtgpu_fb_info.fb_size, M_DEVBUF); + } + + /* TODO: Tell the host we are detaching */ + + return (0); +} + +static int +vtgpu_negotiate_features(struct vtgpu_softc *sc) +{ + device_t dev; + uint64_t features; + + dev = sc->vtgpu_dev; + features = VTGPU_FEATURES; + + sc->vtgpu_features = virtio_negotiate_features(dev, features); + return (virtio_finalize_features(dev)); +} + +static int +vtgpu_setup_features(struct vtgpu_softc *sc) +{ + int error; + + error = vtgpu_negotiate_features(sc); + if (error != 0) + return (error); + + return (0); +} + +static void +vtgpu_read_config(struct vtgpu_softc *sc, + struct virtio_gpu_config *gpucfg) +{ + device_t dev; + + dev = sc->vtgpu_dev; + + bzero(gpucfg, sizeof(struct virtio_gpu_config)); + +#define VTGPU_GET_CONFIG(_dev, _field, _cfg) \ + virtio_read_device_config(_dev, \ + offsetof(struct virtio_gpu_config, _field), \ + &(_cfg)->_field, sizeof((_cfg)->_field)) \ + + VTGPU_GET_CONFIG(dev, events_read, gpucfg); + VTGPU_GET_CONFIG(dev, events_clear, gpucfg); + VTGPU_GET_CONFIG(dev, num_scanouts, gpucfg); + VTGPU_GET_CONFIG(dev, num_capsets, gpucfg); + +#undef VTGPU_GET_CONFIG +} + +static int +vtgpu_alloc_virtqueue(struct vtgpu_softc *sc) +{ + device_t dev; + struct vq_alloc_info vq_info[2]; + int nvqs; + + dev = sc->vtgpu_dev; + nvqs = 1; + + VQ_ALLOC_INFO_INIT(&vq_info[0], 0, NULL, sc, &sc->vtgpu_ctrl_vq, + "%s control", device_get_nameunit(dev)); + + return (virtio_alloc_virtqueues(dev, 0, nvqs, vq_info)); +} + +static int +vtgpu_req_resp(struct vtgpu_softc *sc, void *req, size_t reqlen, + void *resp, size_t resplen) +{ + struct sglist sg; + struct sglist_seg segs[2]; + int error; + + sglist_init(&sg, 2, segs); + + error = sglist_append(&sg, req, reqlen); + if (error != 0) { + device_printf(sc->vtgpu_dev, + "Unable to append the request to the sglist: %d\n", error); + return (error); + } + error = sglist_append(&sg, resp, resplen); + if (error != 0) { + device_printf(sc->vtgpu_dev, + "Unable to append the response buffer to the sglist: %d\n", + error); + return (error); + } + error = virtqueue_enqueue(sc->vtgpu_ctrl_vq, resp, &sg, 1, 1); + if (error != 0) { + device_printf(sc->vtgpu_dev, "Enqueue failed: %d\n", error); + return (error); + } + + virtqueue_notify(sc->vtgpu_ctrl_vq); + virtqueue_poll(sc->vtgpu_ctrl_vq, NULL); + + return (0); +} + +static int +vtgpu_get_display_info(struct vtgpu_softc *sc) +{ + struct { + struct virtio_gpu_ctrl_hdr req; + char pad; + struct virtio_gpu_resp_display_info resp; + } s = { 0 }; + int error; + + s.req.type = htole32(VIRTIO_GPU_CMD_GET_DISPLAY_INFO); + s.req.flags = htole32(VIRTIO_GPU_FLAG_FENCE); + s.req.fence_id = htole64(atomic_fetchadd_64(&sc->vtgpu_next_fence, 1)); + + error = vtgpu_req_resp(sc, &s.req, sizeof(s.req), &s.resp, + sizeof(s.resp)); + if (error != 0) + return (error); + + for (int i = 0; i < sc->vtgpu_gpucfg.num_scanouts; i++) { + if (s.resp.pmodes[i].enabled != 0) + MPASS(i == 0); + sc->vtgpu_fb_info.fb_name = + device_get_nameunit(sc->vtgpu_dev); + + sc->vtgpu_fb_info.fb_width = + le32toh(s.resp.pmodes[i].r.width); + sc->vtgpu_fb_info.fb_height = + le32toh(s.resp.pmodes[i].r.height); + /* 32 bits per pixel */ + sc->vtgpu_fb_info.fb_bpp = 32; + sc->vtgpu_fb_info.fb_depth = 32; + sc->vtgpu_fb_info.fb_size = sc->vtgpu_fb_info.fb_width * + sc->vtgpu_fb_info.fb_height * 4; + sc->vtgpu_fb_info.fb_stride = + sc->vtgpu_fb_info.fb_width * 4; + return (0); + } + + return (ENXIO); +} + +static int +vtgpu_create_2d(struct vtgpu_softc *sc) +{ + struct { + struct virtio_gpu_resource_create_2d req; + char pad; + struct virtio_gpu_ctrl_hdr resp; + } s = { 0 }; + int error; + + s.req.hdr.type = htole32(VIRTIO_GPU_CMD_RESOURCE_CREATE_2D); + s.req.hdr.flags = htole32(VIRTIO_GPU_FLAG_FENCE); + s.req.hdr.fence_id = htole64( + atomic_fetchadd_64(&sc->vtgpu_next_fence, 1)); + + s.req.resource_id = htole32(VTGPU_RESOURCE_ID); + s.req.format = htole32(VIRTIO_GPU_FORMAT_B8G8R8X8_UNORM); + s.req.width = htole32(sc->vtgpu_fb_info.fb_width); + s.req.height = htole32(sc->vtgpu_fb_info.fb_height); + + error = vtgpu_req_resp(sc, &s.req, sizeof(s.req), &s.resp, + sizeof(s.resp)); + if (error != 0) + return (error); + + if (s.resp.type != htole32(VIRTIO_GPU_RESP_OK_NODATA)) { + device_printf(sc->vtgpu_dev, "Invalid reponse type %x\n", + le32toh(s.resp.type)); + return (EINVAL); + } + + return (0); +} + +static int +vtgpu_attach_backing(struct vtgpu_softc *sc) +{ + struct { + struct { + struct virtio_gpu_resource_attach_backing backing; + struct virtio_gpu_mem_entry mem[1]; + } req; + char pad; + struct virtio_gpu_ctrl_hdr resp; + } s = { 0 }; + int error; + + s.req.backing.hdr.type = + htole32(VIRTIO_GPU_CMD_RESOURCE_ATTACH_BACKING); + s.req.backing.hdr.flags = htole32(VIRTIO_GPU_FLAG_FENCE); + s.req.backing.hdr.fence_id = htole64( + atomic_fetchadd_64(&sc->vtgpu_next_fence, 1)); + + s.req.backing.resource_id = htole32(VTGPU_RESOURCE_ID); + s.req.backing.nr_entries = htole32(1); + + s.req.mem[0].addr = htole32(sc->vtgpu_fb_info.fb_pbase); + s.req.mem[0].length = htole32(sc->vtgpu_fb_info.fb_size); + + error = vtgpu_req_resp(sc, &s.req, sizeof(s.req), &s.resp, + sizeof(s.resp)); + if (error != 0) + return (error); + + if (s.resp.type != htole32(VIRTIO_GPU_RESP_OK_NODATA)) { + device_printf(sc->vtgpu_dev, "Invalid reponse type %x\n", + le32toh(s.resp.type)); + return (EINVAL); + } + + return (0); +} + +static int +vtgpu_set_scanout(struct vtgpu_softc *sc, uint32_t x, uint32_t y, + uint32_t width, uint32_t height) +{ + struct { + struct virtio_gpu_set_scanout req; + char pad; + struct virtio_gpu_ctrl_hdr resp; + } s = { 0 }; + int error; + + s.req.hdr.type = htole32(VIRTIO_GPU_CMD_SET_SCANOUT); + s.req.hdr.flags = htole32(VIRTIO_GPU_FLAG_FENCE); + s.req.hdr.fence_id = htole64( + atomic_fetchadd_64(&sc->vtgpu_next_fence, 1)); + + s.req.r.x = htole32(x); + s.req.r.y = htole32(y); + s.req.r.width = htole32(width); + s.req.r.height = htole32(height); + + s.req.scanout_id = 0; + s.req.resource_id = htole32(VTGPU_RESOURCE_ID); + + error = vtgpu_req_resp(sc, &s.req, sizeof(s.req), &s.resp, + sizeof(s.resp)); + if (error != 0) + return (error); + + if (s.resp.type != htole32(VIRTIO_GPU_RESP_OK_NODATA)) { + device_printf(sc->vtgpu_dev, "Invalid reponse type %x\n", + le32toh(s.resp.type)); + return (EINVAL); + } + + return (0); +} + +static int +vtgpu_transfer_to_host_2d(struct vtgpu_softc *sc, uint32_t x, uint32_t y, + uint32_t width, uint32_t height) +{ + struct { + struct virtio_gpu_transfer_to_host_2d req; + char pad; + struct virtio_gpu_ctrl_hdr resp; + } s = { 0 }; + int error; + + s.req.hdr.type = htole32(VIRTIO_GPU_CMD_TRANSFER_TO_HOST_2D); + s.req.hdr.flags = htole32(VIRTIO_GPU_FLAG_FENCE); + s.req.hdr.fence_id = htole64( + atomic_fetchadd_64(&sc->vtgpu_next_fence, 1)); + + s.req.r.x = htole32(x); + s.req.r.y = htole32(y); + s.req.r.width = htole32(width); + s.req.r.height = htole32(height); + + s.req.offset = htole64((y * sc->vtgpu_fb_info.fb_width + x) + * (sc->vtgpu_fb_info.fb_bpp / 8)); + s.req.resource_id = htole32(VTGPU_RESOURCE_ID); + + error = vtgpu_req_resp(sc, &s.req, sizeof(s.req), &s.resp, + sizeof(s.resp)); + if (error != 0) + return (error); + + if (s.resp.type != htole32(VIRTIO_GPU_RESP_OK_NODATA)) { + device_printf(sc->vtgpu_dev, "Invalid reponse type %x\n", + le32toh(s.resp.type)); + return (EINVAL); + } + + return (0); +} + +static int +vtgpu_resource_flush(struct vtgpu_softc *sc, uint32_t x, uint32_t y, + uint32_t width, uint32_t height) +{ + struct { + struct virtio_gpu_resource_flush req; + char pad; + struct virtio_gpu_ctrl_hdr resp; + } s = { 0 }; + int error; + + s.req.hdr.type = htole32(VIRTIO_GPU_CMD_RESOURCE_FLUSH); + s.req.hdr.flags = htole32(VIRTIO_GPU_FLAG_FENCE); + s.req.hdr.fence_id = htole64( + atomic_fetchadd_64(&sc->vtgpu_next_fence, 1)); + + s.req.r.x = htole32(x); + s.req.r.y = htole32(y); + s.req.r.width = htole32(width); + s.req.r.height = htole32(height); + + s.req.resource_id = htole32(VTGPU_RESOURCE_ID); + + error = vtgpu_req_resp(sc, &s.req, sizeof(s.req), &s.resp, + sizeof(s.resp)); + if (error != 0) + return (error); + + if (s.resp.type != htole32(VIRTIO_GPU_RESP_OK_NODATA)) { + device_printf(sc->vtgpu_dev, "Invalid reponse type %x\n", + le32toh(s.resp.type)); + return (EINVAL); + } + + return (0); +}