← Documents Documentation/gpu/drm-vm-bind-locking.rst GitHub 원문 ↗

Linux 6.18.37 · GPU·DRM

VM_BIND locking

VM_BIND의 reservation lock ordering, object lifetime, userptr MMU notifier와 recoverable fault locking 전문 번역입니다.

Source pathDocumentation/gpu/drm-vm-bind-locking.rst
Source versionLinux v6.18.37
TranslationDUJINLABS 전문 번역 + 해설

요약·해설과 원문, 전문 번역을 서로 분리했습니다. API 이름, symbol, source path는 원문 표기를 사용합니다.

1. 요약·해설

원문의 핵심 논리와 kernel programming 관점의 보충 설명입니다. 아래의 전문 번역과는 별도로 작성했습니다.

요약·해설

drm-vm-bind-locking.rst:1-582

VM_BIND의 local·external object reservation locking, `gpu_vm_bo`·`gpu_vma` lifetime, eviction·revalidation, spinlock list iteration, userptr MMU invalidation, bind·unbind linking과 recoverable page-fault PTE update를 설명합니다. 7개 pseudo-C block과 모든 source line을 보존해 lock ordering을 구현 수준에서 대조할 수 있습니다.

Locking 검토 순서
단계검토 항목
Lifetimegpuva_lock을 놓지 않는 list iteration과 close-time cleanup
Local objectobj->resv == gpu_vm->resv, exec·eviction 직렬화
External objectObject-private resv, evicted bool, VM별 fence 등록
Spinlock listPrivate list·reference·outer semaphore
UserptrMMU sequence retry와 notifier/exec atomic visibility
Recoverable faultMapping 제거·TLB flush 뒤 page reuse, map/zap race 금지

Driver 구현을 검토할 때 확인할 핵심 invariant입니다.

2. 영어 원문 전체

번역 기준이 된 Linux v6.18.37 원문입니다. 줄 번호는 이 버전의 파일 좌표입니다.

원문 전체 펼치기
1 .. SPDX-License-Identifier: (GPL-2.0+ OR MIT)
2
3 ===============
4 VM_BIND locking
5 ===============
6
7 This document attempts to describe what's needed to get VM_BIND locking right,
8 including the userptr mmu_notifier locking. It also discusses some
9 optimizations to get rid of the looping through of all userptr mappings and
10 external / shared object mappings that is needed in the simplest
11 implementation. In addition, there is a section describing the VM_BIND locking
12 required for implementing recoverable pagefaults.
13
14 The DRM GPUVM set of helpers
15 ============================
16
17 There is a set of helpers for drivers implementing VM_BIND, and this
18 set of helpers implements much, but not all of the locking described
19 in this document. In particular, it is currently lacking a userptr
20 implementation. This document does not intend to describe the DRM GPUVM
21 implementation in detail, but it is covered in :ref:`its own
22 documentation <drm_gpuvm>`. It is highly recommended for any driver
23 implementing VM_BIND to use the DRM GPUVM helpers and to extend it if
24 common functionality is missing.
25
26 Nomenclature
27 ============
28
29 * ``gpu_vm``: Abstraction of a virtual GPU address space with
30 meta-data. Typically one per client (DRM file-private), or one per
31 execution context.
32 * ``gpu_vma``: Abstraction of a GPU address range within a gpu_vm with
33 associated meta-data. The backing storage of a gpu_vma can either be
34 a GEM object or anonymous or page-cache pages mapped also into the CPU
35 address space for the process.
36 * ``gpu_vm_bo``: Abstracts the association of a GEM object and
37 a VM. The GEM object maintains a list of gpu_vm_bos, where each gpu_vm_bo
38 maintains a list of gpu_vmas.
39 * ``userptr gpu_vma or just userptr``: A gpu_vma, whose backing store
40 is anonymous or page-cache pages as described above.
41 * ``revalidating``: Revalidating a gpu_vma means making the latest version
42 of the backing store resident and making sure the gpu_vma's
43 page-table entries point to that backing store.
44 * ``dma_fence``: A struct dma_fence that is similar to a struct completion
45 and which tracks GPU activity. When the GPU activity is finished,
46 the dma_fence signals. Please refer to the ``DMA Fences`` section of
47 the :doc:`dma-buf doc </driver-api/dma-buf>`.
48 * ``dma_resv``: A struct dma_resv (a.k.a reservation object) that is used
49 to track GPU activity in the form of multiple dma_fences on a
50 gpu_vm or a GEM object. The dma_resv contains an array / list
51 of dma_fences and a lock that needs to be held when adding
52 additional dma_fences to the dma_resv. The lock is of a type that
53 allows deadlock-safe locking of multiple dma_resvs in arbitrary
54 order. Please refer to the ``Reservation Objects`` section of the
55 :doc:`dma-buf doc </driver-api/dma-buf>`.
56 * ``exec function``: An exec function is a function that revalidates all
57 affected gpu_vmas, submits a GPU command batch and registers the
58 dma_fence representing the GPU command's activity with all affected
59 dma_resvs. For completeness, although not covered by this document,
60 it's worth mentioning that an exec function may also be the
61 revalidation worker that is used by some drivers in compute /
62 long-running mode.
63 * ``local object``: A GEM object which is only mapped within a
64 single VM. Local GEM objects share the gpu_vm's dma_resv.
65 * ``external object``: a.k.a shared object: A GEM object which may be shared
66 by multiple gpu_vms and whose backing storage may be shared with
67 other drivers.
68
69 Locks and locking order
70 =======================
71
72 One of the benefits of VM_BIND is that local GEM objects share the gpu_vm's
73 dma_resv object and hence the dma_resv lock. So, even with a huge
74 number of local GEM objects, only one lock is needed to make the exec
75 sequence atomic.
76
77 The following locks and locking orders are used:
78
79 * The ``gpu_vm->lock`` (optionally an rwsem). Protects the gpu_vm's
80 data structure keeping track of gpu_vmas. It can also protect the
81 gpu_vm's list of userptr gpu_vmas. With a CPU mm analogy this would
82 correspond to the mmap_lock. An rwsem allows several readers to walk
83 the VM tree concurrently, but the benefit of that concurrency most
84 likely varies from driver to driver.
85 * The ``userptr_seqlock``. This lock is taken in read mode for each
86 userptr gpu_vma on the gpu_vm's userptr list, and in write mode during mmu
87 notifier invalidation. This is not a real seqlock but described in
88 ``mm/mmu_notifier.c`` as a "Collision-retry read-side/write-side
89 'lock' a lot like a seqcount. However this allows multiple
90 write-sides to hold it at once...". The read side critical section
91 is enclosed by ``mmu_interval_read_begin() /
92 mmu_interval_read_retry()`` with ``mmu_interval_read_begin()``
93 sleeping if the write side is held.
94 The write side is held by the core mm while calling mmu interval
95 invalidation notifiers.
96 * The ``gpu_vm->resv`` lock. Protects the gpu_vm's list of gpu_vmas needing
97 rebinding, as well as the residency state of all the gpu_vm's local
98 GEM objects.
99 Furthermore, it typically protects the gpu_vm's list of evicted and
100 external GEM objects.
101 * The ``gpu_vm->userptr_notifier_lock``. This is an rwsem that is
102 taken in read mode during exec and write mode during a mmu notifier
103 invalidation. The userptr notifier lock is per gpu_vm.
104 * The ``gem_object->gpuva_lock`` This lock protects the GEM object's
105 list of gpu_vm_bos. This is usually the same lock as the GEM
106 object's dma_resv, but some drivers protects this list differently,
107 see below.
108 * The ``gpu_vm list spinlocks``. With some implementations they are needed
109 to be able to update the gpu_vm evicted- and external object
110 list. For those implementations, the spinlocks are grabbed when the
111 lists are manipulated. However, to avoid locking order violations
112 with the dma_resv locks, a special scheme is needed when iterating
113 over the lists.
114
115 .. _gpu_vma lifetime:
116
117 Protection and lifetime of gpu_vm_bos and gpu_vmas
118 ==================================================
119
120 The GEM object's list of gpu_vm_bos, and the gpu_vm_bo's list of gpu_vmas
121 is protected by the ``gem_object->gpuva_lock``, which is typically the
122 same as the GEM object's dma_resv, but if the driver
123 needs to access these lists from within a dma_fence signalling
124 critical section, it can instead choose to protect it with a
125 separate lock, which can be locked from within the dma_fence signalling
126 critical section. Such drivers then need to pay additional attention
127 to what locks need to be taken from within the loop when iterating
128 over the gpu_vm_bo and gpu_vma lists to avoid locking-order violations.
129
130 The DRM GPUVM set of helpers provide lockdep asserts that this lock is
131 held in relevant situations and also provides a means of making itself
132 aware of which lock is actually used: :c:func:`drm_gem_gpuva_set_lock`.
133
134 Each gpu_vm_bo holds a reference counted pointer to the underlying GEM
135 object, and each gpu_vma holds a reference counted pointer to the
136 gpu_vm_bo. When iterating over the GEM object's list of gpu_vm_bos and
137 over the gpu_vm_bo's list of gpu_vmas, the ``gem_object->gpuva_lock`` must
138 not be dropped, otherwise, gpu_vmas attached to a gpu_vm_bo may
139 disappear without notice since those are not reference-counted. A
140 driver may implement its own scheme to allow this at the expense of
141 additional complexity, but this is outside the scope of this document.
142
143 In the DRM GPUVM implementation, each gpu_vm_bo and each gpu_vma
144 holds a reference count on the gpu_vm itself. Due to this, and to avoid circular
145 reference counting, cleanup of the gpu_vm's gpu_vmas must not be done from the
146 gpu_vm's destructor. Drivers typically implements a gpu_vm close
147 function for this cleanup. The gpu_vm close function will abort gpu
148 execution using this VM, unmap all gpu_vmas and release page-table memory.
149
150 Revalidation and eviction of local objects
151 ==========================================
152
153 Note that in all the code examples given below we use simplified
154 pseudo-code. In particular, the dma_resv deadlock avoidance algorithm
155 as well as reserving memory for dma_resv fences is left out.
156
157 Revalidation
158 ____________
159 With VM_BIND, all local objects need to be resident when the gpu is
160 executing using the gpu_vm, and the objects need to have valid
161 gpu_vmas set up pointing to them. Typically, each gpu command buffer
162 submission is therefore preceded with a re-validation section:
163
164 .. code-block:: C
165
166 dma_resv_lock(gpu_vm->resv);
167
168 // Validation section starts here.
169 for_each_gpu_vm_bo_on_evict_list(&gpu_vm->evict_list, &gpu_vm_bo) {
170 validate_gem_bo(&gpu_vm_bo->gem_bo);
171
172 // The following list iteration needs the Gem object's
173 // dma_resv to be held (it protects the gpu_vm_bo's list of
174 // gpu_vmas, but since local gem objects share the gpu_vm's
175 // dma_resv, it is already held at this point.
176 for_each_gpu_vma_of_gpu_vm_bo(&gpu_vm_bo, &gpu_vma)
177 move_gpu_vma_to_rebind_list(&gpu_vma, &gpu_vm->rebind_list);
178 }
179
180 for_each_gpu_vma_on_rebind_list(&gpu vm->rebind_list, &gpu_vma) {
181 rebind_gpu_vma(&gpu_vma);
182 remove_gpu_vma_from_rebind_list(&gpu_vma);
183 }
184 // Validation section ends here, and job submission starts.
185
186 add_dependencies(&gpu_job, &gpu_vm->resv);
187 job_dma_fence = gpu_submit(&gpu_job));
188
189 add_dma_fence(job_dma_fence, &gpu_vm->resv);
190 dma_resv_unlock(gpu_vm->resv);
191
192 The reason for having a separate gpu_vm rebind list is that there
193 might be userptr gpu_vmas that are not mapping a buffer object that
194 also need rebinding.
195
196 Eviction
197 ________
198
199 Eviction of one of these local objects will then look similar to the
200 following:
201
202 .. code-block:: C
203
204 obj = get_object_from_lru();
205
206 dma_resv_lock(obj->resv);
207 for_each_gpu_vm_bo_of_obj(obj, &gpu_vm_bo);
208 add_gpu_vm_bo_to_evict_list(&gpu_vm_bo, &gpu_vm->evict_list);
209
210 add_dependencies(&eviction_job, &obj->resv);
211 job_dma_fence = gpu_submit(&eviction_job);
212 add_dma_fence(&obj->resv, job_dma_fence);
213
214 dma_resv_unlock(&obj->resv);
215 put_object(obj);
216
217 Note that since the object is local to the gpu_vm, it will share the gpu_vm's
218 dma_resv lock such that ``obj->resv == gpu_vm->resv``.
219 The gpu_vm_bos marked for eviction are put on the gpu_vm's evict list,
220 which is protected by ``gpu_vm->resv``. During eviction all local
221 objects have their dma_resv locked and, due to the above equality, also
222 the gpu_vm's dma_resv protecting the gpu_vm's evict list is locked.
223
224 With VM_BIND, gpu_vmas don't need to be unbound before eviction,
225 since the driver must ensure that the eviction blit or copy will wait
226 for GPU idle or depend on all previous GPU activity. Furthermore, any
227 subsequent attempt by the GPU to access freed memory through the
228 gpu_vma will be preceded by a new exec function, with a revalidation
229 section which will make sure all gpu_vmas are rebound. The eviction
230 code holding the object's dma_resv while revalidating will ensure a
231 new exec function may not race with the eviction.
232
233 A driver can be implemented in such a way that, on each exec function,
234 only a subset of vmas are selected for rebind. In this case, all vmas that are
235 *not* selected for rebind must be unbound before the exec
236 function workload is submitted.
237
238 Locking with external buffer objects
239 ====================================
240
241 Since external buffer objects may be shared by multiple gpu_vm's they
242 can't share their reservation object with a single gpu_vm. Instead
243 they need to have a reservation object of their own. The external
244 objects bound to a gpu_vm using one or many gpu_vmas are therefore put on a
245 per-gpu_vm list which is protected by the gpu_vm's dma_resv lock or
246 one of the :ref:`gpu_vm list spinlocks <Spinlock iteration>`. Once
247 the gpu_vm's reservation object is locked, it is safe to traverse the
248 external object list and lock the dma_resvs of all external
249 objects. However, if instead a list spinlock is used, a more elaborate
250 iteration scheme needs to be used.
251
252 At eviction time, the gpu_vm_bos of *all* the gpu_vms an external
253 object is bound to need to be put on their gpu_vm's evict list.
254 However, when evicting an external object, the dma_resvs of the
255 gpu_vms the object is bound to are typically not held. Only
256 the object's private dma_resv can be guaranteed to be held. If there
257 is a ww_acquire context at hand at eviction time we could grab those
258 dma_resvs but that could cause expensive ww_mutex rollbacks. A simple
259 option is to just mark the gpu_vm_bos of the evicted gem object with
260 an ``evicted`` bool that is inspected before the next time the
261 corresponding gpu_vm evicted list needs to be traversed. For example, when
262 traversing the list of external objects and locking them. At that time,
263 both the gpu_vm's dma_resv and the object's dma_resv is held, and the
264 gpu_vm_bo marked evicted, can then be added to the gpu_vm's list of
265 evicted gpu_vm_bos. The ``evicted`` bool is formally protected by the
266 object's dma_resv.
267
268 The exec function becomes
269
270 .. code-block:: C
271
272 dma_resv_lock(gpu_vm->resv);
273
274 // External object list is protected by the gpu_vm->resv lock.
275 for_each_gpu_vm_bo_on_extobj_list(gpu_vm, &gpu_vm_bo) {
276 dma_resv_lock(gpu_vm_bo.gem_obj->resv);
277 if (gpu_vm_bo_marked_evicted(&gpu_vm_bo))
278 add_gpu_vm_bo_to_evict_list(&gpu_vm_bo, &gpu_vm->evict_list);
279 }
280
281 for_each_gpu_vm_bo_on_evict_list(&gpu_vm->evict_list, &gpu_vm_bo) {
282 validate_gem_bo(&gpu_vm_bo->gem_bo);
283
284 for_each_gpu_vma_of_gpu_vm_bo(&gpu_vm_bo, &gpu_vma)
285 move_gpu_vma_to_rebind_list(&gpu_vma, &gpu_vm->rebind_list);
286 }
287
288 for_each_gpu_vma_on_rebind_list(&gpu vm->rebind_list, &gpu_vma) {
289 rebind_gpu_vma(&gpu_vma);
290 remove_gpu_vma_from_rebind_list(&gpu_vma);
291 }
292
293 add_dependencies(&gpu_job, &gpu_vm->resv);
294 job_dma_fence = gpu_submit(&gpu_job));
295
296 add_dma_fence(job_dma_fence, &gpu_vm->resv);
297 for_each_external_obj(gpu_vm, &obj)
298 add_dma_fence(job_dma_fence, &obj->resv);
299 dma_resv_unlock_all_resv_locks();
300
301 And the corresponding shared-object aware eviction would look like:
302
303 .. code-block:: C
304
305 obj = get_object_from_lru();
306
307 dma_resv_lock(obj->resv);
308 for_each_gpu_vm_bo_of_obj(obj, &gpu_vm_bo)
309 if (object_is_vm_local(obj))
310 add_gpu_vm_bo_to_evict_list(&gpu_vm_bo, &gpu_vm->evict_list);
311 else
312 mark_gpu_vm_bo_evicted(&gpu_vm_bo);
313
314 add_dependencies(&eviction_job, &obj->resv);
315 job_dma_fence = gpu_submit(&eviction_job);
316 add_dma_fence(&obj->resv, job_dma_fence);
317
318 dma_resv_unlock(&obj->resv);
319 put_object(obj);
320
321 .. _Spinlock iteration:
322
323 Accessing the gpu_vm's lists without the dma_resv lock held
324 ===========================================================
325
326 Some drivers will hold the gpu_vm's dma_resv lock when accessing the
327 gpu_vm's evict list and external objects lists. However, there are
328 drivers that need to access these lists without the dma_resv lock
329 held, for example due to asynchronous state updates from within the
330 dma_fence signalling critical path. In such cases, a spinlock can be
331 used to protect manipulation of the lists. However, since higher level
332 sleeping locks need to be taken for each list item while iterating
333 over the lists, the items already iterated over need to be
334 temporarily moved to a private list and the spinlock released
335 while processing each item:
336
337 .. code block:: C
338
339 struct list_head still_in_list;
340
341 INIT_LIST_HEAD(&still_in_list);
342
343 spin_lock(&gpu_vm->list_lock);
344 do {
345 struct list_head *entry = list_first_entry_or_null(&gpu_vm->list, head);
346
347 if (!entry)
348 break;
349
350 list_move_tail(&entry->head, &still_in_list);
351 list_entry_get_unless_zero(entry);
352 spin_unlock(&gpu_vm->list_lock);
353
354 process(entry);
355
356 spin_lock(&gpu_vm->list_lock);
357 list_entry_put(entry);
358 } while (true);
359
360 list_splice_tail(&still_in_list, &gpu_vm->list);
361 spin_unlock(&gpu_vm->list_lock);
362
363 Due to the additional locking and atomic operations, drivers that *can*
364 avoid accessing the gpu_vm's list outside of the dma_resv lock
365 might want to avoid also this iteration scheme. Particularly, if the
366 driver anticipates a large number of list items. For lists where the
367 anticipated number of list items is small, where list iteration doesn't
368 happen very often or if there is a significant additional cost
369 associated with each iteration, the atomic operation overhead
370 associated with this type of iteration is, most likely, negligible. Note that
371 if this scheme is used, it is necessary to make sure this list
372 iteration is protected by an outer level lock or semaphore, since list
373 items are temporarily pulled off the list while iterating, and it is
374 also worth mentioning that the local list ``still_in_list`` should
375 also be considered protected by the ``gpu_vm->list_lock``, and it is
376 thus possible that items can be removed also from the local list
377 concurrently with list iteration.
378
379 Please refer to the :ref:`DRM GPUVM locking section
380 <drm_gpuvm_locking>` and its internal
381 :c:func:`get_next_vm_bo_from_list` function.
382
383
384 userptr gpu_vmas
385 ================
386
387 A userptr gpu_vma is a gpu_vma that, instead of mapping a buffer object to a
388 GPU virtual address range, directly maps a CPU mm range of anonymous-
389 or file page-cache pages.
390 A very simple approach would be to just pin the pages using
391 pin_user_pages() at bind time and unpin them at unbind time, but this
392 creates a Denial-Of-Service vector since a single user-space process
393 would be able to pin down all of system memory, which is not
394 desirable. (For special use-cases and assuming proper accounting pinning might
395 still be a desirable feature, though). What we need to do in the
396 general case is to obtain a reference to the desired pages, make sure
397 we are notified using a MMU notifier just before the CPU mm unmaps the
398 pages, dirty them if they are not mapped read-only to the GPU, and
399 then drop the reference.
400 When we are notified by the MMU notifier that CPU mm is about to drop the
401 pages, we need to stop GPU access to the pages by waiting for VM idle
402 in the MMU notifier and make sure that before the next time the GPU
403 tries to access whatever is now present in the CPU mm range, we unmap
404 the old pages from the GPU page tables and repeat the process of
405 obtaining new page references. (See the :ref:`notifier example
406 <Invalidation example>` below). Note that when the core mm decides to
407 laundry pages, we get such an unmap MMU notification and can mark the
408 pages dirty again before the next GPU access. We also get similar MMU
409 notifications for NUMA accounting which the GPU driver doesn't really
410 need to care about, but so far it has proven difficult to exclude
411 certain notifications.
412
413 Using a MMU notifier for device DMA (and other methods) is described in
414 :ref:`the pin_user_pages() documentation <mmu-notifier-registration-case>`.
415
416 Now, the method of obtaining struct page references using
417 get_user_pages() unfortunately can't be used under a dma_resv lock
418 since that would violate the locking order of the dma_resv lock vs the
419 mmap_lock that is grabbed when resolving a CPU pagefault. This means
420 the gpu_vm's list of userptr gpu_vmas needs to be protected by an
421 outer lock, which in our example below is the ``gpu_vm->lock``.
422
423 The MMU interval seqlock for a userptr gpu_vma is used in the following
424 way:
425
426 .. code-block:: C
427
428 // Exclusive locking mode here is strictly needed only if there are
429 // invalidated userptr gpu_vmas present, to avoid concurrent userptr
430 // revalidations of the same userptr gpu_vma.
431 down_write(&gpu_vm->lock);
432 retry:
433
434 // Note: mmu_interval_read_begin() blocks until there is no
435 // invalidation notifier running anymore.
436 seq = mmu_interval_read_begin(&gpu_vma->userptr_interval);
437 if (seq != gpu_vma->saved_seq) {
438 obtain_new_page_pointers(&gpu_vma);
439 dma_resv_lock(&gpu_vm->resv);
440 add_gpu_vma_to_revalidate_list(&gpu_vma, &gpu_vm);
441 dma_resv_unlock(&gpu_vm->resv);
442 gpu_vma->saved_seq = seq;
443 }
444
445 // The usual revalidation goes here.
446
447 // Final userptr sequence validation may not happen before the
448 // submission dma_fence is added to the gpu_vm's resv, from the POW
449 // of the MMU invalidation notifier. Hence the
450 // userptr_notifier_lock that will make them appear atomic.
451
452 add_dependencies(&gpu_job, &gpu_vm->resv);
453 down_read(&gpu_vm->userptr_notifier_lock);
454 if (mmu_interval_read_retry(&gpu_vma->userptr_interval, gpu_vma->saved_seq)) {
455 up_read(&gpu_vm->userptr_notifier_lock);
456 goto retry;
457 }
458
459 job_dma_fence = gpu_submit(&gpu_job));
460
461 add_dma_fence(job_dma_fence, &gpu_vm->resv);
462
463 for_each_external_obj(gpu_vm, &obj)
464 add_dma_fence(job_dma_fence, &obj->resv);
465
466 dma_resv_unlock_all_resv_locks();
467 up_read(&gpu_vm->userptr_notifier_lock);
468 up_write(&gpu_vm->lock);
469
470 The code between ``mmu_interval_read_begin()`` and the
471 ``mmu_interval_read_retry()`` marks the read side critical section of
472 what we call the ``userptr_seqlock``. In reality, the gpu_vm's userptr
473 gpu_vma list is looped through, and the check is done for *all* of its
474 userptr gpu_vmas, although we only show a single one here.
475
476 The userptr gpu_vma MMU invalidation notifier might be called from
477 reclaim context and, again, to avoid locking order violations, we can't
478 take any dma_resv lock nor the gpu_vm->lock from within it.
479
480 .. _Invalidation example:
481 .. code-block:: C
482
483 bool gpu_vma_userptr_invalidate(userptr_interval, cur_seq)
484 {
485 // Make sure the exec function either sees the new sequence
486 // and backs off or we wait for the dma-fence:
487
488 down_write(&gpu_vm->userptr_notifier_lock);
489 mmu_interval_set_seq(userptr_interval, cur_seq);
490 up_write(&gpu_vm->userptr_notifier_lock);
491
492 // At this point, the exec function can't succeed in
493 // submitting a new job, because cur_seq is an invalid
494 // sequence number and will always cause a retry. When all
495 // invalidation callbacks, the mmu notifier core will flip
496 // the sequence number to a valid one. However we need to
497 // stop gpu access to the old pages here.
498
499 dma_resv_wait_timeout(&gpu_vm->resv, DMA_RESV_USAGE_BOOKKEEP,
500 false, MAX_SCHEDULE_TIMEOUT);
501 return true;
502 }
503
504 When this invalidation notifier returns, the GPU can no longer be
505 accessing the old pages of the userptr gpu_vma and needs to redo the
506 page-binding before a new GPU submission can succeed.
507
508 Efficient userptr gpu_vma exec_function iteration
509 _________________________________________________
510
511 If the gpu_vm's list of userptr gpu_vmas becomes large, it's
512 inefficient to iterate through the complete lists of userptrs on each
513 exec function to check whether each userptr gpu_vma's saved
514 sequence number is stale. A solution to this is to put all
515 *invalidated* userptr gpu_vmas on a separate gpu_vm list and
516 only check the gpu_vmas present on this list on each exec
517 function. This list will then lend itself very-well to the spinlock
518 locking scheme that is
519 :ref:`described in the spinlock iteration section <Spinlock iteration>`, since
520 in the mmu notifier, where we add the invalidated gpu_vmas to the
521 list, it's not possible to take any outer locks like the
522 ``gpu_vm->lock`` or the ``gpu_vm->resv`` lock. Note that the
523 ``gpu_vm->lock`` still needs to be taken while iterating to ensure the list is
524 complete, as also mentioned in that section.
525
526 If using an invalidated userptr list like this, the retry check in the
527 exec function trivially becomes a check for invalidated list empty.
528
529 Locking at bind and unbind time
530 ===============================
531
532 At bind time, assuming a GEM object backed gpu_vma, each
533 gpu_vma needs to be associated with a gpu_vm_bo and that
534 gpu_vm_bo in turn needs to be added to the GEM object's
535 gpu_vm_bo list, and possibly to the gpu_vm's external object
536 list. This is referred to as *linking* the gpu_vma, and typically
537 requires that the ``gpu_vm->lock`` and the ``gem_object->gpuva_lock``
538 are held. When unlinking a gpu_vma the same locks should be held,
539 and that ensures that when iterating over ``gpu_vmas`, either under
540 the ``gpu_vm->resv`` or the GEM object's dma_resv, that the gpu_vmas
541 stay alive as long as the lock under which we iterate is not released. For
542 userptr gpu_vmas it's similarly required that during vma destroy, the
543 outer ``gpu_vm->lock`` is held, since otherwise when iterating over
544 the invalidated userptr list as described in the previous section,
545 there is nothing keeping those userptr gpu_vmas alive.
546
547 Locking for recoverable page-fault page-table updates
548 =====================================================
549
550 There are two important things we need to ensure with locking for
551 recoverable page-faults:
552
553 * At the time we return pages back to the system / allocator for
554 reuse, there should be no remaining GPU mappings and any GPU TLB
555 must have been flushed.
556 * The unmapping and mapping of a gpu_vma must not race.
557
558 Since the unmapping (or zapping) of GPU ptes is typically taking place
559 where it is hard or even impossible to take any outer level locks we
560 must either introduce a new lock that is held at both mapping and
561 unmapping time, or look at the locks we do hold at unmapping time and
562 make sure that they are held also at mapping time. For userptr
563 gpu_vmas, the ``userptr_seqlock`` is held in write mode in the mmu
564 invalidation notifier where zapping happens. Hence, if the
565 ``userptr_seqlock`` as well as the ``gpu_vm->userptr_notifier_lock``
566 is held in read mode during mapping, it will not race with the
567 zapping. For GEM object backed gpu_vmas, zapping will take place under
568 the GEM object's dma_resv and ensuring that the dma_resv is held also
569 when populating the page-tables for any gpu_vma pointing to the GEM
570 object, will similarly ensure we are race-free.
571
572 If any part of the mapping is performed asynchronously
573 under a dma-fence with these locks released, the zapping will need to
574 wait for that dma-fence to signal under the relevant lock before
575 starting to modify the page-table.
576
577 Since modifying the
578 page-table structure in a way that frees up page-table memory
579 might also require outer level locks, the zapping of GPU ptes
580 typically focuses only on zeroing page-table or page-directory entries
581 and flushing TLB, whereas freeing of page-table memory is deferred to
582 unbind or rebind time.
583

3. 한국어 전문 번역

영어 원문의 문단 순서와 의미를 유지한 전체 번역입니다. 코드, 함수명, symbol과 URL은 원문 표기를 유지합니다.

문서 범위와 DRM GPUVM helper

1-26

이 문서는 userptr `mmu_notifier` locking을 포함해 VM_BIND locking을 올바르게 구현하는 데 필요한 조건을 설명합니다. 가장 단순한 구현이 모든 userptr mapping과 external·shared object mapping을 매번 순회하는 비용을 줄이는 최적화도 다룹니다.

또한 recoverable page fault를 구현할 때 필요한 VM_BIND locking을 별도 절에서 설명합니다.

VM_BIND driver를 위한 DRM GPUVM helper 집합은 이 문서의 locking 상당 부분을 구현하지만 전부는 아닙니다. 특히 현재 userptr 구현이 빠져 있습니다.

이 문서는 DRM GPUVM 내부 구현을 상세히 설명하지 않으며 `drm_gpuvm` 참조 문서가 그 역할을 합니다. VM_BIND를 구현하는 driver는 DRM GPUVM helper를 사용하고 공통 기능이 부족하면 helper 자체를 확장하는 것이 강하게 권장됩니다.

VM_BIND locking 설계 범위
GPU VM·VMA·reservation object의 기본 lifetime 정의Local object의 revalidation·eviction 직렬화External object와 여러 dma_resv의 deadlock-safe lockingUserptr MMU invalidation과 exec submission 조정Recoverable fault의 mapping·zapping race 방지

문서가 단순 구현에서 최적화·page fault까지 확장하는 순서입니다.

DRM GPUVM helper 적용
항목상태
공통 GPUVM lockingHelper가 상당 부분 제공
gpu_vm_bo·gpu_vma 관리DRM GPUVM 문서와 helper 사용 권장
Userptr현재 helper에 완전한 구현이 없어 driver 보완 필요
공통 기능 누락Driver-local 복제보다 helper 확장 권장

Helper가 제공하는 기반과 driver가 보완할 부분입니다.

.. SPDX-License-Identifier: (GPL-2.0+ OR MIT)

===============
VM_BIND locking
===============

This document attempts to describe what's needed to get VM_BIND locking right,
including the userptr mmu_notifier locking. It also discusses some
optimizations to get rid of the looping through of all userptr mappings and
external / shared object mappings that is needed in the simplest
implementation. In addition, there is a section describing the VM_BIND locking
required for implementing recoverable pagefaults.

The DRM GPUVM set of helpers
============================

There is a set of helpers for drivers implementing VM_BIND, and this
set of helpers implements much, but not all of the locking described
in this document. In particular, it is currently lacking a userptr
implementation. This document does not intend to describe the DRM GPUVM
implementation in detail, but it is covered in :ref:`its own
documentation <drm_gpuvm>`. It is highly recommended for any driver
implementing VM_BIND to use the DRM GPUVM helpers and to extend it if
common functionality is missing.

Nomenclature

용어, lock 역할과 ordering

27-114

`gpu_vm`은 metadata를 포함한 virtual GPU address space 추상화로 보통 client(DRM file-private) 또는 execution context마다 하나입니다. `gpu_vma`는 `gpu_vm` 안의 GPU address range와 metadata를 나타내며 backing store는 GEM object 또는 process CPU address space에도 mapping된 anonymous·page-cache page일 수 있습니다.

`gpu_vm_bo`는 GEM object와 VM의 연관 관계입니다. GEM object는 `gpu_vm_bo` list를 갖고 각 `gpu_vm_bo`는 `gpu_vma` list를 갖습니다. `userptr gpu_vma`는 anonymous 또는 page-cache page를 backing store로 쓰는 `gpu_vma`입니다.

`revalidating`은 backing store 최신 version을 resident로 만들고 `gpu_vma` page-table entry가 그 backing store를 가리키도록 확인하는 과정입니다.

`dma_fence`는 `struct completion`과 비슷하게 GPU activity를 추적하고 activity가 끝나면 signal합니다. `dma_resv`는 `gpu_vm` 또는 GEM object의 여러 dma-fence를 추적하는 reservation object입니다. Fence를 추가할 때 잡아야 하는 lock과 fence array·list를 포함하며 여러 dma_resv를 임의 순서로 deadlock 없이 lock할 수 있는 유형입니다. 자세한 규칙은 `/driver-api/dma-buf`의 DMA Fences와 Reservation Objects 절을 따릅니다.

`exec function`은 영향받는 `gpu_vma`를 revalidate하고 GPU command batch를 submit한 뒤 activity dma-fence를 모든 관련 dma_resv에 등록합니다. Compute·long-running mode에서는 revalidation worker가 exec function 역할을 할 수도 있습니다.

`local object`는 단일 VM 안에서만 mapping되는 GEM object이므로 `gpu_vm`의 dma_resv를 공유합니다. `external object` 또는 shared object는 여러 `gpu_vm`과 다른 driver가 backing storage를 공유할 수 있어 자체 dma_resv가 필요합니다.

Local object가 `gpu_vm->resv`를 공유하는 것이 VM_BIND의 중요한 장점입니다. Local GEM object 수가 매우 많아도 exec sequence 전체를 atomic하게 만드는 데 lock 하나면 충분합니다.

`gpu_vm->lock`은 `gpu_vma`를 추적하는 VM data structure와 선택적으로 userptr `gpu_vma` list를 보호합니다. CPU mm의 `mmap_lock`에 대응하며 rwsem을 쓰면 여러 reader가 VM tree를 동시에 순회할 수 있지만 효과는 driver마다 다릅니다.

`userptr_seqlock`은 실제 seqlock이 아니라 `mm/mmu_notifier.c`가 seqcount와 비슷하다고 설명하는 collision-retry read/write lock입니다. 여러 write side가 동시에 잡을 수 있습니다. Read critical section은 `mmu_interval_read_begin()`과 `mmu_interval_read_retry()` 사이이며 write side가 active이면 begin이 sleep합니다. Core mm은 MMU interval invalidation notifier를 호출하는 동안 write side를 잡습니다.

`gpu_vm->resv` lock은 rebind가 필요한 `gpu_vma` list, 모든 local GEM object의 residency, 보통 evicted·external object list까지 보호합니다. `gpu_vm->userptr_notifier_lock`은 VM별 rwsem으로 exec에서 read mode, MMU notifier invalidation에서 write mode로 잡습니다.

`gem_object->gpuva_lock`은 GEM object의 `gpu_vm_bo` list를 보호합니다. 보통 GEM object dma_resv와 같지만 driver가 다른 lock을 쓸 수도 있습니다. 일부 구현은 evicted·external list를 갱신하려고 `gpu_vm` list spinlock을 쓰며, dma_resv ordering 위반 없이 순회하려면 특별한 iteration 방식이 필요합니다.

VM_BIND lock 역할
Lock보호 대상대표 사용
gpu_vm->lockVM tree·userptr gpu_vma listTree traversal 또는 linking lifetime
userptr_seqlockUserptr MMU interval sequenceExec read side·invalidation write side
gpu_vm->resvRebind list·local residency·evicted/external listExec·eviction transaction
gpu_vm->userptr_notifier_lockExec submit과 notifier visibilityExec read·invalidation write
gem_object->gpuva_lockObject의 gpu_vm_bo listLink·unlink·list iteration
gpu_vm list spinlockEvicted·external list 조작Fence signaling critical path

각 lock이 보호하는 state와 대표 mode입니다.

Exec sequence atomicity
gpu_vm->resv lock 획득Local object residency와 gpu_vma mapping revalidate기존 reservation fence를 job dependency로 추가GPU job submit새 job dma_fence를 gpu_vm->resv에 등록gpu_vm->resv unlock

Local object가 하나의 reservation lock을 공유할 때의 기본 transaction입니다.

============

* ``gpu_vm``: Abstraction of a virtual GPU address space with
  meta-data. Typically one per client (DRM file-private), or one per
  execution context.
* ``gpu_vma``: Abstraction of a GPU address range within a gpu_vm with
  associated meta-data. The backing storage of a gpu_vma can either be
  a GEM object or anonymous or page-cache pages mapped also into the CPU
  address space for the process.
* ``gpu_vm_bo``: Abstracts the association of a GEM object and
  a VM. The GEM object maintains a list of gpu_vm_bos, where each gpu_vm_bo
  maintains a list of gpu_vmas.
* ``userptr gpu_vma or just userptr``: A gpu_vma, whose backing store
  is anonymous or page-cache pages as described above.
* ``revalidating``: Revalidating a gpu_vma means making the latest version
  of the backing store resident and making sure the gpu_vma's
  page-table entries point to that backing store.
* ``dma_fence``: A struct dma_fence that is similar to a struct completion
  and which tracks GPU activity. When the GPU activity is finished,
  the dma_fence signals. Please refer to the ``DMA Fences`` section of
  the :doc:`dma-buf doc </driver-api/dma-buf>`.
* ``dma_resv``: A struct dma_resv (a.k.a reservation object) that is used
  to track GPU activity in the form of multiple dma_fences on a
  gpu_vm or a GEM object. The dma_resv contains an array / list
  of dma_fences and a lock that needs to be held when adding
  additional dma_fences to the dma_resv. The lock is of a type that
  allows deadlock-safe locking of multiple dma_resvs in arbitrary
  order. Please refer to the ``Reservation Objects`` section of the
  :doc:`dma-buf doc </driver-api/dma-buf>`.
* ``exec function``: An exec function is a function that revalidates all
  affected gpu_vmas, submits a GPU command batch and registers the
  dma_fence representing the GPU command's activity with all affected
  dma_resvs. For completeness, although not covered by this document,
  it's worth mentioning that an exec function may also be the
  revalidation worker that is used by some drivers in compute /
  long-running mode.
* ``local object``: A GEM object which is only mapped within a
  single VM. Local GEM objects share the gpu_vm's dma_resv.
* ``external object``: a.k.a shared object: A GEM object which may be shared
  by multiple gpu_vms and whose backing storage may be shared with
  other drivers.

Locks and locking order
=======================

One of the benefits of VM_BIND is that local GEM objects share the gpu_vm's
dma_resv object and hence the dma_resv lock. So, even with a huge
number of local GEM objects, only one lock is needed to make the exec
sequence atomic.

The following locks and locking orders are used:

* The ``gpu_vm->lock`` (optionally an rwsem). Protects the gpu_vm's
  data structure keeping track of gpu_vmas. It can also protect the
  gpu_vm's list of userptr gpu_vmas. With a CPU mm analogy this would
  correspond to the mmap_lock. An rwsem allows several readers to walk
  the VM tree concurrently, but the benefit of that concurrency most
  likely varies from driver to driver.
* The ``userptr_seqlock``. This lock is taken in read mode for each
  userptr gpu_vma on the gpu_vm's userptr list, and in write mode during mmu
  notifier invalidation. This is not a real seqlock but described in
  ``mm/mmu_notifier.c`` as a "Collision-retry read-side/write-side
  'lock' a lot like a seqcount. However this allows multiple
  write-sides to hold it at once...". The read side critical section
  is enclosed by ``mmu_interval_read_begin() /
  mmu_interval_read_retry()`` with ``mmu_interval_read_begin()``
  sleeping if the write side is held.
  The write side is held by the core mm while calling mmu interval
  invalidation notifiers.
* The ``gpu_vm->resv`` lock. Protects the gpu_vm's list of gpu_vmas needing
  rebinding, as well as the residency state of all the gpu_vm's local
  GEM objects.
  Furthermore, it typically protects the gpu_vm's list of evicted and
  external GEM objects.
* The ``gpu_vm->userptr_notifier_lock``. This is an rwsem that is
  taken in read mode during exec and write mode during a mmu notifier
  invalidation. The userptr notifier lock is per gpu_vm.
* The ``gem_object->gpuva_lock`` This lock protects the GEM object's
  list of gpu_vm_bos. This is usually the same lock as the GEM
  object's dma_resv, but some drivers protects this list differently,
  see below.
* The ``gpu_vm list spinlocks``. With some implementations they are needed
  to be able to update the gpu_vm evicted- and external object
  list. For those implementations, the spinlocks are grabbed when the
  lists are manipulated. However, to avoid locking order violations
  with the dma_resv locks, a special scheme is needed when iterating
  over the lists.

gpu_vm_bo·gpu_vma 보호와 lifetime

115-150

GEM object의 `gpu_vm_bo` list와 각 `gpu_vm_bo`의 `gpu_vma` list는 `gem_object->gpuva_lock`이 보호합니다. 보통 이 lock은 GEM object의 dma_resv와 같지만, dma-fence signaling critical section 안에서 list에 접근해야 하는 driver는 그 경로에서 잡을 수 있는 별도 lock을 선택할 수 있습니다.

별도 lock을 선택한 driver는 `gpu_vm_bo`와 `gpu_vma` list를 순회하면서 추가로 잡는 lock이 ordering을 위반하지 않도록 더 주의해야 합니다.

DRM GPUVM helper는 관련 상황에서 이 lock이 잡혀 있는지 lockdep assertion을 제공하며, 실제로 어떤 lock을 사용하는지 helper에 알리는 `drm_gem_gpuva_set_lock()`도 제공합니다.

각 `gpu_vm_bo`는 underlying GEM object에 대한 reference-counted pointer를, 각 `gpu_vma`는 `gpu_vm_bo`에 대한 reference-counted pointer를 보유합니다.

그러나 GEM object의 `gpu_vm_bo` list와 그 아래 `gpu_vma` list를 순회하는 동안 `gem_object->gpuva_lock`을 놓으면 안 됩니다. 순회 자체가 각 attached `gpu_vma` reference를 따로 잡지 않으므로 lock을 놓는 순간 entry가 예고 없이 사라질 수 있습니다. 별도 scheme도 가능하지만 추가 복잡성이 필요하며 이 문서 범위 밖입니다.

DRM GPUVM 구현에서는 각 `gpu_vm_bo`와 `gpu_vma`가 `gpu_vm` reference count도 보유합니다. Circular reference를 피하려면 `gpu_vm` destructor에서 `gpu_vma` cleanup을 수행하면 안 됩니다.

Driver는 보통 별도 `gpu_vm` close function에서 해당 VM을 사용하는 GPU execution을 abort하고 모든 `gpu_vma`를 unmap한 뒤 page-table memory를 release합니다.

Reference와 list lifetime
관계보장
gpu_vm_bo → GEM objectReference-counted pointer
gpu_vma → gpu_vm_boReference-counted pointer
gpu_vm_bo·gpu_vma → gpu_vmDRM GPUVM 구현에서 reference count 보유
List iterationgem_object->gpuva_lock을 놓지 않아야 entry 생존
gpu_vm destructorCircular reference 때문에 VMA cleanup 금지

어떤 reference와 lock이 object 생존을 보장하는지 구분합니다.

gpu_vm close
새 GPU execution 차단해당 VM을 사용하는 실행 abort모든 gpu_vma unmapPage-table memory releasegpu_vm_bo·gpu_vma reference 해제

Destructor가 아닌 close path에서 수행할 cleanup입니다.

.. _gpu_vma lifetime:

Protection and lifetime of gpu_vm_bos and gpu_vmas
==================================================

The GEM object's list of gpu_vm_bos, and the gpu_vm_bo's list of gpu_vmas
is protected by the ``gem_object->gpuva_lock``, which is typically the
same as the GEM object's dma_resv, but if the driver
needs to access these lists from within a dma_fence signalling
critical section, it can instead choose to protect it with a
separate lock, which can be locked from within the dma_fence signalling
critical section. Such drivers then need to pay additional attention
to what locks need to be taken from within the loop when iterating
over the gpu_vm_bo and gpu_vma lists to avoid locking-order violations.

The DRM GPUVM set of helpers provide lockdep asserts that this lock is
held in relevant situations and also provides a means of making itself
aware of which lock is actually used: :c:func:`drm_gem_gpuva_set_lock`.

Each gpu_vm_bo holds a reference counted pointer to the underlying GEM
object, and each gpu_vma holds a reference counted pointer to the
gpu_vm_bo. When iterating over the GEM object's list of gpu_vm_bos and
over the gpu_vm_bo's list of gpu_vmas, the ``gem_object->gpuva_lock`` must
not be dropped, otherwise, gpu_vmas attached to a gpu_vm_bo may
disappear without notice since those are not reference-counted. A
driver may implement its own scheme to allow this at the expense of
additional complexity, but this is outside the scope of this document.

In the DRM GPUVM implementation, each gpu_vm_bo and each gpu_vma
holds a reference count on the gpu_vm itself. Due to this, and to avoid circular
reference counting, cleanup of the gpu_vm's gpu_vmas must not be done from the
gpu_vm's destructor. Drivers typically implements a gpu_vm close
function for this cleanup. The gpu_vm close function will abort gpu
execution using this VM, unmap all gpu_vmas and release page-table memory.

Revalidation and eviction of local objects

Local object revalidation과 eviction

151-238

이 절의 code는 간소화한 pseudo-code입니다. 실제 구현에 필요한 dma_resv deadlock-avoidance algorithm과 dma_resv fence memory 예약은 생략되어 있습니다.

VM_BIND에서 GPU가 `gpu_vm`을 사용해 실행하는 동안 모든 local object는 resident여야 하고 이를 가리키는 유효한 `gpu_vma`가 설정되어 있어야 합니다. 따라서 GPU command buffer submission 앞에는 보통 revalidation section이 옵니다.

Revalidation은 `gpu_vm->resv`를 lock하고 evict list의 각 `gpu_vm_bo` backing object를 validate합니다. Local object는 `gpu_vm->resv`를 공유하므로 `gpu_vm_bo`의 `gpu_vma` list를 보호하는 reservation lock도 이미 잡혀 있습니다.

각 VMA를 별도 rebind list로 옮겨 page table을 rebind한 뒤 list에서 제거합니다. 별도 `gpu_vm` rebind list가 필요한 이유는 buffer object를 map하지 않는 userptr `gpu_vma`도 rebind 대상일 수 있기 때문입니다.

Validation 뒤에는 기존 `gpu_vm->resv` fence를 GPU job dependency로 추가하고 job을 submit한 다음 새 job dma-fence를 reservation에 등록하고 unlock합니다.

Local object eviction은 object LRU에서 대상을 얻어 `obj->resv`를 lock하고 모든 `gpu_vm_bo`를 VM evict list에 넣습니다. Eviction job에 기존 dependency를 연결하고 submit fence를 reservation에 추가한 뒤 unlock하고 object reference를 놓습니다.

Local object는 `obj->resv == gpu_vm->resv`이므로 object lock이 VM evict list도 보호합니다. Eviction 동안 local object의 dma_resv가 잡혀 있어 새 exec function이 eviction과 race할 수 없습니다.

VM_BIND에서는 eviction 전에 `gpu_vma`를 unbind할 필요가 없습니다. Driver가 eviction blit·copy를 GPU idle 뒤에 실행하거나 이전 GPU activity에 dependency로 연결해야 하고, freed memory에 대한 다음 GPU access 앞의 exec revalidation이 모든 VMA를 다시 bind하기 때문입니다.

Exec마다 VMA 일부만 rebind하는 구현이라면 선택되지 않은 모든 VMA는 workload submit 전에 반드시 unbind해야 합니다.

Local revalidation
gpu_vm->resv lockEvict list의 GEM object validate연결된 gpu_vma를 rebind list로 이동모든 gpu_vma rebind 후 list에서 제거기존 fence dependency를 추가하고 GPU job submitJob dma_fence 등록 후 unlock

Evicted local object를 resident로 만들고 job을 제출하는 순서입니다.

Local object 불변식
조건효과
obj->resv == gpu_vm->resvObject eviction과 VM evict list가 같은 lock으로 직렬화
Eviction copy가 이전 GPU activity를 기다림사용 중 backing store 이동 방지
다음 exec가 revalidateFreed memory에 접근하기 전 VMA 재bind
Selective rebind선택되지 않은 VMA는 submit 전에 unbind

Local object가 VM reservation을 공유할 때 성립하는 조건입니다.

Local eviction
LRU에서 object 획득obj->resv lockObject의 gpu_vm_bo를 gpu_vm evict list에 추가Eviction dependency 추가·job submitEviction fence를 obj->resv에 등록Unlock 후 object reference release

Object를 evict 대상으로 표시하고 copy job을 제출하는 순서입니다.

   dma_resv_lock(gpu_vm->resv);

   // Validation section starts here.
   for_each_gpu_vm_bo_on_evict_list(&gpu_vm->evict_list, &gpu_vm_bo) {
           validate_gem_bo(&gpu_vm_bo->gem_bo);

           // The following list iteration needs the Gem object's
           // dma_resv to be held (it protects the gpu_vm_bo's list of
           // gpu_vmas, but since local gem objects share the gpu_vm's
           // dma_resv, it is already held at this point.
           for_each_gpu_vma_of_gpu_vm_bo(&gpu_vm_bo, &gpu_vma)
                  move_gpu_vma_to_rebind_list(&gpu_vma, &gpu_vm->rebind_list);
   }

   for_each_gpu_vma_on_rebind_list(&gpu vm->rebind_list, &gpu_vma) {
           rebind_gpu_vma(&gpu_vma);
           remove_gpu_vma_from_rebind_list(&gpu_vma);
   }
   // Validation section ends here, and job submission starts.

   add_dependencies(&gpu_job, &gpu_vm->resv);
   job_dma_fence = gpu_submit(&gpu_job));

   add_dma_fence(job_dma_fence, &gpu_vm->resv);
   dma_resv_unlock(gpu_vm->resv);
   obj = get_object_from_lru();

   dma_resv_lock(obj->resv);
   for_each_gpu_vm_bo_of_obj(obj, &gpu_vm_bo);
           add_gpu_vm_bo_to_evict_list(&gpu_vm_bo, &gpu_vm->evict_list);

   add_dependencies(&eviction_job, &obj->resv);
   job_dma_fence = gpu_submit(&eviction_job);
   add_dma_fence(&obj->resv, job_dma_fence);

   dma_resv_unlock(&obj->resv);
   put_object(obj);
==========================================

Note that in all the code examples given below we use simplified
pseudo-code. In particular, the dma_resv deadlock avoidance algorithm
as well as reserving memory for dma_resv fences is left out.

Revalidation
____________
With VM_BIND, all local objects need to be resident when the gpu is
executing using the gpu_vm, and the objects need to have valid
gpu_vmas set up pointing to them. Typically, each gpu command buffer
submission is therefore preceded with a re-validation section:

.. code-block:: C

   dma_resv_lock(gpu_vm->resv);

   // Validation section starts here.
   for_each_gpu_vm_bo_on_evict_list(&gpu_vm->evict_list, &gpu_vm_bo) {
           validate_gem_bo(&gpu_vm_bo->gem_bo);

           // The following list iteration needs the Gem object's
           // dma_resv to be held (it protects the gpu_vm_bo's list of
           // gpu_vmas, but since local gem objects share the gpu_vm's
           // dma_resv, it is already held at this point.
           for_each_gpu_vma_of_gpu_vm_bo(&gpu_vm_bo, &gpu_vma)
                  move_gpu_vma_to_rebind_list(&gpu_vma, &gpu_vm->rebind_list);
   }

   for_each_gpu_vma_on_rebind_list(&gpu vm->rebind_list, &gpu_vma) {
           rebind_gpu_vma(&gpu_vma);
           remove_gpu_vma_from_rebind_list(&gpu_vma);
   }
   // Validation section ends here, and job submission starts.

   add_dependencies(&gpu_job, &gpu_vm->resv);
   job_dma_fence = gpu_submit(&gpu_job));

   add_dma_fence(job_dma_fence, &gpu_vm->resv);
   dma_resv_unlock(gpu_vm->resv);

The reason for having a separate gpu_vm rebind list is that there
might be userptr gpu_vmas that are not mapping a buffer object that
also need rebinding.

Eviction
________

Eviction of one of these local objects will then look similar to the
following:

.. code-block:: C

   obj = get_object_from_lru();

   dma_resv_lock(obj->resv);
   for_each_gpu_vm_bo_of_obj(obj, &gpu_vm_bo);
           add_gpu_vm_bo_to_evict_list(&gpu_vm_bo, &gpu_vm->evict_list);

   add_dependencies(&eviction_job, &obj->resv);
   job_dma_fence = gpu_submit(&eviction_job);
   add_dma_fence(&obj->resv, job_dma_fence);

   dma_resv_unlock(&obj->resv);
   put_object(obj);

Note that since the object is local to the gpu_vm, it will share the gpu_vm's
dma_resv lock such that ``obj->resv == gpu_vm->resv``.
The gpu_vm_bos marked for eviction are put on the gpu_vm's evict list,
which is protected by ``gpu_vm->resv``. During eviction all local
objects have their dma_resv locked and, due to the above equality, also
the gpu_vm's dma_resv protecting the gpu_vm's evict list is locked.

With VM_BIND, gpu_vmas don't need to be unbound before eviction,
since the driver must ensure that the eviction blit or copy will wait
for GPU idle or depend on all previous GPU activity. Furthermore, any
subsequent attempt by the GPU to access freed memory through the
gpu_vma will be preceded by a new exec function, with a revalidation
section which will make sure all gpu_vmas are rebound. The eviction
code holding the object's dma_resv while revalidating will ensure a
new exec function may not race with the eviction.

A driver can be implemented in such a way that, on each exec function,
only a subset of vmas are selected for rebind.  In this case, all vmas that are
*not* selected for rebind must be unbound before the exec
function workload is submitted.

Locking with external buffer objects

External buffer object locking

239-320

External buffer object는 여러 `gpu_vm`이 공유할 수 있으므로 특정 `gpu_vm`의 reservation object를 공유할 수 없고 자체 dma_resv를 가져야 합니다.

하나 이상의 `gpu_vma`로 VM에 bind된 external object는 VM별 list에 넣습니다. 이 list는 `gpu_vm->resv` 또는 `Spinlock iteration` 절의 `gpu_vm` list spinlock이 보호합니다.

VM reservation을 lock하면 external object list를 안전하게 순회하며 각 external object의 dma_resv를 lock할 수 있습니다. List spinlock을 사용하면 더 정교한 iteration scheme이 필요합니다.

External object를 evict할 때는 그 object가 bind된 모든 `gpu_vm`의 `gpu_vm_bo`를 각 VM evict list에 넣어야 합니다. 그러나 이 시점에는 보통 object 자체 dma_resv만 잡혀 있고 VM dma_resv는 잡혀 있지 않습니다.

Eviction 시점에 `ww_acquire` context가 있다면 VM dma_resv도 잡을 수 있지만 비싼 `ww_mutex` rollback을 유발할 수 있습니다. 단순한 방법은 evicted GEM object의 모든 `gpu_vm_bo`에 `evicted` bool을 표시하는 것입니다. 이 bool은 object dma_resv가 정식으로 보호합니다.

다음 exec에서 external object list를 순회할 때 VM dma_resv와 object dma_resv를 모두 잡습니다. 이때 `evicted` 표시를 확인해 해당 `gpu_vm_bo`를 VM evict list로 옮긴 뒤 backing object validate와 VMA rebind를 수행합니다.

Job submit 후 같은 job dma-fence를 `gpu_vm->resv`뿐 아니라 모든 external object dma_resv에도 등록해야 합니다. 그 다음 모든 reservation lock을 해제합니다.

Shared-object-aware eviction은 local object면 즉시 VM evict list에 추가하고 external object면 `evicted` bool만 표시합니다. 그런 다음 eviction job fence를 object dma_resv에 등록합니다.

External object eviction 전달
Object eviction에서 object dma_resv만 lock각 external gpu_vm_bo에 evicted bool 표시다음 exec가 gpu_vm->resv와 object dma_resv를 함께 lockEvicted gpu_vm_bo를 해당 VM evict list로 이동Object validate·VMA rebindJob fence를 VM과 모든 external object reservation에 등록

Object-private lock에서 VM별 evict list로 상태가 전달되는 과정입니다.

Local·external eviction 차이
구분ReservationEviction 표시
Local objectgpu_vm->resv 공유즉시 gpu_vm evict list에 추가
External objectObject-private dma_resvevicted bool 표시 후 다음 exec에서 VM list로 이동
External object + ww_acquireVM locks 추가 획득 가능비싼 ww_mutex rollback 위험

Reservation ownership에 따른 표시 방식을 비교합니다.

   dma_resv_lock(gpu_vm->resv);

   // External object list is protected by the gpu_vm->resv lock.
   for_each_gpu_vm_bo_on_extobj_list(gpu_vm, &gpu_vm_bo) {
           dma_resv_lock(gpu_vm_bo.gem_obj->resv);
           if (gpu_vm_bo_marked_evicted(&gpu_vm_bo))
                   add_gpu_vm_bo_to_evict_list(&gpu_vm_bo, &gpu_vm->evict_list);
   }

   for_each_gpu_vm_bo_on_evict_list(&gpu_vm->evict_list, &gpu_vm_bo) {
           validate_gem_bo(&gpu_vm_bo->gem_bo);

           for_each_gpu_vma_of_gpu_vm_bo(&gpu_vm_bo, &gpu_vma)
                  move_gpu_vma_to_rebind_list(&gpu_vma, &gpu_vm->rebind_list);
   }

   for_each_gpu_vma_on_rebind_list(&gpu vm->rebind_list, &gpu_vma) {
           rebind_gpu_vma(&gpu_vma);
           remove_gpu_vma_from_rebind_list(&gpu_vma);
   }

   add_dependencies(&gpu_job, &gpu_vm->resv);
   job_dma_fence = gpu_submit(&gpu_job));

   add_dma_fence(job_dma_fence, &gpu_vm->resv);
   for_each_external_obj(gpu_vm, &obj)
          add_dma_fence(job_dma_fence, &obj->resv);
   dma_resv_unlock_all_resv_locks();
   obj = get_object_from_lru();

   dma_resv_lock(obj->resv);
   for_each_gpu_vm_bo_of_obj(obj, &gpu_vm_bo)
           if (object_is_vm_local(obj))
                add_gpu_vm_bo_to_evict_list(&gpu_vm_bo, &gpu_vm->evict_list);
           else
                mark_gpu_vm_bo_evicted(&gpu_vm_bo);

   add_dependencies(&eviction_job, &obj->resv);
   job_dma_fence = gpu_submit(&eviction_job);
   add_dma_fence(&obj->resv, job_dma_fence);

   dma_resv_unlock(&obj->resv);
   put_object(obj);
====================================

Since external buffer objects may be shared by multiple gpu_vm's they
can't share their reservation object with a single gpu_vm. Instead
they need to have a reservation object of their own. The external
objects bound to a gpu_vm using one or many gpu_vmas are therefore put on a
per-gpu_vm list which is protected by the gpu_vm's dma_resv lock or
one of the :ref:`gpu_vm list spinlocks <Spinlock iteration>`. Once
the gpu_vm's reservation object is locked, it is safe to traverse the
external object list and lock the dma_resvs of all external
objects. However, if instead a list spinlock is used, a more elaborate
iteration scheme needs to be used.

At eviction time, the gpu_vm_bos of *all* the gpu_vms an external
object is bound to need to be put on their gpu_vm's evict list.
However, when evicting an external object, the dma_resvs of the
gpu_vms the object is bound to are typically not held. Only
the object's private dma_resv can be guaranteed to be held. If there
is a ww_acquire context at hand at eviction time we could grab those
dma_resvs but that could cause expensive ww_mutex rollbacks. A simple
option is to just mark the gpu_vm_bos of the evicted gem object with
an ``evicted`` bool that is inspected before the next time the
corresponding gpu_vm evicted list needs to be traversed. For example, when
traversing the list of external objects and locking them. At that time,
both the gpu_vm's dma_resv and the object's dma_resv is held, and the
gpu_vm_bo marked evicted, can then be added to the gpu_vm's list of
evicted gpu_vm_bos. The ``evicted`` bool is formally protected by the
object's dma_resv.

The exec function becomes

.. code-block:: C

   dma_resv_lock(gpu_vm->resv);

   // External object list is protected by the gpu_vm->resv lock.
   for_each_gpu_vm_bo_on_extobj_list(gpu_vm, &gpu_vm_bo) {
           dma_resv_lock(gpu_vm_bo.gem_obj->resv);
           if (gpu_vm_bo_marked_evicted(&gpu_vm_bo))
                   add_gpu_vm_bo_to_evict_list(&gpu_vm_bo, &gpu_vm->evict_list);
   }

   for_each_gpu_vm_bo_on_evict_list(&gpu_vm->evict_list, &gpu_vm_bo) {
           validate_gem_bo(&gpu_vm_bo->gem_bo);

           for_each_gpu_vma_of_gpu_vm_bo(&gpu_vm_bo, &gpu_vma)
                  move_gpu_vma_to_rebind_list(&gpu_vma, &gpu_vm->rebind_list);
   }

   for_each_gpu_vma_on_rebind_list(&gpu vm->rebind_list, &gpu_vma) {
           rebind_gpu_vma(&gpu_vma);
           remove_gpu_vma_from_rebind_list(&gpu_vma);
   }

   add_dependencies(&gpu_job, &gpu_vm->resv);
   job_dma_fence = gpu_submit(&gpu_job));

   add_dma_fence(job_dma_fence, &gpu_vm->resv);
   for_each_external_obj(gpu_vm, &obj)
          add_dma_fence(job_dma_fence, &obj->resv);
   dma_resv_unlock_all_resv_locks();

And the corresponding shared-object aware eviction would look like:

.. code-block:: C

   obj = get_object_from_lru();

   dma_resv_lock(obj->resv);
   for_each_gpu_vm_bo_of_obj(obj, &gpu_vm_bo)
           if (object_is_vm_local(obj))
                add_gpu_vm_bo_to_evict_list(&gpu_vm_bo, &gpu_vm->evict_list);
           else
                mark_gpu_vm_bo_evicted(&gpu_vm_bo);

   add_dependencies(&eviction_job, &obj->resv);
   job_dma_fence = gpu_submit(&eviction_job);
   add_dma_fence(&obj->resv, job_dma_fence);

   dma_resv_unlock(&obj->resv);
   put_object(obj);

dma_resv 없이 gpu_vm list 순회

321-384

일부 driver는 VM evict·external object list에 접근할 때 항상 `gpu_vm` dma_resv를 잡습니다. 그러나 dma-fence signaling critical path의 asynchronous state update처럼 dma_resv 없이 접근해야 하는 driver도 있습니다.

이 경우 list 조작은 spinlock으로 보호할 수 있습니다. 하지만 각 item을 처리하려면 상위 sleeping lock이 필요하므로 이미 방문한 item을 임시 private list로 옮기고, item마다 처리하는 동안 spinlock을 놓아야 합니다.

Pseudo-code는 첫 entry를 `still_in_list`로 옮기고 `list_entry_get_unless_zero()`로 reference를 확보한 뒤 spinlock을 놓고 `process(entry)`를 실행합니다. 다시 spinlock을 잡아 reference를 놓고 반복한 뒤 임시 list를 원래 list tail에 splice합니다.

추가 lock과 atomic operation 비용 때문에 dma_resv 밖에서 list에 접근하지 않아도 되는 driver, 특히 item 수가 많을 것으로 예상되는 driver는 이 scheme을 피하는 편이 좋을 수 있습니다.

반대로 item 수가 적거나 iteration 빈도가 낮거나 각 처리 자체의 비용이 크면 atomic overhead는 대체로 무시할 수 있습니다.

Iteration 중 item이 원래 list에서 임시로 빠지므로 이 전체 iteration은 외부 lock 또는 semaphore가 보호해야 합니다. Local `still_in_list`도 `gpu_vm->list_lock`의 보호 대상이며 iteration과 동시에 local list에서 item이 제거될 수 있음을 고려해야 합니다.

관련 구현은 `drm_gpuvm_locking` 참조와 내부 `get_next_vm_bo_from_list()` function에서 확인할 수 있습니다.

Spinlock list iteration
Outer lock 또는 semaphore 획득gpu_vm->list_lock spinlock 획득첫 entry를 still_in_list로 이동하고 reference 획득Spinlock을 놓고 sleeping 가능한 process(entry) 수행Spinlock 재획득 후 reference 해제모든 entry 처리 뒤 still_in_list를 원래 list에 splice

Sleeping lock이 필요한 item을 spinlock list에서 안전하게 처리합니다.

Spinlock scheme 선택 기준
상황권장
Fence signaling path에서 list update 필요Spinlock scheme 사용
항상 dma_resv 아래에서 접근 가능복잡한 scheme 회피
List item이 매우 많음Atomic overhead와 순회 비용 주의
Item이 적거나 처리 비용이 큼Atomic overhead가 대체로 미미

추가 atomic 비용과 사용 필요성을 평가합니다.

    struct list_head still_in_list;

    INIT_LIST_HEAD(&still_in_list);

    spin_lock(&gpu_vm->list_lock);
    do {
            struct list_head *entry = list_first_entry_or_null(&gpu_vm->list, head);

            if (!entry)
                    break;

            list_move_tail(&entry->head, &still_in_list);
            list_entry_get_unless_zero(entry);
            spin_unlock(&gpu_vm->list_lock);

            process(entry);

            spin_lock(&gpu_vm->list_lock);
            list_entry_put(entry);
    } while (true);

    list_splice_tail(&still_in_list, &gpu_vm->list);
    spin_unlock(&gpu_vm->list_lock);
.. _Spinlock iteration:

Accessing the gpu_vm's lists without the dma_resv lock held
===========================================================

Some drivers will hold the gpu_vm's dma_resv lock when accessing the
gpu_vm's evict list and external objects lists. However, there are
drivers that need to access these lists without the dma_resv lock
held, for example due to asynchronous state updates from within the
dma_fence signalling critical path. In such cases, a spinlock can be
used to protect manipulation of the lists. However, since higher level
sleeping locks need to be taken for each list item while iterating
over the lists, the items already iterated over need to be
temporarily moved to a private list and the spinlock released
while processing each item:

.. code block:: C

    struct list_head still_in_list;

    INIT_LIST_HEAD(&still_in_list);

    spin_lock(&gpu_vm->list_lock);
    do {
            struct list_head *entry = list_first_entry_or_null(&gpu_vm->list, head);

            if (!entry)
                    break;

            list_move_tail(&entry->head, &still_in_list);
            list_entry_get_unless_zero(entry);
            spin_unlock(&gpu_vm->list_lock);

            process(entry);

            spin_lock(&gpu_vm->list_lock);
            list_entry_put(entry);
    } while (true);

    list_splice_tail(&still_in_list, &gpu_vm->list);
    spin_unlock(&gpu_vm->list_lock);

Due to the additional locking and atomic operations, drivers that *can*
avoid accessing the gpu_vm's list outside of the dma_resv lock
might want to avoid also this iteration scheme. Particularly, if the
driver anticipates a large number of list items. For lists where the
anticipated number of list items is small, where list iteration doesn't
happen very often or if there is a significant additional cost
associated with each iteration, the atomic operation overhead
associated with this type of iteration is, most likely, negligible. Note that
if this scheme is used, it is necessary to make sure this list
iteration is protected by an outer level lock or semaphore, since list
items are temporarily pulled off the list while iterating, and it is
also worth mentioning that the local list ``still_in_list`` should
also be considered protected by the ``gpu_vm->list_lock``, and it is
thus possible that items can be removed also from the local list
concurrently with list iteration.

Please refer to the :ref:`DRM GPUVM locking section
<drm_gpuvm_locking>` and its internal
:c:func:`get_next_vm_bo_from_list` function.


userptr gpu_vmas

Userptr mapping과 exec revalidation

385-479

Userptr `gpu_vma`는 buffer object 대신 CPU mm의 anonymous page 또는 file page-cache page range를 GPU virtual address에 직접 map합니다.

Bind 때 `pin_user_pages()`로 page를 pin하고 unbind 때 unpin하는 단순 방식은 한 userspace process가 system memory 전체를 pin하는 denial-of-service vector가 됩니다. 올바른 accounting이 있는 특수 use case에서는 가능하지만 일반 해법은 아닙니다.

일반적인 구현은 필요한 page reference를 얻고 CPU mm이 page를 unmap하기 직전에 MMU notifier로 통지받습니다. GPU에 read-only로 map하지 않았다면 page를 dirty로 표시한 뒤 reference를 놓습니다.

MMU notifier가 CPU mm의 page 제거를 알리면 notifier 안에서 VM idle을 기다려 GPU access를 중단해야 합니다. 다음 GPU access 전에는 GPU page table에서 old page를 unmap하고 새 page reference를 다시 얻어야 합니다.

Core mm이 page를 laundry할 때도 unmap notification이 오므로 다음 GPU access 전에 다시 dirty 표시할 수 있습니다. GPU driver에 필요하지 않은 NUMA accounting notification도 유사하게 오지만 특정 notification만 제외하기는 어렵습니다. Device DMA용 MMU notifier 등록은 `mmu-notifier-registration-case` 참조를 따릅니다.

`get_user_pages()`로 `struct page` reference를 얻는 작업은 dma_resv lock 아래에서 할 수 없습니다. CPU page fault를 resolve할 때 잡는 `mmap_lock`과 dma_resv lock의 ordering을 위반하기 때문입니다. 따라서 userptr VMA list는 예제의 `gpu_vm->lock` 같은 outer lock이 보호해야 합니다.

Exec revalidation은 필요할 때 `gpu_vm->lock`을 write mode로 잡고 `mmu_interval_read_begin()`으로 sequence를 얻습니다. Saved sequence와 다르면 새 page pointer를 얻고 잠시 `gpu_vm->resv`를 잡아 VMA를 revalidate list에 추가한 뒤 saved sequence를 갱신합니다.

일반 revalidation과 dependency 추가 뒤 `gpu_vm->userptr_notifier_lock`을 read mode로 잡고 `mmu_interval_read_retry()`를 실행합니다. Sequence가 invalidated됐으면 notifier lock을 놓고 처음부터 retry합니다.

Retry가 필요 없으면 GPU job을 submit하고 job dma-fence를 VM과 모든 external object reservation에 등록합니다. Reservation locks, notifier read lock, VM outer lock 순서로 해제합니다.

`mmu_interval_read_begin()`과 `mmu_interval_read_retry()` 사이가 `userptr_seqlock` read-side critical section입니다. 예제는 VMA 하나만 보이지만 실제로는 VM의 모든 userptr `gpu_vma`를 순회해 검사합니다.

Userptr MMU invalidation notifier는 reclaim context에서 호출될 수 있으므로 ordering 위반을 피하려면 notifier 안에서 어떤 dma_resv lock이나 `gpu_vm->lock`도 잡을 수 없습니다.

Userptr exec revalidation
gpu_vm->lock write 획득mmu_interval_read_begin으로 sequence snapshotSequence 변경 시 새 page reference와 revalidate list 갱신기존 reservation dependency 추가userptr_notifier_lock read 획득 후 read_retry유효하면 job submit·fence 등록, 아니면 retryReservation·notifier·VM lock 해제

Page sequence 확인부터 job fence 등록까지의 critical path입니다.

Userptr lock 제약
작업필요 lock·금지
get_user_pagesdma_resv 아래에서 금지, outer gpu_vm->lock 사용
Sequence snapshotuserptr_seqlock read side
Exec final validationuserptr_notifier_lock read
MMU invalidationuserptr_notifier_lock write
Notifier reclaim contextdma_resv와 gpu_vm->lock 획득 금지

CPU mm과 GPU submission 사이의 ordering 조건입니다.

   // Exclusive locking mode here is strictly needed only if there are
   // invalidated userptr gpu_vmas present, to avoid concurrent userptr
   // revalidations of the same userptr gpu_vma.
   down_write(&gpu_vm->lock);
   retry:

   // Note: mmu_interval_read_begin() blocks until there is no
   // invalidation notifier running anymore.
   seq = mmu_interval_read_begin(&gpu_vma->userptr_interval);
   if (seq != gpu_vma->saved_seq) {
           obtain_new_page_pointers(&gpu_vma);
           dma_resv_lock(&gpu_vm->resv);
           add_gpu_vma_to_revalidate_list(&gpu_vma, &gpu_vm);
           dma_resv_unlock(&gpu_vm->resv);
           gpu_vma->saved_seq = seq;
   }

   // The usual revalidation goes here.

   // Final userptr sequence validation may not happen before the
   // submission dma_fence is added to the gpu_vm's resv, from the POW
   // of the MMU invalidation notifier. Hence the
   // userptr_notifier_lock that will make them appear atomic.

   add_dependencies(&gpu_job, &gpu_vm->resv);
   down_read(&gpu_vm->userptr_notifier_lock);
   if (mmu_interval_read_retry(&gpu_vma->userptr_interval, gpu_vma->saved_seq)) {
          up_read(&gpu_vm->userptr_notifier_lock);
          goto retry;
   }

   job_dma_fence = gpu_submit(&gpu_job));

   add_dma_fence(job_dma_fence, &gpu_vm->resv);

   for_each_external_obj(gpu_vm, &obj)
          add_dma_fence(job_dma_fence, &obj->resv);

   dma_resv_unlock_all_resv_locks();
   up_read(&gpu_vm->userptr_notifier_lock);
   up_write(&gpu_vm->lock);
================

A userptr gpu_vma is a gpu_vma that, instead of mapping a buffer object to a
GPU virtual address range, directly maps a CPU mm range of anonymous-
or file page-cache pages.
A very simple approach would be to just pin the pages using
pin_user_pages() at bind time and unpin them at unbind time, but this
creates a Denial-Of-Service vector since a single user-space process
would be able to pin down all of system memory, which is not
desirable. (For special use-cases and assuming proper accounting pinning might
still be a desirable feature, though). What we need to do in the
general case is to obtain a reference to the desired pages, make sure
we are notified using a MMU notifier just before the CPU mm unmaps the
pages, dirty them if they are not mapped read-only to the GPU, and
then drop the reference.
When we are notified by the MMU notifier that CPU mm is about to drop the
pages, we need to stop GPU access to the pages by waiting for VM idle
in the MMU notifier and make sure that before the next time the GPU
tries to access whatever is now present in the CPU mm range, we unmap
the old pages from the GPU page tables and repeat the process of
obtaining new page references. (See the :ref:`notifier example
<Invalidation example>` below). Note that when the core mm decides to
laundry pages, we get such an unmap MMU notification and can mark the
pages dirty again before the next GPU access. We also get similar MMU
notifications for NUMA accounting which the GPU driver doesn't really
need to care about, but so far it has proven difficult to exclude
certain notifications.

Using a MMU notifier for device DMA (and other methods) is described in
:ref:`the pin_user_pages() documentation <mmu-notifier-registration-case>`.

Now, the method of obtaining struct page references using
get_user_pages() unfortunately can't be used under a dma_resv lock
since that would violate the locking order of the dma_resv lock vs the
mmap_lock that is grabbed when resolving a CPU pagefault. This means
the gpu_vm's list of userptr gpu_vmas needs to be protected by an
outer lock, which in our example below is the ``gpu_vm->lock``.

The MMU interval seqlock for a userptr gpu_vma is used in the following
way:

.. code-block:: C

   // Exclusive locking mode here is strictly needed only if there are
   // invalidated userptr gpu_vmas present, to avoid concurrent userptr
   // revalidations of the same userptr gpu_vma.
   down_write(&gpu_vm->lock);
   retry:

   // Note: mmu_interval_read_begin() blocks until there is no
   // invalidation notifier running anymore.
   seq = mmu_interval_read_begin(&gpu_vma->userptr_interval);
   if (seq != gpu_vma->saved_seq) {
           obtain_new_page_pointers(&gpu_vma);
           dma_resv_lock(&gpu_vm->resv);
           add_gpu_vma_to_revalidate_list(&gpu_vma, &gpu_vm);
           dma_resv_unlock(&gpu_vm->resv);
           gpu_vma->saved_seq = seq;
   }

   // The usual revalidation goes here.

   // Final userptr sequence validation may not happen before the
   // submission dma_fence is added to the gpu_vm's resv, from the POW
   // of the MMU invalidation notifier. Hence the
   // userptr_notifier_lock that will make them appear atomic.

   add_dependencies(&gpu_job, &gpu_vm->resv);
   down_read(&gpu_vm->userptr_notifier_lock);
   if (mmu_interval_read_retry(&gpu_vma->userptr_interval, gpu_vma->saved_seq)) {
          up_read(&gpu_vm->userptr_notifier_lock);
          goto retry;
   }

   job_dma_fence = gpu_submit(&gpu_job));

   add_dma_fence(job_dma_fence, &gpu_vm->resv);

   for_each_external_obj(gpu_vm, &obj)
          add_dma_fence(job_dma_fence, &obj->resv);

   dma_resv_unlock_all_resv_locks();
   up_read(&gpu_vm->userptr_notifier_lock);
   up_write(&gpu_vm->lock);

The code between ``mmu_interval_read_begin()`` and the
``mmu_interval_read_retry()`` marks the read side critical section of
what we call the ``userptr_seqlock``. In reality, the gpu_vm's userptr
gpu_vma list is looped through, and the check is done for *all* of its
userptr gpu_vmas, although we only show a single one here.

The userptr gpu_vma MMU invalidation notifier might be called from
reclaim context and, again, to avoid locking order violations, we can't
take any dma_resv lock nor the gpu_vm->lock from within it.

MMU invalidation과 효율적인 userptr iteration

480-529

Invalidation notifier는 exec function이 새 sequence를 보고 물러나거나 이미 제출된 job의 dma-fence를 notifier가 기다리도록 만들어야 합니다.

Notifier는 `gpu_vm->userptr_notifier_lock`을 write mode로 잡고 `mmu_interval_set_seq()`로 현재 sequence를 invalid value로 설정한 뒤 unlock합니다. 이 시점부터 exec의 final retry check는 반드시 실패하므로 새 job을 성공적으로 submit할 수 없습니다.

모든 invalidation callback이 끝나면 MMU notifier core가 sequence를 다시 valid value로 바꿉니다. 그 전에 old page에 대한 GPU access를 멈춰야 하므로 notifier는 `gpu_vm->resv`의 `DMA_RESV_USAGE_BOOKKEEP` fence를 `MAX_SCHEDULE_TIMEOUT`까지 기다립니다.

Invalidation notifier가 반환하면 GPU는 더 이상 old userptr page에 접근하지 않으며 새 GPU submission이 성공하려면 page binding을 다시 수행해야 합니다.

Userptr list가 커지면 exec마다 모든 VMA의 saved sequence를 확인하는 방식은 비효율적입니다. 해결책은 invalidated userptr VMA만 별도 VM list에 넣고 exec마다 그 list만 검사하는 것입니다.

MMU notifier에서는 `gpu_vm->lock`이나 `gpu_vm->resv` 같은 outer lock을 잡을 수 없으므로 invalidated list에는 앞의 spinlock iteration scheme이 잘 맞습니다. 다만 iteration 중 list가 완전함을 보장하려면 여전히 `gpu_vm->lock`을 잡아야 합니다.

이 별도 list를 사용하면 exec retry check는 invalidated list가 비었는지 확인하는 단순한 검사로 바뀝니다.

MMU invalidation handshake
Notifier가 userptr_notifier_lock write 획득Sequence를 invalid value로 설정이후 exec final check는 retry로 전환이미 제출된 GPU activity의 BOOKKEEP fence 대기Old page GPU access 종료Notifier 반환 후 다음 exec가 page binding 재수행

Exec submit과 old-page 접근 중단을 atomic하게 보이게 하는 순서입니다.

Userptr iteration 최적화
방식Exec 비용Lock 조건
모든 userptr 순회VM의 모든 saved sequence 검사gpu_vm->lock + userptr sequence
Invalidated listInvalidated VMA만 검사, empty이면 retry 불필요Notifier 추가는 spinlock, iteration completeness는 gpu_vm->lock

전체 순회와 invalidated-list 방식의 차이입니다.

  bool gpu_vma_userptr_invalidate(userptr_interval, cur_seq)
  {
          // Make sure the exec function either sees the new sequence
          // and backs off or we wait for the dma-fence:

          down_write(&gpu_vm->userptr_notifier_lock);
          mmu_interval_set_seq(userptr_interval, cur_seq);
          up_write(&gpu_vm->userptr_notifier_lock);

          // At this point, the exec function can't succeed in
          // submitting a new job, because cur_seq is an invalid
          // sequence number and will always cause a retry. When all
          // invalidation callbacks, the mmu notifier core will flip
          // the sequence number to a valid one. However we need to
          // stop gpu access to the old pages here.

          dma_resv_wait_timeout(&gpu_vm->resv, DMA_RESV_USAGE_BOOKKEEP,
                                false, MAX_SCHEDULE_TIMEOUT);
          return true;
  }
.. _Invalidation example:
.. code-block:: C

  bool gpu_vma_userptr_invalidate(userptr_interval, cur_seq)
  {
          // Make sure the exec function either sees the new sequence
          // and backs off or we wait for the dma-fence:

          down_write(&gpu_vm->userptr_notifier_lock);
          mmu_interval_set_seq(userptr_interval, cur_seq);
          up_write(&gpu_vm->userptr_notifier_lock);

          // At this point, the exec function can't succeed in
          // submitting a new job, because cur_seq is an invalid
          // sequence number and will always cause a retry. When all
          // invalidation callbacks, the mmu notifier core will flip
          // the sequence number to a valid one. However we need to
          // stop gpu access to the old pages here.

          dma_resv_wait_timeout(&gpu_vm->resv, DMA_RESV_USAGE_BOOKKEEP,
                                false, MAX_SCHEDULE_TIMEOUT);
          return true;
  }

When this invalidation notifier returns, the GPU can no longer be
accessing the old pages of the userptr gpu_vma and needs to redo the
page-binding before a new GPU submission can succeed.

Efficient userptr gpu_vma exec_function iteration
_________________________________________________

If the gpu_vm's list of userptr gpu_vmas becomes large, it's
inefficient to iterate through the complete lists of userptrs on each
exec function to check whether each userptr gpu_vma's saved
sequence number is stale. A solution to this is to put all
*invalidated* userptr gpu_vmas on a separate gpu_vm list and
only check the gpu_vmas present on this list on each exec
function. This list will then lend itself very-well to the spinlock
locking scheme that is
:ref:`described in the spinlock iteration section <Spinlock iteration>`, since
in the mmu notifier, where we add the invalidated gpu_vmas to the
list, it's not possible to take any outer locks like the
``gpu_vm->lock`` or the ``gpu_vm->resv`` lock. Note that the
``gpu_vm->lock`` still needs to be taken while iterating to ensure the list is
complete, as also mentioned in that section.

If using an invalidated userptr list like this, the retry check in the
exec function trivially becomes a check for invalidated list empty.

Locking at bind and unbind time

Bind·unbind 시 linking과 lifetime

530-547

GEM object-backed `gpu_vma`를 bind할 때 각 VMA를 `gpu_vm_bo`와 연결하고, 그 `gpu_vm_bo`를 GEM object의 `gpu_vm_bo` list와 필요하면 VM external object list에 추가해야 합니다. 이를 `gpu_vma` linking이라고 합니다.

Linking에는 보통 `gpu_vm->lock`과 `gem_object->gpuva_lock`이 모두 필요합니다. Unlinking할 때도 같은 lock을 잡아야 합니다.

이 규칙은 `gpu_vm->resv` 또는 GEM object dma_resv 아래에서 `gpu_vma`를 순회하는 동안 해당 iteration lock을 놓지 않는 한 VMA가 살아 있도록 보장합니다.

Userptr `gpu_vma`를 destroy할 때도 outer `gpu_vm->lock`을 잡아야 합니다. 그렇지 않으면 invalidated userptr list를 순회할 때 VMA lifetime을 보장할 수 없습니다.

gpu_vma linking
gpu_vm->lock 획득gem_object->gpuva_lock 획득gpu_vma를 gpu_vm_bo와 연결gpu_vm_bo를 GEM object list에 추가필요하면 VM external object list에도 추가역순으로 lock 해제

Object-backed VMA를 두 list에 안전하게 연결하는 순서입니다.

Link·unlink lifetime 규칙
대상필수 lock
GEM object-backed gpu_vma link/unlinkgpu_vm->lock + gem_object->gpuva_lock
Reservation 아래 list iterationIteration이 끝날 때까지 해당 lock 유지
Userptr gpu_vma destroyOuter gpu_vm->lock

VMA 종류에 따른 필수 outer lock입니다.

===============================

At bind time, assuming a GEM object backed gpu_vma, each
gpu_vma needs to be associated with a gpu_vm_bo and that
gpu_vm_bo in turn needs to be added to the GEM object's
gpu_vm_bo list, and possibly to the gpu_vm's external object
list. This is referred to as *linking* the gpu_vma, and typically
requires that the ``gpu_vm->lock`` and the ``gem_object->gpuva_lock``
are held. When unlinking a gpu_vma the same locks should be held,
and that ensures that when iterating over ``gpu_vmas`, either under
the ``gpu_vm->resv`` or the GEM object's dma_resv, that the gpu_vmas
stay alive as long as the lock under which we iterate is not released. For
userptr gpu_vmas it's similarly required that during vma destroy, the
outer ``gpu_vm->lock`` is held, since otherwise when iterating over
the invalidated userptr list as described in the previous section,
there is nothing keeping those userptr gpu_vmas alive.

Locking for recoverable page-fault page-table updates

Recoverable page-fault의 page-table locking

548-582

Recoverable page fault locking은 두 조건을 보장해야 합니다. 첫째, page를 system 또는 allocator에 reuse 용도로 돌려줄 때 남은 GPU mapping이 없어야 하고 모든 GPU TLB가 flush되어야 합니다. 둘째, `gpu_vma`의 unmapping과 mapping이 race하면 안 됩니다.

GPU PTE unmapping 또는 zapping은 outer lock을 잡기 어렵거나 불가능한 context에서 일어나는 경우가 많습니다. 따라서 mapping과 unmapping 양쪽에서 잡는 새 lock을 만들거나, unmapping 시 이미 잡는 lock을 mapping 때도 잡아야 합니다.

Userptr `gpu_vma`는 MMU invalidation notifier의 zapping 동안 `userptr_seqlock` write side가 잡혀 있습니다. Mapping 동안 `userptr_seqlock`과 `gpu_vm->userptr_notifier_lock`을 모두 read mode로 잡으면 zapping과 race하지 않습니다.

GEM object-backed `gpu_vma`의 zapping은 GEM object dma_resv 아래에서 수행됩니다. 해당 object를 가리키는 VMA의 page table을 populate할 때도 같은 dma_resv를 잡으면 race를 막을 수 있습니다.

Mapping 일부를 lock 없이 dma-fence 아래에서 asynchronous하게 수행한다면 zapping은 page table을 수정하기 전에 관련 lock 아래에서 그 dma-fence가 signal되기를 기다려야 합니다.

Page-table memory를 free하는 구조 변경에는 outer lock이 필요할 수 있습니다. 따라서 GPU PTE zapping은 보통 page-table 또는 page-directory entry를 0으로 만들고 TLB를 flush하는 데 집중하며, page-table memory 해제는 unbind 또는 rebind 시점으로 미룹니다.

Recoverable fault 불변식
VMA 유형·작업조건
Page reuseGPU mapping 제거 + GPU TLB flush 완료
Userptr mappinguserptr_seqlock read + userptr_notifier_lock read
Userptr zappingMMU notifier의 userptr_seqlock write
GEM mapping·zapping같은 GEM object dma_resv 유지
Async mappingZapping 전에 관련 dma-fence signal 대기

Backing page reuse와 map/unmap race를 막는 lock 조건입니다.

PTE zapping과 memory 해제 분리
관련 map/unmap serialization lock 획득필요하면 asynchronous mapping dma-fence 대기Page-table·page-directory entry를 0으로 설정GPU TLB flushBacking page를 system에 반환 가능Page-table memory 자체는 unbind·rebind 때 해제

Outer lock이 없는 invalidation path에서 수행할 최소 작업입니다.

=====================================================

There are two important things we need to ensure with locking for
recoverable page-faults:

* At the time we return pages back to the system / allocator for
  reuse, there should be no remaining GPU mappings and any GPU TLB
  must have been flushed.
* The unmapping and mapping of a gpu_vma must not race.

Since the unmapping (or zapping) of GPU ptes is typically taking place
where it is hard or even impossible to take any outer level locks we
must either introduce a new lock that is held at both mapping and
unmapping time, or look at the locks we do hold at unmapping time and
make sure that they are held also at mapping time. For userptr
gpu_vmas, the ``userptr_seqlock`` is held in write mode in the mmu
invalidation notifier where zapping happens. Hence, if the
``userptr_seqlock`` as well as the ``gpu_vm->userptr_notifier_lock``
is held in read mode during mapping, it will not race with the
zapping. For GEM object backed gpu_vmas, zapping will take place under
the GEM object's dma_resv and ensuring that the dma_resv is held also
when populating the page-tables for any gpu_vma pointing to the GEM
object, will similarly ensure we are race-free.

If any part of the mapping is performed asynchronously
under a dma-fence with these locks released, the zapping will need to
wait for that dma-fence to signal under the relevant lock before
starting to modify the page-table.

Since modifying the
page-table structure in a way that frees up page-table memory
might also require outer level locks, the zapping of GPU ptes
typically focuses only on zeroing page-table or page-directory entries
and flushing TLB, whereas freeing of page-table memory is deferred to
unbind or rebind time.