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 <airlied@redhat.com>
+ *     Gerd Hoffmann <kraxel@redhat.com>
+ *
+ * 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 <bryanv@FreeBSD.org>
+ * 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 <sys/param.h>
+#include <sys/types.h>
+#include <sys/bus.h>
+#include <sys/callout.h>
+#include <sys/fbio.h>
+#include <sys/kernel.h>
+#include <sys/malloc.h>
+#include <sys/module.h>
+#include <sys/sglist.h>
+
+#include <machine/atomic.h>
+#include <machine/bus.h>
+#include <machine/resource.h>
+
+#include <vm/vm.h>
+#include <vm/pmap.h>
+
+#include <dev/virtio/virtio.h>
+#include <dev/virtio/virtqueue.h>
+#include <dev/virtio/gpu/virtio_gpu.h>
+
+#include <dev/vt/vt.h>
+#include <dev/vt/hw/fb/vt_fb.h>
+#include <dev/vt/colors/vt_termcolors.h>
+
+#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);
+}

