linux-stable/drivers/gpu/drm/xe/xe_engine.c

735 lines
17 KiB
C

// SPDX-License-Identifier: MIT
/*
* Copyright © 2021 Intel Corporation
*/
#include "xe_engine.h"
#include <drm/drm_device.h>
#include <drm/drm_file.h>
#include <drm/xe_drm.h>
#include <linux/nospec.h>
#include "xe_device.h"
#include "xe_gt.h"
#include "xe_lrc.h"
#include "xe_macros.h"
#include "xe_migrate.h"
#include "xe_pm.h"
#include "xe_trace.h"
#include "xe_vm.h"
static struct xe_engine *__xe_engine_create(struct xe_device *xe,
struct xe_vm *vm,
u32 logical_mask,
u16 width, struct xe_hw_engine *hwe,
u32 flags)
{
struct xe_engine *e;
struct xe_gt *gt = hwe->gt;
int err;
int i;
e = kzalloc(sizeof(*e) + sizeof(struct xe_lrc) * width, GFP_KERNEL);
if (!e)
return ERR_PTR(-ENOMEM);
kref_init(&e->refcount);
e->flags = flags;
e->hwe = hwe;
e->gt = gt;
if (vm)
e->vm = xe_vm_get(vm);
e->class = hwe->class;
e->width = width;
e->logical_mask = logical_mask;
e->fence_irq = &gt->fence_irq[hwe->class];
e->ring_ops = gt->ring_ops[hwe->class];
e->ops = gt->engine_ops;
INIT_LIST_HEAD(&e->persitent.link);
INIT_LIST_HEAD(&e->compute.link);
INIT_LIST_HEAD(&e->multi_gt_link);
/* FIXME: Wire up to configurable default value */
e->sched_props.timeslice_us = 1 * 1000;
e->sched_props.preempt_timeout_us = 640 * 1000;
if (xe_engine_is_parallel(e)) {
e->parallel.composite_fence_ctx = dma_fence_context_alloc(1);
e->parallel.composite_fence_seqno = 1;
}
if (e->flags & ENGINE_FLAG_VM) {
e->bind.fence_ctx = dma_fence_context_alloc(1);
e->bind.fence_seqno = 1;
}
for (i = 0; i < width; ++i) {
err = xe_lrc_init(e->lrc + i, hwe, e, vm, SZ_16K);
if (err)
goto err_lrc;
}
err = e->ops->init(e);
if (err)
goto err_lrc;
return e;
err_lrc:
for (i = i - 1; i >= 0; --i)
xe_lrc_finish(e->lrc + i);
kfree(e);
return ERR_PTR(err);
}
struct xe_engine *xe_engine_create(struct xe_device *xe, struct xe_vm *vm,
u32 logical_mask, u16 width,
struct xe_hw_engine *hwe, u32 flags)
{
struct ww_acquire_ctx ww;
struct xe_engine *e;
int err;
if (vm) {
err = xe_vm_lock(vm, &ww, 0, true);
if (err)
return ERR_PTR(err);
}
e = __xe_engine_create(xe, vm, logical_mask, width, hwe, flags);
if (vm)
xe_vm_unlock(vm, &ww);
return e;
}
struct xe_engine *xe_engine_create_class(struct xe_device *xe, struct xe_gt *gt,
struct xe_vm *vm,
enum xe_engine_class class, u32 flags)
{
struct xe_hw_engine *hwe, *hwe0 = NULL;
enum xe_hw_engine_id id;
u32 logical_mask = 0;
for_each_hw_engine(hwe, gt, id) {
if (xe_hw_engine_is_reserved(hwe))
continue;
if (hwe->class == class) {
logical_mask |= BIT(hwe->logical_instance);
if (!hwe0)
hwe0 = hwe;
}
}
if (!logical_mask)
return ERR_PTR(-ENODEV);
return xe_engine_create(xe, vm, logical_mask, 1, hwe0, flags);
}
void xe_engine_destroy(struct kref *ref)
{
struct xe_engine *e = container_of(ref, struct xe_engine, refcount);
struct xe_engine *engine, *next;
if (!(e->flags & ENGINE_FLAG_BIND_ENGINE_CHILD)) {
list_for_each_entry_safe(engine, next, &e->multi_gt_list,
multi_gt_link)
xe_engine_put(engine);
}
e->ops->fini(e);
}
void xe_engine_fini(struct xe_engine *e)
{
int i;
for (i = 0; i < e->width; ++i)
xe_lrc_finish(e->lrc + i);
if (e->vm)
xe_vm_put(e->vm);
kfree(e);
}
struct xe_engine *xe_engine_lookup(struct xe_file *xef, u32 id)
{
struct xe_engine *e;
mutex_lock(&xef->engine.lock);
e = xa_load(&xef->engine.xa, id);
mutex_unlock(&xef->engine.lock);
if (e)
xe_engine_get(e);
return e;
}
static int engine_set_priority(struct xe_device *xe, struct xe_engine *e,
u64 value, bool create)
{
if (XE_IOCTL_ERR(xe, value > XE_ENGINE_PRIORITY_HIGH))
return -EINVAL;
if (XE_IOCTL_ERR(xe, value == XE_ENGINE_PRIORITY_HIGH &&
!capable(CAP_SYS_NICE)))
return -EPERM;
return e->ops->set_priority(e, value);
}
static int engine_set_timeslice(struct xe_device *xe, struct xe_engine *e,
u64 value, bool create)
{
if (!capable(CAP_SYS_NICE))
return -EPERM;
return e->ops->set_timeslice(e, value);
}
static int engine_set_preemption_timeout(struct xe_device *xe,
struct xe_engine *e, u64 value,
bool create)
{
if (!capable(CAP_SYS_NICE))
return -EPERM;
return e->ops->set_preempt_timeout(e, value);
}
static int engine_set_compute_mode(struct xe_device *xe, struct xe_engine *e,
u64 value, bool create)
{
if (XE_IOCTL_ERR(xe, !create))
return -EINVAL;
if (XE_IOCTL_ERR(xe, e->flags & ENGINE_FLAG_COMPUTE_MODE))
return -EINVAL;
if (XE_IOCTL_ERR(xe, e->flags & ENGINE_FLAG_VM))
return -EINVAL;
if (value) {
struct xe_vm *vm = e->vm;
int err;
if (XE_IOCTL_ERR(xe, xe_vm_in_fault_mode(vm)))
return -EOPNOTSUPP;
if (XE_IOCTL_ERR(xe, !xe_vm_in_compute_mode(vm)))
return -EOPNOTSUPP;
if (XE_IOCTL_ERR(xe, e->width != 1))
return -EINVAL;
e->compute.context = dma_fence_context_alloc(1);
spin_lock_init(&e->compute.lock);
err = xe_vm_add_compute_engine(vm, e);
if (XE_IOCTL_ERR(xe, err))
return err;
e->flags |= ENGINE_FLAG_COMPUTE_MODE;
e->flags &= ~ENGINE_FLAG_PERSISTENT;
}
return 0;
}
static int engine_set_persistence(struct xe_device *xe, struct xe_engine *e,
u64 value, bool create)
{
if (XE_IOCTL_ERR(xe, !create))
return -EINVAL;
if (XE_IOCTL_ERR(xe, e->flags & ENGINE_FLAG_COMPUTE_MODE))
return -EINVAL;
if (value)
e->flags |= ENGINE_FLAG_PERSISTENT;
else
e->flags &= ~ENGINE_FLAG_PERSISTENT;
return 0;
}
static int engine_set_job_timeout(struct xe_device *xe, struct xe_engine *e,
u64 value, bool create)
{
if (XE_IOCTL_ERR(xe, !create))
return -EINVAL;
if (!capable(CAP_SYS_NICE))
return -EPERM;
return e->ops->set_job_timeout(e, value);
}
static int engine_set_acc_trigger(struct xe_device *xe, struct xe_engine *e,
u64 value, bool create)
{
if (XE_IOCTL_ERR(xe, !create))
return -EINVAL;
if (XE_IOCTL_ERR(xe, !xe->info.supports_usm))
return -EINVAL;
e->usm.acc_trigger = value;
return 0;
}
static int engine_set_acc_notify(struct xe_device *xe, struct xe_engine *e,
u64 value, bool create)
{
if (XE_IOCTL_ERR(xe, !create))
return -EINVAL;
if (XE_IOCTL_ERR(xe, !xe->info.supports_usm))
return -EINVAL;
e->usm.acc_notify = value;
return 0;
}
static int engine_set_acc_granularity(struct xe_device *xe, struct xe_engine *e,
u64 value, bool create)
{
if (XE_IOCTL_ERR(xe, !create))
return -EINVAL;
if (XE_IOCTL_ERR(xe, !xe->info.supports_usm))
return -EINVAL;
e->usm.acc_granularity = value;
return 0;
}
typedef int (*xe_engine_set_property_fn)(struct xe_device *xe,
struct xe_engine *e,
u64 value, bool create);
static const xe_engine_set_property_fn engine_set_property_funcs[] = {
[XE_ENGINE_PROPERTY_PRIORITY] = engine_set_priority,
[XE_ENGINE_PROPERTY_TIMESLICE] = engine_set_timeslice,
[XE_ENGINE_PROPERTY_PREEMPTION_TIMEOUT] = engine_set_preemption_timeout,
[XE_ENGINE_PROPERTY_COMPUTE_MODE] = engine_set_compute_mode,
[XE_ENGINE_PROPERTY_PERSISTENCE] = engine_set_persistence,
[XE_ENGINE_PROPERTY_JOB_TIMEOUT] = engine_set_job_timeout,
[XE_ENGINE_PROPERTY_ACC_TRIGGER] = engine_set_acc_trigger,
[XE_ENGINE_PROPERTY_ACC_NOTIFY] = engine_set_acc_notify,
[XE_ENGINE_PROPERTY_ACC_GRANULARITY] = engine_set_acc_granularity,
};
static int engine_user_ext_set_property(struct xe_device *xe,
struct xe_engine *e,
u64 extension,
bool create)
{
u64 __user *address = u64_to_user_ptr(extension);
struct drm_xe_ext_engine_set_property ext;
int err;
u32 idx;
err = __copy_from_user(&ext, address, sizeof(ext));
if (XE_IOCTL_ERR(xe, err))
return -EFAULT;
if (XE_IOCTL_ERR(xe, ext.property >=
ARRAY_SIZE(engine_set_property_funcs)))
return -EINVAL;
idx = array_index_nospec(ext.property, ARRAY_SIZE(engine_set_property_funcs));
return engine_set_property_funcs[idx](xe, e, ext.value, create);
}
typedef int (*xe_engine_user_extension_fn)(struct xe_device *xe,
struct xe_engine *e,
u64 extension,
bool create);
static const xe_engine_set_property_fn engine_user_extension_funcs[] = {
[XE_ENGINE_EXTENSION_SET_PROPERTY] = engine_user_ext_set_property,
};
#define MAX_USER_EXTENSIONS 16
static int engine_user_extensions(struct xe_device *xe, struct xe_engine *e,
u64 extensions, int ext_number, bool create)
{
u64 __user *address = u64_to_user_ptr(extensions);
struct xe_user_extension ext;
int err;
u32 idx;
if (XE_IOCTL_ERR(xe, ext_number >= MAX_USER_EXTENSIONS))
return -E2BIG;
err = __copy_from_user(&ext, address, sizeof(ext));
if (XE_IOCTL_ERR(xe, err))
return -EFAULT;
if (XE_IOCTL_ERR(xe, ext.name >=
ARRAY_SIZE(engine_user_extension_funcs)))
return -EINVAL;
idx = array_index_nospec(ext.name,
ARRAY_SIZE(engine_user_extension_funcs));
err = engine_user_extension_funcs[idx](xe, e, extensions, create);
if (XE_IOCTL_ERR(xe, err))
return err;
if (ext.next_extension)
return engine_user_extensions(xe, e, ext.next_extension,
++ext_number, create);
return 0;
}
static const enum xe_engine_class user_to_xe_engine_class[] = {
[DRM_XE_ENGINE_CLASS_RENDER] = XE_ENGINE_CLASS_RENDER,
[DRM_XE_ENGINE_CLASS_COPY] = XE_ENGINE_CLASS_COPY,
[DRM_XE_ENGINE_CLASS_VIDEO_DECODE] = XE_ENGINE_CLASS_VIDEO_DECODE,
[DRM_XE_ENGINE_CLASS_VIDEO_ENHANCE] = XE_ENGINE_CLASS_VIDEO_ENHANCE,
[DRM_XE_ENGINE_CLASS_COMPUTE] = XE_ENGINE_CLASS_COMPUTE,
};
static struct xe_hw_engine *
find_hw_engine(struct xe_device *xe,
struct drm_xe_engine_class_instance eci)
{
u32 idx;
if (eci.engine_class > ARRAY_SIZE(user_to_xe_engine_class))
return NULL;
if (eci.gt_id >= xe->info.tile_count)
return NULL;
idx = array_index_nospec(eci.engine_class,
ARRAY_SIZE(user_to_xe_engine_class));
return xe_gt_hw_engine(xe_device_get_gt(xe, eci.gt_id),
user_to_xe_engine_class[idx],
eci.engine_instance, true);
}
static u32 bind_engine_logical_mask(struct xe_device *xe, struct xe_gt *gt,
struct drm_xe_engine_class_instance *eci,
u16 width, u16 num_placements)
{
struct xe_hw_engine *hwe;
enum xe_hw_engine_id id;
u32 logical_mask = 0;
if (XE_IOCTL_ERR(xe, width != 1))
return 0;
if (XE_IOCTL_ERR(xe, num_placements != 1))
return 0;
if (XE_IOCTL_ERR(xe, eci[0].engine_instance != 0))
return 0;
eci[0].engine_class = DRM_XE_ENGINE_CLASS_COPY;
for_each_hw_engine(hwe, gt, id) {
if (xe_hw_engine_is_reserved(hwe))
continue;
if (hwe->class ==
user_to_xe_engine_class[DRM_XE_ENGINE_CLASS_COPY])
logical_mask |= BIT(hwe->logical_instance);
}
return logical_mask;
}
static u32 calc_validate_logical_mask(struct xe_device *xe, struct xe_gt *gt,
struct drm_xe_engine_class_instance *eci,
u16 width, u16 num_placements)
{
int len = width * num_placements;
int i, j, n;
u16 class;
u16 gt_id;
u32 return_mask = 0, prev_mask;
if (XE_IOCTL_ERR(xe, !xe_device_guc_submission_enabled(xe) &&
len > 1))
return 0;
for (i = 0; i < width; ++i) {
u32 current_mask = 0;
for (j = 0; j < num_placements; ++j) {
struct xe_hw_engine *hwe;
n = j * width + i;
hwe = find_hw_engine(xe, eci[n]);
if (XE_IOCTL_ERR(xe, !hwe))
return 0;
if (XE_IOCTL_ERR(xe, xe_hw_engine_is_reserved(hwe)))
return 0;
if (XE_IOCTL_ERR(xe, n && eci[n].gt_id != gt_id) ||
XE_IOCTL_ERR(xe, n && eci[n].engine_class != class))
return 0;
class = eci[n].engine_class;
gt_id = eci[n].gt_id;
if (width == 1 || !i)
return_mask |= BIT(eci[n].engine_instance);
current_mask |= BIT(eci[n].engine_instance);
}
/* Parallel submissions must be logically contiguous */
if (i && XE_IOCTL_ERR(xe, current_mask != prev_mask << 1))
return 0;
prev_mask = current_mask;
}
return return_mask;
}
int xe_engine_create_ioctl(struct drm_device *dev, void *data,
struct drm_file *file)
{
struct xe_device *xe = to_xe_device(dev);
struct xe_file *xef = to_xe_file(file);
struct drm_xe_engine_create *args = data;
struct drm_xe_engine_class_instance eci[XE_HW_ENGINE_MAX_INSTANCE];
struct drm_xe_engine_class_instance __user *user_eci =
u64_to_user_ptr(args->instances);
struct xe_hw_engine *hwe;
struct xe_vm *vm, *migrate_vm;
struct xe_gt *gt;
struct xe_engine *e = NULL;
u32 logical_mask;
u32 id;
int len;
int err;
if (XE_IOCTL_ERR(xe, args->flags))
return -EINVAL;
len = args->width * args->num_placements;
if (XE_IOCTL_ERR(xe, !len || len > XE_HW_ENGINE_MAX_INSTANCE))
return -EINVAL;
err = __copy_from_user(eci, user_eci,
sizeof(struct drm_xe_engine_class_instance) *
len);
if (XE_IOCTL_ERR(xe, err))
return -EFAULT;
if (XE_IOCTL_ERR(xe, eci[0].gt_id >= xe->info.tile_count))
return -EINVAL;
xe_pm_runtime_get(xe);
if (eci[0].engine_class == DRM_XE_ENGINE_CLASS_VM_BIND) {
for_each_gt(gt, xe, id) {
struct xe_engine *new;
if (xe_gt_is_media_type(gt))
continue;
eci[0].gt_id = gt->info.id;
logical_mask = bind_engine_logical_mask(xe, gt, eci,
args->width,
args->num_placements);
if (XE_IOCTL_ERR(xe, !logical_mask)) {
err = -EINVAL;
goto put_rpm;
}
hwe = find_hw_engine(xe, eci[0]);
if (XE_IOCTL_ERR(xe, !hwe)) {
err = -EINVAL;
goto put_rpm;
}
migrate_vm = xe_migrate_get_vm(gt->migrate);
new = xe_engine_create(xe, migrate_vm, logical_mask,
args->width, hwe,
ENGINE_FLAG_PERSISTENT |
ENGINE_FLAG_VM |
(id ?
ENGINE_FLAG_BIND_ENGINE_CHILD :
0));
xe_vm_put(migrate_vm);
if (IS_ERR(new)) {
err = PTR_ERR(new);
if (e)
goto put_engine;
goto put_rpm;
}
if (id == 0)
e = new;
else
list_add_tail(&new->multi_gt_list,
&e->multi_gt_link);
}
} else {
gt = xe_device_get_gt(xe, eci[0].gt_id);
logical_mask = calc_validate_logical_mask(xe, gt, eci,
args->width,
args->num_placements);
if (XE_IOCTL_ERR(xe, !logical_mask)) {
err = -EINVAL;
goto put_rpm;
}
hwe = find_hw_engine(xe, eci[0]);
if (XE_IOCTL_ERR(xe, !hwe)) {
err = -EINVAL;
goto put_rpm;
}
vm = xe_vm_lookup(xef, args->vm_id);
if (XE_IOCTL_ERR(xe, !vm)) {
err = -ENOENT;
goto put_rpm;
}
e = xe_engine_create(xe, vm, logical_mask,
args->width, hwe, ENGINE_FLAG_PERSISTENT);
xe_vm_put(vm);
if (IS_ERR(e)) {
err = PTR_ERR(e);
goto put_rpm;
}
}
if (args->extensions) {
err = engine_user_extensions(xe, e, args->extensions, 0, true);
if (XE_IOCTL_ERR(xe, err))
goto put_engine;
}
if (XE_IOCTL_ERR(xe, e->vm && xe_vm_in_compute_mode(e->vm) !=
!!(e->flags & ENGINE_FLAG_COMPUTE_MODE))) {
err = -ENOTSUPP;
goto put_engine;
}
e->persitent.xef = xef;
mutex_lock(&xef->engine.lock);
err = xa_alloc(&xef->engine.xa, &id, e, xa_limit_32b, GFP_KERNEL);
mutex_unlock(&xef->engine.lock);
if (err)
goto put_engine;
args->engine_id = id;
return 0;
put_engine:
xe_engine_kill(e);
xe_engine_put(e);
put_rpm:
xe_pm_runtime_put(xe);
return err;
}
static void engine_kill_compute(struct xe_engine *e)
{
if (!xe_vm_in_compute_mode(e->vm))
return;
down_write(&e->vm->lock);
list_del(&e->compute.link);
--e->vm->preempt.num_engines;
if (e->compute.pfence) {
dma_fence_enable_sw_signaling(e->compute.pfence);
dma_fence_put(e->compute.pfence);
e->compute.pfence = NULL;
}
up_write(&e->vm->lock);
}
void xe_engine_kill(struct xe_engine *e)
{
struct xe_engine *engine = e, *next;
list_for_each_entry_safe(engine, next, &engine->multi_gt_list,
multi_gt_link) {
e->ops->kill(engine);
engine_kill_compute(engine);
}
e->ops->kill(e);
engine_kill_compute(e);
}
int xe_engine_destroy_ioctl(struct drm_device *dev, void *data,
struct drm_file *file)
{
struct xe_device *xe = to_xe_device(dev);
struct xe_file *xef = to_xe_file(file);
struct drm_xe_engine_destroy *args = data;
struct xe_engine *e;
if (XE_IOCTL_ERR(xe, args->pad))
return -EINVAL;
mutex_lock(&xef->engine.lock);
e = xa_erase(&xef->engine.xa, args->engine_id);
mutex_unlock(&xef->engine.lock);
if (XE_IOCTL_ERR(xe, !e))
return -ENOENT;
if (!(e->flags & ENGINE_FLAG_PERSISTENT))
xe_engine_kill(e);
else
xe_device_add_persitent_engines(xe, e);
trace_xe_engine_close(e);
xe_engine_put(e);
xe_pm_runtime_put(xe);
return 0;
}
int xe_engine_set_property_ioctl(struct drm_device *dev, void *data,
struct drm_file *file)
{
struct xe_device *xe = to_xe_device(dev);
struct xe_file *xef = to_xe_file(file);
struct drm_xe_engine_set_property *args = data;
struct xe_engine *e;
int ret;
u32 idx;
e = xe_engine_lookup(xef, args->engine_id);
if (XE_IOCTL_ERR(xe, !e))
return -ENOENT;
if (XE_IOCTL_ERR(xe, args->property >=
ARRAY_SIZE(engine_set_property_funcs))) {
ret = -EINVAL;
goto out;
}
idx = array_index_nospec(args->property,
ARRAY_SIZE(engine_set_property_funcs));
ret = engine_set_property_funcs[idx](xe, e, args->value, false);
if (XE_IOCTL_ERR(xe, ret))
goto out;
if (args->extensions)
ret = engine_user_extensions(xe, e, args->extensions, 0,
false);
out:
xe_engine_put(e);
return ret;
}