Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
@@ -0,0 +1,25 @@
# SPDX-License-Identifier: GPL-2.0-only
config DRM_VIRTIO_GPU
tristate "Virtio GPU driver"
depends on DRM && VIRTIO_MENU && MMU
select VIRTIO
select DRM_CLIENT_SELECTION
select DRM_KMS_HELPER
select DRM_GEM_SHMEM_HELPER
select VIRTIO_DMA_SHARED_BUFFER
help
This is the virtual GPU driver for virtio. It can be used with
QEMU based VMMs (like KVM or Xen).
If unsure say M.
config DRM_VIRTIO_GPU_KMS
bool "Virtio GPU driver modesetting support"
depends on DRM_VIRTIO_GPU
default y
help
Enable modesetting support for virtio GPU driver. This can be
disabled in cases where only "headless" usage of the GPU is
required.
If unsure, say Y.
@@ -0,0 +1,11 @@
# SPDX-License-Identifier: GPL-2.0
#
# Makefile for the drm device driver. This driver provides support for the
# Direct Rendering Infrastructure (DRI) in XFree86 4.1.0 and higher.
virtio-gpu-y := virtgpu_drv.o virtgpu_kms.o virtgpu_gem.o virtgpu_vram.o \
virtgpu_display.o virtgpu_vq.o \
virtgpu_fence.o virtgpu_object.o virtgpu_debugfs.o virtgpu_plane.o \
virtgpu_ioctl.o virtgpu_prime.o virtgpu_trace_points.o virtgpu_submit.o
obj-$(CONFIG_DRM_VIRTIO_GPU) += virtio-gpu.o
@@ -0,0 +1,112 @@
/*
* Copyright (C) 2015 Red Hat, Inc.
* All Rights Reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice (including the
* next paragraph) shall be included in all copies or substantial
* portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE COPYRIGHT OWNER(S) AND/OR ITS SUPPLIERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include <linux/string_helpers.h>
#include <drm/drm_debugfs.h>
#include <drm/drm_file.h>
#include <drm/drm_print.h>
#include "virtgpu_drv.h"
static void virtio_gpu_add_bool(struct seq_file *m, const char *name,
bool value)
{
seq_printf(m, "%-16s : %s\n", name, str_yes_no(value));
}
static void virtio_gpu_add_int(struct seq_file *m, const char *name, int value)
{
seq_printf(m, "%-16s : %d\n", name, value);
}
static int virtio_gpu_features(struct seq_file *m, void *data)
{
struct drm_info_node *node = (struct drm_info_node *)m->private;
struct virtio_gpu_device *vgdev = node->minor->dev->dev_private;
virtio_gpu_add_bool(m, "virgl", vgdev->has_virgl_3d);
virtio_gpu_add_bool(m, "edid", vgdev->has_edid);
virtio_gpu_add_bool(m, "indirect", vgdev->has_indirect);
virtio_gpu_add_bool(m, "resource uuid",
vgdev->has_resource_assign_uuid);
virtio_gpu_add_bool(m, "blob resources", vgdev->has_resource_blob);
virtio_gpu_add_bool(m, "context init", vgdev->has_context_init);
virtio_gpu_add_int(m, "cap sets", vgdev->num_capsets);
virtio_gpu_add_int(m, "scanouts", vgdev->num_scanouts);
if (vgdev->host_visible_region.len) {
seq_printf(m, "%-16s : 0x%lx +0x%lx\n", "host visible region",
(unsigned long)vgdev->host_visible_region.addr,
(unsigned long)vgdev->host_visible_region.len);
}
return 0;
}
static int
virtio_gpu_debugfs_irq_info(struct seq_file *m, void *data)
{
struct drm_info_node *node = (struct drm_info_node *) m->private;
struct virtio_gpu_device *vgdev = node->minor->dev->dev_private;
seq_printf(m, "fence %llu %lld\n",
(u64)atomic64_read(&vgdev->fence_drv.last_fence_id),
vgdev->fence_drv.current_fence_id);
return 0;
}
static int
virtio_gpu_debugfs_host_visible_mm(struct seq_file *m, void *data)
{
struct drm_info_node *node = (struct drm_info_node *)m->private;
struct virtio_gpu_device *vgdev = node->minor->dev->dev_private;
struct drm_printer p;
if (!vgdev->has_host_visible) {
seq_puts(m, "Host allocations not visible to guest\n");
return 0;
}
p = drm_seq_file_printer(m);
drm_mm_print(&vgdev->host_visible_mm, &p);
return 0;
}
static struct drm_info_list virtio_gpu_debugfs_list[] = {
{ "virtio-gpu-features", virtio_gpu_features },
{ "virtio-gpu-irq-fence", virtio_gpu_debugfs_irq_info, 0, NULL },
{ "virtio-gpu-host-visible-mm", virtio_gpu_debugfs_host_visible_mm },
};
#define VIRTIO_GPU_DEBUGFS_ENTRIES ARRAY_SIZE(virtio_gpu_debugfs_list)
void
virtio_gpu_debugfs_init(struct drm_minor *minor)
{
drm_debugfs_create_files(virtio_gpu_debugfs_list,
VIRTIO_GPU_DEBUGFS_ENTRIES,
minor->debugfs_root, minor);
}
@@ -0,0 +1,401 @@
/*
* Copyright (C) 2015 Red Hat, Inc.
* All Rights Reserved.
*
* Authors:
* Dave Airlie
* Alon Levy
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR
* OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*/
#include <drm/drm_atomic_helper.h>
#include <drm/drm_damage_helper.h>
#include <drm/drm_edid.h>
#include <drm/drm_fourcc.h>
#include <drm/drm_gem_framebuffer_helper.h>
#include <drm/drm_print.h>
#include <drm/drm_probe_helper.h>
#include <drm/drm_simple_kms_helper.h>
#include <drm/drm_vblank.h>
#include <drm/drm_vblank_helper.h>
#include "virtgpu_drv.h"
#define XRES_MIN 32
#define YRES_MIN 32
#define XRES_DEF 1024
#define YRES_DEF 768
#define XRES_MAX 8192
#define YRES_MAX 8192
#define drm_connector_to_virtio_gpu_output(x) \
container_of(x, struct virtio_gpu_output, conn)
static const struct drm_crtc_funcs virtio_gpu_crtc_funcs = {
.set_config = drm_atomic_helper_set_config,
.destroy = drm_crtc_cleanup,
.page_flip = drm_atomic_helper_page_flip,
.reset = drm_atomic_helper_crtc_reset,
.atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state,
.atomic_destroy_state = drm_atomic_helper_crtc_destroy_state,
DRM_CRTC_VBLANK_TIMER_FUNCS,
};
static const struct drm_framebuffer_funcs virtio_gpu_fb_funcs = {
.create_handle = drm_gem_fb_create_handle,
.destroy = drm_gem_fb_destroy,
.dirty = drm_atomic_helper_dirtyfb,
};
static int
virtio_gpu_framebuffer_init(struct drm_device *dev,
struct virtio_gpu_framebuffer *vgfb,
const struct drm_format_info *info,
const struct drm_mode_fb_cmd2 *mode_cmd,
struct drm_gem_object *obj)
{
int ret;
vgfb->base.obj[0] = obj;
drm_helper_mode_fill_fb_struct(dev, &vgfb->base, info, mode_cmd);
ret = drm_framebuffer_init(dev, &vgfb->base, &virtio_gpu_fb_funcs);
if (ret) {
vgfb->base.obj[0] = NULL;
return ret;
}
return 0;
}
static void virtio_gpu_crtc_mode_set_nofb(struct drm_crtc *crtc)
{
struct drm_device *dev = crtc->dev;
struct virtio_gpu_device *vgdev = dev->dev_private;
struct virtio_gpu_output *output = drm_crtc_to_virtio_gpu_output(crtc);
virtio_gpu_cmd_set_scanout(vgdev, output->index, 0,
crtc->mode.hdisplay,
crtc->mode.vdisplay, 0, 0);
virtio_gpu_notify(vgdev);
}
static void virtio_gpu_crtc_atomic_enable(struct drm_crtc *crtc,
struct drm_atomic_commit *state)
{
drm_crtc_vblank_on(crtc);
}
static void virtio_gpu_crtc_atomic_disable(struct drm_crtc *crtc,
struct drm_atomic_commit *state)
{
struct drm_device *dev = crtc->dev;
struct virtio_gpu_device *vgdev = dev->dev_private;
struct virtio_gpu_output *output = drm_crtc_to_virtio_gpu_output(crtc);
drm_crtc_vblank_off(crtc);
virtio_gpu_cmd_set_scanout(vgdev, output->index, 0, 0, 0, 0, 0);
virtio_gpu_notify(vgdev);
}
static int virtio_gpu_crtc_atomic_check(struct drm_crtc *crtc,
struct drm_atomic_commit *state)
{
return 0;
}
static void virtio_gpu_crtc_atomic_flush(struct drm_crtc *crtc,
struct drm_atomic_commit *state)
{
struct drm_device *dev = crtc->dev;
struct drm_crtc_state *crtc_state = drm_atomic_get_new_crtc_state(state, crtc);
struct virtio_gpu_output *output = drm_crtc_to_virtio_gpu_output(crtc);
struct drm_pending_vblank_event *event;
/*
* virtio-gpu can't do modeset and plane update operations
* independent from each other. So the actual modeset happens
* in the plane update callback, and here we just check
* whenever we must force the modeset.
*/
if (drm_atomic_crtc_needs_modeset(crtc_state))
output->needs_modeset = true;
spin_lock_irq(&dev->event_lock);
event = crtc_state->event;
crtc_state->event = NULL;
if (event) {
if (drm_crtc_vblank_get(crtc) == 0)
drm_crtc_arm_vblank_event(crtc, event);
else
drm_crtc_send_vblank_event(crtc, event);
}
spin_unlock_irq(&dev->event_lock);
}
static const struct drm_crtc_helper_funcs virtio_gpu_crtc_helper_funcs = {
.mode_set_nofb = virtio_gpu_crtc_mode_set_nofb,
.atomic_check = virtio_gpu_crtc_atomic_check,
.atomic_flush = virtio_gpu_crtc_atomic_flush,
.atomic_enable = virtio_gpu_crtc_atomic_enable,
.atomic_disable = virtio_gpu_crtc_atomic_disable,
};
static void virtio_gpu_enc_mode_set(struct drm_encoder *encoder,
struct drm_display_mode *mode,
struct drm_display_mode *adjusted_mode)
{
}
static void virtio_gpu_enc_enable(struct drm_encoder *encoder)
{
}
static void virtio_gpu_enc_disable(struct drm_encoder *encoder)
{
}
static int virtio_gpu_conn_get_modes(struct drm_connector *connector)
{
struct virtio_gpu_output *output =
drm_connector_to_virtio_gpu_output(connector);
struct drm_display_mode *mode = NULL;
int count, width, height;
count = drm_edid_connector_add_modes(connector);
if (count)
return count;
width = le32_to_cpu(output->info.r.width);
height = le32_to_cpu(output->info.r.height);
count = drm_add_modes_noedid(connector, XRES_MAX, YRES_MAX);
if (width == 0 || height == 0) {
drm_set_preferred_mode(connector, XRES_DEF, YRES_DEF);
} else {
DRM_DEBUG("add mode: %dx%d\n", width, height);
mode = drm_cvt_mode(connector->dev, width, height, 60,
false, false, false);
if (!mode)
return count;
mode->type |= DRM_MODE_TYPE_PREFERRED;
drm_mode_probed_add(connector, mode);
count++;
}
return count;
}
static enum drm_mode_status virtio_gpu_conn_mode_valid(struct drm_connector *connector,
const struct drm_display_mode *mode)
{
struct virtio_gpu_output *output =
drm_connector_to_virtio_gpu_output(connector);
int width, height;
width = le32_to_cpu(output->info.r.width);
height = le32_to_cpu(output->info.r.height);
if (!(mode->type & DRM_MODE_TYPE_PREFERRED))
return MODE_OK;
if (mode->hdisplay == XRES_DEF && mode->vdisplay == YRES_DEF)
return MODE_OK;
if (mode->hdisplay <= width && mode->hdisplay >= width - 16 &&
mode->vdisplay <= height && mode->vdisplay >= height - 16)
return MODE_OK;
DRM_DEBUG("del mode: %dx%d\n", mode->hdisplay, mode->vdisplay);
return MODE_BAD;
}
static const struct drm_encoder_helper_funcs virtio_gpu_enc_helper_funcs = {
.mode_set = virtio_gpu_enc_mode_set,
.enable = virtio_gpu_enc_enable,
.disable = virtio_gpu_enc_disable,
};
static const struct drm_connector_helper_funcs virtio_gpu_conn_helper_funcs = {
.get_modes = virtio_gpu_conn_get_modes,
.mode_valid = virtio_gpu_conn_mode_valid,
};
static enum drm_connector_status virtio_gpu_conn_detect(
struct drm_connector *connector,
bool force)
{
struct virtio_gpu_output *output =
drm_connector_to_virtio_gpu_output(connector);
if (output->info.enabled)
return connector_status_connected;
else
return connector_status_disconnected;
}
static void virtio_gpu_conn_destroy(struct drm_connector *connector)
{
drm_connector_unregister(connector);
drm_connector_cleanup(connector);
}
static const struct drm_connector_funcs virtio_gpu_connector_funcs = {
.detect = virtio_gpu_conn_detect,
.fill_modes = drm_helper_probe_single_connector_modes,
.destroy = virtio_gpu_conn_destroy,
.reset = drm_atomic_helper_connector_reset,
.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
};
static int vgdev_output_init(struct virtio_gpu_device *vgdev, int index)
{
struct drm_device *dev = vgdev->ddev;
struct virtio_gpu_output *output = vgdev->outputs + index;
struct drm_connector *connector = &output->conn;
struct drm_encoder *encoder = &output->enc;
struct drm_crtc *crtc = &output->crtc;
struct drm_plane *primary, *cursor;
int ret;
output->index = index;
if (index == 0) {
output->info.enabled = cpu_to_le32(true);
output->info.r.width = cpu_to_le32(XRES_DEF);
output->info.r.height = cpu_to_le32(YRES_DEF);
}
primary = virtio_gpu_plane_init(vgdev, DRM_PLANE_TYPE_PRIMARY, index);
if (IS_ERR(primary))
return PTR_ERR(primary);
cursor = virtio_gpu_plane_init(vgdev, DRM_PLANE_TYPE_CURSOR, index);
if (IS_ERR(cursor))
return PTR_ERR(cursor);
ret = drm_crtc_init_with_planes(dev, crtc, primary, cursor,
&virtio_gpu_crtc_funcs, NULL);
if (ret)
return ret;
drm_crtc_helper_add(crtc, &virtio_gpu_crtc_helper_funcs);
drm_connector_init(dev, connector, &virtio_gpu_connector_funcs,
DRM_MODE_CONNECTOR_VIRTUAL);
drm_connector_helper_add(connector, &virtio_gpu_conn_helper_funcs);
if (vgdev->has_edid)
drm_connector_attach_edid_property(connector);
drm_simple_encoder_init(dev, encoder, DRM_MODE_ENCODER_VIRTUAL);
drm_encoder_helper_add(encoder, &virtio_gpu_enc_helper_funcs);
encoder->possible_crtcs = 1 << index;
drm_connector_attach_encoder(connector, encoder);
drm_connector_register(connector);
return 0;
}
static struct drm_framebuffer *
virtio_gpu_user_framebuffer_create(struct drm_device *dev,
struct drm_file *file_priv,
const struct drm_format_info *info,
const struct drm_mode_fb_cmd2 *mode_cmd)
{
struct drm_gem_object *obj = NULL;
struct virtio_gpu_framebuffer *virtio_gpu_fb;
int ret;
if (mode_cmd->pixel_format != DRM_FORMAT_HOST_XRGB8888 &&
mode_cmd->pixel_format != DRM_FORMAT_HOST_ARGB8888)
return ERR_PTR(-ENOENT);
/* lookup object associated with res handle */
obj = drm_gem_object_lookup(file_priv, mode_cmd->handles[0]);
if (!obj)
return ERR_PTR(-EINVAL);
virtio_gpu_fb = kzalloc_obj(*virtio_gpu_fb);
if (virtio_gpu_fb == NULL) {
drm_gem_object_put(obj);
return ERR_PTR(-ENOMEM);
}
ret = virtio_gpu_framebuffer_init(dev, virtio_gpu_fb, info, mode_cmd, obj);
if (ret) {
kfree(virtio_gpu_fb);
drm_gem_object_put(obj);
return NULL;
}
return &virtio_gpu_fb->base;
}
static const struct drm_mode_config_funcs virtio_gpu_mode_funcs = {
.fb_create = virtio_gpu_user_framebuffer_create,
.atomic_check = drm_atomic_helper_check,
.atomic_commit = drm_atomic_helper_commit,
};
int virtio_gpu_modeset_init(struct virtio_gpu_device *vgdev)
{
int i, ret;
if (!vgdev->num_scanouts)
return 0;
ret = drmm_mode_config_init(vgdev->ddev);
if (ret)
return ret;
vgdev->ddev->mode_config.quirk_addfb_prefer_host_byte_order = true;
vgdev->ddev->mode_config.funcs = &virtio_gpu_mode_funcs;
/* modes will be validated against the framebuffer size */
vgdev->ddev->mode_config.min_width = XRES_MIN;
vgdev->ddev->mode_config.min_height = YRES_MIN;
vgdev->ddev->mode_config.max_width = XRES_MAX;
vgdev->ddev->mode_config.max_height = YRES_MAX;
vgdev->ddev->mode_config.fb_modifiers_not_supported = true;
for (i = 0 ; i < vgdev->num_scanouts; ++i)
vgdev_output_init(vgdev, i);
ret = drm_vblank_init(vgdev->ddev, vgdev->num_scanouts);
if (ret)
return ret;
drm_mode_config_reset(vgdev->ddev);
return 0;
}
void virtio_gpu_modeset_fini(struct virtio_gpu_device *vgdev)
{
int i;
if (!vgdev->num_scanouts)
return;
for (i = 0 ; i < vgdev->num_scanouts; ++i)
drm_edid_free(vgdev->outputs[i].drm_edid);
}
@@ -0,0 +1,262 @@
/*
* Copyright (C) 2015 Red Hat, Inc.
* All Rights Reserved.
*
* Authors:
* Dave Airlie <airlied@redhat.com>
* Gerd Hoffmann <kraxel@redhat.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice (including the next
* paragraph) shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* VA LINUX SYSTEMS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
* OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*/
#include <linux/aperture.h>
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/poll.h>
#include <linux/vgaarb.h>
#include <linux/wait.h>
#include <drm/clients/drm_client_setup.h>
#include <drm/drm.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_drv.h>
#include <drm/drm_fbdev_shmem.h>
#include <drm/drm_file.h>
#include <drm/drm_print.h>
#include "virtgpu_drv.h"
#define PCI_DEVICE_ID_VIRTIO_GPU 0x1050
static const struct drm_driver driver;
static int virtio_gpu_modeset = -1;
MODULE_PARM_DESC(modeset, "Disable/Enable modesetting");
module_param_named(modeset, virtio_gpu_modeset, int, 0400);
static int virtio_gpu_pci_quirk(struct drm_device *dev)
{
struct pci_dev *pdev = to_pci_dev(dev->dev);
const char *pname = dev_name(&pdev->dev);
bool vga = pci_is_vga(pdev);
int ret;
DRM_INFO("pci: %s detected at %s\n",
vga ? "virtio-vga" : "virtio-gpu-pci",
pname);
if (vga) {
ret = aperture_remove_conflicting_pci_devices(pdev, driver.name);
if (ret)
return ret;
}
return 0;
}
static int virtio_gpu_probe(struct virtio_device *vdev)
{
struct drm_device *dev;
int ret;
if (drm_firmware_drivers_only() && virtio_gpu_modeset == -1)
return -EINVAL;
if (virtio_gpu_modeset == 0)
return -EINVAL;
/*
* The virtio-gpu device is a virtual device that doesn't have DMA
* ops assigned to it, nor DMA mask set and etc. Its parent device
* is actual GPU device we want to use it for the DRM's device in
* order to benefit from using generic DRM APIs.
*/
dev = drm_dev_alloc(&driver, vdev->dev.parent);
if (IS_ERR(dev))
return PTR_ERR(dev);
vdev->priv = dev;
if (dev_is_pci(vdev->dev.parent)) {
ret = virtio_gpu_pci_quirk(dev);
if (ret)
goto err_free;
}
dma_set_max_seg_size(dev->dev, dma_max_mapping_size(dev->dev) ?: UINT_MAX);
ret = virtio_gpu_init(vdev, dev);
if (ret)
goto err_free;
ret = drm_dev_register(dev, 0);
if (ret)
goto err_deinit;
drm_client_setup(vdev->priv, NULL);
return 0;
err_deinit:
virtio_gpu_deinit(dev);
err_free:
drm_dev_put(dev);
return ret;
}
static void virtio_gpu_remove(struct virtio_device *vdev)
{
struct drm_device *dev = vdev->priv;
drm_dev_unplug(dev);
if (drm_core_check_feature(dev, DRIVER_ATOMIC))
drm_atomic_helper_shutdown(dev);
virtio_gpu_deinit(dev);
drm_dev_put(dev);
}
static void virtio_gpu_shutdown(struct virtio_device *vdev)
{
struct drm_device *dev = vdev->priv;
/* stop talking to the device */
drm_dev_unplug(dev);
}
static void virtio_gpu_config_changed(struct virtio_device *vdev)
{
struct drm_device *dev = vdev->priv;
struct virtio_gpu_device *vgdev = dev->dev_private;
schedule_work(&vgdev->config_changed_work);
}
static struct virtio_device_id id_table[] = {
{ VIRTIO_ID_GPU, VIRTIO_DEV_ANY_ID },
{ 0 },
};
static unsigned int features[] = {
#ifdef __LITTLE_ENDIAN
/*
* Gallium command stream send by virgl is native endian.
* Because of that we only support little endian guests on
* little endian hosts.
*/
VIRTIO_GPU_F_VIRGL,
#endif
VIRTIO_GPU_F_EDID,
VIRTIO_GPU_F_RESOURCE_UUID,
VIRTIO_GPU_F_RESOURCE_BLOB,
VIRTIO_GPU_F_CONTEXT_INIT,
VIRTIO_GPU_F_BLOB_ALIGNMENT,
};
static struct virtio_driver virtio_gpu_driver = {
.feature_table = features,
.feature_table_size = ARRAY_SIZE(features),
.driver.name = KBUILD_MODNAME,
.id_table = id_table,
.probe = virtio_gpu_probe,
.remove = virtio_gpu_remove,
.shutdown = virtio_gpu_shutdown,
.config_changed = virtio_gpu_config_changed
};
static int __init virtio_gpu_driver_init(void)
{
struct pci_dev *pdev;
int ret;
pdev = pci_get_device(PCI_VENDOR_ID_REDHAT_QUMRANET,
PCI_DEVICE_ID_VIRTIO_GPU,
NULL);
if (pdev && pci_is_vga(pdev)) {
ret = vga_get_interruptible(pdev,
VGA_RSRC_LEGACY_IO | VGA_RSRC_LEGACY_MEM);
if (ret) {
pci_dev_put(pdev);
return ret;
}
}
ret = register_virtio_driver(&virtio_gpu_driver);
if (pdev) {
if (pci_is_vga(pdev))
vga_put(pdev,
VGA_RSRC_LEGACY_IO | VGA_RSRC_LEGACY_MEM);
pci_dev_put(pdev);
}
return ret;
}
static void __exit virtio_gpu_driver_exit(void)
{
unregister_virtio_driver(&virtio_gpu_driver);
}
module_init(virtio_gpu_driver_init);
module_exit(virtio_gpu_driver_exit);
MODULE_DEVICE_TABLE(virtio, id_table);
MODULE_DESCRIPTION("Virtio GPU driver");
MODULE_LICENSE("GPL and additional rights");
MODULE_AUTHOR("Dave Airlie <airlied@redhat.com>");
MODULE_AUTHOR("Gerd Hoffmann <kraxel@redhat.com>");
MODULE_AUTHOR("Alon Levy");
DEFINE_DRM_GEM_FOPS(virtio_gpu_driver_fops);
static const struct drm_driver driver = {
/*
* If KMS is disabled DRIVER_MODESET and DRIVER_ATOMIC are masked
* out via drm_device::driver_features:
*/
.driver_features = DRIVER_MODESET | DRIVER_GEM | DRIVER_RENDER | DRIVER_ATOMIC |
DRIVER_SYNCOBJ | DRIVER_SYNCOBJ_TIMELINE | DRIVER_CURSOR_HOTSPOT,
.open = virtio_gpu_driver_open,
.postclose = virtio_gpu_driver_postclose,
.dumb_create = virtio_gpu_mode_dumb_create,
DRM_FBDEV_SHMEM_DRIVER_OPS,
#if defined(CONFIG_DEBUG_FS)
.debugfs_init = virtio_gpu_debugfs_init,
#endif
.gem_prime_import = virtgpu_gem_prime_import,
.gem_prime_import_sg_table = virtgpu_gem_prime_import_sg_table,
.gem_create_object = virtio_gpu_create_object,
.fops = &virtio_gpu_driver_fops,
.ioctls = virtio_gpu_ioctls,
.num_ioctls = DRM_VIRTIO_NUM_IOCTLS,
.name = DRIVER_NAME,
.desc = DRIVER_DESC,
.major = DRIVER_MAJOR,
.minor = DRIVER_MINOR,
.patchlevel = DRIVER_PATCHLEVEL,
.release = virtio_gpu_release,
};
@@ -0,0 +1,520 @@
/*
* Copyright (C) 2015 Red Hat, Inc.
* All Rights Reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice (including the
* next paragraph) shall be included in all copies or substantial
* portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE COPYRIGHT OWNER(S) AND/OR ITS SUPPLIERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef VIRTIO_DRV_H
#define VIRTIO_DRV_H
#include <linux/dma-direction.h>
#include <linux/virtio.h>
#include <linux/virtio_ids.h>
#include <linux/virtio_config.h>
#include <linux/virtio_gpu.h>
#include <drm/drm_atomic.h>
#include <drm/drm_drv.h>
#include <drm/drm_encoder.h>
#include <drm/drm_fourcc.h>
#include <drm/drm_framebuffer.h>
#include <drm/drm_gem.h>
#include <drm/drm_gem_shmem_helper.h>
#include <drm/drm_ioctl.h>
#include <drm/drm_probe_helper.h>
#include <drm/virtgpu_drm.h>
#define DRIVER_NAME "virtio_gpu"
#define DRIVER_DESC "virtio GPU"
#define DRIVER_MAJOR 0
#define DRIVER_MINOR 1
#define DRIVER_PATCHLEVEL 0
#define STATE_INITIALIZING 0
#define STATE_OK 1
#define STATE_ERR 2
#define MAX_CAPSET_ID 63
#define MAX_RINGS 64
/* See virtio_gpu_ctx_create. One additional character for NULL terminator. */
#define DEBUG_NAME_MAX_LEN 65
struct virtio_gpu_object_params {
unsigned long size;
bool dumb;
/* 3d */
bool virgl;
bool blob;
/* classic resources only */
uint32_t format;
uint32_t width;
uint32_t height;
uint32_t target;
uint32_t bind;
uint32_t depth;
uint32_t array_size;
uint32_t last_level;
uint32_t nr_samples;
uint32_t flags;
/* blob resources only */
uint32_t ctx_id;
uint32_t blob_mem;
uint32_t blob_flags;
uint64_t blob_id;
uint32_t blob_hints;
};
struct virtio_gpu_object {
struct drm_gem_shmem_object base;
struct sg_table *sgt;
uint32_t hw_res_handle;
bool dumb;
bool created;
bool attached;
bool host3d_blob, guest_blob;
uint32_t blob_mem, blob_flags;
int uuid_state;
uuid_t uuid;
};
#define gem_to_virtio_gpu_obj(gobj) \
container_of((gobj), struct virtio_gpu_object, base.base)
struct virtio_gpu_object_shmem {
struct virtio_gpu_object base;
};
struct virtio_gpu_object_vram {
struct virtio_gpu_object base;
uint32_t map_state;
uint32_t map_info;
struct drm_mm_node vram_node;
};
#define to_virtio_gpu_shmem(virtio_gpu_object) \
container_of((virtio_gpu_object), struct virtio_gpu_object_shmem, base)
#define to_virtio_gpu_vram(virtio_gpu_object) \
container_of((virtio_gpu_object), struct virtio_gpu_object_vram, base)
struct virtio_gpu_object_array {
struct ww_acquire_ctx ticket;
struct list_head next;
u32 nents, total;
struct drm_gem_object *objs[] __counted_by(total);
};
struct virtio_gpu_vbuffer;
struct virtio_gpu_device;
typedef void (*virtio_gpu_resp_cb)(struct virtio_gpu_device *vgdev,
struct virtio_gpu_vbuffer *vbuf);
struct virtio_gpu_fence_driver {
atomic64_t last_fence_id;
uint64_t current_fence_id;
uint64_t context;
struct list_head fences;
spinlock_t lock;
};
struct virtio_gpu_fence_event {
struct drm_pending_event base;
struct drm_event event;
};
struct virtio_gpu_fence {
struct dma_fence f;
uint32_t ring_idx;
uint64_t fence_id;
bool emit_fence_info;
struct virtio_gpu_fence_event *e;
struct virtio_gpu_fence_driver *drv;
struct list_head node;
};
struct virtio_gpu_vbuffer {
char *buf;
int size;
void *data_buf;
uint32_t data_size;
char *resp_buf;
int resp_size;
virtio_gpu_resp_cb resp_cb;
void *resp_cb_data;
struct virtio_gpu_object_array *objs;
struct list_head list;
uint32_t seqno;
};
struct virtio_gpu_output {
int index;
struct drm_crtc crtc;
struct drm_connector conn;
struct drm_encoder enc;
struct virtio_gpu_display_one info;
struct virtio_gpu_update_cursor cursor;
const struct drm_edid *drm_edid;
int cur_x;
int cur_y;
bool needs_modeset;
};
#define drm_crtc_to_virtio_gpu_output(x) \
container_of(x, struct virtio_gpu_output, crtc)
struct virtio_gpu_framebuffer {
struct drm_framebuffer base;
struct virtio_gpu_fence *fence;
};
#define to_virtio_gpu_framebuffer(x) \
container_of(x, struct virtio_gpu_framebuffer, base)
struct virtio_gpu_plane_state {
struct drm_plane_state base;
struct virtio_gpu_fence *fence;
};
#define to_virtio_gpu_plane_state(x) \
container_of(x, struct virtio_gpu_plane_state, base)
struct virtio_gpu_queue {
struct virtqueue *vq;
spinlock_t qlock;
wait_queue_head_t ack_queue;
struct work_struct dequeue_work;
uint32_t seqno;
};
struct virtio_gpu_drv_capset {
uint32_t id;
uint32_t max_version;
uint32_t max_size;
};
struct virtio_gpu_drv_cap_cache {
struct list_head head;
void *caps_cache;
uint32_t id;
uint32_t version;
uint32_t size;
atomic_t is_valid;
};
struct virtio_gpu_device {
struct drm_device *ddev;
struct virtio_device *vdev;
struct virtio_gpu_output outputs[VIRTIO_GPU_MAX_SCANOUTS];
uint32_t num_scanouts;
struct virtio_gpu_queue ctrlq;
struct virtio_gpu_queue cursorq;
struct kmem_cache *vbufs;
atomic_t pending_commands;
struct ida resource_ida;
wait_queue_head_t resp_wq;
/* current display info */
spinlock_t display_info_lock;
bool display_info_pending;
struct virtio_gpu_fence_driver fence_drv;
struct ida ctx_id_ida;
bool has_virgl_3d;
bool has_edid;
bool has_indirect;
bool has_resource_assign_uuid;
bool has_resource_blob;
bool has_host_visible;
bool has_context_init;
bool has_blob_alignment;
struct virtio_shm_region host_visible_region;
struct drm_mm host_visible_mm;
struct work_struct config_changed_work;
struct work_struct obj_free_work;
spinlock_t obj_free_lock;
struct list_head obj_free_list;
struct virtio_gpu_drv_capset *capsets;
uint32_t num_capsets;
uint64_t capset_id_mask;
struct list_head cap_cache;
uint32_t blob_alignment;
/* protects uuid state when exporting */
spinlock_t resource_export_lock;
/* protects map state and host_visible_mm */
spinlock_t host_visible_lock;
};
struct virtio_gpu_fpriv {
uint32_t ctx_id;
uint32_t context_init;
bool context_created;
uint32_t num_rings;
uint64_t base_fence_ctx;
uint64_t ring_idx_mask;
struct mutex context_lock;
char debug_name[DEBUG_NAME_MAX_LEN];
bool explicit_debug_name;
};
/* virtgpu_ioctl.c */
#define DRM_VIRTIO_NUM_IOCTLS 12
extern struct drm_ioctl_desc virtio_gpu_ioctls[DRM_VIRTIO_NUM_IOCTLS];
void virtio_gpu_create_context(struct drm_device *dev, struct drm_file *file);
/* virtgpu_kms.c */
int virtio_gpu_init(struct virtio_device *vdev, struct drm_device *dev);
void virtio_gpu_deinit(struct drm_device *dev);
void virtio_gpu_release(struct drm_device *dev);
int virtio_gpu_driver_open(struct drm_device *dev, struct drm_file *file);
void virtio_gpu_driver_postclose(struct drm_device *dev, struct drm_file *file);
/* virtgpu_gem.c */
int virtio_gpu_gem_object_open(struct drm_gem_object *obj,
struct drm_file *file);
void virtio_gpu_gem_object_close(struct drm_gem_object *obj,
struct drm_file *file);
int virtio_gpu_mode_dumb_create(struct drm_file *file_priv,
struct drm_device *dev,
struct drm_mode_create_dumb *args);
struct virtio_gpu_object_array *virtio_gpu_panic_array_alloc(void);
struct virtio_gpu_object_array *virtio_gpu_array_alloc(u32 nents);
struct virtio_gpu_object_array*
virtio_gpu_array_from_handles(struct drm_file *drm_file, u32 *handles, u32 nents);
void virtio_gpu_array_add_obj(struct virtio_gpu_object_array *objs,
struct drm_gem_object *obj);
int virtio_gpu_array_lock_resv(struct virtio_gpu_object_array *objs);
int virtio_gpu_lock_one_resv_uninterruptible(struct virtio_gpu_object_array *objs);
void virtio_gpu_array_unlock_resv(struct virtio_gpu_object_array *objs);
void virtio_gpu_array_add_fence(struct virtio_gpu_object_array *objs,
struct dma_fence *fence);
void virtio_gpu_array_put_free(struct virtio_gpu_object_array *objs);
void virtio_gpu_array_put_free_delayed(struct virtio_gpu_device *vgdev,
struct virtio_gpu_object_array *objs);
void virtio_gpu_array_put_free_work(struct work_struct *work);
/* virtgpu_vq.c */
int virtio_gpu_alloc_vbufs(struct virtio_gpu_device *vgdev);
void virtio_gpu_free_vbufs(struct virtio_gpu_device *vgdev);
void virtio_gpu_cmd_create_resource(struct virtio_gpu_device *vgdev,
struct virtio_gpu_object *bo,
struct virtio_gpu_object_params *params,
struct virtio_gpu_object_array *objs,
struct virtio_gpu_fence *fence);
void virtio_gpu_cmd_unref_resource(struct virtio_gpu_device *vgdev,
struct virtio_gpu_object *bo);
int virtio_gpu_panic_cmd_transfer_to_host_2d(struct virtio_gpu_device *vgdev,
uint64_t offset,
uint32_t width, uint32_t height,
uint32_t x, uint32_t y,
struct virtio_gpu_object_array *objs);
void virtio_gpu_cmd_transfer_to_host_2d(struct virtio_gpu_device *vgdev,
uint64_t offset,
uint32_t width, uint32_t height,
uint32_t x, uint32_t y,
struct virtio_gpu_object_array *objs,
struct virtio_gpu_fence *fence);
void virtio_gpu_panic_cmd_resource_flush(struct virtio_gpu_device *vgdev,
uint32_t resource_id,
uint32_t x, uint32_t y,
uint32_t width, uint32_t height);
void virtio_gpu_cmd_resource_flush(struct virtio_gpu_device *vgdev,
uint32_t resource_id,
uint32_t x, uint32_t y,
uint32_t width, uint32_t height,
struct virtio_gpu_object_array *objs,
struct virtio_gpu_fence *fence);
void virtio_gpu_cmd_set_scanout(struct virtio_gpu_device *vgdev,
uint32_t scanout_id, uint32_t resource_id,
uint32_t width, uint32_t height,
uint32_t x, uint32_t y);
void virtio_gpu_object_attach(struct virtio_gpu_device *vgdev,
struct virtio_gpu_object *obj,
struct virtio_gpu_mem_entry *ents,
unsigned int nents);
void virtio_gpu_object_detach(struct virtio_gpu_device *vgdev,
struct virtio_gpu_object *obj,
struct virtio_gpu_fence *fence);
int virtio_gpu_detach_object_fenced(struct virtio_gpu_object *bo);
void virtio_gpu_cursor_ping(struct virtio_gpu_device *vgdev,
struct virtio_gpu_output *output);
int virtio_gpu_cmd_get_display_info(struct virtio_gpu_device *vgdev);
int virtio_gpu_cmd_get_capset_info(struct virtio_gpu_device *vgdev, int idx);
int virtio_gpu_cmd_get_capset(struct virtio_gpu_device *vgdev,
int idx, int version,
struct virtio_gpu_drv_cap_cache **cache_p);
int virtio_gpu_cmd_get_edids(struct virtio_gpu_device *vgdev);
void virtio_gpu_cmd_context_create(struct virtio_gpu_device *vgdev, uint32_t id,
uint32_t context_init, uint32_t nlen,
const char *name);
void virtio_gpu_cmd_context_destroy(struct virtio_gpu_device *vgdev,
uint32_t id);
void virtio_gpu_cmd_context_attach_resource(struct virtio_gpu_device *vgdev,
uint32_t ctx_id,
struct virtio_gpu_object_array *objs);
void virtio_gpu_cmd_context_detach_resource(struct virtio_gpu_device *vgdev,
uint32_t ctx_id,
struct virtio_gpu_object_array *objs);
void virtio_gpu_cmd_submit(struct virtio_gpu_device *vgdev,
void *data, uint32_t data_size,
uint32_t ctx_id,
struct virtio_gpu_object_array *objs,
struct virtio_gpu_fence *fence);
void virtio_gpu_cmd_transfer_from_host_3d(struct virtio_gpu_device *vgdev,
uint32_t ctx_id,
uint64_t offset, uint32_t level,
uint32_t stride,
uint32_t layer_stride,
struct drm_virtgpu_3d_box *box,
struct virtio_gpu_object_array *objs,
struct virtio_gpu_fence *fence);
void virtio_gpu_cmd_transfer_to_host_3d(struct virtio_gpu_device *vgdev,
uint32_t ctx_id,
uint64_t offset, uint32_t level,
uint32_t stride,
uint32_t layer_stride,
struct drm_virtgpu_3d_box *box,
struct virtio_gpu_object_array *objs,
struct virtio_gpu_fence *fence);
void
virtio_gpu_cmd_resource_create_3d(struct virtio_gpu_device *vgdev,
struct virtio_gpu_object *bo,
struct virtio_gpu_object_params *params,
struct virtio_gpu_object_array *objs,
struct virtio_gpu_fence *fence);
void virtio_gpu_ctrl_ack(struct virtqueue *vq);
void virtio_gpu_cursor_ack(struct virtqueue *vq);
void virtio_gpu_dequeue_ctrl_func(struct work_struct *work);
void virtio_gpu_dequeue_cursor_func(struct work_struct *work);
void virtio_gpu_panic_notify(struct virtio_gpu_device *vgdev);
void virtio_gpu_notify(struct virtio_gpu_device *vgdev);
int
virtio_gpu_cmd_resource_assign_uuid(struct virtio_gpu_device *vgdev,
struct virtio_gpu_object_array *objs);
int virtio_gpu_cmd_map(struct virtio_gpu_device *vgdev,
struct virtio_gpu_object_array *objs, uint64_t offset);
void virtio_gpu_cmd_unmap(struct virtio_gpu_device *vgdev,
struct virtio_gpu_object *bo);
void
virtio_gpu_cmd_resource_create_blob(struct virtio_gpu_device *vgdev,
struct virtio_gpu_object *bo,
struct virtio_gpu_object_params *params,
struct virtio_gpu_mem_entry *ents,
uint32_t nents);
void
virtio_gpu_cmd_set_scanout_blob(struct virtio_gpu_device *vgdev,
uint32_t scanout_id,
struct virtio_gpu_object *bo,
struct drm_framebuffer *fb,
uint32_t width, uint32_t height,
uint32_t x, uint32_t y);
/* virtgpu_display.c */
int virtio_gpu_modeset_init(struct virtio_gpu_device *vgdev);
void virtio_gpu_modeset_fini(struct virtio_gpu_device *vgdev);
/* virtgpu_plane.c */
uint32_t virtio_gpu_translate_format(uint32_t drm_fourcc);
struct drm_plane *virtio_gpu_plane_init(struct virtio_gpu_device *vgdev,
enum drm_plane_type type,
int index);
/* virtgpu_fence.c */
struct virtio_gpu_fence *virtio_gpu_fence_alloc(struct virtio_gpu_device *vgdev,
uint64_t base_fence_ctx,
uint32_t ring_idx);
void virtio_gpu_fence_emit(struct virtio_gpu_device *vgdev,
struct virtio_gpu_ctrl_hdr *cmd_hdr,
struct virtio_gpu_fence *fence);
void virtio_gpu_fence_event_process(struct virtio_gpu_device *vdev,
u64 fence_id);
/* virtgpu_object.c */
void virtio_gpu_cleanup_object(struct virtio_gpu_object *bo);
struct drm_gem_object *virtio_gpu_create_object(struct drm_device *dev,
size_t size);
int virtio_gpu_object_create(struct virtio_gpu_device *vgdev,
struct virtio_gpu_object_params *params,
struct virtio_gpu_object **bo_ptr,
struct virtio_gpu_fence *fence);
bool virtio_gpu_is_shmem(struct virtio_gpu_object *bo);
int virtio_gpu_resource_id_get(struct virtio_gpu_device *vgdev,
uint32_t *resid);
/* virtgpu_prime.c */
int virtio_gpu_resource_assign_uuid(struct virtio_gpu_device *vgdev,
struct virtio_gpu_object *bo);
struct dma_buf *virtgpu_gem_prime_export(struct drm_gem_object *obj,
int flags);
struct drm_gem_object *virtgpu_gem_prime_import(struct drm_device *dev,
struct dma_buf *buf);
struct drm_gem_object *virtgpu_gem_prime_import_sg_table(
struct drm_device *dev, struct dma_buf_attachment *attach,
struct sg_table *sgt);
int virtgpu_dma_buf_import_sgt(struct virtio_gpu_mem_entry **ents,
unsigned int *nents,
struct virtio_gpu_object *bo,
struct dma_buf_attachment *attach);
/* virtgpu_debugfs.c */
void virtio_gpu_debugfs_init(struct drm_minor *minor);
/* virtgpu_vram.c */
bool virtio_gpu_is_vram(struct virtio_gpu_object *bo);
int virtio_gpu_vram_create(struct virtio_gpu_device *vgdev,
struct virtio_gpu_object_params *params,
struct virtio_gpu_object **bo_ptr);
struct sg_table *virtio_gpu_vram_map_dma_buf(struct virtio_gpu_object *bo,
struct device *dev,
enum dma_data_direction dir);
void virtio_gpu_vram_unmap_dma_buf(struct device *dev,
struct sg_table *sgt,
enum dma_data_direction dir);
void virtio_gpu_vram_map_deferred(struct virtio_gpu_object_vram *vram);
/* virtgpu_submit.c */
int virtio_gpu_execbuffer_ioctl(struct drm_device *dev, void *data,
struct drm_file *file);
#endif
@@ -0,0 +1,158 @@
/*
* Copyright (C) 2015 Red Hat, Inc.
* All Rights Reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice (including the
* next paragraph) shall be included in all copies or substantial
* portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE COPYRIGHT OWNER(S) AND/OR ITS SUPPLIERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include <trace/events/dma_fence.h>
#include "virtgpu_drv.h"
#define to_virtio_gpu_fence(x) \
container_of(x, struct virtio_gpu_fence, f)
static const char *virtio_gpu_get_driver_name(struct dma_fence *f)
{
return "virtio_gpu";
}
static const char *virtio_gpu_get_timeline_name(struct dma_fence *f)
{
return "controlq";
}
static bool virtio_gpu_fence_signaled(struct dma_fence *f)
{
/* leaked fence outside driver before completing
* initialization with virtio_gpu_fence_emit.
*/
WARN_ON_ONCE(f->seqno == 0);
return false;
}
static const struct dma_fence_ops virtio_gpu_fence_ops = {
.get_driver_name = virtio_gpu_get_driver_name,
.get_timeline_name = virtio_gpu_get_timeline_name,
.signaled = virtio_gpu_fence_signaled,
};
struct virtio_gpu_fence *virtio_gpu_fence_alloc(struct virtio_gpu_device *vgdev,
uint64_t base_fence_ctx,
uint32_t ring_idx)
{
uint64_t fence_context = base_fence_ctx + ring_idx;
struct virtio_gpu_fence_driver *drv = &vgdev->fence_drv;
struct virtio_gpu_fence *fence = kzalloc_obj(struct virtio_gpu_fence);
if (!fence)
return fence;
fence->drv = drv;
fence->ring_idx = ring_idx;
fence->emit_fence_info = !(base_fence_ctx == drv->context);
/* This only partially initializes the fence because the seqno is
* unknown yet. The fence must not be used outside of the driver
* until virtio_gpu_fence_emit is called.
*/
dma_fence_init(&fence->f, &virtio_gpu_fence_ops, &drv->lock,
fence_context, 0);
return fence;
}
void virtio_gpu_fence_emit(struct virtio_gpu_device *vgdev,
struct virtio_gpu_ctrl_hdr *cmd_hdr,
struct virtio_gpu_fence *fence)
{
struct virtio_gpu_fence_driver *drv = &vgdev->fence_drv;
unsigned long irq_flags;
spin_lock_irqsave(&drv->lock, irq_flags);
fence->fence_id = fence->f.seqno = ++drv->current_fence_id;
dma_fence_get(&fence->f);
list_add_tail(&fence->node, &drv->fences);
spin_unlock_irqrestore(&drv->lock, irq_flags);
trace_dma_fence_emit(&fence->f);
cmd_hdr->flags |= cpu_to_le32(VIRTIO_GPU_FLAG_FENCE);
cmd_hdr->fence_id = cpu_to_le64(fence->fence_id);
/* Only currently defined fence param. */
if (fence->emit_fence_info) {
cmd_hdr->flags |=
cpu_to_le32(VIRTIO_GPU_FLAG_INFO_RING_IDX);
cmd_hdr->ring_idx = (u8)fence->ring_idx;
}
}
void virtio_gpu_fence_event_process(struct virtio_gpu_device *vgdev,
u64 fence_id)
{
struct virtio_gpu_fence_driver *drv = &vgdev->fence_drv;
struct virtio_gpu_fence *signaled, *curr, *tmp;
unsigned long irq_flags;
spin_lock_irqsave(&drv->lock, irq_flags);
atomic64_set(&vgdev->fence_drv.last_fence_id, fence_id);
list_for_each_entry_safe(curr, tmp, &drv->fences, node) {
if (fence_id != curr->fence_id)
continue;
signaled = curr;
/*
* Signal any fences with a strictly smaller sequence number
* than the current signaled fence.
*/
list_for_each_entry_safe(curr, tmp, &drv->fences, node) {
/* dma-fence contexts must match */
if (signaled->f.context != curr->f.context)
continue;
if (!dma_fence_is_later(&signaled->f, &curr->f))
continue;
dma_fence_signal_locked(&curr->f);
if (curr->e) {
drm_send_event(vgdev->ddev, &curr->e->base);
curr->e = NULL;
}
list_del(&curr->node);
dma_fence_put(&curr->f);
}
dma_fence_signal_locked(&signaled->f);
if (signaled->e) {
drm_send_event(vgdev->ddev, &signaled->e->base);
signaled->e = NULL;
}
list_del(&signaled->node);
dma_fence_put(&signaled->f);
break;
}
spin_unlock_irqrestore(&drv->lock, irq_flags);
}
@@ -0,0 +1,317 @@
/*
* Copyright (C) 2015 Red Hat, Inc.
* All Rights Reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice (including the
* next paragraph) shall be included in all copies or substantial
* portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE COPYRIGHT OWNER(S) AND/OR ITS SUPPLIERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include <drm/drm_file.h>
#include <drm/drm_fourcc.h>
#include "virtgpu_drv.h"
static int virtio_gpu_gem_create(struct drm_file *file,
struct drm_device *dev,
struct virtio_gpu_object_params *params,
struct drm_gem_object **obj_p,
uint32_t *handle_p)
{
struct virtio_gpu_device *vgdev = dev->dev_private;
struct virtio_gpu_object *obj;
int ret;
u32 handle;
ret = virtio_gpu_object_create(vgdev, params, &obj, NULL);
if (ret < 0)
return ret;
ret = drm_gem_handle_create(file, &obj->base.base, &handle);
if (ret) {
drm_gem_object_release(&obj->base.base);
return ret;
}
*obj_p = &obj->base.base;
/* drop reference from allocate - handle holds it now */
drm_gem_object_put(&obj->base.base);
*handle_p = handle;
return 0;
}
int virtio_gpu_mode_dumb_create(struct drm_file *file_priv,
struct drm_device *dev,
struct drm_mode_create_dumb *args)
{
struct drm_gem_object *gobj;
struct virtio_gpu_object_params params = { 0 };
struct virtio_gpu_device *vgdev = dev->dev_private;
int ret;
uint32_t pitch;
if (args->bpp != 32)
return -EINVAL;
pitch = args->width * 4;
args->size = pitch * args->height;
args->size = ALIGN(args->size, PAGE_SIZE);
params.format = virtio_gpu_translate_format(DRM_FORMAT_HOST_XRGB8888);
params.width = args->width;
params.height = args->height;
params.size = args->size;
params.dumb = true;
if (vgdev->has_resource_blob && !vgdev->has_virgl_3d) {
params.blob_mem = VIRTGPU_BLOB_MEM_GUEST;
params.blob_flags = VIRTGPU_BLOB_FLAG_USE_SHAREABLE;
params.blob = true;
}
ret = virtio_gpu_gem_create(file_priv, dev, &params, &gobj,
&args->handle);
if (ret)
goto fail;
args->pitch = pitch;
return ret;
fail:
return ret;
}
int virtio_gpu_gem_object_open(struct drm_gem_object *obj,
struct drm_file *file)
{
struct virtio_gpu_device *vgdev = obj->dev->dev_private;
struct virtio_gpu_fpriv *vfpriv = file->driver_priv;
struct virtio_gpu_object_array *objs;
if (!vgdev->has_virgl_3d)
goto out_notify;
/* the context might still be missing when the first ioctl is
* DRM_IOCTL_MODE_CREATE_DUMB or DRM_IOCTL_PRIME_FD_TO_HANDLE
*/
if (!vgdev->has_context_init)
virtio_gpu_create_context(obj->dev, file);
if (vfpriv->context_created) {
objs = virtio_gpu_array_alloc(1);
if (!objs)
return -ENOMEM;
virtio_gpu_array_add_obj(objs, obj);
virtio_gpu_cmd_context_attach_resource(vgdev, vfpriv->ctx_id, objs);
}
out_notify:
virtio_gpu_notify(vgdev);
return 0;
}
void virtio_gpu_gem_object_close(struct drm_gem_object *obj,
struct drm_file *file)
{
struct virtio_gpu_device *vgdev = obj->dev->dev_private;
struct virtio_gpu_fpriv *vfpriv = file->driver_priv;
struct virtio_gpu_object_array *objs;
if (!vgdev->has_virgl_3d)
return;
if (vfpriv->context_created) {
objs = virtio_gpu_array_alloc(1);
if (!objs)
return;
virtio_gpu_array_add_obj(objs, obj);
virtio_gpu_cmd_context_detach_resource(vgdev, vfpriv->ctx_id,
objs);
}
virtio_gpu_notify(vgdev);
}
/* For drm panic */
struct virtio_gpu_object_array *virtio_gpu_panic_array_alloc(void)
{
struct virtio_gpu_object_array *objs;
objs = kmalloc_obj(struct virtio_gpu_object_array, GFP_ATOMIC);
if (!objs)
return NULL;
objs->nents = 0;
objs->total = 1;
return objs;
}
struct virtio_gpu_object_array *virtio_gpu_array_alloc(u32 nents)
{
struct virtio_gpu_object_array *objs;
objs = kmalloc_flex(*objs, objs, nents);
if (!objs)
return NULL;
objs->nents = 0;
objs->total = nents;
return objs;
}
static void virtio_gpu_array_free(struct virtio_gpu_object_array *objs)
{
kfree(objs);
}
struct virtio_gpu_object_array*
virtio_gpu_array_from_handles(struct drm_file *drm_file, u32 *handles, u32 nents)
{
struct virtio_gpu_object_array *objs;
u32 i;
objs = virtio_gpu_array_alloc(nents);
if (!objs)
return NULL;
for (i = 0; i < nents; i++) {
objs->objs[i] = drm_gem_object_lookup(drm_file, handles[i]);
if (!objs->objs[i]) {
objs->nents = i;
virtio_gpu_array_put_free(objs);
return NULL;
}
}
objs->nents = i;
return objs;
}
void virtio_gpu_array_add_obj(struct virtio_gpu_object_array *objs,
struct drm_gem_object *obj)
{
if (WARN_ON_ONCE(objs->nents == objs->total))
return;
drm_gem_object_get(obj);
objs->objs[objs->nents] = obj;
objs->nents++;
}
int virtio_gpu_array_lock_resv(struct virtio_gpu_object_array *objs)
{
unsigned int i;
int ret;
if (objs->nents == 1) {
ret = dma_resv_lock_interruptible(objs->objs[0]->resv, NULL);
} else {
ret = drm_gem_lock_reservations(objs->objs, objs->nents,
&objs->ticket);
}
if (ret)
return ret;
for (i = 0; i < objs->nents; ++i) {
ret = dma_resv_reserve_fences(objs->objs[i]->resv, 1);
if (ret) {
virtio_gpu_array_unlock_resv(objs);
return ret;
}
}
return ret;
}
int virtio_gpu_lock_one_resv_uninterruptible(struct virtio_gpu_object_array *objs)
{
int ret;
if (objs->nents != 1)
return -EINVAL;
dma_resv_lock(objs->objs[0]->resv, NULL);
ret = dma_resv_reserve_fences(objs->objs[0]->resv, 1);
if (ret) {
virtio_gpu_array_unlock_resv(objs);
return ret;
}
return 0;
}
void virtio_gpu_array_unlock_resv(struct virtio_gpu_object_array *objs)
{
if (objs->nents == 1) {
dma_resv_unlock(objs->objs[0]->resv);
} else {
drm_gem_unlock_reservations(objs->objs, objs->nents,
&objs->ticket);
}
}
void virtio_gpu_array_add_fence(struct virtio_gpu_object_array *objs,
struct dma_fence *fence)
{
int i;
for (i = 0; i < objs->nents; i++)
dma_resv_add_fence(objs->objs[i]->resv, fence,
DMA_RESV_USAGE_WRITE);
}
void virtio_gpu_array_put_free(struct virtio_gpu_object_array *objs)
{
u32 i;
if (!objs)
return;
for (i = 0; i < objs->nents; i++)
drm_gem_object_put(objs->objs[i]);
virtio_gpu_array_free(objs);
}
void virtio_gpu_array_put_free_delayed(struct virtio_gpu_device *vgdev,
struct virtio_gpu_object_array *objs)
{
spin_lock(&vgdev->obj_free_lock);
list_add_tail(&objs->next, &vgdev->obj_free_list);
spin_unlock(&vgdev->obj_free_lock);
schedule_work(&vgdev->obj_free_work);
}
void virtio_gpu_array_put_free_work(struct work_struct *work)
{
struct virtio_gpu_device *vgdev =
container_of(work, struct virtio_gpu_device, obj_free_work);
struct virtio_gpu_object_array *objs;
spin_lock(&vgdev->obj_free_lock);
while (!list_empty(&vgdev->obj_free_list)) {
objs = list_first_entry(&vgdev->obj_free_list,
struct virtio_gpu_object_array, next);
list_del(&objs->next);
spin_unlock(&vgdev->obj_free_lock);
virtio_gpu_array_put_free(objs);
spin_lock(&vgdev->obj_free_lock);
}
spin_unlock(&vgdev->obj_free_lock);
}
@@ -0,0 +1,746 @@
/*
* Copyright (C) 2015 Red Hat, Inc.
* All Rights Reserved.
*
* Authors:
* Dave Airlie
* Alon Levy
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR
* OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*/
#include <linux/file.h>
#include <linux/sync_file.h>
#include <linux/uaccess.h>
#include <drm/drm_file.h>
#include <drm/virtgpu_drm.h>
#include "virtgpu_drv.h"
#define VIRTGPU_BLOB_FLAG_USE_MASK (VIRTGPU_BLOB_FLAG_USE_MAPPABLE | \
VIRTGPU_BLOB_FLAG_USE_SHAREABLE | \
VIRTGPU_BLOB_FLAG_USE_CROSS_DEVICE)
/* Must be called with &virtio_gpu_fpriv.struct_mutex held. */
static void virtio_gpu_create_context_locked(struct virtio_gpu_device *vgdev,
struct virtio_gpu_fpriv *vfpriv)
{
if (vfpriv->explicit_debug_name) {
virtio_gpu_cmd_context_create(vgdev, vfpriv->ctx_id,
vfpriv->context_init,
strlen(vfpriv->debug_name),
vfpriv->debug_name);
} else {
char dbgname[TASK_COMM_LEN];
get_task_comm(dbgname, current);
virtio_gpu_cmd_context_create(vgdev, vfpriv->ctx_id,
vfpriv->context_init, strlen(dbgname),
dbgname);
}
vfpriv->context_created = true;
}
void virtio_gpu_create_context(struct drm_device *dev, struct drm_file *file)
{
struct virtio_gpu_device *vgdev = dev->dev_private;
struct virtio_gpu_fpriv *vfpriv = file->driver_priv;
mutex_lock(&vfpriv->context_lock);
if (vfpriv->context_created)
goto out_unlock;
virtio_gpu_create_context_locked(vgdev, vfpriv);
out_unlock:
mutex_unlock(&vfpriv->context_lock);
}
static int virtio_gpu_map_ioctl(struct drm_device *dev, void *data,
struct drm_file *file)
{
struct virtio_gpu_device *vgdev = dev->dev_private;
struct drm_virtgpu_map *virtio_gpu_map = data;
return drm_gem_dumb_map_offset(file, vgdev->ddev,
virtio_gpu_map->handle,
&virtio_gpu_map->offset);
}
static int virtio_gpu_getparam_ioctl(struct drm_device *dev, void *data,
struct drm_file *file)
{
struct virtio_gpu_device *vgdev = dev->dev_private;
struct drm_virtgpu_getparam *param = data;
int value;
switch (param->param) {
case VIRTGPU_PARAM_3D_FEATURES:
value = vgdev->has_virgl_3d ? 1 : 0;
break;
case VIRTGPU_PARAM_CAPSET_QUERY_FIX:
value = 1;
break;
case VIRTGPU_PARAM_RESOURCE_BLOB:
value = vgdev->has_resource_blob ? 1 : 0;
break;
case VIRTGPU_PARAM_HOST_VISIBLE:
value = vgdev->has_host_visible ? 1 : 0;
break;
case VIRTGPU_PARAM_CROSS_DEVICE:
value = vgdev->has_resource_assign_uuid ? 1 : 0;
break;
case VIRTGPU_PARAM_CONTEXT_INIT:
value = vgdev->has_context_init ? 1 : 0;
break;
case VIRTGPU_PARAM_SUPPORTED_CAPSET_IDs:
value = vgdev->capset_id_mask;
break;
case VIRTGPU_PARAM_EXPLICIT_DEBUG_NAME:
value = vgdev->has_context_init ? 1 : 0;
break;
case VIRTGPU_PARAM_BLOB_ALIGNMENT:
if (!vgdev->has_blob_alignment)
return -ENOENT;
value = vgdev->blob_alignment;
break;
default:
return -EINVAL;
}
if (copy_to_user(u64_to_user_ptr(param->value), &value, sizeof(int)))
return -EFAULT;
return 0;
}
static int virtio_gpu_resource_create_ioctl(struct drm_device *dev, void *data,
struct drm_file *file)
{
struct virtio_gpu_device *vgdev = dev->dev_private;
struct drm_virtgpu_resource_create *rc = data;
struct virtio_gpu_fence *fence;
int ret;
struct virtio_gpu_object *qobj;
struct drm_gem_object *obj;
uint32_t handle = 0;
struct virtio_gpu_object_params params = { 0 };
if (vgdev->has_virgl_3d) {
virtio_gpu_create_context(dev, file);
params.virgl = true;
params.target = rc->target;
params.bind = rc->bind;
params.depth = rc->depth;
params.array_size = rc->array_size;
params.last_level = rc->last_level;
params.nr_samples = rc->nr_samples;
params.flags = rc->flags;
} else {
if (rc->depth > 1)
return -EINVAL;
if (rc->nr_samples > 1)
return -EINVAL;
if (rc->last_level > 1)
return -EINVAL;
if (rc->target != 2)
return -EINVAL;
if (rc->array_size > 1)
return -EINVAL;
}
params.format = rc->format;
params.width = rc->width;
params.height = rc->height;
params.size = rc->size;
/* allocate a single page size object */
if (params.size == 0)
params.size = PAGE_SIZE;
fence = virtio_gpu_fence_alloc(vgdev, vgdev->fence_drv.context, 0);
if (!fence)
return -ENOMEM;
ret = virtio_gpu_object_create(vgdev, &params, &qobj, fence);
dma_fence_put(&fence->f);
if (ret < 0)
return ret;
obj = &qobj->base.base;
ret = drm_gem_handle_create(file, obj, &handle);
if (ret) {
drm_gem_object_release(obj);
return ret;
}
rc->res_handle = qobj->hw_res_handle; /* similiar to a VM address */
rc->bo_handle = handle;
/*
* The handle owns the reference now. But we must drop our
* remaining reference *after* we no longer need to dereference
* the obj. Otherwise userspace could guess the handle and
* race closing it from another thread.
*/
drm_gem_object_put(obj);
return 0;
}
static int virtio_gpu_resource_info_ioctl(struct drm_device *dev, void *data,
struct drm_file *file)
{
struct drm_virtgpu_resource_info *ri = data;
struct drm_gem_object *gobj = NULL;
struct virtio_gpu_object *qobj = NULL;
gobj = drm_gem_object_lookup(file, ri->bo_handle);
if (gobj == NULL)
return -ENOENT;
qobj = gem_to_virtio_gpu_obj(gobj);
ri->size = qobj->base.base.size;
ri->res_handle = qobj->hw_res_handle;
if (qobj->host3d_blob || qobj->guest_blob)
ri->blob_mem = qobj->blob_mem;
drm_gem_object_put(gobj);
return 0;
}
static int virtio_gpu_transfer_from_host_ioctl(struct drm_device *dev,
void *data,
struct drm_file *file)
{
struct virtio_gpu_device *vgdev = dev->dev_private;
struct virtio_gpu_fpriv *vfpriv = file->driver_priv;
struct drm_virtgpu_3d_transfer_from_host *args = data;
struct virtio_gpu_object *bo;
struct virtio_gpu_object_array *objs;
struct virtio_gpu_fence *fence;
int ret;
u32 offset = args->offset;
if (vgdev->has_virgl_3d == false)
return -ENOSYS;
virtio_gpu_create_context(dev, file);
objs = virtio_gpu_array_from_handles(file, &args->bo_handle, 1);
if (objs == NULL)
return -ENOENT;
bo = gem_to_virtio_gpu_obj(objs->objs[0]);
if (bo->guest_blob && !bo->host3d_blob) {
ret = -EINVAL;
goto err_put_free;
}
if (!bo->host3d_blob && (args->stride || args->layer_stride)) {
ret = -EINVAL;
goto err_put_free;
}
ret = virtio_gpu_array_lock_resv(objs);
if (ret != 0)
goto err_put_free;
fence = virtio_gpu_fence_alloc(vgdev, vgdev->fence_drv.context, 0);
if (!fence) {
ret = -ENOMEM;
goto err_unlock;
}
virtio_gpu_cmd_transfer_from_host_3d
(vgdev, vfpriv->ctx_id, offset, args->level, args->stride,
args->layer_stride, &args->box, objs, fence);
dma_fence_put(&fence->f);
virtio_gpu_notify(vgdev);
return 0;
err_unlock:
virtio_gpu_array_unlock_resv(objs);
err_put_free:
virtio_gpu_array_put_free(objs);
return ret;
}
static int virtio_gpu_transfer_to_host_ioctl(struct drm_device *dev, void *data,
struct drm_file *file)
{
struct virtio_gpu_device *vgdev = dev->dev_private;
struct virtio_gpu_fpriv *vfpriv = file->driver_priv;
struct drm_virtgpu_3d_transfer_to_host *args = data;
struct virtio_gpu_object *bo;
struct virtio_gpu_object_array *objs;
struct virtio_gpu_fence *fence;
int ret;
u32 offset = args->offset;
objs = virtio_gpu_array_from_handles(file, &args->bo_handle, 1);
if (objs == NULL)
return -ENOENT;
bo = gem_to_virtio_gpu_obj(objs->objs[0]);
if (bo->guest_blob && !bo->host3d_blob) {
ret = -EINVAL;
goto err_put_free;
}
if (!vgdev->has_virgl_3d) {
virtio_gpu_cmd_transfer_to_host_2d
(vgdev, offset,
args->box.w, args->box.h, args->box.x, args->box.y,
objs, NULL);
} else {
virtio_gpu_create_context(dev, file);
if (!bo->host3d_blob && (args->stride || args->layer_stride)) {
ret = -EINVAL;
goto err_put_free;
}
ret = virtio_gpu_array_lock_resv(objs);
if (ret != 0)
goto err_put_free;
ret = -ENOMEM;
fence = virtio_gpu_fence_alloc(vgdev, vgdev->fence_drv.context,
0);
if (!fence)
goto err_unlock;
virtio_gpu_cmd_transfer_to_host_3d
(vgdev,
vfpriv ? vfpriv->ctx_id : 0, offset, args->level,
args->stride, args->layer_stride, &args->box, objs,
fence);
dma_fence_put(&fence->f);
}
virtio_gpu_notify(vgdev);
return 0;
err_unlock:
virtio_gpu_array_unlock_resv(objs);
err_put_free:
virtio_gpu_array_put_free(objs);
return ret;
}
static int virtio_gpu_wait_ioctl(struct drm_device *dev, void *data,
struct drm_file *file)
{
struct drm_virtgpu_3d_wait *args = data;
struct drm_gem_object *obj;
long timeout = 15 * HZ;
int ret;
obj = drm_gem_object_lookup(file, args->handle);
if (obj == NULL)
return -ENOENT;
if (args->flags & VIRTGPU_WAIT_NOWAIT) {
ret = dma_resv_test_signaled(obj->resv, DMA_RESV_USAGE_READ);
} else {
ret = dma_resv_wait_timeout(obj->resv, DMA_RESV_USAGE_READ,
true, timeout);
}
if (ret == 0)
ret = -EBUSY;
else if (ret > 0)
ret = 0;
drm_gem_object_put(obj);
return ret;
}
static int virtio_gpu_get_caps_ioctl(struct drm_device *dev,
void *data, struct drm_file *file)
{
struct virtio_gpu_device *vgdev = dev->dev_private;
struct drm_virtgpu_get_caps *args = data;
unsigned size, host_caps_size;
int i;
int found_valid = -1;
int ret;
struct virtio_gpu_drv_cap_cache *cache_ent;
void *ptr;
if (vgdev->num_capsets == 0)
return -ENOSYS;
/* don't allow userspace to pass 0 */
if (args->size == 0)
return -EINVAL;
spin_lock(&vgdev->display_info_lock);
for (i = 0; i < vgdev->num_capsets; i++) {
if (vgdev->capsets[i].id == args->cap_set_id) {
if (vgdev->capsets[i].max_version >= args->cap_set_ver) {
found_valid = i;
break;
}
}
}
if (found_valid == -1) {
spin_unlock(&vgdev->display_info_lock);
return -EINVAL;
}
host_caps_size = vgdev->capsets[found_valid].max_size;
/* only copy to user the minimum of the host caps size or the guest caps size */
size = min(args->size, host_caps_size);
list_for_each_entry(cache_ent, &vgdev->cap_cache, head) {
if (cache_ent->id == args->cap_set_id &&
cache_ent->version == args->cap_set_ver) {
spin_unlock(&vgdev->display_info_lock);
goto copy_exit;
}
}
spin_unlock(&vgdev->display_info_lock);
/* not in cache - need to talk to hw */
ret = virtio_gpu_cmd_get_capset(vgdev, found_valid, args->cap_set_ver,
&cache_ent);
if (ret)
return ret;
virtio_gpu_notify(vgdev);
copy_exit:
ret = wait_event_timeout(vgdev->resp_wq,
atomic_read(&cache_ent->is_valid), 5 * HZ);
if (!ret)
return -EBUSY;
/* is_valid check must proceed before copy of the cache entry. */
smp_rmb();
ptr = cache_ent->caps_cache;
if (copy_to_user(u64_to_user_ptr(args->addr), ptr, size))
return -EFAULT;
return 0;
}
static int verify_blob(struct virtio_gpu_device *vgdev,
struct virtio_gpu_fpriv *vfpriv,
struct virtio_gpu_object_params *params,
struct drm_virtgpu_resource_create_blob *rc_blob,
bool *guest_blob, bool *host3d_blob)
{
if (!vgdev->has_resource_blob)
return -EINVAL;
if (rc_blob->blob_flags & ~VIRTGPU_BLOB_FLAG_USE_MASK)
return -EINVAL;
if (rc_blob->blob_flags & VIRTGPU_BLOB_FLAG_USE_CROSS_DEVICE) {
if (!vgdev->has_resource_assign_uuid)
return -EINVAL;
}
switch (rc_blob->blob_mem) {
case VIRTGPU_BLOB_MEM_GUEST:
*guest_blob = true;
break;
case VIRTGPU_BLOB_MEM_HOST3D_GUEST:
*guest_blob = true;
fallthrough;
case VIRTGPU_BLOB_MEM_HOST3D:
*host3d_blob = true;
break;
default:
return -EINVAL;
}
if (*host3d_blob) {
if (!vgdev->has_virgl_3d)
return -EINVAL;
/* Must be dword aligned. */
if (rc_blob->cmd_size % 4 != 0)
return -EINVAL;
params->ctx_id = vfpriv->ctx_id;
params->blob_id = rc_blob->blob_id;
} else {
if (rc_blob->blob_id != 0)
return -EINVAL;
if (rc_blob->cmd_size != 0)
return -EINVAL;
}
params->blob_mem = rc_blob->blob_mem;
params->size = rc_blob->size;
params->blob = true;
params->blob_flags = rc_blob->blob_flags;
params->blob_hints = rc_blob->blob_hints;
if (vgdev->has_blob_alignment &&
!IS_ALIGNED(params->size, vgdev->blob_alignment))
return -EINVAL;
return 0;
}
static int virtio_gpu_resource_create_blob_ioctl(struct drm_device *dev,
void *data,
struct drm_file *file)
{
int ret = 0;
uint32_t handle = 0;
bool guest_blob = false;
bool host3d_blob = false;
struct drm_gem_object *obj;
struct virtio_gpu_object *bo;
struct virtio_gpu_object_params params = { 0 };
struct virtio_gpu_device *vgdev = dev->dev_private;
struct virtio_gpu_fpriv *vfpriv = file->driver_priv;
struct drm_virtgpu_resource_create_blob *rc_blob = data;
if (verify_blob(vgdev, vfpriv, &params, rc_blob,
&guest_blob, &host3d_blob))
return -EINVAL;
if (vgdev->has_virgl_3d)
virtio_gpu_create_context(dev, file);
if (rc_blob->cmd_size) {
void *buf;
buf = memdup_user(u64_to_user_ptr(rc_blob->cmd),
rc_blob->cmd_size);
if (IS_ERR(buf))
return PTR_ERR(buf);
virtio_gpu_cmd_submit(vgdev, buf, rc_blob->cmd_size,
vfpriv->ctx_id, NULL, NULL);
}
if (guest_blob)
ret = virtio_gpu_object_create(vgdev, &params, &bo, NULL);
else if (!guest_blob && host3d_blob)
ret = virtio_gpu_vram_create(vgdev, &params, &bo);
else
return -EINVAL;
if (ret < 0)
return ret;
bo->guest_blob = guest_blob;
bo->host3d_blob = host3d_blob;
bo->blob_mem = rc_blob->blob_mem;
bo->blob_flags = rc_blob->blob_flags;
obj = &bo->base.base;
if (params.blob_flags & VIRTGPU_BLOB_FLAG_USE_CROSS_DEVICE) {
ret = virtio_gpu_resource_assign_uuid(vgdev, bo);
if (ret) {
drm_gem_object_release(obj);
return ret;
}
}
ret = drm_gem_handle_create(file, obj, &handle);
if (ret) {
drm_gem_object_release(obj);
return ret;
}
rc_blob->res_handle = bo->hw_res_handle;
rc_blob->bo_handle = handle;
/*
* The handle owns the reference now. But we must drop our
* remaining reference *after* we no longer need to dereference
* the obj. Otherwise userspace could guess the handle and
* race closing it from another thread.
*/
drm_gem_object_put(obj);
return 0;
}
static int virtio_gpu_context_init_ioctl(struct drm_device *dev,
void *data, struct drm_file *file)
{
int ret = 0;
uint32_t num_params, i;
uint64_t valid_ring_mask, param, value;
size_t len;
struct drm_virtgpu_context_set_param *ctx_set_params = NULL;
struct virtio_gpu_device *vgdev = dev->dev_private;
struct virtio_gpu_fpriv *vfpriv = file->driver_priv;
struct drm_virtgpu_context_init *args = data;
num_params = args->num_params;
len = num_params * sizeof(struct drm_virtgpu_context_set_param);
if (!vgdev->has_context_init || !vgdev->has_virgl_3d)
return -EINVAL;
/* Number of unique parameters supported at this time. */
if (num_params > 4)
return -EINVAL;
ctx_set_params = memdup_user(u64_to_user_ptr(args->ctx_set_params),
len);
if (IS_ERR(ctx_set_params))
return PTR_ERR(ctx_set_params);
mutex_lock(&vfpriv->context_lock);
if (vfpriv->context_created) {
ret = -EEXIST;
goto out_unlock;
}
for (i = 0; i < num_params; i++) {
param = ctx_set_params[i].param;
value = ctx_set_params[i].value;
switch (param) {
case VIRTGPU_CONTEXT_PARAM_CAPSET_ID:
if (value > MAX_CAPSET_ID) {
ret = -EINVAL;
goto out_unlock;
}
if ((vgdev->capset_id_mask & (1ULL << value)) == 0) {
ret = -EINVAL;
goto out_unlock;
}
/* Context capset ID already set */
if (vfpriv->context_init &
VIRTIO_GPU_CONTEXT_INIT_CAPSET_ID_MASK) {
ret = -EINVAL;
goto out_unlock;
}
vfpriv->context_init |= value;
break;
case VIRTGPU_CONTEXT_PARAM_NUM_RINGS:
if (vfpriv->base_fence_ctx) {
ret = -EINVAL;
goto out_unlock;
}
if (value > MAX_RINGS) {
ret = -EINVAL;
goto out_unlock;
}
vfpriv->base_fence_ctx = dma_fence_context_alloc(value);
vfpriv->num_rings = value;
break;
case VIRTGPU_CONTEXT_PARAM_POLL_RINGS_MASK:
if (vfpriv->ring_idx_mask) {
ret = -EINVAL;
goto out_unlock;
}
vfpriv->ring_idx_mask = value;
break;
case VIRTGPU_CONTEXT_PARAM_DEBUG_NAME:
if (vfpriv->explicit_debug_name) {
ret = -EINVAL;
goto out_unlock;
}
ret = strncpy_from_user(vfpriv->debug_name,
u64_to_user_ptr(value),
DEBUG_NAME_MAX_LEN - 1);
if (ret < 0)
goto out_unlock;
vfpriv->explicit_debug_name = true;
ret = 0;
break;
default:
ret = -EINVAL;
goto out_unlock;
}
}
if (vfpriv->ring_idx_mask) {
valid_ring_mask = 0;
for (i = 0; i < vfpriv->num_rings; i++)
valid_ring_mask |= 1ULL << i;
if (~valid_ring_mask & vfpriv->ring_idx_mask) {
ret = -EINVAL;
goto out_unlock;
}
}
virtio_gpu_create_context_locked(vgdev, vfpriv);
virtio_gpu_notify(vgdev);
out_unlock:
mutex_unlock(&vfpriv->context_lock);
kfree(ctx_set_params);
return ret;
}
struct drm_ioctl_desc virtio_gpu_ioctls[DRM_VIRTIO_NUM_IOCTLS] = {
DRM_IOCTL_DEF_DRV(VIRTGPU_MAP, virtio_gpu_map_ioctl,
DRM_RENDER_ALLOW),
DRM_IOCTL_DEF_DRV(VIRTGPU_EXECBUFFER, virtio_gpu_execbuffer_ioctl,
DRM_RENDER_ALLOW),
DRM_IOCTL_DEF_DRV(VIRTGPU_GETPARAM, virtio_gpu_getparam_ioctl,
DRM_RENDER_ALLOW),
DRM_IOCTL_DEF_DRV(VIRTGPU_RESOURCE_CREATE,
virtio_gpu_resource_create_ioctl,
DRM_RENDER_ALLOW),
DRM_IOCTL_DEF_DRV(VIRTGPU_RESOURCE_INFO, virtio_gpu_resource_info_ioctl,
DRM_RENDER_ALLOW),
/* make transfer async to the main ring? - no sure, can we
* thread these in the underlying GL
*/
DRM_IOCTL_DEF_DRV(VIRTGPU_TRANSFER_FROM_HOST,
virtio_gpu_transfer_from_host_ioctl,
DRM_RENDER_ALLOW),
DRM_IOCTL_DEF_DRV(VIRTGPU_TRANSFER_TO_HOST,
virtio_gpu_transfer_to_host_ioctl,
DRM_RENDER_ALLOW),
DRM_IOCTL_DEF_DRV(VIRTGPU_WAIT, virtio_gpu_wait_ioctl,
DRM_RENDER_ALLOW),
DRM_IOCTL_DEF_DRV(VIRTGPU_GET_CAPS, virtio_gpu_get_caps_ioctl,
DRM_RENDER_ALLOW),
DRM_IOCTL_DEF_DRV(VIRTGPU_RESOURCE_CREATE_BLOB,
virtio_gpu_resource_create_blob_ioctl,
DRM_RENDER_ALLOW),
DRM_IOCTL_DEF_DRV(VIRTGPU_CONTEXT_INIT, virtio_gpu_context_init_ioctl,
DRM_RENDER_ALLOW),
};
@@ -0,0 +1,364 @@
/*
* Copyright (C) 2015 Red Hat, Inc.
* All Rights Reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice (including the
* next paragraph) shall be included in all copies or substantial
* portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE COPYRIGHT OWNER(S) AND/OR ITS SUPPLIERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include <linux/virtio.h>
#include <linux/virtio_config.h>
#include <linux/virtio_ring.h>
#include <drm/drm_file.h>
#include <drm/drm_managed.h>
#include <drm/drm_print.h>
#include "virtgpu_drv.h"
static void virtio_gpu_config_changed_work_func(struct work_struct *work)
{
struct virtio_gpu_device *vgdev =
container_of(work, struct virtio_gpu_device,
config_changed_work);
u32 events_read, events_clear = 0;
/* read the config space */
virtio_cread_le(vgdev->vdev, struct virtio_gpu_config,
events_read, &events_read);
if (events_read & VIRTIO_GPU_EVENT_DISPLAY) {
if (vgdev->num_scanouts) {
if (vgdev->has_edid)
virtio_gpu_cmd_get_edids(vgdev);
virtio_gpu_cmd_get_display_info(vgdev);
virtio_gpu_notify(vgdev);
wait_event_timeout(vgdev->resp_wq,
!vgdev->display_info_pending,
5 * HZ);
drm_kms_helper_hotplug_event(vgdev->ddev);
}
events_clear |= VIRTIO_GPU_EVENT_DISPLAY;
}
virtio_cwrite_le(vgdev->vdev, struct virtio_gpu_config,
events_clear, &events_clear);
}
static void virtio_gpu_init_vq(struct virtio_gpu_queue *vgvq,
void (*work_func)(struct work_struct *work))
{
spin_lock_init(&vgvq->qlock);
init_waitqueue_head(&vgvq->ack_queue);
INIT_WORK(&vgvq->dequeue_work, work_func);
}
static void virtio_gpu_get_capsets(struct virtio_gpu_device *vgdev,
int num_capsets)
{
int i, ret;
bool invalid_capset_id = false;
struct drm_device *drm = vgdev->ddev;
vgdev->capsets = drmm_kcalloc(drm, num_capsets,
sizeof(struct virtio_gpu_drv_capset),
GFP_KERNEL);
if (!vgdev->capsets) {
DRM_ERROR("failed to allocate cap sets\n");
return;
}
for (i = 0; i < num_capsets; i++) {
virtio_gpu_cmd_get_capset_info(vgdev, i);
virtio_gpu_notify(vgdev);
ret = wait_event_timeout(vgdev->resp_wq,
vgdev->capsets[i].id > 0, 5 * HZ);
/*
* Capability ids are defined in the virtio-gpu spec and are
* between 1 to 63, inclusive.
*/
if (!vgdev->capsets[i].id ||
vgdev->capsets[i].id > MAX_CAPSET_ID)
invalid_capset_id = true;
if (ret == 0)
DRM_ERROR("timed out waiting for cap set %d\n", i);
else if (invalid_capset_id)
DRM_ERROR("invalid capset id %u", vgdev->capsets[i].id);
if (ret == 0 || invalid_capset_id) {
spin_lock(&vgdev->display_info_lock);
drmm_kfree(drm, vgdev->capsets);
vgdev->capsets = NULL;
spin_unlock(&vgdev->display_info_lock);
return;
}
vgdev->capset_id_mask |= 1 << vgdev->capsets[i].id;
DRM_INFO("cap set %d: id %d, max-version %d, max-size %d\n",
i, vgdev->capsets[i].id,
vgdev->capsets[i].max_version,
vgdev->capsets[i].max_size);
}
vgdev->num_capsets = num_capsets;
}
int virtio_gpu_init(struct virtio_device *vdev, struct drm_device *dev)
{
struct virtqueue_info vqs_info[] = {
{ "control", virtio_gpu_ctrl_ack },
{ "cursor", virtio_gpu_cursor_ack },
};
struct virtio_gpu_device *vgdev;
/* this will expand later */
struct virtqueue *vqs[2];
u32 num_scanouts, num_capsets, blob_alignment;
int ret = 0;
if (!virtio_has_feature(vdev, VIRTIO_F_VERSION_1))
return -ENODEV;
vgdev = drmm_kzalloc(dev, sizeof(struct virtio_gpu_device), GFP_KERNEL);
if (!vgdev)
return -ENOMEM;
vgdev->ddev = dev;
dev->dev_private = vgdev;
vgdev->vdev = vdev;
spin_lock_init(&vgdev->display_info_lock);
spin_lock_init(&vgdev->resource_export_lock);
spin_lock_init(&vgdev->host_visible_lock);
ida_init(&vgdev->ctx_id_ida);
ida_init(&vgdev->resource_ida);
init_waitqueue_head(&vgdev->resp_wq);
virtio_gpu_init_vq(&vgdev->ctrlq, virtio_gpu_dequeue_ctrl_func);
virtio_gpu_init_vq(&vgdev->cursorq, virtio_gpu_dequeue_cursor_func);
vgdev->fence_drv.context = dma_fence_context_alloc(1);
spin_lock_init(&vgdev->fence_drv.lock);
INIT_LIST_HEAD(&vgdev->fence_drv.fences);
INIT_LIST_HEAD(&vgdev->cap_cache);
INIT_WORK(&vgdev->config_changed_work,
virtio_gpu_config_changed_work_func);
INIT_WORK(&vgdev->obj_free_work,
virtio_gpu_array_put_free_work);
INIT_LIST_HEAD(&vgdev->obj_free_list);
spin_lock_init(&vgdev->obj_free_lock);
#ifdef __LITTLE_ENDIAN
if (virtio_has_feature(vgdev->vdev, VIRTIO_GPU_F_VIRGL))
vgdev->has_virgl_3d = true;
#endif
if (virtio_has_feature(vgdev->vdev, VIRTIO_GPU_F_EDID))
vgdev->has_edid = true;
if (virtio_has_feature(vgdev->vdev, VIRTIO_RING_F_INDIRECT_DESC))
vgdev->has_indirect = true;
if (virtio_has_feature(vgdev->vdev, VIRTIO_GPU_F_RESOURCE_UUID))
vgdev->has_resource_assign_uuid = true;
if (virtio_has_feature(vgdev->vdev, VIRTIO_GPU_F_RESOURCE_BLOB))
vgdev->has_resource_blob = true;
if (virtio_get_shm_region(vgdev->vdev, &vgdev->host_visible_region,
VIRTIO_GPU_SHM_ID_HOST_VISIBLE)) {
if (!devm_request_mem_region(&vgdev->vdev->dev,
vgdev->host_visible_region.addr,
vgdev->host_visible_region.len,
dev_name(&vgdev->vdev->dev))) {
DRM_ERROR("Could not reserve host visible region\n");
ret = -EBUSY;
goto err_vqs;
}
DRM_INFO("Host memory window: 0x%lx +0x%lx\n",
(unsigned long)vgdev->host_visible_region.addr,
(unsigned long)vgdev->host_visible_region.len);
vgdev->has_host_visible = true;
drm_mm_init(&vgdev->host_visible_mm,
(unsigned long)vgdev->host_visible_region.addr,
(unsigned long)vgdev->host_visible_region.len);
}
if (virtio_has_feature(vgdev->vdev, VIRTIO_GPU_F_CONTEXT_INIT))
vgdev->has_context_init = true;
if (virtio_has_feature(vgdev->vdev, VIRTIO_GPU_F_BLOB_ALIGNMENT)) {
vgdev->has_blob_alignment = true;
virtio_cread_le(vgdev->vdev, struct virtio_gpu_config,
blob_alignment, &blob_alignment);
vgdev->blob_alignment = blob_alignment;
}
DRM_INFO("features: %cvirgl %cedid %cresource_blob %chost_visible",
vgdev->has_virgl_3d ? '+' : '-',
vgdev->has_edid ? '+' : '-',
vgdev->has_resource_blob ? '+' : '-',
vgdev->has_host_visible ? '+' : '-');
DRM_INFO("features: %ccontext_init %cblob_alignment\n",
vgdev->has_context_init ? '+' : '-',
vgdev->has_blob_alignment ? '+' : '-');
ret = virtio_find_vqs(vgdev->vdev, 2, vqs, vqs_info, NULL);
if (ret) {
DRM_ERROR("failed to find virt queues\n");
goto err_vqs;
}
vgdev->ctrlq.vq = vqs[0];
vgdev->cursorq.vq = vqs[1];
ret = virtio_gpu_alloc_vbufs(vgdev);
if (ret) {
DRM_ERROR("failed to alloc vbufs\n");
goto err_vbufs;
}
/* get display info */
virtio_cread_le(vgdev->vdev, struct virtio_gpu_config,
num_scanouts, &num_scanouts);
vgdev->num_scanouts = min_t(uint32_t, num_scanouts,
VIRTIO_GPU_MAX_SCANOUTS);
if (!IS_ENABLED(CONFIG_DRM_VIRTIO_GPU_KMS) || !vgdev->num_scanouts) {
DRM_INFO("KMS disabled\n");
vgdev->num_scanouts = 0;
vgdev->has_edid = false;
dev->driver_features &= ~(DRIVER_MODESET | DRIVER_ATOMIC);
} else {
DRM_INFO("number of scanouts: %d\n", num_scanouts);
}
virtio_cread_le(vgdev->vdev, struct virtio_gpu_config,
num_capsets, &num_capsets);
DRM_INFO("number of cap sets: %d\n", num_capsets);
ret = virtio_gpu_modeset_init(vgdev);
if (ret) {
DRM_ERROR("modeset init failed\n");
goto err_scanouts;
}
virtio_device_ready(vgdev->vdev);
if (num_capsets)
virtio_gpu_get_capsets(vgdev, num_capsets);
if (vgdev->num_scanouts) {
if (vgdev->has_edid)
virtio_gpu_cmd_get_edids(vgdev);
virtio_gpu_cmd_get_display_info(vgdev);
virtio_gpu_notify(vgdev);
wait_event_timeout(vgdev->resp_wq, !vgdev->display_info_pending,
5 * HZ);
}
return 0;
err_scanouts:
virtio_gpu_free_vbufs(vgdev);
err_vbufs:
vgdev->vdev->config->del_vqs(vgdev->vdev);
err_vqs:
dev->dev_private = NULL;
return ret;
}
static void virtio_gpu_cleanup_cap_cache(struct virtio_gpu_device *vgdev)
{
struct virtio_gpu_drv_cap_cache *cache_ent, *tmp;
list_for_each_entry_safe(cache_ent, tmp, &vgdev->cap_cache, head) {
kfree(cache_ent->caps_cache);
kfree(cache_ent);
}
}
void virtio_gpu_deinit(struct drm_device *dev)
{
struct virtio_gpu_device *vgdev = dev->dev_private;
flush_work(&vgdev->obj_free_work);
flush_work(&vgdev->ctrlq.dequeue_work);
flush_work(&vgdev->cursorq.dequeue_work);
flush_work(&vgdev->config_changed_work);
virtio_reset_device(vgdev->vdev);
vgdev->vdev->config->del_vqs(vgdev->vdev);
}
void virtio_gpu_release(struct drm_device *dev)
{
struct virtio_gpu_device *vgdev = dev->dev_private;
if (!vgdev)
return;
virtio_gpu_modeset_fini(vgdev);
virtio_gpu_free_vbufs(vgdev);
virtio_gpu_cleanup_cap_cache(vgdev);
if (vgdev->has_host_visible)
drm_mm_takedown(&vgdev->host_visible_mm);
}
int virtio_gpu_driver_open(struct drm_device *dev, struct drm_file *file)
{
struct virtio_gpu_device *vgdev = dev->dev_private;
struct virtio_gpu_fpriv *vfpriv;
int handle;
/* can't create contexts without 3d renderer */
if (!vgdev->has_virgl_3d)
return 0;
/* allocate a virt GPU context for this opener */
vfpriv = kzalloc_obj(*vfpriv);
if (!vfpriv)
return -ENOMEM;
mutex_init(&vfpriv->context_lock);
handle = ida_alloc(&vgdev->ctx_id_ida, GFP_KERNEL);
if (handle < 0) {
kfree(vfpriv);
return handle;
}
vfpriv->ctx_id = handle + 1;
file->driver_priv = vfpriv;
return 0;
}
void virtio_gpu_driver_postclose(struct drm_device *dev, struct drm_file *file)
{
struct virtio_gpu_device *vgdev = dev->dev_private;
struct virtio_gpu_fpriv *vfpriv = file->driver_priv;
if (!vgdev->has_virgl_3d)
return;
if (vfpriv->context_created) {
virtio_gpu_cmd_context_destroy(vgdev, vfpriv->ctx_id);
virtio_gpu_notify(vgdev);
}
ida_free(&vgdev->ctx_id_ida, vfpriv->ctx_id - 1);
mutex_destroy(&vfpriv->context_lock);
kfree(vfpriv);
file->driver_priv = NULL;
}
@@ -0,0 +1,273 @@
/*
* Copyright (C) 2015 Red Hat, Inc.
* All Rights Reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice (including the
* next paragraph) shall be included in all copies or substantial
* portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE COPYRIGHT OWNER(S) AND/OR ITS SUPPLIERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include <linux/dma-mapping.h>
#include <linux/moduleparam.h>
#include <drm/drm_print.h>
#include "virtgpu_drv.h"
static int virtio_gpu_virglrenderer_workaround = 1;
module_param_named(virglhack, virtio_gpu_virglrenderer_workaround, int, 0400);
int virtio_gpu_resource_id_get(struct virtio_gpu_device *vgdev, uint32_t *resid)
{
if (virtio_gpu_virglrenderer_workaround) {
/*
* Hack to avoid re-using resource IDs.
*
* virglrenderer versions up to (and including) 0.7.0
* can't deal with that. virglrenderer commit
* "f91a9dd35715 Fix unlinking resources from hash
* table." (Feb 2019) fixes the bug.
*/
static atomic_t seqno = ATOMIC_INIT(0);
int handle = atomic_inc_return(&seqno);
*resid = handle + 1;
} else {
int handle = ida_alloc(&vgdev->resource_ida, GFP_KERNEL);
if (handle < 0)
return handle;
*resid = handle + 1;
}
return 0;
}
static void virtio_gpu_resource_id_put(struct virtio_gpu_device *vgdev, uint32_t id)
{
if (!virtio_gpu_virglrenderer_workaround)
ida_free(&vgdev->resource_ida, id - 1);
}
void virtio_gpu_cleanup_object(struct virtio_gpu_object *bo)
{
struct virtio_gpu_device *vgdev = bo->base.base.dev->dev_private;
virtio_gpu_resource_id_put(vgdev, bo->hw_res_handle);
if (virtio_gpu_is_shmem(bo)) {
drm_gem_shmem_free(&bo->base);
} else if (virtio_gpu_is_vram(bo)) {
struct virtio_gpu_object_vram *vram = to_virtio_gpu_vram(bo);
spin_lock(&vgdev->host_visible_lock);
if (drm_mm_node_allocated(&vram->vram_node))
drm_mm_remove_node(&vram->vram_node);
spin_unlock(&vgdev->host_visible_lock);
drm_gem_free_mmap_offset(&vram->base.base.base);
drm_gem_object_release(&vram->base.base.base);
kfree(vram);
} else {
drm_gem_object_release(&bo->base.base);
kfree(bo);
}
}
static void virtio_gpu_free_object(struct drm_gem_object *obj)
{
struct virtio_gpu_object *bo = gem_to_virtio_gpu_obj(obj);
struct virtio_gpu_device *vgdev = bo->base.base.dev->dev_private;
if (bo->created) {
virtio_gpu_cmd_unref_resource(vgdev, bo);
virtio_gpu_notify(vgdev);
/* completion handler calls virtio_gpu_cleanup_object() */
return;
}
virtio_gpu_cleanup_object(bo);
}
int virtio_gpu_detach_object_fenced(struct virtio_gpu_object *bo)
{
struct virtio_gpu_device *vgdev = bo->base.base.dev->dev_private;
struct virtio_gpu_fence *fence;
if (!bo->attached)
return 0;
fence = virtio_gpu_fence_alloc(vgdev, vgdev->fence_drv.context, 0);
if (!fence)
return -ENOMEM;
virtio_gpu_object_detach(vgdev, bo, fence);
virtio_gpu_notify(vgdev);
dma_fence_wait(&fence->f, false);
dma_fence_put(&fence->f);
return 0;
}
static const struct drm_gem_object_funcs virtio_gpu_shmem_funcs = {
.free = virtio_gpu_free_object,
.open = virtio_gpu_gem_object_open,
.close = virtio_gpu_gem_object_close,
.print_info = drm_gem_shmem_object_print_info,
.export = virtgpu_gem_prime_export,
.pin = drm_gem_shmem_object_pin,
.unpin = drm_gem_shmem_object_unpin,
.get_sg_table = drm_gem_shmem_object_get_sg_table,
.vmap = drm_gem_shmem_object_vmap,
.vunmap = drm_gem_shmem_object_vunmap,
.mmap = drm_gem_shmem_object_mmap,
.vm_ops = &drm_gem_shmem_vm_ops,
};
bool virtio_gpu_is_shmem(struct virtio_gpu_object *bo)
{
return bo->base.base.funcs == &virtio_gpu_shmem_funcs;
}
struct drm_gem_object *virtio_gpu_create_object(struct drm_device *dev,
size_t size)
{
struct virtio_gpu_object_shmem *shmem;
struct drm_gem_shmem_object *dshmem;
shmem = kzalloc_obj(*shmem);
if (!shmem)
return ERR_PTR(-ENOMEM);
dshmem = &shmem->base.base;
dshmem->base.funcs = &virtio_gpu_shmem_funcs;
return &dshmem->base;
}
static int virtio_gpu_object_shmem_init(struct virtio_gpu_device *vgdev,
struct virtio_gpu_object *bo,
struct virtio_gpu_mem_entry **ents,
unsigned int *nents)
{
bool use_dma_api = !virtio_has_dma_quirk(vgdev->vdev);
struct scatterlist *sg;
struct sg_table *pages;
int si;
pages = drm_gem_shmem_get_pages_sgt(&bo->base);
if (IS_ERR(pages))
return PTR_ERR(pages);
if (use_dma_api)
*nents = pages->nents;
else
*nents = pages->orig_nents;
*ents = kvmalloc_objs(struct virtio_gpu_mem_entry, *nents);
if (!(*ents)) {
DRM_ERROR("failed to allocate ent list\n");
return -ENOMEM;
}
if (use_dma_api) {
for_each_sgtable_dma_sg(pages, sg, si) {
(*ents)[si].addr = cpu_to_le64(sg_dma_address(sg));
(*ents)[si].length = cpu_to_le32(sg_dma_len(sg));
(*ents)[si].padding = 0;
}
} else {
for_each_sgtable_sg(pages, sg, si) {
(*ents)[si].addr = cpu_to_le64(sg_phys(sg));
(*ents)[si].length = cpu_to_le32(sg->length);
(*ents)[si].padding = 0;
}
}
return 0;
}
int virtio_gpu_object_create(struct virtio_gpu_device *vgdev,
struct virtio_gpu_object_params *params,
struct virtio_gpu_object **bo_ptr,
struct virtio_gpu_fence *fence)
{
struct virtio_gpu_object_array *objs = NULL;
struct drm_gem_shmem_object *shmem_obj;
struct virtio_gpu_object *bo;
struct virtio_gpu_mem_entry *ents = NULL;
unsigned int nents;
int ret;
*bo_ptr = NULL;
params->size = roundup(params->size, PAGE_SIZE);
shmem_obj = drm_gem_shmem_create(vgdev->ddev, params->size);
if (IS_ERR(shmem_obj))
return PTR_ERR(shmem_obj);
bo = gem_to_virtio_gpu_obj(&shmem_obj->base);
ret = virtio_gpu_resource_id_get(vgdev, &bo->hw_res_handle);
if (ret < 0)
goto err_free_gem;
bo->dumb = params->dumb;
ret = virtio_gpu_object_shmem_init(vgdev, bo, &ents, &nents);
if (ret != 0)
goto err_put_id;
if (fence) {
ret = -ENOMEM;
objs = virtio_gpu_array_alloc(1);
if (!objs)
goto err_free_entry;
virtio_gpu_array_add_obj(objs, &bo->base.base);
ret = virtio_gpu_array_lock_resv(objs);
if (ret != 0)
goto err_put_objs;
}
if (params->blob) {
if (params->blob_mem == VIRTGPU_BLOB_MEM_GUEST)
bo->guest_blob = true;
virtio_gpu_cmd_resource_create_blob(vgdev, bo, params,
ents, nents);
} else if (params->virgl) {
virtio_gpu_cmd_resource_create_3d(vgdev, bo, params,
objs, fence);
virtio_gpu_object_attach(vgdev, bo, ents, nents);
} else {
virtio_gpu_cmd_create_resource(vgdev, bo, params,
objs, fence);
virtio_gpu_object_attach(vgdev, bo, ents, nents);
}
*bo_ptr = bo;
return 0;
err_put_objs:
virtio_gpu_array_put_free(objs);
err_free_entry:
kvfree(ents);
err_put_id:
virtio_gpu_resource_id_put(vgdev, bo->hw_res_handle);
err_free_gem:
drm_gem_shmem_free(shmem_obj);
return ret;
}
@@ -0,0 +1,614 @@
/*
* Copyright (C) 2015 Red Hat, Inc.
* All Rights Reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice (including the
* next paragraph) shall be included in all copies or substantial
* portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE COPYRIGHT OWNER(S) AND/OR ITS SUPPLIERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include <drm/drm_atomic_helper.h>
#include <drm/drm_damage_helper.h>
#include <drm/drm_fourcc.h>
#include <drm/drm_gem_atomic_helper.h>
#include <linux/virtio_dma_buf.h>
#include <drm/drm_managed.h>
#include <drm/drm_panic.h>
#include <drm/drm_print.h>
#include "virtgpu_drv.h"
static const uint32_t virtio_gpu_formats[] = {
DRM_FORMAT_HOST_XRGB8888,
};
static const uint32_t virtio_gpu_cursor_formats[] = {
DRM_FORMAT_HOST_ARGB8888,
};
uint32_t virtio_gpu_translate_format(uint32_t drm_fourcc)
{
uint32_t format;
switch (drm_fourcc) {
case DRM_FORMAT_XRGB8888:
format = VIRTIO_GPU_FORMAT_B8G8R8X8_UNORM;
break;
case DRM_FORMAT_ARGB8888:
format = VIRTIO_GPU_FORMAT_B8G8R8A8_UNORM;
break;
case DRM_FORMAT_BGRX8888:
format = VIRTIO_GPU_FORMAT_X8R8G8B8_UNORM;
break;
case DRM_FORMAT_BGRA8888:
format = VIRTIO_GPU_FORMAT_A8R8G8B8_UNORM;
break;
default:
/*
* This should not happen, we handle everything listed
* in virtio_gpu_formats[].
*/
format = 0;
break;
}
WARN_ON(format == 0);
return format;
}
static struct
drm_plane_state *virtio_gpu_plane_duplicate_state(struct drm_plane *plane)
{
struct virtio_gpu_plane_state *new;
if (WARN_ON(!plane->state))
return NULL;
new = kzalloc_obj(*new);
if (!new)
return NULL;
__drm_atomic_helper_plane_duplicate_state(plane, &new->base);
return &new->base;
}
static const struct drm_plane_funcs virtio_gpu_plane_funcs = {
.update_plane = drm_atomic_helper_update_plane,
.disable_plane = drm_atomic_helper_disable_plane,
.reset = drm_atomic_helper_plane_reset,
.atomic_duplicate_state = virtio_gpu_plane_duplicate_state,
.atomic_destroy_state = drm_atomic_helper_plane_destroy_state,
};
static int virtio_gpu_plane_atomic_check(struct drm_plane *plane,
struct drm_atomic_commit *state)
{
struct drm_plane_state *new_plane_state = drm_atomic_get_new_plane_state(state,
plane);
struct drm_plane_state *old_plane_state = drm_atomic_get_old_plane_state(state,
plane);
bool is_cursor = plane->type == DRM_PLANE_TYPE_CURSOR;
struct drm_crtc_state *crtc_state;
int ret;
if (!new_plane_state->fb || WARN_ON(!new_plane_state->crtc))
return 0;
/*
* Ignore damage clips if the framebuffer attached to the plane's state
* has changed since the last plane update (page-flip). In this case, a
* full plane update should happen because uploads are done per-buffer.
*/
if (old_plane_state->fb != new_plane_state->fb)
new_plane_state->ignore_damage_clips = true;
crtc_state = drm_atomic_get_crtc_state(state,
new_plane_state->crtc);
if (IS_ERR(crtc_state))
return PTR_ERR(crtc_state);
ret = drm_atomic_helper_check_plane_state(new_plane_state, crtc_state,
DRM_PLANE_NO_SCALING,
DRM_PLANE_NO_SCALING,
is_cursor, true);
return ret;
}
/* For drm panic */
static int virtio_gpu_panic_update_dumb_bo(struct virtio_gpu_device *vgdev,
struct drm_plane_state *state,
struct drm_rect *rect)
{
struct virtio_gpu_object *bo =
gem_to_virtio_gpu_obj(state->fb->obj[0]);
struct virtio_gpu_object_array *objs;
uint32_t w = rect->x2 - rect->x1;
uint32_t h = rect->y2 - rect->y1;
uint32_t x = rect->x1;
uint32_t y = rect->y1;
uint32_t off = x * state->fb->format->cpp[0] +
y * state->fb->pitches[0];
objs = virtio_gpu_panic_array_alloc();
if (!objs)
return -ENOMEM;
virtio_gpu_array_add_obj(objs, &bo->base.base);
return virtio_gpu_panic_cmd_transfer_to_host_2d(vgdev, off, w, h, x, y,
objs);
}
static void virtio_gpu_update_dumb_bo(struct virtio_gpu_device *vgdev,
struct drm_plane_state *state,
struct drm_rect *rect)
{
struct virtio_gpu_object *bo =
gem_to_virtio_gpu_obj(state->fb->obj[0]);
struct virtio_gpu_object_array *objs;
uint32_t w = rect->x2 - rect->x1;
uint32_t h = rect->y2 - rect->y1;
uint32_t x = rect->x1;
uint32_t y = rect->y1;
uint32_t off = x * state->fb->format->cpp[0] +
y * state->fb->pitches[0];
objs = virtio_gpu_array_alloc(1);
if (!objs)
return;
virtio_gpu_array_add_obj(objs, &bo->base.base);
virtio_gpu_cmd_transfer_to_host_2d(vgdev, off, w, h, x, y,
objs, NULL);
}
/* For drm_panic */
static void virtio_gpu_panic_resource_flush(struct drm_plane *plane,
uint32_t x, uint32_t y,
uint32_t width, uint32_t height)
{
struct drm_device *dev = plane->dev;
struct virtio_gpu_device *vgdev = dev->dev_private;
struct virtio_gpu_framebuffer *vgfb;
struct virtio_gpu_object *bo;
vgfb = to_virtio_gpu_framebuffer(plane->state->fb);
bo = gem_to_virtio_gpu_obj(vgfb->base.obj[0]);
virtio_gpu_panic_cmd_resource_flush(vgdev, bo->hw_res_handle, x, y,
width, height);
virtio_gpu_panic_notify(vgdev);
}
static void virtio_gpu_resource_flush(struct drm_plane *plane,
uint32_t x, uint32_t y,
uint32_t width, uint32_t height)
{
struct drm_device *dev = plane->dev;
struct virtio_gpu_device *vgdev = dev->dev_private;
struct virtio_gpu_framebuffer *vgfb;
struct virtio_gpu_plane_state *vgplane_st;
struct virtio_gpu_object *bo;
vgfb = to_virtio_gpu_framebuffer(plane->state->fb);
vgplane_st = to_virtio_gpu_plane_state(plane->state);
bo = gem_to_virtio_gpu_obj(vgfb->base.obj[0]);
if (vgplane_st->fence) {
struct virtio_gpu_object_array *objs;
objs = virtio_gpu_array_alloc(1);
if (!objs)
return;
virtio_gpu_array_add_obj(objs, vgfb->base.obj[0]);
if (virtio_gpu_lock_one_resv_uninterruptible(objs)) {
virtio_gpu_array_put_free(objs);
return;
}
virtio_gpu_cmd_resource_flush(vgdev, bo->hw_res_handle, x, y,
width, height, objs,
vgplane_st->fence);
virtio_gpu_notify(vgdev);
dma_fence_wait_timeout(&vgplane_st->fence->f, true,
msecs_to_jiffies(50));
} else {
virtio_gpu_cmd_resource_flush(vgdev, bo->hw_res_handle, x, y,
width, height, NULL, NULL);
virtio_gpu_notify(vgdev);
}
}
static void virtio_gpu_primary_plane_update(struct drm_plane *plane,
struct drm_atomic_commit *state)
{
struct drm_plane_state *old_state = drm_atomic_get_old_plane_state(state,
plane);
struct drm_device *dev = plane->dev;
struct virtio_gpu_device *vgdev = dev->dev_private;
struct virtio_gpu_output *output = NULL;
struct virtio_gpu_object *bo;
struct drm_rect rect;
if (plane->state->crtc)
output = drm_crtc_to_virtio_gpu_output(plane->state->crtc);
if (old_state->crtc)
output = drm_crtc_to_virtio_gpu_output(old_state->crtc);
if (WARN_ON(!output))
return;
if (!plane->state->fb || !output->crtc.state->active) {
DRM_DEBUG("nofb\n");
virtio_gpu_cmd_set_scanout(vgdev, output->index, 0,
plane->state->src_w >> 16,
plane->state->src_h >> 16,
0, 0);
virtio_gpu_notify(vgdev);
return;
}
if (!drm_atomic_helper_damage_merged(old_state, plane->state, &rect))
return;
bo = gem_to_virtio_gpu_obj(plane->state->fb->obj[0]);
if (bo->dumb)
virtio_gpu_update_dumb_bo(vgdev, plane->state, &rect);
if (plane->state->fb != old_state->fb ||
plane->state->src_w != old_state->src_w ||
plane->state->src_h != old_state->src_h ||
plane->state->src_x != old_state->src_x ||
plane->state->src_y != old_state->src_y ||
output->needs_modeset) {
output->needs_modeset = false;
DRM_DEBUG("handle 0x%x, crtc %dx%d+%d+%d, src %dx%d+%d+%d\n",
bo->hw_res_handle,
plane->state->crtc_w, plane->state->crtc_h,
plane->state->crtc_x, plane->state->crtc_y,
plane->state->src_w >> 16,
plane->state->src_h >> 16,
plane->state->src_x >> 16,
plane->state->src_y >> 16);
if (bo->host3d_blob || bo->guest_blob) {
virtio_gpu_cmd_set_scanout_blob
(vgdev, output->index, bo,
plane->state->fb,
plane->state->src_w >> 16,
plane->state->src_h >> 16,
plane->state->src_x >> 16,
plane->state->src_y >> 16);
} else {
virtio_gpu_cmd_set_scanout(vgdev, output->index,
bo->hw_res_handle,
plane->state->src_w >> 16,
plane->state->src_h >> 16,
plane->state->src_x >> 16,
plane->state->src_y >> 16);
}
}
virtio_gpu_resource_flush(plane,
rect.x1,
rect.y1,
rect.x2 - rect.x1,
rect.y2 - rect.y1);
}
static int virtio_gpu_prepare_imported_obj(struct drm_plane *plane,
struct drm_plane_state *new_state,
struct drm_gem_object *obj)
{
struct virtio_gpu_device *vgdev = plane->dev->dev_private;
struct virtio_gpu_object *bo = gem_to_virtio_gpu_obj(obj);
struct dma_buf_attachment *attach = obj->import_attach;
struct dma_resv *resv = attach->dmabuf->resv;
struct virtio_gpu_mem_entry *ents = NULL;
unsigned int nents;
int ret;
dma_resv_lock(resv, NULL);
ret = dma_buf_pin(attach);
if (ret) {
dma_resv_unlock(resv);
return ret;
}
if (!bo->sgt) {
ret = virtgpu_dma_buf_import_sgt(&ents, &nents,
bo, attach);
if (ret)
goto err;
virtio_gpu_object_attach(vgdev, bo, ents, nents);
}
dma_resv_unlock(resv);
return 0;
err:
dma_buf_unpin(attach);
dma_resv_unlock(resv);
return ret;
}
static int virtio_gpu_plane_prepare_fb(struct drm_plane *plane,
struct drm_plane_state *new_state)
{
struct drm_device *dev = plane->dev;
struct virtio_gpu_device *vgdev = dev->dev_private;
struct virtio_gpu_framebuffer *vgfb;
struct virtio_gpu_plane_state *vgplane_st;
struct virtio_gpu_object *bo;
struct drm_gem_object *obj;
int ret;
if (!new_state->fb)
return 0;
vgfb = to_virtio_gpu_framebuffer(new_state->fb);
vgplane_st = to_virtio_gpu_plane_state(new_state);
bo = gem_to_virtio_gpu_obj(vgfb->base.obj[0]);
drm_gem_plane_helper_prepare_fb(plane, new_state);
if (!bo || (plane->type == DRM_PLANE_TYPE_PRIMARY && !bo->guest_blob))
return 0;
obj = new_state->fb->obj[0];
if (bo->dumb || drm_gem_is_imported(obj)) {
vgplane_st->fence = virtio_gpu_fence_alloc(vgdev,
vgdev->fence_drv.context,
0);
if (!vgplane_st->fence)
return -ENOMEM;
}
if (drm_gem_is_imported(obj)) {
ret = virtio_gpu_prepare_imported_obj(plane, new_state, obj);
if (ret)
goto err_fence;
}
return 0;
err_fence:
if (vgplane_st->fence) {
dma_fence_put(&vgplane_st->fence->f);
vgplane_st->fence = NULL;
}
return ret;
}
static void virtio_gpu_cleanup_imported_obj(struct drm_gem_object *obj)
{
struct dma_buf_attachment *attach = obj->import_attach;
struct dma_resv *resv = attach->dmabuf->resv;
dma_resv_lock(resv, NULL);
dma_buf_unpin(attach);
dma_resv_unlock(resv);
}
static void virtio_gpu_plane_cleanup_fb(struct drm_plane *plane,
struct drm_plane_state *state)
{
struct virtio_gpu_plane_state *vgplane_st;
struct drm_gem_object *obj;
if (!state->fb)
return;
vgplane_st = to_virtio_gpu_plane_state(state);
if (vgplane_st->fence) {
dma_fence_put(&vgplane_st->fence->f);
vgplane_st->fence = NULL;
}
obj = state->fb->obj[0];
if (drm_gem_is_imported(obj))
virtio_gpu_cleanup_imported_obj(obj);
}
static void virtio_gpu_cursor_plane_update(struct drm_plane *plane,
struct drm_atomic_commit *state)
{
struct drm_plane_state *old_state = drm_atomic_get_old_plane_state(state,
plane);
struct drm_device *dev = plane->dev;
struct virtio_gpu_device *vgdev = dev->dev_private;
struct virtio_gpu_output *output = NULL;
struct virtio_gpu_framebuffer *vgfb;
struct virtio_gpu_plane_state *vgplane_st;
struct virtio_gpu_object *bo = NULL;
uint32_t handle;
if (plane->state->crtc)
output = drm_crtc_to_virtio_gpu_output(plane->state->crtc);
if (old_state->crtc)
output = drm_crtc_to_virtio_gpu_output(old_state->crtc);
if (WARN_ON(!output))
return;
if (plane->state->fb) {
vgfb = to_virtio_gpu_framebuffer(plane->state->fb);
vgplane_st = to_virtio_gpu_plane_state(plane->state);
bo = gem_to_virtio_gpu_obj(vgfb->base.obj[0]);
handle = bo->hw_res_handle;
} else {
handle = 0;
}
if (bo && bo->dumb && (plane->state->fb != old_state->fb)) {
/* new cursor -- update & wait */
struct virtio_gpu_object_array *objs;
objs = virtio_gpu_array_alloc(1);
if (!objs)
return;
virtio_gpu_array_add_obj(objs, vgfb->base.obj[0]);
if (virtio_gpu_lock_one_resv_uninterruptible(objs)) {
virtio_gpu_array_put_free(objs);
return;
}
virtio_gpu_cmd_transfer_to_host_2d
(vgdev, 0,
plane->state->crtc_w,
plane->state->crtc_h,
0, 0, objs, vgplane_st->fence);
virtio_gpu_notify(vgdev);
dma_fence_wait(&vgplane_st->fence->f, true);
}
if (plane->state->fb != old_state->fb) {
DRM_DEBUG("update, handle %d, pos +%d+%d, hot %d,%d\n", handle,
plane->state->crtc_x,
plane->state->crtc_y,
plane->state->hotspot_x,
plane->state->hotspot_y);
output->cursor.hdr.type =
cpu_to_le32(VIRTIO_GPU_CMD_UPDATE_CURSOR);
output->cursor.resource_id = cpu_to_le32(handle);
if (plane->state->fb) {
output->cursor.hot_x =
cpu_to_le32(plane->state->hotspot_x);
output->cursor.hot_y =
cpu_to_le32(plane->state->hotspot_y);
} else {
output->cursor.hot_x = cpu_to_le32(0);
output->cursor.hot_y = cpu_to_le32(0);
}
} else {
DRM_DEBUG("move +%d+%d\n",
plane->state->crtc_x,
plane->state->crtc_y);
output->cursor.hdr.type =
cpu_to_le32(VIRTIO_GPU_CMD_MOVE_CURSOR);
}
output->cursor.pos.x = cpu_to_le32(plane->state->crtc_x);
output->cursor.pos.y = cpu_to_le32(plane->state->crtc_y);
virtio_gpu_cursor_ping(vgdev, output);
}
static int virtio_drm_get_scanout_buffer(struct drm_plane *plane,
struct drm_scanout_buffer *sb)
{
struct virtio_gpu_object *bo;
if (!plane->state || !plane->state->fb || !plane->state->visible)
return -ENODEV;
bo = gem_to_virtio_gpu_obj(plane->state->fb->obj[0]);
if (virtio_gpu_is_vram(bo) || drm_gem_is_imported(&bo->base.base))
return -ENODEV;
if (bo->base.vaddr) {
iosys_map_set_vaddr(&sb->map[0], bo->base.vaddr);
} else {
struct drm_gem_shmem_object *shmem = &bo->base;
if (!shmem->pages)
return -ENODEV;
/* map scanout buffer later */
sb->pages = shmem->pages;
}
sb->format = plane->state->fb->format;
sb->height = plane->state->fb->height;
sb->width = plane->state->fb->width;
sb->pitch[0] = plane->state->fb->pitches[0];
return 0;
}
static void virtio_panic_flush(struct drm_plane *plane)
{
struct virtio_gpu_object *bo;
struct drm_device *dev = plane->dev;
struct virtio_gpu_device *vgdev = dev->dev_private;
struct drm_rect rect;
rect.x1 = 0;
rect.y1 = 0;
rect.x2 = plane->state->fb->width;
rect.y2 = plane->state->fb->height;
bo = gem_to_virtio_gpu_obj(plane->state->fb->obj[0]);
if (bo->dumb) {
if (virtio_gpu_panic_update_dumb_bo(vgdev, plane->state,
&rect))
return;
}
virtio_gpu_panic_resource_flush(plane,
plane->state->src_x >> 16,
plane->state->src_y >> 16,
plane->state->src_w >> 16,
plane->state->src_h >> 16);
}
static const struct drm_plane_helper_funcs virtio_gpu_primary_helper_funcs = {
.prepare_fb = virtio_gpu_plane_prepare_fb,
.cleanup_fb = virtio_gpu_plane_cleanup_fb,
.atomic_check = virtio_gpu_plane_atomic_check,
.atomic_update = virtio_gpu_primary_plane_update,
.get_scanout_buffer = virtio_drm_get_scanout_buffer,
.panic_flush = virtio_panic_flush,
};
static const struct drm_plane_helper_funcs virtio_gpu_cursor_helper_funcs = {
.prepare_fb = virtio_gpu_plane_prepare_fb,
.cleanup_fb = virtio_gpu_plane_cleanup_fb,
.atomic_check = virtio_gpu_plane_atomic_check,
.atomic_update = virtio_gpu_cursor_plane_update,
};
struct drm_plane *virtio_gpu_plane_init(struct virtio_gpu_device *vgdev,
enum drm_plane_type type,
int index)
{
struct drm_device *dev = vgdev->ddev;
const struct drm_plane_helper_funcs *funcs;
struct drm_plane *plane;
const uint32_t *formats;
int nformats;
if (type == DRM_PLANE_TYPE_CURSOR) {
formats = virtio_gpu_cursor_formats;
nformats = ARRAY_SIZE(virtio_gpu_cursor_formats);
funcs = &virtio_gpu_cursor_helper_funcs;
} else {
formats = virtio_gpu_formats;
nformats = ARRAY_SIZE(virtio_gpu_formats);
funcs = &virtio_gpu_primary_helper_funcs;
}
plane = drmm_universal_plane_alloc(dev, struct drm_plane, dev,
1 << index, &virtio_gpu_plane_funcs,
formats, nformats, NULL, type, NULL);
if (IS_ERR(plane))
return plane;
drm_plane_helper_add(plane, funcs);
if (type == DRM_PLANE_TYPE_PRIMARY)
drm_plane_enable_fb_damage_clips(plane);
return plane;
}
@@ -0,0 +1,347 @@
/*
* Copyright 2014 Canonical
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR
* OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*
* Authors: Andreas Pokorny
*/
#include <drm/drm_prime.h>
#include <linux/virtio_dma_buf.h>
#include "virtgpu_drv.h"
MODULE_IMPORT_NS("DMA_BUF");
static int virtgpu_virtio_get_uuid(struct dma_buf *buf,
uuid_t *uuid)
{
struct drm_gem_object *obj = buf->priv;
struct virtio_gpu_object *bo = gem_to_virtio_gpu_obj(obj);
struct virtio_gpu_device *vgdev = obj->dev->dev_private;
wait_event(vgdev->resp_wq, bo->uuid_state != STATE_INITIALIZING);
if (bo->uuid_state != STATE_OK)
return -ENODEV;
uuid_copy(uuid, &bo->uuid);
return 0;
}
static struct sg_table *
virtgpu_gem_map_dma_buf(struct dma_buf_attachment *attach,
enum dma_data_direction dir)
{
struct drm_gem_object *obj = attach->dmabuf->priv;
struct virtio_gpu_object *bo = gem_to_virtio_gpu_obj(obj);
if (virtio_gpu_is_vram(bo))
return virtio_gpu_vram_map_dma_buf(bo, attach->dev, dir);
return drm_gem_map_dma_buf(attach, dir);
}
static void virtgpu_gem_unmap_dma_buf(struct dma_buf_attachment *attach,
struct sg_table *sgt,
enum dma_data_direction dir)
{
struct drm_gem_object *obj = attach->dmabuf->priv;
struct virtio_gpu_object *bo = gem_to_virtio_gpu_obj(obj);
if (virtio_gpu_is_vram(bo)) {
virtio_gpu_vram_unmap_dma_buf(attach->dev, sgt, dir);
return;
}
drm_gem_unmap_dma_buf(attach, sgt, dir);
}
static const struct virtio_dma_buf_ops virtgpu_dmabuf_ops = {
.ops = {
.attach = virtio_dma_buf_attach,
.detach = drm_gem_map_detach,
.map_dma_buf = virtgpu_gem_map_dma_buf,
.unmap_dma_buf = virtgpu_gem_unmap_dma_buf,
.release = drm_gem_dmabuf_release,
.mmap = drm_gem_dmabuf_mmap,
.vmap = drm_gem_dmabuf_vmap,
.vunmap = drm_gem_dmabuf_vunmap,
},
.device_attach = drm_gem_map_attach,
.get_uuid = virtgpu_virtio_get_uuid,
};
int virtio_gpu_resource_assign_uuid(struct virtio_gpu_device *vgdev,
struct virtio_gpu_object *bo)
{
struct virtio_gpu_object_array *objs;
objs = virtio_gpu_array_alloc(1);
if (!objs)
return -ENOMEM;
virtio_gpu_array_add_obj(objs, &bo->base.base);
return virtio_gpu_cmd_resource_assign_uuid(vgdev, objs);
}
struct dma_buf *virtgpu_gem_prime_export(struct drm_gem_object *obj,
int flags)
{
struct dma_buf *buf;
struct drm_device *dev = obj->dev;
struct virtio_gpu_device *vgdev = dev->dev_private;
struct virtio_gpu_object *bo = gem_to_virtio_gpu_obj(obj);
int ret = 0;
bool blob = bo->host3d_blob || bo->guest_blob;
DEFINE_DMA_BUF_EXPORT_INFO(exp_info);
if (!blob) {
if (vgdev->has_resource_assign_uuid) {
ret = virtio_gpu_resource_assign_uuid(vgdev, bo);
if (ret)
return ERR_PTR(ret);
virtio_gpu_notify(vgdev);
} else {
bo->uuid_state = STATE_ERR;
}
} else if (!(bo->blob_flags & VIRTGPU_BLOB_FLAG_USE_CROSS_DEVICE)) {
bo->uuid_state = STATE_ERR;
}
exp_info.ops = &virtgpu_dmabuf_ops.ops;
exp_info.size = obj->size;
exp_info.flags = flags;
exp_info.priv = obj;
exp_info.resv = obj->resv;
buf = virtio_dma_buf_export(&exp_info);
if (IS_ERR(buf))
return buf;
drm_dev_get(dev);
drm_gem_object_get(obj);
return buf;
}
int virtgpu_dma_buf_import_sgt(struct virtio_gpu_mem_entry **ents,
unsigned int *nents,
struct virtio_gpu_object *bo,
struct dma_buf_attachment *attach)
{
struct scatterlist *sl;
struct sg_table *sgt;
long i, ret;
dma_resv_assert_held(attach->dmabuf->resv);
ret = dma_resv_wait_timeout(attach->dmabuf->resv,
DMA_RESV_USAGE_KERNEL,
false, MAX_SCHEDULE_TIMEOUT);
if (ret <= 0)
return ret < 0 ? ret : -ETIMEDOUT;
sgt = dma_buf_map_attachment(attach, DMA_BIDIRECTIONAL);
if (IS_ERR(sgt))
return PTR_ERR(sgt);
*ents = kvmalloc_objs(struct virtio_gpu_mem_entry, sgt->nents);
if (!(*ents)) {
dma_buf_unmap_attachment(attach, sgt, DMA_BIDIRECTIONAL);
return -ENOMEM;
}
*nents = sgt->nents;
for_each_sgtable_dma_sg(sgt, sl, i) {
(*ents)[i].addr = cpu_to_le64(sg_dma_address(sl));
(*ents)[i].length = cpu_to_le32(sg_dma_len(sl));
(*ents)[i].padding = 0;
}
bo->sgt = sgt;
return 0;
}
static void virtgpu_dma_buf_unmap(struct virtio_gpu_object *bo)
{
struct dma_buf_attachment *attach = bo->base.base.import_attach;
dma_resv_assert_held(attach->dmabuf->resv);
if (bo->created) {
virtio_gpu_detach_object_fenced(bo);
if (bo->sgt)
dma_buf_unmap_attachment(attach, bo->sgt,
DMA_BIDIRECTIONAL);
bo->sgt = NULL;
}
}
static void virtgpu_dma_buf_free_obj(struct drm_gem_object *obj)
{
struct virtio_gpu_object *bo = gem_to_virtio_gpu_obj(obj);
struct virtio_gpu_device *vgdev = obj->dev->dev_private;
struct dma_buf_attachment *attach = obj->import_attach;
if (drm_gem_is_imported(obj)) {
struct dma_buf *dmabuf = attach->dmabuf;
dma_resv_lock(dmabuf->resv, NULL);
virtgpu_dma_buf_unmap(bo);
dma_resv_unlock(dmabuf->resv);
dma_buf_detach(dmabuf, attach);
dma_buf_put(dmabuf);
}
if (bo->created) {
virtio_gpu_cmd_unref_resource(vgdev, bo);
virtio_gpu_notify(vgdev);
return;
}
virtio_gpu_cleanup_object(bo);
}
static int virtgpu_dma_buf_init_obj(struct drm_device *dev,
struct virtio_gpu_object *bo,
struct dma_buf_attachment *attach)
{
struct virtio_gpu_device *vgdev = dev->dev_private;
struct virtio_gpu_object_params params = { 0 };
struct dma_resv *resv = attach->dmabuf->resv;
struct virtio_gpu_mem_entry *ents = NULL;
unsigned int nents;
int ret;
ret = virtio_gpu_resource_id_get(vgdev, &bo->hw_res_handle);
if (ret) {
virtgpu_dma_buf_free_obj(&bo->base.base);
return ret;
}
dma_resv_lock(resv, NULL);
ret = dma_buf_pin(attach);
if (ret)
goto err_pin;
ret = virtgpu_dma_buf_import_sgt(&ents, &nents, bo, attach);
if (ret)
goto err_import;
params.blob = true;
params.blob_mem = VIRTGPU_BLOB_MEM_GUEST;
params.blob_flags = VIRTGPU_BLOB_FLAG_USE_SHAREABLE;
params.size = attach->dmabuf->size;
virtio_gpu_cmd_resource_create_blob(vgdev, bo, &params,
ents, nents);
bo->guest_blob = true;
dma_buf_unpin(attach);
dma_resv_unlock(resv);
return 0;
err_import:
dma_buf_unpin(attach);
err_pin:
dma_resv_unlock(resv);
virtgpu_dma_buf_free_obj(&bo->base.base);
return ret;
}
static const struct drm_gem_object_funcs virtgpu_gem_dma_buf_funcs = {
.free = virtgpu_dma_buf_free_obj,
};
static void virtgpu_dma_buf_move_notify(struct dma_buf_attachment *attach)
{
struct drm_gem_object *obj = attach->importer_priv;
struct virtio_gpu_object *bo = gem_to_virtio_gpu_obj(obj);
virtgpu_dma_buf_unmap(bo);
}
static const struct dma_buf_attach_ops virtgpu_dma_buf_attach_ops = {
.allow_peer2peer = true,
.invalidate_mappings = virtgpu_dma_buf_move_notify
};
struct drm_gem_object *virtgpu_gem_prime_import(struct drm_device *dev,
struct dma_buf *buf)
{
struct virtio_gpu_device *vgdev = dev->dev_private;
struct dma_buf_attachment *attach;
struct virtio_gpu_object *bo;
struct drm_gem_object *obj;
int ret;
if (buf->ops == &virtgpu_dmabuf_ops.ops) {
obj = buf->priv;
if (obj->dev == dev) {
/*
* Importing dmabuf exported from our own gem increases
* refcount on gem itself instead of f_count of dmabuf.
*/
drm_gem_object_get(obj);
return obj;
}
}
if (!vgdev->has_resource_blob)
return drm_gem_prime_import(dev, buf);
bo = kzalloc_obj(*bo);
if (!bo)
return ERR_PTR(-ENOMEM);
obj = &bo->base.base;
obj->resv = buf->resv;
obj->funcs = &virtgpu_gem_dma_buf_funcs;
drm_gem_private_object_init(dev, obj, buf->size);
attach = dma_buf_dynamic_attach(buf, dev->dev,
&virtgpu_dma_buf_attach_ops, obj);
if (IS_ERR(attach)) {
kfree(bo);
return ERR_CAST(attach);
}
obj->import_attach = attach;
get_dma_buf(buf);
ret = virtgpu_dma_buf_init_obj(dev, bo, attach);
if (ret < 0)
return ERR_PTR(ret);
return obj;
}
struct drm_gem_object *virtgpu_gem_prime_import_sg_table(
struct drm_device *dev, struct dma_buf_attachment *attach,
struct sg_table *table)
{
return ERR_PTR(-ENODEV);
}
@@ -0,0 +1,544 @@
// SPDX-License-Identifier: MIT
/*
* Copyright (C) 2015 Red Hat, Inc.
* All Rights Reserved.
*
* Authors:
* Dave Airlie
* Alon Levy
*/
#include <linux/dma-fence-unwrap.h>
#include <linux/file.h>
#include <linux/sync_file.h>
#include <linux/uaccess.h>
#include <drm/drm_file.h>
#include <drm/drm_syncobj.h>
#include <drm/virtgpu_drm.h>
#include "virtgpu_drv.h"
struct virtio_gpu_submit_post_dep {
struct drm_syncobj *syncobj;
struct dma_fence_chain *chain;
u64 point;
};
struct virtio_gpu_submit {
struct virtio_gpu_submit_post_dep *post_deps;
unsigned int num_out_syncobjs;
struct drm_syncobj **in_syncobjs;
unsigned int num_in_syncobjs;
struct virtio_gpu_object_array *buflist;
struct drm_virtgpu_execbuffer *exbuf;
struct virtio_gpu_fence *out_fence;
struct virtio_gpu_fpriv *vfpriv;
struct virtio_gpu_device *vgdev;
struct sync_file *sync_file;
struct drm_file *file;
int out_fence_fd;
u64 fence_ctx;
u32 ring_idx;
void *buf;
};
static int virtio_gpu_do_fence_wait(struct virtio_gpu_submit *submit,
struct dma_fence *in_fence)
{
u64 context = submit->fence_ctx + submit->ring_idx;
if (dma_fence_match_context(in_fence, context))
return 0;
return dma_fence_wait(in_fence, true);
}
static int virtio_gpu_dma_fence_wait(struct virtio_gpu_submit *submit,
struct dma_fence *fence)
{
struct dma_fence_unwrap itr;
struct dma_fence *f;
int err;
dma_fence_unwrap_for_each(f, &itr, fence) {
err = virtio_gpu_do_fence_wait(submit, f);
if (err) {
dma_fence_put(itr.chain);
return err;
}
}
return 0;
}
static void virtio_gpu_free_syncobjs(struct drm_syncobj **syncobjs,
u32 nr_syncobjs)
{
u32 i = nr_syncobjs;
while (i--) {
if (syncobjs[i])
drm_syncobj_put(syncobjs[i]);
}
kvfree(syncobjs);
}
static int
virtio_gpu_parse_deps(struct virtio_gpu_submit *submit)
{
struct drm_virtgpu_execbuffer *exbuf = submit->exbuf;
struct drm_virtgpu_execbuffer_syncobj syncobj_desc;
size_t syncobj_stride = exbuf->syncobj_stride;
u32 num_in_syncobjs = exbuf->num_in_syncobjs;
struct drm_syncobj **syncobjs;
int ret = 0, i;
if (!num_in_syncobjs)
return 0;
/*
* kvmalloc() at first tries to allocate memory using kmalloc() and
* falls back to vmalloc() only on failure. It also uses __GFP_NOWARN
* internally for allocations larger than a page size, preventing
* storm of KMSG warnings.
*/
syncobjs = kvzalloc_objs(*syncobjs, num_in_syncobjs);
if (!syncobjs)
return -ENOMEM;
for (i = 0; i < num_in_syncobjs; i++) {
u64 address = exbuf->in_syncobjs + i * syncobj_stride;
struct dma_fence *fence;
memset(&syncobj_desc, 0, sizeof(syncobj_desc));
if (copy_from_user(&syncobj_desc,
u64_to_user_ptr(address),
min(syncobj_stride, sizeof(syncobj_desc)))) {
ret = -EFAULT;
break;
}
if (syncobj_desc.flags & ~VIRTGPU_EXECBUF_SYNCOBJ_FLAGS) {
ret = -EINVAL;
break;
}
ret = drm_syncobj_find_fence(submit->file, syncobj_desc.handle,
syncobj_desc.point, 0, &fence);
if (ret)
break;
ret = virtio_gpu_dma_fence_wait(submit, fence);
dma_fence_put(fence);
if (ret)
break;
if (syncobj_desc.flags & VIRTGPU_EXECBUF_SYNCOBJ_RESET) {
syncobjs[i] = drm_syncobj_find(submit->file,
syncobj_desc.handle);
if (!syncobjs[i]) {
ret = -EINVAL;
break;
}
}
}
if (ret) {
virtio_gpu_free_syncobjs(syncobjs, i);
return ret;
}
submit->num_in_syncobjs = num_in_syncobjs;
submit->in_syncobjs = syncobjs;
return ret;
}
static void virtio_gpu_reset_syncobjs(struct drm_syncobj **syncobjs,
u32 nr_syncobjs)
{
u32 i;
for (i = 0; i < nr_syncobjs; i++) {
if (syncobjs[i])
drm_syncobj_replace_fence(syncobjs[i], NULL);
}
}
static void
virtio_gpu_free_post_deps(struct virtio_gpu_submit_post_dep *post_deps,
u32 nr_syncobjs)
{
u32 i = nr_syncobjs;
while (i--) {
kfree(post_deps[i].chain);
drm_syncobj_put(post_deps[i].syncobj);
}
kvfree(post_deps);
}
static int virtio_gpu_parse_post_deps(struct virtio_gpu_submit *submit)
{
struct drm_virtgpu_execbuffer *exbuf = submit->exbuf;
struct drm_virtgpu_execbuffer_syncobj syncobj_desc;
struct virtio_gpu_submit_post_dep *post_deps;
u32 num_out_syncobjs = exbuf->num_out_syncobjs;
size_t syncobj_stride = exbuf->syncobj_stride;
int ret = 0, i;
if (!num_out_syncobjs)
return 0;
post_deps = kvzalloc_objs(*post_deps, num_out_syncobjs);
if (!post_deps)
return -ENOMEM;
for (i = 0; i < num_out_syncobjs; i++) {
u64 address = exbuf->out_syncobjs + i * syncobj_stride;
memset(&syncobj_desc, 0, sizeof(syncobj_desc));
if (copy_from_user(&syncobj_desc,
u64_to_user_ptr(address),
min(syncobj_stride, sizeof(syncobj_desc)))) {
ret = -EFAULT;
break;
}
post_deps[i].point = syncobj_desc.point;
if (syncobj_desc.flags) {
ret = -EINVAL;
break;
}
if (syncobj_desc.point) {
post_deps[i].chain = dma_fence_chain_alloc();
if (!post_deps[i].chain) {
ret = -ENOMEM;
break;
}
}
post_deps[i].syncobj = drm_syncobj_find(submit->file,
syncobj_desc.handle);
if (!post_deps[i].syncobj) {
kfree(post_deps[i].chain);
ret = -EINVAL;
break;
}
}
if (ret) {
virtio_gpu_free_post_deps(post_deps, i);
return ret;
}
submit->num_out_syncobjs = num_out_syncobjs;
submit->post_deps = post_deps;
return 0;
}
static void
virtio_gpu_process_post_deps(struct virtio_gpu_submit *submit)
{
struct virtio_gpu_submit_post_dep *post_deps = submit->post_deps;
if (post_deps) {
struct dma_fence *fence = &submit->out_fence->f;
u32 i;
for (i = 0; i < submit->num_out_syncobjs; i++) {
if (post_deps[i].chain) {
drm_syncobj_add_point(post_deps[i].syncobj,
post_deps[i].chain,
fence, post_deps[i].point);
post_deps[i].chain = NULL;
} else {
drm_syncobj_replace_fence(post_deps[i].syncobj,
fence);
}
}
}
}
static int virtio_gpu_fence_event_create(struct drm_device *dev,
struct drm_file *file,
struct virtio_gpu_fence *fence,
u32 ring_idx)
{
struct virtio_gpu_fence_event *e = NULL;
int ret;
e = kzalloc_obj(*e);
if (!e)
return -ENOMEM;
e->event.type = VIRTGPU_EVENT_FENCE_SIGNALED;
e->event.length = sizeof(e->event);
ret = drm_event_reserve_init(dev, file, &e->base, &e->event);
if (ret) {
kfree(e);
return ret;
}
fence->e = e;
return 0;
}
static int virtio_gpu_init_submit_buflist(struct virtio_gpu_submit *submit)
{
struct drm_virtgpu_execbuffer *exbuf = submit->exbuf;
u32 *bo_handles;
if (!exbuf->num_bo_handles)
return 0;
bo_handles = kvmalloc_array(exbuf->num_bo_handles, sizeof(*bo_handles),
GFP_KERNEL);
if (!bo_handles)
return -ENOMEM;
if (copy_from_user(bo_handles, u64_to_user_ptr(exbuf->bo_handles),
exbuf->num_bo_handles * sizeof(*bo_handles))) {
kvfree(bo_handles);
return -EFAULT;
}
submit->buflist = virtio_gpu_array_from_handles(submit->file, bo_handles,
exbuf->num_bo_handles);
if (!submit->buflist) {
kvfree(bo_handles);
return -ENOENT;
}
kvfree(bo_handles);
return 0;
}
static void virtio_gpu_cleanup_submit(struct virtio_gpu_submit *submit)
{
virtio_gpu_reset_syncobjs(submit->in_syncobjs, submit->num_in_syncobjs);
virtio_gpu_free_syncobjs(submit->in_syncobjs, submit->num_in_syncobjs);
virtio_gpu_free_post_deps(submit->post_deps, submit->num_out_syncobjs);
if (!IS_ERR(submit->buf))
kvfree(submit->buf);
if (submit->buflist)
virtio_gpu_array_put_free(submit->buflist);
if (submit->out_fence_fd >= 0)
put_unused_fd(submit->out_fence_fd);
if (submit->out_fence)
dma_fence_put(&submit->out_fence->f);
if (submit->sync_file)
fput(submit->sync_file->file);
}
static void virtio_gpu_submit(struct virtio_gpu_submit *submit)
{
virtio_gpu_cmd_submit(submit->vgdev, submit->buf, submit->exbuf->size,
submit->vfpriv->ctx_id, submit->buflist,
submit->out_fence);
virtio_gpu_notify(submit->vgdev);
}
static void virtio_gpu_complete_submit(struct virtio_gpu_submit *submit)
{
submit->buf = NULL;
submit->buflist = NULL;
submit->sync_file = NULL;
submit->out_fence_fd = -1;
}
static int virtio_gpu_init_submit(struct virtio_gpu_submit *submit,
struct drm_virtgpu_execbuffer *exbuf,
struct drm_device *dev,
struct drm_file *file,
u64 fence_ctx, u32 ring_idx)
{
struct virtio_gpu_fpriv *vfpriv = file->driver_priv;
struct virtio_gpu_device *vgdev = dev->dev_private;
struct virtio_gpu_fence *out_fence;
bool drm_fence_event;
int err;
memset(submit, 0, sizeof(*submit));
if ((exbuf->flags & VIRTGPU_EXECBUF_RING_IDX) &&
(vfpriv->ring_idx_mask & BIT_ULL(ring_idx)))
drm_fence_event = true;
else
drm_fence_event = false;
if ((exbuf->flags & VIRTGPU_EXECBUF_FENCE_FD_OUT) ||
exbuf->num_out_syncobjs ||
exbuf->num_bo_handles ||
drm_fence_event)
out_fence = virtio_gpu_fence_alloc(vgdev, fence_ctx, ring_idx);
else
out_fence = NULL;
if (drm_fence_event) {
err = virtio_gpu_fence_event_create(dev, file, out_fence, ring_idx);
if (err) {
dma_fence_put(&out_fence->f);
return err;
}
}
submit->out_fence = out_fence;
submit->fence_ctx = fence_ctx;
submit->ring_idx = ring_idx;
submit->out_fence_fd = -1;
submit->vfpriv = vfpriv;
submit->vgdev = vgdev;
submit->exbuf = exbuf;
submit->file = file;
err = virtio_gpu_init_submit_buflist(submit);
if (err)
return err;
submit->buf = vmemdup_user(u64_to_user_ptr(exbuf->command), exbuf->size);
if (IS_ERR(submit->buf))
return PTR_ERR(submit->buf);
if (exbuf->flags & VIRTGPU_EXECBUF_FENCE_FD_OUT) {
err = get_unused_fd_flags(O_CLOEXEC);
if (err < 0)
return err;
submit->out_fence_fd = err;
submit->sync_file = sync_file_create(&out_fence->f);
if (!submit->sync_file)
return -ENOMEM;
}
return 0;
}
static int virtio_gpu_wait_in_fence(struct virtio_gpu_submit *submit)
{
int ret = 0;
if (submit->exbuf->flags & VIRTGPU_EXECBUF_FENCE_FD_IN) {
struct dma_fence *in_fence =
sync_file_get_fence(submit->exbuf->fence_fd);
if (!in_fence)
return -EINVAL;
/*
* Wait if the fence is from a foreign context, or if the
* fence array contains any fence from a foreign context.
*/
ret = virtio_gpu_dma_fence_wait(submit, in_fence);
dma_fence_put(in_fence);
}
return ret;
}
static void virtio_gpu_install_out_fence_fd(struct virtio_gpu_submit *submit)
{
if (submit->sync_file) {
submit->exbuf->fence_fd = submit->out_fence_fd;
fd_install(submit->out_fence_fd, submit->sync_file->file);
}
}
static int virtio_gpu_lock_buflist(struct virtio_gpu_submit *submit)
{
if (submit->buflist)
return virtio_gpu_array_lock_resv(submit->buflist);
return 0;
}
int virtio_gpu_execbuffer_ioctl(struct drm_device *dev, void *data,
struct drm_file *file)
{
struct virtio_gpu_device *vgdev = dev->dev_private;
struct virtio_gpu_fpriv *vfpriv = file->driver_priv;
u64 fence_ctx = vgdev->fence_drv.context;
struct drm_virtgpu_execbuffer *exbuf = data;
struct virtio_gpu_submit submit;
u32 ring_idx = 0;
int ret = -EINVAL;
if (!vgdev->has_virgl_3d)
return -ENOSYS;
if (exbuf->flags & ~VIRTGPU_EXECBUF_FLAGS)
return ret;
if (exbuf->flags & VIRTGPU_EXECBUF_RING_IDX) {
if (exbuf->ring_idx >= vfpriv->num_rings)
return ret;
if (!vfpriv->base_fence_ctx)
return ret;
fence_ctx = vfpriv->base_fence_ctx;
ring_idx = exbuf->ring_idx;
}
virtio_gpu_create_context(dev, file);
ret = virtio_gpu_init_submit(&submit, exbuf, dev, file,
fence_ctx, ring_idx);
if (ret)
goto cleanup;
ret = virtio_gpu_parse_post_deps(&submit);
if (ret)
goto cleanup;
ret = virtio_gpu_parse_deps(&submit);
if (ret)
goto cleanup;
/*
* Await in-fences in the end of the job submission path to
* optimize the path by proceeding directly to the submission
* to virtio after the waits.
*/
ret = virtio_gpu_wait_in_fence(&submit);
if (ret)
goto cleanup;
ret = virtio_gpu_lock_buflist(&submit);
if (ret)
goto cleanup;
virtio_gpu_submit(&submit);
/*
* Set up user-out data after submitting the job to optimize
* the job submission path.
*/
virtio_gpu_install_out_fence_fd(&submit);
virtio_gpu_process_post_deps(&submit);
virtio_gpu_complete_submit(&submit);
cleanup:
virtio_gpu_cleanup_submit(&submit);
return ret;
}
@@ -0,0 +1,56 @@
/* SPDX-License-Identifier: GPL-2.0 */
#if !defined(_VIRTGPU_TRACE_H_) || defined(TRACE_HEADER_MULTI_READ)
#define _VIRTGPU_TRACE_H_
#include <linux/tracepoint.h>
#undef TRACE_SYSTEM
#define TRACE_SYSTEM virtio_gpu
#define TRACE_INCLUDE_FILE virtgpu_trace
DECLARE_EVENT_CLASS(virtio_gpu_cmd,
TP_PROTO(struct virtqueue *vq, struct virtio_gpu_ctrl_hdr *hdr, u32 seqno),
TP_ARGS(vq, hdr, seqno),
TP_STRUCT__entry(
__field(int, dev)
__field(unsigned int, vq)
__string(name, vq->name)
__field(u32, type)
__field(u32, flags)
__field(u64, fence_id)
__field(u32, ctx_id)
__field(u32, num_free)
__field(u32, seqno)
),
TP_fast_assign(
__entry->dev = vq->vdev->index;
__entry->vq = vq->index;
__assign_str(name);
__entry->type = le32_to_cpu(hdr->type);
__entry->flags = le32_to_cpu(hdr->flags);
__entry->fence_id = le64_to_cpu(hdr->fence_id);
__entry->ctx_id = le32_to_cpu(hdr->ctx_id);
__entry->num_free = vq->num_free;
__entry->seqno = seqno;
),
TP_printk("vdev=%d vq=%u name=%s type=0x%x flags=0x%x fence_id=%llu ctx_id=%u num_free=%u seqno=%u",
__entry->dev, __entry->vq, __get_str(name),
__entry->type, __entry->flags, __entry->fence_id,
__entry->ctx_id, __entry->num_free, __entry->seqno)
);
DEFINE_EVENT(virtio_gpu_cmd, virtio_gpu_cmd_queue,
TP_PROTO(struct virtqueue *vq, struct virtio_gpu_ctrl_hdr *hdr, u32 seqno),
TP_ARGS(vq, hdr, seqno)
);
DEFINE_EVENT(virtio_gpu_cmd, virtio_gpu_cmd_response,
TP_PROTO(struct virtqueue *vq, struct virtio_gpu_ctrl_hdr *hdr, u32 seqno),
TP_ARGS(vq, hdr, seqno)
);
#endif
#undef TRACE_INCLUDE_PATH
#define TRACE_INCLUDE_PATH ../../drivers/gpu/drm/virtio
#include <trace/define_trace.h>
@@ -0,0 +1,5 @@
// SPDX-License-Identifier: GPL-2.0
#include "virtgpu_drv.h"
#define CREATE_TRACE_POINTS
#include "virtgpu_trace.h"
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,251 @@
// SPDX-License-Identifier: GPL-2.0
#include "virtgpu_drv.h"
#include <linux/dma-mapping.h>
static DEFINE_MUTEX(map_lock);
static void virtio_gpu_vram_free(struct drm_gem_object *obj)
{
struct virtio_gpu_object *bo = gem_to_virtio_gpu_obj(obj);
struct virtio_gpu_device *vgdev = obj->dev->dev_private;
struct virtio_gpu_object_vram *vram = to_virtio_gpu_vram(bo);
bool unmap;
if (bo->created) {
spin_lock(&vgdev->host_visible_lock);
unmap = drm_mm_node_allocated(&vram->vram_node);
spin_unlock(&vgdev->host_visible_lock);
if (unmap)
virtio_gpu_cmd_unmap(vgdev, bo);
virtio_gpu_cmd_unref_resource(vgdev, bo);
virtio_gpu_notify(vgdev);
return;
}
}
static const struct vm_operations_struct virtio_gpu_vram_vm_ops = {
.open = drm_gem_vm_open,
.close = drm_gem_vm_close,
};
static int virtio_gpu_vram_mmap(struct drm_gem_object *obj,
struct vm_area_struct *vma)
{
int ret;
struct virtio_gpu_device *vgdev = obj->dev->dev_private;
struct virtio_gpu_object *bo = gem_to_virtio_gpu_obj(obj);
struct virtio_gpu_object_vram *vram = to_virtio_gpu_vram(bo);
unsigned long vm_size = vma->vm_end - vma->vm_start;
unsigned long vm_end;
if (!(bo->blob_flags & VIRTGPU_BLOB_FLAG_USE_MAPPABLE))
return -EINVAL;
virtio_gpu_vram_map_deferred(vram);
if (vram->map_state == STATE_INITIALIZING)
virtio_gpu_notify(vgdev);
wait_event(vgdev->resp_wq, vram->map_state != STATE_INITIALIZING);
if (vram->map_state != STATE_OK)
return -EINVAL;
vma->vm_pgoff -= drm_vma_node_start(&obj->vma_node);
vm_flags_set(vma, VM_MIXEDMAP | VM_DONTEXPAND);
vma->vm_page_prot = vm_get_page_prot(vma->vm_flags);
vma->vm_page_prot = pgprot_decrypted(vma->vm_page_prot);
vma->vm_ops = &virtio_gpu_vram_vm_ops;
if (vram->map_info == VIRTIO_GPU_MAP_CACHE_WC)
vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot);
else if (vram->map_info == VIRTIO_GPU_MAP_CACHE_UNCACHED)
vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
if (check_add_overflow(vma->vm_pgoff << PAGE_SHIFT, vm_size, &vm_end))
return -EINVAL;
if (vm_end > vram->vram_node.size)
return -EINVAL;
ret = io_remap_pfn_range(vma, vma->vm_start,
(vram->vram_node.start >> PAGE_SHIFT) + vma->vm_pgoff,
vm_size, vma->vm_page_prot);
return ret;
}
struct sg_table *virtio_gpu_vram_map_dma_buf(struct virtio_gpu_object *bo,
struct device *dev,
enum dma_data_direction dir)
{
struct virtio_gpu_device *vgdev = bo->base.base.dev->dev_private;
struct virtio_gpu_object_vram *vram = to_virtio_gpu_vram(bo);
struct sg_table *sgt;
dma_addr_t addr;
int ret;
sgt = kzalloc_obj(*sgt);
if (!sgt)
return ERR_PTR(-ENOMEM);
if (!(bo->blob_flags & VIRTGPU_BLOB_FLAG_USE_MAPPABLE)) {
// Virtio devices can access the dma-buf via its UUID. Return a stub
// sg_table so the dma-buf API still works.
if (!is_virtio_device(dev) || !vgdev->has_resource_assign_uuid) {
ret = -EIO;
goto out;
}
return sgt;
}
ret = sg_alloc_table(sgt, 1, GFP_KERNEL);
if (ret)
goto out;
addr = dma_map_resource(dev, vram->vram_node.start,
vram->vram_node.size, dir,
DMA_ATTR_SKIP_CPU_SYNC);
ret = dma_mapping_error(dev, addr);
if (ret)
goto out;
sg_set_page(sgt->sgl, NULL, vram->vram_node.size, 0);
sg_dma_address(sgt->sgl) = addr;
sg_dma_len(sgt->sgl) = vram->vram_node.size;
return sgt;
out:
sg_free_table(sgt);
kfree(sgt);
return ERR_PTR(ret);
}
void virtio_gpu_vram_unmap_dma_buf(struct device *dev,
struct sg_table *sgt,
enum dma_data_direction dir)
{
if (sgt->nents) {
dma_unmap_resource(dev, sg_dma_address(sgt->sgl),
sg_dma_len(sgt->sgl), dir,
DMA_ATTR_SKIP_CPU_SYNC);
}
sg_free_table(sgt);
kfree(sgt);
}
static const struct drm_gem_object_funcs virtio_gpu_vram_funcs = {
.open = virtio_gpu_gem_object_open,
.close = virtio_gpu_gem_object_close,
.free = virtio_gpu_vram_free,
.mmap = virtio_gpu_vram_mmap,
.export = virtgpu_gem_prime_export,
};
bool virtio_gpu_is_vram(struct virtio_gpu_object *bo)
{
return bo->base.base.funcs == &virtio_gpu_vram_funcs;
}
static int virtio_gpu_vram_map(struct virtio_gpu_object *bo)
{
int ret;
uint64_t offset;
struct virtio_gpu_object_array *objs;
struct virtio_gpu_device *vgdev = bo->base.base.dev->dev_private;
struct virtio_gpu_object_vram *vram = to_virtio_gpu_vram(bo);
if (!vgdev->has_host_visible)
return -EINVAL;
spin_lock(&vgdev->host_visible_lock);
ret = drm_mm_insert_node(&vgdev->host_visible_mm, &vram->vram_node,
bo->base.base.size);
spin_unlock(&vgdev->host_visible_lock);
if (ret)
return ret;
objs = virtio_gpu_array_alloc(1);
if (!objs) {
ret = -ENOMEM;
goto err_remove_node;
}
virtio_gpu_array_add_obj(objs, &bo->base.base);
/*TODO: Add an error checking helper function in drm_mm.h */
offset = vram->vram_node.start - vgdev->host_visible_region.addr;
ret = virtio_gpu_cmd_map(vgdev, objs, offset);
if (ret) {
virtio_gpu_array_put_free(objs);
goto err_remove_node;
}
return 0;
err_remove_node:
spin_lock(&vgdev->host_visible_lock);
drm_mm_remove_node(&vram->vram_node);
spin_unlock(&vgdev->host_visible_lock);
return ret;
}
int virtio_gpu_vram_create(struct virtio_gpu_device *vgdev,
struct virtio_gpu_object_params *params,
struct virtio_gpu_object **bo_ptr)
{
struct drm_gem_object *obj;
struct virtio_gpu_object_vram *vram;
int ret;
vram = kzalloc_obj(*vram);
if (!vram)
return -ENOMEM;
obj = &vram->base.base.base;
obj->funcs = &virtio_gpu_vram_funcs;
params->size = PAGE_ALIGN(params->size);
drm_gem_private_object_init(vgdev->ddev, obj, params->size);
/* Create fake offset */
ret = drm_gem_create_mmap_offset(obj);
if (ret) {
kfree(vram);
return ret;
}
ret = virtio_gpu_resource_id_get(vgdev, &vram->base.hw_res_handle);
if (ret) {
kfree(vram);
return ret;
}
virtio_gpu_cmd_resource_create_blob(vgdev, &vram->base, params, NULL,
0);
if (!(params->blob_hints & DRM_VIRTGPU_BLOB_FLAG_HINT_DEFER_MAPPING)) {
if (params->blob_flags & VIRTGPU_BLOB_FLAG_USE_MAPPABLE) {
ret = virtio_gpu_vram_map(&vram->base);
if (ret) {
virtio_gpu_vram_free(obj);
return ret;
}
}
}
*bo_ptr = &vram->base;
return 0;
}
void virtio_gpu_vram_map_deferred(struct virtio_gpu_object_vram *vram)
{
if (!(vram->base.blob_flags & VIRTGPU_BLOB_FLAG_USE_MAPPABLE))
return;
mutex_lock(&map_lock);
if (!drm_mm_node_allocated(&vram->vram_node))
virtio_gpu_vram_map(&vram->base);
mutex_unlock(&map_lock);
}