blob: 31897829842025de53e79221717764535c1d67e6 [file] [edit]
// SPDX-License-Identifier: MIT
/*
* Copyright © 2026 Intel Corporation
*/
#include <drm/drm_managed.h>
#include <linux/mutex.h>
#include "xe_cpu_bind.h"
#include "xe_device_types.h"
#include "xe_exec_queue.h"
#include "xe_pt.h"
#include "xe_sched_job.h"
#include "xe_trace_bo.h"
#include "xe_vm.h"
/**
* struct xe_cpu_bind - cpu_bind context.
*/
struct xe_cpu_bind {
/** @xe: Xe device */
struct xe_device *xe;
/** @q: Default exec queue used for kernel binds */
struct xe_exec_queue *q;
/** @job_mutex: Timeline mutex for @q. */
struct mutex job_mutex;
};
static bool is_cpu_bind_queue(struct xe_cpu_bind *cpu_bind,
struct xe_exec_queue *q)
{
return cpu_bind->q == q;
}
static void xe_cpu_bind_fini(void *arg)
{
struct xe_cpu_bind *cpu_bind = arg;
mutex_destroy(&cpu_bind->job_mutex);
xe_exec_queue_put(cpu_bind->q);
}
/**
* xe_cpu_bind_init() - Initialize a cpu_bind context
* @xe: &struct xe_device
*
* Return: 0 if successful, negative error code on failure
*/
int xe_cpu_bind_init(struct xe_device *xe)
{
struct xe_cpu_bind *cpu_bind =
drmm_kzalloc(&xe->drm, sizeof(*cpu_bind), GFP_KERNEL);
struct xe_exec_queue *q;
if (!cpu_bind)
return -ENOMEM;
q = xe_exec_queue_create_bind(xe, xe_device_get_root_tile(xe), NULL,
EXEC_QUEUE_FLAG_KERNEL |
EXEC_QUEUE_FLAG_MIGRATE, 0);
if (IS_ERR(q))
return PTR_ERR(q);
cpu_bind->xe = xe;
cpu_bind->q = q;
xe->cpu_bind = cpu_bind;
mutex_init(&cpu_bind->job_mutex);
fs_reclaim_acquire(GFP_KERNEL);
might_lock(&cpu_bind->job_mutex);
fs_reclaim_release(GFP_KERNEL);
return devm_add_action_or_reset(cpu_bind->xe->drm.dev, xe_cpu_bind_fini,
cpu_bind);
}
/**
* xe_cpu_bind_queue() - Get the bind queue from cpu_bind context.
* @cpu_bind: The cpu bind context.
*
* Return: Pointer to bind queue.
*/
struct xe_exec_queue *xe_cpu_bind_queue(struct xe_cpu_bind *cpu_bind)
{
return cpu_bind->q;
}
/**
* xe_cpu_bind_update_pgtables_execute() - Update a VM's PTEs via the CPU
* @vm: The VM being updated
* @tile: The tile being updated
* @ops: The CPU bind PT update ops
* @pt_op: The VM PT update op
* @num_ops: The number of The VM PT update ops
* @force_clear: Force clear operation
*
* Execute the VM PT update ops array which results in a VM's PTEs being updated
* via the CPU.
*/
void
xe_cpu_bind_update_pgtables_execute(struct xe_vm *vm, struct xe_tile *tile,
const struct xe_cpu_bind_pt_update_ops *ops,
struct xe_vm_pgtable_update_op *pt_op,
u32 num_ops, bool force_clear)
{
u32 j, i;
for (j = 0; j < num_ops; ++j, ++pt_op) {
for (i = 0; i < pt_op->num_entries; i++) {
const struct xe_vm_pgtable_update *update =
&pt_op->entries[i];
xe_assert(vm->xe, !iosys_map_is_null(&update->pt_bo->vmap));
if (pt_op->bind && !force_clear)
ops->populate(tile, &update->pt_bo->vmap,
update);
else
ops->clear(vm, tile, &update->pt_bo->vmap,
update);
}
}
trace_xe_vm_cpu_bind(vm);
xe_device_wmb(vm->xe);
}
static struct dma_fence *
xe_cpu_bind_update_pgtables_no_job(struct xe_cpu_bind *cpu_bind,
struct xe_cpu_bind_pt_update *pt_update)
{
const struct xe_cpu_bind_pt_update_ops *ops = pt_update->ops;
struct xe_vm *vm = pt_update->vops->vm;
struct xe_tile *tile;
int err, id;
if (ops->pre_commit) {
pt_update->job = NULL;
err = ops->pre_commit(pt_update);
if (err)
return ERR_PTR(err);
}
for_each_tile(tile, vm->xe, id) {
struct xe_vm_pgtable_update_ops *pt_update_ops =
&pt_update->vops->pt_update_ops[tile->id];
if (!pt_update_ops->pt_job_ops)
continue;
xe_cpu_bind_update_pgtables_execute(vm, tile, ops,
pt_update_ops->pt_job_ops->ops,
pt_update_ops->pt_job_ops->current_op,
false);
}
return dma_fence_get_stub();
}
static struct dma_fence *
xe_cpu_bind_update_pgtables_job(struct xe_cpu_bind *cpu_bind,
struct xe_cpu_bind_pt_update *pt_update)
{
const struct xe_cpu_bind_pt_update_ops *ops = pt_update->ops;
struct xe_exec_queue *q = pt_update->vops->q;
struct xe_device *xe = cpu_bind->xe;
struct xe_sched_job *job;
struct dma_fence *fence;
struct xe_tile *tile;
int err, id;
bool is_cpu_bind = is_cpu_bind_queue(cpu_bind, q);
job = xe_sched_job_create(q, NULL);
if (IS_ERR(job))
return ERR_CAST(job);
xe_assert(xe, job->is_pt_job);
if (ops->pre_commit) {
pt_update->job = job;
err = ops->pre_commit(pt_update);
if (err)
goto err_job;
}
if (is_cpu_bind)
mutex_lock(&cpu_bind->job_mutex);
job->pt_update[0].vm = pt_update->vops->vm;
job->pt_update[0].ops = ops;
for_each_tile(tile, xe, id) {
struct xe_vm_pgtable_update_ops *pt_update_ops =
&pt_update->vops->pt_update_ops[tile->id];
job->pt_update[0].pt_job_ops[tile->id] =
xe_pt_job_ops_get(pt_update_ops->pt_job_ops);
}
xe_sched_job_arm(job);
fence = dma_fence_get(&job->drm.s_fence->finished);
xe_sched_job_push(job);
if (is_cpu_bind)
mutex_unlock(&cpu_bind->job_mutex);
return fence;
err_job:
xe_sched_job_put(job);
return ERR_PTR(err);
}
/**
* xe_cpu_bind_update_pgtables() - Pipelined page-table update
* @cpu_bind: The cpu bind context.
* @pt_update: PT update arguments
*
* Perform a pipelined page-table update. The update descriptors are typically
* built under the same lock critical section as a call to this function. If
* using the default engine for the updates, they will be performed in the
* order they grab the job_mutex. If different engines are used, external
* synchronization is needed for overlapping updates to maintain page-table
* consistency. Note that the meaning of "overlapping" is that the updates
* touch the same page-table, which might be a higher-level page-directory.
* If no pipelining is needed, then updates may be performed by the cpu.
*
* Return: A dma_fence that, when signaled, indicates the update completion.
*/
struct dma_fence *
xe_cpu_bind_update_pgtables(struct xe_cpu_bind *cpu_bind,
struct xe_cpu_bind_pt_update *pt_update)
{
struct dma_fence *fence;
fence = xe_cpu_bind_update_pgtables_no_job(cpu_bind, pt_update);
/* -ETIME indicates a job is needed, anything else is legit error */
if (!IS_ERR(fence) || PTR_ERR(fence) != -ETIME)
return fence;
return xe_cpu_bind_update_pgtables_job(cpu_bind, pt_update);
}
/**
* xe_cpu_bind_job_lock() - Lock cpu_bind job lock
* @cpu_bind: The cpu bind context.
* @q: Queue associated with the operation which requires a lock
*
* Lock the cpu_bind job lock if the queue is a cpu bind queue, otherwise
* assert the VM's dma-resv is held (user queue's have own locking).
*/
void xe_cpu_bind_job_lock(struct xe_cpu_bind *cpu_bind,
struct xe_exec_queue *q)
{
bool is_cpu_bind = is_cpu_bind_queue(cpu_bind, q);
if (is_cpu_bind)
mutex_lock(&cpu_bind->job_mutex);
else
xe_vm_assert_held(q->user_vm); /* User queues VM's should be locked */
}
/**
* xe_cpu_bind_job_unlock() - Unlock cpu_bind job lock
* @cpu_bind: The cpu bind context.
* @q: Queue associated with the operation which requires a lock
*
* Unlock the cpu_bind job lock if the queue is a cpu bind queue, otherwise
* assert the VM's dma-resv is held (user queue's have own locking).
*/
void xe_cpu_bind_job_unlock(struct xe_cpu_bind *cpu_bind,
struct xe_exec_queue *q)
{
bool is_cpu_bind = is_cpu_bind_queue(cpu_bind, q);
if (is_cpu_bind)
mutex_unlock(&cpu_bind->job_mutex);
else
xe_vm_assert_held(q->user_vm); /* User queues VM's should be locked */
}
#if IS_ENABLED(CONFIG_PROVE_LOCKING)
/**
* xe_cpu_bind_job_lock_assert() - Assert cpu_bind job lock held of queue
* @q: cpu bind queue
*/
void xe_cpu_bind_job_lock_assert(struct xe_exec_queue *q)
{
struct xe_device *xe = gt_to_xe(q->gt);
struct xe_cpu_bind *cpu_bind = xe->cpu_bind;
xe_assert(xe, q == cpu_bind->q);
lockdep_assert_held(&cpu_bind->job_mutex);
}
#endif