xref: /linux/drivers/gpu/drm/xe/xe_tlb_inval_job.c (revision bf4afc53b77aeaa48b5409da5c8da6bb4eff7f43)
1 // SPDX-License-Identifier: MIT
2 /*
3  * Copyright © 2025 Intel Corporation
4  */
5 
6 #include "xe_assert.h"
7 #include "xe_dep_job_types.h"
8 #include "xe_dep_scheduler.h"
9 #include "xe_exec_queue.h"
10 #include "xe_gt_printk.h"
11 #include "xe_gt_types.h"
12 #include "xe_page_reclaim.h"
13 #include "xe_tlb_inval.h"
14 #include "xe_tlb_inval_job.h"
15 #include "xe_migrate.h"
16 #include "xe_pm.h"
17 #include "xe_vm.h"
18 
19 /** struct xe_tlb_inval_job - TLB invalidation job */
20 struct xe_tlb_inval_job {
21 	/** @dep: base generic dependency Xe job */
22 	struct xe_dep_job dep;
23 	/** @tlb_inval: TLB invalidation client */
24 	struct xe_tlb_inval *tlb_inval;
25 	/** @q: exec queue issuing the invalidate */
26 	struct xe_exec_queue *q;
27 	/** @vm: VM which TLB invalidation is being issued for */
28 	struct xe_vm *vm;
29 	/** @prl: Embedded copy of page reclaim list */
30 	struct xe_page_reclaim_list prl;
31 	/** @refcount: ref count of this job */
32 	struct kref refcount;
33 	/**
34 	 * @fence: dma fence to indicate completion. 1 way relationship - job
35 	 * can safely reference fence, fence cannot safely reference job.
36 	 */
37 	struct dma_fence *fence;
38 	/** @start: Start address to invalidate */
39 	u64 start;
40 	/** @end: End address to invalidate */
41 	u64 end;
42 	/** @type: GT type */
43 	int type;
44 	/** @fence_armed: Fence has been armed */
45 	bool fence_armed;
46 };
47 
xe_tlb_inval_job_run(struct xe_dep_job * dep_job)48 static struct dma_fence *xe_tlb_inval_job_run(struct xe_dep_job *dep_job)
49 {
50 	struct xe_tlb_inval_job *job =
51 		container_of(dep_job, typeof(*job), dep);
52 	struct xe_tlb_inval_fence *ifence =
53 		container_of(job->fence, typeof(*ifence), base);
54 	struct drm_suballoc *prl_sa = NULL;
55 
56 	if (xe_page_reclaim_list_valid(&job->prl)) {
57 		prl_sa = xe_page_reclaim_create_prl_bo(job->tlb_inval, &job->prl, ifence);
58 		if (IS_ERR(prl_sa))
59 			prl_sa = NULL; /* Indicate fall back PPC flush with NULL */
60 	}
61 
62 	xe_tlb_inval_range(job->tlb_inval, ifence, job->start,
63 			   job->end, job->vm->usm.asid, prl_sa);
64 
65 	return job->fence;
66 }
67 
xe_tlb_inval_job_free(struct xe_dep_job * dep_job)68 static void xe_tlb_inval_job_free(struct xe_dep_job *dep_job)
69 {
70 	struct xe_tlb_inval_job *job =
71 		container_of(dep_job, typeof(*job), dep);
72 
73 	/* Pairs with get in xe_tlb_inval_job_push */
74 	xe_tlb_inval_job_put(job);
75 }
76 
77 static const struct xe_dep_job_ops dep_job_ops = {
78 	.run_job = xe_tlb_inval_job_run,
79 	.free_job = xe_tlb_inval_job_free,
80 };
81 
82 /**
83  * xe_tlb_inval_job_create() - TLB invalidation job create
84  * @q: exec queue issuing the invalidate
85  * @tlb_inval: TLB invalidation client
86  * @dep_scheduler: Dependency scheduler for job
87  * @vm: VM which TLB invalidation is being issued for
88  * @start: Start address to invalidate
89  * @end: End address to invalidate
90  * @type: GT type
91  *
92  * Create a TLB invalidation job and initialize internal fields. The caller is
93  * responsible for releasing the creation reference.
94  *
95  * Return: TLB invalidation job object on success, ERR_PTR failure
96  */
97 struct xe_tlb_inval_job *
xe_tlb_inval_job_create(struct xe_exec_queue * q,struct xe_tlb_inval * tlb_inval,struct xe_dep_scheduler * dep_scheduler,struct xe_vm * vm,u64 start,u64 end,int type)98 xe_tlb_inval_job_create(struct xe_exec_queue *q, struct xe_tlb_inval *tlb_inval,
99 			struct xe_dep_scheduler *dep_scheduler,
100 			struct xe_vm *vm, u64 start, u64 end, int type)
101 {
102 	struct xe_tlb_inval_job *job;
103 	struct drm_sched_entity *entity =
104 		xe_dep_scheduler_entity(dep_scheduler);
105 	struct xe_tlb_inval_fence *ifence;
106 	int err;
107 
108 	xe_assert(vm->xe, type == XE_EXEC_QUEUE_TLB_INVAL_MEDIA_GT ||
109 		  type == XE_EXEC_QUEUE_TLB_INVAL_PRIMARY_GT);
110 
111 	job = kmalloc_obj(*job);
112 	if (!job)
113 		return ERR_PTR(-ENOMEM);
114 
115 	job->q = q;
116 	job->vm = vm;
117 	job->tlb_inval = tlb_inval;
118 	job->start = start;
119 	job->end = end;
120 	job->fence_armed = false;
121 	xe_page_reclaim_list_init(&job->prl);
122 	job->dep.ops = &dep_job_ops;
123 	job->type = type;
124 	kref_init(&job->refcount);
125 	xe_exec_queue_get(q);	/* Pairs with put in xe_tlb_inval_job_destroy */
126 	xe_vm_get(vm);		/* Pairs with put in xe_tlb_inval_job_destroy */
127 
128 	ifence = kmalloc_obj(*ifence);
129 	if (!ifence) {
130 		err = -ENOMEM;
131 		goto err_job;
132 	}
133 	job->fence = &ifence->base;
134 
135 	err = drm_sched_job_init(&job->dep.drm, entity, 1, NULL,
136 				 q->xef ? q->xef->drm->client_id : 0);
137 	if (err)
138 		goto err_fence;
139 
140 	/* Pairs with put in xe_tlb_inval_job_destroy */
141 	xe_pm_runtime_get_noresume(gt_to_xe(q->gt));
142 
143 	return job;
144 
145 err_fence:
146 	kfree(ifence);
147 err_job:
148 	xe_vm_put(vm);
149 	xe_exec_queue_put(q);
150 	kfree(job);
151 
152 	return ERR_PTR(err);
153 }
154 
155 /**
156  * xe_tlb_inval_job_add_page_reclaim() - Embed PRL into a TLB job
157  * @job: TLB invalidation job that may trigger reclamation
158  * @prl: Page reclaim list populated during unbind
159  *
160  * Copies @prl into the job and takes an extra reference to the entry page so
161  * ownership can transfer to the TLB fence when the job is pushed.
162  */
xe_tlb_inval_job_add_page_reclaim(struct xe_tlb_inval_job * job,struct xe_page_reclaim_list * prl)163 void xe_tlb_inval_job_add_page_reclaim(struct xe_tlb_inval_job *job,
164 				       struct xe_page_reclaim_list *prl)
165 {
166 	struct xe_device *xe = gt_to_xe(job->q->gt);
167 
168 	xe_gt_WARN_ON(job->q->gt, !xe->info.has_page_reclaim_hw_assist);
169 	job->prl = *prl;
170 	/* Pair with put in job_destroy */
171 	xe_page_reclaim_entries_get(job->prl.entries);
172 }
173 
xe_tlb_inval_job_destroy(struct kref * ref)174 static void xe_tlb_inval_job_destroy(struct kref *ref)
175 {
176 	struct xe_tlb_inval_job *job = container_of(ref, typeof(*job),
177 						    refcount);
178 	struct xe_tlb_inval_fence *ifence =
179 		container_of(job->fence, typeof(*ifence), base);
180 	struct xe_exec_queue *q = job->q;
181 	struct xe_device *xe = gt_to_xe(q->gt);
182 	struct xe_vm *vm = job->vm;
183 
184 	/* BO creation retains a copy (if used), so no longer needed */
185 	xe_page_reclaim_entries_put(job->prl.entries);
186 
187 	if (!job->fence_armed)
188 		kfree(ifence);
189 	else
190 		/* Ref from xe_tlb_inval_fence_init */
191 		dma_fence_put(job->fence);
192 
193 	drm_sched_job_cleanup(&job->dep.drm);
194 	kfree(job);
195 	xe_vm_put(vm);		/* Pairs with get from xe_tlb_inval_job_create */
196 	xe_exec_queue_put(q);	/* Pairs with get from xe_tlb_inval_job_create */
197 	xe_pm_runtime_put(xe);	/* Pairs with get from xe_tlb_inval_job_create */
198 }
199 
200 /**
201  * xe_tlb_inval_job_alloc_dep() - TLB invalidation job alloc dependency
202  * @job: TLB invalidation job to alloc dependency for
203  *
204  * Allocate storage for a dependency in the TLB invalidation fence. This
205  * function should be called at most once per job and must be paired with
206  * xe_tlb_inval_job_push being called with a real fence.
207  *
208  * Return: 0 on success, -errno on failure
209  */
xe_tlb_inval_job_alloc_dep(struct xe_tlb_inval_job * job)210 int xe_tlb_inval_job_alloc_dep(struct xe_tlb_inval_job *job)
211 {
212 	xe_assert(gt_to_xe(job->q->gt), !xa_load(&job->dep.drm.dependencies, 0));
213 	might_alloc(GFP_KERNEL);
214 
215 	return drm_sched_job_add_dependency(&job->dep.drm,
216 					    dma_fence_get_stub());
217 }
218 
219 /**
220  * xe_tlb_inval_job_push() - TLB invalidation job push
221  * @job: TLB invalidation job to push
222  * @m: The migration object being used
223  * @fence: Dependency for TLB invalidation job
224  *
225  * Pushes a TLB invalidation job for execution, using @fence as a dependency.
226  * Storage for @fence must be preallocated with xe_tlb_inval_job_alloc_dep
227  * prior to this call if @fence is not signaled. Takes a reference to the job’s
228  * finished fence, which the caller is responsible for releasing, and return it
229  * to the caller. This function is safe to be called in the path of reclaim.
230  *
231  * Return: Job's finished fence on success, cannot fail
232  */
xe_tlb_inval_job_push(struct xe_tlb_inval_job * job,struct xe_migrate * m,struct dma_fence * fence)233 struct dma_fence *xe_tlb_inval_job_push(struct xe_tlb_inval_job *job,
234 					struct xe_migrate *m,
235 					struct dma_fence *fence)
236 {
237 	struct xe_tlb_inval_fence *ifence =
238 		container_of(job->fence, typeof(*ifence), base);
239 
240 	if (!dma_fence_is_signaled(fence)) {
241 		void *ptr;
242 
243 		/*
244 		 * Can be in path of reclaim, hence the preallocation of fence
245 		 * storage in xe_tlb_inval_job_alloc_dep. Verify caller did
246 		 * this correctly.
247 		 */
248 		xe_assert(gt_to_xe(job->q->gt),
249 			  xa_load(&job->dep.drm.dependencies, 0) ==
250 			  dma_fence_get_stub());
251 
252 		dma_fence_get(fence);	/* ref released once dependency processed by scheduler */
253 		ptr = xa_store(&job->dep.drm.dependencies, 0, fence,
254 			       GFP_ATOMIC);
255 		xe_assert(gt_to_xe(job->q->gt), !xa_is_err(ptr));
256 	}
257 
258 	xe_tlb_inval_job_get(job);	/* Pairs with put in free_job */
259 	job->fence_armed = true;
260 
261 	/*
262 	 * We need the migration lock to protect the job's seqno and the spsc
263 	 * queue, only taken on migration queue, user queues protected dma-resv
264 	 * VM lock.
265 	 */
266 	xe_migrate_job_lock(m, job->q);
267 
268 	/* Creation ref pairs with put in xe_tlb_inval_job_destroy */
269 	xe_tlb_inval_fence_init(job->tlb_inval, ifence, false);
270 	dma_fence_get(job->fence);	/* Pairs with put in DRM scheduler */
271 
272 	drm_sched_job_arm(&job->dep.drm);
273 	/*
274 	 * caller ref, get must be done before job push as it could immediately
275 	 * signal and free.
276 	 */
277 	dma_fence_get(&job->dep.drm.s_fence->finished);
278 	drm_sched_entity_push_job(&job->dep.drm);
279 
280 	/* Let the upper layers fish this out */
281 	xe_exec_queue_tlb_inval_last_fence_set(job->q, job->vm,
282 					       &job->dep.drm.s_fence->finished,
283 					       job->type);
284 
285 	xe_migrate_job_unlock(m, job->q);
286 
287 	/*
288 	 * Not using job->fence, as it has its own dma-fence context, which does
289 	 * not allow TLB invalidation fences on the same queue, GT tuple to
290 	 * be squashed in dma-resv/DRM scheduler. Instead, we use the DRM scheduler
291 	 * context and job's finished fence, which enables squashing.
292 	 */
293 	return &job->dep.drm.s_fence->finished;
294 }
295 
296 /**
297  * xe_tlb_inval_job_get() - Get a reference to TLB invalidation job
298  * @job: TLB invalidation job object
299  *
300  * Increment the TLB invalidation job's reference count
301  */
xe_tlb_inval_job_get(struct xe_tlb_inval_job * job)302 void xe_tlb_inval_job_get(struct xe_tlb_inval_job *job)
303 {
304 	kref_get(&job->refcount);
305 }
306 
307 /**
308  * xe_tlb_inval_job_put() - Put a reference to TLB invalidation job
309  * @job: TLB invalidation job object
310  *
311  * Decrement the TLB invalidation job's reference count, call
312  * xe_tlb_inval_job_destroy when reference count == 0. Skips decrement if
313  * input @job is NULL or IS_ERR.
314  */
xe_tlb_inval_job_put(struct xe_tlb_inval_job * job)315 void xe_tlb_inval_job_put(struct xe_tlb_inval_job *job)
316 {
317 	if (!IS_ERR_OR_NULL(job))
318 		kref_put(&job->refcount, xe_tlb_inval_job_destroy);
319 }
320